blob: fffbfe90905b8a902936866d9c444dd40bde5e1c [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//#define LOG_NDEBUG 0
#define LOG_TAG "TranscoderWrapper"
#include <aidl/android/media/TranscodingErrorCode.h>
#include <aidl/android/media/TranscodingRequestParcel.h>
#include <media/MediaTranscoder.h>
#include <media/NdkCommon.h>
#include <media/TranscoderWrapper.h>
#include <utils/Log.h>
#include <thread>
namespace android {
using Status = ::ndk::ScopedAStatus;
using ::aidl::android::media::TranscodingErrorCode;
using ::aidl::android::media::TranscodingVideoCodecType;
using ::aidl::android::media::TranscodingVideoTrackFormat;
static TranscodingErrorCode toTranscodingError(media_status_t status) {
switch (status) {
case AMEDIA_OK:
return TranscodingErrorCode::kNoError;
case AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE: // FALLTHRU
case AMEDIACODEC_ERROR_RECLAIMED:
return TranscodingErrorCode::kInsufficientResources;
case AMEDIA_ERROR_MALFORMED:
return TranscodingErrorCode::kMalformed;
case AMEDIA_ERROR_UNSUPPORTED:
return TranscodingErrorCode::kUnsupported;
case AMEDIA_ERROR_INVALID_OBJECT: // FALLTHRU
case AMEDIA_ERROR_INVALID_PARAMETER:
return TranscodingErrorCode::kInvalidParameter;
case AMEDIA_ERROR_INVALID_OPERATION:
return TranscodingErrorCode::kInvalidOperation;
case AMEDIA_ERROR_IO:
return TranscodingErrorCode::kErrorIO;
case AMEDIA_ERROR_UNKNOWN: // FALLTHRU
default:
return TranscodingErrorCode::kUnknown;
}
}
static AMediaFormat* getVideoFormat(
const char* originalMime,
const std::optional<TranscodingVideoTrackFormat>& requestedFormat) {
if (requestedFormat == std::nullopt) {
return nullptr;
}
AMediaFormat* format = AMediaFormat_new();
bool changed = false;
if (requestedFormat->codecType == TranscodingVideoCodecType::kHevc &&
strcmp(originalMime, AMEDIA_MIMETYPE_VIDEO_HEVC)) {
AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_HEVC);
changed = true;
} else if (requestedFormat->codecType == TranscodingVideoCodecType::kAvc &&
strcmp(originalMime, AMEDIA_MIMETYPE_VIDEO_AVC)) {
AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_AVC);
changed = true;
}
if (requestedFormat->bitrateBps > 0) {
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, requestedFormat->bitrateBps);
changed = true;
}
// TODO: translate other fields from requestedFormat to the format for MediaTranscoder.
// Also need to determine more settings to expose in TranscodingVideoTrackFormat.
if (!changed) {
AMediaFormat_delete(format);
// Use null format for passthru.
format = nullptr;
}
return format;
}
//static
std::string TranscoderWrapper::toString(const Event& event) {
std::string typeStr;
switch (event.type) {
case Event::Start:
typeStr = "Start";
break;
case Event::Pause:
typeStr = "Pause";
break;
case Event::Resume:
typeStr = "Resume";
break;
case Event::Stop:
typeStr = "Stop";
break;
case Event::Finish:
typeStr = "Finish";
break;
case Event::Error:
typeStr = "Error";
break;
case Event::Progress:
typeStr = "Progress";
break;
default:
return "(unknown)";
}
std::string result;
result = "session {" + std::to_string(event.clientId) + "," + std::to_string(event.sessionId) +
"}: " + typeStr;
if (event.type == Event::Error || event.type == Event::Progress) {
result += " " + std::to_string(event.arg);
}
return result;
}
class TranscoderWrapper::CallbackImpl : public MediaTranscoder::CallbackInterface {
public:
CallbackImpl(const std::shared_ptr<TranscoderWrapper>& owner, ClientIdType clientId,
SessionIdType sessionId)
: mOwner(owner), mClientId(clientId), mSessionId(sessionId) {}
virtual void onFinished(const MediaTranscoder* transcoder __unused) override {
auto owner = mOwner.lock();
if (owner != nullptr) {
owner->onFinish(mClientId, mSessionId);
}
}
virtual void onError(const MediaTranscoder* transcoder __unused,
media_status_t error) override {
auto owner = mOwner.lock();
if (owner != nullptr) {
owner->onError(mClientId, mSessionId, error);
}
}
virtual void onProgressUpdate(const MediaTranscoder* transcoder __unused,
int32_t progress) override {
auto owner = mOwner.lock();
if (owner != nullptr) {
owner->onProgress(mClientId, mSessionId, progress);
}
}
virtual void onCodecResourceLost(const MediaTranscoder* transcoder __unused,
const std::shared_ptr<ndk::ScopedAParcel>& pausedState
__unused) override {
ALOGV("%s: session {%lld, %d}", __FUNCTION__, (long long)mClientId, mSessionId);
}
private:
std::weak_ptr<TranscoderWrapper> mOwner;
ClientIdType mClientId;
SessionIdType mSessionId;
};
TranscoderWrapper::TranscoderWrapper() : mCurrentClientId(0), mCurrentSessionId(-1) {
std::thread(&TranscoderWrapper::threadLoop, this).detach();
}
void TranscoderWrapper::setCallback(const std::shared_ptr<TranscoderCallbackInterface>& cb) {
mCallback = cb;
}
static bool isResourceError(media_status_t err) {
return err == AMEDIACODEC_ERROR_RECLAIMED || err == AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE;
}
void TranscoderWrapper::reportError(ClientIdType clientId, SessionIdType sessionId,
media_status_t err) {
auto callback = mCallback.lock();
if (callback != nullptr) {
if (isResourceError(err)) {
// Add a placeholder pause state to mPausedStateMap. This is required when resuming.
// TODO: remove this when transcoder pause/resume logic is ready. New logic will
// no longer use the pause states.
auto it = mPausedStateMap.find(SessionKeyType(clientId, sessionId));
if (it == mPausedStateMap.end()) {
mPausedStateMap.emplace(SessionKeyType(clientId, sessionId),
new ndk::ScopedAParcel());
}
callback->onResourceLost();
} else {
callback->onError(clientId, sessionId, toTranscodingError(err));
}
}
}
void TranscoderWrapper::start(ClientIdType clientId, SessionIdType sessionId,
const TranscodingRequestParcel& request,
const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
queueEvent(Event::Start, clientId, sessionId, [=] {
media_status_t err = handleStart(clientId, sessionId, request, clientCb);
if (err != AMEDIA_OK) {
cleanup();
reportError(clientId, sessionId, err);
} else {
auto callback = mCallback.lock();
if (callback != nullptr) {
callback->onStarted(clientId, sessionId);
}
}
});
}
void TranscoderWrapper::pause(ClientIdType clientId, SessionIdType sessionId) {
queueEvent(Event::Pause, clientId, sessionId, [=] {
media_status_t err = handlePause(clientId, sessionId);
cleanup();
if (err != AMEDIA_OK) {
reportError(clientId, sessionId, err);
} else {
auto callback = mCallback.lock();
if (callback != nullptr) {
callback->onPaused(clientId, sessionId);
}
}
});
}
void TranscoderWrapper::resume(ClientIdType clientId, SessionIdType sessionId,
const TranscodingRequestParcel& request,
const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
queueEvent(Event::Resume, clientId, sessionId, [=] {
media_status_t err = handleResume(clientId, sessionId, request, clientCb);
if (err != AMEDIA_OK) {
cleanup();
reportError(clientId, sessionId, err);
} else {
auto callback = mCallback.lock();
if (callback != nullptr) {
callback->onResumed(clientId, sessionId);
}
}
});
}
void TranscoderWrapper::stop(ClientIdType clientId, SessionIdType sessionId) {
queueEvent(Event::Stop, clientId, sessionId, [=] {
if (mTranscoder != nullptr && clientId == mCurrentClientId &&
sessionId == mCurrentSessionId) {
// Cancelling the currently running session.
media_status_t err = mTranscoder->cancel();
if (err != AMEDIA_OK) {
ALOGW("failed to stop transcoder: %d", err);
} else {
ALOGI("transcoder stopped");
}
cleanup();
} else {
// For sessions that's not currently running, release any pausedState for the session.
mPausedStateMap.erase(SessionKeyType(clientId, sessionId));
}
// No callback needed for stop.
});
}
void TranscoderWrapper::onFinish(ClientIdType clientId, SessionIdType sessionId) {
queueEvent(Event::Finish, clientId, sessionId, [=] {
if (mTranscoder != nullptr && clientId == mCurrentClientId &&
sessionId == mCurrentSessionId) {
cleanup();
}
auto callback = mCallback.lock();
if (callback != nullptr) {
callback->onFinish(clientId, sessionId);
}
});
}
void TranscoderWrapper::onError(ClientIdType clientId, SessionIdType sessionId,
media_status_t error) {
queueEvent(
Event::Error, clientId, sessionId,
[=] {
if (mTranscoder != nullptr && clientId == mCurrentClientId &&
sessionId == mCurrentSessionId) {
cleanup();
}
reportError(clientId, sessionId, error);
},
error);
}
void TranscoderWrapper::onProgress(ClientIdType clientId, SessionIdType sessionId,
int32_t progress) {
queueEvent(
Event::Progress, clientId, sessionId,
[=] {
auto callback = mCallback.lock();
if (callback != nullptr) {
callback->onProgressUpdate(clientId, sessionId, progress);
}
},
progress);
}
media_status_t TranscoderWrapper::setupTranscoder(
ClientIdType clientId, SessionIdType sessionId, const TranscodingRequestParcel& request,
const std::shared_ptr<ITranscodingClientCallback>& clientCb,
const std::shared_ptr<ndk::ScopedAParcel>& pausedState) {
if (clientCb == nullptr) {
ALOGE("client callback is null");
return AMEDIA_ERROR_INVALID_PARAMETER;
}
if (mTranscoder != nullptr) {
ALOGE("transcoder already running");
return AMEDIA_ERROR_INVALID_OPERATION;
}
Status status;
::ndk::ScopedFileDescriptor srcFd, dstFd;
status = clientCb->openFileDescriptor(request.sourceFilePath, "r", &srcFd);
if (!status.isOk() || srcFd.get() < 0) {
ALOGE("failed to open source");
return AMEDIA_ERROR_IO;
}
// Open dest file with "rw", as the transcoder could potentially reuse part of it
// for resume case. We might want the further differentiate and open with "w" only
// for start.
status = clientCb->openFileDescriptor(request.destinationFilePath, "rw", &dstFd);
if (!status.isOk() || dstFd.get() < 0) {
ALOGE("failed to open destination");
return AMEDIA_ERROR_IO;
}
mCurrentClientId = clientId;
mCurrentSessionId = sessionId;
mTranscoderCb = std::make_shared<CallbackImpl>(shared_from_this(), clientId, sessionId);
mTranscoder = MediaTranscoder::create(mTranscoderCb, pausedState);
if (mTranscoder == nullptr) {
ALOGE("failed to create transcoder");
return AMEDIA_ERROR_UNKNOWN;
}
media_status_t err = mTranscoder->configureSource(srcFd.get());
if (err != AMEDIA_OK) {
ALOGE("failed to configure source: %d", err);
return err;
}
std::vector<std::shared_ptr<AMediaFormat>> trackFormats = mTranscoder->getTrackFormats();
if (trackFormats.size() == 0) {
ALOGE("failed to get track formats!");
return AMEDIA_ERROR_MALFORMED;
}
for (int i = 0; i < trackFormats.size(); ++i) {
AMediaFormat* format = nullptr;
const char* mime = nullptr;
AMediaFormat_getString(trackFormats[i].get(), AMEDIAFORMAT_KEY_MIME, &mime);
if (!strncmp(mime, "video/", 6)) {
format = getVideoFormat(mime, request.requestedVideoTrackFormat);
}
err = mTranscoder->configureTrackFormat(i, format);
if (format != nullptr) {
AMediaFormat_delete(format);
}
if (err != AMEDIA_OK) {
ALOGE("failed to configure track format for track %d: %d", i, err);
return err;
}
}
err = mTranscoder->configureDestination(dstFd.get());
if (err != AMEDIA_OK) {
ALOGE("failed to configure dest: %d", err);
return err;
}
return AMEDIA_OK;
}
media_status_t TranscoderWrapper::handleStart(
ClientIdType clientId, SessionIdType sessionId, const TranscodingRequestParcel& request,
const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
ALOGI("%s: setting up transcoder for start", __FUNCTION__);
media_status_t err = setupTranscoder(clientId, sessionId, request, clientCb);
if (err != AMEDIA_OK) {
ALOGI("%s: failed to setup transcoder", __FUNCTION__);
return err;
}
err = mTranscoder->start();
if (err != AMEDIA_OK) {
ALOGE("%s: failed to start transcoder: %d", __FUNCTION__, err);
return err;
}
ALOGI("%s: transcoder started", __FUNCTION__);
return AMEDIA_OK;
}
media_status_t TranscoderWrapper::handlePause(ClientIdType clientId, SessionIdType sessionId) {
if (mTranscoder == nullptr) {
ALOGE("%s: transcoder is not running", __FUNCTION__);
return AMEDIA_ERROR_INVALID_OPERATION;
}
if (clientId != mCurrentClientId || sessionId != mCurrentSessionId) {
ALOGW("%s: stopping session {%lld, %d} that's not current session {%lld, %d}", __FUNCTION__,
(long long)clientId, sessionId, (long long)mCurrentClientId, mCurrentSessionId);
}
ALOGI("%s: pausing transcoder", __FUNCTION__);
std::shared_ptr<ndk::ScopedAParcel> pauseStates;
media_status_t err = mTranscoder->pause(&pauseStates);
if (err != AMEDIA_OK) {
ALOGE("%s: failed to pause transcoder: %d", __FUNCTION__, err);
return err;
}
mPausedStateMap[SessionKeyType(clientId, sessionId)] = pauseStates;
ALOGI("%s: transcoder paused", __FUNCTION__);
return AMEDIA_OK;
}
media_status_t TranscoderWrapper::handleResume(
ClientIdType clientId, SessionIdType sessionId, const TranscodingRequestParcel& request,
const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
std::shared_ptr<ndk::ScopedAParcel> pausedState;
auto it = mPausedStateMap.find(SessionKeyType(clientId, sessionId));
if (it != mPausedStateMap.end()) {
pausedState = it->second;
mPausedStateMap.erase(it);
} else {
ALOGE("%s: can't find paused state", __FUNCTION__);
return AMEDIA_ERROR_INVALID_OPERATION;
}
ALOGI("%s: setting up transcoder for resume", __FUNCTION__);
media_status_t err = setupTranscoder(clientId, sessionId, request, clientCb, pausedState);
if (err != AMEDIA_OK) {
ALOGE("%s: failed to setup transcoder: %d", __FUNCTION__, err);
return err;
}
err = mTranscoder->resume();
if (err != AMEDIA_OK) {
ALOGE("%s: failed to resume transcoder: %d", __FUNCTION__, err);
return err;
}
ALOGI("%s: transcoder resumed", __FUNCTION__);
return AMEDIA_OK;
}
void TranscoderWrapper::cleanup() {
mCurrentClientId = 0;
mCurrentSessionId = -1;
mTranscoderCb = nullptr;
mTranscoder = nullptr;
}
void TranscoderWrapper::queueEvent(Event::Type type, ClientIdType clientId, SessionIdType sessionId,
const std::function<void()> runnable, int32_t arg) {
std::scoped_lock lock{mLock};
mQueue.push_back({type, clientId, sessionId, runnable, arg});
mCondition.notify_one();
}
void TranscoderWrapper::threadLoop() {
std::unique_lock<std::mutex> lock{mLock};
// TranscoderWrapper currently lives in the transcoding service, as long as
// MediaTranscodingService itself.
while (true) {
// Wait for the next event.
while (mQueue.empty()) {
mCondition.wait(lock);
}
Event event = *mQueue.begin();
mQueue.pop_front();
ALOGD("%s: %s", __FUNCTION__, toString(event).c_str());
lock.unlock();
event.runnable();
lock.lock();
}
}
} // namespace android