1
0
mirror of https://git.dev.opencascade.org/repos/occt.git synced 2025-04-04 18:06:22 +03:00
occt/src/Media/Media_FormatContext.cxx
kgv a87b1b3738 0031501: Foundation Classes, Message_Printer - remove theToPutEndl argument -- use shortcuts
Message::DefaultMessenger()->Send() have been replaced by shortcuts Message::Send().
2020-05-09 17:22:15 +03:00

558 lines
17 KiB
C++

// 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::SendFail (TCollection_AsciiString ("FFmpeg: Couldn't open video file '") + theInput
+ "'\nError: " + FormatAVErrorDescription (avErrCode));
Close();
return false;
}
// retrieve stream information
if (avformat_find_stream_info (myFormatCtx, NULL) < 0)
{
Message::SendFail (TCollection_AsciiString ("FFmpeg: Couldn't find stream information in '") + theInput + "'");
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::SendFail ("Error: FFmpeg library is unavailable");
(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::SendWarning (TCollection_AsciiString ("Error while seeking ") + aStreamType + " stream to "
+ theSeekPts + " sec (" + (theSeekPts + StreamUnitsToSeconds (aStream, aStream.start_time)) + " sec)");
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::SendWarning (TCollection_AsciiString("Disaster! Seeking to ") + theSeekPts + " [" + aFileName + "] has failed.");
return false;
#else
(void )theSeekPts;
(void )theToSeekBack;
return false;
#endif
}