1use crate::catalog::hang::CatalogExt;
9use crate::container::{Frame, Timestamp};
10
11#[derive(Debug, Clone, thiserror::Error)]
13#[non_exhaustive]
14pub enum Error {
15 #[error("MP3 frame header must be at least 4 bytes")]
17 HeaderTooShort,
18
19 #[error("missing MP3 frame sync")]
21 MissingSync,
22
23 #[error("reserved MPEG version")]
25 ReservedVersion,
26
27 #[error("not an MPEG Layer III (MP3) frame")]
30 NotLayer3,
31
32 #[error("reserved MP3 sample rate")]
34 ReservedSampleRate,
35}
36
37pub type Result<T> = std::result::Result<T, Error>;
38
39pub struct Config {
41 pub sample_rate: u32,
43 pub channel_count: u32,
45}
46
47impl Config {
48 pub fn parse(data: &[u8]) -> Result<Self> {
55 if data.len() < 4 {
56 return Err(Error::HeaderTooShort);
57 }
58
59 if data[0] != 0xFF || (data[1] & 0xE0) != 0xE0 {
61 return Err(Error::MissingSync);
62 }
63
64 let version = (data[1] >> 3) & 0x03;
65 let layer = (data[1] >> 1) & 0x03;
66 if layer != 0b01 {
68 return Err(Error::NotLayer3);
69 }
70
71 let sr_index = ((data[2] >> 2) & 0x03) as usize;
72 if sr_index == 0b11 {
73 return Err(Error::ReservedSampleRate);
74 }
75
76 let sample_rate = match version {
77 0b11 => [44100, 48000, 32000][sr_index], 0b10 => [22050, 24000, 16000][sr_index], 0b00 => [11025, 12000, 8000][sr_index], _ => return Err(Error::ReservedVersion),
81 };
82
83 let channel_count = if (data[3] >> 6) & 0x03 == 0b11 { 1 } else { 2 };
85
86 Ok(Self {
87 sample_rate,
88 channel_count,
89 })
90 }
91}
92
93pub struct Import<E: CatalogExt = ()> {
103 track: crate::container::Producer<crate::catalog::hang::Container>,
104 rendition: crate::catalog::AudioTrack<E>,
105}
106
107impl<E: CatalogExt> Import<E> {
108 pub fn new(
110 track: moq_net::TrackProducer,
111 catalog: crate::catalog::Producer<E>,
112 config: Config,
113 ) -> crate::Result<Self> {
114 let mut audio =
115 hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Mp3, config.sample_rate, config.channel_count);
116 audio.container = hang::catalog::Container::Legacy;
117
118 tracing::debug!(name = ?track.name(), config = ?audio, "starting track");
119
120 let mut rendition = catalog.audio_track(track.name());
121 rendition.set(audio);
122
123 Ok(Self {
124 track: crate::container::Producer::new(track, crate::catalog::hang::Container::Legacy),
125 rendition,
126 })
127 }
128
129 pub fn demand(&self) -> moq_net::TrackDemand {
131 self.track.track().demand()
132 }
133
134 pub fn finish(&mut self) -> crate::Result<()> {
136 self.track.finish()?;
137 Ok(())
138 }
139
140 pub fn seek(&mut self, sequence: u64) -> crate::Result<()> {
142 self.track.seek(sequence)?;
143 Ok(())
144 }
145
146 pub fn decode(&mut self, frame: &[u8], pts: Option<Timestamp>) -> crate::Result<()> {
148 let timestamp = self.rendition.timestamp(pts)?;
149 self.track.write(Frame {
150 timestamp,
151 payload: bytes::Bytes::copy_from_slice(frame),
152 keyframe: true,
153 duration: None,
154 })?;
155 self.track.finish_group()?;
156 Ok(())
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn parses_mpeg1_stereo() {
166 let header = [0xFF, 0xFB, 0x90, 0x44];
168 let cfg = Config::parse(&header).unwrap();
169 assert_eq!(cfg.sample_rate, 44100);
170 assert_eq!(cfg.channel_count, 2);
171 }
172
173 #[test]
174 fn parses_mpeg1_mono() {
175 let header = [0xFF, 0xFB, 0x90, 0xC4];
177 let cfg = Config::parse(&header).unwrap();
178 assert_eq!(cfg.channel_count, 1);
179 }
180
181 #[test]
182 fn parses_mpeg2_sample_rate() {
183 let header = [0xFF, 0xF3, 0x90, 0x44];
185 let cfg = Config::parse(&header).unwrap();
186 assert_eq!(cfg.sample_rate, 22050);
187 }
188
189 #[test]
190 fn rejects_layer2() {
191 let header = [0xFF, 0xFD, 0x90, 0x44];
193 assert!(matches!(Config::parse(&header), Err(Error::NotLayer3)));
194 }
195
196 #[test]
197 fn rejects_missing_sync() {
198 assert!(matches!(
199 Config::parse(&[0x00, 0x00, 0x00, 0x00]),
200 Err(Error::MissingSync)
201 ));
202 }
203
204 #[test]
205 fn rejects_short() {
206 assert!(matches!(Config::parse(&[0xFF, 0xFB]), Err(Error::HeaderTooShort)));
207 }
208}