Merge "AudioRecord: Allow to share capture history." into sc-dev

This commit is contained in:
Eric Laurent
2021-04-21 17:03:28 +00:00
committed by Android (Google) Code Review
6 changed files with 286 additions and 34 deletions

View File

@@ -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
} }

View File

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

View File

@@ -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

View File

@@ -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
//------------------ //------------------

View File

@@ -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

View File

@@ -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;
}
}
} }