diff --git a/api/current.txt b/api/current.txt index 2b34f4f0dab79..15166b09f2944 100644 --- a/api/current.txt +++ b/api/current.txt @@ -23455,10 +23455,14 @@ package android.media { public final class AudioRecordingConfiguration implements android.os.Parcelable { method public int describeContents(); method public android.media.AudioDeviceInfo getAudioDevice(); + method public int getAudioSource(); method public int getClientAudioSessionId(); method public int getClientAudioSource(); + method public java.util.List getClientEffects(); method public android.media.AudioFormat getClientFormat(); + method public java.util.List getEffects(); method public android.media.AudioFormat getFormat(); + method public boolean isClientSilenced(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } diff --git a/api/test-current.txt b/api/test-current.txt index 46e7683c3cb7c..40c81663a8971 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -639,6 +639,11 @@ package android.media { method public static boolean isEncodingLinearPcm(int); } + public final class AudioRecordingConfiguration implements android.os.Parcelable { + ctor public AudioRecordingConfiguration(int, int, int, android.media.AudioFormat, android.media.AudioFormat, int, java.lang.String, int, boolean, int, android.media.audiofx.AudioEffect.Descriptor[], android.media.audiofx.AudioEffect.Descriptor[]); + ctor public AudioRecordingConfiguration(int, int, int, android.media.AudioFormat, android.media.AudioFormat, int, java.lang.String); + } + public final class BufferingParams implements android.os.Parcelable { method public int describeContents(); method public int getInitialMarkMs(); diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 283eb035c6f7e..29d8f30543e6a 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -32,6 +32,7 @@ #include #include #include +#include "android_media_AudioEffectDescriptor.h" #include "android_media_AudioFormat.h" #include "android_media_AudioErrors.h" #include "android_media_MicrophoneInfo.h" @@ -427,9 +428,14 @@ android_media_AudioSystem_dyn_policy_callback(int event, String8 regId, int val) } static void -android_media_AudioSystem_recording_callback(int event, const record_client_info_t *clientInfo, - const audio_config_base_t *clientConfig, const audio_config_base_t *deviceConfig, - audio_patch_handle_t patchHandle) +android_media_AudioSystem_recording_callback(int event, + const record_client_info_t *clientInfo, + const audio_config_base_t *clientConfig, + std::vector clientEffects, + const audio_config_base_t *deviceConfig, + std::vector effects __unused, + audio_patch_handle_t patchHandle, + audio_source_t source) { JNIEnv *env = AndroidRuntime::getJNIEnv(); if (env == NULL) { @@ -460,14 +466,24 @@ android_media_AudioSystem_recording_callback(int event, const record_client_info recParamData[6] = (jint) patchHandle; env->SetIntArrayRegion(recParamArray, 0, REC_PARAM_SIZE, recParamData); + jobjectArray jClientEffects; + convertAudioEffectDescriptorVectorFromNative(env, &jClientEffects, clientEffects); + + jobjectArray jEffects; + convertAudioEffectDescriptorVectorFromNative(env, &jEffects, effects); + // callback into java jclass clazz = env->FindClass(kClassPathName); - env->CallStaticVoidMethod(clazz, - gAudioPolicyEventHandlerMethods.postRecordConfigEventFromNative, - event, (jint) clientInfo->uid, clientInfo->session, clientInfo->source, recParamArray); - env->DeleteLocalRef(clazz); + env->CallStaticVoidMethod(clazz, + gAudioPolicyEventHandlerMethods.postRecordConfigEventFromNative, + event, (jint) clientInfo->uid, clientInfo->session, + clientInfo->source, clientInfo->port_id, clientInfo->silenced, + recParamArray, jClientEffects, jEffects, source); + env->DeleteLocalRef(clazz); env->DeleteLocalRef(recParamArray); + env->DeleteLocalRef(jClientEffects); + env->DeleteLocalRef(jEffects); } static jint @@ -2260,7 +2276,7 @@ int register_android_media_AudioSystem(JNIEnv *env) "dynamicPolicyCallbackFromNative", "(ILjava/lang/String;I)V"); gAudioPolicyEventHandlerMethods.postRecordConfigEventFromNative = GetStaticMethodIDOrDie(env, env->FindClass(kClassPathName), - "recordingCallbackFromNative", "(IIII[I)V"); + "recordingCallbackFromNative", "(IIIIIZ[I[Landroid/media/audiofx/AudioEffect$Descriptor;[Landroid/media/audiofx/AudioEffect$Descriptor;I)V"); jclass audioMixClass = FindClassOrDie(env, "android/media/audiopolicy/AudioMix"); gAudioMixClass = MakeGlobalRefOrDie(env, audioMixClass); diff --git a/media/java/android/media/AudioRecordingConfiguration.java b/media/java/android/media/AudioRecordingConfiguration.java index 9ada216d5e95d..de76aeff82c4b 100644 --- a/media/java/android/media/AudioRecordingConfiguration.java +++ b/media/java/android/media/AudioRecordingConfiguration.java @@ -18,7 +18,9 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; +import android.media.audiofx.AudioEffect; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; @@ -27,6 +29,8 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.Objects; /** @@ -48,7 +52,7 @@ import java.util.Objects; public final class AudioRecordingConfiguration implements Parcelable { private final static String TAG = new String("AudioRecordingConfiguration"); - private final int mSessionId; + private final int mClientSessionId; private final int mClientSource; @@ -60,18 +64,50 @@ public final class AudioRecordingConfiguration implements Parcelable { private final int mPatchHandle; + private final int mClientPortId; + + private boolean mClientSilenced; + + private final int mDeviceSource; + + private final AudioEffect.Descriptor[] mClientEffects; + + private final AudioEffect.Descriptor[] mDeviceEffects; + /** * @hide */ + @TestApi public AudioRecordingConfiguration(int uid, int session, int source, AudioFormat clientFormat, - AudioFormat devFormat, int patchHandle, String packageName) { + AudioFormat devFormat, int patchHandle, String packageName, int clientPortId, + boolean clientSilenced, int deviceSource, + AudioEffect.Descriptor[] clientEffects, AudioEffect.Descriptor[] deviceEffects) { mClientUid = uid; - mSessionId = session; + mClientSessionId = session; mClientSource = source; mClientFormat = clientFormat; mDeviceFormat = devFormat; mPatchHandle = patchHandle; mClientPackageName = packageName; + mClientPortId = clientPortId; + mClientSilenced = clientSilenced; + mDeviceSource = deviceSource; + mClientEffects = clientEffects; + mDeviceEffects = deviceEffects; + } + + /** + * @hide + */ + @TestApi + public AudioRecordingConfiguration(int uid, int session, int source, + AudioFormat clientFormat, AudioFormat devFormat, + int patchHandle, String packageName) { + this(uid, session, source, clientFormat, + devFormat, patchHandle, packageName, 0 /*clientPortId*/, + false /*clientSilenced*/, MediaRecorder.AudioSource.DEFAULT /*deviceSource*/, + new AudioEffect.Descriptor[0] /*clientEffects*/, + new AudioEffect.Descriptor[0] /*deviceEffects*/); } /** @@ -87,13 +123,26 @@ public final class AudioRecordingConfiguration implements Parcelable { * @hide */ public static String toLogFriendlyString(AudioRecordingConfiguration arc) { - return new String("session:" + arc.mSessionId - + " -- source:" + MediaRecorder.toLogFriendlyAudioSource(arc.mClientSource) + String clientEffects = new String(); + for (AudioEffect.Descriptor desc : arc.mClientEffects) { + clientEffects += "'" + desc.name + "' "; + } + String deviceEffects = new String(); + for (AudioEffect.Descriptor desc : arc.mDeviceEffects) { + deviceEffects += "'" + desc.name + "' "; + } + + return new String("session:" + arc.mClientSessionId + + " -- source client=" + MediaRecorder.toLogFriendlyAudioSource(arc.mClientSource) + + ", dev=" + arc.mDeviceFormat.toLogFriendlyString() + " -- uid:" + arc.mClientUid + " -- patch:" + arc.mPatchHandle + " -- pack:" + arc.mClientPackageName + " -- format client=" + arc.mClientFormat.toLogFriendlyString() - + ", dev=" + arc.mDeviceFormat.toLogFriendlyString()); + + ", dev=" + arc.mDeviceFormat.toLogFriendlyString() + + " -- silenced:" + arc.mClientSilenced + + " -- effects client=" + clientEffects + + ", dev=" + deviceEffects); } // Note that this method is called server side, so no "privileged" information is ever sent @@ -106,8 +155,10 @@ public final class AudioRecordingConfiguration implements Parcelable { */ public static AudioRecordingConfiguration anonymizedCopy(AudioRecordingConfiguration in) { return new AudioRecordingConfiguration( /*anonymized uid*/ -1, - in.mSessionId, in.mClientSource, in.mClientFormat, - in.mDeviceFormat, in.mPatchHandle, "" /*empty package name*/); + in.mClientSessionId, in.mClientSource, in.mClientFormat, + in.mDeviceFormat, in.mPatchHandle, "" /*empty package name*/, + in.mClientPortId, in.mClientSilenced, in.mDeviceSource, in.mClientEffects, + in.mDeviceEffects); } // matches the sources that return false in MediaRecorder.isSystemOnlyAudioSource(source) @@ -129,16 +180,8 @@ public final class AudioRecordingConfiguration implements Parcelable { // documented return values match the sources that return false // in MediaRecorder.isSystemOnlyAudioSource(source) /** - * Returns the audio source being used for the recording. - * @return one of {@link MediaRecorder.AudioSource#DEFAULT}, - * {@link MediaRecorder.AudioSource#MIC}, - * {@link MediaRecorder.AudioSource#VOICE_UPLINK}, - * {@link MediaRecorder.AudioSource#VOICE_DOWNLINK}, - * {@link MediaRecorder.AudioSource#VOICE_CALL}, - * {@link MediaRecorder.AudioSource#CAMCORDER}, - * {@link MediaRecorder.AudioSource#VOICE_RECOGNITION}, - * {@link MediaRecorder.AudioSource#VOICE_COMMUNICATION}, - * {@link MediaRecorder.AudioSource#UNPROCESSED}. + * Returns the audio source selected by the client. + * @return the audio source selected by the client. */ public @AudioSource int getClientAudioSource() { return mClientSource; } @@ -146,7 +189,9 @@ public final class AudioRecordingConfiguration implements Parcelable { * Returns the session number of the recording, see {@link AudioRecord#getAudioSessionId()}. * @return the session number. */ - public int getClientAudioSessionId() { return mSessionId; } + public int getClientAudioSessionId() { + return mClientSessionId; + } /** * Returns the audio format at which audio is recorded on this Android device. @@ -223,6 +268,54 @@ public final class AudioRecordingConfiguration implements Parcelable { return null; } + /** + * Returns the system unique ID assigned for the AudioRecord object corresponding to this + * AudioRecordingConfiguration client. + * @return the port ID. + */ + int getClientPortId() { + return mClientPortId; + } + + /** + * Returns true if the audio returned to the client is currently being silenced by the + * audio framework due to concurrent capture policy (e.g the capturing application does not have + * an active foreground process or service anymore). + * @return true if captured audio is silenced, false otherwise . + */ + public boolean isClientSilenced() { + return mClientSilenced; + } + + /** + * Returns the audio source currently used to configure the capture path. It can be different + * from the source returned by {@link #getClientAudioSource()} if another capture is active. + * @return the audio source active on the capture path. + */ + public @AudioSource int getAudioSource() { + return mDeviceSource; + } + + /** + * Returns the list of {@link AudioEffect.Descriptor} for all effects currently enabled on + * the audio capture client (e.g. {@link AudioRecord} or {@link MediaRecorder}). + * @return List of {@link AudioEffect.Descriptor} containing all effects enabled for the client. + */ + public @NonNull List getClientEffects() { + return new ArrayList(Arrays.asList(mClientEffects)); + } + + /** + * Returns the list of {@link AudioEffect.Descriptor} for all effects currently enabled on + * the capture stream. + * @return List of {@link AudioEffect.Descriptor} containing all effects enabled on the + * capture stream. This can be different from the list returned by {@link #getClientEffects()} + * if another capture is active. + */ + public @NonNull List getEffects() { + return new ArrayList(Arrays.asList(mDeviceEffects)); + } + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { /** @@ -240,7 +333,7 @@ public final class AudioRecordingConfiguration implements Parcelable { @Override public int hashCode() { - return Objects.hash(mSessionId, mClientSource); + return Objects.hash(mClientSessionId, mClientSource); } @Override @@ -250,23 +343,45 @@ public final class AudioRecordingConfiguration implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mSessionId); + dest.writeInt(mClientSessionId); dest.writeInt(mClientSource); mClientFormat.writeToParcel(dest, 0); mDeviceFormat.writeToParcel(dest, 0); dest.writeInt(mPatchHandle); dest.writeString(mClientPackageName); dest.writeInt(mClientUid); + dest.writeInt(mClientPortId); + dest.writeBoolean(mClientSilenced); + dest.writeInt(mDeviceSource); + dest.writeInt(mClientEffects.length); + for (int i = 0; i < mClientEffects.length; i++) { + mClientEffects[i].writeToParcel(dest, 0); + } + dest.writeInt(mDeviceEffects.length); + for (int i = 0; i < mDeviceEffects.length; i++) { + mDeviceEffects[i].writeToParcel(dest, 0); + } } private AudioRecordingConfiguration(Parcel in) { - mSessionId = in.readInt(); + mClientSessionId = in.readInt(); mClientSource = in.readInt(); mClientFormat = AudioFormat.CREATOR.createFromParcel(in); mDeviceFormat = AudioFormat.CREATOR.createFromParcel(in); mPatchHandle = in.readInt(); mClientPackageName = in.readString(); mClientUid = in.readInt(); + mClientPortId = in.readInt(); + mClientSilenced = in.readBoolean(); + mDeviceSource = in.readInt(); + mClientEffects = AudioEffect.Descriptor.CREATOR.newArray(in.readInt()); + for (int i = 0; i < mClientEffects.length; i++) { + mClientEffects[i] = AudioEffect.Descriptor.CREATOR.createFromParcel(in); + } + mDeviceEffects = AudioEffect.Descriptor.CREATOR.newArray(in.readInt()); + for (int i = 0; i < mClientEffects.length; i++) { + mDeviceEffects[i] = AudioEffect.Descriptor.CREATOR.createFromParcel(in); + } } @Override @@ -277,11 +392,16 @@ public final class AudioRecordingConfiguration implements Parcelable { AudioRecordingConfiguration that = (AudioRecordingConfiguration) o; return ((mClientUid == that.mClientUid) - && (mSessionId == that.mSessionId) + && (mClientSessionId == that.mClientSessionId) && (mClientSource == that.mClientSource) && (mPatchHandle == that.mPatchHandle) && (mClientFormat.equals(that.mClientFormat)) && (mDeviceFormat.equals(that.mDeviceFormat)) - && (mClientPackageName.equals(that.mClientPackageName))); + && (mClientPackageName.equals(that.mClientPackageName)) + && (mClientPortId == that.mClientPortId) + && (mClientSilenced == that.mClientSilenced) + && (mDeviceSource == that.mDeviceSource) + && (mClientEffects.equals(that.mClientEffects)) + && (mDeviceEffects.equals(that.mDeviceEffects))); } } diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 36f635a8e5728..58fc1ab1c4ddc 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.PackageManager; +import android.media.audiofx.AudioEffect; import android.media.audiopolicy.AudioMix; import android.os.Build; import android.util.Log; @@ -334,7 +335,9 @@ public class AudioSystem * @param packName package name of the client app performing the recording. NOT SUPPORTED */ void onRecordingConfigurationChanged(int event, int uid, int session, int source, - int[] recordingFormat, String packName); + int portId, boolean silenced, int[] recordingFormat, + AudioEffect.Descriptor[] clienteffects, AudioEffect.Descriptor[] effects, + int activeSource, String packName); } private static AudioRecordingCallback sRecordingCallback; @@ -352,19 +355,27 @@ public class AudioSystem * @param session * @param source * @param recordingFormat see - * {@link AudioRecordingCallback#onRecordingConfigurationChanged(int, int, int, int, int[])} + * {@link AudioRecordingCallback#onRecordingConfigurationChanged(int, int, int, int, int,\ + boolean, int[], AudioEffect.Descriptor[], AudioEffect.Descriptor[], int, String)} * for the description of the record format. */ @UnsupportedAppUsage private static void recordingCallbackFromNative(int event, int uid, int session, int source, - int[] recordingFormat) { + int portId, boolean silenced, int[] recordingFormat, + AudioEffect.Descriptor[] clientEffects, AudioEffect.Descriptor[] effects, + int activeSource) { AudioRecordingCallback cb = null; synchronized (AudioSystem.class) { cb = sRecordingCallback; } + + String clientEffectName = clientEffects.length == 0 ? "None" : clientEffects[0].name; + String effectName = effects.length == 0 ? "None" : effects[0].name; + if (cb != null) { // TODO receive package name from native - cb.onRecordingConfigurationChanged(event, uid, session, source, recordingFormat, ""); + cb.onRecordingConfigurationChanged(event, uid, session, source, portId, silenced, + recordingFormat, clientEffects, effects, activeSource, ""); } } diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java index 2feea41807f76..905f826939807 100644 --- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java +++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java @@ -20,11 +20,11 @@ import android.content.Context; import android.content.pm.PackageManager; import android.media.AudioFormat; import android.media.AudioManager; -import android.media.AudioPlaybackConfiguration; import android.media.AudioRecordingConfiguration; import android.media.AudioSystem; import android.media.IRecordingConfigDispatcher; import android.media.MediaRecorder; +import android.media.audiofx.AudioEffect; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; @@ -64,12 +64,19 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin * Implementation of android.media.AudioSystem.AudioRecordingCallback */ public void onRecordingConfigurationChanged(int event, int uid, int session, int source, - int[] recordingInfo, String packName) { + int portId, boolean silenced, int[] recordingInfo, + AudioEffect.Descriptor[] clientEffects, + AudioEffect.Descriptor[] effects, + int activeSource, String packName) { if (MediaRecorder.isSystemOnlyAudioSource(source)) { return; } + String clientEffectName = clientEffects.length == 0 ? "None" : clientEffects[0].name; + String effectName = effects.length == 0 ? "None" : effects[0].name; + final List configsSystem = - updateSnapshot(event, uid, session, source, recordingInfo); + updateSnapshot(event, uid, session, source, recordingInfo, + portId, silenced, activeSource, clientEffects, effects); if (configsSystem != null){ synchronized (mClients) { // list of recording configurations for "public consumption". It is only computed if @@ -179,13 +186,20 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin * @param session * @param source * @param recordingFormat see - * {@link AudioSystem.AudioRecordingCallback#onRecordingConfigurationChanged(int, int, int, int[])} + * {@link AudioSystem.AudioRecordingCallback#onRecordingConfigurationChanged(int, int, int,\ + int, int, boolean, int[], AudioEffect.Descriptor[], AudioEffect.Descriptor[], int, String)} * for the definition of the contents of the array + * @param portId + * @param silenced + * @param activeSource + * @param clientEffects + * @param effects * @return null if the list of active recording sessions has not been modified, a list * with the current active configurations otherwise. */ private List updateSnapshot(int event, int uid, int session, - int source, int[] recordingInfo) { + int source, int[] recordingInfo, int portId, boolean silenced, int activeSource, + AudioEffect.Descriptor[] clientEffects, AudioEffect.Descriptor[] effects) { final boolean configChanged; final ArrayList configs; synchronized(mRecordConfigs) { @@ -211,7 +225,7 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin .setSampleRate(recordingInfo[5]) .build(); final int patchHandle = recordingInfo[6]; - final Integer sessionKey = new Integer(session); + final Integer portIdKey = new Integer(portId); final String[] packages = mPackMan.getPackagesForUid(uid); final String packageName; @@ -222,19 +236,20 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin } final AudioRecordingConfiguration updatedConfig = new AudioRecordingConfiguration(uid, session, source, - clientFormat, deviceFormat, patchHandle, packageName); + clientFormat, deviceFormat, patchHandle, packageName, + portId, silenced, activeSource, clientEffects, effects); - if (mRecordConfigs.containsKey(sessionKey)) { - if (updatedConfig.equals(mRecordConfigs.get(sessionKey))) { + if (mRecordConfigs.containsKey(portIdKey)) { + if (updatedConfig.equals(mRecordConfigs.get(portIdKey))) { configChanged = false; } else { // config exists but has been modified - mRecordConfigs.remove(sessionKey); - mRecordConfigs.put(sessionKey, updatedConfig); + mRecordConfigs.remove(portIdKey); + mRecordConfigs.put(portIdKey, updatedConfig); configChanged = true; } } else { - mRecordConfigs.put(sessionKey, updatedConfig); + mRecordConfigs.put(portIdKey, updatedConfig); configChanged = true; } if (configChanged) {