Skip to content

feat!: Opus SDP negotiation with pinned re-offers#49

Merged
wavekat-eason merged 1 commit into
mainfrom
feat/opus-sdp
Jul 3, 2026
Merged

feat!: Opus SDP negotiation with pinned re-offers#49
wavekat-eason merged 1 commit into
mainfrom
feat/opus-sdp

Conversation

@wavekat-eason

Copy link
Copy Markdown
Contributor

Summary

Teaches wavekat-sip's SDP layer to negotiate Opus wideband alongside G.711, with real RFC 3264 answer intersection and codec-pinned re-offers so a mid-call re-INVITE can't renegotiate under a live audio path. This is the wavekat-sip half of the WaveKat Voice Opus work (voice doc 45); it pairs with wavekat-core 0.0.13 (the opus feature) and lands second in the publish order.

The crate stays codec-agnostic — it advertises and resolves payload types and clocks; the actual encode/decode lives in wavekat-core. No new dependency.

What changes

  • build_sdp_offer advertises the full menu Opus-first (opus/48000/2 at PT 111 with useinbandfec=1), G.711 after, and telephone-event at both the 8 kHz and 48 kHz clocks.
  • Answers are a real RFC 3264 intersectionIncomingCall::accept picks Opus wherever the offer carries it (at the offer's PT), else the first G.711, echoes only that one selection, and answers 488 when nothing matches.
  • Pinned re-offers — every post-negotiation re-offer (hold/resume, consumer-built refresh bodies) pins the negotiated codec via CodecMenu::Pinned, so a re-INVITE can't swap the codec while audio is flowing.
  • parse_sdp resolves dynamic payload types into RemoteMedia::codec / opus_payload_type and records telephone-event entries at any clock (DtmfSpec); RemoteMedia::dtmf returns the clock-matched entry.
  • DtmfBurstConfig.clock_rate — burst duration ticks and pacing follow the negotiated event clock (160/20 ms at 8 kHz, 960 at 48 kHz).

BREAKING CHANGE → releases as 0.2.0

  • build_sdp / build_sdp_with are replaced by build_sdp_offer and a CodecMenu-taking build_sdp_with.
  • RemoteMedia::dtmf_payload_type is replaced by dtmf_events / dtmf(); RemoteMedia gains codec / opus_payload_type.
  • DtmfBurstConfig gains a required clock_rate field.

Test plan

  • cargo test -p wavekat-sip — 229 lib tests + integration + doctests pass. cargo fmt --all -- --check clean.
  • End-to-end loopback (tests/end_to_end_call.rs) negotiates Opus over real UDP sockets; a raw G.711-only INVITE exercises the legacy intersection path (and the 488-on-no-match path is covered).
  • docs/RFC-COVERAGE.md updated for the new negotiation/answer behavior.

Notes for review

  • Draft pending the cross-repo publish sequence: wavekat-core 0.0.13 is already published; this publishes next as 0.2.0 (breaking), then WaveKat Voice drops its local [patch.crates-io] and pins the released versions.
  • One socket/timing lib test flaked once under heavy parallel load during local runs, then passed 5× clean in isolation — CI is authoritative; flag if it recurs there.

🤖 Generated with Claude Code

Per wavekat-voice doc 45:

- build_sdp_offer advertises the full menu Opus-first (opus/48000/2 at
  PT 111 with useinbandfec=1), G.711 after, telephone-event at both the
  8 kHz and 48 kHz clocks
- answers are a real RFC 3264 intersection: IncomingCall::accept picks
  Opus wherever offered (at the offer's PT) else the first G.711, echoes
  only that selection, and answers 488 when nothing matches
- every post-negotiation re-offer (hold/resume, and consumer-built
  refresh bodies) pins the negotiated codec via CodecMenu::Pinned so a
  re-INVITE can't renegotiate the codec under a live audio path
- parse_sdp resolves dynamic payload types into RemoteMedia::codec /
  opus_payload_type and records telephone-event entries at any clock
  (DtmfSpec); RemoteMedia::dtmf picks the clock-matched entry
- DtmfBurstConfig gains clock_rate: duration ticks and pacing follow the
  negotiated event clock (160/20 ms at 8 kHz, 960 at 48 kHz)

BREAKING CHANGE: build_sdp/build_sdp_with are replaced by
build_sdp_offer and a CodecMenu-taking build_sdp_with;
RemoteMedia::dtmf_payload_type is replaced by dtmf_events/dtmf() and
RemoteMedia gains codec/opus_payload_type; DtmfBurstConfig gains a
required clock_rate field.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@wavekat-eason
wavekat-eason marked this pull request as ready for review July 3, 2026 07:32
@wavekat-eason
wavekat-eason merged commit 56dbffb into main Jul 3, 2026
1 check passed
@wavekat-eason
wavekat-eason deleted the feat/opus-sdp branch July 3, 2026 07:33
@github-actions github-actions Bot mentioned this pull request Jul 3, 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.

1 participant