codec2: MTS add new tests for video dec
flush, EOS, thumbnail and adaptive tests with timestamp deviation support
Test: MtsHidlC2V1_0TargetVideoDecTest -I software \
-C c2.android.avc.decoder -P /sdcard/media/
Test: MtsHidlC2V1_0TargetVideoDecTest -I software \
-C c2.android.hevc.decoder -P /sdcard/media/
Bug: 80272610
Bug: 110549953
Change-Id: Idd3da38ac7e4c0870a6a2d3c0cc1d5d646f73408
diff --git a/codec2/hidl/1.0/mts/video/MtsHidlC2V1_0TargetVideoDecTest.cpp b/codec2/hidl/1.0/mts/video/MtsHidlC2V1_0TargetVideoDecTest.cpp
index ae8336e..1a3820d 100644
--- a/codec2/hidl/1.0/mts/video/MtsHidlC2V1_0TargetVideoDecTest.cpp
+++ b/codec2/hidl/1.0/mts/video/MtsHidlC2V1_0TargetVideoDecTest.cpp
@@ -124,6 +124,8 @@
}
mEos = false;
mFramesReceived = 0;
+ mTimestampUs = 0u;
+ mTimestampDevTest = false;
if (mCompName == unknown_comp) mDisableTest = true;
if (mDisableTest) std::cout << "[ WARN ] Test Disabled \n";
}
@@ -159,15 +161,61 @@
mComponent->config(configParam, C2_DONT_BLOCK, &failures);
ASSERT_EQ(failures.size(), 0u);
}
+
mFramesReceived++;
mEos = (work->worklets.front()->output.flags &
C2FrameData::FLAG_END_OF_STREAM) != 0;
+ auto frameIndexIt =
+ std::find(mFlushedIndices.begin(), mFlushedIndices.end(),
+ work->input.ordinal.frameIndex.peeku());
+
+ // For decoder components current timestamp always exceeds
+ // previous timestamp
+ typedef std::unique_lock<std::mutex> ULock;
+ bool codecConfig = ((work->worklets.front()->output.flags &
+ C2FrameData::FLAG_CODEC_CONFIG) != 0);
+ if (!codecConfig &&
+ !work->worklets.front()->output.buffers.empty()) {
+ EXPECT_GE(
+ (work->worklets.front()->output.ordinal.timestamp.peeku()),
+ mTimestampUs);
+ mTimestampUs =
+ work->worklets.front()->output.ordinal.timestamp.peeku();
+
+ ULock l(mQueueLock);
+ if (mTimestampDevTest) {
+ bool tsHit = false;
+ std::list<uint64_t>::iterator it = mTimestampUslist.begin();
+ while (it != mTimestampUslist.end()) {
+ if (*it == mTimestampUs) {
+ mTimestampUslist.erase(it);
+ tsHit = true;
+ break;
+ }
+ it++;
+ }
+ if (tsHit == false) {
+ if (mTimestampUslist.empty() == false) {
+ EXPECT_EQ(tsHit, true)
+ << "TimeStamp not recognized";
+ } else {
+ std::cout << "[ INFO ] Received non-zero "
+ "output / TimeStamp not recognized \n";
+ }
+ }
+ }
+ }
+
work->input.buffers.clear();
work->worklets.clear();
- typedef std::unique_lock<std::mutex> ULock;
- ULock l(mQueueLock);
- mWorkQueue.push_back(std::move(work));
- mQueueCondition.notify_all();
+ {
+ ULock l(mQueueLock);
+ mWorkQueue.push_back(std::move(work));
+ if (!mFlushedIndices.empty()) {
+ mFlushedIndices.erase(frameIndexIt);
+ }
+ mQueueCondition.notify_all();
+ }
}
}
@@ -184,6 +232,10 @@
bool mEos;
bool mDisableTest;
+ bool mTimestampDevTest;
+ uint64_t mTimestampUs;
+ std::list<uint64_t> mTimestampUslist;
+ std::list<uint64_t> mFlushedIndices;
standardComp mCompName;
uint32_t mFramesReceived;
C2BlockPool::local_id_t mBlockPoolId;
@@ -303,17 +355,13 @@
void decodeNFrames(const std::shared_ptr<android::Codec2Client::Component> &component,
std::mutex &queueLock, std::condition_variable &queueCondition,
std::list<std::unique_ptr<C2Work>> &workQueue,
- std::shared_ptr<C2Allocator> &linearAllocator,
+ std::list<uint64_t> &flushedIndices,
std::shared_ptr<C2BlockPool> &linearPool,
- C2BlockPool::local_id_t blockPoolId,
std::ifstream& eleStream,
android::Vector<FrameInfo>* Info,
int offset, int range, bool signalEOS = true) {
typedef std::unique_lock<std::mutex> ULock;
- int frameID = 0;
- linearPool =
- std::make_shared<C2PooledBlockPool>(linearAllocator, blockPoolId++);
- component->start();
+ int frameID = offset;
int maxRetry = 0;
while (1) {
if (frameID == (int)Info->size() || frameID == (offset + range)) break;
@@ -342,6 +390,10 @@
work->input.flags = (C2FrameData::flags_t)flags;
work->input.ordinal.timestamp = timestamp;
work->input.ordinal.frameIndex = frameID;
+ {
+ ULock l(queueLock);
+ flushedIndices.emplace_back(frameID);
+ }
int size = (*Info)[frameID].bytesCount;
char* data = (char*)malloc(size);
@@ -385,17 +437,18 @@
}
}
-void waitOnInputConsumption(std::mutex &queueLock,
- std::condition_variable &queueCondition,
- std::list<std::unique_ptr<C2Work>> &workQueue) {
+void waitOnInputConsumption(std::mutex& queueLock,
+ std::condition_variable& queueCondition,
+ std::list<std::unique_ptr<C2Work>>& workQueue,
+ size_t bufferCount = MAX_INPUT_BUFFERS) {
typedef std::unique_lock<std::mutex> ULock;
uint32_t queueSize;
- int maxRetry = 0;
+ uint32_t maxRetry = 0;
{
ULock l(queueLock);
queueSize = workQueue.size();
}
- while ((maxRetry < MAX_RETRY) && (queueSize < MAX_INPUT_BUFFERS)) {
+ while ((maxRetry < MAX_RETRY) && (queueSize < bufferCount)) {
ULock l(queueLock);
if (queueSize != workQueue.size()) {
queueSize = workQueue.size();
@@ -436,16 +489,21 @@
if (!(eleInfo >> bytesCount)) break;
eleInfo >> flags;
eleInfo >> timestamp;
+ bool codecConfig =
+ ((1 << (flags - 1)) & C2FrameData::FLAG_CODEC_CONFIG) != 0;
+ if (mTimestampDevTest && !codecConfig)
+ mTimestampUslist.push_back(timestamp);
Info.push_back({bytesCount, flags, timestamp});
}
eleInfo.close();
+ ASSERT_EQ(mComponent->start(), C2_OK);
ALOGV("mURL : %s", mURL);
eleStream.open(mURL, std::ifstream::binary);
ASSERT_EQ(eleStream.is_open(), true);
ASSERT_NO_FATAL_FAILURE(decodeNFrames(
- mComponent, mQueueLock, mQueueCondition, mWorkQueue, mLinearAllocator,
- mLinearPool, mBlockPoolId, eleStream, &Info, 0, (int)Info.size()));
+ mComponent, mQueueLock, mQueueCondition, mWorkQueue, mFlushedIndices,
+ mLinearPool, eleStream, &Info, 0, (int)Info.size()));
// blocking call to ensures application to Wait till all the inputs are
// consumed
@@ -462,16 +520,362 @@
Info.size());
ASSERT_TRUE(false);
}
+
+ if (mTimestampDevTest) EXPECT_EQ(mTimestampUslist.empty(), true);
+}
+
+
+// Adaptive Test
+TEST_F(Codec2VideoDecHidlTest, AdaptiveDecodeTest) {
+ description("Adaptive Decode Test");
+ if (mDisableTest) return;
+ if (!(mCompName == avc || mCompName == hevc || mCompName == vp8 ||
+ mCompName == vp9 || mCompName == mpeg2))
+ return;
+
+ typedef std::unique_lock<std::mutex> ULock;
+ ASSERT_EQ(mComponent->start(), C2_OK);
+
+ mTimestampDevTest = true;
+ uint32_t timestampOffset = 0;
+ uint32_t offset = 0;
+ android::Vector<FrameInfo> Info;
+ for (uint32_t i = 0; i < STREAM_COUNT * 2; i++) {
+ char mURL[512], info[512];
+ std::ifstream eleStream, eleInfo;
+
+ strcpy(mURL, gEnv->getRes().c_str());
+ strcpy(info, gEnv->getRes().c_str());
+ GetURLForComponent(mCompName, mURL, info, i % STREAM_COUNT);
+
+ eleInfo.open(info);
+ ASSERT_EQ(eleInfo.is_open(), true) << mURL << " - file not found";
+ int bytesCount = 0;
+ uint32_t flags = 0;
+ uint32_t timestamp = 0;
+ uint32_t timestampMax = 0;
+ while (1) {
+ if (!(eleInfo >> bytesCount)) break;
+ eleInfo >> flags;
+ eleInfo >> timestamp;
+ timestamp += timestampOffset;
+ Info.push_back({bytesCount, flags, timestamp});
+ bool codecConfig =
+ ((1 << (flags - 1)) & C2FrameData::FLAG_CODEC_CONFIG) != 0;
+ {
+ ULock l(mQueueLock);
+ if (mTimestampDevTest && !codecConfig)
+ mTimestampUslist.push_back(timestamp);
+ }
+ if (timestampMax < timestamp) timestampMax = timestamp;
+ }
+ timestampOffset = timestampMax;
+ eleInfo.close();
+
+ // Reset Total frames before second decode loop
+ // mFramesReceived = 0;
+ ALOGV("mURL : %s", mURL);
+ eleStream.open(mURL, std::ifstream::binary);
+ ASSERT_EQ(eleStream.is_open(), true);
+ ASSERT_NO_FATAL_FAILURE(
+ decodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
+ mFlushedIndices, mLinearPool, eleStream, &Info,
+ offset, (int)(Info.size() - offset), false));
+
+ eleStream.close();
+ offset = (int)Info.size();
+ }
+
+ // Send EOS
+ // TODO Add function to send EOS work item
+ int maxRetry = 0;
+ std::unique_ptr<C2Work> work;
+ while (!work && (maxRetry < MAX_RETRY)) {
+ ULock l(mQueueLock);
+ if (!mWorkQueue.empty()) {
+ work.swap(mWorkQueue.front());
+ mWorkQueue.pop_front();
+ } else {
+ mQueueCondition.wait_for(l, TIME_OUT);
+ maxRetry++;
+ }
+ }
+ ASSERT_NE(work, nullptr);
+ work->input.flags = (C2FrameData::flags_t)C2FrameData::FLAG_END_OF_STREAM;
+ work->input.ordinal.timestamp = 0;
+ work->input.ordinal.frameIndex = 0;
+ work->input.buffers.clear();
+ work->worklets.clear();
+ work->worklets.emplace_back(new C2Worklet);
+
+ std::list<std::unique_ptr<C2Work>> items;
+ items.push_back(std::move(work));
+ ASSERT_EQ(mComponent->queue(&items), C2_OK);
+
+ // blocking call to ensures application to Wait till all the inputs are
+ // consumed
+ ALOGV("Waiting for input consumption");
+ ASSERT_NO_FATAL_FAILURE(
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue));
+
+ if (mFramesReceived != ((Info.size()) + 1)) {
+ ALOGE("Input buffer count and Output buffer count mismatch");
+ ALOGV("framesReceived : %d inputFrames : %zu", mFramesReceived,
+ Info.size() + 1);
+ ASSERT_TRUE(false);
+ }
+
+ if (mTimestampDevTest) EXPECT_EQ(mTimestampUslist.empty(), true);
+}
+
+// thumbnail test
+TEST_F(Codec2VideoDecHidlTest, ThumbnailTest) {
+ description("Test Request for thumbnail");
+ if (mDisableTest) return;
+
+ char mURL[512], info[512];
+ std::ifstream eleStream, eleInfo;
+
+ strcpy(mURL, gEnv->getRes().c_str());
+ strcpy(info, gEnv->getRes().c_str());
+ GetURLForComponent(mCompName, mURL, info);
+
+ eleInfo.open(info);
+ ASSERT_EQ(eleInfo.is_open(), true);
+ android::Vector<FrameInfo> Info;
+ int bytesCount = 0;
+ uint32_t flags = 0;
+ uint32_t timestamp = 0;
+ while (1) {
+ if (!(eleInfo >> bytesCount)) break;
+ eleInfo >> flags;
+ eleInfo >> timestamp;
+ Info.push_back({bytesCount, flags, timestamp});
+ }
+ eleInfo.close();
+
+ for (size_t i = 0; i < MAX_ITERATIONS; i++) {
+ ASSERT_EQ(mComponent->start(), C2_OK);
+
+ // request EOS for thumbnail
+ // signal EOS flag with last frame
+ size_t j = -1;
+ do {
+ j++;
+ flags = 0;
+ if (Info[j].flags) flags = 1u << (Info[j].flags - 1);
+
+ } while (!(flags & SYNC_FRAME));
+ eleStream.open(mURL, std::ifstream::binary);
+ ASSERT_EQ(eleStream.is_open(), true);
+ ASSERT_NO_FATAL_FAILURE(decodeNFrames(
+ mComponent, mQueueLock, mQueueCondition, mWorkQueue,
+ mFlushedIndices, mLinearPool, eleStream, &Info, 0, j + 1));
+ ASSERT_NO_FATAL_FAILURE(
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue));
+ eleStream.close();
+ EXPECT_GE(mFramesReceived, 1U);
+ ASSERT_EQ(mEos, true);
+ ASSERT_EQ(mComponent->stop(), C2_OK);
+ ASSERT_EQ(mComponent->release(), C2_OK);
+ }
+}
+
+TEST_F(Codec2VideoDecHidlTest, EOSTest) {
+ description("Test empty input buffer with EOS flag");
+ if (mDisableTest) return;
+ typedef std::unique_lock<std::mutex> ULock;
+ ASSERT_EQ(mComponent->start(), C2_OK);
+ std::unique_ptr<C2Work> work;
+ // Prepare C2Work
+ {
+ ULock l(mQueueLock);
+ if (!mWorkQueue.empty()) {
+ work.swap(mWorkQueue.front());
+ mWorkQueue.pop_front();
+ } else {
+ ASSERT_TRUE(false) << "mWorkQueue Empty at the start of test";
+ }
+ }
+ ASSERT_NE(work, nullptr);
+
+ work->input.flags = (C2FrameData::flags_t)C2FrameData::FLAG_END_OF_STREAM;
+ work->input.ordinal.timestamp = 0;
+ work->input.ordinal.frameIndex = 0;
+ work->input.buffers.clear();
+ work->worklets.clear();
+ work->worklets.emplace_back(new C2Worklet);
+
+ std::list<std::unique_ptr<C2Work>> items;
+ items.push_back(std::move(work));
+ ASSERT_EQ(mComponent->queue(&items), C2_OK);
+
+ {
+ typedef std::unique_lock<std::mutex> ULock;
+ ULock l(mQueueLock);
+ if (mWorkQueue.size() != MAX_INPUT_BUFFERS) {
+ mQueueCondition.wait_for(l, TIME_OUT);
+ }
+ }
+ ASSERT_EQ(mEos, true);
+ ASSERT_EQ(mWorkQueue.size(), (size_t)MAX_INPUT_BUFFERS);
+ ASSERT_EQ(mComponent->stop(), C2_OK);
+}
+
+TEST_F(Codec2VideoDecHidlTest, EmptyBufferTest) {
+ description("Tests empty input buffer");
+ if (mDisableTest) return;
+ typedef std::unique_lock<std::mutex> ULock;
+ ASSERT_EQ(mComponent->start(), C2_OK);
+ std::unique_ptr<C2Work> work;
+ // Prepare C2Work
+ {
+ ULock l(mQueueLock);
+ if (!mWorkQueue.empty()) {
+ work.swap(mWorkQueue.front());
+ mWorkQueue.pop_front();
+ } else {
+ ASSERT_TRUE(false) << "mWorkQueue Empty at the start of test";
+ }
+ }
+ ASSERT_NE(work, nullptr);
+
+ work->input.flags = (C2FrameData::flags_t)0;
+ work->input.ordinal.timestamp = 0;
+ work->input.ordinal.frameIndex = 0;
+ work->input.buffers.clear();
+ work->worklets.clear();
+ work->worklets.emplace_back(new C2Worklet);
+
+ std::list<std::unique_ptr<C2Work>> items;
+ items.push_back(std::move(work));
+ ASSERT_EQ(mComponent->queue(&items), C2_OK);
+
+ {
+ typedef std::unique_lock<std::mutex> ULock;
+ ULock l(mQueueLock);
+ if (mWorkQueue.size() != MAX_INPUT_BUFFERS) {
+ mQueueCondition.wait_for(l, TIME_OUT);
+ }
+ }
+ ASSERT_EQ(mWorkQueue.size(), (size_t)MAX_INPUT_BUFFERS);
+ ASSERT_EQ(mComponent->stop(), C2_OK);
+}
+
+TEST_F(Codec2VideoDecHidlTest, FlushTest) {
+ description("Tests Flush calls");
+ if (mDisableTest) return;
+ typedef std::unique_lock<std::mutex> ULock;
+ ASSERT_EQ(mComponent->start(), C2_OK);
+ char mURL[512], info[512];
+ std::ifstream eleStream, eleInfo;
+
+ strcpy(mURL, gEnv->getRes().c_str());
+ strcpy(info, gEnv->getRes().c_str());
+ GetURLForComponent(mCompName, mURL, info);
+
+ eleInfo.open(info);
+ ASSERT_EQ(eleInfo.is_open(), true);
+ android::Vector<FrameInfo> Info;
+ int bytesCount = 0;
+ uint32_t flags = 0;
+ uint32_t timestamp = 0;
+ mFlushedIndices.clear();
+ while (1) {
+ if (!(eleInfo >> bytesCount)) break;
+ eleInfo >> flags;
+ eleInfo >> timestamp;
+ Info.push_back({bytesCount, flags, timestamp});
+ }
+ eleInfo.close();
+
+ ALOGV("mURL : %s", mURL);
+ eleStream.open(mURL, std::ifstream::binary);
+ ASSERT_EQ(eleStream.is_open(), true);
+ // Decode 128 frames and flush. here 128 is chosen to ensure there is a key
+ // frame after this so that the below section can be covered for all
+ // components
+ uint32_t numFramesFlushed = 128;
+ ASSERT_NO_FATAL_FAILURE(decodeNFrames(
+ mComponent, mQueueLock, mQueueCondition, mWorkQueue, mFlushedIndices,
+ mLinearPool, eleStream, &Info, 0, numFramesFlushed, false));
+ // flush
+ std::list<std::unique_ptr<C2Work>> flushedWork;
+ c2_status_t err =
+ mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
+ ASSERT_EQ(err, C2_OK);
+ ASSERT_NO_FATAL_FAILURE(
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
+ (size_t)MAX_INPUT_BUFFERS - flushedWork.size()));
+
+ {
+ // Update mFlushedIndices based on the index received from flush()
+ ULock l(mQueueLock);
+ for (std::unique_ptr<C2Work>& work : flushedWork) {
+ ASSERT_NE(work, nullptr);
+ auto frameIndexIt =
+ std::find(mFlushedIndices.begin(), mFlushedIndices.end(),
+ work->input.ordinal.frameIndex.peeku());
+ if (!mFlushedIndices.empty() &&
+ (frameIndexIt != mFlushedIndices.end())) {
+ mFlushedIndices.erase(frameIndexIt);
+ work->input.buffers.clear();
+ work->worklets.clear();
+ mWorkQueue.push_back(std::move(work));
+ }
+ }
+ }
+ // Seek to next key frame and start decoding till the end
+ mFlushedIndices.clear();
+ int index = numFramesFlushed;
+ bool keyFrame = false;
+ flags = 0;
+ while (index < (int)Info.size()) {
+ if (Info[index].flags) flags = 1u << (Info[index].flags - 1);
+ if ((flags & SYNC_FRAME) == SYNC_FRAME) {
+ keyFrame = true;
+ break;
+ }
+ flags = 0;
+ eleStream.ignore(Info[index].bytesCount);
+ index++;
+ }
+ if (keyFrame) {
+ ASSERT_NO_FATAL_FAILURE(
+ decodeNFrames(mComponent, mQueueLock, mQueueCondition, mWorkQueue,
+ mFlushedIndices, mLinearPool, eleStream, &Info, index,
+ (int)Info.size() - index));
+ }
+ eleStream.close();
+ err = mComponent->flush(C2Component::FLUSH_COMPONENT, &flushedWork);
+ ASSERT_EQ(err, C2_OK);
+ ASSERT_NO_FATAL_FAILURE(
+ waitOnInputConsumption(mQueueLock, mQueueCondition, mWorkQueue,
+ (size_t)MAX_INPUT_BUFFERS - flushedWork.size()));
+ {
+ // Update mFlushedIndices based on the index received from flush()
+ ULock l(mQueueLock);
+ for (std::unique_ptr<C2Work>& work : flushedWork) {
+ ASSERT_NE(work, nullptr);
+ uint64_t frameIndex = work->input.ordinal.frameIndex.peeku();
+ std::list<uint64_t>::iterator frameIndexIt = std::find(
+ mFlushedIndices.begin(), mFlushedIndices.end(), frameIndex);
+ if (!mFlushedIndices.empty() &&
+ (frameIndexIt != mFlushedIndices.end())) {
+ mFlushedIndices.erase(frameIndexIt);
+ work->input.buffers.clear();
+ work->worklets.clear();
+ mWorkQueue.push_back(std::move(work));
+ }
+ }
+ }
+ ASSERT_EQ(mFlushedIndices.empty(), true);
+ ASSERT_EQ(mComponent->stop(), C2_OK);
}
} // anonymous namespace
// TODO : Video specific configuration Test
-// TODO : Thumbnail Test
-// TODO : Test EOS
-// TODO : Flush Test
-// TODO : Check timestamps deviation
-// TODO : Adaptive Test
int main(int argc, char** argv) {
gEnv = new ComponentTestEnvironment();
::testing::AddGlobalTestEnvironment(gEnv);
diff --git a/codec2/hidl/1.0/mts/video/media_c2_video_hidl_test_common.h b/codec2/hidl/1.0/mts/video/media_c2_video_hidl_test_common.h
index b717390..1215b13 100644
--- a/codec2/hidl/1.0/mts/video/media_c2_video_hidl_test_common.h
+++ b/codec2/hidl/1.0/mts/video/media_c2_video_hidl_test_common.h
@@ -24,6 +24,7 @@
#define ENC_NUM_FRAMES 32
#define ENC_DEFAULT_FRAME_WIDTH 352
#define ENC_DEFAULT_FRAME_HEIGHT 288
+#define MAX_ITERATIONS 128
/*
* Common video utils