diff --git a/frontend/cmake/ui-dialogs.cmake b/frontend/cmake/ui-dialogs.cmake index ec1c96c44ea83b..334db0239002fc 100644 --- a/frontend/cmake/ui-dialogs.cmake +++ b/frontend/cmake/ui-dialogs.cmake @@ -33,6 +33,8 @@ target_sources( dialogs/OBSBasicSourceSelect.hpp dialogs/OBSBasicTransform.cpp dialogs/OBSBasicTransform.hpp + dialogs/OBSBasicRBConfig.cpp + dialogs/OBSBasicRBConfig.hpp dialogs/OBSBasicVCamConfig.cpp dialogs/OBSBasicVCamConfig.hpp dialogs/OBSLogViewer.cpp diff --git a/frontend/cmake/ui-qt.cmake b/frontend/cmake/ui-qt.cmake index f730b86d6ff004..a2a88aa158b203 100644 --- a/frontend/cmake/ui-qt.cmake +++ b/frontend/cmake/ui-qt.cmake @@ -43,6 +43,7 @@ target_sources( forms/OBSBasicProperties.ui forms/OBSBasicSettings.ui forms/OBSBasicSourceSelect.ui + forms/OBSBasicRBConfig.ui forms/OBSBasicVCamConfig.ui forms/OBSExtraBrowsers.ui forms/OBSImporter.ui diff --git a/frontend/cmake/ui-utility.cmake b/frontend/cmake/ui-utility.cmake index 8b02d5ec75e051..7a6d916ecaa039 100644 --- a/frontend/cmake/ui-utility.cmake +++ b/frontend/cmake/ui-utility.cmake @@ -49,6 +49,7 @@ target_sources( utility/RemuxQueueModel.hpp utility/RemuxWorker.cpp utility/RemuxWorker.hpp + utility/ReplayBufferConfig.hpp utility/SceneRenameDelegate.cpp utility/SceneRenameDelegate.hpp utility/ScreenshotObj.cpp diff --git a/frontend/data/locale/en-US.ini b/frontend/data/locale/en-US.ini index 882380935c01d6..203853905360e1 100644 --- a/frontend/data/locale/en-US.ini +++ b/frontend/data/locale/en-US.ini @@ -797,6 +797,11 @@ Basic.VCam.OutputType.Program="Program (Default)" Basic.VCam.OutputSelection.NoSelection="No selection for this output type" Basic.VCam.RestartWarning="The virtual camera will be restarted to apply this change" +# replay buffer configuration +Basic.Main.ReplayBufferConfig="Configure Replay Buffer" +Basic.ReplayBuffer.Config="Replay Buffer Configuration" +Basic.ReplayBuffer.OutputType="Output Type" + # basic mode file menu Basic.MainMenu.File="&File" Basic.MainMenu.File.Export="&Export" diff --git a/frontend/dialogs/OBSBasicRBConfig.cpp b/frontend/dialogs/OBSBasicRBConfig.cpp new file mode 100644 index 00000000000000..781487eff25373 --- /dev/null +++ b/frontend/dialogs/OBSBasicRBConfig.cpp @@ -0,0 +1,57 @@ +#include "OBSBasicRBConfig.hpp" + +#include + +#include "moc_OBSBasicRBConfig.cpp" + +OBSBasicRBConfig::OBSBasicRBConfig(const ReplayBufferConfig &_config, bool _rbActive, QWidget *parent) + : config(_config), + rbActive(_rbActive), + QDialog(parent), + ui(new Ui::OBSBasicRBConfig) +{ + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + + ui->setupUi(this); + + ui->outputType->addItem(QTStr("Basic.VCam.OutputType.Program"), (int)RBOutputProgramView); + ui->outputType->addItem(QTStr("Basic.Scene"), (int)RBOutputSceneView); + + ui->outputType->setCurrentIndex(ui->outputType->findData((int)config.type)); + OutputTypeChanged(); + connect(ui->outputType, &QComboBox::currentIndexChanged, this, &OBSBasicRBConfig::OutputTypeChanged); + + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &OBSBasicRBConfig::UpdateConfig); +} + +void OBSBasicRBConfig::OutputTypeChanged() +{ + ReplayBufferOutputType type = (ReplayBufferOutputType)ui->outputType->currentData().toInt(); + + ui->sceneSelection->clear(); + + if (type == RBOutputProgramView) { + ui->sceneSelection->setDisabled(true); + ui->sceneSelection->addItem(QTStr("Basic.VCam.OutputSelection.NoSelection")); + } else { + ui->sceneSelection->setDisabled(false); + BPtr scenes = obs_frontend_get_scene_names(); + for (char **temp = scenes; *temp; temp++) { + ui->sceneSelection->addItem(*temp); + + if (config.scene.compare(*temp) == 0) + ui->sceneSelection->setCurrentIndex(ui->sceneSelection->count() - 1); + } + } +} + +void OBSBasicRBConfig::UpdateConfig() +{ + ReplayBufferOutputType type = (ReplayBufferOutputType)ui->outputType->currentData().toInt(); + + config.type = type; + if (type == RBOutputSceneView) + config.scene = ui->sceneSelection->currentText().toStdString(); + + emit Accepted(config); +} diff --git a/frontend/dialogs/OBSBasicRBConfig.hpp b/frontend/dialogs/OBSBasicRBConfig.hpp new file mode 100644 index 00000000000000..696a3d4211d1c3 --- /dev/null +++ b/frontend/dialogs/OBSBasicRBConfig.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "ui_OBSBasicRBConfig.h" + +#include + +#include + +class OBSBasicRBConfig : public QDialog { + Q_OBJECT + + ReplayBufferConfig config; + bool rbActive; + +public: + explicit OBSBasicRBConfig(const ReplayBufferConfig &config, bool rbActive, QWidget *parent = 0); + +private slots: + void OutputTypeChanged(); + void UpdateConfig(); + +private: + std::unique_ptr ui; + +signals: + void Accepted(const ReplayBufferConfig &config); +}; diff --git a/frontend/forms/OBSBasicControls.ui b/frontend/forms/OBSBasicControls.ui index 9ee1fbbbfeb28d..5e7ddd919dd222 100644 --- a/frontend/forms/OBSBasicControls.ui +++ b/frontend/forms/OBSBasicControls.ui @@ -224,6 +224,32 @@ + + + + true + + + + 0 + 0 + + + + Basic.Main.ReplayBufferConfig + + + Basic.Main.ReplayBufferConfig + + + + :/settings/images/settings/general.svg:/settings/images/settings/general.svg + + + icon-gear + + + diff --git a/frontend/forms/OBSBasicRBConfig.ui b/frontend/forms/OBSBasicRBConfig.ui new file mode 100644 index 00000000000000..e979157a1d11e7 --- /dev/null +++ b/frontend/forms/OBSBasicRBConfig.ui @@ -0,0 +1,97 @@ + + + OBSBasicRBConfig + + + + 0 + 0 + 400 + 150 + + + + Basic.ReplayBuffer.Config + + + + + + Basic.ReplayBuffer.OutputType + + + + + + + + + + Basic.Scene + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + OBSBasicRBConfig + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + OBSBasicRBConfig + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/frontend/utility/AdvancedOutput.cpp b/frontend/utility/AdvancedOutput.cpp index aff3ad6231072b..5b1575cc084ca4 100644 --- a/frontend/utility/AdvancedOutput.cpp +++ b/frontend/utility/AdvancedOutput.cpp @@ -354,8 +354,12 @@ inline void AdvancedOutput::SetupRecording() if (useStreamEncoder) { obs_output_set_video_encoder(fileOutput, videoStreaming); - if (replayBuffer) - obs_output_set_video_encoder(replayBuffer, videoStreaming); + if (replayBuffer) { + if (replayBufferVideoEncoder) + obs_output_set_video_encoder(replayBuffer, replayBufferVideoEncoder); + else + obs_output_set_video_encoder(replayBuffer, videoStreaming); + } } else { if (rescaleFilter != OBS_SCALE_DISABLE && rescaleRes && *rescaleRes) { if (sscanf(rescaleRes, "%ux%u", &cx, &cy) != 2) { @@ -367,8 +371,12 @@ inline void AdvancedOutput::SetupRecording() obs_encoder_set_scaled_size(videoRecording, cx, cy); obs_encoder_set_gpu_scale_type(videoRecording, (obs_scale_type)rescaleFilter); obs_output_set_video_encoder(fileOutput, videoRecording); - if (replayBuffer) - obs_output_set_video_encoder(replayBuffer, videoRecording); + if (replayBuffer) { + if (replayBufferVideoEncoder) + obs_output_set_video_encoder(replayBuffer, replayBufferVideoEncoder); + else + obs_output_set_video_encoder(replayBuffer, videoRecording); + } } if (!flv) { @@ -552,6 +560,9 @@ void AdvancedOutput::SetupOutputs() SetupFFmpeg(); else SetupRecording(); + + if (replayBufferVideoEncoder && replayBufferVideo) + obs_encoder_set_video(replayBufferVideoEncoder, replayBufferVideo); } int AdvancedOutput::GetAudioBitrate(size_t i, const char *id) const @@ -836,6 +847,8 @@ bool AdvancedOutput::StartRecording() bool AdvancedOutput::StartReplayBuffer() { + SetupReplayBufferSceneOverride(useStreamEncoder ? videoStreaming : videoRecording); + const char *path; const char *recFormat; const char *filenameFormat; diff --git a/frontend/utility/BasicOutputHandler.cpp b/frontend/utility/BasicOutputHandler.cpp index 910867b33b1f42..63e022368f75d0 100644 --- a/frontend/utility/BasicOutputHandler.cpp +++ b/frontend/utility/BasicOutputHandler.cpp @@ -384,6 +384,54 @@ void BasicOutputHandler::DestroyVirtualCameraScene() vCamSourceSceneItem = nullptr; } +void BasicOutputHandler::SetupReplayBufferView(const std::string &sceneName) +{ + DestroyReplayBufferView(); + + if (sceneName.empty()) + return; + + OBSSourceAutoRelease source = obs_get_source_by_name(sceneName.c_str()); + if (!source) + return; + + replayBufferView = obs_view_create(); + obs_view_set_source(replayBufferView, 0, source); + replayBufferVideo = obs_view_add(replayBufferView); +} + +void BasicOutputHandler::SetupReplayBufferSceneOverride(OBSEncoder baseEncoder) +{ + if (main->rbConfig.type != RBOutputSceneView) + return; + + SetupReplayBufferView(main->rbConfig.scene); + if (!replayBufferVideo) + return; + + replayBufferVideoEncoder = + obs_video_encoder_create(obs_encoder_get_id(baseEncoder), "replay_buffer_video", nullptr, nullptr); + + OBSDataAutoRelease settings = obs_encoder_get_settings(baseEncoder); + obs_encoder_update(replayBufferVideoEncoder, settings); + obs_encoder_set_video(replayBufferVideoEncoder, replayBufferVideo); +} + +void BasicOutputHandler::DestroyReplayBufferView() +{ + replayBufferVideoEncoder = nullptr; + + if (!replayBufferView) + return; + + obs_view_remove(replayBufferView); + obs_view_set_source(replayBufferView, 0, nullptr); + replayBufferVideo = nullptr; + + obs_view_destroy(replayBufferView); + replayBufferView = nullptr; +} + const char *FindAudioEncoderFromCodec(const char *type) { const char *alt_enc_id = nullptr; diff --git a/frontend/utility/BasicOutputHandler.hpp b/frontend/utility/BasicOutputHandler.hpp index 29300792a3afb8..d303a8308c62cf 100644 --- a/frontend/utility/BasicOutputHandler.hpp +++ b/frontend/utility/BasicOutputHandler.hpp @@ -43,6 +43,10 @@ struct BasicOutputHandler { obs_scene_t *vCamSourceScene = nullptr; obs_sceneitem_t *vCamSourceSceneItem = nullptr; + obs_view_t *replayBufferView = nullptr; + video_t *replayBufferVideo = nullptr; + OBSEncoder replayBufferVideoEncoder; + std::unique_ptr whipSimulcastEncoders; std::string outputType; @@ -68,7 +72,7 @@ struct BasicOutputHandler { BasicOutputHandler(OBSBasic *main_); - virtual ~BasicOutputHandler() {}; + virtual ~BasicOutputHandler() { DestroyReplayBufferView(); } virtual std::shared_future SetupStreaming(obs_service_t *service, SetupStreamingContinuation_t continuation) = 0; @@ -92,6 +96,10 @@ struct BasicOutputHandler { virtual void DestroyVirtualCamView(); virtual void DestroyVirtualCameraScene(); + void SetupReplayBufferView(const std::string &sceneName); + void SetupReplayBufferSceneOverride(OBSEncoder baseEncoder); + void DestroyReplayBufferView(); + inline bool Active() const { return streamingActive || recordingActive || delayActive || replayBufferActive || virtualCamActive || diff --git a/frontend/utility/ReplayBufferConfig.hpp b/frontend/utility/ReplayBufferConfig.hpp new file mode 100644 index 00000000000000..8b228f995c9c5a --- /dev/null +++ b/frontend/utility/ReplayBufferConfig.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include + +enum ReplayBufferOutputType { + RBOutputProgramView, + RBOutputSceneView, +}; + +struct ReplayBufferConfig { + ReplayBufferOutputType type = RBOutputProgramView; + std::string scene; +}; diff --git a/frontend/utility/SimpleOutput.cpp b/frontend/utility/SimpleOutput.cpp index c3491fd91c9319..f1f0d010955030 100644 --- a/frontend/utility/SimpleOutput.cpp +++ b/frontend/utility/SimpleOutput.cpp @@ -588,6 +588,9 @@ inline void SimpleOutput::SetupOutputs() } else { obs_encoder_set_audio(audioRecording, obs_get_audio()); } + + if (replayBufferVideoEncoder && replayBufferVideo) + obs_encoder_set_video(replayBufferVideoEncoder, replayBufferVideo); } std::shared_future SimpleOutput::SetupStreaming(obs_service_t *service, SetupStreamingContinuation_t continuation) @@ -782,7 +785,10 @@ void SimpleOutput::UpdateRecording() } } if (replayBuffer) { - obs_output_set_video_encoder(replayBuffer, videoRecording); + if (replayBufferVideoEncoder) + obs_output_set_video_encoder(replayBuffer, replayBufferVideoEncoder); + else + obs_output_set_video_encoder(replayBuffer, videoRecording); if (flv || strcmp(quality, "Stream") == 0) { obs_output_set_audio_encoder(replayBuffer, audioRecording, 0); } else { @@ -879,6 +885,8 @@ bool SimpleOutput::StartRecording() bool SimpleOutput::StartReplayBuffer() { + SetupReplayBufferSceneOverride(usingRecordingPreset ? videoRecording : videoStreaming); + UpdateRecording(); if (!ConfigureRecording(true)) return false; diff --git a/frontend/widgets/OBSBasic.cpp b/frontend/widgets/OBSBasic.cpp index e3277eb5f445dc..a2d3c1749b3842 100644 --- a/frontend/widgets/OBSBasic.cpp +++ b/frontend/widgets/OBSBasic.cpp @@ -300,6 +300,7 @@ OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); + connect(controls, &OBSBasicControls::ReplayBufferConfigButtonClicked, this, &OBSBasic::OpenReplayBufferConfig); connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); diff --git a/frontend/widgets/OBSBasic.hpp b/frontend/widgets/OBSBasic.hpp index 2561a7c107fc47..9bdcf65146cbbc 100644 --- a/frontend/widgets/OBSBasic.hpp +++ b/frontend/widgets/OBSBasic.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -1027,6 +1028,7 @@ public slots: private: bool replayBufferStopping = false; std::string lastReplay; + ReplayBufferConfig rbConfig; public slots: void ShowReplayBufferPauseWarning(); @@ -1044,6 +1046,8 @@ public slots: private slots: /* Replay Buffer action (start/stop) slot */ void ReplayBufferActionTriggered(); + void OpenReplayBufferConfig(); + void UpdateReplayBufferConfig(const ReplayBufferConfig &config); signals: /* Replay Buffer signals */ diff --git a/frontend/widgets/OBSBasicControls.cpp b/frontend/widgets/OBSBasicControls.cpp index 5473fcdae55f1e..bd7e2427222f14 100644 --- a/frontend/widgets/OBSBasicControls.cpp +++ b/frontend/widgets/OBSBasicControls.cpp @@ -39,6 +39,9 @@ OBSBasicControls::OBSBasicControls(OBSBasic *main) : QFrame(nullptr), ui(new Ui: connect( ui->virtualCamConfigButton, &QPushButton::clicked, this, [this]() { emit this->VirtualCamConfigButtonClicked(); }, Qt::DirectConnection); + connect( + ui->replayBufferConfigButton, &QPushButton::clicked, this, + [this]() { emit this->ReplayBufferConfigButtonClicked(); }, Qt::DirectConnection); connect( ui->modeSwitch, &QPushButton::clicked, this, [this]() { emit this->StudioModeButtonClicked(); }, Qt::DirectConnection); @@ -64,6 +67,7 @@ OBSBasicControls::OBSBasicControls(OBSBasic *main) : QFrame(nullptr), ui(new Ui: ui->saveReplayButton->setVisible(false); ui->virtualCamButton->setVisible(false); ui->virtualCamConfigButton->setVisible(false); + ui->replayBufferConfigButton->setVisible(false); /* Set up state update connections */ connect(main, &OBSBasic::StreamingPreparing, this, &OBSBasicControls::StreamingPreparing); @@ -274,6 +278,7 @@ void OBSBasicControls::EnableBroadcastFlow(bool enabled) void OBSBasicControls::EnableReplayBufferButtons(bool enabled) { ui->replayBufferButton->setVisible(enabled); + ui->replayBufferConfigButton->setVisible(enabled); } void OBSBasicControls::EnableVirtualCamButtons() diff --git a/frontend/widgets/OBSBasicControls.hpp b/frontend/widgets/OBSBasicControls.hpp index 4dae83b61802c7..39c171f0b67f5b 100644 --- a/frontend/widgets/OBSBasicControls.hpp +++ b/frontend/widgets/OBSBasicControls.hpp @@ -62,6 +62,7 @@ private slots: void SaveReplayBufferButtonClicked(); void VirtualCamButtonClicked(); void VirtualCamConfigButtonClicked(); + void ReplayBufferConfigButtonClicked(); void StudioModeButtonClicked(); void SettingsButtonClicked(); diff --git a/frontend/widgets/OBSBasic_ReplayBuffer.cpp b/frontend/widgets/OBSBasic_ReplayBuffer.cpp index 8d95f2210677ba..9e44b6406a4ca0 100644 --- a/frontend/widgets/OBSBasic_ReplayBuffer.cpp +++ b/frontend/widgets/OBSBasic_ReplayBuffer.cpp @@ -20,6 +20,7 @@ #include "OBSBasic.hpp" #include +#include #include @@ -179,6 +180,8 @@ void OBSBasic::ReplayBufferStop(int code) if (!outputHandler || !outputHandler->replayBuffer) return; + outputHandler->DestroyReplayBufferView(); + emit ReplayBufStopped(); if (sysTrayReplayBuffer) @@ -216,3 +219,22 @@ bool OBSBasic::ReplayBufferActive() return false; return outputHandler->ReplayBufferActive(); } + +void OBSBasic::OpenReplayBufferConfig() +{ + OBSBasicRBConfig dialog(rbConfig, outputHandler->ReplayBufferActive(), this); + + connect(&dialog, &OBSBasicRBConfig::Accepted, this, &OBSBasic::UpdateReplayBufferConfig); + + dialog.exec(); +} + +void OBSBasic::UpdateReplayBufferConfig(const ReplayBufferConfig &config) +{ + rbConfig = config; + + if (config.type == RBOutputSceneView) + blog(LOG_INFO, "Replay buffer output set to scene: %s", config.scene.c_str()); + else + blog(LOG_INFO, "Replay buffer output set to program view"); +}