Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4a886e0c authored by Jean-Michel Trivi's avatar Jean-Michel Trivi Committed by The Android Automerger
Browse files

Full volume on remote submix for apps that need it

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
parent a49bb14d
Loading
Loading
Loading
Loading
+39 −1
Original line number Diff line number Diff line
@@ -18,10 +18,15 @@ package android.media;

import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.util.Iterator;

import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;

/**
@@ -99,6 +104,8 @@ public class AudioRecord

    private final static String TAG = "android.media.AudioRecord";

    /** @hide */
    public final static String SUBMIX_FIXED_VOLUME = "fixedVolume";

    //---------------------------------------------------------
    // Used exclusively by native code
@@ -184,6 +191,7 @@ public class AudioRecord
     * AudioAttributes
     */
    private AudioAttributes mAudioAttributes;
    private boolean mIsSubmixFullVolume = false;

    //---------------------------------------------------------
    // Constructor, Finalize
@@ -267,6 +275,18 @@ public class AudioRecord

        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)
@@ -419,7 +439,8 @@ public class AudioRecord

    @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) {
                handleFullVolumeRec(true);
                mRecordingState = RECORDSTATE_RECORDING;
            }
        }
@@ -608,6 +630,7 @@ public class AudioRecord
        // start recording
        synchronized(mRecordingStateLock) {
            if (native_start(syncEvent.getType(), syncEvent.getAudioSessionId()) == SUCCESS) {
                handleFullVolumeRec(true);
                mRecordingState = RECORDSTATE_RECORDING;
            }
        }
@@ -625,11 +648,25 @@ public class AudioRecord

        // stop recording
        synchronized(mRecordingStateLock) {
            handleFullVolumeRec(false);
            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
@@ -880,6 +917,7 @@ public class AudioRecord
            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();
+113 −5
Original line number Diff line number Diff line
@@ -485,6 +485,7 @@ public class AudioService extends IAudioService.Stub {
            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;
@@ -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];
@@ -1466,6 +1471,106 @@ public class AudioService extends IAudioService.Stub {
        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) {
@@ -3243,8 +3348,8 @@ public class AudioService extends IAudioService.Stub {
            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;
@@ -3272,8 +3377,10 @@ public class AudioService extends IAudioService.Stub {
                    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;
@@ -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();
                        if (((device & mFixedVolumeDevices) != 0) && index != 0) {
                        if (((device & mFullVolumeDevices) != 0)
                                || (((device & mFixedVolumeDevices) != 0) && index != 0)) {
                            entry.setValue(mIndexMax);
                        }
                        applyDeviceVolume(device);
+2 −0
Original line number Diff line number Diff line
@@ -56,6 +56,8 @@ interface IAudioService {

    boolean isStreamMute(int streamType);

    void forceRemoteSubmixFullVolume(boolean startForcing, IBinder cb);

    void setMasterMute(boolean state, int flags, String callingPackage, IBinder cb);

    boolean isMasterMute();