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