mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 11:51:49 +09:00
Working TAV-DT encoder/decoder
This commit is contained in:
@@ -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")
|
ZSTD_LIBS = $(shell pkg-config --libs libzstd 2>/dev/null || echo "-lzstd")
|
||||||
LIBS = -lm $(ZSTD_LIBS)
|
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
|
# Library Object Files
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -39,44 +35,47 @@ LIBTADENC_OBJ = lib/libtadenc/encoder_tad.o
|
|||||||
# libtaddec - TAD decoder library
|
# libtaddec - TAD decoder library
|
||||||
LIBTADDEC_OBJ = lib/libtaddec/decoder_tad.o
|
LIBTADDEC_OBJ = lib/libtaddec/decoder_tad.o
|
||||||
|
|
||||||
|
# libfec - Forward Error Correction library (LDPC + Reed-Solomon)
|
||||||
|
LIBFEC_OBJ = lib/libfec/ldpc.o lib/libfec/reed_solomon.o
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Targets
|
# Targets
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
# Source files and 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
|
TAD_TARGETS = encoder_tad decoder_tad
|
||||||
LIBRARIES = lib/libtavenc.a lib/libtavdec.a lib/libtadenc.a lib/libtaddec.a
|
DT_TARGETS = encoder_tav_dt decoder_tav_dt
|
||||||
TEST_TARGETS = test_mesh_warp test_mesh_roundtrip
|
|
||||||
|
|
||||||
# Build all encoders (default)
|
# Build all encoders (default)
|
||||||
all: $(TARGETS)
|
all: clean $(TARGETS)
|
||||||
|
|
||||||
# Build all libraries
|
# Build all libraries
|
||||||
libs: $(LIBRARIES)
|
libs: $(LIBRARIES)
|
||||||
|
|
||||||
# Build main encoder
|
# Reference encoder using libtavenc (replaces old monolithic encoder)
|
||||||
tev: encoder_tev.c
|
encoder_tav_ref: src/encoder_tav.c lib/libtavenc.a lib/libtadenc.a
|
||||||
rm -f encoder_tev
|
rm -f encoder_tav_ref
|
||||||
$(CC) $(CFLAGS) $(ZSTD_CFLAGS) -o encoder_tev $< $(LIBS)
|
$(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
|
# Reference decoder using libtavdec (replaces old monolithic decoder)
|
||||||
rm -f encoder_tav encoder_tav.o encoder_tad.o encoder_tav_opencv.o
|
decoder_tav_ref: src/decoder_tav.c lib/libtavdec.a lib/libtaddec.a
|
||||||
$(CC) $(CFLAGS) $(ZSTD_CFLAGS) -c src/encoder_tav.c -o encoder_tav.o
|
rm -f decoder_tav_ref
|
||||||
$(CC) $(CFLAGS) $(ZSTD_CFLAGS) -c lib/libtadenc/encoder_tad.c -o encoder_tad.o
|
$(CC) $(CFLAGS) $(ZSTD_CFLAGS) -Iinclude -o decoder_tav_ref src/decoder_tav.c lib/libtavdec.a lib/libtaddec.a $(LIBS)
|
||||||
$(CXX) $(CXXFLAGS) $(OPENCV_CFLAGS) $(ZSTD_CFLAGS) -c encoder_tav_opencv.cpp -o encoder_tav_opencv.o
|
@echo ""
|
||||||
$(CXX) $(DBGFLAGS) -o encoder_tav encoder_tav.o encoder_tad.o encoder_tav_opencv.o $(LIBS) $(OPENCV_LIBS)
|
@echo "Reference decoder built: decoder_tav_ref"
|
||||||
|
@echo "This is the official reference implementation with all features"
|
||||||
|
|
||||||
tav_inspector: tav_inspector.c
|
tav_inspector: tav_inspector.c
|
||||||
rm -f tav_inspector
|
rm -f tav_inspector
|
||||||
$(CC) $(CFLAGS) $(ZSTD_CFLAGS) -o tav_inspector $< $(LIBS)
|
$(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
|
tav: $(TAV_TARGETS)
|
||||||
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)
|
|
||||||
|
|
||||||
# Build TAD (Terrarum Advanced Audio) tools
|
# Build TAD (Terrarum Advanced Audio) tools
|
||||||
encoder_tad: src/encoder_tad_standalone.c lib/libtadenc/encoder_tad.c include/encoder_tad.h
|
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
|
lib/libtaddec/%.o: lib/libtaddec/%.c
|
||||||
$(CC) $(CFLAGS) $(ZSTD_CFLAGS) -DTAD_DECODER_LIB -c $< -o $@
|
$(CC) $(CFLAGS) $(ZSTD_CFLAGS) -DTAD_DECODER_LIB -c $< -o $@
|
||||||
|
|
||||||
|
lib/libfec/%.o: lib/libfec/%.c
|
||||||
|
$(CC) $(CFLAGS) -Ilib/libfec -c $< -o $@
|
||||||
|
|
||||||
# Build static libraries
|
# Build static libraries
|
||||||
lib/libtavenc.a: $(LIBTAVENC_OBJ)
|
lib/libtavenc.a: $(LIBTAVENC_OBJ)
|
||||||
ar rcs $@ $^
|
ar rcs $@ $^
|
||||||
@@ -122,62 +124,66 @@ lib/libtadenc.a: $(LIBTADENC_OBJ)
|
|||||||
lib/libtaddec.a: $(LIBTADDEC_OBJ)
|
lib/libtaddec.a: $(LIBTADDEC_OBJ)
|
||||||
ar rcs $@ $^
|
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
|
# TAV-DT encoder with FEC (multithreaded)
|
||||||
rm -f test_mesh_roundtrip test_mesh_roundtrip.o
|
encoder_tav_dt: src/encoder_tav_dt.c lib/libtavenc.a lib/libtadenc.a lib/libfec.a
|
||||||
$(CXX) $(CXXFLAGS) $(OPENCV_CFLAGS) -o test_mesh_roundtrip test_mesh_roundtrip.cpp encoder_tav_opencv.cpp $(OPENCV_LIBS)
|
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
|
# TAV-DT decoder with FEC (multithreaded)
|
||||||
rm -f test_interpolation_comparison test_interpolation_comparison.o
|
decoder_tav_dt: src/decoder_tav_dt.c lib/libtavdec.a lib/libtaddec.a lib/libfec.a
|
||||||
$(CXX) $(CXXFLAGS) $(OPENCV_CFLAGS) -o test_interpolation_comparison test_interpolation_comparison.cpp encoder_tav_opencv.cpp $(OPENCV_LIBS)
|
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
|
# Build all TAV-DT tools
|
||||||
rm -f test_bidirectional_prediction test_bidirectional_prediction.o
|
tav_dt: $(DT_TARGETS)
|
||||||
$(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 with debug symbols
|
# Build with debug symbols
|
||||||
debug: CFLAGS += -g -DDEBUG -fsanitize=address -fno-omit-frame-pointer
|
debug: CFLAGS += -g -DDEBUG -fsanitize=address -fno-omit-frame-pointer
|
||||||
debug: DBGFLAGS += -fsanitize=address -fno-omit-frame-pointer
|
debug: DBGFLAGS += -fsanitize=address -fno-omit-frame-pointer
|
||||||
debug: $(TARGETS)
|
debug: clean $(TARGETS)
|
||||||
|
|
||||||
# Clean build artifacts
|
# Clean build artifacts
|
||||||
clean:
|
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 (copy to PATH)
|
||||||
install: $(TARGETS) $(TAD_TARGETS)
|
install: $(TARGETS)
|
||||||
cp encoder_tev $(PREFIX)/bin/
|
cp encoder_tav_ref $(PREFIX)/bin/
|
||||||
cp encoder_tav $(PREFIX)/bin/
|
cp decoder_tav_ref $(PREFIX)/bin/
|
||||||
cp decoder_tav $(PREFIX)/bin/
|
|
||||||
cp encoder_tad $(PREFIX)/bin/
|
cp encoder_tad $(PREFIX)/bin/
|
||||||
cp decoder_tad $(PREFIX)/bin/
|
cp decoder_tad $(PREFIX)/bin/
|
||||||
|
cp encoder_tav_dt $(PREFIX)/bin/
|
||||||
cp decoder_tav_dt $(PREFIX)/bin/
|
cp decoder_tav_dt $(PREFIX)/bin/
|
||||||
|
cp tav_inspector $(PREFIX)/bin/
|
||||||
|
|
||||||
# Check for required dependencies
|
# Check for required dependencies
|
||||||
check-deps:
|
check-deps:
|
||||||
@echo "Checking dependencies..."
|
@echo "Checking dependencies..."
|
||||||
@pkg-config --exists libzstd || (echo "Error: libzstd-dev not found. Install with: sudo apt install libzstd-dev" && exit 1)
|
@pkg-config --exists libzstd || (echo "Error: libzstd-dev not found. Install 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."
|
@echo "All dependencies found."
|
||||||
|
|
||||||
# Help
|
# Help
|
||||||
help:
|
help:
|
||||||
@echo "TSVM Enhanced Video (TEV) and Audio (TAD) Encoders"
|
@echo "TSVM Advanced Video (TAV) and Audio (TAD) Encoders"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Targets:"
|
@echo "Targets:"
|
||||||
@echo " all - Build video encoders (default)"
|
@echo " all - Build video encoders (default)"
|
||||||
@echo " libs - Build all codec libraries (.a files)"
|
@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 - 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 " tad - Build all TAD audio tools (encoder, decoder)"
|
||||||
@echo " encoder_tad - Build TAD audio encoder"
|
@echo " encoder_tad - Build TAD audio encoder"
|
||||||
@echo " decoder_tad - Build TAD audio decoder"
|
@echo " decoder_tad - Build TAD audio decoder"
|
||||||
@@ -193,30 +199,14 @@ help:
|
|||||||
@echo " lib/libtavdec.a - TAV decoder library"
|
@echo " lib/libtavdec.a - TAV decoder library"
|
||||||
@echo " lib/libtadenc.a - TAD encoder library"
|
@echo " lib/libtadenc.a - TAD encoder library"
|
||||||
@echo " lib/libtaddec.a - TAD decoder library"
|
@echo " lib/libtaddec.a - TAD decoder library"
|
||||||
|
@echo " lib/libfec.a - Forward Error Correction library (LDPC + RS)"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Usage:"
|
@echo "Usage:"
|
||||||
@echo " make # Build video encoders"
|
@echo " make # Build video encoders"
|
||||||
@echo " make libs # Build all libraries"
|
@echo " make libs # Build all libraries"
|
||||||
@echo " make tev # Build TEV encoder"
|
|
||||||
@echo " make tav # Build TAV 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 " make tad # Build all TAD audio tools"
|
||||||
@echo " sudo make install # Install all encoders"
|
@echo " sudo make install # Install all encoders"
|
||||||
|
|
||||||
.PHONY: all libs clean install check-deps help debug tad tests
|
.PHONY: all libs clean install check-deps help debug tad tav_dt 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"
|
|
||||||
|
|
||||||
|
|||||||
309
video_encoder/lib/libfec/ldpc.c
Normal file
309
video_encoder/lib/libfec/ldpc.c
Normal file
@@ -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 <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
68
video_encoder/lib/libfec/ldpc.h
Normal file
68
video_encoder/lib/libfec/ldpc.h
Normal file
@@ -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 <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
// 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
|
||||||
417
video_encoder/lib/libfec/reed_solomon.c
Normal file
417
video_encoder/lib/libfec/reed_solomon.c
Normal file
@@ -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 <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
82
video_encoder/lib/libfec/reed_solomon.h
Normal file
82
video_encoder/lib/libfec/reed_solomon.h
Normal file
@@ -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 <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
// 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
|
||||||
@@ -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--) {
|
for (int level = temporal_levels - 1; level >= 0; level--) {
|
||||||
const int level_frames = temporal_lengths[level];
|
const int level_frames = temporal_lengths[level];
|
||||||
if (level_frames >= 2) {
|
if (level_frames >= 2) {
|
||||||
if (temporal_wavelet == 0) {
|
if (temporal_wavelet == 255) {
|
||||||
dwt_haar_inverse_1d(temporal_line, level_frames);
|
dwt_haar_inverse_1d(temporal_line, level_frames);
|
||||||
} else {
|
} else {
|
||||||
dwt_53_inverse_1d(temporal_line, level_frames);
|
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--) {
|
for (int level = temporal_levels - 1; level >= 0; level--) {
|
||||||
const int level_frames = temporal_lengths[level];
|
const int level_frames = temporal_lengths[level];
|
||||||
if (level_frames >= 2) {
|
if (level_frames >= 2) {
|
||||||
if (temporal_wavelet == 0) {
|
if (temporal_wavelet == 255) {
|
||||||
dwt_haar_inverse_1d(temporal_line, level_frames);
|
dwt_haar_inverse_1d(temporal_line, level_frames);
|
||||||
} else {
|
} else {
|
||||||
dwt_53_inverse_1d(temporal_line, level_frames);
|
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--) {
|
for (int level = temporal_levels - 1; level >= 0; level--) {
|
||||||
const int level_frames = temporal_lengths[level];
|
const int level_frames = temporal_lengths[level];
|
||||||
if (level_frames >= 2) {
|
if (level_frames >= 2) {
|
||||||
if (temporal_wavelet == 0) {
|
if (temporal_wavelet == 255) {
|
||||||
dwt_haar_inverse_1d(temporal_line, level_frames);
|
dwt_haar_inverse_1d(temporal_line, level_frames);
|
||||||
} else {
|
} else {
|
||||||
dwt_53_inverse_1d(temporal_line, level_frames);
|
dwt_53_inverse_1d(temporal_line, level_frames);
|
||||||
|
|||||||
@@ -573,7 +573,7 @@ static int init_decoder_threads(decoder_context_t *ctx) {
|
|||||||
.decomp_levels = ctx->header.decomp_levels,
|
.decomp_levels = ctx->header.decomp_levels,
|
||||||
.temporal_levels = 2,
|
.temporal_levels = 2,
|
||||||
.wavelet_filter = ctx->header.wavelet_filter,
|
.wavelet_filter = ctx->header.wavelet_filter,
|
||||||
.temporal_wavelet = 0,
|
.temporal_wavelet = 255,
|
||||||
.entropy_coder = ctx->header.entropy_coder,
|
.entropy_coder = ctx->header.entropy_coder,
|
||||||
.channel_layout = ctx->header.channel_layout,
|
.channel_layout = ctx->header.channel_layout,
|
||||||
.perceptual_tuning = ctx->perceptual_mode,
|
.perceptual_tuning = ctx->perceptual_mode,
|
||||||
@@ -1944,7 +1944,7 @@ int main(int argc, char *argv[]) {
|
|||||||
.decomp_levels = ctx.header.decomp_levels,
|
.decomp_levels = ctx.header.decomp_levels,
|
||||||
.temporal_levels = 2, // Default
|
.temporal_levels = 2, // Default
|
||||||
.wavelet_filter = ctx.header.wavelet_filter,
|
.wavelet_filter = ctx.header.wavelet_filter,
|
||||||
.temporal_wavelet = 0, // Haar
|
.temporal_wavelet = 255, // Haar
|
||||||
.entropy_coder = ctx.header.entropy_coder,
|
.entropy_coder = ctx.header.entropy_coder,
|
||||||
.channel_layout = ctx.header.channel_layout,
|
.channel_layout = ctx.header.channel_layout,
|
||||||
.perceptual_tuning = ctx.perceptual_mode,
|
.perceptual_tuning = ctx.perceptual_mode,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
781
video_encoder/src/encoder_tav_dt.c
Normal file
781
video_encoder/src/encoder_tav_dt.c
Normal file
@@ -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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user