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
RF64andBW6464-bit-extended forms) demuxer + muxer withfmt,data, and the full Microsoft RIFF MCI §3 "INFO List Chunk" baseline (23 sub-IDs from the 1991 spec:IARL→archival_location,IART→artist,ICMS→commissioned,ICMT→comment,ICOP→copyright,ICRD→date,ICRP→cropped,IDIM→dimensions,IDPI→dpi,IENG→engineer,IGNR→genre,IKEY→keywords,ILGT→lightness,IMED→medium,INAM→title,IPLT→palette_setting,IPRD→album,ISBJ→subject,ISFT→encoder,ISHP→sharpness,ISRC→source,ISRF→source_form,ITCH→technician; non-baselineITRK→trackretained for compatibility; unknown sub-IDs are skipped silently). DispatchesWAVE_FORMAT_ALAW (0x0006)/WAVE_FORMAT_MULAW (0x0007)to thepcm_alaw/pcm_mulawcodecs (host runtime applies G.711 decode).WAVE_FORMAT_EXTENSIBLE (0xFFFE)is parsed end-to-end — the 22-byte extension'swValidBitsPerSample,dwChannelMaskand SubFormat GUID are surfaced through bothwav:fmt.*metadata keys and typed accessors on the concreteWavDemuxer. ThedwChannelMaskbitmap is also decoded into a human-readableSPEAKER_*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) indocs/container/riff/waveformatextensible/ms-waveformatextensible.html; bits above the highest defined flag are preserved asUNKNOWN(0x...). Any SubFormat GUID built from theKSMedia.hDEFINE_WAVEFORMATEX_GUID(x)template —{0000xxxx-0000-0010-8000- 00AA00389B71}, where the leading 16 bits carry the legacywFormatTagx— resolves through the SAMEwFormatTagdispatch the legacyWAVEFORMATEXpath uses, implementing the documentedIS_VALID_WAVEFORMATEX_GUID/EXTRACT_WAVEFORMATEX_IDmacros fromdocs/container/riff/waveformatextensible/ms-converting-format-tags-and-subformat-guids.md. This generalises the four hand-listedKSDATAFORMAT_SUBTYPE_*GUIDs (PCM0x0001, IEEE_FLOAT0x0003, ALAW0x0006, MULAW0x0007) to every tag-derived GUID, and surfaces the embedded tag aswav: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 awav:guid_<canonical-text>id.WavMuxOptions::with_extensible(mask)opts the muxer into writing a 40-byte EXTENSIBLEfmtchunk. ThebextBroadcast Audio Extension chunk (EBU Tech 3285) is parsed and surfaced throughwav:bext.*metadata keys — description, originator, origination date/time, 64-bitTimeReference, BWF version, SMPTE-330M UMID (v1+) and the v2 loudness fields (LoudnessValue,LoudnessRange,MaxTruePeakLevel,MaxMomentaryLoudness,MaxShortTermLoudness, each ×100 fixed-point rendered to two decimals) plusCodingHistory. Thefactchunk (RIFF MCI §3 "FACT Chunk") is parsed —dwFileSize(per-channel sample count) surfaces aswav:fact.sample_countand becomes the authoritativeStreamInfo::duration(matters for compressed streams wheredata_size / block_alignis meaningless); future-extension bytes past the 4-byte fixed field surface their total underwav:fact.body_len; a fact-vs-heuristic mismatch surfaces aswav:fact.mismatch. The muxer emits afactchunk for every non-PCMwFormatTag(G.711 A-law/μ-law and the EXTENSIBLE escape hatch) per spec, and skips it for plain PCM where it is optional. Thecuechunk,plst(Playlist) chunk andLIST adtl(Associated Data List) sub-chunks are parsed per Microsoft RIFF MCI §3 — cue points surface aswav:cue.countplus per-pointwav:cue.<dwName>.position/.fcc_chunk/.chunk_start/.block_start/.sample_offset; playlist segments surface aswav:plst.countplus per-segmentwav:plst.<n>.cue_id/.length/.loops(zero-based segment index<n>because a single cue id can be replayed by multiple playlist entries);labl/notetext sub-chunks surface aswav:adtl.labl.<dwName>/wav:adtl.note.<dwName>; theltxt(text-with-segment-length) sub-chunk surfaces aswav:adtl.ltxt.<dwName>.length/.purpose(FOURCC) /.textplus its four §3 locale WORDs.country/.language/.dialect/.code_page(raw decimals, always emitted) with.country_name/.language_nameresolved through the same §3 Chapter-2 tables theCSETchunk uses (emitted only when the code is in the enumerated set); thefile(embedded media file) sub-chunk surfaceswav:adtl.file.<dwName>.med_type(FOURCC when printable, the spec-allowed zero as0, hex otherwise) and.body_len(embedded payload length — thefileDatabytes themselves are not exposed through the string-typed metadata API); sub-chunks shorter than their fixed headers are skipped as opaque. Thesmpl(Sampler) andinst(Instrument) chunks surface throughwav:smpl.*(manufacturer / product / sample_period / midi_unity_note / midi_pitch_fraction / smpte_format / smpte_offset rendered asHH:MM:SS:FF/ sampler_data_len / num_sample_loops + per-loopwav:smpl.loop.<n>.{cue_point_id,type,start,end,fraction,play_count}) andwav:inst.{unshifted_note,fine_tune,gain,low_note,high_note, low_velocity,high_velocity}(signedfine_tune/gaindecoded asi8). Loop counts that exceed the chunk body are clamped; bodies shorter than the 36-bytesmpl/ 7-byteinstfixed header are treated as opaque. TheiXMLthird-party metadata block (the production-recorder schema catalogued in ExifTool's RIFF tag table) is surfaced throughwav:ixml(UTF-8 text payload, trimmed at the first NUL + surrounding whitespace) andwav: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 onlywav: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 throughwav:axml(text payload trimmed at the first NUL + surrounding whitespace, schema-agnostic) andwav: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_PMXchunk (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 throughwav: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) andwav: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 innerx:xmpmeta/ RDF tree pass through unchanged. TheCSET(Character Set) chunk (RIFF MCI §3 "CSET Chunk") is parsed end-to-end:wCodePage/wCountryCode/wLanguageCode/wDialect(each a 16-bit LE field) surface underwav:cset.code_page/.country/.language/.dialect, the §3 country and(language, dialect)tables resolve to human-readablewav:cset.country_name/wav:cset.language_namekeys, andwav:cset.body_lenis 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 isRF64orBW64(the latter signalling an ADM-carrying file per ITU-R BS.2088) the demuxer expects a mandatoryds64chunk immediately afterWAVEper EBU Tech 3306 §3 and Annex A.2. The 28-byte fixed prefix carries the 64-bitriffSize,dataSizeandsampleCountoverrides plus atableLengthcount for an optional array of(chunkId, chunkSize64)records describing other non-datachunks that exceed 4 GiB. The 32-bit on-wire size field on any chunk may be the0xFFFFFFFFsentinel —datais promoted via the dedicateddataSizefield, other chunk-IDs via the table lookup. Surfaceswav:rf64.magic(RF64/BW64),wav:rf64.riff_size,wav:rf64.data_size,wav:rf64.sample_count,wav:rf64.table.countplus per-entrywav:rf64.table.<i>.id/.sizeandwav:rf64.body_len. A sentinel without ads64override is rejected as malformed; ads64body shorter than 28 bytes is rejected. The 32-bit legacyfact.dwFileSizeis promoted to the 64-bitds64.sampleCountwhen it carries the sentinel. TheJUNK(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 everyJUNKchunk seen:wav:junk.count(total number ofJUNKchunks),wav:junk.total_bytes(cumulative payload bytes across allJUNKchunks; excludes the 8-byte chunk header and the word-align pad), and per-chunkwav:junk.<n>.body_lenindexed 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. MultipleJUNKchunks are allowed; emptyJUNKchunks (size = 0) still increment the count. Files with noJUNKchunk emit nowav:junk.*keys at all (absence is observable). Theslnt(Silence) chunk (RIFF MCI §3 "Wave Data" —slnt( dwSamples:DWORD )) is recognised end-to-end: each chunk's 4-bytedwSamplescount of silent samples surfaces aswav:slnt.<n>.samples(zero-based by encounter order), the rolling aggregateswav:slnt.count/wav:slnt.total_samplesaccumulate, 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 (nosampleskey); over-length bodies decode the leading DWORD and tolerate trailing forward-extension bytes. Files with noslntchunk emit nowav:slnt.*keys at all. TheLIST '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 (datasub-chunks) withslntsilence-count markers, so the demuxer resolves the FIRST embeddeddatasub-chunk as the decode anchor (awavl-form file is now decodable rather than yielding no audio) and surfaces every segment aswav:wavl.segment_count/wav:wavl.data_count/wav:wavl.data_bytesplus per-segmentwav:wavl.<n>.kind(data/slnt) /.length. Embeddedslntsegments feed the samewav:slnt.*accounting as top-levelslntchunks (silence is a count, never synthesised baseline samples); thefactchunk (required by §3 forwavl-form data) remains the authoritative duration. A silence-onlywavl(nodatasegment) is rejected as having no waveform; odd-lengthdatasegments respect RIFF word-alignment. The Acidizeracidchunk (layout per the staged byte-indexed Acidizer table indocs/container/riff/metadata/exiftool-riff-tags.html) is supported on BOTH the read and write sides through the typedwav::AcidChunkstruct (24-byte LE body: flags bit-field @0, root note @4, six reserved bytes carried verbatim @6..12, beats @12, meter @16, tempo @20, withparse/to_bytesand the five documented flag-bit helpers plus the 48..=71 root-note name table). The demuxer surfaceswav:acid.*keys (flags hex, each flag bit, root note + name, beats, meter, tempo, plus reserved-hex / body_len observability keys) and the typed view viaWavDemuxer::acid(); the muxer writes the chunk viaWavMuxOptions::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 viawav::open_wav_demuxerso every typed accessor is reachable without downcasting. ADMchna(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 underdocs/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
.y4mfiles, supporting 4:2:0 / 4:2:2 / 4:4:4 / mono at 8/10/12-bit. HeaderX<key>=<val>extensions are surfaced verbatim throughDemuxer::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 distinctSceneLuminance/DisplayLuminancewrapper 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 fromdocs/image/filter/tone-mapping-operators.md§2.2.
Usage
[]
= "0.0"
License
MIT — see LICENSE.