1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
//! Media (audio/video) related content.
use std::time::Instant;
pub use crate::rtp::VideoOrientation;
pub use crate::rtp::{Direction, ExtensionValues, MediaTime, Mid, Pt, Rid};
pub use crate::sdp::{Codec, FormatParams};
use crate::{Input, Rtc, RtcError};
mod event;
pub use event::*;
mod codec;
pub use codec::{CodecConfig, PayloadParams};
mod app;
pub(crate) use app::App;
mod receiver;
mod sender;
mod register;
mod mline;
pub(crate) use mline::{MLine, Source};
/// Half internal structures regarding RTP level.
pub mod rtp {
pub use crate::packet::RtpMeta;
pub use crate::packet::{CodecExtra, Vp8CodecExtra};
pub use crate::rtp::{ExtensionValues, RtpHeader, SeqNo, Ssrc};
}
/// Audio or video media. An m-line in the SDP.
///
/// Instances of [`Media`] are obtained via [`Rtc::media()`][crate::Rtc::media()]. The instance
/// only exists for m-lines that have passed the offer/answer SDP negotiation.
///
/// This is mainly a handle to send outgoing media, but also contains information about the media.
///
/// ```no_run
/// # use str0m::{Rtc, media::Mid};
///
/// let mut rtc = Rtc::new();
///
/// // add candidates, do SDP negotation
/// let mid: Mid = todo!(); // obtain mid from Event::MediaAdded.
///
/// let media = rtc.media(mid).unwrap();
/// ```
pub struct Media<'a> {
rtc: &'a mut Rtc,
index: usize,
}
impl Media<'_> {
fn m_line(&self) -> &MLine {
self.rtc.m_line(self.index)
}
fn m_line_mut(&mut self) -> &mut MLine {
self.rtc.m_line_mut(self.index)
}
/// Identifier of the m-line.
pub fn mid(&self) -> Mid {
self.m_line().mid()
}
/// The index of the line in the SDP. Once negotiated this cannot change.
pub fn index(&self) -> usize {
self.index
}
/// Current direction. This can be changed using
/// [`ChangeSet::set_direction()`][crate::ChangeSet::set_direction()] followed by an SDP negotiation.
///
/// To test whether it's possible to send media with the current direction, use
///
/// ```no_run
/// # use str0m::media::Media;
/// let media: Media = todo!(); // Get hold of media row.
/// if media.direction().is_sending() {
/// // media.write(...);
/// }
/// ```
pub fn direction(&self) -> Direction {
self.m_line().direction()
}
/// The negotiated payload parameters for this m-line.
pub fn payload_params(&self) -> &[PayloadParams] {
self.m_line().payload_params()
}
/// Match the given parameters to the configured parameters for this [`Media`].
///
/// In a server scenario, a certain codec configuration might not have the same
/// payload type (PT) for two different peers. We will have incoming data with one
/// PT and need to match that against the PT of the outgoing `Media`/m-line.
///
/// This call performs matching and if a match is found, returns the _local_ PT
/// that can be used for sending media.
pub fn match_params(&self, params: PayloadParams) -> Option<Pt> {
self.m_line().match_params(params)
}
/// Send outgoing media data via this m-line.
///
/// The `pt` is the payload type for sending and must match the codec of the media data.
/// This is typically done using [`Media::match_params()`] to compare an incoming set of
/// parameters with the configured ones in this `Media` instance. It's also possible to
/// manually match the codec using [`Media::payload_params()`].
///
/// The `now` parameter is required to know the exact time the data is enqueued. This has a
/// side effect of driving the time of the `Rtc` instance forward. I.e. passing the `now` here
/// is the same driving time forward with `handle_input()`.
///
/// `rid` is [Rtp Stream Identifier][1]. In classic RTP, individual RTP packets are identified
/// via an RTP header value `SSRC` (Synchronization Source). However it's been proposed to send
/// the RID in a header extension as an alternative way, making SSRC less important. Currently
/// this is only used in Chrome when doing Simulcast.
///
/// This operation fails if the current [`Media::direction()`] does not allow sending, the
/// PT doesn't match a negotiated codec, or the RID (`None` or a value) does not match
/// anything negotiated.
///
/// ```no_run
/// # use str0m::{Rtc};
/// # use str0m::media::{PayloadParams, MediaData, Mid};
/// # use std::time::Instant;
/// let mut rtc = Rtc::new();
///
/// // add candidates, do SDP negotation
/// let mid: Mid = todo!(); // obtain mid from Event::MediaAdded.
///
/// let media = rtc.media(mid).unwrap();
///
/// // Get incoming media data from another peer
/// let data: MediaData = todo!();
///
/// // Match incoming PT to an outgoing PT.
/// let pt = media.match_params(data.params).unwrap();
///
/// media.writer(pt, Instant::now()).write(data.network_time, data.time, &data.data).unwrap();
/// ```
///
/// [1]: https://datatracker.ietf.org/doc/html/rfc8852
pub fn writer(&mut self, pt: Pt, now: Instant) -> Writer<'_> {
let media = Media {
rtc: self.rtc,
index: self.index,
};
Writer {
media,
now,
pt,
rid: None,
ext_vals: ExtensionValues::default(),
}
}
/// Test if the kind of keyframe request is possible.
///
/// Sending a keyframe request requires the mechanic to be negotiated as a feedback mechanic
/// in the SDP offer/answer dance first.
///
/// Specifically these SDP lines would enable FIR and PLI respectively (for payload type 96).
///
/// ```text
/// a=rtcp-fb:96 ccm fir
/// a=rtcp-fb:96 nack pli
/// ```
pub fn is_request_keyframe_possible(&self, kind: KeyframeRequestKind) -> bool {
self.m_line().is_request_keyframe_possible(kind)
}
/// Request a keyframe from a remote peer sending media data.
///
/// This can fail if the kind of request (PLI or FIR), as specified by the
/// [`KeyframeRequestKind`], is not negotiated in the SDP answer/offer for
/// this m-line.
///
/// To ensure the call will not fail, use [`Media::is_request_keyframe_possible()`] to
/// check whether the feedback mechanism is enabled.
///
/// # Example
///
/// ```no_run
/// # use str0m::{Rtc};
/// # use str0m::media::{Mid, KeyframeRequestKind};
/// let mut rtc = Rtc::new();
///
/// // add candidates, do SDP negotation
/// let mid: Mid = todo!(); // obtain mid from Event::MediaAdded.
///
/// let media = rtc.media(mid).unwrap();
///
/// media.request_keyframe(None, KeyframeRequestKind::Pli).unwrap();
/// ```
pub fn request_keyframe(
&mut self,
rid: Option<Rid>,
kind: KeyframeRequestKind,
) -> Result<(), RtcError> {
self.m_line_mut().request_keyframe(rid, kind)
}
pub(crate) fn new(rtc: &mut Rtc, index: usize) -> Media {
Media { rtc, index }
}
}
/// Helper obtained by [`Media::writer()`] to send media.
///
/// This type follows a builder pattern to allow for additional data to be sent as
/// RTP extension headers.
///
/// ```no_run
/// # use str0m::{Rtc};
/// # use str0m::media::{PayloadParams, MediaData, Mid};
/// # use std::time::Instant;
/// let mut rtc = Rtc::new();
///
/// // add candidates, do SDP negotation
/// let mid: Mid = todo!(); // obtain mid from Event::MediaAdded.
///
/// let media = rtc.media(mid).unwrap();
///
/// // Get incoming media data from another peer
/// let data: MediaData = todo!();
/// let video_orientation = data.ext_vals.video_orientation.unwrap();
///
/// // Match incoming PT to an outgoing PT.
/// let pt = media.match_params(data.params).unwrap();
///
/// // Send data with video orientation added.
/// media.writer(pt, Instant::now())
/// .video_orientation(video_orientation)
/// .write(data.network_time, data.time, &data.data).unwrap();
/// ```
pub struct Writer<'a> {
media: Media<'a>,
now: Instant,
pt: Pt,
rid: Option<Rid>,
ext_vals: ExtensionValues,
}
impl<'a> Writer<'a> {
/// Add on an Rtp Stream Id. This is typically used to separate simulcast layers.
pub fn rid(mut self, rid: Rid) -> Self {
self.rid = Some(rid);
self
}
/// Add on audio level and voice activity. These values are communicated in the same
/// RTP header extension, hence it makes sense setting both at the same time.
///
/// Audio level is measured in negative decibel. 0 is max and a "normal" value might be -30.
pub fn audio_level(mut self, audio_level: i8, voice_activity: bool) -> Self {
self.ext_vals.audio_level = Some(audio_level);
self.ext_vals.voice_activity = Some(voice_activity);
self
}
/// Add video orientation. This can be used by a player on the receiver end to decide
/// whether the video requires to be rotated to show correctly.
pub fn video_orientation(mut self, o: VideoOrientation) -> Self {
self.ext_vals.video_orientation = Some(o);
self
}
/// Do the actual write of media. This consumed the builder.
///
/// Regarding `wallclock` and `rtp_time`, the wallclock is the real world time that corresponds to
/// the `MediaTime`. For an SFU, this can be hard to know, since RTP packets typically only
/// contain the media time (RTP time). In the simplest SFU setup, the wallclock could simply
/// be the arrival time of the incoming RTP data (see
/// [`MediaData::network_time`][crate::media::MediaData]). For better synchronization the SFU
/// probably needs to weigh in clock drifts and data provided via the statistics.
///
/// Notice that incorrect [`Pt`] values would surface as an error here, not when
/// doing [`Media::writer()`].
pub fn write(
mut self,
wallclock: Instant,
rtp_time: MediaTime,
data: &[u8],
) -> Result<usize, RtcError> {
let m_line = self.media.m_line_mut();
let n = m_line.write(
self.now,
self.pt,
wallclock,
rtp_time,
data,
self.rid,
self.ext_vals,
)?;
// Handle_input with a Input::Timeout can't fail, hence the expect.
self.media
.rtc
.handle_input(Input::Timeout(self.now))
.expect("handle_input with Timeout to not panic");
Ok(n)
}
}