From 01a89f3b36e17d06189b2c35436d269af88077e0 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Fri, 12 Dec 2025 01:56:29 +0900 Subject: [PATCH] Working TAV-DT encoder/decoder --- video_encoder/Makefile | 126 +- video_encoder/lib/libfec/ldpc.c | 309 ++++ video_encoder/lib/libfec/ldpc.h | 68 + video_encoder/lib/libfec/reed_solomon.c | 417 ++++++ video_encoder/lib/libfec/reed_solomon.h | 82 ++ .../lib/libtavdec/tav_video_decoder.c | 6 +- video_encoder/src/decoder_tav.c | 4 +- video_encoder/src/decoder_tav_dt.c | 1286 +++++++++-------- video_encoder/src/encoder_tav_dt.c | 781 ++++++++++ 9 files changed, 2412 insertions(+), 667 deletions(-) create mode 100644 video_encoder/lib/libfec/ldpc.c create mode 100644 video_encoder/lib/libfec/ldpc.h create mode 100644 video_encoder/lib/libfec/reed_solomon.c create mode 100644 video_encoder/lib/libfec/reed_solomon.h create mode 100644 video_encoder/src/encoder_tav_dt.c diff --git a/video_encoder/Makefile b/video_encoder/Makefile index c3dcd55..7794887 100644 --- a/video_encoder/Makefile +++ b/video_encoder/Makefile @@ -13,10 +13,6 @@ ZSTD_CFLAGS = $(shell pkg-config --cflags libzstd 2>/dev/null || echo "") ZSTD_LIBS = $(shell pkg-config --libs libzstd 2>/dev/null || echo "-lzstd") LIBS = -lm $(ZSTD_LIBS) -# OpenCV flags (for TAV encoder with mesh warping) -OPENCV_CFLAGS = $(shell pkg-config --cflags opencv4) -OPENCV_LIBS = $(shell pkg-config --libs opencv4) - # ============================================================================= # Library Object Files # ============================================================================= @@ -39,44 +35,47 @@ LIBTADENC_OBJ = lib/libtadenc/encoder_tad.o # libtaddec - TAD decoder library 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 + # ============================================================================= # Targets # ============================================================================= # Source files and targets -TARGETS = clean libs encoder_tav_ref decoder_tav_ref tav_inspector +TARGETS = libs encoder_tav_ref decoder_tav_ref tav_inspector tad tav_dt +LIBRARIES = lib/libtavenc.a lib/libtavdec.a lib/libtadenc.a lib/libtaddec.a lib/libfec.a +TAV_TARGETS = encoder_tav_ref decoder_tav_ref tav_inspector TAD_TARGETS = encoder_tad decoder_tad -LIBRARIES = lib/libtavenc.a lib/libtavdec.a lib/libtadenc.a lib/libtaddec.a -TEST_TARGETS = test_mesh_warp test_mesh_roundtrip +DT_TARGETS = encoder_tav_dt decoder_tav_dt # Build all encoders (default) -all: $(TARGETS) +all: clean $(TARGETS) # Build all libraries libs: $(LIBRARIES) -# Build main encoder -tev: encoder_tev.c - rm -f encoder_tev - $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -o encoder_tev $< $(LIBS) +# Reference encoder using libtavenc (replaces old monolithic encoder) +encoder_tav_ref: src/encoder_tav.c lib/libtavenc.a lib/libtadenc.a + rm -f encoder_tav_ref + $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -Iinclude -o encoder_tav_ref src/encoder_tav.c lib/libtavenc.a lib/libtadenc.a $(LIBS) + @echo "" + @echo "Reference encoder built: encoder_tav_ref" + @echo "This is the official reference implementation with all features" -tav: src/encoder_tav.c lib/libtadenc/encoder_tad.c encoder_tav_opencv.cpp - rm -f encoder_tav encoder_tav.o encoder_tad.o encoder_tav_opencv.o - $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -c src/encoder_tav.c -o encoder_tav.o - $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -c lib/libtadenc/encoder_tad.c -o encoder_tad.o - $(CXX) $(CXXFLAGS) $(OPENCV_CFLAGS) $(ZSTD_CFLAGS) -c encoder_tav_opencv.cpp -o encoder_tav_opencv.o - $(CXX) $(DBGFLAGS) -o encoder_tav encoder_tav.o encoder_tad.o encoder_tav_opencv.o $(LIBS) $(OPENCV_LIBS) +# Reference decoder using libtavdec (replaces old monolithic decoder) +decoder_tav_ref: src/decoder_tav.c lib/libtavdec.a lib/libtaddec.a + rm -f decoder_tav_ref + $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -Iinclude -o decoder_tav_ref src/decoder_tav.c lib/libtavdec.a lib/libtaddec.a $(LIBS) + @echo "" + @echo "Reference decoder built: decoder_tav_ref" + @echo "This is the official reference implementation with all features" tav_inspector: tav_inspector.c rm -f tav_inspector $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -o tav_inspector $< $(LIBS) -tav_dt_decoder: src/decoder_tav_dt.c lib/libtaddec/decoder_tad.c include/decoder_tad.h lib/libtavdec/tav_video_decoder.c include/tav_video_decoder.h - rm -f decoder_tav_dt decoder_tav_dt.o tav_video_decoder.o decoder_tad.o - $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -DTAD_DECODER_LIB -c lib/libtaddec/decoder_tad.c -o decoder_tad.o - $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -c lib/libtavdec/tav_video_decoder.c -o tav_video_decoder.o - $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -c src/decoder_tav_dt.c -o decoder_tav_dt.o - $(CC) $(DBGFLAGS) -o decoder_tav_dt decoder_tav_dt.o decoder_tad.o tav_video_decoder.o $(LIBS) +tav: $(TAV_TARGETS) # Build TAD (Terrarum Advanced Audio) tools encoder_tad: src/encoder_tad_standalone.c lib/libtadenc/encoder_tad.c include/encoder_tad.h @@ -109,6 +108,9 @@ lib/libtadenc/%.o: lib/libtadenc/%.c lib/libtaddec/%.o: lib/libtaddec/%.c $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -DTAD_DECODER_LIB -c $< -o $@ +lib/libfec/%.o: lib/libfec/%.c + $(CC) $(CFLAGS) -Ilib/libfec -c $< -o $@ + # Build static libraries lib/libtavenc.a: $(LIBTAVENC_OBJ) ar rcs $@ $^ @@ -122,62 +124,66 @@ lib/libtadenc.a: $(LIBTADENC_OBJ) lib/libtaddec.a: $(LIBTADDEC_OBJ) ar rcs $@ $^ +lib/libfec.a: $(LIBFEC_OBJ) + ar rcs $@ $^ + # ============================================================================= -# Test Programs +# TAV-DT (Digital Tape) Encoder/Decoder # ============================================================================= -test_mesh_roundtrip: test_mesh_roundtrip.cpp encoder_tav_opencv.cpp - rm -f test_mesh_roundtrip test_mesh_roundtrip.o - $(CXX) $(CXXFLAGS) $(OPENCV_CFLAGS) -o test_mesh_roundtrip test_mesh_roundtrip.cpp encoder_tav_opencv.cpp $(OPENCV_LIBS) +# TAV-DT encoder with FEC (multithreaded) +encoder_tav_dt: src/encoder_tav_dt.c lib/libtavenc.a lib/libtadenc.a lib/libfec.a + rm -f encoder_tav_dt + $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -Iinclude -Ilib/libfec -o encoder_tav_dt src/encoder_tav_dt.c lib/libtavenc.a lib/libtadenc.a lib/libfec.a $(LIBS) -lpthread + @echo "" + @echo "TAV-DT encoder built: encoder_tav_dt" + @echo "Digital Tape format with LDPC and Reed-Solomon FEC (multithreaded)" -test_interpolation_comparison: test_interpolation_comparison.cpp encoder_tav_opencv.cpp - rm -f test_interpolation_comparison test_interpolation_comparison.o - $(CXX) $(CXXFLAGS) $(OPENCV_CFLAGS) -o test_interpolation_comparison test_interpolation_comparison.cpp encoder_tav_opencv.cpp $(OPENCV_LIBS) +# TAV-DT decoder with FEC (multithreaded) +decoder_tav_dt: src/decoder_tav_dt.c lib/libtavdec.a lib/libtaddec.a lib/libfec.a + rm -f decoder_tav_dt + $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -Iinclude -Ilib/libfec -o decoder_tav_dt src/decoder_tav_dt.c lib/libtavdec.a lib/libtaddec.a lib/libfec.a $(LIBS) -lpthread + @echo "" + @echo "TAV-DT decoder built: decoder_tav_dt" + @echo "Digital Tape format with LDPC and Reed-Solomon FEC (multithreaded)" -test_bidirectional_prediction: test_bidirectional_prediction.cpp encoder_tav_opencv.cpp - rm -f test_bidirectional_prediction test_bidirectional_prediction.o - $(CXX) $(CXXFLAGS) $(OPENCV_CFLAGS) -o test_bidirectional_prediction test_bidirectional_prediction.cpp encoder_tav_opencv.cpp $(OPENCV_LIBS) - -test_mpeg_motion: test_mpeg_motion.cpp - rm -f test_mpeg_motion test_mpeg_motion.o - $(CXX) $(CXXFLAGS) $(OPENCV_CFLAGS) -o test_mpeg_motion test_mpeg_motion.cpp $(OPENCV_LIBS) - -tests: $(TEST_TARGETS) +# Build all TAV-DT tools +tav_dt: $(DT_TARGETS) # Build with debug symbols debug: CFLAGS += -g -DDEBUG -fsanitize=address -fno-omit-frame-pointer debug: DBGFLAGS += -fsanitize=address -fno-omit-frame-pointer -debug: $(TARGETS) +debug: clean $(TARGETS) # Clean build artifacts clean: - rm -f $(TARGETS) $(TAD_TARGETS) $(LIBRARIES) *.o lib/*/*.o + rm -f $(TARGETS) $(TAD_TARGETS) $(DT_TARGETS) $(LIBRARIES) *.o lib/*/*.o # Install (copy to PATH) -install: $(TARGETS) $(TAD_TARGETS) - cp encoder_tev $(PREFIX)/bin/ - cp encoder_tav $(PREFIX)/bin/ - cp decoder_tav $(PREFIX)/bin/ +install: $(TARGETS) + cp encoder_tav_ref $(PREFIX)/bin/ + cp decoder_tav_ref $(PREFIX)/bin/ cp encoder_tad $(PREFIX)/bin/ cp decoder_tad $(PREFIX)/bin/ + cp encoder_tav_dt $(PREFIX)/bin/ cp decoder_tav_dt $(PREFIX)/bin/ + cp tav_inspector $(PREFIX)/bin/ # 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 opencv4 || (echo "Error: OpenCV 4 not found. Install with: sudo apt install libopencv-dev" && exit 1) @echo "All dependencies found." # Help help: - @echo "TSVM Enhanced Video (TEV) and Audio (TAD) Encoders" + @echo "TSVM Advanced Video (TAV) and Audio (TAD) Encoders" @echo "" @echo "Targets:" @echo " all - Build video encoders (default)" @echo " libs - Build all codec libraries (.a files)" - @echo " tev - Build the TEV video encoder" @echo " tav - Build the TAV advanced video encoder" + @echo " tav_dt - Build all TAV-DT (Digital Tape) tools with FEC" @echo " tad - Build all TAD audio tools (encoder, decoder)" @echo " encoder_tad - Build TAD audio encoder" @echo " decoder_tad - Build TAD audio decoder" @@ -193,30 +199,14 @@ help: @echo " lib/libtavdec.a - TAV decoder library" @echo " lib/libtadenc.a - TAD encoder library" @echo " lib/libtaddec.a - TAD decoder library" + @echo " lib/libfec.a - Forward Error Correction library (LDPC + RS)" @echo "" @echo "Usage:" @echo " make # Build video encoders" @echo " make libs # Build all libraries" - @echo " make tev # Build TEV encoder" @echo " make tav # Build TAV encoder" + @echo " make tav_dt # Build TAV-DT encoder/decoder with FEC" @echo " make tad # Build all TAD audio tools" @echo " sudo make install # Install all encoders" -.PHONY: all libs clean install check-deps help debug tad tests - -# Reference encoder using libtavenc (replaces old monolithic encoder) -encoder_tav_ref: src/encoder_tav.c lib/libtavenc.a lib/libtadenc.a - rm -f encoder_tav_ref - $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -Iinclude -o encoder_tav_ref src/encoder_tav.c lib/libtavenc.a lib/libtadenc.a $(LIBS) - @echo "" - @echo "Reference encoder built: encoder_tav_ref" - @echo "This is the official reference implementation with all features" - -# Reference decoder using libtavdec (replaces old monolithic decoder) -decoder_tav_ref: src/decoder_tav.c lib/libtavdec.a lib/libtaddec.a - rm -f decoder_tav_ref - $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -Iinclude -o decoder_tav_ref src/decoder_tav.c lib/libtavdec.a lib/libtaddec.a $(LIBS) - @echo "" - @echo "Reference decoder built: decoder_tav_ref" - @echo "This is the official reference implementation with all features" - +.PHONY: all libs clean install check-deps help debug tad tav_dt tests diff --git a/video_encoder/lib/libfec/ldpc.c b/video_encoder/lib/libfec/ldpc.c new file mode 100644 index 0000000..3713af9 --- /dev/null +++ b/video_encoder/lib/libfec/ldpc.c @@ -0,0 +1,309 @@ +/** + * LDPC Rate 1/2 Codec Implementation + * + * Simple LDPC for TAV-DT header protection. + * Uses a systematic rate 1/2 code with bit-flipping decoder. + * + * The parity-check matrix is designed for good error correction on small blocks. + * Each parity bit is computed as XOR of multiple data bits using a pseudo-random + * but deterministic pattern. + * + * Created by CuriousTorvald and Claude on 2025-12-09. + */ + +#include "ldpc.h" +#include +#include + +// ============================================================================= +// Parity-Check Matrix Generation +// ============================================================================= + +// For rate 1/2 LDPC: n = 2k bits, parity-check matrix H is (n-k) x n = k x 2k +// We use H = [P | I_k] where P is the parity pattern matrix +// This gives systematic encoding: c = [data | parity] where parity = P * data + +// Parity pattern: each parity bit j depends on data bits where pattern[j][i] = 1 +// We use a regular pattern with column weight 3 (each data bit affects 3 parity bits) +// and row weight varies to cover the data bits well + +// Simple hash function for generating parity connections +static inline uint32_t hash_mix(uint32_t a, uint32_t b) { + a ^= b; + a = (a ^ (a >> 16)) * 0x85ebca6b; + a = (a ^ (a >> 13)) * 0xc2b2ae35; + return a ^ (a >> 16); +} + +// Get bit from byte array +static inline int get_bit(const uint8_t *data, int bit_idx) { + return (data[bit_idx >> 3] >> (7 - (bit_idx & 7))) & 1; +} + +// Set bit in byte array +static inline void set_bit(uint8_t *data, int bit_idx, int value) { + int byte_idx = bit_idx >> 3; + int bit_pos = 7 - (bit_idx & 7); + if (value) { + data[byte_idx] |= (1 << bit_pos); + } else { + data[byte_idx] &= ~(1 << bit_pos); + } +} + +// Flip bit in byte array +static inline void flip_bit(uint8_t *data, int bit_idx) { + int byte_idx = bit_idx >> 3; + int bit_pos = 7 - (bit_idx & 7); + data[byte_idx] ^= (1 << bit_pos); +} + +// Get list of data bits that affect parity bit j +// Returns number of connected data bits, stores indices in connections[] +// For rate 1/2: data bits are 0 to k*8-1, parity bits are k*8 to 2*k*8-1 +static int get_parity_connections(int parity_idx, int k_bits, int *connections) { + int count = 0; + + // Use a deterministic pseudo-random pattern + // Each parity bit connects to approximately k_bits/3 data bits + // Different seeds for different parity positions ensure coverage + + uint32_t seed = hash_mix(0xDEADBEEF, (uint32_t)parity_idx); + + for (int i = 0; i < k_bits; i++) { + // Each data bit has ~3/k_bits chance of connecting to this parity bit + // Total connections per parity ~ 3 (column weight) + uint32_t h = hash_mix(seed, (uint32_t)i); + if ((h % (k_bits / 3 + 1)) == 0) { + connections[count++] = i; + } + } + + // Ensure at least 2 connections per parity bit + if (count < 2) { + connections[count++] = parity_idx % k_bits; + connections[count++] = (parity_idx + k_bits / 2) % k_bits; + } + + return count; +} + +// Get list of parity bits affected by data bit i +static int get_data_connections(int data_idx, int k_bits, int *connections) { + int count = 0; + + for (int j = 0; j < k_bits; j++) { + int parity_conns[LDPC_MAX_DATA_BYTES * 8]; + int n_conns = get_parity_connections(j, k_bits, parity_conns); + + for (int c = 0; c < n_conns; c++) { + if (parity_conns[c] == data_idx) { + connections[count++] = j; + break; + } + } + } + + return count; +} + +// ============================================================================= +// Initialization +// ============================================================================= + +static int ldpc_initialized = 0; + +void ldpc_init(void) { + if (ldpc_initialized) return; + // No pre-computation needed - patterns generated on the fly + ldpc_initialized = 1; +} + +// ============================================================================= +// Encoding +// ============================================================================= + +size_t ldpc_encode(const uint8_t *data, size_t data_len, uint8_t *output) { + if (!ldpc_initialized) ldpc_init(); + + if (data_len > LDPC_MAX_DATA_BYTES) { + data_len = LDPC_MAX_DATA_BYTES; + } + + int k_bits = (int)(data_len * 8); // Number of data bits + + // Copy data to output (systematic encoding) + memcpy(output, data, data_len); + + // Initialize parity bytes to zero + memset(output + data_len, 0, data_len); + + // Compute parity bits + for (int j = 0; j < k_bits; j++) { + // Get data bits connected to parity bit j + int connections[LDPC_MAX_DATA_BYTES * 8]; + int n_conns = get_parity_connections(j, k_bits, connections); + + // Parity bit = XOR of connected data bits + int parity = 0; + for (int c = 0; c < n_conns; c++) { + parity ^= get_bit(data, connections[c]); + } + + // Set parity bit + set_bit(output + data_len, j, parity); + } + + return data_len * 2; +} + +// ============================================================================= +// Decoding +// ============================================================================= + +int ldpc_check_syndrome(const uint8_t *codeword, size_t len) { + if (!ldpc_initialized) ldpc_init(); + + size_t data_len = len / 2; + int k_bits = (int)(data_len * 8); + + // Check all parity equations + for (int j = 0; j < k_bits; j++) { + int connections[LDPC_MAX_DATA_BYTES * 8]; + int n_conns = get_parity_connections(j, k_bits, connections); + + // Compute syndrome bit: XOR of connected data bits XOR parity bit + int syndrome = get_bit(codeword + data_len, j); + for (int c = 0; c < n_conns; c++) { + syndrome ^= get_bit(codeword, connections[c]); + } + + if (syndrome != 0) { + return 0; // Syndrome non-zero: errors detected + } + } + + return 1; // Zero syndrome: valid codeword +} + +int ldpc_decode(const uint8_t *encoded, size_t encoded_len, uint8_t *output) { + if (!ldpc_initialized) ldpc_init(); + + if (encoded_len < 2 || (encoded_len & 1) != 0) { + return -1; // Invalid length + } + + size_t data_len = encoded_len / 2; + if (data_len > LDPC_MAX_DATA_BYTES) { + return -1; + } + + int k_bits = (int)(data_len * 8); + + // Working copy of codeword + uint8_t codeword[LDPC_MAX_DATA_BYTES * 2]; + memcpy(codeword, encoded, encoded_len); + + // Bit-flipping decoder + for (int iter = 0; iter < LDPC_MAX_ITERATIONS; iter++) { + // Compute syndromes (which parity checks fail) + int syndrome[LDPC_MAX_DATA_BYTES * 8]; + int syndrome_count = 0; + + for (int j = 0; j < k_bits; j++) { + int connections[LDPC_MAX_DATA_BYTES * 8]; + int n_conns = get_parity_connections(j, k_bits, connections); + + // Syndrome bit = XOR of connected data bits XOR parity bit + syndrome[j] = get_bit(codeword + data_len, j); + for (int c = 0; c < n_conns; c++) { + syndrome[j] ^= get_bit(codeword, connections[c]); + } + + if (syndrome[j]) syndrome_count++; + } + + // Check if we're done (all syndromes zero) + if (syndrome_count == 0) { + // Success - copy decoded data + memcpy(output, codeword, data_len); + return 0; + } + + // Count failed checks for each bit + int data_fails[LDPC_MAX_DATA_BYTES * 8]; + int parity_fails[LDPC_MAX_DATA_BYTES * 8]; + memset(data_fails, 0, sizeof(data_fails)); + memset(parity_fails, 0, sizeof(parity_fails)); + + for (int j = 0; j < k_bits; j++) { + if (syndrome[j]) { + // This check failed - increment count for all connected bits + int connections[LDPC_MAX_DATA_BYTES * 8]; + int n_conns = get_parity_connections(j, k_bits, connections); + + for (int c = 0; c < n_conns; c++) { + data_fails[connections[c]]++; + } + parity_fails[j]++; + } + } + + // Find bit with most failures + int max_fails = 0; + int flip_type = 0; // 0 = data, 1 = parity + int flip_idx = 0; + + for (int i = 0; i < k_bits; i++) { + if (data_fails[i] > max_fails) { + max_fails = data_fails[i]; + flip_type = 0; + flip_idx = i; + } + } + + for (int j = 0; j < k_bits; j++) { + if (parity_fails[j] > max_fails) { + max_fails = parity_fails[j]; + flip_type = 1; + flip_idx = j; + } + } + + // Flip the most suspicious bit + if (max_fails > 0) { + if (flip_type == 0) { + flip_bit(codeword, flip_idx); + } else { + flip_bit(codeword + data_len, flip_idx); + } + } else { + // No progress possible + break; + } + } + + // Failed to decode - return best effort + // Check if we at least have valid data by syndrome count + int final_syndromes = 0; + for (int j = 0; j < k_bits; j++) { + int connections[LDPC_MAX_DATA_BYTES * 8]; + int n_conns = get_parity_connections(j, k_bits, connections); + + int syn = get_bit(codeword + data_len, j); + for (int c = 0; c < n_conns; c++) { + syn ^= get_bit(codeword, connections[c]); + } + if (syn) final_syndromes++; + } + + // If only a few syndromes fail, return data anyway (soft failure) + if (final_syndromes <= k_bits / 8) { + memcpy(output, codeword, data_len); + return 0; // Partial success + } + + // Total failure - return original data as best effort + memcpy(output, encoded, data_len); + return -1; +} diff --git a/video_encoder/lib/libfec/ldpc.h b/video_encoder/lib/libfec/ldpc.h new file mode 100644 index 0000000..d8af04f --- /dev/null +++ b/video_encoder/lib/libfec/ldpc.h @@ -0,0 +1,68 @@ +/** + * LDPC Rate 1/2 Codec for TAV-DT + * + * Simple LDPC implementation for header protection in TAV-DT format. + * Rate 1/2: k data bytes → 2k encoded bytes (doubles the size) + * + * Uses systematic encoding where first k bytes are data, last k bytes are parity. + * Decoding uses iterative bit-flipping algorithm. + * + * Designed for small blocks (headers up to 64 bytes). + * + * Created by CuriousTorvald and Claude on 2025-12-09. + */ + +#ifndef LDPC_H +#define LDPC_H + +#include +#include + +// Maximum block size (data bytes before encoding) +#define LDPC_MAX_DATA_BYTES 64 + +// LDPC decoder parameters +#define LDPC_MAX_ITERATIONS 50 + +/** + * Initialize LDPC codec. + * Must be called once before using encode/decode functions. + * Thread-safe: uses static initialization. + */ +void ldpc_init(void); + +/** + * Encode data block with LDPC rate 1/2. + * + * @param data Input data bytes + * @param data_len Length of input data (1 to LDPC_MAX_DATA_BYTES) + * @param output Output buffer (must hold 2 * data_len bytes) + * @return Output length (2 * data_len) + * + * Output format: [data bytes][parity bytes] + * The output is systematic: first data_len bytes are the original data. + */ +size_t ldpc_encode(const uint8_t *data, size_t data_len, uint8_t *output); + +/** + * Decode LDPC rate 1/2 encoded block. + * + * @param encoded Input encoded data (2 * data_len bytes) + * @param encoded_len Length of encoded data (must be even, max 2*LDPC_MAX_DATA_BYTES) + * @param output Output buffer for decoded data (encoded_len / 2 bytes) + * @return 0 on success, -1 if decoding failed (too many errors) + * + * Uses iterative bit-flipping decoder. + */ +int ldpc_decode(const uint8_t *encoded, size_t encoded_len, uint8_t *output); + +/** + * Calculate syndrome for validation. + * + * @param codeword Encoded codeword (2 * data_len bytes) + * @param len Length of codeword + * @return 1 if valid (zero syndrome), 0 if errors detected + */ +int ldpc_check_syndrome(const uint8_t *codeword, size_t len); + +#endif // LDPC_H diff --git a/video_encoder/lib/libfec/reed_solomon.c b/video_encoder/lib/libfec/reed_solomon.c new file mode 100644 index 0000000..b8256a5 --- /dev/null +++ b/video_encoder/lib/libfec/reed_solomon.c @@ -0,0 +1,417 @@ +/** + * Reed-Solomon (255,223) Codec Implementation + * + * Standard RS code over GF(2^8) for TAV-DT forward error correction. + * + * Created by CuriousTorvald and Claude on 2025-12-09. + */ + +#include "reed_solomon.h" +#include +#include + +// ============================================================================= +// Galois Field GF(2^8) Arithmetic +// ============================================================================= + +// Primitive polynomial: x^8 + x^4 + x^3 + x^2 + 1 = 0x11D +#define GF_PRIMITIVE 0x11D +#define GF_SIZE 256 +#define GF_MAX 255 + +// Lookup tables for GF(2^8) arithmetic +static uint8_t gf_exp[512]; // Anti-log table (doubled for easy modular reduction) +static uint8_t gf_log[256]; // Log table +static uint8_t gf_generator[RS_PARITY_SIZE + 1]; // Generator polynomial coefficients + +static int rs_initialized = 0; + +// Initialize GF(2^8) exp/log tables +static void init_gf_tables(void) { + uint16_t x = 1; + + for (int i = 0; i < GF_MAX; i++) { + gf_exp[i] = (uint8_t)x; + gf_log[x] = (uint8_t)i; + + // Multiply by alpha (primitive element = 2) + x <<= 1; + if (x & 0x100) { + x ^= GF_PRIMITIVE; + } + } + + // Double the exp table for easy modular reduction + for (int i = GF_MAX; i < 512; i++) { + gf_exp[i] = gf_exp[i - GF_MAX]; + } + + // gf_log[0] is undefined, set to 0 for safety + gf_log[0] = 0; +} + +// GF multiplication +static inline uint8_t gf_mul(uint8_t a, uint8_t b) { + if (a == 0 || b == 0) return 0; + return gf_exp[gf_log[a] + gf_log[b]]; +} + +// GF division +static inline uint8_t gf_div(uint8_t a, uint8_t b) { + if (a == 0) return 0; + if (b == 0) return 0; // Division by zero - shouldn't happen + return gf_exp[gf_log[a] + GF_MAX - gf_log[b]]; +} + +// GF power +static inline uint8_t gf_pow(uint8_t a, int n) { + if (n == 0) return 1; + if (a == 0) return 0; + return gf_exp[(gf_log[a] * n) % GF_MAX]; +} + +// GF inverse +static inline uint8_t gf_inv(uint8_t a) { + if (a == 0) return 0; + return gf_exp[GF_MAX - gf_log[a]]; +} + +// ============================================================================= +// Generator Polynomial +// ============================================================================= + +// Build generator polynomial: g(x) = (x - alpha^0)(x - alpha^1)...(x - alpha^31) +static void init_generator(void) { + // Start with g(x) = 1 + gf_generator[0] = 1; + for (int i = 1; i <= RS_PARITY_SIZE; i++) { + gf_generator[i] = 0; + } + + // Multiply by (x - alpha^i) for i = 0 to 31 + for (int i = 0; i < RS_PARITY_SIZE; i++) { + uint8_t alpha_i = gf_exp[i]; // alpha^i + + // Multiply current polynomial by (x - alpha^i) + for (int j = RS_PARITY_SIZE; j > 0; j--) { + gf_generator[j] = gf_generator[j - 1] ^ gf_mul(gf_generator[j], alpha_i); + } + gf_generator[0] = gf_mul(gf_generator[0], alpha_i); + } +} + +// ============================================================================= +// Public API +// ============================================================================= + +void rs_init(void) { + if (rs_initialized) return; + + init_gf_tables(); + init_generator(); + rs_initialized = 1; +} + +size_t rs_encode(const uint8_t *data, size_t data_len, uint8_t *output) { + if (!rs_initialized) rs_init(); + + // Validate input + if (data_len > RS_DATA_SIZE) { + data_len = RS_DATA_SIZE; + } + + // Copy data to output + memcpy(output, data, data_len); + + // Initialize parity bytes to zero + memset(output + data_len, 0, RS_PARITY_SIZE); + + // Create padded message polynomial (RS_DATA_SIZE + RS_PARITY_SIZE coefficients) + // Message is shifted to leave room for parity (systematic encoding) + uint8_t msg[RS_BLOCK_SIZE]; + memset(msg, 0, sizeof(msg)); + memcpy(msg, data, data_len); + + // Polynomial division: compute remainder of msg(x) * x^32 / g(x) + uint8_t remainder[RS_PARITY_SIZE]; + memset(remainder, 0, RS_PARITY_SIZE); + + for (size_t i = 0; i < data_len; i++) { + uint8_t coef = msg[i] ^ remainder[0]; + + // Shift remainder + memmove(remainder, remainder + 1, RS_PARITY_SIZE - 1); + remainder[RS_PARITY_SIZE - 1] = 0; + + // Subtract coef * g(x) from remainder + if (coef != 0) { + for (int j = 0; j < RS_PARITY_SIZE; j++) { + remainder[j] ^= gf_mul(gf_generator[RS_PARITY_SIZE - 1 - j], coef); + } + } + } + + // Append parity to output + memcpy(output + data_len, remainder, RS_PARITY_SIZE); + + return data_len + RS_PARITY_SIZE; +} + +// ============================================================================= +// Berlekamp-Massey Decoder +// ============================================================================= + +// Compute syndromes S_i = r(alpha^i) for i = 0..31 +static void compute_syndromes(const uint8_t *r, size_t len, uint8_t *syndromes) { + for (int i = 0; i < RS_PARITY_SIZE; i++) { + syndromes[i] = 0; + for (size_t j = 0; j < len; j++) { + syndromes[i] ^= gf_mul(r[j], gf_pow(gf_exp[i], (int)(len - 1 - j))); + } + } +} + +// Berlekamp-Massey algorithm to find error locator polynomial +static int berlekamp_massey(const uint8_t *syndromes, uint8_t *sigma, int *sigma_deg) { + uint8_t C[RS_PARITY_SIZE + 1]; // Connection polynomial + uint8_t B[RS_PARITY_SIZE + 1]; // Previous connection polynomial + int L = 0; // Current length of LFSR + int m = 1; // Number of steps since last update + uint8_t b = 1; // Previous discrepancy + + // Initialize: C(x) = 1, B(x) = 1 + memset(C, 0, sizeof(C)); + memset(B, 0, sizeof(B)); + C[0] = 1; + B[0] = 1; + + for (int n = 0; n < RS_PARITY_SIZE; n++) { + // Compute discrepancy + uint8_t d = syndromes[n]; + for (int i = 1; i <= L; i++) { + d ^= gf_mul(C[i], syndromes[n - i]); + } + + if (d == 0) { + // No update needed + m++; + } else if (2 * L <= n) { + // Update both C and L + uint8_t T[RS_PARITY_SIZE + 1]; + memcpy(T, C, sizeof(T)); + + uint8_t factor = gf_div(d, b); + for (int i = 0; i <= RS_PARITY_SIZE - m; i++) { + C[i + m] ^= gf_mul(factor, B[i]); + } + + L = n + 1 - L; + memcpy(B, T, sizeof(B)); + b = d; + m = 1; + } else { + // Only update C + uint8_t factor = gf_div(d, b); + for (int i = 0; i <= RS_PARITY_SIZE - m; i++) { + C[i + m] ^= gf_mul(factor, B[i]); + } + m++; + } + } + + // Copy result + memcpy(sigma, C, RS_PARITY_SIZE + 1); + *sigma_deg = L; + + return L; +} + +// Chien search: find error positions (roots of sigma) +static int chien_search(const uint8_t *sigma, int sigma_deg, size_t n, uint8_t *positions, int *num_errors) { + *num_errors = 0; + + // Evaluate sigma(alpha^(-i)) for i = 0 to n-1 + for (size_t i = 0; i < n; i++) { + uint8_t eval = 0; + for (int j = 0; j <= sigma_deg; j++) { + // sigma(alpha^(-i)) = sum of sigma[j] * alpha^(-i*j) + int exp = (GF_MAX - (int)((i * j) % GF_MAX)) % GF_MAX; + eval ^= gf_mul(sigma[j], gf_exp[exp]); + } + + if (eval == 0) { + // Found a root - error at position n-1-i + positions[*num_errors] = (uint8_t)(n - 1 - i); + (*num_errors)++; + } + } + + // Check if we found the expected number of errors + return (*num_errors == sigma_deg) ? 0 : -1; +} + +// Compute formal derivative of polynomial +static void poly_derivative(const uint8_t *poly, int deg, uint8_t *deriv) { + for (int i = 0; i < deg; i++) { + // Derivative of x^(i+1) is (i+1) * x^i + // In GF(2^m), coefficient is 1 if (i+1) is odd, 0 if even + deriv[i] = ((i + 1) & 1) ? poly[i + 1] : 0; + } +} + +// Forney algorithm: compute error values +static void forney(const uint8_t *syndromes, const uint8_t *sigma, int sigma_deg, + const uint8_t *positions, int num_errors, size_t n, uint8_t *errors) { + // Compute error evaluator polynomial omega(x) = S(x) * sigma(x) mod x^2t + uint8_t omega[RS_PARITY_SIZE + 1]; + memset(omega, 0, sizeof(omega)); + + for (int i = 0; i < RS_PARITY_SIZE; i++) { + for (int j = 0; j <= sigma_deg && i - j >= 0; j++) { + omega[i] ^= gf_mul(syndromes[i - j], sigma[j]); + } + } + + // Compute formal derivative of sigma + uint8_t sigma_prime[RS_PARITY_SIZE]; + poly_derivative(sigma, sigma_deg, sigma_prime); + + // Compute error values using Forney formula + for (int i = 0; i < num_errors; i++) { + uint8_t pos = positions[i]; + uint8_t Xi = gf_exp[n - 1 - pos]; // alpha^(n-1-pos) + uint8_t Xi_inv = gf_inv(Xi); + + // Evaluate omega at Xi_inv + uint8_t omega_val = 0; + for (int j = 0; j < RS_PARITY_SIZE; j++) { + omega_val ^= gf_mul(omega[j], gf_pow(Xi_inv, j)); + } + + // Evaluate sigma' at Xi_inv + uint8_t sigma_prime_val = 0; + for (int j = 0; j < sigma_deg; j++) { + sigma_prime_val ^= gf_mul(sigma_prime[j], gf_pow(Xi_inv, j)); + } + + // Error value: e_i = Xi * omega(Xi_inv) / sigma'(Xi_inv) + errors[i] = gf_mul(Xi, gf_div(omega_val, sigma_prime_val)); + } +} + +int rs_decode(uint8_t *data, size_t data_len) { + if (!rs_initialized) rs_init(); + + size_t total_len = data_len + RS_PARITY_SIZE; + if (total_len > RS_BLOCK_SIZE) { + return -1; + } + + // Compute syndromes + uint8_t syndromes[RS_PARITY_SIZE]; + compute_syndromes(data, total_len, syndromes); + + // Check if all syndromes are zero (no errors) + int has_errors = 0; + for (int i = 0; i < RS_PARITY_SIZE; i++) { + if (syndromes[i] != 0) { + has_errors = 1; + break; + } + } + + if (!has_errors) { + return 0; // No errors + } + + // Find error locator polynomial using Berlekamp-Massey + uint8_t sigma[RS_PARITY_SIZE + 1]; + int sigma_deg; + int num_errors_expected = berlekamp_massey(syndromes, sigma, &sigma_deg); + + if (num_errors_expected > RS_MAX_ERRORS) { + return -1; // Too many errors + } + + // Find error positions using Chien search + uint8_t positions[RS_MAX_ERRORS]; + int num_errors; + if (chien_search(sigma, sigma_deg, total_len, positions, &num_errors) != 0) { + return -1; // Inconsistent error count + } + + // Compute error values using Forney algorithm + uint8_t error_values[RS_MAX_ERRORS]; + forney(syndromes, sigma, sigma_deg, positions, num_errors, total_len, error_values); + + // Apply corrections + for (int i = 0; i < num_errors; i++) { + if (positions[i] < total_len) { + data[positions[i]] ^= error_values[i]; + } + } + + return num_errors; +} + +// ============================================================================= +// Block-level operations +// ============================================================================= + +size_t rs_encode_blocks(const uint8_t *data, size_t data_len, uint8_t *output) { + if (!rs_initialized) rs_init(); + + size_t output_len = 0; + size_t remaining = data_len; + const uint8_t *src = data; + uint8_t *dst = output; + + while (remaining > 0) { + size_t block_data = (remaining > RS_DATA_SIZE) ? RS_DATA_SIZE : remaining; + size_t encoded_len = rs_encode(src, block_data, dst); + + // Pad to full block size for consistent block boundaries + if (encoded_len < RS_BLOCK_SIZE) { + memset(dst + encoded_len, 0, RS_BLOCK_SIZE - encoded_len); + } + + src += block_data; + dst += RS_BLOCK_SIZE; + output_len += RS_BLOCK_SIZE; + remaining -= block_data; + } + + return output_len; +} + +int rs_decode_blocks(uint8_t *data, size_t total_len, uint8_t *output, size_t output_len) { + if (!rs_initialized) rs_init(); + + int total_errors = 0; + size_t remaining_output = output_len; + uint8_t *src = data; + uint8_t *dst = output; + + while (total_len >= RS_BLOCK_SIZE && remaining_output > 0) { + // Always decode with full RS_DATA_SIZE since encoder pads to full blocks + // But only copy the bytes we actually need + size_t bytes_to_copy = (remaining_output > RS_DATA_SIZE) ? RS_DATA_SIZE : remaining_output; + + // Decode block with full data size (modifies src in place) + int errors = rs_decode(src, RS_DATA_SIZE); + if (errors < 0) { + return -1; // Uncorrectable block + } + total_errors += errors; + + // Copy only the bytes we need to output + memcpy(dst, src, bytes_to_copy); + + src += RS_BLOCK_SIZE; + dst += bytes_to_copy; + total_len -= RS_BLOCK_SIZE; + remaining_output -= bytes_to_copy; + } + + return total_errors; +} diff --git a/video_encoder/lib/libfec/reed_solomon.h b/video_encoder/lib/libfec/reed_solomon.h new file mode 100644 index 0000000..23858c7 --- /dev/null +++ b/video_encoder/lib/libfec/reed_solomon.h @@ -0,0 +1,82 @@ +/** + * Reed-Solomon (255,223) Codec for TAV-DT + * + * Standard RS code over GF(2^8): + * - Block size: 255 bytes (223 data + 32 parity) + * - Error correction: up to 16 byte errors + * - Error detection: up to 32 byte errors + * + * Uses primitive polynomial: x^8 + x^4 + x^3 + x^2 + 1 (0x11D) + * Generator polynomial: g(x) = product of (x - alpha^i) for i = 0..31 + * + * Created by CuriousTorvald and Claude on 2025-12-09. + */ + +#ifndef REED_SOLOMON_H +#define REED_SOLOMON_H + +#include +#include + +// RS(255,223) parameters +#define RS_BLOCK_SIZE 255 // Total codeword size +#define RS_DATA_SIZE 223 // Data bytes per block +#define RS_PARITY_SIZE 32 // Parity bytes per block (2t = 32, t = 16) +#define RS_MAX_ERRORS 16 // Maximum correctable errors (t) + +/** + * Initialize Reed-Solomon codec. + * Must be called once before using encode/decode functions. + * Thread-safe: uses static initialization. + */ +void rs_init(void); + +/** + * Encode data block with Reed-Solomon parity. + * + * @param data Input data (up to RS_DATA_SIZE bytes) + * @param data_len Length of input data (1 to RS_DATA_SIZE) + * @param output Output buffer (must hold data_len + RS_PARITY_SIZE bytes) + * Format: [data][parity] + * @return Total output length (data_len + RS_PARITY_SIZE) + * + * Note: For data shorter than RS_DATA_SIZE, the encoder pads with zeros + * internally but only outputs actual data + parity. + */ +size_t rs_encode(const uint8_t *data, size_t data_len, uint8_t *output); + +/** + * Decode and correct Reed-Solomon encoded block. + * + * @param data Buffer containing [data][parity] (modified in-place) + * @param data_len Length of data portion (1 to RS_DATA_SIZE) + * @return Number of errors corrected (0-16), or -1 if uncorrectable + * + * On success, data buffer contains corrected data (parity may also be corrected). + * On failure, data buffer contents are undefined. + */ +int rs_decode(uint8_t *data, size_t data_len); + +/** + * Encode data with automatic block splitting. + * For data larger than RS_DATA_SIZE, splits into multiple RS blocks. + * + * @param data Input data + * @param data_len Length of input data + * @param output Output buffer (must hold ceil(data_len/223) * 255 bytes) + * @return Total output length + */ +size_t rs_encode_blocks(const uint8_t *data, size_t data_len, uint8_t *output); + +/** + * Decode data with automatic block splitting. + * + * @param data Buffer containing RS-encoded blocks (modified in-place) + * @param total_len Total length of encoded data (multiple of RS_BLOCK_SIZE) + * @param output Output buffer for decoded data + * @param output_len Expected length of decoded data + * @return Total errors corrected across all blocks, or -1 if any block failed + */ +int rs_decode_blocks(uint8_t *data, size_t total_len, uint8_t *output, size_t output_len); + +#endif // REED_SOLOMON_H diff --git a/video_encoder/lib/libtavdec/tav_video_decoder.c b/video_encoder/lib/libtavdec/tav_video_decoder.c index acccd2b..9cb3144 100644 --- a/video_encoder/lib/libtavdec/tav_video_decoder.c +++ b/video_encoder/lib/libtavdec/tav_video_decoder.c @@ -1024,7 +1024,7 @@ static void apply_inverse_3d_dwt(float **gop_y, float **gop_co, float **gop_cg, for (int level = temporal_levels - 1; level >= 0; level--) { const int level_frames = temporal_lengths[level]; if (level_frames >= 2) { - if (temporal_wavelet == 0) { + if (temporal_wavelet == 255) { dwt_haar_inverse_1d(temporal_line, level_frames); } else { dwt_53_inverse_1d(temporal_line, level_frames); @@ -1042,7 +1042,7 @@ static void apply_inverse_3d_dwt(float **gop_y, float **gop_co, float **gop_cg, for (int level = temporal_levels - 1; level >= 0; level--) { const int level_frames = temporal_lengths[level]; if (level_frames >= 2) { - if (temporal_wavelet == 0) { + if (temporal_wavelet == 255) { dwt_haar_inverse_1d(temporal_line, level_frames); } else { dwt_53_inverse_1d(temporal_line, level_frames); @@ -1060,7 +1060,7 @@ static void apply_inverse_3d_dwt(float **gop_y, float **gop_co, float **gop_cg, for (int level = temporal_levels - 1; level >= 0; level--) { const int level_frames = temporal_lengths[level]; if (level_frames >= 2) { - if (temporal_wavelet == 0) { + if (temporal_wavelet == 255) { dwt_haar_inverse_1d(temporal_line, level_frames); } else { dwt_53_inverse_1d(temporal_line, level_frames); diff --git a/video_encoder/src/decoder_tav.c b/video_encoder/src/decoder_tav.c index 8d3ad7b..bed6510 100644 --- a/video_encoder/src/decoder_tav.c +++ b/video_encoder/src/decoder_tav.c @@ -573,7 +573,7 @@ static int init_decoder_threads(decoder_context_t *ctx) { .decomp_levels = ctx->header.decomp_levels, .temporal_levels = 2, .wavelet_filter = ctx->header.wavelet_filter, - .temporal_wavelet = 0, + .temporal_wavelet = 255, .entropy_coder = ctx->header.entropy_coder, .channel_layout = ctx->header.channel_layout, .perceptual_tuning = ctx->perceptual_mode, @@ -1944,7 +1944,7 @@ int main(int argc, char *argv[]) { .decomp_levels = ctx.header.decomp_levels, .temporal_levels = 2, // Default .wavelet_filter = ctx.header.wavelet_filter, - .temporal_wavelet = 0, // Haar + .temporal_wavelet = 255, // Haar .entropy_coder = ctx.header.entropy_coder, .channel_layout = ctx.header.channel_layout, .perceptual_tuning = ctx.perceptual_mode, diff --git a/video_encoder/src/decoder_tav_dt.c b/video_encoder/src/decoder_tav_dt.c index e6febac..0a85621 100644 --- a/video_encoder/src/decoder_tav_dt.c +++ b/video_encoder/src/decoder_tav_dt.c @@ -1,40 +1,79 @@ -// Created by CuriousTorvald and Claude on 2025-12-02. -// TAV-DT (Digital Tape) Decoder - Headerless streaming format decoder -// Decodes TAV-DT packets to video (FFV1/rawvideo) and audio (PCMu8) +/** + * TAV-DT Decoder - Digital Tape Format Decoder + * + * Decodes TAV-DT format with forward error correction. + * + * TAV-DT is a packetised streaming format designed for digital tape/broadcast: + * - Fixed dimensions: 720x480 (NTSC) or 720x576 (PAL) + * - 16-frame GOPs with 9/7 spatial wavelet, Haar temporal + * - 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) + * - 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. + */ +#define _POSIX_C_SOURCE 200809L #include #include #include #include +#include #include #include #include -#include -#include "decoder_tad.h" // Shared TAD decoder library -#include "tav_video_decoder.h" // Shared TAV video decoder library +#include -#define DECODER_VENDOR_STRING "Decoder-TAV-DT 20251202" +#include "tav_video_decoder.h" +#include "decoder_tad.h" +#include "reed_solomon.h" +#include "ldpc.h" + +// ============================================================================= +// Constants +// ============================================================================= // TAV-DT sync patterns (big endian) -#define TAV_DT_SYNC_NTSC 0xE3537A1F // 720x480 -#define TAV_DT_SYNC_PAL 0xD193A745 // 720x576 +#define TAV_DT_SYNC_NTSC 0xE3537A1F +#define TAV_DT_SYNC_PAL 0xD193A745 -// Standard TAV quality arrays (0-5, must match encoder) -static const int QUALITY_Y[] = {79, 47, 23, 11, 5, 2, 0}; -static const int QUALITY_CO[] = {123, 108, 91, 76, 59, 29, 3}; -static const int QUALITY_CG[] = {148, 133, 113, 99, 76, 39, 5}; +// TAV-DT dimensions +#define DT_WIDTH 720 +#define DT_HEIGHT_NTSC 480 +#define DT_HEIGHT_PAL 576 -// TAV-DT packet types (reused from TAV) -#define TAV_PACKET_IFRAME 0x10 -#define TAV_PACKET_GOP_UNIFIED 0x12 -#define TAV_PACKET_AUDIO_TAD 0x24 +// Fixed parameters +#define DT_SPATIAL_LEVELS 4 +#define DT_TEMPORAL_LEVELS 2 + +// Header sizes (before LDPC encoding) +#define DT_MAIN_HEADER_SIZE 28 // sync(4) + fps(1) + flags(1) + reserved(2) + size(4) + crc(4) + timecode(8) + offset(4) +#define DT_TAD_HEADER_SIZE 10 // sample_count(2) + quant_bits(1) + compressed_size(4) + rs_block_count(3) +#define DT_TAV_HEADER_SIZE 8 // gop_size(1) + compressed_size(4) + rs_block_count(3) + +// Quality level to quantiser mapping (must match encoder) +static const int QUALITY_Y[] = {79, 47, 23, 11, 5, 2}; +static const int QUALITY_CO[] = {123, 108, 91, 76, 59, 29}; +static const int QUALITY_CG[] = {148, 133, 113, 99, 76, 39}; + +#define MAX_PATH 4096 + +// ============================================================================= +// CRC-32 +// ============================================================================= -// CRC-32 table and functions static uint32_t crc32_table[256]; -static int crc32_table_initialized = 0; +static int crc32_initialized = 0; static void init_crc32_table(void) { - if (crc32_table_initialized) return; + if (crc32_initialized) return; for (uint32_t i = 0; i < 256; i++) { uint32_t crc = i; for (int j = 0; j < 8; j++) { @@ -46,7 +85,7 @@ static void init_crc32_table(void) { } crc32_table[i] = crc; } - crc32_table_initialized = 1; + crc32_initialized = 1; } static uint32_t calculate_crc32(const uint8_t *data, size_t length) { @@ -58,36 +97,34 @@ static uint32_t calculate_crc32(const uint8_t *data, size_t length) { return crc ^ 0xFFFFFFFF; } -// DT packet header structure (16 bytes) -typedef struct { - uint32_t sync_pattern; // 0xE3537A1F (NTSC) or 0xD193A745 (PAL) - uint8_t framerate; - uint8_t flags; // bit 0=interlaced, bit 1=NTSC framerate, bits 4-7=quality index - uint16_t reserved; - uint32_t packet_size; // Size of data after header - uint32_t crc32; // CRC-32 of first 12 bytes -} dt_packet_header_t; +// ============================================================================= +// Decoder Context +// ============================================================================= -// Decoder state typedef struct { + // Input/output + char *input_file; + char *output_file; FILE *input_fp; - FILE *output_video_fp; // For packet dump mode - FILE *output_audio_fp; // FFmpeg integration pid_t ffmpeg_pid; - FILE *video_pipe; // Pipe to FFmpeg for RGB24 frames - char *audio_temp_file; // Temporary file for PCMu8 audio + FILE *video_pipe; + char audio_temp_file[MAX_PATH]; + FILE *audio_temp_fp; + char video_temp_file[MAX_PATH]; + FILE *video_temp_fp; - // Video parameters (derived from sync pattern and quality index) + // Video parameters (derived from first packet) int width; int height; int framerate; int is_interlaced; int is_ntsc_framerate; int quality_index; + int is_pal; - // Video decoding context (uses shared library) + // Video decoder context tav_video_context_t *video_ctx; // Statistics @@ -95,66 +132,165 @@ typedef struct { uint64_t frames_decoded; uint64_t bytes_read; uint64_t crc_errors; + uint64_t fec_corrections; uint64_t sync_losses; // Options int verbose; - int ffmpeg_output; // If 1, output to FFmpeg (FFV1/MKV), if 0, dump packets + int dump_mode; // Just dump packets, don't decode } dt_decoder_t; -// Read DT packet header and verify -static int read_dt_header(dt_decoder_t *dec, dt_packet_header_t *header) { - uint8_t header_bytes[16]; +// ============================================================================= +// Utility Functions +// ============================================================================= - // Read 16-byte header - size_t bytes_read = fread(header_bytes, 1, 16, dec->input_fp); - if (bytes_read < 16) { - if (bytes_read > 0) { - fprintf(stderr, "Warning: Incomplete header at end of file (%zu bytes)\n", bytes_read); - } - return -1; // EOF or incomplete +static void print_usage(const char *program) { + printf("TAV-DT Decoder - Digital Tape Format with FEC\n"); + printf("\nUsage: %s -i input.tavdt -o output.mkv [options]\n\n", program); + printf("Required:\n"); + printf(" -i, --input FILE Input TAV-DT file\n"); + printf(" -o, --output FILE Output video file (FFV1/MKV)\n"); + printf("\nOptions:\n"); + printf(" --dump Dump packet info without decoding\n"); + printf(" -v, --verbose Verbose output\n"); + printf(" --help Show this help\n"); +} + +static void generate_random_filename(char *filename, size_t size) { + static int seeded = 0; + if (!seeded) { + srand((unsigned int)time(NULL)); + seeded = 1; } - dec->bytes_read += 16; + const char charset[] = "0123456789abcdefghijklmnopqrstuvwxyz"; + snprintf(filename, size, "/tmp/tavdt_dec_"); + size_t prefix_len = strlen(filename); + for (int i = 0; i < 16; i++) { + filename[prefix_len + i] = charset[rand() % (sizeof(charset) - 1)]; + } + filename[prefix_len + 16] = '\0'; +} + +// ============================================================================= +// Sync Pattern Search +// ============================================================================= + +static int find_sync_pattern(dt_decoder_t *dec) { + uint8_t sync_bytes[4] = {0}; + uint8_t byte; + + // NTSC and PAL sync patterns as byte arrays (big endian) + const uint8_t ntsc_sync[4] = {0xE3, 0x53, 0x7A, 0x1F}; + const uint8_t pal_sync[4] = {0xD1, 0x93, 0xA7, 0x45}; + + // Sliding window search + while (fread(&byte, 1, 1, dec->input_fp) == 1) { + dec->bytes_read++; + + // Shift window + sync_bytes[0] = sync_bytes[1]; + sync_bytes[1] = sync_bytes[2]; + sync_bytes[2] = sync_bytes[3]; + sync_bytes[3] = byte; + + // Check NTSC sync + if (memcmp(sync_bytes, ntsc_sync, 4) == 0) { + dec->is_pal = 0; + // Seek back to start of sync pattern + fseek(dec->input_fp, -4, SEEK_CUR); + dec->bytes_read -= 4; + return 0; + } + + // Check PAL sync + if (memcmp(sync_bytes, pal_sync, 4) == 0) { + dec->is_pal = 1; + // Seek back to start of sync pattern + fseek(dec->input_fp, -4, SEEK_CUR); + dec->bytes_read -= 4; + return 0; + } + } + + return -1; // EOF +} + +// ============================================================================= +// Header Decoding +// ============================================================================= + +typedef struct { + uint32_t sync_pattern; + uint8_t framerate; + uint8_t flags; + uint16_t reserved; + uint32_t packet_size; + uint32_t crc32; + uint64_t timecode_ns; + uint32_t offset_to_video; +} dt_packet_header_t; + +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]; + size_t 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 (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) { + if (dec->verbose) { + fprintf(stderr, "Warning: LDPC decode failed for main header\n"); + } + // Try to use raw data anyway (first half) + memcpy(decoded_header, encoded_header, DT_MAIN_HEADER_SIZE); + } else if (ldpc_result > 0) { + dec->fec_corrections++; + } // Parse header fields - header->sync_pattern = (header_bytes[0] << 24) | (header_bytes[1] << 16) | - (header_bytes[2] << 8) | header_bytes[3]; - header->framerate = header_bytes[4]; - header->flags = header_bytes[5]; - header->reserved = header_bytes[6] | (header_bytes[7] << 8); - header->packet_size = header_bytes[8] | (header_bytes[9] << 8) | - (header_bytes[10] << 16) | (header_bytes[11] << 24); - header->crc32 = header_bytes[12] | (header_bytes[13] << 8) | - (header_bytes[14] << 16) | (header_bytes[15] << 24); + 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); // 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 at offset %lu\n", - header->sync_pattern, dec->bytes_read - 16); + fprintf(stderr, "Warning: Invalid sync pattern 0x%08X\n", header->sync_pattern); } dec->sync_losses++; - return -2; // Invalid sync + return -2; } - // Calculate and verify CRC-32 of first 12 bytes - uint32_t calculated_crc = calculate_crc32(header_bytes, 12); + // 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) { - fprintf(stderr, "Warning: CRC mismatch at offset %lu (expected 0x%08X, got 0x%08X)\n", - dec->bytes_read - 16, header->crc32, calculated_crc); + if (dec->verbose) { + fprintf(stderr, "Warning: CRC mismatch (expected 0x%08X, got 0x%08X)\n", + header->crc32, calculated_crc); + } dec->crc_errors++; - // Continue anyway - data might still be usable + // Continue anyway } - // Update decoder state from header (first packet only) + // Update decoder state from first packet if (dec->packets_processed == 0) { - dec->width = (header->sync_pattern == TAV_DT_SYNC_NTSC) ? 720 : 720; - dec->height = (header->sync_pattern == TAV_DT_SYNC_NTSC) ? 480 : 576; + dec->width = DT_WIDTH; + dec->height = (header->sync_pattern == TAV_DT_SYNC_PAL) ? DT_HEIGHT_PAL : DT_HEIGHT_NTSC; dec->framerate = header->framerate; dec->is_interlaced = header->flags & 0x01; dec->is_ntsc_framerate = header->flags & 0x02; dec->quality_index = (header->flags >> 4) & 0x0F; + if (dec->quality_index > 5) dec->quality_index = 5; if (dec->verbose) { printf("=== TAV-DT Stream Info ===\n"); @@ -172,82 +308,353 @@ static int read_dt_header(dt_decoder_t *dec, dt_packet_header_t *header) { return 0; } -// Search for next sync pattern (for recovery from errors) -static int find_next_sync(dt_decoder_t *dec) { - uint8_t sync_bytes[4] = {0}; - uint8_t byte; +// ============================================================================= +// Subpacket Decoding +// ============================================================================= - // NTSC and PAL sync patterns as byte arrays (big endian) - const uint8_t ntsc_sync[4] = {0xE3, 0x53, 0x7A, 0x1F}; - const uint8_t pal_sync[4] = {0xD1, 0x93, 0xA7, 0x45}; +static int decode_audio_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t data_len, + size_t *consumed) { + // Minimum: 20 byte LDPC header + if (data_len < DT_TAD_HEADER_SIZE * 2) return -1; - // Read first 4 bytes to initialize window - for (int i = 0; i < 4; i++) { - if (fread(&byte, 1, 1, dec->input_fp) != 1) { - return -1; // EOF + size_t offset = 0; + + // LDPC decode TAD header (20 bytes -> 10 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) { + if (dec->verbose) { + fprintf(stderr, "Warning: LDPC decode failed for TAD header\n"); } - dec->bytes_read++; - sync_bytes[i] = byte; + memcpy(decoded_tad_header, data + offset, DT_TAD_HEADER_SIZE); + } else if (ldpc_result > 0) { + dec->fec_corrections++; + } + offset += DT_TAD_HEADER_SIZE * 2; + + // Parse TAD header + uint16_t sample_count; + uint8_t quant_bits; + uint32_t compressed_size; + uint32_t rs_block_count; + + memcpy(&sample_count, decoded_tad_header, 2); + quant_bits = decoded_tad_header[2]; + memcpy(&compressed_size, decoded_tad_header + 3, 4); + // uint24 rs_block_count (little endian) + rs_block_count = decoded_tad_header[7] | + ((uint32_t)decoded_tad_header[8] << 8) | + ((uint32_t)decoded_tad_header[9] << 16); + + if (dec->verbose) { + printf(" TAD: samples=%u, quant_bits=%u, compressed=%u, rs_blocks=%u\n", + sample_count, quant_bits, compressed_size, rs_block_count); } - // Check if we already have a sync pattern at current position - if (memcmp(sync_bytes, ntsc_sync, 4) == 0 || memcmp(sync_bytes, pal_sync, 4) == 0) { - // Rewind to start of sync pattern - fseek(dec->input_fp, -4, SEEK_CUR); - dec->bytes_read -= 4; + // Calculate RS payload size + size_t rs_total = rs_block_count * RS_BLOCK_SIZE; + + if (offset + rs_total > data_len) { if (dec->verbose) { - printf("Found sync at offset %lu\n", dec->bytes_read); + fprintf(stderr, "Warning: Audio packet truncated\n"); + } + *consumed = data_len; + return -1; + } + + // RS decode payload + uint8_t *rs_data = malloc(rs_total); + if (!rs_data) return -1; + memcpy(rs_data, data + offset, rs_total); + + uint8_t *decoded_payload = malloc(compressed_size); + if (!decoded_payload) { + free(rs_data); + return -1; + } + + int rs_result = rs_decode_blocks(rs_data, rs_total, decoded_payload, compressed_size); + if (rs_result < 0) { + if (dec->verbose) { + fprintf(stderr, "Warning: RS decode failed for audio\n"); + } + } else if (rs_result > 0) { + dec->fec_corrections += rs_result; + } + + // decoded_payload already contains the full TAD chunk format: + // [sample_count(2)][max_index(1)][payload_size(4)][zstd_data] + // No need to rebuild the header - pass it directly to the TAD decoder + + // Decode TAD to PCMu8 + uint8_t *pcmu8_output = malloc(sample_count * 2); + if (!pcmu8_output) { + free(rs_data); + free(decoded_payload); + return -1; + } + + size_t bytes_consumed_tad, samples_decoded; + int tad_result = tad32_decode_chunk(decoded_payload, compressed_size, pcmu8_output, + &bytes_consumed_tad, &samples_decoded); + + if (tad_result == 0 && samples_decoded > 0 && dec->audio_temp_fp) { + fwrite(pcmu8_output, 1, samples_decoded * 2, dec->audio_temp_fp); + } + + free(pcmu8_output); + free(rs_data); + free(decoded_payload); + + offset += rs_total; + *consumed = offset; + + return 0; +} + +static int decode_video_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t data_len, + size_t *consumed) { + // Minimum: 16 byte LDPC header + if (data_len < DT_TAV_HEADER_SIZE * 2) return -1; + + size_t offset = 0; + + // LDPC decode TAV header (16 bytes -> 8 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) { + if (dec->verbose) { + fprintf(stderr, "Warning: LDPC decode failed for TAV header\n"); + } + memcpy(decoded_tav_header, data + offset, DT_TAV_HEADER_SIZE); + } else if (ldpc_result > 0) { + dec->fec_corrections++; + } + offset += DT_TAV_HEADER_SIZE * 2; + + // Parse TAV header + uint8_t gop_size = decoded_tav_header[0]; + uint32_t compressed_size; + uint32_t rs_block_count; + + memcpy(&compressed_size, decoded_tav_header + 1, 4); + // uint24 rs_block_count (little endian) + rs_block_count = decoded_tav_header[5] | + ((uint32_t)decoded_tav_header[6] << 8) | + ((uint32_t)decoded_tav_header[7] << 16); + + if (dec->verbose) { + printf(" TAV: gop_size=%u, compressed=%u, rs_blocks=%u\n", + gop_size, compressed_size, rs_block_count); + } + + // Calculate RS payload size + size_t rs_total = rs_block_count * RS_BLOCK_SIZE; + + if (offset + rs_total > data_len) { + if (dec->verbose) { + fprintf(stderr, "Warning: Video packet truncated\n"); + } + *consumed = data_len; + return -1; + } + + // RS decode payload + uint8_t *rs_data = malloc(rs_total); + if (!rs_data) return -1; + memcpy(rs_data, data + offset, rs_total); + + uint8_t *decoded_payload = malloc(compressed_size); + if (!decoded_payload) { + free(rs_data); + return -1; + } + + int rs_result = rs_decode_blocks(rs_data, rs_total, decoded_payload, compressed_size); + if (rs_result < 0) { + if (dec->verbose) { + fprintf(stderr, "Warning: RS decode failed for video\n"); + } + } else if (rs_result > 0) { + dec->fec_corrections += rs_result; + } + + // Initialize video decoder if needed + if (!dec->video_ctx) { + tav_video_params_t vparams; + vparams.width = dec->width; + vparams.height = dec->is_interlaced ? dec->height / 2 : dec->height; + vparams.decomp_levels = DT_SPATIAL_LEVELS; + vparams.temporal_levels = DT_TEMPORAL_LEVELS; + vparams.wavelet_filter = 1; // CDF 9/7 + vparams.temporal_wavelet = 255; // Haar + vparams.entropy_coder = 1; // EZBC + vparams.channel_layout = 0; // YCoCg-R + vparams.perceptual_tuning = 1; + vparams.quantiser_y = QUALITY_Y[dec->quality_index]; + vparams.quantiser_co = QUALITY_CO[dec->quality_index]; + vparams.quantiser_cg = QUALITY_CG[dec->quality_index]; + vparams.encoder_preset = 0x01; // Sports + vparams.monoblock = 1; + + dec->video_ctx = tav_video_create(&vparams); + if (!dec->video_ctx) { + fprintf(stderr, "Error: Cannot create video decoder\n"); + free(rs_data); + free(decoded_payload); + return -1; + } + if (dec->verbose) { + tav_video_set_verbose(dec->video_ctx, 1); + } + } + + // Allocate frame buffers + int internal_height = dec->is_interlaced ? dec->height / 2 : dec->height; + size_t frame_size = dec->width * internal_height * 3; + uint8_t **rgb_frames = malloc(gop_size * sizeof(uint8_t *)); + for (int i = 0; i < gop_size; i++) { + rgb_frames[i] = malloc(frame_size); + } + + // Decode GOP + // The encoder packet format is [type(1)][gop_size(1)][size(4)][zstd_data] + // Skip the 6-byte header to get to the raw Zstd-compressed data + const uint8_t *zstd_data = decoded_payload + 6; + size_t zstd_size = compressed_size > 6 ? compressed_size - 6 : 0; + + // Debug: check packet header + if (dec->verbose && decoded_payload) { + fprintf(stderr, "DEBUG: Video packet header: type=0x%02x gop=%d size=%u (total=%u, zstd=%zu)\n", + decoded_payload[0], decoded_payload[1], + *(uint32_t*)(decoded_payload + 2), (unsigned)compressed_size, zstd_size); + fprintf(stderr, "DEBUG: First 16 bytes of zstd data: "); + for (int i = 0; i < 16 && i < (int)zstd_size; i++) { + fprintf(stderr, "%02x ", zstd_data[i]); + } + fprintf(stderr, "\n"); + } + + int decode_result = tav_video_decode_gop(dec->video_ctx, zstd_data, zstd_size, + gop_size, rgb_frames); + + if (decode_result == 0) { + // Write frames to video temp file + for (int i = 0; i < gop_size; i++) { + if (dec->video_temp_fp) { + fwrite(rgb_frames[i], 1, frame_size, dec->video_temp_fp); + } + dec->frames_decoded++; + } + } else { + if (dec->verbose) { + const char *err = tav_video_get_error(dec->video_ctx); + fprintf(stderr, "Warning: Video decode failed: %s\n", err ? err : "unknown error"); + } + } + + // Cleanup + for (int i = 0; i < gop_size; i++) { + free(rgb_frames[i]); + } + free(rgb_frames); + free(rs_data); + free(decoded_payload); + + offset += rs_total; + *consumed = offset; + + return 0; +} + +// ============================================================================= +// FFmpeg Output +// ============================================================================= + +// Mux decoded video and audio temp files into final output +static int mux_output(dt_decoder_t *dec) { + if (!dec->output_file) { + if (dec->verbose) { + printf("No output file specified, skipping mux\n"); } return 0; } - // Sliding window search - while (fread(&byte, 1, 1, dec->input_fp) == 1) { - dec->bytes_read++; - - // Shift window - sync_bytes[0] = sync_bytes[1]; - sync_bytes[1] = sync_bytes[2]; - sync_bytes[2] = sync_bytes[3]; - sync_bytes[3] = byte; - - // Check NTSC sync - if (memcmp(sync_bytes, ntsc_sync, 4) == 0) { - // Rewind to start of sync pattern - fseek(dec->input_fp, -4, SEEK_CUR); - dec->bytes_read -= 4; - if (dec->verbose) { - printf("Found NTSC sync at offset %lu\n", dec->bytes_read); - } - return 0; - } - - // Check PAL sync - if (memcmp(sync_bytes, pal_sync, 4) == 0) { - // Rewind to start of sync pattern - fseek(dec->input_fp, -4, SEEK_CUR); - dec->bytes_read -= 4; - if (dec->verbose) { - printf("Found PAL sync at offset %lu\n", dec->bytes_read); - } - return 0; - } + if (dec->frames_decoded == 0) { + fprintf(stderr, "Warning: No frames decoded, skipping mux\n"); + return -1; } - return -1; // EOF without finding sync + if (dec->verbose) { + printf("Muxing output to %s...\n", dec->output_file); + } + + int internal_height = dec->is_interlaced ? dec->height / 2 : dec->height; + char video_size[32]; + char framerate[16]; + snprintf(video_size, sizeof(video_size), "%dx%d", dec->width, internal_height); + snprintf(framerate, sizeof(framerate), "%d", dec->framerate); + + pid_t pid = fork(); + if (pid < 0) { + fprintf(stderr, "Error: Failed to fork for FFmpeg\n"); + return -1; + } + + if (pid == 0) { + // Child process - execute FFmpeg + execl("/usr/bin/ffmpeg", "ffmpeg", + "-f", "rawvideo", + "-pixel_format", "rgb24", + "-video_size", video_size, + "-framerate", framerate, + "-i", dec->video_temp_file, + "-f", "u8", + "-ar", "32000", + "-ac", "2", + "-i", dec->audio_temp_file, + "-c:v", "ffv1", + "-level", "3", + "-coder", "1", + "-context", "1", + "-g", "1", + "-slices", "24", + "-slicecrc", "1", + "-pixel_format", "rgb24", + "-c:a", "pcm_u8", + "-f", "matroska", + dec->output_file, + "-y", + "-v", "warning", + (char*)NULL); + + fprintf(stderr, "Error: Failed to execute FFmpeg\n"); + exit(1); + } else { + // Parent process - wait for FFmpeg + int status; + waitpid(pid, &status, 0); + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { + if (dec->verbose) { + printf("Output written to %s\n", dec->output_file); + } + return 0; + } else { + fprintf(stderr, "Warning: FFmpeg mux failed (status %d)\n", WEXITSTATUS(status)); + return -1; + } + } } -// Spawn FFmpeg process for video/audio muxing -static int spawn_ffmpeg(dt_decoder_t *dec, const char *output_file) { +// Spawn FFmpeg for streaming output (unused in current implementation) +static int spawn_ffmpeg(dt_decoder_t *dec) { int video_pipe_fd[2]; - // Create pipe for video data if (pipe(video_pipe_fd) < 0) { fprintf(stderr, "Error: Failed to create video pipe\n"); return -1; } - // Fork FFmpeg process dec->ffmpeg_pid = fork(); if (dec->ffmpeg_pid < 0) { @@ -259,14 +666,14 @@ static int spawn_ffmpeg(dt_decoder_t *dec, const char *output_file) { if (dec->ffmpeg_pid == 0) { // Child process - execute FFmpeg - close(video_pipe_fd[1]); // Close write end + close(video_pipe_fd[1]); + int internal_height = dec->is_interlaced ? dec->height / 2 : dec->height; char video_size[32]; char framerate[16]; - snprintf(video_size, sizeof(video_size), "%dx%d", dec->width, dec->height); + snprintf(video_size, sizeof(video_size), "%dx%d", dec->width, internal_height); snprintf(framerate, sizeof(framerate), "%d", dec->framerate); - // Redirect video pipe to fd 3 dup2(video_pipe_fd[0], 3); close(video_pipe_fd[0]); @@ -275,37 +682,33 @@ static int spawn_ffmpeg(dt_decoder_t *dec, const char *output_file) { "-pixel_format", "rgb24", "-video_size", video_size, "-framerate", framerate, - "-i", "pipe:3", // Video from fd 3 - "-f", "u8", // Raw unsigned 8-bit PCM - "-ar", "32000", // 32 KHz sample rate - "-ac", "2", // Stereo - "-i", dec->audio_temp_file, // Audio from temp file - "-color_range", "2", - "-c:v", "ffv1", // FFV1 codec - "-level", "3", // FFV1 level 3 - "-coder", "1", // Range coder - "-context", "1", // Large context - "-g", "1", // GOP size 1 (all I-frames) - "-slices", "24", // 24 slices for threading - "-slicecrc", "1", // CRC per slice + "-i", "pipe:3", + "-f", "u8", + "-ar", "32000", + "-ac", "2", + "-i", dec->audio_temp_file, + "-c:v", "ffv1", + "-level", "3", + "-coder", "1", + "-context", "1", + "-g", "1", + "-slices", "24", + "-slicecrc", "1", "-pixel_format", "rgb24", - "-color_range", "2", - "-c:a", "pcm_u8", // Audio codec (PCM unsigned 8-bit) - "-f", "matroska", // MKV container - output_file, - "-y", // Overwrite output - "-v", "warning", // Minimal logging + "-c:a", "pcm_u8", + "-f", "matroska", + dec->output_file, + "-y", + "-v", "warning", (char*)NULL); fprintf(stderr, "Error: Failed to execute FFmpeg\n"); exit(1); } else { - // Parent process - close(video_pipe_fd[0]); // Close read end - + close(video_pipe_fd[0]); dec->video_pipe = fdopen(video_pipe_fd[1], "wb"); if (!dec->video_pipe) { - fprintf(stderr, "Error: Failed to open video pipe for writing\n"); + fprintf(stderr, "Error: Failed to open video pipe\n"); kill(dec->ffmpeg_pid, SIGTERM); return -1; } @@ -314,495 +717,190 @@ static int spawn_ffmpeg(dt_decoder_t *dec, const char *output_file) { return 0; } -// Process single DT packet -static int process_dt_packet(dt_decoder_t *dec) { +// ============================================================================= +// Main Decoding Loop +// ============================================================================= + +static int process_packet(dt_decoder_t *dec) { dt_packet_header_t header; - // Read and verify header - int result = read_dt_header(dec, &header); - if (result == -1) { + // Find and read header + if (find_sync_pattern(dec) != 0) { return -1; // EOF - } else if (result == -2) { - // Invalid sync - try to recover (sync search always enabled) - if (find_next_sync(dec) == 0) { - // Found sync, try again - return process_dt_packet(dec); - } - return -2; // Unrecoverable sync loss } - // Allocate buffer for packet data + if (read_and_decode_header(dec, &header) != 0) { + // Try to recover + return 0; // Continue + } + + if (dec->verbose) { + double timecode_sec = header.timecode_ns / 1000000000.0; + printf("Packet %lu: timecode=%.3fs, size=%u, offset_to_video=%u\n", + dec->packets_processed + 1, timecode_sec, header.packet_size, header.offset_to_video); + } + + // Read packet payload (contains both TAD and TAV subpackets) uint8_t *packet_data = malloc(header.packet_size); - if (!packet_data) { - fprintf(stderr, "Error: Failed to allocate %u bytes for packet data\n", header.packet_size); - return -3; - } + if (!packet_data) return -1; - // Read packet data size_t bytes_read = fread(packet_data, 1, header.packet_size, dec->input_fp); if (bytes_read < header.packet_size) { - fprintf(stderr, "Error: Incomplete packet data (%zu/%u bytes)\n", bytes_read, header.packet_size); + if (dec->verbose) { + fprintf(stderr, "Warning: Incomplete packet (got %zu, expected %u)\n", + bytes_read, header.packet_size); + } free(packet_data); - return -4; + return -1; } - dec->bytes_read += bytes_read; - // Parse packet contents: - // 1. Timecode (8 bytes, no header) - // 2. TAD audio packet(s) (full packet with 0x24 header) - // 3. TAV video packet (full packet with 0x10 or 0x12 header) - - size_t offset = 0; - - // 1. Read timecode (8 bytes) - if (offset + 8 > header.packet_size) { - fprintf(stderr, "Error: Packet too small for timecode\n"); - free(packet_data); - return -5; + // 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, &tad_consumed); } - uint64_t timecode_ns = 0; - for (int i = 0; i < 8; i++) { - timecode_ns |= ((uint64_t)packet_data[offset + i]) << (i * 8); - } - offset += 8; - - if (dec->verbose && dec->packets_processed % 100 == 0) { - double timecode_sec = timecode_ns / 1000000000.0; - printf("Packet %lu: timecode=%.3fs, size=%u bytes\n", - dec->packets_processed, timecode_sec, header.packet_size); + // Process TAV subpacket (video comes after audio) + if (header.offset_to_video < header.packet_size) { + size_t tav_consumed = 0; + decode_video_subpacket(dec, packet_data + header.offset_to_video, + header.packet_size - header.offset_to_video, &tav_consumed); } - // 2. Process TAD audio packet(s) - while (offset < header.packet_size && packet_data[offset] == TAV_PACKET_AUDIO_TAD) { - offset++; // Skip packet type byte (0x24) + dec->packets_processed++; - // Parse TAD packet format: [sample_count(2)][payload_size+7(4)][sample_count(2)][quant_index(1)][compressed_size(4)][compressed_data] - if (offset + 6 > header.packet_size) break; - - uint16_t sample_count = packet_data[offset] | (packet_data[offset+1] << 8); - offset += 2; - - uint32_t payload_size_plus_7 = packet_data[offset] | (packet_data[offset+1] << 8) | - (packet_data[offset+2] << 16) | (packet_data[offset+3] << 24); - offset += 4; - - // Total TAD packet content size (everything after the payload_size_plus_7 field) - uint32_t tad_content_size = payload_size_plus_7; - - // TAD packet data (sample_count repeat + quant_index + compressed_size + compressed_data) - if (offset + tad_content_size > header.packet_size) { - fprintf(stderr, "Warning: TAD packet extends beyond DT packet boundary (offset=%zu, content=%u, packet_size=%u)\n", - offset, tad_content_size, header.packet_size); - break; - } - - // The TAD decoder expects: [sample_count(2)][quant_index(1)][compressed_size(4)][compressed_data] - // This is exactly what we have starting at the current offset (the repeated sample_count field) - - // Peek at the TAD packet structure for verbose output - uint16_t sample_count_repeat = packet_data[offset] | (packet_data[offset+1] << 8); - uint8_t quant_index = packet_data[offset + 2]; - uint32_t compressed_size = packet_data[offset+3] | (packet_data[offset+4] << 8) | - (packet_data[offset+5] << 16) | (packet_data[offset+6] << 24); - - if (dec->verbose) { - printf(" TAD: samples=%u, quant=%u, compressed=%u bytes\n", - sample_count, quant_index, compressed_size); - } - - // Decode TAD audio using shared decoder - // Allocate output buffer (max chunk size * 2 channels) - uint8_t *pcm_output = malloc(65536 * 2); // Max chunk size for TAD - if (!pcm_output) { - fprintf(stderr, "Error: Failed to allocate audio decode buffer\n"); - offset += tad_content_size; - continue; - } - - size_t bytes_consumed = 0; - size_t samples_decoded = 0; - - // Pass the TAD data starting from repeated sample_count - // The decoder expects: [sample_count(2)][quant(1)][payload_size(4)][compressed_data] - int decode_result = tad32_decode_chunk(packet_data + offset, tad_content_size, - pcm_output, &bytes_consumed, &samples_decoded); - if (decode_result == 0) { - // Write PCMu8 to output (samples * 2 channels) - if (dec->output_audio_fp) { - fwrite(pcm_output, 1, samples_decoded * 2, dec->output_audio_fp); - } - } else { - fprintf(stderr, "Warning: TAD decode failed at offset %zu\n", offset); - } - - free(pcm_output); - - offset += tad_content_size; - } - - // 3. Process TAV video packet - if (offset < header.packet_size) { - uint8_t packet_type = packet_data[offset]; - offset++; // Skip packet type byte - - if (packet_type == TAV_PACKET_GOP_UNIFIED) { - // Read GOP_UNIFIED packet structure: [gop_size(1)][compressed_size(4)][compressed_data] - if (offset + 5 > header.packet_size) { - fprintf(stderr, "Warning: Incomplete GOP packet header\n"); - free(packet_data); - return 0; - } - - uint8_t gop_size = packet_data[offset]; - offset++; - - uint32_t compressed_size = packet_data[offset] | (packet_data[offset+1] << 8) | - (packet_data[offset+2] << 16) | (packet_data[offset+3] << 24); - offset += 4; - - if (dec->verbose) { - printf(" Video packet: GOP_UNIFIED, %u frames, %u bytes compressed\n", - gop_size, compressed_size); - } - - if (offset + compressed_size > header.packet_size) { - fprintf(stderr, "Warning: GOP data extends beyond packet boundary\n"); - free(packet_data); - return 0; - } - - // Allocate frame buffers for GOP - uint8_t **rgb_frames = malloc(gop_size * sizeof(uint8_t*)); - for (int i = 0; i < gop_size; i++) { - rgb_frames[i] = malloc(dec->width * dec->height * 3); - } - - // Decode GOP using shared library - int decode_result = tav_video_decode_gop(dec->video_ctx, - packet_data + offset, compressed_size, - gop_size, rgb_frames); - - if (decode_result == 0) { - // Write frames to FFmpeg or dump file - for (int i = 0; i < gop_size; i++) { - if (dec->video_pipe) { - // Write RGB24 frame to FFmpeg - fwrite(rgb_frames[i], 1, dec->width * dec->height * 3, dec->video_pipe); - } else if (dec->output_video_fp) { - // Packet dump mode - write raw packet - if (i == 0) { // Only write packet once - fwrite(&packet_type, 1, 1, dec->output_video_fp); - fwrite(&gop_size, 1, 1, dec->output_video_fp); - fwrite(&compressed_size, 4, 1, dec->output_video_fp); - fwrite(packet_data + offset, 1, compressed_size, dec->output_video_fp); - } - } - } - dec->frames_decoded += gop_size; - } else { - fprintf(stderr, "Warning: GOP decode failed: %s\n", - tav_video_get_error(dec->video_ctx)); - } - - // Free frame buffers - for (int i = 0; i < gop_size; i++) { - free(rgb_frames[i]); - } - free(rgb_frames); - - } else if (packet_type == TAV_PACKET_IFRAME) { - // I-frame packet - for packet dump mode - if (dec->output_video_fp) { - fwrite(&packet_type, 1, 1, dec->output_video_fp); - fwrite(packet_data + offset, 1, header.packet_size - offset, dec->output_video_fp); - } - dec->frames_decoded++; - } + if (!dec->verbose && dec->packets_processed % 10 == 0) { + fprintf(stderr, "\rDecoding packet %lu, frames: %lu...", + dec->packets_processed, dec->frames_decoded); } free(packet_data); - dec->packets_processed++; + return 0; +} + +static int run_decoder(dt_decoder_t *dec) { + // Open input file + dec->input_fp = fopen(dec->input_file, "rb"); + if (!dec->input_fp) { + fprintf(stderr, "Error: Cannot open input file: %s\n", dec->input_file); + return -1; + } + + // Create temp file for audio + generate_random_filename(dec->audio_temp_file, sizeof(dec->audio_temp_file)); + dec->audio_temp_fp = fopen(dec->audio_temp_file, "wb"); + if (!dec->audio_temp_fp) { + fprintf(stderr, "Warning: Cannot create temp audio file, audio will be skipped\n"); + } + + // Create temp file for video + generate_random_filename(dec->video_temp_file, sizeof(dec->video_temp_file)); + dec->video_temp_fp = fopen(dec->video_temp_file, "wb"); + if (!dec->video_temp_fp) { + fprintf(stderr, "Warning: Cannot create temp video file, video will be skipped\n"); + } + + // Decode all packets + if (dec->verbose) { + printf("Decoding TAV-DT stream...\n"); + } + + // Decode all packets, writing to temp files + while (process_packet(dec) == 0) { + // Progress is shown in process_packet + } + + // Close temp files for reading by FFmpeg + if (dec->audio_temp_fp) { + fclose(dec->audio_temp_fp); + dec->audio_temp_fp = NULL; + } + if (dec->video_temp_fp) { + fclose(dec->video_temp_fp); + dec->video_temp_fp = NULL; + } + + 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(" Sync losses: %lu\n", dec->sync_losses); + + // Mux output files + mux_output(dec); + + // Cleanup + if (dec->video_ctx) { + tav_video_free(dec->video_ctx); + } + if (dec->video_pipe) { + fclose(dec->video_pipe); + waitpid(dec->ffmpeg_pid, NULL, 0); + } + if (dec->input_fp) { + fclose(dec->input_fp); + } + + // Remove temp files + unlink(dec->audio_temp_file); + unlink(dec->video_temp_file); return 0; } -static void show_usage(const char *prog_name) { - printf("Usage: %s [options] -i input.tav -o output.mkv\n\n", prog_name); - printf("TAV-DT Decoder - Headerless streaming format decoder\n\n"); - printf("Options:\n"); - printf(" -i, --input FILE Input TAV-DT file (required)\n"); - printf(" -o, --output FILE Output MKV file (default: input with .mkv extension)\n"); - printf(" -v, --verbose Verbose output\n"); - printf(" -h, --help Show this help\n\n"); - printf("Notes:\n"); - printf(" - Audio is decoded to temporary file in /tmp/\n"); - printf(" - Sync pattern searching is always enabled\n\n"); - printf("Example:\n"); - printf(" %s -i stream.tavdt # Creates stream.mkv\n", prog_name); - printf(" %s -i stream.tavdt -o out.mkv # Creates out.mkv\n\n", prog_name); -} +// ============================================================================= +// Main +// ============================================================================= -int main(int argc, char *argv[]) { - dt_decoder_t decoder = {0}; - char *input_file = NULL; - char *output_file = NULL; +int main(int argc, char **argv) { + dt_decoder_t dec; + memset(&dec, 0, sizeof(dec)); + + // Initialize FEC libraries + rs_init(); + ldpc_init(); - // Parse command line options static struct option long_options[] = { - {"input", required_argument, 0, 'i'}, - {"output", required_argument, 0, 'o'}, - {"verbose", no_argument, 0, 'v'}, - {"help", no_argument, 0, 'h'}, + {"input", required_argument, 0, 'i'}, + {"output", required_argument, 0, 'o'}, + {"dump", no_argument, 0, 'd'}, + {"verbose", no_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; int opt; - while ((opt = getopt_long(argc, argv, "i:o:vh", long_options, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "i:o:dvh", long_options, NULL)) != -1) { switch (opt) { case 'i': - input_file = optarg; + dec.input_file = optarg; break; case 'o': - output_file = optarg; + dec.output_file = optarg; + break; + case 'd': + dec.dump_mode = 1; break; case 'v': - decoder.verbose = 1; + dec.verbose = 1; break; case 'h': - show_usage(argv[0]); - return 0; default: - show_usage(argv[0]); - return 1; + print_usage(argv[0]); + return opt == 'h' ? 0 : 1; } } - if (!input_file) { - fprintf(stderr, "Error: Input file must be specified\n"); - show_usage(argv[0]); + // Validate arguments + if (!dec.input_file || !dec.output_file) { + fprintf(stderr, "Error: Input and output files are required\n"); + print_usage(argv[0]); return 1; } - // Generate output filename if not provided - if (!output_file) { - size_t input_len = strlen(input_file); - output_file = malloc(input_len + 32); // Extra space for extension - - // Find the last directory separator - const char *basename_start = strrchr(input_file, '/'); - if (!basename_start) basename_start = strrchr(input_file, '\\'); - basename_start = basename_start ? basename_start + 1 : input_file; - - // Copy directory part - size_t dir_len = basename_start - input_file; - strncpy(output_file, input_file, dir_len); - - // Find the extension - const char *ext = strrchr(basename_start, '.'); - if (ext && (strcmp(ext, ".tavdt") == 0 || strcmp(ext, ".tav") == 0 || strcmp(ext, ".dt") == 0)) { - // Copy basename without extension - size_t name_len = ext - basename_start; - strncpy(output_file + dir_len, basename_start, name_len); - output_file[dir_len + name_len] = '\0'; - } else { - // No recognized extension, copy entire basename - strcpy(output_file + dir_len, basename_start); - } - - // Append .mkv extension - strcat(output_file, ".mkv"); - - if (decoder.verbose) { - printf("Auto-generated output path: %s\n", output_file); - } - } - - // Open input file - decoder.input_fp = fopen(input_file, "rb"); - if (!decoder.input_fp) { - fprintf(stderr, "Error: Cannot open input file: %s\n", input_file); - return 1; - } - - // Determine output mode based on file extension - int output_is_mkv = (strstr(output_file, ".mkv") != NULL || strstr(output_file, ".MKV") != NULL); - decoder.ffmpeg_output = output_is_mkv; - - // Create temporary audio file in /tmp/ (using process ID for uniqueness) - char temp_audio_file[256]; - snprintf(temp_audio_file, sizeof(temp_audio_file), "/tmp/tav_dt_audio_%d.pcm", getpid()); - decoder.audio_temp_file = strdup(temp_audio_file); - - // Open audio output file - decoder.output_audio_fp = fopen(decoder.audio_temp_file, "wb"); - if (!decoder.output_audio_fp) { - fprintf(stderr, "Error: Cannot open temporary audio file: %s\n", decoder.audio_temp_file); - fclose(decoder.input_fp); - return 1; - } - - // In packet dump mode, open video packet file - char video_packets_file[256]; - if (!decoder.ffmpeg_output) { - snprintf(video_packets_file, sizeof(video_packets_file), "%s.packets", output_file); - decoder.output_video_fp = fopen(video_packets_file, "wb"); - } - - printf("TAV-DT Decoder - %s\n", DECODER_VENDOR_STRING); - printf("Input: %s\n", input_file); - if (decoder.ffmpeg_output) { - printf("Output: %s (FFV1/MKV)\n", output_file); - } else { - printf("Output video: %s (packet dump)\n", video_packets_file); - } - printf("\n"); - - // Find first sync pattern (works even when sync is at offset 0) - if (decoder.verbose) { - printf("Searching for first sync pattern...\n"); - } - if (find_next_sync(&decoder) != 0) { - fprintf(stderr, "Error: No sync pattern found in file\n"); - fclose(decoder.input_fp); - fclose(decoder.output_audio_fp); - if (decoder.output_video_fp) fclose(decoder.output_video_fp); - return 1; - } - - // Read first DT packet header to get video parameters (without processing content) - dt_packet_header_t first_header; - if (read_dt_header(&decoder, &first_header) != 0) { - fprintf(stderr, "Error: Failed to read first packet header\n"); - fclose(decoder.input_fp); - fclose(decoder.output_audio_fp); - if (decoder.output_video_fp) fclose(decoder.output_video_fp); - return 1; - } - - // Rewind to start of header so process_dt_packet() can read it again - fseek(decoder.input_fp, -16, SEEK_CUR); - - // Validate quality index (0-5) - if (decoder.quality_index > 5) { - fprintf(stderr, "Warning: Quality index %d out of range (0-5), clamping to 5\n", decoder.quality_index); - decoder.quality_index = 5; - } - - // Map quality index to actual quantiser values using standard TAV arrays - uint16_t quant_y = QUALITY_Y[decoder.quality_index]; - uint16_t quant_co = QUALITY_CO[decoder.quality_index]; - uint16_t quant_cg = QUALITY_CG[decoder.quality_index]; - - if (decoder.verbose) { - printf("=== Quantiser Mapping ===\n"); - printf(" Quality index: %d\n", decoder.quality_index); - printf(" Quantiser indices: Y=%d Co=%d Cg=%d\n", quant_y, quant_co, quant_cg); - printf("=========================\n\n"); - } - - // Initialize video decoder with TAV-DT fixed parameters - tav_video_params_t video_params = { - .width = decoder.width, - .height = decoder.height, - .decomp_levels = 4, // TAV-DT fixed: 4 spatial levels - .temporal_levels = 2, // TAV-DT fixed: 2 temporal levels - .wavelet_filter = 1, // TAV-DT fixed: CDF 9/7 - .temporal_wavelet = 0, // TAV-DT fixed: Haar - .entropy_coder = 1, // TAV-DT fixed: EZBC - .channel_layout = 0, // TAV-DT fixed: YCoCg-R - .perceptual_tuning = 1, // TAV-DT fixed: Perceptual - .quantiser_y = (uint8_t)quant_y, // From DT quality map - .quantiser_co = (uint8_t)quant_co, - .quantiser_cg = (uint8_t)quant_cg, - .encoder_preset = 1, // Sports mode - .monoblock = 1 // TAV-DT fixed: Single tile - }; - - decoder.video_ctx = tav_video_create(&video_params); - if (!decoder.video_ctx) { - fprintf(stderr, "Error: Failed to create video decoder context\n"); - fclose(decoder.input_fp); - fclose(decoder.output_audio_fp); - if (decoder.output_video_fp) fclose(decoder.output_video_fp); - return 1; - } - - tav_video_set_verbose(decoder.video_ctx, decoder.verbose); - - int result; - - // In MKV mode, use two-pass approach: - // Pass 1: Extract all audio (video_pipe is NULL) - // Pass 2: Spawn FFmpeg and decode all video (audio file is complete) - if (decoder.ffmpeg_output) { - // Save starting position - long start_pos = ftell(decoder.input_fp); - - // Pass 1: Process all packets for audio only - printf("\n=== Pass 1: Extracting audio ===\n"); - while ((result = process_dt_packet(&decoder)) == 0) { - // Continue processing (only audio is written) - } - - // Close and flush audio file - fclose(decoder.output_audio_fp); - decoder.output_audio_fp = NULL; - - // Spawn FFmpeg with complete audio file - if (spawn_ffmpeg(&decoder, output_file) != 0) { - fprintf(stderr, "Error: Failed to spawn FFmpeg process\n"); - tav_video_free(decoder.video_ctx); - fclose(decoder.input_fp); - return 1; - } - - // Pass 2: Rewind and process all packets for video - printf("\n=== Pass 2: Decoding video ===\n"); - fseek(decoder.input_fp, start_pos, SEEK_SET); - decoder.packets_processed = 0; // Reset statistics - decoder.frames_decoded = 0; - decoder.bytes_read = 0; - - while ((result = process_dt_packet(&decoder)) == 0) { - // Continue processing (only video is written) - } - } else { - // Dump mode: Single pass for both audio and video - while ((result = process_dt_packet(&decoder)) == 0) { - // Continue processing - } - } - - // Cleanup - if (decoder.video_pipe) { - fclose(decoder.video_pipe); - waitpid(decoder.ffmpeg_pid, NULL, 0); - } - - if (decoder.video_ctx) { - tav_video_free(decoder.video_ctx); - } - - fclose(decoder.input_fp); - if (decoder.output_audio_fp) fclose(decoder.output_audio_fp); - if (decoder.output_video_fp) fclose(decoder.output_video_fp); - - // Clean up temporary audio file - if (decoder.audio_temp_file) { - unlink(decoder.audio_temp_file); - free(decoder.audio_temp_file); - } - - // Print statistics - printf("\n=== Decoding Complete ===\n"); - printf(" Packets processed: %lu\n", decoder.packets_processed); - printf(" Frames decoded: %lu (estimate)\n", decoder.frames_decoded); - printf(" Bytes read: %lu\n", decoder.bytes_read); - printf(" CRC errors: %lu\n", decoder.crc_errors); - printf(" Sync losses: %lu\n", decoder.sync_losses); - printf("=========================\n"); - - return 0; + return run_decoder(&dec); } diff --git a/video_encoder/src/encoder_tav_dt.c b/video_encoder/src/encoder_tav_dt.c new file mode 100644 index 0000000..07c64ce --- /dev/null +++ b/video_encoder/src/encoder_tav_dt.c @@ -0,0 +1,781 @@ +/** + * TAV-DT Encoder - Digital Tape Format Encoder + * + * Encodes video to TAV-DT format with forward error correction. + * + * TAV-DT is a packetised streaming format designed for digital tape/broadcast: + * - Fixed dimensions: 720x480 (NTSC) or 720x576 (PAL) + * - 16-frame GOPs with 9/7 spatial wavelet, Haar temporal + * - Mandatory TAD audio + * - LDPC rate 1/2 for headers, Reed-Solomon (255,223) for payloads + * + * Packet structure (revised 2025-12-11): + * - Main header: 28 bytes -> 56 bytes LDPC encoded + * (sync + fps + flags + reserved + size + crc + timecode + offset_to_video) + * - 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. + */ + +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tav_encoder_lib.h" +#include "encoder_tad.h" +#include "reed_solomon.h" +#include "ldpc.h" + +// ============================================================================= +// Constants +// ============================================================================= + +// TAV-DT sync patterns (big endian) +#define TAV_DT_SYNC_NTSC 0xE3537A1F +#define TAV_DT_SYNC_PAL 0xD193A745 + +// TAV-DT dimensions +#define DT_WIDTH 720 +#define DT_HEIGHT_NTSC 480 +#define DT_HEIGHT_PAL 576 + +// Fixed parameters +#define DT_GOP_SIZE 16 +#define DT_SPATIAL_LEVELS 4 +#define DT_TEMPORAL_LEVELS 2 + +// Header sizes (before LDPC encoding) +#define DT_MAIN_HEADER_SIZE 28 // sync(4) + fps(1) + flags(1) + reserved(2) + size(4) + crc(4) + timecode(8) + offset(4) +#define DT_TAD_HEADER_SIZE 10 // sample_count(2) + quant_bits(1) + compressed_size(4) + rs_block_count(3) +#define DT_TAV_HEADER_SIZE 8 // gop_size(1) + compressed_size(4) + rs_block_count(3) + +// Quality level to quantiser mapping +static const int QUALITY_Y[] = {79, 47, 23, 11, 5, 2}; +static const int QUALITY_CO[] = {123, 108, 91, 76, 59, 29}; +static const int QUALITY_CG[] = {148, 133, 113, 99, 76, 39}; + +// Audio samples per GOP (32kHz / framerate * gop_size) +#define AUDIO_SAMPLE_RATE 32000 + +// ============================================================================= +// CRC-32 +// ============================================================================= + +static uint32_t crc32_table[256]; +static int crc32_initialized = 0; + +static void init_crc32_table(void) { + if (crc32_initialized) return; + for (uint32_t i = 0; i < 256; i++) { + uint32_t crc = i; + for (int j = 0; j < 8; j++) { + if (crc & 1) { + crc = (crc >> 1) ^ 0xEDB88320; + } else { + crc >>= 1; + } + } + crc32_table[i] = crc; + } + crc32_initialized = 1; +} + +static uint32_t calculate_crc32(const uint8_t *data, size_t length) { + init_crc32_table(); + uint32_t crc = 0xFFFFFFFF; + for (size_t i = 0; i < length; i++) { + crc = (crc >> 8) ^ crc32_table[(crc ^ data[i]) & 0xFF]; + } + return crc ^ 0xFFFFFFFF; +} + +// ============================================================================= +// Encoder Context +// ============================================================================= + +typedef struct { + // Input/output + char *input_file; + char *output_file; + FILE *output_fp; + + // Video encoder context + tav_encoder_context_t *video_ctx; + + // Video parameters + int width; + int height; + int fps_num; + int fps_den; + int is_interlaced; + int is_pal; + int quality_index; + + // Frame buffers + uint8_t **gop_frames; + int gop_frame_count; + + // Audio buffer + float *audio_buffer; + size_t audio_buffer_samples; + size_t audio_buffer_capacity; + + // Timecode + uint64_t current_timecode_ns; + int frame_number; + + // Statistics + uint64_t packets_written; + uint64_t bytes_written; + uint64_t frames_encoded; + + // Options + int verbose; + int encode_limit; +} dt_encoder_t; + +// ============================================================================= +// Utility Functions +// ============================================================================= + +static void print_usage(const char *program) { + printf("TAV-DT Encoder - Digital Tape Format with FEC\n"); + printf("\nUsage: %s -i input.mp4 -o output.tavdt [options]\n\n", program); + printf("Required:\n"); + printf(" -i, --input FILE Input video file (via FFmpeg)\n"); + printf(" -o, --output FILE Output TAV-DT file\n"); + printf("\nOptions:\n"); + printf(" -q, --quality N Quality level 0-5 (default: 3)\n"); + printf(" --ntsc Force NTSC format (720x480, default)\n"); + printf(" --pal Force PAL format (720x576)\n"); + printf(" --interlaced Interlaced output\n"); + printf(" --encode-limit N Encode only N frames (for testing)\n"); + printf(" -v, --verbose Verbose output\n"); + printf(" -h, --help Show this help\n"); +} + +// ============================================================================= +// RS Block Encoding +// ============================================================================= + +static size_t encode_rs_blocks(const uint8_t *data, size_t data_len, uint8_t *output) { + size_t output_len = 0; + size_t remaining = data_len; + const uint8_t *src = data; + uint8_t *dst = output; + + while (remaining > 0) { + size_t block_data = (remaining > RS_DATA_SIZE) ? RS_DATA_SIZE : remaining; + size_t encoded_len = rs_encode(src, block_data, dst); + + // Pad to full block size for consistent block boundaries + if (encoded_len < RS_BLOCK_SIZE) { + memset(dst + encoded_len, 0, RS_BLOCK_SIZE - encoded_len); + } + + src += block_data; + dst += RS_BLOCK_SIZE; + output_len += RS_BLOCK_SIZE; + remaining -= block_data; + } + + return output_len; +} + +// ============================================================================= +// Packet Writing +// ============================================================================= + +static int write_packet(dt_encoder_t *enc, uint64_t timecode_ns, + const uint8_t *tad_data, size_t tad_size, + const uint8_t *tav_data, size_t tav_size, + int gop_size, uint16_t audio_samples, uint8_t audio_quant_bits) { + + // Calculate RS block counts + uint32_t tad_rs_blocks = (tad_size + RS_DATA_SIZE - 1) / RS_DATA_SIZE; + uint32_t tav_rs_blocks = (tav_size + RS_DATA_SIZE - 1) / RS_DATA_SIZE; + + // Calculate sizes + size_t tad_rs_size = tad_rs_blocks * RS_BLOCK_SIZE; + size_t tav_rs_size = tav_rs_blocks * RS_BLOCK_SIZE; + + size_t tad_subpacket_size = DT_TAD_HEADER_SIZE * 2 + tad_rs_size; // LDPC header + RS payload + size_t tav_subpacket_size = DT_TAV_HEADER_SIZE * 2 + tav_rs_size; // LDPC header + RS payload + + uint32_t offset_to_video = tad_subpacket_size; + uint32_t packet_size = tad_subpacket_size + tav_subpacket_size; + + // Build main header (28 bytes) + 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; + header[0] = (sync >> 24) & 0xFF; + header[1] = (sync >> 16) & 0xFF; + header[2] = (sync >> 8) & 0xFF; + header[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; + + // Flags byte + uint8_t flags = 0; + flags |= (enc->is_interlaced ? 0x01 : 0x00); + flags |= (enc->fps_den == 1001 ? 0x02 : 0x00); + flags |= (enc->quality_index & 0x0F) << 4; + header[5] = flags; + + // Reserved (2 bytes) + header[6] = 0; + header[7] = 0; + + // 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) + memcpy(header + 16, &timecode_ns, 8); + + // Offset to video (4 bytes) + memcpy(header + 24, &offset_to_video, 4); + + // LDPC encode main header + uint8_t ldpc_header[DT_MAIN_HEADER_SIZE * 2]; + ldpc_encode(header, DT_MAIN_HEADER_SIZE, ldpc_header); + + // Build TAD subpacket header (10 bytes) + uint8_t tad_header[DT_TAD_HEADER_SIZE]; + memcpy(tad_header + 0, &audio_samples, 2); + tad_header[2] = audio_quant_bits; + uint32_t tad_compressed_size = tad_size; + memcpy(tad_header + 3, &tad_compressed_size, 4); + // RS block count as uint24 + tad_header[7] = tad_rs_blocks & 0xFF; + tad_header[8] = (tad_rs_blocks >> 8) & 0xFF; + tad_header[9] = (tad_rs_blocks >> 16) & 0xFF; + + 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 (8 bytes) + uint8_t tav_header[DT_TAV_HEADER_SIZE]; + tav_header[0] = gop_size; + uint32_t tav_compressed_size = tav_size; + memcpy(tav_header + 1, &tav_compressed_size, 4); + // RS block count as uint24 + tav_header[5] = tav_rs_blocks & 0xFF; + tav_header[6] = (tav_rs_blocks >> 8) & 0xFF; + tav_header[7] = (tav_rs_blocks >> 16) & 0xFF; + + uint8_t ldpc_tav_header[DT_TAV_HEADER_SIZE * 2]; + ldpc_encode(tav_header, DT_TAV_HEADER_SIZE, ldpc_tav_header); + + // RS encode payloads + uint8_t *tad_rs_data = malloc(tad_rs_size); + uint8_t *tav_rs_data = malloc(tav_rs_size); + + encode_rs_blocks(tad_data, tad_size, tad_rs_data); + encode_rs_blocks(tav_data, tav_size, tav_rs_data); + + // 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); + + size_t total_written = DT_MAIN_HEADER_SIZE * 2 + tad_subpacket_size + tav_subpacket_size; + + if (enc->verbose) { + printf("GOP %lu: %d frames, header=%zu tad=%zu tav=%zu total=%zu bytes\n", + enc->packets_written + 1, gop_size, + (size_t)(DT_MAIN_HEADER_SIZE * 2), tad_subpacket_size, tav_subpacket_size, total_written); + } + + free(tad_rs_data); + free(tav_rs_data); + + enc->packets_written++; + enc->bytes_written += total_written; + + return 0; +} + +// ============================================================================= +// FFmpeg Integration +// ============================================================================= + +static FILE *spawn_ffmpeg_video(dt_encoder_t *enc, pid_t *pid) { + int pipefd[2]; + if (pipe(pipefd) < 0) { + fprintf(stderr, "Error: Failed to create pipe\n"); + return NULL; + } + + *pid = fork(); + if (*pid < 0) { + fprintf(stderr, "Error: Failed to fork\n"); + close(pipefd[0]); + close(pipefd[1]); + return NULL; + } + + if (*pid == 0) { + // Child process + close(pipefd[0]); + dup2(pipefd[1], STDOUT_FILENO); + close(pipefd[1]); + + char video_size[32]; + snprintf(video_size, sizeof(video_size), "%dx%d", enc->width, enc->height); + + // Use same filtergraph as reference TAV encoder + char vf[256]; + snprintf(vf, sizeof(vf), + "scale=%d:%d:force_original_aspect_ratio=increase,crop=%d:%d%s", + enc->width, enc->height, enc->width, enc->height, + enc->is_interlaced ? ",setfield=tff" : ""); + + execlp("ffmpeg", "ffmpeg", + "-hide_banner", + "-i", enc->input_file, + "-vf", vf, + "-pix_fmt", "rgb24", + "-f", "rawvideo", + "-an", + "-v", "warning", + "-", + (char*)NULL); + + fprintf(stderr, "Error: Failed to execute FFmpeg\n"); + exit(1); + } + + close(pipefd[1]); + return fdopen(pipefd[0], "rb"); +} + +static FILE *spawn_ffmpeg_audio(dt_encoder_t *enc, pid_t *pid) { + int pipefd[2]; + if (pipe(pipefd) < 0) { + fprintf(stderr, "Error: Failed to create pipe\n"); + return NULL; + } + + *pid = fork(); + if (*pid < 0) { + fprintf(stderr, "Error: Failed to fork\n"); + close(pipefd[0]); + close(pipefd[1]); + return NULL; + } + + if (*pid == 0) { + // Child process + close(pipefd[0]); + dup2(pipefd[1], STDOUT_FILENO); + close(pipefd[1]); + + execlp("ffmpeg", "ffmpeg", + "-i", enc->input_file, + "-f", "f32le", + "-acodec", "pcm_f32le", + "-ar", "32000", + "-ac", "2", + "-vn", + "-v", "warning", + "-", + (char*)NULL); + + fprintf(stderr, "Error: Failed to execute FFmpeg\n"); + exit(1); + } + + close(pipefd[1]); + return fdopen(pipefd[0], "rb"); +} + +// ============================================================================= +// Main Encoding Loop +// ============================================================================= + +static int run_encoder(dt_encoder_t *enc) { + // Open output file + enc->output_fp = fopen(enc->output_file, "wb"); + if (!enc->output_fp) { + fprintf(stderr, "Error: Cannot create output file: %s\n", enc->output_file); + return -1; + } + + // Set up video encoder + tav_encoder_params_t params; + tav_encoder_params_init(¶ms, enc->width, enc->height); + params.fps_num = enc->fps_num; + params.fps_den = enc->fps_den; + params.wavelet_type = 1; // CDF 9/7 + params.temporal_wavelet = 255; // Haar + params.decomp_levels = DT_SPATIAL_LEVELS; + params.temporal_levels = DT_TEMPORAL_LEVELS; + params.enable_temporal_dwt = 1; + params.gop_size = DT_GOP_SIZE; + params.quality_level = enc->quality_index; + params.quantiser_y = QUALITY_Y[enc->quality_index]; + params.quantiser_co = QUALITY_CO[enc->quality_index]; + params.quantiser_cg = QUALITY_CG[enc->quality_index]; + params.entropy_coder = 1; // EZBC + params.encoder_preset = 0x01; // Sports mode + params.monoblock = 1; // Force monoblock + params.verbose = enc->verbose; + + enc->video_ctx = tav_encoder_create(¶ms); + if (!enc->video_ctx) { + fprintf(stderr, "Error: Cannot create video encoder\n"); + fclose(enc->output_fp); + return -1; + } + + printf("Forced Monoblock mode (--monoblock)\n"); + + // Get actual parameters (may have been adjusted) + tav_encoder_get_params(enc->video_ctx, ¶ms); + + if (enc->verbose) { + printf("Auto-selected Haar temporal wavelet with sports mode (resolution: %dx%d = %d pixels, quantiser_y = %d)\n", + enc->width, enc->height, enc->width * enc->height, params.quantiser_y); + } + + // Spawn FFmpeg for video + pid_t video_pid; + FILE *video_pipe = spawn_ffmpeg_video(enc, &video_pid); + if (!video_pipe) { + tav_encoder_free(enc->video_ctx); + fclose(enc->output_fp); + return -1; + } + + // Spawn FFmpeg for audio + pid_t audio_pid; + FILE *audio_pipe = spawn_ffmpeg_audio(enc, &audio_pid); + if (!audio_pipe) { + fclose(video_pipe); + waitpid(video_pid, NULL, 0); + tav_encoder_free(enc->video_ctx); + fclose(enc->output_fp); + return -1; + } + + // Allocate frame buffers + size_t frame_size = enc->width * enc->height * 3; + enc->gop_frames = malloc(DT_GOP_SIZE * sizeof(uint8_t *)); + for (int i = 0; i < DT_GOP_SIZE; i++) { + enc->gop_frames[i] = malloc(frame_size); + } + + // Audio buffer (enough for one GOP worth of audio) + double gop_duration = (double)DT_GOP_SIZE * enc->fps_den / enc->fps_num; + size_t audio_samples_per_gop = (size_t)(AUDIO_SAMPLE_RATE * gop_duration) + 1024; + enc->audio_buffer = malloc(audio_samples_per_gop * 2 * sizeof(float)); + enc->audio_buffer_capacity = audio_samples_per_gop; + enc->audio_buffer_samples = 0; + + // TAD output buffer + size_t tad_buffer_size = audio_samples_per_gop * 2; // Conservative estimate + uint8_t *tad_output = malloc(tad_buffer_size); + + // Encoding loop + enc->frame_number = 0; + enc->gop_frame_count = 0; + enc->current_timecode_ns = 0; + + clock_t start_time = clock(); + + while (1) { + // Check encode limit + if (enc->encode_limit > 0 && enc->frame_number >= enc->encode_limit) { + break; + } + + // Read video frame + size_t bytes_read = fread(enc->gop_frames[enc->gop_frame_count], 1, frame_size, video_pipe); + if (bytes_read < frame_size) { + if (enc->verbose) { + fprintf(stderr, "Video read incomplete: got %zu/%zu bytes, frame %d, eof=%d, error=%d\n", + bytes_read, frame_size, enc->frame_number, feof(video_pipe), ferror(video_pipe)); + fprintf(stderr, "Audio buffer status: %zu/%zu samples\n", + enc->audio_buffer_samples, enc->audio_buffer_capacity); + // Try to read more audio to see if pipe is blocked + float test_audio[16]; + size_t test_read = fread(test_audio, sizeof(float), 16, audio_pipe); + fprintf(stderr, "Test audio read: %zu floats, eof=%d, error=%d\n", + test_read, feof(audio_pipe), ferror(audio_pipe)); + } + break; // End of video + } + + enc->gop_frame_count++; + enc->frame_number++; + + // Read corresponding audio + double frame_duration = (double)enc->fps_den / enc->fps_num; + size_t audio_samples_per_frame = (size_t)(AUDIO_SAMPLE_RATE * frame_duration); + size_t audio_bytes = audio_samples_per_frame * 2 * sizeof(float); + + // Always read audio to prevent pipe from filling up and blocking FFmpeg + // Expand buffer if needed + if (enc->audio_buffer_samples + audio_samples_per_frame > enc->audio_buffer_capacity) { + size_t new_capacity = enc->audio_buffer_capacity * 2; + float *new_buffer = realloc(enc->audio_buffer, new_capacity * 2 * sizeof(float)); + if (new_buffer) { + enc->audio_buffer = new_buffer; + enc->audio_buffer_capacity = new_capacity; + } + } + + size_t audio_read = fread(enc->audio_buffer + enc->audio_buffer_samples * 2, + 1, audio_bytes, audio_pipe); + enc->audio_buffer_samples += audio_read / (2 * sizeof(float)); + + // Encode GOP when full + if (enc->gop_frame_count >= DT_GOP_SIZE) { + // Encode video GOP + tav_encoder_packet_t *video_packet = NULL; + int frame_numbers[DT_GOP_SIZE]; + for (int i = 0; i < DT_GOP_SIZE; i++) { + frame_numbers[i] = enc->frame_number - DT_GOP_SIZE + i; + } + + int result = tav_encoder_encode_gop(enc->video_ctx, + (const uint8_t **)enc->gop_frames, + DT_GOP_SIZE, frame_numbers, &video_packet); + + if (result < 0 || !video_packet) { + fprintf(stderr, "Error: Video encoding failed\n"); + break; + } + + // Encode audio + int max_index = tad32_quality_to_max_index(enc->quality_index); + size_t tad_size = tad32_encode_chunk(enc->audio_buffer, enc->audio_buffer_samples, + max_index, 1.0f, tad_output); + + // Write packet + write_packet(enc, enc->current_timecode_ns, + tad_output, tad_size, + video_packet->data, video_packet->size, + DT_GOP_SIZE, (uint16_t)enc->audio_buffer_samples, max_index); + + // Update timecode + enc->current_timecode_ns += (uint64_t)(gop_duration * 1e9); + enc->frames_encoded += DT_GOP_SIZE; + + // Reset buffers + enc->gop_frame_count = 0; + enc->audio_buffer_samples = 0; + + tav_encoder_free_packet(video_packet); + + // Display progress (similar to reference TAV encoder) + clock_t now = clock(); + double elapsed = (double)(now - start_time) / CLOCKS_PER_SEC; + double fps = elapsed > 0 ? (double)enc->frame_number / elapsed : 0.0; + + // Calculate bitrate: output_size_bits / duration_seconds / 1000 + double duration = (double)enc->frame_number * enc->fps_den / enc->fps_num; + double bitrate = duration > 0 ? (ftell(enc->output_fp) * 8.0) / duration / 1000.0 : 0.0; + + long gop_count = enc->frame_number / DT_GOP_SIZE; + size_t total_kb = ftell(enc->output_fp) / 1024; + + printf("\rFrame %d | GOPs: %ld | %.1f fps | %.1f kbps | %zu KB ", + enc->frame_number, gop_count, fps, bitrate, total_kb); + fflush(stdout); + } + } + + // Handle partial final GOP + if (enc->gop_frame_count > 0) { + tav_encoder_packet_t *video_packet = NULL; + int *frame_numbers = malloc(enc->gop_frame_count * sizeof(int)); + for (int i = 0; i < enc->gop_frame_count; i++) { + frame_numbers[i] = enc->frame_number - enc->gop_frame_count + i; + } + + int result = tav_encoder_encode_gop(enc->video_ctx, + (const uint8_t **)enc->gop_frames, + enc->gop_frame_count, frame_numbers, &video_packet); + + if (result >= 0 && video_packet) { + int max_index = tad32_quality_to_max_index(enc->quality_index); + size_t tad_size = tad32_encode_chunk(enc->audio_buffer, enc->audio_buffer_samples, + max_index, 1.0f, tad_output); + + write_packet(enc, enc->current_timecode_ns, + tad_output, tad_size, + video_packet->data, video_packet->size, + enc->gop_frame_count, (uint16_t)enc->audio_buffer_samples, max_index); + + enc->frames_encoded += enc->gop_frame_count; + tav_encoder_free_packet(video_packet); + } + + free(frame_numbers); + } + + clock_t end_time = clock(); + double elapsed = (double)(end_time - start_time) / CLOCKS_PER_SEC; + + // Print statistics + printf("\nEncoding complete:\n"); + printf(" Frames: %lu\n", enc->frames_encoded); + printf(" GOPs: %lu\n", enc->packets_written); + printf(" Output size: %lu bytes (%.2f MB)\n", enc->bytes_written, enc->bytes_written / 1048576.0); + printf(" Encoding speed: %.1f fps\n", enc->frames_encoded / elapsed); + printf(" Bitrate: %.1f kbps\n", + enc->bytes_written * 8.0 / (enc->frames_encoded * enc->fps_den / enc->fps_num) / 1000.0); + + // Cleanup + free(tad_output); + free(enc->audio_buffer); + for (int i = 0; i < DT_GOP_SIZE; i++) { + free(enc->gop_frames[i]); + } + free(enc->gop_frames); + + fclose(video_pipe); + fclose(audio_pipe); + waitpid(video_pid, NULL, 0); + waitpid(audio_pid, NULL, 0); + + tav_encoder_free(enc->video_ctx); + fclose(enc->output_fp); + + return 0; +} + +// ============================================================================= +// Main +// ============================================================================= + +int main(int argc, char **argv) { + dt_encoder_t enc; + memset(&enc, 0, sizeof(enc)); + + // Defaults + enc.width = DT_WIDTH; + enc.height = DT_HEIGHT_NTSC; + enc.fps_num = 24; + enc.fps_den = 1; + enc.quality_index = 3; + enc.is_pal = 0; + enc.is_interlaced = 0; + + // Initialize FEC libraries + rs_init(); + ldpc_init(); + + static struct option long_options[] = { + {"input", required_argument, 0, 'i'}, + {"output", required_argument, 0, 'o'}, + {"quality", required_argument, 0, 'q'}, + {"ntsc", no_argument, 0, 'N'}, + {"pal", no_argument, 0, 'P'}, + {"interlaced", no_argument, 0, 'I'}, + {"encode-limit", required_argument, 0, 'L'}, + {"verbose", no_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + int opt; + while ((opt = getopt_long(argc, argv, "i:o:q:vhNPI", long_options, NULL)) != -1) { + switch (opt) { + case 'i': + enc.input_file = optarg; + break; + case 'o': + enc.output_file = optarg; + break; + case 'q': + enc.quality_index = atoi(optarg); + if (enc.quality_index < 0) enc.quality_index = 0; + if (enc.quality_index > 5) enc.quality_index = 5; + break; + case 'N': + enc.is_pal = 0; + enc.height = DT_HEIGHT_NTSC; + break; + case 'P': + enc.is_pal = 1; + enc.height = DT_HEIGHT_PAL; + break; + case 'I': + enc.is_interlaced = 1; + break; + case 'L': + enc.encode_limit = atoi(optarg); + break; + case 'v': + enc.verbose = 1; + break; + case 'h': + print_usage(argv[0]); + return 0; + default: + print_usage(argv[0]); + return 1; + } + } + + if (!enc.input_file || !enc.output_file) { + fprintf(stderr, "Error: Input and output files are required\n"); + print_usage(argv[0]); + return 1; + } + + // Probe input file for framerate + char probe_cmd[4096]; + snprintf(probe_cmd, sizeof(probe_cmd), + "ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate -of default=nw=1:nk=1 '%s'", + enc.input_file); + + FILE *probe = popen(probe_cmd, "r"); + if (probe) { + char line[256]; + if (fgets(line, sizeof(line), probe)) { + if (sscanf(line, "%d/%d", &enc.fps_num, &enc.fps_den) != 2) { + enc.fps_num = 24; + enc.fps_den = 1; + } + } + pclose(probe); + } + + printf("\nTAV-DT Encoder (Revised Spec 2025-12-11)\n"); + printf(" Format: %s %s\n", enc.is_pal ? "PAL" : "NTSC", + enc.is_interlaced ? "interlaced" : "progressive"); + printf(" Resolution: %dx%d (internal: %dx%d)\n", enc.width, enc.height, + enc.width, enc.is_interlaced ? enc.height / 2 : enc.height); + printf(" Framerate: %d/%d\n", enc.fps_num, enc.fps_den); + printf(" Quality: %d\n", enc.quality_index); + printf(" GOP size: %d\n", DT_GOP_SIZE); + printf(" Header sizes: main=%dB tad=%dB tav=%dB (after LDPC)\n", + DT_MAIN_HEADER_SIZE * 2, DT_TAD_HEADER_SIZE * 2, DT_TAV_HEADER_SIZE * 2); + + return run_encoder(&enc); +}