Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

obs-webrtc: Add Simulcast Support #10885

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading