]>
Commit | Line | Data |
---|---|---|
1 | use nihav_core::demuxers::*; | |
2 | ||
3 | const VIDEO_CODECS: &[(i32, &str)] = &[ | |
4 | ( 1, "movinglines"), | |
5 | ( 2, "arm_rawvideo"), | |
6 | ( 3, "arm_rawvideo"), | |
7 | ( 5, "arm_rawvideo"), | |
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 | ||
20 | trait ReadString { | |
21 | fn read_string(&mut self) -> DemuxerResult<Vec<u8>>; | |
22 | } | |
23 | ||
24 | impl<'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' || c == 0 { | |
30 | break; | |
31 | } | |
32 | res.push(c); | |
33 | validate!(res.len() < (1 << 10)); // insanity check | |
34 | } | |
35 | Ok(res) | |
36 | } | |
37 | } | |
38 | ||
39 | fn 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 | ||
65 | fn 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 | ||
71 | fn 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)] | |
105 | fn 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 | ||
136 | struct ChunkInfo { | |
137 | offset: u32, | |
138 | vid_size: u32, | |
139 | aud_sizes: Vec<u32>, | |
140 | } | |
141 | ||
142 | enum ReadState { | |
143 | None, | |
144 | Video, | |
145 | Audio(usize), | |
146 | } | |
147 | ||
148 | struct 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 | ||
157 | impl<'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 | ||
206 | impl<'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)?; | |
221 | validate!((video_codec <= 0) || (width > 0 && height > 0)); | |
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 | ||
278 | let mut edata = vec![video_codec as u8, (video_codec >> 8) as u8]; | |
279 | edata.extend_from_slice(&vformat); | |
280 | ||
281 | let vci = NACodecTypeInfo::Video(NAVideoInfo::new(width, height, false, YUV420_FORMAT)); | |
282 | let vinfo = NACodecInfo::new(codec_name, vci, Some(edata)); | |
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)?; | |
299 | let codec_name = if codec_id == 1 { "arm_rawaudio" } else { "unknown" }; | |
300 | let channels = parse_uint(chan)?; | |
301 | validate!(channels > 0 && channels < 16); | |
302 | let edata = fmt.to_owned(); | |
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)); | |
312 | let ainfo = NACodecInfo::new(codec_name, aci, Some(edata)); | |
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 | ||
376 | impl<'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 | ||
382 | pub struct ARMovieDemuxerCreator { } | |
383 | ||
384 | impl 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)] | |
396 | mod 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 | } |