Immersive audio APIs for headtracking
Headtracking:
- setter / getter and listener for headtracking mode
- listener for head pose
- global transform
- recentering
Bug: 191404931
Test: atest SpatializerHeadTrackingTest
Change-Id: I15cea62365584f84ae5a9149719902c671944f28
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0d3dd7c..9c01bca 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -5375,9 +5375,33 @@
public class Spatializer {
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void addCompatibleAudioDevice(@NonNull android.media.AudioDeviceAttributes);
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void addOnHeadTrackingModeChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnHeadTrackingModeChangedListener);
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void clearOnHeadToSoundstagePoseUpdatedListener();
method @NonNull @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public java.util.List<android.media.AudioDeviceAttributes> getCompatibleAudioDevices();
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getDesiredHeadTrackingMode();
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public int getHeadTrackingMode();
+ method @NonNull @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public java.util.List<java.lang.Integer> getSupportedHeadTrackingModes();
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void recenterHeadTracker();
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void removeCompatibleAudioDevice(@NonNull android.media.AudioDeviceAttributes);
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void removeOnHeadTrackingModeChangedListener(@NonNull android.media.Spatializer.OnHeadTrackingModeChangedListener);
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setDesiredHeadTrackingMode(int);
method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setEnabled(boolean);
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setGlobalTransform(@NonNull float[]);
+ method @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public void setOnHeadToSoundstagePoseUpdatedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnHeadToSoundstagePoseUpdatedListener);
+ field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_DISABLED = -1; // 0xffffffff
+ field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_OTHER = 0; // 0x0
+ field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_RELATIVE_DEVICE = 2; // 0x2
+ field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_RELATIVE_WORLD = 1; // 0x1
+ field @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public static final int HEAD_TRACKING_MODE_UNSUPPORTED = -2; // 0xfffffffe
+ }
+
+ public static interface Spatializer.OnHeadToSoundstagePoseUpdatedListener {
+ method public void onHeadToSoundstagePoseUpdated(@NonNull android.media.Spatializer, @NonNull float[]);
+ }
+
+ public static interface Spatializer.OnHeadTrackingModeChangedListener {
+ method public void onDesiredHeadTrackingModeChanged(@NonNull android.media.Spatializer, int);
+ method public void onHeadTrackingModeChanged(@NonNull android.media.Spatializer, int);
}
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 8480c52..bc10401 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -36,6 +36,8 @@
import android.media.IRingtonePlayer;
import android.media.IStrategyPreferredDevicesDispatcher;
import android.media.ISpatializerCallback;
+import android.media.ISpatializerHeadTrackingModeCallback;
+import android.media.ISpatializerHeadToSoundStagePoseCallback;
import android.media.IVolumeController;
import android.media.IVolumeController;
import android.media.PlayerBase;
@@ -405,13 +407,33 @@
boolean canBeSpatialized(in AudioAttributes aa, in AudioFormat af);
- void registerSpatializerCallback(in ISpatializerCallback callback);
+ void registerSpatializerCallback(in ISpatializerCallback cb);
- void unregisterSpatializerCallback(in ISpatializerCallback callback);
+ void unregisterSpatializerCallback(in ISpatializerCallback cb);
+
+ void registerSpatializerHeadTrackingCallback(in ISpatializerHeadTrackingModeCallback cb);
+
+ void unregisterSpatializerHeadTrackingCallback(in ISpatializerHeadTrackingModeCallback cb);
+
+ void registerHeadToSoundstagePoseCallback(in ISpatializerHeadToSoundStagePoseCallback cb);
+
+ void unregisterHeadToSoundstagePoseCallback(in ISpatializerHeadToSoundStagePoseCallback cb);
List<AudioDeviceAttributes> getSpatializerCompatibleAudioDevices();
void addSpatializerCompatibleAudioDevice(in AudioDeviceAttributes ada);
void removeSpatializerCompatibleAudioDevice(in AudioDeviceAttributes ada);
+
+ void setDesiredHeadTrackingMode(int mode);
+
+ int getDesiredHeadTrackingMode();
+
+ int[] getSupportedHeadTrackingModes();
+
+ int getActualHeadTrackingMode();
+
+ oneway void setSpatializerGlobalTransform(in float[] transform);
+
+ oneway void recenterHeadTracker();
}
diff --git a/media/java/android/media/ISpatializerHeadToSoundStagePoseCallback.aidl b/media/java/android/media/ISpatializerHeadToSoundStagePoseCallback.aidl
new file mode 100644
index 0000000..01a1465
--- /dev/null
+++ b/media/java/android/media/ISpatializerHeadToSoundStagePoseCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+/**
+ * AIDL for the AudioService to signal Spatializer state changes.
+ *
+ * {@hide}
+ */
+oneway interface ISpatializerHeadToSoundStagePoseCallback {
+
+ /**
+ * The pose is sent as an array of 6 float values, the first 3 are the translation vector, the
+ * other 3 are the rotation vector.
+ */
+ void dispatchPoseChanged(in float[] pose);
+}
diff --git a/media/java/android/media/ISpatializerHeadTrackingModeCallback.aidl b/media/java/android/media/ISpatializerHeadTrackingModeCallback.aidl
new file mode 100644
index 0000000..c61f86e
--- /dev/null
+++ b/media/java/android/media/ISpatializerHeadTrackingModeCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+/**
+ * AIDL for the AudioService to signal Spatializer head tracking mode changes.
+ *
+ * {@hide}
+ */
+oneway interface ISpatializerHeadTrackingModeCallback {
+
+ void dispatchSpatializerActualHeadTrackingModeChanged(int mode);
+
+ void dispatchSpatializerDesiredHeadTrackingModeChanged(int mode);
+}
diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java
index d8519b6..b062eea 100644
--- a/media/java/android/media/Spatializer.java
+++ b/media/java/android/media/Spatializer.java
@@ -49,21 +49,9 @@
private final @NonNull AudioManager mAm;
- private final Object mStateListenerLock = new Object();
-
private static final String TAG = "Spatializer";
/**
- * List of listeners for state listener and their associated Executor.
- * List is lazy-initialized on first registration
- */
- @GuardedBy("mStateListenerLock")
- private @Nullable ArrayList<StateListenerInfo> mStateListeners;
-
- @GuardedBy("mStateListenerLock")
- private SpatializerInfoDispatcherStub mInfoDispatcherStub;
-
- /**
* @hide
* Constructor with AudioManager acting as proxy to AudioService
* @param am a non-null AudioManager
@@ -141,6 +129,75 @@
*/
public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1;
+ /** @hide */
+ @IntDef(flag = false, value = {
+ HEAD_TRACKING_MODE_UNSUPPORTED,
+ HEAD_TRACKING_MODE_DISABLED,
+ HEAD_TRACKING_MODE_RELATIVE_WORLD,
+ HEAD_TRACKING_MODE_RELATIVE_DEVICE,
+ }) public @interface HeadTrackingMode {};
+
+ /** @hide */
+ @IntDef(flag = false, value = {
+ HEAD_TRACKING_MODE_DISABLED,
+ HEAD_TRACKING_MODE_RELATIVE_WORLD,
+ HEAD_TRACKING_MODE_RELATIVE_DEVICE,
+ }) public @interface HeadTrackingModeSet {};
+
+ /** @hide */
+ @IntDef(flag = false, value = {
+ HEAD_TRACKING_MODE_RELATIVE_WORLD,
+ HEAD_TRACKING_MODE_RELATIVE_DEVICE,
+ }) public @interface HeadTrackingModeSupported {};
+
+ /**
+ * @hide
+ * Constant indicating head tracking is not supported by this {@code Spatializer}
+ * @see #getHeadTrackingMode()
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public static final int HEAD_TRACKING_MODE_UNSUPPORTED = -2;
+
+ /**
+ * @hide
+ * Constant indicating head tracking is disabled on this {@code Spatializer}
+ * @see #getHeadTrackingMode()
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public static final int HEAD_TRACKING_MODE_DISABLED = -1;
+
+ /**
+ * @hide
+ * Constant indicating head tracking is in a mode whose behavior is unknown. This is not an
+ * error state but represents a customized behavior not defined by this API.
+ * @see #getHeadTrackingMode()
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public static final int HEAD_TRACKING_MODE_OTHER = 0;
+
+ /**
+ * @hide
+ * Constant indicating head tracking is tracking the user's position / orientation relative to
+ * the world around them
+ * @see #getHeadTrackingMode()
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public static final int HEAD_TRACKING_MODE_RELATIVE_WORLD = 1;
+
+ /**
+ * @hide
+ * Constant indicating head tracking is tracking the user's position / orientation relative to
+ * the device
+ * @see #getHeadTrackingMode()
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public static final int HEAD_TRACKING_MODE_RELATIVE_DEVICE = 2;
+
/**
* Return the level of support for the spatialization feature on this device.
* This level of support is independent of whether the {@code Spatializer} is currently
@@ -176,7 +233,7 @@
}
/**
- * An interface to be notified of changes to the state of the spatializer.
+ * An interface to be notified of changes to the state of the spatializer effect.
*/
public interface OnSpatializerStateChangedListener {
/**
@@ -200,6 +257,58 @@
}
/**
+ * @hide
+ * An interface to be notified of changes to the head tracking mode, used by the spatializer
+ * effect.
+ * Changes to the mode may come from explicitly setting a different mode
+ * (see {@link #setDesiredHeadTrackingMode(int)}) or a change in system conditions (see
+ * {@link #getHeadTrackingMode()}
+ * @see #addOnHeadTrackingModeChangedListener(Executor, OnHeadTrackingModeChangedListener)
+ * @see #removeOnHeadTrackingModeChangedListener(OnHeadTrackingModeChangedListener)
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ public interface OnHeadTrackingModeChangedListener {
+ /**
+ * Called when the actual head tracking mode of the spatializer changed.
+ * @param spatializer the {@code Spatializer} instance whose head tracking mode is changing
+ * @param mode the new head tracking mode
+ */
+ void onHeadTrackingModeChanged(@NonNull Spatializer spatializer,
+ @HeadTrackingMode int mode);
+
+ /**
+ * Called when the desired head tracking mode of the spatializer changed
+ * @param spatializer the {@code Spatializer} instance whose head tracking mode was set
+ * @param mode the newly set head tracking mode
+ */
+ void onDesiredHeadTrackingModeChanged(@NonNull Spatializer spatializer,
+ @HeadTrackingModeSet int mode);
+ }
+
+ /**
+ * @hide
+ * An interface to be notified of updates to the head to soundstage pose, as represented by the
+ * current head tracking mode.
+ * @see #setOnHeadToSoundstagePoseUpdatedListener(Executor, OnHeadToSoundstagePoseUpdatedListener)
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ public interface OnHeadToSoundstagePoseUpdatedListener {
+ /**
+ * Called when the head to soundstage transform is updated
+ * @param spatializer the {@code Spatializer} instance affected by the pose update
+ * @param pose the new pose data representing the transform between the frame
+ * of reference for the current head tracking mode (see
+ * {@link #getHeadTrackingMode()}) and the device being tracked (for
+ * instance a pair of headphones with a head tracker).<br>
+ * The head pose data is represented as an array of six float values, where
+ * the first three values are the translation vector, and the next three
+ * are the rotation vector.
+ */
+ void onHeadToSoundstagePoseUpdated(@NonNull Spatializer spatializer,
+ @NonNull float[] pose);
+ }
+
+ /**
* Returns whether audio of the given {@link AudioFormat}, played with the given
* {@link AudioAttributes} can be spatialized.
* Note that the result reflects the capabilities of the device and may change when
@@ -342,6 +451,17 @@
}
}
+ private final Object mStateListenerLock = new Object();
+ /**
+ * List of listeners for state listener and their associated Executor.
+ * List is lazy-initialized on first registration
+ */
+ @GuardedBy("mStateListenerLock")
+ private @Nullable ArrayList<StateListenerInfo> mStateListeners;
+
+ @GuardedBy("mStateListenerLock")
+ private @Nullable SpatializerInfoDispatcherStub mInfoDispatcherStub;
+
private final class SpatializerInfoDispatcherStub extends ISpatializerCallback.Stub {
@Override
public void dispatchSpatializerEnabledChanged(boolean enabled) {
@@ -423,4 +543,378 @@
}
return false;
}
+
+
+ /**
+ * @hide
+ * Return the current head tracking mode as used by the system.
+ * Note this may differ from the desired head tracking mode. Reasons for the two to differ
+ * include: a head tracking device is not available for the current audio output device,
+ * the transmission conditions between the tracker and device have deteriorated and tracking
+ * has been disabled.
+ * @see #getDesiredHeadTrackingMode()
+ * @return the current head tracking mode
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public @HeadTrackingMode int getHeadTrackingMode() {
+ try {
+ return mAm.getService().getActualHeadTrackingMode();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling getActualHeadTrackingMode", e);
+ return HEAD_TRACKING_MODE_UNSUPPORTED;
+ }
+
+ }
+
+ /**
+ * @hide
+ * Return the desired head tracking mode.
+ * Note this may differ from the actual head tracking mode, reflected by
+ * {@link #getHeadTrackingMode()}.
+ * @return the desired head tring mode
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public @HeadTrackingMode int getDesiredHeadTrackingMode() {
+ try {
+ return mAm.getService().getDesiredHeadTrackingMode();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling getDesiredHeadTrackingMode", e);
+ return HEAD_TRACKING_MODE_UNSUPPORTED;
+ }
+ }
+
+ /**
+ * @hide
+ * Returns the list of supported head tracking modes.
+ * @return the list of modes that can be used in {@link #setDesiredHeadTrackingMode(int)} to
+ * enable head tracking. The list will be empty if {@link #getHeadTrackingMode()}
+ * is {@link #HEAD_TRACKING_MODE_UNSUPPORTED}. Values can be
+ * {@link #HEAD_TRACKING_MODE_OTHER},
+ * {@link #HEAD_TRACKING_MODE_RELATIVE_WORLD} or
+ * {@link #HEAD_TRACKING_MODE_RELATIVE_DEVICE}
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public @NonNull List<Integer> getSupportedHeadTrackingModes() {
+ try {
+ final int[] modes = mAm.getService().getSupportedHeadTrackingModes();
+ final ArrayList<Integer> list = new ArrayList<>(0);
+ for (int mode : modes) {
+ list.add(mode);
+ }
+ return list;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling getSupportedHeadTrackModes", e);
+ return new ArrayList(0);
+ }
+ }
+
+ /**
+ * @hide
+ * Sets the desired head tracking mode.
+ * Note a set desired mode may differ from the actual head tracking mode.
+ * @see #getHeadTrackingMode()
+ * @param mode the desired head tracking mode, one of the values returned by
+ * {@link #getSupportedHeadTrackModes()}, or {@link #HEAD_TRACKING_MODE_DISABLED} to
+ * disable head tracking.
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void setDesiredHeadTrackingMode(@HeadTrackingModeSet int mode) {
+ try {
+ mAm.getService().setDesiredHeadTrackingMode(mode);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling setDesiredHeadTrackingMode to " + mode, e);
+ }
+ }
+
+ /**
+ * @hide
+ * Recenters the head tracking at the current position / orientation.
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void recenterHeadTracker() {
+ try {
+ mAm.getService().recenterHeadTracker();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling recenterHeadTracker", e);
+ }
+ }
+
+ /**
+ * @hide
+ * Adds a listener to be notified of changes to the head tracking mode of the
+ * {@code Spatializer}
+ * @param executor the {@code Executor} handling the callbacks
+ * @param listener the listener to register
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void addOnHeadTrackingModeChangedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnHeadTrackingModeChangedListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ synchronized (mHeadTrackingListenerLock) {
+ if (hasListener(listener, mHeadTrackingListeners)) {
+ throw new IllegalArgumentException(
+ "Called addOnHeadTrackingModeChangedListener() "
+ + "on a previously registered listener");
+ }
+ // lazy initialization of the list of strategy-preferred device listener
+ if (mHeadTrackingListeners == null) {
+ mHeadTrackingListeners = new ArrayList<>();
+ }
+ mHeadTrackingListeners.add(
+ new ListenerInfo<OnHeadTrackingModeChangedListener>(listener, executor));
+ if (mHeadTrackingListeners.size() == 1) {
+ // register binder for callbacks
+ if (mHeadTrackingDispatcherStub == null) {
+ mHeadTrackingDispatcherStub =
+ new SpatializerHeadTrackingDispatcherStub();
+ }
+ try {
+ mAm.getService().registerSpatializerHeadTrackingCallback(
+ mHeadTrackingDispatcherStub);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+ }
+
+ /**
+ * @hide
+ * Removes a previously added listener for changes to the head tracking mode of the
+ * {@code Spatializer}.
+ * @param listener the listener to unregister
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void removeOnHeadTrackingModeChangedListener(
+ @NonNull OnHeadTrackingModeChangedListener listener) {
+ Objects.requireNonNull(listener);
+ synchronized (mHeadTrackingListenerLock) {
+ if (!removeListener(listener, mHeadTrackingListeners)) {
+ throw new IllegalArgumentException(
+ "Called removeOnHeadTrackingModeChangedListener() "
+ + "on an unregistered listener");
+ }
+ if (mHeadTrackingListeners.size() == 0) {
+ // unregister binder for callbacks
+ try {
+ mAm.getService().unregisterSpatializerHeadTrackingCallback(
+ mHeadTrackingDispatcherStub);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } finally {
+ mHeadTrackingDispatcherStub = null;
+ mHeadTrackingListeners = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * @hide
+ * Set the listener to receive head to soundstage pose updates.
+ * @param executor the {@code Executor} handling the callbacks
+ * @param listener the listener to register
+ * @see #clearOnHeadToSoundstagePoseUpdatedListener()
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void setOnHeadToSoundstagePoseUpdatedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnHeadToSoundstagePoseUpdatedListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ synchronized (mPoseListenerLock) {
+ if (mPoseListener != null) {
+ throw new IllegalStateException("Trying to overwrite existing listener");
+ }
+ mPoseListener =
+ new ListenerInfo<OnHeadToSoundstagePoseUpdatedListener>(listener, executor);
+ mPoseDispatcher = new SpatializerPoseDispatcherStub();
+ try {
+ mAm.getService().registerHeadToSoundstagePoseCallback(mPoseDispatcher);
+ } catch (RemoteException e) {
+ mPoseListener = null;
+ mPoseDispatcher = null;
+ }
+ }
+ }
+
+ /**
+ * @hide
+ * Clears the listener for head to soundstage pose updates
+ * @see #setOnHeadToSoundstagePoseUpdatedListener(Executor, OnHeadToSoundstagePoseUpdatedListener)
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void clearOnHeadToSoundstagePoseUpdatedListener() {
+ synchronized (mPoseListenerLock) {
+ if (mPoseDispatcher == null) {
+ throw (new IllegalStateException("No listener to clear"));
+ }
+ try {
+ mAm.getService().unregisterHeadToSoundstagePoseCallback(mPoseDispatcher);
+ } catch (RemoteException e) { }
+ mPoseListener = null;
+ mPoseDispatcher = null;
+ }
+ }
+
+ /**
+ * @hide
+ * Sets an additional transform over the soundstage.
+ * The transform represents the pose of the soundstage, relative
+ * to either the device (in {@link #HEAD_TRACKING_MODE_RELATIVE_DEVICE} mode), the world (in
+ * {@link #HEAD_TRACKING_MODE_RELATIVE_WORLD}) or the listener’s head (in
+ * {@link #HEAD_TRACKING_MODE_DISABLED} mode).
+ * @param transform an array of 6 float values, the first 3 are the translation vector, the
+ * other 3 are the rotation vector.
+ */
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public void setGlobalTransform(@NonNull float[] transform) {
+ if (Objects.requireNonNull(transform).length != 6) {
+ throw new IllegalArgumentException("transform array must be of size 6, was "
+ + transform.length);
+ }
+ try {
+ mAm.getService().setSpatializerGlobalTransform(transform);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling setGlobalTransform", e);
+ }
+ }
+
+ //-----------------------------------------------------------------------------
+ // callback helper definitions
+
+ private static class ListenerInfo<T> {
+ final @NonNull T mListener;
+ final @NonNull Executor mExecutor;
+
+ ListenerInfo(T listener, Executor exe) {
+ mListener = listener;
+ mExecutor = exe;
+ }
+ }
+
+ private static <T> ListenerInfo<T> getListenerInfo(
+ T listener, ArrayList<ListenerInfo<T>> listeners) {
+ if (listeners == null) {
+ return null;
+ }
+ for (ListenerInfo<T> info : listeners) {
+ if (info.mListener == listener) {
+ return info;
+ }
+ }
+ return null;
+ }
+
+ private static <T> boolean hasListener(T listener, ArrayList<ListenerInfo<T>> listeners) {
+ return getListenerInfo(listener, listeners) != null;
+ }
+
+ private static <T> boolean removeListener(T listener, ArrayList<ListenerInfo<T>> listeners) {
+ final ListenerInfo<T> infoToRemove = getListenerInfo(listener, listeners);
+ if (infoToRemove != null) {
+ listeners.remove(infoToRemove);
+ return true;
+ }
+ return false;
+ }
+
+ //-----------------------------------------------------------------------------
+ // head tracking callback management and stub
+
+ private final Object mHeadTrackingListenerLock = new Object();
+ /**
+ * List of listeners for head tracking mode listener and their associated Executor.
+ * List is lazy-initialized on first registration
+ */
+ @GuardedBy("mHeadTrackingListenerLock")
+ private @Nullable ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>
+ mHeadTrackingListeners;
+
+ @GuardedBy("mHeadTrackingListenerLock")
+ private @Nullable SpatializerHeadTrackingDispatcherStub mHeadTrackingDispatcherStub;
+
+ private final class SpatializerHeadTrackingDispatcherStub
+ extends ISpatializerHeadTrackingModeCallback.Stub {
+ @Override
+ public void dispatchSpatializerActualHeadTrackingModeChanged(int mode) {
+ // make a shallow copy of listeners so callback is not executed under lock
+ final ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>> headTrackingListeners;
+ synchronized (mHeadTrackingListenerLock) {
+ if (mHeadTrackingListeners == null || mHeadTrackingListeners.size() == 0) {
+ return;
+ }
+ headTrackingListeners = (ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>)
+ mHeadTrackingListeners.clone();
+ }
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ for (ListenerInfo<OnHeadTrackingModeChangedListener> info : headTrackingListeners) {
+ info.mExecutor.execute(() -> info.mListener
+ .onHeadTrackingModeChanged(Spatializer.this, mode));
+ }
+ }
+ }
+
+ @Override
+ public void dispatchSpatializerDesiredHeadTrackingModeChanged(int mode) {
+ // make a shallow copy of listeners so callback is not executed under lock
+ final ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>> headTrackingListeners;
+ synchronized (mHeadTrackingListenerLock) {
+ if (mHeadTrackingListeners == null || mHeadTrackingListeners.size() == 0) {
+ return;
+ }
+ headTrackingListeners = (ArrayList<ListenerInfo<OnHeadTrackingModeChangedListener>>)
+ mHeadTrackingListeners.clone();
+ }
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ for (ListenerInfo<OnHeadTrackingModeChangedListener> info : headTrackingListeners) {
+ info.mExecutor.execute(() -> info.mListener
+ .onDesiredHeadTrackingModeChanged(Spatializer.this, mode));
+ }
+ }
+ }
+ }
+
+ //-----------------------------------------------------------------------------
+ // head pose callback management and stub
+ private final Object mPoseListenerLock = new Object();
+ /**
+ * Listener for head to soundstage updates
+ */
+ @GuardedBy("mPoseListenerLock")
+ private @Nullable ListenerInfo<OnHeadToSoundstagePoseUpdatedListener> mPoseListener;
+ @GuardedBy("mPoseListenerLock")
+ private @Nullable SpatializerPoseDispatcherStub mPoseDispatcher;
+
+ private final class SpatializerPoseDispatcherStub
+ extends ISpatializerHeadToSoundStagePoseCallback.Stub {
+
+ @Override
+ public void dispatchPoseChanged(float[] pose) {
+ // make a copy of ref to listener so callback is not executed under lock
+ final ListenerInfo<OnHeadToSoundstagePoseUpdatedListener> listener;
+ synchronized (mPoseListenerLock) {
+ listener = mPoseListener;
+ }
+ if (listener == null) {
+ return;
+ }
+ try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+ listener.mExecutor.execute(() -> listener.mListener
+ .onHeadToSoundstagePoseUpdated(Spatializer.this, pose));
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index b230200..5d0bad2 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -94,6 +94,8 @@
import android.media.IRecordingConfigDispatcher;
import android.media.IRingtonePlayer;
import android.media.ISpatializerCallback;
+import android.media.ISpatializerHeadToSoundStagePoseCallback;
+import android.media.ISpatializerHeadTrackingModeCallback;
import android.media.IStrategyPreferredDevicesDispatcher;
import android.media.IVolumeController;
import android.media.MediaMetrics;
@@ -8322,16 +8324,48 @@
/** @see Spatializer.SpatializerInfoDispatcherStub */
public void registerSpatializerCallback(
- @NonNull ISpatializerCallback dispatcher) {
- Objects.requireNonNull(dispatcher);
- mSpatializerHelper.registerStateCallback(dispatcher);
+ @NonNull ISpatializerCallback cb) {
+ Objects.requireNonNull(cb);
+ mSpatializerHelper.registerStateCallback(cb);
}
/** @see Spatializer.SpatializerInfoDispatcherStub */
public void unregisterSpatializerCallback(
- @NonNull ISpatializerCallback dispatcher) {
- Objects.requireNonNull(dispatcher);
- mSpatializerHelper.unregisterStateCallback(dispatcher);
+ @NonNull ISpatializerCallback cb) {
+ Objects.requireNonNull(cb);
+ mSpatializerHelper.unregisterStateCallback(cb);
+ }
+
+ /** @see Spatializer#SpatializerHeadTrackingDispatcherStub */
+ public void registerSpatializerHeadTrackingCallback(
+ @NonNull ISpatializerHeadTrackingModeCallback cb) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(cb);
+ mSpatializerHelper.registerHeadTrackingModeCallback(cb);
+ }
+
+ /** @see Spatializer#SpatializerHeadTrackingDispatcherStub */
+ public void unregisterSpatializerHeadTrackingCallback(
+ @NonNull ISpatializerHeadTrackingModeCallback cb) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(cb);
+ mSpatializerHelper.unregisterHeadTrackingModeCallback(cb);
+ }
+
+ /** @see Spatializer#setOnHeadToSoundstagePoseUpdatedListener */
+ public void registerHeadToSoundstagePoseCallback(
+ @NonNull ISpatializerHeadToSoundStagePoseCallback cb) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(cb);
+ mSpatializerHelper.registerHeadToSoundstagePoseCallback(cb);
+ }
+
+ /** @see Spatializer#clearOnHeadToSoundstagePoseUpdatedListener */
+ public void unregisterHeadToSoundstagePoseCallback(
+ @NonNull ISpatializerHeadToSoundStagePoseCallback cb) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(cb);
+ mSpatializerHelper.unregisterHeadToSoundstagePoseCallback(cb);
}
/** @see Spatializer#getSpatializerCompatibleAudioDevices() */
@@ -8354,6 +8388,51 @@
mSpatializerHelper.removeCompatibleAudioDevice(ada);
}
+ /** @see Spatializer#getSupportedHeadTrackingModes() */
+ public int[] getSupportedHeadTrackingModes() {
+ enforceModifyDefaultAudioEffectsPermission();
+ return mSpatializerHelper.getSupportedHeadTrackingModes();
+ }
+
+ /** @see Spatializer#getHeadTrackingMode() */
+ public int getActualHeadTrackingMode() {
+ enforceModifyDefaultAudioEffectsPermission();
+ return mSpatializerHelper.getActualHeadTrackingMode();
+ }
+
+ /** @see Spatializer#getDesiredHeadTrackingMode() */
+ public int getDesiredHeadTrackingMode() {
+ enforceModifyDefaultAudioEffectsPermission();
+ return mSpatializerHelper.getDesiredHeadTrackingMode();
+ }
+
+ /** @see Spatializer#setGlobalTransform */
+ public void setSpatializerGlobalTransform(@NonNull float[] transform) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(transform);
+ mSpatializerHelper.setGlobalTransform(transform);
+ }
+
+ /** @see Spatializer#recenterHeadTracker() */
+ public void recenterHeadTracker() {
+ enforceModifyDefaultAudioEffectsPermission();
+ mSpatializerHelper.recenterHeadTracker();
+ }
+
+ /** @see Spatializer#setDesiredHeadTrackingMode */
+ public void setDesiredHeadTrackingMode(@Spatializer.HeadTrackingModeSet int mode) {
+ enforceModifyDefaultAudioEffectsPermission();
+ switch(mode) {
+ case Spatializer.HEAD_TRACKING_MODE_DISABLED:
+ case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD:
+ case Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE:
+ break;
+ default:
+ return;
+ }
+ mSpatializerHelper.setDesiredHeadTrackingMode(mode);
+ }
+
//==========================================================================================
private boolean readCameraSoundForced() {
return SystemProperties.getBoolean("audio.camerasound.force", false) ||
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 32ac785..1d85974 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -25,7 +25,10 @@
import android.media.INativeSpatializerCallback;
import android.media.ISpatializer;
import android.media.ISpatializerCallback;
+import android.media.ISpatializerHeadToSoundStagePoseCallback;
+import android.media.ISpatializerHeadTrackingModeCallback;
import android.media.Spatializer;
+import android.media.SpatializerHeadTrackingMode;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
@@ -64,6 +67,8 @@
/** current level as reported by native Spatializer in callback */
private int mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+ private int mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
+ private int mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
private @Nullable ISpatializer mSpat;
private @Nullable SpatializerCallback mSpatCallback;
@@ -150,6 +155,7 @@
mState = STATE_UNINITIALIZED;
mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+ mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
init();
setFeatureEnabled(featureEnabled);
}
@@ -189,6 +195,15 @@
public void onHeadTrackingModeChanged(byte mode) {
logd("SpatializerCallback.onHeadTrackingModeChanged mode:" + mode);
+ int oldMode, newMode;
+ synchronized (this) {
+ oldMode = mActualHeadTrackingMode;
+ mActualHeadTrackingMode = headTrackingModeTypeToSpatializerInt(mode);
+ newMode = mActualHeadTrackingMode;
+ }
+ if (oldMode != newMode) {
+ dispatchActualHeadTrackingMode(newMode);
+ }
}
public void onHeadToSoundStagePoseUpdated(float[] headToStage) {
@@ -196,6 +211,11 @@
Log.e(TAG, "SpatializerCallback.onHeadToStagePoseUpdated null transform");
return;
}
+ if (headToStage.length != 6) {
+ Log.e(TAG, "SpatializerCallback.onHeadToStagePoseUpdated invalid transform length"
+ + headToStage.length);
+ return;
+ }
if (DEBUG) {
// 6 values * (4 digits + 1 dot + 2 brackets) = 42 characters
StringBuilder t = new StringBuilder(42);
@@ -204,6 +224,7 @@
}
logd("SpatializerCallback.onHeadToStagePoseUpdated headToStage:" + t);
}
+ dispatchPoseUpdate(headToStage);
}
};
@@ -472,4 +493,239 @@
logd("canBeSpatialized returning " + able);
return able;
}
+
+ //------------------------------------------------------
+ // head tracking
+ final RemoteCallbackList<ISpatializerHeadTrackingModeCallback> mHeadTrackingModeCallbacks =
+ new RemoteCallbackList<ISpatializerHeadTrackingModeCallback>();
+
+ synchronized void registerHeadTrackingModeCallback(
+ @NonNull ISpatializerHeadTrackingModeCallback callback) {
+ mHeadTrackingModeCallbacks.register(callback);
+ }
+
+ synchronized void unregisterHeadTrackingModeCallback(
+ @NonNull ISpatializerHeadTrackingModeCallback callback) {
+ mHeadTrackingModeCallbacks.unregister(callback);
+ }
+
+ synchronized int[] getSupportedHeadTrackingModes() {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ return new int[0];
+ case STATE_NOT_SUPPORTED:
+ // return an empty list when Spatializer functionality is not supported
+ // because the list of head tracking modes you can set is actually empty
+ // as defined in {@link Spatializer#getSupportedHeadTrackingModes()}
+ return new int[0];
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_DISABLED_UNAVAILABLE:
+ case STATE_DISABLED_AVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ if (mSpat == null) {
+ return new int[0];
+ }
+ break;
+ }
+ // mSpat != null
+ try {
+ final byte[] values = mSpat.getSupportedHeadTrackingModes();
+ ArrayList<Integer> list = new ArrayList<>(0);
+ for (byte value : values) {
+ switch (value) {
+ case SpatializerHeadTrackingMode.OTHER:
+ case SpatializerHeadTrackingMode.DISABLED:
+ // not expected here, skip
+ break;
+ case SpatializerHeadTrackingMode.RELATIVE_WORLD:
+ case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
+ list.add(headTrackingModeTypeToSpatializerInt(value));
+ break;
+ default:
+ Log.e(TAG, "Unexpected head tracking mode:" + value,
+ new IllegalArgumentException("invalid mode"));
+ break;
+ }
+ }
+ int[] modes = new int[list.size()];
+ for (int i = 0; i < list.size(); i++) {
+ modes[i] = list.get(i);
+ }
+ return modes;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling getSupportedHeadTrackingModes", e);
+ return new int[] { Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED };
+ }
+ }
+
+ synchronized int getActualHeadTrackingMode() {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ return Spatializer.HEAD_TRACKING_MODE_DISABLED;
+ case STATE_NOT_SUPPORTED:
+ return Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_DISABLED_UNAVAILABLE:
+ case STATE_DISABLED_AVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ if (mSpat == null) {
+ return Spatializer.HEAD_TRACKING_MODE_DISABLED;
+ }
+ break;
+ }
+ // mSpat != null
+ try {
+ return headTrackingModeTypeToSpatializerInt(mSpat.getActualHeadTrackingMode());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling getActualHeadTrackingMode", e);
+ return Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
+ }
+ }
+
+ synchronized int getDesiredHeadTrackingMode() {
+ return mDesiredHeadTrackingMode;
+ }
+
+ synchronized void setGlobalTransform(@NonNull float[] transform) {
+ if (transform.length != 6) {
+ throw new IllegalArgumentException("invalid array size" + transform.length);
+ }
+ if (!checkSpatForHeadTracking("setGlobalTransform")) {
+ return;
+ }
+ try {
+ mSpat.setGlobalTransform(transform);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling setGlobalTransform", e);
+ }
+ }
+
+ synchronized void recenterHeadTracker() {
+ if (!checkSpatForHeadTracking("recenterHeadTracker")) {
+ return;
+ }
+ try {
+ mSpat.recenterHeadTracker();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling recenterHeadTracker", e);
+ }
+ }
+
+ synchronized void setDesiredHeadTrackingMode(@Spatializer.HeadTrackingModeSet int mode) {
+ if (!checkSpatForHeadTracking("setDesiredHeadTrackingMode")) {
+ return;
+ }
+ try {
+ if (mode != mDesiredHeadTrackingMode) {
+ mSpat.setDesiredHeadTrackingMode(spatializerIntToHeadTrackingModeType(mode));
+ mDesiredHeadTrackingMode = mode;
+ dispatchDesiredHeadTrackingMode(mode);
+ }
+
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling setDesiredHeadTrackingMode", e);
+ }
+ }
+
+ private int headTrackingModeTypeToSpatializerInt(byte mode) {
+ switch (mode) {
+ case SpatializerHeadTrackingMode.OTHER:
+ return Spatializer.HEAD_TRACKING_MODE_OTHER;
+ case SpatializerHeadTrackingMode.DISABLED:
+ return Spatializer.HEAD_TRACKING_MODE_DISABLED;
+ case SpatializerHeadTrackingMode.RELATIVE_WORLD:
+ return Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD;
+ case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
+ return Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE;
+ default:
+ throw(new IllegalArgumentException("Unexpected head tracking mode:" + mode));
+ }
+ }
+
+ private byte spatializerIntToHeadTrackingModeType(int sdkMode) {
+ switch (sdkMode) {
+ case Spatializer.HEAD_TRACKING_MODE_OTHER:
+ return SpatializerHeadTrackingMode.OTHER;
+ case Spatializer.HEAD_TRACKING_MODE_DISABLED:
+ return SpatializerHeadTrackingMode.DISABLED;
+ case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD:
+ return SpatializerHeadTrackingMode.RELATIVE_WORLD;
+ case Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE:
+ return SpatializerHeadTrackingMode.RELATIVE_SCREEN;
+ default:
+ throw(new IllegalArgumentException("Unexpected head tracking mode:" + sdkMode));
+ }
+ }
+
+ private boolean checkSpatForHeadTracking(String funcName) {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ case STATE_NOT_SUPPORTED:
+ return false;
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_DISABLED_UNAVAILABLE:
+ case STATE_DISABLED_AVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ if (mSpat == null) {
+ throw (new IllegalStateException(
+ "null Spatializer when calling " + funcName));
+ }
+ break;
+ }
+ return true;
+ }
+
+ private void dispatchActualHeadTrackingMode(int newMode) {
+ final int nbCallbacks = mHeadTrackingModeCallbacks.beginBroadcast();
+ for (int i = 0; i < nbCallbacks; i++) {
+ try {
+ mHeadTrackingModeCallbacks.getBroadcastItem(i)
+ .dispatchSpatializerActualHeadTrackingModeChanged(newMode);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in dispatchSpatializerActualHeadTrackingModeChanged", e);
+ }
+ }
+ mHeadTrackingModeCallbacks.finishBroadcast();
+ }
+
+ private void dispatchDesiredHeadTrackingMode(int newMode) {
+ final int nbCallbacks = mHeadTrackingModeCallbacks.beginBroadcast();
+ for (int i = 0; i < nbCallbacks; i++) {
+ try {
+ mHeadTrackingModeCallbacks.getBroadcastItem(i)
+ .dispatchSpatializerDesiredHeadTrackingModeChanged(newMode);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in dispatchSpatializerDesiredHeadTrackingModeChanged", e);
+ }
+ }
+ mHeadTrackingModeCallbacks.finishBroadcast();
+ }
+
+ //------------------------------------------------------
+ // head pose
+ final RemoteCallbackList<ISpatializerHeadToSoundStagePoseCallback> mHeadPoseCallbacks =
+ new RemoteCallbackList<ISpatializerHeadToSoundStagePoseCallback>();
+
+ synchronized void registerHeadToSoundstagePoseCallback(
+ @NonNull ISpatializerHeadToSoundStagePoseCallback callback) {
+ mHeadPoseCallbacks.register(callback);
+ }
+
+ synchronized void unregisterHeadToSoundstagePoseCallback(
+ @NonNull ISpatializerHeadToSoundStagePoseCallback callback) {
+ mHeadPoseCallbacks.unregister(callback);
+ }
+
+ private void dispatchPoseUpdate(float[] pose) {
+ final int nbCallbacks = mHeadPoseCallbacks.beginBroadcast();
+ for (int i = 0; i < nbCallbacks; i++) {
+ try {
+ mHeadPoseCallbacks.getBroadcastItem(i)
+ .dispatchPoseChanged(pose);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in dispatchPoseChanged", e);
+ }
+ }
+ mHeadPoseCallbacks.finishBroadcast();
+ }
}