Skip to content

feat(voip): add encoded audio pipeline and native Opus negotiation - #1050

Merged
jlucaso1 merged 11 commits into
mainfrom
feat/voip-audio-codec-architecture
Jul 17, 2026
Merged

jlucaso1 merged 11 commits into
mainfrom
feat/voip-audio-codec-architecture

Conversation

@jlucaso1

Copy link
Copy Markdown
Collaborator

Summary

  • add an encoded-audio source/sink boundary so applications can provide raw MLOW or Opus packets without linking a codec
  • split the VoIP runtime, MLOW, and libopus Cargo features while keeping voip backward compatible
  • preserve the selected audio format through signaling, RTP/SRTP, call state, and peer format checks
  • negotiate native Opus bidirectionally, with PT120/16 kHz as the production profile and PT111/48 kHz as an explicit variant
  • reduce codec hot-path allocations and add focused protocol, engine, handler, and feature-matrix coverage

Root cause

Clearing the MLOW capability selected the Android Opus encoder, so Android-to-Rust audio worked. The answer still omitted the directional voip_settings field that selects the Android decoder, leaving Rust-to-Android packets on the MLOW decode path.

Native Opus answers now send the matching capability plus a minimal uncompressed settings overlay:

  • encode.use_mlow_codec_v1=false
  • options.enable_48khz_rtp_clock=false

Codec selection and RTP clock selection remain independent.

Impact

  • WA_AUDIO_CODEC=opus now provides full-duplex native Opus
  • MLOW remains the compatibility default and keeps its pure-Rust PCM path
  • external FFmpeg/libavcodec/process integrations can inject complete raw codec packets through encoded_audio
  • deployments can omit MLOW and/or libopus when those codecs are supplied externally
  • MLOW and Opus packet scratch/history buffers are reused on hot paths

Validation

  • live Android ↔ CLI full-duplex native Opus call
  • cargo fmt --all
  • cargo clippy --all --tests
  • cargo test --workspace --exclude e2e-tests
  • cargo test -p whatsapp-rust-voip-cli --no-default-features --features voip-opus
  • cargo test -p whatsapp-rust-voip-cli --no-default-features --features voip-mlow

The local E2E crate was excluded because it requires the separate mock WebSocket server.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added selectable 1:1 VoIP audio modes (built-in MLOW and encoded Opus/MLOW), including encoded-audio routing.
    • Extended call signaling and accept behavior to carry selected audio codecs/rates; improved offer negotiation and mismatch rejection.
    • Updated RTP audio handling (dynamic payload types) and improved MLOW/DTX marker behavior.
    • Enhanced the VoIP CLI to choose codec/profile via WA_AUDIO_CODEC and WA_AUDIO_PROFILE, wired through the full media pipeline.
  • Documentation
    • Added end-to-end VoIP codec/negotiation documentation and feature-flag guidance.
  • Performance
    • Reduced allocations and improved MLOW/Opus encoding throughput via buffer reuse.

Walkthrough

VoIP features now separate runtime support from codec adapters. Audio profiles flow through signaling, call setup, RTP, and the media engine, with encoded endpoints, Opus/MLOW bridging, MLOW buffer reuse, and updated documentation.

Changes

VoIP codec runtime and encoded audio

Layer / File(s) Summary
Feature and signaling contracts
Cargo.toml, wacore/src/stanza/..., wacore/src/types/..., src/client/..., src/handlers/call.rs
VoIP runtime and adapter features were separated; signaling preserves audio selections and supports configurable rates, capabilities, and mismatch handling.
Audio formats and media engine
wacore/src/voip/..., src/voip/audio.rs, src/voip/transport.rs
Explicit audio formats, encoded channels, RTP payload selection, marker controls, encoded routing, and PCM/MLOW gating were added.
Call facade and CLI integration
src/voip/facade.rs, examples/voip-cli/*
Call builders distinguish PCM and encoded endpoints, while the CLI selects MLOW or Opus modes and bridges Opus media.
MLOW encoder reuse and validation
wacore/src/voip/mlow/*, wacore/benches/*, wacore/examples/*, README.md, agent_docs/voip_audio_codecs.md
MLOW analysis and output buffers are reused; profiling, feature wiring, examples, README text, and codec documentation were updated.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant VoipFacade
  participant Signaling
  participant CallEngine
  participant RTP
  CLI->>VoipFacade: select codec and audio endpoints
  VoipFacade->>Signaling: build offer or accept with audio profile
  Signaling->>VoipFacade: return negotiated rates and capability
  VoipFacade->>CallEngine: start with AudioConfig
  CallEngine->>RTP: encode or forward audio payload
  RTP->>CallEngine: receive negotiated audio
  CallEngine->>CLI: deliver PCM or EncodedAudioFrame
Loading

Possibly related PRs

Suggested labels: api-design, breaking-change

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: encoded audio pipeline plus native Opus negotiation.
Description check ✅ Passed The description is detailed and directly matches the encoded audio, feature split, and Opus negotiation changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/voip-audio-codec-architecture

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.

@greptile-apps

greptile-apps Bot commented Jul 16, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds an encoded-audio source/sink boundary so applications can inject raw MLOW or Opus packets without going through a built-in codec, splits the voip Cargo feature into voip-runtime, voip-mlow, and voip-libopus, and adds full-duplex native Opus negotiation with Android peers (PT120/16 kHz as the production profile, PT111/48 kHz as an explicit variant).

  • AudioFormat / AudioConfig / AudioIo types (wacore/src/voip/audio.rs, new file, 571 lines) encapsulate all codec, clock, and I/O-mode decisions that were previously scattered across the engine, session, and RTP stream; the engine, facade, driver, and handler all carry this structure through the full call lifecycle now.
  • MLOW offer shape changes: outgoing offers now advertise only the single selected signaling rate instead of the legacy two-rate (8000/16000) form; DEFAULT_AUDIO_RATES is exported as an escape hatch but is unused in production paths.
  • Inbound PreAccept/Accept audio-format mismatch is now detected and terminates the call, emitting CallEvent::AudioFormatMismatch; dismiss_outgoing_siblings is a no-op for PreAccept, so sibling callee devices continue ringing until timeout when a mismatch is caught at the PreAccept stage.

Confidence Score: 5/5

Safe to merge; the refactor is internally consistent and all new paths are guarded by feature flags and explicit AudioConfig dispatch.

The new AudioFormat/AudioConfig/AudioIo types thread cleanly through engine, session, driver, handler, and facade with correct feature-gating. Hot-path allocations (MLOW encoder scratch, Opus decoder PCM buffer) are eliminated as intended. The encoded-audio driver arm mirrors the existing mic-arm closed-channel pattern exactly. The two findings — sibling dismiss incomplete on PreAccept mismatch, and the single-rate offer — are quality-of-life improvements rather than present data-loss or security defects.

src/handlers/call.rs — the PreAccept audio-mismatch branch does not dismiss sibling callee devices; wacore/src/stanza/call.rs — the removed legacy rate=8000 audio child in default MLOW offers deserves a doc note for downstream callers.

Important Files Changed

Filename Overview
wacore/src/voip/audio.rs New 571-line file defining AudioFormat, AudioConfig, AudioIo, AudioCodec, AudioRtpProfile, EncodedAudioFrame, and the MLOW↔Opus packet transcoding helpers. Well-tested with 12 unit tests covering round-trip, TOC rewrite, DTX mapping, and edge cases.
wacore/src/voip/engine.rs Major refactor replacing the monolithic MLOW-or-ForeignAudio dispatch with AudioConfig-driven routing. MLOW PCM state moved into PcmAudioState (feature-gated), encoded I/O path added. The audio_tx_invalid_streak counter resets on valid payloads, addressing the previous one-shot-flag concern.
wacore/src/voip/rtp.rs RTP_PAYLOAD_TYPE_OPUS renamed to 111 (RFC 7587) and RTP_PAYLOAD_TYPE_WHATSAPP_AUDIO/MLOW added at 120. New set_payload_type and set_mlow_profile methods on RtpStream work correctly; speech_started is now reset on DTX.
src/voip/facade.rs AcceptCall/OutgoingCall builders now carry AudioEndpoints enum instead of separate source/sink arcs. PCM and encoded channels are wired independently in attach_engine; inactive channels close immediately so the driver loop retires their select arms without busy-spinning.
src/handlers/call.rs Adds AudioFormatMismatch detection for inbound PreAccept/Accept stanzas. On mismatch, dismiss_outgoing_siblings is a no-op for PreAccept actions; sibling callee devices continue ringing until timeout when mismatch is caught at the PreAccept stage.
wacore/src/voip/driver.rs encoded_audio_in/out channels added to CallChannels; the select loop handles encoded_audio_fut with the same closed-channel guard pattern as the mic arm. Outputs are drained at the top of the next drive iteration, consistent with existing handling.
wacore/src/stanza/call.rs PreAccept/Accept parsers now extract children into CallAction variants. build_offer now takes audio_rates slice. CAPABILITY_STANDARD_OPUS_* constants are computed at compile time via without_mlow_capability const fn.
wacore/src/voip/mlow/encode.rs encode_into added to MlowEncoder reusing clean and range fields; hot-path allocations eliminated. encode_smpl_frame_into is now private; encode is the public wrapper that allocates its own Vec.
src/voip/audio.rs WaOpusDecoder now has pcm_scratch and packet_scratch fields; decode and decode_mlow_escape reuse them, eliminating per-frame allocation. WaOpusEncoder gains new_mlow_escape constructor for the CELT escape path.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant App as Application
    participant Facade as VoIP Facade
    participant Engine as CallEngine (sans-IO)
    participant Driver as Driver Loop
    participant Peer as Android Peer

    App->>Facade: OutgoingCall.encoded_audio(OPUS_16KHZ_60MS, src, sink)
    Facade->>Peer: "offer capability=STANDARD_OPUS, audio rate=16000"
    Peer-->>Facade: "preaccept audio rate=16000"
    Note over Facade: AudioFormatMismatch check passes
    Peer-->>Facade: "accept audio rate=16000, voip_settings"
    Facade->>Engine: "CallConfig audio=AudioConfig::encoded(OPUS_16KHZ_60MS)"
    Engine->>Engine: "set PT=120, mlow_profile=false"

    loop per 60ms frame
        App->>Driver: EncodedAudio bytes via encoded_audio_in
        Driver->>Engine: Input::EncodedAudio(payload)
        Engine->>Engine: accepts_encoded_payload true
        Engine->>Engine: pipe.protect_audio
        Engine-->>Driver: Output::Transmit(srtp_packet)
        Driver->>Peer: "UDP SRTP PT=120"
    end

    loop inbound
        Peer->>Driver: "UDP SRTP PT=120"
        Driver->>Engine: Input::RelayPacket
        Engine->>Engine: accepts_rtp_payload_type(120) true
        Engine->>Engine: pipe.unprotect_audio
        Engine->>Engine: inbound_codec AudioCodec::Opus
        Engine-->>Driver: Output::EncodedAudio(EncodedAudioFrame)
        Driver->>App: encoded_audio_out channel
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant App as Application
    participant Facade as VoIP Facade
    participant Engine as CallEngine (sans-IO)
    participant Driver as Driver Loop
    participant Peer as Android Peer

    App->>Facade: OutgoingCall.encoded_audio(OPUS_16KHZ_60MS, src, sink)
    Facade->>Peer: "offer capability=STANDARD_OPUS, audio rate=16000"
    Peer-->>Facade: "preaccept audio rate=16000"
    Note over Facade: AudioFormatMismatch check passes
    Peer-->>Facade: "accept audio rate=16000, voip_settings"
    Facade->>Engine: "CallConfig audio=AudioConfig::encoded(OPUS_16KHZ_60MS)"
    Engine->>Engine: "set PT=120, mlow_profile=false"

    loop per 60ms frame
        App->>Driver: EncodedAudio bytes via encoded_audio_in
        Driver->>Engine: Input::EncodedAudio(payload)
        Engine->>Engine: accepts_encoded_payload true
        Engine->>Engine: pipe.protect_audio
        Engine-->>Driver: Output::Transmit(srtp_packet)
        Driver->>Peer: "UDP SRTP PT=120"
    end

    loop inbound
        Peer->>Driver: "UDP SRTP PT=120"
        Driver->>Engine: Input::RelayPacket
        Engine->>Engine: accepts_rtp_payload_type(120) true
        Engine->>Engine: pipe.unprotect_audio
        Engine->>Engine: inbound_codec AudioCodec::Opus
        Engine-->>Driver: Output::EncodedAudio(EncodedAudioFrame)
        Driver->>App: encoded_audio_out channel
    end
Loading

Reviews (3): Last reviewed commit: "fix(voip): report fallback decoder failu..." | Re-trigger Greptile

Comment thread src/voip/facade.rs

@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: 5

Caution

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

⚠️ Outside diff range comments (2)
src/client/voip.rs (1)

105-115: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document every feature profile that exposes these builders.

accept and call now compile under voip-runtime, which is enabled by voip-encoded, voip-mlow, and voip-libopus, not only the compatibility voip aggregate. The current rustdoc misdirects minimal-feature consumers.

Proposed documentation fix
-/// accept) is the consumer's concern; this drives only media. Requires the `voip` feature.
+/// accept) is the consumer's concern; this drives only media. Requires a VoIP profile:
+/// `voip`, `voip-encoded`, `voip-mlow`, or `voip-libopus`.

-/// handle is dormant until then. Requires the `voip` feature.
+/// handle is dormant until then. Requires a VoIP profile:
+/// `voip`, `voip-encoded`, `voip-mlow`, or `voip-libopus`.
🤖 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/client/voip.rs` around lines 105 - 115, The rustdoc for the `accept` and
outgoing-call builder methods in the client API must list every feature profile
that exposes them. Update the documentation around `accept` and `call` to
mention `voip-runtime`, `voip-encoded`, `voip-mlow`, and `voip-libopus`, rather
than directing users only to the aggregate `voip` feature.
src/voip/facade.rs (1)

1136-1219: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

set_muted() silently does nothing on encoded-audio calls.

The AudioEndpoints::Encoded branch never wires muted into anything — no MuteFeed, nothing reading the flag. Mute is exclusively a PCM-adapter trick (zeroing frames to trigger Opus DTX), which is fine as an implementation choice, but CallHandle::set_muted is the one public API for both audio modes and its doc comment doesn't say a word about this. A consumer building on encoded_audio() and calling set_muted(true) will just... keep transmitting. If we're shipping a feature, it needs to actually do what it says, or at minimum tell people it won't.

📝 Proposed doc fix
     /// Mute or unmute the local microphone. While muted the engine sends DTX comfort-noise (the
     /// stream stays fed); it does not gap, so the peer doesn't re-negotiate the transport.
+    ///
+    /// Only affects PCM/MLOW audio (`.audio(...)`). For `.encoded_audio(...)` calls this has no
+    /// effect — muting an externally-encoded stream is the caller's responsibility.
     pub fn set_muted(&self, muted: bool) {
🤖 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/voip/facade.rs` around lines 1136 - 1219, Update the documentation for
the public CallHandle::set_muted method to explicitly state that muting affects
PCM audio calls only and has no effect for encoded-audio calls. Leave the
AudioEndpoints::Encoded behavior unchanged.
🤖 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 `@examples/voip-cli/src/main.rs`:
- Line 860: Replace the Tokio tasks that perform codec processing at the
indicated spawn sites with tokio::task::spawn_blocking workers, covering both
encoding and decoding operations. Keep the surrounding async call flow and
result handling intact while ensuring libopus and MLOW CPU work never runs
directly on Tokio worker threads.
- Around line 868-871: Update the Opus encoding error branch in the audio
processing loop so a single encode failure does not close the encoded source
while leaving the call connected with dead outbound audio. In the Err(error)
handling near the Opus encode operation, drop the failed frame and continue
processing, or explicitly propagate a fatal error that terminates the call; do
not break only the source loop.

In `@src/handlers/call.rs`:
- Around line 97-140: Ensure the audio-format mismatch branch performs the
existing dismiss_outgoing_siblings cleanup before returning. Reuse or move the
sibling-dismiss path from the surrounding call handling so incompatible calls
dismiss other outgoing sibling devices before termination and the early return,
without changing behavior for compatible calls.

In `@src/voip/audio.rs`:
- Around line 150-190: Update WaOpusDecoder to reuse an internal PCM output
buffer instead of allocating a new Vec<i16> for each packet. Add a decoder
method following the decode_into pattern that writes into the reusable buffer
and returns the valid decoded sample count or view, and use it from both decode
and decode_mlow_escape while preserving existing error handling and output
behavior.

In `@wacore/src/voip/mlow/analysis.rs`:
- Around line 204-205: Eliminate per-frame heap allocations in the hot path by
moving hp, x, xn, and hp_full into SmplEncoderState as reusable buffers. Update
the processing logic around smpl_filt_arma2 to resize or clear and refill these
persistent buffers with copy_from_slice or extend_from_slice, preserving the
existing contents and lengths required by each computation.

---

Outside diff comments:
In `@src/client/voip.rs`:
- Around line 105-115: The rustdoc for the `accept` and outgoing-call builder
methods in the client API must list every feature profile that exposes them.
Update the documentation around `accept` and `call` to mention `voip-runtime`,
`voip-encoded`, `voip-mlow`, and `voip-libopus`, rather than directing users
only to the aggregate `voip` feature.

In `@src/voip/facade.rs`:
- Around line 1136-1219: Update the documentation for the public
CallHandle::set_muted method to explicitly state that muting affects PCM audio
calls only and has no effect for encoded-audio calls. Leave the
AudioEndpoints::Encoded behavior unchanged.
🪄 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: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a356b803-1a2e-43c4-8557-8f00dfec114a

📥 Commits

Reviewing files that changed from the base of the PR and between a395daa and 23713bd.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (35)
  • Cargo.toml
  • README.md
  • agent_docs/voip_audio_codecs.md
  • examples/voip-cli/Cargo.toml
  • examples/voip-cli/src/main.rs
  • src/client.rs
  • src/client/lifecycle.rs
  • src/client/sessions.rs
  • src/client/voip.rs
  • src/handlers/call.rs
  • src/lib.rs
  • src/send/tctoken_lifecycle.rs
  • src/voip/audio.rs
  • src/voip/facade.rs
  • src/voip/mod.rs
  • src/voip/transport.rs
  • wacore/Cargo.toml
  • wacore/benches/voip_benchmark.rs
  • wacore/examples/voip_profile.rs
  • wacore/src/stanza/call.rs
  • wacore/src/types/call.rs
  • wacore/src/voip/audio.rs
  • wacore/src/voip/driver.rs
  • wacore/src/voip/engine.rs
  • wacore/src/voip/mlow/analysis.rs
  • wacore/src/voip/mlow/encode.rs
  • wacore/src/voip/mlow/mod.rs
  • wacore/src/voip/mlow/quality_tests.rs
  • wacore/src/voip/mlow/rangecoder.rs
  • wacore/src/voip/mlow/smpl_pitch_enc.rs
  • wacore/src/voip/mlow/toc.rs
  • wacore/src/voip/mod.rs
  • wacore/src/voip/registry.rs
  • wacore/src/voip/rtp.rs
  • wacore/src/voip/session.rs

Comment thread examples/voip-cli/src/main.rs Outdated
Comment thread examples/voip-cli/src/main.rs
Comment thread src/handlers/call.rs
Comment thread src/voip/audio.rs
Comment thread wacore/src/voip/mlow/analysis.rs Outdated
@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

📦 Binary size report

Metric main PR Δ
bin size (stripped) 10.93 MiB 10.93 MiB +1.56 KiB (+0.01%) 🔺
bin .text 8.91 MiB 8.92 MiB +1.50 KiB (+0.02%) 🔺
bin allocated (text+data+bss) 10.93 MiB 10.93 MiB +4.01 KiB (+0.04%) 🔺
llvm-lines wacore 514,696 516,227 +1,531 (+0.30%) 🔺
llvm-lines wacore copies 17,572 17,625 +53 (+0.30%) 🔺
llvm-lines whatsapp-rust lib 777,662 777,666 +4 (+0.00%) 🔺
llvm-lines whatsapp-rust lib copies 25,303 25,303 0
deps crates (Cargo.lock) 472 472 0
.text per crate
Crate main PR Δ
.text whatsapp_rust 1.69 MiB 1.69 MiB +948 B (+0.05%) 🔺
.text wacore 537.22 KiB 537.97 KiB +769 B (+0.14%) 🔺
.text wacore_binary 148.45 KiB 148.45 KiB 0
.text wacore_libsignal 203.49 KiB 203.49 KiB 0
.text wacore_appstate 158.33 KiB 158.33 KiB 0
.text wacore_noise 26.03 KiB 26.03 KiB 0
.text waproto 1.60 MiB 1.60 MiB 0
.text whatsapp_rust_sqlite_storage 513.08 KiB 513.08 KiB 0
.text whatsapp_rust_tokio_transport 43.79 KiB 43.79 KiB 0
.text whatsapp_rust_ureq_http_client 10.47 KiB 10.47 KiB 0
.text std 1.02 MiB 1.02 MiB -219 B (-0.02%) 🔽
.text other deps 2.95 MiB 2.95 MiB 0

Baseline: a395daac3 (latest main run) · Head: d9da12c6e · Graphs

@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: 2

🤖 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 `@examples/voip-cli/src/main.rs`:
- Around line 1264-1268: Move the fallback Opus decoding in the tokio::spawn
event handler out of the async worker thread by executing
decoder.decode_mlow_escape and the resulting speaker.try_send through
tokio::task::spawn_blocking. Preserve the existing fallback_opus handling and
PCM delivery behavior while ensuring the async event loop only schedules the
blocking task.

In `@wacore/src/voip/rtp.rs`:
- Around line 109-113: Update is_mlow_dtx_payload to replace the two-part
bitwise condition with the equivalent direct check that the masked byte value
equals 0x80, while preserving the existing first-byte handling.
🪄 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: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: dd41daea-3d40-4c94-a6f1-d774d778d31b

📥 Commits

Reviewing files that changed from the base of the PR and between 23713bd and dd834b3.

📒 Files selected for processing (9)
  • examples/voip-cli/src/main.rs
  • src/client/voip.rs
  • src/handlers/call.rs
  • src/voip/audio.rs
  • src/voip/facade.rs
  • wacore/src/voip/engine.rs
  • wacore/src/voip/mlow/analysis.rs
  • wacore/src/voip/rtp.rs
  • wacore/src/voip/session.rs

Comment thread examples/voip-cli/src/main.rs Outdated
Comment thread wacore/src/voip/rtp.rs

@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

🤖 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 `@examples/voip-cli/src/main.rs`:
- Around line 924-931: Update the fallback decoder flow around
WaOpusDecoder::new and decode_mlow_escape to log initialization failures instead
of silently returning via .ok()?, and log each decoding failure instead of
dropping the payload without feedback. Preserve the existing successful decode
and speaker.try_send behavior while using the surrounding logging facility for
actionable error details.
🪄 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: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 69a28e4e-b83f-4652-8347-7edd56397f9c

📥 Commits

Reviewing files that changed from the base of the PR and between dd834b3 and 9136f7c.

📒 Files selected for processing (2)
  • examples/voip-cli/src/main.rs
  • wacore/src/voip/rtp.rs

Comment thread examples/voip-cli/src/main.rs Outdated

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

Caution

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

⚠️ Outside diff range comments (1)
examples/voip-cli/src/main.rs (1)

84-104: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fix the missing None match arm to prevent build failures.

Look, we're building WhatsApp here. If someone compiles this CLI with both voip-mlow and voip-opus features disabled, the build is going to completely crash with a non-exhaustive patterns error because the None pattern isn't covered. We need our infrastructure to be bulletproof under all configurations. Add a fallback for None so the build doesn't break.

🛠️ Proposed fix to cover all configurations
             Some(other) => bail!(
                 "WA_AUDIO_CODEC={other:?} is unavailable in this build; compile the matching \
                  voip-mlow/voip-opus feature"
             ),
+            #[cfg(all(not(feature = "voip-mlow"), not(feature = "voip-opus")))]
+            None => bail!("No audio codecs enabled in this build; compile the matching voip-mlow/voip-opus feature"),
         }
     }
🤖 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 `@examples/voip-cli/src/main.rs` around lines 84 - 104, Update the from_env
match to cover None when both voip-mlow and voip-opus features are disabled,
preventing a non-exhaustive-patterns build failure. Add a fallback None arm that
returns an appropriate unavailable-codec error, while preserving the existing
feature-specific defaults and explicit codec handling.
🤖 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.

Outside diff comments:
In `@examples/voip-cli/src/main.rs`:
- Around line 84-104: Update the from_env match to cover None when both
voip-mlow and voip-opus features are disabled, preventing a
non-exhaustive-patterns build failure. Add a fallback None arm that returns an
appropriate unavailable-codec error, while preserving the existing
feature-specific defaults and explicit codec handling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ca5afafb-4afa-41b6-9fa8-be2acaf1f34d

📥 Commits

Reviewing files that changed from the base of the PR and between 9136f7c and 70c2a0b.

📒 Files selected for processing (1)
  • examples/voip-cli/src/main.rs

@jlucaso1
jlucaso1 merged commit 4d9e8ed into main Jul 17, 2026
18 checks passed
@jlucaso1
jlucaso1 deleted the feat/voip-audio-codec-architecture branch July 17, 2026 01:00
jlucaso1 added a commit to oxidezap/whatsapp-rust-docs that referenced this pull request Jul 17, 2026
Reflects the whatsapp-rust feature split (voip-runtime/voip-encoded/
voip-mlow/voip-libopus, with voip as a compatibility alias), the new
encoded_audio() builder for bring-your-own-codec audio, AudioFormat
profiles, and native Opus negotiation from oxidezap/whatsapp-rust#1050.

@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: 70c2a0b989

ℹ️ 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 src/voip/facade.rs
Comment on lines +146 to +149
&& !audio.iter().any(|codec| {
codec.enc.eq_ignore_ascii_case("opus")
&& codec.rate == audio_config.format.signaling_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 Validate the negotiated Opus profile before accepting

When an incoming offer comes from a MLOW-only peer, this rate check still passes for .encoded_audio(AudioFormat::OPUS_16KHZ_60MS, ...) because both MLOW and native Opus advertise <audio enc="opus" rate="16000">. The engine is then configured for StandardOpus/PT120 while the caller expects MLOW, so both directions decode the wrong payload type/profile; the accept path needs to validate the capability/voip_settings profile, not just the signaling rate.

Useful? React with 👍 / 👎.

Comment thread src/handlers/call.rs
Comment on lines +105 to +108
&& !audio.iter().any(|codec| {
codec.enc.eq_ignore_ascii_case("opus")
&& codec.rate == expected.signaling_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 Reject peer selections that match only by rate

For an outgoing native-Opus call, a peer that ignores or cannot honor the standard-Opus capability can still answer with <audio enc="opus" rate="16000">, which satisfies this check even though it selected the MLOW RTP profile. The session then keeps expected as StandardOpus and the media engine sends/decodes Opus while the peer sends/expects MLOW, so the mismatch is never reported or terminated.

Useful? React with 👍 / 👎.

Comment thread src/voip/facade.rs
Comment on lines +520 to +522
fn offer_capability(video: bool, audio: AudioFormat) -> &'static [u8] {
let standard_opus = matches!(audio.rtp_profile, AudioRtpProfile::StandardOpus);
match (video, standard_opus) {

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 Signal PT111/48 kHz when offering RFC7587 Opus

OPUS_16KHZ_60MS and the OPUS_RFC7587_* formats all have rtp_profile == StandardOpus, so this maps them to the same offer capability. If a caller selects an RFC7587 format, place_call still sends no enable_48khz_rtp_clock/PT111 signal while the engine is configured to send and accept PT111 with a 48 kHz RTP clock, leaving the peer on the default PT120 profile and causing media to be dropped or undecodable.

Useful? React with 👍 / 👎.

Comment thread src/voip/facade.rs
{
self.source = Some(Arc::new(source));
self.sink = Some(Arc::new(sink));
self.audio = Some(AudioEndpoints::Pcm {

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 Gate PCM audio when MLOW is not compiled

In voip-encoded or voip-libopus builds without voip-mlow, this .audio() path is still available and stores a PCM/MLOW endpoint. For outgoing calls, start() proceeds to send the offer and ring the peer before CallEngine::new later returns MlowUnavailable during relay attachment, so reject or cfg-gate this path before signaling when the MLOW codec is not compiled.

Useful? React with 👍 / 👎.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant