Merge "Immersive audio APIs for headtracking" into sc-v2-dev

This commit is contained in:
TreeHugger Robot
2021-09-16 23:43:50 +00:00
committed by Android (Google) Code Review
7 changed files with 956 additions and 21 deletions

View File

@@ -5375,9 +5375,33 @@ package android.media {
public class Spatializer { 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 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 @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 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 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);
} }
} }

View File

@@ -36,6 +36,8 @@ import android.media.IRecordingConfigDispatcher;
import android.media.IRingtonePlayer; import android.media.IRingtonePlayer;
import android.media.IStrategyPreferredDevicesDispatcher; import android.media.IStrategyPreferredDevicesDispatcher;
import android.media.ISpatializerCallback; import android.media.ISpatializerCallback;
import android.media.ISpatializerHeadTrackingModeCallback;
import android.media.ISpatializerHeadToSoundStagePoseCallback;
import android.media.IVolumeController; import android.media.IVolumeController;
import android.media.IVolumeController; import android.media.IVolumeController;
import android.media.PlayerBase; import android.media.PlayerBase;
@@ -405,13 +407,33 @@ interface IAudioService {
boolean canBeSpatialized(in AudioAttributes aa, in AudioFormat af); 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(); List<AudioDeviceAttributes> getSpatializerCompatibleAudioDevices();
void addSpatializerCompatibleAudioDevice(in AudioDeviceAttributes ada); void addSpatializerCompatibleAudioDevice(in AudioDeviceAttributes ada);
void removeSpatializerCompatibleAudioDevice(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();
} }

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -49,20 +49,8 @@ public class Spatializer {
private final @NonNull AudioManager mAm; private final @NonNull AudioManager mAm;
private final Object mStateListenerLock = new Object();
private static final String TAG = "Spatializer"; 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 * @hide
* Constructor with AudioManager acting as proxy to AudioService * Constructor with AudioManager acting as proxy to AudioService
@@ -141,6 +129,75 @@ public class Spatializer {
*/ */
public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1; 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. * 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 * This level of support is independent of whether the {@code Spatializer} is currently
@@ -176,7 +233,7 @@ public class Spatializer {
} }
/** /**
* 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 { public interface OnSpatializerStateChangedListener {
/** /**
@@ -199,6 +256,58 @@ public class Spatializer {
void onSpatializerAvailableChanged(@NonNull Spatializer spat, boolean available); void onSpatializerAvailableChanged(@NonNull Spatializer spat, boolean available);
} }
/**
* @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 * Returns whether audio of the given {@link AudioFormat}, played with the given
* {@link AudioAttributes} can be spatialized. * {@link AudioAttributes} can be spatialized.
@@ -342,6 +451,17 @@ public class Spatializer {
} }
} }
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 { private final class SpatializerInfoDispatcherStub extends ISpatializerCallback.Stub {
@Override @Override
public void dispatchSpatializerEnabledChanged(boolean enabled) { public void dispatchSpatializerEnabledChanged(boolean enabled) {
@@ -423,4 +543,378 @@ public class Spatializer {
} }
return false; 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));
}
}
}
} }

View File

@@ -94,6 +94,8 @@ import android.media.IPlaybackConfigDispatcher;
import android.media.IRecordingConfigDispatcher; import android.media.IRecordingConfigDispatcher;
import android.media.IRingtonePlayer; import android.media.IRingtonePlayer;
import android.media.ISpatializerCallback; import android.media.ISpatializerCallback;
import android.media.ISpatializerHeadToSoundStagePoseCallback;
import android.media.ISpatializerHeadTrackingModeCallback;
import android.media.IStrategyPreferredDevicesDispatcher; import android.media.IStrategyPreferredDevicesDispatcher;
import android.media.IVolumeController; import android.media.IVolumeController;
import android.media.MediaMetrics; import android.media.MediaMetrics;
@@ -8322,16 +8324,48 @@ public class AudioService extends IAudioService.Stub
/** @see Spatializer.SpatializerInfoDispatcherStub */ /** @see Spatializer.SpatializerInfoDispatcherStub */
public void registerSpatializerCallback( public void registerSpatializerCallback(
@NonNull ISpatializerCallback dispatcher) { @NonNull ISpatializerCallback cb) {
Objects.requireNonNull(dispatcher); Objects.requireNonNull(cb);
mSpatializerHelper.registerStateCallback(dispatcher); mSpatializerHelper.registerStateCallback(cb);
} }
/** @see Spatializer.SpatializerInfoDispatcherStub */ /** @see Spatializer.SpatializerInfoDispatcherStub */
public void unregisterSpatializerCallback( public void unregisterSpatializerCallback(
@NonNull ISpatializerCallback dispatcher) { @NonNull ISpatializerCallback cb) {
Objects.requireNonNull(dispatcher); Objects.requireNonNull(cb);
mSpatializerHelper.unregisterStateCallback(dispatcher); 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() */ /** @see Spatializer#getSpatializerCompatibleAudioDevices() */
@@ -8354,6 +8388,51 @@ public class AudioService extends IAudioService.Stub
mSpatializerHelper.removeCompatibleAudioDevice(ada); 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() { private boolean readCameraSoundForced() {
return SystemProperties.getBoolean("audio.camerasound.force", false) || return SystemProperties.getBoolean("audio.camerasound.force", false) ||

View File

@@ -25,7 +25,10 @@ import android.media.AudioSystem;
import android.media.INativeSpatializerCallback; import android.media.INativeSpatializerCallback;
import android.media.ISpatializer; import android.media.ISpatializer;
import android.media.ISpatializerCallback; import android.media.ISpatializerCallback;
import android.media.ISpatializerHeadToSoundStagePoseCallback;
import android.media.ISpatializerHeadTrackingModeCallback;
import android.media.Spatializer; import android.media.Spatializer;
import android.media.SpatializerHeadTrackingMode;
import android.os.RemoteCallbackList; import android.os.RemoteCallbackList;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.Log; import android.util.Log;
@@ -64,6 +67,8 @@ public class SpatializerHelper {
/** current level as reported by native Spatializer in callback */ /** current level as reported by native Spatializer in callback */
private int mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; private int mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
private int mCapableSpatLevel = 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 ISpatializer mSpat;
private @Nullable SpatializerCallback mSpatCallback; private @Nullable SpatializerCallback mSpatCallback;
@@ -150,6 +155,7 @@ public class SpatializerHelper {
mState = STATE_UNINITIALIZED; mState = STATE_UNINITIALIZED;
mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
init(); init();
setFeatureEnabled(featureEnabled); setFeatureEnabled(featureEnabled);
} }
@@ -189,6 +195,15 @@ public class SpatializerHelper {
public void onHeadTrackingModeChanged(byte mode) { public void onHeadTrackingModeChanged(byte mode) {
logd("SpatializerCallback.onHeadTrackingModeChanged mode:" + 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) { public void onHeadToSoundStagePoseUpdated(float[] headToStage) {
@@ -196,6 +211,11 @@ public class SpatializerHelper {
Log.e(TAG, "SpatializerCallback.onHeadToStagePoseUpdated null transform"); Log.e(TAG, "SpatializerCallback.onHeadToStagePoseUpdated null transform");
return; return;
} }
if (headToStage.length != 6) {
Log.e(TAG, "SpatializerCallback.onHeadToStagePoseUpdated invalid transform length"
+ headToStage.length);
return;
}
if (DEBUG) { if (DEBUG) {
// 6 values * (4 digits + 1 dot + 2 brackets) = 42 characters // 6 values * (4 digits + 1 dot + 2 brackets) = 42 characters
StringBuilder t = new StringBuilder(42); StringBuilder t = new StringBuilder(42);
@@ -204,6 +224,7 @@ public class SpatializerHelper {
} }
logd("SpatializerCallback.onHeadToStagePoseUpdated headToStage:" + t); logd("SpatializerCallback.onHeadToStagePoseUpdated headToStage:" + t);
} }
dispatchPoseUpdate(headToStage);
} }
}; };
@@ -472,4 +493,239 @@ public class SpatializerHelper {
logd("canBeSpatialized returning " + able); logd("canBeSpatialized returning " + able);
return 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();
}
} }