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}