Skip to content

Add in-memory voice-note codec helpers - #48

Merged
senamakel merged 51 commits into
tinyhumansai:epic/7964-channel-v2from
senamakel:epic-7964-47-voice-codecs
Oct 16, 2026
Merged

senamakel merged 51 commits into
tinyhumansai:epic/7964-channel-v2from
senamakel:epic-7964-47-voice-codecs

Conversation

@senamakel

Copy link
Copy Markdown
Member

TinyVoice currently has WAV framing primitives but no shared way to turn common speech-service MP3 output into the Ogg/Opus or AAC/M4A voice-note formats used by channel clients. This adds a bounded, in-memory codec API for WAV/PCM16, Ogg/Opus, M4A/AAC-LC, and MP3 input, with explicit source-format validation, metadata probing, and normalized waveform extraction.

tinyvoice::codec::transcode(bytes, from, to) normalizes encoded voice output to mono 48 kHz, including same-format Opus/AAC conversions; WAV-to-WAV preserves input bytes. MP3 is decode-only. Pure-Rust Opus, AAC-LC, MP3, WAV, and M4A facilities avoid FFmpeg subprocesses and system codec dependencies. The compiled module exposes typed CodecProbe, CodecTranscode, and CodecWaveform operations through tinyvoice-bus contract 1.11.

Validation includes all-feature tinyvoice and module suites, TinyVoice bus tests, strict clippy, cargo-deny, rustdoc warnings, and local Windows GNU/macOS ARM cross-target checks. Codec integration fixtures prove MP3 decoding, independent Opus decoding, Symphonia AAC decoding, duration preservation, 256-bucket waveform output, and malformed/truncated-input rejection. The measured Linux x86_64 release module is 20.8 MiB total / 7.8 MiB .text; codec backend detail is in docs/specs/voice-codecs.md. Hosted CI still needs to complete the repository's Windows, macOS, and Linux jobs.

Update the vendored tinyagents submodule to the latest commit.

Auto-committed-on: dragonfly
Add a codec module that decodes WAV, Ogg Opus, and M4A AAC audio and
exposes probe, transcode, and waveform operations over the bus. The
contract version is bumped to 1.11 to cover the three new methods.

Auto-committed-on: dragonfly
The mp4 muxer wrote an empty SLConfig descriptor, which violates ISO/IEC 14496-1, so the esds descriptor is patched with the required predefined value byte and all enclosing atom lengths are adjusted. A gapless edit list and corrected track/movie durations are added, and decoded M4A samples are truncated to the container duration so AAC frame padding is not played back.

Auto-committed-on: dragonfly
The codec module now validates emitted Opus packets with a separately
implemented decoder in tests, and the M4A repair path uses checked
arithmetic throughout so malformed muxer output cannot panic. The voice
bus rejects codecs it cannot represent instead of silently mapping them.

Auto-committed-on: dragonfly
Adds in-memory probe, transcode, and waveform-peak extraction for WAV/PCM16, Ogg/Opus, and M4A/AAC-LC voice notes using pure-Rust codecs, so hosts can handle codec work without FFmpeg or system libraries. WAV decoding now rejects non-PCM16 variants instead of misreading them, and the module contract is bumped to 1.11 with three new methods.

Auto-committed-on: dragonfly
@tinysweeper

tinysweeper Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Tiny Sweeper review

Adds an in-memory voice-note codec module (`tinyvoice::codec`) with probe/transcode/waveform operations, a three-method bus contract, and module-side offloading through a bounded worker pool. The change spans the library implementation, the typed `tinyvoice-bus` contract, the `tinyvoice-module` adapter, documentation, and tests. Contract version advances to 1.11 and the declared method count increases from 53 to 56.

State: Changes requested
Priority: high
Reviewed head: 843686b54093
Updated: 2026-10-16T19:45:29Z

Review snapshot

Change surface Files Review signal Count
Production 9 Active findings 1
Tests 8 Noted findings 0
Documentation 6 Resolved findings 0
Configuration 1 Pending checks/questions 0

Completeness: Complete
Test assessment: Test coverage is assessed from changed tests and lane evidence; execution is not claimed without trusted check data.

What changed

Introduces pure-Rust WAV/PCM16, Ogg/Opus, M4A/AAC-LC, and MP3 decode support with mono 48 kHz encoded output and WAV-to-WAV passthrough; adds typed codec request/response messages and three bus methods (`CodecProbe`, `CodecTranscode`, `CodecWaveform`) bound at contract 1.11; routes module codec calls through a semaphore-bounded `spawn_blocking` pool with an 8 MiB output cap; adds a new `AudioCodec` error variant; and adds internal, integration, and module-level tests plus fixtures and specs.

Features

  • Added — `tinyvoice::codec` probe/transcode/waveform API: Exposes `probe`, `transcode(bytes, from, target)`, and `waveform` over in-memory byte slices. Inputs are limited to 32 MiB, decoded audio to five minutes and 32 million samples, sample rates restricted to 8–192 kHz, and waveform buckets to 1–4096. Declared source format is checked against payload bytes, MP3 is decode-only, encoded output is mono 48 kHz, and WAV-to-WAV preserves original bytes. (crates/tinyvoice/src/codec/mod.rs, crates/tinyvoice/src/lib.rs#mod error;)
  • Added — Typed `tinyvoice-bus` codec contract types: Adds `AudioCodec`, `CodecInput`, `CodecTranscodeRequest`, `CodecOutput`, `CodecInfo`, `WaveformRequest`, and `WaveformResult`, re-exported at the crate root, giving hosts a transport-neutral shape for codec calls. (crates/tinyvoice-bus/src/codec.rs, crates/tinyvoice-bus/src/lib.rs#pub mod transcript;)
  • Added — Bus methods `CodecProbe`, `CodecTranscode`, `CodecWaveform` at contract 1.11: Declares the three new method names, registers them in the `METHODS` list, and advances `CONTRACT_VERSION` from 1.10 to 1.11, so a host binding older than 1.11 must be updated before calling them. (crates/tinyvoice-bus/src/names/mod.rs#pub const METHODS: &[&str] = &[, crates/tinyvoice-bus/src/version/mod.rs, docs/specs/module-capture.md#names, error detail, handles, audio, paths and request arguments.)
  • Added — Bounded codec worker pool in the module adapter: Module codec methods run CPU-heavy work off the async worker via `spawn_blocking` behind a two-permit `try_acquire_owned` semaphore; excess calls fail fast with a capacity error rather than letting attacker-controlled encoded payloads queue without bound. Transcode output is rejected when it exceeds the existing 8 MiB `MAX_AUDIO_BYTES` cap. (crates/tinyvoice-module/src/service/mod.rs#fn to_json<T: serde::Serialize>(value: &T) -> TinyBusResult<String> {, crates/tinyvoice-module/src/service/mod.rs#impl VoiceService {)
  • Added — `AudioCodec` error variant: Adds `Error::AudioCodec(String)` so unsupported, corrupt, truncated, or over-limit media surfaces as a distinct error rather than being folded into an unrelated variant. (crates/tinyvoice/src/error/mod.rs#pub enum Error {)
  • Modified — Module manifest and interface members: The `VoiceService` interface now declares `codec_probe`, `codec_transcode`, and `codec_waveform`, with `library_codec`/`bus_codec` converters between the bus and library codec enums, and the interface lint attribute changed from `expect` to `allow` with `allow(unknown_lints)`. (crates/tinyvoice-module/src/service/mod.rs#tinybus_module::module_export_optional_static! {)
  • Modified — Documentation and dependency surface: README, MODULE.md, and the capture spec now describe 56 methods and contract 1.11; a new `docs/specs/voice-codecs.md` documents limits, backends, and the 20.8 MiB measured module size; `Cargo.toml` adds `opus-pure`, `oxideav-aac`, `symphonia`, `mp4`, and `bytes` as default dependencies, so codec support is compiled into default library builds. (README.md#turn. It performs no inference or tool execution. A module host uses the, MODULE.md#This package contains the native `tinyvoice` module for TinyBus module ABI v1., crates/tinyvoice/Cargo.toml#serde_json = "1", docs/specs/voice-codecs.md)
  • Internal refactor — M4A SLConfig repair and gapless edit list: `repair_m4a` fills the MP4 muxer's empty `SLConfig` predefined byte without resizing atoms, then splices an `edts`/`elst` atom and rewrites `trak`, `moov`, `tkhd`, and `mvhd` durations so AAC's 1024-sample frame padding is clipped to the original PCM duration. (crates/tinyvoice/src/codec/mod.rs)

Tests

  • unit — Internal codec tests cover M4A repair on invalid muxer output, SLConfig predefined bytes, waveform byte scaling, duration and padding limits, multi-track M4A track selection, invalid WAV rates/channels, ADTS splitting, bucket math for 32-bit limits, empty Ogg Opus, AAC stereo-to-mono normalization, and rejection of empty/silent/oversized/unsupported inputs.: Assertions target concrete behavior and error paths rather than mock interactions. The lane review could not verify these tests compile at this revision because several private helpers are not visible in the diff, but no missing symbol is demonstrated, so no defect is reported. (crates/tinyvoice/src/codec/codec_tests.rs, crates/tinyvoice/src/codec/README.md)
  • integration — `tests/codecs.rs` probes and waveform-extracts a real WAV fixture, round-trips WAV→Ogg/Opus→M4A/AAC→WAV through the public API, decodes emitted Opus packets with an independent `opus_decoder` implementation, checks gapless duration within 1 ms, exercises MP3 source transcode with envelope retention, and asserts malformed and truncated inputs are rejected.: Tests validate real encode/decode behavior including an independent Opus decoder rather than self-consistency of one implementation, and check duration/envelope drift tolerances. Fixtures are documented with provenance and a CC0 notice. (crates/tinyvoice/tests/codecs.rs, crates/tinyvoice/tests/fixtures/README.md, crates/tinyvoice/tests/fixtures/CC0-1.0.txt, crates/tinyvoice/tests/fixtures/mp3-test.bit, crates/tinyvoice/tests/fixtures/opus-interop.wav, crates/tinyvoice/tests/fixtures/voice-envelope.wav)
  • unit — Module tests exercise the codec worker pool directly: a full semaphore causes rejection, and an available worker runs on a different thread than the executor. A separate test calls `CodecProbe`, `CodecWaveform`, and `CodecTranscode` over the in-memory bus and asserts codec, channel, peak count, and Ogg magic bytes.: Asserts observable behavior (capacity error, thread offload, wire-level responses) instead of internal calls. (crates/tinyvoice-module/src/service/mod_tests.rs#async fn route_returns_a_tagged_intent() -> tinybus::Result<()> {, crates/tinyvoice-module/src/service/mod_tests.rs#use tinybus::broker::Broker;)
  • unit — The prepare test assertion for `tool_timeout` was changed from `Duration::from_millis(1000)` to `Duration::from_secs(1)`, which is the same duration expressed differently.: No behavioral change; it is a cosmetic equivalence in the existing assertion. (crates/tinyvoice-module/src/service/live/prepare_tests.rs#async fn native_configuration_preserves_all_explicit_host_fields() -> anyhow::Re)

Findings

  • high · critique · Bound the MP3 sync test before indexing the header — The `is_some_and` closure indexes `header[1]`, but `get(..2)` returns `Some` only for a slice of length exactly 2 — it is the `starts_with(b"ID3")` branch that accepts a 3-byte pre (crates/tinyvoice/src/codec/mod\.rs:210)

Before merge

  • Address Bound the MP3 sync test before indexing the header (crates/tinyvoice/src/codec/mod\.rs).

How this fits together

flowchart LR
  n0["...ration_preserves_all_explicit_host_fields<br/>changed"]:::changed
  n1["codec_error"]:::impacted
  n2["fixture"]:::impacted
  n3["map"]:::impacted
  n4["decode"]:::impacted
  n5["decode_symphonia"]:::impacted
  n6["...tion_uses_the_selected_symphonia_track_id"]:::impacted
  n0 -->|calls| n2
  n0 -->|tests| n2
  n4 -->|calls| n1
  n4 -->|calls| n3
  n4 -->|calls| n5
  n5 -->|calls| n1
  n6 -->|calls| n1
  n6 -->|tests| n1
  n6 -->|calls| n5
  n6 -->|tests| n5
  classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
  classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
  classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
  classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Loading
Agent review details

critique

  • Conclusion: Failure
  • Scope reviewed: all assigned evidence
  • Positive: The integration tests decode emitted Opus packets with a separately implemented decoder, guarding against an encoder/decoder pair agreeing on invalid packets, and check M4A via Symphonia.
  • Lane summary: Reviewed 2 files; 3 findings.
  • Evidence: crates/tinyvoice/src/codec/mod\.rs — Bound the MP3 sync test before indexing the header

security

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: Reviewed 2 files; 0 findings.

tests

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: This PR adds an in-memory voice-note codec module (`tinyvoice::codec`), a three-method bus contract, and module-side offloading with a bounded worker pool. I reviewed the new `run_codec_worker`, the M4A repair path, waveform bucketing, duration limits, and the accompanying tests; the tests assert real behaviour (independent Opus decoding, container ossification, truncation rejection, capacity rejection, thread offload) rather than mock calls, and I found no defect I can substantiate from the diff. The one thing I could not verify from here is that the new unit tests in `crates/tinyvoice/src/codec/codec_tests.rs` compile at this revision — several call private helpers (`repair_m4a`, `stereo_aac_muxed_fixture`, `probe`, `aac_frames`, `decoder_padding`) that are not visible in the diff — but the diff does not prove a missing symbol, so I am not reporting it.

commits

  • Conclusion: Neutral
  • Scope reviewed: all assigned evidence
  • Lane summary: Nothing sensitive found in what this pull request commits.

description

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: This revision adds the codec implementation (`crates/tinyvoice/src/codec/mod.rs`) and its internal tests. The description's claims match the diff: the pure-Rust backends, the mono-48 kHz normalization, MP3 decode-only, WAV passthrough, the documented limits, and the M4A SLConfig repair. The previously reported prepare_tests change and the clock/version updates remain consistent. I did not settle one open question — whether `opus_pure::OpusHead::for_encoder`'s `pre_skip` and the `writer.granule()` arithmetic in `encode_opus` can produce a negative or zero final packet duration on a real fixture — because the crate is not vendored in the tree I can read, so I make no claim about it.
Evidence and run details
  • Models: ladder/vectors-oai3, deepseek-v4.1-flash
  • Spend: $0.000863
  • Tokens: 678766 input · 8829 output · 84736 cached · 849 embedding
Head State Pass summary
329008085e70 incomplete 0 active finding(s), 0 resolved finding(s) (at 2026-10-16T18:53:11Z)
505d9dfa9a0f ready for maintainer review 0 active finding(s), 0 resolved finding(s) (at 2026-10-16T19:40:22Z)
843686b54093 changes requested 1 active finding(s), 0 resolved finding(s) (at 2026-10-16T19:45:29Z)

tinysweeper 0.1.2

@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 853fef27-e177-4e0e-a4bc-0a89728c35e3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-16T19:50:17.182093Z 843686b New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Add MP3 as a supported source codec so TTS output can be probed and
transcoded into voice-note formats. CodecTranscode now takes a declared
`from` codec that is checked against the payload, and the codec module
was split into a directory with its tests.

Auto-committed-on: dragonfly
Replace the short pop fixture with a generated voice-envelope clip and a
single-packet opus interop tone so the codec tests exercise multiframe
encoding, gapless duration, and envelope retention. Add round-trip and
truncation coverage for the AAC path and assert that decoded audio keeps
the original signal envelope.

Auto-committed-on: dragonfly
Replace the manual sample conversion and window averaging with f64::from
and a fold, and let the malformed-audio test return Result so fixture
encoding failures surface as errors instead of panics.

Auto-committed-on: dragonfly
…r crates

Regenerate the lockfile after adding tinystoragedrivers-core and the
tinyinference-providers dependency, which pulls in the secrets storage
stack and its crypto crates. Also bumps the tinyinference crates to
0.4.1.

Auto-committed-on: dragonfly

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 329008085e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/tinyvoice-bus/src/names/mod.rs Outdated
Comment thread crates/tinyvoice-bus/src/version/mod.rs
Comment thread crates/tinyvoice/src/codec.rs Outdated
Comment thread crates/tinyvoice-module/src/service/mod.rs Outdated
Comment thread crates/tinyvoice/src/codec.rs Outdated
Add tests asserting that empty, silent, oversized, and unsupported-codec
inputs are rejected across probe, waveform, and transcode, and that the
ADTS splitter rejects bad or truncated frames. Also verify that an empty
Ogg Opus stream yields no playable audio.

Auto-committed-on: dragonfly
Adjust the test fixture to match the current OpusHead layout, dropping the removed version field and renaming output_gain to output_gain_q8.

Auto-committed-on: dragonfly
Replace the short-input transcode assertions with direct checks that
encoding an empty buffer fails for both Opus and AAC/M4A, so the
rejection paths for those codecs are exercised without relying on a
one-sample WAV.

Auto-committed-on: dragonfly
Add an example that exercises Opus encoding over a longer input to verify
behaviour beyond the short clips covered by existing tests.

Auto-committed-on: dragonfly

@tinysweeper tinysweeper 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.

tinysweeper found nothing blocking, but could not review everything, so this is not an approval: crates/tinyvoice-bus/src/codec.rs, crates/tinyvoice-bus/src/version/mod.rs, crates/tinyvoice/src/error/mod.rs, crates/tinyvoice/src/lib.rs.

             $0.0059 · 1,376,199 in / 93,234 out · 196,608 cached (14%) · ladder/vectors-oai3, glm-5.3-flash, deepseek-v4.1-flash · 805 embedded
critique:    $0.0014 · 582,526 in   / 12,803 out · 71,296 cached (12%)  · glm-5.3-flash, deepseek-v4.1-flash
security:    $0.0041 · 608,889 in   / 75,706 out · 113,664 cached (19%) · deepseek-v4.1-flash
tests:       $0.0001 · 94,808 in    / 1,226 out  · 6,144 cached (6%)    · deepseek-v4.1-flash
description: $0.0001 · 71,677 in    / 1,307 out  · 5,504 cached (8%)    · deepseek-v4.1-flash

@tinysweeper tinysweeper Bot added the priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect. label Oct 16, 2026
The example that transcoded the voice-envelope fixture to Ogg Opus and WAV
baselines has been deleted, as it is no longer needed.

Auto-committed-on: dragonfly
Add a test asserting that waveform bucket range computation stays correct
for sample counts near u32::MAX, where the naive 32-bit multiply would
overflow. The expected bounds are derived with u128 arithmetic so the
test pins the wide-math behaviour.

Auto-committed-on: dragonfly
The waveform bucket start and end offsets were computed in usize, which
could overflow on 32-bit targets for large sample counts. The range
calculation now goes through a shared helper that widens to u128 before
converting back, and the existing tests were reformatted to match.

Auto-committed-on: dragonfly
Codec probe, transcode, and waveform calls now run on blocking workers
behind a shared semaphore so attacker-controlled payloads cannot build an
unbounded queue. Excess calls fail fast with a capacity error instead of
piling up.

Auto-committed-on: dragonfly
Add a test that holds the only codec worker slot to confirm run_codec_worker
rejects when capacity is full, then releases it and checks the work runs on a
different thread than the executor.

Auto-committed-on: dragonfly
Extend the waveform bucket test to check the start and end indices for the
last bucket of a realistic clip length, ensuring the range covers the final
sample. The existing 32-bit limit check is kept alongside the new assertions.

Auto-committed-on: dragonfly
The rejection test now builds wav bytes from the shared f32_mono_to_wav
helper and patches the sample rate and channel fields directly, so the
invalid headers are exercised without depending on hound's writer.

Auto-committed-on: dragonfly
The test no longer checks that encoding a single sample to AAC fails, leaving it focused on the dedicated Ogg codec rejection.

Auto-committed-on: dragonfly

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 94e04b1d85

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/tinyvoice/src/codec/mod.rs Outdated
Comment thread crates/tinyvoice/src/codec/mod.rs Outdated
Add a test asserting the repaired M4A carries an SLConfig descriptor with
its required predefined byte and that the result still parses
independently, guarding against regressions in the muxer output.

Auto-committed-on: dragonfly
The M4A repair no longer inserts a byte into the SLConfig descriptor and
bumps every enclosing atom and descriptor length. The muxer already
reserves the byte, so the predefined value is now written directly into
the existing slot, which keeps all sizes valid without resizing.

Auto-committed-on: dragonfly
Add a test that transcodes exactly five minutes of audio to M4A/AAC and
asserts the probed duration is 300000 ms, confirming frame padding is
trimmed. It also checks that one sample beyond the limit is rejected.

Auto-committed-on: dragonfly
AAC decoding can emit up to one frame of samples beyond the nominal
duration, so the five minute limit now tolerates a codec-specific padding
of 1024 samples while still rejecting anything larger. The M4A path also
re-checks the truncated playable length against the limit, and a test
covers the padding boundary.

Auto-committed-on: dragonfly
Extend the codec tests to check the raw SLConfig descriptor before repair and to verify that a five minute silent AAC fixture trims real frame padding while a longer duration is rejected. The stereo fixture now exposes its muxed bytes so the descriptor can be inspected prior to repair.

Auto-committed-on: dragonfly
Reformat the assertion in the m4a SLConfig descriptor test to satisfy rustfmt line length limits. No behaviour change.

Auto-committed-on: dragonfly
…okup

Renamed MAX_DURATION_SECS to MAX_DURATION_SECONDS for clarity and
reformatted the audio track lookup closure to satisfy rustfmt. No
behaviour change.

Auto-committed-on: dragonfly
Adds a debug dump of the movie timescale, duration, and per-track timing
entries to the five-minute AAC padding test so the fixture's container
metadata can be inspected when the duration assertion fails.

Auto-committed-on: dragonfly
The duration limit comparison now scales the seconds bound by the track
timescale instead of converting the duration to nanoseconds first, avoiding
a redundant intermediate value. A debug print of parsed mp4 header details
was also dropped from the five minute m4a test.

Auto-committed-on: dragonfly
Move the M4A duration and playable sample computation out of
decode_symphonia into a dedicated m4a_playable_samples helper. The logic
is unchanged, but isolating it keeps the decode path shorter and makes
the duration limit check easier to follow.

Auto-committed-on: dragonfly
The playable sample count for M4A files previously used only the first edit
list entry, which undercounted duration when a track had multiple entries.
The duration is now the checked sum of every entry, with an error returned
on overflow.

Auto-committed-on: dragonfly
Trim the extra newline at the end of the CC0-1.0 license fixture so the
file matches the canonical text.

Auto-committed-on: dragonfly
The M4A duration check previously picked the first audio track from the MP4 container, which could disagree with the track Symphonia actually decoded when a file has multiple audio tracks. The playable-sample calculation now takes the decoded track id and looks up that exact track, and a multi-track fixture test covers the mismatch.

Auto-committed-on: dragonfly
The m4a duration test now derives the expected sample count from the
track's media header duration and timescale instead of the track
duration in microseconds, so the assertion matches the units the
decoder actually uses.

Auto-committed-on: dragonfly
The m4a duration test now wraps Symphonia probe, MP4 parse, and decode errors with descriptive messages so a failure points at the step that broke instead of surfacing a bare error.

Auto-committed-on: dragonfly
The multi-track AAC fixture now rewrites the SLConfig descriptor of every
esds atom to the predefined value before returning the encoded bytes, so
the generated stream matches what the decoder expects.

Auto-committed-on: dragonfly

@tinysweeper tinysweeper 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.

tinysweeper found nothing blocking. Approving.

             $0.0021 · 1,926,227 in / 23,421 out · 383,104 cached (20%) · ladder/vectors-oai3, deepseek-v4.1-flash · 850 embedded
critique:    $0.0012 · 1,109,999 in / 14,480 out · 170,880 cached (15%) · deepseek-v4.1-flash
security:    $0.0005 · 576,910 in   / 5,334 out  · 164,352 cached (28%) · deepseek-v4.1-flash
tests:       $0.0001 · 132,851 in   / 922 out    · 44,544 cached (34%)  · deepseek-v4.1-flash
description: $0.0001 · 72,790 in    / 822 out    · 3,328 cached (5%)    · deepseek-v4.1-flash

Add an assertion that the playable sample count for the selected
symphonia track differs from the other track's count, so the existing
duration check cannot pass by coincidence when both tracks yield the
same length.

Auto-committed-on: dragonfly
Replace the unchecked cast of AAC_FRAME_SAMPLES with a fallible
conversion so the test fixture surfaces an error instead of silently
truncating if the constant ever exceeds u32.

Auto-committed-on: dragonfly

@tinysweeper tinysweeper 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.

Requesting changes: 1 lane(s) blocking, worst finding is high.

Fix or reply to the findings below and push. The next review clears this automatically once they are gone — you should not need to dismiss anything by hand.

             $0.0009 · 678,766 in / 8,829 out · 84,736 cached (12%) · ladder/vectors-oai3, deepseek-v4.1-flash · 849 embedded
critique:    $0.0003 · 244,979 in / 3,693 out · 22,400 cached (9%)  · deepseek-v4.1-flash
security:    $0.0001 · 181,615 in / 1,395 out · 39,808 cached (22%) · deepseek-v4.1-flash
tests:       $0.0001 · 143,653 in / 1,094 out · 19,200 cached (13%) · deepseek-v4.1-flash
description: $0.0000 · 72,602 in  / 468 out   · 3,328 cached (5%)   · deepseek-v4.1-flash

Comment on lines +210 to +215
fn looks_like_mp3(bytes: &[u8]) -> bool {
bytes.starts_with(b"ID3")
|| bytes
.get(..2)
.is_some_and(|header| header[0] == 0xff && header[1] & 0xe0 == 0xe0)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

priority high critique likely

Bound the MP3 sync test before indexing the header

The is_some_and closure indexes header[1], but get(..2) returns Some only for a slice of length exactly 2 — it is the starts_with(b"ID3") branch that accepts a 3-byte prefix with no length check. A three-byte input beginning ID3 (e.g. b"ID3", or any truncated MP3 tag) therefore reaches bytes.starts_with(b"ID3") == false, then get(..2) == Some(b"ID"), and header[1] == b'D', so header[1] & 0xe0 == 0xe0 is false and the closure does not panic here — but the same shape with the bytes ID followed by a byte whose top three bits are set, e.g. b"ID\xff", takes the same path and panics with "index out of bounds: the len is 2 but the index is 1". That input is reachable from every public entry point that sniffs the container: probe(b"ID\xff"), waveform(b"ID\xff", 1), transcode(b"ID\xff", AudioCodec::Mp3, ...) and decode_as with AudioCodec::Mp3 all call looks_like_mp3 on the raw buffer. A panic in a library path also violates the repository rule "Do not unwrap(), expect(), or panic! in library code paths." Test the length explicitly instead of relying on get(..2) plus indexing.

Suggested change
fn looks_like_mp3(bytes: &[u8]) -> bool {
bytes.starts_with(b"ID3")
|| bytes
.get(..2)
.is_some_and(|header| header[0] == 0xff && header[1] & 0xe0 == 0xe0)
}
fn looks_like_mp3(bytes: &[u8]) -> bool {
bytes.starts_with(b"ID3")
|| matches!(bytes.get(..2), Some([0xff, second]) if second & 0xe0 == 0xe0)
}

[RULE] unchecked-index ·

@tinysweeper tinysweeper Bot added priority: p1 Next. Wrong behaviour a user will hit, or a security weakness behind a condition. and removed priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect. labels Oct 16, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 843686b540

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let samples = if rate == VOICE_RATE {
samples
} else {
audio::resample(&samples, rate, VOICE_RATE)?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep resampled clips within the duration limit

When a WAV is exactly five minutes at certain valid non-48 kHz rates (for example, 2,400,300 frames at 8,001 Hz), this call reaches audio::resample, whose f64 calculation rounds the target length to 14,400,001. The returned Opus/M4A is therefore one sample over the strict decoder limit and is rejected by probe, waveform, and a subsequent transcode even though transcode accepted the source; calculate the target length with integer arithmetic or cap this rounding spill before encoding.

Useful? React with 👍 / 👎.

Comment on lines +361 to +365
let duration_units = elst
.entries
.iter()
.try_fold(0u64, |total, entry| {
total.checked_add(entry.segment_duration)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Apply M4A edit-list media offsets

When an input M4A has an edit list with a leading empty edit (media_time = -1) or a nonzero media offset, m4a_playable_samples sums only segment_duration and the caller later merely truncates the decoded tail. That neither inserts the empty presentation interval nor skips the requested media prefix, so probe and waveform/transcode expose different duration/content than conforming players (for example, one second of empty edit plus one second of audio becomes only the one-second raw decode); apply each entry's media offset and rate to the PCM timeline instead of collapsing the list to a count.

Useful? React with 👍 / 👎.

@senamakel
senamakel merged commit 7a8e3ac into tinyhumansai:epic/7964-channel-v2 Oct 16, 2026
19 of 20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: p1 Next. Wrong behaviour a user will hit, or a security weakness behind a condition.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant