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 = ()> {
104 track: crate::container::Producer<crate::catalog::hang::Container>,
105 rendition: crate::catalog::AudioTrack<E>,
106}
107
108impl<E: CatalogExt> Import<E> {
109 pub fn new(
114 track: moq_net::track::Producer,
115 reserved: crate::catalog::Reserved<E>,
116 mut config: hang::catalog::AudioConfig,
117 ) -> crate::Result<Self> {
118 tracing::debug!(name = ?track.name(), ?config, "starting track");
119 config.timeline = Some(reserved.producer().timeline(track.name())?.section());
121 let mut rendition = reserved.audio(track.name());
122 rendition.set(config);
123 Ok(Self {
124 track: reserved
125 .producer()
126 .media_producer(track, crate::catalog::hang::Container::Legacy)?,
127 rendition,
128 })
129 }
130
131 pub fn demand(&self) -> moq_net::track::Demand {
133 self.track.track().demand()
134 }
135
136 pub fn finish(&mut self) -> crate::Result<()> {
138 self.rendition.record_group_end(None);
139 self.track.finish()?;
140 Ok(())
141 }
142
143 pub fn abort(self, err: moq_net::Error) {
146 self.track.abort(err);
147 }
148
149 pub fn cut(&mut self, end: Option<moq_net::Timestamp>) -> crate::Result<()> {
151 self.rendition.record_group_end(end);
152 self.track.cut(end)?;
153 Ok(())
154 }
155
156 pub fn seek(&mut self, sequence: u64) -> crate::Result<()> {
158 self.rendition.record_group_end(None);
159 self.track.seek(sequence)?;
160 Ok(())
161 }
162
163 pub fn decode<B: moq_net::IntoBytes>(&mut self, frame: B, pts: Option<Timestamp>) -> crate::Result<()> {
165 let timestamp = self.rendition.timestamp(pts)?;
166 self.rendition.record_group_end(Some(timestamp));
167 let bytes = frame.as_ref().len();
168 self.track.write(Frame {
169 timestamp,
170 payload: frame.into_bytes(),
171 keyframe: true,
172 duration: None,
173 })?;
174 self.track.cut(None)?;
175 self.rendition.record_frame(timestamp, bytes);
176 Ok(())
177 }
178}
179
180pub fn config(init: &[u8]) -> crate::Result<hang::catalog::AudioConfig> {
182 Ok(Config::parse(init)?.into())
183}
184
185impl From<Config> for hang::catalog::AudioConfig {
186 fn from(config: Config) -> Self {
188 let mut audio =
189 hang::catalog::AudioConfig::new(hang::catalog::AudioCodec::Mp3, config.sample_rate, config.channel_count);
190 audio.container = hang::catalog::Container::Legacy;
191 audio
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn parses_mpeg1_stereo() {
201 let header = [0xFF, 0xFB, 0x90, 0x44];
203 let cfg = Config::parse(&header).unwrap();
204 assert_eq!(cfg.sample_rate, 44100);
205 assert_eq!(cfg.channel_count, 2);
206 }
207
208 #[test]
209 fn parses_mpeg1_mono() {
210 let header = [0xFF, 0xFB, 0x90, 0xC4];
212 let cfg = Config::parse(&header).unwrap();
213 assert_eq!(cfg.channel_count, 1);
214 }
215
216 #[test]
217 fn parses_mpeg2_sample_rate() {
218 let header = [0xFF, 0xF3, 0x90, 0x44];
220 let cfg = Config::parse(&header).unwrap();
221 assert_eq!(cfg.sample_rate, 22050);
222 }
223
224 #[test]
225 fn rejects_layer2() {
226 let header = [0xFF, 0xFD, 0x90, 0x44];
228 assert!(matches!(Config::parse(&header), Err(Error::NotLayer3)));
229 }
230
231 #[test]
232 fn rejects_missing_sync() {
233 assert!(matches!(
234 Config::parse(&[0x00, 0x00, 0x00, 0x00]),
235 Err(Error::MissingSync)
236 ));
237 }
238
239 #[test]
240 fn rejects_short() {
241 assert!(matches!(Config::parse(&[0xFF, 0xFB]), Err(Error::HeaderTooShort)));
242 }
243}