/* * 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 #include #include #include #include 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 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& /*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 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(); 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> 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] \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); }