blob: aaa15c44740de2400e0fb4f9057dfa2f028873db [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";
106 default:
107 break;
108 }
109 return "(unknown)";
110}
111
112class TranscoderWrapper::CallbackImpl : public MediaTranscoder::CallbackInterface {
113public:
114 CallbackImpl(const std::shared_ptr<TranscoderWrapper>& owner, ClientIdType clientId,
115 JobIdType jobId)
116 : mOwner(owner), mClientId(clientId), mJobId(jobId) {}
117
118 virtual void onFinished(const MediaTranscoder* transcoder __unused) override {
119 auto owner = mOwner.lock();
120 if (owner != nullptr) {
121 owner->onFinish(mClientId, mJobId);
122 }
123 }
124
125 virtual void onError(const MediaTranscoder* transcoder __unused,
126 media_status_t error) override {
127 auto owner = mOwner.lock();
128 if (owner != nullptr) {
129 owner->onError(mClientId, mJobId, toTranscodingError(error));
130 }
131 }
132
133 virtual void onProgressUpdate(const MediaTranscoder* transcoder __unused,
134 int32_t progress) override {
135 ALOGV("%s: job {%lld, %d}, progress %d", __FUNCTION__, (long long)mClientId, mJobId,
136 progress);
137 }
138
139 virtual void onCodecResourceLost(const MediaTranscoder* transcoder __unused,
Chong Zhangb55c5452020-06-26 14:32:12 -0700140 const std::shared_ptr<const Parcel>& pausedState
Chong Zhang66469272020-06-04 16:51:55 -0700141 __unused) override {
142 ALOGV("%s: job {%lld, %d}", __FUNCTION__, (long long)mClientId, mJobId);
143 }
144
145private:
146 std::weak_ptr<TranscoderWrapper> mOwner;
147 ClientIdType mClientId;
148 JobIdType mJobId;
149};
150
151TranscoderWrapper::TranscoderWrapper() : mCurrentClientId(0), mCurrentJobId(-1) {
152 std::thread(&TranscoderWrapper::threadLoop, this).detach();
153}
154
155void TranscoderWrapper::setCallback(const std::shared_ptr<TranscoderCallbackInterface>& cb) {
156 mCallback = cb;
157}
158
159void TranscoderWrapper::start(ClientIdType clientId, JobIdType jobId,
160 const TranscodingRequestParcel& request,
Chong Zhangb55c5452020-06-26 14:32:12 -0700161 const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
Chong Zhang66469272020-06-04 16:51:55 -0700162 queueEvent(Event::Start, clientId, jobId, [=] {
Chong Zhangb55c5452020-06-26 14:32:12 -0700163 TranscodingErrorCode err = handleStart(clientId, jobId, request, clientCb);
Chong Zhang66469272020-06-04 16:51:55 -0700164
Chong Zhangb55c5452020-06-26 14:32:12 -0700165 auto callback = mCallback.lock();
Chong Zhang66469272020-06-04 16:51:55 -0700166 if (err != TranscodingErrorCode::kNoError) {
167 cleanup();
168
Chong Zhang66469272020-06-04 16:51:55 -0700169 if (callback != nullptr) {
170 callback->onError(clientId, jobId, err);
171 }
Chong Zhangb55c5452020-06-26 14:32:12 -0700172 } else {
173 if (callback != nullptr) {
174 callback->onStarted(clientId, jobId);
175 }
Chong Zhang66469272020-06-04 16:51:55 -0700176 }
177 });
178}
179
180void TranscoderWrapper::pause(ClientIdType clientId, JobIdType jobId) {
Chong Zhangb55c5452020-06-26 14:32:12 -0700181 queueEvent(Event::Pause, clientId, jobId, [=] {
182 TranscodingErrorCode err = handlePause(clientId, jobId);
183
184 cleanup();
185
186 auto callback = mCallback.lock();
187 if (callback != nullptr) {
188 if (err != TranscodingErrorCode::kNoError) {
189 callback->onError(clientId, jobId, err);
190 } else {
191 callback->onPaused(clientId, jobId);
192 }
193 }
194 });
Chong Zhang66469272020-06-04 16:51:55 -0700195}
196
Chong Zhangb55c5452020-06-26 14:32:12 -0700197void TranscoderWrapper::resume(ClientIdType clientId, JobIdType jobId,
198 const TranscodingRequestParcel& request,
199 const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
200 queueEvent(Event::Resume, clientId, jobId, [=] {
201 TranscodingErrorCode err = handleResume(clientId, jobId, request, clientCb);
202
203 auto callback = mCallback.lock();
204 if (err != TranscodingErrorCode::kNoError) {
205 cleanup();
206
207 if (callback != nullptr) {
208 callback->onError(clientId, jobId, err);
209 }
210 } else {
211 if (callback != nullptr) {
212 callback->onResumed(clientId, jobId);
213 }
214 }
215 });
Chong Zhang66469272020-06-04 16:51:55 -0700216}
217
218void TranscoderWrapper::stop(ClientIdType clientId, JobIdType jobId) {
219 queueEvent(Event::Stop, clientId, jobId, [=] {
Chong Zhangb55c5452020-06-26 14:32:12 -0700220 if (mTranscoder != nullptr && clientId == mCurrentClientId && jobId == mCurrentJobId) {
221 // Cancelling the currently running job.
222 media_status_t err = mTranscoder->cancel();
223 if (err != AMEDIA_OK) {
224 ALOGE("failed to stop transcoder: %d", err);
225 } else {
226 ALOGI("transcoder stopped");
227 }
228 cleanup();
Chong Zhang66469272020-06-04 16:51:55 -0700229 } else {
Chong Zhangb55c5452020-06-26 14:32:12 -0700230 // For jobs that's not currently running, release any pausedState for the job.
231 mPausedStateMap.erase(JobKeyType(clientId, jobId));
Chong Zhang66469272020-06-04 16:51:55 -0700232 }
Chong Zhangb55c5452020-06-26 14:32:12 -0700233 // No callback needed for stop.
Chong Zhang66469272020-06-04 16:51:55 -0700234 });
235}
236
237void TranscoderWrapper::onFinish(ClientIdType clientId, JobIdType jobId) {
238 queueEvent(Event::Finish, clientId, jobId, [=] {
Chong Zhangb55c5452020-06-26 14:32:12 -0700239 if (mTranscoder != nullptr && clientId == mCurrentClientId && jobId == mCurrentJobId) {
240 cleanup();
241 }
Chong Zhang66469272020-06-04 16:51:55 -0700242
243 auto callback = mCallback.lock();
244 if (callback != nullptr) {
245 callback->onFinish(clientId, jobId);
246 }
247 });
248}
249
250void TranscoderWrapper::onError(ClientIdType clientId, JobIdType jobId,
251 TranscodingErrorCode error) {
252 queueEvent(Event::Error, clientId, jobId, [=] {
Chong Zhangb55c5452020-06-26 14:32:12 -0700253 if (mTranscoder != nullptr && clientId == mCurrentClientId && jobId == mCurrentJobId) {
254 cleanup();
255 }
Chong Zhang66469272020-06-04 16:51:55 -0700256
257 auto callback = mCallback.lock();
258 if (callback != nullptr) {
259 callback->onError(clientId, jobId, error);
260 }
261 });
262}
263
Chong Zhangb55c5452020-06-26 14:32:12 -0700264TranscodingErrorCode TranscoderWrapper::setupTranscoder(
Chong Zhang66469272020-06-04 16:51:55 -0700265 ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
Chong Zhangb55c5452020-06-26 14:32:12 -0700266 const std::shared_ptr<ITranscodingClientCallback>& clientCb,
267 const std::shared_ptr<const Parcel>& pausedState) {
Chong Zhang66469272020-06-04 16:51:55 -0700268 if (clientCb == nullptr) {
269 ALOGE("client callback is null");
270 return TranscodingErrorCode::kInvalidParameter;
271 }
272
273 if (mTranscoder != nullptr) {
274 ALOGE("transcoder already running");
275 return TranscodingErrorCode::kInvalidOperation;
276 }
277
278 Status status;
279 ::ndk::ScopedFileDescriptor srcFd, dstFd;
280 status = clientCb->openFileDescriptor(request.sourceFilePath, "r", &srcFd);
281 if (!status.isOk() || srcFd.get() < 0) {
282 ALOGE("failed to open source");
283 return TranscodingErrorCode::kErrorIO;
284 }
285
Chong Zhangb55c5452020-06-26 14:32:12 -0700286 // Open dest file with "rw", as the transcoder could potentially reuse part of it
287 // for resume case. We might want the further differentiate and open with "w" only
288 // for start.
289 status = clientCb->openFileDescriptor(request.destinationFilePath, "rw", &dstFd);
Chong Zhang66469272020-06-04 16:51:55 -0700290 if (!status.isOk() || dstFd.get() < 0) {
291 ALOGE("failed to open destination");
292 return TranscodingErrorCode::kErrorIO;
293 }
294
295 mCurrentClientId = clientId;
296 mCurrentJobId = jobId;
297 mTranscoderCb = std::make_shared<CallbackImpl>(shared_from_this(), clientId, jobId);
Chong Zhangb55c5452020-06-26 14:32:12 -0700298 mTranscoder = MediaTranscoder::create(mTranscoderCb, pausedState);
Chong Zhang66469272020-06-04 16:51:55 -0700299 if (mTranscoder == nullptr) {
300 ALOGE("failed to create transcoder");
301 return TranscodingErrorCode::kUnknown;
302 }
303
304 media_status_t err = mTranscoder->configureSource(srcFd.get());
305 if (err != AMEDIA_OK) {
306 ALOGE("failed to configure source: %d", err);
307 return toTranscodingError(err);
308 }
309
310 std::vector<std::shared_ptr<AMediaFormat>> trackFormats = mTranscoder->getTrackFormats();
311 if (trackFormats.size() == 0) {
312 ALOGE("failed to get track formats!");
313 return TranscodingErrorCode::kMalformed;
314 }
315
316 for (int i = 0; i < trackFormats.size(); ++i) {
317 AMediaFormat* format = nullptr;
318 const char* mime = nullptr;
319 AMediaFormat_getString(trackFormats[i].get(), AMEDIAFORMAT_KEY_MIME, &mime);
320
321 if (!strncmp(mime, "video/", 6)) {
322 format = getVideoFormat(mime, request.requestedVideoTrackFormat);
323 }
324
325 err = mTranscoder->configureTrackFormat(i, format);
326 if (format != nullptr) {
327 AMediaFormat_delete(format);
328 }
329 if (err != AMEDIA_OK) {
330 ALOGE("failed to configure track format for track %d: %d", i, err);
331 return toTranscodingError(err);
332 }
333 }
334
335 err = mTranscoder->configureDestination(dstFd.get());
336 if (err != AMEDIA_OK) {
337 ALOGE("failed to configure dest: %d", err);
338 return toTranscodingError(err);
339 }
340
Chong Zhangb55c5452020-06-26 14:32:12 -0700341 return TranscodingErrorCode::kNoError;
342}
343
344TranscodingErrorCode TranscoderWrapper::handleStart(
345 ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
346 const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
347 ALOGI("setting up transcoder for start");
348 TranscodingErrorCode err = setupTranscoder(clientId, jobId, request, clientCb);
349 if (err != TranscodingErrorCode::kNoError) {
350 ALOGI("%s: failed to setup transcoder", __FUNCTION__);
351 return err;
Chong Zhang66469272020-06-04 16:51:55 -0700352 }
353
Chong Zhangb55c5452020-06-26 14:32:12 -0700354 media_status_t status = mTranscoder->start();
355 if (status != AMEDIA_OK) {
356 ALOGE("%s: failed to start transcoder: %d", __FUNCTION__, err);
357 return toTranscodingError(status);
358 }
359
360 ALOGI("%s: transcoder started", __FUNCTION__);
361 return TranscodingErrorCode::kNoError;
362}
363
364TranscodingErrorCode TranscoderWrapper::handlePause(ClientIdType clientId, JobIdType jobId) {
365 if (mTranscoder == nullptr) {
366 ALOGE("%s: transcoder is not running", __FUNCTION__);
367 return TranscodingErrorCode::kInvalidOperation;
368 }
369
370 if (clientId != mCurrentClientId || jobId != mCurrentJobId) {
371 ALOGW("%s: stopping job {%lld, %d} that's not current job {%lld, %d}", __FUNCTION__,
372 (long long)clientId, jobId, (long long)mCurrentClientId, mCurrentJobId);
373 }
374
375 std::shared_ptr<const Parcel> pauseStates;
376 media_status_t err = mTranscoder->pause(&pauseStates);
377 if (err != AMEDIA_OK) {
378 ALOGE("%s: failed to pause transcoder: %d", __FUNCTION__, err);
379 return toTranscodingError(err);
380 }
381 mPausedStateMap[JobKeyType(clientId, jobId)] = pauseStates;
382
383 ALOGI("%s: transcoder paused", __FUNCTION__);
384 return TranscodingErrorCode::kNoError;
385}
386
387TranscodingErrorCode TranscoderWrapper::handleResume(
388 ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
389 const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
390 std::shared_ptr<const Parcel> pausedState;
391 auto it = mPausedStateMap.find(JobKeyType(clientId, jobId));
392 if (it != mPausedStateMap.end()) {
393 pausedState = it->second;
394 mPausedStateMap.erase(it);
395 } else {
396 ALOGE("%s: can't find paused state", __FUNCTION__);
397 return TranscodingErrorCode::kInvalidOperation;
398 }
399
400 ALOGI("setting up transcoder for resume");
401 TranscodingErrorCode err = setupTranscoder(clientId, jobId, request, clientCb, pausedState);
402 if (err != TranscodingErrorCode::kNoError) {
403 ALOGE("%s: failed to setup transcoder", __FUNCTION__);
404 return err;
405 }
406
407 media_status_t status = mTranscoder->resume();
408 if (status != AMEDIA_OK) {
409 ALOGE("%s: failed to resume transcoder: %d", __FUNCTION__, err);
410 return toTranscodingError(status);
411 }
412
413 ALOGI("%s: transcoder resumed", __FUNCTION__);
Chong Zhang66469272020-06-04 16:51:55 -0700414 return TranscodingErrorCode::kNoError;
415}
416
417void TranscoderWrapper::cleanup() {
418 mCurrentClientId = 0;
419 mCurrentJobId = -1;
420 mTranscoderCb = nullptr;
421 mTranscoder = nullptr;
422}
423
424void TranscoderWrapper::queueEvent(Event::Type type, ClientIdType clientId, JobIdType jobId,
425 const std::function<void()> runnable) {
426 ALOGV("%s: job {%lld, %d}: %s", __FUNCTION__, (long long)clientId, jobId, toString(type));
427
428 std::scoped_lock lock{mLock};
429
430 mQueue.push_back({type, clientId, jobId, runnable});
431 mCondition.notify_one();
432}
433
434void TranscoderWrapper::threadLoop() {
435 std::unique_lock<std::mutex> lock{mLock};
436 // TranscoderWrapper currently lives in the transcoding service, as long as
437 // MediaTranscodingService itself.
438 while (true) {
439 // Wait for the next event.
440 while (mQueue.empty()) {
441 mCondition.wait(lock);
442 }
443
444 Event event = *mQueue.begin();
445 mQueue.pop_front();
446
447 ALOGD("%s: job {%lld, %d}: %s", __FUNCTION__, (long long)event.clientId, event.jobId,
448 toString(event.type));
449
Chong Zhangb55c5452020-06-26 14:32:12 -0700450 lock.unlock();
Chong Zhang66469272020-06-04 16:51:55 -0700451 event.runnable();
Chong Zhangb55c5452020-06-26 14:32:12 -0700452 lock.lock();
Chong Zhang66469272020-06-04 16:51:55 -0700453 }
454}
455
456} // namespace android