Loading media/java/android/media/AudioRecord.java +39 −1 Original line number Diff line number Diff line Loading @@ -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; /** Loading Loading @@ -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 Loading Loading @@ -184,6 +191,7 @@ public class AudioRecord * AudioAttributes */ private AudioAttributes mAudioAttributes; private boolean mIsSubmixFullVolume = false; //--------------------------------------------------------- // Constructor, Finalize Loading Loading @@ -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) Loading Loading @@ -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(); } Loading Loading @@ -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; } } Loading @@ -608,6 +630,7 @@ public class AudioRecord // start recording synchronized(mRecordingStateLock) { if (native_start(syncEvent.getType(), syncEvent.getAudioSessionId()) == SUCCESS) { handleFullVolumeRec(true); mRecordingState = RECORDSTATE_RECORDING; } } Loading @@ -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 Loading Loading @@ -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(); Loading media/java/android/media/AudioService.java +113 −5 Original line number Diff line number Diff line Loading @@ -486,6 +486,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; Loading Loading @@ -734,6 +735,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]; Loading Loading @@ -1492,6 +1497,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) { Loading Loading @@ -3269,8 +3374,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; Loading Loading @@ -3298,8 +3403,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; Loading Loading @@ -3429,7 +3536,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); Loading media/java/android/media/IAudioService.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -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(); Loading Loading
media/java/android/media/AudioRecord.java +39 −1 Original line number Diff line number Diff line Loading @@ -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; /** Loading Loading @@ -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 Loading Loading @@ -184,6 +191,7 @@ public class AudioRecord * AudioAttributes */ private AudioAttributes mAudioAttributes; private boolean mIsSubmixFullVolume = false; //--------------------------------------------------------- // Constructor, Finalize Loading Loading @@ -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) Loading Loading @@ -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(); } Loading Loading @@ -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; } } Loading @@ -608,6 +630,7 @@ public class AudioRecord // start recording synchronized(mRecordingStateLock) { if (native_start(syncEvent.getType(), syncEvent.getAudioSessionId()) == SUCCESS) { handleFullVolumeRec(true); mRecordingState = RECORDSTATE_RECORDING; } } Loading @@ -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 Loading Loading @@ -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(); Loading
media/java/android/media/AudioService.java +113 −5 Original line number Diff line number Diff line Loading @@ -486,6 +486,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; Loading Loading @@ -734,6 +735,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]; Loading Loading @@ -1492,6 +1497,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) { Loading Loading @@ -3269,8 +3374,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; Loading Loading @@ -3298,8 +3403,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; Loading Loading @@ -3429,7 +3536,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); Loading
media/java/android/media/IAudioService.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -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(); Loading