Skip to main content

MediaSession

Struct MediaSession 

Source
pub struct MediaSession { /* private fields */ }
Expand description

A running media session.

Implementations§

Source§

impl MediaSession

Source

pub async fn start(bind: SocketAddr, config: Config) -> Result<Self, StartError>

Bind a socket and start the session in one step.

Only for callers that already know the far end — an answerer, which has the offer in hand. A caller making the offer needs MediaPort instead.

§Errors

Returns StartError::Setup before binding for invalid timing or codec construction, and StartError::Io if the media sockets cannot be bound.

Source

pub fn local_addr(&self) -> SocketAddr

The address media arrives on, for the SDP.

Source

pub fn local_ssrc(&self) -> u32

This session generation’s local RTP synchronisation source.

Source

pub fn set_rtcp_quality_hook(&self, hook: Option<RtcpQualityHook>)

Install or clear the application callback for peer RTCP quality reports.

The slot is shared by this session’s RTP/RTCP workers. Registering a callback does not enable RTCP when Config::rtcp_interval is None.

Source

pub fn rtcp_quality_hook(&self) -> Option<RtcpQualityHook>

The callback currently installed for peer RTCP quality reports.

Source

pub fn runs_ice(&self) -> bool

Whether ICE is driving this stream’s path.

The signalling layer’s question before it builds any later description: RFC 8839 §4.4 makes the ICE attributes mandatory on every subsequent offer and answer for a stream doing ICE, and §6 makes their absence mean the peer has stopped. A session carrying no agent must therefore not grow ICE attributes on a re-offer, and one carrying an agent must not lose them.

Source

pub fn ice_path(&self) -> IcePath

The candidate path ICE actually selected for RTP.

Checking is honest intermediate state: an ICE exchange was negotiated, but no nominated pair has replaced the default destination yet. A terminal diagnostic can therefore report what happened without inferring it from the policy that was requested.

Source

pub fn browser_component(&self) -> Option<BrowserComponentSnapshot>

Security and nominated-pair facts for a browser-audio component.

Source

pub async fn reconfigure(&mut self, config: Config) -> Result<bool, SetupError>

Rebuild codec and packet workers on this session’s existing sockets.

Used when a later SDP exchange changes the remote address, codec, payload type, or keys. The local RTP/RTCP addresses do not change: they are already published to the peer, and a replacement that rebound an ephemeral port would make the new description false. Mute and encoded-relay policy survive the transition. The stopped generation remains owned until all of its workers have joined; if this future is cancelled during that join, the next reconfiguration or shutdown resumes the cleanup.

Returns false without changing the session when ICE owns the destinations. Rebuilding an ICE-backed session requires the agent and its selected pair to move with the workers; a caller must refuse that renegotiation rather than silently fall back to an unchecked path.

§Errors

Returns SetupError before stopping the current workers if the new timing or codec cannot be constructed.

Source

pub async fn renegotiate_ice( &self, local: Option<(Credentials, u64)>, peer: Option<&Negotiation>, ) -> Option<Local>

Apply a later exchange’s ICE half, and read back what this side must now signal (RFC 8839 §4.4; docs/specs/ice.md §13.5).

local carries fresh credentials and a fresh tiebreaker when this exchange is a restart — §4.4.1.1.1 says a new ICE session, and the answer to one names the answerer’s own new credentials rather than the ones the finished session keyed its checks with. None is every ordinary re-offer: hold, resume, a codec change, a session refresh. peer is the description that arrived, when one has.

Returns None when this stream is not running ICE, or when the driver has already stopped. Both mean the same thing to a caller: answer without ICE attributes rather than block on a session that is ending.

Whether the peer’s half is a restart is deliberately not asked here. That is §4.4.1.1.1’s question about the peer’s two credentials, the agent has always answered it, and a second implementation of it on this side would be a second thing to keep right.

Source

pub async fn send(&self, samples: Vec<i16>) -> bool

Queue one packet’s worth of samples.

Queued rather than sent: the pacing timer decides when it goes out.

Source

pub async fn send_digit(&self, digit: Digit, duration: Duration) -> bool

Send a DTMF digit, held for duration.

The packets go through the same paced queue as audio, so the tone occupies the slots audio would have. That is deliberate: RFC 4733 events replace the audio for their duration rather than being sent alongside it, and sending both means the far end hears the keypress twice.

Source

pub async fn recv_digit(&self) -> Option<(Digit, Duration)>

Take the next DTMF digit the far end pressed, and how long it was held.

The duration comes from the RFC 4733 event itself (its duration field, converted from the negotiated clock rate to wall-clock time), not from timing our own arrival: the event carries the sender’s own clock, and measuring anything else would make the number depend on jitter rather than on how long the key was actually down.

Source

pub async fn collect_digits(&self, within: Duration, gap: Duration) -> String

Collect the digits the far end presses, for at most within, stopping once it has been quiet for gap.

Two questions, two bounds — the same split Self::record_at_least made on the audio path, and the reason this takes two durations rather than one.

within bounds the wait for the first digit, and with it the whole collection. It is a bound on failure: how long this side is prepared to wait before concluding no digits are coming, so it belongs an order of magnitude above the honest answer — a whole call’s worth, typically — rather than close to it. Nothing about it is a measurement: how long a caller takes to press the first key is a property of the caller, and how long the keypress takes to get here is a property of the machines between them.

gap is a definition of silence: how long a caller has to leave a hole for the dialling to be treated as finished. It is the only question a fixed window can answer here, and it can only be asked once a digit has arrived, because a caller who has not dialled is not a caller who has stopped dialling.

Whatever was collected is returned, including nothing. A collection cut short by within keeps the digits it already has.

§Inferring the end of the dialling (M-34)

RFC 4733 carries keypresses, not a completion signal: there is no “the caller is done” event to wait for, so the digits ended is always this side’s inference from silence, and gap is the whole of that inference. What makes it safe to draw is that the input it draws on is exact rather than approximate. A digit is delivered here once, when the first packet carrying that tone’s end bit arrives; the tone is identified by its own RTP timestamp, which is constant across every packet of the tone, so the end retransmissions RFC 4733 §2.5.1.3 asks for are absorbed rather than counted again, and “44” is told from a single long “4” by the timestamp changing. So a gap that elapses means no keypress completed in it — never that a packet was missed mid-tone.

A digit that arrives a millisecond after gap expires is not lost — up to the 32 the keypress channel holds, past which the receive loop drops rather than blocks, deliberately and by the same reasoning as every other queue here. Within that bound it stays queued and is the first digit the next Self::recv_digit or collect_digits yields. It is in the wrong collection, though, and no wall clock can fix that — which is why gap is set past any plausible scheduling delay rather than close to the spacing digits actually arrive with, and why an application that knows how many digits it wants should stop at that count with Self::recv_digit instead of waiting for a silence at all.

§Why this takes two durations (M-34)

It used to take one, spent on both questions, and that is the defect X-40 measured one layer up: a single window covering both “has it started” and “has it ended” is beaten by whichever of the two is slower on the day, and the result is not a degraded collection but an empty one, since the loop ends before its first iteration. sipx answer produced a valid recording of zero samples that way. Widening the single window would have moved that cliff rather than removed it, and left the same defect for a slower caller.

Source

pub fn codec(&self) -> Codec

The codec this session negotiated.

Source

pub fn wire_payload_type(&self) -> u8

The payload type this negotiated stream puts on the wire.

Static codecs usually return their assigned number. Dynamic codecs return the number from the negotiated description, which need not be the number this endpoint prefers in an offer.

Source

pub fn receive_payload_type(&self) -> u8

The payload type this negotiated stream accepts from the wire.

Source

pub fn dtmf_payload_type(&self) -> Option<u8>

The negotiated RTP payload type for telephone events, when enabled.

Source

pub fn rtcp_mode(&self) -> RtcpMode

Whether RTP and RTCP share one socket for this session.

Source

pub fn is_encrypted(&self) -> bool

Whether this session was constructed with SRTP key material.

The key bytes remain owned by the workers and are never exposed by this fact.

Source

pub fn clock_rate(&self) -> u32

The RTP timestamp clock negotiated for this stream.

This is intentionally the wire clock rather than an inferred playback rate. In particular, RFC 7587 fixes Opus at 48 kHz on the RTP timeline.

Source

pub fn set_relay(&self, relay: bool)

Hand received packets on still encoded, rather than decoding them to samples.

Switchable at run time because a bridge is formed between calls that are already running: the decision belongs to whoever connects them, and it is not known when the session starts.

Source

pub fn set_muted(&self, muted: bool) -> bool

Gate this session’s outbound audio, or let it through again (M-18).

Returns what the gate was set to before, so a caller that only wants to report real transitions does not have to read the flag and then write it — two steps that race each other when a call is muted from more than one place.

What muting does to the stream. Every audio frame the send loop takes off the queue is replaced by the same number of samples of silence, encoded in this session’s own codec and sent on its own payload type. The stream keeps its pacing, its sequence numbers and its timestamps; what changes is only what the far end decodes. See the module documentation for why this rather than suppressing the packets, and for the RFC 3550 §6 consequence.

Reception is not affected in any way, and neither is DTMF: an RFC 4733 event is generated by this endpoint on purpose, the way a keypad tone is on a handset, so it goes out muted or not.

Source

pub fn is_muted(&self) -> bool

Whether this session’s outbound audio is gated to silence.

Source

pub async fn recv_encoded(&self) -> Option<Encoded>

Take the next packet as it arrived, still encoded. Only ever yields under Self::set_relay.

Source

pub async fn send_encoded(&self, encoded: Encoded) -> bool

Put a payload on the wire exactly as given, bypassing the codec.

Source

pub async fn recv(&self) -> Option<Vec<i16>>

Take the next packet’s worth of received samples.

Source

pub fn capture(&self, format: PcmFormat) -> Result<PcmCapture<'_>, PcmError>

Receive this session as linear PCM at an application-chosen rate and depth.

§Errors

Returns sipx_audio::PcmError::UnsupportedSampleRate when the requested format’s rate cannot be converted safely.

Source

pub async fn record_until_idle(&self, idle: Duration) -> Vec<i16>

Take received samples until the session goes quiet for idle.

The idle timeout rather than a packet count, because the caller generally knows how long the far end will talk for and not how many packets that becomes.

idle answers one question — has the far end stopped talking — and it answers it by wall clock. A caller that already knows how many samples it expects is asking a different question and wants Self::record_at_least; see there for what goes wrong when the two are confused (X-28).

Source

pub async fn record_at_least( &self, samples: usize, within: Duration, ) -> Vec<i16>

Take received samples until samples of them have arrived, or within elapses.

The wait for a caller that knows the size of what the far end was given — a test that played a clip of its own, most often. within is a bound on failure, not a measurement: it is how long this side is prepared to wait before concluding the audio is not coming, so it should be far longer than the clip rather than close to it. Whatever arrived is returned, so a caller that got fewer samples than it asked for can say so itself.

§Why this exists (X-28)

Self::record_until_idle spends one duration on two different jobs: how long to wait for the stream to start, and how long a gap means it has ended. Neither is a property of the audio — both are properties of how fast the machine happens to be — so a caller that knows the count and uses the idle window instead is racing a fixed wall clock against a pipeline that is merely slow. On a loaded machine that pipeline is slow in exactly the two places the single window covers: the first packet is the one that waits out both jitter buffers filling, and a stalled scheduler opens mid-stream gaps wider than any packet interval. The observed result is a recording of zero samples — not a degraded one — because once the first frame lands the rest follow at the packet rate.

Widening the window would not have fixed that; it would have moved the cliff.

Source

pub fn samples_per_packet(&self) -> usize

How many samples one packet of this session’s audio carries.

Settled once when the session started, from the negotiated codec’s clock rate and the packet duration. Exposed so a caller playing a clip does not have to recompute what the session already decided — and get it wrong for a codec whose rate is not 8 kHz.

Source

pub async fn play(&self, samples: &[i16], samples_per_packet: usize) -> bool

Send a whole clip, paced by the send loop, and wait for it.

Returns whether the clip reached the end. false means it did not: the send queue closed part way — the call ended, or the session was stopped, under a playback still running — or something cut it short. The caller needs to be able to tell those apart: “the clip finished” and “the clip was cut off” are different things to anything waiting on the playback, and returning () made them indistinguishable.

This is Self::start_playback with the handle thrown away and the answer awaited through Playback::play_out, so it stays cancel-on-drop: a caller that wraps it in a timeout still stops the audio when the timeout fires. A caller that wants to stop the clip explicitly, or to have a keypress stop it, needs the handle.

Source

pub async fn play_pcm(&self, pcm: &Pcm) -> Result<bool, PcmError>

Convert and play an explicit linear-PCM buffer.

§Errors

Returns sipx_audio::PcmError before queuing anything when its rate or representation cannot be converted.

Source

pub fn start_pcm_playback( &self, pcm: &Pcm, interrupt: Interrupt, ) -> Result<Playback, PcmError>

Convert an explicit PCM buffer and start it as a controllable playback.

§Errors

Returns sipx_audio::PcmError before creating a playback when conversion is refused.

Source

pub fn start_playback( &self, samples: Vec<i16>, interrupt: Interrupt, ) -> Playback

Start a clip and hand back a handle to it, without waiting (M-17).

The clip is played at this session’s own packet size, so it is right under a codec whose clock is not 8 kHz without the caller knowing the rate.

§Clips queue; they do not replace

Starting a second playback while one is running puts it behind the one playing, and it begins when that one ends — however that one ends. This is the choice the story left open, and it is recorded in docs/designs/app-sdk.md. The reasoning in short: replacement would make “stop” an implicit side effect of “play”, so an application that wanted a prompt followed by a menu would hear only the menu, and the first clip’s cancellation would be an event nobody asked for. Replacement is still available and still says what it means — Playback::stop the one playing, then start the next.

Queueing while a clip is stopping is the case worth naming, because it is what barge-in does: stop the prompt, then immediately play something else. The clip being stopped releases the queue at once and its unsent packets are discarded rather than played, so the new clip starts within Playback::STOP_BOUND_PACKETS packets — it does not have to wait out the backlog of the clip it replaced.

A queue Playback::QUEUE_DEPTH deep. A clip that arrives at a full queue is not played and its handle resolves immediately as PlaybackEnd::Refused, rather than being silently dropped or waiting for room that a live call may never have.

Source

pub fn packets_sent(&self) -> u64

How many packets have been sent.

Source

pub fn packets_received(&self) -> u64

How many have been received.

Source

pub fn discard_counts(&self) -> MediaDiscardCounts

A synchronous snapshot of everything this session’s media path has discarded.

This includes candidate-gathering losses on the MediaPort the session consumed. Each field is monotonic, but independent workers can advance different fields while this snapshot is read, so relationships across fields are exact only while the session is quiet.

Source

pub async fn quality(&self) -> Quality

How the call is going: loss, jitter, round-trip time and an estimated score.

Readable at any point, not only at the end. The round-trip time is None until a report has come back from the far end carrying an echo of one of ours — which needs both a control port on this side and a peer that answers, so it stays None against a peer that does not do RTCP rather than being filled in with a guess.

Source

pub async fn stats(&self) -> ReportBlock

The receiver report this session would send right now (RFC 3550 §6.4.1).

Safe to poll, as often as a dashboard likes: reading does not close the reporting interval, so it cannot make the next RTCP report claim a clean interval that was in fact lossy. That is a decision and not an accident (M-33) — §6.4.1 defines fraction_lost as loss since the previous SR or RR packet, so the interval boundary is a report having been sent, and a read is not one. The RTCP loop closes it, via StreamStats::report_block; this reads with pending_report_block.

fraction_lost is therefore whatever has accumulated since the last report went out, which makes it a poor thing to display: it swings with each interval, and a poller sees whichever interval it happened to catch. Self::quality is the figure for a caller to show.

The two echo fields are zero here. They are filled in by the sending loop, which is the only place that knows how long a peer’s sender report has been held.

Source

pub async fn flush(&self, within: Duration)

Wait until everything queued has actually been sent.

Sending is paced, so play and send_digit return as soon as the packets are queued — which is long before they are on the wire. Hanging up at that point discards the tail: the last word of a clip, or the last digit of a PIN. Anything still queued after within is given up on, so this cannot hold a caller open indefinitely.

Source

pub fn stop(&self)

Stop the session and release its socket.

Source

pub async fn shutdown(&self)

Stop and join every worker owned by this session.

Handles stay in the registry until their await completes. Cancelling this future therefore leaves the current handle owned, and a later call resumes the same drain.

Source

pub fn is_stopped(&self) -> bool

Whether the session has been stopped.

Trait Implementations§

Source§

impl Debug for MediaSession

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Drop for MediaSession

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
§

impl<T> Pointable for T

§

const ALIGN: usize

The alignment of pointer.
§

type Init = T

The type for initializers.
§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,