mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-11 23:34:04 +09:00
tav: librarying
This commit is contained in:
415
video_encoder/lib/libtavenc/tav_encoder_ezbc.c
Normal file
415
video_encoder/lib/libtavenc/tav_encoder_ezbc.c
Normal file
@@ -0,0 +1,415 @@
|
||||
/**
|
||||
* TAV Encoder - EZBC (Embedded Zero Block Coding) Library
|
||||
*
|
||||
* Implements binary tree embedded zero block coding for efficient storage
|
||||
* of sparse wavelet coefficients. Exploits coefficient sparsity through
|
||||
* hierarchical significance testing and progressive bitplane encoding.
|
||||
*
|
||||
* Extracted from encoder_tav.c as part of library refactoring.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <math.h>
|
||||
|
||||
// =============================================================================
|
||||
// EZBC Structures
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Bitstream writer for bit-level encoding.
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t *data;
|
||||
size_t capacity;
|
||||
size_t byte_pos;
|
||||
uint8_t bit_pos; // 0-7, current bit position in current byte
|
||||
} bitstream_t;
|
||||
|
||||
/**
|
||||
* Block structure for EZBC quadtree decomposition.
|
||||
*/
|
||||
typedef struct {
|
||||
int x, y; // Top-left position in 2D coefficient array
|
||||
int width, height; // Block dimensions
|
||||
} ezbc_block_t;
|
||||
|
||||
/**
|
||||
* Queue for EZBC block processing.
|
||||
*/
|
||||
typedef struct {
|
||||
ezbc_block_t *blocks;
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
} block_queue_t;
|
||||
|
||||
/**
|
||||
* Track coefficient state for refinement.
|
||||
*/
|
||||
typedef struct {
|
||||
bool significant; // Has been marked significant
|
||||
int first_bitplane; // Bitplane where it became significant
|
||||
} coeff_state_t;
|
||||
|
||||
/**
|
||||
* EZBC encoding context for recursive processing.
|
||||
*/
|
||||
typedef struct {
|
||||
bitstream_t *bs;
|
||||
int16_t *coeffs;
|
||||
coeff_state_t *states;
|
||||
int width;
|
||||
int height;
|
||||
int bitplane;
|
||||
int threshold;
|
||||
block_queue_t *next_insignificant;
|
||||
block_queue_t *next_significant;
|
||||
int *sign_count;
|
||||
} ezbc_context_t;
|
||||
|
||||
// =============================================================================
|
||||
// Bitstream Operations
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Initialize bitstream with initial capacity.
|
||||
*/
|
||||
static void bitstream_init(bitstream_t *bs, size_t initial_capacity) {
|
||||
// Ensure minimum capacity to avoid issues with zero-size allocations
|
||||
if (initial_capacity < 64) initial_capacity = 64;
|
||||
bs->capacity = initial_capacity;
|
||||
bs->data = calloc(1, initial_capacity);
|
||||
if (!bs->data) {
|
||||
fprintf(stderr, "ERROR: Failed to allocate bitstream buffer of size %zu\n", initial_capacity);
|
||||
exit(1);
|
||||
}
|
||||
bs->byte_pos = 0;
|
||||
bs->bit_pos = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a single bit to bitstream.
|
||||
*/
|
||||
static void bitstream_write_bit(bitstream_t *bs, int bit) {
|
||||
// Grow if needed
|
||||
if (bs->byte_pos >= bs->capacity) {
|
||||
size_t old_capacity = bs->capacity;
|
||||
bs->capacity *= 2;
|
||||
bs->data = realloc(bs->data, bs->capacity);
|
||||
// Clear only the newly allocated memory region
|
||||
memset(bs->data + old_capacity, 0, bs->capacity - old_capacity);
|
||||
}
|
||||
|
||||
if (bit) {
|
||||
bs->data[bs->byte_pos] |= (1 << bs->bit_pos);
|
||||
}
|
||||
|
||||
bs->bit_pos++;
|
||||
if (bs->bit_pos == 8) {
|
||||
bs->bit_pos = 0;
|
||||
bs->byte_pos++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write multiple bits to bitstream (LSB first).
|
||||
*/
|
||||
static void bitstream_write_bits(bitstream_t *bs, uint32_t value, int num_bits) {
|
||||
for (int i = 0; i < num_bits; i++) {
|
||||
bitstream_write_bit(bs, (value >> i) & 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current bitstream size in bytes.
|
||||
*/
|
||||
static size_t bitstream_size(bitstream_t *bs) {
|
||||
return bs->byte_pos + (bs->bit_pos > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Free bitstream buffer.
|
||||
*/
|
||||
static void bitstream_free(bitstream_t *bs) {
|
||||
free(bs->data);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Block Queue Operations
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Initialize block queue with initial capacity.
|
||||
*/
|
||||
static void queue_init(block_queue_t *q) {
|
||||
q->capacity = 1024;
|
||||
q->blocks = malloc(q->capacity * sizeof(ezbc_block_t));
|
||||
q->count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push block onto queue, growing if needed.
|
||||
*/
|
||||
static void queue_push(block_queue_t *q, ezbc_block_t block) {
|
||||
if (q->count >= q->capacity) {
|
||||
q->capacity *= 2;
|
||||
q->blocks = realloc(q->blocks, q->capacity * sizeof(ezbc_block_t));
|
||||
}
|
||||
q->blocks[q->count++] = block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Free block queue.
|
||||
*/
|
||||
static void queue_free(block_queue_t *q) {
|
||||
free(q->blocks);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// EZBC Helper Functions
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Check if all coefficients in block have |coeff| < threshold.
|
||||
*/
|
||||
static bool is_zero_block_ezbc(int16_t *coeffs, int width, int height,
|
||||
const ezbc_block_t *block, int threshold) {
|
||||
for (int y = block->y; y < block->y + block->height && y < height; y++) {
|
||||
for (int x = block->x; x < block->x + block->width && x < width; x++) {
|
||||
int idx = y * width + x;
|
||||
if (abs(coeffs[idx]) >= threshold) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find maximum absolute value in coefficient array.
|
||||
*/
|
||||
static int find_max_abs_ezbc(int16_t *coeffs, size_t count) {
|
||||
int max_abs = 0;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
int abs_val = abs(coeffs[i]);
|
||||
if (abs_val > max_abs) {
|
||||
max_abs = abs_val;
|
||||
}
|
||||
}
|
||||
return max_abs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get MSB position (bitplane number).
|
||||
* Returns floor(log2(value)), i.e., the position of the highest set bit.
|
||||
*/
|
||||
static int get_msb_bitplane(int value) {
|
||||
if (value == 0) return 0;
|
||||
int bitplane = 0;
|
||||
while (value > 1) {
|
||||
value >>= 1;
|
||||
bitplane++;
|
||||
}
|
||||
return bitplane;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively process a significant block - subdivide until 1x1.
|
||||
*/
|
||||
static void process_significant_block_recursive(ezbc_context_t *ctx, ezbc_block_t block) {
|
||||
// If 1x1 block: emit sign bit and add to significant queue
|
||||
if (block.width == 1 && block.height == 1) {
|
||||
int idx = block.y * ctx->width + block.x;
|
||||
bitstream_write_bit(ctx->bs, ctx->coeffs[idx] < 0 ? 1 : 0);
|
||||
(*ctx->sign_count)++;
|
||||
ctx->states[idx].significant = true;
|
||||
ctx->states[idx].first_bitplane = ctx->bitplane;
|
||||
queue_push(ctx->next_significant, block);
|
||||
return;
|
||||
}
|
||||
|
||||
// Block is > 1x1: subdivide into children and recursively process each
|
||||
int mid_x = block.width / 2;
|
||||
int mid_y = block.height / 2;
|
||||
if (mid_x == 0) mid_x = 1;
|
||||
if (mid_y == 0) mid_y = 1;
|
||||
|
||||
// Process top-left child
|
||||
ezbc_block_t tl = {block.x, block.y, mid_x, mid_y};
|
||||
if (!is_zero_block_ezbc(ctx->coeffs, ctx->width, ctx->height, &tl, ctx->threshold)) {
|
||||
bitstream_write_bit(ctx->bs, 1); // Significant
|
||||
process_significant_block_recursive(ctx, tl);
|
||||
} else {
|
||||
bitstream_write_bit(ctx->bs, 0); // Insignificant
|
||||
queue_push(ctx->next_insignificant, tl);
|
||||
}
|
||||
|
||||
// Process top-right child (if exists)
|
||||
if (block.width > mid_x) {
|
||||
ezbc_block_t tr = {block.x + mid_x, block.y, block.width - mid_x, mid_y};
|
||||
if (!is_zero_block_ezbc(ctx->coeffs, ctx->width, ctx->height, &tr, ctx->threshold)) {
|
||||
bitstream_write_bit(ctx->bs, 1);
|
||||
process_significant_block_recursive(ctx, tr);
|
||||
} else {
|
||||
bitstream_write_bit(ctx->bs, 0);
|
||||
queue_push(ctx->next_insignificant, tr);
|
||||
}
|
||||
}
|
||||
|
||||
// Process bottom-left child (if exists)
|
||||
if (block.height > mid_y) {
|
||||
ezbc_block_t bl = {block.x, block.y + mid_y, mid_x, block.height - mid_y};
|
||||
if (!is_zero_block_ezbc(ctx->coeffs, ctx->width, ctx->height, &bl, ctx->threshold)) {
|
||||
bitstream_write_bit(ctx->bs, 1);
|
||||
process_significant_block_recursive(ctx, bl);
|
||||
} else {
|
||||
bitstream_write_bit(ctx->bs, 0);
|
||||
queue_push(ctx->next_insignificant, bl);
|
||||
}
|
||||
}
|
||||
|
||||
// Process bottom-right child (if exists)
|
||||
if (block.width > mid_x && block.height > mid_y) {
|
||||
ezbc_block_t br = {block.x + mid_x, block.y + mid_y, block.width - mid_x, block.height - mid_y};
|
||||
if (!is_zero_block_ezbc(ctx->coeffs, ctx->width, ctx->height, &br, ctx->threshold)) {
|
||||
bitstream_write_bit(ctx->bs, 1);
|
||||
process_significant_block_recursive(ctx, br);
|
||||
} else {
|
||||
bitstream_write_bit(ctx->bs, 0);
|
||||
queue_push(ctx->next_insignificant, br);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Main EZBC Encoding Function
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* EZBC encoding for a single channel.
|
||||
*
|
||||
* Uses two separate queues for insignificant blocks and significant 1x1 blocks.
|
||||
* Encodes coefficients progressively from MSB to LSB bitplane.
|
||||
*
|
||||
* Algorithm:
|
||||
* 1. Find MSB bitplane from maximum absolute coefficient value
|
||||
* 2. Write header: MSB bitplane, width, height
|
||||
* 3. For each bitplane from MSB to 0:
|
||||
* a. Process insignificant blocks: check if they become significant
|
||||
* b. For newly significant blocks: recursively subdivide until 1x1
|
||||
* c. Emit sign bits for newly significant 1x1 coefficients
|
||||
* d. Process already-significant coefficients: emit refinement bits
|
||||
* 4. Return encoded bitstream
|
||||
*
|
||||
* @param coeffs Input quantized coefficients (int16_t array)
|
||||
* @param count Number of coefficients
|
||||
* @param width Frame width
|
||||
* @param height Frame height
|
||||
* @param output Output buffer pointer (allocated by this function)
|
||||
* @return Encoded size in bytes
|
||||
*/
|
||||
size_t tav_encode_channel_ezbc(int16_t *coeffs, size_t count, int width, int height,
|
||||
uint8_t **output) {
|
||||
bitstream_t bs;
|
||||
bitstream_init(&bs, count / 4); // Initial guess
|
||||
|
||||
// Track coefficient significance
|
||||
coeff_state_t *states = calloc(count, sizeof(coeff_state_t));
|
||||
|
||||
// Find maximum value to determine MSB bitplane
|
||||
int max_abs = find_max_abs_ezbc(coeffs, count);
|
||||
int msb_bitplane = get_msb_bitplane(max_abs);
|
||||
|
||||
// Write header: MSB bitplane and dimensions
|
||||
bitstream_write_bits(&bs, msb_bitplane, 8);
|
||||
bitstream_write_bits(&bs, width, 16);
|
||||
bitstream_write_bits(&bs, height, 16);
|
||||
|
||||
// Initialise two queues: insignificant blocks and significant 1x1 blocks
|
||||
block_queue_t insignificant_queue, next_insignificant;
|
||||
block_queue_t significant_queue, next_significant;
|
||||
|
||||
queue_init(&insignificant_queue);
|
||||
queue_init(&next_insignificant);
|
||||
queue_init(&significant_queue);
|
||||
queue_init(&next_significant);
|
||||
|
||||
// Start with root block as insignificant
|
||||
ezbc_block_t root = {0, 0, width, height};
|
||||
queue_push(&insignificant_queue, root);
|
||||
|
||||
// Process bitplanes from MSB to LSB
|
||||
for (int bitplane = msb_bitplane; bitplane >= 0; bitplane--) {
|
||||
int threshold = 1 << bitplane;
|
||||
|
||||
int sign_bits_this_bitplane = 0;
|
||||
|
||||
// Process insignificant blocks - check if they become significant
|
||||
for (size_t i = 0; i < insignificant_queue.count; i++) {
|
||||
ezbc_block_t block = insignificant_queue.blocks[i];
|
||||
|
||||
// Check if this block has any coefficient >= threshold
|
||||
if (is_zero_block_ezbc(coeffs, width, height, &block, threshold)) {
|
||||
// Still insignificant: emit 0
|
||||
bitstream_write_bit(&bs, 0);
|
||||
// Keep in insignificant queue for next bitplane
|
||||
queue_push(&next_insignificant, block);
|
||||
} else {
|
||||
// Became significant: emit 1
|
||||
bitstream_write_bit(&bs, 1);
|
||||
|
||||
// Use recursive subdivision to process this block and all children
|
||||
ezbc_context_t ctx = {
|
||||
.bs = &bs,
|
||||
.coeffs = coeffs,
|
||||
.states = states,
|
||||
.width = width,
|
||||
.height = height,
|
||||
.bitplane = bitplane,
|
||||
.threshold = threshold,
|
||||
.next_insignificant = &next_insignificant,
|
||||
.next_significant = &next_significant,
|
||||
.sign_count = &sign_bits_this_bitplane
|
||||
};
|
||||
process_significant_block_recursive(&ctx, block);
|
||||
}
|
||||
}
|
||||
|
||||
// Process significant 1x1 blocks - emit refinement bits
|
||||
for (size_t i = 0; i < significant_queue.count; i++) {
|
||||
ezbc_block_t block = significant_queue.blocks[i];
|
||||
int idx = block.y * width + block.x;
|
||||
int abs_val = abs(coeffs[idx]);
|
||||
|
||||
// Emit refinement bit at current bitplane
|
||||
int bit = (abs_val >> bitplane) & 1;
|
||||
bitstream_write_bit(&bs, bit);
|
||||
|
||||
// Keep in significant queue for next bitplane
|
||||
queue_push(&next_significant, block);
|
||||
}
|
||||
|
||||
// Swap queues for next bitplane
|
||||
queue_free(&insignificant_queue);
|
||||
queue_free(&significant_queue);
|
||||
insignificant_queue = next_insignificant;
|
||||
significant_queue = next_significant;
|
||||
queue_init(&next_insignificant);
|
||||
queue_init(&next_significant);
|
||||
}
|
||||
|
||||
// Free all queues
|
||||
queue_free(&insignificant_queue);
|
||||
queue_free(&significant_queue);
|
||||
queue_free(&next_insignificant);
|
||||
queue_free(&next_significant);
|
||||
free(states);
|
||||
|
||||
size_t final_size = bitstream_size(&bs);
|
||||
*output = bs.data;
|
||||
|
||||
return final_size;
|
||||
}
|
||||
Reference in New Issue
Block a user