diff --git a/.ci/linux-appimage/docker.sh b/.ci/linux-appimage/docker.sh index bb5488c59..fc866940e 100755 --- a/.ci/linux-appimage/docker.sh +++ b/.ci/linux-appimage/docker.sh @@ -3,7 +3,7 @@ #Building Citra mkdir build cd build -cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_FFMPEG_VIDEO_DUMPER=ON +cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON ninja ctest -VV -C Release diff --git a/.ci/linux-fresh/docker.sh b/.ci/linux-fresh/docker.sh index 29bf52cf3..fd145ed62 100755 --- a/.ci/linux-fresh/docker.sh +++ b/.ci/linux-fresh/docker.sh @@ -1,7 +1,7 @@ #!/bin/bash -ex mkdir build && cd build -cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_FFMPEG_VIDEO_DUMPER=ON +cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON ninja ctest -VV -C Release diff --git a/.ci/linux-mingw/docker.sh b/.ci/linux-mingw/docker.sh index c26bfa30c..df1a4c9bf 100755 --- a/.ci/linux-mingw/docker.sh +++ b/.ci/linux-mingw/docker.sh @@ -5,7 +5,7 @@ mkdir -p "$HOME/.ccache/" echo 'max_size = 3.0G' > "$HOME/.ccache/ccache.conf" mkdir build && cd build -cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DCITRA_USE_CCACHE=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_MF=ON -DENABLE_FFMPEG_VIDEO_DUMPER=ON -DCMAKE_NO_SYSTEM_FROM_IMPORTED=TRUE -DCOMPILE_WITH_DWARF=OFF +cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DCITRA_USE_CCACHE=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_MF=ON -DCMAKE_NO_SYSTEM_FROM_IMPORTED=TRUE -DCOMPILE_WITH_DWARF=OFF ninja echo "Tests skipped" diff --git a/.ci/macos/build.sh b/.ci/macos/build.sh index 94fd523af..9ef3f4973 100755 --- a/.ci/macos/build.sh +++ b/.ci/macos/build.sh @@ -22,7 +22,6 @@ cmake .. -DCMAKE_BUILD_TYPE=Release \ -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} \ -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \ -DUSE_DISCORD_PRESENCE=ON \ - -DENABLE_FFMPEG_VIDEO_DUMPER=ON \ -DENABLE_ASM=OFF \ -GNinja ninja diff --git a/.ci/windows-msvc/build.sh b/.ci/windows-msvc/build.sh index 68c2da01d..a811171c6 100644 --- a/.ci/windows-msvc/build.sh +++ b/.ci/windows-msvc/build.sh @@ -11,7 +11,6 @@ cmake .. \ -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \ -DUSE_DISCORD_PRESENCE=ON \ -DENABLE_MF=ON \ - -DENABLE_FFMPEG_VIDEO_DUMPER=ON \ -DOPENSSL_DLL_DIR="C:\Program Files\OpenSSL\bin" ninja diff --git a/.gitmodules b/.gitmodules index 6d0b09029..e5187598c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -79,3 +79,6 @@ [submodule "sirit"] path = externals/sirit url = https://github.com/yuzu-emu/sirit +[submodule "library-headers"] + path = externals/library-headers + url = https://github.com/citra-emu/ext-library-headers.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 12eae712f..81b956070 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,15 +48,6 @@ option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON) CMAKE_DEPENDENT_OPTION(ENABLE_LIBUSB "Enable libusb for GameCube Adapter support" ON "NOT IOS" OFF) -option(ENABLE_FFMPEG_AUDIO_DECODER "Enable FFmpeg audio (AAC) decoder" OFF) -option(ENABLE_FFMPEG_VIDEO_DUMPER "Enable FFmpeg video dumper" OFF) - -if (ENABLE_FFMPEG_AUDIO_DECODER OR ENABLE_FFMPEG_VIDEO_DUMPER) - set(ENABLE_FFMPEG ON) -endif() - -CMAKE_DEPENDENT_OPTION(CITRA_USE_BUNDLED_FFMPEG "Download bundled FFmpeg binaries" ON "ENABLE_FFMPEG;MSVC OR APPLE" OFF) - option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF) option(CITRA_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON) @@ -68,7 +59,6 @@ CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ON " option(USE_SYSTEM_BOOST "Use the system Boost libs (instead of the bundled ones)" OFF) -CMAKE_DEPENDENT_OPTION(ENABLE_FDK "Use FDK AAC decoder" OFF "NOT ENABLE_FFMPEG_AUDIO_DECODER;NOT ENABLE_MF" OFF) CMAKE_DEPENDENT_OPTION(CITRA_USE_BUNDLED_MOLTENVK "Download the bundled MoltenVK" ON "APPLE" OFF) @@ -200,6 +190,8 @@ message(STATUS "Target architecture: ${ARCHITECTURE}") # boost asio's concept usage doesn't play nicely with some compilers yet. add_definitions(-DBOOST_ASIO_DISABLE_CONCEPTS) +# boost can have issues compiling with C++17 and up on newer versions of Clang. +add_definitions(-DBOOST_NO_CXX98_FUNCTION_BASE) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -250,43 +242,6 @@ if (ENABLE_LIBUSB) endif() endif() -if (ENABLE_FFMPEG) - if (CITRA_USE_BUNDLED_FFMPEG) - if ((MSVC_VERSION GREATER_EQUAL 1920 AND MSVC_VERSION LESS 1940) AND "x86_64" IN_LIST ARCHITECTURE) - set(FFmpeg_VER "ffmpeg-4.1-win64") - elseif (APPLE) - set(FFmpeg_VER "ffmpeg-6.0") - else() - message(FATAL_ERROR "No bundled FFmpeg binaries for your toolchain. Disable CITRA_USE_BUNDLED_FFMPEG and provide your own.") - endif() - - if (DEFINED FFmpeg_VER) - download_bundled_external("ffmpeg/" ${FFmpeg_VER} FFmpeg_PREFIX) - set(FFMPEG_DIR "${FFmpeg_PREFIX}") - endif() - endif() - - if (ENABLE_FFMPEG_VIDEO_DUMPER) - find_package(FFmpeg REQUIRED COMPONENTS avcodec avfilter avformat avutil swresample) - else() - find_package(FFmpeg REQUIRED COMPONENTS avcodec) - endif() - if ("${FFmpeg_avcodec_VERSION}" VERSION_LESS "58.4.100") - message(FATAL_ERROR "Found version for libavcodec is too low. The required version is at least 58.4.100 (included in FFmpeg 4.0 and later).") - endif() -endif() - -if (ENABLE_FFMPEG_VIDEO_DUMPER) - add_definitions(-DENABLE_FFMPEG_VIDEO_DUMPER) -endif() - -if (ENABLE_FDK) - find_library(FDK_AAC fdk-aac DOC "The path to fdk_aac library") - if(FDK_AAC STREQUAL "FDK_AAC-NOTFOUND") - message(FATAL_ERROR "fdk_aac library not found.") - endif() -endif() - # Use system tsl::robin_map if available (otherwise we fallback to version bundled with dynarmic) find_package(tsl-robin-map QUIET) diff --git a/CMakeModules/CopyCitraFFmpegDeps.cmake b/CMakeModules/CopyCitraFFmpegDeps.cmake deleted file mode 100644 index 532f478ce..000000000 --- a/CMakeModules/CopyCitraFFmpegDeps.cmake +++ /dev/null @@ -1,13 +0,0 @@ -function(copy_citra_FFmpeg_deps target_dir) - include(WindowsCopyFiles) - set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/$/") - windows_copy_files(${target_dir} ${FFMPEG_DIR}/bin ${DLL_DEST} - avcodec*.dll - avfilter*.dll - avformat*.dll - avutil*.dll - postproc*.dll - swresample*.dll - swscale*.dll - ) -endfunction(copy_citra_FFmpeg_deps) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index e744dac8c..8130df3fe 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -117,6 +117,9 @@ endif() # Open Source Archives add_subdirectory(open_source_archives) +# Dynamic library headers +add_subdirectory(library-headers EXCLUDE_FROM_ALL) + # SoundTouch set(INTEGER_SAMPLES ON CACHE BOOL "") set(SOUNDSTRETCH OFF CACHE BOOL "") diff --git a/externals/cmake-modules/FindFFmpeg.cmake b/externals/cmake-modules/FindFFmpeg.cmake deleted file mode 100644 index 5a1a6c4b8..000000000 --- a/externals/cmake-modules/FindFFmpeg.cmake +++ /dev/null @@ -1,192 +0,0 @@ -# FindFFmpeg -# ---------- -# -# Find the native FFmpeg includes and libraries -# -# This module defines the following variables: -# -# FFmpeg_INCLUDE_: where to find .h -# FFmpeg_LIBRARY_: where to find the library -# FFmpeg_INCLUDES: aggregate all the include paths -# FFmpeg_LIBRARIES: aggregate all the paths to the libraries -# FFmpeg_FOUND: True if all components have been found -# -# This module defines the following targets, which are prefered over variables: -# -# FFmpeg::: Target to use directly, with include path, -# library and dependencies set up. If you are using a static build, you are -# responsible for adding any external dependencies (such as zlib, bzlib...). -# -# can be one of: -# avcodec -# avdevice -# avfilter -# avformat -# postproc -# swresample -# swscale -# - -set(_FFmpeg_ALL_COMPONENTS - avcodec - avdevice - avfilter - avformat - avutil - postproc - swresample - swscale -) - -set(_FFmpeg_DEPS_avcodec avutil) -set(_FFmpeg_DEPS_avdevice avcodec avformat avutil) -set(_FFmpeg_DEPS_avfilter avutil) -set(_FFmpeg_DEPS_avformat avcodec avutil) -set(_FFmpeg_DEPS_postproc avutil) -set(_FFmpeg_DEPS_swresample avutil) -set(_FFmpeg_DEPS_swscale avutil) - -function(find_ffmpeg LIBNAME) - if(DEFINED ENV{FFMPEG_DIR}) - set(FFMPEG_DIR $ENV{FFMPEG_DIR}) - endif() - - if(FFMPEG_DIR) - list(APPEND INCLUDE_PATHS - ${FFMPEG_DIR} - ${FFMPEG_DIR}/ffmpeg - ${FFMPEG_DIR}/lib${LIBNAME} - ${FFMPEG_DIR}/include/lib${LIBNAME} - ${FFMPEG_DIR}/include/ffmpeg - ${FFMPEG_DIR}/include - NO_DEFAULT_PATH - NO_CMAKE_FIND_ROOT_PATH - ) - list(APPEND LIB_PATHS - ${FFMPEG_DIR} - ${FFMPEG_DIR}/lib - ${FFMPEG_DIR}/lib${LIBNAME} - NO_DEFAULT_PATH - NO_CMAKE_FIND_ROOT_PATH - ) - else() - list(APPEND INCLUDE_PATHS - /usr/local/include/ffmpeg - /usr/local/include/lib${LIBNAME} - /usr/include/ffmpeg - /usr/include/lib${LIBNAME} - /usr/include/ffmpeg/lib${LIBNAME} - ) - - list(APPEND LIB_PATHS - /usr/local/lib - /usr/lib - ) - endif() - - find_path(FFmpeg_INCLUDE_${LIBNAME} lib${LIBNAME}/${LIBNAME}.h - HINTS ${INCLUDE_PATHS} - ) - - find_library(FFmpeg_LIBRARY_${LIBNAME} ${LIBNAME} - HINTS ${LIB_PATHS} - ) - - if(NOT FFMPEG_DIR AND (NOT FFmpeg_LIBRARY_${LIBNAME} OR NOT FFmpeg_INCLUDE_${LIBNAME})) - # Didn't find it in the usual paths, try pkg-config - find_package(PkgConfig QUIET) - pkg_check_modules(FFmpeg_PKGCONFIG_${LIBNAME} QUIET lib${LIBNAME}) - - find_path(FFmpeg_INCLUDE_${LIBNAME} lib${LIBNAME}/${LIBNAME}.h - ${FFmpeg_PKGCONFIG_${LIBNAME}_INCLUDE_DIRS} - ) - - find_library(FFmpeg_LIBRARY_${LIBNAME} ${LIBNAME} - ${FFmpeg_PKGCONFIG_${LIBNAME}_LIBRARY_DIRS} - ) - endif() - - if(FFmpeg_INCLUDE_${LIBNAME} AND FFmpeg_LIBRARY_${LIBNAME}) - set(FFmpeg_INCLUDE_${LIBNAME} "${FFmpeg_INCLUDE_${LIBNAME}}" PARENT_SCOPE) - set(FFmpeg_LIBRARY_${LIBNAME} "${FFmpeg_LIBRARY_${LIBNAME}}" PARENT_SCOPE) - - # Extract FFmpeg version from version.h - foreach(v MAJOR MINOR MICRO) - set(FFmpeg_${LIBNAME}_VERSION_${v} 0) - endforeach() - string(TOUPPER ${LIBNAME} LIBNAME_UPPER) - file(STRINGS "${FFmpeg_INCLUDE_${LIBNAME}}/lib${LIBNAME}/version.h" _FFmpeg_VERSION_H_CONTENTS REGEX "#define LIB${LIBNAME_UPPER}_VERSION_(MAJOR|MINOR|MICRO) ") - if (EXISTS "${FFmpeg_INCLUDE_${LIBNAME}}/lib${LIBNAME}/version_major.h") - file(STRINGS "${FFmpeg_INCLUDE_${LIBNAME}}/lib${LIBNAME}/version_major.h" _FFmpeg_MAJOR_VERSION_H_CONTENTS REGEX "#define LIB${LIBNAME_UPPER}_VERSION_MAJOR ") - string(APPEND _FFmpeg_VERSION_H_CONTENTS "\n" ${_FFmpeg_MAJOR_VERSION_H_CONTENTS}) - endif() - set(_FFmpeg_VERSION_REGEX "([0-9]+)") - foreach(v MAJOR MINOR MICRO) - if("${_FFmpeg_VERSION_H_CONTENTS}" MATCHES "#define LIB${LIBNAME_UPPER}_VERSION_${v}[\\t ]+${_FFmpeg_VERSION_REGEX}") - set(FFmpeg_${LIBNAME}_VERSION_${v} "${CMAKE_MATCH_1}") - endif() - endforeach() - set(FFmpeg_${LIBNAME}_VERSION "${FFmpeg_${LIBNAME}_VERSION_MAJOR}.${FFmpeg_${LIBNAME}_VERSION_MINOR}.${FFmpeg_${LIBNAME}_VERSION_MICRO}") - set(FFmpeg_${c}_VERSION "${FFmpeg_${LIBNAME}_VERSION}" PARENT_SCOPE) - unset(_FFmpeg_VERSION_REGEX) - unset(_FFmpeg_VERSION_H_CONTENTS) - - set(FFmpeg_${c}_FOUND TRUE PARENT_SCOPE) - if(NOT FFmpeg_FIND_QUIETLY) - message("-- Found ${LIBNAME}: ${FFmpeg_INCLUDE_${LIBNAME}} ${FFmpeg_LIBRARY_${LIBNAME}} (version: ${FFmpeg_${LIBNAME}_VERSION})") - endif() - endif() -endfunction() - -foreach(c ${_FFmpeg_ALL_COMPONENTS}) - find_ffmpeg(${c}) -endforeach() - -foreach(c ${_FFmpeg_ALL_COMPONENTS}) - if(FFmpeg_${c}_FOUND) - list(APPEND FFmpeg_INCLUDES ${FFmpeg_INCLUDE_${c}}) - list(APPEND FFmpeg_LIBRARIES ${FFmpeg_LIBRARY_${c}}) - - add_library(FFmpeg::${c} IMPORTED UNKNOWN) - set_target_properties(FFmpeg::${c} PROPERTIES - IMPORTED_LOCATION ${FFmpeg_LIBRARY_${c}} - INTERFACE_INCLUDE_DIRECTORIES ${FFmpeg_INCLUDE_${c}} - ) - if(APPLE) - set_target_properties(FFmpeg::${c} PROPERTIES - MACOSX_RPATH 1 - ) - endif() - if(_FFmpeg_DEPS_${c}) - set(deps) - foreach(dep ${_FFmpeg_DEPS_${c}}) - list(APPEND deps FFmpeg::${dep}) - endforeach() - - set_target_properties(FFmpeg::${c} PROPERTIES - INTERFACE_LINK_LIBRARIES "${deps}" - ) - unset(deps) - endif() - endif() -endforeach() - -if(FFmpeg_INCLUDES) - list(REMOVE_DUPLICATES FFmpeg_INCLUDES) -endif() - -foreach(c ${FFmpeg_FIND_COMPONENTS}) - list(APPEND _FFmpeg_REQUIRED_VARS FFmpeg_INCLUDE_${c} FFmpeg_LIBRARY_${c}) -endforeach() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(FFmpeg - REQUIRED_VARS ${_FFmpeg_REQUIRED_VARS} - HANDLE_COMPONENTS -) - -foreach(c ${_FFmpeg_ALL_COMPONENTS}) - unset(_FFmpeg_DEPS_${c}) -endforeach() -unset(_FFmpeg_ALL_COMPONENTS) -unset(_FFmpeg_REQUIRED_VARS) diff --git a/externals/library-headers b/externals/library-headers new file mode 160000 index 000000000..071bc4282 --- /dev/null +++ b/externals/library-headers @@ -0,0 +1 @@ +Subproject commit 071bc4282ca29ec255ab2dae32c978481ca5dfea diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 32fb85a13..4385cdf5f 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -9,6 +9,10 @@ add_library(audio_core STATIC hle/common.h hle/decoder.cpp hle/decoder.h + hle/fdk_decoder.cpp + hle/fdk_decoder.h + hle/ffmpeg_decoder.cpp + hle/ffmpeg_decoder.h hle/filter.cpp hle/filter.h hle/hle.cpp @@ -67,26 +71,6 @@ elseif(ENABLE_AUDIOTOOLBOX) find_library(AUDIOTOOLBOX AudioToolbox) target_link_libraries(audio_core PRIVATE ${AUDIOTOOLBOX}) target_compile_definitions(audio_core PUBLIC HAVE_AUDIOTOOLBOX) -elseif(ENABLE_FFMPEG_AUDIO_DECODER) - target_sources(audio_core PRIVATE - hle/ffmpeg_decoder.cpp - hle/ffmpeg_decoder.h - hle/ffmpeg_dl.cpp - hle/ffmpeg_dl.h - ) - if(UNIX) - target_link_libraries(audio_core PRIVATE FFmpeg::avcodec) - else() - target_include_directories(audio_core PRIVATE ${FFMPEG_DIR}/include) - endif() - target_compile_definitions(audio_core PUBLIC HAVE_FFMPEG) -elseif(ENABLE_FDK) - target_sources(audio_core PRIVATE - hle/fdk_decoder.cpp - hle/fdk_decoder.h - ) - target_link_libraries(audio_core PRIVATE ${FDK_AAC}) - target_compile_definitions(audio_core PUBLIC HAVE_FDK) endif() if(ANDROID) diff --git a/src/audio_core/dsp_interface.cpp b/src/audio_core/dsp_interface.cpp index fee8caed9..4948eb958 100644 --- a/src/audio_core/dsp_interface.cpp +++ b/src/audio_core/dsp_interface.cpp @@ -47,8 +47,9 @@ void DspInterface::OutputFrame(StereoFrame16 frame) { fifo.Push(frame.data(), frame.size()); - if (Core::System::GetInstance().VideoDumper().IsDumping()) { - Core::System::GetInstance().VideoDumper().AddAudioFrame(std::move(frame)); + auto video_dumper = Core::System::GetInstance().GetVideoDumper(); + if (video_dumper && video_dumper->IsDumping()) { + video_dumper->AddAudioFrame(std::move(frame)); } } @@ -58,8 +59,9 @@ void DspInterface::OutputSample(std::array sample) { fifo.Push(&sample, 1); - if (Core::System::GetInstance().VideoDumper().IsDumping()) { - Core::System::GetInstance().VideoDumper().AddAudioSample(std::move(sample)); + auto video_dumper = Core::System::GetInstance().GetVideoDumper(); + if (video_dumper && video_dumper->IsDumping()) { + video_dumper->AddAudioSample(std::move(sample)); } } diff --git a/src/audio_core/hle/fdk_decoder.cpp b/src/audio_core/hle/fdk_decoder.cpp index 6a29d623d..0de539085 100644 --- a/src/audio_core/hle/fdk_decoder.cpp +++ b/src/audio_core/hle/fdk_decoder.cpp @@ -2,8 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include #include "audio_core/hle/fdk_decoder.h" +#include "common/dynamic_library/fdk-aac.h" + +using namespace DynamicLibrary; namespace AudioCore::HLE { @@ -29,13 +31,17 @@ private: }; FDKDecoder::Impl::Impl(Memory::MemorySystem& memory) : memory(memory) { + if (!FdkAac::LoadFdkAac()) { + return; + } + // allocate an array of LIB_INFO structures // if we don't pre-fill the whole segment with zeros, when we call `aacDecoder_GetLibInfo` // it will segfault, upon investigation, there is some code in fdk_aac depends on your initial // values in this array LIB_INFO decoder_info[FDK_MODULE_LAST] = {}; // get library information and fill the struct - if (aacDecoder_GetLibInfo(decoder_info) != 0) { + if (FdkAac::aacDecoder_GetLibInfo(decoder_info) != 0) { LOG_ERROR(Audio_DSP, "Failed to retrieve fdk_aac library information!"); return; } @@ -44,14 +50,14 @@ FDKDecoder::Impl::Impl(Memory::MemorySystem& memory) : memory(memory) { decoder_info[0].build_date); // choose the input format when initializing: 1 layer of ADTS - decoder = aacDecoder_Open(TRANSPORT_TYPE::TT_MP4_ADTS, 1); + decoder = FdkAac::aacDecoder_Open(TRANSPORT_TYPE::TT_MP4_ADTS, 1); // set maximum output channel to two (stereo) // if the input samples have more channels, fdk_aac will perform a downmix - AAC_DECODER_ERROR ret = aacDecoder_SetParam(decoder, AAC_PCM_MAX_OUTPUT_CHANNELS, 2); + AAC_DECODER_ERROR ret = FdkAac::aacDecoder_SetParam(decoder, AAC_PCM_MAX_OUTPUT_CHANNELS, 2); if (ret != AAC_DEC_OK) { // unable to set this parameter reflects the decoder implementation might be broken // we'd better shuts down everything - aacDecoder_Close(decoder); + FdkAac::aacDecoder_Close(decoder); decoder = nullptr; LOG_ERROR(Audio_DSP, "Unable to set downmix parameter: {}", ret); return; @@ -73,8 +79,9 @@ std::optional FDKDecoder::Impl::Initalize(const BinaryMessage& re } FDKDecoder::Impl::~Impl() { - if (decoder) - aacDecoder_Close(decoder); + if (decoder) { + FdkAac::aacDecoder_Close(decoder); + } } void FDKDecoder::Impl::Clear() { @@ -84,9 +91,10 @@ void FDKDecoder::Impl::Clear() { // FLUSH - flush internal buffer // INTR - treat the current internal buffer as discontinuous // CONCEAL - try to interpolate and smooth out the samples - if (decoder) - aacDecoder_DecodeFrame(decoder, decoder_output, 8192, - AACDEC_FLUSH & AACDEC_INTR & AACDEC_CONCEAL); + if (decoder) { + FdkAac::aacDecoder_DecodeFrame(decoder, decoder_output, 8192, + AACDEC_FLUSH & AACDEC_INTR & AACDEC_CONCEAL); + } } std::optional FDKDecoder::Impl::ProcessRequest(const BinaryMessage& request) { @@ -140,7 +148,7 @@ std::optional FDKDecoder::Impl::Decode(const BinaryMessage& reque std::array, 2> out_streams; - std::size_t data_size = request.decode_aac_request.size; + u32 data_size = request.decode_aac_request.size; // decoding loops AAC_DECODER_ERROR result = AAC_DEC_OK; @@ -156,18 +164,18 @@ std::optional FDKDecoder::Impl::Decode(const BinaryMessage& reque while (buffer_remaining) { // queue the input buffer, fdk_aac will automatically slice out the buffer it needs // from the input buffer - result = aacDecoder_Fill(decoder, &data, &input_size, &buffer_remaining); + result = FdkAac::aacDecoder_Fill(decoder, &data, &input_size, &buffer_remaining); if (result != AAC_DEC_OK) { // there are some issues when queuing the input buffer LOG_ERROR(Audio_DSP, "Failed to enqueue the input samples"); return std::nullopt; } // get output from decoder - result = aacDecoder_DecodeFrame(decoder, decoder_output, - sizeof(decoder_output) / sizeof(s16), 0); + result = FdkAac::aacDecoder_DecodeFrame(decoder, decoder_output, + sizeof(decoder_output) / sizeof(s16), 0); if (result == AAC_DEC_OK) { // get the stream information - stream_info = aacDecoder_GetStreamInfo(decoder); + stream_info = FdkAac::aacDecoder_GetStreamInfo(decoder); // fill the stream information for binary response response.decode_aac_response.sample_rate = GetSampleRateEnum(stream_info->sampleRate); response.decode_aac_response.num_channels = stream_info->numChannels; diff --git a/src/audio_core/hle/ffmpeg_decoder.cpp b/src/audio_core/hle/ffmpeg_decoder.cpp index c58bc0c3e..d291343ca 100644 --- a/src/audio_core/hle/ffmpeg_decoder.cpp +++ b/src/audio_core/hle/ffmpeg_decoder.cpp @@ -3,7 +3,9 @@ // Refer to the license.txt file included. #include "audio_core/hle/ffmpeg_decoder.h" -#include "audio_core/hle/ffmpeg_dl.h" +#include "common/dynamic_library/ffmpeg.h" + +using namespace DynamicLibrary; namespace AudioCore::HLE { @@ -25,25 +27,25 @@ private: struct AVPacketDeleter { void operator()(AVPacket* packet) const { - av_packet_free_dl(&packet); + FFmpeg::av_packet_free(&packet); } }; struct AVCodecContextDeleter { void operator()(AVCodecContext* context) const { - avcodec_free_context_dl(&context); + FFmpeg::avcodec_free_context(&context); } }; struct AVCodecParserContextDeleter { void operator()(AVCodecParserContext* parser) const { - av_parser_close_dl(parser); + FFmpeg::av_parser_close(parser); } }; struct AVFrameDeleter { void operator()(AVFrame* frame) const { - av_frame_free_dl(&frame); + FFmpeg::av_frame_free(&frame); } }; @@ -60,7 +62,7 @@ private: }; FFMPEGDecoder::Impl::Impl(Memory::MemorySystem& memory) : memory(memory) { - have_ffmpeg_dl = InitFFmpegDL(); + have_ffmpeg_dl = FFmpeg::LoadFFmpeg(); } FFMPEGDecoder::Impl::~Impl() = default; @@ -102,27 +104,27 @@ std::optional FFMPEGDecoder::Impl::Initalize(const BinaryMessage& return response; } - av_packet.reset(av_packet_alloc_dl()); + av_packet.reset(FFmpeg::av_packet_alloc()); - codec = avcodec_find_decoder_dl(AV_CODEC_ID_AAC); + codec = FFmpeg::avcodec_find_decoder(AV_CODEC_ID_AAC); if (!codec) { LOG_ERROR(Audio_DSP, "Codec not found\n"); return response; } - parser.reset(av_parser_init_dl(codec->id)); + parser.reset(FFmpeg::av_parser_init(codec->id)); if (!parser) { LOG_ERROR(Audio_DSP, "Parser not found\n"); return response; } - av_context.reset(avcodec_alloc_context3_dl(codec)); + av_context.reset(FFmpeg::avcodec_alloc_context3(codec)); if (!av_context) { LOG_ERROR(Audio_DSP, "Could not allocate audio codec context\n"); return response; } - if (avcodec_open2_dl(av_context.get(), codec, nullptr) < 0) { + if (FFmpeg::avcodec_open2(av_context.get(), codec, nullptr) < 0) { LOG_ERROR(Audio_DSP, "Could not open codec\n"); return response; } @@ -170,16 +172,16 @@ std::optional FFMPEGDecoder::Impl::Decode(const BinaryMessage& re std::size_t data_size = request.decode_aac_request.size; while (data_size > 0) { if (!decoded_frame) { - decoded_frame.reset(av_frame_alloc_dl()); + decoded_frame.reset(FFmpeg::av_frame_alloc()); if (!decoded_frame) { LOG_ERROR(Audio_DSP, "Could not allocate audio frame"); return {}; } } - int ret = - av_parser_parse2_dl(parser.get(), av_context.get(), &av_packet->data, &av_packet->size, - data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0); + int ret = FFmpeg::av_parser_parse2(parser.get(), av_context.get(), &av_packet->data, + &av_packet->size, data, static_cast(data_size), + AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0); if (ret < 0) { LOG_ERROR(Audio_DSP, "Error while parsing"); return {}; @@ -187,7 +189,7 @@ std::optional FFMPEGDecoder::Impl::Decode(const BinaryMessage& re data += ret; data_size -= ret; - ret = avcodec_send_packet_dl(av_context.get(), av_packet.get()); + ret = FFmpeg::avcodec_send_packet(av_context.get(), av_packet.get()); if (ret < 0) { LOG_ERROR(Audio_DSP, "Error submitting the packet to the decoder"); return {}; @@ -195,33 +197,39 @@ std::optional FFMPEGDecoder::Impl::Decode(const BinaryMessage& re if (av_packet->size) { while (ret >= 0) { - ret = avcodec_receive_frame_dl(av_context.get(), decoded_frame.get()); + ret = FFmpeg::avcodec_receive_frame(av_context.get(), decoded_frame.get()); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; else if (ret < 0) { LOG_ERROR(Audio_DSP, "Error during decoding"); return {}; } - int bytes_per_sample = av_get_bytes_per_sample_dl(av_context->sample_fmt); + int bytes_per_sample = FFmpeg::av_get_bytes_per_sample(av_context->sample_fmt); if (bytes_per_sample < 0) { LOG_ERROR(Audio_DSP, "Failed to calculate data size"); return {}; } - ASSERT(decoded_frame->channels <= out_streams.size()); +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100) + auto num_channels = static_cast(decoded_frame->ch_layout.nb_channels); +#else + auto num_channels = static_cast(decoded_frame->channels); +#endif + + ASSERT(num_channels <= out_streams.size()); std::size_t size = bytes_per_sample * (decoded_frame->nb_samples); response.decode_aac_response.sample_rate = GetSampleRateEnum(decoded_frame->sample_rate); - response.decode_aac_response.num_channels = decoded_frame->channels; + response.decode_aac_response.num_channels = num_channels; response.decode_aac_response.num_samples += decoded_frame->nb_samples; // FFmpeg converts to 32 signed floating point PCM, we need s16 PCM so we need to // convert it f32 val_float; for (std::size_t current_pos(0); current_pos < size;) { - for (std::size_t channel(0); channel < decoded_frame->channels; channel++) { + for (std::size_t channel(0); channel < num_channels; channel++) { std::memcpy(&val_float, decoded_frame->data[channel] + current_pos, sizeof(val_float)); val_float = std::clamp(val_float, -1.0f, 1.0f); diff --git a/src/audio_core/hle/ffmpeg_dl.cpp b/src/audio_core/hle/ffmpeg_dl.cpp deleted file mode 100644 index 073d0aedd..000000000 --- a/src/audio_core/hle/ffmpeg_dl.cpp +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#ifdef _WIN32 - -#include -#include "audio_core/hle/ffmpeg_dl.h" -#include "common/file_util.h" -#include "common/logging/log.h" -#include "common/string_util.h" - -namespace { - -struct LibraryDeleter { - using pointer = HMODULE; - void operator()(HMODULE h) const { - if (h != nullptr) - FreeLibrary(h); - } -}; - -std::unique_ptr dll_util{nullptr}; -std::unique_ptr dll_codec{nullptr}; - -} // namespace - -FuncDL av_get_bytes_per_sample_dl; -FuncDL av_frame_alloc_dl; -FuncDL av_frame_free_dl; -FuncDL avcodec_alloc_context3_dl; -FuncDL avcodec_free_context_dl; -FuncDL avcodec_open2_dl; -FuncDL av_packet_alloc_dl; -FuncDL av_packet_free_dl; -FuncDL avcodec_find_decoder_dl; -FuncDL avcodec_send_packet_dl; -FuncDL avcodec_receive_frame_dl; -FuncDL av_parser_init_dl; -FuncDL - av_parser_parse2_dl; -FuncDL av_parser_close_dl; - -bool InitFFmpegDL() { - std::string dll_path = FileUtil::GetUserPath(FileUtil::UserPath::DLLDir); - FileUtil::CreateDir(dll_path); - std::wstring w_dll_path = Common::UTF8ToUTF16W(dll_path); - SetDllDirectoryW(w_dll_path.c_str()); - - dll_util.reset(LoadLibrary("avutil-56.dll")); - if (!dll_util) { - DWORD error_message_id = GetLastError(); - LPSTR message_buffer = nullptr; - size_t size = - FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, error_message_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - reinterpret_cast(&message_buffer), 0, nullptr); - - std::string message(message_buffer, size); - - LocalFree(message_buffer); - LOG_ERROR(Audio_DSP, "Could not load avutil-56.dll: {}", message); - return false; - } - - dll_codec.reset(LoadLibrary("avcodec-58.dll")); - if (!dll_codec) { - DWORD error_message_id = GetLastError(); - LPSTR message_buffer = nullptr; - size_t size = - FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, error_message_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - reinterpret_cast(&message_buffer), 0, nullptr); - - std::string message(message_buffer, size); - - LocalFree(message_buffer); - LOG_ERROR(Audio_DSP, "Could not load avcodec-58.dll: {}", message); - return false; - } - av_get_bytes_per_sample_dl = - FuncDL(dll_util.get(), "av_get_bytes_per_sample"); - if (!av_get_bytes_per_sample_dl) { - LOG_ERROR(Audio_DSP, "Can not load function av_get_bytes_per_sample"); - return false; - } - - av_frame_alloc_dl = FuncDL(dll_util.get(), "av_frame_alloc"); - if (!av_frame_alloc_dl) { - LOG_ERROR(Audio_DSP, "Can not load function av_frame_alloc"); - return false; - } - - av_frame_free_dl = FuncDL(dll_util.get(), "av_frame_free"); - if (!av_frame_free_dl) { - LOG_ERROR(Audio_DSP, "Can not load function av_frame_free"); - return false; - } - - avcodec_alloc_context3_dl = - FuncDL(dll_codec.get(), "avcodec_alloc_context3"); - if (!avcodec_alloc_context3_dl) { - LOG_ERROR(Audio_DSP, "Can not load function avcodec_alloc_context3"); - return false; - } - - avcodec_free_context_dl = - FuncDL(dll_codec.get(), "avcodec_free_context"); - if (!av_get_bytes_per_sample_dl) { - LOG_ERROR(Audio_DSP, "Can not load function avcodec_free_context"); - return false; - } - - avcodec_open2_dl = FuncDL( - dll_codec.get(), "avcodec_open2"); - if (!avcodec_open2_dl) { - LOG_ERROR(Audio_DSP, "Can not load function avcodec_open2"); - return false; - } - av_packet_alloc_dl = FuncDL(dll_codec.get(), "av_packet_alloc"); - if (!av_packet_alloc_dl) { - LOG_ERROR(Audio_DSP, "Can not load function av_packet_alloc"); - return false; - } - - av_packet_free_dl = FuncDL(dll_codec.get(), "av_packet_free"); - if (!av_packet_free_dl) { - LOG_ERROR(Audio_DSP, "Can not load function av_packet_free"); - return false; - } - - avcodec_find_decoder_dl = FuncDL(dll_codec.get(), "avcodec_find_decoder"); - if (!avcodec_find_decoder_dl) { - LOG_ERROR(Audio_DSP, "Can not load function avcodec_find_decoder"); - return false; - } - - avcodec_send_packet_dl = - FuncDL(dll_codec.get(), "avcodec_send_packet"); - if (!avcodec_send_packet_dl) { - LOG_ERROR(Audio_DSP, "Can not load function avcodec_send_packet"); - return false; - } - - avcodec_receive_frame_dl = - FuncDL(dll_codec.get(), "avcodec_receive_frame"); - if (!avcodec_receive_frame_dl) { - LOG_ERROR(Audio_DSP, "Can not load function avcodec_receive_frame"); - return false; - } - - av_parser_init_dl = FuncDL(dll_codec.get(), "av_parser_init"); - if (!av_parser_init_dl) { - LOG_ERROR(Audio_DSP, "Can not load function av_parser_init"); - return false; - } - - av_parser_parse2_dl = - FuncDL(dll_codec.get(), "av_parser_parse2"); - if (!av_parser_parse2_dl) { - LOG_ERROR(Audio_DSP, "Can not load function av_parser_parse2"); - return false; - } - - av_parser_close_dl = FuncDL(dll_codec.get(), "av_parser_close"); - if (!av_parser_close_dl) { - LOG_ERROR(Audio_DSP, "Can not load function av_parser_close"); - return false; - } - - return true; -} - -#endif // _Win32 diff --git a/src/audio_core/hle/ffmpeg_dl.h b/src/audio_core/hle/ffmpeg_dl.h deleted file mode 100644 index 6ca2066fc..000000000 --- a/src/audio_core/hle/ffmpeg_dl.h +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#ifdef _WIN32 -#include -#endif // _WIN32 - -extern "C" { -#include -} - -#ifdef _WIN32 - -template -struct FuncDL { - FuncDL() = default; - FuncDL(HMODULE dll, const char* name) { - if (dll) { - ptr_function = reinterpret_cast(GetProcAddress(dll, name)); - } - } - - operator T*() const { - return ptr_function; - } - - explicit operator bool() const { - return ptr_function != nullptr; - } - - T* ptr_function = nullptr; -}; - -extern FuncDL av_get_bytes_per_sample_dl; -extern FuncDL av_frame_alloc_dl; -extern FuncDL av_frame_free_dl; -extern FuncDL avcodec_alloc_context3_dl; -extern FuncDL avcodec_free_context_dl; -extern FuncDL avcodec_open2_dl; -extern FuncDL av_packet_alloc_dl; -extern FuncDL av_packet_free_dl; -extern FuncDL avcodec_find_decoder_dl; -extern FuncDL avcodec_send_packet_dl; -extern FuncDL avcodec_receive_frame_dl; -extern FuncDL av_parser_init_dl; -extern FuncDL - av_parser_parse2_dl; -extern FuncDL av_parser_close_dl; - -bool InitFFmpegDL(); - -#else // _Win32 - -// No dynamic loading for Unix and Apple - -const auto av_get_bytes_per_sample_dl = &av_get_bytes_per_sample; -const auto av_frame_alloc_dl = &av_frame_alloc; -const auto av_frame_free_dl = &av_frame_free; -const auto avcodec_alloc_context3_dl = &avcodec_alloc_context3; -const auto avcodec_free_context_dl = &avcodec_free_context; -const auto avcodec_open2_dl = &avcodec_open2; -const auto av_packet_alloc_dl = &av_packet_alloc; -const auto av_packet_free_dl = &av_packet_free; -const auto avcodec_find_decoder_dl = &avcodec_find_decoder; -const auto avcodec_send_packet_dl = &avcodec_send_packet; -const auto avcodec_receive_frame_dl = &avcodec_receive_frame; -const auto av_parser_init_dl = &av_parser_init; -const auto av_parser_parse2_dl = &av_parser_parse2; -const auto av_parser_close_dl = &av_parser_close; - -bool InitFFmpegDL() { - return true; -} - -#endif // _Win32 diff --git a/src/audio_core/hle/hle.cpp b/src/audio_core/hle/hle.cpp index d7e70f328..de42c13bc 100644 --- a/src/audio_core/hle/hle.cpp +++ b/src/audio_core/hle/hle.cpp @@ -12,15 +12,13 @@ #include "audio_core/hle/wmf_decoder.h" #elif HAVE_AUDIOTOOLBOX #include "audio_core/hle/audiotoolbox_decoder.h" -#elif HAVE_FFMPEG -#include "audio_core/hle/ffmpeg_decoder.h" #elif ANDROID #include "audio_core/hle/mediandk_decoder.h" -#elif HAVE_FDK -#include "audio_core/hle/fdk_decoder.h" #endif #include "audio_core/hle/common.h" #include "audio_core/hle/decoder.h" +#include "audio_core/hle/fdk_decoder.h" +#include "audio_core/hle/ffmpeg_decoder.h" #include "audio_core/hle/hle.h" #include "audio_core/hle/mixers.h" #include "audio_core/hle/shared_memory.h" @@ -120,6 +118,31 @@ private: friend class boost::serialization::access; }; +static std::vector(Memory::MemorySystem&)>> + decoder_backends = { +#if defined(HAVE_MF) + [](Memory::MemorySystem& memory) -> std::unique_ptr { + return std::make_unique(memory); + }, +#endif +#if defined(HAVE_AUDIOTOOLBOX) + [](Memory::MemorySystem& memory) -> std::unique_ptr { + return std::make_unique(memory); + }, +#endif +#if ANDROID + [](Memory::MemorySystem& memory) -> std::unique_ptr { + return std::make_unique(memory); + }, +#endif + [](Memory::MemorySystem& memory) -> std::unique_ptr { + return std::make_unique(memory); + }, + [](Memory::MemorySystem& memory) -> std::unique_ptr { + return std::make_unique(memory); + }, +}; + DspHle::Impl::Impl(DspHle& parent_, Memory::MemorySystem& memory, Core::Timing& timing) : parent(parent_), core_timing(timing) { dsp_memory.raw_memory.fill(0); @@ -128,28 +151,14 @@ DspHle::Impl::Impl(DspHle& parent_, Memory::MemorySystem& memory, Core::Timing& source.SetMemory(memory); } -#if defined(HAVE_MF) && defined(HAVE_FFMPEG) - decoder = std::make_unique(memory); - if (!decoder->IsValid()) { - LOG_WARNING(Audio_DSP, "Unable to load MediaFoundation. Attempting to load FFMPEG instead"); - decoder = std::make_unique(memory); + for (auto& factory : decoder_backends) { + decoder = factory(memory); + if (decoder && decoder->IsValid()) { + break; + } } -#elif defined(HAVE_MF) - decoder = std::make_unique(memory); -#elif defined(HAVE_AUDIOTOOLBOX) - decoder = std::make_unique(memory); -#elif defined(HAVE_FFMPEG) - decoder = std::make_unique(memory); -#elif ANDROID - decoder = std::make_unique(memory); -#elif defined(HAVE_FDK) - decoder = std::make_unique(memory); -#else - LOG_WARNING(Audio_DSP, "No decoder found, this could lead to missing audio"); - decoder = std::make_unique(); -#endif // HAVE_MF - if (!decoder->IsValid()) { + if (!decoder || !decoder->IsValid()) { LOG_WARNING(Audio_DSP, "Unable to load any decoders, this could cause missing audio in some games"); decoder = std::make_unique(); diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp index b2b447a03..7445c1d07 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp @@ -27,6 +27,7 @@ #include "common/string_util.h" #include "core/core.h" #include "core/dumping/backend.h" +#include "core/dumping/ffmpeg_backend.h" #include "core/file_sys/cia_container.h" #include "core/frontend/applets/default_applets.h" #include "core/frontend/framebuffer_layout.h" @@ -445,10 +446,13 @@ int main(int argc, char** argv) { if (!movie_record.empty()) { Core::Movie::GetInstance().StartRecording(movie_record, movie_record_author); } - if (!dump_video.empty()) { + if (!dump_video.empty() && DynamicLibrary::FFmpeg::LoadFFmpeg()) { Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale( VideoCore::g_renderer->GetResolutionScaleFactor())}; - system.VideoDumper().StartDumping(dump_video, layout); + auto dumper = std::make_shared(); + if (dumper->StartDumping(dump_video, layout)) { + Core::System::GetInstance().RegisterVideoDumper(dumper); + } } std::thread main_render_thread([&emu_window] { emu_window->Present(); }); @@ -491,8 +495,10 @@ int main(int argc, char** argv) { secondary_render_thread.join(); Core::Movie::GetInstance().Shutdown(); - if (system.VideoDumper().IsDumping()) { - system.VideoDumper().StopDumping(); + + auto video_dumper = system.GetVideoDumper(); + if (video_dumper && video_dumper->IsDumping()) { + video_dumper->StopDumping(); } Network::Shutdown(); diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 18802c957..04d113abb 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -120,6 +120,15 @@ add_executable(citra-qt debugger/wait_tree.cpp debugger/wait_tree.h discord.h + dumping/dumping_dialog.cpp + dumping/dumping_dialog.h + dumping/dumping_dialog.ui + dumping/option_set_dialog.cpp + dumping/option_set_dialog.h + dumping/option_set_dialog.ui + dumping/options_dialog.cpp + dumping/options_dialog.h + dumping/options_dialog.ui game_list.cpp game_list.h game_list_p.h @@ -178,20 +187,6 @@ add_executable(citra-qt util/util.h ) -if (ENABLE_FFMPEG_VIDEO_DUMPER) - target_sources(citra-qt PRIVATE - dumping/dumping_dialog.cpp - dumping/dumping_dialog.h - dumping/dumping_dialog.ui - dumping/option_set_dialog.cpp - dumping/option_set_dialog.h - dumping/option_set_dialog.ui - dumping/options_dialog.cpp - dumping/options_dialog.h - dumping/options_dialog.ui - ) -endif() - file(GLOB COMPAT_LIST ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) @@ -377,11 +372,6 @@ if (MSVC) include(CopyCitraOpensslDeps) copy_citra_openssl_deps(citra-qt) endif() - - if (ENABLE_FFMPEG) - include(CopyCitraFFmpegDeps) - copy_citra_FFmpeg_deps(citra-qt) - endif() endif() if (NOT APPLE) diff --git a/src/citra_qt/dumping/option_set_dialog.cpp b/src/citra_qt/dumping/option_set_dialog.cpp index 84fd619f9..f6d6e4af7 100644 --- a/src/citra_qt/dumping/option_set_dialog.cpp +++ b/src/citra_qt/dumping/option_set_dialog.cpp @@ -10,10 +10,6 @@ #include "common/string_util.h" #include "ui_option_set_dialog.h" -extern "C" { -#include -} - static const std::unordered_map TypeNameMap{{ {AV_OPT_TYPE_BOOL, QT_TR_NOOP("boolean")}, {AV_OPT_TYPE_FLAGS, QT_TR_NOOP("flags")}, @@ -56,21 +52,14 @@ std::vector> GetPresetValues(const VideoDumper::Opti } case AV_OPT_TYPE_PIXEL_FMT: { std::vector> out{{QObject::tr("none"), QStringLiteral("none")}}; - // List all pixel formats - const AVPixFmtDescriptor* current = nullptr; - while ((current = av_pix_fmt_desc_next(current))) { - out.emplace_back(QString::fromUtf8(current->name), QString::fromUtf8(current->name)); + for (const auto& name : VideoDumper::GetPixelFormats()) { + out.emplace_back(QString::fromUtf8(name), QString::fromUtf8(name)); } return out; } case AV_OPT_TYPE_SAMPLE_FMT: { std::vector> out{{QObject::tr("none"), QStringLiteral("none")}}; - // List all sample formats - int current = 0; - while (true) { - const char* name = av_get_sample_fmt_name(static_cast(current)); - if (name == nullptr) - break; + for (const auto& name : VideoDumper::GetSampleFormats()) { out.emplace_back(QString::fromUtf8(name), QString::fromUtf8(name)); } return out; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 5b218c6d4..ad53376ad 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -49,6 +49,7 @@ #include "citra_qt/debugger/registers.h" #include "citra_qt/debugger/wait_tree.h" #include "citra_qt/discord.h" +#include "citra_qt/dumping/dumping_dialog.h" #include "citra_qt/game_list.h" #include "citra_qt/hotkeys.h" #include "citra_qt/loading_screen.h" @@ -102,10 +103,6 @@ #include "citra_qt/discord_impl.h" #endif -#ifdef ENABLE_FFMPEG_VIDEO_DUMPER -#include "citra_qt/dumping/dumping_dialog.h" -#endif - #ifdef QT_STATICPLUGIN Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); #endif @@ -834,18 +831,7 @@ void GMainWindow::ConnectMenuEvents() { } }); connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot); - -#ifdef ENABLE_FFMPEG_VIDEO_DUMPER - connect_menu(ui->action_Dump_Video, [this] { - if (ui->action_Dump_Video->isChecked()) { - OnStartVideoDumping(); - } else { - OnStopVideoDumping(); - } - }); -#else - ui->action_Dump_Video->setEnabled(false); -#endif + connect_menu(ui->action_Dump_Video, &GMainWindow::OnDumpVideo); // Help connect_menu(ui->action_Open_Citra_Folder, &GMainWindow::OnOpenCitraFolder); @@ -1203,15 +1189,7 @@ void GMainWindow::BootGame(const QString& filename) { } if (video_dumping_on_start) { - Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale( - VideoCore::g_renderer->GetResolutionScaleFactor())}; - if (!system.VideoDumper().StartDumping(video_dumping_path.toStdString(), layout)) { - - QMessageBox::critical( - this, tr("Citra"), - tr("Could not start video dumping.
Refer to the log for details.")); - ui->action_Dump_Video->setChecked(false); - } + StartVideoDumping(video_dumping_path); video_dumping_on_start = false; video_dumping_path.clear(); } @@ -1279,13 +1257,12 @@ void GMainWindow::ShutdownGame() { HideFullscreen(); } -#ifdef ENABLE_FFMPEG_VIDEO_DUMPER - if (system.VideoDumper().IsDumping()) { + auto video_dumper = system.GetVideoDumper(); + if (video_dumper && video_dumper->IsDumping()) { game_shutdown_delayed = true; OnStopVideoDumping(); return; } -#endif AllowOSSleep(); @@ -2215,7 +2192,97 @@ void GMainWindow::OnCaptureScreenshot() { OnStartGame(); } -#ifdef ENABLE_FFMPEG_VIDEO_DUMPER +void GMainWindow::OnDumpVideo() { + if (DynamicLibrary::FFmpeg::LoadFFmpeg()) { + if (ui->action_Dump_Video->isChecked()) { + OnStartVideoDumping(); + } else { + OnStopVideoDumping(); + } + } else { + ui->action_Dump_Video->setChecked(false); + + QMessageBox message_box; + message_box.setWindowTitle(tr("Could not load video dumper")); + message_box.setText( + tr("FFmpeg could not be loaded. Make sure you have a compatible version installed." +#ifdef _WIN32 + "\n\nTo install FFmpeg to Citra, press Open and select your FFmpeg directory." +#endif + "\n\nTo view a guide on how to install FFmpeg, press Help.")); + message_box.setStandardButtons(QMessageBox::Ok | QMessageBox::Help +#ifdef _WIN32 + | QMessageBox::Open +#endif + ); + auto result = message_box.exec(); + if (result == QMessageBox::Help) { + QDesktopServices::openUrl(QUrl(QStringLiteral( + "https://citra-emu.org/wiki/installing-ffmpeg-for-the-video-dumper/"))); +#ifdef _WIN32 + } else if (result == QMessageBox::Open) { + OnOpenFFmpeg(); +#endif + } + } +} + +#ifdef _WIN32 +void GMainWindow::OnOpenFFmpeg() { + auto filename = + QFileDialog::getExistingDirectory(this, tr("Select FFmpeg Directory")).toStdString(); + if (filename.empty()) { + return; + } + // Check for a bin directory if they chose the FFmpeg root directory. + auto bin_dir = filename + DIR_SEP + "bin"; + if (!FileUtil::Exists(bin_dir)) { + // Otherwise, assume the user directly selected the directory containing the DLLs. + bin_dir = filename; + } + + static const std::array library_names = { + DynamicLibrary::DynamicLibrary::GetLibraryName("avcodec", LIBAVCODEC_VERSION_MAJOR), + DynamicLibrary::DynamicLibrary::GetLibraryName("avfilter", LIBAVFILTER_VERSION_MAJOR), + DynamicLibrary::DynamicLibrary::GetLibraryName("avformat", LIBAVFORMAT_VERSION_MAJOR), + DynamicLibrary::DynamicLibrary::GetLibraryName("avutil", LIBAVUTIL_VERSION_MAJOR), + DynamicLibrary::DynamicLibrary::GetLibraryName("swresample", LIBSWRESAMPLE_VERSION_MAJOR), + }; + + for (auto& library_name : library_names) { + if (!FileUtil::Exists(bin_dir + DIR_SEP + library_name)) { + QMessageBox::critical(this, tr("Citra"), + tr("The provided FFmpeg directory is missing %1. Please make " + "sure the correct directory was selected.") + .arg(QString::fromStdString(library_name))); + return; + } + } + + std::atomic success(true); + auto process_file = [&success](u64* num_entries_out, const std::string& directory, + const std::string& virtual_name) -> bool { + auto file_path = directory + DIR_SEP + virtual_name; + if (file_path.ends_with(".dll")) { + auto destination_path = FileUtil::GetExeDirectory() + DIR_SEP + virtual_name; + if (!FileUtil::Copy(file_path, destination_path)) { + success.store(false); + return false; + } + } + return true; + }; + FileUtil::ForeachDirectoryEntry(nullptr, bin_dir, process_file); + + if (success.load()) { + QMessageBox::information(this, tr("Citra"), tr("FFmpeg has been sucessfully installed.")); + } else { + QMessageBox::critical(this, tr("Citra"), + tr("Installation of FFmpeg failed. Check the log file for details.")); + } +} +#endif + void GMainWindow::OnStartVideoDumping() { DumpingDialog dialog(this); if (dialog.exec() != QDialog::DialogCode::Accepted) { @@ -2224,20 +2291,28 @@ void GMainWindow::OnStartVideoDumping() { } const auto path = dialog.GetFilePath(); if (emulation_running) { - Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale( - VideoCore::g_renderer->GetResolutionScaleFactor())}; - if (!system.VideoDumper().StartDumping(path.toStdString(), layout)) { - QMessageBox::critical( - this, tr("Citra"), - tr("Could not start video dumping.
Refer to the log for details.")); - ui->action_Dump_Video->setChecked(false); - } + StartVideoDumping(path); } else { video_dumping_on_start = true; video_dumping_path = path; } } +void GMainWindow::StartVideoDumping(const QString& path) { + Layout::FramebufferLayout layout{ + Layout::FrameLayoutFromResolutionScale(VideoCore::g_renderer->GetResolutionScaleFactor())}; + + auto dumper = std::make_shared(); + if (dumper->StartDumping(path.toStdString(), layout)) { + system.RegisterVideoDumper(dumper); + } else { + QMessageBox::critical( + this, tr("Citra"), + tr("Could not start video dumping.
Refer to the log for details.")); + ui->action_Dump_Video->setChecked(false); + } +} + void GMainWindow::OnStopVideoDumping() { ui->action_Dump_Video->setChecked(false); @@ -2245,14 +2320,15 @@ void GMainWindow::OnStopVideoDumping() { video_dumping_on_start = false; video_dumping_path.clear(); } else { - const bool was_dumping = system.VideoDumper().IsDumping(); - if (!was_dumping) + auto dumper = system.GetVideoDumper(); + if (!dumper || !dumper->IsDumping()) { return; + } game_paused_for_dumping = emu_thread->IsRunning(); OnPauseGame(); - auto future = QtConcurrent::run([this] { system.VideoDumper().StopDumping(); }); + auto future = QtConcurrent::run([dumper] { dumper->StopDumping(); }); auto* future_watcher = new QFutureWatcher(this); connect(future_watcher, &QFutureWatcher::finished, this, [this] { if (game_shutdown_delayed) { @@ -2266,7 +2342,6 @@ void GMainWindow::OnStopVideoDumping() { future_watcher->setFuture(future); } } -#endif void GMainWindow::UpdateStatusBar() { if (!emu_thread) [[unlikely]] { diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index 03b9f54a7..d3c4d03dc 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -242,10 +242,13 @@ private slots: void OnCloseMovie(); void OnSaveMovie(); void OnCaptureScreenshot(); -#ifdef ENABLE_FFMPEG_VIDEO_DUMPER - void OnStartVideoDumping(); - void OnStopVideoDumping(); + void OnDumpVideo(); +#ifdef _WIN32 + void OnOpenFFmpeg(); #endif + void OnStartVideoDumping(); + void StartVideoDumping(const QString& path); + void OnStopVideoDumping(); void OnCoreError(Core::System::ResultStatus, std::string); /// Called whenever a user selects Help->About Citra void OnMenuAboutCitra(); diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 9efb1eac1..7c3e3f841 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -65,6 +65,12 @@ add_library(citra_common STATIC common_precompiled_headers.h common_types.h construct.h + dynamic_library/dynamic_library.cpp + dynamic_library/dynamic_library.h + dynamic_library/fdk-aac.cpp + dynamic_library/fdk-aac.h + dynamic_library/ffmpeg.cpp + dynamic_library/ffmpeg.h error.cpp error.h file_util.cpp @@ -153,7 +159,7 @@ endif() create_target_directory_groups(citra_common) -target_link_libraries(citra_common PUBLIC fmt::fmt microprofile Boost::boost Boost::serialization Boost::iostreams) +target_link_libraries(citra_common PUBLIC fmt::fmt library-headers microprofile Boost::boost Boost::serialization Boost::iostreams) target_link_libraries(citra_common PRIVATE libzstd_static) set_target_properties(citra_common PROPERTIES INTERPROCEDURAL_OPTIMIZATION ${ENABLE_LTO}) diff --git a/src/common/dynamic_library/dynamic_library.cpp b/src/common/dynamic_library/dynamic_library.cpp new file mode 100644 index 000000000..b90cb1424 --- /dev/null +++ b/src/common/dynamic_library/dynamic_library.cpp @@ -0,0 +1,87 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#if defined(_WIN32) +#include +#else +#include +#endif +#include "dynamic_library.h" + +namespace DynamicLibrary { + +DynamicLibrary::DynamicLibrary(std::string_view name, int major, int minor) { + auto full_name = GetLibraryName(name, major, minor); +#if defined(_WIN32) + handle = reinterpret_cast(LoadLibraryA(full_name.c_str())); + if (!handle) { + DWORD error_message_id = GetLastError(); + LPSTR message_buffer = nullptr; + size_t size = + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, error_message_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&message_buffer), 0, nullptr); + std::string message(message_buffer, size); + load_error = message; + } +#else + handle = dlopen(full_name.c_str(), RTLD_LAZY); + if (!handle) { + load_error = dlerror(); + } +#endif // defined(_WIN32) +} + +DynamicLibrary::~DynamicLibrary() { + if (handle) { +#if defined(_WIN32) + FreeLibrary(reinterpret_cast(handle)); +#else + dlclose(handle); +#endif // defined(_WIN32) + handle = nullptr; + } +} + +void* DynamicLibrary::GetRawSymbol(std::string_view name) { +#if defined(_WIN32) + return reinterpret_cast(GetProcAddress(reinterpret_cast(handle), name.data())); +#else + return dlsym(handle, name.data()); +#endif // defined(_WIN32) +} + +std::string DynamicLibrary::GetLibraryName(std::string_view name, int major, int minor) { +#if defined(_WIN32) + if (major >= 0 && minor >= 0) { + return fmt::format("{}-{}-{}.dll", name, major, minor); + } else if (major >= 0) { + return fmt::format("{}-{}.dll", name, major); + } else { + return fmt::format("{}.dll", name); + } +#elif defined(__APPLE__) + auto prefix = name.starts_with("lib") ? "" : "lib"; + if (major >= 0 && minor >= 0) { + return fmt::format("{}{}.{}.{}.dylib", prefix, name, major, minor); + } else if (major >= 0) { + return fmt::format("{}{}.{}.dylib", prefix, name, major); + } else { + return fmt::format("{}{}.dylib", prefix, name); + } +#else + auto prefix = name.starts_with("lib") ? "" : "lib"; + if (major >= 0 && minor >= 0) { + return fmt::format("{}{}.so.{}.{}", prefix, name, major, minor); + } else if (major >= 0) { + return fmt::format("{}{}.so.{}", prefix, name, major); + } else { + return fmt::format("{}{}.so", prefix, name); + } +#endif +} + +} // namespace DynamicLibrary diff --git a/src/common/dynamic_library/dynamic_library.h b/src/common/dynamic_library/dynamic_library.h new file mode 100644 index 000000000..9846c8326 --- /dev/null +++ b/src/common/dynamic_library/dynamic_library.h @@ -0,0 +1,39 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "common/common_types.h" + +namespace DynamicLibrary { + +class DynamicLibrary { +public: + explicit DynamicLibrary(std::string_view name, int major = -1, int minor = -1); + ~DynamicLibrary(); + + bool IsLoaded() { + return handle != nullptr; + } + + std::string_view GetLoadError() { + return load_error; + } + + template + T GetSymbol(std::string_view name) { + return reinterpret_cast(GetRawSymbol(name)); + } + + static std::string GetLibraryName(std::string_view name, int major = -1, int minor = -1); + +private: + void* GetRawSymbol(std::string_view name); + + void* handle; + std::string load_error; +}; + +} // namespace DynamicLibrary diff --git a/src/common/dynamic_library/fdk-aac.cpp b/src/common/dynamic_library/fdk-aac.cpp new file mode 100644 index 000000000..d4b0bcd62 --- /dev/null +++ b/src/common/dynamic_library/fdk-aac.cpp @@ -0,0 +1,54 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/dynamic_library/fdk-aac.h" +#include "common/logging/log.h" + +namespace DynamicLibrary::FdkAac { + +aacDecoder_GetLibInfo_func aacDecoder_GetLibInfo; +aacDecoder_Open_func aacDecoder_Open; +aacDecoder_Close_func aacDecoder_Close; +aacDecoder_SetParam_func aacDecoder_SetParam; +aacDecoder_GetStreamInfo_func aacDecoder_GetStreamInfo; +aacDecoder_DecodeFrame_func aacDecoder_DecodeFrame; +aacDecoder_Fill_func aacDecoder_Fill; + +static std::unique_ptr fdk_aac; + +#define LOAD_SYMBOL(library, name) \ + any_failed = any_failed || (name = library->GetSymbol(#name)) == nullptr + +bool LoadFdkAac() { + if (fdk_aac) { + return true; + } + + fdk_aac = std::make_unique("fdk-aac", 2); + if (!fdk_aac->IsLoaded()) { + LOG_WARNING(Common, "Could not dynamically load libfdk-aac: {}", fdk_aac->GetLoadError()); + fdk_aac.reset(); + return false; + } + + auto any_failed = false; + LOAD_SYMBOL(fdk_aac, aacDecoder_GetLibInfo); + LOAD_SYMBOL(fdk_aac, aacDecoder_Open); + LOAD_SYMBOL(fdk_aac, aacDecoder_Close); + LOAD_SYMBOL(fdk_aac, aacDecoder_SetParam); + LOAD_SYMBOL(fdk_aac, aacDecoder_GetStreamInfo); + LOAD_SYMBOL(fdk_aac, aacDecoder_DecodeFrame); + LOAD_SYMBOL(fdk_aac, aacDecoder_Fill); + + if (any_failed) { + LOG_WARNING(Common, "Could not find all required functions in libfdk-aac."); + fdk_aac.reset(); + return false; + } + + LOG_INFO(Common, "Successfully loaded libfdk-aac."); + return true; +} + +} // namespace DynamicLibrary::FdkAac diff --git a/src/common/dynamic_library/fdk-aac.h b/src/common/dynamic_library/fdk-aac.h new file mode 100644 index 000000000..4c1dca4b8 --- /dev/null +++ b/src/common/dynamic_library/fdk-aac.h @@ -0,0 +1,37 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +extern "C" { +#include +} + +#include "common/common_types.h" +#include "common/dynamic_library/dynamic_library.h" + +namespace DynamicLibrary::FdkAac { + +typedef INT (*aacDecoder_GetLibInfo_func)(LIB_INFO* info); +typedef HANDLE_AACDECODER (*aacDecoder_Open_func)(TRANSPORT_TYPE transportFmt, UINT nrOfLayers); +typedef void (*aacDecoder_Close_func)(HANDLE_AACDECODER self); +typedef AAC_DECODER_ERROR (*aacDecoder_SetParam_func)(const HANDLE_AACDECODER self, + const AACDEC_PARAM param, const INT value); +typedef CStreamInfo* (*aacDecoder_GetStreamInfo_func)(HANDLE_AACDECODER self); +typedef AAC_DECODER_ERROR (*aacDecoder_DecodeFrame_func)(HANDLE_AACDECODER self, INT_PCM* pTimeData, + const INT timeDataSize, const UINT flags); +typedef AAC_DECODER_ERROR (*aacDecoder_Fill_func)(HANDLE_AACDECODER self, UCHAR* pBuffer[], + const UINT bufferSize[], UINT* bytesValid); + +extern aacDecoder_GetLibInfo_func aacDecoder_GetLibInfo; +extern aacDecoder_Open_func aacDecoder_Open; +extern aacDecoder_Close_func aacDecoder_Close; +extern aacDecoder_SetParam_func aacDecoder_SetParam; +extern aacDecoder_GetStreamInfo_func aacDecoder_GetStreamInfo; +extern aacDecoder_DecodeFrame_func aacDecoder_DecodeFrame; +extern aacDecoder_Fill_func aacDecoder_Fill; + +bool LoadFdkAac(); + +} // namespace DynamicLibrary::FdkAac diff --git a/src/common/dynamic_library/ffmpeg.cpp b/src/common/dynamic_library/ffmpeg.cpp new file mode 100644 index 000000000..0a1f0a280 --- /dev/null +++ b/src/common/dynamic_library/ffmpeg.cpp @@ -0,0 +1,390 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/dynamic_library/ffmpeg.h" +#include "common/logging/log.h" + +namespace DynamicLibrary::FFmpeg { + +// avutil +av_buffer_ref_func av_buffer_ref; +av_buffer_unref_func av_buffer_unref; +av_d2q_func av_d2q; +av_dict_count_func av_dict_count; +av_dict_get_func av_dict_get; +av_dict_get_string_func av_dict_get_string; +av_dict_set_func av_dict_set; +av_frame_alloc_func av_frame_alloc; +av_frame_free_func av_frame_free; +av_frame_unref_func av_frame_unref; +av_freep_func av_freep; +av_get_bytes_per_sample_func av_get_bytes_per_sample; +av_get_pix_fmt_func av_get_pix_fmt; +av_get_pix_fmt_name_func av_get_pix_fmt_name; +av_get_sample_fmt_name_func av_get_sample_fmt_name; +av_hwdevice_ctx_create_func av_hwdevice_ctx_create; +av_hwdevice_get_hwframe_constraints_func av_hwdevice_get_hwframe_constraints; +av_hwframe_constraints_free_func av_hwframe_constraints_free; +av_hwframe_ctx_alloc_func av_hwframe_ctx_alloc; +av_hwframe_ctx_init_func av_hwframe_ctx_init; +av_hwframe_get_buffer_func av_hwframe_get_buffer; +av_hwframe_transfer_data_func av_hwframe_transfer_data; +av_int_list_length_for_size_func av_int_list_length_for_size; +#if LIBAVCODEC_VERSION_MAJOR >= 59 +av_opt_child_class_iterate_func av_opt_child_class_iterate; +#else +av_opt_child_class_next_func av_opt_child_class_next; +#endif +av_opt_next_func av_opt_next; +av_opt_set_bin_func av_opt_set_bin; +av_pix_fmt_desc_get_func av_pix_fmt_desc_get; +av_pix_fmt_desc_next_func av_pix_fmt_desc_next; +av_sample_fmt_is_planar_func av_sample_fmt_is_planar; +av_samples_alloc_array_and_samples_func av_samples_alloc_array_and_samples; +av_strdup_func av_strdup; +avutil_version_func avutil_version; + +// avcodec +av_codec_is_encoder_func av_codec_is_encoder; +av_codec_iterate_func av_codec_iterate; +av_init_packet_func av_init_packet; +av_packet_alloc_func av_packet_alloc; +av_packet_free_func av_packet_free; +av_packet_rescale_ts_func av_packet_rescale_ts; +av_parser_close_func av_parser_close; +av_parser_init_func av_parser_init; +av_parser_parse2_func av_parser_parse2; +avcodec_alloc_context3_func avcodec_alloc_context3; +avcodec_descriptor_next_func avcodec_descriptor_next; +avcodec_find_decoder_func avcodec_find_decoder; +avcodec_find_encoder_by_name_func avcodec_find_encoder_by_name; +avcodec_free_context_func avcodec_free_context; +avcodec_get_class_func avcodec_get_class; +avcodec_get_hw_config_func avcodec_get_hw_config; +avcodec_open2_func avcodec_open2; +avcodec_parameters_from_context_func avcodec_parameters_from_context; +avcodec_receive_frame_func avcodec_receive_frame; +avcodec_receive_packet_func avcodec_receive_packet; +avcodec_send_frame_func avcodec_send_frame; +avcodec_send_packet_func avcodec_send_packet; +avcodec_version_func avcodec_version; + +// avfilter +av_buffersink_get_frame_func av_buffersink_get_frame; +av_buffersrc_add_frame_func av_buffersrc_add_frame; +avfilter_get_by_name_func avfilter_get_by_name; +avfilter_graph_alloc_func avfilter_graph_alloc; +avfilter_graph_config_func avfilter_graph_config; +avfilter_graph_create_filter_func avfilter_graph_create_filter; +avfilter_graph_free_func avfilter_graph_free; +avfilter_graph_parse_ptr_func avfilter_graph_parse_ptr; +avfilter_inout_alloc_func avfilter_inout_alloc; +avfilter_inout_free_func avfilter_inout_free; +avfilter_version_func avfilter_version; + +// avformat +av_guess_format_func av_guess_format; +av_interleaved_write_frame_func av_interleaved_write_frame; +av_muxer_iterate_func av_muxer_iterate; +av_write_trailer_func av_write_trailer; +avformat_alloc_output_context2_func avformat_alloc_output_context2; +avformat_free_context_func avformat_free_context; +avformat_get_class_func avformat_get_class; +avformat_network_init_func avformat_network_init; +avformat_new_stream_func avformat_new_stream; +avformat_query_codec_func avformat_query_codec; +avformat_write_header_func avformat_write_header; +avformat_version_func avformat_version; +avio_closep_func avio_closep; +avio_open_func avio_open; + +// swresample +#if LIBSWRESAMPLE_VERSION_INT >= AV_VERSION_INT(4, 5, 100) +swr_alloc_set_opts2_func swr_alloc_set_opts2; +#else +swr_alloc_set_opts_func swr_alloc_set_opts; +#endif +swr_convert_func swr_convert; +swr_free_func swr_free; +swr_init_func swr_init; +swresample_version_func swresample_version; + +static std::unique_ptr avutil; +static std::unique_ptr avcodec; +static std::unique_ptr avfilter; +static std::unique_ptr avformat; +static std::unique_ptr swresample; + +#define LOAD_SYMBOL(library, name) \ + any_failed = any_failed || (name = library->GetSymbol(#name)) == nullptr + +static bool LoadAVUtil() { + if (avutil) { + return true; + } + + avutil = std::make_unique("avutil", LIBAVUTIL_VERSION_MAJOR); + if (!avutil->IsLoaded()) { + LOG_WARNING(Common, "Could not dynamically load libavutil: {}", avutil->GetLoadError()); + avutil.reset(); + return false; + } + + auto any_failed = false; + + LOAD_SYMBOL(avutil, avutil_version); + + auto major_version = AV_VERSION_MAJOR(avutil_version()); + if (major_version != LIBAVUTIL_VERSION_MAJOR) { + LOG_WARNING(Common, "libavutil version {} does not match supported version {}.", + major_version, LIBAVUTIL_VERSION_MAJOR); + avutil.reset(); + return false; + } + + LOAD_SYMBOL(avutil, av_buffer_ref); + LOAD_SYMBOL(avutil, av_buffer_unref); + LOAD_SYMBOL(avutil, av_d2q); + LOAD_SYMBOL(avutil, av_dict_count); + LOAD_SYMBOL(avutil, av_dict_get); + LOAD_SYMBOL(avutil, av_dict_get_string); + LOAD_SYMBOL(avutil, av_dict_set); + LOAD_SYMBOL(avutil, av_frame_alloc); + LOAD_SYMBOL(avutil, av_frame_free); + LOAD_SYMBOL(avutil, av_frame_unref); + LOAD_SYMBOL(avutil, av_freep); + LOAD_SYMBOL(avutil, av_get_bytes_per_sample); + LOAD_SYMBOL(avutil, av_get_pix_fmt); + LOAD_SYMBOL(avutil, av_get_pix_fmt_name); + LOAD_SYMBOL(avutil, av_get_sample_fmt_name); + LOAD_SYMBOL(avutil, av_hwdevice_ctx_create); + LOAD_SYMBOL(avutil, av_hwdevice_get_hwframe_constraints); + LOAD_SYMBOL(avutil, av_hwframe_constraints_free); + LOAD_SYMBOL(avutil, av_hwframe_ctx_alloc); + LOAD_SYMBOL(avutil, av_hwframe_ctx_init); + LOAD_SYMBOL(avutil, av_hwframe_get_buffer); + LOAD_SYMBOL(avutil, av_hwframe_transfer_data); + LOAD_SYMBOL(avutil, av_int_list_length_for_size); +#if LIBAVCODEC_VERSION_MAJOR >= 59 + LOAD_SYMBOL(avutil, av_opt_child_class_iterate); +#else + LOAD_SYMBOL(avutil, av_opt_child_class_next); +#endif + LOAD_SYMBOL(avutil, av_opt_next); + LOAD_SYMBOL(avutil, av_opt_set_bin); + LOAD_SYMBOL(avutil, av_pix_fmt_desc_get); + LOAD_SYMBOL(avutil, av_pix_fmt_desc_next); + LOAD_SYMBOL(avutil, av_sample_fmt_is_planar); + LOAD_SYMBOL(avutil, av_samples_alloc_array_and_samples); + LOAD_SYMBOL(avutil, av_strdup); + + if (any_failed) { + LOG_WARNING(Common, "Could not find all required functions in libavutil."); + avutil.reset(); + return false; + } + + LOG_INFO(Common, "Successfully loaded libavutil."); + return true; +} + +static bool LoadAVCodec() { + if (avcodec) { + return true; + } + + avcodec = std::make_unique("avcodec", LIBAVCODEC_VERSION_MAJOR); + if (!avcodec->IsLoaded()) { + LOG_WARNING(Common, "Could not dynamically load libavcodec: {}", avcodec->GetLoadError()); + avcodec.reset(); + return false; + } + + auto any_failed = false; + + LOAD_SYMBOL(avcodec, avcodec_version); + + auto major_version = AV_VERSION_MAJOR(avcodec_version()); + if (major_version != LIBAVCODEC_VERSION_MAJOR) { + LOG_WARNING(Common, "libavcodec version {} does not match supported version {}.", + major_version, LIBAVCODEC_VERSION_MAJOR); + avcodec.reset(); + return false; + } + + LOAD_SYMBOL(avcodec, av_codec_is_encoder); + LOAD_SYMBOL(avcodec, av_codec_iterate); + LOAD_SYMBOL(avcodec, av_init_packet); + LOAD_SYMBOL(avcodec, av_packet_alloc); + LOAD_SYMBOL(avcodec, av_packet_free); + LOAD_SYMBOL(avcodec, av_packet_rescale_ts); + LOAD_SYMBOL(avcodec, av_parser_close); + LOAD_SYMBOL(avcodec, av_parser_init); + LOAD_SYMBOL(avcodec, av_parser_parse2); + LOAD_SYMBOL(avcodec, avcodec_alloc_context3); + LOAD_SYMBOL(avcodec, avcodec_descriptor_next); + LOAD_SYMBOL(avcodec, avcodec_find_decoder); + LOAD_SYMBOL(avcodec, avcodec_find_encoder_by_name); + LOAD_SYMBOL(avcodec, avcodec_free_context); + LOAD_SYMBOL(avcodec, avcodec_get_class); + LOAD_SYMBOL(avcodec, avcodec_get_hw_config); + LOAD_SYMBOL(avcodec, avcodec_open2); + LOAD_SYMBOL(avcodec, avcodec_parameters_from_context); + LOAD_SYMBOL(avcodec, avcodec_receive_frame); + LOAD_SYMBOL(avcodec, avcodec_receive_packet); + LOAD_SYMBOL(avcodec, avcodec_send_frame); + LOAD_SYMBOL(avcodec, avcodec_send_packet); + + if (any_failed) { + LOG_WARNING(Common, "Could not find all required functions in libavcodec."); + avcodec.reset(); + return false; + } + + LOG_INFO(Common, "Successfully loaded libavcodec."); + return true; +} + +static bool LoadAVFilter() { + if (avfilter) { + return true; + } + + avfilter = std::make_unique("avfilter", LIBAVFILTER_VERSION_MAJOR); + if (!avfilter->IsLoaded()) { + LOG_WARNING(Common, "Could not dynamically load libavfilter: {}", avfilter->GetLoadError()); + avfilter.reset(); + return false; + } + + auto any_failed = false; + + LOAD_SYMBOL(avfilter, avfilter_version); + + auto major_version = AV_VERSION_MAJOR(avfilter_version()); + if (major_version != LIBAVFILTER_VERSION_MAJOR) { + LOG_WARNING(Common, "libavfilter version {} does not match supported version {}.", + major_version, LIBAVFILTER_VERSION_MAJOR); + avfilter.reset(); + return false; + } + + LOAD_SYMBOL(avfilter, av_buffersink_get_frame); + LOAD_SYMBOL(avfilter, av_buffersrc_add_frame); + LOAD_SYMBOL(avfilter, avfilter_get_by_name); + LOAD_SYMBOL(avfilter, avfilter_graph_alloc); + LOAD_SYMBOL(avfilter, avfilter_graph_config); + LOAD_SYMBOL(avfilter, avfilter_graph_create_filter); + LOAD_SYMBOL(avfilter, avfilter_graph_free); + LOAD_SYMBOL(avfilter, avfilter_graph_parse_ptr); + LOAD_SYMBOL(avfilter, avfilter_inout_alloc); + LOAD_SYMBOL(avfilter, avfilter_inout_free); + + if (any_failed) { + LOG_WARNING(Common, "Could not find all required functions in libavfilter."); + avfilter.reset(); + return false; + } + + LOG_INFO(Common, "Successfully loaded libavfilter."); + return true; +} + +static bool LoadAVFormat() { + if (avformat) { + return true; + } + + avformat = std::make_unique("avformat", LIBAVFORMAT_VERSION_MAJOR); + if (!avformat->IsLoaded()) { + LOG_WARNING(Common, "Could not dynamically load libavformat: {}", avformat->GetLoadError()); + avformat.reset(); + return false; + } + + auto any_failed = false; + + LOAD_SYMBOL(avformat, avformat_version); + + auto major_version = AV_VERSION_MAJOR(avformat_version()); + if (major_version != LIBAVFORMAT_VERSION_MAJOR) { + LOG_WARNING(Common, "libavformat version {} does not match supported version {}.", + major_version, LIBAVFORMAT_VERSION_MAJOR); + avformat.reset(); + return false; + } + + LOAD_SYMBOL(avformat, av_guess_format); + LOAD_SYMBOL(avformat, av_interleaved_write_frame); + LOAD_SYMBOL(avformat, av_muxer_iterate); + LOAD_SYMBOL(avformat, av_write_trailer); + LOAD_SYMBOL(avformat, avformat_alloc_output_context2); + LOAD_SYMBOL(avformat, avformat_free_context); + LOAD_SYMBOL(avformat, avformat_get_class); + LOAD_SYMBOL(avformat, avformat_network_init); + LOAD_SYMBOL(avformat, avformat_new_stream); + LOAD_SYMBOL(avformat, avformat_query_codec); + LOAD_SYMBOL(avformat, avformat_write_header); + LOAD_SYMBOL(avformat, avio_closep); + LOAD_SYMBOL(avformat, avio_open); + + if (any_failed) { + LOG_WARNING(Common, "Could not find all required functions in libavformat."); + avformat.reset(); + return false; + } + + LOG_INFO(Common, "Successfully loaded libavformat."); + return true; +} + +static bool LoadSWResample() { + if (swresample) { + return true; + } + + swresample = std::make_unique("swresample", LIBSWRESAMPLE_VERSION_MAJOR); + if (!swresample->IsLoaded()) { + LOG_WARNING(Common, "Could not dynamically load libswresample: {}", + swresample->GetLoadError()); + swresample.reset(); + return false; + } + + auto any_failed = false; + + LOAD_SYMBOL(swresample, swresample_version); + + auto major_version = AV_VERSION_MAJOR(swresample_version()); + if (major_version != LIBSWRESAMPLE_VERSION_MAJOR) { + LOG_WARNING(Common, "libswresample version {} does not match supported version {}.", + major_version, LIBSWRESAMPLE_VERSION_MAJOR); + swresample.reset(); + return false; + } + +#if LIBSWRESAMPLE_VERSION_INT >= AV_VERSION_INT(4, 5, 100) + LOAD_SYMBOL(swresample, swr_alloc_set_opts2); +#else + LOAD_SYMBOL(swresample, swr_alloc_set_opts); +#endif + LOAD_SYMBOL(swresample, swr_convert); + LOAD_SYMBOL(swresample, swr_free); + LOAD_SYMBOL(swresample, swr_init); + + if (any_failed) { + LOG_WARNING(Common, "Could not find all required functions in libswresample."); + swresample.reset(); + return false; + } + + LOG_INFO(Common, "Successfully loaded libswresample."); + return true; +} + +bool LoadFFmpeg() { + return LoadAVUtil() && LoadAVCodec() && LoadAVFilter() && LoadAVFormat() && LoadSWResample(); +} + +} // namespace DynamicLibrary::FFmpeg diff --git a/src/common/dynamic_library/ffmpeg.h b/src/common/dynamic_library/ffmpeg.h new file mode 100644 index 000000000..023847179 --- /dev/null +++ b/src/common/dynamic_library/ffmpeg.h @@ -0,0 +1,236 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +} + +#include "common/common_types.h" +#include "common/dynamic_library/dynamic_library.h" + +namespace DynamicLibrary::FFmpeg { + +// avutil +typedef AVBufferRef* (*av_buffer_ref_func)(const AVBufferRef*); +typedef void (*av_buffer_unref_func)(AVBufferRef**); +typedef AVRational (*av_d2q_func)(double d, int max); +typedef int (*av_dict_count_func)(const AVDictionary*); +typedef AVDictionaryEntry* (*av_dict_get_func)(const AVDictionary*, const char*, + const AVDictionaryEntry*, int); +typedef int (*av_dict_get_string_func)(const AVDictionary*, char**, const char, const char); +typedef int (*av_dict_set_func)(AVDictionary**, const char*, const char*, int); +typedef AVFrame* (*av_frame_alloc_func)(); +typedef void (*av_frame_free_func)(AVFrame**); +typedef void (*av_frame_unref_func)(AVFrame*); +typedef void (*av_freep_func)(void*); +typedef int (*av_get_bytes_per_sample_func)(AVSampleFormat); +typedef AVPixelFormat (*av_get_pix_fmt_func)(const char*); +typedef const char* (*av_get_pix_fmt_name_func)(AVPixelFormat); +typedef const char* (*av_get_sample_fmt_name_func)(AVSampleFormat); +typedef int (*av_hwdevice_ctx_create_func)(AVBufferRef**, AVHWDeviceType, const char*, + AVDictionary*, int); +typedef AVHWFramesConstraints* (*av_hwdevice_get_hwframe_constraints_func)(AVBufferRef*, + const void*); +typedef void (*av_hwframe_constraints_free_func)(AVHWFramesConstraints**); +typedef AVBufferRef* (*av_hwframe_ctx_alloc_func)(AVBufferRef*); +typedef int (*av_hwframe_ctx_init_func)(AVBufferRef*); +typedef int (*av_hwframe_get_buffer_func)(AVBufferRef*, AVFrame*, int); +typedef int (*av_hwframe_transfer_data_func)(AVFrame*, const AVFrame*, int); +typedef unsigned (*av_int_list_length_for_size_func)(unsigned, const void*, uint64_t); +#if LIBAVCODEC_VERSION_MAJOR >= 59 +typedef const AVClass* (*av_opt_child_class_iterate_func)(const AVClass*, void**); +#else +typedef const AVClass* (*av_opt_child_class_next_func)(const AVClass*, const AVClass*); +#endif +typedef const AVOption* (*av_opt_next_func)(const void*, const AVOption*); +typedef int (*av_opt_set_bin_func)(void*, const char*, const uint8_t*, int, int); +typedef const AVPixFmtDescriptor* (*av_pix_fmt_desc_get_func)(AVPixelFormat); +typedef const AVPixFmtDescriptor* (*av_pix_fmt_desc_next_func)(const AVPixFmtDescriptor*); +typedef int (*av_sample_fmt_is_planar_func)(AVSampleFormat); +typedef int (*av_samples_alloc_array_and_samples_func)(uint8_t***, int*, int, int, AVSampleFormat, + int); +typedef char* (*av_strdup_func)(const char*); +typedef unsigned (*avutil_version_func)(); + +extern av_buffer_ref_func av_buffer_ref; +extern av_buffer_unref_func av_buffer_unref; +extern av_d2q_func av_d2q; +extern av_dict_count_func av_dict_count; +extern av_dict_get_func av_dict_get; +extern av_dict_get_string_func av_dict_get_string; +extern av_dict_set_func av_dict_set; +extern av_frame_alloc_func av_frame_alloc; +extern av_frame_free_func av_frame_free; +extern av_frame_unref_func av_frame_unref; +extern av_freep_func av_freep; +extern av_get_bytes_per_sample_func av_get_bytes_per_sample; +extern av_get_pix_fmt_func av_get_pix_fmt; +extern av_get_pix_fmt_name_func av_get_pix_fmt_name; +extern av_get_sample_fmt_name_func av_get_sample_fmt_name; +extern av_hwdevice_ctx_create_func av_hwdevice_ctx_create; +extern av_hwdevice_get_hwframe_constraints_func av_hwdevice_get_hwframe_constraints; +extern av_hwframe_constraints_free_func av_hwframe_constraints_free; +extern av_hwframe_ctx_alloc_func av_hwframe_ctx_alloc; +extern av_hwframe_ctx_init_func av_hwframe_ctx_init; +extern av_hwframe_get_buffer_func av_hwframe_get_buffer; +extern av_hwframe_transfer_data_func av_hwframe_transfer_data; +extern av_int_list_length_for_size_func av_int_list_length_for_size; +#if LIBAVCODEC_VERSION_MAJOR >= 59 +extern av_opt_child_class_iterate_func av_opt_child_class_iterate; +#else +extern av_opt_child_class_next_func av_opt_child_class_next; +#endif +extern av_opt_next_func av_opt_next; +extern av_opt_set_bin_func av_opt_set_bin; +extern av_pix_fmt_desc_get_func av_pix_fmt_desc_get; +extern av_pix_fmt_desc_next_func av_pix_fmt_desc_next; +extern av_sample_fmt_is_planar_func av_sample_fmt_is_planar; +extern av_samples_alloc_array_and_samples_func av_samples_alloc_array_and_samples; +extern av_strdup_func av_strdup; +extern avutil_version_func avutil_version; + +// avcodec +typedef int (*av_codec_is_encoder_func)(const AVCodec*); +typedef const AVCodec* (*av_codec_iterate_func)(void**); +typedef void (*av_init_packet_func)(AVPacket*); +typedef AVPacket* (*av_packet_alloc_func)(); +typedef void (*av_packet_free_func)(AVPacket**); +typedef void (*av_packet_rescale_ts_func)(AVPacket*, AVRational, AVRational); +typedef void (*av_parser_close_func)(AVCodecParserContext*); +typedef AVCodecParserContext* (*av_parser_init_func)(int); +typedef int (*av_parser_parse2_func)(AVCodecParserContext*, AVCodecContext*, uint8_t**, int*, + const uint8_t*, int, int64_t, int64_t, int64_t); +typedef AVCodecContext* (*avcodec_alloc_context3_func)(const AVCodec*); +typedef const AVCodecDescriptor* (*avcodec_descriptor_next_func)(const AVCodecDescriptor*); +typedef AVCodec* (*avcodec_find_decoder_func)(AVCodecID); +typedef const AVCodec* (*avcodec_find_encoder_by_name_func)(const char*); +typedef void (*avcodec_free_context_func)(AVCodecContext**); +typedef const AVClass* (*avcodec_get_class_func)(); +typedef const AVCodecHWConfig* (*avcodec_get_hw_config_func)(const AVCodec*, int); +typedef int (*avcodec_open2_func)(AVCodecContext*, const AVCodec*, AVDictionary**); +typedef int (*avcodec_parameters_from_context_func)(AVCodecParameters* par, const AVCodecContext*); +typedef int (*avcodec_receive_frame_func)(AVCodecContext*, AVFrame*); +typedef int (*avcodec_receive_packet_func)(AVCodecContext*, AVPacket*); +typedef int (*avcodec_send_frame_func)(AVCodecContext*, const AVFrame*); +typedef int (*avcodec_send_packet_func)(AVCodecContext*, const AVPacket*); +typedef unsigned (*avcodec_version_func)(); + +extern av_codec_is_encoder_func av_codec_is_encoder; +extern av_codec_iterate_func av_codec_iterate; +extern av_init_packet_func av_init_packet; +extern av_packet_alloc_func av_packet_alloc; +extern av_packet_free_func av_packet_free; +extern av_packet_rescale_ts_func av_packet_rescale_ts; +extern av_parser_close_func av_parser_close; +extern av_parser_init_func av_parser_init; +extern av_parser_parse2_func av_parser_parse2; +extern avcodec_alloc_context3_func avcodec_alloc_context3; +extern avcodec_descriptor_next_func avcodec_descriptor_next; +extern avcodec_find_decoder_func avcodec_find_decoder; +extern avcodec_find_encoder_by_name_func avcodec_find_encoder_by_name; +extern avcodec_free_context_func avcodec_free_context; +extern avcodec_get_class_func avcodec_get_class; +extern avcodec_get_hw_config_func avcodec_get_hw_config; +extern avcodec_open2_func avcodec_open2; +extern avcodec_parameters_from_context_func avcodec_parameters_from_context; +extern avcodec_receive_frame_func avcodec_receive_frame; +extern avcodec_receive_packet_func avcodec_receive_packet; +extern avcodec_send_frame_func avcodec_send_frame; +extern avcodec_send_packet_func avcodec_send_packet; +extern avcodec_version_func avcodec_version; + +// avfilter +typedef int (*av_buffersink_get_frame_func)(AVFilterContext*, AVFrame*); +typedef int (*av_buffersrc_add_frame_func)(AVFilterContext*, AVFrame*); +typedef const AVFilter* (*avfilter_get_by_name_func)(const char*); +typedef AVFilterGraph* (*avfilter_graph_alloc_func)(); +typedef int (*avfilter_graph_config_func)(AVFilterGraph*, void*); +typedef int (*avfilter_graph_create_filter_func)(AVFilterContext**, const AVFilter*, const char*, + const char*, void*, AVFilterGraph*); +typedef void (*avfilter_graph_free_func)(AVFilterGraph** graph); +typedef int (*avfilter_graph_parse_ptr_func)(AVFilterGraph*, const char*, AVFilterInOut**, + AVFilterInOut**, void*); +typedef AVFilterInOut* (*avfilter_inout_alloc_func)(); +typedef void (*avfilter_inout_free_func)(AVFilterInOut**); +typedef unsigned (*avfilter_version_func)(); + +extern av_buffersink_get_frame_func av_buffersink_get_frame; +extern av_buffersrc_add_frame_func av_buffersrc_add_frame; +extern avfilter_get_by_name_func avfilter_get_by_name; +extern avfilter_graph_alloc_func avfilter_graph_alloc; +extern avfilter_graph_config_func avfilter_graph_config; +extern avfilter_graph_create_filter_func avfilter_graph_create_filter; +extern avfilter_graph_free_func avfilter_graph_free; +extern avfilter_graph_parse_ptr_func avfilter_graph_parse_ptr; +extern avfilter_inout_alloc_func avfilter_inout_alloc; +extern avfilter_inout_free_func avfilter_inout_free; +extern avfilter_version_func avfilter_version; + +// avformat +typedef const AVOutputFormat* (*av_guess_format_func)(const char*, const char*, const char*); +typedef int (*av_interleaved_write_frame_func)(AVFormatContext*, AVPacket*); +typedef const AVOutputFormat* (*av_muxer_iterate_func)(void**); +typedef int (*av_write_trailer_func)(AVFormatContext*); +typedef int (*avformat_alloc_output_context2_func)(AVFormatContext**, const AVOutputFormat*, + const char*, const char*); +typedef void (*avformat_free_context_func)(AVFormatContext*); +typedef const AVClass* (*avformat_get_class_func)(); +typedef int (*avformat_network_init_func)(); +typedef AVStream* (*avformat_new_stream_func)(AVFormatContext*, const AVCodec*); +typedef int (*avformat_query_codec_func)(const AVOutputFormat*, AVCodecID, int); +typedef int (*avformat_write_header_func)(AVFormatContext*, AVDictionary**); +typedef unsigned (*avformat_version_func)(); +typedef int (*avio_closep_func)(AVIOContext**); +typedef int (*avio_open_func)(AVIOContext**, const char*, int); + +extern av_guess_format_func av_guess_format; +extern av_interleaved_write_frame_func av_interleaved_write_frame; +extern av_muxer_iterate_func av_muxer_iterate; +extern av_write_trailer_func av_write_trailer; +extern avformat_alloc_output_context2_func avformat_alloc_output_context2; +extern avformat_free_context_func avformat_free_context; +extern avformat_get_class_func avformat_get_class; +extern avformat_network_init_func avformat_network_init; +extern avformat_new_stream_func avformat_new_stream; +extern avformat_query_codec_func avformat_query_codec; +extern avformat_write_header_func avformat_write_header; +extern avformat_version_func avformat_version; +extern avio_closep_func avio_closep; +extern avio_open_func avio_open; + +// swresample +#if LIBSWRESAMPLE_VERSION_INT >= AV_VERSION_INT(4, 5, 100) +typedef SwrContext* (*swr_alloc_set_opts2_func)(SwrContext**, AVChannelLayout*, AVSampleFormat, int, + AVChannelLayout*, AVSampleFormat, int, int, void*); +#else +typedef SwrContext* (*swr_alloc_set_opts_func)(SwrContext*, int64_t, AVSampleFormat, int, int64_t, + AVSampleFormat, int, int, void*); +#endif +typedef int (*swr_convert_func)(SwrContext*, uint8_t**, int, const uint8_t**, int); +typedef void (*swr_free_func)(SwrContext**); +typedef int (*swr_init_func)(SwrContext*); +typedef unsigned (*swresample_version_func)(); + +#if LIBSWRESAMPLE_VERSION_INT >= AV_VERSION_INT(4, 5, 100) +extern swr_alloc_set_opts2_func swr_alloc_set_opts2; +#else +extern swr_alloc_set_opts_func swr_alloc_set_opts; +#endif +extern swr_convert_func swr_convert; +extern swr_free_func swr_free; +extern swr_init_func swr_init; +extern swresample_version_func swresample_version; + +bool LoadFFmpeg(); + +} // namespace DynamicLibrary::FFmpeg diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 1ee217a6b..b8a9d778e 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -38,6 +38,8 @@ add_library(citra_core STATIC core_timing.h dumping/backend.cpp dumping/backend.h + dumping/ffmpeg_backend.cpp + dumping/ffmpeg_backend.h file_sys/archive_backend.cpp file_sys/archive_backend.h file_sys/archive_extsavedata.cpp @@ -468,13 +470,6 @@ add_library(citra_core STATIC tracer/recorder.h ) -if (ENABLE_FFMPEG_VIDEO_DUMPER) - target_sources(citra_core PRIVATE - dumping/ffmpeg_backend.cpp - dumping/ffmpeg_backend.h - ) -endif() - create_target_directory_groups(citra_core) target_link_libraries(citra_core PUBLIC citra_common PRIVATE audio_core network video_core) @@ -504,10 +499,6 @@ if ("x86_64" IN_LIST ARCHITECTURE OR "arm64" IN_LIST ARCHITECTURE) target_link_libraries(citra_core PRIVATE dynarmic) endif() -if (ENABLE_FFMPEG_VIDEO_DUMPER) - target_link_libraries(citra_core PUBLIC FFmpeg::avcodec FFmpeg::avfilter FFmpeg::avformat FFmpeg::swresample FFmpeg::avutil) -endif() - if (CITRA_USE_PRECOMPILED_HEADERS) target_precompile_headers(citra_core PRIVATE precompiled_headers.h) endif() diff --git a/src/core/core.cpp b/src/core/core.cpp index e1710b4b7..a4ad8a817 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -11,6 +11,7 @@ #include "audio_core/lle/lle.h" #include "common/arch.h" #include "common/logging/log.h" +#include "common/settings.h" #include "common/texture.h" #include "core/arm/arm_interface.h" #include "core/arm/exclusive_monitor.h" @@ -22,10 +23,7 @@ #include "core/core.h" #include "core/core_timing.h" #include "core/dumping/backend.h" -#ifdef ENABLE_FFMPEG_VIDEO_DUMPER #include "core/dumping/ffmpeg_backend.h" -#endif -#include "common/settings.h" #include "core/frontend/image_interface.h" #include "core/gdbstub/gdbstub.h" #include "core/global.h" @@ -423,12 +421,6 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, Service::Init(*this); GDBStub::DeferStart(); -#ifdef ENABLE_FFMPEG_VIDEO_DUMPER - video_dumper = std::make_unique(); -#else - video_dumper = std::make_unique(); -#endif - if (!registered_image_interface) { registered_image_interface = std::make_shared(); } @@ -500,12 +492,8 @@ const Cheats::CheatEngine& System::CheatEngine() const { return *cheat_engine; } -VideoDumper::Backend& System::VideoDumper() { - return *video_dumper; -} - -const VideoDumper::Backend& System::VideoDumper() const { - return *video_dumper; +void System::RegisterVideoDumper(std::shared_ptr dumper) { + video_dumper = std::move(dumper); } VideoCore::CustomTexManager& System::CustomTexManager() { diff --git a/src/core/core.h b/src/core/core.h index d4cab87e3..b29fc20e2 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -258,11 +258,13 @@ public: /// Gets a const reference to the custom texture cache system [[nodiscard]] const VideoCore::CustomTexManager& CustomTexManager() const; - /// Gets a reference to the video dumper backend - [[nodiscard]] VideoDumper::Backend& VideoDumper(); + /// Video Dumper interface - /// Gets a const reference to the video dumper backend - [[nodiscard]] const VideoDumper::Backend& VideoDumper() const; + void RegisterVideoDumper(std::shared_ptr video_dumper); + + [[nodiscard]] std::shared_ptr GetVideoDumper() const { + return video_dumper; + } std::unique_ptr perf_stats; FrameLimiter frame_limiter; @@ -370,7 +372,7 @@ private: std::unique_ptr cheat_engine; /// Video dumper backend - std::unique_ptr video_dumper; + std::shared_ptr video_dumper; /// Custom texture cache system std::unique_ptr custom_tex_manager; diff --git a/src/core/dumping/ffmpeg_backend.cpp b/src/core/dumping/ffmpeg_backend.cpp index 0981a2715..c0c8a4f46 100644 --- a/src/core/dumping/ffmpeg_backend.cpp +++ b/src/core/dumping/ffmpeg_backend.cpp @@ -15,24 +15,17 @@ #include "video_core/renderer_base.h" #include "video_core/video_core.h" -extern "C" { -#include -#include -#include -#include -} +using namespace DynamicLibrary; namespace VideoDumper { void InitializeFFmpegLibraries() { static bool initialized = false; - - if (initialized) + if (initialized) { return; -#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100) - av_register_all(); -#endif - avformat_network_init(); + } + + FFmpeg::avformat_network_init(); initialized = true; } @@ -40,7 +33,7 @@ AVDictionary* ToAVDictionary(const std::string& serialized) { Common::ParamPackage param_package{serialized}; AVDictionary* result = nullptr; for (const auto& [key, value] : param_package) { - av_dict_set(&result, key.c_str(), value.c_str(), 0); + FFmpeg::av_dict_set(&result, key.c_str(), value.c_str(), 0); } return result; } @@ -67,29 +60,29 @@ void FFmpegStream::Flush() { } void FFmpegStream::WritePacket(AVPacket& packet) { - av_packet_rescale_ts(&packet, codec_context->time_base, stream->time_base); + FFmpeg::av_packet_rescale_ts(&packet, codec_context->time_base, stream->time_base); packet.stream_index = stream->index; { std::lock_guard lock{*format_context_mutex}; - av_interleaved_write_frame(format_context, &packet); + FFmpeg::av_interleaved_write_frame(format_context, &packet); } } void FFmpegStream::SendFrame(AVFrame* frame) { // Initialize packet AVPacket packet; - av_init_packet(&packet); + FFmpeg::av_init_packet(&packet); packet.data = nullptr; packet.size = 0; // Encode frame - if (avcodec_send_frame(codec_context.get(), frame) < 0) { + if (FFmpeg::avcodec_send_frame(codec_context.get(), frame) < 0) { LOG_ERROR(Render, "Frame dropped: could not send frame"); return; } int error = 1; while (error >= 0) { - error = avcodec_receive_packet(codec_context.get(), &packet); + error = FFmpeg::avcodec_receive_packet(codec_context.get(), &packet); if (error == AVERROR(EAGAIN) || error == AVERROR_EOF) return; if (error < 0) { @@ -111,7 +104,7 @@ FFmpegVideoStream::~FFmpegVideoStream() { static AVPixelFormat GetPixelFormat(AVCodecContext* avctx, const AVPixelFormat* fmt) { // Choose a software pixel format if any, prefering those in the front of the list for (int i = 0; fmt[i] != AV_PIX_FMT_NONE; i++) { - const AVPixFmtDescriptor* desc = av_pix_fmt_desc_get(fmt[i]); + const AVPixFmtDescriptor* desc = FFmpeg::av_pix_fmt_desc_get(fmt[i]); if (!(desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) { return fmt[i]; } @@ -123,7 +116,7 @@ static AVPixelFormat GetPixelFormat(AVCodecContext* avctx, const AVPixelFormat* for (int i = 0; fmt[i] != AV_PIX_FMT_NONE; i++) { const AVCodecHWConfig* config; for (int j = 0;; j++) { - config = avcodec_get_hw_config(avctx->codec, j); + config = FFmpeg::avcodec_get_hw_config(avctx->codec, j); if (!config || config->pix_fmt == fmt[i]) { break; } @@ -144,18 +137,19 @@ static AVPixelFormat GetPixelFormat(AVCodecContext* avctx, const AVPixelFormat* } bool FFmpegVideoStream::Init(FFmpegMuxer& muxer, const Layout::FramebufferLayout& layout_) { - InitializeFFmpegLibraries(); - if (!FFmpegStream::Init(muxer)) + if (!FFmpegStream::Init(muxer)) { return false; + } layout = layout_; frame_count = 0; // Initialize video codec - const AVCodec* codec = avcodec_find_encoder_by_name(Settings::values.video_encoder.c_str()); - codec_context.reset(avcodec_alloc_context3(codec)); + const AVCodec* codec = + FFmpeg::avcodec_find_encoder_by_name(Settings::values.video_encoder.c_str()); + codec_context.reset(FFmpeg::avcodec_alloc_context3(codec)); if (!codec || !codec_context) { LOG_ERROR(Render, "Could not find video encoder or allocate video codec context"); return false; @@ -173,9 +167,9 @@ bool FFmpegVideoStream::Init(FFmpegMuxer& muxer, const Layout::FramebufferLayout // Get pixel format for codec auto options = ToAVDictionary(Settings::values.video_encoder_options); - auto pixel_format_opt = av_dict_get(options, "pixel_format", nullptr, 0); + auto pixel_format_opt = FFmpeg::av_dict_get(options, "pixel_format", nullptr, 0); if (pixel_format_opt) { - sw_pixel_format = av_get_pix_fmt(pixel_format_opt->value); + sw_pixel_format = FFmpeg::av_get_pix_fmt(pixel_format_opt->value); } else if (codec->pix_fmts) { sw_pixel_format = GetPixelFormat(codec_context.get(), codec->pix_fmts); } else { @@ -192,23 +186,25 @@ bool FFmpegVideoStream::Init(FFmpegMuxer& muxer, const Layout::FramebufferLayout codec_context->pix_fmt = sw_pixel_format; } - if (format_context->oformat->flags & AVFMT_GLOBALHEADER) + if (format_context->oformat->flags & AVFMT_GLOBALHEADER) { codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + } - if (avcodec_open2(codec_context.get(), codec, &options) < 0) { + if (FFmpeg::avcodec_open2(codec_context.get(), codec, &options) < 0) { LOG_ERROR(Render, "Could not open video codec"); return false; } - if (av_dict_count(options) != 0) { // Successfully set options are removed from the dict + if (FFmpeg::av_dict_count(options) != 0) { // Successfully set options are removed from the dict char* buf = nullptr; - av_dict_get_string(options, &buf, ':', ';'); + FFmpeg::av_dict_get_string(options, &buf, ':', ';'); LOG_WARNING(Render, "Video encoder options not found: {}", buf); } // Create video stream - stream = avformat_new_stream(format_context, codec); - if (!stream || avcodec_parameters_from_context(stream->codecpar, codec_context.get()) < 0) { + stream = FFmpeg::avformat_new_stream(format_context, codec); + if (!stream || + FFmpeg::avcodec_parameters_from_context(stream->codecpar, codec_context.get()) < 0) { LOG_ERROR(Render, "Could not create video stream"); return false; } @@ -216,12 +212,12 @@ bool FFmpegVideoStream::Init(FFmpegMuxer& muxer, const Layout::FramebufferLayout stream->time_base = codec_context->time_base; // Allocate frames - current_frame.reset(av_frame_alloc()); - filtered_frame.reset(av_frame_alloc()); + current_frame.reset(FFmpeg::av_frame_alloc()); + filtered_frame.reset(FFmpeg::av_frame_alloc()); if (requires_hw_frames) { - hw_frame.reset(av_frame_alloc()); - if (av_hwframe_get_buffer(codec_context->hw_frames_ctx, hw_frame.get(), 0) < 0) { + hw_frame.reset(FFmpeg::av_frame_alloc()); + if (FFmpeg::av_hwframe_get_buffer(codec_context->hw_frames_ctx, hw_frame.get(), 0) < 0) { LOG_ERROR(Render, "Could not allocate buffer for HW frame"); return false; } @@ -255,12 +251,12 @@ void FFmpegVideoStream::ProcessFrame(VideoFrame& frame) { current_frame->pts = frame_count++; // Filter the frame - if (av_buffersrc_add_frame(source_context, current_frame.get()) < 0) { + if (FFmpeg::av_buffersrc_add_frame(source_context, current_frame.get()) < 0) { LOG_ERROR(Render, "Video frame dropped: Could not add frame to filter graph"); return; } while (true) { - const int error = av_buffersink_get_frame(sink_context, filtered_frame.get()); + const int error = FFmpeg::av_buffersink_get_frame(sink_context, filtered_frame.get()); if (error == AVERROR(EAGAIN) || error == AVERROR_EOF) { return; } @@ -269,7 +265,7 @@ void FFmpegVideoStream::ProcessFrame(VideoFrame& frame) { return; } else { if (requires_hw_frames) { - if (av_hwframe_transfer_data(hw_frame.get(), filtered_frame.get(), 0) < 0) { + if (FFmpeg::av_hwframe_transfer_data(hw_frame.get(), filtered_frame.get(), 0) < 0) { LOG_ERROR(Render, "Video frame dropped: Could not upload to HW frame"); return; } @@ -278,7 +274,7 @@ void FFmpegVideoStream::ProcessFrame(VideoFrame& frame) { SendFrame(filtered_frame.get()); } - av_frame_unref(filtered_frame.get()); + FFmpeg::av_frame_unref(filtered_frame.get()); } } } @@ -287,7 +283,7 @@ bool FFmpegVideoStream::InitHWContext(const AVCodec* codec) { for (std::size_t i = 0; codec->pix_fmts[i] != AV_PIX_FMT_NONE; ++i) { const AVCodecHWConfig* config; for (int j = 0;; ++j) { - config = avcodec_get_hw_config(codec, j); + config = FFmpeg::avcodec_get_hw_config(codec, j); if (!config || config->pix_fmt == codec->pix_fmts[i]) { break; } @@ -306,22 +302,22 @@ bool FFmpegVideoStream::InitHWContext(const AVCodec* codec) { // Create HW device context AVBufferRef* hw_device_context; - SCOPE_EXIT({ av_buffer_unref(&hw_device_context); }); + SCOPE_EXIT({ FFmpeg::av_buffer_unref(&hw_device_context); }); // TODO: Provide the argument here somehow. // This is necessary for some devices like CUDA where you must supply the GPU name. // This is not necessary for VAAPI, etc. - if (av_hwdevice_ctx_create(&hw_device_context, config->device_type, nullptr, nullptr, 0) < - 0) { + if (FFmpeg::av_hwdevice_ctx_create(&hw_device_context, config->device_type, nullptr, + nullptr, 0) < 0) { LOG_ERROR(Render, "Failed to create HW device context"); continue; } - codec_context->hw_device_ctx = av_buffer_ref(hw_device_context); + codec_context->hw_device_ctx = FFmpeg::av_buffer_ref(hw_device_context); // Get the SW format AVHWFramesConstraints* constraints = - av_hwdevice_get_hwframe_constraints(hw_device_context, nullptr); - SCOPE_EXIT({ av_hwframe_constraints_free(&constraints); }); + FFmpeg::av_hwdevice_get_hwframe_constraints(hw_device_context, nullptr); + SCOPE_EXIT({ FFmpeg::av_hwframe_constraints_free(&constraints); }); if (constraints) { sw_pixel_format = constraints->valid_sw_formats ? constraints->valid_sw_formats[0] @@ -341,9 +337,9 @@ bool FFmpegVideoStream::InitHWContext(const AVCodec* codec) { // Create HW frames context AVBufferRef* hw_frames_context_ref; - SCOPE_EXIT({ av_buffer_unref(&hw_frames_context_ref); }); + SCOPE_EXIT({ FFmpeg::av_buffer_unref(&hw_frames_context_ref); }); - if (!(hw_frames_context_ref = av_hwframe_ctx_alloc(hw_device_context))) { + if (!(hw_frames_context_ref = FFmpeg::av_hwframe_ctx_alloc(hw_device_context))) { LOG_ERROR(Render, "Failed to create HW frames context"); continue; } @@ -356,12 +352,12 @@ bool FFmpegVideoStream::InitHWContext(const AVCodec* codec) { hw_frames_context->height = codec_context->height; hw_frames_context->initial_pool_size = 20; // value from FFmpeg's example - if (av_hwframe_ctx_init(hw_frames_context_ref) < 0) { + if (FFmpeg::av_hwframe_ctx_init(hw_frames_context_ref) < 0) { LOG_ERROR(Render, "Failed to initialize HW frames context"); continue; } - codec_context->hw_frames_ctx = av_buffer_ref(hw_frames_context_ref); + codec_context->hw_frames_ctx = FFmpeg::av_buffer_ref(hw_frames_context_ref); return true; } @@ -370,10 +366,10 @@ bool FFmpegVideoStream::InitHWContext(const AVCodec* codec) { } bool FFmpegVideoStream::InitFilters() { - filter_graph.reset(avfilter_graph_alloc()); + filter_graph.reset(FFmpeg::avfilter_graph_alloc()); - const AVFilter* source = avfilter_get_by_name("buffer"); - const AVFilter* sink = avfilter_get_by_name("buffersink"); + const AVFilter* source = FFmpeg::avfilter_get_by_name("buffer"); + const AVFilter* sink = FFmpeg::avfilter_get_by_name("buffersink"); if (!source || !sink) { LOG_ERROR(Render, "Could not find buffer source or sink"); return false; @@ -385,18 +381,23 @@ bool FFmpegVideoStream::InitFilters() { const std::string in_args = fmt::format("video_size={}x{}:pix_fmt={}:time_base={}/{}:pixel_aspect=1", layout.width, layout.height, pixel_format, src_time_base.num, src_time_base.den); - if (avfilter_graph_create_filter(&source_context, source, "in", in_args.c_str(), nullptr, - filter_graph.get()) < 0) { + if (FFmpeg::avfilter_graph_create_filter(&source_context, source, "in", in_args.c_str(), + nullptr, filter_graph.get()) < 0) { LOG_ERROR(Render, "Could not create buffer source"); return false; } // Configure buffer sink - if (avfilter_graph_create_filter(&sink_context, sink, "out", nullptr, nullptr, - filter_graph.get()) < 0) { + if (FFmpeg::avfilter_graph_create_filter(&sink_context, sink, "out", nullptr, nullptr, + filter_graph.get()) < 0) { LOG_ERROR(Render, "Could not create buffer sink"); return false; } + + // Point av_opt_set_int_list to correct functions. +#define av_int_list_length_for_size FFmpeg::av_int_list_length_for_size +#define av_opt_set_bin FFmpeg::av_opt_set_bin + const AVPixelFormat pix_fmts[] = {sw_pixel_format, AV_PIX_FMT_NONE}; if (av_opt_set_int_list(sink_context, "pix_fmts", pix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN) < 0) { @@ -406,30 +407,30 @@ bool FFmpegVideoStream::InitFilters() { // Initialize filter graph // `outputs` as in outputs of the 'previous' graphs - AVFilterInOut* outputs = avfilter_inout_alloc(); - outputs->name = av_strdup("in"); + AVFilterInOut* outputs = FFmpeg::avfilter_inout_alloc(); + outputs->name = FFmpeg::av_strdup("in"); outputs->filter_ctx = source_context; outputs->pad_idx = 0; outputs->next = nullptr; // `inputs` as in inputs to the 'next' graphs - AVFilterInOut* inputs = avfilter_inout_alloc(); - inputs->name = av_strdup("out"); + AVFilterInOut* inputs = FFmpeg::avfilter_inout_alloc(); + inputs->name = FFmpeg::av_strdup("out"); inputs->filter_ctx = sink_context; inputs->pad_idx = 0; inputs->next = nullptr; SCOPE_EXIT({ - avfilter_inout_free(&outputs); - avfilter_inout_free(&inputs); + FFmpeg::avfilter_inout_free(&outputs); + FFmpeg::avfilter_inout_free(&inputs); }); - if (avfilter_graph_parse_ptr(filter_graph.get(), filter_graph_desc.data(), &inputs, &outputs, - nullptr) < 0) { + if (FFmpeg::avfilter_graph_parse_ptr(filter_graph.get(), filter_graph_desc.data(), &inputs, + &outputs, nullptr) < 0) { LOG_ERROR(Render, "Could not parse or create filter graph"); return false; } - if (avfilter_graph_config(filter_graph.get(), nullptr) < 0) { + if (FFmpeg::avfilter_graph_config(filter_graph.get(), nullptr) < 0) { LOG_ERROR(Render, "Could not configure filter graph"); return false; } @@ -444,14 +445,16 @@ FFmpegAudioStream::~FFmpegAudioStream() { bool FFmpegAudioStream::Init(FFmpegMuxer& muxer) { InitializeFFmpegLibraries(); - if (!FFmpegStream::Init(muxer)) + if (!FFmpegStream::Init(muxer)) { return false; + } frame_count = 0; // Initialize audio codec - const AVCodec* codec = avcodec_find_encoder_by_name(Settings::values.audio_encoder.c_str()); - codec_context.reset(avcodec_alloc_context3(codec)); + const AVCodec* codec = + FFmpeg::avcodec_find_encoder_by_name(Settings::values.audio_encoder.c_str()); + codec_context.reset(FFmpeg::avcodec_alloc_context3(codec)); if (!codec || !codec_context) { LOG_ERROR(Render, "Could not find audio encoder or allocate audio codec context"); return false; @@ -482,20 +485,25 @@ bool FFmpegAudioStream::Init(FFmpegMuxer& muxer) { } codec_context->time_base.num = 1; codec_context->time_base.den = codec_context->sample_rate; +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100) + codec_context->ch_layout = AV_CHANNEL_LAYOUT_STEREO; +#else codec_context->channel_layout = AV_CH_LAYOUT_STEREO; codec_context->channels = 2; - if (format_context->oformat->flags & AVFMT_GLOBALHEADER) +#endif + if (format_context->oformat->flags & AVFMT_GLOBALHEADER) { codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + } AVDictionary* options = ToAVDictionary(Settings::values.audio_encoder_options); - if (avcodec_open2(codec_context.get(), codec, &options) < 0) { + if (FFmpeg::avcodec_open2(codec_context.get(), codec, &options) < 0) { LOG_ERROR(Render, "Could not open audio codec"); return false; } - if (av_dict_count(options) != 0) { // Successfully set options are removed from the dict + if (FFmpeg::av_dict_count(options) != 0) { // Successfully set options are removed from the dict char* buf = nullptr; - av_dict_get_string(options, &buf, ':', ';'); + FFmpeg::av_dict_get_string(options, &buf, ':', ';'); LOG_WARNING(Render, "Audio encoder options not found: {}", buf); } @@ -506,39 +514,49 @@ bool FFmpegAudioStream::Init(FFmpegMuxer& muxer) { } // Create audio stream - stream = avformat_new_stream(format_context, codec); - if (!stream || avcodec_parameters_from_context(stream->codecpar, codec_context.get()) < 0) { + stream = FFmpeg::avformat_new_stream(format_context, codec); + if (!stream || + FFmpeg::avcodec_parameters_from_context(stream->codecpar, codec_context.get()) < 0) { LOG_ERROR(Render, "Could not create audio stream"); return false; } // Allocate frame - audio_frame.reset(av_frame_alloc()); + audio_frame.reset(FFmpeg::av_frame_alloc()); audio_frame->format = codec_context->sample_fmt; - audio_frame->channel_layout = codec_context->channel_layout; - audio_frame->channels = codec_context->channels; audio_frame->sample_rate = codec_context->sample_rate; - // Allocate SWR context - auto* context = - swr_alloc_set_opts(nullptr, codec_context->channel_layout, codec_context->sample_fmt, - codec_context->sample_rate, codec_context->channel_layout, - AV_SAMPLE_FMT_S16P, AudioCore::native_sample_rate, 0, nullptr); +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100) + auto num_channels = codec_context->ch_layout.nb_channels; + audio_frame->ch_layout = codec_context->ch_layout; + SwrContext* context = nullptr; + FFmpeg::swr_alloc_set_opts2(&context, &codec_context->ch_layout, codec_context->sample_fmt, + codec_context->sample_rate, &codec_context->ch_layout, + AV_SAMPLE_FMT_S16P, AudioCore::native_sample_rate, 0, nullptr); +#else + auto num_channels = codec_context->channels; + audio_frame->channel_layout = codec_context->channel_layout; + audio_frame->channels = num_channels; + auto* context = FFmpeg::swr_alloc_set_opts( + nullptr, codec_context->channel_layout, codec_context->sample_fmt, + codec_context->sample_rate, codec_context->channel_layout, AV_SAMPLE_FMT_S16P, + AudioCore::native_sample_rate, 0, nullptr); +#endif + if (!context) { LOG_ERROR(Render, "Could not create SWR context"); return false; } swr_context.reset(context); - if (swr_init(swr_context.get()) < 0) { + if (FFmpeg::swr_init(swr_context.get()) < 0) { LOG_ERROR(Render, "Could not init SWR context"); return false; } // Allocate resampled data - int error = - av_samples_alloc_array_and_samples(&resampled_data, nullptr, codec_context->channels, - frame_size, codec_context->sample_fmt, 0); + int error = FFmpeg::av_samples_alloc_array_and_samples( + &resampled_data, nullptr, num_channels, frame_size, codec_context->sample_fmt, 0); if (error < 0) { LOG_ERROR(Render, "Could not allocate samples storage"); return false; @@ -554,9 +572,9 @@ void FFmpegAudioStream::Free() { swr_context.reset(); // Free resampled data if (resampled_data) { - av_freep(&resampled_data[0]); + FFmpeg::av_freep(&resampled_data[0]); } - av_freep(&resampled_data); + FFmpeg::av_freep(&resampled_data); } void FFmpegAudioStream::ProcessFrame(const VariableAudioFrame& channel0, @@ -564,20 +582,21 @@ void FFmpegAudioStream::ProcessFrame(const VariableAudioFrame& channel0, ASSERT_MSG(channel0.size() == channel1.size(), "Frames of the two channels must have the same number of samples"); - const auto sample_size = av_get_bytes_per_sample(codec_context->sample_fmt); + const auto sample_size = FFmpeg::av_get_bytes_per_sample(codec_context->sample_fmt); std::array src_data = {reinterpret_cast(channel0.data()), reinterpret_cast(channel1.data())}; std::array dst_data; - if (av_sample_fmt_is_planar(codec_context->sample_fmt)) { + if (FFmpeg::av_sample_fmt_is_planar(codec_context->sample_fmt)) { dst_data = {resampled_data[0] + sample_size * offset, resampled_data[1] + sample_size * offset}; } else { dst_data = {resampled_data[0] + sample_size * offset * 2}; // 2 channels } - auto resampled_count = swr_convert(swr_context.get(), dst_data.data(), frame_size - offset, - src_data.data(), static_cast(channel0.size())); + auto resampled_count = + FFmpeg::swr_convert(swr_context.get(), dst_data.data(), frame_size - offset, + src_data.data(), static_cast(channel0.size())); if (resampled_count < 0) { LOG_ERROR(Render, "Audio frame dropped: Could not resample data"); return; @@ -592,7 +611,7 @@ void FFmpegAudioStream::ProcessFrame(const VariableAudioFrame& channel0, // Prepare frame audio_frame->nb_samples = frame_size; audio_frame->data[0] = resampled_data[0]; - if (av_sample_fmt_is_planar(codec_context->sample_fmt)) { + if (FFmpeg::av_sample_fmt_is_planar(codec_context->sample_fmt)) { audio_frame->data[1] = resampled_data[1]; } audio_frame->pts = frame_count * frame_size; @@ -601,7 +620,8 @@ void FFmpegAudioStream::ProcessFrame(const VariableAudioFrame& channel0, SendFrame(audio_frame.get()); // swr_convert buffers input internally. Try to get more resampled data - resampled_count = swr_convert(swr_context.get(), resampled_data, frame_size, nullptr, 0); + resampled_count = + FFmpeg::swr_convert(swr_context.get(), resampled_data, frame_size, nullptr, 0); if (resampled_count < 0) { LOG_ERROR(Render, "Audio frame dropped: Could not resample data"); return; @@ -617,7 +637,7 @@ void FFmpegAudioStream::Flush() { // Send the last samples audio_frame->nb_samples = offset; audio_frame->data[0] = resampled_data[0]; - if (av_sample_fmt_is_planar(codec_context->sample_fmt)) { + if (FFmpeg::av_sample_fmt_is_planar(codec_context->sample_fmt)) { audio_frame->data[1] = resampled_data[1]; } audio_frame->pts = frame_count * frame_size; @@ -632,7 +652,6 @@ FFmpegMuxer::~FFmpegMuxer() { } bool FFmpegMuxer::Init(const std::string& path, const Layout::FramebufferLayout& layout) { - InitializeFFmpegLibraries(); if (!FileUtil::CreateFullPath(path)) { @@ -641,7 +660,7 @@ bool FFmpegMuxer::Init(const std::string& path, const Layout::FramebufferLayout& // Get output format const auto format = Settings::values.output_format; - auto* output_format = av_guess_format(format.c_str(), path.c_str(), nullptr); + auto* output_format = FFmpeg::av_guess_format(format.c_str(), path.c_str(), nullptr); if (!output_format) { LOG_ERROR(Render, "Could not get format {}", format); return false; @@ -649,9 +668,8 @@ bool FFmpegMuxer::Init(const std::string& path, const Layout::FramebufferLayout& // Initialize format context auto* format_context_raw = format_context.get(); - if (avformat_alloc_output_context2(&format_context_raw, output_format, nullptr, path.c_str()) < - 0) { - + if (FFmpeg::avformat_alloc_output_context2(&format_context_raw, output_format, nullptr, + path.c_str()) < 0) { LOG_ERROR(Render, "Could not allocate output context"); return false; } @@ -664,15 +682,15 @@ bool FFmpegMuxer::Init(const std::string& path, const Layout::FramebufferLayout& AVDictionary* options = ToAVDictionary(Settings::values.format_options); // Open video file - if (avio_open(&format_context->pb, path.c_str(), AVIO_FLAG_WRITE) < 0 || - avformat_write_header(format_context.get(), &options)) { + if (FFmpeg::avio_open(&format_context->pb, path.c_str(), AVIO_FLAG_WRITE) < 0 || + FFmpeg::avformat_write_header(format_context.get(), &options)) { LOG_ERROR(Render, "Could not open {}", path); return false; } - if (av_dict_count(options) != 0) { // Successfully set options are removed from the dict + if (FFmpeg::av_dict_count(options) != 0) { // Successfully set options are removed from the dict char* buf = nullptr; - av_dict_get_string(options, &buf, ':', ';'); + FFmpeg::av_dict_get_string(options, &buf, ':', ';'); LOG_WARNING(Render, "Format options not found: {}", buf); } @@ -705,8 +723,8 @@ void FFmpegMuxer::FlushAudio() { void FFmpegMuxer::WriteTrailer() { std::lock_guard lock{format_context_mutex}; - av_interleaved_write_frame(format_context.get(), nullptr); - av_write_trailer(format_context.get()); + FFmpeg::av_interleaved_write_frame(format_context.get(), nullptr); + FFmpeg::av_write_trailer(format_context.get()); } FFmpegBackend::FFmpegBackend() = default; @@ -722,7 +740,6 @@ FFmpegBackend::~FFmpegBackend() { } bool FFmpegBackend::StartDumping(const std::string& path, const Layout::FramebufferLayout& layout) { - InitializeFFmpegLibraries(); if (!ffmpeg.Init(path, layout)) { @@ -732,8 +749,9 @@ bool FFmpegBackend::StartDumping(const std::string& path, const Layout::Framebuf video_layout = layout; - if (video_processing_thread.joinable()) + if (video_processing_thread.joinable()) { video_processing_thread.join(); + } video_processing_thread = std::thread([&] { event1.Set(); while (true) { @@ -756,8 +774,9 @@ bool FFmpegBackend::StartDumping(const std::string& path, const Layout::Framebuf EndDumping(); }); - if (audio_processing_thread.joinable()) + if (audio_processing_thread.joinable()) { audio_processing_thread.join(); + } audio_processing_thread = std::thread([&] { VariableAudioFrame channel0, channel1; while (true) { @@ -912,16 +931,17 @@ std::string FormatDefaultValue(const AVOption* option, return fmt::format("{}", option->default_val.dbl); } case AV_OPT_TYPE_RATIONAL: { - const auto q = av_d2q(option->default_val.dbl, std::numeric_limits::max()); + const auto q = FFmpeg::av_d2q(option->default_val.dbl, std::numeric_limits::max()); return fmt::format("{}/{}", q.num, q.den); } case AV_OPT_TYPE_PIXEL_FMT: { - const char* name = av_get_pix_fmt_name(static_cast(option->default_val.i64)); + const char* name = + FFmpeg::av_get_pix_fmt_name(static_cast(option->default_val.i64)); return ToStdString(name, "none"); } case AV_OPT_TYPE_SAMPLE_FMT: { const char* name = - av_get_sample_fmt_name(static_cast(option->default_val.i64)); + FFmpeg::av_get_sample_fmt_name(static_cast(option->default_val.i64)); return ToStdString(name, "none"); } case AV_OPT_TYPE_COLOR: @@ -947,7 +967,7 @@ void GetOptionListSingle(std::vector& out, const AVClass* av_class) const AVOption* current = nullptr; std::unordered_map> named_constants_map; // First iteration: find and place all named constants - while ((current = av_opt_next(&av_class, current))) { + while ((current = FFmpeg::av_opt_next(&av_class, current))) { if (current->type != AV_OPT_TYPE_CONST || !current->unit) { continue; } @@ -956,7 +976,7 @@ void GetOptionListSingle(std::vector& out, const AVClass* av_class) } // Second iteration: find all options current = nullptr; - while ((current = av_opt_next(&av_class, current))) { + while ((current = FFmpeg::av_opt_next(&av_class, current))) { // Currently we cannot handle binary options if (current->type == AV_OPT_TYPE_CONST || current->type == AV_OPT_TYPE_BINARY) { continue; @@ -985,9 +1005,9 @@ void GetOptionList(std::vector& out, const AVClass* av_class, bool s const AVClass* child_class = nullptr; #if LIBAVCODEC_VERSION_MAJOR >= 59 void* iter = nullptr; - while ((child_class = av_opt_child_class_iterate(av_class, &iter))) { + while ((child_class = FFmpeg::av_opt_child_class_iterate(av_class, &iter))) { #else - while ((child_class = av_opt_child_class_next(av_class, child_class))) { + while ((child_class = FFmpeg::av_opt_child_class_next(av_class, child_class))) { #endif GetOptionListSingle(out, child_class); } @@ -1005,13 +1025,9 @@ std::vector ListEncoders(AVMediaType type) { std::vector out; const AVCodec* current = nullptr; -#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100) - while ((current = av_codec_next(current))) { -#else void* data = nullptr; // For libavcodec to save the iteration state - while ((current = av_codec_iterate(&data))) { -#endif - if (!av_codec_is_encoder(current) || current->type != type) { + while ((current = FFmpeg::av_codec_iterate(&data))) { + if (!FFmpeg::av_codec_is_encoder(current) || current->type != type) { continue; } out.push_back({current->name, ToStdString(current->long_name), current->id, @@ -1021,7 +1037,7 @@ std::vector ListEncoders(AVMediaType type) { } std::vector GetEncoderGenericOptions() { - return GetOptionList(avcodec_get_class(), false); + return GetOptionList(FFmpeg::avcodec_get_class(), false); } std::vector ListFormats() { @@ -1030,20 +1046,16 @@ std::vector ListFormats() { std::vector out; const AVOutputFormat* current = nullptr; -#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100) - while ((current = av_oformat_next(current))) { -#else void* data = nullptr; // For libavformat to save the iteration state - while ((current = av_muxer_iterate(&data))) { -#endif + while ((current = FFmpeg::av_muxer_iterate(&data))) { const auto extensions = Common::SplitString(ToStdString(current->extensions), ','); std::set supported_video_codecs; std::set supported_audio_codecs; // Go through all codecs const AVCodecDescriptor* codec = nullptr; - while ((codec = avcodec_descriptor_next(codec))) { - if (avformat_query_codec(current, codec->id, FF_COMPLIANCE_NORMAL) == 1) { + while ((codec = FFmpeg::avcodec_descriptor_next(codec))) { + if (FFmpeg::avformat_query_codec(current, codec->id, FF_COMPLIANCE_NORMAL) == 1) { if (codec->type == AVMEDIA_TYPE_VIDEO) { supported_video_codecs.emplace(codec->id); } else if (codec->type == AVMEDIA_TYPE_AUDIO) { @@ -1064,7 +1076,24 @@ std::vector ListFormats() { } std::vector GetFormatGenericOptions() { - return GetOptionList(avformat_get_class(), false); + return GetOptionList(FFmpeg::avformat_get_class(), false); +} + +std::vector GetPixelFormats() { + std::vector out; + const AVPixFmtDescriptor* current = nullptr; + while ((current = FFmpeg::av_pix_fmt_desc_next(current))) { + out.emplace_back(current->name); + } + return out; +} + +std::vector GetSampleFormats() { + std::vector out; + for (int current = AV_SAMPLE_FMT_U8; current < AV_SAMPLE_FMT_NB; current++) { + out.emplace_back(FFmpeg::av_get_sample_fmt_name(static_cast(current))); + } + return out; } } // namespace VideoDumper diff --git a/src/core/dumping/ffmpeg_backend.h b/src/core/dumping/ffmpeg_backend.h index f7ba53ebb..115731f67 100644 --- a/src/core/dumping/ffmpeg_backend.h +++ b/src/core/dumping/ffmpeg_backend.h @@ -13,24 +13,15 @@ #include #include #include "common/common_types.h" +#include "common/dynamic_library/ffmpeg.h" #include "common/thread.h" #include "common/threadsafe_queue.h" #include "core/dumping/backend.h" -extern "C" { -#include -#include -#include -#include -#include -} - namespace VideoDumper { using VariableAudioFrame = std::vector; -void InitFFmpegLibraries(); - class FFmpegMuxer; /** @@ -51,13 +42,13 @@ protected: struct AVCodecContextDeleter { void operator()(AVCodecContext* codec_context) const { - avcodec_free_context(&codec_context); + DynamicLibrary::FFmpeg::avcodec_free_context(&codec_context); } }; struct AVFrameDeleter { void operator()(AVFrame* frame) const { - av_frame_free(&frame); + DynamicLibrary::FFmpeg::av_frame_free(&frame); } }; @@ -103,7 +94,7 @@ private: // Filter related struct AVFilterGraphDeleter { void operator()(AVFilterGraph* filter_graph) const { - avfilter_graph_free(&filter_graph); + DynamicLibrary::FFmpeg::avfilter_graph_free(&filter_graph); } }; std::unique_ptr filter_graph{}; @@ -132,7 +123,7 @@ public: private: struct SwrContextDeleter { void operator()(SwrContext* swr_context) const { - swr_free(&swr_context); + DynamicLibrary::FFmpeg::swr_free(&swr_context); } }; @@ -165,8 +156,8 @@ public: private: struct AVFormatContextDeleter { void operator()(AVFormatContext* format_context) const { - avio_closep(&format_context->pb); - avformat_free_context(format_context); + DynamicLibrary::FFmpeg::avio_closep(&format_context->pb); + DynamicLibrary::FFmpeg::avformat_free_context(format_context); } }; @@ -253,5 +244,7 @@ std::vector ListEncoders(AVMediaType type); std::vector GetEncoderGenericOptions(); std::vector ListFormats(); std::vector GetFormatGenericOptions(); +std::vector GetPixelFormats(); +std::vector GetSampleFormats(); } // namespace VideoDumper diff --git a/src/video_core/renderer_opengl/frame_dumper_opengl.cpp b/src/video_core/renderer_opengl/frame_dumper_opengl.cpp index 3122de2f7..1d4ac8c32 100644 --- a/src/video_core/renderer_opengl/frame_dumper_opengl.cpp +++ b/src/video_core/renderer_opengl/frame_dumper_opengl.cpp @@ -3,32 +3,37 @@ // Refer to the license.txt file included. #include + +#include #include "core/frontend/emu_window.h" #include "video_core/renderer_opengl/frame_dumper_opengl.h" #include "video_core/renderer_opengl/renderer_opengl.h" namespace OpenGL { -FrameDumperOpenGL::FrameDumperOpenGL(VideoDumper::Backend& video_dumper_, - Frontend::EmuWindow& emu_window) - : video_dumper(video_dumper_), context(emu_window.CreateSharedContext()) {} +FrameDumperOpenGL::FrameDumperOpenGL(Core::System& system_, Frontend::EmuWindow& emu_window) + : system(system_), context(emu_window.CreateSharedContext()) {} FrameDumperOpenGL::~FrameDumperOpenGL() { - if (present_thread.joinable()) + if (present_thread.joinable()) { present_thread.join(); + } } bool FrameDumperOpenGL::IsDumping() const { - return video_dumper.IsDumping(); + auto video_dumper = system.GetVideoDumper(); + return video_dumper && video_dumper->IsDumping(); } Layout::FramebufferLayout FrameDumperOpenGL::GetLayout() const { - return video_dumper.GetLayout(); + auto video_dumper = system.GetVideoDumper(); + return video_dumper ? video_dumper->GetLayout() : Layout::FramebufferLayout{}; } void FrameDumperOpenGL::StartDumping() { - if (present_thread.joinable()) + if (present_thread.joinable()) { present_thread.join(); + } present_thread = std::thread(&FrameDumperOpenGL::PresentLoop, this); } @@ -62,13 +67,17 @@ void FrameDumperOpenGL::PresentLoop() { frame->present_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); glFlush(); - // Bind the previous PBO and read the pixels - glBindBuffer(GL_PIXEL_PACK_BUFFER, pbos[next_pbo].handle); - GLubyte* pixels = static_cast(glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY)); - VideoDumper::VideoFrame frame_data{layout.width, layout.height, pixels}; - video_dumper.AddVideoFrame(std::move(frame_data)); - glUnmapBuffer(GL_PIXEL_PACK_BUFFER); - glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + auto video_dumper = system.GetVideoDumper(); + if (video_dumper) { + // Bind the previous PBO and read the pixels + glBindBuffer(GL_PIXEL_PACK_BUFFER, pbos[next_pbo].handle); + GLubyte* pixels = + static_cast(glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY)); + VideoDumper::VideoFrame frame_data{layout.width, layout.height, pixels}; + video_dumper->AddVideoFrame(std::move(frame_data)); + glUnmapBuffer(GL_PIXEL_PACK_BUFFER); + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + } current_pbo = (current_pbo + 1) % 2; next_pbo = (current_pbo + 1) % 2; diff --git a/src/video_core/renderer_opengl/frame_dumper_opengl.h b/src/video_core/renderer_opengl/frame_dumper_opengl.h index da6d96053..bcd498094 100644 --- a/src/video_core/renderer_opengl/frame_dumper_opengl.h +++ b/src/video_core/renderer_opengl/frame_dumper_opengl.h @@ -7,6 +7,7 @@ #include #include #include +#include "core/core.h" #include "core/dumping/backend.h" #include "core/frontend/framebuffer_layout.h" #include "video_core/renderer_opengl/gl_resource_manager.h" @@ -28,7 +29,7 @@ class RendererOpenGL; */ class FrameDumperOpenGL { public: - explicit FrameDumperOpenGL(VideoDumper::Backend& video_dumper, Frontend::EmuWindow& emu_window); + explicit FrameDumperOpenGL(Core::System& system, Frontend::EmuWindow& emu_window); ~FrameDumperOpenGL(); bool IsDumping() const; @@ -43,7 +44,7 @@ private: void CleanupOpenGLObjects(); void PresentLoop(); - VideoDumper::Backend& video_dumper; + Core::System& system; std::unique_ptr context; std::thread present_thread; std::atomic_bool stop_requested{false}; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 89ac5fc49..bc897d73f 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -306,7 +306,7 @@ static std::array MakeOrthographicMatrix(const float width, cons RendererOpenGL::RendererOpenGL(Core::System& system, Frontend::EmuWindow& window, Frontend::EmuWindow* secondary_window) : VideoCore::RendererBase{system, window, secondary_window}, driver{system.TelemetrySession()}, - frame_dumper{system.VideoDumper(), window} { + frame_dumper{system, window} { const bool has_debug_tool = driver.HasDebugTool(); window.mailbox = std::make_unique(has_debug_tool); if (secondary_window) {