TAV-DT format revision and soft sync recovery

This commit is contained in:
minjaesong
2025-12-15 17:42:08 +09:00
parent 3d76006ad9
commit 4929d84cec
4 changed files with 189 additions and 37 deletions

View File

@@ -1647,9 +1647,9 @@ start of the next packet
* Quality indices follow TSVM encoder's
int16 Reserved (zero-fill)
uint32 Total packet size (sum of TAD packet and TAV packet size)
uint32 CRC-32 of [sync pattern + framerate + flags + reserved + total packet size]
uint64 Timecode in nanoseconds
uint32 Offset to video packet
uint32 CRC-32 of [sync pattern + framerate + flags + reserved + total packet size + timecode + offset]
<packet header end; encoded with rate 1/2 LDPC>
bytes TAD with forward error correction
<TAD header start>
@@ -1688,7 +1688,24 @@ start of the next packet
6. If they match, sync to the stream; if not, find a next sync pattern
7. "Offset to video packet" and the actual length of the TAD packet can be used together to recover video packet when stream is damaged, using the fact that in error-free stream, length of TAD packet is equal to "Offset to video packet", and the internal packet order is always audio-then-video
The decoder "may" try to sync to the sync pattern that appears damaged when its contents are seem to be intact.
## Soft Sync Recovery
The decoder MAY try to sync to the sync pattern that appears damaged when its contents are seem to be intact, under the following strategies.
### Stage 1
On the stream position where the sync pattern is supposed to be:
1. Substitute damaged sync pattern with known sync pattern (videos are not allowed to change NTSC/PAL mode mid-stream, so there's only one known value)
2. Zero-fill the reserved area if haven't already
3. Re-calculate CRC. If match, sync. If not, head to the next stage
### Stage 2
1. Further substitute the framerate, flags, timecode to the last known value (as these values rarely change mid-stream; timecode must be incremented appropriately. e.g. FPS=16, last known timecode=5.0, packets missed so far=4, then assumed timecode is 5.0 + 4 + 1 = 10.0)
2. Re-calculate CRC. If match, sync. If not, soft sync recovery is failed, and discard the packet
Note: If CRC is unmatched, the packet MUST be discarded, as the header content cannot be trusted if all soft recovery stages have failed
--------------------------------------------------------------------------------

View File

@@ -36,7 +36,7 @@ LIBTADENC_OBJ = lib/libtadenc/encoder_tad.o
LIBTADDEC_OBJ = lib/libtaddec/decoder_tad.o
# libfec - Forward Error Correction library (LDPC + Reed-Solomon)
LIBFEC_OBJ = lib/libfec/ldpc.o lib/libfec/reed_solomon.o
LIBFEC_OBJ = lib/libfec/ldpc.o lib/libfec/reed_solomon.o lib/libfec/ldpc_payload.o
# =============================================================================
# Targets
@@ -150,7 +150,7 @@ decoder_tav_dt: src/decoder_tav_dt.c lib/libtavdec.a lib/libtaddec.a lib/libfec.
# TAV-DT noise injector (channel simulator)
tavdt_noise_injector: tavdt_noise_injector.c
rm -f tavdt_noise_injector
$(CC) -std=c99 -Wall -O2 -D_GNU_SOURCE -o tavdt_noise_injector tavdt_noise_injector.c -lm
$(CC) -std=c99 -Wall -Ofast -D_GNU_SOURCE -o tavdt_noise_injector tavdt_noise_injector.c -lm
@echo ""
@echo "TAV-DT noise injector built: tavdt_noise_injector"
@echo "Simulates QPSK satellite channel noise (AWGN + burst)"
@@ -180,7 +180,7 @@ install: $(TARGETS)
# Check for required dependencies
check-deps:
@echo "Checking dependencies..."
@pkg-config --exists libzstd || (echo "Error: libzstd-dev not found. Install with: sudo apt install libzstd-dev" && exit 1)
@pkg-config --exists libzstd || (echo "Error: libzstd-dev not found. Install libzstd-dev or equivalent" && exit 1)
@echo "All dependencies found."
# Help

View File

@@ -9,15 +9,19 @@
* - Mandatory TAD audio
* - LDPC rate 1/2 for headers, Reed-Solomon (255,223) for payloads
*
* Packet structure (revised 2025-12-11):
* - Main header: 24 bytes → 48 bytes LDPC encoded
* (sync + fps + flags + reserved + size + crc + timecode + offset_to_video)
* Packet structure (revised 2025-12-15):
* - Main header: 28 bytes → 56 bytes LDPC encoded
* Layout: sync(4) + fps(1) + flags(1) + reserved(2) + size(4) + timecode(8) + offset(4) + crc(4)
* CRC covers bytes 0-23 (everything except CRC itself)
* - TAD subpacket: header (10→20 bytes LDPC) + RS-encoded payload
* - TAV subpacket: header (8→16 bytes LDPC) + RS-encoded payload
* - No packet type bytes - always audio then video
*
* Features (revised 2025-12-15):
* - Soft Sync Recovery: Attempts to recover corrupted headers using known values
*
* Created by CuriousTorvald and Claude on 2025-12-09.
* Revised 2025-12-11 for updated TAV-DT specification.
* Revised 2025-12-15 for updated TAV-DT specification (CRC over timecode+offset, Soft Sync Recovery).
*/
#define _POSIX_C_SOURCE 200809L
@@ -217,6 +221,13 @@ typedef struct {
uint64_t last_timecode_ns; // Last processed timecode
uint64_t audio_samples_written; // Total audio samples written
uint64_t video_frames_written; // Total video frames written (for sync check)
// Soft Sync Recovery state
uint8_t last_valid_framerate;
uint8_t last_valid_flags;
uint64_t last_valid_timecode_ns;
int packets_since_valid_sync;
int soft_sync_recoveries; // Statistics counter
} dt_decoder_t;
// =============================================================================
@@ -419,11 +430,97 @@ typedef struct {
uint8_t flags;
uint16_t reserved;
uint32_t packet_size;
uint32_t crc32;
uint64_t timecode_ns;
uint32_t offset_to_video;
uint64_t timecode_ns; // Now at offset 12 (moved before CRC)
uint32_t offset_to_video; // Now at offset 20 (moved before CRC)
uint32_t crc32; // Now at offset 24 (last field)
} dt_packet_header_t;
// Soft Sync Recovery state
typedef struct {
uint8_t last_framerate;
uint8_t last_flags;
uint64_t last_timecode_ns;
int packets_since_valid;
int is_initialized;
} soft_sync_state_t;
/**
* Attempt Soft Sync Recovery on a header with CRC mismatch.
* Returns 1 if recovery succeeded, 0 if failed.
*
* Stage 1: Substitute known sync pattern, zero-fill reserved, recalculate CRC
* Stage 2: Also substitute framerate, flags, timecode with last known values
*/
static int attempt_soft_sync_recovery(dt_decoder_t *dec, uint8_t *decoded_header,
dt_packet_header_t *header __attribute__((unused))) {
uint32_t stored_crc;
memcpy(&stored_crc, decoded_header + 24, 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;
// === Stage 1 ===
// Substitute sync pattern and zero-fill reserved
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
recovery_header[6] = 0;
recovery_header[7] = 0;
// 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");
}
// Use recovered header
memcpy(decoded_header, recovery_header, DT_MAIN_HEADER_SIZE);
dec->soft_sync_recoveries++;
return 1;
}
// === 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;
// Calculate expected timecode based on last known timecode + GOP duration
// GOP duration = 16 frames / framerate
uint64_t gop_duration_ns = (16ULL * 1000000000ULL) / dec->framerate;
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);
// Recalculate CRC
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(" Reconstructed timecode: %.3f s\n", expected_timecode / 1000000000.0);
}
// Use recovered header
memcpy(decoded_header, recovery_header, DT_MAIN_HEADER_SIZE);
dec->soft_sync_recoveries++;
return 1;
}
}
if (dec->verbose) {
fprintf(stderr, " Soft Sync Recovery: FAILED (all stages exhausted)\n");
}
return 0;
}
static int read_and_decode_header(dt_decoder_t *dec, dt_packet_header_t *header) {
// Read LDPC-encoded header (56 bytes = 28 bytes * 2)
uint8_t encoded_header[DT_MAIN_HEADER_SIZE * 2];
@@ -444,37 +541,71 @@ static int read_and_decode_header(dt_decoder_t *dec, dt_packet_header_t *header)
dec->fec_corrections++;
}
// Parse header fields
// Parse header fields (revised layout 2025-12-15)
// Layout: sync(4) + fps(1) + flags(1) + reserved(2) + size(4) + timecode(8) + offset(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->crc32, decoded_header + 12, 4);
memcpy(&header->timecode_ns, decoded_header + 16, 8);
memcpy(&header->offset_to_video, decoded_header + 24, 4);
memcpy(&header->timecode_ns, decoded_header + 12, 8); // Now at offset 12
memcpy(&header->offset_to_video, decoded_header + 20, 4); // Now at offset 20
memcpy(&header->crc32, decoded_header + 24, 4); // Now at offset 24
// Verify sync pattern
if (header->sync_pattern != TAV_DT_SYNC_NTSC && header->sync_pattern != TAV_DT_SYNC_PAL) {
if (dec->verbose) {
fprintf(stderr, "Warning: Invalid sync pattern 0x%08X\n", header->sync_pattern);
}
dec->sync_losses++;
return -2;
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 first 12 bytes: sync + fps + flags + reserved + size)
uint32_t calculated_crc = calculate_crc32(decoded_header, 12);
if (calculated_crc != header->crc32) {
// Verify CRC-32 (covers bytes 0-23: sync + fps + flags + reserved + size + timecode + offset)
uint32_t calculated_crc = calculate_crc32(decoded_header, 24);
int crc_valid = (calculated_crc == header->crc32);
if (!crc_valid) {
if (dec->verbose) {
fprintf(stderr, "Warning: CRC mismatch (expected 0x%08X, got 0x%08X)\n",
header->crc32, calculated_crc);
}
dec->crc_errors++;
// Continue anyway
// Attempt Soft Sync Recovery
if (dec->packets_processed > 0 && attempt_soft_sync_recovery(dec, decoded_header, header)) {
// Re-parse header from recovered data
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);
memcpy(&header->crc32, decoded_header + 24, 4);
crc_valid = 1; // Recovery succeeded
} else {
dec->crc_errors++;
dec->packets_since_valid_sync++;
// Per spec: If CRC is unmatched after all soft recovery stages, packet MUST be discarded
if (dec->verbose) {
fprintf(stderr, "Warning: Packet discarded due to unrecoverable CRC error\n");
}
dec->sync_losses++;
return -2;
}
}
if (!sync_valid) {
dec->sync_losses++;
return -2;
}
// CRC is valid - update soft sync recovery state
dec->last_valid_framerate = header->framerate;
dec->last_valid_flags = header->flags;
dec->last_valid_timecode_ns = header->timecode_ns;
dec->packets_since_valid_sync = 0;
// Update decoder state from first packet
if (dec->packets_processed == 0) {
dec->width = DT_WIDTH;
@@ -1660,6 +1791,7 @@ static int run_decoder(dt_decoder_t *dec) {
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);
// Mux output files

View File

@@ -9,15 +9,16 @@
* - Mandatory TAD audio
* - LDPC rate 1/2 for headers, Reed-Solomon (255,223) for payloads
*
* Packet structure (revised 2025-12-11):
* Packet structure (revised 2025-12-15):
* - Main header: 28 bytes -> 56 bytes LDPC encoded
* (sync + fps + flags + reserved + size + crc + timecode + offset_to_video)
* Layout: sync(4) + fps(1) + flags(1) + reserved(2) + size(4) + timecode(8) + offset(4) + crc(4)
* CRC covers bytes 0-23 (everything except CRC itself)
* - TAD subpacket: header (10->20 bytes LDPC) + RS-encoded payload
* - TAV subpacket: header (8->16 bytes LDPC) + RS-encoded payload
* - No packet type bytes - always audio then video
*
* Created by CuriousTorvald and Claude on 2025-12-09.
* Revised 2025-12-11 for updated TAV-DT specification.
* Revised 2025-12-15 for updated TAV-DT specification (CRC now covers timecode and offset).
*/
#define _POSIX_C_SOURCE 200809L
@@ -299,6 +300,8 @@ static int write_packet(dt_encoder_t *enc, uint64_t timecode_ns,
uint32_t packet_size = tad_subpacket_size + tav_subpacket_size;
// Build main header (28 bytes)
// Layout (revised 2025-12-15): sync(4) + fps(1) + flags(1) + reserved(2) + size(4) + timecode(8) + offset(4) + crc(4)
// CRC is calculated over bytes 0-23 (everything except CRC itself)
uint8_t header[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;
@@ -328,15 +331,15 @@ static int write_packet(dt_encoder_t *enc, uint64_t timecode_ns,
// Packet size (4 bytes)
memcpy(header + 8, &packet_size, 4);
// CRC placeholder (will be calculated over header bytes 0-11)
uint32_t crc = calculate_crc32(header, 12);
memcpy(header + 12, &crc, 4);
// Timecode (8 bytes) - now at offset 12
memcpy(header + 12, &timecode_ns, 8);
// Timecode (8 bytes)
memcpy(header + 16, &timecode_ns, 8);
// Offset to video (4 bytes) - now at offset 20
memcpy(header + 20, &offset_to_video, 4);
// Offset to video (4 bytes)
memcpy(header + 24, &offset_to_video, 4);
// CRC-32 (4 bytes) - calculated over bytes 0-23 (sync + fps + flags + reserved + size + timecode + offset)
uint32_t crc = calculate_crc32(header, 24);
memcpy(header + 24, &crc, 4);
// LDPC encode main header
uint8_t ldpc_header[DT_MAIN_HEADER_SIZE * 2];