Skip to content

Phone events #994 - #1012

Draft
xnorpx wants to merge 1 commit into
mainfrom
dev/phone_events
Draft

Phone events #994#1012
xnorpx wants to merge 1 commit into
mainfrom
dev/phone_events

Conversation

@xnorpx

@xnorpx xnorpx commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Not ready, need to review

This PR adds opt-in support for RFC 4733 telephone events, primarily DTMF tones.

1. Configuration and negotiation

Telephone events are disabled by default and enabled with:

Rtc::builder().enable_telephone_event(true)

When enabled, str0m adds telephone-event/8000 to audio m-lines using dynamic payload type 126. PT 126 was selected because it does not collide with the existing default codec or RTX payload types.

The generated SDP contains:

a=rtpmap:126 telephone-event/8000
a=fmtp:126 0-16

The fmtp range is parsed into a compact bitset. Each media line remembers the payload type and event codes accepted by the remote endpoint.

2. Sending a tone

Applications send a tone through the existing media writer:

rtc.writer(mid)?.write_dtmf(
    pt,
    wallclock,
    rtp_time,
    Dtmf::D5,
    Duration::from_millis(100),
)?;

Before queueing the tone, str0m checks that:

  • The payload type is configured as telephone-event.
  • The payload type was negotiated for this media line.
  • The remote endpoint advertised support for the requested event.

Invalid requests return UnknownPt or UnsupportedDtmfEvent.

3. RTP generation

write_dtmf creates a DtmfTone and places it in a FIFO sender queue.

The sender generates one four-byte telephone-event payload every 20 ms:

  • The first packet has the RTP marker bit.
  • Packets in one segment share an RTP timestamp.
  • The duration field grows with each packet.
  • The final report is sent a total of three times for robustness.
  • Audio and telephone events use the same RTP stream and sequence-number space.

Events longer than the 16-bit duration field are divided into contiguous RFC 4733 segments. Each segment gets the correct RTP timestamp and wallclock mapping.

4. Receiving a tone

Incoming telephone-event packets use a pass-through depacketizer and are delivered immediately. They are not held for missing sequence numbers because telephone events share sequence numbers with regular audio, so gaps are expected.

The payload parser handles one or more packed four-byte events using a zero-allocation iterator.

The receiver then:

  • Groups reports by RTP timestamp and event code.
  • Keeps the largest duration reported for each segment.
  • Combines contiguous long-event segments.
  • Deduplicates repeated final reports.
  • Ignores stale reordered reports from an earlier event.
  • Best-effort completes an event if all final reports were lost and a newer event begins.

5. Application event

In frame mode, raw telephone-event media is consumed internally and surfaced once as:

Event::DtmfEvent(DtmfEvent {
    mid,
    event,
    dtmf,
    volume,
    duration,
})

The event contains both the raw RFC event code and an optional decoded Dtmf value.

In RTP mode, packets continue to be exposed as raw RtpPacket events without aggregation.

6. Scheduling

DTMF packet deadlines participate in the normal Rtc::poll_output timeout calculation. When multiple media lines have queued tones, the earliest deadline is selected.

Testing

The test coverage includes:

  • SDP offer/answer negotiation and event-range enforcement.
  • Default PT collision checks.
  • Frame-to-RTP, RTP-to-frame, and RTP-to-RTP operation.
  • Marker bits, timestamps, durations, and final-report repetition.
  • Short and segmented long tones.
  • Packed events.
  • Lost and reordered final reports.
  • Multiple queued digits.
  • Interleaved audio and DTMF.
  • Invalid and unnegotiated payload types.
  • Direct API and mixed-mode operation.

@xnorpx

xnorpx commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator Author

#994

@xnorpx xnorpx changed the title Phone events Phone events #994 Jul 18, 2026
@xnorpx
xnorpx force-pushed the dev/phone_events branch 2 times, most recently from dc11006 to 96dab49 Compare July 18, 2026 16:52
@xnorpx
xnorpx force-pushed the dev/phone_events branch from 96dab49 to fd467d2 Compare July 18, 2026 17:09
fn is_marker(&mut self, _data: &[u8], _previous: Option<&[u8]>, _last: bool) -> bool {
// The marker bit for the first packet of a new event is set by the
// caller via the start-of-talkspurt mechanism.
false

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is this comment accurate? shouldn't this be true for the first packet representing an event?

Comment thread src/lib.rs
/// Emitted once per completed tone received on a negotiated
/// `telephone-event` payload type. Enable telephone events with
/// [`CodecConfig::enable_telephone_event`][crate::format::CodecConfig::enable_telephone_event].
DtmfEvent(DtmfEvent),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not clear when this fires. I assume you want to capture the start of the dtmf tone and the end. not just emit when it's complete, but I could be wrong. So maybe you emit a Dtmf event for tone start and another for tone end?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

It's 2026 and we are pondering DTMF.

I feel old :)

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.

3 participants