Merge "Add non-blocking writes to AudioTrack"

This commit is contained in:
Andy Hung
2015-04-17 20:38:23 +00:00
committed by Android (Google) Code Review
4 changed files with 164 additions and 129 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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);
@@ -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},

View File

@@ -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,