A loudness meter that’s quietly wrong by 3 decibels is worse than no meter at all — it tells you the audio passed when it didn’t. OxiMedia 0.2.1 goes looking for exactly that kind of bug, in its own code, and fixes what it finds.
Today we released OxiMedia 0.2.1 — a release built on four things: an EBU R128 / K-weighting loudness stack that was measurably wrong and is now standards-accurate, a new Pure-Rust oximedia-capture crate that brings live camera and device input into the framework without linking a single C library, VP8 and VP9 decoders that now handle inter frames bit-exactly instead of just key frames, and a workspace-wide sweep that took nine crates from fabricated success to real work or an honest error.
No C. No C++. No Fortran. That held for the decode/transcode core since 0.1.9, and 0.2.1 extends it to the one place a “sovereign” media framework usually cheats: camera input. oximedia-capture talks to AVFoundation on macOS through objc2, to Video4Linux2 on Linux through raw rustix syscalls, and to Media Foundation on Windows through windows-rs COM calls — zero -sys crates, zero compiled C, on any of the three. It still compiles to one static binary (or to wasm32-unknown-unknown for the browser) and installs with one cargo add.
Why OxiMedia 0.2.1 is a game changer
Loudness measurement, camera capture, and inter-frame video decode are three places where media tooling quietly cuts corners:
- EBU R128 / ITU-R BS.1770-4 loudness meters are notoriously easy to get subtly wrong — a few dB of error is invisible until a broadcast QC pass fails or a streaming platform’s loudness normalization over- or under-corrects every file that passes through it
- “Pure Rust” media stacks that need a camera usually reach for a C binding the moment capture is required, quietly breaking the zero-C story exactly where the pipeline begins
- A VP8 or VP9 decoder that only handles key frames is a demonstration, not a codec — real-world streams are inter-frame-dominated, and the interesting bugs (motion vectors, reference-frame management, loop-filter state) only show up once inter frames are in play
- Self-consistent test suites prove a codec agrees with itself, not with the format it claims to implement — OxiMedia’s own FLAC pair passed every internal round-trip test right up until it was fed a real
ffmpeg-produced file and panicked
OxiMedia 0.2.1 ends all four.
- The EBU R128 loudness stack is now standards-accurate. The default K-weighting filter chain was wrong by up to ±35 dB against ITU-R BS.1770-4 Table 1 — a mis-implemented highpass stage, and a
3.9998decibel shelf gain used as a linear multiplier. Separately, every stereo and multichannel measurement read a flat 3.01 LU too quiet from a channel-averaging bug where the spec calls for a channel sum, and true peak was measured on channel 0 only. All fixed, andLoudnessNormalizer::normalize()— which computed a correct gain and then silently discarded it, returning the input byte-identical — now actually writes the gain back into the audio. - New
oximedia-capturecrate: enumerate devices, negotiate a format against what one actually advertises, and receive frames on a bounded queue withDropOldest/DropNewest/Blockpolicies. Verification is honestly per-platform rather than uniform: macOS is hardware-verified against real AVFoundation devices; Linux’s V4L2 struct layout is const-verified against kernel 6.19 UAPI headers; Windows’ Media Foundation logic is host-tested against synthetic input. A target with none of the three reportsUnsupportedPlatform— “not implemented” and “no camera attached” are never the same answer. - VP8 and VP9 now decode inter frames bit-exactly against libvpx. VP9 gained cross-frame reference and probability state, inter-mode and motion-vector entropy decode, eight-tap motion compensation and compound prediction — 10 of 10 conformance streams bit-exact through the public decoder API. VP8 gained motion-vector entropy decode, sub-pixel motion compensation, and last/golden/altref reference management — bit-exact on 5 multi-frame libvpx streams, including one with a hidden altref frame.
- FLAC is conformant in both directions. The encoder and decoder were a self-consistent toy pair; fed a real
ffmpeg-produced file, the decoder panicked. Both sides were rebuilt against RFC 9639 — subframe bit-layout, the Rice unary convention, LPC precision/shift fields, stereo decorrelation — and now round-trip againstlibFLACandffmpegin both directions. - A nine-crate fabricated-success sweep.
oximedia-videoipexposed hardcoded 1920×1080 “decoders”;oximedia-mam’s cloud storage backends logged and returnedOkunconditionally, withexists()alwaystrue;oximedia-serveruploaded nothing to any CDN and fabricated unsigned “presigned” URLs;oximedia-denoisewrote its output into a clone it immediately discarded, at 12 sites across 8 files, so most of the denoiser silently returned its input unchanged. Each now does the real work or refuses by name. TheTODO(0.2.x)marker count in Rust source fell from 123 to 22. - The in-tree Opus codec is honestly demoted, not silently left broken. Fed real libopus packets for the first time this cycle, decode returned silence and the encoder’s TOC byte misdescribed its own payload. Every wired consumer already refused to pass Opus through and continues to — this is a label correction surfaced by finally testing against real fixtures, not a new regression.
Technical Deep Dive: how the loudness, capture, and codec work fit together
- Loudness layer —
oximedia-meteringandoximedia-audio.ebu_r128_impl.rsandfilters.rsnow carry the exact ITU-R BS.1770-4 Table 1 biquad coefficients at 48 kHz and correct bilinear-transform designs at every other rate;complete_hopsums per-channel weighted power instead of averaging it;true_peak_dbtp()returns the maximum across every channel’s 4×-oversampled detector, not just channel 0. New conformance tests pin the chain magnitude to within 0.1 dB at 100 Hz, 997 Hz, and 4 kHz. - Capture layer — the new
oximedia-capturecrate. Three platform backends behind oneenumerate()/open()entry point, delivering raw pixel formats or an untouched MJPEG bitstream only — never a decode of an encumbered codec. NewPixelFormat::{Yuyv422, Uyvy422}back the two most common UVC/AVFoundation raw formats end to end, frombits_per_pixelthroughframe_buffer_size. - Codec layer —
oximedia-codec’svp8/dec/andvp9/dec/. VP8’s near-MV survey (NEAREST/NEAR/ZERO/NEWMV,SPLITMVsub-partitioning), six-tap and bilinear sub-pixel motion compensation, and sequential last/golden/altref reference updates; VP9’s persistent cross-frame state (four probability contexts, an eight-slot MI-aligned reference DPB),vpx_convolve8motion compensation, andfind_mv_refswith sign-bias flipping — every function a cited port of libvpx v1.15.2, diff-verified against a fresh fetch of the tagged upstream before use. Five hollow VP9 modules (~4,800 lines) that had zero real consumers were deleted in the same pass. - The honesty sweep, crate by crate.
oximedia-videoip’s dummy encoders/decoders andoximedia-mam’s fake cloud backends were deleted outright rather than patched;oximedia-server’sCdnUploader::new()— which produced an uploader with an empty bucket and a worker that silently dropped every packet — is gone, replaced byCdnUploader::with_config(...), which fails fast instead.
Getting Started
cargo add oximedia
or pin the version and pick features in Cargo.toml:
[dependencies]
oximedia = { version = "0.2.1", features = ["full"] }
Measure loudness the standards-accurate way — straight from the crate’s own conformance tests:
use oximedia::prelude::*;
// Probe a media file
let data = std::fs::read("video.webm")?;
let result = probe_format(&data)?;
println!("Format: {:?}, Confidence: {:.1}%",
result.format, result.confidence * 100.0);
// Transcode with quality control
let pipeline = TranscodePipeline::builder()
.input("input.mkv")
.video_codec(VideoCodec::Vp9)
.audio_codec(AudioCodec::Flac)
.output("output.webm")
.build()?;
pipeline.run().await?;
Or open a camera with the new capture crate directly:
use oximedia_capture::{CaptureConfig, CaptureEncoding, DeviceSelector};
use oximedia_core::PixelFormat;
fn main() -> Result<(), oximedia_capture::CaptureError> {
let devices = oximedia_capture::enumerate()?;
let Some(device) = devices.first() else {
eprintln!("no capture device found");
return Ok(());
};
let config = CaptureConfig::default()
.with_device(DeviceSelector::Id(device.id.clone()))
.with_size(1280, 720)
.with_fps(30.0)
.with_preferred([CaptureEncoding::Raw(PixelFormat::Nv12)]);
let mut session = oximedia_capture::open(config)?;
if let Some(mut stream) = session.take_stream() {
while let Ok(Some(frame)) = stream.recv() {
println!("frame {} at {:?}", frame.sequence, frame.timestamp);
}
}
Ok(())
}
What’s New in 0.2.1
- Added: the
oximedia-capturecrate (AVFoundation/V4L2/Media Foundation);PixelFormat::{Yuyv422, Uyvy422}; VP8 inter-frame decode (bit-exact vs libvpx); VP9 inter-frame and intra-only decode (bit-exact vs libvpx, 10/10 conformance streams); lossy WebP decode wired to the existing bit-exact VP8 key-frame path; an honestCodecId::Aacidentification variant; MP4 seek (seek_position/seek_to_stream_pts) and rotation metadata (tkhddisplay matrix); a TikTok loudness delivery preset. - Changed:
docs/codec_status.mdand the README re-synchronized — VP9 and FLAC promoted to Verified, AVIF and WebP to Functional, Opus demoted to Bitstream-parsing; several BREAKING signature changes inoximedia-videoipandoximedia-serverthat were part of removing their fabricated paths (see the CHANGELOG for the full list). - Removed: five hollow VP9 modules (~4,800 lines, zero real consumers);
oximedia-videoip’sDummy*encoders/decoders;oximedia-mam’s fakeS3Storage/AzureStorage/GCSStorage;oximedia-server’s legacy uploaders that fabricated “presigned” URLs. - Fixed: the ±35 dB K-weighting bug and the 3.01 LU stereo channel-sum bug in the loudness stack; a WebP lossy encoder that produced VP8 frames no decoder could read (rebuilt to RFC 6386 §7.3/§9/§13/§14); FLAC rebuilt to RFC 9639 in both directions; fragmented MP4 (
moof/traf/trun) demuxing, which previously yielded zero packets; a real MP4 seek implementation; a reachable panic-as-DoS in the Matroska demuxer on untrusted input; 12 sites across 8 files whereoximedia-denoisewrote into a clone it discarded. - Performance:
oximedia_graph::filters::video::ScaleFilteris roughly 6x faster on 9:16 vertical-video reframing with zero output bytes changed (pinned by BLAKE3 digests across 42 geometry/algorithm combinations); the AV1 inverse transform moved fromi64toi32with proven-safe magnitude bounds; the AV1 loop filter is now SIMD via a portableSimd4backend.
Tips
- Re-run anything you loudness-normalized before 0.2.1. If your pipeline used
LoudnessNormalizer::normalize(),EbuR128Meter, orR128Meteron stereo/multichannel audio, the measurement was up to 3 LU too quiet and the “normalized” output may never have actually been gain-adjusted. Re-measure and re-normalize with 0.2.1. - Treat Opus as untrustworthy in both directions, in-tree, until a conformant implementation lands — every wired consumer (
oximedia-videoip, the loudness normalizer, the frame-level transcode path) refuses it by name rather than passing corrupted audio through silently. - Check
oximedia’svideofeature before relying on it alone. A known gap means--features videoon the facade currently reaches zero codecs by itself — pull infull, or depend onoximedia-codecdirectly with explicit codec features, until the forwarding gap closes. oximedia-capturedelivers raw frames or untouched MJPEG only — pipe MJPEG throughoximedia-codec’smjpegfeature yourself if you need decoded pixels; the capture crate deliberately never decodes an encumbered codec, even implicitly.- Recheck error handling anywhere you call the newly-honest paths —
oximedia-videoip,oximedia-mam,oximedia-server’s CDN uploader, andoximedia-denoisemay now return a realError genuinely different bytes where they used to fabricate success. That’s the fix working as intended. - Use the new
TikTokloudness preset (Standard::TikTok/TargetPreset::TikTok, or--standard tiktokon the CLI) for −14.0 LUFS / −1.0 dBTP delivery without hand-rolling the targets yourself.
This is the foundation
OxiMedia is the pure-Rust media and computer-vision layer of the COOLJAPAN ecosystem, leaning on its siblings directly:
- OxiONNX — the Pure-Rust ONNX runtime behind every opt-in
oximedia-mlpipeline, still zero symbols linked by default. - OxiFFT — the spectral engine behind phase correlation, log-mel spectrograms, and the audio-feature paths this release corrected.
- SciRS2 — scientific-computing primitives (linalg, SIMD) for the CV and signal paths.
- OxiArc — Pure-Rust compression (
oxiarc-archive,oxiarc-lz4,oxiarc-zstd,oxiarc-deflate) for containers and archival. - OxiSQL —
oxisql-sqlite-compatbacks every SQLite-persisted path inoximedia-server,oximedia-rights,oximedia-archive, andoximedia-dedup. - OxiXML —
oxixml-quickxml-compatnow backs every XML-parsing path across 14 dependent crates, a source-compatible drop-in for the upstream cratedeny.tomlnow bans outright. - OxiScope and OxiLink — production consumers of the
oximedia-webWASM modules (scopes, color, scale, quality), built on the same Pure-Rust discipline as this release’s native-side work.
Repository: https://github.com/cool-japan/oximedia
Star the repo if you think a loudness meter that’s wrong by 3 decibels is a worse failure mode than a codec that refuses to run at all — and that a media framework should go looking for both kinds of bug in its own code before anyone else finds them.
The era of media tooling that measures loudness approximately, captures video through a C shim, and calls a key-frame-only decoder “done” is over. Pure Rust media is here — bit-exact where it counts, standards-accurate where it’s measured, and honest everywhere else.
— KitaSan at COOLJAPAN OÜ August 13, 2026