Merge "AudioService: initialize Spatializer, track routing changes" into sc-v2-dev
This commit is contained in:
@@ -2443,8 +2443,7 @@ public class AudioManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a handle to the optional platform's {@link Spatializer}
|
* Return a handle to the optional platform's {@link Spatializer}
|
||||||
* @return {@code null} if spatialization is not supported, the {@code Spatializer} instance
|
* @return the {@code Spatializer} instance.
|
||||||
* otherwise.
|
|
||||||
*/
|
*/
|
||||||
public @Nullable Spatializer getSpatializer() {
|
public @Nullable Spatializer getSpatializer() {
|
||||||
int level = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
|
int level = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
|
||||||
|
|||||||
@@ -114,27 +114,53 @@ public class Spatializer {
|
|||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
@IntDef(flag = false, value = {
|
@IntDef(flag = false, value = {
|
||||||
|
SPATIALIZER_IMMERSIVE_LEVEL_OTHER,
|
||||||
SPATIALIZER_IMMERSIVE_LEVEL_NONE,
|
SPATIALIZER_IMMERSIVE_LEVEL_NONE,
|
||||||
SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL,
|
SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL,
|
||||||
})
|
})
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
public @interface ImmersiveAudioLevel {};
|
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
|
* @hide
|
||||||
* Constant indicating there are no spatialization capabilities supported on this device.
|
* 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;
|
public static final int SPATIALIZER_IMMERSIVE_LEVEL_NONE = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hide
|
* @hide
|
||||||
* Constant indicating the {@link Spatializer} on this device supports multichannel
|
* Constant indicating the {@code Spatializer} on this device supports multichannel
|
||||||
* spatialization.
|
* spatialization.
|
||||||
* @see AudioManager#getImmersiveAudioLevel()
|
* @see #getImmersiveAudioLevel()
|
||||||
*/
|
*/
|
||||||
public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1;
|
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
|
* @hide
|
||||||
* Enables / disables the spatializer effect.
|
* Enables / disables the spatializer effect.
|
||||||
|
|||||||
@@ -200,7 +200,8 @@ import java.util.stream.Collectors;
|
|||||||
*/
|
*/
|
||||||
public class AudioService extends IAudioService.Stub
|
public class AudioService extends IAudioService.Stub
|
||||||
implements AccessibilityManager.TouchExplorationStateChangeListener,
|
implements AccessibilityManager.TouchExplorationStateChangeListener,
|
||||||
AccessibilityManager.AccessibilityServicesStateChangeListener {
|
AccessibilityManager.AccessibilityServicesStateChangeListener,
|
||||||
|
AudioSystemAdapter.OnRoutingUpdatedListener {
|
||||||
|
|
||||||
private static final String TAG = "AS.AudioService";
|
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_SET_A2DP_DEV_CONNECTION_STATE = 38;
|
||||||
private static final int MSG_A2DP_DEV_CONFIG_CHANGE = 39;
|
private static final int MSG_A2DP_DEV_CONFIG_CHANGE = 39;
|
||||||
private static final int MSG_DISPATCH_AUDIO_MODE = 40;
|
private static final int MSG_DISPATCH_AUDIO_MODE = 40;
|
||||||
|
private static final int MSG_ROUTING_UPDATED = 41;
|
||||||
|
|
||||||
// start of messages handled under wakelock
|
// start of messages handled under wakelock
|
||||||
// these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
|
// these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
|
||||||
// and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
|
// and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
|
||||||
private static final int MSG_DISABLE_AUDIO_FOR_UID = 100;
|
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_STREAMS_VOLUMES = 101;
|
||||||
|
private static final int MSG_INIT_SPATIALIZER = 102;
|
||||||
// end of messages handled under wakelock
|
// end of messages handled under wakelock
|
||||||
|
|
||||||
// retry delay in case of failure to indicate system ready to AudioFlinger
|
// 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);
|
mSfxHelper = new SoundEffectsHelper(mContext);
|
||||||
|
|
||||||
|
mSpatializerHelper = new SpatializerHelper(this, mAudioSystem);
|
||||||
|
|
||||||
mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
|
mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
|
||||||
mHasVibrator = mVibrator == null ? false : mVibrator.hasVibrator();
|
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
|
// done with service initialization, continue additional work in our Handler thread
|
||||||
queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_STREAMS_VOLUMES,
|
queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_STREAMS_VOLUMES,
|
||||||
0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
|
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();
|
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;
|
RoleObserver mRoleObserver;
|
||||||
|
|
||||||
class RoleObserver implements OnRoleHoldersChangedListener {
|
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();
|
onIndicateSystemReady();
|
||||||
// indicate the end of reconfiguration phase to audio HAL
|
// indicate the end of reconfiguration phase to audio HAL
|
||||||
AudioSystem.setParameters("restarting=false");
|
AudioSystem.setParameters("restarting=false");
|
||||||
@@ -7539,6 +7566,13 @@ public class AudioService extends IAudioService.Stub
|
|||||||
mAudioEventWakeLock.release();
|
mAudioEventWakeLock.release();
|
||||||
break;
|
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:
|
case MSG_CHECK_MUSIC_ACTIVE:
|
||||||
onCheckMusicActive((String) msg.obj);
|
onCheckMusicActive((String) msg.obj);
|
||||||
break;
|
break;
|
||||||
@@ -7671,6 +7705,10 @@ public class AudioService extends IAudioService.Stub
|
|||||||
case MSG_DISPATCH_AUDIO_MODE:
|
case MSG_DISPATCH_AUDIO_MODE:
|
||||||
dispatchMode(msg.arg1);
|
dispatchMode(msg.arg1);
|
||||||
break;
|
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() {
|
private void enforceModifyDefaultAudioEffectsPermission() {
|
||||||
if (mContext.checkCallingOrSelfPermission(
|
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() {
|
public int getSpatializerImmersiveAudioLevel() {
|
||||||
return mSpatializerHelper.getImmersiveAudioLevel();
|
return mSpatializerHelper.getCapableImmersiveAudioLevel();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @see Spatializer#isEnabled() */
|
/** @see Spatializer#isEnabled() */
|
||||||
@@ -8267,7 +8308,7 @@ public class AudioService extends IAudioService.Stub
|
|||||||
/** @see Spatializer#setSpatializerEnabled(boolean) */
|
/** @see Spatializer#setSpatializerEnabled(boolean) */
|
||||||
public void setSpatializerEnabled(boolean enabled) {
|
public void setSpatializerEnabled(boolean enabled) {
|
||||||
enforceModifyDefaultAudioEffectsPermission();
|
enforceModifyDefaultAudioEffectsPermission();
|
||||||
mSpatializerHelper.setEnabled(enabled);
|
mSpatializerHelper.setFeatureEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @see Spatializer#canBeSpatialized() */
|
/** @see Spatializer#canBeSpatialized() */
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package com.android.server.audio;
|
package com.android.server.audio;
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
import android.media.AudioAttributes;
|
import android.media.AudioAttributes;
|
||||||
import android.media.AudioDeviceAttributes;
|
import android.media.AudioDeviceAttributes;
|
||||||
import android.media.AudioSystem;
|
import android.media.AudioSystem;
|
||||||
@@ -24,6 +25,8 @@ import android.media.audiopolicy.AudioMix;
|
|||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.GuardedBy;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -59,6 +62,9 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback {
|
|||||||
private ConcurrentHashMap<AudioAttributes, ArrayList<AudioDeviceAttributes>>
|
private ConcurrentHashMap<AudioAttributes, ArrayList<AudioDeviceAttributes>>
|
||||||
mDevicesForAttrCache;
|
mDevicesForAttrCache;
|
||||||
private int[] mMethodCacheHit;
|
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
|
* 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) ----------");
|
Log.d(TAG, "---- onRoutingUpdated (from native) ----------");
|
||||||
}
|
}
|
||||||
invalidateRoutingCache();
|
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;
|
package com.android.server.audio;
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
import android.media.AudioAttributes;
|
import android.media.AudioAttributes;
|
||||||
import android.media.AudioDeviceAttributes;
|
import android.media.AudioDeviceAttributes;
|
||||||
import android.media.AudioFormat;
|
import android.media.AudioFormat;
|
||||||
|
import android.media.AudioSystem;
|
||||||
|
import android.media.INativeSpatializerCallback;
|
||||||
|
import android.media.ISpatializer;
|
||||||
import android.media.ISpatializerCallback;
|
import android.media.ISpatializerCallback;
|
||||||
import android.media.Spatializer;
|
import android.media.Spatializer;
|
||||||
import android.os.RemoteCallbackList;
|
import android.os.RemoteCallbackList;
|
||||||
@@ -28,6 +32,7 @@ import android.util.Log;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A helper class to manage Spatializer related functionality
|
* A helper class to manage Spatializer related functionality
|
||||||
@@ -35,12 +40,167 @@ import java.util.List;
|
|||||||
public class SpatializerHelper {
|
public class SpatializerHelper {
|
||||||
|
|
||||||
private static final String TAG = "AS.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
|
// audio device compatibility / enabled
|
||||||
|
|
||||||
private final ArrayList<AudioDeviceAttributes> mCompatibleAudioDevices = new ArrayList<>(0);
|
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
|
* @return a shallow copy of the list of compatible audio devices
|
||||||
*/
|
*/
|
||||||
@@ -59,37 +219,72 @@ public class SpatializerHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------------------
|
//------------------------------------------------------
|
||||||
// enabled state
|
// states
|
||||||
|
|
||||||
// global state of feature
|
|
||||||
boolean mFeatureEnabled = false;
|
|
||||||
// initialized state, checked after each audio_server start
|
|
||||||
boolean mInitialized = false;
|
|
||||||
|
|
||||||
synchronized boolean isEnabled() {
|
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() {
|
synchronized boolean isAvailable() {
|
||||||
if (!mInitialized) {
|
switch (mState) {
|
||||||
|
case STATE_UNINITIALIZED:
|
||||||
|
case STATE_NOT_SUPPORTED:
|
||||||
|
case STATE_ENABLED_UNAVAILABLE:
|
||||||
|
case STATE_DISABLED_UNAVAILABLE:
|
||||||
return false;
|
return false;
|
||||||
}
|
case STATE_DISABLED_AVAILABLE:
|
||||||
// TODO check device compatibility
|
case STATE_ENABLED_AVAILABLE:
|
||||||
// ...
|
default:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void setEnabled(boolean enabled) {
|
|
||||||
final boolean oldState = mFeatureEnabled;
|
|
||||||
mFeatureEnabled = enabled;
|
|
||||||
if (oldState != enabled) {
|
|
||||||
dispatchEnabledState();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getImmersiveAudioLevel() {
|
synchronized void setFeatureEnabled(boolean enabled) {
|
||||||
// TODO replace placeholder code with actual effect discovery
|
switch (mState) {
|
||||||
return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
|
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 =
|
final RemoteCallbackList<ISpatializerCallback> mStateCallbacks =
|
||||||
@@ -105,12 +300,94 @@ public class SpatializerHelper {
|
|||||||
mStateCallbacks.unregister(callback);
|
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();
|
final int nbCallbacks = mStateCallbacks.beginBroadcast();
|
||||||
for (int i = 0; i < nbCallbacks; i++) {
|
for (int i = 0; i < nbCallbacks; i++) {
|
||||||
try {
|
try {
|
||||||
mStateCallbacks.getBroadcastItem(i)
|
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) {
|
} catch (RemoteException e) {
|
||||||
Log.e(TAG, "Error in dispatchSpatializerEnabledChanged", e);
|
Log.e(TAG, "Error in dispatchSpatializerEnabledChanged", e);
|
||||||
}
|
}
|
||||||
@@ -118,11 +395,73 @@ public class SpatializerHelper {
|
|||||||
mStateCallbacks.finishBroadcast();
|
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
|
// virtualization capabilities
|
||||||
synchronized boolean canBeSpatialized(
|
synchronized boolean canBeSpatialized(
|
||||||
@NonNull AudioAttributes attributes, @NonNull AudioFormat format) {
|
@NonNull AudioAttributes attributes, @NonNull AudioFormat format) {
|
||||||
// TODO hook up to spatializer effect for query
|
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;
|
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