Transcoder: Added MediaTranscoder and unit test.

MediaTranscoder is the API for the native transcoding library.

Test: Unit tests.
Bug: 156003955, 152091443, 155918341
Change-Id: I24b52d174db0faecea8f331ef6d8a3dc4e473c4e
diff --git a/media/libmediatranscoding/transcoder/Android.bp b/media/libmediatranscoding/transcoder/Android.bp
index 0b7ddbb..843d047 100644
--- a/media/libmediatranscoding/transcoder/Android.bp
+++ b/media/libmediatranscoding/transcoder/Android.bp
@@ -22,6 +22,7 @@
         "MediaSampleReaderNDK.cpp",
         "MediaSampleWriter.cpp",
         "MediaTrackTranscoder.cpp",
+        "MediaTranscoder.cpp",
         "PassthroughTrackTranscoder.cpp",
         "VideoTrackTranscoder.cpp",
     ],
diff --git a/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
index 1673b5b..10c0c6c 100644
--- a/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
@@ -101,4 +101,8 @@
     return false;
 }
 
-}  // namespace android
\ No newline at end of file
+std::shared_ptr<MediaSampleQueue> MediaTrackTranscoder::getOutputQueue() const {
+    return mOutputQueue;
+}
+
+}  // namespace android
diff --git a/media/libmediatranscoding/transcoder/MediaTranscoder.cpp b/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
new file mode 100644
index 0000000..f2f7810
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "MediaTranscoder"
+
+#include <android-base/logging.h>
+#include <fcntl.h>
+#include <media/MediaSampleReaderNDK.h>
+#include <media/MediaTranscoder.h>
+#include <media/PassthroughTrackTranscoder.h>
+#include <media/VideoTrackTranscoder.h>
+#include <unistd.h>
+
+namespace android {
+
+#define DEFINE_FORMAT_VALUE_COPY_FUNC(_type, _typeName)                                  \
+    static void copy##_typeName(const char* key, AMediaFormat* to, AMediaFormat* from) { \
+        _type value;                                                                     \
+        if (AMediaFormat_get##_typeName(from, key, &value)) {                            \
+            AMediaFormat_set##_typeName(to, key, value);                                 \
+        }                                                                                \
+    }
+
+DEFINE_FORMAT_VALUE_COPY_FUNC(const char*, String);
+DEFINE_FORMAT_VALUE_COPY_FUNC(int64_t, Int64);
+DEFINE_FORMAT_VALUE_COPY_FUNC(int32_t, Int32);
+
+static AMediaFormat* mergeMediaFormats(AMediaFormat* base, AMediaFormat* overlay) {
+    if (base == nullptr || overlay == nullptr) {
+        LOG(ERROR) << "Cannot merge null formats";
+        return nullptr;
+    }
+
+    AMediaFormat* format = AMediaFormat_new();
+    if (AMediaFormat_copy(format, base) != AMEDIA_OK) {
+        AMediaFormat_delete(format);
+        return nullptr;
+    }
+
+    // Note: AMediaFormat does not expose a function for appending values from another format or for
+    // iterating over all values and keys in a format. Instead we define a static list of known keys
+    // along with their value types and copy the ones that are present. A better solution would be
+    // to either implement required functions in NDK or to parse the overlay format's string
+    // representation and copy all existing keys.
+    static const struct {
+        const char* key;
+        void (*copyValue)(const char* key, AMediaFormat* to, AMediaFormat* from);
+    } kSupportedConfigs[] = {
+            {AMEDIAFORMAT_KEY_MIME, copyString},
+            {AMEDIAFORMAT_KEY_DURATION, copyInt64},
+            {AMEDIAFORMAT_KEY_WIDTH, copyInt32},
+            {AMEDIAFORMAT_KEY_HEIGHT, copyInt32},
+            {AMEDIAFORMAT_KEY_BIT_RATE, copyInt32},
+            {AMEDIAFORMAT_KEY_PROFILE, copyInt32},
+            {AMEDIAFORMAT_KEY_LEVEL, copyInt32},
+            {AMEDIAFORMAT_KEY_COLOR_FORMAT, copyInt32},
+            {AMEDIAFORMAT_KEY_COLOR_RANGE, copyInt32},
+            {AMEDIAFORMAT_KEY_COLOR_STANDARD, copyInt32},
+            {AMEDIAFORMAT_KEY_COLOR_TRANSFER, copyInt32},
+            {AMEDIAFORMAT_KEY_FRAME_RATE, copyInt32},
+            {AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, copyInt32},
+    };
+
+    for (int i = 0; i < (sizeof(kSupportedConfigs) / sizeof(kSupportedConfigs[0])); ++i) {
+        kSupportedConfigs[i].copyValue(kSupportedConfigs[i].key, format, overlay);
+    }
+
+    return format;
+}
+
+void MediaTranscoder::sendCallback(media_status_t status) {
+    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 then
+        // automatically delete the output file.
+        const bool deleteOutputFile = status != AMEDIA_OK;
+        std::thread asyncCancelThread{
+                [self = shared_from_this(), deleteOutputFile] { self->cancel(deleteOutputFile); }};
+        asyncCancelThread.detach();
+    }
+}
+
+void MediaTranscoder::onTrackFinished(const MediaTrackTranscoder* transcoder) {
+    LOG(DEBUG) << "TrackTranscoder " << transcoder << " finished";
+}
+
+void MediaTranscoder::onTrackError(const MediaTrackTranscoder* transcoder, media_status_t status) {
+    LOG(DEBUG) << "TrackTranscoder " << transcoder << " returned error " << status;
+    sendCallback(status);
+}
+
+void MediaTranscoder::onSampleWriterFinished(media_status_t status) {
+    LOG((status != AMEDIA_OK) ? ERROR : DEBUG) << "Sample writer finished with status " << status;
+    sendCallback(status);
+}
+
+std::shared_ptr<MediaTranscoder> MediaTranscoder::create(
+        const std::shared_ptr<CallbackInterface>& callbacks,
+        const std::shared_ptr<Parcel>& pausedState) {
+    if (pausedState != nullptr) {
+        LOG(ERROR) << "Initializing from paused state is currently not supported.";
+        return nullptr;
+    } else if (callbacks == nullptr) {
+        LOG(ERROR) << "Callbacks cannot be null";
+        return nullptr;
+    }
+
+    return std::shared_ptr<MediaTranscoder>(new MediaTranscoder(callbacks));
+}
+
+media_status_t MediaTranscoder::configureSource(const char* path) {
+    if (path == nullptr) {
+        LOG(ERROR) << "Source path cannot be null";
+        return AMEDIA_ERROR_INVALID_PARAMETER;
+    }
+
+    const int fd = open(path, O_RDONLY);
+    if (fd <= 0) {
+        LOG(ERROR) << "Unable to open source path: " << path;
+        return AMEDIA_ERROR_INVALID_PARAMETER;
+    }
+
+    const size_t fileSize = lseek(fd, 0, SEEK_END);
+    lseek(fd, 0, SEEK_SET);
+
+    mSampleReader = MediaSampleReaderNDK::createFromFd(fd, 0 /* offset */, fileSize);
+    close(fd);
+
+    if (mSampleReader == nullptr) {
+        LOG(ERROR) << "Unable to parse source file: " << path;
+        return AMEDIA_ERROR_UNSUPPORTED;
+    }
+
+    const size_t trackCount = mSampleReader->getTrackCount();
+    for (size_t trackIndex = 0; trackIndex < trackCount; ++trackIndex) {
+        AMediaFormat* trackFormat = mSampleReader->getTrackFormat(static_cast<int>(trackIndex));
+        if (trackFormat == nullptr) {
+            LOG(ERROR) << "Track #" << trackIndex << " has no format";
+            return AMEDIA_ERROR_MALFORMED;
+        }
+
+        mSourceTrackFormats.emplace_back(trackFormat, &AMediaFormat_delete);
+    }
+
+    return AMEDIA_OK;
+}
+
+std::vector<std::shared_ptr<AMediaFormat>> MediaTranscoder::getTrackFormats() const {
+    // Return a deep copy of the formats to avoid the caller modifying our internal formats.
+    std::vector<std::shared_ptr<AMediaFormat>> trackFormats;
+    for (const std::shared_ptr<AMediaFormat>& sourceFormat : mSourceTrackFormats) {
+        AMediaFormat* copy = AMediaFormat_new();
+        AMediaFormat_copy(copy, sourceFormat.get());
+        trackFormats.emplace_back(copy, &AMediaFormat_delete);
+    }
+    return trackFormats;
+}
+
+media_status_t MediaTranscoder::configureTrackFormat(size_t trackIndex, AMediaFormat* trackFormat) {
+    if (mSampleReader == nullptr) {
+        LOG(ERROR) << "Source must be configured before tracks";
+        return AMEDIA_ERROR_INVALID_OPERATION;
+    } else if (trackIndex >= mSourceTrackFormats.size()) {
+        LOG(ERROR) << "Track index " << trackIndex
+                   << " is out of bounds. Track count: " << mSourceTrackFormats.size();
+        return AMEDIA_ERROR_INVALID_PARAMETER;
+    }
+
+    std::unique_ptr<MediaTrackTranscoder> transcoder = nullptr;
+    std::shared_ptr<AMediaFormat> format = nullptr;
+
+    if (trackFormat == nullptr) {
+        transcoder = std::make_unique<PassthroughTrackTranscoder>(shared_from_this());
+    } else {
+        const char* srcMime = nullptr;
+        if (!AMediaFormat_getString(mSourceTrackFormats[trackIndex].get(), AMEDIAFORMAT_KEY_MIME,
+                                    &srcMime)) {
+            LOG(ERROR) << "Source track #" << trackIndex << " has no mime type";
+            return AMEDIA_ERROR_MALFORMED;
+        }
+
+        if (strncmp(srcMime, "video/", 6) != 0) {
+            LOG(ERROR) << "Only video tracks are supported for transcoding. Unable to configure "
+                          "track #"
+                       << trackIndex << " with mime " << srcMime;
+            return AMEDIA_ERROR_UNSUPPORTED;
+        }
+
+        const char* dstMime = nullptr;
+        if (AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &dstMime)) {
+            if (strncmp(dstMime, "video/", 6) != 0) {
+                LOG(ERROR) << "Unable to convert media types for track #" << trackIndex << ", from "
+                           << srcMime << " to " << dstMime;
+                return AMEDIA_ERROR_UNSUPPORTED;
+            }
+        }
+
+        transcoder = std::make_unique<VideoTrackTranscoder>(shared_from_this());
+
+        AMediaFormat* mergedFormat =
+                mergeMediaFormats(mSourceTrackFormats[trackIndex].get(), trackFormat);
+        if (mergedFormat == nullptr) {
+            LOG(ERROR) << "Unable to merge source and destination formats";
+            return AMEDIA_ERROR_UNKNOWN;
+        }
+
+        format = std::shared_ptr<AMediaFormat>(mergedFormat, &AMediaFormat_delete);
+    }
+
+    media_status_t status = transcoder->configure(mSampleReader, trackIndex, format);
+    if (status != AMEDIA_OK) {
+        LOG(ERROR) << "Configure track transcoder for track #" << trackIndex << " returned error "
+                   << status;
+        return status;
+    }
+
+    mTrackTranscoders.emplace_back(std::move(transcoder));
+    return AMEDIA_OK;
+}
+
+media_status_t MediaTranscoder::configureDestination(const char* path) {
+    if (path == nullptr || strlen(path) < 1) {
+        LOG(ERROR) << "Invalid destination path: " << path;
+        return AMEDIA_ERROR_INVALID_PARAMETER;
+    } else if (mSampleWriter != nullptr) {
+        LOG(ERROR) << "Destination is already configured.";
+        return AMEDIA_ERROR_INVALID_OPERATION;
+    }
+
+    // Write-only, create file if non-existent, don't overwrite existing file.
+    static constexpr int kOpenFlags = O_WRONLY | O_CREAT | O_EXCL;
+    // User R+W permission.
+    static constexpr int kFileMode = S_IRUSR | S_IWUSR;
+
+    const int fd = open(path, kOpenFlags, kFileMode);
+    if (fd < 0) {
+        LOG(ERROR) << "Unable to open destination file \"" << path << "\" for writing: " << fd;
+        return AMEDIA_ERROR_INVALID_PARAMETER;
+    }
+
+    mDestinationPath = std::string(path);
+
+    mSampleWriter = std::make_unique<MediaSampleWriter>();
+    const bool initOk = mSampleWriter->init(
+            fd, std::bind(&MediaTranscoder::onSampleWriterFinished, this, std::placeholders::_1));
+    close(fd);
+
+    if (!initOk) {
+        LOG(ERROR) << "Unable to initialize sample writer with destination path " << path;
+        mSampleWriter.reset();
+        return AMEDIA_ERROR_UNKNOWN;
+    }
+
+    return AMEDIA_OK;
+}
+
+media_status_t MediaTranscoder::start() {
+    if (mTrackTranscoders.size() < 1) {
+        LOG(ERROR) << "Unable to start, no tracks are configured.";
+        return AMEDIA_ERROR_INVALID_OPERATION;
+    } else if (mSampleWriter == nullptr) {
+        LOG(ERROR) << "Unable to start, destination is not configured";
+        return AMEDIA_ERROR_INVALID_OPERATION;
+    }
+
+    // Add tracks to the writer.
+    for (auto& transcoder : mTrackTranscoders) {
+        const bool ok = mSampleWriter->addTrack(transcoder->getOutputQueue(),
+                                                transcoder->getOutputFormat());
+        if (!ok) {
+            LOG(ERROR) << "Unable to add track to sample writer.";
+            return AMEDIA_ERROR_UNKNOWN;
+        }
+    }
+
+    bool started = mSampleWriter->start();
+    if (!started) {
+        LOG(ERROR) << "Unable to start sample writer.";
+        return AMEDIA_ERROR_UNKNOWN;
+    }
+
+    // Start transcoders
+    for (auto& transcoder : mTrackTranscoders) {
+        started = transcoder->start();
+        if (!started) {
+            LOG(ERROR) << "Unable to start track transcoder.";
+            cancel(true);
+            return AMEDIA_ERROR_UNKNOWN;
+        }
+    }
+    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::resume() {
+    LOG(ERROR) << "Resume is not currently supported";
+    return AMEDIA_ERROR_UNSUPPORTED;
+}
+
+media_status_t MediaTranscoder::cancel(bool deleteDestinationFile) {
+    bool expected = false;
+    if (!mCancelled.compare_exchange_strong(expected, true)) {
+        // Already cancelled.
+        return AMEDIA_OK;
+    }
+
+    mSampleWriter->stop();
+    for (auto& transcoder : mTrackTranscoders) {
+        transcoder->stop();
+    }
+
+    // TODO(chz): file deletion should be done by upper level from the content URI.
+    if (deleteDestinationFile && !mDestinationPath.empty()) {
+        int error = unlink(mDestinationPath.c_str());
+        if (error) {
+            LOG(ERROR) << "Unable to delete destination file " << mDestinationPath.c_str() << ": "
+                       << error;
+            return AMEDIA_ERROR_IO;
+        }
+    }
+    return AMEDIA_OK;
+}
+
+}  // namespace android
diff --git a/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp
index 4404bbb..7806208 100644
--- a/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp
@@ -134,7 +134,7 @@
         }
 
         sample->info = info;
-        if (mOutputQueue.enqueue(sample)) {
+        if (mOutputQueue->enqueue(sample)) {
             LOG(ERROR) << "Output queue aborted";
             return AMEDIA_ERROR_IO;
         }
@@ -153,4 +153,7 @@
     mBufferPool->abort();
 }
 
+std::shared_ptr<AMediaFormat> PassthroughTrackTranscoder::getOutputFormat() const {
+    return mSourceFormat;
+}
 }  // namespace android
diff --git a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
index 311e9be..3818545 100644
--- a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
@@ -30,6 +30,11 @@
 static_assert(SAMPLE_FLAG_PARTIAL_FRAME == AMEDIACODEC_BUFFER_FLAG_PARTIAL_FRAME,
               "Sample flag mismatch: PARTIAL_FRAME");
 
+// Color format defined by surface. (See MediaCodecInfo.CodecCapabilities#COLOR_FormatSurface.)
+static constexpr int32_t kColorFormatSurface = 0x7f000789;
+// Default key frame interval in seconds.
+static constexpr float kDefaultKeyFrameIntervalSeconds = 1.0f;
+
 template <typename T>
 void VideoTrackTranscoder::BlockingQueue<T>::push(T const& value, bool front) {
     {
@@ -113,11 +118,24 @@
     media_status_t status = AMEDIA_OK;
 
     if (destinationFormat == nullptr) {
-        LOG(ERROR) << "Destination format is null";
+        LOG(ERROR) << "Destination format is null, use passthrough transcoder";
         return AMEDIA_ERROR_INVALID_PARAMETER;
     }
 
-    mDestinationFormat = destinationFormat;
+    AMediaFormat* encoderFormat = AMediaFormat_new();
+    if (!encoderFormat || AMediaFormat_copy(encoderFormat, destinationFormat.get()) != AMEDIA_OK) {
+        LOG(ERROR) << "Unable to copy destination format";
+        return AMEDIA_ERROR_INVALID_PARAMETER;
+    }
+
+    float tmp;
+    if (!AMediaFormat_getFloat(encoderFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, &tmp)) {
+        AMediaFormat_setFloat(encoderFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL,
+                              kDefaultKeyFrameIntervalSeconds);
+    }
+    AMediaFormat_setInt32(encoderFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT, kColorFormatSurface);
+
+    mDestinationFormat = std::shared_ptr<AMediaFormat>(encoderFormat, &AMediaFormat_delete);
 
     // Create and configure the encoder.
     const char* destinationMime = nullptr;
@@ -276,7 +294,7 @@
         sample->info.flags = bufferInfo.flags;
         sample->info.presentationTimeUs = bufferInfo.presentationTimeUs;
 
-        const bool aborted = mOutputQueue.enqueue(sample);
+        const bool aborted = mOutputQueue->enqueue(sample);
         if (aborted) {
             LOG(ERROR) << "Output sample queue was aborted. Stopping transcode.";
             mStatus = AMEDIA_ERROR_IO;  // TODO: Define custom error codes?
@@ -321,7 +339,8 @@
     }
 
     AMediaCodec_stop(mDecoder);
-    AMediaCodec_stop(mEncoder.get());
+    // TODO: Stop invalidates all buffers. Stop encoder when last buffer is released.
+    //    AMediaCodec_stop(mEncoder.get());
     return mStatus;
 }
 
@@ -330,4 +349,8 @@
     mCodecMessageQueue.push([this] { mStopRequested = true; }, true /* front */);
 }
 
+std::shared_ptr<AMediaFormat> VideoTrackTranscoder::getOutputFormat() const {
+    return mDestinationFormat;
+}
+
 }  // namespace android
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
index 235766c..a71db67 100644
--- a/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
@@ -40,14 +40,14 @@
      * successfully.
      * @param transcoder The MediaTrackTranscoder that finished the transcoding.
      */
-    virtual void onTrackFinished(MediaTrackTranscoder* transcoder);
+    virtual void onTrackFinished(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.
      */
-    virtual void onTrackError(MediaTrackTranscoder* transcoder, media_status_t status);
+    virtual void onTrackError(const MediaTrackTranscoder* transcoder, media_status_t status);
 
 protected:
     virtual ~MediaTrackTranscoderCallback() = default;
@@ -96,15 +96,24 @@
     bool stop();
 
     /**
-     * Sample output queue.
-     * TODO(b/155918341) Move to protected.
+     * Retrieves the track transcoder's output sample queue.
+     * @return The output sample queue.
      */
-    MediaSampleQueue mOutputQueue = {};
+    std::shared_ptr<MediaSampleQueue> getOutputQueue() const;
+
+    /**
+      * Retrieves the track transcoder's final output format. The output is available after the
+      * track transcoder has been successfully configured.
+      * @return The track output format.
+      */
+    virtual std::shared_ptr<AMediaFormat> getOutputFormat() const = 0;
+
+    virtual ~MediaTrackTranscoder() = default;
 
 protected:
     MediaTrackTranscoder(const std::weak_ptr<MediaTrackTranscoderCallback>& transcoderCallback)
-          : mTranscoderCallback(transcoderCallback){};
-    virtual ~MediaTrackTranscoder() = default;
+          : mOutputQueue(std::make_shared<MediaSampleQueue>()),
+            mTranscoderCallback(transcoderCallback){};
 
     // configureDestinationFormat needs to be implemented by subclasses, and gets called on an
     // external thread before start.
@@ -119,6 +128,7 @@
     // be aborted as soon as possible. It should be safe to call abortTranscodeLoop multiple times.
     virtual void abortTranscodeLoop() = 0;
 
+    std::shared_ptr<MediaSampleQueue> mOutputQueue;
     std::shared_ptr<MediaSampleReader> mMediaSampleReader;
     int mTrackIndex;
     std::shared_ptr<AMediaFormat> mSourceFormat;
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h b/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
new file mode 100644
index 0000000..2d18eea
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_TRANSCODER_H
+#define ANDROID_MEDIA_TRANSCODER_H
+
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <media/MediaSampleReader.h>
+#include <media/MediaSampleWriter.h>
+#include <media/MediaTrackTranscoder.h>
+#include <media/NdkMediaError.h>
+#include <media/NdkMediaFormat.h>
+
+#include <atomic>
+#include <memory>
+#include <unordered_set>
+
+namespace android {
+
+class MediaTranscoder : public std::enable_shared_from_this<MediaTranscoder>,
+                        public MediaTrackTranscoderCallback {
+public:
+    /** Callbacks from transcoder to client. */
+    class CallbackInterface {
+    public:
+        /** Transcoder finished successfully. */
+        virtual void onFinished(const MediaTranscoder* transcoder) = 0;
+
+        /** Transcoder encountered an unrecoverable error. */
+        virtual void onError(const MediaTranscoder* transcoder, media_status_t error) = 0;
+
+        /** Transcoder progress update reported in percent from 0 to 100. */
+        virtual void onProgressUpdate(const MediaTranscoder* transcoder, int32_t progress) = 0;
+
+        /**
+         * Transcoder lost codec resources and paused operations. The client can resume transcoding
+         * again when resources are available by either:
+         *   1) Calling resume on the same MediaTranscoder instance.
+         *   2) Creating a new MediaTranscoding instance with the paused state and then calling
+         *      resume.
+         */
+        virtual void onCodecResourceLost(const MediaTranscoder* transcoder,
+                                         const std::shared_ptr<const Parcelable>& pausedState) = 0;
+
+        virtual ~CallbackInterface() = default;
+    };
+
+    /**
+     * Creates a new MediaTranscoder instance. If the supplied paused state is valid, the transcoder
+     * will be initialized with the paused state and be ready to be resumed right away. It is not
+     * possible to change any configurations on a paused transcoder.
+     */
+    static std::shared_ptr<MediaTranscoder> create(
+            const std::shared_ptr<CallbackInterface>& callbacks,
+            const std::shared_ptr<Parcel>& pausedState = nullptr);
+
+    /** Configures source from path. */
+    media_status_t configureSource(const char* path);
+
+    /** Gets the media formats of all tracks in the file. */
+    std::vector<std::shared_ptr<AMediaFormat>> getTrackFormats() const;
+
+    /**
+     * Configures transcoding of a track. Tracks that are not configured will not present in the
+     * final transcoded file, i.e. tracks will be dropped by default. Passing nullptr for
+     * trackFormat means the track will be copied unchanged ("passthrough") to the destination.
+     * Track configurations must be done after the source has been configured.
+     * Note: trackFormat is not modified but cannot be const.
+     */
+    media_status_t configureTrackFormat(size_t trackIndex, AMediaFormat* trackFormat);
+
+    /** Configures destination from path. */
+    media_status_t configureDestination(const char* path);
+
+    /** Starts transcoding. No configurations can be made once the transcoder has started. */
+    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.
+     */
+    media_status_t pause(std::shared_ptr<const Parcelable>* pausedState);
+
+    /** Resumes a paused transcoding. */
+    media_status_t resume();
+
+    /** Cancels the transcoding. Once canceled the transcoding can not be restarted. returns error
+     * if file could not be deleted. */
+    media_status_t cancel(bool deleteDestinationFile = true);
+
+    virtual ~MediaTranscoder() = default;
+
+private:
+    MediaTranscoder(const std::shared_ptr<CallbackInterface>& callbacks)
+          : mCallbacks(callbacks),
+            mSampleReader(nullptr),
+            mSampleWriter(nullptr),
+            mSourceTrackFormats(),
+            mTrackTranscoders() {}
+
+    // MediaTrackTranscoderCallback
+    virtual void onTrackFinished(const MediaTrackTranscoder* transcoder) override;
+    virtual void onTrackError(const MediaTrackTranscoder* transcoder,
+                              media_status_t status) override;
+    // ~MediaTrackTranscoderCallback
+    void onSampleWriterFinished(media_status_t status);
+    void sendCallback(media_status_t status);
+
+    std::shared_ptr<CallbackInterface> mCallbacks;
+    std::shared_ptr<MediaSampleReader> mSampleReader;
+    std::unique_ptr<MediaSampleWriter> mSampleWriter;
+    std::vector<std::shared_ptr<AMediaFormat>> mSourceTrackFormats;
+    std::vector<std::unique_ptr<MediaTrackTranscoder>> mTrackTranscoders;
+
+    std::string mDestinationPath;
+    std::atomic_bool mCallbackSent = false;
+    std::atomic_bool mCancelled = false;
+};
+
+}  // namespace android
+#endif  // ANDROID_MEDIA_TRANSCODER_H
diff --git a/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h
index 42feb85..b9491ed 100644
--- a/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h
@@ -90,6 +90,7 @@
     void abortTranscodeLoop() override;
     media_status_t configureDestinationFormat(
             const std::shared_ptr<AMediaFormat>& destinationFormat) override;
+    std::shared_ptr<AMediaFormat> getOutputFormat() const override;
     // ~MediaTrackTranscoder
 
     std::shared_ptr<BufferPool> mBufferPool;
diff --git a/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
index 7d93d60..c47e4b7 100644
--- a/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
@@ -61,6 +61,7 @@
     void abortTranscodeLoop() override;
     media_status_t configureDestinationFormat(
             const std::shared_ptr<AMediaFormat>& destinationFormat) override;
+    std::shared_ptr<AMediaFormat> getOutputFormat() const override;
     // ~MediaTrackTranscoder
 
     // Enqueues an input sample with the decoder.
diff --git a/media/libmediatranscoding/transcoder/tests/Android.bp b/media/libmediatranscoding/transcoder/tests/Android.bp
index 926110885..4160c30 100644
--- a/media/libmediatranscoding/transcoder/tests/Android.bp
+++ b/media/libmediatranscoding/transcoder/tests/Android.bp
@@ -74,3 +74,10 @@
     defaults: ["testdefaults"],
     srcs: ["MediaSampleWriterTests.cpp"],
 }
+
+// MediaTranscoder unit test
+cc_test {
+    name: "MediaTranscoderTests",
+    defaults: ["testdefaults"],
+    srcs: ["MediaTranscoderTests.cpp"],
+}
diff --git a/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
index c5b181d..4d9386a 100644
--- a/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
@@ -60,6 +60,7 @@
             break;
         }
         ASSERT_NE(mTranscoder, nullptr);
+        mTranscoderOutputQueue = mTranscoder->getOutputQueue();
 
         initSampleReader();
     }
@@ -120,7 +121,7 @@
             std::shared_ptr<MediaSample> sample;
             bool aborted = false;
             do {
-                aborted = mTranscoder->mOutputQueue.dequeue(&sample);
+                aborted = mTranscoderOutputQueue->dequeue(&sample);
             } while (!aborted && !(sample->info.flags & SAMPLE_FLAG_END_OF_STREAM));
             mQueueWasAborted = aborted;
             mGotEndOfStream =
@@ -142,6 +143,7 @@
 
 protected:
     std::shared_ptr<MediaTrackTranscoder> mTranscoder;
+    std::shared_ptr<MediaSampleQueue> mTranscoderOutputQueue;
     std::shared_ptr<TestCallback> mCallback;
 
     std::shared_ptr<MediaSampleReader> mMediaSampleReader;
@@ -242,7 +244,7 @@
     EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
               AMEDIA_OK);
     ASSERT_TRUE(mTranscoder->start());
-    mTranscoder->mOutputQueue.abort();
+    mTranscoderOutputQueue->abort();
     drainOutputSampleQueue();
     EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_ERROR_IO);
     EXPECT_TRUE(mTranscoder->stop());
@@ -259,7 +261,7 @@
     ASSERT_TRUE(mTranscoder->start());
 
     std::shared_ptr<MediaSample> sample;
-    EXPECT_FALSE(mTranscoder->mOutputQueue.dequeue(&sample));
+    EXPECT_FALSE(mTranscoderOutputQueue->dequeue(&sample));
 
     drainOutputSampleQueue();
     EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
@@ -269,6 +271,7 @@
     EXPECT_TRUE(mGotEndOfStream);
 
     mTranscoder.reset();
+    mTranscoderOutputQueue.reset();
     std::this_thread::sleep_for(std::chrono::milliseconds(20));
     sample.reset();
 }
@@ -280,7 +283,7 @@
     ASSERT_TRUE(mTranscoder->start());
 
     std::shared_ptr<MediaSample> sample;
-    EXPECT_FALSE(mTranscoder->mOutputQueue.dequeue(&sample));
+    EXPECT_FALSE(mTranscoderOutputQueue->dequeue(&sample));
     EXPECT_TRUE(mTranscoder->stop());
 
     std::this_thread::sleep_for(std::chrono::milliseconds(20));
diff --git a/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
new file mode 100644
index 0000000..c4a67bb
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Unit Test for MediaTranscoder
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "MediaTranscoderTests"
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <media/MediaTranscoder.h>
+
+namespace android {
+
+class TestCallbacks : public MediaTranscoder::CallbackInterface {
+public:
+    virtual void onFinished(const MediaTranscoder* transcoder __unused) override {
+        std::unique_lock<std::mutex> lock(mMutex);
+        EXPECT_FALSE(mFinished);
+        mFinished = true;
+        mCondition.notify_all();
+    }
+
+    virtual void onError(const MediaTranscoder* transcoder __unused,
+                         media_status_t error) override {
+        std::unique_lock<std::mutex> lock(mMutex);
+        EXPECT_NE(error, AMEDIA_OK);
+        EXPECT_FALSE(mFinished);
+        mFinished = true;
+        mStatus = error;
+        mCondition.notify_all();
+    }
+
+    virtual void onProgressUpdate(const MediaTranscoder* transcoder __unused,
+                                  int32_t progress __unused) override {}
+
+    virtual void onCodecResourceLost(const MediaTranscoder* transcoder __unused,
+                                     const std::shared_ptr<const Parcelable>& pausedState
+                                             __unused) override {}
+
+    void waitForTranscodingFinished() {
+        std::unique_lock<std::mutex> lock(mMutex);
+        while (!mFinished) {
+            mCondition.wait(lock);
+        }
+    }
+
+    media_status_t mStatus = AMEDIA_OK;
+
+private:
+    std::mutex mMutex;
+    std::condition_variable mCondition;
+    bool mFinished = false;
+};
+
+static const char* SOURCE_PATH =
+        "/data/local/tmp/TranscoderTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
+
+class MediaTranscoderTests : public ::testing::Test {
+public:
+    MediaTranscoderTests() { LOG(DEBUG) << "MediaTranscoderTests created"; }
+    ~MediaTranscoderTests() { LOG(DEBUG) << "MediaTranscoderTests destroyed"; }
+
+    void SetUp() override {
+        LOG(DEBUG) << "MediaTranscoderTests set up";
+        mCallbacks = std::make_shared<TestCallbacks>();
+    }
+
+    void TearDown() override {
+        LOG(DEBUG) << "MediaTranscoderTests tear down";
+        mCallbacks.reset();
+    }
+
+    void deleteFile(const char* path) { unlink(path); }
+
+    using FormatConfigurationCallback = std::function<AMediaFormat*(AMediaFormat*)>;
+    media_status_t transcodeHelper(const char* destPath,
+                                   FormatConfigurationCallback formatCallback) {
+        auto transcoder = MediaTranscoder::create(mCallbacks, nullptr);
+        EXPECT_NE(transcoder, nullptr);
+
+        EXPECT_EQ(transcoder->configureSource(SOURCE_PATH), AMEDIA_OK);
+
+        std::vector<std::shared_ptr<AMediaFormat>> trackFormats = transcoder->getTrackFormats();
+        EXPECT_GT(trackFormats.size(), 0);
+
+        for (int i = 0; i < trackFormats.size(); ++i) {
+            AMediaFormat* format = formatCallback(trackFormats[i].get());
+            EXPECT_EQ(transcoder->configureTrackFormat(i, format), AMEDIA_OK);
+            if (format != nullptr) {
+                AMediaFormat_delete(format);
+            }
+        }
+        deleteFile(destPath);
+        EXPECT_EQ(transcoder->configureDestination(destPath), AMEDIA_OK);
+
+        media_status_t startStatus = transcoder->start();
+        EXPECT_EQ(startStatus, AMEDIA_OK);
+        if (startStatus == AMEDIA_OK) {
+            mCallbacks->waitForTranscodingFinished();
+        }
+
+        return mCallbacks->mStatus;
+    }
+
+    std::shared_ptr<TestCallbacks> mCallbacks;
+};
+
+TEST_F(MediaTranscoderTests, TestPassthrough) {
+    const char* destPath = "/data/local/tmp/MediaTranscoder_Passthrough.MP4";
+
+    EXPECT_EQ(transcodeHelper(destPath, [](AMediaFormat*) { return nullptr; }), AMEDIA_OK);
+
+    // TODO: Validate output file
+}
+
+TEST_F(MediaTranscoderTests, TestBasicVideoTranscode) {
+    const char* destPath = "/data/local/tmp/MediaTranscoder_VideoTranscode.MP4";
+
+    EXPECT_EQ(transcodeHelper(
+                      destPath,
+                      [](AMediaFormat* sourceFormat) {
+                          AMediaFormat* format = nullptr;
+                          const char* mime = nullptr;
+                          AMediaFormat_getString(sourceFormat, AMEDIAFORMAT_KEY_MIME, &mime);
+
+                          if (strncmp(mime, "video/", 6) == 0) {
+                              const int32_t kBitRate = 8 * 1000 * 1000;  // 8Mbs
+                              format = AMediaFormat_new();
+                              AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, kBitRate);
+                          }
+                          return format;
+                      }),
+              AMEDIA_OK);
+
+    // TODO: Validate output file
+}
+
+}  // namespace android
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp
index 7a92a37..316793a 100644
--- a/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp
@@ -166,7 +166,8 @@
     // Pull transcoder's output samples and compare against input checksums.
     uint64_t sampleCount = 0;
     std::shared_ptr<MediaSample> sample;
-    while (!transcoder.mOutputQueue.dequeue(&sample)) {
+    std::shared_ptr<MediaSampleQueue> outputQueue = transcoder.getOutputQueue();
+    while (!outputQueue->dequeue(&sample)) {
         ASSERT_NE(sample, nullptr);
 
         if (sample->info.flags & SAMPLE_FLAG_END_OF_STREAM) {
diff --git a/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h b/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
index 6b9131c..79c227b 100644
--- a/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
+++ b/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
@@ -56,13 +56,13 @@
     ~TestCallback() = default;
 
     // MediaTrackTranscoderCallback
-    void onTrackFinished(MediaTrackTranscoder* transcoder __unused) {
+    void onTrackFinished(const MediaTrackTranscoder* transcoder __unused) {
         std::unique_lock<std::mutex> lock(mMutex);
         mTranscodingFinished = true;
         mCv.notify_all();
     }
 
-    void onTrackError(MediaTrackTranscoder* transcoder __unused, media_status_t status) {
+    void onTrackError(const MediaTrackTranscoder* transcoder __unused, media_status_t status) {
         std::unique_lock<std::mutex> lock(mMutex);
         mTranscodingFinished = true;
         mStatus = status;
diff --git a/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
index 3cec1a1..6b1f640 100644
--- a/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
@@ -100,10 +100,11 @@
     EXPECT_EQ(transcoder.configure(mMediaSampleReader, mTrackIndex, mDestinationFormat), AMEDIA_OK);
     ASSERT_TRUE(transcoder.start());
 
-    std::thread sampleConsumerThread{[&transcoder] {
+    std::shared_ptr<MediaSampleQueue> outputQueue = transcoder.getOutputQueue();
+    std::thread sampleConsumerThread{[&outputQueue] {
         uint64_t sampleCount = 0;
         std::shared_ptr<MediaSample> sample;
-        while (!transcoder.mOutputQueue.dequeue(&sample)) {
+        while (!outputQueue->dequeue(&sample)) {
             ASSERT_NE(sample, nullptr);
             const uint32_t flags = sample->info.flags;
 
diff --git a/media/libmediatranscoding/transcoder/tests/build_and_run_all_unit_tests.sh b/media/libmediatranscoding/transcoder/tests/build_and_run_all_unit_tests.sh
index 70e2111..01beeee 100755
--- a/media/libmediatranscoding/transcoder/tests/build_and_run_all_unit_tests.sh
+++ b/media/libmediatranscoding/transcoder/tests/build_and_run_all_unit_tests.sh
@@ -39,3 +39,6 @@
 
 echo "testing MediaSampleWriter"
 adb shell /data/nativetest64/MediaSampleWriterTests/MediaSampleWriterTests
+
+echo "testing MediaTranscoder"
+adb shell /data/nativetest64/MediaTranscoderTests/MediaTranscoderTests