mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 11:51:49 +09:00
TAV: interlaced mode
This commit is contained in:
@@ -305,6 +305,13 @@ console.log(`Channel layout: ${getChannelLayoutName(header.channelLayout)}`)
|
||||
console.log(`Tiles: ${tilesX}x${tilesY} (${numTiles} total)`)
|
||||
console.log(`Colour space: ${header.version % 2 == 0 ? "ICtCp" : "YCoCg-R"}`)
|
||||
console.log(`Features: ${hasAudio ? "Audio " : ""}${hasSubtitles ? "Subtitles " : ""}${progressiveTransmission ? "Progressive " : ""}${roiCoding ? "ROI " : ""}`)
|
||||
console.log(`Video flags raw: 0x${header.videoFlags.toString(16)}`)
|
||||
console.log(`Scan type: ${isInterlaced ? "Interlaced" : "Progressive"}`)
|
||||
|
||||
// Adjust decode height for interlaced content
|
||||
// For interlaced: header.height is display height (448)
|
||||
// Each field is half of display height (448/2 = 224)
|
||||
let decodeHeight = isInterlaced ? (header.height >> 1) : header.height
|
||||
|
||||
// Frame buffer addresses - same as TEV
|
||||
const FRAME_PIXELS = header.width * header.height
|
||||
@@ -313,10 +320,28 @@ const FRAME_SIZE = FRAME_PIXELS * 3 // RGB buffer size
|
||||
const RGB_BUFFER_A = sys.malloc(FRAME_SIZE)
|
||||
const RGB_BUFFER_B = sys.malloc(FRAME_SIZE)
|
||||
|
||||
// Field buffers for interlaced mode (half-height fields)
|
||||
const FIELD_SIZE = header.width * decodeHeight * 3
|
||||
const CURR_FIELD_BUFFER = isInterlaced ? sys.malloc(FIELD_SIZE) : 0
|
||||
const PREV_FIELD_BUFFER = isInterlaced ? sys.malloc(FIELD_SIZE) : 0
|
||||
const NEXT_FIELD_BUFFER = isInterlaced ? sys.malloc(FIELD_SIZE) : 0
|
||||
|
||||
// Ping-pong buffer pointers (swap instead of copy)
|
||||
let CURRENT_RGB_ADDR = RGB_BUFFER_A
|
||||
let PREV_RGB_ADDR = RGB_BUFFER_B
|
||||
|
||||
// Initialize field buffers to black for interlaced mode
|
||||
if (isInterlaced) {
|
||||
sys.memset(CURR_FIELD_BUFFER, 0, FIELD_SIZE)
|
||||
sys.memset(PREV_FIELD_BUFFER, 0, FIELD_SIZE)
|
||||
sys.memset(NEXT_FIELD_BUFFER, 0, FIELD_SIZE)
|
||||
}
|
||||
|
||||
// Field buffer pointers for temporal deinterlacing
|
||||
let prevFieldAddr = PREV_FIELD_BUFFER
|
||||
let currentFieldAddr = CURR_FIELD_BUFFER
|
||||
let nextFieldAddr = NEXT_FIELD_BUFFER
|
||||
|
||||
// Audio state
|
||||
let audioBufferBytesLastFrame = 0
|
||||
let frame_cnt = 0
|
||||
@@ -631,13 +656,26 @@ try {
|
||||
try {
|
||||
let decodeStart = sys.nanoTime()
|
||||
|
||||
// For interlaced mode, decode to field buffer at half height
|
||||
let decodeTarget = isInterlaced ? currentFieldAddr : CURRENT_RGB_ADDR
|
||||
|
||||
// Debug interlaced mode
|
||||
if (frameCount === 0 && isInterlaced) {
|
||||
serial.println(`[DEBUG] Interlaced mode active:`)
|
||||
serial.println(` decodeHeight: ${decodeHeight}`)
|
||||
serial.println(` currentFieldAddr: ${currentFieldAddr}`)
|
||||
serial.println(` prevFieldAddr: ${prevFieldAddr}`)
|
||||
serial.println(` nextFieldAddr: ${nextFieldAddr}`)
|
||||
serial.println(` FIELD_SIZE: ${FIELD_SIZE}`)
|
||||
}
|
||||
|
||||
// Call new TAV hardware decoder that handles Zstd decompression internally
|
||||
// Note: No longer using JS gzip.decompFromTo - Kotlin handles Zstd natively
|
||||
decoderDbgInfo = graphics.tavDecodeCompressed(
|
||||
compressedPtr, // Pass compressed data directly
|
||||
compressedSize, // Size of compressed data
|
||||
CURRENT_RGB_ADDR, PREV_RGB_ADDR, // RGB buffer pointers
|
||||
header.width, header.height,
|
||||
decodeTarget, PREV_RGB_ADDR, // RGB buffer pointers (field buffer for interlaced)
|
||||
header.width, decodeHeight, // Use half height for interlaced
|
||||
header.qualityLevel, QLUT[header.qualityY], QLUT[header.qualityCo], QLUT[header.qualityCg],
|
||||
header.channelLayout, // Channel layout for variable processing
|
||||
trueFrameCount,
|
||||
@@ -650,8 +688,38 @@ try {
|
||||
decodeTime = (sys.nanoTime() - decodeStart) / 1000000.0
|
||||
decompressTime = 0 // Decompression time now included in decode time
|
||||
|
||||
// Upload RGB buffer to display framebuffer (like TEV)
|
||||
// For interlaced: deinterlace fields into full frame, otherwise upload directly
|
||||
let uploadStart = sys.nanoTime()
|
||||
if (isInterlaced) {
|
||||
if (frameCount === 0) {
|
||||
serial.println(`[DEBUG] Calling tavDeinterlace for first frame`)
|
||||
}
|
||||
// Weave fields using temporal deinterlacing (yadif algorithm)
|
||||
try {
|
||||
graphics.tavDeinterlace(trueFrameCount, header.width, decodeHeight,
|
||||
prevFieldAddr, currentFieldAddr, nextFieldAddr,
|
||||
CURRENT_RGB_ADDR, "yadif")
|
||||
if (frameCount === 0) {
|
||||
serial.println(`[DEBUG] tavDeinterlace succeeded`)
|
||||
}
|
||||
} catch (deinterlaceError) {
|
||||
serial.printerr(`[ERROR] tavDeinterlace failed: ${deinterlaceError}`)
|
||||
serial.printerr(` frame: ${trueFrameCount}, width: ${header.width}, height: ${decodeHeight}`)
|
||||
serial.printerr(` prevField: ${prevFieldAddr}, currField: ${currentFieldAddr}, nextField: ${nextFieldAddr}`)
|
||||
throw deinterlaceError
|
||||
}
|
||||
|
||||
// Rotate field buffers for next frame: NEXT -> CURRENT -> PREV
|
||||
let tempField = prevFieldAddr
|
||||
prevFieldAddr = currentFieldAddr
|
||||
currentFieldAddr = nextFieldAddr
|
||||
nextFieldAddr = tempField
|
||||
} else {
|
||||
if (frameCount === 0) {
|
||||
serial.println(`[DEBUG] Progressive mode - no deinterlacing`)
|
||||
}
|
||||
}
|
||||
|
||||
graphics.uploadRGBToFramebuffer(CURRENT_RGB_ADDR, header.width, header.height, trueFrameCount, false)
|
||||
uploadTime = (sys.nanoTime() - uploadStart) / 1000000.0
|
||||
|
||||
@@ -739,7 +807,7 @@ try {
|
||||
akku: akku2,
|
||||
fileName: fullFilePathStr,
|
||||
fileOrd: currentFileIndex,
|
||||
resolution: `${header.width}x${header.height}`,
|
||||
resolution: `${header.width}x${header.height}${(isInterlaced) ? 'i' : ''}`,
|
||||
colourSpace: header.version % 2 == 0 ? "ICtCp" : "YCoCg",
|
||||
currentStatus: 1
|
||||
}
|
||||
@@ -759,6 +827,13 @@ finally {
|
||||
sys.free(RGB_BUFFER_A)
|
||||
sys.free(RGB_BUFFER_B)
|
||||
|
||||
// Free field buffers if interlaced
|
||||
if (isInterlaced) {
|
||||
sys.free(CURR_FIELD_BUFFER)
|
||||
sys.free(PREV_FIELD_BUFFER)
|
||||
sys.free(NEXT_FIELD_BUFFER)
|
||||
}
|
||||
|
||||
con.curs_set(1)
|
||||
con.clear()
|
||||
|
||||
|
||||
@@ -3139,6 +3139,11 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
||||
}
|
||||
}
|
||||
|
||||
fun tavDeinterlace(frameCount: Int, width: Int, height: Int, prevField: Long, currentField: Long, nextField: Long, outputRGB: Long, algorithm: String = "yadif") {
|
||||
// TAV deinterlacing - same logic as TEV
|
||||
tevDeinterlace(frameCount, width, height, prevField, currentField, nextField, outputRGB, algorithm)
|
||||
}
|
||||
|
||||
// Helper functions for motion compensation and block handling in two-pass mode
|
||||
private fun tevHandleSkipBlockTwoPass(startX: Int, startY: Int, currentRGBAddr: Long, prevRGBAddr: Long,
|
||||
width: Int, height: Int, thisAddrIncVec: Int, prevAddrIncVec: Int) {
|
||||
|
||||
@@ -2370,6 +2370,7 @@ static int write_tav_header(tav_encoder_t *enc) {
|
||||
fputc(version, enc->output_fp);
|
||||
|
||||
// Video parameters
|
||||
// For interlaced: enc->height is already halved internally, so double it back for display height
|
||||
uint16_t height = enc->progressive_mode ? enc->height : enc->height * 2;
|
||||
fwrite(&enc->width, sizeof(uint16_t), 1, enc->output_fp);
|
||||
fwrite(&height, sizeof(uint16_t), 1, enc->output_fp);
|
||||
@@ -3367,6 +3368,7 @@ int main(int argc, char *argv[]) {
|
||||
{"input", required_argument, 0, 'i'},
|
||||
{"output", required_argument, 0, 'o'},
|
||||
{"size", required_argument, 0, 's'},
|
||||
{"dimension", required_argument, 0, 's'},
|
||||
{"fps", required_argument, 0, 'f'},
|
||||
{"quality", required_argument, 0, 'q'},
|
||||
{"quantizer", required_argument, 0, 'Q'},
|
||||
@@ -3381,13 +3383,17 @@ int main(int argc, char *argv[]) {
|
||||
{"test", no_argument, 0, 't'},
|
||||
{"lossless", no_argument, 0, 1000},
|
||||
{"intra-only", no_argument, 0, 1006},
|
||||
{"intraonly", no_argument, 0, 1006},
|
||||
{"ictcp", no_argument, 0, 1005},
|
||||
{"no-perceptual-tuning", no_argument, 0, 1007},
|
||||
{"no-dead-zone", no_argument, 0, 1013},
|
||||
{"no-deadzone", no_argument, 0, 1013},
|
||||
{"encode-limit", required_argument, 0, 1008},
|
||||
{"dump-frame", required_argument, 0, 1009},
|
||||
{"fontrom-lo", required_argument, 0, 1011},
|
||||
{"fontrom-low", required_argument, 0, 1011},
|
||||
{"fontrom-hi", required_argument, 0, 1012},
|
||||
{"fontrom-high", required_argument, 0, 1012},
|
||||
{"zstd-level", required_argument, 0, 1014},
|
||||
{"interlace", no_argument, 0, 1015},
|
||||
{"interlaced", no_argument, 0, 1015},
|
||||
@@ -3396,7 +3402,7 @@ int main(int argc, char *argv[]) {
|
||||
};
|
||||
|
||||
int c, option_index = 0;
|
||||
while ((c = getopt_long(argc, argv, "i:o:s:f:q:Q:a:w:c:d:b:pS:vt", long_options, &option_index)) != -1) {
|
||||
while ((c = getopt_long(argc, argv, "i:o:s:f:q:Q:a:w:c:d:b:S:vt?", long_options, &option_index)) != -1) {
|
||||
switch (c) {
|
||||
case 'i':
|
||||
enc->input_file = strdup(optarg);
|
||||
@@ -3570,6 +3576,15 @@ int main(int argc, char *argv[]) {
|
||||
enc->quantiser_cg = enc->quantiser_co;
|
||||
}
|
||||
|
||||
// Halve internal height for interlaced mode (FFmpeg will output half-height fields)
|
||||
if (!enc->progressive_mode) {
|
||||
enc->height = enc->height / 2;
|
||||
if (enc->verbose) {
|
||||
printf("Interlaced mode: internal height adjusted to %d\n", enc->height);
|
||||
}
|
||||
enc->intra_only = 1;
|
||||
}
|
||||
|
||||
// disable perceptual tuning if wavelet filter is not CDF 9/7
|
||||
if (enc->wavelet_filter != WAVELET_9_7_IRREVERSIBLE) {
|
||||
enc->perceptual_tuning = 0;
|
||||
|
||||
Reference in New Issue
Block a user