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
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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() */
|
||||
|
||||
@@ -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<AudioAttributes, ArrayList<AudioDeviceAttributes>>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<AudioDeviceAttributes> 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<ISpatializerCallback> 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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user