From bd2b2c2747b14d862d9bd68392dafac68c991d72 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 29 Mar 2026 09:31:35 +0200 Subject: [PATCH] Qt: add recording settings dialog --- Utilities/Config.h | 6 +- rpcs3/Emu/Io/recording_config.h | 12 +- rpcs3/rpcs3.vcxproj | 28 ++ rpcs3/rpcs3.vcxproj.filters | 18 + rpcs3/rpcs3qt/CMakeLists.txt | 2 + rpcs3/rpcs3qt/main_window.cpp | 7 + rpcs3/rpcs3qt/main_window.ui | 6 + rpcs3/rpcs3qt/recording_settings_dialog.cpp | 455 ++++++++++++++++++++ rpcs3/rpcs3qt/recording_settings_dialog.h | 47 ++ rpcs3/rpcs3qt/recording_settings_dialog.ui | 273 ++++++++++++ 10 files changed, 845 insertions(+), 9 deletions(-) create mode 100644 rpcs3/rpcs3qt/recording_settings_dialog.cpp create mode 100644 rpcs3/rpcs3qt/recording_settings_dialog.h create mode 100644 rpcs3/rpcs3qt/recording_settings_dialog.ui diff --git a/Utilities/Config.h b/Utilities/Config.h index ca9af028a5..4c79cbf31c 100644 --- a/Utilities/Config.h +++ b/Utilities/Config.h @@ -393,7 +393,7 @@ namespace cfg void set(const s64& value) { - ensure(value >= Min && value <= Max); + if (value < Min || value > Max) fmt::throw_exception("'%s': value %d out of bounds (min=%d, max=%d)", m_name, value, Min, Max); m_value = static_cast(value); } @@ -484,7 +484,7 @@ namespace cfg void set(const f64& value) { - ensure(value >= Min && value <= Max); + if (value < Min || value > Max) fmt::throw_exception("'%s': value %d out of bounds (min=%d, max=%d)", m_name, value, Min, Max); m_value = static_cast(value); } @@ -571,7 +571,7 @@ namespace cfg void set(const u64& value) { - ensure(value >= Min && value <= Max); + if (value < Min || value > Max) fmt::throw_exception("'%s': value %d out of bounds (min=%d, max=%d)", m_name, value, Min, Max); m_value = static_cast(value); } diff --git a/rpcs3/Emu/Io/recording_config.h b/rpcs3/Emu/Io/recording_config.h index 127d24015f..ef73149f5e 100644 --- a/rpcs3/Emu/Io/recording_config.h +++ b/rpcs3/Emu/Io/recording_config.h @@ -13,13 +13,13 @@ struct cfg_recording final : cfg::node node_video(cfg::node* _this) : cfg::node(_this, "Video") {} cfg::uint<0, 60> framerate{this, "Framerate", 30}; - cfg::uint<0, 7680> width{this, "Width", 1280}; - cfg::uint<0, 4320> height{this, "Height", 720}; + cfg::uint<640, 7680> width{this, "Width", 1280}; + cfg::uint<360, 4320> height{this, "Height", 720}; cfg::uint<0, 192> pixel_format{this, "AVPixelFormat", 0}; // AVPixelFormat::AV_PIX_FMT_YUV420P cfg::uint<0, 0xFFFF> video_codec{this, "AVCodecID", 12}; // AVCodecID::AV_CODEC_ID_MPEG4 - cfg::uint<0, 25000000> video_bps{this, "Video Bitrate", 4000000}; - cfg::uint<0, 5> max_b_frames{this, "Max B-Frames", 2}; - cfg::uint<0, 20> gop_size{this, "Group of Pictures Size", 12}; + cfg::uint<1'000'000, 60'000'000> video_bps{this, "Video Bitrate", 4'000'000}; + cfg::uint<0, 3> max_b_frames{this, "Max B-Frames", 2}; + cfg::uint<1, 120> gop_size{this, "Group of Pictures Size", 30}; } video{ this }; @@ -28,7 +28,7 @@ struct cfg_recording final : cfg::node node_audio(cfg::node* _this) : cfg::node(_this, "Audio") {} cfg::uint<0x10000, 0x17000> audio_codec{this, "AVCodecID", 86018}; // AVCodecID::AV_CODEC_ID_AAC - cfg::uint<0, 25000000> audio_bps{this, "Audio Bitrate", 320000}; + cfg::uint<64'000, 320'000> audio_bps{this, "Audio Bitrate", 192'000}; } audio{ this }; diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index 5401efb323..d25ca3905c 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -386,6 +386,9 @@ true + + true + true @@ -689,6 +692,9 @@ true + + true + true @@ -868,6 +874,7 @@ + @@ -1157,6 +1164,7 @@ + @@ -1624,6 +1632,16 @@ .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\protobuf\protobuf\src" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\protobuf\protobuf\src" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\protobuf\protobuf\src" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + @@ -2196,6 +2214,16 @@ .\QTGeneratedFiles\ui_%(Filename).h;%(Outputs) "$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)" + + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + .\QTGeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)" + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + .\QTGeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)" + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) Uic%27ing %(Identity)... diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index d9ebc34ef6..28af783415 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -291,6 +291,12 @@ Generated Files\Release + + Generated Files\Debug + + + Generated Files\Release + Generated Files\Debug @@ -441,6 +447,9 @@ Gui\settings + + Gui\settings + Gui\log @@ -1304,6 +1313,9 @@ Generated Files + + Generated Files + Generated Files @@ -1555,6 +1567,9 @@ Form Files + + Form Files + Form Files @@ -1597,6 +1612,9 @@ Gui\settings + + Gui\settings + Gui\log diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index 5c330c7ae4..b0a75d9bfa 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -83,6 +83,7 @@ add_library(rpcs3_ui STATIC qt_video_source.cpp raw_mouse_settings_dialog.cpp register_editor_dialog.cpp + recording_settings_dialog.cpp recvmessage_dialog_frame.cpp render_creator.cpp rpcn_settings_dialog.cpp @@ -135,6 +136,7 @@ add_library(rpcs3_ui STATIC patch_creator_dialog.ui patch_manager_dialog.ui ps_move_tracker_dialog.ui + recording_settings_dialog.ui settings_dialog.ui shortcut_dialog.ui welcome_dialog.ui diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 0d62ef5b72..b4776d2fc1 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -46,6 +46,7 @@ #include "welcome_dialog.h" #include "music_player_dialog.h" #include "sound_effect_manager_dialog.h" +#include "recording_settings_dialog.h" #include #include @@ -3130,6 +3131,12 @@ void main_window::CreateConnects() dlg->show(); }); + connect(ui->actionRecording, &QAction::triggered, this, [this] + { + recording_settings_dialog* dlg = new recording_settings_dialog(this); + dlg->open(); + }); + connect(ui->toolsCgDisasmAct, &QAction::triggered, this, [this] { cg_disasm_window* cgdw = new cg_disasm_window(m_gui_settings); diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index 8fcf4ac610..7a7c965ce8 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -290,6 +290,7 @@ + @@ -1536,6 +1537,11 @@ Play Hover Music + + + Recording + + diff --git a/rpcs3/rpcs3qt/recording_settings_dialog.cpp b/rpcs3/rpcs3qt/recording_settings_dialog.cpp new file mode 100644 index 0000000000..befd14713a --- /dev/null +++ b/rpcs3/rpcs3qt/recording_settings_dialog.cpp @@ -0,0 +1,455 @@ +#include "stdafx.h" +#include "recording_settings_dialog.h" +#include "ui_recording_settings_dialog.h" + +#include + +#ifdef _MSC_VER +#pragma warning(push, 0) +#else +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wall" +#pragma GCC diagnostic ignored "-Wextra" +#pragma GCC diagnostic ignored "-Wold-style-cast" +#endif +extern "C" { +#include "libavcodec/avcodec.h" +#include "libavformat/avformat.h" +} +#ifdef _MSC_VER +#pragma warning(pop) +#else +#pragma GCC diagnostic pop +#endif + +LOG_CHANNEL(cfg_log, "CFG"); + +static std::vector get_video_codecs(const AVOutputFormat* fmt) +{ + std::vector codecs; + + void* opaque = nullptr; + while (const AVCodec* codec = av_codec_iterate(&opaque)) + { + if (!codec->pix_fmts) + continue; + + if (codec->capabilities & AV_CODEC_CAP_EXPERIMENTAL) + continue; + + if (codec->type != AVMediaType::AVMEDIA_TYPE_VIDEO) + continue; + + switch (codec->id) + { + case AV_CODEC_ID_H264: + case AV_CODEC_ID_HEVC: + case AV_CODEC_ID_MPEG4: + case AV_CODEC_ID_AV1: + break; + default: + continue; + } + + if (!av_codec_is_encoder(codec)) + continue; + + if (avformat_query_codec(fmt, codec->id, FF_COMPLIANCE_NORMAL) != 1) + continue; + + codecs.push_back(codec); + } + + return codecs; +} + +static std::vector get_audio_codecs(const AVOutputFormat* fmt) +{ + std::vector codecs; + + void* opaque = nullptr; + while (const AVCodec* codec = av_codec_iterate(&opaque)) + { + if (codec->capabilities & AV_CODEC_CAP_EXPERIMENTAL) + continue; + + if (codec->type != AVMediaType::AVMEDIA_TYPE_AUDIO) + continue; + + if (!av_codec_is_encoder(codec)) + continue; + + if (avformat_query_codec(fmt, codec->id, FF_COMPLIANCE_NORMAL) != 1) + continue; + + codecs.push_back(codec); + } + + return codecs; +} + +recording_settings_dialog::recording_settings_dialog(QWidget* parent) + : QDialog(parent), ui(new Ui::recording_settings_dialog) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + if (!g_cfg_recording.load()) + { + cfg_log.notice("Could not load recording config. Using defaults."); + } + + ui->combo_presets->addItem(tr("720p 30fps"), static_cast(quality_preset::_720p_30)); + ui->combo_presets->addItem(tr("720p 60fps"), static_cast(quality_preset::_720p_60)); + ui->combo_presets->addItem(tr("1080p 30fps"), static_cast(quality_preset::_1080p_30)); + ui->combo_presets->addItem(tr("1080p 60fps"), static_cast(quality_preset::_1080p_60)); + ui->combo_presets->addItem(tr("1440p 30fps"), static_cast(quality_preset::_1440p_30)); + ui->combo_presets->addItem(tr("1440p 60fps"), static_cast(quality_preset::_1440p_60)); + ui->combo_presets->addItem(tr("2160p 30fps"), static_cast(quality_preset::_2160p_30)); + ui->combo_presets->addItem(tr("2160p 60fps"), static_cast(quality_preset::_2160p_60)); + ui->combo_presets->addItem(tr("Custom"), static_cast(quality_preset::custom)); + connect(ui->combo_presets, &QComboBox::currentIndexChanged, this, [this](int index) + { + const QVariant var = ui->combo_presets->itemData(index); + if (var.canConvert()) + { + const quality_preset preset = static_cast(var.toInt()); + select_preset(preset, g_cfg_recording); + update_ui(); + } + }); + + ui->combo_resolution->addItem("360p", QVariant::fromValue(QPair(640, 360))); + ui->combo_resolution->addItem("480p", QVariant::fromValue(QPair(854, 480))); + ui->combo_resolution->addItem("720p", QVariant::fromValue(QPair(1280, 720))); + ui->combo_resolution->addItem("1080p", QVariant::fromValue(QPair(1920, 1080))); + ui->combo_resolution->addItem("1440p", QVariant::fromValue(QPair(2560, 1440))); + ui->combo_resolution->addItem("2160p", QVariant::fromValue(QPair(3840, 2160))); + connect(ui->combo_resolution, &QComboBox::currentIndexChanged, this, [this](int index) + { + const QVariant var = ui->combo_resolution->itemData(index); + if (var.canConvert>()) + { + const QPair size = var.value>(); + g_cfg_recording.video.width.set(size.first); + g_cfg_recording.video.height.set(size.second); + update_preset(); + } + }); + + const AVOutputFormat* fmt = av_guess_format("mp4", nullptr, nullptr); + m_video_codecs = get_video_codecs(fmt); + m_audio_codecs = get_audio_codecs(fmt); + + for (const AVCodec* codec : m_video_codecs) + { + if (!codec) continue; + + const std::string name = codec->long_name ? codec->long_name : avcodec_get_name(codec->id); + ui->combo_video_codec->addItem(QString::fromStdString(name), static_cast(codec->id)); + } + + for (const AVCodec* codec : m_audio_codecs) + { + if (!codec) continue; + + const std::string name = codec->long_name ? codec->long_name : avcodec_get_name(codec->id); + ui->combo_audio_codec->addItem(QString::fromStdString(name), static_cast(codec->id)); + } + + connect(ui->combo_video_codec, &QComboBox::currentIndexChanged, this, [this](int index) + { + const QVariant var = ui->combo_video_codec->itemData(index); + if (var.canConvert()) + { + const int codec_id = var.toInt(); + g_cfg_recording.video.video_codec.set(codec_id); + update_preset(); + } + }); + + connect(ui->combo_audio_codec, &QComboBox::currentIndexChanged, this, [this](int index) + { + const QVariant var = ui->combo_audio_codec->itemData(index); + if (var.canConvert()) + { + const int codec_id = var.toInt(); + g_cfg_recording.audio.audio_codec.set(codec_id); + update_preset(); + } + }); + + ui->combo_framerate->addItem("30", 30); + ui->combo_framerate->addItem("60", 60); + connect(ui->combo_framerate, &QComboBox::currentIndexChanged, this, [this](int index) + { + const QVariant var = ui->combo_framerate->itemData(index); + if (var.canConvert()) + { + const int fps = var.toInt(); + g_cfg_recording.video.framerate.set(fps); + update_preset(); + } + }); + + ui->spinbox_video_bitrate->setSingleStep(1); + ui->spinbox_video_bitrate->setMinimum(g_cfg_recording.video.video_bps.min); + ui->spinbox_video_bitrate->setMaximum(g_cfg_recording.video.video_bps.max); + connect(ui->spinbox_video_bitrate, &QSpinBox::valueChanged, this, [this](int value) + { + g_cfg_recording.video.video_bps.set(value); + update_preset(); + }); + + ui->spinbox_audio_bitrate->setSingleStep(1); + ui->spinbox_audio_bitrate->setMinimum(g_cfg_recording.audio.audio_bps.min); + ui->spinbox_audio_bitrate->setMaximum(g_cfg_recording.audio.audio_bps.max); + connect(ui->spinbox_audio_bitrate, &QSpinBox::valueChanged, this, [this](int value) + { + g_cfg_recording.audio.audio_bps.set(value); + update_preset(); + }); + + ui->spinbox_gop_size->setSingleStep(1); + ui->spinbox_gop_size->setMinimum(g_cfg_recording.video.gop_size.min); + ui->spinbox_gop_size->setMaximum(g_cfg_recording.video.gop_size.max); + connect(ui->spinbox_gop_size, &QSpinBox::valueChanged, this, [this](int value) + { + g_cfg_recording.video.gop_size.set(value); + update_preset(); + }); + + ui->spinbox_max_b_frames->setSingleStep(1); + ui->spinbox_max_b_frames->setMinimum(g_cfg_recording.video.max_b_frames.min); + ui->spinbox_max_b_frames->setMaximum(g_cfg_recording.video.max_b_frames.max); + connect(ui->spinbox_max_b_frames, &QSpinBox::valueChanged, this, [this](int value) + { + g_cfg_recording.video.max_b_frames.set(value); + update_preset(); + }); + + connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button) + { + if (button == ui->buttonBox->button(QDialogButtonBox::Save)) + { + g_cfg_recording.save(); + accept(); + } + else if (button == ui->buttonBox->button(QDialogButtonBox::Cancel)) + { + reject(); + } + else if (button == ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)) + { + g_cfg_recording.from_default(); + update_ui(); + update_preset(); + } + }); + + connect(this, &QDialog::rejected, this, []() + { + if (!g_cfg_recording.load()) + { + cfg_log.notice("Could not load recording config. Using defaults."); + } + }); + + update_ui(); + update_preset(); +} + +recording_settings_dialog::~recording_settings_dialog() +{ +} + +void recording_settings_dialog::update_preset() +{ + const quality_preset preset = current_preset(); + ui->combo_presets->setCurrentIndex(ui->combo_presets->findData(static_cast(preset))); +} + +void recording_settings_dialog::update_ui() +{ + ui->combo_resolution->blockSignals(true); + ui->combo_framerate->blockSignals(true); + ui->combo_video_codec->blockSignals(true); + ui->combo_audio_codec->blockSignals(true); + ui->spinbox_video_bitrate->blockSignals(true); + ui->spinbox_audio_bitrate->blockSignals(true); + ui->spinbox_gop_size->blockSignals(true); + ui->spinbox_max_b_frames->blockSignals(true); + + ui->combo_resolution->setCurrentIndex(ui->combo_resolution->findData(QVariant::fromValue(QPair(g_cfg_recording.video.width.get(), g_cfg_recording.video.height.get())))); + ui->combo_framerate->setCurrentIndex(ui->combo_framerate->findData(static_cast(g_cfg_recording.video.framerate.get()))); + ui->combo_video_codec->setCurrentIndex(ui->combo_video_codec->findData(static_cast(g_cfg_recording.video.video_codec.get()))); + ui->combo_audio_codec->setCurrentIndex(ui->combo_audio_codec->findData(static_cast(g_cfg_recording.audio.audio_codec.get()))); + ui->spinbox_video_bitrate->setValue(g_cfg_recording.video.video_bps); + ui->spinbox_audio_bitrate->setValue(g_cfg_recording.audio.audio_bps); + ui->spinbox_gop_size->setValue(g_cfg_recording.video.gop_size); + ui->spinbox_max_b_frames->setValue(g_cfg_recording.video.max_b_frames); + + ui->combo_resolution->blockSignals(false); + ui->combo_framerate->blockSignals(false); + ui->combo_video_codec->blockSignals(false); + ui->combo_audio_codec->blockSignals(false); + ui->spinbox_video_bitrate->blockSignals(false); + ui->spinbox_audio_bitrate->blockSignals(false); + ui->spinbox_gop_size->blockSignals(false); + ui->spinbox_max_b_frames->blockSignals(false); + + const auto get_codec_name = [](const std::vector& codecs, u32 id) + { + for (const AVCodec* codec : codecs) + { + if (codec && codec->id == static_cast(id)) + { + const std::string name = codec->long_name ? codec->long_name : avcodec_get_name(codec->id); + return name; + } + } + return std::string(); + }; + + ui->label_info_keys->setText( + tr("Resolution:") + "\n" + + tr("Framerate:") + "\n" + + tr("Video Codec:") + "\n" + + tr("Video Bitrate:") + "\n" + + tr("Audio Codec:") + "\n" + + tr("Audio Bitrate:") + "\n" + + tr("Gop-Size:") + "\n" + + tr("Max B-Frames:") + ); + + ui->label_info_values->setText(QString::fromStdString( + fmt::format("%d x %d\n%d fps\n%s\n%d\n%s\n%d\n%d\n%d", + g_cfg_recording.video.width.get(), g_cfg_recording.video.height.get(), + g_cfg_recording.video.framerate.get(), + get_codec_name(m_video_codecs, g_cfg_recording.video.video_codec.get()), + g_cfg_recording.video.video_bps.get(), + get_codec_name(m_audio_codecs, g_cfg_recording.audio.audio_codec.get()), + g_cfg_recording.audio.audio_bps.get(), + g_cfg_recording.video.gop_size.get(), + g_cfg_recording.video.max_b_frames.get() + ) + )); +} + +void recording_settings_dialog::select_preset(quality_preset preset, cfg_recording& cfg) +{ + if (preset == quality_preset::custom) + { + return; + } + + cfg.audio.audio_codec.set(static_cast(AVCodecID::AV_CODEC_ID_AAC)); + cfg.audio.audio_bps.set(192'000); // 192 kbps + + cfg.video.video_codec.set(static_cast(AVCodecID::AV_CODEC_ID_MPEG4)); + cfg.video.pixel_format.set(static_cast(::AV_PIX_FMT_YUV420P)); + + switch (preset) + { + case quality_preset::_720p_30: + case quality_preset::_720p_60: + cfg.video.width.set(1280); + cfg.video.height.set(720); + break; + case quality_preset::_1080p_30: + case quality_preset::_1080p_60: + cfg.video.width.set(1920); + cfg.video.height.set(1080); + break; + case quality_preset::_1440p_30: + case quality_preset::_1440p_60: + cfg.video.width.set(2560); + cfg.video.height.set(1440); + break; + case quality_preset::_2160p_30: + case quality_preset::_2160p_60: + cfg.video.width.set(3840); + cfg.video.height.set(2160); + break; + case quality_preset::custom: + break; + } + + switch (preset) + { + case quality_preset::_720p_30: + case quality_preset::_1080p_30: + case quality_preset::_1440p_30: + case quality_preset::_2160p_30: + cfg.video.framerate.set(30); + break; + case quality_preset::_720p_60: + case quality_preset::_1080p_60: + case quality_preset::_1440p_60: + case quality_preset::_2160p_60: + cfg.video.framerate.set(60); + break; + case quality_preset::custom: + break; + } + + switch (preset) + { + case quality_preset::_720p_30: + cfg.video.video_bps.set(4'000'000); + break; + case quality_preset::_720p_60: + cfg.video.video_bps.set(6'000'000); + break; + case quality_preset::_1080p_30: + cfg.video.video_bps.set(8'000'000); + break; + case quality_preset::_1080p_60: + cfg.video.video_bps.set(12'000'000); + break; + case quality_preset::_1440p_30: + cfg.video.video_bps.set(16'000'000); + break; + case quality_preset::_1440p_60: + cfg.video.video_bps.set(24'000'000); + break; + case quality_preset::_2160p_30: + cfg.video.video_bps.set(40'000'000); + break; + case quality_preset::_2160p_60: + cfg.video.video_bps.set(60'000'000); + break; + case quality_preset::custom: + break; + } + + cfg.video.gop_size.set(cfg.video.framerate.get()); + cfg.video.max_b_frames.set(2); +} + +recording_settings_dialog::quality_preset recording_settings_dialog::current_preset() +{ + for (u32 i = 0; i < static_cast(quality_preset::custom); i++) + { + const quality_preset preset = static_cast(i); + + cfg_recording cfg; + select_preset(preset, cfg); + + if (g_cfg_recording.video.framerate.get() == cfg.video.framerate.get() && + g_cfg_recording.video.width.get() == cfg.video.width.get() && + g_cfg_recording.video.height.get() == cfg.video.height.get() && + g_cfg_recording.video.pixel_format.get() == cfg.video.pixel_format.get() && + g_cfg_recording.video.video_codec.get() == cfg.video.video_codec.get() && + g_cfg_recording.video.video_bps.get() == cfg.video.video_bps.get() && + g_cfg_recording.video.max_b_frames.get() == cfg.video.max_b_frames.get() && + g_cfg_recording.video.gop_size.get() == cfg.video.gop_size.get() && + g_cfg_recording.audio.audio_codec.get() == cfg.audio.audio_codec.get() && + g_cfg_recording.audio.audio_bps.get() == cfg.audio.audio_bps.get()) + { + return preset; + } + } + + return quality_preset::custom; +} diff --git a/rpcs3/rpcs3qt/recording_settings_dialog.h b/rpcs3/rpcs3qt/recording_settings_dialog.h new file mode 100644 index 0000000000..3a17d7d8a8 --- /dev/null +++ b/rpcs3/rpcs3qt/recording_settings_dialog.h @@ -0,0 +1,47 @@ +#pragma once + +#include "util/types.hpp" +#include "Emu/Io/recording_config.h" + +#include + +namespace Ui +{ + class recording_settings_dialog; +} + +struct AVCodec; + +class recording_settings_dialog : public QDialog +{ + Q_OBJECT + +public: + recording_settings_dialog(QWidget* parent = nullptr); + virtual ~recording_settings_dialog(); + +private: + enum class quality_preset + { + _720p_30, + _720p_60, + _1080p_30, + _1080p_60, + _1440p_30, + _1440p_60, + _2160p_30, + _2160p_60, + custom + }; + + void update_preset(); + void update_ui(); + + static void select_preset(quality_preset preset, cfg_recording& cfg); + static quality_preset current_preset(); + + Ui::recording_settings_dialog* ui; + + std::vector m_video_codecs; + std::vector m_audio_codecs; +}; diff --git a/rpcs3/rpcs3qt/recording_settings_dialog.ui b/rpcs3/rpcs3qt/recording_settings_dialog.ui new file mode 100644 index 0000000000..9d11e1299a --- /dev/null +++ b/rpcs3/rpcs3qt/recording_settings_dialog.ui @@ -0,0 +1,273 @@ + + + recording_settings_dialog + + + + 0 + 0 + 692 + 734 + + + + Recording Settings + + + + + + 0 + + + + Presets + + + + + + Preset + + + + + + + + + + + + Info + + + + + + Keys + + + + + + + Values + + + + + + + Qt::Orientation::Horizontal + + + + 0 + 0 + + + + + + + + + + + Qt::Orientation::Vertical + + + + 0 + 0 + + + + + + + + + Advanced + + + + + + Video + + + + + + Codec + + + + + + + + + + + + Resolution + + + + + + + + + + + + Framerate + + + + + + + + + + + + Bitrate + + + + + + + + + + + + Group of Pictures Size + + + + + + + + + + + + Max. B-Frames + + + + + + + + + + + + + + + Audio + + + + + + Codec + + + + + + + + + + + + Bitrate + + + + + + + + + + + + + + + Qt::Orientation::Vertical + + + + 0 + 0 + + + + + + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::RestoreDefaults|QDialogButtonBox::StandardButton::Save + + + + + + + + + buttonBox + accepted() + recording_settings_dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + recording_settings_dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +