035e89df5dc3ddb73c4ba0d32163775a49b5bf4c
[nihav.git] / nihav-codec-support / src / test / dec_video.rs
1 //! Routines for testing decoders.
2 use std::fs::File;
3 use nihav_core::frame::*;
4 use nihav_core::codecs::*;
5 use nihav_core::demuxers::*;
6 //use nihav_core::io::byteio::*;
7 //use nihav_core::scale::*;
8 use nihav_core::reorder::MTFrameReorderer;
9 use super::wavwriter::WavWriter;
10 use super::md5::MD5;
11 use crate::imgwrite::write_pnm;
12 pub use super::ExpectedTestResult;
13
14 const OUTPUT_PREFIX: &str = "assets/test_out/";
15
16 /*fn open_wav_out(pfx: &str, strno: usize) -> WavWriter {
17 let name = format!("assets/{}out{:02}.wav", pfx, strno);
18 let mut file = File::create(name).unwrap();
19 let mut fw = FileWriter::new_write(&mut file);
20 let mut wr = ByteWriter::new(&mut fw);
21 WavWriter::new(&mut wr)
22 }*/
23
24 /// Tests decoding of provided file and optionally outputs video frames as PNM (PPM for RGB video, PGM for YUV).
25 ///
26 /// This function expects the following arguments:
27 /// * `demuxer` - container format name (used to find proper demuxer for it)
28 /// * `name` - input file name
29 /// * `limit` - optional PTS value after which decoding is stopped
30 /// * `decode_video`/`decode_audio` - flags for enabling video/audio decoding
31 /// * `video_pfx` - prefix for video frames written as pictures (if enabled then output picture names should look like `<crate_name>/assets/test_out/PFXout00_000000.ppm`
32 /// * `dmx_reg` and `dec_reg` - registered demuxers and decoders that should contain demuxer and decoder(s) needed to decode the provided file.
33 ///
34 /// Since the function is intended for tests, it will panic instead of returning an error.
35 pub fn test_file_decoding(demuxer: &str, name: &str, limit: Option<u64>,
36 decode_video: bool, decode_audio: bool,
37 video_pfx: Option<&str>,
38 dmx_reg: &RegisteredDemuxers, dec_reg: &RegisteredDecoders) {
39 let dmx_f = dmx_reg.find_demuxer(demuxer).unwrap();
40 let mut file = File::open(name).unwrap();
41 let mut fr = FileReader::new_read(&mut file);
42 let mut br = ByteReader::new(&mut fr);
43 let mut dmx = create_demuxer(dmx_f, &mut br).unwrap();
44
45 let mut decs: Vec<Option<(Box<NADecoderSupport>, Box<dyn NADecoder>)>> = Vec::new();
46 for i in 0..dmx.get_num_streams() {
47 let s = dmx.get_stream(i).unwrap();
48 let info = s.get_info();
49 let decfunc = dec_reg.find_decoder(info.get_name());
50 if let Some(df) = decfunc {
51 if (decode_video && info.is_video()) || (decode_audio && info.is_audio()) {
52 let mut dec = (df)();
53 let mut dsupp = Box::new(NADecoderSupport::new());
54 dec.init(&mut dsupp, info).unwrap();
55 decs.push(Some((dsupp, dec)));
56 } else {
57 decs.push(None);
58 }
59 } else {
60 decs.push(None);
61 }
62 }
63
64 loop {
65 let pktres = dmx.get_frame();
66 if let Err(e) = pktres {
67 if e == DemuxerError::EOF { break; }
68 panic!("error");
69 }
70 let pkt = pktres.unwrap();
71 let streamno = pkt.get_stream().get_id() as usize;
72 if let Some((ref mut dsupp, ref mut dec)) = decs[streamno] {
73 if let (Some(lim), Some(ppts)) = (limit, pkt.get_pts()) {
74 if ppts > lim { break; }
75 }
76 let frm = dec.decode(dsupp, &pkt).unwrap();
77 if pkt.get_stream().get_info().is_video() && video_pfx.is_some() && frm.get_frame_type() != FrameType::Skip {
78 let pts = if let Some(fpts) = frm.get_pts() { fpts } else { pkt.get_pts().unwrap() };
79 let pfx = OUTPUT_PREFIX.to_owned() + video_pfx.unwrap_or("") + "out";
80 write_pnm(pfx.as_str(), streamno, pts, frm).unwrap();
81 }
82 }
83 }
84 }
85
86 /// Tests audio decoder with the content in the provided file and optionally outputs decoded audio.
87 ///
88 /// The syntax is very similar to [`test_file_decoding`] except that it is intended for testing audio codecs.
89 ///
90 /// Since the function is intended for tests, it will panic instead of returning an error.
91 ///
92 /// [`test_file_decoding`]: ./fn.test_file_decoding.html
93 pub fn test_decode_audio(demuxer: &str, name: &str, limit: Option<u64>, audio_pfx: Option<&str>,
94 dmx_reg: &RegisteredDemuxers, dec_reg: &RegisteredDecoders) {
95 let dmx_f = dmx_reg.find_demuxer(demuxer).unwrap();
96 let mut file = File::open(name).unwrap();
97 let mut fr = FileReader::new_read(&mut file);
98 let mut br = ByteReader::new(&mut fr);
99 let mut dmx = create_demuxer(dmx_f, &mut br).unwrap();
100
101 let mut decs: Vec<Option<(Box<NADecoderSupport>, Box<dyn NADecoder>)>> = Vec::new();
102 for i in 0..dmx.get_num_streams() {
103 let s = dmx.get_stream(i).unwrap();
104 let info = s.get_info();
105 let decfunc = dec_reg.find_decoder(info.get_name());
106 if let Some(df) = decfunc {
107 if info.is_audio() {
108 let mut dec = (df)();
109 let mut dsupp = Box::new(NADecoderSupport::new());
110 dec.init(&mut dsupp, info).unwrap();
111 decs.push(Some((dsupp, dec)));
112 } else {
113 decs.push(None);
114 }
115 } else {
116 decs.push(None);
117 }
118 }
119
120 if let Some(audio_pfx) = audio_pfx {
121 let name = format!("{}/{}out.wav", OUTPUT_PREFIX, audio_pfx);
122 let file = File::create(name).unwrap();
123 let mut fw = FileWriter::new_write(file);
124 let mut wr = ByteWriter::new(&mut fw);
125 let mut wwr = WavWriter::new(&mut wr);
126 let mut wrote_header = false;
127
128 loop {
129 let pktres = dmx.get_frame();
130 if let Err(e) = pktres {
131 if e == DemuxerError::EOF { break; }
132 panic!("error");
133 }
134 let pkt = pktres.unwrap();
135 if limit.is_some() && pkt.get_pts().is_some() && pkt.get_pts().unwrap() > limit.unwrap() {
136 break;
137 }
138 let streamno = pkt.get_stream().get_id() as usize;
139 if let Some((ref mut dsupp, ref mut dec)) = decs[streamno] {
140 let frm = dec.decode(dsupp, &pkt).unwrap();
141 if frm.get_info().is_audio() {
142 if !wrote_header {
143 wwr.write_header(frm.get_info().as_ref().get_properties().get_audio_info().unwrap()).unwrap();
144 wrote_header = true;
145 }
146 wwr.write_frame(frm.get_buffer()).unwrap();
147 }
148 }
149 }
150 } else {
151 loop {
152 let pktres = dmx.get_frame();
153 if let Err(e) = pktres {
154 if e == DemuxerError::EOF { break; }
155 panic!("error");
156 }
157 let pkt = pktres.unwrap();
158 if limit.is_some() && pkt.get_pts().is_some() && pkt.get_pts().unwrap() > limit.unwrap() {
159 break;
160 }
161 let streamno = pkt.get_stream().get_id() as usize;
162 if let Some((ref mut dsupp, ref mut dec)) = decs[streamno] {
163 let _ = dec.decode(dsupp, &pkt).unwrap();
164 }
165 }
166 }
167 }
168
169 fn frame_checksum(md5: &mut MD5, frm: NAFrameRef) {
170 match frm.get_buffer() {
171 NABufferType::Video(ref vb) => {
172 md5.update_hash(vb.get_data());
173 },
174 NABufferType::Video16(ref vb) => {
175 let mut samp = [0u8; 2];
176 let data = vb.get_data();
177 for el in data.iter() {
178 samp[0] = (*el >> 8) as u8;
179 samp[1] = (*el >> 0) as u8;
180 md5.update_hash(&samp);
181 }
182 },
183 NABufferType::Video32(ref vb) => {
184 let mut samp = [0u8; 4];
185 let data = vb.get_data();
186 for el in data.iter() {
187 samp[0] = (*el >> 24) as u8;
188 samp[1] = (*el >> 16) as u8;
189 samp[2] = (*el >> 8) as u8;
190 samp[3] = (*el >> 0) as u8;
191 md5.update_hash(&samp);
192 }
193 },
194 NABufferType::VideoPacked(ref vb) => {
195 md5.update_hash(vb.get_data());
196 },
197 NABufferType::AudioU8(ref ab) => {
198 md5.update_hash(ab.get_data());
199 },
200 NABufferType::AudioI16(ref ab) => {
201 let mut samp = [0u8; 2];
202 let data = ab.get_data();
203 for el in data.iter() {
204 samp[0] = (*el >> 8) as u8;
205 samp[1] = (*el >> 0) as u8;
206 md5.update_hash(&samp);
207 }
208 },
209 NABufferType::AudioI32(ref ab) => {
210 let mut samp = [0u8; 4];
211 let data = ab.get_data();
212 for el in data.iter() {
213 samp[0] = (*el >> 24) as u8;
214 samp[1] = (*el >> 16) as u8;
215 samp[2] = (*el >> 8) as u8;
216 samp[3] = (*el >> 0) as u8;
217 md5.update_hash(&samp);
218 }
219 },
220 NABufferType::AudioF32(ref ab) => {
221 let mut samp = [0u8; 4];
222 let data = ab.get_data();
223 for el in data.iter() {
224 let bits = el.to_bits();
225 samp[0] = (bits >> 24) as u8;
226 samp[1] = (bits >> 16) as u8;
227 samp[2] = (bits >> 8) as u8;
228 samp[3] = (bits >> 0) as u8;
229 md5.update_hash(&samp);
230 }
231 },
232 NABufferType::AudioPacked(ref ab) => {
233 md5.update_hash(ab.get_data());
234 },
235 NABufferType::Data(ref db) => {
236 md5.update_hash(db.as_ref());
237 },
238 NABufferType::None => {},
239 };
240 }
241
242 /// Tests decoder for requested codec in provided file.
243 ///
244 /// This functions tries to decode a stream corresponding to `dec_name` codec in input file and validate the results against expected ones.
245 ///
246 /// Since the function is intended for tests, it will panic instead of returning an error.
247 ///
248 /// # Examples
249 ///
250 /// Test RealVideo 4 decoder in test stream:
251 /// ```no_run
252 /// use nihav_codec_support::test::ExpectedTestResult;
253 /// use nihav_codec_support::test::dec_video::test_decoding;
254 /// use nihav_core::codecs::RegisteredDecoders;
255 /// use nihav_core::demuxers::RegisteredDemuxers;
256 ///
257 /// let mut dmx_reg = RegisteredDemuxers::new();
258 /// let mut dec_reg = RegisteredDecoders::new();
259 /// // ... register RealMedia demuxers and RealVideo decoders ...
260 /// test_decoding("realmedia", "rv40", "assets/test_file.rmvb", None, &dmx_reg, &dec_reg, ExpectedTestResult::MD5([0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f]));
261 /// ```
262 pub fn test_decoding(demuxer: &str, dec_name: &str, filename: &str, limit: Option<u64>,
263 dmx_reg: &RegisteredDemuxers, dec_reg: &RegisteredDecoders,
264 test: ExpectedTestResult) {
265 let dmx_f = dmx_reg.find_demuxer(demuxer).unwrap();
266 let mut file = File::open(filename).unwrap();
267 let mut fr = FileReader::new_read(&mut file);
268 let mut br = ByteReader::new(&mut fr);
269 let mut dmx = create_demuxer(dmx_f, &mut br).unwrap();
270
271 let mut decs: Vec<Option<(Box<NADecoderSupport>, Box<dyn NADecoder>)>> = Vec::new();
272 let mut found = false;
273 for i in 0..dmx.get_num_streams() {
274 let s = dmx.get_stream(i).unwrap();
275 let info = s.get_info();
276 println!("stream {} codec {} / {}", i, info.get_name(), dec_name);
277 if !found && (info.get_name() == dec_name) {
278 let decfunc = dec_reg.find_decoder(info.get_name());
279 if let Some(df) = decfunc {
280 let mut dec = (df)();
281 let mut dsupp = Box::new(NADecoderSupport::new());
282 dec.init(&mut dsupp, info).unwrap();
283 decs.push(Some((dsupp, dec)));
284 found = true;
285 } else {
286 decs.push(None);
287 }
288 } else {
289 decs.push(None);
290 }
291 }
292
293 let mut md5 = MD5::new();
294 let mut frameiter = if let ExpectedTestResult::MD5Frames(ref vec) = test {
295 Some(vec.iter())
296 } else {
297 None
298 };
299 loop {
300 let pktres = dmx.get_frame();
301 if let Err(e) = pktres {
302 if e == DemuxerError::EOF { break; }
303 panic!("error");
304 }
305 let pkt = pktres.unwrap();
306 let streamno = pkt.get_stream().get_id() as usize;
307 if let Some((ref mut dsupp, ref mut dec)) = decs[streamno] {
308 if limit.is_some() && pkt.get_pts().is_some() && pkt.get_pts().unwrap() > limit.unwrap() {
309 break;
310 }
311 let frm = dec.decode(dsupp, &pkt).unwrap();
312 match &test {
313 ExpectedTestResult::Decodes => {},
314 ExpectedTestResult::MD5(_) => { frame_checksum(&mut md5, frm); },
315 ExpectedTestResult::MD5Frames(_) => {
316 md5 = MD5::new();
317 frame_checksum(&mut md5, frm);
318 md5.finish();
319 if let Some(ref mut iter) = frameiter {
320 let ret = iter.next();
321 if ret.is_none() { break; }
322 let ref_hash = ret.unwrap();
323 let mut hash = [0u32; 4];
324 md5.get_hash(&mut hash);
325 println!("frame pts {:?} hash {}", pkt.get_pts(), md5);
326 assert_eq!(&hash, ref_hash);
327 }
328 },
329 ExpectedTestResult::GenerateMD5Frames => {
330 md5 = MD5::new();
331 frame_checksum(&mut md5, frm);
332 md5.finish();
333 let mut hash = [0u32; 4];
334 md5.get_hash(&mut hash);
335 println!("frame pts {:?} hash [0x{:08x}, 0x{:08x}, 0x{:08x}, 0x{:08x}],", pkt.get_pts(), hash[0], hash[1], hash[2], hash[3]);
336 },
337 };
338 }
339 }
340 if let ExpectedTestResult::MD5(ref ref_hash) = test {
341 md5.finish();
342 let mut hash = [0u32; 4];
343 md5.get_hash(&mut hash);
344 println!("full hash {}", md5);
345 assert_eq!(&hash, ref_hash);
346 }
347 if let ExpectedTestResult::GenerateMD5Frames = test {
348 panic!("generated hashes");
349 }
350 }
351
352 const THREADS: usize = 3;
353
354 fn check_frame(frm: NAFrameRef, test: &ExpectedTestResult, glbl_md5: &mut MD5, frameiter: &mut Option<std::slice::Iter<[u32; 4]>>, last_ts: &mut Option<u64>) -> bool {
355 let frm_pts = frm.get_pts();
356 let frm_dts = frm.get_dts();
357 if let (Some(lts), Some(cts)) = (*last_ts, frm_dts) {
358 assert!(lts < cts);
359 }
360 *last_ts = frm_dts;
361 match test {
362 ExpectedTestResult::Decodes => {},
363 ExpectedTestResult::MD5(_) => { frame_checksum(glbl_md5, frm); },
364 ExpectedTestResult::MD5Frames(_) => {
365 let mut loc_md5 = MD5::new();
366 frame_checksum(&mut loc_md5, frm);
367 loc_md5.finish();
368 if let Some(ref mut iter) = frameiter {
369 let ret = iter.next();
370 if ret.is_none() {
371 return true;
372 }
373 let ref_hash = ret.unwrap();
374 let mut hash = [0u32; 4];
375 loc_md5.get_hash(&mut hash);
376 println!("frame pts {:?} dts {:?} hash {}", frm_pts, frm_dts, loc_md5);
377 assert_eq!(&hash, ref_hash);
378 }
379 },
380 ExpectedTestResult::GenerateMD5Frames => {
381 let mut loc_md5 = MD5::new();
382 frame_checksum(&mut loc_md5, frm);
383 loc_md5.finish();
384 let mut hash = [0u32; 4];
385 loc_md5.get_hash(&mut hash);
386 println!("frame pts {:?} dts {:?} hash [0x{:08x}, 0x{:08x}, 0x{:08x}, 0x{:08x}],", frm_pts, frm_dts, hash[0], hash[1], hash[2], hash[3]);
387 },
388 };
389 false
390 }
391
392 /// Tests multi-threaded decoder for requested codec in provided file.
393 ///
394 /// The syntax is very similar to [`test_file_decoding`] except that it tests multi-threaded decoders instead.
395 ///
396 /// [`test_file_decoding`]: ./fn.test_file_decoding.html
397 pub fn test_mt_decoding(demuxer: &str, dec_name: &str, filename: &str, limit: Option<u64>,
398 dmx_reg: &RegisteredDemuxers, dec_reg: &RegisteredMTDecoders,
399 test: ExpectedTestResult) {
400 let mut dec_threads = THREADS;
401 for (key, value) in std::env::vars_os() {
402 if key == "MT_THREADS" {
403 if let Some(val) = value.to_str() {
404 dec_threads = val.parse::<usize>().unwrap_or(THREADS);
405 break;
406 }
407 }
408 }
409
410 let dmx_f = dmx_reg.find_demuxer(demuxer).expect("demuxer is not found");
411 let mut file = File::open(filename).expect("input file should be present");
412 let mut fr = FileReader::new_read(&mut file);
413 let mut br = ByteReader::new(&mut fr);
414 let mut dmx = create_demuxer(dmx_f, &mut br).expect("demuxer can't be created");
415
416 let mut decs: Vec<Option<(Box<NADecoderSupport>, Box<dyn NADecoderMT>)>> = Vec::new();
417 let mut found = false;
418 for i in 0..dmx.get_num_streams() {
419 let s = dmx.get_stream(i).unwrap();
420 let info = s.get_info();
421 println!("stream {} codec {} / {}", i, info.get_name(), dec_name);
422 if !found && (info.get_name() == dec_name) {
423 let decfunc = dec_reg.find_decoder(info.get_name());
424 if let Some(df) = decfunc {
425 let mut dec = (df)();
426 let mut dsupp = Box::new(NADecoderSupport::new());
427 dec.init(&mut dsupp, info, dec_threads).unwrap();
428 decs.push(Some((dsupp, dec)));
429 found = true;
430 } else {
431 decs.push(None);
432 }
433 } else {
434 decs.push(None);
435 }
436 }
437
438 let mut md5 = MD5::new();
439 let mut frameiter = if let ExpectedTestResult::MD5Frames(ref vec) = test {
440 Some(vec.iter())
441 } else {
442 None
443 };
444 let mut reord = MTFrameReorderer::new();
445 let mut last_ts = None;
446 'dec_loop: loop {
447 let pktres = dmx.get_frame();
448 if let Err(e) = pktres {
449 if e == DemuxerError::EOF { break; }
450 panic!("error");
451 }
452 let pkt = pktres.expect("packet");
453 let streamno = pkt.get_stream().get_id() as usize;
454 if let Some((ref mut dsupp, ref mut dec)) = decs[streamno] {
455 if limit.is_some() && pkt.get_pts().is_some() && pkt.get_pts().unwrap() > limit.unwrap() {
456 break;
457 }
458 let queue_id = reord.register_frame();
459 match dec.queue_pkt(dsupp, &pkt, queue_id) {
460 Ok(true) => {},
461 Ok(false) => {
462 while !dec.can_take_input() || dec.has_output() {
463 match dec.get_frame() {
464 (Ok(frm), id) => {
465 reord.add_frame(frm, id);
466 while let Some(nfrm) = reord.get_frame() {
467 if check_frame(nfrm, &test, &mut md5, &mut frameiter, &mut last_ts) {
468 break 'dec_loop;
469 }
470 }
471 },
472 (Err(err), id) => {
473 reord.drop_frame(id);
474 panic!("frame {} decoding error {:?}", id, err);
475 },
476 };
477 }
478 match dec.queue_pkt(dsupp, &pkt, queue_id) {
479 Ok(true) => {},
480 Ok(false) => panic!("still can't queue frame!"),
481 Err(err) => panic!("queueing error {:?}", err),
482 };
483 },
484 Err(err) => panic!("queueing error {:?}", err),
485 };
486
487 }
488 }
489 'tail_loop: for dentry in decs.iter_mut() {
490 if let Some((_, ref mut dec)) = dentry {
491 loop {
492 match dec.get_frame() {
493 (Ok(frm), id) => {
494 reord.add_frame(frm, id);
495 while let Some(nfrm) = reord.get_frame() {
496 if check_frame(nfrm, &test, &mut md5, &mut frameiter, &mut last_ts) {
497 break 'tail_loop;
498 }
499 }
500 },
501 (Err(DecoderError::NoFrame), _) => break,
502 (Err(err), id) => panic!("frame {} decoding error {:?}", id, err),
503 };
504 }
505 while let Some(nfrm) = reord.get_last_frames() {
506 if check_frame(nfrm, &test, &mut md5, &mut frameiter, &mut last_ts) {
507 break;
508 }
509 }
510 }
511 }
512 if let ExpectedTestResult::MD5(ref ref_hash) = test {
513 md5.finish();
514 let mut hash = [0u32; 4];
515 md5.get_hash(&mut hash);
516 println!("full hash {}", md5);
517 assert_eq!(&hash, ref_hash);
518 }
519 if let ExpectedTestResult::GenerateMD5Frames = test {
520 panic!("generated hashes");
521 }
522 }
523
524 /// Tests decoding of provided file by outputting video frames as PNM (PPM for RGB video, PGM for YUV).
525 ///
526 /// This function expects the following arguments:
527 /// * `demuxer` - container format name (used to find proper demuxer for it)
528 /// * `name` - input file name
529 /// * `video_pfx` - prefix for video frames written as pictures (output picture names should look like `<crate_name>/assets/test_out/PFXout00_000000.ppm`
530 /// * `limit` - optional PTS value after which decoding is stopped
531 /// * `dmx_reg` and `dec_reg` - registered demuxers and decoders that should contain demuxer and decoder(s) needed to decode the provided file.
532 ///
533 /// Since the function is intended for tests, it will panic instead of returning an error.
534 pub fn test_decode_images(demuxer: &str, name: &str, video_pfx: &str, limit: Option<u64>,
535 dmx_reg: &RegisteredDemuxers, dec_reg: &RegisteredDecoders) {
536 let dmx_f = dmx_reg.find_demuxer(demuxer).unwrap();
537 let mut file = File::open(name).unwrap();
538 let mut fr = FileReader::new_read(&mut file);
539 let mut br = ByteReader::new(&mut fr);
540 let mut dmx = create_demuxer(dmx_f, &mut br).unwrap();
541
542 let mut decs: Vec<Option<(Box<NADecoderSupport>, Box<dyn NADecoder>)>> = Vec::new();
543 for i in 0..dmx.get_num_streams() {
544 let s = dmx.get_stream(i).unwrap();
545 let info = s.get_info();
546 let decfunc = dec_reg.find_decoder(info.get_name());
547 if let Some(df) = decfunc {
548 if info.is_video() {
549 let mut dec = (df)();
550 let mut dsupp = Box::new(NADecoderSupport::new());
551 dec.init(&mut dsupp, info).unwrap();
552 decs.push(Some((dsupp, dec)));
553 break;
554 } else {
555 decs.push(None);
556 }
557 } else {
558 decs.push(None);
559 }
560 }
561
562 loop {
563 let pktres = dmx.get_frame();
564 if let Err(e) = pktres {
565 if e == DemuxerError::EOF { break; }
566 panic!("error");
567 }
568 let pkt = pktres.unwrap();
569 let streamno = pkt.get_stream().get_id() as usize;
570 if streamno >= decs.len() { continue; }
571 if let Some((ref mut dsupp, ref mut dec)) = decs[streamno] {
572 if let (Some(lim), Some(ppts)) = (limit, pkt.get_pts()) {
573 if ppts > lim { break; }
574 }
575 let frm = dec.decode(dsupp, &pkt).unwrap();
576 if frm.get_frame_type() != FrameType::Skip {
577 let pts = if let Some(fpts) = frm.get_pts() { fpts } else { pkt.get_pts().unwrap() };
578 let pfx = OUTPUT_PREFIX.to_owned() + video_pfx + "out";
579 write_pnm(pfx.as_str(), streamno, pts, frm).unwrap();
580 }
581 }
582 }
583 }