All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.android.media.Spatializer Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * 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;

import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.media.permission.ClearCallingIdentityContext;
import android.media.permission.SafeCloseable;
import android.os.RemoteException;
import android.util.Log;

import com.android.internal.annotations.GuardedBy;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;

/**
 * Spatializer provides access to querying capabilities and behavior of sound spatialization
 * on the device.
 * Sound spatialization simulates sounds originating around the listener as if they were coming
 * from virtual speakers placed around the listener.
* Support for spatialization is optional, use {@link AudioManager#getSpatializer()} to obtain an * instance of this class if the feature is supported. * */ public class Spatializer { private final @NonNull AudioManager mAm; private static final String TAG = "Spatializer"; /** * @hide * Constructor with AudioManager acting as proxy to AudioService * @param am a non-null AudioManager */ protected Spatializer(@NonNull AudioManager am) { mAm = Objects.requireNonNull(am); } /** * Returns whether spatialization is enabled or not. * A false value can originate for instance from the user electing to * disable the feature, or when the feature is not supported on the device (indicated * by {@link #getImmersiveAudioLevel()} returning {@link #SPATIALIZER_IMMERSIVE_LEVEL_NONE}). *
* Note that this state reflects a platform-wide state of the "desire" to use spatialization, * but availability of the audio processing is still dictated by the compatibility between * the effect and the hardware configuration, as indicated by {@link #isAvailable()}. * @return {@code true} if spatialization is enabled * @see #isAvailable() */ public boolean isEnabled() { try { return mAm.getService().isSpatializerEnabled(); } catch (RemoteException e) { Log.e(TAG, "Error querying isSpatializerEnabled, returning false", e); return false; } } /** * Returns whether spatialization is available. * Reasons for spatialization being unavailable include situations where audio output is * incompatible with sound spatialization, such as playback on a monophonic speaker.
* Note that spatialization can be available, but disabled by the user, in which case this * method would still return {@code true}, whereas {@link #isEnabled()} * would return {@code false}.
* Also when the feature is not supported on the device (indicated * by {@link #getImmersiveAudioLevel()} returning {@link #SPATIALIZER_IMMERSIVE_LEVEL_NONE}), * the return value will be false. * @return {@code true} if the spatializer effect is available and capable * of processing the audio for the current configuration of the device, * {@code false} otherwise. * @see #isEnabled() */ public boolean isAvailable() { try { return mAm.getService().isSpatializerAvailable(); } catch (RemoteException e) { Log.e(TAG, "Error querying isSpatializerAvailable, returning false", e); return false; } } /** @hide */ @IntDef(flag = false, value = { SPATIALIZER_IMMERSIVE_LEVEL_OTHER, SPATIALIZER_IMMERSIVE_LEVEL_NONE, SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL, }) @Retention(RetentionPolicy.SOURCE) public @interface ImmersiveAudioLevel {}; /** * Constant indicating the {@code Spatializer} on this device supports a spatialization * mode that differs from the ones available at this SDK level. * @see #getImmersiveAudioLevel() */ public static final int SPATIALIZER_IMMERSIVE_LEVEL_OTHER = -1; /** * Constant indicating there are no spatialization capabilities supported on this device. * @see #getImmersiveAudioLevel() */ public static final int SPATIALIZER_IMMERSIVE_LEVEL_NONE = 0; /** * Constant indicating the {@code Spatializer} on this device supports multichannel * spatialization. * @see #getImmersiveAudioLevel() */ public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1; /** * @hide * Constant indicating the {@code Spatializer} on this device supports the spatialization of * multichannel bed plus objects. * @see #getImmersiveAudioLevel() */ public static final int SPATIALIZER_IMMERSIVE_LEVEL_MCHAN_BED_PLUS_OBJECTS = 2; /** @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 * enabled or available and will not change over time. * @return the level of spatialization support * @see #isEnabled() * @see #isAvailable() */ public @ImmersiveAudioLevel int getImmersiveAudioLevel() { int level = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; try { level = mAm.getService().getSpatializerImmersiveAudioLevel(); } catch (Exception e) { /* using NONE */ } return level; } /** * @hide * Enables / disables the spatializer effect. * Changing the enabled state will trigger the public * {@link OnSpatializerStateChangedListener#onSpatializerEnabledChanged(Spatializer, boolean)} * registered listeners. * @param enabled {@code true} for enabling the effect */ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) public void setEnabled(boolean enabled) { try { mAm.getService().setSpatializerEnabled(enabled); } catch (RemoteException e) { Log.e(TAG, "Error calling setSpatializerEnabled", e); } } /** * An interface to be notified of changes to the state of the spatializer effect. */ public interface OnSpatializerStateChangedListener { /** * Called when the enabled state of the spatializer effect changes * @param spat the {@code Spatializer} instance whose state changed * @param enabled {@code true} if the spatializer effect is enabled on the device, * {@code false} otherwise * @see #isEnabled() */ void onSpatializerEnabledChanged(@NonNull Spatializer spat, boolean enabled); /** * Called when the availability of the spatializer effect changes * @param spat the {@code Spatializer} instance whose state changed * @param available {@code true} if the spatializer effect is available and capable * of processing the audio for the current configuration of the device, * {@code false} otherwise. * @see #isAvailable() */ 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 changes to the output stream used by the spatializer * effect. * @see #getOutput() */ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) public interface OnSpatializerOutputChangedListener { /** * Called when the id of the output stream of the spatializer effect changed. * @param spatializer the {@code Spatializer} instance whose output is updated * @param output the id of the output stream, or 0 when there is no spatializer output */ void onSpatializerOutputChanged(@NonNull Spatializer spatializer, @IntRange(from = 0) int output); } /** * @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).
* 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 * audio accessories are connected/disconnected (e.g. wired headphones plugged in or not). * The result is independent from whether spatialization processing is enabled or not. * @param attributes the {@code AudioAttributes} of the content as used for playback * @param format the {@code AudioFormat} of the content as used for playback * @return {@code true} if the device is capable of spatializing the combination of audio format * and attributes, {@code false} otherwise. */ public boolean canBeSpatialized( @NonNull AudioAttributes attributes, @NonNull AudioFormat format) { try { return mAm.getService().canBeSpatialized( Objects.requireNonNull(attributes), Objects.requireNonNull(format)); } catch (RemoteException e) { Log.e(TAG, "Error querying canBeSpatialized for attr:" + attributes + " format:" + format + " returning false", e); return false; } } /** * Adds a listener to be notified of changes to the enabled state of the * {@code Spatializer}. * @param executor the {@code Executor} handling the callback * @param listener the listener to receive enabled state updates * @see #isEnabled() */ public void addOnSpatializerStateChangedListener( @NonNull @CallbackExecutor Executor executor, @NonNull OnSpatializerStateChangedListener listener) { Objects.requireNonNull(executor); Objects.requireNonNull(listener); synchronized (mStateListenerLock) { if (hasSpatializerStateListener(listener)) { throw new IllegalArgumentException( "Called addOnSpatializerStateChangedListener() " + "on a previously registered listener"); } // lazy initialization of the list of strategy-preferred device listener if (mStateListeners == null) { mStateListeners = new ArrayList<>(); } mStateListeners.add(new StateListenerInfo(listener, executor)); if (mStateListeners.size() == 1) { // register binder for callbacks if (mInfoDispatcherStub == null) { mInfoDispatcherStub = new SpatializerInfoDispatcherStub(); } try { mAm.getService().registerSpatializerCallback( mInfoDispatcherStub); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } } /** * Removes a previously added listener for changes to the enabled state of the * {@code Spatializer}. * @param listener the listener to receive enabled state updates * @see #isEnabled() */ public void removeOnSpatializerStateChangedListener( @NonNull OnSpatializerStateChangedListener listener) { Objects.requireNonNull(listener); synchronized (mStateListenerLock) { if (!removeStateListener(listener)) { throw new IllegalArgumentException( "Called removeOnSpatializerStateChangedListener() " + "on an unregistered listener"); } if (mStateListeners.size() == 0) { // unregister binder for callbacks try { mAm.getService().unregisterSpatializerCallback(mInfoDispatcherStub); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } finally { mInfoDispatcherStub = null; mStateListeners = null; } } } } /** * @hide * Returns the list of playback devices that are compatible with the playback of multichannel * audio through virtualization * @return a list of devices. An empty list indicates virtualization is not supported. */ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) public @NonNull List getCompatibleAudioDevices() { try { return mAm.getService().getSpatializerCompatibleAudioDevices(); } catch (RemoteException e) { Log.e(TAG, "Error querying getSpatializerCompatibleAudioDevices(), " + " returning empty list", e); return new ArrayList(0); } } /** * @hide * Adds a playback device to the list of devices compatible with the playback of multichannel * audio through spatialization. * @see #getCompatibleAudioDevices() * @param ada the audio device compatible with spatialization */ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) public void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { try { mAm.getService().addSpatializerCompatibleAudioDevice(Objects.requireNonNull(ada)); } catch (RemoteException e) { Log.e(TAG, "Error calling addSpatializerCompatibleAudioDevice(), ", e); } } /** * @hide * Remove a playback device from the list of devices compatible with the playback of * multichannel audio through spatialization. * @see #getCompatibleAudioDevices() * @param ada the audio device incompatible with spatialization */ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) public void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { try { mAm.getService().removeSpatializerCompatibleAudioDevice(Objects.requireNonNull(ada)); } catch (RemoteException e) { Log.e(TAG, "Error calling removeSpatializerCompatibleAudioDevice(), ", e); } } 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 mStateListeners; @GuardedBy("mStateListenerLock") private @Nullable SpatializerInfoDispatcherStub mInfoDispatcherStub; private final class SpatializerInfoDispatcherStub extends ISpatializerCallback.Stub { @Override public void dispatchSpatializerEnabledChanged(boolean enabled) { // make a shallow copy of listeners so callback is not executed under lock final ArrayList stateListeners; synchronized (mStateListenerLock) { if (mStateListeners == null || mStateListeners.size() == 0) { return; } stateListeners = (ArrayList) mStateListeners.clone(); } try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { for (StateListenerInfo info : stateListeners) { info.mExecutor.execute(() -> info.mListener.onSpatializerEnabledChanged(Spatializer.this, enabled)); } } } @Override public void dispatchSpatializerAvailableChanged(boolean available) { // make a shallow copy of listeners so callback is not executed under lock final ArrayList stateListeners; synchronized (mStateListenerLock) { if (mStateListeners == null || mStateListeners.size() == 0) { return; } stateListeners = (ArrayList) mStateListeners.clone(); } try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { for (StateListenerInfo info : stateListeners) { info.mExecutor.execute(() -> info.mListener.onSpatializerAvailableChanged( Spatializer.this, available)); } } } } private static class StateListenerInfo { final @NonNull OnSpatializerStateChangedListener mListener; final @NonNull Executor mExecutor; StateListenerInfo(@NonNull OnSpatializerStateChangedListener listener, @NonNull Executor exe) { mListener = listener; mExecutor = exe; } } @GuardedBy("mStateListenerLock") private boolean hasSpatializerStateListener(OnSpatializerStateChangedListener listener) { return getStateListenerInfo(listener) != null; } @GuardedBy("mStateListenerLock") private @Nullable StateListenerInfo getStateListenerInfo( OnSpatializerStateChangedListener listener) { if (mStateListeners == null) { return null; } for (StateListenerInfo info : mStateListeners) { if (info.mListener == listener) { return info; } } return null; } @GuardedBy("mStateListenerLock") /** * @return true if the listener was removed from the list */ private boolean removeStateListener(OnSpatializerStateChangedListener listener) { final StateListenerInfo infoToRemove = getStateListenerInfo(listener); if (infoToRemove != null) { mStateListeners.remove(infoToRemove); return true; } 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 getSupportedHeadTrackingModes() { try { final int[] modes = mAm.getService().getSupportedHeadTrackingModes(); final ArrayList 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(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(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); } } /** * @hide * Sets a parameter on the platform spatializer effect implementation. * This is to be used for vendor-specific configurations of their effect, keys and values are * not reuseable across implementations. * @param key the parameter to change * @param value an array for the value of the parameter to change */ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) public void setEffectParameter(int key, @NonNull byte[] value) { Objects.requireNonNull(value); try { mAm.getService().setSpatializerParameter(key, value); } catch (RemoteException e) { Log.e(TAG, "Error calling setEffectParameter", e); } } /** * @hide * Retrieves a parameter value from the platform spatializer effect implementation. * This is to be used for vendor-specific configurations of their effect, keys and values are * not reuseable across implementations. * @param key the parameter for which the value is queried * @param value a non-empty array to contain the return value. The caller is responsible for * passing an array of size matching the parameter. */ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) public void getEffectParameter(int key, @NonNull byte[] value) { Objects.requireNonNull(value); try { mAm.getService().getSpatializerParameter(key, value); } catch (RemoteException e) { Log.e(TAG, "Error calling getEffectParameter", e); } } /** * @hide * Returns the id of the output stream used for the spatializer effect playback * @return id of the output stream, or 0 if no spatializer playback is active */ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) public @IntRange(from = 0) int getOutput() { try { return mAm.getService().getSpatializerOutput(); } catch (RemoteException e) { Log.e(TAG, "Error calling getSpatializerOutput", e); return 0; } } /** * @hide * Sets the listener to receive spatializer effect output updates * @param executor the {@code Executor} handling the callbacks * @param listener the listener to register * @see #clearOnSpatializerOutputChangedListener() * @see #getOutput() */ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) public void setOnSpatializerOutputChangedListener( @NonNull @CallbackExecutor Executor executor, @NonNull OnSpatializerOutputChangedListener listener) { Objects.requireNonNull(executor); Objects.requireNonNull(listener); synchronized (mOutputListenerLock) { if (mOutputListener != null) { throw new IllegalStateException("Trying to overwrite existing listener"); } mOutputListener = new ListenerInfo(listener, executor); mOutputDispatcher = new SpatializerOutputDispatcherStub(); try { mAm.getService().registerSpatializerOutputCallback(mOutputDispatcher); } catch (RemoteException e) { mOutputListener = null; mOutputDispatcher = null; } } } /** * @hide * Clears the listener for spatializer effect output updates * @see #setOnSpatializerOutputChangedListener(Executor, OnSpatializerOutputChangedListener) */ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) public void clearOnSpatializerOutputChangedListener() { synchronized (mOutputListenerLock) { if (mOutputDispatcher == null) { throw (new IllegalStateException("No listener to clear")); } try { mAm.getService().unregisterSpatializerOutputCallback(mOutputDispatcher); } catch (RemoteException e) { } mOutputListener = null; mOutputDispatcher = null; } } //----------------------------------------------------------------------------- // callback helper definitions private static class ListenerInfo { final @NonNull T mListener; final @NonNull Executor mExecutor; ListenerInfo(T listener, Executor exe) { mListener = listener; mExecutor = exe; } } private static ListenerInfo getListenerInfo( T listener, ArrayList> listeners) { if (listeners == null) { return null; } for (ListenerInfo info : listeners) { if (info.mListener == listener) { return info; } } return null; } private static boolean hasListener(T listener, ArrayList> listeners) { return getListenerInfo(listener, listeners) != null; } private static boolean removeListener(T listener, ArrayList> listeners) { final ListenerInfo 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> 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> headTrackingListeners; synchronized (mHeadTrackingListenerLock) { if (mHeadTrackingListeners == null || mHeadTrackingListeners.size() == 0) { return; } headTrackingListeners = (ArrayList>) mHeadTrackingListeners.clone(); } try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { for (ListenerInfo 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> headTrackingListeners; synchronized (mHeadTrackingListenerLock) { if (mHeadTrackingListeners == null || mHeadTrackingListeners.size() == 0) { return; } headTrackingListeners = (ArrayList>) mHeadTrackingListeners.clone(); } try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { for (ListenerInfo 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 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 listener; synchronized (mPoseListenerLock) { listener = mPoseListener; } if (listener == null) { return; } try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { listener.mExecutor.execute(() -> listener.mListener .onHeadToSoundstagePoseUpdated(Spatializer.this, pose)); } } } //----------------------------------------------------------------------------- // output callback management and stub private final Object mOutputListenerLock = new Object(); /** * Listener for output updates */ @GuardedBy("mOutputListenerLock") private @Nullable ListenerInfo mOutputListener; @GuardedBy("mOutputListenerLock") private @Nullable SpatializerOutputDispatcherStub mOutputDispatcher; private final class SpatializerOutputDispatcherStub extends ISpatializerOutputCallback.Stub { @Override public void dispatchSpatializerOutputChanged(int output) { // make a copy of ref to listener so callback is not executed under lock final ListenerInfo listener; synchronized (mOutputListenerLock) { listener = mOutputListener; } if (listener == null) { return; } try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { listener.mExecutor.execute(() -> listener.mListener .onSpatializerOutputChanged(Spatializer.this, output)); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy