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}