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:
waitForTranscodingFinished()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:
onFinished(const MediaTranscoder *)38 virtual void onFinished(const MediaTranscoder* /*transcoder*/) override {
39 notifyTranscoderFinished(AMEDIA_OK);
40 }
41
onError(const MediaTranscoder *,media_status_t error)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
onProgressUpdate(const MediaTranscoder *,int32_t)47 virtual void onProgressUpdate(const MediaTranscoder* /*transcoder*/,
48 int32_t /*progress*/) override {}
49
onCodecResourceLost(const MediaTranscoder *,const std::shared_ptr<ndk::ScopedAParcel> &)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
onHeartBeat(const MediaTranscoder *)57 virtual void onHeartBeat(const MediaTranscoder* /*transcoder*/) override {}
58
notifyTranscoderFinished(media_status_t status)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
transcode(const struct TranscodeConfig & config)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
printUsageAndExit()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
main(int argc,char ** argv)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 }
211