Merge "Add hotword stream configuration to AudioRecord"

This commit is contained in:
Atneya Nair
2022-12-28 22:06:47 +00:00
committed by Android (Google) Code Review
3 changed files with 124 additions and 20 deletions

View File

@@ -6763,12 +6763,16 @@ package android.media {
public class AudioRecord implements android.media.AudioRecordingMonitor android.media.AudioRouting android.media.MicrophoneDirection {
ctor @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public AudioRecord(android.media.AudioAttributes, android.media.AudioFormat, int, int) throws java.lang.IllegalArgumentException;
method public static long getMaxSharedAudioHistoryMillis();
method public boolean isHotwordLookbackStream();
method public boolean isHotwordStream();
method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public android.media.MediaSyncEvent shareAudioHistory(@NonNull String, @IntRange(from=0) long);
}
public static class AudioRecord.Builder {
method public android.media.AudioRecord.Builder setAudioAttributes(@NonNull android.media.AudioAttributes) throws java.lang.IllegalArgumentException;
method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public android.media.AudioRecord.Builder setMaxSharedAudioHistoryMillis(long) throws java.lang.IllegalArgumentException;
method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public android.media.AudioRecord.Builder setRequestHotwordLookbackStream(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public android.media.AudioRecord.Builder setRequestHotwordStream(boolean);
method public android.media.AudioRecord.Builder setSessionId(int) throws java.lang.IllegalArgumentException;
method @NonNull public android.media.AudioRecord.Builder setSharedAudioEvent(@NonNull android.media.MediaSyncEvent) throws java.lang.IllegalArgumentException;
}

View File

@@ -149,7 +149,8 @@ static jint android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject w
jint channelIndexMask, jint audioFormat,
jint buffSizeInBytes, jintArray jSession,
jobject jAttributionSource, jlong nativeRecordInJavaObj,
jint sharedAudioHistoryMs) {
jint sharedAudioHistoryMs,
jint halFlags) {
//ALOGV(">> Entering android_media_AudioRecord_setup");
//ALOGV("sampleRate=%d, audioFormat=%d, channel mask=%x, buffSizeInBytes=%d "
// "nativeRecordInJavaObj=0x%llX",
@@ -239,10 +240,7 @@ static jint android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject w
}
ALOGV("AudioRecord_setup for source=%d tags=%s flags=%08x", paa->source, paa->tags, paa->flags);
audio_input_flags_t flags = AUDIO_INPUT_FLAG_NONE;
if (paa->flags & AUDIO_FLAG_HW_HOTWORD) {
flags = AUDIO_INPUT_FLAG_HW_HOTWORD;
}
const auto flags = static_cast<audio_input_flags_t>(halFlags);
// create the callback information:
// this data will be passed with every AudioRecord callback
// we use a weak reference so the AudioRecord object can be garbage collected.
@@ -831,7 +829,7 @@ static const JNINativeMethod gMethods[] = {
{"native_start", "(II)I", (void *)android_media_AudioRecord_start},
{"native_stop", "()V", (void *)android_media_AudioRecord_stop},
{"native_setup",
"(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILandroid/os/Parcel;JI)I",
"(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILandroid/os/Parcel;JII)I",
(void *)android_media_AudioRecord_setup},
{"native_finalize", "()V", (void *)android_media_AudioRecord_finalize},
{"native_release", "()V", (void *)android_media_AudioRecord_release},

View File

@@ -37,6 +37,7 @@ import android.content.AttributionSource;
import android.content.AttributionSource.ScopedParcelState;
import android.content.Context;
import android.media.MediaRecorder.Source;
import android.media.audio.common.AudioInputFlags;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioMixingRule;
import android.media.audiopolicy.AudioPolicy;
@@ -275,6 +276,11 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
* Audio session ID
*/
private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
/**
* Audio HAL Input Flags as bitfield.
*/
private int mHalInputFlags = 0;
/**
* AudioAttributes
*/
@@ -360,7 +366,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
public AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
int sessionId) throws IllegalArgumentException {
this(attributes, format, bufferSizeInBytes, sessionId,
ActivityThread.currentApplication(), 0 /*maxSharedAudioHistoryMs*/);
ActivityThread.currentApplication(),
0 /*maxSharedAudioHistoryMs*/, 0 /* halInputFlags */);
}
/**
@@ -382,14 +389,15 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
* time. See also {@link AudioManager#generateAudioSessionId()} to obtain a session ID before
* construction.
* @param context An optional context on whose behalf the recoding is performed.
*
* @param maxSharedAudioHistoryMs
* @param halInputFlags Bitfield indexed by {@link AudioInputFlags} which is passed to the HAL.
* @throws IllegalArgumentException
*/
private AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
int sessionId, @Nullable Context context,
int maxSharedAudioHistoryMs) throws IllegalArgumentException {
int maxSharedAudioHistoryMs, int halInputFlags) throws IllegalArgumentException {
mRecordingState = RECORDSTATE_STOPPED;
mHalInputFlags = halInputFlags;
if (attributes == null) {
throw new IllegalArgumentException("Illegal null AudioAttributes");
}
@@ -469,7 +477,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
int initResult = native_setup(new WeakReference<AudioRecord>(this), mAudioAttributes,
sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,
mNativeBufferSizeInBytes, session, attributionSourceState.getParcel(),
0 /*nativeRecordInJavaObj*/, maxSharedAudioHistoryMs);
0 /*nativeRecordInJavaObj*/, maxSharedAudioHistoryMs, mHalInputFlags);
if (initResult != SUCCESS) {
loge("Error code " + initResult + " when initializing native AudioRecord object.");
return; // with mState == STATE_UNINITIALIZED
@@ -535,7 +543,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
session,
attributionSourceState.getParcel(),
nativeRecordInJavaObj,
0);
0 /*maxSharedAudioHistoryMs*/,
0 /*halInputFlags*/);
}
if (initResult != SUCCESS) {
loge("Error code "+initResult+" when initializing native AudioRecord object.");
@@ -597,6 +606,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
private int mPrivacySensitive = PRIVACY_SENSITIVE_DEFAULT;
private int mMaxSharedAudioHistoryMs = 0;
private int mCallRedirectionMode = AudioManager.CALL_REDIRECT_NONE;
private boolean mIsHotwordStream = false;
private boolean mIsHotwordLookback = false;
private static final int PRIVACY_SENSITIVE_DEFAULT = -1;
private static final int PRIVACY_SENSITIVE_DISABLED = 0;
@@ -905,18 +916,74 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
return this;
}
/**
* @hide
* Set to indicate that the requested AudioRecord object should produce the same type
* of audio content that the hotword recognition model consumes. SoundTrigger hotword
* recognition will not be disrupted. The source in the set AudioAttributes and the set
* audio source will be overridden if this API is used.
* <br> Use {@link AudioManager#isHotwordStreamSupported(boolean)} to query support.
* @param hotwordContent true if AudioRecord should produce content captured from the
* hotword pipeline. false if AudioRecord should produce content captured outside
* the hotword pipeline.
* @return the same Builder instance.
**/
@SystemApi
@RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD)
public @NonNull Builder setRequestHotwordStream(boolean hotwordContent) {
mIsHotwordStream = hotwordContent;
return this;
}
/**
* @hide
* Set to indicate that the requested AudioRecord object should produce the same type
* of audio content that the hotword recognition model consumes and that the stream will
* be able to provide buffered audio content from an unspecified duration prior to stream
* open. The source in the set AudioAttributes and the set audio source will be overridden
* if this API is used.
* <br> Use {@link AudioManager#isHotwordStreamSupported(boolean)} to query support.
* <br> If this is set, {@link AudioRecord.Builder#setRequestHotwordStream(boolean)}
* must not be set, or {@link AudioRecord.Builder#build()} will throw.
* @param hotwordLookbackContent true if AudioRecord should produce content captured from
* the hotword pipeline with capture content from prior to open. false if AudioRecord
* should not capture such content.
* to stream open is requested.
* @return the same Builder instance.
**/
@SystemApi
@RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD)
public @NonNull Builder setRequestHotwordLookbackStream(boolean hotwordLookbackContent) {
mIsHotwordLookback = hotwordLookbackContent;
return this;
}
/**
* @return a new {@link AudioRecord} instance successfully initialized with all
* the parameters set on this <code>Builder</code>.
* @throws UnsupportedOperationException if the parameters set on the <code>Builder</code>
* were incompatible, or if they are not supported by the device,
* or if the device was not available.
* were incompatible, if the parameters are not supported by the device, if the caller
* does not hold the appropriate permissions, or if the device was not available.
*/
@RequiresPermission(android.Manifest.permission.RECORD_AUDIO)
public AudioRecord build() throws UnsupportedOperationException {
if (mAudioPlaybackCaptureConfiguration != null) {
return buildAudioPlaybackCaptureRecord();
}
int halInputFlags = 0;
if (mIsHotwordStream) {
if (mIsHotwordLookback) {
throw new UnsupportedOperationException(
"setRequestHotwordLookbackStream and " +
"setRequestHotwordStream used concurrently");
} else {
halInputFlags = (1 << AudioInputFlags.HOTWORD_TAP);
}
} else if (mIsHotwordLookback) {
halInputFlags = (1 << AudioInputFlags.HOTWORD_TAP) |
(1 << AudioInputFlags.HW_LOOKBACK);
}
if (mFormat == null) {
mFormat = new AudioFormat.Builder()
@@ -942,6 +1009,12 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
.build();
}
if (mIsHotwordStream || mIsHotwordLookback) {
mAttributes = new AudioAttributes.Builder(mAttributes)
.setInternalCapturePreset(MediaRecorder.AudioSource.VOICE_RECOGNITION)
.build();
}
// If mPrivacySensitive is default, the privacy flag is already set
// according to audio source in audio attributes.
if (mPrivacySensitive != PRIVACY_SENSITIVE_DEFAULT) {
@@ -980,7 +1053,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
}
final AudioRecord record = new AudioRecord(
mAttributes, mFormat, mBufferSizeInBytes, mSessionId, mContext,
mMaxSharedAudioHistoryMs);
mMaxSharedAudioHistoryMs, halInputFlags);
if (record.getState() == STATE_UNINITIALIZED) {
// release is not necessary
throw new UnsupportedOperationException("Cannot create AudioRecord");
@@ -1041,8 +1114,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
case AudioFormat.CHANNEL_IN_DEFAULT: // AudioFormat.CHANNEL_CONFIGURATION_DEFAULT
case AudioFormat.CHANNEL_IN_MONO:
case AudioFormat.CHANNEL_CONFIGURATION_MONO:
mask = AudioFormat.CHANNEL_IN_MONO;
break;
mask = AudioFormat.CHANNEL_IN_MONO;
break;
case AudioFormat.CHANNEL_IN_STEREO:
case AudioFormat.CHANNEL_CONFIGURATION_STEREO:
mask = AudioFormat.CHANNEL_IN_STEREO;
@@ -1384,6 +1457,35 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
return (mAudioAttributes.getAllFlags() & AudioAttributes.FLAG_CAPTURE_PRIVATE) != 0;
}
/**
* @hide
* Returns whether the AudioRecord object produces the same type of audio content that
* the hotword recognition model consumes.
* <br> If {@link isHotwordLookbackStream(boolean)} is true, this will return false
* <br> See {@link Builder#setRequestHotwordStream(boolean)}
* @return true if AudioRecord produces hotword content, false otherwise
**/
@SystemApi
public boolean isHotwordStream() {
return ((mHalInputFlags & (1 << AudioInputFlags.HOTWORD_TAP)) != 0 &&
(mHalInputFlags & (1 << AudioInputFlags.HW_LOOKBACK)) == 0);
}
/**
* @hide
* Returns whether the AudioRecord object produces the same type of audio content that
* the hotword recognition model consumes, and includes capture content from prior to
* stream open.
* <br> See {@link Builder#setRequestHotwordLookbackStream(boolean)}
* @return true if AudioRecord produces hotword capture content from
* prior to stream open, false otherwise
**/
@SystemApi
public boolean isHotwordLookbackStream() {
return ((mHalInputFlags & (1 << AudioInputFlags.HW_LOOKBACK)) != 0);
}
//---------------------------------------------------------
// Transport control methods
//--------------------
@@ -2346,13 +2448,13 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
Object /*AudioAttributes*/ attributes,
int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
int buffSizeInBytes, int[] sessionId, String opPackageName,
long nativeRecordInJavaObj) {
long nativeRecordInJavaObj, int halInputFlags) {
AttributionSource attributionSource = AttributionSource.myAttributionSource()
.withPackageName(opPackageName);
try (ScopedParcelState attributionSourceState = attributionSource.asScopedParcelState()) {
return native_setup(audiorecordThis, attributes, sampleRate, channelMask,
channelIndexMask, audioFormat, buffSizeInBytes, sessionId,
attributionSourceState.getParcel(), nativeRecordInJavaObj, 0);
attributionSourceState.getParcel(), nativeRecordInJavaObj, 0, halInputFlags);
}
}
@@ -2360,7 +2462,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
Object /*AudioAttributes*/ attributes,
int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
int buffSizeInBytes, int[] sessionId, @NonNull Parcel attributionSource,
long nativeRecordInJavaObj, int maxSharedAudioHistoryMs);
long nativeRecordInJavaObj, int maxSharedAudioHistoryMs, int halInputFlags);
// TODO remove: implementation calls directly into implementation of native_release()
private native void native_finalize();