Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

Thor Codec User Guide

Generated April 2026 from cisco/thor at 18de8f9

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

Thor is a perceptually-tuned video codec authored by Cisco, originally developed as a candidate technology for the IETF NETVC working group. The repository ships a reference encoder (Thorenc) and reference decoder (Thordec) written in portable C99, plus a curated set of configuration files that mirror the operating points described in the Thor Internet-Draft. Thor is a hybrid block-based codec in the same lineage as H.264/AVC, HEVC, and AV1: it builds a prediction (intra or inter), transforms and quantises the residual, and entropy-codes the result, with deblocking, CDEF, and CLPF as in-loop post-processing options.

This guide is written for video-codec researchers and engineers who want to encode and decode raw YUV (or Y4M) sequences with Thor, run rate-distortion comparisons against other codecs, and reproduce the operating points from Cisco's reference configurations. It documents every command-line flag the encoder and decoder accept, every supported colour format and bit depth, and the canonical directory of preset configuration files. It is paired with the Thor Codec Administrator & Developer Guide, which covers architecture, bitstream syntax, security posture, and developer internals.

1. Quick Start

You need a POSIX-style shell, make, a C99 compiler (gcc or clang), and a short raw YUV input clip. Thor's reference build produces two binaries in the build/ directory: Thorenc and Thordec.

git clone https://github.com/cisco/thor.git
cd thor
make -j8
./build/Thorenc -cf config_LDB_low_complexity.txt -if input_1280x720_30.yuv \
    -of stream.bit -rf recon.yuv -width 1280 -height 720 -f 30 -qp 32 -n 30
./build/Thordec stream.bit decoded.yuv

The first call encodes 30 frames of input_1280x720_30.yuv with the Low-Delay B (LDB) low-complexity preset at QP 32, writing the compressed bitstream to stream.bit and the encoder's reconstructed YUV to recon.yuv. The second call decodes the bitstream and writes raw YUV to decoded.yuv.

Tip

If your input is a Y4M file, Thor reads the width, height, frame rate, chroma subsampling, and bit depth out of the Y4M header automatically, so the -width, -height, and -f flags become optional. Likewise, naming the decoder output with a .y4m extension makes Thordec emit a Y4M stream instead of headerless raw YUV — handy for pipelines that already expect Y4M.

What you get out of Thor

  • A self-contained C99 encoder and decoder, no external runtime dependencies beyond libm.
  • Thirty ready-to-use configuration presets covering Random Access (RA*), Low-Delay B (LDB*), and high-bit-depth (HDB*, HDB16*) operating points, each in low_complexity, medium_complexity, and high_efficiency flavours, with optional _qm_ (quantisation matrix) variants.
  • Optional Constrained Directional Enhancement Filter (CDEF) and Constrained Low-Pass Filter (CLPF) loop post-processing.
  • Frame-level rate control with target bitrate, plus per-frame and per-superblock QP adaptation.
  • Built-in PSNR Y/U/V reporting after every encoded frame and a per-block, per-mode, per-size statistics dump after every decoded sequence.
  • A check.sh round-trip harness driven by make check that runs an encode-decode-cmp loop on randomly generated YUV files (optionally under Valgrind) — perfect for catching regressions when modifying the codec.

Verify your build in 60 seconds

make check test-config=config_LDB_low_complexity.txt test-frames-count=2 test-valgrind=0
1920 1080 30 ./rnd_test_tmp_1920x1080_30.yuv 2
SH:    19 bits
   0 I   30     162488    34.3416  39.0421  39.7245  ...
   1 P   38      11425    33.6512  38.7104  39.4118  ...
1280 720 30 ...
640 480 30 ...
640 360 30 ...

check.sh generates four randomly-filled YUV files at 1920x1080, 1280x720, 640x480, and 640x360, encodes each one, decodes the resulting bitstream, and byte-compares the decoder output against the encoder's reconstructed buffer. Any mismatch — even a single byte — exits with Encoder/Decoder mismatch detected and the offending frame number.

2. Installation

Thor has no package manager presence. You build it from source. The reference build is a hand-written Makefile that targets POSIX systems (Linux, macOS, WSL, MSYS2). Windows users without WSL can use the Visual Studio solution at build/Thor.sln.

2.1 System requirements

Component Requirement
OS Linux, macOS, or Windows (via WSL, MSYS2, or Visual Studio)
Compiler gcc or clang with C99 support and -march=native capability
Linker Standard POSIX toolchain; the only library link is -lm
RAM ~1 GB for 1080p encoding at default settings
Disk The repository checkout is roughly 3 MB; raw YUV inputs dominate disk use
Optional valgrind (for make check test-valgrind=1); bash (for check.sh)

Important

Thor's CFLAGS default to -march=native -mtune=native. Binaries built on one host are not portable to a different micro-architecture. If you are distributing builds, pass ARCH=core2, ARCH=ssse3, ARCH=sse4, ARCH=avx2, or ARCH=neon (ARM) to make to target a specific instruction-set baseline.

2.2 Build from source — Linux and macOS

git clone https://github.com/cisco/thor.git
cd thor
make -j8
cc -std=c99 -g -O3 -Wall -pedantic -I common -march=native -mtune=native -c -o common/common_block.o common/common_block.c
...
cc -o build/Thorenc enc/encode_block.o enc/encode_frame.o ... -lm
cc -o build/Thordec dec/decode_block.o dec/decode_frame.o ... -lm

After a clean build, you have:

Binary Purpose
build/Thorenc Reference encoder. Reads raw YUV or Y4M, writes a Thor bitstream.
build/Thordec Reference decoder. Reads a Thor bitstream, writes raw YUV or Y4M.

Warning

The build emits two generated files, common/common_kernels_gen.c and enc/enc_kernels_gen.c, by running scripts/lbd_to_hbd.sh over the corresponding low-bit-depth source. The generator is bash, so a missing or non-executable bash in PATH will fail the build with a confusing "no such file or directory" error from make. Install bash and ensure scripts/lbd_to_hbd.sh is executable (chmod +x scripts/lbd_to_hbd.sh).

2.3 Build with a specific instruction-set baseline

The Makefile recognises five ARCH values that swap the SIMD compiler flags:

make -j8 ARCH=avx2          # x86-64 with AVX2 (Haswell or newer)
make -j8 ARCH=sse4          # x86-64 with SSE4
make -j8 ARCH=ssse3         # x86-64 with SSSE3 (older Intel)
make -j8 ARCH=core2         # SSE2 + SSSE3, tuned for Core2
make -j8 ARCH=neon          # ARM with NEON SIMD
make -j8 ARCH=native        # Default — march/mtune=native

The matching -mfpu=neon, -msse2 -mssse3 -mtune=core2, -mssse3, -msse4, -mavx2, or -march=native -mtune=native flags are appended to the C99 baseline (-std=c99 -g -O3 -Wall -pedantic -I common). Passing an unrecognised ARCH value silently disables the SIMD-specific flag — you get a portable build at the C99 baseline.

2.4 Build on Windows with Visual Studio

The repository ships a build/Thor.sln solution. Open it in Visual Studio, choose a build configuration (Release recommended), and build the Thorenc and Thordec projects. The solution mirrors the Makefile sources but does not run scripts/lbd_to_hbd.sh; the generated _gen.c files must be present in the working tree before you build (commit them or generate them under WSL/MSYS2 first).

2.5 Verify the install

After the build, encode and decode a tiny clip end-to-end:

# Generate 3 frames of 640x480 random YUV
head --bytes $((640 * 480 * 3 * 3 / 2)) </dev/urandom > test.yuv
./build/Thorenc -cf config_LDB_low_complexity.txt -if test.yuv \
    -of test.bit -rf test_rec.yuv -width 640 -height 480 -f 30 -n 3 -qp 32
./build/Thordec test.bit test_dec.yuv
cmp test_dec.yuv test_rec.yuv && echo OK

OK means the encoder's reconstructed reference matches the decoder output byte-for-byte. Any other result is a regression.

Note

The encoder always prints a per-frame line of the form <frame> <I|P|B> <qp> <bits> <psnr_y> <psnr_u> <psnr_v>. Final summary lines report bitrate in kbps (when -snrcalc is enabled) and average Y/U/V PSNR over the encoded sequence. The decoder prints exhaustive bit-allocation and mode-distribution statistics at the end of the run.

2.6 Install troubleshooting

Error: Compilation fails on ARM with gcc 4.6.1

Cause: Thor's NEON SIMD kernels rely on intrinsics that gcc 4.6 does not expose. Tracked upstream as issue #5.

Solution: Upgrade to gcc 4.7 or newer (gcc 6+ recommended), or build with ARCH= set to a non-NEON value to fall back to the portable C SIMD wrappers.

Prevention: Pin a known-good toolchain in CI; Thor's CI history exercises gcc 5+ and clang 6+.

Error: Code fail to build with GCC 5.2

Cause: A pre-2018 issue with restrict and inline rules under GCC 5.2. Tracked as issue #11, resolved.

Solution: Use GCC 5.3 or newer, or pull master (commit 18de8f9 or later).

Prevention: Verify your compiler version with gcc --version before building. Thor uses -std=c99 -pedantic, which makes the build sensitive to old language-rule corner cases.

Error: Compilation fails with clang due to intrinsics

Cause: Older clang versions did not provide every SIMD intrinsic Thor expected. Tracked as issue #6, resolved in current master.

Solution: Upgrade clang to 6.0 or newer.

Prevention: When porting Thor to a new platform, build first with ARCH= left at the default to surface intrinsics issues; only opt into specific SIMD baselines once the portable build succeeds.

3. Configuration

Thorenc is configured via a flat keyword=value list passed either on the command line (e.g. -qp 32 -width 1920 -height 1080 ...) or through one or more configuration files referenced with -cf <file>. Configuration files hold one parameter per line in the form -name value and accept comments introduced by a semicolon (;) up to the end of the line. Command-line flags take precedence over config-file values; later -cf files override earlier ones.

Thordec takes no configuration flags — it derives every parameter (width, height, bit depth, chroma subsampling, deblocking, CDEF, CLPF, etc.) from the sequence header carried in the bitstream itself.

3.1 The shipped preset configurations

The repository includes 30 ready-to-use preset configs in the root directory. The naming scheme breaks down as config_<profile>_[qm_]<complexity>.txt:

Profile Coding mode
LDB Low-Delay B-frames (no reordering, B references only past frames)
RA Random Access (hierarchical B-frames with reordering)
RA16 Random Access with num_reorder_pics=15 (16-frame subgroup)
HDB High-Delay B (longer reorder buffer)
HDB16 High-Delay B with num_reorder_pics=15
*_qm_* Same operating point, with quantisation matrices enabled (-qmtx 1)

Each profile also has three complexity flavours:

Flavour Encoder speed Tools enabled
low_complexity Fast (-encoder_speed 2) Block contexts, CFL-intra, deblock, no PB/TB split, no bipred
medium_complexity Moderate (-encoder_speed 1) Adds -enable_pb_split 1
high_efficiency Slow (-encoder_speed 0) RDO intra, PB+TB split, bipred, max 4 references

Tip

Pair config_RA_high_efficiency.txt with -qp 27 (or 22 for higher quality) when you want the best compression efficiency Thor can deliver; use config_LDB_low_complexity.txt with the encoder's default QP 32 for low-latency real-time scenarios. The RA16 and HDB16 variants extend the GOP to 16 frames for offline VOD-style benchmarks.

3.2 Minimal annotated configuration

; Geometry
-width              1920
-height             1080
-f                  30          ; frame rate in fps

; Coding tools
-num_reorder_pics   7           ; sub-GOP size minus 1 (8-frame sub-GOP)
-max_num_ref        4           ; up to 4 reference frames per inter frame
-intra_period       64          ; insert an I-frame every 64 frames

; Rate-distortion knobs
-qp                 32          ; base QP
-dqpI              -2           ; QP offset for I-frames (lower = better quality)
-mqpP               1.2         ; QP multiplier for low-quality P-frames
-lambda_coeffI      0.8         ; lambda multiplier for I-frames

; In-loop filters
-deblocking         1
-clpf               1           ; 0=off, 1=SB-level, 2=frame-level
-cdef               2           ; 0=off, 1=slow, 2=medium, 3=fast

Note

Default values come from the encoder itself, not from any of the shipped config files. Defaults set -n 600, -width 1920, -height 1080, -qp 32, -f 60, -log2_sb_size 7 (128x128 superblocks), -deblocking 1, -cdef 2, -clpf 0, -bitdepth 8, -input_bitdepth 8, -subsample 420, -max_num_ref 1, -snrcalc 1. If you launch Thorenc with only -if, -of, and -rf, those defaults kick in.

3.3 Combining command-line flags with -cf

Configuration files are recursive: a flag inside a .txt config can itself be -cf path/to/another.txt. Command-line arguments are parsed in order, so the canonical "config + override" pattern is:

./build/Thorenc \
    -cf config_RA_high_efficiency.txt \
    -if input.yuv -of stream.bit -rf recon.yuv \
    -width 3840 -height 2160 -f 60 \
    -n 300 -qp 24 -intra_period 64

The preset establishes the coding-tool baseline; the trailing flags override geometry, frame count, QP, and intra period. You can also point -cf at a locally-tuned file that itself includes a preset:

; my_lowlatency.txt
-cf config_LDB_low_complexity.txt
-bitrate 4000000          ; target 4 Mbps
-max_delta_qp 4

See the Admin Guide's Configuration Reference for the full list of every encoder parameter, default value, and acceptable range.

4. Core Usage

The two binaries follow a strict separation of duties: Thorenc consumes raw pixels and produces a Thor bitstream; Thordec consumes a Thor bitstream and produces raw pixels. Neither tool muxes container formats — there is no ISOBMFF, no Matroska, no IVF wrapper. The encoder writes a byte stream that begins with a sequence header followed by per-frame payloads.

4.1 The encoder: Thorenc

usage: Thorenc <parameters>

Parameters are zero or more flags from the table below, passed in any order. At minimum you need an input file (-if) and an output bitstream (-of). A reconstructed-frame output (-rf) is optional but strongly recommended for testing — it lets check.sh byte-compare against the decoder.

File I/O flags

Flag Type Default Description
-cf path — Configuration file. May be specified more than once; later configs override earlier ones.
-if path — Input file. Raw YUV (planar I420/I422/I444/Y400) or Y4M. Required.
-of path — Output Thor bitstream. Required.
-rf path — Optional reconstructed YUV (for verification). Append .y4m to emit a Y4M file.
-stat path — Optional stat-file path (used by tools that consume per-frame stats).
-ph int 0 File header length in bytes to skip before the first frame in -if. Auto-set to the Y4M header length when -if is Y4M.
-fh int 0 Per-frame header length to skip in -if (set to 6 automatically for Y4M, which prefixes each frame with FRAME\n).

Geometry and frame selection

Flag Type Default Description
-width int 1920 Frame width in luma samples. Must be a multiple of 8.
-height int 1080 Frame height in luma samples. Must be a multiple of 8.
-n int 600 Number of frames to encode.
-skip int 0 Number of frames to skip from the start of the input file.
-f float 60 Frame rate in frames per second.
-subsample int 420 Chroma subsampling: 400 (mono), 420, 422, or 444.
-bitdepth int 8 Internal codec bit depth: 8, 10, or 12.
-input_bitdepth int 8 Bit depth of pixels in -if: 8, 10, or 12.
-frame_bitdepth int 8 Bit depth of internal frame buffers: 8 or 16. Forced to 16 when -bitdepth>8.

Important

Width and height must each be a multiple of 8. The encoder calls fatalerror("Width and height must be a multiple of 8\n") and aborts otherwise. To encode an arbitrary-resolution sequence, pad the source width and height up to the nearest multiple of 8 and signal the active region out-of-band.

QP, lambda, and rate control

Flag Type Default Description
-qp int 32 Base QP applied to anchor frames. Range 0–51.
-max_qp int 51 Upper QP clamp for non-I frames during rate control.
-min_qp int 1 Lower QP clamp for non-I frames during rate control.
-max_qpI int 32 Upper QP clamp for I-frames.
-min_qpI int 32 Lower QP clamp for I-frames.
-dqpI int 0 QP offset for I-frames (negative = higher quality).
-dqpP int 0 QP offset for low-quality P-frames.
-dqpB int 0 QP offset for non-dyadic B-frames.
-dqpB0..-dqpB3 int 0 QP offsets for hierarchical B levels 0–3.
-mqpP float 1.0 QP multiplier for low-quality P-frames.
-mqpB float 1.0 QP multiplier for non-dyadic B-frames.
-mqpB0..-mqpB3 float 1.0 QP multipliers for hierarchical B levels 0–3.
-max_delta_qp int 0 Per-superblock delta-QP search range (max 7).
-delta_qp_step int 1 Step size for the delta-QP search.
-bitrate int 0 Target bitrate in bps (0 = no rate control). Mutually exclusive with -num_reorder_pics > 0.
-lambda_coeffI float 1.0 Multiplier for the rate-distortion lambda on I-frames.
-lambda_coeffP float 1.0 Multiplier for lambda on P-frames.
-lambda_coeffB float 1.0 Multiplier for lambda on non-dyadic B-frames.
-lambda_coeffB0..B3 float 1.0 Multipliers for lambda on hierarchical B levels 0–3.
-early_skip_thr float 0.0 Early-skip threshold (higher = more aggressive skip decisions).

Coding tools

Flag Type Default Description
-log2_sb_size int 7 Log2 of superblock size. Range 6–7 (64x64 or 128x128).
-enable_tb_split int 0 1 = allow splitting a coding block into 4 transform blocks.
-enable_pb_split int 0 1 = allow splitting an inter coding block into 4 prediction blocks.
-max_num_ref int 1 Maximum number of reference frames per inter block. Range 1–4.
-HQperiod int 1 Period of high-quality (anchor) frames.
-num_reorder_pics int 0 Sub-GOP size minus 1. With >0 enables B-frames; subject to dyadic-coding power-of-two rule.
-dyadic_coding int 1 1 = hierarchical B with dyadic structure; 0 = flat B.
-interp_ref int 0 1/2 enable a synthetic interpolated reference frame.
-intra_period int 0 Force an I-frame every N frames; 0 = only one initial I-frame.
-intra_rdo int 0 1 = full RDO over all intra modes; 0 = use the four most likely modes.
-encoder_speed int 0 Speed preset. 0 = slow, 1 = moderate, 2 = fast.
-sync int 0 1 = require frame-by-frame deterministic output. Requires -encoder_speed 2.
-deblocking int 1 1 = run the in-loop deblocking filter.
-cdef int 2 Constrained Directional Enhancement Filter: 0 off, 1 slow, 2 medium, 3 fast.
-clpf int 0 Constrained Low-Pass Filter: 0 off, 1 SB-level, 2 frame-level.
-max_clpf_strength int 4 Maximum CLPF filter strength.
-snrcalc int 1 1 = compute and print PSNR every frame.
-use_block_contexts int 0 1 = use spatial context for block-level coding.
-enable_bipred int 0 1 = allow biprediction; 2 = bipred without single-frame prediction.
-qmtx int 0 1 = enable per-block quantisation matrices.
-qmtx_offset int 0 QP offset (-32..31) used when picking the QM level.
-enable_cfl_intra int 1 1 = chroma-from-luma prediction for intra blocks.
-enable_cfl_inter int 0 1 = chroma-from-luma prediction for inter blocks.

Tip

The shipped preset configs already pick a coherent set of these tools. Start from a preset (-cf config_RA_medium_complexity.txt) and only override the handful of flags you actually want to change.

4.2 The decoder: Thordec

usage: Thordec infile [outfile]

Thordec is intentionally minimal — it has exactly two positional arguments and no flags.

Position Argument Required Description
1 infile Yes Path to the Thor bitstream produced by Thorenc.
2 outfile No Path to the decoded YUV output. If the path ends in .y4m, the decoder writes a Y4M stream including a YUV4MPEG2 header and a FRAME\n prefix per picture. Otherwise it writes headerless raw YUV. If omitted, the decoder still parses the bitstream and prints statistics, but discards pixels.

The decoder reads the sequence header from the bitstream, allocates frame buffers from the embedded width / height / chroma subsampling / bit depth fields, then loops decoding frames until the input ends. After the run it prints a thorough breakdown of bit allocation, mode and size distribution, super-mode distribution, and reference-index distribution.

Note

The decoder does no rate control, no PSNR comparison, and no in-band scaling. To compare decoder output against the encoder's reconstruction, run cmp out.yuv recon.yuv — they must match exactly. A mismatch is a codec bug.

5. Common Tasks

5.1 Encode and decode a raw YUV file

The canonical end-to-end path. input_1280x720_30.yuv is a planar I420 file at 30 fps:

./build/Thorenc -cf config_LDB_medium_complexity.txt \
    -if input_1280x720_30.yuv -of stream.bit -rf recon.yuv \
    -width 1280 -height 720 -f 30 -qp 30 -n 300

./build/Thordec stream.bit decoded.yuv

cmp decoded.yuv recon.yuv && echo "Decoder matches reconstruction"

Use -rf out.y4m instead of -rf recon.yuv if your downstream tools want a Y4M-wrapped reconstructed output.

5.2 Batch-encode a directory of YUV files

The repository's check.sh already does this for round-trip testing. For a plain encode-only pass over a directory, a small shell loop is enough:

#!/bin/bash
CONFIG=config_RA_medium_complexity.txt
for f in inputs/*.yuv; do
    base=$(basename "$f" .yuv)
    # Filename convention: name_WIDTHxHEIGHT_FPS.yuv
    geom=$(echo "$base" | rev | cut -d_ -f2 | rev)
    fps=$(echo "$base" | rev | cut -d_ -f1 | rev)
    w=$(echo "$geom" | cut -dx -f1)
    h=$(echo "$geom" | cut -dx -f2)

    ./build/Thorenc -cf "$CONFIG" -if "$f" \
        -of "out/${base}_qp32.bit" -rf "out/${base}_qp32_rec.yuv" \
        -width "$w" -height "$h" -f "$fps" -qp 32 -n 300 \
        2> "out/${base}_qp32.log"
done

Tip

The encoder's per-frame stdout line ends with the cumulative bit count, so post-processing is as simple as awk '{print $4}' out/<file>.log to pull bits per frame, or piping the final summary through grep -E '^Bitrate' on some sequences.

5.3 Compare PSNR against another codec

Thor prints PSNR Y/U/V on every frame when -snrcalc 1 (the default). To benchmark Thor head-to-head with another codec on the same source:

# Encode with Thor
./build/Thorenc -cf config_RA_high_efficiency.txt -if src.yuv -of thor.bit \
    -rf thor_rec.yuv -width 1920 -height 1080 -f 30 -qp 27 -n 300 \
    | tee thor.log

# Capture Thor's average PSNR and bitrate from the trailing summary
grep -E "Bitrate|^PSNR" thor.log

# Decode and compute PSNR with an external tool against the original
./build/Thordec thor.bit thor_dec.yuv
ffmpeg -s 1920x1080 -pix_fmt yuv420p -i thor_dec.yuv \
       -s 1920x1080 -pix_fmt yuv420p -i src.yuv \
       -lavfi psnr=stats_file=thor_psnr.log -f null -

Thor's per-frame PSNR is computed against the original source; any other codec compared on the same source at matching bitrate gives a fair RD point. Sweep -qp over {22, 27, 32, 37} with each codec to draw an RD curve.

Note

Thor's PSNR is computed in the encoder's input_bitdepth domain. If you use -bitdepth 10 but -input_bitdepth 8, the encoder still reports PSNR in the 8-bit input domain. Match -input_bitdepth across codecs before comparing.

5.4 Custom QP per frame

Thor exposes per-frame QP control via three layered mechanisms:

  1. Anchor QP with -qp <N> — the I-frame and high-quality P-frame QP.
  2. Frame-type offsets with -dqpI, -dqpP, -dqpB, and the dyadic -dqpB0..-dqpB3. These are applied additively after the multipliers.
  3. Frame-type multipliers with -mqpP, -mqpB, and dyadic -mqpB0..-mqpB3. Multipliers are applied first, then offsets.

The effective QP for a non-I frame is computed inside mainenc.c as:

qp = clip(int(mqp<X> * base_qp) + dqp<X>, 0, 51)

For per-superblock variation within a frame, set -max_delta_qp <N> (1–7). The encoder does an additional rate-distortion search over QP offsets in [-N, +N] per superblock and signals the chosen delta in the bitstream.

Warning

-max_delta_qp greater than or equal to 8 triggers fatalerror("max_delta_qp too large\n"). Range 1–7 only. Higher values increase encoder work roughly linearly per superblock.

5.5 Multi-resolution rate ladder

A typical adaptive-streaming ladder encodes the same source at multiple spatial resolutions and target bitrates. Drive Thor with a tiny shell loop:

#!/bin/bash
CFG=config_RA_high_efficiency.txt

# rendition  dst_w  dst_h  qp   tag
RUNGS=(
    "2160 3840 2160 22 4k_high"
    "1080 1920 1080 24 1080p"
    "720  1280  720 27  720p"
    "540   960  540 30  540p"
    "360   640  360 33  360p"
)

for rung in "${RUNGS[@]}"; do
    set -- $rung
    name=$1 w=$2 h=$3 qp=$4 tag=$5
    # Optional: downscale outside Thor (e.g. with ffmpeg) before this loop;
    # Thor itself does not resample.
    ./build/Thorenc -cf "$CFG" \
        -if "scaled_${w}x${h}.yuv" \
        -of "ladder/${tag}.bit" \
        -rf "ladder/${tag}_rec.yuv" \
        -width "$w" -height "$h" -f 60 -qp "$qp" -n 300 \
        -intra_period 64 \
        2>&1 | tee "ladder/${tag}.log"
done

Tip

Thor itself does not resample frames — it requires the input to already be at the target resolution. Use an external scaler (FFmpeg, swscale, dav1d-tools) to produce each rung's source YUV first. Match -intra_period across rungs so segment boundaries align for ABR switching.

6. Troubleshooting

Error: Width and height must be a multiple of 8

Cause: The encoder validates params->width % 8 == 0 and params->height % 8 == 0 in check_parameters() and aborts otherwise.

Solution: Pad the source to the next multiple of 8 in both dimensions before passing it to Thorenc. Crop on the decoder side or signal the active region out-of-band.

Prevention: Inspect raw YUV inputs with echo $(($(stat -c %s in.yuv) / (W * H * 3 / 2))) to verify the assumed geometry matches the file size.

Error: Number of frames must be positive

Cause: -n 0 or a negative -n value. The encoder aborts in check_parameters().

Solution: Pass -n <positive integer>. The default is 600.

Prevention: When wrapping Thorenc in a script, always set -n explicitly — the default of 600 may be larger than your test clip.

Error: Subgop length (num_reorder_pics+1) must divide HQperiod.

Cause: Hierarchical B-frame coding requires the high-quality period (-HQperiod) to be an exact multiple of the sub-GOP size (num_reorder_pics + 1). The encoder aborts in check_parameters().

Solution: Either set -HQperiod to a multiple of num_reorder_pics+1 or drop -HQperiod to 1.

Prevention: Stick to the shipped RA* and HDB* presets, which already satisfy this constraint.

Error: num_reorder_pics+1 must be a power of 2 with dyadic coding.

Cause: With -dyadic_coding 1 (default) the sub-GOP must be a power of two. -num_reorder_pics 4 (sub-GOP 5) violates this.

Solution: Use -num_reorder_pics 0, 1, 3, 7, or 15 for sub-GOPs of 1, 2, 4, 8, or 16, or set -dyadic_coding 0.

Prevention: Use the RA (num_reorder_pics=7) or RA16 (num_reorder_pics=15) presets to stay within the supported set.

Error: Illegal value for log2_sb_size

Cause: -log2_sb_size outside the supported range. The encoder aborts in check_parameters().

Solution: Use -log2_sb_size 6 (64x64 superblocks) or -log2_sb_size 7 (128x128, default).

Prevention: Don't override -log2_sb_size unless you specifically need 64x64 superblocks for comparison studies.

Error: Illegal value for subsample. Only 444, 422, 420 and 400 supported.

Cause: -subsample set to a value other than 400, 420, 422, or 444.

Solution: Use one of the four supported chroma formats.

Prevention: Match -subsample to your input. Y4M files set this automatically.

Error: Illegal value for bitdepth. Only 8, 10 and 12 supported.

Cause: -bitdepth, -input_bitdepth outside {8, 10, 12}.

Solution: Use one of the supported bit depths. With high bit depth, remember the input file is 16-bit per sample (planar) and twice as large.

Prevention: Use the HDB16 configs as a starting point for 10-bit encodes; they bundle a sane -bitdepth 10, -input_bitdepth 10 combination.

Error: Could not open in-file for reading.

Cause: -if path does not exist, is unreadable, or the working directory is wrong. Same applies to Thordec's positional input.

Solution: Verify with ls -l <path> that the file exists and is readable by the user running Thor.

Prevention: Always pass absolute paths to Thorenc from scripts.

Decoder reports decode_frame_num increments but writes garbage

Cause: Mismatched bit depth, chroma subsampling, or geometry between the encoder and the consumer of the decoder's output. The decoder reads these from the bitstream's sequence header, so the output is correct — but your downstream tool may be treating an I420 10-bit planar buffer as I420 8-bit.

Solution: Check the encoder log for the subsample, bitdepth, width, and height it used; configure your downstream tool to match. Use the .y4m extension on the decoder output for self-describing output.

Prevention: Always emit .y4m for downstream consumption when bit depth or subsampling differs from 420 8-bit.

Crash: NULL Pointer Dereference in interpolate_frame

Cause: Known fuzzer-found bug parsing certain malformed bitstreams. Tracked as issue #40.

Solution: Treat Thordec as research-grade software. Do not feed untrusted bitstreams without sandboxing. The reference decoder is not hardened against adversarial input.

Prevention: Run Thordec under valgrind or ASan in CI, never on a production server reachable from the public internet.

Crash: Stack buffer overflow in read_block()

Cause: Adversarial input with values outside the expected ranges. Tracked as issue #39.

Solution: Same as above — sandbox the decoder.

Prevention: Use a fuzzed/hardened decoder fork in any pipeline that processes untrusted bitstreams. The reference decoder prioritises clarity over hardening.

Crash: get_vlc(): infinite loop on invalid streams

Cause: Malformed VLC code in the bitstream causes the parser to spin. Tracked as issue #30.

Solution: Wrap Thordec invocations with a timeout 30s (or similar) when parsing untrusted inputs.

Prevention: Validate bitstreams against a known-good encoder output before sharing them across systems.

Encoder error detected from check.sh

Cause: The encoder returned a non-zero exit code. Almost always a parameter-validation failure or a missing input file.

Solution: Re-run the encode standalone with the same arguments check.sh would use; the parameter error message goes to stderr.

Prevention: Pre-validate config files with a dry encode of 1 frame before committing them.

Encoder/Decoder mismatch detected. Frame nr.: N

Cause: The decoder's output diverged from the encoder's reconstructed frame at frame N. This is always a codec bug; report it upstream with a minimal reproducer.

Solution: Reduce to the smallest -n and resolution that triggers the mismatch. File a ticket with the input dimensions, config preset, and encoder output log.

Prevention: Track the issue tracker for known mismatch reports (issue #29 discusses the encoder/decoder agreement check infrastructure).

7. FAQ

Q: Is Thor a finished standard?

A: No. Thor is research-grade reference software for the IETF NETVC working group, evolving alongside the draft-fuldseth-netvc-thor Internet-Draft. It is not a published video coding standard and the bitstream is not version-stable. Two builds at different commits may not interoperate.

Q: Does Thor support hardware acceleration?

A: No. Both encoder and decoder are CPU-only C99 with hand-written SIMD intrinsics for SSE2/SSSE3/SSE4/AVX2 on x86 and NEON on ARM. There is no GPU path.

Q: Can I package Thor in a Matroska/MP4 container?

A: Not directly. Thor produces a raw byte stream with no IVF, ISOBMFF, or Matroska wrapper. To carry Thor inside a container you would need to define your own packetisation. For research workflows, pair the raw .bit file with a sidecar JSON describing geometry/framerate.

Q: What chroma formats does Thor support?

A: Four: 400 (luma only), 420, 422, and 444. The encoder validates this in check_parameters() and the decoder reads it from the sequence header.

Q: What bit depths does Thor support?

A: Three: 8, 10, and 12 bits. Both -bitdepth (internal) and -input_bitdepth (source) accept the same set. -frame_bitdepth is forced to 16 whenever the internal bit depth exceeds 8 — this affects how the encoder allocates frame buffers.

Q: How do I disable PSNR computation to speed up encoding?

A: Set -snrcalc 0. By default PSNR is computed and printed every frame, which adds a small per-frame cost. Disabling it is useful for pure rate sweeps where you only want the bitrate.

Q: How do I enable rate control?

A: Set -bitrate <bps> (in bits per second, not kbps). This activates the per-superblock rate controller in enc/rc.c. Note that rate control is mutually exclusive with frame reordering — -num_reorder_pics > 0 together with -bitrate > 0 triggers fatalerror("Current rate control doesn't work with frame reordering\n").

Q: Why does my low-bit-depth output look slightly different on different machines?

A: Thor's default build is -march=native -mtune=native, which can fold floating-point reductions differently on different CPUs. For bit-exact reproducibility across machines, build with ARCH=core2 (the lowest common SSE baseline) and pin one toolchain version.

Q: How do I use a Y4M file as input?

A: Pass the .y4m file to -if. The encoder detects the YUV4MPEG2 header, populates -width, -height, -f, -subsample, and -input_bitdepth from the header, and sets -ph and -fh to skip the file and per-frame headers. Y4M's chroma tags map to Thor as mono → subsample=400, 420 → 420, 422 → 422, 444 → 444. Interlaced Y4M (Im / It / Ib) is rejected with Only progressive input supported.

Q: How do I get a Y4M decoder output?

A: Name the second positional argument with a .y4m extension: ./build/Thordec stream.bit decoded.y4m. The decoder writes a YUV4MPEG2 header followed by a FRAME\n prefix per picture.

Q: What is CDEF? Should I leave it on?

A: Constrained Directional Enhancement Filter — an in-loop post-processing filter inherited from AV1 research. It improves perceptual quality at low QPs at a small encode-time cost. Default is -cdef 2 (medium). Turn it off with -cdef 0 for compatibility studies; turn to -cdef 3 (fast) for real-time encoding scenarios.

Q: What is CLPF?

A: Constrained Low-Pass Filter — an alternative in-loop filter that runs before CDEF in the pipeline. Default is -clpf 0 (off). -clpf 1 applies it at superblock granularity; -clpf 2 applies it at frame granularity. The maximum filter strength is set by -max_clpf_strength (default 4).

Q: Where can I find the bitstream specification?

A: The IETF Internet-Draft draft-fuldseth-netvc-thor is the closest thing to a spec. The reference decoder's dec/read_bits.c (read_sequence_header() and read_frame_header()) is the executable specification — when the draft and the code disagree, the code wins. See the Admin Guide for a syntax-element-by-syntax-element walk-through.

Q: Is the encoder deterministic?

A: Mostly. With -encoder_speed 0 and -snrcalc 1 on a single thread, Thor is bit-deterministic given the same toolchain and ARCH flags. -encoder_speed 2 -sync 1 enforces deterministic output across configurations. Multi-threaded execution is not supported by the reference encoder.

Q: Why does make check only test 4 resolutions?

A: The default check.sh hard-codes the four W/H pairs (1920,1080), (1280,720), (640,480), (640,360). Pass test-files=path/to/dir or test-files=path/to/file.yuv to use your own inputs. Filenames must follow name_WIDTHxHEIGHT_RATE.yuv so the script can extract geometry.

Q: Where do I report a security issue?

A: Through the upstream issue tracker at github.com/cisco/thor/issues, following Cisco's responsible-disclosure guidelines. Open issues #36–#40 already track several memory-safety problems in the reference decoder.