提交 4049a5a8 编写于 作者: J jp9000

UI: Add auto-configuration wizard

The auto-configuration wizard is designed to allow first-time or
novice/uneducated users or to set up video and encoding settings in a
very quick and easy way.  It'll automatically perform a bandwidth test,
and/or test the user's video settings to determine the most ideal
settings for streaming and recording (assuming a 1-pc setup).
上级 51e5f5fc
......@@ -135,8 +135,10 @@ set(obs_SOURCES
window-basic-settings.cpp
window-basic-interaction.cpp
window-basic-properties.cpp
window-basic-auto-config.cpp
window-basic-main-outputs.cpp
window-basic-source-select.cpp
window-basic-auto-config-test.cpp
window-basic-main-scene-collections.cpp
window-basic-main-transitions.cpp
window-basic-main-dropfiles.cpp
......@@ -181,6 +183,7 @@ set(obs_HEADERS
window-basic-settings.hpp
window-basic-interaction.hpp
window-basic-properties.hpp
window-basic-auto-config.hpp
window-basic-main-outputs.hpp
window-basic-source-select.hpp
window-license-agreement.hpp
......@@ -217,6 +220,10 @@ set(obs_HEADERS
set(obs_UI
forms/NameDialog.ui
forms/AutoConfigStartPage.ui
forms/AutoConfigVideoPage.ui
forms/AutoConfigStreamPage.ui
forms/AutoConfigTestPage.ui
forms/OBSLicenseAgreement.ui
forms/OBSLogReply.ui
forms/OBSBasic.ui
......
......@@ -74,6 +74,63 @@ RemuxRecordings="Remux Recordings"
Copy.Filters="Copy Filters"
Paste.Filters="Paste Filters"
# bandwidth test
BandwidthTest.Region="Region"
BandwidthTest.Region.US="United States"
BandwidthTest.Region.EU="Europe"
BandwidthTest.Region.Asia="Asia"
BandwidthTest.Region.Other="Other"
# first time startup
Basic.FirstStartup.RunWizard="Would you like to run the auto-configuration wizard? You can also manually configure your settings by clicking the Settings button in the main window."
Basic.FirstStartup.RunWizard.BetaWarning="(Note: The auto-configuration wizard is currently in beta)"
Basic.FirstStartup.RunWizard.NoClicked="If you change your mind, you can run the auto-configuration wizard any time again from the Tools menu."
# auto config wizard
Basic.AutoConfig="Auto-Configuration Wizard"
Basic.AutoConfig.Beta="Auto-Configuration Wizard (Beta)"
Basic.AutoConfig.ApplySettings="Apply Settings"
Basic.AutoConfig.StartPage="Usage Information"
Basic.AutoConfig.StartPage.SubTitle="Specify what you want to to use the program for"
Basic.AutoConfig.StartPage.PrioritizeStreaming="Optimize for streaming, recording is secondary"
Basic.AutoConfig.StartPage.PrioritizeRecording="Optimize just for recording, I will not be streaming"
Basic.AutoConfig.VideoPage="Video Settings"
Basic.AutoConfig.VideoPage.SubTitle="Specify the desired video settings you would like to use"
Basic.AutoConfig.VideoPage.BaseResolution.UseCurrent="Use Current (%1x%2)"
Basic.AutoConfig.VideoPage.BaseResolution.Display="Display %1 (%2x%3)"
Basic.AutoConfig.VideoPage.FPS.UseCurrent="Use Current (%1)"
Basic.AutoConfig.VideoPage.FPS.PreferHighFPS="Either 60 or 30, but prefer 60 when possible"
Basic.AutoConfig.VideoPage.FPS.PreferHighRes="Either 60 or 30, but prefer high resolution"
Basic.AutoConfig.VideoPage.CanvasExplanation="Note: The canvas (base) resolution is not necessarily the same as the resolution you will stream or record with. Your actual stream/recording resolution may be scaled down from the canvas resolution to reduce resource usage or bitrate requirements."
Basic.AutoConfig.StreamPage="Stream Information"
Basic.AutoConfig.StreamPage.SubTitle="Please enter your stream information"
Basic.AutoConfig.StreamPage.Service="Service"
Basic.AutoConfig.StreamPage.Service.ShowAll="Show All..."
Basic.AutoConfig.StreamPage.Server="Server"
Basic.AutoConfig.StreamPage.StreamKey="Stream Key"
Basic.AutoConfig.StreamPage.StreamKey.LinkToSite="(Link)"
Basic.AutoConfig.StreamPage.PerformBandwidthTest="Estimate bitrate with bandwidth test (may take a few minutes)"
Basic.AutoConfig.StreamPage.PreferHardwareEncoding="Prefer hardware encoding"
Basic.AutoConfig.StreamPage.PreferHardwareEncoding.ToolTip="Hardware Encoding eliminates most CPU usage, but may require more bitrate to obtain the same level of quality."
Basic.AutoConfig.StreamPage.StreamWarning.Title="Stream warning"
Basic.AutoConfig.StreamPage.StreamWarning.Text="The bandwidth test is about to stream randomized video data without audio to your channel. If you're able, it's recommended to temporarily turn off saving videos of streams and set the stream to private until after the test has completed. Continue?"
Basic.AutoConfig.TestPage="Final Results"
Basic.AutoConfig.TestPage.SubTitle.Testing="The program is now executing a set of tests to estimate the most ideal settings"
Basic.AutoConfig.TestPage.SubTitle.Complete="Testing complete"
Basic.AutoConfig.TestPage.TestingBandwidth="Performing bandwidth test, this may take a few minutes..."
Basic.AutoConfig.TestPage.TestingBandwidth.Connecting="Connecting to: %1..."
Basic.AutoConfig.TestPage.TestingBandwidth.ConnectFailed="Failed to connect to any servers, please check your internet connection and try again."
Basic.AutoConfig.TestPage.TestingBandwidth.Server="Testing bandwidth for: %1"
Basic.AutoConfig.TestPage.TestingStreamEncoder="Testing stream encoder, this may take a minute..."
Basic.AutoConfig.TestPage.TestingRecordingEncoder="Testing recording encoder, this may take a minute..."
Basic.AutoConfig.TestPage.TestingRes="Testing resolutions, this may take a few minutes..."
Basic.AutoConfig.TestPage.TestingRes.Fail="Failed to start up encoder"
Basic.AutoConfig.TestPage.TestingRes.Resolution="Testing %1x%2 %3 FPS..."
Basic.AutoConfig.TestPage.Result.StreamingEncoder="Streaming Encoder"
Basic.AutoConfig.TestPage.Result.RecordingEncoder="Recording Encoder"
Basic.AutoConfig.TestPage.Result.Header="The program has determined that these estimated settings are the most ideal for you:"
Basic.AutoConfig.TestPage.Result.Footer="To use these settings, click Apply Settings. To reconfigure the wizard and try again, click Back. To manually configure settings yourself, click Cancel and open Settings."
# updater
Updater.Title="New update available"
Updater.Text="There is a new update available:"
......
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AutoConfigFinishPage</class>
<widget class="QWidget" name="AutoConfigFinishPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true"/>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string notr="true"/>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AutoConfigStartPage</class>
<widget class="QWidget" name="AutoConfigStartPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true"/>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QRadioButton" name="prioritizeStreaming">
<property name="text">
<string>Basic.AutoConfig.StartPage.PrioritizeStreaming</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="prioritizeRecording">
<property name="text">
<string>Basic.AutoConfig.StartPage.PrioritizeRecording</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AutoConfigStreamPage</class>
<widget class="QWidget" name="AutoConfigStreamPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>566</width>
<height>335</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true"/>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
</property>
<item row="1" column="0">
<widget class="QLabel" name="serviceLabel">
<property name="text">
<string>Basic.AutoConfig.StreamPage.Service</string>
</property>
<property name="buddy">
<cstring>service</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="service"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="streamKeyLabel">
<property name="text">
<string>Basic.AutoConfig.StreamPage.StreamKey</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="buddy">
<cstring>key</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="key">
<property name="inputMask">
<string notr="true"/>
</property>
<property name="text">
<string notr="true"/>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="show">
<property name="text">
<string>Show</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="6" column="1">
<widget class="QCheckBox" name="doBandwidthTest">
<property name="text">
<string>Basic.AutoConfig.StreamPage.PerformBandwidthTest</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="preferHardware">
<property name="text">
<string>Basic.AutoConfig.StreamPage.PreferHardwareEncoding</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="6" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>90</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0">
<widget class="QLabel" name="serverLabel">
<property name="text">
<string>Basic.AutoConfig.StreamPage.Server</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Basic.Settings.Stream.StreamType</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="streamType"/>
</item>
<item row="2" column="1">
<widget class="QStackedWidget" name="serverStackedWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="servicePage">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="server"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="customPage">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="customServer"/>
</item>
</layout>
</widget>
</widget>
</item>
<item row="7" column="1">
<widget class="QGroupBox" name="region">
<property name="title">
<string>BandwidthTest.Region</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="0">
<widget class="QCheckBox" name="regionAsia">
<property name="text">
<string>BandwidthTest.Region.Asia</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="regionUS">
<property name="text">
<string>BandwidthTest.Region.US</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="regionEU">
<property name="text">
<string>BandwidthTest.Region.EU</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="regionOther">
<property name="text">
<string>BandwidthTest.Region.Other</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="bitrate">
<property name="suffix">
<string notr="true"/>
</property>
<property name="minimum">
<number>500</number>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="value">
<number>2500</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="bitrateLabel">
<property name="text">
<string>Basic.Settings.Output.VideoBitrate</string>
</property>
<property name="buddy">
<cstring>bitrate</cstring>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>streamType</tabstop>
<tabstop>service</tabstop>
<tabstop>server</tabstop>
<tabstop>customServer</tabstop>
<tabstop>key</tabstop>
<tabstop>show</tabstop>
<tabstop>preferHardware</tabstop>
<tabstop>doBandwidthTest</tabstop>
<tabstop>regionUS</tabstop>
<tabstop>regionEU</tabstop>
<tabstop>regionAsia</tabstop>
<tabstop>regionOther</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AutoConfigTestPage</class>
<widget class="QWidget" name="AutoConfigTestPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true"/>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>2</number>
</property>
<widget class="QWidget" name="testPage">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="progressLabel">
<property name="text">
<string notr="true"/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="subProgressLabel">
<property name="text">
<string notr="true"/>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>24</number>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="finishPage">
<layout class="QVBoxLayout" name="finishPageLayout">
<item>
<widget class="QLabel" name="finalResultLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Basic.AutoConfig.TestPage.Result.Header</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="finalResultLabel_2">
<property name="text">
<string>Basic.AutoConfig.TestPage.Result.Footer</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="errorPage">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="errorLabel">
<property name="text">
<string notr="true"/>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AutoConfigVideoPage</class>
<widget class="QWidget" name="AutoConfigVideoPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>470</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true"/>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout_2">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Basic.Settings.Video.BaseResolution</string>
</property>
<property name="buddy">
<cstring>canvasRes</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="canvasRes"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Basic.Settings.Video.FPS</string>
</property>
<property name="buddy">
<cstring>fps</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="fps"/>
</item>
<item row="3" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>87</width>
<height>17</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="warningLabel">
<property name="text">
<string>Basic.AutoConfig.VideoPage.CanvasExplanation</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
......@@ -983,12 +983,11 @@
<addaction name="toggleStatusBar"/>
</widget>
<widget class="QMenu" name="menuTools">
<property name="enabled">
<bool>false</bool>
</property>
<property name="title">
<string>Basic.MainMenu.Tools</string>
</property>
<addaction name="autoConfigure"/>
<addaction name="separator"/>
</widget>
<addaction name="menu_File"/>
<addaction name="menuBasic_MainMenu_Edit"/>
......@@ -1462,6 +1461,16 @@
<string>PasteDuplicate</string>
</property>
</action>
<action name="autoConfigure2">
<property name="text">
<string>Basic.AutoConfig</string>
</property>
</action>
<action name="autoConfigure">
<property name="text">
<string>Basic.AutoConfig.Beta</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
......
#include <chrono>
#include <QFormLayout>
#include <obs.hpp>
#include <util/platform.h>
#include <graphics/vec4.h>
#include <graphics/graphics.h>
#include <graphics/math-extra.h>
#include "window-basic-auto-config.hpp"
#include "window-basic-main.hpp"
#include "qt-wrappers.hpp"
#include "obs-app.hpp"
#include "ui_AutoConfigTestPage.h"
#define wiz reinterpret_cast<AutoConfig*>(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);
}
};
/* ------------------------------------------------------------------------- */
#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_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 TEST_STR("TestingRes")
#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<ServerInfo> &servers)
{
OBSData settings = obs_data_create();
obs_data_release(settings);
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);
}
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";
OBSEncoder vencoder = obs_video_encoder_create("obs_x264",
"test_x264", nullptr, nullptr);
OBSEncoder aencoder = obs_audio_encoder_create("ffmpeg_aac",
"test_aac", nullptr, 0, nullptr);
OBSService service = obs_service_create(serverType,
"test_service", nullptr, nullptr);
OBSOutput output = obs_output_create("rtmp_output",
"test_stream", nullptr, nullptr);
obs_output_release(output);
obs_encoder_release(vencoder);
obs_encoder_release(aencoder);
obs_service_release(service);
/* -----------------------------------*/
/* 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
OBSData service_settings = obs_data_create();
OBSData vencoder_settings = obs_data_create();
OBSData aencoder_settings = obs_data_create();
OBSData output_settings = obs_data_create();
obs_data_release(service_settings);
obs_data_release(vencoder_settings);
obs_data_release(aencoder_settings);
obs_data_release(output_settings);
std::string key = wiz->key;
if (wiz->service == AutoConfig::Service::Twitch)
key += "?bandwidthtest";
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<OBSBasic*>(App()->GetMainWindow());
const char *bind_ip = config_get_string(main->Config(), "Output",
"BindIP");
obs_data_set_string(output_settings, "bind_ip", bind_ip);
/* -----------------------------------*/
/* determine which servers to test */
std::vector<ServerInfo> 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 */
if (servers.size() < 3)
servers.resize(1);
/* -----------------------------------*/
/* apply settings */
obs_service_apply_encoder_settings(service,
vencoder_settings, aencoder_settings);
obs_encoder_update(vencoder, vencoder_settings);
obs_encoder_update(aencoder, aencoder_settings);
obs_service_update(service, service_settings);
obs_output_update(output, output_settings);
/* -----------------------------------*/
/* connect encoders/services/outputs */
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_service(output, service);
/* -----------------------------------*/
/* connect signals */
auto on_started = [&] ()
{
unique_lock<mutex> lock(m);
connected = true;
stopped = false;
cv.notify_one();
};
auto on_stopped = [&] ()
{
unique_lock<mutex> 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<on_started_t*>(data);
on_started();
};
auto pre_on_stopped = [] (void *data, calldata_t *)
{
on_stopped_t &on_stopped =
*reinterpret_cast<on_stopped_t*>(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<mutex> ul(m);
if (cancel) {
obs_output_force_stop(output);
return;
}
if (!stopped && !connected)
cv.wait(ul);
if (cancel) {
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())));
uint64_t t_start = os_gettime_ns();
cv.wait_for(ul, chrono::seconds(10));
if (stopped)
continue;
if (cancel) {
obs_output_force_stop(output);
return;
}
obs_output_stop(output);
cv.wait(ul);
uint64_t total_time = os_gettime_ns() - t_start;
int total_bytes = (int)obs_output_get_total_bytes(output);
uint64_t bitrate = (uint64_t)total_bytes * 8
* 1000000000 / total_time / 1000;
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 = bestServer;
wiz->serverName = 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 */
OBSEncoder vencoder = obs_video_encoder_create("obs_x264",
"test_x264", nullptr, nullptr);
OBSEncoder aencoder = obs_audio_encoder_create("ffmpeg_aac",
"test_aac", nullptr, 0, nullptr);
OBSOutput output = obs_output_create("null_output",
"null", nullptr, nullptr);
obs_output_release(output);
obs_encoder_release(vencoder);
obs_encoder_release(aencoder);
/* -----------------------------------*/
/* configure settings */
OBSData aencoder_settings = obs_data_create();
OBSData vencoder_settings = obs_data_create();
obs_data_release(aencoder_settings);
obs_data_release(vencoder_settings);
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<mutex> 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<on_stopped_t*>(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<Result> results;
int i = 0;
int count = 1;
auto testRes = [&] (long double div, int fps_num, int fps_den,
bool force)
{
int per = ++i * 100 / count;
QMetaObject::invokeMethod(this, "Progress", Q_ARG(int, per));
/* 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 / div);
int cy = int((long double)baseCY / div);
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<mutex> 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 = 5;
if (!testRes(1.0, 0, 0, false)) return false;
if (!testRes(1.5, 0, 0, false)) return false;
if (!testRes(1.0 / 0.6, 0, 0, false)) return false;
if (!testRes(2.0, 0, 0, false)) return false;
if (!testRes(2.25, 0, 0, true)) return false;
} else {
count = 10;
if (!testRes(1.0, 60, 1, false)) return false;
if (!testRes(1.0, 30, 1, false)) return false;
if (!testRes(1.5, 60, 1, false)) return false;
if (!testRes(1.5, 30, 1, false)) return false;
if (!testRes(1.0 / 0.6, 60, 1, false)) return false;
if (!testRes(1.0 / 0.6, 30, 1, false)) return false;
if (!testRes(2.0, 60, 1, false)) return false;
if (!testRes(2.0, 30, 1, false)) return false;
if (!testRes(2.25, 60, 1, false)) return false;
if (!testRes(2.25, 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->idealBitrate > upperBitrate)
wiz->idealBitrate = upperBitrate;
softwareTested = true;
return true;
}
void AutoConfigTestPage::FindIdealHardwareResolution()
{
int baseCX = wiz->baseResolutionCX;
int baseCY = wiz->baseResolutionCY;
CalcBaseRes(baseCX, baseCY);
vector<Result> 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 = [&] (long double div, int fps_num, int fps_den,
bool force)
{
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 / div);
int cy = int((long double)baseCY / div);
long double rate = (long double)cx * (long double)cy * fps;
if (!force && rate > maxDataRate)
return;
int minBitrate = EstimateMinBitrate(cx, cy, fps_num, fps_den)
* 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(1.0, 0, 0, false);
testRes(1.5, 0, 0, false);
testRes(1.0 / 0.6, 0, 0, false);
testRes(2.0, 0, 0, false);
testRes(2.25, 0, 0, true);
} else {
testRes(1.0, 60, 1, false);
testRes(1.0, 30, 1, false);
testRes(1.5, 60, 1, false);
testRes(1.5, 30, 1, false);
testRes(1.0 / 0.6, 60, 1, false);
testRes(1.0 / 0.6, 30, 1, false);
testRes(2.0, 60, 1, false);
testRes(2.0, 30, 1, false);
testRes(2.25, 60, 1, false);
testRes(2.25, 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 (preferHardware && !softwareTested && wiz->hardwareEncodingAvailable)
FindIdealHardwareResolution();
if (!softwareTested) {
if (wiz->nvencAvailable)
wiz->streamingEncoder = AutoConfig::Encoder::NVENC;
else if (wiz->qsvAvailable)
wiz->streamingEncoder = AutoConfig::Encoder::QSV;
else
wiz->streamingEncoder = AutoConfig::Encoder::AMD;
} else {
wiz->streamingEncoder = AutoConfig::Encoder::x264;
}
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
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")
#define ENCODER_QSV ENCODER_TEXT("Hardware.QSV")
#define ENCODER_AMD ENCODER_TEXT("Hardware.AMD")
#define QUALITY_SAME "Basic.Settings.Output.Simple.RecordingQuality.Stream"
#define QUALITY_HIGH "Basic.Settings.Output.Simple.RecordingQuality.Small"
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::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::Recording) {
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.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));
}
#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::Recording) {
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()
{
delete ui;
if (testThread.joinable()) {
{
unique_lock<mutex> ul(m);
cancel = true;
cv.notify_one();
}
testThread.join();
}
if (started)
blog(LOG_INFO, STOPPING_SEPARATOR);
}
void AutoConfigTestPage::initializePage()
{
setSubTitle(QTStr(SUBTITLE_TESTING));
stage = Stage::Starting;
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<mutex> ul(m);
cancel = true;
cv.notify_one();
}
testThread.join();
}
}
bool AutoConfigTestPage::isComplete() const
{
return stage == Stage::Finished;
}
int AutoConfigTestPage::nextId() const
{
return -1;
}
#include "window-basic-auto-config.hpp"
#include "window-basic-main.hpp"
#include "qt-wrappers.hpp"
#include "obs-app.hpp"
#include <QMessageBox>
#include <QScreen>
#include <obs.hpp>
#include "ui_AutoConfigStartPage.h"
#include "ui_AutoConfigVideoPage.h"
#include "ui_AutoConfigStreamPage.h"
#define wiz reinterpret_cast<AutoConfig*>(wizard())
/* ------------------------------------------------------------------------- */
#define SERVICE_PATH "service.json"
static OBSData OpenServiceSettings(std::string &type)
{
char serviceJsonPath[512];
int ret = GetProfilePath(serviceJsonPath, sizeof(serviceJsonPath),
SERVICE_PATH);
if (ret <= 0)
return OBSData();
OBSData data = obs_data_create_from_json_file_safe(serviceJsonPath,
"bak");
obs_data_release(data);
obs_data_set_default_string(data, "type", "rtmp_common");
type = obs_data_get_string(data, "type");
OBSData settings = obs_data_get_obj(data, "settings");
obs_data_release(settings);
return settings;
}
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"));
}
AutoConfigStartPage::~AutoConfigStartPage()
{
delete ui;
}
int AutoConfigStartPage::nextId() const
{
return AutoConfig::VideoPage;
}
void AutoConfigStartPage::on_prioritizeStreaming_clicked()
{
wiz->type = AutoConfig::Type::Streaming;
}
void AutoConfigStartPage::on_prioritizeRecording_clicked()
{
wiz->type = AutoConfig::Type::Recording;
}
/* ------------------------------------------------------------------------- */
#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', 2);
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);
ui->canvasRes->addItem(QTStr(RES_USE_CURRENT).arg(cxStr, cyStr),
(int)encRes);
QList<QScreen*> screens = QGuiApplication::screens();
for (int i = 0; i < screens.size(); i++) {
QScreen *screen = screens[i];
QSize as = screen->size();
encRes = int(as.width() << 16) | int(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()
{
delete ui;
}
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;
}
/* ------------------------------------------------------------------------- */
AutoConfigStreamPage::AutoConfigStreamPage(QWidget *parent)
: QWizardPage (parent),
ui (new Ui_AutoConfigStreamPage)
{
ui->setupUi(this);
ui->bitrateLabel->setVisible(false);
ui->bitrate->setVisible(false);
ui->streamType->addItem(obs_service_get_display_name("rtmp_common"));
ui->streamType->addItem(obs_service_get_display_name("rtmp_custom"));
setTitle(QTStr("Basic.AutoConfig.StreamPage"));
setSubTitle(QTStr("Basic.AutoConfig.StreamPage.SubTitle"));
LoadServices(false);
connect(ui->streamType, SIGNAL(currentIndexChanged(int)),
this, SLOT(ServiceChanged()));
connect(ui->service, SIGNAL(currentIndexChanged(int)),
this, SLOT(ServiceChanged()));
connect(ui->customServer, SIGNAL(textChanged(const QString &)),
this, SLOT(ServiceChanged()));
connect(ui->doBandwidthTest, SIGNAL(toggled(bool)),
this, SLOT(ServiceChanged()));
connect(ui->service, SIGNAL(currentIndexChanged(int)),
this, SLOT(UpdateServerList()));
connect(ui->streamType, SIGNAL(currentIndexChanged(int)),
this, SLOT(UpdateKeyLink()));
connect(ui->service, SIGNAL(currentIndexChanged(int)),
this, SLOT(UpdateKeyLink()));
connect(ui->key, SIGNAL(textChanged(const QString &)),
this, SLOT(UpdateCompleted()));
connect(ui->regionUS, SIGNAL(toggled(bool)),
this, SLOT(UpdateCompleted()));
connect(ui->regionEU, SIGNAL(toggled(bool)),
this, SLOT(UpdateCompleted()));
connect(ui->regionAsia, SIGNAL(toggled(bool)),
this, SLOT(UpdateCompleted()));
connect(ui->regionOther, SIGNAL(toggled(bool)),
this, SLOT(UpdateCompleted()));
}
AutoConfigStreamPage::~AutoConfigStreamPage()
{
delete ui;
}
bool AutoConfigStreamPage::isComplete() const
{
return ready;
}
int AutoConfigStreamPage::nextId() const
{
return AutoConfig::TestPage;
}
bool AutoConfigStreamPage::validatePage()
{
OBSData service_settings = obs_data_create();
obs_data_release(service_settings);
obs_data_set_string(service_settings, "service",
QT_TO_UTF8(ui->service->currentText()));
OBSService service = obs_service_create("rtmp_common", "temp_service",
service_settings, nullptr);
obs_service_release(service);
wiz->customServer = ui->streamType->currentIndex() == 1;
int bitrate = 6000;
if (!ui->doBandwidthTest->isChecked()) {
bitrate = ui->bitrate->value();
wiz->idealBitrate = bitrate;
}
OBSData settings = obs_data_create();
obs_data_release(settings);
obs_data_set_int(settings, "bitrate", bitrate);
obs_service_apply_encoder_settings(service, settings, nullptr);
if (wiz->customServer) {
QString server = ui->customServer->text();
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;
else if (wiz->serviceName == "hitbox.tv")
wiz->service = AutoConfig::Service::Hitbox;
else if (wiz->serviceName == "beam.pro")
wiz->service = AutoConfig::Service::Beam;
else
wiz->service = AutoConfig::Service::Other;
} else {
wiz->service = AutoConfig::Service::Other;
}
if (wiz->service != AutoConfig::Service::Twitch && wiz->bandwidthTest) {
QMessageBox::StandardButton button;
#define WARNING_TEXT(x) QTStr("Basic.AutoConfig.StreamPage.StreamWarning." x)
button = QMessageBox::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::ServiceChanged()
{
bool showMore = ui->service->currentData().toBool();
if (showMore)
return;
std::string service = QT_TO_UTF8(ui->service->currentText());
bool regionBased = service == "Twitch" ||
service == "hitbox.tv" ||
service == "beam.pro";
bool testBandwidth = ui->doBandwidthTest->isChecked();
bool custom = ui->streamType->currentIndex() == 1;
ui->service->setVisible(!custom);
ui->serviceLabel->setVisible(!custom);
ui->formLayout->removeWidget(ui->serviceLabel);
ui->formLayout->removeWidget(ui->service);
ui->formLayout->removeWidget(ui->serverLabel);
ui->formLayout->removeWidget(ui->serverStackedWidget);
if (custom) {
ui->formLayout->insertRow(1, ui->serverLabel,
ui->serverStackedWidget);
ui->region->setVisible(false);
ui->serverStackedWidget->setCurrentIndex(1);
ui->serverStackedWidget->setVisible(true);
ui->serverLabel->setVisible(true);
} else {
ui->formLayout->insertRow(1, ui->serviceLabel, ui->service);
if (!testBandwidth)
ui->formLayout->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);
UpdateCompleted();
}
void AutoConfigStreamPage::UpdateKeyLink()
{
bool custom = ui->streamType->currentIndex() == 1;
QString serviceName = ui->service->currentText();
if (custom)
serviceName = "";
QString text = QTStr("Basic.AutoConfig.StreamPage.StreamKey");
if (serviceName == "Twitch") {
text += " <a href=\"https://";
text += "www.twitch.tv/broadcast/dashboard/streamkey";
text += "\">";
text += QTStr("Basic.AutoConfig.StreamPage.StreamKey.LinkToSite");
text += "</a>";
} else if (serviceName == "YouTube / YouTube Gaming") {
text += " <a href=\"https://";
text += "www.youtube.com/live_dashboard";
text += "\">";
text += QTStr("Basic.AutoConfig.StreamPage.StreamKey.LinkToSite");
text += "</a>";
}
ui->streamKeyLabel->setText(text);
}
void AutoConfigStreamPage::LoadServices(bool showAll)
{
obs_properties_t *props = obs_get_service_properties("rtmp_common");
OBSData settings = obs_data_create();
obs_data_release(settings);
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();
for (QString &name : names)
ui->service->addItem(name);
if (!lastService.isEmpty()) {
int idx = ui->service->findText(lastService);
if (idx != -1)
ui->service->setCurrentIndex(idx);
}
if (!showAll) {
ui->service->addItem(
QTStr("Basic.AutoConfig.StreamPage.Service.ShowAll"),
QVariant(true));
}
obs_properties_destroy(props);
ui->service->blockSignals(false);
}
void AutoConfigStreamPage::UpdateServerList()
{
QString serviceName = ui->service->currentText();
bool showMore = ui->service->currentData().toBool();
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");
OBSData settings = obs_data_create();
obs_data_release(settings);
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()
{
if (ui->key->text().isEmpty()) {
ready = false;
} else {
bool custom = ui->streamType->currentIndex() == 1;
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)
{
OBSBasic *main = reinterpret_cast<OBSBasic*>(parent);
main->EnableOutputs(false);
installEventFilter(CreateShortcutFilter());
std::string serviceType;
GetServiceInfo(serviceType, serviceName, server, key);
#ifdef _WIN32
setWizardStyle(QWizard::ModernStyle);
#endif
AutoConfigStreamPage *streamPage = new AutoConfigStreamPage();
setPage(StartPage, new AutoConfigStartPage());
setPage(VideoPage, new AutoConfigVideoPage());
setPage(StreamPage, streamPage);
setPage(TestPage, new AutoConfigTestPage());
setWindowTitle(QTStr("Basic.AutoConfig.Beta"));
obs_video_info ovi;
obs_get_video_info(&ovi);
baseResolutionCX = ovi.base_width;
baseResolutionCY = ovi.base_height;
/* ----------------------------------------- */
/* 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();
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());
streamPage->ui->streamType->setCurrentIndex(1);
}
if (!key.empty())
streamPage->ui->key->setText(key.c_str());
int bitrate = config_get_int(main->Config(), "SimpleOutput", "VBitrate");
streamPage->ui->bitrate->setValue(bitrate);
streamPage->ServiceChanged();
streamPage->ui->preferHardware->setChecked(os_get_physical_cores() <= 4);
TestHardwareEncoding();
if (!hardwareEncodingAvailable) {
delete streamPage->ui->preferHardware;
streamPage->ui->preferHardware = nullptr;
}
setOptions(0);
setButtonText(QWizard::FinishButton,
QTStr("Basic.AutoConfig.ApplySettings"));
}
AutoConfig::~AutoConfig()
{
OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
main->EnableOutputs(true);
}
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, "amd_amf_h264") == 0)
hardwareEncodingAvailable = vceAvailable = true;
}
}
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 if (service == Service::Hitbox) {
if (strcmp(server, "Default") == 0) {
return true;
} else if (astrcmp_n(server, "US-West:", 8) == 0 ||
astrcmp_n(server, "US-East:", 8) == 0) {
return regionUS;
} else if (astrcmp_n(server, "EU-", 3) == 0) {
return regionEU;
} else if (astrcmp_n(server, "South Korea:", 12) == 0 ||
astrcmp_n(server, "Asia:", 5) == 0 ||
astrcmp_n(server, "China:", 6) == 0) {
return regionAsia;
} else if (regionOther) {
return true;
}
} else if (service == Service::Beam) {
if (astrcmp_n(server, "US:", 3) == 0) {
return regionUS;
} else if (astrcmp_n(server, "EU:", 3) == 0) {
return regionEU;
} else if (astrcmp_n(server, "South Korea:", 12) == 0 ||
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();
}
}
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;
default:
return SIMPLE_ENCODER_X264;
}
};
void AutoConfig::SaveStreamSettings()
{
OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
/* ---------------------------------- */
/* save service */
const char *service_id = customServer
? "rtmp_custom"
: "rtmp_common";
obs_service_t *oldService = main->GetService();
OBSData hotkeyData = obs_hotkeys_save_service(oldService);
obs_data_release(hotkeyData);
OBSData settings = obs_data_create();
obs_data_release(settings);
if (!customServer)
obs_data_set_string(settings, "service", serviceName.c_str());
obs_data_set_string(settings, "server", server.c_str());
obs_data_set_string(settings, "key", key.c_str());
OBSService newService = obs_service_create(service_id,
"default_service", settings, hotkeyData);
obs_service_release(newService);
if (!newService)
return;
main->SetService(newService);
main->SaveService();
/* ---------------------------------- */
/* save stream settings */
config_set_int(main->Config(), "SimpleOutput", "VBitrate",
idealBitrate);
config_set_string(main->Config(), "SimpleOutput", "StreamEncoder",
GetEncoderId(streamingEncoder));
}
void AutoConfig::SaveSettings()
{
OBSBasic *main = reinterpret_cast<OBSBasic*>(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);
}
#pragma once
#include <QWizard>
#include <QPointer>
#include <QFormLayout>
#include <QWizardPage>
#include <condition_variable>
#include <utility>
#include <thread>
#include <vector>
#include <string>
#include <mutex>
class Ui_AutoConfigStartPage;
class Ui_AutoConfigVideoPage;
class Ui_AutoConfigStreamPage;
class Ui_AutoConfigTestPage;
class AutoConfig : public QWizard {
Q_OBJECT
friend class AutoConfigStartPage;
friend class AutoConfigVideoPage;
friend class AutoConfigStreamPage;
friend class AutoConfigTestPage;
enum class Type {
Invalid,
Streaming,
Recording
};
enum class Service {
Twitch,
Hitbox,
Beam,
Other
};
enum class Encoder {
x264,
NVENC,
QSV,
AMD,
Stream
};
enum class Quality {
Stream,
High
};
enum class FPSType : int {
PreferHighFPS,
PreferHighRes,
UseCurrent,
fps30,
fps60
};
static inline const char *GetEncoderId(Encoder enc);
Service service = Service::Other;
Quality recordingQuality = Quality::Stream;
Encoder recordingEncoder = Encoder::Stream;
Encoder streamingEncoder = Encoder::x264;
Type type = Type::Invalid;
FPSType fpsType = FPSType::PreferHighFPS;
int idealBitrate = 2500;
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::string key;
bool hardwareEncodingAvailable = false;
bool nvencAvailable = false;
bool qsvAvailable = false;
bool vceAvailable = false;
int startingBitrate = 2500;
bool customServer = false;
bool bandwidthTest = false;
bool testRegions = true;
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;
Ui_AutoConfigStartPage *ui;
public:
AutoConfigStartPage(QWidget *parent = nullptr);
~AutoConfigStartPage();
virtual int nextId() const override;
public slots:
void on_prioritizeStreaming_clicked();
void on_prioritizeRecording_clicked();
};
class AutoConfigVideoPage : public QWizardPage {
Q_OBJECT
friend class AutoConfig;
Ui_AutoConfigVideoPage *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;
Ui_AutoConfigStreamPage *ui;
QString lastService;
bool ready = false;
void LoadServices(bool showAll);
public:
AutoConfigStreamPage(QWidget *parent = nullptr);
~AutoConfigStreamPage();
virtual bool isComplete() const override;
virtual int nextId() const override;
virtual bool validatePage() override;
public slots:
void on_show_clicked();
void ServiceChanged();
void UpdateKeyLink();
void UpdateServerList();
void UpdateCompleted();
};
class AutoConfigTestPage : public QWizardPage {
Q_OBJECT
friend class AutoConfig;
QPointer<QFormLayout> results;
Ui_AutoConfigTestPage *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<ServerInfo> &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);
};
......@@ -40,6 +40,7 @@
#include "item-widget-helpers.hpp"
#include "window-basic-settings.hpp"
#include "window-namedialog.hpp"
#include "window-basic-auto-config.hpp"
#include "window-basic-source-select.hpp"
#include "window-basic-main.hpp"
#include "window-basic-main-outputs.hpp"
......@@ -1422,6 +1423,36 @@ void OBSBasic::OBSInit()
SystemTray(true);
OpenSavedProjectors();
bool has_last_version = config_has_user_value(App()->GlobalConfig(),
"General", "LastVersion");
bool first_run = config_get_bool(App()->GlobalConfig(), "General",
"FirstRun");
if (!first_run) {
config_set_bool(App()->GlobalConfig(), "General", "FirstRun",
true);
config_save_safe(App()->GlobalConfig(), "tmp", nullptr);
}
if (!first_run && !has_last_version && !Active()) {
QString msg;
msg = QTStr("Basic.FirstStartup.RunWizard");
msg += "\n\n";
msg += QTStr("Basic.FirstStartup.RunWizard.BetaWarning");
QMessageBox::StandardButton button =
QMessageBox::question(this, QTStr("Basic.AutoConfig"),
msg);
if (button == QMessageBox::Yes) {
on_autoConfigure_triggered();
} else {
msg = QTStr("Basic.FirstStartup.RunWizard.NoClicked");
QMessageBox::information(this,
QTStr("Basic.AutoConfig"), msg);
}
}
}
void OBSBasic::InitHotkeys()
......@@ -3976,6 +4007,7 @@ inline void OBSBasic::OnActivate()
{
if (ui->profileMenu->isEnabled()) {
ui->profileMenu->setEnabled(false);
ui->autoConfigure->setEnabled(false);
App()->IncrementSleepInhibition();
UpdateProcessPriority();
......@@ -3988,6 +4020,7 @@ inline void OBSBasic::OnDeactivate()
{
if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) {
ui->profileMenu->setEnabled(true);
ui->autoConfigure->setEnabled(true);
App()->DecrementSleepInhibition();
ClearProcessPriority();
......@@ -5445,3 +5478,11 @@ void OBSBasic::on_actionPasteFilters_triggered()
obs_source_copy_filters(dstSource, source);
}
void OBSBasic::on_autoConfigure_triggered()
{
AutoConfig test(this);
test.setModal(true);
test.show();
test.exec();
}
......@@ -613,6 +613,8 @@ private slots:
void on_modeSwitch_clicked();
void on_autoConfigure_triggered();
void logUploadFinished(const QString &text, const QString &error);
void updateCheckFinished();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册