OSDN Git Service

Add hidden support for ENCODING_PCM_FLOAT to AudioTrack
authorGlenn Kasten <gkasten@google.com>
Thu, 1 May 2014 01:03:15 +0000 (18:03 -0700)
committerGlenn Kasten <gkasten@google.com>
Thu, 8 May 2014 23:11:42 +0000 (16:11 -0700)
Change-Id: I15e6d6ab34846366e7f5cbf948fd3cb327e72632

core/jni/android_media_AudioTrack.cpp
media/java/android/media/AudioTrack.java

index da75275..463a0a8 100644 (file)
@@ -526,12 +526,15 @@ jint writeToTrack(const sp<AudioTrack>& track, jint audioFormat, const jbyte* da
         switch (format) {
 
         default:
-            // TODO Currently the only possible values for format are AUDIO_FORMAT_PCM_16_BIT
-            // and AUDIO_FORMAT_PCM_8_BIT, due to the limited set of values for audioFormat.
+            // TODO Currently the only possible values for format are AUDIO_FORMAT_PCM_16_BIT,
+            // AUDIO_FORMAT_PCM_8_BIT, and AUDIO_FORMAT_PCM_FLOAT,
+            // due to the limited set of values for audioFormat.
             // The next section of the switch will probably work for more formats, but it has only
-            // been tested for AUDIO_FORMAT_PCM_16_BIT, so that's why the "default" case fails.
+            // been tested for AUDIO_FORMAT_PCM_16_BIT and AUDIO_FORMAT_PCM_FLOAT,
+            // so that's why the "default" case fails.
             break;
 
+        case AUDIO_FORMAT_PCM_FLOAT:
         case AUDIO_FORMAT_PCM_16_BIT: {
             // writing to shared memory, check for capacity
             if ((size_t)sizeInBytes > track->sharedBuffer()->size()) {
@@ -679,6 +682,44 @@ static jint android_media_AudioTrack_write_short(JNIEnv *env,  jobject thiz,
 
 
 // ----------------------------------------------------------------------------
+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) {
@@ -963,6 +1004,7 @@ static JNINativeMethod gMethods[] = {
                              "(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_setVolume",     "(FF)V",    (void *)android_media_AudioTrack_set_volume},
     {"native_get_native_frame_count",
                              "()I",      (void *)android_media_AudioTrack_get_native_frame_count},
index 007eb40..dab6eed 100644 (file)
@@ -72,6 +72,7 @@ import com.android.internal.app.IAppOpsService;
  *
  * AudioTrack is not final and thus permits subclasses, but such use is not recommended.
  */
+// add {@link #write(float[], int, int)} when @hide removed
 public class AudioTrack
 {
     //---------------------------------------------------------
@@ -245,6 +246,7 @@ public class AudioTrack
      * @see AudioFormat#ENCODING_PCM_8BIT
      * @see AudioFormat#ENCODING_PCM_16BIT
      */
+    // add @see AudioFormat#ENCODING_PCM_FLOAT when @hide removed
     private int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
     /**
      * Audio session ID
@@ -300,6 +302,7 @@ public class AudioTrack
      * @param mode streaming or static buffer. See {@link #MODE_STATIC} and {@link #MODE_STREAM}
      * @throws java.lang.IllegalArgumentException
      */
+    // add {@link AudioFormat#ENCODING_PCM_FLOAT} to audioFormat section above, when @hide removed
     public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,
             int bufferSizeInBytes, int mode)
     throws IllegalArgumentException {
@@ -341,6 +344,7 @@ public class AudioTrack
      * @param sessionId Id of audio session the AudioTrack must be attached to
      * @throws java.lang.IllegalArgumentException
      */
+    // add {@link AudioFormat#ENCODING_PCM_FLOAT} to audioFormat section above, when @hide removed
     public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,
             int bufferSizeInBytes, int mode, int sessionId)
     throws IllegalArgumentException {
@@ -459,11 +463,14 @@ public class AudioTrack
             break;
         case AudioFormat.ENCODING_PCM_16BIT:
         case AudioFormat.ENCODING_PCM_8BIT:
+        case AudioFormat.ENCODING_PCM_FLOAT:
             mAudioFormat = audioFormat;
             break;
         default:
             throw new IllegalArgumentException("Unsupported sample encoding."
-                + " Should be ENCODING_PCM_8BIT or ENCODING_PCM_16BIT.");
+                + " Should be ENCODING_PCM_8BIT or ENCODING_PCM_16BIT"
+             // + " or ENCODING_PCM_FLOAT" when @hide removed
+                + ".");
         }
 
         //--------------
@@ -728,6 +735,7 @@ public class AudioTrack
      *   or {@link #ERROR} if unable to query for output properties,
      *   or the minimum buffer size expressed in bytes.
      */
+    // add {@link AudioFormat#ENCODING_PCM_FLOAT} to audioFormat section above, when @hide removed
     static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) {
         int channelCount = 0;
         switch(channelConfig) {
@@ -750,7 +758,8 @@ public class AudioTrack
         }
 
         if ((audioFormat != AudioFormat.ENCODING_PCM_16BIT)
-            && (audioFormat != AudioFormat.ENCODING_PCM_8BIT)) {
+            && (audioFormat != AudioFormat.ENCODING_PCM_8BIT)
+            && (audioFormat != AudioFormat.ENCODING_PCM_FLOAT)) {
             loge("getMinBufferSize(): Invalid audio format.");
             return ERROR_BAD_VALUE;
         }
@@ -1150,7 +1159,7 @@ public class AudioTrack
 
     public int write(byte[] audioData, int offsetInBytes, int sizeInBytes) {
 
-        if (mState == STATE_UNINITIALIZED) {
+        if (mState == STATE_UNINITIALIZED || mAudioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
             return ERROR_INVALID_OPERATION;
         }
 
@@ -1188,13 +1197,13 @@ public class AudioTrack
      *     starts.
      * @param sizeInShorts the number of shorts to read in audioData after the offset.
      * @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.
+     *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
+     *    the parameters don't resolve to valid data and indexes.
      */
 
     public int write(short[] audioData, int offsetInShorts, int sizeInShorts) {
 
-        if (mState == STATE_UNINITIALIZED) {
+        if (mState == STATE_UNINITIALIZED || mAudioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
             return ERROR_INVALID_OPERATION;
         }
 
@@ -1220,6 +1229,80 @@ 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).
+     * 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.
+     * <p>
+     * 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.
+     * <p>
+     * @param audioData the array that holds the data to play.
+     *     The implementation does not clip for sample values within the nominal range
+     *     [-1.0f, 1.0f], provided that all gains in the audio pipeline are
+     *     less than or equal to unity (1.0f), and in the absence of post-processing effects
+     *     that could add energy, such as reverb.  For the convenience of applications
+     *     that compute samples using filters with non-unity gain,
+     *     sample values +3 dB beyond the nominal range are permitted.
+     *     However such values may eventually be limited or clipped, depending on various gains
+     *     and later processing in the audio path.  Therefore applications are encouraged
+     *     to provide samples values within the nominal range.
+     * @param offsetInFloats the offset, expressed as a number of floats,
+     *     in audioData where the data to play starts.
+     * @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
+     *         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 floats 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.
+     * @hide candidate for public API
+     */
+    public int write(float[] audioData, int offsetInFloats, int sizeInFloats,
+            @WriteMode int writeMode) {
+
+        if (mState == STATE_UNINITIALIZED) {
+            Log.e(TAG, "AudioTrack.write() called in invalid state STATE_UNINITIALIZED");
+            return ERROR_INVALID_OPERATION;
+        }
+
+        if (mAudioFormat != AudioFormat.ENCODING_PCM_FLOAT) {
+            Log.e(TAG, "AudioTrack.write(float[] ...) requires format 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) || (offsetInFloats < 0 ) || (sizeInFloats < 0)
+                || (offsetInFloats + sizeInFloats < 0)  // detect integer overflow
+                || (offsetInFloats + sizeInFloats > audioData.length)) {
+            Log.e(TAG, "AudioTrack.write() called with invalid array, offset, or size");
+            return ERROR_BAD_VALUE;
+        }
+
+        int ret = native_write_float(audioData, offsetInFloats, sizeInFloats, mAudioFormat,
+                writeMode == WRITE_BLOCKING);
+
+        if ((mDataLoadMode == MODE_STATIC)
+                && (mState == STATE_NO_STATIC_DATA)
+                && (ret > 0)) {
+            // benign race with respect to other APIs that read mState
+            mState = STATE_INITIALIZED;
+        }
+
+        return ret;
+    }
+
+
+    /**
+     * Writes the audio data to the audio sink for playback (streaming mode),
+     * or copies audio data for later playback (static buffer mode).
      * In static buffer mode, copies the data to the buffer starting at its 0 offset, and the write
      * mode is ignored.
      * In streaming mode, the blocking behavior will depend on the write mode.
@@ -1247,6 +1330,11 @@ public class AudioTrack
             return ERROR_INVALID_OPERATION;
         }
 
+        if (mAudioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
+            Log.e(TAG, "AudioTrack.write(ByteBuffer ...) not yet supported for 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;
@@ -1487,6 +1575,10 @@ public class AudioTrack
     private native final int native_write_short(short[] audioData,
                                                 int offsetInShorts, int sizeInShorts, int format);
 
+    private native final int native_write_float(float[] audioData,
+                                                int offsetInFloats, int sizeInFloats, int format,
+                                                boolean isBlocking);
+
     private native final int native_write_native_bytes(Object audioData,
             int positionInBytes, int sizeInBytes, int format, boolean blocking);