aac: clear M/S flags
[nihav.git] / nihav-acorn / src / demuxers / armovie.rs
1 use nihav_core::demuxers::*;
2
3 const VIDEO_CODECS: &[(i32, &str)] = &[
4 ( 1, "movinglines"),
5 ( 7, "movingblocks"),
6 ( 17, "movingblockshq"),
7 ( 19, "supermovingblocks"),
8 (100, "escape100"),
9 (102, "escape102"),
10 (122, "escape122"),
11 (124, "escape124"),
12 (130, "escape130"),
13 (800, "linepack"),
14 (802, "movie16_3"),
15 ];
16
17 trait ReadString {
18 fn read_string(&mut self) -> DemuxerResult<Vec<u8>>;
19 }
20
21 impl<'a> ReadString for ByteReader<'a> {
22 fn read_string(&mut self) -> DemuxerResult<Vec<u8>> {
23 let mut res = Vec::new();
24 loop {
25 let c = self.read_byte()?;
26 if c == b'\n' {
27 break;
28 }
29 res.push(c);
30 validate!(res.len() < (1 << 10)); // insanity check
31 }
32 Ok(res)
33 }
34 }
35
36 fn parse_int(src: &[u8]) -> DemuxerResult<i32> {
37 let mut val = 0;
38 let mut parsed = false;
39 let mut sign = false;
40 for &c in src.iter() {
41 match c {
42 b'-' if !parsed => { sign = true; },
43 b'-' => return Err(DemuxerError::InvalidData),
44 b'0'..=b'9' => {
45 val = val * 10 + ((c - b'0') as i32);
46 if val > (1 << 27) {
47 return Err(DemuxerError::InvalidData);
48 }
49 parsed = true;
50 },
51 b' ' | b'\t' if !parsed => {},
52 _ => break,
53 }
54 }
55 if parsed {
56 Ok(if !sign { val } else { -val })
57 } else {
58 Err(DemuxerError::InvalidData)
59 }
60 }
61
62 fn parse_uint(src: &[u8]) -> DemuxerResult<u32> {
63 let val = parse_int(src)?;
64 if val < 0 { return Err(DemuxerError::InvalidData); }
65 Ok(val as u32)
66 }
67
68 fn parse_float(src: &[u8]) -> DemuxerResult<f32> {
69 let mut val = 0.0f32;
70 let mut parsed = false;
71 let mut frac_part = 1.0;
72 for &c in src.iter() {
73 match c {
74 b'0'..=b'9' => {
75 if frac_part == 1.0 {
76 val = val * 10.0 + ((c - b'0') as f32);
77 if val > 1000.0 {
78 return Err(DemuxerError::InvalidData);
79 }
80 } else {
81 val += ((c - b'0') as f32) * frac_part;
82 frac_part *= 0.1;
83 }
84 parsed = true;
85 },
86 b'.' if frac_part != 1.0 => return Err(DemuxerError::InvalidData),
87 b'.' => {
88 frac_part = 0.1;
89 },
90 b' ' | b'\t' => {},
91 _ => break,
92 }
93 }
94 if parsed {
95 Ok(val)
96 } else {
97 Err(DemuxerError::InvalidData)
98 }
99 }
100
101 #[allow(clippy::while_let_on_iterator)]
102 fn split_sound_str(string: &[u8]) -> DemuxerResult<Vec<&[u8]>> {
103 let mut start = 0;
104 let mut ret = Vec::new();
105 let mut ref_trk_id = 2;
106
107 let mut iter = string.iter().enumerate();
108 while let Some((pos, &c)) = iter.next() {
109 if c == b'|' {
110 ret.push(&string[start..pos]);
111
112 validate!(pos + 2 < string.len());
113
114 let mut num_end = pos + 2;
115 while let Some((pos2, c)) = iter.next() {
116 if !c.is_ascii_digit() {
117 num_end = pos2 + 1;
118 break;
119 }
120 }
121 let trk_id = parse_uint(&string[pos + 1..num_end])?;
122 validate!(trk_id == ref_trk_id);
123 ref_trk_id += 1;
124 start = num_end;
125 }
126 }
127 if start < string.len() {
128 ret.push(&string[start..]);
129 }
130 Ok(ret)
131 }
132
133 struct ChunkInfo {
134 offset: u32,
135 vid_size: u32,
136 aud_sizes: Vec<u32>,
137 }
138
139 enum ReadState {
140 None,
141 Video,
142 Audio(usize),
143 }
144
145 struct ARMovieDemuxer<'a> {
146 src: &'a mut ByteReader<'a>,
147 chunk_offs: Vec<ChunkInfo>,
148 cur_chunk: usize,
149 state: ReadState,
150 video_id: Option<usize>,
151 audio_ids: Vec<usize>,
152 }
153
154 impl<'a> ARMovieDemuxer<'a> {
155 fn new(src: &'a mut ByteReader<'a>) -> Self {
156 Self {
157 src,
158 chunk_offs: Vec::new(),
159 cur_chunk: 0,
160 state: ReadState::None,
161 video_id: None,
162 audio_ids: Vec::new(),
163 }
164 }
165 fn parse_catalogue(&mut self, offset: u32, num_chunks: usize, even_csize: usize, odd_csize: usize, aud_tracks: usize) -> DemuxerResult<()> {
166 self.src.seek(SeekFrom::Start(u64::from(offset)))?;
167 self.chunk_offs.clear();
168 for i in 0..num_chunks {
169 let cur_chunk_size = if (i & 1) == 0 { even_csize } else { odd_csize };
170
171 let entry = self.src.read_string()?;
172 let comma_pos = entry.iter().position(|&c| c == b',');
173 let semicolon_pos = entry.iter().position(|&c| c == b';');
174 if let (Some(c_pos), Some(sc_pos)) = (comma_pos, semicolon_pos) {
175 validate!(c_pos > 0 && c_pos + 1 < sc_pos);
176 let offset = parse_uint(&entry[..c_pos])?;
177 let vid_size = parse_uint(&entry[c_pos + 1..sc_pos])?;
178 let astring = &entry[sc_pos + 1..];
179 let asizes = split_sound_str(astring)?;
180
181 let mut aud_sizes = Vec::with_capacity(aud_tracks);
182 if aud_tracks > 0 {
183 let aud_size = parse_uint(asizes[0])?;
184 aud_sizes.push(aud_size);
185 }
186 for &aud_entry in asizes.iter().skip(1) {
187 let aud_size = parse_uint(aud_entry)?;
188 aud_sizes.push(aud_size);
189 }
190
191 let tot_size: u32 = vid_size + aud_sizes.iter().sum::<u32>();
192 validate!((tot_size as usize) <= cur_chunk_size);
193 self.chunk_offs.push(ChunkInfo { offset, vid_size, aud_sizes });
194 } else {
195 return Err(DemuxerError::InvalidData);
196 }
197 }
198
199 Ok(())
200 }
201 }
202
203 impl<'a> RawDemuxCore<'a> for ARMovieDemuxer<'a> {
204 #[allow(clippy::neg_cmp_op_on_partial_ord)]
205 fn open(&mut self, strmgr: &mut StreamManager, _seek_index: &mut SeekIndex) -> DemuxerResult<()> {
206 let magic = self.src.read_string()?;
207 validate!(&magic == b"ARMovie");
208 let _name = self.src.read_string()?;
209 let _date_and_copyright = self.src.read_string()?;
210 let _author = self.src.read_string()?;
211
212 let video_id = self.src.read_string()?;
213 let video_codec = parse_int(&video_id)?;
214 let width = self.src.read_string()?;
215 let width = parse_int(&width)?;
216 let height = self.src.read_string()?;
217 let height = parse_int(&height)?;
218 validate!((video_codec <= 0) ^ (width > 0 && height > 0));
219 let width = width as usize;
220 let height = height as usize;
221 let vformat = self.src.read_string()?;
222 let fps = self.src.read_string()?;
223 let fps = parse_float(&fps)?;
224
225 let sound_id = self.src.read_string()?;
226 let sound_ids = split_sound_str(&sound_id)?;
227 let mut num_sound = sound_ids.len();
228 if num_sound == 1 {
229 let sound_codec = parse_int(sound_ids[0])?;
230 if sound_codec < 1 {
231 num_sound = 0;
232 }
233 }
234 let srate = self.src.read_string()?;
235 let srates = split_sound_str(&srate)?;
236 let chan = self.src.read_string()?;
237 let channels = split_sound_str(&chan)?;
238 let sndformat = self.src.read_string()?;
239 let sndformats = split_sound_str(&sndformat)?;
240
241 let frm_per_chunk = self.src.read_string()?;
242 let frm_per_chunk = parse_uint(&frm_per_chunk)? as usize;
243 validate!(frm_per_chunk > 0);
244 let num_chunks = self.src.read_string()?;
245 let num_chunks = parse_uint(&num_chunks)? as usize + 1;
246 let even_chunk_size = self.src.read_string()?;
247 let even_chunk_size = parse_uint(&even_chunk_size)? as usize;
248 let odd_chunk_size = self.src.read_string()?;
249 let odd_chunk_size = parse_uint(&odd_chunk_size)? as usize;
250 let cat_offset = self.src.read_string()?;
251 let cat_offset = parse_uint(&cat_offset)?;
252
253 let _sprite_offset = self.src.read_string()?;
254 let _sprite_size = self.src.read_string()?;
255 let _kf_offset_res = self.src.read_string(); // may be not present for older ARMovies
256
257 self.parse_catalogue(cat_offset, num_chunks, even_chunk_size, odd_chunk_size, num_sound)?;
258
259 let mut stream_id = 0;
260 if video_codec > 0 {
261 let codec_name = if let Some(idx) = VIDEO_CODECS.iter().position(|&(id, _)| id == video_codec) {
262 VIDEO_CODECS[idx].1
263 } else {
264 "unknown"
265 };
266 validate!(fps > 1.0e-4);
267 let mut tbase = fps;
268 let mut tb_num = 1;
269 while tbase.fract() > 1.0e-4 {
270 tb_num *= 10;
271 tbase *= 10.0;
272 }
273 let tb_den = tbase as u32;
274
275 let vci = NACodecTypeInfo::Video(NAVideoInfo::new(width, height, false, YUV420_FORMAT));
276 let vinfo = NACodecInfo::new(codec_name, vci, Some(vformat));
277 let ret = strmgr.add_stream(NAStream::new(StreamType::Video, stream_id, vinfo, tb_num, tb_den, (frm_per_chunk * num_chunks) as u64));
278 if ret.is_some() {
279 stream_id += 1;
280 self.video_id = ret;
281 } else {
282 return Err(DemuxerError::MemoryError);
283 }
284 }
285
286 if num_sound > 0 {
287 validate!(sound_ids.len() == srates.len());
288 validate!(sound_ids.len() == channels.len());
289 validate!(sound_ids.len() == sndformats.len());
290 for ((&id, &sratestr), (&chan, &fmt)) in sound_ids.iter().zip(srates.iter())
291 .zip(channels.iter().zip(sndformats.iter())) {
292 let codec_id = parse_uint(id)?;
293 let codec_name = if codec_id == 1 { "pcm" } else { "unknown" };
294 let channels = parse_uint(chan)?;
295 validate!(channels > 0 && channels < 16);
296 let bits = parse_uint(fmt)?;
297 let mut srate = parse_uint(sratestr)?;
298 if srate > 0 && srate < 1000 { // probably in microseconds instead of Hertz
299 srate = 1000000 / srate;
300 }
301 //println!(" codec id {codec_id} srate {srate} chan {channels} bits {bits}");
302 let fmt = if bits == 8 { SND_U8_FORMAT } else { SND_S16_FORMAT };
303
304 let aci = NACodecTypeInfo::Audio(NAAudioInfo::new(srate, channels as u8, fmt, 0));
305 let ainfo = NACodecInfo::new(codec_name, aci, None);
306 let ret = strmgr.add_stream(NAStream::new(StreamType::Audio, stream_id, ainfo, 1, srate, 0));
307 if let Some(id) = ret {
308 self.audio_ids.push(id);
309 stream_id += 1;
310 } else {
311 return Err(DemuxerError::MemoryError);
312 }
313 }
314 }
315
316 Ok(())
317 }
318
319 fn get_data(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<NARawData> {
320 while self.cur_chunk < self.chunk_offs.len() {
321 let chunk = &self.chunk_offs[self.cur_chunk];
322 match self.state {
323 ReadState::None => {
324 self.src.seek(SeekFrom::Start(u64::from(chunk.offset)))?;
325 self.state = ReadState::Video;
326 }
327 ReadState::Video => {
328 self.state = ReadState::Audio(0);
329 if chunk.vid_size > 0 {
330 validate!(self.video_id.is_some());
331 if let Some(stream) = strmgr.get_stream(self.video_id.unwrap_or(0)) {
332 let mut buf = vec![0; chunk.vid_size as usize];
333 self.src.read_buf(&mut buf)?;
334 return Ok(NARawData::new(stream, buf));
335 } else {
336 return Err(DemuxerError::InvalidData);
337 }
338 }
339 },
340 ReadState::Audio(idx) => {
341 if idx < chunk.aud_sizes.len() {
342 self.state = ReadState::Audio(idx + 1);
343 if chunk.aud_sizes[idx] > 0 {
344 if let Some(stream) = strmgr.get_stream(self.audio_ids[idx]) {
345 let mut buf = vec![0; chunk.aud_sizes[idx] as usize];
346 self.src.read_buf(&mut buf)?;
347 return Ok(NARawData::new(stream, buf));
348 } else {
349 return Err(DemuxerError::InvalidData);
350 }
351 }
352 } else {
353 self.cur_chunk += 1;
354 self.state = ReadState::None;
355 }
356 },
357 }
358 }
359
360 Err(DemuxerError::EOF)
361 }
362
363 fn seek(&mut self, _time: NATimePoint, _seek_index: &SeekIndex) -> DemuxerResult<()> {
364 Err(DemuxerError::NotPossible)
365 }
366 fn get_duration(&self) -> u64 { 0 }
367 }
368
369 impl<'a> NAOptionHandler for ARMovieDemuxer<'a> {
370 fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
371 fn set_options(&mut self, _options: &[NAOption]) { }
372 fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
373 }
374
375 pub struct ARMovieDemuxerCreator { }
376
377 impl RawDemuxerCreator for ARMovieDemuxerCreator {
378 fn new_demuxer<'a>(&self, br: &'a mut ByteReader<'a>) -> Box<dyn RawDemuxCore<'a> + 'a> {
379 Box::new(ARMovieDemuxer::new(br))
380 }
381 fn get_name(&self) -> &'static str { "armovie" }
382 fn check_format(&self, br: &mut ByteReader) -> bool {
383 let mut hdr = [0; 8];
384 br.read_buf(&mut hdr).is_ok() && &hdr == b"ARMovie\n"
385 }
386 }
387
388 #[cfg(test)]
389 mod test {
390 use super::*;
391 use std::fs::File;
392
393 #[test]
394 fn test_armovie_demux() {
395 // a sample from Acorn Replay Demonstration Disc 2
396 let mut file = File::open("assets/Acorn/CHEMSET2").unwrap();
397 let mut fr = FileReader::new_read(&mut file);
398 let mut br = ByteReader::new(&mut fr);
399 let mut dmx = ARMovieDemuxer::new(&mut br);
400 let mut sm = StreamManager::new();
401 let mut si = SeekIndex::new();
402 dmx.open(&mut sm, &mut si).unwrap();
403
404 loop {
405 let pktres = dmx.get_data(&mut sm);
406 if let Err(e) = pktres {
407 if e == DemuxerError::EOF { break; }
408 panic!("error");
409 }
410 let pkt = pktres.unwrap();
411 println!("Got {}", pkt);
412 }
413 }
414 }