Visualization - Enhance FFmpeg Compatibility Layer and Update Video Recorder (#582)

- Creation of Media_FFmpegCompatibility.pxx to wrap deprecated functions and API changes.
- Refactoring of Media_FormatContext and Media_CodecContext to use the new compatibility layer.
- Updates to Image_VideoRecorder and its tests to leverage the new compatibility functions and ensure proper codec context handling.
This commit is contained in:
Pasukhin Dmitry
2025-06-27 14:38:01 +01:00
committed by GitHub
parent 6b69f59803
commit 22d437b771
33 changed files with 583 additions and 2287 deletions

View File

@@ -2,4 +2,5 @@
set(OCCT_TKService_GTests_FILES_LOCATION "${CMAKE_CURRENT_LIST_DIR}")
set(OCCT_TKService_GTests_FILES
Image_VideoRecorder_Test.cxx
)

View File

@@ -0,0 +1,156 @@
// Copyright (c) 2025 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.
#include <Image_VideoRecorder.hxx>
#include <Image_PixMap.hxx>
#include <gtest/gtest.h>
class Image_VideoRecorderTest : public ::testing::Test
{
protected:
void SetUp() override { myRecorder = new Image_VideoRecorder(); }
void TearDown() override
{
if (!myRecorder.IsNull())
{
myRecorder->Close();
}
}
Handle(Image_VideoRecorder) myRecorder;
};
TEST_F(Image_VideoRecorderTest, DefaultConstructor)
{
EXPECT_FALSE(myRecorder.IsNull());
EXPECT_EQ(0, myRecorder->FrameCount());
}
TEST_F(Image_VideoRecorderTest, VideoParamsStructure)
{
Image_VideoParams params;
// Test default values
EXPECT_EQ(0, params.Width);
EXPECT_EQ(0, params.Height);
EXPECT_EQ(0, params.FpsNum);
EXPECT_EQ(1, params.FpsDen);
EXPECT_TRUE(params.Format.IsEmpty());
EXPECT_TRUE(params.VideoCodec.IsEmpty());
EXPECT_TRUE(params.PixelFormat.IsEmpty());
// Test setters
params.SetFramerate(30);
EXPECT_EQ(30, params.FpsNum);
EXPECT_EQ(1, params.FpsDen);
params.SetFramerate(25, 2);
EXPECT_EQ(25, params.FpsNum);
EXPECT_EQ(2, params.FpsDen);
}
TEST_F(Image_VideoRecorderTest, OpenVideoFile)
{
#ifdef HAVE_FFMPEG
Image_VideoParams params;
params.Width = 320;
params.Height = 240;
params.SetFramerate(15); // Low framerate for test
params.Format = "avi";
params.VideoCodec = "mpeg4"; // Use a commonly available codec
params.PixelFormat = "yuv420p";
// Test opening a valid video file
Standard_Boolean isOpened = myRecorder->Open("test_video.avi", params);
EXPECT_TRUE(isOpened);
if (isOpened)
{
// Test frame access
Image_PixMap& frame = myRecorder->ChangeFrame();
EXPECT_EQ(params.Width, frame.Width());
EXPECT_EQ(params.Height, frame.Height());
EXPECT_EQ(Image_Format_RGBA, frame.Format());
myRecorder->Close();
}
#endif
}
TEST_F(Image_VideoRecorderTest, InvalidParameters)
{
#ifdef HAVE_FFMPEG
Image_VideoParams params;
// Leave parameters invalid (width=0, height=0)
Standard_Boolean isOpened = myRecorder->Open("invalid_test.avi", params);
EXPECT_FALSE(isOpened);
#endif
}
TEST_F(Image_VideoRecorderTest, WriteFrames)
{
#ifdef HAVE_FFMPEG
Image_VideoParams params;
params.Width = 160; // Small size for fast test
params.Height = 120;
params.SetFramerate(10); // Low framerate
params.Format = "avi";
params.VideoCodec = "mpeg4";
params.PixelFormat = "yuv420p";
Standard_Boolean isOpened = myRecorder->Open("test_frames.avi", params);
if (isOpened)
{
// Fill frame with test pattern
Image_PixMap& frame = myRecorder->ChangeFrame();
// Create a simple red-to-blue gradient
for (Standard_Integer y = 0; y < params.Height; ++y)
{
for (Standard_Integer x = 0; x < params.Width; ++x)
{
Standard_Byte* pixel = frame.ChangeData() + (y * frame.SizeRowBytes()) + (x * 4);
pixel[0] = (Standard_Byte)(255 * x / params.Width); // Red gradient
pixel[1] = 0; // Green
pixel[2] = (Standard_Byte)(255 * y / params.Height); // Blue gradient
pixel[3] = 255; // Alpha
}
}
// Test writing a few frames
EXPECT_EQ(0, myRecorder->FrameCount());
EXPECT_TRUE(myRecorder->PushFrame());
EXPECT_EQ(1, myRecorder->FrameCount());
EXPECT_TRUE(myRecorder->PushFrame());
EXPECT_EQ(2, myRecorder->FrameCount());
EXPECT_TRUE(myRecorder->PushFrame());
EXPECT_EQ(3, myRecorder->FrameCount());
myRecorder->Close();
}
#endif // HAVE_FFMPEG
}
TEST_F(Image_VideoRecorderTest, CloseWithoutOpen)
{
// Test that closing without opening doesn't crash
myRecorder->Close();
EXPECT_EQ(0, myRecorder->FrameCount());
}

View File

@@ -13,18 +13,13 @@
// 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 <Image_VideoRecorder.hxx>
#include "../Media/Media_FFmpegCompatibility.pxx"
#include <Message.hxx>
#include <Message_Messenger.hxx>
#ifdef HAVE_FFMPEG
// Suppress deprecation warnings - it is difficult to provide compatibility with old and new API
// at once since new APIs are introduced too often. Should be disabled from time to time to clean
// up usage of old APIs.
@@ -34,22 +29,6 @@ _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
Standard_DISABLE_DEPRECATION_WARNINGS
#endif
extern "C" {
#ifdef _MSC_VER
// suppress some common warnings in FFmpeg headers
#pragma warning(disable : 4244)
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#ifdef _MSC_VER
#pragma warning(default : 4244)
#endif
};
// Undefine macro that clashes with name used by field of Image_VideoParams;
// this macro is defined in headers of older versions of libavutil
// (see definition of macro FF_API_PIX_FMT in version.h)
@@ -59,14 +38,15 @@ Standard_DISABLE_DEPRECATION_WARNINGS
#endif
IMPLEMENT_STANDARD_RTTIEXT(Image_VideoRecorder, Standard_Transient)
IMPLEMENT_STANDARD_RTTIEXT(Image_VideoRecorder, Standard_Transient)
//=================================================================================================
//=================================================================================================
Image_VideoRecorder::Image_VideoRecorder()
Image_VideoRecorder::Image_VideoRecorder()
: myAVContext(NULL),
myVideoStream(NULL),
myVideoCodec(NULL),
myCodecCtx(NULL),
myFrame(NULL),
myScaleCtx(NULL),
myFrameCount(0)
@@ -76,7 +56,7 @@ Image_VideoRecorder::Image_VideoRecorder()
#ifdef HAVE_FFMPEG
// initialize libavcodec, and register all codecs and formats, should be done once
av_register_all();
ffmpeg_register_all();
#endif
}
@@ -102,6 +82,21 @@ TCollection_AsciiString Image_VideoRecorder::formatAvError(const int theError) c
//=================================================================================================
AVCodecContext* Image_VideoRecorder::getCodecContext() const
{
#ifdef HAVE_FFMPEG
#if FFMPEG_HAVE_AVCODEC_PARAMETERS
return myCodecCtx;
#else
return myVideoStream != NULL ? myVideoStream->codec : NULL;
#endif
#else
return NULL;
#endif
}
//=================================================================================================
void Image_VideoRecorder::Close()
{
#ifdef HAVE_FFMPEG
@@ -129,7 +124,15 @@ void Image_VideoRecorder::Close()
// close each codec
if (myVideoStream != NULL)
{
#if FFMPEG_HAVE_AVCODEC_PARAMETERS
if (myCodecCtx != NULL)
{
avcodec_free_context(&myCodecCtx);
myCodecCtx = NULL;
}
#else
avcodec_close(myVideoStream->codec);
#endif
myVideoStream = NULL;
}
if (myFrame != NULL)
@@ -235,12 +238,12 @@ Standard_Boolean Image_VideoRecorder::addVideoStream(const Image_VideoParams& th
TCollection_AsciiString aCodecName;
if (!theParams.VideoCodec.IsEmpty())
{
myVideoCodec = avcodec_find_encoder_by_name(theParams.VideoCodec.ToCString());
myVideoCodec = ffmpeg_find_encoder_by_name(theParams.VideoCodec.ToCString());
aCodecName = theParams.VideoCodec;
}
else
{
myVideoCodec = avcodec_find_encoder((AVCodecID)theDefCodecId);
myVideoCodec = ffmpeg_find_encoder((AVCodecID)theDefCodecId);
aCodecName = avcodec_get_name((AVCodecID)theDefCodecId);
}
if (myVideoCodec == NULL)
@@ -258,8 +261,21 @@ Standard_Boolean Image_VideoRecorder::addVideoStream(const Image_VideoParams& th
}
myVideoStream->id = myAVContext->nb_streams - 1;
#if FFMPEG_HAVE_AVCODEC_PARAMETERS
// For FFmpeg 5.0+, allocate and use separate codec context
myCodecCtx = avcodec_alloc_context3(myVideoCodec);
if (myCodecCtx == NULL)
{
::Message::SendFail("Error: can not allocate codec context");
return Standard_False;
}
AVCodecContext* aCodecCtx = myCodecCtx;
#else
// For FFmpeg 4.x, use stream's codec context
AVCodecContext* aCodecCtx = myVideoStream->codec;
aCodecCtx->codec_id = aCodecId;
#endif
aCodecCtx->codec_id = aCodecId;
// resolution must be a multiple of two
aCodecCtx->width = theParams.Width;
aCodecCtx->height = theParams.Height;
@@ -275,6 +291,15 @@ Standard_Boolean Image_VideoRecorder::addVideoStream(const Image_VideoParams& th
{
aCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
#if FFMPEG_HAVE_AVCODEC_PARAMETERS
// Copy codec context parameters to stream
if (avcodec_parameters_from_context(myVideoStream->codecpar, aCodecCtx) < 0)
{
::Message::SendFail("Error: can not copy codec parameters to stream");
return Standard_False;
}
#endif
return Standard_True;
#else
(void)theParams;
@@ -289,37 +314,43 @@ Standard_Boolean Image_VideoRecorder::openVideoCodec(const Image_VideoParams& th
{
#ifdef HAVE_FFMPEG
AVDictionary* anOptions = NULL;
AVCodecContext* aCodecCtx = myVideoStream->codec;
AVCodecContext* aCodecCtx = getCodecContext();
if (aCodecCtx == NULL)
{
::Message::SendFail("Error: codec context is not available");
return Standard_False;
}
// setup default values
aCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
// av_dict_set (&anOptions, "threads", "auto", 0);
if (aCodecCtx->codec == avcodec_find_encoder_by_name("mpeg2video"))
if (myVideoCodec == ffmpeg_find_encoder_by_name("mpeg2video"))
{
// just for testing, we also add B frames
aCodecCtx->max_b_frames = 2;
aCodecCtx->bit_rate = 6000000;
}
else if (aCodecCtx->codec == avcodec_find_encoder_by_name("mpeg4"))
else if (myVideoCodec == ffmpeg_find_encoder_by_name("mpeg4"))
{
//
}
else if (aCodecCtx->codec == avcodec_find_encoder_by_name("mjpeg"))
else if (myVideoCodec == ffmpeg_find_encoder_by_name("mjpeg"))
{
aCodecCtx->pix_fmt = AV_PIX_FMT_YUVJ420P;
aCodecCtx->qmin = aCodecCtx->qmax = 5;
}
else if (aCodecCtx->codec == avcodec_find_encoder_by_name("huffyuv"))
else if (myVideoCodec == ffmpeg_find_encoder_by_name("huffyuv"))
{
aCodecCtx->pix_fmt = AV_PIX_FMT_RGB24;
}
else if (aCodecCtx->codec == avcodec_find_encoder_by_name("png"))
else if (myVideoCodec == ffmpeg_find_encoder_by_name("png"))
{
aCodecCtx->pix_fmt = AV_PIX_FMT_RGB24;
aCodecCtx->compression_level = 9; // 0..9
}
else if (aCodecCtx->codec == avcodec_find_encoder_by_name("h264")
|| aCodecCtx->codec == avcodec_find_encoder_by_name("libx264"))
else if (myVideoCodec == ffmpeg_find_encoder_by_name("h264")
|| myVideoCodec == ffmpeg_find_encoder_by_name("libx264"))
{
// use CRF (Constant Rate Factor) as best single-pass compressing method
// clang-format off
@@ -335,8 +366,8 @@ Standard_Boolean Image_VideoRecorder::openVideoCodec(const Image_VideoParams& th
// av_dict_set (&anOptions, "profile", "baseline", 0);
// av_dict_set (&anOptions, "level", "3.0", 0);
}
else if (aCodecCtx->codec == avcodec_find_encoder_by_name("vp8")
|| aCodecCtx->codec == avcodec_find_encoder_by_name("vp9"))
else if (myVideoCodec == ffmpeg_find_encoder_by_name("vp8")
|| myVideoCodec == ffmpeg_find_encoder_by_name("vp9"))
{
av_dict_set(&anOptions, "crf", "20", 0); // quality 4-63, 10 is normal
}
@@ -445,7 +476,7 @@ Standard_Boolean Image_VideoRecorder::writeVideoFrame(const Standard_Boolean the
}
int aResAv = 0;
AVCodecContext* aCodecCtx = myVideoStream->codec;
AVCodecContext* aCodecCtx = getCodecContext();
if (!theToFlush)
{
uint8_t* aSrcData[4] = {(uint8_t*)myImgSrcRgba.ChangeData(), NULL, NULL, NULL};
@@ -459,12 +490,77 @@ Standard_Boolean Image_VideoRecorder::writeVideoFrame(const Standard_Boolean the
myFrame->linesize);
}
#if FFMPEG_HAVE_NEW_DECODE_API
// New API: use avcodec_send_frame/avcodec_receive_packet
if (!theToFlush)
{
myFrame->pts = myFrameCount;
aResAv = avcodec_send_frame(aCodecCtx, myFrame);
}
else
{
aResAv = avcodec_send_frame(aCodecCtx, NULL); // flush
}
if (aResAv < 0)
{
::Message::SendFail(TCollection_AsciiString("Error: can not send frame to encoder, ")
+ formatAvError(aResAv));
return Standard_False;
}
while (aResAv >= 0)
{
AVPacket* aPacket = av_packet_alloc();
aResAv = avcodec_receive_packet(aCodecCtx, aPacket);
if (aResAv == AVERROR(EAGAIN) || aResAv == AVERROR_EOF)
{
av_packet_free(&aPacket);
break; // need more input or end of stream
}
else if (aResAv < 0)
{
av_packet_free(&aPacket);
::Message::SendFail(TCollection_AsciiString("Error: can not encode video frame, ")
+ formatAvError(aResAv));
return Standard_False;
}
// rescale output packet timestamp values from codec to stream timebase
aPacket->pts = av_rescale_q_rnd(aPacket->pts,
aCodecCtx->time_base,
myVideoStream->time_base,
AVRounding(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
aPacket->dts = av_rescale_q_rnd(aPacket->dts,
aCodecCtx->time_base,
myVideoStream->time_base,
AVRounding(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
aPacket->duration =
av_rescale_q(aPacket->duration, aCodecCtx->time_base, myVideoStream->time_base);
aPacket->stream_index = myVideoStream->index;
// write the compressed frame to the media file
aResAv = av_interleaved_write_frame(myAVContext, aPacket);
av_packet_free(&aPacket);
if (aResAv < 0)
{
::Message::SendFail(TCollection_AsciiString("Error: can not write video frame, ")
+ formatAvError(aResAv));
return Standard_False;
}
}
#else
// Old API: use avcodec_encode_video2
AVPacket aPacket;
memset(&aPacket, 0, sizeof(aPacket));
av_init_packet(&aPacket);
{
// encode the image
myFrame->pts = myFrameCount;
if (!theToFlush)
{
myFrame->pts = myFrameCount;
}
int isGotPacket = 0;
aResAv = avcodec_encode_video2(aCodecCtx, &aPacket, theToFlush ? NULL : myFrame, &isGotPacket);
if (aResAv < 0)
@@ -506,6 +602,7 @@ Standard_Boolean Image_VideoRecorder::writeVideoFrame(const Standard_Boolean the
+ formatAvError(aResAv));
return Standard_False;
}
#endif
++myFrameCount;
return Standard_True;

View File

@@ -23,6 +23,7 @@
// forward declarations
struct AVFormatContext;
struct AVCodecContext;
struct AVStream;
struct AVCodec;
struct AVFrame;
@@ -111,6 +112,9 @@ protected:
//! Wrapper for av_strerror().
Standard_EXPORT TCollection_AsciiString formatAvError(const int theError) const;
//! Get codec context compatible with both old and new FFmpeg API.
Standard_EXPORT AVCodecContext* getCodecContext() const;
//! Append video stream.
//! theParams[in] video parameters
//! theDefCodecId[in] identifier of codec managed by FFmpeg library (AVCodecID enum)
@@ -135,6 +139,7 @@ protected:
AVFormatContext* myAVContext; //!< video context
AVStream* myVideoStream; //!< video stream
AVCodec* myVideoCodec; //!< video codec
AVCodecContext* myCodecCtx; //!< codec context (for FFmpeg 5.0+)
AVFrame* myFrame; //!< frame to record
SwsContext* myScaleCtx; //!< scale context for conversion from RGBA to YUV

View File

@@ -6,6 +6,7 @@ set(OCCT_Media_FILES
Media_BufferPool.hxx
Media_CodecContext.cxx
Media_CodecContext.hxx
Media_FFmpegCompatibility.pxx
Media_FormatContext.cxx
Media_FormatContext.hxx
Media_Frame.cxx

View File

@@ -18,6 +18,7 @@
#endif
#include <Media_CodecContext.hxx>
#include "../Media/Media_FFmpegCompatibility.pxx"
#include <Media_Frame.hxx>
#include <Media_FormatContext.hxx>
@@ -26,15 +27,6 @@
#include <Message_Messenger.hxx>
#include <OSD_Parallel.hxx>
#ifdef HAVE_FFMPEG
#include <Standard_WarningsDisable.hxx>
extern "C"
{
#include <libavformat/avformat.h>
};
#include <Standard_WarningsRestore.hxx>
#endif
IMPLEMENT_STANDARD_RTTIEXT(Media_CodecContext, Standard_Transient)
//=================================================================================================
@@ -80,20 +72,35 @@ bool Media_CodecContext::Init(const AVStream& theStream,
{
#ifdef HAVE_FFMPEG
myStreamIndex = theStream.index;
#if FFMPEG_HAVE_AVCODEC_PARAMETERS
if (avcodec_parameters_to_context(myCodecCtx, theStream.codecpar) < 0)
{
Message::SendFail("Internal error: unable to copy codec parameters");
Close();
return false;
}
#else
// For older FFmpeg, copy from stream's codec context
if (avcodec_copy_context(myCodecCtx, theStream.codec) < 0)
{
Message::SendFail("Internal error: unable to copy codec context");
Close();
return false;
}
#endif
myTimeBase = av_q2d(theStream.time_base);
myPtsStartBase = thePtsStartBase;
myPtsStartStream = Media_FormatContext::StreamUnitsToSeconds(theStream, theStream.start_time);
#if FFMPEG_HAVE_AVCODEC_PARAMETERS
const AVCodecID aCodecId =
theCodecId != AV_CODEC_ID_NONE ? (AVCodecID)theCodecId : theStream.codecpar->codec_id;
myCodec = avcodec_find_decoder(aCodecId);
#else
const AVCodecID aCodecId = theCodecId != 0 ? (AVCodecID)theCodecId : theStream.codec->codec_id;
#endif
myCodec = ffmpeg_find_decoder(aCodecId);
if (myCodec == NULL)
{
Message::Send("FFmpeg: unable to find decoder", Message_Fail);
@@ -104,7 +111,12 @@ bool Media_CodecContext::Init(const AVStream& theStream,
myCodecCtx->codec_id = aCodecId;
AVDictionary* anOpts = NULL;
av_dict_set(&anOpts, "refcounted_frames", "1", 0);
#if FFMPEG_HAVE_AVCODEC_PARAMETERS
if (theStream.codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
#else
if (theStream.codec->codec_type == AVMEDIA_TYPE_VIDEO)
#endif
{
myCodecCtx->thread_count =
theNbThreads <= -1 ? OSD_Parallel::NbLogicalProcessors() : theNbThreads;
@@ -119,7 +131,7 @@ bool Media_CodecContext::Init(const AVStream& theStream,
myPixelAspectRatio = 1.0f;
if (theStream.sample_aspect_ratio.num
&& av_cmp_q(theStream.sample_aspect_ratio, myCodecCtx->sample_aspect_ratio))
&& av_cmp_q(theStream.sample_aspect_ratio, myCodecCtx->sample_aspect_ratio) != 0)
{
myPixelAspectRatio =
float(theStream.sample_aspect_ratio.num) / float(theStream.sample_aspect_ratio.den);
@@ -137,8 +149,13 @@ bool Media_CodecContext::Init(const AVStream& theStream,
}
}
#if FFMPEG_HAVE_AVCODEC_PARAMETERS
if (theStream.codecpar->codec_type == AVMEDIA_TYPE_VIDEO
&& (myCodecCtx->width <= 0 || myCodecCtx->height <= 0))
#else
if (theStream.codec->codec_type == AVMEDIA_TYPE_VIDEO
&& (myCodecCtx->width <= 0 || myCodecCtx->height <= 0))
#endif
{
Message::SendFail("FFmpeg: video stream has invalid dimensions");
Close();
@@ -162,7 +179,13 @@ void Media_CodecContext::Close()
if (myCodecCtx != NULL)
{
#ifdef HAVE_FFMPEG
#if FFMPEG_NEW_API
avcodec_free_context(&myCodecCtx);
#else
avcodec_close(myCodecCtx);
av_free(myCodecCtx);
myCodecCtx = NULL;
#endif
#endif
}
}
@@ -218,12 +241,22 @@ bool Media_CodecContext::SendPacket(const Handle(Media_Packet)& thePacket)
}
#ifdef HAVE_FFMPEG
#if FFMPEG_HAVE_NEW_DECODE_API
const int aRes = avcodec_send_packet(myCodecCtx, thePacket->Packet());
if (aRes < 0 && aRes != AVERROR_EOF)
{
return false;
}
return true;
#else
// For older FFmpeg versions, fallback to older decode API if needed
const int aRes = avcodec_send_packet(myCodecCtx, thePacket->Packet());
if (aRes < 0 && aRes != AVERROR_EOF)
{
return false;
}
return true;
#endif
#else
return false;
#endif
@@ -239,6 +272,7 @@ bool Media_CodecContext::ReceiveFrame(const Handle(Media_Frame)& theFrame)
}
#ifdef HAVE_FFMPEG
#if FFMPEG_HAVE_NEW_DECODE_API
const int aRes2 = avcodec_receive_frame(myCodecCtx, theFrame->ChangeFrame());
if (aRes2 < 0)
{
@@ -250,6 +284,20 @@ bool Media_CodecContext::ReceiveFrame(const Handle(Media_Frame)& theFrame)
const double aFramePts = double(aPacketPts) * myTimeBase - myPtsStartBase;
theFrame->SetPts(aFramePts);
return true;
#else
// For older FFmpeg, use the older decoding API
const int aRes2 = avcodec_receive_frame(myCodecCtx, theFrame->ChangeFrame());
if (aRes2 < 0)
{
return false;
}
const int64_t aPacketPts =
theFrame->BestEffortTimestamp() != AV_NOPTS_VALUE ? theFrame->BestEffortTimestamp() : 0;
const double aFramePts = double(aPacketPts) * myTimeBase - myPtsStartBase;
theFrame->SetPts(aFramePts);
return true;
#endif
#else
return false;
#endif

View File

@@ -0,0 +1,179 @@
// Created on: 2025-06-22
// Copyright (c) 2025 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.
//! FFmpeg compatibility layer for OCCT
//!
//! Provides compatibility between FFmpeg 4.4.x and 7.1.x by wrapping
//! deprecated/removed functions and handling API changes automatically.
#ifndef _FFmpeg_Compatibility_HeaderFile
#define _FFmpeg_Compatibility_HeaderFile
#ifdef HAVE_FFMPEG
// activate some C99 macros like UINT64_C in "stdint.h" which used by FFmpeg
#ifndef __STDC_CONSTANT_MACROS
#define __STDC_CONSTANT_MACROS
#endif
// Standard C headers needed for compilation
#include <stdint.h>
#include <Standard_WarningsDisable.hxx>
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
};
#include <Standard_WarningsRestore.hxx>
// Version detection macros
#define FFMPEG_VERSION_4_4 AV_VERSION_INT(58, 0, 0)
#define FFMPEG_VERSION_5_0 AV_VERSION_INT(59, 0, 0)
#define FFMPEG_VERSION_6_0 AV_VERSION_INT(60, 0, 0)
#define FFMPEG_VERSION_7_0 AV_VERSION_INT(61, 0, 0)
// Check if we're using FFmpeg 5.0+ (major API change point)
#if LIBAVCODEC_VERSION_INT >= FFMPEG_VERSION_5_0
#define FFMPEG_NEW_API 1
#else
#define FFMPEG_NEW_API 0
#endif
// Additional version checks for specific functions
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 100)
#define FFMPEG_HAVE_NEW_DECODE_API 1
#else
#define FFMPEG_HAVE_NEW_DECODE_API 0
#endif
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 0, 0)
#define FFMPEG_HAVE_AVCODEC_PARAMETERS 1
#else
#define FFMPEG_HAVE_AVCODEC_PARAMETERS 0
#endif
// Constant compatibility for different FFmpeg versions
// Error buffer size might be different
#ifndef AV_ERROR_MAX_STRING_SIZE
#define AV_ERROR_MAX_STRING_SIZE 64
#endif
// Codec flags compatibility
#ifndef AV_CODEC_FLAG_GLOBAL_HEADER
#define AV_CODEC_FLAG_GLOBAL_HEADER CODEC_FLAG_GLOBAL_HEADER
#endif
// Pixel format compatibility - old names to new names
#ifndef AV_PIX_FMT_YUV420P
#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P
#endif
#ifndef AV_PIX_FMT_RGBA
#define AV_PIX_FMT_RGBA PIX_FMT_RGBA
#endif
#ifndef AV_PIX_FMT_RGB24
#define AV_PIX_FMT_RGB24 PIX_FMT_RGB24
#endif
#ifndef AV_PIX_FMT_NONE
#define AV_PIX_FMT_NONE PIX_FMT_NONE
#endif
#ifndef AV_PIX_FMT_YUVJ420P
#define AV_PIX_FMT_YUVJ420P PIX_FMT_YUVJ420P
#endif
// For old FFmpeg versions that don't have AV_PIX_FMT_* constants
#ifndef PIX_FMT_YUV420P
#define PIX_FMT_YUV420P AV_PIX_FMT_YUV420P
#endif
#ifndef PIX_FMT_RGBA
#define PIX_FMT_RGBA AV_PIX_FMT_RGBA
#endif
#ifndef PIX_FMT_RGB24
#define PIX_FMT_RGB24 AV_PIX_FMT_RGB24
#endif
#ifndef PIX_FMT_NONE
#define PIX_FMT_NONE AV_PIX_FMT_NONE
#endif
#ifndef PIX_FMT_YUVJ420P
#define PIX_FMT_YUVJ420P AV_PIX_FMT_YUVJ420P
#endif
// AVRounding compatibility - handle missing AV_ prefix
#ifndef AV_ROUND_NEAR_INF
#ifdef AVROUND_NEAR_INF
#define AV_ROUND_NEAR_INF AVROUND_NEAR_INF
#else
#define AV_ROUND_NEAR_INF 5
#endif
#endif
#ifndef AV_ROUND_PASS_MINMAX
#ifdef AVROUND_PASS_MINMAX
#define AV_ROUND_PASS_MINMAX AVROUND_PASS_MINMAX
#else
#define AV_ROUND_PASS_MINMAX 8192
#endif
#endif
// Also define the old names for compatibility
#ifndef AVROUND_NEAR_INF
#define AVROUND_NEAR_INF AV_ROUND_NEAR_INF
#endif
#ifndef AVROUND_PASS_MINMAX
#define AVROUND_PASS_MINMAX AV_ROUND_PASS_MINMAX
#endif
// Compatibility functions and macros
// av_register_all() - deprecated/removed in FFmpeg 4.0+
inline void ffmpeg_register_all()
{
#if !FFMPEG_NEW_API
av_register_all();
#endif
}
// AVCodec constness changes
inline AVCodec* ffmpeg_find_encoder(enum AVCodecID id)
{
#if FFMPEG_NEW_API
return const_cast<AVCodec*>(avcodec_find_encoder(id));
#else
return avcodec_find_encoder(id);
#endif
}
inline AVCodec* ffmpeg_find_encoder_by_name(const char* name)
{
#if FFMPEG_NEW_API
return const_cast<AVCodec*>(avcodec_find_encoder_by_name(name));
#else
return avcodec_find_encoder_by_name(name);
#endif
}
inline AVCodec* ffmpeg_find_decoder(enum AVCodecID id)
{
#if FFMPEG_NEW_API
return const_cast<AVCodec*>(avcodec_find_decoder(id));
#else
return avcodec_find_decoder(id);
#endif
}
#endif // HAVE_FFMPEG
#endif // _FFmpeg_Compatibility_HeaderFile

View File

@@ -18,19 +18,11 @@
#endif
#include <Media_FormatContext.hxx>
#include "../Media/Media_FFmpegCompatibility.pxx"
#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
@@ -374,16 +366,53 @@ TCollection_AsciiString Media_FormatContext::StreamInfo(unsigned int theIndex
AVCodecContext* aCodecCtx = theCodecCtx;
if (aCodecCtx == NULL)
{
#if FFMPEG_HAVE_AVCODEC_PARAMETERS
// For new API, need to allocate context and copy parameters
aCodecCtx = avcodec_alloc_context3(NULL);
if (aCodecCtx != NULL && avcodec_parameters_to_context(aCodecCtx, aStream.codecpar) < 0)
{
avcodec_free_context(&aCodecCtx);
aCodecCtx = NULL;
}
#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 != NULL)
{
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 == NULL && aCodecCtx != NULL)
{
avcodec_free_context(&aCodecCtx);
}
#endif
if (aStream.sample_aspect_ratio.num
&& av_cmp_q(aStream.sample_aspect_ratio, aStream.codecpar->sample_aspect_ratio))
#if FFMPEG_HAVE_AVCODEC_PARAMETERS
&& av_cmp_q(aStream.sample_aspect_ratio, aStream.codecpar->sample_aspect_ratio) != 0)
#else
&& av_cmp_q(aStream.sample_aspect_ratio, aStream.codec->sample_aspect_ratio) != 0)
#endif
{
AVRational aDispAspectRatio;
av_reduce(&aDispAspectRatio.num,
@@ -468,7 +497,11 @@ bool Media_FormatContext::SeekStream(unsigned int theStreamId,
// 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);