oxideav-basic 0.0.9

Simple standard codecs and containers for oxideav (PCM, WAV, ...)
Documentation

oxideav-basic

Simple standard codecs and containers for oxideav (PCM, WAV, ...)

Part of the oxideav framework — a pure-Rust media transcoding and streaming stack. Codec, container, and filter crates are implemented from the spec (no C codec libraries linked or wrapped, no *-sys crates). Optional hardware-engine crates (oxideav-videotoolbox / -audiotoolbox / -vaapi / -vdpau / -nvidia / -vulkan-video) bridge to OS APIs via runtime libloading; pass --no-hwaccel (or omit the hwaccel feature) to opt out.

What's included

  • PCM codecs: pcm_u8, pcm_s16le, pcm_s24le, pcm_s32le, pcm_f32le, pcm_f64le.
  • WAV container: RIFF/WAVE (plus EBU Tech 3306 / ITU-R BS.2088 RF64 and BW64 64-bit-extended forms) demuxer + muxer with fmt, data, and the full Microsoft RIFF MCI §3 "INFO List Chunk" baseline (23 sub-IDs from the 1991 spec: IARLarchival_location, IARTartist, ICMScommissioned, ICMTcomment, ICOPcopyright, ICRDdate, ICRPcropped, IDIMdimensions, IDPIdpi, IENGengineer, IGNRgenre, IKEYkeywords, ILGTlightness, IMEDmedium, INAMtitle, IPLTpalette_setting, IPRDalbum, ISBJsubject, ISFTencoder, ISHPsharpness, ISRCsource, ISRFsource_form, ITCHtechnician; non-baseline ITRKtrack retained for compatibility; unknown sub-IDs are skipped silently). Dispatches WAVE_FORMAT_ALAW (0x0006) / WAVE_FORMAT_MULAW (0x0007) to the pcm_alaw / pcm_mulaw codecs (host runtime applies G.711 decode). WAVE_FORMAT_EXTENSIBLE (0xFFFE) is parsed end-to-end — the 22-byte extension's wValidBitsPerSample, dwChannelMask and SubFormat GUID are surfaced through both wav:fmt.* metadata keys and typed accessors on the concrete WavDemuxer. The dwChannelMask bitmap is also decoded into a human-readable SPEAKER_* layout (wav:fmt.channel_layout + WavDemuxer::channel_layout), +-joined least-significant-bit-first per the 18 documented flag bits (FRONT_LEFT 0x1 .. TOP_BACK_RIGHT 0x20000) in docs/container/riff/waveformatextensible/ms-waveformatextensible.html; bits above the highest defined flag are preserved as UNKNOWN(0x...). Any SubFormat GUID built from the KSMedia.h DEFINE_WAVEFORMATEX_GUID(x) template — {0000xxxx-0000-0010-8000- 00AA00389B71}, where the leading 16 bits carry the legacy wFormatTag x — resolves through the SAME wFormatTag dispatch the legacy WAVEFORMATEX path uses, implementing the documented IS_VALID_WAVEFORMATEX_GUID / EXTRACT_WAVEFORMATEX_ID macros from docs/container/riff/waveformatextensible/ms-converting-format-tags-and-subformat-guids.md. This generalises the four hand-listed KSDATAFORMAT_SUBTYPE_* GUIDs (PCM 0x0001, IEEE_FLOAT 0x0003, ALAW 0x0006, MULAW 0x0007) to every tag-derived GUID, and surfaces the embedded tag as wav:fmt.subformat_tag (e.g. an EXTENSIBLE file whose SubFormat is {00000055-...} is observably MP3-tagged even though this crate doesn't decode MP3). Template GUIDs whose embedded tag isn't a format this crate maps directly, and non-template (unknown) GUIDs, both synthesise a wav:guid_<canonical-text> id. WavMuxOptions::with_extensible(mask) opts the muxer into writing a 40-byte EXTENSIBLE fmt chunk. The bext Broadcast Audio Extension chunk (EBU Tech 3285) is parsed and surfaced through wav:bext.* metadata keys — description, originator, origination date/time, 64-bit TimeReference, BWF version, SMPTE-330M UMID (v1+) and the v2 loudness fields (LoudnessValue, LoudnessRange, MaxTruePeakLevel, MaxMomentaryLoudness, MaxShortTermLoudness, each ×100 fixed-point rendered to two decimals) plus CodingHistory. The fact chunk (RIFF MCI §3 "FACT Chunk") is parsed — dwFileSize (per-channel sample count) surfaces as wav:fact.sample_count and becomes the authoritative StreamInfo::duration (matters for compressed streams where data_size / block_align is meaningless); future-extension bytes past the 4-byte fixed field surface their total under wav:fact.body_len; a fact-vs-heuristic mismatch surfaces as wav:fact.mismatch. The muxer emits a fact chunk for every non-PCM wFormatTag (G.711 A-law/μ-law and the EXTENSIBLE escape hatch) per spec, and skips it for plain PCM where it is optional. The cue chunk, plst (Playlist) chunk and LIST adtl (Associated Data List) sub-chunks are parsed per Microsoft RIFF MCI §3 — cue points surface as wav:cue.count plus per-point wav:cue.<dwName>.position / .fcc_chunk / .chunk_start / .block_start / .sample_offset; playlist segments surface as wav:plst.count plus per-segment wav:plst.<n>.cue_id / .length / .loops (zero-based segment index <n> because a single cue id can be replayed by multiple playlist entries); labl / note text sub-chunks surface as wav:adtl.labl.<dwName> / wav:adtl.note.<dwName>; the ltxt (text-with-segment-length) sub-chunk surfaces as wav:adtl.ltxt.<dwName>.length / .purpose (FOURCC) / .text plus its four §3 locale WORDs .country / .language / .dialect / .code_page (raw decimals, always emitted) with .country_name / .language_name resolved through the same §3 Chapter-2 tables the CSET chunk uses (emitted only when the code is in the enumerated set); the file (embedded media file) sub-chunk surfaces wav:adtl.file.<dwName>.med_type (FOURCC when printable, the spec-allowed zero as 0, hex otherwise) and .body_len (embedded payload length — the fileData bytes themselves are not exposed through the string-typed metadata API); sub-chunks shorter than their fixed headers are skipped as opaque. The smpl (Sampler) and inst (Instrument) chunks surface through wav:smpl.* (manufacturer / product / sample_period / midi_unity_note / midi_pitch_fraction / smpte_format / smpte_offset rendered as HH:MM:SS:FF / sampler_data_len / num_sample_loops + per-loop wav:smpl.loop.<n>.{cue_point_id,type,start,end,fraction,play_count}) and wav:inst.{unshifted_note,fine_tune,gain,low_note,high_note, low_velocity,high_velocity} (signed fine_tune / gain decoded as i8). Loop counts that exceed the chunk body are clamped; bodies shorter than the 36-byte smpl / 7-byte inst fixed header are treated as opaque. The iXML third-party metadata block (the production-recorder schema catalogued in ExifTool's RIFF tag table) is surfaced through wav:ixml (UTF-8 text payload, trimmed at the first NUL + surrounding whitespace) and wav:ixml.body_len (raw on-wire chunk size, always emitted when the chunk is present so a NUL-padded "reserved for in-place editing" region is still visible to downstream tooling); bodies that are empty or entirely NUL/whitespace surface only wav:ixml.body_len. The <axml> chunk (EBU Tech 3285 Supplement 5) carries a UTF-8 XML document — typically an EBUCore wrapper around an <audioFormatExtended> ADM document or an ISRC identifier declaration — and surfaces through wav:axml (text payload trimmed at the first NUL + surrounding whitespace, schema-agnostic) and wav:axml.body_len (always emitted when the chunk is present, so a NUL-padded ADM reservation block reserved for in-place editing is observable). The _PMX chunk (Adobe XMP packet, the WAV/AVI carrier for an XMP serialised packet — FOURCC is little-endian "XMP_" reversed; catalogued in exiftool-riff-tags.html § "RIFF Main tags" entry '_PMX', scope "AVI and WAV files") surfaces through wav:xmp (UTF-8 XMP packet text trimmed at the first NUL + surrounding whitespace, so writers that NUL-pad a fixed-size XMP region for in-place editing do not leak padding into the text key) and wav:xmp.body_len (always emitted when the chunk is present, so an XMP-aware reserved block is observable). Schema-agnostic — <?xpacket begin=...?> / <?xpacket end=...?> and the inner x:xmpmeta / RDF tree pass through unchanged. The CSET (Character Set) chunk (RIFF MCI §3 "CSET Chunk") is parsed end-to-end: wCodePage / wCountryCode / wLanguageCode / wDialect (each a 16-bit LE field) surface under wav:cset.code_page / .country / .language / .dialect, the §3 country and (language, dialect) tables resolve to human-readable wav:cset.country_name / wav:cset.language_name keys, and wav:cset.body_len is always emitted (so writers that extend the chunk past its canonical 8-byte struct are observable). Bodies shorter than 8 bytes are treated as opaque; bodies longer than 8 bytes tolerate the trailing region for forward compatibility. When the top-level magic is RF64 or BW64 (the latter signalling an ADM-carrying file per ITU-R BS.2088) the demuxer expects a mandatory ds64 chunk immediately after WAVE per EBU Tech 3306 §3 and Annex A.2. The 28-byte fixed prefix carries the 64-bit riffSize, dataSize and sampleCount overrides plus a tableLength count for an optional array of (chunkId, chunkSize64) records describing other non-data chunks that exceed 4 GiB. The 32-bit on-wire size field on any chunk may be the 0xFFFFFFFF sentinel — data is promoted via the dedicated dataSize field, other chunk-IDs via the table lookup. Surfaces wav:rf64.magic (RF64/BW64), wav:rf64.riff_size, wav:rf64.data_size, wav:rf64.sample_count, wav:rf64.table.count plus per-entry wav:rf64.table.<i>.id / .size and wav:rf64.body_len. A sentinel without a ds64 override is rejected as malformed; a ds64 body shorter than 28 bytes is rejected. The 32-bit legacy fact.dwFileSize is promoted to the 64-bit ds64.sampleCount when it carries the sentinel. The JUNK (Filler) chunk (RIFF MCI §2 "JUNK (Filler) Chunk") is recognised end-to-end — the chunk body is defined as "no relevant data" so its bytes are not surfaced, but the demuxer accounts for every JUNK chunk seen: wav:junk.count (total number of JUNK chunks), wav:junk.total_bytes (cumulative payload bytes across all JUNK chunks; excludes the 8-byte chunk header and the word-align pad), and per-chunk wav:junk.<n>.body_len indexed zero-based by encounter order. Lets a downstream tool observe how much filler a writer reserved for in-place edits without pretending the bytes carry meaning. Multiple JUNK chunks are allowed; empty JUNK chunks (size = 0) still increment the count. Files with no JUNK chunk emit no wav:junk.* keys at all (absence is observable). The slnt (Silence) chunk (RIFF MCI §3 "Wave Data" — slnt( dwSamples:DWORD )) is recognised end-to-end: each chunk's 4-byte dwSamples count of silent samples surfaces as wav:slnt.<n>.samples (zero-based by encounter order), the rolling aggregates wav:slnt.count / wav:slnt.total_samples accumulate, and no real zero/baseline samples are synthesised into the decoded stream (the §3 note is explicit that the correct fill value is context-dependent, not necessarily zero). Bodies shorter than the 4-byte field are counted but treated as opaque (no samples key); over-length bodies decode the leading DWORD and tolerate trailing forward-extension bytes. Files with no slnt chunk emit no wav:slnt.* keys at all. The LIST 'wavl' wave-list waveform container (RIFF MCI §3 "Storage of WAVE Data" — <wave-data> -> { <data-ck> | <data-list> }, <wave-list> -> LIST('wavl' { <data-ck> | <silence-ck> }... )) is parsed end-to-end: the segmented form interleaves runs of PCM (data sub-chunks) with slnt silence-count markers, so the demuxer resolves the FIRST embedded data sub-chunk as the decode anchor (a wavl-form file is now decodable rather than yielding no audio) and surfaces every segment as wav:wavl.segment_count / wav:wavl.data_count / wav:wavl.data_bytes plus per-segment wav:wavl.<n>.kind (data/slnt) / .length. Embedded slnt segments feed the same wav:slnt.* accounting as top-level slnt chunks (silence is a count, never synthesised baseline samples); the fact chunk (required by §3 for wavl-form data) remains the authoritative duration. A silence-only wavl (no data segment) is rejected as having no waveform; odd-length data segments respect RIFF word-alignment. The Acidizer acid chunk (layout per the staged byte-indexed Acidizer table in docs/container/riff/metadata/exiftool-riff-tags.html) is supported on BOTH the read and write sides through the typed wav::AcidChunk struct (24-byte LE body: flags bit-field @0, root note @4, six reserved bytes carried verbatim @6..12, beats @12, meter @16, tempo @20, with parse / to_bytes and the five documented flag-bit helpers plus the 48..=71 root-note name table). The demuxer surfaces wav:acid.* keys (flags hex, each flag bit, root note + name, beats, meter, tempo, plus reserved-hex / body_len observability keys) and the typed view via WavDemuxer::acid(); the muxer writes the chunk via WavMuxOptions::with_acid. Truncated bodies are skipped-as-opaque; the mux→demux round-trip is pinned byte-for-byte in tests. The concrete demuxer is now publicly constructible via wav::open_wav_demuxer so every typed accessor is reachable without downcasting. ADM chna (channel-allocation) support is blocked on a docs gap: the staged ITU-R BS.2076-1 §7 defines only the logical record content (Table 46) and defers the binary chunk layout to ITU-R BS.2088, which is not yet staged under docs/container/riff/metadata/.
  • slin container: Asterisk-style headerless .sln* / .slin* raw S16LE PCM (extension drives the sample rate).
  • Y4M (YUV4MPEG2) container: rawvideo demuxer + muxer for .y4m files, supporting 4:2:0 / 4:2:2 / 4:4:4 / mono at 8/10/12-bit. Header X<key>=<val> extensions are surfaced verbatim through Demuxer::metadata.
  • Filter primitive: typed scalar Reinhard 2002 simple global tone-mapping operator (Ld = L / (1 + L)) with its closed-form inverse (L = Ld / (1 − Ld)). Scene-luminance and display-luminance domains are separated by distinct SceneLuminance / DisplayLuminance wrapper types, so callers can't accidentally swap pre- and post-tone-map values; constructors reject invalid inputs (negative, NaN, non-finite, or display ≥ 1.0). The forward map is a published mathematical fact transcribed from docs/image/filter/tone-mapping-operators.md §2.2.

Usage

[dependencies]
oxideav-basic = "0.0"

License

MIT — see LICENSE.