Skip to content

Commit 5ad9d03

Browse files
committed
obs-webrtc: Add Simulcast Support
1 parent a0e4e37 commit 5ad9d03

File tree

9 files changed

+287
-29
lines changed

9 files changed

+287
-29
lines changed

UI/data/locale/en-US.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,9 @@ Basic.Settings.Stream.MultitrackVideoStreamDumpEnable="Enable stream dump to FLV
985985
Basic.Settings.Stream.MultitrackVideoConfigOverride="Config Override (JSON)"
986986
Basic.Settings.Stream.MultitrackVideoConfigOverrideEnable="Enable Config Override"
987987
Basic.Settings.Stream.MultitrackVideoLabel="Multitrack Video"
988+
Basic.Settings.Stream.SimulcastLabel="Simulcast"
989+
Basic.Settings.Stream.SimulcastInfo="Simulcast allows you to encode and send multiple video qualities. No information about your computer and software setup will be sent."
990+
Basic.Settings.Stream.SimulcastTotalLayers="Total Layers"
988991
Basic.Settings.Stream.AdvancedOptions="Advanced Options"
989992

990993
# basic mode 'output' settings

UI/forms/OBSBasicSettings.ui

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1849,6 +1849,91 @@
18491849
</layout>
18501850
</widget>
18511851
</item>
1852+
<item>
1853+
<widget class="QGroupBox" name="simulcastGroupBox">
1854+
<property name="title">
1855+
<string>Basic.Settings.Stream.SimulcastLabel</string>
1856+
</property>
1857+
<layout class="QVBoxLayout" name="verticalLayout_35">
1858+
<property name="leftMargin">
1859+
<number>9</number>
1860+
</property>
1861+
<property name="topMargin">
1862+
<number>2</number>
1863+
</property>
1864+
<property name="rightMargin">
1865+
<number>9</number>
1866+
</property>
1867+
<property name="bottomMargin">
1868+
<number>9</number>
1869+
</property>
1870+
<item>
1871+
<layout class="QHBoxLayout" name="horizontalLayout_33">
1872+
<item>
1873+
<spacer name="horizontalSpacer_33">
1874+
<property name="orientation">
1875+
<enum>Qt::Horizontal</enum>
1876+
</property>
1877+
<property name="sizeType">
1878+
<enum>QSizePolicy::Fixed</enum>
1879+
</property>
1880+
<property name="sizeHint" stdset="0">
1881+
<size>
1882+
<width>170</width>
1883+
<height>10</height>
1884+
</size>
1885+
</property>
1886+
</spacer>
1887+
</item>
1888+
<item>
1889+
<widget class="QLabel" name="simulcastInfo">
1890+
<property name="text">
1891+
<string>Basic.Settings.Stream.SimulcastInfo</string>
1892+
</property>
1893+
<property name="textFormat">
1894+
<enum>Qt::RichText</enum>
1895+
</property>
1896+
<property name="wordWrap">
1897+
<bool>true</bool>
1898+
</property>
1899+
</widget>
1900+
</item>
1901+
</layout>
1902+
</item>
1903+
<item>
1904+
<layout class="QFormLayout" name="formLayout_39">
1905+
<property name="fieldGrowthPolicy">
1906+
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
1907+
</property>
1908+
<item row="1" column="0">
1909+
<widget class="QLabel" name="simulcastTotalLayersLabel">
1910+
<property name="text">
1911+
<string>Basic.Settings.Stream.SimulcastTotalLayers</string>
1912+
</property>
1913+
</widget>
1914+
</item>
1915+
<item row="1" column="1">
1916+
<layout class="QHBoxLayout" name="horizontalLayout_34" stretch="0,0">
1917+
<item>
1918+
<widget class="QSpinBox" name="simulcastTotalLayers">
1919+
<property name="minimum">
1920+
<number>1</number>
1921+
</property>
1922+
<property name="maximum">
1923+
<number>4</number>
1924+
</property>
1925+
<property name="value">
1926+
<number>1</number>
1927+
</property>
1928+
</widget>
1929+
</item>
1930+
</layout>
1931+
</item>
1932+
</layout>
1933+
</item>
1934+
</layout>
1935+
</widget>
1936+
</item>
18521937
<item>
18531938
<widget class="QGroupBox" name="serviceAdvancedOptionsGroupBox">
18541939
<property name="title">

UI/window-basic-main-outputs.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,8 @@ void SimpleOutput::LoadStreamingPreset_Lossy(const char *encoderId)
541541
if (!videoStreaming)
542542
throw "Failed to create video streaming encoder (simple output)";
543543
obs_encoder_release(videoStreaming);
544+
545+
CreateSimulcastEncoders(encoderId);
544546
}
545547

546548
/* mistakes have been made to lead us to this. */
@@ -800,11 +802,14 @@ void SimpleOutput::Update()
800802
break;
801803
default:
802804
obs_encoder_set_preferred_video_format(videoStreaming, VIDEO_FORMAT_NV12);
805+
for (auto enc : simulcastEncoders)
806+
obs_encoder_set_preferred_video_format(enc, VIDEO_FORMAT_NV12);
803807
}
804808

805809
obs_encoder_update(videoStreaming, videoSettings);
806810
obs_encoder_update(audioStreaming, audioSettings);
807811
obs_encoder_update(audioArchive, audioSettings);
812+
SimulcastEncodersUpdate(videoSettings, videoBitrate);
808813
}
809814

810815
void SimpleOutput::UpdateRecordingAudioSettings()
@@ -1094,6 +1099,8 @@ std::shared_future<void> SimpleOutput::SetupStreaming(obs_service_t *service, Se
10941099
}
10951100

10961101
obs_output_set_video_encoder(streamOutput, videoStreaming);
1102+
for (size_t i = 0; i < simulcastEncoders.size(); i++)
1103+
obs_output_set_video_encoder2(streamOutput, simulcastEncoders[i], i + 1);
10971104
obs_output_set_audio_encoder(streamOutput, audioStreaming, 0);
10981105
obs_output_set_service(streamOutput, service);
10991106
return true;
@@ -1568,6 +1575,7 @@ AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_)
15681575
throw "Failed to create streaming video encoder "
15691576
"(advanced output)";
15701577
obs_encoder_release(videoStreaming);
1578+
CreateSimulcastEncoders(streamEncoder);
15711579

15721580
const char *rate_control =
15731581
obs_data_get_string(useStreamEncoder ? streamEncSettings : recordEncSettings, "rate_control");
@@ -1668,6 +1676,7 @@ void AdvancedOutput::UpdateStreamSettings()
16681676
}
16691677

16701678
obs_encoder_update(videoStreaming, settings);
1679+
SimulcastEncodersUpdate(settings, obs_data_get_int(settings, "bitrate"));
16711680
}
16721681

16731682
inline void AdvancedOutput::UpdateRecordingSettings()
@@ -2082,6 +2091,8 @@ std::shared_future<void> AdvancedOutput::SetupStreaming(obs_service_t *service,
20822091
}
20832092

20842093
obs_output_set_video_encoder(streamOutput, videoStreaming);
2094+
for (size_t i = 0; i < simulcastEncoders.size(); i++)
2095+
obs_output_set_video_encoder2(streamOutput, simulcastEncoders[i], i + 1);
20852096
obs_output_set_audio_encoder(streamOutput, streamAudioEnc, 0);
20862097

20872098
if (!is_multitrack_output) {
@@ -2539,3 +2550,51 @@ BasicOutputHandler *CreateAdvancedOutputHandler(OBSBasic *main)
25392550
{
25402551
return new AdvancedOutput(main);
25412552
}
2553+
2554+
void BasicOutputHandler::CreateSimulcastEncoders(const char *encoderId)
2555+
{
2556+
int rescaleFilter = config_get_int(main->Config(), "AdvOut", "RescaleFilter");
2557+
if (rescaleFilter == OBS_SCALE_DISABLE) {
2558+
rescaleFilter = OBS_SCALE_BICUBIC;
2559+
}
2560+
2561+
std::string encoder_name = "simulcast_0";
2562+
auto simulcastTotalLayers = config_get_int(main->Config(), "Stream1", "SimulcastTotalLayers");
2563+
if (simulcastTotalLayers <= 1) {
2564+
return;
2565+
}
2566+
2567+
auto widthStep = video_output_get_width(obs_get_video()) / simulcastTotalLayers;
2568+
auto heightStep = video_output_get_height(obs_get_video()) / simulcastTotalLayers;
2569+
2570+
for (auto i = simulcastTotalLayers - 1; i > 0; i--) {
2571+
uint32_t width = widthStep * i;
2572+
width -= width % 2;
2573+
2574+
uint32_t height = heightStep * i;
2575+
height -= height % 2;
2576+
2577+
encoder_name[encoder_name.size() - 1] = to_string(i).at(0);
2578+
auto simulcast_encoder = obs_video_encoder_create(encoderId, encoder_name.c_str(), nullptr, nullptr);
2579+
2580+
if (simulcast_encoder) {
2581+
obs_encoder_set_video(simulcast_encoder, obs_get_video());
2582+
obs_encoder_set_scaled_size(simulcast_encoder, width, height);
2583+
obs_encoder_set_gpu_scale_type(simulcast_encoder, (obs_scale_type)rescaleFilter);
2584+
simulcastEncoders.push_back(simulcast_encoder);
2585+
obs_encoder_release(simulcast_encoder);
2586+
} else {
2587+
blog(LOG_WARNING, "Failed to create video streaming simulcast encoders (BasicOutputHandler)");
2588+
}
2589+
}
2590+
}
2591+
2592+
void BasicOutputHandler::SimulcastEncodersUpdate(obs_data_t *videoSettings, int videoBitrate)
2593+
{
2594+
auto bitrateStep = videoBitrate / static_cast<int>(simulcastEncoders.size() + 1);
2595+
for (auto &simulcastEncoder : simulcastEncoders) {
2596+
videoBitrate -= bitrateStep;
2597+
obs_data_set_int(videoSettings, "bitrate", videoBitrate);
2598+
obs_encoder_update(simulcastEncoder, videoSettings);
2599+
}
2600+
}

UI/window-basic-main-outputs.hpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ struct BasicOutputHandler {
3737
obs_scene_t *vCamSourceScene = nullptr;
3838
obs_sceneitem_t *vCamSourceSceneItem = nullptr;
3939

40+
std::vector<OBSEncoder> simulcastEncoders;
41+
4042
std::string outputType;
4143
std::string lastError;
4244

@@ -60,7 +62,7 @@ struct BasicOutputHandler {
6062

6163
inline BasicOutputHandler(OBSBasic *main_);
6264

63-
virtual ~BasicOutputHandler(){};
65+
virtual ~BasicOutputHandler() {};
6466

6567
virtual std::shared_future<void> SetupStreaming(obs_service_t *service,
6668
SetupStreamingContinuation_t continuation) = 0;
@@ -99,6 +101,8 @@ struct BasicOutputHandler {
99101
size_t main_audio_mixer, std::optional<size_t> vod_track_mixer,
100102
std::function<void(std::optional<bool>)> continuation);
101103
OBSDataAutoRelease GenerateMultitrackVideoStreamDumpConfig();
104+
void CreateSimulcastEncoders(const char *encoderId);
105+
void SimulcastEncodersUpdate(obs_data_t *videoSettings, int videoBitrate);
102106
};
103107

104108
BasicOutputHandler *CreateSimpleOutputHandler(OBSBasic *main);

UI/window-basic-settings-stream.cpp

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ void OBSBasicSettings::InitStreamPage()
102102
void OBSBasicSettings::LoadStream1Settings()
103103
{
104104
bool ignoreRecommended = config_get_bool(main->Config(), "Stream1", "IgnoreRecommended");
105+
int simulcastTotalLayers = config_get_int(main->Config(), "Stream1", "SimulcastTotalLayers");
105106

106107
obs_service_t *service_obj = main->GetService();
107108
const char *type = obs_service_get_type(service_obj);
@@ -198,10 +199,13 @@ void OBSBasicSettings::LoadStream1Settings()
198199
if (use_custom_server)
199200
ui->serviceCustomServer->setText(server);
200201

201-
if (is_whip)
202+
if (is_whip) {
202203
ui->key->setText(bearer_token);
203-
else
204+
ui->simulcastGroupBox->show();
205+
} else {
204206
ui->key->setText(key);
207+
ui->simulcastGroupBox->hide();
208+
}
205209

206210
ServiceChanged(true);
207211

@@ -215,6 +219,7 @@ void OBSBasicSettings::LoadStream1Settings()
215219
ui->streamPage->setEnabled(!streamActive);
216220

217221
ui->ignoreRecommended->setChecked(ignoreRecommended);
222+
ui->simulcastTotalLayers->setValue(simulcastTotalLayers);
218223

219224
loading = false;
220225

@@ -316,6 +321,9 @@ void OBSBasicSettings::SaveStream1Settings()
316321

317322
SaveCheckBox(ui->ignoreRecommended, "Stream1", "IgnoreRecommended");
318323

324+
auto oldSimulcastTotalLayers = config_get_int(main->Config(), "Stream1", "SimulcastTotalLayers");
325+
SaveSpinBox(ui->simulcastTotalLayers, "Stream1", "SimulcastTotalLayers");
326+
319327
auto oldMultitrackVideoSetting = config_get_bool(main->Config(), "Stream1", "EnableMultitrackVideo");
320328

321329
if (!IsCustomService()) {
@@ -343,7 +351,8 @@ void OBSBasicSettings::SaveStream1Settings()
343351
SaveCheckBox(ui->multitrackVideoConfigOverrideEnable, "Stream1", "MultitrackVideoConfigOverrideEnabled");
344352
SaveText(ui->multitrackVideoConfigOverride, "Stream1", "MultitrackVideoConfigOverride");
345353

346-
if (oldMultitrackVideoSetting != ui->enableMultitrackVideo->isChecked())
354+
if (oldMultitrackVideoSetting != ui->enableMultitrackVideo->isChecked() ||
355+
oldSimulcastTotalLayers != ui->simulcastTotalLayers->value())
347356
main->ResetOutputs();
348357

349358
SwapMultiTrack(QT_TO_UTF8(protocol));
@@ -576,6 +585,12 @@ void OBSBasicSettings::on_service_currentIndexChanged(int idx)
576585
} else {
577586
SwapMultiTrack(QT_TO_UTF8(protocol));
578587
}
588+
589+
if (IsWHIP()) {
590+
ui->simulcastGroupBox->show();
591+
} else {
592+
ui->simulcastGroupBox->hide();
593+
}
579594
}
580595

581596
void OBSBasicSettings::on_customServer_textChanged(const QString &)

UI/window-basic-settings.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
407407
HookWidget(ui->authUsername, EDIT_CHANGED, STREAM1_CHANGED);
408408
HookWidget(ui->authPw, EDIT_CHANGED, STREAM1_CHANGED);
409409
HookWidget(ui->ignoreRecommended, CHECK_CHANGED, STREAM1_CHANGED);
410+
HookWidget(ui->simulcastTotalLayers, SCROLL_CHANGED, STREAM1_CHANGED);
410411
HookWidget(ui->enableMultitrackVideo, CHECK_CHANGED, STREAM1_CHANGED);
411412
HookWidget(ui->multitrackVideoMaximumAggregateBitrateAuto, CHECK_CHANGED, STREAM1_CHANGED);
412413
HookWidget(ui->multitrackVideoMaximumAggregateBitrate, SCROLL_CHANGED, STREAM1_CHANGED);

plugins/obs-webrtc/data/locale/en-US.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ Service.BearerToken="Bearer Token"
44

55
Error.InvalidSDP="WHIP server responded with invalid SDP: %1"
66
Error.NoRemoteDescription="Failed to set remote description: %1"
7+
Error.SimulcastLayersRejected="WHIP server only accepted %1 simulcast layers"

0 commit comments

Comments
 (0)