From e132ac996c45c192b45b7a0958bfe72faf93ce05 Mon Sep 17 00:00:00 2001 From: julee Date: Wed, 17 Jul 2024 12:29:17 +0800 Subject: [PATCH] fix: resolve crashes caused by using the ffmpeg library - Addressed issue where repeated reloading of the ffmpeg library led to crashes. Previously, each component attempted to load the ffmpeg library and executed an unload before loading, causing other components to retain stale function addresses if the library was reloaded at a different address. This issue was particularly prevalent on macOS. - Fixed undefined behavior due to incorrect size assumption of std::function objects. On certain platforms like arm64 macOS, sizeof(std::function) is 32, not the same as a pointer size. This discrepancy led to undefined behavior when forcibly casting. This fix ensures that the library is loaded only once per process and corrects the handling of std::function objects to prevent crashes. --- .../src/ffmpeg/FFmpegLibraryFunctions.cpp | 120 +++++++++++++----- YUViewLib/src/ffmpeg/FFmpegLibraryFunctions.h | 107 ++++++++-------- 2 files changed, 137 insertions(+), 90 deletions(-) diff --git a/YUViewLib/src/ffmpeg/FFmpegLibraryFunctions.cpp b/YUViewLib/src/ffmpeg/FFmpegLibraryFunctions.cpp index d4ffa014c..78def6496 100644 --- a/YUViewLib/src/ffmpeg/FFmpegLibraryFunctions.cpp +++ b/YUViewLib/src/ffmpeg/FFmpegLibraryFunctions.cpp @@ -36,12 +36,29 @@ namespace FFmpeg { +static QMutex mutex; +static bool isLibLoaded; +static QString avformatLibPath; +static QString avcodecLibPath; +static QString avutilLibPath; +static QString swresampleLibPath; +static QString libDirectoryPath; +static QLibrary libAvutil; +static QLibrary libSwresample; +static QLibrary libAvcodec; +static QLibrary libAvformat; + +FFmpegLibraryFunctions::AvFormatFunctions FFmpegLibraryFunctions::avformat{}; +FFmpegLibraryFunctions::AvCodecFunctions FFmpegLibraryFunctions::avcodec{}; +FFmpegLibraryFunctions::AvUtilFunctions FFmpegLibraryFunctions::avutil{}; +FFmpegLibraryFunctions::SwResampleFunction FFmpegLibraryFunctions::swresample{}; + namespace { template bool resolveFunction(QLibrary & lib, - std::function &function, + T &function, const char * symbolName, QStringList * logList) { @@ -53,7 +70,7 @@ bool resolveFunction(QLibrary & lib, return false; } - function = reinterpret_cast(ptr); + function = reinterpret_cast(ptr); return true; } @@ -179,13 +196,15 @@ bool bindLibraryFunctions(QLibrary & lib, FFmpegLibraryFunctions::~FFmpegLibraryFunctions() { - this->unloadAllLibraries(); } bool FFmpegLibraryFunctions::loadFFmpegLibraryInPath(QString path, LibraryVersion &libraryVersion) { // We will load the following libraries (in this order): // avutil, swresample, avcodec, avformat. + QMutexLocker locker(&mutex); + if (isLibLoaded && libDirectoryPath == path) + return true; if (!path.isEmpty()) { @@ -210,7 +229,7 @@ bool FFmpegLibraryFunctions::loadFFmpegLibraryInPath(QString path, LibraryVersio bool success = false; for (unsigned i = 0; i < nrNames; i++) { - this->unloadAllLibraries(); + this->unloadAllLibrariesLocked(); // This is how we the library name is constructed per platform QString constructLibName; @@ -224,21 +243,22 @@ bool FFmpegLibraryFunctions::loadFFmpegLibraryInPath(QString path, LibraryVersio constructLibName = "lib%1.%2.dylib"; auto loadLibrary = - [this, &constructLibName, &path](QLibrary &lib, QString libName, unsigned version) { + [this, &constructLibName, &path](QLibrary &lib, QString libName, unsigned version, QString &output) { auto filename = constructLibName.arg(libName).arg(version); lib.setFileName(path + filename); auto success = lib.load(); this->log("Loading library " + filename + (success ? " succeded" : " failed")); + output = path + filename; return success; }; - if (!loadLibrary(this->libAvutil, "avutil", libraryVersion.avutil.major)) + if (!loadLibrary(libAvutil, "avutil", libraryVersion.avutil.major, avutilLibPath)) continue; - if (!loadLibrary(this->libSwresample, "swresample", libraryVersion.swresample.major)) + if (!loadLibrary(libSwresample, "swresample", libraryVersion.swresample.major, swresampleLibPath)) continue; - if (!loadLibrary(this->libAvcodec, "avcodec", libraryVersion.avcodec.major)) + if (!loadLibrary(libAvcodec, "avcodec", libraryVersion.avcodec.major, avcodecLibPath)) continue; - if (!loadLibrary(this->libAvformat, "avformat", libraryVersion.avformat.major)) + if (!loadLibrary(libAvformat, "avformat", libraryVersion.avformat.major, avformatLibPath)) continue; success = true; @@ -247,16 +267,22 @@ bool FFmpegLibraryFunctions::loadFFmpegLibraryInPath(QString path, LibraryVersio if (!success) { - this->unloadAllLibraries(); + this->unloadAllLibrariesLocked(); return false; } - success = (bindLibraryFunctions(this->libAvformat, this->avformat, this->logList) && - bindLibraryFunctions(this->libAvcodec, this->avcodec, this->logList) && - bindLibraryFunctions(this->libAvutil, this->avutil, this->logList) && - bindLibraryFunctions(this->libSwresample, this->swresample, this->logList)); + success = (bindLibraryFunctions(libAvformat, avformat, this->logList) && + bindLibraryFunctions(libAvcodec, avcodec, this->logList) && + bindLibraryFunctions(libAvutil, avutil, this->logList) && + bindLibraryFunctions(libSwresample, swresample, this->logList)); this->log(QString("Binding functions ") + (success ? "successfull" : "failed")); + if (!success) + { + this->unloadAllLibrariesLocked(); + return false; + } + isLibLoaded = true; return success; } @@ -265,7 +291,18 @@ bool FFmpegLibraryFunctions::loadFFMpegLibrarySpecific(QString avFormatLib, QString avUtilLib, QString swResampleLib) { - this->unloadAllLibraries(); + QMutexLocker locker(&mutex); + this->log("loadFFMpegLibrarySpecific()..."); + if (isLibLoaded + && avFormatLib == avformatLibPath + && avCodecLib == avcodecLibPath + && avUtilLib == avutilLibPath + && swResampleLib == swresampleLibPath) { + return true; + } + if (isLibLoaded) { + this->unloadAllLibrariesLocked(); + } auto loadLibrary = [this](QLibrary &lib, QString libPath) { lib.setFileName(libPath); @@ -274,23 +311,31 @@ bool FFmpegLibraryFunctions::loadFFMpegLibrarySpecific(QString avFormatLib, return success; }; - auto success = (loadLibrary(this->libAvutil, avUtilLib) && // - loadLibrary(this->libSwresample, swResampleLib) && // - loadLibrary(this->libAvcodec, avCodecLib) && // - loadLibrary(this->libAvformat, avFormatLib)); + auto success = (loadLibrary(libAvutil, avUtilLib) && // + loadLibrary(libSwresample, swResampleLib) && // + loadLibrary(libAvcodec, avCodecLib) && // + loadLibrary(libAvformat, avFormatLib)); - if (!success) - { - this->unloadAllLibraries(); + if (!success) { + this->unloadAllLibrariesLocked(); return false; } - success = (bindLibraryFunctions(this->libAvformat, this->avformat, this->logList) && - bindLibraryFunctions(this->libAvcodec, this->avcodec, this->logList) && - bindLibraryFunctions(this->libAvutil, this->avutil, this->logList) && - bindLibraryFunctions(this->libSwresample, this->swresample, this->logList)); + success = (bindLibraryFunctions(libAvformat, avformat, this->logList) && + bindLibraryFunctions(libAvcodec, avcodec, this->logList) && + bindLibraryFunctions(libAvutil, avutil, this->logList) && + bindLibraryFunctions(libSwresample, swresample, this->logList)); this->log(QString("Binding functions ") + (success ? "successfull" : "failed")); + if (!success) { + this->unloadAllLibrariesLocked(); + return false; + } + avformatLibPath = avFormatLib; + avcodecLibPath = avCodecLib; + avutilLibPath = avUtilLib; + swresampleLibPath = swResampleLib; + isLibLoaded = true; return success; } @@ -311,13 +356,18 @@ void FFmpegLibraryFunctions::addLibNamesToList(QString libName, } } -void FFmpegLibraryFunctions::unloadAllLibraries() +void FFmpegLibraryFunctions::unloadAllLibrariesLocked() { this->log("Unloading all loaded libraries"); - this->libAvutil.unload(); - this->libSwresample.unload(); - this->libAvcodec.unload(); - this->libAvformat.unload(); + libAvutil.unload(); + libSwresample.unload(); + libAvcodec.unload(); + libAvformat.unload(); + avcodec = {}; + avformat = {}; + avutil = {}; + swresample = {}; + isLibLoaded = false; } QStringList FFmpegLibraryFunctions::getLibPaths() const @@ -338,10 +388,10 @@ QStringList FFmpegLibraryFunctions::getLibPaths() const } }; - addName("AVCodec", this->libAvcodec); - addName("AVFormat", this->libAvformat); - addName("AVUtil", this->libAvutil); - addName("SwResample", this->libSwresample); + addName("AVCodec", libAvcodec); + addName("AVFormat", libAvformat); + addName("AVUtil", libAvutil); + addName("SwResample", libSwresample); return libPaths; } diff --git a/YUViewLib/src/ffmpeg/FFmpegLibraryFunctions.h b/YUViewLib/src/ffmpeg/FFmpegLibraryFunctions.h index 4f0487a62..44bec220f 100644 --- a/YUViewLib/src/ffmpeg/FFmpegLibraryFunctions.h +++ b/YUViewLib/src/ffmpeg/FFmpegLibraryFunctions.h @@ -34,6 +34,8 @@ #include "FFMpegLibrariesTypes.h" #include +#include +#include #include namespace FFmpeg @@ -57,86 +59,81 @@ class FFmpegLibraryFunctions struct AvFormatFunctions { - std::function av_register_all; - std::function - avformat_open_input; - std::function avformat_close_input; - std::function avformat_find_stream_info; - std::function av_read_frame; - std::function - av_seek_frame; - std::function avformat_version; + void (*av_register_all)(); + int(*avformat_open_input)( + AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options); + ; + void (*avformat_close_input)(AVFormatContext **s) ; + int (*avformat_find_stream_info)(AVFormatContext *ic, AVDictionary **options) ; + int (*av_read_frame)(AVFormatContext *s, AVPacket *pkt) ; + int (*av_seek_frame)(AVFormatContext *s, int stream_index, int64_t timestamp, int flags) + ; + unsigned(*avformat_version)() ; }; - AvFormatFunctions avformat{}; + static AvFormatFunctions avformat; struct AvCodecFunctions { - std::function avcodec_find_decoder; - std::function avcodec_alloc_context3; - std::function - avcodec_open2; - std::function avcodec_free_context; - std::function av_packet_alloc; - std::function av_packet_free; - std::function av_init_packet; - std::function av_packet_unref; - std::function avcodec_flush_buffers; - std::function avcodec_version; - std::function avcodec_get_name; - std::function avcodec_parameters_alloc; + AVCodec *(*avcodec_find_decoder)(AVCodecID id) ; + AVCodecContext *(*avcodec_alloc_context3)(const AVCodec *codec) ; + int (*avcodec_open2)(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options) ; + void (*avcodec_free_context)(AVCodecContext **avctx) ; + AVPacket *(*av_packet_alloc)() ; + void (*av_packet_free)(AVPacket **pkt) ; + void (*av_init_packet)(AVPacket *pkt) ; + void (*av_packet_unref)(AVPacket *pkt) ; + void (*avcodec_flush_buffers)(AVCodecContext *avctx) ; + unsigned(*avcodec_version)() ; + const char *(*avcodec_get_name)(AVCodecID id) ; + AVCodecParameters *(*avcodec_parameters_alloc)() ; // The following functions are part of the new API. // We will check if it is available. If not, we will use the old decoding API. bool newParametersAPIAvailable{}; - std::function avcodec_send_packet; - std::function avcodec_receive_frame; - std::function - avcodec_parameters_to_context; - std::function avcodec_decode_video2; + int (*avcodec_send_packet)(AVCodecContext *avctx, const AVPacket *avpkt) ; + int (*avcodec_receive_frame)(AVCodecContext *avctx, AVFrame *frame) ; + int (*avcodec_parameters_to_context)(AVCodecContext *codec, const AVCodecParameters *par) + ; + void (*avcodec_decode_video2)() ; }; - AvCodecFunctions avcodec{}; + static AvCodecFunctions avcodec; struct AvUtilFunctions { - std::function av_frame_alloc; - std::function av_frame_free; - std::function av_mallocz; - std::function avutil_version; - std::function - av_dict_set; - std::function - av_dict_get; - std::function - av_frame_get_side_data; - std::function av_frame_get_metadata; - std::function av_log_set_callback; - std::function av_log_set_level; - std::function av_pix_fmt_desc_get; - std::function av_pix_fmt_desc_next; - std::function av_pix_fmt_desc_get_id; + AVFrame *(*av_frame_alloc)() ; + void (*av_frame_free)(AVFrame **frame) ; + void (*av_mallocz)(size_t size) ; + unsigned(*avutil_version)() ; + int (*av_dict_set)(AVDictionary **pm, const char *key, const char *value, int flags) + ; + AVDictionaryEntry*(*av_dict_get)( + AVDictionary *m, const char *key, const AVDictionaryEntry *prev, int flags) + ; + AVFrameSideData *(*av_frame_get_side_data)(const AVFrame *frame, AVFrameSideDataType type) + ; + AVDictionary *(*av_frame_get_metadata)(const AVFrame *frame) ; + void (*av_log_set_callback)(void (*callback)(void *, int, const char *, va_list)) ; + void (*av_log_set_level)(int level) ; + AVPixFmtDescriptor *(*av_pix_fmt_desc_get)(AVPixelFormat pix_fmt) ; + AVPixFmtDescriptor *(*av_pix_fmt_desc_next)(const AVPixFmtDescriptor *prev) ; + AVPixelFormat (*av_pix_fmt_desc_get_id)(const AVPixFmtDescriptor *desc) ; }; - AvUtilFunctions avutil{}; + static AvUtilFunctions avutil; struct SwResampleFunction { - std::function swresample_version; + unsigned(*swresample_version)() ; }; - SwResampleFunction swresample{}; + static SwResampleFunction swresample; void setLogList(QStringList *l) { logList = l; } private: void addLibNamesToList(QString libName, QStringList &l, const QLibrary &lib) const; - void unloadAllLibraries(); + void unloadAllLibrariesLocked(); QStringList *logList{}; void log(QString message); - QLibrary libAvutil; - QLibrary libSwresample; - QLibrary libAvcodec; - QLibrary libAvformat; }; } // namespace FFmpeg