Merge changes from topic "record" am: e843b7c118 am: 360776a89e

Change-Id: Idfc4ab4cc53b2f63fb353c3d68952262d3719b72
This commit is contained in:
Automerger Merge Worker 2019-12-19 00:19:50 +00:00
commit 4b73b59554
10 changed files with 531 additions and 56 deletions

View file

@ -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);

View file

@ -51,7 +51,8 @@ Return<Result> Demux::setFrontendDataSource(uint32_t frontendId) {
mFrontendSourceFile = mFrontend->getSourceFile();
mTunerService->setFrontendAsDemuxSource(frontendId, mDemuxId);
return startBroadcastInputLoop();
return startFrontendInputLoop();
}
Return<void> Demux::openFilter(const DemuxFilterType& type, uint32_t bufferSize,
@ -136,14 +137,14 @@ Return<void> Demux::openDvr(DvrType type, uint32_t bufferSize, const sp<IDvrCall
return Void();
}
sp<Dvr> 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<uint8_t> data) {
void Demux::startBroadcastTsFilter(vector<uint8_t> data) {
set<uint32_t>::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<uint8_t> data) {
}
}
bool Demux::startFilterDispatcher() {
void Demux::sendFrontendInputToRecord(vector<uint8_t> data) {
set<uint32_t>::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<uint32_t>::iterator it;
// Handle the output data per filter type
@ -198,6 +210,18 @@ bool Demux::startFilterDispatcher() {
return true;
}
bool Demux::startRecordFilterDispatcher() {
set<uint32_t>::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<Demux*>(user);
self->broadcastInputThreadLoop();
self->frontendInputThreadLoop();
return 0;
}
void Demux::broadcastInputThreadLoop() {
std::lock_guard<std::mutex> lock(mBroadcastInputThreadLock);
mBroadcastInputThreadRunning = true;
void Demux::frontendInputThreadLoop() {
std::lock_guard<std::mutex> 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<uint8_t>(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<std::mutex> lock(mBroadcastInputThreadLock);
mFrontendInputThreadRunning = false;
std::lock_guard<std::mutex> 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

View file

@ -81,11 +81,14 @@ class Demux : public IDemux {
virtual Return<Result> 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<uint8_t> 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<uint8_t> data);
bool startBroadcastFilterDispatcher();
void startBroadcastTsFilter(vector<uint8_t> data);
void sendFrontendInputToRecord(vector<uint8_t> data);
bool startRecordFilterDispatcher();
uint32_t mDemuxId;
uint32_t mCiCamId;
@ -140,19 +147,33 @@ class Demux : public IDemux {
* and added into usedFilterIds.
*/
set<uint32_t> mUnusedFilterIds;
/**
* Record all the attached record filter Ids.
* Any removed filter id should be removed from this set.
*/
set<uint32_t> mRecordFilterIds;
/**
* A list of created FilterMQ ptrs.
* The array number is the filter ID.
*/
std::map<uint32_t, sp<Filter>> mFilters;
/**
* Local reference to the opened DVR object.
*/
sp<Dvr> 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

View file

@ -70,7 +70,14 @@ Return<Result> Dvr::attachFilter(const sp<IFilter>& 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<Result> Dvr::detachFilter(const sp<IFilter>& 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<Result> 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<Result> Dvr::stop() {
std::lock_guard<std::mutex> lock(mDvrThreadLock);
mIsRecordStarted = false;
mDemux->setIsRecording(mIsRecordStarted | mIsRecordFilterAttached);
return Result::SUCCESS;
}
Return<Result> 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<uint8_t>& data) {
std::lock_guard<std::mutex> lock(mWriteLock);
ALOGW("[Dvr] write record FMQ");
if (mDvrMQ->write(data.data(), data.size())) {
mDvrEventFlag->wake(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY));
maySendRecordStatusCallback();
return true;
}
maySendRecordStatusCallback();
return false;
}
void Dvr::maySendRecordStatusCallback() {
std::lock_guard<std::mutex> 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

View file

@ -79,6 +79,8 @@ class Dvr : public IDvr {
* Return false is any of the above processes fails.
*/
bool createDvrMQ();
void sendBroadcastInputToDvrRecord(vector<uint8_t> byteBuffer);
bool writeRecordFMQ(const std::vector<uint8_t>& 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<uint8_t> 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<DvrMQ> 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

View file

@ -265,10 +265,16 @@ uint16_t Filter::getTpid() {
void Filter::updateFilterOutput(vector<uint8_t> data) {
std::lock_guard<std::mutex> lock(mFilterOutputLock);
ALOGD("[Filter] handler output updated");
ALOGD("[Filter] filter output updated");
mFilterOutput.insert(mFilterOutput.end(), data.begin(), data.end());
}
void Filter::updateRecordOutput(vector<uint8_t> data) {
std::lock_guard<std::mutex> lock(mRecordFilterOutputLock);
ALOGD("[Filter] record filter output updated");
mRecordFilterOutput.insert(mRecordFilterOutput.end(), data.begin(), data.end());
}
Result Filter::startFilterHandler() {
std::lock_guard<std::mutex> 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];
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;
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;
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<uint16_t>(mPesOutput.size()),
};
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<std::mutex> 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<uint8_t>& data) {
return false;
}
void Filter::attachFilterToRecord(const sp<Dvr> dvr) {
mDvr = dvr;
}
void Filter::detachFilterFromRecord() {
mDvr = nullptr;
}
} // namespace implementation
} // namespace V1_0
} // namespace tuner

View file

@ -22,6 +22,7 @@
#include <math.h>
#include <set>
#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<uint8_t, kSynchronizedReadWrite>;
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<uint8_t> data);
void updateRecordOutput(vector<uint8_t> data);
Result startFilterHandler();
Result startRecordFilterHandler();
void attachFilterToRecord(const sp<Dvr> dvr);
void detachFilterFromRecord();
private:
// Tuner service
sp<Demux> mDemux;
// Dvr reference once the filter is attached to any
sp<Dvr> mDvr = nullptr;
/**
* Filter callbacks used on filter events or FMQ status
*/
@ -99,6 +107,7 @@ class Filter : public IFilter {
sp<IFilter> mDataSource;
bool mIsDataSourceDemux = true;
vector<uint8_t> mFilterOutput;
vector<uint8_t> mRecordFilterOutput;
unique_ptr<FilterMQ> 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

View file

@ -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();
}
}

View file

@ -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 {
/**

View file

@ -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<void> onRecordStatus(RecordStatus /*status*/) override { return Void(); }
virtual Return<void> 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<void> 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<uint8_t> mDataOutputBuffer;
std::map<uint32_t, std::unique_ptr<FilterMQ>> mFilterIdToMQ;
std::unique_ptr<FilterMQ> mPlaybackMQ;
std::unique_ptr<FilterMQ> mRecordMQ;
std::map<uint32_t, EventFlag*> mFilterIdToMQEventFlag;
std::map<uint32_t, DemuxFilterEvent> 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<FilterMQ>(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<DvrCallback*>(((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<uint32_t>(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<DvrCallback> mDvrCallback;
MQDesc mFilterMQDescriptor;
MQDesc mPlaybackMQDescriptor;
MQDesc mRecordMQDescriptor;
vector<uint32_t> 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> filterConf,
PlaybackConf playbackConf,
vector<string> goldenOutputFiles);
::testing::AssertionResult recordDataFlowTest(vector<FilterConf> filterConf,
RecordSettings recordSetting,
vector<string> goldenOutputFiles);
::testing::AssertionResult broadcastDataFlowTest(vector<FilterConf> filterConf,
vector<string> 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<IDvr>& 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> filterConf,
RecordSettings recordSetting,
vector<string> /*goldenOutputFiles*/) {
Result status;
hidl_vec<FrontendId> feIds;
mService->getFrontendIds([&](Result result, const hidl_vec<FrontendId>& 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;
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<string> goldenOutputFiles;
ASSERT_TRUE(recordDataFlowTest(filterConf, recordSetting, goldenOutputFiles));
}
} // namespace
int main(int argc, char** argv) {