diff --git a/api/current.txt b/api/current.txt index cf2ea6d99ec4b..daac1ba480cfd 100644 --- a/api/current.txt +++ b/api/current.txt @@ -19874,6 +19874,7 @@ package android.media { method public android.media.AudioDeviceInfo getRoutedDevice(); method public int getSampleRate(); method public int getState(); + method public int getTimestamp(android.media.AudioTimestamp, int); method public int read(byte[], int, int); method public int read(byte[], int, int, int); method public int read(short[], int, int); @@ -19945,6 +19946,8 @@ package android.media { public final class AudioTimestamp { ctor public AudioTimestamp(); + field public static final int TIMEBASE_BOOTTIME = 1; // 0x1 + field public static final int TIMEBASE_MONOTONIC = 0; // 0x0 field public long framePosition; field public long nanoTime; } diff --git a/api/system-current.txt b/api/system-current.txt index 6a420e2f06ad3..06187504d6546 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -21140,6 +21140,7 @@ package android.media { method public android.media.AudioDeviceInfo getRoutedDevice(); method public int getSampleRate(); method public int getState(); + method public int getTimestamp(android.media.AudioTimestamp, int); method public int read(byte[], int, int); method public int read(byte[], int, int, int); method public int read(short[], int, int); @@ -21213,6 +21214,8 @@ package android.media { public final class AudioTimestamp { ctor public AudioTimestamp(); + field public static final int TIMEBASE_BOOTTIME = 1; // 0x1 + field public static final int TIMEBASE_MONOTONIC = 0; // 0x0 field public long framePosition; field public long nanoTime; } diff --git a/api/test-current.txt b/api/test-current.txt index 22eeef43a1ae9..684b385507ab9 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -19883,6 +19883,7 @@ package android.media { method public android.media.AudioDeviceInfo getRoutedDevice(); method public int getSampleRate(); method public int getState(); + method public int getTimestamp(android.media.AudioTimestamp, int); method public int read(byte[], int, int); method public int read(byte[], int, int, int); method public int read(short[], int, int); @@ -19954,6 +19955,8 @@ package android.media { public final class AudioTimestamp { ctor public AudioTimestamp(); + field public static final int TIMEBASE_BOOTTIME = 1; // 0x1 + field public static final int TIMEBASE_MONOTONIC = 0; // 0x0 field public long framePosition; field public long nanoTime; } diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp index b977e377f2f3a..d25da78237194 100644 --- a/core/jni/android_media_AudioRecord.cpp +++ b/core/jni/android_media_AudioRecord.cpp @@ -54,6 +54,10 @@ struct audio_attributes_fields_t { }; static audio_attributes_fields_t javaAudioAttrFields; static audio_record_fields_t javaAudioRecordFields; +static struct { + jfieldID fieldFramePosition; // AudioTimestamp.framePosition + jfieldID fieldNanoTime; // AudioTimestamp.nanoTime +} javaAudioTimestampFields; struct audiorecord_callback_cookie { jclass audioRecord_class; @@ -678,7 +682,40 @@ static void android_media_AudioRecord_disableDeviceCallback( } } +// ---------------------------------------------------------------------------- +static jint android_media_AudioRecord_get_timestamp(JNIEnv *env, jobject thiz, + jobject timestamp, jint timebase) { + sp lpRecorder = getAudioRecord(env, thiz); + if (lpRecorder == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", + "Unable to retrieve AudioRecord pointer for getTimestamp()"); + return (jint)AUDIO_JAVA_ERROR; + } + + // TODO Enable. +#if 0 + // get the record timestamp + ExtendedTimestamp ts; + jint status = nativeToJavaStatus(lpRecorder->getExtendedTimestamp(&ts)); + + if (status == AUDIO_JAVA_SUCCESS) { + // set the data + int64_t position, time; + + status = nativeToJavaStatus(ts.getBestTimestamp(&position, &time, timebase)); + if (status == AUDIO_JAVA_SUCCESS) { + env->SetLongField( + timestamp, javaAudioTimestampFields.fieldFramePosition, position); + env->SetLongField( + timestamp, javaAudioTimestampFields.fieldNanoTime, time); + } + } + return status; +#else + return (jint)AUDIO_JAVA_INVALID_OPERATION; +#endif +} // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- @@ -716,6 +753,8 @@ static const JNINativeMethod gMethods[] = { {"native_enableDeviceCallback", "()V", (void *)android_media_AudioRecord_enableDeviceCallback}, {"native_disableDeviceCallback", "()V", (void *)android_media_AudioRecord_disableDeviceCallback}, + {"native_get_timestamp", "(Landroid/media/AudioTimestamp;I)I", + (void *)android_media_AudioRecord_get_timestamp}, }; // field names found in android/media/AudioRecord.java @@ -758,6 +797,13 @@ int register_android_media_AudioRecord(JNIEnv *env) javaAudioAttrFields.fieldFormattedTags = GetFieldIDOrDie(env, audioAttrClass, "mFormattedTags", "Ljava/lang/String;"); + // Get the RecordTimestamp class and fields + jclass audioTimestampClass = FindClassOrDie(env, "android/media/AudioTimestamp"); + javaAudioTimestampFields.fieldFramePosition = + GetFieldIDOrDie(env, audioTimestampClass, "framePosition", "J"); + javaAudioTimestampFields.fieldNanoTime = + GetFieldIDOrDie(env, audioTimestampClass, "nanoTime", "J"); + return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods)); } diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index 7c218936a1d19..800b914970582 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -825,6 +825,33 @@ public class AudioRecord implements AudioRouting return native_get_pos_update_period(); } + /** + * Poll for an {@link AudioTimestamp} on demand. + *

+ * The AudioTimestamp reflects the frame delivery information at + * the earliest point available in the capture pipeline. + *

+ * Calling {@link #startRecording()} following a {@link #stop()} will reset + * the frame count to 0. + * + * @param timestamp a reference to a non-null AudioTimestamp instance. + * @param timebase one of + * {@link AudioTimestamp#TIMEBASE_BOOTTIME AudioTimestamp.TIMEBASE_BOOTTIME} or + * {@link AudioTimestamp#TIMEBASE_MONOTONIC AudioTimestamp.TIMEBASE_MONOTONIC}. + * @return {@link #SUCCESS} if a timestamp is available, + * or {@link #ERROR_INVALID_OPERATION} if a timestamp not available. + */ + public int getTimestamp(@NonNull AudioTimestamp timestamp, + @AudioTimestamp.Timebase int timebase) + { + if (timestamp == null || + (timebase != AudioTimestamp.TIMEBASE_BOOTTIME + && timebase != AudioTimestamp.TIMEBASE_MONOTONIC)) { + throw new IllegalArgumentException(); + } + return native_get_timestamp(timestamp, timebase); + } + /** * Returns the minimum buffer size required for the successful creation of an AudioRecord * object, in byte units. @@ -1709,6 +1736,9 @@ public class AudioRecord implements AudioRouting private native final void native_enableDeviceCallback(); private native final void native_disableDeviceCallback(); + private native final int native_get_timestamp(@NonNull AudioTimestamp timestamp, + @AudioTimestamp.Timebase int timebase); + //--------------------------------------------------------- // Utility methods //------------------ diff --git a/media/java/android/media/AudioTimestamp.java b/media/java/android/media/AudioTimestamp.java index 965ba859dcbd9..be8ca151580db 100644 --- a/media/java/android/media/AudioTimestamp.java +++ b/media/java/android/media/AudioTimestamp.java @@ -16,29 +16,74 @@ package android.media; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import android.annotation.IntDef; + /** * Structure that groups a position in frame units relative to an assumed audio stream, - * together with the estimated time when that frame was presented or is committed to be - * presented. - * In the case of audio output, "present" means that audio produced on device - * is detectable by an external observer off device. + * together with the estimated time when that frame enters or leaves the audio + * processing pipeline on that device. This can be used to coordinate events + * and interactions with the external environment. + *

* The time is based on the implementation's best effort, using whatever knowledge * is available to the system, but cannot account for any delay unknown to the implementation. * - * @see AudioTrack#getTimestamp + * @see AudioTrack#getTimestamp AudioTrack.getTimestamp(AudioTimestamp) + * @see AudioRecord#getTimestamp AudioRecord.getTimestamp(AudioTimestamp, int) */ public final class AudioTimestamp { + /** + * Clock monotonic or its equivalent on the system, + * in the same units and timebase as {@link java.lang.System#nanoTime}. + */ + public static final int TIMEBASE_MONOTONIC = 0; + + /** + * Clock monotonic including suspend time or its equivalent on the system, + * in the same units and timebase as {@link android.os.SystemClock#elapsedRealtimeNanos}. + */ + public static final int TIMEBASE_BOOTTIME = 1; + + /** @hide */ + @IntDef({ + TIMEBASE_MONOTONIC, + TIMEBASE_BOOTTIME, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Timebase {} + /** * Position in frames relative to start of an assumed audio stream. - * The low-order 32 bits of position is in wrapping frame units similar to - * {@link AudioTrack#getPlaybackHeadPosition}. + *

+ * When obtained through + * {@link AudioRecord#getTimestamp AudioRecord.getTimestamp(AudioTimestamp, int)}, + * all 64 bits of position are valid. + *

+ * When obtained through + * {@link AudioTrack#getTimestamp AudioTrack.getTimestamp(AudioTimestamp)}, + * the low-order 32 bits of position is in wrapping frame units similar to + * {@link AudioTrack#getPlaybackHeadPosition AudioTrack.getPlaybackHeadPosition()}. */ public long framePosition; /** - * The estimated time when frame was presented or is committed to be presented, - * in the same units and timebase as {@link java.lang.System#nanoTime}. + * Time associated with the frame in the audio pipeline. + *

+ * When obtained through + * {@link AudioRecord#getTimestamp AudioRecord.getTimestamp(AudioTimestamp, int)}, + * this is the estimated time in nanoseconds when the frame referred to by + * {@link #framePosition} was captured. The timebase is either + * {@link #TIMEBASE_MONOTONIC} or {@link #TIMEBASE_BOOTTIME}, depending + * on the timebase parameter used in + * {@link AudioRecord#getTimestamp AudioRecord.getTimestamp(AudioTimestamp, int)}. + *

+ * When obtained through + * {@link AudioTrack#getTimestamp AudioTrack.getTimestamp(AudioTimestamp)}, + * this is the estimated time when the frame was presented or is committed to be presented, + * with a timebase of {@link #TIMEBASE_MONOTONIC}. */ public long nanoTime; }