Getting started
Build
yah264 builds with Meson and Ninja. It needs nothing beyond a C11 compiler. The Makefile wraps the usual commands, so from a clean checkout:
make build
That configures into build/ and compiles. make test runs the unit tests.
make conformance runs the recon-match gate described below. make help lists
the rest.
One build gives you both sample depths. make build produces an 8-bit encoder
library and a 10-bit one. A single yah264 binary links both. Each library
installs with its own pkg-config file: yah264.pc and yah264_10.pc.
-Dbit_depth doesn’t change any of that. It only picks the depth the tests and
the in-tree tools build at.
-Dgpu=enabled links the Metal compute library. It’s off by default.
Encode something
The shortest useful command reads Y4M and writes an Annex-B stream:
yah264 --input-y4m in.y4m -o out.264
Input is a flag. There is no positional input argument. Both ends accept - for
stdin and stdout, so yah264 works in pipes.
Raw YUV goes in through --input-raw, which needs --input-res WxH.
--input-csp, --fps and --input-range supply the rest of what a Y4M header
would hold. --seek N drops the first N input frames. --crop-rect L,T,R,B
crops the input before encoding, in luma samples.
The defaults are close to x264’s medium preset: --preset medium --cabac --ref 3
--bframes 3 --transform-8x8. That lets you compare the two encoders without a
pile of tuning arguments, and it’s how most of the measurements on this site
were made.
Two defaults differ from x264’s on purpose. We swept both against our own
quality band. --aq-strength is 0.4 where x264 uses 1.0, and --psy-rd is 2.0
where x264 uses 1.0. Psychovisual RD trades pixel accuracy for visible texture.
It’s the setting most likely to flatter a VMAF-scored comparison. --tune psnr
turns both off if you want a run without it.
Choosing a rate control mode
Each of these flags selects a mode:
| flag | mode | use it when |
|---|---|---|
--crf N |
constant quality | file size is negotiable |
--crf N --vbv-maxrate M --vbv-bufsize B |
capped CRF | streaming VOD and ABR ladder rungs |
--bitrate N |
single-pass average bitrate | you have a budget for the whole file |
--qp N |
constant QP | you are measuring a coding change and want the rate controller out of the way |
Capped CRF is a common choice for VOD libraries and adaptive ladders:
yah264 --input-y4m in.y4m --crf 21 --vbv-maxrate 6000 --vbv-bufsize 12000 -o out.264
The encoder codes to the quality target. The buffer sets a ceiling. Easy titles encode at CRF 21 and come out small. Hard titles hit the ceiling and stay under it. Nothing in the library ends up undeliverable.
Plan around two things. Every GOP after the first assumes a half-full buffer. That keeps concatenated segments safe but costs a few bits. Short keyints also run hot, so aim the cap low on two-second segments.
Broadcast and live work differently, because the target there is a rate. Pair
the VBV flags with --bitrate. A cap equal to the target gives CBR. A cap above
it gives capped VBR.
--nal-hrd vbr or --nal-hrd cbr writes the buffer model into the stream, so a
receiver can verify the buffer instead of trusting it. It needs both VBV flags
and a frame rate, and it’s refused without them. Under cbr, --filler is
already on and pads every access unit up to the rate. --no-filler removes the
padding but keeps the CBR headers. Use it to measure, never to ship.
Two-pass is for bitrate targets only. --pass 1 then --pass 2 runs against
--bitrate. --pass 3 reads the stats and writes them back, so each further
pass refines against a real encode. Two-pass CRF isn’t implemented. On the
threaded path, two-pass needs a seekable input.
Presets
--preset runs from ultrafast through medium to veryslow and placebo.
It sets the subpel and mode-decision tier. The motion search follows from it:
hex at medium and faster, umh at slow and up, unless --me overrides it.
Medium is the default because every comparison on this site is against x264
medium.
--tune adjusts for content: grain, film, animation, psnr, ssim,
zerolatency, stillimage and fastdecode. zerolatency turns off B frames, the
rate-control lookahead and the sync lookahead. It’s the only tune that trades quality for latency.
Four tunes come with a caveat:
grainlowers psy-rd to 1.5 from the 2.0 default. Nobody has re-measured that direction.filmhasn’t been BD-measured at all, because the test set has no film clip.stillimageborrows x264’s constants for that tune. We haven’t measured them, because there’s no still-image clip to measure them on.fastdecodeturns off deblocking, CABAC and weighted biprediction. That’s a real quality loss, on purpose. It also sets--weightp 0, so explicit P weighted prediction is off too.
Other flags
options.md documents every flag with its default and how it differs from x264. A few are worth knowing here:
--profileis enforced. Naming a profile narrows any tool the preset chose and refuses a tool you asked for that doesn’t fit.--slices Ncuts each picture into N independently decodable slices.--tffand--bffcode each frame as two field pictures. An interlaced Y4M turns field coding on by itself.--no-interlacedrefuses it.--constrained-intrakeeps intra prediction in P and B slices off inter-coded neighbors. It costs bits and buys error resilience.--deblock A:Bmoves the in-loop filter offsets.--audopens every access unit with a delimiter, and--pic-structadds a pic_timing SEI to each picture. Neither changes a sample.
--cut-split and --shot-crf encode per shot in one pass. The
shot-aware section of the home page covers
both.
Threading and the memory it costs
--threads defaults to auto. Auto picks the smaller of your core count and 16,
then caps that by what the picture can absorb. The 16 is a conservative default.
Nobody measured a knee there. Wavefront scaling hits the picture’s critical
path before it runs out of cores, and on a machine with two kinds of core the
last workers end up on the slower ones. An explicit --threads N is honored,
then clamped by the picture. The design page has the caps and what
has been measured.
The threaded path streams. It reads on its own thread and writes each GOP as it
finishes, so clip length isn’t the limit. From a file, frames are read in the
order the GOP workers need them. Only a few frames per worker sit in memory at
a time. From a pipe, the reader holds a window of up to
(--threads + 1) x --keyint frames. Neither grows with the length of the clip.
yah264 prices the pipe window up front. It refuses a job that needs more than
half your RAM and quotes the figure and the window it came from, so the job
fails at once instead of being killed an hour in. If that happens, lower
--threads or --keyint, or set the window directly with
Y264_STREAM_WINDOW.
The encoder workers have a budget of their own. --max-memory defaults to half
your RAM. When the GOP workers wouldn’t fit in it, fewer run at once, each with
a wider wavefront. The thread count stays the same, and yah264 says so on
stderr.
--frames N encodes a segment. Splitting the input is the other way out.
Using it from ffmpeg
Piping Y4M into the CLI works. It runs three processes on one machine, though, and the decode alone can take a third of it. Calling the encoder as a library inside ffmpeg removes that overhead. It’s also how you’d use yah264 in a real pipeline:
ffmpeg -i in.mp4 -c:v libyah264 -preset medium -crf 23 out.mp4
That takes one extra step today. The ffmpeg side of the integration is a
wrapper inside libavcodec, which is LGPL, so it can’t live in this repository.
It’s on the yah264 branch of an ffmpeg fork that you build yourself. It isn’t
upstream yet.
# 1. install the libraries, headers and pkg-config files
meson setup build -Dprefix=$HOME/.local && ninja -C build install
# 2. build the fork against them
git clone -b yah264 https://github.com/terranvigil/FFmpeg.git ffmpeg-yah264
cd ffmpeg-yah264
PKG_CONFIG_PATH=$HOME/.local/lib/pkgconfig ./configure --enable-gpl --enable-libyah264
make -j
--enable-libyah264 needs --enable-gpl, the same as --enable-libx264.
yah264 is GPL-2.0-or-later, and ffmpeg’s configure refuses a GPL library without
that flag. The flag puts the whole binary under the GPL. A product that can’t
ship that way takes yah264’s commercial license. x264 and x265 offer the same
arrangement.
The encoder takes the ffmpeg options you’d expect: -b:v, -g, -bf,
-threads, -crf and -preset. Four more are worth setting directly:
-subme, -trellis, -aq-strength and -psy-rd. Each defaults to -1, meaning
“whatever the preset chose”. Setting one overrides only that.
ffmpeg -h encoder=libyah264 prints the current list.
The encoder does its own threading. The wrapper declares
AV_CODEC_CAP_OTHER_THREADS, so ffmpeg passes the thread count through instead
of wrapping the encoder in frame threads of its own. -threads 0 means auto,
as it does everywhere else here.
Bit depth
One binary encodes both depths and picks from the input’s own chroma tag. A
C420 Y4M goes to the 8-bit library. A C420p10 one goes to High 10.
--output-depth 10 upshifts every sample of 8-bit input and codes it as High
10. Raw input has no tag, so it declares its depth in the format instead:
--input-csp i420p10, and likewise i422p10 and i444p10.
Both libraries install side by side. yah264.pc and yah264_10.pc each name
their depth in Cflags. That’s what makes a caller’s yah264_encoder_open()
resolve to the library it configured against.
The ffmpeg wrapper offers yuv420p, yuv422p and yuv444p today. The wrapper
on the fork’s yah264 branch is being updated to add yuv420p10le,
yuv422p10le and yuv444p10le.
Checking that it decoded
The gate the whole project depends on is available to you as well:
make conformance
It encodes each clip across a range of QPs and decodes the result with ffmpeg. The decoder’s output has to equal the encoder’s own reconstruction bit for bit. A mismatch anywhere is a hard failure.
By default ffmpeg is the only decoder it uses.
YAH264_CONF_DECODERS="ffmpeg openh264 jm" adds openh264 and the JM reference
decoder, so three independent readings of the specification have to agree.
fetch_openh264.sh
and fetch_jm.sh
build those two.
The test clips aren’t in the repository. fetch_corpus.sh downloads them. Without them the gate has nothing to run.
What ships
Everything in this table is in the encoder today. --help and
options.md
have the defaults and the differences from x264.
| area | what ships |
|---|---|
| profiles | --profile takes baseline, main, high, high10, high422 and high444. It refuses a tool that doesn’t fit the profile you named. |
| bit depth | 8-bit and 10-bit in one binary, picked from the input’s Y4M C tag. --output-depth 10 codes 8-bit input as High 10. |
| chroma | 4:2:0, 4:2:2 and 4:4:4. |
| entropy coding | CABAC and CAVLC. Both must pass the conformance gate before a change merges. |
| rate control | --crf for constant quality, the same flag under a VBV cap for capped CRF, --bitrate for single-pass ABR, --qp for constant QP, and --pass 1/2/3 for two-pass. --crf-max caps the QP a buffer may push a CRF encode to. --ratetol sets how far ABR may drift. --qpfile forces a type and QP per frame. |
| buffer signaling | --nal-hrd vbr or --nal-hrd cbr writes the buffer model into the stream. Under cbr, --filler pads every access unit up to the rate. |
| slices | --slices N cuts each picture into N independently decodable slices, one NAL each. |
| field coding | --tff and --bff code each frame as two field pictures. I, P and B fields are all coded, and --slices works with them. |
| error resilience | --constrained-intra keeps intra prediction in P and B slices off inter-coded neighbors. |
| open GOP | --open-gop makes every keyframe after the first a plain I picture with a recovery point instead of an IDR. The B frames before it keep their references. |
| partitions | --partitions picks which block shapes the mode decision may try, using x264’s names: p8x8, p4x4, b8x8, i8x8, i4x4, none, all. |
| weighted prediction | --weightp 0, 1 or 2. Mode 1 is the default. Mode 0 turns it off and lets a stream declare Baseline profile. Mode 2 adds a weighted copy of a reference so each block can choose. |
| shot-aware | --cut-split starts every shot on its own keyframe. --shot-crf gives each shot its own quality setting from the same scan. --scan-out saves that scan, and --scan-in reuses it on a later encode of the same input. |
| intra refresh | --intra-refresh replaces keyframes after the first with a column of intra macroblocks that sweeps the picture once every --keyint frames. No single picture arrives as a burst. |
| hardware | --hw videotoolbox encodes through the Mac’s fixed-function H.264 engine, with our options mapped onto it. |
| SIMD | NEON on arm64, plus the dot-product and I8MM extensions where the chip has them. SVE kernels on Linux machines with 256-bit vectors, such as AWS Graviton3. SSE4.2, AVX2 and AVX-512 on x86-64, each used where the chip has it. -Davx512=false builds without AVX-512. The architectures page has the details. |
Some things are left out on purpose. Each has a reason written down:
- MBAFF. Field coding covers interlaced sources with a fraction of the code through the macroblock, CABAC and deblocking paths.
- OpenCL. The GPU path here is Metal.
--sliced-threads. Threading is GOP-parallel plus a row wavefront, so slices aren’t needed for threading.- Containers, muxing and filtering. yah264 takes Y4M, raw YUV or an AVFrame. ffmpeg does the rest.
--psnrand--ssiminside the encoder. The test harness scores every encode from outside.
what-we-dont-do.md is the full list, with the reason for each line.
The full option list
yah264 --help prints every flag with its default. Beyond that there are about
440 Y264_* environment knobs. They’re research instruments and change between
commits without notice.
knob_census.py
generates the catalog in
knobs.md,
where the exact count lives.