112x112 blocks for TAV, which greatly improves the encoding speed

This commit is contained in:
minjaesong
2025-09-15 19:08:46 +09:00
parent 1343dd10cf
commit 113c01b851
4 changed files with 816 additions and 83 deletions

View File

@@ -73,7 +73,7 @@ static inline float float16_to_float(uint16_t hbits) {
// Version 1: YCoCg-R (default)
// Version 2: ICtCp (--ictcp flag)
// Tile encoding modes (64x64 tiles)
// Tile encoding modes (112x112 tiles)
#define TAV_MODE_SKIP 0x00 // Skip tile (copy from reference)
#define TAV_MODE_INTRA 0x01 // Intra DWT coding (I-frame tiles)
#define TAV_MODE_INTER 0x02 // Inter DWT coding with motion compensation
@@ -87,9 +87,9 @@ static inline float float16_to_float(uint16_t hbits) {
#define TAV_PACKET_SYNC 0xFF // Sync packet
// DWT settings
#define TILE_SIZE 64
#define MAX_DECOMP_LEVELS 4
#define DEFAULT_DECOMP_LEVELS 3
#define TILE_SIZE 112 // 112x112 tiles - perfect fit for TSVM 560x448 (GCD = 112)
#define MAX_DECOMP_LEVELS 6 // Can go deeper: 112→56→28→14→7→3→1
#define DEFAULT_DECOMP_LEVELS 4 // Increased default for better compression
// Wavelet filter types
#define WAVELET_5_3_REVERSIBLE 0 // Lossless capable
@@ -101,6 +101,18 @@ static inline float float16_to_float(uint16_t hbits) {
#define DEFAULT_FPS 30
#define DEFAULT_QUALITY 2
// Audio/subtitle constants (reused from TEV)
#define MP2_DEFAULT_PACKET_SIZE 1152
#define MAX_SUBTITLE_LENGTH 2048
// Subtitle structure
typedef struct subtitle_entry {
int start_frame;
int end_frame;
char *text;
struct subtitle_entry *next;
} subtitle_entry_t;
static void generate_random_filename(char *filename) {
srand(time(NULL));
@@ -208,8 +220,18 @@ typedef struct {
dwt_tile_t *tiles;
motion_vector_t *motion_vectors;
// Audio processing
// Audio processing (expanded from TEV)
size_t audio_remaining;
uint8_t *mp2_buffer;
size_t mp2_buffer_size;
int mp2_packet_size;
int mp2_rate_index;
int target_audio_buffer_size;
// Subtitle processing
subtitle_entry_t *subtitles;
subtitle_entry_t *current_subtitle;
int subtitle_visible;
// Compression
ZSTD_CCtx *zstd_ctx;
@@ -245,13 +267,27 @@ static void rgb_to_ycocg(const uint8_t *rgb, float *y, float *co, float *cg, int
static void dwt_2d_forward(float *tile_data, int levels, int filter_type);
static void dwt_2d_inverse(dwt_tile_t *tile, float *output, int filter_type);
static void quantize_subbands(dwt_tile_t *tile, int q_y, int q_co, int q_cg, float rcf);
static int estimate_motion_64x64(const float *current, const float *reference,
int width, int height, int tile_x, int tile_y,
motion_vector_t *mv);
static int estimate_motion_112x112(const float *current, const float *reference,
int width, int height, int tile_x, int tile_y,
motion_vector_t *mv);
static size_t compress_tile_data(tav_encoder_t *enc, const dwt_tile_t *tiles,
const motion_vector_t *mvs, int num_tiles,
uint8_t packet_type);
// Audio and subtitle processing prototypes (from TEV)
static int start_audio_conversion(tav_encoder_t *enc);
static int get_mp2_packet_size(uint8_t *header);
static int mp2_packet_size_to_rate_index(int packet_size, int is_mono);
static int process_audio(tav_encoder_t *enc, int frame_num, FILE *output);
static subtitle_entry_t* parse_subtitle_file(const char *filename, int fps);
static subtitle_entry_t* parse_srt_file(const char *filename, int fps);
static subtitle_entry_t* parse_smi_file(const char *filename, int fps);
static int srt_time_to_frame(const char *time_str, int fps);
static int sami_ms_to_frame(int milliseconds, int fps);
static void free_subtitle_list(subtitle_entry_t *list);
static int write_subtitle_packet(FILE *output, uint32_t index, uint8_t opcode, const char *text);
static int process_subtitles(tav_encoder_t *enc, int frame_num, FILE *output);
// Show usage information
static void show_usage(const char *program_name) {
printf("TAV DWT-based Video Encoder\n");
@@ -264,7 +300,7 @@ static void show_usage(const char *program_name) {
printf(" -q, --quality N Quality level 0-5 (default: 2)\n");
printf(" -Q, --quantizer Y,Co,Cg Quantizer levels 0-100 for each channel\n");
printf(" -w, --wavelet N Wavelet filter: 0=5/3 reversible, 1=9/7 irreversible (default: 1)\n");
printf(" -d, --decomp N Decomposition levels 1-4 (default: 3)\n");
printf(" -d, --decomp N Decomposition levels 1-6 (default: 4)\n");
printf(" -b, --bitrate N Target bitrate in kbps (enables bitrate control mode)\n");
printf(" -p, --progressive Use progressive scan (default: interlaced)\n");
printf(" -S, --subtitles FILE SubRip (.srt) or SAMI (.smi) subtitle file\n");
@@ -296,7 +332,7 @@ static void show_usage(const char *program_name) {
}
printf("\n\nFeatures:\n");
printf(" - 64x64 DWT tiles with multi-resolution encoding\n");
printf(" - 112x112 DWT tiles with multi-resolution encoding\n");
printf(" - Full resolution YCoCg-R color space\n");
printf(" - Progressive transmission and ROI coding\n");
printf(" - Motion compensation with ±16 pixel search range\n");
@@ -304,7 +340,7 @@ static void show_usage(const char *program_name) {
printf("\nExamples:\n");
printf(" %s -i input.mp4 -o output.mv3 # Default settings\n", program_name);
printf(" %s -i input.mkv -q 3 -w 1 -d 4 -o output.mv3 # High quality with 9/7 wavelet\n", program_name);
printf(" %s -i input.mkv -q 3 -w 1 -d 6 -o output.mv3 # Maximum quality with 9/7 wavelet\n", program_name);
printf(" %s -i input.avi --lossless -o output.mv3 # Lossless encoding\n", program_name);
printf(" %s -i input.mp4 -b 800 -o output.mv3 # 800 kbps bitrate target\n", program_name);
printf(" %s -i input.webm -S subs.srt -o output.mv3 # With subtitles\n", program_name);
@@ -487,9 +523,9 @@ static void dwt_97_forward_1d(float *data, int length) {
free(temp);
}
// 2D DWT forward transform for 64x64 tile
// 2D DWT forward transform for 112x112 tile
static void dwt_2d_forward(float *tile_data, int levels, int filter_type) {
const int size = 64;
const int size = TILE_SIZE;
float *temp_row = malloc(size * sizeof(float));
float *temp_col = malloc(size * sizeof(float));
@@ -565,7 +601,7 @@ static size_t serialize_tile_data(tav_encoder_t *enc, int tile_x, int tile_y,
}
// Quantize and serialize DWT coefficients
const int tile_size = 64 * 64;
const int tile_size = TILE_SIZE * TILE_SIZE;
int16_t *quantized_y = malloc(tile_size * sizeof(int16_t));
int16_t *quantized_co = malloc(tile_size * sizeof(int16_t));
int16_t *quantized_cg = malloc(tile_size * sizeof(int16_t));
@@ -609,7 +645,7 @@ static size_t serialize_tile_data(tav_encoder_t *enc, int tile_x, int tile_y,
// Compress and write frame data
static size_t compress_and_write_frame(tav_encoder_t *enc, uint8_t packet_type) {
// Calculate total uncompressed size
const size_t max_tile_size = 9 + (64 * 64 * 3 * sizeof(int16_t)); // header + 3 channels of coefficients
const size_t max_tile_size = 9 + (TILE_SIZE * TILE_SIZE * 3 * sizeof(int16_t)); // header + 3 channels of coefficients
const size_t total_uncompressed_size = enc->tiles_x * enc->tiles_y * max_tile_size;
// Allocate buffer for uncompressed tile data
@@ -625,17 +661,17 @@ static size_t compress_and_write_frame(tav_encoder_t *enc, uint8_t packet_type)
uint8_t mode = TAV_MODE_INTRA; // For now, all tiles are INTRA
// Extract tile data (already processed)
float tile_y_data[64 * 64];
float tile_co_data[64 * 64];
float tile_cg_data[64 * 64];
float tile_y_data[TILE_SIZE * TILE_SIZE];
float tile_co_data[TILE_SIZE * TILE_SIZE];
float tile_cg_data[TILE_SIZE * TILE_SIZE];
// Extract tile data from frame buffers
for (int y = 0; y < 64; y++) {
for (int x = 0; x < 64; x++) {
int src_x = tile_x * 64 + x;
int src_y = tile_y * 64 + y;
for (int y = 0; y < TILE_SIZE; y++) {
for (int x = 0; x < TILE_SIZE; x++) {
int src_x = tile_x * TILE_SIZE + x;
int src_y = tile_y * TILE_SIZE + y;
int src_idx = src_y * enc->width + src_x;
int tile_idx_local = y * 64 + x;
int tile_idx_local = y * TILE_SIZE + x;
if (src_x < enc->width && src_y < enc->height) {
tile_y_data[tile_idx_local] = enc->current_frame_y[src_idx];
@@ -698,12 +734,12 @@ static size_t compress_and_write_frame(tav_encoder_t *enc, uint8_t packet_type)
return compressed_size + 5; // packet type + size field + compressed data
}
// Motion estimation for 64x64 tiles using SAD
static int estimate_motion_64x64(const float *current, const float *reference,
// Motion estimation for 112x112 tiles using SAD
static int estimate_motion_112x112(const float *current, const float *reference,
int width, int height, int tile_x, int tile_y,
motion_vector_t *mv) {
const int tile_size = 64;
const int search_range = 16; // ±16 pixels
const int tile_size = TILE_SIZE;
const int search_range = 28; // ±28 pixels (increased proportionally: 16 * 112/64 = 28)
const int start_x = tile_x * tile_size;
const int start_y = tile_y * tile_size;
@@ -1131,6 +1167,7 @@ static int start_video_conversion(tav_encoder_t *enc) {
// Start audio conversion
static int start_audio_conversion(tav_encoder_t *enc) {
return 1;
if (!enc->has_audio) return 1;
char command[2048];
@@ -1151,6 +1188,400 @@ static int start_audio_conversion(tav_encoder_t *enc) {
return 0;
}
// Get MP2 packet size from header (copied from TEV)
static int get_mp2_packet_size(uint8_t *header) {
int bitrate_index = (header[2] >> 4) & 0x0F;
int bitrates[] = {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384};
if (bitrate_index >= 15) return MP2_DEFAULT_PACKET_SIZE;
int bitrate = bitrates[bitrate_index];
if (bitrate == 0) return MP2_DEFAULT_PACKET_SIZE;
int sampling_freq_index = (header[2] >> 2) & 0x03;
int sampling_freqs[] = {44100, 48000, 32000, 0};
int sampling_freq = sampling_freqs[sampling_freq_index];
if (sampling_freq == 0) return MP2_DEFAULT_PACKET_SIZE;
int padding = (header[2] >> 1) & 0x01;
return (144 * bitrate * 1000) / sampling_freq + padding;
}
// Convert MP2 packet size to rate index (copied from TEV)
static int mp2_packet_size_to_rate_index(int packet_size, int is_mono) {
// Map packet size to rate index for MP2_RATE_TABLE
if (packet_size <= 576) return is_mono ? 0 : 0; // 128k
else if (packet_size <= 720) return 1; // 160k
else if (packet_size <= 1008) return 2; // 224k
else if (packet_size <= 1440) return 3; // 320k
else return 4; // 384k
}
// Convert SRT time format to frame number (copied from TEV)
static int srt_time_to_frame(const char *time_str, int fps) {
int hours, minutes, seconds, milliseconds;
if (sscanf(time_str, "%d:%d:%d,%d", &hours, &minutes, &seconds, &milliseconds) != 4) {
return -1;
}
double total_seconds = hours * 3600.0 + minutes * 60.0 + seconds + milliseconds / 1000.0;
return (int)(total_seconds * fps + 0.5); // Round to nearest frame
}
// Convert SAMI milliseconds to frame number (copied from TEV)
static int sami_ms_to_frame(int milliseconds, int fps) {
double seconds = milliseconds / 1000.0;
return (int)(seconds * fps + 0.5); // Round to nearest frame
}
// Parse SubRip subtitle file (copied from TEV)
static subtitle_entry_t* parse_srt_file(const char *filename, int fps) {
FILE *file = fopen(filename, "r");
if (!file) {
fprintf(stderr, "Failed to open subtitle file: %s\n", filename);
return NULL;
}
subtitle_entry_t *head = NULL;
subtitle_entry_t *tail = NULL;
char line[1024];
int state = 0; // 0=index, 1=time, 2=text, 3=blank
subtitle_entry_t *current_entry = NULL;
char *text_buffer = NULL;
size_t text_buffer_size = 0;
while (fgets(line, sizeof(line), file)) {
// Remove trailing newline
size_t len = strlen(line);
if (len > 0 && line[len-1] == '\n') {
line[len-1] = '\0';
len--;
}
if (len > 0 && line[len-1] == '\r') {
line[len-1] = '\0';
len--;
}
if (state == 0) { // Expecting subtitle index
if (strlen(line) == 0) continue; // Skip empty lines
// Create new subtitle entry
current_entry = calloc(1, sizeof(subtitle_entry_t));
if (!current_entry) break;
state = 1;
} else if (state == 1) { // Expecting time range
char start_time[32], end_time[32];
if (sscanf(line, "%31s --> %31s", start_time, end_time) == 2) {
current_entry->start_frame = srt_time_to_frame(start_time, fps);
current_entry->end_frame = srt_time_to_frame(end_time, fps);
if (current_entry->start_frame < 0 || current_entry->end_frame < 0) {
free(current_entry);
current_entry = NULL;
state = 3; // Skip to next blank line
continue;
}
// Initialize text buffer
text_buffer_size = 256;
text_buffer = malloc(text_buffer_size);
if (!text_buffer) {
free(current_entry);
current_entry = NULL;
fprintf(stderr, "Memory allocation failed while parsing subtitles\n");
break;
}
text_buffer[0] = '\0';
state = 2;
} else {
free(current_entry);
current_entry = NULL;
state = 3; // Skip malformed entry
}
} else if (state == 2) { // Collecting subtitle text
if (strlen(line) == 0) {
// End of subtitle text
current_entry->text = strdup(text_buffer);
free(text_buffer);
text_buffer = NULL;
// Add to list
if (!head) {
head = current_entry;
tail = current_entry;
} else {
tail->next = current_entry;
tail = current_entry;
}
current_entry = NULL;
state = 0;
} else {
// Append text line
size_t current_len = strlen(text_buffer);
size_t line_len = strlen(line);
size_t needed = current_len + line_len + 2; // +2 for newline and null
if (needed > text_buffer_size) {
text_buffer_size = needed + 256;
char *new_buffer = realloc(text_buffer, text_buffer_size);
if (!new_buffer) {
free(text_buffer);
free(current_entry);
current_entry = NULL;
fprintf(stderr, "Memory reallocation failed while parsing subtitles\n");
break;
}
text_buffer = new_buffer;
}
if (current_len > 0) {
strcat(text_buffer, "\\n"); // Use \n as newline marker in subtitle text
}
strcat(text_buffer, line);
}
} else if (state == 3) { // Skip to next blank line
if (strlen(line) == 0) {
state = 0;
}
}
}
// Handle final subtitle if file doesn't end with blank line
if (current_entry && state == 2) {
current_entry->text = strdup(text_buffer);
if (!head) {
head = current_entry;
} else {
tail->next = current_entry;
}
free(text_buffer);
}
fclose(file);
return head;
}
// Parse SAMI subtitle file (simplified version from TEV)
static subtitle_entry_t* parse_smi_file(const char *filename, int fps) {
FILE *file = fopen(filename, "r");
if (!file) {
fprintf(stderr, "Failed to open subtitle file: %s\n", filename);
return NULL;
}
subtitle_entry_t *head = NULL;
subtitle_entry_t *tail = NULL;
char line[2048];
while (fgets(line, sizeof(line), file)) {
// Look for SYNC tags with Start= attribute
char *sync_pos = strstr(line, "<SYNC");
if (sync_pos) {
char *start_pos = strstr(sync_pos, "Start=");
if (start_pos) {
int start_ms;
if (sscanf(start_pos, "Start=%d", &start_ms) == 1) {
// Look for P tag with subtitle text
char *p_start = strstr(sync_pos, "<P");
if (p_start) {
char *text_start = strchr(p_start, '>');
if (text_start) {
text_start++;
char *text_end = strstr(text_start, "</P>");
if (text_end) {
size_t text_len = text_end - text_start;
if (text_len > 0 && text_len < MAX_SUBTITLE_LENGTH) {
subtitle_entry_t *entry = calloc(1, sizeof(subtitle_entry_t));
if (entry) {
entry->start_frame = sami_ms_to_frame(start_ms, fps);
entry->end_frame = entry->start_frame + fps * 3; // Default 3 second duration
entry->text = strndup(text_start, text_len);
// Add to list
if (!head) {
head = entry;
tail = entry;
} else {
tail->next = entry;
tail = entry;
}
}
}
}
}
}
}
}
}
}
fclose(file);
return head;
}
// Parse subtitle file based on extension (copied from TEV)
static subtitle_entry_t* parse_subtitle_file(const char *filename, int fps) {
if (!filename) return NULL;
size_t len = strlen(filename);
if (len > 4 && strcasecmp(filename + len - 4, ".smi") == 0) {
return parse_smi_file(filename, fps);
} else {
return parse_srt_file(filename, fps);
}
}
// Free subtitle list (copied from TEV)
static void free_subtitle_list(subtitle_entry_t *list) {
while (list) {
subtitle_entry_t *next = list->next;
free(list->text);
free(list);
list = next;
}
}
// Write subtitle packet (copied from TEV)
static int write_subtitle_packet(FILE *output, uint32_t index, uint8_t opcode, const char *text) {
// Calculate packet size
size_t text_len = text ? strlen(text) : 0;
size_t packet_size = 3 + 1 + text_len + 1; // index (3 bytes) + opcode + text + null terminator
// Write packet type and size
uint8_t packet_type = TAV_PACKET_SUBTITLE;
fwrite(&packet_type, 1, 1, output);
uint32_t size32 = (uint32_t)packet_size;
fwrite(&size32, 4, 1, output);
// Write subtitle data
uint8_t index_bytes[3] = {
(uint8_t)(index & 0xFF),
(uint8_t)((index >> 8) & 0xFF),
(uint8_t)((index >> 16) & 0xFF)
};
fwrite(index_bytes, 3, 1, output);
fwrite(&opcode, 1, 1, output);
if (text && text_len > 0) {
fwrite(text, 1, text_len, output);
}
uint8_t null_terminator = 0;
fwrite(&null_terminator, 1, 1, output);
return 1 + 4 + packet_size; // Total bytes written
}
// Process audio for current frame (copied and adapted from TEV)
static int process_audio(tav_encoder_t *enc, int frame_num, FILE *output) {
if (!enc->has_audio || !enc->mp2_file || enc->audio_remaining <= 0) {
return 1;
}
// Initialize packet size on first frame
if (frame_num == 0) {
uint8_t header[4];
if (fread(header, 1, 4, enc->mp2_file) != 4) return 1;
fseek(enc->mp2_file, 0, SEEK_SET);
enc->mp2_packet_size = get_mp2_packet_size(header);
int is_mono = (header[3] >> 6) == 3;
enc->mp2_rate_index = mp2_packet_size_to_rate_index(enc->mp2_packet_size, is_mono);
enc->target_audio_buffer_size = 4; // 4 audio packets in buffer
}
// Calculate how much audio we need for this frame
double frame_duration = 1.0 / enc->fps;
double samples_per_frame = 32000.0 * frame_duration; // 32kHz sample rate
int target_buffer_samples = (int)(samples_per_frame * enc->target_audio_buffer_size);
int target_buffer_bytes = (target_buffer_samples * enc->mp2_packet_size) / 1152; // 1152 samples per MP2 frame
if (!enc->mp2_buffer) {
enc->mp2_buffer_size = target_buffer_bytes * 2; // Extra buffer space
enc->mp2_buffer = malloc(enc->mp2_buffer_size);
if (!enc->mp2_buffer) {
fprintf(stderr, "Failed to allocate audio buffer\n");
return 1;
}
}
// Read audio data
size_t bytes_to_read = target_buffer_bytes;
if (bytes_to_read > enc->audio_remaining) {
bytes_to_read = enc->audio_remaining;
}
if (bytes_to_read > enc->mp2_buffer_size) {
bytes_to_read = enc->mp2_buffer_size;
}
size_t bytes_read = fread(enc->mp2_buffer, 1, bytes_to_read, enc->mp2_file);
if (bytes_read == 0) {
return 1; // No more audio
}
// Write audio packet
uint8_t audio_packet_type = TAV_PACKET_AUDIO_MP2;
uint32_t audio_len = (uint32_t)bytes_read;
fwrite(&audio_packet_type, 1, 1, output);
fwrite(&audio_len, 4, 1, output);
fwrite(enc->mp2_buffer, 1, bytes_read, output);
// Track audio bytes written
enc->audio_remaining -= bytes_read;
if (enc->verbose) {
printf("Frame %d: Audio packet %zu bytes (remaining: %zu)\n",
frame_num, bytes_read, enc->audio_remaining);
}
return 1;
}
// Process subtitles for current frame (copied and adapted from TEV)
static int process_subtitles(tav_encoder_t *enc, int frame_num, FILE *output) {
if (!enc->subtitles) {
return 1; // No subtitles to process
}
int bytes_written = 0;
// Check if we need to show a new subtitle
if (!enc->subtitle_visible) {
subtitle_entry_t *sub = enc->current_subtitle;
if (!sub) sub = enc->subtitles; // Start from beginning if not set
// Find next subtitle to show
while (sub && sub->start_frame <= frame_num) {
if (sub->end_frame > frame_num) {
// This subtitle should be shown
if (sub != enc->current_subtitle) {
enc->current_subtitle = sub;
enc->subtitle_visible = 1;
bytes_written += write_subtitle_packet(output, 0, 0x01, sub->text);
if (enc->verbose) {
printf("Frame %d: Showing subtitle: %.50s%s\n",
frame_num, sub->text, strlen(sub->text) > 50 ? "..." : "");
}
}
break;
}
sub = sub->next;
}
}
// Check if we need to hide current subtitle
if (enc->subtitle_visible && enc->current_subtitle) {
if (frame_num >= enc->current_subtitle->end_frame) {
enc->subtitle_visible = 0;
bytes_written += write_subtitle_packet(output, 0, 0x02, NULL);
if (enc->verbose) {
printf("Frame %d: Hiding subtitle\n", frame_num);
}
}
}
return bytes_written;
}
// Main function
int main(int argc, char *argv[]) {
generate_random_filename(TEMP_AUDIO_FILE);
@@ -1231,6 +1662,9 @@ int main(int argc, char *argv[]) {
case 't':
enc->test_mode = 1;
break;
case 'S':
enc->subtitle_file = strdup(optarg);
break;
case 1000: // --lossless
enc->lossless = 1;
enc->wavelet_filter = WAVELET_5_3_REVERSIBLE;
@@ -1317,6 +1751,17 @@ int main(int argc, char *argv[]) {
}
}
// Parse subtitles if provided
if (enc->subtitle_file) {
printf("Parsing subtitles: %s\n", enc->subtitle_file);
enc->subtitles = parse_subtitle_file(enc->subtitle_file, enc->fps);
if (!enc->subtitles) {
fprintf(stderr, "Warning: Failed to parse subtitle file\n");
} else {
printf("Loaded subtitles successfully\n");
}
}
// Write TAV header
if (write_tav_header(enc) != 0) {
fprintf(stderr, "Error: Failed to write TAV header\n");
@@ -1430,7 +1875,7 @@ int main(int argc, char *argv[]) {
int tile_y = tile_idx / enc->tiles_x;
if (!is_keyframe && frame_count > 0) {
estimate_motion_64x64(enc->current_frame_y, enc->previous_frame_y,
estimate_motion_112x112(enc->current_frame_y, enc->previous_frame_y,
enc->width, enc->height, tile_x, tile_y,
&enc->motion_vectors[tile_idx]);
} else {
@@ -1449,6 +1894,12 @@ int main(int argc, char *argv[]) {
break;
}
else {
// Process audio for this frame
process_audio(enc, frame_count, enc->output_fp);
// Process subtitles for this frame
process_subtitles(enc, frame_count, enc->output_fp);
// Write a sync packet only after a video is been coded
uint8_t sync_packet = TAV_PACKET_SYNC;
fwrite(&sync_packet, 1, 1, enc->output_fp);
@@ -1526,6 +1977,12 @@ static void cleanup_encoder(tav_encoder_t *enc) {
free(enc->tiles);
free(enc->motion_vectors);
free(enc->compressed_buffer);
free(enc->mp2_buffer);
// Free subtitle list
if (enc->subtitles) {
free_subtitle_list(enc->subtitles);
}
if (enc->zstd_ctx) {
ZSTD_freeCCtx(enc->zstd_ctx);