blob: 35bdc40773ac3970bc0b83f51b2772e2a0a00d21 [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>
Chong Zhangb55c5452020-06-26 14:32:12 -070021#include <binder/Parcel.h>
Linus Nilssoncab39d82020-05-14 16:32:21 -070022#include <fcntl.h>
23#include <media/MediaSampleReaderNDK.h>
Chong Zhang308e91f2020-06-10 15:27:56 -070024#include <media/MediaSampleWriter.h>
Linus Nilssoncab39d82020-05-14 16:32:21 -070025#include <media/MediaTranscoder.h>
26#include <media/PassthroughTrackTranscoder.h>
27#include <media/VideoTrackTranscoder.h>
28#include <unistd.h>
29
30namespace android {
31
32#define DEFINE_FORMAT_VALUE_COPY_FUNC(_type, _typeName) \
33 static void copy##_typeName(const char* key, AMediaFormat* to, AMediaFormat* from) { \
34 _type value; \
35 if (AMediaFormat_get##_typeName(from, key, &value)) { \
36 AMediaFormat_set##_typeName(to, key, value); \
37 } \
38 }
39
40DEFINE_FORMAT_VALUE_COPY_FUNC(const char*, String);
41DEFINE_FORMAT_VALUE_COPY_FUNC(int64_t, Int64);
42DEFINE_FORMAT_VALUE_COPY_FUNC(int32_t, Int32);
43
44static AMediaFormat* mergeMediaFormats(AMediaFormat* base, AMediaFormat* overlay) {
45 if (base == nullptr || overlay == nullptr) {
46 LOG(ERROR) << "Cannot merge null formats";
47 return nullptr;
48 }
49
50 AMediaFormat* format = AMediaFormat_new();
51 if (AMediaFormat_copy(format, base) != AMEDIA_OK) {
52 AMediaFormat_delete(format);
53 return nullptr;
54 }
55
56 // Note: AMediaFormat does not expose a function for appending values from another format or for
57 // iterating over all values and keys in a format. Instead we define a static list of known keys
58 // along with their value types and copy the ones that are present. A better solution would be
59 // to either implement required functions in NDK or to parse the overlay format's string
60 // representation and copy all existing keys.
61 static const struct {
62 const char* key;
63 void (*copyValue)(const char* key, AMediaFormat* to, AMediaFormat* from);
64 } kSupportedConfigs[] = {
65 {AMEDIAFORMAT_KEY_MIME, copyString},
66 {AMEDIAFORMAT_KEY_DURATION, copyInt64},
67 {AMEDIAFORMAT_KEY_WIDTH, copyInt32},
68 {AMEDIAFORMAT_KEY_HEIGHT, copyInt32},
69 {AMEDIAFORMAT_KEY_BIT_RATE, copyInt32},
70 {AMEDIAFORMAT_KEY_PROFILE, copyInt32},
71 {AMEDIAFORMAT_KEY_LEVEL, copyInt32},
72 {AMEDIAFORMAT_KEY_COLOR_FORMAT, copyInt32},
73 {AMEDIAFORMAT_KEY_COLOR_RANGE, copyInt32},
74 {AMEDIAFORMAT_KEY_COLOR_STANDARD, copyInt32},
75 {AMEDIAFORMAT_KEY_COLOR_TRANSFER, copyInt32},
76 {AMEDIAFORMAT_KEY_FRAME_RATE, copyInt32},
77 {AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, copyInt32},
78 };
79
80 for (int i = 0; i < (sizeof(kSupportedConfigs) / sizeof(kSupportedConfigs[0])); ++i) {
81 kSupportedConfigs[i].copyValue(kSupportedConfigs[i].key, format, overlay);
82 }
83
84 return format;
85}
86
87void MediaTranscoder::sendCallback(media_status_t status) {
Chong Zhangb55c5452020-06-26 14:32:12 -070088 // If the transcoder is already cancelled explicitly, don't send any error callbacks.
89 // Tracks and sample writer will report errors for abort. However, currently we can't
90 // tell it apart from real errors. Ideally we still want to report real errors back
91 // to client, as there is a small chance that explicit abort and the real error come
92 // at around the same time, we should report that if abort has a specific error code.
93 // On the other hand, if the transcoder actually finished (status is AMEDIA_OK) at around
94 // the same time of the abort, we should still report the finish back to the client.
95 if (mCancelled && status != AMEDIA_OK) {
96 return;
97 }
98
Linus Nilssoncab39d82020-05-14 16:32:21 -070099 bool expected = false;
100 if (mCallbackSent.compare_exchange_strong(expected, true)) {
101 if (status == AMEDIA_OK) {
102 mCallbacks->onFinished(this);
103 } else {
104 mCallbacks->onError(this, status);
105 }
106
107 // Transcoding is done and the callback to the client has been sent, so tear down the
Chong Zhang308e91f2020-06-10 15:27:56 -0700108 // pipeline but do it asynchronously to avoid deadlocks. If an error occurred, client
109 // should clean up the file.
Chong Zhanga2cc86b2020-06-17 16:56:49 -0700110 std::thread asyncCancelThread{[self = shared_from_this()] { self->cancel(); }};
Linus Nilssoncab39d82020-05-14 16:32:21 -0700111 asyncCancelThread.detach();
112 }
113}
114
Chong Zhanga2cc86b2020-06-17 16:56:49 -0700115void MediaTranscoder::onTrackFormatAvailable(const MediaTrackTranscoder* transcoder) {
116 LOG(INFO) << "TrackTranscoder " << transcoder << " format available.";
117
118 std::scoped_lock lock{mTracksAddedMutex};
119
120 // Ignore duplicate format change.
121 if (mTracksAdded.count(transcoder) > 0) {
122 return;
123 }
124
125 // Add track to the writer.
Linus Nilssonc31d2492020-09-23 12:30:00 -0700126 auto consumer = mSampleWriter->addTrack(transcoder->getOutputFormat());
127 if (consumer == nullptr) {
Chong Zhanga2cc86b2020-06-17 16:56:49 -0700128 LOG(ERROR) << "Unable to add track to sample writer.";
129 sendCallback(AMEDIA_ERROR_UNKNOWN);
130 return;
131 }
132
Linus Nilssonc31d2492020-09-23 12:30:00 -0700133 MediaTrackTranscoder* mutableTranscoder = const_cast<MediaTrackTranscoder*>(transcoder);
134 mutableTranscoder->setSampleConsumer(consumer);
135
Chong Zhanga2cc86b2020-06-17 16:56:49 -0700136 mTracksAdded.insert(transcoder);
137 if (mTracksAdded.size() == mTrackTranscoders.size()) {
Linus Nilsson6233fed2020-08-13 15:15:14 -0700138 // Enable sequential access mode on the sample reader to achieve optimal read performance.
139 // This has to wait until all tracks have delivered their output formats and the sample
140 // writer is started. Otherwise the tracks will not get their output sample queues drained
141 // and the transcoder could hang due to one track running out of buffers and blocking the
142 // other tracks from reading source samples before they could output their formats.
143 mSampleReader->setEnforceSequentialAccess(true);
Chong Zhanga2cc86b2020-06-17 16:56:49 -0700144 LOG(INFO) << "Starting sample writer.";
145 bool started = mSampleWriter->start();
146 if (!started) {
147 LOG(ERROR) << "Unable to start sample writer.";
148 sendCallback(AMEDIA_ERROR_UNKNOWN);
149 }
150 }
151}
152
Linus Nilssoncab39d82020-05-14 16:32:21 -0700153void MediaTranscoder::onTrackFinished(const MediaTrackTranscoder* transcoder) {
154 LOG(DEBUG) << "TrackTranscoder " << transcoder << " finished";
155}
156
157void MediaTranscoder::onTrackError(const MediaTrackTranscoder* transcoder, media_status_t status) {
158 LOG(DEBUG) << "TrackTranscoder " << transcoder << " returned error " << status;
159 sendCallback(status);
160}
161
Linus Nilssone2cdd1f2020-07-07 17:29:26 -0700162void MediaTranscoder::onFinished(const MediaSampleWriter* writer __unused, media_status_t status) {
Linus Nilssoncab39d82020-05-14 16:32:21 -0700163 LOG((status != AMEDIA_OK) ? ERROR : DEBUG) << "Sample writer finished with status " << status;
164 sendCallback(status);
165}
166
Linus Nilssone2cdd1f2020-07-07 17:29:26 -0700167void MediaTranscoder::onProgressUpdate(const MediaSampleWriter* writer __unused, int32_t progress) {
168 // Dispatch progress updated to the client.
169 mCallbacks->onProgressUpdate(this, progress);
170}
171
Chong Zhang308e91f2020-06-10 15:27:56 -0700172MediaTranscoder::MediaTranscoder(const std::shared_ptr<CallbackInterface>& callbacks)
Chong Zhanga2cc86b2020-06-17 16:56:49 -0700173 : mCallbacks(callbacks) {}
Chong Zhang308e91f2020-06-10 15:27:56 -0700174
Linus Nilssoncab39d82020-05-14 16:32:21 -0700175std::shared_ptr<MediaTranscoder> MediaTranscoder::create(
176 const std::shared_ptr<CallbackInterface>& callbacks,
Chong Zhangb55c5452020-06-26 14:32:12 -0700177 const std::shared_ptr<const Parcel>& pausedState) {
Linus Nilssoncab39d82020-05-14 16:32:21 -0700178 if (pausedState != nullptr) {
Chong Zhangb55c5452020-06-26 14:32:12 -0700179 LOG(INFO) << "Initializing from paused state.";
180 }
181 if (callbacks == nullptr) {
Linus Nilssoncab39d82020-05-14 16:32:21 -0700182 LOG(ERROR) << "Callbacks cannot be null";
183 return nullptr;
184 }
185
186 return std::shared_ptr<MediaTranscoder>(new MediaTranscoder(callbacks));
187}
188
Chong Zhang308e91f2020-06-10 15:27:56 -0700189media_status_t MediaTranscoder::configureSource(int fd) {
190 if (fd < 0) {
191 LOG(ERROR) << "Invalid source fd: " << fd;
Linus Nilssoncab39d82020-05-14 16:32:21 -0700192 return AMEDIA_ERROR_INVALID_PARAMETER;
193 }
194
195 const size_t fileSize = lseek(fd, 0, SEEK_END);
196 lseek(fd, 0, SEEK_SET);
197
198 mSampleReader = MediaSampleReaderNDK::createFromFd(fd, 0 /* offset */, fileSize);
Linus Nilssoncab39d82020-05-14 16:32:21 -0700199
200 if (mSampleReader == nullptr) {
Chong Zhang308e91f2020-06-10 15:27:56 -0700201 LOG(ERROR) << "Unable to parse source fd: " << fd;
Linus Nilssoncab39d82020-05-14 16:32:21 -0700202 return AMEDIA_ERROR_UNSUPPORTED;
203 }
204
205 const size_t trackCount = mSampleReader->getTrackCount();
206 for (size_t trackIndex = 0; trackIndex < trackCount; ++trackIndex) {
207 AMediaFormat* trackFormat = mSampleReader->getTrackFormat(static_cast<int>(trackIndex));
208 if (trackFormat == nullptr) {
209 LOG(ERROR) << "Track #" << trackIndex << " has no format";
210 return AMEDIA_ERROR_MALFORMED;
211 }
212
213 mSourceTrackFormats.emplace_back(trackFormat, &AMediaFormat_delete);
214 }
215
216 return AMEDIA_OK;
217}
218
219std::vector<std::shared_ptr<AMediaFormat>> MediaTranscoder::getTrackFormats() const {
220 // Return a deep copy of the formats to avoid the caller modifying our internal formats.
221 std::vector<std::shared_ptr<AMediaFormat>> trackFormats;
222 for (const std::shared_ptr<AMediaFormat>& sourceFormat : mSourceTrackFormats) {
223 AMediaFormat* copy = AMediaFormat_new();
224 AMediaFormat_copy(copy, sourceFormat.get());
225 trackFormats.emplace_back(copy, &AMediaFormat_delete);
226 }
227 return trackFormats;
228}
229
230media_status_t MediaTranscoder::configureTrackFormat(size_t trackIndex, AMediaFormat* trackFormat) {
231 if (mSampleReader == nullptr) {
232 LOG(ERROR) << "Source must be configured before tracks";
233 return AMEDIA_ERROR_INVALID_OPERATION;
234 } else if (trackIndex >= mSourceTrackFormats.size()) {
235 LOG(ERROR) << "Track index " << trackIndex
236 << " is out of bounds. Track count: " << mSourceTrackFormats.size();
237 return AMEDIA_ERROR_INVALID_PARAMETER;
238 }
239
Linus Nilsson6233fed2020-08-13 15:15:14 -0700240 media_status_t status = mSampleReader->selectTrack(trackIndex);
241 if (status != AMEDIA_OK) {
242 LOG(ERROR) << "Unable to select track " << trackIndex;
243 return status;
244 }
245
Linus Nilssone4716f22020-07-10 16:07:57 -0700246 std::shared_ptr<MediaTrackTranscoder> transcoder;
247 std::shared_ptr<AMediaFormat> format;
Linus Nilssoncab39d82020-05-14 16:32:21 -0700248
249 if (trackFormat == nullptr) {
Linus Nilssone4716f22020-07-10 16:07:57 -0700250 transcoder = std::make_shared<PassthroughTrackTranscoder>(shared_from_this());
Linus Nilssoncab39d82020-05-14 16:32:21 -0700251 } else {
252 const char* srcMime = nullptr;
253 if (!AMediaFormat_getString(mSourceTrackFormats[trackIndex].get(), AMEDIAFORMAT_KEY_MIME,
254 &srcMime)) {
255 LOG(ERROR) << "Source track #" << trackIndex << " has no mime type";
256 return AMEDIA_ERROR_MALFORMED;
257 }
258
259 if (strncmp(srcMime, "video/", 6) != 0) {
260 LOG(ERROR) << "Only video tracks are supported for transcoding. Unable to configure "
261 "track #"
262 << trackIndex << " with mime " << srcMime;
263 return AMEDIA_ERROR_UNSUPPORTED;
264 }
265
266 const char* dstMime = nullptr;
267 if (AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &dstMime)) {
268 if (strncmp(dstMime, "video/", 6) != 0) {
269 LOG(ERROR) << "Unable to convert media types for track #" << trackIndex << ", from "
270 << srcMime << " to " << dstMime;
271 return AMEDIA_ERROR_UNSUPPORTED;
272 }
273 }
274
Linus Nilssone4716f22020-07-10 16:07:57 -0700275 transcoder = VideoTrackTranscoder::create(shared_from_this());
Linus Nilssoncab39d82020-05-14 16:32:21 -0700276
277 AMediaFormat* mergedFormat =
278 mergeMediaFormats(mSourceTrackFormats[trackIndex].get(), trackFormat);
279 if (mergedFormat == nullptr) {
280 LOG(ERROR) << "Unable to merge source and destination formats";
281 return AMEDIA_ERROR_UNKNOWN;
282 }
283
284 format = std::shared_ptr<AMediaFormat>(mergedFormat, &AMediaFormat_delete);
285 }
286
Chong Zhangc8c88cc2020-09-17 12:50:49 -0700287 status = transcoder->configure(mSampleReader, trackIndex, format);
Linus Nilssoncab39d82020-05-14 16:32:21 -0700288 if (status != AMEDIA_OK) {
289 LOG(ERROR) << "Configure track transcoder for track #" << trackIndex << " returned error "
290 << status;
291 return status;
292 }
293
294 mTrackTranscoders.emplace_back(std::move(transcoder));
295 return AMEDIA_OK;
296}
297
Chong Zhang308e91f2020-06-10 15:27:56 -0700298media_status_t MediaTranscoder::configureDestination(int fd) {
299 if (fd < 0) {
300 LOG(ERROR) << "Invalid destination fd: " << fd;
Linus Nilssoncab39d82020-05-14 16:32:21 -0700301 return AMEDIA_ERROR_INVALID_PARAMETER;
Chong Zhang308e91f2020-06-10 15:27:56 -0700302 }
303
304 if (mSampleWriter != nullptr) {
Linus Nilssoncab39d82020-05-14 16:32:21 -0700305 LOG(ERROR) << "Destination is already configured.";
306 return AMEDIA_ERROR_INVALID_OPERATION;
307 }
308
Linus Nilssonc31d2492020-09-23 12:30:00 -0700309 mSampleWriter = MediaSampleWriter::Create();
Linus Nilssone2cdd1f2020-07-07 17:29:26 -0700310 const bool initOk = mSampleWriter->init(fd, shared_from_this());
Linus Nilssoncab39d82020-05-14 16:32:21 -0700311
312 if (!initOk) {
Chong Zhang308e91f2020-06-10 15:27:56 -0700313 LOG(ERROR) << "Unable to initialize sample writer with destination fd: " << fd;
Linus Nilssoncab39d82020-05-14 16:32:21 -0700314 mSampleWriter.reset();
315 return AMEDIA_ERROR_UNKNOWN;
316 }
317
318 return AMEDIA_OK;
319}
320
321media_status_t MediaTranscoder::start() {
322 if (mTrackTranscoders.size() < 1) {
323 LOG(ERROR) << "Unable to start, no tracks are configured.";
324 return AMEDIA_ERROR_INVALID_OPERATION;
325 } else if (mSampleWriter == nullptr) {
326 LOG(ERROR) << "Unable to start, destination is not configured";
327 return AMEDIA_ERROR_INVALID_OPERATION;
328 }
329
Linus Nilssoncab39d82020-05-14 16:32:21 -0700330 // Start transcoders
331 for (auto& transcoder : mTrackTranscoders) {
Chong Zhanga2cc86b2020-06-17 16:56:49 -0700332 bool started = transcoder->start();
Linus Nilssoncab39d82020-05-14 16:32:21 -0700333 if (!started) {
334 LOG(ERROR) << "Unable to start track transcoder.";
Chong Zhang308e91f2020-06-10 15:27:56 -0700335 cancel();
Linus Nilssoncab39d82020-05-14 16:32:21 -0700336 return AMEDIA_ERROR_UNKNOWN;
337 }
338 }
339 return AMEDIA_OK;
340}
341
Chong Zhangb55c5452020-06-26 14:32:12 -0700342media_status_t MediaTranscoder::pause(std::shared_ptr<const Parcel>* pausedState) {
343 // TODO: write internal states to parcel.
344 *pausedState = std::make_shared<Parcel>();
345 return cancel();
Linus Nilssoncab39d82020-05-14 16:32:21 -0700346}
347
348media_status_t MediaTranscoder::resume() {
Chong Zhangb55c5452020-06-26 14:32:12 -0700349 // TODO: restore internal states from parcel.
350 return start();
Linus Nilssoncab39d82020-05-14 16:32:21 -0700351}
352
Chong Zhang308e91f2020-06-10 15:27:56 -0700353media_status_t MediaTranscoder::cancel() {
Linus Nilssoncab39d82020-05-14 16:32:21 -0700354 bool expected = false;
355 if (!mCancelled.compare_exchange_strong(expected, true)) {
356 // Already cancelled.
357 return AMEDIA_OK;
358 }
359
360 mSampleWriter->stop();
Linus Nilsson6233fed2020-08-13 15:15:14 -0700361 mSampleReader->setEnforceSequentialAccess(false);
Linus Nilssoncab39d82020-05-14 16:32:21 -0700362 for (auto& transcoder : mTrackTranscoders) {
363 transcoder->stop();
364 }
365
Linus Nilssoncab39d82020-05-14 16:32:21 -0700366 return AMEDIA_OK;
367}
368
369} // namespace android