Skip to content

feat(codec): Mp4Sink records a 3-to-8-channel Opus track (channel mapping family 1) #2139

Description

@tato123

What we want

Mp4Sink records an Opus track of any channel count OpusEncoder can mint — one to eight — so a surround or ambisonic rig can be recorded, not just a mono or stereo microphone.

What we do today

Mp4Sink records one- and two-channel Opus tracks and refuses three to eight by name, naming the container rather than the codec:

the opus track on <link> carries N channels, which this writer cannot describe: an Opus sample entry with more than 2 channels needs channel mapping family 1, and mp4-atom writes family 0 only. The encoder mints such a stream; recording it does not yet follow

OpusEncoder is unaffected — it mints and encodes 1–8 channels today (opus_stream_layout.rs carries libopus's vorbis_mappings table for family 1), and OpusDecoder decodes them. Only the container write is missing.

Why we're moving away from it

mp4-atom 0.15 — 0.15.0 is the latest published release as of 2026-09-03 — cannot express mapping family 1:

  • Dops::encode_body writes ChannelMappingFamily as a hardcoded 0u8 and carries no stream count, coupled count or channel mapping table.
  • Dops::decode_body returns Error::Unsupported("OPUS channel mapping") for any non-zero family, so even a correctly hand-written family-1 track would not re-parse through the same crate the container-bytes tests use.
  • Codec::Unknown(FourCC) carries only the four-character code, not a payload, so a hand-built Opus sample entry cannot be placed into a typed Stsd.

Opus-in-ISOBMFF §4.3.2 requires dOps.OutputChannelCount to match the identification header and permits exactly one Opus Specific Box per sample entry, so there is no second entry to fall back to.

Options

  1. Contribute family-1 dOps upstream to mp4-atom and take the released version — cleanest, and the crate is MIT/Apache-2.0.
  2. Fork and pin by rev — engine doctrine allows a rev-pinned git dep, but it adds a second vendored fork to carry beside tatolab-vulkanalia.
  3. Hand-write the dOps bytes and patch the ancestor box sizes — rejected on 2026-09-03 as the "hand-written box writer" the recording rung already rejected as a maintenance burden and no capability.

Option 1 is the recommendation.

Not blocking

examples/camera-audio-recorder (the rung's showcase) is mono or stereo, so nothing in the current milestone waits on this. Owner ruling 2026-09-03 during #2127: refuse by name now, track the gap.

Where it is written down

  • runtime/streamlib-media-builtins/src/mp4_track_sample_entry.rsHIGHEST_CHANNEL_COUNT_THIS_CONTAINER_WRITER_PLACES and the refusal, with the reason in its doc comment.
  • docs/plan/ARCHITECTURE.md §Media I/O — the DECIDED entry "Three to eight channels record no Opus track yet", which is where the gap now lives.
  • docs/plan/changes/archive/2026-09-03-opus-mp4-recording-rung.md — the change that recorded it, folded and archived 2026-09-03 (ship PR docs(plan): ship opus-mp4-recording-rung — §Media I/O gains the Opus pair and Mp4Sink #2146).
  • Test: a_channel_count_needing_mapping_family_one_is_refused_naming_the_container.
Original body, before the 2026-09-07 tracker reconcile

The two citations of docs/plan/changes/opus-mp4-recording-rung.md were repointed: that file
was folded into the plan and archived on 2026-09-03, so the path no longer resolves. Nothing
else changed — the ask, the refusal text, the options and the recommendation are untouched.

What we want

Mp4Sink records an Opus track of any channel count OpusEncoder can mint — one to eight — so a surround or ambisonic rig can be recorded, not just a mono or stereo microphone.

What we do today

Mp4Sink records one- and two-channel Opus tracks and refuses three to eight by name, naming the container rather than the codec:

the opus track on <link> carries N channels, which this writer cannot describe: an Opus sample entry with more than 2 channels needs channel mapping family 1, and mp4-atom writes family 0 only. The encoder mints such a stream; recording it does not yet follow

OpusEncoder is unaffected — it mints and encodes 1–8 channels today (opus_stream_layout.rs carries libopus's vorbis_mappings table for family 1), and OpusDecoder decodes them. Only the container write is missing.

Why we're moving away from it

mp4-atom 0.15 — 0.15.0 is the latest published release as of 2026-09-03 — cannot express mapping family 1:

  • Dops::encode_body writes ChannelMappingFamily as a hardcoded 0u8 and carries no stream count, coupled count or channel mapping table.
  • Dops::decode_body returns Error::Unsupported("OPUS channel mapping") for any non-zero family, so even a correctly hand-written family-1 track would not re-parse through the same crate the container-bytes tests use.
  • Codec::Unknown(FourCC) carries only the four-character code, not a payload, so a hand-built Opus sample entry cannot be placed into a typed Stsd.

Opus-in-ISOBMFF §4.3.2 requires dOps.OutputChannelCount to match the identification header and permits exactly one Opus Specific Box per sample entry, so there is no second entry to fall back to.

Options

  1. Contribute family-1 dOps upstream to mp4-atom and take the released version — cleanest, and the crate is MIT/Apache-2.0.
  2. Fork and pin by rev — engine doctrine allows a rev-pinned git dep, but it adds a second vendored fork to carry beside tatolab-vulkanalia.
  3. Hand-write the dOps bytes and patch the ancestor box sizes — rejected on 2026-09-03 as the "hand-written box writer" docs/plan/changes/opus-mp4-recording-rung.md already rejected as a maintenance burden and no capability.

Option 1 is the recommendation.

Not blocking

examples/camera-audio-recorder (the rung's showcase) is mono or stereo, so nothing in the current milestone waits on this. Owner ruling 2026-09-03 during #2127: refuse by name now, track the gap.

Where it is written down

  • runtime/streamlib-media-builtins/src/mp4_track_sample_entry.rsHIGHEST_CHANNEL_COUNT_THIS_CONTAINER_WRITER_PLACES and the refusal, with the reason in its doc comment.
  • docs/plan/changes/opus-mp4-recording-rung.md — the DECIDED bullet recording the gap.
  • Test: a_channel_count_needing_mapping_family_one_is_refused_naming_the_container.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions