mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	Dynamically load FFmpeg and libfdk-aac if available. (#6570)
This commit is contained in:
		
							parent
							
								
									d807cdfe62
								
							
						
					
					
						commit
						38435e9b3e
					
				
					 38 changed files with 1311 additions and 877 deletions
				
			
		|  | @ -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() | ||||
|  |  | |||
|  | @ -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<VideoDumper::FFmpegBackend>(); | ||||
| #else | ||||
|     video_dumper = std::make_unique<VideoDumper::NullBackend>(); | ||||
| #endif | ||||
| 
 | ||||
|     if (!registered_image_interface) { | ||||
|         registered_image_interface = std::make_shared<Frontend::ImageInterface>(); | ||||
|     } | ||||
|  | @ -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<VideoDumper::Backend> dumper) { | ||||
|     video_dumper = std::move(dumper); | ||||
| } | ||||
| 
 | ||||
| VideoCore::CustomTexManager& System::CustomTexManager() { | ||||
|  |  | |||
|  | @ -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<VideoDumper::Backend> video_dumper); | ||||
| 
 | ||||
|     [[nodiscard]] std::shared_ptr<VideoDumper::Backend> GetVideoDumper() const { | ||||
|         return video_dumper; | ||||
|     } | ||||
| 
 | ||||
|     std::unique_ptr<PerfStats> perf_stats; | ||||
|     FrameLimiter frame_limiter; | ||||
|  | @ -370,7 +372,7 @@ private: | |||
|     std::unique_ptr<Cheats::CheatEngine> cheat_engine; | ||||
| 
 | ||||
|     /// Video dumper backend
 | ||||
|     std::unique_ptr<VideoDumper::Backend> video_dumper; | ||||
|     std::shared_ptr<VideoDumper::Backend> video_dumper; | ||||
| 
 | ||||
|     /// Custom texture cache system
 | ||||
|     std::unique_ptr<VideoCore::CustomTexManager> custom_tex_manager; | ||||
|  |  | |||
|  | @ -15,24 +15,17 @@ | |||
| #include "video_core/renderer_base.h" | ||||
| #include "video_core/video_core.h" | ||||
| 
 | ||||
| extern "C" { | ||||
| #include <libavfilter/buffersink.h> | ||||
| #include <libavfilter/buffersrc.h> | ||||
| #include <libavutil/hwcontext.h> | ||||
| #include <libavutil/pixdesc.h> | ||||
| } | ||||
| 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<const u8*, 2> src_data = {reinterpret_cast<const u8*>(channel0.data()), | ||||
|                                          reinterpret_cast<const u8*>(channel1.data())}; | ||||
| 
 | ||||
|     std::array<u8*, 2> 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<int>(channel0.size())); | ||||
|     auto resampled_count = | ||||
|         FFmpeg::swr_convert(swr_context.get(), dst_data.data(), frame_size - offset, | ||||
|                             src_data.data(), static_cast<int>(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<int>::max()); | ||||
|         const auto q = FFmpeg::av_d2q(option->default_val.dbl, std::numeric_limits<int>::max()); | ||||
|         return fmt::format("{}/{}", q.num, q.den); | ||||
|     } | ||||
|     case AV_OPT_TYPE_PIXEL_FMT: { | ||||
|         const char* name = av_get_pix_fmt_name(static_cast<AVPixelFormat>(option->default_val.i64)); | ||||
|         const char* name = | ||||
|             FFmpeg::av_get_pix_fmt_name(static_cast<AVPixelFormat>(option->default_val.i64)); | ||||
|         return ToStdString(name, "none"); | ||||
|     } | ||||
|     case AV_OPT_TYPE_SAMPLE_FMT: { | ||||
|         const char* name = | ||||
|             av_get_sample_fmt_name(static_cast<AVSampleFormat>(option->default_val.i64)); | ||||
|             FFmpeg::av_get_sample_fmt_name(static_cast<AVSampleFormat>(option->default_val.i64)); | ||||
|         return ToStdString(name, "none"); | ||||
|     } | ||||
|     case AV_OPT_TYPE_COLOR: | ||||
|  | @ -947,7 +967,7 @@ void GetOptionListSingle(std::vector<OptionInfo>& out, const AVClass* av_class) | |||
|     const AVOption* current = nullptr; | ||||
|     std::unordered_map<std::string, std::vector<OptionInfo::NamedConstant>> 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<OptionInfo>& 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<OptionInfo>& 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<EncoderInfo> ListEncoders(AVMediaType type) { | |||
|     std::vector<EncoderInfo> 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<EncoderInfo> ListEncoders(AVMediaType type) { | |||
| } | ||||
| 
 | ||||
| std::vector<OptionInfo> GetEncoderGenericOptions() { | ||||
|     return GetOptionList(avcodec_get_class(), false); | ||||
|     return GetOptionList(FFmpeg::avcodec_get_class(), false); | ||||
| } | ||||
| 
 | ||||
| std::vector<FormatInfo> ListFormats() { | ||||
|  | @ -1030,20 +1046,16 @@ std::vector<FormatInfo> ListFormats() { | |||
|     std::vector<FormatInfo> 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<AVCodecID> supported_video_codecs; | ||||
|         std::set<AVCodecID> 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<FormatInfo> ListFormats() { | |||
| } | ||||
| 
 | ||||
| std::vector<OptionInfo> GetFormatGenericOptions() { | ||||
|     return GetOptionList(avformat_get_class(), false); | ||||
|     return GetOptionList(FFmpeg::avformat_get_class(), false); | ||||
| } | ||||
| 
 | ||||
| std::vector<std::string> GetPixelFormats() { | ||||
|     std::vector<std::string> out; | ||||
|     const AVPixFmtDescriptor* current = nullptr; | ||||
|     while ((current = FFmpeg::av_pix_fmt_desc_next(current))) { | ||||
|         out.emplace_back(current->name); | ||||
|     } | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| std::vector<std::string> GetSampleFormats() { | ||||
|     std::vector<std::string> 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<AVSampleFormat>(current))); | ||||
|     } | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| } // namespace VideoDumper
 | ||||
|  |  | |||
|  | @ -13,24 +13,15 @@ | |||
| #include <thread> | ||||
| #include <vector> | ||||
| #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 <libavcodec/avcodec.h> | ||||
| #include <libavfilter/avfilter.h> | ||||
| #include <libavformat/avformat.h> | ||||
| #include <libavutil/opt.h> | ||||
| #include <libswresample/swresample.h> | ||||
| } | ||||
| 
 | ||||
| namespace VideoDumper { | ||||
| 
 | ||||
| using VariableAudioFrame = std::vector<s16>; | ||||
| 
 | ||||
| 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<AVFilterGraph, AVFilterGraphDeleter> 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<EncoderInfo> ListEncoders(AVMediaType type); | |||
| std::vector<OptionInfo> GetEncoderGenericOptions(); | ||||
| std::vector<FormatInfo> ListFormats(); | ||||
| std::vector<OptionInfo> GetFormatGenericOptions(); | ||||
| std::vector<std::string> GetPixelFormats(); | ||||
| std::vector<std::string> GetSampleFormats(); | ||||
| 
 | ||||
| } // namespace VideoDumper
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue