Better tev preset table

This commit is contained in:
minjaesong
2025-08-27 17:45:37 +09:00
parent cda9a34b23
commit 6f7e407a1c
6 changed files with 189 additions and 133 deletions

View File

@@ -296,8 +296,16 @@ let width = seqread.readShort()
let height = seqread.readShort()
let fps = seqread.readOneByte()
let totalFrames = seqread.readInt()
let quality = seqread.readOneByte()
let hasAudio = seqread.readOneByte()
let qualityY = seqread.readOneByte()
let qualityCo = seqread.readOneByte()
let qualityCg = seqread.readOneByte()
let flags = seqread.readOneByte()
let hasAudio = flags & 1
let hasSubtitle = flags & 2
let unused1 = seqread.readOneByte()
let unused2 = seqread.readOneByte()
serial.println(`TEV Format ${version} (${colorSpace}); Q: ${qualityY} ${qualityCo} ${qualityCg}`)
function updateDataRateBin(rate) {
videoRateBin.push(rate)
@@ -464,7 +472,7 @@ try {
// Hardware-accelerated TEV decoding to RGB buffers (YCoCg-R or XYB based on version)
try {
let decodeStart = sys.nanoTime()
graphics.tevDecode(blockDataPtr, CURRENT_RGB_ADDR, PREV_RGB_ADDR, width, height, quality, debugMotionVectors, version)
graphics.tevDecode(blockDataPtr, CURRENT_RGB_ADDR, PREV_RGB_ADDR, width, height, [qualityY, qualityCo, qualityCg], debugMotionVectors, version)
decodeTime = (sys.nanoTime() - decodeStart) / 1000000.0 // Convert to milliseconds
// Upload RGB buffer to display framebuffer with dithering

View File

@@ -29,7 +29,8 @@ const COL_HL_EXT = {
"mp3": 33,
"mp2": 34,
"mov": 213,
"mv2": 213,
"mv2": 214,
"mv3": 214,
"ipf1": 190,
"ipf2": 191,
"txt": 223,
@@ -43,6 +44,7 @@ const EXEC_FUNS = {
"mp2": (f) => _G.shell.execute(`playmp2 "${f}" -i`),
"mov": (f) => _G.shell.execute(`playmov "${f}" -i`),
"mv2": (f) => _G.shell.execute(`playtev "${f}" -i`),
"mv3": (f) => _G.shell.execute(`playtev "${f}" -i`),
"pcm": (f) => _G.shell.execute(`playpcm "${f}" -i`),
"ipf1": (f) => _G.shell.execute(`decodeipf "${f}" -i`),
"ipf2": (f) => _G.shell.execute(`decodeipf "${f}" -i`),

View File

@@ -693,15 +693,20 @@ DCT-based compression, motion compensation, and efficient temporal coding.
[PACKET 2]
...
## Header (20 bytes)
## Header (24 bytes)
uint8 Magic[8]: "\x1FTSVM TEV"
uint8 Version: 2 or 3
uint16 Width: video width in pixels
uint16 Height: video height in pixels
uint8 FPS: frames per second
uint32 Total Frames: number of video frames
uint8 Quality: quantization quality (0-4, higher = better)
uint8 Flags: bit 0 = has audio
uint8 Quality Index for Y channel (0-99; 100 denotes all quantiser is 1)
uint8 Quality Index for Co channel (0-99; 100 denotes all quantiser is 1)
uint8 Quality Index for Cg channel (0-99; 100 denotes all quantiser is 1)
uint8 Flags
bit 0 = has audio
bit 1 = has subtitle
unit16 Reserved, fill with zero
## Packet Types
0x10: I-frame (intra-coded frame)
@@ -712,7 +717,7 @@ DCT-based compression, motion compensation, and efficient temporal coding.
## Video Packet Structure
uint8 Packet Type
uint32 Compressed Size (includes rate control factor size)
uint32 Compressed Size
* Gzip-compressed Block Data
## Block Data (per 16x16 block)

View File

@@ -1299,39 +1299,40 @@ class GraphicsJSR223Delegate(private val vm: VM) {
// TEV (TSVM Enhanced Video) format support
// Created by Claude on 2025-08-17
private val QUANT_MULT_Y = intArrayOf(40, 10, 6, 4, 1)
private val QUANT_MULT_CO = intArrayOf(40, 10, 6, 4, 1)
private val QUANT_MULT_CG = intArrayOf(106, 22, 10, 5, 1) // CO[i] * sqrt(7 - 2i)
fun jpeg_quality_to_mult(q: Int): Float {
return (if ((q < 50)) 5000f / q else 200f - 2 * q) / 100f
}
// Quality settings for quantization (Y channel) - 16x16 tables
val QUANT_TABLE_Y: IntArray = intArrayOf(
2, 1, 1, 2, 3, 5, 6, 7, 6, 7, 8, 9, 10, 11, 12, 13,
1, 1, 1, 2, 3, 6, 7, 9, 7, 9, 10, 11, 12, 13, 14, 15,
1, 1, 2, 3, 5, 6, 7, 9, 7, 9, 10, 11, 12, 13, 14, 15,
1, 2, 3, 4, 6, 7, 9, 10, 9, 10, 11, 12, 13, 14, 15, 16,
2, 3, 5, 6, 7, 9, 10, 11, 10, 11, 12, 13, 14, 15, 16, 17,
3, 4, 6, 7, 9, 10, 11, 12, 11, 12, 13, 14, 15, 16, 17, 18,
6, 6, 7, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, 18, 19,
6, 7, 9, 10, 11, 12, 13, 14, 13, 14, 15, 16, 17, 18, 19, 20,
6, 7, 9, 10, 11, 12, 13, 14, 13, 14, 15, 16, 17, 18, 19, 20,
7, 9, 10, 11, 12, 13, 14, 15, 14, 15, 16, 17, 18, 19, 20, 21,
9, 10, 11, 12, 13, 14, 15, 16, 15, 16, 17, 18, 19, 20, 21, 22,
10, 11, 12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 22, 23,
11, 12, 13, 14, 15, 16, 17, 18, 17, 18, 19, 20, 21, 22, 23, 24,
12, 13, 14, 15, 16, 17, 18, 19, 18, 19, 20, 21, 22, 23, 24, 25,
13, 14, 15, 16, 17, 18, 19, 20, 19, 20, 21, 22, 23, 24, 25, 26,
14, 15, 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 26, 27)
16, 14, 12, 11, 11, 13, 16, 20, 24, 30, 39, 48, 54, 61, 67, 73,
14, 13, 12, 12, 12, 15, 18, 21, 25, 33, 46, 57, 61, 65, 67, 70,
13, 12, 12, 13, 14, 17, 19, 23, 27, 36, 53, 66, 68, 69, 68, 67,
13, 13, 13, 14, 15, 18, 22, 26, 32, 41, 56, 67, 71, 74, 70, 67,
14, 14, 14, 15, 17, 20, 24, 30, 38, 47, 58, 68, 74, 79, 73, 67,
15, 15, 15, 17, 19, 22, 27, 34, 44, 55, 68, 79, 83, 85, 78, 70,
15, 16, 17, 20, 22, 26, 30, 38, 49, 63, 81, 94, 93, 91, 83, 74,
16, 18, 20, 24, 28, 33, 38, 47, 57, 73, 93, 108, 105, 101, 91, 81,
19, 21, 23, 29, 35, 43, 52, 60, 68, 83, 105, 121, 118, 115, 102, 89,
21, 24, 27, 35, 43, 53, 62, 70, 78, 91, 113, 128, 127, 125, 112, 99,
25, 30, 34, 43, 53, 61, 68, 76, 85, 97, 114, 127, 130, 132, 120, 108,
31, 38, 44, 54, 64, 71, 76, 84, 94, 105, 118, 129, 135, 138, 127, 116,
45, 52, 60, 69, 78, 84, 90, 97, 107, 118, 130, 139, 142, 143, 133, 122,
59, 68, 76, 84, 91, 97, 102, 110, 120, 129, 139, 147, 147, 146, 137, 127,
73, 82, 92, 98, 103, 107, 110, 117, 126, 132, 134, 136, 138, 138, 133, 127,
86, 98, 109, 112, 114, 116, 118, 124, 133, 135, 129, 125, 128, 130, 128, 127)
// Quality settings for quantization (Co channel - orange-blue, 8x8)
val QUANT_TABLE_C: IntArray = intArrayOf(
2, 3, 4, 6, 8, 12, 16, 20,
3, 4, 6, 8, 12, 16, 20, 24,
4, 6, 8, 12, 16, 20, 24, 28,
6, 8, 12, 16, 20, 24, 28, 32,
8, 12, 16, 20, 24, 28, 32, 36,
12, 16, 20, 24, 28, 32, 36, 40,
16, 20, 24, 28, 32, 36, 40, 44,
20, 24, 28, 32, 36, 40, 44, 48)
17, 18, 24, 47, 99, 99, 99, 99,
18, 21, 26, 66, 99, 99, 99, 99,
24, 26, 56, 99, 99, 99, 99, 99,
47, 66, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99)
/**
* Upload RGB frame buffer to graphics framebuffer with dithering
@@ -1416,7 +1417,7 @@ class GraphicsJSR223Delegate(private val vm: VM) {
}
}
private fun tevIdct8x8_fast(coeffs: IntArray, quantTable: IntArray, isChromaResidual: Boolean = false): IntArray {
private fun tevIdct8x8_fast(coeffs: IntArray, quantTable: FloatArray, isChromaResidual: Boolean = false, mult: Float = 1f): IntArray {
val result = IntArray(64)
// Reuse preallocated temp buffer to reduce GC pressure
@@ -1430,7 +1431,7 @@ class GraphicsJSR223Delegate(private val vm: VM) {
val coeff = if (isChromaResidual && coeffIdx == 0) {
coeffs[coeffIdx].toFloat() // DC lossless for chroma residual
} else {
coeffs[coeffIdx] * quantTable[coeffIdx].toFloat()
coeffs[coeffIdx] * quantTable[coeffIdx] * mult
}
sum += dctBasis8[u][col] * coeff
}
@@ -1468,7 +1469,7 @@ class GraphicsJSR223Delegate(private val vm: VM) {
}
// 16x16 IDCT for Y channel (YCoCg-R format)
private fun tevIdct16x16_fast(coeffs: IntArray, quantTable: IntArray): IntArray {
private fun tevIdct16x16_fast(coeffs: IntArray, quantTable: FloatArray, mult: Float = 1.0f): IntArray {
val result = IntArray(256) // 16x16 = 256
// Process coefficients and dequantize using preallocated buffer
@@ -1478,7 +1479,7 @@ class GraphicsJSR223Delegate(private val vm: VM) {
val coeff = if (idx == 0) {
coeffs[idx].toFloat() // DC lossless for luma
} else {
coeffs[idx] * quantTable[idx].toFloat()
coeffs[idx] * quantTable[idx] * mult
}
idct16TempBuffer[idx] = coeff
}
@@ -1662,7 +1663,7 @@ class GraphicsJSR223Delegate(private val vm: VM) {
val Y_MAX = 0.85
val yVal = (y / 255.0) * Y_MAX // Y: inverse of improved scale
val B_MAX = 0.85
val bVal = (((b - 1.0) + 128.0) / 255.0) * B_MAX // B: inverse of ((val/B_MAX*255)-128+1)
val bVal = ((b + 128.0) / 255.0) * B_MAX // B: inverse of ((val/B_MAX*255)-128)
// XYB to LMS gamma
val lgamma = xVal + yVal
@@ -1733,9 +1734,9 @@ class GraphicsJSR223Delegate(private val vm: VM) {
// Y: range 0 to 0.85, map to 0 to 255 (improved scale)
val Y_MAX = 0.85
val yQuant = ((yVal / Y_MAX) * 255.0).toInt().coerceIn(0, 255)
// B: range 0 to 0.85, map to -128 to +127 (improved precision with +1 offset for yellow-green)
// B: range 0 to 0.85, map to -128 to +127 (optimized precision)
val B_MAX = 0.85
val bQuant = (((bVal / B_MAX) * 255.0) - 128.0 + 1.0).toInt().coerceIn(-128, 127)
val bQuant = (((bVal / B_MAX) * 255.0) - 128.0).toInt().coerceIn(-128, 127)
// Store XYB values
val yIdx = py * 16 + px
@@ -1761,21 +1762,23 @@ class GraphicsJSR223Delegate(private val vm: VM) {
* @param frameCounter Frame counter for temporal patterns
*/
fun tevDecode(blockDataPtr: Long, currentRGBAddr: Long, prevRGBAddr: Long,
width: Int, height: Int, quality: Int, debugMotionVectors: Boolean = false,
width: Int, height: Int, qualityIndices: IntArray, debugMotionVectors: Boolean = false,
tevVersion: Int = 2) {
val blocksX = (width + 15) / 16 // 16x16 blocks now
val blocksY = (height + 15) / 16
val quantYmult = QUANT_MULT_Y[quality]
val quantCOmult = QUANT_MULT_CO[quality]
val quantCGmult = QUANT_MULT_CG[quality]
val quantYmult = jpeg_quality_to_mult(qualityIndices[0])
val quantCOmult = jpeg_quality_to_mult(qualityIndices[1])
val quantCGmult = jpeg_quality_to_mult(qualityIndices[2])
val quantBmult = quantCGmult
// Apply rate control factor to quantization tables (if not ~1.0, skip optimization)
val quantTableY = QUANT_TABLE_Y.map { it * quantYmult }.toIntArray()
val quantTableCo = QUANT_TABLE_C.map { it * quantCOmult }.toIntArray()
val quantTableCg = QUANT_TABLE_C.map { it * quantCGmult }.toIntArray()
val quantTableY = QUANT_TABLE_Y.map { (it * quantYmult).coerceIn(1f, 255f) }.toFloatArray()
val quantTableCo = QUANT_TABLE_C.map { (it * quantCOmult).coerceIn(1f, 255f) }.toFloatArray()
val quantTableCg = QUANT_TABLE_C.map { (it * quantCGmult).coerceIn(1f, 255f) }.toFloatArray()
val quantTableB = QUANT_TABLE_C.map { it * quantBmult.toFloat() }.toFloatArray()
var readPtr = blockDataPtr
// decide increment "direction" by the sign of the pointer
@@ -1914,9 +1917,9 @@ class GraphicsJSR223Delegate(private val vm: VM) {
}
// Perform hardware IDCT for each channel using fast algorithm
val yBlock = tevIdct16x16_fast(yCoeffs, quantTableY)
val coBlock = tevIdct8x8_fast(coCoeffs, quantTableCo, true)
val cgBlock = tevIdct8x8_fast(cgCoeffs, quantTableCg, true)
val yBlock = tevIdct16x16_fast(yCoeffs, quantTableY, rateControlFactor)
val coBlock = tevIdct8x8_fast(coCoeffs, quantTableCo, true, rateControlFactor)
val cgBlock = tevIdct8x8_fast(cgCoeffs, if (tevVersion == 3) quantTableB else quantTableCg, true, rateControlFactor)
// Convert to RGB (YCoCg-R for v2, XYB for v3)
val rgbData = if (tevVersion == 3) {
@@ -1974,9 +1977,9 @@ class GraphicsJSR223Delegate(private val vm: VM) {
}
// Step 2: Decode residual DCT
val yResidual = tevIdct16x16_fast(yCoeffs, quantTableY)
val coResidual = tevIdct8x8_fast(coCoeffs, quantTableCo, true)
val cgResidual = tevIdct8x8_fast(cgCoeffs, quantTableCg, true)
val yResidual = tevIdct16x16_fast(yCoeffs, quantTableY, rateControlFactor)
val coResidual = tevIdct8x8_fast(coCoeffs, quantTableCo, true, rateControlFactor)
val cgResidual = tevIdct8x8_fast(cgCoeffs, if (tevVersion == 3) quantTableB else quantTableCg, true, rateControlFactor)
// Step 3: Build motion-compensated YCoCg-R block and add residuals
val finalY = IntArray(256)

View File

@@ -39,43 +39,52 @@ static inline int CLAMP(int x, int min, int max) {
static inline float FCLAMP(float x, float min, float max) {
return x < min ? min : (x > max ? max : x);
}
// Which preset should I be using?
// from dataset of three videos with Q0..Q95: (real life video, low res pixel art, high res pixel art)
// 56 96 128 192 256 Claude Opus 4.1 (with data analysis)
// 64 96 128 192 256 ChatGPT-5 (without data analysis)
static const int MP2_RATE_TABLE[] = {64, 96, 128, 192, 256};
// Which preset should I be using?
// from dataset of three videos with Q0..Q95: (real life video, low res pixel art, high res pixel art)
// 5 25 50 75 90 Claude Opus 4.1 (with data analysis)
// 10 25 45 65 85 ChatGPT-5 (without data analysis)
static const int QUALITY_Y[] = {8, 24, 48, 70, 88};
static const int QUALITY_CO[] = {8, 24, 48, 70, 88};
static const int MP2_RATE_TABLE[5] = {80, 128, 192, 224, 384};
static const int QUANT_MULT_Y[5] = {40, 10, 6, 4, 1};
static const int QUANT_MULT_CO[5] = {40, 10, 6, 4, 1};
static const int QUANT_MULT_CG[5] = {106, 22, 10, 5, 1}; // CO[i] * sqrt(7 - 2i)
// only leave (4, 6, 7)
static float jpeg_quality_to_mult(int q) {
return ((q < 50) ? 5000.f / q : 200.f - 2*q) / 100.f;
}
// Quality settings for quantisation (Y channel) - 16x16 tables
static const uint32_t QUANT_TABLE_Y[256] =
// Quality 7 (highest)
{2, 1, 1, 2, 3, 5, 6, 7, 6, 7, 8, 9, 10, 11, 12, 13,
1, 1, 1, 2, 3, 6, 7, 9, 7, 9, 10, 11, 12, 13, 14, 15,
1, 1, 2, 3, 5, 6, 7, 9, 7, 9, 10, 11, 12, 13, 14, 15,
1, 2, 3, 4, 6, 7, 9, 10, 9, 10, 11, 12, 13, 14, 15, 16,
2, 3, 5, 6, 7, 9, 10, 11, 10, 11, 12, 13, 14, 15, 16, 17,
3, 4, 6, 7, 9, 10, 11, 12, 11, 12, 13, 14, 15, 16, 17, 18,
6, 6, 7, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, 18, 19,
6, 7, 9, 10, 11, 12, 13, 14, 13, 14, 15, 16, 17, 18, 19, 20,
6, 7, 9, 10, 11, 12, 13, 14, 13, 14, 15, 16, 17, 18, 19, 20,
7, 9, 10, 11, 12, 13, 14, 15, 14, 15, 16, 17, 18, 19, 20, 21,
9, 10, 11, 12, 13, 14, 15, 16, 15, 16, 17, 18, 19, 20, 21, 22,
10, 11, 12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 22, 23,
11, 12, 13, 14, 15, 16, 17, 18, 17, 18, 19, 20, 21, 22, 23, 24,
12, 13, 14, 15, 16, 17, 18, 19, 18, 19, 20, 21, 22, 23, 24, 25,
13, 14, 15, 16, 17, 18, 19, 20, 19, 20, 21, 22, 23, 24, 25, 26,
14, 15, 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 26, 27};
// Quality 50
{16, 14, 12, 11, 11, 13, 16, 20, 24, 30, 39, 48, 54, 61, 67, 73,
14, 13, 12, 12, 12, 15, 18, 21, 25, 33, 46, 57, 61, 65, 67, 70,
13, 12, 12, 13, 14, 17, 19, 23, 27, 36, 53, 66, 68, 69, 68, 67,
13, 13, 13, 14, 15, 18, 22, 26, 32, 41, 56, 67, 71, 74, 70, 67,
14, 14, 14, 15, 17, 20, 24, 30, 38, 47, 58, 68, 74, 79, 73, 67,
15, 15, 15, 17, 19, 22, 27, 34, 44, 55, 68, 79, 83, 85, 78, 70,
15, 16, 17, 20, 22, 26, 30, 38, 49, 63, 81, 94, 93, 91, 83, 74,
16, 18, 20, 24, 28, 33, 38, 47, 57, 73, 93, 108, 105, 101, 91, 81,
19, 21, 23, 29, 35, 43, 52, 60, 68, 83, 105, 121, 118, 115, 102, 89,
21, 24, 27, 35, 43, 53, 62, 70, 78, 91, 113, 128, 127, 125, 112, 99,
25, 30, 34, 43, 53, 61, 68, 76, 85, 97, 114, 127, 130, 132, 120, 108,
31, 38, 44, 54, 64, 71, 76, 84, 94, 105, 118, 129, 135, 138, 127, 116,
45, 52, 60, 69, 78, 84, 90, 97, 107, 118, 130, 139, 142, 143, 133, 122,
59, 68, 76, 84, 91, 97, 102, 110, 120, 129, 139, 147, 147, 146, 137, 127,
73, 82, 92, 98, 103, 107, 110, 117, 126, 132, 134, 136, 138, 138, 133, 127,
86, 98, 109, 112, 114, 116, 118, 124, 133, 135, 129, 125, 128, 130, 128, 127};
// Quality settings for quantisation (Co channel - 8x8)
// Quality settings for quantisation (X channel - 8x8)
static const uint32_t QUANT_TABLE_C[64] =
{2, 3, 4, 6, 8, 12, 16, 20,
3, 4, 6, 8, 12, 16, 20, 24,
4, 6, 8, 12, 16, 20, 24, 28,
6, 8, 12, 16, 20, 24, 28, 32,
8, 12, 16, 20, 24, 28, 32, 36,
12, 16, 20, 24, 28, 32, 36, 40,
16, 20, 24, 28, 32, 36, 40, 44,
20, 24, 28, 32, 36, 40, 44, 48};
{17, 18, 24, 47, 99, 99, 99, 99,
18, 21, 26, 66, 99, 99, 99, 99,
24, 26, 56, 99, 99, 99, 99, 99,
47, 66, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99};
// Audio constants (reuse MP2 from existing system)
@@ -123,7 +132,10 @@ typedef struct {
int has_audio;
int has_subtitles;
int output_to_stdout;
int quality; // 0-4, higher = better quality
int qualityIndex; // -q option
int qualityY;
int qualityCo;
int qualityCg;
int verbose;
// Bitrate control
@@ -344,7 +356,7 @@ static void dct_8x8(float *input, float *output) {
}
// quantise DCT coefficient using quality table with rate control
static int16_t quantise_coeff(float coeff, uint32_t quant, int is_dc, int is_chroma, float rate_factor) {
static int16_t quantise_coeff(float coeff, float quant, int is_dc, int is_chroma, float rate_factor) {
if (is_dc) {
if (is_chroma) {
// Chroma DC: range -256 to +255, use lossless quantisation for testing
@@ -814,9 +826,9 @@ static void encode_block(tev_encoder_t *enc, int block_x, int block_y, int is_ke
// quantise Y coefficients (luma)
const uint32_t *y_quant = QUANT_TABLE_Y;
const uint32_t qmult_y = QUANT_MULT_Y[enc->quality];
const float qmult_y = jpeg_quality_to_mult(enc->qualityY);
for (int i = 0; i < 256; i++) {
block->y_coeffs[i] = quantise_coeff(enc->dct_workspace[i], y_quant[i] * qmult_y, i == 0, 0, enc->rate_control_factor);
block->y_coeffs[i] = quantise_coeff(enc->dct_workspace[i], FCLAMP(y_quant[i] * qmult_y, 1.f, 255.f), i == 0, 0, enc->rate_control_factor);
}
// Apply fast DCT transform to chroma
@@ -824,9 +836,9 @@ static void encode_block(tev_encoder_t *enc, int block_x, int block_y, int is_ke
// quantise Co coefficients (chroma - orange-blue)
const uint32_t *co_quant = QUANT_TABLE_C;
const uint32_t qmult_co = QUANT_MULT_CO[enc->quality];
const float qmult_co = jpeg_quality_to_mult(enc->qualityCo);
for (int i = 0; i < 64; i++) {
block->co_coeffs[i] = quantise_coeff(enc->dct_workspace[i], co_quant[i] * qmult_co, i == 0, 1, enc->rate_control_factor);
block->co_coeffs[i] = quantise_coeff(enc->dct_workspace[i], FCLAMP(co_quant[i] * qmult_co, 1.f, 255.f), i == 0, 1, enc->rate_control_factor);
}
// Apply fast DCT transform to Cg
@@ -834,9 +846,9 @@ static void encode_block(tev_encoder_t *enc, int block_x, int block_y, int is_ke
// quantise Cg coefficients (chroma - green-magenta, qmult_cg is more aggressive like NTSC Q)
const uint32_t *cg_quant = QUANT_TABLE_C;
const uint32_t qmult_cg = QUANT_MULT_CG[enc->quality];
const float qmult_cg = jpeg_quality_to_mult(enc->qualityCg);
for (int i = 0; i < 64; i++) {
block->cg_coeffs[i] = quantise_coeff(enc->dct_workspace[i], cg_quant[i] * qmult_cg, i == 0, 1, enc->rate_control_factor);
block->cg_coeffs[i] = quantise_coeff(enc->dct_workspace[i], FCLAMP(cg_quant[i] * qmult_cg, 1.f, 255.f), i == 0, 1, enc->rate_control_factor);
}
// Set CBP (simplified - always encode all channels)
@@ -1066,7 +1078,10 @@ static tev_encoder_t* init_encoder(void) {
if (!enc) return NULL;
// set defaults
enc->quality = 2; // Default quality
enc->qualityIndex = 2; // Default quality
enc->qualityY = QUALITY_Y[enc->qualityIndex];
enc->qualityCo = QUALITY_CO[enc->qualityIndex];
enc->qualityCg = enc->qualityCo / 2;
enc->mp2_packet_size = 0; // Will be detected from MP2 header
enc->mp2_rate_index = 0;
enc->audio_frames_in_buffer = 0;
@@ -1173,15 +1188,21 @@ static int write_tev_header(FILE *output, tev_encoder_t *enc) {
uint16_t height = enc->height;
uint8_t fps = enc->fps;
uint32_t total_frames = enc->total_frames;
uint8_t quality = enc->quality;
uint8_t has_audio = enc->has_audio;
uint8_t qualityY = enc->qualityY;
uint8_t qualityCo = enc->qualityCo;
uint8_t qualityCg = enc->qualityCg;
uint8_t flags = (enc->has_audio) | (enc->has_subtitles << 1);
uint16_t unused = 0;
fwrite(&width, 2, 1, output);
fwrite(&height, 2, 1, output);
fwrite(&fps, 1, 1, output);
fwrite(&total_frames, 4, 1, output);
fwrite(&quality, 1, 1, output);
fwrite(&has_audio, 1, 1, output);
fwrite(&qualityY, 1, 1, output);
fwrite(&qualityCo, 1, 1, output);
fwrite(&qualityCg, 1, 1, output);
fwrite(&flags, 1, 1, output);
fwrite(&unused, 2, 1, output);
return 0;
}
@@ -1475,7 +1496,7 @@ static int start_audio_conversion(tev_encoder_t *enc) {
char command[2048];
snprintf(command, sizeof(command),
"ffmpeg -v quiet -i \"%s\" -acodec libtwolame -psymodel 4 -b:a %dk -ar %d -ac 2 -y \"%s\" 2>/dev/null",
enc->input_file, MP2_RATE_TABLE[enc->quality], MP2_SAMPLE_RATE, TEMP_AUDIO_FILE);
enc->input_file, MP2_RATE_TABLE[enc->qualityIndex], MP2_SAMPLE_RATE, TEMP_AUDIO_FILE);
int result = system(command);
if (result == 0) {
@@ -1625,7 +1646,8 @@ static void show_usage(const char *program_name) {
printf(" -w, --width N Video width (default: %d)\n", DEFAULT_WIDTH);
printf(" -h, --height N Video height (default: %d)\n", DEFAULT_HEIGHT);
printf(" -f, --fps N Output frames per second (enables frame rate conversion)\n");
printf(" -q, --quality N Quality level 0-4 (default: 2, only decides audio rate in bitrate mode)\n");
printf(" -q, --quality N Quality level 0-4 (default: 2, only decides audio rate in bitrate mode and quantiser mode)\n");
printf(" -Q, --quantiser N Quantiser level 0-100 (100: lossless, 0: potato)\n");
printf(" -b, --bitrate N Target bitrate in kbps (enables bitrate control mode; DON'T USE - NOT WORKING AS INTENDED)\n");
printf(" -v, --verbose Verbose output\n");
printf(" -t, --test Test mode: generate solid colour frames\n");
@@ -1638,6 +1660,11 @@ static void show_usage(const char *program_name) {
for (int i = 0; i < sizeof(MP2_RATE_TABLE) / sizeof(int); i++) {
printf("%d: %d kbps\t", i, MP2_RATE_TABLE[i]);
}
printf("\nQuantiser Value by Quality:\n");
printf(" ");
for (int i = 0; i < sizeof(QUALITY_Y) / sizeof(int); i++) {
printf("%d: -Q %d \t", i, QUALITY_Y[i]);
}
printf("\n\n");
printf("Features:\n");
printf(" - YCoCg-R 4:2:0 chroma subsampling for 50%% compression improvement\n");
@@ -1692,6 +1719,8 @@ int main(int argc, char *argv[]) {
{"height", required_argument, 0, 'h'},
{"fps", required_argument, 0, 'f'},
{"quality", required_argument, 0, 'q'},
{"quantiser", required_argument, 0, 'Q'},
{"quantizer", required_argument, 0, 'Q'},
{"bitrate", required_argument, 0, 'b'},
{"verbose", no_argument, 0, 'v'},
{"test", no_argument, 0, 't'},
@@ -1702,7 +1731,7 @@ int main(int argc, char *argv[]) {
int option_index = 0;
int c;
while ((c = getopt_long(argc, argv, "i:o:s:w:h:f:q:b:vt", long_options, &option_index)) != -1) {
while ((c = getopt_long(argc, argv, "i:o:s:w:h:f:q:b:Q:vt", long_options, &option_index)) != -1) {
switch (c) {
case 'i':
enc->input_file = strdup(optarg);
@@ -1729,7 +1758,10 @@ int main(int argc, char *argv[]) {
}
break;
case 'q':
enc->quality = CLAMP(atoi(optarg), 0, 4);
enc->qualityIndex = CLAMP(atoi(optarg), 0, 4);
enc->qualityY = QUALITY_Y[enc->qualityIndex];
enc->qualityCo = QUALITY_CO[enc->qualityIndex];
enc->qualityCg = enc->qualityCo / 2;
break;
case 'b':
enc->target_bitrate_kbps = atoi(optarg);
@@ -1750,6 +1782,11 @@ int main(int argc, char *argv[]) {
return 0;
}
break;
case 'Q':
enc->qualityY = CLAMP(atoi(optarg), 0, 100);
enc->qualityCo = enc->qualityY;
enc->qualityCg = enc->qualityCo / 2;
break;
default:
show_usage(argv[0]);
cleanup_encoder(enc);
@@ -1844,7 +1881,8 @@ int main(int argc, char *argv[]) {
if (enc->bitrate_mode > 0) {
printf("Bitrate control enabled: targeting %d kbps\n", enc->target_bitrate_kbps);
} else {
printf("Quality mode: q=%d\n", enc->quality);
printf("Quality mode: q=%d\n", enc->qualityIndex);
printf("Quantiser levels: %d, %d, %d\n", enc->qualityY, enc->qualityCo, enc->qualityCg);
}
// Process frames

View File

@@ -43,39 +43,39 @@ static inline float FCLAMP(float x, float min, float max) {
static const int MP2_RATE_TABLE[5] = {80, 128, 192, 224, 384};
static const int QUANT_MULT_Y[5] = {40, 10, 6, 4, 1};
static const int QUANT_MULT_X[5] = {40, 10, 6, 4, 1};
static const int QUANT_MULT_B[5] = {106, 22, 10, 5, 1}; // X[i] * sqrt(7 - 2i) - B channel aggressively quantized
static const int QUANT_MULT_B[5] = {80, 40, 20, 10, 2}; // B channel aggressively quantized
// only leave (4, 6, 7)
// Quality settings for quantisation (Y channel) - 16x16 tables
static const uint32_t QUANT_TABLE_Y[256] =
// Quality 7 (highest)
{2, 1, 1, 2, 3, 5, 6, 7, 6, 7, 8, 9, 10, 11, 12, 13,
1, 1, 1, 2, 3, 6, 7, 9, 7, 9, 10, 11, 12, 13, 14, 15,
1, 1, 2, 3, 5, 6, 7, 9, 7, 9, 10, 11, 12, 13, 14, 15,
1, 2, 3, 4, 6, 7, 9, 10, 9, 10, 11, 12, 13, 14, 15, 16,
2, 3, 5, 6, 7, 9, 10, 11, 10, 11, 12, 13, 14, 15, 16, 17,
3, 4, 6, 7, 9, 10, 11, 12, 11, 12, 13, 14, 15, 16, 17, 18,
6, 6, 7, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, 18, 19,
6, 7, 9, 10, 11, 12, 13, 14, 13, 14, 15, 16, 17, 18, 19, 20,
6, 7, 9, 10, 11, 12, 13, 14, 13, 14, 15, 16, 17, 18, 19, 20,
7, 9, 10, 11, 12, 13, 14, 15, 14, 15, 16, 17, 18, 19, 20, 21,
9, 10, 11, 12, 13, 14, 15, 16, 15, 16, 17, 18, 19, 20, 21, 22,
10, 11, 12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 22, 23,
11, 12, 13, 14, 15, 16, 17, 18, 17, 18, 19, 20, 21, 22, 23, 24,
12, 13, 14, 15, 16, 17, 18, 19, 18, 19, 20, 21, 22, 23, 24, 25,
13, 14, 15, 16, 17, 18, 19, 20, 19, 20, 21, 22, 23, 24, 25, 26,
14, 15, 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 26, 27};
{16, 14, 12, 11, 11, 13, 16, 20, 24, 30, 39, 48, 54, 61, 67, 73,
14, 13, 12, 12, 12, 15, 18, 21, 25, 33, 46, 57, 61, 65, 67, 70,
13, 12, 12, 13, 14, 17, 19, 23, 27, 36, 53, 66, 68, 69, 68, 67,
13, 13, 13, 14, 15, 18, 22, 26, 32, 41, 56, 67, 71, 74, 70, 67,
14, 14, 14, 15, 17, 20, 24, 30, 38, 47, 58, 68, 74, 79, 73, 67,
15, 15, 15, 17, 19, 22, 27, 34, 44, 55, 68, 79, 83, 85, 78, 70,
15, 16, 17, 20, 22, 26, 30, 38, 49, 63, 81, 94, 93, 91, 83, 74,
16, 18, 20, 24, 28, 33, 38, 47, 57, 73, 93, 108, 105, 101, 91, 81,
19, 21, 23, 29, 35, 43, 52, 60, 68, 83, 105, 121, 118, 115, 102, 89,
21, 24, 27, 35, 43, 53, 62, 70, 78, 91, 113, 128, 127, 125, 112, 99,
25, 30, 34, 43, 53, 61, 68, 76, 85, 97, 114, 127, 130, 132, 120, 108,
31, 38, 44, 54, 64, 71, 76, 84, 94, 105, 118, 129, 135, 138, 127, 116,
45, 52, 60, 69, 78, 84, 90, 97, 107, 118, 130, 139, 142, 143, 133, 122,
59, 68, 76, 84, 91, 97, 102, 110, 120, 129, 139, 147, 147, 146, 137, 127,
73, 82, 92, 98, 103, 107, 110, 117, 126, 132, 134, 136, 138, 138, 133, 127,
86, 98, 109, 112, 114, 116, 118, 124, 133, 135, 129, 125, 128, 130, 128, 127};
// Quality settings for quantisation (X channel - 8x8)
static const uint32_t QUANT_TABLE_C[64] =
{2, 3, 4, 6, 8, 12, 16, 20,
3, 4, 6, 8, 12, 16, 20, 24,
4, 6, 8, 12, 16, 20, 24, 28,
6, 8, 12, 16, 20, 24, 28, 32,
8, 12, 16, 20, 24, 28, 32, 36,
12, 16, 20, 24, 28, 32, 36, 40,
16, 20, 24, 28, 32, 36, 40, 44,
20, 24, 28, 32, 36, 40, 44, 48};
{17, 18, 24, 47, 99, 99, 99, 99,
18, 21, 26, 66, 99, 99, 99, 99,
24, 26, 56, 99, 99, 99, 99, 99,
47, 66, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99};
// Audio constants (reuse MP2 from existing system)
@@ -239,9 +239,9 @@ static void rgb_to_xyb(uint8_t r, uint8_t g, uint8_t b, int *y, int *x, int *xyb
// Y: range 0 to 0.85, map to 0 to 255 (improved scale)
const double Y_MAX = 0.85;
*y = CLAMP((int)((y_val / Y_MAX) * 255.0), 0, 255);
// B: range 0 to 0.85, map to -128 to +127 (improved precision with +1 offset for yellow-green)
// B: range 0 to 0.85, map to -128 to +127 (optimized precision)
const double B_MAX = 0.85;
*xyb_b = CLAMP((int)(((b_val / B_MAX) * 255.0) - 128.0 + 1.0), -128, 127);
*xyb_b = CLAMP((int)(((b_val / B_MAX) * 255.0) - 128.0), -128, 127);
}
// XYB to RGB transform (for verification)
@@ -252,7 +252,7 @@ static void xyb_to_rgb(int y, int x, int xyb_b, uint8_t *r, uint8_t *g, uint8_t
const double Y_MAX = 0.85;
double y_val = (y / 255.0) * Y_MAX; // Y: inverse of improved scale
const double B_MAX = 0.85;
double b_val = (((xyb_b - 1.0) + 128.0) / 255.0) * B_MAX; // B: inverse of ((val/B_MAX*255)-128+1)
double b_val = ((xyb_b + 128.0) / 255.0) * B_MAX; // B: inverse of ((val/B_MAX*255)-128)
// Debug print for red color
if (x == 127 && y == 147 && xyb_b == 28) {