| /* |
| * Copyright (C) 2023 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. |
| */ |
| |
| #include <cstdint> |
| #include <cstring> |
| #define LOG_TAG "AidlConversionSpatializer" |
| //#define LOG_NDEBUG 0 |
| |
| #include <aidl/android/hardware/audio/effect/DefaultExtension.h> |
| #include <aidl/android/hardware/audio/effect/VendorExtension.h> |
| #include <error/expected_utils.h> |
| #include <media/AidlConversionCppNdk.h> |
| #include <media/AidlConversionEffect.h> |
| #include <media/AidlConversionNdk.h> |
| #include <system/audio_effects/aidl_effects_utils.h> |
| #include <system/audio_effects/effect_spatializer.h> |
| #include <utils/Log.h> |
| |
| #include "AidlConversionSpatializer.h" |
| |
| namespace android { |
| namespace effect { |
| |
| using aidl::android::getParameterSpecificField; |
| using aidl::android::aidl_utils::statusTFromBinderStatus; |
| using aidl::android::hardware::audio::common::SourceMetadata; |
| using aidl::android::hardware::audio::effect::DefaultExtension; |
| using aidl::android::hardware::audio::effect::Parameter; |
| using aidl::android::hardware::audio::effect::Range; |
| using aidl::android::hardware::audio::effect::Spatializer; |
| using aidl::android::hardware::audio::effect::VendorExtension; |
| using aidl::android::media::audio::common::AudioChannelLayout; |
| using aidl::android::media::audio::common::HeadTracking; |
| using aidl::android::media::audio::common::Spatialization; |
| using aidl::android::media::audio::common::toString; |
| using android::status_t; |
| using utils::EffectParamReader; |
| using utils::EffectParamWriter; |
| |
| bool AidlConversionSpatializer::isSpatializerParameterSupported() { |
| return mIsSpatializerAidlParamSupported.value_or( |
| (mIsSpatializerAidlParamSupported = |
| [&]() { |
| ::aidl::android::hardware::audio::effect::Parameter aidlParam; |
| auto id = MAKE_SPECIFIC_PARAMETER_ID(Spatializer, spatializerTag, |
| Spatializer::vendor); |
| // No range defined in descriptor capability means no Spatializer AIDL |
| // implementation BAD_VALUE return from getParameter indicates the |
| // parameter is not supported by HAL |
| return mDesc.capability.range.getTag() == Range::spatializer && |
| mEffect->getParameter(id, &aidlParam).getStatus() != |
| android::BAD_VALUE; |
| }()) |
| .value()); |
| } |
| |
| status_t AidlConversionSpatializer::setParameter(EffectParamReader& param) { |
| Parameter aidlParam; |
| if (isSpatializerParameterSupported()) { |
| uint32_t command = 0; |
| if (!param.validateParamValueSize(sizeof(uint32_t), sizeof(int8_t)) || |
| OK != param.readFromParameter(&command)) { |
| ALOGE("%s %d invalid param %s", __func__, __LINE__, param.toString().c_str()); |
| return BAD_VALUE; |
| } |
| |
| switch (command) { |
| case SPATIALIZER_PARAM_LEVEL: { |
| Spatialization::Level level = Spatialization::Level::NONE; |
| if (OK != param.readFromValue(&level)) { |
| ALOGE("%s invalid level value %s", __func__, param.toString().c_str()); |
| return BAD_VALUE; |
| } |
| aidlParam = MAKE_SPECIFIC_PARAMETER(Spatializer, spatializer, |
| spatializationLevel, level); |
| break; |
| } |
| case SPATIALIZER_PARAM_HEADTRACKING_MODE: { |
| HeadTracking::Mode mode = HeadTracking::Mode::DISABLED; |
| if (OK != param.readFromValue(&mode)) { |
| ALOGE("%s invalid mode value %s", __func__, param.toString().c_str()); |
| return BAD_VALUE; |
| } |
| aidlParam = MAKE_SPECIFIC_PARAMETER(Spatializer, spatializer, headTrackingMode, |
| mode); |
| break; |
| } |
| case SPATIALIZER_PARAM_HEAD_TO_STAGE: { |
| const size_t valueSize = param.getValueSize(); |
| if (valueSize / sizeof(float) > 6 || valueSize % sizeof(float) != 0) { |
| ALOGE("%s invalid parameter value size %zu", __func__, valueSize); |
| return BAD_VALUE; |
| } |
| std::array<float, 6> headToStage = {}; |
| for (size_t i = 0; i < valueSize / sizeof(float); i++) { |
| if (OK != param.readFromValue(&headToStage[i])) { |
| ALOGE("%s failed to read headToStage from %s", __func__, |
| param.toString().c_str()); |
| return BAD_VALUE; |
| } |
| } |
| HeadTracking::SensorData sensorData = |
| HeadTracking::SensorData::make<HeadTracking::SensorData::headToStage>( |
| headToStage); |
| aidlParam = MAKE_SPECIFIC_PARAMETER(Spatializer, spatializer, |
| headTrackingSensorData, sensorData); |
| break; |
| } |
| case SPATIALIZER_PARAM_HEADTRACKING_CONNECTION: { |
| int32_t modeInt32 = 0; |
| int32_t sensorId = -1; |
| if (OK != param.readFromValue(&modeInt32) || OK != param.readFromValue(&sensorId)) { |
| ALOGE("%s %d invalid parameter value %s", __func__, __LINE__, |
| param.toString().c_str()); |
| return BAD_VALUE; |
| } |
| |
| const auto mode = static_cast<HeadTracking::ConnectionMode>(modeInt32); |
| if (mode < *ndk::enum_range<HeadTracking::ConnectionMode>().begin() || |
| mode > *ndk::enum_range<HeadTracking::ConnectionMode>().end()) { |
| ALOGE("%s %d invalid mode %d", __func__, __LINE__, modeInt32); |
| return BAD_VALUE; |
| } |
| aidlParam = MAKE_SPECIFIC_PARAMETER(Spatializer, spatializer, |
| headTrackingConnectionMode, mode); |
| if (status_t status = statusTFromBinderStatus(mEffect->setParameter(aidlParam)); |
| status != OK) { |
| ALOGE("%s failed to set headTrackingConnectionMode %s", __func__, |
| toString(mode).c_str()); |
| return status; |
| } |
| aidlParam = MAKE_SPECIFIC_PARAMETER(Spatializer, spatializer, headTrackingSensorId, |
| sensorId); |
| return statusTFromBinderStatus(mEffect->setParameter(aidlParam)); |
| } |
| default: { |
| // for vendor extension, copy data area to the DefaultExtension, parameter ignored |
| VendorExtension ext = VALUE_OR_RETURN_STATUS( |
| aidl::android::legacy2aidl_EffectParameterReader_VendorExtension(param)); |
| aidlParam = |
| MAKE_SPECIFIC_PARAMETER(Spatializer, spatializer, vendor, ext); |
| break; |
| } |
| } |
| } else { |
| aidlParam = VALUE_OR_RETURN_STATUS( |
| ::aidl::android::legacy2aidl_EffectParameterReader_Parameter(param)); |
| } |
| |
| return statusTFromBinderStatus(mEffect->setParameter(aidlParam)); |
| } |
| |
| status_t AidlConversionSpatializer::getParameter(EffectParamWriter& param) { |
| if (isSpatializerParameterSupported()) { |
| uint32_t command = 0; |
| if (!param.validateParamValueSize(sizeof(uint32_t), sizeof(int8_t)) || |
| OK != param.readFromParameter(&command)) { |
| ALOGE("%s %d invalid param %s", __func__, __LINE__, param.toString().c_str()); |
| return BAD_VALUE; |
| } |
| |
| switch (command) { |
| case SPATIALIZER_PARAM_SUPPORTED_LEVELS: { |
| const auto& range = getRange<Range::spatializer, Range::SpatializerRange>( |
| mDesc.capability, Spatializer::spatializationLevel); |
| if (!range) { |
| return BAD_VALUE; |
| } |
| std::vector<Spatialization::Level> levels; |
| for (const auto level : ::ndk::enum_range<Spatialization::Level>()) { |
| const auto spatializer = |
| Spatializer::make<Spatializer::spatializationLevel>(level); |
| if (spatializer >= range->min && spatializer <= range->max) { |
| levels.emplace_back(level); |
| } |
| } |
| const uint8_t num = levels.size(); |
| RETURN_STATUS_IF_ERROR(param.writeToValue(&num)); |
| for (const auto level : levels) { |
| RETURN_STATUS_IF_ERROR(param.writeToValue(&level)); |
| } |
| return OK; |
| } |
| case SPATIALIZER_PARAM_LEVEL: { |
| Parameter aidlParam; |
| Parameter::Id id = MAKE_SPECIFIC_PARAMETER_ID(Spatializer, spatializerTag, |
| Spatializer::spatializationLevel); |
| RETURN_STATUS_IF_ERROR( |
| statusTFromBinderStatus(mEffect->getParameter(id, &aidlParam))); |
| const auto level = VALUE_OR_RETURN_STATUS(GET_PARAMETER_SPECIFIC_FIELD( |
| aidlParam, Spatializer, spatializer, Spatializer::spatializationLevel, |
| Spatialization::Level)); |
| return param.writeToValue(&level); |
| } |
| case SPATIALIZER_PARAM_HEADTRACKING_SUPPORTED: { |
| const auto& range = getRange<Range::spatializer, Range::SpatializerRange>( |
| mDesc.capability, Spatializer::spatializationLevel); |
| if (!range) { |
| ALOGE("%s %d: range not defined for spatializationLevel", __func__, __LINE__); |
| return BAD_VALUE; |
| } |
| const auto& nonSupport = Spatializer::make<Spatializer::spatializationLevel>( |
| Spatialization::Level::NONE); |
| const bool support = (range->min > range->max || |
| (range->min == nonSupport && range->max == nonSupport)) |
| ? false |
| : true; |
| return param.writeToValue(&support); |
| } |
| case SPATIALIZER_PARAM_HEADTRACKING_MODE: { |
| Parameter aidlParam; |
| Parameter::Id id = MAKE_SPECIFIC_PARAMETER_ID(Spatializer, spatializerTag, |
| Spatializer::headTrackingMode); |
| RETURN_STATUS_IF_ERROR( |
| statusTFromBinderStatus(mEffect->getParameter(id, &aidlParam))); |
| const auto mode = VALUE_OR_RETURN_STATUS(GET_PARAMETER_SPECIFIC_FIELD( |
| aidlParam, Spatializer, spatializer, Spatializer::headTrackingMode, |
| HeadTracking::Mode)); |
| return param.writeToValue(&mode); |
| } |
| case SPATIALIZER_PARAM_SUPPORTED_CHANNEL_MASKS: { |
| Parameter aidlParam; |
| Parameter::Id id = MAKE_SPECIFIC_PARAMETER_ID(Spatializer, spatializerTag, |
| Spatializer::supportedChannelLayout); |
| RETURN_STATUS_IF_ERROR( |
| statusTFromBinderStatus(mEffect->getParameter(id, &aidlParam))); |
| const auto& supportedLayouts = VALUE_OR_RETURN_STATUS(GET_PARAMETER_SPECIFIC_FIELD( |
| aidlParam, Spatializer, spatializer, Spatializer::supportedChannelLayout, |
| std::vector<AudioChannelLayout>)); |
| // audio_channel_mask_t is uint32_t enum, write number in 32bit |
| const uint32_t num = supportedLayouts.size(); |
| RETURN_STATUS_IF_ERROR(param.writeToValue(&num)); |
| for (const auto& layout : supportedLayouts) { |
| audio_channel_mask_t mask = VALUE_OR_RETURN_STATUS( |
| ::aidl::android::aidl2legacy_AudioChannelLayout_audio_channel_mask_t( |
| layout, false /* isInput */)); |
| RETURN_STATUS_IF_ERROR(param.writeToValue(&mask)); |
| } |
| return OK; |
| } |
| case SPATIALIZER_PARAM_SUPPORTED_SPATIALIZATION_MODES: { |
| const auto& range = getRange<Range::spatializer, Range::SpatializerRange>( |
| mDesc.capability, Spatializer::spatializationMode); |
| if (!range) { |
| return BAD_VALUE; |
| } |
| std::vector<Spatialization::Mode> modes; |
| for (const auto mode : ::ndk::enum_range<Spatialization::Mode>()) { |
| if (const auto spatializer = |
| Spatializer::make<Spatializer::spatializationMode>(mode); |
| spatializer >= range->min && spatializer <= range->max) { |
| modes.emplace_back(mode); |
| } |
| } |
| const uint8_t num = modes.size(); |
| RETURN_STATUS_IF_ERROR(param.writeToValue(&num)); |
| for (const auto mode : modes) { |
| RETURN_STATUS_IF_ERROR(param.writeToValue(&mode)); |
| } |
| return OK; |
| } |
| case SPATIALIZER_PARAM_SUPPORTED_HEADTRACKING_CONNECTION: { |
| const auto& range = getRange<Range::spatializer, Range::SpatializerRange>( |
| mDesc.capability, Spatializer::headTrackingConnectionMode); |
| if (!range) { |
| return BAD_VALUE; |
| } |
| std::vector<HeadTracking::ConnectionMode> modes; |
| for (const auto mode : ::ndk::enum_range<HeadTracking::ConnectionMode>()) { |
| if (const auto spatializer = |
| Spatializer::make<Spatializer::headTrackingConnectionMode>(mode); |
| spatializer < range->min || spatializer > range->max) { |
| modes.emplace_back(mode); |
| } |
| } |
| const uint8_t num = modes.size(); |
| RETURN_STATUS_IF_ERROR(param.writeToValue(&num)); |
| for (const auto mode : modes) { |
| RETURN_STATUS_IF_ERROR(param.writeToValue(&mode)); |
| } |
| return OK; |
| } |
| case SPATIALIZER_PARAM_HEADTRACKING_CONNECTION: { |
| status_t status = OK; |
| Parameter aidlParam; |
| Parameter::Id id = MAKE_SPECIFIC_PARAMETER_ID( |
| Spatializer, spatializerTag, Spatializer::headTrackingConnectionMode); |
| RETURN_STATUS_IF_ERROR( |
| statusTFromBinderStatus(mEffect->getParameter(id, &aidlParam))); |
| const auto mode = VALUE_OR_RETURN_STATUS(GET_PARAMETER_SPECIFIC_FIELD( |
| aidlParam, Spatializer, spatializer, |
| Spatializer::headTrackingConnectionMode, HeadTracking::ConnectionMode)); |
| |
| id = MAKE_SPECIFIC_PARAMETER_ID(Spatializer, spatializerTag, |
| Spatializer::headTrackingSensorId); |
| RETURN_STATUS_IF_ERROR( |
| statusTFromBinderStatus(mEffect->getParameter(id, &aidlParam))); |
| const auto sensorId = VALUE_OR_RETURN_STATUS( |
| GET_PARAMETER_SPECIFIC_FIELD(aidlParam, Spatializer, spatializer, |
| Spatializer::headTrackingSensorId, int32_t)); |
| uint32_t modeInt32 = static_cast<int32_t>(mode); |
| if (status = param.writeToValue(&modeInt32); status != OK) { |
| ALOGW("%s %d: write mode %s to value failed %d", __func__, __LINE__, |
| toString(mode).c_str(), status); |
| return status; |
| } |
| if (status = param.writeToValue(&sensorId); status != OK) { |
| ALOGW("%s %d: write sensorId %d to value failed %d", __func__, __LINE__, |
| sensorId, status); |
| return status; |
| } |
| return OK; |
| } |
| default: { |
| VENDOR_EXTENSION_GET_AND_RETURN(Spatializer, spatializer, param); |
| } |
| } |
| } else { |
| Parameter aidlParam; |
| DefaultExtension defaultExt; |
| // read parameters into DefaultExtension vector<uint8_t> |
| defaultExt.bytes.resize(param.getParameterSize()); |
| if (OK != param.readFromParameter(defaultExt.bytes.data(), param.getParameterSize())) { |
| ALOGE("%s %d invalid param %s", __func__, __LINE__, param.toString().c_str()); |
| param.setStatus(BAD_VALUE); |
| return BAD_VALUE; |
| } |
| |
| VendorExtension idTag; |
| idTag.extension.setParcelable(defaultExt); |
| Parameter::Id id = UNION_MAKE(Parameter::Id, vendorEffectTag, idTag); |
| RETURN_STATUS_IF_ERROR(statusTFromBinderStatus(mEffect->getParameter(id, &aidlParam))); |
| // copy the AIDL extension data back to effect_param_t |
| return VALUE_OR_RETURN_STATUS( |
| ::aidl::android::aidl2legacy_Parameter_EffectParameterWriter(aidlParam, param)); |
| } |
| } |
| |
| } // namespace effect |
| } // namespace android |