From ed9ba5e08c74e78cf41b4864bc2fc827699cf8e6 Mon Sep 17 00:00:00 2001 From: Amy Date: Tue, 17 Dec 2019 15:44:36 -0800 Subject: [PATCH] Add a DVR Record data flow test in the Tuner HAL 1.0 VTS Test: atest on cuttlefish Bug: 135708935 Change-Id: I3923b7bcfae6ea07a46603bd9f97e743d36284e2 (cherry picked from commit 97ce9c553d1e446ab52da974ac0b66c97be65d32) Merged-In: I3923b7bcfae6ea07a46603bd9f97e743d36284e2 --- .../VtsHalTvTunerV1_0TargetTest.cpp | 284 +++++++++++++++++- 1 file changed, 282 insertions(+), 2 deletions(-) diff --git a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp index da3e300fc5..7977f25389 100644 --- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp +++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp @@ -65,6 +65,7 @@ using android::hardware::tv::tuner::V1_0::DemuxFilterEvent; using android::hardware::tv::tuner::V1_0::DemuxFilterMainType; using android::hardware::tv::tuner::V1_0::DemuxFilterPesDataSettings; using android::hardware::tv::tuner::V1_0::DemuxFilterPesEvent; +using android::hardware::tv::tuner::V1_0::DemuxFilterRecordSettings; using android::hardware::tv::tuner::V1_0::DemuxFilterSectionEvent; using android::hardware::tv::tuner::V1_0::DemuxFilterSectionSettings; using android::hardware::tv::tuner::V1_0::DemuxFilterSettings; @@ -95,6 +96,7 @@ using android::hardware::tv::tuner::V1_0::IFrontendCallback; using android::hardware::tv::tuner::V1_0::ITuner; using android::hardware::tv::tuner::V1_0::PlaybackSettings; using android::hardware::tv::tuner::V1_0::PlaybackStatus; +using android::hardware::tv::tuner::V1_0::RecordSettings; using android::hardware::tv::tuner::V1_0::RecordStatus; using android::hardware::tv::tuner::V1_0::Result; @@ -379,7 +381,20 @@ bool FilterCallback::readFilterEventData() { class DvrCallback : public IDvrCallback { public: - virtual Return onRecordStatus(RecordStatus /*status*/) override { return Void(); } + virtual Return onRecordStatus(DemuxFilterStatus status) override { + ALOGW("[vts] record status %hhu", status); + switch (status) { + case DemuxFilterStatus::DATA_READY: + break; + case DemuxFilterStatus::LOW_WATER: + break; + case DemuxFilterStatus::HIGH_WATER: + case DemuxFilterStatus::OVERFLOW: + ALOGW("[vts] record overflow. Flushing"); + break; + } + return Void(); + } virtual Return onPlaybackStatus(PlaybackStatus status) override { // android::Mutex::Autolock autoLock(mMsgLock); @@ -401,10 +416,17 @@ class DvrCallback : public IDvrCallback { void testFilterDataOutput(); void stopPlaybackThread(); + void testRecordOutput(); + void stopRecordThread(); void startPlaybackInputThread(PlaybackConf playbackConf, MQDesc& playbackMQDescriptor); + void startRecordOutputThread(RecordSettings recordSetting, MQDesc& recordMQDescriptor); static void* __threadLoopPlayback(void* threadArgs); + static void* __threadLoopRecord(void* threadArgs); void playbackThreadLoop(PlaybackConf* playbackConf, bool* keepWritingPlaybackFMQ); + void recordThreadLoop(RecordSettings* recordSetting, bool* keepWritingPlaybackFMQ); + + bool readRecordFMQ(); private: struct PlaybackThreadArgs { @@ -412,22 +434,31 @@ class DvrCallback : public IDvrCallback { PlaybackConf* playbackConf; bool* keepWritingPlaybackFMQ; }; + struct RecordThreadArgs { + DvrCallback* user; + RecordSettings* recordSetting; + bool* keepReadingRecordFMQ; + }; uint16_t mDataLength = 0; std::vector mDataOutputBuffer; std::map> mFilterIdToMQ; std::unique_ptr mPlaybackMQ; + std::unique_ptr mRecordMQ; std::map mFilterIdToMQEventFlag; std::map mFilterIdToEvent; - EventFlag* mPlaybackMQEventFlag; android::Mutex mMsgLock; android::Mutex mPlaybackThreadLock; + android::Mutex mRecordThreadLock; android::Condition mMsgCondition; bool mKeepWritingPlaybackFMQ = true; + bool mKeepReadingRecordFMQ = true; bool mPlaybackThreadRunning; + bool mRecordThreadRunning; pthread_t mPlaybackThread; + pthread_t mRecordThread; int mPidFilterOutputCount = 0; }; @@ -516,6 +547,92 @@ void DvrCallback::playbackThreadLoop(PlaybackConf* playbackConf, bool* keepWriti inputData.close(); } +void DvrCallback::testRecordOutput() { + android::Mutex::Autolock autoLock(mMsgLock); + while (mDataOutputBuffer.empty()) { + if (-ETIMEDOUT == mMsgCondition.waitRelative(mMsgLock, WAIT_TIMEOUT)) { + EXPECT_TRUE(false) << "record output matching pid does not output within timeout"; + return; + } + } + stopRecordThread(); + ALOGW("[vts] record pass and stop"); +} + +void DvrCallback::startRecordOutputThread(RecordSettings recordSetting, + MQDesc& recordMQDescriptor) { + mRecordMQ = std::make_unique(recordMQDescriptor, true /* resetPointers */); + EXPECT_TRUE(mRecordMQ); + struct RecordThreadArgs* threadArgs = + (struct RecordThreadArgs*)malloc(sizeof(struct RecordThreadArgs)); + threadArgs->user = this; + threadArgs->recordSetting = &recordSetting; + threadArgs->keepReadingRecordFMQ = &mKeepReadingRecordFMQ; + + pthread_create(&mRecordThread, NULL, __threadLoopRecord, (void*)threadArgs); + pthread_setname_np(mRecordThread, "test_record_input_loop"); +} + +void* DvrCallback::__threadLoopRecord(void* threadArgs) { + DvrCallback* const self = + static_cast(((struct RecordThreadArgs*)threadArgs)->user); + self->recordThreadLoop(((struct RecordThreadArgs*)threadArgs)->recordSetting, + ((struct RecordThreadArgs*)threadArgs)->keepReadingRecordFMQ); + return 0; +} + +void DvrCallback::recordThreadLoop(RecordSettings* /*recordSetting*/, bool* keepReadingRecordFMQ) { + ALOGD("[vts] DvrCallback record threadLoop start."); + android::Mutex::Autolock autoLock(mRecordThreadLock); + mRecordThreadRunning = true; + + // Create the EventFlag that is used to signal the HAL impl that data have been + // read from the Record FMQ + EventFlag* recordMQEventFlag; + EXPECT_TRUE(EventFlag::createEventFlag(mRecordMQ->getEventFlagWord(), &recordMQEventFlag) == + android::OK); + + while (mRecordThreadRunning) { + while (*keepReadingRecordFMQ) { + uint32_t efState = 0; + android::status_t status = recordMQEventFlag->wait( + static_cast(DemuxQueueNotifyBits::DATA_READY), &efState, WAIT_TIMEOUT, + true /* retry on spurious wake */); + if (status != android::OK) { + ALOGD("[vts] wait for data ready on the record FMQ"); + continue; + } + // Our current implementation filter the data and write it into the filter FMQ + // immediately after the DATA_READY from the VTS/framework + if (!readRecordFMQ()) { + ALOGD("[vts] record data failed to be filtered. Ending thread"); + mRecordThreadRunning = false; + break; + } + } + } + + mRecordThreadRunning = false; + ALOGD("[vts] record thread ended."); +} + +bool DvrCallback::readRecordFMQ() { + android::Mutex::Autolock autoLock(mMsgLock); + bool result = false; + mDataOutputBuffer.clear(); + mDataOutputBuffer.resize(mRecordMQ->availableToRead()); + result = mRecordMQ->read(mDataOutputBuffer.data(), mRecordMQ->availableToRead()); + EXPECT_TRUE(result) << "can't read from Record MQ"; + mMsgCondition.signal(); + return result; +} + +void DvrCallback::stopRecordThread() { + mKeepReadingRecordFMQ = false; + mRecordThreadRunning = false; + android::Mutex::Autolock autoLock(mRecordThreadLock); +} + // Test environment for Tuner HIDL HAL. class TunerHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { public: @@ -555,6 +672,7 @@ class TunerHidlTest : public ::testing::VtsHalHidlTargetTestBase { sp mDvrCallback; MQDesc mFilterMQDescriptor; MQDesc mPlaybackMQDescriptor; + MQDesc mRecordMQDescriptor; vector mUsedFilterIds; uint32_t mDemuxId; @@ -572,6 +690,8 @@ class TunerHidlTest : public ::testing::VtsHalHidlTargetTestBase { FrontendSettings settings); ::testing::AssertionResult getPlaybackMQDescriptor(); ::testing::AssertionResult addPlaybackToDemux(PlaybackSettings setting); + ::testing::AssertionResult getRecordMQDescriptor(); + ::testing::AssertionResult addRecordToDemux(RecordSettings setting); ::testing::AssertionResult addFilterToDemux(DemuxFilterType type, DemuxFilterSettings setting); ::testing::AssertionResult getFilterMQDescriptor(); ::testing::AssertionResult closeDemux(); @@ -581,6 +701,9 @@ class TunerHidlTest : public ::testing::VtsHalHidlTargetTestBase { ::testing::AssertionResult playbackDataFlowTest(vector filterConf, PlaybackConf playbackConf, vector goldenOutputFiles); + ::testing::AssertionResult recordDataFlowTest(vector filterConf, + RecordSettings recordSetting, + vector goldenOutputFiles); ::testing::AssertionResult broadcastDataFlowTest(vector filterConf, vector goldenOutputFiles); }; @@ -766,6 +889,49 @@ class TunerHidlTest : public ::testing::VtsHalHidlTargetTestBase { return ::testing::AssertionResult(status == Result::SUCCESS); } +::testing::AssertionResult TunerHidlTest::addRecordToDemux(RecordSettings setting) { + Result status; + + if (!mDemux && createDemux() == ::testing::AssertionFailure()) { + return ::testing::AssertionFailure(); + } + + // Create dvr callback + mDvrCallback = new DvrCallback(); + + // Add playback input to the local demux + mDemux->openDvr(DvrType::RECORD, FMQ_SIZE_1M, mDvrCallback, + [&](Result result, const sp& dvr) { + mDvr = dvr; + status = result; + }); + + if (status != Result::SUCCESS) { + return ::testing::AssertionFailure(); + } + + DvrSettings dvrSetting; + dvrSetting.record(setting); + status = mDvr->configure(dvrSetting); + + return ::testing::AssertionResult(status == Result::SUCCESS); +} + +::testing::AssertionResult TunerHidlTest::getRecordMQDescriptor() { + Result status; + + if ((!mDemux && createDemux() == ::testing::AssertionFailure()) || !mDvr) { + return ::testing::AssertionFailure(); + } + + mDvr->getQueueDesc([&](Result result, const MQDesc& dvrMQDesc) { + mRecordMQDescriptor = dvrMQDesc; + status = result; + }); + + return ::testing::AssertionResult(status == Result::SUCCESS); +} + ::testing::AssertionResult TunerHidlTest::addFilterToDemux(DemuxFilterType type, DemuxFilterSettings setting) { Result status; @@ -997,6 +1163,82 @@ class TunerHidlTest : public ::testing::VtsHalHidlTargetTestBase { return closeDemux(); } +::testing::AssertionResult TunerHidlTest::recordDataFlowTest(vector filterConf, + RecordSettings recordSetting, + vector /*goldenOutputFiles*/) { + Result status; + hidl_vec feIds; + + mService->getFrontendIds([&](Result result, const hidl_vec& frontendIds) { + status = result; + feIds = frontendIds; + }); + + if (feIds.size() == 0) { + ALOGW("[ WARN ] Frontend isn't available"); + return ::testing::AssertionFailure(); + } + + FrontendDvbtSettings dvbt{ + .frequency = 1000, + }; + FrontendSettings settings; + settings.dvbt(dvbt); + + int filterIdsSize; + // Filter Configuration Module + for (int i = 0; i < filterConf.size(); i++) { + if (addFilterToDemux(filterConf[i].type, filterConf[i].setting) == + ::testing::AssertionFailure() || + // TODO use a map to save the FMQs/EvenFlags and pass to callback + getFilterMQDescriptor() == ::testing::AssertionFailure()) { + return ::testing::AssertionFailure(); + } + filterIdsSize = mUsedFilterIds.size(); + mUsedFilterIds.resize(filterIdsSize + 1); + mUsedFilterIds[filterIdsSize] = mFilterId; + mFilters[mFilterId] = mFilter; + } + + // Record Config Module + if (addRecordToDemux(recordSetting) == ::testing::AssertionFailure() || + getRecordMQDescriptor() == ::testing::AssertionFailure()) { + return ::testing::AssertionFailure(); + } + for (int i = 0; i <= filterIdsSize; i++) { + if (mDvr->attachFilter(mFilters[mUsedFilterIds[i]]) != Result::SUCCESS) { + return ::testing::AssertionFailure(); + } + } + + mDvrCallback->startRecordOutputThread(recordSetting, mRecordMQDescriptor); + status = mDvr->start(); + if (status != Result::SUCCESS) { + return ::testing::AssertionFailure(); + } + + if (createDemuxWithFrontend(feIds[0], settings) != ::testing::AssertionSuccess()) { + return ::testing::AssertionFailure(); + } + + // Data Verify Module + mDvrCallback->testRecordOutput(); + + // Clean Up Module + for (int i = 0; i <= filterIdsSize; i++) { + if (mFilters[mUsedFilterIds[i]]->stop() != Result::SUCCESS) { + return ::testing::AssertionFailure(); + } + } + if (mFrontend->stopTune() != Result::SUCCESS) { + return ::testing::AssertionFailure(); + } + mUsedFilterIds.clear(); + mFilterCallbacks.clear(); + mFilters.clear(); + return closeDemux(); +} + /* * API STATUS TESTS */ @@ -1203,6 +1445,44 @@ TEST_F(TunerHidlTest, BroadcastDataFlowWithPesFilterTest) { ASSERT_TRUE(broadcastDataFlowTest(filterConf, goldenOutputFiles)); } +TEST_F(TunerHidlTest, RecordDataFlowWithTsRecordFilterTest) { + description("Feed ts data from frontend to recording and test with ts record filter"); + + // todo modulize the filter conf parser + vector filterConf; + filterConf.resize(1); + + DemuxFilterSettings filterSetting; + DemuxTsFilterSettings tsFilterSetting{ + .tpid = 119, + }; + DemuxFilterRecordSettings recordFilterSetting; + tsFilterSetting.filterSettings.record(recordFilterSetting); + filterSetting.ts(tsFilterSetting); + + DemuxFilterType type{ + .mainType = DemuxFilterMainType::TS, + }; + type.subType.tsFilterType(DemuxTsFilterType::RECORD); + FilterConf recordFilterConf{ + .type = type, + .setting = filterSetting, + }; + filterConf[0] = recordFilterConf; + + RecordSettings recordSetting{ + .statusMask = 0xf, + .lowThreshold = 0x1000, + .highThreshold = 0x07fff, + .dataFormat = DataFormat::TS, + .packetSize = 188, + }; + + vector goldenOutputFiles; + + ASSERT_TRUE(recordDataFlowTest(filterConf, recordSetting, goldenOutputFiles)); +} + } // namespace int main(int argc, char** argv) {