File
Blob: firmware/vendor/str0m/src/media/writer.rs
| 1 | use std::sync::Arc; |
| 2 | use std::time::Instant; |
| 3 | |
| 4 | use crate::RtcError; |
| 5 | use crate::format::PayloadParams; |
| 6 | use crate::rtp_::AbsCaptureTime; |
| 7 | use crate::rtp_::MidRid; |
| 8 | use crate::rtp_::VideoOrientation; |
| 9 | use crate::session::Session; |
| 10 | |
| 11 | use super::{ExtensionValues, KeyframeRequestKind, Media, MediaTime, Mid, Pt, Rid, ToPayload}; |
| 12 | |
| 13 | /// Writer of frame level data. |
| 14 | /// |
| 15 | /// Obtained via [`Rtc::writer`][crate::Rtc::writer]. |
| 16 | /// |
| 17 | /// This is the Frame Level API. For RTP level see |
| 18 | /// [`DirectApi::stream_tx`][crate::change::DirectApi::stream_tx]. |
| 19 | pub struct Writer<'a> { |
| 20 | session: &'a mut Session, |
| 21 | mid: Mid, |
| 22 | rid: Option<Rid>, |
| 23 | start_of_talkspurt: Option<bool>, |
| 24 | ext_vals: ExtensionValues, |
| 25 | } |
| 26 | |
| 27 | impl<'a> Writer<'a> { |
| 28 | /// Create a new writer object. |
| 29 | /// |
| 30 | /// The `mid` parameter is required to have a corresponding media in `self.session`. |
| 31 | pub(crate) fn new(session: &'a mut Session, mid: Mid) -> Self { |
| 32 | Writer { |
| 33 | session, |
| 34 | mid, |
| 35 | rid: None, |
| 36 | start_of_talkspurt: None, |
| 37 | ext_vals: ExtensionValues::default(), |
| 38 | } |
| 39 | } |
| 40 | |
| 41 | /// Get the configured payload parameters for the `mid` this writer is for. |
| 42 | /// |
| 43 | /// For the [`Writer::write()`] call, the `pt` must be set correctly. |
| 44 | pub fn payload_params(&self) -> impl Iterator<Item = &PayloadParams> { |
| 45 | // This unwrap is OK due to the invariant of self.mid being resolvable |
| 46 | let media = self.session.media_by_mid(self.mid).unwrap(); |
| 47 | self.session |
| 48 | .codec_config |
| 49 | .params() |
| 50 | .iter() |
| 51 | .filter(|p| media.remote_pts().contains(&p.pt)) |
| 52 | } |
| 53 | |
| 54 | /// Match the given parameters to the configured parameters for this [`Media`]. |
| 55 | /// |
| 56 | /// In a server scenario, a certain codec configuration might not have the same |
| 57 | /// payload type (PT) for two different peers. We will have incoming data with one |
| 58 | /// PT and need to match that against the PT of the outgoing [`Media`]. |
| 59 | /// |
| 60 | /// This call performs matching and if a match is found, returns the _local_ PT |
| 61 | /// that can be used for sending media. |
| 62 | pub fn match_params(&self, params: PayloadParams) -> Option<Pt> { |
| 63 | self.session |
| 64 | .codec_config |
| 65 | .match_params(params) |
| 66 | .map(|p| p.pt()) |
| 67 | } |
| 68 | |
| 69 | /// Add on an Rtp Stream Id. This is typically used to separate simulcast layers. |
| 70 | pub fn rid(mut self, rid: Rid) -> Self { |
| 71 | self.rid = Some(rid); |
| 72 | self |
| 73 | } |
| 74 | |
| 75 | /// Add on audio level and voice activity. These values are communicated in the same |
| 76 | /// RTP header extension, hence it makes sense setting both at the same time. |
| 77 | /// |
| 78 | /// Audio level is measured in negative decibel. 0 is max and a "normal" value might be -30. |
| 79 | pub fn audio_level(mut self, audio_level: i8, voice_activity: bool) -> Self { |
| 80 | self.ext_vals.audio_level = Some(audio_level); |
| 81 | self.ext_vals.voice_activity = Some(voice_activity); |
| 82 | self |
| 83 | } |
| 84 | |
| 85 | /// First packet of a talkspurt, that is the first packet after a silence period during |
| 86 | /// which packets have not been transmitted contiguously. |
| 87 | /// |
| 88 | /// For audio only when dtx or silence suppression is enabled. |
| 89 | /// This will set the marker bit in the RTP header. |
| 90 | pub fn start_of_talkspurt(mut self, start_of_talkspurt: bool) -> Self { |
| 91 | self.start_of_talkspurt = Some(start_of_talkspurt); |
| 92 | self |
| 93 | } |
| 94 | |
| 95 | /// Add video orientation. This can be used by a player on the receiver end to decide |
| 96 | /// whether the video requires to be rotated to show correctly. |
| 97 | pub fn video_orientation(mut self, o: VideoOrientation) -> Self { |
| 98 | self.ext_vals.video_orientation = Some(o); |
| 99 | self |
| 100 | } |
| 101 | |
| 102 | /// Set absolute capture time for this frame. |
| 103 | pub fn abs_capture_time(mut self, capture_time: AbsCaptureTime) -> Self { |
| 104 | self.ext_vals.abs_capture_time = Some(capture_time); |
| 105 | self |
| 106 | } |
| 107 | |
| 108 | /// Set the minimum and maximum playout delay values. This can be used by a player |
| 109 | /// on the receiver end to determine the size of the jitter buffer. |
| 110 | pub fn playout_delay(mut self, min: MediaTime, max: MediaTime) -> Self { |
| 111 | self.ext_vals.play_delay_min = Some(min); |
| 112 | self.ext_vals.play_delay_max = Some(max); |
| 113 | self |
| 114 | } |
| 115 | |
| 116 | /// Set a user extension value. |
| 117 | pub fn user_extension_value<T: Send + Sync + 'static>(mut self, val: T) -> Self { |
| 118 | self.ext_vals.user_values.set(val); |
| 119 | self |
| 120 | } |
| 121 | |
| 122 | /// Write media. |
| 123 | /// |
| 124 | /// This operation fails if the PT doesn't match a negotiated codec, or the RID (`None` or a value) |
| 125 | /// does not match anything negotiated. |
| 126 | /// |
| 127 | /// Regarding `wallclock` and `rtp_time`, the wallclock is the real world time that corresponds to |
| 128 | /// the `MediaTime`. For an SFU, this can be hard to know, since RTP packets typically only |
| 129 | /// contain the media time (RTP time). In the simplest SFU setup, the wallclock could simply |
| 130 | /// be the arrival time of the incoming RTP data (see |
| 131 | /// [`MediaData::network_time`][crate::media::MediaData]). For better synchronization the SFU |
| 132 | /// probably needs to weigh in clock drifts and data provided via the statistics. |
| 133 | /// |
| 134 | /// If you write media before `IceConnectionState` is `Connected` it will be dropped. |
| 135 | /// |
| 136 | /// Panics if [`RtcConfig::set_rtp_mode()`][crate::RtcConfig::set_rtp_mode] is `true`. |
| 137 | pub fn write( |
| 138 | self, |
| 139 | pt: Pt, |
| 140 | wallclock: Instant, |
| 141 | rtp_time: MediaTime, |
| 142 | data: impl Into<Arc<[u8]>>, |
| 143 | ) -> Result<(), RtcError> { |
| 144 | // This (indirect) unwrap is OK due to the invariant of self.mid being resolvable |
| 145 | let media = media_by_mid_mut(&mut self.session.medias, self.mid); |
| 146 | |
| 147 | if !self.session.codec_config.has_pt(pt) { |
| 148 | return Err(RtcError::UnknownPt(pt)); |
| 149 | } |
| 150 | |
| 151 | if let Some(rid) = self.rid { |
| 152 | if !media.rids_tx().contains(rid) { |
| 153 | return Err(RtcError::UnknownRid(rid)); |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | let data: Arc<[u8]> = data.into(); |
| 158 | |
| 159 | trace!( |
| 160 | "write {:?} {:?} {:?} time: {:?} len: {}", |
| 161 | self.mid, |
| 162 | self.rid, |
| 163 | pt, |
| 164 | rtp_time, |
| 165 | data.len() |
| 166 | ); |
| 167 | |
| 168 | let to_payload = ToPayload { |
| 169 | pt, |
| 170 | rid: self.rid, |
| 171 | wallclock, |
| 172 | rtp_time, |
| 173 | data, |
| 174 | start_of_talk_spurt: self.start_of_talkspurt.unwrap_or(false), |
| 175 | ext_vals: self.ext_vals, |
| 176 | }; |
| 177 | |
| 178 | media.set_to_payload(to_payload)?; |
| 179 | |
| 180 | Ok(()) |
| 181 | } |
| 182 | |
| 183 | /// Test if the kind of keyframe request is possible. |
| 184 | /// |
| 185 | /// Sending a keyframe request requires the mechanic to be negotiated as a feedback mechanic |
| 186 | /// in the SDP offer/answer dance first. |
| 187 | /// |
| 188 | /// Specifically these SDP lines would enable FIR and PLI respectively (for payload type 96). |
| 189 | /// |
| 190 | /// ```text |
| 191 | /// a=rtcp-fb:96 ccm fir |
| 192 | /// a=rtcp-fb:96 nack pli |
| 193 | /// ``` |
| 194 | pub fn is_request_keyframe_possible(&self, kind: KeyframeRequestKind) -> bool { |
| 195 | self.session.is_request_keyframe_possible(kind) |
| 196 | } |
| 197 | |
| 198 | /// Request a keyframe from a remote peer sending media data. |
| 199 | /// |
| 200 | /// For SDP: This can fail if the kind of request (PLI or FIR), as specified by the |
| 201 | /// [`KeyframeRequestKind`], is not negotiated in the SDP answer/offer for this m-line. |
| 202 | /// |
| 203 | /// To ensure the call will not fail, use [`Writer::is_request_keyframe_possible()`] to |
| 204 | /// check whether the feedback mechanism is enabled. |
| 205 | /// |
| 206 | /// # Example |
| 207 | /// |
| 208 | /// ```no_run |
| 209 | /// # use std::time::Instant; |
| 210 | /// # use str0m::Rtc; |
| 211 | /// # use str0m::media::{Mid, KeyframeRequestKind}; |
| 212 | /// let mut rtc = Rtc::new(Instant::now()); |
| 213 | /// |
| 214 | /// // add candidates, do SDP negotiation |
| 215 | /// let mid: Mid = todo!(); // obtain mid from Event::MediaAdded. |
| 216 | /// |
| 217 | /// let writer = rtc.writer(mid).unwrap(); |
| 218 | /// |
| 219 | /// writer.request_keyframe(None, KeyframeRequestKind::Pli).unwrap(); |
| 220 | /// ``` |
| 221 | pub fn request_keyframe( |
| 222 | &mut self, |
| 223 | rid: Option<Rid>, |
| 224 | kind: KeyframeRequestKind, |
| 225 | ) -> Result<(), RtcError> { |
| 226 | if !self.is_request_keyframe_possible(kind) { |
| 227 | return Err(RtcError::NotReceivingDirection); |
| 228 | } |
| 229 | |
| 230 | let midrid = MidRid(self.mid, rid); |
| 231 | |
| 232 | let stream = self |
| 233 | .session |
| 234 | .streams |
| 235 | .stream_rx_by_midrid(midrid, false) |
| 236 | .ok_or(RtcError::NoReceiverSource(rid))?; |
| 237 | |
| 238 | stream.request_keyframe(kind); |
| 239 | |
| 240 | Ok(()) |
| 241 | } |
| 242 | } |
| 243 | |
| 244 | /// Get a &mut Media in a slice for a `mid`. |
| 245 | /// |
| 246 | /// `mid` must be resolvable or panic will ensue. |
| 247 | fn media_by_mid_mut(medias: &mut [Media], mid: Mid) -> &mut Media { |
| 248 | medias.iter_mut().find(|m| m.mid() == mid).unwrap() |
| 249 | } |