Skip to content

Add mp4v sample entry and decode QuickTime v2 sound descriptions - #176

Closed
AdrianEddy wants to merge 2 commits into
kixelated:mainfrom
AdrianEddy:add-mp4v-and-v2-sound-description
Closed

Add mp4v sample entry and decode QuickTime v2 sound descriptions#176
AdrianEddy wants to merge 2 commits into
kixelated:mainfrom
AdrianEddy:add-mp4v-and-v2-sound-description

Conversation

@AdrianEddy

@AdrianEddy AdrianEddy commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Depends on #174 (the esds DecoderSpecificInfo payload-preservation change). This branch is stacked on it, so the diff currently includes #174's commit — it will drop out automatically once #174 merges.

Two additions that broaden real-world sample-entry coverage.

1. mp4v — MPEG-4 Part 2 Visual (ISO/IEC 14496-2)

A new VisualSampleEntry whose codec configuration rides in an MPEG-4 esds (the same descriptor container as mp4a, here with a visual object-type indication). Common in older MP4/3GP files (DivX/Xvid, MPEG-4 SP/ASP), where it previously decoded as Codec::Unknown.

Registered in the Any table and the Codec enum; optional pasp/colr/btrt/fiel children are carried like the other visual entries. The MPEG-4 Visual VOS/VOL config in the esds is preserved verbatim through DecoderSpecific::raw (from #174), so the entry round-trips faithfully.

2. Decode QuickTime version-2 sound sample descriptions

Audio::decode read the version-1/2 sound-description extensions past the base fields but discarded them. QTFF defines the lpcm fourcc only inside a version-2 description, so an lpcm entry lost its real sample rate, channel count, and formatSpecificFlags (and Lpcm::format() could only return a fourcc-implied guess).

Audio now surfaces the version-2 fields as a SoundV2 (real rate / channels / bits-per-channel / format flags), exposed on the Pcm helper and a hand-written Lpcm entry, so Lpcm::format() resolves the true storage format — float vs integer, endianness — from the flags. A version-2 entry round-trips through encode.

Both additions include tests.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The changes add QuickTime version-2 sound description support through SoundV2, including encoding, decoding, and PCM/lpcm format resolution. They also add MPEG-4 Part 2 mp4v sample-entry support, including its atom implementation, generic dispatch, Codec integration, optional child boxes, and round-trip tests.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately summarizes the two main changes: mp4v sample entry support and QuickTime v2 sound-description decoding.
Description check ✅ Passed The description is clearly related to the changeset and matches the added mp4v and SoundV2 support.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/moov/trak/mdia/minf/stbl/stsd/pcm.rs (1)

172-195: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stale docstring: lpcm format resolution now does consult version-2 flags.

The doc comment for Pcm::format() still says version-2 flags for lpcm "are not decoded here; defaults to big-endian integer, sample_size bits" (lines 181-183), but resolve_format (below, lines 218-226) now resolves the real format from SoundV2.format_flags/bits_per_channel whenever sound_v2 is Some. This doc block wasn't updated to reflect the behavior change in this PR and will mislead API consumers.

📝 Proposed doc fix
-    /// - `lpcm`: QTFF format flags are only defined for version 2 sound sample
-    ///   descriptions, which are not decoded here; defaults to big-endian
-    ///   integer, `sample_size` bits
+    /// - `lpcm`: when a version-2 `SoundV2` extension is present, the real
+    ///   storage format is resolved from its `formatSpecificFlags`; otherwise
+    ///   defaults to big-endian integer, `sample_size` bits
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/moov/trak/mdia/minf/stbl/stsd/pcm.rs` around lines 172 - 195, Update the
Pcm::format documentation to state that lpcm entries use SoundV2.format_flags
and bits_per_channel when sound_v2 is present, rather than claiming version-2
flags are not decoded and defaulting to big-endian integer. Keep the existing
fallback description for lpcm entries without usable version-2 metadata.
🧹 Nitpick comments (3)
src/moov/trak/mdia/minf/stbl/stsd/mp4v.rs (1)

34-38: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer Some(atom) over atom.into() for clarity.

Using atom.into() relies on the standard library's impl<T> From<T> for Option<T>, which obscures the fact that you are simply wrapping the parsed atom in a Some variant. Explicitly using Some(atom) is more idiomatic and easier to read.

♻️ Proposed refactor
-                Any::Esds(atom) => esds = atom.into(),
-                Any::Btrt(atom) => btrt = atom.into(),
-                Any::Colr(atom) => colr = atom.into(),
-                Any::Pasp(atom) => pasp = atom.into(),
-                Any::Fiel(atom) => fiel = atom.into(),
+                Any::Esds(atom) => esds = Some(atom),
+                Any::Btrt(atom) => btrt = Some(atom),
+                Any::Colr(atom) => colr = Some(atom),
+                Any::Pasp(atom) => pasp = Some(atom),
+                Any::Fiel(atom) => fiel = Some(atom),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/moov/trak/mdia/minf/stbl/stsd/mp4v.rs` around lines 34 - 38, Replace the
atom.into() assignments in the Any::Esds, Any::Btrt, Any::Colr, Any::Pasp, and
Any::Fiel match arms with explicit Some(atom) wrapping, preserving the existing
field assignments.
src/moov/trak/mdia/minf/stbl/stsd/audio.rs (2)

54-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing test coverage for the version-1 skip branch.

decode_with_v2 now explicitly matches QuickTime sound version 1 and skips its 16-byte extension, but no test in this file (or pcm.rs) exercises this path. test_pcm_v1_chnl in pcm.rs only covers chnl-atom versioning, not this audio sound-version-1 branch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/moov/trak/mdia/minf/stbl/stsd/audio.rs` around lines 54 - 59, Add a
focused test in the audio sample-description tests covering decode_with_v2 with
sound version 1, supplying the 16-byte extension and asserting the decoder skips
it and returns the expected result. Keep test_pcm_v1_chnl unchanged, since it
covers a different versioning path.

60-77: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

constBytesPerAudioPacket/constLPCMFramesPerAudioPacket are discarded on decode and re-derived on encode, not round-tripped.

decode_with_v2 reads but drops constBytesPerAudioPacket and constLPCMFramesPerAudioPacket (they aren't stored in SoundV2), and encode_with_v2 recomputes the former from channel_count * (bits_per_channel / 8) (line 114) and hardcodes the latter to 1 (line 115). This is safe today because the only consumer (Lpcm, uncompressed PCM) always has framesPerPacket == 1 and integer-byte-aligned samples, but Audio::decode_with_v2/encode_with_v2 are generic, publicly-exposed APIs — a future caller using them for a compressed codec's version-2 sound description, or a bit depth not divisible by 8, would silently get corrupted values on a decode→encode round trip. Worth a doc note on these methods spelling out this uncompressed-PCM assumption, so future reuse doesn't hit a silent data-fidelity bug.

Also applies to: 107-116

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/moov/trak/mdia/minf/stbl/stsd/audio.rs` around lines 60 - 77, Document
the uncompressed-PCM assumption on the public Audio::decode_with_v2 and
Audio::encode_with_v2 methods: the version-2 fields constBytesPerAudioPacket and
constLPCMFramesPerAudioPacket are discarded during decoding and reconstructed
during encoding, so decode→encode preserves values only for integer-byte-aligned
PCM with one frame per packet. Make the note explicit for future callers without
changing the current encoding behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/moov/trak/mdia/minf/stbl/stsd/mp4v.rs`:
- Around line 67-90: Update sample_esds() so the visual MPEG-4 configuration
does not use esds::DecoderSpecific, which is limited to the two-byte audio
AudioSpecificConfig format. Add or reuse raw-byte or MPEG-4 Visual-specific
decoder configuration support in the relevant ESDS types, then preserve the
complete decoder payload (including VOL data) during round trips and construct
the helper with that representation.

---

Outside diff comments:
In `@src/moov/trak/mdia/minf/stbl/stsd/pcm.rs`:
- Around line 172-195: Update the Pcm::format documentation to state that lpcm
entries use SoundV2.format_flags and bits_per_channel when sound_v2 is present,
rather than claiming version-2 flags are not decoded and defaulting to
big-endian integer. Keep the existing fallback description for lpcm entries
without usable version-2 metadata.

---

Nitpick comments:
In `@src/moov/trak/mdia/minf/stbl/stsd/audio.rs`:
- Around line 54-59: Add a focused test in the audio sample-description tests
covering decode_with_v2 with sound version 1, supplying the 16-byte extension
and asserting the decoder skips it and returns the expected result. Keep
test_pcm_v1_chnl unchanged, since it covers a different versioning path.
- Around line 60-77: Document the uncompressed-PCM assumption on the public
Audio::decode_with_v2 and Audio::encode_with_v2 methods: the version-2 fields
constBytesPerAudioPacket and constLPCMFramesPerAudioPacket are discarded during
decoding and reconstructed during encoding, so decode→encode preserves values
only for integer-byte-aligned PCM with one frame per packet. Make the note
explicit for future callers without changing the current encoding behavior.

In `@src/moov/trak/mdia/minf/stbl/stsd/mp4v.rs`:
- Around line 34-38: Replace the atom.into() assignments in the Any::Esds,
Any::Btrt, Any::Colr, Any::Pasp, and Any::Fiel match arms with explicit
Some(atom) wrapping, preserving the existing field assignments.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a29bc2e4-63df-44e9-93d6-4a3e5e66a272

📥 Commits

Reviewing files that changed from the base of the PR and between 2a82fb8 and afc45ee.

📒 Files selected for processing (5)
  • src/any.rs
  • src/moov/trak/mdia/minf/stbl/stsd/audio.rs
  • src/moov/trak/mdia/minf/stbl/stsd/mod.rs
  • src/moov/trak/mdia/minf/stbl/stsd/mp4v.rs
  • src/moov/trak/mdia/minf/stbl/stsd/pcm.rs

Comment thread src/moov/trak/mdia/minf/stbl/stsd/mp4v.rs
@AdrianEddy
AdrianEddy force-pushed the add-mp4v-and-v2-sound-description branch from afc45ee to 34f6eda Compare July 15, 2026 03:32
AdrianEddy and others added 2 commits July 15, 2026 05:53
…erate trailing bytes

Three robustness fixes for MPEG-4 (ISO/IEC 14496-1) descriptor parsing in
`esds`, all found on real-world files the strict decoder mishandled.

1. Preserve the DecoderSpecificInfo payload. `DecoderSpecific` parsed the first
   two bytes as the AAC AudioSpecificConfig (audioObjectType /
   samplingFrequencyIndex / channelConfiguration) and DISCARDED the rest — so a
   longer AAC config (e.g. a GASpecificConfig extension) or a non-AAC config
   (the MPEG-4 Visual VOS/VOL headers an `mp4v` `esds` carries) did not
   round-trip. `DecoderSpecific` now keeps the complete payload in `raw`; the
   AAC fields remain a parsed view of its prefix, and encode emits `raw`
   verbatim (or re-derives the 2-byte config from the fields when `raw` is
   empty, for a hand-constructed value). `DecoderSpecific` / `DecoderConfig` /
   `EsDescriptor` consequently lose `Copy` (the payload is a `Vec<u8>`).

2. DecoderSpecificInfo (tag 0x05) is optional. ISO/IEC 14496-1 defines it as
   "if available" — a stream whose config is derivable in-band (e.g. AAC carried
   with ADTS headers) legitimately omits it. The decoder treated it as mandatory
   (`MissingDescriptor(0x05)`) and failed the whole `esds`. `dec_specific`
   becomes an `Option`; a `None` round-trips to no tag-5 descriptor.

3. Tolerate trailing bytes within a descriptor's declared size. Descriptors are
   length-prefixed and forward-compatible: the size field is authoritative and a
   parser skips to the declared end. `Descriptor::decode` used `decode_exact`,
   which `ShortRead`s on unconsumed bytes — so a 2-byte `SLConfigDescriptor`
   failed the whole `esds` with `UnderDecode(esds)`. Each typed descriptor is now
   decoded within a `size`-bounded slice and advanced to the declared end.

All three add regression tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…escriptions

Two additions that broaden real-world sample-entry coverage. Builds on the
`esds` payload-preservation change so the `mp4v` decoder configuration
round-trips faithfully.

1. `mp4v` — MPEG-4 Part 2 Visual (ISO/IEC 14496-2) sample entry. A
   VisualSampleEntry carrying its codec configuration in an MPEG-4 `esds` (the
   same descriptor container as `mp4a`, with a visual object-type indication).
   Common in older MP4/3GP files (DivX/Xvid, MPEG-4 SP/ASP). Registered in the
   `Any` table and the `Codec` enum; optional `pasp`/`colr`/`btrt`/`fiel`
   children are carried like the other visual entries. The MPEG-4 Visual VOL
   config in the `esds` is preserved verbatim (via `DecoderSpecific::raw`).

2. QuickTime version-2 sound sample descriptions. `Audio::decode` read the
   version-1/2 sound-description extensions past the base fields but discarded
   them — so a `lpcm` entry (which QTFF defines ONLY as a version-2 description)
   lost its real sample rate, channel count, and `formatSpecificFlags`. `Audio`
   now surfaces the version-2 fields as `SoundV2`, exposed on the `Pcm` helper
   and a hand-written `Lpcm` entry, so `Lpcm::format()` resolves the true storage
   format from the flags. Round-trips through the version-2 encoding.

Both additions include tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@AdrianEddy
AdrianEddy force-pushed the add-mp4v-and-v2-sound-description branch from 34f6eda to 8b33645 Compare July 15, 2026 03:55
@bradh

bradh commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Will look at this one once #174 lands. It would help to not have two changes in the same PR though.

@AdrianEddy

Copy link
Copy Markdown
Contributor Author

Closing in favor of the maintainers' own in-flight work, which covers both halves: mp4v by #107 (approved) and the QuickTime version-2 sound descriptions by #213. Happy to review or help rebase either.

@AdrianEddy AdrianEddy closed this Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants