# Thor Codec User Guide

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`.

```bash
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

```bash
make check test-config=config_LDB_low_complexity.txt test-frames-count=2 test-valgrind=0
```

```text
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

```bash
git clone https://github.com/cisco/thor.git
cd thor
make -j8
```

```text
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:

```bash
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:

```bash
# 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](https://github.com/cisco/thor/issues/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](https://github.com/cisco/thor/issues/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](https://github.com/cisco/thor/issues/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

```text
; 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:

```bash
./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:

```text
; 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`

```text
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`

```text
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:

```bash
./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:

```bash
#!/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:

```bash
# 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:

```text
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:

```bash
#!/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](https://github.com/cisco/thor/issues/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](https://github.com/cisco/thor/issues/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](https://github.com/cisco/thor/issues/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](https://github.com/cisco/thor/issues/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](https://tools.ietf.org/html/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](https://tools.ietf.org/html/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](https://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.

---

_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. Unofficial; not affiliated with or reviewed by the project._
