Linus Nilsson | fd945ef | 2021-03-08 21:19:07 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2021 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #include <android-base/macros.h> |
| 18 | #include <fcntl.h> |
| 19 | #include <getopt.h> |
| 20 | #include <media/MediaTranscoder.h> |
| 21 | #include <media/NdkCommon.h> |
| 22 | |
| 23 | using namespace android; |
| 24 | |
| 25 | #define ERR_MSG(fmt, ...) fprintf(stderr, "Error: " fmt "\n", ##__VA_ARGS__) |
| 26 | |
| 27 | class TranscoderCallbacks : public MediaTranscoder::CallbackInterface { |
| 28 | public: |
| 29 | media_status_t waitForTranscodingFinished() { |
| 30 | std::unique_lock<std::mutex> lock(mMutex); |
| 31 | while (!mFinished) { |
| 32 | mCondition.wait(lock); |
| 33 | } |
| 34 | return mStatus; |
| 35 | } |
| 36 | |
| 37 | private: |
| 38 | virtual void onFinished(const MediaTranscoder* /*transcoder*/) override { |
| 39 | notifyTranscoderFinished(AMEDIA_OK); |
| 40 | } |
| 41 | |
| 42 | virtual void onError(const MediaTranscoder* /*transcoder*/, media_status_t error) override { |
| 43 | ERR_MSG("Transcoder failed with error %d", error); |
| 44 | notifyTranscoderFinished(error); |
| 45 | } |
| 46 | |
| 47 | virtual void onProgressUpdate(const MediaTranscoder* /*transcoder*/, |
| 48 | int32_t /*progress*/) override {} |
| 49 | |
| 50 | virtual void onCodecResourceLost( |
| 51 | const MediaTranscoder* /*transcoder*/, |
| 52 | const std::shared_ptr<ndk::ScopedAParcel>& /*pausedState*/) override { |
| 53 | ERR_MSG("Transcoder lost codec resource while transcoding"); |
| 54 | notifyTranscoderFinished(AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE); |
| 55 | } |
| 56 | |
| 57 | virtual void onHeartBeat(const MediaTranscoder* /*transcoder*/) override {} |
| 58 | |
| 59 | void notifyTranscoderFinished(media_status_t status) { |
| 60 | std::unique_lock<std::mutex> lock(mMutex); |
| 61 | mFinished = true; |
| 62 | mStatus = status; |
| 63 | mCondition.notify_all(); |
| 64 | } |
| 65 | |
| 66 | std::mutex mMutex; |
| 67 | std::condition_variable mCondition; |
| 68 | bool mFinished = false; |
| 69 | media_status_t mStatus = AMEDIA_OK; |
| 70 | }; |
| 71 | |
| 72 | struct TranscodeConfig { |
| 73 | std::string srcFile; |
| 74 | std::string dstFile; |
| 75 | |
| 76 | std::string dstCodec{AMEDIA_MIMETYPE_VIDEO_AVC}; |
| 77 | int32_t bitrate = -1; |
| 78 | }; |
| 79 | |
| 80 | static int transcode(const struct TranscodeConfig& config) { |
| 81 | auto callbacks = std::make_shared<TranscoderCallbacks>(); |
| 82 | auto transcoder = MediaTranscoder::create(callbacks, -1 /*heartBeatIntervalUs*/); |
| 83 | |
| 84 | const int srcFd = open(config.srcFile.c_str(), O_RDONLY); |
| 85 | if (srcFd <= 0) { |
| 86 | ERR_MSG("Unable to open source file %s", config.srcFile.c_str()); |
| 87 | return AMEDIA_ERROR_INVALID_PARAMETER; |
| 88 | } |
| 89 | |
| 90 | media_status_t status = transcoder->configureSource(srcFd); |
| 91 | close(srcFd); |
| 92 | if (status != AMEDIA_OK) { |
| 93 | ERR_MSG("configureSource returned error %d", status); |
| 94 | return status; |
| 95 | } |
| 96 | |
| 97 | std::vector<std::shared_ptr<AMediaFormat>> trackFormats = transcoder->getTrackFormats(); |
| 98 | if (trackFormats.size() <= 0) { |
| 99 | ERR_MSG("No tracks found in source file"); |
| 100 | return AMEDIA_ERROR_MALFORMED; |
| 101 | } |
| 102 | |
| 103 | for (int i = 0; i < trackFormats.size(); ++i) { |
| 104 | AMediaFormat* dstFormat = nullptr; |
| 105 | |
| 106 | const char* mime = nullptr; |
| 107 | AMediaFormat_getString(trackFormats[i].get(), AMEDIAFORMAT_KEY_MIME, &mime); |
| 108 | |
| 109 | if (strncmp(mime, "video/", 6) == 0) { |
| 110 | dstFormat = AMediaFormat_new(); |
| 111 | AMediaFormat_setString(dstFormat, AMEDIAFORMAT_KEY_MIME, config.dstCodec.c_str()); |
| 112 | |
| 113 | if (config.bitrate > 0) { |
| 114 | AMediaFormat_setInt32(dstFormat, AMEDIAFORMAT_KEY_BIT_RATE, config.bitrate); |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | status = transcoder->configureTrackFormat(i, dstFormat); |
| 119 | |
| 120 | if (dstFormat != nullptr) { |
| 121 | AMediaFormat_delete(dstFormat); |
| 122 | } |
| 123 | |
| 124 | if (status != AMEDIA_OK) { |
| 125 | ERR_MSG("configureTrack returned error %d", status); |
| 126 | return status; |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | // Note: Overwrites existing file. |
| 131 | const int dstFd = open(config.dstFile.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); |
| 132 | if (dstFd <= 0) { |
| 133 | ERR_MSG("Unable to open destination file %s", config.dstFile.c_str()); |
| 134 | return AMEDIA_ERROR_INVALID_PARAMETER; |
| 135 | } |
| 136 | |
| 137 | status = transcoder->configureDestination(dstFd); |
| 138 | close(dstFd); |
| 139 | if (status != AMEDIA_OK) { |
| 140 | ERR_MSG("configureDestination returned error %d", status); |
| 141 | return status; |
| 142 | } |
| 143 | |
| 144 | status = transcoder->start(); |
| 145 | if (status != AMEDIA_OK) { |
| 146 | ERR_MSG("start returned error %d", status); |
| 147 | return status; |
| 148 | } |
| 149 | |
| 150 | return callbacks->waitForTranscodingFinished(); |
| 151 | } |
| 152 | |
| 153 | // Options. |
| 154 | static const struct option kLongOpts[] = {{"help", no_argument, nullptr, 'h'}, |
| 155 | {"codec", required_argument, nullptr, 'c'}, |
| 156 | {"bitrate", required_argument, nullptr, 'b'}, |
| 157 | {0, 0, 0, 0}}; |
| 158 | static const char kShortOpts[] = "hc:b:"; |
| 159 | |
| 160 | static void printUsageAndExit() { |
| 161 | const char* usage = |
| 162 | " -h / --help : Print this usage message and exit.\n" |
| 163 | " -c / --codec : Specify output video codec type using MediaFormat codec mime " |
| 164 | "type.\n" |
| 165 | " Defaults to \"video/avc\".\n" |
| 166 | " -b / --bitrate : Specify output video bitrate in bits per second.\n" |
| 167 | " Defaults to estimating and preserving the original bitrate.\n" |
| 168 | ""; |
| 169 | |
| 170 | printf("Usage: %s [-h] [-c CODEC] <srcfile> <dstfile>\n%s", getprogname(), usage); |
| 171 | exit(-1); |
| 172 | } |
| 173 | |
| 174 | int main(int argc, char** argv) { |
| 175 | int c; |
| 176 | TranscodeConfig config; |
| 177 | |
| 178 | while ((c = getopt_long(argc, argv, kShortOpts, kLongOpts, nullptr)) >= 0) { |
| 179 | switch (c) { |
| 180 | case 'c': |
| 181 | config.dstCodec.assign(optarg); |
| 182 | break; |
| 183 | |
| 184 | case 'b': |
| 185 | config.bitrate = atoi(optarg); |
| 186 | if (config.bitrate <= 0) { |
| 187 | ERR_MSG("Bitrate must an integer larger than zero."); |
| 188 | printUsageAndExit(); |
| 189 | } |
| 190 | break; |
| 191 | |
| 192 | case '?': |
| 193 | FALLTHROUGH_INTENDED; |
| 194 | case 'h': |
| 195 | FALLTHROUGH_INTENDED; |
| 196 | default: |
| 197 | printUsageAndExit(); |
| 198 | break; |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | if (optind > (argc - 2)) { |
| 203 | ERR_MSG("Source and destination file not specified"); |
| 204 | printUsageAndExit(); |
| 205 | } |
| 206 | config.srcFile.assign(argv[optind++]); |
| 207 | config.dstFile.assign(argv[optind]); |
| 208 | |
| 209 | return transcode(config); |
| 210 | } |