mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 11:51:49 +09:00
tavenc: tiling on uniform
This commit is contained in:
@@ -27,7 +27,8 @@ LIBTAVENC_OBJ = lib/libtavenc/tav_encoder_lib.o \
|
|||||||
lib/libtavenc/tav_encoder_dwt.o \
|
lib/libtavenc/tav_encoder_dwt.o \
|
||||||
lib/libtavenc/tav_encoder_quantize.o \
|
lib/libtavenc/tav_encoder_quantize.o \
|
||||||
lib/libtavenc/tav_encoder_ezbc.o \
|
lib/libtavenc/tav_encoder_ezbc.o \
|
||||||
lib/libtavenc/tav_encoder_utils.o
|
lib/libtavenc/tav_encoder_utils.o \
|
||||||
|
lib/libtavenc/tav_encoder_tile.o
|
||||||
|
|
||||||
# libtavdec - TAV decoder library
|
# libtavdec - TAV decoder library
|
||||||
LIBTAVDEC_OBJ = lib/libtavdec/tav_video_decoder.o
|
LIBTAVDEC_OBJ = lib/libtavdec/tav_video_decoder.o
|
||||||
|
|||||||
@@ -153,38 +153,12 @@ int tav_encoder_validate_context(tav_encoder_context_t *ctx);
|
|||||||
// Video Encoding
|
// Video Encoding
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Encode a single RGB24 frame.
|
* DEPRECATED: tav_encoder_encode_frame() and tav_encoder_flush() have been
|
||||||
*
|
* removed. Use tav_encoder_encode_gop() instead, which works for both
|
||||||
* Frames are buffered internally until a GOP is full, then encoded and returned.
|
* single-threaded and multi-threaded modes. The CLI should buffer frames
|
||||||
* For GOP encoding: returns NULL until GOP is complete.
|
* and call encode_gop() when a full GOP is ready.
|
||||||
* For intra-only: returns packet immediately.
|
|
||||||
*
|
|
||||||
* Thread-safety: NOT thread-safe. Caller must serialize calls to encode_frame().
|
|
||||||
*
|
|
||||||
* @param ctx Encoder context
|
|
||||||
* @param rgb_frame RGB24 frame data (planar: [R...][G...][B...]), width×height×3 bytes
|
|
||||||
* @param frame_pts Presentation timestamp (frame number or time)
|
|
||||||
* @param packet Output packet pointer (NULL if GOP not yet complete)
|
|
||||||
* @return 1 if packet ready, 0 if buffering for GOP, -1 on error
|
|
||||||
*/
|
*/
|
||||||
int tav_encoder_encode_frame(tav_encoder_context_t *ctx,
|
|
||||||
const uint8_t *rgb_frame,
|
|
||||||
int64_t frame_pts,
|
|
||||||
tav_encoder_packet_t **packet);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flush encoder and encode any remaining buffered frames.
|
|
||||||
*
|
|
||||||
* Call at end of encoding to output final GOP (even if not full).
|
|
||||||
* Returns packets one at a time through repeated calls.
|
|
||||||
*
|
|
||||||
* @param ctx Encoder context
|
|
||||||
* @param packet Output packet pointer (NULL when no more packets)
|
|
||||||
* @return 1 if packet ready, 0 if no more packets, -1 on error
|
|
||||||
*/
|
|
||||||
int tav_encoder_flush(tav_encoder_context_t *ctx,
|
|
||||||
tav_encoder_packet_t **packet);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode a complete GOP (Group of Pictures) directly.
|
* Encode a complete GOP (Group of Pictures) directly.
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "tav_encoder_quantize.h"
|
#include "tav_encoder_quantize.h"
|
||||||
#include "tav_encoder_ezbc.h"
|
#include "tav_encoder_ezbc.h"
|
||||||
#include "tav_encoder_utils.h"
|
#include "tav_encoder_utils.h"
|
||||||
|
#include "tav_encoder_tile.h"
|
||||||
#include "encoder_tad.h"
|
#include "encoder_tad.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -390,26 +391,26 @@ tav_encoder_context_t *tav_encoder_create(const tav_encoder_params_t *params) {
|
|||||||
// Auto mode: use monoblock for <= D1 PAL, tiled for larger
|
// Auto mode: use monoblock for <= D1 PAL, tiled for larger
|
||||||
if (ctx->width > TAV_MONOBLOCK_MAX_WIDTH || ctx->height > TAV_MONOBLOCK_MAX_HEIGHT) {
|
if (ctx->width > TAV_MONOBLOCK_MAX_WIDTH || ctx->height > TAV_MONOBLOCK_MAX_HEIGHT) {
|
||||||
ctx->monoblock = 0;
|
ctx->monoblock = 0;
|
||||||
if (ctx->verbose) {
|
// if (ctx->verbose) {
|
||||||
printf("Auto-selected Padded Tiling mode: %dx%d exceeds D1 PAL threshold (%dx%d)\n",
|
printf("Auto-selected Padded Tiling mode: %dx%d exceeds D1 PAL threshold (%dx%d)\n",
|
||||||
ctx->width, ctx->height, TAV_MONOBLOCK_MAX_WIDTH, TAV_MONOBLOCK_MAX_HEIGHT);
|
ctx->width, ctx->height, TAV_MONOBLOCK_MAX_WIDTH, TAV_MONOBLOCK_MAX_HEIGHT);
|
||||||
}
|
// }
|
||||||
} else {
|
} else {
|
||||||
ctx->monoblock = 1;
|
ctx->monoblock = 1;
|
||||||
if (ctx->verbose) {
|
// if (ctx->verbose) {
|
||||||
printf("Auto-selected Monoblock mode: %dx%d within D1 PAL threshold\n",
|
printf("Auto-selected Monoblock mode: %dx%d within D1 PAL threshold\n",
|
||||||
ctx->width, ctx->height);
|
ctx->width, ctx->height);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
} else if (ctx->monoblock == 0) {
|
} else if (ctx->monoblock == 0) {
|
||||||
if (ctx->verbose) {
|
// if (ctx->verbose) {
|
||||||
printf("Forced Padded Tiling mode (--tiled)\n");
|
printf("Forced Padded Tiling mode (--tiled)\n");
|
||||||
}
|
// }
|
||||||
} else {
|
} else {
|
||||||
// monoblock == 1: force monoblock even for large dimensions
|
// monoblock == 1: force monoblock even for large dimensions
|
||||||
if (ctx->verbose) {
|
// if (ctx->verbose) {
|
||||||
printf("Forced Monoblock mode (--monoblock)\n");
|
printf("Forced Monoblock mode (--monoblock)\n");
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate tile dimensions based on monoblock setting
|
// Calculate tile dimensions based on monoblock setting
|
||||||
@@ -421,10 +422,10 @@ tav_encoder_context_t *tav_encoder_create(const tav_encoder_params_t *params) {
|
|||||||
// Padded Tiling mode: multiple tiles of TILE_SIZE_X × TILE_SIZE_Y
|
// Padded Tiling mode: multiple tiles of TILE_SIZE_X × TILE_SIZE_Y
|
||||||
ctx->tiles_x = (ctx->width + TAV_TILE_SIZE_X - 1) / TAV_TILE_SIZE_X;
|
ctx->tiles_x = (ctx->width + TAV_TILE_SIZE_X - 1) / TAV_TILE_SIZE_X;
|
||||||
ctx->tiles_y = (ctx->height + TAV_TILE_SIZE_Y - 1) / TAV_TILE_SIZE_Y;
|
ctx->tiles_y = (ctx->height + TAV_TILE_SIZE_Y - 1) / TAV_TILE_SIZE_Y;
|
||||||
if (ctx->verbose) {
|
// if (ctx->verbose) {
|
||||||
printf("Padded Tiling mode: %dx%d tiles (%d total)\n",
|
printf("Padded Tiling mode: %dx%d tiles (%d total)\n",
|
||||||
ctx->tiles_x, ctx->tiles_y, ctx->tiles_x * ctx->tiles_y);
|
ctx->tiles_x, ctx->tiles_y, ctx->tiles_x * ctx->tiles_y);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate decomp levels if auto (0)
|
// Calculate decomp levels if auto (0)
|
||||||
@@ -655,9 +656,17 @@ void tav_encoder_get_stats(tav_encoder_context_t *ctx, tav_encoder_stats_t *stat
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Frame Encoding (Single-threaded implementation for now)
|
// Frame Encoding - DEPRECATED, use tav_encoder_encode_gop() instead
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
|
/*
|
||||||
|
* tav_encoder_encode_frame() is deprecated and will be removed.
|
||||||
|
* Use tav_encoder_encode_gop() which works for both single-threaded and
|
||||||
|
* multi-threaded modes. The CLI should buffer frames and call encode_gop()
|
||||||
|
* when a full GOP is ready.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if 0 // DEPRECATED - kept for reference, will be deleted
|
||||||
int tav_encoder_encode_frame(tav_encoder_context_t *ctx,
|
int tav_encoder_encode_frame(tav_encoder_context_t *ctx,
|
||||||
const uint8_t *rgb_frame,
|
const uint8_t *rgb_frame,
|
||||||
int64_t frame_pts,
|
int64_t frame_pts,
|
||||||
@@ -733,11 +742,19 @@ int tav_encoder_encode_frame(tav_encoder_context_t *ctx,
|
|||||||
"Multi-threaded encoding not yet implemented");
|
"Multi-threaded encoding not yet implemented");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
#endif // DEPRECATED
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Flush Encoder
|
// Flush Encoder - DEPRECATED, CLI handles partial GOPs directly
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
|
/*
|
||||||
|
* tav_encoder_flush() is deprecated and will be removed.
|
||||||
|
* The CLI should track remaining frames and call tav_encoder_encode_gop()
|
||||||
|
* directly for partial GOPs at the end of encoding.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if 0 // DEPRECATED - kept for reference, will be deleted
|
||||||
int tav_encoder_flush(tav_encoder_context_t *ctx,
|
int tav_encoder_flush(tav_encoder_context_t *ctx,
|
||||||
tav_encoder_packet_t **packet) {
|
tav_encoder_packet_t **packet) {
|
||||||
if (!ctx || !packet) {
|
if (!ctx || !packet) {
|
||||||
@@ -847,6 +864,7 @@ int tav_encoder_flush(tav_encoder_context_t *ctx,
|
|||||||
|
|
||||||
return 0; // No more packets
|
return 0; // No more packets
|
||||||
}
|
}
|
||||||
|
#endif // DEPRECATED
|
||||||
|
|
||||||
void tav_encoder_free_packet(tav_encoder_packet_t *packet) {
|
void tav_encoder_free_packet(tav_encoder_packet_t *packet) {
|
||||||
if (!packet) return;
|
if (!packet) return;
|
||||||
@@ -1359,72 +1377,167 @@ static int encode_gop_intra_only(tav_encoder_context_t *ctx, gop_slot_t *slot) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate work buffers for single frame
|
// Step 1: RGB to YCoCg-R (or ICtCp) for full frame
|
||||||
float *work_y = tav_calloc(num_pixels, sizeof(float));
|
float *frame_y = tav_calloc(num_pixels, sizeof(float));
|
||||||
float *work_co = tav_calloc(num_pixels, sizeof(float));
|
float *frame_co = tav_calloc(num_pixels, sizeof(float));
|
||||||
float *work_cg = tav_calloc(num_pixels, sizeof(float));
|
float *frame_cg = tav_calloc(num_pixels, sizeof(float));
|
||||||
int16_t *quant_y = tav_calloc(num_pixels, sizeof(int16_t));
|
|
||||||
int16_t *quant_co = tav_calloc(num_pixels, sizeof(int16_t));
|
|
||||||
int16_t *quant_cg = tav_calloc(num_pixels, sizeof(int16_t));
|
|
||||||
|
|
||||||
// Step 1: RGB to YCoCg-R (or ICtCp)
|
rgb_to_colour_space_frame(ctx, slot->rgb_frames[0], frame_y, frame_co, frame_cg, width, height);
|
||||||
rgb_to_colour_space_frame(ctx, slot->rgb_frames[0], work_y, work_co, work_cg, width, height);
|
|
||||||
|
|
||||||
// Step 2: Apply 2D DWT
|
// Get quantiser values from QLUT indices
|
||||||
tav_dwt_2d_forward(work_y, width, height, ctx->decomp_levels, ctx->wavelet_type);
|
|
||||||
tav_dwt_2d_forward(work_co, width, height, ctx->decomp_levels, ctx->wavelet_type);
|
|
||||||
tav_dwt_2d_forward(work_cg, width, height, ctx->decomp_levels, ctx->wavelet_type);
|
|
||||||
|
|
||||||
// Step 3: Quantize coefficients
|
|
||||||
// ctx->quantiser_y/co/cg contain QLUT indices, lookup actual quantiser values
|
|
||||||
int base_quantiser_y = QLUT[ctx->quantiser_y];
|
int base_quantiser_y = QLUT[ctx->quantiser_y];
|
||||||
int base_quantiser_co = QLUT[ctx->quantiser_co];
|
int base_quantiser_co = QLUT[ctx->quantiser_co];
|
||||||
int base_quantiser_cg = QLUT[ctx->quantiser_cg];
|
int base_quantiser_cg = QLUT[ctx->quantiser_cg];
|
||||||
|
|
||||||
if (ctx->perceptual_tuning) {
|
// Allocate preprocess buffer for all tiles
|
||||||
tav_quantise_perceptual(ctx->compat_enc, work_y, quant_y, num_pixels,
|
// For tiled mode: num_tiles * (4-byte header + max_tile_coeff_size * 3 * sizeof(int16_t))
|
||||||
base_quantiser_y, (float)ctx->dead_zone_threshold, width, height, ctx->decomp_levels, 0, 0);
|
// For monoblock: just the frame
|
||||||
tav_quantise_perceptual(ctx->compat_enc, work_co, quant_co, num_pixels,
|
const int tile_coeff_count = ctx->monoblock ? num_pixels : (TAV_PADDED_TILE_SIZE_X * TAV_PADDED_TILE_SIZE_Y);
|
||||||
base_quantiser_co, (float)ctx->dead_zone_threshold, width, height, ctx->decomp_levels, 1, 0);
|
const int num_tiles = ctx->tiles_x * ctx->tiles_y;
|
||||||
tav_quantise_perceptual(ctx->compat_enc, work_cg, quant_cg, num_pixels,
|
size_t preprocess_capacity = num_tiles * (4 + tile_coeff_count * 3 * sizeof(int16_t) * 2); // Conservative with EZBC overhead
|
||||||
base_quantiser_cg, (float)ctx->dead_zone_threshold, width, height, ctx->decomp_levels, 1, 0);
|
uint8_t *preprocess_buffer = tav_malloc(preprocess_capacity);
|
||||||
|
size_t preprocess_offset = 0;
|
||||||
|
|
||||||
|
if (ctx->monoblock) {
|
||||||
|
// ======================================================================
|
||||||
|
// Monoblock mode: process entire frame as single tile
|
||||||
|
// ======================================================================
|
||||||
|
int16_t *quant_y = tav_calloc(num_pixels, sizeof(int16_t));
|
||||||
|
int16_t *quant_co = tav_calloc(num_pixels, sizeof(int16_t));
|
||||||
|
int16_t *quant_cg = tav_calloc(num_pixels, sizeof(int16_t));
|
||||||
|
|
||||||
|
// Apply 2D DWT to full frame
|
||||||
|
tav_dwt_2d_forward(frame_y, width, height, ctx->decomp_levels, ctx->wavelet_type);
|
||||||
|
tav_dwt_2d_forward(frame_co, width, height, ctx->decomp_levels, ctx->wavelet_type);
|
||||||
|
tav_dwt_2d_forward(frame_cg, width, height, ctx->decomp_levels, ctx->wavelet_type);
|
||||||
|
|
||||||
|
// Quantize
|
||||||
|
if (ctx->perceptual_tuning) {
|
||||||
|
tav_quantise_perceptual(ctx->compat_enc, frame_y, quant_y, num_pixels,
|
||||||
|
base_quantiser_y, (float)ctx->dead_zone_threshold, width, height, ctx->decomp_levels, 0, 0);
|
||||||
|
tav_quantise_perceptual(ctx->compat_enc, frame_co, quant_co, num_pixels,
|
||||||
|
base_quantiser_co, (float)ctx->dead_zone_threshold, width, height, ctx->decomp_levels, 1, 0);
|
||||||
|
tav_quantise_perceptual(ctx->compat_enc, frame_cg, quant_cg, num_pixels,
|
||||||
|
base_quantiser_cg, (float)ctx->dead_zone_threshold, width, height, ctx->decomp_levels, 1, 0);
|
||||||
|
} else {
|
||||||
|
tav_quantise_uniform(frame_y, quant_y, num_pixels, base_quantiser_y,
|
||||||
|
(float)ctx->dead_zone_threshold, width, height,
|
||||||
|
ctx->decomp_levels, 0);
|
||||||
|
tav_quantise_uniform(frame_co, quant_co, num_pixels, base_quantiser_co,
|
||||||
|
(float)ctx->dead_zone_threshold, width, height,
|
||||||
|
ctx->decomp_levels, 1);
|
||||||
|
tav_quantise_uniform(frame_cg, quant_cg, num_pixels, base_quantiser_cg,
|
||||||
|
(float)ctx->dead_zone_threshold, width, height,
|
||||||
|
ctx->decomp_levels, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// EZBC encode
|
||||||
|
preprocess_offset = preprocess_coefficients_ezbc(
|
||||||
|
quant_y, quant_co, quant_cg, NULL,
|
||||||
|
num_pixels, width, height, ctx->channel_layout,
|
||||||
|
preprocess_buffer
|
||||||
|
);
|
||||||
|
|
||||||
|
free(quant_y); free(quant_co); free(quant_cg);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
tav_quantise_uniform(work_y, quant_y, num_pixels, base_quantiser_y,
|
// ======================================================================
|
||||||
(float)ctx->dead_zone_threshold, width, height,
|
// Tiled mode: process each tile independently
|
||||||
ctx->decomp_levels, 0);
|
// ======================================================================
|
||||||
tav_quantise_uniform(work_co, quant_co, num_pixels, base_quantiser_co,
|
const int padded_pixels = TAV_PADDED_TILE_SIZE_X * TAV_PADDED_TILE_SIZE_Y;
|
||||||
(float)ctx->dead_zone_threshold, width, height,
|
|
||||||
ctx->decomp_levels, 1);
|
// Allocate reusable tile buffers
|
||||||
tav_quantise_uniform(work_cg, quant_cg, num_pixels, base_quantiser_cg,
|
float *tile_y = tav_calloc(padded_pixels, sizeof(float));
|
||||||
(float)ctx->dead_zone_threshold, width, height,
|
float *tile_co = tav_calloc(padded_pixels, sizeof(float));
|
||||||
ctx->decomp_levels, 1);
|
float *tile_cg = tav_calloc(padded_pixels, sizeof(float));
|
||||||
|
int16_t *quant_y = tav_calloc(padded_pixels, sizeof(int16_t));
|
||||||
|
int16_t *quant_co = tav_calloc(padded_pixels, sizeof(int16_t));
|
||||||
|
int16_t *quant_cg = tav_calloc(padded_pixels, sizeof(int16_t));
|
||||||
|
|
||||||
|
for (int tile_y_idx = 0; tile_y_idx < ctx->tiles_y; tile_y_idx++) {
|
||||||
|
for (int tile_x_idx = 0; tile_x_idx < ctx->tiles_x; tile_x_idx++) {
|
||||||
|
// Write tile header: [mode(1)][qY_override(1)][qCo_override(1)][qCg_override(1)]
|
||||||
|
preprocess_buffer[preprocess_offset++] = 0x01; // TAV_MODE_INTRA
|
||||||
|
preprocess_buffer[preprocess_offset++] = 0; // qY override (0 = use header)
|
||||||
|
preprocess_buffer[preprocess_offset++] = 0; // qCo override
|
||||||
|
preprocess_buffer[preprocess_offset++] = 0; // qCg override
|
||||||
|
|
||||||
|
// Extract padded tile from full frame
|
||||||
|
tav_extract_padded_tile(frame_y, frame_co, frame_cg,
|
||||||
|
width, height,
|
||||||
|
tile_x_idx, tile_y_idx,
|
||||||
|
tile_y, tile_co, tile_cg);
|
||||||
|
|
||||||
|
// Apply 2D DWT to padded tile
|
||||||
|
tav_dwt_2d_forward_padded_tile(tile_y, ctx->decomp_levels, ctx->wavelet_type);
|
||||||
|
tav_dwt_2d_forward_padded_tile(tile_co, ctx->decomp_levels, ctx->wavelet_type);
|
||||||
|
tav_dwt_2d_forward_padded_tile(tile_cg, ctx->decomp_levels, ctx->wavelet_type);
|
||||||
|
|
||||||
|
// Quantize tile coefficients
|
||||||
|
if (ctx->perceptual_tuning) {
|
||||||
|
tav_quantise_perceptual(ctx->compat_enc, tile_y, quant_y, padded_pixels,
|
||||||
|
base_quantiser_y, (float)ctx->dead_zone_threshold,
|
||||||
|
TAV_PADDED_TILE_SIZE_X, TAV_PADDED_TILE_SIZE_Y,
|
||||||
|
ctx->decomp_levels, 0, 0);
|
||||||
|
tav_quantise_perceptual(ctx->compat_enc, tile_co, quant_co, padded_pixels,
|
||||||
|
base_quantiser_co, (float)ctx->dead_zone_threshold,
|
||||||
|
TAV_PADDED_TILE_SIZE_X, TAV_PADDED_TILE_SIZE_Y,
|
||||||
|
ctx->decomp_levels, 1, 0);
|
||||||
|
tav_quantise_perceptual(ctx->compat_enc, tile_cg, quant_cg, padded_pixels,
|
||||||
|
base_quantiser_cg, (float)ctx->dead_zone_threshold,
|
||||||
|
TAV_PADDED_TILE_SIZE_X, TAV_PADDED_TILE_SIZE_Y,
|
||||||
|
ctx->decomp_levels, 1, 0);
|
||||||
|
} else {
|
||||||
|
tav_quantise_uniform(tile_y, quant_y, padded_pixels, base_quantiser_y,
|
||||||
|
(float)ctx->dead_zone_threshold,
|
||||||
|
TAV_PADDED_TILE_SIZE_X, TAV_PADDED_TILE_SIZE_Y,
|
||||||
|
ctx->decomp_levels, 0);
|
||||||
|
tav_quantise_uniform(tile_co, quant_co, padded_pixels, base_quantiser_co,
|
||||||
|
(float)ctx->dead_zone_threshold,
|
||||||
|
TAV_PADDED_TILE_SIZE_X, TAV_PADDED_TILE_SIZE_Y,
|
||||||
|
ctx->decomp_levels, 1);
|
||||||
|
tav_quantise_uniform(tile_cg, quant_cg, padded_pixels, base_quantiser_cg,
|
||||||
|
(float)ctx->dead_zone_threshold,
|
||||||
|
TAV_PADDED_TILE_SIZE_X, TAV_PADDED_TILE_SIZE_Y,
|
||||||
|
ctx->decomp_levels, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// EZBC encode tile
|
||||||
|
size_t tile_size = preprocess_coefficients_ezbc(
|
||||||
|
quant_y, quant_co, quant_cg, NULL,
|
||||||
|
padded_pixels, TAV_PADDED_TILE_SIZE_X, TAV_PADDED_TILE_SIZE_Y,
|
||||||
|
ctx->channel_layout,
|
||||||
|
preprocess_buffer + preprocess_offset
|
||||||
|
);
|
||||||
|
preprocess_offset += tile_size;
|
||||||
|
|
||||||
|
// Clear tile buffers for next iteration
|
||||||
|
memset(tile_y, 0, padded_pixels * sizeof(float));
|
||||||
|
memset(tile_co, 0, padded_pixels * sizeof(float));
|
||||||
|
memset(tile_cg, 0, padded_pixels * sizeof(float));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(tile_y); free(tile_co); free(tile_cg);
|
||||||
|
free(quant_y); free(quant_co); free(quant_cg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Preprocess coefficients
|
// Free full-frame YCoCg buffers
|
||||||
size_t preprocess_capacity = num_pixels * 3 * sizeof(int16_t) + 65536; // Conservative
|
free(frame_y); free(frame_co); free(frame_cg);
|
||||||
uint8_t *preprocess_buffer = tav_malloc(preprocess_capacity);
|
|
||||||
|
|
||||||
// Use EZBC preprocessing (Twobitmap is deprecated)
|
// Step 5: Zstd compress all tile data
|
||||||
size_t preprocessed_size = preprocess_coefficients_ezbc(
|
size_t compressed_bound = ZSTD_compressBound(preprocess_offset);
|
||||||
quant_y, quant_co, quant_cg, NULL,
|
|
||||||
num_pixels, width, height, ctx->channel_layout,
|
|
||||||
preprocess_buffer
|
|
||||||
);
|
|
||||||
|
|
||||||
// Step 5: Zstd compress
|
|
||||||
size_t compressed_bound = ZSTD_compressBound(preprocessed_size);
|
|
||||||
uint8_t *compression_buffer = tav_malloc(compressed_bound);
|
uint8_t *compression_buffer = tav_malloc(compressed_bound);
|
||||||
|
|
||||||
size_t compressed_size = ZSTD_compress(
|
size_t compressed_size = ZSTD_compress(
|
||||||
compression_buffer, compressed_bound,
|
compression_buffer, compressed_bound,
|
||||||
preprocess_buffer, preprocessed_size,
|
preprocess_buffer, preprocess_offset,
|
||||||
ctx->zstd_level
|
ctx->zstd_level
|
||||||
);
|
);
|
||||||
|
|
||||||
|
free(preprocess_buffer);
|
||||||
|
|
||||||
if (ZSTD_isError(compressed_size)) {
|
if (ZSTD_isError(compressed_size)) {
|
||||||
free(work_y); free(work_co); free(work_cg);
|
|
||||||
free(quant_y); free(quant_co); free(quant_cg);
|
|
||||||
free(preprocess_buffer);
|
|
||||||
free(compression_buffer);
|
free(compression_buffer);
|
||||||
snprintf(slot->error_message, MAX_ERROR_MESSAGE,
|
snprintf(slot->error_message, MAX_ERROR_MESSAGE,
|
||||||
"Zstd compression failed: %s", ZSTD_getErrorName(compressed_size));
|
"Zstd compression failed: %s", ZSTD_getErrorName(compressed_size));
|
||||||
@@ -1452,10 +1565,6 @@ static int encode_gop_intra_only(tav_encoder_context_t *ctx, gop_slot_t *slot) {
|
|||||||
slot->packets = pkt;
|
slot->packets = pkt;
|
||||||
slot->num_packets = 1;
|
slot->num_packets = 1;
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
free(work_y); free(work_co); free(work_cg);
|
|
||||||
free(quant_y); free(quant_co); free(quant_cg);
|
|
||||||
free(preprocess_buffer);
|
|
||||||
free(compression_buffer);
|
free(compression_buffer);
|
||||||
|
|
||||||
return 0; // Success
|
return 0; // Success
|
||||||
|
|||||||
@@ -91,6 +91,11 @@ typedef struct {
|
|||||||
size_t total_bytes;
|
size_t total_bytes;
|
||||||
time_t start_time;
|
time_t start_time;
|
||||||
|
|
||||||
|
// GOP frame buffer (for tav_encoder_encode_gop())
|
||||||
|
uint8_t **gop_frames; // Array of frame pointers [gop_size]
|
||||||
|
int gop_frame_count; // Number of frames in current GOP
|
||||||
|
int *gop_frame_numbers; // Frame numbers for timecodes [gop_size]
|
||||||
|
|
||||||
// CLI options
|
// CLI options
|
||||||
int verbose;
|
int verbose;
|
||||||
int encode_limit; // Max frames to encode (0=all)
|
int encode_limit; // Max frames to encode (0=all)
|
||||||
@@ -254,7 +259,7 @@ static int get_video_info(const char *input_file, int *width, int *height,
|
|||||||
static FILE* open_ffmpeg_pipe(const char *input_file, int width, int height) {
|
static FILE* open_ffmpeg_pipe(const char *input_file, int width, int height) {
|
||||||
char cmd[MAX_PATH * 2];
|
char cmd[MAX_PATH * 2];
|
||||||
snprintf(cmd, sizeof(cmd),
|
snprintf(cmd, sizeof(cmd),
|
||||||
"ffmpeg -i \"%s\" -f rawvideo -pix_fmt rgb24 -vf \"scale=%d:%d:force_original_aspect_ratio=increase,crop=%d:%d\" -",
|
"ffmpeg -hide_banner -v quiet -i \"%s\" -f rawvideo -pix_fmt rgb24 -vf \"scale=%d:%d:force_original_aspect_ratio=increase,crop=%d:%d\" -",
|
||||||
input_file, width, height, width, height);
|
input_file, width, height, width, height);
|
||||||
|
|
||||||
FILE *fp = popen(cmd, "r");
|
FILE *fp = popen(cmd, "r");
|
||||||
@@ -305,12 +310,22 @@ static int write_tav_header(FILE *fp, const tav_encoder_params_t *params, int ha
|
|||||||
// 6 = ICtCp monoblock perceptual
|
// 6 = ICtCp monoblock perceptual
|
||||||
// Add 8 if using CDF 5/3 temporal wavelet
|
// Add 8 if using CDF 5/3 temporal wavelet
|
||||||
uint8_t version;
|
uint8_t version;
|
||||||
if (params->perceptual_tuning) {
|
if (params->monoblock) {
|
||||||
// Monoblock perceptual: version 5 (YCoCg-R) or 6 (ICtCp)
|
if (params->perceptual_tuning) {
|
||||||
version = params->channel_layout ? 6 : 5;
|
// Monoblock perceptual: version 5 (YCoCg-R) or 6 (ICtCp)
|
||||||
|
version = params->channel_layout ? 6 : 5;
|
||||||
|
} else {
|
||||||
|
// Monoblock uniform: version 3 (YCoCg-R) or 4 (ICtCp)
|
||||||
|
version = params->channel_layout ? 4 : 3;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Monoblock uniform: version 3 (YCoCg-R) or 4 (ICtCp)
|
if (params->perceptual_tuning) {
|
||||||
version = params->channel_layout ? 4 : 3;
|
// Tiled perceptual: version 7 (YCoCg-R) or 8 (ICtCp)
|
||||||
|
version = params->channel_layout ? 7 : 8;
|
||||||
|
} else {
|
||||||
|
// Tiled uniform: version 1 (YCoCg-R) or 2 (ICtCp)
|
||||||
|
version = params->channel_layout ? 1 : 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Add 8 if using CDF 5/3 temporal wavelet
|
// Add 8 if using CDF 5/3 temporal wavelet
|
||||||
if (params->enable_temporal_dwt && params->temporal_wavelet == 0) {
|
if (params->enable_temporal_dwt && params->temporal_wavelet == 0) {
|
||||||
@@ -468,7 +483,7 @@ static int write_gop_sync_packet(FILE *fp, int frame_count) {
|
|||||||
static int extract_audio_to_file(const char *input_file, const char *output_file) {
|
static int extract_audio_to_file(const char *input_file, const char *output_file) {
|
||||||
char cmd[MAX_PATH * 2];
|
char cmd[MAX_PATH * 2];
|
||||||
snprintf(cmd, sizeof(cmd),
|
snprintf(cmd, sizeof(cmd),
|
||||||
"ffmpeg -v quiet -i \"%s\" -f f32le -acodec pcm_f32le -ar %d -ac 2 "
|
"ffmpeg -hide_banner -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\" "
|
"-af \"aresample=resampler=soxr:precision=28:cutoff=0.99:dither_scale=0,highpass=f=16\" "
|
||||||
"-y \"%s\" 2>/dev/null",
|
"-y \"%s\" 2>/dev/null",
|
||||||
input_file, AUDIO_SAMPLE_RATE, output_file);
|
input_file, AUDIO_SAMPLE_RATE, output_file);
|
||||||
@@ -952,11 +967,51 @@ static int encode_video(cli_context_t *cli) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate frame buffer
|
// Allocate GOP frame buffer for tav_encoder_encode_gop()
|
||||||
size_t frame_size = cli->enc_params.width * cli->enc_params.height * 3;
|
size_t frame_size = cli->enc_params.width * cli->enc_params.height * 3;
|
||||||
|
int gop_size = cli->enc_params.gop_size;
|
||||||
|
|
||||||
|
// In intra-only mode, encode each frame immediately (GOP size = 1)
|
||||||
|
if (!cli->enc_params.enable_temporal_dwt) {
|
||||||
|
gop_size = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cli->gop_frames = malloc(gop_size * sizeof(uint8_t*));
|
||||||
|
cli->gop_frame_numbers = malloc(gop_size * sizeof(int));
|
||||||
|
cli->gop_frame_count = 0;
|
||||||
|
|
||||||
|
if (!cli->gop_frames || !cli->gop_frame_numbers) {
|
||||||
|
fprintf(stderr, "Error: Failed to allocate GOP frame buffer\n");
|
||||||
|
tav_encoder_free(ctx);
|
||||||
|
pclose(cli->ffmpeg_pipe);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < gop_size; i++) {
|
||||||
|
cli->gop_frames[i] = malloc(frame_size);
|
||||||
|
if (!cli->gop_frames[i]) {
|
||||||
|
fprintf(stderr, "Error: Failed to allocate GOP frame %d\n", i);
|
||||||
|
for (int j = 0; j < i; j++) free(cli->gop_frames[j]);
|
||||||
|
free(cli->gop_frames);
|
||||||
|
free(cli->gop_frame_numbers);
|
||||||
|
tav_encoder_free(ctx);
|
||||||
|
pclose(cli->ffmpeg_pipe);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cli->verbose) {
|
||||||
|
printf(" GOP frame buffer: %d frames x %zu bytes = %zu KB\n",
|
||||||
|
gop_size, frame_size, (gop_size * frame_size) / 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temporary frame buffer for reading from FFmpeg
|
||||||
uint8_t *rgb_frame = malloc(frame_size);
|
uint8_t *rgb_frame = malloc(frame_size);
|
||||||
if (!rgb_frame) {
|
if (!rgb_frame) {
|
||||||
fprintf(stderr, "Error: Failed to allocate frame buffer\n");
|
fprintf(stderr, "Error: Failed to allocate frame buffer\n");
|
||||||
|
for (int i = 0; i < gop_size; i++) free(cli->gop_frames[i]);
|
||||||
|
free(cli->gop_frames);
|
||||||
|
free(cli->gop_frame_numbers);
|
||||||
tav_encoder_free(ctx);
|
tav_encoder_free(ctx);
|
||||||
pclose(cli->ffmpeg_pipe);
|
pclose(cli->ffmpeg_pipe);
|
||||||
return -1;
|
return -1;
|
||||||
@@ -981,12 +1036,12 @@ static int encode_video(cli_context_t *cli) {
|
|||||||
write_fontrom_packet(cli->output_fp, cli->fontrom_high, FONTROM_OPCODE_HIGH, cli->verbose);
|
write_fontrom_packet(cli->output_fp, cli->fontrom_high, FONTROM_OPCODE_HIGH, cli->verbose);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encoding loop
|
// Encoding loop using tav_encoder_encode_gop()
|
||||||
printf("Encoding frames...\n");
|
printf("Encoding frames...\n");
|
||||||
cli->start_time = time(NULL);
|
cli->start_time = time(NULL);
|
||||||
|
|
||||||
int64_t frame_pts = 0;
|
|
||||||
tav_encoder_packet_t *packet = NULL;
|
tav_encoder_packet_t *packet = NULL;
|
||||||
|
int encoding_error = 0;
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
// Check encode limit
|
// Check encode limit
|
||||||
@@ -1000,9 +1055,15 @@ static int encode_video(cli_context_t *cli) {
|
|||||||
break; // EOF
|
break; // EOF
|
||||||
} else if (result < 0) {
|
} else if (result < 0) {
|
||||||
fprintf(stderr, "Error reading frame\n");
|
fprintf(stderr, "Error reading frame\n");
|
||||||
|
encoding_error = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copy frame to GOP buffer
|
||||||
|
memcpy(cli->gop_frames[cli->gop_frame_count], rgb_frame, frame_size);
|
||||||
|
cli->gop_frame_numbers[cli->gop_frame_count] = (int)cli->frame_count;
|
||||||
|
cli->gop_frame_count++;
|
||||||
|
|
||||||
// Accumulate audio samples for this frame (will write when GOP completes)
|
// Accumulate audio samples for this frame (will write when GOP completes)
|
||||||
if (cli->has_audio && cli->audio_buffer && cli->gop_audio_buffer) {
|
if (cli->has_audio && cli->audio_buffer && cli->gop_audio_buffer) {
|
||||||
size_t samples_read = read_audio_samples(cli, cli->audio_buffer, cli->samples_per_frame);
|
size_t samples_read = read_audio_samples(cli, cli->audio_buffer, cli->samples_per_frame);
|
||||||
@@ -1015,40 +1076,55 @@ static int encode_video(cli_context_t *cli) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode frame
|
cli->frame_count++;
|
||||||
result = tav_encoder_encode_frame(ctx, rgb_frame, frame_pts, &packet);
|
|
||||||
|
|
||||||
if (result < 0) {
|
// Check if GOP is full
|
||||||
fprintf(stderr, "Error: %s\n", tav_encoder_get_error(ctx));
|
if (cli->gop_frame_count >= gop_size) {
|
||||||
break;
|
// Encode complete GOP
|
||||||
}
|
result = tav_encoder_encode_gop(ctx,
|
||||||
|
(const uint8_t**)cli->gop_frames,
|
||||||
|
cli->gop_frame_count,
|
||||||
|
cli->gop_frame_numbers,
|
||||||
|
&packet);
|
||||||
|
|
||||||
if (result > 0 && packet) {
|
if (result < 0) {
|
||||||
// GOP is complete - write in correct order: TIMECODE, AUDIO, VIDEO, GOP_SYNC
|
fprintf(stderr, "Error: %s\n", tav_encoder_get_error(ctx));
|
||||||
|
encoding_error = 1;
|
||||||
// 1. Write timecode before GOP
|
break;
|
||||||
write_timecode_packet(cli->output_fp, cli->frame_count - (cli->frame_count % cli->enc_params.gop_size),
|
|
||||||
cli->enc_params.fps_num, cli->enc_params.fps_den);
|
|
||||||
|
|
||||||
// 2. Write accumulated audio for this GOP as single TAD packet
|
|
||||||
if (cli->has_audio && cli->gop_audio_samples > 0) {
|
|
||||||
write_audio_packet(cli->output_fp, cli, cli->gop_audio_buffer, cli->gop_audio_samples);
|
|
||||||
cli->gop_audio_samples = 0; // Reset for next GOP
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Write video GOP packet
|
if (packet) {
|
||||||
write_tav_packet(cli->output_fp, packet);
|
// GOP is complete - write in correct order: TIMECODE, AUDIO, VIDEO, GOP_SYNC
|
||||||
cli->total_bytes += packet->size;
|
|
||||||
cli->gop_count++;
|
|
||||||
|
|
||||||
// 4. Write GOP_SYNC after GOP packets (0x12 = GOP unified)
|
// 1. Write timecode before GOP (use first frame number in GOP)
|
||||||
if (packet->packet_type == TAV_PACKET_GOP_UNIFIED) {
|
write_timecode_packet(cli->output_fp, cli->gop_frame_numbers[0],
|
||||||
// Extract GOP size from packet (byte 1)
|
cli->enc_params.fps_num, cli->enc_params.fps_den);
|
||||||
int gop_size = packet->data[1];
|
|
||||||
write_gop_sync_packet(cli->output_fp, gop_size);
|
// 2. Write accumulated audio for this GOP as single TAD packet
|
||||||
|
if (cli->has_audio && cli->gop_audio_samples > 0) {
|
||||||
|
write_audio_packet(cli->output_fp, cli, cli->gop_audio_buffer, cli->gop_audio_samples);
|
||||||
|
cli->gop_audio_samples = 0; // Reset for next GOP
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Write video GOP packet
|
||||||
|
write_tav_packet(cli->output_fp, packet);
|
||||||
|
cli->total_bytes += packet->size;
|
||||||
|
cli->gop_count++;
|
||||||
|
|
||||||
|
// 4. Write GOP_SYNC after GOP packets
|
||||||
|
if (packet->packet_type == TAV_PACKET_GOP_UNIFIED) {
|
||||||
|
int frames_in_gop = packet->data[1];
|
||||||
|
write_gop_sync_packet(cli->output_fp, frames_in_gop);
|
||||||
|
} else if (packet->packet_type == TAV_PACKET_IFRAME) {
|
||||||
|
write_gop_sync_packet(cli->output_fp, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
tav_encoder_free_packet(packet);
|
||||||
|
packet = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
tav_encoder_free_packet(packet);
|
// Reset GOP buffer
|
||||||
|
cli->gop_frame_count = 0;
|
||||||
|
|
||||||
// Progress
|
// Progress
|
||||||
if (cli->verbose || cli->frame_count % 60 == 0) {
|
if (cli->verbose || cli->frame_count % 60 == 0) {
|
||||||
@@ -1065,21 +1141,27 @@ static int encode_video(cli_context_t *cli) {
|
|||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cli->frame_count++;
|
|
||||||
frame_pts++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
|
||||||
// Flush encoder
|
// Encode remaining frames in GOP buffer (partial GOP)
|
||||||
printf("Flushing encoder...\n");
|
if (!encoding_error && cli->gop_frame_count > 0) {
|
||||||
while (tav_encoder_flush(ctx, &packet) > 0) {
|
printf("Encoding final partial GOP (%d frames)...\n", cli->gop_frame_count);
|
||||||
if (packet) {
|
|
||||||
|
int result = tav_encoder_encode_gop(ctx,
|
||||||
|
(const uint8_t**)cli->gop_frames,
|
||||||
|
cli->gop_frame_count,
|
||||||
|
cli->gop_frame_numbers,
|
||||||
|
&packet);
|
||||||
|
|
||||||
|
if (result < 0) {
|
||||||
|
fprintf(stderr, "Error encoding final GOP: %s\n", tav_encoder_get_error(ctx));
|
||||||
|
} else if (packet) {
|
||||||
// Write remaining packets in correct order: TIMECODE, AUDIO, VIDEO, GOP_SYNC
|
// Write remaining packets in correct order: TIMECODE, AUDIO, VIDEO, GOP_SYNC
|
||||||
|
|
||||||
// 1. Write timecode
|
// 1. Write timecode
|
||||||
write_timecode_packet(cli->output_fp, cli->frame_count - (cli->frame_count % cli->enc_params.gop_size),
|
write_timecode_packet(cli->output_fp, cli->gop_frame_numbers[0],
|
||||||
cli->enc_params.fps_num, cli->enc_params.fps_den);
|
cli->enc_params.fps_num, cli->enc_params.fps_den);
|
||||||
|
|
||||||
// 2. Write any remaining accumulated audio for this GOP
|
// 2. Write any remaining accumulated audio for this GOP
|
||||||
@@ -1095,8 +1177,8 @@ static int encode_video(cli_context_t *cli) {
|
|||||||
|
|
||||||
// 4. Write GOP_SYNC after GOP packets
|
// 4. Write GOP_SYNC after GOP packets
|
||||||
if (packet->packet_type == TAV_PACKET_GOP_UNIFIED) {
|
if (packet->packet_type == TAV_PACKET_GOP_UNIFIED) {
|
||||||
int gop_size = packet->data[1];
|
int frames_in_gop = packet->data[1];
|
||||||
write_gop_sync_packet(cli->output_fp, gop_size);
|
write_gop_sync_packet(cli->output_fp, frames_in_gop);
|
||||||
} else if (packet->packet_type == TAV_PACKET_IFRAME) {
|
} else if (packet->packet_type == TAV_PACKET_IFRAME) {
|
||||||
write_gop_sync_packet(cli->output_fp, 1);
|
write_gop_sync_packet(cli->output_fp, 1);
|
||||||
}
|
}
|
||||||
@@ -1113,6 +1195,19 @@ static int encode_video(cli_context_t *cli) {
|
|||||||
tav_encoder_free(ctx);
|
tav_encoder_free(ctx);
|
||||||
pclose(cli->ffmpeg_pipe);
|
pclose(cli->ffmpeg_pipe);
|
||||||
|
|
||||||
|
// Cleanup GOP frame buffer
|
||||||
|
if (cli->gop_frames) {
|
||||||
|
for (int i = 0; i < gop_size; i++) {
|
||||||
|
free(cli->gop_frames[i]);
|
||||||
|
}
|
||||||
|
free(cli->gop_frames);
|
||||||
|
cli->gop_frames = NULL;
|
||||||
|
}
|
||||||
|
if (cli->gop_frame_numbers) {
|
||||||
|
free(cli->gop_frame_numbers);
|
||||||
|
cli->gop_frame_numbers = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
// Cleanup audio resources
|
// Cleanup audio resources
|
||||||
if (cli->audio_buffer) {
|
if (cli->audio_buffer) {
|
||||||
free(cli->audio_buffer);
|
free(cli->audio_buffer);
|
||||||
|
|||||||
Reference in New Issue
Block a user