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](https://github.com/OxideAV/oxideav-workspace) 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: `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-baseline `ITRK``track`
  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

```toml
[dependencies]
oxideav-basic = "0.0"
```

## License

MIT — see [LICENSE](LICENSE).