diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml index 3dec27b3ce..946392458e 100644 --- a/compatibility_matrices/compatibility_matrix.current.xml +++ b/compatibility_matrices/compatibility_matrix.current.xml @@ -482,7 +482,7 @@ android.hardware.tv.tuner - 1.0 + 1.0-1 ITuner default diff --git a/tv/tuner/1.1/Android.bp b/tv/tuner/1.1/Android.bp new file mode 100644 index 0000000000..c051ab0902 --- /dev/null +++ b/tv/tuner/1.1/Android.bp @@ -0,0 +1,16 @@ +// This file is autogenerated by hidl-gen -Landroidbp. + +hidl_interface { + name: "android.hardware.tv.tuner@1.1", + root: "android.hardware", + srcs: [ + "IFilter.hal", + "ITuner.hal", + ], + interfaces: [ + "android.hidl.base@1.0", + "android.hidl.safe_union@1.0", + "android.hardware.tv.tuner@1.0", + ], + gen_java: false, +} diff --git a/tv/tuner/1.1/IFilter.hal b/tv/tuner/1.1/IFilter.hal new file mode 100644 index 0000000000..6c4d8a5a03 --- /dev/null +++ b/tv/tuner/1.1/IFilter.hal @@ -0,0 +1,43 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.tv.tuner@1.1; + +import @1.0::IFilter; +import @1.0::Result; + +/** + * The Filter is used to filter wanted data according to the filter's + * configuration. + * + * To access the v1.1 IFilter APIs, the implementation can cast the IFilter + * interface returned from the @1.0::IDemux openFilter into a v1.1 IFiler + * using V1_1::IFilter::castFrom(V1_0::IFilter). + */ +interface IFilter extends @1.0::IFilter { + /** + * Get the 64-bit filter Id. This id is 32-bit in Tuner HAL 1.0. + * + * It is used by the client to ask 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. + */ + getId64Bit() generates (Result result, uint64_t filterId); +}; diff --git a/tv/tuner/1.1/ITuner.hal b/tv/tuner/1.1/ITuner.hal new file mode 100644 index 0000000000..915fb85806 --- /dev/null +++ b/tv/tuner/1.1/ITuner.hal @@ -0,0 +1,26 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.tv.tuner@1.1; + +import @1.0::ITuner; +import @1.0::Result; + +/** + * Top level interface to manage Frontend, Demux and Decrambler hardware + * resources which are needed for Android TV. + */ +interface ITuner extends @1.0::ITuner {}; diff --git a/tv/tuner/1.1/TEST_MAPPING b/tv/tuner/1.1/TEST_MAPPING new file mode 100644 index 0000000000..7c91b8fe6c --- /dev/null +++ b/tv/tuner/1.1/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "VtsHalTvTunerV1_1TargetTest" + } + ] +} \ No newline at end of file diff --git a/tv/tuner/1.1/default/Android.bp b/tv/tuner/1.1/default/Android.bp new file mode 100644 index 0000000000..7e45864791 --- /dev/null +++ b/tv/tuner/1.1/default/Android.bp @@ -0,0 +1,52 @@ +cc_defaults { + name: "tuner_service_defaults@1.1", + defaults: ["hidl_defaults"], + vendor: true, + relative_install_path: "hw", + srcs: [ + "Filter.cpp", + "Frontend.cpp", + "Descrambler.cpp", + "Demux.cpp", + "Dvr.cpp", + "TimeFilter.cpp", + "Tuner.cpp", + "Lnb.cpp", + "service.cpp", + ], + + compile_multilib: "first", + + shared_libs: [ + "android.hardware.tv.tuner@1.0", + "android.hardware.tv.tuner@1.1", + "android.hidl.memory@1.0", + "libcutils", + "libfmq", + "libhidlbase", + "libhidlmemory", + "libion", + "liblog", + "libstagefright_foundation", + "libutils", + ], + header_libs: [ + "media_plugin_headers", + ], +} + +cc_binary { + name: "android.hardware.tv.tuner@1.1-service", + vintf_fragments: ["android.hardware.tv.tuner@1.1-service.xml"], + defaults: ["tuner_service_defaults@1.1"], + init_rc: ["android.hardware.tv.tuner@1.1-service.rc"], +} + +cc_binary { + name: "android.hardware.tv.tuner@1.1-service-lazy", + vintf_fragments: ["android.hardware.tv.tuner@1.1-service-lazy.xml"], + overrides: ["android.hardware.tv.tuner@1.1-service"], + defaults: ["tuner_service_defaults@1.1"], + init_rc: ["android.hardware.tv.tuner@1.1-service-lazy.rc"], + cflags: ["-DLAZY_SERVICE"], +} diff --git a/tv/tuner/1.1/default/Demux.cpp b/tv/tuner/1.1/default/Demux.cpp new file mode 100644 index 0000000000..b6b029fdb4 --- /dev/null +++ b/tv/tuner/1.1/default/Demux.cpp @@ -0,0 +1,395 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "android.hardware.tv.tuner@1.1-Demux" + +#include "Demux.h" +#include + +namespace android { +namespace hardware { +namespace tv { +namespace tuner { +namespace V1_0 { +namespace implementation { + +#define WAIT_TIMEOUT 3000000000 +Demux::Demux(uint32_t demuxId, sp tuner) { + mDemuxId = demuxId; + mTunerService = tuner; +} + +Demux::~Demux() {} + +Return Demux::setFrontendDataSource(uint32_t frontendId) { + ALOGV("%s", __FUNCTION__); + + if (mTunerService == nullptr) { + return Result::NOT_INITIALIZED; + } + + mFrontend = mTunerService->getFrontendById(frontendId); + + if (mFrontend == nullptr) { + return Result::INVALID_STATE; + } + + mTunerService->setFrontendAsDemuxSource(frontendId, mDemuxId); + + return Result::SUCCESS; +} + +Return Demux::openFilter(const DemuxFilterType& type, uint32_t bufferSize, + const sp& cb, openFilter_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + uint64_t filterId; + filterId = ++mLastUsedFilterId; + + if (cb == nullptr) { + ALOGW("[Demux] callback can't be null"); + _hidl_cb(Result::INVALID_ARGUMENT, new Filter()); + return Void(); + } + + sp filter = new Filter(type, filterId, bufferSize, cb, this); + + if (!filter->createFilterMQ()) { + _hidl_cb(Result::UNKNOWN_ERROR, filter); + return Void(); + } + + mFilters[filterId] = filter; + if (filter->isPcrFilter()) { + mPcrFilterIds.insert(filterId); + } + bool result = true; + if (!filter->isRecordFilter()) { + // Only save non-record filters for now. Record filters are saved when the + // IDvr.attacheFilter is called. + mPlaybackFilterIds.insert(filterId); + if (mDvrPlayback != nullptr) { + result = mDvrPlayback->addPlaybackFilter(filterId, filter); + } + } + + _hidl_cb(result ? Result::SUCCESS : Result::INVALID_ARGUMENT, filter); + return Void(); +} + +Return Demux::openTimeFilter(openTimeFilter_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + mTimeFilter = new TimeFilter(this); + + _hidl_cb(Result::SUCCESS, mTimeFilter); + return Void(); +} + +Return Demux::getAvSyncHwId(const sp& filter, getAvSyncHwId_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + uint32_t avSyncHwId = -1; + uint64_t id; + Result status; + + sp filter_v1_1 = V1_1::IFilter::castFrom(filter); + if (filter_v1_1 != NULL) { + filter_v1_1->getId64Bit([&](Result result, uint64_t filterId) { + id = filterId; + status = result; + }); + } else { + filter->getId([&](Result result, uint32_t filterId) { + id = filterId; + status = result; + }); + } + + if (status != Result::SUCCESS) { + ALOGE("[Demux] Can't get filter Id."); + _hidl_cb(Result::INVALID_STATE, avSyncHwId); + return Void(); + } + + if (!mFilters[id]->isMediaFilter()) { + ALOGE("[Demux] Given filter is not a media filter."); + _hidl_cb(Result::INVALID_ARGUMENT, avSyncHwId); + return Void(); + } + + if (!mPcrFilterIds.empty()) { + // Return the lowest pcr filter id in the default implementation as the av sync id + _hidl_cb(Result::SUCCESS, *mPcrFilterIds.begin()); + return Void(); + } + + ALOGE("[Demux] No PCR filter opened."); + _hidl_cb(Result::INVALID_STATE, avSyncHwId); + return Void(); +} + +Return Demux::getAvSyncTime(AvSyncHwId avSyncHwId, getAvSyncTime_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + uint64_t avSyncTime = -1; + if (mPcrFilterIds.empty()) { + _hidl_cb(Result::INVALID_STATE, avSyncTime); + return Void(); + } + if (avSyncHwId != *mPcrFilterIds.begin()) { + _hidl_cb(Result::INVALID_ARGUMENT, avSyncTime); + return Void(); + } + + _hidl_cb(Result::SUCCESS, avSyncTime); + return Void(); +} + +Return Demux::close() { + ALOGV("%s", __FUNCTION__); + + set::iterator it; + for (it = mPlaybackFilterIds.begin(); it != mPlaybackFilterIds.end(); it++) { + mDvrPlayback->removePlaybackFilter(*it); + } + mPlaybackFilterIds.clear(); + mRecordFilterIds.clear(); + mFilters.clear(); + mLastUsedFilterId = -1; + + return Result::SUCCESS; +} + +Return Demux::openDvr(DvrType type, uint32_t bufferSize, const sp& cb, + openDvr_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + if (cb == nullptr) { + ALOGW("[Demux] DVR callback can't be null"); + _hidl_cb(Result::INVALID_ARGUMENT, new Dvr()); + return Void(); + } + + set::iterator it; + switch (type) { + case DvrType::PLAYBACK: + mDvrPlayback = new Dvr(type, bufferSize, cb, this); + if (!mDvrPlayback->createDvrMQ()) { + _hidl_cb(Result::UNKNOWN_ERROR, mDvrPlayback); + return Void(); + } + + for (it = mPlaybackFilterIds.begin(); it != mPlaybackFilterIds.end(); it++) { + if (!mDvrPlayback->addPlaybackFilter(*it, mFilters[*it])) { + ALOGE("[Demux] Can't get filter info for DVR playback"); + _hidl_cb(Result::UNKNOWN_ERROR, mDvrPlayback); + return Void(); + } + } + + _hidl_cb(Result::SUCCESS, mDvrPlayback); + return Void(); + case DvrType::RECORD: + mDvrRecord = new Dvr(type, bufferSize, cb, this); + if (!mDvrRecord->createDvrMQ()) { + _hidl_cb(Result::UNKNOWN_ERROR, mDvrRecord); + return Void(); + } + + _hidl_cb(Result::SUCCESS, mDvrRecord); + return Void(); + default: + _hidl_cb(Result::INVALID_ARGUMENT, nullptr); + return Void(); + } +} + +Return Demux::connectCiCam(uint32_t ciCamId) { + ALOGV("%s", __FUNCTION__); + + mCiCamId = ciCamId; + + return Result::SUCCESS; +} + +Return Demux::disconnectCiCam() { + ALOGV("%s", __FUNCTION__); + + return Result::SUCCESS; +} + +Result Demux::removeFilter(uint64_t filterId) { + ALOGV("%s", __FUNCTION__); + + if (mDvrPlayback != nullptr) { + mDvrPlayback->removePlaybackFilter(filterId); + } + mPlaybackFilterIds.erase(filterId); + mRecordFilterIds.erase(filterId); + mFilters.erase(filterId); + + return Result::SUCCESS; +} + +void Demux::startBroadcastTsFilter(vector data) { + set::iterator it; + uint16_t pid = ((data[1] & 0x1f) << 8) | ((data[2] & 0xff)); + if (DEBUG_DEMUX) { + ALOGW("[Demux] start ts filter pid: %d", pid); + } + for (it = mPlaybackFilterIds.begin(); it != mPlaybackFilterIds.end(); it++) { + if (pid == mFilters[*it]->getTpid()) { + mFilters[*it]->updateFilterOutput(data); + } + } +} + +void Demux::sendFrontendInputToRecord(vector data) { + set::iterator it; + if (DEBUG_DEMUX) { + ALOGW("[Demux] update record filter output"); + } + for (it = mRecordFilterIds.begin(); it != mRecordFilterIds.end(); it++) { + mFilters[*it]->updateRecordOutput(data); + } +} + +bool Demux::startBroadcastFilterDispatcher() { + set::iterator it; + + // Handle the output data per filter type + for (it = mPlaybackFilterIds.begin(); it != mPlaybackFilterIds.end(); it++) { + if (mFilters[*it]->startFilterHandler() != Result::SUCCESS) { + return false; + } + } + + 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(uint64_t filterId) { + return mFilters[filterId]->startFilterHandler(); +} + +void Demux::updateFilterOutput(uint64_t filterId, vector data) { + mFilters[filterId]->updateFilterOutput(data); +} + +void Demux::updateMediaFilterOutput(uint64_t filterId, vector data, uint64_t pts) { + updateFilterOutput(filterId, data); + mFilters[filterId]->updatePts(pts); +} + +uint16_t Demux::getFilterTpid(uint64_t filterId) { + return mFilters[filterId]->getTpid(); +} + +void Demux::startFrontendInputLoop() { + pthread_create(&mFrontendInputThread, NULL, __threadLoopFrontend, this); + pthread_setname_np(mFrontendInputThread, "frontend_input_thread"); +} + +void* Demux::__threadLoopFrontend(void* user) { + Demux* const self = static_cast(user); + self->frontendInputThreadLoop(); + return 0; +} + +void Demux::frontendInputThreadLoop() { + std::lock_guard lock(mFrontendInputThreadLock); + mFrontendInputThreadRunning = true; + + while (mFrontendInputThreadRunning) { + uint32_t efState = 0; + status_t status = mDvrPlayback->getDvrEventFlag()->wait( + static_cast(DemuxQueueNotifyBits::DATA_READY), &efState, WAIT_TIMEOUT, + true /* retry on spurious wake */); + if (status != OK) { + ALOGD("[Demux] wait for data ready on the playback FMQ"); + continue; + } + if (mDvrPlayback->getSettings().playback().dataFormat == DataFormat::ES) { + if (!mDvrPlayback->processEsDataOnPlayback(true /*isVirtualFrontend*/, mIsRecording)) { + ALOGE("[Demux] playback es data failed to be filtered. Ending thread"); + break; + } + } + // Our current implementation filter the data and write it into the filter FMQ immediately + // after the DATA_READY from the VTS/framework + if (!mDvrPlayback->readPlaybackFMQ(true /*isVirtualFrontend*/, mIsRecording) || + !mDvrPlayback->startFilterDispatcher(true /*isVirtualFrontend*/, mIsRecording)) { + ALOGE("[Demux] playback data failed to be filtered. Ending thread"); + break; + } + } + + mFrontendInputThreadRunning = false; + ALOGW("[Demux] Frontend Input thread end."); +} + +void Demux::stopFrontendInput() { + ALOGD("[Demux] stop frontend on demux"); + mKeepFetchingDataFromFrontend = false; + mFrontendInputThreadRunning = false; + std::lock_guard lock(mFrontendInputThreadLock); +} + +void Demux::setIsRecording(bool isRecording) { + mIsRecording = isRecording; +} + +bool Demux::attachRecordFilter(uint64_t filterId) { + if (mFilters[filterId] == nullptr || mDvrRecord == nullptr || + !mFilters[filterId]->isRecordFilter()) { + return false; + } + + mRecordFilterIds.insert(filterId); + mFilters[filterId]->attachFilterToRecord(mDvrRecord); + + return true; +} + +bool Demux::detachRecordFilter(uint64_t filterId) { + if (mFilters[filterId] == nullptr || mDvrRecord == nullptr) { + return false; + } + + mRecordFilterIds.erase(filterId); + mFilters[filterId]->detachFilterFromRecord(); + + return true; +} + +} // namespace implementation +} // namespace V1_0 +} // namespace tuner +} // namespace tv +} // namespace hardware +} // namespace android diff --git a/tv/tuner/1.1/default/Demux.h b/tv/tuner/1.1/default/Demux.h new file mode 100644 index 0000000000..62f916267f --- /dev/null +++ b/tv/tuner/1.1/default/Demux.h @@ -0,0 +1,197 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_TV_TUNER_V1_1_DEMUX_H_ +#define ANDROID_HARDWARE_TV_TUNER_V1_1_DEMUX_H_ + +#include +#include +#include +#include "Dvr.h" +#include "Filter.h" +#include "Frontend.h" +#include "TimeFilter.h" +#include "Tuner.h" + +using namespace std; + +namespace android { +namespace hardware { +namespace tv { +namespace tuner { +namespace V1_0 { +namespace implementation { + +using ::android::hardware::EventFlag; +using ::android::hardware::kSynchronizedReadWrite; +using ::android::hardware::MessageQueue; +using ::android::hardware::MQDescriptorSync; + +using FilterMQ = MessageQueue; + +class Dvr; +class Filter; +class Frontend; +class TimeFilter; +class Tuner; + +class Demux : public IDemux { + public: + Demux(uint32_t demuxId, sp tuner); + + ~Demux(); + + virtual Return setFrontendDataSource(uint32_t frontendId) override; + + virtual Return openFilter(const DemuxFilterType& type, uint32_t bufferSize, + const sp& cb, openFilter_cb _hidl_cb) override; + + virtual Return openTimeFilter(openTimeFilter_cb _hidl_cb) override; + + virtual Return getAvSyncHwId(const sp& filter, + getAvSyncHwId_cb _hidl_cb) override; + + virtual Return getAvSyncTime(AvSyncHwId avSyncHwId, getAvSyncTime_cb _hidl_cb) override; + + virtual Return close() override; + + virtual Return openDvr(DvrType type, uint32_t bufferSize, const sp& cb, + openDvr_cb _hidl_cb) override; + + virtual Return connectCiCam(uint32_t ciCamId) override; + + virtual Return disconnectCiCam() override; + + // Functions interacts with Tuner Service + void stopFrontendInput(); + Result removeFilter(uint64_t filterId); + bool attachRecordFilter(uint64_t filterId); + bool detachRecordFilter(uint64_t filterId); + Result startFilterHandler(uint64_t filterId); + void updateFilterOutput(uint64_t filterId, vector data); + void updateMediaFilterOutput(uint64_t filterId, vector data, uint64_t pts); + uint16_t getFilterTpid(uint64_t filterId); + void setIsRecording(bool isRecording); + void startFrontendInputLoop(); + + /** + * 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 startBroadcastFilterDispatcher(); + void startBroadcastTsFilter(vector data); + + void sendFrontendInputToRecord(vector data); + bool startRecordFilterDispatcher(); + + private: + // Tuner service + sp mTunerService; + + // Frontend source + sp mFrontend; + + // A struct that passes the arguments to a newly created filter thread + struct ThreadArgs { + Demux* user; + uint64_t filterId; + }; + + static void* __threadLoopFrontend(void* user); + void frontendInputThreadLoop(); + + /** + * To create a FilterMQ with the next available Filter ID. + * Creating Event Flag at the same time. + * Add the successfully created/saved FilterMQ into the local list. + * + * Return false is any of the above processes fails. + */ + void deleteEventFlag(); + bool readDataFromMQ(); + + uint32_t mDemuxId = -1; + uint32_t mCiCamId; + set mPcrFilterIds; + /** + * Record the last used filter id. Initial value is -1. + * Filter Id starts with 0. + */ + uint64_t mLastUsedFilterId = -1; + /** + * Record all the used playback filter Ids. + * Any removed filter id should be removed from this set. + */ + set mPlaybackFilterIds; + /** + * Record all the attached record filter Ids. + * Any removed filter id should be removed from this set. + */ + set mRecordFilterIds; + /** + * A list of created Filter sp. + * The array number is the filter ID. + */ + std::map> mFilters; + + /** + * Local reference to the opened Timer Filter instance. + */ + sp mTimeFilter; + + /** + * Local reference to the opened DVR object. + */ + sp mDvrPlayback; + sp mDvrRecord; + + // Thread handlers + pthread_t mFrontendInputThread; + /** + * If a specific filter's writing loop is still running + */ + bool mFrontendInputThreadRunning; + bool mKeepFetchingDataFromFrontend; + /** + * If the dvr recording is running. + */ + bool mIsRecording = false; + /** + * Lock to protect writes to the FMQs + */ + std::mutex mWriteLock; + /** + * Lock to protect writes to the input status + */ + std::mutex mFrontendInputThreadLock; + + // temp handle single PES filter + // TODO handle mulptiple Pes filters + int mPesSizeLeft = 0; + vector mPesOutput; + + const bool DEBUG_DEMUX = false; +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace tuner +} // namespace tv +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_TV_TUNER_V1_1_DEMUX_H_ diff --git a/tv/tuner/1.1/default/Descrambler.cpp b/tv/tuner/1.1/default/Descrambler.cpp new file mode 100644 index 0000000000..1fbc780c34 --- /dev/null +++ b/tv/tuner/1.1/default/Descrambler.cpp @@ -0,0 +1,80 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "android.hardware.tv.tuner@1.1-Descrambler" + +#include +#include + +#include "Descrambler.h" + +namespace android { +namespace hardware { +namespace tv { +namespace tuner { +namespace V1_0 { +namespace implementation { + +Descrambler::Descrambler() {} + +Descrambler::~Descrambler() {} + +Return Descrambler::setDemuxSource(uint32_t demuxId) { + ALOGV("%s", __FUNCTION__); + if (mDemuxSet) { + ALOGW("[ WARN ] Descrambler has already been set with a demux id %" PRIu32, + mSourceDemuxId); + return Result::INVALID_STATE; + } + mDemuxSet = true; + mSourceDemuxId = static_cast(demuxId); + + return Result::SUCCESS; +} + +Return Descrambler::setKeyToken(const hidl_vec& /* keyToken */) { + ALOGV("%s", __FUNCTION__); + + return Result::SUCCESS; +} + +Return Descrambler::addPid(const DemuxPid& /* pid */, + const sp& /* optionalSourceFilter */) { + ALOGV("%s", __FUNCTION__); + + return Result::SUCCESS; +} + +Return Descrambler::removePid(const DemuxPid& /* pid */, + const sp& /* optionalSourceFilter */) { + ALOGV("%s", __FUNCTION__); + + return Result::SUCCESS; +} + +Return Descrambler::close() { + ALOGV("%s", __FUNCTION__); + mDemuxSet = false; + + return Result::SUCCESS; +} + +} // namespace implementation +} // namespace V1_0 +} // namespace tuner +} // namespace tv +} // namespace hardware +} // namespace android diff --git a/tv/tuner/1.1/default/Descrambler.h b/tv/tuner/1.1/default/Descrambler.h new file mode 100644 index 0000000000..ffc284ddde --- /dev/null +++ b/tv/tuner/1.1/default/Descrambler.h @@ -0,0 +1,62 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_TV_TUNER_V1_1_DESCRAMBLER_H_ +#define ANDROID_HARDWARE_TV_TUNER_V1_1_DESCRAMBLER_H_ + +#include +#include +#include + +using namespace std; + +namespace android { +namespace hardware { +namespace tv { +namespace tuner { +namespace V1_0 { +namespace implementation { + +class Descrambler : public IDescrambler { + public: + Descrambler(); + + virtual Return setDemuxSource(uint32_t demuxId) override; + + virtual Return setKeyToken(const hidl_vec& keyToken) override; + + virtual Return addPid(const DemuxPid& pid, + const sp& optionalSourceFilter) override; + + virtual Return removePid(const DemuxPid& pid, + const sp& optionalSourceFilter) override; + + virtual Return close() override; + + private: + virtual ~Descrambler(); + uint32_t mSourceDemuxId; + bool mDemuxSet = false; +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace tuner +} // namespace tv +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_TV_TUNER_V1_DESCRAMBLER_H_ diff --git a/tv/tuner/1.1/default/Dvr.cpp b/tv/tuner/1.1/default/Dvr.cpp new file mode 100644 index 0000000000..02d6a42e58 --- /dev/null +++ b/tv/tuner/1.1/default/Dvr.cpp @@ -0,0 +1,498 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "android.hardware.tv.tuner@1.1-Dvr" + +#include "Dvr.h" +#include + +namespace android { +namespace hardware { +namespace tv { +namespace tuner { +namespace V1_0 { +namespace implementation { + +#define WAIT_TIMEOUT 3000000000 + +Dvr::Dvr() {} + +Dvr::Dvr(DvrType type, uint32_t bufferSize, const sp& cb, sp demux) { + mType = type; + mBufferSize = bufferSize; + mCallback = cb; + mDemux = demux; +} + +Dvr::~Dvr() {} + +Return Dvr::getQueueDesc(getQueueDesc_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + _hidl_cb(Result::SUCCESS, *mDvrMQ->getDesc()); + return Void(); +} + +Return Dvr::configure(const DvrSettings& settings) { + ALOGV("%s", __FUNCTION__); + + mDvrSettings = settings; + mDvrConfigured = true; + + return Result::SUCCESS; +} + +Return Dvr::attachFilter(const sp& filter) { + ALOGV("%s", __FUNCTION__); + + uint64_t filterId; + Result status; + + sp filter_v1_1 = V1_1::IFilter::castFrom(filter); + if (filter_v1_1 != NULL) { + filter_v1_1->getId64Bit([&](Result result, uint64_t id) { + filterId = id; + status = result; + }); + } else { + filter->getId([&](Result result, uint32_t id) { + filterId = id; + status = result; + }); + } + + if (status != Result::SUCCESS) { + return status; + } + + // TODO check if the attached filter is a record filter + if (!mDemux->attachRecordFilter(filterId)) { + return Result::INVALID_ARGUMENT; + } + + return Result::SUCCESS; +} + +Return Dvr::detachFilter(const sp& filter) { + ALOGV("%s", __FUNCTION__); + + uint64_t filterId; + Result status; + + sp filter_v1_1 = V1_1::IFilter::castFrom(filter); + if (filter_v1_1 != NULL) { + filter_v1_1->getId64Bit([&](Result result, uint64_t id) { + filterId = id; + status = result; + }); + } else { + filter->getId([&](Result result, uint32_t id) { + filterId = id; + status = result; + }); + } + + if (status != Result::SUCCESS) { + return status; + } + + if (!mDemux->detachRecordFilter(filterId)) { + return Result::INVALID_ARGUMENT; + } + + return Result::SUCCESS; +} + +Return Dvr::start() { + ALOGV("%s", __FUNCTION__); + + if (!mCallback) { + return Result::NOT_INITIALIZED; + } + + if (!mDvrConfigured) { + return Result::INVALID_STATE; + } + + if (mType == DvrType::PLAYBACK) { + pthread_create(&mDvrThread, NULL, __threadLoopPlayback, this); + pthread_setname_np(mDvrThread, "playback_waiting_loop"); + } else if (mType == DvrType::RECORD) { + mRecordStatus = RecordStatus::DATA_READY; + mDemux->setIsRecording(mType == DvrType::RECORD); + } + + // TODO start another thread to send filter status callback to the framework + + return Result::SUCCESS; +} + +Return Dvr::stop() { + ALOGV("%s", __FUNCTION__); + + mDvrThreadRunning = false; + + lock_guard lock(mDvrThreadLock); + + mIsRecordStarted = false; + mDemux->setIsRecording(false); + + return Result::SUCCESS; +} + +Return Dvr::flush() { + ALOGV("%s", __FUNCTION__); + + mRecordStatus = RecordStatus::DATA_READY; + + return Result::SUCCESS; +} + +Return Dvr::close() { + ALOGV("%s", __FUNCTION__); + + return Result::SUCCESS; +} + +bool Dvr::createDvrMQ() { + ALOGV("%s", __FUNCTION__); + + // Create a synchronized FMQ that supports blocking read/write + unique_ptr tmpDvrMQ = unique_ptr(new (nothrow) DvrMQ(mBufferSize, true)); + if (!tmpDvrMQ->isValid()) { + ALOGW("[Dvr] Failed to create FMQ of DVR"); + return false; + } + + mDvrMQ = move(tmpDvrMQ); + + if (EventFlag::createEventFlag(mDvrMQ->getEventFlagWord(), &mDvrEventFlag) != OK) { + return false; + } + + return true; +} + +EventFlag* Dvr::getDvrEventFlag() { + return mDvrEventFlag; +} + +void* Dvr::__threadLoopPlayback(void* user) { + Dvr* const self = static_cast(user); + self->playbackThreadLoop(); + return 0; +} + +void Dvr::playbackThreadLoop() { + ALOGD("[Dvr] playback threadLoop start."); + lock_guard lock(mDvrThreadLock); + mDvrThreadRunning = true; + + while (mDvrThreadRunning) { + uint32_t efState = 0; + status_t status = + mDvrEventFlag->wait(static_cast(DemuxQueueNotifyBits::DATA_READY), + &efState, WAIT_TIMEOUT, true /* retry on spurious wake */); + if (status != OK) { + ALOGD("[Dvr] wait for data ready on the playback FMQ"); + continue; + } + + if (mDvrSettings.playback().dataFormat == DataFormat::ES) { + if (!processEsDataOnPlayback(false /*isVirtualFrontend*/, false /*isRecording*/)) { + ALOGE("[Dvr] playback es data failed to be filtered. Ending thread"); + break; + } + maySendPlaybackStatusCallback(); + } + // Our current implementation filter the data and write it into the filter FMQ immediately + // after the DATA_READY from the VTS/framework + if (!readPlaybackFMQ(false /*isVirtualFrontend*/, false /*isRecording*/) || + !startFilterDispatcher(false /*isVirtualFrontend*/, false /*isRecording*/)) { + ALOGE("[Dvr] playback data failed to be filtered. Ending thread"); + break; + } + + maySendPlaybackStatusCallback(); + } + + mDvrThreadRunning = false; + ALOGD("[Dvr] playback thread ended."); +} + +void Dvr::maySendPlaybackStatusCallback() { + lock_guard lock(mPlaybackStatusLock); + int availableToRead = mDvrMQ->availableToRead(); + int availableToWrite = mDvrMQ->availableToWrite(); + + PlaybackStatus newStatus = checkPlaybackStatusChange(availableToWrite, availableToRead, + mDvrSettings.playback().highThreshold, + mDvrSettings.playback().lowThreshold); + if (mPlaybackStatus != newStatus) { + mCallback->onPlaybackStatus(newStatus); + mPlaybackStatus = newStatus; + } +} + +PlaybackStatus Dvr::checkPlaybackStatusChange(uint32_t availableToWrite, uint32_t availableToRead, + uint32_t highThreshold, uint32_t lowThreshold) { + if (availableToWrite == 0) { + return PlaybackStatus::SPACE_FULL; + } else if (availableToRead > highThreshold) { + return PlaybackStatus::SPACE_ALMOST_FULL; + } else if (availableToRead < lowThreshold) { + return PlaybackStatus::SPACE_ALMOST_EMPTY; + } else if (availableToRead == 0) { + return PlaybackStatus::SPACE_EMPTY; + } + return mPlaybackStatus; +} + +bool Dvr::readPlaybackFMQ(bool isVirtualFrontend, bool isRecording) { + // Read playback data from the input FMQ + int size = mDvrMQ->availableToRead(); + int playbackPacketSize = mDvrSettings.playback().packetSize; + vector dataOutputBuffer; + dataOutputBuffer.resize(playbackPacketSize); + // Dispatch the packet to the PID matching filter output buffer + for (int i = 0; i < size / playbackPacketSize; i++) { + if (!mDvrMQ->read(dataOutputBuffer.data(), playbackPacketSize)) { + return false; + } + if (isVirtualFrontend) { + if (isRecording) { + mDemux->sendFrontendInputToRecord(dataOutputBuffer); + } else { + mDemux->startBroadcastTsFilter(dataOutputBuffer); + } + } else { + startTpidFilter(dataOutputBuffer); + } + } + + return true; +} + +bool Dvr::processEsDataOnPlayback(bool isVirtualFrontend, bool isRecording) { + // Read ES from the DVR FMQ + // Note that currently we only provides ES with metaData in a specific format to be parsed. + // The ES size should be smaller than the Playback FMQ size to avoid reading truncated data. + int size = mDvrMQ->availableToRead(); + vector dataOutputBuffer; + dataOutputBuffer.resize(size); + if (!mDvrMQ->read(dataOutputBuffer.data(), size)) { + return false; + } + + int metaDataSize = size; + int totalFrames = 0; + int videoEsDataSize = 0; + int audioEsDataSize = 0; + int audioPid = 0; + int videoPid = 0; + + vector esMeta; + int videoReadPointer = 0; + int audioReadPointer = 0; + int frameCount = 0; + // Get meta data from the es + for (int i = 0; i < metaDataSize; i++) { + switch (dataOutputBuffer[i]) { + case 'm': + metaDataSize = 0; + getMetaDataValue(i, dataOutputBuffer.data(), metaDataSize); + videoReadPointer = metaDataSize; + continue; + case 'l': + getMetaDataValue(i, dataOutputBuffer.data(), totalFrames); + esMeta.resize(totalFrames); + continue; + case 'V': + getMetaDataValue(i, dataOutputBuffer.data(), videoEsDataSize); + audioReadPointer = metaDataSize + videoEsDataSize; + continue; + case 'A': + getMetaDataValue(i, dataOutputBuffer.data(), audioEsDataSize); + continue; + case 'p': + if (dataOutputBuffer[++i] == 'a') { + getMetaDataValue(i, dataOutputBuffer.data(), audioPid); + } else if (dataOutputBuffer[i] == 'v') { + getMetaDataValue(i, dataOutputBuffer.data(), videoPid); + } + continue; + case 'v': + case 'a': + if (dataOutputBuffer[i + 1] != ',') { + ALOGE("[Dvr] Invalid format meta data."); + return false; + } + esMeta[frameCount] = { + .isAudio = dataOutputBuffer[i] == 'a' ? true : false, + }; + i += 5; // Move to Len + getMetaDataValue(i, dataOutputBuffer.data(), esMeta[frameCount].len); + if (esMeta[frameCount].isAudio) { + esMeta[frameCount].startIndex = audioReadPointer; + audioReadPointer += esMeta[frameCount].len; + } else { + esMeta[frameCount].startIndex = videoReadPointer; + videoReadPointer += esMeta[frameCount].len; + } + i += 4; // move to PTS + getMetaDataValue(i, dataOutputBuffer.data(), esMeta[frameCount].pts); + frameCount++; + continue; + default: + continue; + } + } + + if (frameCount != totalFrames) { + ALOGE("[Dvr] Invalid meta data, frameCount=%d, totalFrames reported=%d", frameCount, + totalFrames); + return false; + } + + if (metaDataSize + audioEsDataSize + videoEsDataSize != size) { + ALOGE("[Dvr] Invalid meta data, metaSize=%d, videoSize=%d, audioSize=%d, totolSize=%d", + metaDataSize, videoEsDataSize, audioEsDataSize, size); + return false; + } + + // Read es raw data from the FMQ per meta data built previously + vector frameData; + map>::iterator it; + int pid = 0; + for (int i = 0; i < totalFrames; i++) { + frameData.resize(esMeta[i].len); + pid = esMeta[i].isAudio ? audioPid : videoPid; + memcpy(dataOutputBuffer.data() + esMeta[i].startIndex, frameData.data(), esMeta[i].len); + // Send to the media filter + if (isVirtualFrontend && isRecording) { + // TODO validate record + mDemux->sendFrontendInputToRecord(frameData); + } else { + for (it = mFilters.begin(); it != mFilters.end(); it++) { + if (pid == mDemux->getFilterTpid(it->first)) { + mDemux->updateMediaFilterOutput(it->first, frameData, + static_cast(esMeta[i].pts)); + startFilterDispatcher(isVirtualFrontend, isRecording); + } + } + } + } + + return true; +} + +void Dvr::getMetaDataValue(int& index, uint8_t* dataOutputBuffer, int& value) { + index += 2; // Move the pointer across the ":" to the value + while (dataOutputBuffer[index] != ',' && dataOutputBuffer[index] != '\n') { + value = ((dataOutputBuffer[index++] - 48) + value * 10); + } +} + +void Dvr::startTpidFilter(vector data) { + map>::iterator it; + for (it = mFilters.begin(); it != mFilters.end(); it++) { + uint16_t pid = ((data[1] & 0x1f) << 8) | ((data[2] & 0xff)); + if (DEBUG_DVR) { + ALOGW("[Dvr] start ts filter pid: %d", pid); + } + if (pid == mDemux->getFilterTpid(it->first)) { + mDemux->updateFilterOutput(it->first, data); + } + } +} + +bool Dvr::startFilterDispatcher(bool isVirtualFrontend, bool isRecording) { + if (isVirtualFrontend) { + if (isRecording) { + return mDemux->startRecordFilterDispatcher(); + } else { + return mDemux->startBroadcastFilterDispatcher(); + } + } + + map>::iterator it; + // Handle the output data per filter type + for (it = mFilters.begin(); it != mFilters.end(); it++) { + if (mDemux->startFilterHandler(it->first) != Result::SUCCESS) { + return false; + } + } + + return true; +} + +bool Dvr::writeRecordFMQ(const vector& data) { + lock_guard lock(mWriteLock); + if (mRecordStatus == RecordStatus::OVERFLOW) { + ALOGW("[Dvr] stops writing and wait for the client side flushing."); + return true; + } + if (mDvrMQ->write(data.data(), data.size())) { + mDvrEventFlag->wake(static_cast(DemuxQueueNotifyBits::DATA_READY)); + maySendRecordStatusCallback(); + return true; + } + + maySendRecordStatusCallback(); + return false; +} + +void Dvr::maySendRecordStatusCallback() { + 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; +} + +bool Dvr::addPlaybackFilter(uint64_t filterId, sp filter) { + mFilters[filterId] = filter; + return true; +} + +bool Dvr::removePlaybackFilter(uint64_t filterId) { + mFilters.erase(filterId); + return true; +} +} // namespace implementation +} // namespace V1_0 +} // namespace tuner +} // namespace tv +} // namespace hardware +} // namespace android diff --git a/tv/tuner/1.1/default/Dvr.h b/tv/tuner/1.1/default/Dvr.h new file mode 100644 index 0000000000..7b7efefada --- /dev/null +++ b/tv/tuner/1.1/default/Dvr.h @@ -0,0 +1,167 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_TV_TUNER_V1_1_DVR_H_ +#define ANDROID_HARDWARE_TV_TUNER_V1_1_DVR_H_ + +#include +#include +#include +#include "Demux.h" +#include "Frontend.h" +#include "Tuner.h" + +using namespace std; + +namespace android { +namespace hardware { +namespace tv { +namespace tuner { +namespace V1_0 { +namespace implementation { + +using ::android::hardware::EventFlag; +using ::android::hardware::kSynchronizedReadWrite; +using ::android::hardware::MessageQueue; +using ::android::hardware::MQDescriptorSync; + +using DvrMQ = MessageQueue; + +struct MediaEsMetaData { + bool isAudio; + int startIndex; + int len; + int pts; +}; + +class Demux; +class Filter; +class Frontend; +class Tuner; + +class Dvr : public IDvr { + public: + Dvr(); + + Dvr(DvrType type, uint32_t bufferSize, const sp& cb, sp demux); + + ~Dvr(); + + virtual Return getQueueDesc(getQueueDesc_cb _hidl_cb) override; + + virtual Return configure(const DvrSettings& settings) override; + + virtual Return attachFilter(const sp& filter) override; + + virtual Return detachFilter(const sp& filter) override; + + virtual Return start() override; + + virtual Return stop() override; + + virtual Return flush() override; + + virtual Return close() override; + + /** + * To create a DvrMQ and its Event Flag. + * + * Return false is any of the above processes fails. + */ + bool createDvrMQ(); + void sendBroadcastInputToDvrRecord(vector byteBuffer); + bool writeRecordFMQ(const std::vector& data); + bool addPlaybackFilter(uint64_t filterId, sp filter); + bool removePlaybackFilter(uint64_t filterId); + bool readPlaybackFMQ(bool isVirtualFrontend, bool isRecording); + bool processEsDataOnPlayback(bool isVirtualFrontend, bool isRecording); + bool startFilterDispatcher(bool isVirtualFrontend, bool isRecording); + EventFlag* getDvrEventFlag(); + DvrSettings getSettings() { return mDvrSettings; } + + private: + // Demux service + sp mDemux; + + DvrType mType; + uint32_t mBufferSize; + sp mCallback; + std::map> mFilters; + + void deleteEventFlag(); + bool readDataFromMQ(); + void getMetaDataValue(int& index, uint8_t* dataOutputBuffer, int& value); + void maySendPlaybackStatusCallback(); + 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. + */ + void startTpidFilter(vector data); + static void* __threadLoopPlayback(void* user); + static void* __threadLoopRecord(void* user); + void playbackThreadLoop(); + void recordThreadLoop(); + + unique_ptr mDvrMQ; + EventFlag* mDvrEventFlag; + /** + * Demux callbacks used on filter events or IO buffer status + */ + bool mDvrConfigured = false; + DvrSettings mDvrSettings; + + // Thread handlers + pthread_t mDvrThread; + + // FMQ status local records + PlaybackStatus mPlaybackStatus; + RecordStatus mRecordStatus; + /** + * If a specific filter's writing loop is still running + */ + bool mDvrThreadRunning; + bool mKeepFetchingDataFromFrontend; + /** + * Lock to protect writes to the FMQs + */ + std::mutex mWriteLock; + /** + * Lock to protect writes to the input status + */ + std::mutex mPlaybackStatusLock; + std::mutex mRecordStatusLock; + 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; +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace tuner +} // namespace tv +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_TV_TUNER_V1_1_DVR_H_ \ No newline at end of file diff --git a/tv/tuner/1.1/default/Filter.cpp b/tv/tuner/1.1/default/Filter.cpp new file mode 100644 index 0000000000..108baf74f3 --- /dev/null +++ b/tv/tuner/1.1/default/Filter.cpp @@ -0,0 +1,672 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "android.hardware.tv.tuner@1.1-Filter" + +#include "Filter.h" +#include + +namespace android { +namespace hardware { +namespace tv { +namespace tuner { +namespace V1_0 { +namespace implementation { + +#define WAIT_TIMEOUT 3000000000 + +Filter::Filter() {} + +Filter::Filter(DemuxFilterType type, uint64_t filterId, uint32_t bufferSize, + const sp& cb, sp demux) { + mType = type; + mFilterId = filterId; + mBufferSize = bufferSize; + mCallback = cb; + mDemux = demux; + + switch (mType.mainType) { + case DemuxFilterMainType::TS: + if (mType.subType.tsFilterType() == DemuxTsFilterType::AUDIO || + mType.subType.tsFilterType() == DemuxTsFilterType::VIDEO) { + mIsMediaFilter = true; + } + if (mType.subType.tsFilterType() == DemuxTsFilterType::PCR) { + mIsPcrFilter = true; + } + if (mType.subType.tsFilterType() == DemuxTsFilterType::RECORD) { + mIsRecordFilter = true; + } + break; + case DemuxFilterMainType::MMTP: + if (mType.subType.mmtpFilterType() == DemuxMmtpFilterType::AUDIO || + mType.subType.mmtpFilterType() == DemuxMmtpFilterType::VIDEO) { + mIsMediaFilter = true; + } + if (mType.subType.mmtpFilterType() == DemuxMmtpFilterType::RECORD) { + mIsRecordFilter = true; + } + break; + case DemuxFilterMainType::IP: + break; + case DemuxFilterMainType::TLV: + break; + case DemuxFilterMainType::ALP: + break; + default: + break; + } +} + +Filter::~Filter() {} + +Return Filter::getId64Bit(getId64Bit_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + _hidl_cb(Result::SUCCESS, mFilterId); + return Void(); +} + +Return Filter::getId(getId_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + _hidl_cb(Result::SUCCESS, static_cast(mFilterId)); + return Void(); +} + +Return Filter::setDataSource(const sp& filter) { + ALOGV("%s", __FUNCTION__); + + mDataSource = filter; + mIsDataSourceDemux = false; + + return Result::SUCCESS; +} + +Return Filter::getQueueDesc(getQueueDesc_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + mIsUsingFMQ = true; + + _hidl_cb(Result::SUCCESS, *mFilterMQ->getDesc()); + return Void(); +} + +Return Filter::configure(const DemuxFilterSettings& settings) { + ALOGV("%s", __FUNCTION__); + + mFilterSettings = settings; + switch (mType.mainType) { + case DemuxFilterMainType::TS: + mTpid = settings.ts().tpid; + break; + case DemuxFilterMainType::MMTP: + break; + case DemuxFilterMainType::IP: + break; + case DemuxFilterMainType::TLV: + break; + case DemuxFilterMainType::ALP: + break; + default: + break; + } + + return Result::SUCCESS; +} + +Return Filter::start() { + ALOGV("%s", __FUNCTION__); + + return startFilterLoop(); +} + +Return Filter::stop() { + ALOGV("%s", __FUNCTION__); + + mFilterThreadRunning = false; + + std::lock_guard lock(mFilterThreadLock); + + return Result::SUCCESS; +} + +Return Filter::flush() { + ALOGV("%s", __FUNCTION__); + + // temp implementation to flush the FMQ + int size = mFilterMQ->availableToRead(); + char* buffer = new char[size]; + mFilterMQ->read((unsigned char*)&buffer[0], size); + delete[] buffer; + mFilterStatus = DemuxFilterStatus::DATA_READY; + + return Result::SUCCESS; +} + +Return Filter::releaseAvHandle(const hidl_handle& /*avMemory*/, uint64_t avDataId) { + ALOGV("%s", __FUNCTION__); + if (mDataId2Avfd.find(avDataId) == mDataId2Avfd.end()) { + return Result::INVALID_ARGUMENT; + } + + ::close(mDataId2Avfd[avDataId]); + return Result::SUCCESS; +} + +Return Filter::close() { + ALOGV("%s", __FUNCTION__); + + return mDemux->removeFilter(mFilterId); +} + +bool Filter::createFilterMQ() { + ALOGV("%s", __FUNCTION__); + + // Create a synchronized FMQ that supports blocking read/write + std::unique_ptr tmpFilterMQ = + std::unique_ptr(new (std::nothrow) FilterMQ(mBufferSize, true)); + if (!tmpFilterMQ->isValid()) { + ALOGW("[Filter] Failed to create FMQ of filter with id: %" PRIu64, mFilterId); + return false; + } + + mFilterMQ = std::move(tmpFilterMQ); + + if (EventFlag::createEventFlag(mFilterMQ->getEventFlagWord(), &mFilterEventFlag) != OK) { + return false; + } + + return true; +} + +Result Filter::startFilterLoop() { + pthread_create(&mFilterThread, NULL, __threadLoopFilter, this); + pthread_setname_np(mFilterThread, "filter_waiting_loop"); + + return Result::SUCCESS; +} + +void* Filter::__threadLoopFilter(void* user) { + Filter* const self = static_cast(user); + self->filterThreadLoop(); + return 0; +} + +void Filter::filterThreadLoop() { + ALOGD("[Filter] filter %" PRIu64 " threadLoop start.", mFilterId); + std::lock_guard lock(mFilterThreadLock); + mFilterThreadRunning = true; + + // For the first time of filter output, implementation needs to send the filter + // Event Callback without waiting for the DATA_CONSUMED to init the process. + while (mFilterThreadRunning) { + if (mFilterEvent.events.size() == 0) { + if (DEBUG_FILTER) { + ALOGD("[Filter] wait for filter data output."); + } + usleep(1000 * 1000); + continue; + } + // After successfully write, send a callback and wait for the read to be done + mCallback->onFilterEvent(mFilterEvent); + freeAvHandle(); + mFilterEvent.events.resize(0); + mFilterStatus = DemuxFilterStatus::DATA_READY; + if (mCallback == nullptr) { + ALOGD("[Filter] filter %" PRIu64 " does not hava callback. Ending thread", mFilterId); + break; + } + mCallback->onFilterStatus(mFilterStatus); + break; + } + + while (mFilterThreadRunning) { + uint32_t efState = 0; + // We do not wait for the last round of written data to be read to finish the thread + // because the VTS can verify the reading itself. + for (int i = 0; i < SECTION_WRITE_COUNT; i++) { + while (mFilterThreadRunning && mIsUsingFMQ) { + status_t status = mFilterEventFlag->wait( + static_cast(DemuxQueueNotifyBits::DATA_CONSUMED), &efState, + WAIT_TIMEOUT, true /* retry on spurious wake */); + if (status != OK) { + ALOGD("[Filter] wait for data consumed"); + continue; + } + break; + } + + maySendFilterStatusCallback(); + + while (mFilterThreadRunning) { + std::lock_guard lock(mFilterEventLock); + if (mFilterEvent.events.size() == 0) { + continue; + } + // After successfully write, send a callback and wait for the read to be done + mCallback->onFilterEvent(mFilterEvent); + mFilterEvent.events.resize(0); + break; + } + // We do not wait for the last read to be done + // VTS can verify the read result itself. + if (i == SECTION_WRITE_COUNT - 1) { + ALOGD("[Filter] filter %" PRIu64 " writing done. Ending thread", mFilterId); + break; + } + } + mFilterThreadRunning = false; + } + + ALOGD("[Filter] filter thread ended."); +} + +void Filter::freeAvHandle() { + if (!mIsMediaFilter) { + return; + } + for (int i = 0; i < mFilterEvent.events.size(); i++) { + ::close(mFilterEvent.events[i].media().avMemory.getNativeHandle()->data[0]); + native_handle_close(mFilterEvent.events[i].media().avMemory.getNativeHandle()); + } +} + +void Filter::maySendFilterStatusCallback() { + if (!mIsUsingFMQ) { + return; + } + std::lock_guard lock(mFilterStatusLock); + int availableToRead = mFilterMQ->availableToRead(); + int availableToWrite = mFilterMQ->availableToWrite(); + int fmqSize = mFilterMQ->getQuantumCount(); + + DemuxFilterStatus newStatus = checkFilterStatusChange( + availableToWrite, availableToRead, ceil(fmqSize * 0.75), ceil(fmqSize * 0.25)); + if (mFilterStatus != newStatus) { + mCallback->onFilterStatus(newStatus); + mFilterStatus = newStatus; + } +} + +DemuxFilterStatus Filter::checkFilterStatusChange(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 mFilterStatus; +} + +uint16_t Filter::getTpid() { + return mTpid; +} + +void Filter::updateFilterOutput(vector data) { + std::lock_guard lock(mFilterOutputLock); + mFilterOutput.insert(mFilterOutput.end(), data.begin(), data.end()); +} + +void Filter::updatePts(uint64_t pts) { + std::lock_guard lock(mFilterOutputLock); + mPts = pts; +} + +void Filter::updateRecordOutput(vector data) { + std::lock_guard lock(mRecordFilterOutputLock); + mRecordFilterOutput.insert(mRecordFilterOutput.end(), data.begin(), data.end()); +} + +Result Filter::startFilterHandler() { + std::lock_guard lock(mFilterOutputLock); + switch (mType.mainType) { + case DemuxFilterMainType::TS: + switch (mType.subType.tsFilterType()) { + case DemuxTsFilterType::UNDEFINED: + break; + case DemuxTsFilterType::SECTION: + startSectionFilterHandler(); + break; + case DemuxTsFilterType::PES: + startPesFilterHandler(); + break; + case DemuxTsFilterType::TS: + startTsFilterHandler(); + break; + case DemuxTsFilterType::AUDIO: + case DemuxTsFilterType::VIDEO: + startMediaFilterHandler(); + break; + case DemuxTsFilterType::PCR: + startPcrFilterHandler(); + break; + case DemuxTsFilterType::TEMI: + startTemiFilterHandler(); + break; + default: + break; + } + break; + case DemuxFilterMainType::MMTP: + /*mmtpSettings*/ + break; + case DemuxFilterMainType::IP: + /*ipSettings*/ + break; + case DemuxFilterMainType::TLV: + /*tlvSettings*/ + break; + case DemuxFilterMainType::ALP: + /*alpSettings*/ + break; + default: + break; + } + return Result::SUCCESS; +} + +Result Filter::startSectionFilterHandler() { + if (mFilterOutput.empty()) { + return Result::SUCCESS; + } + if (!writeSectionsAndCreateEvent(mFilterOutput)) { + ALOGD("[Filter] filter %" PRIu64 " fails to write into FMQ. Ending thread", mFilterId); + return Result::UNKNOWN_ERROR; + } + + mFilterOutput.clear(); + + return Result::SUCCESS; +} + +Result Filter::startPesFilterHandler() { + std::lock_guard lock(mFilterEventLock); + if (mFilterOutput.empty()) { + return Result::SUCCESS; + } + + for (int i = 0; i < mFilterOutput.size(); i += 188) { + 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; + } + } + + int endPoint = min(184, mPesSizeLeft); + // append data and check size + vector::const_iterator first = mFilterOutput.begin() + i + 4; + vector::const_iterator last = mFilterOutput.begin() + i + 4 + endPoint; + 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; + } + // size match then create event + if (!writeDataToFilterMQ(mPesOutput)) { + ALOGD("[Filter] pes data write failed"); + mFilterOutput.clear(); + return Result::INVALID_STATE; + } + maySendFilterStatusCallback(); + DemuxFilterPesEvent pesEvent; + pesEvent = { + // temp dump meta data + .streamId = mPesOutput[3], + .dataLength = static_cast(mPesOutput.size()), + }; + if (DEBUG_FILTER) { + ALOGD("[Filter] assembled pes data length %d", pesEvent.dataLength); + } + + int size = mFilterEvent.events.size(); + mFilterEvent.events.resize(size + 1); + mFilterEvent.events[size].pes(pesEvent); + mPesOutput.clear(); + } + + mFilterOutput.clear(); + + return Result::SUCCESS; +} + +Result Filter::startTsFilterHandler() { + // TODO handle starting TS filter + return Result::SUCCESS; +} + +Result Filter::startMediaFilterHandler() { + std::lock_guard lock(mFilterEventLock); + if (mFilterOutput.empty()) { + return Result::SUCCESS; + } + + if (mPts) { + return createMediaFilterEventWithIon(mFilterOutput); + } + + for (int i = 0; i < mFilterOutput.size(); i += 188) { + 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; + } + } + + int endPoint = min(184, mPesSizeLeft); + // append data and check size + vector::const_iterator first = mFilterOutput.begin() + i + 4; + vector::const_iterator last = mFilterOutput.begin() + i + 4 + endPoint; + 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 || mAvBufferCopyCount++ < 10) { + continue; + } + + createMediaFilterEventWithIon(mPesOutput); + } + + mFilterOutput.clear(); + + return Result::SUCCESS; +} + +Result Filter::createMediaFilterEventWithIon(vector output) { + int av_fd = createAvIonFd(output.size()); + if (av_fd == -1) { + return Result::UNKNOWN_ERROR; + } + // copy the filtered data to the buffer + uint8_t* avBuffer = getIonBuffer(av_fd, output.size()); + if (avBuffer == NULL) { + return Result::UNKNOWN_ERROR; + } + memcpy(avBuffer, output.data(), output.size() * sizeof(uint8_t)); + + native_handle_t* nativeHandle = createNativeHandle(av_fd); + if (nativeHandle == NULL) { + return Result::UNKNOWN_ERROR; + } + hidl_handle handle; + handle.setTo(nativeHandle, /*shouldOwn=*/true); + + // Create a dataId and add a pair into the dataId2Avfd map + uint64_t dataId = mLastUsedDataId++ /*createdUID*/; + mDataId2Avfd[dataId] = dup(av_fd); + + // Create mediaEvent and send callback + DemuxFilterMediaEvent mediaEvent; + mediaEvent = { + .avMemory = std::move(handle), + .dataLength = static_cast(output.size()), + .avDataId = dataId, + }; + if (mPts) { + mediaEvent.pts = mPts; + mPts = 0; + } + int size = mFilterEvent.events.size(); + mFilterEvent.events.resize(size + 1); + mFilterEvent.events[size].media(mediaEvent); + + // Clear and log + output.clear(); + mAvBufferCopyCount = 0; + ::close(av_fd); + if (DEBUG_FILTER) { + ALOGD("[Filter] av data length %d", mediaEvent.dataLength); + } + return Result::SUCCESS; +} + +Result Filter::startRecordFilterHandler() { + std::lock_guard lock(mRecordFilterOutputLock); + if (mRecordFilterOutput.empty()) { + return Result::SUCCESS; + } + + if (mDvr == nullptr || !mDvr->writeRecordFMQ(mRecordFilterOutput)) { + ALOGD("[Filter] dvr fails to write into record FMQ."); + return Result::UNKNOWN_ERROR; + } + + mRecordFilterOutput.clear(); + return Result::SUCCESS; +} + +Result Filter::startPcrFilterHandler() { + // TODO handle starting PCR filter + return Result::SUCCESS; +} + +Result Filter::startTemiFilterHandler() { + // TODO handle starting TEMI filter + return Result::SUCCESS; +} + +bool Filter::writeSectionsAndCreateEvent(vector data) { + // TODO check how many sections has been read + ALOGD("[Filter] section handler"); + std::lock_guard lock(mFilterEventLock); + if (!writeDataToFilterMQ(data)) { + return false; + } + int size = mFilterEvent.events.size(); + mFilterEvent.events.resize(size + 1); + DemuxFilterSectionEvent secEvent; + secEvent = { + // temp dump meta data + .tableId = 0, + .version = 1, + .sectionNum = 1, + .dataLength = static_cast(data.size()), + }; + mFilterEvent.events[size].section(secEvent); + return true; +} + +bool Filter::writeDataToFilterMQ(const std::vector& data) { + std::lock_guard lock(mWriteLock); + if (mFilterMQ->write(data.data(), data.size())) { + return true; + } + return false; +} + +void Filter::attachFilterToRecord(const sp dvr) { + mDvr = dvr; +} + +void Filter::detachFilterFromRecord() { + mDvr = nullptr; +} + +int Filter::createAvIonFd(int size) { + // Create an ion fd and allocate an av fd mapped to a buffer to it. + int ion_fd = ion_open(); + if (ion_fd == -1) { + ALOGE("[Filter] Failed to open ion fd %d", errno); + return -1; + } + int av_fd = -1; + ion_alloc_fd(dup(ion_fd), size, 0 /*align*/, ION_HEAP_SYSTEM_MASK, 0 /*flags*/, &av_fd); + if (av_fd == -1) { + ALOGE("[Filter] Failed to create av fd %d", errno); + return -1; + } + return av_fd; +} + +uint8_t* Filter::getIonBuffer(int fd, int size) { + uint8_t* avBuf = static_cast( + mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 /*offset*/)); + if (avBuf == MAP_FAILED) { + ALOGE("[Filter] fail to allocate buffer %d", errno); + return NULL; + } + return avBuf; +} + +native_handle_t* Filter::createNativeHandle(int fd) { + // Create a native handle to pass the av fd via the callback event. + native_handle_t* nativeHandle = native_handle_create(/*numFd*/ 1, 0); + if (nativeHandle == NULL) { + ALOGE("[Filter] Failed to create native_handle %d", errno); + return NULL; + } + nativeHandle->data[0] = dup(fd); + return nativeHandle; +} +} // namespace implementation +} // namespace V1_0 +} // namespace tuner +} // namespace tv +} // namespace hardware +} // namespace android diff --git a/tv/tuner/1.1/default/Filter.h b/tv/tuner/1.1/default/Filter.h new file mode 100644 index 0000000000..d5801d4f74 --- /dev/null +++ b/tv/tuner/1.1/default/Filter.h @@ -0,0 +1,214 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_TV_TUNER_V1_1_FILTER_H_ +#define ANDROID_HARDWARE_TV_TUNER_V1_1_FILTER_H_ + +#include +#include +#include +#include +#include +#include +#include "Demux.h" +#include "Dvr.h" +#include "Frontend.h" + +using namespace std; + +namespace android { +namespace hardware { +namespace tv { +namespace tuner { +namespace V1_0 { +namespace implementation { + +using ::android::hardware::EventFlag; +using ::android::hardware::kSynchronizedReadWrite; +using ::android::hardware::MessageQueue; +using ::android::hardware::MQDescriptorSync; + +using FilterMQ = MessageQueue; + +class Demux; +class Dvr; + +class Filter : public V1_1::IFilter { + public: + Filter(); + + Filter(DemuxFilterType type, uint64_t filterId, uint32_t bufferSize, + const sp& cb, sp demux); + + ~Filter(); + + virtual Return getId64Bit(getId64Bit_cb _hidl_cb) override; + + virtual Return getId(getId_cb _hidl_cb) override; + + virtual Return setDataSource(const sp& filter) override; + + virtual Return getQueueDesc(getQueueDesc_cb _hidl_cb) override; + + virtual Return configure(const DemuxFilterSettings& settings) override; + + virtual Return start() override; + + virtual Return stop() override; + + virtual Return flush() override; + + virtual Return releaseAvHandle(const hidl_handle& avMemory, uint64_t avDataId) override; + + virtual Return close() override; + + /** + * To create a FilterMQ and its Event Flag. + * + * Return false is any of the above processes fails. + */ + bool createFilterMQ(); + uint16_t getTpid(); + void updateFilterOutput(vector data); + void updateRecordOutput(vector data); + void updatePts(uint64_t pts); + Result startFilterHandler(); + Result startRecordFilterHandler(); + void attachFilterToRecord(const sp dvr); + void detachFilterFromRecord(); + void freeAvHandle(); + bool isMediaFilter() { return mIsMediaFilter; }; + bool isPcrFilter() { return mIsPcrFilter; }; + bool isRecordFilter() { return mIsRecordFilter; }; + + 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 + */ + sp mCallback; + + uint64_t mFilterId; + uint32_t mBufferSize; + DemuxFilterType mType; + bool mIsMediaFilter = false; + bool mIsPcrFilter = false; + bool mIsRecordFilter = false; + DemuxFilterSettings mFilterSettings; + + uint16_t mTpid; + sp mDataSource; + bool mIsDataSourceDemux = true; + vector mFilterOutput; + vector mRecordFilterOutput; + uint64_t mPts = 0; + unique_ptr mFilterMQ; + bool mIsUsingFMQ = false; + EventFlag* mFilterEventFlag; + DemuxFilterEvent mFilterEvent; + + // Thread handlers + pthread_t mFilterThread; + + // FMQ status local records + DemuxFilterStatus mFilterStatus; + /** + * If a specific filter's writing loop is still running + */ + bool mFilterThreadRunning; + bool mKeepFetchingDataFromFrontend; + + /** + * How many times a filter should write + * TODO make this dynamic/random/can take as a parameter + */ + 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 + * and update the filterEvent bound with the same filterId. + */ + Result startSectionFilterHandler(); + Result startPesFilterHandler(); + Result startTsFilterHandler(); + Result startMediaFilterHandler(); + Result startPcrFilterHandler(); + Result startTemiFilterHandler(); + Result startFilterLoop(); + + void deleteEventFlag(); + bool writeDataToFilterMQ(const std::vector& data); + bool readDataFromMQ(); + bool writeSectionsAndCreateEvent(vector data); + void maySendFilterStatusCallback(); + DemuxFilterStatus checkFilterStatusChange(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. + */ + void startTsFilter(vector data); + bool startFilterDispatcher(); + static void* __threadLoopFilter(void* user); + void filterThreadLoop(); + + int createAvIonFd(int size); + uint8_t* getIonBuffer(int fd, int size); + native_handle_t* createNativeHandle(int fd); + Result createMediaFilterEventWithIon(vector output); + + /** + * Lock to protect writes to the FMQs + */ + std::mutex mWriteLock; + /** + * Lock to protect writes to the filter event + */ + // TODO make each filter separate event lock + std::mutex mFilterEventLock; + /** + * Lock to protect writes to the input status + */ + std::mutex mFilterStatusLock; + std::mutex mFilterThreadLock; + std::mutex mFilterOutputLock; + std::mutex mRecordFilterOutputLock; + + // temp handle single PES filter + // TODO handle mulptiple Pes filters + int mPesSizeLeft = 0; + vector mPesOutput; + + // A map from data id to ion handle + std::map mDataId2Avfd; + uint64_t mLastUsedDataId = 1; + int mAvBufferCopyCount = 0; +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace tuner +} // namespace tv +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_TV_TUNER_V1_1_FILTER_H_ diff --git a/tv/tuner/1.1/default/Frontend.cpp b/tv/tuner/1.1/default/Frontend.cpp new file mode 100644 index 0000000000..3475c3686b --- /dev/null +++ b/tv/tuner/1.1/default/Frontend.cpp @@ -0,0 +1,284 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "android.hardware.tv.tuner@1.1-Frontend" + +#include "Frontend.h" +#include +#include + +namespace android { +namespace hardware { +namespace tv { +namespace tuner { +namespace V1_0 { +namespace implementation { + +Frontend::Frontend(FrontendType type, FrontendId id, sp tuner) { + mType = type; + mId = id; + mTunerService = tuner; + // Init callback to nullptr + mCallback = nullptr; +} + +Frontend::~Frontend() {} + +Return Frontend::close() { + ALOGV("%s", __FUNCTION__); + // Reset callback + mCallback = nullptr; + mIsLocked = false; + + return Result::SUCCESS; +} + +Return Frontend::setCallback(const sp& callback) { + ALOGV("%s", __FUNCTION__); + if (callback == nullptr) { + ALOGW("[ WARN ] Set Frontend callback with nullptr"); + return Result::INVALID_ARGUMENT; + } + + mCallback = callback; + return Result::SUCCESS; +} + +Return Frontend::tune(const FrontendSettings& /* settings */) { + ALOGV("%s", __FUNCTION__); + if (mCallback == nullptr) { + ALOGW("[ WARN ] Frontend callback is not set when tune"); + return Result::INVALID_STATE; + } + + mTunerService->frontendStartTune(mId); + mCallback->onEvent(FrontendEventType::LOCKED); + mIsLocked = true; + return Result::SUCCESS; +} + +Return Frontend::stopTune() { + ALOGV("%s", __FUNCTION__); + + mTunerService->frontendStopTune(mId); + mIsLocked = false; + + return Result::SUCCESS; +} + +Return Frontend::scan(const FrontendSettings& settings, FrontendScanType type) { + ALOGV("%s", __FUNCTION__); + + if (mType == FrontendType::ATSC) { + FrontendScanMessage msg; + msg.isLocked(true); + mCallback->onScanMessage(FrontendScanMessageType::LOCKED, msg); + mIsLocked = true; + return Result::SUCCESS; + } + if (mType != FrontendType::DVBT) { + return Result::UNAVAILABLE; + } + + FrontendScanMessage msg; + + if (mIsLocked) { + msg.isEnd(true); + mCallback->onScanMessage(FrontendScanMessageType::END, msg); + return Result::SUCCESS; + } + + uint32_t frequency = settings.dvbt().frequency; + if (type == FrontendScanType::SCAN_BLIND) { + frequency += 100; + } + msg.frequencies({frequency}); + mCallback->onScanMessage(FrontendScanMessageType::FREQUENCY, msg); + msg.isLocked(true); + mCallback->onScanMessage(FrontendScanMessageType::LOCKED, msg); + mIsLocked = true; + + return Result::SUCCESS; +} + +Return Frontend::stopScan() { + ALOGV("%s", __FUNCTION__); + + mIsLocked = false; + return Result::SUCCESS; +} + +Return Frontend::getStatus(const hidl_vec& statusTypes, + getStatus_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + vector statuses; + for (int i = 0; i < statusTypes.size(); i++) { + FrontendStatusType type = statusTypes[i]; + FrontendStatus status; + // assign randomly selected values for testing. + switch (type) { + case FrontendStatusType::DEMOD_LOCK: { + status.isDemodLocked(true); + break; + } + case FrontendStatusType::SNR: { + status.snr(221); + break; + } + case FrontendStatusType::BER: { + status.ber(1); + break; + } + case FrontendStatusType::PER: { + status.per(2); + break; + } + case FrontendStatusType::PRE_BER: { + status.preBer(3); + break; + } + case FrontendStatusType::SIGNAL_QUALITY: { + status.signalQuality(4); + break; + } + case FrontendStatusType::SIGNAL_STRENGTH: { + status.signalStrength(5); + break; + } + case FrontendStatusType::SYMBOL_RATE: { + status.symbolRate(6); + break; + } + case FrontendStatusType::FEC: { + status.innerFec(FrontendInnerFec::FEC_2_9); // value = 1 << 7 + break; + } + case FrontendStatusType::MODULATION: { + FrontendModulationStatus modulationStatus; + modulationStatus.isdbt(FrontendIsdbtModulation::MOD_16QAM); // value = 1 << 3 + status.modulation(modulationStatus); + break; + } + case FrontendStatusType::SPECTRAL: { + status.inversion(FrontendDvbcSpectralInversion::NORMAL); + break; + } + case FrontendStatusType::LNB_VOLTAGE: { + status.lnbVoltage(LnbVoltage::VOLTAGE_5V); + break; + } + case FrontendStatusType::PLP_ID: { + status.plpId(101); // type uint8_t + break; + } + case FrontendStatusType::EWBS: { + status.isEWBS(false); + break; + } + case FrontendStatusType::AGC: { + status.agc(7); + break; + } + case FrontendStatusType::LNA: { + status.isLnaOn(false); + break; + } + case FrontendStatusType::LAYER_ERROR: { + vector v = {false, true, true}; + status.isLayerError(v); + break; + } + case FrontendStatusType::MER: { + status.mer(8); + break; + } + case FrontendStatusType::FREQ_OFFSET: { + status.freqOffset(9); + break; + } + case FrontendStatusType::HIERARCHY: { + status.hierarchy(FrontendDvbtHierarchy::HIERARCHY_1_NATIVE); + break; + } + case FrontendStatusType::RF_LOCK: { + status.isRfLocked(false); + break; + } + case FrontendStatusType::ATSC3_PLP_INFO: { + vector v; + FrontendStatusAtsc3PlpInfo info1{ + .plpId = 3, + .isLocked = false, + .uec = 313, + }; + FrontendStatusAtsc3PlpInfo info2{ + .plpId = 5, + .isLocked = true, + .uec = 515, + }; + v.push_back(info1); + v.push_back(info2); + status.plpInfo(v); + break; + } + default: { + continue; + } + } + statuses.push_back(status); + } + _hidl_cb(Result::SUCCESS, statuses); + + return Void(); +} + +Return Frontend::setLna(bool /* bEnable */) { + ALOGV("%s", __FUNCTION__); + + return Result::SUCCESS; +} + +Return Frontend::setLnb(uint32_t /* lnb */) { + ALOGV("%s", __FUNCTION__); + if (!supportsSatellite()) { + return Result::INVALID_STATE; + } + return Result::SUCCESS; +} + +FrontendType Frontend::getFrontendType() { + return mType; +} + +FrontendId Frontend::getFrontendId() { + return mId; +} + +bool Frontend::supportsSatellite() { + return mType == FrontendType::DVBS || mType == FrontendType::ISDBS || + mType == FrontendType::ISDBS3; +} + +bool Frontend::isLocked() { + return mIsLocked; +} +} // namespace implementation +} // namespace V1_0 +} // namespace tuner +} // namespace tv +} // namespace hardware +} // namespace android diff --git a/tv/tuner/1.1/default/Frontend.h b/tv/tuner/1.1/default/Frontend.h new file mode 100644 index 0000000000..89b4a6b5f3 --- /dev/null +++ b/tv/tuner/1.1/default/Frontend.h @@ -0,0 +1,85 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_TV_TUNER_V1_1_FRONTEND_H_ +#define ANDROID_HARDWARE_TV_TUNER_V1_1_FRONTEND_H_ + +#include +#include +#include "Tuner.h" + +using namespace std; + +namespace android { +namespace hardware { +namespace tv { +namespace tuner { +namespace V1_0 { +namespace implementation { + +class Tuner; + +class Frontend : public IFrontend { + public: + Frontend(FrontendType type, FrontendId id, sp tuner); + + virtual Return close() override; + + virtual Return setCallback(const sp& callback) override; + + virtual Return tune(const FrontendSettings& settings) override; + + virtual Return stopTune() override; + + virtual Return scan(const FrontendSettings& settings, FrontendScanType type) override; + + virtual Return stopScan() override; + + virtual Return getStatus(const hidl_vec& statusTypes, + getStatus_cb _hidl_cb) override; + + virtual Return setLna(bool bEnable) override; + + virtual Return setLnb(uint32_t lnb) override; + + FrontendType getFrontendType(); + + FrontendId getFrontendId(); + + string getSourceFile(); + + bool isLocked(); + + private: + virtual ~Frontend(); + bool supportsSatellite(); + sp mCallback; + sp mTunerService; + FrontendType mType = FrontendType::UNDEFINED; + FrontendId mId = 0; + bool mIsLocked = false; + + std::ifstream mFrontendData; +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace tuner +} // namespace tv +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_TV_TUNER_V1_1_FRONTEND_H_ diff --git a/tv/tuner/1.1/default/Lnb.cpp b/tv/tuner/1.1/default/Lnb.cpp new file mode 100644 index 0000000000..044727ff54 --- /dev/null +++ b/tv/tuner/1.1/default/Lnb.cpp @@ -0,0 +1,81 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "android.hardware.tv.tuner@1.1-Lnb" + +#include "Lnb.h" +#include + +namespace android { +namespace hardware { +namespace tv { +namespace tuner { +namespace V1_0 { +namespace implementation { + +Lnb::Lnb() {} +Lnb::Lnb(int id) { + mId = id; +} + +Lnb::~Lnb() {} + +Return Lnb::setCallback(const sp& /* callback */) { + ALOGV("%s", __FUNCTION__); + + return Result::SUCCESS; +} + +Return Lnb::setVoltage(LnbVoltage /* voltage */) { + ALOGV("%s", __FUNCTION__); + + return Result::SUCCESS; +} + +Return Lnb::setTone(LnbTone /* tone */) { + ALOGV("%s", __FUNCTION__); + + return Result::SUCCESS; +} + +Return Lnb::setSatellitePosition(LnbPosition /* position */) { + ALOGV("%s", __FUNCTION__); + + return Result::SUCCESS; +} + +Return Lnb::sendDiseqcMessage(const hidl_vec& /* diseqcMessage */) { + ALOGV("%s", __FUNCTION__); + + return Result::SUCCESS; +} + +Return Lnb::close() { + ALOGV("%s", __FUNCTION__); + + return Result::SUCCESS; +} + +int Lnb::getId() { + return mId; +} + +} // namespace implementation +} // namespace V1_0 +} // namespace tuner +} // namespace tv +} // namespace hardware +} // namespace android diff --git a/tv/tuner/1.1/default/Lnb.h b/tv/tuner/1.1/default/Lnb.h new file mode 100644 index 0000000000..70a8e41b8b --- /dev/null +++ b/tv/tuner/1.1/default/Lnb.h @@ -0,0 +1,63 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_TV_TUNER_V1_1_LNB_H_ +#define ANDROID_HARDWARE_TV_TUNER_V1_1_LNB_H_ + +#include +#include + +using namespace std; + +namespace android { +namespace hardware { +namespace tv { +namespace tuner { +namespace V1_0 { +namespace implementation { + +class Lnb : public ILnb { + public: + Lnb(); + Lnb(int id); + + virtual Return setCallback(const sp& callback) override; + + virtual Return setVoltage(LnbVoltage voltage) override; + + virtual Return setTone(LnbTone tone) override; + + virtual Return setSatellitePosition(LnbPosition position) override; + + virtual Return sendDiseqcMessage(const hidl_vec& diseqcMessage) override; + + virtual Return close() override; + + int getId(); + + private: + int mId; + virtual ~Lnb(); +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace tuner +} // namespace tv +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_TV_TUNER_V1_1_LNB_H_ diff --git a/tv/tuner/1.1/default/OWNERS b/tv/tuner/1.1/default/OWNERS new file mode 100644 index 0000000000..1b3d095f9c --- /dev/null +++ b/tv/tuner/1.1/default/OWNERS @@ -0,0 +1,4 @@ +nchalko@google.com +amyjojo@google.com +shubang@google.com +quxiangfang@google.com diff --git a/tv/tuner/1.1/default/TimeFilter.cpp b/tv/tuner/1.1/default/TimeFilter.cpp new file mode 100644 index 0000000000..bb243a66bc --- /dev/null +++ b/tv/tuner/1.1/default/TimeFilter.cpp @@ -0,0 +1,87 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "android.hardware.tv.tuner@1.1-TimeFilter" + +#include "TimeFilter.h" +#include + +namespace android { +namespace hardware { +namespace tv { +namespace tuner { +namespace V1_0 { +namespace implementation { + +TimeFilter::TimeFilter() {} + +TimeFilter::TimeFilter(sp demux) { + mDemux = demux; +} + +TimeFilter::~TimeFilter() {} + +Return TimeFilter::setTimeStamp(uint64_t timeStamp) { + ALOGV("%s", __FUNCTION__); + if (timeStamp == INVALID_TIME_STAMP) { + return Result::INVALID_ARGUMENT; + } + mTimeStamp = timeStamp; + mBeginTime = time(NULL); + + return Result::SUCCESS; +} + +Return TimeFilter::clearTimeStamp() { + ALOGV("%s", __FUNCTION__); + mTimeStamp = INVALID_TIME_STAMP; + + return Result::SUCCESS; +} + +Return TimeFilter::getTimeStamp(getTimeStamp_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + if (mTimeStamp == INVALID_TIME_STAMP) { + _hidl_cb(Result::INVALID_STATE, mTimeStamp); + } + + uint64_t currentTimeStamp = mTimeStamp + difftime(time(NULL), mBeginTime) * 900000; + _hidl_cb(Result::SUCCESS, currentTimeStamp); + return Void(); +} + +Return TimeFilter::getSourceTime(getSourceTime_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + uint64_t time = 0; + + _hidl_cb(Result::SUCCESS, time); + return Void(); +} + +Return TimeFilter::close() { + ALOGV("%s", __FUNCTION__); + mTimeStamp = INVALID_TIME_STAMP; + + return Result::SUCCESS; +} + +} // namespace implementation +} // namespace V1_0 +} // namespace tuner +} // namespace tv +} // namespace hardware +} // namespace android \ No newline at end of file diff --git a/tv/tuner/1.1/default/TimeFilter.h b/tv/tuner/1.1/default/TimeFilter.h new file mode 100644 index 0000000000..d53ad2c900 --- /dev/null +++ b/tv/tuner/1.1/default/TimeFilter.h @@ -0,0 +1,70 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_TV_TUNER_V1_1_TIMEFILTER_H_ +#define ANDROID_HARDWARE_TV_TUNER_V1_1_TIMEFILTER_H_ + +#include +#include "Demux.h" +#include "time.h" + +using namespace std; + +namespace android { +namespace hardware { +namespace tv { +namespace tuner { +namespace V1_0 { +namespace implementation { + +using FilterMQ = MessageQueue; + +#define INVALID_TIME_STAMP -1 + +class Demux; + +class TimeFilter : public ITimeFilter { + public: + TimeFilter(); + + TimeFilter(sp demux); + + ~TimeFilter(); + + virtual Return setTimeStamp(uint64_t timeStamp) override; + + virtual Return clearTimeStamp() override; + + virtual Return getTimeStamp(getTimeStamp_cb _hidl_cb) override; + + virtual Return getSourceTime(getSourceTime_cb _hidl_cb) override; + + virtual Return close() override; + + private: + sp mDemux; + uint64_t mTimeStamp = INVALID_TIME_STAMP; + time_t mBeginTime; +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace tuner +} // namespace tv +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_TV_TUNER_V1_1_TIMEFILTER_H_ \ No newline at end of file diff --git a/tv/tuner/1.1/default/Tuner.cpp b/tv/tuner/1.1/default/Tuner.cpp new file mode 100644 index 0000000000..c220ea2682 --- /dev/null +++ b/tv/tuner/1.1/default/Tuner.cpp @@ -0,0 +1,259 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "android.hardware.tv.tuner@1.1-Tuner" + +#include "Tuner.h" +#include +#include "Demux.h" +#include "Descrambler.h" +#include "Frontend.h" +#include "Lnb.h" + +namespace android { +namespace hardware { +namespace tv { +namespace tuner { +namespace V1_0 { +namespace implementation { + +Tuner::Tuner() { + // Static Frontends array to maintain local frontends information + // Array index matches their FrontendId in the default impl + mFrontendSize = 8; + mFrontends.resize(mFrontendSize); + mFrontends[0] = new Frontend(FrontendType::DVBT, 0, this); + mFrontends[1] = new Frontend(FrontendType::ATSC, 1, this); + mFrontends[2] = new Frontend(FrontendType::DVBC, 2, this); + mFrontends[3] = new Frontend(FrontendType::DVBS, 3, this); + mFrontends[4] = new Frontend(FrontendType::DVBT, 4, this); + mFrontends[5] = new Frontend(FrontendType::ISDBT, 5, this); + mFrontends[6] = new Frontend(FrontendType::ANALOG, 6, this); + mFrontends[7] = new Frontend(FrontendType::ATSC, 7, this); + + FrontendInfo::FrontendCapabilities caps; + mFrontendCaps.resize(mFrontendSize); + caps = FrontendInfo::FrontendCapabilities(); + caps.dvbtCaps(FrontendDvbtCapabilities()); + mFrontendCaps[0] = caps; + + caps = FrontendInfo::FrontendCapabilities(); + caps.atscCaps(FrontendAtscCapabilities()); + mFrontendCaps[1] = caps; + + caps = FrontendInfo::FrontendCapabilities(); + caps.dvbcCaps(FrontendDvbcCapabilities()); + mFrontendCaps[2] = caps; + + caps = FrontendInfo::FrontendCapabilities(); + caps.dvbsCaps(FrontendDvbsCapabilities()); + mFrontendCaps[3] = caps; + + caps = FrontendInfo::FrontendCapabilities(); + caps.dvbtCaps(FrontendDvbtCapabilities()); + mFrontendCaps[4] = caps; + + caps = FrontendInfo::FrontendCapabilities(); + FrontendIsdbtCapabilities isdbtCaps{ + .modeCap = FrontendIsdbtMode::MODE_1 | FrontendIsdbtMode::MODE_2, + .bandwidthCap = (unsigned int)FrontendIsdbtBandwidth::BANDWIDTH_6MHZ, + .modulationCap = (unsigned int)FrontendIsdbtModulation::MOD_16QAM, + // ISDBT shares coderate and guard interval with DVBT + .coderateCap = FrontendDvbtCoderate::CODERATE_4_5 | FrontendDvbtCoderate::CODERATE_6_7, + .guardIntervalCap = (unsigned int)FrontendDvbtGuardInterval::INTERVAL_1_128, + }; + caps.isdbtCaps(isdbtCaps); + mFrontendCaps[5] = caps; + + caps = FrontendInfo::FrontendCapabilities(); + caps.analogCaps(FrontendAnalogCapabilities()); + mFrontendCaps[6] = caps; + + caps = FrontendInfo::FrontendCapabilities(); + caps.atscCaps(FrontendAtscCapabilities()); + mFrontendCaps[7] = caps; + + mLnbs.resize(2); + mLnbs[0] = new Lnb(0); + mLnbs[1] = new Lnb(1); +} + +Tuner::~Tuner() {} + +Return Tuner::getFrontendIds(getFrontendIds_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + vector frontendIds; + frontendIds.resize(mFrontendSize); + for (int i = 0; i < mFrontendSize; i++) { + frontendIds[i] = mFrontends[i]->getFrontendId(); + } + + _hidl_cb(Result::SUCCESS, frontendIds); + return Void(); +} + +Return Tuner::openFrontendById(uint32_t frontendId, openFrontendById_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + if (frontendId >= mFrontendSize || frontendId < 0) { + ALOGW("[ WARN ] Frontend with id %d isn't available", frontendId); + _hidl_cb(Result::UNAVAILABLE, nullptr); + return Void(); + } + + _hidl_cb(Result::SUCCESS, mFrontends[frontendId]); + return Void(); +} + +Return Tuner::openDemux(openDemux_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + uint32_t demuxId = mLastUsedId + 1; + mLastUsedId += 1; + sp demux = new Demux(demuxId, this); + mDemuxes[demuxId] = demux; + + _hidl_cb(Result::SUCCESS, demuxId, demux); + return Void(); +} + +Return Tuner::getDemuxCaps(getDemuxCaps_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + DemuxCapabilities caps; + + // IP filter can be an MMTP filter's data source. + caps.linkCaps = {0x00, 0x00, 0x02, 0x00, 0x00}; + _hidl_cb(Result::SUCCESS, caps); + return Void(); +} + +Return Tuner::openDescrambler(openDescrambler_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + sp descrambler = new Descrambler(); + + _hidl_cb(Result::SUCCESS, descrambler); + return Void(); +} + +Return Tuner::getFrontendInfo(FrontendId frontendId, getFrontendInfo_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + FrontendInfo info; + if (frontendId >= mFrontendSize) { + _hidl_cb(Result::INVALID_ARGUMENT, info); + return Void(); + } + + vector statusCaps = { + FrontendStatusType::DEMOD_LOCK, + FrontendStatusType::SNR, + FrontendStatusType::FEC, + FrontendStatusType::MODULATION, + FrontendStatusType::PLP_ID, + FrontendStatusType::LAYER_ERROR, + FrontendStatusType::ATSC3_PLP_INFO, + }; + // assign randomly selected values for testing. + info = { + .type = mFrontends[frontendId]->getFrontendType(), + .minFrequency = 139, + .maxFrequency = 1139, + .minSymbolRate = 45, + .maxSymbolRate = 1145, + .acquireRange = 30, + .exclusiveGroupId = 57, + .statusCaps = statusCaps, + .frontendCaps = mFrontendCaps[frontendId], + }; + + _hidl_cb(Result::SUCCESS, info); + return Void(); +} + +Return Tuner::getLnbIds(getLnbIds_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + vector lnbIds; + lnbIds.resize(mLnbs.size()); + for (int i = 0; i < lnbIds.size(); i++) { + lnbIds[i] = mLnbs[i]->getId(); + } + + _hidl_cb(Result::SUCCESS, lnbIds); + return Void(); +} + +Return Tuner::openLnbById(V1_0::LnbId lnbId, openLnbById_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + if (lnbId >= mLnbs.size()) { + _hidl_cb(Result::INVALID_ARGUMENT, nullptr); + return Void(); + } + + _hidl_cb(Result::SUCCESS, mLnbs[lnbId]); + return Void(); +} + +sp Tuner::getFrontendById(uint32_t frontendId) { + ALOGV("%s", __FUNCTION__); + + return mFrontends[frontendId]; +} + +Return Tuner::openLnbByName(const hidl_string& /*lnbName*/, openLnbByName_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + sp lnb = new Lnb(); + + _hidl_cb(Result::SUCCESS, 1234, lnb); + return Void(); +} + +void Tuner::setFrontendAsDemuxSource(uint32_t frontendId, uint32_t demuxId) { + mFrontendToDemux[frontendId] = demuxId; + if (mFrontends[frontendId] != nullptr && mFrontends[frontendId]->isLocked()) { + mDemuxes[demuxId]->startFrontendInputLoop(); + } +} + +void Tuner::frontendStopTune(uint32_t frontendId) { + map::iterator it = mFrontendToDemux.find(frontendId); + uint32_t demuxId; + if (it != mFrontendToDemux.end()) { + demuxId = it->second; + mDemuxes[demuxId]->stopFrontendInput(); + } +} + +void Tuner::frontendStartTune(uint32_t frontendId) { + map::iterator it = mFrontendToDemux.find(frontendId); + uint32_t demuxId; + if (it != mFrontendToDemux.end()) { + demuxId = it->second; + mDemuxes[demuxId]->startFrontendInputLoop(); + } +} + +} // namespace implementation +} // namespace V1_0 +} // namespace tuner +} // namespace tv +} // namespace hardware +} // namespace android diff --git a/tv/tuner/1.1/default/Tuner.h b/tv/tuner/1.1/default/Tuner.h new file mode 100644 index 0000000000..9463278a1e --- /dev/null +++ b/tv/tuner/1.1/default/Tuner.h @@ -0,0 +1,94 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_TV_TUNER_V1_1_TUNER_H_ +#define ANDROID_HARDWARE_TV_TUNER_V1_1_TUNER_H_ + +#include +#include +#include "Demux.h" +#include "Frontend.h" +#include "Lnb.h" + +using namespace std; + +namespace android { +namespace hardware { +namespace tv { +namespace tuner { +namespace V1_0 { +namespace implementation { + +using ::android::hardware::tv::tuner::V1_1::ITuner; + +class Frontend; +class Demux; +class Lnb; + +class Tuner : public ITuner { + public: + Tuner(); + + virtual Return getFrontendIds(getFrontendIds_cb _hidl_cb) override; + + virtual Return openFrontendById(uint32_t frontendId, + openFrontendById_cb _hidl_cb) override; + + virtual Return openDemux(openDemux_cb _hidl_cb) override; + + virtual Return getDemuxCaps(getDemuxCaps_cb _hidl_cb) override; + + virtual Return openDescrambler(openDescrambler_cb _hidl_cb) override; + + virtual Return getFrontendInfo(uint32_t frontendId, getFrontendInfo_cb _hidl_cb) override; + + virtual Return getLnbIds(getLnbIds_cb _hidl_cb) override; + + virtual Return openLnbById(uint32_t lnbId, openLnbById_cb _hidl_cb) override; + + virtual Return openLnbByName(const hidl_string& lnbName, + openLnbByName_cb _hidl_cb) override; + + sp getFrontendById(uint32_t frontendId); + + void setFrontendAsDemuxSource(uint32_t frontendId, uint32_t demuxId); + + void frontendStartTune(uint32_t frontendId); + void frontendStopTune(uint32_t frontendId); + + private: + virtual ~Tuner(); + // Static mFrontends array to maintain local frontends information + vector> mFrontends; + vector mFrontendCaps; + std::map mFrontendToDemux; + std::map> mDemuxes; + // To maintain how many Frontends we have + int mFrontendSize; + // The last used demux id. Initial value is -1. + // First used id will be 0. + uint32_t mLastUsedId = -1; + vector> mLnbs; +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace tuner +} // namespace tv +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_TV_TUNER_V1_1_TUNER_H_ diff --git a/tv/tuner/1.1/default/android.hardware.tv.tuner@1.1-service-lazy.rc b/tv/tuner/1.1/default/android.hardware.tv.tuner@1.1-service-lazy.rc new file mode 100644 index 0000000000..9e228f7d58 --- /dev/null +++ b/tv/tuner/1.1/default/android.hardware.tv.tuner@1.1-service-lazy.rc @@ -0,0 +1,9 @@ +service vendor.tuner-hal-1-1 /vendor/bin/hw/android.hardware.tv.tuner@1.1-service-lazy + interface android.hardware.tv.tuner@1.1::ITuner default + oneshot + disabled + class hal + user media + group mediadrm drmrpc + ioprio rt 4 + writepid /dev/cpuset/foreground/tasks \ No newline at end of file diff --git a/tv/tuner/1.1/default/android.hardware.tv.tuner@1.1-service-lazy.xml b/tv/tuner/1.1/default/android.hardware.tv.tuner@1.1-service-lazy.xml new file mode 100644 index 0000000000..86b044525f --- /dev/null +++ b/tv/tuner/1.1/default/android.hardware.tv.tuner@1.1-service-lazy.xml @@ -0,0 +1,11 @@ + + + android.hardware.tv.tuner + hwbinder + 1.1 + + ITuner + default + + + \ No newline at end of file diff --git a/tv/tuner/1.1/default/android.hardware.tv.tuner@1.1-service.rc b/tv/tuner/1.1/default/android.hardware.tv.tuner@1.1-service.rc new file mode 100644 index 0000000000..3718a9388f --- /dev/null +++ b/tv/tuner/1.1/default/android.hardware.tv.tuner@1.1-service.rc @@ -0,0 +1,6 @@ +service vendor.tuner-hal-1-1 /vendor/bin/hw/android.hardware.tv.tuner@1.1-service + class hal + user media + group mediadrm drmrpc + ioprio rt 4 + writepid /dev/cpuset/foreground/tasks \ No newline at end of file diff --git a/tv/tuner/1.1/default/android.hardware.tv.tuner@1.1-service.xml b/tv/tuner/1.1/default/android.hardware.tv.tuner@1.1-service.xml new file mode 100644 index 0000000000..86b044525f --- /dev/null +++ b/tv/tuner/1.1/default/android.hardware.tv.tuner@1.1-service.xml @@ -0,0 +1,11 @@ + + + android.hardware.tv.tuner + hwbinder + 1.1 + + ITuner + default + + + \ No newline at end of file diff --git a/tv/tuner/1.1/default/service.cpp b/tv/tuner/1.1/default/service.cpp new file mode 100644 index 0000000000..232030850d --- /dev/null +++ b/tv/tuner/1.1/default/service.cpp @@ -0,0 +1,57 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#ifdef LAZY_SERVICE +#define LOG_TAG "android.hardware.tv.tuner@1.1-service-lazy" +#else +#define LOG_TAG "android.hardware.tv.tuner@1.1-service" +#endif + +#include +#include + +#include "Tuner.h" + +using android::hardware::configureRpcThreadpool; +using android::hardware::joinRpcThreadpool; +using android::hardware::LazyServiceRegistrar; +using android::hardware::tv::tuner::V1_0::implementation::Tuner; +using android::hardware::tv::tuner::V1_1::ITuner; + +#ifdef LAZY_SERVICE +const bool kLazyService = true; +#else +const bool kLazyService = false; +#endif + +int main() { + configureRpcThreadpool(8, true /* callerWillJoin */); + + // Setup hwbinder service + android::sp service = new Tuner(); + android::status_t status; + if (kLazyService) { + auto serviceRegistrar = LazyServiceRegistrar::getInstance(); + status = serviceRegistrar.registerService(service); + } else { + status = service->registerAsService(); + } + LOG_ALWAYS_FATAL_IF(status != android::OK, "Error while registering tuner service: %d", status); + + joinRpcThreadpool(); + return 0; +} diff --git a/tv/tuner/1.1/vts/OWNERS b/tv/tuner/1.1/vts/OWNERS new file mode 100644 index 0000000000..1b3d095f9c --- /dev/null +++ b/tv/tuner/1.1/vts/OWNERS @@ -0,0 +1,4 @@ +nchalko@google.com +amyjojo@google.com +shubang@google.com +quxiangfang@google.com diff --git a/tv/tuner/1.1/vts/functional/Android.bp b/tv/tuner/1.1/vts/functional/Android.bp new file mode 100644 index 0000000000..7ab0f87119 --- /dev/null +++ b/tv/tuner/1.1/vts/functional/Android.bp @@ -0,0 +1,48 @@ +// +// Copyright 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +cc_test { + name: "VtsHalTvTunerV1_1TargetTest", + defaults: ["VtsHalTargetTestDefaults"], + srcs: [ + "VtsHalTvTunerV1_1TargetTest.cpp", + "FrontendTests.cpp", + "DemuxTests.cpp", + "FilterTests.cpp", + ], + static_libs: [ + "android.hardware.cas@1.0", + "android.hardware.cas@1.1", + "android.hardware.cas@1.2", + "android.hardware.tv.tuner@1.0", + "android.hardware.tv.tuner@1.1", + "android.hidl.allocator@1.0", + "android.hidl.memory@1.0", + "libhidlallocatorutils", + "libhidlmemory", + "libcutils", + "libfmq", + ], + shared_libs: [ + "libbinder", + ], + test_suites: [ + "general-tests", + "vts", + ], + + require_root: true, +} diff --git a/tv/tuner/1.1/vts/functional/DemuxTests.cpp b/tv/tuner/1.1/vts/functional/DemuxTests.cpp new file mode 100644 index 0000000000..b1d8a0a0b2 --- /dev/null +++ b/tv/tuner/1.1/vts/functional/DemuxTests.cpp @@ -0,0 +1,41 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DemuxTests.h" + +AssertionResult DemuxTests::openDemux(sp& demux, uint32_t& demuxId) { + Result status; + mService->openDemux([&](Result result, uint32_t id, const sp& demuxSp) { + mDemux = demuxSp; + demux = demuxSp; + demuxId = id; + status = result; + }); + return AssertionResult(status == Result::SUCCESS); +} + +AssertionResult DemuxTests::setDemuxFrontendDataSource(uint32_t frontendId) { + EXPECT_TRUE(mDemux) << "Test with openDemux first."; + auto status = mDemux->setFrontendDataSource(frontendId); + return AssertionResult(status.isOk()); +} + +AssertionResult DemuxTests::closeDemux() { + EXPECT_TRUE(mDemux) << "Test with openDemux first."; + auto status = mDemux->close(); + mDemux = nullptr; + return AssertionResult(status.isOk()); +} \ No newline at end of file diff --git a/tv/tuner/1.1/vts/functional/DemuxTests.h b/tv/tuner/1.1/vts/functional/DemuxTests.h new file mode 100644 index 0000000000..c28d6ca524 --- /dev/null +++ b/tv/tuner/1.1/vts/functional/DemuxTests.h @@ -0,0 +1,56 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using android::sp; +using android::hardware::Return; +using android::hardware::Void; +using android::hardware::tv::tuner::V1_0::IDemux; +using android::hardware::tv::tuner::V1_0::IFilter; +using android::hardware::tv::tuner::V1_0::Result; +using android::hardware::tv::tuner::V1_1::ITuner; + +using ::testing::AssertionResult; + +class DemuxTests { + public: + void setService(sp tuner) { mService = tuner; } + + AssertionResult openDemux(sp& demux, uint32_t& demuxId); + AssertionResult setDemuxFrontendDataSource(uint32_t frontendId); + AssertionResult closeDemux(); + + protected: + static AssertionResult failure() { return ::testing::AssertionFailure(); } + + static AssertionResult success() { return ::testing::AssertionSuccess(); } + + sp mService; + sp mDemux; +}; diff --git a/tv/tuner/1.1/vts/functional/FilterTests.cpp b/tv/tuner/1.1/vts/functional/FilterTests.cpp new file mode 100644 index 0000000000..fb0c1a5cc8 --- /dev/null +++ b/tv/tuner/1.1/vts/functional/FilterTests.cpp @@ -0,0 +1,111 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "FilterTests.h" + +AssertionResult FilterTests::openFilterInDemux(DemuxFilterType type, uint32_t bufferSize) { + Result status; + EXPECT_TRUE(mDemux) << "Test with openDemux first."; + + // Create demux callback + mFilterCallback = new FilterCallback(); + + // Add filter to the local demux + mDemux->openFilter(type, bufferSize, mFilterCallback, + [&](Result result, const sp& filter) { + mFilter = filter; + status = result; + }); + + return AssertionResult(status == Result::SUCCESS); +} + +AssertionResult FilterTests::getNewlyOpenedFilterId_64bit(uint64_t& filterId) { + Result status; + EXPECT_TRUE(mDemux) << "Test with openDemux first."; + EXPECT_TRUE(mFilter) << "Test with openFilterInDemux first."; + EXPECT_TRUE(mFilterCallback) << "Test with openFilterInDemux first."; + + sp filter_v1_1 = + android::hardware::tv::tuner::V1_1::IFilter::castFrom(mFilter); + if (filter_v1_1 != NULL) { + filter_v1_1->getId64Bit([&](Result result, uint64_t filterId) { + mFilterId = filterId; + status = result; + }); + } else { + ALOGW("[vts] Can't cast IFilter into v1_1."); + return failure(); + } + + if (status == Result::SUCCESS) { + mUsedFilterIds.insert(mUsedFilterIds.end(), mFilterId); + mFilters[mFilterId] = mFilter; + mFilterCallbacks[mFilterId] = mFilterCallback; + filterId = mFilterId; + } + + return AssertionResult(status == Result::SUCCESS); +} + +AssertionResult FilterTests::configFilter(DemuxFilterSettings setting, uint64_t filterId) { + Result status; + EXPECT_TRUE(mFilters[filterId]) << "Test with getNewlyOpenedFilterId first."; + status = mFilters[filterId]->configure(setting); + + return AssertionResult(status == Result::SUCCESS); +} + +AssertionResult FilterTests::getFilterMQDescriptor(uint64_t filterId) { + Result status; + EXPECT_TRUE(mFilters[filterId]) << "Test with getNewlyOpenedFilterId first."; + EXPECT_TRUE(mFilterCallbacks[filterId]) << "Test with getNewlyOpenedFilterId first."; + + mFilter->getQueueDesc([&](Result result, const MQDesc& filterMQDesc) { + mFilterMQDescriptor = filterMQDesc; + status = result; + }); + + return AssertionResult(status == Result::SUCCESS); +} + +AssertionResult FilterTests::startFilter(uint64_t filterId) { + EXPECT_TRUE(mFilters[filterId]) << "Test with getNewlyOpenedFilterId first."; + Result status = mFilters[filterId]->start(); + return AssertionResult(status == Result::SUCCESS); +} + +AssertionResult FilterTests::stopFilter(uint64_t filterId) { + EXPECT_TRUE(mFilters[filterId]) << "Test with getNewlyOpenedFilterId first."; + Result status = mFilters[filterId]->stop(); + return AssertionResult(status == Result::SUCCESS); +} + +AssertionResult FilterTests::closeFilter(uint64_t filterId) { + EXPECT_TRUE(mFilters[filterId]) << "Test with getNewlyOpenedFilterId first."; + Result status = mFilters[filterId]->close(); + if (status == Result::SUCCESS) { + for (int i = 0; i < mUsedFilterIds.size(); i++) { + if (mUsedFilterIds[i] == filterId) { + mUsedFilterIds.erase(mUsedFilterIds.begin() + i); + break; + } + } + mFilterCallbacks.erase(filterId); + mFilters.erase(filterId); + } + return AssertionResult(status == Result::SUCCESS); +} diff --git a/tv/tuner/1.1/vts/functional/FilterTests.h b/tv/tuner/1.1/vts/functional/FilterTests.h new file mode 100644 index 0000000000..b1b6e13759 --- /dev/null +++ b/tv/tuner/1.1/vts/functional/FilterTests.h @@ -0,0 +1,168 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using android::Condition; +using android::Mutex; +using android::sp; +using android::hardware::EventFlag; +using android::hardware::hidl_handle; +using android::hardware::hidl_string; +using android::hardware::hidl_vec; +using android::hardware::kSynchronizedReadWrite; +using android::hardware::MessageQueue; +using android::hardware::MQDescriptorSync; +using android::hardware::Return; +using android::hardware::Void; +using android::hardware::tv::tuner::V1_0::DemuxFilterEvent; +using android::hardware::tv::tuner::V1_0::DemuxFilterMainType; +using android::hardware::tv::tuner::V1_0::DemuxFilterSettings; +using android::hardware::tv::tuner::V1_0::DemuxFilterStatus; +using android::hardware::tv::tuner::V1_0::DemuxFilterType; +using android::hardware::tv::tuner::V1_0::DemuxTsFilterType; +using android::hardware::tv::tuner::V1_0::IDemux; +using android::hardware::tv::tuner::V1_0::IFilter; +using android::hardware::tv::tuner::V1_0::IFilterCallback; +using android::hardware::tv::tuner::V1_0::Result; +using android::hardware::tv::tuner::V1_1::ITuner; + +using ::testing::AssertionResult; + +using namespace std; + +enum FilterEventType : uint8_t { + UNDEFINED, + SECTION, + MEDIA, + PES, + RECORD, + MMTPRECORD, + DOWNLOAD, + TEMI, +}; + +using FilterMQ = MessageQueue; +using MQDesc = MQDescriptorSync; + +#define WAIT_TIMEOUT 3000000000 + +class FilterCallback : public IFilterCallback { + public: + virtual Return onFilterEvent(const DemuxFilterEvent& /*filterEvent*/) override { + return Void(); + } + + virtual Return onFilterStatus(const DemuxFilterStatus /*status*/) override { + return Void(); + } +}; + +class FilterTests { + public: + void setService(sp tuner) { mService = tuner; } + void setDemux(sp demux) { mDemux = demux; } + sp getFilterById(uint64_t filterId) { return mFilters[filterId]; } + + std::map> getFilterCallbacks() { return mFilterCallbacks; } + + AssertionResult openFilterInDemux(DemuxFilterType type, uint32_t bufferSize); + AssertionResult getNewlyOpenedFilterId_64bit(uint64_t& filterId); + AssertionResult configFilter(DemuxFilterSettings setting, uint64_t filterId); + AssertionResult getFilterMQDescriptor(uint64_t filterId); + AssertionResult startFilter(uint64_t filterId); + AssertionResult stopFilter(uint64_t filterId); + AssertionResult closeFilter(uint64_t filterId); + + FilterEventType getFilterEventType(DemuxFilterType type) { + FilterEventType eventType = FilterEventType::UNDEFINED; + switch (type.mainType) { + case DemuxFilterMainType::TS: + switch (type.subType.tsFilterType()) { + case DemuxTsFilterType::UNDEFINED: + break; + case DemuxTsFilterType::SECTION: + eventType = FilterEventType::SECTION; + break; + case DemuxTsFilterType::PES: + eventType = FilterEventType::PES; + break; + case DemuxTsFilterType::TS: + break; + case DemuxTsFilterType::AUDIO: + case DemuxTsFilterType::VIDEO: + eventType = FilterEventType::MEDIA; + break; + case DemuxTsFilterType::PCR: + break; + case DemuxTsFilterType::RECORD: + eventType = FilterEventType::RECORD; + break; + case DemuxTsFilterType::TEMI: + eventType = FilterEventType::TEMI; + break; + } + break; + case DemuxFilterMainType::MMTP: + /*mmtpSettings*/ + break; + case DemuxFilterMainType::IP: + /*ipSettings*/ + break; + case DemuxFilterMainType::TLV: + /*tlvSettings*/ + break; + case DemuxFilterMainType::ALP: + /*alpSettings*/ + break; + default: + break; + } + return eventType; + } + + protected: + static AssertionResult failure() { return ::testing::AssertionFailure(); } + + static AssertionResult success() { return ::testing::AssertionSuccess(); } + + sp mService; + sp mFilter; + sp mDemux; + std::map> mFilters; + std::map> mFilterCallbacks; + + sp mFilterCallback; + MQDesc mFilterMQDescriptor; + vector mUsedFilterIds; + + uint64_t mFilterId = -1; +}; diff --git a/tv/tuner/1.1/vts/functional/FrontendTests.cpp b/tv/tuner/1.1/vts/functional/FrontendTests.cpp new file mode 100644 index 0000000000..8c359c51a6 --- /dev/null +++ b/tv/tuner/1.1/vts/functional/FrontendTests.cpp @@ -0,0 +1,83 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "FrontendTests.h" + +Return FrontendCallback::onEvent(FrontendEventType /*frontendEventType*/) { + return Void(); +} + +Return FrontendCallback::onScanMessage(FrontendScanMessageType /*type*/, + const FrontendScanMessage& /*message*/) { + return Void(); +} + +AssertionResult FrontendTests::getFrontendIds() { + Result status; + mService->getFrontendIds([&](Result result, const hidl_vec& frontendIds) { + status = result; + mFeIds = frontendIds; + }); + return AssertionResult(status == Result::SUCCESS); +} + +AssertionResult FrontendTests::getFrontendInfo(uint32_t frontendId) { + Result status; + mService->getFrontendInfo(frontendId, [&](Result result, const FrontendInfo& frontendInfo) { + mFrontendInfo = frontendInfo; + status = result; + }); + return AssertionResult(status == Result::SUCCESS); +} + +AssertionResult FrontendTests::openFrontendById(uint32_t frontendId) { + Result status; + mService->openFrontendById(frontendId, [&](Result result, const sp& frontend) { + mFrontend = frontend; + status = result; + }); + return AssertionResult(status == Result::SUCCESS); +} + +AssertionResult FrontendTests::setFrontendCallback() { + EXPECT_TRUE(mFrontend) << "Test with openFrontendById first."; + mFrontendCallback = new FrontendCallback(); + auto callbackStatus = mFrontend->setCallback(mFrontendCallback); + return AssertionResult(callbackStatus.isOk()); +} + +AssertionResult FrontendTests::closeFrontend() { + EXPECT_TRUE(mFrontend) << "Test with openFrontendById first."; + Result status; + status = mFrontend->close(); + mFrontend = nullptr; + mFrontendCallback = nullptr; + return AssertionResult(status == Result::SUCCESS); +} + +void FrontendTests::getFrontendIdByType(FrontendType feType, uint32_t& feId) { + ASSERT_TRUE(getFrontendIds()); + ASSERT_TRUE(mFeIds.size() > 0); + for (size_t i = 0; i < mFeIds.size(); i++) { + ASSERT_TRUE(getFrontendInfo(mFeIds[i])); + if (mFrontendInfo.type != feType) { + continue; + } + feId = mFeIds[i]; + return; + } + feId = INVALID_ID; +} \ No newline at end of file diff --git a/tv/tuner/1.1/vts/functional/FrontendTests.h b/tv/tuner/1.1/vts/functional/FrontendTests.h new file mode 100644 index 0000000000..e68758919d --- /dev/null +++ b/tv/tuner/1.1/vts/functional/FrontendTests.h @@ -0,0 +1,96 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "VtsHalTvTunerV1_1TestConfigurations.h" + +#define WAIT_TIMEOUT 3000000000 +#define INVALID_ID -1 + +using android::Condition; +using android::IMemory; +using android::IMemoryHeap; +using android::MemoryDealer; +using android::Mutex; +using android::sp; +using android::hardware::fromHeap; +using android::hardware::hidl_vec; +using android::hardware::Return; +using android::hardware::Void; +using android::hardware::tv::tuner::V1_0::FrontendEventType; +using android::hardware::tv::tuner::V1_0::FrontendId; +using android::hardware::tv::tuner::V1_0::FrontendInfo; +using android::hardware::tv::tuner::V1_0::FrontendScanMessage; +using android::hardware::tv::tuner::V1_0::FrontendScanMessageType; +using android::hardware::tv::tuner::V1_0::IFrontend; +using android::hardware::tv::tuner::V1_0::IFrontendCallback; +using android::hardware::tv::tuner::V1_0::Result; +using android::hardware::tv::tuner::V1_1::ITuner; + +using ::testing::AssertionResult; + +using namespace std; + +#define INVALID_ID -1 + +class FrontendCallback : public IFrontendCallback { + public: + virtual Return onEvent(FrontendEventType frontendEventType) override; + virtual Return onScanMessage(FrontendScanMessageType type, + const FrontendScanMessage& message) override; +}; + +class FrontendTests { + public: + sp mService; + + void setService(sp tuner) { mService = tuner; } + + AssertionResult getFrontendIds(); + AssertionResult getFrontendInfo(uint32_t frontendId); + AssertionResult openFrontendById(uint32_t frontendId); + AssertionResult setFrontendCallback(); + AssertionResult closeFrontend(); + + void getFrontendIdByType(FrontendType feType, uint32_t& feId); + + protected: + static AssertionResult failure() { return ::testing::AssertionFailure(); } + static AssertionResult success() { return ::testing::AssertionSuccess(); } + + sp mFrontend; + FrontendInfo mFrontendInfo; + sp mFrontendCallback; + hidl_vec mFeIds; + + bool mIsSoftwareFe = false; +}; \ No newline at end of file diff --git a/tv/tuner/1.1/vts/functional/VtsHalTvTunerV1_1TargetTest.cpp b/tv/tuner/1.1/vts/functional/VtsHalTvTunerV1_1TargetTest.cpp new file mode 100644 index 0000000000..7c113d4421 --- /dev/null +++ b/tv/tuner/1.1/vts/functional/VtsHalTvTunerV1_1TargetTest.cpp @@ -0,0 +1,58 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "VtsHalTvTunerV1_1TargetTest.h" + +namespace { + +void TunerFilterHidlTest::configSingleFilterInDemuxTest(FilterConfig filterConf, + FrontendConfig frontendConf) { + uint32_t feId; + uint32_t demuxId; + sp demux; + uint64_t filterId; + + mFrontendTests.getFrontendIdByType(frontendConf.type, feId); + ASSERT_TRUE(feId != INVALID_ID); + ASSERT_TRUE(mFrontendTests.openFrontendById(feId)); + ASSERT_TRUE(mFrontendTests.setFrontendCallback()); + ASSERT_TRUE(mDemuxTests.openDemux(demux, demuxId)); + ASSERT_TRUE(mDemuxTests.setDemuxFrontendDataSource(feId)); + mFilterTests.setDemux(demux); + ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize)); + ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId_64bit(filterId)); + ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId)); + ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId)); + ASSERT_TRUE(mFilterTests.startFilter(filterId)); + ASSERT_TRUE(mFilterTests.stopFilter(filterId)); + ASSERT_TRUE(mFilterTests.closeFilter(filterId)); + ASSERT_TRUE(mDemuxTests.closeDemux()); + ASSERT_TRUE(mFrontendTests.closeFrontend()); +} + +TEST_P(TunerFilterHidlTest, StartFilterInDemux) { + description("Open and start a filter in Demux."); + // TODO use parameterized tests + configSingleFilterInDemuxTest(filterArray[TS_VIDEO0], frontendArray[DVBT]); +} + +INSTANTIATE_TEST_SUITE_P( + PerInstance, TunerFilterHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(ITuner::descriptor)), + android::hardware::PrintInstanceNameToString); + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TunerFilterHidlTest); +} // namespace diff --git a/tv/tuner/1.1/vts/functional/VtsHalTvTunerV1_1TargetTest.h b/tv/tuner/1.1/vts/functional/VtsHalTvTunerV1_1TargetTest.h new file mode 100644 index 0000000000..04536343fa --- /dev/null +++ b/tv/tuner/1.1/vts/functional/VtsHalTvTunerV1_1TargetTest.h @@ -0,0 +1,52 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DemuxTests.h" +#include "FilterTests.h" +#include "FrontendTests.h" + +namespace { + +void initConfiguration() { + initFrontendConfig(); + initFilterConfig(); +} + +class TunerFilterHidlTest : public testing::TestWithParam { + public: + virtual void SetUp() override { + mService = ITuner::getService(GetParam()); + ASSERT_NE(mService, nullptr); + initConfiguration(); + + mFrontendTests.setService(mService); + mDemuxTests.setService(mService); + mFilterTests.setService(mService); + } + + protected: + static void description(const std::string& description) { + RecordProperty("description", description); + } + + void configSingleFilterInDemuxTest(FilterConfig filterConf, FrontendConfig frontendConf); + + sp mService; + FrontendTests mFrontendTests; + DemuxTests mDemuxTests; + FilterTests mFilterTests; +}; +} // namespace \ No newline at end of file diff --git a/tv/tuner/1.1/vts/functional/VtsHalTvTunerV1_1TestConfigurations.h b/tv/tuner/1.1/vts/functional/VtsHalTvTunerV1_1TestConfigurations.h new file mode 100644 index 0000000000..23c9353d03 --- /dev/null +++ b/tv/tuner/1.1/vts/functional/VtsHalTvTunerV1_1TestConfigurations.h @@ -0,0 +1,178 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +using android::hardware::tv::tuner::V1_0::DemuxAlpFilterType; +using android::hardware::tv::tuner::V1_0::DemuxFilterEvent; +using android::hardware::tv::tuner::V1_0::DemuxFilterMainType; +using android::hardware::tv::tuner::V1_0::DemuxFilterSettings; +using android::hardware::tv::tuner::V1_0::DemuxFilterType; +using android::hardware::tv::tuner::V1_0::DemuxIpFilterType; +using android::hardware::tv::tuner::V1_0::DemuxMmtpFilterType; +using android::hardware::tv::tuner::V1_0::DemuxRecordScIndexType; +using android::hardware::tv::tuner::V1_0::DemuxTsFilterType; +using android::hardware::tv::tuner::V1_0::FrontendDvbtBandwidth; +using android::hardware::tv::tuner::V1_0::FrontendDvbtCoderate; +using android::hardware::tv::tuner::V1_0::FrontendDvbtConstellation; +using android::hardware::tv::tuner::V1_0::FrontendDvbtGuardInterval; +using android::hardware::tv::tuner::V1_0::FrontendDvbtHierarchy; +using android::hardware::tv::tuner::V1_0::FrontendDvbtSettings; +using android::hardware::tv::tuner::V1_0::FrontendDvbtStandard; +using android::hardware::tv::tuner::V1_0::FrontendDvbtTransmissionMode; +using android::hardware::tv::tuner::V1_0::FrontendSettings; +using android::hardware::tv::tuner::V1_0::FrontendStatus; +using android::hardware::tv::tuner::V1_0::FrontendStatusType; +using android::hardware::tv::tuner::V1_0::FrontendType; + +using namespace std; + +const uint32_t FMQ_SIZE_1M = 0x100000; +const uint32_t FMQ_SIZE_4M = 0x400000; +const uint32_t FMQ_SIZE_16M = 0x1000000; + +typedef enum { + TS_VIDEO0, + TS_VIDEO1, + TS_AUDIO0, + TS_AUDIO1, + TS_PES0, + TS_PCR0, + TS_SECTION0, + TS_TS0, + TS_RECORD0, + FILTER_MAX, +} Filter; + +typedef enum { + DVBT, + DVBS, + FRONTEND_MAX, +} Frontend; + +struct FilterConfig { + uint32_t bufferSize; + DemuxFilterType type; + DemuxFilterSettings settings; + + bool operator<(const FilterConfig& /*c*/) const { return false; } +}; + +struct FrontendConfig { + bool isSoftwareFe; + FrontendType type; + FrontendSettings settings; + vector tuneStatusTypes; + vector expectTuneStatuses; +}; + +static FrontendConfig frontendArray[FILTER_MAX]; +static FilterConfig filterArray[FILTER_MAX]; + +/** Configuration array for the frontend tune test */ +inline void initFrontendConfig() { + FrontendDvbtSettings dvbtSettings{ + .frequency = 578000, + .transmissionMode = FrontendDvbtTransmissionMode::AUTO, + .bandwidth = FrontendDvbtBandwidth::BANDWIDTH_8MHZ, + .constellation = FrontendDvbtConstellation::AUTO, + .hierarchy = FrontendDvbtHierarchy::AUTO, + .hpCoderate = FrontendDvbtCoderate::AUTO, + .lpCoderate = FrontendDvbtCoderate::AUTO, + .guardInterval = FrontendDvbtGuardInterval::AUTO, + .isHighPriority = true, + .standard = FrontendDvbtStandard::T, + }; + frontendArray[DVBT].type = FrontendType::DVBT, frontendArray[DVBT].settings.dvbt(dvbtSettings); + vector types; + types.push_back(FrontendStatusType::DEMOD_LOCK); + FrontendStatus status; + status.isDemodLocked(true); + vector statuses; + statuses.push_back(status); + frontendArray[DVBT].tuneStatusTypes = types; + frontendArray[DVBT].expectTuneStatuses = statuses; + frontendArray[DVBT].isSoftwareFe = true; + frontendArray[DVBS].type = FrontendType::DVBS; + frontendArray[DVBS].isSoftwareFe = true; +}; + +/** Configuration array for the filter test */ +inline void initFilterConfig() { + // TS VIDEO filter setting for default implementation testing + filterArray[TS_VIDEO0].type.mainType = DemuxFilterMainType::TS; + filterArray[TS_VIDEO0].type.subType.tsFilterType(DemuxTsFilterType::VIDEO); + filterArray[TS_VIDEO0].bufferSize = FMQ_SIZE_16M; + filterArray[TS_VIDEO0].settings.ts().tpid = 256; + filterArray[TS_VIDEO0].settings.ts().filterSettings.av({.isPassthrough = false}); + filterArray[TS_VIDEO1].type.mainType = DemuxFilterMainType::TS; + filterArray[TS_VIDEO1].type.subType.tsFilterType(DemuxTsFilterType::VIDEO); + filterArray[TS_VIDEO1].bufferSize = FMQ_SIZE_16M; + filterArray[TS_VIDEO1].settings.ts().tpid = 256; + filterArray[TS_VIDEO1].settings.ts().filterSettings.av({.isPassthrough = false}); + // TS AUDIO filter setting + filterArray[TS_AUDIO0].type.mainType = DemuxFilterMainType::TS; + filterArray[TS_AUDIO0].type.subType.tsFilterType(DemuxTsFilterType::AUDIO); + filterArray[TS_AUDIO0].bufferSize = FMQ_SIZE_16M; + filterArray[TS_AUDIO0].settings.ts().tpid = 256; + filterArray[TS_AUDIO0].settings.ts().filterSettings.av({.isPassthrough = false}); + filterArray[TS_AUDIO1].type.mainType = DemuxFilterMainType::TS; + filterArray[TS_AUDIO1].type.subType.tsFilterType(DemuxTsFilterType::AUDIO); + filterArray[TS_AUDIO1].bufferSize = FMQ_SIZE_16M; + filterArray[TS_AUDIO1].settings.ts().tpid = 257; + filterArray[TS_AUDIO1].settings.ts().filterSettings.av({.isPassthrough = false}); + // TS PES filter setting + filterArray[TS_PES0].type.mainType = DemuxFilterMainType::TS; + filterArray[TS_PES0].type.subType.tsFilterType(DemuxTsFilterType::PES); + filterArray[TS_PES0].bufferSize = FMQ_SIZE_16M; + filterArray[TS_PES0].settings.ts().tpid = 256; + filterArray[TS_PES0].settings.ts().filterSettings.pesData({ + .isRaw = false, + .streamId = 0xbd, + }); + // TS PCR filter setting + filterArray[TS_PCR0].type.mainType = DemuxFilterMainType::TS; + filterArray[TS_PCR0].type.subType.tsFilterType(DemuxTsFilterType::PCR); + filterArray[TS_PCR0].bufferSize = FMQ_SIZE_16M; + filterArray[TS_PCR0].settings.ts().tpid = 256; + filterArray[TS_PCR0].settings.ts().filterSettings.noinit(); + // TS filter setting + filterArray[TS_TS0].type.mainType = DemuxFilterMainType::TS; + filterArray[TS_TS0].type.subType.tsFilterType(DemuxTsFilterType::TS); + filterArray[TS_TS0].bufferSize = FMQ_SIZE_16M; + filterArray[TS_TS0].settings.ts().tpid = 256; + filterArray[TS_TS0].settings.ts().filterSettings.noinit(); + // TS SECTION filter setting + filterArray[TS_SECTION0].type.mainType = DemuxFilterMainType::TS; + filterArray[TS_SECTION0].type.subType.tsFilterType(DemuxTsFilterType::SECTION); + filterArray[TS_SECTION0].bufferSize = FMQ_SIZE_16M; + filterArray[TS_SECTION0].settings.ts().tpid = 256; + filterArray[TS_SECTION0].settings.ts().filterSettings.section({ + .isRaw = false, + }); + // TS RECORD filter setting + filterArray[TS_RECORD0].type.mainType = DemuxFilterMainType::TS; + filterArray[TS_RECORD0].type.subType.tsFilterType(DemuxTsFilterType::RECORD); + filterArray[TS_RECORD0].settings.ts().tpid = 81; + filterArray[TS_RECORD0].settings.ts().filterSettings.record({ + .scIndexType = DemuxRecordScIndexType::NONE, + }); +};