transcoder: initial version of pause/resume
- Add pause/resume in TranscoderWrapper, save paused state on pause
and use it to create new transcoder on resume.
Misc fixes:
- TranscoderWrapper::stop should only cancel transcoder if the stop
is for the currently running job. Scheduler could call stop to
cancel a job any time.
- Don't hold TranscoderWrapper lock when running event runnable. If
the runnable calls back into scheduler, and scheduler may call
transcoder again and deadlock.
- Don't report abort as error if the transcoder is cancelled explicitly.
- Push decoder/encoder start as msgs, so that they could be skipped too
if the job is cancelled shortly after starts.
Tests:
Add tests for cancel/pause/resume with real transcoder.
bug: 154734285
bug: 154733948
test: unit testing
Change-Id: I2b7d3da69df53b92ab351db455310799ba0e0e8f
diff --git a/media/libmediatranscoding/TranscoderWrapper.cpp b/media/libmediatranscoding/TranscoderWrapper.cpp
index 428d86e..aaa15c4 100644
--- a/media/libmediatranscoding/TranscoderWrapper.cpp
+++ b/media/libmediatranscoding/TranscoderWrapper.cpp
@@ -56,6 +56,38 @@
}
}
+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
const char* TranscoderWrapper::toString(Event::Type type) {
switch (type) {
@@ -105,7 +137,7 @@
}
virtual void onCodecResourceLost(const MediaTranscoder* transcoder __unused,
- const std::shared_ptr<const Parcelable>& pausedState
+ const std::shared_ptr<const Parcel>& pausedState
__unused) override {
ALOGV("%s: job {%lld, %d}", __FUNCTION__, (long long)mClientId, mJobId);
}
@@ -126,51 +158,87 @@
void TranscoderWrapper::start(ClientIdType clientId, JobIdType jobId,
const TranscodingRequestParcel& request,
- const std::shared_ptr<ITranscodingClientCallback>& callback) {
+ const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
queueEvent(Event::Start, clientId, jobId, [=] {
- TranscodingErrorCode err = handleStart(clientId, jobId, request, callback);
+ TranscodingErrorCode err = handleStart(clientId, jobId, request, clientCb);
+ auto callback = mCallback.lock();
if (err != TranscodingErrorCode::kNoError) {
cleanup();
- auto callback = mCallback.lock();
if (callback != nullptr) {
callback->onError(clientId, jobId, err);
}
+ } else {
+ if (callback != nullptr) {
+ callback->onStarted(clientId, jobId);
+ }
}
});
}
void TranscoderWrapper::pause(ClientIdType clientId, JobIdType jobId) {
- queueEvent(Event::Pause, clientId, jobId, [] {});
+ queueEvent(Event::Pause, clientId, jobId, [=] {
+ TranscodingErrorCode err = handlePause(clientId, jobId);
+
+ cleanup();
+
+ auto callback = mCallback.lock();
+ if (callback != nullptr) {
+ if (err != TranscodingErrorCode::kNoError) {
+ callback->onError(clientId, jobId, err);
+ } else {
+ callback->onPaused(clientId, jobId);
+ }
+ }
+ });
}
-void TranscoderWrapper::resume(ClientIdType clientId, JobIdType jobId) {
- queueEvent(Event::Resume, clientId, jobId, [] {});
+void TranscoderWrapper::resume(ClientIdType clientId, JobIdType jobId,
+ const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
+ queueEvent(Event::Resume, clientId, jobId, [=] {
+ TranscodingErrorCode err = handleResume(clientId, jobId, request, clientCb);
+
+ auto callback = mCallback.lock();
+ if (err != TranscodingErrorCode::kNoError) {
+ cleanup();
+
+ if (callback != nullptr) {
+ callback->onError(clientId, jobId, err);
+ }
+ } else {
+ if (callback != nullptr) {
+ callback->onResumed(clientId, jobId);
+ }
+ }
+ });
}
void TranscoderWrapper::stop(ClientIdType clientId, JobIdType jobId) {
queueEvent(Event::Stop, clientId, jobId, [=] {
- if (clientId != mCurrentClientId || jobId != mCurrentJobId) {
- ALOGW("Stopping job {%lld, %d} that's not current job {%lld, %d}", (long long)clientId,
- jobId, (long long)mCurrentClientId, mCurrentJobId);
- }
-
- // stop transcoder.
- media_status_t err = mTranscoder->cancel();
- if (err != AMEDIA_OK) {
- ALOGE("failed to stop transcoder: %d", err);
+ if (mTranscoder != nullptr && clientId == mCurrentClientId && jobId == mCurrentJobId) {
+ // Cancelling the currently running job.
+ media_status_t err = mTranscoder->cancel();
+ if (err != AMEDIA_OK) {
+ ALOGE("failed to stop transcoder: %d", err);
+ } else {
+ ALOGI("transcoder stopped");
+ }
+ cleanup();
} else {
- ALOGI("transcoder stopped");
+ // For jobs that's not currently running, release any pausedState for the job.
+ mPausedStateMap.erase(JobKeyType(clientId, jobId));
}
-
- cleanup();
+ // No callback needed for stop.
});
}
void TranscoderWrapper::onFinish(ClientIdType clientId, JobIdType jobId) {
queueEvent(Event::Finish, clientId, jobId, [=] {
- cleanup();
+ if (mTranscoder != nullptr && clientId == mCurrentClientId && jobId == mCurrentJobId) {
+ cleanup();
+ }
auto callback = mCallback.lock();
if (callback != nullptr) {
@@ -182,7 +250,9 @@
void TranscoderWrapper::onError(ClientIdType clientId, JobIdType jobId,
TranscodingErrorCode error) {
queueEvent(Event::Error, clientId, jobId, [=] {
- cleanup();
+ if (mTranscoder != nullptr && clientId == mCurrentClientId && jobId == mCurrentJobId) {
+ cleanup();
+ }
auto callback = mCallback.lock();
if (callback != nullptr) {
@@ -191,41 +261,10 @@
});
}
-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;
-}
-
-TranscodingErrorCode TranscoderWrapper::handleStart(
+TranscodingErrorCode TranscoderWrapper::setupTranscoder(
ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
- const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
+ const std::shared_ptr<ITranscodingClientCallback>& clientCb,
+ const std::shared_ptr<const Parcel>& pausedState) {
if (clientCb == nullptr) {
ALOGE("client callback is null");
return TranscodingErrorCode::kInvalidParameter;
@@ -244,7 +283,10 @@
return TranscodingErrorCode::kErrorIO;
}
- status = clientCb->openFileDescriptor(request.destinationFilePath, "w", &dstFd);
+ // 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 TranscodingErrorCode::kErrorIO;
@@ -253,7 +295,7 @@
mCurrentClientId = clientId;
mCurrentJobId = jobId;
mTranscoderCb = std::make_shared<CallbackImpl>(shared_from_this(), clientId, jobId);
- mTranscoder = MediaTranscoder::create(mTranscoderCb, nullptr);
+ mTranscoder = MediaTranscoder::create(mTranscoderCb, pausedState);
if (mTranscoder == nullptr) {
ALOGE("failed to create transcoder");
return TranscodingErrorCode::kUnknown;
@@ -296,13 +338,79 @@
return toTranscodingError(err);
}
- err = mTranscoder->start();
- if (err != AMEDIA_OK) {
- ALOGE("failed to start transcoder: %d", err);
- return toTranscodingError(err);
+ return TranscodingErrorCode::kNoError;
+}
+
+TranscodingErrorCode TranscoderWrapper::handleStart(
+ ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
+ ALOGI("setting up transcoder for start");
+ TranscodingErrorCode err = setupTranscoder(clientId, jobId, request, clientCb);
+ if (err != TranscodingErrorCode::kNoError) {
+ ALOGI("%s: failed to setup transcoder", __FUNCTION__);
+ return err;
}
- ALOGI("transcoder started");
+ media_status_t status = mTranscoder->start();
+ if (status != AMEDIA_OK) {
+ ALOGE("%s: failed to start transcoder: %d", __FUNCTION__, err);
+ return toTranscodingError(status);
+ }
+
+ ALOGI("%s: transcoder started", __FUNCTION__);
+ return TranscodingErrorCode::kNoError;
+}
+
+TranscodingErrorCode TranscoderWrapper::handlePause(ClientIdType clientId, JobIdType jobId) {
+ if (mTranscoder == nullptr) {
+ ALOGE("%s: transcoder is not running", __FUNCTION__);
+ return TranscodingErrorCode::kInvalidOperation;
+ }
+
+ if (clientId != mCurrentClientId || jobId != mCurrentJobId) {
+ ALOGW("%s: stopping job {%lld, %d} that's not current job {%lld, %d}", __FUNCTION__,
+ (long long)clientId, jobId, (long long)mCurrentClientId, mCurrentJobId);
+ }
+
+ std::shared_ptr<const Parcel> pauseStates;
+ media_status_t err = mTranscoder->pause(&pauseStates);
+ if (err != AMEDIA_OK) {
+ ALOGE("%s: failed to pause transcoder: %d", __FUNCTION__, err);
+ return toTranscodingError(err);
+ }
+ mPausedStateMap[JobKeyType(clientId, jobId)] = pauseStates;
+
+ ALOGI("%s: transcoder paused", __FUNCTION__);
+ return TranscodingErrorCode::kNoError;
+}
+
+TranscodingErrorCode TranscoderWrapper::handleResume(
+ ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
+ std::shared_ptr<const Parcel> pausedState;
+ auto it = mPausedStateMap.find(JobKeyType(clientId, jobId));
+ if (it != mPausedStateMap.end()) {
+ pausedState = it->second;
+ mPausedStateMap.erase(it);
+ } else {
+ ALOGE("%s: can't find paused state", __FUNCTION__);
+ return TranscodingErrorCode::kInvalidOperation;
+ }
+
+ ALOGI("setting up transcoder for resume");
+ TranscodingErrorCode err = setupTranscoder(clientId, jobId, request, clientCb, pausedState);
+ if (err != TranscodingErrorCode::kNoError) {
+ ALOGE("%s: failed to setup transcoder", __FUNCTION__);
+ return err;
+ }
+
+ media_status_t status = mTranscoder->resume();
+ if (status != AMEDIA_OK) {
+ ALOGE("%s: failed to resume transcoder: %d", __FUNCTION__, err);
+ return toTranscodingError(status);
+ }
+
+ ALOGI("%s: transcoder resumed", __FUNCTION__);
return TranscodingErrorCode::kNoError;
}
@@ -339,7 +447,9 @@
ALOGD("%s: job {%lld, %d}: %s", __FUNCTION__, (long long)event.clientId, event.jobId,
toString(event.type));
+ lock.unlock();
event.runnable();
+ lock.lock();
}
}
diff --git a/media/libmediatranscoding/TranscodingJobScheduler.cpp b/media/libmediatranscoding/TranscodingJobScheduler.cpp
index 07eb949..3e4f319 100644
--- a/media/libmediatranscoding/TranscodingJobScheduler.cpp
+++ b/media/libmediatranscoding/TranscodingJobScheduler.cpp
@@ -79,7 +79,8 @@
mTranscoder->start(topJob->key.first, topJob->key.second, topJob->request,
topJob->callback.lock());
} else if (topJob->state == Job::PAUSED) {
- mTranscoder->resume(topJob->key.first, topJob->key.second);
+ mTranscoder->resume(topJob->key.first, topJob->key.second, topJob->request,
+ topJob->callback.lock());
}
topJob->state = Job::RUNNING;
}
diff --git a/media/libmediatranscoding/include/media/TranscoderInterface.h b/media/libmediatranscoding/include/media/TranscoderInterface.h
index 85e0164..1a3f505 100644
--- a/media/libmediatranscoding/include/media/TranscoderInterface.h
+++ b/media/libmediatranscoding/include/media/TranscoderInterface.h
@@ -32,14 +32,14 @@
// Interface for the scheduler to call the transcoder to take actions.
class TranscoderInterface {
public:
- // TODO(chz): determine what parameters are needed here.
- // For now, always pass in clientId&jobId.
virtual void setCallback(const std::shared_ptr<TranscoderCallbackInterface>& cb) = 0;
virtual void start(ClientIdType clientId, JobIdType jobId,
const TranscodingRequestParcel& request,
const std::shared_ptr<ITranscodingClientCallback>& clientCallback) = 0;
virtual void pause(ClientIdType clientId, JobIdType jobId) = 0;
- virtual void resume(ClientIdType clientId, JobIdType jobId) = 0;
+ virtual void resume(ClientIdType clientId, JobIdType jobId,
+ const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& clientCallback) = 0;
virtual void stop(ClientIdType clientId, JobIdType jobId) = 0;
protected:
diff --git a/media/libmediatranscoding/include/media/TranscoderWrapper.h b/media/libmediatranscoding/include/media/TranscoderWrapper.h
index ba48085..804119f 100644
--- a/media/libmediatranscoding/include/media/TranscoderWrapper.h
+++ b/media/libmediatranscoding/include/media/TranscoderWrapper.h
@@ -21,11 +21,13 @@
#include <media/TranscoderInterface.h>
#include <list>
+#include <map>
#include <mutex>
namespace android {
class MediaTranscoder;
+class Parcelable;
/*
* Wrapper class around MediaTranscoder.
@@ -41,7 +43,9 @@
const TranscodingRequestParcel& request,
const std::shared_ptr<ITranscodingClientCallback>& clientCallback) override;
virtual void pause(ClientIdType clientId, JobIdType jobId) override;
- virtual void resume(ClientIdType clientId, JobIdType jobId) override;
+ virtual void resume(ClientIdType clientId, JobIdType jobId,
+ const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& clientCallback) override;
virtual void stop(ClientIdType clientId, JobIdType jobId) override;
private:
@@ -52,12 +56,15 @@
JobIdType jobId;
std::function<void()> runnable;
};
+ using JobKeyType = std::pair<ClientIdType, JobIdType>;
+
std::shared_ptr<CallbackImpl> mTranscoderCb;
std::shared_ptr<MediaTranscoder> mTranscoder;
std::weak_ptr<TranscoderCallbackInterface> mCallback;
std::mutex mLock;
std::condition_variable mCondition;
std::list<Event> mQueue; // GUARDED_BY(mLock);
+ std::map<JobKeyType, std::shared_ptr<const Parcel>> mPausedStateMap;
ClientIdType mCurrentClientId;
JobIdType mCurrentJobId;
@@ -68,6 +75,14 @@
TranscodingErrorCode handleStart(ClientIdType clientId, JobIdType jobId,
const TranscodingRequestParcel& request,
const std::shared_ptr<ITranscodingClientCallback>& callback);
+ TranscodingErrorCode handlePause(ClientIdType clientId, JobIdType jobId);
+ TranscodingErrorCode handleResume(ClientIdType clientId, JobIdType jobId,
+ const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& callback);
+ TranscodingErrorCode setupTranscoder(
+ ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& callback,
+ const std::shared_ptr<const Parcel>& pausedState = nullptr);
void cleanup();
void queueEvent(Event::Type type, ClientIdType clientId, JobIdType jobId,
diff --git a/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp b/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
index 1931a0e..d21b595 100644
--- a/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
+++ b/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
@@ -94,7 +94,8 @@
void pause(ClientIdType clientId, JobIdType jobId) override {
mEventQueue.push_back(Pause(clientId, jobId));
}
- void resume(ClientIdType clientId, JobIdType jobId) override {
+ void resume(ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& /*request*/,
+ const std::shared_ptr<ITranscodingClientCallback>& /*clientCallback*/) override {
mEventQueue.push_back(Resume(clientId, jobId));
}
void stop(ClientIdType clientId, JobIdType jobId) override {
diff --git a/media/libmediatranscoding/tests/assets/longtest_15s.mp4 b/media/libmediatranscoding/tests/assets/longtest_15s.mp4
new file mode 100644
index 0000000..b50d8e4
--- /dev/null
+++ b/media/libmediatranscoding/tests/assets/longtest_15s.mp4
Binary files differ
diff --git a/media/libmediatranscoding/transcoder/Android.bp b/media/libmediatranscoding/transcoder/Android.bp
index 59cd96d..c153a42 100644
--- a/media/libmediatranscoding/transcoder/Android.bp
+++ b/media/libmediatranscoding/transcoder/Android.bp
@@ -34,6 +34,8 @@
"libmediandk",
"libnativewindow",
"libutils",
+ // TODO: Use libbinder_ndk
+ "libbinder",
],
export_include_dirs: [
diff --git a/media/libmediatranscoding/transcoder/MediaTranscoder.cpp b/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
index 69932f4..bde1cf6 100644
--- a/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
@@ -18,6 +18,7 @@
#define LOG_TAG "MediaTranscoder"
#include <android-base/logging.h>
+#include <binder/Parcel.h>
#include <fcntl.h>
#include <media/MediaSampleReaderNDK.h>
#include <media/MediaSampleWriter.h>
@@ -84,6 +85,17 @@
}
void MediaTranscoder::sendCallback(media_status_t status) {
+ // If the transcoder is already cancelled explicitly, don't send any error callbacks.
+ // Tracks and sample writer will report errors for abort. However, currently we can't
+ // tell it apart from real errors. Ideally we still want to report real errors back
+ // to client, as there is a small chance that explicit abort and the real error come
+ // at around the same time, we should report that if abort has a specific error code.
+ // On the other hand, if the transcoder actually finished (status is AMEDIA_OK) at around
+ // the same time of the abort, we should still report the finish back to the client.
+ if (mCancelled && status != AMEDIA_OK) {
+ return;
+ }
+
bool expected = false;
if (mCallbackSent.compare_exchange_strong(expected, true)) {
if (status == AMEDIA_OK) {
@@ -149,11 +161,11 @@
std::shared_ptr<MediaTranscoder> MediaTranscoder::create(
const std::shared_ptr<CallbackInterface>& callbacks,
- const std::shared_ptr<Parcel>& pausedState) {
+ const std::shared_ptr<const Parcel>& pausedState) {
if (pausedState != nullptr) {
- LOG(ERROR) << "Initializing from paused state is currently not supported.";
- return nullptr;
- } else if (callbacks == nullptr) {
+ LOG(INFO) << "Initializing from paused state.";
+ }
+ if (callbacks == nullptr) {
LOG(ERROR) << "Callbacks cannot be null";
return nullptr;
}
@@ -309,15 +321,15 @@
return AMEDIA_OK;
}
-media_status_t MediaTranscoder::pause(std::shared_ptr<const Parcelable>* pausedState) {
- (void)pausedState;
- LOG(ERROR) << "Pause is not currently supported";
- return AMEDIA_ERROR_UNSUPPORTED;
+media_status_t MediaTranscoder::pause(std::shared_ptr<const Parcel>* pausedState) {
+ // TODO: write internal states to parcel.
+ *pausedState = std::make_shared<Parcel>();
+ return cancel();
}
media_status_t MediaTranscoder::resume() {
- LOG(ERROR) << "Resume is not currently supported";
- return AMEDIA_ERROR_UNSUPPORTED;
+ // TODO: restore internal states from parcel.
+ return start();
}
media_status_t MediaTranscoder::cancel() {
diff --git a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
index 4df3296..ca94b16 100644
--- a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
@@ -380,20 +380,24 @@
}
media_status_t VideoTrackTranscoder::runTranscodeLoop() {
- media_status_t status = AMEDIA_OK;
+ // Push start decoder and encoder as two messages, so that these are subject to the
+ // stop request as well. If the job is cancelled (or paused) immediately after start,
+ // we don't need to waste time start then stop the codecs.
+ mCodecMessageQueue.push([this] {
+ media_status_t status = AMediaCodec_start(mDecoder);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to start video decoder: " << status;
+ mStatus = status;
+ }
+ });
- status = AMediaCodec_start(mDecoder);
- if (status != AMEDIA_OK) {
- LOG(ERROR) << "Unable to start video decoder: " << status;
- return status;
- }
-
- status = AMediaCodec_start(mEncoder.get());
- if (status != AMEDIA_OK) {
- LOG(ERROR) << "Unable to start video encoder: " << status;
- AMediaCodec_stop(mDecoder);
- return status;
- }
+ mCodecMessageQueue.push([this] {
+ media_status_t status = AMediaCodec_start(mEncoder.get());
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << "Unable to start video encoder: " << status;
+ mStatus = status;
+ }
+ });
// Process codec events until EOS is reached, transcoding is stopped or an error occurs.
while (!mStopRequested && !mEosFromEncoder && mStatus == AMEDIA_OK) {
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h b/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
index e5a5d64..7a36c8c 100644
--- a/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
@@ -17,8 +17,6 @@
#ifndef ANDROID_MEDIA_TRANSCODER_H
#define ANDROID_MEDIA_TRANSCODER_H
-#include <binder/Parcel.h>
-#include <binder/Parcelable.h>
#include <media/MediaTrackTranscoderCallback.h>
#include <media/NdkMediaError.h>
#include <media/NdkMediaFormat.h>
@@ -33,6 +31,7 @@
class MediaSampleReader;
class MediaSampleWriter;
+class Parcel;
class MediaTranscoder : public std::enable_shared_from_this<MediaTranscoder>,
public MediaTrackTranscoderCallback {
@@ -57,7 +56,7 @@
* resume.
*/
virtual void onCodecResourceLost(const MediaTranscoder* transcoder,
- const std::shared_ptr<const Parcelable>& pausedState) = 0;
+ const std::shared_ptr<const Parcel>& pausedState) = 0;
virtual ~CallbackInterface() = default;
};
@@ -69,7 +68,7 @@
*/
static std::shared_ptr<MediaTranscoder> create(
const std::shared_ptr<CallbackInterface>& callbacks,
- const std::shared_ptr<Parcel>& pausedState = nullptr);
+ const std::shared_ptr<const Parcel>& pausedState = nullptr);
/** Configures source from path fd. */
media_status_t configureSource(int fd);
@@ -102,8 +101,12 @@
* release the transcoder instance, clear the paused state and delete the partial destination
* file. The caller can optionally call cancel to let the transcoder clean up the partial
* destination file.
+ *
+ * TODO: use NDK AParcel instead
+ * libbinder shouldn't be used by mainline modules. When transcoding goes mainline
+ * it needs to be replaced by stable AParcel.
*/
- media_status_t pause(std::shared_ptr<const Parcelable>* pausedState);
+ media_status_t pause(std::shared_ptr<const Parcel>* pausedState);
/** Resumes a paused transcoding. */
media_status_t resume();
diff --git a/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
index 67f1ca1..330261c 100644
--- a/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
@@ -20,6 +20,7 @@
#define LOG_TAG "MediaTranscoderTests"
#include <android-base/logging.h>
+#include <fcntl.h>
#include <gtest/gtest.h>
#include <media/MediaSampleReaderNDK.h>
#include <media/MediaTranscoder.h>
@@ -74,7 +75,7 @@
int32_t progress __unused) override {}
virtual void onCodecResourceLost(const MediaTranscoder* transcoder __unused,
- const std::shared_ptr<const Parcelable>& pausedState
+ const std::shared_ptr<const Parcel>& pausedState
__unused) override {}
void waitForTranscodingFinished() {
diff --git a/services/mediatranscoding/SimulatedTranscoder.cpp b/services/mediatranscoding/SimulatedTranscoder.cpp
index 5aa325f..97d5f5f 100644
--- a/services/mediatranscoding/SimulatedTranscoder.cpp
+++ b/services/mediatranscoding/SimulatedTranscoder.cpp
@@ -72,7 +72,9 @@
});
}
-void SimulatedTranscoder::resume(ClientIdType clientId, JobIdType jobId) {
+void SimulatedTranscoder::resume(
+ ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& /*request*/,
+ const std::shared_ptr<ITranscodingClientCallback>& /*clientCallback*/) {
queueEvent(Event::Resume, clientId, jobId, [=] {
auto callback = mCallback.lock();
if (callback != nullptr) {
diff --git a/services/mediatranscoding/SimulatedTranscoder.h b/services/mediatranscoding/SimulatedTranscoder.h
index 030222b..1c359dd 100644
--- a/services/mediatranscoding/SimulatedTranscoder.h
+++ b/services/mediatranscoding/SimulatedTranscoder.h
@@ -54,7 +54,8 @@
void start(ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
const std::shared_ptr<ITranscodingClientCallback>& clientCallback) override;
void pause(ClientIdType clientId, JobIdType jobId) override;
- void resume(ClientIdType clientId, JobIdType jobId) override;
+ void resume(ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& clientCallback) override;
void stop(ClientIdType clientId, JobIdType jobId) override;
// ~TranscoderInterface
diff --git a/services/mediatranscoding/tests/MediaTranscodingServiceTestHelper.h b/services/mediatranscoding/tests/MediaTranscodingServiceTestHelper.h
index 4259f54..2f4e74b 100644
--- a/services/mediatranscoding/tests/MediaTranscodingServiceTestHelper.h
+++ b/services/mediatranscoding/tests/MediaTranscodingServiceTestHelper.h
@@ -66,6 +66,8 @@
constexpr const char* kClientPackageB = "com.android.tests.transcoding.testapp.B";
constexpr const char* kClientPackageC = "com.android.tests.transcoding.testapp.C";
+constexpr const char* kTestActivityName = "/com.android.tests.transcoding.MainActivity";
+
static status_t getUidForPackage(String16 packageName, userid_t userId, /*inout*/ uid_t& uid) {
PermissionController pc;
uid = pc.getPackageUid(packageName, 0);
@@ -167,20 +169,28 @@
}
// Push 1 event to back.
- void append(const Event& event) {
+ void append(const Event& event,
+ const TranscodingErrorCode err = TranscodingErrorCode::kNoError) {
ALOGD("%s", toString(event).c_str());
std::unique_lock lock(mLock);
mEventQueue.push_back(event);
+ mLastErr = err;
mCondition.notify_one();
}
+ TranscodingErrorCode getLastError() {
+ std::unique_lock lock(mLock);
+ return mLastErr;
+ }
+
private:
std::mutex mLock;
std::condition_variable mCondition;
Event mPoppedEvent;
std::list<Event> mEventQueue;
+ TranscodingErrorCode mLastErr;
};
// Operators for GTest macros.
@@ -205,8 +215,14 @@
ALOGD("@@@ openFileDescriptor: %s", in_fileUri.c_str());
int fd;
if (in_mode == "w" || in_mode == "rw") {
- // Write-only, create file if non-existent, don't overwrite existing file.
- constexpr int kOpenFlags = O_WRONLY | O_CREAT | O_EXCL;
+ int kOpenFlags;
+ if (in_mode == "w") {
+ // Write-only, create file if non-existent, truncate existing file.
+ kOpenFlags = O_WRONLY | O_CREAT | O_TRUNC;
+ } else {
+ // Read-Write, create if non-existent, no truncate (service will truncate if needed)
+ kOpenFlags = O_RDWR | O_CREAT;
+ }
// User R+W permission.
constexpr int kFileMode = S_IRUSR | S_IWUSR;
fd = open(in_fileUri.c_str(), kOpenFlags, kFileMode);
@@ -239,10 +255,9 @@
return Status::ok();
}
- Status onTranscodingFailed(
- int32_t in_jobId,
- ::aidl::android::media::TranscodingErrorCode /* in_errorCode */) override {
- append(Failed(mClientId, in_jobId));
+ Status onTranscodingFailed(int32_t in_jobId,
+ ::aidl::android::media::TranscodingErrorCode in_errorCode) override {
+ append(Failed(mClientId, in_jobId), in_errorCode);
return Status::ok();
}
diff --git a/services/mediatranscoding/tests/mediatranscodingservice_real_tests.cpp b/services/mediatranscoding/tests/mediatranscodingservice_real_tests.cpp
index 8cecfed..c6368a8 100644
--- a/services/mediatranscoding/tests/mediatranscodingservice_real_tests.cpp
+++ b/services/mediatranscoding/tests/mediatranscodingservice_real_tests.cpp
@@ -35,9 +35,13 @@
constexpr int64_t kPaddingUs = 200000;
constexpr int64_t kJobWithPaddingUs = 10000000 + kPaddingUs;
+constexpr int32_t kBitRate = 8 * 1000 * 1000; // 8Mbs
-constexpr const char* kSrcPath =
+constexpr const char* kShortSrcPath =
"/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
+constexpr const char* kLongSrcPath = "/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4";
+
+#define OUTPATH(name) "/data/local/tmp/MediaTranscodingService_" #name ".MP4"
class MediaTranscodingServiceRealTest : public MediaTranscodingServiceTestBase {
public:
@@ -46,16 +50,34 @@
void deleteFile(const char* path) { unlink(path); }
};
-TEST_F(MediaTranscodingServiceRealTest, TestTranscodePassthru) {
+TEST_F(MediaTranscodingServiceRealTest, TestInvalidSource) {
registerMultipleClients();
- const char* dstPath = "/data/local/tmp/MediaTranscodingService_Passthru.MP4";
+ const char* srcPath = "bad_file_uri";
+ const char* dstPath = OUTPATH(TestInvalidSource);
deleteFile(dstPath);
// Submit one job.
- EXPECT_TRUE(submit(mClient1, 0, kSrcPath, dstPath));
+ EXPECT_TRUE(submit(mClient1, 0, srcPath, dstPath, TranscodingJobPriority::kNormal, kBitRate));
+
+ // Check expected error.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Failed(CLIENT(1), 0));
+ EXPECT_EQ(mClientCallback1->getLastError(), TranscodingErrorCode::kErrorIO);
+
+ unregisterMultipleClients();
+}
+
+TEST_F(MediaTranscodingServiceRealTest, TestPassthru) {
+ registerMultipleClients();
+
+ const char* dstPath = OUTPATH(TestPassthru);
+ deleteFile(dstPath);
+
+ // Submit one job.
+ EXPECT_TRUE(submit(mClient1, 0, kShortSrcPath, dstPath));
// Wait for job to finish.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 0));
unregisterMultipleClients();
@@ -64,20 +86,187 @@
TEST_F(MediaTranscodingServiceRealTest, TestTranscodeVideo) {
registerMultipleClients();
- const char* dstPath = "/data/local/tmp/MediaTranscodingService_Video.MP4";
+ const char* dstPath = OUTPATH(TestTranscodeVideo);
deleteFile(dstPath);
- const int32_t kBitRate = 8 * 1000 * 1000; // 8Mbs
// Submit one job.
- EXPECT_TRUE(submit(mClient1, 0, kSrcPath, dstPath, TranscodingJobPriority::kNormal, kBitRate));
+ EXPECT_TRUE(
+ submit(mClient1, 0, kShortSrcPath, dstPath, TranscodingJobPriority::kNormal, kBitRate));
// Wait for job to finish.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 0));
- // TODO: verify output file format.
+ unregisterMultipleClients();
+}
+
+/*
+ * Test cancel immediately after start.
+ */
+TEST_F(MediaTranscodingServiceRealTest, TestCancelImmediately) {
+ registerMultipleClients();
+
+ const char* srcPath0 = kLongSrcPath;
+ const char* srcPath1 = kShortSrcPath;
+ const char* dstPath0 = OUTPATH(TestCancelImmediately_Job0);
+ const char* dstPath1 = OUTPATH(TestCancelImmediately_Job1);
+
+ deleteFile(dstPath0);
+ deleteFile(dstPath1);
+ // Submit one job, should start immediately.
+ EXPECT_TRUE(submit(mClient1, 0, srcPath0, dstPath0, TranscodingJobPriority::kNormal, kBitRate));
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
+ EXPECT_TRUE(getJob(mClient1, 0, srcPath0, dstPath0));
+
+ // Test cancel job immediately, getJob should fail after cancel.
+ EXPECT_TRUE(cancel(mClient1, 0));
+ EXPECT_TRUE(getJob<fail>(mClient1, 0, "", ""));
+
+ // Submit new job, new job should start immediately and finish.
+ EXPECT_TRUE(submit(mClient1, 1, srcPath1, dstPath1, TranscodingJobPriority::kNormal, kBitRate));
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 1));
+ EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 1));
unregisterMultipleClients();
}
+/*
+ * Test cancel in the middle of transcoding.
+ */
+TEST_F(MediaTranscodingServiceRealTest, TestCancelWhileRunning) {
+ registerMultipleClients();
+
+ const char* srcPath0 = kLongSrcPath;
+ const char* srcPath1 = kShortSrcPath;
+ const char* dstPath0 = OUTPATH(TestCancelWhileRunning_Job0);
+ const char* dstPath1 = OUTPATH(TestCancelWhileRunning_Job1);
+
+ deleteFile(dstPath0);
+ deleteFile(dstPath1);
+ // Submit two jobs, job 0 should start immediately, job 1 should be queued.
+ EXPECT_TRUE(submit(mClient1, 0, srcPath0, dstPath0, TranscodingJobPriority::kNormal, kBitRate));
+ EXPECT_TRUE(submit(mClient1, 1, srcPath1, dstPath1, TranscodingJobPriority::kNormal, kBitRate));
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
+ EXPECT_TRUE(getJob(mClient1, 0, srcPath0, dstPath0));
+ EXPECT_TRUE(getJob(mClient1, 1, srcPath1, dstPath1));
+
+ // Job 0 (longtest) shouldn't finish in 1 seconds.
+ EXPECT_EQ(mClientCallback1->pop(1000000), EventTracker::NoEvent);
+
+ // Now cancel job 0. Job 1 should start immediately and finish.
+ EXPECT_TRUE(cancel(mClient1, 0));
+ EXPECT_TRUE(getJob<fail>(mClient1, 0, "", ""));
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 1));
+ EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 1));
+
+ unregisterMultipleClients();
+}
+
+TEST_F(MediaTranscodingServiceRealTest, TestPauseResumeSingleClient) {
+ registerMultipleClients();
+
+ const char* srcPath0 = kLongSrcPath;
+ const char* srcPath1 = kShortSrcPath;
+ const char* dstPath0 = OUTPATH(TestPauseResumeSingleClient_Job0);
+ const char* dstPath1 = OUTPATH(TestPauseResumeSingleClient_Job1);
+ deleteFile(dstPath0);
+ deleteFile(dstPath1);
+
+ // Submit one offline job, should start immediately.
+ EXPECT_TRUE(submit(mClient1, 0, srcPath0, dstPath0, TranscodingJobPriority::kUnspecified,
+ kBitRate));
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
+ // Test get job after starts.
+ EXPECT_TRUE(getJob(mClient1, 0, srcPath0, dstPath0));
+
+ // Submit one realtime job.
+ EXPECT_TRUE(submit(mClient1, 1, srcPath1, dstPath1, TranscodingJobPriority::kNormal, kBitRate));
+
+ // Offline job should pause.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Pause(CLIENT(1), 0));
+ EXPECT_TRUE(getJob(mClient1, 0, srcPath0, dstPath0));
+
+ // Realtime job should start immediately, and run to finish.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 1));
+ EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 1));
+
+ // Test get job after finish fails.
+ EXPECT_TRUE(getJob<fail>(mClient1, 1, "", ""));
+
+ // Then offline job should resume.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Resume(CLIENT(1), 0));
+ // Test get job after resume.
+ EXPECT_TRUE(getJob(mClient1, 0, srcPath0, dstPath0));
+
+ // Offline job should finish.
+ EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 0));
+ // Test get job after finish fails.
+ EXPECT_TRUE(getJob<fail>(mClient1, 0, "", ""));
+
+ unregisterMultipleClients();
+}
+
+/*
+ * Basic test for pause/resume with two clients, with one job each.
+ * Top app's job should preempt the other app's job.
+ */
+TEST_F(MediaTranscodingServiceRealTest, TestPauseResumeMultiClients) {
+ ALOGD("TestPauseResumeMultiClients starting...");
+
+ EXPECT_TRUE(ShellHelper::RunCmd("input keyevent KEYCODE_WAKEUP"));
+ EXPECT_TRUE(ShellHelper::RunCmd("wm dismiss-keyguard"));
+ EXPECT_TRUE(ShellHelper::Stop(kClientPackageA));
+ EXPECT_TRUE(ShellHelper::Stop(kClientPackageB));
+ EXPECT_TRUE(ShellHelper::Stop(kClientPackageC));
+
+ registerMultipleClients();
+
+ const char* srcPath0 = kLongSrcPath;
+ const char* srcPath1 = kShortSrcPath;
+ const char* dstPath0 = OUTPATH(TestPauseResumeMultiClients_Client0);
+ const char* dstPath1 = OUTPATH(TestPauseResumeMultiClients_Client1);
+ deleteFile(dstPath0);
+ deleteFile(dstPath1);
+
+ ALOGD("Moving app A to top...");
+ EXPECT_TRUE(ShellHelper::Start(kClientPackageA, kTestActivityName));
+
+ // Submit job to Client1.
+ ALOGD("Submitting job to client1 (app A) ...");
+ EXPECT_TRUE(submit(mClient1, 0, srcPath0, dstPath0, TranscodingJobPriority::kNormal, kBitRate));
+
+ // Client1's job should start immediately.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Start(CLIENT(1), 0));
+
+ ALOGD("Moving app B to top...");
+ EXPECT_TRUE(ShellHelper::Start(kClientPackageB, kTestActivityName));
+
+ // Client1's job should continue to run, since Client2 (app B) doesn't have any job.
+ EXPECT_EQ(mClientCallback1->pop(1000000), EventTracker::NoEvent);
+
+ // Submit job to Client2.
+ ALOGD("Submitting job to client2 (app B) ...");
+ EXPECT_TRUE(submit(mClient2, 0, srcPath1, dstPath1, TranscodingJobPriority::kNormal, kBitRate));
+
+ // Client1's job should pause, client2's job should start.
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Pause(CLIENT(1), 0));
+ EXPECT_EQ(mClientCallback2->pop(kPaddingUs), EventTracker::Start(CLIENT(2), 0));
+
+ // Client2's job should finish, then Client1's job should resume.
+ EXPECT_EQ(mClientCallback2->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(2), 0));
+ EXPECT_EQ(mClientCallback1->pop(kPaddingUs), EventTracker::Resume(CLIENT(1), 0));
+
+ // Client1's job should finish.
+ EXPECT_EQ(mClientCallback1->pop(kJobWithPaddingUs), EventTracker::Finished(CLIENT(1), 0));
+
+ unregisterMultipleClients();
+
+ EXPECT_TRUE(ShellHelper::Stop(kClientPackageA));
+ EXPECT_TRUE(ShellHelper::Stop(kClientPackageB));
+ EXPECT_TRUE(ShellHelper::Stop(kClientPackageC));
+
+ ALOGD("TestPauseResumeMultiClients finished.");
+}
+
} // namespace media
} // namespace android
diff --git a/services/mediatranscoding/tests/mediatranscodingservice_simulated_tests.cpp b/services/mediatranscoding/tests/mediatranscodingservice_simulated_tests.cpp
index 309e39d..42b5877 100644
--- a/services/mediatranscoding/tests/mediatranscodingservice_simulated_tests.cpp
+++ b/services/mediatranscoding/tests/mediatranscodingservice_simulated_tests.cpp
@@ -56,7 +56,6 @@
constexpr int64_t kJobWithPaddingUs = SimulatedTranscoder::kJobDurationUs + kPaddingUs;
constexpr const char* kClientOpPackageName = "TestClientPackage";
-constexpr const char* kTestActivityName = "/com.android.tests.transcoding.MainActivity";
class MediaTranscodingServiceSimulatedTest : public MediaTranscodingServiceTestBase {
public: