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