Add Tuner AIDL default implementation
Bug: 191825295
Test: make and run VtsHalTvTunerTargetTest
Change-Id: I781f67424ca1890f038160a4fda660507ab9e916
diff --git a/tv/tuner/aidl/default/Android.bp b/tv/tuner/aidl/default/Android.bp
new file mode 100644
index 0000000..ba4af44
--- /dev/null
+++ b/tv/tuner/aidl/default/Android.bp
@@ -0,0 +1,46 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "hardware_interfaces_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+cc_binary {
+ name: "android.hardware.tv.tuner-service.example",
+ relative_install_path: "hw",
+ init_rc: ["tuner-default.rc"],
+ vintf_fragments: ["tuner-default.xml"],
+ vendor: true,
+ compile_multilib: "first",
+ srcs: [
+ "Demux.cpp",
+ "Descrambler.cpp",
+ "Dvr.cpp",
+ "Filter.cpp",
+ "Frontend.cpp",
+ "Lnb.cpp",
+ "TimeFilter.cpp",
+ "Tuner.cpp",
+ "service.cpp",
+ ],
+ static_libs: [
+ "libaidlcommonsupport",
+ ],
+ shared_libs: [
+ "android.hardware.common.fmq-V1-ndk_platform",
+ "android.hardware.tv.tuner-V1-ndk_platform",
+ "libbase",
+ "libbinder_ndk",
+ "libcutils",
+ "libdmabufheap",
+ "libfmq",
+ "libion",
+ "liblog",
+ "libutils",
+ ],
+ header_libs: [
+ "media_plugin_headers",
+ ],
+}
diff --git a/tv/tuner/aidl/default/Demux.cpp b/tv/tuner/aidl/default/Demux.cpp
new file mode 100644
index 0000000..15022ee
--- /dev/null
+++ b/tv/tuner/aidl/default/Demux.cpp
@@ -0,0 +1,427 @@
+/*
+ * Copyright 2021 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
+#define LOG_TAG "android.hardware.tv.tuner-service.example-Demux"
+
+#include <aidl/android/hardware/tv/tuner/DemuxQueueNotifyBits.h>
+
+#include <utils/Log.h>
+#include "Demux.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+#define WAIT_TIMEOUT 3000000000
+
+Demux::Demux(int32_t demuxId, std::shared_ptr<Tuner> tuner) {
+ mDemuxId = demuxId;
+ mTunerService = tuner;
+}
+
+Demux::~Demux() {
+ mFrontendInputThreadRunning = false;
+ std::lock_guard<std::mutex> lock(mFrontendInputThreadLock);
+}
+
+::ndk::ScopedAStatus Demux::setFrontendDataSource(int32_t in_frontendId) {
+ ALOGV("%s", __FUNCTION__);
+
+ if (mTunerService == nullptr) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_NO_INIT);
+ }
+
+ mFrontend = mTunerService->getFrontendById(in_frontendId);
+
+ if (mFrontend == nullptr) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+
+ mTunerService->setFrontendAsDemuxSource(in_frontendId, mDemuxId);
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Demux::openFilter(const DemuxFilterType& in_type, int32_t in_bufferSize,
+ const std::shared_ptr<IFilterCallback>& in_cb,
+ std::shared_ptr<IFilter>* _aidl_return) {
+ ALOGV("%s", __FUNCTION__);
+
+ int64_t filterId;
+ filterId = ++mLastUsedFilterId;
+
+ if (in_cb == nullptr) {
+ ALOGW("[Demux] callback can't be null");
+ *_aidl_return = nullptr;
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+
+ std::shared_ptr<Filter> filter =
+ ndk::SharedRefBase::make<Filter>(in_type, filterId, in_bufferSize, in_cb, ref<Demux>());
+ if (!filter->createFilterMQ()) {
+ *_aidl_return = nullptr;
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+ }
+
+ 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);
+ }
+ }
+
+ if (!result) {
+ *_aidl_return = nullptr;
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+
+ *_aidl_return = filter;
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Demux::openTimeFilter(std::shared_ptr<ITimeFilter>* _aidl_return) {
+ ALOGV("%s", __FUNCTION__);
+
+ mTimeFilter = ndk::SharedRefBase::make<TimeFilter>(ref<Demux>());
+
+ *_aidl_return = mTimeFilter;
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Demux::getAvSyncHwId(const std::shared_ptr<IFilter>& in_filter,
+ int32_t* _aidl_return) {
+ ALOGV("%s", __FUNCTION__);
+
+ int64_t id;
+ ::ndk::ScopedAStatus status;
+
+ status = in_filter->getId64Bit(&id);
+ if (!status.isOk()) {
+ ALOGE("[Demux] Can't get filter Id.");
+ *_aidl_return = -1;
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+
+ if (!mFilters[id]->isMediaFilter()) {
+ ALOGE("[Demux] Given filter is not a media filter.");
+ *_aidl_return = -1;
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+
+ if (!mPcrFilterIds.empty()) {
+ // Return the lowest pcr filter id in the default implementation as the av sync id
+ *_aidl_return = *mPcrFilterIds.begin();
+ return ::ndk::ScopedAStatus::ok();
+ }
+
+ ALOGE("[Demux] No PCR filter opened.");
+ *_aidl_return = -1;
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+}
+
+::ndk::ScopedAStatus Demux::getAvSyncTime(int32_t in_avSyncHwId, int64_t* _aidl_return) {
+ ALOGV("%s", __FUNCTION__);
+
+ if (mPcrFilterIds.empty()) {
+ *_aidl_return = -1;
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+ if (in_avSyncHwId != *mPcrFilterIds.begin()) {
+ *_aidl_return = -1;
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+
+ *_aidl_return = -1;
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Demux::close() {
+ ALOGV("%s", __FUNCTION__);
+
+ set<int64_t>::iterator it;
+ for (it = mPlaybackFilterIds.begin(); it != mPlaybackFilterIds.end(); it++) {
+ mDvrPlayback->removePlaybackFilter(*it);
+ }
+ mPlaybackFilterIds.clear();
+ mRecordFilterIds.clear();
+ mFilters.clear();
+ mLastUsedFilterId = -1;
+ mTunerService->removeDemux(mDemuxId);
+ mFrontendInputThreadRunning = false;
+ std::lock_guard<std::mutex> lock(mFrontendInputThreadLock);
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Demux::openDvr(DvrType in_type, int32_t in_bufferSize,
+ const std::shared_ptr<IDvrCallback>& in_cb,
+ std::shared_ptr<IDvr>* _aidl_return) {
+ ALOGV("%s", __FUNCTION__);
+
+ if (in_cb == nullptr) {
+ ALOGW("[Demux] DVR callback can't be null");
+ *_aidl_return = nullptr;
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+
+ set<int64_t>::iterator it;
+ switch (in_type) {
+ case DvrType::PLAYBACK:
+ mDvrPlayback =
+ ndk::SharedRefBase::make<Dvr>(in_type, in_bufferSize, in_cb, ref<Demux>());
+ if (!mDvrPlayback->createDvrMQ()) {
+ mDvrPlayback = nullptr;
+ *_aidl_return = mDvrPlayback;
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+ }
+
+ for (it = mPlaybackFilterIds.begin(); it != mPlaybackFilterIds.end(); it++) {
+ if (!mDvrPlayback->addPlaybackFilter(*it, mFilters[*it])) {
+ ALOGE("[Demux] Can't get filter info for DVR playback");
+ mDvrPlayback = nullptr;
+ *_aidl_return = mDvrPlayback;
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+ }
+ }
+
+ *_aidl_return = mDvrPlayback;
+ return ::ndk::ScopedAStatus::ok();
+ case DvrType::RECORD:
+ mDvrRecord = ndk::SharedRefBase::make<Dvr>(in_type, in_bufferSize, in_cb, ref<Demux>());
+ if (!mDvrRecord->createDvrMQ()) {
+ mDvrRecord = nullptr;
+ *_aidl_return = mDvrRecord;
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+ }
+
+ *_aidl_return = mDvrRecord;
+ return ::ndk::ScopedAStatus::ok();
+ default:
+ *_aidl_return = nullptr;
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+}
+
+::ndk::ScopedAStatus Demux::connectCiCam(int32_t in_ciCamId) {
+ ALOGV("%s", __FUNCTION__);
+
+ mCiCamId = in_ciCamId;
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Demux::disconnectCiCam() {
+ ALOGV("%s", __FUNCTION__);
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Demux::removeFilter(int64_t filterId) {
+ ALOGV("%s", __FUNCTION__);
+
+ if (mDvrPlayback != nullptr) {
+ mDvrPlayback->removePlaybackFilter(filterId);
+ }
+ mPlaybackFilterIds.erase(filterId);
+ mRecordFilterIds.erase(filterId);
+ mFilters.erase(filterId);
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+void Demux::startBroadcastTsFilter(vector<int8_t> data) {
+ set<int64_t>::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<int8_t> data) {
+ set<int64_t>::iterator it;
+ if (DEBUG_DEMUX) {
+ ALOGW("[Demux] update record filter output");
+ }
+ for (it = mRecordFilterIds.begin(); it != mRecordFilterIds.end(); it++) {
+ mFilters[*it]->updateRecordOutput(data);
+ }
+}
+
+void Demux::sendFrontendInputToRecord(vector<int8_t> data, uint16_t pid, uint64_t pts) {
+ sendFrontendInputToRecord(data);
+ set<int64_t>::iterator it;
+ for (it = mRecordFilterIds.begin(); it != mRecordFilterIds.end(); it++) {
+ if (pid == mFilters[*it]->getTpid()) {
+ mFilters[*it]->updatePts(pts);
+ }
+ }
+}
+
+bool Demux::startBroadcastFilterDispatcher() {
+ set<int64_t>::iterator it;
+
+ // Handle the output data per filter type
+ for (it = mPlaybackFilterIds.begin(); it != mPlaybackFilterIds.end(); it++) {
+ if (!mFilters[*it]->startFilterHandler().isOk()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool Demux::startRecordFilterDispatcher() {
+ set<int64_t>::iterator it;
+
+ for (it = mRecordFilterIds.begin(); it != mRecordFilterIds.end(); it++) {
+ if (!mFilters[*it]->startRecordFilterHandler().isOk()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+::ndk::ScopedAStatus Demux::startFilterHandler(int64_t filterId) {
+ return mFilters[filterId]->startFilterHandler();
+}
+
+void Demux::updateFilterOutput(int64_t filterId, vector<int8_t> data) {
+ mFilters[filterId]->updateFilterOutput(data);
+}
+
+void Demux::updateMediaFilterOutput(int64_t filterId, vector<int8_t> data, uint64_t pts) {
+ updateFilterOutput(filterId, data);
+ mFilters[filterId]->updatePts(pts);
+}
+
+uint16_t Demux::getFilterTpid(int64_t filterId) {
+ return mFilters[filterId]->getTpid();
+}
+
+void Demux::startFrontendInputLoop() {
+ mFrontendInputThreadRunning = true;
+ pthread_create(&mFrontendInputThread, NULL, __threadLoopFrontend, this);
+ pthread_setname_np(mFrontendInputThread, "frontend_input_thread");
+}
+
+void* Demux::__threadLoopFrontend(void* user) {
+ Demux* const self = static_cast<Demux*>(user);
+ self->frontendInputThreadLoop();
+ return 0;
+}
+
+void Demux::frontendInputThreadLoop() {
+ if (!mFrontendInputThreadRunning) {
+ return;
+ }
+
+ std::lock_guard<std::mutex> lock(mFrontendInputThreadLock);
+ if (!mDvrPlayback) {
+ ALOGW("[Demux] No software Frontend input configured. Ending Frontend thread loop.");
+ mFrontendInputThreadRunning = false;
+ return;
+ }
+
+ while (mFrontendInputThreadRunning) {
+ uint32_t efState = 0;
+ ::android::status_t status = mDvrPlayback->getDvrEventFlag()->wait(
+ static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY), &efState, WAIT_TIMEOUT,
+ true /* retry on spurious wake */);
+ if (status != ::android::OK) {
+ ALOGD("[Demux] wait for data ready on the playback FMQ");
+ continue;
+ }
+ if (mDvrPlayback->getSettings().get<DvrSettings::Tag::playback>().dataFormat ==
+ DataFormat::ES) {
+ if (!mDvrPlayback->processEsDataOnPlayback(true /*isVirtualFrontend*/, mIsRecording)) {
+ ALOGE("[Demux] playback es data failed to be filtered. Ending thread");
+ break;
+ }
+ continue;
+ }
+ // Our current implementation filter the data and write it into the filter FMQ immediately
+ // after the DATA_READY from the VTS/framework
+ // This is for the non-ES data source, real playback use case handling.
+ 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<std::mutex> lock(mFrontendInputThreadLock);
+}
+
+void Demux::setIsRecording(bool isRecording) {
+ mIsRecording = isRecording;
+}
+
+bool Demux::isRecording() {
+ return mIsRecording;
+}
+
+bool Demux::attachRecordFilter(int64_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(int64_t filterId) {
+ if (mFilters[filterId] == nullptr || mDvrRecord == nullptr) {
+ return false;
+ }
+
+ mRecordFilterIds.erase(filterId);
+ mFilters[filterId]->detachFilterFromRecord();
+
+ return true;
+}
+
+} // namespace tuner
+} // namespace tv
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/tv/tuner/aidl/default/Demux.h b/tv/tuner/aidl/default/Demux.h
new file mode 100644
index 0000000..9f96d0f
--- /dev/null
+++ b/tv/tuner/aidl/default/Demux.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/tv/tuner/BnDemux.h>
+
+#include <fmq/AidlMessageQueue.h>
+#include <math.h>
+#include <set>
+#include "Dvr.h"
+#include "Filter.h"
+#include "Frontend.h"
+#include "TimeFilter.h"
+#include "Tuner.h"
+
+using namespace std;
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+using ::aidl::android::hardware::common::fmq::MQDescriptor;
+using ::aidl::android::hardware::common::fmq::SynchronizedReadWrite;
+using ::android::AidlMessageQueue;
+using ::android::hardware::EventFlag;
+
+using FilterMQ = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
+
+class Dvr;
+class Filter;
+class Frontend;
+class TimeFilter;
+class Tuner;
+
+class Demux : public BnDemux {
+ public:
+ Demux(int32_t demuxId, std::shared_ptr<Tuner> tuner);
+ ~Demux();
+
+ ::ndk::ScopedAStatus setFrontendDataSource(int32_t in_frontendId) override;
+ ::ndk::ScopedAStatus openFilter(const DemuxFilterType& in_type, int32_t in_bufferSize,
+ const std::shared_ptr<IFilterCallback>& in_cb,
+ std::shared_ptr<IFilter>* _aidl_return) override;
+ ::ndk::ScopedAStatus openTimeFilter(std::shared_ptr<ITimeFilter>* _aidl_return) override;
+ ::ndk::ScopedAStatus getAvSyncHwId(const std::shared_ptr<IFilter>& in_filter,
+ int32_t* _aidl_return) override;
+ ::ndk::ScopedAStatus getAvSyncTime(int32_t in_avSyncHwId, int64_t* _aidl_return) override;
+ ::ndk::ScopedAStatus close() override;
+ ::ndk::ScopedAStatus openDvr(DvrType in_type, int32_t in_bufferSize,
+ const std::shared_ptr<IDvrCallback>& in_cb,
+ std::shared_ptr<IDvr>* _aidl_return) override;
+ ::ndk::ScopedAStatus connectCiCam(int32_t in_ciCamId) override;
+ ::ndk::ScopedAStatus disconnectCiCam() override;
+
+ // Functions interacts with Tuner Service
+ void stopFrontendInput();
+ ::ndk::ScopedAStatus removeFilter(int64_t filterId);
+ bool attachRecordFilter(int64_t filterId);
+ bool detachRecordFilter(int64_t filterId);
+ ::ndk::ScopedAStatus startFilterHandler(int64_t filterId);
+ void updateFilterOutput(int64_t filterId, vector<int8_t> data);
+ void updateMediaFilterOutput(int64_t filterId, vector<int8_t> data, uint64_t pts);
+ uint16_t getFilterTpid(int64_t filterId);
+ void setIsRecording(bool isRecording);
+ 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<int8_t> data);
+
+ void sendFrontendInputToRecord(vector<int8_t> data);
+ void sendFrontendInputToRecord(vector<int8_t> data, uint16_t pid, uint64_t pts);
+ bool startRecordFilterDispatcher();
+
+ private:
+ // Tuner service
+ std::shared_ptr<Tuner> mTunerService;
+
+ // Frontend source
+ std::shared_ptr<Frontend> mFrontend;
+
+ // A struct that passes the arguments to a newly created filter thread
+ struct ThreadArgs {
+ Demux* user;
+ int64_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();
+
+ int32_t mDemuxId = -1;
+ int32_t mCiCamId;
+ set<int64_t> mPcrFilterIds;
+ /**
+ * Record the last used filter id. Initial value is -1.
+ * Filter Id starts with 0.
+ */
+ int64_t mLastUsedFilterId = -1;
+ /**
+ * Record all the used playback filter Ids.
+ * Any removed filter id should be removed from this set.
+ */
+ set<int64_t> mPlaybackFilterIds;
+ /**
+ * Record all the attached record filter Ids.
+ * Any removed filter id should be removed from this set.
+ */
+ set<int64_t> mRecordFilterIds;
+ /**
+ * A list of created Filter sp.
+ * The array number is the filter ID.
+ */
+ std::map<int64_t, std::shared_ptr<Filter>> mFilters;
+
+ /**
+ * Local reference to the opened Timer Filter instance.
+ */
+ std::shared_ptr<TimeFilter> mTimeFilter;
+
+ /**
+ * Local reference to the opened DVR object.
+ */
+ std::shared_ptr<Dvr> mDvrPlayback;
+ std::shared_ptr<Dvr> 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<uint8_t> mPesOutput;
+
+ const bool DEBUG_DEMUX = false;
+};
+
+} // namespace tuner
+} // namespace tv
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/tv/tuner/aidl/default/Descrambler.cpp b/tv/tuner/aidl/default/Descrambler.cpp
new file mode 100644
index 0000000..8af3a92
--- /dev/null
+++ b/tv/tuner/aidl/default/Descrambler.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2021 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
+#define LOG_TAG "android.hardware.tv.tuner-service.example-Descrambler"
+
+#include <aidl/android/hardware/tv/tuner/IFrontendCallback.h>
+#include <utils/Log.h>
+
+#include "Descrambler.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+Descrambler::Descrambler() {}
+
+Descrambler::~Descrambler() {}
+
+::ndk::ScopedAStatus Descrambler::setDemuxSource(int32_t in_demuxId) {
+ ALOGV("%s", __FUNCTION__);
+ if (mDemuxSet) {
+ ALOGW("[ WARN ] Descrambler has already been set with a demux id %" PRIu32,
+ mSourceDemuxId);
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+ mDemuxSet = true;
+ mSourceDemuxId = in_demuxId;
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Descrambler::setKeyToken(const std::vector<uint8_t>& /* in_keyToken */) {
+ ALOGV("%s", __FUNCTION__);
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Descrambler::addPid(
+ const DemuxPid& /* in_pid */,
+ const std::shared_ptr<IFilter>& /* in_optionalSourceFilter */) {
+ ALOGV("%s", __FUNCTION__);
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Descrambler::removePid(
+ const DemuxPid& /* in_pid */,
+ const std::shared_ptr<IFilter>& /* in_optionalSourceFilter */) {
+ ALOGV("%s", __FUNCTION__);
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Descrambler::close() {
+ ALOGV("%s", __FUNCTION__);
+ mDemuxSet = false;
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+} // namespace tuner
+} // namespace tv
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/tv/tuner/aidl/default/Descrambler.h b/tv/tuner/aidl/default/Descrambler.h
new file mode 100644
index 0000000..ddf2c1d
--- /dev/null
+++ b/tv/tuner/aidl/default/Descrambler.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/tv/tuner/BnDescrambler.h>
+#include <aidl/android/hardware/tv/tuner/ITuner.h>
+#include <inttypes.h>
+
+using namespace std;
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+class Descrambler : public BnDescrambler {
+ public:
+ Descrambler();
+
+ ::ndk::ScopedAStatus setDemuxSource(int32_t in_demuxId) override;
+ ::ndk::ScopedAStatus setKeyToken(const std::vector<uint8_t>& in_keyToken) override;
+ ::ndk::ScopedAStatus addPid(const DemuxPid& in_pid,
+ const std::shared_ptr<IFilter>& in_optionalSourceFilter) override;
+ ::ndk::ScopedAStatus removePid(
+ const DemuxPid& in_pid,
+ const std::shared_ptr<IFilter>& in_optionalSourceFilter) override;
+ ::ndk::ScopedAStatus close() override;
+
+ private:
+ virtual ~Descrambler();
+ int32_t mSourceDemuxId;
+ bool mDemuxSet = false;
+};
+
+} // namespace tuner
+} // namespace tv
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/tv/tuner/aidl/default/Dvr.cpp b/tv/tuner/aidl/default/Dvr.cpp
new file mode 100644
index 0000000..a042dc3
--- /dev/null
+++ b/tv/tuner/aidl/default/Dvr.cpp
@@ -0,0 +1,489 @@
+/*
+ * Copyright 2021 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
+#define LOG_TAG "android.hardware.tv.tuner-service.example-Dvr"
+
+#include <aidl/android/hardware/tv/tuner/DemuxQueueNotifyBits.h>
+
+#include <utils/Log.h>
+#include "Dvr.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+#define WAIT_TIMEOUT 3000000000
+
+Dvr::Dvr(DvrType type, uint32_t bufferSize, const std::shared_ptr<IDvrCallback>& cb,
+ std::shared_ptr<Demux> demux) {
+ mType = type;
+ mBufferSize = bufferSize;
+ mCallback = cb;
+ mDemux = demux;
+}
+
+Dvr::~Dvr() {
+ mDvrThreadRunning = false;
+ lock_guard<mutex> lock(mDvrThreadLock);
+}
+
+::ndk::ScopedAStatus Dvr::getQueueDesc(MQDescriptor<int8_t, SynchronizedReadWrite>* out_queue) {
+ ALOGV("%s", __FUNCTION__);
+
+ *out_queue = mDvrMQ->dupeDesc();
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Dvr::configure(const DvrSettings& in_settings) {
+ ALOGV("%s", __FUNCTION__);
+
+ mDvrSettings = in_settings;
+ mDvrConfigured = true;
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Dvr::attachFilter(const std::shared_ptr<IFilter>& in_filter) {
+ ALOGV("%s", __FUNCTION__);
+
+ int64_t filterId;
+ ::ndk::ScopedAStatus status = in_filter->getId64Bit(&filterId);
+ if (!status.isOk()) {
+ return status;
+ }
+
+ if (!mDemux->attachRecordFilter(filterId)) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Dvr::detachFilter(const std::shared_ptr<IFilter>& in_filter) {
+ ALOGV("%s", __FUNCTION__);
+
+ int64_t filterId;
+ ::ndk::ScopedAStatus status = in_filter->getId64Bit(&filterId);
+ if (!status.isOk()) {
+ return status;
+ }
+
+ if (!mDemux->detachRecordFilter(filterId)) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Dvr::start() {
+ ALOGV("%s", __FUNCTION__);
+ if (mDvrThreadRunning) {
+ return ::ndk::ScopedAStatus::ok();
+ }
+
+ if (!mCallback) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_NO_INIT);
+ }
+
+ if (!mDvrConfigured) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+
+ if (mType == DvrType::PLAYBACK) {
+ mDvrThreadRunning = true;
+ 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 ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Dvr::stop() {
+ ALOGV("%s", __FUNCTION__);
+
+ mDvrThreadRunning = false;
+ lock_guard<mutex> lock(mDvrThreadLock);
+
+ mIsRecordStarted = false;
+ mDemux->setIsRecording(false);
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Dvr::flush() {
+ ALOGV("%s", __FUNCTION__);
+
+ mRecordStatus = RecordStatus::DATA_READY;
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Dvr::close() {
+ ALOGV("%s", __FUNCTION__);
+
+ mDvrThreadRunning = false;
+ lock_guard<mutex> lock(mDvrThreadLock);
+ return ::ndk::ScopedAStatus::ok();
+}
+
+bool Dvr::createDvrMQ() {
+ ALOGV("%s", __FUNCTION__);
+
+ // Create a synchronized FMQ that supports blocking read/write
+ unique_ptr<DvrMQ> tmpDvrMQ = unique_ptr<DvrMQ>(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) != ::android::OK) {
+ return false;
+ }
+
+ return true;
+}
+
+EventFlag* Dvr::getDvrEventFlag() {
+ return mDvrEventFlag;
+}
+
+void* Dvr::__threadLoopPlayback(void* user) {
+ Dvr* const self = static_cast<Dvr*>(user);
+ self->playbackThreadLoop();
+ return 0;
+}
+
+void Dvr::playbackThreadLoop() {
+ ALOGD("[Dvr] playback threadLoop start.");
+ lock_guard<mutex> lock(mDvrThreadLock);
+
+ while (mDvrThreadRunning) {
+ uint32_t efState = 0;
+ ::android::status_t status =
+ mDvrEventFlag->wait(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY),
+ &efState, WAIT_TIMEOUT, true /* retry on spurious wake */);
+ if (status != ::android::OK) {
+ ALOGD("[Dvr] wait for data ready on the playback FMQ");
+ continue;
+ }
+
+ // If the both dvr playback and dvr record are created, the playback will be treated as
+ // the source of the record. isVirtualFrontend set to true would direct the dvr playback
+ // input to the demux record filters or live broadcast filters.
+ bool isRecording = mDemux->isRecording();
+ bool isVirtualFrontend = isRecording;
+
+ if (mDvrSettings.get<DvrSettings::Tag::playback>().dataFormat == DataFormat::ES) {
+ if (!processEsDataOnPlayback(isVirtualFrontend, isRecording)) {
+ ALOGE("[Dvr] playback es data failed to be filtered. Ending thread");
+ break;
+ }
+ maySendPlaybackStatusCallback();
+ continue;
+ }
+
+ // Our current implementation filter the data and write it into the filter FMQ immediately
+ // after the DATA_READY from the VTS/framework
+ // This is for the non-ES data source, real playback use case handling.
+ if (!readPlaybackFMQ(isVirtualFrontend, isRecording) ||
+ !startFilterDispatcher(isVirtualFrontend, 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<mutex> lock(mPlaybackStatusLock);
+ int availableToRead = mDvrMQ->availableToRead();
+ int availableToWrite = mDvrMQ->availableToWrite();
+
+ PlaybackStatus newStatus =
+ checkPlaybackStatusChange(availableToWrite, availableToRead,
+ mDvrSettings.get<DvrSettings::Tag::playback>().highThreshold,
+ mDvrSettings.get<DvrSettings::Tag::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.get<DvrSettings::Tag::playback>().packetSize;
+ vector<int8_t> 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<int8_t> 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<MediaEsMetaData> 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<int8_t> frameData;
+ map<int64_t, std::shared_ptr<IFilter>>::iterator it;
+ int pid = 0;
+ for (int i = 0; i < totalFrames; i++) {
+ frameData.resize(esMeta[i].len);
+ pid = esMeta[i].isAudio ? audioPid : videoPid;
+ memcpy(frameData.data(), dataOutputBuffer.data() + esMeta[i].startIndex, esMeta[i].len);
+ // Send to the media filters or record filters
+ if (!isRecording) {
+ for (it = mFilters.begin(); it != mFilters.end(); it++) {
+ if (pid == mDemux->getFilterTpid(it->first)) {
+ mDemux->updateMediaFilterOutput(it->first, frameData,
+ static_cast<uint64_t>(esMeta[i].pts));
+ }
+ }
+ } else {
+ mDemux->sendFrontendInputToRecord(frameData, pid, static_cast<uint64_t>(esMeta[i].pts));
+ }
+ startFilterDispatcher(isVirtualFrontend, isRecording);
+ frameData.clear();
+ }
+
+ return true;
+}
+
+void Dvr::getMetaDataValue(int& index, int8_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<int8_t> data) {
+ map<int64_t, std::shared_ptr<IFilter>>::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<int64_t, std::shared_ptr<IFilter>>::iterator it;
+ // Handle the output data per filter type
+ for (it = mFilters.begin(); it != mFilters.end(); it++) {
+ if (mDemux->startFilterHandler(it->first).isOk()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool Dvr::writeRecordFMQ(const vector<int8_t>& data) {
+ lock_guard<mutex> 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<uint32_t>(DemuxQueueNotifyBits::DATA_READY));
+ maySendRecordStatusCallback();
+ return true;
+ }
+
+ maySendRecordStatusCallback();
+ return false;
+}
+
+void Dvr::maySendRecordStatusCallback() {
+ lock_guard<mutex> lock(mRecordStatusLock);
+ int availableToRead = mDvrMQ->availableToRead();
+ int availableToWrite = mDvrMQ->availableToWrite();
+
+ RecordStatus newStatus =
+ checkRecordStatusChange(availableToWrite, availableToRead,
+ mDvrSettings.get<DvrSettings::Tag::record>().highThreshold,
+ mDvrSettings.get<DvrSettings::Tag::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 RecordStatus::OVERFLOW;
+ } else if (availableToRead > highThreshold) {
+ return RecordStatus::HIGH_WATER;
+ } else if (availableToRead < lowThreshold) {
+ return RecordStatus::LOW_WATER;
+ }
+ return mRecordStatus;
+}
+
+bool Dvr::addPlaybackFilter(int64_t filterId, std::shared_ptr<IFilter> filter) {
+ mFilters[filterId] = filter;
+ return true;
+}
+
+bool Dvr::removePlaybackFilter(int64_t filterId) {
+ mFilters.erase(filterId);
+ return true;
+}
+
+} // namespace tuner
+} // namespace tv
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/tv/tuner/aidl/default/Dvr.h b/tv/tuner/aidl/default/Dvr.h
new file mode 100644
index 0000000..fb22a2e
--- /dev/null
+++ b/tv/tuner/aidl/default/Dvr.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/tv/tuner/BnDvr.h>
+#include <aidl/android/hardware/tv/tuner/RecordStatus.h>
+
+#include <fmq/AidlMessageQueue.h>
+#include <math.h>
+#include <set>
+#include "Demux.h"
+#include "Frontend.h"
+#include "Tuner.h"
+
+using namespace std;
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+using ::aidl::android::hardware::common::fmq::MQDescriptor;
+using ::aidl::android::hardware::common::fmq::SynchronizedReadWrite;
+using ::android::AidlMessageQueue;
+using ::android::hardware::EventFlag;
+
+using DvrMQ = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
+
+struct MediaEsMetaData {
+ bool isAudio;
+ int startIndex;
+ int len;
+ int pts;
+};
+
+class Demux;
+class Filter;
+class Frontend;
+class Tuner;
+
+class Dvr : public BnDvr {
+ public:
+ Dvr(DvrType type, uint32_t bufferSize, const std::shared_ptr<IDvrCallback>& cb,
+ std::shared_ptr<Demux> demux);
+ ~Dvr();
+
+ ::ndk::ScopedAStatus getQueueDesc(
+ MQDescriptor<int8_t, SynchronizedReadWrite>* out_queue) override;
+ ::ndk::ScopedAStatus configure(const DvrSettings& in_settings) override;
+ ::ndk::ScopedAStatus attachFilter(const std::shared_ptr<IFilter>& in_filter) override;
+ ::ndk::ScopedAStatus detachFilter(const std::shared_ptr<IFilter>& in_filter) override;
+ ::ndk::ScopedAStatus start() override;
+ ::ndk::ScopedAStatus stop() override;
+ ::ndk::ScopedAStatus flush() override;
+ ::ndk::ScopedAStatus close() override;
+
+ /**
+ * To create a DvrMQ and its Event Flag.
+ *
+ * Return false is any of the above processes fails.
+ */
+ bool createDvrMQ();
+ bool writeRecordFMQ(const std::vector<int8_t>& data);
+ bool addPlaybackFilter(int64_t filterId, std::shared_ptr<IFilter> filter);
+ bool removePlaybackFilter(int64_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
+ std::shared_ptr<Demux> mDemux;
+
+ DvrType mType;
+ uint32_t mBufferSize;
+ std::shared_ptr<IDvrCallback> mCallback;
+ std::map<int64_t, std::shared_ptr<IFilter>> mFilters;
+
+ void deleteEventFlag();
+ bool readDataFromMQ();
+ void getMetaDataValue(int& index, int8_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<int8_t> data);
+ static void* __threadLoopPlayback(void* user);
+ static void* __threadLoopRecord(void* user);
+ void playbackThreadLoop();
+ void recordThreadLoop();
+
+ unique_ptr<DvrMQ> 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 tuner
+} // namespace tv
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/tv/tuner/aidl/default/Filter.cpp b/tv/tuner/aidl/default/Filter.cpp
new file mode 100644
index 0000000..18c1f00
--- /dev/null
+++ b/tv/tuner/aidl/default/Filter.cpp
@@ -0,0 +1,1103 @@
+/*
+ * Copyright 2021 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
+#define LOG_TAG "android.hardware.tv.tuner-service.example-Filter"
+
+#include <BufferAllocator/BufferAllocator.h>
+#include <aidl/android/hardware/tv/tuner/DemuxFilterMonitorEventType.h>
+#include <aidl/android/hardware/tv/tuner/DemuxQueueNotifyBits.h>
+#include <aidlcommonsupport/NativeHandle.h>
+#include <utils/Log.h>
+
+#include "Filter.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+#define WAIT_TIMEOUT 3000000000
+
+Filter::Filter() {}
+
+Filter::Filter(DemuxFilterType type, int64_t filterId, uint32_t bufferSize,
+ const std::shared_ptr<IFilterCallback>& cb, std::shared_ptr<Demux> demux) {
+ mType = type;
+ mFilterId = filterId;
+ mBufferSize = bufferSize;
+ mDemux = demux;
+ mCallback = cb;
+
+ switch (mType.mainType) {
+ case DemuxFilterMainType::TS:
+ if (mType.subType.get<DemuxFilterTypeDemuxFilterSubType::Tag::tsFilterType>() ==
+ DemuxTsFilterType::AUDIO ||
+ mType.subType.get<DemuxFilterTypeDemuxFilterSubType::Tag::tsFilterType>() ==
+ DemuxTsFilterType::VIDEO) {
+ mIsMediaFilter = true;
+ }
+ if (mType.subType.get<DemuxFilterTypeDemuxFilterSubType::Tag::tsFilterType>() ==
+ DemuxTsFilterType::PCR) {
+ mIsPcrFilter = true;
+ }
+ if (mType.subType.get<DemuxFilterTypeDemuxFilterSubType::Tag::tsFilterType>() ==
+ DemuxTsFilterType::RECORD) {
+ mIsRecordFilter = true;
+ }
+ break;
+ case DemuxFilterMainType::MMTP:
+ if (mType.subType.get<DemuxFilterTypeDemuxFilterSubType::Tag::mmtpFilterType>() ==
+ DemuxMmtpFilterType::AUDIO ||
+ mType.subType.get<DemuxFilterTypeDemuxFilterSubType::Tag::mmtpFilterType>() ==
+ DemuxMmtpFilterType::VIDEO) {
+ mIsMediaFilter = true;
+ }
+ if (mType.subType.get<DemuxFilterTypeDemuxFilterSubType::Tag::mmtpFilterType>() ==
+ DemuxMmtpFilterType::RECORD) {
+ mIsRecordFilter = true;
+ }
+ break;
+ case DemuxFilterMainType::IP:
+ break;
+ case DemuxFilterMainType::TLV:
+ break;
+ case DemuxFilterMainType::ALP:
+ break;
+ default:
+ break;
+ }
+}
+
+Filter::~Filter() {
+ mFilterThreadRunning = false;
+ std::lock_guard<std::mutex> lock(mFilterThreadLock);
+}
+
+::ndk::ScopedAStatus Filter::getId64Bit(int64_t* _aidl_return) {
+ ALOGV("%s", __FUNCTION__);
+
+ *_aidl_return = mFilterId;
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::getId(int32_t* _aidl_return) {
+ ALOGV("%s", __FUNCTION__);
+
+ *_aidl_return = static_cast<int32_t>(mFilterId);
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::setDataSource(const std::shared_ptr<IFilter>& in_filter) {
+ ALOGV("%s", __FUNCTION__);
+
+ mDataSource = in_filter;
+ mIsDataSourceDemux = false;
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::getQueueDesc(MQDescriptor<int8_t, SynchronizedReadWrite>* out_queue) {
+ ALOGV("%s", __FUNCTION__);
+
+ mIsUsingFMQ = mIsRecordFilter ? false : true;
+
+ *out_queue = mFilterMQ->dupeDesc();
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::configure(const DemuxFilterSettings& in_settings) {
+ ALOGV("%s", __FUNCTION__);
+
+ mFilterSettings = in_settings;
+ switch (mType.mainType) {
+ case DemuxFilterMainType::TS:
+ mTpid = in_settings.get<DemuxFilterSettings::Tag::ts>().tpid;
+ break;
+ case DemuxFilterMainType::MMTP:
+ break;
+ case DemuxFilterMainType::IP:
+ break;
+ case DemuxFilterMainType::TLV:
+ break;
+ case DemuxFilterMainType::ALP:
+ break;
+ default:
+ break;
+ }
+
+ mConfigured = true;
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::start() {
+ ALOGV("%s", __FUNCTION__);
+ mFilterThreadRunning = true;
+ vector<DemuxFilterEvent> events;
+ // All the filter event callbacks in start are for testing purpose.
+ switch (mType.mainType) {
+ case DemuxFilterMainType::TS:
+ createMediaEvent(events);
+ mCallback->onFilterEvent(events);
+ createTsRecordEvent(events);
+ mCallback->onFilterEvent(events);
+ createTemiEvent(events);
+ mCallback->onFilterEvent(events);
+ break;
+ case DemuxFilterMainType::MMTP:
+ createDownloadEvent(events);
+ mCallback->onFilterEvent(events);
+ createMmtpRecordEvent(events);
+ mCallback->onFilterEvent(events);
+ break;
+ case DemuxFilterMainType::IP:
+ createSectionEvent(events);
+ mCallback->onFilterEvent(events);
+ createIpPayloadEvent(events);
+ mCallback->onFilterEvent(events);
+ break;
+ case DemuxFilterMainType::TLV:
+ createMonitorEvent(events);
+ mCallback->onFilterEvent(events);
+ break;
+ case DemuxFilterMainType::ALP:
+ createMonitorEvent(events);
+ mCallback->onFilterEvent(events);
+ break;
+ default:
+ break;
+ }
+ return startFilterLoop();
+}
+
+::ndk::ScopedAStatus Filter::stop() {
+ ALOGV("%s", __FUNCTION__);
+ mFilterThreadRunning = false;
+ std::lock_guard<std::mutex> lock(mFilterThreadLock);
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::flush() {
+ ALOGV("%s", __FUNCTION__);
+
+ // temp implementation to flush the FMQ
+ int size = mFilterMQ->availableToRead();
+ int8_t* buffer = new int8_t[size];
+ mFilterMQ->read(buffer, size);
+ delete[] buffer;
+ mFilterStatus = DemuxFilterStatus::DATA_READY;
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::releaseAvHandle(const NativeHandle& in_avMemory, int64_t in_avDataId) {
+ ALOGV("%s", __FUNCTION__);
+
+ if ((mSharedAvMemHandle != nullptr) && (in_avMemory.fds.size() > 0) &&
+ (sameFile(in_avMemory.fds[0].get(), mSharedAvMemHandle->data[0]))) {
+ freeSharedAvHandle();
+ return ::ndk::ScopedAStatus::ok();
+ }
+
+ if (mDataId2Avfd.find(in_avDataId) == mDataId2Avfd.end()) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+
+ ::close(mDataId2Avfd[in_avDataId]);
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::close() {
+ ALOGV("%s", __FUNCTION__);
+
+ mFilterThreadRunning = false;
+ std::lock_guard<std::mutex> lock(mFilterThreadLock);
+ return mDemux->removeFilter(mFilterId);
+}
+
+::ndk::ScopedAStatus Filter::configureIpCid(int32_t in_ipCid) {
+ ALOGV("%s", __FUNCTION__);
+
+ if (mType.mainType != DemuxFilterMainType::IP) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+
+ mCid = in_ipCid;
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::getAvSharedHandle(NativeHandle* out_avMemory, int64_t* _aidl_return) {
+ ALOGV("%s", __FUNCTION__);
+
+ if (!mIsMediaFilter) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+
+ if (mSharedAvMemHandle != nullptr) {
+ *out_avMemory = ::android::dupToAidl(mSharedAvMemHandle);
+ *_aidl_return = BUFFER_SIZE_16M;
+ mUsingSharedAvMem = true;
+ return ::ndk::ScopedAStatus::ok();
+ }
+
+ int av_fd = createAvIonFd(BUFFER_SIZE_16M);
+ if (av_fd < 0) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_NO_MEMORY);
+ }
+
+ mSharedAvMemHandle = createNativeHandle(av_fd);
+ if (mSharedAvMemHandle == nullptr) {
+ ::close(av_fd);
+ *_aidl_return = 0;
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+ }
+ ::close(av_fd);
+ mUsingSharedAvMem = true;
+
+ *out_avMemory = ::android::dupToAidl(mSharedAvMemHandle);
+ *_aidl_return = BUFFER_SIZE_16M;
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::configureAvStreamType(const AvStreamType& in_avStreamType) {
+ ALOGV("%s", __FUNCTION__);
+
+ if (!mIsMediaFilter) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+
+ switch (in_avStreamType.getTag()) {
+ case AvStreamType::Tag::audio:
+ mAudioStreamType =
+ static_cast<uint32_t>(in_avStreamType.get<AvStreamType::Tag::audio>());
+ break;
+ case AvStreamType::Tag::video:
+ mVideoStreamType =
+ static_cast<uint32_t>(in_avStreamType.get<AvStreamType::Tag::video>());
+ break;
+ default:
+ break;
+ }
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::configureMonitorEvent(int in_monitorEventTypes) {
+ ALOGV("%s", __FUNCTION__);
+
+ int32_t newScramblingStatus =
+ in_monitorEventTypes &
+ static_cast<int32_t>(DemuxFilterMonitorEventType::SCRAMBLING_STATUS);
+ int32_t newIpCid =
+ in_monitorEventTypes & static_cast<int32_t>(DemuxFilterMonitorEventType::IP_CID_CHANGE);
+
+ // if scrambling status monitoring flipped, record the new state and send msg on enabling
+ if (newScramblingStatus ^ mScramblingStatusMonitored) {
+ mScramblingStatusMonitored = newScramblingStatus;
+ if (mScramblingStatusMonitored) {
+ if (mCallback != nullptr) {
+ // Assuming current status is always NOT_SCRAMBLED
+ vector<DemuxFilterEvent> events;
+ DemuxFilterMonitorEvent monitorEvent;
+ events.resize(1);
+ monitorEvent.set<DemuxFilterMonitorEvent::Tag::scramblingStatus>(
+ ScramblingStatus::NOT_SCRAMBLED);
+ events[0].set<DemuxFilterEvent::monitorEvent>(monitorEvent);
+ mCallback->onFilterEvent(events);
+ } else {
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+ }
+ }
+
+ // if ip cid monitoring flipped, record the new state and send msg on enabling
+ if (newIpCid ^ mIpCidMonitored) {
+ mIpCidMonitored = newIpCid;
+ if (mIpCidMonitored) {
+ if (mCallback != nullptr) {
+ // Return random cid
+ vector<DemuxFilterEvent> events;
+ DemuxFilterMonitorEvent monitorEvent;
+ events.resize(1);
+ monitorEvent.set<DemuxFilterMonitorEvent::Tag::cid>(1);
+ events[0].set<DemuxFilterEvent::monitorEvent>(monitorEvent);
+ mCallback->onFilterEvent(events);
+ } else {
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+ }
+ }
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+bool Filter::createFilterMQ() {
+ ALOGV("%s", __FUNCTION__);
+
+ // Create a synchronized FMQ that supports blocking read/write
+ std::unique_ptr<FilterMQ> tmpFilterMQ =
+ std::unique_ptr<FilterMQ>(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(), &mFilterEventsFlag) !=
+ ::android::OK) {
+ return false;
+ }
+
+ return true;
+}
+
+::ndk::ScopedAStatus Filter::startFilterLoop() {
+ pthread_create(&mFilterThread, NULL, __threadLoopFilter, this);
+ pthread_setname_np(mFilterThread, "filter_waiting_loop");
+ return ::ndk::ScopedAStatus::ok();
+}
+
+void* Filter::__threadLoopFilter(void* user) {
+ Filter* const self = static_cast<Filter*>(user);
+ self->filterThreadLoop();
+ return 0;
+}
+
+void Filter::filterThreadLoop() {
+ if (!mFilterThreadRunning) {
+ return;
+ }
+ std::lock_guard<std::mutex> lock(mFilterThreadLock);
+ ALOGD("[Filter] filter %" PRIu64 " threadLoop start.", mFilterId);
+
+ // 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 (mFilterEvents.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
+ if (mCallback != nullptr) {
+ if (mConfigured) {
+ vector<DemuxFilterEvent> startEvent;
+ startEvent.resize(1);
+ startEvent[0].set<DemuxFilterEvent::Tag::startId>(mStartId++);
+ mCallback->onFilterEvent(startEvent);
+ mConfigured = false;
+ }
+ mCallback->onFilterEvent(mFilterEvents);
+ } else {
+ ALOGD("[Filter] filter callback is not configured yet.");
+ mFilterThreadRunning = false;
+ return;
+ }
+
+ mFilterEvents.resize(0);
+ mFilterStatus = DemuxFilterStatus::DATA_READY;
+ if (mCallback != nullptr) {
+ 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++) {
+ if (!mFilterThreadRunning) {
+ break;
+ }
+ while (mFilterThreadRunning && mIsUsingFMQ) {
+ ::android::status_t status = mFilterEventsFlag->wait(
+ static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_CONSUMED), &efState,
+ WAIT_TIMEOUT, true /* retry on spurious wake */);
+ if (status != ::android::OK) {
+ ALOGD("[Filter] wait for data consumed");
+ continue;
+ }
+ break;
+ }
+
+ maySendFilterStatusCallback();
+
+ while (mFilterThreadRunning) {
+ std::lock_guard<std::mutex> lock(mFilterEventsLock);
+ if (mFilterEvents.size() == 0) {
+ continue;
+ }
+ // After successfully write, send a callback and wait for the read to be done
+ if (mCallback != nullptr) {
+ mCallback->onFilterEvent(mFilterEvents);
+ }
+ mFilterEvents.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;
+ }
+ }
+ break;
+ }
+ ALOGD("[Filter] filter thread ended.");
+}
+
+void Filter::freeSharedAvHandle() {
+ if (!mIsMediaFilter) {
+ return;
+ }
+ native_handle_close(mSharedAvMemHandle);
+ native_handle_delete(mSharedAvMemHandle);
+ mSharedAvMemHandle = nullptr;
+}
+
+void Filter::maySendFilterStatusCallback() {
+ if (!mIsUsingFMQ) {
+ return;
+ }
+ std::lock_guard<std::mutex> 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) {
+ if (mCallback != nullptr) {
+ 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<int8_t> data) {
+ std::lock_guard<std::mutex> lock(mFilterOutputLock);
+ mFilterOutput.insert(mFilterOutput.end(), data.begin(), data.end());
+}
+
+void Filter::updatePts(uint64_t pts) {
+ std::lock_guard<std::mutex> lock(mFilterOutputLock);
+ mPts = pts;
+}
+
+void Filter::updateRecordOutput(vector<int8_t> data) {
+ std::lock_guard<std::mutex> lock(mRecordFilterOutputLock);
+ mRecordFilterOutput.insert(mRecordFilterOutput.end(), data.begin(), data.end());
+}
+
+::ndk::ScopedAStatus Filter::startFilterHandler() {
+ std::lock_guard<std::mutex> lock(mFilterOutputLock);
+ switch (mType.mainType) {
+ case DemuxFilterMainType::TS:
+ switch (mType.subType.get<DemuxFilterTypeDemuxFilterSubType::Tag::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 ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::startSectionFilterHandler() {
+ if (mFilterOutput.empty()) {
+ return ::ndk::ScopedAStatus::ok();
+ }
+ if (!writeSectionsAndCreateEvent(mFilterOutput)) {
+ ALOGD("[Filter] filter %" PRIu64 " fails to write into FMQ. Ending thread", mFilterId);
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+ }
+
+ mFilterOutput.clear();
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::startPesFilterHandler() {
+ std::lock_guard<std::mutex> lock(mFilterEventsLock);
+ if (mFilterOutput.empty()) {
+ return ::ndk::ScopedAStatus::ok();
+ }
+
+ 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<int8_t>::const_iterator first = mFilterOutput.begin() + i + 4;
+ vector<int8_t>::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 ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+ maySendFilterStatusCallback();
+ DemuxFilterPesEvent pesEvent;
+ pesEvent = {
+ // temp dump meta data
+ .streamId = static_cast<char16_t>(mPesOutput[3]),
+ .dataLength = static_cast<char16_t>(mPesOutput.size()),
+ };
+ if (DEBUG_FILTER) {
+ ALOGD("[Filter] assembled pes data length %d", pesEvent.dataLength);
+ }
+
+ int size = mFilterEvents.size();
+ mFilterEvents.resize(size + 1);
+ mFilterEvents[size].set<DemuxFilterEvent::Tag::pes>(pesEvent);
+ mPesOutput.clear();
+ }
+
+ mFilterOutput.clear();
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::startTsFilterHandler() {
+ // TODO handle starting TS filter
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::startMediaFilterHandler() {
+ std::lock_guard<std::mutex> lock(mFilterEventsLock);
+ if (mFilterOutput.empty()) {
+ return ::ndk::ScopedAStatus::ok();
+ }
+
+ ::ndk::ScopedAStatus result;
+ if (mPts) {
+ result = createMediaFilterEventWithIon(mFilterOutput);
+ if (result.isOk()) {
+ mFilterOutput.clear();
+ }
+ return result;
+ }
+
+ 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<int8_t>::const_iterator first = mFilterOutput.begin() + i + 4;
+ vector<int8_t>::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;
+ }
+
+ result = createMediaFilterEventWithIon(mPesOutput);
+ if (result.isOk()) {
+ return result;
+ }
+ }
+
+ mFilterOutput.clear();
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::createMediaFilterEventWithIon(vector<int8_t>& output) {
+ if (mUsingSharedAvMem) {
+ if (mSharedAvMemHandle == nullptr) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+ }
+ return createShareMemMediaEvents(output);
+ }
+
+ return createIndependentMediaEvents(output);
+}
+
+::ndk::ScopedAStatus Filter::startRecordFilterHandler() {
+ std::lock_guard<std::mutex> lock(mRecordFilterOutputLock);
+ if (mRecordFilterOutput.empty()) {
+ return ::ndk::ScopedAStatus::ok();
+ }
+
+ if (mDvr == nullptr || !mDvr->writeRecordFMQ(mRecordFilterOutput)) {
+ ALOGD("[Filter] dvr fails to write into record FMQ.");
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+ }
+
+ DemuxFilterTsRecordEvent recordEvent;
+ recordEvent = {
+ .byteNumber = static_cast<int64_t>(mRecordFilterOutput.size()),
+ .pts = (mPts == 0) ? static_cast<int64_t>(time(NULL)) * 900000 : mPts,
+ .firstMbInSlice = 0, // random address
+ };
+
+ int size;
+ size = mFilterEvents.size();
+ mFilterEvents.resize(size + 1);
+ mFilterEvents[size].set<DemuxFilterEvent::Tag::tsRecord>(recordEvent);
+
+ mRecordFilterOutput.clear();
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::startPcrFilterHandler() {
+ // TODO handle starting PCR filter
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::startTemiFilterHandler() {
+ // TODO handle starting TEMI filter
+ return ::ndk::ScopedAStatus::ok();
+}
+
+bool Filter::writeSectionsAndCreateEvent(vector<int8_t>& data) {
+ // TODO check how many sections has been read
+ ALOGD("[Filter] section handler");
+ std::lock_guard<std::mutex> lock(mFilterEventsLock);
+ if (!writeDataToFilterMQ(data)) {
+ return false;
+ }
+ int size = mFilterEvents.size();
+ mFilterEvents.resize(size + 1);
+ DemuxFilterSectionEvent secEvent;
+ secEvent = {
+ // temp dump meta data
+ .tableId = 0,
+ .version = 1,
+ .sectionNum = 1,
+ .dataLength = static_cast<char16_t>(data.size()),
+ };
+ mFilterEvents[size].set<DemuxFilterEvent::Tag::section>(secEvent);
+ return true;
+}
+
+bool Filter::writeDataToFilterMQ(const std::vector<int8_t>& data) {
+ std::lock_guard<std::mutex> lock(mWriteLock);
+ if (mFilterMQ->write(data.data(), data.size())) {
+ return true;
+ }
+ return false;
+}
+
+void Filter::attachFilterToRecord(const std::shared_ptr<Dvr> dvr) {
+ mDvr = dvr;
+}
+
+void Filter::detachFilterFromRecord() {
+ mDvr = nullptr;
+}
+
+int Filter::createAvIonFd(int size) {
+ // Create an DMA-BUF fd and allocate an av fd mapped to a buffer to it.
+ auto buffer_allocator = std::make_unique<BufferAllocator>();
+ if (!buffer_allocator) {
+ ALOGE("[Filter] Unable to create BufferAllocator object");
+ return -1;
+ }
+ int av_fd = -1;
+ av_fd = buffer_allocator->Alloc("system-uncached", size);
+ if (av_fd < 0) {
+ 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<uint8_t*>(
+ 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) {
+ native_handle_t* nativeHandle;
+ if (fd < 0) {
+ nativeHandle = native_handle_create(/*numFd*/ 0, 0);
+ } else {
+ // Create a native handle to pass the av fd via the callback event.
+ nativeHandle = native_handle_create(/*numFd*/ 1, 0);
+ }
+ if (nativeHandle == NULL) {
+ ALOGE("[Filter] Failed to create native_handle %d", errno);
+ return NULL;
+ }
+ if (nativeHandle->numFds > 0) {
+ nativeHandle->data[0] = dup(fd);
+ }
+ return nativeHandle;
+}
+
+::ndk::ScopedAStatus Filter::createIndependentMediaEvents(vector<int8_t>& output) {
+ int av_fd = createAvIonFd(output.size());
+ if (av_fd == -1) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+ }
+ // copy the filtered data to the buffer
+ uint8_t* avBuffer = getIonBuffer(av_fd, output.size());
+ if (avBuffer == NULL) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+ }
+ memcpy(avBuffer, output.data(), output.size() * sizeof(uint8_t));
+
+ native_handle_t* nativeHandle = createNativeHandle(av_fd);
+ if (nativeHandle == NULL) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+ }
+
+ // Create a dataId and add a <dataId, av_fd> pair into the dataId2Avfd map
+ uint64_t dataId = mLastUsedDataId++ /*createdUID*/;
+ mDataId2Avfd[dataId] = dup(av_fd);
+
+ // Create mediaEvent and send callback
+ int size = mFilterEvents.size();
+ mFilterEvents.resize(size + 1);
+
+ mFilterEvents[size] = DemuxFilterEvent::make<DemuxFilterEvent::Tag::media>();
+ mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().avMemory =
+ ::android::dupToAidl(nativeHandle);
+ mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().dataLength =
+ static_cast<int32_t>(output.size());
+ mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().avDataId = static_cast<int64_t>(dataId);
+ if (mPts) {
+ mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().pts = mPts;
+ mPts = 0;
+ }
+
+ // Clear and log
+ native_handle_close(nativeHandle);
+ native_handle_delete(nativeHandle);
+ output.clear();
+ mAvBufferCopyCount = 0;
+ if (DEBUG_FILTER) {
+ ALOGD("[Filter] av data length %d", static_cast<int32_t>(output.size()));
+ }
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Filter::createShareMemMediaEvents(vector<int8_t>& output) {
+ // copy the filtered data to the shared buffer
+ uint8_t* sharedAvBuffer =
+ getIonBuffer(mSharedAvMemHandle->data[0], output.size() + mSharedAvMemOffset);
+ if (sharedAvBuffer == NULL) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+ }
+ memcpy(sharedAvBuffer + mSharedAvMemOffset, output.data(), output.size() * sizeof(uint8_t));
+
+ // Create a memory handle with numFds == 0
+ native_handle_t* nativeHandle = createNativeHandle(-1);
+ if (nativeHandle == NULL) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_UNKNOWN_ERROR);
+ }
+
+ // Create mediaEvent and send callback
+ int size = mFilterEvents.size();
+ mFilterEvents.resize(size + 1);
+ mFilterEvents[size] = DemuxFilterEvent::make<DemuxFilterEvent::Tag::media>();
+ mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().avMemory =
+ ::android::dupToAidl(nativeHandle);
+ mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().offset =
+ static_cast<int32_t>(mSharedAvMemOffset);
+ mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().dataLength =
+ static_cast<int32_t>(output.size());
+ if (mPts) {
+ mFilterEvents[size].get<DemuxFilterEvent::Tag::media>().pts = mPts;
+ mPts = 0;
+ }
+ mSharedAvMemOffset += output.size();
+
+ // Clear and log
+ native_handle_close(nativeHandle);
+ native_handle_delete(nativeHandle);
+ output.clear();
+ if (DEBUG_FILTER) {
+ ALOGD("[Filter] shared av data length %d", static_cast<int32_t>(output.size()));
+ }
+ return ::ndk::ScopedAStatus::ok();
+}
+
+bool Filter::sameFile(int fd1, int fd2) {
+ struct stat stat1, stat2;
+ if (fstat(fd1, &stat1) < 0 || fstat(fd2, &stat2) < 0) {
+ return false;
+ }
+ return (stat1.st_dev == stat2.st_dev) && (stat1.st_ino == stat2.st_ino);
+}
+
+void Filter::createMediaEvent(vector<DemuxFilterEvent>& events) {
+ AudioExtraMetaData audio;
+ events.resize(1);
+
+ audio.adFade = 1;
+ audio.adPan = 2;
+ audio.versionTextTag = 3;
+ audio.adGainCenter = 4;
+ audio.adGainFront = 5;
+ audio.adGainSurround = 6;
+
+ events[0] = DemuxFilterEvent::make<DemuxFilterEvent::Tag::media>();
+ events[0].get<DemuxFilterEvent::Tag::media>().streamId = 1;
+ events[0].get<DemuxFilterEvent::Tag::media>().isPtsPresent = true;
+ events[0].get<DemuxFilterEvent::Tag::media>().dataLength = 3;
+ events[0].get<DemuxFilterEvent::Tag::media>().offset = 4;
+ events[0].get<DemuxFilterEvent::Tag::media>().isSecureMemory = true;
+ events[0].get<DemuxFilterEvent::Tag::media>().mpuSequenceNumber = 6;
+ events[0].get<DemuxFilterEvent::Tag::media>().isPesPrivateData = true;
+ events[0]
+ .get<DemuxFilterEvent::Tag::media>()
+ .extraMetaData.set<DemuxFilterMediaEventExtraMetaData::Tag::audio>(audio);
+
+ int av_fd = createAvIonFd(BUFFER_SIZE_16M);
+ if (av_fd == -1) {
+ return;
+ }
+
+ native_handle_t* nativeHandle = createNativeHandle(av_fd);
+ if (nativeHandle == nullptr) {
+ ::close(av_fd);
+ ALOGE("[Filter] Failed to create native_handle %d", errno);
+ return;
+ }
+
+ // Create a dataId and add a <dataId, av_fd> pair into the dataId2Avfd map
+ uint64_t dataId = mLastUsedDataId++ /*createdUID*/;
+ mDataId2Avfd[dataId] = dup(av_fd);
+
+ events[0].get<DemuxFilterEvent::Tag::media>().avDataId = static_cast<int64_t>(dataId);
+ events[0].get<DemuxFilterEvent::Tag::media>().avMemory = ::android::dupToAidl(nativeHandle);
+
+ native_handle_close(nativeHandle);
+ native_handle_delete(nativeHandle);
+}
+
+void Filter::createTsRecordEvent(vector<DemuxFilterEvent>& events) {
+ events.resize(2);
+
+ DemuxPid pid;
+ DemuxFilterScIndexMask mask;
+ DemuxFilterTsRecordEvent tsRecord1;
+ pid.set<DemuxPid::Tag::tPid>(1);
+ mask.set<DemuxFilterScIndexMask::Tag::scIndex>(1);
+ tsRecord1.pid = pid;
+ tsRecord1.tsIndexMask = 1;
+ tsRecord1.scIndexMask = mask;
+ tsRecord1.byteNumber = 2;
+
+ DemuxFilterTsRecordEvent tsRecord2;
+ tsRecord2.pts = 1;
+ tsRecord2.firstMbInSlice = 2; // random address
+
+ events[0].set<DemuxFilterEvent::Tag::tsRecord>(tsRecord1);
+ events[1].set<DemuxFilterEvent::Tag::tsRecord>(tsRecord2);
+}
+
+void Filter::createMmtpRecordEvent(vector<DemuxFilterEvent>& events) {
+ events.resize(2);
+
+ DemuxFilterMmtpRecordEvent mmtpRecord1;
+ mmtpRecord1.scHevcIndexMask = 1;
+ mmtpRecord1.byteNumber = 2;
+
+ DemuxFilterMmtpRecordEvent mmtpRecord2;
+ mmtpRecord2.pts = 1;
+ mmtpRecord2.mpuSequenceNumber = 2;
+ mmtpRecord2.firstMbInSlice = 3;
+ mmtpRecord2.tsIndexMask = 4;
+
+ events[0].set<DemuxFilterEvent::Tag::mmtpRecord>(mmtpRecord1);
+ events[1].set<DemuxFilterEvent::Tag::mmtpRecord>(mmtpRecord2);
+}
+
+void Filter::createSectionEvent(vector<DemuxFilterEvent>& events) {
+ events.resize(1);
+
+ DemuxFilterSectionEvent section;
+ section.tableId = 1;
+ section.version = 2;
+ section.sectionNum = 3;
+ section.dataLength = 0;
+
+ events[0].set<DemuxFilterEvent::Tag::section>(section);
+}
+
+void Filter::createPesEvent(vector<DemuxFilterEvent>& events) {
+ events.resize(1);
+
+ DemuxFilterPesEvent pes;
+ pes.streamId = 1;
+ pes.dataLength = 1;
+ pes.mpuSequenceNumber = 2;
+
+ events[0].set<DemuxFilterEvent::Tag::pes>(pes);
+}
+
+void Filter::createDownloadEvent(vector<DemuxFilterEvent>& events) {
+ events.resize(1);
+
+ DemuxFilterDownloadEvent download;
+ download.itemId = 1;
+ download.mpuSequenceNumber = 2;
+ download.itemFragmentIndex = 3;
+ download.lastItemFragmentIndex = 4;
+ download.dataLength = 0;
+
+ events[0].set<DemuxFilterEvent::Tag::download>(download);
+}
+
+void Filter::createIpPayloadEvent(vector<DemuxFilterEvent>& events) {
+ events.resize(1);
+
+ DemuxFilterIpPayloadEvent ipPayload;
+ ipPayload.dataLength = 0;
+
+ events[0].set<DemuxFilterEvent::Tag::ipPayload>(ipPayload);
+}
+
+void Filter::createTemiEvent(vector<DemuxFilterEvent>& events) {
+ events.resize(1);
+
+ DemuxFilterTemiEvent temi;
+ temi.pts = 1;
+ temi.descrTag = 2;
+ temi.descrData = {3};
+
+ events[0].set<DemuxFilterEvent::Tag::temi>(temi);
+}
+
+void Filter::createMonitorEvent(vector<DemuxFilterEvent>& events) {
+ events.resize(1);
+
+ DemuxFilterMonitorEvent monitor;
+ monitor.set<DemuxFilterMonitorEvent::Tag::scramblingStatus>(ScramblingStatus::SCRAMBLED);
+ events[0].set<DemuxFilterEvent::Tag::monitorEvent>(monitor);
+}
+
+void Filter::createRestartEvent(vector<DemuxFilterEvent>& events) {
+ events.resize(1);
+
+ events[0].set<DemuxFilterEvent::Tag::startId>(1);
+}
+
+} // namespace tuner
+} // namespace tv
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/tv/tuner/aidl/default/Filter.h b/tv/tuner/aidl/default/Filter.h
new file mode 100644
index 0000000..7a037e6
--- /dev/null
+++ b/tv/tuner/aidl/default/Filter.h
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/tv/tuner/BnFilter.h>
+#include <aidl/android/hardware/tv/tuner/Constant.h>
+
+#include <fmq/AidlMessageQueue.h>
+#include <inttypes.h>
+#include <ion/ion.h>
+#include <math.h>
+#include <sys/stat.h>
+#include <set>
+#include "Demux.h"
+#include "Dvr.h"
+#include "Frontend.h"
+
+using namespace std;
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+using ::aidl::android::hardware::common::NativeHandle;
+using ::aidl::android::hardware::common::fmq::MQDescriptor;
+using ::aidl::android::hardware::common::fmq::SynchronizedReadWrite;
+using ::android::AidlMessageQueue;
+using ::android::hardware::EventFlag;
+
+using FilterMQ = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
+const uint32_t BUFFER_SIZE_16M = 0x1000000;
+
+class Demux;
+class Dvr;
+
+class Filter : public BnFilter {
+ public:
+ Filter();
+
+ Filter(DemuxFilterType type, int64_t filterId, uint32_t bufferSize,
+ const std::shared_ptr<IFilterCallback>& cb, std::shared_ptr<Demux> demux);
+
+ ~Filter();
+
+ ::ndk::ScopedAStatus getQueueDesc(
+ MQDescriptor<int8_t, SynchronizedReadWrite>* out_queue) override;
+ ::ndk::ScopedAStatus close() override;
+ ::ndk::ScopedAStatus configure(const DemuxFilterSettings& in_settings) override;
+ ::ndk::ScopedAStatus configureAvStreamType(const AvStreamType& in_avStreamType) override;
+ ::ndk::ScopedAStatus configureIpCid(int32_t in_ipCid) override;
+ ::ndk::ScopedAStatus configureMonitorEvent(int32_t in_monitorEventTypes) override;
+ ::ndk::ScopedAStatus start() override;
+ ::ndk::ScopedAStatus stop() override;
+ ::ndk::ScopedAStatus flush() override;
+ ::ndk::ScopedAStatus getAvSharedHandle(NativeHandle* out_avMemory,
+ int64_t* _aidl_return) override;
+ ::ndk::ScopedAStatus getId(int32_t* _aidl_return) override;
+ ::ndk::ScopedAStatus getId64Bit(int64_t* _aidl_return) override;
+ ::ndk::ScopedAStatus releaseAvHandle(const NativeHandle& in_avMemory,
+ int64_t in_avDataId) override;
+ ::ndk::ScopedAStatus setDataSource(const std::shared_ptr<IFilter>& in_filter) 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<int8_t> data);
+ void updateRecordOutput(vector<int8_t> data);
+ void updatePts(uint64_t pts);
+ ::ndk::ScopedAStatus startFilterHandler();
+ ::ndk::ScopedAStatus startRecordFilterHandler();
+ void attachFilterToRecord(const std::shared_ptr<Dvr> dvr);
+ void detachFilterFromRecord();
+ void freeSharedAvHandle();
+ bool isMediaFilter() { return mIsMediaFilter; };
+ bool isPcrFilter() { return mIsPcrFilter; };
+ bool isRecordFilter() { return mIsRecordFilter; };
+
+ private:
+ // Tuner service
+ std::shared_ptr<Demux> mDemux;
+ // Dvr reference once the filter is attached to any
+ std::shared_ptr<Dvr> mDvr = nullptr;
+ /**
+ * Filter callbacks used on filter events or FMQ status
+ */
+ std::shared_ptr<IFilterCallback> mCallback = nullptr;
+
+ int64_t mFilterId;
+ int32_t mCid = static_cast<int32_t>(Constant::INVALID_IP_FILTER_CONTEXT_ID);
+ uint32_t mBufferSize;
+ DemuxFilterType mType;
+ bool mIsMediaFilter = false;
+ bool mIsPcrFilter = false;
+ bool mIsRecordFilter = false;
+ DemuxFilterSettings mFilterSettings;
+
+ uint16_t mTpid;
+ std::shared_ptr<IFilter> mDataSource;
+ bool mIsDataSourceDemux = true;
+ vector<int8_t> mFilterOutput;
+ vector<int8_t> mRecordFilterOutput;
+ int64_t mPts = 0;
+ unique_ptr<FilterMQ> mFilterMQ;
+ bool mIsUsingFMQ = false;
+ EventFlag* mFilterEventsFlag;
+ vector<DemuxFilterEvent> mFilterEvents;
+
+ // 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.
+ */
+ ::ndk::ScopedAStatus startSectionFilterHandler();
+ ::ndk::ScopedAStatus startPesFilterHandler();
+ ::ndk::ScopedAStatus startTsFilterHandler();
+ ::ndk::ScopedAStatus startMediaFilterHandler();
+ ::ndk::ScopedAStatus startPcrFilterHandler();
+ ::ndk::ScopedAStatus startTemiFilterHandler();
+ ::ndk::ScopedAStatus startFilterLoop();
+
+ void deleteEventFlag();
+ bool writeDataToFilterMQ(const std::vector<int8_t>& data);
+ bool readDataFromMQ();
+ bool writeSectionsAndCreateEvent(vector<int8_t>& 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.
+ */
+ 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);
+ ::ndk::ScopedAStatus createMediaFilterEventWithIon(vector<int8_t>& output);
+ ::ndk::ScopedAStatus createIndependentMediaEvents(vector<int8_t>& output);
+ ::ndk::ScopedAStatus createShareMemMediaEvents(vector<int8_t>& output);
+ bool sameFile(int fd1, int fd2);
+
+ void createMediaEvent(vector<DemuxFilterEvent>&);
+ void createTsRecordEvent(vector<DemuxFilterEvent>&);
+ void createMmtpRecordEvent(vector<DemuxFilterEvent>&);
+ void createSectionEvent(vector<DemuxFilterEvent>&);
+ void createPesEvent(vector<DemuxFilterEvent>&);
+ void createDownloadEvent(vector<DemuxFilterEvent>&);
+ void createIpPayloadEvent(vector<DemuxFilterEvent>&);
+ void createTemiEvent(vector<DemuxFilterEvent>&);
+ void createMonitorEvent(vector<DemuxFilterEvent>&);
+ void createRestartEvent(vector<DemuxFilterEvent>&);
+
+ /**
+ * 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 mFilterEventsLock;
+ /**
+ * 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<int8_t> mPesOutput;
+
+ // A map from data id to ion handle
+ std::map<uint64_t, int> mDataId2Avfd;
+ uint64_t mLastUsedDataId = 1;
+ int mAvBufferCopyCount = 0;
+
+ // Shared A/V memory handle
+ native_handle_t* mSharedAvMemHandle = nullptr;
+ bool mUsingSharedAvMem = false;
+ uint32_t mSharedAvMemOffset = 0;
+
+ uint32_t mAudioStreamType;
+ uint32_t mVideoStreamType;
+
+ // Scrambling status to be monitored
+ uint32_t mStatuses = 0;
+
+ bool mConfigured = false;
+ int mStartId = 0;
+ uint8_t mScramblingStatusMonitored = 0;
+ uint8_t mIpCidMonitored = 0;
+};
+
+} // namespace tuner
+} // namespace tv
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/tv/tuner/aidl/default/Frontend.cpp b/tv/tuner/aidl/default/Frontend.cpp
new file mode 100644
index 0000000..2be13d3
--- /dev/null
+++ b/tv/tuner/aidl/default/Frontend.cpp
@@ -0,0 +1,721 @@
+/*
+ * Copyright 2021 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
+#define LOG_TAG "android.hardware.tv.tuner-service.example-Frontend"
+
+#include "Frontend.h"
+#include <utils/Log.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+Frontend::Frontend(FrontendType type, int32_t id, std::shared_ptr<Tuner> tuner) {
+ mType = type;
+ mId = id;
+ mTunerService = tuner;
+ // Init callback to nullptr
+ mCallback = nullptr;
+}
+
+Frontend::~Frontend() {}
+
+::ndk::ScopedAStatus Frontend::close() {
+ ALOGV("%s", __FUNCTION__);
+ // Reset callback
+ mCallback = nullptr;
+ mIsLocked = false;
+ mTunerService->removeFrontend(mId);
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Frontend::setCallback(const std::shared_ptr<IFrontendCallback>& in_callback) {
+ ALOGV("%s", __FUNCTION__);
+ if (in_callback == nullptr) {
+ ALOGW("[ WARN ] Set Frontend callback with nullptr");
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+
+ mCallback = in_callback;
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Frontend::tune(const FrontendSettings& /* in_settings */) {
+ ALOGV("%s", __FUNCTION__);
+ if (mCallback == nullptr) {
+ ALOGW("[ WARN ] Frontend callback is not set when tune");
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+
+ mTunerService->frontendStartTune(mId);
+ mCallback->onEvent(FrontendEventType::LOCKED);
+ mIsLocked = true;
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Frontend::stopTune() {
+ ALOGV("%s", __FUNCTION__);
+
+ mTunerService->frontendStopTune(mId);
+ mIsLocked = false;
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Frontend::scan(const FrontendSettings& in_settings, FrontendScanType in_type) {
+ ALOGV("%s", __FUNCTION__);
+ FrontendScanMessage msg;
+
+ if (mIsLocked) {
+ msg.set<FrontendScanMessage::Tag::isEnd>(true);
+ mCallback->onScanMessage(FrontendScanMessageType::END, msg);
+ return ::ndk::ScopedAStatus::ok();
+ }
+
+ int32_t frequency = 0;
+ switch (in_settings.getTag()) {
+ case FrontendSettings::Tag::analog:
+ frequency = in_settings.get<FrontendSettings::Tag::analog>().frequency;
+ break;
+ case FrontendSettings::Tag::atsc:
+ frequency = in_settings.get<FrontendSettings::Tag::atsc>().frequency;
+ break;
+ case FrontendSettings::Tag::atsc3:
+ frequency = in_settings.get<FrontendSettings::Tag::atsc3>().frequency;
+ break;
+ case FrontendSettings::Tag::dvbs:
+ frequency = in_settings.get<FrontendSettings::Tag::dvbs>().frequency;
+ break;
+ case FrontendSettings::Tag::dvbc:
+ frequency = in_settings.get<FrontendSettings::Tag::dvbc>().frequency;
+ break;
+ case FrontendSettings::Tag::dvbt:
+ frequency = in_settings.get<FrontendSettings::Tag::dvbt>().frequency;
+ break;
+ case FrontendSettings::Tag::isdbs:
+ frequency = in_settings.get<FrontendSettings::Tag::isdbs>().frequency;
+ break;
+ case FrontendSettings::Tag::isdbs3:
+ frequency = in_settings.get<FrontendSettings::Tag::isdbs3>().frequency;
+ break;
+ case FrontendSettings::Tag::isdbt:
+ frequency = in_settings.get<FrontendSettings::Tag::isdbt>().frequency;
+ break;
+ default:
+ break;
+ }
+
+ if (in_type == FrontendScanType::SCAN_BLIND) {
+ frequency += 100;
+ }
+
+ {
+ FrontendScanMessage msg;
+ vector<int32_t> frequencies = {frequency};
+ msg.set<FrontendScanMessage::Tag::frequencies>(frequencies);
+ mCallback->onScanMessage(FrontendScanMessageType::FREQUENCY, msg);
+ }
+
+ {
+ FrontendScanMessage msg;
+ msg.set<FrontendScanMessage::Tag::progressPercent>(20);
+ mCallback->onScanMessage(FrontendScanMessageType::PROGRESS_PERCENT, msg);
+ }
+
+ {
+ FrontendScanMessage msg;
+ vector<int32_t> symbolRates = {30};
+ msg.set<FrontendScanMessage::Tag::symbolRates>(symbolRates);
+ mCallback->onScanMessage(FrontendScanMessageType::SYMBOL_RATE, msg);
+ }
+
+ if (mType == FrontendType::DVBT) {
+ FrontendScanMessage msg;
+ msg.set<FrontendScanMessage::Tag::hierarchy>(FrontendDvbtHierarchy::HIERARCHY_NON_NATIVE);
+ mCallback->onScanMessage(FrontendScanMessageType::HIERARCHY, msg);
+ }
+
+ if (mType == FrontendType::ANALOG) {
+ FrontendScanMessage msg;
+ msg.set<FrontendScanMessage::Tag::analogType>(FrontendAnalogType::PAL);
+ mCallback->onScanMessage(FrontendScanMessageType::ANALOG_TYPE, msg);
+ }
+
+ {
+ FrontendScanMessage msg;
+ vector<uint8_t> plpIds = {2};
+ msg.set<FrontendScanMessage::Tag::plpIds>(plpIds);
+ mCallback->onScanMessage(FrontendScanMessageType::PLP_IDS, msg);
+ }
+
+ {
+ FrontendScanMessage msg;
+ vector<uint8_t> groupIds = {3};
+ msg.set<FrontendScanMessage::Tag::groupIds>(groupIds);
+ mCallback->onScanMessage(FrontendScanMessageType::GROUP_IDS, msg);
+ }
+
+ {
+ FrontendScanMessage msg;
+ vector<char16_t> inputStreamIds = {1};
+ msg.set<FrontendScanMessage::Tag::inputStreamIds>(inputStreamIds);
+ mCallback->onScanMessage(FrontendScanMessageType::INPUT_STREAM_IDS, msg);
+ }
+
+ switch (mType) {
+ case FrontendType::DVBT: {
+ FrontendScanMessage msg;
+ FrontendScanMessageStandard std;
+ std.set<FrontendScanMessageStandard::Tag::tStd>(FrontendDvbtStandard::AUTO);
+ msg.set<FrontendScanMessage::Tag::std>(std);
+ mCallback->onScanMessage(FrontendScanMessageType::STANDARD, msg);
+ break;
+ }
+ case FrontendType::DVBS: {
+ FrontendScanMessage msg;
+ FrontendScanMessageStandard std;
+ std.set<FrontendScanMessageStandard::Tag::sStd>(FrontendDvbsStandard::AUTO);
+ msg.set<FrontendScanMessage::Tag::std>(std);
+ mCallback->onScanMessage(FrontendScanMessageType::STANDARD, msg);
+ break;
+ }
+ case FrontendType::ANALOG: {
+ FrontendScanMessage msg;
+ FrontendScanMessageStandard std;
+ std.set<FrontendScanMessageStandard::Tag::sifStd>(FrontendAnalogSifStandard::AUTO);
+ msg.set<FrontendScanMessage::Tag::std>(std);
+ mCallback->onScanMessage(FrontendScanMessageType::STANDARD, msg);
+ break;
+ }
+ default:
+ break;
+ }
+
+ {
+ FrontendScanMessage msg;
+ FrontendScanAtsc3PlpInfo info;
+ info.plpId = 1;
+ info.bLlsFlag = false;
+ vector<FrontendScanAtsc3PlpInfo> atsc3PlpInfos = {info};
+ msg.set<FrontendScanMessage::Tag::atsc3PlpInfos>(atsc3PlpInfos);
+ mCallback->onScanMessage(FrontendScanMessageType::ATSC3_PLP_INFO, msg);
+ }
+
+ {
+ FrontendScanMessage msg;
+ FrontendModulation modulation;
+ modulation.set<FrontendModulation::Tag::dvbc>(FrontendDvbcModulation::MOD_16QAM);
+ msg.set<FrontendScanMessage::Tag::modulation>(modulation);
+ mCallback->onScanMessage(FrontendScanMessageType::MODULATION, msg);
+ }
+
+ {
+ FrontendScanMessage msg;
+ msg.set<FrontendScanMessage::Tag::isHighPriority>(true);
+ mCallback->onScanMessage(FrontendScanMessageType::HIGH_PRIORITY, msg);
+ }
+
+ {
+ FrontendScanMessage msg;
+ msg.set<FrontendScanMessage::Tag::isLocked>(true);
+ mCallback->onScanMessage(FrontendScanMessageType::LOCKED, msg);
+ mIsLocked = true;
+ }
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Frontend::stopScan() {
+ ALOGV("%s", __FUNCTION__);
+
+ mIsLocked = false;
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Frontend::getStatus(const std::vector<FrontendStatusType>& in_statusTypes,
+ std::vector<FrontendStatus>* _aidl_return) {
+ ALOGV("%s", __FUNCTION__);
+
+ for (int i = 0; i < in_statusTypes.size(); i++) {
+ FrontendStatusType type = in_statusTypes[i];
+ FrontendStatus status;
+ // assign randomly selected values for testing.
+ switch (type) {
+ case FrontendStatusType::DEMOD_LOCK: {
+ status.set<FrontendStatus::isDemodLocked>(true);
+ break;
+ }
+ case FrontendStatusType::SNR: {
+ status.set<FrontendStatus::snr>(221);
+ break;
+ }
+ case FrontendStatusType::BER: {
+ status.set<FrontendStatus::ber>(1);
+ break;
+ }
+ case FrontendStatusType::PER: {
+ status.set<FrontendStatus::per>(2);
+ break;
+ }
+ case FrontendStatusType::PRE_BER: {
+ status.set<FrontendStatus::preBer>(3);
+ break;
+ }
+ case FrontendStatusType::SIGNAL_QUALITY: {
+ status.set<FrontendStatus::signalQuality>(4);
+ break;
+ }
+ case FrontendStatusType::SIGNAL_STRENGTH: {
+ status.set<FrontendStatus::signalStrength>(5);
+ break;
+ }
+ case FrontendStatusType::SYMBOL_RATE: {
+ status.set<FrontendStatus::symbolRate>(6);
+ break;
+ }
+ case FrontendStatusType::FEC: {
+ status.set<FrontendStatus::innerFec>(FrontendInnerFec::FEC_2_9); // value = 1 << 7
+ break;
+ }
+ case FrontendStatusType::MODULATION: {
+ switch (mType) {
+ case FrontendType::ISDBS: {
+ FrontendModulationStatus modulationStatus;
+ modulationStatus.set<FrontendModulationStatus::Tag::isdbs>(
+ FrontendIsdbsModulation::MOD_BPSK); // value = 1 << 1
+ status.set<FrontendStatus::modulationStatus>(modulationStatus);
+ break;
+ }
+ case FrontendType::DVBC: {
+ FrontendModulationStatus modulationStatus;
+ modulationStatus.set<FrontendModulationStatus::Tag::dvbc>(
+ FrontendDvbcModulation::MOD_16QAM); // value = 1 << 1
+ status.set<FrontendStatus::modulationStatus>(modulationStatus);
+ break;
+ }
+ case FrontendType::DVBS: {
+ FrontendModulationStatus modulationStatus;
+ modulationStatus.set<FrontendModulationStatus::Tag::dvbs>(
+ FrontendDvbsModulation::MOD_QPSK); // value = 1 << 1
+ status.set<FrontendStatus::modulationStatus>(modulationStatus);
+ break;
+ }
+ case FrontendType::ISDBS3: {
+ FrontendModulationStatus modulationStatus;
+ modulationStatus.set<FrontendModulationStatus::Tag::isdbs3>(
+ FrontendIsdbs3Modulation::MOD_BPSK); // value = 1 << 1
+ status.set<FrontendStatus::modulationStatus>(modulationStatus);
+ break;
+ }
+ case FrontendType::ISDBT: {
+ FrontendModulationStatus modulationStatus;
+ modulationStatus.set<FrontendModulationStatus::Tag::isdbt>(
+ FrontendIsdbtModulation::MOD_DQPSK); // value = 1 << 1
+ status.set<FrontendStatus::modulationStatus>(modulationStatus);
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ }
+ case FrontendStatusType::SPECTRAL: {
+ status.set<FrontendStatus::inversion>(FrontendSpectralInversion::NORMAL);
+ break;
+ }
+ case FrontendStatusType::LNB_VOLTAGE: {
+ status.set<FrontendStatus::lnbVoltage>(LnbVoltage::VOLTAGE_5V);
+ break;
+ }
+ case FrontendStatusType::PLP_ID: {
+ status.set<FrontendStatus::plpId>(101); // type uint8_t
+ break;
+ }
+ case FrontendStatusType::EWBS: {
+ status.set<FrontendStatus::isEWBS>(false);
+ break;
+ }
+ case FrontendStatusType::AGC: {
+ status.set<FrontendStatus::agc>(7);
+ break;
+ }
+ case FrontendStatusType::LNA: {
+ status.set<FrontendStatus::isLnaOn>(false);
+ break;
+ }
+ case FrontendStatusType::LAYER_ERROR: {
+ vector<bool> v = {false, true, true};
+ status.set<FrontendStatus::isLayerError>(v);
+ break;
+ }
+ case FrontendStatusType::MER: {
+ status.set<FrontendStatus::mer>(8);
+ break;
+ }
+ case FrontendStatusType::FREQ_OFFSET: {
+ status.set<FrontendStatus::freqOffset>(9);
+ break;
+ }
+ case FrontendStatusType::HIERARCHY: {
+ status.set<FrontendStatus::hierarchy>(FrontendDvbtHierarchy::HIERARCHY_1_NATIVE);
+ break;
+ }
+ case FrontendStatusType::RF_LOCK: {
+ status.set<FrontendStatus::isRfLocked>(false);
+ break;
+ }
+ case FrontendStatusType::ATSC3_PLP_INFO: {
+ FrontendStatusAtsc3PlpInfo info1;
+ info1.plpId = 3;
+ info1.isLocked = false;
+ info1.uec = 313;
+ FrontendStatusAtsc3PlpInfo info2;
+ info2.plpId = 5;
+ info2.isLocked = true;
+ info2.uec = 515;
+ vector<FrontendStatusAtsc3PlpInfo> infos = {info1, info2};
+ status.set<FrontendStatus::plpInfo>(infos);
+ break;
+ }
+ case FrontendStatusType::MODULATIONS: {
+ FrontendModulation modulation;
+ vector<FrontendModulation> modulations;
+ switch (mType) {
+ case FrontendType::ISDBS: {
+ modulation.set<FrontendModulation::Tag::isdbs>(
+ FrontendIsdbsModulation::MOD_BPSK); // value = 1 << 1
+ modulations.push_back(modulation);
+ status.set<FrontendStatus::modulations>(modulations);
+ break;
+ }
+ case FrontendType::DVBC: {
+ modulation.set<FrontendModulation::Tag::dvbc>(
+ FrontendDvbcModulation::MOD_16QAM); // value = 1 << 1
+ modulations.push_back(modulation);
+ status.set<FrontendStatus::modulations>(modulations);
+ break;
+ }
+ case FrontendType::DVBS: {
+ modulation.set<FrontendModulation::Tag::dvbs>(
+ FrontendDvbsModulation::MOD_QPSK); // value = 1 << 1
+ modulations.push_back(modulation);
+ status.set<FrontendStatus::modulations>(modulations);
+ break;
+ }
+ case FrontendType::DVBT: {
+ modulation.set<FrontendModulation::Tag::dvbt>(
+ FrontendDvbtConstellation::CONSTELLATION_16QAM_R); // value = 1 <<
+ // 16
+ modulations.push_back(modulation);
+ status.set<FrontendStatus::modulations>(modulations);
+ break;
+ }
+ case FrontendType::ISDBS3: {
+ modulation.set<FrontendModulation::Tag::isdbs3>(
+ FrontendIsdbs3Modulation::MOD_BPSK); // value = 1 << 1
+ modulations.push_back(modulation);
+ status.set<FrontendStatus::modulations>(modulations);
+ break;
+ }
+ case FrontendType::ISDBT: {
+ modulation.set<FrontendModulation::Tag::isdbt>(
+ FrontendIsdbtModulation::MOD_DQPSK); // value = 1 << 1
+ modulations.push_back(modulation);
+ status.set<FrontendStatus::modulations>(modulations);
+ break;
+ }
+ case FrontendType::ATSC: {
+ modulation.set<FrontendModulation::Tag::atsc>(
+ FrontendAtscModulation::MOD_8VSB); // value = 1 << 2
+ modulations.push_back(modulation);
+ status.set<FrontendStatus::modulations>(modulations);
+ break;
+ }
+ case FrontendType::ATSC3: {
+ modulation.set<FrontendModulation::Tag::atsc3>(
+ FrontendAtsc3Modulation::MOD_QPSK); // value = 1 << 1
+ modulations.push_back(modulation);
+ status.set<FrontendStatus::modulations>(modulations);
+ break;
+ }
+ case FrontendType::DTMB: {
+ modulation.set<FrontendModulation::Tag::dtmb>(
+ FrontendDtmbModulation::CONSTELLATION_4QAM); // value = 1 << 1
+ modulations.push_back(modulation);
+ status.set<FrontendStatus::modulations>(modulations);
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ }
+ case FrontendStatusType::BERS: {
+ vector<int32_t> bers = {1};
+ status.set<FrontendStatus::bers>(bers);
+ break;
+ }
+ case FrontendStatusType::CODERATES: {
+ vector<FrontendInnerFec> rates;
+ rates.push_back(FrontendInnerFec::FEC_6_15); // value = 1 << 39
+ status.set<FrontendStatus::codeRates>(rates);
+ break;
+ }
+ case FrontendStatusType::BANDWIDTH: {
+ FrontendBandwidth bandwidth;
+ switch (mType) {
+ case FrontendType::DVBC: {
+ bandwidth.set<FrontendBandwidth::Tag::dvbc>(
+ FrontendDvbcBandwidth::BANDWIDTH_6MHZ); // value = 1 << 1
+ status.set<FrontendStatus::bandwidth>(bandwidth);
+ break;
+ }
+ case FrontendType::DVBT: {
+ bandwidth.set<FrontendBandwidth::Tag::dvbt>(
+ FrontendDvbtBandwidth::BANDWIDTH_8MHZ); // value = 1 << 1
+ status.set<FrontendStatus::bandwidth>(bandwidth);
+ break;
+ }
+ case FrontendType::ISDBT: {
+ bandwidth.set<FrontendBandwidth::Tag::isdbt>(
+ FrontendIsdbtBandwidth::BANDWIDTH_8MHZ); // value = 1 << 1
+ status.set<FrontendStatus::bandwidth>(bandwidth);
+ break;
+ }
+ case FrontendType::ATSC3: {
+ bandwidth.set<FrontendBandwidth::Tag::atsc3>(
+ FrontendAtsc3Bandwidth::BANDWIDTH_6MHZ); // value = 1 << 1
+ status.set<FrontendStatus::bandwidth>(bandwidth);
+ break;
+ }
+ case FrontendType::DTMB: {
+ bandwidth.set<FrontendBandwidth::Tag::dtmb>(
+ FrontendDtmbBandwidth::BANDWIDTH_8MHZ); // value = 1 << 1
+ status.set<FrontendStatus::bandwidth>(bandwidth);
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ }
+ case FrontendStatusType::GUARD_INTERVAL: {
+ FrontendGuardInterval interval;
+ switch (mType) {
+ case FrontendType::DVBT: {
+ interval.set<FrontendGuardInterval::Tag::dvbt>(
+ FrontendDvbtGuardInterval::INTERVAL_1_32); // value = 1 << 1
+ status.set<FrontendStatus::interval>(interval);
+ break;
+ }
+ case FrontendType::ISDBT: {
+ interval.set<FrontendGuardInterval::Tag::isdbt>(
+ FrontendDvbtGuardInterval::INTERVAL_1_32); // value = 1 << 1
+ status.set<FrontendStatus::interval>(interval);
+ break;
+ }
+ case FrontendType::DTMB: {
+ interval.set<FrontendGuardInterval::Tag::dtmb>(
+ FrontendDtmbGuardInterval::PN_420_VARIOUS); // value = 1 << 1
+ status.set<FrontendStatus::interval>(interval);
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ }
+ case FrontendStatusType::TRANSMISSION_MODE: {
+ FrontendTransmissionMode transMode;
+ switch (mType) {
+ case FrontendType::DVBT: {
+ transMode.set<FrontendTransmissionMode::Tag::dvbt>(
+ FrontendDvbtTransmissionMode::MODE_16K_E); // value = 1 << 8
+ status.set<FrontendStatus::transmissionMode>(transMode);
+ break;
+ }
+ case FrontendType::ISDBT: {
+ transMode.set<FrontendTransmissionMode::Tag::isdbt>(
+ FrontendIsdbtMode::MODE_1); // value = 1 << 1
+ status.set<FrontendStatus::transmissionMode>(transMode);
+ break;
+ }
+ case FrontendType::DTMB: {
+ transMode.set<FrontendTransmissionMode::Tag::dtmb>(
+ FrontendDtmbTransmissionMode::C1); // value = 1 << 1
+ status.set<FrontendStatus::transmissionMode>(transMode);
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ }
+ case FrontendStatusType::UEC: {
+ status.set<FrontendStatus::uec>(4);
+ break;
+ }
+ case FrontendStatusType::T2_SYSTEM_ID: {
+ status.set<FrontendStatus::systemId>(5);
+ break;
+ }
+ case FrontendStatusType::INTERLEAVINGS: {
+ FrontendInterleaveMode interleave;
+ vector<FrontendInterleaveMode> interleaves;
+ switch (mType) {
+ case FrontendType::DVBC: {
+ // value = 1 << 1
+ interleave.set<FrontendInterleaveMode::Tag::dvbc>(
+ FrontendCableTimeInterleaveMode::INTERLEAVING_128_1_0);
+ interleaves.push_back(interleave);
+ status.set<FrontendStatus::interleaving>(interleaves);
+ break;
+ }
+ case FrontendType::ATSC3: {
+ interleave.set<FrontendInterleaveMode::Tag::atsc3>(
+ FrontendAtsc3TimeInterleaveMode::CTI); // value = 1 << 1
+ interleaves.push_back(interleave);
+ status.set<FrontendStatus::interleaving>(interleaves);
+ break;
+ }
+ case FrontendType::DTMB: {
+ interleave.set<FrontendInterleaveMode::Tag::dtmb>(
+ FrontendDtmbTimeInterleaveMode::TIMER_INT_240); // value = 1 << 1
+ interleaves.push_back(interleave);
+ status.set<FrontendStatus::interleaving>(interleaves);
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ }
+ case FrontendStatusType::ISDBT_SEGMENTS: {
+ vector<uint8_t> segments = {2, 3};
+ status.set<FrontendStatus::isdbtSegment>(segments);
+ break;
+ }
+ case FrontendStatusType::TS_DATA_RATES: {
+ vector<int32_t> dataRates = {4, 5};
+ status.set<FrontendStatus::tsDataRate>(dataRates);
+ break;
+ }
+ case FrontendStatusType::ROLL_OFF: {
+ FrontendRollOff rollOff;
+ switch (mType) {
+ case FrontendType::DVBS: {
+ rollOff.set<FrontendRollOff::Tag::dvbs>(
+ FrontendDvbsRolloff::ROLLOFF_0_35); // value = 1
+ status.set<FrontendStatus::rollOff>(rollOff);
+ break;
+ }
+ case FrontendType::ISDBS: {
+ rollOff.set<FrontendRollOff::Tag::isdbs>(
+ FrontendIsdbsRolloff::ROLLOFF_0_35); // value = 1
+ status.set<FrontendStatus::rollOff>(rollOff);
+ break;
+ }
+ case FrontendType::ISDBS3: {
+ rollOff.set<FrontendRollOff::Tag::isdbs3>(
+ FrontendIsdbs3Rolloff::ROLLOFF_0_03); // value = 1
+ status.set<FrontendStatus::rollOff>(rollOff);
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ }
+ case FrontendStatusType::IS_MISO: {
+ status.set<FrontendStatus::isMiso>(true);
+ break;
+ }
+ case FrontendStatusType::IS_LINEAR: {
+ status.set<FrontendStatus::isLinear>(true);
+ break;
+ }
+ case FrontendStatusType::IS_SHORT_FRAMES: {
+ status.set<FrontendStatus::isShortFrames>(true);
+ break;
+ }
+ default: {
+ continue;
+ }
+ }
+ _aidl_return->push_back(status);
+ }
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Frontend::setLna(bool /* in_bEnable */) {
+ ALOGV("%s", __FUNCTION__);
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Frontend::setLnb(int32_t /* in_lnbId */) {
+ ALOGV("%s", __FUNCTION__);
+ if (!supportsSatellite()) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Frontend::linkCiCam(int32_t in_ciCamId, int32_t* _aidl_return) {
+ ALOGV("%s", __FUNCTION__);
+
+ mCiCamId = in_ciCamId;
+ *_aidl_return = 0;
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Frontend::unlinkCiCam(int32_t /* in_ciCamId */) {
+ ALOGV("%s", __FUNCTION__);
+
+ mCiCamId = -1;
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+FrontendType Frontend::getFrontendType() {
+ return mType;
+}
+
+int32_t Frontend::getFrontendId() {
+ return mId;
+}
+
+bool Frontend::supportsSatellite() {
+ return mType == FrontendType::DVBS || mType == FrontendType::ISDBS ||
+ mType == FrontendType::ISDBS3;
+}
+
+bool Frontend::isLocked() {
+ return mIsLocked;
+}
+
+} // namespace tuner
+} // namespace tv
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/tv/tuner/aidl/default/Frontend.h b/tv/tuner/aidl/default/Frontend.h
new file mode 100644
index 0000000..f89e74d
--- /dev/null
+++ b/tv/tuner/aidl/default/Frontend.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/tv/tuner/BnFrontend.h>
+#include <fstream>
+#include <iostream>
+#include "Tuner.h"
+
+using namespace std;
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+class Tuner;
+
+class Frontend : public BnFrontend {
+ public:
+ Frontend(FrontendType type, int32_t id, std::shared_ptr<Tuner> tuner);
+
+ ::ndk::ScopedAStatus setCallback(
+ const std::shared_ptr<IFrontendCallback>& in_callback) override;
+ ::ndk::ScopedAStatus tune(const FrontendSettings& in_settings) override;
+ ::ndk::ScopedAStatus stopTune() override;
+ ::ndk::ScopedAStatus close() override;
+ ::ndk::ScopedAStatus scan(const FrontendSettings& in_settings,
+ FrontendScanType in_type) override;
+ ::ndk::ScopedAStatus stopScan() override;
+ ::ndk::ScopedAStatus getStatus(const std::vector<FrontendStatusType>& in_statusTypes,
+ std::vector<FrontendStatus>* _aidl_return) override;
+ ::ndk::ScopedAStatus setLnb(int32_t in_lnbId) override;
+ ::ndk::ScopedAStatus setLna(bool in_bEnable) override;
+ ::ndk::ScopedAStatus linkCiCam(int32_t in_ciCamId, int32_t* _aidl_return) override;
+ ::ndk::ScopedAStatus unlinkCiCam(int32_t in_ciCamId) override;
+
+ FrontendType getFrontendType();
+ int32_t getFrontendId();
+ string getSourceFile();
+ bool isLocked();
+
+ private:
+ virtual ~Frontend();
+ bool supportsSatellite();
+ std::shared_ptr<IFrontendCallback> mCallback;
+ std::shared_ptr<Tuner> mTunerService;
+ FrontendType mType = FrontendType::UNDEFINED;
+ int32_t mId = 0;
+ bool mIsLocked = false;
+ int32_t mCiCamId;
+
+ std::ifstream mFrontendData;
+};
+
+} // namespace tuner
+} // namespace tv
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/tv/tuner/aidl/default/Lnb.cpp b/tv/tuner/aidl/default/Lnb.cpp
new file mode 100644
index 0000000..35d2da6
--- /dev/null
+++ b/tv/tuner/aidl/default/Lnb.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2021 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
+#define LOG_TAG "android.hardware.tv.tuner-service.example-Lnb"
+
+#include "Lnb.h"
+#include <utils/Log.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+Lnb::Lnb() {}
+
+Lnb::Lnb(int id) {
+ mId = id;
+}
+
+Lnb::~Lnb() {}
+
+::ndk::ScopedAStatus Lnb::setCallback(const std::shared_ptr<ILnbCallback>& /* in_callback */) {
+ ALOGV("%s", __FUNCTION__);
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Lnb::setVoltage(LnbVoltage /* in_voltage */) {
+ ALOGV("%s", __FUNCTION__);
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Lnb::setTone(LnbTone /* in_tone */) {
+ ALOGV("%s", __FUNCTION__);
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Lnb::setSatellitePosition(LnbPosition /* in_position */) {
+ ALOGV("%s", __FUNCTION__);
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Lnb::sendDiseqcMessage(const std::vector<uint8_t>& /* in_diseqcMessage */) {
+ ALOGV("%s", __FUNCTION__);
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Lnb::close() {
+ ALOGV("%s", __FUNCTION__);
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+int Lnb::getId() {
+ return mId;
+}
+
+} // namespace tuner
+} // namespace tv
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/tv/tuner/aidl/default/Lnb.h b/tv/tuner/aidl/default/Lnb.h
new file mode 100644
index 0000000..bfe3097
--- /dev/null
+++ b/tv/tuner/aidl/default/Lnb.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/tv/tuner/BnLnb.h>
+#include <aidl/android/hardware/tv/tuner/ITuner.h>
+
+using namespace std;
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+class Lnb : public BnLnb {
+ public:
+ Lnb();
+ Lnb(int id);
+
+ ::ndk::ScopedAStatus setCallback(const std::shared_ptr<ILnbCallback>& in_callback) override;
+ ::ndk::ScopedAStatus setVoltage(LnbVoltage in_voltage) override;
+ ::ndk::ScopedAStatus setTone(LnbTone in_tone) override;
+ ::ndk::ScopedAStatus setSatellitePosition(LnbPosition in_position) override;
+ ::ndk::ScopedAStatus sendDiseqcMessage(const std::vector<uint8_t>& in_diseqcMessage) override;
+ ::ndk::ScopedAStatus close() override;
+
+ int getId();
+
+ private:
+ int mId;
+ virtual ~Lnb();
+};
+
+} // namespace tuner
+} // namespace tv
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/tv/tuner/aidl/default/OWNERS b/tv/tuner/aidl/default/OWNERS
new file mode 100644
index 0000000..bf2b609
--- /dev/null
+++ b/tv/tuner/aidl/default/OWNERS
@@ -0,0 +1,3 @@
+hgchen@google.com
+shubang@google.com
+quxiangfang@google.com
diff --git a/tv/tuner/aidl/default/TimeFilter.cpp b/tv/tuner/aidl/default/TimeFilter.cpp
new file mode 100644
index 0000000..4fd8d21
--- /dev/null
+++ b/tv/tuner/aidl/default/TimeFilter.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2021 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
+#define LOG_TAG "android.hardware.tv.tuner-service.example-TimeFilter"
+
+#include "TimeFilter.h"
+#include <utils/Log.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+TimeFilter::TimeFilter() {}
+
+TimeFilter::TimeFilter(std::shared_ptr<Demux> demux) {
+ mDemux = demux;
+}
+
+TimeFilter::~TimeFilter() {}
+
+::ndk::ScopedAStatus TimeFilter::setTimeStamp(int64_t in_timeStamp) {
+ ALOGV("%s", __FUNCTION__);
+ if (in_timeStamp == INVALID_TIME_STAMP) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+ mTimeStamp = in_timeStamp;
+ mBeginTime = time(NULL);
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus TimeFilter::clearTimeStamp() {
+ ALOGV("%s", __FUNCTION__);
+ mTimeStamp = INVALID_TIME_STAMP;
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus TimeFilter::getTimeStamp(int64_t* _aidl_return) {
+ ALOGV("%s", __FUNCTION__);
+ if (mTimeStamp == INVALID_TIME_STAMP) {
+ *_aidl_return = mTimeStamp;
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+
+ uint64_t currentTimeStamp = mTimeStamp + difftime(time(NULL), mBeginTime) * 900000;
+ *_aidl_return = currentTimeStamp;
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus TimeFilter::getSourceTime(int64_t* _aidl_return) {
+ ALOGV("%s", __FUNCTION__);
+
+ *_aidl_return = 0;
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus TimeFilter::close() {
+ ALOGV("%s", __FUNCTION__);
+ mTimeStamp = INVALID_TIME_STAMP;
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+} // namespace tuner
+} // namespace tv
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/tv/tuner/aidl/default/TimeFilter.h b/tv/tuner/aidl/default/TimeFilter.h
new file mode 100644
index 0000000..ff35c47
--- /dev/null
+++ b/tv/tuner/aidl/default/TimeFilter.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/tv/tuner/BnTimeFilter.h>
+#include "Demux.h"
+#include "time.h"
+
+using namespace std;
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+#define INVALID_TIME_STAMP -1
+
+class Demux;
+
+class TimeFilter : public BnTimeFilter {
+ public:
+ TimeFilter();
+ TimeFilter(std::shared_ptr<Demux> demux);
+ ~TimeFilter();
+
+ ::ndk::ScopedAStatus setTimeStamp(int64_t in_timeStamp) override;
+ ::ndk::ScopedAStatus clearTimeStamp() override;
+ ::ndk::ScopedAStatus getTimeStamp(int64_t* _aidl_return) override;
+ ::ndk::ScopedAStatus getSourceTime(int64_t* _aidl_return) override;
+ ::ndk::ScopedAStatus close() override;
+
+ private:
+ std::shared_ptr<Demux> mDemux;
+ uint64_t mTimeStamp = INVALID_TIME_STAMP;
+ time_t mBeginTime;
+};
+
+} // namespace tuner
+} // namespace tv
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/tv/tuner/aidl/default/Tuner.cpp b/tv/tuner/aidl/default/Tuner.cpp
new file mode 100644
index 0000000..dc0bd5e
--- /dev/null
+++ b/tv/tuner/aidl/default/Tuner.cpp
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2021 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
+#define LOG_TAG "android.hardware.tv.tuner-service.example-Tuner"
+
+#include "Tuner.h"
+#include <utils/Log.h>
+#include "Demux.h"
+#include "Descrambler.h"
+#include "Frontend.h"
+#include "Lnb.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+Tuner::Tuner() {
+ // Static Frontends array to maintain local frontends information
+ // Array index matches their FrontendId in the default impl
+ mFrontendSize = 10;
+ mFrontends[0] = ndk::SharedRefBase::make<Frontend>(FrontendType::ISDBS, 0, ref<Tuner>());
+ mFrontends[1] = ndk::SharedRefBase::make<Frontend>(FrontendType::ATSC3, 1, ref<Tuner>());
+ mFrontends[2] = ndk::SharedRefBase::make<Frontend>(FrontendType::DVBC, 2, ref<Tuner>());
+ mFrontends[3] = ndk::SharedRefBase::make<Frontend>(FrontendType::DVBS, 3, ref<Tuner>());
+ mFrontends[4] = ndk::SharedRefBase::make<Frontend>(FrontendType::DVBT, 4, ref<Tuner>());
+ mFrontends[5] = ndk::SharedRefBase::make<Frontend>(FrontendType::ISDBT, 5, ref<Tuner>());
+ mFrontends[6] = ndk::SharedRefBase::make<Frontend>(FrontendType::ANALOG, 6, ref<Tuner>());
+ mFrontends[7] = ndk::SharedRefBase::make<Frontend>(FrontendType::ATSC, 7, ref<Tuner>());
+ mFrontends[8] = ndk::SharedRefBase::make<Frontend>(FrontendType::ISDBS3, 8, ref<Tuner>());
+ mFrontends[9] = ndk::SharedRefBase::make<Frontend>(FrontendType::DTMB, 9, ref<Tuner>());
+
+ vector<FrontendStatusType> statusCaps;
+
+ FrontendCapabilities capsIsdbs;
+ capsIsdbs.set<FrontendCapabilities::Tag::isdbsCaps>(FrontendIsdbsCapabilities());
+ mFrontendCaps[0] = capsIsdbs;
+ statusCaps = {
+ FrontendStatusType::DEMOD_LOCK, FrontendStatusType::SNR,
+ FrontendStatusType::FEC, FrontendStatusType::MODULATION,
+ FrontendStatusType::MODULATIONS, FrontendStatusType::ROLL_OFF,
+ };
+ mFrontendStatusCaps[0] = statusCaps;
+
+ FrontendCapabilities capsAtsc3;
+ capsAtsc3.set<FrontendCapabilities::Tag::atsc3Caps>(FrontendAtsc3Capabilities());
+ mFrontendCaps[1] = capsAtsc3;
+ statusCaps = {
+ FrontendStatusType::BER,
+ FrontendStatusType::PER,
+ FrontendStatusType::ATSC3_PLP_INFO,
+ FrontendStatusType::MODULATIONS,
+ FrontendStatusType::BERS,
+ FrontendStatusType::INTERLEAVINGS,
+ FrontendStatusType::BANDWIDTH,
+ };
+ mFrontendStatusCaps[1] = statusCaps;
+
+ FrontendCapabilities capsDvbc;
+ capsDvbc.set<FrontendCapabilities::Tag::dvbcCaps>(FrontendDvbcCapabilities());
+ mFrontendCaps[2] = capsDvbc;
+ statusCaps = {
+ FrontendStatusType::PRE_BER, FrontendStatusType::SIGNAL_QUALITY,
+ FrontendStatusType::MODULATION, FrontendStatusType::SPECTRAL,
+ FrontendStatusType::MODULATIONS, FrontendStatusType::CODERATES,
+ FrontendStatusType::INTERLEAVINGS, FrontendStatusType::BANDWIDTH,
+ };
+ mFrontendStatusCaps[2] = statusCaps;
+
+ FrontendCapabilities capsDvbs;
+ capsDvbs.set<FrontendCapabilities::Tag::dvbsCaps>(FrontendDvbsCapabilities());
+ mFrontendCaps[3] = capsDvbs;
+ statusCaps = {
+ FrontendStatusType::SIGNAL_STRENGTH, FrontendStatusType::SYMBOL_RATE,
+ FrontendStatusType::MODULATION, FrontendStatusType::MODULATIONS,
+ FrontendStatusType::ROLL_OFF, FrontendStatusType::IS_MISO,
+ };
+ mFrontendStatusCaps[3] = statusCaps;
+
+ FrontendCapabilities capsDvbt;
+ capsDvbt.set<FrontendCapabilities::Tag::dvbtCaps>(FrontendDvbtCapabilities());
+ mFrontendCaps[4] = capsDvbt;
+ statusCaps = {
+ FrontendStatusType::EWBS,
+ FrontendStatusType::PLP_ID,
+ FrontendStatusType::HIERARCHY,
+ FrontendStatusType::MODULATIONS,
+ FrontendStatusType::BANDWIDTH,
+ FrontendStatusType::GUARD_INTERVAL,
+ FrontendStatusType::TRANSMISSION_MODE,
+ FrontendStatusType::T2_SYSTEM_ID,
+ };
+ mFrontendStatusCaps[4] = statusCaps;
+
+ FrontendCapabilities capsIsdbt;
+ FrontendIsdbtCapabilities isdbtCaps{
+ .modeCap = (int)FrontendIsdbtMode::MODE_1 | (int)FrontendIsdbtMode::MODE_2,
+ .bandwidthCap = (int)FrontendIsdbtBandwidth::BANDWIDTH_6MHZ,
+ .modulationCap = (int)FrontendIsdbtModulation::MOD_16QAM,
+ // ISDBT shares coderate and guard interval with DVBT
+ .coderateCap = (int)FrontendDvbtCoderate::CODERATE_4_5 |
+ (int)FrontendDvbtCoderate::CODERATE_6_7,
+ .guardIntervalCap = (int)FrontendDvbtGuardInterval::INTERVAL_1_128,
+ };
+ capsIsdbt.set<FrontendCapabilities::Tag::isdbtCaps>(isdbtCaps);
+ mFrontendCaps[5] = capsIsdbt;
+ statusCaps = {
+ FrontendStatusType::AGC,
+ FrontendStatusType::LNA,
+ FrontendStatusType::MODULATION,
+ FrontendStatusType::MODULATIONS,
+ FrontendStatusType::BANDWIDTH,
+ FrontendStatusType::GUARD_INTERVAL,
+ FrontendStatusType::TRANSMISSION_MODE,
+ FrontendStatusType::ISDBT_SEGMENTS,
+ };
+ mFrontendStatusCaps[5] = statusCaps;
+
+ FrontendCapabilities capsAnalog;
+ capsAnalog.set<FrontendCapabilities::Tag::analogCaps>(FrontendAnalogCapabilities());
+ mFrontendCaps[6] = capsAnalog;
+ statusCaps = {
+ FrontendStatusType::LAYER_ERROR,
+ FrontendStatusType::MER,
+ FrontendStatusType::UEC,
+ FrontendStatusType::TS_DATA_RATES,
+ };
+ mFrontendStatusCaps[6] = statusCaps;
+
+ FrontendCapabilities capsAtsc;
+ capsAtsc.set<FrontendCapabilities::Tag::atscCaps>(FrontendAtscCapabilities());
+ mFrontendCaps[7] = capsAtsc;
+ statusCaps = {
+ FrontendStatusType::FREQ_OFFSET,
+ FrontendStatusType::RF_LOCK,
+ FrontendStatusType::MODULATIONS,
+ FrontendStatusType::IS_LINEAR,
+ };
+ mFrontendStatusCaps[7] = statusCaps;
+
+ FrontendCapabilities capsIsdbs3;
+ capsIsdbs3.set<FrontendCapabilities::Tag::isdbs3Caps>(FrontendIsdbs3Capabilities());
+ mFrontendCaps[8] = capsIsdbs3;
+ statusCaps = {
+ FrontendStatusType::DEMOD_LOCK, FrontendStatusType::MODULATION,
+ FrontendStatusType::MODULATIONS, FrontendStatusType::ROLL_OFF,
+ FrontendStatusType::IS_SHORT_FRAMES,
+ };
+ mFrontendStatusCaps[8] = statusCaps;
+
+ FrontendCapabilities capsDtmb;
+ capsDtmb.set<FrontendCapabilities::Tag::dtmbCaps>(FrontendDtmbCapabilities());
+ mFrontendCaps[9] = capsDtmb;
+ statusCaps = {
+ FrontendStatusType::MODULATIONS, FrontendStatusType::INTERLEAVINGS,
+ FrontendStatusType::BANDWIDTH, FrontendStatusType::GUARD_INTERVAL,
+ FrontendStatusType::TRANSMISSION_MODE,
+ };
+ mFrontendStatusCaps[9] = statusCaps;
+
+ mLnbs.resize(2);
+ mLnbs[0] = ndk::SharedRefBase::make<Lnb>(0);
+ mLnbs[1] = ndk::SharedRefBase::make<Lnb>(1);
+}
+
+Tuner::~Tuner() {}
+
+::ndk::ScopedAStatus Tuner::getFrontendIds(std::vector<int32_t>* _aidl_return) {
+ ALOGV("%s", __FUNCTION__);
+
+ _aidl_return->resize(mFrontendSize);
+ for (int i = 0; i < mFrontendSize; i++) {
+ (*_aidl_return)[i] = mFrontends[i]->getFrontendId();
+ }
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Tuner::openFrontendById(int32_t in_frontendId,
+ std::shared_ptr<IFrontend>* _aidl_return) {
+ ALOGV("%s", __FUNCTION__);
+
+ if (in_frontendId >= mFrontendSize || in_frontendId < 0) {
+ ALOGW("[ WARN ] Frontend with id %d isn't available", in_frontendId);
+ *_aidl_return = nullptr;
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+
+ *_aidl_return = mFrontends[in_frontendId];
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Tuner::openDemux(std::vector<int32_t>* out_demuxId,
+ std::shared_ptr<IDemux>* _aidl_return) {
+ ALOGV("%s", __FUNCTION__);
+
+ mLastUsedId += 1;
+ mDemuxes[mLastUsedId] = ndk::SharedRefBase::make<Demux>(mLastUsedId, ref<Tuner>());
+
+ out_demuxId->push_back(mLastUsedId);
+ *_aidl_return = mDemuxes[mLastUsedId];
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Tuner::getDemuxCaps(DemuxCapabilities* _aidl_return) {
+ ALOGV("%s", __FUNCTION__);
+
+ // IP filter can be an MMTP filter's data source.
+ _aidl_return->linkCaps = {0x00, 0x00, 0x02, 0x00, 0x00};
+ // Support time filter testing
+ _aidl_return->bTimeFilter = true;
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Tuner::openDescrambler(std::shared_ptr<IDescrambler>* _aidl_return) {
+ ALOGV("%s", __FUNCTION__);
+
+ *_aidl_return = ndk::SharedRefBase::make<Descrambler>();
+
+ return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Tuner::getFrontendInfo(int32_t in_frontendId, FrontendInfo* _aidl_return) {
+ ALOGV("%s", __FUNCTION__);
+
+ if (in_frontendId >= mFrontendSize) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+
+ // assign randomly selected values for testing.
+ *_aidl_return = {
+ .type = mFrontends[in_frontendId]->getFrontendType(),
+ .minFrequency = 139,
+ .maxFrequency = 1139,
+ .minSymbolRate = 45,
+ .maxSymbolRate = 1145,
+ .acquireRange = 30,
+ .exclusiveGroupId = 57,
+ .statusCaps = mFrontendStatusCaps[in_frontendId],
+ .frontendCaps = mFrontendCaps[in_frontendId],
+ };
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Tuner::getLnbIds(std::vector<int32_t>* _aidl_return) {
+ ALOGV("%s", __FUNCTION__);
+
+ _aidl_return->resize(mLnbs.size());
+ for (int i = 0; i < mLnbs.size(); i++) {
+ (*_aidl_return)[i] = mLnbs[i]->getId();
+ }
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus Tuner::openLnbById(int32_t in_lnbId, std::shared_ptr<ILnb>* _aidl_return) {
+ ALOGV("%s", __FUNCTION__);
+
+ if (in_lnbId >= mLnbs.size()) {
+ *_aidl_return = nullptr;
+ return ::ndk::ScopedAStatus::fromExceptionCode(STATUS_INVALID_OPERATION);
+ }
+
+ *_aidl_return = mLnbs[in_lnbId];
+ return ::ndk::ScopedAStatus::ok();
+}
+
+std::shared_ptr<Frontend> Tuner::getFrontendById(int32_t frontendId) {
+ ALOGV("%s", __FUNCTION__);
+
+ return mFrontends[frontendId];
+}
+
+::ndk::ScopedAStatus Tuner::openLnbByName(const std::string& /* in_lnbName */,
+ std::vector<int32_t>* out_lnbId,
+ std::shared_ptr<ILnb>* _aidl_return) {
+ ALOGV("%s", __FUNCTION__);
+
+ out_lnbId->push_back(1234);
+ *_aidl_return = ndk::SharedRefBase::make<Lnb>();
+
+ return ::ndk::ScopedAStatus::ok();
+}
+
+void Tuner::setFrontendAsDemuxSource(int32_t frontendId, int32_t demuxId) {
+ mFrontendToDemux[frontendId] = demuxId;
+ if (mFrontends[frontendId] != nullptr && mFrontends[frontendId]->isLocked()) {
+ mDemuxes[demuxId]->startFrontendInputLoop();
+ }
+}
+
+void Tuner::removeDemux(int32_t demuxId) {
+ map<int32_t, int32_t>::iterator it;
+ for (it = mFrontendToDemux.begin(); it != mFrontendToDemux.end(); it++) {
+ if (it->second == demuxId) {
+ it = mFrontendToDemux.erase(it);
+ break;
+ }
+ }
+ mDemuxes.erase(demuxId);
+}
+
+void Tuner::removeFrontend(int32_t frontendId) {
+ mFrontendToDemux.erase(frontendId);
+}
+
+void Tuner::frontendStopTune(int32_t frontendId) {
+ map<int32_t, int32_t>::iterator it = mFrontendToDemux.find(frontendId);
+ int32_t demuxId;
+ if (it != mFrontendToDemux.end()) {
+ demuxId = it->second;
+ mDemuxes[demuxId]->stopFrontendInput();
+ }
+}
+
+void Tuner::frontendStartTune(int32_t frontendId) {
+ map<int32_t, int32_t>::iterator it = mFrontendToDemux.find(frontendId);
+ int32_t demuxId;
+ if (it != mFrontendToDemux.end()) {
+ demuxId = it->second;
+ mDemuxes[demuxId]->startFrontendInputLoop();
+ }
+}
+
+} // namespace tuner
+} // namespace tv
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/tv/tuner/aidl/default/Tuner.h b/tv/tuner/aidl/default/Tuner.h
new file mode 100644
index 0000000..e69990d
--- /dev/null
+++ b/tv/tuner/aidl/default/Tuner.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/tv/tuner/BnTuner.h>
+#include <aidl/android/hardware/tv/tuner/FrontendCapabilities.h>
+
+#include <map>
+#include "Demux.h"
+#include "Frontend.h"
+#include "Lnb.h"
+
+using namespace std;
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace tv {
+namespace tuner {
+
+class Frontend;
+class Demux;
+class Lnb;
+
+class Tuner : public BnTuner {
+ public:
+ Tuner();
+ virtual ~Tuner();
+
+ ::ndk::ScopedAStatus getFrontendIds(std::vector<int32_t>* _aidl_return) override;
+ ::ndk::ScopedAStatus openFrontendById(int32_t in_frontendId,
+ std::shared_ptr<IFrontend>* _aidl_return) override;
+ ::ndk::ScopedAStatus openDemux(std::vector<int32_t>* out_demuxId,
+ std::shared_ptr<IDemux>* _aidl_return) override;
+ ::ndk::ScopedAStatus getDemuxCaps(DemuxCapabilities* _aidl_return) override;
+ ::ndk::ScopedAStatus openDescrambler(std::shared_ptr<IDescrambler>* _aidl_return) override;
+ ::ndk::ScopedAStatus getFrontendInfo(int32_t in_frontendId,
+ FrontendInfo* _aidl_return) override;
+ ::ndk::ScopedAStatus getLnbIds(std::vector<int32_t>* _aidl_return) override;
+ ::ndk::ScopedAStatus openLnbById(int32_t in_lnbId,
+ std::shared_ptr<ILnb>* _aidl_return) override;
+ ::ndk::ScopedAStatus openLnbByName(const std::string& in_lnbName,
+ std::vector<int32_t>* out_lnbId,
+ std::shared_ptr<ILnb>* _aidl_return) override;
+
+ std::shared_ptr<Frontend> getFrontendById(int32_t frontendId);
+ void setFrontendAsDemuxSource(int32_t frontendId, int32_t demuxId);
+ void frontendStartTune(int32_t frontendId);
+ void frontendStopTune(int32_t frontendId);
+ void removeDemux(int32_t demuxId);
+ void removeFrontend(int32_t frontendId);
+
+ private:
+ // Static mFrontends array to maintain local frontends information
+ map<int32_t, std::shared_ptr<Frontend>> mFrontends;
+ map<int32_t, FrontendCapabilities> mFrontendCaps;
+ map<int32_t, vector<FrontendStatusType>> mFrontendStatusCaps;
+ map<int32_t, int32_t> mFrontendToDemux;
+ map<int32_t, std::shared_ptr<Demux>> 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.
+ int32_t mLastUsedId = -1;
+ vector<std::shared_ptr<Lnb>> mLnbs;
+};
+
+} // namespace tuner
+} // namespace tv
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/tv/tuner/aidl/default/service.cpp b/tv/tuner/aidl/default/service.cpp
new file mode 100644
index 0000000..649b763
--- /dev/null
+++ b/tv/tuner/aidl/default/service.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2021 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
+#define LOG_TAG "android.hardware.tv.tuner-service.example"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "Tuner.h"
+
+using ::aidl::android::hardware::tv::tuner::Tuner;
+
+int main() {
+ ABinderProcess_setThreadPoolMaxThreadCount(8);
+ std::shared_ptr<Tuner> tuner = ndk::SharedRefBase::make<Tuner>();
+
+ const std::string instance = std::string() + Tuner::descriptor + "/default";
+ binder_status_t status = AServiceManager_addService(tuner->asBinder().get(), instance.c_str());
+ CHECK(status == STATUS_OK);
+
+ ABinderProcess_joinThreadPool();
+ return EXIT_FAILURE; // should not reached
+}
diff --git a/tv/tuner/aidl/default/tuner-default.rc b/tv/tuner/aidl/default/tuner-default.rc
new file mode 100644
index 0000000..fa09456
--- /dev/null
+++ b/tv/tuner/aidl/default/tuner-default.rc
@@ -0,0 +1,6 @@
+service vendor.tuner-default /vendor/bin/hw/android.hardware.tv.tuner-service.example
+ class hal
+ user media
+ group mediadrm drmrpc
+ ioprio rt 4
+ writepid /dev/cpuset/foreground/tasks
diff --git a/tv/tuner/aidl/default/tuner-default.xml b/tv/tuner/aidl/default/tuner-default.xml
new file mode 100644
index 0000000..f0d03ad
--- /dev/null
+++ b/tv/tuner/aidl/default/tuner-default.xml
@@ -0,0 +1,6 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.tv.tuner</name>
+ <fqname>ITuner/default</fqname>
+ </hal>
+</manifest>