mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-13 08:04:03 +09:00
interlacing wip3
This commit is contained in:
@@ -436,6 +436,11 @@ const FRAME_SIZE = 560*448*3 // Total frame size = 752,640 bytes
|
|||||||
const RGB_BUFFER_A = sys.malloc(FRAME_SIZE)
|
const RGB_BUFFER_A = sys.malloc(FRAME_SIZE)
|
||||||
const RGB_BUFFER_B = sys.malloc(FRAME_SIZE)
|
const RGB_BUFFER_B = sys.malloc(FRAME_SIZE)
|
||||||
|
|
||||||
|
// Static Yadif deinterlacing buffers (half-height field buffers for interlaced mode)
|
||||||
|
const FIELD_SIZE = 560 * 224 * 3 // Half-height field buffer size
|
||||||
|
const TEMP_FIELD_BUFFER = sys.malloc(FIELD_SIZE)
|
||||||
|
const PREV_FIELD_BUFFER = sys.malloc(FIELD_SIZE)
|
||||||
|
|
||||||
// Ping-pong buffer pointers (swap instead of copy)
|
// Ping-pong buffer pointers (swap instead of copy)
|
||||||
let CURRENT_RGB_ADDR = RGB_BUFFER_A
|
let CURRENT_RGB_ADDR = RGB_BUFFER_A
|
||||||
let PREV_RGB_ADDR = RGB_BUFFER_B
|
let PREV_RGB_ADDR = RGB_BUFFER_B
|
||||||
@@ -444,6 +449,10 @@ let PREV_RGB_ADDR = RGB_BUFFER_B
|
|||||||
sys.memset(RGB_BUFFER_A, 0, FRAME_PIXELS * 3)
|
sys.memset(RGB_BUFFER_A, 0, FRAME_PIXELS * 3)
|
||||||
sys.memset(RGB_BUFFER_B, 0, FRAME_PIXELS * 3)
|
sys.memset(RGB_BUFFER_B, 0, FRAME_PIXELS * 3)
|
||||||
|
|
||||||
|
// Initialize Yadif field buffers to black
|
||||||
|
sys.memset(TEMP_FIELD_BUFFER, 0, FIELD_SIZE)
|
||||||
|
sys.memset(PREV_FIELD_BUFFER, 0, FIELD_SIZE)
|
||||||
|
|
||||||
// Initialize display framebuffer to black
|
// Initialize display framebuffer to black
|
||||||
sys.memset(DISPLAY_RG_ADDR, 0, FRAME_PIXELS) // Black in RG plane
|
sys.memset(DISPLAY_RG_ADDR, 0, FRAME_PIXELS) // Black in RG plane
|
||||||
sys.memset(DISPLAY_BA_ADDR, 15, FRAME_PIXELS) // Black with alpha=15 (opaque) in BA plane
|
sys.memset(DISPLAY_BA_ADDR, 15, FRAME_PIXELS) // Black with alpha=15 (opaque) in BA plane
|
||||||
@@ -504,7 +513,7 @@ function setBiasLighting() {
|
|||||||
graphics.setBackground(Math.round(bgr * 255), Math.round(bgg * 255), Math.round(bgb * 255))
|
graphics.setBackground(Math.round(bgr * 255), Math.round(bgg * 255), Math.round(bgb * 255))
|
||||||
}
|
}
|
||||||
|
|
||||||
let blockDataPtr = sys.malloc(560 * 448 * 3)
|
let blockDataPtr = sys.malloc(FRAME_SIZE)
|
||||||
|
|
||||||
// Main decoding loop - simplified for performance
|
// Main decoding loop - simplified for performance
|
||||||
try {
|
try {
|
||||||
@@ -573,7 +582,7 @@ try {
|
|||||||
// Hardware-accelerated TEV decoding to RGB buffers (YCoCg-R or XYB based on version)
|
// Hardware-accelerated TEV decoding to RGB buffers (YCoCg-R or XYB based on version)
|
||||||
try {
|
try {
|
||||||
let decodeStart = sys.nanoTime()
|
let decodeStart = sys.nanoTime()
|
||||||
graphics.tevDecode(blockDataPtr, CURRENT_RGB_ADDR, PREV_RGB_ADDR, width, height, [qualityY, qualityCo, qualityCg], frameCount, debugMotionVectors, version, isInterlaced)
|
graphics.tevDecode(blockDataPtr, CURRENT_RGB_ADDR, PREV_RGB_ADDR, width, height, [qualityY, qualityCo, qualityCg], frameCount, debugMotionVectors, version, isInterlaced, TEMP_FIELD_BUFFER, PREV_FIELD_BUFFER)
|
||||||
decodeTime = (sys.nanoTime() - decodeStart) / 1000000.0 // Convert to milliseconds
|
decodeTime = (sys.nanoTime() - decodeStart) / 1000000.0 // Convert to milliseconds
|
||||||
|
|
||||||
// Upload RGB buffer to display framebuffer with dithering
|
// Upload RGB buffer to display framebuffer with dithering
|
||||||
@@ -661,9 +670,11 @@ catch (e) {
|
|||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
// Cleanup working memory (graphics memory is automatically managed)
|
// Cleanup working memory (graphics memory is automatically managed)
|
||||||
sys.free(blockDataPtr)
|
if (blockDataPtr > 0) sys.free(blockDataPtr)
|
||||||
if (RGB_BUFFER_A > 0) sys.free(RGB_BUFFER_A)
|
if (RGB_BUFFER_A > 0) sys.free(RGB_BUFFER_A)
|
||||||
if (RGB_BUFFER_B > 0) sys.free(RGB_BUFFER_B)
|
if (RGB_BUFFER_B > 0) sys.free(RGB_BUFFER_B)
|
||||||
|
if (TEMP_FIELD_BUFFER > 0) sys.free(TEMP_FIELD_BUFFER)
|
||||||
|
if (PREV_FIELD_BUFFER > 0) sys.free(PREV_FIELD_BUFFER)
|
||||||
|
|
||||||
audio.stop(0)
|
audio.stop(0)
|
||||||
audio.purgeQueue(0)
|
audio.purgeQueue(0)
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ const COL_HL_EXT = {
|
|||||||
"ipf2": 191,
|
"ipf2": 191,
|
||||||
"txt": 223,
|
"txt": 223,
|
||||||
"md": 223,
|
"md": 223,
|
||||||
|
"log": 223
|
||||||
}
|
}
|
||||||
|
|
||||||
const EXEC_FUNS = {
|
const EXEC_FUNS = {
|
||||||
@@ -49,7 +50,9 @@ const EXEC_FUNS = {
|
|||||||
"ipf1": (f) => _G.shell.execute(`decodeipf "${f}" -i`),
|
"ipf1": (f) => _G.shell.execute(`decodeipf "${f}" -i`),
|
||||||
"ipf2": (f) => _G.shell.execute(`decodeipf "${f}" -i`),
|
"ipf2": (f) => _G.shell.execute(`decodeipf "${f}" -i`),
|
||||||
"bas": (f) => _G.shell.execute(`basic "${f}"`),
|
"bas": (f) => _G.shell.execute(`basic "${f}"`),
|
||||||
"txt": (f) => _G.shell.execute(`less "${f}"`)
|
"txt": (f) => _G.shell.execute(`less "${f}"`),
|
||||||
|
"md": (f) => _G.shell.execute(`less "${f}"`),
|
||||||
|
"log": (f) => _G.shell.execute(`less "${f}"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
let windowMode = 0 // 0 == left, 1 == right
|
let windowMode = 0 // 0 == left, 1 == right
|
||||||
|
|||||||
@@ -485,33 +485,6 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
)
|
)
|
||||||
).map{ it.map { (it.toFloat() + 0.5f) / 16f }.toFloatArray() }
|
).map{ it.map { (it.toFloat() + 0.5f) / 16f }.toFloatArray() }
|
||||||
|
|
||||||
private val bayerKernels2 = arrayOf(
|
|
||||||
intArrayOf(
|
|
||||||
0,8,2,10,
|
|
||||||
12,4,14,6,
|
|
||||||
3,11,1,9,
|
|
||||||
15,7,13,5,
|
|
||||||
),
|
|
||||||
intArrayOf(
|
|
||||||
8,2,10,0,
|
|
||||||
4,14,6,12,
|
|
||||||
11,1,9,3,
|
|
||||||
7,13,5,15,
|
|
||||||
),
|
|
||||||
intArrayOf(
|
|
||||||
7,13,5,15,
|
|
||||||
8,2,10,0,
|
|
||||||
4,14,6,12,
|
|
||||||
11,1,9,3,
|
|
||||||
),
|
|
||||||
intArrayOf(
|
|
||||||
15,7,13,5,
|
|
||||||
0,8,2,10,
|
|
||||||
12,4,14,6,
|
|
||||||
3,11,1,9,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method always assume that you're using the default palette
|
* This method always assume that you're using the default palette
|
||||||
*
|
*
|
||||||
@@ -1519,6 +1492,27 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
// Temporary buffer for interlaced field processing
|
// Temporary buffer for interlaced field processing
|
||||||
private val interlacedFieldBuffer = IntArray(560 * 224 * 3) // Half-height RGB buffer
|
private val interlacedFieldBuffer = IntArray(560 * 224 * 3) // Half-height RGB buffer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract a specific field (even or odd lines) from a progressive frame
|
||||||
|
* Used for temporal prediction in Yadif deinterlacing
|
||||||
|
*/
|
||||||
|
private fun extractFieldFromProgressive(progressiveAddr: Long, fieldAddr: Long, width: Int, height: Int,
|
||||||
|
fieldParity: Int, addrIncVec: Int) {
|
||||||
|
val fieldHeight = height / 2
|
||||||
|
for (y in 0 until fieldHeight) {
|
||||||
|
val progressiveY = y * 2 + fieldParity // Extract even (0) or odd (1) lines
|
||||||
|
val progressiveOffset = (progressiveY * width) * 3
|
||||||
|
val fieldOffset = (y * width) * 3
|
||||||
|
|
||||||
|
for (x in 0 until width) {
|
||||||
|
for (c in 0..2) {
|
||||||
|
val pixel = vm.peek(progressiveAddr + (progressiveOffset + x * 3 + c) * addrIncVec)!!
|
||||||
|
vm.poke(fieldAddr + (fieldOffset + x * 3 + c) * addrIncVec, pixel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* YADIF (Yet Another Deinterlacing Filter) implementation
|
* YADIF (Yet Another Deinterlacing Filter) implementation
|
||||||
* Converts interlaced field to progressive frame with temporal/spatial interpolation
|
* Converts interlaced field to progressive frame with temporal/spatial interpolation
|
||||||
@@ -1550,18 +1544,49 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
val below = vm.peek(fieldRGBAddr + (fieldOffset + width * 3 + c) * fieldIncVec)!!.toInt() and 0xFF
|
val below = vm.peek(fieldRGBAddr + (fieldOffset + width * 3 + c) * fieldIncVec)!!.toInt() and 0xFF
|
||||||
val current = vm.peek(fieldRGBAddr + (fieldOffset + c) * fieldIncVec)!!.toInt() and 0xFF
|
val current = vm.peek(fieldRGBAddr + (fieldOffset + c) * fieldIncVec)!!.toInt() and 0xFF
|
||||||
|
|
||||||
// Simple spatial interpolation (can be enhanced with temporal prediction)
|
// Spatial interpolation
|
||||||
val spatialInterp = (above + below) / 2
|
val spatialInterp = (above + below) / 2
|
||||||
|
|
||||||
// Apply edge-directed interpolation bias
|
// Temporal prediction using previous and next fields
|
||||||
val edgeBias = if (kotlin.math.abs(above - below) < 32) {
|
var temporalPred = spatialInterp
|
||||||
(current + spatialInterp) / 2 // Low edge activity: blend with current
|
if (prevFieldAddr != 0L && nextFieldAddr != 0L) {
|
||||||
|
// Get temporal neighbors from same spatial position
|
||||||
|
val prevPixel = (vm.peek(prevFieldAddr + (fieldOffset + c) * fieldIncVec)?.toInt() ?: current) and 0xFF
|
||||||
|
val nextPixel = (vm.peek(nextFieldAddr + (fieldOffset + c) * fieldIncVec)?.toInt() ?: current) and 0xFF
|
||||||
|
|
||||||
|
// Simple temporal interpolation
|
||||||
|
val tempInterp = (prevPixel + nextPixel) / 2
|
||||||
|
|
||||||
|
// Yadif edge-directed temporal-spatial decision
|
||||||
|
val spatialDiff = kotlin.math.abs(above - below)
|
||||||
|
val temporalDiff = kotlin.math.abs(prevPixel - nextPixel)
|
||||||
|
|
||||||
|
// Choose between spatial and temporal prediction based on local characteristics
|
||||||
|
temporalPred = when {
|
||||||
|
spatialDiff < 32 && temporalDiff < 32 -> {
|
||||||
|
// Low spatial and temporal variation: blend all
|
||||||
|
(spatialInterp + tempInterp + current) / 3
|
||||||
|
}
|
||||||
|
spatialDiff < temporalDiff -> {
|
||||||
|
// Prefer spatial interpolation
|
||||||
|
(spatialInterp * 3 + tempInterp) / 4
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// Prefer temporal interpolation
|
||||||
|
(tempInterp * 3 + spatialInterp) / 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final edge-directed filtering
|
||||||
|
val finalValue = if (kotlin.math.abs(above - below) < 16) {
|
||||||
|
(current + temporalPred) / 2 // Very low edge activity: blend with current
|
||||||
} else {
|
} else {
|
||||||
spatialInterp // High edge activity: use spatial only
|
temporalPred // Higher edge activity: use prediction
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.poke(outputRGBAddr + (interpOutputOffset + c) * outputIncVec,
|
vm.poke(outputRGBAddr + (interpOutputOffset + c) * outputIncVec,
|
||||||
edgeBias.coerceIn(0, 255).toByte())
|
finalValue.coerceIn(0, 255).toByte())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1798,7 +1823,8 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
*/
|
*/
|
||||||
fun tevDecode(blockDataPtr: Long, currentRGBAddr: Long, prevRGBAddr: Long,
|
fun tevDecode(blockDataPtr: Long, currentRGBAddr: Long, prevRGBAddr: Long,
|
||||||
width: Int, height: Int, qualityIndices: IntArray, frameCounter: Int,
|
width: Int, height: Int, qualityIndices: IntArray, frameCounter: Int,
|
||||||
debugMotionVectors: Boolean = false, tevVersion: Int = 2, isInterlaced: Boolean = false) {
|
debugMotionVectors: Boolean = false, tevVersion: Int = 2, isInterlaced: Boolean = false,
|
||||||
|
tempFieldBuffer: Long = 0L, prevFieldBuffer: Long = 0L) {
|
||||||
|
|
||||||
// height doesn't change when interlaced, because that's the encoder's output
|
// height doesn't change when interlaced, because that's the encoder's output
|
||||||
|
|
||||||
@@ -2193,21 +2219,32 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
|
|
||||||
// Apply Yadif deinterlacing if this is an interlaced frame
|
// Apply Yadif deinterlacing if this is an interlaced frame
|
||||||
if (isInterlaced) {
|
if (isInterlaced) {
|
||||||
// Decode produced a field at half-height, now deinterlace to full progressive frame
|
// Use static buffers provided by playtev.js for better performance
|
||||||
val tempFieldBuffer = vm.malloc(width * decodingHeight * 3)
|
// require(tempFieldBuffer != 0L) { "tempFieldBuffer must be provided for interlaced decoding" }
|
||||||
|
// require(prevFieldBuffer != 0L) { "prevFieldBuffer must be provided for interlaced decoding" }
|
||||||
|
|
||||||
// Copy the decoded field to temporary buffer
|
// Copy the decoded field to temporary buffer
|
||||||
vm.memcpy(currentRGBAddr.toInt(), tempFieldBuffer.toInt(), width * decodingHeight * 3)
|
vm.memcpy(currentRGBAddr.toInt(), tempFieldBuffer.toInt(), width * decodingHeight * 3)
|
||||||
|
|
||||||
// Apply Yadif deinterlacing: field -> progressive frame
|
// Apply Yadif deinterlacing: field -> progressive frame
|
||||||
|
// For temporal prediction, we need proper field management
|
||||||
|
val fieldParity = frameCounter % 2
|
||||||
|
val prevFieldAddr = if (prevRGBAddr != 0L) {
|
||||||
|
// Extract the corresponding field from the previous progressive frame
|
||||||
|
// Even field lines: y = 0, 2, 4, 6...
|
||||||
|
// Odd field lines: y = 1, 3, 5, 7...
|
||||||
|
extractFieldFromProgressive(prevRGBAddr, prevFieldBuffer, width, height, fieldParity, thisAddrIncVec)
|
||||||
|
prevFieldBuffer
|
||||||
|
} else {
|
||||||
|
0L
|
||||||
|
}
|
||||||
|
|
||||||
yadifDeinterlace(
|
yadifDeinterlace(
|
||||||
tempFieldBuffer.toLong(), currentRGBAddr, width, height,
|
tempFieldBuffer, currentRGBAddr, width, height,
|
||||||
prevRGBAddr, prevRGBAddr, // TODO: Implement proper temporal prediction
|
prevFieldAddr, 0L, // Use previous field, no next field available
|
||||||
frameCounter % 2, // Field parity (0=even field first)
|
fieldParity,
|
||||||
thisAddrIncVec, thisAddrIncVec
|
thisAddrIncVec, thisAddrIncVec
|
||||||
)
|
)
|
||||||
|
|
||||||
vm.free(tempFieldBuffer.toInt())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1850,11 +1850,11 @@ void main() {
|
|||||||
|
|
||||||
val DEFAULT_CONFIG_COLOR_CRT = AdapterConfig(
|
val DEFAULT_CONFIG_COLOR_CRT = AdapterConfig(
|
||||||
"crt_color",
|
"crt_color",
|
||||||
560, 448, 80, 32, 253, 255, 256.kB(), "", 0.32f, TEXT_TILING_SHADER_COLOUR
|
560, 448, 80, 32, 253, 255, 256.kB(), "", 0.62f, TEXT_TILING_SHADER_COLOUR
|
||||||
)
|
)
|
||||||
val DEFAULT_CONFIG_PMLCD = AdapterConfig(
|
val DEFAULT_CONFIG_PMLCD = AdapterConfig(
|
||||||
"pmlcd_inverted",
|
"pmlcd_inverted",
|
||||||
560, 448, 80, 32, 253, 255, 256.kB(), "", 0.64f, TEXT_TILING_SHADER_LCD, DRAW_SHADER_FRAG_LCD
|
560, 448, 80, 32, 253, 255, 256.kB(), "", 0.88f, TEXT_TILING_SHADER_LCD, DRAW_SHADER_FRAG_LCD
|
||||||
)
|
)
|
||||||
|
|
||||||
val DEFAULT_CONFIG_FOR_TESTING = AdapterConfig(
|
val DEFAULT_CONFIG_FOR_TESTING = AdapterConfig(
|
||||||
|
|||||||
@@ -1814,14 +1814,14 @@ static int start_video_conversion(tev_encoder_t *enc) {
|
|||||||
// Frame rate conversion requested
|
// Frame rate conversion requested
|
||||||
snprintf(command, sizeof(command),
|
snprintf(command, sizeof(command),
|
||||||
"ffmpeg -v error -i \"%s\" -f rawvideo -pix_fmt rgb24 "
|
"ffmpeg -v error -i \"%s\" -f rawvideo -pix_fmt rgb24 "
|
||||||
"-vf \"fps=%d,scale=%d:%d:force_original_aspect_ratio=increase,crop=%d:%d,tinterlace=interleave_top,separatefields\" "
|
"-vf \"fps=%d,scale=%d:%d:force_original_aspect_ratio=increase,crop=%d:%d,tinterlace=interleave_top:cvlpf,separatefields\" "
|
||||||
"-y - 2>&1",
|
"-y - 2>&1",
|
||||||
enc->input_file, enc->output_fps, enc->width, enc->height * 2, enc->width, enc->height * 2);
|
enc->input_file, enc->output_fps, enc->width, enc->height * 2, enc->width, enc->height * 2);
|
||||||
} else {
|
} else {
|
||||||
// No frame rate conversion
|
// No frame rate conversion
|
||||||
snprintf(command, sizeof(command),
|
snprintf(command, sizeof(command),
|
||||||
"ffmpeg -v error -i \"%s\" -f rawvideo -pix_fmt rgb24 "
|
"ffmpeg -v error -i \"%s\" -f rawvideo -pix_fmt rgb24 "
|
||||||
"-vf \"scale=%d:%d:force_original_aspect_ratio=increase,crop=%d:%d,tinterlace=interleave_top,separatefields\" "
|
"-vf \"scale=%d:%d:force_original_aspect_ratio=increase,crop=%d:%d,tinterlace=interleave_top:cvlpf,separatefields\" "
|
||||||
"-y -",
|
"-y -",
|
||||||
enc->input_file, enc->width, enc->height * 2, enc->width, enc->height * 2);
|
enc->input_file, enc->width, enc->height * 2, enc->width, enc->height * 2);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user