diff options
-rw-r--r-- | api/current.txt | 2 | ||||
-rw-r--r-- | api/system-current.txt | 2 | ||||
-rw-r--r-- | core/jni/android_media_AudioTrack.cpp | 189 | ||||
-rw-r--r-- | media/java/android/media/AudioTrack.java | 100 |
4 files changed, 164 insertions, 129 deletions
diff --git a/api/current.txt b/api/current.txt index 1ce0f57..7016cf9 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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 diff --git a/api/system-current.txt b/api/system-current.txt index 322a04d..34a9905 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -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 diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index 610c7ed..8d3a9aa 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -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, - jint javaAudioFormat, - jboolean isWriteBlocking) { - //ALOGV("android_media_AudioTrack_write_byte(offset=%d, sizeInBytes=%d) called", - // offsetInBytes, 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_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); - 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; + + // 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"); + 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); @@ -602,90 +639,6 @@ static jint android_media_AudioTrack_write_native_bytes(JNIEnv *env, jobject th } // ---------------------------------------------------------------------------- -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); if (lpTrack == NULL) { @@ -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}, diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 6c41a2a..44455fa 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -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, |