use rff_codec::{Codec, CodecRegistry, Decoder, Encoder};
use rff_core::{
AudioFrame, CodecId, Dictionary, Error, Frame, MediaType, Packet, Result, SampleFormat,
};
pub use rusty_mp3;
#[cfg(feature = "prometheus-telemetry")]
pub use rusty_mp3::prometheus_telemetry;
pub fn register(registry: &mut CodecRegistry) {
registry.register(Codec {
id: CodecId::Mp3,
name: "mp3",
long_name: "MP3 (MPEG-1/2 Audio Layer III)",
media_type: MediaType::Audio,
decoder: Some(|| Box::new(Mp3Decoder::default())),
encoder: Some(|| Box::new(Mp3Encoder::default())),
});
}
fn map_err(e: rusty_mp3::Error) -> Error {
match e {
rusty_mp3::Error::Unimplemented(what) => Error::Unimplemented(what),
rusty_mp3::Error::Eof => Error::Eof,
rusty_mp3::Error::Again => Error::Again,
rusty_mp3::Error::InvalidData(msg) => Error::InvalidData(msg),
rusty_mp3::Error::Unsupported(msg) => Error::Unsupported(msg),
other => Error::InvalidData(format!("rusty_mp3: {other}")),
}
}
#[derive(Default)]
struct Mp3Decoder {
inner: rusty_mp3::Mp3Decoder,
}
impl Decoder for Mp3Decoder {
fn send_packet(&mut self, packet: &Packet) -> Result<()> {
self.inner.push(&packet.data);
Ok(())
}
fn receive_frame(&mut self) -> Result<Frame> {
let audio = self.inner.next_frame().map_err(map_err)?;
let channels = audio.channels.max(1);
let mut bytes = Vec::with_capacity(audio.samples.len() * 4);
for s in &audio.samples {
bytes.extend_from_slice(&s.to_le_bytes());
}
Ok(Frame::Audio(AudioFrame {
sample_rate: audio.sample_rate,
channels,
format: SampleFormat::F32,
samples: audio.samples.len() / channels as usize,
planes: vec![bytes],
pts: None,
}))
}
fn flush(&mut self) {
self.inner.flush();
}
}
fn parse_bitrate(s: &str) -> Option<u32> {
let s = s.trim();
if let Some(n) = s.strip_suffix(['k', 'K']) {
n.trim().parse::<f32>().ok().map(|x| x as u32)
} else if let Some(n) = s.strip_suffix(['m', 'M']) {
n.trim().parse::<f32>().ok().map(|x| (x * 1000.0) as u32)
} else {
s.parse::<u32>()
.ok()
.map(|x| if x >= 1000 { x / 1000 } else { x })
}
}
#[derive(Default)]
struct Mp3Encoder {
inner: Option<rusty_mp3::Mp3Encoder>,
cbr_kbps: u32,
quality: Option<f32>,
eof: bool,
}
impl Mp3Encoder {
fn inner(&mut self) -> &mut rusty_mp3::Mp3Encoder {
if self.inner.is_none() {
self.inner = Some(rusty_mp3::Mp3Encoder::new(rusty_mp3::Mp3EncoderConfig {
bitrate_kbps: self.cbr_kbps,
vbr_quality: self.quality,
}));
}
self.inner.as_mut().unwrap()
}
}
impl Encoder for Mp3Encoder {
fn configure(&mut self, options: &Dictionary) -> Result<()> {
if let Some(b) = options.get("b").and_then(parse_bitrate) {
self.cbr_kbps = b;
}
for key in ["q", "qp", "crf", "qscale"] {
if let Some(q) = options.get(key).and_then(|v| v.trim().parse::<f32>().ok()) {
self.quality = Some(rusty_mp3::vbr_quality_index(q));
break;
}
}
Ok(())
}
fn send_frame(&mut self, frame: &Frame) -> Result<()> {
let Frame::Audio(af) = frame else {
return Ok(());
};
let in_ch = af.channels.max(1);
let data = &af.planes[0];
match af.format {
SampleFormat::F32 => {
let mut pcm = Vec::with_capacity(af.samples * in_ch as usize);
for s in 0..af.samples {
for c in 0..in_ch as usize {
let off = (s * in_ch as usize + c) * 4;
pcm.push(if off + 4 <= data.len() {
f32::from_le_bytes([
data[off],
data[off + 1],
data[off + 2],
data[off + 3],
])
} else {
0.0
});
}
}
let sr = af.sample_rate;
self.inner()
.push_pcm_f32(&pcm, in_ch, sr)
.map_err(map_err)?;
}
SampleFormat::S16 => {
let mut pcm = Vec::with_capacity(af.samples * in_ch as usize);
for s in 0..af.samples {
for c in 0..in_ch as usize {
let off = (s * in_ch as usize + c) * 2;
pcm.push(if off + 2 <= data.len() {
i16::from_le_bytes([data[off], data[off + 1]])
} else {
0
});
}
}
let sr = af.sample_rate;
self.inner()
.push_pcm_s16(&pcm, in_ch, sr)
.map_err(map_err)?;
}
other => {
return Err(Error::unsupported(format!(
"mp3 encode: sample format `{}` (need interleaved s16/f32)",
other.name()
)))
}
}
Ok(())
}
fn receive_packet(&mut self) -> Result<Packet> {
match self.inner.as_mut() {
Some(inner) => inner
.next_packet()
.map(|bytes| Packet::from_data(0, bytes))
.map_err(map_err),
None if self.eof => Err(Error::Eof),
None => Err(Error::Again),
}
}
fn flush(&mut self) {
if let Some(inner) = self.inner.as_mut() {
inner.finish();
}
self.eof = true;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn encode_mono_s16(input: &[i16], sample_rate: u32) -> Vec<u8> {
let bytes: Vec<u8> = input.iter().flat_map(|s| s.to_le_bytes()).collect();
let mut enc = Mp3Encoder::default();
enc.send_frame(&Frame::Audio(AudioFrame {
sample_rate,
channels: 1,
format: SampleFormat::S16,
planes: vec![bytes],
samples: input.len(),
pts: None,
}))
.unwrap();
enc.flush();
let mut mp3 = Vec::new();
while let Ok(p) = enc.receive_packet() {
mp3.extend_from_slice(&p.data);
}
mp3
}
fn decode_mono(mp3: Vec<u8>) -> Vec<f32> {
let mut dec = Mp3Decoder::default();
dec.send_packet(&Packet::from_data(0, mp3)).unwrap();
dec.flush();
let mut out = Vec::new();
while let Ok(Frame::Audio(af)) = dec.receive_frame() {
for c in af.planes[0].chunks_exact(4) {
out.push(f32::from_le_bytes([c[0], c[1], c[2], c[3]]));
}
}
out
}
#[test]
fn s16_input_encodes_to_audible_output() {
let sr = 44100u32;
let n = sr as usize; let s16: Vec<i16> = (0..n)
.map(|i| {
let t = i as f32 / sr as f32;
(0.5 * (2.0 * std::f32::consts::PI * 440.0 * t).sin() * 32767.0) as i16
})
.collect();
let out = decode_mono(encode_mono_s16(&s16, sr));
let rms = (out.iter().map(|x| x * x).sum::<f32>() / out.len().max(1) as f32).sqrt();
assert!(
rms > 0.1,
"S16 input produced near-silent output (rms={rms}); encoder ignored af.format"
);
}
#[test]
fn registers_as_audio_codec() {
let mut reg = CodecRegistry::new();
register(&mut reg);
let codec = reg.by_name("mp3").expect("mp3 registered");
assert_eq!(codec.id, CodecId::Mp3);
assert_eq!(codec.media_type, MediaType::Audio);
assert!(codec.can_decode() && codec.can_encode());
}
}