OSDN Git Service

Audio focus enforcement: use VolumeShaper for ducking
authorJean-Michel Trivi <jmtrivi@google.com>
Wed, 8 Feb 2017 00:02:33 +0000 (16:02 -0800)
committerJean-Michel Trivi <jmtrivi@google.com>
Fri, 17 Feb 2017 18:08:53 +0000 (10:08 -0800)
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
services/core/java/com/android/server/audio/MediaFocusControl.java
services/core/java/com/android/server/audio/PlaybackActivityMonitor.java

index 77af359..4a2c4d8 100644 (file)
@@ -37,8 +37,9 @@ import java.util.Objects;
 public final class VolumeShaper {
     /* member variables */
     private int mId;
-    private final WeakReference<PlayerBase> mPlayerBase;
-    private final WeakReference<PlayerProxy> mPlayerProxy;
+    private final WeakReference<PlayerBase> mWeakPlayerBase;
+    private final WeakReference<PlayerProxy> 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>(playerBase);
+        mWeakPlayerBase = new WeakReference<PlayerBase>(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>(playerProxy);
-        mPlayerBase = null;
+        if (keepReference) {
+            mPlayerProxy = playerProxy;
+            mWeakPlayerProxy = null;
+        } else {
+            mWeakPlayerProxy = new WeakReference<PlayerProxy>(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");
             }
index b4feef3..abdcfe7 100644 (file)
@@ -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:
index a95a627..185c778 100644 (file)
@@ -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<PlayMonitorClient> mClients = new ArrayList<PlayMonitorClient>();
     // 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<Integer> mDuckedPlayers = new ArrayList<Integer>();
     private final ArrayList<Integer> mMutedPlayers = new ArrayList<Integer>();
 
+    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<Integer, VolumeShaper> mDuckVolumeShapers =
+            new HashMap<Integer, VolumeShaper>();
+
     @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);
                     }