AudioRecordingConfiguration: report more info about active use case

Report more information in onRecordingConfigChanged() callback and
AudioRecordingConfiguration:
    - For client:
      - Port ID (package private)
      - Enabled effects
      - Silenced by policy
    - For stream:
      - Active effects
      - Active audio source

Bug: 111438757
Test: CTS tests for AudioRecordingConfiguration
Change-Id: I84952614ee5d9ede23afd5836c68da7a20e79a2e
This commit is contained in:
Eric Laurent
2018-11-30 12:16:52 -08:00
parent d692ddf66d
commit c7a0cdfe1b
6 changed files with 219 additions and 48 deletions

View File

@@ -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<android.media.audiofx.AudioEffect.Descriptor> getClientEffects();
method public android.media.AudioFormat getClientFormat();
method public java.util.List<android.media.audiofx.AudioEffect.Descriptor> 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<android.media.AudioRecordingConfiguration> CREATOR;
}

View File

@@ -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();

View File

@@ -32,6 +32,7 @@
#include <nativehelper/ScopedLocalRef.h>
#include <system/audio.h>
#include <system/audio_policy.h>
#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<effect_descriptor_t> clientEffects,
const audio_config_base_t *deviceConfig,
std::vector<effect_descriptor_t> 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);

View File

@@ -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<AudioEffect.Descriptor> getClientEffects() {
return new ArrayList<AudioEffect.Descriptor>(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<AudioEffect.Descriptor> getEffects() {
return new ArrayList<AudioEffect.Descriptor>(Arrays.asList(mDeviceEffects));
}
public static final Parcelable.Creator<AudioRecordingConfiguration> CREATOR
= new Parcelable.Creator<AudioRecordingConfiguration>() {
/**
@@ -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)));
}
}

View File

@@ -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, "");
}
}

View File

@@ -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<AudioRecordingConfiguration> 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<AudioRecordingConfiguration> 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<AudioRecordingConfiguration> 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) {