From af7762890050dca6972448dfe754c90eb68e4492 Mon Sep 17 00:00:00 2001 From: Alex Luccisano Date: Mon, 25 May 2026 11:54:34 -0400 Subject: [PATCH] frontend: Fix null settings and DBR for custom configs When using custom RTMP output with a JSON config that omits encoder settings, settings.dump() produces "null" which obs_data_create_from_json cannot parse. Fall back to obs_data_create() for null/non-object settings so encoders use their defaults. Additionally, when bitrate_interpolation_points is absent, the code unconditionally set interpolation_table_data as empty arrays. This caused build_dbr_interpolation_table to wipe the default linear table, leaving DBR enabled but unable to adjust bitrates during congestion. Only set the interpolation data when all encoders provide it, otherwise fall back to the default linear 0-to-max interpolation. --- frontend/utility/MultitrackVideoOutput.cpp | 38 +++++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/frontend/utility/MultitrackVideoOutput.cpp b/frontend/utility/MultitrackVideoOutput.cpp index 0410ff19e..76614e02f 100644 --- a/frontend/utility/MultitrackVideoOutput.cpp +++ b/frontend/utility/MultitrackVideoOutput.cpp @@ -16,6 +16,7 @@ #include #include +#include #include // Codec profile strings @@ -238,7 +239,14 @@ static OBSEncoderAutoRelease create_video_encoder(DStr &name_buffer, size_t enco dstr_printf(name_buffer, "multitrack video video encoder %zu", encoder_index); - OBSDataAutoRelease encoder_settings = obs_data_create_from_json(encoder_config.settings.dump().c_str()); + // settings.dump() produces "null" when the config omits encoder settings, which + // obs_data_create_from_json cannot parse. Fall back to empty settings so the encoder uses its defaults. + OBSDataAutoRelease encoder_settings = + encoder_config.settings.is_object() ? obs_data_create_from_json(encoder_config.settings.dump().c_str()) + : obs_data_create(); + if (!encoder_config.settings.is_object()) { + blog(LOG_INFO, "Encoder %zu has no settings, using defaults", encoder_index); + } /* VAAPI-based encoders unfortunately use an integer for "profile". Until a string-based "profile" can be used with * VAAPI, find the corresponding integer value and update the settings with an integer-based "profile". @@ -613,10 +621,11 @@ static bool create_video_encoders(const GoLiveApi::Config &go_live_config, obs_output_set_video_encoder2(recording_output, encoder, i); auto &data = go_live_config.encoder_configurations[i].bitrate_interpolation_points; - if (data.has_value()) + if (data.has_value()) { bitrate_interpolation_array.push_back(*data); - else + } else { bitrate_interpolation_array.push_back(json::array()); + } } video_encoder_group = encoder_group; @@ -675,7 +684,15 @@ static void create_audio_encoders(const GoLiveApi::Config &go_live_config, for (size_t i = 0; i < configs.size(); i++) { dstr_printf(encoder_name_buffer, "%s %zu", name_prefix, i); - OBSDataAutoRelease settings = obs_data_create_from_json(configs[i].settings.dump().c_str()); + // settings.dump() produces "null" when the config omits encoder settings, + // which obs_data_create_from_json cannot parse. Fall back to empty settings. + OBSDataAutoRelease settings = + configs[i].settings.is_object() + ? obs_data_create_from_json(configs[i].settings.dump().c_str()) + : obs_data_create(); + if (!configs[i].settings.is_object()) { + blog(LOG_INFO, "Audio encoder %zu has no settings, using defaults", i); + } OBSEncoderAutoRelease audio_encoder = create_audio_encoder(encoder_name_buffer->array, audio_encoder_id, settings, mixer_idx); @@ -782,7 +799,18 @@ static OBSOutputs SetupOBSOutput(QWidget *parent, const QString &multitrack_vide return {nullptr, nullptr}; OBSDataAutoRelease settings = obs_output_get_settings(output); - obs_data_set_string(settings, "interpolation_table_data", bitrate_interpolation_array.dump().c_str()); + // Only set interpolation_table_data if every encoder has interpolation points. Partial data would + // fail validation in the output plugin and disable DBR. Omitting it lets the output use its default + // linear interpolation (proportional scaling from 0 to max bitrate). + bool has_interpolation_data = std::all_of(bitrate_interpolation_array.begin(), + bitrate_interpolation_array.end(), + [](const json &arr) { return !arr.empty(); }); + if (has_interpolation_data) { + obs_data_set_string(settings, "interpolation_table_data", bitrate_interpolation_array.dump().c_str()); + } else { + blog(LOG_INFO, + "Interpolation data missing for one or more encoders, using default linear interpolation"); + } obs_output_update(output, settings); std::vector requested_speaker_layouts;