Airwave

Formats tested

The full CAP_MATRIX — every container, video codec, audio codec, HDR mode, performance rung, subtitle, and edge case the diagnostic measures, and what each maps to.

Everything keys off one table: CAP_MATRIX in packages/api/src/services/capabilities/matrix.ts. Each entry is one test — an id, a category, a container, a video/audio codec key, an optional feature or subtitle, a human diagnostic label, and an ffmpeg recipe. The clip generator, the server manifest, and both TV apps all read from this one list, so adding a test is a one-line change that propagates everywhere.

The matrix is deliberately axis-comprehensive (every container / video codec / audio codec / feature / subtitle) plus the cross-combos that actually break — E-AC3, DTS, and Dolby Vision are sensitive to whether they sit in MKV vs MP4, so those pairings are tested in both.

Fabricated vs real samples

Two ways a clip comes to exist:

  • Fabricated by ffmpegapps/server/scripts/gen-capability-media.ts builds a ~5-second clip from a single master source, driven entirely by CAP_MATRIX. Run on a box with a full ffmpeg (libx265, libsvtav1, libvpx-vp9, plus the truehd/dca encoders):

    bun --env-file=.env run scripts/gen-capability-media.ts <master> <outdir>
  • Real sample (realSample: true) — formats ffmpeg can't fabricate, because they carry proprietary or dynamic metadata (Dolby Vision, HDR10+, DTS-HD MA, Atmos, PGS/VobSub image subs). The generator skips these; you drop a real clip of that filename in by hand to cover those rows.

Of the ~49 entries, 39 are ffmpeg-fabricated and 10 are real samples. Three details keep the fabricated clips honest (so the measurement isn't a false positive):

  • Audio is synthesized, not copied from the master. The script builds a 5.1 tone bed once (a distinct pitch per channel via sine=…join=inputs=6:channel_layout=5.1), so every audio-codec test encodes real multichannel audio regardless of the master — and you can verify surround by ear.
  • Non-HDR clips are tonemapped to 8-bit SDR BT.709. The master is 10-bit HDR PQ/BT.2020; without normalizing, the HDR transfer would ride along on clips meant to be plain SDR and confuse TV decoders. HDR10 clips instead get explicit -x265-params hdr10=1:….
  • 8-bit codecs pin yuv420p. Otherwise libx264/libx265 emit High-10/Main-10 from the 10-bit master, which real TV hardware decoders reject with error 4 even though desktop software decoders accept it — exactly the false positive the whole exercise exists to avoid.

Containers (cont_*)

Baseline H.264/AAC in each container, to isolate demuxer support. MP4 is the control.

Test idContainerVideo / AudioSourceNotes
cont_mp4MP4H.264 / AACffmpegControl: MP4 baseline
cont_mkvMKVH.264 / AACffmpegMKV demuxer
cont_tsMPEG-TSH.264 / AACffmpegMPEG-TS demuxer
cont_movMOVH.264 / AACffmpegQuickTime MOV
cont_aviAVIMPEG-4 (DivX) / MP3ffmpegLegacy AVI/DivX
cont_webmWebMVP9 / OpusffmpegWebM (VP9/Opus)
cont_flvFLVH.264 / AACffmpegFlash FLV demuxer

Container tokens map to their Plex aliases in native-caps.ts: mp4 covers m4v, ts covers mpegts.

Video codecs (vid_*)

Each in MKV, with HEVC Main10 and AV1 also tested in MP4 because the container changes the answer. The Plex token column is what a decoded clip credits; null means the variant must not credit base support.

Test idCodec / profileContainerffmpeg encoderSourcePlex token
vid_h264_mkvH.264 8-bitMKVlibx264, yuv420pffmpegh264
vid_h264_10_mkvH.264 Hi10P (10-bit AVC)MKVlibx264, yuv420p10leffmpegnull (never credits base H.264)
vid_hevc_mkvHEVC 8-bitMKVlibx265, yuv420p, hvc1ffmpeghevc
vid_hevc_10_mkvHEVC Main10MKVlibx265, yuv420p10le, hvc1ffmpeghevc
vid_hevc_10_mp4HEVC Main10MP4libx265, yuv420p10le, hvc1ffmpeghevc
vid_av1_mkvAV1MKV (Opus)libsvtav1ffmpegav1
vid_av1_mp4AV1MP4 (AAC)libsvtav1ffmpegav1
vid_vp9_webmVP9WebM (Opus)libvpx-vp9ffmpegvp9
vid_mpeg2_tsMPEG-2 (broadcast/DVD)MPEG-TS (AC3)mpeg2videoffmpegmpeg2video

Audio codecs (aud_*)

Each over H.264 video in MKV, so a silent-audio result is unambiguous. E-AC3 is tested in both MKV and MP4 — the container-sensitivity that bit us. Surround codecs (AC3/E-AC3/DTS/TrueHD/FLAC) are encoded from the synthesized 5.1 tone bed. Plex names get folded to one token by canonicalAudioCodec() (dca/dca-madts, ec-3eac3, mlptruehd).

Test idCodecContainerffmpeg encoderSourcePlex token
aud_aacAACMKVaacffmpegaac
aud_ac3AC3 (Dolby Digital)MKVac3ffmpegac3
aud_eac3_mkvE-AC3 in MKVMKVeac3ffmpegeac3
aud_eac3_mp4E-AC3 in MP4MP4eac3ffmpegeac3
aud_dtsDTS Digital SurroundMKVdca (-strict -2)ffmpegdts
aud_truehdDolby TrueHD (lossless)MKVtruehd (-strict -2)ffmpegtruehd
aud_flacFLAC (lossless)MKVflacffmpegflac
aud_alacALACMKValacffmpegalac
aud_opusOpusMKVlibopusffmpegopus
aud_pcmPCM (raw)MKVpcm_s16leffmpegpcm
aud_dtshdDTS-HD Master AudioMKV (HEVC)real sampledts
aud_atmos_eac3Atmos (E-AC3 JOC)MP4 (HEVC)real sampleeac3
aud_atmos_truehdAtmos (TrueHD)MKV (HEVC)real sampletruehd

HDR / color (hdr_*)

HEVC Main10 video throughout. Only HDR10 is generatable (via explicit -x265-params); dynamic-metadata and broadcast HDR need real samples.

Test idFeatureContainerSourceNotes
hdr_hdr10_mkvHDR10MKV (AC3)ffmpeg-x265-params hdr10=1:…
hdr_hdr10_mp4HDR10MP4 (E-AC3)ffmpegHDR10 in MP4
hdr_hdr10plusHDR10+ dynamic metadataMKVreal sample
hdr_hlgHLG (broadcast HDR)MKVreal sample
hdr_dv_p5_mp4Dolby Vision Profile 5MP4real sample
hdr_dv_p8_mp4Dolby Vision Profile 8.1MP4real sample
hdr_dv_p7_mkvDolby Vision Profile 7MKV (TrueHD)real sampleUsually rejected in MKV

HDR verdicts are marked manual in the matrix (whether HDR renders correctly is a subjective axis the auto-probe can't verify), so the automatic run measures decode but doesn't assert the HDR result.

Performance ladder (perf_*)

HEVC/AAC in MKV at a rising resolution + bitrate, to find the panel's decode ceiling — all ffmpeg-fabricated.

Test idFeatureNotes
perf_1080p60_10m1080p60 @ 10 MbpsHFR baseline
perf_4k30_40m4K30 @ 40 MbpsStandard 4K
perf_4k60_80m4K60 @ 80 Mbps4K60 streaming tier
perf_4k60_120m4K60 @ 120 Mbps4K decode ceiling
perf_8k60_150m8K60 @ 150 Mbps8K raster + network limit

Subtitles (sub_*)

Text subs are generatable; image subs (which force software rendering) need real samples.

Test idSubtitleContainerSourceNotes
sub_srtSRT textMKVffmpeg
sub_assASS/SSA styledMKVffmpeg
sub_pgsPGS image subsMKV (HEVC)real sampleSW-render torture
sub_vobsubVobSub image subsMKVreal sample

Edge cases (edge_*)

Test idFeatureContainer / codecSourceNotes
edge_interlaced1080i interlacedMPEG-TS / MPEG-2 / AC3ffmpegDe-interlace path
edge_anamorphicNon-square pixels (SAR)MP4 / H.264ffmpegsetsar=32/27
edge_tracks_3030 audio tracksMKV / H.264ffmpegDemuxer stress
edge_hfr_120120 fpsMKV / HEVCffmpegPanel refresh ceiling

Baking the clips into the image

The fabricated clips are frozen into a tarball and published on the private media-v1 GitHub release. The Docker build stages it into the image (gh release download media-v1 -p capability-media.tar.gz), extracts it, and sets CAP_MEDIA_DIR. No ffmpeg and no release token ever live in the running image — the clips are pre-generated and simply unpacked. If the tarball is absent the image still builds; the diagnostic clips are just missing.

On this page