diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index c95632577..3f8238c77 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -115,6 +115,7 @@ MixerToolbarMenu="Audio Mixer Menu" SceneFilters="Open Scene Filters" List="List" Grid="Grid" +Automatic="Automatic" # warning for plugin load failures PluginsFailedToLoad.Title="Plugin Load Error" @@ -224,6 +225,7 @@ Basic.AutoConfig.StreamPage.PreferHardwareEncoding="Prefer hardware encoding" Basic.AutoConfig.StreamPage.PreferHardwareEncoding.ToolTip="Hardware Encoding eliminates most CPU usage, but may require more bitrate to obtain the same level of quality." Basic.AutoConfig.StreamPage.StreamWarning.Title="Stream warning" Basic.AutoConfig.StreamPage.StreamWarning.Text="The bandwidth test is about to stream randomized video data without audio to your channel. If you're able, it's recommended to temporarily turn off saving videos of streams and set the stream to private until after the test has completed. Continue?" +Basic.AutoConfig.StreamPage.UseMultitrackVideo="Test %1" Basic.AutoConfig.TestPage="Final Results" Basic.AutoConfig.TestPage.SubTitle.Testing="The program is now executing a set of tests to estimate the ideal settings" Basic.AutoConfig.TestPage.SubTitle.Complete="Testing complete" @@ -242,6 +244,7 @@ Basic.AutoConfig.TestPage.Result.Header="The program has determined that these e Basic.AutoConfig.TestPage.Result.Footer="To use these settings, click Apply Settings. To reconfigure the wizard and try again, click Back. To manually configure settings yourself, click Cancel and open Settings." Basic.AutoConfig.Info="The auto-configuration wizard will determine the best settings based on your computer specs and internet speed." Basic.AutoConfig.RunAnytime="This can be run at any time by going to the Tools menu." +Basic.AutoConfig.TestPage.Result.StreamingResolution="Streaming (Scaled) Resolution" # stats Basic.Stats="Stats" diff --git a/UI/forms/AutoConfigStreamPage.ui b/UI/forms/AutoConfigStreamPage.ui index b52f4d02a..241292c6d 100644 --- a/UI/forms/AutoConfigStreamPage.ui +++ b/UI/forms/AutoConfigStreamPage.ui @@ -330,7 +330,73 @@ - + + + + PointingHandCursor + + + Basic.AutoConfig.StreamPage.ConnectAccount + + + + + + + Basic.AutoConfig.StreamPage.ConnectedAccount + + + + + + + 7 + + + 7 + + + + + + true + + + + Auth.LoadingChannel.Title + + + + + + + Basic.AutoConfig.StreamPage.DisconnectAccount + + + + + + + + + Qt::Horizontal + + + + 40 + 0 + + + + + + + + Basic.AutoConfig.StreamPage.UseStreamKeyAdvanced + + + + Basic.Settings.Output.VideoBitrate @@ -340,7 +406,7 @@ - + @@ -356,7 +422,7 @@ - + Basic.AutoConfig.StreamPage.PreferHardwareEncoding.ToolTip @@ -369,7 +435,7 @@ - + Basic.AutoConfig.StreamPage.PerformBandwidthTest @@ -379,7 +445,49 @@ - + + + + Basic.AutoConfig.StreamPage.UseMultitrackVideo + + + true + + + + + + + MultitrackVideo.Info + + + Qt::RichText + + + true + + + true + + + QSizePolicy::MinimumExpanding, QSizePolicy::Minimum + + + + + + + Qt::Vertical + + + + 6 + 6 + + + + + BandwidthTest.Region @@ -416,85 +524,6 @@ - - - - PointingHandCursor - - - Basic.AutoConfig.StreamPage.ConnectAccount - - - - - - - Qt::Vertical - - - - 6 - 6 - - - - - - - - 7 - - - 7 - - - - - - true - - - - Auth.LoadingChannel.Title - - - - - - - Basic.AutoConfig.StreamPage.DisconnectAccount - - - - - - - - - Basic.AutoConfig.StreamPage.ConnectedAccount - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Basic.AutoConfig.StreamPage.UseStreamKeyAdvanced - - - diff --git a/UI/window-basic-auto-config-test.cpp b/UI/window-basic-auto-config-test.cpp index 7d7c4278e..904875783 100644 --- a/UI/window-basic-auto-config-test.cpp +++ b/UI/window-basic-auto-config-test.cpp @@ -280,6 +280,34 @@ void AutoConfigTestPage::TestBandwidthThread() servers.resize(2); } + if (!wiz->serviceConfigServers.empty()) { + if (wiz->service == AutoConfig::Service::Twitch && + wiz->twitchAuto) { + // servers from Twitch service config replace the "auto" entry + servers.erase(servers.begin()); + } + + for (auto it = std::rbegin(wiz->serviceConfigServers); + it != std::rend(wiz->serviceConfigServers); it++) { + auto same_server = std::find_if( + std::begin(servers), std::end(servers), + [&](const ServerInfo &si) { + return si.address == it->address; + }); + if (same_server != std::end(servers)) + servers.erase(same_server); + servers.emplace(std::begin(servers), it->name.c_str(), + it->address.c_str()); + } + + if (wiz->service == AutoConfig::Service::Twitch && + wiz->twitchAuto) { + // see above, only test 3 servers + // rtmps urls are currently counted as separate servers + servers.resize(3); + } + } + /* -----------------------------------*/ /* apply service settings */ @@ -287,6 +315,11 @@ void AutoConfigTestPage::TestBandwidthThread() obs_service_apply_encoder_settings(service, vencoder_settings, aencoder_settings); + if (wiz->multitrackVideo.testSuccessful) { + obs_data_set_int(vencoder_settings, "bitrate", + wiz->startingBitrate); + } + /* -----------------------------------*/ /* create output */ @@ -823,6 +856,10 @@ bool AutoConfigTestPage::TestSoftwareEncoding() upperBitrate /= 100; } + if (wiz->testMultitrackVideo && wiz->multitrackVideo.testSuccessful && + !wiz->multitrackVideo.bitrate.has_value()) + wiz->multitrackVideo.bitrate = wiz->idealBitrate; + if (wiz->idealBitrate > upperBitrate) wiz->idealBitrate = upperBitrate; @@ -1080,6 +1117,11 @@ void AutoConfigTestPage::FinalizeResults() OBSDataAutoRelease service_settings = obs_data_create(); OBSDataAutoRelease vencoder_settings = obs_data_create(); + if (wiz->testMultitrackVideo && + wiz->multitrackVideo.testSuccessful && + !wiz->multitrackVideo.bitrate.has_value()) + wiz->multitrackVideo.bitrate = wiz->idealBitrate; + obs_data_set_int(vencoder_settings, "bitrate", wiz->idealBitrate); @@ -1121,12 +1163,30 @@ void AutoConfigTestPage::FinalizeResults() form->addRow(newLabel("Basic.AutoConfig.StreamPage.Server"), new QLabel(wiz->serverName.c_str(), ui->finishPage)); - form->addRow(newLabel("Basic.Settings.Output.VideoBitrate"), - new QLabel(QString::number(wiz->idealBitrate), - ui->finishPage)); - form->addRow(newLabel(TEST_RESULT_SE), - new QLabel(encName(wiz->streamingEncoder), - ui->finishPage)); + form->addRow( + newLabel("Basic.Settings.Stream.MultitrackVideoLabel"), + newLabel(wiz->multitrackVideo.testSuccessful ? "Yes" + : "No")); + + if (wiz->multitrackVideo.testSuccessful) { + form->addRow( + newLabel("Basic.Settings.Output.VideoBitrate"), + newLabel("Automatic")); + form->addRow(newLabel(TEST_RESULT_SE), + newLabel("Automatic")); + form->addRow( + newLabel( + "Basic.AutoConfig.TestPage.Result.StreamingResolution"), + newLabel("Automatic")); + } else { + form->addRow( + newLabel("Basic.Settings.Output.VideoBitrate"), + new QLabel(QString::number(wiz->idealBitrate), + ui->finishPage)); + form->addRow(newLabel(TEST_RESULT_SE), + new QLabel(encName(wiz->streamingEncoder), + ui->finishPage)); + } } QString baseRes = @@ -1168,6 +1228,8 @@ void AutoConfigTestPage::FinalizeResults() new QLabel(scaleRes, ui->finishPage)); form->addRow(newLabel("Basic.Settings.Video.FPS"), new QLabel(fpsStr, ui->finishPage)); + + // FIXME: form layout is super squished, probably need to set proper sizepolicy on all widgets? } #define STARTING_SEPARATOR \ diff --git a/UI/window-basic-auto-config.cpp b/UI/window-basic-auto-config.cpp index e06e65eca..da2ecb0cd 100644 --- a/UI/window-basic-auto-config.cpp +++ b/UI/window-basic-auto-config.cpp @@ -11,6 +11,10 @@ #include "obs-app.hpp" #include "url-push-button.hpp" +#include "goliveapi-postdata.hpp" +#include "goliveapi-network.hpp" +#include "multitrack-video-error.hpp" + #include "ui_AutoConfigStartPage.h" #include "ui_AutoConfigVideoPage.h" #include "ui_AutoConfigStreamPage.h" @@ -262,6 +266,7 @@ AutoConfigStreamPage::AutoConfigStreamPage(QWidget *parent) ui->bitrate->setVisible(false); ui->connectAccount2->setVisible(false); ui->disconnectAccount->setVisible(false); + ui->useMultitrackVideo->setVisible(false); ui->connectedAccountLabel->setVisible(false); ui->connectedAccountText->setVisible(false); @@ -412,6 +417,76 @@ bool AutoConfigStreamPage::validatePage() wiz->service = AutoConfig::Service::Other; } + if (wiz->service == AutoConfig::Service::Twitch) { + wiz->testMultitrackVideo = ui->useMultitrackVideo->isChecked(); + + auto postData = + constructGoLivePost(QString::fromStdString(wiz->key), + std::nullopt, std::nullopt, false); + + OBSDataAutoRelease service_settings = + obs_service_get_settings(service); + auto multitrack_video_name = + QTStr("Basic.Settings.Stream.MultitrackVideoLabel"); + if (obs_data_has_user_value(service_settings, + "multitrack_video_name")) { + multitrack_video_name = obs_data_get_string( + service_settings, "multitrack_video_name"); + } + + try { + auto config = DownloadGoLiveConfig( + this, MultitrackVideoAutoConfigURL(service), + postData, multitrack_video_name); + + for (const auto &endpoint : config.ingest_endpoints) { + if (qstrnicmp("RTMP", endpoint.protocol.c_str(), + 4) != 0) + continue; + + std::string address = endpoint.url_template; + auto pos = address.find("/{stream_key}"); + if (pos != address.npos) + address.erase(pos); + + wiz->serviceConfigServers.push_back( + {address, address}); + } + + int multitrackVideoBitrate = 0; + for (auto &encoder_config : + config.encoder_configurations) { + multitrackVideoBitrate += + encoder_config.config.bitrate; + } + + // grab a streamkey from the go live config if we can + for (auto &endpoint : config.ingest_endpoints) { + const char *p = endpoint.protocol.c_str(); + const char *auth = + endpoint.authentication + ? endpoint.authentication + ->c_str() + : nullptr; + if (qstrnicmp("RTMP", p, 4) == 0 && auth && + *auth) { + wiz->key = auth; + break; + } + } + + if (multitrackVideoBitrate > 0) { + wiz->startingBitrate = multitrackVideoBitrate; + wiz->idealBitrate = multitrackVideoBitrate; + wiz->multitrackVideo.targetBitrate = + multitrackVideoBitrate; + wiz->multitrackVideo.testSuccessful = true; + } + } catch (const MultitrackVideoError & /*err*/) { + // FIXME: do something sensible + } + } + if (wiz->service != AutoConfig::Service::Twitch && wiz->service != AutoConfig::Service::YouTube && wiz->bandwidthTest) { @@ -563,6 +638,22 @@ void AutoConfigStreamPage::on_useStreamKey_clicked() UpdateCompleted(); } +void AutoConfigStreamPage::on_preferHardware_clicked() +{ + auto *main = OBSBasic::Get(); + bool multitrackVideoEnabled = + config_has_user_value(main->Config(), "Stream1", + "EnableMultitrackVideo") + ? config_get_bool(main->Config(), "Stream1", + "EnableMultitrackVideo") + : true; + + ui->useMultitrackVideo->setEnabled(ui->preferHardware->isChecked()); + ui->multitrackVideoInfo->setEnabled(ui->preferHardware->isChecked()); + ui->useMultitrackVideo->setChecked(ui->preferHardware->isChecked() && + multitrackVideoEnabled); +} + static inline bool is_auth_service(const std::string &service) { return Auth::AuthType(service) != Auth::Type::None; @@ -629,6 +720,48 @@ void AutoConfigStreamPage::ServiceChanged() bool testBandwidth = ui->doBandwidthTest->isChecked(); bool custom = IsCustomService(); + bool ertmp_multitrack_video_available = service == "Twitch"; + + bool custom_disclaimer = false; + auto multitrack_video_name = + QTStr("Basic.Settings.Stream.MultitrackVideoLabel"); + if (!custom) { + OBSDataAutoRelease service_settings = obs_data_create(); + obs_data_set_string(service_settings, "service", + service.c_str()); + OBSServiceAutoRelease obs_service = + obs_service_create("rtmp_common", "temp service", + service_settings, nullptr); + + if (obs_data_has_user_value(service_settings, + "multitrack_video_name")) { + multitrack_video_name = obs_data_get_string( + service_settings, "multitrack_video_name"); + } + + if (obs_data_has_user_value(service_settings, + "multitrack_video_disclaimer")) { + ui->multitrackVideoInfo->setText(obs_data_get_string( + service_settings, + "multitrack_video_disclaimer")); + custom_disclaimer = true; + } + } + + if (!custom_disclaimer) { + ui->multitrackVideoInfo->setText( + QTStr("MultitrackVideo.Info") + .arg(multitrack_video_name, service.c_str())); + } + + ui->multitrackVideoInfo->setVisible(ertmp_multitrack_video_available); + ui->useMultitrackVideo->setVisible(ertmp_multitrack_video_available); + ui->useMultitrackVideo->setText( + QTStr("Basic.AutoConfig.StreamPage.UseMultitrackVideo") + .arg(multitrack_video_name)); + ui->multitrackVideoInfo->setEnabled(wiz->hardwareEncodingAvailable); + ui->useMultitrackVideo->setEnabled(wiz->hardwareEncodingAvailable); + reset_service_ui_fields(service); /* Test three closest servers if "Auto" is available for Twitch */ @@ -979,12 +1112,21 @@ AutoConfig::AutoConfig(QWidget *parent) : QWizard(parent) if (!key.empty()) streamPage->ui->key->setText(key.c_str()); + TestHardwareEncoding(); + int bitrate = config_get_int(main->Config(), "SimpleOutput", "VBitrate"); + bool multitrackVideoEnabled = + config_has_user_value(main->Config(), "Stream1", + "EnableMultitrackVideo") + ? config_get_bool(main->Config(), "Stream1", + "EnableMultitrackVideo") + : true; streamPage->ui->bitrate->setValue(bitrate); + streamPage->ui->useMultitrackVideo->setChecked( + hardwareEncodingAvailable && multitrackVideoEnabled); streamPage->ServiceChanged(); - TestHardwareEncoding(); if (!hardwareEncodingAvailable) { delete streamPage->ui->preferHardware; streamPage->ui->preferHardware = nullptr; @@ -1144,6 +1286,33 @@ void AutoConfig::SaveStreamSettings() config_set_string(main->Config(), "SimpleOutput", "StreamEncoder", GetEncoderId(streamingEncoder)); config_remove_value(main->Config(), "SimpleOutput", "UseAdvanced"); + + config_set_bool(main->Config(), "Stream1", "EnableMultitrackVideo", + multitrackVideo.testSuccessful); + + if (multitrackVideo.targetBitrate.has_value()) + config_set_int(main->Config(), "Stream1", + "MultitrackVideoTargetBitrate", + *multitrackVideo.targetBitrate); + else + config_remove_value(main->Config(), "Stream1", + "MultitrackVideoTargetBitrate"); + + if (multitrackVideo.bitrate.has_value() && + multitrackVideo.targetBitrate.has_value() && + (static_cast(*multitrackVideo.bitrate) / + *multitrackVideo.targetBitrate) >= 0.90) { + config_set_bool(main->Config(), "Stream1", + "MultitrackVideoMaximumAggregateBitrateAuto", + true); + } else if (multitrackVideo.bitrate.has_value()) { + config_set_bool(main->Config(), "Stream1", + "MultitrackVideoMaximumAggregateBitrateAuto", + false); + config_set_int(main->Config(), "Stream1", + "MultitrackVideoMaximumAggregateBitrate", + *multitrackVideo.bitrate); + } } void AutoConfig::SaveSettings() diff --git a/UI/window-basic-auto-config.hpp b/UI/window-basic-auto-config.hpp index 5d966c795..2f93c540b 100644 --- a/UI/window-basic-auto-config.hpp +++ b/UI/window-basic-auto-config.hpp @@ -12,6 +12,7 @@ #include #include #include +#include class Ui_AutoConfigStartPage; class Ui_AutoConfigVideoPage; @@ -64,6 +65,11 @@ class AutoConfig : public QWizard { fps60, }; + struct StreamServer { + std::string name; + std::string address; + }; + static inline const char *GetEncoderId(Encoder enc); AutoConfigStreamPage *streamPage = nullptr; @@ -75,6 +81,11 @@ class AutoConfig : public QWizard { Type type = Type::Streaming; FPSType fpsType = FPSType::PreferHighFPS; int idealBitrate = 2500; + struct { + std::optional targetBitrate; + std::optional bitrate; + bool testSuccessful = false; + } multitrackVideo; int baseResolutionCX = 1920; int baseResolutionCY = 1080; int idealResolutionCX = 1280; @@ -84,6 +95,7 @@ class AutoConfig : public QWizard { std::string serviceName; std::string serverName; std::string server; + std::vector serviceConfigServers; std::string key; bool hardwareEncodingAvailable = false; @@ -95,6 +107,7 @@ class AutoConfig : public QWizard { int startingBitrate = 2500; bool customServer = false; bool bandwidthTest = false; + bool testMultitrackVideo = false; bool testRegions = true; bool twitchAuto = false; bool regionUS = true; @@ -195,6 +208,7 @@ public slots: void on_connectAccount_clicked(); void on_disconnectAccount_clicked(); void on_useStreamKey_clicked(); + void on_preferHardware_clicked(); void ServiceChanged(); void UpdateKeyLink(); void UpdateMoreInfoLink();