Merge "AudioRecord: Allow to share capture history." into sc-dev
This commit is contained in:
@@ -23413,11 +23413,14 @@ package android.media {
|
|||||||
method public void onError(@NonNull android.media.MediaSync, int, int);
|
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 static android.media.MediaSyncEvent createEvent(int) throws java.lang.IllegalArgumentException;
|
||||||
|
method public int describeContents();
|
||||||
method public int getAudioSessionId();
|
method public int getAudioSessionId();
|
||||||
method public int getType();
|
method public int getType();
|
||||||
method public android.media.MediaSyncEvent setAudioSessionId(int) throws java.lang.IllegalArgumentException;
|
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_NONE = 0; // 0x0
|
||||||
field public static final int SYNC_EVENT_PRESENTATION_COMPLETE = 1; // 0x1
|
field public static final int SYNC_EVENT_PRESENTATION_COMPLETE = 1; // 0x1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5271,11 +5271,15 @@ package android.media {
|
|||||||
|
|
||||||
public class AudioRecord implements android.media.AudioRecordingMonitor android.media.AudioRouting android.media.MicrophoneDirection {
|
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;
|
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 {
|
public static class AudioRecord.Builder {
|
||||||
method public android.media.AudioRecord.Builder setAudioAttributes(@NonNull android.media.AudioAttributes) throws java.lang.IllegalArgumentException;
|
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 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 {
|
public final class AudioRecordingConfiguration implements android.os.Parcelable {
|
||||||
@@ -5338,6 +5342,10 @@ package android.media {
|
|||||||
method public void onPreferredFeaturesChanged(@NonNull java.util.List<java.lang.String>);
|
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 {
|
public class PlayerProxy {
|
||||||
method public void pause();
|
method public void pause();
|
||||||
method public void setPan(float);
|
method public void setPan(float);
|
||||||
|
|||||||
@@ -189,7 +189,8 @@ static jint android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject w
|
|||||||
jobject jaa, jintArray jSampleRate, jint channelMask,
|
jobject jaa, jintArray jSampleRate, jint channelMask,
|
||||||
jint channelIndexMask, jint audioFormat,
|
jint channelIndexMask, jint audioFormat,
|
||||||
jint buffSizeInBytes, jintArray jSession,
|
jint buffSizeInBytes, jintArray jSession,
|
||||||
jobject jIdentity, jlong nativeRecordInJavaObj) {
|
jobject jIdentity, jlong nativeRecordInJavaObj,
|
||||||
|
jint sharedAudioHistoryMs) {
|
||||||
//ALOGV(">> Entering android_media_AudioRecord_setup");
|
//ALOGV(">> Entering android_media_AudioRecord_setup");
|
||||||
//ALOGV("sampleRate=%d, audioFormat=%d, channel mask=%x, buffSizeInBytes=%d "
|
//ALOGV("sampleRate=%d, audioFormat=%d, channel mask=%x, buffSizeInBytes=%d "
|
||||||
// "nativeRecordInJavaObj=0x%llX",
|
// "nativeRecordInJavaObj=0x%llX",
|
||||||
@@ -288,20 +289,18 @@ static jint android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject w
|
|||||||
lpCallbackData->audioRecord_ref = env->NewGlobalRef(weak_this);
|
lpCallbackData->audioRecord_ref = env->NewGlobalRef(weak_this);
|
||||||
lpCallbackData->busy = false;
|
lpCallbackData->busy = false;
|
||||||
|
|
||||||
const status_t status = lpRecorder->set(paa->source,
|
const status_t status =
|
||||||
sampleRateInHertz,
|
lpRecorder->set(paa->source, sampleRateInHertz,
|
||||||
format, // word length, PCM
|
format, // word length, PCM
|
||||||
localChanMask,
|
localChanMask, frameCount,
|
||||||
frameCount,
|
recorderCallback, // callback_t
|
||||||
recorderCallback,// callback_t
|
lpCallbackData, // void* user
|
||||||
lpCallbackData,// void* user
|
0, // notificationFrames,
|
||||||
0, // notificationFrames,
|
true, // threadCanCallJava
|
||||||
true, // threadCanCallJava
|
sessionId, AudioRecord::TRANSFER_DEFAULT, flags, -1,
|
||||||
sessionId,
|
-1, // default uid, pid
|
||||||
AudioRecord::TRANSFER_DEFAULT,
|
paa.get(), AUDIO_PORT_HANDLE_NONE, MIC_DIRECTION_UNSPECIFIED,
|
||||||
flags,
|
MIC_FIELD_DIMENSION_DEFAULT, sharedAudioHistoryMs);
|
||||||
-1, -1, // default uid, pid
|
|
||||||
paa.get());
|
|
||||||
|
|
||||||
if (status != NO_ERROR) {
|
if (status != NO_ERROR) {
|
||||||
ALOGE("Error creating AudioRecord instance: initialization check failed with status %d.",
|
ALOGE("Error creating AudioRecord instance: initialization check failed with status %d.",
|
||||||
@@ -877,6 +876,23 @@ static void android_media_AudioRecord_setLogSessionId(JNIEnv *env, jobject thiz,
|
|||||||
record->setLogSessionId(logSessionId.c_str());
|
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) {
|
static jint android_media_AudioRecord_get_port_id(JNIEnv *env, jobject thiz) {
|
||||||
sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
|
sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
|
||||||
@@ -896,7 +912,7 @@ static const JNINativeMethod gMethods[] = {
|
|||||||
{"native_start", "(II)I", (void *)android_media_AudioRecord_start},
|
{"native_start", "(II)I", (void *)android_media_AudioRecord_start},
|
||||||
{"native_stop", "()V", (void *)android_media_AudioRecord_stop},
|
{"native_stop", "()V", (void *)android_media_AudioRecord_stop},
|
||||||
{"native_setup",
|
{"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},
|
(void *)android_media_AudioRecord_setup},
|
||||||
{"native_finalize", "()V", (void *)android_media_AudioRecord_finalize},
|
{"native_finalize", "()V", (void *)android_media_AudioRecord_finalize},
|
||||||
{"native_release", "()V", (void *)android_media_AudioRecord_release},
|
{"native_release", "()V", (void *)android_media_AudioRecord_release},
|
||||||
@@ -936,6 +952,8 @@ static const JNINativeMethod gMethods[] = {
|
|||||||
(void *)android_media_AudioRecord_set_preferred_microphone_field_dimension},
|
(void *)android_media_AudioRecord_set_preferred_microphone_field_dimension},
|
||||||
{"native_setLogSessionId", "(Ljava/lang/String;)V",
|
{"native_setLogSessionId", "(Ljava/lang/String;)V",
|
||||||
(void *)android_media_AudioRecord_setLogSessionId},
|
(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
|
// field names found in android/media/AudioRecord.java
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import static android.media.permission.PermissionUtil.myIdentity;
|
|||||||
import android.annotation.CallbackExecutor;
|
import android.annotation.CallbackExecutor;
|
||||||
import android.annotation.FloatRange;
|
import android.annotation.FloatRange;
|
||||||
import android.annotation.IntDef;
|
import android.annotation.IntDef;
|
||||||
|
import android.annotation.IntRange;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.annotation.RequiresPermission;
|
import android.annotation.RequiresPermission;
|
||||||
@@ -358,7 +359,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
|
|||||||
@RequiresPermission(android.Manifest.permission.RECORD_AUDIO)
|
@RequiresPermission(android.Manifest.permission.RECORD_AUDIO)
|
||||||
public AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
|
public AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
|
||||||
int sessionId) throws IllegalArgumentException {
|
int sessionId) throws IllegalArgumentException {
|
||||||
this(attributes, format, bufferSizeInBytes, sessionId, ActivityThread.currentApplication());
|
this(attributes, format, bufferSizeInBytes, sessionId,
|
||||||
|
ActivityThread.currentApplication(), 0 /*maxSharedAudioHistoryMs*/);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -383,7 +385,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
|
|||||||
* @throws IllegalArgumentException
|
* @throws IllegalArgumentException
|
||||||
*/
|
*/
|
||||||
private AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
|
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;
|
mRecordingState = RECORDSTATE_STOPPED;
|
||||||
|
|
||||||
if (attributes == null) {
|
if (attributes == null) {
|
||||||
@@ -455,12 +458,14 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
|
|||||||
int[] sampleRate = new int[] {mSampleRate};
|
int[] sampleRate = new int[] {mSampleRate};
|
||||||
int[] session = new int[1];
|
int[] session = new int[1];
|
||||||
session[0] = sessionId;
|
session[0] = sessionId;
|
||||||
|
|
||||||
//TODO: update native initialization when information about hardware init failure
|
//TODO: update native initialization when information about hardware init failure
|
||||||
// due to capture device already open is available.
|
// due to capture device already open is available.
|
||||||
int initResult = native_setup(new WeakReference<AudioRecord>(this),
|
int initResult = native_setup(new WeakReference<AudioRecord>(this),
|
||||||
mAudioAttributes, sampleRate, mChannelMask, mChannelIndexMask,
|
mAudioAttributes, sampleRate, mChannelMask, mChannelIndexMask,
|
||||||
mAudioFormat, mNativeBufferSizeInBytes,
|
mAudioFormat, mNativeBufferSizeInBytes,
|
||||||
session, identity, 0 /*nativeRecordInJavaObj*/);
|
session, identity, 0 /*nativeRecordInJavaObj*/,
|
||||||
|
maxSharedAudioHistoryMs);
|
||||||
if (initResult != SUCCESS) {
|
if (initResult != SUCCESS) {
|
||||||
loge("Error code "+initResult+" when initializing native AudioRecord object.");
|
loge("Error code "+initResult+" when initializing native AudioRecord object.");
|
||||||
return; // with mState == STATE_UNINITIALIZED
|
return; // with mState == STATE_UNINITIALIZED
|
||||||
@@ -522,7 +527,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
|
|||||||
0 /*mNativeBufferSizeInBytes*/,
|
0 /*mNativeBufferSizeInBytes*/,
|
||||||
session,
|
session,
|
||||||
myIdentity(null),
|
myIdentity(null),
|
||||||
nativeRecordInJavaObj);
|
nativeRecordInJavaObj,
|
||||||
|
0);
|
||||||
if (initResult != SUCCESS) {
|
if (initResult != SUCCESS) {
|
||||||
loge("Error code "+initResult+" when initializing native AudioRecord object.");
|
loge("Error code "+initResult+" when initializing native AudioRecord object.");
|
||||||
return; // with mState == STATE_UNINITIALIZED
|
return; // with mState == STATE_UNINITIALIZED
|
||||||
@@ -581,7 +587,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
|
|||||||
private int mBufferSizeInBytes;
|
private int mBufferSizeInBytes;
|
||||||
private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
|
private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
|
||||||
private int mPrivacySensitive = PRIVACY_SENSITIVE_DEFAULT;
|
private int mPrivacySensitive = PRIVACY_SENSITIVE_DEFAULT;
|
||||||
|
private int mMaxSharedAudioHistoryMs = 0;
|
||||||
private static final int PRIVACY_SENSITIVE_DEFAULT = -1;
|
private static final int PRIVACY_SENSITIVE_DEFAULT = -1;
|
||||||
private static final int PRIVACY_SENSITIVE_DISABLED = 0;
|
private static final int PRIVACY_SENSITIVE_DISABLED = 0;
|
||||||
private static final int PRIVACY_SENSITIVE_ENABLED = 1;
|
private static final int PRIVACY_SENSITIVE_ENABLED = 1;
|
||||||
@@ -747,7 +753,12 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
|
|||||||
if (sessionId < 0) {
|
if (sessionId < 0) {
|
||||||
throw new IllegalArgumentException("Invalid session ID " + sessionId);
|
throw new IllegalArgumentException("Invalid session ID " + sessionId);
|
||||||
}
|
}
|
||||||
mSessionId = 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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -771,6 +782,57 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
|
|||||||
return record;
|
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
|
* @return a new {@link AudioRecord} instance successfully initialized with all
|
||||||
* the parameters set on this <code>Builder</code>.
|
* the parameters set on this <code>Builder</code>.
|
||||||
@@ -837,7 +899,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
|
|||||||
* mFormat.getBytesPerSample(mFormat.getEncoding());
|
* mFormat.getBytesPerSample(mFormat.getEncoding());
|
||||||
}
|
}
|
||||||
final AudioRecord record = new AudioRecord(
|
final AudioRecord record = new AudioRecord(
|
||||||
mAttributes, mFormat, mBufferSizeInBytes, mSessionId, mContext);
|
mAttributes, mFormat, mBufferSizeInBytes, mSessionId, mContext,
|
||||||
|
mMaxSharedAudioHistoryMs);
|
||||||
if (record.getState() == STATE_UNINITIALIZED) {
|
if (record.getState() == STATE_UNINITIALIZED) {
|
||||||
// release is not necessary
|
// release is not necessary
|
||||||
throw new UnsupportedOperationException("Cannot create AudioRecord");
|
throw new UnsupportedOperationException("Cannot create AudioRecord");
|
||||||
@@ -1423,7 +1486,6 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
|
|||||||
|| (offsetInShorts + sizeInShorts > audioData.length)) {
|
|| (offsetInShorts + sizeInShorts > audioData.length)) {
|
||||||
return ERROR_BAD_VALUE;
|
return ERROR_BAD_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return native_read_in_short_array(audioData, offsetInShorts, sizeInShorts,
|
return native_read_in_short_array(audioData, offsetInShorts, sizeInShorts,
|
||||||
readMode == READ_BLOCKING);
|
readMode == READ_BLOCKING);
|
||||||
}
|
}
|
||||||
@@ -1642,6 +1704,70 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
|
|||||||
return AudioManager.getDeviceForPortId(deviceId, AudioManager.GET_DEVICES_INPUTS);
|
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.
|
* Call BEFORE adding a routing callback handler.
|
||||||
*/
|
*/
|
||||||
@@ -2105,13 +2231,14 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
|
|||||||
identity.packageName = opPackageName;
|
identity.packageName = opPackageName;
|
||||||
|
|
||||||
return native_setup(audiorecordThis, attributes, sampleRate, channelMask, channelIndexMask,
|
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,
|
private native int native_setup(Object audiorecordThis,
|
||||||
Object /*AudioAttributes*/ attributes,
|
Object /*AudioAttributes*/ attributes,
|
||||||
int[] sampleRate, int channelMask, int channelIndexMask, int audioFormat,
|
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()
|
// TODO remove: implementation calls directly into implementation of native_release()
|
||||||
private native void native_finalize();
|
private native void native_finalize();
|
||||||
@@ -2170,6 +2297,8 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
|
|||||||
|
|
||||||
private native void native_setLogSessionId(@Nullable String logSessionId);
|
private native void native_setLogSessionId(@Nullable String logSessionId);
|
||||||
|
|
||||||
|
private native int native_shareAudioHistory(@NonNull String sharedPackage, long startFromMs);
|
||||||
|
|
||||||
//---------------------------------------------------------
|
//---------------------------------------------------------
|
||||||
// Utility methods
|
// Utility methods
|
||||||
//------------------
|
//------------------
|
||||||
|
|||||||
@@ -1464,6 +1464,10 @@ public class AudioSystem
|
|||||||
// usage for AudioRecord.startRecordingSync(), must match AudioSystem::sync_event_t
|
// 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_NONE = 0;
|
||||||
/** @hide */ public static final int SYNC_EVENT_PRESENTATION_COMPLETE = 1;
|
/** @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
|
* @hide
|
||||||
|
|||||||
@@ -16,6 +16,13 @@
|
|||||||
|
|
||||||
package android.media;
|
package android.media;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.SystemApi;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The MediaSyncEvent class defines events that can be used to synchronize playback or capture
|
* The MediaSyncEvent class defines events that can be used to synchronize playback or capture
|
||||||
* actions between different players and recorders.
|
* actions between different players and recorders.
|
||||||
@@ -24,7 +31,7 @@ package android.media;
|
|||||||
* The audio session ID is retrieved from a player (e.g {@link MediaPlayer}, {@link AudioTrack} or
|
* The audio session ID is retrieved from a player (e.g {@link MediaPlayer}, {@link AudioTrack} or
|
||||||
* {@link ToneGenerator}) by use of the getAudioSessionId() method.
|
* {@link ToneGenerator}) by use of the getAudioSessionId() method.
|
||||||
*/
|
*/
|
||||||
public class MediaSyncEvent {
|
public class MediaSyncEvent implements Parcelable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No sync event specified. When used with a synchronized playback or capture method, the
|
* No sync event specified. When used with a synchronized playback or capture method, the
|
||||||
@@ -39,8 +46,16 @@ public class MediaSyncEvent {
|
|||||||
* {@link #setAudioSessionId(int)} method.
|
* {@link #setAudioSessionId(int)} method.
|
||||||
*/
|
*/
|
||||||
public static final int SYNC_EVENT_PRESENTATION_COMPLETE =
|
public static final int SYNC_EVENT_PRESENTATION_COMPLETE =
|
||||||
AudioSystem.SYNC_EVENT_PRESENTATION_COMPLETE;
|
AudioSystem.SYNC_EVENT_PRESENTATION_COMPLETE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @hide
|
||||||
|
* Used when sharing audio history between AudioRecord instances.
|
||||||
|
* See {@link AudioRecord.Builder#setSharedAudioEvent(MediaSyncEvent).
|
||||||
|
*/
|
||||||
|
@SystemApi
|
||||||
|
public static final int SYNC_EVENT_SHARE_AUDIO_HISTORY =
|
||||||
|
AudioSystem.SYNC_EVENT_SHARE_AUDIO_HISTORY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a synchronization event of the sepcified type.
|
* Creates a synchronization event of the sepcified type.
|
||||||
@@ -112,11 +127,86 @@ public class MediaSyncEvent {
|
|||||||
|
|
||||||
private static boolean isValidType(int type) {
|
private static boolean isValidType(int type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case SYNC_EVENT_NONE:
|
case SYNC_EVENT_NONE:
|
||||||
case SYNC_EVENT_PRESENTATION_COMPLETE:
|
case SYNC_EVENT_PRESENTATION_COMPLETE:
|
||||||
return true;
|
case SYNC_EVENT_SHARE_AUDIO_HISTORY:
|
||||||
default:
|
return true;
|
||||||
return false;
|
default:
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parcelable implementation
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||||
|
Objects.requireNonNull(dest);
|
||||||
|
dest.writeInt(mType);
|
||||||
|
dest.writeInt(mAudioSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MediaSyncEvent(Parcel in) {
|
||||||
|
mType = in.readInt();
|
||||||
|
mAudioSession = in.readInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final @NonNull Parcelable.Creator<MediaSyncEvent> CREATOR =
|
||||||
|
new Parcelable.Creator<MediaSyncEvent>() {
|
||||||
|
/**
|
||||||
|
* Rebuilds an MediaSyncEvent previously stored with writeToParcel().
|
||||||
|
* @param p Parcel object to read the MediaSyncEvent from
|
||||||
|
* @return a new MediaSyncEvent created from the data in the parcel
|
||||||
|
*/
|
||||||
|
public MediaSyncEvent createFromParcel(Parcel p) {
|
||||||
|
return new MediaSyncEvent(p);
|
||||||
|
}
|
||||||
|
public MediaSyncEvent[] newArray(int size) {
|
||||||
|
return new MediaSyncEvent[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
MediaSyncEvent that = (MediaSyncEvent) o;
|
||||||
|
return ((mType == that.mType)
|
||||||
|
&& (mAudioSession == that.mAudioSession));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(mType, mAudioSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new String("MediaSyncEvent:"
|
||||||
|
+ " type=" + typeToString(mType)
|
||||||
|
+ " session=" + mAudioSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the string representation for the type.
|
||||||
|
* @param type one of the {@link MediaSyncEvent} type constants
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static @NonNull String typeToString(int type) {
|
||||||
|
switch (type) {
|
||||||
|
case SYNC_EVENT_NONE:
|
||||||
|
return "SYNC_EVENT_NONE";
|
||||||
|
case SYNC_EVENT_PRESENTATION_COMPLETE:
|
||||||
|
return "SYNC_EVENT_PRESENTATION_COMPLETE";
|
||||||
|
case SYNC_EVENT_SHARE_AUDIO_HISTORY:
|
||||||
|
return "SYNC_EVENT_SHARE_AUDIO_HISTORY";
|
||||||
|
default:
|
||||||
|
return "unknown event type " + type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user