Skip to main content

active_call/media/track/
track_codec.rs

1use crate::{media::AudioFrame, media::PcmBuf, media::Samples};
2use audio_codec::{
3    bytes_to_samples,
4    g722::{G722Decoder, G722Encoder},
5    pcma::{PcmaDecoder, PcmaEncoder},
6    pcmu::{PcmuDecoder, PcmuEncoder},
7    samples_to_bytes, BoxedResampler, CodecType, Decoder, Encoder,
8};
9use std::collections::HashMap;
10use std::sync::{Arc, RwLock};
11use tracing::warn;
12
13use audio_codec::g729::{G729Decoder, G729Encoder};
14use audio_codec::opus::{OpusDecoder, OpusEncoder};
15
16pub struct TrackCodec {
17    pcmu_encoder: PcmuEncoder,
18    pcmu_decoder: PcmuDecoder,
19    pcma_encoder: PcmaEncoder,
20    pcma_decoder: PcmaDecoder,
21
22    g722_encoder: Option<Box<G722Encoder>>,
23    g722_decoder: Option<Box<G722Decoder>>,
24
25    g729_encoder: Option<Box<G729Encoder>>,
26    g729_decoder: Option<Box<G729Decoder>>,
27
28    opus_encoder: Option<OpusEncoder>,
29    opus_decoder: Option<OpusDecoder>,
30
31    resampler: Option<BoxedResampler>,
32    resampler_in_rate: u32,
33    resampler_out_rate: u32,
34    pub payload_type_map: Arc<RwLock<HashMap<u8, CodecType>>>,
35}
36
37impl Clone for TrackCodec {
38    fn clone(&self) -> Self {
39        let mut new = Self::new();
40        // Share the same underlying map so reinvite PT updates are visible to all clones.
41        new.payload_type_map = Arc::clone(&self.payload_type_map);
42        new
43    }
44}
45
46impl TrackCodec {
47    pub fn new() -> Self {
48        let mut map = HashMap::new();
49        map.insert(0, CodecType::PCMU);
50        map.insert(8, CodecType::PCMA);
51        map.insert(9, CodecType::G722);
52        map.insert(18, CodecType::G729);
53        map.insert(101, CodecType::TelephoneEvent);
54        map.insert(111, CodecType::Opus);
55        let payload_type_map = Arc::new(RwLock::new(map));
56
57        Self {
58            pcmu_encoder: PcmuEncoder::new(),
59            pcmu_decoder: PcmuDecoder::new(),
60            pcma_encoder: PcmaEncoder::new(),
61            pcma_decoder: PcmaDecoder::new(),
62            g722_encoder: None,
63            g722_decoder: None,
64            g729_encoder: None,
65            g729_decoder: None,
66            opus_encoder: None,
67            opus_decoder: None,
68            resampler: None,
69            resampler_in_rate: 0,
70            resampler_out_rate: 0,
71            payload_type_map,
72        }
73    }
74
75    pub fn set_payload_type(&mut self, pt: u8, codec: CodecType) {
76        self.payload_type_map.write().unwrap().insert(pt, codec);
77    }
78
79    /// Look up the codec for a given RTP payload type, consulting the negotiated map first
80    /// and falling back to the static payload type registry.
81    pub fn get_codec_for_pt(&self, pt: u8) -> Option<CodecType> {
82        self.payload_type_map
83            .read()
84            .unwrap()
85            .get(&pt)
86            .cloned()
87            .or_else(|| CodecType::try_from(pt).ok())
88    }
89
90    pub fn is_audio(payload_type: u8) -> bool {
91        match payload_type {
92            0 | 8 | 9 | 18 | 111 => true,
93            101 => false, // Telephone Event (DTMF) should not be decoded as audio
94            pt if pt >= 96 && pt <= 127 => true,
95            _ => false,
96        }
97    }
98
99    /// Decode an RTP payload into PCM at the codec's native sample rate.
100    /// Returns `(native_sample_rate, channels, samples)`; resampling to the
101    /// pipeline rate is the caller's responsibility (see ProcessorChain).
102    pub fn decode(&mut self, payload_type: u8, payload: &[u8]) -> (u32, u16, PcmBuf) {
103        let codec = self
104            .payload_type_map
105            .read()
106            .unwrap()
107            .get(&payload_type)
108            .cloned()
109            .unwrap_or_else(|| match payload_type {
110                0 => CodecType::PCMU,
111                8 => CodecType::PCMA,
112                9 => CodecType::G722,
113                18 => CodecType::G729,
114                111 => CodecType::Opus,
115                _ => CodecType::PCMU,
116            });
117
118        let pcm = match codec {
119            CodecType::PCMU => self.pcmu_decoder.decode(payload),
120            CodecType::PCMA => self.pcma_decoder.decode(payload),
121            CodecType::G722 => self
122                .g722_decoder
123                .get_or_insert_with(|| Box::new(G722Decoder::new()))
124                .decode(payload),
125            CodecType::G729 => self
126                .g729_decoder
127                .get_or_insert_with(|| Box::new(G729Decoder::new()))
128                .decode(payload),
129            CodecType::Opus => self
130                .opus_decoder
131                .get_or_insert_with(OpusDecoder::new_default)
132                .decode(payload),
133            _ => bytes_to_samples(payload),
134        };
135
136        let (in_rate, channels) = match codec {
137            CodecType::PCMU => (8000, 1),
138            CodecType::PCMA => (8000, 1),
139            CodecType::G722 => (16000, 1),
140            CodecType::G729 => (8000, 1),
141            CodecType::Opus => {
142                if pcm.len() >= 1920 {
143                    (48000, 2)
144                } else {
145                    (48000, 1)
146                }
147            }
148            _ => (8000, 1),
149        };
150
151        (in_rate, channels, pcm)
152    }
153
154    pub fn resample(&mut self, pcm: PcmBuf, in_rate: u32, out_rate: u32) -> PcmBuf {
155        // A zero rate means the caller has no valid source rate (e.g. a
156        // media-pass track created without an explicit input sample rate).
157        // There is nothing sensible to resample from, so pass the samples
158        // through instead of panicking: `BoxedResampler::new` rejects zero
159        // rates, and unwinding here poisons the `ProcessorChain` mutex,
160        // permanently breaking every subsequent frame on the track.
161        if in_rate == 0 || out_rate == 0 || in_rate == out_rate {
162            return pcm;
163        }
164
165        if self.resampler.is_none()
166            || self.resampler_in_rate != in_rate
167            || self.resampler_out_rate != out_rate
168        {
169            match BoxedResampler::new(in_rate as usize, out_rate as usize) {
170                Ok(resampler) => {
171                    self.resampler = Some(resampler);
172                    self.resampler_in_rate = in_rate;
173                    self.resampler_out_rate = out_rate;
174                }
175                Err(e) => {
176                    warn!(in_rate, out_rate, "failed to build resampler: {e}");
177                    self.resampler = None;
178                    self.resampler_in_rate = 0;
179                    self.resampler_out_rate = 0;
180                    return pcm;
181                }
182            }
183        }
184        self.resampler.as_mut().unwrap().resample(&pcm)
185    }
186
187    pub fn encode(&mut self, payload_type: u8, frame: AudioFrame) -> (u8, Vec<u8>) {
188        match frame.samples {
189            Samples::PCM { samples: mut pcm } => {
190                let codec = self
191                    .payload_type_map
192                    .read()
193                    .unwrap()
194                    .get(&payload_type)
195                    .cloned()
196                    .or_else(|| CodecType::try_from(payload_type).ok());
197
198                let target_samplerate = codec.map(|c| c.samplerate()).unwrap_or(8000);
199                if frame.sample_rate != target_samplerate {
200                    pcm = self.resample(pcm, frame.sample_rate, target_samplerate);
201                }
202
203                let payload = match codec {
204                    Some(CodecType::PCMU) => self.pcmu_encoder.encode(&pcm),
205                    Some(CodecType::PCMA) => self.pcma_encoder.encode(&pcm),
206                    Some(CodecType::G722) => self
207                        .g722_encoder
208                        .get_or_insert_with(|| Box::new(G722Encoder::new()))
209                        .encode(&pcm),
210                    Some(CodecType::G729) => self
211                        .g729_encoder
212                        .get_or_insert_with(|| Box::new(G729Encoder::new()))
213                        .encode(&pcm),
214                    Some(CodecType::Opus) => self
215                        .opus_encoder
216                        .get_or_insert_with(OpusEncoder::new_default)
217                        .encode(&pcm),
218                    _ => samples_to_bytes(&pcm),
219                };
220                (payload_type, payload)
221            }
222            Samples::RTP {
223                payload_type,
224                payload,
225                ..
226            } => (payload_type, payload),
227            _ => (payload_type, vec![]),
228        }
229    }
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235
236    #[test]
237    fn test_encode_dynamic_opus_payload_type_uses_opus_encoder() {
238        let mut codec = TrackCodec::new();
239        codec.set_payload_type(96, CodecType::Opus);
240
241        let frame = AudioFrame {
242            samples: Samples::PCM {
243                // 20ms @ 16k mono
244                samples: vec![0; 320],
245            },
246            sample_rate: 16000,
247            channels: 1,
248            ..Default::default()
249        };
250
251        let (pt, payload) = codec.encode(96, frame);
252        assert_eq!(pt, 96);
253        assert!(!payload.is_empty());
254        // If this were raw PCM bytes, it would be 640 bytes.
255        assert!(payload.len() < 640);
256    }
257
258    #[test]
259    fn test_resample_zero_rate_passes_through_without_panic() {
260        let mut codec = TrackCodec::new();
261        let samples = vec![1000i16; 160];
262
263        assert_eq!(codec.resample(samples.clone(), 0, 16000), samples);
264        assert_eq!(codec.resample(samples.clone(), 16000, 0), samples);
265        assert_eq!(codec.resample(samples.clone(), 0, 0), samples);
266    }
267}