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 static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES; 20 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN; 21 import static android.media.AudioSystem.isBluetoothDevice; 22 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.content.Context; 26 import android.hardware.Sensor; 27 import android.hardware.SensorManager; 28 import android.media.AudioAttributes; 29 import android.media.AudioDeviceAttributes; 30 import android.media.AudioDeviceInfo; 31 import android.media.AudioFormat; 32 import android.media.AudioSystem; 33 import android.media.INativeSpatializerCallback; 34 import android.media.ISpatializer; 35 import android.media.ISpatializerCallback; 36 import android.media.ISpatializerHeadToSoundStagePoseCallback; 37 import android.media.ISpatializerHeadTrackerAvailableCallback; 38 import android.media.ISpatializerHeadTrackingCallback; 39 import android.media.ISpatializerHeadTrackingModeCallback; 40 import android.media.ISpatializerOutputCallback; 41 import android.media.MediaMetrics; 42 import android.media.SpatializationLevel; 43 import android.media.SpatializationMode; 44 import android.media.Spatializer; 45 import android.media.SpatializerHeadTrackingMode; 46 import android.os.RemoteCallbackList; 47 import android.os.RemoteException; 48 import android.text.TextUtils; 49 import android.util.Log; 50 import android.util.Pair; 51 import android.util.SparseIntArray; 52 53 import com.android.internal.annotations.GuardedBy; 54 import com.android.internal.annotations.VisibleForTesting; 55 import com.android.server.utils.EventLogger; 56 57 import java.io.PrintWriter; 58 import java.util.ArrayList; 59 import java.util.List; 60 import java.util.Locale; 61 import java.util.UUID; 62 63 /** 64 * A helper class to manage Spatializer related functionality 65 */ 66 public class SpatializerHelper { 67 68 private static final String TAG = "AS.SpatializerHelper"; 69 private static final boolean DEBUG = true; 70 private static final boolean DEBUG_MORE = false; 71 logd(String s)72 private static void logd(String s) { 73 if (DEBUG) { 74 Log.i(TAG, s); 75 } 76 } 77 78 private final @NonNull AudioSystemAdapter mASA; 79 private final @NonNull AudioService mAudioService; 80 private final @NonNull AudioDeviceBroker mDeviceBroker; 81 private @Nullable SensorManager mSensorManager; 82 83 //------------------------------------------------------------ 84 85 /*package*/ static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(14) { 86 { 87 append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL); 88 append(AudioDeviceInfo.TYPE_WIRED_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL); 89 append(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, SpatializationMode.SPATIALIZER_BINAURAL); 90 // assumption for A2DP: mostly headsets 91 append(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, SpatializationMode.SPATIALIZER_BINAURAL); 92 append(AudioDeviceInfo.TYPE_DOCK, SpatializationMode.SPATIALIZER_TRANSAURAL); 93 append(AudioDeviceInfo.TYPE_USB_ACCESSORY, SpatializationMode.SPATIALIZER_TRANSAURAL); 94 append(AudioDeviceInfo.TYPE_USB_DEVICE, SpatializationMode.SPATIALIZER_TRANSAURAL); 95 append(AudioDeviceInfo.TYPE_USB_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL); 96 append(AudioDeviceInfo.TYPE_LINE_ANALOG, SpatializationMode.SPATIALIZER_TRANSAURAL); 97 append(AudioDeviceInfo.TYPE_LINE_DIGITAL, SpatializationMode.SPATIALIZER_TRANSAURAL); 98 append(AudioDeviceInfo.TYPE_AUX_LINE, SpatializationMode.SPATIALIZER_TRANSAURAL); 99 append(AudioDeviceInfo.TYPE_BLE_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL); 100 append(AudioDeviceInfo.TYPE_BLE_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL); 101 // assumption that BLE broadcast would be mostly consumed on headsets 102 append(AudioDeviceInfo.TYPE_BLE_BROADCAST, SpatializationMode.SPATIALIZER_BINAURAL); 103 } 104 }; 105 106 // Spatializer state machine 107 /*package*/ static final int STATE_UNINITIALIZED = 0; 108 /*package*/ static final int STATE_NOT_SUPPORTED = 1; 109 /*package*/ static final int STATE_DISABLED_UNAVAILABLE = 3; 110 /*package*/ static final int STATE_ENABLED_UNAVAILABLE = 4; 111 /*package*/ static final int STATE_ENABLED_AVAILABLE = 5; 112 /*package*/ static final int STATE_DISABLED_AVAILABLE = 6; 113 private int mState = STATE_UNINITIALIZED; 114 115 @VisibleForTesting boolean mBinauralEnabledDefault; 116 @VisibleForTesting boolean mTransauralEnabledDefault; 117 @VisibleForTesting boolean mHeadTrackingEnabledDefault; 118 119 private boolean mFeatureEnabled = false; 120 /** current level as reported by native Spatializer in callback */ 121 private int mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; 122 private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; 123 124 private boolean mTransauralSupported = false; 125 private boolean mBinauralSupported = false; 126 private boolean mIsHeadTrackingSupported = false; 127 private int[] mSupportedHeadTrackingModes = new int[0]; 128 private int mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; 129 private int mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD; 130 private boolean mHeadTrackerAvailable = false; 131 /** 132 * The desired head tracking mode when enabling head tracking, tracks mDesiredHeadTrackingMode, 133 * except when head tracking gets disabled through setting the desired mode to 134 * {@link Spatializer#HEAD_TRACKING_MODE_DISABLED}. 135 */ 136 private int mDesiredHeadTrackingModeWhenEnabled = Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD; 137 private int mSpatOutput = 0; 138 private @Nullable ISpatializer mSpat; 139 private @Nullable SpatializerCallback mSpatCallback; 140 private @Nullable SpatializerHeadTrackingCallback mSpatHeadTrackingCallback = 141 new SpatializerHeadTrackingCallback(); 142 private @Nullable HelperDynamicSensorCallback mDynSensorCallback; 143 144 // default attributes and format that determine basic availability of spatialization 145 private static final AudioAttributes DEFAULT_ATTRIBUTES = new AudioAttributes.Builder() 146 .setUsage(AudioAttributes.USAGE_MEDIA) 147 .build(); 148 private static final AudioFormat DEFAULT_FORMAT = new AudioFormat.Builder() 149 .setEncoding(AudioFormat.ENCODING_PCM_16BIT) 150 .setSampleRate(48000) 151 .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1) 152 .build(); 153 // device array to store the routing for the default attributes and format, initialized to 154 // an empty list as routing hasn't been established yet 155 private static ArrayList<AudioDeviceAttributes> sRoutingDevices = new ArrayList<>(0); 156 157 //--------------------------------------------------------------- 158 // audio device compatibility / enabled 159 /** 160 * List of device types that can be used on this device with Spatial Audio. 161 * It is initialized based on the transaural/binaural capabilities 162 * of the effect. 163 */ 164 private final ArrayList<Integer> mSACapableDeviceTypes = new ArrayList<>(0); 165 166 //------------------------------------------------------ 167 // initialization SpatializerHelper(@onNull AudioService mother, @NonNull AudioSystemAdapter asa, @NonNull AudioDeviceBroker deviceBroker, boolean binauralEnabledDefault, boolean transauralEnabledDefault, boolean headTrackingEnabledDefault)168 SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa, 169 @NonNull AudioDeviceBroker deviceBroker, boolean binauralEnabledDefault, 170 boolean transauralEnabledDefault, boolean headTrackingEnabledDefault) { 171 mAudioService = mother; 172 mASA = asa; 173 mDeviceBroker = deviceBroker; 174 175 mBinauralEnabledDefault = binauralEnabledDefault; 176 mTransauralEnabledDefault = transauralEnabledDefault; 177 mHeadTrackingEnabledDefault = headTrackingEnabledDefault; 178 } 179 init(boolean effectExpected)180 synchronized void init(boolean effectExpected) { 181 loglogi("init effectExpected=" + effectExpected); 182 if (!effectExpected) { 183 loglogi("init(): setting state to STATE_NOT_SUPPORTED due to effect not expected"); 184 mState = STATE_NOT_SUPPORTED; 185 return; 186 } 187 if (mState != STATE_UNINITIALIZED) { 188 throw new IllegalStateException(logloge("init() called in state " + mState)); 189 } 190 // is there a spatializer? 191 mSpatCallback = new SpatializerCallback(); 192 final ISpatializer spat = AudioSystem.getSpatializer(mSpatCallback); 193 if (spat == null) { 194 loglogi("init(): No Spatializer found"); 195 mState = STATE_NOT_SUPPORTED; 196 return; 197 } 198 // capabilities of spatializer? 199 resetCapabilities(); 200 201 try { 202 byte[] levels = spat.getSupportedLevels(); 203 if (levels == null 204 || levels.length == 0 205 || (levels.length == 1 206 && levels[0] == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE)) { 207 logloge("init(): found Spatializer is useless"); 208 mState = STATE_NOT_SUPPORTED; 209 return; 210 } 211 for (byte level : levels) { 212 loglogi("init(): found support for level: " + level); 213 if (level == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL) { 214 loglogi("init(): setting capable level to LEVEL_MULTICHANNEL"); 215 mCapableSpatLevel = level; 216 break; 217 } 218 } 219 220 // Note: head tracking support must be initialized before spatialization modes as 221 // addCompatibleAudioDevice() calls onRoutingUpdated() which will initialize the 222 // sensors according to mIsHeadTrackingSupported. 223 mIsHeadTrackingSupported = spat.isHeadTrackingSupported(); 224 if (mIsHeadTrackingSupported) { 225 final byte[] values = spat.getSupportedHeadTrackingModes(); 226 ArrayList<Integer> list = new ArrayList<>(0); 227 for (byte value : values) { 228 switch (value) { 229 case SpatializerHeadTrackingMode.OTHER: 230 case SpatializerHeadTrackingMode.DISABLED: 231 // not expected here, skip 232 break; 233 case SpatializerHeadTrackingMode.RELATIVE_WORLD: 234 case SpatializerHeadTrackingMode.RELATIVE_SCREEN: 235 list.add(headTrackingModeTypeToSpatializerInt(value)); 236 break; 237 default: 238 Log.e(TAG, "Unexpected head tracking mode:" + value, 239 new IllegalArgumentException("invalid mode")); 240 break; 241 } 242 } 243 mSupportedHeadTrackingModes = new int[list.size()]; 244 for (int i = 0; i < list.size(); i++) { 245 mSupportedHeadTrackingModes[i] = list.get(i); 246 } 247 mActualHeadTrackingMode = 248 headTrackingModeTypeToSpatializerInt(spat.getActualHeadTrackingMode()); 249 } else { 250 mDesiredHeadTrackingModeWhenEnabled = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; 251 mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; 252 } 253 254 byte[] spatModes = spat.getSupportedModes(); 255 for (byte mode : spatModes) { 256 switch (mode) { 257 case SpatializationMode.SPATIALIZER_BINAURAL: 258 mBinauralSupported = true; 259 break; 260 case SpatializationMode.SPATIALIZER_TRANSAURAL: 261 mTransauralSupported = true; 262 break; 263 default: 264 logloge("init(): Spatializer reports unknown supported mode:" + mode); 265 break; 266 } 267 } 268 // if neither transaural nor binaural is supported, bail 269 if (!mBinauralSupported && !mTransauralSupported) { 270 mState = STATE_NOT_SUPPORTED; 271 return; 272 } 273 274 // initialize list of compatible devices 275 for (int i = 0; i < SPAT_MODE_FOR_DEVICE_TYPE.size(); i++) { 276 int mode = SPAT_MODE_FOR_DEVICE_TYPE.valueAt(i); 277 if ((mode == (int) SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported) 278 || (mode == (int) SpatializationMode.SPATIALIZER_TRANSAURAL 279 && mTransauralSupported)) { 280 mSACapableDeviceTypes.add(SPAT_MODE_FOR_DEVICE_TYPE.keyAt(i)); 281 } 282 } 283 284 // Log the saved device states that are compatible with SA 285 for (AdiDeviceState deviceState : mDeviceBroker.getImmutableDeviceInventory()) { 286 if (isSADevice(deviceState)) { 287 logDeviceState(deviceState, "setSADeviceSettings"); 288 } 289 } 290 291 // for both transaural / binaural, we are not forcing enablement as the init() method 292 // could have been called another time after boot in case of audioserver restart 293 addCompatibleAudioDevice( 294 new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""), 295 false /*forceEnable*/); 296 // not force-enabling as this device might already be in the device list 297 addCompatibleAudioDevice( 298 new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, ""), 299 false /*forceEnable*/); 300 } catch (RemoteException e) { 301 resetCapabilities(); 302 } finally { 303 if (spat != null) { 304 try { 305 spat.release(); 306 } catch (RemoteException e) { /* capable level remains at NONE*/ } 307 } 308 } 309 if (mCapableSpatLevel == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) { 310 mState = STATE_NOT_SUPPORTED; 311 return; 312 } 313 mState = STATE_DISABLED_UNAVAILABLE; 314 sRoutingDevices = getRoutingDevices(DEFAULT_ATTRIBUTES); 315 // note at this point mSpat is still not instantiated 316 } 317 318 /** 319 * Like init() but resets the state and spatializer levels 320 * @param featureEnabled 321 */ reset(boolean featureEnabled)322 synchronized void reset(boolean featureEnabled) { 323 loglogi("Resetting featureEnabled=" + featureEnabled); 324 releaseSpat(); 325 mState = STATE_UNINITIALIZED; 326 mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; 327 mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; 328 init(/*effectExpected=*/true); 329 setSpatializerEnabledInt(featureEnabled); 330 } 331 resetCapabilities()332 private void resetCapabilities() { 333 mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; 334 mBinauralSupported = false; 335 mTransauralSupported = false; 336 mIsHeadTrackingSupported = false; 337 mSupportedHeadTrackingModes = new int[0]; 338 } 339 340 //------------------------------------------------------ 341 // routing monitoring onRoutingUpdated()342 synchronized void onRoutingUpdated() { 343 if (!mFeatureEnabled) { 344 return; 345 } 346 switch (mState) { 347 case STATE_UNINITIALIZED: 348 case STATE_NOT_SUPPORTED: 349 return; 350 case STATE_DISABLED_UNAVAILABLE: 351 case STATE_ENABLED_UNAVAILABLE: 352 case STATE_ENABLED_AVAILABLE: 353 case STATE_DISABLED_AVAILABLE: 354 break; 355 } 356 357 sRoutingDevices = getRoutingDevices(DEFAULT_ATTRIBUTES); 358 359 // check validity of routing information 360 if (sRoutingDevices.isEmpty()) { 361 logloge("onRoutingUpdated: no device, no Spatial Audio"); 362 setDispatchAvailableState(false); 363 // not changing the spatializer level as this is likely a transient state 364 return; 365 } 366 final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0); 367 368 // is media routed to a new device? 369 if (isBluetoothDevice(currentDevice.getInternalType())) { 370 addWirelessDeviceIfNew(currentDevice); 371 } 372 373 // find if media device enabled / available 374 final Pair<Boolean, Boolean> enabledAvailable = evaluateState(currentDevice); 375 376 boolean able = false; 377 if (enabledAvailable.second) { 378 // available for Spatial audio, check w/ effect 379 able = canBeSpatializedOnDevice(DEFAULT_ATTRIBUTES, DEFAULT_FORMAT, sRoutingDevices); 380 loglogi("onRoutingUpdated: can spatialize media 5.1:" + able 381 + " on device:" + currentDevice); 382 setDispatchAvailableState(able); 383 } else { 384 loglogi("onRoutingUpdated: device:" + currentDevice 385 + " not available for Spatial Audio"); 386 setDispatchAvailableState(false); 387 } 388 389 boolean enabled = able && enabledAvailable.first; 390 if (enabled) { 391 loglogi("Enabling Spatial Audio since enabled for media device:" 392 + currentDevice); 393 } else { 394 loglogi("Disabling Spatial Audio since disabled for media device:" 395 + currentDevice); 396 } 397 if (mSpat != null) { 398 byte level = enabled ? (byte) Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL 399 : (byte) Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; 400 loglogi("Setting spatialization level to: " + level); 401 try { 402 mSpat.setLevel(level); 403 } catch (RemoteException e) { 404 Log.e(TAG, "onRoutingUpdated() Can't set spatializer level", e); 405 // try to recover by resetting the native spatializer state 406 postReset(); 407 return; 408 } 409 } 410 411 setDispatchFeatureEnabledState(enabled, "onRoutingUpdated"); 412 413 if (mDesiredHeadTrackingMode != Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED 414 && mDesiredHeadTrackingMode != Spatializer.HEAD_TRACKING_MODE_DISABLED) { 415 postInitSensors(); 416 } 417 } 418 postReset()419 private void postReset() { 420 mAudioService.postResetSpatializer(); 421 } 422 423 //------------------------------------------------------ 424 // spatializer callback from native 425 private final class SpatializerCallback extends INativeSpatializerCallback.Stub { 426 onLevelChanged(byte level)427 public void onLevelChanged(byte level) { 428 loglogi("SpatializerCallback.onLevelChanged level:" + level); 429 synchronized (SpatializerHelper.this) { 430 mSpatLevel = spatializationLevelToSpatializerInt(level); 431 } 432 // TODO use reported spat level to change state 433 434 // init sensors 435 postInitSensors(); 436 } 437 onOutputChanged(int output)438 public void onOutputChanged(int output) { 439 loglogi("SpatializerCallback.onOutputChanged output:" + output); 440 int oldOutput; 441 synchronized (SpatializerHelper.this) { 442 oldOutput = mSpatOutput; 443 mSpatOutput = output; 444 } 445 if (oldOutput != output) { 446 dispatchOutputUpdate(output); 447 } 448 } 449 }; 450 451 //------------------------------------------------------ 452 // spatializer head tracking callback from native 453 private final class SpatializerHeadTrackingCallback 454 extends ISpatializerHeadTrackingCallback.Stub { onHeadTrackingModeChanged(byte mode)455 public void onHeadTrackingModeChanged(byte mode) { 456 int oldMode, newMode; 457 synchronized (this) { 458 oldMode = mActualHeadTrackingMode; 459 mActualHeadTrackingMode = headTrackingModeTypeToSpatializerInt(mode); 460 newMode = mActualHeadTrackingMode; 461 } 462 loglogi("SpatializerHeadTrackingCallback.onHeadTrackingModeChanged mode:" 463 + Spatializer.headtrackingModeToString(newMode)); 464 if (oldMode != newMode) { 465 dispatchActualHeadTrackingMode(newMode); 466 } 467 } 468 onHeadToSoundStagePoseUpdated(float[] headToStage)469 public void onHeadToSoundStagePoseUpdated(float[] headToStage) { 470 if (headToStage == null) { 471 Log.e(TAG, "SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated" 472 + "null transform"); 473 return; 474 } 475 if (headToStage.length != 6) { 476 Log.e(TAG, "SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated" 477 + " invalid transform length" + headToStage.length); 478 return; 479 } 480 if (DEBUG_MORE) { 481 // 6 values * (4 digits + 1 dot + 2 brackets) = 42 characters 482 StringBuilder t = new StringBuilder(42); 483 for (float val : headToStage) { 484 t.append("[").append(String.format(Locale.ENGLISH, "%.3f", val)).append("]"); 485 } 486 loglogi("SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated headToStage:" 487 + t); 488 } 489 dispatchPoseUpdate(headToStage); 490 } 491 }; 492 493 //------------------------------------------------------ 494 // dynamic sensor callback 495 private final class HelperDynamicSensorCallback extends SensorManager.DynamicSensorCallback { 496 @Override onDynamicSensorConnected(Sensor sensor)497 public void onDynamicSensorConnected(Sensor sensor) { 498 postInitSensors(); 499 } 500 501 @Override onDynamicSensorDisconnected(Sensor sensor)502 public void onDynamicSensorDisconnected(Sensor sensor) { 503 postInitSensors(); 504 } 505 } 506 507 //------------------------------------------------------ 508 // compatible devices 509 /** 510 * Return the list of compatible devices, which reflects the device compatible with the 511 * spatializer effect, and those that have been explicitly enabled or disabled 512 * @return the list of compatible audio devices 513 */ getCompatibleAudioDevices()514 synchronized @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() { 515 // build unionOf(mCompatibleAudioDevices, mEnabledDevice) - mDisabledAudioDevices 516 ArrayList<AudioDeviceAttributes> compatList = new ArrayList<>(); 517 for (AdiDeviceState deviceState : mDeviceBroker.getImmutableDeviceInventory()) { 518 if (deviceState.isSAEnabled() && isSADevice(deviceState)) { 519 compatList.add(deviceState.getAudioDeviceAttributes()); 520 } 521 } 522 return compatList; 523 } 524 addCompatibleAudioDevice(@onNull AudioDeviceAttributes ada)525 synchronized void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { 526 addCompatibleAudioDevice(ada, true /*forceEnable*/); 527 } 528 529 /** 530 * Add the given device to the list of devices for which spatial audio will be available 531 * (== possible). 532 * @param ada the compatible device 533 * @param forceEnable if true, spatial audio is enabled for this device, regardless of whether 534 * this device was already in the list. If false, the enabled field is only 535 * set to true if the device is added to the list, otherwise, if already 536 * present, the setting is left untouched. 537 */ 538 @GuardedBy("this") addCompatibleAudioDevice(@onNull AudioDeviceAttributes ada, boolean forceEnable)539 private void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada, 540 boolean forceEnable) { 541 if (!isDeviceCompatibleWithSpatializationModes(ada)) { 542 return; 543 } 544 loglogi("addCompatibleAudioDevice: dev=" + ada); 545 final AdiDeviceState deviceState = findSACompatibleDeviceStateForAudioDeviceAttributes(ada); 546 AdiDeviceState updatedDevice = null; // non-null on update. 547 if (deviceState != null) { 548 if (forceEnable && !deviceState.isSAEnabled()) { 549 updatedDevice = deviceState; 550 updatedDevice.setSAEnabled(true); 551 } 552 } else { 553 // When adding, force the device type to be a canonical one. 554 final int canonicalDeviceType = getCanonicalDeviceType(ada.getType(), 555 ada.getInternalType()); 556 if (canonicalDeviceType == AudioDeviceInfo.TYPE_UNKNOWN) { 557 Log.e(TAG, "addCompatibleAudioDevice with incompatible AudioDeviceAttributes " 558 + ada); 559 return; 560 } 561 updatedDevice = new AdiDeviceState(canonicalDeviceType, ada.getInternalType(), 562 ada.getAddress()); 563 initSAState(updatedDevice); 564 mDeviceBroker.addOrUpdateDeviceSAStateInInventory(updatedDevice); 565 } 566 if (updatedDevice != null) { 567 onRoutingUpdated(); 568 mDeviceBroker.persistAudioDeviceSettings(); 569 logDeviceState(updatedDevice, "addCompatibleAudioDevice"); 570 } 571 } 572 initSAState(AdiDeviceState device)573 private void initSAState(AdiDeviceState device) { 574 if (device == null) { 575 return; 576 } 577 578 int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(device.getDeviceType(), 579 Integer.MIN_VALUE); 580 device.setSAEnabled(spatMode == SpatializationMode.SPATIALIZER_BINAURAL 581 ? mBinauralEnabledDefault 582 : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL 583 ? mTransauralEnabledDefault 584 : false); 585 device.setHeadTrackerEnabled(mHeadTrackingEnabledDefault); 586 } 587 588 private static final String METRICS_DEVICE_PREFIX = "audio.spatializer.device."; 589 590 // Device logging is accomplished in the Java Audio Service level. 591 // (System capabilities is done in the Native AudioPolicyManager level). 592 // 593 // There may be different devices with the same device type (aliasing). 594 // We always send the full device state info on each change. logDeviceState(AdiDeviceState deviceState, String event)595 static void logDeviceState(AdiDeviceState deviceState, String event) { 596 final int deviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice( 597 deviceState.getDeviceType()); 598 final String deviceName = AudioSystem.getDeviceName(deviceType); 599 new MediaMetrics.Item(METRICS_DEVICE_PREFIX + deviceName) 600 .set(MediaMetrics.Property.ADDRESS, deviceState.getDeviceAddress()) 601 .set(MediaMetrics.Property.ENABLED, deviceState.isSAEnabled() ? "true" : "false") 602 .set(MediaMetrics.Property.EVENT, TextUtils.emptyIfNull(event)) 603 .set(MediaMetrics.Property.HAS_HEAD_TRACKER, 604 deviceState.hasHeadTracker() ? "true" 605 : "false") // this may be updated later. 606 .set(MediaMetrics.Property.HEAD_TRACKER_ENABLED, 607 deviceState.isHeadTrackerEnabled() ? "true" : "false") 608 .record(); 609 } 610 removeCompatibleAudioDevice(@onNull AudioDeviceAttributes ada)611 synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { 612 loglogi("removeCompatibleAudioDevice: dev=" + ada); 613 614 final AdiDeviceState deviceState = findSACompatibleDeviceStateForAudioDeviceAttributes(ada); 615 if (deviceState != null && deviceState.isSAEnabled()) { 616 deviceState.setSAEnabled(false); 617 onRoutingUpdated(); 618 mDeviceBroker.persistAudioDeviceSettings(); 619 logDeviceState(deviceState, "removeCompatibleAudioDevice"); 620 } 621 } 622 623 /** 624 * Returns a possibly aliased device type which is used 625 * for spatial audio settings (or TYPE_UNKNOWN if it doesn't exist). 626 */ 627 @AudioDeviceInfo.AudioDeviceType getCanonicalDeviceType(int deviceType, int internalDeviceType)628 private static int getCanonicalDeviceType(int deviceType, int internalDeviceType) { 629 if (isBluetoothDevice(internalDeviceType)) return deviceType; 630 631 final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE); 632 if (spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL) { 633 return AudioDeviceInfo.TYPE_BUILTIN_SPEAKER; 634 } else if (spatMode == SpatializationMode.SPATIALIZER_BINAURAL) { 635 return AudioDeviceInfo.TYPE_WIRED_HEADPHONES; 636 } 637 return AudioDeviceInfo.TYPE_UNKNOWN; 638 } 639 640 /** 641 * Returns the audio device state for the audio device attributes in case 642 * spatial audio is supported or null otherwise. 643 */ 644 @GuardedBy("this") 645 @Nullable findSACompatibleDeviceStateForAudioDeviceAttributes( AudioDeviceAttributes ada)646 private AdiDeviceState findSACompatibleDeviceStateForAudioDeviceAttributes( 647 AudioDeviceAttributes ada) { 648 final AdiDeviceState deviceState = 649 mDeviceBroker.findDeviceStateForAudioDeviceAttributes(ada, 650 getCanonicalDeviceType(ada.getType(), ada.getInternalType())); 651 if (deviceState == null) { 652 return null; 653 } 654 655 if (!isSADevice(deviceState)) { 656 return null; 657 } 658 659 return deviceState; 660 } 661 662 /** 663 * Return if Spatial Audio is enabled and available for the given device 664 * @param ada 665 * @return a pair of boolean, 1/ enabled? 2/ available? 666 */ evaluateState(AudioDeviceAttributes ada)667 private synchronized Pair<Boolean, Boolean> evaluateState(AudioDeviceAttributes ada) { 668 final @AudioDeviceInfo.AudioDeviceType int deviceType = ada.getType(); 669 // is the device type capable of doing SA? 670 if (!mSACapableDeviceTypes.contains(deviceType)) { 671 Log.i(TAG, "Device incompatible with Spatial Audio dev:" + ada); 672 return new Pair<>(false, false); 673 } 674 // what spatialization mode to use for this device? 675 final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE); 676 if (spatMode == Integer.MIN_VALUE) { 677 // error case, device not found 678 Log.e(TAG, "no spatialization mode found for device type:" + deviceType); 679 return new Pair<>(false, false); 680 } 681 final AdiDeviceState deviceState = findSACompatibleDeviceStateForAudioDeviceAttributes(ada); 682 if (deviceState == null) { 683 // no matching device state? 684 Log.i(TAG, "no spatialization device state found for Spatial Audio device:" + ada); 685 return new Pair<>(false, false); 686 } 687 boolean available = true; 688 if (isBluetoothDevice(deviceType)) { 689 // only checking headphones/binaural because external speakers cannot use transaural 690 // since their physical characteristics are unknown 691 if (deviceState.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_UNKNOWN 692 || deviceState.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_HEADPHONES) { 693 available = (spatMode == SpatializationMode.SPATIALIZER_BINAURAL) 694 && mBinauralSupported; 695 } else { 696 available = false; 697 } 698 } 699 // found the matching device state. 700 return new Pair<>(deviceState.isSAEnabled(), available); 701 } 702 addWirelessDeviceIfNew(@onNull AudioDeviceAttributes ada)703 private synchronized void addWirelessDeviceIfNew(@NonNull AudioDeviceAttributes ada) { 704 if (!isDeviceCompatibleWithSpatializationModes(ada)) { 705 return; 706 } 707 if (findSACompatibleDeviceStateForAudioDeviceAttributes(ada) == null) { 708 // wireless device types should be canonical, but we translate to be sure. 709 final int canonicalDeviceType = getCanonicalDeviceType(ada.getType(), 710 ada.getInternalType()); 711 if (canonicalDeviceType == AudioDeviceInfo.TYPE_UNKNOWN) { 712 Log.e(TAG, "addWirelessDeviceIfNew with incompatible AudioDeviceAttributes " 713 + ada); 714 return; 715 } 716 final AdiDeviceState deviceState = 717 new AdiDeviceState(canonicalDeviceType, ada.getInternalType(), 718 ada.getAddress()); 719 initSAState(deviceState); 720 mDeviceBroker.addOrUpdateDeviceSAStateInInventory(deviceState); 721 mDeviceBroker.persistAudioDeviceSettings(); 722 logDeviceState(deviceState, "addWirelessDeviceIfNew"); // may be updated later. 723 } 724 } 725 726 //------------------------------------------------------ 727 // states 728 isEnabled()729 synchronized boolean isEnabled() { 730 switch (mState) { 731 case STATE_UNINITIALIZED: 732 case STATE_NOT_SUPPORTED: 733 case STATE_DISABLED_UNAVAILABLE: 734 case STATE_DISABLED_AVAILABLE: 735 return false; 736 case STATE_ENABLED_UNAVAILABLE: 737 case STATE_ENABLED_AVAILABLE: 738 default: 739 return true; 740 } 741 } 742 isAvailable()743 synchronized boolean isAvailable() { 744 switch (mState) { 745 case STATE_UNINITIALIZED: 746 case STATE_NOT_SUPPORTED: 747 case STATE_ENABLED_UNAVAILABLE: 748 case STATE_DISABLED_UNAVAILABLE: 749 return false; 750 case STATE_DISABLED_AVAILABLE: 751 case STATE_ENABLED_AVAILABLE: 752 default: 753 return true; 754 } 755 } 756 refreshDevice(@onNull AudioDeviceAttributes ada)757 synchronized void refreshDevice(@NonNull AudioDeviceAttributes ada) { 758 final AdiDeviceState deviceState = findSACompatibleDeviceStateForAudioDeviceAttributes(ada); 759 if (isAvailableForAdiDeviceState(deviceState)) { 760 addCompatibleAudioDevice(ada, /*forceEnable=*/deviceState.isSAEnabled()); 761 setHeadTrackerEnabled(deviceState.isHeadTrackerEnabled(), ada); 762 } else { 763 removeCompatibleAudioDevice(ada); 764 } 765 } 766 isAvailableForDevice(@onNull AudioDeviceAttributes ada)767 synchronized boolean isAvailableForDevice(@NonNull AudioDeviceAttributes ada) { 768 if (ada.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) { 769 return false; 770 } 771 772 return isAvailableForAdiDeviceState( 773 findSACompatibleDeviceStateForAudioDeviceAttributes(ada)); 774 } 775 isAvailableForAdiDeviceState(AdiDeviceState deviceState)776 private boolean isAvailableForAdiDeviceState(AdiDeviceState deviceState) { 777 if (deviceState == null) { 778 return false; 779 } 780 781 if (isBluetoothDevice(deviceState.getInternalDeviceType()) 782 && deviceState.getAudioDeviceCategory() != AUDIO_DEVICE_CATEGORY_UNKNOWN 783 && deviceState.getAudioDeviceCategory() != AUDIO_DEVICE_CATEGORY_HEADPHONES) { 784 return false; 785 } 786 return true; 787 } 788 canBeSpatializedOnDevice(@onNull AudioAttributes attributes, @NonNull AudioFormat format, @NonNull ArrayList<AudioDeviceAttributes> devices)789 private synchronized boolean canBeSpatializedOnDevice(@NonNull AudioAttributes attributes, 790 @NonNull AudioFormat format, @NonNull ArrayList<AudioDeviceAttributes> devices) { 791 if (devices.isEmpty()) { 792 return false; 793 } 794 if (isDeviceCompatibleWithSpatializationModes(devices.get(0))) { 795 AudioDeviceAttributes[] devArray = new AudioDeviceAttributes[devices.size()]; 796 return AudioSystem.canBeSpatialized(attributes, format, devices.toArray(devArray)); 797 } 798 return false; 799 } 800 isDeviceCompatibleWithSpatializationModes(@onNull AudioDeviceAttributes ada)801 private boolean isDeviceCompatibleWithSpatializationModes(@NonNull AudioDeviceAttributes ada) { 802 // modeForDevice will be neither transaural or binaural for devices that do not support 803 // spatial audio. For instance mono devices like earpiece, speaker safe or sco must 804 // not be included. 805 final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(ada.getType(), 806 /*default when type not found*/ -1); 807 if ((modeForDevice == SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported) 808 || (modeForDevice == SpatializationMode.SPATIALIZER_TRANSAURAL 809 && mTransauralSupported)) { 810 return true; 811 } 812 return false; 813 } 814 isSADevice(AdiDeviceState deviceState)815 private boolean isSADevice(AdiDeviceState deviceState) { 816 return deviceState.getDeviceType() == getCanonicalDeviceType(deviceState.getDeviceType(), 817 deviceState.getInternalDeviceType()) && isDeviceCompatibleWithSpatializationModes( 818 deviceState.getAudioDeviceAttributes()); 819 } 820 setFeatureEnabled(boolean enabled)821 synchronized void setFeatureEnabled(boolean enabled) { 822 loglogi("setFeatureEnabled(" + enabled + ") was featureEnabled:" + mFeatureEnabled); 823 if (mFeatureEnabled == enabled) { 824 return; 825 } 826 mFeatureEnabled = enabled; 827 if (mFeatureEnabled) { 828 if (mState == STATE_NOT_SUPPORTED) { 829 Log.e(TAG, "Can't enabled Spatial Audio, unsupported"); 830 return; 831 } 832 if (mState == STATE_UNINITIALIZED) { 833 init(true); 834 } 835 setSpatializerEnabledInt(true); 836 } else { 837 setSpatializerEnabledInt(false); 838 } 839 } 840 setSpatializerEnabledInt(boolean enabled)841 synchronized void setSpatializerEnabledInt(boolean enabled) { 842 switch (mState) { 843 case STATE_UNINITIALIZED: 844 if (enabled) { 845 throw (new IllegalStateException("Can't enable when uninitialized")); 846 } 847 break; 848 case STATE_NOT_SUPPORTED: 849 if (enabled) { 850 Log.e(TAG, "Can't enable when unsupported"); 851 } 852 break; 853 case STATE_DISABLED_UNAVAILABLE: 854 case STATE_DISABLED_AVAILABLE: 855 if (enabled) { 856 createSpat(); 857 onRoutingUpdated(); 858 // onRoutingUpdated() can update the "enabled" state based on context 859 // and will call setDispatchFeatureEnabledState(). 860 } // else { nothing to do as already disabled } 861 break; 862 case STATE_ENABLED_UNAVAILABLE: 863 case STATE_ENABLED_AVAILABLE: 864 if (!enabled) { 865 releaseSpat(); 866 setDispatchFeatureEnabledState(false, "setSpatializerEnabledInt"); 867 } // else { nothing to do as already enabled } 868 break; 869 } 870 } 871 getCapableImmersiveAudioLevel()872 synchronized int getCapableImmersiveAudioLevel() { 873 return mCapableSpatLevel; 874 } 875 876 final RemoteCallbackList<ISpatializerCallback> mStateCallbacks = 877 new RemoteCallbackList<ISpatializerCallback>(); 878 registerStateCallback( @onNull ISpatializerCallback callback)879 synchronized void registerStateCallback( 880 @NonNull ISpatializerCallback callback) { 881 mStateCallbacks.register(callback); 882 } 883 unregisterStateCallback( @onNull ISpatializerCallback callback)884 synchronized void unregisterStateCallback( 885 @NonNull ISpatializerCallback callback) { 886 mStateCallbacks.unregister(callback); 887 } 888 889 /** 890 * Update the feature state, no-op if no change 891 * @param featureEnabled 892 */ setDispatchFeatureEnabledState(boolean featureEnabled, String source)893 private synchronized void setDispatchFeatureEnabledState(boolean featureEnabled, String source) 894 { 895 if (featureEnabled) { 896 switch (mState) { 897 case STATE_DISABLED_UNAVAILABLE: 898 mState = STATE_ENABLED_UNAVAILABLE; 899 break; 900 case STATE_DISABLED_AVAILABLE: 901 mState = STATE_ENABLED_AVAILABLE; 902 break; 903 case STATE_ENABLED_AVAILABLE: 904 case STATE_ENABLED_UNAVAILABLE: 905 // already enabled: no-op 906 loglogi("setDispatchFeatureEnabledState(" + featureEnabled 907 + ") no dispatch: mState:" 908 + spatStateString(mState) + " src:" + source); 909 return; 910 default: 911 throw (new IllegalStateException("Invalid mState:" + mState 912 + " for enabled true")); 913 } 914 } else { 915 switch (mState) { 916 case STATE_ENABLED_UNAVAILABLE: 917 mState = STATE_DISABLED_UNAVAILABLE; 918 break; 919 case STATE_ENABLED_AVAILABLE: 920 mState = STATE_DISABLED_AVAILABLE; 921 break; 922 case STATE_DISABLED_AVAILABLE: 923 case STATE_DISABLED_UNAVAILABLE: 924 // already disabled: no-op 925 loglogi("setDispatchFeatureEnabledState(" + featureEnabled 926 + ") no dispatch: mState:" + spatStateString(mState) 927 + " src:" + source); 928 return; 929 default: 930 throw (new IllegalStateException("Invalid mState:" + mState 931 + " for enabled false")); 932 } 933 } 934 loglogi("setDispatchFeatureEnabledState(" + featureEnabled 935 + ") mState:" + spatStateString(mState)); 936 final int nbCallbacks = mStateCallbacks.beginBroadcast(); 937 for (int i = 0; i < nbCallbacks; i++) { 938 try { 939 mStateCallbacks.getBroadcastItem(i) 940 .dispatchSpatializerEnabledChanged(featureEnabled); 941 } catch (RemoteException e) { 942 Log.e(TAG, "Error in dispatchSpatializerEnabledChanged", e); 943 } 944 } 945 mStateCallbacks.finishBroadcast(); 946 } 947 setDispatchAvailableState(boolean available)948 private synchronized void setDispatchAvailableState(boolean available) { 949 switch (mState) { 950 case STATE_UNINITIALIZED: 951 case STATE_NOT_SUPPORTED: 952 throw (new IllegalStateException( 953 "Should not update available state in state:" + mState)); 954 case STATE_DISABLED_UNAVAILABLE: 955 if (available) { 956 mState = STATE_DISABLED_AVAILABLE; 957 break; 958 } else { 959 // already in unavailable state 960 loglogi("setDispatchAvailableState(" + available 961 + ") no dispatch: mState:" + spatStateString(mState)); 962 return; 963 } 964 case STATE_ENABLED_UNAVAILABLE: 965 if (available) { 966 mState = STATE_ENABLED_AVAILABLE; 967 break; 968 } else { 969 // already in unavailable state 970 loglogi("setDispatchAvailableState(" + available 971 + ") no dispatch: mState:" + spatStateString(mState)); 972 return; 973 } 974 case STATE_DISABLED_AVAILABLE: 975 if (available) { 976 // already in available state 977 loglogi("setDispatchAvailableState(" + available 978 + ") no dispatch: mState:" + spatStateString(mState)); 979 return; 980 } else { 981 mState = STATE_DISABLED_UNAVAILABLE; 982 break; 983 } 984 case STATE_ENABLED_AVAILABLE: 985 if (available) { 986 // already in available state 987 loglogi("setDispatchAvailableState(" + available 988 + ") no dispatch: mState:" + spatStateString(mState)); 989 return; 990 } else { 991 mState = STATE_ENABLED_UNAVAILABLE; 992 break; 993 } 994 } 995 loglogi("setDispatchAvailableState(" + available + ") mState:" + spatStateString(mState)); 996 final int nbCallbacks = mStateCallbacks.beginBroadcast(); 997 for (int i = 0; i < nbCallbacks; i++) { 998 try { 999 mStateCallbacks.getBroadcastItem(i) 1000 .dispatchSpatializerAvailableChanged(available); 1001 } catch (RemoteException e) { 1002 Log.e(TAG, "Error in dispatchSpatializerEnabledChanged", e); 1003 } 1004 } 1005 mStateCallbacks.finishBroadcast(); 1006 } 1007 1008 //------------------------------------------------------ 1009 // native Spatializer management 1010 1011 /** 1012 * precondition: mState == STATE_DISABLED_* 1013 */ createSpat()1014 private void createSpat() { 1015 if (mSpat == null) { 1016 mSpatCallback = new SpatializerCallback(); 1017 mSpat = AudioSystem.getSpatializer(mSpatCallback); 1018 try { 1019 //TODO: register heatracking callback only when sensors are registered 1020 if (mIsHeadTrackingSupported) { 1021 mActualHeadTrackingMode = 1022 headTrackingModeTypeToSpatializerInt(mSpat.getActualHeadTrackingMode()); 1023 mSpat.registerHeadTrackingCallback(mSpatHeadTrackingCallback); 1024 } 1025 } catch (RemoteException e) { 1026 Log.e(TAG, "Can't configure head tracking", e); 1027 mState = STATE_NOT_SUPPORTED; 1028 mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; 1029 mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; 1030 } 1031 } 1032 } 1033 1034 /** 1035 * precondition: mState == STATE_ENABLED_* 1036 */ releaseSpat()1037 private void releaseSpat() { 1038 if (mSpat != null) { 1039 mSpatCallback = null; 1040 try { 1041 if (mIsHeadTrackingSupported) { 1042 mSpat.registerHeadTrackingCallback(null); 1043 } 1044 mHeadTrackerAvailable = false; 1045 mSpat.release(); 1046 } catch (RemoteException e) { 1047 Log.e(TAG, "Can't set release spatializer cleanly", e); 1048 } 1049 mSpat = null; 1050 } 1051 } 1052 1053 //------------------------------------------------------ 1054 // virtualization capabilities canBeSpatialized( @onNull AudioAttributes attributes, @NonNull AudioFormat format)1055 synchronized boolean canBeSpatialized( 1056 @NonNull AudioAttributes attributes, @NonNull AudioFormat format) { 1057 switch (mState) { 1058 case STATE_UNINITIALIZED: 1059 case STATE_NOT_SUPPORTED: 1060 case STATE_ENABLED_UNAVAILABLE: 1061 case STATE_DISABLED_UNAVAILABLE: 1062 logd("canBeSpatialized false due to state:" + mState); 1063 return false; 1064 case STATE_DISABLED_AVAILABLE: 1065 case STATE_ENABLED_AVAILABLE: 1066 break; 1067 } 1068 1069 // filter on AudioAttributes usage 1070 switch (attributes.getUsage()) { 1071 case AudioAttributes.USAGE_MEDIA: 1072 case AudioAttributes.USAGE_GAME: 1073 break; 1074 default: 1075 logd("canBeSpatialized false due to usage:" + attributes.getUsage()); 1076 return false; 1077 } 1078 1079 // going through adapter to take advantage of routing cache 1080 final ArrayList<AudioDeviceAttributes> devices = getRoutingDevices(attributes); 1081 if (devices.isEmpty()) { 1082 logloge("canBeSpatialized got no device for " + attributes); 1083 return false; 1084 } 1085 final boolean able = canBeSpatializedOnDevice(attributes, format, devices); 1086 logd("canBeSpatialized usage:" + attributes.getUsage() 1087 + " format:" + format.toLogFriendlyString() + " returning " + able); 1088 return able; 1089 } 1090 1091 //------------------------------------------------------ 1092 // head tracking 1093 final RemoteCallbackList<ISpatializerHeadTrackingModeCallback> mHeadTrackingModeCallbacks = 1094 new RemoteCallbackList<ISpatializerHeadTrackingModeCallback>(); 1095 registerHeadTrackingModeCallback( @onNull ISpatializerHeadTrackingModeCallback callback)1096 synchronized void registerHeadTrackingModeCallback( 1097 @NonNull ISpatializerHeadTrackingModeCallback callback) { 1098 mHeadTrackingModeCallbacks.register(callback); 1099 } 1100 unregisterHeadTrackingModeCallback( @onNull ISpatializerHeadTrackingModeCallback callback)1101 synchronized void unregisterHeadTrackingModeCallback( 1102 @NonNull ISpatializerHeadTrackingModeCallback callback) { 1103 mHeadTrackingModeCallbacks.unregister(callback); 1104 } 1105 1106 final RemoteCallbackList<ISpatializerHeadTrackerAvailableCallback> mHeadTrackerCallbacks = 1107 new RemoteCallbackList<>(); 1108 registerHeadTrackerAvailableCallback( @onNull ISpatializerHeadTrackerAvailableCallback cb, boolean register)1109 synchronized void registerHeadTrackerAvailableCallback( 1110 @NonNull ISpatializerHeadTrackerAvailableCallback cb, boolean register) { 1111 if (register) { 1112 mHeadTrackerCallbacks.register(cb); 1113 } else { 1114 mHeadTrackerCallbacks.unregister(cb); 1115 } 1116 } 1117 getSupportedHeadTrackingModes()1118 synchronized int[] getSupportedHeadTrackingModes() { 1119 return mSupportedHeadTrackingModes; 1120 } 1121 getActualHeadTrackingMode()1122 synchronized int getActualHeadTrackingMode() { 1123 return mActualHeadTrackingMode; 1124 } 1125 getDesiredHeadTrackingMode()1126 synchronized int getDesiredHeadTrackingMode() { 1127 return mDesiredHeadTrackingMode; 1128 } 1129 setGlobalTransform(@onNull float[] transform)1130 synchronized void setGlobalTransform(@NonNull float[] transform) { 1131 if (transform.length != 6) { 1132 throw new IllegalArgumentException("invalid array size" + transform.length); 1133 } 1134 if (!checkSpatializerForHeadTracking("setGlobalTransform")) { 1135 return; 1136 } 1137 try { 1138 mSpat.setGlobalTransform(transform); 1139 } catch (RemoteException e) { 1140 Log.e(TAG, "Error calling setGlobalTransform", e); 1141 } 1142 } 1143 recenterHeadTracker()1144 synchronized void recenterHeadTracker() { 1145 if (!checkSpatializerForHeadTracking("recenterHeadTracker")) { 1146 return; 1147 } 1148 try { 1149 mSpat.recenterHeadTracker(); 1150 } catch (RemoteException e) { 1151 Log.e(TAG, "Error calling recenterHeadTracker", e); 1152 } 1153 } 1154 setDisplayOrientation(float displayOrientation)1155 synchronized void setDisplayOrientation(float displayOrientation) { 1156 if (!checkSpatializer("setDisplayOrientation")) { 1157 return; 1158 } 1159 try { 1160 mSpat.setDisplayOrientation(displayOrientation); 1161 } catch (RemoteException e) { 1162 Log.e(TAG, "Error calling setDisplayOrientation", e); 1163 } 1164 } 1165 setFoldState(boolean folded)1166 synchronized void setFoldState(boolean folded) { 1167 if (!checkSpatializer("setFoldState")) { 1168 return; 1169 } 1170 try { 1171 mSpat.setFoldState(folded); 1172 } catch (RemoteException e) { 1173 Log.e(TAG, "Error calling setFoldState", e); 1174 } 1175 } 1176 setDesiredHeadTrackingMode(@patializer.HeadTrackingModeSet int mode)1177 synchronized void setDesiredHeadTrackingMode(@Spatializer.HeadTrackingModeSet int mode) { 1178 if (!checkSpatializerForHeadTracking("setDesiredHeadTrackingMode")) { 1179 return; 1180 } 1181 if (mode != Spatializer.HEAD_TRACKING_MODE_DISABLED) { 1182 mDesiredHeadTrackingModeWhenEnabled = mode; 1183 } 1184 try { 1185 if (mDesiredHeadTrackingMode != mode) { 1186 mDesiredHeadTrackingMode = mode; 1187 dispatchDesiredHeadTrackingMode(mode); 1188 } 1189 Log.i(TAG, "setDesiredHeadTrackingMode(" 1190 + Spatializer.headtrackingModeToString(mode) + ")"); 1191 mSpat.setDesiredHeadTrackingMode(spatializerIntToHeadTrackingModeType(mode)); 1192 } catch (RemoteException e) { 1193 Log.e(TAG, "Error calling setDesiredHeadTrackingMode", e); 1194 } 1195 } 1196 setHeadTrackerEnabled(boolean enabled, @NonNull AudioDeviceAttributes ada)1197 synchronized void setHeadTrackerEnabled(boolean enabled, @NonNull AudioDeviceAttributes ada) { 1198 if (!mIsHeadTrackingSupported) { 1199 Log.v(TAG, "no headtracking support, ignoring setHeadTrackerEnabled to " + enabled 1200 + " for " + ada); 1201 } 1202 final AdiDeviceState deviceState = findSACompatibleDeviceStateForAudioDeviceAttributes(ada); 1203 if (deviceState == null) return; 1204 if (!deviceState.hasHeadTracker()) { 1205 Log.e(TAG, "Called setHeadTrackerEnabled enabled:" + enabled 1206 + " device:" + ada + " on a device without headtracker"); 1207 return; 1208 } 1209 Log.i(TAG, "setHeadTrackerEnabled enabled:" + enabled + " device:" + ada); 1210 deviceState.setHeadTrackerEnabled(enabled); 1211 mDeviceBroker.persistAudioDeviceSettings(); 1212 logDeviceState(deviceState, "setHeadTrackerEnabled"); 1213 1214 // check current routing to see if it affects the headtracking mode 1215 if (sRoutingDevices.isEmpty()) { 1216 logloge("setHeadTrackerEnabled: no device, bailing"); 1217 return; 1218 } 1219 final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0); 1220 if (currentDevice.getType() == ada.getType() 1221 && currentDevice.getAddress().equals(ada.getAddress())) { 1222 setDesiredHeadTrackingMode(enabled ? mDesiredHeadTrackingModeWhenEnabled 1223 : Spatializer.HEAD_TRACKING_MODE_DISABLED); 1224 if (enabled && !mHeadTrackerAvailable) { 1225 postInitSensors(); 1226 } 1227 } 1228 } 1229 hasHeadTracker(@onNull AudioDeviceAttributes ada)1230 synchronized boolean hasHeadTracker(@NonNull AudioDeviceAttributes ada) { 1231 if (!mIsHeadTrackingSupported) { 1232 Log.v(TAG, "no headtracking support, hasHeadTracker always false for " + ada); 1233 return false; 1234 } 1235 final AdiDeviceState deviceState = findSACompatibleDeviceStateForAudioDeviceAttributes(ada); 1236 return deviceState != null && deviceState.hasHeadTracker(); 1237 } 1238 1239 /** 1240 * Configures device in list as having a head tracker 1241 * @param ada 1242 * @return true if the head tracker is enabled, false otherwise or if device not found 1243 */ setHasHeadTracker(@onNull AudioDeviceAttributes ada)1244 synchronized boolean setHasHeadTracker(@NonNull AudioDeviceAttributes ada) { 1245 if (!mIsHeadTrackingSupported) { 1246 Log.v(TAG, "no headtracking support, setHasHeadTracker always false for " + ada); 1247 return false; 1248 } 1249 final AdiDeviceState deviceState = findSACompatibleDeviceStateForAudioDeviceAttributes(ada); 1250 if (deviceState != null) { 1251 if (!deviceState.hasHeadTracker()) { 1252 deviceState.setHasHeadTracker(true); 1253 mDeviceBroker.persistAudioDeviceSettings(); 1254 logDeviceState(deviceState, "setHasHeadTracker"); 1255 } 1256 return deviceState.isHeadTrackerEnabled(); 1257 } 1258 Log.e(TAG, "setHasHeadTracker: device not found for:" + ada); 1259 return false; 1260 } 1261 isHeadTrackerEnabled(@onNull AudioDeviceAttributes ada)1262 synchronized boolean isHeadTrackerEnabled(@NonNull AudioDeviceAttributes ada) { 1263 if (!mIsHeadTrackingSupported) { 1264 Log.v(TAG, "no headtracking support, isHeadTrackerEnabled always false for " + ada); 1265 return false; 1266 } 1267 final AdiDeviceState deviceState = findSACompatibleDeviceStateForAudioDeviceAttributes(ada); 1268 return deviceState != null 1269 && deviceState.hasHeadTracker() && deviceState.isHeadTrackerEnabled(); 1270 } 1271 isHeadTrackerAvailable()1272 synchronized boolean isHeadTrackerAvailable() { 1273 return mHeadTrackerAvailable; 1274 } 1275 checkSpatializer(String funcName)1276 private boolean checkSpatializer(String funcName) { 1277 switch (mState) { 1278 case STATE_UNINITIALIZED: 1279 case STATE_NOT_SUPPORTED: 1280 return false; 1281 case STATE_ENABLED_UNAVAILABLE: 1282 case STATE_DISABLED_UNAVAILABLE: 1283 case STATE_DISABLED_AVAILABLE: 1284 case STATE_ENABLED_AVAILABLE: 1285 if (mSpat == null) { 1286 // try to recover by resetting the native spatializer state 1287 Log.e(TAG, "checkSpatializer(): called from " + funcName 1288 + "(), native spatializer should not be null in state: " + mState); 1289 postReset(); 1290 return false; 1291 } 1292 break; 1293 } 1294 return true; 1295 } 1296 checkSpatializerForHeadTracking(String funcName)1297 private boolean checkSpatializerForHeadTracking(String funcName) { 1298 return checkSpatializer(funcName) && mIsHeadTrackingSupported; 1299 } 1300 dispatchActualHeadTrackingMode(int newMode)1301 private void dispatchActualHeadTrackingMode(int newMode) { 1302 final int nbCallbacks = mHeadTrackingModeCallbacks.beginBroadcast(); 1303 for (int i = 0; i < nbCallbacks; i++) { 1304 try { 1305 mHeadTrackingModeCallbacks.getBroadcastItem(i) 1306 .dispatchSpatializerActualHeadTrackingModeChanged(newMode); 1307 } catch (RemoteException e) { 1308 Log.e(TAG, "Error in dispatchSpatializerActualHeadTrackingModeChanged(" 1309 + newMode + ")", e); 1310 } 1311 } 1312 mHeadTrackingModeCallbacks.finishBroadcast(); 1313 } 1314 dispatchDesiredHeadTrackingMode(int newMode)1315 private void dispatchDesiredHeadTrackingMode(int newMode) { 1316 final int nbCallbacks = mHeadTrackingModeCallbacks.beginBroadcast(); 1317 for (int i = 0; i < nbCallbacks; i++) { 1318 try { 1319 mHeadTrackingModeCallbacks.getBroadcastItem(i) 1320 .dispatchSpatializerDesiredHeadTrackingModeChanged(newMode); 1321 } catch (RemoteException e) { 1322 Log.e(TAG, "Error in dispatchSpatializerDesiredHeadTrackingModeChanged(" 1323 + newMode + ")", e); 1324 } 1325 } 1326 mHeadTrackingModeCallbacks.finishBroadcast(); 1327 } 1328 dispatchHeadTrackerAvailable(boolean available)1329 private void dispatchHeadTrackerAvailable(boolean available) { 1330 final int nbCallbacks = mHeadTrackerCallbacks.beginBroadcast(); 1331 for (int i = 0; i < nbCallbacks; i++) { 1332 try { 1333 mHeadTrackerCallbacks.getBroadcastItem(i) 1334 .dispatchSpatializerHeadTrackerAvailable(available); 1335 } catch (RemoteException e) { 1336 Log.e(TAG, "Error in dispatchSpatializerHeadTrackerAvailable(" 1337 + available + ")", e); 1338 } 1339 } 1340 mHeadTrackerCallbacks.finishBroadcast(); 1341 } 1342 1343 //------------------------------------------------------ 1344 // head pose 1345 final RemoteCallbackList<ISpatializerHeadToSoundStagePoseCallback> mHeadPoseCallbacks = 1346 new RemoteCallbackList<ISpatializerHeadToSoundStagePoseCallback>(); 1347 registerHeadToSoundstagePoseCallback( @onNull ISpatializerHeadToSoundStagePoseCallback callback)1348 synchronized void registerHeadToSoundstagePoseCallback( 1349 @NonNull ISpatializerHeadToSoundStagePoseCallback callback) { 1350 mHeadPoseCallbacks.register(callback); 1351 } 1352 unregisterHeadToSoundstagePoseCallback( @onNull ISpatializerHeadToSoundStagePoseCallback callback)1353 synchronized void unregisterHeadToSoundstagePoseCallback( 1354 @NonNull ISpatializerHeadToSoundStagePoseCallback callback) { 1355 mHeadPoseCallbacks.unregister(callback); 1356 } 1357 dispatchPoseUpdate(float[] pose)1358 private void dispatchPoseUpdate(float[] pose) { 1359 final int nbCallbacks = mHeadPoseCallbacks.beginBroadcast(); 1360 for (int i = 0; i < nbCallbacks; i++) { 1361 try { 1362 mHeadPoseCallbacks.getBroadcastItem(i) 1363 .dispatchPoseChanged(pose); 1364 } catch (RemoteException e) { 1365 Log.e(TAG, "Error in dispatchPoseChanged", e); 1366 } 1367 } 1368 mHeadPoseCallbacks.finishBroadcast(); 1369 } 1370 1371 //------------------------------------------------------ 1372 // vendor parameters setEffectParameter(int key, @NonNull byte[] value)1373 synchronized void setEffectParameter(int key, @NonNull byte[] value) { 1374 switch (mState) { 1375 case STATE_UNINITIALIZED: 1376 case STATE_NOT_SUPPORTED: 1377 throw (new IllegalStateException( 1378 "Can't set parameter key:" + key + " without a spatializer")); 1379 case STATE_ENABLED_UNAVAILABLE: 1380 case STATE_DISABLED_UNAVAILABLE: 1381 case STATE_DISABLED_AVAILABLE: 1382 case STATE_ENABLED_AVAILABLE: 1383 if (mSpat == null) { 1384 Log.e(TAG, "setParameter(" + key + "): null spatializer in state: " + mState); 1385 return; 1386 } 1387 break; 1388 } 1389 // mSpat != null 1390 try { 1391 mSpat.setParameter(key, value); 1392 } catch (RemoteException e) { 1393 Log.e(TAG, "Error in setParameter for key:" + key, e); 1394 } 1395 } 1396 getEffectParameter(int key, @NonNull byte[] value)1397 synchronized void getEffectParameter(int key, @NonNull byte[] value) { 1398 switch (mState) { 1399 case STATE_UNINITIALIZED: 1400 case STATE_NOT_SUPPORTED: 1401 throw (new IllegalStateException( 1402 "Can't get parameter key:" + key + " without a spatializer")); 1403 case STATE_ENABLED_UNAVAILABLE: 1404 case STATE_DISABLED_UNAVAILABLE: 1405 case STATE_DISABLED_AVAILABLE: 1406 case STATE_ENABLED_AVAILABLE: 1407 if (mSpat == null) { 1408 Log.e(TAG, "getParameter(" + key + "): null spatializer in state: " + mState); 1409 return; 1410 } 1411 break; 1412 } 1413 // mSpat != null 1414 try { 1415 mSpat.getParameter(key, value); 1416 } catch (RemoteException e) { 1417 Log.e(TAG, "Error in getParameter for key:" + key, e); 1418 } 1419 } 1420 1421 //------------------------------------------------------ 1422 // output 1423 1424 /** @see Spatializer#getOutput */ getOutput()1425 synchronized int getOutput() { 1426 switch (mState) { 1427 case STATE_UNINITIALIZED: 1428 case STATE_NOT_SUPPORTED: 1429 throw (new IllegalStateException( 1430 "Can't get output without a spatializer")); 1431 case STATE_ENABLED_UNAVAILABLE: 1432 case STATE_DISABLED_UNAVAILABLE: 1433 case STATE_DISABLED_AVAILABLE: 1434 case STATE_ENABLED_AVAILABLE: 1435 if (mSpat == null) { 1436 throw (new IllegalStateException( 1437 "null Spatializer for getOutput")); 1438 } 1439 break; 1440 } 1441 // mSpat != null 1442 try { 1443 return mSpat.getOutput(); 1444 } catch (RemoteException e) { 1445 Log.e(TAG, "Error in getOutput", e); 1446 return 0; 1447 } 1448 } 1449 1450 final RemoteCallbackList<ISpatializerOutputCallback> mOutputCallbacks = 1451 new RemoteCallbackList<ISpatializerOutputCallback>(); 1452 registerSpatializerOutputCallback( @onNull ISpatializerOutputCallback callback)1453 synchronized void registerSpatializerOutputCallback( 1454 @NonNull ISpatializerOutputCallback callback) { 1455 mOutputCallbacks.register(callback); 1456 } 1457 unregisterSpatializerOutputCallback( @onNull ISpatializerOutputCallback callback)1458 synchronized void unregisterSpatializerOutputCallback( 1459 @NonNull ISpatializerOutputCallback callback) { 1460 mOutputCallbacks.unregister(callback); 1461 } 1462 dispatchOutputUpdate(int output)1463 private void dispatchOutputUpdate(int output) { 1464 final int nbCallbacks = mOutputCallbacks.beginBroadcast(); 1465 for (int i = 0; i < nbCallbacks; i++) { 1466 try { 1467 mOutputCallbacks.getBroadcastItem(i).dispatchSpatializerOutputChanged(output); 1468 } catch (RemoteException e) { 1469 Log.e(TAG, "Error in dispatchOutputUpdate", e); 1470 } 1471 } 1472 mOutputCallbacks.finishBroadcast(); 1473 } 1474 1475 //------------------------------------------------------ 1476 // sensors postInitSensors()1477 private void postInitSensors() { 1478 mAudioService.postInitSpatializerHeadTrackingSensors(); 1479 } 1480 onInitSensors()1481 synchronized void onInitSensors() { 1482 final boolean init = mFeatureEnabled && (mSpatLevel != SpatializationLevel.NONE); 1483 final String action = init ? "initializing" : "releasing"; 1484 if (mSpat == null) { 1485 logloge("not " + action + " sensors, null spatializer"); 1486 return; 1487 } 1488 if (!mIsHeadTrackingSupported) { 1489 logloge("not " + action + " sensors, spatializer doesn't support headtracking"); 1490 return; 1491 } 1492 int headHandle = -1; 1493 int screenHandle = -1; 1494 if (init) { 1495 if (mSensorManager == null) { 1496 try { 1497 mSensorManager = (SensorManager) 1498 mAudioService.mContext.getSystemService(Context.SENSOR_SERVICE); 1499 mDynSensorCallback = new HelperDynamicSensorCallback(); 1500 mSensorManager.registerDynamicSensorCallback(mDynSensorCallback); 1501 } catch (Exception e) { 1502 Log.e(TAG, "Error with SensorManager, can't initialize sensors", e); 1503 mSensorManager = null; 1504 mDynSensorCallback = null; 1505 return; 1506 } 1507 } 1508 // initialize sensor handles 1509 // TODO check risk of race condition for updating the association of a head tracker 1510 // and an audio device: 1511 // does this happen before routing is updated? 1512 // avoid by supporting adding device here AND in onRoutingUpdated() 1513 headHandle = getHeadSensorHandleUpdateTracker(); 1514 loglogi("head tracker sensor handle initialized to " + headHandle); 1515 screenHandle = getScreenSensorHandle(); 1516 Log.i(TAG, "found screen sensor handle initialized to " + screenHandle); 1517 } else { 1518 if (mSensorManager != null && mDynSensorCallback != null) { 1519 mSensorManager.unregisterDynamicSensorCallback(mDynSensorCallback); 1520 mSensorManager = null; 1521 mDynSensorCallback = null; 1522 } 1523 // -1 is disable value for both screen and head tracker handles 1524 } 1525 try { 1526 Log.i(TAG, "setScreenSensor:" + screenHandle); 1527 mSpat.setScreenSensor(screenHandle); 1528 } catch (Exception e) { 1529 Log.e(TAG, "Error calling setScreenSensor:" + screenHandle, e); 1530 } 1531 try { 1532 Log.i(TAG, "setHeadSensor:" + headHandle); 1533 mSpat.setHeadSensor(headHandle); 1534 if (mHeadTrackerAvailable != (headHandle != -1)) { 1535 mHeadTrackerAvailable = (headHandle != -1); 1536 dispatchHeadTrackerAvailable(mHeadTrackerAvailable); 1537 } 1538 } catch (Exception e) { 1539 Log.e(TAG, "Error calling setHeadSensor:" + headHandle, e); 1540 } 1541 setDesiredHeadTrackingMode(mDesiredHeadTrackingMode); 1542 } 1543 1544 //------------------------------------------------------ 1545 // SDK <-> AIDL converters headTrackingModeTypeToSpatializerInt(byte mode)1546 private static int headTrackingModeTypeToSpatializerInt(byte mode) { 1547 switch (mode) { 1548 case SpatializerHeadTrackingMode.OTHER: 1549 return Spatializer.HEAD_TRACKING_MODE_OTHER; 1550 case SpatializerHeadTrackingMode.DISABLED: 1551 return Spatializer.HEAD_TRACKING_MODE_DISABLED; 1552 case SpatializerHeadTrackingMode.RELATIVE_WORLD: 1553 return Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD; 1554 case SpatializerHeadTrackingMode.RELATIVE_SCREEN: 1555 return Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE; 1556 default: 1557 throw (new IllegalArgumentException("Unexpected head tracking mode:" + mode)); 1558 } 1559 } 1560 spatializerIntToHeadTrackingModeType(int sdkMode)1561 private static byte spatializerIntToHeadTrackingModeType(int sdkMode) { 1562 switch (sdkMode) { 1563 case Spatializer.HEAD_TRACKING_MODE_OTHER: 1564 return SpatializerHeadTrackingMode.OTHER; 1565 case Spatializer.HEAD_TRACKING_MODE_DISABLED: 1566 return SpatializerHeadTrackingMode.DISABLED; 1567 case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD: 1568 return SpatializerHeadTrackingMode.RELATIVE_WORLD; 1569 case Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE: 1570 return SpatializerHeadTrackingMode.RELATIVE_SCREEN; 1571 default: 1572 throw (new IllegalArgumentException("Unexpected head tracking mode:" + sdkMode)); 1573 } 1574 } 1575 spatializationLevelToSpatializerInt(byte level)1576 private static int spatializationLevelToSpatializerInt(byte level) { 1577 switch (level) { 1578 case SpatializationLevel.NONE: 1579 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; 1580 case SpatializationLevel.SPATIALIZER_MULTICHANNEL: 1581 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL; 1582 case SpatializationLevel.SPATIALIZER_MCHAN_BED_PLUS_OBJECTS: 1583 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MCHAN_BED_PLUS_OBJECTS; 1584 default: 1585 throw (new IllegalArgumentException("Unexpected spatializer level:" + level)); 1586 } 1587 } 1588 dump(PrintWriter pw)1589 void dump(PrintWriter pw) { 1590 pw.println("SpatializerHelper:"); 1591 pw.println("\tmState:" + mState); 1592 pw.println("\tmSpatLevel:" + mSpatLevel); 1593 pw.println("\tmCapableSpatLevel:" + mCapableSpatLevel); 1594 pw.println("\tmIsHeadTrackingSupported:" + mIsHeadTrackingSupported); 1595 StringBuilder modesString = new StringBuilder(); 1596 for (int mode : mSupportedHeadTrackingModes) { 1597 modesString.append(Spatializer.headtrackingModeToString(mode)).append(" "); 1598 } 1599 pw.println("\tsupported head tracking modes:" + modesString); 1600 pw.println("\tmDesiredHeadTrackingMode:" 1601 + Spatializer.headtrackingModeToString(mDesiredHeadTrackingMode)); 1602 pw.println("\tmActualHeadTrackingMode:" 1603 + Spatializer.headtrackingModeToString(mActualHeadTrackingMode)); 1604 pw.println("\theadtracker available:" + mHeadTrackerAvailable); 1605 pw.println("\tsupports binaural:" + mBinauralSupported + " / transaural:" 1606 + mTransauralSupported); 1607 pw.println("\tmSpatOutput:" + mSpatOutput); 1608 } 1609 spatStateString(int state)1610 private static String spatStateString(int state) { 1611 switch (state) { 1612 case STATE_UNINITIALIZED: 1613 return "STATE_UNINITIALIZED"; 1614 case STATE_NOT_SUPPORTED: 1615 return "STATE_NOT_SUPPORTED"; 1616 case STATE_DISABLED_UNAVAILABLE: 1617 return "STATE_DISABLED_UNAVAILABLE"; 1618 case STATE_ENABLED_UNAVAILABLE: 1619 return "STATE_ENABLED_UNAVAILABLE"; 1620 case STATE_ENABLED_AVAILABLE: 1621 return "STATE_ENABLED_AVAILABLE"; 1622 case STATE_DISABLED_AVAILABLE: 1623 return "STATE_DISABLED_AVAILABLE"; 1624 default: 1625 return "invalid state"; 1626 } 1627 } 1628 getHeadSensorHandleUpdateTracker()1629 private int getHeadSensorHandleUpdateTracker() { 1630 int headHandle = -1; 1631 if (sRoutingDevices.isEmpty()) { 1632 logloge("getHeadSensorHandleUpdateTracker: no device, no head tracker"); 1633 return headHandle; 1634 } 1635 final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0); 1636 UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(currentDevice); 1637 // We limit only to Sensor.TYPE_HEAD_TRACKER here to avoid confusion 1638 // with gaming sensors. (Note that Sensor.TYPE_ROTATION_VECTOR 1639 // and Sensor.TYPE_GAME_ROTATION_VECTOR are supported internally by 1640 // SensorPoseProvider). 1641 // Note: this is a dynamic sensor list right now. 1642 List<Sensor> sensors = mSensorManager.getDynamicSensorList(Sensor.TYPE_HEAD_TRACKER); 1643 for (Sensor sensor : sensors) { 1644 final UUID uuid = sensor.getUuid(); 1645 if (uuid.equals(routingDeviceUuid)) { 1646 headHandle = sensor.getHandle(); 1647 if (!setHasHeadTracker(currentDevice)) { 1648 headHandle = -1; 1649 } 1650 break; 1651 } 1652 if (uuid.equals(UuidUtils.STANDALONE_UUID)) { 1653 headHandle = sensor.getHandle(); 1654 // we do not break, perhaps we find a head tracker on device. 1655 } 1656 } 1657 return headHandle; 1658 } 1659 getScreenSensorHandle()1660 private int getScreenSensorHandle() { 1661 int screenHandle = -1; 1662 Sensor screenSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); 1663 if (screenSensor != null) { 1664 screenHandle = screenSensor.getHandle(); 1665 } 1666 return screenHandle; 1667 } 1668 1669 /** 1670 * Returns routing for the given attributes 1671 * @param aa AudioAttributes whose routing is being queried 1672 * @return a non-null never-empty list of devices. If the routing query failed, the list 1673 * will contain null. 1674 */ getRoutingDevices(AudioAttributes aa)1675 private @NonNull ArrayList<AudioDeviceAttributes> getRoutingDevices(AudioAttributes aa) { 1676 final ArrayList<AudioDeviceAttributes> devices = mASA.getDevicesForAttributes( 1677 aa, false /* forVolume */); 1678 for (AudioDeviceAttributes ada : devices) { 1679 if (ada == null) { 1680 // invalid entry, reject this routing query by returning an empty list 1681 return new ArrayList<>(0); 1682 } 1683 } 1684 return devices; 1685 } 1686 loglogi(String msg)1687 private static void loglogi(String msg) { 1688 AudioService.sSpatialLogger.enqueueAndLog(msg, EventLogger.Event.ALOGI, TAG); 1689 } 1690 logloge(String msg)1691 private static String logloge(String msg) { 1692 AudioService.sSpatialLogger.enqueueAndLog(msg, EventLogger.Event.ALOGE, TAG); 1693 return msg; 1694 } 1695 1696 //------------------------------------------------ 1697 // for testing purposes only forceStateForTest(int state)1698 /*package*/ synchronized void forceStateForTest(int state) { 1699 mState = state; 1700 } 1701 initForTest(boolean hasBinaural, boolean hasTransaural)1702 /*package*/ synchronized void initForTest(boolean hasBinaural, boolean hasTransaural) { 1703 mBinauralSupported = hasBinaural; 1704 mTransauralSupported = hasTransaural; 1705 } 1706 } 1707