obs-studio/UI/audio-encoders.cpp

419 lines
9.6 KiB
C++

#include <algorithm>
#include <iomanip>
#include <map>
#include <memory>
#include <mutex>
#include <sstream>
#include <string>
#include <vector>
#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<int> &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<int> &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<int>(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<int>(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<obs_data_t, decltype(ReleaseData)> 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<OBSMainWindow *>(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<int> &bitrates)
{
auto DestroyProperties = [](obs_properties_t *props) {
obs_properties_destroy(props);
};
std::unique_ptr<obs_properties_t, decltype(DestroyProperties)> 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<int>(type));
}
static const char *GetCodec(const char *id)
{
return NullToEmpty(obs_get_encoder_codec(id));
}
static std::vector<int> fallbackBitrates;
static map<std::string, std::vector<int>> 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<int, std::string> 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<int, std::string> 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<int, std::string> &GetSimpleAACEncoderBitrateMap()
{
PopulateSimpleAACBitrateMap();
return simpleAACBitrateMap;
}
const map<int, std::string> &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<int, std::string> &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<int> &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;
}