From b442c9bc17a175b247e578fb3fccca6295d59010 Mon Sep 17 00:00:00 2001 From: Henry Fang Date: Tue, 3 Dec 2019 18:13:01 -0800 Subject: [PATCH 1/3] Tuner HAL minor corrections Test: Manual bug: 135708935 Change-Id: I2f74b2a4266ea86a80dab8b729bf57a78319b207 (cherry picked from commit d5f570e7d791f6777e808fde6d7f7dffad14353d) Merged-In: I2f74b2a4266ea86a80dab8b729bf57a78319b207 --- tv/tuner/1.0/IFilter.hal | 2 +- tv/tuner/1.0/types.hal | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tv/tuner/1.0/IFilter.hal b/tv/tuner/1.0/IFilter.hal index 3ed09f6db5..94e3c0ce26 100644 --- a/tv/tuner/1.0/IFilter.hal +++ b/tv/tuner/1.0/IFilter.hal @@ -104,11 +104,11 @@ interface IFilter { * * It is used by the client to ask the hardware resource id for the filter. * - * @param filterId the hardware resource Id for the filter. * @return result Result status of the operation. * SUCCESS if successful, * INVALID_STATE if failed for wrong state. * UNKNOWN_ERROR if failed for other reasons. + * @return filterId the hardware resource Id for the filter. */ getId() generates (Result result, uint32_t filterId); diff --git a/tv/tuner/1.0/types.hal b/tv/tuner/1.0/types.hal index ef33952cd9..944da5a840 100644 --- a/tv/tuner/1.0/types.hal +++ b/tv/tuner/1.0/types.hal @@ -2230,8 +2230,6 @@ struct DemuxIpFilterSettings { DemuxFilterSectionSettings section; - DemuxFilterPesDataSettings pesData; - /** * true if the data from IP subtype go to next filter directly */ @@ -2248,7 +2246,7 @@ struct DemuxTlvFilterSettings { /** * true if the filtered data is commpressed ip packet */ - bool bIsCompressedIpPacket; + bool isCompressedIpPacket; safe_union FilterSettings { /** From 4f68e81e701983e9a9e0f4cd689f098bdb2e1029 Mon Sep 17 00:00:00 2001 From: Amy Date: Wed, 11 Dec 2019 15:33:51 -0800 Subject: [PATCH 2/3] Adding a DVR Record default implementation in Tuner HAL 1.0 Test: cuttlefish Bug: 135709325 Change-Id: I415426d6ec048bdd2ae61a3c5142ad02f1d7f1e4 (cherry picked from commit 5ed13574aa41138358ad040ebd62daad8b4497c0) Merged-In: I415426d6ec048bdd2ae61a3c5142ad02f1d7f1e4 --- tv/tuner/1.0/default/Demux.cpp | 112 +++++++++++++++++++++++++------- tv/tuner/1.0/default/Demux.h | 39 ++++++++--- tv/tuner/1.0/default/Dvr.cpp | 65 +++++++++++++++++- tv/tuner/1.0/default/Dvr.h | 15 ++++- tv/tuner/1.0/default/Filter.cpp | 51 ++++++++++++--- tv/tuner/1.0/default/Filter.h | 13 +++- tv/tuner/1.0/default/Tuner.cpp | 2 +- 7 files changed, 247 insertions(+), 50 deletions(-) diff --git a/tv/tuner/1.0/default/Demux.cpp b/tv/tuner/1.0/default/Demux.cpp index 71a26ab6ec..43c4e3a086 100644 --- a/tv/tuner/1.0/default/Demux.cpp +++ b/tv/tuner/1.0/default/Demux.cpp @@ -51,7 +51,8 @@ Return Demux::setFrontendDataSource(uint32_t frontendId) { mFrontendSourceFile = mFrontend->getSourceFile(); mTunerService->setFrontendAsDemuxSource(frontendId, mDemuxId); - return startBroadcastInputLoop(); + + return startFrontendInputLoop(); } Return Demux::openFilter(const DemuxFilterType& type, uint32_t bufferSize, @@ -136,14 +137,14 @@ Return Demux::openDvr(DvrType type, uint32_t bufferSize, const sp dvr = new Dvr(type, bufferSize, cb, this); + mDvr = new Dvr(type, bufferSize, cb, this); - if (!dvr->createDvrMQ()) { - _hidl_cb(Result::UNKNOWN_ERROR, dvr); + if (!mDvr->createDvrMQ()) { + _hidl_cb(Result::UNKNOWN_ERROR, mDvr); return Void(); } - _hidl_cb(Result::SUCCESS, dvr); + _hidl_cb(Result::SUCCESS, mDvr); return Void(); } @@ -166,13 +167,14 @@ Result Demux::removeFilter(uint32_t filterId) { // resetFilterRecords(filterId); mUsedFilterIds.erase(filterId); + mRecordFilterIds.erase(filterId); mUnusedFilterIds.insert(filterId); mFilters.erase(filterId); return Result::SUCCESS; } -void Demux::startTsFilter(vector data) { +void Demux::startBroadcastTsFilter(vector data) { set::iterator it; for (it = mUsedFilterIds.begin(); it != mUsedFilterIds.end(); it++) { uint16_t pid = ((data[1] & 0x1f) << 8) | ((data[2] & 0xff)); @@ -185,7 +187,17 @@ void Demux::startTsFilter(vector data) { } } -bool Demux::startFilterDispatcher() { +void Demux::sendFrontendInputToRecord(vector data) { + set::iterator it; + for (it = mRecordFilterIds.begin(); it != mRecordFilterIds.end(); it++) { + if (DEBUG_FILTER) { + ALOGW("update record filter output"); + } + mFilters[*it]->updateRecordOutput(data); + } +} + +bool Demux::startBroadcastFilterDispatcher() { set::iterator it; // Handle the output data per filter type @@ -198,6 +210,18 @@ bool Demux::startFilterDispatcher() { return true; } +bool Demux::startRecordFilterDispatcher() { + set::iterator it; + + for (it = mRecordFilterIds.begin(); it != mRecordFilterIds.end(); it++) { + if (mFilters[*it]->startRecordFilterHandler() != Result::SUCCESS) { + return false; + } + } + + return true; +} + Result Demux::startFilterHandler(uint32_t filterId) { return mFilters[filterId]->startFilterHandler(); } @@ -210,22 +234,22 @@ uint16_t Demux::getFilterTpid(uint32_t filterId) { return mFilters[filterId]->getTpid(); } -Result Demux::startBroadcastInputLoop() { - pthread_create(&mBroadcastInputThread, NULL, __threadLoopBroadcast, this); - pthread_setname_np(mBroadcastInputThread, "broadcast_input_thread"); +Result Demux::startFrontendInputLoop() { + pthread_create(&mFrontendInputThread, NULL, __threadLoopFrontend, this); + pthread_setname_np(mFrontendInputThread, "frontend_input_thread"); return Result::SUCCESS; } -void* Demux::__threadLoopBroadcast(void* user) { +void* Demux::__threadLoopFrontend(void* user) { Demux* const self = static_cast(user); - self->broadcastInputThreadLoop(); + self->frontendInputThreadLoop(); return 0; } -void Demux::broadcastInputThreadLoop() { - std::lock_guard lock(mBroadcastInputThreadLock); - mBroadcastInputThreadRunning = true; +void Demux::frontendInputThreadLoop() { + std::lock_guard lock(mFrontendInputThreadLock); + mFrontendInputThreadRunning = true; mKeepFetchingDataFromFrontend = true; // open the stream and get its length @@ -234,20 +258,20 @@ void Demux::broadcastInputThreadLoop() { int packetSize = 188; int writePacketAmount = 6; char* buffer = new char[packetSize]; - ALOGW("[Demux] broadcast input thread loop start %s", mFrontendSourceFile.c_str()); + ALOGW("[Demux] Frontend input thread loop start %s", mFrontendSourceFile.c_str()); if (!inputData.is_open()) { - mBroadcastInputThreadRunning = false; + mFrontendInputThreadRunning = false; ALOGW("[Demux] Error %s", strerror(errno)); } - while (mBroadcastInputThreadRunning) { + while (mFrontendInputThreadRunning) { // move the stream pointer for packet size * 6 every read until the end while (mKeepFetchingDataFromFrontend) { for (int i = 0; i < writePacketAmount; i++) { inputData.read(buffer, packetSize); if (!inputData) { mKeepFetchingDataFromFrontend = false; - mBroadcastInputThreadRunning = false; + mFrontendInputThreadRunning = false; break; } // filter and dispatch filter output @@ -256,23 +280,61 @@ void Demux::broadcastInputThreadLoop() { for (int index = 0; index < byteBuffer.size(); index++) { byteBuffer[index] = static_cast(buffer[index]); } - startTsFilter(byteBuffer); + if (mIsRecording) { + // Feed the data into the Dvr recording input + sendFrontendInputToRecord(byteBuffer); + } else { + // Feed the data into the broadcast demux filter + startBroadcastTsFilter(byteBuffer); + } + } + if (mIsRecording) { + // Dispatch the data into the broadcasting filters. + startRecordFilterDispatcher(); + } else { + // Dispatch the data into the broadcasting filters. + startBroadcastFilterDispatcher(); } - startFilterDispatcher(); usleep(100); } } - ALOGW("[Demux] Broadcast Input thread end."); + ALOGW("[Demux] Frontend Input thread end."); delete[] buffer; inputData.close(); } -void Demux::stopBroadcastInput() { +void Demux::stopFrontendInput() { ALOGD("[Demux] stop frontend on demux"); mKeepFetchingDataFromFrontend = false; - mBroadcastInputThreadRunning = false; - std::lock_guard lock(mBroadcastInputThreadLock); + mFrontendInputThreadRunning = false; + std::lock_guard lock(mFrontendInputThreadLock); +} + +void Demux::setIsRecording(bool isRecording) { + mIsRecording = isRecording; +} + +bool Demux::attachRecordFilter(int filterId) { + if (mFilters[filterId] == nullptr || mDvr == nullptr) { + return false; + } + + mRecordFilterIds.insert(filterId); + mFilters[filterId]->attachFilterToRecord(mDvr); + + return true; +} + +bool Demux::detachRecordFilter(int filterId) { + if (mFilters[filterId] == nullptr || mDvr == nullptr) { + return false; + } + + mRecordFilterIds.erase(filterId); + mFilters[filterId]->detachFilterFromRecord(); + + return true; } } // namespace implementation diff --git a/tv/tuner/1.0/default/Demux.h b/tv/tuner/1.0/default/Demux.h index 037429dd97..1405d0c2ca 100644 --- a/tv/tuner/1.0/default/Demux.h +++ b/tv/tuner/1.0/default/Demux.h @@ -81,11 +81,14 @@ class Demux : public IDemux { virtual Return disconnectCiCam() override; // Functions interacts with Tuner Service - void stopBroadcastInput(); + void stopFrontendInput(); Result removeFilter(uint32_t filterId); + bool attachRecordFilter(int filterId); + bool detachRecordFilter(int filterId); Result startFilterHandler(uint32_t filterId); void updateFilterOutput(uint16_t filterId, vector data); uint16_t getFilterTpid(uint32_t filterId); + void setIsRecording(bool isRecording); private: // Tuner service @@ -101,9 +104,9 @@ class Demux : public IDemux { uint32_t filterId; }; - Result startBroadcastInputLoop(); - static void* __threadLoopBroadcast(void* user); - void broadcastInputThreadLoop(); + Result startFrontendInputLoop(); + static void* __threadLoopFrontend(void* user); + void frontendInputThreadLoop(); /** * To create a FilterMQ with the the next available Filter ID. @@ -117,9 +120,13 @@ class Demux : public IDemux { /** * A dispatcher to read and dispatch input data to all the started filters. * Each filter handler handles the data filtering/output writing/filterEvent updating. + * Note that recording filters are not included. */ - bool startFilterDispatcher(); - void startTsFilter(vector data); + bool startBroadcastFilterDispatcher(); + void startBroadcastTsFilter(vector data); + + void sendFrontendInputToRecord(vector data); + bool startRecordFilterDispatcher(); uint32_t mDemuxId; uint32_t mCiCamId; @@ -140,19 +147,33 @@ class Demux : public IDemux { * and added into usedFilterIds. */ set mUnusedFilterIds; + /** + * Record all the attached record filter Ids. + * Any removed filter id should be removed from this set. + */ + set mRecordFilterIds; /** * A list of created FilterMQ ptrs. * The array number is the filter ID. */ std::map> mFilters; + /** + * Local reference to the opened DVR object. + */ + sp mDvr; + // Thread handlers - pthread_t mBroadcastInputThread; + pthread_t mFrontendInputThread; /** * If a specific filter's writing loop is still running */ - bool mBroadcastInputThreadRunning; + bool mFrontendInputThreadRunning; bool mKeepFetchingDataFromFrontend; + /** + * If the dvr recording is running. + */ + bool mIsRecording = false; /** * Lock to protect writes to the FMQs */ @@ -160,7 +181,7 @@ class Demux : public IDemux { /** * Lock to protect writes to the input status */ - std::mutex mBroadcastInputThreadLock; + std::mutex mFrontendInputThreadLock; // temp handle single PES filter // TODO handle mulptiple Pes filters diff --git a/tv/tuner/1.0/default/Dvr.cpp b/tv/tuner/1.0/default/Dvr.cpp index eb38f9040e..3088a9d73b 100644 --- a/tv/tuner/1.0/default/Dvr.cpp +++ b/tv/tuner/1.0/default/Dvr.cpp @@ -70,7 +70,14 @@ Return Dvr::attachFilter(const sp& filter) { return status; } + // check if the attached filter is a record filter + mFilters[filterId] = filter; + mIsRecordFilterAttached = true; + if (!mDemux->attachRecordFilter(filterId)) { + return Result::INVALID_ARGUMENT; + } + mDemux->setIsRecording(mIsRecordStarted | mIsRecordFilterAttached); return Result::SUCCESS; } @@ -95,6 +102,15 @@ Return Dvr::detachFilter(const sp& filter) { it = mFilters.find(filterId); if (it != mFilters.end()) { mFilters.erase(filterId); + if (!mDemux->detachRecordFilter(filterId)) { + return Result::INVALID_ARGUMENT; + } + } + + // If all the filters are detached, record can't be started + if (mFilters.empty()) { + mIsRecordFilterAttached = false; + mDemux->setIsRecording(mIsRecordStarted | mIsRecordFilterAttached); } return Result::SUCCESS; @@ -115,8 +131,9 @@ Return Dvr::start() { pthread_create(&mDvrThread, NULL, __threadLoopPlayback, this); pthread_setname_np(mDvrThread, "playback_waiting_loop"); } else if (mType == DvrType::RECORD) { - /*pthread_create(&mInputThread, NULL, __threadLoopInput, this); - pthread_setname_np(mInputThread, "playback_waiting_loop");*/ + mRecordStatus = RecordStatus::DATA_READY; + mIsRecordStarted = true; + mDemux->setIsRecording(mIsRecordStarted | mIsRecordFilterAttached); } // TODO start another thread to send filter status callback to the framework @@ -131,12 +148,17 @@ Return Dvr::stop() { std::lock_guard lock(mDvrThreadLock); + mIsRecordStarted = false; + mDemux->setIsRecording(mIsRecordStarted | mIsRecordFilterAttached); + return Result::SUCCESS; } Return Dvr::flush() { ALOGV("%s", __FUNCTION__); + mRecordStatus = RecordStatus::DATA_READY; + return Result::SUCCESS; } @@ -272,6 +294,45 @@ bool Dvr::startFilterDispatcher() { return true; } +bool Dvr::writeRecordFMQ(const std::vector& data) { + std::lock_guard lock(mWriteLock); + ALOGW("[Dvr] write record FMQ"); + if (mDvrMQ->write(data.data(), data.size())) { + mDvrEventFlag->wake(static_cast(DemuxQueueNotifyBits::DATA_READY)); + maySendRecordStatusCallback(); + return true; + } + + maySendRecordStatusCallback(); + return false; +} + +void Dvr::maySendRecordStatusCallback() { + std::lock_guard lock(mRecordStatusLock); + int availableToRead = mDvrMQ->availableToRead(); + int availableToWrite = mDvrMQ->availableToWrite(); + + RecordStatus newStatus = checkRecordStatusChange(availableToWrite, availableToRead, + mDvrSettings.record().highThreshold, + mDvrSettings.record().lowThreshold); + if (mRecordStatus != newStatus) { + mCallback->onRecordStatus(newStatus); + mRecordStatus = newStatus; + } +} + +RecordStatus Dvr::checkRecordStatusChange(uint32_t availableToWrite, uint32_t availableToRead, + uint32_t highThreshold, uint32_t lowThreshold) { + if (availableToWrite == 0) { + return DemuxFilterStatus::OVERFLOW; + } else if (availableToRead > highThreshold) { + return DemuxFilterStatus::HIGH_WATER; + } else if (availableToRead < lowThreshold) { + return DemuxFilterStatus::LOW_WATER; + } + return mRecordStatus; +} + } // namespace implementation } // namespace V1_0 } // namespace tuner diff --git a/tv/tuner/1.0/default/Dvr.h b/tv/tuner/1.0/default/Dvr.h index fbb778ca6c..f39d8db152 100644 --- a/tv/tuner/1.0/default/Dvr.h +++ b/tv/tuner/1.0/default/Dvr.h @@ -79,6 +79,8 @@ class Dvr : public IDvr { * Return false is any of the above processes fails. */ bool createDvrMQ(); + void sendBroadcastInputToDvrRecord(vector byteBuffer); + bool writeRecordFMQ(const std::vector& data); private: // Demux service @@ -95,6 +97,8 @@ class Dvr : public IDvr { void maySendRecordStatusCallback(); PlaybackStatus checkPlaybackStatusChange(uint32_t availableToWrite, uint32_t availableToRead, uint32_t highThreshold, uint32_t lowThreshold); + RecordStatus checkRecordStatusChange(uint32_t availableToWrite, uint32_t availableToRead, + uint32_t highThreshold, uint32_t lowThreshold); /** * A dispatcher to read and dispatch input data to all the started filters. * Each filter handler handles the data filtering/output writing/filterEvent updating. @@ -103,9 +107,9 @@ class Dvr : public IDvr { void startTpidFilter(vector data); bool startFilterDispatcher(); static void* __threadLoopPlayback(void* user); - static void* __threadLoopBroadcast(void* user); + static void* __threadLoopRecord(void* user); void playbackThreadLoop(); - void broadcastInputThreadLoop(); + void recordThreadLoop(); unique_ptr mDvrMQ; EventFlag* mDvrEventFlag; @@ -121,6 +125,7 @@ class Dvr : public IDvr { // FMQ status local records PlaybackStatus mPlaybackStatus; + RecordStatus mRecordStatus; /** * If a specific filter's writing loop is still running */ @@ -135,10 +140,16 @@ class Dvr : public IDvr { * Lock to protect writes to the input status */ std::mutex mPlaybackStatusLock; + std::mutex mRecordStatusLock; std::mutex mBroadcastInputThreadLock; std::mutex mDvrThreadLock; const bool DEBUG_DVR = false; + + // Booleans to check if recording is running. + // Recording is ready when both of the following are set to true. + bool mIsRecordStarted = false; + bool mIsRecordFilterAttached = false; }; } // namespace implementation diff --git a/tv/tuner/1.0/default/Filter.cpp b/tv/tuner/1.0/default/Filter.cpp index befd1e6550..b3160fc1f7 100644 --- a/tv/tuner/1.0/default/Filter.cpp +++ b/tv/tuner/1.0/default/Filter.cpp @@ -265,10 +265,16 @@ uint16_t Filter::getTpid() { void Filter::updateFilterOutput(vector data) { std::lock_guard lock(mFilterOutputLock); - ALOGD("[Filter] handler output updated"); + ALOGD("[Filter] filter output updated"); mFilterOutput.insert(mFilterOutput.end(), data.begin(), data.end()); } +void Filter::updateRecordOutput(vector data) { + std::lock_guard lock(mRecordFilterOutputLock); + ALOGD("[Filter] record filter output updated"); + mRecordFilterOutput.insert(mRecordFilterOutput.end(), data.begin(), data.end()); +} + Result Filter::startFilterHandler() { std::lock_guard lock(mFilterOutputLock); switch (mType.mainType) { @@ -292,12 +298,11 @@ Result Filter::startFilterHandler() { case DemuxTsFilterType::PCR: startPcrFilterHandler(); break; - case DemuxTsFilterType::RECORD: - startRecordFilterHandler(); - break; case DemuxTsFilterType::TEMI: startTemiFilterHandler(); break; + default: + break; } break; case DemuxFilterMainType::MMTP: @@ -342,12 +347,16 @@ Result Filter::startPesFilterHandler() { if (mPesSizeLeft == 0) { uint32_t prefix = (mFilterOutput[i + 4] << 16) | (mFilterOutput[i + 5] << 8) | mFilterOutput[i + 6]; - ALOGD("[Filter] prefix %d", prefix); + if (DEBUG_FILTER) { + ALOGD("[Filter] prefix %d", prefix); + } if (prefix == 0x000001) { // TODO handle mulptiple Pes filters mPesSizeLeft = (mFilterOutput[i + 8] << 8) | mFilterOutput[i + 9]; mPesSizeLeft += 6; - ALOGD("[Filter] pes data length %d", mPesSizeLeft); + if (DEBUG_FILTER) { + ALOGD("[Filter] pes data length %d", mPesSizeLeft); + } } else { continue; } @@ -360,7 +369,9 @@ Result Filter::startPesFilterHandler() { mPesOutput.insert(mPesOutput.end(), first, last); // size does not match then continue mPesSizeLeft -= endPoint; - ALOGD("[Filter] pes data left %d", mPesSizeLeft); + if (DEBUG_FILTER) { + ALOGD("[Filter] pes data left %d", mPesSizeLeft); + } if (mPesSizeLeft > 0) { continue; } @@ -377,7 +388,9 @@ Result Filter::startPesFilterHandler() { .streamId = mPesOutput[3], .dataLength = static_cast(mPesOutput.size()), }; - ALOGD("[Filter] assembled pes data length %d", pesEvent.dataLength); + if (DEBUG_FILTER) { + ALOGD("[Filter] assembled pes data length %d", pesEvent.dataLength); + } int size = mFilterEvent.events.size(); mFilterEvent.events.resize(size + 1); @@ -413,13 +426,23 @@ Result Filter::startMediaFilterHandler() { } Result Filter::startRecordFilterHandler() { - DemuxFilterTsRecordEvent tsRecordEvent; + /*DemuxFilterTsRecordEvent tsRecordEvent; tsRecordEvent.pid.tPid(0); tsRecordEvent.indexMask.tsIndexMask(0x01); mFilterEvent.events.resize(1); mFilterEvent.events[0].tsRecord(tsRecordEvent); +*/ + std::lock_guard lock(mRecordFilterOutputLock); + if (mRecordFilterOutput.empty()) { + return Result::SUCCESS; + } - mFilterOutput.clear(); + if (mDvr == nullptr || !mDvr->writeRecordFMQ(mRecordFilterOutput)) { + ALOGD("[Filter] dvr fails to write into record FMQ."); + return Result::UNKNOWN_ERROR; + } + + mRecordFilterOutput.clear(); return Result::SUCCESS; } @@ -462,6 +485,14 @@ bool Filter::writeDataToFilterMQ(const std::vector& data) { return false; } +void Filter::attachFilterToRecord(const sp dvr) { + mDvr = dvr; +} + +void Filter::detachFilterFromRecord() { + mDvr = nullptr; +} + } // namespace implementation } // namespace V1_0 } // namespace tuner diff --git a/tv/tuner/1.0/default/Filter.h b/tv/tuner/1.0/default/Filter.h index fbd965a1a1..d397f73081 100644 --- a/tv/tuner/1.0/default/Filter.h +++ b/tv/tuner/1.0/default/Filter.h @@ -22,6 +22,7 @@ #include #include #include "Demux.h" +#include "Dvr.h" #include "Frontend.h" using namespace std; @@ -44,6 +45,7 @@ using ::android::hardware::tv::tuner::V1_0::Result; using FilterMQ = MessageQueue; class Demux; +class Dvr; class Filter : public IFilter { public: @@ -80,11 +82,17 @@ class Filter : public IFilter { bool createFilterMQ(); uint16_t getTpid(); void updateFilterOutput(vector data); + void updateRecordOutput(vector data); Result startFilterHandler(); + Result startRecordFilterHandler(); + void attachFilterToRecord(const sp dvr); + void detachFilterFromRecord(); private: // Tuner service sp mDemux; + // Dvr reference once the filter is attached to any + sp mDvr = nullptr; /** * Filter callbacks used on filter events or FMQ status */ @@ -99,6 +107,7 @@ class Filter : public IFilter { sp mDataSource; bool mIsDataSourceDemux = true; vector mFilterOutput; + vector mRecordFilterOutput; unique_ptr mFilterMQ; EventFlag* mFilterEventFlag; DemuxFilterEvent mFilterEvent; @@ -120,6 +129,8 @@ class Filter : public IFilter { */ const uint16_t SECTION_WRITE_COUNT = 10; + bool DEBUG_FILTER = false; + /** * Filter handlers to handle the data filtering. * They are also responsible to write the filtered output into the filter FMQ @@ -129,7 +140,6 @@ class Filter : public IFilter { Result startPesFilterHandler(); Result startTsFilterHandler(); Result startMediaFilterHandler(); - Result startRecordFilterHandler(); Result startPcrFilterHandler(); Result startTemiFilterHandler(); Result startFilterLoop(); @@ -165,6 +175,7 @@ class Filter : public IFilter { std::mutex mFilterStatusLock; std::mutex mFilterThreadLock; std::mutex mFilterOutputLock; + std::mutex mRecordFilterOutputLock; // temp handle single PES filter // TODO handle mulptiple Pes filters diff --git a/tv/tuner/1.0/default/Tuner.cpp b/tv/tuner/1.0/default/Tuner.cpp index f86b28d3c6..c143d61154 100644 --- a/tv/tuner/1.0/default/Tuner.cpp +++ b/tv/tuner/1.0/default/Tuner.cpp @@ -148,7 +148,7 @@ void Tuner::frontendStopTune(uint32_t frontendId) { uint32_t demuxId; if (it != mFrontendToDemux.end()) { demuxId = it->second; - mDemuxes[demuxId]->stopBroadcastInput(); + mDemuxes[demuxId]->stopFrontendInput(); } } From ed9ba5e08c74e78cf41b4864bc2fc827699cf8e6 Mon Sep 17 00:00:00 2001 From: Amy Date: Tue, 17 Dec 2019 15:44:36 -0800 Subject: [PATCH 3/3] 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) {