Skip to main content

media_pp/elements/driver/webrtc/
command.rs

1use std::time::Duration;
2
3use crossbeam_channel::Sender;
4use str0m::{
5    RtcError,
6    change::{SdpAnswer, SdpOffer},
7    format::Codec,
8    media::{Direction, MediaKind, Mid},
9};
10use thiserror::Error as ThisError;
11
12use crate::buffer::MediaBuffer;
13
14/// Identifies one outbound track before/after negotiation. str0m's own
15/// [`Mid`] doesn't exist until the SDP exchange that creates it completes,
16/// so this is a stable handle usable from the moment
17/// [`super::track::WebRtcHandle::add_track`] returns.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub struct TrackId(pub(super) u64);
20
21/// Errors specific to `WebRtcPeer`, its handle, and its track endpoints.
22/// Converts into the crate-wide `Error` via `?` (see [`crate::error::Error`]).
23#[derive(Debug, ThisError)]
24pub enum WebRtcError {
25    /// str0m rejected signaling, RTP, or connection state.
26    #[error("str0m error: {0}")]
27    Str0m(#[from] RtcError),
28    /// Sending or receiving a network datagram failed.
29
30    #[error("network error: {0}")]
31    Io(#[from] std::io::Error),
32    /// An outbound track received a buffer other than an encoded packet.
33
34    #[error(
35        "WebRtcTrackSink only accepts already-encoded Packet buffers \
36         (an encoder's output), got a {0}"
37    )]
38    UnsupportedBuffer(&'static str),
39    /// An outbound encoded packet has no presentation timestamp.
40
41    #[error("WebRTC packet has no PTS")]
42    MissingPacketPts,
43    /// A packet timestamp is negative and cannot be represented as RTP media time.
44
45    #[error("WebRTC packet has a negative PTS: {0}")]
46    NegativePacketPts(i64),
47    /// Applying the track's initial timestamp shift overflowed an FFmpeg
48    /// packet timestamp.
49    #[error("WebRTC packet timestamp normalization overflows: value={value}, offset={offset}")]
50    PacketTimestampNormalizationOverflow {
51        /// PTS or DTS before normalization.
52        value: i64,
53        /// Track-wide shift established by its first packet.
54        offset: i64,
55    },
56    /// A packet time base has a non-positive component.
57
58    #[error("WebRTC packet has an invalid time base: {numerator}/{denominator}")]
59    InvalidPacketTimeBase {
60        /// Invalid rational numerator.
61        numerator: i32,
62        /// Invalid rational denominator.
63        denominator: i32,
64    },
65    /// Rescaling the packet timestamp exceeds str0m's media-time range.
66
67    #[error(
68        "WebRTC packet timestamp overflows MediaTime: pts={pts}, time_base={numerator}/{denominator}"
69    )]
70    PacketTimestampOverflow {
71        /// Non-negative packet PTS that overflowed during rescaling.
72        pts: u64,
73        /// Packet time-base numerator.
74        numerator: i32,
75        /// Packet time-base denominator.
76        denominator: i32,
77    },
78    /// A sink for a track added by the remote peer was pushed before the
79    /// caller declared which codec its packets contain. SDP can negotiate
80    /// several codecs for one track, so the peer cannot infer this from the
81    /// track itself without risking a payload-type mismatch.
82    #[error("track {0:?} has no outbound codec declaration; call WebRtcTrackSink::set_codec first")]
83    OutboundCodecNotDeclared(TrackId),
84    /// The caller selected or tried to send an outbound codec that this
85    /// track's SDP negotiation did not retain. A failed
86    /// [`super::track::WebRtcTrackSink::set_codec`] leaves the previous valid
87    /// selection unchanged.
88    #[error(
89        "codec {codec:?} is not negotiated for track {track_id:?}; negotiated codecs: {negotiated:?}"
90    )]
91    OutboundCodecNotNegotiated {
92        /// Track whose outbound codec was being selected.
93        track_id: TrackId,
94        /// Codec rejected by the negotiated media section.
95        codec: Codec,
96        /// Distinct codecs currently available on that media section.
97        negotiated: Vec<Codec>,
98    },
99    /// No RTP media arrived before a caller's explicit wait deadline. The
100    /// source remains usable and the caller may retry with another timeout.
101    #[error("timed out after {timeout:?} waiting for stream info on track {track_id:?}")]
102    StreamInfoTimeout {
103        /// Track whose first actual payload has not arrived yet.
104        track_id: TrackId,
105        /// Caller-supplied maximum wait.
106        timeout: Duration,
107    },
108    /// The selected RTP payload is not media that can be represented as
109    /// FFmpeg codec parameters (for example, RTX repair payload).
110    #[error("WebRTC codec {0:?} cannot be converted to FFmpeg codec parameters")]
111    UnsupportedCodecParameters(Codec),
112    /// H.264 codec parameters were requested from an SDP-only value rather
113    /// than stream info confirmed from received SPS/PPS.
114    #[error("H.264 SPS/PPS have not been received yet")]
115    H264ParameterSetsNotReceived,
116    /// A received H.264 parameter set cannot form codec configuration.
117    #[error("received H.264 {0} is invalid")]
118    InvalidH264ParameterSet(&'static str),
119    /// Codec configuration cannot fit FFmpeg's signed extradata length.
120    #[error("codec configuration is too large: {size} bytes")]
121    CodecConfigurationTooLarge {
122        /// Configuration size that could not be represented.
123        size: usize,
124    },
125    /// FFmpeg could not allocate owned codec extradata plus its required
126    /// padding.
127    #[error("failed to allocate {size} bytes for FFmpeg codec parameters")]
128    CodecParametersAllocationFailed {
129        /// Requested allocation size including FFmpeg padding.
130        size: usize,
131    },
132    /// str0m accepts any non-zero `u32` clock rate, while FFmpeg's Rational
133    /// denominator and audio sample rate are signed 32-bit values.
134    #[error("WebRTC codec {codec:?} has a clock rate FFmpeg cannot represent: {clock_rate}")]
135    InvalidStreamClockRate {
136        /// Codec whose RTP clock rate was being converted.
137        codec: Codec,
138        /// Clock rate outside FFmpeg's signed range.
139        clock_rate: u32,
140    },
141    /// An audio payload declared a channel count that FFmpeg cannot use.
142    #[error("WebRTC codec {codec:?} has an invalid audio channel count: {channels}")]
143    InvalidStreamChannelCount {
144        /// Audio codec whose channel count was being converted.
145        codec: Codec,
146        /// Invalid channel count from the payload specification.
147        channels: u8,
148    },
149    /// The remote peer renegotiated a track's direction after this element
150    /// had already handed out its endpoints. Those describe the direction
151    /// the track attached with (see
152    /// [`super::track::TrackEndpoints`]) and cannot be re-issued, so what
153    /// the caller holds no longer matches the connection. Reported rather
154    /// than silently tolerated — the outbound half of a track that just
155    /// became receive-only is dropped by str0m without an error, and the
156    /// inbound half of one that just became send-only simply goes quiet.
157    #[error("track {mid} renegotiated its direction from {from:?} to {to:?} after attaching")]
158    DirectionChanged {
159        /// The `mid` of the track whose direction changed.
160        mid: Mid,
161        /// The direction the track attached with.
162        from: Direction,
163        /// The direction the remote peer renegotiated it to.
164        to: Direction,
165    },
166
167    /// The peer run loop has ended and accepts no further commands.
168
169    #[error("WebRtcPeer's run() has already ended")]
170    Closed,
171}
172
173/// One command sent from a [`WebRtcHandle`]/[`WebRtcTrackSink`] (any
174/// thread) into [`super::peer::WebRtcPeer::run`]'s own thread.
175pub(super) enum Command {
176    AddTrack(TrackId, MediaKind, Direction, Codec),
177    Push(TrackId, Option<Codec>, MediaBuffer),
178    SetAnswer(SdpAnswer),
179    /// A fresh offer from the *remote* peer (their own renegotiation, e.g.
180    /// them adding a track) — out of scope to originate ourselves in v1
181    /// (see the module docs), but still something this side has to be able
182    /// to *accept*, or a two-way call could only ever renegotiate from one
183    /// side. `Sender` here is a one-shot rendezvous, same idea as
184    /// [`crate::control::ControlSender::send`]'s ack channel.
185    AcceptOffer(
186        SdpOffer,
187        Sender<std::result::Result<SdpAnswer, WebRtcError>>,
188    ),
189}
190
191/// Where one outbound track is in str0m's own offer/answer dance — mirrors
192/// str0m's own `chat.rs` example's `TrackOutState`.
193pub(super) enum TrackOutState {
194    ToOpen(MediaKind, Direction),
195    Negotiating(Mid),
196    Open(Mid),
197}
198
199impl TrackOutState {
200    pub(super) fn mid(&self) -> Option<Mid> {
201        match self {
202            TrackOutState::ToOpen(..) => None,
203            TrackOutState::Negotiating(mid) | TrackOutState::Open(mid) => Some(*mid),
204        }
205    }
206}