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/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