use crate::catalog::hang::CatalogExt;
use crate::container::{Frame, Timestamp};
#[derive(Debug, Clone, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("MP3 frame header must be at least 4 bytes")]
HeaderTooShort,
#[error("missing MP3 frame sync")]
MissingSync,
#[error("reserved MPEG version")]
ReservedVersion,
#[error("not an MPEG Layer III (MP3) frame")]
NotLayer3,
#[error("reserved MP3 sample rate")]
ReservedSampleRate,
}
pub type Result<T> = std::result::Result<T, Error>;
pub struct Config {
pub sample_rate: u32,
pub channel_count: u32,
}
impl Config {
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < 4 {
return Err(Error::HeaderTooShort);
}
if data[0] != 0xFF || (data[1] & 0xE0) != 0xE0 {
return Err(Error::MissingSync);
}
let version = (data[1] >> 3) & 0x03;
let layer = (data[1] >> 1) & 0x03;
if layer != 0b01 {
return Err(Error::NotLayer3);
}
let sr_index = ((data[2] >> 2) & 0x03) as usize;
if sr_index == 0b11 {
return Err(Error::ReservedSampleRate);
}
let sample_rate = match version {
0b11 => [44100, 48000, 32000][sr_index], 0b10 => [22050, 24000, 16000][sr_index], 0b00 => [11025, 12000, 8000][sr_index], _ => return Err(Error::ReservedVersion),
};
let channel_count = if (data[3] >> 6) & 0x03 == 0b11 { 1 } else { 2 };
Ok(Self {
sample_rate,
channel_count,
})
}
}
pub struct Import<E: CatalogExt = ()> {
track: crate::container::Producer<crate::catalog::hang::Container>,
rendition: crate::catalog::AudioTrack<E>,
}
impl<E: CatalogExt> Import<E> {
pub fn new(
track: moq_net::TrackProducer,
catalog: crate::catalog::Producer<E>,
config: Config,
) -> crate::Result<Self> {
let mut audio =
hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Mp3, config.sample_rate, config.channel_count);
audio.container = hang::catalog::Container::Legacy;
tracing::debug!(name = ?track.name(), config = ?audio, "starting track");
let mut rendition = catalog.audio_track(track.name());
rendition.set(audio);
Ok(Self {
track: crate::container::Producer::new(track, crate::catalog::hang::Container::Legacy),
rendition,
})
}
pub fn demand(&self) -> moq_net::TrackDemand {
self.track.track().demand()
}
pub fn finish(&mut self) -> crate::Result<()> {
self.track.finish()?;
Ok(())
}
pub fn seek(&mut self, sequence: u64) -> crate::Result<()> {
self.track.seek(sequence)?;
Ok(())
}
pub fn decode(&mut self, frame: &[u8], pts: Option<Timestamp>) -> crate::Result<()> {
let timestamp = self.rendition.timestamp(pts)?;
self.track.write(Frame {
timestamp,
payload: bytes::Bytes::copy_from_slice(frame),
keyframe: true,
duration: None,
})?;
self.track.finish_group()?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_mpeg1_stereo() {
let header = [0xFF, 0xFB, 0x90, 0x44];
let cfg = Config::parse(&header).unwrap();
assert_eq!(cfg.sample_rate, 44100);
assert_eq!(cfg.channel_count, 2);
}
#[test]
fn parses_mpeg1_mono() {
let header = [0xFF, 0xFB, 0x90, 0xC4];
let cfg = Config::parse(&header).unwrap();
assert_eq!(cfg.channel_count, 1);
}
#[test]
fn parses_mpeg2_sample_rate() {
let header = [0xFF, 0xF3, 0x90, 0x44];
let cfg = Config::parse(&header).unwrap();
assert_eq!(cfg.sample_rate, 22050);
}
#[test]
fn rejects_layer2() {
let header = [0xFF, 0xFD, 0x90, 0x44];
assert!(matches!(Config::parse(&header), Err(Error::NotLayer3)));
}
#[test]
fn rejects_missing_sync() {
assert!(matches!(
Config::parse(&[0x00, 0x00, 0x00, 0x00]),
Err(Error::MissingSync)
));
}
#[test]
fn rejects_short() {
assert!(matches!(Config::parse(&[0xFF, 0xFB]), Err(Error::HeaderTooShort)));
}
}