From dce82ab7bfd5ec7c1ef658825c18506a89e567d6 Mon Sep 17 00:00:00 2001 From: Jean-Michel Trivi Date: Tue, 7 Feb 2017 16:02:33 -0800 Subject: [PATCH] Audio focus enforcement: use VolumeShaper for ducking When ducking players, check whether their content type is speech, or the player is a SoundPool. If yes, do not enforce ducking and leave it to the app. Otherwise use a VolumeShaper to duck, and keep it in a list so it can be reused to unduck (by using the REVERSE operation). Test: play a notification while an AudioTrack is playing Bug 30258418 Change-Id: I7e0204ad38d4ef48e88ffc45533caccfc6e279b5 --- media/java/android/media/VolumeShaper.java | 50 +++++++++++-------- .../server/audio/MediaFocusControl.java | 2 +- .../server/audio/PlaybackActivityMonitor.java | 44 ++++++++++++++-- 3 files changed, 69 insertions(+), 27 deletions(-) diff --git a/media/java/android/media/VolumeShaper.java b/media/java/android/media/VolumeShaper.java index 77af3596302bf..4a2c4d88c50e8 100644 --- a/media/java/android/media/VolumeShaper.java +++ b/media/java/android/media/VolumeShaper.java @@ -37,8 +37,9 @@ import java.util.Objects; public final class VolumeShaper { /* member variables */ private int mId; - private final WeakReference mPlayerBase; - private final WeakReference mPlayerProxy; + private final WeakReference mWeakPlayerBase; + private final WeakReference mWeakPlayerProxy; + private PlayerProxy mPlayerProxy; /** * Constructs a {@code VolumeShaper} from a {@link VolumeShaper.Configuration} and an @@ -64,14 +65,14 @@ public final class VolumeShaper { /* package */ VolumeShaper( @NonNull Configuration configuration, @NonNull PlayerBase playerBase) { - mPlayerBase = new WeakReference(playerBase); + mWeakPlayerBase = new WeakReference(playerBase); mPlayerProxy = null; + mWeakPlayerProxy = null; mId = applyPlayer(configuration, new Operation.Builder().defer().build()); } /** * @hide - * TODO SystemApi * Constructs a {@code VolumeShaper} from a {@link VolumeShaper.Configuration} and a * {@code PlayerProxy} object. The PlayerProxy object requires that the configuration * be set with a system VolumeShaper id (this is a reserved value). @@ -80,12 +81,20 @@ public final class VolumeShaper { * @param playerProxy */ public VolumeShaper( - @NonNull Configuration configuration, @NonNull PlayerProxy playerProxy) { + @NonNull Configuration configuration, + @NonNull PlayerProxy playerProxy, + boolean keepReference) { if (configuration.getId() < 0) { throw new IllegalArgumentException("playerProxy configuration id must be specified"); } - mPlayerProxy = new WeakReference(playerProxy); - mPlayerBase = null; + if (keepReference) { + mPlayerProxy = playerProxy; + mWeakPlayerProxy = null; + } else { + mWeakPlayerProxy = new WeakReference(playerProxy); + mPlayerProxy = null; + } + mWeakPlayerBase = null; mId = applyPlayer(configuration, new Operation.Builder().defer().build()); } @@ -143,12 +152,13 @@ public final class VolumeShaper { } catch (IllegalStateException ise) { ; // ok } - if (mPlayerBase != null) { - mPlayerBase.clear(); + if (mWeakPlayerBase != null) { + mWeakPlayerBase.clear(); } - if (mPlayerProxy != null) { - mPlayerProxy.clear(); + if (mWeakPlayerProxy != null) { + mWeakPlayerProxy.clear(); } + mPlayerProxy = null; } @Override @@ -167,11 +177,11 @@ public final class VolumeShaper { @NonNull VolumeShaper.Configuration configuration, @NonNull VolumeShaper.Operation operation) { final int id; - if (mPlayerProxy != null) { + if (mPlayerProxy != null || mWeakPlayerProxy != null) { // The PlayerProxy accepts only one way transactions so // the Configuration must have an id set to one of the system // ids (a positive value less than 16). - PlayerProxy player = mPlayerProxy.get(); + PlayerProxy player = mWeakPlayerProxy != null ? mWeakPlayerProxy.get() : mPlayerProxy; if (player == null) { throw new IllegalStateException("player deallocated"); } @@ -180,8 +190,8 @@ public final class VolumeShaper { throw new IllegalArgumentException("proxy requires configuration with id"); } player.applyVolumeShaper(configuration, operation); - } else if (mPlayerBase != null) { - PlayerBase player = mPlayerBase.get(); + } else if (mWeakPlayerBase != null) { + PlayerBase player = mWeakPlayerBase.get(); if (player == null) { throw new IllegalStateException("player deallocated"); } @@ -210,14 +220,10 @@ public final class VolumeShaper { */ private @NonNull VolumeShaper.State getStatePlayer(int id) { final VolumeShaper.State state; - if (mPlayerProxy != null) { - PlayerProxy player = mPlayerProxy.get(); - if (player == null) { - throw new IllegalStateException("player deallocated"); - } + if (mPlayerProxy != null || mWeakPlayerProxy != null) { throw new IllegalStateException("getStatePlayer not permitted through proxy"); - } else if (mPlayerBase != null) { - PlayerBase player = mPlayerBase.get(); + } else if (mWeakPlayerBase != null) { + PlayerBase player = mWeakPlayerBase.get(); if (player == null) { throw new IllegalStateException("player deallocated"); } diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java index b4feef3da291f..abdcfe78a5168 100644 --- a/services/core/java/com/android/server/audio/MediaFocusControl.java +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -437,7 +437,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer { * @param attr attributes of the sound about to start playing * @return time in ms */ - protected int getFocusRampTimeMs(int focusGain, AudioAttributes attr) { + protected static int getFocusRampTimeMs(int focusGain, AudioAttributes attr) { switch (attr.getUsage()) { case AudioAttributes.USAGE_MEDIA: case AudioAttributes.USAGE_GAME: diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index a95a627d54e6a..185c778b51971 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -25,6 +25,7 @@ import android.media.AudioSystem; import android.media.IPlaybackConfigDispatcher; import android.media.MediaRecorder; import android.media.PlayerBase; +import android.media.VolumeShaper; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; @@ -47,6 +48,7 @@ public final class PlaybackActivityMonitor public final static String TAG = "AudioService.PlaybackActivityMonitor"; private final static boolean DEBUG = false; + private final static int VOLUME_SHAPER_SYSTEM_DUCK_ID = 1; private ArrayList mClients = new ArrayList(); // a public client is one that needs an anonymized version of the playback configurations, we @@ -126,6 +128,11 @@ public final class PlaybackActivityMonitor final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid)); if (checkConfigurationCaller(piid, apc, binderUid)) { mPlayers.remove(new Integer(piid)); + final VolumeShaper vs = mDuckVolumeShapers.get(new Integer(piid)); + if (vs != null) { + vs.release(); + mDuckVolumeShapers.remove(new Integer(piid)); + } } else { Log.e(TAG, "Error releasing player " + piid); } @@ -242,6 +249,20 @@ public final class PlaybackActivityMonitor private final ArrayList mDuckedPlayers = new ArrayList(); private final ArrayList mMutedPlayers = new ArrayList(); + private final VolumeShaper.Configuration DUCK_VSHAPE = + new VolumeShaper.Configuration.Builder() + .setId(VOLUME_SHAPER_SYSTEM_DUCK_ID) + .setCurve(new float[] { 0.f, 1.f } /* times */, + new float[] { 1.f, 0.2f } /* volumes */) + .setDurationMs(MediaFocusControl.getFocusRampTimeMs( + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, + new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION) + .build())) + .build(); + + private final HashMap mDuckVolumeShapers = + new HashMap(); + @Override public boolean duckPlayers(FocusRequester winner, FocusRequester loser) { if (DEBUG) { @@ -268,11 +289,24 @@ public final class PlaybackActivityMonitor // the player is speaking, ducking will make the speech unintelligible // so let the app handle it instead return false; + } else if (apc.getPlayerType() + == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) { + // TODO support ducking of SoundPool players + return false; } else { try { if (DEBUG) { Log.v(TAG, "ducking player " + piid); } - //FIXME just a test before we have VolumeShape - apc.getPlayerProxy().setPan(-1.0f); + final VolumeShaper ducker; + if (mDuckVolumeShapers.containsKey(new Integer(piid))) { + ducker = mDuckVolumeShapers.get(new Integer(piid)); + } else { + ducker = new VolumeShaper( + DUCK_VSHAPE, + apc.getPlayerProxy(), + true /* keepReference */); + mDuckVolumeShapers.put(new Integer(piid), ducker); + } + ducker.apply(VolumeShaper.Operation.PLAY); // duck mDuckedPlayers.add(piid); } catch (Exception e) { Log.e(TAG, "Error ducking player " + piid, e); @@ -301,8 +335,10 @@ public final class PlaybackActivityMonitor try { if (DEBUG) { Log.v(TAG, "unducking player" + piid); } mDuckedPlayers.remove(new Integer(piid)); - //FIXME just a test before we have VolumeShape - apc.getPlayerProxy().setPan(0.0f); + if (mDuckVolumeShapers.containsKey(new Integer(piid))) { + final VolumeShaper ducker = mDuckVolumeShapers.get(new Integer(piid)); + ducker.apply(VolumeShaper.Operation.REVERSE); // unduck + } } catch (Exception e) { Log.e(TAG, "Error unducking player " + piid, e); }