File
Blob: firmware/vendor/str0m/src/format/codec.rs
| 1 | use serde::{Deserialize, Serialize}; |
| 2 | use std::fmt; |
| 3 | |
| 4 | use crate::packet::MediaKind; |
| 5 | use crate::rtp_::Frequency; |
| 6 | |
| 7 | use super::format_params::FormatParams; |
| 8 | |
| 9 | /// Codec specification |
| 10 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] |
| 11 | pub struct CodecSpec { |
| 12 | /// The codec identifier. |
| 13 | pub codec: Codec, |
| 14 | |
| 15 | /// Clock rate of the codec. |
| 16 | pub clock_rate: Frequency, |
| 17 | |
| 18 | /// Number of audio channels (if any). |
| 19 | pub channels: Option<u8>, |
| 20 | |
| 21 | /// Codec specific format parameters. This might carry additional config for |
| 22 | /// things like h264. |
| 23 | pub format: FormatParams, |
| 24 | } |
| 25 | |
| 26 | impl CodecSpec { |
| 27 | /// The RTP clock rate used on the wire for this codec. |
| 28 | /// |
| 29 | /// For most codecs this is the same as [`CodecSpec::clock_rate`]. G722 is a |
| 30 | /// special case: although the codec samples audio at 16 kHz, its RTP timestamp |
| 31 | /// clock is 8000 Hz (to remain backwards compatible with RFC 1890, which |
| 32 | /// incorrectly used this value). str0m treats G722 as a 16 kHz codec everywhere |
| 33 | /// user facing, and only maps to 8 kHz when converting to and from RTP timestamps |
| 34 | /// (and in the SDP `a=rtpmap` line). |
| 35 | /// |
| 36 | /// See RFC 3551 §4.5.2 and |
| 37 | /// <https://en.wikipedia.org/wiki/RTP_payload_formats#cite_note-55> |
| 38 | pub(crate) fn rtp_clock_rate(&self) -> Frequency { |
| 39 | if self.codec == Codec::G722 { |
| 40 | Frequency::EIGHT_KHZ |
| 41 | } else { |
| 42 | self.clock_rate |
| 43 | } |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | /// Known codecs. |
| 48 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] |
| 49 | #[non_exhaustive] |
| 50 | #[allow(missing_docs)] |
| 51 | pub enum Codec { |
| 52 | Opus, |
| 53 | PCMU, |
| 54 | PCMA, |
| 55 | G722, |
| 56 | /// Comfort Noise payload, per RFC 3389. |
| 57 | CN, |
| 58 | H264, |
| 59 | // TODO show this when we support h265. |
| 60 | #[doc(hidden)] |
| 61 | H265, |
| 62 | /// H266/VVC. |
| 63 | H266, |
| 64 | Vp8, |
| 65 | Vp9, |
| 66 | // TODO show this when we support Av1. |
| 67 | #[doc(hidden)] |
| 68 | Av1, |
| 69 | /// Technically not a codec, but used in places where codecs go |
| 70 | /// in `a=rtpmap` lines. |
| 71 | #[doc(hidden)] |
| 72 | Rtx, |
| 73 | /// For RTP mode. No codec. |
| 74 | #[doc(hidden)] |
| 75 | Null, |
| 76 | #[doc(hidden)] |
| 77 | Unknown, |
| 78 | } |
| 79 | |
| 80 | #[cfg(feature = "drv")] |
| 81 | crate::drv_identity_copy!(Codec, CodecSpec); |
| 82 | |
| 83 | impl Codec { |
| 84 | /// Tells if codec is audio. |
| 85 | pub fn is_audio(&self) -> bool { |
| 86 | use Codec::*; |
| 87 | matches!(self, Opus | PCMU | PCMA | G722 | CN) |
| 88 | } |
| 89 | |
| 90 | /// Tells if codec is video. |
| 91 | pub fn is_video(&self) -> bool { |
| 92 | use Codec::*; |
| 93 | matches!(self, H266 | H265 | H264 | Vp8 | Vp9 | Av1) |
| 94 | } |
| 95 | |
| 96 | /// Audio/Video. |
| 97 | pub fn kind(&self) -> MediaKind { |
| 98 | if self.is_audio() { |
| 99 | MediaKind::Audio |
| 100 | } else { |
| 101 | MediaKind::Video |
| 102 | } |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | impl<'a> From<&'a str> for Codec { |
| 107 | fn from(v: &'a str) -> Self { |
| 108 | let lc = v.to_ascii_lowercase(); |
| 109 | match &lc[..] { |
| 110 | "opus" => Codec::Opus, |
| 111 | "pcmu" => Codec::PCMU, |
| 112 | "pcma" => Codec::PCMA, |
| 113 | "g722" => Codec::G722, |
| 114 | "cn" => Codec::CN, |
| 115 | "h264" => Codec::H264, |
| 116 | "h265" => Codec::H265, |
| 117 | "h266" => Codec::H266, |
| 118 | "vp8" => Codec::Vp8, |
| 119 | "vp9" => Codec::Vp9, |
| 120 | "av1" => Codec::Av1, |
| 121 | "rtx" => Codec::Rtx, // resends |
| 122 | _ => Codec::Unknown, |
| 123 | } |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | impl fmt::Display for Codec { |
| 128 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 129 | match self { |
| 130 | Codec::Opus => write!(f, "opus"), |
| 131 | Codec::PCMU => write!(f, "PCMU"), |
| 132 | Codec::PCMA => write!(f, "PCMA"), |
| 133 | Codec::G722 => write!(f, "G722"), |
| 134 | Codec::CN => write!(f, "CN"), |
| 135 | Codec::H264 => write!(f, "H264"), |
| 136 | Codec::H265 => write!(f, "H265"), |
| 137 | Codec::H266 => write!(f, "H266"), |
| 138 | Codec::Vp8 => write!(f, "VP8"), |
| 139 | Codec::Vp9 => write!(f, "VP9"), |
| 140 | Codec::Av1 => write!(f, "AV1"), |
| 141 | Codec::Rtx => write!(f, "rtx"), |
| 142 | Codec::Null => write!(f, "null"), |
| 143 | Codec::Unknown => write!(f, "unknown"), |
| 144 | } |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | #[cfg(test)] |
| 149 | mod test { |
| 150 | use super::*; |
| 151 | use crate::format::FormatParams; |
| 152 | use crate::rtp_::{Frequency, MediaTime}; |
| 153 | |
| 154 | fn g722_spec() -> CodecSpec { |
| 155 | CodecSpec { |
| 156 | codec: Codec::G722, |
| 157 | clock_rate: Frequency::SIXTEEN_KHZ, |
| 158 | channels: None, |
| 159 | format: FormatParams::default(), |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | #[test] |
| 164 | fn g722_is_audio_and_parses() { |
| 165 | assert!(Codec::G722.is_audio()); |
| 166 | assert!(!Codec::G722.is_video()); |
| 167 | assert_eq!(Codec::from("G722"), Codec::G722); |
| 168 | assert_eq!(Codec::from("g722"), Codec::G722); |
| 169 | assert_eq!(Codec::G722.to_string(), "G722"); |
| 170 | } |
| 171 | |
| 172 | #[test] |
| 173 | fn comfort_noise_is_audio_and_parses() { |
| 174 | assert!(Codec::CN.is_audio()); |
| 175 | assert!(!Codec::CN.is_video()); |
| 176 | assert_eq!(Codec::from("CN"), Codec::CN); |
| 177 | assert_eq!(Codec::from("cn"), Codec::CN); |
| 178 | assert_eq!(Codec::CN.to_string(), "CN"); |
| 179 | } |
| 180 | |
| 181 | #[test] |
| 182 | fn g722_rtp_clock_rate_is_8khz() { |
| 183 | let spec = g722_spec(); |
| 184 | // User facing clock rate is 16 kHz ... |
| 185 | assert_eq!(spec.clock_rate, Frequency::SIXTEEN_KHZ); |
| 186 | // ... but the RTP wire clock rate is 8 kHz (RFC 3551). |
| 187 | assert_eq!(spec.rtp_clock_rate(), Frequency::EIGHT_KHZ); |
| 188 | } |
| 189 | |
| 190 | #[test] |
| 191 | fn g722_16khz_media_time_halves_on_the_wire() { |
| 192 | // 20 ms of 16 kHz audio is 320 samples. On the wire the RTP timestamp must |
| 193 | // advance by 160 (8000 Hz clock), i.e. the 16 kHz media time is halved when |
| 194 | // converted to an RTP timestamp. |
| 195 | let spec = g722_spec(); |
| 196 | let media_time = MediaTime::new(320, spec.clock_rate); |
| 197 | let wire = media_time.rebase(spec.rtp_clock_rate()); |
| 198 | assert_eq!(wire.numer(), 160); |
| 199 | } |
| 200 | |
| 201 | #[test] |
| 202 | fn non_g722_rtp_clock_rate_equals_clock_rate() { |
| 203 | let spec = CodecSpec { |
| 204 | codec: Codec::Opus, |
| 205 | clock_rate: Frequency::FORTY_EIGHT_KHZ, |
| 206 | channels: Some(2), |
| 207 | format: FormatParams::default(), |
| 208 | }; |
| 209 | assert_eq!(spec.rtp_clock_rate(), Frequency::FORTY_EIGHT_KHZ); |
| 210 | } |
| 211 | } |