]>
Commit | Line | Data |
---|---|---|
e981a888 KS |
1 | use nihav_core::demuxers::*; |
2 | ||
df67d50c | 3 | #[cfg(feature="demuxer_tca")] |
727aa32e | 4 | use super::tca::TCACoreDemuxer; |
df67d50c | 5 | |
e981a888 KS |
6 | const 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 | ||
27 | trait ReadString { | |
28 | fn read_string(&mut self) -> DemuxerResult<Vec<u8>>; | |
29 | } | |
30 | ||
31 | impl<'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 | ||
46 | fn 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 | ||
72 | fn 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 | ||
78 | fn 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)] | |
112 | fn 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 | ||
143 | struct ChunkInfo { | |
144 | offset: u32, | |
145 | vid_size: u32, | |
146 | aud_sizes: Vec<u32>, | |
147 | } | |
148 | ||
149 | enum ReadState { | |
150 | None, | |
151 | Video, | |
152 | Audio(usize), | |
153 | } | |
154 | ||
155 | struct 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 | ||
166 | impl<'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 | ||
217 | impl<'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 | ||
421 | impl<'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 | ||
427 | pub struct ARMovieDemuxerCreator { } | |
428 | ||
429 | impl 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)] | |
441 | mod 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 | } |