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 frontend/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. <a href='https://obsproject.com/kb/whip-streaming-guide'>Learn More</a>"
Basic.Settings.Stream.WHIPSimulcastTotalLayers="Total Layers"
Basic.Settings.Stream.AdvancedOptions="Advanced Options"

# basic mode 'output' settings
Expand Down
85 changes: 85 additions & 0 deletions frontend/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
1 change: 1 addition & 0 deletions frontend/settings/OBSBasicSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,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
21 changes: 18 additions & 3 deletions frontend/settings/OBSBasicSettings_Stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,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 @@ -191,10 +192,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 @@ -208,6 +212,7 @@ void OBSBasicSettings::LoadStream1Settings()
ui->streamPage->setEnabled(!streamActive);

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

loading = false;

Expand Down Expand Up @@ -309,6 +314,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 @@ -336,7 +344,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 @@ -569,6 +578,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
12 changes: 12 additions & 0 deletions frontend/utility/AdvancedOutput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ 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, config_get_int(main->Config(), "AdvOut", "RescaleFilter"),
config_get_int(main->Config(), "Stream1", "WHIPSimulcastTotalLayers"),
video_output_get_width(obs_get_video()),
video_output_get_height(obs_get_video()));
}

const char *rate_control =
obs_data_get_string(useStreamEncoder ? streamEncSettings : recordEncSettings, "rate_control");
Expand Down Expand Up @@ -228,6 +234,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 @@ -630,6 +639,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 frontend/utility/BasicOutputHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,9 @@ 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
3 changes: 3 additions & 0 deletions frontend/utility/BasicOutputHandler.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include <utility/MultitrackVideoOutput.hpp>
#include <utility/WHIPSimulcastEncoders.hpp>

#include <obs.hpp>
#include <util/dstr.hpp>
Expand Down Expand Up @@ -42,6 +43,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
17 changes: 17 additions & 0 deletions frontend/utility/SimpleOutput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ 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, config_get_int(main->Config(), "AdvOut", "RescaleFilter"),
config_get_int(main->Config(), "Stream1", "WHIPSimulcastTotalLayers"),
video_output_get_width(obs_get_video()),
video_output_get_height(obs_get_video()));
}
}

/* mistakes have been made to lead us to this. */
Expand Down Expand Up @@ -334,11 +341,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 @@ -613,6 +627,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
68 changes: 68 additions & 0 deletions frontend/utility/WHIPSimulcastEncoders.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#pragma once

struct WHIPSimulcastEncoders {
public:
void Create(const char *encoderId, int rescaleFilter, int whipSimulcastTotalLayers, uint32_t outputWidth,
uint32_t outputHeight)
{
if (rescaleFilter == OBS_SCALE_DISABLE) {
rescaleFilter = OBS_SCALE_BICUBIC;
}

if (whipSimulcastTotalLayers <= 1) {
return;
}

auto widthStep = outputWidth / whipSimulcastTotalLayers;
auto heightStep = outputHeight / whipSimulcastTotalLayers;
std::string encoder_name = "whip_simulcast_0";

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;
};
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