| // 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 |