]> git.sesse.net Git - vlc/blobdiff - modules/audio_output/audiotrack.c
audiotrack: finish float support
[vlc] / modules / audio_output / audiotrack.c
index 3ee1fcf5eee49dd1232c6317f2cbf58f15955b2a..ab398c9326d53d6ae7b721be304515b8d9fe5295 100644 (file)
@@ -58,6 +58,8 @@ struct aout_sys_t {
     jobject p_audioTimestamp; /* AudioTimestamp ref */
     jbyteArray p_bytearray; /* ByteArray ref (for Write) */
     size_t i_bytearray_size; /* size of the ByteArray */
+    jfloatArray p_floatarray; /* FloatArray ref (for WriteFloat) */
+    size_t i_floatarray_size; /* size of the FloatArray */
     jobject p_bytebuffer; /* ByteBuffer ref (for WriteV21) */
     audio_sample_format_t fmt; /* fmt setup by Start */
     uint32_t i_pos_initial; /* initial position set by getPlaybackHeadPosition */
@@ -69,6 +71,11 @@ struct aout_sys_t {
     int i_audiotrack_stuck_count;
     uint8_t i_chans_to_reorder; /* do we need channel reordering */
     uint8_t p_chan_table[AOUT_CHAN_MAX];
+    enum {
+        WRITE,
+        WRITE_V21,
+        WRITE_FLOAT
+    } i_write_type;
 
     /* JNIThread control */
     vlc_mutex_t mutex;
@@ -84,7 +91,7 @@ struct aout_sys_t {
 /* Soft volume helper */
 #include "audio_output/volume.h"
 
-//#define AUDIOTRACK_USE_FLOAT
+#define AUDIOTRACK_USE_FLOAT
 // TODO: activate getTimestamp for new android versions
 //#define AUDIOTRACK_USE_TIMESTAMP
 
@@ -158,6 +165,7 @@ static struct
         jmethodID pause;
         jmethodID write;
         jmethodID writeV21;
+        jmethodID writeFloat;
         jmethodID getPlaybackHeadPosition;
         jmethodID getTimestamp;
         jmethodID getMinBufferSize;
@@ -275,8 +283,10 @@ InitJNIFields( audio_output_t *p_aout )
     GET_ID( GetMethodID, AudioTrack.writeV21, "write", "(Ljava/nio/ByteBuffer;II)I", false );
     if( jfields.AudioTrack.writeV21 )
     {
-        jfields.AudioTrack.write = NULL;
         GET_CONST_INT( AudioTrack.WRITE_NON_BLOCKING, "WRITE_NON_BLOCKING", true );
+#ifdef AUDIOTRACK_USE_FLOAT
+        GET_ID( GetMethodID, AudioTrack.writeFloat, "write", "([FIII)I", true );
+#endif
     } else
         GET_ID( GetMethodID, AudioTrack.write, "write", "([BII)I", true );
 
@@ -307,12 +317,6 @@ InitJNIFields( audio_output_t *p_aout )
                 "framePosition", "J", true );
         GET_ID( GetFieldID, AudioTimestamp.nanoTime,
                 "nanoTime", "J", true );
-    } else
-    {
-        jfields.AudioTimestamp.clazz = NULL;
-        jfields.AudioTimestamp.ctor = NULL;
-        jfields.AudioTimestamp.framePosition = NULL;
-        jfields.AudioTimestamp.nanoTime = NULL;
     }
 
     /* AudioFormat class init */
@@ -322,7 +326,8 @@ InitJNIFields( audio_output_t *p_aout )
 #ifdef AUDIOTRACK_USE_FLOAT
     GET_CONST_INT( AudioFormat.ENCODING_PCM_FLOAT, "ENCODING_PCM_FLOAT",
                    false );
-    jfields.AudioFormat.has_ENCODING_PCM_FLOAT = field != NULL;
+    jfields.AudioFormat.has_ENCODING_PCM_FLOAT = field != NULL &&
+                                                 jfields.AudioTrack.writeFloat;
 #else
     jfields.AudioFormat.has_ENCODING_PCM_FLOAT = false;
 #endif
@@ -800,6 +805,21 @@ JNIThread_Start( JNIEnv *env, audio_output_t *p_aout )
     }
 #endif
 
+    if( p_sys->fmt.i_format == VLC_CODEC_FL32 )
+    {
+        msg_Dbg( p_aout, "using WRITE_FLOAT");
+        p_sys->i_write_type = WRITE_FLOAT;
+    }
+    else if( jfields.AudioTrack.writeV21 )
+        msg_Dbg( p_aout, "using WRITE_V21");
+        p_sys->i_write_type = WRITE_V21;
+    }
+    else
+    {
+        msg_Dbg( p_aout, "using WRITE");
+        p_sys->i_write_type = WRITE;
+    }
+
     JNI_AT_CALL_VOID( play );
     CHECK_AT_EXCEPTION( "play" );
     p_sys->i_play_time = mdate();
@@ -840,15 +860,16 @@ JNIThread_Stop( JNIEnv *env, audio_output_t *p_aout )
  * that we won't wait in AudioTrack.write() method
  */
 static int
-JNIThread_Write( JNIEnv *env, audio_output_t *p_aout, block_t *p_buffer )
+JNIThread_Write( JNIEnv *env, audio_output_t *p_aout, block_t *p_buffer,
+                 size_t i_buffer_offset )
 {
     aout_sys_t *p_sys = p_aout->sys;
-    uint8_t *p_data = p_buffer->p_buffer;
     size_t i_data;
     uint32_t i_samples;
     uint32_t i_audiotrack_pos;
     uint32_t i_samples_pending;
 
+    i_data = p_buffer->i_buffer - i_buffer_offset;
     i_audiotrack_pos = JNIThread_GetAudioTrackPos( env, p_aout );
     if( i_audiotrack_pos > p_sys->i_samples_written )
     {
@@ -880,37 +901,12 @@ JNIThread_Write( JNIEnv *env, audio_output_t *p_aout, block_t *p_buffer )
     } else
         p_sys->i_audiotrack_stuck_count = 0;
     i_samples = __MIN( p_sys->i_max_audiotrack_samples - i_samples_pending,
-                       p_buffer->i_nb_samples );
+                       BYTES_TO_FRAMES( i_data ) );
 
     i_data = i_samples * p_sys->i_bytes_per_frame;
 
-    /* check if we need to realloc a ByteArray */
-    if( i_data > p_sys->i_bytearray_size )
-    {
-        jbyteArray p_bytearray;
-
-        if( p_sys->p_bytearray )
-        {
-            (*env)->DeleteGlobalRef( env, p_sys->p_bytearray );
-            p_sys->p_bytearray = NULL;
-        }
-
-        p_bytearray = (*env)->NewByteArray( env, i_data );
-        if( p_bytearray )
-        {
-            p_sys->p_bytearray = (*env)->NewGlobalRef( env, p_bytearray );
-            (*env)->DeleteLocalRef( env, p_bytearray );
-        }
-        p_sys->i_bytearray_size = i_data;
-    }
-    if( !p_sys->p_bytearray )
-        return jfields.AudioTrack.ERROR_BAD_VALUE;
-
-    /* copy p_buffer in to ByteArray */
-    (*env)->SetByteArrayRegion( env, p_sys->p_bytearray, 0, i_data,
-                                (jbyte *)p_data);
-
-    return JNI_AT_CALL_INT( write, p_sys->p_bytearray, 0, i_data );
+    return JNI_AT_CALL_INT( write, p_sys->p_bytearray,
+                            i_buffer_offset, i_data );
 }
 
 /**
@@ -918,17 +914,19 @@ JNIThread_Write( JNIEnv *env, audio_output_t *p_aout, block_t *p_buffer )
  * It calls a new write method with WRITE_NON_BLOCKING flags.
  */
 static int
-JNIThread_WriteV21( JNIEnv *env, audio_output_t *p_aout, block_t *p_buffer )
+JNIThread_WriteV21( JNIEnv *env, audio_output_t *p_aout, block_t *p_buffer,
+                    size_t i_buffer_offset )
 {
     aout_sys_t *p_sys = p_aout->sys;
     int i_ret;
+    size_t i_data = p_buffer->i_buffer - i_buffer_offset;
+    uint8_t *p_data = p_buffer->p_buffer + i_buffer_offset;
 
     if( !p_sys->p_bytebuffer )
     {
         jobject p_bytebuffer;
 
-        p_bytebuffer = (*env)->NewDirectByteBuffer( env, p_buffer->p_buffer,
-                                                    p_buffer->i_buffer );
+        p_bytebuffer = (*env)->NewDirectByteBuffer( env, p_data, i_data );
         if( !p_bytebuffer )
             return jfields.AudioTrack.ERROR_BAD_VALUE;
 
@@ -943,7 +941,7 @@ JNIThread_WriteV21( JNIEnv *env, audio_output_t *p_aout, block_t *p_buffer )
         }
     }
 
-    i_ret = JNI_AT_CALL_INT( writeV21, p_sys->p_bytebuffer, p_buffer->i_buffer,
+    i_ret = JNI_AT_CALL_INT( writeV21, p_sys->p_bytebuffer, i_data,
                              jfields.AudioTrack.WRITE_NON_BLOCKING );
     if( i_ret > 0 )
     {
@@ -955,18 +953,131 @@ JNIThread_WriteV21( JNIEnv *env, audio_output_t *p_aout, block_t *p_buffer )
     return i_ret;
 }
 
+/**
+ * Non blocking write float function for Lollipop and after.
+ * It calls a new write method with WRITE_NON_BLOCKING flags.
+ */
 static int
-JNIThread_Play( JNIEnv *env, audio_output_t *p_aout,
-                block_t **pp_buffer, mtime_t *p_wait )
+JNIThread_WriteFloat( JNIEnv *env, audio_output_t *p_aout, block_t *p_buffer,
+                      size_t i_buffer_offset )
 {
     aout_sys_t *p_sys = p_aout->sys;
-    block_t *p_buffer = *pp_buffer;
     int i_ret;
+    size_t i_data;
 
-    if( jfields.AudioTrack.writeV21 )
-        i_ret = JNIThread_WriteV21( env, p_aout, p_buffer );
+    i_buffer_offset /= 4;
+    i_data = p_buffer->i_buffer / 4 - i_buffer_offset;
+
+    i_ret = JNI_AT_CALL_INT( writeFloat, p_sys->p_floatarray,
+                             i_buffer_offset, i_data,
+                             jfields.AudioTrack.WRITE_NON_BLOCKING );
+    if( i_ret < 0 )
+        return i_ret;
     else
-        i_ret = JNIThread_Write( env, p_aout, p_buffer );
+        return i_ret * 4;
+}
+
+static int
+JNIThread_PreparePlay( JNIEnv *env, audio_output_t *p_aout,
+                       block_t *p_buffer )
+{
+    aout_sys_t *p_sys = p_aout->sys;
+
+    if( p_sys->i_chans_to_reorder )
+       aout_ChannelReorder( p_buffer->p_buffer, p_buffer->i_buffer,
+                            p_sys->i_chans_to_reorder, p_sys->p_chan_table,
+                            p_sys->fmt.i_format );
+
+    switch( p_sys->i_write_type )
+    {
+    case WRITE:
+        /* check if we need to realloc a ByteArray */
+        if( p_buffer->i_buffer > p_sys->i_bytearray_size )
+        {
+            jbyteArray p_bytearray;
+
+            if( p_sys->p_bytearray )
+            {
+                (*env)->DeleteGlobalRef( env, p_sys->p_bytearray );
+                p_sys->p_bytearray = NULL;
+            }
+
+            p_bytearray = (*env)->NewByteArray( env, p_buffer->i_buffer );
+            if( p_bytearray )
+            {
+                p_sys->p_bytearray = (*env)->NewGlobalRef( env, p_bytearray );
+                (*env)->DeleteLocalRef( env, p_bytearray );
+            }
+            p_sys->i_bytearray_size = p_buffer->i_buffer;
+        }
+        if( !p_sys->p_bytearray )
+            return VLC_EGENERIC;
+
+        /* copy p_buffer in to ByteArray */
+        (*env)->SetByteArrayRegion( env, p_sys->p_bytearray, 0,
+                                    p_buffer->i_buffer,
+                                    (jbyte *)p_buffer->p_buffer);
+        break;
+    case WRITE_FLOAT:
+    {
+        size_t i_data = p_buffer->i_buffer / 4;
+
+        /* check if we need to realloc a floatArray */
+        if( i_data > p_sys->i_floatarray_size )
+        {
+            jfloatArray p_floatarray;
+
+            if( p_sys->p_floatarray )
+            {
+                (*env)->DeleteGlobalRef( env, p_sys->p_floatarray );
+                p_sys->p_floatarray = NULL;
+            }
+
+            p_floatarray = (*env)->NewFloatArray( env, i_data );
+            if( p_floatarray )
+            {
+                p_sys->p_floatarray = (*env)->NewGlobalRef( env, p_floatarray );
+                (*env)->DeleteLocalRef( env, p_floatarray );
+            }
+            p_sys->i_floatarray_size = i_data;
+        }
+        if( !p_sys->p_floatarray )
+            return VLC_EGENERIC;
+
+        /* copy p_buffer in to FloatArray */
+        (*env)->SetFloatArrayRegion( env, p_sys->p_floatarray, 0, i_data,
+                                    (jfloat *)p_buffer->p_buffer);
+
+        break;
+    }
+    case WRITE_V21:
+        break;
+    }
+
+    return VLC_SUCCESS;
+}
+
+static int
+JNIThread_Play( JNIEnv *env, audio_output_t *p_aout,
+                block_t *p_buffer, size_t *p_buffer_offset, mtime_t *p_wait )
+{
+    aout_sys_t *p_sys = p_aout->sys;
+    int i_ret;
+
+    switch( p_sys->i_write_type )
+    {
+    case WRITE_V21:
+        i_ret = JNIThread_WriteV21( env, p_aout, p_buffer, *p_buffer_offset );
+        break;
+    case WRITE:
+        i_ret = JNIThread_Write( env, p_aout, p_buffer, *p_buffer_offset );
+        break;
+    case WRITE_FLOAT:
+        i_ret = JNIThread_WriteFloat( env, p_aout, p_buffer, *p_buffer_offset );
+        break;
+    default:
+        vlc_assert_unreachable();
+    }
 
     if( i_ret < 0 ) {
         if( jfields.AudioManager.has_ERROR_DEAD_OBJECT
@@ -998,11 +1109,7 @@ JNIThread_Play( JNIEnv *env, audio_output_t *p_aout,
         p_sys->i_samples_queued -= i_samples;
         p_sys->i_samples_written += i_samples;
 
-        p_buffer->p_buffer += i_ret;
-        p_buffer->i_buffer -= i_ret;
-        p_buffer->i_nb_samples -= i_samples;
-        if( p_buffer->i_buffer == 0 )
-            *pp_buffer = NULL;
+        *p_buffer_offset += i_ret;
 
         /* HACK: There is a known issue in audiotrack, "due to an internal
          * timeout within the AudioTrackThread". It happens after android
@@ -1011,7 +1118,7 @@ JNIThread_Play( JNIEnv *env, audio_output_t *p_aout,
          * write. This hack is done only for API 19 (AudioTimestamp was added
          * in API 19). */
 
-        if( jfields.AudioTimestamp.clazz && !jfields.AudioTrack.writeV21 )
+        if( p_sys->i_write_type == WRITE && jfields.AudioTimestamp.clazz )
             *p_wait = FRAMES_TO_US( i_samples ) / 2;
     }
     return i_ret >= 0 ? VLC_SUCCESS : VLC_EGENERIC;
@@ -1085,6 +1192,7 @@ JNIThread( void *data )
     bool b_error = false;
     bool b_paused = false;
     block_t *p_buffer = NULL;
+    size_t i_buffer_offset = 0;
     mtime_t i_play_deadline = 0;
     JNIEnv* env;
 
@@ -1162,18 +1270,25 @@ JNIThread( void *data )
                 if( p_buffer == NULL )
                 {
                     p_buffer = p_cmd->in.play.p_buffer;
-                    if( p_sys->i_chans_to_reorder )
-                       aout_ChannelReorder( p_buffer->p_buffer,
-                                            p_buffer->i_buffer,
-                                            p_sys->i_chans_to_reorder,
-                                            p_sys->p_chan_table,
-                                            p_sys->fmt.i_format );
-
+                    b_error = JNIThread_PreparePlay( env, p_aout, p_buffer )
+                              != VLC_SUCCESS;
                 }
-                b_error = JNIThread_Play( env, p_aout, &p_buffer,
+                if( b_error )
+                    break;
+                b_error = JNIThread_Play( env, p_aout, p_buffer,
+                                          &i_buffer_offset,
                                           &i_play_wait ) != VLC_SUCCESS;
-                if( p_buffer != NULL )
+                if( i_buffer_offset < p_buffer->i_buffer )
+                {
+                    /* buffer is not fully processed, try again with the
+                     * same cmd and buffer */
                     b_remove_cmd = false;
+                }
+                else
+                {
+                    p_buffer = NULL;
+                    i_buffer_offset = 0;
+                }
                 if( i_play_wait > 0 )
                     i_play_deadline = mdate() + i_play_wait;
                 break;
@@ -1190,7 +1305,10 @@ JNIThread( void *data )
             case CMD_TIME_GET:
                 assert( p_sys->p_audiotrack );
                 if( b_error )
+                {
+                    p_cmd->out.time_get.i_ret = -1;
                     break;
+                }
                 p_cmd->out.time_get.i_ret =
                         JNIThread_TimeGet( env, p_aout,
                                            &p_cmd->out.time_get.i_delay );
@@ -1209,6 +1327,8 @@ JNIThread( void *data )
         }
         if( p_sys->b_audiotrack_exception )
             b_error = true;
+        if( b_error )
+            p_sys->i_samples_queued = 0;
 
         if( b_remove_cmd )
         {
@@ -1226,6 +1346,8 @@ end:
     {
         if( p_sys->p_bytearray )
             (*env)->DeleteGlobalRef( env, p_sys->p_bytearray );
+        if( p_sys->p_floatarray )
+            (*env)->DeleteGlobalRef( env, p_sys->p_floatarray );
         if( p_sys->p_bytebuffer )
             (*env)->DeleteGlobalRef( env, p_sys->p_bytebuffer );
         jni_detach_thread();