From e1fade1fc6906ca7f7b27c119f5b7ab43b513add Mon Sep 17 00:00:00 2001 From: PatTheMav Date: Wed, 11 Dec 2024 19:00:06 +0100 Subject: [PATCH] frontend: Split Qt UI autoconfig implementations into single files --- frontend/wizards/AutoConfig.cpp | 899 +-------------- frontend/wizards/AutoConfig.hpp | 164 --- frontend/wizards/AutoConfigStartPage.cpp | 1200 +------------------- frontend/wizards/AutoConfigStartPage.hpp | 264 ----- frontend/wizards/AutoConfigStreamPage.cpp | 573 +--------- frontend/wizards/AutoConfigStreamPage.hpp | 239 +--- frontend/wizards/AutoConfigTestPage.cpp | 97 +- frontend/wizards/AutoConfigVideoPage.cpp | 1128 +------------------ frontend/wizards/AutoConfigVideoPage.hpp | 268 ----- frontend/wizards/TestMode.hpp | 1203 +-------------------- 10 files changed, 63 insertions(+), 5972 deletions(-) diff --git a/frontend/wizards/AutoConfig.cpp b/frontend/wizards/AutoConfig.cpp index 6b1667609..90bfe0f65 100644 --- a/frontend/wizards/AutoConfig.cpp +++ b/frontend/wizards/AutoConfig.cpp @@ -1,46 +1,29 @@ -#include -#include +#include "AutoConfig.hpp" +#include "AutoConfigStartPage.hpp" +#include "AutoConfigStreamPage.hpp" +#include "AutoConfigTestPage.hpp" +#include "AutoConfigVideoPage.hpp" +#include "ui_AutoConfigStartPage.h" +#include "ui_AutoConfigStreamPage.h" +#include "ui_AutoConfigVideoPage.h" + +#ifdef YOUTUBE_ENABLED +#include +#include +#endif +#include -#include #include -#include - -#include "moc_window-basic-auto-config.cpp" -#include "window-basic-main.hpp" -#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" - -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "auth-oauth.hpp" -#include "ui-config.h" -#ifdef YOUTUBE_ENABLED -#include "youtube-api-wrappers.hpp" -#endif - -struct QCef; -struct QCefCookieManager; - -extern QCef *cef; -extern QCefCookieManager *panel_cookies; - -#define wiz reinterpret_cast(wizard()) - -/* ------------------------------------------------------------------------- */ +#include "moc_AutoConfig.cpp" constexpr std::string_view OBSServiceFileName = "service.json"; +enum class ListOpt : int { + ShowAll = 1, + Custom, +}; + static OBSData OpenServiceSettings(std::string &type) { const OBSBasic *basic = reinterpret_cast(App()->GetMainWindow()); @@ -71,848 +54,6 @@ static void GetServiceInfo(std::string &type, std::string &service, std::string key = obs_data_get_string(settings, "key"); } -/* ------------------------------------------------------------------------- */ - -AutoConfigStartPage::AutoConfigStartPage(QWidget *parent) : QWizardPage(parent), ui(new Ui_AutoConfigStartPage) -{ - ui->setupUi(this); - setTitle(QTStr("Basic.AutoConfig.StartPage")); - setSubTitle(QTStr("Basic.AutoConfig.StartPage.SubTitle")); - - OBSBasic *main = OBSBasic::Get(); - if (main->VCamEnabled()) { - QRadioButton *prioritizeVCam = - new QRadioButton(QTStr("Basic.AutoConfig.StartPage.PrioritizeVirtualCam"), this); - QBoxLayout *box = reinterpret_cast(layout()); - box->insertWidget(2, prioritizeVCam); - - connect(prioritizeVCam, &QPushButton::clicked, this, &AutoConfigStartPage::PrioritizeVCam); - } -} - -AutoConfigStartPage::~AutoConfigStartPage() {} - -int AutoConfigStartPage::nextId() const -{ - return wiz->type == AutoConfig::Type::VirtualCam ? AutoConfig::TestPage : AutoConfig::VideoPage; -} - -void AutoConfigStartPage::on_prioritizeStreaming_clicked() -{ - wiz->type = AutoConfig::Type::Streaming; -} - -void AutoConfigStartPage::on_prioritizeRecording_clicked() -{ - wiz->type = AutoConfig::Type::Recording; -} - -void AutoConfigStartPage::PrioritizeVCam() -{ - wiz->type = AutoConfig::Type::VirtualCam; -} - -/* ------------------------------------------------------------------------- */ - -#define RES_TEXT(x) "Basic.AutoConfig.VideoPage." x -#define RES_USE_CURRENT RES_TEXT("BaseResolution.UseCurrent") -#define RES_USE_DISPLAY RES_TEXT("BaseResolution.Display") -#define FPS_USE_CURRENT RES_TEXT("FPS.UseCurrent") -#define FPS_PREFER_HIGH_FPS RES_TEXT("FPS.PreferHighFPS") -#define FPS_PREFER_HIGH_RES RES_TEXT("FPS.PreferHighRes") - -AutoConfigVideoPage::AutoConfigVideoPage(QWidget *parent) : QWizardPage(parent), ui(new Ui_AutoConfigVideoPage) -{ - ui->setupUi(this); - - setTitle(QTStr("Basic.AutoConfig.VideoPage")); - setSubTitle(QTStr("Basic.AutoConfig.VideoPage.SubTitle")); - - obs_video_info ovi; - obs_get_video_info(&ovi); - - long double fpsVal = (long double)ovi.fps_num / (long double)ovi.fps_den; - - QString fpsStr = (ovi.fps_den > 1) ? QString::number(fpsVal, 'f', 2) : QString::number(fpsVal, 'g'); - - ui->fps->addItem(QTStr(FPS_PREFER_HIGH_FPS), (int)AutoConfig::FPSType::PreferHighFPS); - ui->fps->addItem(QTStr(FPS_PREFER_HIGH_RES), (int)AutoConfig::FPSType::PreferHighRes); - ui->fps->addItem(QTStr(FPS_USE_CURRENT).arg(fpsStr), (int)AutoConfig::FPSType::UseCurrent); - ui->fps->addItem(QStringLiteral("30"), (int)AutoConfig::FPSType::fps30); - ui->fps->addItem(QStringLiteral("60"), (int)AutoConfig::FPSType::fps60); - ui->fps->setCurrentIndex(0); - - QString cxStr = QString::number(ovi.base_width); - QString cyStr = QString::number(ovi.base_height); - - int encRes = int(ovi.base_width << 16) | int(ovi.base_height); - - // Auto config only supports testing down to 240p, don't allow current - // resolution if it's lower than that. - if (ovi.base_height >= 240) - ui->canvasRes->addItem(QTStr(RES_USE_CURRENT).arg(cxStr, cyStr), (int)encRes); - - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QSize as = screen->size(); - int as_width = as.width(); - int as_height = as.height(); - - // Calculate physical screen resolution based on the virtual screen resolution - // They might differ if scaling is enabled, e.g. for HiDPI screens - as_width = round(as_width * screen->devicePixelRatio()); - as_height = round(as_height * screen->devicePixelRatio()); - - encRes = as_width << 16 | as_height; - - QString str = - QTStr(RES_USE_DISPLAY) - .arg(QString::number(i + 1), QString::number(as_width), QString::number(as_height)); - - ui->canvasRes->addItem(str, encRes); - } - - auto addRes = [&](int cx, int cy) { - encRes = (cx << 16) | cy; - QString str = QString("%1x%2").arg(QString::number(cx), QString::number(cy)); - ui->canvasRes->addItem(str, encRes); - }; - - addRes(1920, 1080); - addRes(1280, 720); - - ui->canvasRes->setCurrentIndex(0); -} - -AutoConfigVideoPage::~AutoConfigVideoPage() {} - -int AutoConfigVideoPage::nextId() const -{ - return wiz->type == AutoConfig::Type::Recording ? AutoConfig::TestPage : AutoConfig::StreamPage; -} - -bool AutoConfigVideoPage::validatePage() -{ - int encRes = ui->canvasRes->currentData().toInt(); - wiz->baseResolutionCX = encRes >> 16; - wiz->baseResolutionCY = encRes & 0xFFFF; - wiz->fpsType = (AutoConfig::FPSType)ui->fps->currentData().toInt(); - - obs_video_info ovi; - obs_get_video_info(&ovi); - - switch (wiz->fpsType) { - case AutoConfig::FPSType::PreferHighFPS: - wiz->specificFPSNum = 0; - wiz->specificFPSDen = 0; - wiz->preferHighFPS = true; - break; - case AutoConfig::FPSType::PreferHighRes: - wiz->specificFPSNum = 0; - wiz->specificFPSDen = 0; - wiz->preferHighFPS = false; - break; - case AutoConfig::FPSType::UseCurrent: - wiz->specificFPSNum = ovi.fps_num; - wiz->specificFPSDen = ovi.fps_den; - wiz->preferHighFPS = false; - break; - case AutoConfig::FPSType::fps30: - wiz->specificFPSNum = 30; - wiz->specificFPSDen = 1; - wiz->preferHighFPS = false; - break; - case AutoConfig::FPSType::fps60: - wiz->specificFPSNum = 60; - wiz->specificFPSDen = 1; - wiz->preferHighFPS = false; - break; - } - - return true; -} - -/* ------------------------------------------------------------------------- */ - -enum class ListOpt : int { - ShowAll = 1, - Custom, -}; - -AutoConfigStreamPage::AutoConfigStreamPage(QWidget *parent) : QWizardPage(parent), ui(new Ui_AutoConfigStreamPage) -{ - ui->setupUi(this); - ui->bitrateLabel->setVisible(false); - ui->bitrate->setVisible(false); - ui->connectAccount2->setVisible(false); - ui->disconnectAccount->setVisible(false); - ui->useMultitrackVideo->setVisible(false); - - ui->connectedAccountLabel->setVisible(false); - ui->connectedAccountText->setVisible(false); - - int vertSpacing = ui->topLayout->verticalSpacing(); - - QMargins m = ui->topLayout->contentsMargins(); - m.setBottom(vertSpacing / 2); - ui->topLayout->setContentsMargins(m); - - m = ui->loginPageLayout->contentsMargins(); - m.setTop(vertSpacing / 2); - ui->loginPageLayout->setContentsMargins(m); - - m = ui->streamkeyPageLayout->contentsMargins(); - m.setTop(vertSpacing / 2); - ui->streamkeyPageLayout->setContentsMargins(m); - - setTitle(QTStr("Basic.AutoConfig.StreamPage")); - setSubTitle(QTStr("Basic.AutoConfig.StreamPage.SubTitle")); - - LoadServices(false); - - connect(ui->service, &QComboBox::currentIndexChanged, this, &AutoConfigStreamPage::ServiceChanged); - connect(ui->customServer, &QLineEdit::textChanged, this, &AutoConfigStreamPage::ServiceChanged); - connect(ui->customServer, &QLineEdit::textChanged, this, &AutoConfigStreamPage::UpdateKeyLink); - connect(ui->customServer, &QLineEdit::editingFinished, this, &AutoConfigStreamPage::UpdateKeyLink); - connect(ui->doBandwidthTest, &QCheckBox::toggled, this, &AutoConfigStreamPage::ServiceChanged); - - connect(ui->service, &QComboBox::currentIndexChanged, this, &AutoConfigStreamPage::UpdateServerList); - - connect(ui->service, &QComboBox::currentIndexChanged, this, &AutoConfigStreamPage::UpdateKeyLink); - connect(ui->service, &QComboBox::currentIndexChanged, this, &AutoConfigStreamPage::UpdateMoreInfoLink); - - connect(ui->useStreamKeyAdv, &QPushButton::clicked, [&]() { - ui->streamKeyWidget->setVisible(true); - ui->streamKeyLabel->setVisible(true); - ui->useStreamKeyAdv->setVisible(false); - }); - - connect(ui->key, &QLineEdit::textChanged, this, &AutoConfigStreamPage::UpdateCompleted); - connect(ui->regionUS, &QCheckBox::toggled, this, &AutoConfigStreamPage::UpdateCompleted); - connect(ui->regionEU, &QCheckBox::toggled, this, &AutoConfigStreamPage::UpdateCompleted); - connect(ui->regionAsia, &QCheckBox::toggled, this, &AutoConfigStreamPage::UpdateCompleted); - connect(ui->regionOther, &QCheckBox::toggled, this, &AutoConfigStreamPage::UpdateCompleted); -} - -AutoConfigStreamPage::~AutoConfigStreamPage() {} - -bool AutoConfigStreamPage::isComplete() const -{ - return ready; -} - -int AutoConfigStreamPage::nextId() const -{ - return AutoConfig::TestPage; -} - -inline bool AutoConfigStreamPage::IsCustomService() const -{ - return ui->service->currentData().toInt() == (int)ListOpt::Custom; -} - -bool AutoConfigStreamPage::validatePage() -{ - OBSDataAutoRelease service_settings = obs_data_create(); - - wiz->customServer = IsCustomService(); - - const char *serverType = wiz->customServer ? "rtmp_custom" : "rtmp_common"; - - if (!wiz->customServer) { - obs_data_set_string(service_settings, "service", QT_TO_UTF8(ui->service->currentText())); - } - - OBSServiceAutoRelease service = obs_service_create(serverType, "temp_service", service_settings, nullptr); - - int bitrate; - if (!ui->doBandwidthTest->isChecked()) { - bitrate = ui->bitrate->value(); - wiz->idealBitrate = bitrate; - } else { - /* Default test target is 10 Mbps */ - bitrate = 10000; -#ifdef YOUTUBE_ENABLED - if (IsYouTubeService(wiz->serviceName)) { - /* Adjust upper bound to YouTube limits - * for resolutions above 1080p */ - if (wiz->baseResolutionCY > 1440) - bitrate = 51000; - else if (wiz->baseResolutionCY > 1080) - bitrate = 18000; - } -#endif - } - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_int(settings, "bitrate", bitrate); - obs_service_apply_encoder_settings(service, settings, nullptr); - - if (wiz->customServer) { - QString server = ui->customServer->text().trimmed(); - wiz->server = wiz->serverName = QT_TO_UTF8(server); - } else { - wiz->serverName = QT_TO_UTF8(ui->server->currentText()); - wiz->server = QT_TO_UTF8(ui->server->currentData().toString()); - } - - wiz->bandwidthTest = ui->doBandwidthTest->isChecked(); - wiz->startingBitrate = (int)obs_data_get_int(settings, "bitrate"); - wiz->idealBitrate = wiz->startingBitrate; - wiz->regionUS = ui->regionUS->isChecked(); - wiz->regionEU = ui->regionEU->isChecked(); - wiz->regionAsia = ui->regionAsia->isChecked(); - wiz->regionOther = ui->regionOther->isChecked(); - wiz->serviceName = QT_TO_UTF8(ui->service->currentText()); - if (ui->preferHardware) - wiz->preferHardware = ui->preferHardware->isChecked(); - wiz->key = QT_TO_UTF8(ui->key->text()); - - if (!wiz->customServer) { - if (wiz->serviceName == "Twitch") - wiz->service = AutoConfig::Service::Twitch; -#ifdef YOUTUBE_ENABLED - else if (IsYouTubeService(wiz->serviceName)) - wiz->service = AutoConfig::Service::YouTube; -#endif - else if (wiz->serviceName == "Amazon IVS") - wiz->service = AutoConfig::Service::AmazonIVS; - else - wiz->service = AutoConfig::Service::Other; - } else { - wiz->service = AutoConfig::Service::Other; - } - - if (wiz->service == AutoConfig::Service::Twitch) { - wiz->testMultitrackVideo = ui->useMultitrackVideo->isChecked(); - - if (wiz->testMultitrackVideo) { - 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) { - auto it = encoder_config.settings.find("bitrate"); - if (it == encoder_config.settings.end()) - continue; - - if (!it->is_number_integer()) - continue; - - int bitrate = 0; - it->get_to(bitrate); - multitrackVideoBitrate += bitrate; - } - - 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->service != AutoConfig::Service::AmazonIVS && wiz->bandwidthTest) { - QMessageBox::StandardButton button; -#define WARNING_TEXT(x) QTStr("Basic.AutoConfig.StreamPage.StreamWarning." x) - button = OBSMessageBox::question(this, WARNING_TEXT("Title"), WARNING_TEXT("Text")); -#undef WARNING_TEXT - - if (button == QMessageBox::No) - return false; - } - - return true; -} - -void AutoConfigStreamPage::on_show_clicked() -{ - if (ui->key->echoMode() == QLineEdit::Password) { - ui->key->setEchoMode(QLineEdit::Normal); - ui->show->setText(QTStr("Hide")); - } else { - ui->key->setEchoMode(QLineEdit::Password); - ui->show->setText(QTStr("Show")); - } -} - -void AutoConfigStreamPage::OnOAuthStreamKeyConnected() -{ - OAuthStreamKey *a = reinterpret_cast(auth.get()); - - if (a) { - bool validKey = !a->key().empty(); - - if (validKey) - ui->key->setText(QT_UTF8(a->key().c_str())); - - ui->streamKeyWidget->setVisible(false); - ui->streamKeyLabel->setVisible(false); - ui->connectAccount2->setVisible(false); - ui->disconnectAccount->setVisible(true); - ui->useStreamKeyAdv->setVisible(false); - - ui->connectedAccountLabel->setVisible(false); - ui->connectedAccountText->setVisible(false); - -#ifdef YOUTUBE_ENABLED - if (IsYouTubeService(a->service())) { - ui->key->clear(); - - ui->connectedAccountLabel->setVisible(true); - ui->connectedAccountText->setVisible(true); - - ui->connectedAccountText->setText(QTStr("Auth.LoadingChannel.Title")); - - YoutubeApiWrappers *ytAuth = reinterpret_cast(a); - ChannelDescription cd; - if (ytAuth->GetChannelDescription(cd)) { - ui->connectedAccountText->setText(cd.title); - - /* Create throwaway stream key for bandwidth test */ - if (ui->doBandwidthTest->isChecked()) { - StreamDescription stream = {"", "", "OBS Studio Test Stream"}; - if (ytAuth->InsertStream(stream)) { - ui->key->setText(stream.name); - } - } - } - } -#endif - } - - ui->stackedWidget->setCurrentIndex((int)Section::StreamKey); - UpdateCompleted(); -} - -void AutoConfigStreamPage::OnAuthConnected() -{ - std::string service = QT_TO_UTF8(ui->service->currentText()); - Auth::Type type = Auth::AuthType(service); - - if (type == Auth::Type::OAuth_StreamKey || type == Auth::Type::OAuth_LinkedAccount) { - OnOAuthStreamKeyConnected(); - } -} - -void AutoConfigStreamPage::on_connectAccount_clicked() -{ - std::string service = QT_TO_UTF8(ui->service->currentText()); - - OAuth::DeleteCookies(service); - - auth = OAuthStreamKey::Login(this, service); - if (!!auth) { - OnAuthConnected(); - - ui->useStreamKeyAdv->setVisible(false); - } -} - -#define DISCONNECT_COMFIRM_TITLE "Basic.AutoConfig.StreamPage.DisconnectAccount.Confirm.Title" -#define DISCONNECT_COMFIRM_TEXT "Basic.AutoConfig.StreamPage.DisconnectAccount.Confirm.Text" - -void AutoConfigStreamPage::on_disconnectAccount_clicked() -{ - QMessageBox::StandardButton button; - - button = OBSMessageBox::question(this, QTStr(DISCONNECT_COMFIRM_TITLE), QTStr(DISCONNECT_COMFIRM_TEXT)); - - if (button == QMessageBox::No) { - return; - } - - OBSBasic *main = OBSBasic::Get(); - - main->auth.reset(); - auth.reset(); - - std::string service = QT_TO_UTF8(ui->service->currentText()); - -#ifdef BROWSER_AVAILABLE - OAuth::DeleteCookies(service); -#endif - - reset_service_ui_fields(service); - - ui->streamKeyWidget->setVisible(true); - ui->streamKeyLabel->setVisible(true); - ui->key->setText(""); - - ui->connectedAccountLabel->setVisible(false); - ui->connectedAccountText->setVisible(false); - - /* Restore key link when disconnecting account */ - UpdateKeyLink(); -} - -void AutoConfigStreamPage::on_useStreamKey_clicked() -{ - ui->stackedWidget->setCurrentIndex((int)Section::StreamKey); - 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; -} - -static inline bool is_external_oauth(const std::string &service) -{ - return Auth::External(service); -} - -void AutoConfigStreamPage::reset_service_ui_fields(std::string &service) -{ -#ifdef YOUTUBE_ENABLED - // when account is already connected: - OAuthStreamKey *a = reinterpret_cast(auth.get()); - if (a && service == a->service() && IsYouTubeService(a->service())) { - ui->connectedAccountLabel->setVisible(true); - ui->connectedAccountText->setVisible(true); - ui->connectAccount2->setVisible(false); - ui->disconnectAccount->setVisible(true); - return; - } -#endif - - bool external_oauth = is_external_oauth(service); - if (external_oauth) { - ui->streamKeyWidget->setVisible(false); - ui->streamKeyLabel->setVisible(false); - ui->connectAccount2->setVisible(true); - ui->useStreamKeyAdv->setVisible(true); - - ui->stackedWidget->setCurrentIndex((int)Section::StreamKey); - - } else if (cef) { - QString key = ui->key->text(); - bool can_auth = is_auth_service(service); - int page = can_auth && key.isEmpty() ? (int)Section::Connect : (int)Section::StreamKey; - - ui->stackedWidget->setCurrentIndex(page); - ui->streamKeyWidget->setVisible(true); - ui->streamKeyLabel->setVisible(true); - ui->connectAccount2->setVisible(can_auth); - ui->useStreamKeyAdv->setVisible(false); - } else { - ui->connectAccount2->setVisible(false); - ui->useStreamKeyAdv->setVisible(false); - } - - ui->connectedAccountLabel->setVisible(false); - ui->connectedAccountText->setVisible(false); - ui->disconnectAccount->setVisible(false); -} - -void AutoConfigStreamPage::ServiceChanged() -{ - bool showMore = ui->service->currentData().toInt() == (int)ListOpt::ShowAll; - if (showMore) - return; - - std::string service = QT_TO_UTF8(ui->service->currentText()); - bool regionBased = service == "Twitch"; - 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 */ - if ((service == "Twitch" && wiz->twitchAuto) || (service == "Amazon IVS" && wiz->amazonIVSAuto)) - regionBased = false; - - ui->streamkeyPageLayout->removeWidget(ui->serverLabel); - ui->streamkeyPageLayout->removeWidget(ui->serverStackedWidget); - - if (custom) { - ui->streamkeyPageLayout->insertRow(1, ui->serverLabel, ui->serverStackedWidget); - - ui->region->setVisible(false); - ui->serverStackedWidget->setCurrentIndex(1); - ui->serverStackedWidget->setVisible(true); - ui->serverLabel->setVisible(true); - } else { - if (!testBandwidth) - ui->streamkeyPageLayout->insertRow(2, ui->serverLabel, ui->serverStackedWidget); - - ui->region->setVisible(regionBased && testBandwidth); - ui->serverStackedWidget->setCurrentIndex(0); - ui->serverStackedWidget->setHidden(testBandwidth); - ui->serverLabel->setHidden(testBandwidth); - } - - wiz->testRegions = regionBased && testBandwidth; - - ui->bitrateLabel->setHidden(testBandwidth); - ui->bitrate->setHidden(testBandwidth); - - OBSBasic *main = OBSBasic::Get(); - - if (main->auth) { - auto system_auth_service = main->auth->service(); - bool service_check = service.find(system_auth_service) != std::string::npos; -#ifdef YOUTUBE_ENABLED - service_check = service_check ? service_check - : IsYouTubeService(system_auth_service) && IsYouTubeService(service); -#endif - if (service_check) { - auth.reset(); - auth = main->auth; - OnAuthConnected(); - } - } - - UpdateCompleted(); -} - -void AutoConfigStreamPage::UpdateMoreInfoLink() -{ - if (IsCustomService()) { - ui->moreInfoButton->hide(); - return; - } - - QString serviceName = ui->service->currentText(); - obs_properties_t *props = obs_get_service_properties("rtmp_common"); - obs_property_t *services = obs_properties_get(props, "service"); - - OBSDataAutoRelease settings = obs_data_create(); - - obs_data_set_string(settings, "service", QT_TO_UTF8(serviceName)); - obs_property_modified(services, settings); - - const char *more_info_link = obs_data_get_string(settings, "more_info_link"); - - if (!more_info_link || (*more_info_link == '\0')) { - ui->moreInfoButton->hide(); - } else { - ui->moreInfoButton->setTargetUrl(QUrl(more_info_link)); - ui->moreInfoButton->show(); - } - obs_properties_destroy(props); -} - -void AutoConfigStreamPage::UpdateKeyLink() -{ - QString serviceName = ui->service->currentText(); - QString customServer = ui->customServer->text().trimmed(); - QString streamKeyLink; - - obs_properties_t *props = obs_get_service_properties("rtmp_common"); - obs_property_t *services = obs_properties_get(props, "service"); - - OBSDataAutoRelease settings = obs_data_create(); - - obs_data_set_string(settings, "service", QT_TO_UTF8(serviceName)); - obs_property_modified(services, settings); - - streamKeyLink = obs_data_get_string(settings, "stream_key_link"); - - if (customServer.contains("fbcdn.net") && IsCustomService()) { - streamKeyLink = "https://www.facebook.com/live/producer?ref=OBS"; - } - - if (serviceName == "Dacast") { - ui->streamKeyLabel->setText(QTStr("Basic.AutoConfig.StreamPage.EncoderKey")); - ui->streamKeyLabel->setToolTip(""); - } else if (!IsCustomService()) { - ui->streamKeyLabel->setText(QTStr("Basic.AutoConfig.StreamPage.StreamKey")); - ui->streamKeyLabel->setToolTip(""); - } else { - /* add tooltips for stream key */ - QString file = !App()->IsThemeDark() ? ":/res/images/help.svg" : ":/res/images/help_light.svg"; - QString lStr = "%1 "; - - ui->streamKeyLabel->setText(lStr.arg(QTStr("Basic.AutoConfig.StreamPage.StreamKey"), file)); - ui->streamKeyLabel->setToolTip(QTStr("Basic.AutoConfig.StreamPage.StreamKey.ToolTip")); - } - - if (QString(streamKeyLink).isNull() || QString(streamKeyLink).isEmpty()) { - ui->streamKeyButton->hide(); - } else { - ui->streamKeyButton->setTargetUrl(QUrl(streamKeyLink)); - ui->streamKeyButton->show(); - } - obs_properties_destroy(props); -} - -void AutoConfigStreamPage::LoadServices(bool showAll) -{ - obs_properties_t *props = obs_get_service_properties("rtmp_common"); - - OBSDataAutoRelease settings = obs_data_create(); - - obs_data_set_bool(settings, "show_all", showAll); - - obs_property_t *prop = obs_properties_get(props, "show_all"); - obs_property_modified(prop, settings); - - ui->service->blockSignals(true); - ui->service->clear(); - - QStringList names; - - obs_property_t *services = obs_properties_get(props, "service"); - size_t services_count = obs_property_list_item_count(services); - for (size_t i = 0; i < services_count; i++) { - const char *name = obs_property_list_item_string(services, i); - names.push_back(name); - } - - if (showAll) - names.sort(Qt::CaseInsensitive); - - for (QString &name : names) - ui->service->addItem(name); - - if (!showAll) { - ui->service->addItem(QTStr("Basic.AutoConfig.StreamPage.Service.ShowAll"), - QVariant((int)ListOpt::ShowAll)); - } - - ui->service->insertItem(0, QTStr("Basic.AutoConfig.StreamPage.Service.Custom"), QVariant((int)ListOpt::Custom)); - - if (!lastService.isEmpty()) { - int idx = ui->service->findText(lastService); - if (idx != -1) - ui->service->setCurrentIndex(idx); - } - - obs_properties_destroy(props); - - ui->service->blockSignals(false); -} - -void AutoConfigStreamPage::UpdateServerList() -{ - QString serviceName = ui->service->currentText(); - bool showMore = ui->service->currentData().toInt() == (int)ListOpt::ShowAll; - - if (showMore) { - LoadServices(true); - ui->service->showPopup(); - return; - } else { - lastService = serviceName; - } - - obs_properties_t *props = obs_get_service_properties("rtmp_common"); - obs_property_t *services = obs_properties_get(props, "service"); - - OBSDataAutoRelease settings = obs_data_create(); - - obs_data_set_string(settings, "service", QT_TO_UTF8(serviceName)); - obs_property_modified(services, settings); - - obs_property_t *servers = obs_properties_get(props, "server"); - - ui->server->clear(); - - size_t servers_count = obs_property_list_item_count(servers); - for (size_t i = 0; i < servers_count; i++) { - const char *name = obs_property_list_item_name(servers, i); - const char *server = obs_property_list_item_string(servers, i); - ui->server->addItem(name, server); - } - - obs_properties_destroy(props); -} - -void AutoConfigStreamPage::UpdateCompleted() -{ - const bool custom = IsCustomService(); - if (ui->stackedWidget->currentIndex() == (int)Section::Connect || - (ui->key->text().isEmpty() && !auth && !custom)) { - ready = false; - } else { - if (custom) { - ready = !ui->customServer->text().isEmpty(); - } else { - ready = !wiz->testRegions || ui->regionUS->isChecked() || ui->regionEU->isChecked() || - ui->regionAsia->isChecked() || ui->regionOther->isChecked(); - } - } - emit completeChanged(); -} - -/* ------------------------------------------------------------------------- */ - AutoConfig::AutoConfig(QWidget *parent) : QWizard(parent) { EnableThreadedMessageBoxes(true); diff --git a/frontend/wizards/AutoConfig.hpp b/frontend/wizards/AutoConfig.hpp index 7e31bdf89..92fa568f9 100644 --- a/frontend/wizards/AutoConfig.hpp +++ b/frontend/wizards/AutoConfig.hpp @@ -1,26 +1,8 @@ #pragma once #include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -class Ui_AutoConfigStartPage; -class Ui_AutoConfigVideoPage; -class Ui_AutoConfigStreamPage; -class Ui_AutoConfigTestPage; class AutoConfigStreamPage; -class Auth; class AutoConfig : public QWizard { Q_OBJECT @@ -140,149 +122,3 @@ public: TestPage, }; }; - -class AutoConfigStartPage : public QWizardPage { - Q_OBJECT - - friend class AutoConfig; - - std::unique_ptr ui; - -public: - AutoConfigStartPage(QWidget *parent = nullptr); - ~AutoConfigStartPage(); - - virtual int nextId() const override; - -public slots: - void on_prioritizeStreaming_clicked(); - void on_prioritizeRecording_clicked(); - void PrioritizeVCam(); -}; - -class AutoConfigVideoPage : public QWizardPage { - Q_OBJECT - - friend class AutoConfig; - - std::unique_ptr ui; - -public: - AutoConfigVideoPage(QWidget *parent = nullptr); - ~AutoConfigVideoPage(); - - virtual int nextId() const override; - virtual bool validatePage() override; -}; - -class AutoConfigStreamPage : public QWizardPage { - Q_OBJECT - - friend class AutoConfig; - - enum class Section : int { - Connect, - StreamKey, - }; - - std::shared_ptr auth; - - std::unique_ptr ui; - QString lastService; - bool ready = false; - - void LoadServices(bool showAll); - inline bool IsCustomService() const; - -public: - AutoConfigStreamPage(QWidget *parent = nullptr); - ~AutoConfigStreamPage(); - - virtual bool isComplete() const override; - virtual int nextId() const override; - virtual bool validatePage() override; - - void OnAuthConnected(); - void OnOAuthStreamKeyConnected(); - -public slots: - void on_show_clicked(); - void on_connectAccount_clicked(); - void on_disconnectAccount_clicked(); - void on_useStreamKey_clicked(); - void on_preferHardware_clicked(); - void ServiceChanged(); - void UpdateKeyLink(); - void UpdateMoreInfoLink(); - void UpdateServerList(); - void UpdateCompleted(); - - void reset_service_ui_fields(std::string &service); -}; - -class AutoConfigTestPage : public QWizardPage { - Q_OBJECT - - friend class AutoConfig; - - QPointer results; - - std::unique_ptr ui; - std::thread testThread; - std::condition_variable cv; - std::mutex m; - bool cancel = false; - bool started = false; - - enum class Stage { - Starting, - BandwidthTest, - StreamEncoder, - RecordingEncoder, - Finished, - }; - - Stage stage = Stage::Starting; - bool softwareTested = false; - - void StartBandwidthStage(); - void StartStreamEncoderStage(); - void StartRecordingEncoderStage(); - - void FindIdealHardwareResolution(); - bool TestSoftwareEncoding(); - - void TestBandwidthThread(); - void TestStreamEncoderThread(); - void TestRecordingEncoderThread(); - - void FinalizeResults(); - - struct ServerInfo { - std::string name; - std::string address; - int bitrate = 0; - int ms = -1; - - inline ServerInfo() {} - - inline ServerInfo(const char *name_, const char *address_) : name(name_), address(address_) {} - }; - - void GetServers(std::vector &servers); - -public: - AutoConfigTestPage(QWidget *parent = nullptr); - ~AutoConfigTestPage(); - - virtual void initializePage() override; - virtual void cleanupPage() override; - virtual bool isComplete() const override; - virtual int nextId() const override; - -public slots: - void NextStage(); - void UpdateMessage(QString message); - void Failure(QString message); - void Progress(int percentage); -}; diff --git a/frontend/wizards/AutoConfigStartPage.cpp b/frontend/wizards/AutoConfigStartPage.cpp index 6b1667609..706123a6f 100644 --- a/frontend/wizards/AutoConfigStartPage.cpp +++ b/frontend/wizards/AutoConfigStartPage.cpp @@ -1,78 +1,13 @@ -#include -#include - -#include -#include - -#include - -#include "moc_window-basic-auto-config.cpp" -#include "window-basic-main.hpp" -#include "obs-app.hpp" -#include "url-push-button.hpp" - -#include "goliveapi-postdata.hpp" -#include "goliveapi-network.hpp" -#include "multitrack-video-error.hpp" - +#include "AutoConfigStartPage.hpp" +#include "AutoConfig.hpp" #include "ui_AutoConfigStartPage.h" -#include "ui_AutoConfigVideoPage.h" -#include "ui_AutoConfigStreamPage.h" -#ifdef BROWSER_AVAILABLE -#include -#endif +#include -#include "auth-oauth.hpp" -#include "ui-config.h" -#ifdef YOUTUBE_ENABLED -#include "youtube-api-wrappers.hpp" -#endif - -struct QCef; -struct QCefCookieManager; - -extern QCef *cef; -extern QCefCookieManager *panel_cookies; +#include "moc_AutoConfigStartPage.cpp" #define wiz reinterpret_cast(wizard()) -/* ------------------------------------------------------------------------- */ - -constexpr std::string_view OBSServiceFileName = "service.json"; - -static OBSData OpenServiceSettings(std::string &type) -{ - const OBSBasic *basic = reinterpret_cast(App()->GetMainWindow()); - const OBSProfile ¤tProfile = basic->GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - if (!std::filesystem::exists(jsonFilePath)) { - return OBSData(); - } - - OBSDataAutoRelease data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - - return settings.Get(); -} - -static void GetServiceInfo(std::string &type, std::string &service, std::string &server, std::string &key) -{ - OBSData settings = OpenServiceSettings(type); - - service = obs_data_get_string(settings, "service"); - server = obs_data_get_string(settings, "server"); - key = obs_data_get_string(settings, "key"); -} - -/* ------------------------------------------------------------------------- */ - AutoConfigStartPage::AutoConfigStartPage(QWidget *parent) : QWizardPage(parent), ui(new Ui_AutoConfigStartPage) { ui->setupUi(this); @@ -111,1130 +46,3 @@ void AutoConfigStartPage::PrioritizeVCam() { wiz->type = AutoConfig::Type::VirtualCam; } - -/* ------------------------------------------------------------------------- */ - -#define RES_TEXT(x) "Basic.AutoConfig.VideoPage." x -#define RES_USE_CURRENT RES_TEXT("BaseResolution.UseCurrent") -#define RES_USE_DISPLAY RES_TEXT("BaseResolution.Display") -#define FPS_USE_CURRENT RES_TEXT("FPS.UseCurrent") -#define FPS_PREFER_HIGH_FPS RES_TEXT("FPS.PreferHighFPS") -#define FPS_PREFER_HIGH_RES RES_TEXT("FPS.PreferHighRes") - -AutoConfigVideoPage::AutoConfigVideoPage(QWidget *parent) : QWizardPage(parent), ui(new Ui_AutoConfigVideoPage) -{ - ui->setupUi(this); - - setTitle(QTStr("Basic.AutoConfig.VideoPage")); - setSubTitle(QTStr("Basic.AutoConfig.VideoPage.SubTitle")); - - obs_video_info ovi; - obs_get_video_info(&ovi); - - long double fpsVal = (long double)ovi.fps_num / (long double)ovi.fps_den; - - QString fpsStr = (ovi.fps_den > 1) ? QString::number(fpsVal, 'f', 2) : QString::number(fpsVal, 'g'); - - ui->fps->addItem(QTStr(FPS_PREFER_HIGH_FPS), (int)AutoConfig::FPSType::PreferHighFPS); - ui->fps->addItem(QTStr(FPS_PREFER_HIGH_RES), (int)AutoConfig::FPSType::PreferHighRes); - ui->fps->addItem(QTStr(FPS_USE_CURRENT).arg(fpsStr), (int)AutoConfig::FPSType::UseCurrent); - ui->fps->addItem(QStringLiteral("30"), (int)AutoConfig::FPSType::fps30); - ui->fps->addItem(QStringLiteral("60"), (int)AutoConfig::FPSType::fps60); - ui->fps->setCurrentIndex(0); - - QString cxStr = QString::number(ovi.base_width); - QString cyStr = QString::number(ovi.base_height); - - int encRes = int(ovi.base_width << 16) | int(ovi.base_height); - - // Auto config only supports testing down to 240p, don't allow current - // resolution if it's lower than that. - if (ovi.base_height >= 240) - ui->canvasRes->addItem(QTStr(RES_USE_CURRENT).arg(cxStr, cyStr), (int)encRes); - - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QSize as = screen->size(); - int as_width = as.width(); - int as_height = as.height(); - - // Calculate physical screen resolution based on the virtual screen resolution - // They might differ if scaling is enabled, e.g. for HiDPI screens - as_width = round(as_width * screen->devicePixelRatio()); - as_height = round(as_height * screen->devicePixelRatio()); - - encRes = as_width << 16 | as_height; - - QString str = - QTStr(RES_USE_DISPLAY) - .arg(QString::number(i + 1), QString::number(as_width), QString::number(as_height)); - - ui->canvasRes->addItem(str, encRes); - } - - auto addRes = [&](int cx, int cy) { - encRes = (cx << 16) | cy; - QString str = QString("%1x%2").arg(QString::number(cx), QString::number(cy)); - ui->canvasRes->addItem(str, encRes); - }; - - addRes(1920, 1080); - addRes(1280, 720); - - ui->canvasRes->setCurrentIndex(0); -} - -AutoConfigVideoPage::~AutoConfigVideoPage() {} - -int AutoConfigVideoPage::nextId() const -{ - return wiz->type == AutoConfig::Type::Recording ? AutoConfig::TestPage : AutoConfig::StreamPage; -} - -bool AutoConfigVideoPage::validatePage() -{ - int encRes = ui->canvasRes->currentData().toInt(); - wiz->baseResolutionCX = encRes >> 16; - wiz->baseResolutionCY = encRes & 0xFFFF; - wiz->fpsType = (AutoConfig::FPSType)ui->fps->currentData().toInt(); - - obs_video_info ovi; - obs_get_video_info(&ovi); - - switch (wiz->fpsType) { - case AutoConfig::FPSType::PreferHighFPS: - wiz->specificFPSNum = 0; - wiz->specificFPSDen = 0; - wiz->preferHighFPS = true; - break; - case AutoConfig::FPSType::PreferHighRes: - wiz->specificFPSNum = 0; - wiz->specificFPSDen = 0; - wiz->preferHighFPS = false; - break; - case AutoConfig::FPSType::UseCurrent: - wiz->specificFPSNum = ovi.fps_num; - wiz->specificFPSDen = ovi.fps_den; - wiz->preferHighFPS = false; - break; - case AutoConfig::FPSType::fps30: - wiz->specificFPSNum = 30; - wiz->specificFPSDen = 1; - wiz->preferHighFPS = false; - break; - case AutoConfig::FPSType::fps60: - wiz->specificFPSNum = 60; - wiz->specificFPSDen = 1; - wiz->preferHighFPS = false; - break; - } - - return true; -} - -/* ------------------------------------------------------------------------- */ - -enum class ListOpt : int { - ShowAll = 1, - Custom, -}; - -AutoConfigStreamPage::AutoConfigStreamPage(QWidget *parent) : QWizardPage(parent), ui(new Ui_AutoConfigStreamPage) -{ - ui->setupUi(this); - ui->bitrateLabel->setVisible(false); - ui->bitrate->setVisible(false); - ui->connectAccount2->setVisible(false); - ui->disconnectAccount->setVisible(false); - ui->useMultitrackVideo->setVisible(false); - - ui->connectedAccountLabel->setVisible(false); - ui->connectedAccountText->setVisible(false); - - int vertSpacing = ui->topLayout->verticalSpacing(); - - QMargins m = ui->topLayout->contentsMargins(); - m.setBottom(vertSpacing / 2); - ui->topLayout->setContentsMargins(m); - - m = ui->loginPageLayout->contentsMargins(); - m.setTop(vertSpacing / 2); - ui->loginPageLayout->setContentsMargins(m); - - m = ui->streamkeyPageLayout->contentsMargins(); - m.setTop(vertSpacing / 2); - ui->streamkeyPageLayout->setContentsMargins(m); - - setTitle(QTStr("Basic.AutoConfig.StreamPage")); - setSubTitle(QTStr("Basic.AutoConfig.StreamPage.SubTitle")); - - LoadServices(false); - - connect(ui->service, &QComboBox::currentIndexChanged, this, &AutoConfigStreamPage::ServiceChanged); - connect(ui->customServer, &QLineEdit::textChanged, this, &AutoConfigStreamPage::ServiceChanged); - connect(ui->customServer, &QLineEdit::textChanged, this, &AutoConfigStreamPage::UpdateKeyLink); - connect(ui->customServer, &QLineEdit::editingFinished, this, &AutoConfigStreamPage::UpdateKeyLink); - connect(ui->doBandwidthTest, &QCheckBox::toggled, this, &AutoConfigStreamPage::ServiceChanged); - - connect(ui->service, &QComboBox::currentIndexChanged, this, &AutoConfigStreamPage::UpdateServerList); - - connect(ui->service, &QComboBox::currentIndexChanged, this, &AutoConfigStreamPage::UpdateKeyLink); - connect(ui->service, &QComboBox::currentIndexChanged, this, &AutoConfigStreamPage::UpdateMoreInfoLink); - - connect(ui->useStreamKeyAdv, &QPushButton::clicked, [&]() { - ui->streamKeyWidget->setVisible(true); - ui->streamKeyLabel->setVisible(true); - ui->useStreamKeyAdv->setVisible(false); - }); - - connect(ui->key, &QLineEdit::textChanged, this, &AutoConfigStreamPage::UpdateCompleted); - connect(ui->regionUS, &QCheckBox::toggled, this, &AutoConfigStreamPage::UpdateCompleted); - connect(ui->regionEU, &QCheckBox::toggled, this, &AutoConfigStreamPage::UpdateCompleted); - connect(ui->regionAsia, &QCheckBox::toggled, this, &AutoConfigStreamPage::UpdateCompleted); - connect(ui->regionOther, &QCheckBox::toggled, this, &AutoConfigStreamPage::UpdateCompleted); -} - -AutoConfigStreamPage::~AutoConfigStreamPage() {} - -bool AutoConfigStreamPage::isComplete() const -{ - return ready; -} - -int AutoConfigStreamPage::nextId() const -{ - return AutoConfig::TestPage; -} - -inline bool AutoConfigStreamPage::IsCustomService() const -{ - return ui->service->currentData().toInt() == (int)ListOpt::Custom; -} - -bool AutoConfigStreamPage::validatePage() -{ - OBSDataAutoRelease service_settings = obs_data_create(); - - wiz->customServer = IsCustomService(); - - const char *serverType = wiz->customServer ? "rtmp_custom" : "rtmp_common"; - - if (!wiz->customServer) { - obs_data_set_string(service_settings, "service", QT_TO_UTF8(ui->service->currentText())); - } - - OBSServiceAutoRelease service = obs_service_create(serverType, "temp_service", service_settings, nullptr); - - int bitrate; - if (!ui->doBandwidthTest->isChecked()) { - bitrate = ui->bitrate->value(); - wiz->idealBitrate = bitrate; - } else { - /* Default test target is 10 Mbps */ - bitrate = 10000; -#ifdef YOUTUBE_ENABLED - if (IsYouTubeService(wiz->serviceName)) { - /* Adjust upper bound to YouTube limits - * for resolutions above 1080p */ - if (wiz->baseResolutionCY > 1440) - bitrate = 51000; - else if (wiz->baseResolutionCY > 1080) - bitrate = 18000; - } -#endif - } - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_int(settings, "bitrate", bitrate); - obs_service_apply_encoder_settings(service, settings, nullptr); - - if (wiz->customServer) { - QString server = ui->customServer->text().trimmed(); - wiz->server = wiz->serverName = QT_TO_UTF8(server); - } else { - wiz->serverName = QT_TO_UTF8(ui->server->currentText()); - wiz->server = QT_TO_UTF8(ui->server->currentData().toString()); - } - - wiz->bandwidthTest = ui->doBandwidthTest->isChecked(); - wiz->startingBitrate = (int)obs_data_get_int(settings, "bitrate"); - wiz->idealBitrate = wiz->startingBitrate; - wiz->regionUS = ui->regionUS->isChecked(); - wiz->regionEU = ui->regionEU->isChecked(); - wiz->regionAsia = ui->regionAsia->isChecked(); - wiz->regionOther = ui->regionOther->isChecked(); - wiz->serviceName = QT_TO_UTF8(ui->service->currentText()); - if (ui->preferHardware) - wiz->preferHardware = ui->preferHardware->isChecked(); - wiz->key = QT_TO_UTF8(ui->key->text()); - - if (!wiz->customServer) { - if (wiz->serviceName == "Twitch") - wiz->service = AutoConfig::Service::Twitch; -#ifdef YOUTUBE_ENABLED - else if (IsYouTubeService(wiz->serviceName)) - wiz->service = AutoConfig::Service::YouTube; -#endif - else if (wiz->serviceName == "Amazon IVS") - wiz->service = AutoConfig::Service::AmazonIVS; - else - wiz->service = AutoConfig::Service::Other; - } else { - wiz->service = AutoConfig::Service::Other; - } - - if (wiz->service == AutoConfig::Service::Twitch) { - wiz->testMultitrackVideo = ui->useMultitrackVideo->isChecked(); - - if (wiz->testMultitrackVideo) { - 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) { - auto it = encoder_config.settings.find("bitrate"); - if (it == encoder_config.settings.end()) - continue; - - if (!it->is_number_integer()) - continue; - - int bitrate = 0; - it->get_to(bitrate); - multitrackVideoBitrate += bitrate; - } - - 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->service != AutoConfig::Service::AmazonIVS && wiz->bandwidthTest) { - QMessageBox::StandardButton button; -#define WARNING_TEXT(x) QTStr("Basic.AutoConfig.StreamPage.StreamWarning." x) - button = OBSMessageBox::question(this, WARNING_TEXT("Title"), WARNING_TEXT("Text")); -#undef WARNING_TEXT - - if (button == QMessageBox::No) - return false; - } - - return true; -} - -void AutoConfigStreamPage::on_show_clicked() -{ - if (ui->key->echoMode() == QLineEdit::Password) { - ui->key->setEchoMode(QLineEdit::Normal); - ui->show->setText(QTStr("Hide")); - } else { - ui->key->setEchoMode(QLineEdit::Password); - ui->show->setText(QTStr("Show")); - } -} - -void AutoConfigStreamPage::OnOAuthStreamKeyConnected() -{ - OAuthStreamKey *a = reinterpret_cast(auth.get()); - - if (a) { - bool validKey = !a->key().empty(); - - if (validKey) - ui->key->setText(QT_UTF8(a->key().c_str())); - - ui->streamKeyWidget->setVisible(false); - ui->streamKeyLabel->setVisible(false); - ui->connectAccount2->setVisible(false); - ui->disconnectAccount->setVisible(true); - ui->useStreamKeyAdv->setVisible(false); - - ui->connectedAccountLabel->setVisible(false); - ui->connectedAccountText->setVisible(false); - -#ifdef YOUTUBE_ENABLED - if (IsYouTubeService(a->service())) { - ui->key->clear(); - - ui->connectedAccountLabel->setVisible(true); - ui->connectedAccountText->setVisible(true); - - ui->connectedAccountText->setText(QTStr("Auth.LoadingChannel.Title")); - - YoutubeApiWrappers *ytAuth = reinterpret_cast(a); - ChannelDescription cd; - if (ytAuth->GetChannelDescription(cd)) { - ui->connectedAccountText->setText(cd.title); - - /* Create throwaway stream key for bandwidth test */ - if (ui->doBandwidthTest->isChecked()) { - StreamDescription stream = {"", "", "OBS Studio Test Stream"}; - if (ytAuth->InsertStream(stream)) { - ui->key->setText(stream.name); - } - } - } - } -#endif - } - - ui->stackedWidget->setCurrentIndex((int)Section::StreamKey); - UpdateCompleted(); -} - -void AutoConfigStreamPage::OnAuthConnected() -{ - std::string service = QT_TO_UTF8(ui->service->currentText()); - Auth::Type type = Auth::AuthType(service); - - if (type == Auth::Type::OAuth_StreamKey || type == Auth::Type::OAuth_LinkedAccount) { - OnOAuthStreamKeyConnected(); - } -} - -void AutoConfigStreamPage::on_connectAccount_clicked() -{ - std::string service = QT_TO_UTF8(ui->service->currentText()); - - OAuth::DeleteCookies(service); - - auth = OAuthStreamKey::Login(this, service); - if (!!auth) { - OnAuthConnected(); - - ui->useStreamKeyAdv->setVisible(false); - } -} - -#define DISCONNECT_COMFIRM_TITLE "Basic.AutoConfig.StreamPage.DisconnectAccount.Confirm.Title" -#define DISCONNECT_COMFIRM_TEXT "Basic.AutoConfig.StreamPage.DisconnectAccount.Confirm.Text" - -void AutoConfigStreamPage::on_disconnectAccount_clicked() -{ - QMessageBox::StandardButton button; - - button = OBSMessageBox::question(this, QTStr(DISCONNECT_COMFIRM_TITLE), QTStr(DISCONNECT_COMFIRM_TEXT)); - - if (button == QMessageBox::No) { - return; - } - - OBSBasic *main = OBSBasic::Get(); - - main->auth.reset(); - auth.reset(); - - std::string service = QT_TO_UTF8(ui->service->currentText()); - -#ifdef BROWSER_AVAILABLE - OAuth::DeleteCookies(service); -#endif - - reset_service_ui_fields(service); - - ui->streamKeyWidget->setVisible(true); - ui->streamKeyLabel->setVisible(true); - ui->key->setText(""); - - ui->connectedAccountLabel->setVisible(false); - ui->connectedAccountText->setVisible(false); - - /* Restore key link when disconnecting account */ - UpdateKeyLink(); -} - -void AutoConfigStreamPage::on_useStreamKey_clicked() -{ - ui->stackedWidget->setCurrentIndex((int)Section::StreamKey); - 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; -} - -static inline bool is_external_oauth(const std::string &service) -{ - return Auth::External(service); -} - -void AutoConfigStreamPage::reset_service_ui_fields(std::string &service) -{ -#ifdef YOUTUBE_ENABLED - // when account is already connected: - OAuthStreamKey *a = reinterpret_cast(auth.get()); - if (a && service == a->service() && IsYouTubeService(a->service())) { - ui->connectedAccountLabel->setVisible(true); - ui->connectedAccountText->setVisible(true); - ui->connectAccount2->setVisible(false); - ui->disconnectAccount->setVisible(true); - return; - } -#endif - - bool external_oauth = is_external_oauth(service); - if (external_oauth) { - ui->streamKeyWidget->setVisible(false); - ui->streamKeyLabel->setVisible(false); - ui->connectAccount2->setVisible(true); - ui->useStreamKeyAdv->setVisible(true); - - ui->stackedWidget->setCurrentIndex((int)Section::StreamKey); - - } else if (cef) { - QString key = ui->key->text(); - bool can_auth = is_auth_service(service); - int page = can_auth && key.isEmpty() ? (int)Section::Connect : (int)Section::StreamKey; - - ui->stackedWidget->setCurrentIndex(page); - ui->streamKeyWidget->setVisible(true); - ui->streamKeyLabel->setVisible(true); - ui->connectAccount2->setVisible(can_auth); - ui->useStreamKeyAdv->setVisible(false); - } else { - ui->connectAccount2->setVisible(false); - ui->useStreamKeyAdv->setVisible(false); - } - - ui->connectedAccountLabel->setVisible(false); - ui->connectedAccountText->setVisible(false); - ui->disconnectAccount->setVisible(false); -} - -void AutoConfigStreamPage::ServiceChanged() -{ - bool showMore = ui->service->currentData().toInt() == (int)ListOpt::ShowAll; - if (showMore) - return; - - std::string service = QT_TO_UTF8(ui->service->currentText()); - bool regionBased = service == "Twitch"; - 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 */ - if ((service == "Twitch" && wiz->twitchAuto) || (service == "Amazon IVS" && wiz->amazonIVSAuto)) - regionBased = false; - - ui->streamkeyPageLayout->removeWidget(ui->serverLabel); - ui->streamkeyPageLayout->removeWidget(ui->serverStackedWidget); - - if (custom) { - ui->streamkeyPageLayout->insertRow(1, ui->serverLabel, ui->serverStackedWidget); - - ui->region->setVisible(false); - ui->serverStackedWidget->setCurrentIndex(1); - ui->serverStackedWidget->setVisible(true); - ui->serverLabel->setVisible(true); - } else { - if (!testBandwidth) - ui->streamkeyPageLayout->insertRow(2, ui->serverLabel, ui->serverStackedWidget); - - ui->region->setVisible(regionBased && testBandwidth); - ui->serverStackedWidget->setCurrentIndex(0); - ui->serverStackedWidget->setHidden(testBandwidth); - ui->serverLabel->setHidden(testBandwidth); - } - - wiz->testRegions = regionBased && testBandwidth; - - ui->bitrateLabel->setHidden(testBandwidth); - ui->bitrate->setHidden(testBandwidth); - - OBSBasic *main = OBSBasic::Get(); - - if (main->auth) { - auto system_auth_service = main->auth->service(); - bool service_check = service.find(system_auth_service) != std::string::npos; -#ifdef YOUTUBE_ENABLED - service_check = service_check ? service_check - : IsYouTubeService(system_auth_service) && IsYouTubeService(service); -#endif - if (service_check) { - auth.reset(); - auth = main->auth; - OnAuthConnected(); - } - } - - UpdateCompleted(); -} - -void AutoConfigStreamPage::UpdateMoreInfoLink() -{ - if (IsCustomService()) { - ui->moreInfoButton->hide(); - return; - } - - QString serviceName = ui->service->currentText(); - obs_properties_t *props = obs_get_service_properties("rtmp_common"); - obs_property_t *services = obs_properties_get(props, "service"); - - OBSDataAutoRelease settings = obs_data_create(); - - obs_data_set_string(settings, "service", QT_TO_UTF8(serviceName)); - obs_property_modified(services, settings); - - const char *more_info_link = obs_data_get_string(settings, "more_info_link"); - - if (!more_info_link || (*more_info_link == '\0')) { - ui->moreInfoButton->hide(); - } else { - ui->moreInfoButton->setTargetUrl(QUrl(more_info_link)); - ui->moreInfoButton->show(); - } - obs_properties_destroy(props); -} - -void AutoConfigStreamPage::UpdateKeyLink() -{ - QString serviceName = ui->service->currentText(); - QString customServer = ui->customServer->text().trimmed(); - QString streamKeyLink; - - obs_properties_t *props = obs_get_service_properties("rtmp_common"); - obs_property_t *services = obs_properties_get(props, "service"); - - OBSDataAutoRelease settings = obs_data_create(); - - obs_data_set_string(settings, "service", QT_TO_UTF8(serviceName)); - obs_property_modified(services, settings); - - streamKeyLink = obs_data_get_string(settings, "stream_key_link"); - - if (customServer.contains("fbcdn.net") && IsCustomService()) { - streamKeyLink = "https://www.facebook.com/live/producer?ref=OBS"; - } - - if (serviceName == "Dacast") { - ui->streamKeyLabel->setText(QTStr("Basic.AutoConfig.StreamPage.EncoderKey")); - ui->streamKeyLabel->setToolTip(""); - } else if (!IsCustomService()) { - ui->streamKeyLabel->setText(QTStr("Basic.AutoConfig.StreamPage.StreamKey")); - ui->streamKeyLabel->setToolTip(""); - } else { - /* add tooltips for stream key */ - QString file = !App()->IsThemeDark() ? ":/res/images/help.svg" : ":/res/images/help_light.svg"; - QString lStr = "%1 "; - - ui->streamKeyLabel->setText(lStr.arg(QTStr("Basic.AutoConfig.StreamPage.StreamKey"), file)); - ui->streamKeyLabel->setToolTip(QTStr("Basic.AutoConfig.StreamPage.StreamKey.ToolTip")); - } - - if (QString(streamKeyLink).isNull() || QString(streamKeyLink).isEmpty()) { - ui->streamKeyButton->hide(); - } else { - ui->streamKeyButton->setTargetUrl(QUrl(streamKeyLink)); - ui->streamKeyButton->show(); - } - obs_properties_destroy(props); -} - -void AutoConfigStreamPage::LoadServices(bool showAll) -{ - obs_properties_t *props = obs_get_service_properties("rtmp_common"); - - OBSDataAutoRelease settings = obs_data_create(); - - obs_data_set_bool(settings, "show_all", showAll); - - obs_property_t *prop = obs_properties_get(props, "show_all"); - obs_property_modified(prop, settings); - - ui->service->blockSignals(true); - ui->service->clear(); - - QStringList names; - - obs_property_t *services = obs_properties_get(props, "service"); - size_t services_count = obs_property_list_item_count(services); - for (size_t i = 0; i < services_count; i++) { - const char *name = obs_property_list_item_string(services, i); - names.push_back(name); - } - - if (showAll) - names.sort(Qt::CaseInsensitive); - - for (QString &name : names) - ui->service->addItem(name); - - if (!showAll) { - ui->service->addItem(QTStr("Basic.AutoConfig.StreamPage.Service.ShowAll"), - QVariant((int)ListOpt::ShowAll)); - } - - ui->service->insertItem(0, QTStr("Basic.AutoConfig.StreamPage.Service.Custom"), QVariant((int)ListOpt::Custom)); - - if (!lastService.isEmpty()) { - int idx = ui->service->findText(lastService); - if (idx != -1) - ui->service->setCurrentIndex(idx); - } - - obs_properties_destroy(props); - - ui->service->blockSignals(false); -} - -void AutoConfigStreamPage::UpdateServerList() -{ - QString serviceName = ui->service->currentText(); - bool showMore = ui->service->currentData().toInt() == (int)ListOpt::ShowAll; - - if (showMore) { - LoadServices(true); - ui->service->showPopup(); - return; - } else { - lastService = serviceName; - } - - obs_properties_t *props = obs_get_service_properties("rtmp_common"); - obs_property_t *services = obs_properties_get(props, "service"); - - OBSDataAutoRelease settings = obs_data_create(); - - obs_data_set_string(settings, "service", QT_TO_UTF8(serviceName)); - obs_property_modified(services, settings); - - obs_property_t *servers = obs_properties_get(props, "server"); - - ui->server->clear(); - - size_t servers_count = obs_property_list_item_count(servers); - for (size_t i = 0; i < servers_count; i++) { - const char *name = obs_property_list_item_name(servers, i); - const char *server = obs_property_list_item_string(servers, i); - ui->server->addItem(name, server); - } - - obs_properties_destroy(props); -} - -void AutoConfigStreamPage::UpdateCompleted() -{ - const bool custom = IsCustomService(); - if (ui->stackedWidget->currentIndex() == (int)Section::Connect || - (ui->key->text().isEmpty() && !auth && !custom)) { - ready = false; - } else { - if (custom) { - ready = !ui->customServer->text().isEmpty(); - } else { - ready = !wiz->testRegions || ui->regionUS->isChecked() || ui->regionEU->isChecked() || - ui->regionAsia->isChecked() || ui->regionOther->isChecked(); - } - } - emit completeChanged(); -} - -/* ------------------------------------------------------------------------- */ - -AutoConfig::AutoConfig(QWidget *parent) : QWizard(parent) -{ - EnableThreadedMessageBoxes(true); - - calldata_t cd = {0}; - calldata_set_int(&cd, "seconds", 5); - - proc_handler_t *ph = obs_get_proc_handler(); - proc_handler_call(ph, "twitch_ingests_refresh", &cd); - proc_handler_call(ph, "amazon_ivs_ingests_refresh", &cd); - calldata_free(&cd); - - OBSBasic *main = reinterpret_cast(parent); - main->EnableOutputs(false); - - installEventFilter(CreateShortcutFilter()); - - std::string serviceType; - GetServiceInfo(serviceType, serviceName, server, key); -#if defined(_WIN32) || defined(__APPLE__) - setWizardStyle(QWizard::ModernStyle); -#endif - streamPage = new AutoConfigStreamPage(); - - setPage(StartPage, new AutoConfigStartPage()); - setPage(VideoPage, new AutoConfigVideoPage()); - setPage(StreamPage, streamPage); - setPage(TestPage, new AutoConfigTestPage()); - setWindowTitle(QTStr("Basic.AutoConfig")); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - - obs_video_info ovi; - obs_get_video_info(&ovi); - - baseResolutionCX = ovi.base_width; - baseResolutionCY = ovi.base_height; - - /* ----------------------------------------- */ - /* check to see if Twitch's "auto" available */ - - OBSDataAutoRelease twitchSettings = obs_data_create(); - - obs_data_set_string(twitchSettings, "service", "Twitch"); - - obs_properties_t *props = obs_get_service_properties("rtmp_common"); - obs_properties_apply_settings(props, twitchSettings); - - obs_property_t *p = obs_properties_get(props, "server"); - const char *first = obs_property_list_item_string(p, 0); - twitchAuto = strcmp(first, "auto") == 0; - - obs_properties_destroy(props); - - /* ----------------------------------------- */ - /* check to see if Amazon IVS "auto" entries are available */ - - OBSDataAutoRelease amazonIVSSettings = obs_data_create(); - - obs_data_set_string(amazonIVSSettings, "service", "Amazon IVS"); - - props = obs_get_service_properties("rtmp_common"); - obs_properties_apply_settings(props, amazonIVSSettings); - - p = obs_properties_get(props, "server"); - first = obs_property_list_item_string(p, 0); - amazonIVSAuto = strncmp(first, "auto", 4) == 0; - - obs_properties_destroy(props); - - /* ----------------------------------------- */ - /* load service/servers */ - - customServer = serviceType == "rtmp_custom"; - - QComboBox *serviceList = streamPage->ui->service; - - if (!serviceName.empty()) { - serviceList->blockSignals(true); - - int count = serviceList->count(); - bool found = false; - - for (int i = 0; i < count; i++) { - QString name = serviceList->itemText(i); - - if (name == serviceName.c_str()) { - serviceList->setCurrentIndex(i); - found = true; - break; - } - } - - if (!found) { - serviceList->insertItem(0, serviceName.c_str()); - serviceList->setCurrentIndex(0); - } - - serviceList->blockSignals(false); - } - - streamPage->UpdateServerList(); - streamPage->UpdateKeyLink(); - streamPage->UpdateMoreInfoLink(); - streamPage->lastService.clear(); - - if (!customServer) { - QComboBox *serverList = streamPage->ui->server; - int idx = serverList->findData(QString(server.c_str())); - if (idx == -1) - idx = 0; - - serverList->setCurrentIndex(idx); - } else { - streamPage->ui->customServer->setText(server.c_str()); - int idx = streamPage->ui->service->findData(QVariant((int)ListOpt::Custom)); - streamPage->ui->service->setCurrentIndex(idx); - } - - 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(); - - if (!hardwareEncodingAvailable) { - delete streamPage->ui->preferHardware; - streamPage->ui->preferHardware = nullptr; - } else { - /* Newer generations of NVENC have a high enough quality to - * bitrate ratio that if NVENC is available, it makes sense to - * just always prefer hardware encoding by default */ - bool preferHardware = nvencAvailable || appleAvailable || os_get_physical_cores() <= 4; - streamPage->ui->preferHardware->setChecked(preferHardware); - } - - setOptions(QWizard::WizardOptions()); - setButtonText(QWizard::FinishButton, QTStr("Basic.AutoConfig.ApplySettings")); - setButtonText(QWizard::BackButton, QTStr("Back")); - setButtonText(QWizard::NextButton, QTStr("Next")); - setButtonText(QWizard::CancelButton, QTStr("Cancel")); -} - -AutoConfig::~AutoConfig() -{ - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - main->EnableOutputs(true); - EnableThreadedMessageBoxes(false); -} - -void AutoConfig::TestHardwareEncoding() -{ - size_t idx = 0; - const char *id; - while (obs_enum_encoder_types(idx++, &id)) { - if (strcmp(id, "ffmpeg_nvenc") == 0) - hardwareEncodingAvailable = nvencAvailable = true; - else if (strcmp(id, "obs_qsv11") == 0) - hardwareEncodingAvailable = qsvAvailable = true; - else if (strcmp(id, "h264_texture_amf") == 0) - hardwareEncodingAvailable = vceAvailable = true; -#ifdef __APPLE__ - else if (strcmp(id, "com.apple.videotoolbox.videoencoder.ave.avc") == 0 -#ifndef __aarch64__ - && os_get_emulation_status() == true -#endif - ) - if (__builtin_available(macOS 13.0, *)) - hardwareEncodingAvailable = appleAvailable = true; -#endif - } -} - -bool AutoConfig::CanTestServer(const char *server) -{ - if (!testRegions || (regionUS && regionEU && regionAsia && regionOther)) - return true; - - if (service == Service::Twitch) { - if (astrcmp_n(server, "US West:", 8) == 0 || astrcmp_n(server, "US East:", 8) == 0 || - astrcmp_n(server, "US Central:", 11) == 0) { - return regionUS; - } else if (astrcmp_n(server, "EU:", 3) == 0) { - return regionEU; - } else if (astrcmp_n(server, "Asia:", 5) == 0) { - return regionAsia; - } else if (regionOther) { - return true; - } - } else { - return true; - } - - return false; -} - -void AutoConfig::done(int result) -{ - QWizard::done(result); - - if (result == QDialog::Accepted) { - if (type == Type::Streaming) - SaveStreamSettings(); - SaveSettings(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) { - OBSBasic *main = OBSBasic::Get(); - main->NewYouTubeAppDock(); - } -#endif - } -} - -inline const char *AutoConfig::GetEncoderId(Encoder enc) -{ - switch (enc) { - case Encoder::NVENC: - return SIMPLE_ENCODER_NVENC; - case Encoder::QSV: - return SIMPLE_ENCODER_QSV; - case Encoder::AMD: - return SIMPLE_ENCODER_AMD; - case Encoder::Apple: - return SIMPLE_ENCODER_APPLE_H264; - default: - return SIMPLE_ENCODER_X264; - } -}; - -void AutoConfig::SaveStreamSettings() -{ - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - /* ---------------------------------- */ - /* save service */ - - const char *service_id = customServer ? "rtmp_custom" : "rtmp_common"; - - obs_service_t *oldService = main->GetService(); - OBSDataAutoRelease hotkeyData = obs_hotkeys_save_service(oldService); - - OBSDataAutoRelease settings = obs_data_create(); - - if (!customServer) - obs_data_set_string(settings, "service", serviceName.c_str()); - obs_data_set_string(settings, "server", server.c_str()); -#ifdef YOUTUBE_ENABLED - if (!streamPage->auth || !IsYouTubeService(serviceName)) - obs_data_set_string(settings, "key", key.c_str()); -#else - obs_data_set_string(settings, "key", key.c_str()); -#endif - - OBSServiceAutoRelease newService = obs_service_create(service_id, "default_service", settings, hotkeyData); - - if (!newService) - return; - - main->SetService(newService); - main->SaveService(); - main->auth = streamPage->auth; - if (!!main->auth) { - main->auth->LoadUI(); - main->SetBroadcastFlowEnabled(main->auth->broadcastFlow()); - } else { - main->SetBroadcastFlowEnabled(false); - } - - /* ---------------------------------- */ - /* save stream settings */ - - config_set_int(main->Config(), "SimpleOutput", "VBitrate", idealBitrate); - 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() -{ - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - if (recordingEncoder != Encoder::Stream) - config_set_string(main->Config(), "SimpleOutput", "RecEncoder", GetEncoderId(recordingEncoder)); - - const char *quality = recordingQuality == Quality::High ? "Small" : "Stream"; - - config_set_string(main->Config(), "Output", "Mode", "Simple"); - config_set_string(main->Config(), "SimpleOutput", "RecQuality", quality); - config_set_int(main->Config(), "Video", "BaseCX", baseResolutionCX); - config_set_int(main->Config(), "Video", "BaseCY", baseResolutionCY); - config_set_int(main->Config(), "Video", "OutputCX", idealResolutionCX); - config_set_int(main->Config(), "Video", "OutputCY", idealResolutionCY); - - if (fpsType != FPSType::UseCurrent) { - config_set_uint(main->Config(), "Video", "FPSType", 0); - config_set_string(main->Config(), "Video", "FPSCommon", std::to_string(idealFPSNum).c_str()); - } - - main->ResetVideo(); - main->ResetOutputs(); - config_save_safe(main->Config(), "tmp", nullptr); -} diff --git a/frontend/wizards/AutoConfigStartPage.hpp b/frontend/wizards/AutoConfigStartPage.hpp index 7e31bdf89..73cff2838 100644 --- a/frontend/wizards/AutoConfigStartPage.hpp +++ b/frontend/wizards/AutoConfigStartPage.hpp @@ -1,145 +1,8 @@ #pragma once -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include - class Ui_AutoConfigStartPage; -class Ui_AutoConfigVideoPage; -class Ui_AutoConfigStreamPage; -class Ui_AutoConfigTestPage; - -class AutoConfigStreamPage; -class Auth; - -class AutoConfig : public QWizard { - Q_OBJECT - - friend class AutoConfigStartPage; - friend class AutoConfigVideoPage; - friend class AutoConfigStreamPage; - friend class AutoConfigTestPage; - - enum class Type { - Invalid, - Streaming, - Recording, - VirtualCam, - }; - - enum class Service { - Twitch, - YouTube, - AmazonIVS, - Other, - }; - - enum class Encoder { - x264, - NVENC, - QSV, - AMD, - Apple, - Stream, - }; - - enum class Quality { - Stream, - High, - }; - - enum class FPSType : int { - PreferHighFPS, - PreferHighRes, - UseCurrent, - fps30, - fps60, - }; - - struct StreamServer { - std::string name; - std::string address; - }; - - static inline const char *GetEncoderId(Encoder enc); - - AutoConfigStreamPage *streamPage = nullptr; - - Service service = Service::Other; - Quality recordingQuality = Quality::Stream; - Encoder recordingEncoder = Encoder::Stream; - Encoder streamingEncoder = Encoder::x264; - 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; - int idealResolutionCY = 720; - int idealFPSNum = 60; - int idealFPSDen = 1; - std::string serviceName; - std::string serverName; - std::string server; - std::vector serviceConfigServers; - std::string key; - - bool hardwareEncodingAvailable = false; - bool nvencAvailable = false; - bool qsvAvailable = false; - bool vceAvailable = false; - bool appleAvailable = false; - - int startingBitrate = 2500; - bool customServer = false; - bool bandwidthTest = false; - bool testMultitrackVideo = false; - bool testRegions = true; - bool twitchAuto = false; - bool amazonIVSAuto = false; - bool regionUS = true; - bool regionEU = true; - bool regionAsia = true; - bool regionOther = true; - bool preferHighFPS = false; - bool preferHardware = false; - int specificFPSNum = 0; - int specificFPSDen = 0; - - void TestHardwareEncoding(); - bool CanTestServer(const char *server); - - virtual void done(int result) override; - - void SaveStreamSettings(); - void SaveSettings(); - -public: - AutoConfig(QWidget *parent); - ~AutoConfig(); - - enum Page { - StartPage, - VideoPage, - StreamPage, - TestPage, - }; -}; class AutoConfigStartPage : public QWizardPage { Q_OBJECT @@ -159,130 +22,3 @@ public slots: void on_prioritizeRecording_clicked(); void PrioritizeVCam(); }; - -class AutoConfigVideoPage : public QWizardPage { - Q_OBJECT - - friend class AutoConfig; - - std::unique_ptr ui; - -public: - AutoConfigVideoPage(QWidget *parent = nullptr); - ~AutoConfigVideoPage(); - - virtual int nextId() const override; - virtual bool validatePage() override; -}; - -class AutoConfigStreamPage : public QWizardPage { - Q_OBJECT - - friend class AutoConfig; - - enum class Section : int { - Connect, - StreamKey, - }; - - std::shared_ptr auth; - - std::unique_ptr ui; - QString lastService; - bool ready = false; - - void LoadServices(bool showAll); - inline bool IsCustomService() const; - -public: - AutoConfigStreamPage(QWidget *parent = nullptr); - ~AutoConfigStreamPage(); - - virtual bool isComplete() const override; - virtual int nextId() const override; - virtual bool validatePage() override; - - void OnAuthConnected(); - void OnOAuthStreamKeyConnected(); - -public slots: - void on_show_clicked(); - void on_connectAccount_clicked(); - void on_disconnectAccount_clicked(); - void on_useStreamKey_clicked(); - void on_preferHardware_clicked(); - void ServiceChanged(); - void UpdateKeyLink(); - void UpdateMoreInfoLink(); - void UpdateServerList(); - void UpdateCompleted(); - - void reset_service_ui_fields(std::string &service); -}; - -class AutoConfigTestPage : public QWizardPage { - Q_OBJECT - - friend class AutoConfig; - - QPointer results; - - std::unique_ptr ui; - std::thread testThread; - std::condition_variable cv; - std::mutex m; - bool cancel = false; - bool started = false; - - enum class Stage { - Starting, - BandwidthTest, - StreamEncoder, - RecordingEncoder, - Finished, - }; - - Stage stage = Stage::Starting; - bool softwareTested = false; - - void StartBandwidthStage(); - void StartStreamEncoderStage(); - void StartRecordingEncoderStage(); - - void FindIdealHardwareResolution(); - bool TestSoftwareEncoding(); - - void TestBandwidthThread(); - void TestStreamEncoderThread(); - void TestRecordingEncoderThread(); - - void FinalizeResults(); - - struct ServerInfo { - std::string name; - std::string address; - int bitrate = 0; - int ms = -1; - - inline ServerInfo() {} - - inline ServerInfo(const char *name_, const char *address_) : name(name_), address(address_) {} - }; - - void GetServers(std::vector &servers); - -public: - AutoConfigTestPage(QWidget *parent = nullptr); - ~AutoConfigTestPage(); - - virtual void initializePage() override; - virtual void cleanupPage() override; - virtual bool isComplete() const override; - virtual int nextId() const override; - -public slots: - void NextStage(); - void UpdateMessage(QString message); - void Failure(QString message); - void Progress(int percentage); -}; diff --git a/frontend/wizards/AutoConfigStreamPage.cpp b/frontend/wizards/AutoConfigStreamPage.cpp index 6b1667609..86d11c3cf 100644 --- a/frontend/wizards/AutoConfigStreamPage.cpp +++ b/frontend/wizards/AutoConfigStreamPage.cpp @@ -1,245 +1,30 @@ -#include -#include - -#include -#include - -#include - -#include "moc_window-basic-auto-config.cpp" -#include "window-basic-main.hpp" -#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 "AutoConfigStreamPage.hpp" +#include "AutoConfig.hpp" #include "ui_AutoConfigStreamPage.h" -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "auth-oauth.hpp" -#include "ui-config.h" +#include +#include +#include +#include #ifdef YOUTUBE_ENABLED -#include "youtube-api-wrappers.hpp" +#include #endif +#include -struct QCef; -struct QCefCookieManager; +#include -extern QCef *cef; -extern QCefCookieManager *panel_cookies; - -#define wiz reinterpret_cast(wizard()) - -/* ------------------------------------------------------------------------- */ - -constexpr std::string_view OBSServiceFileName = "service.json"; - -static OBSData OpenServiceSettings(std::string &type) -{ - const OBSBasic *basic = reinterpret_cast(App()->GetMainWindow()); - const OBSProfile ¤tProfile = basic->GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - if (!std::filesystem::exists(jsonFilePath)) { - return OBSData(); - } - - OBSDataAutoRelease data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - - return settings.Get(); -} - -static void GetServiceInfo(std::string &type, std::string &service, std::string &server, std::string &key) -{ - OBSData settings = OpenServiceSettings(type); - - service = obs_data_get_string(settings, "service"); - server = obs_data_get_string(settings, "server"); - key = obs_data_get_string(settings, "key"); -} - -/* ------------------------------------------------------------------------- */ - -AutoConfigStartPage::AutoConfigStartPage(QWidget *parent) : QWizardPage(parent), ui(new Ui_AutoConfigStartPage) -{ - ui->setupUi(this); - setTitle(QTStr("Basic.AutoConfig.StartPage")); - setSubTitle(QTStr("Basic.AutoConfig.StartPage.SubTitle")); - - OBSBasic *main = OBSBasic::Get(); - if (main->VCamEnabled()) { - QRadioButton *prioritizeVCam = - new QRadioButton(QTStr("Basic.AutoConfig.StartPage.PrioritizeVirtualCam"), this); - QBoxLayout *box = reinterpret_cast(layout()); - box->insertWidget(2, prioritizeVCam); - - connect(prioritizeVCam, &QPushButton::clicked, this, &AutoConfigStartPage::PrioritizeVCam); - } -} - -AutoConfigStartPage::~AutoConfigStartPage() {} - -int AutoConfigStartPage::nextId() const -{ - return wiz->type == AutoConfig::Type::VirtualCam ? AutoConfig::TestPage : AutoConfig::VideoPage; -} - -void AutoConfigStartPage::on_prioritizeStreaming_clicked() -{ - wiz->type = AutoConfig::Type::Streaming; -} - -void AutoConfigStartPage::on_prioritizeRecording_clicked() -{ - wiz->type = AutoConfig::Type::Recording; -} - -void AutoConfigStartPage::PrioritizeVCam() -{ - wiz->type = AutoConfig::Type::VirtualCam; -} - -/* ------------------------------------------------------------------------- */ - -#define RES_TEXT(x) "Basic.AutoConfig.VideoPage." x -#define RES_USE_CURRENT RES_TEXT("BaseResolution.UseCurrent") -#define RES_USE_DISPLAY RES_TEXT("BaseResolution.Display") -#define FPS_USE_CURRENT RES_TEXT("FPS.UseCurrent") -#define FPS_PREFER_HIGH_FPS RES_TEXT("FPS.PreferHighFPS") -#define FPS_PREFER_HIGH_RES RES_TEXT("FPS.PreferHighRes") - -AutoConfigVideoPage::AutoConfigVideoPage(QWidget *parent) : QWizardPage(parent), ui(new Ui_AutoConfigVideoPage) -{ - ui->setupUi(this); - - setTitle(QTStr("Basic.AutoConfig.VideoPage")); - setSubTitle(QTStr("Basic.AutoConfig.VideoPage.SubTitle")); - - obs_video_info ovi; - obs_get_video_info(&ovi); - - long double fpsVal = (long double)ovi.fps_num / (long double)ovi.fps_den; - - QString fpsStr = (ovi.fps_den > 1) ? QString::number(fpsVal, 'f', 2) : QString::number(fpsVal, 'g'); - - ui->fps->addItem(QTStr(FPS_PREFER_HIGH_FPS), (int)AutoConfig::FPSType::PreferHighFPS); - ui->fps->addItem(QTStr(FPS_PREFER_HIGH_RES), (int)AutoConfig::FPSType::PreferHighRes); - ui->fps->addItem(QTStr(FPS_USE_CURRENT).arg(fpsStr), (int)AutoConfig::FPSType::UseCurrent); - ui->fps->addItem(QStringLiteral("30"), (int)AutoConfig::FPSType::fps30); - ui->fps->addItem(QStringLiteral("60"), (int)AutoConfig::FPSType::fps60); - ui->fps->setCurrentIndex(0); - - QString cxStr = QString::number(ovi.base_width); - QString cyStr = QString::number(ovi.base_height); - - int encRes = int(ovi.base_width << 16) | int(ovi.base_height); - - // Auto config only supports testing down to 240p, don't allow current - // resolution if it's lower than that. - if (ovi.base_height >= 240) - ui->canvasRes->addItem(QTStr(RES_USE_CURRENT).arg(cxStr, cyStr), (int)encRes); - - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QSize as = screen->size(); - int as_width = as.width(); - int as_height = as.height(); - - // Calculate physical screen resolution based on the virtual screen resolution - // They might differ if scaling is enabled, e.g. for HiDPI screens - as_width = round(as_width * screen->devicePixelRatio()); - as_height = round(as_height * screen->devicePixelRatio()); - - encRes = as_width << 16 | as_height; - - QString str = - QTStr(RES_USE_DISPLAY) - .arg(QString::number(i + 1), QString::number(as_width), QString::number(as_height)); - - ui->canvasRes->addItem(str, encRes); - } - - auto addRes = [&](int cx, int cy) { - encRes = (cx << 16) | cy; - QString str = QString("%1x%2").arg(QString::number(cx), QString::number(cy)); - ui->canvasRes->addItem(str, encRes); - }; - - addRes(1920, 1080); - addRes(1280, 720); - - ui->canvasRes->setCurrentIndex(0); -} - -AutoConfigVideoPage::~AutoConfigVideoPage() {} - -int AutoConfigVideoPage::nextId() const -{ - return wiz->type == AutoConfig::Type::Recording ? AutoConfig::TestPage : AutoConfig::StreamPage; -} - -bool AutoConfigVideoPage::validatePage() -{ - int encRes = ui->canvasRes->currentData().toInt(); - wiz->baseResolutionCX = encRes >> 16; - wiz->baseResolutionCY = encRes & 0xFFFF; - wiz->fpsType = (AutoConfig::FPSType)ui->fps->currentData().toInt(); - - obs_video_info ovi; - obs_get_video_info(&ovi); - - switch (wiz->fpsType) { - case AutoConfig::FPSType::PreferHighFPS: - wiz->specificFPSNum = 0; - wiz->specificFPSDen = 0; - wiz->preferHighFPS = true; - break; - case AutoConfig::FPSType::PreferHighRes: - wiz->specificFPSNum = 0; - wiz->specificFPSDen = 0; - wiz->preferHighFPS = false; - break; - case AutoConfig::FPSType::UseCurrent: - wiz->specificFPSNum = ovi.fps_num; - wiz->specificFPSDen = ovi.fps_den; - wiz->preferHighFPS = false; - break; - case AutoConfig::FPSType::fps30: - wiz->specificFPSNum = 30; - wiz->specificFPSDen = 1; - wiz->preferHighFPS = false; - break; - case AutoConfig::FPSType::fps60: - wiz->specificFPSNum = 60; - wiz->specificFPSDen = 1; - wiz->preferHighFPS = false; - break; - } - - return true; -} - -/* ------------------------------------------------------------------------- */ +#include "moc_AutoConfigStreamPage.cpp" enum class ListOpt : int { ShowAll = 1, Custom, }; +struct QCef; +extern QCef *cef; + +#define wiz reinterpret_cast(wizard()) + AutoConfigStreamPage::AutoConfigStreamPage(QWidget *parent) : QWizardPage(parent), ui(new Ui_AutoConfigStreamPage) { ui->setupUi(this); @@ -910,331 +695,3 @@ void AutoConfigStreamPage::UpdateCompleted() } emit completeChanged(); } - -/* ------------------------------------------------------------------------- */ - -AutoConfig::AutoConfig(QWidget *parent) : QWizard(parent) -{ - EnableThreadedMessageBoxes(true); - - calldata_t cd = {0}; - calldata_set_int(&cd, "seconds", 5); - - proc_handler_t *ph = obs_get_proc_handler(); - proc_handler_call(ph, "twitch_ingests_refresh", &cd); - proc_handler_call(ph, "amazon_ivs_ingests_refresh", &cd); - calldata_free(&cd); - - OBSBasic *main = reinterpret_cast(parent); - main->EnableOutputs(false); - - installEventFilter(CreateShortcutFilter()); - - std::string serviceType; - GetServiceInfo(serviceType, serviceName, server, key); -#if defined(_WIN32) || defined(__APPLE__) - setWizardStyle(QWizard::ModernStyle); -#endif - streamPage = new AutoConfigStreamPage(); - - setPage(StartPage, new AutoConfigStartPage()); - setPage(VideoPage, new AutoConfigVideoPage()); - setPage(StreamPage, streamPage); - setPage(TestPage, new AutoConfigTestPage()); - setWindowTitle(QTStr("Basic.AutoConfig")); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - - obs_video_info ovi; - obs_get_video_info(&ovi); - - baseResolutionCX = ovi.base_width; - baseResolutionCY = ovi.base_height; - - /* ----------------------------------------- */ - /* check to see if Twitch's "auto" available */ - - OBSDataAutoRelease twitchSettings = obs_data_create(); - - obs_data_set_string(twitchSettings, "service", "Twitch"); - - obs_properties_t *props = obs_get_service_properties("rtmp_common"); - obs_properties_apply_settings(props, twitchSettings); - - obs_property_t *p = obs_properties_get(props, "server"); - const char *first = obs_property_list_item_string(p, 0); - twitchAuto = strcmp(first, "auto") == 0; - - obs_properties_destroy(props); - - /* ----------------------------------------- */ - /* check to see if Amazon IVS "auto" entries are available */ - - OBSDataAutoRelease amazonIVSSettings = obs_data_create(); - - obs_data_set_string(amazonIVSSettings, "service", "Amazon IVS"); - - props = obs_get_service_properties("rtmp_common"); - obs_properties_apply_settings(props, amazonIVSSettings); - - p = obs_properties_get(props, "server"); - first = obs_property_list_item_string(p, 0); - amazonIVSAuto = strncmp(first, "auto", 4) == 0; - - obs_properties_destroy(props); - - /* ----------------------------------------- */ - /* load service/servers */ - - customServer = serviceType == "rtmp_custom"; - - QComboBox *serviceList = streamPage->ui->service; - - if (!serviceName.empty()) { - serviceList->blockSignals(true); - - int count = serviceList->count(); - bool found = false; - - for (int i = 0; i < count; i++) { - QString name = serviceList->itemText(i); - - if (name == serviceName.c_str()) { - serviceList->setCurrentIndex(i); - found = true; - break; - } - } - - if (!found) { - serviceList->insertItem(0, serviceName.c_str()); - serviceList->setCurrentIndex(0); - } - - serviceList->blockSignals(false); - } - - streamPage->UpdateServerList(); - streamPage->UpdateKeyLink(); - streamPage->UpdateMoreInfoLink(); - streamPage->lastService.clear(); - - if (!customServer) { - QComboBox *serverList = streamPage->ui->server; - int idx = serverList->findData(QString(server.c_str())); - if (idx == -1) - idx = 0; - - serverList->setCurrentIndex(idx); - } else { - streamPage->ui->customServer->setText(server.c_str()); - int idx = streamPage->ui->service->findData(QVariant((int)ListOpt::Custom)); - streamPage->ui->service->setCurrentIndex(idx); - } - - 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(); - - if (!hardwareEncodingAvailable) { - delete streamPage->ui->preferHardware; - streamPage->ui->preferHardware = nullptr; - } else { - /* Newer generations of NVENC have a high enough quality to - * bitrate ratio that if NVENC is available, it makes sense to - * just always prefer hardware encoding by default */ - bool preferHardware = nvencAvailable || appleAvailable || os_get_physical_cores() <= 4; - streamPage->ui->preferHardware->setChecked(preferHardware); - } - - setOptions(QWizard::WizardOptions()); - setButtonText(QWizard::FinishButton, QTStr("Basic.AutoConfig.ApplySettings")); - setButtonText(QWizard::BackButton, QTStr("Back")); - setButtonText(QWizard::NextButton, QTStr("Next")); - setButtonText(QWizard::CancelButton, QTStr("Cancel")); -} - -AutoConfig::~AutoConfig() -{ - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - main->EnableOutputs(true); - EnableThreadedMessageBoxes(false); -} - -void AutoConfig::TestHardwareEncoding() -{ - size_t idx = 0; - const char *id; - while (obs_enum_encoder_types(idx++, &id)) { - if (strcmp(id, "ffmpeg_nvenc") == 0) - hardwareEncodingAvailable = nvencAvailable = true; - else if (strcmp(id, "obs_qsv11") == 0) - hardwareEncodingAvailable = qsvAvailable = true; - else if (strcmp(id, "h264_texture_amf") == 0) - hardwareEncodingAvailable = vceAvailable = true; -#ifdef __APPLE__ - else if (strcmp(id, "com.apple.videotoolbox.videoencoder.ave.avc") == 0 -#ifndef __aarch64__ - && os_get_emulation_status() == true -#endif - ) - if (__builtin_available(macOS 13.0, *)) - hardwareEncodingAvailable = appleAvailable = true; -#endif - } -} - -bool AutoConfig::CanTestServer(const char *server) -{ - if (!testRegions || (regionUS && regionEU && regionAsia && regionOther)) - return true; - - if (service == Service::Twitch) { - if (astrcmp_n(server, "US West:", 8) == 0 || astrcmp_n(server, "US East:", 8) == 0 || - astrcmp_n(server, "US Central:", 11) == 0) { - return regionUS; - } else if (astrcmp_n(server, "EU:", 3) == 0) { - return regionEU; - } else if (astrcmp_n(server, "Asia:", 5) == 0) { - return regionAsia; - } else if (regionOther) { - return true; - } - } else { - return true; - } - - return false; -} - -void AutoConfig::done(int result) -{ - QWizard::done(result); - - if (result == QDialog::Accepted) { - if (type == Type::Streaming) - SaveStreamSettings(); - SaveSettings(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) { - OBSBasic *main = OBSBasic::Get(); - main->NewYouTubeAppDock(); - } -#endif - } -} - -inline const char *AutoConfig::GetEncoderId(Encoder enc) -{ - switch (enc) { - case Encoder::NVENC: - return SIMPLE_ENCODER_NVENC; - case Encoder::QSV: - return SIMPLE_ENCODER_QSV; - case Encoder::AMD: - return SIMPLE_ENCODER_AMD; - case Encoder::Apple: - return SIMPLE_ENCODER_APPLE_H264; - default: - return SIMPLE_ENCODER_X264; - } -}; - -void AutoConfig::SaveStreamSettings() -{ - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - /* ---------------------------------- */ - /* save service */ - - const char *service_id = customServer ? "rtmp_custom" : "rtmp_common"; - - obs_service_t *oldService = main->GetService(); - OBSDataAutoRelease hotkeyData = obs_hotkeys_save_service(oldService); - - OBSDataAutoRelease settings = obs_data_create(); - - if (!customServer) - obs_data_set_string(settings, "service", serviceName.c_str()); - obs_data_set_string(settings, "server", server.c_str()); -#ifdef YOUTUBE_ENABLED - if (!streamPage->auth || !IsYouTubeService(serviceName)) - obs_data_set_string(settings, "key", key.c_str()); -#else - obs_data_set_string(settings, "key", key.c_str()); -#endif - - OBSServiceAutoRelease newService = obs_service_create(service_id, "default_service", settings, hotkeyData); - - if (!newService) - return; - - main->SetService(newService); - main->SaveService(); - main->auth = streamPage->auth; - if (!!main->auth) { - main->auth->LoadUI(); - main->SetBroadcastFlowEnabled(main->auth->broadcastFlow()); - } else { - main->SetBroadcastFlowEnabled(false); - } - - /* ---------------------------------- */ - /* save stream settings */ - - config_set_int(main->Config(), "SimpleOutput", "VBitrate", idealBitrate); - 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() -{ - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - if (recordingEncoder != Encoder::Stream) - config_set_string(main->Config(), "SimpleOutput", "RecEncoder", GetEncoderId(recordingEncoder)); - - const char *quality = recordingQuality == Quality::High ? "Small" : "Stream"; - - config_set_string(main->Config(), "Output", "Mode", "Simple"); - config_set_string(main->Config(), "SimpleOutput", "RecQuality", quality); - config_set_int(main->Config(), "Video", "BaseCX", baseResolutionCX); - config_set_int(main->Config(), "Video", "BaseCY", baseResolutionCY); - config_set_int(main->Config(), "Video", "OutputCX", idealResolutionCX); - config_set_int(main->Config(), "Video", "OutputCY", idealResolutionCY); - - if (fpsType != FPSType::UseCurrent) { - config_set_uint(main->Config(), "Video", "FPSType", 0); - config_set_string(main->Config(), "Video", "FPSCommon", std::to_string(idealFPSNum).c_str()); - } - - main->ResetVideo(); - main->ResetOutputs(); - config_save_safe(main->Config(), "tmp", nullptr); -} diff --git a/frontend/wizards/AutoConfigStreamPage.hpp b/frontend/wizards/AutoConfigStreamPage.hpp index 7e31bdf89..89216c461 100644 --- a/frontend/wizards/AutoConfigStreamPage.hpp +++ b/frontend/wizards/AutoConfigStreamPage.hpp @@ -1,179 +1,9 @@ #pragma once -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include - -class Ui_AutoConfigStartPage; -class Ui_AutoConfigVideoPage; -class Ui_AutoConfigStreamPage; -class Ui_AutoConfigTestPage; - -class AutoConfigStreamPage; class Auth; - -class AutoConfig : public QWizard { - Q_OBJECT - - friend class AutoConfigStartPage; - friend class AutoConfigVideoPage; - friend class AutoConfigStreamPage; - friend class AutoConfigTestPage; - - enum class Type { - Invalid, - Streaming, - Recording, - VirtualCam, - }; - - enum class Service { - Twitch, - YouTube, - AmazonIVS, - Other, - }; - - enum class Encoder { - x264, - NVENC, - QSV, - AMD, - Apple, - Stream, - }; - - enum class Quality { - Stream, - High, - }; - - enum class FPSType : int { - PreferHighFPS, - PreferHighRes, - UseCurrent, - fps30, - fps60, - }; - - struct StreamServer { - std::string name; - std::string address; - }; - - static inline const char *GetEncoderId(Encoder enc); - - AutoConfigStreamPage *streamPage = nullptr; - - Service service = Service::Other; - Quality recordingQuality = Quality::Stream; - Encoder recordingEncoder = Encoder::Stream; - Encoder streamingEncoder = Encoder::x264; - 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; - int idealResolutionCY = 720; - int idealFPSNum = 60; - int idealFPSDen = 1; - std::string serviceName; - std::string serverName; - std::string server; - std::vector serviceConfigServers; - std::string key; - - bool hardwareEncodingAvailable = false; - bool nvencAvailable = false; - bool qsvAvailable = false; - bool vceAvailable = false; - bool appleAvailable = false; - - int startingBitrate = 2500; - bool customServer = false; - bool bandwidthTest = false; - bool testMultitrackVideo = false; - bool testRegions = true; - bool twitchAuto = false; - bool amazonIVSAuto = false; - bool regionUS = true; - bool regionEU = true; - bool regionAsia = true; - bool regionOther = true; - bool preferHighFPS = false; - bool preferHardware = false; - int specificFPSNum = 0; - int specificFPSDen = 0; - - void TestHardwareEncoding(); - bool CanTestServer(const char *server); - - virtual void done(int result) override; - - void SaveStreamSettings(); - void SaveSettings(); - -public: - AutoConfig(QWidget *parent); - ~AutoConfig(); - - enum Page { - StartPage, - VideoPage, - StreamPage, - TestPage, - }; -}; - -class AutoConfigStartPage : public QWizardPage { - Q_OBJECT - - friend class AutoConfig; - - std::unique_ptr ui; - -public: - AutoConfigStartPage(QWidget *parent = nullptr); - ~AutoConfigStartPage(); - - virtual int nextId() const override; - -public slots: - void on_prioritizeStreaming_clicked(); - void on_prioritizeRecording_clicked(); - void PrioritizeVCam(); -}; - -class AutoConfigVideoPage : public QWizardPage { - Q_OBJECT - - friend class AutoConfig; - - std::unique_ptr ui; - -public: - AutoConfigVideoPage(QWidget *parent = nullptr); - ~AutoConfigVideoPage(); - - virtual int nextId() const override; - virtual bool validatePage() override; -}; +class Ui_AutoConfigStreamPage; class AutoConfigStreamPage : public QWizardPage { Q_OBJECT @@ -219,70 +49,3 @@ public slots: void reset_service_ui_fields(std::string &service); }; - -class AutoConfigTestPage : public QWizardPage { - Q_OBJECT - - friend class AutoConfig; - - QPointer results; - - std::unique_ptr ui; - std::thread testThread; - std::condition_variable cv; - std::mutex m; - bool cancel = false; - bool started = false; - - enum class Stage { - Starting, - BandwidthTest, - StreamEncoder, - RecordingEncoder, - Finished, - }; - - Stage stage = Stage::Starting; - bool softwareTested = false; - - void StartBandwidthStage(); - void StartStreamEncoderStage(); - void StartRecordingEncoderStage(); - - void FindIdealHardwareResolution(); - bool TestSoftwareEncoding(); - - void TestBandwidthThread(); - void TestStreamEncoderThread(); - void TestRecordingEncoderThread(); - - void FinalizeResults(); - - struct ServerInfo { - std::string name; - std::string address; - int bitrate = 0; - int ms = -1; - - inline ServerInfo() {} - - inline ServerInfo(const char *name_, const char *address_) : name(name_), address(address_) {} - }; - - void GetServers(std::vector &servers); - -public: - AutoConfigTestPage(QWidget *parent = nullptr); - ~AutoConfigTestPage(); - - virtual void initializePage() override; - virtual void cleanupPage() override; - virtual bool isComplete() const override; - virtual int nextId() const override; - -public slots: - void NextStage(); - void UpdateMessage(QString message); - void Failure(QString message); - void Progress(int percentage); -}; diff --git a/frontend/wizards/AutoConfigTestPage.cpp b/frontend/wizards/AutoConfigTestPage.cpp index b53f37aa2..1d5c91850 100644 --- a/frontend/wizards/AutoConfigTestPage.cpp +++ b/frontend/wizards/AutoConfigTestPage.cpp @@ -1,86 +1,16 @@ -#include +#include "AutoConfigTestPage.hpp" +#include "AutoConfig.hpp" +#include "TestMode.hpp" +#include "ui_AutoConfigTestPage.h" -#include +#include -#include -#include -#include -#include -#include #include #include -#include "window-basic-auto-config.hpp" -#include "window-basic-main.hpp" -#include "obs-app.hpp" +#include -#include "ui_AutoConfigTestPage.h" - -#define wiz reinterpret_cast(wizard()) - -using namespace std; - -/* ------------------------------------------------------------------------- */ - -class TestMode { - obs_video_info ovi; - OBSSource source[6]; - - static void render_rand(void *, uint32_t cx, uint32_t cy) - { - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *randomvals[3] = {gs_effect_get_param_by_name(solid, "randomvals1"), - gs_effect_get_param_by_name(solid, "randomvals2"), - gs_effect_get_param_by_name(solid, "randomvals3")}; - - struct vec4 r; - - for (int i = 0; i < 3; i++) { - vec4_set(&r, rand_float(true) * 100.0f, rand_float(true) * 100.0f, - rand_float(true) * 50000.0f + 10000.0f, 0.0f); - gs_effect_set_vec4(randomvals[i], &r); - } - - while (gs_effect_loop(solid, "Random")) - gs_draw_sprite(nullptr, 0, cx, cy); - } - -public: - inline TestMode() - { - obs_get_video_info(&ovi); - obs_add_main_render_callback(render_rand, this); - - for (uint32_t i = 0; i < 6; i++) { - source[i] = obs_get_output_source(i); - obs_source_release(source[i]); - obs_set_output_source(i, nullptr); - } - } - - inline ~TestMode() - { - for (uint32_t i = 0; i < 6; i++) - obs_set_output_source(i, source[i]); - - obs_remove_main_render_callback(render_rand, this); - obs_reset_video(&ovi); - } - - inline void SetVideo(int cx, int cy, int fps_num, int fps_den) - { - obs_video_info newOVI = ovi; - - newOVI.output_width = (uint32_t)cx; - newOVI.output_height = (uint32_t)cy; - newOVI.fps_num = (uint32_t)fps_num; - newOVI.fps_den = (uint32_t)fps_den; - - obs_reset_video(&newOVI); - } -}; - -/* ------------------------------------------------------------------------- */ +#include "moc_AutoConfigTestPage.cpp" #define TEST_STR(x) "Basic.AutoConfig.TestPage." x #define SUBTITLE_TESTING TEST_STR("SubTitle.Testing") @@ -97,6 +27,10 @@ public: #define TEST_RESULT_SE TEST_STR("Result.StreamingEncoder") #define TEST_RESULT_RE TEST_STR("Result.RecordingEncoder") +#define wiz reinterpret_cast(wizard()) + +using namespace std; + void AutoConfigTestPage::StartBandwidthStage() { ui->progressLabel->setText(QTStr(TEST_BW)); @@ -152,14 +86,7 @@ static inline void string_depad_key(string &key) } } -const char *FindAudioEncoderFromCodec(const char *type); - -static inline bool can_use_output(const char *prot, const char *output, const char *prot_test1, - const char *prot_test2 = nullptr) -{ - return (strcmp(prot, prot_test1) == 0 || (prot_test2 && strcmp(prot, prot_test2) == 0)) && - (obs_get_output_flags(output) & OBS_OUTPUT_SERVICE) != 0; -} +extern const char *FindAudioEncoderFromCodec(const char *type); static bool return_first_id(void *data, const char *id) { diff --git a/frontend/wizards/AutoConfigVideoPage.cpp b/frontend/wizards/AutoConfigVideoPage.cpp index 6b1667609..69de8e2ec 100644 --- a/frontend/wizards/AutoConfigVideoPage.cpp +++ b/frontend/wizards/AutoConfigVideoPage.cpp @@ -1,118 +1,12 @@ -#include +#include "AutoConfigVideoPage.hpp" +#include "AutoConfig.hpp" +#include "ui_AutoConfigVideoPage.h" + +#include + #include -#include -#include - -#include - -#include "moc_window-basic-auto-config.cpp" -#include "window-basic-main.hpp" -#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" - -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "auth-oauth.hpp" -#include "ui-config.h" -#ifdef YOUTUBE_ENABLED -#include "youtube-api-wrappers.hpp" -#endif - -struct QCef; -struct QCefCookieManager; - -extern QCef *cef; -extern QCefCookieManager *panel_cookies; - -#define wiz reinterpret_cast(wizard()) - -/* ------------------------------------------------------------------------- */ - -constexpr std::string_view OBSServiceFileName = "service.json"; - -static OBSData OpenServiceSettings(std::string &type) -{ - const OBSBasic *basic = reinterpret_cast(App()->GetMainWindow()); - const OBSProfile ¤tProfile = basic->GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - if (!std::filesystem::exists(jsonFilePath)) { - return OBSData(); - } - - OBSDataAutoRelease data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - - return settings.Get(); -} - -static void GetServiceInfo(std::string &type, std::string &service, std::string &server, std::string &key) -{ - OBSData settings = OpenServiceSettings(type); - - service = obs_data_get_string(settings, "service"); - server = obs_data_get_string(settings, "server"); - key = obs_data_get_string(settings, "key"); -} - -/* ------------------------------------------------------------------------- */ - -AutoConfigStartPage::AutoConfigStartPage(QWidget *parent) : QWizardPage(parent), ui(new Ui_AutoConfigStartPage) -{ - ui->setupUi(this); - setTitle(QTStr("Basic.AutoConfig.StartPage")); - setSubTitle(QTStr("Basic.AutoConfig.StartPage.SubTitle")); - - OBSBasic *main = OBSBasic::Get(); - if (main->VCamEnabled()) { - QRadioButton *prioritizeVCam = - new QRadioButton(QTStr("Basic.AutoConfig.StartPage.PrioritizeVirtualCam"), this); - QBoxLayout *box = reinterpret_cast(layout()); - box->insertWidget(2, prioritizeVCam); - - connect(prioritizeVCam, &QPushButton::clicked, this, &AutoConfigStartPage::PrioritizeVCam); - } -} - -AutoConfigStartPage::~AutoConfigStartPage() {} - -int AutoConfigStartPage::nextId() const -{ - return wiz->type == AutoConfig::Type::VirtualCam ? AutoConfig::TestPage : AutoConfig::VideoPage; -} - -void AutoConfigStartPage::on_prioritizeStreaming_clicked() -{ - wiz->type = AutoConfig::Type::Streaming; -} - -void AutoConfigStartPage::on_prioritizeRecording_clicked() -{ - wiz->type = AutoConfig::Type::Recording; -} - -void AutoConfigStartPage::PrioritizeVCam() -{ - wiz->type = AutoConfig::Type::VirtualCam; -} - -/* ------------------------------------------------------------------------- */ +#include "moc_AutoConfigVideoPage.cpp" #define RES_TEXT(x) "Basic.AutoConfig.VideoPage." x #define RES_USE_CURRENT RES_TEXT("BaseResolution.UseCurrent") @@ -121,6 +15,8 @@ void AutoConfigStartPage::PrioritizeVCam() #define FPS_PREFER_HIGH_FPS RES_TEXT("FPS.PreferHighFPS") #define FPS_PREFER_HIGH_RES RES_TEXT("FPS.PreferHighRes") +#define wiz reinterpret_cast(wizard()) + AutoConfigVideoPage::AutoConfigVideoPage(QWidget *parent) : QWizardPage(parent), ui(new Ui_AutoConfigVideoPage) { ui->setupUi(this); @@ -232,1009 +128,3 @@ bool AutoConfigVideoPage::validatePage() return true; } - -/* ------------------------------------------------------------------------- */ - -enum class ListOpt : int { - ShowAll = 1, - Custom, -}; - -AutoConfigStreamPage::AutoConfigStreamPage(QWidget *parent) : QWizardPage(parent), ui(new Ui_AutoConfigStreamPage) -{ - ui->setupUi(this); - ui->bitrateLabel->setVisible(false); - ui->bitrate->setVisible(false); - ui->connectAccount2->setVisible(false); - ui->disconnectAccount->setVisible(false); - ui->useMultitrackVideo->setVisible(false); - - ui->connectedAccountLabel->setVisible(false); - ui->connectedAccountText->setVisible(false); - - int vertSpacing = ui->topLayout->verticalSpacing(); - - QMargins m = ui->topLayout->contentsMargins(); - m.setBottom(vertSpacing / 2); - ui->topLayout->setContentsMargins(m); - - m = ui->loginPageLayout->contentsMargins(); - m.setTop(vertSpacing / 2); - ui->loginPageLayout->setContentsMargins(m); - - m = ui->streamkeyPageLayout->contentsMargins(); - m.setTop(vertSpacing / 2); - ui->streamkeyPageLayout->setContentsMargins(m); - - setTitle(QTStr("Basic.AutoConfig.StreamPage")); - setSubTitle(QTStr("Basic.AutoConfig.StreamPage.SubTitle")); - - LoadServices(false); - - connect(ui->service, &QComboBox::currentIndexChanged, this, &AutoConfigStreamPage::ServiceChanged); - connect(ui->customServer, &QLineEdit::textChanged, this, &AutoConfigStreamPage::ServiceChanged); - connect(ui->customServer, &QLineEdit::textChanged, this, &AutoConfigStreamPage::UpdateKeyLink); - connect(ui->customServer, &QLineEdit::editingFinished, this, &AutoConfigStreamPage::UpdateKeyLink); - connect(ui->doBandwidthTest, &QCheckBox::toggled, this, &AutoConfigStreamPage::ServiceChanged); - - connect(ui->service, &QComboBox::currentIndexChanged, this, &AutoConfigStreamPage::UpdateServerList); - - connect(ui->service, &QComboBox::currentIndexChanged, this, &AutoConfigStreamPage::UpdateKeyLink); - connect(ui->service, &QComboBox::currentIndexChanged, this, &AutoConfigStreamPage::UpdateMoreInfoLink); - - connect(ui->useStreamKeyAdv, &QPushButton::clicked, [&]() { - ui->streamKeyWidget->setVisible(true); - ui->streamKeyLabel->setVisible(true); - ui->useStreamKeyAdv->setVisible(false); - }); - - connect(ui->key, &QLineEdit::textChanged, this, &AutoConfigStreamPage::UpdateCompleted); - connect(ui->regionUS, &QCheckBox::toggled, this, &AutoConfigStreamPage::UpdateCompleted); - connect(ui->regionEU, &QCheckBox::toggled, this, &AutoConfigStreamPage::UpdateCompleted); - connect(ui->regionAsia, &QCheckBox::toggled, this, &AutoConfigStreamPage::UpdateCompleted); - connect(ui->regionOther, &QCheckBox::toggled, this, &AutoConfigStreamPage::UpdateCompleted); -} - -AutoConfigStreamPage::~AutoConfigStreamPage() {} - -bool AutoConfigStreamPage::isComplete() const -{ - return ready; -} - -int AutoConfigStreamPage::nextId() const -{ - return AutoConfig::TestPage; -} - -inline bool AutoConfigStreamPage::IsCustomService() const -{ - return ui->service->currentData().toInt() == (int)ListOpt::Custom; -} - -bool AutoConfigStreamPage::validatePage() -{ - OBSDataAutoRelease service_settings = obs_data_create(); - - wiz->customServer = IsCustomService(); - - const char *serverType = wiz->customServer ? "rtmp_custom" : "rtmp_common"; - - if (!wiz->customServer) { - obs_data_set_string(service_settings, "service", QT_TO_UTF8(ui->service->currentText())); - } - - OBSServiceAutoRelease service = obs_service_create(serverType, "temp_service", service_settings, nullptr); - - int bitrate; - if (!ui->doBandwidthTest->isChecked()) { - bitrate = ui->bitrate->value(); - wiz->idealBitrate = bitrate; - } else { - /* Default test target is 10 Mbps */ - bitrate = 10000; -#ifdef YOUTUBE_ENABLED - if (IsYouTubeService(wiz->serviceName)) { - /* Adjust upper bound to YouTube limits - * for resolutions above 1080p */ - if (wiz->baseResolutionCY > 1440) - bitrate = 51000; - else if (wiz->baseResolutionCY > 1080) - bitrate = 18000; - } -#endif - } - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_int(settings, "bitrate", bitrate); - obs_service_apply_encoder_settings(service, settings, nullptr); - - if (wiz->customServer) { - QString server = ui->customServer->text().trimmed(); - wiz->server = wiz->serverName = QT_TO_UTF8(server); - } else { - wiz->serverName = QT_TO_UTF8(ui->server->currentText()); - wiz->server = QT_TO_UTF8(ui->server->currentData().toString()); - } - - wiz->bandwidthTest = ui->doBandwidthTest->isChecked(); - wiz->startingBitrate = (int)obs_data_get_int(settings, "bitrate"); - wiz->idealBitrate = wiz->startingBitrate; - wiz->regionUS = ui->regionUS->isChecked(); - wiz->regionEU = ui->regionEU->isChecked(); - wiz->regionAsia = ui->regionAsia->isChecked(); - wiz->regionOther = ui->regionOther->isChecked(); - wiz->serviceName = QT_TO_UTF8(ui->service->currentText()); - if (ui->preferHardware) - wiz->preferHardware = ui->preferHardware->isChecked(); - wiz->key = QT_TO_UTF8(ui->key->text()); - - if (!wiz->customServer) { - if (wiz->serviceName == "Twitch") - wiz->service = AutoConfig::Service::Twitch; -#ifdef YOUTUBE_ENABLED - else if (IsYouTubeService(wiz->serviceName)) - wiz->service = AutoConfig::Service::YouTube; -#endif - else if (wiz->serviceName == "Amazon IVS") - wiz->service = AutoConfig::Service::AmazonIVS; - else - wiz->service = AutoConfig::Service::Other; - } else { - wiz->service = AutoConfig::Service::Other; - } - - if (wiz->service == AutoConfig::Service::Twitch) { - wiz->testMultitrackVideo = ui->useMultitrackVideo->isChecked(); - - if (wiz->testMultitrackVideo) { - 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) { - auto it = encoder_config.settings.find("bitrate"); - if (it == encoder_config.settings.end()) - continue; - - if (!it->is_number_integer()) - continue; - - int bitrate = 0; - it->get_to(bitrate); - multitrackVideoBitrate += bitrate; - } - - 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->service != AutoConfig::Service::AmazonIVS && wiz->bandwidthTest) { - QMessageBox::StandardButton button; -#define WARNING_TEXT(x) QTStr("Basic.AutoConfig.StreamPage.StreamWarning." x) - button = OBSMessageBox::question(this, WARNING_TEXT("Title"), WARNING_TEXT("Text")); -#undef WARNING_TEXT - - if (button == QMessageBox::No) - return false; - } - - return true; -} - -void AutoConfigStreamPage::on_show_clicked() -{ - if (ui->key->echoMode() == QLineEdit::Password) { - ui->key->setEchoMode(QLineEdit::Normal); - ui->show->setText(QTStr("Hide")); - } else { - ui->key->setEchoMode(QLineEdit::Password); - ui->show->setText(QTStr("Show")); - } -} - -void AutoConfigStreamPage::OnOAuthStreamKeyConnected() -{ - OAuthStreamKey *a = reinterpret_cast(auth.get()); - - if (a) { - bool validKey = !a->key().empty(); - - if (validKey) - ui->key->setText(QT_UTF8(a->key().c_str())); - - ui->streamKeyWidget->setVisible(false); - ui->streamKeyLabel->setVisible(false); - ui->connectAccount2->setVisible(false); - ui->disconnectAccount->setVisible(true); - ui->useStreamKeyAdv->setVisible(false); - - ui->connectedAccountLabel->setVisible(false); - ui->connectedAccountText->setVisible(false); - -#ifdef YOUTUBE_ENABLED - if (IsYouTubeService(a->service())) { - ui->key->clear(); - - ui->connectedAccountLabel->setVisible(true); - ui->connectedAccountText->setVisible(true); - - ui->connectedAccountText->setText(QTStr("Auth.LoadingChannel.Title")); - - YoutubeApiWrappers *ytAuth = reinterpret_cast(a); - ChannelDescription cd; - if (ytAuth->GetChannelDescription(cd)) { - ui->connectedAccountText->setText(cd.title); - - /* Create throwaway stream key for bandwidth test */ - if (ui->doBandwidthTest->isChecked()) { - StreamDescription stream = {"", "", "OBS Studio Test Stream"}; - if (ytAuth->InsertStream(stream)) { - ui->key->setText(stream.name); - } - } - } - } -#endif - } - - ui->stackedWidget->setCurrentIndex((int)Section::StreamKey); - UpdateCompleted(); -} - -void AutoConfigStreamPage::OnAuthConnected() -{ - std::string service = QT_TO_UTF8(ui->service->currentText()); - Auth::Type type = Auth::AuthType(service); - - if (type == Auth::Type::OAuth_StreamKey || type == Auth::Type::OAuth_LinkedAccount) { - OnOAuthStreamKeyConnected(); - } -} - -void AutoConfigStreamPage::on_connectAccount_clicked() -{ - std::string service = QT_TO_UTF8(ui->service->currentText()); - - OAuth::DeleteCookies(service); - - auth = OAuthStreamKey::Login(this, service); - if (!!auth) { - OnAuthConnected(); - - ui->useStreamKeyAdv->setVisible(false); - } -} - -#define DISCONNECT_COMFIRM_TITLE "Basic.AutoConfig.StreamPage.DisconnectAccount.Confirm.Title" -#define DISCONNECT_COMFIRM_TEXT "Basic.AutoConfig.StreamPage.DisconnectAccount.Confirm.Text" - -void AutoConfigStreamPage::on_disconnectAccount_clicked() -{ - QMessageBox::StandardButton button; - - button = OBSMessageBox::question(this, QTStr(DISCONNECT_COMFIRM_TITLE), QTStr(DISCONNECT_COMFIRM_TEXT)); - - if (button == QMessageBox::No) { - return; - } - - OBSBasic *main = OBSBasic::Get(); - - main->auth.reset(); - auth.reset(); - - std::string service = QT_TO_UTF8(ui->service->currentText()); - -#ifdef BROWSER_AVAILABLE - OAuth::DeleteCookies(service); -#endif - - reset_service_ui_fields(service); - - ui->streamKeyWidget->setVisible(true); - ui->streamKeyLabel->setVisible(true); - ui->key->setText(""); - - ui->connectedAccountLabel->setVisible(false); - ui->connectedAccountText->setVisible(false); - - /* Restore key link when disconnecting account */ - UpdateKeyLink(); -} - -void AutoConfigStreamPage::on_useStreamKey_clicked() -{ - ui->stackedWidget->setCurrentIndex((int)Section::StreamKey); - 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; -} - -static inline bool is_external_oauth(const std::string &service) -{ - return Auth::External(service); -} - -void AutoConfigStreamPage::reset_service_ui_fields(std::string &service) -{ -#ifdef YOUTUBE_ENABLED - // when account is already connected: - OAuthStreamKey *a = reinterpret_cast(auth.get()); - if (a && service == a->service() && IsYouTubeService(a->service())) { - ui->connectedAccountLabel->setVisible(true); - ui->connectedAccountText->setVisible(true); - ui->connectAccount2->setVisible(false); - ui->disconnectAccount->setVisible(true); - return; - } -#endif - - bool external_oauth = is_external_oauth(service); - if (external_oauth) { - ui->streamKeyWidget->setVisible(false); - ui->streamKeyLabel->setVisible(false); - ui->connectAccount2->setVisible(true); - ui->useStreamKeyAdv->setVisible(true); - - ui->stackedWidget->setCurrentIndex((int)Section::StreamKey); - - } else if (cef) { - QString key = ui->key->text(); - bool can_auth = is_auth_service(service); - int page = can_auth && key.isEmpty() ? (int)Section::Connect : (int)Section::StreamKey; - - ui->stackedWidget->setCurrentIndex(page); - ui->streamKeyWidget->setVisible(true); - ui->streamKeyLabel->setVisible(true); - ui->connectAccount2->setVisible(can_auth); - ui->useStreamKeyAdv->setVisible(false); - } else { - ui->connectAccount2->setVisible(false); - ui->useStreamKeyAdv->setVisible(false); - } - - ui->connectedAccountLabel->setVisible(false); - ui->connectedAccountText->setVisible(false); - ui->disconnectAccount->setVisible(false); -} - -void AutoConfigStreamPage::ServiceChanged() -{ - bool showMore = ui->service->currentData().toInt() == (int)ListOpt::ShowAll; - if (showMore) - return; - - std::string service = QT_TO_UTF8(ui->service->currentText()); - bool regionBased = service == "Twitch"; - 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 */ - if ((service == "Twitch" && wiz->twitchAuto) || (service == "Amazon IVS" && wiz->amazonIVSAuto)) - regionBased = false; - - ui->streamkeyPageLayout->removeWidget(ui->serverLabel); - ui->streamkeyPageLayout->removeWidget(ui->serverStackedWidget); - - if (custom) { - ui->streamkeyPageLayout->insertRow(1, ui->serverLabel, ui->serverStackedWidget); - - ui->region->setVisible(false); - ui->serverStackedWidget->setCurrentIndex(1); - ui->serverStackedWidget->setVisible(true); - ui->serverLabel->setVisible(true); - } else { - if (!testBandwidth) - ui->streamkeyPageLayout->insertRow(2, ui->serverLabel, ui->serverStackedWidget); - - ui->region->setVisible(regionBased && testBandwidth); - ui->serverStackedWidget->setCurrentIndex(0); - ui->serverStackedWidget->setHidden(testBandwidth); - ui->serverLabel->setHidden(testBandwidth); - } - - wiz->testRegions = regionBased && testBandwidth; - - ui->bitrateLabel->setHidden(testBandwidth); - ui->bitrate->setHidden(testBandwidth); - - OBSBasic *main = OBSBasic::Get(); - - if (main->auth) { - auto system_auth_service = main->auth->service(); - bool service_check = service.find(system_auth_service) != std::string::npos; -#ifdef YOUTUBE_ENABLED - service_check = service_check ? service_check - : IsYouTubeService(system_auth_service) && IsYouTubeService(service); -#endif - if (service_check) { - auth.reset(); - auth = main->auth; - OnAuthConnected(); - } - } - - UpdateCompleted(); -} - -void AutoConfigStreamPage::UpdateMoreInfoLink() -{ - if (IsCustomService()) { - ui->moreInfoButton->hide(); - return; - } - - QString serviceName = ui->service->currentText(); - obs_properties_t *props = obs_get_service_properties("rtmp_common"); - obs_property_t *services = obs_properties_get(props, "service"); - - OBSDataAutoRelease settings = obs_data_create(); - - obs_data_set_string(settings, "service", QT_TO_UTF8(serviceName)); - obs_property_modified(services, settings); - - const char *more_info_link = obs_data_get_string(settings, "more_info_link"); - - if (!more_info_link || (*more_info_link == '\0')) { - ui->moreInfoButton->hide(); - } else { - ui->moreInfoButton->setTargetUrl(QUrl(more_info_link)); - ui->moreInfoButton->show(); - } - obs_properties_destroy(props); -} - -void AutoConfigStreamPage::UpdateKeyLink() -{ - QString serviceName = ui->service->currentText(); - QString customServer = ui->customServer->text().trimmed(); - QString streamKeyLink; - - obs_properties_t *props = obs_get_service_properties("rtmp_common"); - obs_property_t *services = obs_properties_get(props, "service"); - - OBSDataAutoRelease settings = obs_data_create(); - - obs_data_set_string(settings, "service", QT_TO_UTF8(serviceName)); - obs_property_modified(services, settings); - - streamKeyLink = obs_data_get_string(settings, "stream_key_link"); - - if (customServer.contains("fbcdn.net") && IsCustomService()) { - streamKeyLink = "https://www.facebook.com/live/producer?ref=OBS"; - } - - if (serviceName == "Dacast") { - ui->streamKeyLabel->setText(QTStr("Basic.AutoConfig.StreamPage.EncoderKey")); - ui->streamKeyLabel->setToolTip(""); - } else if (!IsCustomService()) { - ui->streamKeyLabel->setText(QTStr("Basic.AutoConfig.StreamPage.StreamKey")); - ui->streamKeyLabel->setToolTip(""); - } else { - /* add tooltips for stream key */ - QString file = !App()->IsThemeDark() ? ":/res/images/help.svg" : ":/res/images/help_light.svg"; - QString lStr = "%1 "; - - ui->streamKeyLabel->setText(lStr.arg(QTStr("Basic.AutoConfig.StreamPage.StreamKey"), file)); - ui->streamKeyLabel->setToolTip(QTStr("Basic.AutoConfig.StreamPage.StreamKey.ToolTip")); - } - - if (QString(streamKeyLink).isNull() || QString(streamKeyLink).isEmpty()) { - ui->streamKeyButton->hide(); - } else { - ui->streamKeyButton->setTargetUrl(QUrl(streamKeyLink)); - ui->streamKeyButton->show(); - } - obs_properties_destroy(props); -} - -void AutoConfigStreamPage::LoadServices(bool showAll) -{ - obs_properties_t *props = obs_get_service_properties("rtmp_common"); - - OBSDataAutoRelease settings = obs_data_create(); - - obs_data_set_bool(settings, "show_all", showAll); - - obs_property_t *prop = obs_properties_get(props, "show_all"); - obs_property_modified(prop, settings); - - ui->service->blockSignals(true); - ui->service->clear(); - - QStringList names; - - obs_property_t *services = obs_properties_get(props, "service"); - size_t services_count = obs_property_list_item_count(services); - for (size_t i = 0; i < services_count; i++) { - const char *name = obs_property_list_item_string(services, i); - names.push_back(name); - } - - if (showAll) - names.sort(Qt::CaseInsensitive); - - for (QString &name : names) - ui->service->addItem(name); - - if (!showAll) { - ui->service->addItem(QTStr("Basic.AutoConfig.StreamPage.Service.ShowAll"), - QVariant((int)ListOpt::ShowAll)); - } - - ui->service->insertItem(0, QTStr("Basic.AutoConfig.StreamPage.Service.Custom"), QVariant((int)ListOpt::Custom)); - - if (!lastService.isEmpty()) { - int idx = ui->service->findText(lastService); - if (idx != -1) - ui->service->setCurrentIndex(idx); - } - - obs_properties_destroy(props); - - ui->service->blockSignals(false); -} - -void AutoConfigStreamPage::UpdateServerList() -{ - QString serviceName = ui->service->currentText(); - bool showMore = ui->service->currentData().toInt() == (int)ListOpt::ShowAll; - - if (showMore) { - LoadServices(true); - ui->service->showPopup(); - return; - } else { - lastService = serviceName; - } - - obs_properties_t *props = obs_get_service_properties("rtmp_common"); - obs_property_t *services = obs_properties_get(props, "service"); - - OBSDataAutoRelease settings = obs_data_create(); - - obs_data_set_string(settings, "service", QT_TO_UTF8(serviceName)); - obs_property_modified(services, settings); - - obs_property_t *servers = obs_properties_get(props, "server"); - - ui->server->clear(); - - size_t servers_count = obs_property_list_item_count(servers); - for (size_t i = 0; i < servers_count; i++) { - const char *name = obs_property_list_item_name(servers, i); - const char *server = obs_property_list_item_string(servers, i); - ui->server->addItem(name, server); - } - - obs_properties_destroy(props); -} - -void AutoConfigStreamPage::UpdateCompleted() -{ - const bool custom = IsCustomService(); - if (ui->stackedWidget->currentIndex() == (int)Section::Connect || - (ui->key->text().isEmpty() && !auth && !custom)) { - ready = false; - } else { - if (custom) { - ready = !ui->customServer->text().isEmpty(); - } else { - ready = !wiz->testRegions || ui->regionUS->isChecked() || ui->regionEU->isChecked() || - ui->regionAsia->isChecked() || ui->regionOther->isChecked(); - } - } - emit completeChanged(); -} - -/* ------------------------------------------------------------------------- */ - -AutoConfig::AutoConfig(QWidget *parent) : QWizard(parent) -{ - EnableThreadedMessageBoxes(true); - - calldata_t cd = {0}; - calldata_set_int(&cd, "seconds", 5); - - proc_handler_t *ph = obs_get_proc_handler(); - proc_handler_call(ph, "twitch_ingests_refresh", &cd); - proc_handler_call(ph, "amazon_ivs_ingests_refresh", &cd); - calldata_free(&cd); - - OBSBasic *main = reinterpret_cast(parent); - main->EnableOutputs(false); - - installEventFilter(CreateShortcutFilter()); - - std::string serviceType; - GetServiceInfo(serviceType, serviceName, server, key); -#if defined(_WIN32) || defined(__APPLE__) - setWizardStyle(QWizard::ModernStyle); -#endif - streamPage = new AutoConfigStreamPage(); - - setPage(StartPage, new AutoConfigStartPage()); - setPage(VideoPage, new AutoConfigVideoPage()); - setPage(StreamPage, streamPage); - setPage(TestPage, new AutoConfigTestPage()); - setWindowTitle(QTStr("Basic.AutoConfig")); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - - obs_video_info ovi; - obs_get_video_info(&ovi); - - baseResolutionCX = ovi.base_width; - baseResolutionCY = ovi.base_height; - - /* ----------------------------------------- */ - /* check to see if Twitch's "auto" available */ - - OBSDataAutoRelease twitchSettings = obs_data_create(); - - obs_data_set_string(twitchSettings, "service", "Twitch"); - - obs_properties_t *props = obs_get_service_properties("rtmp_common"); - obs_properties_apply_settings(props, twitchSettings); - - obs_property_t *p = obs_properties_get(props, "server"); - const char *first = obs_property_list_item_string(p, 0); - twitchAuto = strcmp(first, "auto") == 0; - - obs_properties_destroy(props); - - /* ----------------------------------------- */ - /* check to see if Amazon IVS "auto" entries are available */ - - OBSDataAutoRelease amazonIVSSettings = obs_data_create(); - - obs_data_set_string(amazonIVSSettings, "service", "Amazon IVS"); - - props = obs_get_service_properties("rtmp_common"); - obs_properties_apply_settings(props, amazonIVSSettings); - - p = obs_properties_get(props, "server"); - first = obs_property_list_item_string(p, 0); - amazonIVSAuto = strncmp(first, "auto", 4) == 0; - - obs_properties_destroy(props); - - /* ----------------------------------------- */ - /* load service/servers */ - - customServer = serviceType == "rtmp_custom"; - - QComboBox *serviceList = streamPage->ui->service; - - if (!serviceName.empty()) { - serviceList->blockSignals(true); - - int count = serviceList->count(); - bool found = false; - - for (int i = 0; i < count; i++) { - QString name = serviceList->itemText(i); - - if (name == serviceName.c_str()) { - serviceList->setCurrentIndex(i); - found = true; - break; - } - } - - if (!found) { - serviceList->insertItem(0, serviceName.c_str()); - serviceList->setCurrentIndex(0); - } - - serviceList->blockSignals(false); - } - - streamPage->UpdateServerList(); - streamPage->UpdateKeyLink(); - streamPage->UpdateMoreInfoLink(); - streamPage->lastService.clear(); - - if (!customServer) { - QComboBox *serverList = streamPage->ui->server; - int idx = serverList->findData(QString(server.c_str())); - if (idx == -1) - idx = 0; - - serverList->setCurrentIndex(idx); - } else { - streamPage->ui->customServer->setText(server.c_str()); - int idx = streamPage->ui->service->findData(QVariant((int)ListOpt::Custom)); - streamPage->ui->service->setCurrentIndex(idx); - } - - 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(); - - if (!hardwareEncodingAvailable) { - delete streamPage->ui->preferHardware; - streamPage->ui->preferHardware = nullptr; - } else { - /* Newer generations of NVENC have a high enough quality to - * bitrate ratio that if NVENC is available, it makes sense to - * just always prefer hardware encoding by default */ - bool preferHardware = nvencAvailable || appleAvailable || os_get_physical_cores() <= 4; - streamPage->ui->preferHardware->setChecked(preferHardware); - } - - setOptions(QWizard::WizardOptions()); - setButtonText(QWizard::FinishButton, QTStr("Basic.AutoConfig.ApplySettings")); - setButtonText(QWizard::BackButton, QTStr("Back")); - setButtonText(QWizard::NextButton, QTStr("Next")); - setButtonText(QWizard::CancelButton, QTStr("Cancel")); -} - -AutoConfig::~AutoConfig() -{ - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - main->EnableOutputs(true); - EnableThreadedMessageBoxes(false); -} - -void AutoConfig::TestHardwareEncoding() -{ - size_t idx = 0; - const char *id; - while (obs_enum_encoder_types(idx++, &id)) { - if (strcmp(id, "ffmpeg_nvenc") == 0) - hardwareEncodingAvailable = nvencAvailable = true; - else if (strcmp(id, "obs_qsv11") == 0) - hardwareEncodingAvailable = qsvAvailable = true; - else if (strcmp(id, "h264_texture_amf") == 0) - hardwareEncodingAvailable = vceAvailable = true; -#ifdef __APPLE__ - else if (strcmp(id, "com.apple.videotoolbox.videoencoder.ave.avc") == 0 -#ifndef __aarch64__ - && os_get_emulation_status() == true -#endif - ) - if (__builtin_available(macOS 13.0, *)) - hardwareEncodingAvailable = appleAvailable = true; -#endif - } -} - -bool AutoConfig::CanTestServer(const char *server) -{ - if (!testRegions || (regionUS && regionEU && regionAsia && regionOther)) - return true; - - if (service == Service::Twitch) { - if (astrcmp_n(server, "US West:", 8) == 0 || astrcmp_n(server, "US East:", 8) == 0 || - astrcmp_n(server, "US Central:", 11) == 0) { - return regionUS; - } else if (astrcmp_n(server, "EU:", 3) == 0) { - return regionEU; - } else if (astrcmp_n(server, "Asia:", 5) == 0) { - return regionAsia; - } else if (regionOther) { - return true; - } - } else { - return true; - } - - return false; -} - -void AutoConfig::done(int result) -{ - QWizard::done(result); - - if (result == QDialog::Accepted) { - if (type == Type::Streaming) - SaveStreamSettings(); - SaveSettings(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) { - OBSBasic *main = OBSBasic::Get(); - main->NewYouTubeAppDock(); - } -#endif - } -} - -inline const char *AutoConfig::GetEncoderId(Encoder enc) -{ - switch (enc) { - case Encoder::NVENC: - return SIMPLE_ENCODER_NVENC; - case Encoder::QSV: - return SIMPLE_ENCODER_QSV; - case Encoder::AMD: - return SIMPLE_ENCODER_AMD; - case Encoder::Apple: - return SIMPLE_ENCODER_APPLE_H264; - default: - return SIMPLE_ENCODER_X264; - } -}; - -void AutoConfig::SaveStreamSettings() -{ - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - /* ---------------------------------- */ - /* save service */ - - const char *service_id = customServer ? "rtmp_custom" : "rtmp_common"; - - obs_service_t *oldService = main->GetService(); - OBSDataAutoRelease hotkeyData = obs_hotkeys_save_service(oldService); - - OBSDataAutoRelease settings = obs_data_create(); - - if (!customServer) - obs_data_set_string(settings, "service", serviceName.c_str()); - obs_data_set_string(settings, "server", server.c_str()); -#ifdef YOUTUBE_ENABLED - if (!streamPage->auth || !IsYouTubeService(serviceName)) - obs_data_set_string(settings, "key", key.c_str()); -#else - obs_data_set_string(settings, "key", key.c_str()); -#endif - - OBSServiceAutoRelease newService = obs_service_create(service_id, "default_service", settings, hotkeyData); - - if (!newService) - return; - - main->SetService(newService); - main->SaveService(); - main->auth = streamPage->auth; - if (!!main->auth) { - main->auth->LoadUI(); - main->SetBroadcastFlowEnabled(main->auth->broadcastFlow()); - } else { - main->SetBroadcastFlowEnabled(false); - } - - /* ---------------------------------- */ - /* save stream settings */ - - config_set_int(main->Config(), "SimpleOutput", "VBitrate", idealBitrate); - 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() -{ - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - if (recordingEncoder != Encoder::Stream) - config_set_string(main->Config(), "SimpleOutput", "RecEncoder", GetEncoderId(recordingEncoder)); - - const char *quality = recordingQuality == Quality::High ? "Small" : "Stream"; - - config_set_string(main->Config(), "Output", "Mode", "Simple"); - config_set_string(main->Config(), "SimpleOutput", "RecQuality", quality); - config_set_int(main->Config(), "Video", "BaseCX", baseResolutionCX); - config_set_int(main->Config(), "Video", "BaseCY", baseResolutionCY); - config_set_int(main->Config(), "Video", "OutputCX", idealResolutionCX); - config_set_int(main->Config(), "Video", "OutputCY", idealResolutionCY); - - if (fpsType != FPSType::UseCurrent) { - config_set_uint(main->Config(), "Video", "FPSType", 0); - config_set_string(main->Config(), "Video", "FPSCommon", std::to_string(idealFPSNum).c_str()); - } - - main->ResetVideo(); - main->ResetOutputs(); - config_save_safe(main->Config(), "tmp", nullptr); -} diff --git a/frontend/wizards/AutoConfigVideoPage.hpp b/frontend/wizards/AutoConfigVideoPage.hpp index 7e31bdf89..b6a441d2c 100644 --- a/frontend/wizards/AutoConfigVideoPage.hpp +++ b/frontend/wizards/AutoConfigVideoPage.hpp @@ -1,164 +1,8 @@ #pragma once -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include - -class Ui_AutoConfigStartPage; class Ui_AutoConfigVideoPage; -class Ui_AutoConfigStreamPage; -class Ui_AutoConfigTestPage; - -class AutoConfigStreamPage; -class Auth; - -class AutoConfig : public QWizard { - Q_OBJECT - - friend class AutoConfigStartPage; - friend class AutoConfigVideoPage; - friend class AutoConfigStreamPage; - friend class AutoConfigTestPage; - - enum class Type { - Invalid, - Streaming, - Recording, - VirtualCam, - }; - - enum class Service { - Twitch, - YouTube, - AmazonIVS, - Other, - }; - - enum class Encoder { - x264, - NVENC, - QSV, - AMD, - Apple, - Stream, - }; - - enum class Quality { - Stream, - High, - }; - - enum class FPSType : int { - PreferHighFPS, - PreferHighRes, - UseCurrent, - fps30, - fps60, - }; - - struct StreamServer { - std::string name; - std::string address; - }; - - static inline const char *GetEncoderId(Encoder enc); - - AutoConfigStreamPage *streamPage = nullptr; - - Service service = Service::Other; - Quality recordingQuality = Quality::Stream; - Encoder recordingEncoder = Encoder::Stream; - Encoder streamingEncoder = Encoder::x264; - 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; - int idealResolutionCY = 720; - int idealFPSNum = 60; - int idealFPSDen = 1; - std::string serviceName; - std::string serverName; - std::string server; - std::vector serviceConfigServers; - std::string key; - - bool hardwareEncodingAvailable = false; - bool nvencAvailable = false; - bool qsvAvailable = false; - bool vceAvailable = false; - bool appleAvailable = false; - - int startingBitrate = 2500; - bool customServer = false; - bool bandwidthTest = false; - bool testMultitrackVideo = false; - bool testRegions = true; - bool twitchAuto = false; - bool amazonIVSAuto = false; - bool regionUS = true; - bool regionEU = true; - bool regionAsia = true; - bool regionOther = true; - bool preferHighFPS = false; - bool preferHardware = false; - int specificFPSNum = 0; - int specificFPSDen = 0; - - void TestHardwareEncoding(); - bool CanTestServer(const char *server); - - virtual void done(int result) override; - - void SaveStreamSettings(); - void SaveSettings(); - -public: - AutoConfig(QWidget *parent); - ~AutoConfig(); - - enum Page { - StartPage, - VideoPage, - StreamPage, - TestPage, - }; -}; - -class AutoConfigStartPage : public QWizardPage { - Q_OBJECT - - friend class AutoConfig; - - std::unique_ptr ui; - -public: - AutoConfigStartPage(QWidget *parent = nullptr); - ~AutoConfigStartPage(); - - virtual int nextId() const override; - -public slots: - void on_prioritizeStreaming_clicked(); - void on_prioritizeRecording_clicked(); - void PrioritizeVCam(); -}; class AutoConfigVideoPage : public QWizardPage { Q_OBJECT @@ -174,115 +18,3 @@ public: virtual int nextId() const override; virtual bool validatePage() override; }; - -class AutoConfigStreamPage : public QWizardPage { - Q_OBJECT - - friend class AutoConfig; - - enum class Section : int { - Connect, - StreamKey, - }; - - std::shared_ptr auth; - - std::unique_ptr ui; - QString lastService; - bool ready = false; - - void LoadServices(bool showAll); - inline bool IsCustomService() const; - -public: - AutoConfigStreamPage(QWidget *parent = nullptr); - ~AutoConfigStreamPage(); - - virtual bool isComplete() const override; - virtual int nextId() const override; - virtual bool validatePage() override; - - void OnAuthConnected(); - void OnOAuthStreamKeyConnected(); - -public slots: - void on_show_clicked(); - void on_connectAccount_clicked(); - void on_disconnectAccount_clicked(); - void on_useStreamKey_clicked(); - void on_preferHardware_clicked(); - void ServiceChanged(); - void UpdateKeyLink(); - void UpdateMoreInfoLink(); - void UpdateServerList(); - void UpdateCompleted(); - - void reset_service_ui_fields(std::string &service); -}; - -class AutoConfigTestPage : public QWizardPage { - Q_OBJECT - - friend class AutoConfig; - - QPointer results; - - std::unique_ptr ui; - std::thread testThread; - std::condition_variable cv; - std::mutex m; - bool cancel = false; - bool started = false; - - enum class Stage { - Starting, - BandwidthTest, - StreamEncoder, - RecordingEncoder, - Finished, - }; - - Stage stage = Stage::Starting; - bool softwareTested = false; - - void StartBandwidthStage(); - void StartStreamEncoderStage(); - void StartRecordingEncoderStage(); - - void FindIdealHardwareResolution(); - bool TestSoftwareEncoding(); - - void TestBandwidthThread(); - void TestStreamEncoderThread(); - void TestRecordingEncoderThread(); - - void FinalizeResults(); - - struct ServerInfo { - std::string name; - std::string address; - int bitrate = 0; - int ms = -1; - - inline ServerInfo() {} - - inline ServerInfo(const char *name_, const char *address_) : name(name_), address(address_) {} - }; - - void GetServers(std::vector &servers); - -public: - AutoConfigTestPage(QWidget *parent = nullptr); - ~AutoConfigTestPage(); - - virtual void initializePage() override; - virtual void cleanupPage() override; - virtual bool isComplete() const override; - virtual int nextId() const override; - -public slots: - void NextStage(); - void UpdateMessage(QString message); - void Failure(QString message); - void Progress(int percentage); -}; diff --git a/frontend/wizards/TestMode.hpp b/frontend/wizards/TestMode.hpp index b53f37aa2..fd9b80646 100644 --- a/frontend/wizards/TestMode.hpp +++ b/frontend/wizards/TestMode.hpp @@ -1,26 +1,7 @@ -#include +#pragma once -#include - -#include -#include -#include -#include -#include #include -#include - -#include "window-basic-auto-config.hpp" -#include "window-basic-main.hpp" -#include "obs-app.hpp" - -#include "ui_AutoConfigTestPage.h" - -#define wiz reinterpret_cast(wizard()) - -using namespace std; - -/* ------------------------------------------------------------------------- */ +#include class TestMode { obs_video_info ovi; @@ -79,1183 +60,3 @@ public: obs_reset_video(&newOVI); } }; - -/* ------------------------------------------------------------------------- */ - -#define TEST_STR(x) "Basic.AutoConfig.TestPage." x -#define SUBTITLE_TESTING TEST_STR("SubTitle.Testing") -#define SUBTITLE_COMPLETE TEST_STR("SubTitle.Complete") -#define TEST_BW TEST_STR("TestingBandwidth") -#define TEST_BW_NO_OUTPUT TEST_STR("TestingBandwidth.NoOutput") -#define TEST_BW_CONNECTING TEST_STR("TestingBandwidth.Connecting") -#define TEST_BW_CONNECT_FAIL TEST_STR("TestingBandwidth.ConnectFailed") -#define TEST_BW_SERVER TEST_STR("TestingBandwidth.Server") -#define TEST_RES_VAL TEST_STR("TestingRes.Resolution") -#define TEST_RES_FAIL TEST_STR("TestingRes.Fail") -#define TEST_SE TEST_STR("TestingStreamEncoder") -#define TEST_RE TEST_STR("TestingRecordingEncoder") -#define TEST_RESULT_SE TEST_STR("Result.StreamingEncoder") -#define TEST_RESULT_RE TEST_STR("Result.RecordingEncoder") - -void AutoConfigTestPage::StartBandwidthStage() -{ - ui->progressLabel->setText(QTStr(TEST_BW)); - testThread = std::thread([this]() { TestBandwidthThread(); }); -} - -void AutoConfigTestPage::StartStreamEncoderStage() -{ - ui->progressLabel->setText(QTStr(TEST_SE)); - testThread = std::thread([this]() { TestStreamEncoderThread(); }); -} - -void AutoConfigTestPage::StartRecordingEncoderStage() -{ - ui->progressLabel->setText(QTStr(TEST_RE)); - testThread = std::thread([this]() { TestRecordingEncoderThread(); }); -} - -void AutoConfigTestPage::GetServers(std::vector &servers) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "service", wiz->serviceName.c_str()); - - obs_properties_t *ppts = obs_get_service_properties("rtmp_common"); - obs_property_t *p = obs_properties_get(ppts, "service"); - obs_property_modified(p, settings); - - p = obs_properties_get(ppts, "server"); - size_t count = obs_property_list_item_count(p); - servers.reserve(count); - - for (size_t i = 0; i < count; i++) { - const char *name = obs_property_list_item_name(p, i); - const char *server = obs_property_list_item_string(p, i); - - if (wiz->CanTestServer(name)) { - ServerInfo info(name, server); - servers.push_back(info); - } - } - - obs_properties_destroy(ppts); -} - -static inline void string_depad_key(string &key) -{ - while (!key.empty()) { - char ch = key.back(); - if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r') - key.pop_back(); - else - break; - } -} - -const char *FindAudioEncoderFromCodec(const char *type); - -static inline bool can_use_output(const char *prot, const char *output, const char *prot_test1, - const char *prot_test2 = nullptr) -{ - return (strcmp(prot, prot_test1) == 0 || (prot_test2 && strcmp(prot, prot_test2) == 0)) && - (obs_get_output_flags(output) & OBS_OUTPUT_SERVICE) != 0; -} - -static bool return_first_id(void *data, const char *id) -{ - const char **output = (const char **)data; - - *output = id; - return false; -} - -void AutoConfigTestPage::TestBandwidthThread() -{ - bool connected = false; - bool stopped = false; - - TestMode testMode; - testMode.SetVideo(128, 128, 60, 1); - - QMetaObject::invokeMethod(this, "Progress", Q_ARG(int, 0)); - - /* - * create encoders - * create output - * test for 10 seconds - */ - - QMetaObject::invokeMethod(this, "UpdateMessage", Q_ARG(QString, QStringLiteral(""))); - - /* -----------------------------------*/ - /* create obs objects */ - - const char *serverType = wiz->customServer ? "rtmp_custom" : "rtmp_common"; - - OBSEncoderAutoRelease vencoder = obs_video_encoder_create("obs_x264", "test_x264", nullptr, nullptr); - OBSEncoderAutoRelease aencoder = obs_audio_encoder_create("ffmpeg_aac", "test_aac", nullptr, 0, nullptr); - OBSServiceAutoRelease service = obs_service_create(serverType, "test_service", nullptr, nullptr); - - /* -----------------------------------*/ - /* configure settings */ - - // service: "service", "server", "key" - // vencoder: "bitrate", "rate_control", - // obs_service_apply_encoder_settings - // aencoder: "bitrate" - // output: "bind_ip" via main config -> "Output", "BindIP" - // obs_output_set_service - - OBSDataAutoRelease service_settings = obs_data_create(); - OBSDataAutoRelease vencoder_settings = obs_data_create(); - OBSDataAutoRelease aencoder_settings = obs_data_create(); - OBSDataAutoRelease output_settings = obs_data_create(); - - std::string key = wiz->key; - if (wiz->service == AutoConfig::Service::Twitch || wiz->service == AutoConfig::Service::AmazonIVS) { - string_depad_key(key); - key += "?bandwidthtest"; - } else if (wiz->serviceName == "Restream.io" || wiz->serviceName == "Restream.io - RTMP") { - string_depad_key(key); - key += "?test=true"; - } - - obs_data_set_string(service_settings, "service", wiz->serviceName.c_str()); - obs_data_set_string(service_settings, "key", key.c_str()); - - obs_data_set_int(vencoder_settings, "bitrate", wiz->startingBitrate); - obs_data_set_string(vencoder_settings, "rate_control", "CBR"); - obs_data_set_string(vencoder_settings, "preset", "veryfast"); - obs_data_set_int(vencoder_settings, "keyint_sec", 2); - - obs_data_set_int(aencoder_settings, "bitrate", 32); - - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - const char *bind_ip = config_get_string(main->Config(), "Output", "BindIP"); - obs_data_set_string(output_settings, "bind_ip", bind_ip); - - const char *ip_family = config_get_string(main->Config(), "Output", "IPFamily"); - obs_data_set_string(output_settings, "ip_family", ip_family); - - /* -----------------------------------*/ - /* determine which servers to test */ - - std::vector servers; - if (wiz->customServer) - servers.emplace_back(wiz->server.c_str(), wiz->server.c_str()); - else - GetServers(servers); - - /* just use the first server if it only has one alternate server, - * or if using Restream or Nimo TV due to their "auto" servers */ - if (servers.size() < 3 || wiz->serviceName.substr(0, 11) == "Restream.io" || wiz->serviceName == "Nimo TV") { - servers.resize(1); - - } else if ((wiz->service == AutoConfig::Service::Twitch && wiz->twitchAuto) || - (wiz->service == AutoConfig::Service::AmazonIVS && wiz->amazonIVSAuto)) { - /* if using Twitch and "Auto" is available, test 3 closest - * server */ - servers.erase(servers.begin() + 1); - servers.resize(3); - } else if (wiz->service == AutoConfig::Service::YouTube) { - /* Only test first set of primary + backup servers */ - 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 */ - - obs_service_update(service, service_settings); - 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 */ - - /* Check if the service has a preferred output type */ - const char *output_type = obs_service_get_preferred_output_type(service); - if (!output_type || (obs_get_output_flags(output_type) & OBS_OUTPUT_SERVICE) == 0) { - /* Otherwise, prefer first-party output types */ - const char *protocol = obs_service_get_protocol(service); - - if (can_use_output(protocol, "rtmp_output", "RTMP", "RTMPS")) { - output_type = "rtmp_output"; - } else if (can_use_output(protocol, "ffmpeg_hls_muxer", "HLS")) { - output_type = "ffmpeg_hls_muxer"; - } else if (can_use_output(protocol, "ffmpeg_mpegts_muxer", "SRT", "RIST")) { - output_type = "ffmpeg_mpegts_muxer"; - } - - /* If third-party protocol, use the first enumerated type */ - if (!output_type) - obs_enum_output_types_with_protocol(protocol, &output_type, return_first_id); - - /* If none, fail */ - if (!output_type) { - QMetaObject::invokeMethod(this, "Failure", Q_ARG(QString, QTStr(TEST_BW_NO_OUTPUT))); - return; - } - } - - OBSOutputAutoRelease output = obs_output_create(output_type, "test_stream", nullptr, nullptr); - obs_output_update(output, output_settings); - - const char *audio_codec = obs_output_get_supported_audio_codecs(output); - - if (strcmp(audio_codec, "aac") != 0) { - const char *id = FindAudioEncoderFromCodec(audio_codec); - aencoder = obs_audio_encoder_create(id, "test_audio", nullptr, 0, nullptr); - } - - /* -----------------------------------*/ - /* connect encoders/services/outputs */ - - obs_encoder_update(vencoder, vencoder_settings); - obs_encoder_update(aencoder, aencoder_settings); - obs_encoder_set_video(vencoder, obs_get_video()); - obs_encoder_set_audio(aencoder, obs_get_audio()); - - obs_output_set_video_encoder(output, vencoder); - obs_output_set_audio_encoder(output, aencoder, 0); - obs_output_set_reconnect_settings(output, 0, 0); - - obs_output_set_service(output, service); - - /* -----------------------------------*/ - /* connect signals */ - - auto on_started = [&]() { - unique_lock lock(m); - connected = true; - stopped = false; - cv.notify_one(); - }; - - auto on_stopped = [&]() { - unique_lock lock(m); - connected = false; - stopped = true; - cv.notify_one(); - }; - - using on_started_t = decltype(on_started); - using on_stopped_t = decltype(on_stopped); - - auto pre_on_started = [](void *data, calldata_t *) { - on_started_t &on_started = *reinterpret_cast(data); - on_started(); - }; - - auto pre_on_stopped = [](void *data, calldata_t *) { - on_stopped_t &on_stopped = *reinterpret_cast(data); - on_stopped(); - }; - - signal_handler *sh = obs_output_get_signal_handler(output); - signal_handler_connect(sh, "start", pre_on_started, &on_started); - signal_handler_connect(sh, "stop", pre_on_stopped, &on_stopped); - - /* -----------------------------------*/ - /* test servers */ - - bool success = false; - - for (size_t i = 0; i < servers.size(); i++) { - auto &server = servers[i]; - - connected = false; - stopped = false; - - int per = int((i + 1) * 100 / servers.size()); - QMetaObject::invokeMethod(this, "Progress", Q_ARG(int, per)); - QMetaObject::invokeMethod(this, "UpdateMessage", - Q_ARG(QString, QTStr(TEST_BW_CONNECTING).arg(server.name.c_str()))); - - obs_data_set_string(service_settings, "server", server.address.c_str()); - obs_service_update(service, service_settings); - - if (!obs_output_start(output)) - continue; - - unique_lock ul(m); - if (cancel) { - ul.unlock(); - obs_output_force_stop(output); - return; - } - if (!stopped && !connected) - cv.wait(ul); - if (cancel) { - ul.unlock(); - obs_output_force_stop(output); - return; - } - if (!connected) - continue; - - QMetaObject::invokeMethod(this, "UpdateMessage", - Q_ARG(QString, QTStr(TEST_BW_SERVER).arg(server.name.c_str()))); - - /* ignore first 2.5 seconds due to possible buffering skewing - * the result */ - cv.wait_for(ul, chrono::milliseconds(2500)); - if (stopped) - continue; - if (cancel) { - ul.unlock(); - obs_output_force_stop(output); - return; - } - - /* continue test */ - int start_bytes = (int)obs_output_get_total_bytes(output); - uint64_t t_start = os_gettime_ns(); - - cv.wait_for(ul, chrono::seconds(10)); - if (stopped) - continue; - if (cancel) { - ul.unlock(); - obs_output_force_stop(output); - return; - } - - obs_output_stop(output); - cv.wait(ul); - - uint64_t total_time = os_gettime_ns() - t_start; - if (total_time == 0) - total_time = 1; - - int total_bytes = (int)obs_output_get_total_bytes(output) - start_bytes; - uint64_t bitrate = util_mul_div64(total_bytes, 8ULL * 1000000000ULL / 1000ULL, total_time); - if (obs_output_get_frames_dropped(output) || (int)bitrate < (wiz->startingBitrate * 75 / 100)) { - server.bitrate = (int)bitrate * 70 / 100; - } else { - server.bitrate = wiz->startingBitrate; - } - - server.ms = obs_output_get_connect_time_ms(output); - success = true; - } - - if (!success) { - QMetaObject::invokeMethod(this, "Failure", Q_ARG(QString, QTStr(TEST_BW_CONNECT_FAIL))); - return; - } - - int bestBitrate = 0; - int bestMS = 0x7FFFFFFF; - string bestServer; - string bestServerName; - - for (auto &server : servers) { - bool close = abs(server.bitrate - bestBitrate) < 400; - - if ((!close && server.bitrate > bestBitrate) || (close && server.ms < bestMS)) { - bestServer = server.address; - bestServerName = server.name; - bestBitrate = server.bitrate; - bestMS = server.ms; - } - } - - wiz->server = std::move(bestServer); - wiz->serverName = std::move(bestServerName); - wiz->idealBitrate = bestBitrate; - - QMetaObject::invokeMethod(this, "NextStage"); -} - -/* this is used to estimate the lower bitrate limit for a given - * resolution/fps. yes, it is a totally arbitrary equation that gets - * the closest to the expected values */ -static long double EstimateBitrateVal(int cx, int cy, int fps_num, int fps_den) -{ - long fps = (long double)fps_num / (long double)fps_den; - long double areaVal = pow((long double)(cx * cy), 0.85l); - return areaVal * sqrt(pow(fps, 1.1l)); -} - -static long double EstimateMinBitrate(int cx, int cy, int fps_num, int fps_den) -{ - long double val = EstimateBitrateVal(1920, 1080, 60, 1) / 5800.0l; - return EstimateBitrateVal(cx, cy, fps_num, fps_den) / val; -} - -static long double EstimateUpperBitrate(int cx, int cy, int fps_num, int fps_den) -{ - long double val = EstimateBitrateVal(1280, 720, 30, 1) / 3000.0l; - return EstimateBitrateVal(cx, cy, fps_num, fps_den) / val; -} - -struct Result { - int cx; - int cy; - int fps_num; - int fps_den; - - inline Result(int cx_, int cy_, int fps_num_, int fps_den_) - : cx(cx_), - cy(cy_), - fps_num(fps_num_), - fps_den(fps_den_) - { - } -}; - -static void CalcBaseRes(int &baseCX, int &baseCY) -{ - const int maxBaseArea = 1920 * 1200; - const int clipResArea = 1920 * 1080; - - /* if base resolution unusually high, recalculate to a more reasonable - * value to start the downscaling at, based upon 1920x1080's area. - * - * for 16:9 resolutions this will always change the starting value to - * 1920x1080 */ - if ((baseCX * baseCY) > maxBaseArea) { - long double xyAspect = (long double)baseCX / (long double)baseCY; - baseCY = (int)sqrt((long double)clipResArea / xyAspect); - baseCX = (int)((long double)baseCY * xyAspect); - } -} - -bool AutoConfigTestPage::TestSoftwareEncoding() -{ - TestMode testMode; - QMetaObject::invokeMethod(this, "UpdateMessage", Q_ARG(QString, QStringLiteral(""))); - - /* -----------------------------------*/ - /* create obs objects */ - - OBSEncoderAutoRelease vencoder = obs_video_encoder_create("obs_x264", "test_x264", nullptr, nullptr); - OBSEncoderAutoRelease aencoder = obs_audio_encoder_create("ffmpeg_aac", "test_aac", nullptr, 0, nullptr); - OBSOutputAutoRelease output = obs_output_create("null_output", "null", nullptr, nullptr); - - /* -----------------------------------*/ - /* configure settings */ - - OBSDataAutoRelease aencoder_settings = obs_data_create(); - OBSDataAutoRelease vencoder_settings = obs_data_create(); - obs_data_set_int(aencoder_settings, "bitrate", 32); - - if (wiz->type != AutoConfig::Type::Recording) { - obs_data_set_int(vencoder_settings, "keyint_sec", 2); - obs_data_set_int(vencoder_settings, "bitrate", wiz->idealBitrate); - obs_data_set_string(vencoder_settings, "rate_control", "CBR"); - obs_data_set_string(vencoder_settings, "profile", "main"); - obs_data_set_string(vencoder_settings, "preset", "veryfast"); - } else { - obs_data_set_int(vencoder_settings, "crf", 20); - obs_data_set_string(vencoder_settings, "rate_control", "CRF"); - obs_data_set_string(vencoder_settings, "profile", "high"); - obs_data_set_string(vencoder_settings, "preset", "veryfast"); - } - - /* -----------------------------------*/ - /* apply settings */ - - obs_encoder_update(vencoder, vencoder_settings); - obs_encoder_update(aencoder, aencoder_settings); - - /* -----------------------------------*/ - /* connect encoders/services/outputs */ - - obs_output_set_video_encoder(output, vencoder); - obs_output_set_audio_encoder(output, aencoder, 0); - - /* -----------------------------------*/ - /* connect signals */ - - auto on_stopped = [&]() { - unique_lock lock(m); - cv.notify_one(); - }; - - using on_stopped_t = decltype(on_stopped); - - auto pre_on_stopped = [](void *data, calldata_t *) { - on_stopped_t &on_stopped = *reinterpret_cast(data); - on_stopped(); - }; - - signal_handler *sh = obs_output_get_signal_handler(output); - signal_handler_connect(sh, "deactivate", pre_on_stopped, &on_stopped); - - /* -----------------------------------*/ - /* calculate starting resolution */ - - int baseCX = wiz->baseResolutionCX; - int baseCY = wiz->baseResolutionCY; - CalcBaseRes(baseCX, baseCY); - - /* -----------------------------------*/ - /* calculate starting test rates */ - - int pcores = os_get_physical_cores(); - int lcores = os_get_logical_cores(); - int maxDataRate; - if (lcores > 8 || pcores > 4) { - /* superb */ - maxDataRate = 1920 * 1200 * 60 + 1000; - - } else if (lcores > 4 && pcores == 4) { - /* great */ - maxDataRate = 1920 * 1080 * 60 + 1000; - - } else if (pcores == 4) { - /* okay */ - maxDataRate = 1920 * 1080 * 30 + 1000; - - } else { - /* toaster */ - maxDataRate = 960 * 540 * 30 + 1000; - } - - /* -----------------------------------*/ - /* perform tests */ - - vector results; - int i = 0; - int count = 1; - - auto testRes = [&](int cy, int fps_num, int fps_den, bool force) { - int per = ++i * 100 / count; - QMetaObject::invokeMethod(this, "Progress", Q_ARG(int, per)); - - if (cy > baseCY) - return true; - - /* no need for more than 3 tests max */ - if (results.size() >= 3) - return true; - - if (!fps_num || !fps_den) { - fps_num = wiz->specificFPSNum; - fps_den = wiz->specificFPSDen; - } - - long double fps = ((long double)fps_num / (long double)fps_den); - - int cx = int(((long double)baseCX / (long double)baseCY) * (long double)cy); - - if (!force && wiz->type != AutoConfig::Type::Recording) { - int est = EstimateMinBitrate(cx, cy, fps_num, fps_den); - if (est > wiz->idealBitrate) - return true; - } - - long double rate = (long double)cx * (long double)cy * fps; - if (!force && rate > maxDataRate) - return true; - - testMode.SetVideo(cx, cy, fps_num, fps_den); - - obs_encoder_set_video(vencoder, obs_get_video()); - obs_encoder_set_audio(aencoder, obs_get_audio()); - obs_encoder_update(vencoder, vencoder_settings); - - obs_output_set_media(output, obs_get_video(), obs_get_audio()); - - QString cxStr = QString::number(cx); - QString cyStr = QString::number(cy); - - QString fpsStr = (fps_den > 1) ? QString::number(fps, 'f', 2) : QString::number(fps, 'g', 2); - - QMetaObject::invokeMethod(this, "UpdateMessage", - Q_ARG(QString, QTStr(TEST_RES_VAL).arg(cxStr, cyStr, fpsStr))); - - unique_lock ul(m); - if (cancel) - return false; - - if (!obs_output_start(output)) { - QMetaObject::invokeMethod(this, "Failure", Q_ARG(QString, QTStr(TEST_RES_FAIL))); - return false; - } - - cv.wait_for(ul, chrono::seconds(5)); - - obs_output_stop(output); - cv.wait(ul); - - int skipped = (int)video_output_get_skipped_frames(obs_get_video()); - if (force || skipped <= 10) - results.emplace_back(cx, cy, fps_num, fps_den); - - return !cancel; - }; - - if (wiz->specificFPSNum && wiz->specificFPSDen) { - count = 7; - if (!testRes(2160, 0, 0, false)) - return false; - if (!testRes(1440, 0, 0, false)) - return false; - if (!testRes(1080, 0, 0, false)) - return false; - if (!testRes(720, 0, 0, false)) - return false; - if (!testRes(480, 0, 0, false)) - return false; - if (!testRes(360, 0, 0, false)) - return false; - if (!testRes(240, 0, 0, true)) - return false; - } else { - count = 14; - if (!testRes(2160, 60, 1, false)) - return false; - if (!testRes(2160, 30, 1, false)) - return false; - if (!testRes(1440, 60, 1, false)) - return false; - if (!testRes(1440, 30, 1, false)) - return false; - if (!testRes(1080, 60, 1, false)) - return false; - if (!testRes(1080, 30, 1, false)) - return false; - if (!testRes(720, 60, 1, false)) - return false; - if (!testRes(720, 30, 1, false)) - return false; - if (!testRes(480, 60, 1, false)) - return false; - if (!testRes(480, 30, 1, false)) - return false; - if (!testRes(360, 60, 1, false)) - return false; - if (!testRes(360, 30, 1, false)) - return false; - if (!testRes(240, 60, 1, false)) - return false; - if (!testRes(240, 30, 1, true)) - return false; - } - - /* -----------------------------------*/ - /* find preferred settings */ - - int minArea = 960 * 540 + 1000; - - if (!wiz->specificFPSNum && wiz->preferHighFPS && results.size() > 1) { - Result &result1 = results[0]; - Result &result2 = results[1]; - - if (result1.fps_num == 30 && result2.fps_num == 60) { - int nextArea = result2.cx * result2.cy; - if (nextArea >= minArea) - results.erase(results.begin()); - } - } - - Result result = results.front(); - wiz->idealResolutionCX = result.cx; - wiz->idealResolutionCY = result.cy; - wiz->idealFPSNum = result.fps_num; - wiz->idealFPSDen = result.fps_den; - - long double fUpperBitrate = EstimateUpperBitrate(result.cx, result.cy, result.fps_num, result.fps_den); - - int upperBitrate = int(floor(fUpperBitrate / 50.0l) * 50.0l); - - if (wiz->streamingEncoder != AutoConfig::Encoder::x264) { - upperBitrate *= 114; - 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; - - softwareTested = true; - return true; -} - -void AutoConfigTestPage::FindIdealHardwareResolution() -{ - int baseCX = wiz->baseResolutionCX; - int baseCY = wiz->baseResolutionCY; - CalcBaseRes(baseCX, baseCY); - - vector results; - - int pcores = os_get_physical_cores(); - int maxDataRate; - if (pcores >= 4) { - maxDataRate = 1920 * 1200 * 60 + 1000; - } else { - maxDataRate = 1280 * 720 * 30 + 1000; - } - - auto testRes = [&](int cy, int fps_num, int fps_den, bool force) { - if (cy > baseCY) - return; - - if (results.size() >= 3) - return; - - if (!fps_num || !fps_den) { - fps_num = wiz->specificFPSNum; - fps_den = wiz->specificFPSDen; - } - - long double fps = ((long double)fps_num / (long double)fps_den); - - int cx = int(((long double)baseCX / (long double)baseCY) * (long double)cy); - - long double rate = (long double)cx * (long double)cy * fps; - if (!force && rate > maxDataRate) - return; - - AutoConfig::Encoder encType = wiz->streamingEncoder; - bool nvenc = encType == AutoConfig::Encoder::NVENC; - - int minBitrate = EstimateMinBitrate(cx, cy, fps_num, fps_den); - - /* most hardware encoders don't have a good quality to bitrate - * ratio, so increase the minimum bitrate estimate for them. - * NVENC currently is the exception because of the improvements - * its made to its quality in recent generations. */ - if (!nvenc) - minBitrate = minBitrate * 114 / 100; - - if (wiz->type == AutoConfig::Type::Recording) - force = true; - if (force || wiz->idealBitrate >= minBitrate) - results.emplace_back(cx, cy, fps_num, fps_den); - }; - - if (wiz->specificFPSNum && wiz->specificFPSDen) { - testRes(2160, 0, 0, false); - testRes(1440, 0, 0, false); - testRes(1080, 0, 0, false); - testRes(720, 0, 0, false); - testRes(480, 0, 0, false); - testRes(360, 0, 0, false); - testRes(240, 0, 0, true); - } else { - testRes(2160, 60, 1, false); - testRes(2160, 30, 1, false); - testRes(1440, 60, 1, false); - testRes(1440, 30, 1, false); - testRes(1080, 60, 1, false); - testRes(1080, 30, 1, false); - testRes(720, 60, 1, false); - testRes(720, 30, 1, false); - testRes(480, 60, 1, false); - testRes(480, 30, 1, false); - testRes(360, 60, 1, false); - testRes(360, 30, 1, false); - testRes(240, 60, 1, false); - testRes(240, 30, 1, true); - } - - int minArea = 960 * 540 + 1000; - - if (!wiz->specificFPSNum && wiz->preferHighFPS && results.size() > 1) { - Result &result1 = results[0]; - Result &result2 = results[1]; - - if (result1.fps_num == 30 && result2.fps_num == 60) { - int nextArea = result2.cx * result2.cy; - if (nextArea >= minArea) - results.erase(results.begin()); - } - } - - Result result = results.front(); - wiz->idealResolutionCX = result.cx; - wiz->idealResolutionCY = result.cy; - wiz->idealFPSNum = result.fps_num; - wiz->idealFPSDen = result.fps_den; -} - -void AutoConfigTestPage::TestStreamEncoderThread() -{ - bool preferHardware = wiz->preferHardware; - if (!softwareTested) { - if (!preferHardware || !wiz->hardwareEncodingAvailable) { - if (!TestSoftwareEncoding()) { - return; - } - } - } - - if (!softwareTested) { - if (wiz->nvencAvailable) - wiz->streamingEncoder = AutoConfig::Encoder::NVENC; - else if (wiz->qsvAvailable) - wiz->streamingEncoder = AutoConfig::Encoder::QSV; - else if (wiz->appleAvailable) - wiz->streamingEncoder = AutoConfig::Encoder::Apple; - else - wiz->streamingEncoder = AutoConfig::Encoder::AMD; - } else { - wiz->streamingEncoder = AutoConfig::Encoder::x264; - } - -#ifdef __linux__ - // On linux CBR rate control is not guaranteed so fallback to x264. - if (wiz->streamingEncoder == AutoConfig::Encoder::QSV) { - wiz->streamingEncoder = AutoConfig::Encoder::x264; - if (!TestSoftwareEncoding()) { - return; - } - } -#endif - - if (preferHardware && !softwareTested && wiz->hardwareEncodingAvailable) - FindIdealHardwareResolution(); - - QMetaObject::invokeMethod(this, "NextStage"); -} - -void AutoConfigTestPage::TestRecordingEncoderThread() -{ - if (!wiz->hardwareEncodingAvailable && !softwareTested) { - if (!TestSoftwareEncoding()) { - return; - } - } - - if (wiz->type == AutoConfig::Type::Recording && wiz->hardwareEncodingAvailable) - FindIdealHardwareResolution(); - - wiz->recordingQuality = AutoConfig::Quality::High; - - bool recordingOnly = wiz->type == AutoConfig::Type::Recording; - - if (wiz->hardwareEncodingAvailable) { - if (wiz->nvencAvailable) - wiz->recordingEncoder = AutoConfig::Encoder::NVENC; - else if (wiz->qsvAvailable) - wiz->recordingEncoder = AutoConfig::Encoder::QSV; - else if (wiz->appleAvailable) - wiz->recordingEncoder = AutoConfig::Encoder::Apple; - else - wiz->recordingEncoder = AutoConfig::Encoder::AMD; - } else { - wiz->recordingEncoder = AutoConfig::Encoder::x264; - } - - if (wiz->recordingEncoder != AutoConfig::Encoder::NVENC) { - if (!recordingOnly) { - wiz->recordingEncoder = AutoConfig::Encoder::Stream; - wiz->recordingQuality = AutoConfig::Quality::Stream; - } - } - - QMetaObject::invokeMethod(this, "NextStage"); -} - -#define ENCODER_TEXT(x) "Basic.Settings.Output.Simple.Encoder." x -#define ENCODER_SOFTWARE ENCODER_TEXT("Software") -#define ENCODER_NVENC ENCODER_TEXT("Hardware.NVENC.H264") -#define ENCODER_QSV ENCODER_TEXT("Hardware.QSV.H264") -#define ENCODER_AMD ENCODER_TEXT("Hardware.AMD.H264") -#define ENCODER_APPLE ENCODER_TEXT("Hardware.Apple.H264") - -#define QUALITY_SAME "Basic.Settings.Output.Simple.RecordingQuality.Stream" -#define QUALITY_HIGH "Basic.Settings.Output.Simple.RecordingQuality.Small" - -void set_closest_res(int &cx, int &cy, struct obs_service_resolution *res_list, size_t count) -{ - int best_pixel_diff = 0x7FFFFFFF; - int start_cx = cx; - int start_cy = cy; - - for (size_t i = 0; i < count; i++) { - struct obs_service_resolution &res = res_list[i]; - int pixel_cx_diff = abs(start_cx - res.cx); - int pixel_cy_diff = abs(start_cy - res.cy); - int pixel_diff = pixel_cx_diff + pixel_cy_diff; - - if (pixel_diff < best_pixel_diff) { - best_pixel_diff = pixel_diff; - cx = res.cx; - cy = res.cy; - } - } -} - -void AutoConfigTestPage::FinalizeResults() -{ - ui->stackedWidget->setCurrentIndex(1); - setSubTitle(QTStr(SUBTITLE_COMPLETE)); - - QFormLayout *form = results; - - auto encName = [](AutoConfig::Encoder enc) -> QString { - switch (enc) { - case AutoConfig::Encoder::x264: - return QTStr(ENCODER_SOFTWARE); - case AutoConfig::Encoder::NVENC: - return QTStr(ENCODER_NVENC); - case AutoConfig::Encoder::QSV: - return QTStr(ENCODER_QSV); - case AutoConfig::Encoder::AMD: - return QTStr(ENCODER_AMD); - case AutoConfig::Encoder::Apple: - return QTStr(ENCODER_APPLE); - case AutoConfig::Encoder::Stream: - return QTStr(QUALITY_SAME); - } - - return QTStr(ENCODER_SOFTWARE); - }; - - auto newLabel = [this](const char *str) -> QLabel * { - return new QLabel(QTStr(str), this); - }; - - if (wiz->type == AutoConfig::Type::Streaming) { - const char *serverType = wiz->customServer ? "rtmp_custom" : "rtmp_common"; - - OBSServiceAutoRelease service = obs_service_create(serverType, "temp_service", nullptr, nullptr); - - 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); - - obs_data_set_string(service_settings, "service", wiz->serviceName.c_str()); - obs_service_update(service, service_settings); - obs_service_apply_encoder_settings(service, vencoder_settings, nullptr); - - BPtr res_list; - size_t res_count; - int maxFPS; - obs_service_get_supported_resolutions(service, &res_list, &res_count); - obs_service_get_max_fps(service, &maxFPS); - - if (res_list) { - set_closest_res(wiz->idealResolutionCX, wiz->idealResolutionCY, res_list, res_count); - } - if (maxFPS) { - double idealFPS = (double)wiz->idealFPSNum / (double)wiz->idealFPSDen; - if (idealFPS > (double)maxFPS) { - wiz->idealFPSNum = maxFPS; - wiz->idealFPSDen = 1; - } - } - - wiz->idealBitrate = (int)obs_data_get_int(vencoder_settings, "bitrate"); - - if (!wiz->customServer) - form->addRow(newLabel("Basic.AutoConfig.StreamPage.Service"), - new QLabel(wiz->serviceName.c_str(), ui->finishPage)); - form->addRow(newLabel("Basic.AutoConfig.StreamPage.Server"), - new QLabel(wiz->serverName.c_str(), 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 = - QString("%1x%2").arg(QString::number(wiz->baseResolutionCX), QString::number(wiz->baseResolutionCY)); - QString scaleRes = - QString("%1x%2").arg(QString::number(wiz->idealResolutionCX), QString::number(wiz->idealResolutionCY)); - - if (wiz->recordingEncoder != AutoConfig::Encoder::Stream || - wiz->recordingQuality != AutoConfig::Quality::Stream) - form->addRow(newLabel(TEST_RESULT_RE), new QLabel(encName(wiz->recordingEncoder), ui->finishPage)); - - QString recQuality; - - switch (wiz->recordingQuality) { - case AutoConfig::Quality::High: - recQuality = QTStr(QUALITY_HIGH); - break; - case AutoConfig::Quality::Stream: - recQuality = QTStr(QUALITY_SAME); - break; - } - - form->addRow(newLabel("Basic.Settings.Output.Simple.RecordingQuality"), new QLabel(recQuality, ui->finishPage)); - - long double fps = (long double)wiz->idealFPSNum / (long double)wiz->idealFPSDen; - - QString fpsStr = (wiz->idealFPSDen > 1) ? QString::number(fps, 'f', 2) : QString::number(fps, 'g', 2); - - form->addRow(newLabel("Basic.Settings.Video.BaseResolution"), new QLabel(baseRes, ui->finishPage)); - form->addRow(newLabel("Basic.Settings.Video.ScaledResolution"), 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 "\n==== Auto-config wizard testing commencing ======\n" -#define STOPPING_SEPARATOR "\n==== Auto-config wizard testing stopping ========\n" - -void AutoConfigTestPage::NextStage() -{ - if (testThread.joinable()) - testThread.join(); - if (cancel) - return; - - ui->subProgressLabel->setText(QString()); - - /* make it skip to bandwidth stage if only set to config recording */ - if (stage == Stage::Starting) { - if (!started) { - blog(LOG_INFO, STARTING_SEPARATOR); - started = true; - } - - if (wiz->type != AutoConfig::Type::Streaming) { - stage = Stage::StreamEncoder; - } else if (!wiz->bandwidthTest) { - stage = Stage::BandwidthTest; - } - } - - if (stage == Stage::Starting) { - stage = Stage::BandwidthTest; - StartBandwidthStage(); - - } else if (stage == Stage::BandwidthTest) { - stage = Stage::StreamEncoder; - StartStreamEncoderStage(); - - } else if (stage == Stage::StreamEncoder) { - stage = Stage::RecordingEncoder; - StartRecordingEncoderStage(); - - } else { - stage = Stage::Finished; - FinalizeResults(); - emit completeChanged(); - } -} - -void AutoConfigTestPage::UpdateMessage(QString message) -{ - ui->subProgressLabel->setText(message); -} - -void AutoConfigTestPage::Failure(QString message) -{ - ui->errorLabel->setText(message); - ui->stackedWidget->setCurrentIndex(2); -} - -void AutoConfigTestPage::Progress(int percentage) -{ - ui->progressBar->setValue(percentage); -} - -AutoConfigTestPage::AutoConfigTestPage(QWidget *parent) : QWizardPage(parent), ui(new Ui_AutoConfigTestPage) -{ - ui->setupUi(this); - setTitle(QTStr("Basic.AutoConfig.TestPage")); - setSubTitle(QTStr(SUBTITLE_TESTING)); - setCommitPage(true); -} - -AutoConfigTestPage::~AutoConfigTestPage() -{ - if (testThread.joinable()) { - { - unique_lock ul(m); - cancel = true; - cv.notify_one(); - } - testThread.join(); - } - - if (started) - blog(LOG_INFO, STOPPING_SEPARATOR); -} - -void AutoConfigTestPage::initializePage() -{ - if (wiz->type == AutoConfig::Type::VirtualCam) { - wiz->idealResolutionCX = wiz->baseResolutionCX; - wiz->idealResolutionCY = wiz->baseResolutionCY; - wiz->idealFPSNum = 30; - wiz->idealFPSDen = 1; - stage = Stage::Finished; - } else { - stage = Stage::Starting; - } - - setSubTitle(QTStr(SUBTITLE_TESTING)); - softwareTested = false; - cancel = false; - DeleteLayout(results); - results = new QFormLayout(); - results->setContentsMargins(0, 0, 0, 0); - ui->finishPageLayout->insertLayout(1, results); - ui->stackedWidget->setCurrentIndex(0); - NextStage(); -} - -void AutoConfigTestPage::cleanupPage() -{ - if (testThread.joinable()) { - { - unique_lock ul(m); - cancel = true; - cv.notify_one(); - } - testThread.join(); - } -} - -bool AutoConfigTestPage::isComplete() const -{ - return stage == Stage::Finished; -} - -int AutoConfigTestPage::nextId() const -{ - return -1; -}