transcoder: initial version of pause/resume

- Add pause/resume in TranscoderWrapper, save paused state on pause
  and use it to create new transcoder on resume.

Misc fixes:

- TranscoderWrapper::stop should only cancel transcoder if the stop
  is for the currently running job. Scheduler could call stop to
  cancel a job any time.
- Don't hold TranscoderWrapper lock when running event runnable. If
  the runnable calls back into scheduler, and scheduler may call
  transcoder again and deadlock.
- Don't report abort as error if the transcoder is cancelled explicitly.
- Push decoder/encoder start as msgs, so that they could be skipped too
  if the job is cancelled shortly after starts.

Tests:
Add tests for cancel/pause/resume with real transcoder.

bug: 154734285
bug: 154733948
test: unit testing
Change-Id: I2b7d3da69df53b92ab351db455310799ba0e0e8f
diff --git a/media/libmediatranscoding/TranscoderWrapper.cpp b/media/libmediatranscoding/TranscoderWrapper.cpp
index 428d86e..aaa15c4 100644
--- a/media/libmediatranscoding/TranscoderWrapper.cpp
+++ b/media/libmediatranscoding/TranscoderWrapper.cpp
@@ -56,6 +56,38 @@
     }
 }
 
+static AMediaFormat* getVideoFormat(
+        const char* originalMime,
+        const std::optional<TranscodingVideoTrackFormat>& requestedFormat) {
+    if (requestedFormat == std::nullopt) {
+        return nullptr;
+    }
+
+    AMediaFormat* format = AMediaFormat_new();
+    bool changed = false;
+    if (requestedFormat->codecType == TranscodingVideoCodecType::kHevc &&
+        strcmp(originalMime, AMEDIA_MIMETYPE_VIDEO_HEVC)) {
+        AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_HEVC);
+        changed = true;
+    } else if (requestedFormat->codecType == TranscodingVideoCodecType::kAvc &&
+               strcmp(originalMime, AMEDIA_MIMETYPE_VIDEO_AVC)) {
+        AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_AVC);
+        changed = true;
+    }
+    if (requestedFormat->bitrateBps > 0) {
+        AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, requestedFormat->bitrateBps);
+        changed = true;
+    }
+    // TODO: translate other fields from requestedFormat to the format for MediaTranscoder.
+    // Also need to determine more settings to expose in TranscodingVideoTrackFormat.
+    if (!changed) {
+        AMediaFormat_delete(format);
+        // Use null format for passthru.
+        format = nullptr;
+    }
+    return format;
+}
+
 //static
 const char* TranscoderWrapper::toString(Event::Type type) {
     switch (type) {
@@ -105,7 +137,7 @@
     }
 
     virtual void onCodecResourceLost(const MediaTranscoder* transcoder __unused,
-                                     const std::shared_ptr<const Parcelable>& pausedState
+                                     const std::shared_ptr<const Parcel>& pausedState
                                              __unused) override {
         ALOGV("%s: job {%lld, %d}", __FUNCTION__, (long long)mClientId, mJobId);
     }
@@ -126,51 +158,87 @@
 
 void TranscoderWrapper::start(ClientIdType clientId, JobIdType jobId,
                               const TranscodingRequestParcel& request,
-                              const std::shared_ptr<ITranscodingClientCallback>& callback) {
+                              const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
     queueEvent(Event::Start, clientId, jobId, [=] {
-        TranscodingErrorCode err = handleStart(clientId, jobId, request, callback);
+        TranscodingErrorCode err = handleStart(clientId, jobId, request, clientCb);
 
+        auto callback = mCallback.lock();
         if (err != TranscodingErrorCode::kNoError) {
             cleanup();
 
-            auto callback = mCallback.lock();
             if (callback != nullptr) {
                 callback->onError(clientId, jobId, err);
             }
+        } else {
+            if (callback != nullptr) {
+                callback->onStarted(clientId, jobId);
+            }
         }
     });
 }
 
 void TranscoderWrapper::pause(ClientIdType clientId, JobIdType jobId) {
-    queueEvent(Event::Pause, clientId, jobId, [] {});
+    queueEvent(Event::Pause, clientId, jobId, [=] {
+        TranscodingErrorCode err = handlePause(clientId, jobId);
+
+        cleanup();
+
+        auto callback = mCallback.lock();
+        if (callback != nullptr) {
+            if (err != TranscodingErrorCode::kNoError) {
+                callback->onError(clientId, jobId, err);
+            } else {
+                callback->onPaused(clientId, jobId);
+            }
+        }
+    });
 }
 
-void TranscoderWrapper::resume(ClientIdType clientId, JobIdType jobId) {
-    queueEvent(Event::Resume, clientId, jobId, [] {});
+void TranscoderWrapper::resume(ClientIdType clientId, JobIdType jobId,
+                               const TranscodingRequestParcel& request,
+                               const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
+    queueEvent(Event::Resume, clientId, jobId, [=] {
+        TranscodingErrorCode err = handleResume(clientId, jobId, request, clientCb);
+
+        auto callback = mCallback.lock();
+        if (err != TranscodingErrorCode::kNoError) {
+            cleanup();
+
+            if (callback != nullptr) {
+                callback->onError(clientId, jobId, err);
+            }
+        } else {
+            if (callback != nullptr) {
+                callback->onResumed(clientId, jobId);
+            }
+        }
+    });
 }
 
 void TranscoderWrapper::stop(ClientIdType clientId, JobIdType jobId) {
     queueEvent(Event::Stop, clientId, jobId, [=] {
-        if (clientId != mCurrentClientId || jobId != mCurrentJobId) {
-            ALOGW("Stopping job {%lld, %d} that's not current job {%lld, %d}", (long long)clientId,
-                  jobId, (long long)mCurrentClientId, mCurrentJobId);
-        }
-
-        // stop transcoder.
-        media_status_t err = mTranscoder->cancel();
-        if (err != AMEDIA_OK) {
-            ALOGE("failed to stop transcoder: %d", err);
+        if (mTranscoder != nullptr && clientId == mCurrentClientId && jobId == mCurrentJobId) {
+            // Cancelling the currently running job.
+            media_status_t err = mTranscoder->cancel();
+            if (err != AMEDIA_OK) {
+                ALOGE("failed to stop transcoder: %d", err);
+            } else {
+                ALOGI("transcoder stopped");
+            }
+            cleanup();
         } else {
-            ALOGI("transcoder stopped");
+            // For jobs that's not currently running, release any pausedState for the job.
+            mPausedStateMap.erase(JobKeyType(clientId, jobId));
         }
-
-        cleanup();
+        // No callback needed for stop.
     });
 }
 
 void TranscoderWrapper::onFinish(ClientIdType clientId, JobIdType jobId) {
     queueEvent(Event::Finish, clientId, jobId, [=] {
-        cleanup();
+        if (mTranscoder != nullptr && clientId == mCurrentClientId && jobId == mCurrentJobId) {
+            cleanup();
+        }
 
         auto callback = mCallback.lock();
         if (callback != nullptr) {
@@ -182,7 +250,9 @@
 void TranscoderWrapper::onError(ClientIdType clientId, JobIdType jobId,
                                 TranscodingErrorCode error) {
     queueEvent(Event::Error, clientId, jobId, [=] {
-        cleanup();
+        if (mTranscoder != nullptr && clientId == mCurrentClientId && jobId == mCurrentJobId) {
+            cleanup();
+        }
 
         auto callback = mCallback.lock();
         if (callback != nullptr) {
@@ -191,41 +261,10 @@
     });
 }
 
-static AMediaFormat* getVideoFormat(
-        const char* originalMime,
-        const std::optional<TranscodingVideoTrackFormat>& requestedFormat) {
-    if (requestedFormat == std::nullopt) {
-        return nullptr;
-    }
-
-    AMediaFormat* format = AMediaFormat_new();
-    bool changed = false;
-    if (requestedFormat->codecType == TranscodingVideoCodecType::kHevc &&
-        strcmp(originalMime, AMEDIA_MIMETYPE_VIDEO_HEVC)) {
-        AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_HEVC);
-        changed = true;
-    } else if (requestedFormat->codecType == TranscodingVideoCodecType::kAvc &&
-               strcmp(originalMime, AMEDIA_MIMETYPE_VIDEO_AVC)) {
-        AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_AVC);
-        changed = true;
-    }
-    if (requestedFormat->bitrateBps > 0) {
-        AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, requestedFormat->bitrateBps);
-        changed = true;
-    }
-    // TODO: translate other fields from requestedFormat to the format for MediaTranscoder.
-    // Also need to determine more settings to expose in TranscodingVideoTrackFormat.
-    if (!changed) {
-        AMediaFormat_delete(format);
-        // Use null format for passthru.
-        format = nullptr;
-    }
-    return format;
-}
-
-TranscodingErrorCode TranscoderWrapper::handleStart(
+TranscodingErrorCode TranscoderWrapper::setupTranscoder(
         ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
-        const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
+        const std::shared_ptr<ITranscodingClientCallback>& clientCb,
+        const std::shared_ptr<const Parcel>& pausedState) {
     if (clientCb == nullptr) {
         ALOGE("client callback is null");
         return TranscodingErrorCode::kInvalidParameter;
@@ -244,7 +283,10 @@
         return TranscodingErrorCode::kErrorIO;
     }
 
-    status = clientCb->openFileDescriptor(request.destinationFilePath, "w", &dstFd);
+    // Open dest file with "rw", as the transcoder could potentially reuse part of it
+    // for resume case. We might want the further differentiate and open with "w" only
+    // for start.
+    status = clientCb->openFileDescriptor(request.destinationFilePath, "rw", &dstFd);
     if (!status.isOk() || dstFd.get() < 0) {
         ALOGE("failed to open destination");
         return TranscodingErrorCode::kErrorIO;
@@ -253,7 +295,7 @@
     mCurrentClientId = clientId;
     mCurrentJobId = jobId;
     mTranscoderCb = std::make_shared<CallbackImpl>(shared_from_this(), clientId, jobId);
-    mTranscoder = MediaTranscoder::create(mTranscoderCb, nullptr);
+    mTranscoder = MediaTranscoder::create(mTranscoderCb, pausedState);
     if (mTranscoder == nullptr) {
         ALOGE("failed to create transcoder");
         return TranscodingErrorCode::kUnknown;
@@ -296,13 +338,79 @@
         return toTranscodingError(err);
     }
 
-    err = mTranscoder->start();
-    if (err != AMEDIA_OK) {
-        ALOGE("failed to start transcoder: %d", err);
-        return toTranscodingError(err);
+    return TranscodingErrorCode::kNoError;
+}
+
+TranscodingErrorCode TranscoderWrapper::handleStart(
+        ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
+        const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
+    ALOGI("setting up transcoder for start");
+    TranscodingErrorCode err = setupTranscoder(clientId, jobId, request, clientCb);
+    if (err != TranscodingErrorCode::kNoError) {
+        ALOGI("%s: failed to setup transcoder", __FUNCTION__);
+        return err;
     }
 
-    ALOGI("transcoder started");
+    media_status_t status = mTranscoder->start();
+    if (status != AMEDIA_OK) {
+        ALOGE("%s: failed to start transcoder: %d", __FUNCTION__, err);
+        return toTranscodingError(status);
+    }
+
+    ALOGI("%s: transcoder started", __FUNCTION__);
+    return TranscodingErrorCode::kNoError;
+}
+
+TranscodingErrorCode TranscoderWrapper::handlePause(ClientIdType clientId, JobIdType jobId) {
+    if (mTranscoder == nullptr) {
+        ALOGE("%s: transcoder is not running", __FUNCTION__);
+        return TranscodingErrorCode::kInvalidOperation;
+    }
+
+    if (clientId != mCurrentClientId || jobId != mCurrentJobId) {
+        ALOGW("%s: stopping job {%lld, %d} that's not current job {%lld, %d}", __FUNCTION__,
+              (long long)clientId, jobId, (long long)mCurrentClientId, mCurrentJobId);
+    }
+
+    std::shared_ptr<const Parcel> pauseStates;
+    media_status_t err = mTranscoder->pause(&pauseStates);
+    if (err != AMEDIA_OK) {
+        ALOGE("%s: failed to pause transcoder: %d", __FUNCTION__, err);
+        return toTranscodingError(err);
+    }
+    mPausedStateMap[JobKeyType(clientId, jobId)] = pauseStates;
+
+    ALOGI("%s: transcoder paused", __FUNCTION__);
+    return TranscodingErrorCode::kNoError;
+}
+
+TranscodingErrorCode TranscoderWrapper::handleResume(
+        ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
+        const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
+    std::shared_ptr<const Parcel> pausedState;
+    auto it = mPausedStateMap.find(JobKeyType(clientId, jobId));
+    if (it != mPausedStateMap.end()) {
+        pausedState = it->second;
+        mPausedStateMap.erase(it);
+    } else {
+        ALOGE("%s: can't find paused state", __FUNCTION__);
+        return TranscodingErrorCode::kInvalidOperation;
+    }
+
+    ALOGI("setting up transcoder for resume");
+    TranscodingErrorCode err = setupTranscoder(clientId, jobId, request, clientCb, pausedState);
+    if (err != TranscodingErrorCode::kNoError) {
+        ALOGE("%s: failed to setup transcoder", __FUNCTION__);
+        return err;
+    }
+
+    media_status_t status = mTranscoder->resume();
+    if (status != AMEDIA_OK) {
+        ALOGE("%s: failed to resume transcoder: %d", __FUNCTION__, err);
+        return toTranscodingError(status);
+    }
+
+    ALOGI("%s: transcoder resumed", __FUNCTION__);
     return TranscodingErrorCode::kNoError;
 }
 
@@ -339,7 +447,9 @@
         ALOGD("%s: job {%lld, %d}: %s", __FUNCTION__, (long long)event.clientId, event.jobId,
               toString(event.type));
 
+        lock.unlock();
         event.runnable();
+        lock.lock();
     }
 }
 
diff --git a/media/libmediatranscoding/TranscodingJobScheduler.cpp b/media/libmediatranscoding/TranscodingJobScheduler.cpp
index 07eb949..3e4f319 100644
--- a/media/libmediatranscoding/TranscodingJobScheduler.cpp
+++ b/media/libmediatranscoding/TranscodingJobScheduler.cpp
@@ -79,7 +79,8 @@
                 mTranscoder->start(topJob->key.first, topJob->key.second, topJob->request,
                                    topJob->callback.lock());
             } else if (topJob->state == Job::PAUSED) {
-                mTranscoder->resume(topJob->key.first, topJob->key.second);
+                mTranscoder->resume(topJob->key.first, topJob->key.second, topJob->request,
+                                    topJob->callback.lock());
             }
             topJob->state = Job::RUNNING;
         }
diff --git a/media/libmediatranscoding/include/media/TranscoderInterface.h b/media/libmediatranscoding/include/media/TranscoderInterface.h
index 85e0164..1a3f505 100644
--- a/media/libmediatranscoding/include/media/TranscoderInterface.h
+++ b/media/libmediatranscoding/include/media/TranscoderInterface.h
@@ -32,14 +32,14 @@
 // Interface for the scheduler to call the transcoder to take actions.
 class TranscoderInterface {
 public:
-    // TODO(chz): determine what parameters are needed here.
-    // For now, always pass in clientId&jobId.
     virtual void setCallback(const std::shared_ptr<TranscoderCallbackInterface>& cb) = 0;
     virtual void start(ClientIdType clientId, JobIdType jobId,
                        const TranscodingRequestParcel& request,
                        const std::shared_ptr<ITranscodingClientCallback>& clientCallback) = 0;
     virtual void pause(ClientIdType clientId, JobIdType jobId) = 0;
-    virtual void resume(ClientIdType clientId, JobIdType jobId) = 0;
+    virtual void resume(ClientIdType clientId, JobIdType jobId,
+                        const TranscodingRequestParcel& request,
+                        const std::shared_ptr<ITranscodingClientCallback>& clientCallback) = 0;
     virtual void stop(ClientIdType clientId, JobIdType jobId) = 0;
 
 protected:
diff --git a/media/libmediatranscoding/include/media/TranscoderWrapper.h b/media/libmediatranscoding/include/media/TranscoderWrapper.h
index ba48085..804119f 100644
--- a/media/libmediatranscoding/include/media/TranscoderWrapper.h
+++ b/media/libmediatranscoding/include/media/TranscoderWrapper.h
@@ -21,11 +21,13 @@
 #include <media/TranscoderInterface.h>
 
 #include <list>
+#include <map>
 #include <mutex>
 
 namespace android {
 
 class MediaTranscoder;
+class Parcelable;
 
 /*
  * Wrapper class around MediaTranscoder.
@@ -41,7 +43,9 @@
                        const TranscodingRequestParcel& request,
                        const std::shared_ptr<ITranscodingClientCallback>& clientCallback) override;
     virtual void pause(ClientIdType clientId, JobIdType jobId) override;
-    virtual void resume(ClientIdType clientId, JobIdType jobId) override;
+    virtual void resume(ClientIdType clientId, JobIdType jobId,
+                        const TranscodingRequestParcel& request,
+                        const std::shared_ptr<ITranscodingClientCallback>& clientCallback) override;
     virtual void stop(ClientIdType clientId, JobIdType jobId) override;
 
 private:
@@ -52,12 +56,15 @@
         JobIdType jobId;
         std::function<void()> runnable;
     };
+    using JobKeyType = std::pair<ClientIdType, JobIdType>;
+
     std::shared_ptr<CallbackImpl> mTranscoderCb;
     std::shared_ptr<MediaTranscoder> mTranscoder;
     std::weak_ptr<TranscoderCallbackInterface> mCallback;
     std::mutex mLock;
     std::condition_variable mCondition;
     std::list<Event> mQueue;  // GUARDED_BY(mLock);
+    std::map<JobKeyType, std::shared_ptr<const Parcel>> mPausedStateMap;
     ClientIdType mCurrentClientId;
     JobIdType mCurrentJobId;
 
@@ -68,6 +75,14 @@
     TranscodingErrorCode handleStart(ClientIdType clientId, JobIdType jobId,
                                      const TranscodingRequestParcel& request,
                                      const std::shared_ptr<ITranscodingClientCallback>& callback);
+    TranscodingErrorCode handlePause(ClientIdType clientId, JobIdType jobId);
+    TranscodingErrorCode handleResume(ClientIdType clientId, JobIdType jobId,
+                                      const TranscodingRequestParcel& request,
+                                      const std::shared_ptr<ITranscodingClientCallback>& callback);
+    TranscodingErrorCode setupTranscoder(
+            ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
+            const std::shared_ptr<ITranscodingClientCallback>& callback,
+            const std::shared_ptr<const Parcel>& pausedState = nullptr);
 
     void cleanup();
     void queueEvent(Event::Type type, ClientIdType clientId, JobIdType jobId,
diff --git a/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp b/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
index 1931a0e..d21b595 100644
--- a/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
+++ b/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
@@ -94,7 +94,8 @@
     void pause(ClientIdType clientId, JobIdType jobId) override {
         mEventQueue.push_back(Pause(clientId, jobId));
     }
-    void resume(ClientIdType clientId, JobIdType jobId) override {
+    void resume(ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& /*request*/,
+                const std::shared_ptr<ITranscodingClientCallback>& /*clientCallback*/) override {
         mEventQueue.push_back(Resume(clientId, jobId));
     }
     void stop(ClientIdType clientId, JobIdType jobId) override {
diff --git a/media/libmediatranscoding/tests/assets/longtest_15s.mp4 b/media/libmediatranscoding/tests/assets/longtest_15s.mp4
new file mode 100644
index 0000000..b50d8e4
--- /dev/null
+++ b/media/libmediatranscoding/tests/assets/longtest_15s.mp4
Binary files differ
diff --git a/media/libmediatranscoding/transcoder/Android.bp b/media/libmediatranscoding/transcoder/Android.bp
index 59cd96d..c153a42 100644
--- a/media/libmediatranscoding/transcoder/Android.bp
+++ b/media/libmediatranscoding/transcoder/Android.bp
@@ -34,6 +34,8 @@
         "libmediandk",
         "libnativewindow",
         "libutils",
+        // TODO: Use libbinder_ndk
+        "libbinder",
     ],
 
     export_include_dirs: [
diff --git a/media/libmediatranscoding/transcoder/MediaTranscoder.cpp b/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
index 69932f4..bde1cf6 100644
--- a/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
@@ -18,6 +18,7 @@
 #define LOG_TAG "MediaTranscoder"
 
 #include <android-base/logging.h>
+#include <binder/Parcel.h>
 #include <fcntl.h>
 #include <media/MediaSampleReaderNDK.h>
 #include <media/MediaSampleWriter.h>
@@ -84,6 +85,17 @@
 }
 
 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) {
+        return;
+    }
+
     bool expected = false;
     if (mCallbackSent.compare_exchange_strong(expected, true)) {
         if (status == AMEDIA_OK) {
@@ -149,11 +161,11 @@
 
 std::shared_ptr<MediaTranscoder> MediaTranscoder::create(
         const std::shared_ptr<CallbackInterface>& callbacks,
-        const std::shared_ptr<Parcel>& pausedState) {
+        const std::shared_ptr<const Parcel>& pausedState) {
     if (pausedState != nullptr) {
-        LOG(ERROR) << "Initializing from paused state is currently not supported.";
-        return nullptr;
-    } else if (callbacks == nullptr) {
+        LOG(INFO) << "Initializing from paused state.";
+    }
+    if (callbacks == nullptr) {
         LOG(ERROR) << "Callbacks cannot be null";
         return nullptr;
     }
@@ -309,15 +321,15 @@
     return AMEDIA_OK;
 }
 
-media_status_t MediaTranscoder::pause(std::shared_ptr<const Parcelable>* pausedState) {
-    (void)pausedState;
-    LOG(ERROR) << "Pause is not currently supported";
-    return AMEDIA_ERROR_UNSUPPORTED;
+media_status_t MediaTranscoder::pause(std::shared_ptr<const Parcel>* pausedState) {
+    // TODO: write internal states to parcel.
+    *pausedState = std::make_shared<Parcel>();
+    return cancel();
 }
 
 media_status_t MediaTranscoder::resume() {
-    LOG(ERROR) << "Resume is not currently supported";
-    return AMEDIA_ERROR_UNSUPPORTED;
+    // TODO: restore internal states from parcel.
+    return start();
 }
 
 media_status_t MediaTranscoder::cancel() {
diff --git a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
index 4df3296..ca94b16 100644
--- a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
@@ -380,20 +380,24 @@
 }
 
 media_status_t VideoTrackTranscoder::runTranscodeLoop() {
-    media_status_t status = AMEDIA_OK;
+    // Push start decoder and encoder as two messages, so that these are subject to the
+    // stop request as well. If the job is cancelled (or paused) immediately after start,
+    // we don't need to waste time start then stop the codecs.
+    mCodecMessageQueue.push([this] {
+        media_status_t status = AMediaCodec_start(mDecoder);
+        if (status != AMEDIA_OK) {
+            LOG(ERROR) << "Unable to start video decoder: " << status;
+            mStatus = status;
+        }
+    });
 
-    status = AMediaCodec_start(mDecoder);
-    if (status != AMEDIA_OK) {
-        LOG(ERROR) << "Unable to start video decoder: " << status;
-        return status;
-    }
-
-    status = AMediaCodec_start(mEncoder.get());
-    if (status != AMEDIA_OK) {
-        LOG(ERROR) << "Unable to start video encoder: " << status;
-        AMediaCodec_stop(mDecoder);
-        return status;
-    }
+    mCodecMessageQueue.push([this] {
+        media_status_t status = AMediaCodec_start(mEncoder.get());
+        if (status != AMEDIA_OK) {
+            LOG(ERROR) << "Unable to start video encoder: " << status;
+            mStatus = status;
+        }
+    });
 
     // Process codec events until EOS is reached, transcoding is stopped or an error occurs.
     while (!mStopRequested && !mEosFromEncoder && mStatus == AMEDIA_OK) {
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h b/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
index e5a5d64..7a36c8c 100644
--- a/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
@@ -17,8 +17,6 @@
 #ifndef ANDROID_MEDIA_TRANSCODER_H
 #define ANDROID_MEDIA_TRANSCODER_H
 
-#include <binder/Parcel.h>
-#include <binder/Parcelable.h>
 #include <media/MediaTrackTranscoderCallback.h>
 #include <media/NdkMediaError.h>
 #include <media/NdkMediaFormat.h>
@@ -33,6 +31,7 @@
 
 class MediaSampleReader;
 class MediaSampleWriter;
+class Parcel;
 
 class MediaTranscoder : public std::enable_shared_from_this<MediaTranscoder>,
                         public MediaTrackTranscoderCallback {
@@ -57,7 +56,7 @@
          *      resume.
          */
         virtual void onCodecResourceLost(const MediaTranscoder* transcoder,
-                                         const std::shared_ptr<const Parcelable>& pausedState) = 0;
+                                         const std::shared_ptr<const Parcel>& pausedState) = 0;
 
         virtual ~CallbackInterface() = default;
     };
@@ -69,7 +68,7 @@
      */
     static std::shared_ptr<MediaTranscoder> create(
             const std::shared_ptr<CallbackInterface>& callbacks,
-            const std::shared_ptr<Parcel>& pausedState = nullptr);
+            const std::shared_ptr<const Parcel>& pausedState = nullptr);
 
     /** Configures source from path fd. */
     media_status_t configureSource(int fd);
@@ -102,8 +101,12 @@
      * 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.
+     *
+     * TODO: use NDK AParcel instead
+     * libbinder shouldn't be used by mainline modules. When transcoding goes mainline
+     * it needs to be replaced by stable AParcel.
      */
-    media_status_t pause(std::shared_ptr<const Parcelable>* pausedState);
+    media_status_t pause(std::shared_ptr<const Parcel>* pausedState);
 
     /** Resumes a paused transcoding. */
     media_status_t resume();
diff --git a/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
index 67f1ca1..330261c 100644
--- a/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
@@ -20,6 +20,7 @@
 #define LOG_TAG "MediaTranscoderTests"
 
 #include <android-base/logging.h>
+#include <fcntl.h>
 #include <gtest/gtest.h>
 #include <media/MediaSampleReaderNDK.h>
 #include <media/MediaTranscoder.h>
@@ -74,7 +75,7 @@
                                   int32_t progress __unused) override {}
 
     virtual void onCodecResourceLost(const MediaTranscoder* transcoder __unused,
-                                     const std::shared_ptr<const Parcelable>& pausedState
+                                     const std::shared_ptr<const Parcel>& pausedState
                                              __unused) override {}
 
     void waitForTranscodingFinished() {