1use crate::catalog::hang::CatalogExt;
9use crate::container::Frame;
10use moq_net::Timestamp;
11
12#[derive(Debug, Clone, thiserror::Error)]
14#[non_exhaustive]
15pub enum Error {
16 #[error("MP3 frame header must be at least 4 bytes")]
18 HeaderTooShort,
19
20 #[error("missing MP3 frame sync")]
22 MissingSync,
23
24 #[error("reserved MPEG version")]
26 ReservedVersion,
27
28 #[error("not an MPEG Layer III (MP3) frame")]
31 NotLayer3,
32
33 #[error("reserved MP3 sample rate")]
35 ReservedSampleRate,
36}
37
38pub type Result<T> = std::result::Result<T, Error>;
39
40pub struct Config {
42 pub sample_rate: u32,
44 pub channel_count: u32,
46}
47
48impl Config {
49 pub fn parse(data: &[u8]) -> Result<Self> {
56 if data.len() < 4 {
57 return Err(Error::HeaderTooShort);
58 }
59
60 if data[0] != 0xFF || (data[1] & 0xE0) != 0xE0 {
62 return Err(Error::MissingSync);
63 }
64
65 let version = (data[1] >> 3) & 0x03;
66 let layer = (data[1] >> 1) & 0x03;
67 if layer != 0b01 {
69 return Err(Error::NotLayer3);
70 }
71
72 let sr_index = ((data[2] >> 2) & 0x03) as usize;
73 if sr_index == 0b11 {
74 return Err(Error::ReservedSampleRate);
75 }
76
77 let sample_rate = match version {
78 0b11 => [44100, 48000, 32000][sr_index], 0b10 => [22050, 24000, 16000][sr_index], 0b00 => [11025, 12000, 8000][sr_index], _ => return Err(Error::ReservedVersion),
82 };
83
84 let channel_count = if (data[3] >> 6) & 0x03 == 0b11 { 1 } else { 2 };
86
87 Ok(Self {
88 sample_rate,
89 channel_count,
90 })
91 }
92}
93
94pub struct Import<E: CatalogExt = ()> {
107 track: crate::container::Producer<crate::catalog::hang::Container>,
108 rendition: crate::catalog::AudioTrack<E>,
109}
110
111impl<E: CatalogExt> Import<E> {
112 pub fn new(
117 track: moq_net::track::Producer,
118 reserved: crate::catalog::Reserved<E>,
119 mut config: hang::catalog::AudioConfig,
120 ) -> crate::Result<Self> {
121 tracing::debug!(name = ?track.name(), ?config, "starting track");
122 config.timeline = Some(reserved.producer().timeline(track.name())?.section());
124 let mut rendition = reserved.audio(track.name());
125 rendition.set(config);
126 Ok(Self {
127 track: reserved
128 .producer()
129 .media_producer(track, crate::catalog::hang::Container::Legacy)?,
130 rendition,
131 })
132 }
133
134 pub fn demand(&self) -> moq_net::track::Demand {
136 self.track.track().demand()
137 }
138
139 pub fn finish(&mut self) -> crate::Result<()> {
141 self.track.finish()?;
142 self.estimate();
143 Ok(())
144 }
145
146 pub fn abort(self, err: moq_net::Error) {
149 self.track.abort(err);
150 }
151
152 fn estimate(&mut self) {
155 self.rendition.estimate(self.track.estimate());
156 }
157
158 pub fn cut(&mut self, end: Option<moq_net::Timestamp>) -> crate::Result<()> {
160 self.track.cut(end)?;
161 self.estimate();
162 Ok(())
163 }
164
165 pub fn seek(&mut self, sequence: u64) -> crate::Result<()> {
167 self.track.seek(sequence)?;
168 self.estimate();
169 Ok(())
170 }
171
172 pub fn decode<B: moq_net::IntoBytes>(&mut self, frame: B, pts: Option<Timestamp>) -> crate::Result<()> {
178 let timestamp = self.rendition.timestamp(pts)?;
179 let keyframe = self.track.needs_keyframe();
182 self.track.write(Frame {
183 timestamp,
184 payload: frame.into_bytes(),
185 keyframe,
186 duration: None,
187 })?;
188 self.estimate();
189 Ok(())
190 }
191}
192
193pub fn config(init: &[u8]) -> crate::Result<hang::catalog::AudioConfig> {
195 Ok(Config::parse(init)?.into())
196}
197
198impl From<Config> for hang::catalog::AudioConfig {
199 fn from(config: Config) -> Self {
201 let mut audio =
202 hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Mp3, config.sample_rate, config.channel_count);
203 audio.container = hang::catalog::Container::Legacy;
204 audio
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn parses_mpeg1_stereo() {
214 let header = [0xFF, 0xFB, 0x90, 0x44];
216 let cfg = Config::parse(&header).unwrap();
217 assert_eq!(cfg.sample_rate, 44100);
218 assert_eq!(cfg.channel_count, 2);
219 }
220
221 #[test]
222 fn parses_mpeg1_mono() {
223 let header = [0xFF, 0xFB, 0x90, 0xC4];
225 let cfg = Config::parse(&header).unwrap();
226 assert_eq!(cfg.channel_count, 1);
227 }
228
229 #[test]
230 fn parses_mpeg2_sample_rate() {
231 let header = [0xFF, 0xF3, 0x90, 0x44];
233 let cfg = Config::parse(&header).unwrap();
234 assert_eq!(cfg.sample_rate, 22050);
235 }
236
237 #[test]
238 fn rejects_layer2() {
239 let header = [0xFF, 0xFD, 0x90, 0x44];
241 assert!(matches!(Config::parse(&header), Err(Error::NotLayer3)));
242 }
243
244 #[test]
245 fn rejects_missing_sync() {
246 assert!(matches!(
247 Config::parse(&[0x00, 0x00, 0x00, 0x00]),
248 Err(Error::MissingSync)
249 ));
250 }
251
252 #[test]
253 fn rejects_short() {
254 assert!(matches!(Config::parse(&[0xFF, 0xFB]), Err(Error::HeaderTooShort)));
255 }
256}