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