Skip to content

Commit 5e60202

Browse files
committed
obs-webrtc: Add Simulcast Support
1 parent 608d3bf commit 5e60202

File tree

8 files changed

+191
-14
lines changed

8 files changed

+191
-14
lines changed

UI/data/locale/en-US.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,7 @@ Basic.Settings.Stream.StreamSettingsWarning="Open Settings"
956956
Basic.Settings.Stream.MissingUrlAndApiKey="URL and Stream Key are missing.\n\nOpen settings to enter the URL and Stream Key in the 'stream' tab."
957957
Basic.Settings.Stream.MissingUrl="Stream URL is missing.\n\nOpen settings to enter the URL in the 'Stream' tab."
958958
Basic.Settings.Stream.MissingStreamKey="Stream key is missing.\n\nOpen settings to enter the stream key in the 'Stream' tab."
959+
Basic.Settings.Stream.UseSimulcast="Use Simulcast"
959960
Basic.Settings.Stream.IgnoreRecommended="Ignore streaming service setting recommendations"
960961
Basic.Settings.Stream.IgnoreRecommended.Warn.Title="Override Recommended Settings"
961962
Basic.Settings.Stream.IgnoreRecommended.Warn.Text="Warning: Ignoring the service's limitations may result in degraded stream quality or prevent you from streaming.\n\nContinue?"

UI/forms/OBSBasicSettings.ui

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1884,6 +1884,13 @@
18841884
</property>
18851885
</widget>
18861886
</item>
1887+
<item row="4" column="1">
1888+
<widget class="QCheckBox" name="useSimulcast">
1889+
<property name="text">
1890+
<string>Basic.Settings.Stream.UseSimulcast</string>
1891+
</property>
1892+
</widget>
1893+
</item>
18871894
</layout>
18881895
</widget>
18891896
</item>

UI/window-basic-main-outputs.cpp

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,10 @@ void SimpleOutput::LoadStreamingPreset_Lossy(const char *encoderId)
565565
if (!videoStreaming)
566566
throw "Failed to create video streaming encoder (simple output)";
567567
obs_encoder_release(videoStreaming);
568+
569+
if (config_get_bool(main->Config(), "Stream1", "UseSimulcast")) {
570+
CreateSimulcastEncoders(encoderId);
571+
}
568572
}
569573

570574
/* mistakes have been made to lead us to this. */
@@ -867,9 +871,14 @@ void SimpleOutput::Update()
867871
default:
868872
obs_encoder_set_preferred_video_format(videoStreaming,
869873
VIDEO_FORMAT_NV12);
874+
for (auto enc : simulcastEncoders)
875+
obs_encoder_set_preferred_video_format(
876+
enc, VIDEO_FORMAT_NV12);
870877
}
871878

872879
obs_encoder_update(videoStreaming, videoSettings);
880+
SimulcastEncodersUpdate(videoSettings, videoBitrate);
881+
873882
obs_encoder_update(audioStreaming, audioSettings);
874883
obs_encoder_update(audioArchive, audioSettings);
875884
}
@@ -1189,8 +1198,13 @@ FutureHolder<bool> SimpleOutput::SetupStreaming(obs_service_t *service)
11891198
outputType = type;
11901199
}
11911200

1192-
obs_output_set_video_encoder(streamOutput,
1193-
videoStreaming);
1201+
obs_output_set_video_encoder2(
1202+
streamOutput, videoStreaming, 0);
1203+
for (size_t i = 0; i < simulcastEncoders.size();
1204+
i++)
1205+
obs_output_set_video_encoder2(
1206+
streamOutput,
1207+
simulcastEncoders[i], i + 1);
11941208
obs_output_set_audio_encoder(streamOutput,
11951209
audioStreaming, 0);
11961210
obs_output_set_service(streamOutput, service);
@@ -1729,6 +1743,10 @@ AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_)
17291743
"(advanced output)";
17301744
obs_encoder_release(videoStreaming);
17311745

1746+
if (config_get_bool(main->Config(), "Stream1", "UseSimulcast")) {
1747+
CreateSimulcastEncoders(streamEncoder);
1748+
}
1749+
17321750
const char *rate_control = obs_data_get_string(
17331751
useStreamEncoder ? streamEncSettings : recordEncSettings,
17341752
"rate_control");
@@ -1849,6 +1867,8 @@ void AdvancedOutput::UpdateStreamSettings()
18491867
}
18501868

18511869
obs_encoder_update(videoStreaming, settings);
1870+
SimulcastEncodersUpdate(settings,
1871+
obs_data_get_int(settings, "bitrate"));
18521872
}
18531873

18541874
inline void AdvancedOutput::UpdateRecordingSettings()
@@ -2338,8 +2358,13 @@ FutureHolder<bool> AdvancedOutput::SetupStreaming(obs_service_t *service)
23382358
outputType = type;
23392359
}
23402360

2341-
obs_output_set_video_encoder(streamOutput,
2342-
videoStreaming);
2361+
obs_output_set_video_encoder2(
2362+
streamOutput, videoStreaming, 0);
2363+
for (size_t i = 0; i < simulcastEncoders.size();
2364+
i++)
2365+
obs_output_set_video_encoder2(
2366+
streamOutput,
2367+
simulcastEncoders[i], i + 1);
23432368
obs_output_set_audio_encoder(streamOutput,
23442369
streamAudioEnc, 0);
23452370

@@ -2886,3 +2911,52 @@ BasicOutputHandler *CreateAdvancedOutputHandler(OBSBasic *main)
28862911
{
28872912
return new AdvancedOutput(main);
28882913
}
2914+
2915+
void BasicOutputHandler::CreateSimulcastEncoders(const char *encoderId)
2916+
{
2917+
int rescaleFilter =
2918+
config_get_int(main->Config(), "AdvOut", "RescaleFilter");
2919+
if (rescaleFilter == OBS_SCALE_DISABLE) {
2920+
rescaleFilter = OBS_SCALE_BICUBIC;
2921+
}
2922+
2923+
std::string encoder_name = "simulcast_0";
2924+
for (auto i = 0; i < 2; i++) {
2925+
uint32_t width = video_output_get_width(obs_get_video()) /
2926+
(1.5 + (.5 * i));
2927+
width -= width % 2;
2928+
2929+
uint32_t height = video_output_get_height(obs_get_video()) /
2930+
(1.5 + (.5 * i));
2931+
height -= height % 2;
2932+
2933+
encoder_name[encoder_name.size() - 1] = to_string(i).at(0);
2934+
auto simulcast_encoder = obs_video_encoder_create(
2935+
encoderId, encoder_name.c_str(), nullptr, nullptr);
2936+
2937+
if (simulcast_encoder) {
2938+
obs_encoder_set_video(simulcast_encoder,
2939+
obs_get_video());
2940+
obs_encoder_set_scaled_size(simulcast_encoder, width,
2941+
height);
2942+
obs_encoder_set_gpu_scale_type(
2943+
simulcast_encoder,
2944+
(obs_scale_type)rescaleFilter);
2945+
simulcastEncoders.push_back(simulcast_encoder);
2946+
obs_encoder_release(simulcast_encoder);
2947+
} else {
2948+
blog(LOG_WARNING,
2949+
"Failed to create video streaming simulcast encoders (BasicOutputHandler)");
2950+
}
2951+
}
2952+
}
2953+
2954+
void BasicOutputHandler::SimulcastEncodersUpdate(obs_data_t *videoSettings,
2955+
int videoBitrate)
2956+
{
2957+
for (size_t i = 0; i < simulcastEncoders.size(); i++) {
2958+
obs_data_set_int(videoSettings, "bitrate",
2959+
videoBitrate / (2 * (i + 1)));
2960+
obs_encoder_update(simulcastEncoders[i], videoSettings);
2961+
}
2962+
}

UI/window-basic-main-outputs.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ struct BasicOutputHandler {
3838
obs_scene_t *vCamSourceScene = nullptr;
3939
obs_sceneitem_t *vCamSourceSceneItem = nullptr;
4040

41+
std::vector<OBSEncoder> simulcastEncoders;
42+
4143
std::string outputType;
4244
std::string lastError;
4345

@@ -103,6 +105,9 @@ struct BasicOutputHandler {
103105
std::string audio_encoder_id,
104106
std::optional<size_t> vod_track_mixer);
105107
OBSDataAutoRelease GenerateMultitrackVideoStreamDumpConfig();
108+
void CreateSimulcastEncoders(const char *encoderId);
109+
void SimulcastEncodersUpdate(obs_data_t *videoSettings,
110+
int videoBitrate);
106111
};
107112

108113
BasicOutputHandler *CreateSimpleOutputHandler(OBSBasic *main);

UI/window-basic-settings-stream.cpp

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ void OBSBasicSettings::LoadStream1Settings()
112112
{
113113
bool ignoreRecommended =
114114
config_get_bool(main->Config(), "Stream1", "IgnoreRecommended");
115+
bool useSimulcast =
116+
config_get_bool(main->Config(), "Stream1", "UseSimulcast");
115117

116118
obs_service_t *service_obj = main->GetService();
117119
const char *type = obs_service_get_type(service_obj);
@@ -224,10 +226,13 @@ void OBSBasicSettings::LoadStream1Settings()
224226
if (use_custom_server)
225227
ui->serviceCustomServer->setText(server);
226228

227-
if (is_whip)
229+
if (is_whip) {
228230
ui->key->setText(bearer_token);
229-
else
231+
ui->useSimulcast->show();
232+
} else {
230233
ui->key->setText(key);
234+
ui->useSimulcast->hide();
235+
}
231236

232237
ServiceChanged(true);
233238

@@ -241,6 +246,7 @@ void OBSBasicSettings::LoadStream1Settings()
241246
ui->streamPage->setEnabled(!streamActive);
242247

243248
ui->ignoreRecommended->setChecked(ignoreRecommended);
249+
ui->useSimulcast->setChecked(useSimulcast);
244250

245251
loading = false;
246252

@@ -365,6 +371,10 @@ void OBSBasicSettings::SaveStream1Settings()
365371

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

374+
auto oldSimulcastSetting =
375+
config_get_bool(main->Config(), "Stream1", "UseSimulcast");
376+
SaveCheckBox(ui->useSimulcast, "Stream1", "UseSimulcast");
377+
368378
auto oldMultitrackVideoSetting = config_get_bool(
369379
main->Config(), "Stream1", "EnableMultitrackVideo");
370380

@@ -404,7 +414,9 @@ void OBSBasicSettings::SaveStream1Settings()
404414
SaveText(ui->multitrackVideoConfigOverride, "Stream1",
405415
"MultitrackVideoConfigOverride");
406416

407-
if (oldMultitrackVideoSetting != ui->enableMultitrackVideo->isChecked())
417+
if (oldMultitrackVideoSetting !=
418+
ui->enableMultitrackVideo->isChecked() ||
419+
oldSimulcastSetting != ui->useSimulcast->isChecked())
408420
main->ResetOutputs();
409421

410422
SwapMultiTrack(QT_TO_UTF8(protocol));
@@ -661,6 +673,12 @@ void OBSBasicSettings::on_service_currentIndexChanged(int idx)
661673
} else {
662674
SwapMultiTrack(QT_TO_UTF8(protocol));
663675
}
676+
677+
if (IsWHIP()) {
678+
ui->useSimulcast->show();
679+
} else {
680+
ui->useSimulcast->hide();
681+
}
664682
}
665683

666684
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
@@ -420,6 +420,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
420420
HookWidget(ui->authUsername, EDIT_CHANGED, STREAM1_CHANGED);
421421
HookWidget(ui->authPw, EDIT_CHANGED, STREAM1_CHANGED);
422422
HookWidget(ui->ignoreRecommended, CHECK_CHANGED, STREAM1_CHANGED);
423+
HookWidget(ui->useSimulcast, CHECK_CHANGED, STREAM1_CHANGED);
423424
HookWidget(ui->enableMultitrackVideo, CHECK_CHANGED, STREAM1_CHANGED);
424425
HookWidget(ui->multitrackVideoMaximumAggregateBitrateAuto, CHECK_CHANGED, STREAM1_CHANGED);
425426
HookWidget(ui->multitrackVideoMaximumAggregateBitrate, SCROLL_CHANGED, STREAM1_CHANGED);

plugins/obs-webrtc/whip-output.cpp

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ const uint8_t video_payload_type = 96;
2424
// ~3 seconds of 8.5 Megabit video
2525
const int video_nack_buffer_size = 4000;
2626

27+
const std::string rtpHeaderExtUriMid = "urn:ietf:params:rtp-hdrext:sdes:mid";
28+
const std::string rtpHeaderExtUriRid =
29+
"urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id";
30+
31+
const std::string highRid = "h";
32+
const std::string medRid = "m";
33+
const std::string lowRid = "l";
34+
2735
WHIPOutput::WHIPOutput(obs_data_t *, obs_output_t *output)
2836
: output(output),
2937
endpoint_url(),
@@ -39,8 +47,7 @@ WHIPOutput::WHIPOutput(obs_data_t *, obs_output_t *output)
3947
total_bytes_sent(0),
4048
connect_time_ms(0),
4149
start_time_ns(0),
42-
last_audio_timestamp(0),
43-
last_video_timestamp(0)
50+
last_audio_timestamp(0)
4451
{
4552
}
4653

@@ -57,6 +64,27 @@ bool WHIPOutput::Start()
5764
{
5865
std::lock_guard<std::mutex> l(start_stop_mutex);
5966

67+
for (size_t idx = 0; idx < 5; idx++) {
68+
auto encoder = obs_output_get_video_encoder2(output, idx);
69+
if (encoder == nullptr) {
70+
break;
71+
}
72+
73+
auto v = std::make_shared<videoLayerState>();
74+
if (idx == 0) {
75+
v->ssrc = base_ssrc + 1;
76+
v->rid = highRid;
77+
} else if (idx == 1) {
78+
v->ssrc = base_ssrc + 2;
79+
v->rid = medRid;
80+
} else if (idx == 2) {
81+
v->ssrc = base_ssrc + 3;
82+
v->rid = lowRid;
83+
}
84+
85+
videoLayerStates[obs_encoder_get_width(encoder)] = v;
86+
}
87+
6088
if (!obs_output_can_begin_data_capture(output, 0))
6189
return false;
6290
if (!obs_output_initialize_encoders(output, 0))
@@ -92,10 +120,28 @@ void WHIPOutput::Data(struct encoder_packet *packet)
92120
audio_sr_reporter);
93121
last_audio_timestamp = packet->dts_usec;
94122
} else if (video_track && packet->type == OBS_ENCODER_VIDEO) {
95-
int64_t duration = packet->dts_usec - last_video_timestamp;
123+
auto rtp_config = video_sr_reporter->rtpConfig;
124+
auto videoLayerState =
125+
videoLayerStates[obs_encoder_get_width(packet->encoder)];
126+
if (videoLayerState == nullptr) {
127+
Stop(false);
128+
obs_output_signal_stop(output, OBS_OUTPUT_ENCODE_ERROR);
129+
return;
130+
}
131+
132+
rtp_config->sequenceNumber = videoLayerState->sequenceNumber;
133+
rtp_config->ssrc = videoLayerState->ssrc;
134+
rtp_config->rid = videoLayerState->rid;
135+
rtp_config->timestamp = videoLayerState->rtpTimestamp;
136+
int64_t duration =
137+
packet->dts_usec - videoLayerState->lastVideoTimestamp;
138+
96139
Send(packet->data, packet->size, duration, video_track,
97140
video_sr_reporter);
98-
last_video_timestamp = packet->dts_usec;
141+
142+
videoLayerState->sequenceNumber = rtp_config->sequenceNumber;
143+
videoLayerState->lastVideoTimestamp = packet->dts_usec;
144+
videoLayerState->rtpTimestamp = rtp_config->timestamp;
99145
}
100146
}
101147

@@ -151,9 +197,24 @@ void WHIPOutput::ConfigureVideoTrack(std::string media_stream_id,
151197
video_description.addSSRC(ssrc, cname, media_stream_id,
152198
media_stream_track_id);
153199

200+
video_description.addExtMap(
201+
rtc::Description::Entry::ExtMap(1, rtpHeaderExtUriMid));
202+
video_description.addExtMap(
203+
rtc::Description::Entry::ExtMap(2, rtpHeaderExtUriRid));
204+
205+
if (videoLayerStates.size() >= 2) {
206+
for (auto i = videoLayerStates.rbegin();
207+
i != videoLayerStates.rend(); i++) {
208+
video_description.addRid(i->second->rid);
209+
}
210+
}
211+
154212
auto rtp_config = std::make_shared<rtc::RtpPacketizationConfig>(
155213
ssrc, cname, video_payload_type,
156214
rtc::H264RtpPacketizer::defaultClockRate);
215+
rtp_config->midId = 1;
216+
rtp_config->ridId = 2;
217+
rtp_config->mid = video_mid;
157218

158219
const obs_encoder_t *encoder = obs_output_get_video_encoder2(output, 0);
159220
if (!encoder)
@@ -653,7 +714,7 @@ void WHIPOutput::StopThread(bool signal)
653714
connect_time_ms = 0;
654715
start_time_ns = 0;
655716
last_audio_timestamp = 0;
656-
last_video_timestamp = 0;
717+
videoLayerStates.clear();
657718
}
658719

659720
void WHIPOutput::Send(void *data, uintptr_t size, uint64_t duration,
@@ -697,7 +758,8 @@ void WHIPOutput::Send(void *data, uintptr_t size, uint64_t duration,
697758

698759
void register_whip_output()
699760
{
700-
const uint32_t base_flags = OBS_OUTPUT_ENCODED | OBS_OUTPUT_SERVICE;
761+
const uint32_t base_flags = OBS_OUTPUT_ENCODED | OBS_OUTPUT_SERVICE |
762+
OBS_OUTPUT_MULTI_TRACK_AV;
701763

702764
const char *audio_codecs = "opus";
703765
#ifdef ENABLE_HEVC

0 commit comments

Comments
 (0)