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