MediaSync: fix flush handling and deadlock

- clear pending video frames
- flush audio track
- reset clock
- restart audio track after flush
- don't hold lock during callback

Bug: 22234976
Change-Id: I681df950b67d7f9f77a9c5783279f1c8c91be856
diff --git a/media/java/android/media/MediaSync.java b/media/java/android/media/MediaSync.java
index b07931d..b37e02c 100644
--- a/media/java/android/media/MediaSync.java
+++ b/media/java/android/media/MediaSync.java
@@ -24,6 +24,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.util.Log;
 import android.view.Surface;
 
 import java.lang.annotation.Retention;
@@ -82,7 +83,7 @@
  *         codec.releaseOutputBuffer(bufferId, 1000 * info.presentationTime);
  *     } else {
  *         ByteBuffer audioByteBuffer = codec.getOutputBuffer(bufferId);
- *         sync.queueByteBuffer(audioByteBuffer, bufferId, info.size, info.presentationTime);
+ *         sync.queueAudio(audioByteBuffer, bufferId, info.presentationTime);
  *     }
  *     // ...
  * }
@@ -427,6 +428,11 @@
     /**
      * Flushes all buffers from the sync object.
      * <p>
+     * All pending unprocessed audio and video buffers are discarded. If an audio track was
+     * configured, it is flushed and stopped. If a video output surface was configured, the
+     * last frame queued to it is left on the frame. Queue a blank video frame to clear the
+     * surface,
+     * <p>
      * No callbacks are received for the flushed buffers.
      *
      * @throws IllegalStateException if the internal player engine has not been
@@ -437,10 +443,19 @@
             mAudioBuffers.clear();
             mCallbackHandler.removeCallbacksAndMessages(null);
         }
-        // TODO implement this for surface buffers.
+        if (mAudioTrack != null) {
+            mAudioTrack.pause();
+            mAudioTrack.flush();
+            // Call stop() to signal to the AudioSink to completely fill the
+            // internal buffer before resuming playback.
+            mAudioTrack.stop();
+        }
+        native_flush();
     }
 
-   /**
+    private native final void native_flush();
+
+    /**
      * Get current playback position.
      * <p>
      * The MediaTimestamp represents how the media time correlates to the system time in
@@ -478,6 +493,7 @@
 
     /**
      * Queues the audio data asynchronously for playback (AudioTrack must be in streaming mode).
+     * If the audio track was flushed as a result of {@link #flush}, it will be restarted.
      * @param audioData the buffer that holds the data to play. This buffer will be returned
      *     to the client via registered callback.
      * @param bufferId an integer used to identify audioData. It will be returned to
@@ -519,6 +535,14 @@
 
                     AudioBuffer audioBuffer = mAudioBuffers.get(0);
                     int size = audioBuffer.mByteBuffer.remaining();
+                    // restart audio track after flush
+                    if (size > 0 && mAudioTrack.getPlayState() != AudioTrack.PLAYSTATE_PLAYING) {
+                        try {
+                            mAudioTrack.play();
+                        } catch (IllegalStateException e) {
+                            Log.w(TAG, "could not start audio track");
+                        }
+                    }
                     int sizeWritten = mAudioTrack.write(
                             audioBuffer.mByteBuffer,
                             size,
@@ -558,17 +582,19 @@
                 final MediaSync sync = this;
                 mCallbackHandler.post(new Runnable() {
                     public void run() {
+                        Callback callback;
                         synchronized(mCallbackLock) {
+                            callback = mCallback;
                             if (mCallbackHandler == null
                                     || mCallbackHandler.getLooper().getThread()
                                             != Thread.currentThread()) {
                                 // callback handler has been changed.
                                 return;
                             }
-                            if (mCallback != null) {
-                                mCallback.onAudioBufferConsumed(sync, audioBuffer.mByteBuffer,
-                                        audioBuffer.mBufferIndex);
-                            }
+                        }
+                        if (callback != null) {
+                            callback.onAudioBufferConsumed(sync, audioBuffer.mByteBuffer,
+                                    audioBuffer.mBufferIndex);
                         }
                     }
                 });
diff --git a/media/jni/android_media_MediaSync.cpp b/media/jni/android_media_MediaSync.cpp
index 8e0ed64..893c414 100644
--- a/media/jni/android_media_MediaSync.cpp
+++ b/media/jni/android_media_MediaSync.cpp
@@ -102,6 +102,10 @@
     return mSync->getVideoFrameRate();
 }
 
+void JMediaSync::flush() {
+    mSync->flush();
+}
+
 status_t JMediaSync::updateQueuedAudioData(
         int sizeInBytes, int64_t presentationTimeUs) {
     return mSync->updateQueuedAudioData(sizeInBytes, presentationTimeUs);
@@ -464,6 +468,16 @@
     return scs.asJobject(env, gSyncParamsFields);
 }
 
+static void android_media_MediaSync_native_flush(JNIEnv *env, jobject thiz) {
+    sp<JMediaSync> sync = getMediaSync(env, thiz);
+    if (sync == NULL) {
+        throwExceptionAsNecessary(env, INVALID_OPERATION);
+        return;
+    }
+
+    sync->flush();
+}
+
 static void android_media_MediaSync_native_init(JNIEnv *env) {
     ScopedLocalRef<jclass> clazz(env, env->FindClass("android/media/MediaSync"));
     CHECK(clazz.get() != NULL);
@@ -524,6 +538,8 @@
       "()J",
       (void *)android_media_MediaSync_native_getPlayTimeForPendingAudioFrames },
 
+    { "native_flush", "()V", (void *)android_media_MediaSync_native_flush },
+
     { "native_init", "()V", (void *)android_media_MediaSync_native_init },
 
     { "native_setup", "()V", (void *)android_media_MediaSync_native_setup },
diff --git a/media/jni/android_media_MediaSync.h b/media/jni/android_media_MediaSync.h
index 22c77c7..6f80885 100644
--- a/media/jni/android_media_MediaSync.h
+++ b/media/jni/android_media_MediaSync.h
@@ -49,6 +49,8 @@
     status_t setVideoFrameRateHint(float rate);
     float getVideoFrameRate();
 
+    void flush();
+
     sp<const MediaClock> getMediaClock();
 
 protected: