TAV: TAD encoding

This commit is contained in:
minjaesong
2025-10-24 10:30:58 +09:00
parent cd88885fbf
commit 3adc50365b
7 changed files with 562 additions and 81 deletions

View File

@@ -1342,8 +1342,7 @@ try {
} }
else if (packetType === TAV_PACKET_AUDIO_TAD) { else if (packetType === TAV_PACKET_AUDIO_TAD) {
// Legacy MP2 Audio packet (for backwards compatibility) let payloadLen = seqread.readInt() // compressed size + 6
let payloadLen = seqread.readInt()
if (!tadInitialised) { if (!tadInitialised) {
tadInitialised = true tadInitialised = true

View File

@@ -1068,7 +1068,7 @@ transmission capability, and region-of-interest coding.
## TAD Packet Structure ## TAD Packet Structure
uint8 Packet type (0x24) uint8 Packet type (0x24)
uint32 Compressed Size + 2 uint32 Compressed Size + 6
uint16 Sample Count uint16 Sample Count
uint32 Compressed Size uint32 Compressed Size
* Zstd-compressed TAD * Zstd-compressed TAD
@@ -1781,6 +1781,8 @@ Memory Space
114688..131071 RW: Instrument bin (256 instruments, 64 bytes each) 114688..131071 RW: Instrument bin (256 instruments, 64 bytes each)
131072..196607 RW: Play data 1 131072..196607 RW: Play data 1
196608..262143 RW: Play data 2 196608..262143 RW: Play data 2
262144..327679 RW: TAD Input Buffer
327680..393215 RW: TAD Decode Output
Sample bin: just raw sample data thrown in there. You need to keep track of starting point for each sample Sample bin: just raw sample data thrown in there. You need to keep track of starting point for each sample
@@ -1823,7 +1825,7 @@ Sound Adapter MMIO
... auto-fill to Play head #4 ... auto-fill to Play head #4
40 WO: Media Decoder Control 40 WO: MP2 Decoder Control
Write 16 to initialise the MP2 context (call this before the decoding of NEW music) Write 16 to initialise the MP2 context (call this before the decoding of NEW music)
Write 1 to decode the frame as MP2 Write 1 to decode the frame as MP2
@@ -1832,6 +1834,11 @@ Sound Adapter MMIO
41 RO: Media Decoder Status 41 RO: Media Decoder Status
Non-zero value indicates the decoder is busy Non-zero value indicates the decoder is busy
42 WO: TAD Decoder Control
Write 1 to decode TAD data
43 RW: TAD Quality
Must be set to appropriate value before decoding
64..2367 RW: MP2 Decoded Samples (unsigned 8-bit stereo) 64..2367 RW: MP2 Decoded Samples (unsigned 8-bit stereo)
2368..4095 RW: MP2 Frame to be decoded 2368..4095 RW: MP2 Frame to be decoded
4096..4097 RO: MP2 Frame guard bytes; always return 0 on read 4096..4097 RO: MP2 Frame guard bytes; always return 0 on read

View File

@@ -12,7 +12,7 @@ OPENCV_CFLAGS = $(shell pkg-config --cflags opencv4)
OPENCV_LIBS = $(shell pkg-config --libs opencv4) OPENCV_LIBS = $(shell pkg-config --libs opencv4)
# Source files and targets # Source files and targets
TARGETS = tev tav tav_decoder TARGETS = tev tav tav_decoder tav_inspector
TAD_TARGETS = encoder_tad decoder_tad TAD_TARGETS = encoder_tad decoder_tad
TEST_TARGETS = test_mesh_warp test_mesh_roundtrip TEST_TARGETS = test_mesh_warp test_mesh_roundtrip
@@ -35,6 +35,10 @@ tav_decoder: decoder_tav.c
rm -f decoder_tav rm -f decoder_tav
$(CC) $(CFLAGS) -o decoder_tav $< $(LIBS) $(CC) $(CFLAGS) -o decoder_tav $< $(LIBS)
tav_inspector: tav_inspector.c
rm -f tav_inspector
$(CC) $(CFLAGS) -o tav_inspector $< $(LIBS)
# Build TAD (Terrarum Advanced Audio) tools # Build TAD (Terrarum Advanced Audio) tools
encoder_tad: encoder_tad_standalone.c encoder_tad.c encoder_tad.h encoder_tad: encoder_tad_standalone.c encoder_tad.c encoder_tad.h
rm -f encoder_tad encoder_tad_standalone.o encoder_tad.o rm -f encoder_tad encoder_tad_standalone.o encoder_tad.o

View File

@@ -146,7 +146,7 @@ static void get_quantization_weights(int quality, int dwt_levels, float *weights
/*15*/{0.2f, 0.2f, 0.8f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.25f, 1.5f, 1.5f} /*15*/{0.2f, 0.2f, 0.8f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.25f, 1.5f, 1.5f}
}; };
float quality_scale = 4.0f * (1.0f + FCLAMP((3 - quality) * 0.5f, 0.0f, 1000.0f)); float quality_scale = 4.0f * (1.0f + FCLAMP((4 - quality) * 0.5f, 0.0f, 1000.0f));
for (int i = 0; i < dwt_levels; i++) { for (int i = 0; i < dwt_levels; i++) {
weights[i] = base_weights[dwt_levels][i] * quality_scale; weights[i] = base_weights[dwt_levels][i] * quality_scale;
@@ -154,7 +154,7 @@ static void get_quantization_weights(int quality, int dwt_levels, float *weights
} }
static int get_deadzone_threshold(int quality) { static int get_deadzone_threshold(int quality) {
const int thresholds[] = {1,1,1,1,1,1}; // Q0 to Q5 const int thresholds[] = {0,0,0,0,0,0}; // Q0 to Q5
return thresholds[quality]; return thresholds[quality];
} }

View File

@@ -0,0 +1,294 @@
// Created by CuriousTorvald and Claude on 2025-10-24.
// TAD32 (Terrarum Advanced Audio - PCM16 version) Encoder - Standalone program
// Alternative version: PCM16 throughout encoding, PCM8 conversion only at decoder
// Uses encoder_tad32.c library for encoding functions
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <getopt.h>
#include <time.h>
#include "encoder_tad.h"
#define ENCODER_VENDOR_STRING "Encoder-TAD32 (PCM32f version) 20251024"
// TAD32 format constants
#define TAD32_DEFAULT_CHUNK_SIZE 32768 // Default: power of 2 for optimal performance (2^15)
// Temporary file for FFmpeg PCM extraction
char TEMP_PCM_FILE[42];
static void generate_random_filename(char *filename) {
srand(time(NULL));
const char charset[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const int charset_size = sizeof(charset) - 1;
// Start with the prefix
strcpy(filename, "/tmp/");
// Generate 32 random characters
for (int i = 0; i < 32; i++) {
filename[5 + i] = charset[rand() % charset_size];
}
// Add the extension
strcpy(filename + 37, ".tad");
filename[41] = '\0'; // Null terminate
}
//=============================================================================
// Main Encoder
//=============================================================================
static void print_usage(const char *prog_name) {
printf("Usage: %s -i <input> -o <output> [options]\n", prog_name);
printf("Options:\n");
printf(" -i <file> Input audio file (any format supported by FFmpeg)\n");
printf(" -o <file> Output TAD32 file\n");
printf(" -q <0-5> Quality level (default: %d, higher = better quality)\n", TAD32_QUALITY_DEFAULT);
printf(" --no-zstd Disable Zstd compression\n");
printf(" -v Verbose output\n");
printf(" -h, --help Show this help\n");
printf("\nVersion: %s\n", ENCODER_VENDOR_STRING);
printf("Note: This is the PCM16 alternative version for comparison testing.\n");
printf(" PCM16 is processed throughout encoding; PCM8 conversion happens at decoder.\n");
}
int main(int argc, char *argv[]) {
generate_random_filename(TEMP_PCM_FILE);
char *input_file = NULL;
char *output_file = NULL;
int quality = TAD32_QUALITY_DEFAULT;
int use_zstd = 1;
int verbose = 0;
// Parse command line arguments
static struct option long_options[] = {
{"no-zstd", no_argument, 0, 'z'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
int opt;
int option_index = 0;
while ((opt = getopt_long(argc, argv, "i:o:q:vh", long_options, &option_index)) != -1) {
switch (opt) {
case 'i':
input_file = optarg;
break;
case 'o':
output_file = optarg;
break;
case 'q':
quality = atoi(optarg);
if (quality < TAD32_QUALITY_MIN || quality > TAD32_QUALITY_MAX) {
fprintf(stderr, "Error: Quality must be between %d and %d\n",
TAD32_QUALITY_MIN, TAD32_QUALITY_MAX);
return 1;
}
break;
case 'z':
use_zstd = 0;
break;
case 'v':
verbose = 1;
break;
case 'h':
print_usage(argv[0]);
return 0;
default:
print_usage(argv[0]);
return 1;
}
}
if (!input_file || !output_file) {
fprintf(stderr, "Error: Input and output files are required\n");
print_usage(argv[0]);
return 1;
}
if (verbose) {
printf("%s\n", ENCODER_VENDOR_STRING);
printf("Input: %s\n", input_file);
printf("Output: %s\n", output_file);
printf("Quality: %d\n", quality);
printf("Significance map: 2-bit\n");
printf("Zstd compression: %s\n", use_zstd ? "enabled" : "disabled");
}
// Detect original sample rate for high-quality resampling
char sample_rate_str[32] = "48000"; // Default fallback
char detect_cmd[2048];
snprintf(detect_cmd, sizeof(detect_cmd),
"ffprobe -v error -select_streams a:0 -show_entries stream=sample_rate "
"-of default=noprint_wrappers=1:nokey=1 \"%s\" 2>/dev/null",
input_file);
FILE *probe = popen(detect_cmd, "r");
if (probe) {
if (fgets(sample_rate_str, sizeof(sample_rate_str), probe)) {
// Remove newline
sample_rate_str[strcspn(sample_rate_str, "\n")] = 0;
}
pclose(probe);
}
int original_rate = atoi(sample_rate_str);
if (original_rate <= 0 || original_rate > 192000) {
original_rate = 48000; // Fallback
}
if (verbose) {
printf("Detected original sample rate: %d Hz\n", original_rate);
printf("Extracting and resampling audio to %d Hz...\n", TAD32_SAMPLE_RATE);
}
// Extract and resample in two passes for better quality
// Pass 1: Extract at original sample rate
char temp_original_pcm[256];
snprintf(temp_original_pcm, sizeof(temp_original_pcm), "%s.orig", TEMP_PCM_FILE);
char ffmpeg_cmd[2048];
snprintf(ffmpeg_cmd, sizeof(ffmpeg_cmd),
"ffmpeg -hide_banner -v error -i \"%s\" -f f32le -acodec pcm_f32le -ac %d -y \"%s\" 2>&1",
input_file, TAD32_CHANNELS, temp_original_pcm);
int result = system(ffmpeg_cmd);
if (result != 0) {
fprintf(stderr, "Error: FFmpeg extraction failed\n");
return 1;
}
// Pass 2: Resample to 32kHz with high-quality SoXR resampler and highpass filter
snprintf(ffmpeg_cmd, sizeof(ffmpeg_cmd),
"ffmpeg -hide_banner -v error -f f32le -ar %d -ac %d -i \"%s\" "
"-f f32le -acodec pcm_f32le -ar %d -ac %d "
"-af \"aresample=resampler=soxr:precision=28:cutoff=0.99:dither_scale=0,highpass=f=16\" "
"-y \"%s\" 2>&1",
original_rate, TAD32_CHANNELS, temp_original_pcm, TAD32_SAMPLE_RATE, TAD32_CHANNELS, TEMP_PCM_FILE);
result = system(ffmpeg_cmd);
remove(temp_original_pcm); // Clean up intermediate file
if (result != 0) {
fprintf(stderr, "Error: FFmpeg resampling failed\n");
return 1;
}
// Open PCM file
FILE *pcm_file = fopen(TEMP_PCM_FILE, "rb");
if (!pcm_file) {
fprintf(stderr, "Error: Could not open temporary PCM file\n");
return 1;
}
// Get file size
fseek(pcm_file, 0, SEEK_END);
size_t pcm_size = ftell(pcm_file);
fseek(pcm_file, 0, SEEK_SET);
size_t total_samples = pcm_size / (TAD32_CHANNELS * sizeof(float));
// Pad to even sample count
if (total_samples % 2 == 1) {
total_samples++;
if (verbose) {
printf("Odd sample count detected, padding with one zero sample\n");
}
}
size_t num_chunks = (total_samples + TAD32_DEFAULT_CHUNK_SIZE - 1) / TAD32_DEFAULT_CHUNK_SIZE;
if (verbose) {
printf("Total samples: %zu (%.2f seconds)\n", total_samples,
(double)total_samples / TAD32_SAMPLE_RATE);
printf("Chunks: %zu (chunk size: %d samples)\n", num_chunks, TAD32_DEFAULT_CHUNK_SIZE);
}
// Open output file
FILE *output = fopen(output_file, "wb");
if (!output) {
fprintf(stderr, "Error: Could not open output file\n");
fclose(pcm_file);
return 1;
}
// Process chunks using linked TAD32 encoder library
size_t total_output_size = 0;
float *chunk_buffer = malloc(TAD32_DEFAULT_CHUNK_SIZE * TAD32_CHANNELS * sizeof(float));
uint8_t *output_buffer = malloc(TAD32_DEFAULT_CHUNK_SIZE * 4 * sizeof(float)); // Generous buffer
for (size_t chunk_idx = 0; chunk_idx < num_chunks; chunk_idx++) {
size_t chunk_samples = TAD32_DEFAULT_CHUNK_SIZE;
size_t remaining = total_samples - (chunk_idx * TAD32_DEFAULT_CHUNK_SIZE);
if (remaining < TAD32_DEFAULT_CHUNK_SIZE) {
chunk_samples = remaining;
}
// Read chunk
size_t samples_read = fread(chunk_buffer, TAD32_CHANNELS * sizeof(float),
chunk_samples, pcm_file);
(void)samples_read; // Unused, but kept for compatibility
// Pad with zeros if necessary
if (chunk_samples < TAD32_DEFAULT_CHUNK_SIZE) {
memset(&chunk_buffer[chunk_samples * TAD32_CHANNELS], 0,
(TAD32_DEFAULT_CHUNK_SIZE - chunk_samples) * TAD32_CHANNELS * sizeof(float));
}
// Encode chunk using linked tad32_encode_chunk() from encoder_tad32.c
size_t encoded_size = tad32_encode_chunk(chunk_buffer, TAD32_DEFAULT_CHUNK_SIZE, quality,
use_zstd, output_buffer);
if (encoded_size == 0) {
fprintf(stderr, "Error: Chunk encoding failed at chunk %zu\n", chunk_idx);
free(chunk_buffer);
free(output_buffer);
fclose(pcm_file);
fclose(output);
return 1;
}
// Write chunk to output
fwrite(output_buffer, 1, encoded_size, output);
total_output_size += encoded_size;
if (verbose && (chunk_idx % 10 == 0 || chunk_idx == num_chunks - 1)) {
printf("Processed chunk %zu/%zu (%.1f%%)\r", chunk_idx + 1, num_chunks,
(chunk_idx + 1) * 100.0 / num_chunks);
fflush(stdout);
}
}
if (verbose) {
printf("\n");
}
// Cleanup
free(chunk_buffer);
free(output_buffer);
fclose(pcm_file);
fclose(output);
remove(TEMP_PCM_FILE);
// Print statistics
size_t pcmu8_size = total_samples * TAD32_CHANNELS; // PCMu8 baseline
float compression_ratio = (float)pcmu8_size / total_output_size;
printf("Encoding complete!\n");
printf("PCMu8 size: %zu bytes\n", pcmu8_size);
printf("TAD32 size: %zu bytes\n", total_output_size);
printf("Compression ratio: %.2f:1 (%.1f%% of PCMu8)\n",
compression_ratio, (total_output_size * 100.0) / pcmu8_size);
if (compression_ratio < 1.8) {
printf("Warning: Compression ratio below 2:1 target. Try higher quality or different settings.\n");
}
return 0;
}

View File

@@ -65,13 +65,14 @@
#define TAV_PACKET_SYNC 0xFF // Sync packet #define TAV_PACKET_SYNC 0xFF // Sync packet
// TAD (Terrarum Advanced Audio) settings // TAD (Terrarum Advanced Audio) settings
#define TAD_MIN_CHUNK_SIZE 1024 // Minimum: 1024 samples (supports non-power-of-2) // TAD32 constants (updated to match Float32 version)
#define TAD_SAMPLE_RATE 32000 #define TAD32_MIN_CHUNK_SIZE 1024 // Minimum: 1024 samples
#define TAD_CHANNELS 2 // Stereo #define TAD32_SAMPLE_RATE 32000
#define TAD_SIGMAP_2BIT 1 // 2-bit: 00=0, 01=+1, 10=-1, 11=other #define TAD32_CHANNELS 2 // Stereo
#define TAD_QUALITY_MIN 0 #define TAD32_SIGMAP_2BIT 1 // 2-bit: 00=0, 01=+1, 10=-1, 11=other
#define TAD_QUALITY_MAX 5 #define TAD32_QUALITY_MIN 0
#define TAD_ZSTD_LEVEL 7 #define TAD32_QUALITY_MAX 5
#define TAD32_ZSTD_LEVEL 7
// DWT settings // DWT settings
#define TILE_SIZE_X 640 #define TILE_SIZE_X 640
@@ -1711,7 +1712,7 @@ typedef struct tav_encoder_s {
FILE *output_fp; FILE *output_fp;
FILE *mp2_file; FILE *mp2_file;
FILE *ffmpeg_video_pipe; FILE *ffmpeg_video_pipe;
FILE *pcm_file; // PCM16LE audio file for PCM8 mode FILE *pcm_file; // Float32LE audio file for PCM8/TAD32 mode
// Video parameters // Video parameters
int width, height; int width, height;
@@ -1869,9 +1870,9 @@ typedef struct tav_encoder_s {
// PCM8 audio processing // PCM8 audio processing
int samples_per_frame; // Number of stereo samples per video frame int samples_per_frame; // Number of stereo samples per video frame
int16_t *pcm16_buffer; // Buffer for reading PCM16LE data float *pcm32_buffer; // Buffer for reading Float32LE data
uint8_t *pcm8_buffer; // Buffer for converted PCM8 data uint8_t *pcm8_buffer; // Buffer for converted PCM8 data
int16_t dither_error[2]; // Dithering error for stereo channels [L, R] float dither_error[2][2]; // 2nd-order noise shaping error: [channel][history]
// Subtitle processing // Subtitle processing
subtitle_entry_t *subtitles; subtitle_entry_t *subtitles;
@@ -8064,14 +8065,14 @@ static int start_audio_conversion(tav_encoder_t *enc) {
char command[2048]; char command[2048];
if (enc->pcm8_audio || enc->tad_audio) { if (enc->pcm8_audio || enc->tad_audio) {
// Extract PCM16LE for PCM8/TAD mode // Extract Float32LE for PCM8/TAD32 mode
if (enc->pcm8_audio) { if (enc->pcm8_audio) {
printf(" Audio format: PCM16LE 32kHz stereo (will be converted to 8-bit PCM)\n"); printf(" Audio format: Float32LE 32kHz stereo (will be converted to 8-bit PCM)\n");
} else { } else {
printf(" Audio format: PCM16LE 32kHz stereo (will be encoded with TAD codec)\n"); printf(" Audio format: Float32LE 32kHz stereo (will be encoded with TAD32 codec)\n");
} }
snprintf(command, sizeof(command), snprintf(command, sizeof(command),
"ffmpeg -v quiet -i \"%s\" -f s16le -acodec pcm_s16le -ar %d -ac 2 -af \"aresample=resampler=soxr:precision=28:cutoff=0.99:dither_scale=0,highpass=f=16\" -y \"%s\" 2>/dev/null", "ffmpeg -v quiet -i \"%s\" -f f32le -acodec pcm_f32le -ar %d -ac 2 -af \"aresample=resampler=soxr:precision=28:cutoff=0.99:dither_scale=0,highpass=f=16\" -y \"%s\" 2>/dev/null",
enc->input_file, TSVM_AUDIO_SAMPLE_RATE, TEMP_PCM_FILE); enc->input_file, TSVM_AUDIO_SAMPLE_RATE, TEMP_PCM_FILE);
int result = system(command); int result = system(command);
@@ -8085,9 +8086,11 @@ static int start_audio_conversion(tav_encoder_t *enc) {
// Calculate samples per frame: ceil(sample_rate / fps) // Calculate samples per frame: ceil(sample_rate / fps)
enc->samples_per_frame = (TSVM_AUDIO_SAMPLE_RATE + enc->output_fps - 1) / enc->output_fps; enc->samples_per_frame = (TSVM_AUDIO_SAMPLE_RATE + enc->output_fps - 1) / enc->output_fps;
// Initialize dithering error // Initialize 2nd-order noise shaping error history
enc->dither_error[0] = 0; enc->dither_error[0][0] = 0.0f;
enc->dither_error[1] = 0; enc->dither_error[0][1] = 0.0f;
enc->dither_error[1][0] = 0.0f;
enc->dither_error[1][1] = 0.0f;
if (enc->verbose) { if (enc->verbose) {
printf(" PCM8: %d samples per frame\n", enc->samples_per_frame); printf(" PCM8: %d samples per frame\n", enc->samples_per_frame);
@@ -8741,32 +8744,62 @@ static long write_extended_header(tav_encoder_t *enc) {
return endt_offset + 4 + 1; // 4 bytes for "ENDT", 1 byte for type return endt_offset + 4 + 1; // 4 bytes for "ENDT", 1 byte for type
} }
// Convert PCM16LE to unsigned 8-bit PCM with error-diffusion dithering // Uniform random in [0, 1) for TPDF dithering
static void convert_pcm16_to_pcm8_dithered(tav_encoder_t *enc, const int16_t *pcm16, uint8_t *pcm8, int num_samples) { static inline float frand01(void) {
return (float)rand() / ((float)RAND_MAX + 1.0f);
}
// TPDF (Triangular Probability Density Function) noise in [-1, +1)
static inline float tpdf1(void) {
return (frand01() - frand01());
}
// Convert Float32LE to unsigned 8-bit PCM with 2nd-order noise-shaped dithering
// Matches decoder_tad.c dithering algorithm for optimal quality
static void convert_pcm32_to_pcm8_dithered(tav_encoder_t *enc, const float *pcm32, uint8_t *pcm8, int num_samples) {
const float b1 = 1.5f; // 1st feedback coefficient
const float b2 = -0.75f; // 2nd feedback coefficient
const float scale = 127.5f;
const float bias = 128.0f;
for (int i = 0; i < num_samples; i++) { for (int i = 0; i < num_samples; i++) {
for (int ch = 0; ch < 2; ch++) { // Stereo: L and R for (int ch = 0; ch < 2; ch++) { // Stereo: L and R
int idx = i * 2 + ch; int idx = i * 2 + ch;
// Convert signed 16-bit [-32768, 32767] to unsigned 8-bit [0, 255] // Input float in range [-1.0, 1.0]
// First scale to [0, 65535], then add dithering error float sample = pcm32[idx];
int32_t sample = (int32_t)pcm16[idx] + 32768; // Now in [0, 65535]
// Add accumulated dithering error // Clamp to valid range
sample += enc->dither_error[ch]; if (sample < -1.0f) sample = -1.0f;
if (sample > 1.0f) sample = 1.0f;
// Quantize to 8-bit (divide by 256) // Apply 2nd-order noise shaping feedback
int32_t quantized = sample >> 8; float feedback = b1 * enc->dither_error[ch][0] + b2 * enc->dither_error[ch][1];
// Clamp to [0, 255] // Add TPDF dither (±0.5 LSB)
if (quantized < 0) quantized = 0; float dither = 0.5f * tpdf1();
if (quantized > 255) quantized = 255;
// Store 8-bit value // Shaped signal
pcm8[idx] = (uint8_t)quantized; float shaped = sample + feedback + dither / scale;
// Calculate quantization error for next sample (error diffusion) // Clamp shaped signal
// Error = original - (quantized * 256) if (shaped < -1.0f) shaped = -1.0f;
enc->dither_error[ch] = sample - (quantized << 8); if (shaped > 1.0f) shaped = 1.0f;
// Quantize to signed 8-bit range [-128, 127]
int q = (int)lrintf(shaped * scale);
if (q < -128) q = -128;
else if (q > 127) q = 127;
// Convert to unsigned 8-bit [0, 255]
pcm8[idx] = (uint8_t)(q + (int)bias);
// Calculate quantization error for feedback
float qerr = shaped - (float)q / scale;
// Update error history (shift and store)
enc->dither_error[ch][1] = enc->dither_error[ch][0];
enc->dither_error[ch][0] = qerr;
} }
} }
} }
@@ -8824,56 +8857,57 @@ static int write_separate_audio_track(tav_encoder_t *enc, FILE *output) {
} }
// Write TAD audio packet (0x24) with specified sample count // Write TAD audio packet (0x24) with specified sample count
// Uses linked TAD encoder (encoder_tad.c) // Uses linked TAD32 encoder (encoder_tad.c) - Float32 version
static int write_tad_packet_samples(tav_encoder_t *enc, FILE *output, int samples_to_read) { static int write_tad_packet_samples(tav_encoder_t *enc, FILE *output, int samples_to_read) {
if (!enc->pcm_file || enc->audio_remaining <= 0 || samples_to_read <= 0) { if (!enc->pcm_file || enc->audio_remaining <= 0 || samples_to_read <= 0) {
return 0; return 0;
} }
size_t bytes_to_read = samples_to_read * 2 * sizeof(int16_t); // Stereo PCM16LE size_t bytes_to_read = samples_to_read * 2 * sizeof(float); // Stereo Float32LE
// Don't read more than what's available // Don't read more than what's available
if (bytes_to_read > enc->audio_remaining) { if (bytes_to_read > enc->audio_remaining) {
bytes_to_read = enc->audio_remaining; bytes_to_read = enc->audio_remaining;
samples_to_read = bytes_to_read / (2 * sizeof(int16_t)); samples_to_read = bytes_to_read / (2 * sizeof(float));
} }
if (samples_to_read < TAD_MIN_CHUNK_SIZE) { if (samples_to_read < TAD32_MIN_CHUNK_SIZE) {
// Pad to minimum size // Pad to minimum size
samples_to_read = TAD_MIN_CHUNK_SIZE; samples_to_read = TAD32_MIN_CHUNK_SIZE;
} }
// Allocate PCM16 input buffer // Allocate Float32 input buffer
int16_t *pcm16_buffer = malloc(samples_to_read * 2 * sizeof(int16_t)); float *pcm32_buffer = malloc(samples_to_read * 2 * sizeof(float));
// Read PCM16LE data // Read Float32LE data
size_t bytes_read = fread(pcm16_buffer, 1, bytes_to_read, enc->pcm_file); size_t bytes_read = fread(pcm32_buffer, 1, bytes_to_read, enc->pcm_file);
if (bytes_read == 0) { if (bytes_read == 0) {
free(pcm16_buffer); free(pcm32_buffer);
return 0; return 0;
} }
int samples_read = bytes_read / (2 * sizeof(int16_t)); int samples_read = bytes_read / (2 * sizeof(float));
// Zero-pad if needed // Zero-pad if needed
if (samples_read < samples_to_read) { if (samples_read < samples_to_read) {
memset(&pcm16_buffer[samples_read * 2], 0, memset(&pcm32_buffer[samples_read * 2], 0,
(samples_to_read - samples_read) * 2 * sizeof(int16_t)); (samples_to_read - samples_read) * 2 * sizeof(float));
} }
// Encode with TAD encoder (linked from encoder_tad.o) // Encode with TAD32 encoder (linked from encoder_tad.o)
// Input is already Float32LE in range [-1.0, 1.0] from FFmpeg
int tad_quality = enc->quality_level; // Use video quality level for audio int tad_quality = enc->quality_level; // Use video quality level for audio
if (tad_quality > TAD_QUALITY_MAX) tad_quality = TAD_QUALITY_MAX; if (tad_quality > TAD32_QUALITY_MAX) tad_quality = TAD32_QUALITY_MAX;
if (tad_quality < TAD_QUALITY_MIN) tad_quality = TAD_QUALITY_MIN; if (tad_quality < TAD32_QUALITY_MIN) tad_quality = TAD32_QUALITY_MIN;
// Allocate output buffer (generous size for TAD chunk) // Allocate output buffer (generous size for TAD chunk)
size_t max_output_size = samples_to_read * 4 * sizeof(int16_t) + 1024; size_t max_output_size = samples_to_read * 4 * sizeof(int16_t) + 1024;
uint8_t *tad_output = malloc(max_output_size); uint8_t *tad_output = malloc(max_output_size);
size_t tad_encoded_size = tad_encode_chunk(pcm16_buffer, samples_to_read, tad_quality, 1, tad_output); size_t tad_encoded_size = tad32_encode_chunk(pcm32_buffer, samples_to_read, tad_quality, 1, tad_output);
if (tad_encoded_size == 0) { if (tad_encoded_size == 0) {
fprintf(stderr, "Error: TAD encoding failed\n"); fprintf(stderr, "Error: TAD32 encoding failed\n");
free(pcm16_buffer); free(pcm32_buffer);
free(tad_output); free(tad_output);
return 0; return 0;
} }
@@ -8891,8 +8925,8 @@ static int write_tad_packet_samples(tav_encoder_t *enc, FILE *output, int sample
fwrite(&packet_type, 1, 1, output); fwrite(&packet_type, 1, 1, output);
uint32_t tav_payload_size = (uint32_t)tad_payload_size; uint32_t tav_payload_size = (uint32_t)tad_payload_size;
uint32_t tav_payload_size_plus_two = (uint32_t)tad_payload_size + 2; uint32_t tav_payload_size_plus_6 = (uint32_t)tad_payload_size + 6;
fwrite(&tav_payload_size_plus_two, sizeof(uint32_t), 1, output); fwrite(&tav_payload_size_plus_6, sizeof(uint32_t), 1, output);
fwrite(&sample_count, sizeof(uint16_t), 1, output); fwrite(&sample_count, sizeof(uint16_t), 1, output);
fwrite(&tav_payload_size, sizeof(uint32_t), 1, output); fwrite(&tav_payload_size, sizeof(uint32_t), 1, output);
fwrite(tad_payload, 1, tad_payload_size, output); fwrite(tad_payload, 1, tad_payload_size, output);
@@ -8901,12 +8935,12 @@ static int write_tad_packet_samples(tav_encoder_t *enc, FILE *output, int sample
enc->audio_remaining -= bytes_read; enc->audio_remaining -= bytes_read;
if (enc->verbose) { if (enc->verbose) {
printf("TAD packet: %d samples, %u bytes compressed (Q%d)\n", printf("TAD32 packet: %d samples, %u bytes compressed (Q%d)\n",
sample_count, tad_payload_size, tad_quality); sample_count, tad_payload_size, tad_quality);
} }
// Cleanup // Cleanup
free(pcm16_buffer); free(pcm32_buffer);
free(tad_output); free(tad_output);
return 1; return 1;
@@ -8917,12 +8951,12 @@ static int write_pcm8_packet_samples(tav_encoder_t *enc, FILE *output, int sampl
if (!enc->pcm_file || enc->audio_remaining <= 0 || samples_to_read <= 0) { if (!enc->pcm_file || enc->audio_remaining <= 0 || samples_to_read <= 0) {
return 0; return 0;
} }
size_t bytes_to_read = samples_to_read * 2 * sizeof(int16_t); // Stereo PCM16LE size_t bytes_to_read = samples_to_read * 2 * sizeof(float); // Stereo Float32LE
// Don't read more than what's available // Don't read more than what's available
if (bytes_to_read > enc->audio_remaining) { if (bytes_to_read > enc->audio_remaining) {
bytes_to_read = enc->audio_remaining; bytes_to_read = enc->audio_remaining;
samples_to_read = bytes_to_read / (2 * sizeof(int16_t)); samples_to_read = bytes_to_read / (2 * sizeof(float));
} }
if (samples_to_read == 0) { if (samples_to_read == 0) {
@@ -8931,23 +8965,23 @@ static int write_pcm8_packet_samples(tav_encoder_t *enc, FILE *output, int sampl
// Allocate buffers if needed (size for max samples: 32768) // Allocate buffers if needed (size for max samples: 32768)
int max_samples = 32768; // Maximum samples per packet int max_samples = 32768; // Maximum samples per packet
if (!enc->pcm16_buffer) { if (!enc->pcm32_buffer) {
enc->pcm16_buffer = malloc(max_samples * 2 * sizeof(int16_t)); enc->pcm32_buffer = malloc(max_samples * 2 * sizeof(float));
} }
if (!enc->pcm8_buffer) { if (!enc->pcm8_buffer) {
enc->pcm8_buffer = malloc(max_samples * 2); enc->pcm8_buffer = malloc(max_samples * 2);
} }
// Read PCM16LE data // Read Float32LE data
size_t bytes_read = fread(enc->pcm16_buffer, 1, bytes_to_read, enc->pcm_file); size_t bytes_read = fread(enc->pcm32_buffer, 1, bytes_to_read, enc->pcm_file);
if (bytes_read == 0) { if (bytes_read == 0) {
return 0; return 0;
} }
int samples_read = bytes_read / (2 * sizeof(int16_t)); int samples_read = bytes_read / (2 * sizeof(float));
// Convert to PCM8 with dithering // Convert to PCM8 with dithering
convert_pcm16_to_pcm8_dithered(enc, enc->pcm16_buffer, enc->pcm8_buffer, samples_read); convert_pcm32_to_pcm8_dithered(enc, enc->pcm32_buffer, enc->pcm8_buffer, samples_read);
// Compress with zstd // Compress with zstd
size_t pcm8_size = samples_read * 2; // Stereo size_t pcm8_size = samples_read * 2; // Stereo
@@ -8985,10 +9019,10 @@ static int write_pcm8_packet_samples(tav_encoder_t *enc, FILE *output, int sampl
// Debug: Show first few samples // Debug: Show first few samples
if (samples_read > 0) { if (samples_read > 0) {
printf(" First samples (PCM16→PCM8): "); printf(" First samples (Float32→PCM8): ");
for (int i = 0; i < 4 && i < samples_read; i++) { for (int i = 0; i < 4 && i < samples_read; i++) {
printf("[%d,%d]→[%d,%d] ", printf("[%.3f,%.3f]→[%d,%d] ",
enc->pcm16_buffer[i*2], enc->pcm16_buffer[i*2+1], enc->pcm32_buffer[i*2], enc->pcm32_buffer[i*2+1],
enc->pcm8_buffer[i*2], enc->pcm8_buffer[i*2+1]); enc->pcm8_buffer[i*2], enc->pcm8_buffer[i*2+1]);
} }
printf("\n"); printf("\n");
@@ -10662,7 +10696,7 @@ static void cleanup_encoder(tav_encoder_t *enc) {
} }
// Free PCM8 buffers // Free PCM8 buffers
free(enc->pcm16_buffer); free(enc->pcm32_buffer);
free(enc->pcm8_buffer); free(enc->pcm8_buffer);
free(enc->input_file); free(enc->input_file);

View File

@@ -25,6 +25,7 @@
#define TAV_PACKET_BFRAME_ADAPTIVE 0x17 // B-frame with adaptive quad-tree block partitioning (bidirectional prediction) #define TAV_PACKET_BFRAME_ADAPTIVE 0x17 // B-frame with adaptive quad-tree block partitioning (bidirectional prediction)
#define TAV_PACKET_AUDIO_MP2 0x20 #define TAV_PACKET_AUDIO_MP2 0x20
#define TAV_PACKET_AUDIO_PCM8 0x21 #define TAV_PACKET_AUDIO_PCM8 0x21
#define TAV_PACKET_AUDIO_TAD 0x24
#define TAV_PACKET_SUBTITLE 0x30 #define TAV_PACKET_SUBTITLE 0x30
#define TAV_PACKET_SUBTITLE_KAR 0x31 #define TAV_PACKET_SUBTITLE_KAR 0x31
#define TAV_PACKET_AUDIO_TRACK 0x40 #define TAV_PACKET_AUDIO_TRACK 0x40
@@ -70,6 +71,10 @@ typedef struct {
int gop_sync_count; int gop_sync_count;
int total_gop_frames; int total_gop_frames;
int audio_count; int audio_count;
int audio_mp2_count;
int audio_pcm8_count;
int audio_tad_count;
int audio_track_count;
int subtitle_count; int subtitle_count;
int timecode_count; int timecode_count;
int sync_count; int sync_count;
@@ -81,6 +86,10 @@ typedef struct {
int unknown_count; int unknown_count;
uint64_t total_video_bytes; uint64_t total_video_bytes;
uint64_t total_audio_bytes; uint64_t total_audio_bytes;
uint64_t audio_mp2_bytes;
uint64_t audio_pcm8_bytes;
uint64_t audio_tad_bytes;
uint64_t audio_track_bytes;
} packet_stats_t; } packet_stats_t;
// Display options // Display options
@@ -109,6 +118,7 @@ const char* get_packet_type_name(uint8_t type) {
case TAV_PACKET_BFRAME_ADAPTIVE: return "B-FRAME (quadtree)"; case TAV_PACKET_BFRAME_ADAPTIVE: return "B-FRAME (quadtree)";
case TAV_PACKET_AUDIO_MP2: return "AUDIO MP2"; case TAV_PACKET_AUDIO_MP2: return "AUDIO MP2";
case TAV_PACKET_AUDIO_PCM8: return "AUDIO PCM8 (zstd)"; case TAV_PACKET_AUDIO_PCM8: return "AUDIO PCM8 (zstd)";
case TAV_PACKET_AUDIO_TAD: return "AUDIO TAD (zstd)";
case TAV_PACKET_SUBTITLE: return "SUBTITLE (Simple)"; case TAV_PACKET_SUBTITLE: return "SUBTITLE (Simple)";
case TAV_PACKET_SUBTITLE_KAR: return "SUBTITLE (Karaoke)"; case TAV_PACKET_SUBTITLE_KAR: return "SUBTITLE (Karaoke)";
case TAV_PACKET_AUDIO_TRACK: return "AUDIO TRACK (Separate MP2)"; case TAV_PACKET_AUDIO_TRACK: return "AUDIO TRACK (Separate MP2)";
@@ -139,7 +149,8 @@ int should_display_packet(uint8_t type, display_options_t *opts) {
if (opts->show_video && (type == TAV_PACKET_IFRAME || type == TAV_PACKET_PFRAME || if (opts->show_video && (type == TAV_PACKET_IFRAME || type == TAV_PACKET_PFRAME ||
type == TAV_PACKET_GOP_UNIFIED || type == TAV_PACKET_GOP_SYNC || type == TAV_PACKET_GOP_UNIFIED || type == TAV_PACKET_GOP_SYNC ||
(type >= 0x70 && type <= 0x7F))) return 1; (type >= 0x70 && type <= 0x7F))) return 1;
if (opts->show_audio && type == TAV_PACKET_AUDIO_MP2) return 1; if (opts->show_audio && (type == TAV_PACKET_AUDIO_MP2 || type == TAV_PACKET_AUDIO_PCM8 ||
type == TAV_PACKET_AUDIO_TAD || type == TAV_PACKET_AUDIO_TRACK)) return 1;
if (opts->show_subtitles && (type == TAV_PACKET_SUBTITLE || type == TAV_PACKET_SUBTITLE_KAR)) return 1; if (opts->show_subtitles && (type == TAV_PACKET_SUBTITLE || type == TAV_PACKET_SUBTITLE_KAR)) return 1;
if (opts->show_timecode && type == TAV_PACKET_TIMECODE) return 1; if (opts->show_timecode && type == TAV_PACKET_TIMECODE) return 1;
if (opts->show_metadata && (type >= 0xE0 && type <= 0xE4)) return 1; if (opts->show_metadata && (type >= 0xE0 && type <= 0xE4)) return 1;
@@ -439,15 +450,89 @@ int main(int argc, char *argv[]) {
return 1; return 1;
} }
// Skip header (32 bytes) // Parse and display header
fseek(fp, 32, SEEK_SET);
if (!opts.summary_only) { if (!opts.summary_only) {
printf("TAV Packet Inspector\n"); printf("TAV Packet Inspector\n");
printf("File: %s\n", filename); printf("File: %s\n", filename);
printf("==================================================\n\n"); printf("==================================================\n\n");
} }
// Read TAV header (32 bytes)
uint8_t header[32];
if (fread(header, 1, 32, fp) != 32) {
fprintf(stderr, "Error: Failed to read TAV header\n");
fclose(fp);
return 1;
}
// Verify magic number
const char *magic = "\x1F\x54\x53\x56\x4D\x54\x41\x56"; // "\x1FTSVM TAV"
if (memcmp(header, magic, 8) != 0) {
fprintf(stderr, "Error: Invalid TAV magic number\n");
fclose(fp);
return 1;
}
if (!opts.summary_only) {
// Parse header fields
uint8_t version = header[8];
uint16_t width = *((uint16_t*)&header[9]);
uint16_t height = *((uint16_t*)&header[11]);
uint8_t fps = header[13];
uint32_t total_frames = *((uint32_t*)&header[14]);
uint8_t wavelet = header[18];
uint8_t decomp_levels = header[19];
uint8_t quant_y = header[20];
uint8_t quant_co = header[21];
uint8_t quant_cg = header[22];
uint8_t extra_flags = header[23];
uint8_t video_flags = header[24];
uint8_t quality = header[25];
uint8_t channel_layout = header[26];
uint8_t entropy_coder = header[27];
static const int QLUT[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116,118,120,122,124,126,128,132,136,140,144,148,152,156,160,164,168,172,176,180,184,188,192,196,200,204,208,212,216,220,224,228,232,236,240,244,248,252,256,264,272,280,288,296,304,312,320,328,336,344,352,360,368,376,384,392,400,408,416,424,432,440,448,456,464,472,480,488,496,504,512,528,544,560,576,592,608,624,640,656,672,688,704,720,736,752,768,784,800,816,832,848,864,880,896,912,928,944,960,976,992,1008,1024,1056,1088,1120,1152,1184,1216,1248,1280,1312,1344,1376,1408,1440,1472,1504,1536,1568,1600,1632,1664,1696,1728,1760,1792,1824,1856,1888,1920,1952,1984,2016,2048,2112,2176,2240,2304,2368,2432,2496,2560,2624,2688,2752,2816,2880,2944,3008,3072,3136,3200,3264,3328,3392,3456,3520,3584,3648,3712,3776,3840,3904,3968,4032,4096};
static const char* CLAYOUT[] = {"Luma-Chroma", "Luma-Chroma-Alpha", "Luma", "Luma-Alpha", "Chroma", "Chroma-Alpha"};
int is_monoblock = (3 <= version && version <= 6);
int is_perceptual = (5 <= version && version <= 8);
static const char* VERDESC[] = {"null", "YCoCg tiled, uniform", "ICtCp tiled, uniform", "YCoCg monoblock, uniform", "ICtCp monoblock, uniform", "YCoCg monoblock, perceptual", "ICtCp monoblock, perceptual", "YCoCg tiled, perceptual", "ICtCp tiled, perceptual"};
printf("TAV Header:\n");
printf(" Version: %d (%s)\n", version, VERDESC[version]);
printf(" Resolution: %dx%d\n", width, height);
printf(" Frame rate: %d fps", fps);
if (video_flags & 0x02) printf(" (NTSC)");
printf("\n");
printf(" Total frames: %u\n", total_frames);
printf(" Wavelet: %d", wavelet);
const char *wavelet_names[] = {"LGT 5/3", "CDF 9/7", "CDF 13/7", "Reserved", "Reserved",
"Reserved", "Reserved", "Reserved", "Reserved",
"Reserved", "Reserved", "Reserved", "Reserved",
"Reserved", "Reserved", "Reserved", "DD-4"};
if (wavelet < 17) printf(" (%s)", wavelet_names[wavelet == 16 ? 16 : (wavelet > 16 ? wavelet : wavelet)]);
if (wavelet == 255) printf(" (Haar)");
printf("\n");
printf(" Decomp levels: %d\n", decomp_levels);
printf(" Quantizers: Y=%d, Co=%d, Cg=%d (Index=%d,%d,%d)\n", QLUT[quant_y], QLUT[quant_co], QLUT[quant_cg], quant_y, quant_co, quant_cg);
if (quality > 0)
printf(" Quality: %d\n", quality - 1);
else
printf(" Quality: n/a\n");
printf(" Channel layout: %s\n", CLAYOUT[channel_layout]);
printf(" Entropy coder: %s\n", entropy_coder == 0 ? "Twobit-map" : "EZBC");
printf(" Flags:\n");
printf(" Has audio: %s\n", (extra_flags & 0x01) ? "Yes" : "No");
printf(" Has subtitles: %s\n", (extra_flags & 0x02) ? "Yes" : "No");
printf(" Progressive: %s\n", (video_flags & 0x01) ? "No (interlaced)" : "Yes");
printf(" Lossless: %s\n", (video_flags & 0x04) ? "Yes" : "No");
if (extra_flags & 0x04) printf(" Progressive TX: Enabled\n");
if (extra_flags & 0x08) printf(" ROI encoding: Enabled\n");
printf("\nPackets:\n");
printf("==================================================\n");
}
packet_stats_t stats = {0}; packet_stats_t stats = {0};
int packet_num = 0; int packet_num = 0;
@@ -622,9 +707,11 @@ int main(int argc, char *argv[]) {
case TAV_PACKET_AUDIO_MP2: { case TAV_PACKET_AUDIO_MP2: {
stats.audio_count++; stats.audio_count++;
stats.audio_mp2_count++;
uint32_t size; uint32_t size;
if (fread(&size, sizeof(uint32_t), 1, fp) != 1) break; if (fread(&size, sizeof(uint32_t), 1, fp) != 1) break;
stats.total_audio_bytes += size; stats.total_audio_bytes += size;
stats.audio_mp2_bytes += size;
if (!opts.summary_only && display) { if (!opts.summary_only && display) {
printf(" - size=%u bytes", size); printf(" - size=%u bytes", size);
@@ -635,9 +722,11 @@ int main(int argc, char *argv[]) {
case TAV_PACKET_AUDIO_PCM8: { case TAV_PACKET_AUDIO_PCM8: {
stats.audio_count++; stats.audio_count++;
stats.audio_pcm8_count++;
uint32_t size; uint32_t size;
if (fread(&size, sizeof(uint32_t), 1, fp) != 1) break; if (fread(&size, sizeof(uint32_t), 1, fp) != 1) break;
stats.total_audio_bytes += size; stats.total_audio_bytes += size;
stats.audio_pcm8_bytes += size;
if (!opts.summary_only && display) { if (!opts.summary_only && display) {
printf(" - size=%u bytes (zstd compressed)", size); printf(" - size=%u bytes (zstd compressed)", size);
@@ -646,11 +735,41 @@ int main(int argc, char *argv[]) {
break; break;
} }
case TAV_PACKET_AUDIO_TAD: {
stats.audio_count++;
stats.audio_tad_count++;
// Read payload_size + 2
uint32_t payload_size_plus_6;
if (fread(&payload_size_plus_6, sizeof(uint32_t), 1, fp) != 1) break;
// Read sample count
uint16_t sample_count;
if (fread(&sample_count, sizeof(uint16_t), 1, fp) != 1) break;
// Read compressed size
uint32_t compressed_size;
if (fread(&compressed_size, sizeof(uint32_t), 1, fp) != 1) break;
stats.total_audio_bytes += compressed_size;
stats.audio_tad_bytes += compressed_size;
if (!opts.summary_only && display) {
printf(" - samples=%u, size=%u bytes (zstd compressed TAD32)",
sample_count, compressed_size);
}
// Skip compressed data
fseek(fp, compressed_size, SEEK_CUR);
break;
}
case TAV_PACKET_AUDIO_TRACK: { case TAV_PACKET_AUDIO_TRACK: {
stats.audio_count++; stats.audio_count++;
stats.audio_track_count++;
uint32_t size; uint32_t size;
if (fread(&size, sizeof(uint32_t), 1, fp) != 1) break; if (fread(&size, sizeof(uint32_t), 1, fp) != 1) break;
stats.total_audio_bytes += size; stats.total_audio_bytes += size;
stats.audio_track_bytes += size;
if (!opts.summary_only && display) { if (!opts.summary_only && display) {
printf(" - size=%u bytes (separate track)", size); printf(" - size=%u bytes (separate track)", size);
@@ -756,7 +875,31 @@ int main(int argc, char *argv[]) {
(unsigned long long)stats.total_video_bytes, (unsigned long long)stats.total_video_bytes,
stats.total_video_bytes / 1024.0 / 1024.0); stats.total_video_bytes / 1024.0 / 1024.0);
printf("\nAudio:\n"); printf("\nAudio:\n");
printf(" MP2 packets: %d\n", stats.audio_count); printf(" Total packets: %d\n", stats.audio_count);
if (stats.audio_mp2_count > 0) {
printf(" MP2: %d packets, %llu bytes (%.2f MB)\n",
stats.audio_mp2_count,
(unsigned long long)stats.audio_mp2_bytes,
stats.audio_mp2_bytes / 1024.0 / 1024.0);
}
if (stats.audio_pcm8_count > 0) {
printf(" PCM8 (zstd): %d packets, %llu bytes (%.2f MB)\n",
stats.audio_pcm8_count,
(unsigned long long)stats.audio_pcm8_bytes,
stats.audio_pcm8_bytes / 1024.0 / 1024.0);
}
if (stats.audio_tad_count > 0) {
printf(" TAD32 (zstd): %d packets, %llu bytes (%.2f MB)\n",
stats.audio_tad_count,
(unsigned long long)stats.audio_tad_bytes,
stats.audio_tad_bytes / 1024.0 / 1024.0);
}
if (stats.audio_track_count > 0) {
printf(" Separate track: %d packets, %llu bytes (%.2f MB)\n",
stats.audio_track_count,
(unsigned long long)stats.audio_track_bytes,
stats.audio_track_bytes / 1024.0 / 1024.0);
}
printf(" Total audio bytes: %llu (%.2f MB)\n", printf(" Total audio bytes: %llu (%.2f MB)\n",
(unsigned long long)stats.total_audio_bytes, (unsigned long long)stats.total_audio_bytes,
stats.total_audio_bytes / 1024.0 / 1024.0); stats.total_audio_bytes / 1024.0 / 1024.0);