| /* |
| * Copyright (C) 2024 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #define LOG_TAG "AHAL_Config" |
| |
| #include <aidl/android/media/audio/common/AudioProductStrategyType.h> |
| #include <android-base/logging.h> |
| #include <media/AidlConversionCppNdk.h> |
| #include <media/TypeConverter.h> |
| #include <media/convert.h> |
| #include <utils/FastStrcmp.h> |
| |
| #include "core-impl/CapEngineConfigXmlConverter.h" |
| #include "core-impl/XsdcConversion.h" |
| |
| using aidl::android::hardware::audio::common::iequals; |
| using aidl::android::media::audio::common::AudioDeviceAddress; |
| using aidl::android::media::audio::common::AudioDeviceDescription; |
| using aidl::android::media::audio::common::AudioHalCapConfiguration; |
| using aidl::android::media::audio::common::AudioHalCapCriterionV2; |
| using aidl::android::media::audio::common::AudioHalCapDomain; |
| using aidl::android::media::audio::common::AudioHalCapParameter; |
| using aidl::android::media::audio::common::AudioHalCapRule; |
| using aidl::android::media::audio::common::AudioPolicyForceUse; |
| using aidl::android::media::audio::common::AudioSource; |
| using aidl::android::media::audio::common::AudioStreamType; |
| |
| using ::android::BAD_VALUE; |
| using ::android::base::unexpected; |
| using ::android::utilities::convertTo; |
| |
| namespace eng_xsd = android::audio::policy::capengine::configuration; |
| |
| namespace aidl::android::hardware::audio::core::internal { |
| |
| static constexpr const char* gStrategiesParameter = "product_strategies"; |
| static constexpr const char* gInputSourcesParameter = "input_sources"; |
| static constexpr const char* gStreamsParameter = "streams"; |
| static constexpr const char* gOutputDevicesParameter = "selected_output_devices"; |
| static constexpr const char* gOutputDeviceAddressParameter = "device_address"; |
| static constexpr const char* gStrategyPrefix = "vx_"; |
| static constexpr const char* gLegacyStrategyPrefix = "STRATEGY_"; |
| static constexpr const char* gLegacyOutputDevicePrefix = "AUDIO_DEVICE_OUT_"; |
| static constexpr const char* gLegacyInputDevicePrefix = "AUDIO_DEVICE_IN_"; |
| static constexpr const char* gLegacyStreamPrefix = "AUDIO_STREAM_"; |
| static constexpr const char* gLegacySourcePrefix = "AUDIO_SOURCE_"; |
| |
| std::optional<std::vector<std::optional<AudioHalCapDomain>>>& |
| CapEngineConfigXmlConverter::getAidlCapEngineConfig() { |
| return mAidlCapDomains; |
| } |
| |
| ConversionResult<AudioHalCapRule::CriterionRule> convertCriterionRuleToAidl( |
| const eng_xsd::SelectionCriterionRuleType& xsdcRule) { |
| using Tag = AudioHalCapCriterionV2::Tag; |
| AudioHalCapRule::CriterionRule rule{}; |
| std::string criterionName = xsdcRule.getSelectionCriterion(); |
| std::string criterionValue = xsdcRule.getValue(); |
| if (iequals(criterionName, toString(Tag::availableInputDevices))) { |
| AudioHalCapCriterionV2::AvailableDevices value; |
| value.values.emplace_back(VALUE_OR_RETURN( |
| convertDeviceTypeToAidl(gLegacyInputDevicePrefix + criterionValue))); |
| rule.criterionAndValue = AudioHalCapCriterionV2::make<Tag::availableInputDevices>(value); |
| |
| } else if (iequals(criterionName, toString(Tag::availableOutputDevices))) { |
| AudioHalCapCriterionV2::AvailableDevices value; |
| value.values.emplace_back(VALUE_OR_RETURN( |
| convertDeviceTypeToAidl(gLegacyOutputDevicePrefix + criterionValue))); |
| rule.criterionAndValue = AudioHalCapCriterionV2::make<Tag::availableOutputDevices>(value); |
| } else if (iequals(criterionName, toString(Tag::availableInputDevicesAddresses))) { |
| AudioHalCapCriterionV2::AvailableDevicesAddresses value; |
| value.values.emplace_back(criterionValue); |
| rule.criterionAndValue = |
| AudioHalCapCriterionV2::make<Tag::availableInputDevicesAddresses>(value); |
| } else if (iequals(criterionName, toString(Tag::availableOutputDevicesAddresses))) { |
| AudioHalCapCriterionV2::AvailableDevicesAddresses value; |
| value.values.emplace_back(criterionValue); |
| rule.criterionAndValue = |
| AudioHalCapCriterionV2::make<Tag::availableOutputDevicesAddresses>(value); |
| } else if (iequals(criterionName, toString(Tag::telephonyMode))) { |
| AudioHalCapCriterionV2::TelephonyMode value; |
| value.values.emplace_back(VALUE_OR_RETURN(convertTelephonyModeToAidl(criterionValue))); |
| rule.criterionAndValue = AudioHalCapCriterionV2::make<Tag::telephonyMode>(value); |
| } else if (!fastcmp<strncmp>(criterionName.c_str(), kXsdcForceConfigForUse, |
| strlen(kXsdcForceConfigForUse))) { |
| AudioHalCapCriterionV2::ForceConfigForUse value; |
| value.values.emplace_back( |
| VALUE_OR_RETURN(convertForceUseToAidl(criterionName, criterionValue))); |
| rule.criterionAndValue = AudioHalCapCriterionV2::make<Tag::forceConfigForUse>(value); |
| } else { |
| LOG(ERROR) << __func__ << " unrecognized criterion " << criterionName; |
| return unexpected(BAD_VALUE); |
| } |
| if (xsdcRule.getMatchesWhen() == eng_xsd::MatchesWhenEnum::Excludes) { |
| rule.matchingRule = AudioHalCapRule::MatchingRule::EXCLUDES; |
| } else if (xsdcRule.getMatchesWhen() == eng_xsd::MatchesWhenEnum::Includes) { |
| rule.matchingRule = AudioHalCapRule::MatchingRule::INCLUDES; |
| } else if (xsdcRule.getMatchesWhen() == eng_xsd::MatchesWhenEnum::Is) { |
| rule.matchingRule = AudioHalCapRule::MatchingRule::IS; |
| } else if (xsdcRule.getMatchesWhen() == eng_xsd::MatchesWhenEnum::IsNot) { |
| rule.matchingRule = AudioHalCapRule::MatchingRule::IS_NOT; |
| } else { |
| LOG(ERROR) << "Unsupported match when rule."; |
| return unexpected(BAD_VALUE); |
| } |
| return rule; |
| } |
| |
| ConversionResult<AudioHalCapRule> convertRule(const eng_xsd::CompoundRuleType& xsdcCompoundRule) { |
| AudioHalCapRule rule{}; |
| bool isPreviousCompoundRule = true; |
| if (xsdcCompoundRule.getType() == eng_xsd::TypeEnum::Any) { |
| rule.compoundRule = AudioHalCapRule::CompoundRule::ANY; |
| } else if (xsdcCompoundRule.getType() == eng_xsd::TypeEnum::All) { |
| rule.compoundRule = AudioHalCapRule::CompoundRule::ALL; |
| } else { |
| LOG(ERROR) << "Unsupported compound rule type."; |
| return unexpected(BAD_VALUE); |
| } |
| for (const auto& childXsdcCoumpoundRule : xsdcCompoundRule.getCompoundRule_optional()) { |
| if (childXsdcCoumpoundRule.hasCompoundRule_optional()) { |
| rule.nestedRules.push_back(VALUE_OR_FATAL(convertRule(childXsdcCoumpoundRule))); |
| } else if (childXsdcCoumpoundRule.hasSelectionCriterionRule_optional()) { |
| rule.nestedRules.push_back(VALUE_OR_FATAL(convertRule(childXsdcCoumpoundRule))); |
| } |
| } |
| if (xsdcCompoundRule.hasSelectionCriterionRule_optional()) { |
| for (const auto& xsdcRule : xsdcCompoundRule.getSelectionCriterionRule_optional()) { |
| rule.criterionRules.push_back(VALUE_OR_FATAL(convertCriterionRuleToAidl(xsdcRule))); |
| } |
| } |
| return rule; |
| } |
| |
| ConversionResult<int> getAudioProductStrategyId(const std::string& path) { |
| std::vector<std::string> strings; |
| std::istringstream pathStream(path); |
| std::string stringToken; |
| while (getline(pathStream, stringToken, '/')) { |
| std::size_t pos = stringToken.find(gStrategyPrefix); |
| if (pos != std::string::npos) { |
| std::string strategyIdLiteral = stringToken.substr(pos + std::strlen(gStrategyPrefix)); |
| int strategyId; |
| if (!convertTo(strategyIdLiteral, strategyId)) { |
| LOG(ERROR) << "Invalid strategy " << stringToken << " from path " << path; |
| return unexpected(BAD_VALUE); |
| } |
| return strategyId; |
| } |
| pos = stringToken.find(gLegacyStrategyPrefix); |
| if (pos != std::string::npos) { |
| std::string legacyStrategyIdLiteral = stringToken.substr(pos); |
| const auto legacyStrategies = getLegacyProductStrategyMap(); |
| if (const auto& it = legacyStrategies.find(legacyStrategyIdLiteral); |
| it != legacyStrategies.end()) { |
| return it->second; |
| } |
| LOG(ERROR) << "Invalid legacy strategy " << stringToken << " from path " << path; |
| return unexpected(BAD_VALUE); |
| } |
| } |
| return unexpected(BAD_VALUE); |
| } |
| |
| ConversionResult<AudioSource> getAudioSource(const std::string& path) { |
| std::vector<std::string> strings; |
| std::istringstream pathStream(path); |
| std::string stringToken; |
| while (getline(pathStream, stringToken, '/')) { |
| if (stringToken.find(gInputSourcesParameter) != std::string::npos) { |
| getline(pathStream, stringToken, '/'); |
| std::transform(stringToken.begin(), stringToken.end(), stringToken.begin(), |
| [](char c) { return std::toupper(c); }); |
| std::string legacySourceLiteral = "AUDIO_SOURCE_" + stringToken; |
| audio_source_t legacySource; |
| if (!::android::SourceTypeConverter::fromString(legacySourceLiteral, legacySource)) { |
| LOG(ERROR) << "Invalid source " << stringToken << " from path " << path; |
| return unexpected(BAD_VALUE); |
| } |
| return legacy2aidl_audio_source_t_AudioSource(legacySource); |
| } |
| } |
| return unexpected(BAD_VALUE); |
| } |
| |
| ConversionResult<AudioStreamType> getAudioStreamType(const std::string& path) { |
| std::vector<std::string> strings; |
| std::istringstream pathStream(path); |
| std::string stringToken; |
| |
| while (getline(pathStream, stringToken, '/')) { |
| if (stringToken.find(gStreamsParameter) != std::string::npos) { |
| getline(pathStream, stringToken, '/'); |
| std::transform(stringToken.begin(), stringToken.end(), stringToken.begin(), |
| [](char c) { return std::toupper(c); }); |
| std::string legacyStreamLiteral = std::string(gLegacyStreamPrefix) + stringToken; |
| audio_stream_type_t legacyStream; |
| if (!::android::StreamTypeConverter::fromString(legacyStreamLiteral, legacyStream)) { |
| LOG(ERROR) << "Invalid stream " << stringToken << " from path " << path; |
| return unexpected(BAD_VALUE); |
| } |
| return legacy2aidl_audio_stream_type_t_AudioStreamType(legacyStream); |
| } |
| } |
| return unexpected(BAD_VALUE); |
| } |
| |
| ConversionResult<std::string> toUpperAndAppendPrefix(const std::string& capName, |
| const std::string& legacyPrefix) { |
| std::string legacyName = capName; |
| std::transform(legacyName.begin(), legacyName.end(), legacyName.begin(), |
| [](char c) { return std::toupper(c); }); |
| return legacyPrefix + legacyName; |
| } |
| |
| ConversionResult<AudioHalCapParameter> CapEngineConfigXmlConverter::convertParamToAidl( |
| const eng_xsd::ConfigurableElementSettingsType& element) { |
| const auto& path = element.getPath(); |
| |
| AudioHalCapParameter parameterSetting; |
| if (path.find(gStrategiesParameter) != std::string::npos) { |
| int strategyId = VALUE_OR_FATAL(getAudioProductStrategyId(path)); |
| if (path.find(gOutputDevicesParameter) != std::string::npos) { |
| // Value is 1 or 0 |
| if (!element.hasBitParameter_optional()) { |
| LOG(ERROR) << "Invalid strategy value type"; |
| return unexpected(BAD_VALUE); |
| } |
| // Convert name to output device type |
| const auto* xsdcParam = element.getFirstBitParameter_optional(); |
| std::string outputDevice = VALUE_OR_FATAL(toUpperAndAppendPrefix( |
| eng_xsd::toString(xsdcParam->getName()), gLegacyOutputDevicePrefix)); |
| audio_devices_t legacyType; |
| if (!::android::OutputDeviceConverter::fromString(outputDevice, legacyType)) { |
| LOG(ERROR) << "Invalid strategy device type " << outputDevice; |
| return unexpected(BAD_VALUE); |
| } |
| AudioDeviceDescription aidlDevice = |
| VALUE_OR_FATAL(legacy2aidl_audio_devices_t_AudioDeviceDescription(legacyType)); |
| bool isSelected; |
| if (!convertTo(xsdcParam->getValue(), isSelected)) { |
| LOG(ERROR) << "Invalid strategy device selection value " << xsdcParam->getValue(); |
| return unexpected(BAD_VALUE); |
| } |
| parameterSetting = |
| AudioHalCapParameter::StrategyDevice(aidlDevice, strategyId, isSelected); |
| } else if (path.find(gOutputDeviceAddressParameter) != std::string::npos) { |
| // Value is the address |
| if (!element.hasStringParameter_optional()) { |
| return unexpected(BAD_VALUE); |
| } |
| std::string address = element.getFirstStringParameter_optional()->getValue(); |
| parameterSetting = AudioHalCapParameter::StrategyDeviceAddress( |
| AudioDeviceAddress(address), strategyId); |
| } |
| } else if (path.find(gInputSourcesParameter) != std::string::npos) { |
| // Value is 1 or 0 |
| if (!element.hasBitParameter_optional()) { |
| LOG(ERROR) << "Invalid source value type"; |
| return unexpected(BAD_VALUE); |
| } |
| AudioSource audioSourceAidl = VALUE_OR_FATAL(getAudioSource(path)); |
| const auto* xsdcParam = element.getFirstBitParameter_optional(); |
| std::string inputDeviceLiteral = VALUE_OR_FATAL(toUpperAndAppendPrefix( |
| eng_xsd::toString(xsdcParam->getName()), gLegacyInputDevicePrefix)); |
| audio_devices_t inputDeviceType; |
| if (!::android::InputDeviceConverter::fromString(inputDeviceLiteral, inputDeviceType)) { |
| LOG(ERROR) << "Invalid source device type " << inputDeviceLiteral; |
| return unexpected(BAD_VALUE); |
| } |
| AudioDeviceDescription aidlDevice = |
| VALUE_OR_FATAL(legacy2aidl_audio_devices_t_AudioDeviceDescription(inputDeviceType)); |
| |
| bool isSelected; |
| if (!convertTo(xsdcParam->getValue(), isSelected)) { |
| LOG(ERROR) << "Invalid source value type " << xsdcParam->getValue(); |
| return unexpected(BAD_VALUE); |
| } |
| parameterSetting = |
| AudioHalCapParameter::InputSourceDevice(aidlDevice, audioSourceAidl, isSelected); |
| } else if (path.find(gStreamsParameter) != std::string::npos) { |
| AudioStreamType audioStreamAidl = VALUE_OR_FATAL(getAudioStreamType(path)); |
| if (!element.hasEnumParameter_optional()) { |
| LOG(ERROR) << "Invalid stream value type"; |
| return unexpected(BAD_VALUE); |
| } |
| const auto* xsdcParam = element.getFirstEnumParameter_optional(); |
| std::string profileLiteral = |
| VALUE_OR_FATAL(toUpperAndAppendPrefix(xsdcParam->getValue(), gLegacyStreamPrefix)); |
| audio_stream_type_t profileLegacyStream; |
| if (!::android::StreamTypeConverter::fromString(profileLiteral, profileLegacyStream)) { |
| LOG(ERROR) << "Invalid stream value " << profileLiteral; |
| return unexpected(BAD_VALUE); |
| } |
| AudioStreamType profileStreamAidl = VALUE_OR_FATAL( |
| legacy2aidl_audio_stream_type_t_AudioStreamType(profileLegacyStream)); |
| parameterSetting = |
| AudioHalCapParameter::StreamVolumeProfile(audioStreamAidl, profileStreamAidl); |
| } |
| return parameterSetting; |
| } |
| |
| ConversionResult<std::vector<AudioHalCapParameter>> |
| CapEngineConfigXmlConverter::convertSettingToAidl( |
| const eng_xsd::SettingsType::Configuration& xsdcSetting) { |
| std::vector<AudioHalCapParameter> aidlCapParameterSettings; |
| for (const auto& element : xsdcSetting.getConfigurableElement()) { |
| aidlCapParameterSettings.push_back(VALUE_OR_FATAL(convertParamToAidl(element))); |
| } |
| return aidlCapParameterSettings; |
| } |
| |
| ConversionResult<AudioHalCapConfiguration> CapEngineConfigXmlConverter::convertConfigurationToAidl( |
| const eng_xsd::ConfigurationsType::Configuration& xsdcConfiguration, |
| const eng_xsd::SettingsType::Configuration& xsdcSettingConfiguration) { |
| AudioHalCapConfiguration aidlCapConfiguration; |
| aidlCapConfiguration.name = xsdcConfiguration.getName(); |
| if (xsdcConfiguration.hasCompoundRule()) { |
| if (xsdcConfiguration.getCompoundRule().size() != 1) { |
| return unexpected(BAD_VALUE); |
| } |
| aidlCapConfiguration.rule = |
| VALUE_OR_FATAL(convertRule(xsdcConfiguration.getCompoundRule()[0])); |
| aidlCapConfiguration.parameterSettings = |
| VALUE_OR_FATAL(convertSettingToAidl(xsdcSettingConfiguration)); |
| } |
| return aidlCapConfiguration; |
| } |
| |
| ConversionResult<eng_xsd::SettingsType::Configuration> getConfigurationByName( |
| const std::string& name, const std::vector<eng_xsd::SettingsType>& xsdcSettingsVec) { |
| for (const auto& xsdcSettings : xsdcSettingsVec) { |
| for (const auto& xsdcConfiguration : xsdcSettings.getConfiguration()) { |
| if (xsdcConfiguration.getName() == name) { |
| return xsdcConfiguration; |
| } |
| } |
| } |
| LOG(ERROR) << __func__ << " failed to find configuration " << name; |
| return unexpected(BAD_VALUE); |
| } |
| |
| ConversionResult<std::vector<AudioHalCapConfiguration>> |
| CapEngineConfigXmlConverter::convertConfigurationsToAidl( |
| const std::vector<eng_xsd::ConfigurationsType>& xsdcConfigurationsVec, |
| const std::vector<eng_xsd::SettingsType>& xsdcSettingsVec) { |
| if (xsdcConfigurationsVec.empty() || xsdcSettingsVec.empty()) { |
| LOG(ERROR) << __func__ << " empty configurations/settings"; |
| return unexpected(BAD_VALUE); |
| } |
| std::vector<AudioHalCapConfiguration> aidlConfigurations; |
| for (const auto& xsdcConfigurations : xsdcConfigurationsVec) { |
| for (const auto& xsdcConfiguration : xsdcConfigurations.getConfiguration()) { |
| auto xsdcSettingConfiguration = VALUE_OR_FATAL( |
| getConfigurationByName(xsdcConfiguration.getName(), xsdcSettingsVec)); |
| aidlConfigurations.push_back(VALUE_OR_FATAL( |
| convertConfigurationToAidl(xsdcConfiguration, xsdcSettingConfiguration))); |
| } |
| } |
| return aidlConfigurations; |
| } |
| |
| ConversionResult<AudioHalCapDomain> CapEngineConfigXmlConverter::convertConfigurableDomainToAidl( |
| const eng_xsd::ConfigurableDomainType& xsdcConfigurableDomain) { |
| AudioHalCapDomain aidlConfigurableDomain; |
| |
| aidlConfigurableDomain.name = xsdcConfigurableDomain.getName(); |
| if (xsdcConfigurableDomain.hasSequenceAware() && xsdcConfigurableDomain.getSequenceAware()) { |
| LOG(ERROR) << "sequence aware not supported."; |
| return unexpected(BAD_VALUE); |
| } |
| if (xsdcConfigurableDomain.hasConfigurations() && xsdcConfigurableDomain.hasSettings()) { |
| aidlConfigurableDomain.configurations = VALUE_OR_FATAL(convertConfigurationsToAidl( |
| xsdcConfigurableDomain.getConfigurations(), xsdcConfigurableDomain.getSettings())); |
| } |
| return aidlConfigurableDomain; |
| } |
| |
| void CapEngineConfigXmlConverter::init() { |
| if (getXsdcConfig()->hasConfigurableDomain()) { |
| mAidlCapDomains = std::make_optional<>(VALUE_OR_FATAL( |
| (convertCollectionToAidlOptionalValues<eng_xsd::ConfigurableDomainType, |
| AudioHalCapDomain>( |
| getXsdcConfig()->getConfigurableDomain(), |
| std::bind(&CapEngineConfigXmlConverter::convertConfigurableDomainToAidl, |
| this, std::placeholders::_1))))); |
| } else { |
| mAidlCapDomains = std::nullopt; |
| } |
| } |
| |
| } // namespace aidl::android::hardware::audio::core::internal |