OSDN Git Service

Full volume on remote submix for apps that need it
authorJean-Michel Trivi <jmtrivi@google.com>
Thu, 2 Oct 2014 00:49:29 +0000 (17:49 -0700)
committerJean-Michel Trivi <jmtrivi@google.com>
Tue, 7 Oct 2014 16:09:41 +0000 (09:09 -0700)
If an AudioRecord is created with the "fixedVolume" tag
  when recording from REMOTE_SUBMIX, treat the
  device DEVICE_OUT_REMOTE_SUBMIX as a fixed full volume
  device during the recording. Also register a death
  handler during the recording.
  Otherwise this is a no-op.

Bug 17635294

Change-Id: I8d26fe777047126f34308e1e1b7ac28ba269ad89

media/java/android/media/AudioRecord.java
media/java/android/media/AudioService.java
media/java/android/media/IAudioService.aidl

index 5be6371..52b4649 100644 (file)
@@ -18,10 +18,15 @@ package android.media;
 
 import java.lang.ref.WeakReference;
 import java.nio.ByteBuffer;
 
 import java.lang.ref.WeakReference;
 import java.nio.ByteBuffer;
+import java.util.Iterator;
 
 
+import android.os.Binder;
 import android.os.Handler;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.util.Log;
 
 /**
 import android.util.Log;
 
 /**
@@ -99,6 +104,8 @@ public class AudioRecord
 
     private final static String TAG = "android.media.AudioRecord";
 
 
     private final static String TAG = "android.media.AudioRecord";
 
+    /** @hide */
+    public final static String SUBMIX_FIXED_VOLUME = "fixedVolume";
 
     //---------------------------------------------------------
     // Used exclusively by native code
 
     //---------------------------------------------------------
     // Used exclusively by native code
@@ -184,6 +191,7 @@ public class AudioRecord
      * AudioAttributes
      */
     private AudioAttributes mAudioAttributes;
      * AudioAttributes
      */
     private AudioAttributes mAudioAttributes;
+    private boolean mIsSubmixFullVolume = false;
 
     //---------------------------------------------------------
     // Constructor, Finalize
 
     //---------------------------------------------------------
     // Constructor, Finalize
@@ -267,6 +275,18 @@ public class AudioRecord
 
         mAudioAttributes = attributes;
 
 
         mAudioAttributes = attributes;
 
+        // is this AudioRecord using REMOTE_SUBMIX at full volume?
+        if (mAudioAttributes.getCapturePreset() == MediaRecorder.AudioSource.REMOTE_SUBMIX) {
+            final Iterator<String> tagsIter = mAudioAttributes.getTags().iterator();
+            while (tagsIter.hasNext()) {
+                if (tagsIter.next().equalsIgnoreCase(SUBMIX_FIXED_VOLUME)) {
+                    mIsSubmixFullVolume = true;
+                    Log.v(TAG, "Will record from REMOTE_SUBMIX at full fixed volume");
+                    break;
+                }
+            }
+        }
+
         int rate = 0;
         if ((format.getPropertySetMask()
                 & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE) != 0)
         int rate = 0;
         if ((format.getPropertySetMask()
                 & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE) != 0)
@@ -419,7 +439,8 @@ public class AudioRecord
 
     @Override
     protected void finalize() {
 
     @Override
     protected void finalize() {
-        native_finalize();
+        // will cause stop() to be called, and if appropriate, will handle fixed volume recording
+        release();
     }
 
 
     }
 
 
@@ -586,6 +607,7 @@ public class AudioRecord
         // start recording
         synchronized(mRecordingStateLock) {
             if (native_start(MediaSyncEvent.SYNC_EVENT_NONE, 0) == SUCCESS) {
         // start recording
         synchronized(mRecordingStateLock) {
             if (native_start(MediaSyncEvent.SYNC_EVENT_NONE, 0) == SUCCESS) {
+                handleFullVolumeRec(true);
                 mRecordingState = RECORDSTATE_RECORDING;
             }
         }
                 mRecordingState = RECORDSTATE_RECORDING;
             }
         }
@@ -608,6 +630,7 @@ public class AudioRecord
         // start recording
         synchronized(mRecordingStateLock) {
             if (native_start(syncEvent.getType(), syncEvent.getAudioSessionId()) == SUCCESS) {
         // start recording
         synchronized(mRecordingStateLock) {
             if (native_start(syncEvent.getType(), syncEvent.getAudioSessionId()) == SUCCESS) {
+                handleFullVolumeRec(true);
                 mRecordingState = RECORDSTATE_RECORDING;
             }
         }
                 mRecordingState = RECORDSTATE_RECORDING;
             }
         }
@@ -625,11 +648,25 @@ public class AudioRecord
 
         // stop recording
         synchronized(mRecordingStateLock) {
 
         // stop recording
         synchronized(mRecordingStateLock) {
+            handleFullVolumeRec(false);
             native_stop();
             mRecordingState = RECORDSTATE_STOPPED;
         }
     }
 
             native_stop();
             mRecordingState = RECORDSTATE_STOPPED;
         }
     }
 
+    private final IBinder mICallBack = new Binder();
+    private void handleFullVolumeRec(boolean starting) {
+        if (!mIsSubmixFullVolume) {
+            return;
+        }
+        final IBinder b = ServiceManager.getService(android.content.Context.AUDIO_SERVICE);
+        final IAudioService ias = IAudioService.Stub.asInterface(b);
+        try {
+            ias.forceRemoteSubmixFullVolume(starting, mICallBack);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error talking to AudioService when handling full submix volume", e);
+        }
+    }
 
     //---------------------------------------------------------
     // Audio data supply
 
     //---------------------------------------------------------
     // Audio data supply
@@ -880,6 +917,7 @@ public class AudioRecord
             int sampleRate, int channelMask, int audioFormat,
             int buffSizeInBytes, int[] sessionId);
 
             int sampleRate, int channelMask, int audioFormat,
             int buffSizeInBytes, int[] sessionId);
 
+    // TODO remove: implementation calls directly into implementation of native_release()
     private native final void native_finalize();
 
     private native final void native_release();
     private native final void native_finalize();
 
     private native final void native_release();
index 5c2abc5..96ce2c1 100644 (file)
@@ -485,6 +485,7 @@ public class AudioService extends IAudioService.Stub {
             AudioSystem.DEVICE_OUT_HDMI_ARC |
             AudioSystem.DEVICE_OUT_SPDIF |
             AudioSystem.DEVICE_OUT_AUX_LINE;
             AudioSystem.DEVICE_OUT_HDMI_ARC |
             AudioSystem.DEVICE_OUT_SPDIF |
             AudioSystem.DEVICE_OUT_AUX_LINE;
+    int mFullVolumeDevices = 0;
 
     // TODO merge orientation and rotation
     private final boolean mMonitorOrientation;
 
     // TODO merge orientation and rotation
     private final boolean mMonitorOrientation;
@@ -727,6 +728,10 @@ public class AudioService extends IAudioService.Stub {
         }
     }
 
         }
     }
 
+    private void checkAllFixedVolumeDevices(int streamType) {
+        mStreamStates[streamType].checkFixedVolumeDevices();
+    }
+
     private void createStreamStates() {
         int numStreamTypes = AudioSystem.getNumStreamTypes();
         VolumeStreamState[] streams = mStreamStates = new VolumeStreamState[numStreamTypes];
     private void createStreamStates() {
         int numStreamTypes = AudioSystem.getNumStreamTypes();
         VolumeStreamState[] streams = mStreamStates = new VolumeStreamState[numStreamTypes];
@@ -1466,6 +1471,106 @@ public class AudioService extends IAudioService.Stub {
         return mStreamStates[streamType].isMuted();
     }
 
         return mStreamStates[streamType].isMuted();
     }
 
+    private class RmtSbmxFullVolDeathHandler implements IBinder.DeathRecipient {
+        private IBinder mICallback; // To be notified of client's death
+
+        RmtSbmxFullVolDeathHandler(IBinder cb) {
+            mICallback = cb;
+            try {
+                cb.linkToDeath(this, 0/*flags*/);
+            } catch (RemoteException e) {
+                Log.e(TAG, "can't link to death", e);
+            }
+        }
+
+        boolean isHandlerFor(IBinder cb) {
+            return mICallback.equals(cb);
+        }
+
+        void forget() {
+            try {
+                mICallback.unlinkToDeath(this, 0/*flags*/);
+            } catch (NoSuchElementException e) {
+                Log.e(TAG, "error unlinking to death", e);
+            }
+        }
+
+        public void binderDied() {
+            Log.w(TAG, "Recorder with remote submix at full volume died " + mICallback);
+            forceRemoteSubmixFullVolume(false, mICallback);
+        }
+    }
+
+    /**
+     * call must be synchronized on mRmtSbmxFullVolDeathHandlers
+     * @return true if there is a registered death handler, false otherwise */
+    private boolean discardRmtSbmxFullVolDeathHandlerFor(IBinder cb) {
+        Iterator<RmtSbmxFullVolDeathHandler> it = mRmtSbmxFullVolDeathHandlers.iterator();
+        while (it.hasNext()) {
+            final RmtSbmxFullVolDeathHandler handler = it.next();
+            if (handler.isHandlerFor(cb)) {
+                handler.forget();
+                mRmtSbmxFullVolDeathHandlers.remove(handler);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** call synchronized on mRmtSbmxFullVolDeathHandlers */
+    private boolean hasRmtSbmxFullVolDeathHandlerFor(IBinder cb) {
+        Iterator<RmtSbmxFullVolDeathHandler> it = mRmtSbmxFullVolDeathHandlers.iterator();
+        while (it.hasNext()) {
+            if (it.next().isHandlerFor(cb)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private int mRmtSbmxFullVolRefCount = 0;
+    private ArrayList<RmtSbmxFullVolDeathHandler> mRmtSbmxFullVolDeathHandlers =
+            new ArrayList<RmtSbmxFullVolDeathHandler>();
+
+    public void forceRemoteSubmixFullVolume(boolean startForcing, IBinder cb) {
+        if (cb == null) {
+            return;
+        }
+        if ((PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(
+                        android.Manifest.permission.CAPTURE_AUDIO_OUTPUT))) {
+            Log.w(TAG, "Trying to call forceRemoteSubmixFullVolume() without CAPTURE_AUDIO_OUTPUT");
+            return;
+        }
+        synchronized(mRmtSbmxFullVolDeathHandlers) {
+            boolean applyRequired = false;
+            if (startForcing) {
+                if (!hasRmtSbmxFullVolDeathHandlerFor(cb)) {
+                    mRmtSbmxFullVolDeathHandlers.add(new RmtSbmxFullVolDeathHandler(cb));
+                    if (mRmtSbmxFullVolRefCount == 0) {
+                        mFullVolumeDevices |= AudioSystem.DEVICE_OUT_REMOTE_SUBMIX;
+                        mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_REMOTE_SUBMIX;
+                        applyRequired = true;
+                    }
+                    mRmtSbmxFullVolRefCount++;
+                }
+            } else {
+                if (discardRmtSbmxFullVolDeathHandlerFor(cb) && (mRmtSbmxFullVolRefCount > 0)) {
+                    mRmtSbmxFullVolRefCount--;
+                    if (mRmtSbmxFullVolRefCount == 0) {
+                        mFullVolumeDevices &= ~AudioSystem.DEVICE_OUT_REMOTE_SUBMIX;
+                        mFixedVolumeDevices &= ~AudioSystem.DEVICE_OUT_REMOTE_SUBMIX;
+                        applyRequired = true;
+                    }
+                }
+            }
+            if (applyRequired) {
+                // Assumes only STREAM_MUSIC going through DEVICE_OUT_REMOTE_SUBMIX
+                checkAllFixedVolumeDevices(AudioSystem.STREAM_MUSIC);
+                mStreamStates[AudioSystem.STREAM_MUSIC].applyAllVolumes();
+            }
+        }
+    }
+
     /** @see AudioManager#setMasterMute(boolean, int) */
     public void setMasterMute(boolean state, int flags, String callingPackage, IBinder cb) {
         if (mUseFixedVolume) {
     /** @see AudioManager#setMasterMute(boolean, int) */
     public void setMasterMute(boolean state, int flags, String callingPackage, IBinder cb) {
         if (mUseFixedVolume) {
@@ -3243,8 +3348,8 @@ public class AudioService extends IAudioService.Stub {
             int index;
             if (isMuted()) {
                 index = 0;
             int index;
             if (isMuted()) {
                 index = 0;
-            } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
-                       mAvrcpAbsVolSupported) {
+            } else if (((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported)
+                    || ((device & mFullVolumeDevices) != 0)) {
                 index = (mIndexMax + 5)/10;
             } else {
                 index = (getIndex(device) + 5)/10;
                 index = (mIndexMax + 5)/10;
             } else {
                 index = (getIndex(device) + 5)/10;
@@ -3272,8 +3377,10 @@ public class AudioService extends IAudioService.Stub {
                     if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
                         if (isMuted()) {
                             index = 0;
                     if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
                         if (isMuted()) {
                             index = 0;
-                        } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
-                                mAvrcpAbsVolSupported) {
+                        } else if (((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
+                                mAvrcpAbsVolSupported)
+                                    || ((device & mFullVolumeDevices) != 0))
+                        {
                             index = (mIndexMax + 5)/10;
                         } else {
                             index = ((Integer)entry.getValue() + 5)/10;
                             index = (mIndexMax + 5)/10;
                         } else {
                             index = ((Integer)entry.getValue() + 5)/10;
@@ -3403,7 +3510,8 @@ public class AudioService extends IAudioService.Stub {
                         Map.Entry entry = (Map.Entry)i.next();
                         int device = ((Integer)entry.getKey()).intValue();
                         int index = ((Integer)entry.getValue()).intValue();
                         Map.Entry entry = (Map.Entry)i.next();
                         int device = ((Integer)entry.getKey()).intValue();
                         int index = ((Integer)entry.getValue()).intValue();
-                        if (((device & mFixedVolumeDevices) != 0) && index != 0) {
+                        if (((device & mFullVolumeDevices) != 0)
+                                || (((device & mFixedVolumeDevices) != 0) && index != 0)) {
                             entry.setValue(mIndexMax);
                         }
                         applyDeviceVolume(device);
                             entry.setValue(mIndexMax);
                         }
                         applyDeviceVolume(device);
index 75fc03c..1c41432 100644 (file)
@@ -56,6 +56,8 @@ interface IAudioService {
 
     boolean isStreamMute(int streamType);
 
 
     boolean isStreamMute(int streamType);
 
+    void forceRemoteSubmixFullVolume(boolean startForcing, IBinder cb);
+
     void setMasterMute(boolean state, int flags, String callingPackage, IBinder cb);
 
     boolean isMasterMute();
     void setMasterMute(boolean state, int flags, String callingPackage, IBinder cb);
 
     boolean isMasterMute();