Skip to main content

media_pp/elements/driver/webrtc/
command.rs

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