mirror of
https://github.com/Open-Cascade-SAS/OCCT.git
synced 2026-08-10 08:19:37 +08:00
6c24544fe1
- Refactor boolean expressions and improve code readability across multiple files - Simplified boolean expressions by removing unnecessary comparisons to true/false. - Replaced explicit boolean checks with direct variable usage Used flags: readability-static-accessed-through-instance readability-simplify-boolean-expr performance-for-range-copy performance-move-const-arg misc-unused-parameters misc-redundant-expression
565 lines
17 KiB
C++
565 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 "../Media/Media_FFmpegCompatibility.pxx"
|
|
|
|
#include <Message.hxx>
|
|
#include <Message_Messenger.hxx>
|
|
|
|
IMPLEMENT_STANDARD_RTTIEXT(Media_FormatContext, Standard_Transient)
|
|
|
|
namespace
|
|
{
|
|
constexpr double THE_SECONDS_IN_HOUR = 3600.0;
|
|
constexpr double THE_SECONDS_IN_MINUTE = 60.0;
|
|
constexpr double THE_SECOND_IN_HOUR = 1.0 / THE_SECONDS_IN_HOUR;
|
|
constexpr 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
|
|
} // namespace
|
|
|
|
//=================================================================================================
|
|
|
|
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
|
|
}
|
|
|
|
//=================================================================================================
|
|
|
|
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
|
|
}
|
|
|
|
//=================================================================================================
|
|
|
|
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
|
|
}
|
|
|
|
//=================================================================================================
|
|
|
|
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
|
|
}
|
|
|
|
//=================================================================================================
|
|
|
|
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
|
|
}
|
|
|
|
//=================================================================================================
|
|
|
|
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
|
|
}
|
|
|
|
//=================================================================================================
|
|
|
|
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
|
|
}
|
|
|
|
//=================================================================================================
|
|
|
|
Media_FormatContext::Media_FormatContext()
|
|
: myFormatCtx(nullptr),
|
|
myPtsStartBase(0.0),
|
|
myDuration(0.0)
|
|
{
|
|
}
|
|
|
|
//=================================================================================================
|
|
|
|
Media_FormatContext::~Media_FormatContext()
|
|
{
|
|
Close();
|
|
}
|
|
|
|
//=================================================================================================
|
|
|
|
unsigned int Media_FormatContext::NbSteams() const
|
|
{
|
|
#ifdef HAVE_FFMPEG
|
|
return myFormatCtx->nb_streams;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
//=================================================================================================
|
|
|
|
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
|
|
}
|
|
|
|
//=================================================================================================
|
|
|
|
bool Media_FormatContext::OpenInput(const TCollection_AsciiString& theInput)
|
|
{
|
|
#ifdef HAVE_FFMPEG
|
|
const int avErrCode = avformat_open_input(&myFormatCtx, theInput.ToCString(), nullptr, nullptr);
|
|
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, nullptr) < 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 = std::min(myPtsStartBase, StreamUnitsToSeconds(aStream, aStream.start_time));
|
|
myDuration = std::max(myDuration, StreamUnitsToSeconds(aStream, aStream.duration));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
#else
|
|
Message::SendFail("Error: FFmpeg library is unavailable");
|
|
(void)theInput;
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
//=================================================================================================
|
|
|
|
void Media_FormatContext::Close()
|
|
{
|
|
if (myFormatCtx != nullptr)
|
|
{
|
|
#ifdef HAVE_FFMPEG
|
|
avformat_close_input(&myFormatCtx);
|
|
// avformat_free_context (myFormatCtx);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
//=================================================================================================
|
|
|
|
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";
|
|
}
|
|
|
|
//=================================================================================================
|
|
|
|
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;
|
|
}
|
|
|
|
//=================================================================================================
|
|
|
|
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 == nullptr)
|
|
{
|
|
#if FFMPEG_HAVE_AVCODEC_PARAMETERS
|
|
// For new API, need to allocate context and copy parameters
|
|
aCodecCtx = avcodec_alloc_context3(nullptr);
|
|
if (aCodecCtx != nullptr && avcodec_parameters_to_context(aCodecCtx, aStream.codecpar) < 0)
|
|
{
|
|
avcodec_free_context(&aCodecCtx);
|
|
aCodecCtx = nullptr;
|
|
}
|
|
#else
|
|
Standard_DISABLE_DEPRECATION_WARNINGS aCodecCtx = aStream.codec;
|
|
Standard_ENABLE_DEPRECATION_WARNINGS
|
|
#endif
|
|
}
|
|
|
|
char aFrmtBuff[4096] = {};
|
|
#if FFMPEG_NEW_API
|
|
// avcodec_string was removed in newer FFmpeg versions
|
|
if (aCodecCtx != nullptr)
|
|
{
|
|
Sprintf(aFrmtBuff,
|
|
"Stream #%d: %s",
|
|
theIndex,
|
|
aCodecCtx->codec ? aCodecCtx->codec->long_name : "Unknown");
|
|
}
|
|
else
|
|
{
|
|
Sprintf(aFrmtBuff, "Stream #%d: Unknown", theIndex);
|
|
}
|
|
#else
|
|
avcodec_string(aFrmtBuff, sizeof(aFrmtBuff), aCodecCtx, 0);
|
|
#endif
|
|
TCollection_AsciiString aStreamInfo(aFrmtBuff);
|
|
|
|
#if FFMPEG_HAVE_AVCODEC_PARAMETERS
|
|
// Clean up allocated context if we created it
|
|
if (theCodecCtx == nullptr && aCodecCtx != nullptr)
|
|
{
|
|
avcodec_free_context(&aCodecCtx);
|
|
}
|
|
#endif
|
|
|
|
if (aStream.sample_aspect_ratio.num
|
|
#if FFMPEG_HAVE_AVCODEC_PARAMETERS
|
|
&& av_cmp_q(aStream.sample_aspect_ratio, aStream.codecpar->sample_aspect_ratio) != 0)
|
|
#else
|
|
#ifdef _MSC_VER
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 4996) // deprecated declaration
|
|
#endif
|
|
&& av_cmp_q(aStream.sample_aspect_ratio, aStream.codec->sample_aspect_ratio) != 0)
|
|
#ifdef _MSC_VER
|
|
#pragma warning(pop)
|
|
#endif
|
|
#endif
|
|
{
|
|
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
|
|
}
|
|
|
|
//=================================================================================================
|
|
|
|
bool Media_FormatContext::ReadPacket(const occ::handle<Media_Packet>& thePacket)
|
|
{
|
|
if (thePacket.IsNull())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#ifdef HAVE_FFMPEG
|
|
return av_read_frame(myFormatCtx, thePacket->ChangePacket()) >= 0;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
//=================================================================================================
|
|
|
|
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;
|
|
#if FFMPEG_NEW_API
|
|
isSeekDone && theToSeekBack && aTries > 0; // cur_dts removed in newer FFmpeg
|
|
#else
|
|
isSeekDone && theToSeekBack && aTries > 0 && (aStream.cur_dts > aSeekTarget);
|
|
#endif
|
|
--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
|
|
}
|
|
|
|
//=================================================================================================
|
|
|
|
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
|
|
}
|