blob: 428d86ea2e78b7728ccca36948767cd1597c02ef [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
59//static
60const char* TranscoderWrapper::toString(Event::Type type) {
61 switch (type) {
62 case Event::Start:
63 return "Start";
64 case Event::Pause:
65 return "Pause";
66 case Event::Resume:
67 return "Resume";
68 case Event::Stop:
69 return "Stop";
70 case Event::Finish:
71 return "Finish";
72 case Event::Error:
73 return "Error";
74 default:
75 break;
76 }
77 return "(unknown)";
78}
79
80class TranscoderWrapper::CallbackImpl : public MediaTranscoder::CallbackInterface {
81public:
82 CallbackImpl(const std::shared_ptr<TranscoderWrapper>& owner, ClientIdType clientId,
83 JobIdType jobId)
84 : mOwner(owner), mClientId(clientId), mJobId(jobId) {}
85
86 virtual void onFinished(const MediaTranscoder* transcoder __unused) override {
87 auto owner = mOwner.lock();
88 if (owner != nullptr) {
89 owner->onFinish(mClientId, mJobId);
90 }
91 }
92
93 virtual void onError(const MediaTranscoder* transcoder __unused,
94 media_status_t error) override {
95 auto owner = mOwner.lock();
96 if (owner != nullptr) {
97 owner->onError(mClientId, mJobId, toTranscodingError(error));
98 }
99 }
100
101 virtual void onProgressUpdate(const MediaTranscoder* transcoder __unused,
102 int32_t progress) override {
103 ALOGV("%s: job {%lld, %d}, progress %d", __FUNCTION__, (long long)mClientId, mJobId,
104 progress);
105 }
106
107 virtual void onCodecResourceLost(const MediaTranscoder* transcoder __unused,
108 const std::shared_ptr<const Parcelable>& pausedState
109 __unused) override {
110 ALOGV("%s: job {%lld, %d}", __FUNCTION__, (long long)mClientId, mJobId);
111 }
112
113private:
114 std::weak_ptr<TranscoderWrapper> mOwner;
115 ClientIdType mClientId;
116 JobIdType mJobId;
117};
118
119TranscoderWrapper::TranscoderWrapper() : mCurrentClientId(0), mCurrentJobId(-1) {
120 std::thread(&TranscoderWrapper::threadLoop, this).detach();
121}
122
123void TranscoderWrapper::setCallback(const std::shared_ptr<TranscoderCallbackInterface>& cb) {
124 mCallback = cb;
125}
126
127void TranscoderWrapper::start(ClientIdType clientId, JobIdType jobId,
128 const TranscodingRequestParcel& request,
129 const std::shared_ptr<ITranscodingClientCallback>& callback) {
130 queueEvent(Event::Start, clientId, jobId, [=] {
131 TranscodingErrorCode err = handleStart(clientId, jobId, request, callback);
132
133 if (err != TranscodingErrorCode::kNoError) {
134 cleanup();
135
136 auto callback = mCallback.lock();
137 if (callback != nullptr) {
138 callback->onError(clientId, jobId, err);
139 }
140 }
141 });
142}
143
144void TranscoderWrapper::pause(ClientIdType clientId, JobIdType jobId) {
145 queueEvent(Event::Pause, clientId, jobId, [] {});
146}
147
148void TranscoderWrapper::resume(ClientIdType clientId, JobIdType jobId) {
149 queueEvent(Event::Resume, clientId, jobId, [] {});
150}
151
152void TranscoderWrapper::stop(ClientIdType clientId, JobIdType jobId) {
153 queueEvent(Event::Stop, clientId, jobId, [=] {
154 if (clientId != mCurrentClientId || jobId != mCurrentJobId) {
155 ALOGW("Stopping job {%lld, %d} that's not current job {%lld, %d}", (long long)clientId,
156 jobId, (long long)mCurrentClientId, mCurrentJobId);
157 }
158
159 // stop transcoder.
160 media_status_t err = mTranscoder->cancel();
161 if (err != AMEDIA_OK) {
162 ALOGE("failed to stop transcoder: %d", err);
163 } else {
164 ALOGI("transcoder stopped");
165 }
166
167 cleanup();
168 });
169}
170
171void TranscoderWrapper::onFinish(ClientIdType clientId, JobIdType jobId) {
172 queueEvent(Event::Finish, clientId, jobId, [=] {
173 cleanup();
174
175 auto callback = mCallback.lock();
176 if (callback != nullptr) {
177 callback->onFinish(clientId, jobId);
178 }
179 });
180}
181
182void TranscoderWrapper::onError(ClientIdType clientId, JobIdType jobId,
183 TranscodingErrorCode error) {
184 queueEvent(Event::Error, clientId, jobId, [=] {
185 cleanup();
186
187 auto callback = mCallback.lock();
188 if (callback != nullptr) {
189 callback->onError(clientId, jobId, error);
190 }
191 });
192}
193
194static AMediaFormat* getVideoFormat(
195 const char* originalMime,
196 const std::optional<TranscodingVideoTrackFormat>& requestedFormat) {
197 if (requestedFormat == std::nullopt) {
198 return nullptr;
199 }
200
201 AMediaFormat* format = AMediaFormat_new();
202 bool changed = false;
Chong Zhang5855ee52020-06-22 11:41:25 -0700203 if (requestedFormat->codecType == TranscodingVideoCodecType::kHevc &&
204 strcmp(originalMime, AMEDIA_MIMETYPE_VIDEO_HEVC)) {
Chong Zhang66469272020-06-04 16:51:55 -0700205 AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_HEVC);
206 changed = true;
Chong Zhang5855ee52020-06-22 11:41:25 -0700207 } else if (requestedFormat->codecType == TranscodingVideoCodecType::kAvc &&
208 strcmp(originalMime, AMEDIA_MIMETYPE_VIDEO_AVC)) {
Chong Zhang66469272020-06-04 16:51:55 -0700209 AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_AVC);
210 changed = true;
211 }
212 if (requestedFormat->bitrateBps > 0) {
213 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, requestedFormat->bitrateBps);
214 changed = true;
215 }
216 // TODO: translate other fields from requestedFormat to the format for MediaTranscoder.
217 // Also need to determine more settings to expose in TranscodingVideoTrackFormat.
218 if (!changed) {
219 AMediaFormat_delete(format);
220 // Use null format for passthru.
221 format = nullptr;
222 }
223 return format;
224}
225
226TranscodingErrorCode TranscoderWrapper::handleStart(
227 ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
228 const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
229 if (clientCb == nullptr) {
230 ALOGE("client callback is null");
231 return TranscodingErrorCode::kInvalidParameter;
232 }
233
234 if (mTranscoder != nullptr) {
235 ALOGE("transcoder already running");
236 return TranscodingErrorCode::kInvalidOperation;
237 }
238
239 Status status;
240 ::ndk::ScopedFileDescriptor srcFd, dstFd;
241 status = clientCb->openFileDescriptor(request.sourceFilePath, "r", &srcFd);
242 if (!status.isOk() || srcFd.get() < 0) {
243 ALOGE("failed to open source");
244 return TranscodingErrorCode::kErrorIO;
245 }
246
247 status = clientCb->openFileDescriptor(request.destinationFilePath, "w", &dstFd);
248 if (!status.isOk() || dstFd.get() < 0) {
249 ALOGE("failed to open destination");
250 return TranscodingErrorCode::kErrorIO;
251 }
252
253 mCurrentClientId = clientId;
254 mCurrentJobId = jobId;
255 mTranscoderCb = std::make_shared<CallbackImpl>(shared_from_this(), clientId, jobId);
256 mTranscoder = MediaTranscoder::create(mTranscoderCb, nullptr);
257 if (mTranscoder == nullptr) {
258 ALOGE("failed to create transcoder");
259 return TranscodingErrorCode::kUnknown;
260 }
261
262 media_status_t err = mTranscoder->configureSource(srcFd.get());
263 if (err != AMEDIA_OK) {
264 ALOGE("failed to configure source: %d", err);
265 return toTranscodingError(err);
266 }
267
268 std::vector<std::shared_ptr<AMediaFormat>> trackFormats = mTranscoder->getTrackFormats();
269 if (trackFormats.size() == 0) {
270 ALOGE("failed to get track formats!");
271 return TranscodingErrorCode::kMalformed;
272 }
273
274 for (int i = 0; i < trackFormats.size(); ++i) {
275 AMediaFormat* format = nullptr;
276 const char* mime = nullptr;
277 AMediaFormat_getString(trackFormats[i].get(), AMEDIAFORMAT_KEY_MIME, &mime);
278
279 if (!strncmp(mime, "video/", 6)) {
280 format = getVideoFormat(mime, request.requestedVideoTrackFormat);
281 }
282
283 err = mTranscoder->configureTrackFormat(i, format);
284 if (format != nullptr) {
285 AMediaFormat_delete(format);
286 }
287 if (err != AMEDIA_OK) {
288 ALOGE("failed to configure track format for track %d: %d", i, err);
289 return toTranscodingError(err);
290 }
291 }
292
293 err = mTranscoder->configureDestination(dstFd.get());
294 if (err != AMEDIA_OK) {
295 ALOGE("failed to configure dest: %d", err);
296 return toTranscodingError(err);
297 }
298
299 err = mTranscoder->start();
300 if (err != AMEDIA_OK) {
301 ALOGE("failed to start transcoder: %d", err);
302 return toTranscodingError(err);
303 }
304
305 ALOGI("transcoder started");
306 return TranscodingErrorCode::kNoError;
307}
308
309void TranscoderWrapper::cleanup() {
310 mCurrentClientId = 0;
311 mCurrentJobId = -1;
312 mTranscoderCb = nullptr;
313 mTranscoder = nullptr;
314}
315
316void TranscoderWrapper::queueEvent(Event::Type type, ClientIdType clientId, JobIdType jobId,
317 const std::function<void()> runnable) {
318 ALOGV("%s: job {%lld, %d}: %s", __FUNCTION__, (long long)clientId, jobId, toString(type));
319
320 std::scoped_lock lock{mLock};
321
322 mQueue.push_back({type, clientId, jobId, runnable});
323 mCondition.notify_one();
324}
325
326void TranscoderWrapper::threadLoop() {
327 std::unique_lock<std::mutex> lock{mLock};
328 // TranscoderWrapper currently lives in the transcoding service, as long as
329 // MediaTranscodingService itself.
330 while (true) {
331 // Wait for the next event.
332 while (mQueue.empty()) {
333 mCondition.wait(lock);
334 }
335
336 Event event = *mQueue.begin();
337 mQueue.pop_front();
338
339 ALOGD("%s: job {%lld, %d}: %s", __FUNCTION__, (long long)event.clientId, event.jobId,
340 toString(event.type));
341
342 event.runnable();
343 }
344}
345
346} // namespace android