mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-09 14:44:05 +09:00
Working TAV-DT encoder/decoder
This commit is contained in:
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--) {
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user