diff --git a/terranmon.txt b/terranmon.txt index 6b7c5cf..9aedef5 100644 --- a/terranmon.txt +++ b/terranmon.txt @@ -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] bytes TAD with forward error correction @@ -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 -------------------------------------------------------------------------------- diff --git a/video_encoder/Makefile b/video_encoder/Makefile index f485a04..db5df0a 100644 --- a/video_encoder/Makefile +++ b/video_encoder/Makefile @@ -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 diff --git a/video_encoder/src/decoder_tav_dt.c b/video_encoder/src/decoder_tav_dt.c index 539ec30..b67525a 100644 --- a/video_encoder/src/decoder_tav_dt.c +++ b/video_encoder/src/decoder_tav_dt.c @@ -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 diff --git a/video_encoder/src/encoder_tav_dt.c b/video_encoder/src/encoder_tav_dt.c index 3e1bcce..66eae66 100644 --- a/video_encoder/src/encoder_tav_dt.c +++ b/video_encoder/src/encoder_tav_dt.c @@ -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];