--- /dev/null
+// Created by: Kirill GAVRILOV
+// Copyright (c) 2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+// activate some C99 macros like UINT64_C in "stdint.h" which used by FFmpeg
+#ifndef __STDC_CONSTANT_MACROS
+ #define __STDC_CONSTANT_MACROS
+#endif
+
+#include <Media_FormatContext.hxx>
+
+#include <Message.hxx>
+#include <Message_Messenger.hxx>
+
+#ifdef HAVE_FFMPEG
+#include <Standard_WarningsDisable.hxx>
+extern "C"
+{
+ #include <libavformat/avformat.h>
+};
+#include <Standard_WarningsRestore.hxx>
+#endif
+
+IMPLEMENT_STANDARD_RTTIEXT(Media_FormatContext, Standard_Transient)
+
+namespace
+{
+ static const double THE_SECONDS_IN_HOUR = 3600.0;
+ static const double THE_SECONDS_IN_MINUTE = 60.0;
+ static const double THE_SECOND_IN_HOUR = 1.0 / THE_SECONDS_IN_HOUR;
+ static const double THE_SECOND_IN_MINUTE = 1.0 / THE_SECONDS_IN_MINUTE;
+
+#ifdef HAVE_FFMPEG
+ static const AVRational ST_AV_TIME_BASE_Q = {1, AV_TIME_BASE};
+ static const double ST_AV_TIME_BASE_D = av_q2d (ST_AV_TIME_BASE_Q);
+
+ //! Format framerate value.
+ static TCollection_AsciiString formatFps (double theVal)
+ {
+ const uint64_t aVal = uint64_t(theVal * 100.0 + 0.5);
+ char aBuff[256];
+ if(aVal == 0)
+ {
+ Sprintf (aBuff, "%1.4f", theVal);
+ }
+ else if (aVal % 100)
+ {
+ Sprintf (aBuff, "%3.2f", theVal);
+ }
+ else if (aVal % (100 * 1000))
+ {
+ Sprintf (aBuff, "%1.0f", theVal);
+ }
+ else
+ {
+ Sprintf (aBuff, "%1.0fk", theVal / 1000);
+ }
+ return aBuff;
+ }
+#endif
+}
+
+// =======================================================================
+// function : FormatAVErrorDescription
+// purpose :
+// =======================================================================
+TCollection_AsciiString Media_FormatContext::FormatAVErrorDescription (int theErrCodeAV)
+{
+#ifdef HAVE_FFMPEG
+ char aBuff[4096];
+ memset (aBuff, 0, sizeof(aBuff));
+ if (av_strerror (theErrCodeAV, aBuff, 4096) != -1)
+ {
+ return TCollection_AsciiString (aBuff);
+ }
+
+#ifdef _MSC_VER
+ wchar_t aBuffW[4096];
+ memset (aBuffW, 0, sizeof(aBuffW));
+ if (_wcserror_s (aBuffW, 4096, AVUNERROR(theErrCodeAV)) == 0)
+ {
+ return TCollection_AsciiString (aBuffW);
+ }
+#elif defined(_WIN32)
+ // MinGW has only thread-unsafe variant
+ char* anErrDesc = strerror (AVUNERROR(theErrCodeAV));
+ if (anErrDesc != NULL)
+ {
+ return TCollection_AsciiString (anErrDesc);
+ }
+#endif
+ return TCollection_AsciiString (aBuff);
+#else
+ return TCollection_AsciiString ("AVError #") + theErrCodeAV;
+#endif
+}
+
+// =======================================================================
+// function : FormatUnitsToSeconds
+// purpose :
+// =======================================================================
+double Media_FormatContext::FormatUnitsToSeconds (int64_t theTimeUnits)
+{
+#ifdef HAVE_FFMPEG
+ return (theTimeUnits != AV_NOPTS_VALUE)
+ ? (ST_AV_TIME_BASE_D * theTimeUnits) : 0.0;
+#else
+ (void )theTimeUnits;
+ return 0.0;
+#endif
+}
+
+// =======================================================================
+// function : UnitsToSeconds
+// purpose :
+// =======================================================================
+double Media_FormatContext::UnitsToSeconds (const AVRational& theTimeBase,
+ int64_t theTimeUnits)
+{
+#ifdef HAVE_FFMPEG
+ return (theTimeUnits != AV_NOPTS_VALUE)
+ ? (av_q2d (theTimeBase) * theTimeUnits) : 0.0;
+#else
+ (void )&theTimeBase;
+ (void )theTimeUnits;
+ return 0.0;
+#endif
+}
+
+// =======================================================================
+// function : StreamUnitsToSeconds
+// purpose :
+// =======================================================================
+double Media_FormatContext::StreamUnitsToSeconds (const AVStream& theStream,
+ int64_t theTimeUnits)
+{
+#ifdef HAVE_FFMPEG
+ return UnitsToSeconds (theStream.time_base, theTimeUnits);
+#else
+ (void )&theStream;
+ (void )theTimeUnits;
+ return 0.0;
+#endif
+}
+
+// =======================================================================
+// function : SecondsToUnits
+// purpose :
+// =======================================================================
+int64_t Media_FormatContext::SecondsToUnits (double theTimeSeconds)
+{
+#ifdef HAVE_FFMPEG
+ return int64_t(theTimeSeconds / ST_AV_TIME_BASE_D);
+#else
+ (void )theTimeSeconds;
+ return 0;
+#endif
+}
+
+// =======================================================================
+// function : SecondsToUnits
+// purpose :
+// =======================================================================
+int64_t Media_FormatContext::SecondsToUnits (const AVRational& theTimeBase,
+ double theTimeSeconds)
+{
+#ifdef HAVE_FFMPEG
+ return int64_t(theTimeSeconds / av_q2d (theTimeBase));
+#else
+ (void )&theTimeBase;
+ (void )theTimeSeconds;
+ return 0;
+#endif
+}
+
+// =======================================================================
+// function : Media_FormatContext
+// purpose :
+// =======================================================================
+int64_t Media_FormatContext::StreamSecondsToUnits (const AVStream& theStream,
+ double theTimeSeconds)
+{
+#ifdef HAVE_FFMPEG
+ return SecondsToUnits (theStream.time_base, theTimeSeconds);
+#else
+ (void )&theStream;
+ (void )theTimeSeconds;
+ return 0;
+#endif
+}
+
+// =======================================================================
+// function : Media_FormatContext
+// purpose :
+// =======================================================================
+Media_FormatContext::Media_FormatContext()
+: myFormatCtx (NULL),
+ myPtsStartBase(0.0),
+ myDuration (0.0)
+{
+ //
+}
+
+// =======================================================================
+// function : ~Media_FormatContext
+// purpose :
+// =======================================================================
+Media_FormatContext::~Media_FormatContext()
+{
+ Close();
+}
+
+// =======================================================================
+// function : NbSteams
+// purpose :
+// =======================================================================
+unsigned int Media_FormatContext::NbSteams() const
+{
+#ifdef HAVE_FFMPEG
+ return myFormatCtx->nb_streams;
+#else
+ return 0;
+#endif
+}
+
+// =======================================================================
+// function : Stream
+// purpose :
+// =======================================================================
+const AVStream& Media_FormatContext::Stream (unsigned int theIndex) const
+{
+#ifdef HAVE_FFMPEG
+ return *myFormatCtx->streams[theIndex];
+#else
+ (void )theIndex;
+ throw Standard_ProgramError("Media_FormatContext::Stream()");
+#endif
+}
+
+// =======================================================================
+// function : OpenInput
+// purpose :
+// =======================================================================
+bool Media_FormatContext::OpenInput (const TCollection_AsciiString& theInput)
+{
+#ifdef HAVE_FFMPEG
+ const int avErrCode = avformat_open_input (&myFormatCtx, theInput.ToCString(), NULL, NULL);
+ if (avErrCode != 0)
+ {
+ Message::DefaultMessenger()->Send (TCollection_AsciiString ("FFmpeg: Couldn't open video file '") + theInput
+ + "'\nError: " + FormatAVErrorDescription (avErrCode), Message_Fail);
+ Close();
+ return false;
+ }
+
+ // retrieve stream information
+ if (avformat_find_stream_info (myFormatCtx, NULL) < 0)
+ {
+ Message::DefaultMessenger()->Send (TCollection_AsciiString ("FFmpeg: Couldn't find stream information in '") + theInput + "'", Message_Fail);
+ Close();
+ return false;
+ }
+
+#ifdef _DEBUG
+ av_dump_format (myFormatCtx, 0, theInput.ToCString(), false);
+#endif
+
+ myDuration = 0.0;
+ myPtsStartBase = 0.0;
+
+ TCollection_AsciiString anExt = theInput;
+ anExt.LowerCase();
+ if (anExt.EndsWith (".png")
+ || anExt.EndsWith (".jpg")
+ || anExt.EndsWith (".jpeg")
+ || anExt.EndsWith (".mpo")
+ || anExt.EndsWith (".bmp")
+ || anExt.EndsWith (".tif")
+ || anExt.EndsWith (".tiff"))
+ {
+ // black-list images to workaround non-zero duration
+ return true;
+ }
+
+ myDuration = FormatUnitsToSeconds (myFormatCtx->duration);
+ if (myFormatCtx->nb_streams != 0)
+ {
+ myPtsStartBase = 2.e+100;
+ for (unsigned int aStreamId = 0; aStreamId < myFormatCtx->nb_streams; ++aStreamId)
+ {
+ const AVStream& aStream = *myFormatCtx->streams[aStreamId];
+ myPtsStartBase = Min (myPtsStartBase, StreamUnitsToSeconds (aStream, aStream.start_time));
+ myDuration = Max (myDuration, StreamUnitsToSeconds (aStream, aStream.duration));
+ }
+ }
+
+ return true;
+#else
+ Message::DefaultMessenger()->Send ("Error: FFmpeg library is unavailable", Message_Fail);
+ (void )theInput;
+ return false;
+#endif
+}
+
+// =======================================================================
+// function : Close
+// purpose :
+// =======================================================================
+void Media_FormatContext::Close()
+{
+ if (myFormatCtx != NULL)
+ {
+ #ifdef HAVE_FFMPEG
+ avformat_close_input (&myFormatCtx);
+ //avformat_free_context (myFormatCtx);
+ #endif
+ }
+}
+
+// =======================================================================
+// function : FormatTime
+// purpose :
+// =======================================================================
+TCollection_AsciiString Media_FormatContext::FormatTime (double theSeconds)
+{
+ double aSecIn = theSeconds;
+ unsigned int aHours = (unsigned int )(aSecIn * THE_SECOND_IN_HOUR);
+ aSecIn -= double(aHours) * THE_SECONDS_IN_HOUR;
+ unsigned int aMinutes = (unsigned int )(aSecIn * THE_SECOND_IN_MINUTE);
+ aSecIn -= double(aMinutes) * THE_SECONDS_IN_MINUTE;
+ unsigned int aSeconds = (unsigned int )aSecIn;
+ aSecIn -= double(aSeconds);
+ double aMilliSeconds = 1000.0 * aSecIn;
+
+ char aBuffer[64];
+ if (aHours > 0)
+ {
+ Sprintf (aBuffer, "%02u:%02u:%02u", aHours, aMinutes, aSeconds);
+ return aBuffer;
+ }
+ else if (aMinutes > 0)
+ {
+ Sprintf (aBuffer, "%02u:%02u", aMinutes, aSeconds);
+ return aBuffer;
+ }
+ else if (aSeconds > 0)
+ {
+ Sprintf (aBuffer, "%2u s", aSeconds);
+ return aBuffer;
+ }
+
+ return TCollection_AsciiString (aMilliSeconds) + " ms";
+}
+
+// =======================================================================
+// function : FormatTimeProgress
+// purpose :
+// =======================================================================
+TCollection_AsciiString Media_FormatContext::FormatTimeProgress (double theProgress,
+ double theDuration)
+{
+ double aSecIn1 = theProgress;
+ unsigned int aHours1 = (unsigned int )(aSecIn1 * THE_SECOND_IN_HOUR);
+ aSecIn1 -= double(aHours1) * THE_SECONDS_IN_HOUR;
+ unsigned int aMinutes1 = (unsigned int )(aSecIn1 * THE_SECOND_IN_MINUTE);
+ aSecIn1 -= double(aMinutes1) * THE_SECONDS_IN_MINUTE;
+ unsigned int aSeconds1 = (unsigned int )aSecIn1;
+ aSecIn1 -= double(aSeconds1);
+
+ double aSecIn2 = theDuration;
+ unsigned int aHours2 = (unsigned int )(aSecIn2 * THE_SECOND_IN_HOUR);
+ aSecIn2 -= double(aHours2) * THE_SECONDS_IN_HOUR;
+ unsigned int aMinutes2 = (unsigned int )(aSecIn2 * THE_SECOND_IN_MINUTE);
+ aSecIn2 -= double(aMinutes2) * THE_SECONDS_IN_MINUTE;
+ unsigned int aSeconds2 = (unsigned int )aSecIn2;
+ aSecIn2 -= double(aSeconds2);
+
+ char aBuffer[256];
+ if (aHours1 > 0
+ || aHours2 > 0)
+ {
+ Sprintf (aBuffer, "%02u:%02u:%02u / %02u:%02u:%02u", aHours1, aMinutes1, aSeconds1, aHours2, aMinutes2, aSeconds2);
+ return aBuffer;
+ }
+ Sprintf (aBuffer, "%02u:%02u / %02u:%02u", aMinutes1, aSeconds1, aMinutes2, aSeconds2);
+ return aBuffer;
+}
+
+// =======================================================================
+// function : StreamInfo
+// purpose :
+// =======================================================================
+TCollection_AsciiString Media_FormatContext::StreamInfo (unsigned int theIndex,
+ AVCodecContext* theCodecCtx) const
+{
+#ifdef HAVE_FFMPEG
+ const AVStream& aStream = *myFormatCtx->streams[theIndex];
+
+ AVCodecContext* aCodecCtx = theCodecCtx;
+ if (aCodecCtx == NULL)
+ {
+ Standard_DISABLE_DEPRECATION_WARNINGS
+ aCodecCtx = aStream.codec;
+ Standard_ENABLE_DEPRECATION_WARNINGS
+ }
+
+ char aFrmtBuff[4096] = {};
+ avcodec_string (aFrmtBuff, sizeof(aFrmtBuff), aCodecCtx, 0);
+ TCollection_AsciiString aStreamInfo (aFrmtBuff);
+
+ if (aStream.sample_aspect_ratio.num && av_cmp_q(aStream.sample_aspect_ratio, aStream.codecpar->sample_aspect_ratio))
+ {
+ AVRational aDispAspectRatio;
+ av_reduce (&aDispAspectRatio.num, &aDispAspectRatio.den,
+ aStream.codecpar->width * int64_t(aStream.sample_aspect_ratio.num),
+ aStream.codecpar->height * int64_t(aStream.sample_aspect_ratio.den),
+ 1024 * 1024);
+ aStreamInfo = aStreamInfo + ", SAR " + aStream.sample_aspect_ratio.num + ":" + aStream.sample_aspect_ratio.den
+ + " DAR " + aDispAspectRatio.num + ":" + aDispAspectRatio.den;
+ }
+
+ if (aStream.codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
+ {
+ if (aStream.avg_frame_rate.den != 0 && aStream.avg_frame_rate.num != 0)
+ {
+ aStreamInfo += TCollection_AsciiString(", ") + formatFps (av_q2d (aStream.avg_frame_rate)) + " fps";
+ }
+ if (aStream.r_frame_rate.den != 0 && aStream.r_frame_rate.num != 0)
+ {
+ aStreamInfo += TCollection_AsciiString(", ") + formatFps (av_q2d (aStream.r_frame_rate)) + " tbr";
+ }
+ if (aStream.time_base.den != 0 && aStream.time_base.num != 0)
+ {
+ aStreamInfo += TCollection_AsciiString(", ") + formatFps(1 / av_q2d (aStream.time_base)) + " tbn";
+ }
+ if (aCodecCtx->time_base.den != 0 && aCodecCtx->time_base.num != 0)
+ {
+ aStreamInfo += TCollection_AsciiString(", ") + formatFps(1 / av_q2d (aCodecCtx->time_base)) + " tbc";
+ }
+ }
+ if (myDuration > 0.0)
+ {
+ aStreamInfo += TCollection_AsciiString(", duration: ") + FormatTime (myDuration);
+ }
+ return aStreamInfo;
+#else
+ (void )theIndex;
+ (void )theCodecCtx;
+ return TCollection_AsciiString();
+#endif
+}
+
+// =======================================================================
+// function : ReadPacket
+// purpose :
+// =======================================================================
+bool Media_FormatContext::ReadPacket (const Handle(Media_Packet)& thePacket)
+{
+ if (thePacket.IsNull())
+ {
+ return false;
+ }
+
+#ifdef HAVE_FFMPEG
+ return av_read_frame (myFormatCtx, thePacket->ChangePacket()) >= 0;
+#else
+ return false;
+#endif
+}
+
+// =======================================================================
+// function : SeekStream
+// purpose :
+// =======================================================================
+bool Media_FormatContext::SeekStream (unsigned int theStreamId,
+ double theSeekPts,
+ bool theToSeekBack)
+{
+#ifdef HAVE_FFMPEG
+ const int aFlags = theToSeekBack ? AVSEEK_FLAG_BACKWARD : 0;
+ AVStream& aStream = *myFormatCtx->streams[theStreamId];
+ if ((aStream.disposition & AV_DISPOSITION_ATTACHED_PIC) != 0)
+ {
+ return false;
+ }
+
+ int64_t aSeekTarget = StreamSecondsToUnits (aStream, theSeekPts + StreamUnitsToSeconds (aStream, aStream.start_time));
+ bool isSeekDone = av_seek_frame (myFormatCtx, theStreamId, aSeekTarget, aFlags) >= 0;
+
+ // try 10 more times in backward direction to work-around huge duration between key frames
+ // will not work for some streams with undefined cur_dts (AV_NOPTS_VALUE)!!!
+ for (int aTries = 10; isSeekDone && theToSeekBack && aTries > 0 && (aStream.cur_dts > aSeekTarget); --aTries)
+ {
+ aSeekTarget -= StreamSecondsToUnits (aStream, 1.0);
+ isSeekDone = av_seek_frame (myFormatCtx, theStreamId, aSeekTarget, aFlags) >= 0;
+ }
+ if (isSeekDone)
+ {
+ return true;
+ }
+
+ TCollection_AsciiString aStreamType = aStream.codecpar->codec_type == AVMEDIA_TYPE_VIDEO
+ ? "Video"
+ : (aStream.codecpar->codec_type == AVMEDIA_TYPE_AUDIO
+ ? "Audio"
+ : "");
+ Message::DefaultMessenger()->Send (TCollection_AsciiString ("Error while seeking ") + aStreamType + " stream to "
+ + theSeekPts + " sec (" + (theSeekPts + StreamUnitsToSeconds (aStream, aStream.start_time)) + " sec)",
+ Message_Warning);
+ return false;
+#else
+ (void )theStreamId;
+ (void )theSeekPts;
+ (void )theToSeekBack;
+ return false;
+#endif
+}
+
+// =======================================================================
+// function : Seek
+// purpose :
+// =======================================================================
+bool Media_FormatContext::Seek (double theSeekPts,
+ bool theToSeekBack)
+{
+#ifdef HAVE_FFMPEG
+ const int aFlags = theToSeekBack ? AVSEEK_FLAG_BACKWARD : 0;
+ int64_t aSeekTarget = SecondsToUnits (theSeekPts);
+ if (av_seek_frame (myFormatCtx, -1, aSeekTarget, aFlags) >= 0)
+ {
+ return true;
+ }
+
+ const char* aFileName =
+ #if(LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 7, 100))
+ myFormatCtx->url;
+ #else
+ myFormatCtx->filename;
+ #endif
+
+ Message::DefaultMessenger()->Send (TCollection_AsciiString("Disaster! Seeking to ") + theSeekPts + " [" + aFileName + "] has failed.", Message_Warning);
+ return false;
+#else
+ (void )theSeekPts;
+ (void )theToSeekBack;
+ return false;
+#endif
+}