| Eric Laurent | 6d60701 | 2021-07-05 11:54:40 +0200 | [diff] [blame] | 1 | /* | 
 | 2 |  * Copyright (C) 2021 The Android Open Source Project | 
 | 3 |  * | 
 | 4 |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
 | 5 |  * you may not use this file except in compliance with the License. | 
 | 6 |  * You may obtain a copy of the License at | 
 | 7 |  * | 
 | 8 |  *      http://www.apache.org/licenses/LICENSE-2.0 | 
 | 9 |  * | 
 | 10 |  * Unless required by applicable law or agreed to in writing, software | 
 | 11 |  * distributed under the License is distributed on an "AS IS" BASIS, | 
 | 12 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | 13 |  * See the License for the specific language governing permissions and | 
 | 14 |  * limitations under the License. | 
 | 15 |  */ | 
 | 16 |  | 
 | 17 | #ifndef ANDROID_MEDIA_SPATIALIZER_H | 
 | 18 | #define ANDROID_MEDIA_SPATIALIZER_H | 
 | 19 |  | 
 | 20 | #include <android/media/BnEffect.h> | 
 | 21 | #include <android/media/BnSpatializer.h> | 
| Eric Laurent | 6d60701 | 2021-07-05 11:54:40 +0200 | [diff] [blame] | 22 | #include <android/media/SpatializationLevel.h> | 
| Eric Laurent | 2be8b29 | 2021-08-23 09:44:33 -0700 | [diff] [blame] | 23 | #include <android/media/SpatializationMode.h> | 
 | 24 | #include <android/media/SpatializerHeadTrackingMode.h> | 
 | 25 | #include <android/sensor.h> | 
 | 26 | #include <media/audiohal/EffectHalInterface.h> | 
| Eric Laurent | 8a4259f | 2021-09-14 16:04:00 +0200 | [diff] [blame] | 27 | #include <media/stagefright/foundation/ALooper.h> | 
| Eric Laurent | 6d60701 | 2021-07-05 11:54:40 +0200 | [diff] [blame] | 28 | #include <media/AudioEffect.h> | 
| Eric Laurent | 1c5e2e3 | 2021-08-18 18:50:28 +0200 | [diff] [blame] | 29 | #include <system/audio_effects/effect_spatializer.h> | 
| Eric Laurent | 6d60701 | 2021-07-05 11:54:40 +0200 | [diff] [blame] | 30 |  | 
| Eric Laurent | 2be8b29 | 2021-08-23 09:44:33 -0700 | [diff] [blame] | 31 | #include "SpatializerPoseController.h" | 
| Eric Laurent | 6d60701 | 2021-07-05 11:54:40 +0200 | [diff] [blame] | 32 |  | 
 | 33 | namespace android { | 
 | 34 |  | 
 | 35 |  | 
 | 36 | // ---------------------------------------------------------------------------- | 
 | 37 |  | 
 | 38 | /** | 
 | 39 |  * A callback interface from the Spatializer object or its parent AudioPolicyService. | 
 | 40 |  * This is implemented by the audio policy service hosting the Spatializer to perform | 
 | 41 |  * actions needed when a state change inside the Spatializer requires some audio system | 
 | 42 |  * changes that cannot be performed by the Spatializer. For instance opening or closing a | 
 | 43 |  * spatializer output stream when the spatializer is enabled or disabled | 
 | 44 |  */ | 
 | 45 | class SpatializerPolicyCallback { | 
 | 46 | public: | 
 | 47 |     /** Called when a stage change occurs that requires the parent audio policy service to take | 
 | 48 |      * some action. | 
 | 49 |      */ | 
 | 50 |     virtual void onCheckSpatializer() = 0; | 
 | 51 |  | 
 | 52 |     virtual ~SpatializerPolicyCallback() = default; | 
 | 53 | }; | 
 | 54 | /** | 
 | 55 |  * The Spatializer class implements all functional controlling the multichannel spatializer | 
 | 56 |  * with head tracking implementation in the native audio service: audio policy and audio flinger. | 
 | 57 |  * It presents an AIDL interface available to the java audio service to discover the availability | 
 | 58 |  * of the feature and options, control its state and register an active head tracking sensor. | 
 | 59 |  * It maintains the current state of the platform spatializer and applies the stored parameters | 
 | 60 |  * when the spatializer engine is created and enabled. | 
 | 61 |  * Based on the requested spatializer level, it will request the creation of a specialized output | 
 | 62 |  * mixer to the audio policy service which will in turn notify the Spatializer of the output | 
 | 63 |  * stream on which a spatializer engine should be created, configured and enabled. | 
 | 64 |  * The spatializer also hosts the head tracking management logic. This logic receives the | 
 | 65 |  * desired head tracking mode and selected head tracking sensor, registers a sensor event listener | 
 | 66 |  * and derives the compounded head pose information to the spatializer engine. | 
 | 67 |  * | 
 | 68 |  * Workflow: | 
 | 69 |  * - Initialization: when the audio policy service starts, it checks if a spatializer effect | 
 | 70 |  * engine exists and if the audio policy manager reports a dedicated spatializer output profile. | 
 | 71 |  * If both conditions are met, a Spatializer object is created | 
 | 72 |  * - Capabilities discovery: AudioService will call AudioSystem::canBeSpatialized() and if true, | 
 | 73 |  * acquire an ISpatializer interface with AudioSystem::getSpatializer(). This interface | 
 | 74 |  * will be used to query the implementation capabilities and configure the spatializer. | 
 | 75 |  * - Enabling: when ISpatializer::setLevel() sets a level different from NONE the spatializer | 
 | 76 |  * is considered enabled. The audio policy callback onCheckSpatializer() is called. This | 
 | 77 |  * triggers a request to audio policy manager to open a spatialization output stream and a | 
 | 78 |  * spatializer mixer is created in audio flinger. When an output is returned by audio policy | 
 | 79 |  * manager, Spatializer::attachOutput() is called which creates and enables the spatializer | 
 | 80 |  * stage engine on the specified output. | 
 | 81 |  * - Disabling: when the spatialization level is set to NONE, the spatializer is considered | 
 | 82 |  * disabled. The audio policy callback onCheckSpatializer() is called. This triggers a call | 
 | 83 |  * to Spatializer::detachOutput() and the spatializer engine is released. Then a request is | 
 | 84 |  * made to audio policy manager to release and close the spatializer output stream and the | 
 | 85 |  * spatializer mixer thread is destroyed. | 
 | 86 |  */ | 
| Eric Laurent | 2be8b29 | 2021-08-23 09:44:33 -0700 | [diff] [blame] | 87 | class Spatializer : public media::BnSpatializer, | 
 | 88 |                     public IBinder::DeathRecipient, | 
 | 89 |                     private SpatializerPoseController::Listener { | 
 | 90 |   public: | 
| Eric Laurent | 6d60701 | 2021-07-05 11:54:40 +0200 | [diff] [blame] | 91 |     static sp<Spatializer> create(SpatializerPolicyCallback *callback); | 
 | 92 |  | 
 | 93 |            ~Spatializer() override; | 
 | 94 |  | 
| Eric Laurent | 8a4259f | 2021-09-14 16:04:00 +0200 | [diff] [blame] | 95 |     /** RefBase */ | 
 | 96 |     void onFirstRef(); | 
 | 97 |  | 
| Eric Laurent | 6d60701 | 2021-07-05 11:54:40 +0200 | [diff] [blame] | 98 |     /** ISpatializer, see ISpatializer.aidl */ | 
 | 99 |     binder::Status release() override; | 
 | 100 |     binder::Status getSupportedLevels(std::vector<media::SpatializationLevel>* levels) override; | 
 | 101 |     binder::Status setLevel(media::SpatializationLevel level) override; | 
 | 102 |     binder::Status getLevel(media::SpatializationLevel *level) override; | 
 | 103 |     binder::Status getSupportedHeadTrackingModes( | 
| Ytai Ben-Tsvi | a16a9df | 2021-08-05 08:57:06 -0700 | [diff] [blame] | 104 |             std::vector<media::SpatializerHeadTrackingMode>* modes) override; | 
 | 105 |     binder::Status setDesiredHeadTrackingMode( | 
 | 106 |             media::SpatializerHeadTrackingMode mode) override; | 
 | 107 |     binder::Status getActualHeadTrackingMode( | 
 | 108 |             media::SpatializerHeadTrackingMode* mode) override; | 
 | 109 |     binder::Status recenterHeadTracker() override; | 
| Eric Laurent | 6d60701 | 2021-07-05 11:54:40 +0200 | [diff] [blame] | 110 |     binder::Status setGlobalTransform(const std::vector<float>& screenToStage) override; | 
| Eric Laurent | 2be8b29 | 2021-08-23 09:44:33 -0700 | [diff] [blame] | 111 |     binder::Status setHeadSensor(int sensorHandle) override; | 
 | 112 |     binder::Status setScreenSensor(int sensorHandle) override; | 
 | 113 |     binder::Status setDisplayOrientation(float physicalToLogicalAngle) override; | 
 | 114 |     binder::Status setHingeAngle(float hingeAngle) override; | 
 | 115 |     binder::Status getSupportedModes(std::vector<media::SpatializationMode>* modes) override; | 
| Eric Laurent | 67816e3 | 2021-09-16 15:18:40 +0200 | [diff] [blame^] | 116 |     binder::Status registerHeadTrackingCallback( | 
 | 117 |         const sp<media::ISpatializerHeadTrackingCallback>& callback) override; | 
| Eric Laurent | 6d60701 | 2021-07-05 11:54:40 +0200 | [diff] [blame] | 118 |  | 
 | 119 |     /** IBinder::DeathRecipient. Listen to the death of the INativeSpatializerCallback. */ | 
 | 120 |     virtual void binderDied(const wp<IBinder>& who); | 
 | 121 |  | 
 | 122 |     /** Registers a INativeSpatializerCallback when a client is attached to this Spatializer | 
 | 123 |      * by audio policy service. | 
 | 124 |      */ | 
 | 125 |     status_t registerCallback(const sp<media::INativeSpatializerCallback>& callback); | 
 | 126 |  | 
| Eric Laurent | 2be8b29 | 2021-08-23 09:44:33 -0700 | [diff] [blame] | 127 |     status_t loadEngineConfiguration(sp<EffectHalInterface> effect); | 
 | 128 |  | 
| Eric Laurent | 6d60701 | 2021-07-05 11:54:40 +0200 | [diff] [blame] | 129 |     /** Level getter for use by local classes. */ | 
| Eric Laurent | 2be8b29 | 2021-08-23 09:44:33 -0700 | [diff] [blame] | 130 |     media::SpatializationLevel getLevel() const { std::lock_guard lock(mLock); return mLevel; } | 
| Eric Laurent | 6d60701 | 2021-07-05 11:54:40 +0200 | [diff] [blame] | 131 |  | 
 | 132 |     /** Called by audio policy service when the special output mixer dedicated to spatialization | 
 | 133 |      * is opened and the spatializer engine must be created. | 
 | 134 |      */ | 
 | 135 |     status_t attachOutput(audio_io_handle_t output); | 
 | 136 |     /** Called by audio policy service when the special output mixer dedicated to spatialization | 
 | 137 |      * is closed and the spatializer engine must be release. | 
 | 138 |      */ | 
 | 139 |     audio_io_handle_t detachOutput(); | 
 | 140 |     /** Returns the output stream the spatializer is attached to. */ | 
| Eric Laurent | 2be8b29 | 2021-08-23 09:44:33 -0700 | [diff] [blame] | 141 |     audio_io_handle_t getOutput() const { std::lock_guard lock(mLock); return mOutput; } | 
| Eric Laurent | 6d60701 | 2021-07-05 11:54:40 +0200 | [diff] [blame] | 142 |  | 
 | 143 |     /** Gets the channel mask, sampling rate and format set for the spatializer input. */ | 
| Eric Laurent | 2be8b29 | 2021-08-23 09:44:33 -0700 | [diff] [blame] | 144 |     audio_config_base_t getAudioInConfig() const; | 
| Eric Laurent | 6d60701 | 2021-07-05 11:54:40 +0200 | [diff] [blame] | 145 |  | 
| Eric Laurent | 8a4259f | 2021-09-14 16:04:00 +0200 | [diff] [blame] | 146 |     void calculateHeadPose(); | 
 | 147 |  | 
| Eric Laurent | 6d60701 | 2021-07-05 11:54:40 +0200 | [diff] [blame] | 148 |     /** An implementation of an IEffect interface that can be used to pass advanced parameters to | 
 | 149 |      * the spatializer engine. All APis are noop (i.e. the interface cannot be used to control | 
 | 150 |      * the effect) except for passing parameters via the command() API. */ | 
 | 151 |     class EffectClient: public android::media::BnEffect { | 
 | 152 |     public: | 
 | 153 |  | 
 | 154 |         EffectClient(const sp<media::IEffectClient>& effectClient, | 
 | 155 |                      Spatializer& parent); | 
 | 156 |         virtual ~EffectClient(); | 
 | 157 |  | 
 | 158 |         // IEffect | 
 | 159 |         android::binder::Status enable(int32_t* _aidl_return) override; | 
 | 160 |         android::binder::Status disable(int32_t* _aidl_return) override; | 
 | 161 |         android::binder::Status command(int32_t cmdCode, | 
 | 162 |                                         const std::vector<uint8_t>& cmdData, | 
 | 163 |                                         int32_t maxResponseSize, | 
 | 164 |                                         std::vector<uint8_t>* response, | 
 | 165 |                                         int32_t* _aidl_return) override; | 
 | 166 |         android::binder::Status disconnect() override; | 
 | 167 |         android::binder::Status getCblk(media::SharedFileRegion* _aidl_return) override; | 
 | 168 |  | 
 | 169 |     private: | 
 | 170 |         const sp<media::IEffectClient> mEffectClient; | 
 | 171 |         sp<IMemory> mCblkMemory; | 
 | 172 |         const Spatializer& mParent; | 
 | 173 |         bool mDisconnected = false; | 
 | 174 |     }; | 
 | 175 |  | 
 | 176 | private: | 
| Eric Laurent | 6d60701 | 2021-07-05 11:54:40 +0200 | [diff] [blame] | 177 |     Spatializer(effect_descriptor_t engineDescriptor, | 
 | 178 |                      SpatializerPolicyCallback *callback); | 
 | 179 |  | 
| Eric Laurent | 6d60701 | 2021-07-05 11:54:40 +0200 | [diff] [blame] | 180 |     static void engineCallback(int32_t event, void* user, void *info); | 
 | 181 |  | 
| Eric Laurent | 2be8b29 | 2021-08-23 09:44:33 -0700 | [diff] [blame] | 182 |     // From VirtualizerStageController::Listener | 
 | 183 |     void onHeadToStagePose(const media::Pose3f& headToStage) override; | 
 | 184 |     void onActualModeChange(media::HeadTrackingMode mode) override; | 
 | 185 |  | 
| Eric Laurent | 8a4259f | 2021-09-14 16:04:00 +0200 | [diff] [blame] | 186 |     void onHeadToStagePoseMsg(const std::vector<float>& headToStage); | 
 | 187 |     void onActualModeChangeMsg(media::HeadTrackingMode mode); | 
 | 188 |  | 
| Eric Laurent | 2be8b29 | 2021-08-23 09:44:33 -0700 | [diff] [blame] | 189 |  | 
 | 190 |     static ConversionResult<ASensorRef> getSensorFromHandle(int handle); | 
 | 191 |  | 
 | 192 |     static constexpr int kMaxEffectParamValues = 10; | 
 | 193 |     /** | 
 | 194 |      * Get a parameter from spatializer engine by calling the effect HAL command method directly. | 
 | 195 |      * To be used when the engine instance mEngine is not yet created in the effect framework. | 
 | 196 |      * When MULTI_VALUES is false, the expected reply is only one value of type T. | 
 | 197 |      * When MULTI_VALUES is true, the expected reply is made of a number (of type T) indicating | 
 | 198 |      * how many values are returned, followed by this number for values of type T. | 
 | 199 |      */ | 
 | 200 |     template<bool MULTI_VALUES, typename T> | 
 | 201 |     status_t getHalParameter(sp<EffectHalInterface> effect, uint32_t type, | 
 | 202 |                                           std::vector<T> *values) { | 
 | 203 |         static_assert(sizeof(T) <= sizeof(uint32_t), "The size of T must less than 32 bits"); | 
 | 204 |  | 
 | 205 |         uint32_t cmd[sizeof(effect_param_t) / sizeof(uint32_t) + 1]; | 
 | 206 |         uint32_t reply[sizeof(effect_param_t) / sizeof(uint32_t) + 2 + kMaxEffectParamValues]; | 
 | 207 |  | 
 | 208 |         effect_param_t *p = (effect_param_t *)cmd; | 
 | 209 |         p->psize = sizeof(uint32_t); | 
 | 210 |         if (MULTI_VALUES) { | 
 | 211 |             p->vsize = (kMaxEffectParamValues + 1) * sizeof(T); | 
 | 212 |         } else { | 
 | 213 |             p->vsize = sizeof(T); | 
 | 214 |         } | 
 | 215 |         *(uint32_t *)p->data = type; | 
 | 216 |         uint32_t replySize = sizeof(effect_param_t) + p->psize + p->vsize; | 
 | 217 |  | 
 | 218 |         status_t status = effect->command(EFFECT_CMD_GET_PARAM, | 
 | 219 |                                           sizeof(effect_param_t) + sizeof(uint32_t), cmd, | 
 | 220 |                                           &replySize, reply); | 
 | 221 |         if (status != NO_ERROR) { | 
 | 222 |             return status; | 
 | 223 |         } | 
 | 224 |         if (p->status != NO_ERROR) { | 
 | 225 |             return p->status; | 
 | 226 |         } | 
 | 227 |         if (replySize < | 
 | 228 |                 sizeof(effect_param_t) + sizeof(uint32_t) + (MULTI_VALUES ? 2 : 1) * sizeof(T)) { | 
 | 229 |             return BAD_VALUE; | 
 | 230 |         } | 
 | 231 |  | 
 | 232 |         T *params = (T *)((uint8_t *)reply + sizeof(effect_param_t) + sizeof(uint32_t)); | 
 | 233 |         int numParams = 1; | 
 | 234 |         if (MULTI_VALUES) { | 
 | 235 |             numParams = (int)*params++; | 
 | 236 |         } | 
 | 237 |         if (numParams > kMaxEffectParamValues) { | 
 | 238 |             return BAD_VALUE; | 
 | 239 |         } | 
 | 240 |         std::copy(¶ms[0], ¶ms[numParams], back_inserter(*values)); | 
 | 241 |         return NO_ERROR; | 
 | 242 |     } | 
 | 243 |  | 
 | 244 |     /** | 
 | 245 |      * Set a parameter to spatializer engine by calling setParameter on mEngine AudioEffect object. | 
 | 246 |      * It is possible to pass more than one value of type T according to the parameter type | 
 | 247 |      *  according to values vector size. | 
 | 248 |      */ | 
 | 249 |     template<typename T> | 
 | 250 |     status_t setEffectParameter_l(uint32_t type, const std::vector<T>& values) REQUIRES(mLock) { | 
 | 251 |         static_assert(sizeof(T) <= sizeof(uint32_t), "The size of T must less than 32 bits"); | 
 | 252 |  | 
 | 253 |         uint32_t cmd[sizeof(effect_param_t) / sizeof(uint32_t) + 1 + values.size()]; | 
 | 254 |         effect_param_t *p = (effect_param_t *)cmd; | 
 | 255 |         p->psize = sizeof(uint32_t); | 
 | 256 |         p->vsize = sizeof(T) * values.size(); | 
 | 257 |         *(uint32_t *)p->data = type; | 
 | 258 |         memcpy((uint32_t *)p->data + 1, values.data(), sizeof(T) * values.size()); | 
 | 259 |  | 
 | 260 |         return mEngine->setParameter(p); | 
 | 261 |     } | 
 | 262 |  | 
| Eric Laurent | 8a4259f | 2021-09-14 16:04:00 +0200 | [diff] [blame] | 263 |     void postFramesProcessedMsg(int frames); | 
 | 264 |  | 
| Eric Laurent | 6d60701 | 2021-07-05 11:54:40 +0200 | [diff] [blame] | 265 |     /** Effect engine descriptor */ | 
 | 266 |     const effect_descriptor_t mEngineDescriptor; | 
 | 267 |     /** Callback interface to parent audio policy service */ | 
 | 268 |     SpatializerPolicyCallback* mPolicyCallback; | 
 | 269 |  | 
 | 270 |     /** Mutex protecting internal state */ | 
| Eric Laurent | 2be8b29 | 2021-08-23 09:44:33 -0700 | [diff] [blame] | 271 |     mutable std::mutex mLock; | 
| Eric Laurent | 6d60701 | 2021-07-05 11:54:40 +0200 | [diff] [blame] | 272 |  | 
 | 273 |     /** Client AudioEffect for the engine */ | 
 | 274 |     sp<AudioEffect> mEngine GUARDED_BY(mLock); | 
 | 275 |     /** Output stream the spatializer mixer thread is attached to */ | 
 | 276 |     audio_io_handle_t mOutput GUARDED_BY(mLock) = AUDIO_IO_HANDLE_NONE; | 
| Eric Laurent | 6d60701 | 2021-07-05 11:54:40 +0200 | [diff] [blame] | 277 |  | 
 | 278 |     /** Callback interface to the client (AudioService) controlling this`Spatializer */ | 
 | 279 |     sp<media::INativeSpatializerCallback> mSpatializerCallback GUARDED_BY(mLock); | 
 | 280 |  | 
| Eric Laurent | 67816e3 | 2021-09-16 15:18:40 +0200 | [diff] [blame^] | 281 |     /** Callback interface for head tracking */ | 
 | 282 |     sp<media::ISpatializerHeadTrackingCallback> mHeadTrackingCallback GUARDED_BY(mLock); | 
 | 283 |  | 
| Eric Laurent | 6d60701 | 2021-07-05 11:54:40 +0200 | [diff] [blame] | 284 |     /** Requested spatialization level */ | 
 | 285 |     media::SpatializationLevel mLevel GUARDED_BY(mLock) = media::SpatializationLevel::NONE; | 
| Eric Laurent | 6d60701 | 2021-07-05 11:54:40 +0200 | [diff] [blame] | 286 |  | 
 | 287 |     /** Extended IEffect interface is one has been created */ | 
 | 288 |     sp<EffectClient> mEffectClient GUARDED_BY(mLock); | 
| Eric Laurent | 2be8b29 | 2021-08-23 09:44:33 -0700 | [diff] [blame] | 289 |  | 
 | 290 |     /** Control logic for head-tracking, etc. */ | 
 | 291 |     std::shared_ptr<SpatializerPoseController> mPoseController GUARDED_BY(mLock); | 
 | 292 |  | 
 | 293 |     /** Last requested head tracking mode */ | 
 | 294 |     media::HeadTrackingMode mDesiredHeadTrackingMode GUARDED_BY(mLock) | 
 | 295 |             = media::HeadTrackingMode::STATIC; | 
 | 296 |  | 
 | 297 |     /** Last-reported actual head-tracking mode. */ | 
 | 298 |     media::SpatializerHeadTrackingMode mActualHeadTrackingMode GUARDED_BY(mLock) | 
 | 299 |             = media::SpatializerHeadTrackingMode::DISABLED; | 
 | 300 |  | 
 | 301 |     /** Selected Head pose sensor */ | 
 | 302 |     ASensorRef mHeadSensor GUARDED_BY(mLock) = nullptr; | 
 | 303 |  | 
 | 304 |     /** Selected Screen pose sensor */ | 
 | 305 |     ASensorRef mScreenSensor GUARDED_BY(mLock) = nullptr; | 
 | 306 |  | 
 | 307 |     /** Last display orientation received */ | 
 | 308 |     static constexpr float kDisplayOrientationInvalid = 1000; | 
 | 309 |     float mDisplayOrientation GUARDED_BY(mLock) = kDisplayOrientationInvalid; | 
 | 310 |  | 
 | 311 |     std::vector<media::SpatializationLevel> mLevels; | 
 | 312 |     std::vector<media::SpatializationMode> mSpatializationModes; | 
 | 313 |     std::vector<audio_channel_mask_t> mChannelMasks; | 
 | 314 |     bool mSupportsHeadTracking; | 
| Eric Laurent | 8a4259f | 2021-09-14 16:04:00 +0200 | [diff] [blame] | 315 |  | 
 | 316 |     // Looper thread for mEngine callbacks | 
 | 317 |     class EngineCallbackHandler; | 
 | 318 |  | 
 | 319 |     sp<ALooper> mLooper; | 
 | 320 |     sp<EngineCallbackHandler> mHandler; | 
 | 321 |  | 
 | 322 |     static const std::vector<const char *> sHeadPoseKeys; | 
| Eric Laurent | 6d60701 | 2021-07-05 11:54:40 +0200 | [diff] [blame] | 323 | }; | 
 | 324 |  | 
 | 325 |  | 
 | 326 | }; // namespace android | 
 | 327 |  | 
 | 328 | #endif // ANDROID_MEDIA_SPATIALIZER_H |