mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-11 15:24:05 +09:00
TAV: now writes encoder q-value to the header
This commit is contained in:
@@ -375,7 +375,8 @@ let header = {
|
|||||||
qualityCg: 0, // TAV-specific: Cg channel quality
|
qualityCg: 0, // TAV-specific: Cg channel quality
|
||||||
extraFlags: 0,
|
extraFlags: 0,
|
||||||
videoFlags: 0,
|
videoFlags: 0,
|
||||||
reserved: new Array(7)
|
qualityLevel: 0,
|
||||||
|
fileRole: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read and validate header
|
// Read and validate header
|
||||||
@@ -410,11 +411,11 @@ header.qualityCo = seqread.readOneByte()
|
|||||||
header.qualityCg = seqread.readOneByte()
|
header.qualityCg = seqread.readOneByte()
|
||||||
header.extraFlags = seqread.readOneByte()
|
header.extraFlags = seqread.readOneByte()
|
||||||
header.videoFlags = seqread.readOneByte()
|
header.videoFlags = seqread.readOneByte()
|
||||||
|
header.qualityLevel = seqread.readOneByte() // the decoder expects biased value
|
||||||
|
header.fileRole = seqread.readOneByte()
|
||||||
|
|
||||||
// Skip reserved bytes
|
// Skip reserved bytes
|
||||||
for (let i = 0; i < 7; i++) {
|
seqread.skip(5)
|
||||||
seqread.readOneByte()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (header.version < 1 || header.version > 6) {
|
if (header.version < 1 || header.version > 6) {
|
||||||
printerrln(`Error: Unsupported TAV version ${header.version}`)
|
printerrln(`Error: Unsupported TAV version ${header.version}`)
|
||||||
@@ -710,7 +711,7 @@ try {
|
|||||||
blockDataPtr,
|
blockDataPtr,
|
||||||
CURRENT_RGB_ADDR, PREV_RGB_ADDR, // RGB buffer pointers (not float arrays!)
|
CURRENT_RGB_ADDR, PREV_RGB_ADDR, // RGB buffer pointers (not float arrays!)
|
||||||
header.width, header.height,
|
header.width, header.height,
|
||||||
header.qualityY, header.qualityCo, header.qualityCg,
|
header.qualityLevel, header.qualityY, header.qualityCo, header.qualityCg,
|
||||||
frameCount,
|
frameCount,
|
||||||
header.waveletFilter, // TAV-specific parameter
|
header.waveletFilter, // TAV-specific parameter
|
||||||
header.decompLevels, // TAV-specific parameter
|
header.decompLevels, // TAV-specific parameter
|
||||||
|
|||||||
@@ -915,12 +915,13 @@ transmission capability, and region-of-interest coding.
|
|||||||
- bit 1 = is NTSC framerate
|
- bit 1 = is NTSC framerate
|
||||||
- bit 2 = is lossless mode
|
- bit 2 = is lossless mode
|
||||||
- bit 3 = has region-of-interest coding (for still images only)
|
- bit 3 = has region-of-interest coding (for still images only)
|
||||||
|
uint8 Encoder quality level (stored with bias of 1 (q0=1); used to derive anisotropy value)
|
||||||
uint8 File Role
|
uint8 File Role
|
||||||
- 0 = generic
|
- 0 = generic
|
||||||
- 1 = this file is header-only, and UCF payload will be followed (used by seekable movie file)
|
- 1 = this file is header-only, and UCF payload will be followed (used by seekable movie file)
|
||||||
When header-only file contain video packets, they should be presented as an Intro Movie
|
When header-only file contain video packets, they should be presented as an Intro Movie
|
||||||
before the user-interactable selector (served by the UCF payoad)
|
before the user-interactable selector (served by the UCF payoad)
|
||||||
uint8 Reserved[6]: fill with zeros
|
uint8 Reserved[5]: fill with zeros
|
||||||
|
|
||||||
## Packet Types
|
## Packet Types
|
||||||
0x10: I-frame (intra-coded frame)
|
0x10: I-frame (intra-coded frame)
|
||||||
|
|||||||
@@ -4128,17 +4128,22 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
private val FOUR_PIXEL_DETAILER = 0.88f
|
private val FOUR_PIXEL_DETAILER = 0.88f
|
||||||
private val TWO_PIXEL_DETAILER = 0.92f
|
private val TWO_PIXEL_DETAILER = 0.92f
|
||||||
|
|
||||||
|
private fun tavDeriveEncoderQindex(qIndex: Int, qYGlobal: Int): Int {
|
||||||
|
if (qIndex > 0) return qIndex - 1
|
||||||
|
return if (qYGlobal >= 60) 0
|
||||||
|
else if (qYGlobal >= 42) 1
|
||||||
|
else if (qYGlobal >= 25) 2
|
||||||
|
else if (qYGlobal >= 12) 3
|
||||||
|
else if (qYGlobal >= 6) 4
|
||||||
|
else if (qYGlobal >= 2) 5
|
||||||
|
else 5
|
||||||
|
}
|
||||||
|
|
||||||
// level is one-based index
|
// level is one-based index
|
||||||
private fun getPerceptualWeight(qYGlobal: Int, level: Int, subbandType: Int, isChroma: Boolean, maxLevels: Int): Float {
|
private fun getPerceptualWeight(qIndex: Int, qYGlobal: Int, level: Int, subbandType: Int, isChroma: Boolean, maxLevels: Int): Float {
|
||||||
// Psychovisual model based on DWT coefficient statistics and Human Visual System sensitivity
|
// Psychovisual model based on DWT coefficient statistics and Human Visual System sensitivity
|
||||||
|
|
||||||
val qualityLevel = if (qYGlobal >= 60) 0
|
val qualityLevel = tavDeriveEncoderQindex(qIndex, qYGlobal)
|
||||||
else if (qYGlobal >= 42) 1
|
|
||||||
else if (qYGlobal >= 25) 2
|
|
||||||
else if (qYGlobal >= 12) 3
|
|
||||||
else if (qYGlobal >= 6) 4
|
|
||||||
else if (qYGlobal >= 2) 5
|
|
||||||
else 5
|
|
||||||
|
|
||||||
if (!isChroma) {
|
if (!isChroma) {
|
||||||
// LUMA CHANNEL: Based on statistical analysis from real video content
|
// LUMA CHANNEL: Based on statistical analysis from real video content
|
||||||
@@ -4192,7 +4197,7 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
return "min=$min, Q1=$q1, med=%.1f, Q3=$q3, max=$max, n=$n".format(median)
|
return "min=$min, Q1=$q1, med=%.1f, Q3=$q3, max=$max, n=$n".format(median)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun dequantiseDWTSubbandsPerceptual(qYGlobal: Int, quantised: ShortArray, dequantised: FloatArray,
|
private fun dequantiseDWTSubbandsPerceptual(qIndex: Int, qYGlobal: Int, quantised: ShortArray, dequantised: FloatArray,
|
||||||
subbands: List<DWTSubbandInfo>, baseQuantizer: Float, isChroma: Boolean, decompLevels: Int) {
|
subbands: List<DWTSubbandInfo>, baseQuantizer: Float, isChroma: Boolean, decompLevels: Int) {
|
||||||
|
|
||||||
// Initialise output array to zero (critical for detecting missing coefficients)
|
// Initialise output array to zero (critical for detecting missing coefficients)
|
||||||
@@ -4205,7 +4210,7 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
var maxIdx = -1
|
var maxIdx = -1
|
||||||
|
|
||||||
for (subband in subbands) {
|
for (subband in subbands) {
|
||||||
val weight = getPerceptualWeight(qYGlobal, subband.level, subband.subbandType, isChroma, decompLevels)
|
val weight = getPerceptualWeight(qIndex, qYGlobal, subband.level, subband.subbandType, isChroma, decompLevels)
|
||||||
// CRITICAL FIX: Use the same effective quantizer as encoder for proper reconstruction
|
// CRITICAL FIX: Use the same effective quantizer as encoder for proper reconstruction
|
||||||
val effectiveQuantizer = baseQuantizer * weight
|
val effectiveQuantizer = baseQuantizer * weight
|
||||||
|
|
||||||
@@ -4266,7 +4271,7 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
private var tavDebugCurrentFrameNumber = 0
|
private var tavDebugCurrentFrameNumber = 0
|
||||||
|
|
||||||
fun tavDecode(blockDataPtr: Long, currentRGBAddr: Long, prevRGBAddr: Long,
|
fun tavDecode(blockDataPtr: Long, currentRGBAddr: Long, prevRGBAddr: Long,
|
||||||
width: Int, height: Int, qYGlobal: Int, qCoGlobal: Int, qCgGlobal: Int, frameCount: Int,
|
width: Int, height: Int, qIndex: Int, qYGlobal: Int, qCoGlobal: Int, qCgGlobal: Int, frameCount: Int,
|
||||||
waveletFilter: Int = 1, decompLevels: Int = 6, isLossless: Boolean = false, tavVersion: Int = 1) {
|
waveletFilter: Int = 1, decompLevels: Int = 6, isLossless: Boolean = false, tavVersion: Int = 1) {
|
||||||
|
|
||||||
tavDebugCurrentFrameNumber = frameCount
|
tavDebugCurrentFrameNumber = frameCount
|
||||||
@@ -4314,13 +4319,13 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
}
|
}
|
||||||
0x01 -> { // TAV_MODE_INTRA
|
0x01 -> { // TAV_MODE_INTRA
|
||||||
// Decode DWT coefficients directly to RGB buffer
|
// Decode DWT coefficients directly to RGB buffer
|
||||||
readPtr = tavDecodeDWTIntraTileRGB(qYGlobal, readPtr, tileX, tileY, currentRGBAddr,
|
readPtr = tavDecodeDWTIntraTileRGB(qIndex, qYGlobal, readPtr, tileX, tileY, currentRGBAddr,
|
||||||
width, height, qY, qCo, qCg,
|
width, height, qY, qCo, qCg,
|
||||||
waveletFilter, decompLevels, isLossless, tavVersion, isMonoblock)
|
waveletFilter, decompLevels, isLossless, tavVersion, isMonoblock)
|
||||||
}
|
}
|
||||||
0x02 -> { // TAV_MODE_DELTA
|
0x02 -> { // TAV_MODE_DELTA
|
||||||
// Coefficient delta encoding for efficient P-frames
|
// Coefficient delta encoding for efficient P-frames
|
||||||
readPtr = tavDecodeDeltaTileRGB(qYGlobal, readPtr, tileX, tileY, currentRGBAddr,
|
readPtr = tavDecodeDeltaTileRGB(readPtr, tileX, tileY, currentRGBAddr,
|
||||||
width, height, qY, qCo, qCg,
|
width, height, qY, qCo, qCg,
|
||||||
waveletFilter, decompLevels, isLossless, tavVersion, isMonoblock)
|
waveletFilter, decompLevels, isLossless, tavVersion, isMonoblock)
|
||||||
}
|
}
|
||||||
@@ -4333,7 +4338,7 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tavDecodeDWTIntraTileRGB(qYGlobal: Int, readPtr: Long, tileX: Int, tileY: Int, currentRGBAddr: Long,
|
private fun tavDecodeDWTIntraTileRGB(qIndex: Int, qYGlobal: Int, readPtr: Long, tileX: Int, tileY: Int, currentRGBAddr: Long,
|
||||||
width: Int, height: Int, qY: Int, qCo: Int, qCg: Int,
|
width: Int, height: Int, qY: Int, qCo: Int, qCg: Int,
|
||||||
waveletFilter: Int, decompLevels: Int, isLossless: Boolean, tavVersion: Int, isMonoblock: Boolean = false): Long {
|
waveletFilter: Int, decompLevels: Int, isLossless: Boolean, tavVersion: Int, isMonoblock: Boolean = false): Long {
|
||||||
// Determine coefficient count based on mode
|
// Determine coefficient count based on mode
|
||||||
@@ -4393,9 +4398,9 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
val tileHeight = if (isMonoblock) height else PADDED_TILE_SIZE_Y
|
val tileHeight = if (isMonoblock) height else PADDED_TILE_SIZE_Y
|
||||||
val subbands = calculateSubbandLayout(tileWidth, tileHeight, decompLevels)
|
val subbands = calculateSubbandLayout(tileWidth, tileHeight, decompLevels)
|
||||||
|
|
||||||
dequantiseDWTSubbandsPerceptual(qYGlobal, quantisedY, yTile, subbands, qY.toFloat(), false, decompLevels)
|
dequantiseDWTSubbandsPerceptual(qIndex, qYGlobal, quantisedY, yTile, subbands, qY.toFloat(), false, decompLevels)
|
||||||
dequantiseDWTSubbandsPerceptual(qYGlobal, quantisedCo, coTile, subbands, qCo.toFloat(), true, decompLevels)
|
dequantiseDWTSubbandsPerceptual(qIndex, qYGlobal, quantisedCo, coTile, subbands, qCo.toFloat(), true, decompLevels)
|
||||||
dequantiseDWTSubbandsPerceptual(qYGlobal, quantisedCg, cgTile, subbands, qCg.toFloat(), true, decompLevels)
|
dequantiseDWTSubbandsPerceptual(qIndex, qYGlobal, quantisedCg, cgTile, subbands, qCg.toFloat(), true, decompLevels)
|
||||||
|
|
||||||
// Debug: Check coefficient values before inverse DWT
|
// Debug: Check coefficient values before inverse DWT
|
||||||
if (tavDebugCurrentFrameNumber == tavDebugFrameTarget) {
|
if (tavDebugCurrentFrameNumber == tavDebugFrameTarget) {
|
||||||
@@ -5036,7 +5041,7 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
return 1.0f
|
return 1.0f
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tavDecodeDeltaTileRGB(qYGlobal: Int, readPtr: Long, tileX: Int, tileY: Int, currentRGBAddr: Long,
|
private fun tavDecodeDeltaTileRGB(readPtr: Long, tileX: Int, tileY: Int, currentRGBAddr: Long,
|
||||||
width: Int, height: Int, qY: Int, qCo: Int, qCg: Int,
|
width: Int, height: Int, qY: Int, qCo: Int, qCg: Int,
|
||||||
waveletFilter: Int, decompLevels: Int, isLossless: Boolean, tavVersion: Int, isMonoblock: Boolean = false): Long {
|
waveletFilter: Int, decompLevels: Int, isLossless: Boolean, tavVersion: Int, isMonoblock: Boolean = false): Long {
|
||||||
|
|
||||||
@@ -5271,11 +5276,10 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
|
|
||||||
private object TavFilterDelta : TavWaveletFilter {
|
private object TavFilterDelta : TavWaveletFilter {
|
||||||
override fun getCoeffMultiplier(level: Int): Float {
|
override fun getCoeffMultiplier(level: Int): Float {
|
||||||
//return 1f
|
// return 1f
|
||||||
return when (level) {
|
return when(level) {
|
||||||
0 -> 6f/5f
|
0 -> 6f/5f
|
||||||
1 -> 5f/5f
|
1 -> 5f/6f
|
||||||
2 -> 5f/6f
|
|
||||||
else -> 1f
|
else -> 1f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5283,11 +5287,10 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
|
|
||||||
private object TavFilterIntra : TavWaveletFilter {
|
private object TavFilterIntra : TavWaveletFilter {
|
||||||
override fun getCoeffMultiplier(level: Int): Float {
|
override fun getCoeffMultiplier(level: Int): Float {
|
||||||
//return 1f
|
// return 1f
|
||||||
return when (level) {
|
return when (level) {
|
||||||
0 -> 6f/5f
|
0 -> 6f/5f
|
||||||
1 -> 5f/5f
|
1 -> 5f/6f
|
||||||
2 -> 5f/6f
|
|
||||||
else -> 1f
|
else -> 1f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1606,8 +1606,10 @@ static int write_tav_header(tav_encoder_t *enc) {
|
|||||||
if (enc->lossless) video_flags |= 0x04; // Lossless
|
if (enc->lossless) video_flags |= 0x04; // Lossless
|
||||||
fputc(video_flags, enc->output_fp);
|
fputc(video_flags, enc->output_fp);
|
||||||
|
|
||||||
// Reserved bytes (7 bytes)
|
fputc(enc->quality_level+1, enc->output_fp);
|
||||||
for (int i = 0; i < 7; i++) {
|
|
||||||
|
// Reserved bytes (6 bytes)
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
fputc(0, enc->output_fp);
|
fputc(0, enc->output_fp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user