From 838913c584d0f3a94d35c378f1869a279a4e216a Mon Sep 17 00:00:00 2001 From: Jean-Michel Trivi Date: Thu, 2 Sep 2021 20:55:44 -0700 Subject: [PATCH] AudioService: initialize Spatializer, track routing changes Implement initialization of SpatializerHelper, the AudioService proxy for the native Spatializer effect. Implement release of Spatializer effect when feature is disabled Implement Spatializer available dispatch. Track routing changes reported through AudioSystem to check if device for media routing is compatible with spatialization. Use result to update Spatializer availability Bug: 188502620 Test: adb logcat | grep Spatializer, check for "init succeeded" Change-Id: Ib06b1f54fee20a5057768a7a31a047bd1fba930d --- media/java/android/media/AudioManager.java | 3 +- media/java/android/media/Spatializer.java | 32 +- .../android/server/audio/AudioService.java | 51 ++- .../server/audio/AudioSystemAdapter.java | 23 + .../server/audio/SpatializerHelper.java | 393 ++++++++++++++++-- 5 files changed, 465 insertions(+), 37 deletions(-) diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 5db9ddfac8701..ee75a8d6f4b23 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -2443,8 +2443,7 @@ public class AudioManager { /** * Return a handle to the optional platform's {@link Spatializer} - * @return {@code null} if spatialization is not supported, the {@code Spatializer} instance - * otherwise. + * @return the {@code Spatializer} instance. */ public @Nullable Spatializer getSpatializer() { int level = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java index 3ed8b58959a19..c64bf2c601172 100644 --- a/media/java/android/media/Spatializer.java +++ b/media/java/android/media/Spatializer.java @@ -114,27 +114,53 @@ public class Spatializer { /** @hide */ @IntDef(flag = false, value = { + SPATIALIZER_IMMERSIVE_LEVEL_OTHER, SPATIALIZER_IMMERSIVE_LEVEL_NONE, SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL, }) @Retention(RetentionPolicy.SOURCE) public @interface ImmersiveAudioLevel {}; + /** + * @hide + * 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; + /** * @hide * Constant indicating there are no spatialization capabilities supported on this device. - * @see AudioManager#getImmersiveAudioLevel() + * @see #getImmersiveAudioLevel() */ public static final int SPATIALIZER_IMMERSIVE_LEVEL_NONE = 0; /** * @hide - * Constant indicating the {@link Spatializer} on this device supports multichannel + * Constant indicating the {@code Spatializer} on this device supports multichannel * spatialization. - * @see AudioManager#getImmersiveAudioLevel() + * @see #getImmersiveAudioLevel() */ public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1; + /** + * @hide + * 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. diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index b9b90c0f64eab..f2ba42c6ceeaa 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -200,7 +200,8 @@ import java.util.stream.Collectors; */ public class AudioService extends IAudioService.Stub implements AccessibilityManager.TouchExplorationStateChangeListener, - AccessibilityManager.AccessibilityServicesStateChangeListener { + AccessibilityManager.AccessibilityServicesStateChangeListener, + AudioSystemAdapter.OnRoutingUpdatedListener { private static final String TAG = "AS.AudioService"; @@ -314,12 +315,14 @@ public class AudioService extends IAudioService.Stub private static final int MSG_SET_A2DP_DEV_CONNECTION_STATE = 38; private static final int MSG_A2DP_DEV_CONFIG_CHANGE = 39; private static final int MSG_DISPATCH_AUDIO_MODE = 40; + private static final int MSG_ROUTING_UPDATED = 41; // start of messages handled under wakelock // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), // and not with sendMsg(..., ..., SENDMSG_QUEUE, ...) private static final int MSG_DISABLE_AUDIO_FOR_UID = 100; private static final int MSG_INIT_STREAMS_VOLUMES = 101; + private static final int MSG_INIT_SPATIALIZER = 102; // end of messages handled under wakelock // retry delay in case of failure to indicate system ready to AudioFlinger @@ -869,6 +872,8 @@ public class AudioService extends IAudioService.Stub mSfxHelper = new SoundEffectsHelper(mContext); + mSpatializerHelper = new SpatializerHelper(this, mAudioSystem); + mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); mHasVibrator = mVibrator == null ? false : mVibrator.hasVibrator(); @@ -1033,6 +1038,9 @@ public class AudioService extends IAudioService.Stub // done with service initialization, continue additional work in our Handler thread queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_STREAMS_VOLUMES, 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */); + queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_SPATIALIZER, + 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */); + } /** @@ -1222,6 +1230,22 @@ public class AudioService extends IAudioService.Stub updateVibratorInfos(); } + //----------------------------------------------------------------- + // routing monitoring from AudioSystemAdapter + @Override + public void onRoutingUpdatedFromNative() { + sendMsg(mAudioHandler, + MSG_ROUTING_UPDATED, + SENDMSG_REPLACE, 0, 0, null, + /*delay*/ 0); + } + + void monitorRoutingChanges(boolean enabled) { + mAudioSystem.setRoutingListener(enabled ? this : null); + } + + + //----------------------------------------------------------------- RoleObserver mRoleObserver; class RoleObserver implements OnRoleHoldersChangedListener { @@ -1406,6 +1430,9 @@ public class AudioService extends IAudioService.Stub } } + // TODO check property if feature enabled + mSpatializerHelper.reset(/* featureEnabled */ true); + onIndicateSystemReady(); // indicate the end of reconfiguration phase to audio HAL AudioSystem.setParameters("restarting=false"); @@ -7539,6 +7566,13 @@ public class AudioService extends IAudioService.Stub mAudioEventWakeLock.release(); break; + case MSG_INIT_SPATIALIZER: + mSpatializerHelper.init(); + // TODO read property to see if enabled + mSpatializerHelper.setFeatureEnabled(true); + mAudioEventWakeLock.release(); + break; + case MSG_CHECK_MUSIC_ACTIVE: onCheckMusicActive((String) msg.obj); break; @@ -7671,6 +7705,10 @@ public class AudioService extends IAudioService.Stub case MSG_DISPATCH_AUDIO_MODE: dispatchMode(msg.arg1); break; + + case MSG_ROUTING_UPDATED: + mSpatializerHelper.onRoutingUpdated(); + break; } } } @@ -8239,7 +8277,7 @@ public class AudioService extends IAudioService.Stub } //========================================================================================== - private final SpatializerHelper mSpatializerHelper = new SpatializerHelper(); + private final @NonNull SpatializerHelper mSpatializerHelper; private void enforceModifyDefaultAudioEffectsPermission() { if (mContext.checkCallingOrSelfPermission( @@ -8249,9 +8287,12 @@ public class AudioService extends IAudioService.Stub } } - /** @see AudioManager#getSpatializerImmersiveAudioLevel() */ + /** + * Returns the immersive audio level that the platform is capable of + * @see Spatializer#getImmersiveAudioLevel() + */ public int getSpatializerImmersiveAudioLevel() { - return mSpatializerHelper.getImmersiveAudioLevel(); + return mSpatializerHelper.getCapableImmersiveAudioLevel(); } /** @see Spatializer#isEnabled() */ @@ -8267,7 +8308,7 @@ public class AudioService extends IAudioService.Stub /** @see Spatializer#setSpatializerEnabled(boolean) */ public void setSpatializerEnabled(boolean enabled) { enforceModifyDefaultAudioEffectsPermission(); - mSpatializerHelper.setEnabled(enabled); + mSpatializerHelper.setFeatureEnabled(enabled); } /** @see Spatializer#canBeSpatialized() */ diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index 6d567807f357f..ac212eee21e65 100644 --- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java +++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java @@ -17,6 +17,7 @@ package com.android.server.audio; import android.annotation.NonNull; +import android.annotation.Nullable; import android.media.AudioAttributes; import android.media.AudioDeviceAttributes; import android.media.AudioSystem; @@ -24,6 +25,8 @@ import android.media.audiopolicy.AudioMix; import android.os.SystemClock; import android.util.Log; +import com.android.internal.annotations.GuardedBy; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -59,6 +62,9 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback { private ConcurrentHashMap> mDevicesForAttrCache; private int[] mMethodCacheHit; + private static final Object sRoutingListenerLock = new Object(); + @GuardedBy("sRoutingListenerLock") + private static @Nullable OnRoutingUpdatedListener sRoutingListener; /** * should be false except when trying to debug caching errors. When true, the value retrieved @@ -76,6 +82,23 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback { Log.d(TAG, "---- onRoutingUpdated (from native) ----------"); } invalidateRoutingCache(); + final OnRoutingUpdatedListener listener; + synchronized (sRoutingListenerLock) { + listener = sRoutingListener; + } + if (listener != null) { + listener.onRoutingUpdatedFromNative(); + } + } + + interface OnRoutingUpdatedListener { + void onRoutingUpdatedFromNative(); + } + + static void setRoutingListener(@Nullable OnRoutingUpdatedListener listener) { + synchronized (sRoutingListenerLock) { + sRoutingListener = listener; + } } /** diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index 708d9e10ad737..2ca100c910438 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -17,9 +17,13 @@ package com.android.server.audio; import android.annotation.NonNull; +import android.annotation.Nullable; import android.media.AudioAttributes; import android.media.AudioDeviceAttributes; import android.media.AudioFormat; +import android.media.AudioSystem; +import android.media.INativeSpatializerCallback; +import android.media.ISpatializer; import android.media.ISpatializerCallback; import android.media.Spatializer; import android.os.RemoteCallbackList; @@ -28,6 +32,7 @@ import android.util.Log; import java.util.ArrayList; import java.util.List; +import java.util.Locale; /** * A helper class to manage Spatializer related functionality @@ -35,12 +40,167 @@ import java.util.List; public class SpatializerHelper { private static final String TAG = "AS.SpatializerHelper"; + private static final boolean DEBUG = true; + + private static void logd(String s) { + if (DEBUG) { + Log.i(TAG, s); + } + } + + private final @NonNull AudioSystemAdapter mASA; + private final @NonNull AudioService mAudioService; + + //------------------------------------------------------------ + // Spatializer state machine + private static final int STATE_UNINITIALIZED = 0; + private static final int STATE_NOT_SUPPORTED = 1; + private static final int STATE_DISABLED_UNAVAILABLE = 3; + private static final int STATE_ENABLED_UNAVAILABLE = 4; + private static final int STATE_ENABLED_AVAILABLE = 5; + private static final int STATE_DISABLED_AVAILABLE = 6; + private int mState = STATE_UNINITIALIZED; + + /** 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 @Nullable ISpatializer mSpat; + private @Nullable SpatializerCallback mSpatCallback; + + // default attributes and format that determine basic availability of spatialization + private static final AudioAttributes DEFAULT_ATTRIBUTES = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .build(); + private static final AudioFormat DEFAULT_FORMAT = new AudioFormat.Builder() + .setEncoding(AudioFormat.ENCODING_PCM_16BIT) + .setSampleRate(48000) + .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1) + .build(); + // device array to store the routing for the default attributes and format, size 1 because + // media is never expected to be duplicated + private static final AudioDeviceAttributes[] ROUTING_DEVICES = new AudioDeviceAttributes[1]; //--------------------------------------------------------------- // audio device compatibility / enabled private final ArrayList mCompatibleAudioDevices = new ArrayList<>(0); + //------------------------------------------------------ + // initialization + SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa) { + mAudioService = mother; + mASA = asa; + } + + synchronized void init() { + Log.i(TAG, "Initializing"); + if (mState != STATE_UNINITIALIZED) { + throw new IllegalStateException(("init() called in state:" + mState)); + } + // is there a spatializer? + mSpatCallback = new SpatializerCallback(); + final ISpatializer spat = AudioSystem.getSpatializer(mSpatCallback); + if (spat == null) { + Log.i(TAG, "init(): No Spatializer found"); + mState = STATE_NOT_SUPPORTED; + return; + } + // capabilities of spatializer? + try { + byte[] levels = spat.getSupportedLevels(); + if (levels == null + || levels.length == 0 + || (levels.length == 1 + && levels[0] == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE)) { + Log.e(TAG, "Spatializer is useless"); + mState = STATE_NOT_SUPPORTED; + return; + } + for (byte level : levels) { + logd("found support for level: " + level); + if (level == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL) { + logd("Setting Spatializer to LEVEL_MULTICHANNEL"); + mCapableSpatLevel = level; + break; + } + } + } catch (RemoteException e) { /* capable level remains at NONE*/ } + if (mCapableSpatLevel == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) { + mState = STATE_NOT_SUPPORTED; + return; + } + mState = STATE_DISABLED_UNAVAILABLE; + // note at this point mSpat is still not instantiated + } + + /** + * Like init() but resets the state and spatializer levels + * @param featureEnabled + */ + synchronized void reset(boolean featureEnabled) { + Log.i(TAG, "Resetting"); + mState = STATE_UNINITIALIZED; + mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; + mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; + init(); + setFeatureEnabled(featureEnabled); + } + + //------------------------------------------------------ + // routing monitoring + void onRoutingUpdated() { + switch (mState) { + case STATE_UNINITIALIZED: + case STATE_NOT_SUPPORTED: + return; + case STATE_DISABLED_UNAVAILABLE: + case STATE_ENABLED_UNAVAILABLE: + case STATE_ENABLED_AVAILABLE: + case STATE_DISABLED_AVAILABLE: + break; + } + mASA.getDevicesForAttributes(DEFAULT_ATTRIBUTES).toArray(ROUTING_DEVICES); + final boolean able = + AudioSystem.canBeSpatialized(DEFAULT_ATTRIBUTES, DEFAULT_FORMAT, ROUTING_DEVICES); + logd("onRoutingUpdated: can spatialize media 5.1:" + able + + " on device:" + ROUTING_DEVICES[0]); + setDispatchAvailableState(able); + } + + //------------------------------------------------------ + // spatializer callback from native + private final class SpatializerCallback extends INativeSpatializerCallback.Stub { + + public void onLevelChanged(byte level) { + logd("SpatializerCallback.onLevelChanged level:" + level); + synchronized (SpatializerHelper.this) { + mSpatLevel = level; + } + // TODO use reported spat level to change state + } + + public void onHeadTrackingModeChanged(byte mode) { + logd("SpatializerCallback.onHeadTrackingModeChanged mode:" + mode); + } + + public void onHeadToSoundStagePoseUpdated(float[] headToStage) { + if (headToStage == null) { + Log.e(TAG, "SpatializerCallback.onHeadToStagePoseUpdated null transform"); + return; + } + if (DEBUG) { + // 6 values * (4 digits + 1 dot + 2 brackets) = 42 characters + StringBuilder t = new StringBuilder(42); + for (float val : headToStage) { + t.append("[").append(String.format(Locale.ENGLISH, "%.3f", val)).append("]"); + } + logd("SpatializerCallback.onHeadToStagePoseUpdated headToStage:" + t); + } + } + }; + + //------------------------------------------------------ + // compatible devices /** * @return a shallow copy of the list of compatible audio devices */ @@ -59,37 +219,72 @@ public class SpatializerHelper { } //------------------------------------------------------ - // enabled state - - // global state of feature - boolean mFeatureEnabled = false; - // initialized state, checked after each audio_server start - boolean mInitialized = false; + // states synchronized boolean isEnabled() { - return mFeatureEnabled; + switch (mState) { + case STATE_UNINITIALIZED: + case STATE_NOT_SUPPORTED: + case STATE_DISABLED_UNAVAILABLE: + case STATE_DISABLED_AVAILABLE: + return false; + case STATE_ENABLED_UNAVAILABLE: + case STATE_ENABLED_AVAILABLE: + default: + return true; + } } synchronized boolean isAvailable() { - if (!mInitialized) { - return false; - } - // TODO check device compatibility - // ... - return true; - } - - synchronized void setEnabled(boolean enabled) { - final boolean oldState = mFeatureEnabled; - mFeatureEnabled = enabled; - if (oldState != enabled) { - dispatchEnabledState(); + switch (mState) { + case STATE_UNINITIALIZED: + case STATE_NOT_SUPPORTED: + case STATE_ENABLED_UNAVAILABLE: + case STATE_DISABLED_UNAVAILABLE: + return false; + case STATE_DISABLED_AVAILABLE: + case STATE_ENABLED_AVAILABLE: + default: + return true; } } - public int getImmersiveAudioLevel() { - // TODO replace placeholder code with actual effect discovery - return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; + synchronized void setFeatureEnabled(boolean enabled) { + switch (mState) { + case STATE_UNINITIALIZED: + if (enabled) { + throw(new IllegalStateException("Can't enable when uninitialized")); + } + return; + case STATE_NOT_SUPPORTED: + if (enabled) { + Log.e(TAG, "Can't enable when unsupported"); + } + return; + case STATE_DISABLED_UNAVAILABLE: + case STATE_DISABLED_AVAILABLE: + if (enabled) { + createSpat(); + break; + } else { + // already in disabled state + return; + } + case STATE_ENABLED_UNAVAILABLE: + case STATE_ENABLED_AVAILABLE: + if (!enabled) { + releaseSpat(); + break; + } else { + // already in enabled state + return; + } + } + setDispatchFeatureEnabledState(enabled); + } + + synchronized int getCapableImmersiveAudioLevel() { + return mCapableSpatLevel; } final RemoteCallbackList mStateCallbacks = @@ -105,12 +300,94 @@ public class SpatializerHelper { mStateCallbacks.unregister(callback); } - private synchronized void dispatchEnabledState() { + /** + * precondition: mState = STATE_* + * isFeatureEnabled() != featureEnabled + * @param featureEnabled + */ + private synchronized void setDispatchFeatureEnabledState(boolean featureEnabled) { + if (featureEnabled) { + switch (mState) { + case STATE_DISABLED_UNAVAILABLE: + mState = STATE_ENABLED_UNAVAILABLE; + break; + case STATE_DISABLED_AVAILABLE: + mState = STATE_ENABLED_AVAILABLE; + break; + default: + throw(new IllegalStateException("Invalid mState:" + mState + + " for enabled true")); + } + } else { + switch (mState) { + case STATE_ENABLED_UNAVAILABLE: + mState = STATE_DISABLED_UNAVAILABLE; + break; + case STATE_ENABLED_AVAILABLE: + mState = STATE_DISABLED_AVAILABLE; + break; + default: + throw (new IllegalStateException("Invalid mState:" + mState + + " for enabled false")); + } + } final int nbCallbacks = mStateCallbacks.beginBroadcast(); for (int i = 0; i < nbCallbacks; i++) { try { mStateCallbacks.getBroadcastItem(i) - .dispatchSpatializerEnabledChanged(mFeatureEnabled); + .dispatchSpatializerEnabledChanged(featureEnabled); + } catch (RemoteException e) { + Log.e(TAG, "Error in dispatchSpatializerEnabledChanged", e); + } + } + mStateCallbacks.finishBroadcast(); + // TODO persist enabled state + } + + private synchronized void setDispatchAvailableState(boolean available) { + switch (mState) { + case STATE_UNINITIALIZED: + case STATE_NOT_SUPPORTED: + throw(new IllegalStateException( + "Should not update available state in state:" + mState)); + case STATE_DISABLED_UNAVAILABLE: + if (available) { + mState = STATE_DISABLED_AVAILABLE; + break; + } else { + // already in unavailable state + return; + } + case STATE_ENABLED_UNAVAILABLE: + if (available) { + mState = STATE_ENABLED_AVAILABLE; + break; + } else { + // already in unavailable state + return; + } + case STATE_DISABLED_AVAILABLE: + if (available) { + // already in available state + return; + } else { + mState = STATE_DISABLED_UNAVAILABLE; + break; + } + case STATE_ENABLED_AVAILABLE: + if (available) { + // already in available state + return; + } else { + mState = STATE_ENABLED_UNAVAILABLE; + break; + } + } + final int nbCallbacks = mStateCallbacks.beginBroadcast(); + for (int i = 0; i < nbCallbacks; i++) { + try { + mStateCallbacks.getBroadcastItem(i) + .dispatchSpatializerAvailableChanged(available); } catch (RemoteException e) { Log.e(TAG, "Error in dispatchSpatializerEnabledChanged", e); } @@ -118,11 +395,73 @@ public class SpatializerHelper { mStateCallbacks.finishBroadcast(); } + //------------------------------------------------------ + // native Spatializer management + + /** + * precondition: mState == STATE_DISABLED_* + */ + private void createSpat() { + if (mSpat == null) { + mSpatCallback = new SpatializerCallback(); + mSpat = AudioSystem.getSpatializer(mSpatCallback); + try { + mSpat.setLevel((byte) Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL); + } catch (RemoteException e) { + Log.e(TAG, "Can't set spatializer level", e); + mState = STATE_NOT_SUPPORTED; + mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; + } + } + } + + /** + * precondition: mState == STATE_ENABLED_* + */ + private void releaseSpat() { + if (mSpat != null) { + mSpatCallback = null; + try { + mSpat.release(); + mSpat = null; + } catch (RemoteException e) { + Log.e(TAG, "Can't set release spatializer cleanly", e); + } + } + } + //------------------------------------------------------ // virtualization capabilities synchronized boolean canBeSpatialized( @NonNull AudioAttributes attributes, @NonNull AudioFormat format) { - // TODO hook up to spatializer effect for query - return false; + logd("canBeSpatialized usage:" + attributes.getUsage() + + " format:" + format.toLogFriendlyString()); + switch (mState) { + case STATE_UNINITIALIZED: + case STATE_NOT_SUPPORTED: + case STATE_ENABLED_UNAVAILABLE: + case STATE_DISABLED_UNAVAILABLE: + logd("canBeSpatialized false due to state:" + mState); + return false; + case STATE_DISABLED_AVAILABLE: + case STATE_ENABLED_AVAILABLE: + break; + } + + // filter on AudioAttributes usage + switch (attributes.getUsage()) { + case AudioAttributes.USAGE_MEDIA: + case AudioAttributes.USAGE_GAME: + break; + default: + logd("canBeSpatialized false due to usage:" + attributes.getUsage()); + return false; + } + AudioDeviceAttributes[] devices = + // going through adapter to take advantage of routing cache + (AudioDeviceAttributes[]) mASA.getDevicesForAttributes(attributes).toArray(); + final boolean able = AudioSystem.canBeSpatialized(attributes, format, devices); + logd("canBeSpatialized returning " + able); + return able; } }