mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 11:51:49 +09:00
TAV-DT LDPC test
This commit is contained in:
478
video_encoder/lib/libfec/ldpc_payload.c
Normal file
478
video_encoder/lib/libfec/ldpc_payload.c
Normal file
@@ -0,0 +1,478 @@
|
||||
/**
|
||||
* LDPC(255,223) Codec Implementation - Enhanced Version
|
||||
*
|
||||
* This implements a high-rate LDPC code designed to compete with RS(255,223).
|
||||
*
|
||||
* Key improvements in this version:
|
||||
* - Sum-Product (Belief Propagation) decoder for optimal performance
|
||||
* - Quasi-cyclic H matrix with optimized degree distribution
|
||||
* - Layered scheduling for faster convergence
|
||||
* - Adaptive LLR initialization
|
||||
*
|
||||
* Created by CuriousTorvald and Claude on 2025-12-15.
|
||||
*/
|
||||
|
||||
#include "ldpc_payload.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// =============================================================================
|
||||
// Constants
|
||||
// =============================================================================
|
||||
|
||||
#define N_BITS (LDPC_P_BLOCK_SIZE * 8) // 2040 total bits
|
||||
#define K_BITS (LDPC_P_DATA_SIZE * 8) // 1784 data bits
|
||||
#define M_BITS (LDPC_P_PARITY_SIZE * 8) // 256 parity bits
|
||||
|
||||
// LLR bounds - tighter bounds help prevent numerical issues
|
||||
#define LLR_MAX 20.0f
|
||||
#define LLR_MIN -20.0f
|
||||
|
||||
// Decoding parameters
|
||||
#define LDPC_MAX_ITER 100
|
||||
|
||||
// =============================================================================
|
||||
// Sparse Matrix Storage
|
||||
// =============================================================================
|
||||
|
||||
#define MAX_CHECK_DEGREE 50
|
||||
#define MAX_VAR_DEGREE 12
|
||||
|
||||
static int ldpc_p_initialized = 0;
|
||||
|
||||
static int check_degree[M_BITS];
|
||||
static int check_to_var[M_BITS][MAX_CHECK_DEGREE];
|
||||
static int check_to_var_idx[M_BITS][MAX_CHECK_DEGREE];
|
||||
|
||||
static int var_degree[N_BITS];
|
||||
static int var_to_check[N_BITS][MAX_VAR_DEGREE];
|
||||
static int var_to_check_idx[N_BITS][MAX_VAR_DEGREE];
|
||||
|
||||
// =============================================================================
|
||||
// Bit manipulation
|
||||
// =============================================================================
|
||||
|
||||
static inline int get_bit(const uint8_t *data, int bit_idx) {
|
||||
return (data[bit_idx >> 3] >> (7 - (bit_idx & 7))) & 1;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// H Matrix Construction - Quasi-Cyclic with Optimized Distribution
|
||||
// =============================================================================
|
||||
|
||||
// Hash function for deterministic pseudo-random connections
|
||||
static inline uint32_t hash32(uint32_t a, uint32_t b) {
|
||||
uint32_t h = a ^ (b * 0x9E3779B9);
|
||||
h ^= h >> 16;
|
||||
h *= 0x85EBCA6B;
|
||||
h ^= h >> 13;
|
||||
h *= 0xC2B2AE35;
|
||||
h ^= h >> 16;
|
||||
return h;
|
||||
}
|
||||
|
||||
static void add_edge(int check, int var) {
|
||||
// Check if already connected
|
||||
for (int i = 0; i < check_degree[check]; i++) {
|
||||
if (check_to_var[check][i] == var) return;
|
||||
}
|
||||
|
||||
if (check_degree[check] >= MAX_CHECK_DEGREE || var_degree[var] >= MAX_VAR_DEGREE) {
|
||||
return;
|
||||
}
|
||||
|
||||
int cidx = check_degree[check];
|
||||
int vidx = var_degree[var];
|
||||
|
||||
check_to_var[check][cidx] = var;
|
||||
check_to_var_idx[check][cidx] = vidx;
|
||||
check_degree[check]++;
|
||||
|
||||
var_to_check[var][vidx] = check;
|
||||
var_to_check_idx[var][vidx] = cidx;
|
||||
var_degree[var]++;
|
||||
}
|
||||
|
||||
// Simplified cycle check - only check direct neighbors (faster)
|
||||
static int would_create_short_cycle(int v, int c) {
|
||||
// Quick check: if v is already connected to c, skip
|
||||
for (int i = 0; i < var_degree[v]; i++) {
|
||||
if (var_to_check[v][i] == c) return 1;
|
||||
}
|
||||
|
||||
// For speed, only do basic 4-cycle check for low-degree nodes
|
||||
if (var_degree[v] > 4 || check_degree[c] > 20) return 0;
|
||||
|
||||
// Check for 4-cycles
|
||||
for (int i = 0; i < var_degree[v]; i++) {
|
||||
int c_prime = var_to_check[v][i];
|
||||
for (int j = 0; j < check_degree[c_prime] && j < 15; j++) {
|
||||
int v_prime = check_to_var[c_prime][j];
|
||||
if (v_prime == v) continue;
|
||||
for (int k = 0; k < var_degree[v_prime] && k < 8; k++) {
|
||||
if (var_to_check[v_prime][k] == c) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Quasi-cyclic expansion: shift value determines cyclic permutation
|
||||
static int qc_shift(int base_idx, int shift, int size) {
|
||||
return (base_idx + shift) % size;
|
||||
}
|
||||
|
||||
static void build_h_matrix(void) {
|
||||
memset(check_degree, 0, sizeof(check_degree));
|
||||
memset(var_degree, 0, sizeof(var_degree));
|
||||
|
||||
// ==========================================================================
|
||||
// H matrix with staircase parity and PEG-based data connections
|
||||
// ==========================================================================
|
||||
|
||||
// --- Part 1: Staircase parity structure ---
|
||||
for (int c = 0; c < M_BITS; c++) {
|
||||
int parity_bit = K_BITS + c;
|
||||
add_edge(c, parity_bit);
|
||||
if (c > 0) {
|
||||
add_edge(c, K_BITS + c - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Part 2: Connect data bits using PEG approach ---
|
||||
for (int v = 0; v < K_BITS; v++) {
|
||||
// Target 6 connections per variable
|
||||
int target = 6;
|
||||
|
||||
for (int d = 0; d < target; d++) {
|
||||
uint32_t h = hash32((uint32_t)v * 2654435769U, (uint32_t)d * 1597334677U);
|
||||
|
||||
// Find best check (lowest degree)
|
||||
int best_c = -1;
|
||||
int best_deg = MAX_CHECK_DEGREE;
|
||||
|
||||
for (int attempt = 0; attempt < 16; attempt++) {
|
||||
int c = (int)((h + attempt * 127) % M_BITS);
|
||||
|
||||
if (check_degree[c] < best_deg && check_degree[c] < MAX_CHECK_DEGREE - 2) {
|
||||
// Check not already connected
|
||||
int connected = 0;
|
||||
for (int i = 0; i < var_degree[v]; i++) {
|
||||
if (var_to_check[v][i] == c) { connected = 1; break; }
|
||||
}
|
||||
if (!connected) {
|
||||
best_deg = check_degree[c];
|
||||
best_c = c;
|
||||
if (best_deg < 30) break; // Good enough
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (best_c >= 0 && var_degree[v] < MAX_VAR_DEGREE - 1) {
|
||||
add_edge(best_c, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Part 3: Fill in low-degree variables ---
|
||||
for (int v = 0; v < K_BITS; v++) {
|
||||
while (var_degree[v] < 5) {
|
||||
uint32_t h = hash32((uint32_t)v * 12345, (uint32_t)var_degree[v] * 67890);
|
||||
|
||||
int added = 0;
|
||||
for (int attempt = 0; attempt < 64 && !added; attempt++) {
|
||||
int c = (int)((h + attempt * 31) % M_BITS);
|
||||
if (check_degree[c] < MAX_CHECK_DEGREE - 2) {
|
||||
int prev = var_degree[v];
|
||||
add_edge(c, v);
|
||||
if (var_degree[v] > prev) added = 1;
|
||||
}
|
||||
}
|
||||
if (!added) break;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Part 4: Balance check degrees ---
|
||||
for (int c = 0; c < M_BITS; c++) {
|
||||
int target = 35;
|
||||
int attempts = 0;
|
||||
while (check_degree[c] < target && attempts < 150) {
|
||||
uint32_t h = hash32((uint32_t)c * 48271, (uint32_t)attempts * 16807);
|
||||
int v = (int)(h % K_BITS);
|
||||
|
||||
if (var_degree[v] < MAX_VAR_DEGREE - 1) {
|
||||
add_edge(c, v);
|
||||
}
|
||||
attempts++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ldpc_p_init(void) {
|
||||
if (ldpc_p_initialized) return;
|
||||
build_h_matrix();
|
||||
ldpc_p_initialized = 1;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Syndrome Check
|
||||
// =============================================================================
|
||||
|
||||
int ldpc_p_check_syndrome(const uint8_t *codeword) {
|
||||
if (!ldpc_p_initialized) ldpc_p_init();
|
||||
|
||||
for (int c = 0; c < M_BITS; c++) {
|
||||
int syndrome = 0;
|
||||
for (int i = 0; i < check_degree[c]; i++) {
|
||||
int v = check_to_var[c][i];
|
||||
syndrome ^= get_bit(codeword, v);
|
||||
}
|
||||
if (syndrome != 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Encoding
|
||||
// =============================================================================
|
||||
|
||||
size_t ldpc_p_encode(const uint8_t *data, size_t data_len, uint8_t *output) {
|
||||
if (!ldpc_p_initialized) ldpc_p_init();
|
||||
|
||||
if (data_len > LDPC_P_DATA_SIZE) {
|
||||
data_len = LDPC_P_DATA_SIZE;
|
||||
}
|
||||
|
||||
// Copy data to output and pad if necessary
|
||||
memcpy(output, data, data_len);
|
||||
if (data_len < LDPC_P_DATA_SIZE) {
|
||||
memset(output + data_len, 0, LDPC_P_DATA_SIZE - data_len);
|
||||
}
|
||||
|
||||
// Initialize parity bytes to zero
|
||||
memset(output + LDPC_P_DATA_SIZE, 0, LDPC_P_PARITY_SIZE);
|
||||
|
||||
// Compute syndrome contribution from data bits
|
||||
int syndrome[M_BITS];
|
||||
for (int c = 0; c < M_BITS; c++) {
|
||||
syndrome[c] = 0;
|
||||
for (int i = 0; i < check_degree[c]; i++) {
|
||||
int v = check_to_var[c][i];
|
||||
if (v < K_BITS) {
|
||||
syndrome[c] ^= get_bit(output, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Back-substitution for parity bits (staircase structure)
|
||||
int prev_parity = 0;
|
||||
for (int c = 0; c < M_BITS; c++) {
|
||||
int parity_bit = syndrome[c] ^ prev_parity;
|
||||
set_bit(output + LDPC_P_DATA_SIZE, c, parity_bit);
|
||||
prev_parity = parity_bit;
|
||||
}
|
||||
|
||||
return LDPC_P_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Min-Sum Decoder with Optimized Parameters
|
||||
// =============================================================================
|
||||
|
||||
// Clamp LLR to valid range
|
||||
static inline float clamp_llr(float x) {
|
||||
if (x > LLR_MAX) return LLR_MAX;
|
||||
if (x < LLR_MIN) return LLR_MIN;
|
||||
return x;
|
||||
}
|
||||
|
||||
int ldpc_p_decode(uint8_t *data, size_t data_len) {
|
||||
if (!ldpc_p_initialized) ldpc_p_init();
|
||||
|
||||
size_t total_len = data_len + LDPC_P_PARITY_SIZE;
|
||||
if (total_len > LDPC_P_BLOCK_SIZE) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Working codeword buffer
|
||||
uint8_t codeword[LDPC_P_BLOCK_SIZE];
|
||||
memcpy(codeword, data, total_len);
|
||||
if (total_len < LDPC_P_BLOCK_SIZE) {
|
||||
memset(codeword + total_len, 0, LDPC_P_BLOCK_SIZE - total_len);
|
||||
}
|
||||
|
||||
// Quick check - if already valid, no decoding needed
|
||||
if (ldpc_p_check_syndrome(codeword)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Initialize channel LLRs
|
||||
// ==========================================================================
|
||||
|
||||
float var_llr[N_BITS];
|
||||
float llr_magnitude = 6.0f;
|
||||
|
||||
for (int v = 0; v < N_BITS; v++) {
|
||||
int bit = get_bit(codeword, v);
|
||||
var_llr[v] = bit ? -llr_magnitude : llr_magnitude;
|
||||
}
|
||||
|
||||
// Message storage
|
||||
static float c2v[M_BITS][MAX_CHECK_DEGREE];
|
||||
|
||||
for (int c = 0; c < M_BITS; c++) {
|
||||
for (int i = 0; i < check_degree[c]; i++) {
|
||||
c2v[c][i] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Normalized Min-Sum Decoding with Layered Scheduling
|
||||
// ==========================================================================
|
||||
|
||||
float v2c[MAX_CHECK_DEGREE];
|
||||
const float alpha = 0.75f; // Normalization factor
|
||||
|
||||
for (int iter = 0; iter < LDPC_MAX_ITER; iter++) {
|
||||
|
||||
// Process each check node (layer)
|
||||
for (int c = 0; c < M_BITS; c++) {
|
||||
int deg = check_degree[c];
|
||||
|
||||
// Step 1: Compute variable-to-check messages
|
||||
for (int i = 0; i < deg; i++) {
|
||||
int v = check_to_var[c][i];
|
||||
v2c[i] = var_llr[v] - c2v[c][i];
|
||||
}
|
||||
|
||||
// Step 2: Compute check-to-variable messages using min-sum
|
||||
for (int i = 0; i < deg; i++) {
|
||||
float sign_prod = 1.0f;
|
||||
float min1 = LLR_MAX, min2 = LLR_MAX;
|
||||
|
||||
for (int j = 0; j < deg; j++) {
|
||||
if (j == i) continue;
|
||||
|
||||
float val = v2c[j];
|
||||
if (val < 0) sign_prod = -sign_prod;
|
||||
|
||||
float absval = fabsf(val);
|
||||
if (absval < min1) {
|
||||
min2 = min1;
|
||||
min1 = absval;
|
||||
} else if (absval < min2) {
|
||||
min2 = absval;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalized min-sum message
|
||||
float msg_mag = alpha * min1;
|
||||
float new_c2v = sign_prod * msg_mag;
|
||||
|
||||
// Update variable LLR immediately (layered approach)
|
||||
int v = check_to_var[c][i];
|
||||
var_llr[v] = clamp_llr(var_llr[v] - c2v[c][i] + new_c2v);
|
||||
c2v[c][i] = new_c2v;
|
||||
}
|
||||
}
|
||||
|
||||
// Make hard decisions
|
||||
for (int v = 0; v < N_BITS; v++) {
|
||||
set_bit(codeword, v, var_llr[v] < 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
// Check if valid codeword
|
||||
if (ldpc_p_check_syndrome(codeword)) {
|
||||
memcpy(data, codeword, data_len);
|
||||
return iter + 1;
|
||||
}
|
||||
|
||||
// Adaptive restart at iteration milestones
|
||||
if (iter == 25 || iter == 50 || iter == 75) {
|
||||
float new_mag = 4.0f - (iter / 25) * 0.5f;
|
||||
for (int v = 0; v < N_BITS; v++) {
|
||||
int bit = get_bit(codeword, v);
|
||||
var_llr[v] = bit ? -new_mag : new_mag;
|
||||
}
|
||||
for (int c = 0; c < M_BITS; c++) {
|
||||
for (int i = 0; i < check_degree[c]; i++) {
|
||||
c2v[c][i] = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Failed to converge
|
||||
memcpy(data, codeword, data_len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Block-level operations
|
||||
// =============================================================================
|
||||
|
||||
size_t ldpc_p_encode_blocks(const uint8_t *data, size_t data_len, uint8_t *output) {
|
||||
if (!ldpc_p_initialized) ldpc_p_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 > LDPC_P_DATA_SIZE) ? LDPC_P_DATA_SIZE : remaining;
|
||||
ldpc_p_encode(src, block_data, dst);
|
||||
|
||||
src += block_data;
|
||||
dst += LDPC_P_BLOCK_SIZE;
|
||||
output_len += LDPC_P_BLOCK_SIZE;
|
||||
remaining -= block_data;
|
||||
}
|
||||
|
||||
return output_len;
|
||||
}
|
||||
|
||||
int ldpc_p_decode_blocks(uint8_t *data, size_t total_len, uint8_t *output, size_t output_len) {
|
||||
if (!ldpc_p_initialized) ldpc_p_init();
|
||||
|
||||
int total_iterations = 0;
|
||||
size_t remaining_output = output_len;
|
||||
uint8_t *src = data;
|
||||
uint8_t *dst = output;
|
||||
|
||||
while (total_len >= LDPC_P_BLOCK_SIZE && remaining_output > 0) {
|
||||
size_t bytes_to_copy = (remaining_output > LDPC_P_DATA_SIZE) ? LDPC_P_DATA_SIZE : remaining_output;
|
||||
|
||||
int result = ldpc_p_decode(src, LDPC_P_DATA_SIZE);
|
||||
if (result < 0) {
|
||||
return -1;
|
||||
}
|
||||
total_iterations += result;
|
||||
|
||||
memcpy(dst, src, bytes_to_copy);
|
||||
|
||||
src += LDPC_P_BLOCK_SIZE;
|
||||
dst += bytes_to_copy;
|
||||
total_len -= LDPC_P_BLOCK_SIZE;
|
||||
remaining_output -= bytes_to_copy;
|
||||
}
|
||||
|
||||
return total_iterations;
|
||||
}
|
||||
97
video_encoder/lib/libfec/ldpc_payload.h
Normal file
97
video_encoder/lib/libfec/ldpc_payload.h
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* LDPC(255,223) Codec for TAV-DT Payloads
|
||||
*
|
||||
* Alternative to RS(255,223) with same rate (~0.875):
|
||||
* - Block size: 255 bytes (223 data + 32 parity)
|
||||
* - Uses quasi-cyclic LDPC structure for efficiency
|
||||
* - Soft-decision belief propagation decoder
|
||||
*
|
||||
* Designed as drop-in replacement for RS(255,223):
|
||||
* - Same input/output sizes
|
||||
* - Same API style
|
||||
* - Different error correction characteristics:
|
||||
* - LDPC: Better at high BER (>1e-3), gradual degradation
|
||||
* - RS: Better at low BER, hard threshold at 16 byte errors
|
||||
*
|
||||
* Created by CuriousTorvald and Claude on 2025-12-15.
|
||||
*/
|
||||
|
||||
#ifndef LDPC_PAYLOAD_H
|
||||
#define LDPC_PAYLOAD_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// LDPC(255,223) parameters - matches RS(255,223) for drop-in replacement
|
||||
#define LDPC_P_BLOCK_SIZE 255 // Total codeword size (bytes)
|
||||
#define LDPC_P_DATA_SIZE 223 // Data bytes per block
|
||||
#define LDPC_P_PARITY_SIZE 32 // Parity bytes per block
|
||||
|
||||
// Decoder parameters
|
||||
#define LDPC_P_MAX_ITERATIONS 30 // Maximum BP iterations
|
||||
#define LDPC_P_EARLY_TERM 1 // Enable early termination on valid codeword
|
||||
|
||||
/**
|
||||
* Initialize LDPC(255,223) codec.
|
||||
* Must be called once before using encode/decode functions.
|
||||
* Thread-safe: uses static initialization.
|
||||
*/
|
||||
void ldpc_p_init(void);
|
||||
|
||||
/**
|
||||
* Encode data block with LDPC(255,223).
|
||||
*
|
||||
* @param data Input data (up to LDPC_P_DATA_SIZE bytes)
|
||||
* @param data_len Length of input data (1 to LDPC_P_DATA_SIZE)
|
||||
* @param output Output buffer (must hold data_len + LDPC_P_PARITY_SIZE bytes)
|
||||
* Format: [data][parity]
|
||||
* @return Total output length (data_len + LDPC_P_PARITY_SIZE)
|
||||
*
|
||||
* Note: For data shorter than LDPC_P_DATA_SIZE, the encoder pads with zeros
|
||||
* internally but only outputs actual data + parity.
|
||||
*/
|
||||
size_t ldpc_p_encode(const uint8_t *data, size_t data_len, uint8_t *output);
|
||||
|
||||
/**
|
||||
* Decode and correct LDPC(255,223) encoded block.
|
||||
*
|
||||
* @param data Buffer containing [data][parity] (modified in-place)
|
||||
* @param data_len Length of data portion (1 to LDPC_P_DATA_SIZE)
|
||||
* @return Number of iterations used (1-30), or -1 if uncorrectable
|
||||
*
|
||||
* On success, data buffer contains corrected data.
|
||||
* On failure, data buffer contents are undefined.
|
||||
*/
|
||||
int ldpc_p_decode(uint8_t *data, size_t data_len);
|
||||
|
||||
/**
|
||||
* Encode data with automatic block splitting.
|
||||
* For data larger than LDPC_P_DATA_SIZE, splits into multiple 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 ldpc_p_encode_blocks(const uint8_t *data, size_t data_len, uint8_t *output);
|
||||
|
||||
/**
|
||||
* Decode data with automatic block splitting.
|
||||
*
|
||||
* @param data Buffer containing LDPC-encoded blocks (modified in-place)
|
||||
* @param total_len Total length of encoded data (multiple of LDPC_P_BLOCK_SIZE)
|
||||
* @param output Output buffer for decoded data
|
||||
* @param output_len Expected length of decoded data
|
||||
* @return Total iterations across all blocks, or -1 if any block failed
|
||||
*/
|
||||
int ldpc_p_decode_blocks(uint8_t *data, size_t total_len, uint8_t *output, size_t output_len);
|
||||
|
||||
/**
|
||||
* Check if codeword is valid (syndrome check).
|
||||
*
|
||||
* @param codeword Full codeword (LDPC_P_BLOCK_SIZE bytes)
|
||||
* @return 1 if valid (zero syndrome), 0 if errors detected
|
||||
*/
|
||||
int ldpc_p_check_syndrome(const uint8_t *codeword);
|
||||
|
||||
#endif // LDPC_PAYLOAD_H
|
||||
@@ -36,6 +36,11 @@
|
||||
#include "decoder_tad.h"
|
||||
#include "reed_solomon.h"
|
||||
#include "ldpc.h"
|
||||
#include "ldpc_payload.h"
|
||||
|
||||
// FEC mode for payloads (must match encoder setting)
|
||||
#define FEC_MODE_RS 0 // Reed-Solomon (255,223) - default
|
||||
#define FEC_MODE_LDPC 1 // LDPC (255,223) - experimental
|
||||
|
||||
// =============================================================================
|
||||
// Constants
|
||||
@@ -187,6 +192,7 @@ typedef struct {
|
||||
// Options
|
||||
int verbose;
|
||||
int dump_mode; // Just dump packets, don't decode
|
||||
int fec_mode; // FEC_MODE_RS or FEC_MODE_LDPC (must match encoder)
|
||||
|
||||
// Multithreading
|
||||
int num_threads;
|
||||
@@ -226,11 +232,27 @@ static void print_usage(const char *program) {
|
||||
printf("\nOptions:\n");
|
||||
printf(" -t, --threads N Number of decoder threads (default: min(8, available CPUs))\n");
|
||||
printf(" 0 or 1 = single-threaded, 2-16 = multithreaded\n");
|
||||
printf(" --ldpc-payload Use LDPC(255,223) instead of RS(255,223) for payloads\n");
|
||||
printf(" (must match encoder setting)\n");
|
||||
printf(" --dump Dump packet info without decoding\n");
|
||||
printf(" -v, --verbose Verbose output\n");
|
||||
printf(" --help Show this help\n");
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FEC Block Decoding (RS or LDPC based on mode)
|
||||
// =============================================================================
|
||||
|
||||
static int decode_fec_blocks(uint8_t *data, size_t total_len, uint8_t *output, size_t output_len, int fec_mode) {
|
||||
if (fec_mode == FEC_MODE_LDPC) {
|
||||
// Use LDPC(255,223) decoding
|
||||
return ldpc_p_decode_blocks(data, total_len, output, output_len);
|
||||
} else {
|
||||
// Use RS(255,223) decoding (default)
|
||||
return rs_decode_blocks(data, total_len, output, output_len);
|
||||
}
|
||||
}
|
||||
|
||||
static void generate_random_filename(char *filename, size_t size) {
|
||||
static int seeded = 0;
|
||||
if (!seeded) {
|
||||
@@ -763,17 +785,17 @@ static int decode_audio_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
|
||||
return -1;
|
||||
}
|
||||
|
||||
int rs_result = rs_decode_blocks(rs_data, rs_total, decoded_payload, compressed_size);
|
||||
if (rs_result < 0) {
|
||||
int fec_result = decode_fec_blocks(rs_data, rs_total, decoded_payload, compressed_size, dec->fec_mode);
|
||||
if (fec_result < 0) {
|
||||
if (dec->verbose) {
|
||||
fprintf(stderr, "Warning: RS decode failed for audio - UNRECOVERABLE\n");
|
||||
fprintf(stderr, "Warning: FEC decode failed for audio - UNRECOVERABLE\n");
|
||||
}
|
||||
free(rs_data);
|
||||
free(decoded_payload);
|
||||
*consumed = offset + rs_total;
|
||||
return -1; // Unrecoverable - RS failed
|
||||
} else if (rs_result > 0) {
|
||||
dec->fec_corrections += rs_result;
|
||||
return -1; // Unrecoverable - FEC failed
|
||||
} else if (fec_result > 0) {
|
||||
dec->fec_corrections += fec_result;
|
||||
}
|
||||
|
||||
// decoded_payload already contains the full TAD chunk format:
|
||||
@@ -876,17 +898,17 @@ static int decode_video_subpacket_mt(dt_decoder_t *dec, const uint8_t *data, siz
|
||||
return -1;
|
||||
}
|
||||
|
||||
int rs_result = rs_decode_blocks(rs_data, rs_total, decoded_payload, compressed_size);
|
||||
if (rs_result < 0) {
|
||||
int fec_result = decode_fec_blocks(rs_data, rs_total, decoded_payload, compressed_size, dec->fec_mode);
|
||||
if (fec_result < 0) {
|
||||
if (dec->verbose) {
|
||||
fprintf(stderr, "Warning: RS decode failed for video (MT) - UNRECOVERABLE\n");
|
||||
fprintf(stderr, "Warning: FEC decode failed for video (MT) - UNRECOVERABLE\n");
|
||||
}
|
||||
free(rs_data);
|
||||
free(decoded_payload);
|
||||
*consumed = offset + rs_total;
|
||||
return -1; // Unrecoverable - RS failed
|
||||
} else if (rs_result > 0) {
|
||||
dec->fec_corrections += rs_result;
|
||||
return -1; // Unrecoverable - FEC failed
|
||||
} else if (fec_result > 0) {
|
||||
dec->fec_corrections += fec_result;
|
||||
}
|
||||
free(rs_data);
|
||||
|
||||
@@ -1050,17 +1072,17 @@ static int decode_video_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
|
||||
return -1;
|
||||
}
|
||||
|
||||
int rs_result = rs_decode_blocks(rs_data, rs_total, decoded_payload, compressed_size);
|
||||
if (rs_result < 0) {
|
||||
int fec_result = decode_fec_blocks(rs_data, rs_total, decoded_payload, compressed_size, dec->fec_mode);
|
||||
if (fec_result < 0) {
|
||||
if (dec->verbose) {
|
||||
fprintf(stderr, "Warning: RS decode failed for video - UNRECOVERABLE\n");
|
||||
fprintf(stderr, "Warning: FEC decode failed for video - UNRECOVERABLE\n");
|
||||
}
|
||||
free(rs_data);
|
||||
free(decoded_payload);
|
||||
*consumed = offset + rs_total;
|
||||
return -1; // Unrecoverable - RS failed
|
||||
} else if (rs_result > 0) {
|
||||
dec->fec_corrections += rs_result;
|
||||
return -1; // Unrecoverable - FEC failed
|
||||
} else if (fec_result > 0) {
|
||||
dec->fec_corrections += fec_result;
|
||||
}
|
||||
|
||||
// Initialize video decoder if needed
|
||||
@@ -1680,11 +1702,13 @@ int main(int argc, char **argv) {
|
||||
// Initialize FEC libraries
|
||||
rs_init();
|
||||
ldpc_init();
|
||||
ldpc_p_init(); // LDPC payload codec
|
||||
|
||||
static struct option long_options[] = {
|
||||
{"input", required_argument, 0, 'i'},
|
||||
{"output", required_argument, 0, 'o'},
|
||||
{"threads", required_argument, 0, 't'},
|
||||
{"ldpc-payload", no_argument, 0, 'D'},
|
||||
{"dump", no_argument, 0, 'd'},
|
||||
{"verbose", no_argument, 0, 'v'},
|
||||
{"help", no_argument, 0, 'h'},
|
||||
@@ -1711,6 +1735,9 @@ int main(int argc, char **argv) {
|
||||
if (dec.num_threads > MAX_DECODE_THREADS) dec.num_threads = MAX_DECODE_THREADS;
|
||||
break;
|
||||
}
|
||||
case 'D':
|
||||
dec.fec_mode = FEC_MODE_LDPC;
|
||||
break;
|
||||
case 'd':
|
||||
dec.dump_mode = 1;
|
||||
break;
|
||||
|
||||
@@ -36,6 +36,11 @@
|
||||
#include "encoder_tad.h"
|
||||
#include "reed_solomon.h"
|
||||
#include "ldpc.h"
|
||||
#include "ldpc_payload.h"
|
||||
|
||||
// FEC mode for payloads (stored in flags byte bit 2)
|
||||
#define FEC_MODE_RS 0 // Reed-Solomon (255,223) - default
|
||||
#define FEC_MODE_LDPC 1 // LDPC (255,223) - experimental
|
||||
|
||||
// =============================================================================
|
||||
// Constants
|
||||
@@ -197,6 +202,7 @@ typedef struct {
|
||||
// Options
|
||||
int verbose;
|
||||
int encode_limit;
|
||||
int fec_mode; // FEC_MODE_RS or FEC_MODE_LDPC for payloads
|
||||
|
||||
// Multithreading
|
||||
int num_threads; // 0 = single-threaded, 1+ = num worker threads
|
||||
@@ -226,6 +232,8 @@ static void print_usage(const char *program) {
|
||||
printf(" --ntsc Force NTSC format (720x480, default)\n");
|
||||
printf(" --pal Force PAL format (720x576)\n");
|
||||
printf(" --interlaced Interlaced output\n");
|
||||
printf(" --ldpc-payload Use LDPC(255,223) instead of RS(255,223) for payloads\n");
|
||||
printf(" (experimental: better at high error rates)\n");
|
||||
printf(" --encode-limit N Encode only N frames (for testing)\n");
|
||||
printf(" -t, --threads N Parallel encoding threads (default: min(8, available CPUs))\n");
|
||||
printf(" 0 or 1 = single-threaded, 2-16 = multithreaded\n");
|
||||
@@ -234,10 +242,15 @@ static void print_usage(const char *program) {
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// RS Block Encoding
|
||||
// FEC Block Encoding (RS or LDPC based on mode)
|
||||
// =============================================================================
|
||||
|
||||
static size_t encode_rs_blocks(const uint8_t *data, size_t data_len, uint8_t *output) {
|
||||
static size_t encode_fec_blocks(const uint8_t *data, size_t data_len, uint8_t *output, int fec_mode) {
|
||||
if (fec_mode == FEC_MODE_LDPC) {
|
||||
// Use LDPC(255,223) encoding
|
||||
return ldpc_p_encode_blocks(data, data_len, output);
|
||||
} else {
|
||||
// Use RS(255,223) encoding (default)
|
||||
size_t output_len = 0;
|
||||
size_t remaining = data_len;
|
||||
const uint8_t *src = data;
|
||||
@@ -260,6 +273,7 @@ static size_t encode_rs_blocks(const uint8_t *data, size_t data_len, uint8_t *ou
|
||||
|
||||
return output_len;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Packet Writing
|
||||
@@ -355,12 +369,12 @@ static int write_packet(dt_encoder_t *enc, uint64_t timecode_ns,
|
||||
uint8_t ldpc_tav_header[DT_TAV_HEADER_SIZE * 2];
|
||||
ldpc_encode(tav_header, DT_TAV_HEADER_SIZE, ldpc_tav_header);
|
||||
|
||||
// RS encode payloads
|
||||
// FEC encode payloads (RS or LDPC based on mode)
|
||||
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);
|
||||
encode_fec_blocks(tad_data, tad_size, tad_rs_data, enc->fec_mode);
|
||||
encode_fec_blocks(tav_data, tav_size, tav_rs_data, enc->fec_mode);
|
||||
|
||||
// Write everything
|
||||
fwrite(ldpc_header, 1, DT_MAIN_HEADER_SIZE * 2, enc->output_fp);
|
||||
@@ -1268,6 +1282,7 @@ int main(int argc, char **argv) {
|
||||
// Initialize FEC libraries
|
||||
rs_init();
|
||||
ldpc_init();
|
||||
ldpc_p_init(); // LDPC payload codec
|
||||
|
||||
static struct option long_options[] = {
|
||||
{"input", required_argument, 0, 'i'},
|
||||
@@ -1277,6 +1292,7 @@ int main(int argc, char **argv) {
|
||||
{"ntsc", no_argument, 0, 'N'},
|
||||
{"pal", no_argument, 0, 'P'},
|
||||
{"interlaced", no_argument, 0, 'I'},
|
||||
{"ldpc-payload", no_argument, 0, 'D'},
|
||||
{"encode-limit", required_argument, 0, 'L'},
|
||||
{"verbose", no_argument, 0, 'v'},
|
||||
{"help", no_argument, 0, 'h'},
|
||||
@@ -1319,6 +1335,9 @@ int main(int argc, char **argv) {
|
||||
case 'I':
|
||||
enc.is_interlaced = 1;
|
||||
break;
|
||||
case 'D':
|
||||
enc.fec_mode = FEC_MODE_LDPC;
|
||||
break;
|
||||
case 'L':
|
||||
enc.encode_limit = atoi(optarg);
|
||||
break;
|
||||
@@ -1366,6 +1385,7 @@ int main(int argc, char **argv) {
|
||||
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(" Payload FEC: %s\n", enc.fec_mode == FEC_MODE_LDPC ? "LDPC(255,223)" : "RS(255,223)");
|
||||
printf(" Threads: %d%s\n", enc.num_threads > 0 ? enc.num_threads : 1,
|
||||
enc.num_threads > 0 ? " (multithreaded)" : " (single-threaded)");
|
||||
printf(" Header sizes: main=%dB tad=%dB tav=%dB (after LDPC)\n",
|
||||
|
||||
Reference in New Issue
Block a user