summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt2
-rw-r--r--api/system-current.txt2
-rw-r--r--core/jni/android_media_AudioTrack.cpp189
-rw-r--r--media/java/android/media/AudioTrack.java100
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,