
Overview
Testing code that processes audio and video breaks most of the usual assumptions: output isn't byte-identical between runs, "correct" is often a matter of degree rather than an exact match, and a single test run can involve real encoders that are slow and only loosely deterministic. This guide covers testing strategies that actually hold up for media-processing applications, rather than testing techniques borrowed unchanged from typical CRUD software.
What You Need
- A small set of representative test media files, checked into your repo or fetched in CI setup
- A test runner comfortable running subprocess-based tools (ffmpeg, encoders) alongside your normal test suite
Steps
Build a small, deliberate set of fixture files
Cover the real edge cases your code handles: different resolutions, frame rates, a file with no audio track, a very short clip, a file with an unusual codec. Keep each one as small as possible. A couple of seconds of synthetic color bars and a test tone is usually enough.
Test properties, not exact byte output
Assert on decoded, meaningful properties (duration, resolution, frame rate, measured loudness, presence of an audio track) rather than comparing exported file bytes directly. This is the single most important habit for stable media tests.
Use golden-file comparison for pixel and audio output
Where visual or audio correctness genuinely matters, compare against a stored "golden" reference using similarity metrics (like structural similarity for images, or a frequency/amplitude comparison for audio) rather than exact equality. This tolerates the minor variation real encoders produce.
Add tolerance for non-deterministic encoder output
Define an acceptable difference threshold (a few percent on measured loudness, a small pixel-difference tolerance, a duration tolerance of a few milliseconds) instead of expecting exact matches, encoders legitimately vary slightly across versions and platforms.
Test the actual surface, not just internal functions
A unit test that calls an internal function directly proves the function works in isolation, not that the feature works end-to-end. Drive your tests through the same interface a real user or caller would (a CLI invocation, an API request, or the actual exported file) the way you'd verify any other application.
Keep fixtures small and version them deliberately
Large test media files bloat your repository and slow down CI. Keep fixtures minimal, and if you need to update a golden reference file, do it as a deliberate, reviewed change, not something that silently drifts.
Pro Tips
- Generate synthetic test fixtures programmatically (solid color frames, sine-wave tones at known frequencies) rather than using real footage, synthetic sources make assertions like "is this frame actually red" or "is this tone actually 440Hz" trivial to verify precisely.
- Run the slow, real-encoder tests as a separate CI stage from your fast unit tests, so a quick logic change doesn't have to wait on a full encode every time.
- Log the actual measured values on test failure (measured loudness, actual duration, actual pixel diff). A bare "assertion failed" on a media test is much harder to debug than on typical software.
Tested: how repeatable encoders are, and what tolerances a test needs
We tested the page's testing advice on 2026-09-29 with FFmpeg 8.1.2, ffprobe and numpy, on two seconds of generated colour bars and a 440 Hz tone. Nine encoders each ran three times with the same input and command, then again with one thread and with eight. The output file, its compressed packets and its decoded pixels or samples were hashed. This is one FFmpeg version on one machine, so the page's claim about differences across versions and platforms was not tested.
| Encoder | Same input, same command | Thread count changed |
|---|---|---|
| libx264 | Three runs: file same, packets same, decoded same | 1 thread against 8: file different, decoded same |
| libx265 | Three runs: file same, packets same, decoded same | 1 thread against 8: file different, decoded same |
| libvpx-vp9 | Three runs: file different, packets same, decoded same | 1 thread against 8: file different, decoded same |
| libsvtav1 | Three runs: file same, packets same, decoded same | 1 thread against 8: file same, decoded same |
| prores_ks | Three runs: file same, packets same, decoded same | 1 thread against 8: file same, decoded same |
| aac | Three runs: file same, packets same, decoded same | 1 thread against 8: file same, decoded same |
| libmp3lame | Three runs: file same, packets same, decoded same | 1 thread against 8: file same, decoded same |
| libopus | Three runs: file different, packets same, decoded same | 1 thread against 8: file different, decoded same |
| flac | Three runs: file same, packets same, decoded same | 1 thread against 8: file same, decoded same |
"Not byte-identical between runs" is true for 2 of the 9 encoders and not for the rest. On the same machine and version, 7 produced identical files every time. libvpx-vp9 and libopus produced files that differed in only 16 and 40 bytes, near the start of the file in container data, and their compressed packets and decoded output were identical. So the failing comparison was a hash of the whole file, and the fix the page recommends, comparing decoded content, is the right one. Hashing the packets would also have worked.
Thread count is a cleaner way to see it. Changing it altered the file for libx264, libx265 and libvpx-vp9, and left every decoded result identical. x264 writes its settings, including the thread count, into the stream as text, and x265 records its frame threads, so a file comparison can fail on a change that has no effect on the picture. We did not trace why libvpx-vp9 changed. Setting a timestamp such as creation_time=now also made two runs a second apart differ.
The page's tolerances hold, with numbers to choose from. Audio duration is not exact after a lossy encode: the decoded length came out at AAC +5.3 ms, MP3 +0.0 ms, Opus +0.0 ms, FLAC +0.0 ms, WAV +0.0 ms against the two seconds put in, and the duration ffprobe reported for the Opus file was +6.5 ms off. "A few milliseconds" is the right size, and a test with no tolerance would fail on Opus and AAC. Loudness across the five formats spread over 0.4 LU, from -22.2 to -21.8 LUFS, and every one still measured 440 Hz.
For pixels, the mean and the maximum behave very differently. Against the source, x264 at CRF 18, 23, 30 and 40 gave a mean absolute error of 0.05, 0.12, 0.38 and 1.03 grey levels, and a maximum of 2, 6, 12 and 30, with SSIM from 0.9998 to 0.9757. A tolerance set on the maximum has to be much looser than one set on the mean or on SSIM, and a maximum is dominated by a few edge pixels. Choosing the metric matters as much as choosing the number.
"Is this frame actually red" needs a tolerance too. A pure red (255, 0, 0) frame read back as 254, 0, 0 even from lossless 4:4:4 encoding, because of the colour conversion, and at CRF 40 with 4:2:0 the blue and green channels reached 1. An exact comparison would fail on a correct file. A two second bars and tone fixture came to 23 kB as an MP4, which is small enough that keeping several in a repository is cheap.
Hash the compressed packets of a video stream, which stays stable when the container header changes: ffmpeg -i out.webm -map 0:v:0 -c copy -f md5 -
Validated. Run 2026-09-29 with FFmpeg 8.1.2, ffprobe and numpy on generated media, on one machine and one FFmpeg version. Whether output differs between FFmpeg versions or platforms was not tested, and neither were golden file practices, CI staging or other codecs. Encoder settings were mostly defaults, and other settings change these results. The command is the packet hash that was measured.
Knowledge Base
Media Tests Need Different Assumptions Than Typical Software Tests
Most testing advice assumes deterministic output and cheap, instantaneous test execution. Media processing violates both, encoders can be slow and only loosely deterministic, and "correctness" is frequently a matter of acceptable tolerance rather than an exact match. Testing strategies that ignore this produce flaky, untrustworthy test suites.
Golden Files Are a Trade-off, Not a Silver Bullet
Golden-file comparison catches real regressions effectively, but the reference files themselves need active maintenance. An unreviewed "just update the golden file" habit can hide a real regression as easily as it prevents a false failure. Treat golden-file updates as changes worth reviewing carefully.
Where This Fits
Tests check the software, and Media Asset Management (MAM) Explained covers the asset system that software serves.
FAQ
Q: Why can't I just compare exported files byte-for-byte between test runs?
A: Most encoders (video and audio) aren't fully deterministic across versions, platforms, or even runs, timestamps, encoder metadata, and minor bit-level differences can vary without any real quality difference. Byte-for-byte comparison produces false failures constantly. Comparing decoded properties (duration, resolution, measured loudness, pixel similarity within a tolerance) is what actually holds up.
Q: How small can test fixture files realistically be?
A: Often a second or two is enough. You're testing that your code correctly invokes and interprets the processing, not stress-testing the encoder itself. Small fixtures also keep your repository size and CI run time manageable.
Translate this page
- Español
- 简体中文
- हिन्दी
- العربية
- Português
- Français
- Deutsch
- 日本語
- Русский
- Bahasa Indonesia
- 한국어
- Italiano
- Türkçe
- Tiếng Việt
- Polski
- Nederlands
Machine translation provided by Google Translate, on Google’s servers. We do not check these translations and they will get technical terms wrong. The English page is the authoritative one. Following a link sends this page’s address to Google. Your browser may also offer to translate this page itself, which keeps the request on your device.