Commit | Line | Data |
---|---|---|
c17769db KS |
1 | use nihav_core::frame::*; |
2 | use nihav_core::demuxers::*; | |
3 | ||
4 | struct SmushDemuxer<'a> { | |
5 | src: &'a mut ByteReader<'a>, | |
6 | old: bool, | |
7 | size: u64, | |
8 | ||
9 | nframes: usize, | |
10 | chunks: Vec<u8>, | |
11 | ||
12 | keyframe: bool, | |
13 | cur_frame: usize, | |
14 | frme_end: u64, | |
15 | asize: u64, | |
16 | } | |
17 | ||
18 | fn parse_iact(br: &mut ByteReader, end: u64, arate: &mut u32, abits: &mut u8, chans: &mut u8) -> DemuxerResult<()> { | |
19 | br.read_skip(14)?; | |
20 | let tag = br.read_tag()?; | |
21 | if &tag != b"iMUS" { | |
22 | *arate = 22050; | |
23 | *abits = 16; | |
24 | *chans = 2; | |
25 | return Ok(()); | |
26 | } | |
27 | br.read_skip(4)?; | |
28 | while br.tell() < end { | |
29 | let tag = br.read_tag()?; | |
30 | let size = u64::from(br.read_u32be()?); | |
31 | match &tag { | |
32 | b"MAP " => { | |
33 | let cend = br.tell() + size; | |
34 | while br.tell() < cend { | |
35 | let tag = br.read_tag()?; | |
36 | let size = u64::from(br.read_u32be()?); | |
37 | match &tag { | |
38 | b"FRMT" => { | |
39 | validate!(size == 20); | |
40 | br.read_u32be()?; | |
41 | br.read_u32be()?; | |
42 | let bits = br.read_u32be()?; | |
43 | validate!(bits > 0 && bits <= 16); | |
44 | *abits = bits as u8; | |
45 | *arate = br.read_u32be()?; | |
46 | let c = br.read_u32be()?; | |
47 | validate!(c == 1 || c == 2); | |
48 | *chans = c as u8; | |
49 | return Ok(()); | |
50 | }, | |
51 | _ => br.read_skip(size as usize)?, | |
52 | }; | |
53 | } | |
54 | }, | |
55 | b"DATA" => return Err(DemuxerError::InvalidData), | |
56 | _ => br.read_skip(size as usize)?, | |
57 | }; | |
58 | } | |
59 | Err(DemuxerError::InvalidData) | |
60 | } | |
61 | ||
62 | impl<'a> SmushDemuxer<'a> { | |
63 | fn new(io: &'a mut ByteReader<'a>) -> Self { | |
64 | SmushDemuxer { | |
65 | src: io, | |
66 | ||
67 | old: false, | |
68 | size: 0, | |
69 | ||
70 | nframes: 0, | |
71 | chunks: Vec::new(), | |
72 | ||
73 | keyframe: false, | |
74 | cur_frame: 0, | |
75 | frme_end: 0, | |
76 | asize: 0, | |
77 | } | |
78 | } | |
79 | fn parse_anim_header(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<()> { | |
80 | let src = &mut self.src; | |
81 | ||
82 | let tag = src.read_tag()?; | |
83 | validate!(&tag == b"AHDR"); | |
84 | let size = u64::from(src.read_u32be()?); | |
85 | validate!(size >= 768 + 6); | |
86 | let end = src.tell() + size; | |
87 | validate!(end < self.size); | |
88 | let version = src.read_u16le()?; | |
89 | validate!(version < 3); | |
90 | self.nframes = src.read_u16le()? as usize; | |
91 | validate!(self.nframes != 0); | |
92 | src.read_skip(2)?; //max FRME size | |
93 | let mut edata = vec![0; 768 + 1]; | |
94 | edata[0] = version as u8; | |
95 | src.read_buf(&mut edata[1..][..768])?; | |
96 | src.read_skip(size as usize - 768 - 6)?; | |
97 | ||
98 | let start = src.tell(); | |
99 | let mut size = 0; | |
100 | while size == 0 { | |
101 | let tag = src.read_tag()?; | |
102 | validate!(&tag == b"FRME"); | |
103 | size = u64::from(src.read_u32be()?); | |
104 | } | |
105 | ||
106 | let end = src.tell() + size; | |
107 | validate!(end <= self.size + 8); // some NUTs feature slightly incorrect total size | |
108 | ||
109 | let mut width = 0; | |
110 | let mut height = 0; | |
111 | let mut aname = ""; | |
112 | let mut arate = 0; | |
113 | let mut abits = 0; | |
114 | let mut chans = 0; | |
115 | ||
116 | while src.tell() < end { | |
117 | let tag = src.read_tag()?; | |
118 | let size = u64::from(src.read_u32be()?); | |
119 | ||
120 | let tend = src.tell() + size; | |
121 | validate!(tend <= end); | |
122 | match &tag { | |
123 | b"FOBJ" => { | |
124 | validate!(size >= 10); | |
125 | let _codec = src.read_u16le()?; | |
126 | let x = src.read_u16le()? as i16; | |
127 | let y = src.read_u16le()? as i16; | |
128 | if x == 0 && y == 0 && width == 0 && height == 0 { | |
129 | width = src.read_u16le()? as usize; | |
130 | height = src.read_u16le()? as usize; | |
131 | } else { | |
132 | let w = src.read_u16le()? as usize; | |
133 | let h = src.read_u16le()? as usize; | |
134 | if x == 0 && y == 0 && w >= width && h >= height { | |
135 | width = w; | |
136 | height = h; | |
137 | } | |
138 | } | |
139 | src.read_skip((size - 10) as usize)?; | |
140 | }, | |
141 | b"IACT" => { | |
142 | validate!(size > 8); | |
143 | let end = src.tell() + size; | |
144 | let opcode = src.read_u16le()?; | |
145 | let flags = src.read_u16le()?; | |
146 | if (opcode == 8) && (flags == 0x2E) { | |
147 | if parse_iact(src, end, &mut arate, &mut abits, &mut chans).is_ok() { | |
148 | aname = "smush-iact"; | |
149 | } | |
150 | validate!(src.tell() <= end); | |
151 | } | |
152 | src.seek(SeekFrom::Start(end))?; | |
153 | }, | |
154 | b"PSAD" => { | |
155 | aname = "pcm"; | |
156 | arate = 11025; | |
157 | abits = 8; | |
158 | chans = 2; | |
159 | src.read_skip(size as usize)?; | |
160 | }, | |
161 | _ => { src.read_skip(size as usize)?; }, | |
162 | }; | |
163 | if (src.tell() & 1) != 0 { | |
164 | if let Ok(0) = src.peek_byte() { | |
165 | src.read_skip(1)?; | |
166 | } | |
167 | } | |
168 | } | |
169 | // hack | |
170 | width = width.max(320); | |
171 | height = height.max(200); | |
172 | src.seek(SeekFrom::Start(start))?; | |
173 | self.frme_end = start; | |
174 | ||
175 | let vhdr = NAVideoInfo::new(width, height, false, PAL8_FORMAT); | |
176 | let vci = NACodecTypeInfo::Video(vhdr); | |
177 | let vinfo = NACodecInfo::new("smushv1", vci, Some(edata)); | |
178 | if strmgr.add_stream(NAStream::new(StreamType::Video, 0, vinfo, 1, 15, self.nframes as u64)).is_none() { | |
179 | return Err(DemuxerError::MemoryError); | |
180 | } | |
181 | ||
182 | if !aname.is_empty() { | |
183 | validate!(arate > 0); | |
184 | let mut fmt = SND_S16_FORMAT; | |
185 | match aname { | |
186 | "pcm" => { fmt = SND_U8_FORMAT; }, | |
187 | "smush-iact" => { fmt.bits = abits; fmt.packed = true; }, | |
188 | _ => {}, | |
189 | }; | |
190 | let ahdr = NAAudioInfo::new(arate, chans, fmt, 0); | |
191 | let ainfo = NACodecInfo::new(aname, NACodecTypeInfo::Audio(ahdr), None); | |
192 | if strmgr.add_stream(NAStream::new(StreamType::Audio, 1, ainfo, 1, arate, 0)).is_none() { | |
193 | return Err(DemuxerError::MemoryError); | |
194 | } | |
195 | } | |
196 | ||
197 | Ok(()) | |
198 | } | |
199 | fn parse_sanm_header(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<()> { | |
200 | let src = &mut self.src; | |
201 | ||
202 | let tag = src.read_tag()?; | |
203 | validate!(&tag == b"SHDR"); | |
204 | let size = u64::from(src.read_u32be()?); | |
205 | validate!(src.tell() + size <= self.size); | |
206 | validate!(size >= 0x426); | |
207 | ||
208 | let maj_ver = src.read_byte()?; | |
209 | let min_ver = src.read_byte()?; | |
210 | if maj_ver != 1 || min_ver != 0 { | |
211 | return Err(DemuxerError::NotImplemented); | |
212 | } | |
213 | self.nframes = src.read_u16le()? as usize; | |
214 | let _xoff = src.read_u16le()? as usize; | |
215 | let _yoff = src.read_u16le()? as usize; | |
216 | let width = src.read_u16le()? as usize; | |
217 | let height = src.read_u16le()? as usize; | |
218 | let _imgtype = src.read_u16le()?; | |
219 | let frame_delay = src.read_u32le()?; | |
220 | let _max_frame_size = src.read_u32le()?; | |
221 | src.read_skip(1024)?; // palette | |
222 | src.read_skip((size as usize) - 0x416)?; | |
223 | ||
224 | let tag = src.read_tag()?; | |
225 | validate!(&tag == b"FLHD"); | |
226 | let size = u64::from(src.read_u32be()?); | |
227 | let end = src.tell() + size; | |
228 | ||
229 | let mut arate = 0; | |
230 | let mut chans = 0; | |
231 | let mut alen = 0; | |
232 | while src.tell() < end { | |
233 | let tag = src.read_tag()?; | |
234 | if src.tell() == end { break; } | |
235 | let size = src.read_u32be()?; | |
236 | match &tag { | |
237 | b"Wave" => { | |
238 | validate!(size >= 8); | |
239 | arate = src.read_u32le()?; | |
240 | let cc = src.read_u32le()?; | |
241 | validate!(cc == 1 || cc == 2); | |
242 | chans = cc as u8; | |
243 | if size >= 12 { | |
244 | alen = u64::from(src.read_u32le()? / cc / 2); | |
245 | src.read_skip((size as usize) - 12)?; | |
246 | } | |
247 | }, | |
248 | _ => src.read_skip(size as usize)?, | |
249 | }; | |
250 | } | |
251 | validate!(src.tell() == end); | |
252 | ||
253 | let vhdr = NAVideoInfo::new(width, height, false, RGB565_FORMAT); | |
254 | let vci = NACodecTypeInfo::Video(vhdr); | |
255 | let vinfo = NACodecInfo::new("smushv2", vci, None); | |
256 | if strmgr.add_stream(NAStream::new(StreamType::Video, 0, vinfo, frame_delay, 1000000, self.nframes as u64)).is_none() { | |
257 | return Err(DemuxerError::MemoryError); | |
258 | } | |
259 | if arate != 0 { | |
260 | let ahdr = NAAudioInfo::new(arate, chans, SND_S16P_FORMAT, 0); | |
261 | let ainfo = NACodecInfo::new("smush-vima", NACodecTypeInfo::Audio(ahdr), None); | |
262 | if strmgr.add_stream(NAStream::new(StreamType::Audio, 1, ainfo, 1, arate, alen)).is_none() { | |
263 | return Err(DemuxerError::MemoryError); | |
264 | } | |
265 | } | |
266 | ||
267 | Ok(()) | |
268 | } | |
269 | ||
270 | fn queue_chunk(&mut self, tag: [u8; 4], size: usize) -> DemuxerResult<()> { | |
271 | self.chunks.extend_from_slice(&tag); | |
272 | let start = self.chunks.len(); | |
273 | let nlen = start + size + 4; | |
274 | self.chunks.resize(nlen, 0); | |
275 | write_u32be(&mut self.chunks[start..], size as u32).unwrap(); | |
276 | self.src.read_buf(&mut self.chunks[start + 4..])?; | |
277 | Ok(()) | |
278 | } | |
279 | fn get_frame_anim(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<NAPacket> { | |
280 | loop { | |
281 | if self.src.tell() >= self.frme_end { | |
282 | if !self.chunks.is_empty() { | |
283 | let mut buf = Vec::new(); | |
284 | std::mem::swap(&mut self.chunks, &mut buf); | |
285 | let stream = strmgr.get_stream(0).unwrap(); | |
286 | let (tb_num, tb_den) = stream.get_timebase(); | |
287 | let ts = NATimeInfo::new(Some(self.cur_frame as u64 - 1), None, None, tb_num, tb_den); | |
288 | return Ok(NAPacket::new(stream, ts, false, buf)); | |
289 | } | |
290 | if self.cur_frame == self.nframes { | |
291 | return Err(DemuxerError::EOF); | |
292 | } | |
293 | let tag = self.src.read_tag()?; | |
294 | validate!(&tag == b"FRME"); | |
295 | let size = u64::from(self.src.read_u32be()?); | |
296 | self.frme_end = self.src.tell() + size; | |
297 | ||
298 | self.chunks.clear(); | |
299 | self.cur_frame += 1; | |
300 | if size == 0 { | |
301 | continue; | |
302 | } | |
303 | } | |
304 | let tag = self.src.read_tag()?; | |
305 | let size = u64::from(self.src.read_u32be()?); | |
306 | let tend = self.src.tell() + size; | |
307 | validate!(tend <= self.frme_end); | |
308 | match &tag { | |
309 | b"STOR" | b"FTCH" | b"NPAL" | b"XPAL" | b"FOBJ" => { | |
310 | self.queue_chunk(tag, size as usize)?; | |
311 | }, | |
312 | b"IACT" => { | |
313 | validate!(size >= 4); | |
314 | let opcode = self.src.read_u16le()?; | |
315 | let flags = self.src.read_u16le()?; | |
316 | if (opcode == 8) && (flags == 0x2E) { | |
317 | if let Some(stream) = strmgr.get_stream(1) { | |
318 | let (tb_num, tb_den) = stream.get_timebase(); | |
319 | let ts = NATimeInfo::new(None, None, None, tb_num, tb_den); | |
320 | ||
321 | let mut buf = vec![0; size as usize]; | |
322 | write_u16le(&mut buf[0..2], opcode).unwrap(); | |
323 | write_u16le(&mut buf[2..4], flags).unwrap(); | |
324 | self.src.read_buf(&mut buf[4..])?; | |
325 | ||
326 | if (self.src.tell() & 1) == 1 { | |
327 | if let Ok(0) = self.src.peek_byte() { | |
328 | self.src.read_skip(1)?; | |
329 | } | |
330 | } | |
331 | return Ok(NAPacket::new(stream, ts, true, buf)); | |
332 | } | |
333 | } | |
334 | self.src.read_skip((size as usize) - 4)?; | |
335 | }, | |
336 | b"PSAD" => { | |
337 | if size > 0x30 { | |
338 | self.src.read_skip(0x30)?; | |
339 | if let Some(stream) = strmgr.get_stream(1) { | |
340 | let (tb_num, tb_den) = stream.get_timebase(); | |
341 | ||
342 | let audio_size = size - 0x30; | |
343 | let ts = NATimeInfo::new(Some(self.asize), None, None, tb_num, tb_den); | |
344 | let pkt = self.src.read_packet(stream, ts, true, audio_size as usize)?; | |
345 | self.asize += audio_size; | |
346 | if (self.src.tell() & 1) == 1 { | |
347 | if let Ok(0) = self.src.peek_byte() { | |
348 | self.src.read_skip(1)?; | |
349 | } | |
350 | } | |
351 | return Ok(pkt); | |
352 | } else { | |
353 | self.src.read_skip((size - 0x30) as usize)?; | |
354 | } | |
355 | } else { | |
356 | self.src.read_skip(size as usize)?; | |
357 | } | |
358 | }, | |
359 | _ => { | |
360 | self.src.read_skip(size as usize)?; | |
361 | }, | |
362 | }; | |
363 | if (self.src.tell() & 1) == 1 { | |
364 | if let Ok(0) = self.src.peek_byte() { | |
365 | self.src.read_skip(1)?; | |
366 | } | |
367 | } | |
368 | } | |
369 | } | |
370 | fn get_frame_sanm(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<NAPacket> { | |
371 | loop { | |
372 | if self.src.tell() >= self.frme_end { | |
373 | if !self.chunks.is_empty() { | |
374 | let mut buf = Vec::new(); | |
375 | std::mem::swap(&mut self.chunks, &mut buf); | |
376 | let stream = strmgr.get_stream(0).unwrap(); | |
377 | let (tb_num, tb_den) = stream.get_timebase(); | |
378 | let ts = NATimeInfo::new(Some(self.cur_frame as u64 - 1), None, None, tb_num, tb_den); | |
379 | return Ok(NAPacket::new(stream, ts, self.keyframe, buf)); | |
380 | } | |
381 | if self.cur_frame == self.nframes { | |
382 | return Err(DemuxerError::EOF); | |
383 | } | |
384 | let tag = self.src.read_tag()?; | |
385 | let size = u64::from(self.src.read_u32be()?); | |
386 | self.frme_end = self.src.tell() + size; | |
387 | match &tag { | |
388 | b"FLHD" => { self.keyframe = true; }, | |
389 | b"FRME" => { self.keyframe = false; }, | |
390 | _ => { | |
391 | self.src.read_skip(size as usize)?; | |
392 | continue; | |
393 | }, | |
394 | }; | |
395 | ||
396 | self.chunks.clear(); | |
397 | self.cur_frame += 1; | |
398 | if size == 0 { | |
399 | continue; | |
400 | } | |
401 | } | |
402 | let tag = self.src.read_tag()?; | |
403 | if self.src.tell() >= self.frme_end { // happens after some Wave tags | |
404 | continue; | |
405 | } | |
406 | let size = u64::from(self.src.read_u32be()?); | |
407 | let tend = self.src.tell() + size; | |
408 | validate!(tend <= self.frme_end); | |
409 | match &tag { | |
410 | b"Bl16" => { | |
411 | self.queue_chunk(tag, size as usize)?; | |
412 | }, | |
413 | b"Wave" => { | |
414 | if let Some(stream) = strmgr.get_stream(1) { | |
415 | let mut buf = [0; 12]; | |
416 | let mut nsamples = 0; | |
417 | if size >= 12 { | |
418 | self.src.peek_buf(&mut buf)?; | |
419 | nsamples = read_u32be(&buf[0..])?; | |
420 | if nsamples == 0xFFFFFFFF { | |
421 | nsamples = read_u32be(&buf[8..])?; | |
422 | } | |
423 | } | |
424 | ||
425 | let (tb_num, tb_den) = stream.get_timebase(); | |
426 | let mut ts = NATimeInfo::new(None, None, None, tb_num, tb_den); | |
427 | if nsamples != 0 { | |
428 | ts.pts = Some(self.asize); | |
429 | self.asize += u64::from(nsamples); | |
430 | } | |
431 | let pkt = self.src.read_packet(stream, ts, true, size as usize)?; | |
432 | return Ok(pkt); | |
433 | } else { | |
434 | self.src.read_skip(size as usize)?; | |
435 | } | |
436 | }, | |
437 | _ => { | |
438 | //println!("unknown tag {}{}{}{} size {:X} @ {:X}", tag[0] as char, tag[1] as char, tag[2] as char, tag[3] as char, size, self.src.tell() - 8); | |
439 | self.src.read_skip(size as usize)?; | |
440 | }, | |
441 | }; | |
442 | } | |
443 | } | |
444 | } | |
445 | ||
446 | impl<'a> DemuxCore<'a> for SmushDemuxer<'a> { | |
447 | fn open(&mut self, strmgr: &mut StreamManager, _seek_index: &mut SeekIndex) -> DemuxerResult<()> { | |
448 | let magic = self.src.read_tag()?; | |
449 | match &magic { | |
450 | b"ANIM" => { | |
451 | self.old = true; | |
452 | }, | |
453 | b"SANM" => { | |
454 | self.old = false; | |
455 | }, | |
456 | _ => return Err(DemuxerError::InvalidData), | |
457 | }; | |
458 | self.size = u64::from(self.src.read_u32be()?); | |
459 | if self.old { | |
460 | self.parse_anim_header(strmgr)?; | |
461 | } else { | |
462 | self.parse_sanm_header(strmgr)?; | |
463 | } | |
464 | ||
465 | self.cur_frame = 0; | |
466 | Ok(()) | |
467 | } | |
468 | ||
469 | fn get_frame(&mut self, strmgr: &mut StreamManager) -> DemuxerResult<NAPacket> { | |
470 | if self.cur_frame > self.nframes { return Err(DemuxerError::EOF); } | |
471 | if self.old { | |
472 | self.get_frame_anim(strmgr) | |
473 | } else { | |
474 | self.get_frame_sanm(strmgr) | |
475 | } | |
476 | } | |
477 | ||
478 | fn seek(&mut self, _time: NATimePoint, _seek_index: &SeekIndex) -> DemuxerResult<()> { | |
479 | Err(DemuxerError::NotPossible) | |
480 | } | |
481 | fn get_duration(&self) -> u64 { 0 } | |
482 | } | |
483 | ||
484 | impl<'a> NAOptionHandler for SmushDemuxer<'a> { | |
485 | fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] } | |
486 | fn set_options(&mut self, _options: &[NAOption]) { } | |
487 | fn query_option_value(&self, _name: &str) -> Option<NAValue> { None } | |
488 | } | |
489 | ||
490 | pub struct SmushDemuxerCreator { } | |
491 | ||
492 | impl DemuxerCreator for SmushDemuxerCreator { | |
493 | fn new_demuxer<'a>(&self, br: &'a mut ByteReader<'a>) -> Box<dyn DemuxCore<'a> + 'a> { | |
494 | Box::new(SmushDemuxer::new(br)) | |
495 | } | |
496 | fn get_name(&self) -> &'static str { "smush" } | |
497 | } | |
498 | ||
499 | #[cfg(test)] | |
500 | mod test { | |
501 | use super::*; | |
502 | use std::fs::File; | |
503 | ||
504 | #[test] | |
505 | fn test_smush_demux_anim_v1() { | |
506 | // sample from Rebel Assault game | |
507 | let mut file = File::open("assets/Game/smush/c1block.anm").unwrap(); | |
508 | let mut fr = FileReader::new_read(&mut file); | |
509 | let mut br = ByteReader::new(&mut fr); | |
510 | let mut dmx = SmushDemuxer::new(&mut br); | |
511 | let mut sm = StreamManager::new(); | |
512 | let mut si = SeekIndex::new(); | |
513 | dmx.open(&mut sm, &mut si).unwrap(); | |
514 | loop { | |
515 | let pktres = dmx.get_frame(&mut sm); | |
516 | if let Err(e) = pktres { | |
517 | if (e as i32) == (DemuxerError::EOF as i32) { break; } | |
518 | panic!("error"); | |
519 | } | |
520 | let pkt = pktres.unwrap(); | |
521 | println!("Got {}", pkt); | |
522 | } | |
523 | } | |
524 | #[test] | |
525 | fn test_smush_demux_anim_v2() { | |
526 | // sample from The Dig | |
527 | let mut file = File::open("assets/Game/smush/PIGOUT.SAN").unwrap(); | |
528 | let mut fr = FileReader::new_read(&mut file); | |
529 | let mut br = ByteReader::new(&mut fr); | |
530 | let mut dmx = SmushDemuxer::new(&mut br); | |
531 | let mut sm = StreamManager::new(); | |
532 | let mut si = SeekIndex::new(); | |
533 | dmx.open(&mut sm, &mut si).unwrap(); | |
534 | loop { | |
535 | let pktres = dmx.get_frame(&mut sm); | |
536 | if let Err(e) = pktres { | |
537 | if (e as i32) == (DemuxerError::EOF as i32) { break; } | |
538 | panic!("error"); | |
539 | } | |
540 | let pkt = pktres.unwrap(); | |
541 | println!("Got {}", pkt); | |
542 | } | |
543 | } | |
544 | #[test] | |
545 | fn test_smush_demux_sanm() { | |
546 | // sample from Grim Fandango | |
547 | let mut file = File::open("assets/Game/smush/lol.snm").unwrap(); | |
548 | let mut fr = FileReader::new_read(&mut file); | |
549 | let mut br = ByteReader::new(&mut fr); | |
550 | let mut dmx = SmushDemuxer::new(&mut br); | |
551 | let mut sm = StreamManager::new(); | |
552 | let mut si = SeekIndex::new(); | |
553 | dmx.open(&mut sm, &mut si).unwrap(); | |
554 | loop { | |
555 | let pktres = dmx.get_frame(&mut sm); | |
556 | if let Err(e) = pktres { | |
557 | if (e as i32) == (DemuxerError::EOF as i32) { break; } | |
558 | panic!("error"); | |
559 | } | |
560 | let pkt = pktres.unwrap(); | |
561 | println!("Got {}", pkt); | |
562 | } | |
563 | } | |
564 | } |