#include #include #include #include #include #include #include #include #include "audio-encoders.hpp" #include "obs-app.hpp" #include "window-main.hpp" using namespace std; static const char *NullToEmpty(const char *str) { return str ? str : ""; } static const char *EncoderName(const std::string &id) { return NullToEmpty(obs_encoder_get_display_name(id.c_str())); } 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) bitrates.push_back(i); } 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) { blog(LOG_ERROR, "Encoder '%s' (%s) returned bitrate " "OBS_PROPERTY_LIST property of unhandled " "format %d", EncoderName(id), id, static_cast(format)); return; } const size_t count = obs_property_list_item_count(prop); for (size_t i = 0; i < count; i++) { if (obs_property_list_item_disabled(prop, i)) continue; int bitrate = static_cast(obs_property_list_item_int(prop, i)); bitrates.push_back(bitrate); } } static void HandleSampleRate(obs_property_t *prop, const char *id) { auto ReleaseData = [](obs_data_t *data) { obs_data_release(data); }; std::unique_ptr data{ obs_encoder_defaults(id), ReleaseData}; if (!data) { blog(LOG_ERROR, "Failed to get defaults for encoder '%s' (%s) " "while populating bitrate map", EncoderName(id), id); return; } auto main = reinterpret_cast(App()->GetMainWindow()); if (!main) { blog(LOG_ERROR, "Failed to get main window while populating " "bitrate map"); return; } uint32_t sampleRate = config_get_uint(main->Config(), "Audio", "SampleRate"); obs_data_set_int(data.get(), "samplerate", sampleRate); obs_property_modified(prop, data.get()); } static void HandleEncoderProperties(const char *id, std::vector &bitrates) { auto DestroyProperties = [](obs_properties_t *props) { obs_properties_destroy(props); }; std::unique_ptr props{ obs_get_encoder_properties(id), DestroyProperties}; if (!props) { blog(LOG_ERROR, "Failed to get properties for encoder " "'%s' (%s)", EncoderName(id), id); return; } obs_property_t *samplerate = obs_properties_get(props.get(), "samplerate"); if (samplerate) HandleSampleRate(samplerate, id); obs_property_t *bitrate = obs_properties_get(props.get(), "bitrate"); obs_property_type type = obs_property_get_type(bitrate); switch (type) { case OBS_PROPERTY_INT: return HandleIntProperty(bitrate, bitrates); case OBS_PROPERTY_LIST: return HandleListProperty(bitrate, id, bitrates); default: break; } blog(LOG_ERROR, "Encoder '%s' (%s) returned bitrate property " "of unhandled type %d", EncoderName(id), id, static_cast(type)); } static const char *GetCodec(const char *id) { return NullToEmpty(obs_get_encoder_codec(id)); } 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); /* 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", "libfdk_aac", "CoreAudio_AAC", }; const string fallbackEncoder = encoders[0]; struct obs_audio_info aoi; obs_get_audio_info(&aoi); for (auto &bitrate : fallbackBitrates) simpleAACBitrateMap[bitrate] = fallbackEncoder; const char *id = nullptr; for (size_t i = 0; obs_enum_encoder_types(i, &id); i++) { auto Compare = [=](const string &val) { return val == NullToEmpty(id); }; if (find_if(begin(encoders), end(encoders), Compare) != end(encoders)) continue; if (strcmp(GetCodec(id), "aac") != 0) continue; 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) continue; for (auto &bitrate : encoderBitrates[encoder]) simpleAACBitrateMap[bitrate] = encoder; } if (simpleAACBitrateMap.empty()) { blog(LOG_ERROR, "Could not enumerate any AAC encoder " "bitrates"); return; } ostringstream ss; for (auto &entry : simpleAACBitrateMap) ss << "\n " << setw(3) << entry.first << " kbit/s: '" << EncoderName(entry.second) << "' (" << entry.second << ')'; blog(LOG_DEBUG, "AAC simple encoder bitrate mapping:%s", ss.str().c_str()); }); } static map simpleOpusBitrateMap; static void PopulateSimpleOpusBitrateMap() { PopulateBitrateLists(); static once_flag once; call_once(once, []() { struct obs_audio_info aoi; obs_get_audio_info(&aoi); 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 map &GetSimpleAACEncoderBitrateMap() { 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; return res->second.c_str(); } #define INVALID_BITRATE 10000 static int FindClosestAvailableSimpleBitrate(int bitrate, const map &map) { int prev = 0; int next = INVALID_BITRATE; for (auto val : map) { if (next > val.first) { if (val.first == bitrate) return bitrate; if (val.first < next && val.first > bitrate) next = val.first; if (val.first > prev && val.first < bitrate) prev = val.first; } } if (next != INVALID_BITRATE) return next; if (prev != 0) 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) { PopulateBitrateLists(); 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; }