Files
OCCT/src/Visualization/TKService/Media/Media_FormatContext.cxx
T
Pasukhin Dmitry 6c24544fe1 Coding - Apply more flags from Clang-tidy (#977)
- 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
2026-01-03 12:18:59 +00:00

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
}