1#![forbid(unsafe_code)]
14
15use rff_codec::{Codec, CodecRegistry, Decoder, Encoder};
16use rff_core::{
17 AudioFrame, CodecId, Dictionary, Error, Frame, MediaType, Packet, Result, SampleFormat,
18};
19
20pub use rusty_mp3;
22
23#[cfg(feature = "prometheus-telemetry")]
34pub use rusty_mp3::prometheus_telemetry;
35
36pub fn register(registry: &mut CodecRegistry) {
38 registry.register(Codec {
39 id: CodecId::Mp3,
40 name: "mp3",
41 long_name: "MP3 (MPEG-1/2 Audio Layer III)",
42 media_type: MediaType::Audio,
43 decoder: Some(|| Box::new(Mp3Decoder::default())),
44 encoder: Some(|| Box::new(Mp3Encoder::default())),
45 });
46}
47
48fn map_err(e: rusty_mp3::Error) -> Error {
52 match e {
53 rusty_mp3::Error::Unimplemented(what) => Error::Unimplemented(what),
54 rusty_mp3::Error::Eof => Error::Eof,
55 rusty_mp3::Error::Again => Error::Again,
56 rusty_mp3::Error::InvalidData(msg) => Error::InvalidData(msg),
57 rusty_mp3::Error::Unsupported(msg) => Error::Unsupported(msg),
58 other => Error::InvalidData(format!("rusty_mp3: {other}")),
61 }
62}
63
64#[derive(Default)]
65struct Mp3Decoder {
66 inner: rusty_mp3::Mp3Decoder,
67}
68
69impl Decoder for Mp3Decoder {
70 fn send_packet(&mut self, packet: &Packet) -> Result<()> {
71 self.inner.push(&packet.data);
72 Ok(())
73 }
74
75 fn receive_frame(&mut self) -> Result<Frame> {
76 let audio = self.inner.next_frame().map_err(map_err)?;
77 let channels = audio.channels.max(1);
78 let mut bytes = vec![0u8; audio.samples.len() * 4];
83 for (d, s) in bytes.chunks_exact_mut(4).zip(&audio.samples) {
84 d.copy_from_slice(&s.to_le_bytes());
85 }
86 Ok(Frame::Audio(AudioFrame {
87 sample_rate: audio.sample_rate,
88 channels,
89 format: SampleFormat::F32,
90 samples: audio.samples.len() / channels as usize,
91 planes: vec![bytes],
92 pts: None,
93 }))
94 }
95
96 fn flush(&mut self) {
97 self.inner.flush();
98 }
99}
100
101#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
108fn parse_bitrate(s: &str) -> Option<u32> {
109 let s = s.trim();
110 if let Some(n) = s.strip_suffix(['k', 'K']) {
111 n.trim().parse::<f32>().ok().map(|x| x as u32)
112 } else if let Some(n) = s.strip_suffix(['m', 'M']) {
113 n.trim().parse::<f32>().ok().map(|x| (x * 1000.0) as u32)
114 } else {
115 s.parse::<u32>()
116 .ok()
117 .map(|x| if x >= 1000 { x / 1000 } else { x })
118 }
119}
120
121#[derive(Default)]
125struct Mp3Encoder {
126 inner: Option<rusty_mp3::Mp3Encoder>,
127 cbr_kbps: u32,
129 quality: Option<f32>,
131 eof: bool,
132}
133
134impl Mp3Encoder {
135 fn inner(&mut self) -> &mut rusty_mp3::Mp3Encoder {
136 if self.inner.is_none() {
137 self.inner = Some(rusty_mp3::Mp3Encoder::new(rusty_mp3::Mp3EncoderConfig {
138 bitrate_kbps: self.cbr_kbps,
139 vbr_quality: self.quality,
140 }));
141 }
142 self.inner.as_mut().unwrap()
144 }
145}
146
147impl Encoder for Mp3Encoder {
148 fn configure(&mut self, options: &Dictionary) -> Result<()> {
149 if let Some(b) = options.get("b").and_then(parse_bitrate) {
150 self.cbr_kbps = b;
151 }
152 for key in ["q", "qp", "crf", "qscale"] {
155 if let Some(q) = options.get(key).and_then(|v| v.trim().parse::<f32>().ok()) {
156 self.quality = Some(rusty_mp3::vbr_quality_index(q));
157 break;
158 }
159 }
160 Ok(())
161 }
162
163 fn send_frame(&mut self, frame: &Frame) -> Result<()> {
164 let Frame::Audio(af) = frame else {
165 return Ok(());
166 };
167 let in_ch = af.channels.max(1);
168 let bps = match af.format {
174 SampleFormat::F32 => 4,
175 SampleFormat::S16 => 2,
176 other => {
177 return Err(Error::unsupported(format!(
178 "mp3 encode: sample format `{}` (need interleaved s16/f32)",
179 other.name()
180 )))
181 }
182 };
183 let Some(data) = af.planes.first() else {
195 return Err(Error::invalid(
196 "mp3 encode: audio frame has no sample plane",
197 ));
198 };
199 let frame_bytes = usize::from(in_ch) * bps;
200 let frames = af.samples.min(data.len() / frame_bytes);
201 let plane = &data[..frames * frame_bytes];
202 let sr = af.sample_rate;
203 let inner = self.inner();
204 match bps {
205 4 => inner.push_pcm_f32le(plane, in_ch, sr),
206 _ => inner.push_pcm_s16le(plane, in_ch, sr),
207 }
208 .map_err(map_err)
209 }
210
211 fn receive_packet(&mut self) -> Result<Packet> {
212 match self.inner.as_mut() {
216 Some(inner) => inner
217 .next_packet()
218 .map(|bytes| Packet::from_data(0, bytes))
219 .map_err(map_err),
220 None if self.eof => Err(Error::Eof),
221 None => Err(Error::Again),
222 }
223 }
224
225 fn flush(&mut self) {
226 if let Some(inner) = self.inner.as_mut() {
227 inner.finish();
228 }
229 self.eof = true;
230 }
231}
232
233#[cfg(test)]
234#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)] mod tests {
236 use super::*;
237
238 fn encode_mono_s16(input: &[i16], sample_rate: u32) -> Vec<u8> {
241 let bytes: Vec<u8> = input.iter().flat_map(|s| s.to_le_bytes()).collect();
242 let mut enc = Mp3Encoder::default();
243 enc.send_frame(&Frame::Audio(AudioFrame {
244 sample_rate,
245 channels: 1,
246 format: SampleFormat::S16,
247 planes: vec![bytes],
248 samples: input.len(),
249 pts: None,
250 }))
251 .unwrap();
252 enc.flush();
253 let mut mp3 = Vec::new();
254 while let Ok(p) = enc.receive_packet() {
255 mp3.extend_from_slice(&p.data);
256 }
257 mp3
258 }
259
260 fn decode_mono(mp3: Vec<u8>) -> Vec<f32> {
261 let mut dec = Mp3Decoder::default();
262 dec.send_packet(&Packet::from_data(0, mp3)).unwrap();
263 dec.flush();
264 let mut out = Vec::new();
265 while let Ok(Frame::Audio(af)) = dec.receive_frame() {
266 for c in af.planes[0].chunks_exact(4) {
267 out.push(f32::from_le_bytes([c[0], c[1], c[2], c[3]]));
268 }
269 }
270 out
271 }
272
273 #[test]
279 #[cfg_attr(miri, ignore = "runs real encodes: too slow to interpret under Miri")]
280 fn s16_input_encodes_to_audible_output() {
281 let sr = 44100u32;
282 let n = sr as usize; let s16: Vec<i16> = (0..n)
284 .map(|i| {
285 let t = i as f32 / sr as f32;
286 (0.5 * (2.0 * std::f32::consts::PI * 440.0 * t).sin() * 32767.0) as i16
287 })
288 .collect();
289 let out = decode_mono(encode_mono_s16(&s16, sr));
290 let rms = (out.iter().map(|x| x * x).sum::<f32>() / out.len().max(1) as f32).sqrt();
291 assert!(
292 rms > 0.1,
293 "S16 input produced near-silent output (rms={rms}); encoder ignored af.format"
294 );
295 }
296
297 #[test]
301 #[cfg_attr(miri, ignore = "runs real encodes: too slow to interpret under Miri")]
302 fn hostile_frame_shapes_are_errors_or_bounded() {
303 let mut enc = Mp3Encoder::default();
304 let empty = Frame::Audio(AudioFrame {
305 sample_rate: 44_100,
306 channels: 2,
307 format: SampleFormat::S16,
308 planes: vec![],
309 samples: 1152,
310 pts: None,
311 });
312 assert!(enc.send_frame(&empty).is_err());
313
314 let claim = |samples: usize| {
315 let mut enc = Mp3Encoder::default();
316 enc.send_frame(&Frame::Audio(AudioFrame {
317 sample_rate: 44_100,
318 channels: 1,
319 format: SampleFormat::S16,
320 planes: vec![vec![0x11; 2 * 4608]],
321 samples,
322 pts: None,
323 }))
324 .unwrap();
325 enc.flush();
326 let mut n = 0;
327 while let Ok(p) = enc.receive_packet() {
328 n += p.data.len();
329 }
330 n
331 };
332 assert_eq!(claim(1 << 40), claim(4608));
335 }
336
337 #[test]
341 #[cfg_attr(miri, ignore = "runs real encodes: too slow to interpret under Miri")]
342 fn random_frame_shapes_never_panic() {
343 let mut s = 0x9E37_79B9_7F4A_7C15u64;
344 let mut rnd = move || {
345 s ^= s << 13;
346 s ^= s >> 7;
347 s ^= s << 17;
348 s
349 };
350 let formats = [
351 SampleFormat::S16,
352 SampleFormat::F32,
353 SampleFormat::F32Planar,
354 ];
355 let rates = [44_100u32, 22_050, 8_000, 48_000, 0, 7, 96_000];
356 for _ in 0..300 {
357 let mut enc = Mp3Encoder::default();
358 let plane: Vec<u8> = (0..(rnd() % 20_000)).map(|_| rnd() as u8).collect();
359 let frame = Frame::Audio(AudioFrame {
360 sample_rate: rates[(rnd() % 7) as usize],
361 channels: (rnd() % 9) as u16,
362 format: formats[(rnd() % 3) as usize],
363 planes: if rnd() % 10 == 0 { vec![] } else { vec![plane] },
364 samples: if rnd() % 3 == 0 {
365 (rnd() % (1 << 40)) as usize
366 } else {
367 (rnd() % 6000) as usize
368 },
369 pts: None,
370 });
371 let _ = enc.send_frame(&frame);
372 enc.flush();
373 while enc.receive_packet().is_ok() {}
374 }
375 }
376
377 #[test]
380 fn bitrate_options_parse_or_saturate() {
381 assert_eq!(parse_bitrate("128k"), Some(128));
382 assert_eq!(parse_bitrate("192000"), Some(192));
383 assert_eq!(parse_bitrate("0.32M"), Some(320));
384 assert_eq!(parse_bitrate("-5k"), Some(0));
385 assert_eq!(parse_bitrate("nank"), Some(0));
386 assert_eq!(parse_bitrate("1e30k"), Some(u32::MAX));
387 for s in ["", "k", "M", "abc", " 12 K ", "99999999999999999999", "∞k"] {
388 let _ = parse_bitrate(s);
389 }
390 }
391
392 #[test]
395 #[cfg_attr(miri, ignore = "runs real encodes: too slow to interpret under Miri")]
396 fn adapter_decode_matches_rusty_mp3() {
397 let sr = 44_100u32;
398 let s16: Vec<i16> = (0..sr as usize)
399 .map(|i| ((i as f32 * 0.07).sin() * 9000.0) as i16)
400 .collect();
401 let base = encode_mono_s16(&s16, sr);
402 let mut seed = 0x2545_F491_4F6C_DD1Du64;
403 for case in 0..20 {
404 let mut mp3 = base.clone();
405 for _ in 0..case {
406 seed ^= seed << 13;
407 seed ^= seed >> 7;
408 seed ^= seed << 17;
409 let i = (seed % mp3.len() as u64) as usize;
410 mp3[i] ^= 1 << (seed % 8);
411 }
412 let ours = decode_mono(mp3.clone());
413 let mut reference = rusty_mp3::Mp3Decoder::new();
414 reference.push(&mp3);
415 reference.flush();
416 let mut want = Vec::new();
417 while let Ok(f) = reference.next_frame() {
418 want.extend_from_slice(&f.samples);
419 }
420 assert_eq!(ours.len(), want.len(), "case {case}");
421 assert!(
422 ours.iter()
423 .zip(&want)
424 .all(|(a, b)| a.to_bits() == b.to_bits()),
425 "case {case}"
426 );
427 }
428 }
429
430 #[test]
431 fn registers_as_audio_codec() {
432 let mut reg = CodecRegistry::new();
433 register(&mut reg);
434 let codec = reg.by_name("mp3").expect("mp3 registered");
435 assert_eq!(codec.id, CodecId::Mp3);
436 assert_eq!(codec.media_type, MediaType::Audio);
437 assert!(codec.can_decode() && codec.can_encode());
438 }
439}