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 package com.android.server.audio; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.Context; 22 import android.hardware.Sensor; 23 import android.hardware.SensorManager; 24 import android.media.AudioAttributes; 25 import android.media.AudioDeviceAttributes; 26 import android.media.AudioFormat; 27 import android.media.AudioSystem; 28 import android.media.INativeSpatializerCallback; 29 import android.media.ISpatializer; 30 import android.media.ISpatializerCallback; 31 import android.media.ISpatializerHeadToSoundStagePoseCallback; 32 import android.media.ISpatializerHeadTrackingCallback; 33 import android.media.ISpatializerHeadTrackingModeCallback; 34 import android.media.ISpatializerOutputCallback; 35 import android.media.SpatializationLevel; 36 import android.media.Spatializer; 37 import android.media.SpatializerHeadTrackingMode; 38 import android.os.RemoteCallbackList; 39 import android.os.RemoteException; 40 import android.util.Log; 41 42 import java.util.ArrayList; 43 import java.util.List; 44 import java.util.Locale; 45 46 /** 47 * A helper class to manage Spatializer related functionality 48 */ 49 public class SpatializerHelper { 50 51 private static final String TAG = "AS.SpatializerHelper"; 52 private static final boolean DEBUG = true; 53 private static final boolean DEBUG_MORE = false; 54 logd(String s)55 private static void logd(String s) { 56 if (DEBUG) { 57 Log.i(TAG, s); 58 } 59 } 60 61 private final @NonNull AudioSystemAdapter mASA; 62 private final @NonNull AudioService mAudioService; 63 private @Nullable SensorManager mSensorManager; 64 65 //------------------------------------------------------------ 66 /** head tracker sensor name */ 67 // TODO: replace with generic head tracker sensor name. 68 // the current implementation refers to the "google" namespace but will be replaced 69 // by an android name at the next API level revision, it is not Google-specific. 70 // Also see "TODO-HT" in onInitSensors() method 71 private static final String HEADTRACKER_SENSOR = 72 "com.google.hardware.sensor.hid_dynamic.headtracker"; 73 74 // Spatializer state machine 75 private static final int STATE_UNINITIALIZED = 0; 76 private static final int STATE_NOT_SUPPORTED = 1; 77 private static final int STATE_DISABLED_UNAVAILABLE = 3; 78 private static final int STATE_ENABLED_UNAVAILABLE = 4; 79 private static final int STATE_ENABLED_AVAILABLE = 5; 80 private static final int STATE_DISABLED_AVAILABLE = 6; 81 private int mState = STATE_UNINITIALIZED; 82 83 /** current level as reported by native Spatializer in callback */ 84 private int mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; 85 private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; 86 private int mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; 87 private int mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; 88 private int mSpatOutput = 0; 89 private @Nullable ISpatializer mSpat; 90 private @Nullable SpatializerCallback mSpatCallback; 91 private @Nullable SpatializerHeadTrackingCallback mSpatHeadTrackingCallback; 92 private @Nullable HelperDynamicSensorCallback mDynSensorCallback; 93 94 // default attributes and format that determine basic availability of spatialization 95 private static final AudioAttributes DEFAULT_ATTRIBUTES = new AudioAttributes.Builder() 96 .setUsage(AudioAttributes.USAGE_MEDIA) 97 .build(); 98 private static final AudioFormat DEFAULT_FORMAT = new AudioFormat.Builder() 99 .setEncoding(AudioFormat.ENCODING_PCM_16BIT) 100 .setSampleRate(48000) 101 .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1) 102 .build(); 103 // device array to store the routing for the default attributes and format, size 1 because 104 // media is never expected to be duplicated 105 private static final AudioDeviceAttributes[] ROUTING_DEVICES = new AudioDeviceAttributes[1]; 106 107 //--------------------------------------------------------------- 108 // audio device compatibility / enabled 109 110 private final ArrayList<AudioDeviceAttributes> mCompatibleAudioDevices = new ArrayList<>(0); 111 112 //------------------------------------------------------ 113 // initialization SpatializerHelper(@onNull AudioService mother, @NonNull AudioSystemAdapter asa)114 SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa) { 115 mAudioService = mother; 116 mASA = asa; 117 } 118 init(boolean effectExpected)119 synchronized void init(boolean effectExpected) { 120 Log.i(TAG, "Initializing"); 121 if (!effectExpected) { 122 Log.i(TAG, "Setting state to STATE_NOT_SUPPORTED due to effect not expected"); 123 mState = STATE_NOT_SUPPORTED; 124 return; 125 } 126 if (mState != STATE_UNINITIALIZED) { 127 throw new IllegalStateException(("init() called in state:" + mState)); 128 } 129 // is there a spatializer? 130 mSpatCallback = new SpatializerCallback(); 131 final ISpatializer spat = AudioSystem.getSpatializer(mSpatCallback); 132 if (spat == null) { 133 Log.i(TAG, "init(): No Spatializer found"); 134 mState = STATE_NOT_SUPPORTED; 135 return; 136 } 137 // capabilities of spatializer? 138 try { 139 byte[] levels = spat.getSupportedLevels(); 140 if (levels == null 141 || levels.length == 0 142 || (levels.length == 1 143 && levels[0] == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE)) { 144 Log.e(TAG, "Spatializer is useless"); 145 mState = STATE_NOT_SUPPORTED; 146 return; 147 } 148 for (byte level : levels) { 149 logd("found support for level: " + level); 150 if (level == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL) { 151 logd("Setting capable level to LEVEL_MULTICHANNEL"); 152 mCapableSpatLevel = level; 153 break; 154 } 155 } 156 } catch (RemoteException e) { 157 /* capable level remains at NONE*/ 158 } finally { 159 if (spat != null) { 160 try { 161 spat.release(); 162 } catch (RemoteException e) { /* capable level remains at NONE*/ } 163 } 164 } 165 if (mCapableSpatLevel == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) { 166 mState = STATE_NOT_SUPPORTED; 167 return; 168 } 169 mState = STATE_DISABLED_UNAVAILABLE; 170 // note at this point mSpat is still not instantiated 171 } 172 173 /** 174 * Like init() but resets the state and spatializer levels 175 * @param featureEnabled 176 */ reset(boolean featureEnabled)177 synchronized void reset(boolean featureEnabled) { 178 Log.i(TAG, "Resetting"); 179 mState = STATE_UNINITIALIZED; 180 mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; 181 mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; 182 mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; 183 init(true); 184 setFeatureEnabled(featureEnabled); 185 } 186 187 //------------------------------------------------------ 188 // routing monitoring onRoutingUpdated()189 void onRoutingUpdated() { 190 switch (mState) { 191 case STATE_UNINITIALIZED: 192 case STATE_NOT_SUPPORTED: 193 return; 194 case STATE_DISABLED_UNAVAILABLE: 195 case STATE_ENABLED_UNAVAILABLE: 196 case STATE_ENABLED_AVAILABLE: 197 case STATE_DISABLED_AVAILABLE: 198 break; 199 } 200 mASA.getDevicesForAttributes(DEFAULT_ATTRIBUTES).toArray(ROUTING_DEVICES); 201 final boolean able = 202 AudioSystem.canBeSpatialized(DEFAULT_ATTRIBUTES, DEFAULT_FORMAT, ROUTING_DEVICES); 203 logd("onRoutingUpdated: can spatialize media 5.1:" + able 204 + " on device:" + ROUTING_DEVICES[0]); 205 setDispatchAvailableState(able); 206 } 207 208 //------------------------------------------------------ 209 // spatializer callback from native 210 private final class SpatializerCallback extends INativeSpatializerCallback.Stub { 211 onLevelChanged(byte level)212 public void onLevelChanged(byte level) { 213 logd("SpatializerCallback.onLevelChanged level:" + level); 214 synchronized (SpatializerHelper.this) { 215 mSpatLevel = spatializationLevelToSpatializerInt(level); 216 } 217 // TODO use reported spat level to change state 218 219 // init sensors 220 postInitSensors(); 221 } 222 onOutputChanged(int output)223 public void onOutputChanged(int output) { 224 logd("SpatializerCallback.onOutputChanged output:" + output); 225 int oldOutput; 226 synchronized (SpatializerHelper.this) { 227 oldOutput = mSpatOutput; 228 mSpatOutput = output; 229 } 230 if (oldOutput != output) { 231 dispatchOutputUpdate(output); 232 } 233 } 234 }; 235 236 //------------------------------------------------------ 237 // spatializer head tracking callback from native 238 private final class SpatializerHeadTrackingCallback 239 extends ISpatializerHeadTrackingCallback.Stub { onHeadTrackingModeChanged(byte mode)240 public void onHeadTrackingModeChanged(byte mode) { 241 logd("SpatializerHeadTrackingCallback.onHeadTrackingModeChanged mode:" + mode); 242 int oldMode, newMode; 243 synchronized (this) { 244 oldMode = mActualHeadTrackingMode; 245 mActualHeadTrackingMode = headTrackingModeTypeToSpatializerInt(mode); 246 newMode = mActualHeadTrackingMode; 247 } 248 if (oldMode != newMode) { 249 dispatchActualHeadTrackingMode(newMode); 250 } 251 } 252 onHeadToSoundStagePoseUpdated(float[] headToStage)253 public void onHeadToSoundStagePoseUpdated(float[] headToStage) { 254 if (headToStage == null) { 255 Log.e(TAG, "SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated" 256 + "null transform"); 257 return; 258 } 259 if (headToStage.length != 6) { 260 Log.e(TAG, "SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated" 261 + " invalid transform length" + headToStage.length); 262 return; 263 } 264 if (DEBUG_MORE) { 265 // 6 values * (4 digits + 1 dot + 2 brackets) = 42 characters 266 StringBuilder t = new StringBuilder(42); 267 for (float val : headToStage) { 268 t.append("[").append(String.format(Locale.ENGLISH, "%.3f", val)).append("]"); 269 } 270 logd("SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated headToStage:" + t); 271 } 272 dispatchPoseUpdate(headToStage); 273 } 274 }; 275 276 //------------------------------------------------------ 277 // dynamic sensor callback 278 private final class HelperDynamicSensorCallback extends SensorManager.DynamicSensorCallback { 279 @Override onDynamicSensorConnected(Sensor sensor)280 public void onDynamicSensorConnected(Sensor sensor) { 281 postInitSensors(); 282 } 283 284 @Override onDynamicSensorDisconnected(Sensor sensor)285 public void onDynamicSensorDisconnected(Sensor sensor) { 286 postInitSensors(); 287 } 288 } 289 290 //------------------------------------------------------ 291 // compatible devices 292 /** 293 * @return a shallow copy of the list of compatible audio devices 294 */ getCompatibleAudioDevices()295 synchronized @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() { 296 return (List<AudioDeviceAttributes>) mCompatibleAudioDevices.clone(); 297 } 298 addCompatibleAudioDevice(@onNull AudioDeviceAttributes ada)299 synchronized void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { 300 if (!mCompatibleAudioDevices.contains(ada)) { 301 mCompatibleAudioDevices.add(ada); 302 } 303 } 304 removeCompatibleAudioDevice(@onNull AudioDeviceAttributes ada)305 synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { 306 mCompatibleAudioDevices.remove(ada); 307 } 308 309 //------------------------------------------------------ 310 // states 311 isEnabled()312 synchronized boolean isEnabled() { 313 switch (mState) { 314 case STATE_UNINITIALIZED: 315 case STATE_NOT_SUPPORTED: 316 case STATE_DISABLED_UNAVAILABLE: 317 case STATE_DISABLED_AVAILABLE: 318 return false; 319 case STATE_ENABLED_UNAVAILABLE: 320 case STATE_ENABLED_AVAILABLE: 321 default: 322 return true; 323 } 324 } 325 isAvailable()326 synchronized boolean isAvailable() { 327 switch (mState) { 328 case STATE_UNINITIALIZED: 329 case STATE_NOT_SUPPORTED: 330 case STATE_ENABLED_UNAVAILABLE: 331 case STATE_DISABLED_UNAVAILABLE: 332 return false; 333 case STATE_DISABLED_AVAILABLE: 334 case STATE_ENABLED_AVAILABLE: 335 default: 336 return true; 337 } 338 } 339 setFeatureEnabled(boolean enabled)340 synchronized void setFeatureEnabled(boolean enabled) { 341 switch (mState) { 342 case STATE_UNINITIALIZED: 343 if (enabled) { 344 throw(new IllegalStateException("Can't enable when uninitialized")); 345 } 346 return; 347 case STATE_NOT_SUPPORTED: 348 if (enabled) { 349 Log.e(TAG, "Can't enable when unsupported"); 350 } 351 return; 352 case STATE_DISABLED_UNAVAILABLE: 353 case STATE_DISABLED_AVAILABLE: 354 if (enabled) { 355 createSpat(); 356 break; 357 } else { 358 // already in disabled state 359 return; 360 } 361 case STATE_ENABLED_UNAVAILABLE: 362 case STATE_ENABLED_AVAILABLE: 363 if (!enabled) { 364 releaseSpat(); 365 break; 366 } else { 367 // already in enabled state 368 return; 369 } 370 } 371 setDispatchFeatureEnabledState(enabled); 372 } 373 getCapableImmersiveAudioLevel()374 synchronized int getCapableImmersiveAudioLevel() { 375 return mCapableSpatLevel; 376 } 377 378 final RemoteCallbackList<ISpatializerCallback> mStateCallbacks = 379 new RemoteCallbackList<ISpatializerCallback>(); 380 registerStateCallback( @onNull ISpatializerCallback callback)381 synchronized void registerStateCallback( 382 @NonNull ISpatializerCallback callback) { 383 mStateCallbacks.register(callback); 384 } 385 unregisterStateCallback( @onNull ISpatializerCallback callback)386 synchronized void unregisterStateCallback( 387 @NonNull ISpatializerCallback callback) { 388 mStateCallbacks.unregister(callback); 389 } 390 391 /** 392 * precondition: mState = STATE_* 393 * isFeatureEnabled() != featureEnabled 394 * @param featureEnabled 395 */ setDispatchFeatureEnabledState(boolean featureEnabled)396 private synchronized void setDispatchFeatureEnabledState(boolean featureEnabled) { 397 if (featureEnabled) { 398 switch (mState) { 399 case STATE_DISABLED_UNAVAILABLE: 400 mState = STATE_ENABLED_UNAVAILABLE; 401 break; 402 case STATE_DISABLED_AVAILABLE: 403 mState = STATE_ENABLED_AVAILABLE; 404 break; 405 default: 406 throw(new IllegalStateException("Invalid mState:" + mState 407 + " for enabled true")); 408 } 409 } else { 410 switch (mState) { 411 case STATE_ENABLED_UNAVAILABLE: 412 mState = STATE_DISABLED_UNAVAILABLE; 413 break; 414 case STATE_ENABLED_AVAILABLE: 415 mState = STATE_DISABLED_AVAILABLE; 416 break; 417 default: 418 throw (new IllegalStateException("Invalid mState:" + mState 419 + " for enabled false")); 420 } 421 } 422 final int nbCallbacks = mStateCallbacks.beginBroadcast(); 423 for (int i = 0; i < nbCallbacks; i++) { 424 try { 425 mStateCallbacks.getBroadcastItem(i) 426 .dispatchSpatializerEnabledChanged(featureEnabled); 427 } catch (RemoteException e) { 428 Log.e(TAG, "Error in dispatchSpatializerEnabledChanged", e); 429 } 430 } 431 mStateCallbacks.finishBroadcast(); 432 mAudioService.persistSpatialAudioEnabled(featureEnabled); 433 } 434 setDispatchAvailableState(boolean available)435 private synchronized void setDispatchAvailableState(boolean available) { 436 switch (mState) { 437 case STATE_UNINITIALIZED: 438 case STATE_NOT_SUPPORTED: 439 throw(new IllegalStateException( 440 "Should not update available state in state:" + mState)); 441 case STATE_DISABLED_UNAVAILABLE: 442 if (available) { 443 mState = STATE_DISABLED_AVAILABLE; 444 break; 445 } else { 446 // already in unavailable state 447 return; 448 } 449 case STATE_ENABLED_UNAVAILABLE: 450 if (available) { 451 mState = STATE_ENABLED_AVAILABLE; 452 break; 453 } else { 454 // already in unavailable state 455 return; 456 } 457 case STATE_DISABLED_AVAILABLE: 458 if (available) { 459 // already in available state 460 return; 461 } else { 462 mState = STATE_DISABLED_UNAVAILABLE; 463 break; 464 } 465 case STATE_ENABLED_AVAILABLE: 466 if (available) { 467 // already in available state 468 return; 469 } else { 470 mState = STATE_ENABLED_UNAVAILABLE; 471 break; 472 } 473 } 474 final int nbCallbacks = mStateCallbacks.beginBroadcast(); 475 for (int i = 0; i < nbCallbacks; i++) { 476 try { 477 mStateCallbacks.getBroadcastItem(i) 478 .dispatchSpatializerAvailableChanged(available); 479 } catch (RemoteException e) { 480 Log.e(TAG, "Error in dispatchSpatializerEnabledChanged", e); 481 } 482 } 483 mStateCallbacks.finishBroadcast(); 484 } 485 486 //------------------------------------------------------ 487 // native Spatializer management 488 489 /** 490 * precondition: mState == STATE_DISABLED_* 491 */ createSpat()492 private void createSpat() { 493 if (mSpat == null) { 494 mSpatCallback = new SpatializerCallback(); 495 mSpatHeadTrackingCallback = new SpatializerHeadTrackingCallback(); 496 mSpat = AudioSystem.getSpatializer(mSpatCallback); 497 try { 498 mSpat.setLevel((byte) Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL); 499 //TODO: register heatracking callback only when sensors are registered 500 if (mSpat.isHeadTrackingSupported()) { 501 mSpat.registerHeadTrackingCallback(mSpatHeadTrackingCallback); 502 } 503 } catch (RemoteException e) { 504 Log.e(TAG, "Can't set spatializer level", e); 505 mState = STATE_NOT_SUPPORTED; 506 mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; 507 } 508 } 509 } 510 511 /** 512 * precondition: mState == STATE_ENABLED_* 513 */ releaseSpat()514 private void releaseSpat() { 515 if (mSpat != null) { 516 mSpatCallback = null; 517 try { 518 mSpat.registerHeadTrackingCallback(null); 519 mSpat.release(); 520 mSpat = null; 521 } catch (RemoteException e) { 522 Log.e(TAG, "Can't set release spatializer cleanly", e); 523 } 524 } 525 } 526 527 //------------------------------------------------------ 528 // virtualization capabilities canBeSpatialized( @onNull AudioAttributes attributes, @NonNull AudioFormat format)529 synchronized boolean canBeSpatialized( 530 @NonNull AudioAttributes attributes, @NonNull AudioFormat format) { 531 logd("canBeSpatialized usage:" + attributes.getUsage() 532 + " format:" + format.toLogFriendlyString()); 533 switch (mState) { 534 case STATE_UNINITIALIZED: 535 case STATE_NOT_SUPPORTED: 536 case STATE_ENABLED_UNAVAILABLE: 537 case STATE_DISABLED_UNAVAILABLE: 538 logd("canBeSpatialized false due to state:" + mState); 539 return false; 540 case STATE_DISABLED_AVAILABLE: 541 case STATE_ENABLED_AVAILABLE: 542 break; 543 } 544 545 // filter on AudioAttributes usage 546 switch (attributes.getUsage()) { 547 case AudioAttributes.USAGE_MEDIA: 548 case AudioAttributes.USAGE_GAME: 549 break; 550 default: 551 logd("canBeSpatialized false due to usage:" + attributes.getUsage()); 552 return false; 553 } 554 AudioDeviceAttributes[] devices = new AudioDeviceAttributes[1]; 555 // going through adapter to take advantage of routing cache 556 mASA.getDevicesForAttributes(attributes).toArray(devices); 557 final boolean able = AudioSystem.canBeSpatialized(attributes, format, devices); 558 logd("canBeSpatialized returning " + able); 559 return able; 560 } 561 562 //------------------------------------------------------ 563 // head tracking 564 final RemoteCallbackList<ISpatializerHeadTrackingModeCallback> mHeadTrackingModeCallbacks = 565 new RemoteCallbackList<ISpatializerHeadTrackingModeCallback>(); 566 registerHeadTrackingModeCallback( @onNull ISpatializerHeadTrackingModeCallback callback)567 synchronized void registerHeadTrackingModeCallback( 568 @NonNull ISpatializerHeadTrackingModeCallback callback) { 569 mHeadTrackingModeCallbacks.register(callback); 570 } 571 unregisterHeadTrackingModeCallback( @onNull ISpatializerHeadTrackingModeCallback callback)572 synchronized void unregisterHeadTrackingModeCallback( 573 @NonNull ISpatializerHeadTrackingModeCallback callback) { 574 mHeadTrackingModeCallbacks.unregister(callback); 575 } 576 getSupportedHeadTrackingModes()577 synchronized int[] getSupportedHeadTrackingModes() { 578 switch (mState) { 579 case STATE_UNINITIALIZED: 580 return new int[0]; 581 case STATE_NOT_SUPPORTED: 582 // return an empty list when Spatializer functionality is not supported 583 // because the list of head tracking modes you can set is actually empty 584 // as defined in {@link Spatializer#getSupportedHeadTrackingModes()} 585 return new int[0]; 586 case STATE_ENABLED_UNAVAILABLE: 587 case STATE_DISABLED_UNAVAILABLE: 588 case STATE_DISABLED_AVAILABLE: 589 case STATE_ENABLED_AVAILABLE: 590 if (mSpat == null) { 591 return new int[0]; 592 } 593 break; 594 } 595 // mSpat != null 596 try { 597 final byte[] values = mSpat.getSupportedHeadTrackingModes(); 598 ArrayList<Integer> list = new ArrayList<>(0); 599 for (byte value : values) { 600 switch (value) { 601 case SpatializerHeadTrackingMode.OTHER: 602 case SpatializerHeadTrackingMode.DISABLED: 603 // not expected here, skip 604 break; 605 case SpatializerHeadTrackingMode.RELATIVE_WORLD: 606 case SpatializerHeadTrackingMode.RELATIVE_SCREEN: 607 list.add(headTrackingModeTypeToSpatializerInt(value)); 608 break; 609 default: 610 Log.e(TAG, "Unexpected head tracking mode:" + value, 611 new IllegalArgumentException("invalid mode")); 612 break; 613 } 614 } 615 int[] modes = new int[list.size()]; 616 for (int i = 0; i < list.size(); i++) { 617 modes[i] = list.get(i); 618 } 619 return modes; 620 } catch (RemoteException e) { 621 Log.e(TAG, "Error calling getSupportedHeadTrackingModes", e); 622 return new int[] { Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED }; 623 } 624 } 625 getActualHeadTrackingMode()626 synchronized int getActualHeadTrackingMode() { 627 switch (mState) { 628 case STATE_UNINITIALIZED: 629 return Spatializer.HEAD_TRACKING_MODE_DISABLED; 630 case STATE_NOT_SUPPORTED: 631 return Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; 632 case STATE_ENABLED_UNAVAILABLE: 633 case STATE_DISABLED_UNAVAILABLE: 634 case STATE_DISABLED_AVAILABLE: 635 case STATE_ENABLED_AVAILABLE: 636 if (mSpat == null) { 637 return Spatializer.HEAD_TRACKING_MODE_DISABLED; 638 } 639 break; 640 } 641 // mSpat != null 642 try { 643 return headTrackingModeTypeToSpatializerInt(mSpat.getActualHeadTrackingMode()); 644 } catch (RemoteException e) { 645 Log.e(TAG, "Error calling getActualHeadTrackingMode", e); 646 return Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; 647 } 648 } 649 getDesiredHeadTrackingMode()650 synchronized int getDesiredHeadTrackingMode() { 651 return mDesiredHeadTrackingMode; 652 } 653 setGlobalTransform(@onNull float[] transform)654 synchronized void setGlobalTransform(@NonNull float[] transform) { 655 if (transform.length != 6) { 656 throw new IllegalArgumentException("invalid array size" + transform.length); 657 } 658 if (!checkSpatForHeadTracking("setGlobalTransform")) { 659 return; 660 } 661 try { 662 mSpat.setGlobalTransform(transform); 663 } catch (RemoteException e) { 664 Log.e(TAG, "Error calling setGlobalTransform", e); 665 } 666 } 667 recenterHeadTracker()668 synchronized void recenterHeadTracker() { 669 if (!checkSpatForHeadTracking("recenterHeadTracker")) { 670 return; 671 } 672 try { 673 mSpat.recenterHeadTracker(); 674 } catch (RemoteException e) { 675 Log.e(TAG, "Error calling recenterHeadTracker", e); 676 } 677 } 678 setDesiredHeadTrackingMode(@patializer.HeadTrackingModeSet int mode)679 synchronized void setDesiredHeadTrackingMode(@Spatializer.HeadTrackingModeSet int mode) { 680 if (!checkSpatForHeadTracking("setDesiredHeadTrackingMode")) { 681 return; 682 } 683 try { 684 if (mode != mDesiredHeadTrackingMode) { 685 mSpat.setDesiredHeadTrackingMode(spatializerIntToHeadTrackingModeType(mode)); 686 mDesiredHeadTrackingMode = mode; 687 dispatchDesiredHeadTrackingMode(mode); 688 } 689 690 } catch (RemoteException e) { 691 Log.e(TAG, "Error calling setDesiredHeadTrackingMode", e); 692 } 693 } 694 checkSpatForHeadTracking(String funcName)695 private boolean checkSpatForHeadTracking(String funcName) { 696 switch (mState) { 697 case STATE_UNINITIALIZED: 698 case STATE_NOT_SUPPORTED: 699 return false; 700 case STATE_ENABLED_UNAVAILABLE: 701 case STATE_DISABLED_UNAVAILABLE: 702 case STATE_DISABLED_AVAILABLE: 703 case STATE_ENABLED_AVAILABLE: 704 if (mSpat == null) { 705 throw (new IllegalStateException( 706 "null Spatializer when calling " + funcName)); 707 } 708 break; 709 } 710 return true; 711 } 712 dispatchActualHeadTrackingMode(int newMode)713 private void dispatchActualHeadTrackingMode(int newMode) { 714 final int nbCallbacks = mHeadTrackingModeCallbacks.beginBroadcast(); 715 for (int i = 0; i < nbCallbacks; i++) { 716 try { 717 mHeadTrackingModeCallbacks.getBroadcastItem(i) 718 .dispatchSpatializerActualHeadTrackingModeChanged(newMode); 719 } catch (RemoteException e) { 720 Log.e(TAG, "Error in dispatchSpatializerActualHeadTrackingModeChanged", e); 721 } 722 } 723 mHeadTrackingModeCallbacks.finishBroadcast(); 724 } 725 dispatchDesiredHeadTrackingMode(int newMode)726 private void dispatchDesiredHeadTrackingMode(int newMode) { 727 final int nbCallbacks = mHeadTrackingModeCallbacks.beginBroadcast(); 728 for (int i = 0; i < nbCallbacks; i++) { 729 try { 730 mHeadTrackingModeCallbacks.getBroadcastItem(i) 731 .dispatchSpatializerDesiredHeadTrackingModeChanged(newMode); 732 } catch (RemoteException e) { 733 Log.e(TAG, "Error in dispatchSpatializerDesiredHeadTrackingModeChanged", e); 734 } 735 } 736 mHeadTrackingModeCallbacks.finishBroadcast(); 737 } 738 739 //------------------------------------------------------ 740 // head pose 741 final RemoteCallbackList<ISpatializerHeadToSoundStagePoseCallback> mHeadPoseCallbacks = 742 new RemoteCallbackList<ISpatializerHeadToSoundStagePoseCallback>(); 743 registerHeadToSoundstagePoseCallback( @onNull ISpatializerHeadToSoundStagePoseCallback callback)744 synchronized void registerHeadToSoundstagePoseCallback( 745 @NonNull ISpatializerHeadToSoundStagePoseCallback callback) { 746 mHeadPoseCallbacks.register(callback); 747 } 748 unregisterHeadToSoundstagePoseCallback( @onNull ISpatializerHeadToSoundStagePoseCallback callback)749 synchronized void unregisterHeadToSoundstagePoseCallback( 750 @NonNull ISpatializerHeadToSoundStagePoseCallback callback) { 751 mHeadPoseCallbacks.unregister(callback); 752 } 753 dispatchPoseUpdate(float[] pose)754 private void dispatchPoseUpdate(float[] pose) { 755 final int nbCallbacks = mHeadPoseCallbacks.beginBroadcast(); 756 for (int i = 0; i < nbCallbacks; i++) { 757 try { 758 mHeadPoseCallbacks.getBroadcastItem(i) 759 .dispatchPoseChanged(pose); 760 } catch (RemoteException e) { 761 Log.e(TAG, "Error in dispatchPoseChanged", e); 762 } 763 } 764 mHeadPoseCallbacks.finishBroadcast(); 765 } 766 767 //------------------------------------------------------ 768 // vendor parameters setEffectParameter(int key, @NonNull byte[] value)769 synchronized void setEffectParameter(int key, @NonNull byte[] value) { 770 switch (mState) { 771 case STATE_UNINITIALIZED: 772 case STATE_NOT_SUPPORTED: 773 throw (new IllegalStateException( 774 "Can't set parameter key:" + key + " without a spatializer")); 775 case STATE_ENABLED_UNAVAILABLE: 776 case STATE_DISABLED_UNAVAILABLE: 777 case STATE_DISABLED_AVAILABLE: 778 case STATE_ENABLED_AVAILABLE: 779 if (mSpat == null) { 780 throw (new IllegalStateException( 781 "null Spatializer for setParameter for key:" + key)); 782 } 783 break; 784 } 785 // mSpat != null 786 try { 787 mSpat.setParameter(key, value); 788 } catch (RemoteException e) { 789 Log.e(TAG, "Error in setParameter for key:" + key, e); 790 } 791 } 792 getEffectParameter(int key, @NonNull byte[] value)793 synchronized void getEffectParameter(int key, @NonNull byte[] value) { 794 switch (mState) { 795 case STATE_UNINITIALIZED: 796 case STATE_NOT_SUPPORTED: 797 throw (new IllegalStateException( 798 "Can't get parameter key:" + key + " without a spatializer")); 799 case STATE_ENABLED_UNAVAILABLE: 800 case STATE_DISABLED_UNAVAILABLE: 801 case STATE_DISABLED_AVAILABLE: 802 case STATE_ENABLED_AVAILABLE: 803 if (mSpat == null) { 804 throw (new IllegalStateException( 805 "null Spatializer for getParameter for key:" + key)); 806 } 807 break; 808 } 809 // mSpat != null 810 try { 811 mSpat.getParameter(key, value); 812 } catch (RemoteException e) { 813 Log.e(TAG, "Error in getParameter for key:" + key, e); 814 } 815 } 816 817 //------------------------------------------------------ 818 // output 819 820 /** @see Spatializer#getOutput */ getOutput()821 synchronized int getOutput() { 822 switch (mState) { 823 case STATE_UNINITIALIZED: 824 case STATE_NOT_SUPPORTED: 825 throw (new IllegalStateException( 826 "Can't get output without a spatializer")); 827 case STATE_ENABLED_UNAVAILABLE: 828 case STATE_DISABLED_UNAVAILABLE: 829 case STATE_DISABLED_AVAILABLE: 830 case STATE_ENABLED_AVAILABLE: 831 if (mSpat == null) { 832 throw (new IllegalStateException( 833 "null Spatializer for getOutput")); 834 } 835 break; 836 } 837 // mSpat != null 838 try { 839 return mSpat.getOutput(); 840 } catch (RemoteException e) { 841 Log.e(TAG, "Error in getOutput", e); 842 return 0; 843 } 844 } 845 846 final RemoteCallbackList<ISpatializerOutputCallback> mOutputCallbacks = 847 new RemoteCallbackList<ISpatializerOutputCallback>(); 848 registerSpatializerOutputCallback( @onNull ISpatializerOutputCallback callback)849 synchronized void registerSpatializerOutputCallback( 850 @NonNull ISpatializerOutputCallback callback) { 851 mOutputCallbacks.register(callback); 852 } 853 unregisterSpatializerOutputCallback( @onNull ISpatializerOutputCallback callback)854 synchronized void unregisterSpatializerOutputCallback( 855 @NonNull ISpatializerOutputCallback callback) { 856 mOutputCallbacks.unregister(callback); 857 } 858 dispatchOutputUpdate(int output)859 private void dispatchOutputUpdate(int output) { 860 final int nbCallbacks = mOutputCallbacks.beginBroadcast(); 861 for (int i = 0; i < nbCallbacks; i++) { 862 try { 863 mOutputCallbacks.getBroadcastItem(i).dispatchSpatializerOutputChanged(output); 864 } catch (RemoteException e) { 865 Log.e(TAG, "Error in dispatchOutputUpdate", e); 866 } 867 } 868 mOutputCallbacks.finishBroadcast(); 869 } 870 871 //------------------------------------------------------ 872 // sensors postInitSensors()873 private void postInitSensors() { 874 mAudioService.postInitSpatializerHeadTrackingSensors(); 875 } 876 onInitSensors()877 synchronized void onInitSensors() { 878 final boolean init = (mSpatLevel != SpatializationLevel.NONE); 879 final String action = init ? "initializing" : "releasing"; 880 if (mSpat == null) { 881 Log.e(TAG, "not " + action + " sensors, null spatializer"); 882 return; 883 } 884 try { 885 if (!mSpat.isHeadTrackingSupported()) { 886 Log.e(TAG, "not " + action + " sensors, spatializer doesn't support headtracking"); 887 return; 888 } 889 } catch (RemoteException e) { 890 Log.e(TAG, "not " + action + " sensors, error querying headtracking", e); 891 return; 892 } 893 int headHandle = -1; 894 int screenHandle = -1; 895 if (init) { 896 if (mSensorManager == null) { 897 try { 898 mSensorManager = (SensorManager) 899 mAudioService.mContext.getSystemService(Context.SENSOR_SERVICE); 900 mDynSensorCallback = new HelperDynamicSensorCallback(); 901 mSensorManager.registerDynamicSensorCallback(mDynSensorCallback); 902 } catch (Exception e) { 903 Log.e(TAG, "Error with SensorManager, can't initialize sensors", e); 904 mSensorManager = null; 905 mDynSensorCallback = null; 906 return; 907 } 908 } 909 // initialize sensor handles 910 // TODO-HT update to non-private sensor once head tracker sensor is defined 911 for (Sensor sensor : mSensorManager.getDynamicSensorList( 912 Sensor.TYPE_DEVICE_PRIVATE_BASE)) { 913 if (sensor.getStringType().equals(HEADTRACKER_SENSOR)) { 914 headHandle = sensor.getHandle(); 915 break; 916 } 917 } 918 Sensor screenSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); 919 screenHandle = screenSensor.getHandle(); 920 } else { 921 if (mSensorManager != null && mDynSensorCallback != null) { 922 mSensorManager.unregisterDynamicSensorCallback(mDynSensorCallback); 923 mSensorManager = null; 924 mDynSensorCallback = null; 925 } 926 // -1 is disable value for both screen and head tracker handles 927 } 928 try { 929 Log.i(TAG, "setScreenSensor:" + screenHandle); 930 mSpat.setScreenSensor(screenHandle); 931 } catch (Exception e) { 932 Log.e(TAG, "Error calling setScreenSensor:" + screenHandle, e); 933 } 934 try { 935 Log.i(TAG, "setHeadSensor:" + headHandle); 936 mSpat.setHeadSensor(headHandle); 937 } catch (Exception e) { 938 Log.e(TAG, "Error calling setHeadSensor:" + headHandle, e); 939 } 940 } 941 942 //------------------------------------------------------ 943 // SDK <-> AIDL converters headTrackingModeTypeToSpatializerInt(byte mode)944 private static int headTrackingModeTypeToSpatializerInt(byte mode) { 945 switch (mode) { 946 case SpatializerHeadTrackingMode.OTHER: 947 return Spatializer.HEAD_TRACKING_MODE_OTHER; 948 case SpatializerHeadTrackingMode.DISABLED: 949 return Spatializer.HEAD_TRACKING_MODE_DISABLED; 950 case SpatializerHeadTrackingMode.RELATIVE_WORLD: 951 return Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD; 952 case SpatializerHeadTrackingMode.RELATIVE_SCREEN: 953 return Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE; 954 default: 955 throw(new IllegalArgumentException("Unexpected head tracking mode:" + mode)); 956 } 957 } 958 spatializerIntToHeadTrackingModeType(int sdkMode)959 private static byte spatializerIntToHeadTrackingModeType(int sdkMode) { 960 switch (sdkMode) { 961 case Spatializer.HEAD_TRACKING_MODE_OTHER: 962 return SpatializerHeadTrackingMode.OTHER; 963 case Spatializer.HEAD_TRACKING_MODE_DISABLED: 964 return SpatializerHeadTrackingMode.DISABLED; 965 case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD: 966 return SpatializerHeadTrackingMode.RELATIVE_WORLD; 967 case Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE: 968 return SpatializerHeadTrackingMode.RELATIVE_SCREEN; 969 default: 970 throw(new IllegalArgumentException("Unexpected head tracking mode:" + sdkMode)); 971 } 972 } 973 spatializationLevelToSpatializerInt(byte level)974 private static int spatializationLevelToSpatializerInt(byte level) { 975 switch (level) { 976 case SpatializationLevel.NONE: 977 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; 978 case SpatializationLevel.SPATIALIZER_MULTICHANNEL: 979 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL; 980 case SpatializationLevel.SPATIALIZER_MCHAN_BED_PLUS_OBJECTS: 981 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MCHAN_BED_PLUS_OBJECTS; 982 default: 983 throw(new IllegalArgumentException("Unexpected spatializer level:" + level)); 984 } 985 } 986 } 987