File
Blob: firmware/crates/radio-webrtc/src/audio.rs
| 1 | use crate::{Error, Result}; |
| 2 | use std::time::Instant; |
| 3 | use str0m::{ |
| 4 | Rtc, |
| 5 | format::Codec, |
| 6 | media::{Frequency, Mid}, |
| 7 | rtp::{RtpWrite, SeqNo}, |
| 8 | }; |
| 9 | |
| 10 | /// One Opus packet per RTP packet, with sequence state tied to the SSRC lifetime. |
| 11 | #[derive(Debug)] |
| 12 | pub(crate) struct Audio { |
| 13 | mid: Mid, |
| 14 | next_seq: SeqNo, |
| 15 | } |
| 16 | |
| 17 | impl Audio { |
| 18 | pub(crate) fn new(mid: Mid) -> Self { |
| 19 | Self { |
| 20 | mid, |
| 21 | // Match str0m's random initial sequence and start with a zero ROC. |
| 22 | next_seq: SeqNo::default(), |
| 23 | } |
| 24 | } |
| 25 | |
| 26 | pub(crate) fn write(&mut self, rtc: &mut Rtc, pts_ms: u32, bytes: &[u8]) -> Result<()> { |
| 27 | if bytes.is_empty() || bytes.len() > radio_core::music::MAX_OPUS_BYTES { |
| 28 | return Err(Error::new("invalid Opus packet size")); |
| 29 | } |
| 30 | // Direct writes retain their payload, so never queue audio without a |
| 31 | // live ICE/DTLS/SRTP transport to consume it. |
| 32 | if !rtc.is_alive() || !rtc.is_connected() { |
| 33 | return Err(Error::new("audio transport unavailable")); |
| 34 | } |
| 35 | let media = rtc |
| 36 | .media(self.mid) |
| 37 | .filter(|media| !media.disabled() && media.direction().is_sending()) |
| 38 | .ok_or(Error::new("audio media unavailable"))?; |
| 39 | let pt = rtc |
| 40 | .codec_config() |
| 41 | .params() |
| 42 | .iter() |
| 43 | .find(|params| { |
| 44 | let spec = params.spec(); |
| 45 | media.remote_pts().contains(¶ms.pt()) |
| 46 | && spec.codec == Codec::Opus |
| 47 | && spec.clock_rate == Frequency::FORTY_EIGHT_KHZ |
| 48 | && spec.channels == Some(2) |
| 49 | }) |
| 50 | .ok_or(Error::new("Opus was not negotiated"))? |
| 51 | .pt(); |
| 52 | let mut direct = rtc.direct_api(); |
| 53 | let stream = direct |
| 54 | .stream_tx_by_mid(self.mid, None) |
| 55 | .ok_or(Error::new("audio stream unavailable"))?; |
| 56 | |
| 57 | // Pauses send encoded silence, without DTX/talkspurt markers. str0m |
| 58 | // supplies negotiated header extensions, pacing and SRTP encryption. |
| 59 | // The owned Arc payload outlives the caller's reusable music buffer. |
| 60 | stream.write_rtp(RtpWrite::new( |
| 61 | pt, |
| 62 | self.next_seq.inc(), |
| 63 | pts_ms.wrapping_mul(48), |
| 64 | Instant::now(), |
| 65 | bytes, |
| 66 | )); |
| 67 | Ok(()) |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | #[cfg(test)] |
| 72 | mod tests; |