Loading core/api/current.txt +4 −1 Original line number Diff line number Diff line Loading @@ -23413,11 +23413,14 @@ package android.media { method public void onError(@NonNull android.media.MediaSync, int, int); } public class MediaSyncEvent { public class MediaSyncEvent implements android.os.Parcelable { method public static android.media.MediaSyncEvent createEvent(int) throws java.lang.IllegalArgumentException; method public int describeContents(); method public int getAudioSessionId(); method public int getType(); method public android.media.MediaSyncEvent setAudioSessionId(int) throws java.lang.IllegalArgumentException; method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.media.MediaSyncEvent> CREATOR; field public static final int SYNC_EVENT_NONE = 0; // 0x0 field public static final int SYNC_EVENT_PRESENTATION_COMPLETE = 1; // 0x1 } core/api/system-current.txt +8 −0 Original line number Diff line number Diff line Loading @@ -5271,11 +5271,15 @@ 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 @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 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; } public final class AudioRecordingConfiguration implements android.os.Parcelable { Loading Loading @@ -5338,6 +5342,10 @@ package android.media { method public void onPreferredFeaturesChanged(@NonNull java.util.List<java.lang.String>); } public class MediaSyncEvent implements android.os.Parcelable { field public static final int SYNC_EVENT_SHARE_AUDIO_HISTORY = 100; // 0x64 } public class PlayerProxy { method public void pause(); method public void setPan(float); Loading core/jni/android_media_AudioRecord.cpp +34 −16 Original line number Diff line number Diff line Loading @@ -189,7 +189,8 @@ static jint android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject w jobject jaa, jintArray jSampleRate, jint channelMask, jint channelIndexMask, jint audioFormat, jint buffSizeInBytes, jintArray jSession, jobject jIdentity, jlong nativeRecordInJavaObj) { jobject jIdentity, jlong nativeRecordInJavaObj, jint sharedAudioHistoryMs) { //ALOGV(">> Entering android_media_AudioRecord_setup"); //ALOGV("sampleRate=%d, audioFormat=%d, channel mask=%x, buffSizeInBytes=%d " // "nativeRecordInJavaObj=0x%llX", Loading Loading @@ -288,20 +289,18 @@ static jint android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject w lpCallbackData->audioRecord_ref = env->NewGlobalRef(weak_this); lpCallbackData->busy = false; const status_t status = lpRecorder->set(paa->source, sampleRateInHertz, const status_t status = lpRecorder->set(paa->source, sampleRateInHertz, format, // word length, PCM localChanMask, frameCount, localChanMask, frameCount, recorderCallback, // callback_t lpCallbackData, // void* user 0, // notificationFrames, true, // threadCanCallJava sessionId, AudioRecord::TRANSFER_DEFAULT, flags, -1, -1, // default uid, pid paa.get()); sessionId, AudioRecord::TRANSFER_DEFAULT, flags, -1, -1, // default uid, pid paa.get(), AUDIO_PORT_HANDLE_NONE, MIC_DIRECTION_UNSPECIFIED, MIC_FIELD_DIMENSION_DEFAULT, sharedAudioHistoryMs); if (status != NO_ERROR) { ALOGE("Error creating AudioRecord instance: initialization check failed with status %d.", Loading Loading @@ -877,6 +876,23 @@ static void android_media_AudioRecord_setLogSessionId(JNIEnv *env, jobject thiz, record->setLogSessionId(logSessionId.c_str()); } static jint android_media_AudioRecord_shareAudioHistory(JNIEnv *env, jobject thiz, jstring jSharedPackageName, jlong jSharedStartMs) { sp<AudioRecord> record = getAudioRecord(env, thiz); if (record == nullptr) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioRecord pointer for setLogSessionId()"); } if (jSharedPackageName == nullptr) { jniThrowException(env, "java/lang/IllegalArgumentException", "package name cannot be null"); } ScopedUtfChars nSharedPackageName(env, jSharedPackageName); ALOGV("%s: nSharedPackageName '%s'", __func__, nSharedPackageName.c_str()); return nativeToJavaStatus(record->shareAudioHistory(nSharedPackageName.c_str(), static_cast<int64_t>(jSharedStartMs))); } // ---------------------------------------------------------------------------- static jint android_media_AudioRecord_get_port_id(JNIEnv *env, jobject thiz) { sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz); Loading @@ -896,7 +912,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/media/permission/Identity;J)I", "(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILandroid/media/permission/Identity;JI)I", (void *)android_media_AudioRecord_setup}, {"native_finalize", "()V", (void *)android_media_AudioRecord_finalize}, {"native_release", "()V", (void *)android_media_AudioRecord_release}, Loading Loading @@ -936,6 +952,8 @@ static const JNINativeMethod gMethods[] = { (void *)android_media_AudioRecord_set_preferred_microphone_field_dimension}, {"native_setLogSessionId", "(Ljava/lang/String;)V", (void *)android_media_AudioRecord_setLogSessionId}, {"native_shareAudioHistory", "(Ljava/lang/String;J)I", (void *)android_media_AudioRecord_shareAudioHistory}, }; // field names found in android/media/AudioRecord.java Loading media/java/android/media/AudioRecord.java +139 −10 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static android.media.permission.PermissionUtil.myIdentity; import android.annotation.CallbackExecutor; import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; Loading Loading @@ -358,7 +359,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int sessionId) throws IllegalArgumentException { this(attributes, format, bufferSizeInBytes, sessionId, ActivityThread.currentApplication()); this(attributes, format, bufferSizeInBytes, sessionId, ActivityThread.currentApplication(), 0 /*maxSharedAudioHistoryMs*/); } /** Loading @@ -383,7 +385,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, * @throws IllegalArgumentException */ private AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int sessionId, @Nullable Context context) throws IllegalArgumentException { int sessionId, @Nullable Context context, int maxSharedAudioHistoryMs) throws IllegalArgumentException { mRecordingState = RECORDSTATE_STOPPED; if (attributes == null) { Loading Loading @@ -455,12 +458,14 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, int[] sampleRate = new int[] {mSampleRate}; int[] session = new int[1]; session[0] = sessionId; //TODO: update native initialization when information about hardware init failure // due to capture device already open is available. int initResult = native_setup(new WeakReference<AudioRecord>(this), mAudioAttributes, sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat, mNativeBufferSizeInBytes, session, identity, 0 /*nativeRecordInJavaObj*/); session, identity, 0 /*nativeRecordInJavaObj*/, maxSharedAudioHistoryMs); if (initResult != SUCCESS) { loge("Error code "+initResult+" when initializing native AudioRecord object."); return; // with mState == STATE_UNINITIALIZED Loading Loading @@ -522,7 +527,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, 0 /*mNativeBufferSizeInBytes*/, session, myIdentity(null), nativeRecordInJavaObj); nativeRecordInJavaObj, 0); if (initResult != SUCCESS) { loge("Error code "+initResult+" when initializing native AudioRecord object."); return; // with mState == STATE_UNINITIALIZED Loading Loading @@ -581,7 +587,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, private int mBufferSizeInBytes; private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE; private int mPrivacySensitive = PRIVACY_SENSITIVE_DEFAULT; private int mMaxSharedAudioHistoryMs = 0; private static final int PRIVACY_SENSITIVE_DEFAULT = -1; private static final int PRIVACY_SENSITIVE_DISABLED = 0; private static final int PRIVACY_SENSITIVE_ENABLED = 1; Loading Loading @@ -747,7 +753,12 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, if (sessionId < 0) { throw new IllegalArgumentException("Invalid session ID " + sessionId); } // Do not override a session ID previously set with setSharedAudioEvent() if (mSessionId == AudioManager.AUDIO_SESSION_ID_GENERATE) { mSessionId = sessionId; } else { Log.e(TAG, "setSessionId() called twice or after setSharedAudioEvent()"); } return this; } Loading @@ -771,6 +782,57 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, return record; } /** * @hide * Specifies the maximum duration in the past of the this AudioRecord's capture buffer * that can be shared with another app by calling * {@link AudioRecord#shareAudioHistory(String, long)}. * @param maxSharedAudioHistoryMillis the maximum duration that will be available * in milliseconds. * @return the same Builder instance. * @throws IllegalArgumentException * */ @SystemApi @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public @NonNull Builder setMaxSharedAudioHistoryMillis(long maxSharedAudioHistoryMillis) throws IllegalArgumentException { if (maxSharedAudioHistoryMillis <= 0 || maxSharedAudioHistoryMillis > MAX_SHARED_AUDIO_HISTORY_MS) { throw new IllegalArgumentException("Illegal maxSharedAudioHistoryMillis argument"); } mMaxSharedAudioHistoryMs = (int) maxSharedAudioHistoryMillis; return this; } /** * @hide * Indicates that this AudioRecord will use the audio history shared by another app's * AudioRecord. See {@link AudioRecord#shareAudioHistory(String, long)}. * The audio session ID set with {@link AudioRecord.Builder#setSessionId(int)} will be * ignored if this method is used. * @param event The {@link MediaSyncEvent} provided by the app sharing its audio history * with this AudioRecord. * @return the same Builder instance. * @throws IllegalArgumentException */ @SystemApi public @NonNull Builder setSharedAudioEvent(@NonNull MediaSyncEvent event) throws IllegalArgumentException { Objects.requireNonNull(event); if (event.getType() != MediaSyncEvent.SYNC_EVENT_SHARE_AUDIO_HISTORY) { throw new IllegalArgumentException( "Invalid event type " + event.getType()); } if (event.getAudioSessionId() == AudioSystem.AUDIO_SESSION_ALLOCATE) { throw new IllegalArgumentException( "Invalid session ID " + event.getAudioSessionId()); } // This prevails over a session ID set with setSessionId() mSessionId = event.getAudioSessionId(); return this; } /** * @return a new {@link AudioRecord} instance successfully initialized with all * the parameters set on this <code>Builder</code>. Loading Loading @@ -837,7 +899,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, * mFormat.getBytesPerSample(mFormat.getEncoding()); } final AudioRecord record = new AudioRecord( mAttributes, mFormat, mBufferSizeInBytes, mSessionId, mContext); mAttributes, mFormat, mBufferSizeInBytes, mSessionId, mContext, mMaxSharedAudioHistoryMs); if (record.getState() == STATE_UNINITIALIZED) { // release is not necessary throw new UnsupportedOperationException("Cannot create AudioRecord"); Loading Loading @@ -1423,7 +1486,6 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, || (offsetInShorts + sizeInShorts > audioData.length)) { return ERROR_BAD_VALUE; } return native_read_in_short_array(audioData, offsetInShorts, sizeInShorts, readMode == READ_BLOCKING); } Loading Loading @@ -1642,6 +1704,70 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, return AudioManager.getDeviceForPortId(deviceId, AudioManager.GET_DEVICES_INPUTS); } /** * Must match the native definition in frameworks/av/service/audioflinger/Audioflinger.h. */ private static final long MAX_SHARED_AUDIO_HISTORY_MS = 5000; /** * @hide * returns the maximum duration in milliseconds of the audio history that can be requested * to be made available to other clients using the same session with * {@Link Builder#setMaxSharedAudioHistory(long)}. */ @SystemApi public static long getMaxSharedAudioHistoryMillis() { return MAX_SHARED_AUDIO_HISTORY_MS; } /** * @hide * * A privileged app with permission CAPTURE_AUDIO_HOTWORD can share part of its recent * capture history on a given AudioRecord with the following steps: * 1) Specify the maximum time in the past that will be available for other apps by calling * {@link Builder#setMaxSharedAudioHistoryMillis(long)} when creating the AudioRecord. * 2) Start recording and determine where the other app should start capturing in the past. * 3) Call this method with the package name of the app the history will be shared with and * the intended start time for this app's capture relative to this AudioRecord's start time. * 4) Communicate the {@link MediaSyncEvent} returned by this method to the other app. * 5) The other app will use the MediaSyncEvent when creating its AudioRecord with * {@link Builder#setSharedAudioEvent(MediaSyncEvent). * 6) Only after the other app has started capturing can this app stop capturing and * release its AudioRecord. * This method is intended to be called only once: if called multiple times, only the last * request will be honored. * The implementation is "best effort": if the specified start time if too far in the past * compared to the max available history specified, the start time will be adjusted to the * start of the available history. * @param sharedPackage the package the history will be shared with * @param startFromMillis the start time, relative to the initial start time of this * AudioRecord, at which the other AudioRecord will start. * @return a {@link MediaSyncEvent} to be communicated to the app this AudioRecord's audio * history will be shared with. * @throws IllegalArgumentException * @throws SecurityException */ @SystemApi @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) @NonNull public MediaSyncEvent shareAudioHistory(@NonNull String sharedPackage, @IntRange(from = 0) long startFromMillis) { Objects.requireNonNull(sharedPackage); if (startFromMillis < 0) { throw new IllegalArgumentException("Illegal negative sharedAudioHistoryMs argument"); } int status = native_shareAudioHistory(sharedPackage, startFromMillis); if (status == AudioSystem.BAD_VALUE) { throw new IllegalArgumentException("Illegal sharedAudioHistoryMs argument"); } else if (status == AudioSystem.PERMISSION_DENIED) { throw new SecurityException("permission CAPTURE_AUDIO_HOTWORD required"); } MediaSyncEvent event = MediaSyncEvent.createEvent(MediaSyncEvent.SYNC_EVENT_SHARE_AUDIO_HISTORY); event.setAudioSessionId(mSessionId); return event; } /* * Call BEFORE adding a routing callback handler. */ Loading Loading @@ -2105,13 +2231,14 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, identity.packageName = opPackageName; return native_setup(audiorecordThis, attributes, sampleRate, channelMask, channelIndexMask, audioFormat, buffSizeInBytes, sessionId, identity, nativeRecordInJavaObj); audioFormat, buffSizeInBytes, sessionId, identity, nativeRecordInJavaObj, 0); } private native int native_setup(Object audiorecordThis, Object /*AudioAttributes*/ attributes, int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat, int buffSizeInBytes, int[] sessionId, Identity identity, long nativeRecordInJavaObj); int buffSizeInBytes, int[] sessionId, Identity identity, long nativeRecordInJavaObj, int maxSharedAudioHistoryMs); // TODO remove: implementation calls directly into implementation of native_release() private native void native_finalize(); Loading Loading @@ -2170,6 +2297,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, private native void native_setLogSessionId(@Nullable String logSessionId); private native int native_shareAudioHistory(@NonNull String sharedPackage, long startFromMs); //--------------------------------------------------------- // Utility methods //------------------ Loading media/java/android/media/AudioSystem.java +4 −0 Original line number Diff line number Diff line Loading @@ -1464,6 +1464,10 @@ public class AudioSystem // usage for AudioRecord.startRecordingSync(), must match AudioSystem::sync_event_t /** @hide */ public static final int SYNC_EVENT_NONE = 0; /** @hide */ public static final int SYNC_EVENT_PRESENTATION_COMPLETE = 1; /** @hide * Not used by native implementation. * See {@link AudioRecord.Builder#setSharedAudioEvent(MediaSyncEvent) */ public static final int SYNC_EVENT_SHARE_AUDIO_HISTORY = 100; /** * @hide Loading Loading
core/api/current.txt +4 −1 Original line number Diff line number Diff line Loading @@ -23413,11 +23413,14 @@ package android.media { method public void onError(@NonNull android.media.MediaSync, int, int); } public class MediaSyncEvent { public class MediaSyncEvent implements android.os.Parcelable { method public static android.media.MediaSyncEvent createEvent(int) throws java.lang.IllegalArgumentException; method public int describeContents(); method public int getAudioSessionId(); method public int getType(); method public android.media.MediaSyncEvent setAudioSessionId(int) throws java.lang.IllegalArgumentException; method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.media.MediaSyncEvent> CREATOR; field public static final int SYNC_EVENT_NONE = 0; // 0x0 field public static final int SYNC_EVENT_PRESENTATION_COMPLETE = 1; // 0x1 }
core/api/system-current.txt +8 −0 Original line number Diff line number Diff line Loading @@ -5271,11 +5271,15 @@ 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 @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 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; } public final class AudioRecordingConfiguration implements android.os.Parcelable { Loading Loading @@ -5338,6 +5342,10 @@ package android.media { method public void onPreferredFeaturesChanged(@NonNull java.util.List<java.lang.String>); } public class MediaSyncEvent implements android.os.Parcelable { field public static final int SYNC_EVENT_SHARE_AUDIO_HISTORY = 100; // 0x64 } public class PlayerProxy { method public void pause(); method public void setPan(float); Loading
core/jni/android_media_AudioRecord.cpp +34 −16 Original line number Diff line number Diff line Loading @@ -189,7 +189,8 @@ static jint android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject w jobject jaa, jintArray jSampleRate, jint channelMask, jint channelIndexMask, jint audioFormat, jint buffSizeInBytes, jintArray jSession, jobject jIdentity, jlong nativeRecordInJavaObj) { jobject jIdentity, jlong nativeRecordInJavaObj, jint sharedAudioHistoryMs) { //ALOGV(">> Entering android_media_AudioRecord_setup"); //ALOGV("sampleRate=%d, audioFormat=%d, channel mask=%x, buffSizeInBytes=%d " // "nativeRecordInJavaObj=0x%llX", Loading Loading @@ -288,20 +289,18 @@ static jint android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject w lpCallbackData->audioRecord_ref = env->NewGlobalRef(weak_this); lpCallbackData->busy = false; const status_t status = lpRecorder->set(paa->source, sampleRateInHertz, const status_t status = lpRecorder->set(paa->source, sampleRateInHertz, format, // word length, PCM localChanMask, frameCount, localChanMask, frameCount, recorderCallback, // callback_t lpCallbackData, // void* user 0, // notificationFrames, true, // threadCanCallJava sessionId, AudioRecord::TRANSFER_DEFAULT, flags, -1, -1, // default uid, pid paa.get()); sessionId, AudioRecord::TRANSFER_DEFAULT, flags, -1, -1, // default uid, pid paa.get(), AUDIO_PORT_HANDLE_NONE, MIC_DIRECTION_UNSPECIFIED, MIC_FIELD_DIMENSION_DEFAULT, sharedAudioHistoryMs); if (status != NO_ERROR) { ALOGE("Error creating AudioRecord instance: initialization check failed with status %d.", Loading Loading @@ -877,6 +876,23 @@ static void android_media_AudioRecord_setLogSessionId(JNIEnv *env, jobject thiz, record->setLogSessionId(logSessionId.c_str()); } static jint android_media_AudioRecord_shareAudioHistory(JNIEnv *env, jobject thiz, jstring jSharedPackageName, jlong jSharedStartMs) { sp<AudioRecord> record = getAudioRecord(env, thiz); if (record == nullptr) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioRecord pointer for setLogSessionId()"); } if (jSharedPackageName == nullptr) { jniThrowException(env, "java/lang/IllegalArgumentException", "package name cannot be null"); } ScopedUtfChars nSharedPackageName(env, jSharedPackageName); ALOGV("%s: nSharedPackageName '%s'", __func__, nSharedPackageName.c_str()); return nativeToJavaStatus(record->shareAudioHistory(nSharedPackageName.c_str(), static_cast<int64_t>(jSharedStartMs))); } // ---------------------------------------------------------------------------- static jint android_media_AudioRecord_get_port_id(JNIEnv *env, jobject thiz) { sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz); Loading @@ -896,7 +912,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/media/permission/Identity;J)I", "(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILandroid/media/permission/Identity;JI)I", (void *)android_media_AudioRecord_setup}, {"native_finalize", "()V", (void *)android_media_AudioRecord_finalize}, {"native_release", "()V", (void *)android_media_AudioRecord_release}, Loading Loading @@ -936,6 +952,8 @@ static const JNINativeMethod gMethods[] = { (void *)android_media_AudioRecord_set_preferred_microphone_field_dimension}, {"native_setLogSessionId", "(Ljava/lang/String;)V", (void *)android_media_AudioRecord_setLogSessionId}, {"native_shareAudioHistory", "(Ljava/lang/String;J)I", (void *)android_media_AudioRecord_shareAudioHistory}, }; // field names found in android/media/AudioRecord.java Loading
media/java/android/media/AudioRecord.java +139 −10 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static android.media.permission.PermissionUtil.myIdentity; import android.annotation.CallbackExecutor; import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; Loading Loading @@ -358,7 +359,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int sessionId) throws IllegalArgumentException { this(attributes, format, bufferSizeInBytes, sessionId, ActivityThread.currentApplication()); this(attributes, format, bufferSizeInBytes, sessionId, ActivityThread.currentApplication(), 0 /*maxSharedAudioHistoryMs*/); } /** Loading @@ -383,7 +385,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, * @throws IllegalArgumentException */ private AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int sessionId, @Nullable Context context) throws IllegalArgumentException { int sessionId, @Nullable Context context, int maxSharedAudioHistoryMs) throws IllegalArgumentException { mRecordingState = RECORDSTATE_STOPPED; if (attributes == null) { Loading Loading @@ -455,12 +458,14 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, int[] sampleRate = new int[] {mSampleRate}; int[] session = new int[1]; session[0] = sessionId; //TODO: update native initialization when information about hardware init failure // due to capture device already open is available. int initResult = native_setup(new WeakReference<AudioRecord>(this), mAudioAttributes, sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat, mNativeBufferSizeInBytes, session, identity, 0 /*nativeRecordInJavaObj*/); session, identity, 0 /*nativeRecordInJavaObj*/, maxSharedAudioHistoryMs); if (initResult != SUCCESS) { loge("Error code "+initResult+" when initializing native AudioRecord object."); return; // with mState == STATE_UNINITIALIZED Loading Loading @@ -522,7 +527,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, 0 /*mNativeBufferSizeInBytes*/, session, myIdentity(null), nativeRecordInJavaObj); nativeRecordInJavaObj, 0); if (initResult != SUCCESS) { loge("Error code "+initResult+" when initializing native AudioRecord object."); return; // with mState == STATE_UNINITIALIZED Loading Loading @@ -581,7 +587,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, private int mBufferSizeInBytes; private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE; private int mPrivacySensitive = PRIVACY_SENSITIVE_DEFAULT; private int mMaxSharedAudioHistoryMs = 0; private static final int PRIVACY_SENSITIVE_DEFAULT = -1; private static final int PRIVACY_SENSITIVE_DISABLED = 0; private static final int PRIVACY_SENSITIVE_ENABLED = 1; Loading Loading @@ -747,7 +753,12 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, if (sessionId < 0) { throw new IllegalArgumentException("Invalid session ID " + sessionId); } // Do not override a session ID previously set with setSharedAudioEvent() if (mSessionId == AudioManager.AUDIO_SESSION_ID_GENERATE) { mSessionId = sessionId; } else { Log.e(TAG, "setSessionId() called twice or after setSharedAudioEvent()"); } return this; } Loading @@ -771,6 +782,57 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, return record; } /** * @hide * Specifies the maximum duration in the past of the this AudioRecord's capture buffer * that can be shared with another app by calling * {@link AudioRecord#shareAudioHistory(String, long)}. * @param maxSharedAudioHistoryMillis the maximum duration that will be available * in milliseconds. * @return the same Builder instance. * @throws IllegalArgumentException * */ @SystemApi @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public @NonNull Builder setMaxSharedAudioHistoryMillis(long maxSharedAudioHistoryMillis) throws IllegalArgumentException { if (maxSharedAudioHistoryMillis <= 0 || maxSharedAudioHistoryMillis > MAX_SHARED_AUDIO_HISTORY_MS) { throw new IllegalArgumentException("Illegal maxSharedAudioHistoryMillis argument"); } mMaxSharedAudioHistoryMs = (int) maxSharedAudioHistoryMillis; return this; } /** * @hide * Indicates that this AudioRecord will use the audio history shared by another app's * AudioRecord. See {@link AudioRecord#shareAudioHistory(String, long)}. * The audio session ID set with {@link AudioRecord.Builder#setSessionId(int)} will be * ignored if this method is used. * @param event The {@link MediaSyncEvent} provided by the app sharing its audio history * with this AudioRecord. * @return the same Builder instance. * @throws IllegalArgumentException */ @SystemApi public @NonNull Builder setSharedAudioEvent(@NonNull MediaSyncEvent event) throws IllegalArgumentException { Objects.requireNonNull(event); if (event.getType() != MediaSyncEvent.SYNC_EVENT_SHARE_AUDIO_HISTORY) { throw new IllegalArgumentException( "Invalid event type " + event.getType()); } if (event.getAudioSessionId() == AudioSystem.AUDIO_SESSION_ALLOCATE) { throw new IllegalArgumentException( "Invalid session ID " + event.getAudioSessionId()); } // This prevails over a session ID set with setSessionId() mSessionId = event.getAudioSessionId(); return this; } /** * @return a new {@link AudioRecord} instance successfully initialized with all * the parameters set on this <code>Builder</code>. Loading Loading @@ -837,7 +899,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, * mFormat.getBytesPerSample(mFormat.getEncoding()); } final AudioRecord record = new AudioRecord( mAttributes, mFormat, mBufferSizeInBytes, mSessionId, mContext); mAttributes, mFormat, mBufferSizeInBytes, mSessionId, mContext, mMaxSharedAudioHistoryMs); if (record.getState() == STATE_UNINITIALIZED) { // release is not necessary throw new UnsupportedOperationException("Cannot create AudioRecord"); Loading Loading @@ -1423,7 +1486,6 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, || (offsetInShorts + sizeInShorts > audioData.length)) { return ERROR_BAD_VALUE; } return native_read_in_short_array(audioData, offsetInShorts, sizeInShorts, readMode == READ_BLOCKING); } Loading Loading @@ -1642,6 +1704,70 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, return AudioManager.getDeviceForPortId(deviceId, AudioManager.GET_DEVICES_INPUTS); } /** * Must match the native definition in frameworks/av/service/audioflinger/Audioflinger.h. */ private static final long MAX_SHARED_AUDIO_HISTORY_MS = 5000; /** * @hide * returns the maximum duration in milliseconds of the audio history that can be requested * to be made available to other clients using the same session with * {@Link Builder#setMaxSharedAudioHistory(long)}. */ @SystemApi public static long getMaxSharedAudioHistoryMillis() { return MAX_SHARED_AUDIO_HISTORY_MS; } /** * @hide * * A privileged app with permission CAPTURE_AUDIO_HOTWORD can share part of its recent * capture history on a given AudioRecord with the following steps: * 1) Specify the maximum time in the past that will be available for other apps by calling * {@link Builder#setMaxSharedAudioHistoryMillis(long)} when creating the AudioRecord. * 2) Start recording and determine where the other app should start capturing in the past. * 3) Call this method with the package name of the app the history will be shared with and * the intended start time for this app's capture relative to this AudioRecord's start time. * 4) Communicate the {@link MediaSyncEvent} returned by this method to the other app. * 5) The other app will use the MediaSyncEvent when creating its AudioRecord with * {@link Builder#setSharedAudioEvent(MediaSyncEvent). * 6) Only after the other app has started capturing can this app stop capturing and * release its AudioRecord. * This method is intended to be called only once: if called multiple times, only the last * request will be honored. * The implementation is "best effort": if the specified start time if too far in the past * compared to the max available history specified, the start time will be adjusted to the * start of the available history. * @param sharedPackage the package the history will be shared with * @param startFromMillis the start time, relative to the initial start time of this * AudioRecord, at which the other AudioRecord will start. * @return a {@link MediaSyncEvent} to be communicated to the app this AudioRecord's audio * history will be shared with. * @throws IllegalArgumentException * @throws SecurityException */ @SystemApi @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) @NonNull public MediaSyncEvent shareAudioHistory(@NonNull String sharedPackage, @IntRange(from = 0) long startFromMillis) { Objects.requireNonNull(sharedPackage); if (startFromMillis < 0) { throw new IllegalArgumentException("Illegal negative sharedAudioHistoryMs argument"); } int status = native_shareAudioHistory(sharedPackage, startFromMillis); if (status == AudioSystem.BAD_VALUE) { throw new IllegalArgumentException("Illegal sharedAudioHistoryMs argument"); } else if (status == AudioSystem.PERMISSION_DENIED) { throw new SecurityException("permission CAPTURE_AUDIO_HOTWORD required"); } MediaSyncEvent event = MediaSyncEvent.createEvent(MediaSyncEvent.SYNC_EVENT_SHARE_AUDIO_HISTORY); event.setAudioSessionId(mSessionId); return event; } /* * Call BEFORE adding a routing callback handler. */ Loading Loading @@ -2105,13 +2231,14 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, identity.packageName = opPackageName; return native_setup(audiorecordThis, attributes, sampleRate, channelMask, channelIndexMask, audioFormat, buffSizeInBytes, sessionId, identity, nativeRecordInJavaObj); audioFormat, buffSizeInBytes, sessionId, identity, nativeRecordInJavaObj, 0); } private native int native_setup(Object audiorecordThis, Object /*AudioAttributes*/ attributes, int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat, int buffSizeInBytes, int[] sessionId, Identity identity, long nativeRecordInJavaObj); int buffSizeInBytes, int[] sessionId, Identity identity, long nativeRecordInJavaObj, int maxSharedAudioHistoryMs); // TODO remove: implementation calls directly into implementation of native_release() private native void native_finalize(); Loading Loading @@ -2170,6 +2297,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, private native void native_setLogSessionId(@Nullable String logSessionId); private native int native_shareAudioHistory(@NonNull String sharedPackage, long startFromMs); //--------------------------------------------------------- // Utility methods //------------------ Loading
media/java/android/media/AudioSystem.java +4 −0 Original line number Diff line number Diff line Loading @@ -1464,6 +1464,10 @@ public class AudioSystem // usage for AudioRecord.startRecordingSync(), must match AudioSystem::sync_event_t /** @hide */ public static final int SYNC_EVENT_NONE = 0; /** @hide */ public static final int SYNC_EVENT_PRESENTATION_COMPLETE = 1; /** @hide * Not used by native implementation. * See {@link AudioRecord.Builder#setSharedAudioEvent(MediaSyncEvent) */ public static final int SYNC_EVENT_SHARE_AUDIO_HISTORY = 100; /** * @hide Loading