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 android.media; 18 19 import android.annotation.CallbackExecutor; 20 import android.annotation.IntDef; 21 import android.annotation.IntRange; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.RequiresPermission; 25 import android.annotation.SuppressLint; 26 import android.annotation.SystemApi; 27 import android.media.CallbackUtil.ListenerInfo; 28 import android.media.permission.ClearCallingIdentityContext; 29 import android.media.permission.SafeCloseable; 30 import android.os.RemoteException; 31 import android.util.Log; 32 33 import com.android.internal.annotations.GuardedBy; 34 35 import java.lang.annotation.Retention; 36 import java.lang.annotation.RetentionPolicy; 37 import java.util.ArrayList; 38 import java.util.List; 39 import java.util.Objects; 40 import java.util.concurrent.Executor; 41 42 /** 43 * Spatializer provides access to querying capabilities and behavior of sound spatialization 44 * on the device. 45 * Sound spatialization simulates sounds originating around the listener as if they were coming 46 * from virtual speakers placed around the listener.<br> 47 * Support for spatialization is optional, use {@link AudioManager#getSpatializer()} to obtain an 48 * instance of this class if the feature is supported. 49 * 50 */ 51 public class Spatializer { 52 53 private final @NonNull AudioManager mAm; 54 55 private static final String TAG = "Spatializer"; 56 57 /** 58 * @hide 59 * Constructor with AudioManager acting as proxy to AudioService 60 * @param am a non-null AudioManager 61 */ Spatializer(@onNull AudioManager am)62 protected Spatializer(@NonNull AudioManager am) { 63 mAm = Objects.requireNonNull(am); 64 } 65 66 /** 67 * Returns whether spatialization is enabled or not. 68 * A false value can originate for instance from the user electing to 69 * disable the feature, or when the feature is not supported on the device (indicated 70 * by {@link #getImmersiveAudioLevel()} returning {@link #SPATIALIZER_IMMERSIVE_LEVEL_NONE}). 71 * <br> 72 * Note that this state reflects a platform-wide state of the "desire" to use spatialization, 73 * but availability of the audio processing is still dictated by the compatibility between 74 * the effect and the hardware configuration, as indicated by {@link #isAvailable()}. 75 * @return {@code true} if spatialization is enabled 76 * @see #isAvailable() 77 */ isEnabled()78 public boolean isEnabled() { 79 try { 80 return mAm.getService().isSpatializerEnabled(); 81 } catch (RemoteException e) { 82 Log.e(TAG, "Error querying isSpatializerEnabled, returning false", e); 83 return false; 84 } 85 } 86 87 /** 88 * Returns whether spatialization is available. 89 * Reasons for spatialization being unavailable include situations where audio output is 90 * incompatible with sound spatialization, such as playback on a monophonic speaker.<br> 91 * Note that spatialization can be available, but disabled by the user, in which case this 92 * method would still return {@code true}, whereas {@link #isEnabled()} 93 * would return {@code false}.<br> 94 * Also when the feature is not supported on the device (indicated 95 * by {@link #getImmersiveAudioLevel()} returning {@link #SPATIALIZER_IMMERSIVE_LEVEL_NONE}), 96 * the return value will be false. 97 * @return {@code true} if the spatializer effect is available and capable 98 * of processing the audio for the current configuration of the device, 99 * {@code false} otherwise. 100 * @see #isEnabled() 101 */ isAvailable()102 public boolean isAvailable() { 103 try { 104 return mAm.getService().isSpatializerAvailable(); 105 } catch (RemoteException e) { 106 Log.e(TAG, "Error querying isSpatializerAvailable, returning false", e); 107 return false; 108 } 109 } 110 111 /** 112 * @hide 113 * Returns whether spatialization is available for a given audio device 114 * Reasons for spatialization being unavailable include situations where audio output is 115 * incompatible with sound spatialization, such as the device being a monophonic speaker, or 116 * the spatializer effect not supporting transaural processing when querying for speaker. 117 * @param device the audio device for which spatializer availability is queried 118 * @return {@code true} if the spatializer effect is available and capable 119 * of processing the audio over the given audio device, 120 * {@code false} otherwise. 121 * @see #isEnabled() 122 */ 123 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 124 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) isAvailableForDevice(@onNull AudioDeviceAttributes device)125 public boolean isAvailableForDevice(@NonNull AudioDeviceAttributes device) { 126 Objects.requireNonNull(device); 127 try { 128 return mAm.getService().isSpatializerAvailableForDevice(device); 129 } catch (RemoteException e) { 130 e.rethrowFromSystemServer(); 131 } 132 return false; 133 } 134 135 /** 136 * @hide 137 * Returns whether the given device has an associated headtracker 138 * @param device the audio device to query 139 * @return true if the device has a head tracker, false otherwise 140 */ 141 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 142 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) hasHeadTracker(@onNull AudioDeviceAttributes device)143 public boolean hasHeadTracker(@NonNull AudioDeviceAttributes device) { 144 Objects.requireNonNull(device); 145 try { 146 return mAm.getService().hasHeadTracker(device); 147 } catch (RemoteException e) { 148 e.rethrowFromSystemServer(); 149 } 150 return false; 151 } 152 153 /** 154 * @hide 155 * Enables or disables the head tracker of the given device 156 * @param enabled true to enable, false to disable 157 * @param device the device whose head tracker state is changed 158 */ 159 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 160 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) setHeadTrackerEnabled(boolean enabled, @NonNull AudioDeviceAttributes device)161 public void setHeadTrackerEnabled(boolean enabled, @NonNull AudioDeviceAttributes device) { 162 Objects.requireNonNull(device); 163 try { 164 mAm.getService().setHeadTrackerEnabled(enabled, device); 165 } catch (RemoteException e) { 166 e.rethrowFromSystemServer(); 167 } 168 } 169 170 /** 171 * @hide 172 * Returns whether the head tracker of the device is enabled 173 * @param device the device to query 174 * @return true if the head tracker is enabled, false if disabled or if there isn't one 175 */ 176 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 177 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) isHeadTrackerEnabled(@onNull AudioDeviceAttributes device)178 public boolean isHeadTrackerEnabled(@NonNull AudioDeviceAttributes device) { 179 Objects.requireNonNull(device); 180 try { 181 return mAm.getService().isHeadTrackerEnabled(device); 182 } catch (RemoteException e) { 183 e.rethrowFromSystemServer(); 184 } 185 return false; 186 } 187 188 /** 189 * Returns whether a head tracker is currently available for the audio device used by the 190 * spatializer effect. 191 * @return true if a head tracker is available and the effect is enabled, false otherwise. 192 * @see OnHeadTrackerAvailableListener 193 * @see #addOnHeadTrackerAvailableListener(Executor, OnHeadTrackerAvailableListener) 194 */ isHeadTrackerAvailable()195 public boolean isHeadTrackerAvailable() { 196 try { 197 return mAm.getService().isHeadTrackerAvailable(); 198 } catch (RemoteException e) { 199 e.rethrowFromSystemServer(); 200 } 201 return false; 202 } 203 204 /** 205 * Adds a listener to be notified of changes to the availability of a head tracker. 206 * @param executor the {@code Executor} handling the callback 207 * @param listener the listener to receive availability updates 208 * @see #removeOnHeadTrackerAvailableListener(OnHeadTrackerAvailableListener) 209 */ addOnHeadTrackerAvailableListener(@onNull @allbackExecutor Executor executor, @NonNull OnHeadTrackerAvailableListener listener)210 public void addOnHeadTrackerAvailableListener(@NonNull @CallbackExecutor Executor executor, 211 @NonNull OnHeadTrackerAvailableListener listener) { 212 mHeadTrackerListenerMgr.addListener(executor, listener, 213 "addOnHeadTrackerAvailableListener", 214 () -> new SpatializerHeadTrackerAvailableDispatcherStub()); 215 } 216 217 /** 218 * Removes a previously registered listener for the availability of a head tracker. 219 * @param listener the listener previously registered with 220 * {@link #addOnHeadTrackerAvailableListener(Executor, OnHeadTrackerAvailableListener)} 221 */ removeOnHeadTrackerAvailableListener( @onNull OnHeadTrackerAvailableListener listener)222 public void removeOnHeadTrackerAvailableListener( 223 @NonNull OnHeadTrackerAvailableListener listener) { 224 mHeadTrackerListenerMgr.removeListener(listener, "removeOnHeadTrackerAvailableListener"); 225 } 226 227 /** @hide */ 228 @IntDef(flag = false, value = { 229 SPATIALIZER_IMMERSIVE_LEVEL_OTHER, 230 SPATIALIZER_IMMERSIVE_LEVEL_NONE, 231 SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL, 232 }) 233 @Retention(RetentionPolicy.SOURCE) 234 public @interface ImmersiveAudioLevel {}; 235 236 /** 237 * Constant indicating the {@code Spatializer} on this device supports a spatialization 238 * mode that differs from the ones available at this SDK level. 239 * @see #getImmersiveAudioLevel() 240 */ 241 public static final int SPATIALIZER_IMMERSIVE_LEVEL_OTHER = -1; 242 243 /** 244 * Constant indicating there are no spatialization capabilities supported on this device. 245 * @see #getImmersiveAudioLevel() 246 */ 247 public static final int SPATIALIZER_IMMERSIVE_LEVEL_NONE = 0; 248 249 /** 250 * Constant indicating the {@code Spatializer} on this device supports multichannel 251 * spatialization. 252 * @see #getImmersiveAudioLevel() 253 */ 254 public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1; 255 256 /** 257 * @hide 258 * Constant indicating the {@code Spatializer} on this device supports the spatialization of 259 * multichannel bed plus objects. 260 * @see #getImmersiveAudioLevel() 261 */ 262 public static final int SPATIALIZER_IMMERSIVE_LEVEL_MCHAN_BED_PLUS_OBJECTS = 2; 263 264 /** @hide */ 265 @IntDef(flag = false, value = { 266 HEAD_TRACKING_MODE_UNSUPPORTED, 267 HEAD_TRACKING_MODE_DISABLED, 268 HEAD_TRACKING_MODE_RELATIVE_WORLD, 269 HEAD_TRACKING_MODE_RELATIVE_DEVICE, 270 }) public @interface HeadTrackingMode {}; 271 272 /** @hide */ 273 @IntDef(flag = false, value = { 274 HEAD_TRACKING_MODE_DISABLED, 275 HEAD_TRACKING_MODE_RELATIVE_WORLD, 276 HEAD_TRACKING_MODE_RELATIVE_DEVICE, 277 }) public @interface HeadTrackingModeSet {}; 278 279 /** @hide */ 280 @IntDef(flag = false, value = { 281 HEAD_TRACKING_MODE_RELATIVE_WORLD, 282 HEAD_TRACKING_MODE_RELATIVE_DEVICE, 283 }) public @interface HeadTrackingModeSupported {}; 284 285 /** 286 * @hide 287 * Constant indicating head tracking is not supported by this {@code Spatializer} 288 * @see #getHeadTrackingMode() 289 */ 290 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 291 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) 292 public static final int HEAD_TRACKING_MODE_UNSUPPORTED = -2; 293 294 /** 295 * @hide 296 * Constant indicating head tracking is disabled on this {@code Spatializer} 297 * @see #getHeadTrackingMode() 298 */ 299 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 300 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) 301 public static final int HEAD_TRACKING_MODE_DISABLED = -1; 302 303 /** 304 * @hide 305 * Constant indicating head tracking is in a mode whose behavior is unknown. This is not an 306 * error state but represents a customized behavior not defined by this API. 307 * @see #getHeadTrackingMode() 308 */ 309 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 310 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) 311 public static final int HEAD_TRACKING_MODE_OTHER = 0; 312 313 /** 314 * @hide 315 * Constant indicating head tracking is tracking the user's position / orientation relative to 316 * the world around them 317 * @see #getHeadTrackingMode() 318 */ 319 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 320 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) 321 public static final int HEAD_TRACKING_MODE_RELATIVE_WORLD = 1; 322 323 /** 324 * @hide 325 * Constant indicating head tracking is tracking the user's position / orientation relative to 326 * the device 327 * @see #getHeadTrackingMode() 328 */ 329 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 330 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) 331 public static final int HEAD_TRACKING_MODE_RELATIVE_DEVICE = 2; 332 333 /** 334 * @hide 335 * Head tracking mode to string conversion 336 * @param mode a valid head tracking mode 337 * @return a string containing the matching constant name 338 */ headtrackingModeToString(int mode)339 public static final String headtrackingModeToString(int mode) { 340 switch(mode) { 341 case HEAD_TRACKING_MODE_UNSUPPORTED: 342 return "HEAD_TRACKING_MODE_UNSUPPORTED"; 343 case HEAD_TRACKING_MODE_DISABLED: 344 return "HEAD_TRACKING_MODE_DISABLED"; 345 case HEAD_TRACKING_MODE_OTHER: 346 return "HEAD_TRACKING_MODE_OTHER"; 347 case HEAD_TRACKING_MODE_RELATIVE_WORLD: 348 return "HEAD_TRACKING_MODE_RELATIVE_WORLD"; 349 case HEAD_TRACKING_MODE_RELATIVE_DEVICE: 350 return "HEAD_TRACKING_MODE_RELATIVE_DEVICE"; 351 default: 352 return "head tracking mode unknown " + mode; 353 } 354 } 355 356 /** 357 * Return the level of support for the spatialization feature on this device. 358 * This level of support is independent of whether the {@code Spatializer} is currently 359 * enabled or available and will not change over time. 360 * @return the level of spatialization support 361 * @see #isEnabled() 362 * @see #isAvailable() 363 */ getImmersiveAudioLevel()364 public @ImmersiveAudioLevel int getImmersiveAudioLevel() { 365 int level = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; 366 try { 367 level = mAm.getService().getSpatializerImmersiveAudioLevel(); 368 } catch (Exception e) { /* using NONE */ } 369 return level; 370 } 371 372 /** 373 * @hide 374 * Enables / disables the spatializer effect. 375 * Changing the enabled state will trigger the public 376 * {@link OnSpatializerStateChangedListener#onSpatializerEnabledChanged(Spatializer, boolean)} 377 * registered listeners. 378 * @param enabled {@code true} for enabling the effect 379 */ 380 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 381 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) setEnabled(boolean enabled)382 public void setEnabled(boolean enabled) { 383 try { 384 mAm.getService().setSpatializerEnabled(enabled); 385 } catch (RemoteException e) { 386 Log.e(TAG, "Error calling setSpatializerEnabled", e); 387 } 388 } 389 390 /** 391 * An interface to be notified of changes to the state of the spatializer effect. 392 */ 393 public interface OnSpatializerStateChangedListener { 394 /** 395 * Called when the enabled state of the spatializer effect changes 396 * @param spat the {@code Spatializer} instance whose state changed 397 * @param enabled {@code true} if the spatializer effect is enabled on the device, 398 * {@code false} otherwise 399 * @see #isEnabled() 400 */ onSpatializerEnabledChanged(@onNull Spatializer spat, boolean enabled)401 void onSpatializerEnabledChanged(@NonNull Spatializer spat, boolean enabled); 402 403 /** 404 * Called when the availability of the spatializer effect changes 405 * @param spat the {@code Spatializer} instance whose state changed 406 * @param available {@code true} if the spatializer effect is available and capable 407 * of processing the audio for the current configuration of the device, 408 * {@code false} otherwise. 409 * @see #isAvailable() 410 */ onSpatializerAvailableChanged(@onNull Spatializer spat, boolean available)411 void onSpatializerAvailableChanged(@NonNull Spatializer spat, boolean available); 412 } 413 414 /** 415 * @hide 416 * An interface to be notified of changes to the head tracking mode, used by the spatializer 417 * effect. 418 * Changes to the mode may come from explicitly setting a different mode 419 * (see {@link #setDesiredHeadTrackingMode(int)}) or a change in system conditions (see 420 * {@link #getHeadTrackingMode()} 421 * @see #addOnHeadTrackingModeChangedListener(Executor, OnHeadTrackingModeChangedListener) 422 * @see #removeOnHeadTrackingModeChangedListener(OnHeadTrackingModeChangedListener) 423 */ 424 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 425 public interface OnHeadTrackingModeChangedListener { 426 /** 427 * Called when the actual head tracking mode of the spatializer changed. 428 * @param spatializer the {@code Spatializer} instance whose head tracking mode is changing 429 * @param mode the new head tracking mode 430 */ onHeadTrackingModeChanged(@onNull Spatializer spatializer, @HeadTrackingMode int mode)431 void onHeadTrackingModeChanged(@NonNull Spatializer spatializer, 432 @HeadTrackingMode int mode); 433 434 /** 435 * Called when the desired head tracking mode of the spatializer changed 436 * @param spatializer the {@code Spatializer} instance whose head tracking mode was set 437 * @param mode the newly set head tracking mode 438 */ onDesiredHeadTrackingModeChanged(@onNull Spatializer spatializer, @HeadTrackingModeSet int mode)439 void onDesiredHeadTrackingModeChanged(@NonNull Spatializer spatializer, 440 @HeadTrackingModeSet int mode); 441 } 442 443 /** 444 * Interface to be notified of changes to the availability of a head tracker on the audio 445 * device to be used by the spatializer effect. 446 */ 447 public interface OnHeadTrackerAvailableListener { 448 /** 449 * Called when the availability of the head tracker changed. 450 * @param spatializer the {@code Spatializer} instance for which the head tracker 451 * availability was updated 452 * @param available true if the audio device that would output audio processed by 453 * the {@code Spatializer} has a head tracker associated with it, false 454 * otherwise. 455 */ onHeadTrackerAvailableChanged(@onNull Spatializer spatializer, boolean available)456 void onHeadTrackerAvailableChanged(@NonNull Spatializer spatializer, 457 boolean available); 458 } 459 460 /** 461 * @hide 462 * An interface to be notified of changes to the output stream used by the spatializer 463 * effect. 464 * @see #getOutput() 465 */ 466 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 467 public interface OnSpatializerOutputChangedListener { 468 /** 469 * Called when the id of the output stream of the spatializer effect changed. 470 * @param spatializer the {@code Spatializer} instance whose output is updated 471 * @param output the id of the output stream, or 0 when there is no spatializer output 472 */ onSpatializerOutputChanged(@onNull Spatializer spatializer, @IntRange(from = 0) int output)473 void onSpatializerOutputChanged(@NonNull Spatializer spatializer, 474 @IntRange(from = 0) int output); 475 } 476 477 /** 478 * @hide 479 * An interface to be notified of updates to the head to soundstage pose, as represented by the 480 * current head tracking mode. 481 * @see #setOnHeadToSoundstagePoseUpdatedListener(Executor, OnHeadToSoundstagePoseUpdatedListener) 482 */ 483 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 484 public interface OnHeadToSoundstagePoseUpdatedListener { 485 /** 486 * Called when the head to soundstage transform is updated 487 * @param spatializer the {@code Spatializer} instance affected by the pose update 488 * @param pose the new pose data representing the transform between the frame 489 * of reference for the current head tracking mode (see 490 * {@link #getHeadTrackingMode()}) and the device being tracked (for 491 * instance a pair of headphones with a head tracker).<br> 492 * The head pose data is represented as an array of six float values, where 493 * the first three values are the translation vector, and the next three 494 * are the rotation vector. 495 */ onHeadToSoundstagePoseUpdated(@onNull Spatializer spatializer, @NonNull float[] pose)496 void onHeadToSoundstagePoseUpdated(@NonNull Spatializer spatializer, 497 @NonNull float[] pose); 498 } 499 500 /** 501 * Returns whether audio of the given {@link AudioFormat}, played with the given 502 * {@link AudioAttributes} can be spatialized. 503 * Note that the result reflects the capabilities of the device and may change when 504 * audio accessories are connected/disconnected (e.g. wired headphones plugged in or not). 505 * The result is independent from whether spatialization processing is enabled or not. 506 * @param attributes the {@code AudioAttributes} of the content as used for playback 507 * @param format the {@code AudioFormat} of the content as used for playback 508 * @return {@code true} if the device is capable of spatializing the combination of audio format 509 * and attributes, {@code false} otherwise. 510 */ canBeSpatialized( @onNull AudioAttributes attributes, @NonNull AudioFormat format)511 public boolean canBeSpatialized( 512 @NonNull AudioAttributes attributes, @NonNull AudioFormat format) { 513 try { 514 return mAm.getService().canBeSpatialized( 515 Objects.requireNonNull(attributes), Objects.requireNonNull(format)); 516 } catch (RemoteException e) { 517 Log.e(TAG, "Error querying canBeSpatialized for attr:" + attributes 518 + " format:" + format + " returning false", e); 519 return false; 520 } 521 } 522 523 /** 524 * Adds a listener to be notified of changes to the enabled state of the 525 * {@code Spatializer}. 526 * @param executor the {@code Executor} handling the callback 527 * @param listener the listener to receive enabled state updates 528 * @see #isEnabled() 529 */ addOnSpatializerStateChangedListener( @onNull @allbackExecutor Executor executor, @NonNull OnSpatializerStateChangedListener listener)530 public void addOnSpatializerStateChangedListener( 531 @NonNull @CallbackExecutor Executor executor, 532 @NonNull OnSpatializerStateChangedListener listener) { 533 mStateListenerMgr.addListener(executor, listener, "addOnSpatializerStateChangedListener", 534 () -> new SpatializerInfoDispatcherStub()); 535 } 536 537 /** 538 * Removes a previously added listener for changes to the enabled state of the 539 * {@code Spatializer}. 540 * @param listener the listener to receive enabled state updates 541 * @see #isEnabled() 542 */ removeOnSpatializerStateChangedListener( @onNull OnSpatializerStateChangedListener listener)543 public void removeOnSpatializerStateChangedListener( 544 @NonNull OnSpatializerStateChangedListener listener) { 545 mStateListenerMgr.removeListener(listener, "removeOnSpatializerStateChangedListener"); 546 } 547 548 /** 549 * @hide 550 * Returns the list of playback devices that are compatible with the playback of multichannel 551 * audio through virtualization 552 * @return a list of devices. An empty list indicates virtualization is not supported. 553 */ 554 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 555 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) getCompatibleAudioDevices()556 public @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() { 557 try { 558 return mAm.getService().getSpatializerCompatibleAudioDevices(); 559 } catch (RemoteException e) { 560 Log.e(TAG, "Error querying getSpatializerCompatibleAudioDevices(), " 561 + " returning empty list", e); 562 return new ArrayList<AudioDeviceAttributes>(0); 563 } 564 } 565 566 /** 567 * @hide 568 * Adds a playback device to the list of devices compatible with the playback of multichannel 569 * audio through spatialization. 570 * @see #getCompatibleAudioDevices() 571 * @param ada the audio device compatible with spatialization 572 */ 573 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 574 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) addCompatibleAudioDevice(@onNull AudioDeviceAttributes ada)575 public void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { 576 try { 577 mAm.getService().addSpatializerCompatibleAudioDevice(Objects.requireNonNull(ada)); 578 } catch (RemoteException e) { 579 Log.e(TAG, "Error calling addSpatializerCompatibleAudioDevice(), ", e); 580 } 581 } 582 583 /** 584 * @hide 585 * Remove a playback device from the list of devices compatible with the playback of 586 * multichannel audio through spatialization. 587 * @see #getCompatibleAudioDevices() 588 * @param ada the audio device incompatible with spatialization 589 */ 590 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 591 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) removeCompatibleAudioDevice(@onNull AudioDeviceAttributes ada)592 public void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { 593 try { 594 mAm.getService().removeSpatializerCompatibleAudioDevice(Objects.requireNonNull(ada)); 595 } catch (RemoteException e) { 596 Log.e(TAG, "Error calling removeSpatializerCompatibleAudioDevice(), ", e); 597 } 598 } 599 600 /** 601 * manages the OnSpatializerStateChangedListener listeners and the 602 * SpatializerInfoDispatcherStub 603 */ 604 private final CallbackUtil.LazyListenerManager<OnSpatializerStateChangedListener> 605 mStateListenerMgr = new CallbackUtil.LazyListenerManager(); 606 607 private final class SpatializerInfoDispatcherStub extends ISpatializerCallback.Stub 608 implements CallbackUtil.DispatcherStub { 609 @Override register(boolean register)610 public void register(boolean register) { 611 try { 612 if (register) { 613 mAm.getService().registerSpatializerCallback(this); 614 } else { 615 mAm.getService().unregisterSpatializerCallback(this); 616 } 617 } catch (RemoteException e) { 618 e.rethrowFromSystemServer(); 619 } 620 } 621 622 @Override 623 @SuppressLint("GuardedBy") // lock applied inside callListeners method dispatchSpatializerEnabledChanged(boolean enabled)624 public void dispatchSpatializerEnabledChanged(boolean enabled) { 625 mStateListenerMgr.callListeners( 626 (listener) -> listener.onSpatializerEnabledChanged( 627 Spatializer.this, enabled)); 628 } 629 630 @Override 631 @SuppressLint("GuardedBy") // lock applied inside callListeners method dispatchSpatializerAvailableChanged(boolean available)632 public void dispatchSpatializerAvailableChanged(boolean available) { 633 mStateListenerMgr.callListeners( 634 (listener) -> listener.onSpatializerAvailableChanged( 635 Spatializer.this, available)); 636 } 637 } 638 639 640 /** 641 * @hide 642 * Return the current head tracking mode as used by the system. 643 * Note this may differ from the desired head tracking mode. Reasons for the two to differ 644 * include: a head tracking device is not available for the current audio output device, 645 * the transmission conditions between the tracker and device have deteriorated and tracking 646 * has been disabled. 647 * @see #getDesiredHeadTrackingMode() 648 * @return the current head tracking mode 649 */ 650 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 651 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) getHeadTrackingMode()652 public @HeadTrackingMode int getHeadTrackingMode() { 653 try { 654 return mAm.getService().getActualHeadTrackingMode(); 655 } catch (RemoteException e) { 656 Log.e(TAG, "Error calling getActualHeadTrackingMode", e); 657 return HEAD_TRACKING_MODE_UNSUPPORTED; 658 } 659 660 } 661 662 /** 663 * @hide 664 * Return the desired head tracking mode. 665 * Note this may differ from the actual head tracking mode, reflected by 666 * {@link #getHeadTrackingMode()}. 667 * @return the desired head tring mode 668 */ 669 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 670 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) getDesiredHeadTrackingMode()671 public @HeadTrackingMode int getDesiredHeadTrackingMode() { 672 try { 673 return mAm.getService().getDesiredHeadTrackingMode(); 674 } catch (RemoteException e) { 675 Log.e(TAG, "Error calling getDesiredHeadTrackingMode", e); 676 return HEAD_TRACKING_MODE_UNSUPPORTED; 677 } 678 } 679 680 /** 681 * @hide 682 * Returns the list of supported head tracking modes. 683 * @return the list of modes that can be used in {@link #setDesiredHeadTrackingMode(int)} to 684 * enable head tracking. The list will be empty if {@link #getHeadTrackingMode()} 685 * is {@link #HEAD_TRACKING_MODE_UNSUPPORTED}. Values can be 686 * {@link #HEAD_TRACKING_MODE_OTHER}, 687 * {@link #HEAD_TRACKING_MODE_RELATIVE_WORLD} or 688 * {@link #HEAD_TRACKING_MODE_RELATIVE_DEVICE} 689 */ 690 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 691 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) getSupportedHeadTrackingModes()692 public @NonNull List<Integer> getSupportedHeadTrackingModes() { 693 try { 694 final int[] modes = mAm.getService().getSupportedHeadTrackingModes(); 695 final ArrayList<Integer> list = new ArrayList<>(0); 696 for (int mode : modes) { 697 list.add(mode); 698 } 699 return list; 700 } catch (RemoteException e) { 701 Log.e(TAG, "Error calling getSupportedHeadTrackModes", e); 702 return new ArrayList(0); 703 } 704 } 705 706 /** 707 * @hide 708 * Sets the desired head tracking mode. 709 * Note a set desired mode may differ from the actual head tracking mode. 710 * @see #getHeadTrackingMode() 711 * @param mode the desired head tracking mode, one of the values returned by 712 * {@link #getSupportedHeadTrackModes()}, or {@link #HEAD_TRACKING_MODE_DISABLED} to 713 * disable head tracking. 714 */ 715 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 716 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) setDesiredHeadTrackingMode(@eadTrackingModeSet int mode)717 public void setDesiredHeadTrackingMode(@HeadTrackingModeSet int mode) { 718 try { 719 mAm.getService().setDesiredHeadTrackingMode(mode); 720 } catch (RemoteException e) { 721 Log.e(TAG, "Error calling setDesiredHeadTrackingMode to " + mode, e); 722 } 723 } 724 725 /** 726 * @hide 727 * Recenters the head tracking at the current position / orientation. 728 */ 729 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 730 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) recenterHeadTracker()731 public void recenterHeadTracker() { 732 try { 733 mAm.getService().recenterHeadTracker(); 734 } catch (RemoteException e) { 735 Log.e(TAG, "Error calling recenterHeadTracker", e); 736 } 737 } 738 739 /** 740 * @hide 741 * Adds a listener to be notified of changes to the head tracking mode of the 742 * {@code Spatializer} 743 * @param executor the {@code Executor} handling the callbacks 744 * @param listener the listener to register 745 */ 746 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 747 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) addOnHeadTrackingModeChangedListener( @onNull @allbackExecutor Executor executor, @NonNull OnHeadTrackingModeChangedListener listener)748 public void addOnHeadTrackingModeChangedListener( 749 @NonNull @CallbackExecutor Executor executor, 750 @NonNull OnHeadTrackingModeChangedListener listener) { 751 mHeadTrackingListenerMgr.addListener(executor, listener, 752 "addOnHeadTrackingModeChangedListener", 753 () -> new SpatializerHeadTrackingDispatcherStub()); 754 } 755 756 /** 757 * @hide 758 * Removes a previously added listener for changes to the head tracking mode of the 759 * {@code Spatializer}. 760 * @param listener the listener to unregister 761 */ 762 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 763 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) removeOnHeadTrackingModeChangedListener( @onNull OnHeadTrackingModeChangedListener listener)764 public void removeOnHeadTrackingModeChangedListener( 765 @NonNull OnHeadTrackingModeChangedListener listener) { 766 mHeadTrackingListenerMgr.removeListener(listener, 767 "removeOnHeadTrackingModeChangedListener"); 768 } 769 770 /** 771 * @hide 772 * Set the listener to receive head to soundstage pose updates. 773 * @param executor the {@code Executor} handling the callbacks 774 * @param listener the listener to register 775 * @see #clearOnHeadToSoundstagePoseUpdatedListener() 776 */ 777 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 778 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) setOnHeadToSoundstagePoseUpdatedListener( @onNull @allbackExecutor Executor executor, @NonNull OnHeadToSoundstagePoseUpdatedListener listener)779 public void setOnHeadToSoundstagePoseUpdatedListener( 780 @NonNull @CallbackExecutor Executor executor, 781 @NonNull OnHeadToSoundstagePoseUpdatedListener listener) { 782 Objects.requireNonNull(executor); 783 Objects.requireNonNull(listener); 784 synchronized (mPoseListenerLock) { 785 if (mPoseListener != null) { 786 throw new IllegalStateException("Trying to overwrite existing listener"); 787 } 788 mPoseListener = 789 new ListenerInfo<OnHeadToSoundstagePoseUpdatedListener>(listener, executor); 790 mPoseDispatcher = new SpatializerPoseDispatcherStub(); 791 try { 792 mAm.getService().registerHeadToSoundstagePoseCallback(mPoseDispatcher); 793 } catch (RemoteException e) { 794 mPoseListener = null; 795 mPoseDispatcher = null; 796 } 797 } 798 } 799 800 /** 801 * @hide 802 * Clears the listener for head to soundstage pose updates 803 * @see #setOnHeadToSoundstagePoseUpdatedListener(Executor, OnHeadToSoundstagePoseUpdatedListener) 804 */ 805 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 806 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) clearOnHeadToSoundstagePoseUpdatedListener()807 public void clearOnHeadToSoundstagePoseUpdatedListener() { 808 synchronized (mPoseListenerLock) { 809 if (mPoseDispatcher == null) { 810 throw (new IllegalStateException("No listener to clear")); 811 } 812 try { 813 mAm.getService().unregisterHeadToSoundstagePoseCallback(mPoseDispatcher); 814 } catch (RemoteException e) { } 815 mPoseListener = null; 816 mPoseDispatcher = null; 817 } 818 } 819 820 /** 821 * @hide 822 * Sets an additional transform over the soundstage. 823 * The transform represents the pose of the soundstage, relative 824 * to either the device (in {@link #HEAD_TRACKING_MODE_RELATIVE_DEVICE} mode), the world (in 825 * {@link #HEAD_TRACKING_MODE_RELATIVE_WORLD}) or the listener’s head (in 826 * {@link #HEAD_TRACKING_MODE_DISABLED} mode). 827 * @param transform an array of 6 float values, the first 3 are the translation vector, the 828 * other 3 are the rotation vector. 829 */ 830 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 831 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) setGlobalTransform(@onNull float[] transform)832 public void setGlobalTransform(@NonNull float[] transform) { 833 if (Objects.requireNonNull(transform).length != 6) { 834 throw new IllegalArgumentException("transform array must be of size 6, was " 835 + transform.length); 836 } 837 try { 838 mAm.getService().setSpatializerGlobalTransform(transform); 839 } catch (RemoteException e) { 840 Log.e(TAG, "Error calling setGlobalTransform", e); 841 } 842 } 843 844 /** 845 * @hide 846 * Sets a parameter on the platform spatializer effect implementation. 847 * This is to be used for vendor-specific configurations of their effect, keys and values are 848 * not reuseable across implementations. 849 * @param key the parameter to change 850 * @param value an array for the value of the parameter to change 851 */ 852 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 853 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) setEffectParameter(int key, @NonNull byte[] value)854 public void setEffectParameter(int key, @NonNull byte[] value) { 855 Objects.requireNonNull(value); 856 try { 857 mAm.getService().setSpatializerParameter(key, value); 858 } catch (RemoteException e) { 859 Log.e(TAG, "Error calling setEffectParameter", e); 860 } 861 } 862 863 /** 864 * @hide 865 * Retrieves a parameter value from the platform spatializer effect implementation. 866 * This is to be used for vendor-specific configurations of their effect, keys and values are 867 * not reuseable across implementations. 868 * @param key the parameter for which the value is queried 869 * @param value a non-empty array to contain the return value. The caller is responsible for 870 * passing an array of size matching the parameter. 871 */ 872 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 873 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) getEffectParameter(int key, @NonNull byte[] value)874 public void getEffectParameter(int key, @NonNull byte[] value) { 875 Objects.requireNonNull(value); 876 try { 877 mAm.getService().getSpatializerParameter(key, value); 878 } catch (RemoteException e) { 879 Log.e(TAG, "Error calling getEffectParameter", e); 880 } 881 } 882 883 /** 884 * @hide 885 * Returns the id of the output stream used for the spatializer effect playback. 886 * This getter or associated listener {@link OnSpatializerOutputChangedListener} can be used for 887 * handling spatializer output-specific configurations (e.g. disabling speaker post-processing 888 * to avoid double-processing of the spatialized path). 889 * @return id of the output stream, or 0 if no spatializer playback is active 890 * @see #setOnSpatializerOutputChangedListener(Executor, OnSpatializerOutputChangedListener) 891 */ 892 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 893 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) getOutput()894 public @IntRange(from = 0) int getOutput() { 895 try { 896 return mAm.getService().getSpatializerOutput(); 897 } catch (RemoteException e) { 898 Log.e(TAG, "Error calling getSpatializerOutput", e); 899 return 0; 900 } 901 } 902 903 /** 904 * @hide 905 * Sets the listener to receive spatializer effect output updates 906 * @param executor the {@code Executor} handling the callbacks 907 * @param listener the listener to register 908 * @see #clearOnSpatializerOutputChangedListener() 909 * @see #getOutput() 910 */ 911 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 912 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) setOnSpatializerOutputChangedListener( @onNull @allbackExecutor Executor executor, @NonNull OnSpatializerOutputChangedListener listener)913 public void setOnSpatializerOutputChangedListener( 914 @NonNull @CallbackExecutor Executor executor, 915 @NonNull OnSpatializerOutputChangedListener listener) { 916 Objects.requireNonNull(executor); 917 Objects.requireNonNull(listener); 918 synchronized (mOutputListenerLock) { 919 if (mOutputListener != null) { 920 throw new IllegalStateException("Trying to overwrite existing listener"); 921 } 922 mOutputListener = 923 new ListenerInfo<OnSpatializerOutputChangedListener>(listener, executor); 924 mOutputDispatcher = new SpatializerOutputDispatcherStub(); 925 try { 926 mAm.getService().registerSpatializerOutputCallback(mOutputDispatcher); 927 // immediately report the current output 928 mOutputDispatcher.dispatchSpatializerOutputChanged(getOutput()); 929 } catch (RemoteException e) { 930 mOutputListener = null; 931 mOutputDispatcher = null; 932 } 933 } 934 } 935 936 /** 937 * @hide 938 * Clears the listener for spatializer effect output updates 939 * @see #setOnSpatializerOutputChangedListener(Executor, OnSpatializerOutputChangedListener) 940 */ 941 @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) 942 @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) clearOnSpatializerOutputChangedListener()943 public void clearOnSpatializerOutputChangedListener() { 944 synchronized (mOutputListenerLock) { 945 if (mOutputDispatcher == null) { 946 throw (new IllegalStateException("No listener to clear")); 947 } 948 try { 949 mAm.getService().unregisterSpatializerOutputCallback(mOutputDispatcher); 950 } catch (RemoteException e) { } 951 mOutputListener = null; 952 mOutputDispatcher = null; 953 } 954 } 955 956 //----------------------------------------------------------------------------- 957 // head tracking callback management and stub 958 959 /** 960 * manages the OnHeadTrackingModeChangedListener listeners and the 961 * SpatializerHeadTrackingDispatcherStub 962 */ 963 private final CallbackUtil.LazyListenerManager<OnHeadTrackingModeChangedListener> 964 mHeadTrackingListenerMgr = new CallbackUtil.LazyListenerManager(); 965 966 private final class SpatializerHeadTrackingDispatcherStub 967 extends ISpatializerHeadTrackingModeCallback.Stub 968 implements CallbackUtil.DispatcherStub { 969 @Override register(boolean register)970 public void register(boolean register) { 971 try { 972 if (register) { 973 mAm.getService().registerSpatializerHeadTrackingCallback(this); 974 } else { 975 mAm.getService().unregisterSpatializerHeadTrackingCallback(this); 976 } 977 } catch (RemoteException e) { 978 e.rethrowFromSystemServer(); 979 } 980 } 981 982 @Override 983 @SuppressLint("GuardedBy") // lock applied inside callListeners method dispatchSpatializerActualHeadTrackingModeChanged(int mode)984 public void dispatchSpatializerActualHeadTrackingModeChanged(int mode) { 985 mHeadTrackingListenerMgr.callListeners( 986 (listener) -> listener.onHeadTrackingModeChanged(Spatializer.this, mode)); 987 } 988 989 @Override 990 @SuppressLint("GuardedBy") // lock applied inside callListeners method dispatchSpatializerDesiredHeadTrackingModeChanged(int mode)991 public void dispatchSpatializerDesiredHeadTrackingModeChanged(int mode) { 992 mHeadTrackingListenerMgr.callListeners( 993 (listener) -> listener.onDesiredHeadTrackingModeChanged( 994 Spatializer.this, mode)); 995 } 996 } 997 998 //----------------------------------------------------------------------------- 999 // head tracker availability callback management and stub 1000 /** 1001 * manages the OnHeadTrackerAvailableListener listeners and the 1002 * SpatializerHeadTrackerAvailableDispatcherStub 1003 */ 1004 private final CallbackUtil.LazyListenerManager<OnHeadTrackerAvailableListener> 1005 mHeadTrackerListenerMgr = new CallbackUtil.LazyListenerManager(); 1006 1007 private final class SpatializerHeadTrackerAvailableDispatcherStub 1008 extends ISpatializerHeadTrackerAvailableCallback.Stub 1009 implements CallbackUtil.DispatcherStub { 1010 @Override register(boolean register)1011 public void register(boolean register) { 1012 try { 1013 mAm.getService().registerSpatializerHeadTrackerAvailableCallback(this, register); 1014 } catch (RemoteException e) { 1015 e.rethrowFromSystemServer(); 1016 } 1017 } 1018 1019 @Override 1020 @SuppressLint("GuardedBy") // lock applied inside callListeners method dispatchSpatializerHeadTrackerAvailable(boolean available)1021 public void dispatchSpatializerHeadTrackerAvailable(boolean available) { 1022 mHeadTrackerListenerMgr.callListeners( 1023 (listener) -> listener.onHeadTrackerAvailableChanged( 1024 Spatializer.this, available)); 1025 } 1026 } 1027 1028 //----------------------------------------------------------------------------- 1029 // head pose callback management and stub 1030 private final Object mPoseListenerLock = new Object(); 1031 /** 1032 * Listener for head to soundstage updates 1033 */ 1034 @GuardedBy("mPoseListenerLock") 1035 private @Nullable ListenerInfo<OnHeadToSoundstagePoseUpdatedListener> mPoseListener; 1036 @GuardedBy("mPoseListenerLock") 1037 private @Nullable SpatializerPoseDispatcherStub mPoseDispatcher; 1038 1039 private final class SpatializerPoseDispatcherStub 1040 extends ISpatializerHeadToSoundStagePoseCallback.Stub { 1041 1042 @Override dispatchPoseChanged(float[] pose)1043 public void dispatchPoseChanged(float[] pose) { 1044 // make a copy of ref to listener so callback is not executed under lock 1045 final ListenerInfo<OnHeadToSoundstagePoseUpdatedListener> listener; 1046 synchronized (mPoseListenerLock) { 1047 listener = mPoseListener; 1048 } 1049 if (listener == null) { 1050 return; 1051 } 1052 try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { 1053 listener.mExecutor.execute(() -> listener.mListener 1054 .onHeadToSoundstagePoseUpdated(Spatializer.this, pose)); 1055 } 1056 } 1057 } 1058 1059 //----------------------------------------------------------------------------- 1060 // output callback management and stub 1061 private final Object mOutputListenerLock = new Object(); 1062 /** 1063 * Listener for output updates 1064 */ 1065 @GuardedBy("mOutputListenerLock") 1066 private @Nullable ListenerInfo<OnSpatializerOutputChangedListener> mOutputListener; 1067 @GuardedBy("mOutputListenerLock") 1068 private @Nullable SpatializerOutputDispatcherStub mOutputDispatcher; 1069 1070 private final class SpatializerOutputDispatcherStub 1071 extends ISpatializerOutputCallback.Stub { 1072 1073 @Override dispatchSpatializerOutputChanged(int output)1074 public void dispatchSpatializerOutputChanged(int output) { 1075 // make a copy of ref to listener so callback is not executed under lock 1076 final ListenerInfo<OnSpatializerOutputChangedListener> listener; 1077 synchronized (mOutputListenerLock) { 1078 listener = mOutputListener; 1079 } 1080 if (listener == null) { 1081 return; 1082 } 1083 try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { 1084 listener.mExecutor.execute(() -> listener.mListener 1085 .onSpatializerOutputChanged(Spatializer.this, output)); 1086 } 1087 } 1088 } 1089 } 1090