Download (v14.1) CTC samples from here or use the mini-samples in media/.
To test entropy decoding, change relevant #defines in to enable header and entropy debugging. This is similar to what our CI script does. You can also ignore this and just compare YUV alone.
dav2d patch
commit 4c2581378500eb1ee255a41ebc811392591a3d0c
Author: Ronald S. Bultje <rsbultje@gmail.com>
Date: Sat Feb 7 07:24:39 2026 -0500
decode whole tiles at a time instead of interleaved (debug only).
So it's easier to compare multi-tile files with AVM.
diff --git a/src/decode.c b/src/decode.c
index d6fcaafb..637b8abc 100644
--- a/src/decode.c
+++ b/src/decode.c
@@ -4018,7 +4018,7 @@ static void setup_tile(Dav2dTileState *const ts,
ts->last_qidx = f->frame_hdr->quant.yac;
dav2d_msac_init(&ts->msac, data, sz, f->frame_hdr->disable_cdf_update);
-#if DEBUG_BLOCK_INFO
+#if DEBUG_BLOCK_INFO && 0
struct { int by, bx; } tmem = {
.by = row_sb_start << sb_shift, .bx = col_sb_start << sb_shift,
}, *const t = &tmem;
@@ -4800,6 +4800,7 @@ int dav2d_decode_frame_main(Dav2dFrameContext *const f) {
const int sby_start = f->frame_hdr->tiling.t.row_start_sb[tile_row];
const int sbh_end =
imin(f->frame_hdr->tiling.t.row_start_sb[tile_row + 1], f->sbh);
+#if 0
for (int sby = sby_start; sby < sbh_end; sby++) {
t->by = sby << (4 + f->frame_hdr->sb128);
const int by_end = (t->by + f->sb_step) >> 1;
@@ -4812,6 +4813,41 @@ int dav2d_decode_frame_main(Dav2dFrameContext *const f) {
if (dav2d_decode_tile_sbrow(t)) goto error;
}
}
+#else
+ const int sb_shift = f->sb_shift;
+ for (int tile_col = 0; tile_col < f->frame_hdr->tiling.t.cols; tile_col++) {
+ const int col_start =
+ f->frame_hdr->tiling.t.col_start_sb[tile_col] << sb_shift;
+ const int col_end =
+ imin(f->frame_hdr->tiling.t.col_start_sb[tile_col + 1] << sb_shift, f->bw);
+
+ t->ts = &f->ts[tile_row * f->frame_hdr->tiling.t.cols + tile_col];
+
+#if DEBUG_BLOCK_INFO
+ t->by = sby_start << sb_shift;
+ t->bx = col_start;
+ DEBUG_BLOCK_printf("decode_tile[tilerow=%d/col=%d,y=%d-%d,"
+ "x=%d-%d,size=%td]: r=%d\n",
+ tile_row, tile_col,
+ sby_start << sb_shift,
+ imin(sbh_end << sb_shift, f->bh),
+ col_start, col_end,
+ t->ts->msac.sz, t->ts->msac.rng);
+#endif
+
+ for (int sby = sby_start; sby < sbh_end; sby++) {
+ t->by = sby << (4 + f->frame_hdr->sb128);
+ const int by_end = (t->by + f->sb_step) >> 1;
+ if (f->frame_hdr->use_ref_frame_mvs) {
+ dav2d_refmvs_load_tmvs(&f->rf, tile_row,
+ // FIXME: col_start >> 1, col_end >> 1,
+ 0, f->bw >> 1,
+ t->by >> 1, by_end);
+ }
+ if (dav2d_decode_tile_sbrow(t)) goto error;
+ }
+ }
+#endif
// post filters (deblock + cdef + ccso + ...)
// do this after completing full tiles, so that intra bc works correctly
for (int sby = sby_start; sby < sbh_end; sby++) {
diff --git a/src/msac.c b/src/msac.c
index 6156eab5..7e64aec0 100644
--- a/src/msac.c
+++ b/src/msac.c
@@ -237,6 +237,7 @@ unsigned dav2d_msac_decode_bool_adapt_c(MsacContext *const s,
void dav2d_msac_init(MsacContext *const s, const uint8_t *const data,
const size_t sz, const int disable_cdf_update_flag)
{
+ s->sz = sz;
s->buf_pos = data;
s->buf_end = data + sz;
s->dif = (~(uint64_t)0) >> 1;
diff --git a/src/msac.h b/src/msac.h
index 5abe250c..20897564 100644
--- a/src/msac.h
+++ b/src/msac.h
@@ -40,6 +40,7 @@ typedef struct MsacContext {
unsigned rng;
int cnt;
int allow_update_cdf;
+ ptrdiff_t sz;
#if HAVE_ASM && ARCH_X86_64
unsigned (*unary_bypass6)(struct MsacContext *s, unsigned max_bits);
commit 1e2c289dd523901157e4891704ed5f125b052b5a
Author: Ronald S. Bultje <rsbultje@gmail.com>
Date: Sun Feb 8 14:48:55 2026 -0500
Enable debug macros.
diff --git a/src/debug.h b/src/debug.h
index 993e3166..05881493 100644
--- a/src/debug.h
+++ b/src/debug.h
@@ -28,11 +28,11 @@
#ifndef DAV2D_SRC_DEBUG_H
#define DAV2D_SRC_DEBUG_H
-#define DEBUG_BLOCK_INFO 0
+#define DEBUG_BLOCK_INFO 1
#if DEBUG_BLOCK_INFO
#define DB_ONLY(x...) x,
#define BLOCK_TO_DEBUG_S(frame_offset, by, bx) \
- frame_offset == 0 && by >= 0 && by < 16 && bx >= 0 && bx < 16
+ frame_offset <= 255 && by >= 0 && by < 9999 && bx >= 0 && bx < 9999
#define BLOCK_TO_DEBUG BLOCK_TO_DEBUG_S(f->frame_hdr->frame_offset, t->by, t->bx)
#define DEBUG_BLOCK_printf(fmt...) \
if (BLOCK_TO_DEBUG) { \
diff --git a/src/obu.c b/src/obu.c
index 02de9681..2ba9df6a 100644
--- a/src/obu.c
+++ b/src/obu.c
@@ -174,7 +174,7 @@ static NOINLINE int parse_seq_hdr(Dav2dSequenceHeader *const hdr,
GetBits *const gb,
const int strict_std_compliance)
{
-#define DEBUG_SEQ_HDR 0
+#define DEBUG_SEQ_HDR 1
#if DEBUG_SEQ_HDR
const unsigned init_bit_pos = dav2d_get_bits_pos(gb);
@@ -940,7 +940,7 @@ static NOINLINE void parse_tile_info_frmhdr(Dav2dFrameHeader *const hdr,
static int parse_frame_hdr(Dav2dContext *const c, GetBits *const gb,
const enum Dav2dObuType obu_type)
{
-#define DEBUG_FRAME_HDR 0
+#define DEBUG_FRAME_HDR 1
#if DEBUG_FRAME_HDR
const uint8_t *const init_ptr = &gb->ptr[-!!(gb->bits_left & 7)];
@@ -2044,7 +2044,7 @@ ptrdiff_t dav2d_parse_obus(Dav2dContext *const c, Dav2dData *const in) {
if (!in_temporal_layer || !in_spatial_layer)
return gb.ptr_end - gb.ptr_start;
}
-#define DEBUG_OBU_HDR 0
+#define DEBUG_OBU_HDR 1
if (DEBUG_OBU_HDR)
printf("OBU type=%d size=%td\n",
type, gb.ptr_end - gb.ptr);
diff --git a/src/refmvs.c b/src/refmvs.c
index 6bd61e06..c22720d4 100644
--- a/src/refmvs.c
+++ b/src/refmvs.c
@@ -42,7 +42,7 @@
#include "src/mem.h"
#include "src/refmvs.h"
-#define DEBUG_REFMV 0
+#define DEBUG_REFMV 1
#if DEBUG_BLOCK_INFO && DEBUG_REFMV
#define RDB_ONLY(x...) x
AVM patch
commit 7b1c5f6174aead121886b7cf3e6d9d7aa6615d9b
Author: Ronald S. Bultje <rsbultje@gmail.com>
Date: Sun Feb 8 14:50:25 2026 -0500
Enable debug macros.
diff --git a/av2/common/mvref_common.c b/av2/common/mvref_common.c
index eaa70cf9e1..4258426c0b 100644
--- a/av2/common/mvref_common.c
+++ b/av2/common/mvref_common.c
@@ -55,7 +55,7 @@ enum {
#define TIP_MFMV_STACK_SIZE 3 // The limit for original TMVP w/ TIP.
#define MFMV_STACK_SIZE 4 // The total limit of motion field candidates.
-#define DEBUG_REFMV 0
+#define DEBUG_REFMV 1
#if DEBUG_BLOCK_INFO && DEBUG_REFMV
#define RDB_ONLY(x...) x
diff --git a/avm/debug.h b/avm/debug.h
index 5b486f485d..558ed07e97 100644
--- a/avm/debug.h
+++ b/avm/debug.h
@@ -32,16 +32,16 @@
#include <stdio.h>
#include <stddef.h>
-#define DEBUG_OBU_HDR 0
-#define DEBUG_SEQ_HDR 0
-#define DEBUG_FRAME_HDR 0
-#define DEBUG_BLOCK_INFO 0
+#define DEBUG_OBU_HDR 1
+#define DEBUG_SEQ_HDR 1
+#define DEBUG_FRAME_HDR 1
+#define DEBUG_BLOCK_INFO 1
#define DEBUG_B_PIXELS 0
#if DEBUG_BLOCK_INFO
#define DB_ONLY(x...) x,
#define BLOCK_TO_DEBUG \
- cm->current_frame.frame_number == 0 && \
- mi_row >= 0 && mi_row < 16 && mi_col >= 0 && mi_col < 16
+ cm->current_frame.frame_number <= 255 && \
+ mi_row >= 0 && mi_row < 9999 && mi_col >= 0 && mi_col < 9999
#define DEBUG_BLOCK_printf(fmt...) \
if (BLOCK_TO_DEBUG) printf(fmt)
#define DEBUG_CF_printf(fmt...) \
Then I run the following script:
n=0
set=T2RA # T1AI, T5St
dir=~/Downloads/research-v13.0.0_bitstreams
num=`ls ${dir}/${set}*.bin | wc -l`
for file in ${dir}/${set}*.bin; do
base=`basename $file`
echo "$base ($n/$num)"
n=$(($n+1))
~/Projects/avm/x86-64/avmdec $file -o avm.y4m > /tmp/avm.txt
~/Projects/dav2d/build/tools/dav2d -i $file -o dav2d.y4m --threads=1 --quiet > /tmp/dav2d.txt
diff -q /tmp/{avm,dav2d}.txt || break
( ffmpeg -i avm.y4m -i dav2d.y4m -lavfi psnr=- -v error -nostats -f null - | grep -v psnr_avg:inf ) && break
done
For me, this decodes all T2RA/T1AI/T5St files correctly. (I did not compare all logfiles for 1080p/4K sequences because diff is very slow, but the YUV output was confirmed to match.) T3LB is expected to not work because some low-delay tools (e.g. BRU) are not yet implemented, although the first (key)frame should work correctly.
In terms of postfilters (see --inloopfilters): deblock, CDEF, CCSO, restoration (wiener) and GDF are tested on T2RA/T1AI/T5St and work correctly.
Correctness scripts
Here are some scripts to test for md5 correctness vs avm:
md5_ctc.sh
#!/usr/bin/env bash
set -e
md5bank="${1:0:${#1}-4}".md5bank
files=`cat $1`
echo $md5bank
rm -f $md5bank
touch $md5bank
make -C ../avm/x86-64 -j24 1>&2
parallel --jobs $2 --keep-order --bar --progress '$HOME/Projects/scripts/gen_md5.sh {1} {2}' ::: $files ::: $md5bank
sort $md5bank -o $md5bank
gen_md5.sh
#!/usr/bin/env bash
file=$1
md5bank=$2
md5=$(../avm/x86-64/avmdec $file --limit=99999 --inloopfilters=all --rawvideo --md5 | cut -d' ' -f1)
echo $file $md5 >> $md5bank
run_ctc.sh
#!/usr/bin/env bash
set -e
ninja 1>&2
files_md5=`cat $1`
logfile="/tmp/run_ctc.log"
i=0
while [ -f $logfile ]; do
logfile="/tmp/run_ctc${i}.log"
let "i=i+1"
done
parallel --jobs $2 --joblog $logfile -n 2 --keep-order --bar --progress '$HOME/Projects/scripts/test_file.sh {1} {2} {#} {%}' ::: $files_md5
awk 'BEGIN {fail = 0} NR > 1 {if ($7 != 0) fail++} END {print "Failed: " fail}' $logfile 1>&2
rm $logfile
test_file.sh
#!/usr/bin/env bash
file=$1
a_md5=$2
#job=$3
#thread=$4
d_md5=$(./tools/dav2d -i $file --threads=1 --limit=99999 --inloopfilters=all --quiet -o - --muxer=md5)
res=$(diff -q <(echo "$a_md5") <(echo "$d_md5"))
if [ -z "$res" ]; then
# echo $file
:
else
echo $file: $a_md5 != $d_md5
exit 1
:
fi
Usage:
- Generate a list of filenames:
ls research-v15.0.0_bitstreams/* | grep -v T3LB > ~/Videos/AV2/dec/ctc_all-minus_t3lb.txt - Generate an md5 bank:
md5_ctc.sh ctc_all-minus_t3lb.txt [n_jobs] - Run the checks:
run_ctc.sh ctc_all-minus_t3lb.md5bank [n_jobs]
- note1: Meant to be ran from dav2d's build dir, avm being in the parent directory, its build-dir named x86-64.
- note2: You'll have to edit the parent scripts (
*_ctc.sh) for the location of the child script.