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(&params);
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( &params );
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