diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp index 575632f12b..14c38ddba0 100644 --- a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp @@ -397,6 +397,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* &GraphicsSettingsWidget::onEnableAudioCaptureArgumentsChanged); onCaptureContainerChanged(); + onCaptureCodecChanged(); onEnableVideoCaptureChanged(); onEnableVideoCaptureArgumentsChanged(); onVideoCaptureAutoResolutionChanged(); @@ -735,6 +736,10 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* "If unsure, leave it on default.")); + dialog->registerWidgetHelp(m_ui.videoCaptureFormat, tr("Video Format"), tr("Default"), tr("Selects which Video Format to be used for Video Capture. If by chance the codec does not support the format, the first format available will be used. " + + "If unsure, leave it on default.")); + dialog->registerWidgetHelp(m_ui.videoCaptureBitrate, tr("Video Bitrate"), tr("6000 kbps"), tr("Sets the video bitrate to be used. " "Larger bitrate generally yields better video quality at the cost of larger resulting file size.")); @@ -914,6 +919,7 @@ void GraphicsSettingsWidget::onCaptureContainerChanged() SettingWidgetBinder::BindWidgetToStringSetting( m_dialog->getSettingsInterface(), m_ui.videoCaptureCodec, "EmuCore/GS", "VideoCaptureCodec"); + connect(m_ui.videoCaptureCodec, &QComboBox::currentIndexChanged, this, &GraphicsSettingsWidget::onCaptureCodecChanged); m_ui.audioCaptureCodec->disconnect(); m_ui.audioCaptureCodec->clear(); @@ -929,6 +935,30 @@ void GraphicsSettingsWidget::onCaptureContainerChanged() m_dialog->getSettingsInterface(), m_ui.audioCaptureCodec, "EmuCore/GS", "AudioCaptureCodec"); } +void GraphicsSettingsWidget::GraphicsSettingsWidget::onCaptureCodecChanged() +{ + m_ui.videoCaptureFormat->disconnect(); + m_ui.videoCaptureFormat->clear(); + //: This string refers to a default pixel format + m_ui.videoCaptureFormat->addItem(tr("Default"), ""); + + const std::string codec( + m_dialog->getEffectiveStringValue("EmuCore/GS", "VideoCaptureCodec", "")); + + if (!codec.empty()) + { + for (const auto& [id, name] : GSCapture::GetVideoFormatList(codec.c_str())) + { + const QString qid(QString::number(id)); + const QString qname(QString::fromStdString(name)); + m_ui.videoCaptureFormat->addItem(qname, qid); + } + } + + SettingWidgetBinder::BindWidgetToStringSetting( + m_dialog->getSettingsInterface(), m_ui.videoCaptureFormat, "EmuCore/GS", "VideoCaptureFormat"); +} + void GraphicsSettingsWidget::onEnableVideoCaptureChanged() { const bool enabled = m_dialog->getEffectiveBoolValue("EmuCore/GS", "EnableVideoCapture", true); diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.h b/pcsx2-qt/Settings/GraphicsSettingsWidget.h index 51b7686885..a6cb08e39d 100644 --- a/pcsx2-qt/Settings/GraphicsSettingsWidget.h +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.h @@ -38,6 +38,7 @@ private Q_SLOTS: void onTextureReplacementChanged(); void onShadeBoostChanged(); void onCaptureContainerChanged(); + void onCaptureCodecChanged(); void onEnableVideoCaptureChanged(); void onEnableVideoCaptureArgumentsChanged(); void onVideoCaptureAutoResolutionChanged(); diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui index 8196b3764f..e57bfe65f1 100644 --- a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui @@ -1847,13 +1847,23 @@ + + + Format: + + + + + + + Bitrate: - + kbps @@ -1869,14 +1879,14 @@ - + Resolution: - + @@ -1926,14 +1936,14 @@ - + Extra Arguments - + diff --git a/pcsx2/Config.h b/pcsx2/Config.h index 4fbeecfe6c..d34c9a97a1 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -731,6 +731,7 @@ struct Pcsx2Config std::string CaptureContainer = DEFAULT_CAPTURE_CONTAINER; std::string VideoCaptureCodec; + std::string VideoCaptureFormat; std::string VideoCaptureParameters; std::string AudioCaptureCodec; std::string AudioCaptureParameters; diff --git a/pcsx2/GS/GSCapture.cpp b/pcsx2/GS/GSCapture.cpp index d5ad461665..a1e2710163 100644 --- a/pcsx2/GS/GSCapture.cpp +++ b/pcsx2/GS/GSCapture.cpp @@ -43,6 +43,7 @@ extern "C" { #include "libavutil/opt.h" #include "libavutil/version.h" #include "libavutil/channel_layout.h" +#include "libavutil/pixdesc.h" #include "libswscale/swscale.h" #include "libswscale/version.h" #include "libswresample/swresample.h" @@ -117,7 +118,8 @@ extern "C" { X(av_hwframe_transfer_data) \ X(av_hwframe_get_buffer) \ X(av_buffer_ref) \ - X(av_buffer_unref) + X(av_buffer_unref) \ + X(av_get_pix_fmt_name) #define VISIT_SWSCALE_IMPORTS(X) \ X(sws_getCachedContext) \ @@ -447,25 +449,30 @@ bool GSCapture::BeginCapture(float fps, GSVector2i recommendedResolution, float wrap_av_reduce(&s_video_codec_context->time_base.num, &s_video_codec_context->time_base.den, 10000, static_cast(static_cast(fps) * 10000.0), std::numeric_limits::max()); - // Default to YUV 4:2:0 if the codec doesn't specify a pixel format. - AVPixelFormat sw_pix_fmt = AV_PIX_FMT_YUV420P; + // Default to NV12 if not overridden by the user + const AVPixelFormat preferred_sw_pix_fmt = GSConfig.VideoCaptureFormat.empty() ? AV_PIX_FMT_NV12 : static_cast(std::stoi(GSConfig.VideoCaptureFormat)); + AVPixelFormat sw_pix_fmt = preferred_sw_pix_fmt; if (vcodec->pix_fmts) { - // Prefer YUV420 given the choice, but otherwise fall back to whatever it supports. sw_pix_fmt = vcodec->pix_fmts[0]; for (u32 i = 0; vcodec->pix_fmts[i] != AV_PIX_FMT_NONE; i++) { - if (vcodec->pix_fmts[i] == AV_PIX_FMT_YUV420P) + if (vcodec->pix_fmts[i] == preferred_sw_pix_fmt) { sw_pix_fmt = vcodec->pix_fmts[i]; break; } } } + if (sw_pix_fmt == AV_PIX_FMT_VAAPI) sw_pix_fmt = AV_PIX_FMT_NV12; + s_video_codec_context->pix_fmt = sw_pix_fmt; + if (preferred_sw_pix_fmt != sw_pix_fmt) + Console.Warning("GSCapture: preferred pixel format (%d) was unsupported by the codec. Using (%d) instead.", preferred_sw_pix_fmt, sw_pix_fmt); + // Can we use hardware encoding? 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) @@ -692,12 +699,12 @@ bool GSCapture::BeginCapture(float fps, GSVector2i recommendedResolution, float return false; } - #if LIBAVUTIL_VERSION_MAJOR >= 57 - const AVChannelLayout layout = AV_CHANNEL_LAYOUT_STEREO; - wrap_av_opt_set_chlayout(s_swr_context, "in_chlayout", &layout, 0); - wrap_av_opt_set_chlayout(s_swr_context, "out_chlayout", &layout, 0); - #endif - +#if LIBAVUTIL_VERSION_MAJOR >= 57 + const AVChannelLayout layout = AV_CHANNEL_LAYOUT_STEREO; + wrap_av_opt_set_chlayout(s_swr_context, "in_chlayout", &layout, 0); + wrap_av_opt_set_chlayout(s_swr_context, "out_chlayout", &layout, 0); +#endif + wrap_av_opt_set_int(s_swr_context, "in_channel_count", AUDIO_CHANNELS, 0); wrap_av_opt_set_int(s_swr_context, "in_sample_rate", sample_rate, 0); wrap_av_opt_set_sample_fmt(s_swr_context, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0); @@ -1502,3 +1509,33 @@ GSCapture::CodecList GSCapture::GetAudioCodecList(const char* container) { return GetCodecListForContainer(container, AVMEDIA_TYPE_AUDIO); } + +GSCapture::FormatList GSCapture::GetVideoFormatList(const char* codec) +{ + FormatList ret; + + if (!LoadFFmpeg(false)) + return ret; + + const AVCodec* v_codec = wrap_avcodec_find_encoder_by_name(codec); + + if (!v_codec) + { + Console.Error("(GetVideoFormatList) avcodec_find_encoder_by_name() failed"); + return ret; + } + + // rawvideo doesn't have a list of formats. + if(v_codec->pix_fmts == nullptr) + { + Console.Error("(GetVideoFormatList) v_codec->pix_fmts is null."); + return ret; + } + + for (int i = 0; v_codec->pix_fmts[i] != AVPixelFormat::AV_PIX_FMT_NONE; i++) + { + ret.emplace_back(v_codec->pix_fmts[i], wrap_av_get_pix_fmt_name(v_codec->pix_fmts[i])); + } + + return ret; +} diff --git a/pcsx2/GS/GSCapture.h b/pcsx2/GS/GSCapture.h index 658f8f33eb..c63b947501 100644 --- a/pcsx2/GS/GSCapture.h +++ b/pcsx2/GS/GSCapture.h @@ -37,4 +37,8 @@ namespace GSCapture using CodecList = std::vector; CodecList GetVideoCodecList(const char* container); CodecList GetAudioCodecList(const char* container); + + using FormatName = std::pair; // id,name + using FormatList = std::vector; + FormatList GetVideoFormatList(const char* codec); }; // namespace GSCapture diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index f5ef54c9b3..1f858f0a75 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -750,6 +750,7 @@ bool Pcsx2Config::GSOptions::OptionsAreEqual(const GSOptions& right) const OpEqu(CaptureContainer) && OpEqu(VideoCaptureCodec) && + OpEqu(VideoCaptureFormat) && OpEqu(VideoCaptureParameters) && OpEqu(AudioCaptureCodec) && OpEqu(AudioCaptureParameters) && @@ -925,6 +926,7 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap) SettingsWrapEntryEx(CaptureContainer, "CaptureContainer"); SettingsWrapEntryEx(VideoCaptureCodec, "VideoCaptureCodec"); + SettingsWrapEntryEx(VideoCaptureFormat, "VideoCaptureFormat"); SettingsWrapEntryEx(VideoCaptureParameters, "VideoCaptureParameters"); SettingsWrapEntryEx(AudioCaptureCodec, "AudioCaptureCodec"); SettingsWrapEntryEx(AudioCaptureParameters, "AudioCaptureParameters");