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/Android.bp b/media/libmediatranscoding/transcoder/tools/Android.bp
new file mode 100644
index 0000000..ba30d34
--- /dev/null
+++ b/media/libmediatranscoding/transcoder/tools/Android.bp
@@ -0,0 +1,33 @@
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["frameworks_av_license"],
+}
+
+cc_binary {
+ name: "transcode",
+ srcs: ["Transcode.cpp"],
+
+ shared_libs: [
+ "libmediandk",
+ "libmediatranscoder",
+ ],
+
+ header_libs: [
+ "libbase_headers",
+ ],
+
+ compile_multilib: "32",
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+ sanitize: {
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ cfi: true,
+ },
+}
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);
+}