Files
2026-04-07 09:58:54 +02:00

520 lines
16 KiB
C++

#include <stdio.h>
#include <string.h>
#include "../src/cavlc.h"
using namespace subcodec::cavlc;
/*
* CAVLC Bitstream Verification Tests
*
* These tests verify that CAVLC encoding produces correct bitstreams by checking
* the output against known expected values from the H.264 specification.
*
* Note: Full FFmpeg round-trip verification (encode -> decode -> compare pixels)
* requires complete macroblock encoding which is implemented in Tasks 7-9.
* Once write_p_frame_ex() is complete in Task 9, we can add end-to-end decode
* verification using libavcodec.
*/
// Helper to calculate bits written (bytes * 8 - unused bits in current byte)
static size_t bs_bits_written(bs_t* b) {
size_t bytes = b->p - b->start;
size_t bits = bytes * 8 + (8 - b->bits_left);
return bits;
}
static int test_zero_block(void) {
uint8_t buf[64];
memset(buf, 0, sizeof(buf));
bs_t b;
bs_init(&b, buf, sizeof(buf));
int16_t coeffs[16] = {0};
int tc = write_block(&b, coeffs, 0, 16);
if (tc != 0) {
printf("FAIL: test_zero_block - expected TC=0, got %d\n", tc);
return 1;
}
printf("PASS: test_zero_block\n");
return 0;
}
static int test_dc_only(void) {
uint8_t buf[64];
memset(buf, 0, sizeof(buf));
bs_t b;
bs_init(&b, buf, sizeof(buf));
int16_t coeffs[16] = {5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int tc = write_block(&b, coeffs, 0, 16);
if (tc != 1) {
printf("FAIL: test_dc_only - expected TC=1, got %d\n", tc);
return 1;
}
printf("PASS: test_dc_only\n");
return 0;
}
static int test_trailing_ones(void) {
uint8_t buf[64];
memset(buf, 0, sizeof(buf));
bs_t b;
bs_init(&b, buf, sizeof(buf));
// 3, 0, 1, -1 -> TotalCoeff=3, TrailingOnes=2
int16_t coeffs[16] = {3, 0, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int tc = write_block(&b, coeffs, 0, 16);
if (tc != 3) {
printf("FAIL: test_trailing_ones - expected TC=3, got %d\n", tc);
return 1;
}
printf("PASS: test_trailing_ones\n");
return 0;
}
static int test_three_trailing_ones(void) {
uint8_t buf[64];
memset(buf, 0, sizeof(buf));
bs_t b;
bs_init(&b, buf, sizeof(buf));
// 1, 1, -1 -> TotalCoeff=3, TrailingOnes=3
int16_t coeffs[16] = {1, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int tc = write_block(&b, coeffs, 0, 16);
if (tc != 3) {
printf("FAIL: test_three_trailing_ones - expected TC=3, got %d\n", tc);
return 1;
}
printf("PASS: test_three_trailing_ones\n");
return 0;
}
static int test_large_level(void) {
uint8_t buf[64];
memset(buf, 0, sizeof(buf));
bs_t b;
bs_init(&b, buf, sizeof(buf));
// Large coefficient value to test escape coding
int16_t coeffs[16] = {100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int tc = write_block(&b, coeffs, 0, 16);
if (tc != 1) {
printf("FAIL: test_large_level - expected TC=1, got %d\n", tc);
return 1;
}
printf("PASS: test_large_level\n");
return 0;
}
static int test_multiple_coeffs_with_zeros(void) {
uint8_t buf[64];
memset(buf, 0, sizeof(buf));
bs_t b;
bs_init(&b, buf, sizeof(buf));
// Multiple non-zero coefficients with zeros between them
// This tests the run_before encoding
int16_t coeffs[16] = {5, 0, 0, 3, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int tc = write_block(&b, coeffs, 0, 16);
if (tc != 3) {
printf("FAIL: test_multiple_coeffs_with_zeros - expected TC=3, got %d\n", tc);
return 1;
}
printf("PASS: test_multiple_coeffs_with_zeros\n");
return 0;
}
static int test_chroma_dc(void) {
uint8_t buf[64];
memset(buf, 0, sizeof(buf));
bs_t b;
bs_init(&b, buf, sizeof(buf));
// Chroma DC block (4 coefficients max)
int16_t coeffs[4] = {10, 0, -5, 0};
int tc = write_block(&b, coeffs, -1, 4);
if (tc != 2) {
printf("FAIL: test_chroma_dc - expected TC=2, got %d\n", tc);
return 1;
}
printf("PASS: test_chroma_dc\n");
return 0;
}
static int test_full_block(void) {
uint8_t buf[128];
memset(buf, 0, sizeof(buf));
bs_t b;
bs_init(&b, buf, sizeof(buf));
// All 16 coefficients non-zero
int16_t coeffs[16] = {16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
int tc = write_block(&b, coeffs, 0, 16);
if (tc != 16) {
printf("FAIL: test_full_block - expected TC=16, got %d\n", tc);
return 1;
}
printf("PASS: test_full_block\n");
return 0;
}
static int test_negative_coeffs(void) {
uint8_t buf[128];
bs_t b;
bs_init(&b, buf, sizeof(buf));
// Mix of positive and negative values
int16_t coeffs[16] = {-10, 5, -3, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int tc = write_block(&b, coeffs, 0, 16);
if (tc != 5) {
printf("FAIL: test_negative_coeffs - expected TC=5, got %d\n", tc);
return 1;
}
printf("PASS: test_negative_coeffs\n");
return 0;
}
static int test_nc_variations(void) {
int errors = 0;
// Test with different nC values to exercise different VLC tables
int nc_values[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int num_nc = sizeof(nc_values) / sizeof(nc_values[0]);
for (int i = 0; i < num_nc; i++) {
uint8_t buf[64];
memset(buf, 0, sizeof(buf));
bs_t b;
bs_init(&b, buf, sizeof(buf));
int16_t coeffs[16] = {5, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int tc = write_block(&b, coeffs, nc_values[i], 16);
if (tc != 2) {
printf("FAIL: test_nc_variations (nc=%d) - expected TC=2, got %d\n", nc_values[i], tc);
errors++;
}
}
if (errors == 0) {
printf("PASS: test_nc_variations\n");
}
return errors;
}
/*
* Bitstream-level verification tests
*
* These tests verify CAVLC encoding by checking that the output bitstream
* matches expected patterns from the H.264 specification. This is an
* intermediate verification step before full FFmpeg round-trip testing.
*/
// Test: Verify zero block encoding produces exactly 1 bit (coeff_token for TC=0)
// For nC=0, TotalCoeff=0, T1=0: coeff_token = 1 (1 bit)
static int test_bitstream_zero_block(void) {
uint8_t buf[64];
memset(buf, 0, sizeof(buf));
bs_t b;
bs_init(&b, buf, sizeof(buf));
int16_t coeffs[16] = {0};
int tc = write_block(&b, coeffs, 0, 16);
size_t bits = bs_bits_written(&b);
// Zero block for nC=0 should be exactly 1 bit (coeff_token = "1")
if (tc != 0 || bits != 1) {
printf("FAIL: test_bitstream_zero_block - TC=%d, bits=%zu (expected TC=0, bits=1)\n", tc, bits);
return 1;
}
// The first byte should be 0x80 (1 followed by 7 zeros)
if (buf[0] != 0x80) {
printf("FAIL: test_bitstream_zero_block - byte=0x%02X (expected 0x80)\n", buf[0]);
return 1;
}
printf("PASS: test_bitstream_zero_block (1 bit)\n");
return 0;
}
// Test: Verify single DC coefficient encoding produces valid output
// For nC=0, TotalCoeff=1, T1=0: coeff_token = 000101 (6 bits)
// Level=4: After T1 adjustment, level=3, level_code = 2*3-2+0 = 4, suffix_length=0
// level_prefix = 4 (write 4 zeros + 1) = 00001 (5 bits)
// total_zeros=0 with TC=1: VLC = 1 (1 bit)
// Total: 6 + 5 + 1 = 12 bits
static int test_bitstream_single_dc(void) {
uint8_t buf[64];
memset(buf, 0, sizeof(buf));
bs_t b;
bs_init(&b, buf, sizeof(buf));
int16_t coeffs[16] = {4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int tc = write_block(&b, coeffs, 0, 16);
size_t bits = bs_bits_written(&b);
if (tc != 1) {
printf("FAIL: test_bitstream_single_dc - TC=%d (expected 1)\n", tc);
return 1;
}
// Expected: 12 bits total (coeff_token=6 + level=5 + total_zeros=1)
// The first non-T1 level gets magnitude reduced by 1, so level=4 becomes level=3
if (bits != 12) {
printf("FAIL: test_bitstream_single_dc - bits=%zu (expected 12)\n", bits);
return 1;
}
printf("PASS: test_bitstream_single_dc (%zu bits)\n", bits);
return 0;
}
// Test: Verify trailing ones with sign bits
// Coeffs: [0, 0, 0, 1, 0, -1] in zigzag order
// In reverse scan: -1 at pos 5, 1 at pos 3
// TotalCoeff=2, TrailingOnes=2 (both are +/-1)
// For nC=0, TC=2, T1=2: coeff_token = 001 (3 bits) from Table 9-5(a)
// Signs: -1 = 1, 1 = 0 -> write in reverse: 0, 1 = 01 (2 bits)
// total_zeros: TC=2, total_zeros=3 (positions 3,5 have coeffs, so zeros at 0,1,2,4)
// Actually: last_nz=5, total_zeros = 5+1 - 2 = 4
// Wait, let me recalculate with exact positions
static int test_bitstream_trailing_ones_signs(void) {
uint8_t buf[64];
memset(buf, 0, sizeof(buf));
bs_t b;
bs_init(&b, buf, sizeof(buf));
// Two trailing ones at positions 3 and 5
int16_t coeffs[16] = {0, 0, 0, 1, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int tc = write_block(&b, coeffs, 0, 16);
size_t bits = bs_bits_written(&b);
if (tc != 2) {
printf("FAIL: test_bitstream_trailing_ones_signs - TC=%d (expected 2)\n", tc);
return 1;
}
// Should produce non-trivial output with sign bits
// coeff_token(2,2,nC=0) = 001 (3 bits)
// signs: 0 (for +1), 1 (for -1) -> 2 bits
// total_zeros (TC=2, tz=4): from table
// run_before for first coeff
if (bits < 5) {
printf("FAIL: test_bitstream_trailing_ones_signs - too few bits=%zu\n", bits);
return 1;
}
printf("PASS: test_bitstream_trailing_ones_signs (%zu bits)\n", bits);
return 0;
}
// Test: Verify deterministic encoding (same input = same output)
static int test_bitstream_deterministic(void) {
uint8_t buf1[64], buf2[64];
memset(buf1, 0, sizeof(buf1));
memset(buf2, 0, sizeof(buf2));
bs_t b1, b2;
bs_init(&b1, buf1, sizeof(buf1));
bs_init(&b2, buf2, sizeof(buf2));
int16_t coeffs[16] = {10, 0, -5, 2, 0, 0, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0};
int tc1 = write_block(&b1, coeffs, 0, 16);
int tc2 = write_block(&b2, coeffs, 0, 16);
size_t bits1 = bs_bits_written(&b1);
size_t bits2 = bs_bits_written(&b2);
if (tc1 != tc2) {
printf("FAIL: test_bitstream_deterministic - TC mismatch %d vs %d\n", tc1, tc2);
return 1;
}
if (bits1 != bits2) {
printf("FAIL: test_bitstream_deterministic - bit count mismatch %zu vs %zu\n", bits1, bits2);
return 1;
}
// Compare output bytes
size_t bytes = (bits1 + 7) / 8;
if (memcmp(buf1, buf2, bytes) != 0) {
printf("FAIL: test_bitstream_deterministic - output bytes differ\n");
return 1;
}
printf("PASS: test_bitstream_deterministic (TC=%d, %zu bits)\n", tc1, bits1);
return 0;
}
// Test: Verify different nC values produce different coeff_token encodings
// The same coefficients with different nC should produce different bit counts
// because different VLC tables are used
static int test_bitstream_nc_affects_output(void) {
int16_t coeffs[16] = {5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
size_t bits_nc0, bits_nc4, bits_nc8;
// nC = 0 (Table 9-5(a))
{
uint8_t buf[64];
memset(buf, 0, sizeof(buf));
bs_t b;
bs_init(&b, buf, sizeof(buf));
write_block(&b, coeffs, 0, 16);
bits_nc0 = bs_bits_written(&b);
}
// nC = 4 (Table 9-5(c))
{
uint8_t buf[64];
memset(buf, 0, sizeof(buf));
bs_t b;
bs_init(&b, buf, sizeof(buf));
write_block(&b, coeffs, 4, 16);
bits_nc4 = bs_bits_written(&b);
}
// nC = 8 (Table 9-5(d) - fixed length)
{
uint8_t buf[64];
memset(buf, 0, sizeof(buf));
bs_t b;
bs_init(&b, buf, sizeof(buf));
write_block(&b, coeffs, 8, 16);
bits_nc8 = bs_bits_written(&b);
}
// Different VLC tables should produce different bit counts
// (they might occasionally be the same, but typically differ)
// For this test, we just verify all produce valid output
if (bits_nc0 < 1 || bits_nc4 < 1 || bits_nc8 < 1) {
printf("FAIL: test_bitstream_nc_affects_output - invalid bit counts\n");
return 1;
}
printf("PASS: test_bitstream_nc_affects_output (nC=0:%zu, nC=4:%zu, nC=8:%zu bits)\n",
bits_nc0, bits_nc4, bits_nc8);
return 0;
}
// Test: Verify escape coding for large levels
// Level 100 requires escape coding (level_prefix >= 15)
static int test_bitstream_escape_coding(void) {
uint8_t buf[64];
memset(buf, 0, sizeof(buf));
bs_t b;
bs_init(&b, buf, sizeof(buf));
int16_t coeffs[16] = {100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int tc = write_block(&b, coeffs, 0, 16);
size_t bits = bs_bits_written(&b);
if (tc != 1) {
printf("FAIL: test_bitstream_escape_coding - TC=%d (expected 1)\n", tc);
return 1;
}
// Large level should produce more bits than small level
// Level 100 with escape coding: level_prefix=15 (16 bits) + level_suffix (12 bits)
// Plus coeff_token and total_zeros
if (bits < 25) { // Escape coding produces many bits
printf("FAIL: test_bitstream_escape_coding - too few bits=%zu for escape coding\n", bits);
return 1;
}
printf("PASS: test_bitstream_escape_coding (%zu bits for level=100)\n", bits);
return 0;
}
// Test: Consistency between same coefficients encoded multiple times
// Verifies that encoding is deterministic with zero-initialized buffers
static int test_bitstream_consistency(void) {
int16_t coeffs[16] = {8, -4, 2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
uint8_t buf1[128], buf2[128];
// Both buffers must be zero-initialized since bs_write_u1 clears individual bits
// but doesn't clear trailing bits in the last byte
memset(buf1, 0, sizeof(buf1));
memset(buf2, 0, sizeof(buf2));
bs_t b1, b2;
bs_init(&b1, buf1, sizeof(buf1));
bs_init(&b2, buf2, sizeof(buf2));
int tc1 = write_block(&b1, coeffs, 2, 16);
int tc2 = write_block(&b2, coeffs, 2, 16);
size_t bits1 = bs_bits_written(&b1);
size_t bits2 = bs_bits_written(&b2);
if (tc1 != tc2 || bits1 != bits2) {
printf("FAIL: test_bitstream_consistency - results differ\n");
return 1;
}
// Compare the actual encoded bits (full bytes only to avoid partial byte issues)
size_t full_bytes = bits1 / 8;
if (full_bytes > 0 && memcmp(buf1, buf2, full_bytes) != 0) {
printf("FAIL: test_bitstream_consistency - encoded bytes differ\n");
return 1;
}
// Verify the partial byte if any (mask off unused bits)
if (bits1 % 8 != 0) {
int used_bits = bits1 % 8;
uint8_t mask = (0xFF << (8 - used_bits));
if ((buf1[full_bytes] & mask) != (buf2[full_bytes] & mask)) {
printf("FAIL: test_bitstream_consistency - partial byte differs\n");
return 1;
}
}
printf("PASS: test_bitstream_consistency\n");
return 0;
}
int main(void) {
int errors = 0;
printf("Running CAVLC block writer tests...\n\n");
// Basic functionality tests
errors += test_zero_block();
errors += test_dc_only();
errors += test_trailing_ones();
errors += test_three_trailing_ones();
errors += test_large_level();
errors += test_multiple_coeffs_with_zeros();
errors += test_chroma_dc();
errors += test_full_block();
errors += test_negative_coeffs();
errors += test_nc_variations();
// Bitstream verification tests
printf("\nRunning bitstream verification tests...\n\n");
errors += test_bitstream_zero_block();
errors += test_bitstream_single_dc();
errors += test_bitstream_trailing_ones_signs();
errors += test_bitstream_deterministic();
errors += test_bitstream_nc_affects_output();
errors += test_bitstream_escape_coding();
errors += test_bitstream_consistency();
printf("\n%d test(s) failed\n", errors);
return errors;
}