0031668: Visualization - WebGL sample doesn't work on Emscripten 1.39
[occt.git] / src / Media / Media_FormatContext.cxx
1 // Created by: Kirill GAVRILOV
2 // Copyright (c) 2019 OPEN CASCADE SAS
3 //
4 // This file is part of Open CASCADE Technology software library.
5 //
6 // This library is free software; you can redistribute it and/or modify it under
7 // the terms of the GNU Lesser General Public License version 2.1 as published
8 // by the Free Software Foundation, with special exception defined in the file
9 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
10 // distribution for complete text of the license and disclaimer of any warranty.
11 //
12 // Alternatively, this file may be used under the terms of Open CASCADE
13 // commercial license or contractual agreement.
14
15 // activate some C99 macros like UINT64_C in "stdint.h" which used by FFmpeg
16 #ifndef __STDC_CONSTANT_MACROS
17   #define __STDC_CONSTANT_MACROS
18 #endif
19
20 #include <Media_FormatContext.hxx>
21
22 #include <Message.hxx>
23 #include <Message_Messenger.hxx>
24
25 #ifdef HAVE_FFMPEG
26 #include <Standard_WarningsDisable.hxx>
27 extern "C"
28 {
29   #include <libavformat/avformat.h>
30 };
31 #include <Standard_WarningsRestore.hxx>
32 #endif
33
34 IMPLEMENT_STANDARD_RTTIEXT(Media_FormatContext, Standard_Transient)
35
36 namespace
37 {
38   static const double THE_SECONDS_IN_HOUR = 3600.0;
39   static const double THE_SECONDS_IN_MINUTE = 60.0;
40   static const double THE_SECOND_IN_HOUR   = 1.0 / THE_SECONDS_IN_HOUR;
41   static const double THE_SECOND_IN_MINUTE = 1.0 / THE_SECONDS_IN_MINUTE;
42
43 #ifdef HAVE_FFMPEG
44   static const AVRational ST_AV_TIME_BASE_Q = {1, AV_TIME_BASE};
45   static const double     ST_AV_TIME_BASE_D = av_q2d (ST_AV_TIME_BASE_Q);
46
47   //! Format framerate value.
48   static TCollection_AsciiString formatFps (double theVal)
49   {
50     const uint64_t aVal = uint64_t(theVal * 100.0 + 0.5);
51     char aBuff[256];
52     if(aVal == 0)
53     {
54       Sprintf (aBuff, "%1.4f", theVal);
55     }
56     else if (aVal % 100)
57     {
58       Sprintf (aBuff, "%3.2f", theVal);
59     }
60     else if (aVal % (100 * 1000))
61     {
62       Sprintf (aBuff, "%1.0f", theVal);
63     }
64     else
65     {
66       Sprintf (aBuff, "%1.0fk", theVal / 1000);
67     }
68     return aBuff;
69   }
70 #endif
71 }
72
73 // =======================================================================
74 // function : FormatAVErrorDescription
75 // purpose  :
76 // =======================================================================
77 TCollection_AsciiString Media_FormatContext::FormatAVErrorDescription (int theErrCodeAV)
78 {
79 #ifdef HAVE_FFMPEG
80   char aBuff[4096];
81   memset (aBuff, 0, sizeof(aBuff));
82   if (av_strerror (theErrCodeAV, aBuff, 4096) != -1)
83   {
84     return TCollection_AsciiString (aBuff);
85   }
86
87 #ifdef _MSC_VER
88   wchar_t aBuffW[4096];
89   memset (aBuffW, 0, sizeof(aBuffW));
90   if (_wcserror_s (aBuffW, 4096, AVUNERROR(theErrCodeAV)) == 0)
91   {
92     return TCollection_AsciiString (aBuffW);
93   }
94 #elif defined(_WIN32)
95   // MinGW has only thread-unsafe variant
96   char* anErrDesc = strerror (AVUNERROR(theErrCodeAV));
97   if (anErrDesc != NULL)
98   {
99     return TCollection_AsciiString (anErrDesc);
100   }
101 #endif
102   return TCollection_AsciiString (aBuff);
103 #else
104   return TCollection_AsciiString ("AVError #") + theErrCodeAV;
105 #endif
106 }
107
108 // =======================================================================
109 // function : FormatUnitsToSeconds
110 // purpose  :
111 // =======================================================================
112 double Media_FormatContext::FormatUnitsToSeconds (int64_t theTimeUnits)
113 {
114 #ifdef HAVE_FFMPEG
115   return (theTimeUnits != AV_NOPTS_VALUE)
116         ? (ST_AV_TIME_BASE_D * theTimeUnits) : 0.0;
117 #else
118   (void )theTimeUnits;
119   return 0.0;
120 #endif
121 }
122
123 // =======================================================================
124 // function : UnitsToSeconds
125 // purpose  :
126 // =======================================================================
127 double Media_FormatContext::UnitsToSeconds (const AVRational& theTimeBase,
128                                             int64_t theTimeUnits)
129 {
130 #ifdef HAVE_FFMPEG
131   return (theTimeUnits != AV_NOPTS_VALUE)
132         ? (av_q2d (theTimeBase) * theTimeUnits) : 0.0;
133 #else
134   (void )&theTimeBase;
135   (void )theTimeUnits;
136   return 0.0;
137 #endif
138 }
139
140 // =======================================================================
141 // function : StreamUnitsToSeconds
142 // purpose  :
143 // =======================================================================
144 double Media_FormatContext::StreamUnitsToSeconds (const AVStream& theStream,
145                                                   int64_t theTimeUnits)
146 {
147 #ifdef HAVE_FFMPEG
148   return UnitsToSeconds (theStream.time_base, theTimeUnits);
149 #else
150   (void )&theStream;
151   (void )theTimeUnits;
152   return 0.0;
153 #endif
154 }
155
156 // =======================================================================
157 // function : SecondsToUnits
158 // purpose  :
159 // =======================================================================
160 int64_t Media_FormatContext::SecondsToUnits (double theTimeSeconds)
161 {
162 #ifdef HAVE_FFMPEG
163   return int64_t(theTimeSeconds / ST_AV_TIME_BASE_D);
164 #else
165   (void )theTimeSeconds;
166   return 0;
167 #endif
168 }
169
170 // =======================================================================
171 // function : SecondsToUnits
172 // purpose  :
173 // =======================================================================
174 int64_t Media_FormatContext::SecondsToUnits (const AVRational& theTimeBase,
175                                              double theTimeSeconds)
176 {
177 #ifdef HAVE_FFMPEG
178   return int64_t(theTimeSeconds / av_q2d (theTimeBase));
179 #else
180   (void )&theTimeBase;
181   (void )theTimeSeconds;
182   return 0;
183 #endif
184 }
185
186 // =======================================================================
187 // function : Media_FormatContext
188 // purpose  :
189 // =======================================================================
190 int64_t Media_FormatContext::StreamSecondsToUnits (const AVStream& theStream,
191                                                    double theTimeSeconds)
192 {
193 #ifdef HAVE_FFMPEG
194   return SecondsToUnits (theStream.time_base, theTimeSeconds);
195 #else
196   (void )&theStream;
197   (void )theTimeSeconds;
198   return 0;
199 #endif
200 }
201
202 // =======================================================================
203 // function : Media_FormatContext
204 // purpose  :
205 // =======================================================================
206 Media_FormatContext::Media_FormatContext()
207 : myFormatCtx   (NULL),
208   myPtsStartBase(0.0),
209   myDuration    (0.0)
210 {
211   //
212 }
213
214 // =======================================================================
215 // function : ~Media_FormatContext
216 // purpose  :
217 // =======================================================================
218 Media_FormatContext::~Media_FormatContext()
219 {
220   Close();
221 }
222
223 // =======================================================================
224 // function : NbSteams
225 // purpose  :
226 // =======================================================================
227 unsigned int Media_FormatContext::NbSteams() const
228 {
229 #ifdef HAVE_FFMPEG
230   return myFormatCtx->nb_streams;
231 #else
232   return 0;
233 #endif
234 }
235
236 // =======================================================================
237 // function : Stream
238 // purpose  :
239 // =======================================================================
240 const AVStream& Media_FormatContext::Stream (unsigned int theIndex) const
241 {
242 #ifdef HAVE_FFMPEG
243   return *myFormatCtx->streams[theIndex];
244 #else
245   (void )theIndex;
246   throw Standard_ProgramError("Media_FormatContext::Stream()");
247 #endif
248 }
249
250 // =======================================================================
251 // function : OpenInput
252 // purpose  :
253 // =======================================================================
254 bool Media_FormatContext::OpenInput (const TCollection_AsciiString& theInput)
255 {
256 #ifdef HAVE_FFMPEG
257   const int avErrCode = avformat_open_input (&myFormatCtx, theInput.ToCString(), NULL, NULL);
258   if (avErrCode != 0)
259   {
260     Message::SendFail (TCollection_AsciiString ("FFmpeg: Couldn't open video file '") + theInput
261                      + "'\nError: " + FormatAVErrorDescription (avErrCode));
262     Close();
263     return false;
264   }
265
266   // retrieve stream information
267   if (avformat_find_stream_info (myFormatCtx, NULL) < 0)
268   {
269     Message::SendFail (TCollection_AsciiString ("FFmpeg: Couldn't find stream information in '") + theInput + "'");
270     Close();
271     return false;
272   }
273
274 #ifdef _DEBUG
275   av_dump_format (myFormatCtx, 0, theInput.ToCString(), false);
276 #endif
277
278   myDuration = 0.0;
279   myPtsStartBase = 0.0;
280
281   TCollection_AsciiString anExt = theInput;
282   anExt.LowerCase();
283   if (anExt.EndsWith (".png")
284    || anExt.EndsWith (".jpg")
285    || anExt.EndsWith (".jpeg")
286    || anExt.EndsWith (".mpo")
287    || anExt.EndsWith (".bmp")
288    || anExt.EndsWith (".tif")
289    || anExt.EndsWith (".tiff"))
290   {
291     // black-list images to workaround non-zero duration
292     return true;
293   }
294
295   myDuration = FormatUnitsToSeconds (myFormatCtx->duration);
296   if (myFormatCtx->nb_streams != 0)
297   {
298     myPtsStartBase = 2.e+100;
299     for (unsigned int aStreamId = 0; aStreamId < myFormatCtx->nb_streams; ++aStreamId)
300     {
301       const AVStream& aStream = *myFormatCtx->streams[aStreamId];
302       myPtsStartBase = Min (myPtsStartBase, StreamUnitsToSeconds (aStream, aStream.start_time));
303       myDuration     = Max (myDuration,     StreamUnitsToSeconds (aStream, aStream.duration));
304     }
305   }
306
307   return true;
308 #else
309   Message::SendFail ("Error: FFmpeg library is unavailable");
310   (void )theInput;
311   return false;
312 #endif
313 }
314
315 // =======================================================================
316 // function : Close
317 // purpose  :
318 // =======================================================================
319 void Media_FormatContext::Close()
320 {
321   if (myFormatCtx != NULL)
322   {
323   #ifdef HAVE_FFMPEG
324     avformat_close_input (&myFormatCtx);
325     //avformat_free_context (myFormatCtx);
326   #endif
327   }
328 }
329
330 // =======================================================================
331 // function : FormatTime
332 // purpose  :
333 // =======================================================================
334 TCollection_AsciiString Media_FormatContext::FormatTime (double theSeconds)
335 {
336   double aSecIn = theSeconds;
337   unsigned int aHours   = (unsigned int )(aSecIn * THE_SECOND_IN_HOUR);
338   aSecIn -= double(aHours) * THE_SECONDS_IN_HOUR;
339   unsigned int aMinutes = (unsigned int )(aSecIn * THE_SECOND_IN_MINUTE);
340   aSecIn -= double(aMinutes) * THE_SECONDS_IN_MINUTE;
341   unsigned int aSeconds = (unsigned int )aSecIn;
342   aSecIn -= double(aSeconds);
343   double aMilliSeconds = 1000.0 * aSecIn;
344
345   char aBuffer[64];
346   if (aHours > 0)
347   {
348     Sprintf (aBuffer, "%02u:%02u:%02u", aHours, aMinutes, aSeconds);
349     return aBuffer;
350   }
351   else if (aMinutes > 0)
352   {
353     Sprintf (aBuffer, "%02u:%02u", aMinutes, aSeconds);
354     return aBuffer;
355   }
356   else if (aSeconds > 0)
357   {
358     Sprintf (aBuffer, "%2u s", aSeconds);
359     return aBuffer;
360   }
361
362   return TCollection_AsciiString (aMilliSeconds) + " ms";
363 }
364
365 // =======================================================================
366 // function : FormatTimeProgress
367 // purpose  :
368 // =======================================================================
369 TCollection_AsciiString Media_FormatContext::FormatTimeProgress (double theProgress,
370                                                                  double theDuration)
371 {
372   double aSecIn1 = theProgress;
373   unsigned int aHours1   = (unsigned int )(aSecIn1 * THE_SECOND_IN_HOUR);
374   aSecIn1 -= double(aHours1) * THE_SECONDS_IN_HOUR;
375   unsigned int aMinutes1 = (unsigned int )(aSecIn1 * THE_SECOND_IN_MINUTE);
376   aSecIn1 -= double(aMinutes1) * THE_SECONDS_IN_MINUTE;
377   unsigned int aSeconds1 = (unsigned int )aSecIn1;
378   aSecIn1 -= double(aSeconds1);
379
380   double aSecIn2 = theDuration;
381   unsigned int aHours2   = (unsigned int )(aSecIn2 * THE_SECOND_IN_HOUR);
382   aSecIn2 -= double(aHours2) * THE_SECONDS_IN_HOUR;
383   unsigned int aMinutes2 = (unsigned int )(aSecIn2 * THE_SECOND_IN_MINUTE);
384   aSecIn2 -= double(aMinutes2) * THE_SECONDS_IN_MINUTE;
385   unsigned int aSeconds2 = (unsigned int )aSecIn2;
386   aSecIn2 -= double(aSeconds2);
387
388   char aBuffer[256];
389   if (aHours1 > 0
390    || aHours2 > 0)
391   {
392     Sprintf (aBuffer, "%02u:%02u:%02u / %02u:%02u:%02u", aHours1, aMinutes1, aSeconds1, aHours2, aMinutes2, aSeconds2);
393     return aBuffer;
394   }
395   Sprintf (aBuffer, "%02u:%02u / %02u:%02u", aMinutes1, aSeconds1, aMinutes2, aSeconds2);
396   return aBuffer;
397 }
398
399 // =======================================================================
400 // function : StreamInfo
401 // purpose  :
402 // =======================================================================
403 TCollection_AsciiString Media_FormatContext::StreamInfo (unsigned int theIndex,
404                                                          AVCodecContext* theCodecCtx) const
405 {
406 #ifdef HAVE_FFMPEG
407   const AVStream& aStream = *myFormatCtx->streams[theIndex];
408
409   AVCodecContext* aCodecCtx = theCodecCtx;
410   if (aCodecCtx == NULL)
411   {
412   Standard_DISABLE_DEPRECATION_WARNINGS
413     aCodecCtx = aStream.codec;
414   Standard_ENABLE_DEPRECATION_WARNINGS
415   }
416
417   char aFrmtBuff[4096] = {};
418   avcodec_string (aFrmtBuff, sizeof(aFrmtBuff), aCodecCtx, 0);
419   TCollection_AsciiString aStreamInfo (aFrmtBuff);
420
421   if (aStream.sample_aspect_ratio.num && av_cmp_q(aStream.sample_aspect_ratio, aStream.codecpar->sample_aspect_ratio))
422   {
423     AVRational aDispAspectRatio;
424     av_reduce (&aDispAspectRatio.num, &aDispAspectRatio.den,
425                aStream.codecpar->width  * int64_t(aStream.sample_aspect_ratio.num),
426                aStream.codecpar->height * int64_t(aStream.sample_aspect_ratio.den),
427                1024 * 1024);
428     aStreamInfo = aStreamInfo + ", SAR " + aStream.sample_aspect_ratio.num + ":" + aStream.sample_aspect_ratio.den
429                 + " DAR " + aDispAspectRatio.num + ":" + aDispAspectRatio.den;
430   }
431
432   if (aStream.codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
433   {
434     if (aStream.avg_frame_rate.den != 0 && aStream.avg_frame_rate.num != 0)
435     {
436       aStreamInfo += TCollection_AsciiString(", ") + formatFps (av_q2d (aStream.avg_frame_rate)) + " fps";
437     }
438     if (aStream.r_frame_rate.den != 0 && aStream.r_frame_rate.num != 0)
439     {
440       aStreamInfo += TCollection_AsciiString(", ") + formatFps (av_q2d (aStream.r_frame_rate)) + " tbr";
441     }
442     if (aStream.time_base.den != 0 && aStream.time_base.num != 0)
443     {
444       aStreamInfo += TCollection_AsciiString(", ") + formatFps(1 / av_q2d (aStream.time_base)) + " tbn";
445     }
446     if (aCodecCtx->time_base.den != 0 && aCodecCtx->time_base.num != 0)
447     {
448       aStreamInfo += TCollection_AsciiString(", ") + formatFps(1 / av_q2d (aCodecCtx->time_base)) + " tbc";
449     }
450   }
451   if (myDuration > 0.0)
452   {
453     aStreamInfo += TCollection_AsciiString(", duration: ") + FormatTime (myDuration);
454   }
455   return aStreamInfo;
456 #else
457   (void )theIndex;
458   (void )theCodecCtx;
459   return TCollection_AsciiString();
460 #endif
461 }
462
463 // =======================================================================
464 // function : ReadPacket
465 // purpose  :
466 // =======================================================================
467 bool Media_FormatContext::ReadPacket (const Handle(Media_Packet)& thePacket)
468 {
469   if (thePacket.IsNull())
470   {
471     return false;
472   }
473
474 #ifdef HAVE_FFMPEG
475   return av_read_frame (myFormatCtx, thePacket->ChangePacket()) >= 0;
476 #else
477   return false;
478 #endif
479 }
480
481 // =======================================================================
482 // function : SeekStream
483 // purpose  :
484 // =======================================================================
485 bool Media_FormatContext::SeekStream (unsigned int theStreamId,
486                                       double theSeekPts,
487                                       bool theToSeekBack)
488 {
489 #ifdef HAVE_FFMPEG
490   const int aFlags = theToSeekBack ? AVSEEK_FLAG_BACKWARD : 0;
491   AVStream& aStream = *myFormatCtx->streams[theStreamId];
492   if ((aStream.disposition & AV_DISPOSITION_ATTACHED_PIC) != 0)
493   {
494     return false;
495   }
496
497   int64_t aSeekTarget = StreamSecondsToUnits (aStream, theSeekPts + StreamUnitsToSeconds (aStream, aStream.start_time));
498   bool isSeekDone = av_seek_frame (myFormatCtx, theStreamId, aSeekTarget, aFlags) >= 0;
499
500   // try 10 more times in backward direction to work-around huge duration between key frames
501   // will not work for some streams with undefined cur_dts (AV_NOPTS_VALUE)!!!
502   for (int aTries = 10; isSeekDone && theToSeekBack && aTries > 0 && (aStream.cur_dts > aSeekTarget); --aTries)
503   {
504     aSeekTarget -= StreamSecondsToUnits (aStream, 1.0);
505     isSeekDone = av_seek_frame (myFormatCtx, theStreamId, aSeekTarget, aFlags) >= 0;
506   }
507   if (isSeekDone)
508   {
509     return true;
510   }
511
512   TCollection_AsciiString aStreamType = aStream.codecpar->codec_type == AVMEDIA_TYPE_VIDEO
513                                       ? "Video"
514                                       : (aStream.codecpar->codec_type == AVMEDIA_TYPE_AUDIO
515                                        ? "Audio"
516                                        : "");
517   Message::SendWarning (TCollection_AsciiString ("Error while seeking ") + aStreamType + " stream to "
518                       + theSeekPts + " sec (" + (theSeekPts + StreamUnitsToSeconds (aStream, aStream.start_time)) + " sec)");
519   return false;
520 #else
521   (void )theStreamId;
522   (void )theSeekPts;
523   (void )theToSeekBack;
524   return false;
525 #endif
526 }
527
528 // =======================================================================
529 // function : Seek
530 // purpose  :
531 // =======================================================================
532 bool Media_FormatContext::Seek (double theSeekPts,
533                                 bool   theToSeekBack)
534 {
535 #ifdef HAVE_FFMPEG
536   const int aFlags      = theToSeekBack ? AVSEEK_FLAG_BACKWARD : 0;
537   int64_t   aSeekTarget = SecondsToUnits (theSeekPts);
538   if (av_seek_frame (myFormatCtx, -1, aSeekTarget, aFlags) >= 0)
539   {
540     return true;
541   }
542
543   const char* aFileName = 
544   #if(LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 7, 100))
545     myFormatCtx->url;
546   #else
547     myFormatCtx->filename;
548   #endif
549
550   Message::SendWarning (TCollection_AsciiString("Disaster! Seeking to ") + theSeekPts + " [" + aFileName + "] has failed.");
551   return false;
552 #else
553   (void )theSeekPts;
554   (void )theToSeekBack;
555   return false;
556 #endif
557 }