Register resource observer and handle resource lost.
bug: 168307955
bug: 154733526
Change-Id: I2d5e39ce23bc873dd3bac090e3f7aa1124d0a579
diff --git a/media/libmediatranscoding/Android.bp b/media/libmediatranscoding/Android.bp
index 2e66df7..128d0d8 100644
--- a/media/libmediatranscoding/Android.bp
+++ b/media/libmediatranscoding/Android.bp
@@ -54,6 +54,7 @@
srcs: [
"TranscodingClientManager.cpp",
"TranscodingJobScheduler.cpp",
+ "TranscodingResourcePolicy.cpp",
"TranscodingUidPolicy.cpp",
"TranscoderWrapper.cpp",
],
@@ -67,12 +68,16 @@
"libbinder",
"libmediandk",
],
+ export_shared_lib_headers: [
+ "libmediandk",
+ ],
export_include_dirs: ["include"],
static_libs: [
"mediatranscoding_aidl_interface-ndk_platform",
"resourcemanager_aidl_interface-ndk_platform",
+ "resourceobserver_aidl_interface-ndk_platform",
],
cflags: [
diff --git a/media/libmediatranscoding/TranscoderWrapper.cpp b/media/libmediatranscoding/TranscoderWrapper.cpp
index bd03671..8062fcf 100644
--- a/media/libmediatranscoding/TranscoderWrapper.cpp
+++ b/media/libmediatranscoding/TranscoderWrapper.cpp
@@ -89,26 +89,40 @@
}
//static
-const char* TranscoderWrapper::toString(Event::Type type) {
- switch (type) {
+std::string TranscoderWrapper::toString(const Event& event) {
+ std::string typeStr;
+ switch (event.type) {
case Event::Start:
- return "Start";
- case Event::Pause:
- return "Pause";
- case Event::Resume:
- return "Resume";
- case Event::Stop:
- return "Stop";
- case Event::Finish:
- return "Finish";
- case Event::Error:
- return "Error";
- case Event::Progress:
- return "Progress";
- default:
+ 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)";
}
- return "(unknown)";
+ std::string result;
+ result = "job {" + std::to_string(event.clientId) + "," + std::to_string(event.jobId) +
+ "}: " + typeStr;
+ if (event.type == Event::Error || event.type == Event::Progress) {
+ result += " " + std::to_string(event.arg);
+ }
+ return result;
}
class TranscoderWrapper::CallbackImpl : public MediaTranscoder::CallbackInterface {
@@ -128,7 +142,7 @@
media_status_t error) override {
auto owner = mOwner.lock();
if (owner != nullptr) {
- owner->onError(mClientId, mJobId, toTranscodingError(error));
+ owner->onError(mClientId, mJobId, error);
}
}
@@ -160,20 +174,41 @@
mCallback = cb;
}
+static bool isResourceError(media_status_t err) {
+ return err == AMEDIACODEC_ERROR_RECLAIMED || err == AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE;
+}
+
+void TranscoderWrapper::reportError(ClientIdType clientId, JobIdType jobId, 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(JobKeyType(clientId, jobId));
+ if (it == mPausedStateMap.end()) {
+ mPausedStateMap.emplace(JobKeyType(clientId, jobId),
+ std::shared_ptr<const Parcel>());
+ }
+
+ callback->onResourceLost();
+ } else {
+ callback->onError(clientId, jobId, toTranscodingError(err));
+ }
+ }
+}
+
void TranscoderWrapper::start(ClientIdType clientId, JobIdType jobId,
const TranscodingRequestParcel& request,
const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
queueEvent(Event::Start, clientId, jobId, [=] {
- TranscodingErrorCode err = handleStart(clientId, jobId, request, clientCb);
+ media_status_t err = handleStart(clientId, jobId, request, clientCb);
- auto callback = mCallback.lock();
- if (err != TranscodingErrorCode::kNoError) {
+ if (err != AMEDIA_OK) {
cleanup();
-
- if (callback != nullptr) {
- callback->onError(clientId, jobId, err);
- }
+ reportError(clientId, jobId, err);
} else {
+ auto callback = mCallback.lock();
if (callback != nullptr) {
callback->onStarted(clientId, jobId);
}
@@ -183,15 +218,15 @@
void TranscoderWrapper::pause(ClientIdType clientId, JobIdType jobId) {
queueEvent(Event::Pause, clientId, jobId, [=] {
- TranscodingErrorCode err = handlePause(clientId, jobId);
+ media_status_t err = handlePause(clientId, jobId);
cleanup();
- auto callback = mCallback.lock();
- if (callback != nullptr) {
- if (err != TranscodingErrorCode::kNoError) {
- callback->onError(clientId, jobId, err);
- } else {
+ if (err != AMEDIA_OK) {
+ reportError(clientId, jobId, err);
+ } else {
+ auto callback = mCallback.lock();
+ if (callback != nullptr) {
callback->onPaused(clientId, jobId);
}
}
@@ -202,16 +237,13 @@
const TranscodingRequestParcel& request,
const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
queueEvent(Event::Resume, clientId, jobId, [=] {
- TranscodingErrorCode err = handleResume(clientId, jobId, request, clientCb);
+ media_status_t err = handleResume(clientId, jobId, request, clientCb);
- auto callback = mCallback.lock();
- if (err != TranscodingErrorCode::kNoError) {
+ if (err != AMEDIA_OK) {
cleanup();
-
- if (callback != nullptr) {
- callback->onError(clientId, jobId, err);
- }
+ reportError(clientId, jobId, err);
} else {
+ auto callback = mCallback.lock();
if (callback != nullptr) {
callback->onResumed(clientId, jobId);
}
@@ -225,7 +257,7 @@
// Cancelling the currently running job.
media_status_t err = mTranscoder->cancel();
if (err != AMEDIA_OK) {
- ALOGE("failed to stop transcoder: %d", err);
+ ALOGW("failed to stop transcoder: %d", err);
} else {
ALOGI("transcoder stopped");
}
@@ -251,41 +283,43 @@
});
}
-void TranscoderWrapper::onError(ClientIdType clientId, JobIdType jobId,
- TranscodingErrorCode error) {
- queueEvent(Event::Error, clientId, jobId, [=] {
- if (mTranscoder != nullptr && clientId == mCurrentClientId && jobId == mCurrentJobId) {
- cleanup();
- }
-
- auto callback = mCallback.lock();
- if (callback != nullptr) {
- callback->onError(clientId, jobId, error);
- }
- });
+void TranscoderWrapper::onError(ClientIdType clientId, JobIdType jobId, media_status_t error) {
+ queueEvent(
+ Event::Error, clientId, jobId,
+ [=] {
+ if (mTranscoder != nullptr && clientId == mCurrentClientId &&
+ jobId == mCurrentJobId) {
+ cleanup();
+ }
+ reportError(clientId, jobId, error);
+ },
+ error);
}
void TranscoderWrapper::onProgress(ClientIdType clientId, JobIdType jobId, int32_t progress) {
- queueEvent(Event::Progress, clientId, jobId, [=] {
- auto callback = mCallback.lock();
- if (callback != nullptr) {
- callback->onProgressUpdate(clientId, jobId, progress);
- }
- });
+ queueEvent(
+ Event::Progress, clientId, jobId,
+ [=] {
+ auto callback = mCallback.lock();
+ if (callback != nullptr) {
+ callback->onProgressUpdate(clientId, jobId, progress);
+ }
+ },
+ progress);
}
-TranscodingErrorCode TranscoderWrapper::setupTranscoder(
+media_status_t TranscoderWrapper::setupTranscoder(
ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
const std::shared_ptr<ITranscodingClientCallback>& clientCb,
const std::shared_ptr<const Parcel>& pausedState) {
if (clientCb == nullptr) {
ALOGE("client callback is null");
- return TranscodingErrorCode::kInvalidParameter;
+ return AMEDIA_ERROR_INVALID_PARAMETER;
}
if (mTranscoder != nullptr) {
ALOGE("transcoder already running");
- return TranscodingErrorCode::kInvalidOperation;
+ return AMEDIA_ERROR_INVALID_OPERATION;
}
Status status;
@@ -293,7 +327,7 @@
status = clientCb->openFileDescriptor(request.sourceFilePath, "r", &srcFd);
if (!status.isOk() || srcFd.get() < 0) {
ALOGE("failed to open source");
- return TranscodingErrorCode::kErrorIO;
+ return AMEDIA_ERROR_IO;
}
// Open dest file with "rw", as the transcoder could potentially reuse part of it
@@ -302,7 +336,7 @@
status = clientCb->openFileDescriptor(request.destinationFilePath, "rw", &dstFd);
if (!status.isOk() || dstFd.get() < 0) {
ALOGE("failed to open destination");
- return TranscodingErrorCode::kErrorIO;
+ return AMEDIA_ERROR_IO;
}
mCurrentClientId = clientId;
@@ -311,19 +345,19 @@
mTranscoder = MediaTranscoder::create(mTranscoderCb, pausedState);
if (mTranscoder == nullptr) {
ALOGE("failed to create transcoder");
- return TranscodingErrorCode::kUnknown;
+ return AMEDIA_ERROR_UNKNOWN;
}
media_status_t err = mTranscoder->configureSource(srcFd.get());
if (err != AMEDIA_OK) {
ALOGE("failed to configure source: %d", err);
- return toTranscodingError(err);
+ return err;
}
std::vector<std::shared_ptr<AMediaFormat>> trackFormats = mTranscoder->getTrackFormats();
if (trackFormats.size() == 0) {
ALOGE("failed to get track formats!");
- return TranscodingErrorCode::kMalformed;
+ return AMEDIA_ERROR_MALFORMED;
}
for (int i = 0; i < trackFormats.size(); ++i) {
@@ -341,43 +375,43 @@
}
if (err != AMEDIA_OK) {
ALOGE("failed to configure track format for track %d: %d", i, err);
- return toTranscodingError(err);
+ return err;
}
}
err = mTranscoder->configureDestination(dstFd.get());
if (err != AMEDIA_OK) {
ALOGE("failed to configure dest: %d", err);
- return toTranscodingError(err);
+ return err;
}
- return TranscodingErrorCode::kNoError;
+ return AMEDIA_OK;
}
-TranscodingErrorCode TranscoderWrapper::handleStart(
+media_status_t 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: setting up transcoder for start", __FUNCTION__);
+ media_status_t err = setupTranscoder(clientId, jobId, request, clientCb);
+ if (err != AMEDIA_OK) {
ALOGI("%s: failed to setup transcoder", __FUNCTION__);
return err;
}
- media_status_t status = mTranscoder->start();
- if (status != AMEDIA_OK) {
+ err = mTranscoder->start();
+ if (err != AMEDIA_OK) {
ALOGE("%s: failed to start transcoder: %d", __FUNCTION__, err);
- return toTranscodingError(status);
+ return err;
}
ALOGI("%s: transcoder started", __FUNCTION__);
- return TranscodingErrorCode::kNoError;
+ return AMEDIA_OK;
}
-TranscodingErrorCode TranscoderWrapper::handlePause(ClientIdType clientId, JobIdType jobId) {
+media_status_t TranscoderWrapper::handlePause(ClientIdType clientId, JobIdType jobId) {
if (mTranscoder == nullptr) {
ALOGE("%s: transcoder is not running", __FUNCTION__);
- return TranscodingErrorCode::kInvalidOperation;
+ return AMEDIA_ERROR_INVALID_OPERATION;
}
if (clientId != mCurrentClientId || jobId != mCurrentJobId) {
@@ -385,19 +419,21 @@
(long long)clientId, jobId, (long long)mCurrentClientId, mCurrentJobId);
}
+ ALOGI("%s: pausing transcoder", __FUNCTION__);
+
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);
+ return err;
}
mPausedStateMap[JobKeyType(clientId, jobId)] = pauseStates;
ALOGI("%s: transcoder paused", __FUNCTION__);
- return TranscodingErrorCode::kNoError;
+ return AMEDIA_OK;
}
-TranscodingErrorCode TranscoderWrapper::handleResume(
+media_status_t TranscoderWrapper::handleResume(
ClientIdType clientId, JobIdType jobId, const TranscodingRequestParcel& request,
const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
std::shared_ptr<const Parcel> pausedState;
@@ -407,24 +443,24 @@
mPausedStateMap.erase(it);
} else {
ALOGE("%s: can't find paused state", __FUNCTION__);
- return TranscodingErrorCode::kInvalidOperation;
+ return AMEDIA_ERROR_INVALID_OPERATION;
}
- 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__);
+ ALOGI("%s: setting up transcoder for resume", __FUNCTION__);
+ media_status_t err = setupTranscoder(clientId, jobId, request, clientCb, pausedState);
+ if (err != AMEDIA_OK) {
+ ALOGE("%s: failed to setup transcoder: %d", __FUNCTION__, err);
return err;
}
- media_status_t status = mTranscoder->resume();
- if (status != AMEDIA_OK) {
+ err = mTranscoder->resume();
+ if (err != AMEDIA_OK) {
ALOGE("%s: failed to resume transcoder: %d", __FUNCTION__, err);
- return toTranscodingError(status);
+ return err;
}
ALOGI("%s: transcoder resumed", __FUNCTION__);
- return TranscodingErrorCode::kNoError;
+ return AMEDIA_OK;
}
void TranscoderWrapper::cleanup() {
@@ -435,12 +471,10 @@
}
void TranscoderWrapper::queueEvent(Event::Type type, ClientIdType clientId, JobIdType jobId,
- const std::function<void()> runnable) {
- ALOGV("%s: job {%lld, %d}: %s", __FUNCTION__, (long long)clientId, jobId, toString(type));
-
+ const std::function<void()> runnable, int32_t arg) {
std::scoped_lock lock{mLock};
- mQueue.push_back({type, clientId, jobId, runnable});
+ mQueue.push_back({type, clientId, jobId, runnable, arg});
mCondition.notify_one();
}
@@ -457,8 +491,7 @@
Event event = *mQueue.begin();
mQueue.pop_front();
- ALOGD("%s: job {%lld, %d}: %s", __FUNCTION__, (long long)event.clientId, event.jobId,
- toString(event.type));
+ ALOGD("%s: %s", __FUNCTION__, toString(event).c_str());
lock.unlock();
event.runnable();
diff --git a/media/libmediatranscoding/TranscodingJobScheduler.cpp b/media/libmediatranscoding/TranscodingJobScheduler.cpp
index 3e4f319..24ac682 100644
--- a/media/libmediatranscoding/TranscodingJobScheduler.cpp
+++ b/media/libmediatranscoding/TranscodingJobScheduler.cpp
@@ -38,8 +38,13 @@
TranscodingJobScheduler::TranscodingJobScheduler(
const std::shared_ptr<TranscoderInterface>& transcoder,
- const std::shared_ptr<UidPolicyInterface>& uidPolicy)
- : mTranscoder(transcoder), mUidPolicy(uidPolicy), mCurrentJob(nullptr), mResourceLost(false) {
+ const std::shared_ptr<UidPolicyInterface>& uidPolicy,
+ const std::shared_ptr<ResourcePolicyInterface>& resourcePolicy)
+ : mTranscoder(transcoder),
+ mUidPolicy(uidPolicy),
+ mResourcePolicy(resourcePolicy),
+ mCurrentJob(nullptr),
+ mResourceLost(false) {
// Only push empty offline queue initially. Realtime queues are added when requests come in.
mUidSortedList.push_back(OFFLINE_UID);
mOfflineUidIterator = mUidSortedList.begin();
@@ -398,15 +403,24 @@
}
void TranscodingJobScheduler::onResourceLost() {
- ALOGV("%s", __FUNCTION__);
+ ALOGI("%s", __FUNCTION__);
std::scoped_lock lock{mLock};
+ if (mResourceLost) {
+ return;
+ }
+
// If we receive a resource loss event, the TranscoderLibrary already paused
// the transcoding, so we don't need to call onPaused to notify it to pause.
// Only need to update the job state here.
if (mCurrentJob != nullptr && mCurrentJob->state == Job::RUNNING) {
mCurrentJob->state = Job::PAUSED;
+ // Notify the client as a paused event.
+ auto clientCallback = mCurrentJob->callback.lock();
+ if (clientCallback != nullptr) {
+ clientCallback->onTranscodingPaused(mCurrentJob->key.second);
+ }
}
mResourceLost = true;
@@ -439,10 +453,14 @@
}
void TranscodingJobScheduler::onResourceAvailable() {
- ALOGV("%s", __FUNCTION__);
-
std::scoped_lock lock{mLock};
+ if (!mResourceLost) {
+ return;
+ }
+
+ ALOGI("%s", __FUNCTION__);
+
mResourceLost = false;
updateCurrentJob_l();
diff --git a/media/libmediatranscoding/TranscodingResourcePolicy.cpp b/media/libmediatranscoding/TranscodingResourcePolicy.cpp
new file mode 100644
index 0000000..4fd8338
--- /dev/null
+++ b/media/libmediatranscoding/TranscodingResourcePolicy.cpp
@@ -0,0 +1,169 @@
+/*
+ * 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 "TranscodingResourcePolicy"
+
+#include <aidl/android/media/BnResourceObserver.h>
+#include <aidl/android/media/IResourceObserverService.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <binder/IServiceManager.h>
+#include <media/TranscodingResourcePolicy.h>
+#include <utils/Log.h>
+
+namespace android {
+
+using Status = ::ndk::ScopedAStatus;
+using ::aidl::android::media::BnResourceObserver;
+using ::aidl::android::media::IResourceObserverService;
+using ::aidl::android::media::MediaObservableEvent;
+using ::aidl::android::media::MediaObservableFilter;
+using ::aidl::android::media::MediaObservableParcel;
+using ::aidl::android::media::MediaObservableType;
+
+static std::string toString(const MediaObservableParcel& observable) {
+ return "{" + ::aidl::android::media::toString(observable.type) + ", " +
+ std::to_string(observable.value) + "}";
+}
+
+struct TranscodingResourcePolicy::ResourceObserver : public BnResourceObserver {
+ explicit ResourceObserver(TranscodingResourcePolicy* owner) : mOwner(owner), mPid(getpid()) {}
+
+ // IResourceObserver
+ ::ndk::ScopedAStatus onStatusChanged(
+ MediaObservableEvent event, int32_t uid, int32_t pid,
+ const std::vector<MediaObservableParcel>& observables) override {
+ ALOGD("%s: %s, uid %d, pid %d, %s", __FUNCTION__,
+ ::aidl::android::media::toString(event).c_str(), uid, pid,
+ toString(observables[0]).c_str());
+
+ // Only report kIdle event for codec resources from other processes.
+ if (((uint64_t)event & (uint64_t)MediaObservableEvent::kIdle) != 0 && (pid != mPid)) {
+ for (auto& observable : observables) {
+ if (observable.type == MediaObservableType::kVideoSecureCodec ||
+ observable.type == MediaObservableType::kVideoNonSecureCodec) {
+ mOwner->onResourceAvailable();
+ break;
+ }
+ }
+ }
+ return ::ndk::ScopedAStatus::ok();
+ }
+
+ TranscodingResourcePolicy* mOwner;
+ const pid_t mPid;
+};
+
+// static
+void TranscodingResourcePolicy::BinderDiedCallback(void* cookie) {
+ TranscodingResourcePolicy* owner = reinterpret_cast<TranscodingResourcePolicy*>(cookie);
+ if (owner != nullptr) {
+ owner->unregisterSelf();
+ }
+ // TODO(chz): retry to connecting to IResourceObserverService after failure.
+ // Also need to have back-up logic if IResourceObserverService is offline for
+ // Prolonged period of time. A possible alternative could be, during period where
+ // IResourceObserverService is not available, trigger onResourceAvailable() everytime
+ // when top uid changes (in hope that'll free up some codec instances that we could
+ // reclaim).
+}
+
+TranscodingResourcePolicy::TranscodingResourcePolicy()
+ : mRegistered(false), mDeathRecipient(AIBinder_DeathRecipient_new(BinderDiedCallback)) {
+ registerSelf();
+}
+
+TranscodingResourcePolicy::~TranscodingResourcePolicy() {
+ unregisterSelf();
+}
+
+void TranscodingResourcePolicy::registerSelf() {
+ ALOGI("TranscodingResourcePolicy: registerSelf");
+
+ ::ndk::SpAIBinder binder(AServiceManager_getService("media.resource_observer"));
+
+ std::scoped_lock lock{mRegisteredLock};
+
+ if (mRegistered) {
+ return;
+ }
+
+ // TODO(chz): retry to connecting to IResourceObserverService after failure.
+ mService = IResourceObserverService::fromBinder(binder);
+ if (mService == nullptr) {
+ ALOGE("Failed to get IResourceObserverService");
+ return;
+ }
+
+ // Only register filters for codec resource available.
+ mObserver = ::ndk::SharedRefBase::make<ResourceObserver>(this);
+ std::vector<MediaObservableFilter> filters = {
+ {MediaObservableType::kVideoSecureCodec, MediaObservableEvent::kIdle},
+ {MediaObservableType::kVideoNonSecureCodec, MediaObservableEvent::kIdle}};
+
+ Status status = mService->registerObserver(mObserver, filters);
+ if (!status.isOk()) {
+ ALOGE("failed to register: error %d", status.getServiceSpecificError());
+ mService = nullptr;
+ mObserver = nullptr;
+ return;
+ }
+
+ AIBinder_linkToDeath(binder.get(), mDeathRecipient.get(), reinterpret_cast<void*>(this));
+
+ ALOGD("@@@ registered observer");
+ mRegistered = true;
+}
+
+void TranscodingResourcePolicy::unregisterSelf() {
+ ALOGI("TranscodingResourcePolicy: unregisterSelf");
+
+ std::scoped_lock lock{mRegisteredLock};
+
+ if (!mRegistered) {
+ return;
+ }
+
+ ::ndk::SpAIBinder binder = mService->asBinder();
+ if (binder.get() != nullptr) {
+ Status status = mService->unregisterObserver(mObserver);
+ AIBinder_unlinkToDeath(binder.get(), mDeathRecipient.get(), reinterpret_cast<void*>(this));
+ }
+
+ mService = nullptr;
+ mObserver = nullptr;
+ mRegistered = false;
+}
+
+void TranscodingResourcePolicy::setCallback(
+ const std::shared_ptr<ResourcePolicyCallbackInterface>& cb) {
+ std::scoped_lock lock{mCallbackLock};
+ mResourcePolicyCallback = cb;
+}
+
+void TranscodingResourcePolicy::onResourceAvailable() {
+ std::shared_ptr<ResourcePolicyCallbackInterface> cb;
+ {
+ std::scoped_lock lock{mCallbackLock};
+ cb = mResourcePolicyCallback.lock();
+ }
+
+ if (cb != nullptr) {
+ cb->onResourceAvailable();
+ }
+}
+} // namespace android
diff --git a/media/libmediatranscoding/include/media/ResourcePolicyInterface.h b/media/libmediatranscoding/include/media/ResourcePolicyInterface.h
new file mode 100644
index 0000000..8bd7d6b
--- /dev/null
+++ b/media/libmediatranscoding/include/media/ResourcePolicyInterface.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_MEDIA_RESOURCE_POLICY_INTERFACE_H
+#define ANDROID_MEDIA_RESOURCE_POLICY_INTERFACE_H
+#include <memory>
+namespace android {
+
+class ResourcePolicyCallbackInterface;
+
+// Interface for the JobScheduler to control the resource status updates.
+class ResourcePolicyInterface {
+public:
+ // Set the associated callback interface to send the events when resource
+ // status changes. (Set to nullptr will stop the updates.)
+ virtual void setCallback(const std::shared_ptr<ResourcePolicyCallbackInterface>& cb) = 0;
+
+protected:
+ virtual ~ResourcePolicyInterface() = default;
+};
+
+// Interface for notifying the JobScheduler of a change in resource status.
+class ResourcePolicyCallbackInterface {
+public:
+ // Called when codec resources become available. The scheduler may use this
+ // as a signal to attempt restart transcoding jobs that were previously
+ // paused due to temporary resource loss.
+ virtual void onResourceAvailable() = 0;
+
+protected:
+ virtual ~ResourcePolicyCallbackInterface() = default;
+};
+
+} // namespace android
+#endif // ANDROID_MEDIA_RESOURCE_POLICY_INTERFACE_H
diff --git a/media/libmediatranscoding/include/media/TranscoderWrapper.h b/media/libmediatranscoding/include/media/TranscoderWrapper.h
index a4c92c5..c956042 100644
--- a/media/libmediatranscoding/include/media/TranscoderWrapper.h
+++ b/media/libmediatranscoding/include/media/TranscoderWrapper.h
@@ -18,6 +18,7 @@
#define ANDROID_TRANSCODER_WRAPPER_H
#include <android-base/thread_annotations.h>
+#include <media/NdkMediaError.h>
#include <media/TranscoderInterface.h>
#include <list>
@@ -55,6 +56,7 @@
ClientIdType clientId;
JobIdType jobId;
std::function<void()> runnable;
+ int32_t arg;
};
using JobKeyType = std::pair<ClientIdType, JobIdType>;
@@ -68,26 +70,27 @@
ClientIdType mCurrentClientId;
JobIdType mCurrentJobId;
- static const char* toString(Event::Type type);
+ static std::string toString(const Event& event);
void onFinish(ClientIdType clientId, JobIdType jobId);
- void onError(ClientIdType clientId, JobIdType jobId, TranscodingErrorCode error);
+ void onError(ClientIdType clientId, JobIdType jobId, media_status_t status);
void onProgress(ClientIdType clientId, JobIdType jobId, int32_t progress);
- 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);
+ media_status_t handleStart(ClientIdType clientId, JobIdType jobId,
+ const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& callback);
+ media_status_t handlePause(ClientIdType clientId, JobIdType jobId);
+ media_status_t handleResume(ClientIdType clientId, JobIdType jobId,
+ const TranscodingRequestParcel& request,
+ const std::shared_ptr<ITranscodingClientCallback>& callback);
+ media_status_t 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 reportError(ClientIdType clientId, JobIdType jobId, media_status_t err);
void queueEvent(Event::Type type, ClientIdType clientId, JobIdType jobId,
- const std::function<void()> runnable);
+ const std::function<void()> runnable, int32_t arg = 0);
void threadLoop();
};
diff --git a/media/libmediatranscoding/include/media/TranscodingJobScheduler.h b/media/libmediatranscoding/include/media/TranscodingJobScheduler.h
index 5ccadad..8f5e2aa 100644
--- a/media/libmediatranscoding/include/media/TranscodingJobScheduler.h
+++ b/media/libmediatranscoding/include/media/TranscodingJobScheduler.h
@@ -18,6 +18,7 @@
#define ANDROID_MEDIA_TRANSCODING_JOB_SCHEDULER_H
#include <aidl/android/media/TranscodingJobPriority.h>
+#include <media/ResourcePolicyInterface.h>
#include <media/SchedulerClientInterface.h>
#include <media/TranscoderInterface.h>
#include <media/TranscodingRequest.h>
@@ -34,7 +35,8 @@
class TranscodingJobScheduler : public UidPolicyCallbackInterface,
public SchedulerClientInterface,
- public TranscoderCallbackInterface {
+ public TranscoderCallbackInterface,
+ public ResourcePolicyCallbackInterface {
public:
virtual ~TranscodingJobScheduler();
@@ -58,9 +60,12 @@
// UidPolicyCallbackInterface
void onTopUidsChanged(const std::unordered_set<uid_t>& uids) override;
- void onResourceAvailable() override;
// ~UidPolicyCallbackInterface
+ // ResourcePolicyCallbackInterface
+ void onResourceAvailable() override;
+ // ~ResourcePolicyCallbackInterface
+
private:
friend class MediaTranscodingService;
friend class TranscodingJobSchedulerTest;
@@ -96,13 +101,15 @@
std::shared_ptr<TranscoderInterface> mTranscoder;
std::shared_ptr<UidPolicyInterface> mUidPolicy;
+ std::shared_ptr<ResourcePolicyInterface> mResourcePolicy;
Job* mCurrentJob;
bool mResourceLost;
// Only allow MediaTranscodingService and unit tests to instantiate.
TranscodingJobScheduler(const std::shared_ptr<TranscoderInterface>& transcoder,
- const std::shared_ptr<UidPolicyInterface>& uidPolicy);
+ const std::shared_ptr<UidPolicyInterface>& uidPolicy,
+ const std::shared_ptr<ResourcePolicyInterface>& resourcePolicy);
Job* getTopJob_l();
void updateCurrentJob_l();
diff --git a/media/libmediatranscoding/include/media/TranscodingResourcePolicy.h b/media/libmediatranscoding/include/media/TranscodingResourcePolicy.h
new file mode 100644
index 0000000..0836eda
--- /dev/null
+++ b/media/libmediatranscoding/include/media/TranscodingResourcePolicy.h
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_MEDIA_TRANSCODING_RESOURCE_POLICY_H
+#define ANDROID_MEDIA_TRANSCODING_RESOURCE_POLICY_H
+
+#include <android/binder_auto_utils.h>
+#include <media/ResourcePolicyInterface.h>
+#include <utils/Condition.h>
+
+#include <mutex>
+namespace aidl {
+namespace android {
+namespace media {
+class IResourceObserverService;
+}
+} // namespace android
+} // namespace aidl
+
+namespace android {
+
+using ::aidl::android::media::IResourceObserverService;
+
+class TranscodingResourcePolicy : public ResourcePolicyInterface {
+public:
+ explicit TranscodingResourcePolicy();
+ ~TranscodingResourcePolicy();
+
+ void setCallback(const std::shared_ptr<ResourcePolicyCallbackInterface>& cb) override;
+
+private:
+ struct ResourceObserver;
+ mutable std::mutex mRegisteredLock;
+ bool mRegistered GUARDED_BY(mRegisteredLock);
+ std::shared_ptr<IResourceObserverService> mService GUARDED_BY(mRegisteredLock);
+ std::shared_ptr<ResourceObserver> mObserver;
+
+ mutable std::mutex mCallbackLock;
+ std::weak_ptr<ResourcePolicyCallbackInterface> mResourcePolicyCallback
+ GUARDED_BY(mCallbackLock);
+
+ ::ndk::ScopedAIBinder_DeathRecipient mDeathRecipient;
+
+ static void BinderDiedCallback(void* cookie);
+
+ void registerSelf();
+ void unregisterSelf();
+ void onResourceAvailable();
+}; // class TranscodingUidPolicy
+
+} // namespace android
+#endif // ANDROID_MEDIA_TRANSCODING_RESOURCE_POLICY_H
diff --git a/media/libmediatranscoding/include/media/UidPolicyInterface.h b/media/libmediatranscoding/include/media/UidPolicyInterface.h
index dc28027..f88c1ed 100644
--- a/media/libmediatranscoding/include/media/UidPolicyInterface.h
+++ b/media/libmediatranscoding/include/media/UidPolicyInterface.h
@@ -41,19 +41,13 @@
virtual ~UidPolicyInterface() = default;
};
-// Interface for notifying the scheduler of a change in uid states or
-// transcoding resource availability.
+// Interface for notifying the scheduler of a change in uid states.
class UidPolicyCallbackInterface {
public:
// Called when the set of uids that's top priority among the uids of interest
// has changed. The receiver of this callback should adjust accordingly.
virtual void onTopUidsChanged(const std::unordered_set<uid_t>& uids) = 0;
- // Called when resources become available for transcoding use. The scheduler
- // may use this as a signal to attempt restart transcoding activity that
- // were previously paused due to temporary resource loss.
- virtual void onResourceAvailable() = 0;
-
protected:
virtual ~UidPolicyCallbackInterface() = default;
};
diff --git a/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp b/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
index d21b595..9b9df87 100644
--- a/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
+++ b/media/libmediatranscoding/tests/TranscodingJobScheduler_tests.cpp
@@ -213,7 +213,8 @@
ALOGI("TranscodingJobSchedulerTest set up");
mTranscoder.reset(new TestTranscoder());
mUidPolicy.reset(new TestUidPolicy());
- mScheduler.reset(new TranscodingJobScheduler(mTranscoder, mUidPolicy));
+ mScheduler.reset(
+ new TranscodingJobScheduler(mTranscoder, mUidPolicy, nullptr /*resourcePolicy*/));
mUidPolicy->setCallback(mScheduler);
// Set priority only, ignore other fields for now.
diff --git a/services/mediatranscoding/MediaTranscodingService.cpp b/services/mediatranscoding/MediaTranscodingService.cpp
index ef7d6d2..977995a 100644
--- a/services/mediatranscoding/MediaTranscodingService.cpp
+++ b/services/mediatranscoding/MediaTranscodingService.cpp
@@ -25,6 +25,7 @@
#include <media/TranscoderWrapper.h>
#include <media/TranscodingClientManager.h>
#include <media/TranscodingJobScheduler.h>
+#include <media/TranscodingResourcePolicy.h>
#include <media/TranscodingUidPolicy.h>
#include <private/android_filesystem_config.h>
#include <utils/Log.h>
@@ -57,11 +58,13 @@
MediaTranscodingService::MediaTranscodingService(
const std::shared_ptr<TranscoderInterface>& transcoder)
: mUidPolicy(new TranscodingUidPolicy()),
- mJobScheduler(new TranscodingJobScheduler(transcoder, mUidPolicy)),
+ mResourcePolicy(new TranscodingResourcePolicy()),
+ mJobScheduler(new TranscodingJobScheduler(transcoder, mUidPolicy, mResourcePolicy)),
mClientManager(new TranscodingClientManager(mJobScheduler)) {
ALOGV("MediaTranscodingService is created");
transcoder->setCallback(mJobScheduler);
mUidPolicy->setCallback(mJobScheduler);
+ mResourcePolicy->setCallback(mJobScheduler);
}
MediaTranscodingService::~MediaTranscodingService() {
diff --git a/services/mediatranscoding/MediaTranscodingService.h b/services/mediatranscoding/MediaTranscodingService.h
index 505239c..2ba3d55 100644
--- a/services/mediatranscoding/MediaTranscodingService.h
+++ b/services/mediatranscoding/MediaTranscodingService.h
@@ -32,6 +32,7 @@
class TranscodingJobScheduler;
class TranscoderInterface;
class UidPolicyInterface;
+class ResourcePolicyInterface;
class MediaTranscodingService : public BnMediaTranscodingService {
public:
@@ -60,6 +61,7 @@
mutable std::mutex mServiceLock;
std::shared_ptr<UidPolicyInterface> mUidPolicy;
+ std::shared_ptr<ResourcePolicyInterface> mResourcePolicy;
std::shared_ptr<TranscodingJobScheduler> mJobScheduler;
std::shared_ptr<TranscodingClientManager> mClientManager;
};
diff --git a/services/mediatranscoding/tests/Android.bp b/services/mediatranscoding/tests/Android.bp
index 364a198..6497685 100644
--- a/services/mediatranscoding/tests/Android.bp
+++ b/services/mediatranscoding/tests/Android.bp
@@ -25,14 +25,6 @@
static_libs: [
"mediatranscoding_aidl_interface-ndk_platform",
],
-}
-
-// MediaTranscodingService unit test using simulated transcoder
-cc_test {
- name: "mediatranscodingservice_simulated_tests",
- defaults: ["mediatranscodingservice_test_defaults"],
-
- srcs: ["mediatranscodingservice_simulated_tests.cpp"],
required: [
"TranscodingUidPolicy_TestAppA",
@@ -41,6 +33,14 @@
],
}
+// MediaTranscodingService unit test using simulated transcoder
+cc_test {
+ name: "mediatranscodingservice_simulated_tests",
+ defaults: ["mediatranscodingservice_test_defaults"],
+
+ srcs: ["mediatranscodingservice_simulated_tests.cpp"],
+}
+
// MediaTranscodingService unit test using real transcoder
cc_test {
name: "mediatranscodingservice_real_tests",
@@ -48,3 +48,11 @@
srcs: ["mediatranscodingservice_real_tests.cpp"],
}
+
+// MediaTranscodingService unit test related to resource management
+cc_test {
+ name: "mediatranscodingservice_resource_tests",
+ defaults: ["mediatranscodingservice_test_defaults"],
+
+ srcs: ["mediatranscodingservice_resource_tests.cpp"],
+}
diff --git a/services/mediatranscoding/tests/MediaTranscodingServiceTestHelper.h b/services/mediatranscoding/tests/MediaTranscodingServiceTestHelper.h
index 53fd7ec..b325c5c 100644
--- a/services/mediatranscoding/tests/MediaTranscodingServiceTestHelper.h
+++ b/services/mediatranscoding/tests/MediaTranscodingServiceTestHelper.h
@@ -168,6 +168,32 @@
return mPoppedEvent;
}
+ bool waitForSpecificEventAndPop(const Event& target, std::list<Event>* outEvents,
+ int64_t timeoutUs = 0) {
+ std::unique_lock lock(mLock);
+
+ auto startTime = std::chrono::system_clock::now();
+
+ std::list<Event>::iterator it;
+ while (((it = std::find(mEventQueue.begin(), mEventQueue.end(), target)) ==
+ mEventQueue.end()) &&
+ timeoutUs > 0) {
+ std::cv_status status = mCondition.wait_for(lock, std::chrono::microseconds(timeoutUs));
+ if (status == std::cv_status::timeout) {
+ break;
+ }
+ std::chrono::microseconds elapsedTime = std::chrono::system_clock::now() - startTime;
+ timeoutUs -= elapsedTime.count();
+ }
+
+ if (it == mEventQueue.end()) {
+ return false;
+ }
+ *outEvents = std::list<Event>(mEventQueue.begin(), std::next(it));
+ mEventQueue.erase(mEventQueue.begin(), std::next(it));
+ return true;
+ }
+
// Push 1 event to back.
void append(const Event& event,
const TranscodingErrorCode err = TranscodingErrorCode::kNoError) {
@@ -186,7 +212,7 @@
mUpdateCount++;
}
- int getUpdateCount(int *lastProgress) {
+ int getUpdateCount(int* lastProgress) {
std::unique_lock lock(mLock);
*lastProgress = mLastProgress;
return mUpdateCount;
diff --git a/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppA.xml b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppA.xml
index 91c14a5..0dff171 100644
--- a/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppA.xml
+++ b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/TestAppA.xml
@@ -28,6 +28,14 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+ <activity android:name="com.android.tests.transcoding.ResourcePolicyTestActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/src/com/android/tests/transcoding/MainActivity.java b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/src/com/android/tests/transcoding/MainActivity.java
index 7295073..b79164d 100644
--- a/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/src/com/android/tests/transcoding/MainActivity.java
+++ b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/src/com/android/tests/transcoding/MainActivity.java
@@ -46,7 +46,7 @@
// Called before subsequent visible lifetimes
// for an activity process.
@Override
- public void onRestart(){
+ public void onRestart() {
super.onRestart();
// Load changes knowing that the Activity has already
// been visible within this process.
@@ -54,14 +54,14 @@
// Called at the start of the visible lifetime.
@Override
- public void onStart(){
+ public void onStart() {
super.onStart();
// Apply any required UI change now that the Activity is visible.
}
// Called at the start of the active lifetime.
@Override
- public void onResume(){
+ public void onResume() {
super.onResume();
// Resume any paused UI updates, threads, or processes required
// by the Activity but suspended when it was inactive.
@@ -80,7 +80,7 @@
// Called at the end of the active lifetime.
@Override
- public void onPause(){
+ public void onPause() {
// Suspend UI updates, threads, or CPU intensive processes
// that don't need to be updated when the Activity isn't
// the active foreground Activity.
@@ -89,7 +89,7 @@
// Called at the end of the visible lifetime.
@Override
- public void onStop(){
+ public void onStop() {
// Suspend remaining UI updates, threads, or processing
// that aren't required when the Activity isn't visible.
// Persist all edits or state changes
@@ -99,10 +99,9 @@
// Sometimes called at the end of the full lifetime.
@Override
- public void onDestroy(){
+ public void onDestroy() {
// Clean up any resources including ending threads,
// closing database connections etc.
super.onDestroy();
}
-
}
diff --git a/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/src/com/android/tests/transcoding/ResourcePolicyTestActivity.java b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/src/com/android/tests/transcoding/ResourcePolicyTestActivity.java
new file mode 100644
index 0000000..c9e2ddb
--- /dev/null
+++ b/services/mediatranscoding/tests/TranscodingUidPolicyTestApp/src/com/android/tests/transcoding/ResourcePolicyTestActivity.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 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.
+ */
+
+package com.android.tests.transcoding;
+
+import android.app.Activity;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.VideoCapabilities;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.os.Bundle;
+import android.util.Log;
+import java.io.IOException;
+import java.util.Vector;
+
+public class ResourcePolicyTestActivity extends Activity {
+ public static final int TYPE_NONSECURE = 0;
+ public static final int TYPE_SECURE = 1;
+ public static final int TYPE_MIX = 2;
+
+ protected String TAG;
+ private static final int FRAME_RATE = 10;
+ private static final int IFRAME_INTERVAL = 10; // 10 seconds between I-frames
+ private static final String MIME = MediaFormat.MIMETYPE_VIDEO_AVC;
+ private static final int TIMEOUT_MS = 5000;
+
+ private Vector<MediaCodec> mCodecs = new Vector<MediaCodec>();
+
+ private class TestCodecCallback extends MediaCodec.Callback {
+ @Override
+ public void onInputBufferAvailable(MediaCodec codec, int index) {
+ Log.d(TAG, "onInputBufferAvailable " + codec.toString());
+ }
+
+ @Override
+ public void onOutputBufferAvailable(
+ MediaCodec codec, int index, MediaCodec.BufferInfo info) {
+ Log.d(TAG, "onOutputBufferAvailable " + codec.toString());
+ }
+
+ @Override
+ public void onError(MediaCodec codec, MediaCodec.CodecException e) {
+ Log.d(TAG, "onError " + codec.toString() + " errorCode " + e.getErrorCode());
+ }
+
+ @Override
+ public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
+ Log.d(TAG, "onOutputFormatChanged " + codec.toString());
+ }
+ }
+
+ private MediaCodec.Callback mCallback = new TestCodecCallback();
+
+ private MediaFormat getTestFormat(CodecCapabilities caps, boolean securePlayback) {
+ VideoCapabilities vcaps = caps.getVideoCapabilities();
+ int width = vcaps.getSupportedWidths().getLower();
+ int height = vcaps.getSupportedHeightsFor(width).getLower();
+ int bitrate = vcaps.getBitrateRange().getLower();
+
+ MediaFormat format = MediaFormat.createVideoFormat(MIME, width, height);
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT, caps.colorFormats[0]);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
+ format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+ format.setFeatureEnabled(CodecCapabilities.FEATURE_SecurePlayback, securePlayback);
+ return format;
+ }
+
+ private MediaCodecInfo getTestCodecInfo(boolean securePlayback) {
+ // Use avc decoder for testing.
+ boolean isEncoder = false;
+
+ MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
+ for (MediaCodecInfo info : mcl.getCodecInfos()) {
+ if (info.isEncoder() != isEncoder) {
+ continue;
+ }
+ CodecCapabilities caps;
+ try {
+ caps = info.getCapabilitiesForType(MIME);
+ boolean securePlaybackSupported =
+ caps.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback);
+ boolean securePlaybackRequired =
+ caps.isFeatureRequired(CodecCapabilities.FEATURE_SecurePlayback);
+ if ((securePlayback && securePlaybackSupported)
+ || (!securePlayback && !securePlaybackRequired)) {
+ Log.d(TAG, "securePlayback " + securePlayback + " will use " + info.getName());
+ } else {
+ Log.d(TAG, "securePlayback " + securePlayback + " skip " + info.getName());
+ continue;
+ }
+ } catch (IllegalArgumentException e) {
+ // mime is not supported
+ continue;
+ }
+ return info;
+ }
+
+ return null;
+ }
+
+ protected int allocateCodecs(int max) {
+ Bundle extras = getIntent().getExtras();
+ int type = TYPE_NONSECURE;
+ if (extras != null) {
+ type = extras.getInt("test-type", type);
+ Log.d(TAG, "type is: " + type);
+ }
+
+ boolean shouldSkip = false;
+ boolean securePlayback;
+ if (type == TYPE_NONSECURE || type == TYPE_MIX) {
+ securePlayback = false;
+ MediaCodecInfo info = getTestCodecInfo(securePlayback);
+ if (info != null) {
+ allocateCodecs(max, info, securePlayback);
+ } else {
+ shouldSkip = true;
+ }
+ }
+
+ if (!shouldSkip) {
+ if (type == TYPE_SECURE || type == TYPE_MIX) {
+ securePlayback = true;
+ MediaCodecInfo info = getTestCodecInfo(securePlayback);
+ if (info != null) {
+ allocateCodecs(max, info, securePlayback);
+ } else {
+ shouldSkip = true;
+ }
+ }
+ }
+
+ if (shouldSkip) {
+ Log.d(TAG, "test skipped as there's no supported codec.");
+ finishWithResult(RESULT_OK);
+ }
+
+ Log.d(TAG, "allocateCodecs returned " + mCodecs.size());
+ return mCodecs.size();
+ }
+
+ protected void allocateCodecs(int max, MediaCodecInfo info, boolean securePlayback) {
+ String name = info.getName();
+ CodecCapabilities caps = info.getCapabilitiesForType(MIME);
+ MediaFormat format = getTestFormat(caps, securePlayback);
+ MediaCodec codec = null;
+ for (int i = mCodecs.size(); i < max; ++i) {
+ try {
+ Log.d(TAG, "Create codec " + name + " #" + i);
+ codec = MediaCodec.createByCodecName(name);
+ codec.setCallback(mCallback);
+ Log.d(TAG, "Configure codec " + format);
+ codec.configure(format, null, null, 0);
+ Log.d(TAG, "Start codec " + format);
+ codec.start();
+ mCodecs.add(codec);
+ codec = null;
+ } catch (IllegalArgumentException e) {
+ Log.d(TAG, "IllegalArgumentException " + e.getMessage());
+ break;
+ } catch (IOException e) {
+ Log.d(TAG, "IOException " + e.getMessage());
+ break;
+ } catch (MediaCodec.CodecException e) {
+ Log.d(TAG, "CodecException 0x" + Integer.toHexString(e.getErrorCode()));
+ break;
+ } finally {
+ if (codec != null) {
+ Log.d(TAG, "release codec");
+ codec.release();
+ codec = null;
+ }
+ }
+ }
+ }
+
+ protected void finishWithResult(int result) {
+ for (int i = 0; i < mCodecs.size(); ++i) {
+ Log.d(TAG, "release codec #" + i);
+ mCodecs.get(i).release();
+ }
+ mCodecs.clear();
+ setResult(result);
+ finish();
+ Log.d(TAG, "activity finished");
+ }
+
+ private void doUseCodecs() {
+ int current = 0;
+ try {
+ for (current = 0; current < mCodecs.size(); ++current) {
+ mCodecs.get(current).getName();
+ }
+ } catch (MediaCodec.CodecException e) {
+ Log.d(TAG, "useCodecs got CodecException 0x" + Integer.toHexString(e.getErrorCode()));
+ if (e.getErrorCode() == MediaCodec.CodecException.ERROR_RECLAIMED) {
+ Log.d(TAG, "Remove codec " + current + " from the list");
+ mCodecs.get(current).release();
+ mCodecs.remove(current);
+ mGotReclaimedException = true;
+ mUseCodecs = false;
+ }
+ return;
+ }
+ }
+
+ private Thread mWorkerThread;
+ private volatile boolean mUseCodecs = true;
+ private volatile boolean mGotReclaimedException = false;
+ protected void useCodecs() {
+ mWorkerThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ long start = System.currentTimeMillis();
+ long timeSinceStartedMs = 0;
+ while (mUseCodecs && (timeSinceStartedMs < TIMEOUT_MS)) {
+ doUseCodecs();
+ try {
+ Thread.sleep(50 /* millis */);
+ } catch (InterruptedException e) {
+ }
+ timeSinceStartedMs = System.currentTimeMillis() - start;
+ }
+ if (mGotReclaimedException) {
+ Log.d(TAG, "Got expected reclaim exception.");
+ }
+ finishWithResult(RESULT_OK);
+ }
+ });
+ mWorkerThread.start();
+ }
+
+ private static final int MAX_INSTANCES = 32;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ TAG = "ResourcePolicyTestActivity";
+
+ Log.d(TAG, "onCreate called.");
+ super.onCreate(savedInstanceState);
+
+ if (allocateCodecs(MAX_INSTANCES) == MAX_INSTANCES) {
+ // haven't reached the limit with MAX_INSTANCES, no need to wait for reclaim exception.
+ //mWaitForReclaim = false;
+ Log.d(TAG, "Didn't hit resource limitation");
+ }
+
+ useCodecs();
+ }
+
+ @Override
+ protected void onDestroy() {
+ Log.d(TAG, "onDestroy called.");
+ super.onDestroy();
+ }
+}
diff --git a/services/mediatranscoding/tests/build_and_run_all_unit_tests.sh b/services/mediatranscoding/tests/build_and_run_all_unit_tests.sh
index d66b340..1b42a22 100755
--- a/services/mediatranscoding/tests/build_and_run_all_unit_tests.sh
+++ b/services/mediatranscoding/tests/build_and_run_all_unit_tests.sh
@@ -37,6 +37,11 @@
#adb shell /data/nativetest64/mediatranscodingservice_real_tests/mediatranscodingservice_real_tests
adb shell /data/nativetest/mediatranscodingservice_real_tests/mediatranscodingservice_real_tests
+echo "[==========] running resource tests"
+adb shell kill -9 `pid media.transcoding`
+#adb shell /data/nativetest64/mediatranscodingservice_resource_tests/mediatranscodingservice_resource_tests
+adb shell /data/nativetest/mediatranscodingservice_resource_tests/mediatranscodingservice_resource_tests
+
echo "[==========] removing debug properties"
adb shell setprop debug.transcoding.simulated_transcoder \"\"
adb shell kill -9 `pid media.transcoding`
diff --git a/services/mediatranscoding/tests/mediatranscodingservice_real_tests.cpp b/services/mediatranscoding/tests/mediatranscodingservice_real_tests.cpp
index 1def4b9..45057e2 100644
--- a/services/mediatranscoding/tests/mediatranscodingservice_real_tests.cpp
+++ b/services/mediatranscoding/tests/mediatranscodingservice_real_tests.cpp
@@ -45,9 +45,11 @@
class MediaTranscodingServiceRealTest : public MediaTranscodingServiceTestBase {
public:
- MediaTranscodingServiceRealTest() {}
+ MediaTranscodingServiceRealTest() { ALOGI("MediaTranscodingServiceResourceTest created"); }
- void deleteFile(const char* path) { unlink(path); }
+ virtual ~MediaTranscodingServiceRealTest() {
+ ALOGI("MediaTranscodingServiceResourceTest destroyed");
+ }
};
TEST_F(MediaTranscodingServiceRealTest, TestInvalidSource) {
diff --git a/services/mediatranscoding/tests/mediatranscodingservice_resource_tests.cpp b/services/mediatranscoding/tests/mediatranscodingservice_resource_tests.cpp
new file mode 100644
index 0000000..fe2cfa9
--- /dev/null
+++ b/services/mediatranscoding/tests/mediatranscodingservice_resource_tests.cpp
@@ -0,0 +1,134 @@
+/*
+ * 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.
+ */
+
+// Unit Test for MediaTranscodingService.
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaTranscodingServiceRealTest"
+
+#include "MediaTranscodingServiceTestHelper.h"
+
+/*
+ * Tests media transcoding service with real transcoder.
+ *
+ * Uses the same test assets as the MediaTranscoder unit tests. Before running the test,
+ * please make sure to push the test assets to /sdcard:
+ *
+ * adb push $TOP/frameworks/av/media/libmediatranscoding/transcoder/tests/assets /data/local/tmp/TranscodingTestAssets
+ */
+namespace android {
+
+namespace media {
+
+constexpr int64_t kPaddingUs = 400000;
+constexpr int32_t kBitRate = 8 * 1000 * 1000; // 8Mbs
+
+constexpr const char* kLongSrcPath = "/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4";
+
+constexpr const char* kResourcePolicyTestActivity =
+ "/com.android.tests.transcoding.ResourcePolicyTestActivity";
+
+#define OUTPATH(name) "/data/local/tmp/MediaTranscodingService_" #name ".MP4"
+
+class MediaTranscodingServiceResourceTest : public MediaTranscodingServiceTestBase {
+public:
+ MediaTranscodingServiceResourceTest() { ALOGI("MediaTranscodingServiceResourceTest created"); }
+
+ virtual ~MediaTranscodingServiceResourceTest() {
+ ALOGI("MediaTranscodingServiceResourceTest destroyed");
+ }
+};
+
+/**
+ * Basic testing for handling resource lost.
+ *
+ * This test starts a transcoding job (that's somewhat long and takes several seconds),
+ * then launches an activity that allocates video codec instances until it hits insufficient
+ * resource error. Because the activity is running in foreground,
+ * ResourceManager would reclaim codecs from transcoding service which should
+ * cause the job to be paused. The activity will hold the codecs for a few seconds
+ * before releasing them, and the transcoding service should be able to resume
+ * and complete the job.
+ */
+TEST_F(MediaTranscodingServiceResourceTest, TestResourceLost) {
+ ALOGD("TestResourceLost starting...");
+
+ EXPECT_TRUE(ShellHelper::RunCmd("input keyevent KEYCODE_WAKEUP"));
+ EXPECT_TRUE(ShellHelper::RunCmd("wm dismiss-keyguard"));
+ EXPECT_TRUE(ShellHelper::Stop(kClientPackageA));
+
+ registerMultipleClients();
+
+ const char* srcPath0 = kLongSrcPath;
+ const char* dstPath0 = OUTPATH(TestPauseResumeMultiClients_Client0);
+ deleteFile(dstPath0);
+
+ 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));
+
+ // Launch ResourcePolicyTestActivity, which will try to allocate up to 32
+ // instances, which should trigger insufficient resources on most devices.
+ // (Note that it's possible that the device supports a very high number of
+ // resource instances, in which case we'll simply require that the job completes.)
+ ALOGD("Launch ResourcePolicyTestActivity...");
+ EXPECT_TRUE(ShellHelper::Start(kClientPackageA, kResourcePolicyTestActivity));
+
+ // The basic requirement is that the job should complete. Wait for finish
+ // event to come and pop up all events received.
+ std::list<EventTracker::Event> events;
+ EXPECT_TRUE(mClientCallback1->waitForSpecificEventAndPop(EventTracker::Finished(CLIENT(1), 0),
+ &events, 15000000));
+
+ // If there is only 1 event, it must be finish (otherwise waitForSpecificEventAndPop
+ // woudldn't pop up anything), and we're ok.
+ //
+ // TODO: If there is only 1 event (finish), and no pause/resume happened, we need
+ // to verify that the ResourcePolicyTestActivity actually was able to allocate
+ // all 32 instances without hitting insufficient resources. Otherwise, it could
+ // be that ResourceManager was not able to reclaim codecs from the transcoding
+ // service at all, which means the resource management is broken.
+ if (events.size() > 1) {
+ EXPECT_TRUE(events.size() >= 3);
+ size_t i = 0;
+ for (auto& event : events) {
+ if (i == 0) {
+ EXPECT_EQ(event, EventTracker::Pause(CLIENT(1), 0));
+ } else if (i == events.size() - 2) {
+ EXPECT_EQ(event, EventTracker::Resume(CLIENT(1), 0));
+ } else if (i == events.size() - 1) {
+ EXPECT_EQ(event, EventTracker::Finished(CLIENT(1), 0));
+ } else {
+ EXPECT_TRUE(event == EventTracker::Pause(CLIENT(1), 0) ||
+ event == EventTracker::Resume(CLIENT(1), 0));
+ }
+ i++;
+ }
+ }
+
+ unregisterMultipleClients();
+
+ EXPECT_TRUE(ShellHelper::Stop(kClientPackageA));
+}
+
+} // namespace media
+} // namespace android
diff --git a/services/mediatranscoding/tests/mediatranscodingservice_simulated_tests.cpp b/services/mediatranscoding/tests/mediatranscodingservice_simulated_tests.cpp
index 42b5877..e9fedd9 100644
--- a/services/mediatranscoding/tests/mediatranscodingservice_simulated_tests.cpp
+++ b/services/mediatranscoding/tests/mediatranscodingservice_simulated_tests.cpp
@@ -59,7 +59,11 @@
class MediaTranscodingServiceSimulatedTest : public MediaTranscodingServiceTestBase {
public:
- MediaTranscodingServiceSimulatedTest() {}
+ MediaTranscodingServiceSimulatedTest() { ALOGI("MediaTranscodingServiceResourceTest created"); }
+
+ virtual ~MediaTranscodingServiceSimulatedTest() {
+ ALOGI("MediaTranscodingServiceResourceTest destroyed");
+ }
};
TEST_F(MediaTranscodingServiceSimulatedTest, TestRegisterNullClient) {