MediaCapture/FFmpeg: Tidy up format selection

And remove use of deprecated fields.
This commit is contained in:
Stenzek 2025-05-17 17:45:49 +10:00
parent e439ebc1c6
commit 22109fc8e0
No known key found for this signature in database
2 changed files with 63 additions and 74 deletions

View File

@ -65,7 +65,7 @@ if(NOT WIN32 AND NOT APPLE)
endif() endif()
if(NOT ANDROID AND NOT WIN32) if(NOT ANDROID AND NOT WIN32)
find_package(FFMPEG COMPONENTS avcodec avformat avutil swresample swscale) find_package(FFMPEG 7.1.0 COMPONENTS avcodec avformat avutil swresample swscale)
if(NOT FFMPEG_FOUND) if(NOT FFMPEG_FOUND)
message(WARNING "FFmpeg not found, using bundled headers.") message(WARNING "FFmpeg not found, using bundled headers.")
endif() endif()

View File

@ -56,6 +56,7 @@ extern "C" {
#include "libavformat/version.h" #include "libavformat/version.h"
#include "libavutil/dict.h" #include "libavutil/dict.h"
#include "libavutil/opt.h" #include "libavutil/opt.h"
#include "libavutil/pixdesc.h"
#include "libavutil/version.h" #include "libavutil/version.h"
#include "libswresample/swresample.h" #include "libswresample/swresample.h"
#include "libswresample/version.h" #include "libswresample/version.h"
@ -1796,22 +1797,6 @@ bool MediaCaptureMF::ProcessAudioPackets(s64 video_pts, Error* error)
#ifndef __ANDROID__ #ifndef __ANDROID__
// We're using deprecated fields because we're targeting multiple ffmpeg versions.
#if defined(_MSC_VER)
#pragma warning(disable : 4996) // warning C4996: 'AVCodecContext::channels': was declared deprecated
#elif defined(__clang__)
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#elif defined(__GNUC__)
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
// Compatibility with both ffmpeg 4.x and 5.x.
#if (LIBAVFORMAT_VERSION_MAJOR < 59)
#define ff_const59
#else
#define ff_const59 const
#endif
#define VISIT_AVCODEC_IMPORTS(X) \ #define VISIT_AVCODEC_IMPORTS(X) \
X(avcodec_find_encoder_by_name) \ X(avcodec_find_encoder_by_name) \
X(avcodec_find_encoder) \ X(avcodec_find_encoder) \
@ -1822,6 +1807,7 @@ bool MediaCaptureMF::ProcessAudioPackets(s64 video_pts, Error* error)
X(avcodec_receive_packet) \ X(avcodec_receive_packet) \
X(avcodec_parameters_from_context) \ X(avcodec_parameters_from_context) \
X(avcodec_get_hw_config) \ X(avcodec_get_hw_config) \
X(avcodec_get_supported_config) \
X(av_codec_iterate) \ X(av_codec_iterate) \
X(av_packet_alloc) \ X(av_packet_alloc) \
X(av_packet_free) \ X(av_packet_free) \
@ -1840,17 +1826,10 @@ bool MediaCaptureMF::ProcessAudioPackets(s64 video_pts, Error* error)
X(avio_open) \ X(avio_open) \
X(avio_closep) X(avio_closep)
#if LIBAVUTIL_VERSION_MAJOR < 57 #define VISIT_AVUTIL_IMPORTS(X) \
#define AVUTIL_57_IMPORTS(X)
#else
#define AVUTIL_57_IMPORTS(X) \
X(av_channel_layout_default) \ X(av_channel_layout_default) \
X(av_channel_layout_copy) \ X(av_channel_layout_copy) \
X(av_opt_set_chlayout) X(av_opt_set_chlayout) \
#endif
#define VISIT_AVUTIL_IMPORTS(X) \
AVUTIL_57_IMPORTS(X) \
X(av_frame_alloc) \ X(av_frame_alloc) \
X(av_frame_get_buffer) \ X(av_frame_get_buffer) \
X(av_frame_free) \ X(av_frame_free) \
@ -1863,6 +1842,7 @@ bool MediaCaptureMF::ProcessAudioPackets(s64 video_pts, Error* error)
X(av_opt_set_int) \ X(av_opt_set_int) \
X(av_opt_set_sample_fmt) \ X(av_opt_set_sample_fmt) \
X(av_compare_ts) \ X(av_compare_ts) \
X(av_get_pix_fmt) \
X(av_get_bytes_per_sample) \ X(av_get_bytes_per_sample) \
X(av_sample_fmt_is_planar) \ X(av_sample_fmt_is_planar) \
X(av_d2q) \ X(av_d2q) \
@ -2096,7 +2076,7 @@ bool MediaCaptureFFmpeg::InternalBeginCapture(float fps, float aspect, u32 sampl
std::string_view audio_codec, u32 audio_bitrate, std::string_view audio_codec, u32 audio_bitrate,
std::string_view audio_codec_args, Error* error) std::string_view audio_codec_args, Error* error)
{ {
ff_const59 AVOutputFormat* output_format = wrap_av_guess_format(nullptr, m_path.c_str(), nullptr); const AVOutputFormat* output_format = wrap_av_guess_format(nullptr, m_path.c_str(), nullptr);
if (!output_format) if (!output_format)
{ {
Error::SetStringFmt(error, "Failed to get output format for '{}'", Path::GetFileName(m_path)); Error::SetStringFmt(error, "Failed to get output format for '{}'", Path::GetFileName(m_path));
@ -2151,6 +2131,8 @@ bool MediaCaptureFFmpeg::InternalBeginCapture(float fps, float aspect, u32 sampl
m_video_codec_context->sample_aspect_ratio = wrap_av_d2q(aspect, 100000); m_video_codec_context->sample_aspect_ratio = wrap_av_d2q(aspect, 100000);
wrap_av_reduce(&m_video_codec_context->time_base.num, &m_video_codec_context->time_base.den, 10000, wrap_av_reduce(&m_video_codec_context->time_base.num, &m_video_codec_context->time_base.den, 10000,
static_cast<s64>(static_cast<double>(fps) * 10000.0), std::numeric_limits<s32>::max()); static_cast<s64>(static_cast<double>(fps) * 10000.0), std::numeric_limits<s32>::max());
wrap_av_reduce(&m_video_codec_context->framerate.num, &m_video_codec_context->framerate.den,
static_cast<s64>(static_cast<double>(fps) * 10000.0), 10000, std::numeric_limits<s32>::max());
// Map input pixel format. // Map input pixel format.
static constexpr const std::pair<GPUTexture::Format, AVPixelFormat> texture_pf_mapping[] = { static constexpr const std::pair<GPUTexture::Format, AVPixelFormat> texture_pf_mapping[] = {
@ -2171,43 +2153,35 @@ bool MediaCaptureFFmpeg::InternalBeginCapture(float fps, float aspect, u32 sampl
return false; return false;
} }
// Default to YUV 4:2:0 if the codec doesn't specify a pixel format. if (!video_codec_args.empty())
AVPixelFormat sw_pix_fmt = AV_PIX_FMT_YUV420P;
if (vcodec->pix_fmts)
{ {
// Prefer YUV420 given the choice, but otherwise fall back to whatever it supports. res = wrap_av_dict_parse_string(&m_video_codec_arguments, SmallString(video_codec_args).c_str(), "=", ":", 0);
sw_pix_fmt = vcodec->pix_fmts[0]; if (res < 0)
for (u32 i = 0; vcodec->pix_fmts[i] != AV_PIX_FMT_NONE; i++)
{ {
if (vcodec->pix_fmts[i] == AV_PIX_FMT_YUV420P) SetAVError(error, "av_dict_parse_string() for video failed: ", res);
{ return false;
sw_pix_fmt = vcodec->pix_fmts[i];
break;
}
} }
} }
m_video_codec_context->pix_fmt = sw_pix_fmt;
// Select output pixel format.
AVPixelFormat request_pix_fmt = AV_PIX_FMT_YUV420P;
if (const AVDictionaryEntry* de = wrap_av_dict_get(m_video_codec_arguments, "pixel_format", nullptr, 0))
{
const AVPixelFormat de_fmt = wrap_av_get_pix_fmt(de->value);
request_pix_fmt = (de_fmt != AV_PIX_FMT_NONE) ? de_fmt : request_pix_fmt;
if (de_fmt == AV_PIX_FMT_NONE)
WARNING_LOG("Invalid pixel format override: {}", de->value);
}
// Can we use hardware encoding? // Can we use hardware encoding?
const AVCodecHWConfig* hwconfig = wrap_avcodec_get_hw_config(vcodec, 0); const AVCodecHWConfig* hwconfig = wrap_avcodec_get_hw_config(vcodec, 0);
if (hwconfig && hwconfig->pix_fmt != AV_PIX_FMT_NONE && hwconfig->pix_fmt != sw_pix_fmt) AVPixelFormat sw_pix_fmt;
{
// First index isn't our preferred pixel format, try the others, but fall back if one doesn't exist.
int index = 1;
while (const AVCodecHWConfig* next_hwconfig = wrap_avcodec_get_hw_config(vcodec, index++))
{
if (next_hwconfig->pix_fmt == sw_pix_fmt)
{
hwconfig = next_hwconfig;
break;
}
}
}
if (hwconfig) if (hwconfig)
{ {
Error hw_error; // Can't do this test for hardware codecs, because they don't list the software formats as inputs.
sw_pix_fmt = request_pix_fmt;
Error hw_error;
INFO_LOG("Trying to use {} hardware device for video encoding.", INFO_LOG("Trying to use {} hardware device for video encoding.",
wrap_av_hwdevice_get_type_name(hwconfig->device_type)); wrap_av_hwdevice_get_type_name(hwconfig->device_type));
res = wrap_av_hwdevice_ctx_create(&m_video_hw_context, hwconfig->device_type, nullptr, nullptr, 0); res = wrap_av_hwdevice_ctx_create(&m_video_hw_context, hwconfig->device_type, nullptr, nullptr, 0);
@ -2255,21 +2229,37 @@ bool MediaCaptureFFmpeg::InternalBeginCapture(float fps, float aspect, u32 sampl
} }
} }
if (!video_codec_args.empty()) if (!hwconfig)
{ {
res = wrap_av_dict_parse_string(&m_video_codec_arguments, SmallString(video_codec_args).c_str(), "=", ":", 0); // Default to YUV 4:2:0 if the codec doesn't specify a pixel format.
const AVPixelFormat* supported_pixel_formats = nullptr;
int num_supported_pixel_formats = 0;
res = wrap_avcodec_get_supported_config(m_video_codec_context, vcodec, AV_CODEC_CONFIG_PIX_FORMAT, 0,
reinterpret_cast<const void**>(&supported_pixel_formats),
&num_supported_pixel_formats);
if (res < 0) if (res < 0)
{ {
SetAVError(error, "av_dict_parse_string() for video failed: ", res); SetAVError(error, "avcodec_get_supported_config() failed: ", res);
return false; return false;
} }
// Prefer YUV420 given the choice, but otherwise fall back to whatever it supports.
sw_pix_fmt = supported_pixel_formats[0];
for (int i = 0; i < num_supported_pixel_formats; i++)
{
if (supported_pixel_formats[i] == request_pix_fmt)
{
sw_pix_fmt = supported_pixel_formats[i];
break;
}
}
m_video_codec_context->pix_fmt = sw_pix_fmt;
} }
if (output_format->flags & AVFMT_GLOBALHEADER) if (output_format->flags & AVFMT_GLOBALHEADER)
m_video_codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; m_video_codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
bool has_pixel_format_override = wrap_av_dict_get(m_video_codec_arguments, "pixel_format", nullptr, 0);
res = wrap_avcodec_open2(m_video_codec_context, vcodec, &m_video_codec_arguments); res = wrap_avcodec_open2(m_video_codec_context, vcodec, &m_video_codec_arguments);
if (res < 0) if (res < 0)
{ {
@ -2277,10 +2267,6 @@ bool MediaCaptureFFmpeg::InternalBeginCapture(float fps, float aspect, u32 sampl
return false; return false;
} }
// If the user overrode the pixel format, get that now
if (has_pixel_format_override)
sw_pix_fmt = m_video_codec_context->pix_fmt;
m_converted_video_frame = wrap_av_frame_alloc(); m_converted_video_frame = wrap_av_frame_alloc();
m_hw_video_frame = IsUsingHardwareVideoEncoding() ? wrap_av_frame_alloc() : nullptr; m_hw_video_frame = IsUsingHardwareVideoEncoding() ? wrap_av_frame_alloc() : nullptr;
if (!m_converted_video_frame || (IsUsingHardwareVideoEncoding() && !m_hw_video_frame)) if (!m_converted_video_frame || (IsUsingHardwareVideoEncoding() && !m_hw_video_frame))
@ -2369,17 +2355,23 @@ bool MediaCaptureFFmpeg::InternalBeginCapture(float fps, float aspect, u32 sampl
m_audio_codec_context->sample_fmt = AV_SAMPLE_FMT_S16; m_audio_codec_context->sample_fmt = AV_SAMPLE_FMT_S16;
m_audio_codec_context->sample_rate = sample_rate; m_audio_codec_context->sample_rate = sample_rate;
m_audio_codec_context->time_base = {1, static_cast<int>(sample_rate)}; m_audio_codec_context->time_base = {1, static_cast<int>(sample_rate)};
#if LIBAVUTIL_VERSION_MAJOR < 57
m_audio_codec_context->channels = AUDIO_CHANNELS;
m_audio_codec_context->channel_layout = AV_CH_LAYOUT_STEREO;
#else
wrap_av_channel_layout_default(&m_audio_codec_context->ch_layout, AUDIO_CHANNELS); wrap_av_channel_layout_default(&m_audio_codec_context->ch_layout, AUDIO_CHANNELS);
#endif
bool supports_format = false; bool supports_format = false;
for (const AVSampleFormat* p = acodec->sample_fmts; *p != AV_SAMPLE_FMT_NONE; p++) const AVSampleFormat* supported_sample_formats = nullptr;
int num_supported_sample_formats = 0;
res = wrap_avcodec_get_supported_config(m_video_codec_context, acodec, AV_CODEC_CONFIG_SAMPLE_FORMAT, 0,
reinterpret_cast<const void**>(&supported_sample_formats),
&num_supported_sample_formats);
if (res < 0)
{ {
if (*p == m_audio_codec_context->sample_fmt) SetAVError(error, "avcodec_get_supported_config() for audio failed: ", res);
return false;
}
for (int i = 0; i < num_supported_sample_formats; i++)
{
if (supported_sample_formats[i] == m_audio_codec_context->sample_fmt)
{ {
supports_format = true; supports_format = true;
break; break;
@ -2388,7 +2380,7 @@ bool MediaCaptureFFmpeg::InternalBeginCapture(float fps, float aspect, u32 sampl
if (!supports_format) if (!supports_format)
{ {
WARNING_LOG("Audio codec '{}' does not support S16 samples, using default.", acodec->name); WARNING_LOG("Audio codec '{}' does not support S16 samples, using default.", acodec->name);
m_audio_codec_context->sample_fmt = acodec->sample_fmts[0]; m_audio_codec_context->sample_fmt = supported_sample_formats[0];
m_swr_context = wrap_swr_alloc(); m_swr_context = wrap_swr_alloc();
if (!m_swr_context) if (!m_swr_context)
{ {
@ -2402,11 +2394,8 @@ bool MediaCaptureFFmpeg::InternalBeginCapture(float fps, float aspect, u32 sampl
wrap_av_opt_set_int(m_swr_context, "out_channel_count", AUDIO_CHANNELS, 0); wrap_av_opt_set_int(m_swr_context, "out_channel_count", AUDIO_CHANNELS, 0);
wrap_av_opt_set_int(m_swr_context, "out_sample_rate", sample_rate, 0); wrap_av_opt_set_int(m_swr_context, "out_sample_rate", sample_rate, 0);
wrap_av_opt_set_sample_fmt(m_swr_context, "out_sample_fmt", m_audio_codec_context->sample_fmt, 0); wrap_av_opt_set_sample_fmt(m_swr_context, "out_sample_fmt", m_audio_codec_context->sample_fmt, 0);
#if LIBAVUTIL_VERSION_MAJOR >= 59
wrap_av_opt_set_chlayout(m_swr_context, "in_chlayout", &m_audio_codec_context->ch_layout, 0); wrap_av_opt_set_chlayout(m_swr_context, "in_chlayout", &m_audio_codec_context->ch_layout, 0);
wrap_av_opt_set_chlayout(m_swr_context, "out_chlayout", &m_audio_codec_context->ch_layout, 0); wrap_av_opt_set_chlayout(m_swr_context, "out_chlayout", &m_audio_codec_context->ch_layout, 0);
#endif
res = wrap_swr_init(m_swr_context); res = wrap_swr_init(m_swr_context);
if (res < 0) if (res < 0)