mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-12 23:54:04 +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(`Tiles: ${tilesX}x${tilesY} (${numTiles} total)`)
|
||||||
console.log(`Colour space: ${header.version % 2 == 0 ? "ICtCp" : "YCoCg-R"}`)
|
console.log(`Colour space: ${header.version % 2 == 0 ? "ICtCp" : "YCoCg-R"}`)
|
||||||
console.log(`Features: ${hasAudio ? "Audio " : ""}${hasSubtitles ? "Subtitles " : ""}${progressiveTransmission ? "Progressive " : ""}${roiCoding ? "ROI " : ""}`)
|
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
|
// Frame buffer addresses - same as TEV
|
||||||
const FRAME_PIXELS = header.width * header.height
|
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_A = sys.malloc(FRAME_SIZE)
|
||||||
const RGB_BUFFER_B = 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)
|
// 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
|
||||||
|
|
||||||
|
// 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
|
// Audio state
|
||||||
let audioBufferBytesLastFrame = 0
|
let audioBufferBytesLastFrame = 0
|
||||||
let frame_cnt = 0
|
let frame_cnt = 0
|
||||||
@@ -631,13 +656,26 @@ try {
|
|||||||
try {
|
try {
|
||||||
let decodeStart = sys.nanoTime()
|
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
|
// Call new TAV hardware decoder that handles Zstd decompression internally
|
||||||
// Note: No longer using JS gzip.decompFromTo - Kotlin handles Zstd natively
|
// Note: No longer using JS gzip.decompFromTo - Kotlin handles Zstd natively
|
||||||
decoderDbgInfo = graphics.tavDecodeCompressed(
|
decoderDbgInfo = graphics.tavDecodeCompressed(
|
||||||
compressedPtr, // Pass compressed data directly
|
compressedPtr, // Pass compressed data directly
|
||||||
compressedSize, // Size of compressed data
|
compressedSize, // Size of compressed data
|
||||||
CURRENT_RGB_ADDR, PREV_RGB_ADDR, // RGB buffer pointers
|
decodeTarget, PREV_RGB_ADDR, // RGB buffer pointers (field buffer for interlaced)
|
||||||
header.width, header.height,
|
header.width, decodeHeight, // Use half height for interlaced
|
||||||
header.qualityLevel, QLUT[header.qualityY], QLUT[header.qualityCo], QLUT[header.qualityCg],
|
header.qualityLevel, QLUT[header.qualityY], QLUT[header.qualityCo], QLUT[header.qualityCg],
|
||||||
header.channelLayout, // Channel layout for variable processing
|
header.channelLayout, // Channel layout for variable processing
|
||||||
trueFrameCount,
|
trueFrameCount,
|
||||||
@@ -650,8 +688,38 @@ try {
|
|||||||
decodeTime = (sys.nanoTime() - decodeStart) / 1000000.0
|
decodeTime = (sys.nanoTime() - decodeStart) / 1000000.0
|
||||||
decompressTime = 0 // Decompression time now included in decode time
|
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()
|
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)
|
graphics.uploadRGBToFramebuffer(CURRENT_RGB_ADDR, header.width, header.height, trueFrameCount, false)
|
||||||
uploadTime = (sys.nanoTime() - uploadStart) / 1000000.0
|
uploadTime = (sys.nanoTime() - uploadStart) / 1000000.0
|
||||||
|
|
||||||
@@ -739,7 +807,7 @@ try {
|
|||||||
akku: akku2,
|
akku: akku2,
|
||||||
fileName: fullFilePathStr,
|
fileName: fullFilePathStr,
|
||||||
fileOrd: currentFileIndex,
|
fileOrd: currentFileIndex,
|
||||||
resolution: `${header.width}x${header.height}`,
|
resolution: `${header.width}x${header.height}${(isInterlaced) ? 'i' : ''}`,
|
||||||
colourSpace: header.version % 2 == 0 ? "ICtCp" : "YCoCg",
|
colourSpace: header.version % 2 == 0 ? "ICtCp" : "YCoCg",
|
||||||
currentStatus: 1
|
currentStatus: 1
|
||||||
}
|
}
|
||||||
@@ -759,6 +827,13 @@ finally {
|
|||||||
sys.free(RGB_BUFFER_A)
|
sys.free(RGB_BUFFER_A)
|
||||||
sys.free(RGB_BUFFER_B)
|
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.curs_set(1)
|
||||||
con.clear()
|
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
|
// Helper functions for motion compensation and block handling in two-pass mode
|
||||||
private fun tevHandleSkipBlockTwoPass(startX: Int, startY: Int, currentRGBAddr: Long, prevRGBAddr: Long,
|
private fun tevHandleSkipBlockTwoPass(startX: Int, startY: Int, currentRGBAddr: Long, prevRGBAddr: Long,
|
||||||
width: Int, height: Int, thisAddrIncVec: Int, prevAddrIncVec: Int) {
|
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);
|
fputc(version, enc->output_fp);
|
||||||
|
|
||||||
// Video parameters
|
// 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;
|
uint16_t height = enc->progressive_mode ? enc->height : enc->height * 2;
|
||||||
fwrite(&enc->width, sizeof(uint16_t), 1, enc->output_fp);
|
fwrite(&enc->width, sizeof(uint16_t), 1, enc->output_fp);
|
||||||
fwrite(&height, 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'},
|
{"input", required_argument, 0, 'i'},
|
||||||
{"output", required_argument, 0, 'o'},
|
{"output", required_argument, 0, 'o'},
|
||||||
{"size", required_argument, 0, 's'},
|
{"size", required_argument, 0, 's'},
|
||||||
|
{"dimension", required_argument, 0, 's'},
|
||||||
{"fps", required_argument, 0, 'f'},
|
{"fps", required_argument, 0, 'f'},
|
||||||
{"quality", required_argument, 0, 'q'},
|
{"quality", required_argument, 0, 'q'},
|
||||||
{"quantizer", required_argument, 0, 'Q'},
|
{"quantizer", required_argument, 0, 'Q'},
|
||||||
@@ -3381,13 +3383,17 @@ int main(int argc, char *argv[]) {
|
|||||||
{"test", no_argument, 0, 't'},
|
{"test", no_argument, 0, 't'},
|
||||||
{"lossless", no_argument, 0, 1000},
|
{"lossless", no_argument, 0, 1000},
|
||||||
{"intra-only", no_argument, 0, 1006},
|
{"intra-only", no_argument, 0, 1006},
|
||||||
|
{"intraonly", no_argument, 0, 1006},
|
||||||
{"ictcp", no_argument, 0, 1005},
|
{"ictcp", no_argument, 0, 1005},
|
||||||
{"no-perceptual-tuning", no_argument, 0, 1007},
|
{"no-perceptual-tuning", no_argument, 0, 1007},
|
||||||
{"no-dead-zone", no_argument, 0, 1013},
|
{"no-dead-zone", no_argument, 0, 1013},
|
||||||
|
{"no-deadzone", no_argument, 0, 1013},
|
||||||
{"encode-limit", required_argument, 0, 1008},
|
{"encode-limit", required_argument, 0, 1008},
|
||||||
{"dump-frame", required_argument, 0, 1009},
|
{"dump-frame", required_argument, 0, 1009},
|
||||||
{"fontrom-lo", required_argument, 0, 1011},
|
{"fontrom-lo", required_argument, 0, 1011},
|
||||||
|
{"fontrom-low", required_argument, 0, 1011},
|
||||||
{"fontrom-hi", required_argument, 0, 1012},
|
{"fontrom-hi", required_argument, 0, 1012},
|
||||||
|
{"fontrom-high", required_argument, 0, 1012},
|
||||||
{"zstd-level", required_argument, 0, 1014},
|
{"zstd-level", required_argument, 0, 1014},
|
||||||
{"interlace", no_argument, 0, 1015},
|
{"interlace", no_argument, 0, 1015},
|
||||||
{"interlaced", no_argument, 0, 1015},
|
{"interlaced", no_argument, 0, 1015},
|
||||||
@@ -3396,7 +3402,7 @@ int main(int argc, char *argv[]) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
int c, option_index = 0;
|
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) {
|
switch (c) {
|
||||||
case 'i':
|
case 'i':
|
||||||
enc->input_file = strdup(optarg);
|
enc->input_file = strdup(optarg);
|
||||||
@@ -3570,6 +3576,15 @@ int main(int argc, char *argv[]) {
|
|||||||
enc->quantiser_cg = enc->quantiser_co;
|
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
|
// disable perceptual tuning if wavelet filter is not CDF 9/7
|
||||||
if (enc->wavelet_filter != WAVELET_9_7_IRREVERSIBLE) {
|
if (enc->wavelet_filter != WAVELET_9_7_IRREVERSIBLE) {
|
||||||
enc->perceptual_tuning = 0;
|
enc->perceptual_tuning = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user