blob: bd03671c8a4d1ecc5393093d0a8beed70df2ce87 [file] [log] [blame]
Chong Zhang66469272020-06-04 16:51:55 -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 "TranscoderWrapper"
19
20#include <aidl/android/media/TranscodingErrorCode.h>
21#include <aidl/android/media/TranscodingRequestParcel.h>
22#include <media/MediaTranscoder.h>
23#include <media/NdkCommon.h>
24#include <media/TranscoderWrapper.h>
25#include <utils/Log.h>
26
27#include <thread>
28
29namespace android {
30using Status = ::ndk::ScopedAStatus;
31using ::aidl::android::media::TranscodingErrorCode;
32using ::aidl::android::media::TranscodingVideoCodecType;
33using ::aidl::android::media::TranscodingVideoTrackFormat;
34
35static TranscodingErrorCode toTranscodingError(media_status_t status) {
36 switch (status) {
37 case AMEDIA_OK:
38 return TranscodingErrorCode::kNoError;
39 case AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE: // FALLTHRU
40 case AMEDIACODEC_ERROR_RECLAIMED:
41 return TranscodingErrorCode::kInsufficientResources;
42 case AMEDIA_ERROR_MALFORMED:
43 return TranscodingErrorCode::kMalformed;
44 case AMEDIA_ERROR_UNSUPPORTED:
45 return TranscodingErrorCode::kUnsupported;
46 case AMEDIA_ERROR_INVALID_OBJECT: // FALLTHRU
47 case AMEDIA_ERROR_INVALID_PARAMETER:
48 return TranscodingErrorCode::kInvalidParameter;
49 case AMEDIA_ERROR_INVALID_OPERATION:
50 return TranscodingErrorCode::kInvalidOperation;
51 case AMEDIA_ERROR_IO:
52 return TranscodingErrorCode::kErrorIO;
53 case AMEDIA_ERROR_UNKNOWN: // FALLTHRU
54 default:
55 return TranscodingErrorCode::kUnknown;
56 }
57}
58
Chong Zhangb55c5452020-06-26 14:32:12 -070059static AMediaFormat* getVideoFormat(
60 const char* originalMime,
61 const std::optional<TranscodingVideoTrackFormat>& requestedFormat) {
62 if (requestedFormat == std::nullopt) {
63 return nullptr;
64 }
65
66 AMediaFormat* format = AMediaFormat_new();
67 bool changed = false;
68 if (requestedFormat->codecType == TranscodingVideoCodecType::kHevc &&
69 strcmp(originalMime, AMEDIA_MIMETYPE_VIDEO_HEVC)) {
70 AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_HEVC);
71 changed = true;
72 } else if (requestedFormat->codecType == TranscodingVideoCodecType::kAvc &&
73 strcmp(originalMime, AMEDIA_MIMETYPE_VIDEO_AVC)) {
74 AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_AVC);
75 changed = true;
76 }
77 if (requestedFormat->bitrateBps > 0) {
78 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, requestedFormat->bitrateBps);
79 changed = true;
80 }
81 // TODO: translate other fields from requestedFormat to the format for MediaTranscoder.
82 // Also need to determine more settings to expose in TranscodingVideoTrackFormat.
83 if (!changed) {
84 AMediaFormat_delete(format);
85 // Use null format for passthru.
86 format = nullptr;
87 }
88 return format;
89}
90
Chong Zhang66469272020-06-04 16:51:55 -070091//static
92const char* TranscoderWrapper::toString(Event::Type type) {
93 switch (type) {
94 case Event::Start:
95 return "Start";
96 case Event::Pause:
97 return "Pause";
98 case Event::Resume:
99 return "Resume";
100 case Event::Stop:
101 return "Stop";
102 case Event::Finish:
103 return "Finish";
104 case Event::Error:
105 return "Error";
Chong Zhang98b8a372020-07-08 17:27:37 -0700106 case Event::Progress:
107 return "Progress";
Chong Zhang66469272020-06-04 16:51:55 -0700108 default:
109 break;
110 }
111 return "(unknown)";
112}
113
114class TranscoderWrapper::CallbackImpl : public MediaTranscoder::CallbackInterface {
115public:
116 CallbackImpl(const std::shared_ptr<TranscoderWrapper>& owner, ClientIdType clientId,
117 JobIdType jobId)
118 : mOwner(owner), mClientId(clientId), mJobId(jobId) {}
119
120 virtual void onFinished(const MediaTranscoder* transcoder __unused) override {
121 auto owner = mOwner.lock();
122 if (owner != nullptr) {
123 owner->onFinish(mClientId, mJobId);
124 }
125 }
126
127 virtual void onError(const MediaTranscoder* transcoder __unused,
128 media_status_t error) override {
129 auto owner = mOwner.lock();
130 if (owner != nullptr) {
131 owner->onError(mClientId, mJobId, toTranscodingError(error));
132 }
133 }
134
135 virtual void onProgressUpdate(const MediaTranscoder* transcoder __unused,
136 int32_t progress) override {
Chong Zhang98b8a372020-07-08 17:27:37 -0700137 auto owner = mOwner.lock();
138 if (owner != nullptr) {
139 owner->onProgress(mClientId, mJobId, progress);
140 }
Chong Zhang66469272020-06-04 16:51:55 -0700141 }
142
143 virtual void onCodecResourceLost(const MediaTranscoder* transcoder __unused,
Chong Zhangb55c5452020-06-26 14:32:12 -0700144 const std::shared_ptr<const Parcel>& pausedState
Chong Zhang66469272020-06-04 16:51:55 -0700145 __unused) override {
146 ALOGV("%s: job {%lld, %d}", __FUNCTION__, (long long)mClientId, mJobId);
147 }
148
149private:
150 std::weak_ptr<TranscoderWrapper> mOwner;
151 ClientIdType mClientId;
152 JobIdType mJobId;
153};
154
155TranscoderWrapper::TranscoderWrapper() : mCurrentClientId(0), mCurrentJobId(-1) {
156 std::thread(&TranscoderWrapper::threadLoop, this).detach();
157}
158
159void TranscoderWrapper::setCallback(const std::shared_ptr<TranscoderCallbackInterface>& cb) {
160 mCallback = cb;
161}
162
163void TranscoderWrapper::start(ClientIdType clientId, JobIdType jobId,
164 const TranscodingRequestParcel& request,
Chong Zhangb55c5452020-06-26 14:32:12 -0700165 const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
Chong Zhang66469272020-06-04 16:51:55 -0700166 queueEvent(Event::Start, clientId, jobId, [=] {
Chong Zhangb55c5452020-06-26 14:32:12 -0700167 TranscodingErrorCode err = handleStart(clientId, jobId, request, clientCb);
Chong Zhang66469272020-06-04 16:51:55 -0700168
Chong Zhangb55c5452020-06-26 14:32:12 -0700169 auto callback = mCallback.lock();
Chong Zhang66469272020-06-04 16:51:55 -0700170 if (err != TranscodingErrorCode::kNoError) {
171 cleanup();
172
Chong Zhang66469272020-06-04 16:51:55 -0700173 if (callback != nullptr) {
174 callback->onError(clientId, jobId, err);
175 }
Chong Zhangb55c5452020-06-26 14:32:12 -0700176 } else {
177 if (callback != nullptr) {
178 callback->onStarted(clientId, jobId);
179 }
Chong Zhang66469272020-06-04 16:51:55 -0700180 }
181 });
182}
183
184void TranscoderWrapper::pause(ClientIdType clientId, JobIdType jobId) {
Chong Zhangb55c5452020-06-26 14:32:12 -0700185 queueEvent(Event::Pause, clientId, jobId, [=] {
186 TranscodingErrorCode err = handlePause(clientId, jobId);
187
188 cleanup();
189
190 auto callback = mCallback.lock();
191 if (callback != nullptr) {
192 if (err != TranscodingErrorCode::kNoError) {
193 callback->onError(clientId, jobId, err);
194 } else {
195 callback->onPaused(clientId, jobId);
196 }
197 }
198 });
Chong Zhang66469272020-06-04 16:51:55 -0700199}
200
Chong Zhangb55c5452020-06-26 14:32:12 -0700201void TranscoderWrapper::resume(ClientIdType clientId, JobIdType jobId,
202 const TranscodingRequestParcel& request,
203 const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
204 queueEvent(Event::Resume, clientId, jobId, [=] {
205 TranscodingErrorCode err = handleResume(clientId, jobId, request, clientCb);
206
207 auto callback = mCallback.lock();
208 if (err != TranscodingErrorCode::kNoError) {
209 cleanup();
210
211 if (callback != nullptr) {
212 callback->onError(clientId, jobId, err);
213 }
214 } else {
215 if (callback != nullptr) {
216 callback->onResumed(clientId, jobId);
217 }
218 }
219 });
Chong Zhang66469272020-06-04 16:51:55 -0700220}
221
222void TranscoderWrapper::stop(ClientIdType clientId, JobIdType jobId) {
223 queueEvent(Event::Stop, clientId, jobId, [=] {
Chong Zhangb55c5452020-06-26 14:32:12 -0700224 if (mTranscoder != nullptr && clientId == mCurrentClientId && jobId == mCurrentJobId) {
225 // Cancelling the currently running job.
226 media_status_t err = mTranscoder->cancel();
227 if (err != AMEDIA_OK) {
228 ALOGE("failed to stop transcoder: %d", err);
229 } else {
230 ALOGI("transcoder stopped");
231 }
232 cleanup();
Chong Zhang66469272020-06-04 16:51:55 -0700233 } else {
Chong Zhangb55c5452020-06-26 14:32:12 -0700234 // For jobs that's not currently running, release any pausedState for the job.
235 mPausedStateMap.erase(JobKeyType(clientId, jobId));
Chong Zhang66469272020-06-04 16:51:55 -0700236 }
Chong Zhangb55c5452020-06-26 14:32:12 -0700237 // No callback needed for stop.
Chong Zhang66469272020-06-04 16:51:55 -0700238 });
239}
240
241void TranscoderWrapper::onFinish(ClientIdType clientId, JobIdType jobId) {
242 queueEvent(Event::Finish, clientId, jobId, [=] {
Chong Zhangb55c5452020-06-26 14:32:12 -0700243 if (mTranscoder != nullptr && clientId == mCurrentClientId && jobId == mCurrentJobId) {
244 cleanup();
245 }
Chong Zhang66469272020-06-04 16:51:55 -0700246
247 auto callback = mCallback.lock();
248 if (callback != nullptr) {
249 callback->onFinish(clientId, jobId);
250 }
251 });
252}
253
254void TranscoderWrapper::onError(ClientIdType clientId, JobIdType jobId,
255 TranscodingErrorCode error) {
256 queueEvent(Event::Error, clientId, jobId, [=] {
Chong Zhangb55c5452020-06-26 14:32:12 -0700257 if (mTranscoder != nullptr && clientId == mCurrentClientId && jobId == mCurrentJobId) {
258 cleanup();
259 }
Chong Zhang66469272020-06-04 16:51:55 -0700260
261 auto callback = mCallback.lock();
262 if (callback != nullptr) {
263 callback->onError(clientId, jobId, error);
264 }
265 });
266}
267
Chong Zhang98b8a372020-07-08 17:27:37 -0700268void TranscoderWrapper::onProgress(ClientIdType clientId, JobIdType jobId, int32_t progress) {
269 queueEvent(Event::Progress, clientId, jobId, [=] {
270 auto callback = mCallback.lock();
271 if (callback != nullptr) {
272 callback->onProgressUpdate(clientId, jobId, progress);
273 }
274 });
275}
276
Chong Zhangb55c5452020-06-26 14:32:12 -0700277TranscodingErrorCode TranscoderWrapper::setupTranscoder(
Chong Zhang66469272020-06-04 16:51:55 -0700278 ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
Chong Zhangb55c5452020-06-26 14:32:12 -0700279 const std::shared_ptr<ITranscodingClientCallback>& clientCb,
280 const std::shared_ptr<const Parcel>& pausedState) {
Chong Zhang66469272020-06-04 16:51:55 -0700281 if (clientCb == nullptr) {
282 ALOGE("client callback is null");
283 return TranscodingErrorCode::kInvalidParameter;
284 }
285
286 if (mTranscoder != nullptr) {
287 ALOGE("transcoder already running");
288 return TranscodingErrorCode::kInvalidOperation;
289 }
290
291 Status status;
292 ::ndk::ScopedFileDescriptor srcFd, dstFd;
293 status = clientCb->openFileDescriptor(request.sourceFilePath, "r", &srcFd);
294 if (!status.isOk() || srcFd.get() < 0) {
295 ALOGE("failed to open source");
296 return TranscodingErrorCode::kErrorIO;
297 }
298
Chong Zhangb55c5452020-06-26 14:32:12 -0700299 // Open dest file with "rw", as the transcoder could potentially reuse part of it
300 // for resume case. We might want the further differentiate and open with "w" only
301 // for start.
302 status = clientCb->openFileDescriptor(request.destinationFilePath, "rw", &dstFd);
Chong Zhang66469272020-06-04 16:51:55 -0700303 if (!status.isOk() || dstFd.get() < 0) {
304 ALOGE("failed to open destination");
305 return TranscodingErrorCode::kErrorIO;
306 }
307
308 mCurrentClientId = clientId;
309 mCurrentJobId = jobId;
310 mTranscoderCb = std::make_shared<CallbackImpl>(shared_from_this(), clientId, jobId);
Chong Zhangb55c5452020-06-26 14:32:12 -0700311 mTranscoder = MediaTranscoder::create(mTranscoderCb, pausedState);
Chong Zhang66469272020-06-04 16:51:55 -0700312 if (mTranscoder == nullptr) {
313 ALOGE("failed to create transcoder");
314 return TranscodingErrorCode::kUnknown;
315 }
316
317 media_status_t err = mTranscoder->configureSource(srcFd.get());
318 if (err != AMEDIA_OK) {
319 ALOGE("failed to configure source: %d", err);
320 return toTranscodingError(err);
321 }
322
323 std::vector<std::shared_ptr<AMediaFormat>> trackFormats = mTranscoder->getTrackFormats();
324 if (trackFormats.size() == 0) {
325 ALOGE("failed to get track formats!");
326 return TranscodingErrorCode::kMalformed;
327 }
328
329 for (int i = 0; i < trackFormats.size(); ++i) {
330 AMediaFormat* format = nullptr;
331 const char* mime = nullptr;
332 AMediaFormat_getString(trackFormats[i].get(), AMEDIAFORMAT_KEY_MIME, &mime);
333
334 if (!strncmp(mime, "video/", 6)) {
335 format = getVideoFormat(mime, request.requestedVideoTrackFormat);
336 }
337
338 err = mTranscoder->configureTrackFormat(i, format);
339 if (format != nullptr) {
340 AMediaFormat_delete(format);
341 }
342 if (err != AMEDIA_OK) {
343 ALOGE("failed to configure track format for track %d: %d", i, err);
344 return toTranscodingError(err);
345 }
346 }
347
348 err = mTranscoder->configureDestination(dstFd.get());
349 if (err != AMEDIA_OK) {
350 ALOGE("failed to configure dest: %d", err);
351 return toTranscodingError(err);
352 }
353
Chong Zhangb55c5452020-06-26 14:32:12 -0700354 return TranscodingErrorCode::kNoError;
355}
356
357TranscodingErrorCode TranscoderWrapper::handleStart(
358 ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
359 const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
360 ALOGI("setting up transcoder for start");
361 TranscodingErrorCode err = setupTranscoder(clientId, jobId, request, clientCb);
362 if (err != TranscodingErrorCode::kNoError) {
363 ALOGI("%s: failed to setup transcoder", __FUNCTION__);
364 return err;
Chong Zhang66469272020-06-04 16:51:55 -0700365 }
366
Chong Zhangb55c5452020-06-26 14:32:12 -0700367 media_status_t status = mTranscoder->start();
368 if (status != AMEDIA_OK) {
369 ALOGE("%s: failed to start transcoder: %d", __FUNCTION__, err);
370 return toTranscodingError(status);
371 }
372
373 ALOGI("%s: transcoder started", __FUNCTION__);
374 return TranscodingErrorCode::kNoError;
375}
376
377TranscodingErrorCode TranscoderWrapper::handlePause(ClientIdType clientId, JobIdType jobId) {
378 if (mTranscoder == nullptr) {
379 ALOGE("%s: transcoder is not running", __FUNCTION__);
380 return TranscodingErrorCode::kInvalidOperation;
381 }
382
383 if (clientId != mCurrentClientId || jobId != mCurrentJobId) {
384 ALOGW("%s: stopping job {%lld, %d} that's not current job {%lld, %d}", __FUNCTION__,
385 (long long)clientId, jobId, (long long)mCurrentClientId, mCurrentJobId);
386 }
387
388 std::shared_ptr<const Parcel> pauseStates;
389 media_status_t err = mTranscoder->pause(&pauseStates);
390 if (err != AMEDIA_OK) {
391 ALOGE("%s: failed to pause transcoder: %d", __FUNCTION__, err);
392 return toTranscodingError(err);
393 }
394 mPausedStateMap[JobKeyType(clientId, jobId)] = pauseStates;
395
396 ALOGI("%s: transcoder paused", __FUNCTION__);
397 return TranscodingErrorCode::kNoError;
398}
399
400TranscodingErrorCode TranscoderWrapper::handleResume(
401 ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
402 const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
403 std::shared_ptr<const Parcel> pausedState;
404 auto it = mPausedStateMap.find(JobKeyType(clientId, jobId));
405 if (it != mPausedStateMap.end()) {
406 pausedState = it->second;
407 mPausedStateMap.erase(it);
408 } else {
409 ALOGE("%s: can't find paused state", __FUNCTION__);
410 return TranscodingErrorCode::kInvalidOperation;
411 }
412
413 ALOGI("setting up transcoder for resume");
414 TranscodingErrorCode err = setupTranscoder(clientId, jobId, request, clientCb, pausedState);
415 if (err != TranscodingErrorCode::kNoError) {
416 ALOGE("%s: failed to setup transcoder", __FUNCTION__);
417 return err;
418 }
419
420 media_status_t status = mTranscoder->resume();
421 if (status != AMEDIA_OK) {
422 ALOGE("%s: failed to resume transcoder: %d", __FUNCTION__, err);
423 return toTranscodingError(status);
424 }
425
426 ALOGI("%s: transcoder resumed", __FUNCTION__);
Chong Zhang66469272020-06-04 16:51:55 -0700427 return TranscodingErrorCode::kNoError;
428}
429
430void TranscoderWrapper::cleanup() {
431 mCurrentClientId = 0;
432 mCurrentJobId = -1;
433 mTranscoderCb = nullptr;
434 mTranscoder = nullptr;
435}
436
437void TranscoderWrapper::queueEvent(Event::Type type, ClientIdType clientId, JobIdType jobId,
438 const std::function<void()> runnable) {
439 ALOGV("%s: job {%lld, %d}: %s", __FUNCTION__, (long long)clientId, jobId, toString(type));
440
441 std::scoped_lock lock{mLock};
442
443 mQueue.push_back({type, clientId, jobId, runnable});
444 mCondition.notify_one();
445}
446
447void TranscoderWrapper::threadLoop() {
448 std::unique_lock<std::mutex> lock{mLock};
449 // TranscoderWrapper currently lives in the transcoding service, as long as
450 // MediaTranscodingService itself.
451 while (true) {
452 // Wait for the next event.
453 while (mQueue.empty()) {
454 mCondition.wait(lock);
455 }
456
457 Event event = *mQueue.begin();
458 mQueue.pop_front();
459
460 ALOGD("%s: job {%lld, %d}: %s", __FUNCTION__, (long long)event.clientId, event.jobId,
461 toString(event.type));
462
Chong Zhangb55c5452020-06-26 14:32:12 -0700463 lock.unlock();
Chong Zhang66469272020-06-04 16:51:55 -0700464 event.runnable();
Chong Zhangb55c5452020-06-26 14:32:12 -0700465 lock.lock();
Chong Zhang66469272020-06-04 16:51:55 -0700466 }
467}
468
469} // namespace android