Skip to content

Commit

Permalink
obs-webrtc: Add Simulcast Support
Browse files Browse the repository at this point in the history
  • Loading branch information
Sean-Der committed Dec 28, 2024
1 parent a0e4e37 commit 26340d7
Show file tree
Hide file tree
Showing 11 changed files with 320 additions and 28 deletions.
3 changes: 3 additions & 0 deletions UI/data/locale/en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,9 @@ Basic.Settings.Stream.MultitrackVideoStreamDumpEnable="Enable stream dump to FLV
Basic.Settings.Stream.MultitrackVideoConfigOverride="Config Override (JSON)"
Basic.Settings.Stream.MultitrackVideoConfigOverrideEnable="Enable Config Override"
Basic.Settings.Stream.MultitrackVideoLabel="Multitrack Video"
Basic.Settings.Stream.WHIPSimulcastLabel="Simulcast"
Basic.Settings.Stream.WHIPSimulcastInfo="Simulcast allows you to encode and send multiple video qualities."
Basic.Settings.Stream.WHIPSimulcastTotalLayers="Total Layers"
Basic.Settings.Stream.AdvancedOptions="Advanced Options"

# basic mode 'output' settings
Expand Down
85 changes: 85 additions & 0 deletions UI/forms/OBSBasicSettings.ui
Original file line number Diff line number Diff line change
Expand Up @@ -1849,6 +1849,91 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="whipSimulcastGroupBox">
<property name="title">
<string>Basic.Settings.Stream.WHIPSimulcastLabel</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_35">
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_33">
<item>
<spacer name="horizontalSpacer_33">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>170</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="whipSimulcastInfo">
<property name="text">
<string>Basic.Settings.Stream.WHIPSimulcastInfo</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QFormLayout" name="formLayout_39">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="1" column="0">
<widget class="QLabel" name="whipSimulcastTotalLayersLabel">
<property name="text">
<string>Basic.Settings.Stream.WHIPSimulcastTotalLayers</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_34" stretch="0,0">
<item>
<widget class="QSpinBox" name="whipSimulcastTotalLayers">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>4</number>
</property>
<property name="value">
<number>1</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="serviceAdvancedOptionsGroupBox">
<property name="title">
Expand Down
69 changes: 69 additions & 0 deletions UI/whip-simulcast-encoders.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#pragma once

struct WHIPSimulcastEncoders {
public:
void Create(const char *encoderId, OBSBasic *main)
{
int rescaleFilter = config_get_int(main->Config(), "AdvOut", "RescaleFilter");
if (rescaleFilter == OBS_SCALE_DISABLE) {
rescaleFilter = OBS_SCALE_BICUBIC;
}

std::string encoder_name = "whip_simulcast_0";
auto whipSimulcastTotalLayers = config_get_int(main->Config(), "Stream1", "WHIPSimulcastTotalLayers");
if (whipSimulcastTotalLayers <= 1) {
return;
}

auto widthStep = video_output_get_width(obs_get_video()) / whipSimulcastTotalLayers;
auto heightStep = video_output_get_height(obs_get_video()) / whipSimulcastTotalLayers;

for (auto i = whipSimulcastTotalLayers - 1; i > 0; i--) {
uint32_t width = widthStep * i;
width -= width % 2;

uint32_t height = heightStep * i;
height -= height % 2;

encoder_name[encoder_name.size() - 1] = std::to_string(i).at(0);
auto whip_simulcast_encoder =
obs_video_encoder_create(encoderId, encoder_name.c_str(), nullptr, nullptr);

if (whip_simulcast_encoder) {
obs_encoder_set_video(whip_simulcast_encoder, obs_get_video());
obs_encoder_set_scaled_size(whip_simulcast_encoder, width, height);
obs_encoder_set_gpu_scale_type(whip_simulcast_encoder, (obs_scale_type)rescaleFilter);
whipSimulcastEncoders.push_back(whip_simulcast_encoder);
obs_encoder_release(whip_simulcast_encoder);
} else {
blog(LOG_WARNING,
"Failed to create video streaming WHIP Simulcast encoders (BasicOutputHandler)");
}
}
}

void Update(obs_data_t *videoSettings, int videoBitrate)
{
auto bitrateStep = videoBitrate / static_cast<int>(whipSimulcastEncoders.size() + 1);
for (auto &whipSimulcastEncoder : whipSimulcastEncoders) {
videoBitrate -= bitrateStep;
obs_data_set_int(videoSettings, "bitrate", videoBitrate);
obs_encoder_update(whipSimulcastEncoder, videoSettings);
}
}

void SetVideoFormat(enum video_format format)
{
for (auto enc : whipSimulcastEncoders)
obs_encoder_set_preferred_video_format(enc, format);
}

void SetStreamOutput(obs_output_t *streamOutput)
{
for (size_t i = 0; i < whipSimulcastEncoders.size(); i++)
obs_output_set_video_encoder2(streamOutput, whipSimulcastEncoders[i], i + 1);
}

private:
std::vector<OBSEncoder> whipSimulcastEncoders;
};
27 changes: 27 additions & 0 deletions UI/window-basic-main-outputs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@ inline BasicOutputHandler::BasicOutputHandler(OBSBasic *main_) : main(main_)
}
if (multitrack_enabled)
multitrackVideo = make_unique<MultitrackVideoOutput>();

if (config_get_int(main->Config(), "Stream1", "WHIPSimulcastTotalLayers") > 1) {
whipSimulcastEncoders = make_unique<WHIPSimulcastEncoders>();
};
}

extern void log_vcam_changed(const VCamConfig &config, bool starting);
Expand Down Expand Up @@ -541,6 +545,10 @@ void SimpleOutput::LoadStreamingPreset_Lossy(const char *encoderId)
if (!videoStreaming)
throw "Failed to create video streaming encoder (simple output)";
obs_encoder_release(videoStreaming);

if (whipSimulcastEncoders != nullptr) {
whipSimulcastEncoders->Create(encoderId, main);
}
}

/* mistakes have been made to lead us to this. */
Expand Down Expand Up @@ -800,11 +808,18 @@ void SimpleOutput::Update()
break;
default:
obs_encoder_set_preferred_video_format(videoStreaming, VIDEO_FORMAT_NV12);
if (whipSimulcastEncoders != nullptr) {
whipSimulcastEncoders->SetVideoFormat(VIDEO_FORMAT_NV12);
}
}

obs_encoder_update(videoStreaming, videoSettings);
obs_encoder_update(audioStreaming, audioSettings);
obs_encoder_update(audioArchive, audioSettings);

if (whipSimulcastEncoders != nullptr) {
whipSimulcastEncoders->Update(videoSettings, videoBitrate);
}
}

void SimpleOutput::UpdateRecordingAudioSettings()
Expand Down Expand Up @@ -1094,6 +1109,9 @@ std::shared_future<void> SimpleOutput::SetupStreaming(obs_service_t *service, Se
}

obs_output_set_video_encoder(streamOutput, videoStreaming);
if (whipSimulcastEncoders != nullptr) {
whipSimulcastEncoders->SetStreamOutput(streamOutput);
}
obs_output_set_audio_encoder(streamOutput, audioStreaming, 0);
obs_output_set_service(streamOutput, service);
return true;
Expand Down Expand Up @@ -1568,6 +1586,9 @@ AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_)
throw "Failed to create streaming video encoder "
"(advanced output)";
obs_encoder_release(videoStreaming);
if (whipSimulcastEncoders != nullptr) {
whipSimulcastEncoders->Create(streamEncoder, main);
}

const char *rate_control =
obs_data_get_string(useStreamEncoder ? streamEncSettings : recordEncSettings, "rate_control");
Expand Down Expand Up @@ -1668,6 +1689,9 @@ void AdvancedOutput::UpdateStreamSettings()
}

obs_encoder_update(videoStreaming, settings);
if (whipSimulcastEncoders != nullptr) {
whipSimulcastEncoders->Update(settings, obs_data_get_int(settings, "bitrate"));
}
}

inline void AdvancedOutput::UpdateRecordingSettings()
Expand Down Expand Up @@ -2082,6 +2106,9 @@ std::shared_future<void> AdvancedOutput::SetupStreaming(obs_service_t *service,
}

obs_output_set_video_encoder(streamOutput, videoStreaming);
if (whipSimulcastEncoders != nullptr) {
whipSimulcastEncoders->SetStreamOutput(streamOutput);
}
obs_output_set_audio_encoder(streamOutput, streamAudioEnc, 0);

if (!is_multitrack_output) {
Expand Down
3 changes: 3 additions & 0 deletions UI/window-basic-main-outputs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <string>

#include "multitrack-video-output.hpp"
#include "whip-simulcast-encoders.hpp"

class OBSBasic;

Expand Down Expand Up @@ -37,6 +38,8 @@ struct BasicOutputHandler {
obs_scene_t *vCamSourceScene = nullptr;
obs_sceneitem_t *vCamSourceSceneItem = nullptr;

std::unique_ptr<WHIPSimulcastEncoders> whipSimulcastEncoders;

std::string outputType;
std::string lastError;

Expand Down
21 changes: 18 additions & 3 deletions UI/window-basic-settings-stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ void OBSBasicSettings::InitStreamPage()
void OBSBasicSettings::LoadStream1Settings()
{
bool ignoreRecommended = config_get_bool(main->Config(), "Stream1", "IgnoreRecommended");
int whipSimulcastTotalLayers = config_get_int(main->Config(), "Stream1", "WHIPSimulcastTotalLayers");

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

if (is_whip)
if (is_whip) {
ui->key->setText(bearer_token);
else
ui->whipSimulcastGroupBox->show();
} else {
ui->key->setText(key);
ui->whipSimulcastGroupBox->hide();
}

ServiceChanged(true);

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

ui->ignoreRecommended->setChecked(ignoreRecommended);
ui->whipSimulcastTotalLayers->setValue(whipSimulcastTotalLayers);

loading = false;

Expand Down Expand Up @@ -316,6 +321,9 @@ void OBSBasicSettings::SaveStream1Settings()

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

auto oldWHIPSimulcastTotalLayers = config_get_int(main->Config(), "Stream1", "WHIPSimulcastTotalLayers");
SaveSpinBox(ui->whipSimulcastTotalLayers, "Stream1", "WHIPSimulcastTotalLayers");

auto oldMultitrackVideoSetting = config_get_bool(main->Config(), "Stream1", "EnableMultitrackVideo");

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

if (oldMultitrackVideoSetting != ui->enableMultitrackVideo->isChecked())
if (oldMultitrackVideoSetting != ui->enableMultitrackVideo->isChecked() ||
oldWHIPSimulcastTotalLayers != ui->whipSimulcastTotalLayers->value())
main->ResetOutputs();

SwapMultiTrack(QT_TO_UTF8(protocol));
Expand Down Expand Up @@ -576,6 +585,12 @@ void OBSBasicSettings::on_service_currentIndexChanged(int idx)
} else {
SwapMultiTrack(QT_TO_UTF8(protocol));
}

if (IsWHIP()) {
ui->whipSimulcastGroupBox->show();
} else {
ui->whipSimulcastGroupBox->hide();
}
}

void OBSBasicSettings::on_customServer_textChanged(const QString &)
Expand Down
1 change: 1 addition & 0 deletions UI/window-basic-settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
HookWidget(ui->authUsername, EDIT_CHANGED, STREAM1_CHANGED);
HookWidget(ui->authPw, EDIT_CHANGED, STREAM1_CHANGED);
HookWidget(ui->ignoreRecommended, CHECK_CHANGED, STREAM1_CHANGED);
HookWidget(ui->whipSimulcastTotalLayers, SCROLL_CHANGED, STREAM1_CHANGED);
HookWidget(ui->enableMultitrackVideo, CHECK_CHANGED, STREAM1_CHANGED);
HookWidget(ui->multitrackVideoMaximumAggregateBitrateAuto, CHECK_CHANGED, STREAM1_CHANGED);
HookWidget(ui->multitrackVideoMaximumAggregateBitrate, SCROLL_CHANGED, STREAM1_CHANGED);
Expand Down
1 change: 1 addition & 0 deletions plugins/obs-webrtc/data/locale/en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ Service.BearerToken="Bearer Token"

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

0 comments on commit 26340d7

Please sign in to comment.