mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 19:51:51 +09:00
TAV-DT syncing on damaged stream wip
This commit is contained in:
@@ -47,7 +47,7 @@ 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
|
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
|
TAV_TARGETS = encoder_tav_ref decoder_tav_ref tav_inspector
|
||||||
TAD_TARGETS = encoder_tad decoder_tad
|
TAD_TARGETS = encoder_tad decoder_tad
|
||||||
DT_TARGETS = encoder_tav_dt decoder_tav_dt
|
DT_TARGETS = encoder_tav_dt decoder_tav_dt tavdt_noise_injector
|
||||||
|
|
||||||
# Build all encoders (default)
|
# Build all encoders (default)
|
||||||
all: clean $(TARGETS)
|
all: clean $(TARGETS)
|
||||||
@@ -147,6 +147,14 @@ decoder_tav_dt: src/decoder_tav_dt.c lib/libtavdec.a lib/libtaddec.a lib/libfec.
|
|||||||
@echo "TAV-DT decoder built: decoder_tav_dt"
|
@echo "TAV-DT decoder built: decoder_tav_dt"
|
||||||
@echo "Digital Tape format with LDPC and Reed-Solomon FEC (multithreaded)"
|
@echo "Digital Tape format with LDPC and Reed-Solomon FEC (multithreaded)"
|
||||||
|
|
||||||
|
# TAV-DT noise injector (channel simulator)
|
||||||
|
tavdt_noise_injector: tavdt_noise_injector.c
|
||||||
|
rm -f tavdt_noise_injector
|
||||||
|
$(CC) -std=c99 -Wall -O2 -D_GNU_SOURCE -o tavdt_noise_injector tavdt_noise_injector.c -lm
|
||||||
|
@echo ""
|
||||||
|
@echo "TAV-DT noise injector built: tavdt_noise_injector"
|
||||||
|
@echo "Simulates QPSK satellite channel noise (AWGN + burst)"
|
||||||
|
|
||||||
# Build all TAV-DT tools
|
# Build all TAV-DT tools
|
||||||
tav_dt: $(DT_TARGETS)
|
tav_dt: $(DT_TARGETS)
|
||||||
|
|
||||||
@@ -184,6 +192,7 @@ help:
|
|||||||
@echo " libs - Build all codec libraries (.a files)"
|
@echo " libs - Build all codec libraries (.a files)"
|
||||||
@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 " tav_dt - Build all TAV-DT (Digital Tape) tools with FEC"
|
||||||
|
@echo " tavdt_noise_injector - Build TAV-DT channel noise simulator"
|
||||||
@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"
|
||||||
|
|||||||
@@ -204,6 +204,13 @@ typedef struct {
|
|||||||
|
|
||||||
// Timing
|
// Timing
|
||||||
time_t start_time;
|
time_t start_time;
|
||||||
|
|
||||||
|
// Error concealment
|
||||||
|
uint8_t *freeze_frame; // Last good video frame for error concealment
|
||||||
|
size_t freeze_frame_size;
|
||||||
|
uint64_t last_timecode_ns; // Last processed timecode
|
||||||
|
uint64_t audio_samples_written; // Total audio samples written
|
||||||
|
uint64_t video_frames_written; // Total video frames written (for sync check)
|
||||||
} dt_decoder_t;
|
} dt_decoder_t;
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -240,6 +247,102 @@ static void generate_random_filename(char *filename, size_t size) {
|
|||||||
filename[prefix_len + 16] = '\0';
|
filename[prefix_len + 16] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Error Concealment Functions
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write silent audio samples for error concealment.
|
||||||
|
* Generates PCMu8 silence (value 128) for the specified number of stereo samples.
|
||||||
|
*/
|
||||||
|
static int write_silent_audio(dt_decoder_t *dec, size_t num_samples) {
|
||||||
|
if (!dec->audio_temp_fp || num_samples == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PCMu8 silence is value 128 (0x80)
|
||||||
|
uint8_t *silence = malloc(num_samples * 2);
|
||||||
|
if (!silence) {
|
||||||
|
fprintf(stderr, "Warning: Cannot allocate silence buffer\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(silence, 128, num_samples * 2);
|
||||||
|
fwrite(silence, 1, num_samples * 2, dec->audio_temp_fp);
|
||||||
|
free(silence);
|
||||||
|
|
||||||
|
dec->audio_samples_written += num_samples;
|
||||||
|
|
||||||
|
if (dec->verbose) {
|
||||||
|
printf(" Error concealment: Wrote %zu samples of silent audio\n", num_samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write frozen video frame(s) for error concealment.
|
||||||
|
* Repeats the last good frame or writes black frame if no freeze frame exists.
|
||||||
|
*/
|
||||||
|
static int write_frozen_frames(dt_decoder_t *dec, int num_frames) {
|
||||||
|
if (!dec->video_temp_fp || num_frames <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int internal_height = dec->is_interlaced ? dec->height / 2 : dec->height;
|
||||||
|
size_t frame_size = dec->width * internal_height * 3;
|
||||||
|
|
||||||
|
// If no freeze frame exists, create a black frame
|
||||||
|
if (!dec->freeze_frame) {
|
||||||
|
dec->freeze_frame = calloc(1, frame_size);
|
||||||
|
if (!dec->freeze_frame) {
|
||||||
|
fprintf(stderr, "Warning: Cannot allocate freeze frame buffer\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
dec->freeze_frame_size = frame_size;
|
||||||
|
if (dec->verbose) {
|
||||||
|
printf(" Error concealment: Using black frame (no reference frame available)\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the freeze frame multiple times
|
||||||
|
for (int i = 0; i < num_frames; i++) {
|
||||||
|
fwrite(dec->freeze_frame, 1, dec->freeze_frame_size, dec->video_temp_fp);
|
||||||
|
dec->video_frames_written++;
|
||||||
|
dec->frames_decoded++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dec->verbose) {
|
||||||
|
printf(" Error concealment: Wrote %d frozen frame(s)\n", num_frames);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the freeze frame buffer with the last successfully decoded frame.
|
||||||
|
*/
|
||||||
|
static int update_freeze_frame(dt_decoder_t *dec, const uint8_t *frame_data, size_t frame_size) {
|
||||||
|
if (!frame_data || frame_size == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate or reallocate freeze frame buffer
|
||||||
|
if (!dec->freeze_frame || dec->freeze_frame_size != frame_size) {
|
||||||
|
free(dec->freeze_frame);
|
||||||
|
dec->freeze_frame = malloc(frame_size);
|
||||||
|
if (!dec->freeze_frame) {
|
||||||
|
fprintf(stderr, "Warning: Cannot allocate freeze frame buffer\n");
|
||||||
|
dec->freeze_frame_size = 0;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
dec->freeze_frame_size = frame_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(dec->freeze_frame, frame_data, frame_size);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Sync Pattern Search
|
// Sync Pattern Search
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -592,7 +695,9 @@ static void cleanup_decoder_threads(dt_decoder_t *dec) {
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
static int decode_audio_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t data_len,
|
static int decode_audio_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t data_len,
|
||||||
size_t *consumed) {
|
size_t *consumed, size_t *samples_written) {
|
||||||
|
*samples_written = 0;
|
||||||
|
|
||||||
// Minimum: 20 byte LDPC header
|
// Minimum: 20 byte LDPC header
|
||||||
if (data_len < DT_TAD_HEADER_SIZE * 2) return -1;
|
if (data_len < DT_TAD_HEADER_SIZE * 2) return -1;
|
||||||
|
|
||||||
@@ -644,7 +749,7 @@ static int decode_audio_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
|
|||||||
fprintf(stderr, "Warning: Audio packet truncated\n");
|
fprintf(stderr, "Warning: Audio packet truncated\n");
|
||||||
}
|
}
|
||||||
*consumed = data_len;
|
*consumed = data_len;
|
||||||
return -1;
|
return -1; // Unrecoverable
|
||||||
}
|
}
|
||||||
|
|
||||||
// RS decode payload
|
// RS decode payload
|
||||||
@@ -661,8 +766,12 @@ static int decode_audio_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
|
|||||||
int rs_result = rs_decode_blocks(rs_data, rs_total, decoded_payload, compressed_size);
|
int rs_result = rs_decode_blocks(rs_data, rs_total, decoded_payload, compressed_size);
|
||||||
if (rs_result < 0) {
|
if (rs_result < 0) {
|
||||||
if (dec->verbose) {
|
if (dec->verbose) {
|
||||||
fprintf(stderr, "Warning: RS decode failed for audio\n");
|
fprintf(stderr, "Warning: RS 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) {
|
} else if (rs_result > 0) {
|
||||||
dec->fec_corrections += rs_result;
|
dec->fec_corrections += rs_result;
|
||||||
}
|
}
|
||||||
@@ -690,6 +799,17 @@ static int decode_audio_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
|
|||||||
|
|
||||||
if (tad_result == 0 && samples_decoded > 0 && dec->audio_temp_fp) {
|
if (tad_result == 0 && samples_decoded > 0 && dec->audio_temp_fp) {
|
||||||
fwrite(pcmu8_output, 1, samples_decoded * 2, dec->audio_temp_fp);
|
fwrite(pcmu8_output, 1, samples_decoded * 2, dec->audio_temp_fp);
|
||||||
|
*samples_written = samples_decoded;
|
||||||
|
dec->audio_samples_written += samples_decoded;
|
||||||
|
} else {
|
||||||
|
if (dec->verbose) {
|
||||||
|
fprintf(stderr, "Warning: TAD decode failed - UNRECOVERABLE\n");
|
||||||
|
}
|
||||||
|
free(pcmu8_output);
|
||||||
|
free(rs_data);
|
||||||
|
free(decoded_payload);
|
||||||
|
*consumed = offset + rs_total;
|
||||||
|
return -1; // Unrecoverable - TAD decode failed
|
||||||
}
|
}
|
||||||
|
|
||||||
free(pcmu8_output);
|
free(pcmu8_output);
|
||||||
@@ -699,14 +819,16 @@ static int decode_audio_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
|
|||||||
offset += rs_total;
|
offset += rs_total;
|
||||||
*consumed = offset;
|
*consumed = offset;
|
||||||
|
|
||||||
return 0;
|
return 0; // Success
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Multithreaded video decoding - submit GOP to worker pool
|
* Multithreaded video decoding - submit GOP to worker pool
|
||||||
*/
|
*/
|
||||||
static int decode_video_subpacket_mt(dt_decoder_t *dec, const uint8_t *data, size_t data_len,
|
static int decode_video_subpacket_mt(dt_decoder_t *dec, const uint8_t *data, size_t data_len,
|
||||||
size_t *consumed) {
|
size_t *consumed, int *frames_written) {
|
||||||
|
*frames_written = 0;
|
||||||
|
|
||||||
// Minimum: 16 byte LDPC header
|
// Minimum: 16 byte LDPC header
|
||||||
if (data_len < DT_TAV_HEADER_SIZE * 2) return -1;
|
if (data_len < DT_TAV_HEADER_SIZE * 2) return -1;
|
||||||
|
|
||||||
@@ -740,7 +862,7 @@ static int decode_video_subpacket_mt(dt_decoder_t *dec, const uint8_t *data, siz
|
|||||||
|
|
||||||
if (offset + rs_total > data_len) {
|
if (offset + rs_total > data_len) {
|
||||||
*consumed = data_len;
|
*consumed = data_len;
|
||||||
return -1;
|
return -1; // Unrecoverable
|
||||||
}
|
}
|
||||||
|
|
||||||
// RS decode payload
|
// RS decode payload
|
||||||
@@ -755,7 +877,15 @@ static int decode_video_subpacket_mt(dt_decoder_t *dec, const uint8_t *data, siz
|
|||||||
}
|
}
|
||||||
|
|
||||||
int rs_result = rs_decode_blocks(rs_data, rs_total, decoded_payload, compressed_size);
|
int rs_result = rs_decode_blocks(rs_data, rs_total, decoded_payload, compressed_size);
|
||||||
if (rs_result > 0) {
|
if (rs_result < 0) {
|
||||||
|
if (dec->verbose) {
|
||||||
|
fprintf(stderr, "Warning: RS 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;
|
dec->fec_corrections += rs_result;
|
||||||
}
|
}
|
||||||
free(rs_data);
|
free(rs_data);
|
||||||
@@ -788,11 +918,13 @@ static int decode_video_subpacket_mt(dt_decoder_t *dec, const uint8_t *data, siz
|
|||||||
gop_decode_job_t *job = &dec->slots[i];
|
gop_decode_job_t *job = &dec->slots[i];
|
||||||
pthread_mutex_unlock(&dec->mutex);
|
pthread_mutex_unlock(&dec->mutex);
|
||||||
|
|
||||||
// Write frames to temp file
|
// Write frames to temp file and update freeze frame
|
||||||
if (job->decode_result == 0 && dec->video_temp_fp) {
|
if (job->decode_result == 0 && dec->video_temp_fp) {
|
||||||
for (int f = 0; f < job->gop_size; f++) {
|
for (int f = 0; f < job->gop_size; f++) {
|
||||||
fwrite(job->rgb_frames[f], 1, job->frame_size, dec->video_temp_fp);
|
fwrite(job->rgb_frames[f], 1, job->frame_size, dec->video_temp_fp);
|
||||||
|
update_freeze_frame(dec, job->rgb_frames[f], job->frame_size);
|
||||||
dec->frames_decoded++;
|
dec->frames_decoded++;
|
||||||
|
dec->video_frames_written++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -853,12 +985,15 @@ static int decode_video_subpacket_mt(dt_decoder_t *dec, const uint8_t *data, siz
|
|||||||
|
|
||||||
offset += rs_total;
|
offset += rs_total;
|
||||||
*consumed = offset;
|
*consumed = offset;
|
||||||
|
*frames_written = gop_size; // Optimistic - assume decode will succeed
|
||||||
|
|
||||||
return 0;
|
return 0; // Success - job submitted
|
||||||
}
|
}
|
||||||
|
|
||||||
static int decode_video_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t data_len,
|
static int decode_video_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t data_len,
|
||||||
size_t *consumed) {
|
size_t *consumed, int *frames_written) {
|
||||||
|
*frames_written = 0;
|
||||||
|
|
||||||
// Minimum: 16 byte LDPC header
|
// Minimum: 16 byte LDPC header
|
||||||
if (data_len < DT_TAV_HEADER_SIZE * 2) return -1;
|
if (data_len < DT_TAV_HEADER_SIZE * 2) return -1;
|
||||||
|
|
||||||
@@ -901,7 +1036,7 @@ static int decode_video_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
|
|||||||
fprintf(stderr, "Warning: Video packet truncated\n");
|
fprintf(stderr, "Warning: Video packet truncated\n");
|
||||||
}
|
}
|
||||||
*consumed = data_len;
|
*consumed = data_len;
|
||||||
return -1;
|
return -1; // Unrecoverable
|
||||||
}
|
}
|
||||||
|
|
||||||
// RS decode payload
|
// RS decode payload
|
||||||
@@ -918,8 +1053,12 @@ static int decode_video_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
|
|||||||
int rs_result = rs_decode_blocks(rs_data, rs_total, decoded_payload, compressed_size);
|
int rs_result = rs_decode_blocks(rs_data, rs_total, decoded_payload, compressed_size);
|
||||||
if (rs_result < 0) {
|
if (rs_result < 0) {
|
||||||
if (dec->verbose) {
|
if (dec->verbose) {
|
||||||
fprintf(stderr, "Warning: RS decode failed for video\n");
|
fprintf(stderr, "Warning: RS 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) {
|
} else if (rs_result > 0) {
|
||||||
dec->fec_corrections += rs_result;
|
dec->fec_corrections += rs_result;
|
||||||
}
|
}
|
||||||
@@ -984,18 +1123,31 @@ static int decode_video_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
|
|||||||
gop_size, rgb_frames);
|
gop_size, rgb_frames);
|
||||||
|
|
||||||
if (decode_result == 0) {
|
if (decode_result == 0) {
|
||||||
// Write frames to video temp file
|
// Write frames to video temp file and update freeze frame
|
||||||
for (int i = 0; i < gop_size; i++) {
|
for (int i = 0; i < gop_size; i++) {
|
||||||
if (dec->video_temp_fp) {
|
if (dec->video_temp_fp) {
|
||||||
fwrite(rgb_frames[i], 1, frame_size, dec->video_temp_fp);
|
fwrite(rgb_frames[i], 1, frame_size, dec->video_temp_fp);
|
||||||
}
|
}
|
||||||
|
// Update freeze frame with last successfully decoded frame
|
||||||
|
update_freeze_frame(dec, rgb_frames[i], frame_size);
|
||||||
dec->frames_decoded++;
|
dec->frames_decoded++;
|
||||||
|
dec->video_frames_written++;
|
||||||
}
|
}
|
||||||
|
*frames_written = gop_size;
|
||||||
} else {
|
} else {
|
||||||
if (dec->verbose) {
|
if (dec->verbose) {
|
||||||
const char *err = tav_video_get_error(dec->video_ctx);
|
const char *err = tav_video_get_error(dec->video_ctx);
|
||||||
fprintf(stderr, "Warning: Video decode failed: %s\n", err ? err : "unknown error");
|
fprintf(stderr, "Warning: Video decode failed: %s - UNRECOVERABLE\n", err ? err : "unknown error");
|
||||||
}
|
}
|
||||||
|
// Cleanup and return error
|
||||||
|
for (int i = 0; i < gop_size; i++) {
|
||||||
|
free(rgb_frames[i]);
|
||||||
|
}
|
||||||
|
free(rgb_frames);
|
||||||
|
free(rs_data);
|
||||||
|
free(decoded_payload);
|
||||||
|
*consumed = offset + rs_total;
|
||||||
|
return -1; // Unrecoverable - video decode failed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
@@ -1009,7 +1161,7 @@ static int decode_video_subpacket(dt_decoder_t *dec, const uint8_t *data, size_t
|
|||||||
offset += rs_total;
|
offset += rs_total;
|
||||||
*consumed = offset;
|
*consumed = offset;
|
||||||
|
|
||||||
return 0;
|
return 0; // Success
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -1187,6 +1339,146 @@ static int process_packet(dt_decoder_t *dec) {
|
|||||||
dec->packets_processed + 1, timecode_sec, header.packet_size, header.offset_to_video);
|
dec->packets_processed + 1, timecode_sec, header.packet_size, header.offset_to_video);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate expected samples/frames based on timecode
|
||||||
|
// TAD audio is 32000 Hz stereo, GOP size varies
|
||||||
|
uint64_t timecode_delta_ns = 0;
|
||||||
|
size_t expected_audio_samples = 0;
|
||||||
|
int expected_video_frames = 0;
|
||||||
|
int timecode_valid = 0;
|
||||||
|
|
||||||
|
if (dec->packets_processed > 0) {
|
||||||
|
// Sanity check: detect obviously garbage timecodes (corrupted header data)
|
||||||
|
// A timecode is "garbage" if it's impossibly large (> 24 hours) or if it went backwards
|
||||||
|
// Large forward jumps are OK - they indicate lost packets and should be trusted
|
||||||
|
uint64_t max_reasonable_timecode_ns = 86400ULL * 1000000000ULL; // 24 hours
|
||||||
|
|
||||||
|
uint64_t reconstructed_timecode_ns = 0;
|
||||||
|
int use_reconstructed = 0;
|
||||||
|
uint64_t gop_duration_ns = (16ULL * 1000000000ULL) / dec->framerate;
|
||||||
|
|
||||||
|
if (header.timecode_ns > max_reasonable_timecode_ns) {
|
||||||
|
// Timecode is garbage (e.g., 9007208.588s = 104 days) - reconstruct
|
||||||
|
reconstructed_timecode_ns = dec->last_timecode_ns + gop_duration_ns;
|
||||||
|
timecode_delta_ns = gop_duration_ns;
|
||||||
|
use_reconstructed = 1;
|
||||||
|
|
||||||
|
if (dec->verbose) {
|
||||||
|
double corrupted_tc = header.timecode_ns / 1000000000.0;
|
||||||
|
double reconstructed_tc = reconstructed_timecode_ns / 1000000000.0;
|
||||||
|
fprintf(stderr, "Warning: Timecode garbage (%.3fs), reconstructed as %.3fs based on GOP size\n",
|
||||||
|
corrupted_tc, reconstructed_tc);
|
||||||
|
}
|
||||||
|
} else if (header.timecode_ns > dec->last_timecode_ns) {
|
||||||
|
// Valid timecode moving forward - trust it (even with large jumps from lost packets)
|
||||||
|
timecode_delta_ns = header.timecode_ns - dec->last_timecode_ns;
|
||||||
|
} else if (header.timecode_ns == dec->last_timecode_ns) {
|
||||||
|
// Duplicate timecode - corrupted, reconstruct
|
||||||
|
reconstructed_timecode_ns = dec->last_timecode_ns + gop_duration_ns;
|
||||||
|
timecode_delta_ns = gop_duration_ns;
|
||||||
|
use_reconstructed = 1;
|
||||||
|
|
||||||
|
if (dec->verbose) {
|
||||||
|
fprintf(stderr, "Warning: Duplicate timecode detected, reconstructed based on GOP size\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Timecode went backwards - corrupted, reconstruct
|
||||||
|
reconstructed_timecode_ns = dec->last_timecode_ns + gop_duration_ns;
|
||||||
|
timecode_delta_ns = gop_duration_ns;
|
||||||
|
use_reconstructed = 1;
|
||||||
|
|
||||||
|
if (dec->verbose) {
|
||||||
|
fprintf(stderr, "Warning: Timecode went backwards, reconstructed based on GOP size\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate expected samples/frames from (possibly reconstructed) timecode delta
|
||||||
|
// NOTE: These variables are currently unused - cumulative logic below uses absolute timecodes
|
||||||
|
expected_audio_samples = (timecode_delta_ns * 64000) / 1000000000ULL; // 32kHz stereo = 64000 samples/sec
|
||||||
|
expected_video_frames = (int)((timecode_delta_ns * dec->framerate) / 1000000000ULL);
|
||||||
|
timecode_valid = 1;
|
||||||
|
|
||||||
|
// Store which timecode to use for next packet
|
||||||
|
if (use_reconstructed) {
|
||||||
|
// Override header timecode with reconstructed value
|
||||||
|
header.timecode_ns = reconstructed_timecode_ns;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error concealment: Insert gaps BEFORE decoding current packet
|
||||||
|
// This ensures concealment data appears in the correct timeline position
|
||||||
|
|
||||||
|
// Also handle first packet - if timecode > 0, insert concealment for missed initial data
|
||||||
|
if (dec->packets_processed == 0 && header.timecode_ns > 0) {
|
||||||
|
// First packet but timecode is not 0 - we missed the beginning
|
||||||
|
// Audio: 32000 Hz stereo = 64000 total samples per second (L+R combined)
|
||||||
|
uint64_t expected_cumulative_audio = (header.timecode_ns * 64000ULL) / 1000000000ULL;
|
||||||
|
uint64_t expected_cumulative_video = (header.timecode_ns * (uint64_t)dec->framerate) / 1000000000ULL;
|
||||||
|
|
||||||
|
if (dec->verbose) {
|
||||||
|
printf(" FIRST PACKET CONCEALMENT: timecode=%.3fs, inserting %lu silent samples + %lu frozen frames\n",
|
||||||
|
header.timecode_ns / 1000000000.0, expected_cumulative_audio, expected_cumulative_video);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expected_cumulative_audio > 0) {
|
||||||
|
write_silent_audio(dec, expected_cumulative_audio);
|
||||||
|
}
|
||||||
|
if (expected_cumulative_video > 0) {
|
||||||
|
write_frozen_frames(dec, (int)expected_cumulative_video);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dec->packets_processed > 0 && timecode_valid) {
|
||||||
|
// Save cumulative counts BEFORE decoding this packet
|
||||||
|
uint64_t cumulative_audio_before = dec->audio_samples_written;
|
||||||
|
uint64_t cumulative_video_before = dec->video_frames_written;
|
||||||
|
|
||||||
|
// Calculate expected CUMULATIVE samples/frames at this timecode
|
||||||
|
// Audio: 32000 Hz stereo = 64000 total samples per second (L+R combined)
|
||||||
|
uint64_t expected_cumulative_audio = (header.timecode_ns * 64000ULL) / 1000000000ULL;
|
||||||
|
uint64_t expected_cumulative_video = (header.timecode_ns * (uint64_t)dec->framerate) / 1000000000ULL;
|
||||||
|
|
||||||
|
// Calculate gap between expected and actual (BEFORE this packet)
|
||||||
|
size_t audio_gap = 0;
|
||||||
|
int video_gap = 0;
|
||||||
|
|
||||||
|
if (expected_cumulative_audio > cumulative_audio_before) {
|
||||||
|
audio_gap = expected_cumulative_audio - cumulative_audio_before;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expected_cumulative_video > cumulative_video_before) {
|
||||||
|
video_gap = expected_cumulative_video - cumulative_video_before;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert concealment data FIRST (fills gap from lost packets)
|
||||||
|
if (audio_gap > 0 || video_gap > 0) {
|
||||||
|
if (dec->verbose) {
|
||||||
|
if (audio_gap > 0 && video_gap > 0) {
|
||||||
|
printf(" ERROR CONCEALMENT: Inserting %zu silent samples + %d frozen frames\n",
|
||||||
|
audio_gap, video_gap);
|
||||||
|
printf(" (Expected: %lu samples/%lu frames, Actual: %lu samples/%lu frames)\n",
|
||||||
|
expected_cumulative_audio, expected_cumulative_video,
|
||||||
|
cumulative_audio_before, cumulative_video_before);
|
||||||
|
} else if (audio_gap > 0) {
|
||||||
|
printf(" ERROR CONCEALMENT: Inserting %zu silent samples\n", audio_gap);
|
||||||
|
printf(" (Expected: %lu samples, Actual: %lu samples)\n",
|
||||||
|
expected_cumulative_audio, cumulative_audio_before);
|
||||||
|
} else {
|
||||||
|
printf(" ERROR CONCEALMENT: Inserting %d frozen frames\n", video_gap);
|
||||||
|
printf(" (Expected: %lu frames, Actual: %lu frames)\n",
|
||||||
|
expected_cumulative_video, cumulative_video_before);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audio_gap > 0) {
|
||||||
|
write_silent_audio(dec, audio_gap);
|
||||||
|
}
|
||||||
|
if (video_gap > 0) {
|
||||||
|
write_frozen_frames(dec, video_gap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOW decode current packet (writes AFTER concealment)
|
||||||
// Read packet payload (contains both TAD and TAV subpackets)
|
// Read packet payload (contains both TAD and TAV subpackets)
|
||||||
uint8_t *packet_data = malloc(header.packet_size);
|
uint8_t *packet_data = malloc(header.packet_size);
|
||||||
if (!packet_data) return -1;
|
if (!packet_data) return -1;
|
||||||
@@ -1202,10 +1494,15 @@ static int process_packet(dt_decoder_t *dec) {
|
|||||||
}
|
}
|
||||||
dec->bytes_read += bytes_read;
|
dec->bytes_read += bytes_read;
|
||||||
|
|
||||||
|
// Decode audio and video
|
||||||
|
size_t audio_samples_written = 0;
|
||||||
|
int video_frames_written = 0;
|
||||||
|
|
||||||
// Process TAD subpacket (audio comes first, no type byte)
|
// Process TAD subpacket (audio comes first, no type byte)
|
||||||
size_t tad_consumed = 0;
|
size_t tad_consumed = 0;
|
||||||
if (header.offset_to_video > 0) {
|
if (header.offset_to_video > 0) {
|
||||||
decode_audio_subpacket(dec, packet_data, header.offset_to_video, &tad_consumed);
|
decode_audio_subpacket(dec, packet_data, header.offset_to_video,
|
||||||
|
&tad_consumed, &audio_samples_written);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process TAV subpacket (video comes after audio)
|
// Process TAV subpacket (video comes after audio)
|
||||||
@@ -1213,13 +1510,17 @@ static int process_packet(dt_decoder_t *dec) {
|
|||||||
size_t tav_consumed = 0;
|
size_t tav_consumed = 0;
|
||||||
if (dec->num_threads > 1) {
|
if (dec->num_threads > 1) {
|
||||||
decode_video_subpacket_mt(dec, packet_data + header.offset_to_video,
|
decode_video_subpacket_mt(dec, packet_data + header.offset_to_video,
|
||||||
header.packet_size - header.offset_to_video, &tav_consumed);
|
header.packet_size - header.offset_to_video,
|
||||||
|
&tav_consumed, &video_frames_written);
|
||||||
} else {
|
} else {
|
||||||
decode_video_subpacket(dec, packet_data + header.offset_to_video,
|
decode_video_subpacket(dec, packet_data + header.offset_to_video,
|
||||||
header.packet_size - header.offset_to_video, &tav_consumed);
|
header.packet_size - header.offset_to_video,
|
||||||
|
&tav_consumed, &video_frames_written);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update timecode tracking
|
||||||
|
dec->last_timecode_ns = header.timecode_ns;
|
||||||
dec->packets_processed++;
|
dec->packets_processed++;
|
||||||
|
|
||||||
if (!dec->verbose && dec->packets_processed % 10 == 0) {
|
if (!dec->verbose && dec->packets_processed % 10 == 0) {
|
||||||
@@ -1285,11 +1586,13 @@ static int run_decoder(dt_decoder_t *dec) {
|
|||||||
gop_decode_job_t *job = &dec->slots[found];
|
gop_decode_job_t *job = &dec->slots[found];
|
||||||
pthread_mutex_unlock(&dec->mutex);
|
pthread_mutex_unlock(&dec->mutex);
|
||||||
|
|
||||||
// Write frames
|
// Write frames and update freeze frame
|
||||||
if (job->decode_result == 0 && dec->video_temp_fp) {
|
if (job->decode_result == 0 && dec->video_temp_fp) {
|
||||||
for (int f = 0; f < job->gop_size; f++) {
|
for (int f = 0; f < job->gop_size; f++) {
|
||||||
fwrite(job->rgb_frames[f], 1, job->frame_size, dec->video_temp_fp);
|
fwrite(job->rgb_frames[f], 1, job->frame_size, dec->video_temp_fp);
|
||||||
|
update_freeze_frame(dec, job->rgb_frames[f], job->frame_size);
|
||||||
dec->frames_decoded++;
|
dec->frames_decoded++;
|
||||||
|
dec->video_frames_written++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1351,6 +1654,10 @@ static int run_decoder(dt_decoder_t *dec) {
|
|||||||
if (dec->input_fp) {
|
if (dec->input_fp) {
|
||||||
fclose(dec->input_fp);
|
fclose(dec->input_fp);
|
||||||
}
|
}
|
||||||
|
if (dec->freeze_frame) {
|
||||||
|
free(dec->freeze_frame);
|
||||||
|
dec->freeze_frame = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
// Remove temp files
|
// Remove temp files
|
||||||
unlink(dec->audio_temp_file);
|
unlink(dec->audio_temp_file);
|
||||||
|
|||||||
402
video_encoder/tavdt_noise_injector.c
Normal file
402
video_encoder/tavdt_noise_injector.c
Normal file
@@ -0,0 +1,402 @@
|
|||||||
|
// TAV-DT Noise Injector - Simulates satellite transmission channel noise
|
||||||
|
// Models QPSK over Ku-band satellite with AWGN and burst interference
|
||||||
|
// to compile: gcc -O2 -o tavdt_noise_injector tavdt_noise_injector.c -lm
|
||||||
|
// Created by CuriousTorvald and Claude on 2025-12-14
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
// Buffer size for streaming processing
|
||||||
|
#define BUFFER_SIZE (1024 * 1024) // 1 MB chunks
|
||||||
|
|
||||||
|
// Default TAV-DT bitrate for timing calculations (~2 Mbps)
|
||||||
|
#define DEFAULT_BITRATE_BPS 2000000.0
|
||||||
|
|
||||||
|
// Global bitrate (can be overridden by --bitrate)
|
||||||
|
static double g_bitrate_bps = DEFAULT_BITRATE_BPS;
|
||||||
|
|
||||||
|
// Burst noise parameters
|
||||||
|
#define BURST_LENGTH_MEAN 100.0
|
||||||
|
#define BURST_LENGTH_STDDEV 30.0
|
||||||
|
#define BURST_LENGTH_MIN 10
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// PRNG Functions (xorshift64)
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
static uint64_t xorshift64(uint64_t *state) {
|
||||||
|
uint64_t x = *state;
|
||||||
|
x ^= x << 13;
|
||||||
|
x ^= x >> 7;
|
||||||
|
x ^= x << 17;
|
||||||
|
return *state = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns uniform random in [0, 1)
|
||||||
|
static double rand_uniform(uint64_t *state) {
|
||||||
|
return (double)xorshift64(state) / (double)UINT64_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Box-Muller transform for Gaussian random numbers
|
||||||
|
static double gaussian_rand(uint64_t *state, double mean, double stddev) {
|
||||||
|
double u1 = rand_uniform(state);
|
||||||
|
double u2 = rand_uniform(state);
|
||||||
|
|
||||||
|
// Avoid log(0)
|
||||||
|
if (u1 < 1e-15) u1 = 1e-15;
|
||||||
|
|
||||||
|
double z = sqrt(-2.0 * log(u1)) * cos(2.0 * M_PI * u2);
|
||||||
|
return mean + stddev * z;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// BER Calculation
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
// Calculate BER from SNR in dB for QPSK modulation
|
||||||
|
// BER = 0.5 * erfc(sqrt(Eb/N0))
|
||||||
|
// For QPSK, Eb/N0 = SNR (2 bits per symbol)
|
||||||
|
static double snr_to_ber(double snr_db) {
|
||||||
|
double snr_linear = pow(10.0, snr_db / 10.0);
|
||||||
|
double eb_n0 = snr_linear;
|
||||||
|
return 0.5 * erfc(sqrt(eb_n0));
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// Burst State Management
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
double current_time_sec; // Elapsed playback time
|
||||||
|
double next_burst_time; // When next burst occurs
|
||||||
|
int burst_bytes_remaining; // Bytes left in current burst (0 = no active burst)
|
||||||
|
double burst_interval; // Mean interval between bursts (60.0 / bursts_per_minute)
|
||||||
|
double burst_ber; // BER during burst
|
||||||
|
int burst_count; // Total bursts applied
|
||||||
|
int total_burst_bytes; // Total bytes affected by bursts
|
||||||
|
int verbose; // Verbose output flag
|
||||||
|
} burst_state_t;
|
||||||
|
|
||||||
|
static void burst_state_init(burst_state_t *state, double bursts_per_minute,
|
||||||
|
double burst_ber, int verbose, uint64_t *seed) {
|
||||||
|
state->current_time_sec = 0.0;
|
||||||
|
state->burst_bytes_remaining = 0;
|
||||||
|
state->burst_ber = burst_ber;
|
||||||
|
state->burst_count = 0;
|
||||||
|
state->total_burst_bytes = 0;
|
||||||
|
state->verbose = verbose;
|
||||||
|
|
||||||
|
if (bursts_per_minute > 0) {
|
||||||
|
state->burst_interval = 60.0 / bursts_per_minute;
|
||||||
|
// Schedule first burst using exponential distribution
|
||||||
|
state->next_burst_time = -state->burst_interval * log(rand_uniform(seed));
|
||||||
|
} else {
|
||||||
|
state->burst_interval = 0;
|
||||||
|
state->next_burst_time = 1e30; // Never burst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void burst_state_advance_time(burst_state_t *state, double delta_sec, uint64_t *seed) {
|
||||||
|
double end_time = state->current_time_sec + delta_sec;
|
||||||
|
|
||||||
|
// Check if any bursts should occur during this time span
|
||||||
|
while (state->burst_interval > 0 && state->next_burst_time < end_time) {
|
||||||
|
// A burst should start during this chunk
|
||||||
|
if (state->burst_bytes_remaining == 0) {
|
||||||
|
double length = gaussian_rand(seed, BURST_LENGTH_MEAN, BURST_LENGTH_STDDEV);
|
||||||
|
state->burst_bytes_remaining = (int)fmax(BURST_LENGTH_MIN, length);
|
||||||
|
state->burst_count++;
|
||||||
|
|
||||||
|
if (state->verbose) {
|
||||||
|
fprintf(stderr, " [burst] time %.2fs, %d bytes\n",
|
||||||
|
state->next_burst_time, state->burst_bytes_remaining);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule next burst
|
||||||
|
double wait = -state->burst_interval * log(rand_uniform(seed));
|
||||||
|
if (wait < 0.001) wait = 0.001; // Minimum 1ms between bursts
|
||||||
|
state->next_burst_time += wait;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->current_time_sec = end_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// Noise Application Functions
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
// Apply AWGN-based bit errors to buffer
|
||||||
|
// Returns number of bits flipped
|
||||||
|
static int apply_background_noise(uint8_t *data, size_t len, double ber, uint64_t *seed) {
|
||||||
|
int bits_flipped = 0;
|
||||||
|
|
||||||
|
// Optimization: if BER is extremely low, use probability-based skipping
|
||||||
|
if (ber < 1e-10) {
|
||||||
|
return 0; // Effectively no errors at this BER
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
for (int bit = 0; bit < 8; bit++) {
|
||||||
|
if (rand_uniform(seed) < ber) {
|
||||||
|
data[i] ^= (1 << bit);
|
||||||
|
bits_flipped++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bits_flipped;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply burst noise to buffer (checks/updates burst state)
|
||||||
|
// Returns number of bits flipped
|
||||||
|
static int apply_burst_noise(uint8_t *data, size_t len, burst_state_t *state, uint64_t *seed) {
|
||||||
|
int bits_flipped = 0;
|
||||||
|
|
||||||
|
if (state->burst_bytes_remaining <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply burst BER to bytes while burst is active
|
||||||
|
size_t burst_bytes = (size_t)state->burst_bytes_remaining;
|
||||||
|
if (burst_bytes > len) {
|
||||||
|
burst_bytes = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < burst_bytes; i++) {
|
||||||
|
for (int bit = 0; bit < 8; bit++) {
|
||||||
|
if (rand_uniform(seed) < state->burst_ber) {
|
||||||
|
data[i] ^= (1 << bit);
|
||||||
|
bits_flipped++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state->total_burst_bytes += burst_bytes;
|
||||||
|
state->burst_bytes_remaining -= burst_bytes;
|
||||||
|
|
||||||
|
return bits_flipped;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// Byte Position to Time Conversion
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
// Convert byte position to approximate playback time based on bitrate
|
||||||
|
static double bytes_to_time(size_t byte_pos) {
|
||||||
|
return (double)(byte_pos * 8) / g_bitrate_bps;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// Main Program
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
static void print_usage(const char *prog) {
|
||||||
|
fprintf(stderr, "TAV-DT Noise Injector v1.0\n");
|
||||||
|
fprintf(stderr, "Simulates QPSK satellite transmission channel noise\n\n");
|
||||||
|
fprintf(stderr, "Usage: %s -i input.tavdt -o output.tavdt --snr N [options]\n\n", prog);
|
||||||
|
fprintf(stderr, "Required:\n");
|
||||||
|
fprintf(stderr, " -i, --input FILE Input TAV-DT file\n");
|
||||||
|
fprintf(stderr, " -o, --output FILE Output corrupted file\n");
|
||||||
|
fprintf(stderr, " --snr N Signal-to-noise ratio in dB (0-30)\n");
|
||||||
|
fprintf(stderr, "\nOptional:\n");
|
||||||
|
fprintf(stderr, " --burst N Burst events per minute (default: 0)\n");
|
||||||
|
fprintf(stderr, " --burst-ber N BER during burst events (default: 0.5)\n");
|
||||||
|
fprintf(stderr, " --bitrate N Stream bitrate in Mbps for timing (default: 2.0)\n");
|
||||||
|
fprintf(stderr, " --seed N RNG seed for reproducibility\n");
|
||||||
|
fprintf(stderr, " -v, --verbose Show detailed progress\n");
|
||||||
|
fprintf(stderr, " -h, --help Show this help\n");
|
||||||
|
fprintf(stderr, "\nSNR Reference:\n");
|
||||||
|
fprintf(stderr, " 0 dB: Worst case (BER ~7.9e-2, 1 in 13 bits)\n");
|
||||||
|
fprintf(stderr, " 6 dB: Poor but working (BER ~2.4e-3)\n");
|
||||||
|
fprintf(stderr, " 9 dB: Typical working (BER ~1.9e-4)\n");
|
||||||
|
fprintf(stderr, " 12 dB: Good condition (BER ~3.8e-6)\n");
|
||||||
|
fprintf(stderr, " 30 dB: Near-perfect (BER ~2.9e-16)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
const char *input_file = NULL;
|
||||||
|
const char *output_file = NULL;
|
||||||
|
double snr_db = -1;
|
||||||
|
double bursts_per_minute = 0;
|
||||||
|
double burst_ber = 0.5;
|
||||||
|
uint64_t seed = 0;
|
||||||
|
int seed_provided = 0;
|
||||||
|
int verbose = 0;
|
||||||
|
|
||||||
|
static struct option long_options[] = {
|
||||||
|
{"input", required_argument, 0, 'i'},
|
||||||
|
{"output", required_argument, 0, 'o'},
|
||||||
|
{"snr", required_argument, 0, 's'},
|
||||||
|
{"burst", required_argument, 0, 'b'},
|
||||||
|
{"burst-ber", required_argument, 0, 'B'},
|
||||||
|
{"bitrate", required_argument, 0, 'r'},
|
||||||
|
{"seed", required_argument, 0, 'S'},
|
||||||
|
{"verbose", no_argument, 0, 'v'},
|
||||||
|
{"help", no_argument, 0, 'h'},
|
||||||
|
{0, 0, 0, 0}
|
||||||
|
};
|
||||||
|
|
||||||
|
int opt;
|
||||||
|
while ((opt = getopt_long(argc, argv, "i:o:vh", long_options, NULL)) != -1) {
|
||||||
|
switch (opt) {
|
||||||
|
case 'i':
|
||||||
|
input_file = optarg;
|
||||||
|
break;
|
||||||
|
case 'o':
|
||||||
|
output_file = optarg;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
snr_db = atof(optarg);
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
bursts_per_minute = atof(optarg);
|
||||||
|
break;
|
||||||
|
case 'B':
|
||||||
|
burst_ber = atof(optarg);
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
g_bitrate_bps = atof(optarg) * 1000000.0; // Convert Mbps to bps
|
||||||
|
break;
|
||||||
|
case 'S':
|
||||||
|
seed = strtoull(optarg, NULL, 10);
|
||||||
|
seed_provided = 1;
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
verbose = 1;
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
default:
|
||||||
|
print_usage(argv[0]);
|
||||||
|
return opt == 'h' ? 0 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate arguments
|
||||||
|
if (!input_file || !output_file || snr_db < 0) {
|
||||||
|
fprintf(stderr, "Error: Missing required arguments\n\n");
|
||||||
|
print_usage(argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (burst_ber < 0 || burst_ber > 1) {
|
||||||
|
fprintf(stderr, "Error: --burst-ber must be between 0 and 1\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize RNG
|
||||||
|
if (!seed_provided) {
|
||||||
|
seed = (uint64_t)time(NULL) ^ ((uint64_t)clock() << 32);
|
||||||
|
}
|
||||||
|
// Ensure seed is not zero (xorshift64 requirement)
|
||||||
|
if (seed == 0) seed = 0x853c49e6748fea9bULL;
|
||||||
|
// Warm up the generator (small seeds produce poor initial values)
|
||||||
|
for (int i = 0; i < 10; i++) xorshift64(&seed);
|
||||||
|
|
||||||
|
// Calculate BER from SNR
|
||||||
|
double ber = snr_to_ber(snr_db);
|
||||||
|
|
||||||
|
// Open files
|
||||||
|
FILE *in_fp = fopen(input_file, "rb");
|
||||||
|
if (!in_fp) {
|
||||||
|
fprintf(stderr, "Error: Cannot open input file: %s\n", input_file);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *out_fp = fopen(output_file, "wb");
|
||||||
|
if (!out_fp) {
|
||||||
|
fprintf(stderr, "Error: Cannot open output file: %s\n", output_file);
|
||||||
|
fclose(in_fp);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print header info
|
||||||
|
fprintf(stderr, "TAV-DT Noise Injector v1.0\n");
|
||||||
|
fprintf(stderr, "Input: %s\n", input_file);
|
||||||
|
fprintf(stderr, "Output: %s\n", output_file);
|
||||||
|
fprintf(stderr, "SNR: %.1f dB (BER: %.2e)\n", snr_db, ber);
|
||||||
|
if (bursts_per_minute > 0) {
|
||||||
|
fprintf(stderr, "Burst: %.1f events/minute (burst BER: %.2f)\n",
|
||||||
|
bursts_per_minute, burst_ber);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Burst: disabled\n");
|
||||||
|
}
|
||||||
|
if (seed_provided) {
|
||||||
|
fprintf(stderr, "Seed: %llu\n", (unsigned long long)seed);
|
||||||
|
}
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
|
||||||
|
// Initialize burst state
|
||||||
|
burst_state_t burst;
|
||||||
|
burst_state_init(&burst, bursts_per_minute, burst_ber, verbose, &seed);
|
||||||
|
|
||||||
|
// Allocate buffer for streaming processing
|
||||||
|
uint8_t *buffer = malloc(BUFFER_SIZE);
|
||||||
|
if (!buffer) {
|
||||||
|
fprintf(stderr, "Error: Cannot allocate buffer\n");
|
||||||
|
fclose(in_fp);
|
||||||
|
fclose(out_fp);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Processing statistics
|
||||||
|
long long total_bytes = 0;
|
||||||
|
long long bits_flipped_bg = 0;
|
||||||
|
long long bits_flipped_burst = 0;
|
||||||
|
int chunk_count = 0;
|
||||||
|
|
||||||
|
// Process file in chunks
|
||||||
|
size_t bytes_read;
|
||||||
|
while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, in_fp)) > 0) {
|
||||||
|
// Calculate time delta for this chunk (for burst scheduling)
|
||||||
|
double delta_sec = bytes_to_time(bytes_read);
|
||||||
|
burst_state_advance_time(&burst, delta_sec, &seed);
|
||||||
|
|
||||||
|
// Apply noise to chunk
|
||||||
|
bits_flipped_bg += apply_background_noise(buffer, bytes_read, ber, &seed);
|
||||||
|
bits_flipped_burst += apply_burst_noise(buffer, bytes_read, &burst, &seed);
|
||||||
|
|
||||||
|
// Write corrupted chunk
|
||||||
|
fwrite(buffer, 1, bytes_read, out_fp);
|
||||||
|
|
||||||
|
total_bytes += bytes_read;
|
||||||
|
chunk_count++;
|
||||||
|
|
||||||
|
if (verbose && chunk_count % 10 == 0) {
|
||||||
|
double time_pos = bytes_to_time(total_bytes);
|
||||||
|
fprintf(stderr, "\rProcessed %.1f MB (%.1f sec)...",
|
||||||
|
total_bytes / (1024.0 * 1024.0), time_pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
fprintf(stderr, "\r \r");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
free(buffer);
|
||||||
|
fclose(in_fp);
|
||||||
|
fclose(out_fp);
|
||||||
|
|
||||||
|
// Print summary
|
||||||
|
double duration_sec = bytes_to_time(total_bytes);
|
||||||
|
long long total_bits = total_bytes * 8;
|
||||||
|
|
||||||
|
fprintf(stderr, "Complete.\n");
|
||||||
|
fprintf(stderr, " Total bytes: %lld (%.1f sec @ ~%.1f Mbps)\n",
|
||||||
|
total_bytes, duration_sec, g_bitrate_bps / 1000000.0);
|
||||||
|
fprintf(stderr, " Background bits flipped: %lld (%.4f%%)\n",
|
||||||
|
bits_flipped_bg, 100.0 * bits_flipped_bg / total_bits);
|
||||||
|
if (bursts_per_minute > 0) {
|
||||||
|
fprintf(stderr, " Burst events: %d (%d bytes total)\n",
|
||||||
|
burst.burst_count, burst.total_burst_bytes);
|
||||||
|
fprintf(stderr, " Burst bits flipped: %lld\n", bits_flipped_burst);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user