blob: f6fe2b98e1835a43b3820de778f5fcee42637a14 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/media/audio/audio_core/process_config_loader.h"
#include <lib/syslog/cpp/macros.h>
#include <sstream>
#include <string_view>
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
#include <rapidjson/schema.h>
#include "rapidjson/prettywriter.h"
#include "src/lib/files/file.h"
#include "src/media/audio/audio_core/schema/audio_core_config_schema.inl"
namespace media::audio {
namespace {
static constexpr char kJsonKeyVolumeCurve[] = "volume_curve";
static constexpr char kJsonKeyDefaultRenderUsageVolumes[] = "default_render_usage_volumes";
static constexpr char kJsonKeyPipeline[] = "pipeline";
static constexpr char kJsonKeyLib[] = "lib";
static constexpr char kJsonKeyName[] = "name";
static constexpr char kJsonKeyRate[] = "rate";
static constexpr char kJsonKeyEffect[] = "effect";
static constexpr char kJsonKeyConfig[] = "config";
static constexpr char kJsonKeyStreams[] = "streams";
static constexpr char kJsonKeyInputs[] = "inputs";
static constexpr char kJsonKeyEffects[] = "effects";
static constexpr char kJsonKeyLoopback[] = "loopback";
static constexpr char kJsonKeyDeviceId[] = "device_id";
static constexpr char kJsonKeyOutputRate[] = "output_rate";
static constexpr char kJsonKeyOutputChannels[] = "output_channels";
static constexpr char kJsonKeyInputDevices[] = "input_devices";
static constexpr char kJsonKeyOutputDevices[] = "output_devices";
static constexpr char kJsonKeySupportedStreamTypes[] = "supported_stream_types";
static constexpr char kJsonKeySupportedOutputStreamTypes[] = "supported_output_stream_types";
static constexpr char kJsonKeyEligibleForLoopback[] = "eligible_for_loopback";
static constexpr char kJsonKeyIndependentVolumeControl[] = "independent_volume_control";
static constexpr char kJsonKeyDriverGainDb[] = "driver_gain_db";
static constexpr char kJsonKeyThermalPolicy[] = "thermal_policy";
static constexpr char kJsonKeyTargetName[] = "target_name";
static constexpr char kJsonKeyTripPoint[] = "trip_point";
static constexpr char kJsonKeyTripPointDeactivateBelow[] = "deactivate_below";
static constexpr char kJsonKeyTripPointActivateAt[] = "activate_at";
static constexpr char kJsonKeyStateTransitions[] = "state_transitions";
void CountLoopbackStages(const PipelineConfig::MixGroup& mix_group, uint32_t* count) {
if (mix_group.loopback) {
++*count;
}
for (const auto& input : mix_group.inputs) {
CountLoopbackStages(input, count);
}
}
uint32_t CountLoopbackStages(const PipelineConfig::MixGroup& root) {
uint32_t count = 0;
CountLoopbackStages(root, &count);
return count;
}
fit::result<rapidjson::SchemaDocument, std::string> LoadProcessConfigSchema() {
rapidjson::Document schema_doc;
const rapidjson::ParseResult result = schema_doc.Parse(kAudioCoreConfigSchema);
if (result.IsError()) {
std::ostringstream oss;
oss << "Failed to load config schema: " << rapidjson::GetParseError_En(result.Code()) << "("
<< result.Offset() << ")";
return fit::error(oss.str());
}
return fit::ok(rapidjson::SchemaDocument(schema_doc));
}
fit::result<VolumeCurve, VolumeCurve::Error> ParseVolumeCurveFromJsonObject(
const rapidjson::Value& value) {
FX_CHECK(value.IsArray());
std::vector<VolumeCurve::VolumeMapping> mappings;
for (const auto& mapping : value.GetArray()) {
mappings.emplace_back(mapping["level"].GetFloat(), mapping["db"].GetFloat());
}
return VolumeCurve::FromMappings(std::move(mappings));
}
std::optional<RenderUsage> RenderUsageFromString(std::string_view string) {
if (string == "media" || string == "render:media") {
return RenderUsage::MEDIA;
} else if (string == "background" || string == "render:background") {
return RenderUsage::BACKGROUND;
} else if (string == "communications" || string == "render:communications") {
return RenderUsage::COMMUNICATION;
} else if (string == "interruption" || string == "render:interruption") {
return RenderUsage::INTERRUPTION;
} else if (string == "system_agent" || string == "render:system_agent") {
return RenderUsage::SYSTEM_AGENT;
} else if (string == "ultrasound" || string == "render:ultrasound") {
return RenderUsage::ULTRASOUND;
}
return std::nullopt;
}
std::optional<CaptureUsage> CaptureUsageFromString(std::string_view string) {
if (string == "background" || string == "capture:background") {
return CaptureUsage::BACKGROUND;
} else if (string == "foreground" || string == "capture:foreground") {
return CaptureUsage::FOREGROUND;
} else if (string == "system_agent" || string == "capture:system_agent") {
return CaptureUsage::SYSTEM_AGENT;
} else if (string == "communications" || string == "capture:communications") {
return CaptureUsage::COMMUNICATION;
} else if (string == "ultrasound" || string == "capture:ultrasound") {
return CaptureUsage::ULTRASOUND;
} else if (string == "loopback" || string == "capture:loopback") {
return CaptureUsage::LOOPBACK;
}
return std::nullopt;
}
std::optional<StreamUsage> StreamUsageFromString(std::string_view string) {
auto render_usage = RenderUsageFromString(string);
if (render_usage) {
return StreamUsage::WithRenderUsage(*render_usage);
}
auto capture_usage = CaptureUsageFromString(string);
if (capture_usage) {
return StreamUsage::WithCaptureUsage(*capture_usage);
}
return std::nullopt;
}
RenderUsageVolumes ParseDefaultRenderUsageVolumesFromJsonObject(const rapidjson::Value& value) {
FX_CHECK(value.IsObject());
RenderUsageVolumes default_volumes;
for (const auto& vol : value.GetObject()) {
FX_CHECK(vol.name.IsString());
auto usage = RenderUsageFromString(vol.name.GetString());
FX_DCHECK(usage);
FX_CHECK(vol.value.IsNumber());
auto level = vol.value.GetFloat();
default_volumes[*usage] = level;
}
return default_volumes;
}
PipelineConfig::Effect ParseEffectFromJsonObject(const rapidjson::Value& value) {
FX_CHECK(value.IsObject());
PipelineConfig::Effect effect;
auto it = value.FindMember(kJsonKeyLib);
FX_CHECK(it != value.MemberEnd() && it->value.IsString());
effect.lib_name = it->value.GetString();
it = value.FindMember(kJsonKeyEffect);
if (it != value.MemberEnd()) {
FX_CHECK(it->value.IsString());
effect.effect_name = it->value.GetString();
}
it = value.FindMember(kJsonKeyName);
if (it != value.MemberEnd()) {
FX_CHECK(it->value.IsString());
effect.instance_name = it->value.GetString();
}
it = value.FindMember(kJsonKeyConfig);
if (it != value.MemberEnd()) {
rapidjson::StringBuffer config_buf;
rapidjson::Writer<rapidjson::StringBuffer> writer(config_buf);
it->value.Accept(writer);
effect.effect_config = config_buf.GetString();
}
it = value.FindMember(kJsonKeyOutputChannels);
if (it != value.MemberEnd()) {
FX_DCHECK(it->value.IsUint());
effect.output_channels = it->value.GetUint();
}
return effect;
}
PipelineConfig::MixGroup ParseMixGroupFromJsonObject(const rapidjson::Value& value) {
FX_CHECK(value.IsObject());
PipelineConfig::MixGroup mix_group;
auto it = value.FindMember(kJsonKeyName);
if (it != value.MemberEnd()) {
FX_CHECK(it->value.IsString());
mix_group.name = it->value.GetString();
}
it = value.FindMember(kJsonKeyStreams);
if (it != value.MemberEnd()) {
FX_CHECK(it->value.IsArray());
for (const auto& stream_type : it->value.GetArray()) {
FX_CHECK(stream_type.IsString());
auto render_usage = RenderUsageFromString(stream_type.GetString());
FX_DCHECK(render_usage);
mix_group.input_streams.push_back(*render_usage);
}
}
it = value.FindMember(kJsonKeyEffects);
if (it != value.MemberEnd()) {
FX_CHECK(it->value.IsArray());
for (const auto& effect : it->value.GetArray()) {
mix_group.effects.push_back(ParseEffectFromJsonObject(effect));
}
}
it = value.FindMember(kJsonKeyInputs);
if (it != value.MemberEnd()) {
FX_CHECK(it->value.IsArray());
for (const auto& input : it->value.GetArray()) {
mix_group.inputs.push_back(ParseMixGroupFromJsonObject(input));
}
}
it = value.FindMember(kJsonKeyLoopback);
if (it != value.MemberEnd()) {
FX_CHECK(it->value.IsBool());
mix_group.loopback = it->value.GetBool();
} else {
mix_group.loopback = false;
}
it = value.FindMember(kJsonKeyOutputRate);
if (it != value.MemberEnd()) {
FX_DCHECK(it->value.IsUint());
mix_group.output_rate = it->value.GetUint();
} else {
mix_group.output_rate = PipelineConfig::kDefaultMixGroupRate;
}
it = value.FindMember(kJsonKeyOutputChannels);
if (it != value.MemberEnd()) {
FX_DCHECK(it->value.IsUint());
mix_group.output_channels = it->value.GetUint();
} else {
mix_group.output_channels = PipelineConfig::kDefaultMixGroupChannels;
}
return mix_group;
}
std::optional<audio_stream_unique_id_t> ParseDeviceIdFromJsonString(const rapidjson::Value& value) {
FX_DCHECK(value.IsString());
const auto* device_id_string = value.GetString();
std::optional<audio_stream_unique_id_t> device_id;
if (strcmp(device_id_string, "*") != 0) {
device_id = {{}};
const auto captures = std::sscanf(
device_id_string,
"%02" SCNx8 "%02" SCNx8 "%02" SCNx8 "%02" SCNx8 "%02" SCNx8 "%02" SCNx8 "%02" SCNx8
"%02" SCNx8 "%02" SCNx8 "%02" SCNx8 "%02" SCNx8 "%02" SCNx8 "%02" SCNx8 "%02" SCNx8
"%02" SCNx8 "%02" SCNx8,
&device_id->data[0], &device_id->data[1], &device_id->data[2], &device_id->data[3],
&device_id->data[4], &device_id->data[5], &device_id->data[6], &device_id->data[7],
&device_id->data[8], &device_id->data[9], &device_id->data[10], &device_id->data[11],
&device_id->data[12], &device_id->data[13], &device_id->data[14], &device_id->data[15]);
FX_DCHECK(captures == 16);
}
return device_id;
}
// Returns Some(vector) if there is a list of concrete device id's. Returns nullopt for the default
// configuration.
std::optional<std::vector<audio_stream_unique_id_t>> ParseDeviceIdFromJsonValue(
const rapidjson::Value& value) {
std::vector<audio_stream_unique_id_t> result;
if (value.IsString()) {
auto device_id = ParseDeviceIdFromJsonString(value);
if (device_id) {
result.push_back(*device_id);
} else {
return std::nullopt;
}
} else if (value.IsArray()) {
for (const auto& device_id_value : value.GetArray()) {
auto device_id = ParseDeviceIdFromJsonString(device_id_value);
if (device_id) {
result.push_back(*device_id);
} else {
return std::nullopt;
}
}
}
return {result};
}
StreamUsageSet ParseStreamUsageSetFromJsonArray(const rapidjson::Value& value,
StreamUsageSet* all_supported_usages = nullptr) {
FX_DCHECK(value.IsArray());
StreamUsageSet supported_stream_types;
for (const auto& stream_type : value.GetArray()) {
FX_DCHECK(stream_type.IsString());
const auto supported_usage = StreamUsageFromString(stream_type.GetString());
FX_DCHECK(supported_usage);
if (all_supported_usages) {
all_supported_usages->insert(*supported_usage);
}
supported_stream_types.insert(*supported_usage);
}
return supported_stream_types;
}
fit::result<std::pair<std::optional<std::vector<audio_stream_unique_id_t>>,
DeviceConfig::OutputDeviceProfile>,
std::string>
ParseOutputDeviceProfileFromJsonObject(const rapidjson::Value& value,
StreamUsageSet* all_supported_usages,
VolumeCurve volume_curve) {
FX_DCHECK(value.IsObject());
auto device_id_it = value.FindMember(kJsonKeyDeviceId);
FX_DCHECK(device_id_it != value.MemberEnd());
auto device_id = ParseDeviceIdFromJsonValue(device_id_it->value);
bool eligible_for_loopback = false;
auto eligible_for_loopback_it = value.FindMember(kJsonKeyEligibleForLoopback);
if (eligible_for_loopback_it != value.MemberEnd()) {
FX_DCHECK(eligible_for_loopback_it->value.IsBool());
eligible_for_loopback = eligible_for_loopback_it->value.GetBool();
}
auto independent_volume_control_it = value.FindMember(kJsonKeyIndependentVolumeControl);
bool independent_volume_control = false;
if (independent_volume_control_it != value.MemberEnd()) {
FX_DCHECK(independent_volume_control_it->value.IsBool());
independent_volume_control = independent_volume_control_it->value.GetBool();
}
float driver_gain_db = 0.0;
auto driver_gain_db_it = value.FindMember(kJsonKeyDriverGainDb);
if (driver_gain_db_it != value.MemberEnd()) {
FX_DCHECK(driver_gain_db_it->value.IsNumber());
driver_gain_db = driver_gain_db_it->value.GetDouble();
}
StreamUsageSet supported_stream_types;
auto supported_stream_types_it = value.FindMember(kJsonKeySupportedOutputStreamTypes);
if (supported_stream_types_it != value.MemberEnd()) {
supported_stream_types =
ParseStreamUsageSetFromJsonArray(supported_stream_types_it->value, all_supported_usages);
} else {
supported_stream_types_it = value.FindMember(kJsonKeySupportedStreamTypes);
if (supported_stream_types_it != value.MemberEnd()) {
supported_stream_types =
ParseStreamUsageSetFromJsonArray(supported_stream_types_it->value, all_supported_usages);
} else {
FX_DCHECK(false) << "Missing required stream usage set";
}
}
auto& supported_stream_types_value = supported_stream_types_it->value;
FX_DCHECK(supported_stream_types_value.IsArray());
bool supports_loopback =
supported_stream_types.find(StreamUsage::WithCaptureUsage(CaptureUsage::LOOPBACK)) !=
supported_stream_types.end();
auto pipeline_it = value.FindMember(kJsonKeyPipeline);
PipelineConfig pipeline_config;
if (pipeline_it != value.MemberEnd()) {
FX_CHECK(pipeline_it->value.IsObject());
auto root = ParseMixGroupFromJsonObject(pipeline_it->value);
auto loopback_stages = CountLoopbackStages(root);
if (supports_loopback) {
if (loopback_stages > 1) {
return fit::error("More than 1 loopback stage specified");
}
if (loopback_stages == 0) {
return fit::error("Device supports loopback but no loopback point specified");
}
}
pipeline_config = PipelineConfig(std::move(root));
} else {
// If no pipeline is specified, we'll use a single mix stage.
pipeline_config.mutable_root().name = "default";
pipeline_config.mutable_root().loopback = supports_loopback;
for (const auto& stream_usage : supported_stream_types) {
if (stream_usage.is_render_usage()) {
pipeline_config.mutable_root().input_streams.emplace_back(stream_usage.render_usage());
}
}
}
return fit::ok(std::make_pair(
device_id, DeviceConfig::OutputDeviceProfile(
eligible_for_loopback, std::move(supported_stream_types), volume_curve,
independent_volume_control, std::move(pipeline_config), driver_gain_db)));
}
ThermalConfig::Entry ParseThermalPolicyEntryFromJsonObject(const rapidjson::Value& value) {
FX_DCHECK(value.IsObject());
auto trip_point_it = value.FindMember(kJsonKeyTripPoint);
FX_DCHECK(trip_point_it != value.MemberEnd());
FX_DCHECK(trip_point_it->value.IsObject());
auto deactivate_below_it = trip_point_it->value.FindMember(kJsonKeyTripPointDeactivateBelow);
FX_DCHECK(deactivate_below_it != trip_point_it->value.MemberEnd());
FX_DCHECK(deactivate_below_it->value.IsUint());
uint32_t deactivate_below = deactivate_below_it->value.GetUint();
auto activate_at_it = trip_point_it->value.FindMember(kJsonKeyTripPointActivateAt);
FX_DCHECK(activate_at_it != trip_point_it->value.MemberEnd());
FX_DCHECK(activate_at_it->value.IsUint());
uint32_t activate_at = activate_at_it->value.GetUint();
FX_DCHECK(deactivate_below >= 1);
FX_DCHECK(activate_at <= 100);
auto transitions_it = value.FindMember(kJsonKeyStateTransitions);
FX_DCHECK(transitions_it != value.MemberEnd());
FX_DCHECK(transitions_it->value.IsArray());
std::vector<ThermalConfig::StateTransition> transitions;
for (const auto& transition : transitions_it->value.GetArray()) {
FX_DCHECK(transition.IsObject());
auto target_name_it = transition.FindMember(kJsonKeyTargetName);
FX_DCHECK(target_name_it != value.MemberEnd());
FX_DCHECK(target_name_it->value.IsString());
const auto* target_name = target_name_it->value.GetString();
auto config_it = transition.FindMember(kJsonKeyConfig);
FX_DCHECK(config_it != transition.MemberEnd());
rapidjson::StringBuffer config_buf;
rapidjson::Writer<rapidjson::StringBuffer> writer(config_buf);
config_it->value.Accept(writer);
transitions.emplace_back(target_name, config_buf.GetString());
}
return ThermalConfig::Entry(
ThermalConfig::TripPoint{.deactivate_below = deactivate_below, .activate_at = activate_at},
transitions);
}
fit::result<void, std::string> ParseOutputDevicePoliciesFromJsonObject(
const rapidjson::Value& output_device_profiles, ProcessConfigBuilder* config_builder) {
FX_DCHECK(output_device_profiles.IsArray());
StreamUsageSet all_supported_usages;
for (const auto& output_device_profile : output_device_profiles.GetArray()) {
auto result = ParseOutputDeviceProfileFromJsonObject(
output_device_profile, &all_supported_usages, config_builder->default_volume_curve());
if (result.is_error()) {
return result.take_error_result();
}
config_builder->AddDeviceProfile(result.take_value());
}
// We expect all the usages that clients can select are supported.
for (const auto& render_usage : kFidlRenderUsages) {
auto stream_usage = StreamUsage::WithRenderUsage(render_usage);
if (all_supported_usages.find(stream_usage) == all_supported_usages.end()) {
std::ostringstream oss;
oss << "No output to support usage " << stream_usage.ToString();
return fit::error(oss.str());
}
}
return fit::ok();
}
fit::result<std::pair<std::optional<std::vector<audio_stream_unique_id_t>>,
DeviceConfig::InputDeviceProfile>>
ParseInputDeviceProfileFromJsonObject(const rapidjson::Value& value) {
FX_DCHECK(value.IsObject());
auto device_id_it = value.FindMember(kJsonKeyDeviceId);
FX_DCHECK(device_id_it != value.MemberEnd());
auto device_id = ParseDeviceIdFromJsonValue(device_id_it->value);
auto rate_it = value.FindMember(kJsonKeyRate);
FX_DCHECK(rate_it != value.MemberEnd());
if (!rate_it->value.IsUint()) {
return fit::error();
}
auto rate = rate_it->value.GetUint();
float driver_gain_db = 0.0;
auto driver_gain_db_it = value.FindMember(kJsonKeyDriverGainDb);
if (driver_gain_db_it != value.MemberEnd()) {
FX_DCHECK(driver_gain_db_it->value.IsNumber());
driver_gain_db = driver_gain_db_it->value.GetDouble();
}
StreamUsageSet supported_stream_types;
auto supported_stream_types_it = value.FindMember(kJsonKeySupportedStreamTypes);
if (supported_stream_types_it != value.MemberEnd()) {
supported_stream_types = ParseStreamUsageSetFromJsonArray(supported_stream_types_it->value);
return fit::ok(std::make_pair(
device_id,
DeviceConfig::InputDeviceProfile(rate, std::move(supported_stream_types), driver_gain_db)));
}
return fit::ok(std::make_pair(device_id, DeviceConfig::InputDeviceProfile(rate, driver_gain_db)));
}
fit::result<> ParseInputDevicePoliciesFromJsonObject(const rapidjson::Value& input_device_profiles,
ProcessConfigBuilder* config_builder) {
FX_DCHECK(input_device_profiles.IsArray());
for (const auto& input_device_profile : input_device_profiles.GetArray()) {
auto result = ParseInputDeviceProfileFromJsonObject(input_device_profile);
if (result.is_error()) {
return fit::error();
}
config_builder->AddDeviceProfile(result.take_value());
}
return fit::ok();
}
void ParseThermalPolicyFromJsonObject(const rapidjson::Value& value,
ProcessConfigBuilder* config_builder) {
FX_DCHECK(value.IsArray());
for (const auto& thermal_policy_entry : value.GetArray()) {
config_builder->AddThermalPolicyEntry(
ParseThermalPolicyEntryFromJsonObject(thermal_policy_entry));
}
}
} // namespace
fit::result<ProcessConfig, std::string> ProcessConfigLoader::LoadProcessConfig(
const char* filename) {
std::string buffer;
const auto file_exists = files::ReadFileToString(filename, &buffer);
if (!file_exists) {
return fit::error("File does not exist");
}
auto result = ParseProcessConfig(buffer);
if (result.is_error()) {
std::ostringstream oss;
oss << "Parse error: " << result.error();
return fit::error(oss.str());
}
return fit::ok(result.take_value());
}
fit::result<ProcessConfig, std::string> ProcessConfigLoader::ParseProcessConfig(
const std::string& config) {
rapidjson::Document doc;
std::string parse_buffer = config;
const rapidjson::ParseResult parse_res = doc.ParseInsitu(parse_buffer.data());
if (parse_res.IsError()) {
std::stringstream error;
error << "Parse error (" << rapidjson::GetParseError_En(parse_res.Code())
<< "): " << parse_res.Offset();
return fit::error(error.str());
}
auto schema_result = LoadProcessConfigSchema();
if (schema_result.is_error()) {
return fit::error(schema_result.take_error());
}
rapidjson::SchemaValidator validator(schema_result.value());
if (!doc.Accept(validator)) {
rapidjson::StringBuffer error_buf;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(error_buf);
validator.GetError().Accept(writer);
std::stringstream error;
error << "Schema validation error (" << error_buf.GetString() << ")";
return fit::error(error.str());
}
auto curve_result = ParseVolumeCurveFromJsonObject(doc[kJsonKeyVolumeCurve]);
if (!curve_result.is_ok()) {
std::stringstream error;
error << "Invalid volume curve; error: " << curve_result.take_error();
return fit::error(error.str());
}
auto config_builder = ProcessConfig::Builder();
config_builder.SetDefaultVolumeCurve(curve_result.take_value());
auto default_volume_it = doc.FindMember(kJsonKeyDefaultRenderUsageVolumes);
if (default_volume_it != doc.MemberEnd()) {
auto default_volume_result =
ParseDefaultRenderUsageVolumesFromJsonObject(doc[kJsonKeyDefaultRenderUsageVolumes]);
config_builder.SetDefaultRenderUsageVolumes(default_volume_result);
}
auto output_devices_it = doc.FindMember(kJsonKeyOutputDevices);
if (output_devices_it != doc.MemberEnd()) {
auto result =
ParseOutputDevicePoliciesFromJsonObject(output_devices_it->value, &config_builder);
if (result.is_error()) {
std::ostringstream oss;
oss << "Failed to parse output device policies: " << result.error();
return fit::error(oss.str());
}
}
auto input_devices_it = doc.FindMember(kJsonKeyInputDevices);
if (input_devices_it != doc.MemberEnd()) {
auto result = ParseInputDevicePoliciesFromJsonObject(input_devices_it->value, &config_builder);
if (result.is_error()) {
return fit::error("Failed to parse input device policies");
}
}
auto thermal_policy_it = doc.FindMember(kJsonKeyThermalPolicy);
if (thermal_policy_it != doc.MemberEnd()) {
ParseThermalPolicyFromJsonObject(thermal_policy_it->value, &config_builder);
}
return fit::ok(config_builder.Build());
}
} // namespace media::audio