blob: 23c7d4eb77d906b628dcfa992025ebb01fd45450 [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 <iostream>
#include <gtest/gtest.h>
#include "src/lib/files/file.h"
namespace media::audio {
namespace {
constexpr char kTestAudioCoreConfigFilename[] = "/tmp/audio_core_config.json";
TEST(ProcessConfigLoaderTest, LoadProcessConfigWithOnlyVolumeCurve) {
static const std::string kConfigWithVolumeCurve =
R"JSON({
"volume_curve": [
{
"level": 0.0,
"db": -160.0
},
{
"level": 1.0,
"db": 0.0
}
]
})JSON";
ASSERT_TRUE(files::WriteFile(kTestAudioCoreConfigFilename, kConfigWithVolumeCurve.data(),
kConfigWithVolumeCurve.size()));
auto config_result = ProcessConfigLoader::LoadProcessConfig(kTestAudioCoreConfigFilename);
ASSERT_TRUE(config_result.is_ok());
const auto config = config_result.take_value();
EXPECT_FLOAT_EQ(config.default_volume_curve().VolumeToDb(0.0), -160.0);
EXPECT_FLOAT_EQ(config.default_volume_curve().VolumeToDb(1.0), 0.0);
}
TEST(ProcessConfigLoaderTest, LoadProcessConfigWithDefaultRenderUsageVolumes) {
static const std::string kConfigWithDefaultRenderUsageVolumes = R"JSON({
"volume_curve": [
{
"level": 0.0,
"db": -160.0
},
{
"level": 1.0,
"db": 0.0
}
],
"default_render_usage_volumes": {
"render:media": 0.0,
"background": 0.5,
"render:system_agent": 0.3
}
})JSON";
ASSERT_TRUE(files::WriteFile(kTestAudioCoreConfigFilename,
kConfigWithDefaultRenderUsageVolumes.data(),
kConfigWithDefaultRenderUsageVolumes.size()));
auto result = ProcessConfigLoader::LoadProcessConfig(kTestAudioCoreConfigFilename);
ASSERT_TRUE(result.is_ok());
auto& default_volumes = result.value().default_render_usage_volumes();
EXPECT_FLOAT_EQ(default_volumes.at(RenderUsage::MEDIA), 0.0);
EXPECT_FLOAT_EQ(default_volumes.at(RenderUsage::BACKGROUND), 0.5);
EXPECT_FLOAT_EQ(default_volumes.at(RenderUsage::SYSTEM_AGENT), 0.3);
}
TEST(ProcessConfigLoaderTest, LoadProcessConfigWithRoutingPolicy) {
static const std::string kConfigWithRoutingPolicy =
R"JSON({
"volume_curve": [
{
"level": 0.0,
"db": -160.0
},
{
"level": 1.0,
"db": 0.0
}
],
"output_devices": [
{
"device_id" : "34384e7da9d52c8062a9765baeb6053a",
"supported_stream_types": [
"render:media",
"render:interruption",
"render:background",
"render:communications",
"capture:loopback"
]
},
{
"device_id": "*",
"supported_stream_types": [
"render:media",
"render:system_agent"
],
"independent_volume_control": true
}
]
})JSON";
ASSERT_TRUE(files::WriteFile(kTestAudioCoreConfigFilename, kConfigWithRoutingPolicy.data(),
kConfigWithRoutingPolicy.size()));
const audio_stream_unique_id_t expected_id = {.data = {0x34, 0x38, 0x4e, 0x7d, 0xa9, 0xd5, 0x2c,
0x80, 0x62, 0xa9, 0x76, 0x5b, 0xae, 0xb6,
0x05, 0x3a}};
const audio_stream_unique_id_t unknown_id = {.data = {0x32, 0x38, 0x4e, 0x7d, 0xa9, 0xd5, 0x2c,
0x81, 0x42, 0xa9, 0x76, 0x5b, 0xae, 0xb6,
0x22, 0x3a}};
const auto result = ProcessConfigLoader::LoadProcessConfig(kTestAudioCoreConfigFilename);
ASSERT_TRUE(result.is_ok());
auto& config = result.value().device_config();
EXPECT_TRUE(config.output_device_profile(expected_id).supports_usage(RenderUsage::MEDIA));
EXPECT_TRUE(config.output_device_profile(expected_id).supports_usage(RenderUsage::INTERRUPTION));
EXPECT_FALSE(config.output_device_profile(expected_id).supports_usage(RenderUsage::SYSTEM_AGENT));
EXPECT_FALSE(config.output_device_profile(unknown_id).supports_usage(RenderUsage::INTERRUPTION));
EXPECT_TRUE(config.output_device_profile(unknown_id).supports_usage(RenderUsage::MEDIA));
EXPECT_TRUE(config.output_device_profile(expected_id).eligible_for_loopback());
EXPECT_FALSE(config.output_device_profile(unknown_id).eligible_for_loopback());
EXPECT_FALSE(config.output_device_profile(expected_id).independent_volume_control());
EXPECT_TRUE(config.output_device_profile(unknown_id).independent_volume_control());
}
TEST(ProcessConfigLoaderTest, LoadProcessConfigWithRoutingMultipleDeviceIds) {
static const std::string kConfigWithRoutingPolicy =
R"JSON({
"volume_curve": [
{
"level": 0.0,
"db": -160.0
},
{
"level": 1.0,
"db": 0.0
}
],
"output_devices": [
{
"device_id" : ["34384e7da9d52c8062a9765baeb6053a", "34384e7da9d52c8062a9765baeb6053b" ],
"supported_stream_types": [
"render:media"
]
},
{
"device_id" : "*",
"supported_stream_types": [
"render:media",
"render:interruption",
"render:background",
"render:communications",
"render:system_agent",
"capture:loopback"
]
}
]
})JSON";
ASSERT_TRUE(files::WriteFile(kTestAudioCoreConfigFilename, kConfigWithRoutingPolicy.data(),
kConfigWithRoutingPolicy.size()));
const audio_stream_unique_id_t expected_id1 = {.data = {0x34, 0x38, 0x4e, 0x7d, 0xa9, 0xd5, 0x2c,
0x80, 0x62, 0xa9, 0x76, 0x5b, 0xae, 0xb6,
0x05, 0x3a}};
const audio_stream_unique_id_t expected_id2 = {.data = {0x34, 0x38, 0x4e, 0x7d, 0xa9, 0xd5, 0x2c,
0x80, 0x62, 0xa9, 0x76, 0x5b, 0xae, 0xb6,
0x05, 0x3b}};
auto result = ProcessConfigLoader::LoadProcessConfig(kTestAudioCoreConfigFilename);
ASSERT_TRUE(result.is_ok());
auto& config = result.value().device_config();
for (const auto& device_id : {expected_id1, expected_id2}) {
EXPECT_TRUE(config.output_device_profile(device_id).supports_usage(RenderUsage::MEDIA));
EXPECT_FALSE(config.output_device_profile(device_id).supports_usage(RenderUsage::INTERRUPTION));
EXPECT_FALSE(config.output_device_profile(device_id).supports_usage(RenderUsage::BACKGROUND));
EXPECT_FALSE(
config.output_device_profile(device_id).supports_usage(RenderUsage::COMMUNICATION));
EXPECT_FALSE(config.output_device_profile(device_id).supports_usage(RenderUsage::SYSTEM_AGENT));
EXPECT_FALSE(config.output_device_profile(device_id).eligible_for_loopback());
EXPECT_FALSE(config.output_device_profile(device_id).independent_volume_control());
}
}
TEST(ProcessConfigLoaderTest, LoadProcessConfigWithRoutingPolicyNoDefault) {
static const std::string kConfigWithRoutingPolicy =
R"JSON({
"volume_curve": [
{
"level": 0.0,
"db": -160.0
},
{
"level": 1.0,
"db": 0.0
}
],
"output_devices": [
{
"device_id" : "34384e7da9d52c8062a9765baeb6053a",
"supported_stream_types": [
"render:media",
"render:interruption",
"render:background",
"render:communications",
"render:system_agent",
"render:ultrasound",
"capture:loopback"
]
}
]
})JSON";
ASSERT_TRUE(files::WriteFile(kTestAudioCoreConfigFilename, kConfigWithRoutingPolicy.data(),
kConfigWithRoutingPolicy.size()));
const audio_stream_unique_id_t unknown_id = {.data = {0x32, 0x38, 0x4e, 0x7d, 0xa9, 0xd5, 0x2c,
0x81, 0x42, 0xa9, 0x76, 0x5b, 0xae, 0xb6,
0x22, 0x3a}};
auto result = ProcessConfigLoader::LoadProcessConfig(kTestAudioCoreConfigFilename);
ASSERT_TRUE(result.is_ok());
auto& config = result.value().device_config();
EXPECT_TRUE(config.output_device_profile(unknown_id).supports_usage(RenderUsage::MEDIA));
EXPECT_TRUE(config.output_device_profile(unknown_id).supports_usage(RenderUsage::INTERRUPTION));
EXPECT_TRUE(config.output_device_profile(unknown_id).supports_usage(RenderUsage::BACKGROUND));
EXPECT_TRUE(config.output_device_profile(unknown_id).supports_usage(RenderUsage::COMMUNICATION));
EXPECT_TRUE(config.output_device_profile(unknown_id).supports_usage(RenderUsage::SYSTEM_AGENT));
EXPECT_FALSE(config.output_device_profile(unknown_id).supports_usage(RenderUsage::ULTRASOUND));
EXPECT_TRUE(config.output_device_profile(unknown_id).eligible_for_loopback());
}
TEST(ProcessConfigLoaderTest, RejectConfigWithUnknownStreamTypes) {
static const std::string kConfigWithRoutingPolicy =
R"JSON({
"volume_curve": [
{
"level": 0.0,
"db": -160.0
},
{
"level": 1.0,
"db": 0.0
}
],
"output_devices": [
{
"device_id" : "34384e7da9d52c8062a9765baeb6053a",
"supported_stream_types": [
"render:media",
"render:interruption",
"render:background",
"render:communications",
"render:system_agent",
"render:invalid"
]
}
]
})JSON";
ASSERT_TRUE(files::WriteFile(kTestAudioCoreConfigFilename, kConfigWithRoutingPolicy.data(),
kConfigWithRoutingPolicy.size()));
auto result = ProcessConfigLoader::LoadProcessConfig(kTestAudioCoreConfigFilename);
ASSERT_TRUE(result.is_error());
ASSERT_EQ(result.error(), R"ERROR(Parse error: Schema validation error ({
"enum": {
"instanceRef": "#/output_devices/0/supported_stream_types/5",
"schemaRef": "#/definitions/stream_type"
}
}))ERROR");
}
TEST(ProcessConfigLoaderTest, LoadProcessConfigWithRoutingPolicyInsufficientCoverage) {
static const std::string kConfigWithRoutingPolicy =
R"JSON({
"volume_curve": [
{
"level": 0.0,
"db": -160.0
},
{
"level": 1.0,
"db": 0.0
}
],
"output_devices": [
{
"device_id" : "34384e7da9d52c8062a9765baeb6053a",
"supported_stream_types": [
"render:media",
"render:interruption",
"render:system_agent",
"capture:loopback"
]
}
]
})JSON";
ASSERT_TRUE(files::WriteFile(kTestAudioCoreConfigFilename, kConfigWithRoutingPolicy.data(),
kConfigWithRoutingPolicy.size()));
auto result = ProcessConfigLoader::LoadProcessConfig(kTestAudioCoreConfigFilename);
ASSERT_TRUE(result.is_error());
ASSERT_EQ(result.error(),
"Parse error: Failed to parse output device policies: No output to support usage "
"RenderUsage::BACKGROUND");
}
TEST(ProcessConfigLoaderTest, AllowConfigWithoutUltrasound) {
static const std::string kConfigWithRoutingPolicy =
R"JSON({
"volume_curve": [
{
"level": 0.0,
"db": -160.0
},
{
"level": 1.0,
"db": 0.0
}
],
"output_devices": [
{
"device_id" : "34384e7da9d52c8062a9765baeb6053a",
"supported_stream_types": [
"render:media",
"render:interruption",
"render:background",
"render:communications",
"render:system_agent",
"capture:loopback"
]
}
]
})JSON";
ASSERT_TRUE(files::WriteFile(kTestAudioCoreConfigFilename, kConfigWithRoutingPolicy.data(),
kConfigWithRoutingPolicy.size()));
auto result = ProcessConfigLoader::LoadProcessConfig(kTestAudioCoreConfigFilename);
ASSERT_TRUE(result.is_ok());
}
TEST(ProcessConfigLoaderTest, LoadProcessConfigWithOutputDriverGain) {
static const std::string kConfigWithDriverGain =
R"JSON({
"volume_curve": [
{
"level": 0.0,
"db": -160.0
},
{
"level": 1.0,
"db": 0.0
}
],
"output_devices": [
{
"device_id" : "34384e7da9d52c8062a9765baeb6053a",
"supported_stream_types": [
"render:media",
"render:interruption",
"render:background",
"render:communications",
"render:system_agent",
"capture:loopback"
],
"driver_gain_db": -6.0
}
]
})JSON";
ASSERT_TRUE(files::WriteFile(kTestAudioCoreConfigFilename, kConfigWithDriverGain.data(),
kConfigWithDriverGain.size()));
auto result = ProcessConfigLoader::LoadProcessConfig(kTestAudioCoreConfigFilename);
ASSERT_TRUE(result.is_ok());
const audio_stream_unique_id_t expected_id = {.data = {0x34, 0x38, 0x4e, 0x7d, 0xa9, 0xd5, 0x2c,
0x80, 0x62, 0xa9, 0x76, 0x5b, 0xae, 0xb6,
0x05, 0x3a}};
const audio_stream_unique_id_t unknown_id = {.data = {0x34, 0x38, 0x4e, 0x7d, 0xa9, 0xd5, 0x2c,
0x80, 0x62, 0xa9, 0x76, 0x5b, 0xae, 0xb6,
0x05, 0x3b}};
auto& config = result.value().device_config();
EXPECT_FLOAT_EQ(config.output_device_profile(expected_id).driver_gain_db(), -6.0f);
EXPECT_FLOAT_EQ(config.output_device_profile(unknown_id).driver_gain_db(), 0.0f);
}
TEST(ProcessConfigLoaderTest, LoadProcessConfigWithInputDriverGain) {
static const std::string kConfigWithDriverGain =
R"JSON({
"volume_curve": [
{
"level": 0.0,
"db": -160.0
},
{
"level": 1.0,
"db": 0.0
}
],
"input_devices": [
{
"device_id" : "34384e7da9d52c8062a9765baeb6053a",
"supported_stream_types": [
"capture:background"
],
"rate": 96000,
"driver_gain_db": -6.0
}
]
})JSON";
ASSERT_TRUE(files::WriteFile(kTestAudioCoreConfigFilename, kConfigWithDriverGain.data(),
kConfigWithDriverGain.size()));
auto result = ProcessConfigLoader::LoadProcessConfig(kTestAudioCoreConfigFilename);
ASSERT_TRUE(result.is_ok());
const audio_stream_unique_id_t expected_id = {.data = {0x34, 0x38, 0x4e, 0x7d, 0xa9, 0xd5, 0x2c,
0x80, 0x62, 0xa9, 0x76, 0x5b, 0xae, 0xb6,
0x05, 0x3a}};
const audio_stream_unique_id_t unknown_id = {.data = {0x32, 0x38, 0x4e, 0x7d, 0xa9, 0xd5, 0x2c,
0x81, 0x42, 0xa9, 0x76, 0x5b, 0xae, 0xb6,
0x22, 0x3a}};
auto& config = result.value().device_config();
EXPECT_FLOAT_EQ(config.input_device_profile(expected_id).driver_gain_db(), -6.0f);
EXPECT_FLOAT_EQ(config.input_device_profile(unknown_id).driver_gain_db(), 0.0f);
}
TEST(ProcessConfigLoaderTest, LoadProcessConfigWithInputDevices) {
static const std::string kConfigWithInputDevices =
R"JSON({
"volume_curve": [
{
"level": 0.0,
"db": -160.0
},
{
"level": 1.0,
"db": 0.0
}
],
"input_devices": [
{
"device_id" : "34384e7da9d52c8062a9765baeb6053a",
"supported_stream_types": [
"capture:background"
],
"rate": 96000
},
{
"device_id": "*",
"rate": 24000
}
]
})JSON";
ASSERT_TRUE(files::WriteFile(kTestAudioCoreConfigFilename, kConfigWithInputDevices.data(),
kConfigWithInputDevices.size()));
const audio_stream_unique_id_t expected_id = {.data = {0x34, 0x38, 0x4e, 0x7d, 0xa9, 0xd5, 0x2c,
0x80, 0x62, 0xa9, 0x76, 0x5b, 0xae, 0xb6,
0x05, 0x3a}};
const audio_stream_unique_id_t unknown_id = {.data = {0x32, 0x38, 0x4e, 0x7d, 0xa9, 0xd5, 0x2c,
0x81, 0x42, 0xa9, 0x76, 0x5b, 0xae, 0xb6,
0x22, 0x3a}};
auto result = ProcessConfigLoader::LoadProcessConfig(kTestAudioCoreConfigFilename);
ASSERT_TRUE(result.is_ok());
auto& config = result.value().device_config();
EXPECT_EQ(config.input_device_profile(expected_id).rate(), 96000u);
EXPECT_TRUE(config.input_device_profile(expected_id)
.supports_usage(StreamUsage::WithCaptureUsage(CaptureUsage::BACKGROUND)));
EXPECT_FALSE(config.input_device_profile(expected_id)
.supports_usage(StreamUsage::WithCaptureUsage(CaptureUsage::FOREGROUND)));
EXPECT_FALSE(config.input_device_profile(expected_id)
.supports_usage(StreamUsage::WithCaptureUsage(CaptureUsage::SYSTEM_AGENT)));
EXPECT_FALSE(config.input_device_profile(expected_id)
.supports_usage(StreamUsage::WithCaptureUsage(CaptureUsage::COMMUNICATION)));
EXPECT_FALSE(config.input_device_profile(expected_id)
.supports_usage(StreamUsage::WithCaptureUsage(CaptureUsage::ULTRASOUND)));
EXPECT_EQ(config.input_device_profile(unknown_id).rate(), 24000u);
EXPECT_TRUE(config.input_device_profile(unknown_id)
.supports_usage(StreamUsage::WithCaptureUsage(CaptureUsage::BACKGROUND)));
EXPECT_TRUE(config.input_device_profile(unknown_id)
.supports_usage(StreamUsage::WithCaptureUsage(CaptureUsage::FOREGROUND)));
EXPECT_TRUE(config.input_device_profile(unknown_id)
.supports_usage(StreamUsage::WithCaptureUsage(CaptureUsage::SYSTEM_AGENT)));
EXPECT_TRUE(config.input_device_profile(unknown_id)
.supports_usage(StreamUsage::WithCaptureUsage(CaptureUsage::COMMUNICATION)));
EXPECT_FALSE(config.input_device_profile(unknown_id)
.supports_usage(StreamUsage::WithCaptureUsage(CaptureUsage::ULTRASOUND)));
}
TEST(ProcessConfigLoaderTest, LoadProcessConfigWithEffects) {
static const std::string kConfigWithEffects =
R"JSON({
"volume_curve": [
{ "level": 0.0, "db": -160.0 },
{ "level": 1.0, "db": 0.0 }
],
"output_devices": [
{
"device_id" : "34384e7da9d52c8062a9765baeb6053a",
"supported_stream_types": [
"render:media",
"render:interruption",
"render:background",
"render:communications",
"render:system_agent",
"capture:loopback"
],
"pipeline": {
"streams": [
"render:background",
"render:system_agent",
"render:media",
"render:interruption"
],
"output_rate": 96000,
"output_channels": 4,
"effects": [
{
"lib": "libbar2.so",
"effect": "linearize_effect",
"name": "instance_name",
"_comment": "just a comment",
"config": {
"a": 123,
"b": 456
}
}
],
"inputs": [
{
"streams": [],
"loopback": true,
"output_rate": 48000,
"effects": [
{
"lib": "libfoo2.so",
"effect": "effect3",
"output_channels": 4
}
],
"inputs": [
{
"streams": [
"render:media"
],
"name": "media",
"effects": [
{
"lib": "libfoo.so",
"effect": "effect1",
"config": {
"some_config": 0
}
},
{
"lib": "libbar.so",
"effect": "effect2",
"config": {
"arg1": 55,
"arg2": 3.14
}
}
]
},
{
"streams": [
"render:communications"
],
"name": "communications",
"effects": [
{
"lib": "libbaz.so",
"effect": "baz",
"_comment": "Ignore me",
"config": {
"string_param": "some string value"
}
}
]
}
]
}
]
}
}
]
})JSON";
ASSERT_TRUE(files::WriteFile(kTestAudioCoreConfigFilename, kConfigWithEffects.data(),
kConfigWithEffects.size()));
auto result = ProcessConfigLoader::LoadProcessConfig(kTestAudioCoreConfigFilename);
ASSERT_TRUE(result.is_ok());
const audio_stream_unique_id_t device_id = {.data = {0x34, 0x38, 0x4e, 0x7d, 0xa9, 0xd5, 0x2c,
0x80, 0x62, 0xa9, 0x76, 0x5b, 0xae, 0xb6,
0x05, 0x3a}};
const auto& config = result.value();
const auto& root =
config.device_config().output_device_profile(device_id).pipeline_config().root();
{ // 'linearize' mix_group
const auto& mix_group = root;
EXPECT_EQ("", mix_group.name);
EXPECT_EQ(4u, mix_group.input_streams.size());
EXPECT_EQ(RenderUsage::BACKGROUND, mix_group.input_streams[0]);
EXPECT_EQ(RenderUsage::SYSTEM_AGENT, mix_group.input_streams[1]);
EXPECT_EQ(RenderUsage::MEDIA, mix_group.input_streams[2]);
EXPECT_EQ(RenderUsage::INTERRUPTION, mix_group.input_streams[3]);
ASSERT_EQ(1u, mix_group.effects.size());
{
const auto& effect = mix_group.effects[0];
EXPECT_EQ("libbar2.so", effect.lib_name);
EXPECT_EQ("linearize_effect", effect.effect_name);
EXPECT_EQ("instance_name", effect.instance_name);
EXPECT_EQ("{\"a\":123,\"b\":456}", effect.effect_config);
EXPECT_FALSE(effect.output_channels);
}
ASSERT_EQ(1u, mix_group.inputs.size());
ASSERT_FALSE(mix_group.loopback);
ASSERT_EQ(96000u, mix_group.output_rate);
EXPECT_EQ(4u, mix_group.output_channels);
}
const auto& mix = root.inputs[0];
{ // 'mix' mix_group
const auto& mix_group = mix;
EXPECT_EQ("", mix_group.name);
EXPECT_EQ(0u, mix_group.input_streams.size());
ASSERT_EQ(1u, mix_group.effects.size());
{
const auto& effect = mix_group.effects[0];
EXPECT_EQ("libfoo2.so", effect.lib_name);
EXPECT_EQ("effect3", effect.effect_name);
EXPECT_EQ("", effect.effect_config);
EXPECT_TRUE(effect.output_channels);
EXPECT_EQ(4u, *effect.output_channels);
}
ASSERT_EQ(2u, mix_group.inputs.size());
ASSERT_TRUE(mix_group.loopback);
ASSERT_EQ(48000u, mix_group.output_rate);
}
{ // output mix_group 1
const auto& mix_group = mix.inputs[0];
EXPECT_EQ("media", mix_group.name);
EXPECT_EQ(1u, mix_group.input_streams.size());
EXPECT_EQ(RenderUsage::MEDIA, mix_group.input_streams[0]);
ASSERT_EQ(2u, mix_group.effects.size());
{
const auto& effect = mix_group.effects[0];
EXPECT_EQ("libfoo.so", effect.lib_name);
EXPECT_EQ("effect1", effect.effect_name);
EXPECT_EQ("{\"some_config\":0}", effect.effect_config);
EXPECT_FALSE(effect.output_channels);
}
{
const auto& effect = mix_group.effects[1];
EXPECT_EQ("libbar.so", effect.lib_name);
EXPECT_EQ("effect2", effect.effect_name);
EXPECT_EQ("{\"arg1\":55,\"arg2\":3.14}", effect.effect_config);
EXPECT_FALSE(effect.output_channels);
}
ASSERT_FALSE(mix_group.loopback);
EXPECT_EQ(48000u, mix_group.output_rate);
EXPECT_EQ(2u, mix_group.output_channels);
ASSERT_EQ(PipelineConfig::kDefaultMixGroupRate, mix_group.output_rate);
}
{ // output mix_group 2
const auto& mix_group = mix.inputs[1];
EXPECT_EQ("communications", mix_group.name);
EXPECT_EQ(1u, mix_group.input_streams.size());
EXPECT_EQ(RenderUsage::COMMUNICATION, mix_group.input_streams[0]);
ASSERT_EQ(1u, mix_group.effects.size());
{
const auto& effect = mix_group.effects[0];
EXPECT_EQ("libbaz.so", effect.lib_name);
EXPECT_EQ("baz", effect.effect_name);
EXPECT_EQ("{\"string_param\":\"some string value\"}", effect.effect_config);
EXPECT_FALSE(effect.output_channels);
}
ASSERT_FALSE(mix_group.loopback);
EXPECT_EQ(48000u, mix_group.output_rate);
EXPECT_EQ(2u, mix_group.output_channels);
ASSERT_EQ(PipelineConfig::kDefaultMixGroupRate, mix_group.output_rate);
}
}
TEST(ProcessConfigLoaderTest, FileNotFound) {
const auto result = ProcessConfigLoader::LoadProcessConfig("not-present-file");
ASSERT_TRUE(result.is_error());
ASSERT_EQ(result.error(), "File does not exist");
}
TEST(ProcessConfigLoaderTest, RejectConfigWithoutVolumeCurve) {
static const std::string kConfigWithoutVolumeCurve = R"JSON({ })JSON";
ASSERT_TRUE(files::WriteFile(kTestAudioCoreConfigFilename, kConfigWithoutVolumeCurve.data(),
kConfigWithoutVolumeCurve.size()));
auto result = ProcessConfigLoader::LoadProcessConfig(kTestAudioCoreConfigFilename);
ASSERT_TRUE(result.is_error());
ASSERT_EQ(result.error(),
R"ERROR(Parse error: Schema validation error ({
"required": {
"missing": [
"volume_curve"
],
"instanceRef": "#",
"schemaRef": "#"
}
}))ERROR");
}
TEST(ProcessConfigLoaderTest, RejectConfigWithUnknownKeys) {
static const std::string kConfigWithExtraKeys =
R"JSON({
"extra_key": 3,
"volume_curve": [
{
"level": 0.0,
"db": -160.0
},
{
"level": 1.0,
"db": 0.0
}
]
})JSON";
ASSERT_TRUE(files::WriteFile(kTestAudioCoreConfigFilename, kConfigWithExtraKeys.data(),
kConfigWithExtraKeys.size()));
auto result = ProcessConfigLoader::LoadProcessConfig(kTestAudioCoreConfigFilename);
ASSERT_TRUE(result.is_error());
ASSERT_EQ(result.error(),
R"ERROR(Parse error: Schema validation error ({
"additionalProperties": {
"disallowed": "extra_key",
"instanceRef": "#",
"schemaRef": "#"
}
}))ERROR");
}
TEST(ProcessConfigLoaderTest, RejectConfigWithMultipleLoopbackStages) {
static const std::string kConfigWithVolumeCurve =
R"JSON({
"volume_curve": [
{
"level": 0.0,
"db": -160.0
},
{
"level": 1.0,
"db": 0.0
}
],
"output_devices": [
{
"device_id" : "34384e7da9d52c8062a9765baeb6053a",
"supported_stream_types": [
"render:media",
"render:interruption",
"render:background",
"render:communications",
"render:system_agent",
"capture:loopback"
],
"pipeline": {
"inputs": [
{
"streams": [
"render:media",
"render:interruption",
"render:background",
"render:system_agent"
],
"loopback": true
}, {
"streams": [
"render:communications"
],
"loopback": true
}
]
}
}
]
})JSON";
ASSERT_TRUE(files::WriteFile(kTestAudioCoreConfigFilename, kConfigWithVolumeCurve.data(),
kConfigWithVolumeCurve.size()));
auto result = ProcessConfigLoader::LoadProcessConfig(kTestAudioCoreConfigFilename);
ASSERT_TRUE(result.is_error());
ASSERT_EQ(
result.error(),
"Parse error: Failed to parse output device policies: More than 1 loopback stage specified");
}
TEST(ProcessConfigLoaderTest, RejectConfigWithoutLoopbackPointSpecified) {
static const std::string kConfigWithVolumeCurve =
R"JSON({
"volume_curve": [
{
"level": 0.0,
"db": -160.0
},
{
"level": 1.0,
"db": 0.0
}
],
"output_devices": [
{
"device_id" : "34384e7da9d52c8062a9765baeb6053a",
"supported_stream_types": [
"render:media",
"render:interruption",
"render:background",
"render:communications",
"render:system_agent",
"capture:loopback"
],
"pipeline": {
"streams": [
"render:media",
"render:interruption",
"render:background",
"render:communications",
"render:system_agent"
]
}
}
]
})JSON";
ASSERT_TRUE(files::WriteFile(kTestAudioCoreConfigFilename, kConfigWithVolumeCurve.data(),
kConfigWithVolumeCurve.size()));
auto result = ProcessConfigLoader::LoadProcessConfig(kTestAudioCoreConfigFilename);
ASSERT_TRUE(result.is_error());
ASSERT_EQ(result.error(),
"Parse error: Failed to parse output device policies: Device supports loopback but no "
"loopback point specified");
}
TEST(ProcessConfigLoaderTest, RejectConfigWithInvalidChannelCount) {
const auto& CreateConfig = [](int mix_stage_chans, int effect_chans) {
std::ostringstream oss;
oss << R"JSON({
"volume_curve": [
{
"level": 0.0,
"db": -160.0
},
{
"level": 1.0,
"db": 0.0
}
],
"output_devices": [
{
"device_id" : "*",
"supported_stream_types": [
"render:media",
"render:interruption",
"render:background",
"render:communications",
"render:system_agent"
],
"pipeline": {
"streams": [
"render:media",
"render:interruption",
"render:background",
"render:communications",
"render:system_agent"
],
"output_channels": )JSON"
<< mix_stage_chans << R"JSON(,
"effects": [
{
"lib": "fake_effects.so",
"effect": "effect1",
"output_channels": )JSON"
<< effect_chans << R"JSON(,
"config": { }
}
]
}
}
]
})JSON";
return oss.str();
};
// Sanity test our CreateConfig can build a valid config.
EXPECT_TRUE(ProcessConfigLoader::ParseProcessConfig(CreateConfig(1, 1)).is_ok());
EXPECT_TRUE(ProcessConfigLoader::ParseProcessConfig(CreateConfig(8, 8)).is_ok());
// Now verify we reject channel counts outside the range of [1, 8].
EXPECT_TRUE(ProcessConfigLoader::ParseProcessConfig(CreateConfig(0, 1)).is_error());
EXPECT_TRUE(ProcessConfigLoader::ParseProcessConfig(CreateConfig(1, 0)).is_error());
EXPECT_TRUE(ProcessConfigLoader::ParseProcessConfig(CreateConfig(-1, 2)).is_error());
EXPECT_TRUE(ProcessConfigLoader::ParseProcessConfig(CreateConfig(2, -1)).is_error());
EXPECT_TRUE(ProcessConfigLoader::ParseProcessConfig(CreateConfig(8, 9)).is_error());
EXPECT_TRUE(ProcessConfigLoader::ParseProcessConfig(CreateConfig(9, 8)).is_error());
}
TEST(ProcessConfigLoaderTest, RejectConfigWithInvalidDefaultVolumeRenderUsages) {
static const std::string kConfigWithInvalidRenderUsages = R"JSON({
"volume_curve": [
{
"level": 0.0,
"db": -160.0
},
{
"level": 1.0,
"db": 0.0
}
],
"default_render_usage_volumes": {
"invalid": 0.0
}
})JSON";
ASSERT_TRUE(files::WriteFile(kTestAudioCoreConfigFilename, kConfigWithInvalidRenderUsages.data(),
kConfigWithInvalidRenderUsages.size()));
auto result = ProcessConfigLoader::LoadProcessConfig(kTestAudioCoreConfigFilename);
ASSERT_TRUE(result.is_error());
}
TEST(ProcessConfigLoaderTest, LoadProcessConfigWithThermalPolicy) {
static const std::string kConfigWithThermalPolicy =
R"JSON({
"volume_curve": [
{
"level": 0.0,
"db": -160.0
},
{
"level": 1.0,
"db": 0.0
}
],
"thermal_policy" : [
{
"trip_point": {
"deactivate_below": 23,
"activate_at": 25
},
"state_transitions": [
{
"target_name": "target name 0",
"config": {
"value": "config 0 trip point 0"
}
},
{
"target_name": "target name 1",
"config": {
"value": "config 1 trip point 0"
}
}
]
},
{
"trip_point": {
"deactivate_below": 48,
"activate_at": 50
},
"state_transitions": [
{
"target_name": "target name 1",
"config": {
"value": "config 1 trip point 1"
}
}
]
},
{
"trip_point": {
"deactivate_below": 73,
"activate_at": 75
},
"state_transitions": [
{
"target_name": "target name 0",
"config": {
"value": "config 0 trip point 2"
}
}
]
}
]
})JSON";
ASSERT_TRUE(files::WriteFile(kTestAudioCoreConfigFilename, kConfigWithThermalPolicy.data(),
kConfigWithThermalPolicy.size()));
auto result = ProcessConfigLoader::LoadProcessConfig(kTestAudioCoreConfigFilename);
ASSERT_TRUE(result.is_ok());
auto config = result.value();
const auto& entries = config.thermal_config().entries();
EXPECT_EQ(3u, entries.size());
EXPECT_EQ(23u, entries[0].trip_point().deactivate_below);
EXPECT_EQ(25u, entries[0].trip_point().activate_at);
EXPECT_EQ(2u, entries[0].state_transitions().size());
EXPECT_EQ("target name 0", entries[0].state_transitions()[0].target_name());
EXPECT_EQ("{\"value\":\"config 0 trip point 0\"}", entries[0].state_transitions()[0].config());
EXPECT_EQ("target name 1", entries[0].state_transitions()[1].target_name());
EXPECT_EQ("{\"value\":\"config 1 trip point 0\"}", entries[0].state_transitions()[1].config());
EXPECT_EQ(48u, entries[1].trip_point().deactivate_below);
EXPECT_EQ(50u, entries[1].trip_point().activate_at);
EXPECT_EQ(1u, entries[1].state_transitions().size());
EXPECT_EQ("target name 1", entries[1].state_transitions()[0].target_name());
EXPECT_EQ("{\"value\":\"config 1 trip point 1\"}", entries[1].state_transitions()[0].config());
EXPECT_EQ(73u, entries[2].trip_point().deactivate_below);
EXPECT_EQ(75u, entries[2].trip_point().activate_at);
EXPECT_EQ(1u, entries[2].state_transitions().size());
EXPECT_EQ("target name 0", entries[2].state_transitions()[0].target_name());
EXPECT_EQ("{\"value\":\"config 0 trip point 2\"}", entries[2].state_transitions()[0].config());
}
TEST(ProcessConfigLoaderTest, LoadOutputDevicePolicyWithDefaultPipeline) {
static const std::string kConfigWithRoutingPolicy =
R"JSON({
"volume_curve": [
{
"level": 0.0,
"db": -160.0
},
{
"level": 1.0,
"db": 0.0
}
],
"output_devices": [
{
"device_id" : "34384e7da9d52c8062a9765baeb6053a",
"supported_stream_types": [
"capture:loopback",
"render:media"
]
},
{
"device_id": "*",
"supported_stream_types": [
"render:media",
"render:interruption",
"render:background",
"render:communications",
"render:system_agent"
]
}
]
})JSON";
ASSERT_TRUE(files::WriteFile(kTestAudioCoreConfigFilename, kConfigWithRoutingPolicy.data(),
kConfigWithRoutingPolicy.size()));
const audio_stream_unique_id_t expected_id = {.data = {0x34, 0x38, 0x4e, 0x7d, 0xa9, 0xd5, 0x2c,
0x80, 0x62, 0xa9, 0x76, 0x5b, 0xae, 0xb6,
0x05, 0x3a}};
const auto result = ProcessConfigLoader::LoadProcessConfig(kTestAudioCoreConfigFilename);
ASSERT_TRUE(result.is_ok());
auto& config = result.value().device_config().output_device_profile(expected_id);
EXPECT_TRUE(config.pipeline_config().root().loopback);
EXPECT_TRUE(config.pipeline_config().root().effects.empty());
EXPECT_TRUE(config.pipeline_config().root().inputs.empty());
EXPECT_EQ(PipelineConfig::kDefaultMixGroupRate, config.pipeline_config().root().output_rate);
EXPECT_EQ(PipelineConfig::kDefaultMixGroupChannels,
config.pipeline_config().root().output_channels);
ASSERT_EQ(1u, config.pipeline_config().root().input_streams.size());
EXPECT_EQ(RenderUsage::MEDIA, config.pipeline_config().root().input_streams[0]);
}
TEST(ProcessConfigLoaderTest, LoadOutputDevicePolicyWithNoSupportedStreamTypes) {
static const std::string kConfigWithRoutingPolicy =
R"JSON({
"volume_curve": [
{
"level": 0.0,
"db": -160.0
},
{
"level": 1.0,
"db": 0.0
}
],
"output_devices": [
{
"device_id" : "34384e7da9d52c8062a9765baeb6053a",
"supported_stream_types": []
},
{
"device_id": "*",
"supported_stream_types": [
"render:media",
"render:interruption",
"render:background",
"render:communications",
"render:system_agent"
]
}
]
})JSON";
ASSERT_TRUE(files::WriteFile(kTestAudioCoreConfigFilename, kConfigWithRoutingPolicy.data(),
kConfigWithRoutingPolicy.size()));
const audio_stream_unique_id_t expected_id = {.data = {0x34, 0x38, 0x4e, 0x7d, 0xa9, 0xd5, 0x2c,
0x80, 0x62, 0xa9, 0x76, 0x5b, 0xae, 0xb6,
0x05, 0x3a}};
const auto result = ProcessConfigLoader::LoadProcessConfig(kTestAudioCoreConfigFilename);
ASSERT_TRUE(result.is_ok());
auto& config = result.value().device_config().output_device_profile(expected_id);
for (const auto& render_usage : kRenderUsages) {
EXPECT_FALSE(config.supports_usage(render_usage));
}
EXPECT_FALSE(config.pipeline_config().root().loopback);
EXPECT_TRUE(config.pipeline_config().root().input_streams.empty());
EXPECT_TRUE(config.pipeline_config().root().effects.empty());
EXPECT_TRUE(config.pipeline_config().root().inputs.empty());
EXPECT_EQ(PipelineConfig::kDefaultMixGroupRate, config.pipeline_config().root().output_rate);
EXPECT_EQ(PipelineConfig::kDefaultMixGroupChannels,
config.pipeline_config().root().output_channels);
}
TEST(ProcessConfigLoaderTest, LoadOutputDevicePolicyVolumeCurve) {
static const std::string kConfigWithRoutingPolicy =
R"JSON({
"volume_curve": [
{
"level": 0.0,
"db": -160.0
},
{
"level": 0.5,
"db": -5.0
},
{
"level": 1.0,
"db": 0.0
}
],
"output_devices": [
{
"device_id" : "34384e7da9d52c8062a9765baeb6053a",
"supported_stream_types": []
},
{
"device_id": "*",
"supported_stream_types": [
"render:media",
"render:interruption",
"render:background",
"render:communications",
"render:system_agent"
]
}
]
})JSON";
ASSERT_TRUE(files::WriteFile(kTestAudioCoreConfigFilename, kConfigWithRoutingPolicy.data(),
kConfigWithRoutingPolicy.size()));
const audio_stream_unique_id_t expected_id = {.data = {0x34, 0x38, 0x4e, 0x7d, 0xa9, 0xd5, 0x2c,
0x80, 0x62, 0xa9, 0x76, 0x5b, 0xae, 0xb6,
0x05, 0x3a}};
const auto result = ProcessConfigLoader::LoadProcessConfig(kTestAudioCoreConfigFilename);
ASSERT_TRUE(result.is_ok());
auto& result_curve =
result.value().device_config().output_device_profile(expected_id).volume_curve();
// Matching volume curve in kConfigWithRoutingPolicy above.
auto expected_curve = VolumeCurve::FromMappings({VolumeCurve::VolumeMapping(0.0, -160.0),
VolumeCurve::VolumeMapping(0.5, -5.0),
VolumeCurve::VolumeMapping(1.0, 0.0)});
ASSERT_TRUE(result.is_ok());
auto curve = expected_curve.take_value();
EXPECT_FLOAT_EQ(curve.VolumeToDb(0.25), result_curve.VolumeToDb(0.25));
EXPECT_FLOAT_EQ(curve.VolumeToDb(0.5), result_curve.VolumeToDb(0.5));
EXPECT_FLOAT_EQ(curve.VolumeToDb(0.75), result_curve.VolumeToDb(0.75));
}
} // namespace
} // namespace media::audio