You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
628 lines
23 KiB
628 lines
23 KiB
/*
|
|
* 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 <media/TranscodingRequest.h>
|
|
#include <utils/AndroidThreads.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 std::shared_ptr<AMediaFormat> getVideoFormat(
|
|
const char* originalMime,
|
|
const std::optional<TranscodingVideoTrackFormat>& requestedFormat) {
|
|
if (requestedFormat == std::nullopt) {
|
|
return nullptr;
|
|
}
|
|
|
|
std::shared_ptr<AMediaFormat> format =
|
|
std::shared_ptr<AMediaFormat>(AMediaFormat_new(), &AMediaFormat_delete);
|
|
bool changed = false;
|
|
if (requestedFormat->codecType == TranscodingVideoCodecType::kHevc &&
|
|
strcmp(originalMime, AMEDIA_MIMETYPE_VIDEO_HEVC)) {
|
|
AMediaFormat_setString(format.get(), AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_HEVC);
|
|
changed = true;
|
|
} else if (requestedFormat->codecType == TranscodingVideoCodecType::kAvc &&
|
|
strcmp(originalMime, AMEDIA_MIMETYPE_VIDEO_AVC)) {
|
|
AMediaFormat_setString(format.get(), AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_AVC);
|
|
changed = true;
|
|
}
|
|
if (requestedFormat->bitrateBps > 0) {
|
|
AMediaFormat_setInt32(format.get(), 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) {
|
|
// Use null format for passthru.
|
|
format.reset();
|
|
}
|
|
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;
|
|
case Event::HeartBeat:
|
|
typeStr = "HeartBeat";
|
|
break;
|
|
case Event::Abandon:
|
|
typeStr = "Abandon";
|
|
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 onHeartBeat(const MediaTranscoder* transcoder __unused) override {
|
|
auto owner = mOwner.lock();
|
|
if (owner != nullptr) {
|
|
owner->onHeartBeat(mClientId, mSessionId);
|
|
}
|
|
}
|
|
|
|
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(const std::shared_ptr<TranscoderCallbackInterface>& cb,
|
|
const std::shared_ptr<TranscodingLogger>& logger,
|
|
int64_t heartBeatIntervalUs)
|
|
: mCallback(cb),
|
|
mLogger(logger),
|
|
mHeartBeatIntervalUs(heartBeatIntervalUs),
|
|
mCurrentClientId(0),
|
|
mCurrentSessionId(-1),
|
|
mLooperReady(false) {
|
|
ALOGV("TranscoderWrapper CTOR: %p", this);
|
|
}
|
|
|
|
TranscoderWrapper::~TranscoderWrapper() {
|
|
ALOGV("TranscoderWrapper DTOR: %p", this);
|
|
}
|
|
|
|
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(clientId, sessionId);
|
|
} else {
|
|
callback->onError(clientId, sessionId, toTranscodingError(err));
|
|
}
|
|
}
|
|
}
|
|
|
|
void TranscoderWrapper::start(ClientIdType clientId, SessionIdType sessionId,
|
|
const TranscodingRequestParcel& requestParcel, uid_t callingUid,
|
|
const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
|
|
TranscodingRequest request{requestParcel};
|
|
queueEvent(Event::Start, clientId, sessionId, [=] {
|
|
media_status_t err = handleStart(clientId, sessionId, request, callingUid, 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& requestParcel, uid_t callingUid,
|
|
const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
|
|
TranscodingRequest request{requestParcel};
|
|
queueEvent(Event::Resume, clientId, sessionId, [=] {
|
|
media_status_t err = handleResume(clientId, sessionId, request, callingUid, 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, bool abandon) {
|
|
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");
|
|
}
|
|
logSessionEnded(TranscodingLogger::SessionEndedReason::CANCELLED, err);
|
|
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.
|
|
});
|
|
|
|
if (abandon) {
|
|
queueEvent(Event::Abandon, 0, 0, nullptr);
|
|
}
|
|
}
|
|
|
|
void TranscoderWrapper::onFinish(ClientIdType clientId, SessionIdType sessionId) {
|
|
queueEvent(Event::Finish, clientId, sessionId, [=] {
|
|
if (mTranscoder != nullptr && clientId == mCurrentClientId &&
|
|
sessionId == mCurrentSessionId) {
|
|
logSessionEnded(TranscodingLogger::SessionEndedReason::FINISHED, AMEDIA_OK);
|
|
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) {
|
|
logSessionEnded(TranscodingLogger::SessionEndedReason::ERROR, error);
|
|
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);
|
|
}
|
|
|
|
void TranscoderWrapper::onHeartBeat(ClientIdType clientId, SessionIdType sessionId) {
|
|
queueEvent(Event::HeartBeat, clientId, sessionId, [=] {
|
|
auto callback = mCallback.lock();
|
|
if (callback != nullptr) {
|
|
callback->onHeartBeat(clientId, sessionId);
|
|
}
|
|
});
|
|
}
|
|
|
|
media_status_t TranscoderWrapper::setupTranscoder(
|
|
ClientIdType clientId, SessionIdType sessionId, const TranscodingRequestParcel& request,
|
|
uid_t callingUid, const std::shared_ptr<ITranscodingClientCallback>& clientCb,
|
|
TranscodingLogger::SessionEndedReason* failureReason,
|
|
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;
|
|
}
|
|
|
|
// Unwrap the callback and send heartbeats to the client after each operation during setup.
|
|
auto callback = mCallback.lock();
|
|
if (callback == nullptr) {
|
|
return AMEDIA_ERROR_INVALID_OPERATION;
|
|
}
|
|
|
|
Status status;
|
|
::ndk::ScopedFileDescriptor srcFd, dstFd;
|
|
int srcFdInt = request.sourceFd.get();
|
|
if (srcFdInt < 0) {
|
|
status = clientCb->openFileDescriptor(request.sourceFilePath, "r", &srcFd);
|
|
if (!status.isOk() || srcFd.get() < 0) {
|
|
ALOGE("failed to open source");
|
|
*failureReason = TranscodingLogger::SessionEndedReason::OPEN_SRC_FD_FAILED;
|
|
return AMEDIA_ERROR_IO;
|
|
}
|
|
srcFdInt = srcFd.get();
|
|
}
|
|
|
|
callback->onHeartBeat(clientId, sessionId);
|
|
|
|
int dstFdInt = request.destinationFd.get();
|
|
if (dstFdInt < 0) {
|
|
// 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");
|
|
*failureReason = TranscodingLogger::SessionEndedReason::OPEN_DST_FD_FAILED;
|
|
return AMEDIA_ERROR_IO;
|
|
}
|
|
dstFdInt = dstFd.get();
|
|
}
|
|
|
|
callback->onHeartBeat(clientId, sessionId);
|
|
|
|
mCurrentClientId = clientId;
|
|
mCurrentSessionId = sessionId;
|
|
mCurrentCallingUid = callingUid;
|
|
mTranscoderCb = std::make_shared<CallbackImpl>(shared_from_this(), clientId, sessionId);
|
|
mTranscoder = MediaTranscoder::create(mTranscoderCb, mHeartBeatIntervalUs, request.clientPid,
|
|
request.clientUid, pausedState);
|
|
if (mTranscoder == nullptr) {
|
|
ALOGE("failed to create transcoder");
|
|
*failureReason = TranscodingLogger::SessionEndedReason::CREATE_FAILED;
|
|
return AMEDIA_ERROR_UNKNOWN;
|
|
}
|
|
|
|
callback->onHeartBeat(clientId, sessionId);
|
|
|
|
media_status_t err = mTranscoder->configureSource(srcFdInt);
|
|
if (err != AMEDIA_OK) {
|
|
ALOGE("failed to configure source: %d", err);
|
|
*failureReason = TranscodingLogger::SessionEndedReason::CONFIG_SRC_FAILED;
|
|
return err;
|
|
}
|
|
|
|
callback->onHeartBeat(clientId, sessionId);
|
|
|
|
std::vector<std::shared_ptr<AMediaFormat>> trackFormats = mTranscoder->getTrackFormats();
|
|
if (trackFormats.size() == 0) {
|
|
ALOGE("failed to get track formats!");
|
|
*failureReason = TranscodingLogger::SessionEndedReason::NO_TRACKS;
|
|
return AMEDIA_ERROR_MALFORMED;
|
|
}
|
|
|
|
callback->onHeartBeat(clientId, sessionId);
|
|
|
|
for (int i = 0; i < trackFormats.size(); ++i) {
|
|
std::shared_ptr<AMediaFormat> format;
|
|
const char* mime = nullptr;
|
|
AMediaFormat_getString(trackFormats[i].get(), AMEDIAFORMAT_KEY_MIME, &mime);
|
|
|
|
if (!strncmp(mime, "video/", 6)) {
|
|
format = getVideoFormat(mime, request.requestedVideoTrackFormat);
|
|
|
|
mSrcFormat = trackFormats[i];
|
|
mDstFormat = format;
|
|
}
|
|
|
|
err = mTranscoder->configureTrackFormat(i, format.get());
|
|
if (err != AMEDIA_OK) {
|
|
ALOGE("failed to configure track format for track %d: %d", i, err);
|
|
*failureReason = TranscodingLogger::SessionEndedReason::CONFIG_TRACK_FAILED;
|
|
return err;
|
|
}
|
|
|
|
callback->onHeartBeat(clientId, sessionId);
|
|
}
|
|
|
|
err = mTranscoder->configureDestination(dstFdInt);
|
|
if (err != AMEDIA_OK) {
|
|
ALOGE("failed to configure dest: %d", err);
|
|
*failureReason = TranscodingLogger::SessionEndedReason::CONFIG_DST_FAILED;
|
|
return err;
|
|
}
|
|
|
|
callback->onHeartBeat(clientId, sessionId);
|
|
|
|
return AMEDIA_OK;
|
|
}
|
|
|
|
media_status_t TranscoderWrapper::handleStart(
|
|
ClientIdType clientId, SessionIdType sessionId, const TranscodingRequestParcel& request,
|
|
uid_t callingUid, const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
|
|
ALOGI("%s: setting up transcoder for start", __FUNCTION__);
|
|
TranscodingLogger::SessionEndedReason reason = TranscodingLogger::SessionEndedReason::UNKNOWN;
|
|
media_status_t err =
|
|
setupTranscoder(clientId, sessionId, request, callingUid, clientCb, &reason);
|
|
if (err != AMEDIA_OK) {
|
|
ALOGE("%s: failed to setup transcoder", __FUNCTION__);
|
|
logSessionEnded(reason, err);
|
|
return err;
|
|
}
|
|
|
|
mTranscodeStartTime = std::chrono::steady_clock::now();
|
|
|
|
err = mTranscoder->start();
|
|
if (err != AMEDIA_OK) {
|
|
ALOGE("%s: failed to start transcoder: %d", __FUNCTION__, err);
|
|
logSessionEnded(TranscodingLogger::SessionEndedReason::START_FAILED, 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);
|
|
logSessionEnded(TranscodingLogger::SessionEndedReason::PAUSED, err);
|
|
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,
|
|
uid_t callingUid, 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__);
|
|
TranscodingLogger::SessionEndedReason reason = TranscodingLogger::SessionEndedReason::UNKNOWN;
|
|
media_status_t err = setupTranscoder(clientId, sessionId, request, callingUid, clientCb,
|
|
&reason, pausedState);
|
|
if (err != AMEDIA_OK) {
|
|
ALOGE("%s: failed to setup transcoder: %d", __FUNCTION__, err);
|
|
logSessionEnded(reason, err);
|
|
return err;
|
|
}
|
|
|
|
// Note: For now resume() will just restart transcoding from the beginning, so there is no need
|
|
// to distinguish between resume and start from a performance perspective.
|
|
mTranscodeStartTime = std::chrono::steady_clock::now();
|
|
|
|
err = mTranscoder->resume();
|
|
if (err != AMEDIA_OK) {
|
|
ALOGE("%s: failed to resume transcoder: %d", __FUNCTION__, err);
|
|
logSessionEnded(TranscodingLogger::SessionEndedReason::RESUME_FAILED, err);
|
|
return err;
|
|
}
|
|
|
|
ALOGI("%s: transcoder resumed", __FUNCTION__);
|
|
return AMEDIA_OK;
|
|
}
|
|
|
|
void TranscoderWrapper::cleanup() {
|
|
mCurrentClientId = 0;
|
|
mCurrentSessionId = -1;
|
|
mCurrentCallingUid = -1;
|
|
mTranscoderCb = nullptr;
|
|
mTranscoder = nullptr;
|
|
mSrcFormat = nullptr;
|
|
mDstFormat = nullptr;
|
|
}
|
|
|
|
void TranscoderWrapper::logSessionEnded(const TranscodingLogger::SessionEndedReason& reason,
|
|
int error) {
|
|
std::chrono::microseconds transcodeDuration(-1);
|
|
if (reason == TranscodingLogger::SessionEndedReason::FINISHED && error == AMEDIA_OK) {
|
|
transcodeDuration = std::chrono::duration_cast<std::chrono::microseconds>(
|
|
std::chrono::steady_clock::now() - mTranscodeStartTime);
|
|
}
|
|
|
|
mLogger->logSessionEnded(reason, mCurrentCallingUid, error, transcodeDuration, mSrcFormat.get(),
|
|
mDstFormat.get());
|
|
}
|
|
|
|
void TranscoderWrapper::queueEvent(Event::Type type, ClientIdType clientId, SessionIdType sessionId,
|
|
const std::function<void()> runnable, int32_t arg) {
|
|
std::scoped_lock lock{mLock};
|
|
|
|
if (!mLooperReady) {
|
|
// A shared_ptr to ourselves is given to the thread's stack, so that the TranscoderWrapper
|
|
// object doesn't go away until the thread exits. When a watchdog timeout happens, this
|
|
// allows the session controller to release its reference to the TranscoderWrapper object
|
|
// without blocking on the thread exits.
|
|
std::thread([owner = shared_from_this()]() { owner->threadLoop(); }).detach();
|
|
mLooperReady = true;
|
|
}
|
|
|
|
mQueue.push_back({type, clientId, sessionId, runnable, arg});
|
|
mCondition.notify_one();
|
|
}
|
|
|
|
void TranscoderWrapper::threadLoop() {
|
|
androidSetThreadPriority(0 /*tid (0 = current) */, ANDROID_PRIORITY_BACKGROUND);
|
|
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();
|
|
|
|
ALOGV("%s: %s", __FUNCTION__, toString(event).c_str());
|
|
|
|
if (event.type == Event::Abandon) {
|
|
break;
|
|
}
|
|
|
|
lock.unlock();
|
|
event.runnable();
|
|
lock.lock();
|
|
}
|
|
}
|
|
} // namespace android
|