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

578 lines
19 KiB
C++

#include "mbs_mux_common.h"
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace subcodec::mux;
static int tests_run = 0;
static int tests_passed = 0;
#define CHECK(cond, msg) do { \
tests_run++; \
if (!(cond)) { printf("FAIL: %s\n", msg); } \
else { tests_passed++; } \
} while(0)
/* Test 1: flush_byte writes bytes and inserts EBSP escape */
static void test_flush_byte() {
uint8_t buf[32] = {};
EbspWriter w;
w.out = buf;
w.partial = 0;
w.bits = 0;
w.zero_count = 0;
/* Write 00 00 03 — should insert escape: 00 00 03 03 */
w.flush_byte(0x00);
w.flush_byte(0x00);
w.flush_byte(0x03);
CHECK(w.out - buf == 4, "flush_byte: 00 00 03 → 4 bytes");
CHECK(buf[0] == 0x00, "flush_byte: byte 0 = 0x00");
CHECK(buf[1] == 0x00, "flush_byte: byte 1 = 0x00");
CHECK(buf[2] == 0x03, "flush_byte: byte 2 = 0x03 (escape)");
CHECK(buf[3] == 0x03, "flush_byte: byte 3 = 0x03 (payload)");
/* Continue: write 0x01 — zero_count was reset, no escape */
w.flush_byte(0x01);
CHECK(w.out - buf == 5, "flush_byte: 5 bytes after 0x01");
CHECK(buf[4] == 0x01, "flush_byte: byte 4 = 0x01");
}
/* Test 2: flush_byte with 00 00 00 → needs escape before the third 00 */
static void test_flush_byte_triple_zero() {
uint8_t buf[32] = {};
EbspWriter w;
w.out = buf;
w.partial = 0;
w.bits = 0;
w.zero_count = 0;
w.flush_byte(0x00);
w.flush_byte(0x00);
w.flush_byte(0x00);
CHECK(w.out - buf == 4, "triple zero: 4 bytes output");
CHECK(buf[0] == 0x00, "triple zero: byte 0");
CHECK(buf[1] == 0x00, "triple zero: byte 1");
CHECK(buf[2] == 0x03, "triple zero: byte 2 = escape");
CHECK(buf[3] == 0x00, "triple zero: byte 3 = payload");
}
/* Test 3: write_bits accumulates and flushes bytes */
static void test_write_bits() {
uint8_t buf[32] = {};
EbspWriter w;
w.out = buf;
w.partial = 0;
w.bits = 0;
w.zero_count = 0;
/* Write 0b10110011 (0xB3) as 8 bits */
w.write_bits(0xB3, 8);
CHECK(w.out - buf == 1, "write_bits 8: flushed 1 byte");
CHECK(w.bits == 0, "write_bits 8: 0 bits remaining");
CHECK(buf[0] == 0xB3, "write_bits 8: byte = 0xB3");
/* Write 0b1010 (4 bits) then 0b0011 (4 bits) → 0xA3 */
w.write_bits(0xA, 4);
CHECK(w.bits == 4, "write_bits 4: 4 bits pending");
w.write_bits(0x3, 4);
CHECK(w.out - buf == 2, "write_bits 4+4: flushed 2nd byte");
CHECK(buf[1] == 0xA3, "write_bits 4+4: byte = 0xA3");
}
/* Test 4: write_bits with non-byte-aligned accumulation */
static void test_write_bits_unaligned() {
uint8_t buf[32] = {};
EbspWriter w;
w.out = buf;
w.partial = 0;
w.bits = 0;
w.zero_count = 0;
/* Write 3 bits (0b101), then 13 bits (0b1100001010011) = total 16 bits = 2 bytes */
/* Combined: 101_1100001010011 → 0b1011100001010011 → 0xB853 */
w.write_bits(0x5, 3); /* 101 */
w.write_bits(0x1853, 13); /* 1100001010011 */
CHECK(w.out - buf == 2, "write_bits unaligned: 2 bytes");
CHECK(buf[0] == 0xB8, "write_bits unaligned: byte 0 = 0xB8");
CHECK(buf[1] == 0x53, "write_bits unaligned: byte 1 = 0x53");
}
/* Test 5: write_bits triggers EBSP escape mid-stream */
static void test_write_bits_ebsp() {
uint8_t buf[32] = {};
EbspWriter w;
w.out = buf;
w.partial = 0;
w.bits = 0;
w.zero_count = 0;
/* Write 24 bits: 0x000001 → should produce 00 00 03 01 */
w.write_bits(0x000001, 24);
CHECK(w.out - buf == 4, "write_bits EBSP: 4 bytes (with escape)");
CHECK(buf[0] == 0x00, "write_bits EBSP: byte 0");
CHECK(buf[1] == 0x00, "write_bits EBSP: byte 1");
CHECK(buf[2] == 0x03, "write_bits EBSP: byte 2 = escape");
CHECK(buf[3] == 0x01, "write_bits EBSP: byte 3 = payload");
}
/* Test 6: ue_lut matches bs_write_ue reference for values 0-4095 */
static void test_ue_lut() {
build_ue_lut();
for (uint32_t val = 0; val < 4096; val++) {
/* Reference: use bs_write_ue into a zeroed buffer */
uint8_t ref_buf[8] = {};
bs_t b;
bs_init(&b, ref_buf, sizeof(ref_buf));
bs_write_ue(&b, val);
int ref_bits = static_cast<int>((b.p - b.start) * 8 + (8 - b.bits_left));
/* Extract bit pattern from reference */
uint32_t ref_pattern = 0;
for (int i = 0; i < ref_bits; i++) {
int byte_idx = i / 8;
int bit_idx = 7 - (i % 8);
ref_pattern = (ref_pattern << 1) | ((ref_buf[byte_idx] >> bit_idx) & 1);
}
const auto& entry = ue_lut[val];
if (entry.len != ref_bits || entry.pattern != ref_pattern) {
printf("FAIL: ue_lut[%u]: got pattern=0x%X len=%d, expected pattern=0x%X len=%d\n",
val, entry.pattern, entry.len, ref_pattern, ref_bits);
tests_run++;
return;
}
}
tests_run++;
tests_passed++;
}
/* Test 7: EbspWriter write_ue produces same bytes as bs_write_ue + rbsp_to_ebsp */
static void test_write_ue_output() {
build_ue_lut();
/* Write a sequence of skip runs through both paths and compare */
uint32_t test_vals[] = {0, 1, 2, 5, 10, 42, 100, 255, 1000, 4095};
/* Reference path: bs_write_ue into RBSP, then rbsp_to_ebsp */
uint8_t rbsp[256] = {};
bs_t b;
bs_init(&b, rbsp, sizeof(rbsp));
for (auto val : test_vals) {
bs_write_ue(&b, val);
}
int rbsp_len = bs_pos(&b);
uint8_t ref_ebsp[512] = {};
size_t ref_len = rbsp_to_ebsp(rbsp, rbsp_len, ref_ebsp, sizeof(ref_ebsp));
/* New path: EbspWriter with write_ue */
uint8_t new_ebsp[512] = {};
EbspWriter w;
w.out = new_ebsp;
w.partial = 0;
w.bits = 0;
w.zero_count = 0;
for (auto val : test_vals) {
w.write_ue(val);
}
/* Flush partial byte (pad with zeros, like RBSP trailing) */
if (w.bits > 0) {
w.write_bits(0, 8 - w.bits);
}
size_t new_len = static_cast<size_t>(w.out - new_ebsp);
CHECK(new_len == ref_len, "write_ue output length matches reference");
CHECK(memcmp(new_ebsp, ref_ebsp, ref_len) == 0, "write_ue output bytes match reference");
}
/* Reference helper: write skip_run + blob through old bs_t + rbsp_to_ebsp path */
static size_t ref_skip_blob(uint32_t skip_val, const uint8_t* blob, int blob_bits,
uint8_t* out, size_t out_size) {
uint8_t rbsp[4096] = {};
bs_t b;
bs_init(&b, rbsp, sizeof(rbsp));
bs_write_ue(&b, skip_val);
bs_copy_bits(&b, blob, 0, blob_bits);
/* Pad to byte boundary */
while (((b.p - b.start) * 8 + (8 - b.bits_left)) % 8 != 0) {
bs_write_u1(&b, 0);
}
size_t rbsp_len = static_cast<size_t>(bs_pos(&b));
return rbsp_to_ebsp(rbsp, rbsp_len, out, out_size);
}
/* New path helper: write skip_run + blob through EbspWriter */
static size_t new_skip_blob(uint32_t skip_val, const uint8_t* blob, int blob_bits,
uint8_t* out, size_t out_size) {
EbspWriter w;
w.out = out;
w.partial = 0;
w.bits = 0;
w.zero_count = 0;
w.write_ue(skip_val);
w.copy_blob(blob, blob_bits);
/* Pad to byte boundary */
if (w.bits > 0) {
w.write_bits(0, 8 - w.bits);
}
return static_cast<size_t>(w.out - out);
}
/* Test 8: copy_blob aligned (bits == 0 before blob) */
static void test_copy_blob_aligned() {
build_ue_lut();
uint8_t blob[] = {0xAB, 0xCD, 0xEF, 0x01, 0x23};
int blob_bits = 40;
/* Reference: rbsp_to_ebsp on the raw blob bytes */
uint8_t ref[32] = {};
size_t ref_len = rbsp_to_ebsp(blob, 5, ref, sizeof(ref));
/* New path: copy_blob with bits=0 */
uint8_t out[32] = {};
EbspWriter w;
w.out = out;
w.partial = 0;
w.bits = 0;
w.zero_count = 0;
w.copy_blob(blob, blob_bits);
size_t out_len = static_cast<size_t>(w.out - out);
CHECK(out_len == ref_len, "copy_blob aligned: length matches");
CHECK(memcmp(out, ref, ref_len) == 0, "copy_blob aligned: bytes match");
}
/* Test 9: copy_blob non-aligned (bits != 0 before blob) — various skip runs */
static void test_copy_blob_unaligned() {
build_ue_lut();
/* Test with several skip values to exercise different bit alignments */
uint32_t skip_vals[] = {0, 1, 2, 5, 10, 42, 100};
uint8_t blob[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x02, 0xFF};
int blob_bits = 64; /* 8 full bytes, includes 00 00 02 which triggers EBSP */
for (auto skip : skip_vals) {
uint8_t ref[64] = {};
size_t ref_len = ref_skip_blob(skip, blob, blob_bits, ref, sizeof(ref));
uint8_t out[64] = {};
size_t out_len = new_skip_blob(skip, blob, blob_bits, out, sizeof(out));
char msg[128];
snprintf(msg, sizeof(msg), "copy_blob skip=%u: length matches (ref=%zu new=%zu)",
skip, ref_len, out_len);
CHECK(out_len == ref_len, msg);
snprintf(msg, sizeof(msg), "copy_blob skip=%u: bytes match", skip);
CHECK(memcmp(out, ref, ref_len) == 0, msg);
}
}
/* Test 10: copy_blob with non-byte-aligned blob_bits */
static void test_copy_blob_partial_bits() {
build_ue_lut();
uint8_t blob[] = {0xFF, 0x80}; /* 0xFF followed by 1 bit (MSB of 0x80) */
int blob_bits = 9;
uint32_t skip_vals[] = {0, 3, 7};
for (auto skip : skip_vals) {
uint8_t ref[64] = {};
size_t ref_len = ref_skip_blob(skip, blob, blob_bits, ref, sizeof(ref));
uint8_t out[64] = {};
size_t out_len = new_skip_blob(skip, blob, blob_bits, out, sizeof(out));
char msg[128];
snprintf(msg, sizeof(msg), "copy_blob partial skip=%u: length matches", skip);
CHECK(out_len == ref_len, msg);
snprintf(msg, sizeof(msg), "copy_blob partial skip=%u: bytes match", skip);
CHECK(memcmp(out, ref, ref_len) == 0, msg);
}
}
/* Test 11: copy_blob with multiple skip+blob sequences (simulates real mux) */
static void test_copy_blob_sequence() {
build_ue_lut();
/* Simulate: skip(5) + blob_a + skip(0) + blob_b + skip(12) + blob_c */
uint8_t blob_a[] = {0x12, 0x34, 0x56};
uint8_t blob_b[] = {0x00, 0x00, 0x01, 0xAB}; /* EBSP-triggering content */
uint8_t blob_c[] = {0xFF, 0xEE, 0xDD};
/* Reference path */
uint8_t rbsp[256] = {};
bs_t b;
bs_init(&b, rbsp, sizeof(rbsp));
bs_write_ue(&b, 5);
bs_copy_bits(&b, blob_a, 0, 24);
bs_write_ue(&b, 0);
bs_copy_bits(&b, blob_b, 0, 32);
bs_write_ue(&b, 12);
bs_copy_bits(&b, blob_c, 0, 24);
while (((b.p - b.start) * 8 + (8 - b.bits_left)) % 8 != 0)
bs_write_u1(&b, 0);
size_t rbsp_len = static_cast<size_t>(bs_pos(&b));
uint8_t ref[512] = {};
size_t ref_len = rbsp_to_ebsp(rbsp, rbsp_len, ref, sizeof(ref));
/* New path */
uint8_t out[512] = {};
EbspWriter w;
w.out = out;
w.partial = 0;
w.bits = 0;
w.zero_count = 0;
w.write_ue(5);
w.copy_blob(blob_a, 24);
w.write_ue(0);
w.copy_blob(blob_b, 32);
w.write_ue(12);
w.copy_blob(blob_c, 24);
if (w.bits > 0)
w.write_bits(0, 8 - w.bits);
size_t out_len = static_cast<size_t>(w.out - out);
CHECK(out_len == ref_len, "copy_blob sequence: length matches");
CHECK(memcmp(out, ref, ref_len) == 0, "copy_blob sequence: bytes match");
}
/* Test 12: copy_blob with escape-free blob (no 16+ consecutive zero bits) —
* verify correctness at all 8 bit alignments.
* Uses skip values that produce bit offsets 0-7 after write_ue. */
static void test_copy_blob_escape_free() {
build_ue_lut();
/* Blob with no two consecutive zero bytes — definitely no 16-bit zero run */
uint8_t blob[] = {0xAB, 0x01, 0xCD, 0x02, 0xEF, 0x03, 0x45, 0x67,
0x89, 0x01, 0xAB, 0x02, 0xCD, 0x03, 0xEF, 0x04};
int blob_bits = 128;
/* Test at many skip values to exercise all bit alignments */
uint32_t skip_vals[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 14, 30, 62, 100, 254, 510, 1022};
for (auto skip : skip_vals) {
uint8_t ref[256] = {};
size_t ref_len = ref_skip_blob(skip, blob, blob_bits, ref, sizeof(ref));
uint8_t out[256] = {};
size_t out_len = new_skip_blob(skip, blob, blob_bits, out, sizeof(out));
char msg[128];
snprintf(msg, sizeof(msg), "copy_blob escape-free skip=%u: length (ref=%zu new=%zu)",
skip, ref_len, out_len);
CHECK(out_len == ref_len, msg);
snprintf(msg, sizeof(msg), "copy_blob escape-free skip=%u: bytes match", skip);
CHECK(memcmp(out, ref, ref_len) == 0, msg);
}
}
/* Test 13: copy_blob with incoming zero_count=2 and escape-free blob starting with byte <= 3.
* The fast path must still handle boundary escaping. */
static void test_copy_blob_boundary_escape() {
build_ue_lut();
/* Blob starting with 0x01 — if incoming zero_count >= 2, needs escape */
uint8_t blob[] = {0x01, 0xFF, 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56};
int blob_bits = 64;
/* Manually set up writer with zero_count = 2 (simulating prior 00 00 output) */
uint8_t ref_buf[64] = {};
uint8_t new_buf[64] = {};
/* Reference: flush_byte by byte */
{
EbspWriter w;
w.out = ref_buf;
w.partial = 0;
w.bits = 0;
w.zero_count = 2; /* incoming: two 0x00 bytes were written */
w.copy_blob(blob, blob_bits);
if (w.bits > 0) w.write_bits(0, 8 - w.bits);
}
/* New path should produce identical output */
{
EbspWriter w;
w.out = new_buf;
w.partial = 0;
w.bits = 0;
w.zero_count = 2;
w.copy_blob(blob, blob_bits);
if (w.bits > 0) w.write_bits(0, 8 - w.bits);
}
/* Must be identical — both should have inserted 0x03 before the 0x01 */
CHECK(ref_buf[0] == 0x03, "boundary escape: ref inserts 0x03");
CHECK(ref_buf[1] == 0x01, "boundary escape: ref payload 0x01");
CHECK(memcmp(ref_buf, new_buf, 64) == 0, "boundary escape: new matches ref");
}
/* Test 14: copy_blob with non-aligned writer + escape-free blob, large blob.
* Exercises the bulk shift-copy path. */
static void test_copy_blob_nonaligned_large() {
build_ue_lut();
/* 200-byte blob with no consecutive zero bytes */
uint8_t blob[200];
for (int i = 0; i < 200; i++) {
blob[i] = static_cast<uint8_t>((i * 37 + 13) & 0xFF);
if (blob[i] == 0) blob[i] = 1; /* ensure no zero bytes at all */
}
int blob_bits = 200 * 8;
/* Test all bit offsets 1-7 using different skip values */
/* ue(0)=1bit, ue(1)=3bits, ue(2)=3bits, ue(3)=5bits,
* ue(4)=5bits, ue(5)=5bits, ue(6)=5bits, ue(7)=7bits */
uint32_t skip_vals[] = {0, 1, 3, 7, 15, 31, 63};
for (auto skip : skip_vals) {
uint8_t ref[512] = {};
size_t ref_len = ref_skip_blob(skip, blob, blob_bits, ref, sizeof(ref));
uint8_t out[512] = {};
size_t out_len = new_skip_blob(skip, blob, blob_bits, out, sizeof(out));
char msg[128];
snprintf(msg, sizeof(msg), "copy_blob nonaligned large skip=%u: length (ref=%zu new=%zu)",
skip, ref_len, out_len);
CHECK(out_len == ref_len, msg);
snprintf(msg, sizeof(msg), "copy_blob nonaligned large skip=%u: bytes match", skip);
CHECK(memcmp(out, ref, ref_len) == 0, msg);
}
}
/* Test 15: Directly exercise the 5-arg copy_blob fast path overload.
* Compares output of 5-arg (fast path) vs 2-arg (reference) at various alignments. */
static void test_copy_blob_fast_path_direct() {
build_ue_lut();
/* Escape-free blob: no two consecutive zero bytes */
uint8_t blob[] = {0xAB, 0x01, 0xCD, 0x02, 0xEF, 0x03, 0x45, 0x67,
0x89, 0x01, 0xAB, 0x02, 0xCD, 0x03, 0xEF, 0x04};
int blob_bits = 128;
/* Test at various skip values for different bit alignments */
uint32_t skip_vals[] = {0, 1, 2, 3, 5, 7, 14, 30, 100};
for (auto skip : skip_vals) {
/* Reference: 2-arg copy_blob (old path) */
uint8_t ref[256] = {};
EbspWriter wr;
wr.out = ref;
wr.partial = 0;
wr.bits = 0;
wr.zero_count = 0;
wr.write_ue(skip);
wr.copy_blob(blob, blob_bits); /* 2-arg: old path */
if (wr.bits > 0) wr.write_bits(0, 8 - wr.bits);
size_t ref_len = static_cast<size_t>(wr.out - ref);
/* New: 5-arg copy_blob (fast path, has_long_zero_run=false) */
uint8_t out[256] = {};
EbspWriter wn;
wn.out = out;
wn.partial = 0;
wn.bits = 0;
wn.zero_count = 0;
wn.write_ue(skip);
wn.copy_blob(blob, blob_bits, false, 0, 0); /* 5-arg: fast path */
if (wn.bits > 0) wn.write_bits(0, 8 - wn.bits);
size_t out_len = static_cast<size_t>(wn.out - out);
char msg[128];
snprintf(msg, sizeof(msg), "fast path direct skip=%u: length (ref=%zu new=%zu)",
skip, ref_len, out_len);
CHECK(out_len == ref_len, msg);
snprintf(msg, sizeof(msg), "fast path direct skip=%u: bytes match", skip);
CHECK(memcmp(out, ref, ref_len) == 0, msg);
}
/* Also test with incoming zero_count = 2 (boundary escape case) */
{
uint8_t ref[256] = {};
EbspWriter wr;
wr.out = ref;
wr.partial = 0;
wr.bits = 0;
wr.zero_count = 2;
wr.copy_blob(blob, blob_bits);
if (wr.bits > 0) wr.write_bits(0, 8 - wr.bits);
size_t ref_len = static_cast<size_t>(wr.out - ref);
uint8_t out[256] = {};
EbspWriter wn;
wn.out = out;
wn.partial = 0;
wn.bits = 0;
wn.zero_count = 2;
wn.copy_blob(blob, blob_bits, false, 0, 0);
if (wn.bits > 0) wn.write_bits(0, 8 - wn.bits);
size_t out_len = static_cast<size_t>(wn.out - out);
CHECK(out_len == ref_len, "fast path direct zero_count=2: length matches");
CHECK(memcmp(out, ref, ref_len) == 0, "fast path direct zero_count=2: bytes match");
}
/* Also test with non-aligned + zero_count = 2 */
{
uint8_t ref[256] = {};
EbspWriter wr;
wr.out = ref;
wr.partial = 0x5; /* 3 bits pending */
wr.bits = 3;
wr.zero_count = 2;
wr.copy_blob(blob, blob_bits);
if (wr.bits > 0) wr.write_bits(0, 8 - wr.bits);
size_t ref_len = static_cast<size_t>(wr.out - ref);
uint8_t out[256] = {};
EbspWriter wn;
wn.out = out;
wn.partial = 0x5;
wn.bits = 3;
wn.zero_count = 2;
wn.copy_blob(blob, blob_bits, false, 0, 0);
if (wn.bits > 0) wn.write_bits(0, 8 - wn.bits);
size_t out_len = static_cast<size_t>(wn.out - out);
CHECK(out_len == ref_len, "fast path direct nonaligned+zc2: length matches");
CHECK(memcmp(out, ref, ref_len) == 0, "fast path direct nonaligned+zc2: bytes match");
}
}
int main() {
test_flush_byte();
test_flush_byte_triple_zero();
test_write_bits();
test_write_bits_unaligned();
test_write_bits_ebsp();
test_ue_lut();
test_write_ue_output();
test_copy_blob_aligned();
test_copy_blob_unaligned();
test_copy_blob_partial_bits();
test_copy_blob_sequence();
test_copy_blob_escape_free();
test_copy_blob_boundary_escape();
test_copy_blob_nonaligned_large();
test_copy_blob_fast_path_direct();
printf("%d/%d tests passed\n", tests_passed, tests_run);
if (tests_passed != tests_run) {
printf("FAIL\n");
return 1;
}
printf("PASS\n");
return 0;
}