Transcoder: Add support for pausing transcoding on a sync frame.

- Added support for stopping transcoders on a sync frame.
- Refactored MediaTrackTranscoders and MediaSampleWriter to stop()
asynchronously.
- Fixed callback and error handling logic in MediaTranscoder.
- Added tests for pause and stopping on sync frame.

Bug: 162886306
Test: Unit tests.

Change-Id: If689a10dfee198c674c4c13b865a7c56a901e075
diff --git a/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp b/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp
index 53d567e..d2f6c40 100644
--- a/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp
+++ b/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp
@@ -181,6 +181,11 @@
     if (mEosReached) {
         return AMEDIA_ERROR_END_OF_STREAM;
     }
+
+    if (!mEnforceSequentialAccess) {
+        return moveToTrack_l(trackIndex);
+    }
+
     return AMEDIA_OK;
 }
 
diff --git a/media/libmediatranscoding/transcoder/MediaSampleWriter.cpp b/media/libmediatranscoding/transcoder/MediaSampleWriter.cpp
index afa5021..389b941 100644
--- a/media/libmediatranscoding/transcoder/MediaSampleWriter.cpp
+++ b/media/libmediatranscoding/transcoder/MediaSampleWriter.cpp
@@ -79,7 +79,7 @@
 
 MediaSampleWriter::~MediaSampleWriter() {
     if (mState == STARTED) {
-        stop();  // Join thread.
+        stop();
     }
 }
 
@@ -169,38 +169,41 @@
     }
 
     mState = STARTED;
-    mThread = std::thread([this] {
-        media_status_t status = writeSamples();
+    std::thread([this] {
+        bool wasStopped = false;
+        media_status_t status = writeSamples(&wasStopped);
         if (auto callbacks = mCallbacks.lock()) {
-            callbacks->onFinished(this, status);
+            if (wasStopped && status == AMEDIA_OK) {
+                callbacks->onStopped(this);
+            } else {
+                callbacks->onFinished(this, status);
+            }
         }
-    });
+    }).detach();
     return true;
 }
 
-bool MediaSampleWriter::stop() {
+void MediaSampleWriter::stop() {
     {
         std::scoped_lock lock(mMutex);
         if (mState != STARTED) {
             LOG(ERROR) << "Sample writer is not started.";
-            return false;
+            return;
         }
         mState = STOPPED;
     }
 
     mSampleSignal.notify_all();
-    mThread.join();
-    return true;
 }
 
-media_status_t MediaSampleWriter::writeSamples() {
+media_status_t MediaSampleWriter::writeSamples(bool* wasStopped) {
     media_status_t muxerStatus = mMuxer->start();
     if (muxerStatus != AMEDIA_OK) {
         LOG(ERROR) << "Error starting muxer: " << muxerStatus;
         return muxerStatus;
     }
 
-    media_status_t writeStatus = runWriterLoop();
+    media_status_t writeStatus = runWriterLoop(wasStopped);
     if (writeStatus != AMEDIA_OK) {
         LOG(ERROR) << "Error writing samples: " << writeStatus;
     }
@@ -213,7 +216,7 @@
     return writeStatus != AMEDIA_OK ? writeStatus : muxerStatus;
 }
 
-media_status_t MediaSampleWriter::runWriterLoop() NO_THREAD_SAFETY_ANALYSIS {
+media_status_t MediaSampleWriter::runWriterLoop(bool* wasStopped) NO_THREAD_SAFETY_ANALYSIS {
     AMediaCodecBufferInfo bufferInfo;
     int32_t lastProgressUpdate = 0;
     int trackEosCount = 0;
@@ -242,8 +245,9 @@
                 mSampleSignal.wait(lock);
             }
 
-            if (mState != STARTED) {
-                return AMEDIA_ERROR_UNKNOWN;  // TODO(lnilsson): Custom error code.
+            if (mState == STOPPED) {
+                *wasStopped = true;
+                return AMEDIA_OK;
             }
 
             auto& topEntry = mSampleQueue.top();
diff --git a/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
index 698594f..15f7427 100644
--- a/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
@@ -69,41 +69,44 @@
         LOG(ERROR) << "TrackTranscoder must be configured before started";
         return false;
     }
+    mState = STARTED;
 
-    mTranscodingThread = std::thread([this] {
-        media_status_t status = runTranscodeLoop();
+    std::thread([this] {
+        bool stopped = false;
+        media_status_t status = runTranscodeLoop(&stopped);
+
+        // Output an EOS sample if the transcoder was stopped.
+        if (stopped) {
+            auto sample = std::make_shared<MediaSample>();
+            sample->info.flags = SAMPLE_FLAG_END_OF_STREAM;
+            onOutputSampleAvailable(sample);
+        }
 
         // Notify the client.
         if (auto callbacks = mTranscoderCallback.lock()) {
-            if (status != AMEDIA_OK) {
-                callbacks->onTrackError(this, status);
-            } else {
+            if (stopped) {
+                callbacks->onTrackStopped(this);
+            } else if (status == AMEDIA_OK) {
                 callbacks->onTrackFinished(this);
+            } else {
+                callbacks->onTrackError(this, status);
             }
         }
-    });
+    }).detach();
 
-    mState = STARTED;
     return true;
 }
 
-bool MediaTrackTranscoder::stop() {
+void MediaTrackTranscoder::stop(bool stopOnSyncSample) {
     std::scoped_lock lock{mStateMutex};
 
-    if (mState == STARTED) {
+    if (mState == STARTED || (mStopRequest == STOP_ON_SYNC && !stopOnSyncSample)) {
+        mStopRequest = stopOnSyncSample ? STOP_ON_SYNC : STOP_NOW;
         abortTranscodeLoop();
-        mMediaSampleReader->setEnforceSequentialAccess(false);
-        mTranscodingThread.join();
-        {
-            std::scoped_lock lock{mSampleMutex};
-            mSampleQueue.abort();  // Release any buffered samples.
-        }
         mState = STOPPED;
-        return true;
+    } else {
+        LOG(WARNING) << "TrackTranscoder must be started before stopped";
     }
-
-    LOG(ERROR) << "TrackTranscoder must be started before stopped";
-    return false;
 }
 
 void MediaTrackTranscoder::notifyTrackFormatAvailable() {
diff --git a/media/libmediatranscoding/transcoder/MediaTranscoder.cpp b/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
index d89b58f..dc49014 100644
--- a/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
@@ -69,38 +69,67 @@
     return format;
 }
 
-void MediaTranscoder::sendCallback(media_status_t status) {
-    // If the transcoder is already cancelled explicitly, don't send any error callbacks.
-    // Tracks and sample writer will report errors for abort. However, currently we can't
-    // tell it apart from real errors. Ideally we still want to report real errors back
-    // to client, as there is a small chance that explicit abort and the real error come
-    // at around the same time, we should report that if abort has a specific error code.
-    // On the other hand, if the transcoder actually finished (status is AMEDIA_OK) at around
-    // the same time of the abort, we should still report the finish back to the client.
-    if (mCancelled && status != AMEDIA_OK) {
+void MediaTranscoder::onThreadFinished(const void* thread, media_status_t threadStatus,
+                                       bool threadStopped) {
+    LOG(DEBUG) << "Thread " << thread << " finished with status " << threadStatus << " stopped "
+               << threadStopped;
+
+    // Stop all threads if one reports an error.
+    if (threadStatus != AMEDIA_OK) {
+        requestStop(false /* stopOnSync */);
+    }
+
+    std::scoped_lock lock{mThreadStateMutex};
+
+    // Record the change.
+    mThreadStates[thread] = DONE;
+    if (threadStatus != AMEDIA_OK && mTranscoderStatus == AMEDIA_OK) {
+        mTranscoderStatus = threadStatus;
+    }
+
+    mTranscoderStopped |= threadStopped;
+
+    // Check if all threads are done. Note that if all transcoders have stopped but the sample
+    // writer has not yet started, it never will.
+    bool transcodersDone = true;
+    ThreadState sampleWriterState = PENDING;
+    for (const auto& it : mThreadStates) {
+        LOG(DEBUG) << "  Thread " << it.first << " state" << it.second;
+        if (it.first == static_cast<const void*>(mSampleWriter.get())) {
+            sampleWriterState = it.second;
+        } else {
+            transcodersDone &= (it.second == DONE);
+        }
+    }
+    if (!transcodersDone || sampleWriterState == RUNNING) {
         return;
     }
 
-    bool expected = false;
-    if (mCallbackSent.compare_exchange_strong(expected, true)) {
-        if (status == AMEDIA_OK) {
-            mCallbacks->onFinished(this);
-        } else {
-            mCallbacks->onError(this, status);
-        }
-
-        // Transcoding is done and the callback to the client has been sent, so tear down the
-        // pipeline but do it asynchronously to avoid deadlocks. If an error occurred, client
-        // should clean up the file.
-        std::thread asyncCancelThread{[self = shared_from_this()] { self->cancel(); }};
-        asyncCancelThread.detach();
+    // All done. Send callback asynchronously and wake up threads waiting in cancel/pause.
+    mThreadsDone = true;
+    if (!mCallbackSent) {
+        std::thread asyncNotificationThread{[this, self = shared_from_this(),
+                                             status = mTranscoderStatus,
+                                             stopped = mTranscoderStopped] {
+            // If the transcoder was stopped that means a caller is waiting in stop or pause
+            // in which case we don't send a callback.
+            if (status != AMEDIA_OK) {
+                mCallbacks->onError(this, status);
+            } else if (!stopped) {
+                mCallbacks->onFinished(this);
+            }
+            mThreadsDoneSignal.notify_all();
+        }};
+        asyncNotificationThread.detach();
+        mCallbackSent = true;
     }
 }
 
 void MediaTranscoder::onTrackFormatAvailable(const MediaTrackTranscoder* transcoder) {
-    LOG(INFO) << "TrackTranscoder " << transcoder << " format available.";
+    LOG(DEBUG) << "TrackTranscoder " << transcoder << " format available.";
 
     std::scoped_lock lock{mTracksAddedMutex};
+    const void* sampleWriterPtr = static_cast<const void*>(mSampleWriter.get());
 
     // Ignore duplicate format change.
     if (mTracksAdded.count(transcoder) > 0) {
@@ -111,7 +140,7 @@
     auto consumer = mSampleWriter->addTrack(transcoder->getOutputFormat());
     if (consumer == nullptr) {
         LOG(ERROR) << "Unable to add track to sample writer.";
-        sendCallback(AMEDIA_ERROR_UNKNOWN);
+        onThreadFinished(sampleWriterPtr, AMEDIA_ERROR_UNKNOWN, false /* stopped */);
         return;
     }
 
@@ -119,34 +148,57 @@
     mutableTranscoder->setSampleConsumer(consumer);
 
     mTracksAdded.insert(transcoder);
+    bool errorStarting = false;
     if (mTracksAdded.size() == mTrackTranscoders.size()) {
         // Enable sequential access mode on the sample reader to achieve optimal read performance.
         // This has to wait until all tracks have delivered their output formats and the sample
         // writer is started. Otherwise the tracks will not get their output sample queues drained
         // and the transcoder could hang due to one track running out of buffers and blocking the
         // other tracks from reading source samples before they could output their formats.
-        mSampleReader->setEnforceSequentialAccess(true);
-        LOG(INFO) << "Starting sample writer.";
-        bool started = mSampleWriter->start();
-        if (!started) {
-            LOG(ERROR) << "Unable to start sample writer.";
-            sendCallback(AMEDIA_ERROR_UNKNOWN);
+
+        std::scoped_lock lock{mThreadStateMutex};
+        // Don't start the sample writer if a stop already has been requested.
+        if (!mSampleWriterStopped) {
+            if (!mCancelled) {
+                mSampleReader->setEnforceSequentialAccess(true);
+            }
+            LOG(DEBUG) << "Starting sample writer.";
+            errorStarting = !mSampleWriter->start();
+            if (!errorStarting) {
+                mThreadStates[sampleWriterPtr] = RUNNING;
+            }
         }
     }
+
+    if (errorStarting) {
+        LOG(ERROR) << "Unable to start sample writer.";
+        onThreadFinished(sampleWriterPtr, AMEDIA_ERROR_UNKNOWN, false /* stopped */);
+    }
 }
 
 void MediaTranscoder::onTrackFinished(const MediaTrackTranscoder* transcoder) {
     LOG(DEBUG) << "TrackTranscoder " << transcoder << " finished";
+    onThreadFinished(static_cast<const void*>(transcoder), AMEDIA_OK, false /* stopped */);
+}
+
+void MediaTranscoder::onTrackStopped(const MediaTrackTranscoder* transcoder) {
+    LOG(DEBUG) << "TrackTranscoder " << transcoder << " stopped";
+    onThreadFinished(static_cast<const void*>(transcoder), AMEDIA_OK, true /* stopped */);
 }
 
 void MediaTranscoder::onTrackError(const MediaTrackTranscoder* transcoder, media_status_t status) {
     LOG(ERROR) << "TrackTranscoder " << transcoder << " returned error " << status;
-    sendCallback(status);
+    onThreadFinished(static_cast<const void*>(transcoder), status, false /* stopped */);
 }
 
-void MediaTranscoder::onFinished(const MediaSampleWriter* writer __unused, media_status_t status) {
-    LOG((status != AMEDIA_OK) ? ERROR : DEBUG) << "Sample writer finished with status " << status;
-    sendCallback(status);
+void MediaTranscoder::onFinished(const MediaSampleWriter* writer, media_status_t status) {
+    LOG(status == AMEDIA_OK ? DEBUG : ERROR) << "Sample writer finished with status " << status;
+    onThreadFinished(static_cast<const void*>(writer), status, false /* stopped */);
+}
+
+void MediaTranscoder::onStopped(const MediaSampleWriter* writer) {
+    LOG(DEBUG) << "Sample writer " << writer << " stopped";
+    onThreadFinished(static_cast<const void*>(writer), AMEDIA_OK, true /* stopped */);
 }
 
 void MediaTranscoder::onProgressUpdate(const MediaSampleWriter* writer __unused, int32_t progress) {
@@ -276,6 +328,9 @@
         return status;
     }
 
+    std::scoped_lock lock{mThreadStateMutex};
+    mThreadStates[static_cast<const void*>(transcoder.get())] = PENDING;
+
     mTrackTranscoders.emplace_back(std::move(transcoder));
     return AMEDIA_OK;
 }
@@ -300,6 +355,8 @@
         return AMEDIA_ERROR_UNKNOWN;
     }
 
+    std::scoped_lock lock{mThreadStateMutex};
+    mThreadStates[static_cast<const void*>(mSampleWriter.get())] = PENDING;
     return AMEDIA_OK;
 }
 
@@ -313,21 +370,75 @@
     }
 
     // Start transcoders
-    for (auto& transcoder : mTrackTranscoders) {
-        bool started = transcoder->start();
-        if (!started) {
-            LOG(ERROR) << "Unable to start track transcoder.";
-            cancel();
-            return AMEDIA_ERROR_UNKNOWN;
+    bool started = true;
+    {
+        std::scoped_lock lock{mThreadStateMutex};
+        for (auto& transcoder : mTrackTranscoders) {
+            if (!(started = transcoder->start())) {
+                break;
+            }
+            mThreadStates[static_cast<const void*>(transcoder.get())] = RUNNING;
         }
     }
+    if (!started) {
+        LOG(ERROR) << "Unable to start track transcoder.";
+        cancel();
+        return AMEDIA_ERROR_UNKNOWN;
+    }
     return AMEDIA_OK;
 }
 
+media_status_t MediaTranscoder::requestStop(bool stopOnSync) {
+    std::scoped_lock lock{mThreadStateMutex};
+    if (mCancelled) {
+        LOG(DEBUG) << "MediaTranscoder already cancelled";
+        return AMEDIA_ERROR_UNSUPPORTED;
+    }
+
+    if (!stopOnSync) {
+        mSampleWriterStopped = true;
+        mSampleWriter->stop();
+    }
+
+    mSampleReader->setEnforceSequentialAccess(false);
+    for (auto& transcoder : mTrackTranscoders) {
+        transcoder->stop(stopOnSync);
+    }
+
+    mCancelled = true;
+    return AMEDIA_OK;
+}
+
+void MediaTranscoder::waitForThreads() NO_THREAD_SAFETY_ANALYSIS {
+    std::unique_lock lock{mThreadStateMutex};
+    while (!mThreadsDone) {
+        mThreadsDoneSignal.wait(lock);
+    }
+}
+
 media_status_t MediaTranscoder::pause(std::shared_ptr<ndk::ScopedAParcel>* pausedState) {
+    media_status_t status = requestStop(true /* stopOnSync */);
+    if (status != AMEDIA_OK) {
+        return status;
+    }
+
+    waitForThreads();
+
     // TODO: write internal states to parcel.
     *pausedState = std::shared_ptr<::ndk::ScopedAParcel>(new ::ndk::ScopedAParcel());
-    return cancel();
+    return AMEDIA_OK;
+}
+
+media_status_t MediaTranscoder::cancel() {
+    media_status_t status = requestStop(false /* stopOnSync */);
+    if (status != AMEDIA_OK) {
+        return status;
+    }
+
+    waitForThreads();
+
+    // TODO: Release transcoders?
+    return AMEDIA_OK;
 }
 
 media_status_t MediaTranscoder::resume() {
@@ -335,20 +446,4 @@
     return start();
 }
 
-media_status_t MediaTranscoder::cancel() {
-    bool expected = false;
-    if (!mCancelled.compare_exchange_strong(expected, true)) {
-        // Already cancelled.
-        return AMEDIA_OK;
-    }
-
-    mSampleWriter->stop();
-    mSampleReader->setEnforceSequentialAccess(false);
-    for (auto& transcoder : mTrackTranscoders) {
-        transcoder->stop();
-    }
-
-    return AMEDIA_OK;
-}
-
 }  // namespace android
diff --git a/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp
index 35b1d33..c55e244 100644
--- a/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp
@@ -93,9 +93,10 @@
     return AMEDIA_OK;
 }
 
-media_status_t PassthroughTrackTranscoder::runTranscodeLoop() {
+media_status_t PassthroughTrackTranscoder::runTranscodeLoop(bool* stopped) {
     MediaSampleInfo info;
     std::shared_ptr<MediaSample> sample;
+    bool eosReached = false;
 
     // Notify the track format as soon as we start. It's same as the source format.
     notifyTrackFormatAvailable();
@@ -106,18 +107,18 @@
             };
 
     // Move samples until EOS is reached or transcoding is stopped.
-    while (!mStopRequested && !mEosFromSource) {
+    while (mStopRequest != STOP_NOW && !eosReached) {
         media_status_t status = mMediaSampleReader->getSampleInfoForTrack(mTrackIndex, &info);
 
         if (status == AMEDIA_OK) {
             uint8_t* buffer = mBufferPool->getBufferWithSize(info.size);
             if (buffer == nullptr) {
-                if (mStopRequested) {
+                if (mStopRequest == STOP_NOW) {
                     break;
                 }
 
                 LOG(ERROR) << "Unable to get buffer from pool";
-                return AMEDIA_ERROR_IO;  // TODO: Custom error codes?
+                return AMEDIA_ERROR_UNKNOWN;
             }
 
             sample = MediaSample::createWithReleaseCallback(
@@ -131,7 +132,7 @@
 
         } else if (status == AMEDIA_ERROR_END_OF_STREAM) {
             sample = std::make_shared<MediaSample>();
-            mEosFromSource = true;
+            eosReached = true;
         } else {
             LOG(ERROR) << "Unable to get next sample info. Aborting transcode.";
             return status;
@@ -139,17 +140,22 @@
 
         sample->info = info;
         onOutputSampleAvailable(sample);
+
+        if (mStopRequest == STOP_ON_SYNC && info.flags & SAMPLE_FLAG_SYNC_SAMPLE) {
+            break;
+        }
     }
 
-    if (mStopRequested && !mEosFromSource) {
-        return AMEDIA_ERROR_UNKNOWN;  // TODO: Custom error codes?
+    if (mStopRequest != NONE && !eosReached) {
+        *stopped = true;
     }
     return AMEDIA_OK;
 }
 
 void PassthroughTrackTranscoder::abortTranscodeLoop() {
-    mStopRequested = true;
-    mBufferPool->abort();
+    if (mStopRequest == STOP_NOW) {
+        mBufferPool->abort();
+    }
 }
 
 std::shared_ptr<AMediaFormat> PassthroughTrackTranscoder::getOutputFormat() const {
diff --git a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
index 4cf54f1..08ae9aa 100644
--- a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
@@ -156,11 +156,7 @@
                 static_cast<VideoTrackTranscoder::CodecWrapper*>(userdata);
         if (auto transcoder = wrapper->getTranscoder()) {
             transcoder->mCodecMessageQueue.push(
-                    [transcoder, error] {
-                        transcoder->mStatus = error;
-                        transcoder->mStopRequested = true;
-                    },
-                    true);
+                    [transcoder, error] { transcoder->mStatus = error; }, true);
         }
     }
 };
@@ -404,6 +400,8 @@
         sample->info.presentationTimeUs = bufferInfo.presentationTimeUs;
 
         onOutputSampleAvailable(sample);
+
+        mLastSampleWasSync = sample->info.flags & SAMPLE_FLAG_SYNC_SAMPLE;
     } else if (bufferIndex == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
         AMediaFormat* newFormat = AMediaCodec_getOutputFormat(mEncoder->getCodec());
         LOG(DEBUG) << "Encoder output format changed: " << AMediaFormat_toString(newFormat);
@@ -483,7 +481,7 @@
     notifyTrackFormatAvailable();
 }
 
-media_status_t VideoTrackTranscoder::runTranscodeLoop() {
+media_status_t VideoTrackTranscoder::runTranscodeLoop(bool* stopped) {
     androidSetThreadPriority(0 /* tid (0 = current) */, ANDROID_PRIORITY_VIDEO);
 
     // Push start decoder and encoder as two messages, so that these are subject to the
@@ -507,25 +505,31 @@
     });
 
     // Process codec events until EOS is reached, transcoding is stopped or an error occurs.
-    while (!mStopRequested && !mEosFromEncoder && mStatus == AMEDIA_OK) {
+    while (mStopRequest != STOP_NOW && !mEosFromEncoder && mStatus == AMEDIA_OK) {
         std::function<void()> message = mCodecMessageQueue.pop();
         message();
+
+        if (mStopRequest == STOP_ON_SYNC && mLastSampleWasSync) {
+            break;
+        }
     }
 
     mCodecMessageQueue.abort();
     AMediaCodec_stop(mDecoder);
 
-    // Return error if transcoding was stopped before it finished.
-    if (mStopRequested && !mEosFromEncoder && mStatus == AMEDIA_OK) {
-        mStatus = AMEDIA_ERROR_UNKNOWN;  // TODO: Define custom error codes?
+    // Signal if transcoding was stopped before it finished.
+    if (mStopRequest != NONE && !mEosFromEncoder && mStatus == AMEDIA_OK) {
+        *stopped = true;
     }
 
     return mStatus;
 }
 
 void VideoTrackTranscoder::abortTranscodeLoop() {
-    // Push abort message to the front of the codec event queue.
-    mCodecMessageQueue.push([this] { mStopRequested = true; }, true /* front */);
+    if (mStopRequest == STOP_NOW) {
+        // Wake up transcoder thread.
+        mCodecMessageQueue.push([] {}, true /* front */);
+    }
 }
 
 std::shared_ptr<AMediaFormat> VideoTrackTranscoder::getOutputFormat() const {
diff --git a/media/libmediatranscoding/transcoder/benchmark/MediaTrackTranscoderBenchmark.cpp b/media/libmediatranscoding/transcoder/benchmark/MediaTrackTranscoderBenchmark.cpp
index aee0ed6..351d80b 100644
--- a/media/libmediatranscoding/transcoder/benchmark/MediaTrackTranscoderBenchmark.cpp
+++ b/media/libmediatranscoding/transcoder/benchmark/MediaTrackTranscoderBenchmark.cpp
@@ -61,6 +61,12 @@
         mCondition.notify_all();
     }
 
+    virtual void onTrackStopped(const MediaTrackTranscoder* transcoder __unused) override {
+        std::unique_lock lock(mMutex);
+        mFinished = true;
+        mCondition.notify_all();
+    }
+
     virtual void onTrackError(const MediaTrackTranscoder* transcoder __unused,
                               media_status_t status) override {
         std::unique_lock lock(mMutex);
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaSampleWriter.h b/media/libmediatranscoding/transcoder/include/media/MediaSampleWriter.h
index f762556..080f2b7 100644
--- a/media/libmediatranscoding/transcoder/include/media/MediaSampleWriter.h
+++ b/media/libmediatranscoding/transcoder/include/media/MediaSampleWriter.h
@@ -84,6 +84,9 @@
          */
         virtual void onFinished(const MediaSampleWriter* writer, media_status_t status) = 0;
 
+        /** Sample writer was stopped before it was finished. */
+        virtual void onStopped(const MediaSampleWriter* writer) = 0;
+
         /** Sample writer progress update in percent. */
         virtual void onProgressUpdate(const MediaSampleWriter* writer, int32_t progress) = 0;
 
@@ -129,15 +132,14 @@
     bool start();
 
     /**
-     * Stops the sample writer. If the sample writer is not yet finished its operation will be
-     * aborted and an error value will be returned to the client in the callback supplied to
-     * {@link #start}. If the sample writer has already finished and the client callback has fired
-     * the writer has already automatically stopped and there is no need to call stop manually. Once
-     * the sample writer has been stopped it cannot be restarted.
-     * @return True if the sample writer was successfully stopped on this call. False if the sample
-     *         writer was already stopped or was never started.
+     * Stops the sample writer. If the sample writer is not yet finished, its operation will be
+     * aborted and the onStopped callback will fire. If the sample writer has already finished and
+     * the onFinished callback has fired the writer has already automatically stopped and there is
+     * no need to call stop manually. Once the sample writer has been stopped it cannot be
+     * restarted. This method is asynchronous and will not wait for the sample writer to stop before
+     * returning.
      */
-    bool stop();
+    void stop();
 
     /** Destructor. */
     ~MediaSampleWriter();
@@ -186,7 +188,6 @@
 
     std::mutex mMutex;  // Protects sample queue and state.
     std::condition_variable mSampleSignal;
-    std::thread mThread;
     std::unordered_map<size_t, TrackRecord> mTracks;
     std::priority_queue<SampleEntry, std::vector<SampleEntry>, SampleComparator> mSampleQueue
             GUARDED_BY(mMutex);
@@ -200,8 +201,8 @@
 
     MediaSampleWriter() : mState(UNINITIALIZED){};
     void addSampleToTrack(size_t trackIndex, const std::shared_ptr<MediaSample>& sample);
-    media_status_t writeSamples();
-    media_status_t runWriterLoop();
+    media_status_t writeSamples(bool* wasStopped);
+    media_status_t runWriterLoop(bool* wasStopped);
 };
 
 }  // namespace android
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
index c5e161c..724b919 100644
--- a/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
@@ -62,18 +62,21 @@
                              const std::shared_ptr<AMediaFormat>& destinationFormat);
 
     /**
-     * Starts the track transcoder. Once started the track transcoder have to be stopped by calling
-     * {@link #stop}, even after completing successfully. Start should only be called once.
+     * Starts the track transcoder. After the track transcoder is successfully started it will run
+     * until a callback signals that transcoding has ended. Start should only be called once.
      * @return True if the track transcoder started, or false if it had already been started.
      */
     bool start();
 
     /**
      * Stops the track transcoder. Once the transcoding has been stopped it cannot be restarted
-     * again. It is safe to call stop multiple times.
-     * @return True if the track transcoder stopped, or false if it was already stopped.
+     * again. It is safe to call stop multiple times. Stop is an asynchronous operation. Once the
+     * track transcoder has stopped the onTrackStopped callback will get called, unless the
+     * transcoding finished or encountered an error before it could be stopped in which case the
+     * callbacks corresponding to those events will be called instead.
+     * @param stopOnSyncSample Request the transcoder to stop after emitting a sync sample.
      */
-    bool stop();
+    void stop(bool stopOnSyncSample = false);
 
     /**
      * Set the sample consumer function. The MediaTrackTranscoder will deliver transcoded samples to
@@ -100,7 +103,9 @@
     // Called by subclasses when the actual track format becomes available.
     void notifyTrackFormatAvailable();
 
-    // Called by subclasses when a transcoded sample is available.
+    // Called by subclasses when a transcoded sample is available. Samples must not hold a strong
+    // reference to the track transcoder in order to avoid retain cycles through the track
+    // transcoder's sample queue.
     void onOutputSampleAvailable(const std::shared_ptr<MediaSample>& sample);
 
     // configureDestinationFormat needs to be implemented by subclasses, and gets called on an
@@ -110,7 +115,7 @@
 
     // runTranscodeLoop needs to be implemented by subclasses, and gets called on
     // MediaTrackTranscoder's internal thread when the track transcoder is started.
-    virtual media_status_t runTranscodeLoop() = 0;
+    virtual media_status_t runTranscodeLoop(bool* stopped) = 0;
 
     // abortTranscodeLoop needs to be implemented by subclasses, and should request transcoding to
     // be aborted as soon as possible. It should be safe to call abortTranscodeLoop multiple times.
@@ -120,13 +125,20 @@
     int mTrackIndex;
     std::shared_ptr<AMediaFormat> mSourceFormat;
 
+    enum StopRequest {
+        NONE,
+        STOP_NOW,
+        STOP_ON_SYNC,
+    };
+    std::atomic<StopRequest> mStopRequest = NONE;
+
 private:
     std::mutex mSampleMutex;
+    // SampleQueue for buffering output samples before a sample consumer has been set.
     MediaSampleQueue mSampleQueue GUARDED_BY(mSampleMutex);
     MediaSampleWriter::MediaSampleConsumerFunction mSampleConsumer GUARDED_BY(mSampleMutex);
     const std::weak_ptr<MediaTrackTranscoderCallback> mTranscoderCallback;
     std::mutex mStateMutex;
-    std::thread mTranscodingThread GUARDED_BY(mStateMutex);
     enum {
         UNINITIALIZED,
         CONFIGURED,
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoderCallback.h b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoderCallback.h
index 654171e..7b62d46 100644
--- a/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoderCallback.h
+++ b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoderCallback.h
@@ -39,6 +39,12 @@
     virtual void onTrackFinished(const MediaTrackTranscoder* transcoder);
 
     /**
+     * Called when the MediaTrackTranscoder instance was explicitly stopped before it was finished.
+     * @param transcoder The MediaTrackTranscoder that was stopped.
+     */
+    virtual void onTrackStopped(const MediaTrackTranscoder* transcoder);
+
+    /**
      * Called when the MediaTrackTranscoder instance encountered an error it could not recover from.
      * @param transcoder The MediaTrackTranscoder that encountered the error.
      * @param status The non-zero error code describing the encountered error.
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h b/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
index 555cfce..b995e6d 100644
--- a/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
@@ -94,23 +94,25 @@
     media_status_t start();
 
     /**
-     * Pauses transcoding. The transcoder's paused state is returned through pausedState. The
-     * paused state is only needed for resuming transcoding with a new MediaTranscoder instance. The
-     * caller can resume transcoding with the current MediaTranscoder instance at any time by
-     * calling resume(). It is not required to cancel a paused transcoder. The paused state is
-     * independent and the caller can always initialize a new transcoder instance with the same
-     * paused state. If the caller wishes to abandon a paused transcoder's operation they can
-     * release the transcoder instance, clear the paused state and delete the partial destination
-     * file. The caller can optionally call cancel to let the transcoder clean up the partial
-     * destination file.
+     * Pauses transcoding and finalizes the partial transcoded file to disk. Pause is a synchronous
+     * operation and will wait until all internal components are done. Once this method returns it
+     * is safe to release the transcoder instance. No callback will be called if the transcoder was
+     * paused successfully. But if the transcoding finishes or encountered an error during pause,
+     * the corresponding callback will be called.
      */
     media_status_t pause(std::shared_ptr<ndk::ScopedAParcel>* pausedState);
 
     /** Resumes a paused transcoding. */
     media_status_t resume();
 
-    /** Cancels the transcoding. Once canceled the transcoding can not be restarted. Client
-     * will be responsible for cleaning up the abandoned file. */
+    /**
+     * Cancels the transcoding. Once canceled the transcoding can not be restarted. Client
+     * will be responsible for cleaning up the abandoned file. Cancel is a synchronous operation and
+     * will wait until all internal components are done. Once this method returns it is safe to
+     * release the transcoder instance. Normally no callback will be called when the transcoder is
+     * cancelled. But if the transcoding finishes or encountered an error during cancel, the
+     * corresponding callback will be called.
+     */
     media_status_t cancel();
 
     virtual ~MediaTranscoder() = default;
@@ -121,17 +123,20 @@
     // MediaTrackTranscoderCallback
     virtual void onTrackFormatAvailable(const MediaTrackTranscoder* transcoder) override;
     virtual void onTrackFinished(const MediaTrackTranscoder* transcoder) override;
+    virtual void onTrackStopped(const MediaTrackTranscoder* transcoder) override;
     virtual void onTrackError(const MediaTrackTranscoder* transcoder,
                               media_status_t status) override;
     // ~MediaTrackTranscoderCallback
 
     // MediaSampleWriter::CallbackInterface
     virtual void onFinished(const MediaSampleWriter* writer, media_status_t status) override;
+    virtual void onStopped(const MediaSampleWriter* writer) override;
     virtual void onProgressUpdate(const MediaSampleWriter* writer, int32_t progress) override;
     // ~MediaSampleWriter::CallbackInterface
 
-    void onSampleWriterFinished(media_status_t status);
-    void sendCallback(media_status_t status);
+    void onThreadFinished(const void* thread, media_status_t threadStatus, bool threadStopped);
+    media_status_t requestStop(bool stopOnSync);
+    void waitForThreads();
 
     std::shared_ptr<CallbackInterface> mCallbacks;
     std::shared_ptr<MediaSampleReader> mSampleReader;
@@ -141,7 +146,20 @@
     std::mutex mTracksAddedMutex;
     std::unordered_set<const MediaTrackTranscoder*> mTracksAdded GUARDED_BY(mTracksAddedMutex);
 
-    std::atomic_bool mCallbackSent = false;
+    enum ThreadState {
+        PENDING = 0,  // Not yet started.
+        RUNNING,      // Currently running.
+        DONE,         // Done running (can be finished, stopped or error).
+    };
+    std::mutex mThreadStateMutex;
+    std::condition_variable mThreadsDoneSignal;
+    std::unordered_map<const void*, ThreadState> mThreadStates GUARDED_BY(mThreadStateMutex);
+    media_status_t mTranscoderStatus GUARDED_BY(mThreadStateMutex) = AMEDIA_OK;
+    bool mTranscoderStopped GUARDED_BY(mThreadStateMutex) = false;
+    bool mThreadsDone GUARDED_BY(mThreadStateMutex) = false;
+    bool mCallbackSent GUARDED_BY(mThreadStateMutex) = false;
+    bool mSampleWriterStopped GUARDED_BY(mThreadStateMutex) = false;
+
     std::atomic_bool mCancelled = false;
 };
 
diff --git a/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h
index b9491ed..c074831 100644
--- a/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h
@@ -86,7 +86,7 @@
     };
 
     // MediaTrackTranscoder
-    media_status_t runTranscodeLoop() override;
+    media_status_t runTranscodeLoop(bool* stopped) override;
     void abortTranscodeLoop() override;
     media_status_t configureDestinationFormat(
             const std::shared_ptr<AMediaFormat>& destinationFormat) override;
@@ -94,8 +94,6 @@
     // ~MediaTrackTranscoder
 
     std::shared_ptr<BufferPool> mBufferPool;
-    bool mEosFromSource = false;
-    std::atomic_bool mStopRequested = false;
 };
 
 }  // namespace android
diff --git a/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
index d000d7f..267755d 100644
--- a/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
@@ -65,7 +65,7 @@
           : MediaTrackTranscoder(transcoderCallback){};
 
     // MediaTrackTranscoder
-    media_status_t runTranscodeLoop() override;
+    media_status_t runTranscodeLoop(bool* stopped) override;
     void abortTranscodeLoop() override;
     media_status_t configureDestinationFormat(
             const std::shared_ptr<AMediaFormat>& destinationFormat) override;
@@ -89,7 +89,7 @@
     ANativeWindow* mSurface = nullptr;
     bool mEosFromSource = false;
     bool mEosFromEncoder = false;
-    bool mStopRequested = false;
+    bool mLastSampleWasSync = false;
     media_status_t mStatus = AMEDIA_OK;
     MediaSampleInfo mSampleInfo;
     BlockingQueue<std::function<void()>> mCodecMessageQueue;
diff --git a/media/libmediatranscoding/transcoder/tests/MediaSampleWriterTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaSampleWriterTests.cpp
index 46f3e9b..0a41b00 100644
--- a/media/libmediatranscoding/transcoder/tests/MediaSampleWriterTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/MediaSampleWriterTests.cpp
@@ -179,8 +179,6 @@
 
 class TestCallbacks : public MediaSampleWriter::CallbackInterface {
 public:
-    TestCallbacks(bool expectSuccess = true) : mExpectSuccess(expectSuccess) {}
-
     bool hasFinished() {
         std::unique_lock<std::mutex> lock(mMutex);
         return mFinished;
@@ -191,12 +189,15 @@
                             media_status_t status) override {
         std::unique_lock<std::mutex> lock(mMutex);
         EXPECT_FALSE(mFinished);
-        if (mExpectSuccess) {
-            EXPECT_EQ(status, AMEDIA_OK);
-        } else {
-            EXPECT_NE(status, AMEDIA_OK);
-        }
         mFinished = true;
+        mStatus = status;
+        mCondition.notify_all();
+    }
+
+    virtual void onStopped(const MediaSampleWriter* writer __unused) {
+        std::unique_lock<std::mutex> lock(mMutex);
+        EXPECT_FALSE(mFinished);
+        mStopped = true;
         mCondition.notify_all();
     }
 
@@ -213,18 +214,20 @@
 
     void waitForWritingFinished() {
         std::unique_lock<std::mutex> lock(mMutex);
-        while (!mFinished) {
+        while (!mFinished && !mStopped) {
             mCondition.wait(lock);
         }
     }
 
     uint32_t getProgressUpdateCount() const { return mProgressUpdateCount; }
+    bool wasStopped() const { return mStopped; }
 
 private:
     std::mutex mMutex;
     std::condition_variable mCondition;
     bool mFinished = false;
-    bool mExpectSuccess;
+    bool mStopped = false;
+    media_status_t mStatus = AMEDIA_OK;
     int32_t mLastProgress = -1;
     uint32_t mProgressUpdateCount = 0;
 };
@@ -316,8 +319,7 @@
 TEST_F(MediaSampleWriterTests, TestDoubleStartStop) {
     std::shared_ptr<MediaSampleWriter> writer = MediaSampleWriter::Create();
 
-    std::shared_ptr<TestCallbacks> callbacks =
-            std::make_shared<TestCallbacks>(false /* expectSuccess */);
+    std::shared_ptr<TestCallbacks> callbacks = std::make_shared<TestCallbacks>();
     EXPECT_TRUE(writer->init(mTestMuxer, callbacks));
 
     const TestMediaSource& mediaSource = getMediaSource();
@@ -327,9 +329,10 @@
     ASSERT_TRUE(writer->start());
     EXPECT_FALSE(writer->start());
 
-    EXPECT_TRUE(writer->stop());
-    EXPECT_TRUE(callbacks->hasFinished());
-    EXPECT_FALSE(writer->stop());
+    writer->stop();
+    writer->stop();
+    callbacks->waitForWritingFinished();
+    EXPECT_TRUE(callbacks->wasStopped());
 }
 
 TEST_F(MediaSampleWriterTests, TestStopWithoutStart) {
@@ -340,7 +343,7 @@
     EXPECT_NE(writer->addTrack(mediaSource.mTrackFormats[0]), nullptr);
     EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::AddTrack(mediaSource.mTrackFormats[0].get()));
 
-    EXPECT_FALSE(writer->stop());
+    writer->stop();
     EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::NoEvent);
 }
 
@@ -468,7 +471,6 @@
     }
 
     EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::Stop());
-    EXPECT_TRUE(writer->stop());
     EXPECT_TRUE(mTestCallbacks->hasFinished());
 }
 
@@ -541,7 +543,6 @@
 
     // Wait for writer.
     mTestCallbacks->waitForWritingFinished();
-    EXPECT_TRUE(writer->stop());
 
     // Compare output file with source.
     mediaSource.reset();
diff --git a/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
index 83f0a4a..21f0b86 100644
--- a/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
@@ -61,13 +61,10 @@
         }
         ASSERT_NE(mTranscoder, nullptr);
 
-        initSampleReader();
+        initSampleReader("/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4");
     }
 
-    void initSampleReader() {
-        const char* sourcePath =
-                "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
-
+    void initSampleReader(const char* sourcePath) {
         const int sourceFd = open(sourcePath, O_RDONLY);
         ASSERT_GT(sourceFd, 0);
 
@@ -157,16 +154,23 @@
     ASSERT_TRUE(mTranscoder->start());
     drainOutputSamples();
     EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
-    EXPECT_TRUE(mTranscoder->stop());
+    EXPECT_TRUE(mCallback->transcodingFinished());
     EXPECT_TRUE(mGotEndOfStream);
 }
 
 TEST_P(MediaTrackTranscoderTests, StopNormalOperation) {
     LOG(DEBUG) << "Testing StopNormalOperation";
+
+    // Use a longer test asset to make sure that transcoding can be stopped.
+    initSampleReader("/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4");
+
     EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
               AMEDIA_OK);
     EXPECT_TRUE(mTranscoder->start());
-    EXPECT_TRUE(mTranscoder->stop());
+    mCallback->waitUntilTrackFormatAvailable();
+    mTranscoder->stop();
+    EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
+    EXPECT_TRUE(mCallback->transcodingWasStopped());
 }
 
 TEST_P(MediaTrackTranscoderTests, StartWithoutConfigure) {
@@ -178,17 +182,23 @@
     LOG(DEBUG) << "Testing StopWithoutStart";
     EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
               AMEDIA_OK);
-    EXPECT_FALSE(mTranscoder->stop());
+    mTranscoder->stop();
 }
 
 TEST_P(MediaTrackTranscoderTests, DoubleStartStop) {
     LOG(DEBUG) << "Testing DoubleStartStop";
+
+    // Use a longer test asset to make sure that transcoding can be stopped.
+    initSampleReader("/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4");
+
     EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
               AMEDIA_OK);
     EXPECT_TRUE(mTranscoder->start());
     EXPECT_FALSE(mTranscoder->start());
-    EXPECT_TRUE(mTranscoder->stop());
-    EXPECT_FALSE(mTranscoder->stop());
+    mTranscoder->stop();
+    mTranscoder->stop();
+    EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
+    EXPECT_TRUE(mCallback->transcodingWasStopped());
 }
 
 TEST_P(MediaTrackTranscoderTests, DoubleConfigure) {
@@ -212,7 +222,8 @@
     EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
               AMEDIA_OK);
     EXPECT_TRUE(mTranscoder->start());
-    EXPECT_TRUE(mTranscoder->stop());
+    mTranscoder->stop();
+    EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
     EXPECT_FALSE(mTranscoder->start());
 }
 
@@ -223,7 +234,7 @@
     ASSERT_TRUE(mTranscoder->start());
     drainOutputSamples();
     EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
-    EXPECT_TRUE(mTranscoder->stop());
+    mTranscoder->stop();
     EXPECT_FALSE(mTranscoder->start());
     EXPECT_TRUE(mGotEndOfStream);
 }
@@ -235,7 +246,7 @@
     ASSERT_TRUE(mTranscoder->start());
     drainOutputSamples(1 /* numSamplesToSave */);
     EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
-    EXPECT_TRUE(mTranscoder->stop());
+    mTranscoder->stop();
     EXPECT_TRUE(mGotEndOfStream);
 
     mTranscoder.reset();
@@ -251,7 +262,8 @@
     ASSERT_TRUE(mTranscoder->start());
     drainOutputSamples(1 /* numSamplesToSave */);
     mSamplesSavedSemaphore.wait();
-    EXPECT_TRUE(mTranscoder->stop());
+    mTranscoder->stop();
+    EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
 
     std::this_thread::sleep_for(std::chrono::milliseconds(20));
     mSavedSamples.clear();
@@ -272,6 +284,44 @@
               AMEDIA_OK);
 }
 
+TEST_P(MediaTrackTranscoderTests, StopOnSync) {
+    LOG(DEBUG) << "Testing StopOnSync";
+
+    // Use a longer test asset to make sure there is a GOP to finish.
+    initSampleReader("/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4");
+
+    EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+              AMEDIA_OK);
+
+    bool lastSampleWasEos = false;
+    bool lastRealSampleWasSync = false;
+    OneShotSemaphore samplesReceivedSemaphore;
+    uint32_t sampleCount = 0;
+
+    mTranscoder->setSampleConsumer([&](const std::shared_ptr<MediaSample>& sample) {
+        ASSERT_NE(sample, nullptr);
+
+        if ((lastSampleWasEos = sample->info.flags & SAMPLE_FLAG_END_OF_STREAM)) {
+            samplesReceivedSemaphore.signal();
+            return;
+        }
+        lastRealSampleWasSync = sample->info.flags & SAMPLE_FLAG_SYNC_SAMPLE;
+
+        if (++sampleCount >= 10) {  // Wait for a few samples before stopping.
+            samplesReceivedSemaphore.signal();
+        }
+    });
+
+    ASSERT_TRUE(mTranscoder->start());
+    samplesReceivedSemaphore.wait();
+    mTranscoder->stop(true /* stopOnSync */);
+    EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
+
+    EXPECT_TRUE(lastSampleWasEos);
+    EXPECT_TRUE(lastRealSampleWasSync);
+    EXPECT_TRUE(mCallback->transcodingWasStopped());
+}
+
 };  // namespace android
 
 using namespace android;
diff --git a/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
index 1bf2d8c..2487e08 100644
--- a/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
@@ -99,11 +99,11 @@
         }
     }
     media_status_t mStatus = AMEDIA_OK;
+    bool mFinished = false;
 
 private:
     std::mutex mMutex;
     std::condition_variable mCondition;
-    bool mFinished = false;
     bool mProgressMade = false;
 };
 
@@ -145,6 +145,8 @@
         kRunToCompletion,
         kCancelAfterProgress,
         kCancelAfterStart,
+        kPauseAfterProgress,
+        kPauseAfterStart,
     } TranscodeExecutionControl;
 
     using FormatConfigurationCallback = std::function<AMediaFormat*(AMediaFormat*)>;
@@ -181,7 +183,10 @@
 
         media_status_t startStatus = transcoder->start();
         EXPECT_EQ(startStatus, AMEDIA_OK);
+
         if (startStatus == AMEDIA_OK) {
+            std::shared_ptr<ndk::ScopedAParcel> pausedState;
+
             switch (executionControl) {
             case kCancelAfterProgress:
                 mCallbacks->waitForProgressMade();
@@ -189,6 +194,12 @@
             case kCancelAfterStart:
                 transcoder->cancel();
                 break;
+            case kPauseAfterProgress:
+                mCallbacks->waitForProgressMade();
+                FALLTHROUGH_INTENDED;
+            case kPauseAfterStart:
+                transcoder->pause(&pausedState);
+                break;
             case kRunToCompletion:
             default:
                 mCallbacks->waitForTranscodingFinished();
@@ -360,9 +371,10 @@
     const char* srcPath = "/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4";
     const char* destPath = "/data/local/tmp/MediaTranscoder_Cancel.MP4";
 
-    for (int i = 0; i < 32; ++i) {
+    for (int i = 0; i < 20; ++i) {
         EXPECT_EQ(transcodeHelper(srcPath, destPath, getAVCVideoFormat, kCancelAfterProgress),
                   AMEDIA_OK);
+        EXPECT_FALSE(mCallbacks->mFinished);
         mCallbacks = std::make_shared<TestCallbacks>();
     }
 }
@@ -371,9 +383,34 @@
     const char* srcPath = "/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4";
     const char* destPath = "/data/local/tmp/MediaTranscoder_Cancel.MP4";
 
-    for (int i = 0; i < 32; ++i) {
+    for (int i = 0; i < 20; ++i) {
         EXPECT_EQ(transcodeHelper(srcPath, destPath, getAVCVideoFormat, kCancelAfterStart),
                   AMEDIA_OK);
+        EXPECT_FALSE(mCallbacks->mFinished);
+        mCallbacks = std::make_shared<TestCallbacks>();
+    }
+}
+
+TEST_F(MediaTranscoderTests, TestPauseAfterProgress) {
+    const char* srcPath = "/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4";
+    const char* destPath = "/data/local/tmp/MediaTranscoder_Pause.MP4";
+
+    for (int i = 0; i < 20; ++i) {
+        EXPECT_EQ(transcodeHelper(srcPath, destPath, getAVCVideoFormat, kPauseAfterProgress),
+                  AMEDIA_OK);
+        EXPECT_FALSE(mCallbacks->mFinished);
+        mCallbacks = std::make_shared<TestCallbacks>();
+    }
+}
+
+TEST_F(MediaTranscoderTests, TestPauseAfterStart) {
+    const char* srcPath = "/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4";
+    const char* destPath = "/data/local/tmp/MediaTranscoder_Pause.MP4";
+
+    for (int i = 0; i < 20; ++i) {
+        EXPECT_EQ(transcodeHelper(srcPath, destPath, getAVCVideoFormat, kPauseAfterStart),
+                  AMEDIA_OK);
+        EXPECT_FALSE(mCallbacks->mFinished);
         mCallbacks = std::make_shared<TestCallbacks>();
     }
 }
diff --git a/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp
index 9713e17..5071efd 100644
--- a/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp
@@ -183,7 +183,6 @@
 
     callback->waitUntilFinished();
     EXPECT_EQ(sampleCount, sampleChecksums.size());
-    EXPECT_TRUE(transcoder.stop());
 }
 
 /** Class for testing PassthroughTrackTranscoder's built in buffer pool. */
diff --git a/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h b/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
index 8d05353..a782f71 100644
--- a/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
+++ b/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
@@ -33,20 +33,14 @@
             AMediaFormat* sourceFormat, bool includeBitrate = true) {
         // Default video destination format setup.
         static constexpr float kFrameRate = 30.0f;
-        static constexpr float kIFrameInterval = 30.0f;
         static constexpr int32_t kBitRate = 2 * 1000 * 1000;
-        static constexpr int32_t kColorFormatSurface = 0x7f000789;
 
         AMediaFormat* destinationFormat = AMediaFormat_new();
         AMediaFormat_copy(destinationFormat, sourceFormat);
         AMediaFormat_setFloat(destinationFormat, AMEDIAFORMAT_KEY_FRAME_RATE, kFrameRate);
-        AMediaFormat_setFloat(destinationFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL,
-                              kIFrameInterval);
         if (includeBitrate) {
             AMediaFormat_setInt32(destinationFormat, AMEDIAFORMAT_KEY_BIT_RATE, kBitRate);
         }
-        AMediaFormat_setInt32(destinationFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT,
-                              kColorFormatSurface);
 
         return std::shared_ptr<AMediaFormat>(destinationFormat, &AMediaFormat_delete);
     }
@@ -70,6 +64,13 @@
         mTranscodingFinishedCondition.notify_all();
     }
 
+    virtual void onTrackStopped(const MediaTrackTranscoder* transcoder __unused) override {
+        std::unique_lock<std::mutex> lock(mMutex);
+        mTranscodingFinished = true;
+        mTranscodingStopped = true;
+        mTranscodingFinishedCondition.notify_all();
+    }
+
     void onTrackError(const MediaTrackTranscoder* transcoder __unused, media_status_t status) {
         std::unique_lock<std::mutex> lock(mMutex);
         mTranscodingFinished = true;
@@ -93,12 +94,18 @@
         }
     }
 
+    bool transcodingWasStopped() const { return mTranscodingFinished && mTranscodingStopped; }
+    bool transcodingFinished() const {
+        return mTranscodingFinished && !mTranscodingStopped && mStatus == AMEDIA_OK;
+    }
+
 private:
     media_status_t mStatus = AMEDIA_OK;
     std::mutex mMutex;
     std::condition_variable mTranscodingFinishedCondition;
     std::condition_variable mTrackFormatAvailableCondition;
     bool mTranscodingFinished = false;
+    bool mTranscodingStopped = false;
     bool mTrackFormatAvailable = false;
 };
 
diff --git a/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
index 1b5bd13..4ede97f 100644
--- a/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
@@ -135,7 +135,6 @@
     });
 
     EXPECT_EQ(callback->waitUntilFinished(), AMEDIA_OK);
-    EXPECT_TRUE(transcoder->stop());
 }
 
 TEST_F(VideoTrackTranscoderTests, PreserveBitrate) {
@@ -160,7 +159,8 @@
     auto outputFormat = transcoder->getOutputFormat();
     ASSERT_NE(outputFormat, nullptr);
 
-    ASSERT_TRUE(transcoder->stop());
+    transcoder->stop();
+    EXPECT_EQ(callback->waitUntilFinished(), AMEDIA_OK);
 
     int32_t outBitrate;
     EXPECT_TRUE(AMediaFormat_getInt32(outputFormat.get(), AMEDIAFORMAT_KEY_BIT_RATE, &outBitrate));
@@ -205,7 +205,8 @@
     // Wait for the encoder to output samples before stopping and releasing the transcoder.
     semaphore.wait();
 
-    EXPECT_TRUE(transcoder->stop());
+    transcoder->stop();
+    EXPECT_EQ(callback->waitUntilFinished(), AMEDIA_OK);
     transcoder.reset();
 
     // Return buffers to the codec so that it can resume processing, but keep one buffer to avoid