Files
v10/internal/design/spf/features/audio-abr.md
T

300 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
status: draft
date: 2026-05-20
definition: technical
---
# Audio ABR
Bandwidth-driven adaptive bitrate selection for audio. Wires
`createTrackedFetch` into audio segment loading so audio fetches emit
per-chunk bandwidth samples; adds a `switchAudioQuality` behavior
parallel to `switchVideoQuality` that selects audio renditions based
on bandwidth + constraints. The audio sibling of
[video-abr](./video-abr.md) — same sampling-baked-into-loading pattern,
same EWMA + safety-margin algorithm, same constraint-slot manual-
override shape — applied within a single audio rendition group
(typically a set of bitrate-varied AAC stereo renditions in one
language).
A **Media-src feature** in the framing from
[clusters.md § Feature classification axes](./clusters.md#feature-classification-axes):
audio renditions with multiple bitrates won't *adaptively* select
without it (they'll play at the default-picked bitrate regardless of
bandwidth conditions).
## Status
- **Composition:** not implemented in `createSimpleHlsEngine`. Today
[`audio-playback`](./audio-playback.md) is the single-rendition
baseline; `setupAudioBufferActors`
(`packages/spf/src/playback/behaviors/dom/setup-buffer-actors.ts`)
uses plain `fetchStream` (no bandwidth sampling) and there is no
audio-side equivalent of `switchVideoQuality`. The in-code JSDoc
on `setupAudioBufferActors` (lines 249-255) explicitly documents
the audio-ABR wiring change as "a localized change to this setup
body (swap `fetchStream` for a `createTrackedFetch` call + declare
`bandwidthState` writable here)."
- **Definition depth:** technical — scope and constraints articulated
against [video-abr](./video-abr.md)'s sketched precedent; algorithm
reuses existing primitives (`createTrackedFetch`, `selectQuality`,
`BandwidthState` EWMA accumulator); audio-specific design decisions
open. Source material: [SPF Epics Working Doc — epic #8 Audio ABR /
Multi-bitrate](https://www.notion.so/35f97a7f89d08123a13fecab1ca1cac4)
(cluster C, Media-src, eng M-L, validation M; "Depends on #7" —
the multi-language-audio rendition-group machinery).
- **Prerequisite chain:** Notion #8 lists this as depending on #7
(multi-language-audio) for the rendition-group machinery. In
practice, single-rendition audio-playback is the only hard
prerequisite for the *mechanism*; multi-language-audio enables the
*interesting case* (multiple audio renditions to switch between
within a language).
## Phases of complexity
Scope slices mirroring [video-abr.md](./video-abr.md)'s structure.
Phases here are aspirational (technical-depth doc); they describe
what would land, not what exists.
| Phase | What | Notes |
|---|---|---|
| Bandwidth sampling on audio loading | Swap plain `fetchStream` for `createTrackedFetch` in `setupAudioBufferActors`. Audio segment fetches emit per-chunk bandwidth samples via EWMA accumulator, same as video does today | One-line swap per the in-code JSDoc instruction. Affects state-key writability for the setup behavior (declares `bandwidthState` writable). Reuses `createTrackedFetch` + `sampleBandwidth` primitives unchanged |
| Bandwidth state — shared or separate | Decide whether audio samples feed the existing single `state.bandwidthState` slot (multi-writer, shared estimate) or a new `state.audioBandwidthState` slot (per-type independent estimate). Affects EWMA accumulator design and the slot-multi-writer characterization | Open question; the choice constrains the sampling-phase wiring above and the switching-phase reads below. See Open questions |
| `switchAudioQuality` behavior | New behavior parallel to `switchVideoQuality` (`packages/spf/src/playback/behaviors/quality-switching.ts`). Reads `presentation`, `selectedAudioTrackId`, the relevant bandwidth slot; writes `selectedAudioTrackId`. Reuses the pure `selectQuality` algorithm from `media/abr/quality-selection.ts` unchanged | Same EWMA + safety-margin + upgrade-margin algorithm as video. Mirror of video-abr's "Dynamic bandwidth-driven adjustment" phase |
| Manual override via `userAudioTrackSelection` | New constraint slot analog of `userVideoTrackSelection`. Consumer-driven narrowing of the audio candidate set; when only one survives, `switchAudioQuality` short-circuits — same pattern as video-abr's manual override | Open: whether this is a *separate* slot from multi-language-audio's Tier 2 programmatic write or *shares* it. video-abr uses a single slot (`userVideoTrackSelection`) for both pin-to-quality and manual-track-override; the analogous decision for audio is whether one slot handles both bitrate-pinning and language-pinning |
| Multi-writer coordination on `selectedAudioTrackId` | Slot becomes triple-writer: `selectAudioTrack` default + `switchAudioQuality` ABR + (future) `multi-language-audio` Tier 2 programmatic. Same shape as video's `selectedVideoTrackId` triple-writer (default + ABR + external manual override) | Per the multi-writer characterization in [clusters.md § Multi-writer state slots](./clusters.md#multi-writer-state-slots): three writers from different decision domains (config vs derived vs intent) but all writing the same value type (track id). Coordination shape is well-precedented by video |
| Cross-language / cross-channel boundary handling | Audio-ABR operates within a single rendition group (same language, same channel count, same codec). Cross-language switching consumes `multi-language-audio`'s primitive; cross-channel-count switching consumes `5.1-surround-selection`'s `changeType()` primitive | Defines the boundary of "within ABR scope" vs "consumes a sister feature's primitive." Bandwidth-driven switching across codec/channel/language boundaries is more aggressive and exits this feature's surface |
## What's in scope vs out of scope
**In scope:**
- All six phases above for HLS multi-bitrate audio rendition groups
- Audio-side bandwidth sampling via `createTrackedFetch`
- `switchAudioQuality` behavior + interaction with the (existing)
`selectQuality` algorithm
- `userAudioTrackSelection` constraint slot (or shared-slot
alternative — see Open questions)
- Same-codec, same-channel-count, same-language ABR switching
**Out of scope (separate Media-src candidate features):**
- **[multi-language-audio](./multi-language-audio.md)** *(coarse)*
cross-language switching is its concern. Audio-ABR consumes
multi-language-audio's rendition-group surfacing (Tier 1) when
multiple renditions exist within a language; ABR switches *within*
a language, not across.
- **[5.1-surround-selection](./5.1-surround-selection.md)** — cross-
channel-count switching consumes its `changeType()` primitive
(mid-stream codec change phase). Audio-ABR switches *within* a
channel-count tier; if ABR wants to cross a channel-count boundary
(e.g., downgrade from AC-3 5.1 to AAC stereo under bandwidth
pressure), that's 5.1-surround-selection's `changeType()` phase.
- **`[multi-signal-abr]`** *(candidate)* — non-bandwidth signals
(CPU / thermal / battery) feeding ABR. Same shape as the candidate
for video; would apply to audio too if it lands.
- **Audio rendition caps** — max-bitrate, max-channels on the audio
side. Open whether these fold into
[rendition-selection-caps.md](./rendition-selection-caps.md)
(which is currently video-only) or carry separately. Cross-ref to
that doc's "Audio caps" open question.
**Out of scope (different architectural layer):**
- Adapter-level audio-quality picker UI / "audio quality menu"
customer surfaces.
- Above-engine "current audio bitrate" / "currently switching"
surfaces. Consumers read `selectedAudioTrackId` + the resolved
presentation's audio track bandwidth directly.
## Likely cross-cutting impact
Things this feature probably forces decisions on, not just additions:
- **`bandwidthState` multi-writer (if shared).** Today
`setupVideoBufferActors` is sole writer per the JSDoc at
`setup-buffer-actors.ts:210`. If audio shares the slot,
`setupAudioBufferActors` becomes a second writer to the same slot —
same decision domain (network bandwidth from per-chunk EWMA), same
trigger (segment fetch sample), same cost (cheap write). The
easiest multi-writer shape per
[clusters.md § Multi-writer state slots](./clusters.md#multi-writer-state-slots);
the only real design question is how the EWMA accumulator handles
samples from two sources. Two sub-questions: (a) does one shared
accumulator absorb both video and audio samples (mixes them at
sample time), or (b) does the slot hold separate per-source
accumulators that compose at read time? The current `BandwidthState`
shape would need a small extension either way.
- **`selectedAudioTrackId` writer characterization.** As of multi-
language-audio Tier 2 landing, the slot stays **single-writer**
`switchAudioTrack` (in `playback/behaviors/dom/switch-audio-track.ts`)
is the sole writer. Consumer intent flows through the
`userAudioTrackSelection` constraint+filter slot. Mid-stream flush is
dispatched from `switchAudioTrack` itself (slot owner = flush dispatcher
— keeps slot writes and consequences co-located, sitting architecturally
between `SegmentLoaderActor` and `SourceBufferActor`). When audio-abr
ships, `switchAudioQuality` extends `switchAudioTrack` with bandwidth +
ABR algorithm — either by rename + extension or as a sibling variant
that supplants it in the engine composition. Either way, the slot stays
single-writer and the filter shape carries over.
- **Constraint+filter vs multi-writer for the manual-override slot.**
video-abr's `userVideoTrackSelection` slot is a *constraint+filter*
(the selection writer reads it as a filter; doesn't write the
selection directly). Audio's manual-override slot could be the
same shape (`userAudioTrackSelection` filters the candidate set
for `switchAudioQuality`), OR multi-language-audio Tier 2 could
introduce a *separate* programmatic-write path that directly
writes `selectedAudioTrackId` for language switches. Whether
bitrate-pinning and language-pinning share a slot or split affects
the multi-writer count on `selectedAudioTrackId`.
- **EWMA accumulator under sample-source mixing.** If video and audio
samples both feed the same accumulator, the EWMA estimate is biased
by the sample-size distribution: a small audio segment (~50KB) and
a large video segment (~2MB) both produce one sample, but their
bandwidth measurements may differ (audio fetches may not warm the
network path as effectively). Two mitigation shapes: (a) weight
samples by byte count at accumulator-update time, (b) keep per-
source accumulators and read `min(video, audio)` similar to today's
`min(fast, slow)` read pattern. The dual-EWMA pattern already
precedents the latter approach.
- **`selectQuality` algorithm reuse.** `selectQuality` in
`media/abr/quality-selection.ts` is a pure function over `Track[]`
+ `BandwidthState` + config. It should reuse verbatim for audio;
the algorithm doesn't depend on track type. However, the safety/
upgrade-margin defaults are tuned for video bitrate scales (Mbps);
audio scales are ~10× smaller (kbps). Whether the same defaults
work for audio or whether audio wants its own tuning is an open
question.
- **Engine composition order.** `switchVideoQuality` is composed
after `setupSourceBuffers` + `trackCurrentTime` per
[hls-engine.md](../../../../packages/spf/docs/hls-engine.md)
(Stage 6). `switchAudioQuality` would compose at the same point or
immediately after. Engine variants without audio-ABR continue to
use `selectAudioTrack` (the single-pick behavior); engine variants
with audio-ABR substitute `switchAudioQuality` — same
mutually-exclusive pattern as video-abr's
`switchVideoQuality`/`selectVideoTrack` alternatives.
- **Audio caps integration with
[rendition-selection-caps.md](./rendition-selection-caps.md).**
That doc explicitly flags audio caps as an open question ("Audio
renditions don't have height analogs but could have max-bitrate or
max-channels caps. Extend this feature to cover audio or carry
audio caps separately?"). When audio-ABR lands, audio caps become
load-bearing; the open question needs resolution.
## Open questions
- **Shared `bandwidthState` slot vs separate `audioBandwidthState`.**
Per the cross-cutting note above. Trade-off: shared = one network
estimate, samples from two sources biased toward one if sizes
differ greatly; separate = independent per-type estimates, but
redundant data structure with similar semantics. Decision likely
depends on whether the EWMA pattern wants extension to handle
mixed-source sampling, or whether two slots is genuinely cleaner.
- **Manual-override slot sharing.** `userAudioTrackSelection` slot
exists now (added by multi-language-audio Tier 2) and serves
language-pinning. Open: does audio-abr reuse the same slot for
bitrate-pinning too (one slot, dual role, mirrors video) — likely,
since the partial-track shape supports both. Side-effect concerns
(mid-stream flush) live in `switchAudioTrack`'s flush effect and key
off `initTrackId` vs `selectedAudioTrackId` mismatch — they fire on
language-switch and would naturally also fire on cross-codec
bitrate-switch (whose handling routes to 5.1-surround-selection's
`changeType()` path via a different `flushStrategy`).
- **EWMA accumulator design under shared bandwidth state.** Single
unified accumulator absorbing both video and audio samples, vs
per-source accumulators read with a combining function (e.g.,
`min(videoEwma, audioEwma)` similar to today's `min(fast, slow)`
pattern). The dual-accumulator pattern is precedented; the audio-
inclusive variant is a small extension.
- **Audio-side safety / upgrade margin tuning.** Video defaults
(`safetyMargin: 0.85`, `upgradeMargin: 1.15`) are tuned for video
scales. Audio bitrate ratios are different (a "next quality" audio
rendition is often 2× the bitrate of the previous, vs video's more
granular tiers); margin tuning may differ. Likely defer until
empirical data.
- **Cross-codec-tier ABR scope.** When AAC stereo bitrate variants
coexist with AC-3 5.1 in the same audio rendition group (or
selection set), can ABR cross the codec boundary, or stays within
one codec? Closes back into 5.1-surround-selection's `changeType()`
territory; this feature's scope is "within a codec" by default.
- **Audio caps inclusion in rendition-selection-caps.md.** Per the
cross-cutting note. Resolution likely punts to when audio-ABR + the
first audio cap motivation both land.
- **`picker` config for audio-ABR initial selection.** video-abr's
config includes a `picker` for overriding initial selection.
Whether audio-ABR exposes the same `picker` config (parallel
`audio.picker`?) or relies on `audio-playback`'s existing
`SelectAudioTrackConfig.picker` for initial pick before ABR
re-evaluation kicks in.
## Related features
- **[video-abr](./video-abr.md)** *(structural template)* — the
direct parallel-sibling on the video axis. Same sampling-baked-
into-loading pattern, same algorithm, same constraint-slot manual-
override. The audio doc mirrors the video doc's shape with audio-
specific design decisions (bandwidth-state sharing, multi-writer
with multi-language-audio Tier 2) layered on top.
- **[audio-playback](./audio-playback.md)** — the single-rendition
baseline this feature extends. The "What's not implemented" Audio
ABR bullet there is resolved by this doc.
- **[multi-language-audio](./multi-language-audio.md)** *(partial, sketched)*
prerequisite per Notion #8 for the rendition-group machinery, now mostly
implemented. Audio-ABR operates within a language; multi-language-audio
handles across-language switching. Shared infrastructure landed:
`userAudioTrackSelection` filter slot, `switchAudioTrack` behavior
(slot owner; precursor to `switchAudioQuality`), and pluggable
`flushStrategy` config with `nextSegmentBoundaryFlushStrategy` as the
default. Audio-abr Phase 3's `switchAudioQuality` either renames
`switchAudioTrack` + adds bandwidth logic, or coexists with it as a
sibling variant in the engine composition.
- **[5.1-surround-selection](./5.1-surround-selection.md)** —
consumes its `changeType()` primitive for cross-channel-count
switching when audio-ABR wants to cross those boundaries.
- **[buffer-management](./buffer-management.md)** — sampling lands in
this feature's audio fetch path (`setupAudioBufferActors`); same
shape as video's `bandwidthState` sample-producer relationship to
buffer-management.
- **[mse-mms-pipeline](./mse-mms-pipeline.md)** — owns
`setupAudioBufferActors` where the `createTrackedFetch` swap
happens. Same-codec, same-channel-count switching doesn't touch
the SourceBuffer lifecycle.
- **[rendition-selection-caps](./rendition-selection-caps.md)** —
the "Audio caps" open question becomes load-bearing when audio-ABR
ships. Cross-link.
- **[multi-signal-abr](./multi-signal-abr.md)** — non-bandwidth
signals feeding ABR; extends both video and audio via shared signal-
monitor infrastructure with per-type bias calculation.
- **`[bandwidth-estimation]`** *(coarse, not yet documented,
candidate)* — the dual-EWMA accumulator is a reusable primitive.
Audio-ABR is the second consumer; promoting `BandwidthState` to
its own feature doc may become worthwhile.
## Use cases that compose this feature
- **[`audio-only-mode-override`](../use-cases/audio-only-mode-override.md)**
*(coarse)* — Phase 2 constituent. When audio-abr is
implemented, the audio-only delivery variant composes it for
multi-bitrate audio support. Used as-is.
## See also
- [video-abr.md](./video-abr.md) — structural template; the
parallel-sibling doc this one mirrors on the audio axis
- [clusters.md § Track & variant registry](./clusters.md#track--variant-registry)
— cluster C description
- [clusters.md § Sampling-baked-into-loading](./clusters.md#sampling-baked-into-loading)
— cross-cluster pattern this feature instantiates; audio is the
second consumer of the pattern
- [clusters.md § Multi-writer state slots](./clusters.md#multi-writer-state-slots)
`bandwidthState` (if shared) and `selectedAudioTrackId` both
become multi-writer; characterized in cross-cutting-impact bullets
- [conventions/signals.md](../conventions/signals.md) — multi-writer
slot conventions
- [packages/spf/docs/hls-engine.md](../../../../packages/spf/docs/hls-engine.md)
— Stage 6 documents the video-ABR composition point; audio-ABR
composes at the same Stage 6 location
- [SPF Epics Working Doc](https://www.notion.so/35f97a7f89d08123a13fecab1ca1cac4)
— source material; epic #8 (Audio ABR / Multi-bitrate), depends on
#7 (Multi-language Audio Tracks)
- [Mux Video Permutations Matrix](https://www.notion.so/32c97a7f89d08191b84dd30f06685490)
— Stream Type / Audio bitrate section