diff --git a/UI/audio-encoders.cpp b/UI/audio-encoders.cpp index c03501e05..08c4af32b 100644 --- a/UI/audio-encoders.cpp +++ b/UI/audio-encoders.cpp @@ -23,18 +23,17 @@ static const char *EncoderName(const std::string &id) return NullToEmpty(obs_encoder_get_display_name(id.c_str())); } -static map bitrateMap; - -static void HandleIntProperty(obs_property_t *prop, const char *id) +static void HandleIntProperty(obs_property_t *prop, std::vector &bitrates) { const int max_ = obs_property_int_max(prop); const int step = obs_property_int_step(prop); for (int i = obs_property_int_min(prop); i <= max_; i += step) - bitrateMap[i] = id; + bitrates.push_back(i); } -static void HandleListProperty(obs_property_t *prop, const char *id) +static void HandleListProperty(obs_property_t *prop, const char *id, + std::vector &bitrates) { obs_combo_format format = obs_property_list_format(prop); if (format != OBS_COMBO_FORMAT_INT) { @@ -53,7 +52,7 @@ static void HandleListProperty(obs_property_t *prop, const char *id) int bitrate = static_cast(obs_property_list_item_int(prop, i)); - bitrateMap[bitrate] = id; + bitrates.push_back(bitrate); } } @@ -86,7 +85,7 @@ static void HandleSampleRate(obs_property_t *prop, const char *id) obs_property_modified(prop, data.get()); } -static void HandleEncoderProperties(const char *id) +static void HandleEncoderProperties(const char *id, std::vector &bitrates) { auto DestroyProperties = [](obs_properties_t *props) { obs_properties_destroy(props); @@ -112,10 +111,10 @@ static void HandleEncoderProperties(const char *id) obs_property_type type = obs_property_get_type(bitrate); switch (type) { case OBS_PROPERTY_INT: - return HandleIntProperty(bitrate, id); + return HandleIntProperty(bitrate, bitrates); case OBS_PROPERTY_LIST: - return HandleListProperty(bitrate, id); + return HandleListProperty(bitrate, id, bitrates); default: break; @@ -132,10 +131,75 @@ static const char *GetCodec(const char *id) return NullToEmpty(obs_get_encoder_codec(id)); } -static void PopulateBitrateMap() +static std::vector fallbackBitrates; +static map> encoderBitrates; + +static void PopulateBitrateLists() { static once_flag once; + call_once(once, []() { + struct obs_audio_info aoi; + obs_get_audio_info(&aoi); + uint32_t output_channels = get_audio_channels(aoi.speakers); + + /* NOTE: ffmpeg_aac and ffmpeg_opus have the same properties + * their bitrates will also be used as a fallback */ + HandleEncoderProperties("ffmpeg_aac", fallbackBitrates); + + if (fallbackBitrates.empty()) + blog(LOG_ERROR, "Could not enumerate fallback encoder " + "bitrates"); + + ostringstream ss; + for (auto &bitrate : fallbackBitrates) + ss << "\n " << setw(3) << bitrate << " kbit/s:"; + + blog(LOG_DEBUG, "Fallback encoder bitrates:%s", + ss.str().c_str()); + + const char *id = nullptr; + for (size_t i = 0; obs_enum_encoder_types(i, &id); i++) { + if (obs_get_encoder_type(id) != OBS_ENCODER_AUDIO) + continue; + + if (strcmp(id, "ffmpeg_aac") == 0 || + strcmp(id, "ffmpeg_opus") == 0) + continue; + + std::string encoder = id; + + HandleEncoderProperties(id, encoderBitrates[encoder]); + + if (encoderBitrates[encoder].empty()) + blog(LOG_ERROR, + "Could not enumerate %s encoder " + "bitrates", + id); + + ostringstream ss; + for (auto &bitrate : encoderBitrates[encoder]) + ss << "\n " << setw(3) << bitrate + << " kbit/s"; + + blog(LOG_DEBUG, "%s (%s) encoder bitrates:%s", + EncoderName(id), id, ss.str().c_str()); + } + + if (encoderBitrates.empty() && fallbackBitrates.empty()) + blog(LOG_ERROR, "Could not enumerate any audio encoder " + "bitrates"); + }); +} + +static map simpleAACBitrateMap; + +static void PopulateSimpleAACBitrateMap() +{ + PopulateBitrateLists(); + + static once_flag once; + call_once(once, []() { const string encoders[] = { "ffmpeg_aac", @@ -148,7 +212,8 @@ static void PopulateBitrateMap() struct obs_audio_info aoi; obs_get_audio_info(&aoi); - HandleEncoderProperties(fallbackEncoder.c_str()); + for (auto &bitrate : fallbackBitrates) + simpleAACBitrateMap[bitrate] = fallbackEncoder; const char *id = nullptr; for (size_t i = 0; obs_enum_encoder_types(i, &id); i++) { @@ -160,48 +225,115 @@ static void PopulateBitrateMap() end(encoders)) continue; - if (strcmp(GetCodec(id), "AAC") != 0) + if (strcmp(GetCodec(id), "aac") != 0) continue; - HandleEncoderProperties(id); + std::string encoder = id; + if (encoderBitrates[encoder].empty()) + continue; + + for (auto &bitrate : encoderBitrates[encoder]) + simpleAACBitrateMap[bitrate] = encoder; } for (auto &encoder : encoders) { if (encoder == fallbackEncoder) continue; - if (strcmp(GetCodec(encoder.c_str()), "AAC") != 0) + if (strcmp(GetCodec(encoder.c_str()), "aac") != 0) continue; - HandleEncoderProperties(encoder.c_str()); + for (auto &bitrate : encoderBitrates[encoder]) + simpleAACBitrateMap[bitrate] = encoder; } - if (bitrateMap.empty()) { + if (simpleAACBitrateMap.empty()) { blog(LOG_ERROR, "Could not enumerate any AAC encoder " "bitrates"); return; } ostringstream ss; - for (auto &entry : bitrateMap) + for (auto &entry : simpleAACBitrateMap) ss << "\n " << setw(3) << entry.first << " kbit/s: '" << EncoderName(entry.second) << "' (" << entry.second << ')'; - blog(LOG_DEBUG, "AAC encoder bitrate mapping:%s", + blog(LOG_DEBUG, "AAC simple encoder bitrate mapping:%s", ss.str().c_str()); }); } -const map &GetAACEncoderBitrateMap() +static map simpleOpusBitrateMap; + +static void PopulateSimpleOpusBitrateMap() { - PopulateBitrateMap(); - return bitrateMap; + PopulateBitrateLists(); + + static once_flag once; + + call_once(once, []() { + struct obs_audio_info aoi; + obs_get_audio_info(&aoi); + uint32_t output_channels = get_audio_channels(aoi.speakers); + + for (auto &bitrate : fallbackBitrates) + simpleOpusBitrateMap[bitrate] = "ffmpeg_opus"; + + const char *id = nullptr; + for (size_t i = 0; obs_enum_encoder_types(i, &id); i++) { + if (strcmp(GetCodec(id), "opus") != 0) + continue; + + std::string encoder = id; + if (encoderBitrates[encoder].empty()) + continue; + + for (auto &bitrate : encoderBitrates[encoder]) + simpleOpusBitrateMap[bitrate] = encoder; + } + + if (simpleOpusBitrateMap.empty()) { + blog(LOG_ERROR, "Could not enumerate any Opus encoder " + "bitrates"); + return; + } + + ostringstream ss; + for (auto &entry : simpleOpusBitrateMap) + ss << "\n " << setw(3) << entry.first + << " kbit/s: '" << EncoderName(entry.second) << "' (" + << entry.second << ')'; + + blog(LOG_DEBUG, "Opus simple encoder bitrate mapping:%s", + ss.str().c_str()); + }); } -const char *GetAACEncoderForBitrate(int bitrate) +const map &GetSimpleAACEncoderBitrateMap() { - auto &map_ = GetAACEncoderBitrateMap(); + PopulateSimpleAACBitrateMap(); + return simpleAACBitrateMap; +} + +const map &GetSimpleOpusEncoderBitrateMap() +{ + PopulateSimpleOpusBitrateMap(); + return simpleOpusBitrateMap; +} + +const char *GetSimpleAACEncoderForBitrate(int bitrate) +{ + auto &map_ = GetSimpleAACEncoderBitrateMap(); + auto res = map_.find(bitrate); + if (res == end(map_)) + return NULL; + return res->second.c_str(); +} + +const char *GetSimpleOpusEncoderForBitrate(int bitrate) +{ + auto &map_ = GetSimpleOpusEncoderBitrateMap(); auto res = map_.find(bitrate); if (res == end(map_)) return NULL; @@ -210,13 +342,13 @@ const char *GetAACEncoderForBitrate(int bitrate) #define INVALID_BITRATE 10000 -int FindClosestAvailableAACBitrate(int bitrate) +static int FindClosestAvailableSimpleBitrate(int bitrate, + const map &map) { - auto &map_ = GetAACEncoderBitrateMap(); int prev = 0; int next = INVALID_BITRATE; - for (auto val : map_) { + for (auto val : map) { if (next > val.first) { if (val.first == bitrate) return bitrate; @@ -234,3 +366,51 @@ int FindClosestAvailableAACBitrate(int bitrate) return prev; return 192; } + +int FindClosestAvailableSimpleAACBitrate(int bitrate) +{ + return FindClosestAvailableSimpleBitrate( + bitrate, GetSimpleAACEncoderBitrateMap()); +} + +int FindClosestAvailableSimpleOpusBitrate(int bitrate) +{ + return FindClosestAvailableSimpleBitrate( + bitrate, GetSimpleOpusEncoderBitrateMap()); +} + +const std::vector &GetAudioEncoderBitrates(const char *id) +{ + std::string encoder = id; + PopulateBitrateLists(); + if (encoderBitrates[encoder].empty()) + return fallbackBitrates; + return encoderBitrates[encoder]; +} + +int FindClosestAvailableAudioBitrate(const char *id, int bitrate) +{ + int prev = 0; + int next = INVALID_BITRATE; + std::string encoder = id; + + for (auto val : encoderBitrates[encoder].empty() + ? fallbackBitrates + : encoderBitrates[encoder]) { + if (next > val) { + if (val == bitrate) + return bitrate; + + if (val < next && val > bitrate) + next = val; + if (val > prev && val < bitrate) + prev = val; + } + } + + if (next != INVALID_BITRATE) + return next; + if (prev != 0) + return prev; + return 192; +} diff --git a/UI/audio-encoders.hpp b/UI/audio-encoders.hpp index 3bb0596e6..b7fd0ab10 100644 --- a/UI/audio-encoders.hpp +++ b/UI/audio-encoders.hpp @@ -3,7 +3,15 @@ #include #include +#include -const std::map &GetAACEncoderBitrateMap(); -const char *GetAACEncoderForBitrate(int bitrate); -int FindClosestAvailableAACBitrate(int bitrate); +const std::map &GetSimpleAACEncoderBitrateMap(); +const char *GetSimpleAACEncoderForBitrate(int bitrate); +int FindClosestAvailableSimpleAACBitrate(int bitrate); + +const std::map &GetSimpleOpusEncoderBitrateMap(); +const char *GetSimpleOpusEncoderForBitrate(int bitrate); +int FindClosestAvailableSimpleOpusBitrate(int bitrate); + +const std::vector &GetAudioEncoderBitrates(const char *id); +int FindClosestAvailableAudioBitrate(const char *id, int bitrate); diff --git a/UI/window-basic-main-outputs.cpp b/UI/window-basic-main-outputs.cpp index 534b0d69f..9f8ae3297 100644 --- a/UI/window-basic-main-outputs.cpp +++ b/UI/window-basic-main-outputs.cpp @@ -189,7 +189,7 @@ static void OBSStopVirtualCam(void *data, calldata_t *params) static bool CreateAACEncoder(OBSEncoder &res, string &id, int bitrate, const char *name, size_t idx) { - const char *id_ = GetAACEncoderForBitrate(bitrate); + const char *id_ = GetSimpleAACEncoderForBitrate(bitrate); if (!id_) { id.clear(); res = nullptr; @@ -665,7 +665,7 @@ int SimpleOutput::GetAudioBitrate() const int bitrate = (int)config_get_uint(main->Config(), "SimpleOutput", "ABitrate"); - return FindClosestAvailableAACBitrate(bitrate); + return FindClosestAvailableSimpleAACBitrate(bitrate); } void SimpleOutput::Update() @@ -1904,7 +1904,7 @@ int AdvancedOutput::GetAudioBitrate(size_t i) const "Track4Bitrate", "Track5Bitrate", "Track6Bitrate", }; int bitrate = (int)config_get_uint(main->Config(), "AdvOut", names[i]); - return FindClosestAvailableAACBitrate(bitrate); + return FindClosestAvailableSimpleAACBitrate(bitrate); } inline void AdvancedOutput::SetupVodTrack(obs_service_t *service) diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index 0e372b839..a4c3a1dc7 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -262,7 +262,7 @@ static CodecDesc GetDefaultCodecDesc(const ff_format_desc *formatDesc, static void PopulateAACBitrates(initializer_list boxes) { - auto &bitrateMap = GetAACEncoderBitrateMap(); + auto &bitrateMap = GetSimpleAACEncoderBitrateMap(); if (bitrateMap.empty()) return; @@ -1856,7 +1856,7 @@ void OBSBasicSettings::LoadSimpleOutputSettings() curAMDPreset = amdPreset; curAMDAV1Preset = amdAV1Preset; - audioBitrate = FindClosestAvailableAACBitrate(audioBitrate); + audioBitrate = FindClosestAvailableSimpleAACBitrate(audioBitrate); ui->simpleOutputPath->setText(path); ui->simpleNoSpace->setChecked(noSpace); @@ -2237,12 +2237,12 @@ void OBSBasicSettings::LoadAdvOutputAudioSettings() const char *name6 = config_get_string(main->Config(), "AdvOut", "Track6Name"); - track1Bitrate = FindClosestAvailableAACBitrate(track1Bitrate); - track2Bitrate = FindClosestAvailableAACBitrate(track2Bitrate); - track3Bitrate = FindClosestAvailableAACBitrate(track3Bitrate); - track4Bitrate = FindClosestAvailableAACBitrate(track4Bitrate); - track5Bitrate = FindClosestAvailableAACBitrate(track5Bitrate); - track6Bitrate = FindClosestAvailableAACBitrate(track6Bitrate); + track1Bitrate = FindClosestAvailableSimpleAACBitrate(track1Bitrate); + track2Bitrate = FindClosestAvailableSimpleAACBitrate(track2Bitrate); + track3Bitrate = FindClosestAvailableSimpleAACBitrate(track3Bitrate); + track4Bitrate = FindClosestAvailableSimpleAACBitrate(track4Bitrate); + track5Bitrate = FindClosestAvailableSimpleAACBitrate(track5Bitrate); + track6Bitrate = FindClosestAvailableSimpleAACBitrate(track6Bitrate); // restrict list of bitrates when multichannel is OFF const char *speakers = @@ -4465,7 +4465,8 @@ void RestrictResetBitrates(initializer_list boxes, int maxbitrate) { for (auto box : boxes) { int idx = box->currentIndex(); - int max_bitrate = FindClosestAvailableAACBitrate(maxbitrate); + int max_bitrate = + FindClosestAvailableSimpleAACBitrate(maxbitrate); int count = box->count(); int max_idx = box->findText( QT_UTF8(std::to_string(max_bitrate).c_str())); @@ -4475,7 +4476,8 @@ void RestrictResetBitrates(initializer_list boxes, int maxbitrate) if (idx > max_idx) { int default_bitrate = - FindClosestAvailableAACBitrate(maxbitrate / 2); + FindClosestAvailableSimpleAACBitrate( + maxbitrate / 2); int default_idx = box->findText(QT_UTF8( std::to_string(default_bitrate).c_str()));