| /* |
| ** |
| ** Copyright 2021, 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 "Spatializer" |
| //#define LOG_NDEBUG 0 |
| #include <utils/Log.h> |
| |
| #include <limits.h> |
| #include <stdint.h> |
| #include <sys/types.h> |
| |
| #include <android/content/AttributionSourceState.h> |
| #include <android/sensor.h> |
| #include <audio_utils/fixedfft.h> |
| #include <cutils/bitops.h> |
| #include <hardware/sensors.h> |
| #include <media/ShmemCompat.h> |
| #include <media/audiohal/EffectsFactoryHalInterface.h> |
| #include <mediautils/ServiceUtilities.h> |
| #include <utils/Thread.h> |
| |
| #include "Spatializer.h" |
| |
| namespace android { |
| |
| using aidl_utils::statusTFromBinderStatus; |
| using aidl_utils::binderStatusFromStatusT; |
| using android::content::AttributionSourceState; |
| using binder::Status; |
| using media::HeadTrackingMode; |
| using media::Pose3f; |
| using media::SpatializationLevel; |
| using media::SpatializationMode; |
| using media::SpatializerHeadTrackingMode; |
| using media::SensorPoseProvider; |
| |
| |
| using namespace std::chrono_literals; |
| |
| #define VALUE_OR_RETURN_BINDER_STATUS(x) \ |
| ({ auto _tmp = (x); \ |
| if (!_tmp.ok()) return aidl_utils::binderStatusFromStatusT(_tmp.error()); \ |
| std::move(_tmp.value()); }) |
| |
| #define RETURN_IF_BINDER_ERROR(x) \ |
| { \ |
| binder::Status _tmp = (x); \ |
| if (!_tmp.isOk()) return _tmp; \ |
| } |
| |
| // --------------------------------------------------------------------------- |
| |
| sp<Spatializer> Spatializer::create(SpatializerPolicyCallback *callback) { |
| sp<Spatializer> spatializer; |
| |
| sp<EffectsFactoryHalInterface> effectsFactoryHal = EffectsFactoryHalInterface::create(); |
| if (effectsFactoryHal == nullptr) { |
| ALOGW("%s failed to create effect factory interface", __func__); |
| return spatializer; |
| } |
| |
| std::vector<effect_descriptor_t> descriptors; |
| status_t status = |
| effectsFactoryHal->getDescriptors(FX_IID_SPATIALIZER, &descriptors); |
| if (status != NO_ERROR) { |
| ALOGW("%s failed to get spatializer descriptor, error %d", __func__, status); |
| return spatializer; |
| } |
| ALOG_ASSERT(!descriptors.empty(), |
| "%s getDescriptors() returned no error but empty list", __func__); |
| |
| //TODO: get supported spatialization modes from FX engine or descriptor |
| |
| sp<EffectHalInterface> effect; |
| status = effectsFactoryHal->createEffect(&descriptors[0].uuid, AUDIO_SESSION_OUTPUT_STAGE, |
| AUDIO_IO_HANDLE_NONE, AUDIO_PORT_HANDLE_NONE, &effect); |
| ALOGI("%s FX create status %d effect %p", __func__, status, effect.get()); |
| |
| if (status == NO_ERROR && effect != nullptr) { |
| spatializer = new Spatializer(descriptors[0], callback); |
| if (spatializer->loadEngineConfiguration(effect) != NO_ERROR) { |
| spatializer.clear(); |
| } |
| } |
| |
| return spatializer; |
| } |
| |
| Spatializer::Spatializer(effect_descriptor_t engineDescriptor, SpatializerPolicyCallback* callback) |
| : mEngineDescriptor(engineDescriptor), |
| mPolicyCallback(callback) { |
| ALOGV("%s", __func__); |
| } |
| |
| Spatializer::~Spatializer() { |
| ALOGV("%s", __func__); |
| } |
| |
| status_t Spatializer::loadEngineConfiguration(sp<EffectHalInterface> effect) { |
| ALOGV("%s", __func__); |
| |
| std::vector<bool> supportsHeadTracking; |
| status_t status = getHalParameter<false>(effect, SPATIALIZER_PARAM_HEADTRACKING_SUPPORTED, |
| &supportsHeadTracking); |
| if (status != NO_ERROR) { |
| return status; |
| } |
| mSupportsHeadTracking = supportsHeadTracking[0]; |
| |
| status = getHalParameter<true>(effect, SPATIALIZER_PARAM_SUPPORTED_LEVELS, &mLevels); |
| if (status != NO_ERROR) { |
| return status; |
| } |
| status = getHalParameter<true>(effect, SPATIALIZER_PARAM_SUPPORTED_SPATIALIZATION_MODES, |
| &mSpatializationModes); |
| if (status != NO_ERROR) { |
| return status; |
| } |
| status = getHalParameter<true>(effect, SPATIALIZER_PARAM_SUPPORTED_CHANNEL_MASKS, |
| &mChannelMasks); |
| if (status != NO_ERROR) { |
| return status; |
| } |
| return NO_ERROR; |
| } |
| |
| /** Gets the channel mask, sampling rate and format set for the spatializer input. */ |
| audio_config_base_t Spatializer::getAudioInConfig() const { |
| std::lock_guard lock(mLock); |
| audio_config_base_t config = AUDIO_CONFIG_BASE_INITIALIZER; |
| // For now use highest supported channel count |
| uint32_t maxCount = 0; |
| for ( auto mask : mChannelMasks) { |
| if (audio_channel_count_from_out_mask(mask) > maxCount) { |
| config.channel_mask = mask; |
| } |
| } |
| return config; |
| } |
| |
| status_t Spatializer::registerCallback( |
| const sp<media::INativeSpatializerCallback>& callback) { |
| std::lock_guard lock(mLock); |
| if (callback == nullptr) { |
| return BAD_VALUE; |
| } |
| |
| sp<IBinder> binder = IInterface::asBinder(callback); |
| status_t status = binder->linkToDeath(this); |
| if (status == NO_ERROR) { |
| mSpatializerCallback = callback; |
| } |
| ALOGV("%s status %d", __func__, status); |
| return status; |
| } |
| |
| // IBinder::DeathRecipient |
| void Spatializer::binderDied(__unused const wp<IBinder> &who) { |
| { |
| std::lock_guard lock(mLock); |
| mLevel = SpatializationLevel::NONE; |
| mSpatializerCallback.clear(); |
| } |
| ALOGV("%s", __func__); |
| mPolicyCallback->onCheckSpatializer(); |
| } |
| |
| // ISpatializer |
| Status Spatializer::getSupportedLevels(std::vector<SpatializationLevel> *levels) { |
| ALOGV("%s", __func__); |
| if (levels == nullptr) { |
| return binderStatusFromStatusT(BAD_VALUE); |
| } |
| levels->push_back(SpatializationLevel::NONE); |
| levels->insert(levels->end(), mLevels.begin(), mLevels.end()); |
| return Status::ok(); |
| } |
| |
| Status Spatializer::setLevel(SpatializationLevel level) { |
| ALOGV("%s level %d", __func__, (int)level); |
| if (level != SpatializationLevel::NONE |
| && std::find(mLevels.begin(), mLevels.end(), level) == mLevels.end()) { |
| return binderStatusFromStatusT(BAD_VALUE); |
| } |
| sp<media::INativeSpatializerCallback> callback; |
| bool levelChanged = false; |
| { |
| std::lock_guard lock(mLock); |
| levelChanged = mLevel != level; |
| mLevel = level; |
| callback = mSpatializerCallback; |
| |
| if (levelChanged && mEngine != nullptr) { |
| setEffectParameter_l(SPATIALIZER_PARAM_LEVEL, std::vector<SpatializationLevel>{level}); |
| } |
| } |
| |
| if (levelChanged) { |
| mPolicyCallback->onCheckSpatializer(); |
| if (callback != nullptr) { |
| callback->onLevelChanged(level); |
| } |
| } |
| return Status::ok(); |
| } |
| |
| Status Spatializer::getLevel(SpatializationLevel *level) { |
| if (level == nullptr) { |
| return binderStatusFromStatusT(BAD_VALUE); |
| } |
| std::lock_guard lock(mLock); |
| *level = mLevel; |
| ALOGV("%s level %d", __func__, (int)*level); |
| return Status::ok(); |
| } |
| |
| Status Spatializer::getSupportedHeadTrackingModes( |
| std::vector<SpatializerHeadTrackingMode>* modes) { |
| std::lock_guard lock(mLock); |
| ALOGV("%s", __func__); |
| if (modes == nullptr) { |
| return binderStatusFromStatusT(BAD_VALUE); |
| } |
| |
| modes->push_back(SpatializerHeadTrackingMode::DISABLED); |
| if (mSupportsHeadTracking) { |
| if (mHeadSensor != nullptr) { |
| modes->push_back(SpatializerHeadTrackingMode::RELATIVE_WORLD); |
| if (mScreenSensor != nullptr) { |
| modes->push_back(SpatializerHeadTrackingMode::RELATIVE_SCREEN); |
| } |
| } |
| } |
| return Status::ok(); |
| } |
| |
| Status Spatializer::setDesiredHeadTrackingMode(SpatializerHeadTrackingMode mode) { |
| ALOGV("%s mode %d", __func__, (int)mode); |
| |
| if (!mSupportsHeadTracking) { |
| return binderStatusFromStatusT(INVALID_OPERATION); |
| } |
| std::lock_guard lock(mLock); |
| switch (mode) { |
| case SpatializerHeadTrackingMode::OTHER: |
| return binderStatusFromStatusT(BAD_VALUE); |
| case SpatializerHeadTrackingMode::DISABLED: |
| mDesiredHeadTrackingMode = HeadTrackingMode::STATIC; |
| break; |
| case SpatializerHeadTrackingMode::RELATIVE_WORLD: |
| mDesiredHeadTrackingMode = HeadTrackingMode::WORLD_RELATIVE; |
| break; |
| case SpatializerHeadTrackingMode::RELATIVE_SCREEN: |
| mDesiredHeadTrackingMode = HeadTrackingMode::SCREEN_RELATIVE; |
| break; |
| } |
| |
| if (mPoseController != nullptr) { |
| mPoseController->setDesiredMode(mDesiredHeadTrackingMode); |
| } |
| |
| return Status::ok(); |
| } |
| |
| Status Spatializer::getActualHeadTrackingMode(SpatializerHeadTrackingMode *mode) { |
| if (mode == nullptr) { |
| return binderStatusFromStatusT(BAD_VALUE); |
| } |
| std::lock_guard lock(mLock); |
| *mode = mActualHeadTrackingMode; |
| ALOGV("%s mode %d", __func__, (int)*mode); |
| return Status::ok(); |
| } |
| |
| Status Spatializer::recenterHeadTracker() { |
| std::lock_guard lock(mLock); |
| if (mPoseController != nullptr) { |
| mPoseController->recenter(); |
| } |
| return Status::ok(); |
| } |
| |
| Status Spatializer::setGlobalTransform(const std::vector<float>& screenToStage) { |
| ALOGV("%s", __func__); |
| std::optional<Pose3f> maybePose = Pose3f::fromVector(screenToStage); |
| if (!maybePose.has_value()) { |
| ALOGW("Invalid screenToStage vector."); |
| return binderStatusFromStatusT(BAD_VALUE); |
| } |
| std::lock_guard lock(mLock); |
| if (mPoseController != nullptr) { |
| mPoseController->setScreenToStagePose(maybePose.value()); |
| } |
| return Status::ok(); |
| } |
| |
| Status Spatializer::release() { |
| ALOGV("%s", __func__); |
| bool levelChanged = false; |
| { |
| std::lock_guard lock(mLock); |
| if (mSpatializerCallback == nullptr) { |
| return binderStatusFromStatusT(INVALID_OPERATION); |
| } |
| |
| sp<IBinder> binder = IInterface::asBinder(mSpatializerCallback); |
| binder->unlinkToDeath(this); |
| mSpatializerCallback.clear(); |
| |
| levelChanged = mLevel != SpatializationLevel::NONE; |
| mLevel = SpatializationLevel::NONE; |
| } |
| |
| if (levelChanged) { |
| mPolicyCallback->onCheckSpatializer(); |
| } |
| return Status::ok(); |
| } |
| |
| Status Spatializer::setHeadSensor(int sensorHandle) { |
| ALOGV("%s sensorHandle %d", __func__, sensorHandle); |
| std::lock_guard lock(mLock); |
| if (sensorHandle == ASENSOR_INVALID) { |
| mHeadSensor = nullptr; |
| } else { |
| mHeadSensor = VALUE_OR_RETURN_BINDER_STATUS(getSensorFromHandle(sensorHandle)); |
| } |
| if (mPoseController != nullptr) { |
| mPoseController->setHeadSensor(mHeadSensor); |
| } |
| return Status::ok(); |
| } |
| |
| Status Spatializer::setScreenSensor(int sensorHandle) { |
| ALOGV("%s sensorHandle %d", __func__, sensorHandle); |
| std::lock_guard lock(mLock); |
| if (sensorHandle == ASENSOR_INVALID) { |
| mScreenSensor = nullptr; |
| } else { |
| mScreenSensor = VALUE_OR_RETURN_BINDER_STATUS(getSensorFromHandle(sensorHandle)); |
| } |
| if (mPoseController != nullptr) { |
| mPoseController->setScreenSensor(mScreenSensor); |
| } |
| return Status::ok(); |
| } |
| |
| Status Spatializer::setDisplayOrientation(float physicalToLogicalAngle) { |
| ALOGV("%s physicalToLogicalAngle %f", __func__, physicalToLogicalAngle); |
| std::lock_guard lock(mLock); |
| mDisplayOrientation = physicalToLogicalAngle; |
| if (mPoseController != nullptr) { |
| mPoseController->setDisplayOrientation(mDisplayOrientation); |
| } |
| return Status::ok(); |
| } |
| |
| Status Spatializer::setHingeAngle(float hingeAngle) { |
| std::lock_guard lock(mLock); |
| ALOGV("%s hingeAngle %f", __func__, hingeAngle); |
| if (mEngine != nullptr) { |
| setEffectParameter_l(SPATIALIZER_PARAM_HINGE_ANGLE, std::vector<float>{hingeAngle}); |
| } |
| return Status::ok(); |
| } |
| |
| Status Spatializer::getSupportedModes(std::vector<SpatializationMode> *modes) { |
| ALOGV("%s", __func__); |
| if (modes == nullptr) { |
| return binderStatusFromStatusT(BAD_VALUE); |
| } |
| *modes = mSpatializationModes; |
| return Status::ok(); |
| } |
| |
| // SpatializerPoseController::Listener |
| void Spatializer::onHeadToStagePose(const Pose3f& headToStage) { |
| ALOGV("%s", __func__); |
| sp<media::INativeSpatializerCallback> callback; |
| auto vec = headToStage.toVector(); |
| { |
| std::lock_guard lock(mLock); |
| callback = mSpatializerCallback; |
| if (mEngine != nullptr) { |
| setEffectParameter_l(SPATIALIZER_PARAM_HEAD_TO_STAGE, vec); |
| } |
| } |
| |
| if (callback != nullptr) { |
| callback->onHeadToSoundStagePoseUpdated(vec); |
| } |
| } |
| |
| void Spatializer::onActualModeChange(HeadTrackingMode mode) { |
| ALOGV("onActualModeChange(%d)", (int) mode); |
| sp<media::INativeSpatializerCallback> callback; |
| SpatializerHeadTrackingMode spatializerMode; |
| { |
| std::lock_guard lock(mLock); |
| if (!mSupportsHeadTracking) { |
| spatializerMode = SpatializerHeadTrackingMode::DISABLED; |
| } else { |
| switch (mode) { |
| case HeadTrackingMode::STATIC: |
| spatializerMode = SpatializerHeadTrackingMode::DISABLED; |
| break; |
| case HeadTrackingMode::WORLD_RELATIVE: |
| spatializerMode = SpatializerHeadTrackingMode::RELATIVE_WORLD; |
| break; |
| case HeadTrackingMode::SCREEN_RELATIVE: |
| spatializerMode = SpatializerHeadTrackingMode::RELATIVE_SCREEN; |
| break; |
| default: |
| LOG_ALWAYS_FATAL("Unknown mode: %d", mode); |
| } |
| } |
| mActualHeadTrackingMode = spatializerMode; |
| callback = mSpatializerCallback; |
| } |
| if (callback != nullptr) { |
| callback->onHeadTrackingModeChanged(spatializerMode); |
| } |
| } |
| |
| /* static */ |
| ConversionResult<ASensorRef> Spatializer::getSensorFromHandle(int handle) { |
| ASensorManager* sensorManager = |
| ASensorManager_getInstanceForPackage("headtracker"); |
| if (!sensorManager) { |
| ALOGE("Failed to get a sensor manager"); |
| return base::unexpected(NO_INIT); |
| } |
| ASensorList sensorList; |
| int numSensors = ASensorManager_getSensorList(sensorManager, &sensorList); |
| for (int i = 0; i < numSensors; ++i) { |
| if (ASensor_getHandle(sensorList[i]) == handle) { |
| return sensorList[i]; |
| } |
| } |
| return base::unexpected(BAD_VALUE); |
| } |
| |
| status_t Spatializer::attachOutput(audio_io_handle_t output) { |
| std::shared_ptr<SpatializerPoseController> poseController; |
| { |
| std::lock_guard lock(mLock); |
| ALOGV("%s output %d mOutput %d", __func__, (int)output, (int)mOutput); |
| if (mOutput != AUDIO_IO_HANDLE_NONE) { |
| LOG_ALWAYS_FATAL_IF(mEngine == nullptr, "%s output set without FX engine", __func__); |
| // remove FX instance |
| mEngine->setEnabled(false); |
| mEngine.clear(); |
| } |
| // create FX instance on output |
| AttributionSourceState attributionSource = AttributionSourceState(); |
| mEngine = new AudioEffect(attributionSource); |
| mEngine->set(nullptr, &mEngineDescriptor.uuid, 0, Spatializer::engineCallback /* cbf */, |
| this /* user */, AUDIO_SESSION_OUTPUT_STAGE, output, {} /* device */, |
| false /* probe */, true /* notifyFramesProcessed */); |
| status_t status = mEngine->initCheck(); |
| ALOGV("%s mEngine create status %d", __func__, (int)status); |
| if (status != NO_ERROR) { |
| return status; |
| } |
| |
| setEffectParameter_l(SPATIALIZER_PARAM_LEVEL, |
| std::vector<SpatializationLevel>{mLevel}); |
| setEffectParameter_l(SPATIALIZER_PARAM_HEADTRACKING_MODE, |
| std::vector<SpatializerHeadTrackingMode>{mActualHeadTrackingMode}); |
| |
| mEngine->setEnabled(true); |
| mOutput = output; |
| |
| mPoseController = std::make_shared<SpatializerPoseController>( |
| static_cast<SpatializerPoseController::Listener*>(this), 10ms, 50ms); |
| LOG_ALWAYS_FATAL_IF(mPoseController == nullptr, |
| "%s could not allocate pose controller", __func__); |
| |
| mPoseController->setDesiredMode(mDesiredHeadTrackingMode); |
| mPoseController->setHeadSensor(mHeadSensor); |
| mPoseController->setScreenSensor(mScreenSensor); |
| mPoseController->setDisplayOrientation(mDisplayOrientation); |
| poseController = mPoseController; |
| } |
| poseController->waitUntilCalculated(); |
| return NO_ERROR; |
| } |
| |
| audio_io_handle_t Spatializer::detachOutput() { |
| std::lock_guard lock(mLock); |
| ALOGV("%s mOutput %d", __func__, (int)mOutput); |
| audio_io_handle_t output = AUDIO_IO_HANDLE_NONE; |
| if (mOutput == AUDIO_IO_HANDLE_NONE) { |
| return output; |
| } |
| // remove FX instance |
| mEngine->setEnabled(false); |
| mEngine.clear(); |
| output = mOutput; |
| mOutput = AUDIO_IO_HANDLE_NONE; |
| mPoseController.reset(); |
| return output; |
| } |
| |
| void Spatializer::calculateHeadPose() { |
| ALOGV("%s", __func__); |
| std::lock_guard lock(mLock); |
| if (mPoseController != nullptr) { |
| mPoseController->calculateAsync(); |
| } |
| } |
| |
| void Spatializer::engineCallback(int32_t event, void *user, void *info) { |
| if (user == nullptr) { |
| return; |
| } |
| Spatializer* const me = reinterpret_cast<Spatializer *>(user); |
| switch (event) { |
| case AudioEffect::EVENT_FRAMES_PROCESSED: { |
| int frames = info == nullptr ? 0 : *(int*)info; |
| ALOGD("%s frames processed %d for me %p", __func__, frames, me); |
| if (frames > 0) { |
| me->calculateHeadPose(); |
| } |
| } break; |
| default: |
| ALOGD("%s event %d", __func__, event); |
| break; |
| } |
| } |
| |
| // --------------------------------------------------------------------------- |
| |
| Spatializer::EffectClient::EffectClient(const sp<media::IEffectClient>& effectClient, |
| Spatializer& parent) |
| : BnEffect(), |
| mEffectClient(effectClient), mParent(parent) { |
| } |
| |
| Spatializer::EffectClient::~EffectClient() { |
| } |
| |
| // IEffect |
| |
| #define RETURN(code) \ |
| *_aidl_return = (code); \ |
| return Status::ok(); |
| |
| // Write a POD value into a vector of bytes (clears the previous buffer |
| // content). |
| template<typename T> |
| void writeToBuffer(const T& value, std::vector<uint8_t>* buffer) { |
| buffer->clear(); |
| appendToBuffer(value, buffer); |
| } |
| |
| Status Spatializer::EffectClient::enable(int32_t* _aidl_return) { |
| RETURN(OK); |
| } |
| |
| Status Spatializer::EffectClient::disable(int32_t* _aidl_return) { |
| RETURN(OK); |
| } |
| |
| Status Spatializer::EffectClient::command(int32_t cmdCode, |
| const std::vector<uint8_t>& cmdData __unused, |
| int32_t maxResponseSize __unused, |
| std::vector<uint8_t>* response __unused, |
| int32_t* _aidl_return) { |
| |
| // reject commands reserved for internal use by audio framework if coming from outside |
| // of audioserver |
| switch(cmdCode) { |
| case EFFECT_CMD_ENABLE: |
| case EFFECT_CMD_DISABLE: |
| case EFFECT_CMD_SET_PARAM_DEFERRED: |
| case EFFECT_CMD_SET_PARAM_COMMIT: |
| RETURN(BAD_VALUE); |
| case EFFECT_CMD_SET_PARAM: |
| case EFFECT_CMD_GET_PARAM: |
| break; |
| default: |
| if (cmdCode >= EFFECT_CMD_FIRST_PROPRIETARY) { |
| break; |
| } |
| android_errorWriteLog(0x534e4554, "62019992"); |
| RETURN(BAD_VALUE); |
| } |
| (void)mParent; |
| RETURN(OK); |
| } |
| |
| Status Spatializer::EffectClient::disconnect() { |
| mDisconnected = true; |
| return Status::ok(); |
| } |
| |
| Status Spatializer::EffectClient::getCblk(media::SharedFileRegion* _aidl_return) { |
| LOG_ALWAYS_FATAL_IF(!convertIMemoryToSharedFileRegion(mCblkMemory, _aidl_return)); |
| return Status::ok(); |
| } |
| |
| } // namespace android |