Merge changes Id88d546a,I14c23eea,I08286bff into rvc-qpr-dev
* changes:
MediaCodec: fix possible crash at stop & error
stagefright: add test for MediaCodec::reclaim/release race
MediaCodec: ensure reply does not get lost
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index 0a8d18d..3e191fe 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -617,7 +617,10 @@
return new PersistentSurface(bufferProducer, bufferSource);
}
-MediaCodec::MediaCodec(const sp<ALooper> &looper, pid_t pid, uid_t uid)
+MediaCodec::MediaCodec(
+ const sp<ALooper> &looper, pid_t pid, uid_t uid,
+ std::function<sp<CodecBase>(const AString &, const char *)> getCodecBase,
+ std::function<status_t(const AString &, sp<MediaCodecInfo> *)> getCodecInfo)
: mState(UNINITIALIZED),
mReleasedByResourceManager(false),
mLooper(looper),
@@ -642,7 +645,9 @@
mNumLowLatencyDisables(0),
mIsLowLatencyModeOn(false),
mIndexOfFirstFrameWhenLowLatencyOn(-1),
- mInputBufferCounter(0) {
+ mInputBufferCounter(0),
+ mGetCodecBase(getCodecBase),
+ mGetCodecInfo(getCodecInfo) {
if (uid == kNoUid) {
mUid = AIBinder_getCallingUid();
} else {
@@ -650,6 +655,33 @@
}
mResourceManagerProxy = new ResourceManagerServiceProxy(pid, mUid,
::ndk::SharedRefBase::make<ResourceManagerClient>(this));
+ if (!mGetCodecBase) {
+ mGetCodecBase = [](const AString &name, const char *owner) {
+ return GetCodecBase(name, owner);
+ };
+ }
+ if (!mGetCodecInfo) {
+ mGetCodecInfo = [](const AString &name, sp<MediaCodecInfo> *info) -> status_t {
+ *info = nullptr;
+ const sp<IMediaCodecList> mcl = MediaCodecList::getInstance();
+ if (!mcl) {
+ return NO_INIT; // if called from Java should raise IOException
+ }
+ AString tmp = name;
+ if (tmp.endsWith(".secure")) {
+ tmp.erase(tmp.size() - 7, 7);
+ }
+ for (const AString &codecName : { name, tmp }) {
+ ssize_t codecIdx = mcl->findCodecByName(codecName.c_str());
+ if (codecIdx < 0) {
+ continue;
+ }
+ *info = mcl->getCodecInfo(codecIdx);
+ return OK;
+ }
+ return NAME_NOT_FOUND;
+ };
+ }
initMediametrics();
}
@@ -1011,6 +1043,12 @@
return err;
}
+void MediaCodec::PostReplyWithError(const sp<AMessage> &msg, int32_t err) {
+ sp<AReplyToken> replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+ PostReplyWithError(replyID, err);
+}
+
void MediaCodec::PostReplyWithError(const sp<AReplyToken> &replyID, int32_t err) {
int32_t finalErr = err;
if (mReleasedByResourceManager) {
@@ -1087,40 +1125,30 @@
bool secureCodec = false;
const char *owner = "";
if (!name.startsWith("android.filter.")) {
- AString tmp = name;
- if (tmp.endsWith(".secure")) {
- secureCodec = true;
- tmp.erase(tmp.size() - 7, 7);
- }
- const sp<IMediaCodecList> mcl = MediaCodecList::getInstance();
- if (mcl == NULL) {
+ status_t err = mGetCodecInfo(name, &mCodecInfo);
+ if (err != OK) {
mCodec = NULL; // remove the codec.
- return NO_INIT; // if called from Java should raise IOException
- }
- for (const AString &codecName : { name, tmp }) {
- ssize_t codecIdx = mcl->findCodecByName(codecName.c_str());
- if (codecIdx < 0) {
- continue;
- }
- mCodecInfo = mcl->getCodecInfo(codecIdx);
- Vector<AString> mediaTypes;
- mCodecInfo->getSupportedMediaTypes(&mediaTypes);
- for (size_t i = 0; i < mediaTypes.size(); i++) {
- if (mediaTypes[i].startsWith("video/")) {
- mIsVideo = true;
- break;
- }
- }
- break;
+ return err;
}
if (mCodecInfo == nullptr) {
+ ALOGE("Getting codec info with name '%s' failed", name.c_str());
return NAME_NOT_FOUND;
}
+ secureCodec = name.endsWith(".secure");
+ Vector<AString> mediaTypes;
+ mCodecInfo->getSupportedMediaTypes(&mediaTypes);
+ for (size_t i = 0; i < mediaTypes.size(); ++i) {
+ if (mediaTypes[i].startsWith("video/")) {
+ mIsVideo = true;
+ break;
+ }
+ }
owner = mCodecInfo->getOwnerName();
}
- mCodec = GetCodecBase(name, owner);
+ mCodec = mGetCodecBase(name, owner);
if (mCodec == NULL) {
+ ALOGE("Getting codec base with name '%s' (owner='%s') failed", name.c_str(), owner);
return NAME_NOT_FOUND;
}
@@ -1514,7 +1542,6 @@
mStickyError = OK;
// reset state not reset by setState(UNINITIALIZED)
- mReplyID = 0;
mDequeueInputReplyID = 0;
mDequeueOutputReplyID = 0;
mDequeueInputTimeoutGeneration = 0;
@@ -2109,6 +2136,8 @@
}
bool sendErrorResponse = true;
+ std::string origin{"kWhatError:"};
+ origin += stateString(mState);
switch (mState) {
case INITIALIZING:
@@ -2160,14 +2189,14 @@
// be a shutdown complete notification after
// all.
- // note that we're directly going from
+ // note that we may be directly going from
// STOPPING->UNINITIALIZED, instead of the
// usual STOPPING->INITIALIZED state.
setState(UNINITIALIZED);
if (mState == RELEASING) {
mComponentName.clear();
}
- (new AMessage)->postReply(mReplyID);
+ postPendingRepliesAndDeferredMessages(origin + ":dead");
sendErrorResponse = false;
}
break;
@@ -2193,7 +2222,7 @@
case FLUSHED:
case STARTED:
{
- sendErrorResponse = false;
+ sendErrorResponse = (mReplyID != nullptr);
setStickyError(err);
postActivityNotificationIfPossible();
@@ -2223,7 +2252,7 @@
default:
{
- sendErrorResponse = false;
+ sendErrorResponse = (mReplyID != nullptr);
setStickyError(err);
postActivityNotificationIfPossible();
@@ -2250,7 +2279,15 @@
}
if (sendErrorResponse) {
- PostReplyWithError(mReplyID, err);
+ // TRICKY: replicate PostReplyWithError logic for
+ // err code override
+ int32_t finalErr = err;
+ if (mReleasedByResourceManager) {
+ // override the err code if MediaCodec has been
+ // released by ResourceManager.
+ finalErr = DEAD_OBJECT;
+ }
+ postPendingRepliesAndDeferredMessages(origin, finalErr);
}
break;
}
@@ -2298,7 +2335,7 @@
MediaResource::CodecResource(mFlags & kFlagIsSecure, mIsVideo));
}
- (new AMessage)->postReply(mReplyID);
+ postPendingRepliesAndDeferredMessages("kWhatComponentAllocated");
break;
}
@@ -2337,7 +2374,7 @@
mFlags |= kFlagUsesSoftwareRenderer;
}
setState(CONFIGURED);
- (new AMessage)->postReply(mReplyID);
+ postPendingRepliesAndDeferredMessages("kWhatComponentConfigured");
// augment our media metrics info, now that we know more things
// such as what the codec extracted from any CSD passed in.
@@ -2382,6 +2419,12 @@
case kWhatInputSurfaceCreated:
{
+ if (mState != CONFIGURED) {
+ // state transitioned unexpectedly; we should have replied already.
+ ALOGD("received kWhatInputSurfaceCreated message in state %s",
+ stateString(mState).c_str());
+ break;
+ }
// response to initiateCreateInputSurface()
status_t err = NO_ERROR;
sp<AMessage> response = new AMessage;
@@ -2400,12 +2443,18 @@
} else {
response->setInt32("err", err);
}
- response->postReply(mReplyID);
+ postPendingRepliesAndDeferredMessages("kWhatInputSurfaceCreated", response);
break;
}
case kWhatInputSurfaceAccepted:
{
+ if (mState != CONFIGURED) {
+ // state transitioned unexpectedly; we should have replied already.
+ ALOGD("received kWhatInputSurfaceAccepted message in state %s",
+ stateString(mState).c_str());
+ break;
+ }
// response to initiateSetInputSurface()
status_t err = NO_ERROR;
sp<AMessage> response = new AMessage();
@@ -2416,19 +2465,25 @@
} else {
response->setInt32("err", err);
}
- response->postReply(mReplyID);
+ postPendingRepliesAndDeferredMessages("kWhatInputSurfaceAccepted", response);
break;
}
case kWhatSignaledInputEOS:
{
+ if (!isExecuting()) {
+ // state transitioned unexpectedly; we should have replied already.
+ ALOGD("received kWhatSignaledInputEOS message in state %s",
+ stateString(mState).c_str());
+ break;
+ }
// response to signalEndOfInputStream()
sp<AMessage> response = new AMessage;
status_t err;
if (msg->findInt32("err", &err)) {
response->setInt32("err", err);
}
- response->postReply(mReplyID);
+ postPendingRepliesAndDeferredMessages("kWhatSignaledInputEOS", response);
break;
}
@@ -2447,7 +2502,7 @@
MediaResource::GraphicMemoryResource(getGraphicBufferSize()));
}
setState(STARTED);
- (new AMessage)->postReply(mReplyID);
+ postPendingRepliesAndDeferredMessages("kWhatStartCompleted");
break;
}
@@ -2583,7 +2638,13 @@
break;
}
setState(INITIALIZED);
- (new AMessage)->postReply(mReplyID);
+ if (mReplyID) {
+ postPendingRepliesAndDeferredMessages("kWhatStopCompleted");
+ } else {
+ ALOGW("kWhatStopCompleted: presumably an error occurred earlier, "
+ "but the operation completed anyway. (last reply origin=%s)",
+ mLastReplyOrigin.c_str());
+ }
break;
}
@@ -2607,7 +2668,7 @@
mReleaseSurface.reset();
if (mReplyID != nullptr) {
- (new AMessage)->postReply(mReplyID);
+ postPendingRepliesAndDeferredMessages("kWhatReleaseCompleted");
}
if (mAsyncReleaseCompleteNotification != nullptr) {
flushMediametrics();
@@ -2632,7 +2693,7 @@
mCodec->signalResume();
}
- (new AMessage)->postReply(mReplyID);
+ postPendingRepliesAndDeferredMessages("kWhatFlushCompleted");
break;
}
@@ -2644,14 +2705,18 @@
case kWhatInit:
{
- sp<AReplyToken> replyID;
- CHECK(msg->senderAwaitsResponse(&replyID));
-
if (mState != UNINITIALIZED) {
- PostReplyWithError(replyID, INVALID_OPERATION);
+ PostReplyWithError(msg, INVALID_OPERATION);
break;
}
+ if (mReplyID) {
+ mDeferredMessages.push_back(msg);
+ break;
+ }
+ sp<AReplyToken> replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
mReplyID = replyID;
setState(INITIALIZING);
@@ -2713,14 +2778,18 @@
case kWhatConfigure:
{
- sp<AReplyToken> replyID;
- CHECK(msg->senderAwaitsResponse(&replyID));
-
if (mState != INITIALIZED) {
- PostReplyWithError(replyID, INVALID_OPERATION);
+ PostReplyWithError(msg, INVALID_OPERATION);
break;
}
+ if (mReplyID) {
+ mDeferredMessages.push_back(msg);
+ break;
+ }
+ sp<AReplyToken> replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
sp<RefBase> obj;
CHECK(msg->findObject("surface", &obj));
@@ -2858,15 +2927,19 @@
case kWhatCreateInputSurface:
case kWhatSetInputSurface:
{
- sp<AReplyToken> replyID;
- CHECK(msg->senderAwaitsResponse(&replyID));
-
// Must be configured, but can't have been started yet.
if (mState != CONFIGURED) {
- PostReplyWithError(replyID, INVALID_OPERATION);
+ PostReplyWithError(msg, INVALID_OPERATION);
break;
}
+ if (mReplyID) {
+ mDeferredMessages.push_back(msg);
+ break;
+ }
+ sp<AReplyToken> replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
mReplyID = replyID;
if (msg->what() == kWhatCreateInputSurface) {
mCodec->initiateCreateInputSurface();
@@ -2881,9 +2954,6 @@
}
case kWhatStart:
{
- sp<AReplyToken> replyID;
- CHECK(msg->senderAwaitsResponse(&replyID));
-
if (mState == FLUSHED) {
setState(STARTED);
if (mHavePendingInputBuffers) {
@@ -2891,13 +2961,20 @@
mHavePendingInputBuffers = false;
}
mCodec->signalResume();
- PostReplyWithError(replyID, OK);
+ PostReplyWithError(msg, OK);
break;
} else if (mState != CONFIGURED) {
- PostReplyWithError(replyID, INVALID_OPERATION);
+ PostReplyWithError(msg, INVALID_OPERATION);
break;
}
+ if (mReplyID) {
+ mDeferredMessages.push_back(msg);
+ break;
+ }
+ sp<AReplyToken> replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
mReplyID = replyID;
setState(STARTING);
@@ -2905,15 +2982,42 @@
break;
}
- case kWhatStop:
+ case kWhatStop: {
+ if (mReplyID) {
+ mDeferredMessages.push_back(msg);
+ break;
+ }
+ [[fallthrough]];
+ }
case kWhatRelease:
{
State targetState =
(msg->what() == kWhatStop) ? INITIALIZED : UNINITIALIZED;
+ if ((mState == RELEASING && targetState == UNINITIALIZED)
+ || (mState == STOPPING && targetState == INITIALIZED)) {
+ mDeferredMessages.push_back(msg);
+ break;
+ }
+
sp<AReplyToken> replyID;
CHECK(msg->senderAwaitsResponse(&replyID));
+ sp<AMessage> asyncNotify;
+ (void)msg->findMessage("async", &asyncNotify);
+ // post asyncNotify if going out of scope.
+ struct AsyncNotifyPost {
+ AsyncNotifyPost(const sp<AMessage> &asyncNotify) : mAsyncNotify(asyncNotify) {}
+ ~AsyncNotifyPost() {
+ if (mAsyncNotify) {
+ mAsyncNotify->post();
+ }
+ }
+ void clear() { mAsyncNotify.clear(); }
+ private:
+ sp<AMessage> mAsyncNotify;
+ } asyncNotifyPost{asyncNotify};
+
// already stopped/released
if (mState == UNINITIALIZED && mReleasedByResourceManager) {
sp<AMessage> response = new AMessage;
@@ -2977,12 +3081,15 @@
// after this, and we'll no longer be able to reply.
if (mState == FLUSHING || mState == STOPPING
|| mState == CONFIGURING || mState == STARTING) {
- (new AMessage)->postReply(mReplyID);
+ // mReply is always set if in these states.
+ postPendingRepliesAndDeferredMessages(
+ std::string("kWhatRelease:") + stateString(mState));
}
if (mFlags & kFlagSawMediaServerDie) {
// It's dead, Jim. Don't expect initiateShutdown to yield
// any useful results now...
+ // Any pending reply would have been handled at kWhatError.
setState(UNINITIALIZED);
if (targetState == UNINITIALIZED) {
mComponentName.clear();
@@ -2996,12 +3103,12 @@
// reply now with an error to unblock the client, client can
// release after the failure (instead of ANR).
if (msg->what() == kWhatStop && (mFlags & kFlagStickyError)) {
+ // Any pending reply would have been handled at kWhatError.
PostReplyWithError(replyID, getStickyError());
break;
}
- sp<AMessage> asyncNotify;
- if (msg->findMessage("async", &asyncNotify) && asyncNotify != nullptr) {
+ if (asyncNotify != nullptr) {
if (mSurface != NULL) {
if (!mReleaseSurface) {
mReleaseSurface.reset(new ReleaseSurface);
@@ -3021,6 +3128,12 @@
}
}
+ if (mReplyID) {
+ // State transition replies are handled above, so this reply
+ // would not be related to state transition. As we are
+ // shutting down the component, just fail the operation.
+ postPendingRepliesAndDeferredMessages("kWhatRelease:reply", UNKNOWN_ERROR);
+ }
mReplyID = replyID;
setState(msg->what() == kWhatStop ? STOPPING : RELEASING);
@@ -3035,8 +3148,8 @@
if (asyncNotify != nullptr) {
mResourceManagerProxy->markClientForPendingRemoval();
- (new AMessage)->postReply(mReplyID);
- mReplyID = 0;
+ postPendingRepliesAndDeferredMessages("kWhatRelease:async");
+ asyncNotifyPost.clear();
mAsyncReleaseCompleteNotification = asyncNotify;
}
@@ -3207,17 +3320,21 @@
case kWhatSignalEndOfInputStream:
{
- sp<AReplyToken> replyID;
- CHECK(msg->senderAwaitsResponse(&replyID));
-
if (!isExecuting() || !mHaveInputSurface) {
- PostReplyWithError(replyID, INVALID_OPERATION);
+ PostReplyWithError(msg, INVALID_OPERATION);
break;
} else if (mFlags & kFlagStickyError) {
- PostReplyWithError(replyID, getStickyError());
+ PostReplyWithError(msg, getStickyError());
break;
}
+ if (mReplyID) {
+ mDeferredMessages.push_back(msg);
+ break;
+ }
+ sp<AReplyToken> replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
mReplyID = replyID;
mCodec->signalEndOfInputStream();
break;
@@ -3259,17 +3376,21 @@
case kWhatFlush:
{
- sp<AReplyToken> replyID;
- CHECK(msg->senderAwaitsResponse(&replyID));
-
if (!isExecuting()) {
- PostReplyWithError(replyID, INVALID_OPERATION);
+ PostReplyWithError(msg, INVALID_OPERATION);
break;
} else if (mFlags & kFlagStickyError) {
- PostReplyWithError(replyID, getStickyError());
+ PostReplyWithError(msg, getStickyError());
break;
}
+ if (mReplyID) {
+ mDeferredMessages.push_back(msg);
+ break;
+ }
+ sp<AReplyToken> replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+
mReplyID = replyID;
// TODO: skip flushing if already FLUSHED
setState(FLUSHING);
@@ -4214,6 +4335,33 @@
return OK;
}
+void MediaCodec::postPendingRepliesAndDeferredMessages(
+ std::string origin, status_t err /* = OK */) {
+ sp<AMessage> response{new AMessage};
+ if (err != OK) {
+ response->setInt32("err", err);
+ }
+ postPendingRepliesAndDeferredMessages(origin, response);
+}
+
+void MediaCodec::postPendingRepliesAndDeferredMessages(
+ std::string origin, const sp<AMessage> &response) {
+ LOG_ALWAYS_FATAL_IF(
+ !mReplyID,
+ "postPendingRepliesAndDeferredMessages: mReplyID == null, from %s following %s",
+ origin.c_str(),
+ mLastReplyOrigin.c_str());
+ mLastReplyOrigin = origin;
+ response->postReply(mReplyID);
+ mReplyID.clear();
+ ALOGV_IF(!mDeferredMessages.empty(),
+ "posting %zu deferred messages", mDeferredMessages.size());
+ for (sp<AMessage> msg : mDeferredMessages) {
+ msg->post();
+ }
+ mDeferredMessages.clear();
+}
+
std::string MediaCodec::stateString(State state) {
const char *rval = NULL;
char rawbuffer[16]; // room for "%d"
diff --git a/media/libstagefright/TEST_MAPPING b/media/libstagefright/TEST_MAPPING
index 8b36ea5..5e537dd 100644
--- a/media/libstagefright/TEST_MAPPING
+++ b/media/libstagefright/TEST_MAPPING
@@ -17,6 +17,9 @@
"exclude-filter": "android.media.cts.AudioRecordTest"
}
]
+ },
+ {
+ "name": "mediacodecTest"
}
],
"postsubmit": [
diff --git a/media/libstagefright/include/media/stagefright/MediaCodec.h b/media/libstagefright/include/media/stagefright/MediaCodec.h
index 8057312..7614ba5 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodec.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodec.h
@@ -366,6 +366,8 @@
AString mOwnerName;
sp<MediaCodecInfo> mCodecInfo;
sp<AReplyToken> mReplyID;
+ std::string mLastReplyOrigin;
+ std::vector<sp<AMessage>> mDeferredMessages;
uint32_t mFlags;
status_t mStickyError;
sp<Surface> mSurface;
@@ -428,13 +430,17 @@
std::shared_ptr<BufferChannelBase> mBufferChannel;
- MediaCodec(const sp<ALooper> &looper, pid_t pid, uid_t uid);
+ MediaCodec(
+ const sp<ALooper> &looper, pid_t pid, uid_t uid,
+ std::function<sp<CodecBase>(const AString &, const char *)> getCodecBase = nullptr,
+ std::function<status_t(const AString &, sp<MediaCodecInfo> *)> getCodecInfo = nullptr);
static sp<CodecBase> GetCodecBase(const AString &name, const char *owner = nullptr);
static status_t PostAndAwaitResponse(
const sp<AMessage> &msg, sp<AMessage> *response);
+ void PostReplyWithError(const sp<AMessage> &msg, int32_t err);
void PostReplyWithError(const sp<AReplyToken> &replyID, int32_t err);
status_t init(const AString &name);
@@ -486,6 +492,9 @@
bool hasPendingBuffer(int portIndex);
bool hasPendingBuffer();
+ void postPendingRepliesAndDeferredMessages(std::string origin, status_t err = OK);
+ void postPendingRepliesAndDeferredMessages(std::string origin, const sp<AMessage> &response);
+
/* called to get the last codec error when the sticky flag is set.
* if no such codec error is found, returns UNKNOWN_ERROR.
*/
@@ -571,6 +580,10 @@
Histogram mLatencyHist;
+ std::function<sp<CodecBase>(const AString &, const char *)> mGetCodecBase;
+ std::function<status_t(const AString &, sp<MediaCodecInfo> *)> mGetCodecInfo;
+ friend class MediaTestHelper;
+
DISALLOW_EVIL_CONSTRUCTORS(MediaCodec);
};
diff --git a/media/libstagefright/include/media/stagefright/MediaCodecListWriter.h b/media/libstagefright/include/media/stagefright/MediaCodecListWriter.h
index f53b23e..bf85d7e 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodecListWriter.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodecListWriter.h
@@ -19,7 +19,6 @@
#define MEDIA_CODEC_LIST_WRITER_H_
#include <media/stagefright/foundation/ABase.h>
-#include <media/stagefright/MediaCodecListWriter.h>
#include <media/MediaCodecInfo.h>
#include <utils/Errors.h>
@@ -65,6 +64,7 @@
std::vector<sp<MediaCodecInfo>> mCodecInfos;
friend struct MediaCodecList;
+ friend class MediaTestHelper;
};
/**
diff --git a/media/libstagefright/tests/mediacodec/Android.bp b/media/libstagefright/tests/mediacodec/Android.bp
new file mode 100644
index 0000000..0bd0639
--- /dev/null
+++ b/media/libstagefright/tests/mediacodec/Android.bp
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+cc_test {
+ name: "mediacodecTest",
+ gtest: true,
+
+ srcs: [
+ "MediaCodecTest.cpp",
+ "MediaTestHelper.cpp",
+ ],
+
+ header_libs: [
+ "libmediadrm_headers",
+ ],
+
+ shared_libs: [
+ "libgui",
+ "libmedia",
+ "libmedia_codeclist",
+ "libmediametrics",
+ "libmediandk",
+ "libstagefright",
+ "libstagefright_codecbase",
+ "libstagefright_foundation",
+ "libutils",
+ ],
+
+ static_libs: [
+ "libgmock",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+ sanitize: {
+ cfi: true,
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ },
+
+ test_suites: [
+ "general-tests",
+ ],
+}
diff --git a/media/libstagefright/tests/mediacodec/MediaCodecTest.cpp b/media/libstagefright/tests/mediacodec/MediaCodecTest.cpp
new file mode 100644
index 0000000..d00a50f
--- /dev/null
+++ b/media/libstagefright/tests/mediacodec/MediaCodecTest.cpp
@@ -0,0 +1,350 @@
+/*
+ * 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.
+ */
+
+#include <future>
+#include <thread>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <gui/Surface.h>
+#include <mediadrm/ICrypto.h>
+#include <media/stagefright/CodecBase.h>
+#include <media/stagefright/MediaCodec.h>
+#include <media/stagefright/MediaCodecListWriter.h>
+#include <media/MediaCodecInfo.h>
+
+#include "MediaTestHelper.h"
+
+namespace android {
+
+class MockBufferChannel : public BufferChannelBase {
+public:
+ ~MockBufferChannel() override = default;
+
+ MOCK_METHOD(void, setCrypto, (const sp<ICrypto> &crypto), (override));
+ MOCK_METHOD(void, setDescrambler, (const sp<IDescrambler> &descrambler), (override));
+ MOCK_METHOD(status_t, queueInputBuffer, (const sp<MediaCodecBuffer> &buffer), (override));
+ MOCK_METHOD(status_t, queueSecureInputBuffer,
+ (const sp<MediaCodecBuffer> &buffer,
+ bool secure,
+ const uint8_t *key,
+ const uint8_t *iv,
+ CryptoPlugin::Mode mode,
+ CryptoPlugin::Pattern pattern,
+ const CryptoPlugin::SubSample *subSamples,
+ size_t numSubSamples,
+ AString *errorDetailMsg),
+ (override));
+ MOCK_METHOD(status_t, attachBuffer,
+ (const std::shared_ptr<C2Buffer> &c2Buffer, const sp<MediaCodecBuffer> &buffer),
+ (override));
+ MOCK_METHOD(status_t, attachEncryptedBuffer,
+ (const sp<hardware::HidlMemory> &memory,
+ bool secure,
+ const uint8_t *key,
+ const uint8_t *iv,
+ CryptoPlugin::Mode mode,
+ CryptoPlugin::Pattern pattern,
+ size_t offset,
+ const CryptoPlugin::SubSample *subSamples,
+ size_t numSubSamples,
+ const sp<MediaCodecBuffer> &buffer),
+ (override));
+ MOCK_METHOD(status_t, renderOutputBuffer,
+ (const sp<MediaCodecBuffer> &buffer, int64_t timestampNs),
+ (override));
+ MOCK_METHOD(status_t, discardBuffer, (const sp<MediaCodecBuffer> &buffer), (override));
+ MOCK_METHOD(void, getInputBufferArray, (Vector<sp<MediaCodecBuffer>> *array), (override));
+ MOCK_METHOD(void, getOutputBufferArray, (Vector<sp<MediaCodecBuffer>> *array), (override));
+};
+
+class MockCodec : public CodecBase {
+public:
+ MockCodec(std::function<void(const std::shared_ptr<MockBufferChannel> &)> mock) {
+ mMockBufferChannel = std::make_shared<MockBufferChannel>();
+ mock(mMockBufferChannel);
+ }
+ ~MockCodec() override = default;
+
+ MOCK_METHOD(void, initiateAllocateComponent, (const sp<AMessage> &msg), (override));
+ MOCK_METHOD(void, initiateConfigureComponent, (const sp<AMessage> &msg), (override));
+ MOCK_METHOD(void, initiateCreateInputSurface, (), (override));
+ MOCK_METHOD(void, initiateSetInputSurface, (const sp<PersistentSurface> &surface), (override));
+ MOCK_METHOD(void, initiateStart, (), (override));
+ MOCK_METHOD(void, initiateShutdown, (bool keepComponentAllocated), (override));
+ MOCK_METHOD(void, onMessageReceived, (const sp<AMessage> &msg), (override));
+ MOCK_METHOD(status_t, setSurface, (const sp<Surface> &surface), (override));
+ MOCK_METHOD(void, signalFlush, (), (override));
+ MOCK_METHOD(void, signalResume, (), (override));
+ MOCK_METHOD(void, signalRequestIDRFrame, (), (override));
+ MOCK_METHOD(void, signalSetParameters, (const sp<AMessage> &msg), (override));
+ MOCK_METHOD(void, signalEndOfInputStream, (), (override));
+
+ std::shared_ptr<BufferChannelBase> getBufferChannel() override {
+ return mMockBufferChannel;
+ }
+
+ const std::unique_ptr<CodecCallback> &callback() {
+ return mCallback;
+ }
+
+ std::shared_ptr<MockBufferChannel> mMockBufferChannel;
+};
+
+class Counter {
+public:
+ Counter() = default;
+ explicit Counter(int32_t initCount) : mCount(initCount) {}
+ ~Counter() = default;
+
+ int32_t advance() {
+ std::unique_lock<std::mutex> lock(mMutex);
+ ++mCount;
+ mCondition.notify_all();
+ return mCount;
+ }
+
+ template <typename Rep, typename Period, typename ...Args>
+ int32_t waitFor(const std::chrono::duration<Rep, Period> &duration, Args... values) {
+ std::initializer_list<int32_t> list = {values...};
+ std::unique_lock<std::mutex> lock(mMutex);
+ mCondition.wait_for(
+ lock,
+ duration,
+ [&list, this]{
+ return std::find(list.begin(), list.end(), mCount) != list.end();
+ });
+ return mCount;
+ }
+
+ template <typename ...Args>
+ int32_t wait(Args... values) {
+ std::initializer_list<int32_t> list = {values...};
+ std::unique_lock<std::mutex> lock(mMutex);
+ mCondition.wait(
+ lock,
+ [&list, this]{
+ return std::find(list.begin(), list.end(), mCount) != list.end();
+ });
+ return mCount;
+ }
+
+private:
+ std::mutex mMutex;
+ std::condition_variable mCondition;
+ int32_t mCount = 0;
+};
+
+} // namespace android
+
+using namespace android;
+using ::testing::_;
+
+static sp<MediaCodec> SetupMediaCodec(
+ const AString &owner,
+ const AString &codecName,
+ const AString &mediaType,
+ const sp<ALooper> &looper,
+ std::function<sp<CodecBase>(const AString &name, const char *owner)> getCodecBase) {
+ std::shared_ptr<MediaCodecListWriter> listWriter =
+ MediaTestHelper::CreateCodecListWriter();
+ std::unique_ptr<MediaCodecInfoWriter> infoWriter = listWriter->addMediaCodecInfo();
+ infoWriter->setName(codecName.c_str());
+ infoWriter->setOwner(owner.c_str());
+ infoWriter->addMediaType(mediaType.c_str());
+ std::vector<sp<MediaCodecInfo>> codecInfos;
+ MediaTestHelper::WriteCodecInfos(listWriter, &codecInfos);
+ std::function<status_t(const AString &, sp<MediaCodecInfo> *)> getCodecInfo =
+ [codecInfos](const AString &name, sp<MediaCodecInfo> *info) -> status_t {
+ auto it = std::find_if(
+ codecInfos.begin(), codecInfos.end(),
+ [&name](const sp<MediaCodecInfo> &info) {
+ return name.equalsIgnoreCase(info->getCodecName());
+ });
+
+ *info = (it == codecInfos.end()) ? nullptr : *it;
+ return (*info) ? OK : NAME_NOT_FOUND;
+ };
+
+ looper->start();
+ return MediaTestHelper::CreateCodec(
+ codecName, looper, getCodecBase, getCodecInfo);
+}
+
+TEST(MediaCodecTest, ReclaimReleaseRace) {
+ // Test scenario:
+ //
+ // 1) ResourceManager thread calls reclaim(), message posted to
+ // MediaCodec looper thread.
+ // 2) MediaCodec looper thread calls initiateShutdown(), shutdown being
+ // handled at the component thread.
+ // 3) Client thread calls release(), message posted to & handle at
+ // MediaCodec looper thread.
+ // 4) MediaCodec looper thread may call initiateShutdown().
+ // 5) initiateShutdown() from 2) is handled at onReleaseComplete() event
+ // posted to MediaCodec looper thread.
+ // 6) If called, initiateShutdown() from 4) is handled and
+ // onReleaseComplete() event posted to MediaCodec looper thread.
+
+ static const AString kCodecName{"test.codec"};
+ static const AString kCodecOwner{"nobody"};
+ static const AString kMediaType{"video/x-test"};
+
+ enum {
+ kInit,
+ kShutdownFromReclaimReceived,
+ kReleaseCalled,
+ };
+ Counter counter{kInit};
+ sp<MockCodec> mockCodec;
+ std::function<sp<CodecBase>(const AString &name, const char *owner)> getCodecBase =
+ [&mockCodec, &counter](const AString &, const char *) {
+ mockCodec = new MockCodec([](const std::shared_ptr<MockBufferChannel> &) {
+ // No mock setup, as we don't expect any buffer operations
+ // in this scenario.
+ });
+ ON_CALL(*mockCodec, initiateAllocateComponent(_))
+ .WillByDefault([mockCodec](const sp<AMessage> &) {
+ mockCodec->callback()->onComponentAllocated(kCodecName.c_str());
+ });
+ ON_CALL(*mockCodec, initiateShutdown(_))
+ .WillByDefault([mockCodec, &counter](bool) {
+ int32_t stage = counter.wait(kInit, kReleaseCalled);
+ if (stage == kInit) {
+ // Mark that 2) happened, so test can proceed to 3)
+ counter.advance();
+ } else if (stage == kReleaseCalled) {
+ // Handle 6)
+ mockCodec->callback()->onReleaseCompleted();
+ }
+ });
+ return mockCodec;
+ };
+
+ sp<ALooper> looper{new ALooper};
+ sp<MediaCodec> codec = SetupMediaCodec(
+ kCodecOwner, kCodecName, kMediaType, looper, getCodecBase);
+ ASSERT_NE(nullptr, codec) << "Codec must not be null";
+ ASSERT_NE(nullptr, mockCodec) << "MockCodec must not be null";
+ std::promise<void> reclaimCompleted;
+ std::promise<void> releaseCompleted;
+ Counter threadExitCounter;
+ std::thread([codec, &reclaimCompleted]{
+ // Simulate ResourceManager thread. Proceed with 1)
+ MediaTestHelper::Reclaim(codec, true /* force */);
+ reclaimCompleted.set_value();
+ }).detach();
+ std::thread([codec, &counter, &releaseCompleted]{
+ // Simulate client thread. Wait until 2) is complete
+ (void)counter.wait(kShutdownFromReclaimReceived);
+ // Proceed to 3), and mark that 5) is ready to happen.
+ // NOTE: it's difficult to pinpoint when 4) happens, so we will sleep
+ // to meet the timing.
+ counter.advance();
+ codec->release();
+ releaseCompleted.set_value();
+ }).detach();
+ std::thread([mockCodec, &counter]{
+ // Simulate component thread. Wait until 3) is complete
+ (void)counter.wait(kReleaseCalled);
+ // We want 4) to complete before moving forward, but it is hard to
+ // wait for this exact event. Just sleep so that the other thread can
+ // proceed and complete 4).
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ // Proceed to 5).
+ mockCodec->callback()->onReleaseCompleted();
+ }).detach();
+ EXPECT_EQ(
+ std::future_status::ready,
+ reclaimCompleted.get_future().wait_for(std::chrono::seconds(5)))
+ << "reclaim timed out";
+ EXPECT_EQ(
+ std::future_status::ready,
+ releaseCompleted.get_future().wait_for(std::chrono::seconds(5)))
+ << "release timed out";
+ looper->stop();
+}
+
+TEST(MediaCodecTest, ErrorWhileStopping) {
+ // Test scenario:
+ //
+ // 1) Client thread calls stop(); MediaCodec looper thread calls
+ // initiateShutdown(); shutdown is being handled at the component thread.
+ // 2) Error occurred, but the shutdown operation is still being done.
+ // 3) MediaCodec looper thread handles the error.
+ // 4) Component thread completes shutdown and posts onStopCompleted()
+
+ static const AString kCodecName{"test.codec"};
+ static const AString kCodecOwner{"nobody"};
+ static const AString kMediaType{"video/x-test"};
+
+ std::promise<void> errorOccurred;
+ sp<MockCodec> mockCodec;
+ std::function<sp<CodecBase>(const AString &name, const char *owner)> getCodecBase =
+ [&mockCodec, &errorOccurred](const AString &, const char *) {
+ mockCodec = new MockCodec([](const std::shared_ptr<MockBufferChannel> &) {
+ // No mock setup, as we don't expect any buffer operations
+ // in this scenario.
+ });
+ ON_CALL(*mockCodec, initiateAllocateComponent(_))
+ .WillByDefault([mockCodec](const sp<AMessage> &) {
+ mockCodec->callback()->onComponentAllocated(kCodecName.c_str());
+ });
+ ON_CALL(*mockCodec, initiateConfigureComponent(_))
+ .WillByDefault([mockCodec](const sp<AMessage> &msg) {
+ mockCodec->callback()->onComponentConfigured(
+ msg->dup(), msg->dup());
+ });
+ ON_CALL(*mockCodec, initiateStart())
+ .WillByDefault([mockCodec]() {
+ mockCodec->callback()->onStartCompleted();
+ });
+ ON_CALL(*mockCodec, initiateShutdown(true))
+ .WillByDefault([mockCodec, &errorOccurred](bool) {
+ mockCodec->callback()->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
+ // Mark that 1) and 2) are complete.
+ errorOccurred.set_value();
+ });
+ ON_CALL(*mockCodec, initiateShutdown(false))
+ .WillByDefault([mockCodec](bool) {
+ mockCodec->callback()->onReleaseCompleted();
+ });
+ return mockCodec;
+ };
+
+ sp<ALooper> looper{new ALooper};
+ sp<MediaCodec> codec = SetupMediaCodec(
+ kCodecOwner, kCodecName, kMediaType, looper, getCodecBase);
+ ASSERT_NE(nullptr, codec) << "Codec must not be null";
+ ASSERT_NE(nullptr, mockCodec) << "MockCodec must not be null";
+
+ std::thread([mockCodec, &errorOccurred]{
+ // Simulate component thread that handles stop()
+ errorOccurred.get_future().wait();
+ // Error occurred but shutdown request still got processed.
+ mockCodec->callback()->onStopCompleted();
+ }).detach();
+
+ codec->configure(new AMessage, nullptr, nullptr, 0);
+ codec->start();
+ codec->stop();
+ // Sleep here to give time for the MediaCodec looper thread
+ // to process the messages.
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ codec->release();
+ looper->stop();
+}
diff --git a/media/libstagefright/tests/mediacodec/MediaTestHelper.cpp b/media/libstagefright/tests/mediacodec/MediaTestHelper.cpp
new file mode 100644
index 0000000..bbe3c05
--- /dev/null
+++ b/media/libstagefright/tests/mediacodec/MediaTestHelper.cpp
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+#include <media/stagefright/MediaCodec.h>
+#include <media/stagefright/MediaCodecListWriter.h>
+
+#include "MediaTestHelper.h"
+
+namespace android {
+
+// static
+sp<MediaCodec> MediaTestHelper::CreateCodec(
+ const AString &name,
+ const sp<ALooper> &looper,
+ std::function<sp<CodecBase>(const AString &, const char *)> getCodecBase,
+ std::function<status_t(const AString &, sp<MediaCodecInfo> *)> getCodecInfo) {
+ sp<MediaCodec> codec = new MediaCodec(
+ looper, MediaCodec::kNoPid, MediaCodec::kNoUid, getCodecBase, getCodecInfo);
+ if (codec->init(name) != OK) {
+ return nullptr;
+ }
+ return codec;
+}
+
+// static
+void MediaTestHelper::Reclaim(const sp<MediaCodec> &codec, bool force) {
+ codec->reclaim(force);
+}
+
+// static
+std::shared_ptr<MediaCodecListWriter> MediaTestHelper::CreateCodecListWriter() {
+ return std::shared_ptr<MediaCodecListWriter>(new MediaCodecListWriter);
+}
+
+// static
+void MediaTestHelper::WriteCodecInfos(
+ const std::shared_ptr<MediaCodecListWriter> &writer,
+ std::vector<sp<MediaCodecInfo>> *codecInfos) {
+ writer->writeCodecInfos(codecInfos);
+}
+
+} // namespace android
diff --git a/media/libstagefright/tests/mediacodec/MediaTestHelper.h b/media/libstagefright/tests/mediacodec/MediaTestHelper.h
new file mode 100644
index 0000000..f3d6110
--- /dev/null
+++ b/media/libstagefright/tests/mediacodec/MediaTestHelper.h
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+#ifndef MEDIA_TEST_HELPER_H_
+
+#define MEDIA_TEST_HELPER_H_
+
+#include <media/stagefright/foundation/AString.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+
+struct ALooper;
+struct CodecBase;
+struct MediaCodec;
+struct MediaCodecInfo;
+struct MediaCodecListWriter;
+
+class MediaTestHelper {
+public:
+ // MediaCodec
+ static sp<MediaCodec> CreateCodec(
+ const AString &name,
+ const sp<ALooper> &looper,
+ std::function<sp<CodecBase>(const AString &, const char *)> getCodecBase,
+ std::function<status_t(const AString &, sp<MediaCodecInfo> *)> getCodecInfo);
+ static void Reclaim(const sp<MediaCodec> &codec, bool force);
+
+ // MediaCodecListWriter
+ static std::shared_ptr<MediaCodecListWriter> CreateCodecListWriter();
+ static void WriteCodecInfos(
+ const std::shared_ptr<MediaCodecListWriter> &writer,
+ std::vector<sp<MediaCodecInfo>> *codecInfos);
+};
+
+} // namespace android
+
+#endif // MEDIA_TEST_HELPER_H_