Bug 1017438 - Use SL_PLAYEVENT_HEADATMARKER event to trigger CUBEB_STATE_DRAINED state callback. r=kinetik, a=lmandel
authorBruce Sun <brsun@mozilla.com>
Mon, 30 Jun 2014 11:30:35 +0800
changeset 228575 4b5aae0fe6a1a228cee816ed232620ae3b8f6166
parent 228574 8ecb4e7dc8b71afb0c2a4d6fdaee13c29b0d7899
child 228576 2c0abc52701804ddc88a5757f6c251a60eabbdf7
push id6
push userryanvm@gmail.com
push dateMon, 12 Jan 2015 22:04:06 +0000
treeherdermozilla-b2g37_v2_2@895c8fc7b734 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik, lmandel
bugs1017438
milestone32.0a2
Bug 1017438 - Use SL_PLAYEVENT_HEADATMARKER event to trigger CUBEB_STATE_DRAINED state callback. r=kinetik, a=lmandel
media/libcubeb/src/cubeb_opensl.c
--- a/media/libcubeb/src/cubeb_opensl.c
+++ b/media/libcubeb/src/cubeb_opensl.c
@@ -3,16 +3,17 @@
  *
  * This program is made available under an ISC-style license.  See the
  * accompanying file LICENSE for details.
  */
 #undef NDEBUG
 #include <assert.h>
 #include <dlfcn.h>
 #include <stdlib.h>
+#include <pthread.h>
 #include <SLES/OpenSLES.h>
 #if defined(__ANDROID__)
 #include "android/sles_definitions.h"
 #include <SLES/OpenSLES_Android.h>
 #include <android/log.h>
 #define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL" , ## args)
 #endif
 #include "cubeb/cubeb.h"
@@ -36,70 +37,101 @@ struct cubeb {
 };
 
 #define NELEMS(A) (sizeof(A) / sizeof A[0])
 #define NBUFS 4
 #define AUDIO_STREAM_TYPE_MUSIC 3
 
 struct cubeb_stream {
   cubeb * context;
+  pthread_mutex_t mutex;
   SLObjectItf playerObj;
   SLPlayItf play;
   SLBufferQueueItf bufq;
   void *queuebuf[NBUFS];
   int queuebuf_idx;
   long queuebuf_len;
   long bytespersec;
   long framesize;
+  long written;
   int draining;
   cubeb_stream_type stream_type;
 
   cubeb_data_callback data_callback;
   cubeb_state_callback state_callback;
   void * user_ptr;
 };
 
 static void
+play_callback(SLPlayItf caller, void * user_ptr, SLuint32 event)
+{
+  cubeb_stream * stm = user_ptr;
+  assert(stm);
+  switch (event) {
+    case SL_PLAYEVENT_HEADATMARKER:
+      pthread_mutex_lock(&stm->mutex);
+      assert(stm->draining);
+      stm->draining = 0;
+      pthread_mutex_unlock(&stm->mutex);
+      stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
+      break;
+    default:
+      break;
+  }
+}
+
+static void
 bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr)
 {
   cubeb_stream * stm = user_ptr;
+  assert(stm);
   SLBufferQueueState state;
-  (*stm->bufq)->GetState(stm->bufq, &state);
+  SLresult res;
 
-  if (stm->draining) {
-    if (!state.count) {
-      stm->draining = 0;
-      stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
-    }
-    return;
-  }
+  res = (*stm->bufq)->GetState(stm->bufq, &state);
+  assert(res == SL_RESULT_SUCCESS);
 
   if (state.count > 1)
     return;
 
   SLuint32 i;
   for (i = state.count; i < NBUFS; i++) {
     void *buf = stm->queuebuf[stm->queuebuf_idx];
-    long written = stm->data_callback(stm, stm->user_ptr,
-                                      buf, stm->queuebuf_len / stm->framesize);
-    if (written == CUBEB_ERROR) {
-      (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_STOPPED);
-      return;
+    long written = 0;
+    pthread_mutex_lock(&stm->mutex);
+    int draining = stm->draining;
+    pthread_mutex_unlock(&stm->mutex);
+
+    if (!draining) {
+      written = stm->data_callback(stm, stm->user_ptr, buf, stm->queuebuf_len / stm->framesize);
+      if (written < 0 || written * stm->framesize > stm->queuebuf_len) {
+        (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_STOPPED);
+        return;
+      }
     }
 
-    if (written) {
-      (*stm->bufq)->Enqueue(stm->bufq, buf, written * stm->framesize);
-      stm->queuebuf_idx = (stm->queuebuf_idx + 1) % NBUFS;
-    } else if (!i) {
-      stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
-      return;
+    // Keep sending silent data even in draining mode to prevent the audio
+    // back-end from being stopped automatically by OpenSL/ES.
+    memset(buf + written * stm->framesize, 0, stm->queuebuf_len - written * stm->framesize);
+    (*stm->bufq)->Enqueue(stm->bufq, buf, stm->queuebuf_len);
+    stm->queuebuf_idx = (stm->queuebuf_idx + 1) % NBUFS;
+    if (written > 0) {
+      pthread_mutex_lock(&stm->mutex);
+      stm->written += written;
+      pthread_mutex_unlock(&stm->mutex);
     }
 
-    if ((written * stm->framesize) < stm->queuebuf_len) {
+    if (!draining && written * stm->framesize < stm->queuebuf_len) {
+      pthread_mutex_lock(&stm->mutex);
+      int64_t written_duration = INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec;
       stm->draining = 1;
+      pthread_mutex_unlock(&stm->mutex);
+      // Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf
+      // to make sure all the data has been processed.
+      (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)written_duration);
       return;
     }
   }
 }
 
 #if defined(__ANDROID__)
 static SLuint32
 convert_stream_type_to_sl_stream(cubeb_stream_type stream_type)
@@ -439,16 +471,19 @@ opensl_stream_init(cubeb * ctx, cubeb_st
     stm->queuebuf_len += stm->framesize - (stm->queuebuf_len % stm->framesize);
   }
   int i;
   for (i = 0; i < NBUFS; i++) {
     stm->queuebuf[i] = malloc(stm->queuebuf_len);
     assert(stm->queuebuf[i]);
   }
 
+  int r = pthread_mutex_init(&stm->mutex, NULL);
+  assert(r == 0);
+
   SLDataLocator_BufferQueue loc_bufq;
   loc_bufq.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
   loc_bufq.numBuffers = NBUFS;
   SLDataSource source;
   source.pLocator = &loc_bufq;
   source.pFormat = &format;
 
   SLDataLocator_OutputMix loc_outmix;
@@ -502,16 +537,28 @@ opensl_stream_init(cubeb * ctx, cubeb_st
 
   res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_BUFFERQUEUE,
                                     &stm->bufq);
   if (res != SL_RESULT_SUCCESS) {
     opensl_stream_destroy(stm);
     return CUBEB_ERROR;
   }
 
+  res = (*stm->play)->RegisterCallback(stm->play, play_callback, stm);
+  if (res != SL_RESULT_SUCCESS) {
+    opensl_stream_destroy(stm);
+    return CUBEB_ERROR;
+  }
+
+  res = (*stm->play)->SetCallbackEventsMask(stm->play, (SLuint32)SL_PLAYEVENT_HEADATMARKER);
+  if (res != SL_RESULT_SUCCESS) {
+    opensl_stream_destroy(stm);
+    return CUBEB_ERROR;
+  }
+
   res = (*stm->bufq)->RegisterCallback(stm->bufq, bufferqueue_callback, stm);
   if (res != SL_RESULT_SUCCESS) {
     opensl_stream_destroy(stm);
     return CUBEB_ERROR;
   }
 
   *stream = stm;
   return CUBEB_OK;
@@ -521,16 +568,17 @@ static void
 opensl_stream_destroy(cubeb_stream * stm)
 {
   if (stm->playerObj)
     (*stm->playerObj)->Destroy(stm->playerObj);
   int i;
   for (i = 0; i < NBUFS; i++) {
     free(stm->queuebuf[i]);
   }
+  pthread_mutex_destroy(&stm->mutex);
 
   free(stm);
 }
 
 static int
 opensl_stream_start(cubeb_stream * stm)
 {
   /* To refill the queues before starting playback in order to avoid racing
@@ -568,20 +616,28 @@ opensl_stream_get_position(cubeb_stream 
 
   samplerate = stm->bytespersec / stm->framesize;
 
   rv = stm->context->get_output_latency(&mixer_latency, stm->stream_type);
   if (rv) {
     return CUBEB_ERROR;
   }
 
-  if (msec > mixer_latency) {
-    *position = samplerate * (msec - mixer_latency) / 1000;
+  int64_t compensated_position = samplerate * (msec - mixer_latency) / 1000;
+  pthread_mutex_lock(&stm->mutex);
+  int64_t maximum_position = stm->written;
+  pthread_mutex_unlock(&stm->mutex);
+  assert(maximum_position >= 0);
+
+  if (compensated_position < 0) {
+    *position = 0;
+  } else if(compensated_position > maximum_position) {
+    *position = maximum_position;
   } else {
-    *position = 0;
+    *position = compensated_position;
   }
   return CUBEB_OK;
 }
 
 int
 opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
 {
   int rv;