From 055206239049df32a51f86247f53b674e72e950a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20S=C3=A4dtler?= Date: Wed, 5 Feb 2025 13:32:03 +0100 Subject: [PATCH] frontend: Allow selecting additional canvases for multitrack --- frontend/data/locale/en-US.ini | 3 + frontend/forms/OBSBasicSettings.ui | 62 +++++++++------ frontend/settings/OBSBasicSettings.cpp | 2 + frontend/settings/OBSBasicSettings_Stream.cpp | 19 +++++ frontend/utility/BasicOutputHandler.cpp | 9 ++- frontend/utility/GoLiveAPI_PostData.cpp | 19 +++-- frontend/utility/GoLiveAPI_PostData.hpp | 3 +- frontend/utility/MultitrackVideoOutput.cpp | 78 ++++++++++++++----- frontend/utility/MultitrackVideoOutput.hpp | 4 +- frontend/wizards/AutoConfigStreamPage.cpp | 4 +- 10 files changed, 147 insertions(+), 56 deletions(-) diff --git a/frontend/data/locale/en-US.ini b/frontend/data/locale/en-US.ini index 781feb422..15b1f4aed 100644 --- a/frontend/data/locale/en-US.ini +++ b/frontend/data/locale/en-US.ini @@ -994,6 +994,7 @@ Basic.Settings.Stream.MultitrackVideoStreamDumpEnable="Enable stream dump to FLV Basic.Settings.Stream.MultitrackVideoConfigOverride="Config Override (JSON)" Basic.Settings.Stream.MultitrackVideoConfigOverrideEnable="Enable Config Override" Basic.Settings.Stream.MultitrackVideoLabel="Multitrack Video" +Basic.Settings.Stream.MultitrackVideoExtraCanvas="Additional Canvas" Basic.Settings.Stream.AdvancedOptions="Advanced Options" # basic mode 'output' settings @@ -1576,6 +1577,7 @@ ConfigDownload.WarningMessageTitle="Warning" FailedToStartStream.MissingConfigURL="No config URL available for the current service" FailedToStartStream.NoCustomRTMPURLInSettings="Custom RTMP URL not specified" FailedToStartStream.InvalidCustomConfig="Invalid custom config" +FailedToStartStream.MissingCanvas="A configured extra canvas is missing" FailedToStartStream.FailedToCreateMultitrackVideoService="Failed to create multitrack video service" FailedToStartStream.FailedToCreateMultitrackVideoOutput="Failed to create multitrack video RTMP output" FailedToStartStream.EncoderNotAvailable="NVENC not available.\n\nFailed to find encoder type '%1'" @@ -1589,6 +1591,7 @@ FailedToStartStream.WarningUnknownStatus="Received unknown status value '%1'" FailedToStartStream.WarningRetryNonMultitrackVideo="\n

\nDo you want to continue streaming without %1?" FailedToStartStream.WarningRetry="\n

\nDo you want to continue streaming?" FailedToStartStream.MissingEncoderConfigs="Go live config did not include encoder configurations" +FailedToStartStream.InvalidEncoderConfig="Go live config included an invalid encoder configuration" FailedToStartStream.StatusMissingHTML="Go live request returned an unspecified error" FailedToStartStream.NoConfigSupplied="Missing config" MultitrackVideo.Info="%1 automatically optimizes your settings to encode and send multiple video qualities. Selecting this option will send %2 information about your computer and software setup." diff --git a/frontend/forms/OBSBasicSettings.ui b/frontend/forms/OBSBasicSettings.ui index dfc46c0d3..812f6bb28 100644 --- a/frontend/forms/OBSBasicSettings.ui +++ b/frontend/forms/OBSBasicSettings.ui @@ -1907,6 +1907,29 @@ QFormLayout::AllNonFixedFieldsGrow + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 170 + 10 + + + + + + + + Basic.Settings.Stream.EnableMultitrackVideo + + + @@ -1993,7 +2016,14 @@ - + + + + None + + + + Qt::Horizontal @@ -2009,28 +2039,28 @@ - + Basic.Settings.Stream.MultitrackVideoStreamDumpEnable - + Basic.Settings.Stream.MultitrackVideoConfigOverrideEnable - + Basic.Settings.Stream.MultitrackVideoConfigOverride - + @@ -2040,29 +2070,13 @@ - - + + - Basic.Settings.Stream.EnableMultitrackVideo + Basic.Settings.Stream.MultitrackVideoExtraCanvas - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 170 - 10 - - - - diff --git a/frontend/settings/OBSBasicSettings.cpp b/frontend/settings/OBSBasicSettings.cpp index 5d1dbce0b..260601bd9 100644 --- a/frontend/settings/OBSBasicSettings.cpp +++ b/frontend/settings/OBSBasicSettings.cpp @@ -393,6 +393,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) HookWidget(ui->multitrackVideoStreamDumpEnable, CHECK_CHANGED, STREAM1_CHANGED); HookWidget(ui->multitrackVideoConfigOverrideEnable, CHECK_CHANGED, STREAM1_CHANGED); HookWidget(ui->multitrackVideoConfigOverride, TEXT_CHANGED, STREAM1_CHANGED); + HookWidget(ui->multitrackVideoAdditionalCanvas, COMBO_CHANGED, STREAM1_CHANGED); HookWidget(ui->outputMode, COMBO_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->simpleOutputPath, EDIT_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->simpleNoSpace, CHECK_CHANGED, OUTPUTS_CHANGED); @@ -5621,6 +5622,7 @@ void OBSBasicSettings::UpdateMultitrackVideo() ui->enableMultitrackVideo->isChecked()); ui->multitrackVideoMaximumVideoTracks->setEnabled(toggle_available && ui->enableMultitrackVideo->isChecked() && !ui->multitrackVideoMaximumVideoTracksAuto->isChecked()); + ui->multitrackVideoAdditionalCanvas->setEnabled(toggle_available && ui->enableMultitrackVideo->isChecked()); ui->multitrackVideoStreamDumpEnable->setVisible(available && MultitrackVideoDeveloperModeEnabled()); ui->multitrackVideoConfigOverrideEnable->setVisible(available && MultitrackVideoDeveloperModeEnabled()); diff --git a/frontend/settings/OBSBasicSettings_Stream.cpp b/frontend/settings/OBSBasicSettings_Stream.cpp index eff5d7de3..ce74a1cb1 100644 --- a/frontend/settings/OBSBasicSettings_Stream.cpp +++ b/frontend/settings/OBSBasicSettings_Stream.cpp @@ -170,6 +170,24 @@ void OBSBasicSettings::LoadStream1Settings() config_get_string(main->Config(), "Stream1", "MultitrackVideoConfigOverride")) .c_str()); + ui->multitrackVideoAdditionalCanvas->clear(); + ui->multitrackVideoAdditionalCanvas->addItem(QTStr("None")); + for (const auto &canvas : main->GetCanvases()) { + if (obs_canvas_get_flags(canvas) & EPHEMERAL) + continue; + + ui->multitrackVideoAdditionalCanvas->addItem(obs_canvas_get_name(canvas), obs_canvas_get_uuid(canvas)); + } + + if (config_has_user_value(main->Config(), "Stream1", "MultitrackExtraCanvas")) { + /* Currently we only support one canvas, so the value will just be one UUID. */ + const std::string_view uuid = config_get_string(main->Config(), "Stream1", "MultitrackExtraCanvas"); + if (!uuid.empty()) { + int idx = ui->multitrackVideoAdditionalCanvas->findData(uuid.data()); + ui->multitrackVideoAdditionalCanvas->setCurrentIndex(idx); + } + } + UpdateServerList(); if (is_rtmp_common) { @@ -335,6 +353,7 @@ void OBSBasicSettings::SaveStream1Settings() SaveCheckBox(ui->multitrackVideoStreamDumpEnable, "Stream1", "MultitrackVideoStreamDumpEnabled"); SaveCheckBox(ui->multitrackVideoConfigOverrideEnable, "Stream1", "MultitrackVideoConfigOverrideEnabled"); SaveText(ui->multitrackVideoConfigOverride, "Stream1", "MultitrackVideoConfigOverride"); + SaveComboData(ui->multitrackVideoAdditionalCanvas, "Stream1", "MultitrackExtraCanvas"); if (oldMultitrackVideoSetting != ui->enableMultitrackVideo->isChecked()) main->ResetOutputs(); diff --git a/frontend/utility/BasicOutputHandler.cpp b/frontend/utility/BasicOutputHandler.cpp index 7488c4662..66ea098a1 100644 --- a/frontend/utility/BasicOutputHandler.cpp +++ b/frontend/utility/BasicOutputHandler.cpp @@ -457,6 +457,12 @@ std::shared_future BasicOutputHandler::SetupMultitrackVideo(obs_service_t custom_config = DeserializeConfigText( config_get_string(main->Config(), "Stream1", "MultitrackVideoConfigOverride")); + std::optional extraCanvasUUID; + const char *uuid = config_get_string(main->Config(), "Stream1", "MultitrackExtraCanvas"); + if (uuid && *uuid) { + extraCanvasUUID = uuid; + } + OBSDataAutoRelease settings = obs_service_get_settings(service); QString key = obs_data_get_string(settings, "key"); @@ -532,7 +538,8 @@ std::shared_future BasicOutputHandler::SetupMultitrackVideo(obs_service_t multitrackVideo->PrepareStreaming(main, service_name.c_str(), service, custom_rtmp_url, key, audio_encoder_id.c_str(), maximum_aggregate_bitrate, maximum_video_tracks, custom_config, stream_dump_config, - main_audio_mixer, vod_track_mixer, use_rtmps); + main_audio_mixer, vod_track_mixer, use_rtmps, + extraCanvasUUID); } catch (const MultitrackVideoError &error_) { error.emplace(error_); } diff --git a/frontend/utility/GoLiveAPI_PostData.cpp b/frontend/utility/GoLiveAPI_PostData.cpp index 9d46c4033..eed63f59b 100644 --- a/frontend/utility/GoLiveAPI_PostData.cpp +++ b/frontend/utility/GoLiveAPI_PostData.cpp @@ -6,7 +6,8 @@ #include GoLiveApi::PostData constructGoLivePost(QString streamKey, const std::optional &maximum_aggregate_bitrate, - const std::optional &maximum_video_tracks, bool vod_track_enabled) + const std::optional &maximum_video_tracks, bool vod_track_enabled, + const std::vector &canvases) { GoLiveApi::PostData post_data{}; post_data.service = "IVS"; @@ -49,13 +50,17 @@ GoLiveApi::PostData constructGoLivePost(QString streamKey, const std::optional GoLiveApi::PostData constructGoLivePost(QString streamKey, const std::optional &maximum_aggregate_bitrate, - const std::optional &maximum_video_tracks, bool vod_track_enabled); + const std::optional &maximum_video_tracks, bool vod_track_enabled, + const std::vector &canvases); diff --git a/frontend/utility/MultitrackVideoOutput.cpp b/frontend/utility/MultitrackVideoOutput.cpp index 7eb4cfe10..0f8f5c928 100644 --- a/frontend/utility/MultitrackVideoOutput.cpp +++ b/frontend/utility/MultitrackVideoOutput.cpp @@ -222,7 +222,8 @@ static bool encoder_available(const char *type) } static OBSEncoderAutoRelease create_video_encoder(DStr &name_buffer, size_t encoder_index, - const GoLiveApi::VideoEncoderConfiguration &encoder_config) + const GoLiveApi::VideoEncoderConfiguration &encoder_config, + const OBSCanvasAutoRelease &canvas) { auto encoder_type = encoder_config.type.c_str(); if (!encoder_available(encoder_type)) { @@ -292,10 +293,10 @@ static OBSEncoderAutoRelease create_video_encoder(DStr &name_buffer, size_t enco throw MultitrackVideoError::warning( QTStr("FailedToStartStream.FailedToCreateVideoEncoder").arg(name_buffer->array, encoder_type)); } - obs_encoder_set_video(video_encoder, obs_get_video()); + obs_encoder_set_video(video_encoder, obs_canvas_get_video(canvas)); obs_video_info ovi; - if (!obs_get_video_info(&ovi)) { + if (!obs_canvas_get_video_info(canvas, &ovi)) { blog(LOG_WARNING, "Failed to get obs_video_info while creating encoder %zu", encoder_index); throw MultitrackVideoError::warning( QTStr("FailedToStartStream.FailedToGetOBSVideoInfo").arg(name_buffer->array, encoder_type)); @@ -329,18 +330,17 @@ static OBSOutputs SetupOBSOutput(QWidget *parent, const QString &multitrack_vide std::vector &audio_encoders, std::shared_ptr &video_encoder_group, const char *audio_encoder_id, size_t main_audio_mixer, - std::optional vod_track_mixer); + std::optional vod_track_mixer, + const std::vector &canvases); static void SetupSignalHandlers(bool recording, MultitrackVideoOutput *self, obs_output_t *output, OBSSignal &start, OBSSignal &stop); -void MultitrackVideoOutput::PrepareStreaming(QWidget *parent, const char *service_name, obs_service_t *service, - const std::optional &rtmp_url, const QString &stream_key, - const char *audio_encoder_id, - std::optional maximum_aggregate_bitrate, - std::optional maximum_video_tracks, - std::optional custom_config, - obs_data_t *dump_stream_to_file_config, size_t main_audio_mixer, - std::optional vod_track_mixer, std::optional use_rtmps) +void MultitrackVideoOutput::PrepareStreaming( + QWidget *parent, const char *service_name, obs_service_t *service, const std::optional &rtmp_url, + const QString &stream_key, const char *audio_encoder_id, std::optional maximum_aggregate_bitrate, + std::optional maximum_video_tracks, std::optional custom_config, + obs_data_t *dump_stream_to_file_config, size_t main_audio_mixer, std::optional vod_track_mixer, + std::optional use_rtmps, std::optional extra_canvas) { { const std::lock_guard current_lock{current_mutex}; @@ -366,6 +366,25 @@ void MultitrackVideoOutput::PrepareStreaming(QWidget *parent, const char *servic auto auto_config_url_data = auto_config_url.toUtf8(); + std::vector canvases; + + canvases.emplace_back(obs_get_main_canvas()); + if (extra_canvas) { + obs_canvas_t *canvas = obs_get_canvas_by_uuid(extra_canvas->toUtf8().constData()); + if (!canvas) { + throw MultitrackVideoError::critical(QTStr("FailedToStartStream.MissingCanvas")); + } + canvases.emplace_back(canvas); + } + + std::string canvasNames; + for (const auto &canvas : canvases) { + if (!canvasNames.empty()) + canvasNames += ", "; + + canvasNames += obs_canvas_get_name(canvas); + } + DStr vod_track_info_storage; if (vod_track_mixer.has_value()) dstr_printf(vod_track_info_storage, "Yes (mixer: %zu)", vod_track_mixer.value()); @@ -379,13 +398,14 @@ void MultitrackVideoOutput::PrepareStreaming(QWidget *parent, const char *servic " max aggregate bitrate: %s (%" PRIu32 ")\n" " max video tracks: %s (%" PRIu32 ")\n" " custom rtmp url: %s ('%s')\n" - " vod track: %s", + " vod track: %s\n" + " canvases: %s", is_custom_config ? "Yes" : "No", !auto_config_url.isEmpty() ? auto_config_url_data.constData() : "(null)", service_name, maximum_aggregate_bitrate.has_value() ? "Set" : "Auto", maximum_aggregate_bitrate.value_or(0), maximum_video_tracks.has_value() ? "Set" : "Auto", maximum_video_tracks.value_or(0), rtmp_url.has_value() ? "Yes" : "No", rtmp_url.has_value() ? rtmp_url->c_str() : "", - vod_track_info_storage->array ? vod_track_info_storage->array : "No"); + vod_track_info_storage->array ? vod_track_info_storage->array : "No", canvasNames.c_str()); const bool custom_config_only = auto_config_url.isEmpty() && MultitrackVideoDeveloperModeEnabled() && custom_config.has_value() && @@ -393,7 +413,7 @@ void MultitrackVideoOutput::PrepareStreaming(QWidget *parent, const char *servic if (!custom_config_only) { auto go_live_post = constructGoLivePost(stream_key, maximum_aggregate_bitrate, maximum_video_tracks, - vod_track_mixer.has_value()); + vod_track_mixer.has_value(), canvases); go_live_config = DownloadGoLiveConfig(parent, auto_config_url, go_live_post, multitrack_video_name); } @@ -436,7 +456,7 @@ void MultitrackVideoOutput::PrepareStreaming(QWidget *parent, const char *servic std::shared_ptr video_encoder_group; auto outputs = SetupOBSOutput(parent, multitrack_video_name, dump_stream_to_file_config, output_config, audio_encoders, video_encoder_group, audio_encoder_id, main_audio_mixer, - vod_track_mixer); + vod_track_mixer, canvases); auto output = std::move(outputs.output); auto recording_output = std::move(outputs.recording_output); if (!output) @@ -474,6 +494,11 @@ void MultitrackVideoOutput::PrepareStreaming(QWidget *parent, const char *servic recording_audio_encoders.emplace_back(obs_encoder_get_ref(encoder)); } + std::vector recording_canvases(canvases.size()); + for (const auto &canvas : canvases) { + recording_canvases.emplace_back(obs_canvas_get_ref(canvas)); + } + { const std::lock_guard current_stream_dump_lock{current_stream_dump_mutex}; current_stream_dump.emplace(OBSOutputObjects{ @@ -483,6 +508,7 @@ void MultitrackVideoOutput::PrepareStreaming(QWidget *parent, const char *servic nullptr, std::move(start_recording), std::move(stop_recording), + std::move(recording_canvases), }); } } @@ -495,6 +521,7 @@ void MultitrackVideoOutput::PrepareStreaming(QWidget *parent, const char *servic std::move(multitrack_video_service), std::move(start_streaming), std::move(stop_streaming), + std::move(canvases), }); } @@ -622,7 +649,7 @@ bool MultitrackVideoOutput::HandleIncompatibleSettings(QWidget *parent, config_t static bool create_video_encoders(const GoLiveApi::Config &go_live_config, std::shared_ptr &video_encoder_group, obs_output_t *output, - obs_output_t *recording_output) + obs_output_t *recording_output, const std::vector &canvases) { DStr video_encoder_name_buffer; if (go_live_config.encoder_configurations.empty()) { @@ -634,9 +661,17 @@ static bool create_video_encoders(const GoLiveApi::Config &go_live_config, if (!encoder_group) return false; + auto max_canvas_idx = canvases.size() - 1; + for (size_t i = 0; i < go_live_config.encoder_configurations.size(); i++) { - auto encoder = - create_video_encoder(video_encoder_name_buffer, i, go_live_config.encoder_configurations[i]); + auto &config = go_live_config.encoder_configurations[i]; + if (config.canvas_index > max_canvas_idx) { + blog(LOG_ERROR, "MultitrackVideoOutput: Invalid canvas index: %u", config.canvas_index); + throw MultitrackVideoError::warning(QTStr("FailedToStartStream.InvalidEncoderConfig")); + } + + auto &canvas = canvases[config.canvas_index]; + auto encoder = create_video_encoder(video_encoder_name_buffer, i, config, canvas); if (!encoder) return false; @@ -797,14 +832,15 @@ static OBSOutputs SetupOBSOutput(QWidget *parent, const QString &multitrack_vide std::vector &audio_encoders, std::shared_ptr &video_encoder_group, const char *audio_encoder_id, size_t main_audio_mixer, - std::optional vod_track_mixer) + std::optional vod_track_mixer, + const std::vector &canvases) { auto output = create_output(); OBSOutputAutoRelease recording_output; if (dump_stream_to_file_config) recording_output = create_recording_output(dump_stream_to_file_config); - if (!create_video_encoders(go_live_config, video_encoder_group, output, recording_output)) + if (!create_video_encoders(go_live_config, video_encoder_group, output, recording_output, canvases)) return {nullptr, nullptr}; std::vector requested_speaker_layouts; diff --git a/frontend/utility/MultitrackVideoOutput.hpp b/frontend/utility/MultitrackVideoOutput.hpp index e5e6433a0..f66d14c03 100644 --- a/frontend/utility/MultitrackVideoOutput.hpp +++ b/frontend/utility/MultitrackVideoOutput.hpp @@ -27,7 +27,8 @@ public: const char *audio_encoder_id, std::optional maximum_aggregate_bitrate, std::optional maximum_video_tracks, std::optional custom_config, obs_data_t *dump_stream_to_file_config, size_t main_audio_mixer, - std::optional vod_track_mixer, std::optional use_rtmps); + std::optional vod_track_mixer, std::optional use_rtmps, + std::optional extra_canvas); signal_handler_t *StreamingSignalHandler(); void StartedStreaming(); void StopStreaming(); @@ -49,6 +50,7 @@ private: std::vector audio_encoders_; OBSServiceAutoRelease multitrack_video_service_; OBSSignal start_signal, stop_signal; + std::vector canvases; }; std::optional take_current(); diff --git a/frontend/wizards/AutoConfigStreamPage.cpp b/frontend/wizards/AutoConfigStreamPage.cpp index 86d11c3cf..3086185df 100644 --- a/frontend/wizards/AutoConfigStreamPage.cpp +++ b/frontend/wizards/AutoConfigStreamPage.cpp @@ -173,8 +173,10 @@ bool AutoConfigStreamPage::validatePage() wiz->testMultitrackVideo = ui->useMultitrackVideo->isChecked(); if (wiz->testMultitrackVideo) { + std::vector canvases; + canvases.emplace_back(obs_get_main_canvas()); auto postData = constructGoLivePost(QString::fromStdString(wiz->key), std::nullopt, - std::nullopt, false); + std::nullopt, false, canvases); OBSDataAutoRelease service_settings = obs_service_get_settings(service); auto multitrack_video_name = QTStr("Basic.Settings.Stream.MultitrackVideoLabel");