Transcoder: Create a transcoder command line tool.

Added a command line tool for transcoding videos
using the native MediaTranscoder.

Fixes: 182229384
Test: Transcoding using the tool.
Change-Id: I98948e39c10a3ccd55d37875d5f9f90063db6fff
diff --git a/media/libmediatranscoding/transcoder/tools/Transcode.cpp b/media/libmediatranscoding/transcoder/tools/Transcode.cpp
new file mode 100644
index 0000000..1f5649e
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tools/Transcode.cpp
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include <android-base/macros.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <media/MediaTranscoder.h>
+#include <media/NdkCommon.h>
+
+using namespace android;
+
+#define ERR_MSG(fmt, ...) fprintf(stderr, "Error: " fmt "\n", ##__VA_ARGS__)
+
+class TranscoderCallbacks : public MediaTranscoder::CallbackInterface {
+public:
+    media_status_t waitForTranscodingFinished() {
+        std::unique_lock<std::mutex> lock(mMutex);
+        while (!mFinished) {
+            mCondition.wait(lock);
+        }
+        return mStatus;
+    }
+
+private:
+    virtual void onFinished(const MediaTranscoder* /*transcoder*/) override {
+        notifyTranscoderFinished(AMEDIA_OK);
+    }
+
+    virtual void onError(const MediaTranscoder* /*transcoder*/, media_status_t error) override {
+        ERR_MSG("Transcoder failed with error %d", error);
+        notifyTranscoderFinished(error);
+    }
+
+    virtual void onProgressUpdate(const MediaTranscoder* /*transcoder*/,
+                                  int32_t /*progress*/) override {}
+
+    virtual void onCodecResourceLost(
+            const MediaTranscoder* /*transcoder*/,
+            const std::shared_ptr<ndk::ScopedAParcel>& /*pausedState*/) override {
+        ERR_MSG("Transcoder lost codec resource while transcoding");
+        notifyTranscoderFinished(AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE);
+    }
+
+    virtual void onHeartBeat(const MediaTranscoder* /*transcoder*/) override {}
+
+    void notifyTranscoderFinished(media_status_t status) {
+        std::unique_lock<std::mutex> lock(mMutex);
+        mFinished = true;
+        mStatus = status;
+        mCondition.notify_all();
+    }
+
+    std::mutex mMutex;
+    std::condition_variable mCondition;
+    bool mFinished = false;
+    media_status_t mStatus = AMEDIA_OK;
+};
+
+struct TranscodeConfig {
+    std::string srcFile;
+    std::string dstFile;
+
+    std::string dstCodec{AMEDIA_MIMETYPE_VIDEO_AVC};
+    int32_t bitrate = -1;
+};
+
+static int transcode(const struct TranscodeConfig& config) {
+    auto callbacks = std::make_shared<TranscoderCallbacks>();
+    auto transcoder = MediaTranscoder::create(callbacks, -1 /*heartBeatIntervalUs*/);
+
+    const int srcFd = open(config.srcFile.c_str(), O_RDONLY);
+    if (srcFd <= 0) {
+        ERR_MSG("Unable to open source file %s", config.srcFile.c_str());
+        return AMEDIA_ERROR_INVALID_PARAMETER;
+    }
+
+    media_status_t status = transcoder->configureSource(srcFd);
+    close(srcFd);
+    if (status != AMEDIA_OK) {
+        ERR_MSG("configureSource returned error %d", status);
+        return status;
+    }
+
+    std::vector<std::shared_ptr<AMediaFormat>> trackFormats = transcoder->getTrackFormats();
+    if (trackFormats.size() <= 0) {
+        ERR_MSG("No tracks found in source file");
+        return AMEDIA_ERROR_MALFORMED;
+    }
+
+    for (int i = 0; i < trackFormats.size(); ++i) {
+        AMediaFormat* dstFormat = nullptr;
+
+        const char* mime = nullptr;
+        AMediaFormat_getString(trackFormats[i].get(), AMEDIAFORMAT_KEY_MIME, &mime);
+
+        if (strncmp(mime, "video/", 6) == 0) {
+            dstFormat = AMediaFormat_new();
+            AMediaFormat_setString(dstFormat, AMEDIAFORMAT_KEY_MIME, config.dstCodec.c_str());
+
+            if (config.bitrate > 0) {
+                AMediaFormat_setInt32(dstFormat, AMEDIAFORMAT_KEY_BIT_RATE, config.bitrate);
+            }
+        }
+
+        status = transcoder->configureTrackFormat(i, dstFormat);
+
+        if (dstFormat != nullptr) {
+            AMediaFormat_delete(dstFormat);
+        }
+
+        if (status != AMEDIA_OK) {
+            ERR_MSG("configureTrack returned error %d", status);
+            return status;
+        }
+    }
+
+    // Note: Overwrites existing file.
+    const int dstFd = open(config.dstFile.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
+    if (dstFd <= 0) {
+        ERR_MSG("Unable to open destination file %s", config.dstFile.c_str());
+        return AMEDIA_ERROR_INVALID_PARAMETER;
+    }
+
+    status = transcoder->configureDestination(dstFd);
+    close(dstFd);
+    if (status != AMEDIA_OK) {
+        ERR_MSG("configureDestination returned error %d", status);
+        return status;
+    }
+
+    status = transcoder->start();
+    if (status != AMEDIA_OK) {
+        ERR_MSG("start returned error %d", status);
+        return status;
+    }
+
+    return callbacks->waitForTranscodingFinished();
+}
+
+// Options.
+static const struct option kLongOpts[] = {{"help", no_argument, nullptr, 'h'},
+                                          {"codec", required_argument, nullptr, 'c'},
+                                          {"bitrate", required_argument, nullptr, 'b'},
+                                          {0, 0, 0, 0}};
+static const char kShortOpts[] = "hc:b:";
+
+static void printUsageAndExit() {
+    const char* usage =
+            "  -h / --help    : Print this usage message and exit.\n"
+            "  -c / --codec   : Specify output video codec type using MediaFormat codec mime "
+            "type.\n"
+            "                     Defaults to \"video/avc\".\n"
+            "  -b / --bitrate : Specify output video bitrate in bits per second.\n"
+            "                     Defaults to estimating and preserving the original bitrate.\n"
+            "";
+
+    printf("Usage: %s [-h] [-c CODEC] <srcfile> <dstfile>\n%s", getprogname(), usage);
+    exit(-1);
+}
+
+int main(int argc, char** argv) {
+    int c;
+    TranscodeConfig config;
+
+    while ((c = getopt_long(argc, argv, kShortOpts, kLongOpts, nullptr)) >= 0) {
+        switch (c) {
+        case 'c':
+            config.dstCodec.assign(optarg);
+            break;
+
+        case 'b':
+            config.bitrate = atoi(optarg);
+            if (config.bitrate <= 0) {
+                ERR_MSG("Bitrate must an integer larger than zero.");
+                printUsageAndExit();
+            }
+            break;
+
+        case '?':
+            FALLTHROUGH_INTENDED;
+        case 'h':
+            FALLTHROUGH_INTENDED;
+        default:
+            printUsageAndExit();
+            break;
+        }
+    }
+
+    if (optind > (argc - 2)) {
+        ERR_MSG("Source and destination file not specified");
+        printUsageAndExit();
+    }
+    config.srcFile.assign(argv[optind++]);
+    config.dstFile.assign(argv[optind]);
+
+    return transcode(config);
+}