Skip to content

Commit 839163a

Browse files
committed
fix initial bitrate selection
1 parent 71adf8b commit 839163a

3 files changed

Lines changed: 109 additions & 22 deletions

File tree

obs-studio-server/source/nodeobs_autoconfig.cpp

Lines changed: 77 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
******************************************************************************/
1818

1919
#include "nodeobs_autoconfig.h"
20+
#include <algorithm>
2021
#include <array>
2122
#include <future>
2223
#include <set>
@@ -558,7 +559,34 @@ void autoConfig::TestBandwidthThreadV2(void)
558559
type = "rtmp_output";
559560
std::string outputName = "autoconfig_bw_" + std::to_string(i);
560561
targets[i]->CreateOutput(type, outputName);
561-
targets[i]->testBandwidth(gotError);
562+
563+
// Pick a high test bitrate so the measurement reflects the link's
564+
// real ceiling rather than whatever low value the user has set.
565+
// - user's current bitrate (might already be high)
566+
// - platform cap probed via obs_service_apply_encoder_settings
567+
// (Twitch returns 6000 for non-partners; partners higher)
568+
// - 6000 fallback when no platform hook exists (custom RTMP, etc.)
569+
int userBitrate = 0;
570+
if (targets[i]->videoEncoder) {
571+
obs_data_t *s = obs_encoder_get_settings(targets[i]->videoEncoder);
572+
userBitrate = (int)obs_data_get_int(s, "bitrate");
573+
obs_data_release(s);
574+
}
575+
int platformCap = 0;
576+
if (targets[i]->service) {
577+
obs_data_t *probe = obs_data_create();
578+
obs_data_set_int(probe, "bitrate", 50000);
579+
obs_service_apply_encoder_settings(targets[i]->service, probe, nullptr);
580+
int capped = (int)obs_data_get_int(probe, "bitrate");
581+
if (capped > 0 && capped < 50000)
582+
platformCap = capped;
583+
obs_data_release(probe);
584+
}
585+
int testBitrate = std::max({userBitrate, platformCap, 6000});
586+
blog(LOG_INFO, "TestBandwidthV2: target %zu test bitrate %d (user=%d, platformCap=%d)", i, testBitrate, userBitrate,
587+
platformCap);
588+
589+
targets[i]->testBandwidth(gotError, testBitrate);
562590

563591
if (!gotError && targets[i]->GetOutput()) {
564592
testingServices.push_back(targets[i]);
@@ -647,11 +675,24 @@ void autoConfig::TestBandwidthThreadV2(void)
647675
result.ms = elapsedMs;
648676
result.targetIndex = testingServiceTargetIdx[si];
649677

650-
if (droppedFrames > 0 || (int)bitrate < (runContext.startingBitrate * 75 / 100)) {
651-
result.bitrate = (int)bitrate * 70 / 100;
652-
} else {
653-
result.bitrate = runContext.startingBitrate;
654-
}
678+
// Use the per-target test bitrate (still active on the encoder
679+
// until CleanTestMode runs below) as the reference, not the
680+
// global runContext.startingBitrate which doesn't reflect the
681+
// per-target ceiling-search override.
682+
int testBitrateRef = 0;
683+
if (streaming->videoEncoder) {
684+
obs_data_t *encSettings = obs_encoder_get_settings(streaming->videoEncoder);
685+
testBitrateRef = (int)obs_data_get_int(encSettings, "bitrate");
686+
obs_data_release(encSettings);
687+
}
688+
if (testBitrateRef <= 0)
689+
testBitrateRef = runContext.startingBitrate;
690+
691+
if (droppedFrames > 0 || (int)bitrate < (testBitrateRef * 75 / 100)) {
692+
result.bitrate = (int)bitrate * 70 / 100;
693+
} else {
694+
result.bitrate = testBitrateRef;
695+
}
655696

656697
testResults.push_back(result);
657698
}
@@ -1732,8 +1773,8 @@ static void applyResults()
17321773
v.output_width = ((uint32_t)runContext.idealResolutionCX) & 0xFFFFFFFC;
17331774
v.output_height = ((uint32_t)runContext.idealResolutionCY) & 0xFFFFFFFE;
17341775

1735-
blog(LOG_INFO, "applyResults: ctx=%p current=%ux%u@%u/%u requested=%ux%u@%u/%u", video, video->output_width,
1736-
video->output_height, video->fps_num, video->fps_den, v.output_width, v.output_height, v.fps_num, v.fps_den);
1776+
blog(LOG_INFO, "applyResults: ctx=%p current=%ux%u@%u/%u requested=%ux%u@%u/%u", video, video->output_width, video->output_height,
1777+
video->fps_num, video->fps_den, v.output_width, v.output_height, v.fps_num, v.fps_den);
17371778

17381779
// Skip the libobs call when nothing changes — the common case where
17391780
// autoconfig picks the resolution/FPS the canvas is already running at.
@@ -1775,33 +1816,49 @@ static void applyResults()
17751816
blog(LOG_INFO, "applyResults: target %llu: applied service server '%s'", st.id, targetServer.c_str());
17761817
}
17771818

1778-
// Streaming bitrate — three caps applied in order:
1779-
// a) EstimateUpperBitrate: theoretical max for the chosen res/FPS
1780-
// b) Hardware encoder headroom: 14% multiplier
1781-
// c) Platform cap via obs_service_apply_encoder_settings (per-target)
1819+
// Streaming bitrate selection (Option 2 — heuristic as floor, not ceiling):
1820+
// 1. Read user's current bitrate (CleanTestMode restored it before this).
1821+
// 2. Compute OBS quality heuristic for the chosen res/FPS.
1822+
// 3. Pick max(user, heuristic) — respect user when above the heuristic;
1823+
// otherwise use the heuristic as the recommendation.
1824+
// 4. Cap by what the bandwidth test actually delivered (targetBitrate).
1825+
// 5. Cap by the per-platform service cap (Twitch etc.).
17821826
if (st.streaming->videoEncoder && targetBitrate > 0) {
1827+
int userBitrate = 0;
1828+
{
1829+
obs_data_t *s = obs_encoder_get_settings(st.streaming->videoEncoder);
1830+
userBitrate = (int)obs_data_get_int(s, "bitrate");
1831+
obs_data_release(s);
1832+
}
1833+
17831834
long double upperBitrate_d = EstimateUpperBitrate((int)runContext.idealResolutionCX, (int)runContext.idealResolutionCY,
17841835
runContext.idealFPSNum, runContext.idealFPSDen ? runContext.idealFPSDen : 1);
1785-
uint64_t upperBitrate = (uint64_t)(std::floor(upperBitrate_d / 50.0L) * 50.0L);
1786-
if (runContext.streamingEncoder != Encoder::x264 && upperBitrate > 0) {
1787-
upperBitrate = upperBitrate * 114ULL / 100ULL;
1836+
uint64_t heuristic = (uint64_t)(std::floor(upperBitrate_d / 50.0L) * 50.0L);
1837+
if (runContext.streamingEncoder != Encoder::x264 && heuristic > 0) {
1838+
heuristic = heuristic * 114ULL / 100ULL;
17881839
}
1789-
uint64_t finalBitrate = targetBitrate;
1790-
if (upperBitrate > 0 && finalBitrate > upperBitrate)
1791-
finalBitrate = upperBitrate;
17921840

1841+
uint64_t finalBitrate = ((uint64_t)userBitrate > heuristic) ? (uint64_t)userBitrate : heuristic;
1842+
uint64_t preMeasured = finalBitrate;
1843+
if (finalBitrate > targetBitrate)
1844+
finalBitrate = targetBitrate;
1845+
1846+
uint64_t platformCapped = 0;
17931847
if (st.streaming->service) {
17941848
obs_data_t *capSettings = obs_data_create();
17951849
obs_data_set_int(capSettings, "bitrate", (long long)finalBitrate);
17961850
obs_service_apply_encoder_settings(st.streaming->service, capSettings, nullptr);
1797-
uint64_t platformCapped = (uint64_t)obs_data_get_int(capSettings, "bitrate");
1851+
platformCapped = (uint64_t)obs_data_get_int(capSettings, "bitrate");
17981852
if (platformCapped > 0 && platformCapped < finalBitrate) {
1799-
blog(LOG_INFO, "applyResults: platform cap reduced bitrate from %llu to %llu", finalBitrate, platformCapped);
18001853
finalBitrate = platformCapped;
18011854
}
18021855
obs_data_release(capSettings);
18031856
}
18041857

1858+
blog(LOG_INFO,
1859+
"applyResults: target %llu picked %llu (user=%d heuristic=%llu choseBeforeCaps=%llu measured=%llu platformCap=%llu)",
1860+
st.id, finalBitrate, userBitrate, heuristic, preMeasured, targetBitrate, platformCapped);
1861+
18051862
obs_data_t *encSettings = obs_data_create();
18061863
obs_data_set_int(encSettings, "bitrate", (long long)finalBitrate);
18071864
obs_encoder_update(st.streaming->videoEncoder, encSettings);

obs-studio-server/source/osn-streaming.cpp

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,27 @@ osn::Streaming::~Streaming()
4040
}
4141
}
4242

43-
void osn::Streaming::testBandwidth(bool &gotError)
43+
void osn::Streaming::testBandwidth(bool &gotError, int testBitrate)
4444
{
4545
if (!service || !GetOutput()) {
4646
gotError = true;
4747
return;
4848
}
4949

50+
// Override the user's encoder bitrate with the ceiling-search target so the
51+
// bandwidth measurement isn't capped by whatever the user happens to have
52+
// set (often 2500). Restored in CleanTestMode().
53+
if (testBitrate > 0 && videoEncoder) {
54+
obs_data_t *encSettings = obs_encoder_get_settings(videoEncoder);
55+
originalEncoderBitrate = (int)obs_data_get_int(encSettings, "bitrate");
56+
obs_data_release(encSettings);
57+
58+
obs_data_t *override = obs_data_create();
59+
obs_data_set_int(override, "bitrate", (long long)testBitrate);
60+
obs_encoder_update(videoEncoder, override);
61+
obs_data_release(override);
62+
}
63+
5064
if (originalServiceSettings) {
5165
obs_data_release(originalServiceSettings);
5266
}
@@ -101,6 +115,14 @@ void osn::Streaming::CleanTestMode()
101115
originalServiceSettings = nullptr;
102116
}
103117

118+
if (videoEncoder && originalEncoderBitrate > 0) {
119+
obs_data_t *restore = obs_data_create();
120+
obs_data_set_int(restore, "bitrate", (long long)originalEncoderBitrate);
121+
obs_encoder_update(videoEncoder, restore);
122+
obs_data_release(restore);
123+
originalEncoderBitrate = 0;
124+
}
125+
104126
testMode = false;
105127
}
106128

obs-studio-server/source/osn-streaming.hpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class Streaming : public Output {
5252
simple = true;
5353
testMode = false;
5454
originalServiceSettings = nullptr;
55+
originalEncoderBitrate = 0;
5556
}
5657
virtual ~Streaming();
5758

@@ -74,6 +75,10 @@ class Streaming : public Output {
7475
bool simple;
7576
bool testMode;
7677
obs_data_t *originalServiceSettings;
78+
// Bitrate the user had on videoEncoder before testBandwidth() bumped it for
79+
// the ceiling-search measurement. Restored in CleanTestMode(). 0 = nothing
80+
// to restore.
81+
int originalEncoderBitrate;
7782

7883
bool isTwitchVODSupported();
7984
bool ApplyOutputSettings(obs_output_t *output, std::string &errorMessage);
@@ -89,7 +94,10 @@ class Streaming : public Output {
8994
// pipelines for simple/advanced); testBandwidth() and CleanTestMode() are shared.
9095
virtual void start() {}
9196
virtual void checkOutput() {}
92-
void testBandwidth(bool &gotError);
97+
// testBitrate: bitrate to override videoEncoder with for the duration of the
98+
// measurement. The user's original bitrate is restored by CleanTestMode().
99+
// Pass 0 to leave the encoder untouched.
100+
void testBandwidth(bool &gotError, int testBitrate = 0);
93101
void CleanTestMode();
94102
};
95103

0 commit comments

Comments
 (0)