1 /*
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/MediaSampleWriter.h>
24 #include <media/MediaTranscoder.h>
25 #include <media/NdkCommon.h>
26 #include <media/PassthroughTrackTranscoder.h>
27 #include <media/VideoTrackTranscoder.h>
28 #include <sys/prctl.h>
29 #include <unistd.h>
30
31 namespace android {
32
createVideoTrackFormat(AMediaFormat * srcFormat,AMediaFormat * options)33 static std::shared_ptr<AMediaFormat> createVideoTrackFormat(AMediaFormat* srcFormat,
34 AMediaFormat* options) {
35 if (srcFormat == nullptr || options == nullptr) {
36 LOG(ERROR) << "Cannot merge null formats";
37 return nullptr;
38 }
39
40 // ------- Define parameters to copy from the source track format -------
41 std::vector<AMediaFormatUtils::EntryCopier> srcParamsToCopy{
42 ENTRY_COPIER(AMEDIAFORMAT_KEY_MIME, String),
43 ENTRY_COPIER(AMEDIAFORMAT_KEY_DURATION, Int64),
44 ENTRY_COPIER(AMEDIAFORMAT_KEY_WIDTH, Int32),
45 ENTRY_COPIER(AMEDIAFORMAT_KEY_HEIGHT, Int32),
46 ENTRY_COPIER(AMEDIAFORMAT_KEY_FRAME_RATE, Int32),
47 ENTRY_COPIER(AMEDIAFORMAT_KEY_COLOR_RANGE, Int32),
48 ENTRY_COPIER(AMEDIAFORMAT_KEY_COLOR_STANDARD, Int32),
49 ENTRY_COPIER(AMEDIAFORMAT_KEY_COLOR_TRANSFER, Int32),
50 };
51
52 // If the destination codec is the same as the source codec, we can preserve profile and level
53 // from the source track as default values. Otherwise leave them unspecified.
54 const char *srcMime, *dstMime;
55 AMediaFormat_getString(srcFormat, AMEDIAFORMAT_KEY_MIME, &srcMime);
56 if (!AMediaFormat_getString(options, AMEDIAFORMAT_KEY_MIME, &dstMime) ||
57 strcmp(srcMime, dstMime) == 0) {
58 srcParamsToCopy.push_back(ENTRY_COPIER(AMEDIAFORMAT_KEY_PROFILE, String));
59 srcParamsToCopy.push_back(ENTRY_COPIER(AMEDIAFORMAT_KEY_LEVEL, String));
60 }
61
62 // ------- Define parameters to copy from the caller's options -------
63 static const std::vector<AMediaFormatUtils::EntryCopier> kSupportedOptions{
64 ENTRY_COPIER(AMEDIAFORMAT_KEY_MIME, String),
65 ENTRY_COPIER(AMEDIAFORMAT_KEY_DURATION, Int64),
66 ENTRY_COPIER(AMEDIAFORMAT_KEY_WIDTH, Int32),
67 ENTRY_COPIER(AMEDIAFORMAT_KEY_HEIGHT, Int32),
68 ENTRY_COPIER(AMEDIAFORMAT_KEY_BIT_RATE, Int32),
69 ENTRY_COPIER(AMEDIAFORMAT_KEY_PROFILE, Int32),
70 ENTRY_COPIER(AMEDIAFORMAT_KEY_LEVEL, Int32),
71 ENTRY_COPIER(AMEDIAFORMAT_KEY_COLOR_RANGE, Int32),
72 ENTRY_COPIER(AMEDIAFORMAT_KEY_COLOR_STANDARD, Int32),
73 ENTRY_COPIER(AMEDIAFORMAT_KEY_COLOR_TRANSFER, Int32),
74 ENTRY_COPIER(AMEDIAFORMAT_KEY_FRAME_RATE, Int32),
75 ENTRY_COPIER(AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, Int32),
76 ENTRY_COPIER(AMEDIAFORMAT_KEY_PRIORITY, Int32),
77 ENTRY_COPIER2(AMEDIAFORMAT_KEY_OPERATING_RATE, Float, Int32),
78 };
79
80 // ------- Copy parameters from source and options to the destination -------
81 auto trackFormat = std::shared_ptr<AMediaFormat>(AMediaFormat_new(), &AMediaFormat_delete);
82 AMediaFormatUtils::CopyFormatEntries(srcFormat, trackFormat.get(), srcParamsToCopy);
83 AMediaFormatUtils::CopyFormatEntries(options, trackFormat.get(), kSupportedOptions);
84 return trackFormat;
85 }
86
onThreadFinished(const void * thread,media_status_t threadStatus,bool threadStopped)87 void MediaTranscoder::onThreadFinished(const void* thread, media_status_t threadStatus,
88 bool threadStopped) {
89 LOG(DEBUG) << "Thread " << thread << " finished with status " << threadStatus << " stopped "
90 << threadStopped;
91
92 // Stop all threads if one reports an error.
93 if (threadStatus != AMEDIA_OK) {
94 requestStop(false /* stopOnSync */);
95 }
96
97 std::scoped_lock lock{mThreadStateMutex};
98
99 // Record the change.
100 mThreadStates[thread] = DONE;
101 if (threadStatus != AMEDIA_OK && mTranscoderStatus == AMEDIA_OK) {
102 mTranscoderStatus = threadStatus;
103 }
104
105 mTranscoderStopped |= threadStopped;
106
107 // Check if all threads are done. Note that if all transcoders have stopped but the sample
108 // writer has not yet started, it never will.
109 bool transcodersDone = true;
110 ThreadState sampleWriterState = PENDING;
111 for (const auto& it : mThreadStates) {
112 LOG(DEBUG) << " Thread " << it.first << " state" << it.second;
113 if (it.first == static_cast<const void*>(mSampleWriter.get())) {
114 sampleWriterState = it.second;
115 } else {
116 transcodersDone &= (it.second == DONE);
117 }
118 }
119 if (!transcodersDone || sampleWriterState == RUNNING) {
120 return;
121 }
122
123 // All done. Send callback asynchronously and wake up threads waiting in cancel/pause.
124 mThreadsDone = true;
125 if (!mCallbackSent) {
126 std::thread asyncNotificationThread{[this, self = shared_from_this(),
127 status = mTranscoderStatus,
128 stopped = mTranscoderStopped] {
129 prctl(PR_SET_NAME, (unsigned long)"TranscodCallbk", 0, 0, 0);
130
131 // If the transcoder was stopped that means a caller is waiting in stop or pause
132 // in which case we don't send a callback.
133 if (status != AMEDIA_OK) {
134 mCallbacks->onError(this, status);
135 } else if (!stopped) {
136 mCallbacks->onFinished(this);
137 }
138 mThreadsDoneSignal.notify_all();
139 }};
140 asyncNotificationThread.detach();
141 mCallbackSent = true;
142 }
143 }
144
onTrackFormatAvailable(const MediaTrackTranscoder * transcoder)145 void MediaTranscoder::onTrackFormatAvailable(const MediaTrackTranscoder* transcoder) {
146 LOG(DEBUG) << "TrackTranscoder " << transcoder << " format available.";
147
148 std::scoped_lock lock{mTracksAddedMutex};
149 const void* sampleWriterPtr = static_cast<const void*>(mSampleWriter.get());
150
151 // Ignore duplicate format change.
152 if (mTracksAdded.count(transcoder) > 0) {
153 return;
154 }
155
156 // Add track to the writer.
157 auto consumer = mSampleWriter->addTrack(transcoder->getOutputFormat());
158 if (consumer == nullptr) {
159 LOG(ERROR) << "Unable to add track to sample writer.";
160 onThreadFinished(sampleWriterPtr, AMEDIA_ERROR_UNKNOWN, false /* stopped */);
161 return;
162 }
163
164 // The sample writer is not yet started so notify the caller that progress is still made.
165 if (mHeartBeatIntervalUs > 0) {
166 mCallbacks->onHeartBeat(this);
167 }
168
169 MediaTrackTranscoder* mutableTranscoder = const_cast<MediaTrackTranscoder*>(transcoder);
170 mutableTranscoder->setSampleConsumer(consumer);
171
172 mTracksAdded.insert(transcoder);
173 bool errorStarting = false;
174 if (mTracksAdded.size() == mTrackTranscoders.size()) {
175 // Enable sequential access mode on the sample reader to achieve optimal read performance.
176 // This has to wait until all tracks have delivered their output formats and the sample
177 // writer is started. Otherwise the tracks will not get their output sample queues drained
178 // and the transcoder could hang due to one track running out of buffers and blocking the
179 // other tracks from reading source samples before they could output their formats.
180
181 std::scoped_lock lock{mThreadStateMutex};
182 // Don't start the sample writer if a stop already has been requested.
183 if (!mSampleWriterStopped) {
184 if (!mCancelled) {
185 mSampleReader->setEnforceSequentialAccess(true);
186 }
187 LOG(DEBUG) << "Starting sample writer.";
188 errorStarting = !mSampleWriter->start();
189 if (!errorStarting) {
190 mThreadStates[sampleWriterPtr] = RUNNING;
191 }
192 }
193 }
194
195 if (errorStarting) {
196 LOG(ERROR) << "Unable to start sample writer.";
197 onThreadFinished(sampleWriterPtr, AMEDIA_ERROR_UNKNOWN, false /* stopped */);
198 }
199 }
200
onTrackFinished(const MediaTrackTranscoder * transcoder)201 void MediaTranscoder::onTrackFinished(const MediaTrackTranscoder* transcoder) {
202 LOG(DEBUG) << "TrackTranscoder " << transcoder << " finished";
203 onThreadFinished(static_cast<const void*>(transcoder), AMEDIA_OK, false /* stopped */);
204 }
205
onTrackStopped(const MediaTrackTranscoder * transcoder)206 void MediaTranscoder::onTrackStopped(const MediaTrackTranscoder* transcoder) {
207 LOG(DEBUG) << "TrackTranscoder " << transcoder << " stopped";
208 onThreadFinished(static_cast<const void*>(transcoder), AMEDIA_OK, true /* stopped */);
209 }
210
onTrackError(const MediaTrackTranscoder * transcoder,media_status_t status)211 void MediaTranscoder::onTrackError(const MediaTrackTranscoder* transcoder, media_status_t status) {
212 LOG(ERROR) << "TrackTranscoder " << transcoder << " returned error " << status;
213 onThreadFinished(static_cast<const void*>(transcoder), status, false /* stopped */);
214 }
215
onFinished(const MediaSampleWriter * writer,media_status_t status)216 void MediaTranscoder::onFinished(const MediaSampleWriter* writer, media_status_t status) {
217 LOG(status == AMEDIA_OK ? DEBUG : ERROR) << "Sample writer finished with status " << status;
218 onThreadFinished(static_cast<const void*>(writer), status, false /* stopped */);
219 }
220
onStopped(const MediaSampleWriter * writer)221 void MediaTranscoder::onStopped(const MediaSampleWriter* writer) {
222 LOG(DEBUG) << "Sample writer " << writer << " stopped";
223 onThreadFinished(static_cast<const void*>(writer), AMEDIA_OK, true /* stopped */);
224 }
225
onProgressUpdate(const MediaSampleWriter * writer __unused,int32_t progress)226 void MediaTranscoder::onProgressUpdate(const MediaSampleWriter* writer __unused, int32_t progress) {
227 // Dispatch progress updated to the client.
228 mCallbacks->onProgressUpdate(this, progress);
229 }
230
onHeartBeat(const MediaSampleWriter * writer __unused)231 void MediaTranscoder::onHeartBeat(const MediaSampleWriter* writer __unused) {
232 // Signal heart-beat to the client.
233 mCallbacks->onHeartBeat(this);
234 }
235
MediaTranscoder(const std::shared_ptr<CallbackInterface> & callbacks,int64_t heartBeatIntervalUs,pid_t pid,uid_t uid)236 MediaTranscoder::MediaTranscoder(const std::shared_ptr<CallbackInterface>& callbacks,
237 int64_t heartBeatIntervalUs, pid_t pid, uid_t uid)
238 : mCallbacks(callbacks), mHeartBeatIntervalUs(heartBeatIntervalUs), mPid(pid), mUid(uid) {}
239
create(const std::shared_ptr<CallbackInterface> & callbacks,int64_t heartBeatIntervalUs,pid_t pid,uid_t uid,const std::shared_ptr<ndk::ScopedAParcel> & pausedState)240 std::shared_ptr<MediaTranscoder> MediaTranscoder::create(
241 const std::shared_ptr<CallbackInterface>& callbacks, int64_t heartBeatIntervalUs, pid_t pid,
242 uid_t uid, const std::shared_ptr<ndk::ScopedAParcel>& pausedState) {
243 if (pausedState != nullptr) {
244 LOG(INFO) << "Initializing from paused state.";
245 }
246 if (callbacks == nullptr) {
247 LOG(ERROR) << "Callbacks cannot be null";
248 return nullptr;
249 }
250
251 return std::shared_ptr<MediaTranscoder>(
252 new MediaTranscoder(callbacks, heartBeatIntervalUs, pid, uid));
253 }
254
configureSource(int fd)255 media_status_t MediaTranscoder::configureSource(int fd) {
256 if (fd < 0) {
257 LOG(ERROR) << "Invalid source fd: " << fd;
258 return AMEDIA_ERROR_INVALID_PARAMETER;
259 }
260
261 const size_t fileSize = lseek(fd, 0, SEEK_END);
262 lseek(fd, 0, SEEK_SET);
263
264 mSampleReader = MediaSampleReaderNDK::createFromFd(fd, 0 /* offset */, fileSize);
265 if (mSampleReader == nullptr) {
266 LOG(ERROR) << "Unable to parse source fd: " << fd;
267 return AMEDIA_ERROR_UNSUPPORTED;
268 }
269
270 const size_t trackCount = mSampleReader->getTrackCount();
271 for (size_t trackIndex = 0; trackIndex < trackCount; ++trackIndex) {
272 AMediaFormat* trackFormat = mSampleReader->getTrackFormat(static_cast<int>(trackIndex));
273 if (trackFormat == nullptr) {
274 LOG(ERROR) << "Track #" << trackIndex << " has no format";
275 return AMEDIA_ERROR_MALFORMED;
276 }
277
278 mSourceTrackFormats.emplace_back(trackFormat, &AMediaFormat_delete);
279 }
280
281 return AMEDIA_OK;
282 }
283
getTrackFormats() const284 std::vector<std::shared_ptr<AMediaFormat>> MediaTranscoder::getTrackFormats() const {
285 // Return a deep copy of the formats to avoid the caller modifying our internal formats.
286 std::vector<std::shared_ptr<AMediaFormat>> trackFormats;
287 for (const std::shared_ptr<AMediaFormat>& sourceFormat : mSourceTrackFormats) {
288 AMediaFormat* copy = AMediaFormat_new();
289 AMediaFormat_copy(copy, sourceFormat.get());
290 trackFormats.emplace_back(copy, &AMediaFormat_delete);
291 }
292 return trackFormats;
293 }
294
configureTrackFormat(size_t trackIndex,AMediaFormat * destinationOptions)295 media_status_t MediaTranscoder::configureTrackFormat(size_t trackIndex,
296 AMediaFormat* destinationOptions) {
297 if (mSampleReader == nullptr) {
298 LOG(ERROR) << "Source must be configured before tracks";
299 return AMEDIA_ERROR_INVALID_OPERATION;
300 } else if (trackIndex >= mSourceTrackFormats.size()) {
301 LOG(ERROR) << "Track index " << trackIndex
302 << " is out of bounds. Track count: " << mSourceTrackFormats.size();
303 return AMEDIA_ERROR_INVALID_PARAMETER;
304 }
305
306 std::shared_ptr<MediaTrackTranscoder> transcoder;
307 std::shared_ptr<AMediaFormat> trackFormat;
308
309 if (destinationOptions == nullptr) {
310 transcoder = std::make_shared<PassthroughTrackTranscoder>(shared_from_this());
311 } else {
312 AMediaFormat* srcTrackFormat = mSourceTrackFormats[trackIndex].get();
313
314 const char* srcMime = nullptr;
315 if (!AMediaFormat_getString(srcTrackFormat, AMEDIAFORMAT_KEY_MIME, &srcMime)) {
316 LOG(ERROR) << "Source track #" << trackIndex << " has no mime type";
317 return AMEDIA_ERROR_MALFORMED;
318 }
319
320 if (strncmp(srcMime, "video/", 6) != 0) {
321 LOG(ERROR) << "Only video tracks are supported for transcoding. Unable to configure "
322 "track #"
323 << trackIndex << " with mime " << srcMime;
324 return AMEDIA_ERROR_UNSUPPORTED;
325 }
326
327 const char* dstMime = nullptr;
328 if (AMediaFormat_getString(destinationOptions, AMEDIAFORMAT_KEY_MIME, &dstMime)) {
329 if (strncmp(dstMime, "video/", 6) != 0) {
330 LOG(ERROR) << "Unable to convert media types for track #" << trackIndex << ", from "
331 << srcMime << " to " << dstMime;
332 return AMEDIA_ERROR_UNSUPPORTED;
333 }
334 }
335
336 transcoder = VideoTrackTranscoder::create(shared_from_this(), mPid, mUid);
337
338 trackFormat = createVideoTrackFormat(srcTrackFormat, destinationOptions);
339 if (trackFormat == nullptr) {
340 LOG(ERROR) << "Unable to create video track format";
341 return AMEDIA_ERROR_UNKNOWN;
342 }
343 }
344
345 media_status_t status = mSampleReader->selectTrack(trackIndex);
346 if (status != AMEDIA_OK) {
347 LOG(ERROR) << "Unable to select track " << trackIndex;
348 return status;
349 }
350
351 status = transcoder->configure(mSampleReader, trackIndex, trackFormat);
352 if (status != AMEDIA_OK) {
353 LOG(ERROR) << "Configure track transcoder for track #" << trackIndex << " returned error "
354 << status;
355 mSampleReader->unselectTrack(trackIndex);
356 return status;
357 }
358
359 std::scoped_lock lock{mThreadStateMutex};
360 mThreadStates[static_cast<const void*>(transcoder.get())] = PENDING;
361
362 mTrackTranscoders.emplace_back(std::move(transcoder));
363 return AMEDIA_OK;
364 }
365
configureDestination(int fd)366 media_status_t MediaTranscoder::configureDestination(int fd) {
367 if (fd < 0) {
368 LOG(ERROR) << "Invalid destination fd: " << fd;
369 return AMEDIA_ERROR_INVALID_PARAMETER;
370 }
371
372 if (mSampleWriter != nullptr) {
373 LOG(ERROR) << "Destination is already configured.";
374 return AMEDIA_ERROR_INVALID_OPERATION;
375 }
376
377 mSampleWriter = MediaSampleWriter::Create();
378 const bool initOk = mSampleWriter->init(fd, shared_from_this(), mHeartBeatIntervalUs);
379
380 if (!initOk) {
381 LOG(ERROR) << "Unable to initialize sample writer with destination fd: " << fd;
382 mSampleWriter.reset();
383 return AMEDIA_ERROR_UNKNOWN;
384 }
385
386 std::scoped_lock lock{mThreadStateMutex};
387 mThreadStates[static_cast<const void*>(mSampleWriter.get())] = PENDING;
388 return AMEDIA_OK;
389 }
390
start()391 media_status_t MediaTranscoder::start() {
392 if (mTrackTranscoders.size() < 1) {
393 LOG(ERROR) << "Unable to start, no tracks are configured.";
394 return AMEDIA_ERROR_INVALID_OPERATION;
395 } else if (mSampleWriter == nullptr) {
396 LOG(ERROR) << "Unable to start, destination is not configured";
397 return AMEDIA_ERROR_INVALID_OPERATION;
398 }
399
400 // Start transcoders
401 bool started = true;
402 {
403 std::scoped_lock lock{mThreadStateMutex};
404 for (auto& transcoder : mTrackTranscoders) {
405 if (!(started = transcoder->start())) {
406 break;
407 }
408 mThreadStates[static_cast<const void*>(transcoder.get())] = RUNNING;
409 }
410 }
411 if (!started) {
412 LOG(ERROR) << "Unable to start track transcoder.";
413 cancel();
414 return AMEDIA_ERROR_UNKNOWN;
415 }
416 return AMEDIA_OK;
417 }
418
requestStop(bool stopOnSync)419 media_status_t MediaTranscoder::requestStop(bool stopOnSync) {
420 std::scoped_lock lock{mThreadStateMutex};
421 if (mCancelled) {
422 LOG(DEBUG) << "MediaTranscoder already cancelled";
423 return AMEDIA_ERROR_UNSUPPORTED;
424 }
425
426 if (!stopOnSync) {
427 mSampleWriterStopped = true;
428 mSampleWriter->stop();
429 }
430
431 mSampleReader->setEnforceSequentialAccess(false);
432 for (auto& transcoder : mTrackTranscoders) {
433 transcoder->stop(stopOnSync);
434 }
435
436 mCancelled = true;
437 return AMEDIA_OK;
438 }
439
waitForThreads()440 void MediaTranscoder::waitForThreads() NO_THREAD_SAFETY_ANALYSIS {
441 std::unique_lock lock{mThreadStateMutex};
442 while (!mThreadsDone) {
443 mThreadsDoneSignal.wait(lock);
444 }
445 }
446
pause(std::shared_ptr<ndk::ScopedAParcel> * pausedState)447 media_status_t MediaTranscoder::pause(std::shared_ptr<ndk::ScopedAParcel>* pausedState) {
448 media_status_t status = requestStop(true /* stopOnSync */);
449 if (status != AMEDIA_OK) {
450 return status;
451 }
452
453 waitForThreads();
454
455 // TODO: write internal states to parcel.
456 *pausedState = std::shared_ptr<::ndk::ScopedAParcel>(new ::ndk::ScopedAParcel());
457 return AMEDIA_OK;
458 }
459
cancel()460 media_status_t MediaTranscoder::cancel() {
461 media_status_t status = requestStop(false /* stopOnSync */);
462 if (status != AMEDIA_OK) {
463 return status;
464 }
465
466 waitForThreads();
467
468 // TODO: Release transcoders?
469 return AMEDIA_OK;
470 }
471
resume()472 media_status_t MediaTranscoder::resume() {
473 // TODO: restore internal states from parcel.
474 return start();
475 }
476
477 } // namespace android
478