Dynamically load FFmpeg and libfdk-aac if available. (#6570)

This commit is contained in:
Steveice10 2023-06-16 16:06:18 -07:00 committed by GitHub
parent d807cdfe62
commit 38435e9b3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 1311 additions and 877 deletions

View file

@ -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()

View file

@ -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() {

View file

@ -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;

View file

@ -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

View file

@ -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