1 /* -----------------------------------------------------------------------------
2 The copyright in this software is being made available under the BSD
3 License, included below. No patent rights, trademark rights and/or
4 other Intellectual Property Rights other than the copyrights concerning
5 the Software are granted under this license.
6
7 For any license concerning other Intellectual Property rights than the software,
8 especially patent licenses, a separate Agreement needs to be closed.
9 For more information please contact:
10
11 Fraunhofer Heinrich Hertz Institute
12 Einsteinufer 37
13 10587 Berlin, Germany
14 www.hhi.fraunhofer.de/vvc
15 vvc@hhi.fraunhofer.de
16
17 Copyright (c) 2018-2021, Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V.
18 All rights reserved.
19
20 Redistribution and use in source and binary forms, with or without
21 modification, are permitted provided that the following conditions are met:
22
23 * Redistributions of source code must retain the above copyright notice,
24 this list of conditions and the following disclaimer.
25 * Redistributions in binary form must reproduce the above copyright notice,
26 this list of conditions and the following disclaimer in the documentation
27 and/or other materials provided with the distribution.
28 * Neither the name of Fraunhofer nor the names of its contributors may
29 be used to endorse or promote products derived from this software without
30 specific prior written permission.
31
32 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
33 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
34 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
35 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
36 BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
37 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
38 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
39 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
40 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
42 THE POSSIBILITY OF SUCH DAMAGE.
43
44
45 ------------------------------------------------------------------------------------------- */
46
47 #include <iostream>
48 #include <stdio.h>
49 #include <fstream>
50 #include <string.h>
51 #include <chrono>
52 #include <thread>
53
54 #include "vvdec/vvdec.h"
55 #include "vvdec/version.h"
56
57 #include "CmdLineParser.h"
58
59 #include "vvdecHelper.h"
60 #include "MD5StreamBuf.h"
61
62 #if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
63 #include <io.h>
64 #include <fcntl.h>
65 #endif
66
67 /*! Prototypes */
68 int writeYUVToFile( std::ostream *f, vvdecFrame *frame );
69 int writeYUVToFileInterlaced( std::ostream *f, vvdecFrame *topField, vvdecFrame *botField = nullptr );
70
msgFnc(void *,int level,const char * fmt,va_list args)71 void msgFnc( void *, int level, const char* fmt, va_list args )
72 {
73 vfprintf( level == 1 ? stderr : stdout, fmt, args );
74 }
75
msgFncErr(void *,int,const char * fmt,va_list args)76 void msgFncErr( void *, int, const char* fmt, va_list args )
77 {
78 vfprintf( stderr, fmt, args );
79 }
80
main(int argc,char * argv[])81 int main( int argc, char* argv[] )
82 {
83 std::string cAppname = argv[0];
84 std::size_t iPos = (int)cAppname.find_last_of("/");
85 if( std::string::npos != iPos )
86 {
87 cAppname = cAppname.substr(iPos+1 );
88 }
89 else
90 {
91 iPos = (int)cAppname.find_last_of("\\");
92 if( std::string::npos != iPos )
93 {
94 cAppname = cAppname.substr(iPos+1 );
95 }
96 }
97
98 std::string cBitstreamFile = "";
99 std::string cOutputFile = "";
100 int iMaxFrames = -1;
101 int iLoopCount = 1;
102 std::string cExpectedYuvMD5;
103 vvdecParams params;
104 vvdec_params_default(¶ms);
105
106 params.logLevel = VVDEC_INFO;
107
108 if( argc > 1 && (!strcmp( (const char*) argv[1], "--help" ) || !strcmp( (const char*) argv[1], "-help" )) )
109 {
110 vvdecoderapp::CmdLineParser::print_usage( cAppname, params );
111 return 0;
112 }
113
114 int iRet = vvdecoderapp::CmdLineParser::parse_command_line( argc, argv, params, cBitstreamFile, cOutputFile, iMaxFrames, iLoopCount, cExpectedYuvMD5 );
115 if( iRet != 0 )
116 {
117 if( iRet == 2 )
118 {
119 vvdecoderapp::CmdLineParser::print_usage( cAppname, params);
120 return 0;
121 }
122 else if( iRet == 3 )
123 {
124 std::cout << cAppname << " version " << vvdec_get_version()<< std::endl;
125 return 0;
126 }
127
128 std::cerr << "vvdecapp [error]: cannot parse command line. run vvdecapp --help to see available options" << std::endl;
129 return -1;
130 }
131
132 if( cBitstreamFile.empty() )
133 {
134 std::cerr << "vvdecapp [error]: no bitstream file given. run vvdecapp --help to see available options" << std::endl;
135 return -1;
136 }
137
138 // open input file
139 std::ifstream cInFile( cBitstreamFile.c_str(), std::fstream::binary );
140 if( !cInFile )
141 {
142 std::cerr << "vvdecapp [error]: failed to open bitstream file " << cBitstreamFile << std::endl;
143 return -1;
144 }
145
146 bool writeStdout = false;
147
148 // open output file
149 std::ios * pStream{nullptr};
150 std::fstream cRecFile;
151 if( !cOutputFile.empty() )
152 {
153 if( !strcmp( cOutputFile.c_str(), "-" ) )
154 {
155 writeStdout = true;
156 #if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
157 if( _setmode( _fileno( stdout ), _O_BINARY ) == -1 )
158 {
159 std::cerr << "vvdecapp [error]: failed to set stdout to binary mode" << std::endl;
160 return -1;
161 }
162 #endif
163 }
164 else
165 {
166 cRecFile.open( cOutputFile.c_str(), std::fstream::binary | std::fstream::out );
167 if( !cRecFile )
168 {
169 std::cerr << "vvdecapp [error]: failed to open ouptut file " << cOutputFile << std::endl;
170 return -1;
171 }
172 pStream = &cRecFile;
173 }
174 }
175
176 std::ostream * outStream = writeStdout ? &std::cout : dynamic_cast<std::ostream*>(pStream);
177 std::ostream * logStream = writeStdout ? &std::cerr : &std::cout;
178
179 if( !cOutputFile.empty() && (nullptr == outStream || !outStream) )
180 {
181 *logStream << "vvdecapp [error]: failed to open ouptut file " << cOutputFile << std::endl;
182 return -1;
183 }
184
185 // stream buffer to directly calculate MD5 hash over the YUV output
186 vvdecoderapp::MD5StreamBuf md5Buf( 1024 * 1024 );
187 std::ostream md5Stream( &md5Buf );
188
189 vvdecDecoder *dec = nullptr;
190
191 //> decoding loop
192 vvdecAccessUnit* accessUnit = vvdec_accessUnit_alloc();
193 vvdec_accessUnit_alloc_payload( accessUnit, MAX_CODED_PICTURE_SIZE );
194
195 bool bOutputInfoWritten = false;
196
197 std::chrono::steady_clock::time_point cTPStartRun;
198 std::chrono::steady_clock::time_point cTPEndRun;
199
200 std::chrono::steady_clock::time_point cTPStart;
201 std::chrono::steady_clock::time_point cTPEnd;
202
203 std::vector<double > dFpsPerLoopVec;
204
205 int iSEIHashErrCount = 0;
206
207 int iLoop = 0;
208 bool bContinue = true;
209 while ( bContinue )
210 {
211 vvdecFrame* pcFrame = NULL;
212 vvdecFrame* pcPrevField = NULL;
213
214 if( iLoopCount > 1 )
215 {
216 *logStream << "-------------------------------------" << std::endl;
217 *logStream << "begin decoder loop #" << iLoop << std::endl;
218 *logStream << "-------------------------------------" << std::endl;
219 }
220
221 // initialize the decoder
222 dec = vvdec_decoder_open( ¶ms );
223 if( nullptr == dec )
224 {
225 *logStream << "cannot init decoder" << std::endl;
226 vvdec_accessUnit_free( accessUnit );
227 return -1;
228 }
229
230 vvdec_set_logging_callback( dec, writeStdout ? msgFncErr : msgFnc );
231
232 if( iLoop == 0 )
233 {
234 *logStream << vvdec_get_dec_information( dec ) << std::endl;
235 }
236
237 bool bFlushDecoder = false;
238
239 cTPStartRun = std::chrono::steady_clock::now();
240 cTPStart = std::chrono::steady_clock::now();
241
242 accessUnit->cts = 0; accessUnit->ctsValid = true;
243 accessUnit->dts = 0; accessUnit->dtsValid = true;
244
245 int iComprPics = 0;
246 unsigned int uiFrames = 0;
247 unsigned int uiFramesTmp = 0;
248 unsigned int uiBitrate = 0;
249
250 bool bTunedIn = false;
251 unsigned int uiNoFrameAfterTuneInCount = 0;
252 vvdecNalType eNalTypeSlice = VVC_NAL_UNIT_INVALID;
253 bool bMultipleSlices = false;
254
255 int iRead = 0;
256 do
257 {
258 iRead = readBitstreamFromFile( &cInFile, accessUnit, false );
259 //if( iRead > 0 )
260 {
261 vvdecNalType eNalType = vvdec_get_nal_unit_type( accessUnit );
262 if( params.logLevel == VVDEC_DETAILS )
263 {
264 std::string cNal = getNalUnitTypeAsString( eNalType );
265 *logStream << " read nal " << cNal << " size " << accessUnit->payloadUsedSize << std::endl;
266 }
267
268 if( eNalType == VVC_NAL_UNIT_PH )
269 {
270 // picture header indicates multiple slices
271 bMultipleSlices = true;
272 }
273
274 bool bIsSlice = vvdec_is_nal_unit_slice( eNalType );
275 if( bIsSlice )
276 {
277 if( bMultipleSlices )
278 {
279 if( eNalTypeSlice == VVC_NAL_UNIT_INVALID )
280 {
281 // set current slice type and increment pic count
282 iComprPics++;
283 eNalTypeSlice = eNalType;
284 }
285 else
286 {
287 bIsSlice = false; // prevent cts/dts increase if not first slice
288 }
289 }
290 else
291 {
292 iComprPics++;
293 }
294 }
295
296 if( eNalTypeSlice != VVC_NAL_UNIT_INVALID &&
297 eNalType != eNalTypeSlice )
298 {
299 eNalTypeSlice = VVC_NAL_UNIT_INVALID; // reset slice type
300 }
301
302
303 if( iMaxFrames > 0 && iComprPics >= iMaxFrames )
304 {
305 iRead = -1;
306 }
307
308 // call decode
309
310 iRet = vvdec_decode( dec, accessUnit, &pcFrame );
311 if( bIsSlice )
312 {
313 accessUnit->cts++;
314 accessUnit->dts++;
315 }
316
317 // check success
318 if( iRet == VVDEC_EOF )
319 {
320 //std::cout << "flush" << std::endl;
321 bFlushDecoder = true;
322 }
323 else if( iRet == VVDEC_TRY_AGAIN )
324 {
325 if( !bMultipleSlices )
326 {
327 if( params.logLevel >= VVDEC_VERBOSE ) *logStream << "more data needed to tune in" << std::endl;
328 if( bTunedIn )
329 {
330 // after the first frame is returned, the decoder must always return a frame
331 if( bIsSlice)
332 {
333 *logStream << "vvdecapp [error]: missing output picture!" << std::endl;
334 uiNoFrameAfterTuneInCount++;
335 }
336 }
337 }
338 }
339 else if( iRet != VVDEC_OK )
340 {
341 std::string cErr = vvdec_get_last_error(dec);
342 std::string cAdditionalErr = vvdec_get_last_additional_error(dec);
343 if( !cAdditionalErr.empty() )
344 {
345 *logStream << "vvdecapp [error]: decoding failed: " << cErr << " (" <<vvdec_get_error_msg( iRet ) << ")"
346 << " detail: " << vvdec_get_last_additional_error(dec) << std::endl;
347 }
348 else
349 {
350 *logStream << "vvdecapp [error]: decoding failed: " << cErr << " (" <<vvdec_get_error_msg( iRet ) << ")" << std::endl;
351 }
352 vvdec_accessUnit_free( accessUnit );
353 return iRet;
354 }
355
356 if( NULL != pcFrame && pcFrame->ctsValid )
357 {
358 if (!bOutputInfoWritten)
359 {
360 if( params.logLevel >= VVDEC_INFO )
361 {
362 *logStream << "vvdecapp [info]: SizeInfo: " << pcFrame->width << "x" << pcFrame->height << " (" << pcFrame->bitDepth << "b)" << std::endl;
363 }
364 bOutputInfoWritten = true;
365 }
366
367 if( !bTunedIn )
368 {
369 bTunedIn = true;
370 }
371
372 uiFrames++;
373 uiFramesTmp++;
374
375 if( pcFrame->picAttributes )
376 {
377 uiBitrate += pcFrame->picAttributes->bits;
378 }
379
380 #if 0 // just sample code to retrieve sei messages
381 vvdecSEI *sei = vvdec_find_frame_sei( dec, VVDEC_CONTENT_LIGHT_LEVEL_INFO, pcFrame );
382 if( sei )
383 {
384 vvdecSEIContentLightLevelInfo* p = reinterpret_cast<vvdecSEIContentLightLevelInfo *>(sei->payload);
385 *logStream << "vvdecapp [info]: CONTENT_LIGHT_LEVEL_INFO: " << p->maxContentLightLevel << "," << p->maxPicAverageLightLevel << std::endl;
386 }
387 #endif
388
389 if( pcFrame->frameFormat == VVDEC_FF_PROGRESSIVE )
390 {
391 if( !cExpectedYuvMD5.empty() )
392 {
393 writeYUVToFile( &md5Stream, pcFrame );
394 }
395 if( cRecFile.is_open() || writeStdout )
396 {
397 if( 0 != writeYUVToFile( outStream, pcFrame ) )
398 {
399 *logStream << "vvdecapp [error]: write of rec. yuv failed for picture seq. " << pcFrame->sequenceNumber << std::endl;
400 vvdec_accessUnit_free( accessUnit );
401 return iRet;
402 }
403 }
404 }
405 else if( pcFrame->frameFormat == VVDEC_FF_TOP_FIELD ||
406 pcFrame->frameFormat == VVDEC_FF_BOT_FIELD )
407 {
408 if( !pcPrevField )
409 {
410 pcPrevField = pcFrame;
411 }
412 else
413 {
414 if( !cExpectedYuvMD5.empty() )
415 {
416 writeYUVToFileInterlaced( &md5Stream, pcPrevField, pcFrame );
417 }
418 if( cRecFile.is_open() || writeStdout )
419 {
420 if( 0 != writeYUVToFileInterlaced( outStream, pcPrevField, pcFrame ) )
421 {
422 *logStream << "vvdecapp [error]: write of rec. yuv failed for picture seq. " << pcFrame->sequenceNumber << std::endl;
423 vvdec_accessUnit_free( accessUnit );
424 return iRet;
425 }
426 }
427 vvdec_frame_unref( dec, pcPrevField );
428 pcPrevField = nullptr;
429 }
430 }
431 else
432 {
433 *logStream << "vvdecapp [error]: unsupported FrameFormat " << pcFrame->frameFormat << " for picture seq. " << pcFrame->sequenceNumber << std::endl;
434 vvdec_accessUnit_free( accessUnit );
435 return -1;
436 }
437
438 // free picture memory
439 if( !pcPrevField || pcPrevField != pcFrame)
440 {
441 vvdec_frame_unref( dec, pcFrame );
442 }
443 }
444
445 if( uiFrames && params.logLevel >= VVDEC_INFO )
446 {
447 cTPEnd = std::chrono::steady_clock::now();
448 double dTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>((cTPEnd)-(cTPStart)).count();
449 if( dTimeMs > 1000.0 )
450 {
451 *logStream << "vvdecapp [info]: decoded Frames: " << uiFrames << " Fps: " << uiFramesTmp << std::endl;
452 cTPStart = std::chrono::steady_clock::now();
453 uiFramesTmp = 0;
454 }
455 }
456 }
457 }
458 while( iRead > 0 && !bFlushDecoder ); // end for frames
459
460 //std::cout << "flush" << std::endl;
461
462 // flush the decoder
463 bFlushDecoder = true;
464 while( bFlushDecoder)
465 {
466 vvdecFrame* pcFrame = NULL;
467
468 // flush the decoder
469 iRet = vvdec_flush( dec, &pcFrame );
470 if( iRet != VVDEC_OK && iRet != VVDEC_EOF )
471 {
472 *logStream << "vvdecapp [error]: decoding failed (" << iRet << ")" << std::endl; return iRet;
473 }
474
475 if( NULL != pcFrame )
476 {
477 uiFrames++;
478
479 if( pcFrame->frameFormat == VVDEC_FF_PROGRESSIVE )
480 {
481 if( !cExpectedYuvMD5.empty() )
482 {
483 writeYUVToFile( &md5Stream, pcFrame );
484 }
485 if( cRecFile.is_open() || writeStdout )
486 {
487 if( 0 != writeYUVToFile( outStream, pcFrame ) )
488 {
489 *logStream << "vvdecapp [error]: write of rec. yuv failed for picture seq. " << pcFrame->sequenceNumber << std::endl;
490 vvdec_accessUnit_free( accessUnit );
491 return iRet;
492 }
493 }
494 }
495 else if( pcFrame->frameFormat == VVDEC_FF_TOP_FIELD ||
496 pcFrame->frameFormat == VVDEC_FF_BOT_FIELD )
497 {
498 if( !pcPrevField )
499 {
500 pcPrevField = pcFrame;
501 }
502 else
503 {
504 if( !cExpectedYuvMD5.empty() )
505 {
506 writeYUVToFileInterlaced( &md5Stream, pcPrevField, pcFrame );
507 }
508 if( cRecFile.is_open() || writeStdout )
509 {
510 if( 0 != writeYUVToFileInterlaced( outStream, pcPrevField, pcFrame ) )
511 {
512 *logStream << "vvdecapp [error]: write of rec. yuv failed for picture seq. " << pcFrame->sequenceNumber << std::endl;
513 vvdec_accessUnit_free( accessUnit );
514 return iRet;
515 }
516 }
517 vvdec_frame_unref( dec, pcPrevField );
518 pcPrevField = nullptr;
519 }
520 }
521 else
522 {
523 *logStream << "vvdecapp [error]: unsupported FrameFormat " << pcFrame->frameFormat << " for picture seq. " << pcFrame->sequenceNumber << std::endl;
524 vvdec_accessUnit_free( accessUnit );
525 return -1;
526 }
527
528 // free picture memory
529 if( !pcPrevField || pcPrevField != pcFrame)
530 {
531 vvdec_frame_unref( dec, pcFrame );
532 }
533
534 if( params.logLevel >= VVDEC_INFO )
535 {
536 cTPEnd = std::chrono::steady_clock::now();
537 double dTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>((cTPEnd)-(cTPStart)).count();
538 if( dTimeMs > 1000.0 )
539 {
540 *logStream << "vvdecapp [info]: decoded Frames: " << uiFrames << " Fps: " << uiFramesTmp << std::endl;
541 cTPStart = std::chrono::steady_clock::now();
542 uiFramesTmp = 0;
543 }
544 }
545 }
546 else
547 {
548 break;
549 }
550 };
551
552 if( uiNoFrameAfterTuneInCount )
553 {
554 *logStream << "vvdecapp [error]: Decoder did not return " << uiNoFrameAfterTuneInCount << " pictures during decoding - came out too late" << std::endl;
555 }
556
557 cTPEndRun = std::chrono::steady_clock::now();
558 double dTimeSec = std::chrono::duration_cast<std::chrono::milliseconds>((cTPEndRun)-(cTPStartRun)).count() / 1000.0;
559
560 double dFps = dTimeSec ? ((double)uiFrames / dTimeSec) : uiFrames;
561 dFpsPerLoopVec.push_back( dFps );
562 if( params.logLevel >= VVDEC_INFO )
563 {
564 *logStream << "vvdecapp [info]: " << getTimePointAsString() << ": " << uiFrames << " frames decoded @ " << dFps << " fps (" << dTimeSec << " sec)\n" << std::endl;
565 }
566
567 iSEIHashErrCount = vvdec_get_hash_error_count(dec);
568 if (iSEIHashErrCount )
569 {
570 *logStream << "vvdecapp [error]: MD5 checksum error ( " << iSEIHashErrCount << " errors )" << std::endl;
571 vvdec_accessUnit_free( accessUnit );
572 return iSEIHashErrCount;
573 }
574
575 if( iComprPics && !uiFrames )
576 {
577 *logStream << "vvdecapp [error]: read some input pictures (" << iComprPics << "), but no output was generated." << std::endl;
578 vvdec_accessUnit_free( accessUnit );
579 return -1;
580 }
581
582 if( !cExpectedYuvMD5.empty() )
583 {
584 std::transform( cExpectedYuvMD5.begin(), cExpectedYuvMD5.end(), cExpectedYuvMD5.begin(), (int ( * )( int ))std::tolower );
585
586 const std::string yuvMD5 = md5Buf.finalizeHex();
587 if( cExpectedYuvMD5 != yuvMD5 )
588 {
589 *logStream << "vvdecapp [error] full YUV output MD5 mismatch: " << cExpectedYuvMD5 << " != " << yuvMD5 << std::endl;
590 vvdec_accessUnit_free( accessUnit );
591 return -1;
592 }
593 }
594
595 // un-initialize the decoder
596 iRet = vvdec_decoder_close(dec);
597 if( 0 != iRet )
598 {
599 *logStream << "vvdecapp [error]: cannot uninit decoder (" << iRet << ")" << std::endl;
600 vvdec_accessUnit_free( accessUnit );
601 return iRet;
602 }
603
604 if( iLoopCount < 0 || iLoop < iLoopCount-1 )
605 {
606 cInFile.clear();
607 cInFile.seekg( 0, cInFile.beg );
608 if(cInFile.bad() || cInFile.fail())
609 {
610 *logStream << "vvdecapp [error]: cannot seek to bitstream begin" << std::endl;
611 vvdec_accessUnit_free( accessUnit );
612 return -1;
613 }
614 }
615
616 iLoop++;
617 if( iLoopCount >= 0 && iLoop >= iLoopCount ){ bContinue = false;}
618 }
619 //< decoding loop finished
620
621 // free memory of access unit
622 vvdec_accessUnit_free( accessUnit );
623
624 // close yuv output file
625 if( !cOutputFile.empty() )
626 {
627 cRecFile.close();
628 }
629
630 cInFile.close();
631
632 if( iLoopCount > 1 )
633 {
634 // print average fps over all decoder loops
635 double dFpsSum = .0;
636 for( auto f : dFpsPerLoopVec )
637 {
638 dFpsSum += f;
639 }
640 if( params.logLevel > VVDEC_SILENT )
641 {
642 *logStream <<"vvdecapp [info]: avg. fps for " << dFpsPerLoopVec.size() << " loops: " << dFpsSum/dFpsPerLoopVec.size() << " Hz " << std::endl;
643 }
644 }
645
646 return 0;
647 }
648