mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30: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
				
			
		|  | @ -120,6 +120,15 @@ add_executable(citra-qt | |||
|     debugger/wait_tree.cpp | ||||
|     debugger/wait_tree.h | ||||
|     discord.h | ||||
|     dumping/dumping_dialog.cpp | ||||
|     dumping/dumping_dialog.h | ||||
|     dumping/dumping_dialog.ui | ||||
|     dumping/option_set_dialog.cpp | ||||
|     dumping/option_set_dialog.h | ||||
|     dumping/option_set_dialog.ui | ||||
|     dumping/options_dialog.cpp | ||||
|     dumping/options_dialog.h | ||||
|     dumping/options_dialog.ui | ||||
|     game_list.cpp | ||||
|     game_list.h | ||||
|     game_list_p.h | ||||
|  | @ -178,20 +187,6 @@ add_executable(citra-qt | |||
|     util/util.h | ||||
| ) | ||||
| 
 | ||||
| if (ENABLE_FFMPEG_VIDEO_DUMPER) | ||||
|     target_sources(citra-qt PRIVATE | ||||
|         dumping/dumping_dialog.cpp | ||||
|         dumping/dumping_dialog.h | ||||
|         dumping/dumping_dialog.ui | ||||
|         dumping/option_set_dialog.cpp | ||||
|         dumping/option_set_dialog.h | ||||
|         dumping/option_set_dialog.ui | ||||
|         dumping/options_dialog.cpp | ||||
|         dumping/options_dialog.h | ||||
|         dumping/options_dialog.ui | ||||
|     ) | ||||
| endif() | ||||
| 
 | ||||
| file(GLOB COMPAT_LIST | ||||
|      ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc | ||||
|      ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) | ||||
|  | @ -377,11 +372,6 @@ if (MSVC) | |||
|         include(CopyCitraOpensslDeps) | ||||
|         copy_citra_openssl_deps(citra-qt) | ||||
|     endif() | ||||
| 
 | ||||
|     if (ENABLE_FFMPEG) | ||||
|         include(CopyCitraFFmpegDeps) | ||||
|         copy_citra_FFmpeg_deps(citra-qt) | ||||
|     endif() | ||||
| endif() | ||||
| 
 | ||||
| if (NOT APPLE) | ||||
|  |  | |||
|  | @ -10,10 +10,6 @@ | |||
| #include "common/string_util.h" | ||||
| #include "ui_option_set_dialog.h" | ||||
| 
 | ||||
| extern "C" { | ||||
| #include <libavutil/pixdesc.h> | ||||
| } | ||||
| 
 | ||||
| static const std::unordered_map<AVOptionType, const char*> TypeNameMap{{ | ||||
|     {AV_OPT_TYPE_BOOL, QT_TR_NOOP("boolean")}, | ||||
|     {AV_OPT_TYPE_FLAGS, QT_TR_NOOP("flags")}, | ||||
|  | @ -56,21 +52,14 @@ std::vector<std::pair<QString, QString>> GetPresetValues(const VideoDumper::Opti | |||
|     } | ||||
|     case AV_OPT_TYPE_PIXEL_FMT: { | ||||
|         std::vector<std::pair<QString, QString>> out{{QObject::tr("none"), QStringLiteral("none")}}; | ||||
|         // List all pixel formats
 | ||||
|         const AVPixFmtDescriptor* current = nullptr; | ||||
|         while ((current = av_pix_fmt_desc_next(current))) { | ||||
|             out.emplace_back(QString::fromUtf8(current->name), QString::fromUtf8(current->name)); | ||||
|         for (const auto& name : VideoDumper::GetPixelFormats()) { | ||||
|             out.emplace_back(QString::fromUtf8(name), QString::fromUtf8(name)); | ||||
|         } | ||||
|         return out; | ||||
|     } | ||||
|     case AV_OPT_TYPE_SAMPLE_FMT: { | ||||
|         std::vector<std::pair<QString, QString>> out{{QObject::tr("none"), QStringLiteral("none")}}; | ||||
|         // List all sample formats
 | ||||
|         int current = 0; | ||||
|         while (true) { | ||||
|             const char* name = av_get_sample_fmt_name(static_cast<AVSampleFormat>(current)); | ||||
|             if (name == nullptr) | ||||
|                 break; | ||||
|         for (const auto& name : VideoDumper::GetSampleFormats()) { | ||||
|             out.emplace_back(QString::fromUtf8(name), QString::fromUtf8(name)); | ||||
|         } | ||||
|         return out; | ||||
|  |  | |||
|  | @ -49,6 +49,7 @@ | |||
| #include "citra_qt/debugger/registers.h" | ||||
| #include "citra_qt/debugger/wait_tree.h" | ||||
| #include "citra_qt/discord.h" | ||||
| #include "citra_qt/dumping/dumping_dialog.h" | ||||
| #include "citra_qt/game_list.h" | ||||
| #include "citra_qt/hotkeys.h" | ||||
| #include "citra_qt/loading_screen.h" | ||||
|  | @ -102,10 +103,6 @@ | |||
| #include "citra_qt/discord_impl.h" | ||||
| #endif | ||||
| 
 | ||||
| #ifdef ENABLE_FFMPEG_VIDEO_DUMPER | ||||
| #include "citra_qt/dumping/dumping_dialog.h" | ||||
| #endif | ||||
| 
 | ||||
| #ifdef QT_STATICPLUGIN | ||||
| Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); | ||||
| #endif | ||||
|  | @ -834,18 +831,7 @@ void GMainWindow::ConnectMenuEvents() { | |||
|         } | ||||
|     }); | ||||
|     connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot); | ||||
| 
 | ||||
| #ifdef ENABLE_FFMPEG_VIDEO_DUMPER | ||||
|     connect_menu(ui->action_Dump_Video, [this] { | ||||
|         if (ui->action_Dump_Video->isChecked()) { | ||||
|             OnStartVideoDumping(); | ||||
|         } else { | ||||
|             OnStopVideoDumping(); | ||||
|         } | ||||
|     }); | ||||
| #else | ||||
|     ui->action_Dump_Video->setEnabled(false); | ||||
| #endif | ||||
|     connect_menu(ui->action_Dump_Video, &GMainWindow::OnDumpVideo); | ||||
| 
 | ||||
|     // Help
 | ||||
|     connect_menu(ui->action_Open_Citra_Folder, &GMainWindow::OnOpenCitraFolder); | ||||
|  | @ -1203,15 +1189,7 @@ void GMainWindow::BootGame(const QString& filename) { | |||
|     } | ||||
| 
 | ||||
|     if (video_dumping_on_start) { | ||||
|         Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale( | ||||
|             VideoCore::g_renderer->GetResolutionScaleFactor())}; | ||||
|         if (!system.VideoDumper().StartDumping(video_dumping_path.toStdString(), layout)) { | ||||
| 
 | ||||
|             QMessageBox::critical( | ||||
|                 this, tr("Citra"), | ||||
|                 tr("Could not start video dumping.<br>Refer to the log for details.")); | ||||
|             ui->action_Dump_Video->setChecked(false); | ||||
|         } | ||||
|         StartVideoDumping(video_dumping_path); | ||||
|         video_dumping_on_start = false; | ||||
|         video_dumping_path.clear(); | ||||
|     } | ||||
|  | @ -1279,13 +1257,12 @@ void GMainWindow::ShutdownGame() { | |||
|         HideFullscreen(); | ||||
|     } | ||||
| 
 | ||||
| #ifdef ENABLE_FFMPEG_VIDEO_DUMPER | ||||
|     if (system.VideoDumper().IsDumping()) { | ||||
|     auto video_dumper = system.GetVideoDumper(); | ||||
|     if (video_dumper && video_dumper->IsDumping()) { | ||||
|         game_shutdown_delayed = true; | ||||
|         OnStopVideoDumping(); | ||||
|         return; | ||||
|     } | ||||
| #endif | ||||
| 
 | ||||
|     AllowOSSleep(); | ||||
| 
 | ||||
|  | @ -2215,7 +2192,97 @@ void GMainWindow::OnCaptureScreenshot() { | |||
|     OnStartGame(); | ||||
| } | ||||
| 
 | ||||
| #ifdef ENABLE_FFMPEG_VIDEO_DUMPER | ||||
| void GMainWindow::OnDumpVideo() { | ||||
|     if (DynamicLibrary::FFmpeg::LoadFFmpeg()) { | ||||
|         if (ui->action_Dump_Video->isChecked()) { | ||||
|             OnStartVideoDumping(); | ||||
|         } else { | ||||
|             OnStopVideoDumping(); | ||||
|         } | ||||
|     } else { | ||||
|         ui->action_Dump_Video->setChecked(false); | ||||
| 
 | ||||
|         QMessageBox message_box; | ||||
|         message_box.setWindowTitle(tr("Could not load video dumper")); | ||||
|         message_box.setText( | ||||
|             tr("FFmpeg could not be loaded. Make sure you have a compatible version installed." | ||||
| #ifdef _WIN32 | ||||
|                "\n\nTo install FFmpeg to Citra, press Open and select your FFmpeg directory." | ||||
| #endif | ||||
|                "\n\nTo view a guide on how to install FFmpeg, press Help.")); | ||||
|         message_box.setStandardButtons(QMessageBox::Ok | QMessageBox::Help | ||||
| #ifdef _WIN32 | ||||
|                                        | QMessageBox::Open | ||||
| #endif | ||||
|         ); | ||||
|         auto result = message_box.exec(); | ||||
|         if (result == QMessageBox::Help) { | ||||
|             QDesktopServices::openUrl(QUrl(QStringLiteral( | ||||
|                 "https://citra-emu.org/wiki/installing-ffmpeg-for-the-video-dumper/"))); | ||||
| #ifdef _WIN32 | ||||
|         } else if (result == QMessageBox::Open) { | ||||
|             OnOpenFFmpeg(); | ||||
| #endif | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #ifdef _WIN32 | ||||
| void GMainWindow::OnOpenFFmpeg() { | ||||
|     auto filename = | ||||
|         QFileDialog::getExistingDirectory(this, tr("Select FFmpeg Directory")).toStdString(); | ||||
|     if (filename.empty()) { | ||||
|         return; | ||||
|     } | ||||
|     // Check for a bin directory if they chose the FFmpeg root directory.
 | ||||
|     auto bin_dir = filename + DIR_SEP + "bin"; | ||||
|     if (!FileUtil::Exists(bin_dir)) { | ||||
|         // Otherwise, assume the user directly selected the directory containing the DLLs.
 | ||||
|         bin_dir = filename; | ||||
|     } | ||||
| 
 | ||||
|     static const std::array library_names = { | ||||
|         DynamicLibrary::DynamicLibrary::GetLibraryName("avcodec", LIBAVCODEC_VERSION_MAJOR), | ||||
|         DynamicLibrary::DynamicLibrary::GetLibraryName("avfilter", LIBAVFILTER_VERSION_MAJOR), | ||||
|         DynamicLibrary::DynamicLibrary::GetLibraryName("avformat", LIBAVFORMAT_VERSION_MAJOR), | ||||
|         DynamicLibrary::DynamicLibrary::GetLibraryName("avutil", LIBAVUTIL_VERSION_MAJOR), | ||||
|         DynamicLibrary::DynamicLibrary::GetLibraryName("swresample", LIBSWRESAMPLE_VERSION_MAJOR), | ||||
|     }; | ||||
| 
 | ||||
|     for (auto& library_name : library_names) { | ||||
|         if (!FileUtil::Exists(bin_dir + DIR_SEP + library_name)) { | ||||
|             QMessageBox::critical(this, tr("Citra"), | ||||
|                                   tr("The provided FFmpeg directory is missing %1. Please make " | ||||
|                                      "sure the correct directory was selected.") | ||||
|                                       .arg(QString::fromStdString(library_name))); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     std::atomic<bool> success(true); | ||||
|     auto process_file = [&success](u64* num_entries_out, const std::string& directory, | ||||
|                                    const std::string& virtual_name) -> bool { | ||||
|         auto file_path = directory + DIR_SEP + virtual_name; | ||||
|         if (file_path.ends_with(".dll")) { | ||||
|             auto destination_path = FileUtil::GetExeDirectory() + DIR_SEP + virtual_name; | ||||
|             if (!FileUtil::Copy(file_path, destination_path)) { | ||||
|                 success.store(false); | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|         return true; | ||||
|     }; | ||||
|     FileUtil::ForeachDirectoryEntry(nullptr, bin_dir, process_file); | ||||
| 
 | ||||
|     if (success.load()) { | ||||
|         QMessageBox::information(this, tr("Citra"), tr("FFmpeg has been sucessfully installed.")); | ||||
|     } else { | ||||
|         QMessageBox::critical(this, tr("Citra"), | ||||
|                               tr("Installation of FFmpeg failed. Check the log file for details.")); | ||||
|     } | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| void GMainWindow::OnStartVideoDumping() { | ||||
|     DumpingDialog dialog(this); | ||||
|     if (dialog.exec() != QDialog::DialogCode::Accepted) { | ||||
|  | @ -2224,20 +2291,28 @@ void GMainWindow::OnStartVideoDumping() { | |||
|     } | ||||
|     const auto path = dialog.GetFilePath(); | ||||
|     if (emulation_running) { | ||||
|         Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale( | ||||
|             VideoCore::g_renderer->GetResolutionScaleFactor())}; | ||||
|         if (!system.VideoDumper().StartDumping(path.toStdString(), layout)) { | ||||
|             QMessageBox::critical( | ||||
|                 this, tr("Citra"), | ||||
|                 tr("Could not start video dumping.<br>Refer to the log for details.")); | ||||
|             ui->action_Dump_Video->setChecked(false); | ||||
|         } | ||||
|         StartVideoDumping(path); | ||||
|     } else { | ||||
|         video_dumping_on_start = true; | ||||
|         video_dumping_path = path; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::StartVideoDumping(const QString& path) { | ||||
|     Layout::FramebufferLayout layout{ | ||||
|         Layout::FrameLayoutFromResolutionScale(VideoCore::g_renderer->GetResolutionScaleFactor())}; | ||||
| 
 | ||||
|     auto dumper = std::make_shared<VideoDumper::FFmpegBackend>(); | ||||
|     if (dumper->StartDumping(path.toStdString(), layout)) { | ||||
|         system.RegisterVideoDumper(dumper); | ||||
|     } else { | ||||
|         QMessageBox::critical( | ||||
|             this, tr("Citra"), | ||||
|             tr("Could not start video dumping.<br>Refer to the log for details.")); | ||||
|         ui->action_Dump_Video->setChecked(false); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::OnStopVideoDumping() { | ||||
|     ui->action_Dump_Video->setChecked(false); | ||||
| 
 | ||||
|  | @ -2245,14 +2320,15 @@ void GMainWindow::OnStopVideoDumping() { | |||
|         video_dumping_on_start = false; | ||||
|         video_dumping_path.clear(); | ||||
|     } else { | ||||
|         const bool was_dumping = system.VideoDumper().IsDumping(); | ||||
|         if (!was_dumping) | ||||
|         auto dumper = system.GetVideoDumper(); | ||||
|         if (!dumper || !dumper->IsDumping()) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         game_paused_for_dumping = emu_thread->IsRunning(); | ||||
|         OnPauseGame(); | ||||
| 
 | ||||
|         auto future = QtConcurrent::run([this] { system.VideoDumper().StopDumping(); }); | ||||
|         auto future = QtConcurrent::run([dumper] { dumper->StopDumping(); }); | ||||
|         auto* future_watcher = new QFutureWatcher<void>(this); | ||||
|         connect(future_watcher, &QFutureWatcher<void>::finished, this, [this] { | ||||
|             if (game_shutdown_delayed) { | ||||
|  | @ -2266,7 +2342,6 @@ void GMainWindow::OnStopVideoDumping() { | |||
|         future_watcher->setFuture(future); | ||||
|     } | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| void GMainWindow::UpdateStatusBar() { | ||||
|     if (!emu_thread) [[unlikely]] { | ||||
|  |  | |||
|  | @ -242,10 +242,13 @@ private slots: | |||
|     void OnCloseMovie(); | ||||
|     void OnSaveMovie(); | ||||
|     void OnCaptureScreenshot(); | ||||
| #ifdef ENABLE_FFMPEG_VIDEO_DUMPER | ||||
|     void OnStartVideoDumping(); | ||||
|     void OnStopVideoDumping(); | ||||
|     void OnDumpVideo(); | ||||
| #ifdef _WIN32 | ||||
|     void OnOpenFFmpeg(); | ||||
| #endif | ||||
|     void OnStartVideoDumping(); | ||||
|     void StartVideoDumping(const QString& path); | ||||
|     void OnStopVideoDumping(); | ||||
|     void OnCoreError(Core::System::ResultStatus, std::string); | ||||
|     /// Called whenever a user selects Help->About Citra
 | ||||
|     void OnMenuAboutCitra(); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue