blob: f2f7810dda19bc85f5ba4809000523cbbe2085d5 [file] [log] [blame]
Linus Nilssoncab39d82020-05-14 16:32:21 -07001/*
2 * Copyright (C) 2020 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// #define LOG_NDEBUG 0
18#define LOG_TAG "MediaTranscoder"
19
20#include <android-base/logging.h>
21#include <fcntl.h>
22#include <media/MediaSampleReaderNDK.h>
23#include <media/MediaTranscoder.h>
24#include <media/PassthroughTrackTranscoder.h>
25#include <media/VideoTrackTranscoder.h>
26#include <unistd.h>
27
28namespace android {
29
30#define DEFINE_FORMAT_VALUE_COPY_FUNC(_type, _typeName) \
31 static void copy##_typeName(const char* key, AMediaFormat* to, AMediaFormat* from) { \
32 _type value; \
33 if (AMediaFormat_get##_typeName(from, key, &value)) { \
34 AMediaFormat_set##_typeName(to, key, value); \
35 } \
36 }
37
38DEFINE_FORMAT_VALUE_COPY_FUNC(const char*, String);
39DEFINE_FORMAT_VALUE_COPY_FUNC(int64_t, Int64);
40DEFINE_FORMAT_VALUE_COPY_FUNC(int32_t, Int32);
41
42static AMediaFormat* mergeMediaFormats(AMediaFormat* base, AMediaFormat* overlay) {
43 if (base == nullptr || overlay == nullptr) {
44 LOG(ERROR) << "Cannot merge null formats";
45 return nullptr;
46 }
47
48 AMediaFormat* format = AMediaFormat_new();
49 if (AMediaFormat_copy(format, base) != AMEDIA_OK) {
50 AMediaFormat_delete(format);
51 return nullptr;
52 }
53
54 // Note: AMediaFormat does not expose a function for appending values from another format or for
55 // iterating over all values and keys in a format. Instead we define a static list of known keys
56 // along with their value types and copy the ones that are present. A better solution would be
57 // to either implement required functions in NDK or to parse the overlay format's string
58 // representation and copy all existing keys.
59 static const struct {
60 const char* key;
61 void (*copyValue)(const char* key, AMediaFormat* to, AMediaFormat* from);
62 } kSupportedConfigs[] = {
63 {AMEDIAFORMAT_KEY_MIME, copyString},
64 {AMEDIAFORMAT_KEY_DURATION, copyInt64},
65 {AMEDIAFORMAT_KEY_WIDTH, copyInt32},
66 {AMEDIAFORMAT_KEY_HEIGHT, copyInt32},
67 {AMEDIAFORMAT_KEY_BIT_RATE, copyInt32},
68 {AMEDIAFORMAT_KEY_PROFILE, copyInt32},
69 {AMEDIAFORMAT_KEY_LEVEL, copyInt32},
70 {AMEDIAFORMAT_KEY_COLOR_FORMAT, copyInt32},
71 {AMEDIAFORMAT_KEY_COLOR_RANGE, copyInt32},
72 {AMEDIAFORMAT_KEY_COLOR_STANDARD, copyInt32},
73 {AMEDIAFORMAT_KEY_COLOR_TRANSFER, copyInt32},
74 {AMEDIAFORMAT_KEY_FRAME_RATE, copyInt32},
75 {AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, copyInt32},
76 };
77
78 for (int i = 0; i < (sizeof(kSupportedConfigs) / sizeof(kSupportedConfigs[0])); ++i) {
79 kSupportedConfigs[i].copyValue(kSupportedConfigs[i].key, format, overlay);
80 }
81
82 return format;
83}
84
85void MediaTranscoder::sendCallback(media_status_t status) {
86 bool expected = false;
87 if (mCallbackSent.compare_exchange_strong(expected, true)) {
88 if (status == AMEDIA_OK) {
89 mCallbacks->onFinished(this);
90 } else {
91 mCallbacks->onError(this, status);
92 }
93
94 // Transcoding is done and the callback to the client has been sent, so tear down the
95 // pipeline but do it asynchronously to avoid deadlocks. If an error occurred then
96 // automatically delete the output file.
97 const bool deleteOutputFile = status != AMEDIA_OK;
98 std::thread asyncCancelThread{
99 [self = shared_from_this(), deleteOutputFile] { self->cancel(deleteOutputFile); }};
100 asyncCancelThread.detach();
101 }
102}
103
104void MediaTranscoder::onTrackFinished(const MediaTrackTranscoder* transcoder) {
105 LOG(DEBUG) << "TrackTranscoder " << transcoder << " finished";
106}
107
108void MediaTranscoder::onTrackError(const MediaTrackTranscoder* transcoder, media_status_t status) {
109 LOG(DEBUG) << "TrackTranscoder " << transcoder << " returned error " << status;
110 sendCallback(status);
111}
112
113void MediaTranscoder::onSampleWriterFinished(media_status_t status) {
114 LOG((status != AMEDIA_OK) ? ERROR : DEBUG) << "Sample writer finished with status " << status;
115 sendCallback(status);
116}
117
118std::shared_ptr<MediaTranscoder> MediaTranscoder::create(
119 const std::shared_ptr<CallbackInterface>& callbacks,
120 const std::shared_ptr<Parcel>& pausedState) {
121 if (pausedState != nullptr) {
122 LOG(ERROR) << "Initializing from paused state is currently not supported.";
123 return nullptr;
124 } else if (callbacks == nullptr) {
125 LOG(ERROR) << "Callbacks cannot be null";
126 return nullptr;
127 }
128
129 return std::shared_ptr<MediaTranscoder>(new MediaTranscoder(callbacks));
130}
131
132media_status_t MediaTranscoder::configureSource(const char* path) {
133 if (path == nullptr) {
134 LOG(ERROR) << "Source path cannot be null";
135 return AMEDIA_ERROR_INVALID_PARAMETER;
136 }
137
138 const int fd = open(path, O_RDONLY);
139 if (fd <= 0) {
140 LOG(ERROR) << "Unable to open source path: " << path;
141 return AMEDIA_ERROR_INVALID_PARAMETER;
142 }
143
144 const size_t fileSize = lseek(fd, 0, SEEK_END);
145 lseek(fd, 0, SEEK_SET);
146
147 mSampleReader = MediaSampleReaderNDK::createFromFd(fd, 0 /* offset */, fileSize);
148 close(fd);
149
150 if (mSampleReader == nullptr) {
151 LOG(ERROR) << "Unable to parse source file: " << path;
152 return AMEDIA_ERROR_UNSUPPORTED;
153 }
154
155 const size_t trackCount = mSampleReader->getTrackCount();
156 for (size_t trackIndex = 0; trackIndex < trackCount; ++trackIndex) {
157 AMediaFormat* trackFormat = mSampleReader->getTrackFormat(static_cast<int>(trackIndex));
158 if (trackFormat == nullptr) {
159 LOG(ERROR) << "Track #" << trackIndex << " has no format";
160 return AMEDIA_ERROR_MALFORMED;
161 }
162
163 mSourceTrackFormats.emplace_back(trackFormat, &AMediaFormat_delete);
164 }
165
166 return AMEDIA_OK;
167}
168
169std::vector<std::shared_ptr<AMediaFormat>> MediaTranscoder::getTrackFormats() const {
170 // Return a deep copy of the formats to avoid the caller modifying our internal formats.
171 std::vector<std::shared_ptr<AMediaFormat>> trackFormats;
172 for (const std::shared_ptr<AMediaFormat>& sourceFormat : mSourceTrackFormats) {
173 AMediaFormat* copy = AMediaFormat_new();
174 AMediaFormat_copy(copy, sourceFormat.get());
175 trackFormats.emplace_back(copy, &AMediaFormat_delete);
176 }
177 return trackFormats;
178}
179
180media_status_t MediaTranscoder::configureTrackFormat(size_t trackIndex, AMediaFormat* trackFormat) {
181 if (mSampleReader == nullptr) {
182 LOG(ERROR) << "Source must be configured before tracks";
183 return AMEDIA_ERROR_INVALID_OPERATION;
184 } else if (trackIndex >= mSourceTrackFormats.size()) {
185 LOG(ERROR) << "Track index " << trackIndex
186 << " is out of bounds. Track count: " << mSourceTrackFormats.size();
187 return AMEDIA_ERROR_INVALID_PARAMETER;
188 }
189
190 std::unique_ptr<MediaTrackTranscoder> transcoder = nullptr;
191 std::shared_ptr<AMediaFormat> format = nullptr;
192
193 if (trackFormat == nullptr) {
194 transcoder = std::make_unique<PassthroughTrackTranscoder>(shared_from_this());
195 } else {
196 const char* srcMime = nullptr;
197 if (!AMediaFormat_getString(mSourceTrackFormats[trackIndex].get(), AMEDIAFORMAT_KEY_MIME,
198 &srcMime)) {
199 LOG(ERROR) << "Source track #" << trackIndex << " has no mime type";
200 return AMEDIA_ERROR_MALFORMED;
201 }
202
203 if (strncmp(srcMime, "video/", 6) != 0) {
204 LOG(ERROR) << "Only video tracks are supported for transcoding. Unable to configure "
205 "track #"
206 << trackIndex << " with mime " << srcMime;
207 return AMEDIA_ERROR_UNSUPPORTED;
208 }
209
210 const char* dstMime = nullptr;
211 if (AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &dstMime)) {
212 if (strncmp(dstMime, "video/", 6) != 0) {
213 LOG(ERROR) << "Unable to convert media types for track #" << trackIndex << ", from "
214 << srcMime << " to " << dstMime;
215 return AMEDIA_ERROR_UNSUPPORTED;
216 }
217 }
218
219 transcoder = std::make_unique<VideoTrackTranscoder>(shared_from_this());
220
221 AMediaFormat* mergedFormat =
222 mergeMediaFormats(mSourceTrackFormats[trackIndex].get(), trackFormat);
223 if (mergedFormat == nullptr) {
224 LOG(ERROR) << "Unable to merge source and destination formats";
225 return AMEDIA_ERROR_UNKNOWN;
226 }
227
228 format = std::shared_ptr<AMediaFormat>(mergedFormat, &AMediaFormat_delete);
229 }
230
231 media_status_t status = transcoder->configure(mSampleReader, trackIndex, format);
232 if (status != AMEDIA_OK) {
233 LOG(ERROR) << "Configure track transcoder for track #" << trackIndex << " returned error "
234 << status;
235 return status;
236 }
237
238 mTrackTranscoders.emplace_back(std::move(transcoder));
239 return AMEDIA_OK;
240}
241
242media_status_t MediaTranscoder::configureDestination(const char* path) {
243 if (path == nullptr || strlen(path) < 1) {
244 LOG(ERROR) << "Invalid destination path: " << path;
245 return AMEDIA_ERROR_INVALID_PARAMETER;
246 } else if (mSampleWriter != nullptr) {
247 LOG(ERROR) << "Destination is already configured.";
248 return AMEDIA_ERROR_INVALID_OPERATION;
249 }
250
251 // Write-only, create file if non-existent, don't overwrite existing file.
252 static constexpr int kOpenFlags = O_WRONLY | O_CREAT | O_EXCL;
253 // User R+W permission.
254 static constexpr int kFileMode = S_IRUSR | S_IWUSR;
255
256 const int fd = open(path, kOpenFlags, kFileMode);
257 if (fd < 0) {
258 LOG(ERROR) << "Unable to open destination file \"" << path << "\" for writing: " << fd;
259 return AMEDIA_ERROR_INVALID_PARAMETER;
260 }
261
262 mDestinationPath = std::string(path);
263
264 mSampleWriter = std::make_unique<MediaSampleWriter>();
265 const bool initOk = mSampleWriter->init(
266 fd, std::bind(&MediaTranscoder::onSampleWriterFinished, this, std::placeholders::_1));
267 close(fd);
268
269 if (!initOk) {
270 LOG(ERROR) << "Unable to initialize sample writer with destination path " << path;
271 mSampleWriter.reset();
272 return AMEDIA_ERROR_UNKNOWN;
273 }
274
275 return AMEDIA_OK;
276}
277
278media_status_t MediaTranscoder::start() {
279 if (mTrackTranscoders.size() < 1) {
280 LOG(ERROR) << "Unable to start, no tracks are configured.";
281 return AMEDIA_ERROR_INVALID_OPERATION;
282 } else if (mSampleWriter == nullptr) {
283 LOG(ERROR) << "Unable to start, destination is not configured";
284 return AMEDIA_ERROR_INVALID_OPERATION;
285 }
286
287 // Add tracks to the writer.
288 for (auto& transcoder : mTrackTranscoders) {
289 const bool ok = mSampleWriter->addTrack(transcoder->getOutputQueue(),
290 transcoder->getOutputFormat());
291 if (!ok) {
292 LOG(ERROR) << "Unable to add track to sample writer.";
293 return AMEDIA_ERROR_UNKNOWN;
294 }
295 }
296
297 bool started = mSampleWriter->start();
298 if (!started) {
299 LOG(ERROR) << "Unable to start sample writer.";
300 return AMEDIA_ERROR_UNKNOWN;
301 }
302
303 // Start transcoders
304 for (auto& transcoder : mTrackTranscoders) {
305 started = transcoder->start();
306 if (!started) {
307 LOG(ERROR) << "Unable to start track transcoder.";
308 cancel(true);
309 return AMEDIA_ERROR_UNKNOWN;
310 }
311 }
312 return AMEDIA_OK;
313}
314
315media_status_t MediaTranscoder::pause(std::shared_ptr<const Parcelable>* pausedState) {
316 (void)pausedState;
317 LOG(ERROR) << "Pause is not currently supported";
318 return AMEDIA_ERROR_UNSUPPORTED;
319}
320
321media_status_t MediaTranscoder::resume() {
322 LOG(ERROR) << "Resume is not currently supported";
323 return AMEDIA_ERROR_UNSUPPORTED;
324}
325
326media_status_t MediaTranscoder::cancel(bool deleteDestinationFile) {
327 bool expected = false;
328 if (!mCancelled.compare_exchange_strong(expected, true)) {
329 // Already cancelled.
330 return AMEDIA_OK;
331 }
332
333 mSampleWriter->stop();
334 for (auto& transcoder : mTrackTranscoders) {
335 transcoder->stop();
336 }
337
338 // TODO(chz): file deletion should be done by upper level from the content URI.
339 if (deleteDestinationFile && !mDestinationPath.empty()) {
340 int error = unlink(mDestinationPath.c_str());
341 if (error) {
342 LOG(ERROR) << "Unable to delete destination file " << mDestinationPath.c_str() << ": "
343 << error;
344 return AMEDIA_ERROR_IO;
345 }
346 }
347 return AMEDIA_OK;
348}
349
350} // namespace android