You've already forked FFmpeg
mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2025-08-04 22:03:09 +02:00
lavc: Add unit test for APV entropy decode
This commit is contained in:
@ -1329,6 +1329,7 @@ TESTPROGS = avcodec \
|
|||||||
jpeg2000dwt \
|
jpeg2000dwt \
|
||||||
mathops \
|
mathops \
|
||||||
|
|
||||||
|
TESTPROGS-$(CONFIG_APV_DECODER) += apv
|
||||||
TESTPROGS-$(CONFIG_AV1_VAAPI_ENCODER) += av1_levels
|
TESTPROGS-$(CONFIG_AV1_VAAPI_ENCODER) += av1_levels
|
||||||
TESTPROGS-$(CONFIG_CABAC) += cabac
|
TESTPROGS-$(CONFIG_CABAC) += cabac
|
||||||
TESTPROGS-$(CONFIG_GOLOMB) += golomb
|
TESTPROGS-$(CONFIG_GOLOMB) += golomb
|
||||||
|
449
libavcodec/tests/apv.c
Normal file
449
libavcodec/tests/apv.c
Normal file
@ -0,0 +1,449 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of FFmpeg.
|
||||||
|
*
|
||||||
|
* FFmpeg is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* FFmpeg is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with FFmpeg; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "libavutil/lfg.h"
|
||||||
|
#include "libavutil/random_seed.h"
|
||||||
|
|
||||||
|
#include "libavcodec/apv_decode.h"
|
||||||
|
#include "libavcodec/apv_dsp.h"
|
||||||
|
#include "libavcodec/put_bits.h"
|
||||||
|
|
||||||
|
|
||||||
|
// Whole file included here to get internal symbols.
|
||||||
|
#include "libavcodec/apv_entropy.c"
|
||||||
|
|
||||||
|
|
||||||
|
// As defined in 7.1.4, for testing.
|
||||||
|
// Adds a check to limit loop after reading 16 zero bits to avoid
|
||||||
|
// getting stuck reading a stream of zeroes forever (this matches
|
||||||
|
// the behaviour of the faster version).
|
||||||
|
|
||||||
|
static unsigned int apv_read_vlc_spec(GetBitContext *gbc, int k_param)
|
||||||
|
{
|
||||||
|
unsigned int symbol_value = 0;
|
||||||
|
int parse_exp_golomb = 1;
|
||||||
|
int k = k_param;
|
||||||
|
int stop_loop = 0;
|
||||||
|
|
||||||
|
if(get_bits1(gbc) == 1) {
|
||||||
|
parse_exp_golomb = 0;
|
||||||
|
} else {
|
||||||
|
if (get_bits1(gbc) == 0) {
|
||||||
|
symbol_value += (1 << k);
|
||||||
|
parse_exp_golomb = 0;
|
||||||
|
} else {
|
||||||
|
symbol_value += (2 << k);
|
||||||
|
parse_exp_golomb = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (parse_exp_golomb) {
|
||||||
|
int read_limit = 0;
|
||||||
|
do {
|
||||||
|
if (get_bits1(gbc) == 1) {
|
||||||
|
stop_loop = 1;
|
||||||
|
} else {
|
||||||
|
if (++read_limit == 16)
|
||||||
|
break;
|
||||||
|
symbol_value += (1 << k);
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
} while (!stop_loop);
|
||||||
|
}
|
||||||
|
if (k > 0)
|
||||||
|
symbol_value += get_bits(gbc, k);
|
||||||
|
|
||||||
|
return symbol_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// As defined in 7.2.4, for testing.
|
||||||
|
|
||||||
|
static void apv_write_vlc_spec(PutBitContext *pbc,
|
||||||
|
unsigned int symbol_val, int k_param)
|
||||||
|
{
|
||||||
|
int prefix_vlc_table[3][2] = {{1, 0}, {0, 0}, {0, 1}};
|
||||||
|
|
||||||
|
unsigned int symbol_value = symbol_val;
|
||||||
|
int val_prefix_vlc = av_clip(symbol_val >> k_param, 0, 2);
|
||||||
|
int bit_count = 0;
|
||||||
|
int k = k_param;
|
||||||
|
|
||||||
|
while (symbol_value >= (1 << k)) {
|
||||||
|
symbol_value -= (1 << k);
|
||||||
|
if (bit_count < 2)
|
||||||
|
put_bits(pbc, 1, prefix_vlc_table[val_prefix_vlc][bit_count]);
|
||||||
|
else
|
||||||
|
put_bits(pbc, 1, 0);
|
||||||
|
if (bit_count >= 2)
|
||||||
|
++k;
|
||||||
|
++bit_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(bit_count < 2)
|
||||||
|
put_bits(pbc, 1, prefix_vlc_table[val_prefix_vlc][bit_count]);
|
||||||
|
else
|
||||||
|
put_bits(pbc, 1, 1);
|
||||||
|
|
||||||
|
if(k > 0)
|
||||||
|
put_bits(pbc, k, symbol_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Old version of ff_apv_entropy_decode_block, for test comparison.
|
||||||
|
|
||||||
|
static int apv_entropy_decode_block(int16_t *restrict coeff,
|
||||||
|
GetBitContext *restrict gbc,
|
||||||
|
APVEntropyState *restrict state)
|
||||||
|
{
|
||||||
|
const APVVLCLUT *lut = state->decode_lut;
|
||||||
|
|
||||||
|
// DC coefficient.
|
||||||
|
{
|
||||||
|
int abs_dc_coeff_diff;
|
||||||
|
int sign_dc_coeff_diff;
|
||||||
|
int dc_coeff;
|
||||||
|
|
||||||
|
abs_dc_coeff_diff = apv_read_vlc(gbc, state->prev_k_dc, lut);
|
||||||
|
|
||||||
|
if (abs_dc_coeff_diff > 0)
|
||||||
|
sign_dc_coeff_diff = get_bits1(gbc);
|
||||||
|
else
|
||||||
|
sign_dc_coeff_diff = 0;
|
||||||
|
|
||||||
|
if (sign_dc_coeff_diff)
|
||||||
|
dc_coeff = state->prev_dc - abs_dc_coeff_diff;
|
||||||
|
else
|
||||||
|
dc_coeff = state->prev_dc + abs_dc_coeff_diff;
|
||||||
|
|
||||||
|
if (dc_coeff < APV_MIN_TRANS_COEFF ||
|
||||||
|
dc_coeff > APV_MAX_TRANS_COEFF) {
|
||||||
|
av_log(state->log_ctx, AV_LOG_ERROR,
|
||||||
|
"Out-of-range DC coefficient value: %d "
|
||||||
|
"(from prev_dc %d abs_dc_coeff_diff %d sign_dc_coeff_diff %d)\n",
|
||||||
|
dc_coeff, state->prev_dc, abs_dc_coeff_diff, sign_dc_coeff_diff);
|
||||||
|
return AVERROR_INVALIDDATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
coeff[0] = dc_coeff;
|
||||||
|
|
||||||
|
state->prev_dc = dc_coeff;
|
||||||
|
state->prev_k_dc = FFMIN(abs_dc_coeff_diff >> 1, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
// AC coefficients.
|
||||||
|
{
|
||||||
|
int scan_pos = 1;
|
||||||
|
int first_ac = 1;
|
||||||
|
int k_run = 0;
|
||||||
|
int k_level = state->prev_k_level;
|
||||||
|
|
||||||
|
do {
|
||||||
|
int coeff_zero_run;
|
||||||
|
|
||||||
|
coeff_zero_run = apv_read_vlc(gbc, k_run, lut);
|
||||||
|
|
||||||
|
if (coeff_zero_run > APV_BLK_COEFFS - scan_pos) {
|
||||||
|
av_log(state->log_ctx, AV_LOG_ERROR,
|
||||||
|
"Out-of-range zero-run value: %d (at scan pos %d)\n",
|
||||||
|
coeff_zero_run, scan_pos);
|
||||||
|
return AVERROR_INVALIDDATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < coeff_zero_run; i++) {
|
||||||
|
coeff[ff_zigzag_direct[scan_pos]] = 0;
|
||||||
|
++scan_pos;
|
||||||
|
}
|
||||||
|
k_run = FFMIN(coeff_zero_run >> 2, 2);
|
||||||
|
|
||||||
|
if (scan_pos < APV_BLK_COEFFS) {
|
||||||
|
int abs_ac_coeff_minus1;
|
||||||
|
int sign_ac_coeff;
|
||||||
|
int abs_level, level;
|
||||||
|
|
||||||
|
abs_ac_coeff_minus1 = apv_read_vlc(gbc, k_level, lut);
|
||||||
|
sign_ac_coeff = get_bits(gbc, 1);
|
||||||
|
|
||||||
|
abs_level = abs_ac_coeff_minus1 + 1;
|
||||||
|
if (sign_ac_coeff)
|
||||||
|
level = -abs_level;
|
||||||
|
else
|
||||||
|
level = abs_level;
|
||||||
|
|
||||||
|
if (level < APV_MIN_TRANS_COEFF ||
|
||||||
|
level > APV_MAX_TRANS_COEFF) {
|
||||||
|
av_log(state->log_ctx, AV_LOG_ERROR,
|
||||||
|
"Out-of-range AC coefficient value: %d "
|
||||||
|
"(from k_param %d abs_ac_coeff_minus1 %d sign_ac_coeff %d)\n",
|
||||||
|
level, k_level, abs_ac_coeff_minus1, sign_ac_coeff);
|
||||||
|
}
|
||||||
|
|
||||||
|
coeff[ff_zigzag_direct[scan_pos]] = level;
|
||||||
|
|
||||||
|
k_level = FFMIN(abs_level >> 2, 4);
|
||||||
|
if (first_ac) {
|
||||||
|
state->prev_k_level = k_level;
|
||||||
|
first_ac = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
++scan_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (scan_pos < APV_BLK_COEFFS);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void binary(char *buf, uint32_t value, int bits)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < bits; i++)
|
||||||
|
buf[i] = (value >> (bits - i - 1) & 1) ? '1' : '0';
|
||||||
|
buf[bits] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_apv_read_vlc(void)
|
||||||
|
{
|
||||||
|
APVVLCLUT lut;
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
ff_apv_entropy_build_decode_lut(&lut);
|
||||||
|
|
||||||
|
// Generate all possible 20 bit sequences (padded with zeroes), then
|
||||||
|
// verify that spec and improved parsing functions get the same result
|
||||||
|
// and consume the same number of bits for each possible k_param.
|
||||||
|
|
||||||
|
for (int k = 0; k <= 5; k++) {
|
||||||
|
for (uint32_t b = 0; b < (1 << 20); b++) {
|
||||||
|
uint8_t buf[8] = {
|
||||||
|
b >> 12,
|
||||||
|
b >> 4,
|
||||||
|
b << 4,
|
||||||
|
0, 0, 0, 0, 0
|
||||||
|
};
|
||||||
|
|
||||||
|
GetBitContext gbc_test, gbc_spec;
|
||||||
|
unsigned int res_test, res_spec;
|
||||||
|
int con_test, con_spec;
|
||||||
|
|
||||||
|
init_get_bits8(&gbc_test, buf, 8);
|
||||||
|
init_get_bits8(&gbc_spec, buf, 8);
|
||||||
|
|
||||||
|
res_test = apv_read_vlc (&gbc_test, k, &lut);
|
||||||
|
res_spec = apv_read_vlc_spec(&gbc_spec, k);
|
||||||
|
|
||||||
|
con_test = get_bits_count(&gbc_test);
|
||||||
|
con_spec = get_bits_count(&gbc_spec);
|
||||||
|
|
||||||
|
if (res_test != res_spec ||
|
||||||
|
con_test != con_spec) {
|
||||||
|
char str[21];
|
||||||
|
binary(str, b, 20);
|
||||||
|
av_log(NULL, AV_LOG_ERROR,
|
||||||
|
"Mismatch reading %s (%d) with k=%d:\n", str, b, k);
|
||||||
|
av_log(NULL, AV_LOG_ERROR,
|
||||||
|
"Test function result %d consumed %d bits.\n",
|
||||||
|
res_test, con_test);
|
||||||
|
av_log(NULL, AV_LOG_ERROR,
|
||||||
|
"Spec function result %d consumed %d bits.\n",
|
||||||
|
res_spec, con_spec);
|
||||||
|
++err;
|
||||||
|
if (err > 10)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int random_coeff(AVLFG *lfg)
|
||||||
|
{
|
||||||
|
// Geometric distribution of code lengths (1-14 bits),
|
||||||
|
// uniform distribution within codes of the length,
|
||||||
|
// equal probability of either sign.
|
||||||
|
int length = (av_lfg_get(lfg) / (UINT_MAX / 14 + 1));
|
||||||
|
int random = av_lfg_get(lfg);
|
||||||
|
int value = (1 << length) + (random & (1 << length) - 1);
|
||||||
|
if (random & (1 << length))
|
||||||
|
return value;
|
||||||
|
else
|
||||||
|
return -value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int random_run(AVLFG *lfg)
|
||||||
|
{
|
||||||
|
// Expoenential distrbution of run lengths.
|
||||||
|
unsigned int random = av_lfg_get(lfg);
|
||||||
|
for (int len = 0;; len++) {
|
||||||
|
if (random & (1 << len))
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
// You rolled zero on a 2^32 sided die; well done!
|
||||||
|
return 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_apv_entropy_decode_block(void)
|
||||||
|
{
|
||||||
|
// Generate random entropy blocks, code them, then ensure they
|
||||||
|
// decode to the same block with both implementations.
|
||||||
|
|
||||||
|
APVVLCLUT decode_lut;
|
||||||
|
AVLFG lfg;
|
||||||
|
unsigned int seed = av_get_random_seed();
|
||||||
|
av_lfg_init(&lfg, seed);
|
||||||
|
|
||||||
|
av_log(NULL, AV_LOG_INFO, "seed = %u\n", seed);
|
||||||
|
|
||||||
|
ff_apv_entropy_build_decode_lut(&decode_lut);
|
||||||
|
|
||||||
|
for (int t = 0; t < 100; t++) {
|
||||||
|
APVEntropyState state, save_state;
|
||||||
|
int16_t block[64];
|
||||||
|
int16_t block_test1[64];
|
||||||
|
int16_t block_test2[64];
|
||||||
|
uint8_t buffer[1024];
|
||||||
|
PutBitContext pbc;
|
||||||
|
GetBitContext gbc;
|
||||||
|
int bits_written;
|
||||||
|
int pos, run, coeff, level, err;
|
||||||
|
int k_dc, k_run, k_level;
|
||||||
|
|
||||||
|
memset(block, 0, sizeof(block));
|
||||||
|
memset(buffer, 0, sizeof(buffer));
|
||||||
|
init_put_bits(&pbc, buffer, sizeof(buffer));
|
||||||
|
|
||||||
|
// Randomly-constructed state.
|
||||||
|
memset(&state, 0, sizeof(state));
|
||||||
|
state.decode_lut = &decode_lut;
|
||||||
|
state.prev_dc = random_coeff(&lfg);
|
||||||
|
state.prev_k_dc = av_lfg_get(&lfg) % 5;
|
||||||
|
state.prev_k_level = av_lfg_get(&lfg) % 4;
|
||||||
|
save_state = state;
|
||||||
|
|
||||||
|
k_dc = state.prev_k_dc;
|
||||||
|
k_run = 0;
|
||||||
|
k_level = state.prev_k_level;
|
||||||
|
|
||||||
|
coeff = random_coeff(&lfg) / 2;
|
||||||
|
block[ff_zigzag_direct[0]] = state.prev_dc + coeff;
|
||||||
|
apv_write_vlc_spec(&pbc, FFABS(coeff), k_dc);
|
||||||
|
if (coeff != 0)
|
||||||
|
put_bits(&pbc, 1, coeff < 0);
|
||||||
|
|
||||||
|
pos = 1;
|
||||||
|
while (pos < 64) {
|
||||||
|
run = random_run(&lfg);
|
||||||
|
if (pos + run > 64)
|
||||||
|
run = 64 - pos;
|
||||||
|
apv_write_vlc_spec(&pbc, run, k_run);
|
||||||
|
k_run = av_clip(run >> 2, 0, 2);
|
||||||
|
pos += run;
|
||||||
|
if (pos < 64) {
|
||||||
|
coeff = random_coeff(&lfg);
|
||||||
|
level = FFABS(coeff) - 1;
|
||||||
|
block[ff_zigzag_direct[pos]] = coeff;
|
||||||
|
apv_write_vlc_spec(&pbc, level, k_level);
|
||||||
|
put_bits(&pbc, 1, coeff < 0);
|
||||||
|
k_level = av_clip((level + 1) >> 2, 0, 4);
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bits_written = put_bits_count(&pbc);
|
||||||
|
flush_put_bits(&pbc);
|
||||||
|
|
||||||
|
// Fill output block with a distinctive error value.
|
||||||
|
for (int i = 0; i < 64; i++)
|
||||||
|
block_test1[i] = -9999;
|
||||||
|
init_get_bits8(&gbc, buffer, sizeof(buffer));
|
||||||
|
|
||||||
|
err = apv_entropy_decode_block(block_test1, &gbc, &state);
|
||||||
|
if (err < 0) {
|
||||||
|
av_log(NULL, AV_LOG_ERROR, "Entropy decode returned error.\n");
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
int bits_read = get_bits_count(&gbc);
|
||||||
|
if (bits_written != bits_read) {
|
||||||
|
av_log(NULL, AV_LOG_ERROR, "Wrote %d bits but read %d.\n",
|
||||||
|
bits_written, bits_read);
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
err = 0;
|
||||||
|
for (int i = 0; i < 64; i++) {
|
||||||
|
if (block[i] != block_test1[i])
|
||||||
|
++err;
|
||||||
|
}
|
||||||
|
if (err > 0) {
|
||||||
|
av_log(NULL, AV_LOG_ERROR, "%d mismatches in output block.\n", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init_get_bits8(&gbc, buffer, sizeof(buffer));
|
||||||
|
memset(block_test2, 0, 64 * sizeof(int16_t));
|
||||||
|
|
||||||
|
err = ff_apv_entropy_decode_block(block_test2, &gbc, &save_state);
|
||||||
|
if (err < 0) {
|
||||||
|
av_log(NULL, AV_LOG_ERROR, "Entropy decode returned error.\n");
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
int bits_read = get_bits_count(&gbc);
|
||||||
|
if (bits_written != bits_read) {
|
||||||
|
av_log(NULL, AV_LOG_ERROR, "Wrote %d bits but read %d.\n",
|
||||||
|
bits_written, bits_read);
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
err = 0;
|
||||||
|
for (int i = 0; i < 64; i++) {
|
||||||
|
if (block[i] != block_test2[i])
|
||||||
|
++err;
|
||||||
|
}
|
||||||
|
if (err > 0) {
|
||||||
|
av_log(NULL, AV_LOG_ERROR, "%d mismatches in output block.\n", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.prev_dc != save_state.prev_dc ||
|
||||||
|
state.prev_k_dc != save_state.prev_k_dc ||
|
||||||
|
state.prev_k_level != save_state.prev_k_level) {
|
||||||
|
av_log(NULL, AV_LOG_ERROR, "Entropy state mismatch.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = test_apv_read_vlc();
|
||||||
|
if (err) {
|
||||||
|
av_log(NULL, AV_LOG_ERROR, "Read VLC test failed.\n");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = test_apv_entropy_decode_block();
|
||||||
|
if (err) {
|
||||||
|
av_log(NULL, AV_LOG_ERROR, "Entropy decode block test failed.\n");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -3,6 +3,11 @@ fate-av1-levels: libavcodec/tests/av1_levels$(EXESUF)
|
|||||||
fate-av1-levels: CMD = run libavcodec/tests/av1_levels$(EXESUF)
|
fate-av1-levels: CMD = run libavcodec/tests/av1_levels$(EXESUF)
|
||||||
fate-av1-levels: REF = /dev/null
|
fate-av1-levels: REF = /dev/null
|
||||||
|
|
||||||
|
FATE_LIBAVCODEC-$(CONFIG_APV_DECODER) += fate-apv-entropy
|
||||||
|
fate-apv-entropy: libavcodec/tests/apv$(EXESUF)
|
||||||
|
fate-apv-entropy: CMD = run libavcodec/tests/apv$(EXESUF)
|
||||||
|
fate-apv-entropy: REF = /dev/null
|
||||||
|
|
||||||
FATE_LIBAVCODEC-yes += fate-avpacket
|
FATE_LIBAVCODEC-yes += fate-avpacket
|
||||||
fate-avpacket: libavcodec/tests/avpacket$(EXESUF)
|
fate-avpacket: libavcodec/tests/avpacket$(EXESUF)
|
||||||
fate-avpacket: CMD = run libavcodec/tests/avpacket$(EXESUF)
|
fate-avpacket: CMD = run libavcodec/tests/avpacket$(EXESUF)
|
||||||
|
Reference in New Issue
Block a user