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

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

Merge "Add non-blocking writes to AudioTrack"

parents 601462f2 4aacc903
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -15001,7 +15001,9 @@ package android.media {
    method public int setVolume(float);
    method public void stop() throws java.lang.IllegalStateException;
    method public int write(byte[], int, int);
    method public int write(byte[], int, int, int);
    method public int write(short[], int, int);
    method public int write(short[], int, int, int);
    method public int write(float[], int, int, int);
    method public int write(java.nio.ByteBuffer, int, int);
    field public static final int ERROR = -1; // 0xffffffff
+2 −0
Original line number Diff line number Diff line
@@ -16213,7 +16213,9 @@ package android.media {
    method public int setVolume(float);
    method public void stop() throws java.lang.IllegalStateException;
    method public int write(byte[], int, int);
    method public int write(byte[], int, int, int);
    method public int write(short[], int, int);
    method public int write(short[], int, int, int);
    method public int write(float[], int, int, int);
    method public int write(java.nio.ByteBuffer, int, int);
    field public static final int ERROR = -1; // 0xffffffff
+71 −118
Original line number Diff line number Diff line
@@ -510,14 +510,47 @@ static void android_media_AudioTrack_finalize(JNIEnv *env, jobject thiz) {
    android_media_AudioTrack_release(env, thiz);
}

// overloaded JNI array helper functions (same as in android_media_AudioRecord)
static inline
jbyte *envGetArrayElements(JNIEnv *env, jbyteArray array, jboolean *isCopy) {
    return env->GetByteArrayElements(array, isCopy);
}

static inline
void envReleaseArrayElements(JNIEnv *env, jbyteArray array, jbyte *elems, jint mode) {
    env->ReleaseByteArrayElements(array, elems, mode);
}

static inline
jshort *envGetArrayElements(JNIEnv *env, jshortArray array, jboolean *isCopy) {
    return env->GetShortArrayElements(array, isCopy);
}

static inline
void envReleaseArrayElements(JNIEnv *env, jshortArray array, jshort *elems, jint mode) {
    env->ReleaseShortArrayElements(array, elems, mode);
}

static inline
jfloat *envGetArrayElements(JNIEnv *env, jfloatArray array, jboolean *isCopy) {
    return env->GetFloatArrayElements(array, isCopy);
}

static inline
void envReleaseArrayElements(JNIEnv *env, jfloatArray array, jfloat *elems, jint mode) {
    env->ReleaseFloatArrayElements(array, elems, mode);
}

// ----------------------------------------------------------------------------
jint writeToTrack(const sp<AudioTrack>& track, jint audioFormat, const jbyte* data,
                  jint offsetInBytes, jint sizeInBytes, bool blocking = true) {
template <typename T>
static jint writeToTrack(const sp<AudioTrack>& track, jint audioFormat, const T *data,
                         jint offsetInSamples, jint sizeInSamples, bool blocking) {
    // give the data to the native AudioTrack object (the data starts at the offset)
    ssize_t written = 0;
    // regular write() or copy the data to the AudioTrack's shared memory?
    size_t sizeInBytes = sizeInSamples * sizeof(T);
    if (track->sharedBuffer() == 0) {
        written = track->write(data + offsetInBytes, sizeInBytes, blocking);
        written = track->write(data + offsetInSamples, sizeInBytes, blocking);
        // for compatibility with earlier behavior of write(), return 0 in this case
        if (written == (ssize_t) WOULD_BLOCK) {
            written = 0;
@@ -527,55 +560,59 @@ jint writeToTrack(const sp<AudioTrack>& track, jint audioFormat, const jbyte* da
        if ((size_t)sizeInBytes > track->sharedBuffer()->size()) {
            sizeInBytes = track->sharedBuffer()->size();
        }
        memcpy(track->sharedBuffer()->pointer(), data + offsetInBytes, sizeInBytes);
        memcpy(track->sharedBuffer()->pointer(), data + offsetInSamples, sizeInBytes);
        written = sizeInBytes;
    }
    if (written > 0) {
        return written / sizeof(T);
    }
    // for compatibility, error codes pass through unchanged
    return written;
}

// ----------------------------------------------------------------------------
static jint android_media_AudioTrack_write_byte(JNIEnv *env,  jobject thiz,
                                                  jbyteArray javaAudioData,
                                                  jint offsetInBytes, jint sizeInBytes,
template <typename T>
static jint android_media_AudioTrack_writeArray(JNIEnv *env, jobject thiz,
                                                T javaAudioData,
                                                jint offsetInSamples, jint sizeInSamples,
                                                jint javaAudioFormat,
                                                jboolean isWriteBlocking) {
    //ALOGV("android_media_AudioTrack_write_byte(offset=%d, sizeInBytes=%d) called",
    //    offsetInBytes, sizeInBytes);
    //ALOGV("android_media_AudioTrack_writeArray(offset=%d, sizeInSamples=%d) called",
    //        offsetInSamples, sizeInSamples);
    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
    if (lpTrack == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve AudioTrack pointer for write()");
        return 0;
        return (jint)AUDIO_JAVA_INVALID_OPERATION;
    }

    if (javaAudioData == NULL) {
        ALOGE("NULL java array of audio data to play");
        return (jint)AUDIO_JAVA_BAD_VALUE;
    }

    // get the pointer for the audio data from the java array
    // 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)
    jbyte* cAudioData = NULL;
    if (javaAudioData) {
        cAudioData = (jbyte *)env->GetByteArrayElements(javaAudioData, NULL);

    // get the pointer for the audio data from the java array
    auto cAudioData = envGetArrayElements(env, javaAudioData, NULL);
    if (cAudioData == NULL) {
            ALOGE("Error retrieving source of audio data to play, can't play");
            return 0; // out of memory or no data to load
        }
    } else {
        ALOGE("NULL java array of audio data to play, can't play");
        return 0;
        ALOGE("Error retrieving source of audio data to play");
        return (jint)AUDIO_JAVA_BAD_VALUE; // out of memory or no data to load
    }

    jint written = writeToTrack(lpTrack, javaAudioFormat, cAudioData, offsetInBytes, sizeInBytes,
            isWriteBlocking == JNI_TRUE /* blocking */);
    jint samplesWritten = writeToTrack(lpTrack, javaAudioFormat, cAudioData,
            offsetInSamples, sizeInSamples, isWriteBlocking == JNI_TRUE /* blocking */);

    env->ReleaseByteArrayElements(javaAudioData, cAudioData, 0);
    envReleaseArrayElements(env, javaAudioData, cAudioData, 0);

    //ALOGV("write wrote %d (tried %d) bytes in the native AudioTrack with offset %d",
    //     (int)written, (int)(sizeInBytes), (int)offsetInBytes);
    return written;
    //ALOGV("write wrote %d (tried %d) samples in the native AudioTrack with offset %d",
    //        (int)samplesWritten, (int)(sizeInSamples), (int)offsetInSamples);
    return samplesWritten;
}


// ----------------------------------------------------------------------------
static jint android_media_AudioTrack_write_native_bytes(JNIEnv *env,  jobject thiz,
        jbyteArray javaBytes, jint byteOffset, jint sizeInBytes,
@@ -586,7 +623,7 @@ static jint android_media_AudioTrack_write_native_bytes(JNIEnv *env, jobject th
    if (lpTrack == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
                "Unable to retrieve AudioTrack pointer for write()");
        return 0;
        return (jint)AUDIO_JAVA_INVALID_OPERATION;
    }

    ScopedBytesRO bytes(env, javaBytes);
@@ -601,90 +638,6 @@ static jint android_media_AudioTrack_write_native_bytes(JNIEnv *env, jobject th
    return written;
}

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

    //ALOGV("android_media_AudioTrack_write_short(offset=%d, sizeInShorts=%d) called",
    //    offsetInShorts, sizeInShorts);
    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
    if (lpTrack == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve AudioTrack pointer for write()");
        return 0;
    }

    // get the pointer for the audio data from the java array
    // 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)
    jshort* cAudioData = NULL;
    if (javaAudioData) {
        cAudioData = (jshort *)env->GetShortArrayElements(javaAudioData, NULL);
        if (cAudioData == NULL) {
            ALOGE("Error retrieving source of audio data to play, can't play");
            return 0; // out of memory or no data to load
        }
    } else {
        ALOGE("NULL java array of audio data to play, can't play");
        return 0;
    }
    jint written = writeToTrack(lpTrack, javaAudioFormat, (jbyte *)cAudioData,
                                offsetInShorts * sizeof(short), sizeInShorts * sizeof(short),
            true /*blocking write, legacy behavior*/);
    env->ReleaseShortArrayElements(javaAudioData, cAudioData, 0);

    if (written > 0) {
        written /= sizeof(short);
    }
    //ALOGV("write wrote %d (tried %d) shorts in the native AudioTrack with offset %d",
    //     (int)written, (int)(sizeInShorts), (int)offsetInShorts);

    return written;
}


// ----------------------------------------------------------------------------
static jint android_media_AudioTrack_write_float(JNIEnv *env,  jobject thiz,
                                                  jfloatArray javaAudioData,
                                                  jint offsetInFloats, jint sizeInFloats,
                                                  jint javaAudioFormat,
                                                  jboolean isWriteBlocking) {

    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
    if (lpTrack == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve AudioTrack pointer for write()");
        return 0;
    }

    jfloat* cAudioData = NULL;
    if (javaAudioData) {
        cAudioData = (jfloat *)env->GetFloatArrayElements(javaAudioData, NULL);
        if (cAudioData == NULL) {
            ALOGE("Error retrieving source of audio data to play, can't play");
            return 0; // out of memory or no data to load
        }
    } else {
        ALOGE("NULL java array of audio data to play, can't play");
        return 0;
    }
    jint written = writeToTrack(lpTrack, javaAudioFormat, (jbyte *)cAudioData,
                                offsetInFloats * sizeof(float), sizeInFloats * sizeof(float),
                                isWriteBlocking == JNI_TRUE /* blocking */);
    env->ReleaseFloatArrayElements(javaAudioData, cAudioData, 0);

    if (written > 0) {
        written /= sizeof(float);
    }

    return written;
}


// ----------------------------------------------------------------------------
static jint android_media_AudioTrack_get_native_frame_count(JNIEnv *env,  jobject thiz) {
    sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
@@ -976,12 +929,12 @@ static JNINativeMethod gMethods[] = {
                                         (void *)android_media_AudioTrack_setup},
    {"native_finalize",      "()V",      (void *)android_media_AudioTrack_finalize},
    {"native_release",       "()V",      (void *)android_media_AudioTrack_release},
    {"native_write_byte",    "([BIIIZ)I",(void *)android_media_AudioTrack_write_byte},
    {"native_write_byte",    "([BIIIZ)I",(void *)android_media_AudioTrack_writeArray<jbyteArray>},
    {"native_write_native_bytes",
                             "(Ljava/lang/Object;IIIZ)I",
                                         (void *)android_media_AudioTrack_write_native_bytes},
    {"native_write_short",   "([SIII)I", (void *)android_media_AudioTrack_write_short},
    {"native_write_float",   "([FIIIZ)I",(void *)android_media_AudioTrack_write_float},
    {"native_write_short",   "([SIIIZ)I",(void *)android_media_AudioTrack_writeArray<jshortArray>},
    {"native_write_float",   "([FIIIZ)I",(void *)android_media_AudioTrack_writeArray<jfloatArray>},
    {"native_setVolume",     "(FF)V",    (void *)android_media_AudioTrack_set_volume},
    {"native_get_native_frame_count",
                             "()I",      (void *)android_media_AudioTrack_get_native_frame_count},
+89 −11
Original line number Diff line number Diff line
@@ -1540,6 +1540,8 @@ public class AudioTrack
    /**
     * Writes the audio data to the audio sink for playback (streaming mode),
     * or copies audio data for later playback (static buffer mode).
     * The format specified in the AudioTrack constructor should be
     * {@link AudioFormat#ENCODING_PCM_8BIT} to correspond to the data in the array.
     * In streaming mode, will block until all data has been written to the audio sink.
     * In static buffer mode, copies the data to the buffer starting at offset 0.
     * Note that the actual playback of this data might occur after this function
@@ -1556,13 +1558,49 @@ public class AudioTrack
     *    {@link AudioManager#ERROR_DEAD_OBJECT} if the AudioTrack is not valid anymore and
     *    needs to be recreated.
     */
    public int write(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes) {
        return write(audioData, offsetInBytes, sizeInBytes, WRITE_BLOCKING);
    }

    public int write(byte[] audioData, int offsetInBytes, int sizeInBytes) {
    /**
     * Writes the audio data to the audio sink for playback (streaming mode),
     * or copies audio data for later playback (static buffer mode).
     * The format specified in the AudioTrack constructor should be
     * {@link AudioFormat#ENCODING_PCM_8BIT} to correspond to the data in the array.
     * In streaming mode, will block until all data has been written to the audio sink.
     * In static buffer mode, copies the data to the buffer starting at offset 0.
     * Note that the actual playback of this data might occur after this function
     * returns. This function is thread safe with respect to {@link #stop} calls,
     * in which case all of the specified data might not be written to the audio sink.
     *
     * @param audioData the array that holds the data to play.
     * @param offsetInBytes the offset expressed in bytes in audioData where the data to play
     *    starts.
     * @param sizeInBytes the number of bytes to read in audioData after the offset.
     * @param writeMode one of {@link #WRITE_BLOCKING}, {@link #WRITE_NON_BLOCKING}. It has no
     *     effect in static mode.
     *     <br>With {@link #WRITE_BLOCKING}, the write will block until all data has been written
     *         to the audio sink.
     *     <br>With {@link #WRITE_NON_BLOCKING}, the write will return immediately after
     *     queuing as much audio data for playback as possible without blocking.
     * @return the number of bytes that were written 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, or
     *    {@link AudioManager#ERROR_DEAD_OBJECT} if the AudioTrack is not valid anymore and
     *    needs to be recreated.
     */
    public int write(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes,
            @WriteMode int writeMode) {

        if (mState == STATE_UNINITIALIZED || mAudioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
            return ERROR_INVALID_OPERATION;
        }

        if ((writeMode != WRITE_BLOCKING) && (writeMode != WRITE_NON_BLOCKING)) {
            Log.e(TAG, "AudioTrack.write() 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)) {
@@ -1570,7 +1608,7 @@ public class AudioTrack
        }

        int ret = native_write_byte(audioData, offsetInBytes, sizeInBytes, mAudioFormat,
                true /*isBlocking*/);
                writeMode == WRITE_BLOCKING);

        if ((mDataLoadMode == MODE_STATIC)
                && (mState == STATE_NO_STATIC_DATA)
@@ -1582,10 +1620,11 @@ public class AudioTrack
        return ret;
    }


    /**
     * Writes the audio data to the audio sink for playback (streaming mode),
     * or copies audio data for later playback (static buffer mode).
     * The format specified in the AudioTrack constructor should be
     * {@link AudioFormat#ENCODING_PCM_16BIT} to correspond to the data in the array.
     * In streaming mode, will block until all data has been written to the audio sink.
     * In static buffer mode, copies the data to the buffer starting at offset 0.
     * Note that the actual playback of this data might occur after this function
@@ -1602,20 +1641,57 @@ public class AudioTrack
     *    {@link AudioManager#ERROR_DEAD_OBJECT} if the AudioTrack is not valid anymore and
     *    needs to be recreated.
     */
    public int write(@NonNull short[] audioData, int offsetInShorts, int sizeInShorts) {
        return write(audioData, offsetInShorts, sizeInShorts, WRITE_BLOCKING);
    }

    public int write(short[] audioData, int offsetInShorts, int sizeInShorts) {
    /**
     * Writes the audio data to the audio sink for playback (streaming mode),
     * or copies audio data for later playback (static buffer mode).
     * The format specified in the AudioTrack constructor should be
     * {@link AudioFormat#ENCODING_PCM_16BIT} to correspond to the data in the array.
     * In streaming mode, will block until all data has been written to the audio sink.
     * In static buffer mode, copies the data to the buffer starting at offset 0.
     * Note that the actual playback of this data might occur after this function
     * returns. This function is thread safe with respect to {@link #stop} calls,
     * in which case all of the specified data might not be written to the audio sink.
     *
     * @param audioData the array that holds the data to play.
     * @param offsetInShorts the offset expressed in shorts in audioData where the data to play
     *     starts.
     * @param sizeInShorts the number of shorts to read in audioData after the offset.
     * @param writeMode one of {@link #WRITE_BLOCKING}, {@link #WRITE_NON_BLOCKING}. It has no
     *     effect in static mode.
     *     <br>With {@link #WRITE_BLOCKING}, the write will block until all data has been written
     *         to the audio sink.
     *     <br>With {@link #WRITE_NON_BLOCKING}, the write will return immediately after
     *     queuing as much audio data for playback as possible without blocking.
     * @return the number of shorts that were written 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, or
     *    {@link AudioManager#ERROR_DEAD_OBJECT} if the AudioTrack is not valid anymore and
     *    needs to be recreated.
     */
    public int write(@NonNull short[] audioData, int offsetInShorts, int sizeInShorts,
            @WriteMode int writeMode) {

        if (mState == STATE_UNINITIALIZED || mAudioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
            return ERROR_INVALID_OPERATION;
        }

        if ((writeMode != WRITE_BLOCKING) && (writeMode != WRITE_NON_BLOCKING)) {
            Log.e(TAG, "AudioTrack.write() 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;
        }

        int ret = native_write_short(audioData, offsetInShorts, sizeInShorts, mAudioFormat);
        int ret = native_write_short(audioData, offsetInShorts, sizeInShorts, mAudioFormat,
                writeMode == WRITE_BLOCKING);

        if ((mDataLoadMode == MODE_STATIC)
                && (mState == STATE_NO_STATIC_DATA)
@@ -1627,10 +1703,11 @@ public class AudioTrack
        return ret;
    }


    /**
     * Writes the audio data to the audio sink for playback (streaming mode),
     * or copies audio data for later playback (static buffer mode).
     * The format specified in the AudioTrack constructor should be
     * {@link AudioFormat#ENCODING_PCM_FLOAT} to correspond to the data in the array.
     * In static buffer mode, copies the data to the buffer starting at offset 0,
     * and the write mode is ignored.
     * In streaming mode, the blocking behavior will depend on the write mode.
@@ -1654,9 +1731,9 @@ public class AudioTrack
     * @param sizeInFloats the number of floats to read in audioData after the offset.
     * @param writeMode one of {@link #WRITE_BLOCKING}, {@link #WRITE_NON_BLOCKING}. It has no
     *     effect in static mode.
     *     <BR>With {@link #WRITE_BLOCKING}, the write will block until all data has been written
     *     <br>With {@link #WRITE_BLOCKING}, the write will block until all data has been written
     *         to the audio sink.
     *     <BR>With {@link #WRITE_NON_BLOCKING}, the write will return immediately after
     *     <br>With {@link #WRITE_NON_BLOCKING}, the write will return immediately after
     *     queuing as much audio data for playback as possible without blocking.
     * @return the number of floats that were written, or {@link #ERROR_INVALID_OPERATION}
     *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
@@ -1664,7 +1741,7 @@ public class AudioTrack
     *    {@link AudioManager#ERROR_DEAD_OBJECT} if the AudioTrack is not valid anymore and
     *    needs to be recreated.
     */
    public int write(float[] audioData, int offsetInFloats, int sizeInFloats,
    public int write(@NonNull float[] audioData, int offsetInFloats, int sizeInFloats,
            @WriteMode int writeMode) {

        if (mState == STATE_UNINITIALIZED) {
@@ -1727,7 +1804,7 @@ public class AudioTrack
     *     {@link AudioManager#ERROR_DEAD_OBJECT} if the AudioTrack is not valid anymore and
     *     needs to be recreated.
     */
    public int write(ByteBuffer audioData, int sizeInBytes,
    public int write(@NonNull ByteBuffer audioData, int sizeInBytes,
            @WriteMode int writeMode) {

        if (mState == STATE_UNINITIALIZED) {
@@ -2017,7 +2094,8 @@ public class AudioTrack
                                               boolean isBlocking);

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

    private native final int native_write_float(float[] audioData,
                                                int offsetInFloats, int sizeInFloats, int format,