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