Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e354f5d5 authored by Andy Hung's avatar Andy Hung Committed by Android (Google) Code Review
Browse files

Merge "Add non-blocking reads to AudioRecord"

parents 5e4b823a 0e9e2f37
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -14910,9 +14910,12 @@ package android.media {
    method public int getSampleRate();
    method public int getState();
    method public int read(byte[], int, int);
    method public int read(byte[], int, int, int);
    method public int read(short[], int, int);
    method public int read(short[], int, int, int);
    method public int read(float[], int, int, int);
    method public int read(java.nio.ByteBuffer, int);
    method public int read(java.nio.ByteBuffer, int, int);
    method public void release();
    method public int setNotificationMarkerPosition(int);
    method public int setPositionNotificationPeriod(int);
+3 −0
Original line number Diff line number Diff line
@@ -16120,9 +16120,12 @@ package android.media {
    method public int getSampleRate();
    method public int getState();
    method public int read(byte[], int, int);
    method public int read(byte[], int, int, int);
    method public int read(short[], int, int);
    method public int read(short[], int, int, int);
    method public int read(float[], int, int, int);
    method public int read(java.nio.ByteBuffer, int);
    method public int read(java.nio.ByteBuffer, int, int);
    method public void release();
    method public int setNotificationMarkerPosition(int);
    method public int setPositionNotificationPeriod(int);
+69 −111
Original line number Diff line number Diff line
@@ -356,94 +356,57 @@ static void android_media_AudioRecord_finalize(JNIEnv *env, jobject thiz) {
    android_media_AudioRecord_release(env, thiz);
}


// ----------------------------------------------------------------------------
static jint android_media_AudioRecord_readInByteArray(JNIEnv *env,  jobject thiz,
                                                        jbyteArray javaAudioData,
                                                        jint offsetInBytes, jint sizeInBytes) {
    jbyte* recordBuff = NULL;
    // get the audio recorder from which we'll read new audio samples
    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
    if (lpRecorder == NULL) {
        ALOGE("Unable to retrieve AudioRecord object, can't record");
        return 0;
// overloaded JNI array helper functions
static inline
jbyte *envGetArrayElements(JNIEnv *env, jbyteArray array, jboolean *isCopy) {
    return env->GetByteArrayElements(array, isCopy);
}

    if (!javaAudioData) {
        ALOGE("Invalid Java array to store recorded audio, can't record");
        return 0;
static inline
void envReleaseArrayElements(JNIEnv *env, jbyteArray array, jbyte *elems, jint mode) {
    env->ReleaseByteArrayElements(array, elems, mode);
}

    // get the pointer to where we'll record the audio
    // NOTE: We may use GetPrimitiveArrayCritical() when the JNI implementation changes in such
    // a way that it becomes much more efficient. When doing so, we will have to prevent the
    // AudioSystem callback to be called while in critical section (in case of media server
    // process crash for instance)
    recordBuff = (jbyte *)env->GetByteArrayElements(javaAudioData, NULL);

    if (recordBuff == NULL) {
        ALOGE("Error retrieving destination for recorded audio data, can't record");
        return 0;
static inline
jshort *envGetArrayElements(JNIEnv *env, jshortArray array, jboolean *isCopy) {
    return env->GetShortArrayElements(array, isCopy);
}

    // read the new audio data from the native AudioRecord object
    ssize_t readSize = lpRecorder->read(recordBuff + offsetInBytes, sizeInBytes);
    env->ReleaseByteArrayElements(javaAudioData, recordBuff, 0);

    if (readSize < 0) {
        readSize = (jint)AUDIO_JAVA_INVALID_OPERATION;
    }
    return (jint) readSize;
static inline
void envReleaseArrayElements(JNIEnv *env, jshortArray array, jshort *elems, jint mode) {
    env->ReleaseShortArrayElements(array, elems, mode);
}

// ----------------------------------------------------------------------------
static jint android_media_AudioRecord_readInShortArray(JNIEnv *env,  jobject thiz,
                                                        jshortArray javaAudioData,
                                                        jint offsetInShorts, jint sizeInShorts) {

    jshort* recordBuff = NULL;
    // get the audio recorder from which we'll read new audio samples
    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
    if (lpRecorder == NULL) {
        ALOGE("Unable to retrieve AudioRecord object, can't record");
        return 0;
static inline
jfloat *envGetArrayElements(JNIEnv *env, jfloatArray array, jboolean *isCopy) {
    return env->GetFloatArrayElements(array, isCopy);
}

    if (!javaAudioData) {
        ALOGE("Invalid Java array to store recorded audio, can't record");
        return 0;
static inline
void envReleaseArrayElements(JNIEnv *env, jfloatArray array, jfloat *elems, jint mode) {
    env->ReleaseFloatArrayElements(array, elems, mode);
}

    // get the pointer to where we'll record the audio
    // NOTE: We may use GetPrimitiveArrayCritical() when the JNI implementation changes in such
    // a way that it becomes much more efficient. When doing so, we will have to prevent the
    // AudioSystem callback to be called while in critical section (in case of media server
    // process crash for instance)
    recordBuff = (jshort *)env->GetShortArrayElements(javaAudioData, NULL);

    if (recordBuff == NULL) {
        ALOGE("Error retrieving destination for recorded audio data, can't record");
        return 0;
    }

    // read the new audio data from the native AudioRecord object
    const size_t sizeInBytes = sizeInShorts * sizeof(short);
    ssize_t readSize = lpRecorder->read(recordBuff + offsetInShorts, sizeInBytes);

    env->ReleaseShortArrayElements(javaAudioData, recordBuff, 0);

    if (readSize < 0) {
        readSize = (jint)AUDIO_JAVA_INVALID_OPERATION;
    } else {
        readSize /= sizeof(short);
static inline
jint interpretReadSizeError(ssize_t readSize) {
    ALOGE_IF(readSize != WOULD_BLOCK, "Error %zd during AudioRecord native read", readSize);
    switch (readSize) {
    case WOULD_BLOCK:
        return (jint)0;
    case BAD_VALUE:
        return (jint)AUDIO_JAVA_BAD_VALUE;
    default:
        // may be possible for other errors such as
        // NO_INIT to happen if restoreRecord_l fails.
    case INVALID_OPERATION:
        return (jint)AUDIO_JAVA_INVALID_OPERATION;
    }
    return (jint) readSize;
}

// ----------------------------------------------------------------------------
static jint android_media_AudioRecord_readInFloatArray(JNIEnv *env,  jobject thiz,
                                                        jfloatArray javaAudioData,
                                                        jint offsetInFloats, jint sizeInFloats,
template <typename T>
static jint android_media_AudioRecord_readInArray(JNIEnv *env,  jobject thiz,
                                                  T javaAudioData,
                                                  jint offsetInSamples, jint sizeInSamples,
                                                  jboolean isReadBlocking) {
    // get the audio recorder from which we'll read new audio samples
    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
@@ -457,72 +420,64 @@ static jint android_media_AudioRecord_readInFloatArray(JNIEnv *env, jobject thi
        return (jint)AUDIO_JAVA_BAD_VALUE;
    }

    // get the pointer to where we'll record the audio
    // NOTE: We may use GetPrimitiveArrayCritical() when the JNI implementation changes in such
    // a way that it becomes much more efficient. When doing so, we will have to prevent the
    // AudioSystem callback to be called while in critical section (in case of media server
    // process crash for instance)
    jfloat *recordBuff = (jfloat *)env->GetFloatArrayElements(javaAudioData, NULL);

    // get the pointer to where we'll record the audio
    auto *recordBuff = envGetArrayElements(env, javaAudioData, NULL);
    if (recordBuff == NULL) {
        ALOGE("Error retrieving destination for recorded audio data");
        return (jint)AUDIO_JAVA_BAD_VALUE;
    }

    // read the new audio data from the native AudioRecord object
    const size_t sizeInBytes = sizeInFloats * sizeof(float);
    ssize_t readSize = lpRecorder->read(recordBuff + offsetInFloats, sizeInBytes);
    const size_t sizeInBytes = sizeInSamples * sizeof(*recordBuff);
    ssize_t readSize = lpRecorder->read(
            recordBuff + offsetInSamples, sizeInBytes, isReadBlocking == JNI_TRUE /* blocking */);

    env->ReleaseFloatArrayElements(javaAudioData, recordBuff, 0);
    envReleaseArrayElements(env, javaAudioData, recordBuff, 0);

    if (readSize < 0) {
        ALOGE_IF(readSize != WOULD_BLOCK, "Error %zd during AudioRecord native read", readSize);
        switch (readSize) {
        case WOULD_BLOCK:
            return (jint)0;
        case BAD_VALUE:
            return (jint)AUDIO_JAVA_BAD_VALUE;
        default:
            // may be possible for other errors such as
            // NO_INIT to happen if restoreRecord_l fails.
        case INVALID_OPERATION:
            return (jint)AUDIO_JAVA_INVALID_OPERATION;
        return interpretReadSizeError(readSize);
    }
    }
    return (jint)(readSize / sizeof(float));
    return (jint)(readSize / sizeof(*recordBuff));
}

// ----------------------------------------------------------------------------
static jint android_media_AudioRecord_readInDirectBuffer(JNIEnv *env,  jobject thiz,
                                                  jobject jBuffer, jint sizeInBytes) {
                                                         jobject jBuffer, jint sizeInBytes,
                                                         jboolean isReadBlocking) {
    // get the audio recorder from which we'll read new audio samples
    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
    if (lpRecorder==NULL)
        return 0;
        return (jint)AUDIO_JAVA_INVALID_OPERATION;

    // direct buffer and direct access supported?
    long capacity = env->GetDirectBufferCapacity(jBuffer);
    if (capacity == -1) {
        // buffer direct access is not supported
        ALOGE("Buffer direct access is not supported, can't record");
        return 0;
        return (jint)AUDIO_JAVA_BAD_VALUE;
    }
    //ALOGV("capacity = %ld", capacity);
    jbyte* nativeFromJavaBuf = (jbyte*) env->GetDirectBufferAddress(jBuffer);
    if (nativeFromJavaBuf==NULL) {
        ALOGE("Buffer direct access is not supported, can't record");
        return 0;
        return (jint)AUDIO_JAVA_BAD_VALUE;
    }

    // read new data from the recorder
    ssize_t readSize = lpRecorder->read(nativeFromJavaBuf,
                                   capacity < sizeInBytes ? capacity : sizeInBytes);
                                        capacity < sizeInBytes ? capacity : sizeInBytes,
                                        isReadBlocking == JNI_TRUE /* blocking */);
    if (readSize < 0) {
        readSize = (jint)AUDIO_JAVA_INVALID_OPERATION;
        return interpretReadSizeError(readSize);
    }
    return (jint)readSize;
}


// ----------------------------------------------------------------------------
static jint android_media_AudioRecord_get_native_frame_count(JNIEnv *env,  jobject thiz) {
    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
@@ -633,12 +588,15 @@ static JNINativeMethod gMethods[] = {
    {"native_finalize",      "()V",    (void *)android_media_AudioRecord_finalize},
    {"native_release",       "()V",    (void *)android_media_AudioRecord_release},
    {"native_read_in_byte_array",
                             "([BII)I", (void *)android_media_AudioRecord_readInByteArray},
                             "([BIIZ)I",
                                     (void *)android_media_AudioRecord_readInArray<jbyteArray>},
    {"native_read_in_short_array",
                             "([SII)I", (void *)android_media_AudioRecord_readInShortArray},
                             "([SIIZ)I",
                                     (void *)android_media_AudioRecord_readInArray<jshortArray>},
    {"native_read_in_float_array",
                             "([FIIZ)I", (void *)android_media_AudioRecord_readInFloatArray},
    {"native_read_in_direct_buffer","(Ljava/lang/Object;I)I",
                             "([FIIZ)I",
                                     (void *)android_media_AudioRecord_readInArray<jfloatArray>},
    {"native_read_in_direct_buffer","(Ljava/lang/Object;IZ)I",
                                       (void *)android_media_AudioRecord_readInDirectBuffer},
    {"native_get_native_frame_count",
                             "()I",    (void *)android_media_AudioRecord_get_native_frame_count},
+114 −17
Original line number Diff line number Diff line
@@ -901,20 +901,63 @@ public class AudioRecord
     *    the parameters don't resolve to valid data and indexes.
     *    The number of bytes will not exceed sizeInBytes.
     */
    public int read(byte[] audioData, int offsetInBytes, int sizeInBytes) {
        if (mState != STATE_INITIALIZED) {
    public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes) {
        return read(audioData, offsetInBytes, sizeInBytes, READ_BLOCKING);
    }

    /**
     * Reads audio data from the audio hardware for recording into a byte array.
     * The format specified in the AudioRecord constructor should be
     * {@link AudioFormat#ENCODING_PCM_8BIT} to correspond to the data in the array.
     * @param audioData the array to which the recorded audio data is written.
     * @param offsetInBytes index in audioData from which the data is written expressed in bytes.
     * @param sizeInBytes the number of requested bytes.
     * @param readMode one of {@link #READ_BLOCKING}, {@link #READ_NON_BLOCKING}.
     *     <br>With {@link #READ_BLOCKING}, the read will block until all the requested data
     *     is read.
     *     <br>With {@link #READ_NON_BLOCKING}, the read will return immediately after
     *     reading as much audio data as possible without blocking.
     * @return the number of bytes that were read or {@link #ERROR_INVALID_OPERATION}
     *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
     *    the parameters don't resolve to valid data and indexes.
     *    The number of bytes will not exceed sizeInBytes.
     */
    public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes,
            @ReadMode int readMode) {
        if (mState != STATE_INITIALIZED  || mAudioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
            return ERROR_INVALID_OPERATION;
        }

        if ((readMode != READ_BLOCKING) && (readMode != READ_NON_BLOCKING)) {
            Log.e(TAG, "AudioRecord.read() called with invalid blocking mode");
            return ERROR_BAD_VALUE;
        }

        if ( (audioData == null) || (offsetInBytes < 0 ) || (sizeInBytes < 0)
                || (offsetInBytes + sizeInBytes < 0)  // detect integer overflow
                || (offsetInBytes + sizeInBytes > audioData.length)) {
            return ERROR_BAD_VALUE;
        }

        return native_read_in_byte_array(audioData, offsetInBytes, sizeInBytes);
        return native_read_in_byte_array(audioData, offsetInBytes, sizeInBytes,
                readMode == READ_BLOCKING);
    }

    /**
     * Reads audio data from the audio hardware for recording into a short array.
     * The format specified in the AudioRecord constructor should be
     * {@link AudioFormat#ENCODING_PCM_16BIT} to correspond to the data in the array.
     * @param audioData the array to which the recorded audio data is written.
     * @param offsetInShorts index in audioData from which the data is written expressed in shorts.
     * @param sizeInShorts the number of requested shorts.
     * @return the number of shorts that were read or {@link #ERROR_INVALID_OPERATION}
     *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
     *    the parameters don't resolve to valid data and indexes.
     *    The number of shorts will not exceed sizeInShorts.
     */
    public int read(@NonNull short[] audioData, int offsetInShorts, int sizeInShorts) {
        return read(audioData, offsetInShorts, sizeInShorts, READ_BLOCKING);
    }

    /**
     * Reads audio data from the audio hardware for recording into a short array.
@@ -923,23 +966,35 @@ public class AudioRecord
     * @param audioData the array to which the recorded audio data is written.
     * @param offsetInShorts index in audioData from which the data is written expressed in shorts.
     * @param sizeInShorts the number of requested shorts.
     * @param readMode one of {@link #READ_BLOCKING}, {@link #READ_NON_BLOCKING}.
     *     <br>With {@link #READ_BLOCKING}, the read will block until all the requested data
     *     is read.
     *     <br>With {@link #READ_NON_BLOCKING}, the read will return immediately after
     *     reading as much audio data as possible without blocking.
     * @return the number of shorts that were read or {@link #ERROR_INVALID_OPERATION}
     *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
     *    the parameters don't resolve to valid data and indexes.
     *    The number of shorts will not exceed sizeInShorts.
     */
    public int read(short[] audioData, int offsetInShorts, int sizeInShorts) {
        if (mState != STATE_INITIALIZED) {
    public int read(@NonNull short[] audioData, int offsetInShorts, int sizeInShorts,
            @ReadMode int readMode) {
        if (mState != STATE_INITIALIZED || mAudioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
            return ERROR_INVALID_OPERATION;
        }

        if ((readMode != READ_BLOCKING) && (readMode != READ_NON_BLOCKING)) {
            Log.e(TAG, "AudioRecord.read() called with invalid blocking mode");
            return ERROR_BAD_VALUE;
        }

        if ( (audioData == null) || (offsetInShorts < 0 ) || (sizeInShorts < 0)
                || (offsetInShorts + sizeInShorts < 0)  // detect integer overflow
                || (offsetInShorts + sizeInShorts > audioData.length)) {
            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);
    }

    /**
@@ -950,21 +1005,32 @@ public class AudioRecord
     * @param offsetInFloats index in audioData from which the data is written.
     * @param sizeInFloats the number of requested floats.
     * @param readMode one of {@link #READ_BLOCKING}, {@link #READ_NON_BLOCKING}.
     *     <BR>With {@link #READ_BLOCKING}, the read will block until all the requested data
     *     <br>With {@link #READ_BLOCKING}, the read will block until all the requested data
     *     is read.
     *     <BR>With {@link #READ_NON_BLOCKING}, the read will return immediately after
     *     <br>With {@link #READ_NON_BLOCKING}, the read will return immediately after
     *     reading as much audio data as possible without blocking.
     * @return the number of floats that were read or {@link #ERROR_INVALID_OPERATION}
     *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
     *    the parameters don't resolve to valid data and indexes.
     *    The number of floats will not exceed sizeInFloats.
     */
    public int read(float[] audioData, int offsetInFloats, int sizeInFloats,
    public int read(@NonNull float[] audioData, int offsetInFloats, int sizeInFloats,
            @ReadMode int readMode) {
        if (mState != STATE_INITIALIZED) {
        if (mState == STATE_UNINITIALIZED) {
            Log.e(TAG, "AudioRecord.read() called in invalid state STATE_UNINITIALIZED");
            return ERROR_INVALID_OPERATION;
        }

        if (mAudioFormat != AudioFormat.ENCODING_PCM_FLOAT) {
            Log.e(TAG, "AudioRecord.read(float[] ...) requires format ENCODING_PCM_FLOAT");
            return ERROR_INVALID_OPERATION;
        }

        if ((readMode != READ_BLOCKING) && (readMode != READ_NON_BLOCKING)) {
            Log.e(TAG, "AudioRecord.read() called with invalid blocking mode");
            return ERROR_BAD_VALUE;
        }

        if ((audioData == null) || (offsetInFloats < 0) || (sizeInFloats < 0)
                || (offsetInFloats + sizeInFloats < 0)  // detect integer overflow
                || (offsetInFloats + sizeInFloats > audioData.length)) {
@@ -992,18 +1058,48 @@ public class AudioRecord
     *    The number of bytes will not exceed sizeInBytes.
     *    The number of bytes read will truncated to be a multiple of the frame size.
     */
    public int read(ByteBuffer audioBuffer, int sizeInBytes) {
    public int read(@NonNull ByteBuffer audioBuffer, int sizeInBytes) {
        return read(audioBuffer, sizeInBytes, READ_BLOCKING);
    }

    /**
     * Reads audio data from the audio hardware for recording into a direct buffer. If this buffer
     * is not a direct buffer, this method will always return 0.
     * Note that the value returned by {@link java.nio.Buffer#position()} on this buffer is
     * unchanged after a call to this method.
     * The representation of the data in the buffer will depend on the format specified in
     * the AudioRecord constructor, and will be native endian.
     * @param audioBuffer the direct buffer to which the recorded audio data is written.
     * @param sizeInBytes the number of requested bytes. It is recommended but not enforced
     *    that the number of bytes requested be a multiple of the frame size (sample size in
     *    bytes multiplied by the channel count).
     * @param readMode one of {@link #READ_BLOCKING}, {@link #READ_NON_BLOCKING}.
     *     <br>With {@link #READ_BLOCKING}, the read will block until all the requested data
     *     is read.
     *     <br>With {@link #READ_NON_BLOCKING}, the read will return immediately after
     *     reading as much audio data as possible without blocking.
     * @return the number of bytes that were read or {@link #ERROR_INVALID_OPERATION}
     *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
     *    the parameters don't resolve to valid data and indexes.
     *    The number of bytes will not exceed sizeInBytes.
     *    The number of bytes read will truncated to be a multiple of the frame size.
     */
    public int read(@NonNull ByteBuffer audioBuffer, int sizeInBytes, @ReadMode int readMode) {
        if (mState != STATE_INITIALIZED) {
            return ERROR_INVALID_OPERATION;
        }

        if ( (audioBuffer == null) || (sizeInBytes < 0) ) {
        if ((readMode != READ_BLOCKING) && (readMode != READ_NON_BLOCKING)) {
            Log.e(TAG, "AudioRecord.read() called with invalid blocking mode");
            return ERROR_BAD_VALUE;
        }

        return native_read_in_direct_buffer(audioBuffer, sizeInBytes);
        if ( (audioBuffer == null) || (sizeInBytes < 0) ) {
            return ERROR_BAD_VALUE;
        }

        return native_read_in_direct_buffer(audioBuffer, sizeInBytes, readMode == READ_BLOCKING);
    }

    //--------------------------------------------------------------------------
    // Initialization / configuration
@@ -1186,15 +1282,16 @@ public class AudioRecord
    private native final void native_stop();

    private native final int native_read_in_byte_array(byte[] audioData,
            int offsetInBytes, int sizeInBytes);
            int offsetInBytes, int sizeInBytes, boolean isBlocking);

    private native final int native_read_in_short_array(short[] audioData,
            int offsetInShorts, int sizeInShorts);
            int offsetInShorts, int sizeInShorts, boolean isBlocking);

    private native final int native_read_in_float_array(float[] audioData,
            int offsetInFloats, int sizeInFloats, boolean isBlocking);

    private native final int native_read_in_direct_buffer(Object jBuffer, int sizeInBytes);
    private native final int native_read_in_direct_buffer(Object jBuffer,
            int sizeInBytes, boolean isBlocking);

    private native final int native_get_native_frame_count();