use crate::catalog::hang::CatalogExt;
use crate::container::Frame;
use moq_net::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 {
track: crate::container::Producer<crate::catalog::hang::Container, hang::catalog::AudioConfig>,
}
impl Import {
pub fn new<E: CatalogExt>(
track: moq_net::track::Producer,
reserved: crate::catalog::Reserved<E>,
config: hang::catalog::AudioConfig,
) -> crate::Result<Self> {
tracing::debug!(name = ?track.name(), ?config, "starting track");
let wire = crate::catalog::hang::Container::try_from(&config)?;
let track = reserved.audio(track, wire, config)?;
Ok(Self { track })
}
pub fn demand(&self) -> moq_net::track::Demand {
self.track.track().demand()
}
pub fn flush(&mut self, timestamp: moq_net::Timestamp, now: std::time::Instant) -> crate::Result<()> {
self.track.flush(timestamp, now)
}
pub fn finish(&mut self) -> crate::Result<()> {
self.track.finish()?;
Ok(())
}
pub fn abort(self, err: moq_net::Error) {
self.track.abort(err);
}
pub fn cut(&mut self, end: Option<moq_net::Timestamp>) -> crate::Result<()> {
self.track.cut(end)?;
Ok(())
}
pub fn discontinuity(&mut self) -> crate::Result<()> {
self.track.discontinuity()
}
pub fn seek(&mut self, sequence: u64) -> crate::Result<()> {
self.track.seek(sequence)?;
Ok(())
}
pub fn decode<B: moq_net::IntoBytes>(&mut self, frame: B, pts: Option<Timestamp>) -> crate::Result<()> {
let timestamp = self.track.timestamp(pts)?;
let keyframe = self.track.needs_keyframe();
self.track.write(Frame {
timestamp,
payload: frame.into_bytes(),
keyframe,
duration: None,
})?;
Ok(())
}
}
pub fn config(init: &[u8]) -> crate::Result<hang::catalog::AudioConfig> {
Ok(Config::parse(init)?.into())
}
impl From<Config> for hang::catalog::AudioConfig {
fn from(config: Config) -> Self {
let mut audio =
hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Mp3, config.sample_rate, config.channel_count);
audio.container = hang::catalog::Container::Legacy;
audio
}
}
#[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)));
}
}