TAV-DT: slight format change to allow detailed failure mode inspection

This commit is contained in:
minjaesong
2025-12-17 10:02:17 +09:00
parent e893ca2df5
commit c742bc354a
3 changed files with 369 additions and 244 deletions

View File

@@ -1650,29 +1650,29 @@ start of the next packet
uint64 Timecode in nanoseconds
uint32 Offset to video packet
uint32 Reserved (zero-fill)
uint32 CRC-32 of [sync pattern + above]
<packet header end; encoded with rate 256/512 LDPC>
uint32 CRC-32 of above
<packet header end; encoded with rate 224/448 LDPC> // NOTE: sync pattern must not be LDPC-coded
bytes TAD with forward error correction
<TAD header start>
uint16 Sample Count
uint8 Quantiser Bits
uint16 Reserved (zero-fill)
uint32 Compressed Size
uint24 Reed-Solomon Block Count
uint32 CRC-32 of above
<TAD chunk header end; encoded with rate 128/256 LDPC>
<TAD chunk header end; encoded with rate 112/224 LDPC>
<Reed-Solomon (255,223) block start>
bytes TAD (EZBC, no Zstd)
bytes Parity for TAD
<Reed-Solomon (255,223) block end>
bytes TAV with forward error correction
<TAV header start>
uint32 TAV header sync pattern (0xA3F7C91E)
<TAV header start>
uint8 GOP Size (number of frames in this GOP)
uint16 Reserved (zero-fill)
uint32 Compressed Size
uint24 Reed-Solomon Block Count
uint32 CRC-32 of above
<TAV header end; encoded with rate 128/256 LDPC>
<TAV header end; encoded with rate 112/224 LDPC> // NOTE: sync pattern must not be LDPC-coded
<Reed-Solomon (255,223) block start>
bytes TAV (EZBC, no Zstd)
bytes Parity for TAV

View File

@@ -10,21 +10,22 @@
* - LDPC rate 1/2 for headers, Reed-Solomon (255,223) for payloads
*
* Packet structure (revised 2025-12-17):
* - Main header: 32 bytes raw (256 bits) -> 64 bytes LDPC encoded (512 bits, rate 256/512)
* Layout: sync(4) + fps(1) + flags(1) + reserved(2) + size(4) + timecode(8) + offset(4) + reserved(4) + crc(4)
* CRC covers bytes 0-27 (everything except CRC itself)
* - TAD subpacket: header 16 bytes raw (128 bits) -> 32 bytes LDPC (256 bits, rate 128/256), + RS-encoded payload
* Layout: sample_count(2) + quant_bits(1) + reserved(2) + compressed_size(4) + rs_block_count(3) + crc(4)
* - TAV subpacket: header 16 bytes raw (128 bits) -> 32 bytes LDPC (256 bits, rate 128/256), + RS-encoded payload
* Layout: sync(4) + gop_size(1) + compressed_size(4) + rs_block_count(3) + crc(4)
* - Main packet: sync(4) NOT LDPC + header 28 bytes (224 bits) -> 56 bytes LDPC (448 bits, rate 224/448)
* Header layout: fps(1) + flags(1) + reserved(2) + size(4) + timecode(8) + offset(4) + reserved(4) + crc(4)
* CRC covers bytes 0-23 (everything except CRC itself)
* - TAD subpacket: header 14 bytes (112 bits) -> 28 bytes LDPC (224 bits, rate 112/224), + RS-encoded payload
* Layout: sample_count(2) + quant_bits(1) + compressed_size(4) + rs_block_count(3) + crc(4)
* - TAV subpacket: sync(4) NOT LDPC + header 14 bytes (112 bits) -> 28 bytes LDPC (224 bits, rate 112/224), + RS payload
* Header layout: gop_size(1) + reserved(2) + compressed_size(4) + rs_block_count(3) + crc(4)
* - No packet type bytes - always audio then video
*
* Features (revised 2025-12-17):
* - Soft Sync Recovery: Attempts to recover corrupted headers using known values
* - Stage 3 recovery uses TAV subpacket sync pattern (0xA3F7C91E) to locate video data
* - Sync patterns are NOT LDPC-coded for reliable packet boundary detection
*
* Created by CuriousTorvald and Claude on 2025-12-09.
* Revised 2025-12-17 for power-of-two header sizes, subpacket CRCs, and TAV subpacket sync.
* Revised 2025-12-17 for separate sync patterns, power-of-two LDPC sizes, and subpacket CRCs.
*/
#define _POSIX_C_SOURCE 200809L
@@ -67,11 +68,12 @@
#define DT_TEMPORAL_LEVELS 2
// Header sizes (before LDPC encoding) - revised 2025-12-17
// LDPC rates are specified in bits: 256/512 = 256 bits raw -> 512 bits encoded
// Converted to bytes: 32 -> 64 bytes, 16 -> 32 bytes
#define DT_MAIN_HEADER_SIZE 32 // sync(4) + fps(1) + flags(1) + reserved(2) + size(4) + timecode(8) + offset(4) + reserved(4) + crc(4)
#define DT_TAD_HEADER_SIZE 16 // sample_count(2) + quant_bits(1) + reserved(2) + compressed_size(4) + rs_block_count(3) + crc(4)
#define DT_TAV_HEADER_SIZE 16 // sync(4) + gop_size(1) + compressed_size(4) + rs_block_count(3) + crc(4)
// Sync patterns are written separately (not LDPC-coded)
// Main header: 28 bytes raw (224 bits) -> 56 bytes LDPC (448 bits, rate 224/448)
// Subpacket headers: 14 bytes raw (112 bits) -> 28 bytes LDPC (224 bits, rate 112/224)
#define DT_MAIN_HEADER_SIZE 28 // fps(1) + flags(1) + reserved(2) + size(4) + timecode(8) + offset(4) + reserved(4) + crc(4)
#define DT_TAD_HEADER_SIZE 14 // sample_count(2) + quant_bits(1) + compressed_size(4) + rs_block_count(3) + crc(4)
#define DT_TAV_HEADER_SIZE 14 // gop_size(1) + reserved(2) + compressed_size(4) + rs_block_count(3) + crc(4)
// TAV subpacket sync pattern (big endian)
#define TAV_SUBPACKET_SYNC 0xA3F7C91E
@@ -84,6 +86,12 @@ static const int QUALITY_CG[] = {148, 133, 113, 99, 76, 39};
#define MAX_PATH 4096
#define MAX_DECODE_THREADS 16
// Subpacket decode result codes (for detailed statistics tracking)
#define DECODE_OK 0 // Success
#define DECODE_ERR_HEADER_CRC -1 // Header CRC validation failed
#define DECODE_ERR_PAYLOAD_FEC -2 // Payload FEC decode failed (unrecoverable)
#define DECODE_ERR_TRUNCATED -3 // Data truncated or other error
// =============================================================================
// Multithreading Structures
// =============================================================================
@@ -201,6 +209,24 @@ typedef struct {
uint64_t fec_corrections;
uint64_t sync_losses;
// Detailed packet outcome statistics
// Fully decoded (both audio and video present)
uint64_t stat_fully_decoded_intact; // (a) Both A/V intact, no recovery needed
uint64_t stat_fully_decoded_recovered; // (b) Header recovered by soft sync recovery
// Partially decoded (only audio or only video)
uint64_t stat_partial_video_ok_audio_hdr_damaged; // (c) Video OK, audio lost - header CRC fail
uint64_t stat_partial_video_ok_audio_payload_damaged; // (d) Video OK, audio lost - FEC fail
uint64_t stat_partial_video_rec_audio_hdr_damaged; // (e) Video recovered, audio lost - header CRC fail
uint64_t stat_partial_video_rec_audio_payload_damaged; // (f) Video recovered, audio lost - FEC fail
uint64_t stat_partial_audio_ok_video_hdr_damaged; // (g) Audio OK, video lost - header CRC fail
uint64_t stat_partial_audio_ok_video_payload_damaged; // (h) Audio OK, video lost - FEC fail
// Packets lost (no audio or video)
uint64_t stat_lost_sync_recovery_failure; // (j) Main header sync/CRC unrecoverable
uint64_t stat_lost_both_headers_damaged; // (k) Has sync but both subpacket headers damaged
uint64_t stat_lost_payloads_damaged; // (l) All headers OK but both payloads unrecoverable
// Options
int verbose;
int dump_mode; // Just dump packets, don't decode
@@ -236,6 +262,7 @@ typedef struct {
uint64_t last_valid_timecode_ns;
int packets_since_valid_sync;
int soft_sync_recoveries; // Statistics counter
int current_packet_recovered; // Flag: was current packet's main header recovered?
} dt_decoder_t;
// =============================================================================
@@ -461,37 +488,29 @@ typedef struct {
*/
static int attempt_soft_sync_recovery(dt_decoder_t *dec, uint8_t *decoded_header,
dt_packet_header_t *header __attribute__((unused))) {
// CRC is now at offset 28-31 (revised layout 2025-12-17)
// CRC is at offset 24-27 (revised layout 2025-12-17, sync is separate)
uint32_t stored_crc;
memcpy(&stored_crc, decoded_header + 28, 4);
// Get the expected sync pattern (NTSC or PAL based on first packet)
uint32_t expected_sync = dec->is_pal ? TAV_DT_SYNC_PAL : TAV_DT_SYNC_NTSC;
memcpy(&stored_crc, decoded_header + 24, 4);
// === Stage 1 ===
// Substitute sync pattern and zero-fill reserved fields
// Zero-fill reserved fields (sync is now read separately, not in LDPC header)
// Layout: fps(1) + flags(1) + reserved(2) + size(4) + timecode(8) + offset(4) + reserved(4) + crc(4)
uint8_t recovery_header[DT_MAIN_HEADER_SIZE];
memcpy(recovery_header, decoded_header, DT_MAIN_HEADER_SIZE);
// Substitute sync pattern (big-endian)
recovery_header[0] = (expected_sync >> 24) & 0xFF;
recovery_header[1] = (expected_sync >> 16) & 0xFF;
recovery_header[2] = (expected_sync >> 8) & 0xFF;
recovery_header[3] = expected_sync & 0xFF;
// Zero-fill reserved fields (offset 2-3 and 20-23)
recovery_header[2] = 0;
recovery_header[3] = 0;
recovery_header[20] = 0;
recovery_header[21] = 0;
recovery_header[22] = 0;
recovery_header[23] = 0;
// Zero-fill reserved fields (offset 6-7 and 24-27)
recovery_header[6] = 0;
recovery_header[7] = 0;
recovery_header[24] = 0;
recovery_header[25] = 0;
recovery_header[26] = 0;
recovery_header[27] = 0;
// Recalculate CRC over bytes 0-27
uint32_t calculated_crc = calculate_crc32(recovery_header, 28);
// Recalculate CRC over bytes 0-23
uint32_t calculated_crc = calculate_crc32(recovery_header, 24);
if (calculated_crc == stored_crc) {
if (dec->verbose) {
printf(" Soft Sync Recovery Stage 1: SUCCESS (sync/reserved corrected)\n");
printf(" Soft Sync Recovery Stage 1: SUCCESS (reserved fields corrected)\n");
}
// Use recovered header
memcpy(decoded_header, recovery_header, DT_MAIN_HEADER_SIZE);
@@ -502,8 +521,8 @@ static int attempt_soft_sync_recovery(dt_decoder_t *dec, uint8_t *decoded_header
// === Stage 2 ===
// Also substitute framerate, flags, and timecode with last known values
if (dec->packets_processed > 0) {
recovery_header[4] = dec->last_valid_framerate;
recovery_header[5] = dec->last_valid_flags;
recovery_header[0] = dec->last_valid_framerate;
recovery_header[1] = dec->last_valid_flags;
// Calculate expected timecode based on last known timecode + GOP duration
// GOP duration = 16 frames / framerate
@@ -511,14 +530,14 @@ static int attempt_soft_sync_recovery(dt_decoder_t *dec, uint8_t *decoded_header
uint64_t expected_timecode = dec->last_valid_timecode_ns +
(dec->packets_since_valid_sync + 1) * gop_duration_ns;
// Write expected timecode to bytes 12-19
memcpy(recovery_header + 12, &expected_timecode, 8);
// Write expected timecode to bytes 8-15
memcpy(recovery_header + 8, &expected_timecode, 8);
// Recalculate CRC
calculated_crc = calculate_crc32(recovery_header, 28);
calculated_crc = calculate_crc32(recovery_header, 24);
if (calculated_crc == stored_crc) {
if (dec->verbose) {
printf(" Soft Sync Recovery Stage 2: SUCCESS (sync/reserved/fps/flags/timecode corrected)\n");
printf(" Soft Sync Recovery Stage 2: SUCCESS (reserved/fps/flags/timecode corrected)\n");
printf(" Reconstructed timecode: %.3f s\n", expected_timecode / 1000000000.0);
}
// Use recovered header
@@ -540,13 +559,28 @@ static int attempt_soft_sync_recovery(dt_decoder_t *dec, uint8_t *decoded_header
}
static int read_and_decode_header(dt_decoder_t *dec, dt_packet_header_t *header) {
// Read LDPC-encoded header (64 bytes = 32 bytes * 2, rate 256/512 bits)
// Read sync pattern first (4 bytes, NOT LDPC-coded)
uint8_t sync_bytes[4];
size_t bytes_read = fread(sync_bytes, 1, 4, dec->input_fp);
if (bytes_read < 4) return -1;
dec->bytes_read += 4;
header->sync_pattern = ((uint32_t)sync_bytes[0] << 24) | ((uint32_t)sync_bytes[1] << 16) |
((uint32_t)sync_bytes[2] << 8) | sync_bytes[3];
// Verify sync pattern early
int sync_valid = (header->sync_pattern == TAV_DT_SYNC_NTSC || header->sync_pattern == TAV_DT_SYNC_PAL);
if (!sync_valid && dec->verbose) {
fprintf(stderr, "Warning: Invalid sync pattern 0x%08X\n", header->sync_pattern);
}
// Read LDPC-encoded header (56 bytes = 28 bytes * 2, rate 224/448 bits)
uint8_t encoded_header[DT_MAIN_HEADER_SIZE * 2];
size_t bytes_read = fread(encoded_header, 1, DT_MAIN_HEADER_SIZE * 2, dec->input_fp);
bytes_read = fread(encoded_header, 1, DT_MAIN_HEADER_SIZE * 2, dec->input_fp);
if (bytes_read < DT_MAIN_HEADER_SIZE * 2) return -1;
dec->bytes_read += DT_MAIN_HEADER_SIZE * 2;
// LDPC decode header (64 bytes -> 32 bytes)
// LDPC decode header (56 bytes -> 28 bytes)
uint8_t decoded_header[DT_MAIN_HEADER_SIZE];
int ldpc_result = ldpc_decode(encoded_header, DT_MAIN_HEADER_SIZE * 2, decoded_header);
if (ldpc_result < 0) {
@@ -560,26 +594,19 @@ static int read_and_decode_header(dt_decoder_t *dec, dt_packet_header_t *header)
}
// Parse header fields (revised layout 2025-12-17)
// Layout: sync(4) + fps(1) + flags(1) + reserved(2) + size(4) + timecode(8) + offset(4) + reserved(4) + crc(4)
header->sync_pattern = ((uint32_t)decoded_header[0] << 24) | ((uint32_t)decoded_header[1] << 16) |
((uint32_t)decoded_header[2] << 8) | decoded_header[3];
header->framerate = decoded_header[4];
header->flags = decoded_header[5];
header->reserved = decoded_header[6] | ((uint16_t)decoded_header[7] << 8);
memcpy(&header->packet_size, decoded_header + 8, 4);
memcpy(&header->timecode_ns, decoded_header + 12, 8); // At offset 12
memcpy(&header->offset_to_video, decoded_header + 20, 4); // At offset 20
// Reserved at offset 24-27 (ignored)
memcpy(&header->crc32, decoded_header + 28, 4); // At offset 28
// Layout: fps(1) + flags(1) + reserved(2) + size(4) + timecode(8) + offset(4) + reserved(4) + crc(4) = 28 bytes
// (sync is read separately above)
header->framerate = decoded_header[0];
header->flags = decoded_header[1];
header->reserved = decoded_header[2] | ((uint16_t)decoded_header[3] << 8);
memcpy(&header->packet_size, decoded_header + 4, 4);
memcpy(&header->timecode_ns, decoded_header + 8, 8); // At offset 8
memcpy(&header->offset_to_video, decoded_header + 16, 4); // At offset 16
// Reserved at offset 20-23 (ignored)
memcpy(&header->crc32, decoded_header + 24, 4); // At offset 24
// Verify sync pattern
int sync_valid = (header->sync_pattern == TAV_DT_SYNC_NTSC || header->sync_pattern == TAV_DT_SYNC_PAL);
if (!sync_valid && dec->verbose) {
fprintf(stderr, "Warning: Invalid sync pattern 0x%08X\n", header->sync_pattern);
}
// Verify CRC-32 (covers bytes 0-27: sync + fps + flags + reserved + size + timecode + offset + reserved)
uint32_t calculated_crc = calculate_crc32(decoded_header, 28);
// Verify CRC-32 (covers bytes 0-23: fps + flags + reserved + size + timecode + offset + reserved)
uint32_t calculated_crc = calculate_crc32(decoded_header, 24);
int crc_valid = (calculated_crc == header->crc32);
if (!crc_valid) {
@@ -591,17 +618,16 @@ static int read_and_decode_header(dt_decoder_t *dec, dt_packet_header_t *header)
// Attempt Soft Sync Recovery
if (dec->packets_processed > 0 && attempt_soft_sync_recovery(dec, decoded_header, header)) {
// Re-parse header from recovered data (revised layout 2025-12-17)
header->sync_pattern = ((uint32_t)decoded_header[0] << 24) | ((uint32_t)decoded_header[1] << 16) |
((uint32_t)decoded_header[2] << 8) | decoded_header[3];
header->framerate = decoded_header[4];
header->flags = decoded_header[5];
header->reserved = decoded_header[6] | ((uint16_t)decoded_header[7] << 8);
memcpy(&header->packet_size, decoded_header + 8, 4);
memcpy(&header->timecode_ns, decoded_header + 12, 8);
memcpy(&header->offset_to_video, decoded_header + 20, 4);
// Reserved at offset 24-27 (ignored)
memcpy(&header->crc32, decoded_header + 28, 4);
header->framerate = decoded_header[0];
header->flags = decoded_header[1];
header->reserved = decoded_header[2] | ((uint16_t)decoded_header[3] << 8);
memcpy(&header->packet_size, decoded_header + 4, 4);
memcpy(&header->timecode_ns, decoded_header + 8, 8);
memcpy(&header->offset_to_video, decoded_header + 16, 4);
// Reserved at offset 20-23 (ignored)
memcpy(&header->crc32, decoded_header + 24, 4);
crc_valid = 1; // Recovery succeeded
dec->current_packet_recovered = 1; // Track for detailed statistics
} else {
dec->crc_errors++;
dec->packets_since_valid_sync++;
@@ -611,12 +637,14 @@ static int read_and_decode_header(dt_decoder_t *dec, dt_packet_header_t *header)
fprintf(stderr, "Warning: Packet discarded due to unrecoverable CRC error\n");
}
dec->sync_losses++;
dec->stat_lost_sync_recovery_failure++; // (j) Sync recovery failure
return -2;
}
}
if (!sync_valid) {
dec->sync_losses++;
dec->stat_lost_sync_recovery_failure++; // (j) Sync recovery failure
return -2;
}
@@ -871,12 +899,12 @@ static int decode_audio_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
size_t *consumed, size_t *samples_written) {
*samples_written = 0;
// Minimum: 32 byte LDPC header (16 bytes * 2, rate 128/256 bits)
// Minimum: 28 byte LDPC header (14 bytes * 2, rate 112/224 bits)
if (data_len < DT_TAD_HEADER_SIZE * 2) return -1;
size_t offset = 0;
// LDPC decode TAD header (32 bytes -> 16 bytes)
// LDPC decode TAD header (28 bytes -> 14 bytes)
uint8_t decoded_tad_header[DT_TAD_HEADER_SIZE];
int ldpc_result = ldpc_decode(data + offset, DT_TAD_HEADER_SIZE * 2, decoded_tad_header);
if (ldpc_result < 0) {
@@ -890,7 +918,7 @@ static int decode_audio_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
offset += DT_TAD_HEADER_SIZE * 2;
// Parse TAD header (revised layout 2025-12-17)
// Layout: sample_count(2) + quant_bits(1) + reserved(2) + compressed_size(4) + rs_block_count(3) + crc(4)
// Layout: sample_count(2) + quant_bits(1) + compressed_size(4) + rs_block_count(3) + crc(4) = 14 bytes
uint16_t sample_count;
uint8_t quant_bits;
uint32_t compressed_size;
@@ -899,16 +927,15 @@ static int decode_audio_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
memcpy(&sample_count, decoded_tad_header, 2);
quant_bits = decoded_tad_header[2];
// Reserved at offset 3-4 (ignored)
memcpy(&compressed_size, decoded_tad_header + 5, 4);
// uint24 rs_block_count (little endian) at offset 9-11
rs_block_count = decoded_tad_header[9] |
((uint32_t)decoded_tad_header[10] << 8) |
((uint32_t)decoded_tad_header[11] << 16);
memcpy(&stored_crc, decoded_tad_header + 12, 4);
memcpy(&compressed_size, decoded_tad_header + 3, 4);
// uint24 rs_block_count (little endian) at offset 7-9
rs_block_count = decoded_tad_header[7] |
((uint32_t)decoded_tad_header[8] << 8) |
((uint32_t)decoded_tad_header[9] << 16);
memcpy(&stored_crc, decoded_tad_header + 10, 4);
// Verify CRC-32 (covers bytes 0-11)
uint32_t calculated_crc = calculate_crc32(decoded_tad_header, 12);
// Verify CRC-32 (covers bytes 0-9)
uint32_t calculated_crc = calculate_crc32(decoded_tad_header, 10);
int tad_header_valid = (calculated_crc == stored_crc);
if (!tad_header_valid) {
if (dec->verbose) {
@@ -919,7 +946,7 @@ static int decode_audio_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
// Cannot trust header data - skip audio decoding entirely
// Error concealment will insert silence
*consumed = offset;
return -1;
return DECODE_ERR_HEADER_CRC;
}
if (dec->verbose) {
@@ -939,13 +966,13 @@ static int decode_audio_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
compressed_size, max_data_size);
}
*consumed = offset;
return -1;
return DECODE_ERR_HEADER_CRC; // Header data invalid
}
// Handle empty audio packet (no samples in this GOP)
if (compressed_size == 0 || rs_block_count == 0 || sample_count == 0) {
*consumed = offset;
return 0; // Successfully processed empty audio packet
return DECODE_OK; // Successfully processed empty audio packet
}
if (offset + rs_total > data_len) {
@@ -953,18 +980,18 @@ static int decode_audio_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
fprintf(stderr, "Warning: Audio packet truncated\n");
}
*consumed = data_len;
return -1; // Unrecoverable
return DECODE_ERR_TRUNCATED;
}
// RS decode payload
uint8_t *rs_data = malloc(rs_total);
if (!rs_data) return -1;
if (!rs_data) return DECODE_ERR_TRUNCATED;
memcpy(rs_data, data + offset, rs_total);
uint8_t *decoded_payload = malloc(compressed_size);
if (!decoded_payload) {
free(rs_data);
return -1;
return DECODE_ERR_TRUNCATED;
}
int fec_result = decode_fec_blocks(rs_data, rs_total, decoded_payload, compressed_size, dec->fec_mode);
@@ -975,7 +1002,7 @@ static int decode_audio_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
free(rs_data);
free(decoded_payload);
*consumed = offset + rs_total;
return -1; // Unrecoverable - FEC failed
return DECODE_ERR_PAYLOAD_FEC;
} else if (fec_result > 0) {
dec->fec_corrections += fec_result;
}
@@ -1013,7 +1040,7 @@ static int decode_audio_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
free(rs_data);
free(decoded_payload);
*consumed = offset + rs_total;
return -1; // Unrecoverable - TAD decode failed
return DECODE_ERR_PAYLOAD_FEC; // Treat TAD decode failure as payload error
}
free(pcmu8_output);
@@ -1023,7 +1050,7 @@ static int decode_audio_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
offset += rs_total;
*consumed = offset;
return 0; // Success
return DECODE_OK;
}
/**
@@ -1033,12 +1060,26 @@ static int decode_video_subpacket_mt(dt_decoder_t *dec, const uint8_t *data, siz
size_t *consumed, int *frames_written) {
*frames_written = 0;
// Minimum: 32 byte LDPC header (16 bytes * 2, rate 128/256 bits)
if (data_len < DT_TAV_HEADER_SIZE * 2) return -1;
// Minimum: 4 byte sync + 28 byte LDPC header (14 bytes * 2, rate 112/224 bits)
if (data_len < 4 + DT_TAV_HEADER_SIZE * 2) return -1;
size_t offset = 0;
// LDPC decode TAV header (32 bytes -> 16 bytes)
// Read TAV sync pattern (4 bytes, NOT LDPC-coded)
uint32_t subpacket_sync = ((uint32_t)data[offset] << 24) |
((uint32_t)data[offset + 1] << 16) |
((uint32_t)data[offset + 2] << 8) |
data[offset + 3];
offset += 4;
// Verify sync pattern early
int sync_valid = (subpacket_sync == TAV_SUBPACKET_SYNC);
if (!sync_valid && dec->verbose) {
fprintf(stderr, "Warning: TAV subpacket sync mismatch (MT) (expected 0x%08X, got 0x%08X)\n",
TAV_SUBPACKET_SYNC, subpacket_sync);
}
// LDPC decode TAV header (28 bytes -> 14 bytes)
uint8_t decoded_tav_header[DT_TAV_HEADER_SIZE];
int ldpc_result = ldpc_decode(data + offset, DT_TAV_HEADER_SIZE * 2, decoded_tav_header);
if (ldpc_result < 0) {
@@ -1052,32 +1093,24 @@ static int decode_video_subpacket_mt(dt_decoder_t *dec, const uint8_t *data, siz
offset += DT_TAV_HEADER_SIZE * 2;
// Parse TAV header (revised layout 2025-12-17)
// Layout: sync(4) + gop_size(1) + compressed_size(4) + rs_block_count(3) + crc(4)
uint32_t subpacket_sync = ((uint32_t)decoded_tav_header[0] << 24) |
((uint32_t)decoded_tav_header[1] << 16) |
((uint32_t)decoded_tav_header[2] << 8) |
decoded_tav_header[3];
uint8_t gop_size = decoded_tav_header[4];
// Layout: gop_size(1) + reserved(2) + compressed_size(4) + rs_block_count(3) + crc(4) = 14 bytes
// (sync is read separately above)
uint8_t gop_size = decoded_tav_header[0];
// Reserved at offset 1-2 (ignored)
uint32_t compressed_size;
uint32_t rs_block_count;
uint32_t stored_crc;
memcpy(&compressed_size, decoded_tav_header + 5, 4);
rs_block_count = decoded_tav_header[9] |
((uint32_t)decoded_tav_header[10] << 8) |
((uint32_t)decoded_tav_header[11] << 16);
memcpy(&stored_crc, decoded_tav_header + 12, 4);
memcpy(&compressed_size, decoded_tav_header + 3, 4);
rs_block_count = decoded_tav_header[7] |
((uint32_t)decoded_tav_header[8] << 8) |
((uint32_t)decoded_tav_header[9] << 16);
memcpy(&stored_crc, decoded_tav_header + 10, 4);
// Verify sync pattern and CRC-32 (covers bytes 0-11)
int sync_valid = (subpacket_sync == TAV_SUBPACKET_SYNC);
uint32_t calculated_crc = calculate_crc32(decoded_tav_header, 12);
// Verify CRC-32 (covers bytes 0-9)
uint32_t calculated_crc = calculate_crc32(decoded_tav_header, 10);
int crc_valid = (calculated_crc == stored_crc);
if (!sync_valid && dec->verbose) {
fprintf(stderr, "Warning: TAV subpacket sync mismatch (MT) (expected 0x%08X, got 0x%08X)\n",
TAV_SUBPACKET_SYNC, subpacket_sync);
}
if (!crc_valid) {
if (dec->verbose) {
fprintf(stderr, "Warning: TAV header CRC mismatch (MT) (expected 0x%08X, got 0x%08X) - skipping video\n",
@@ -1086,7 +1119,7 @@ static int decode_video_subpacket_mt(dt_decoder_t *dec, const uint8_t *data, siz
dec->crc_errors++;
// Cannot trust header data - skip video decoding entirely
*consumed = offset;
return -1;
return DECODE_ERR_HEADER_CRC;
}
// Calculate RS payload size
@@ -1100,23 +1133,23 @@ static int decode_video_subpacket_mt(dt_decoder_t *dec, const uint8_t *data, siz
compressed_size, max_data_size);
}
*consumed = offset;
return -1;
return DECODE_ERR_HEADER_CRC; // Header data invalid
}
if (offset + rs_total > data_len) {
*consumed = data_len;
return -1; // Unrecoverable
return DECODE_ERR_TRUNCATED;
}
// RS decode payload
uint8_t *rs_data = malloc(rs_total);
if (!rs_data) return -1;
if (!rs_data) return DECODE_ERR_TRUNCATED;
memcpy(rs_data, data + offset, rs_total);
uint8_t *decoded_payload = malloc(compressed_size);
if (!decoded_payload) {
free(rs_data);
return -1;
return DECODE_ERR_TRUNCATED;
}
int fec_result = decode_fec_blocks(rs_data, rs_total, decoded_payload, compressed_size, dec->fec_mode);
@@ -1127,7 +1160,7 @@ static int decode_video_subpacket_mt(dt_decoder_t *dec, const uint8_t *data, siz
free(rs_data);
free(decoded_payload);
*consumed = offset + rs_total;
return -1; // Unrecoverable - FEC failed
return DECODE_ERR_PAYLOAD_FEC;
} else if (fec_result > 0) {
dec->fec_corrections += fec_result;
}
@@ -1141,7 +1174,7 @@ static int decode_video_subpacket_mt(dt_decoder_t *dec, const uint8_t *data, siz
// Fall back to single-threaded decoding for this packet
free(decoded_payload);
*consumed = offset + rs_total;
return -1;
return DECODE_ERR_TRUNCATED; // Treat as truncated/other error
}
if (dec->verbose) {
printf("Initialized multithreaded decoding: %d threads\n", dec->num_threads);
@@ -1230,19 +1263,33 @@ static int decode_video_subpacket_mt(dt_decoder_t *dec, const uint8_t *data, siz
*consumed = offset;
*frames_written = gop_size; // Optimistic - assume decode will succeed
return 0; // Success - job submitted
return DECODE_OK; // Success - job submitted
}
static int decode_video_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t data_len,
size_t *consumed, int *frames_written) {
*frames_written = 0;
// Minimum: 32 byte LDPC header (16 bytes * 2, rate 128/256 bits)
if (data_len < DT_TAV_HEADER_SIZE * 2) return -1;
// Minimum: 4 byte sync + 28 byte LDPC header (14 bytes * 2, rate 112/224 bits)
if (data_len < 4 + DT_TAV_HEADER_SIZE * 2) return -1;
size_t offset = 0;
// LDPC decode TAV header (32 bytes -> 16 bytes)
// Read TAV sync pattern (4 bytes, NOT LDPC-coded)
uint32_t subpacket_sync = ((uint32_t)data[offset] << 24) |
((uint32_t)data[offset + 1] << 16) |
((uint32_t)data[offset + 2] << 8) |
data[offset + 3];
offset += 4;
// Verify sync pattern early
int sync_valid = (subpacket_sync == TAV_SUBPACKET_SYNC);
if (!sync_valid && dec->verbose) {
fprintf(stderr, "Warning: TAV subpacket sync mismatch (expected 0x%08X, got 0x%08X)\n",
TAV_SUBPACKET_SYNC, subpacket_sync);
}
// LDPC decode TAV header (28 bytes -> 14 bytes)
uint8_t decoded_tav_header[DT_TAV_HEADER_SIZE];
int ldpc_result = ldpc_decode(data + offset, DT_TAV_HEADER_SIZE * 2, decoded_tav_header);
if (ldpc_result < 0) {
@@ -1256,33 +1303,25 @@ static int decode_video_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
offset += DT_TAV_HEADER_SIZE * 2;
// Parse TAV header (revised layout 2025-12-17)
// Layout: sync(4) + gop_size(1) + compressed_size(4) + rs_block_count(3) + crc(4)
uint32_t subpacket_sync = ((uint32_t)decoded_tav_header[0] << 24) |
((uint32_t)decoded_tav_header[1] << 16) |
((uint32_t)decoded_tav_header[2] << 8) |
decoded_tav_header[3];
uint8_t gop_size = decoded_tav_header[4];
// Layout: gop_size(1) + reserved(2) + compressed_size(4) + rs_block_count(3) + crc(4) = 14 bytes
// (sync is read separately above)
uint8_t gop_size = decoded_tav_header[0];
// Reserved at offset 1-2 (ignored)
uint32_t compressed_size;
uint32_t rs_block_count;
uint32_t stored_crc;
memcpy(&compressed_size, decoded_tav_header + 5, 4);
// uint24 rs_block_count (little endian) at offset 9-11
rs_block_count = decoded_tav_header[9] |
((uint32_t)decoded_tav_header[10] << 8) |
((uint32_t)decoded_tav_header[11] << 16);
memcpy(&stored_crc, decoded_tav_header + 12, 4);
memcpy(&compressed_size, decoded_tav_header + 3, 4);
// uint24 rs_block_count (little endian) at offset 7-9
rs_block_count = decoded_tav_header[7] |
((uint32_t)decoded_tav_header[8] << 8) |
((uint32_t)decoded_tav_header[9] << 16);
memcpy(&stored_crc, decoded_tav_header + 10, 4);
// Verify sync pattern and CRC-32 (covers bytes 0-11)
int sync_valid = (subpacket_sync == TAV_SUBPACKET_SYNC);
uint32_t calculated_crc = calculate_crc32(decoded_tav_header, 12);
// Verify CRC-32 (covers bytes 0-9)
uint32_t calculated_crc = calculate_crc32(decoded_tav_header, 10);
int crc_valid = (calculated_crc == stored_crc);
if (!sync_valid && dec->verbose) {
fprintf(stderr, "Warning: TAV subpacket sync mismatch (expected 0x%08X, got 0x%08X)\n",
TAV_SUBPACKET_SYNC, subpacket_sync);
}
if (!crc_valid) {
if (dec->verbose) {
fprintf(stderr, "Warning: TAV header CRC mismatch (expected 0x%08X, got 0x%08X) - skipping video\n",
@@ -1292,7 +1331,7 @@ static int decode_video_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
// Cannot trust header data - skip video decoding entirely
// Error concealment will use freeze frame
*consumed = offset;
return -1;
return DECODE_ERR_HEADER_CRC;
}
if (dec->verbose) {
@@ -1311,7 +1350,7 @@ static int decode_video_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
compressed_size, max_data_size);
}
*consumed = offset;
return -1;
return DECODE_ERR_HEADER_CRC; // Header data invalid
}
if (offset + rs_total > data_len) {
@@ -1319,18 +1358,18 @@ static int decode_video_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
fprintf(stderr, "Warning: Video packet truncated\n");
}
*consumed = data_len;
return -1; // Unrecoverable
return DECODE_ERR_TRUNCATED;
}
// RS decode payload
uint8_t *rs_data = malloc(rs_total);
if (!rs_data) return -1;
if (!rs_data) return DECODE_ERR_TRUNCATED;
memcpy(rs_data, data + offset, rs_total);
uint8_t *decoded_payload = malloc(compressed_size);
if (!decoded_payload) {
free(rs_data);
return -1;
return DECODE_ERR_TRUNCATED;
}
int fec_result = decode_fec_blocks(rs_data, rs_total, decoded_payload, compressed_size, dec->fec_mode);
@@ -1341,7 +1380,7 @@ static int decode_video_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
free(rs_data);
free(decoded_payload);
*consumed = offset + rs_total;
return -1; // Unrecoverable - FEC failed
return DECODE_ERR_PAYLOAD_FEC;
} else if (fec_result > 0) {
dec->fec_corrections += fec_result;
}
@@ -1444,7 +1483,7 @@ static int decode_video_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
offset += rs_total;
*consumed = offset;
return 0; // Success
return DECODE_OK;
}
// =============================================================================
@@ -1780,11 +1819,13 @@ static int process_packet(dt_decoder_t *dec) {
// Decode audio and video
size_t audio_samples_written = 0;
int video_frames_written = 0;
int audio_result = DECODE_OK;
int video_result = DECODE_OK;
// Process TAD subpacket (audio comes first, no type byte)
size_t tad_consumed = 0;
if (header.offset_to_video > 0) {
decode_audio_subpacket(dec, packet_data, header.offset_to_video,
audio_result = decode_audio_subpacket(dec, packet_data, header.offset_to_video,
&tad_consumed, &audio_samples_written);
}
@@ -1792,19 +1833,66 @@ static int process_packet(dt_decoder_t *dec) {
if (header.offset_to_video < header.packet_size) {
size_t tav_consumed = 0;
if (dec->num_threads > 1) {
decode_video_subpacket_mt(dec, packet_data + header.offset_to_video,
video_result = decode_video_subpacket_mt(dec, packet_data + header.offset_to_video,
header.packet_size - header.offset_to_video,
&tav_consumed, &video_frames_written);
} else {
decode_video_subpacket(dec, packet_data + header.offset_to_video,
video_result = decode_video_subpacket(dec, packet_data + header.offset_to_video,
header.packet_size - header.offset_to_video,
&tav_consumed, &video_frames_written);
}
}
// Categorize packet outcome for detailed statistics
int audio_ok = (audio_result == DECODE_OK);
int video_ok = (video_result == DECODE_OK);
int audio_hdr_fail = (audio_result == DECODE_ERR_HEADER_CRC);
int video_hdr_fail = (video_result == DECODE_ERR_HEADER_CRC);
if (audio_ok && video_ok) {
// Fully decoded
if (dec->current_packet_recovered) {
dec->stat_fully_decoded_recovered++; // (b)
} else {
dec->stat_fully_decoded_intact++; // (a)
}
} else if (video_ok && !audio_ok) {
// Video OK, audio lost
if (dec->current_packet_recovered) {
// Video recovered by soft sync
if (audio_hdr_fail) {
dec->stat_partial_video_rec_audio_hdr_damaged++; // (e)
} else {
dec->stat_partial_video_rec_audio_payload_damaged++; // (f)
}
} else {
// Video intact
if (audio_hdr_fail) {
dec->stat_partial_video_ok_audio_hdr_damaged++; // (c)
} else {
dec->stat_partial_video_ok_audio_payload_damaged++; // (d)
}
}
} else if (audio_ok && !video_ok) {
// Audio OK, video lost
if (video_hdr_fail) {
dec->stat_partial_audio_ok_video_hdr_damaged++; // (g)
} else {
dec->stat_partial_audio_ok_video_payload_damaged++; // (h)
}
} else {
// Both audio and video lost
if (audio_hdr_fail && video_hdr_fail) {
dec->stat_lost_both_headers_damaged++; // (k)
} else {
dec->stat_lost_payloads_damaged++; // (l)
}
}
// Update timecode tracking
dec->last_timecode_ns = header.timecode_ns;
dec->packets_processed++;
dec->current_packet_recovered = 0; // Reset for next packet
if (!dec->verbose && dec->packets_processed % 10 == 0) {
fprintf(stderr, "\rDecoding packet %lu, frames: %lu...",
@@ -1915,14 +2003,50 @@ static int run_decoder(dt_decoder_t *dec) {
}
fprintf(stderr, "\n");
printf("\nDecoding complete:\n");
printf(" Packets processed: %lu\n", dec->packets_processed);
printf(" Frames decoded: %lu\n", dec->frames_decoded);
printf(" Bytes read: %lu\n", dec->bytes_read);
printf(" FEC corrections: %lu\n", dec->fec_corrections);
printf(" CRC errors: %lu\n", dec->crc_errors);
printf(" Soft sync recoveries: %d\n", dec->soft_sync_recoveries);
printf(" Sync losses: %lu\n", dec->sync_losses);
// Calculate summary totals
uint64_t fully_decoded = dec->stat_fully_decoded_intact + dec->stat_fully_decoded_recovered;
uint64_t partially_decoded = dec->stat_partial_video_ok_audio_hdr_damaged +
dec->stat_partial_video_ok_audio_payload_damaged +
dec->stat_partial_video_rec_audio_hdr_damaged +
dec->stat_partial_video_rec_audio_payload_damaged +
dec->stat_partial_audio_ok_video_hdr_damaged +
dec->stat_partial_audio_ok_video_payload_damaged;
uint64_t packets_lost = dec->stat_lost_sync_recovery_failure +
dec->stat_lost_both_headers_damaged +
dec->stat_lost_payloads_damaged;
printf("\n=== Decoding Statistics ===\n");
printf("Total packets: %lu\n", dec->packets_processed + packets_lost);
printf("Frames decoded: %lu\n", dec->frames_decoded);
printf("Bytes read: %lu\n", dec->bytes_read);
printf("FEC corrections: %lu\n", dec->fec_corrections);
printf("\n--- Packet Outcome Breakdown ---\n");
printf("Packets fully decoded (audio and video present): %lu\n", fully_decoded);
printf(" (a) Packets intact: %lu\n", dec->stat_fully_decoded_intact);
printf(" (b) Recovered by soft sync recovery: %lu\n", dec->stat_fully_decoded_recovered);
printf("Packets partially decoded (audio or video only): %lu\n", partially_decoded);
printf(" (c) Intact video, audio lost due to subpacket header damage: %lu\n",
dec->stat_partial_video_ok_audio_hdr_damaged);
printf(" (d) Intact video, audio lost due to payload damage: %lu\n",
dec->stat_partial_video_ok_audio_payload_damaged);
printf(" (e) Video resync'd by soft sync recovery, audio lost due to subpacket header damage: %lu\n",
dec->stat_partial_video_rec_audio_hdr_damaged);
printf(" (f) Video resync'd by soft sync recovery, audio lost due to payload damage: %lu\n",
dec->stat_partial_video_rec_audio_payload_damaged);
printf(" (g) Intact audio, video lost due to subpacket header damage: %lu\n",
dec->stat_partial_audio_ok_video_hdr_damaged);
printf(" (h) Intact audio, video lost due to payload damage: %lu\n",
dec->stat_partial_audio_ok_video_payload_damaged);
printf("Packets lost (no audio and video): %lu\n", packets_lost);
printf(" (j) Sync recovery failure: %lu\n", dec->stat_lost_sync_recovery_failure);
printf(" (k) Has sync but both subpacket headers damaged: %lu\n", dec->stat_lost_both_headers_damaged);
printf(" (l) All headers intact but damaged payload: %lu\n", dec->stat_lost_payloads_damaged);
printf("===========================\n");
// Mux output files
mux_output(dec);

View File

@@ -63,12 +63,9 @@
#define DT_SPATIAL_LEVELS 4
#define DT_TEMPORAL_LEVELS 2
// Header sizes (before LDPC encoding) - revised 2025-12-17
// LDPC rates are specified in bits: 256/512 = 256 bits raw -> 512 bits encoded
// Converted to bytes: 32 -> 64 bytes, 16 -> 32 bytes
#define DT_MAIN_HEADER_SIZE 32 // sync(4) + fps(1) + flags(1) + reserved(2) + size(4) + timecode(8) + offset(4) + reserved(4) + crc(4)
#define DT_TAD_HEADER_SIZE 16 // sample_count(2) + quant_bits(1) + reserved(2) + compressed_size(4) + rs_block_count(3) + crc(4)
#define DT_TAV_HEADER_SIZE 16 // sync(4) + gop_size(1) + compressed_size(4) + rs_block_count(3) + crc(4)
#define DT_MAIN_HEADER_SIZE 28 // fps(1) + flags(1) + reserved(2) + size(4) + timecode(8) + offset(4) + reserved(4) + crc(4)
#define DT_TAD_HEADER_SIZE 14 // sample_count(2) + quant_bits(1) + compressed_size(4) + rs_block_count(3) + crc(4)
#define DT_TAV_HEADER_SIZE 14 // gop_size(1) + reserved(2) + compressed_size(4) + rs_block_count(3) + crc(4)
// TAV subpacket sync pattern (big endian)
#define TAV_SUBPACKET_SYNC 0xA3F7C91E
@@ -305,32 +302,33 @@ static int write_packet(dt_encoder_t *enc, uint64_t timecode_ns,
size_t tad_rs_size = tad_rs_blocks * RS_BLOCK_SIZE;
size_t tav_rs_size = tav_rs_blocks * RS_BLOCK_SIZE;
// Subpacket sizes: LDPC header (16->32 bytes) + RS payload
size_t tad_subpacket_size = DT_TAD_HEADER_SIZE * 2 + tad_rs_size;
size_t tav_subpacket_size = DT_TAV_HEADER_SIZE * 2 + tav_rs_size;
// Subpacket sizes: LDPC header + RS payload (TAV includes sync)
size_t tad_subpacket_size = DT_TAD_HEADER_SIZE * 2 + tad_rs_size; // 28 + RS
size_t tav_subpacket_size = 4 + DT_TAV_HEADER_SIZE * 2 + tav_rs_size; // sync(4) + 28 + RS
uint32_t offset_to_video = tad_subpacket_size;
uint32_t offset_to_video = tad_subpacket_size; // Offset from after main header to TAV sync
uint32_t packet_size = tad_subpacket_size + tav_subpacket_size;
// Build main header (32 bytes raw = 256 bits)
// Layout: sync(4) + fps(1) + flags(1) + reserved(2) + size(4) + timecode(8) + offset(4) + reserved(4) + crc(4)
// CRC is calculated over bytes 0-27 (everything except CRC itself)
uint8_t header[DT_MAIN_HEADER_SIZE]; // 32 bytes
// Build main header (28 bytes raw = 224 bits, sync written separately)
// Layout: fps(1) + flags(1) + reserved(2) + size(4) + timecode(8) + offset(4) + reserved(4) + crc(4)
// CRC is calculated over bytes 0-23 (everything except CRC itself)
uint8_t master_sync[4];
uint8_t header[DT_MAIN_HEADER_SIZE]; // 28 bytes
memset(header, 0, DT_MAIN_HEADER_SIZE);
// Write sync pattern in big-endian (network byte order)
uint32_t sync = enc->is_pal ? TAV_DT_SYNC_PAL : TAV_DT_SYNC_NTSC;
header[0] = (sync >> 24) & 0xFF;
header[1] = (sync >> 16) & 0xFF;
header[2] = (sync >> 8) & 0xFF;
header[3] = sync & 0xFF;
master_sync[0] = (sync >> 24) & 0xFF;
master_sync[1] = (sync >> 16) & 0xFF;
master_sync[2] = (sync >> 8) & 0xFF;
master_sync[3] = sync & 0xFF;
// FPS byte: encode framerate
uint8_t fps_byte;
if (enc->fps_den == 1) fps_byte = enc->fps_num;
else if (enc->fps_den == 1001) fps_byte = enc->fps_num / 1000;
else fps_byte = enc->fps_num / enc->fps_den;
header[4] = fps_byte;
header[0] = fps_byte;
// Flags byte
uint8_t flags = 0;
@@ -338,77 +336,77 @@ static int write_packet(dt_encoder_t *enc, uint64_t timecode_ns,
flags |= (enc->fps_den == 1001 ? 0x02 : 0x00);
flags |= ((enc->fec_mode & 0x01) << 2); // FEC mode in bit 2
flags |= (enc->quality_index & 0x0F) << 4;
header[5] = flags;
header[1] = flags;
// Reserved (2 bytes) at offset 6-7
header[6] = 0;
header[7] = 0;
// Reserved (2 bytes) at offset 2-3
header[2] = 0;
header[3] = 0;
// Packet size (4 bytes) at offset 8-11
memcpy(header + 8, &packet_size, 4);
// Packet size (4 bytes) at offset 4-7
memcpy(header + 4, &packet_size, 4);
// Timecode (8 bytes) at offset 12-19
memcpy(header + 12, &timecode_ns, 8);
// Timecode (8 bytes) at offset 8-15
memcpy(header + 8, &timecode_ns, 8);
// Offset to video (4 bytes) at offset 20-23
memcpy(header + 20, &offset_to_video, 4);
// Offset to video (4 bytes) at offset 16-20
memcpy(header + 16, &offset_to_video, 4);
// Reserved (4 bytes) at offset 24-27
// Reserved (4 bytes) at offset 20-24
// Already zero from memset
// CRC-32 (4 bytes) at offset 28-31, calculated over bytes 0-27
uint32_t crc = calculate_crc32(header, 28);
memcpy(header + 28, &crc, 4);
// CRC-32 (4 bytes) at offset 24-27, calculated over bytes 0-23
uint32_t crc = calculate_crc32(header, 24);
memcpy(header + 24, &crc, 4);
// LDPC encode main header (32 -> 64 bytes, rate 256/512 bits)
// LDPC encode main header (28 -> 56 bytes, rate 224/448 bits)
uint8_t ldpc_header[DT_MAIN_HEADER_SIZE * 2];
ldpc_encode(header, DT_MAIN_HEADER_SIZE, ldpc_header);
// Build TAD subpacket header (16 bytes raw = 128 bits)
// Layout: sample_count(2) + quant_bits(1) + reserved(2) + compressed_size(4) + rs_block_count(3) + crc(4)
uint8_t tad_header[DT_TAD_HEADER_SIZE]; // 16 bytes
// Build TAD subpacket header (14 bytes raw = 112 bits)
// Layout: sample_count(2) + quant_bits(1) + compressed_size(4) + rs_block_count(3) + crc(4)
uint8_t tad_header[DT_TAD_HEADER_SIZE]; // 14 bytes
memset(tad_header, 0, DT_TAD_HEADER_SIZE);
memcpy(tad_header + 0, &audio_samples, 2);
tad_header[2] = audio_quant_bits;
// Reserved (2 bytes) at offset 3-4
uint32_t tad_compressed_size = tad_size;
memcpy(tad_header + 5, &tad_compressed_size, 4);
// RS block count as uint24 at offset 9-11
tad_header[9] = tad_rs_blocks & 0xFF;
tad_header[10] = (tad_rs_blocks >> 8) & 0xFF;
tad_header[11] = (tad_rs_blocks >> 16) & 0xFF;
// CRC-32 (4 bytes) at offset 12-15, calculated over bytes 0-11
uint32_t tad_crc = calculate_crc32(tad_header, 12);
memcpy(tad_header + 12, &tad_crc, 4);
memcpy(tad_header + 3, &tad_compressed_size, 4);
// RS block count as uint24 at offset 7-9
tad_header[7] = tad_rs_blocks & 0xFF;
tad_header[8] = (tad_rs_blocks >> 8) & 0xFF;
tad_header[9] = (tad_rs_blocks >> 16) & 0xFF;
// CRC-32 (4 bytes) at offset 12-15, calculated over bytes 0-9
uint32_t tad_crc = calculate_crc32(tad_header, 10);
memcpy(tad_header + 10, &tad_crc, 4);
// LDPC encode TAD header (16 -> 32 bytes, rate 128/256 bits)
// LDPC encode TAD header (14 -> 28 bytes, rate 112/224 bits)
uint8_t ldpc_tad_header[DT_TAD_HEADER_SIZE * 2];
ldpc_encode(tad_header, DT_TAD_HEADER_SIZE, ldpc_tad_header);
// Build TAV subpacket header (16 bytes raw = 128 bits)
// Build TAV subpacket header (14 bytes raw = 112 bits)
// Layout: sync(4) + gop_size(1) + compressed_size(4) + rs_block_count(3) + crc(4)
uint8_t tav_header[DT_TAV_HEADER_SIZE]; // 16 bytes
uint8_t tav_sync[4];
uint8_t tav_header[DT_TAV_HEADER_SIZE]; // 14 bytes
memset(tav_header, 0, DT_TAV_HEADER_SIZE);
// Write TAV subpacket sync pattern in big-endian
tav_header[0] = (TAV_SUBPACKET_SYNC >> 24) & 0xFF;
tav_header[1] = (TAV_SUBPACKET_SYNC >> 16) & 0xFF;
tav_header[2] = (TAV_SUBPACKET_SYNC >> 8) & 0xFF;
tav_header[3] = TAV_SUBPACKET_SYNC & 0xFF;
tav_sync[0] = (TAV_SUBPACKET_SYNC >> 24) & 0xFF;
tav_sync[1] = (TAV_SUBPACKET_SYNC >> 16) & 0xFF;
tav_sync[2] = (TAV_SUBPACKET_SYNC >> 8) & 0xFF;
tav_sync[3] = TAV_SUBPACKET_SYNC & 0xFF;
tav_header[4] = gop_size;
tav_header[0] = gop_size;
uint32_t tav_compressed_size = tav_size;
memcpy(tav_header + 5, &tav_compressed_size, 4);
// RS block count as uint24 at offset 9-11
tav_header[9] = tav_rs_blocks & 0xFF;
tav_header[10] = (tav_rs_blocks >> 8) & 0xFF;
tav_header[11] = (tav_rs_blocks >> 16) & 0xFF;
memcpy(tav_header + 3, &tav_compressed_size, 4);
// RS block count as uint24 at offset 7-9
tav_header[7] = tav_rs_blocks & 0xFF;
tav_header[8] = (tav_rs_blocks >> 8) & 0xFF;
tav_header[9] = (tav_rs_blocks >> 16) & 0xFF;
// CRC-32 (4 bytes) at offset 12-15, calculated over bytes 0-11
uint32_t tav_crc = calculate_crc32(tav_header, 12);
memcpy(tav_header + 12, &tav_crc, 4);
uint32_t tav_crc = calculate_crc32(tav_header, 10);
memcpy(tav_header + 10, &tav_crc, 4);
// LDPC encode TAV header (16 -> 32 bytes, rate 128/256 bits)
// LDPC encode TAV header (14 -> 28 bytes, rate 112/224 bits)
uint8_t ldpc_tav_header[DT_TAV_HEADER_SIZE * 2];
ldpc_encode(tav_header, DT_TAV_HEADER_SIZE, ldpc_tav_header);
@@ -420,13 +418,16 @@ static int write_packet(dt_encoder_t *enc, uint64_t timecode_ns,
encode_fec_blocks(tav_data, tav_size, tav_rs_data, enc->fec_mode);
// Write everything
fwrite(ldpc_header, 1, DT_MAIN_HEADER_SIZE * 2, enc->output_fp);
fwrite(ldpc_tad_header, 1, DT_TAD_HEADER_SIZE * 2, enc->output_fp);
fwrite(tad_rs_data, 1, tad_rs_size, enc->output_fp);
fwrite(ldpc_tav_header, 1, DT_TAV_HEADER_SIZE * 2, enc->output_fp);
fwrite(tav_rs_data, 1, tav_rs_size, enc->output_fp);
// Sync patterns are written separately (not LDPC-coded) per spec
fwrite(master_sync, 1, 4, enc->output_fp); // Main sync (4 bytes)
fwrite(ldpc_header, 1, DT_MAIN_HEADER_SIZE * 2, enc->output_fp); // LDPC header (56 bytes)
fwrite(ldpc_tad_header, 1, DT_TAD_HEADER_SIZE * 2, enc->output_fp); // TAD LDPC header (28 bytes)
fwrite(tad_rs_data, 1, tad_rs_size, enc->output_fp); // TAD RS payload
fwrite(tav_sync, 1, 4, enc->output_fp); // TAV sync (4 bytes)
fwrite(ldpc_tav_header, 1, DT_TAV_HEADER_SIZE * 2, enc->output_fp); // TAV LDPC header (28 bytes)
fwrite(tav_rs_data, 1, tav_rs_size, enc->output_fp); // TAV RS payload
size_t total_written = DT_MAIN_HEADER_SIZE * 2 + tad_subpacket_size + tav_subpacket_size;
size_t total_written = 4 + DT_MAIN_HEADER_SIZE * 2 + tad_subpacket_size + 4 + tav_subpacket_size;
if (enc->verbose) {
printf("GOP %lu: %d frames, header=%zu tad=%zu tav=%zu total=%zu bytes\n",