yolobs-studio/UI/audio-encoders.cpp

243 lines
5.4 KiB
C++
Raw Normal View History

2016-02-23 23:16:51 +00:00
#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 string encoders[] = {
"ffmpeg_aac",
"mf_aac",
"libfdk_aac",
"CoreAudio_AAC",
};
static const string &fallbackEncoder = encoders[0];
static const char *NullToEmpty(const char *str)
{
return str ? str : "";
}
static const char *EncoderName(const char *id)
{
return NullToEmpty(obs_encoder_get_display_name(id));
}
2019-09-22 21:19:10 +00:00
static map<int, const char *> bitrateMap;
2016-02-23 23:16:51 +00:00
static once_flag populateBitrateMap;
static void HandleIntProperty(obs_property_t *prop, const char *id)
{
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;
}
static void HandleListProperty(obs_property_t *prop, const char *id)
{
obs_combo_format format = obs_property_list_format(prop);
if (format != OBS_COMBO_FORMAT_INT) {
2019-09-22 21:19:10 +00:00
blog(LOG_ERROR,
"Encoder '%s' (%s) returned bitrate "
"OBS_PROPERTY_LIST property of unhandled "
"format %d",
EncoderName(id), id, static_cast<int>(format));
2016-02-23 23:16:51 +00:00
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;
2019-09-22 21:19:10 +00:00
int bitrate =
static_cast<int>(obs_property_list_item_int(prop, i));
2016-02-23 23:16:51 +00:00
bitrateMap[bitrate] = id;
}
}
2019-09-22 21:19:10 +00:00
static void HandleSampleRate(obs_property_t *prop, const char *id)
2016-02-23 23:16:51 +00:00
{
2019-09-22 21:19:10 +00:00
auto ReleaseData = [](obs_data_t *data) { obs_data_release(data); };
2016-02-23 23:16:51 +00:00
std::unique_ptr<obs_data_t, decltype(ReleaseData)> data{
2019-09-22 21:19:10 +00:00
obs_encoder_defaults(id), ReleaseData};
2016-02-23 23:16:51 +00:00
if (!data) {
2019-09-22 21:19:10 +00:00
blog(LOG_ERROR,
"Failed to get defaults for encoder '%s' (%s) "
"while populating bitrate map",
EncoderName(id), id);
2016-02-23 23:16:51 +00:00
return;
}
2019-09-22 21:19:10 +00:00
auto main = reinterpret_cast<OBSMainWindow *>(App()->GetMainWindow());
2016-02-23 23:16:51 +00:00
if (!main) {
blog(LOG_ERROR, "Failed to get main window while populating "
"bitrate map");
return;
}
2019-09-22 21:19:10 +00:00
uint32_t sampleRate =
config_get_uint(main->Config(), "Audio", "SampleRate");
2016-02-23 23:16:51 +00:00
obs_data_set_int(data.get(), "samplerate", sampleRate);
obs_property_modified(prop, data.get());
}
static void HandleEncoderProperties(const char *id)
{
2019-09-22 21:19:10 +00:00
auto DestroyProperties = [](obs_properties_t *props) {
2016-02-23 23:16:51 +00:00
obs_properties_destroy(props);
};
std::unique_ptr<obs_properties_t, decltype(DestroyProperties)> props{
2019-09-22 21:19:10 +00:00
obs_get_encoder_properties(id), DestroyProperties};
2016-02-23 23:16:51 +00:00
if (!props) {
2019-09-22 21:19:10 +00:00
blog(LOG_ERROR,
"Failed to get properties for encoder "
"'%s' (%s)",
EncoderName(id), id);
2016-02-23 23:16:51 +00:00
return;
}
2019-09-22 21:19:10 +00:00
obs_property_t *samplerate =
obs_properties_get(props.get(), "samplerate");
2016-02-23 23:16:51 +00:00
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, id);
case OBS_PROPERTY_LIST:
return HandleListProperty(bitrate, id);
2019-09-22 21:19:10 +00:00
default:
break;
2016-02-23 23:16:51 +00:00
}
2019-09-22 21:19:10 +00:00
blog(LOG_ERROR,
"Encoder '%s' (%s) returned bitrate property "
"of unhandled type %d",
EncoderName(id), id, static_cast<int>(type));
2016-02-23 23:16:51 +00:00
}
static const char *GetCodec(const char *id)
{
return NullToEmpty(obs_get_encoder_codec(id));
}
static const string aac_ = "AAC";
static void PopulateBitrateMap()
{
2019-09-22 21:19:10 +00:00
call_once(populateBitrateMap, []() {
2018-02-19 19:54:37 +00:00
struct obs_audio_info aoi;
obs_get_audio_info(&aoi);
uint32_t output_channels = get_audio_channels(aoi.speakers);
2016-02-23 23:16:51 +00:00
HandleEncoderProperties(fallbackEncoder.c_str());
const char *id = nullptr;
for (size_t i = 0; obs_enum_encoder_types(i, &id); i++) {
2019-09-22 21:19:10 +00:00
auto Compare = [=](const string &val) {
2016-02-23 23:16:51 +00:00
return val == NullToEmpty(id);
};
if (find_if(begin(encoders), end(encoders), Compare) !=
2019-09-22 21:19:10 +00:00
end(encoders))
2016-02-23 23:16:51 +00:00
continue;
if (aac_ != GetCodec(id))
continue;
HandleEncoderProperties(id);
}
for (auto &encoder : encoders) {
if (encoder == fallbackEncoder)
continue;
if (aac_ != GetCodec(encoder.c_str()))
continue;
2018-02-19 19:54:37 +00:00
// disable mf_aac if audio ouput is not stereo nor mono
if ((output_channels >= 3) && (encoder == "mf_aac"))
continue;
2016-02-23 23:16:51 +00:00
HandleEncoderProperties(encoder.c_str());
}
if (bitrateMap.empty()) {
blog(LOG_ERROR, "Could not enumerate any AAC encoder "
"bitrates");
return;
}
ostringstream ss;
for (auto &entry : bitrateMap)
ss << "\n " << setw(3) << entry.first
<< " kbit/s: '" << EncoderName(entry.second) << "' ("
<< entry.second << ')';
2016-08-28 12:07:43 +00:00
blog(LOG_DEBUG, "AAC encoder bitrate mapping:%s",
2019-09-22 21:19:10 +00:00
ss.str().c_str());
2016-02-23 23:16:51 +00:00
});
}
2019-09-22 21:19:10 +00:00
const map<int, const char *> &GetAACEncoderBitrateMap()
2016-02-23 23:16:51 +00:00
{
PopulateBitrateMap();
return bitrateMap;
}
const char *GetAACEncoderForBitrate(int bitrate)
{
auto &map_ = GetAACEncoderBitrateMap();
auto res = map_.find(bitrate);
if (res == end(map_))
return NULL;
return res->second;
}
#define INVALID_BITRATE 10000
int FindClosestAvailableAACBitrate(int bitrate)
{
auto &map_ = GetAACEncoderBitrateMap();
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;
}