1use oxideav_container::{
26 ContainerRegistry, Demuxer, Muxer, ProbeData, ReadSeek, WriteSeek, PROBE_SCORE_EXTENSION,
27};
28use oxideav_core::{
29 CodecId, CodecParameters, Error, MediaType, Packet, Result, SampleFormat, StreamInfo, TimeBase,
30};
31use std::io::{Read, Seek, SeekFrom, Write};
32
33pub(crate) const EXTENSIONS: &[(&str, u32)] = &[
37 ("sln", 8_000),
38 ("slin", 8_000),
39 ("sln8", 8_000),
40 ("slin8", 8_000),
41 ("sln12", 12_000),
42 ("slin12", 12_000),
43 ("sln16", 16_000),
44 ("slin16", 16_000),
45 ("sln24", 24_000),
46 ("slin24", 24_000),
47 ("sln32", 32_000),
48 ("slin32", 32_000),
49 ("sln44", 44_100),
50 ("slin44", 44_100),
51 ("sln48", 48_000),
52 ("slin48", 48_000),
53 ("sln96", 96_000),
54 ("slin96", 96_000),
55 ("sln192", 192_000),
56 ("slin192", 192_000),
57];
58
59pub fn register(reg: &mut ContainerRegistry) {
60 reg.register_demuxer("slin", open_demuxer);
61 reg.register_muxer("slin", open_muxer);
62 for (ext, _) in EXTENSIONS {
63 reg.register_extension(ext, "slin");
64 }
65 reg.register_probe("slin", probe);
66}
67
68pub fn probe(p: &ProbeData) -> u8 {
73 let Some(ext) = p.ext else {
74 return 0;
75 };
76 if sample_rate_for_ext(ext).is_some() {
77 PROBE_SCORE_EXTENSION
78 } else {
79 0
80 }
81}
82
83fn sample_rate_for_ext(ext: &str) -> Option<u32> {
86 let lower = ext.to_ascii_lowercase();
87 EXTENSIONS
88 .iter()
89 .find_map(|(e, sr)| if *e == lower { Some(*sr) } else { None })
90}
91
92const DEFAULT_SAMPLE_RATE: u32 = 8_000;
98
99fn open_demuxer(input: Box<dyn ReadSeek>) -> Result<Box<dyn Demuxer>> {
100 open_demuxer_with_rate(input, DEFAULT_SAMPLE_RATE)
101}
102
103pub fn open_demuxer_with_rate(
107 mut input: Box<dyn ReadSeek>,
108 sample_rate: u32,
109) -> Result<Box<dyn Demuxer>> {
110 if sample_rate == 0 {
111 return Err(Error::invalid("slin demuxer: sample_rate must be > 0"));
112 }
113 let start = input.stream_position()?;
116 let end = input.seek(SeekFrom::End(0))?;
117 input.seek(SeekFrom::Start(start))?;
118 let total_bytes = end.saturating_sub(start);
119
120 let channels: u16 = 1;
121 let block_align: u64 = (SampleFormat::S16.bytes_per_sample() as u64) * channels as u64;
122 let total_samples = total_bytes / block_align;
123 let duration_micros: i64 = if sample_rate > 0 {
124 (total_samples as i128 * 1_000_000 / sample_rate as i128) as i64
125 } else {
126 0
127 };
128
129 let codec_id = CodecId::new("pcm_s16le");
130 let mut params = CodecParameters::audio(codec_id);
131 params.channels = Some(channels);
132 params.sample_rate = Some(sample_rate);
133 params.sample_format = Some(SampleFormat::S16);
134 params.bit_rate = Some(
135 (SampleFormat::S16.bytes_per_sample() as u64) * 8 * (channels as u64) * sample_rate as u64,
136 );
137
138 let time_base = TimeBase::new(1, sample_rate as i64);
139 let stream = StreamInfo {
140 index: 0,
141 time_base,
142 duration: Some(total_samples as i64),
143 start_time: Some(0),
144 params,
145 };
146
147 Ok(Box::new(SlinDemuxer {
148 input,
149 streams: vec![stream],
150 data_start: start,
151 data_end: end,
152 cursor: start,
153 block_align,
154 chunk_frames: 1024,
155 samples_emitted: 0,
156 duration_micros,
157 }))
158}
159
160struct SlinDemuxer {
161 input: Box<dyn ReadSeek>,
162 streams: Vec<StreamInfo>,
163 #[allow(dead_code)]
164 data_start: u64,
165 data_end: u64,
166 cursor: u64,
167 block_align: u64,
168 chunk_frames: u64,
169 samples_emitted: i64,
170 duration_micros: i64,
171}
172
173impl Demuxer for SlinDemuxer {
174 fn format_name(&self) -> &str {
175 "slin"
176 }
177
178 fn streams(&self) -> &[StreamInfo] {
179 &self.streams
180 }
181
182 fn next_packet(&mut self) -> Result<Packet> {
183 if self.cursor >= self.data_end {
184 return Err(Error::Eof);
185 }
186 let remaining = self.data_end - self.cursor;
187 let want_bytes = (self.chunk_frames * self.block_align).min(remaining);
188 let want_bytes = (want_bytes / self.block_align) * self.block_align;
189 if want_bytes == 0 {
190 return Err(Error::Eof);
192 }
193
194 self.input.seek(SeekFrom::Start(self.cursor))?;
195 let mut buf = vec![0u8; want_bytes as usize];
196 self.input.read_exact(&mut buf)?;
197 self.cursor += want_bytes;
198
199 let stream = &self.streams[0];
200 let frames = want_bytes / self.block_align;
201 let pts = self.samples_emitted;
202 self.samples_emitted += frames as i64;
203
204 let mut pkt = Packet::new(0, stream.time_base, buf);
205 pkt.pts = Some(pts);
206 pkt.dts = Some(pts);
207 pkt.duration = Some(frames as i64);
208 pkt.flags.keyframe = true;
209 Ok(pkt)
210 }
211
212 fn duration_micros(&self) -> Option<i64> {
213 if self.duration_micros > 0 {
214 Some(self.duration_micros)
215 } else {
216 None
217 }
218 }
219}
220
221fn open_muxer(output: Box<dyn WriteSeek>, streams: &[StreamInfo]) -> Result<Box<dyn Muxer>> {
224 if streams.len() != 1 {
225 return Err(Error::unsupported("slin supports exactly one audio stream"));
226 }
227 let s = &streams[0];
228 if s.params.media_type != MediaType::Audio {
229 return Err(Error::invalid("slin stream must be audio"));
230 }
231 let fmt = s
232 .params
233 .sample_format
234 .or_else(|| super::pcm::sample_format_for(&s.params.codec_id))
235 .ok_or_else(|| Error::invalid("slin muxer: unknown sample format"))?;
236 if fmt != SampleFormat::S16 {
237 return Err(Error::unsupported(
238 "slin muxer requires S16LE samples (pcm_s16le or slin* codec id)",
239 ));
240 }
241 Ok(Box::new(SlinMuxer {
242 output,
243 header_written: false,
244 trailer_written: false,
245 }))
246}
247
248struct SlinMuxer {
249 output: Box<dyn WriteSeek>,
250 header_written: bool,
251 trailer_written: bool,
252}
253
254impl Muxer for SlinMuxer {
255 fn format_name(&self) -> &str {
256 "slin"
257 }
258
259 fn write_header(&mut self) -> Result<()> {
260 if self.header_written {
264 return Err(Error::other("slin header already written"));
265 }
266 self.header_written = true;
267 Ok(())
268 }
269
270 fn write_packet(&mut self, packet: &Packet) -> Result<()> {
271 if !self.header_written {
272 return Err(Error::other("slin muxer: write_header not called"));
273 }
274 self.output.write_all(&packet.data)?;
275 Ok(())
276 }
277
278 fn write_trailer(&mut self) -> Result<()> {
279 if self.trailer_written {
280 return Ok(());
281 }
282 self.output.flush()?;
283 self.trailer_written = true;
284 Ok(())
285 }
286}