diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java index a8f313b9df8a8..dc3041fb4878e 100644 --- a/media/java/android/media/AudioAttributes.java +++ b/media/java/android/media/AudioAttributes.java @@ -400,7 +400,7 @@ public final class AudioAttributes implements Parcelable { /** * Indicates that the audio may be captured by any app. * - * For privacy, the following usages can not be recorded: VOICE_COMMUNICATION*, + * For privacy, the following usages cannot be recorded: VOICE_COMMUNICATION*, * USAGE_NOTIFICATION*, USAGE_ASSISTANCE* and USAGE_ASSISTANT. * * On {@link android.os.Build.VERSION_CODES#Q}, this means only {@link #USAGE_UNKNOWN}, @@ -413,11 +413,11 @@ public final class AudioAttributes implements Parcelable { /** * Indicates that the audio may only be captured by system apps. * - * System apps can capture for many purposes like accessibility, user guidance... + * System apps can capture for many purposes like accessibility, live captions, user guidance... * but abide to the following restrictions: - * - the audio can not leave the device - * - the audio can not be passed to a third party app - * - the audio can not be recorded at a higher quality then 16kHz 16bit mono + * - the audio cannot leave the device + * - the audio cannot be passed to a third party app + * - the audio cannot be recorded at a higher quality than 16kHz 16bit mono * * See {@link Builder#setAllowedCapturePolicy}. */ diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java index 2f03d26830f2b..a82c78fad2710 100644 --- a/media/java/android/media/audiopolicy/AudioMix.java +++ b/media/java/android/media/audiopolicy/AudioMix.java @@ -91,6 +91,20 @@ public class AudioMix { */ public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1; + /** + * An audio mix behavior where the targeted audio is played unaffected but a copy is + * accessible for capture through {@link AudioRecord}. + * + * Only capture of playback is supported, not capture of capture. + * Use concurrent capture instead to capture what is captured by other apps. + * + * The captured audio is an approximation of the played audio. + * Effects and volume are not applied, and track are mixed with different delay then in the HAL. + * As a result, this API is not suitable for echo cancelling. + * @hide + */ + public static final int ROUTE_FLAG_LOOP_BACK_RENDER = ROUTE_FLAG_LOOP_BACK | ROUTE_FLAG_RENDER; + private static final int ROUTE_FLAG_SUPPORTED = ROUTE_FLAG_RENDER | ROUTE_FLAG_LOOP_BACK; // MIX_TYPE_* values to keep in sync with frameworks/av/include/media/AudioPolicy.h @@ -125,6 +139,15 @@ public class AudioMix { */ public static final int MIX_STATE_MIXING = 1; + /** Maximum sampling rate for privileged playback capture*/ + private static final int PRIVILEDGED_CAPTURE_MAX_SAMPLE_RATE = 16000; + + /** Maximum channel number for privileged playback capture*/ + private static final int PRIVILEDGED_CAPTURE_MAX_CHANNEL_NUMBER = 1; + + /** Maximum channel number for privileged playback capture*/ + private static final int PRIVILEDGED_CAPTURE_MAX_BYTES_PER_SAMPLE = 2; + /** * The current mixing state. * @return one of {@link #MIX_STATE_DISABLED}, {@link #MIX_STATE_IDLE}, @@ -140,7 +163,8 @@ public class AudioMix { return mRouteFlags; } - AudioFormat getFormat() { + /** @hide */ + public AudioFormat getFormat() { return mFormat; } @@ -182,6 +206,31 @@ public class AudioMix { return true; } + /** @return an error string if the format would not allow Privileged playbackCapture + * null otherwise + * @hide */ + public static String canBeUsedForPrivilegedCapture(AudioFormat format) { + int sampleRate = format.getSampleRate(); + if (sampleRate > PRIVILEDGED_CAPTURE_MAX_SAMPLE_RATE || sampleRate <= 0) { + return "Privileged audio capture sample rate " + sampleRate + + " can not be over " + PRIVILEDGED_CAPTURE_MAX_SAMPLE_RATE + "kHz"; + } + int channelCount = format.getChannelCount(); + if (channelCount > PRIVILEDGED_CAPTURE_MAX_CHANNEL_NUMBER || channelCount <= 0) { + return "Privileged audio capture channel count " + channelCount + " can not be over " + + PRIVILEDGED_CAPTURE_MAX_CHANNEL_NUMBER; + } + int encoding = format.getEncoding(); + if (!format.isPublicEncoding(encoding) || !format.isEncodingLinearPcm(encoding)) { + return "Privileged audio capture encoding " + encoding + "is not linear"; + } + if (format.getBytesPerSample(encoding) > PRIVILEDGED_CAPTURE_MAX_BYTES_PER_SAMPLE) { + return "Privileged audio capture encoding " + encoding + " can not be over " + + PRIVILEDGED_CAPTURE_MAX_BYTES_PER_SAMPLE + " bytes per sample"; + } + return null; + } + /** @hide */ @Override public boolean equals(Object o) { @@ -390,6 +439,12 @@ public class AudioMix { } } } + if (mRule.allowPrivilegedPlaybackCapture()) { + String error = AudioMix.canBeUsedForPrivilegedCapture(mFormat); + if (error != null) { + throw new IllegalArgumentException(error); + } + } return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags, mDeviceSystemType, mDeviceAddress); } diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java index ed2fdae71fb0e..c4afd95be9ac6 100644 --- a/media/java/android/media/audiopolicy/AudioMixingRule.java +++ b/media/java/android/media/audiopolicy/AudioMixingRule.java @@ -365,6 +365,10 @@ public class AudioMixingRule { /** * Set if the audio of app that opted out of audio playback capture should be captured. * + * Caller of this method with true, MUST abide to the restriction listed in + * {@link ALLOW_CAPTURE_BY_SYSTEM}, including but not limited to the captured audio + * can not leave the capturing app, and the quality is limited to 16k mono. + * * The permission {@link CAPTURE_AUDIO_OUTPUT} or {@link CAPTURE_MEDIA_OUTPUT} is needed * to ignore the opt-out. * diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 2631c23da8e1e..86832f444ff0b 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -6761,6 +6761,12 @@ public class AudioService extends IAudioService.Stub if (mix.getRule().allowPrivilegedPlaybackCapture()) { // then it must have CAPTURE_MEDIA_OUTPUT or CAPTURE_AUDIO_OUTPUT permission requireCaptureAudioOrMediaOutputPerm |= true; + // and its format must be low quality enough + String error = mix.canBeUsedForPrivilegedCapture(mix.getFormat()); + if (error != null) { + Log.e(TAG, error); + return false; + } } // If mix is RENDER|LOOPBACK, then an audio MediaProjection is enough