Merge changes from topic "Tuner_1.1"

* changes:
  Add VTS of Tuner HAL 1.1
  Add default implementation of Tuner HAL 1.1
  Enable Tuner HAL 1.1 interface
This commit is contained in:
Amy Zhang
2020-07-24 18:59:52 +00:00
committed by Android (Google) Code Review
39 changed files with 4446 additions and 1 deletions

View File

@@ -482,7 +482,7 @@
</hal>
<hal format="hidl" optional="true">
<name>android.hardware.tv.tuner</name>
<version>1.0</version>
<version>1.0-1</version>
<interface>
<name>ITuner</name>
<instance>default</instance>

16
tv/tuner/1.1/Android.bp Normal file
View File

@@ -0,0 +1,16 @@
// This file is autogenerated by hidl-gen -Landroidbp.
hidl_interface {
name: "android.hardware.tv.tuner@1.1",
root: "android.hardware",
srcs: [
"IFilter.hal",
"ITuner.hal",
],
interfaces: [
"android.hidl.base@1.0",
"android.hidl.safe_union@1.0",
"android.hardware.tv.tuner@1.0",
],
gen_java: false,
}

43
tv/tuner/1.1/IFilter.hal Normal file
View File

@@ -0,0 +1,43 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.hardware.tv.tuner@1.1;
import @1.0::IFilter;
import @1.0::Result;
/**
* The Filter is used to filter wanted data according to the filter's
* configuration.
*
* To access the v1.1 IFilter APIs, the implementation can cast the IFilter
* interface returned from the @1.0::IDemux openFilter into a v1.1 IFiler
* using V1_1::IFilter::castFrom(V1_0::IFilter).
*/
interface IFilter extends @1.0::IFilter {
/**
* Get the 64-bit filter Id. This id is 32-bit in Tuner HAL 1.0.
*
* It is used by the client to ask the hardware resource id for the filter.
*
* @return result Result status of the operation.
* SUCCESS if successful,
* INVALID_STATE if failed for wrong state.
* UNKNOWN_ERROR if failed for other reasons.
* @return filterId the hardware resource Id for the filter.
*/
getId64Bit() generates (Result result, uint64_t filterId);
};

26
tv/tuner/1.1/ITuner.hal Normal file
View File

@@ -0,0 +1,26 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.hardware.tv.tuner@1.1;
import @1.0::ITuner;
import @1.0::Result;
/**
* Top level interface to manage Frontend, Demux and Decrambler hardware
* resources which are needed for Android TV.
*/
interface ITuner extends @1.0::ITuner {};

View File

@@ -0,0 +1,7 @@
{
"presubmit": [
{
"name": "VtsHalTvTunerV1_1TargetTest"
}
]
}

View File

@@ -0,0 +1,52 @@
cc_defaults {
name: "tuner_service_defaults@1.1",
defaults: ["hidl_defaults"],
vendor: true,
relative_install_path: "hw",
srcs: [
"Filter.cpp",
"Frontend.cpp",
"Descrambler.cpp",
"Demux.cpp",
"Dvr.cpp",
"TimeFilter.cpp",
"Tuner.cpp",
"Lnb.cpp",
"service.cpp",
],
compile_multilib: "first",
shared_libs: [
"android.hardware.tv.tuner@1.0",
"android.hardware.tv.tuner@1.1",
"android.hidl.memory@1.0",
"libcutils",
"libfmq",
"libhidlbase",
"libhidlmemory",
"libion",
"liblog",
"libstagefright_foundation",
"libutils",
],
header_libs: [
"media_plugin_headers",
],
}
cc_binary {
name: "android.hardware.tv.tuner@1.1-service",
vintf_fragments: ["android.hardware.tv.tuner@1.1-service.xml"],
defaults: ["tuner_service_defaults@1.1"],
init_rc: ["android.hardware.tv.tuner@1.1-service.rc"],
}
cc_binary {
name: "android.hardware.tv.tuner@1.1-service-lazy",
vintf_fragments: ["android.hardware.tv.tuner@1.1-service-lazy.xml"],
overrides: ["android.hardware.tv.tuner@1.1-service"],
defaults: ["tuner_service_defaults@1.1"],
init_rc: ["android.hardware.tv.tuner@1.1-service-lazy.rc"],
cflags: ["-DLAZY_SERVICE"],
}

View File

@@ -0,0 +1,395 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "android.hardware.tv.tuner@1.1-Demux"
#include "Demux.h"
#include <utils/Log.h>
namespace android {
namespace hardware {
namespace tv {
namespace tuner {
namespace V1_0 {
namespace implementation {
#define WAIT_TIMEOUT 3000000000
Demux::Demux(uint32_t demuxId, sp<Tuner> tuner) {
mDemuxId = demuxId;
mTunerService = tuner;
}
Demux::~Demux() {}
Return<Result> Demux::setFrontendDataSource(uint32_t frontendId) {
ALOGV("%s", __FUNCTION__);
if (mTunerService == nullptr) {
return Result::NOT_INITIALIZED;
}
mFrontend = mTunerService->getFrontendById(frontendId);
if (mFrontend == nullptr) {
return Result::INVALID_STATE;
}
mTunerService->setFrontendAsDemuxSource(frontendId, mDemuxId);
return Result::SUCCESS;
}
Return<void> Demux::openFilter(const DemuxFilterType& type, uint32_t bufferSize,
const sp<IFilterCallback>& cb, openFilter_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
uint64_t filterId;
filterId = ++mLastUsedFilterId;
if (cb == nullptr) {
ALOGW("[Demux] callback can't be null");
_hidl_cb(Result::INVALID_ARGUMENT, new Filter());
return Void();
}
sp<Filter> filter = new Filter(type, filterId, bufferSize, cb, this);
if (!filter->createFilterMQ()) {
_hidl_cb(Result::UNKNOWN_ERROR, filter);
return Void();
}
mFilters[filterId] = filter;
if (filter->isPcrFilter()) {
mPcrFilterIds.insert(filterId);
}
bool result = true;
if (!filter->isRecordFilter()) {
// Only save non-record filters for now. Record filters are saved when the
// IDvr.attacheFilter is called.
mPlaybackFilterIds.insert(filterId);
if (mDvrPlayback != nullptr) {
result = mDvrPlayback->addPlaybackFilter(filterId, filter);
}
}
_hidl_cb(result ? Result::SUCCESS : Result::INVALID_ARGUMENT, filter);
return Void();
}
Return<void> Demux::openTimeFilter(openTimeFilter_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
mTimeFilter = new TimeFilter(this);
_hidl_cb(Result::SUCCESS, mTimeFilter);
return Void();
}
Return<void> Demux::getAvSyncHwId(const sp<IFilter>& filter, getAvSyncHwId_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
uint32_t avSyncHwId = -1;
uint64_t id;
Result status;
sp<V1_1::IFilter> filter_v1_1 = V1_1::IFilter::castFrom(filter);
if (filter_v1_1 != NULL) {
filter_v1_1->getId64Bit([&](Result result, uint64_t filterId) {
id = filterId;
status = result;
});
} else {
filter->getId([&](Result result, uint32_t filterId) {
id = filterId;
status = result;
});
}
if (status != Result::SUCCESS) {
ALOGE("[Demux] Can't get filter Id.");
_hidl_cb(Result::INVALID_STATE, avSyncHwId);
return Void();
}
if (!mFilters[id]->isMediaFilter()) {
ALOGE("[Demux] Given filter is not a media filter.");
_hidl_cb(Result::INVALID_ARGUMENT, avSyncHwId);
return Void();
}
if (!mPcrFilterIds.empty()) {
// Return the lowest pcr filter id in the default implementation as the av sync id
_hidl_cb(Result::SUCCESS, *mPcrFilterIds.begin());
return Void();
}
ALOGE("[Demux] No PCR filter opened.");
_hidl_cb(Result::INVALID_STATE, avSyncHwId);
return Void();
}
Return<void> Demux::getAvSyncTime(AvSyncHwId avSyncHwId, getAvSyncTime_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
uint64_t avSyncTime = -1;
if (mPcrFilterIds.empty()) {
_hidl_cb(Result::INVALID_STATE, avSyncTime);
return Void();
}
if (avSyncHwId != *mPcrFilterIds.begin()) {
_hidl_cb(Result::INVALID_ARGUMENT, avSyncTime);
return Void();
}
_hidl_cb(Result::SUCCESS, avSyncTime);
return Void();
}
Return<Result> Demux::close() {
ALOGV("%s", __FUNCTION__);
set<uint64_t>::iterator it;
for (it = mPlaybackFilterIds.begin(); it != mPlaybackFilterIds.end(); it++) {
mDvrPlayback->removePlaybackFilter(*it);
}
mPlaybackFilterIds.clear();
mRecordFilterIds.clear();
mFilters.clear();
mLastUsedFilterId = -1;
return Result::SUCCESS;
}
Return<void> Demux::openDvr(DvrType type, uint32_t bufferSize, const sp<IDvrCallback>& cb,
openDvr_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
if (cb == nullptr) {
ALOGW("[Demux] DVR callback can't be null");
_hidl_cb(Result::INVALID_ARGUMENT, new Dvr());
return Void();
}
set<uint64_t>::iterator it;
switch (type) {
case DvrType::PLAYBACK:
mDvrPlayback = new Dvr(type, bufferSize, cb, this);
if (!mDvrPlayback->createDvrMQ()) {
_hidl_cb(Result::UNKNOWN_ERROR, mDvrPlayback);
return Void();
}
for (it = mPlaybackFilterIds.begin(); it != mPlaybackFilterIds.end(); it++) {
if (!mDvrPlayback->addPlaybackFilter(*it, mFilters[*it])) {
ALOGE("[Demux] Can't get filter info for DVR playback");
_hidl_cb(Result::UNKNOWN_ERROR, mDvrPlayback);
return Void();
}
}
_hidl_cb(Result::SUCCESS, mDvrPlayback);
return Void();
case DvrType::RECORD:
mDvrRecord = new Dvr(type, bufferSize, cb, this);
if (!mDvrRecord->createDvrMQ()) {
_hidl_cb(Result::UNKNOWN_ERROR, mDvrRecord);
return Void();
}
_hidl_cb(Result::SUCCESS, mDvrRecord);
return Void();
default:
_hidl_cb(Result::INVALID_ARGUMENT, nullptr);
return Void();
}
}
Return<Result> Demux::connectCiCam(uint32_t ciCamId) {
ALOGV("%s", __FUNCTION__);
mCiCamId = ciCamId;
return Result::SUCCESS;
}
Return<Result> Demux::disconnectCiCam() {
ALOGV("%s", __FUNCTION__);
return Result::SUCCESS;
}
Result Demux::removeFilter(uint64_t filterId) {
ALOGV("%s", __FUNCTION__);
if (mDvrPlayback != nullptr) {
mDvrPlayback->removePlaybackFilter(filterId);
}
mPlaybackFilterIds.erase(filterId);
mRecordFilterIds.erase(filterId);
mFilters.erase(filterId);
return Result::SUCCESS;
}
void Demux::startBroadcastTsFilter(vector<uint8_t> data) {
set<uint64_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<uint8_t> data) {
set<uint64_t>::iterator it;
if (DEBUG_DEMUX) {
ALOGW("[Demux] update record filter output");
}
for (it = mRecordFilterIds.begin(); it != mRecordFilterIds.end(); it++) {
mFilters[*it]->updateRecordOutput(data);
}
}
bool Demux::startBroadcastFilterDispatcher() {
set<uint64_t>::iterator it;
// Handle the output data per filter type
for (it = mPlaybackFilterIds.begin(); it != mPlaybackFilterIds.end(); it++) {
if (mFilters[*it]->startFilterHandler() != Result::SUCCESS) {
return false;
}
}
return true;
}
bool Demux::startRecordFilterDispatcher() {
set<uint64_t>::iterator it;
for (it = mRecordFilterIds.begin(); it != mRecordFilterIds.end(); it++) {
if (mFilters[*it]->startRecordFilterHandler() != Result::SUCCESS) {
return false;
}
}
return true;
}
Result Demux::startFilterHandler(uint64_t filterId) {
return mFilters[filterId]->startFilterHandler();
}
void Demux::updateFilterOutput(uint64_t filterId, vector<uint8_t> data) {
mFilters[filterId]->updateFilterOutput(data);
}
void Demux::updateMediaFilterOutput(uint64_t filterId, vector<uint8_t> data, uint64_t pts) {
updateFilterOutput(filterId, data);
mFilters[filterId]->updatePts(pts);
}
uint16_t Demux::getFilterTpid(uint64_t filterId) {
return mFilters[filterId]->getTpid();
}
void Demux::startFrontendInputLoop() {
pthread_create(&mFrontendInputThread, NULL, __threadLoopFrontend, this);
pthread_setname_np(mFrontendInputThread, "frontend_input_thread");
}
void* Demux::__threadLoopFrontend(void* user) {
Demux* const self = static_cast<Demux*>(user);
self->frontendInputThreadLoop();
return 0;
}
void Demux::frontendInputThreadLoop() {
std::lock_guard<std::mutex> lock(mFrontendInputThreadLock);
mFrontendInputThreadRunning = true;
while (mFrontendInputThreadRunning) {
uint32_t efState = 0;
status_t status = mDvrPlayback->getDvrEventFlag()->wait(
static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY), &efState, WAIT_TIMEOUT,
true /* retry on spurious wake */);
if (status != OK) {
ALOGD("[Demux] wait for data ready on the playback FMQ");
continue;
}
if (mDvrPlayback->getSettings().playback().dataFormat == DataFormat::ES) {
if (!mDvrPlayback->processEsDataOnPlayback(true /*isVirtualFrontend*/, mIsRecording)) {
ALOGE("[Demux] playback es data failed to be filtered. Ending thread");
break;
}
}
// Our current implementation filter the data and write it into the filter FMQ immediately
// after the DATA_READY from the VTS/framework
if (!mDvrPlayback->readPlaybackFMQ(true /*isVirtualFrontend*/, mIsRecording) ||
!mDvrPlayback->startFilterDispatcher(true /*isVirtualFrontend*/, mIsRecording)) {
ALOGE("[Demux] playback data failed to be filtered. Ending thread");
break;
}
}
mFrontendInputThreadRunning = false;
ALOGW("[Demux] Frontend Input thread end.");
}
void Demux::stopFrontendInput() {
ALOGD("[Demux] stop frontend on demux");
mKeepFetchingDataFromFrontend = false;
mFrontendInputThreadRunning = false;
std::lock_guard<std::mutex> lock(mFrontendInputThreadLock);
}
void Demux::setIsRecording(bool isRecording) {
mIsRecording = isRecording;
}
bool Demux::attachRecordFilter(uint64_t filterId) {
if (mFilters[filterId] == nullptr || mDvrRecord == nullptr ||
!mFilters[filterId]->isRecordFilter()) {
return false;
}
mRecordFilterIds.insert(filterId);
mFilters[filterId]->attachFilterToRecord(mDvrRecord);
return true;
}
bool Demux::detachRecordFilter(uint64_t filterId) {
if (mFilters[filterId] == nullptr || mDvrRecord == nullptr) {
return false;
}
mRecordFilterIds.erase(filterId);
mFilters[filterId]->detachFilterFromRecord();
return true;
}
} // namespace implementation
} // namespace V1_0
} // namespace tuner
} // namespace tv
} // namespace hardware
} // namespace android

View File

@@ -0,0 +1,197 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_HARDWARE_TV_TUNER_V1_1_DEMUX_H_
#define ANDROID_HARDWARE_TV_TUNER_V1_1_DEMUX_H_
#include <fmq/MessageQueue.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 android {
namespace hardware {
namespace tv {
namespace tuner {
namespace V1_0 {
namespace implementation {
using ::android::hardware::EventFlag;
using ::android::hardware::kSynchronizedReadWrite;
using ::android::hardware::MessageQueue;
using ::android::hardware::MQDescriptorSync;
using FilterMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>;
class Dvr;
class Filter;
class Frontend;
class TimeFilter;
class Tuner;
class Demux : public IDemux {
public:
Demux(uint32_t demuxId, sp<Tuner> tuner);
~Demux();
virtual Return<Result> setFrontendDataSource(uint32_t frontendId) override;
virtual Return<void> openFilter(const DemuxFilterType& type, uint32_t bufferSize,
const sp<IFilterCallback>& cb, openFilter_cb _hidl_cb) override;
virtual Return<void> openTimeFilter(openTimeFilter_cb _hidl_cb) override;
virtual Return<void> getAvSyncHwId(const sp<IFilter>& filter,
getAvSyncHwId_cb _hidl_cb) override;
virtual Return<void> getAvSyncTime(AvSyncHwId avSyncHwId, getAvSyncTime_cb _hidl_cb) override;
virtual Return<Result> close() override;
virtual Return<void> openDvr(DvrType type, uint32_t bufferSize, const sp<IDvrCallback>& cb,
openDvr_cb _hidl_cb) override;
virtual Return<Result> connectCiCam(uint32_t ciCamId) override;
virtual Return<Result> disconnectCiCam() override;
// Functions interacts with Tuner Service
void stopFrontendInput();
Result removeFilter(uint64_t filterId);
bool attachRecordFilter(uint64_t filterId);
bool detachRecordFilter(uint64_t filterId);
Result startFilterHandler(uint64_t filterId);
void updateFilterOutput(uint64_t filterId, vector<uint8_t> data);
void updateMediaFilterOutput(uint64_t filterId, vector<uint8_t> data, uint64_t pts);
uint16_t getFilterTpid(uint64_t filterId);
void setIsRecording(bool isRecording);
void startFrontendInputLoop();
/**
* A dispatcher to read and dispatch input data to all the started filters.
* Each filter handler handles the data filtering/output writing/filterEvent updating.
* Note that recording filters are not included.
*/
bool startBroadcastFilterDispatcher();
void startBroadcastTsFilter(vector<uint8_t> data);
void sendFrontendInputToRecord(vector<uint8_t> data);
bool startRecordFilterDispatcher();
private:
// Tuner service
sp<Tuner> mTunerService;
// Frontend source
sp<Frontend> mFrontend;
// A struct that passes the arguments to a newly created filter thread
struct ThreadArgs {
Demux* user;
uint64_t filterId;
};
static void* __threadLoopFrontend(void* user);
void frontendInputThreadLoop();
/**
* To create a FilterMQ with the next available Filter ID.
* Creating Event Flag at the same time.
* Add the successfully created/saved FilterMQ into the local list.
*
* Return false is any of the above processes fails.
*/
void deleteEventFlag();
bool readDataFromMQ();
uint32_t mDemuxId = -1;
uint32_t mCiCamId;
set<uint64_t> mPcrFilterIds;
/**
* Record the last used filter id. Initial value is -1.
* Filter Id starts with 0.
*/
uint64_t mLastUsedFilterId = -1;
/**
* Record all the used playback filter Ids.
* Any removed filter id should be removed from this set.
*/
set<uint64_t> mPlaybackFilterIds;
/**
* Record all the attached record filter Ids.
* Any removed filter id should be removed from this set.
*/
set<uint64_t> mRecordFilterIds;
/**
* A list of created Filter sp.
* The array number is the filter ID.
*/
std::map<uint64_t, sp<Filter>> mFilters;
/**
* Local reference to the opened Timer Filter instance.
*/
sp<TimeFilter> mTimeFilter;
/**
* Local reference to the opened DVR object.
*/
sp<Dvr> mDvrPlayback;
sp<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 implementation
} // namespace V1_0
} // namespace tuner
} // namespace tv
} // namespace hardware
} // namespace android
#endif // ANDROID_HARDWARE_TV_TUNER_V1_1_DEMUX_H_

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "android.hardware.tv.tuner@1.1-Descrambler"
#include <android/hardware/tv/tuner/1.0/IFrontendCallback.h>
#include <utils/Log.h>
#include "Descrambler.h"
namespace android {
namespace hardware {
namespace tv {
namespace tuner {
namespace V1_0 {
namespace implementation {
Descrambler::Descrambler() {}
Descrambler::~Descrambler() {}
Return<Result> Descrambler::setDemuxSource(uint32_t demuxId) {
ALOGV("%s", __FUNCTION__);
if (mDemuxSet) {
ALOGW("[ WARN ] Descrambler has already been set with a demux id %" PRIu32,
mSourceDemuxId);
return Result::INVALID_STATE;
}
mDemuxSet = true;
mSourceDemuxId = static_cast<uint32_t>(demuxId);
return Result::SUCCESS;
}
Return<Result> Descrambler::setKeyToken(const hidl_vec<uint8_t>& /* keyToken */) {
ALOGV("%s", __FUNCTION__);
return Result::SUCCESS;
}
Return<Result> Descrambler::addPid(const DemuxPid& /* pid */,
const sp<IFilter>& /* optionalSourceFilter */) {
ALOGV("%s", __FUNCTION__);
return Result::SUCCESS;
}
Return<Result> Descrambler::removePid(const DemuxPid& /* pid */,
const sp<IFilter>& /* optionalSourceFilter */) {
ALOGV("%s", __FUNCTION__);
return Result::SUCCESS;
}
Return<Result> Descrambler::close() {
ALOGV("%s", __FUNCTION__);
mDemuxSet = false;
return Result::SUCCESS;
}
} // namespace implementation
} // namespace V1_0
} // namespace tuner
} // namespace tv
} // namespace hardware
} // namespace android

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_HARDWARE_TV_TUNER_V1_1_DESCRAMBLER_H_
#define ANDROID_HARDWARE_TV_TUNER_V1_1_DESCRAMBLER_H_
#include <android/hardware/tv/tuner/1.0/IDescrambler.h>
#include <android/hardware/tv/tuner/1.1/ITuner.h>
#include <inttypes.h>
using namespace std;
namespace android {
namespace hardware {
namespace tv {
namespace tuner {
namespace V1_0 {
namespace implementation {
class Descrambler : public IDescrambler {
public:
Descrambler();
virtual Return<Result> setDemuxSource(uint32_t demuxId) override;
virtual Return<Result> setKeyToken(const hidl_vec<uint8_t>& keyToken) override;
virtual Return<Result> addPid(const DemuxPid& pid,
const sp<IFilter>& optionalSourceFilter) override;
virtual Return<Result> removePid(const DemuxPid& pid,
const sp<IFilter>& optionalSourceFilter) override;
virtual Return<Result> close() override;
private:
virtual ~Descrambler();
uint32_t mSourceDemuxId;
bool mDemuxSet = false;
};
} // namespace implementation
} // namespace V1_0
} // namespace tuner
} // namespace tv
} // namespace hardware
} // namespace android
#endif // ANDROID_HARDWARE_TV_TUNER_V1_DESCRAMBLER_H_

View File

@@ -0,0 +1,498 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "android.hardware.tv.tuner@1.1-Dvr"
#include "Dvr.h"
#include <utils/Log.h>
namespace android {
namespace hardware {
namespace tv {
namespace tuner {
namespace V1_0 {
namespace implementation {
#define WAIT_TIMEOUT 3000000000
Dvr::Dvr() {}
Dvr::Dvr(DvrType type, uint32_t bufferSize, const sp<IDvrCallback>& cb, sp<Demux> demux) {
mType = type;
mBufferSize = bufferSize;
mCallback = cb;
mDemux = demux;
}
Dvr::~Dvr() {}
Return<void> Dvr::getQueueDesc(getQueueDesc_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
_hidl_cb(Result::SUCCESS, *mDvrMQ->getDesc());
return Void();
}
Return<Result> Dvr::configure(const DvrSettings& settings) {
ALOGV("%s", __FUNCTION__);
mDvrSettings = settings;
mDvrConfigured = true;
return Result::SUCCESS;
}
Return<Result> Dvr::attachFilter(const sp<V1_0::IFilter>& filter) {
ALOGV("%s", __FUNCTION__);
uint64_t filterId;
Result status;
sp<V1_1::IFilter> filter_v1_1 = V1_1::IFilter::castFrom(filter);
if (filter_v1_1 != NULL) {
filter_v1_1->getId64Bit([&](Result result, uint64_t id) {
filterId = id;
status = result;
});
} else {
filter->getId([&](Result result, uint32_t id) {
filterId = id;
status = result;
});
}
if (status != Result::SUCCESS) {
return status;
}
// TODO check if the attached filter is a record filter
if (!mDemux->attachRecordFilter(filterId)) {
return Result::INVALID_ARGUMENT;
}
return Result::SUCCESS;
}
Return<Result> Dvr::detachFilter(const sp<V1_0::IFilter>& filter) {
ALOGV("%s", __FUNCTION__);
uint64_t filterId;
Result status;
sp<V1_1::IFilter> filter_v1_1 = V1_1::IFilter::castFrom(filter);
if (filter_v1_1 != NULL) {
filter_v1_1->getId64Bit([&](Result result, uint64_t id) {
filterId = id;
status = result;
});
} else {
filter->getId([&](Result result, uint32_t id) {
filterId = id;
status = result;
});
}
if (status != Result::SUCCESS) {
return status;
}
if (!mDemux->detachRecordFilter(filterId)) {
return Result::INVALID_ARGUMENT;
}
return Result::SUCCESS;
}
Return<Result> Dvr::start() {
ALOGV("%s", __FUNCTION__);
if (!mCallback) {
return Result::NOT_INITIALIZED;
}
if (!mDvrConfigured) {
return Result::INVALID_STATE;
}
if (mType == DvrType::PLAYBACK) {
pthread_create(&mDvrThread, NULL, __threadLoopPlayback, this);
pthread_setname_np(mDvrThread, "playback_waiting_loop");
} else if (mType == DvrType::RECORD) {
mRecordStatus = RecordStatus::DATA_READY;
mDemux->setIsRecording(mType == DvrType::RECORD);
}
// TODO start another thread to send filter status callback to the framework
return Result::SUCCESS;
}
Return<Result> Dvr::stop() {
ALOGV("%s", __FUNCTION__);
mDvrThreadRunning = false;
lock_guard<mutex> lock(mDvrThreadLock);
mIsRecordStarted = false;
mDemux->setIsRecording(false);
return Result::SUCCESS;
}
Return<Result> Dvr::flush() {
ALOGV("%s", __FUNCTION__);
mRecordStatus = RecordStatus::DATA_READY;
return Result::SUCCESS;
}
Return<Result> Dvr::close() {
ALOGV("%s", __FUNCTION__);
return Result::SUCCESS;
}
bool Dvr::createDvrMQ() {
ALOGV("%s", __FUNCTION__);
// Create a synchronized FMQ that supports blocking read/write
unique_ptr<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) != 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);
mDvrThreadRunning = true;
while (mDvrThreadRunning) {
uint32_t efState = 0;
status_t status =
mDvrEventFlag->wait(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY),
&efState, WAIT_TIMEOUT, true /* retry on spurious wake */);
if (status != OK) {
ALOGD("[Dvr] wait for data ready on the playback FMQ");
continue;
}
if (mDvrSettings.playback().dataFormat == DataFormat::ES) {
if (!processEsDataOnPlayback(false /*isVirtualFrontend*/, false /*isRecording*/)) {
ALOGE("[Dvr] playback es data failed to be filtered. Ending thread");
break;
}
maySendPlaybackStatusCallback();
}
// Our current implementation filter the data and write it into the filter FMQ immediately
// after the DATA_READY from the VTS/framework
if (!readPlaybackFMQ(false /*isVirtualFrontend*/, false /*isRecording*/) ||
!startFilterDispatcher(false /*isVirtualFrontend*/, false /*isRecording*/)) {
ALOGE("[Dvr] playback data failed to be filtered. Ending thread");
break;
}
maySendPlaybackStatusCallback();
}
mDvrThreadRunning = false;
ALOGD("[Dvr] playback thread ended.");
}
void Dvr::maySendPlaybackStatusCallback() {
lock_guard<mutex> lock(mPlaybackStatusLock);
int availableToRead = mDvrMQ->availableToRead();
int availableToWrite = mDvrMQ->availableToWrite();
PlaybackStatus newStatus = checkPlaybackStatusChange(availableToWrite, availableToRead,
mDvrSettings.playback().highThreshold,
mDvrSettings.playback().lowThreshold);
if (mPlaybackStatus != newStatus) {
mCallback->onPlaybackStatus(newStatus);
mPlaybackStatus = newStatus;
}
}
PlaybackStatus Dvr::checkPlaybackStatusChange(uint32_t availableToWrite, uint32_t availableToRead,
uint32_t highThreshold, uint32_t lowThreshold) {
if (availableToWrite == 0) {
return PlaybackStatus::SPACE_FULL;
} else if (availableToRead > highThreshold) {
return PlaybackStatus::SPACE_ALMOST_FULL;
} else if (availableToRead < lowThreshold) {
return PlaybackStatus::SPACE_ALMOST_EMPTY;
} else if (availableToRead == 0) {
return PlaybackStatus::SPACE_EMPTY;
}
return mPlaybackStatus;
}
bool Dvr::readPlaybackFMQ(bool isVirtualFrontend, bool isRecording) {
// Read playback data from the input FMQ
int size = mDvrMQ->availableToRead();
int playbackPacketSize = mDvrSettings.playback().packetSize;
vector<uint8_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<uint8_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<uint8_t> frameData;
map<uint64_t, sp<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(dataOutputBuffer.data() + esMeta[i].startIndex, frameData.data(), esMeta[i].len);
// Send to the media filter
if (isVirtualFrontend && isRecording) {
// TODO validate record
mDemux->sendFrontendInputToRecord(frameData);
} else {
for (it = mFilters.begin(); it != mFilters.end(); it++) {
if (pid == mDemux->getFilterTpid(it->first)) {
mDemux->updateMediaFilterOutput(it->first, frameData,
static_cast<uint64_t>(esMeta[i].pts));
startFilterDispatcher(isVirtualFrontend, isRecording);
}
}
}
}
return true;
}
void Dvr::getMetaDataValue(int& index, uint8_t* dataOutputBuffer, int& value) {
index += 2; // Move the pointer across the ":" to the value
while (dataOutputBuffer[index] != ',' && dataOutputBuffer[index] != '\n') {
value = ((dataOutputBuffer[index++] - 48) + value * 10);
}
}
void Dvr::startTpidFilter(vector<uint8_t> data) {
map<uint64_t, sp<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<uint64_t, sp<IFilter>>::iterator it;
// Handle the output data per filter type
for (it = mFilters.begin(); it != mFilters.end(); it++) {
if (mDemux->startFilterHandler(it->first) != Result::SUCCESS) {
return false;
}
}
return true;
}
bool Dvr::writeRecordFMQ(const vector<uint8_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.record().highThreshold,
mDvrSettings.record().lowThreshold);
if (mRecordStatus != newStatus) {
mCallback->onRecordStatus(newStatus);
mRecordStatus = newStatus;
}
}
RecordStatus Dvr::checkRecordStatusChange(uint32_t availableToWrite, uint32_t availableToRead,
uint32_t highThreshold, uint32_t lowThreshold) {
if (availableToWrite == 0) {
return DemuxFilterStatus::OVERFLOW;
} else if (availableToRead > highThreshold) {
return DemuxFilterStatus::HIGH_WATER;
} else if (availableToRead < lowThreshold) {
return DemuxFilterStatus::LOW_WATER;
}
return mRecordStatus;
}
bool Dvr::addPlaybackFilter(uint64_t filterId, sp<IFilter> filter) {
mFilters[filterId] = filter;
return true;
}
bool Dvr::removePlaybackFilter(uint64_t filterId) {
mFilters.erase(filterId);
return true;
}
} // namespace implementation
} // namespace V1_0
} // namespace tuner
} // namespace tv
} // namespace hardware
} // namespace android

167
tv/tuner/1.1/default/Dvr.h Normal file
View File

@@ -0,0 +1,167 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_HARDWARE_TV_TUNER_V1_1_DVR_H_
#define ANDROID_HARDWARE_TV_TUNER_V1_1_DVR_H_
#include <fmq/MessageQueue.h>
#include <math.h>
#include <set>
#include "Demux.h"
#include "Frontend.h"
#include "Tuner.h"
using namespace std;
namespace android {
namespace hardware {
namespace tv {
namespace tuner {
namespace V1_0 {
namespace implementation {
using ::android::hardware::EventFlag;
using ::android::hardware::kSynchronizedReadWrite;
using ::android::hardware::MessageQueue;
using ::android::hardware::MQDescriptorSync;
using DvrMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>;
struct MediaEsMetaData {
bool isAudio;
int startIndex;
int len;
int pts;
};
class Demux;
class Filter;
class Frontend;
class Tuner;
class Dvr : public IDvr {
public:
Dvr();
Dvr(DvrType type, uint32_t bufferSize, const sp<IDvrCallback>& cb, sp<Demux> demux);
~Dvr();
virtual Return<void> getQueueDesc(getQueueDesc_cb _hidl_cb) override;
virtual Return<Result> configure(const DvrSettings& settings) override;
virtual Return<Result> attachFilter(const sp<IFilter>& filter) override;
virtual Return<Result> detachFilter(const sp<IFilter>& filter) override;
virtual Return<Result> start() override;
virtual Return<Result> stop() override;
virtual Return<Result> flush() override;
virtual Return<Result> close() override;
/**
* To create a DvrMQ and its Event Flag.
*
* Return false is any of the above processes fails.
*/
bool createDvrMQ();
void sendBroadcastInputToDvrRecord(vector<uint8_t> byteBuffer);
bool writeRecordFMQ(const std::vector<uint8_t>& data);
bool addPlaybackFilter(uint64_t filterId, sp<IFilter> filter);
bool removePlaybackFilter(uint64_t filterId);
bool readPlaybackFMQ(bool isVirtualFrontend, bool isRecording);
bool processEsDataOnPlayback(bool isVirtualFrontend, bool isRecording);
bool startFilterDispatcher(bool isVirtualFrontend, bool isRecording);
EventFlag* getDvrEventFlag();
DvrSettings getSettings() { return mDvrSettings; }
private:
// Demux service
sp<Demux> mDemux;
DvrType mType;
uint32_t mBufferSize;
sp<IDvrCallback> mCallback;
std::map<uint64_t, sp<IFilter>> mFilters;
void deleteEventFlag();
bool readDataFromMQ();
void getMetaDataValue(int& index, uint8_t* dataOutputBuffer, int& value);
void maySendPlaybackStatusCallback();
void maySendRecordStatusCallback();
PlaybackStatus checkPlaybackStatusChange(uint32_t availableToWrite, uint32_t availableToRead,
uint32_t highThreshold, uint32_t lowThreshold);
RecordStatus checkRecordStatusChange(uint32_t availableToWrite, uint32_t availableToRead,
uint32_t highThreshold, uint32_t lowThreshold);
/**
* A dispatcher to read and dispatch input data to all the started filters.
* Each filter handler handles the data filtering/output writing/filterEvent updating.
*/
void startTpidFilter(vector<uint8_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 implementation
} // namespace V1_0
} // namespace tuner
} // namespace tv
} // namespace hardware
} // namespace android
#endif // ANDROID_HARDWARE_TV_TUNER_V1_1_DVR_H_

View File

@@ -0,0 +1,672 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "android.hardware.tv.tuner@1.1-Filter"
#include "Filter.h"
#include <utils/Log.h>
namespace android {
namespace hardware {
namespace tv {
namespace tuner {
namespace V1_0 {
namespace implementation {
#define WAIT_TIMEOUT 3000000000
Filter::Filter() {}
Filter::Filter(DemuxFilterType type, uint64_t filterId, uint32_t bufferSize,
const sp<IFilterCallback>& cb, sp<Demux> demux) {
mType = type;
mFilterId = filterId;
mBufferSize = bufferSize;
mCallback = cb;
mDemux = demux;
switch (mType.mainType) {
case DemuxFilterMainType::TS:
if (mType.subType.tsFilterType() == DemuxTsFilterType::AUDIO ||
mType.subType.tsFilterType() == DemuxTsFilterType::VIDEO) {
mIsMediaFilter = true;
}
if (mType.subType.tsFilterType() == DemuxTsFilterType::PCR) {
mIsPcrFilter = true;
}
if (mType.subType.tsFilterType() == DemuxTsFilterType::RECORD) {
mIsRecordFilter = true;
}
break;
case DemuxFilterMainType::MMTP:
if (mType.subType.mmtpFilterType() == DemuxMmtpFilterType::AUDIO ||
mType.subType.mmtpFilterType() == DemuxMmtpFilterType::VIDEO) {
mIsMediaFilter = true;
}
if (mType.subType.mmtpFilterType() == DemuxMmtpFilterType::RECORD) {
mIsRecordFilter = true;
}
break;
case DemuxFilterMainType::IP:
break;
case DemuxFilterMainType::TLV:
break;
case DemuxFilterMainType::ALP:
break;
default:
break;
}
}
Filter::~Filter() {}
Return<void> Filter::getId64Bit(getId64Bit_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
_hidl_cb(Result::SUCCESS, mFilterId);
return Void();
}
Return<void> Filter::getId(getId_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
_hidl_cb(Result::SUCCESS, static_cast<uint32_t>(mFilterId));
return Void();
}
Return<Result> Filter::setDataSource(const sp<V1_0::IFilter>& filter) {
ALOGV("%s", __FUNCTION__);
mDataSource = filter;
mIsDataSourceDemux = false;
return Result::SUCCESS;
}
Return<void> Filter::getQueueDesc(getQueueDesc_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
mIsUsingFMQ = true;
_hidl_cb(Result::SUCCESS, *mFilterMQ->getDesc());
return Void();
}
Return<Result> Filter::configure(const DemuxFilterSettings& settings) {
ALOGV("%s", __FUNCTION__);
mFilterSettings = settings;
switch (mType.mainType) {
case DemuxFilterMainType::TS:
mTpid = settings.ts().tpid;
break;
case DemuxFilterMainType::MMTP:
break;
case DemuxFilterMainType::IP:
break;
case DemuxFilterMainType::TLV:
break;
case DemuxFilterMainType::ALP:
break;
default:
break;
}
return Result::SUCCESS;
}
Return<Result> Filter::start() {
ALOGV("%s", __FUNCTION__);
return startFilterLoop();
}
Return<Result> Filter::stop() {
ALOGV("%s", __FUNCTION__);
mFilterThreadRunning = false;
std::lock_guard<std::mutex> lock(mFilterThreadLock);
return Result::SUCCESS;
}
Return<Result> Filter::flush() {
ALOGV("%s", __FUNCTION__);
// temp implementation to flush the FMQ
int size = mFilterMQ->availableToRead();
char* buffer = new char[size];
mFilterMQ->read((unsigned char*)&buffer[0], size);
delete[] buffer;
mFilterStatus = DemuxFilterStatus::DATA_READY;
return Result::SUCCESS;
}
Return<Result> Filter::releaseAvHandle(const hidl_handle& /*avMemory*/, uint64_t avDataId) {
ALOGV("%s", __FUNCTION__);
if (mDataId2Avfd.find(avDataId) == mDataId2Avfd.end()) {
return Result::INVALID_ARGUMENT;
}
::close(mDataId2Avfd[avDataId]);
return Result::SUCCESS;
}
Return<Result> Filter::close() {
ALOGV("%s", __FUNCTION__);
return mDemux->removeFilter(mFilterId);
}
bool Filter::createFilterMQ() {
ALOGV("%s", __FUNCTION__);
// Create a synchronized FMQ that supports blocking read/write
std::unique_ptr<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(), &mFilterEventFlag) != OK) {
return false;
}
return true;
}
Result Filter::startFilterLoop() {
pthread_create(&mFilterThread, NULL, __threadLoopFilter, this);
pthread_setname_np(mFilterThread, "filter_waiting_loop");
return Result::SUCCESS;
}
void* Filter::__threadLoopFilter(void* user) {
Filter* const self = static_cast<Filter*>(user);
self->filterThreadLoop();
return 0;
}
void Filter::filterThreadLoop() {
ALOGD("[Filter] filter %" PRIu64 " threadLoop start.", mFilterId);
std::lock_guard<std::mutex> lock(mFilterThreadLock);
mFilterThreadRunning = true;
// For the first time of filter output, implementation needs to send the filter
// Event Callback without waiting for the DATA_CONSUMED to init the process.
while (mFilterThreadRunning) {
if (mFilterEvent.events.size() == 0) {
if (DEBUG_FILTER) {
ALOGD("[Filter] wait for filter data output.");
}
usleep(1000 * 1000);
continue;
}
// After successfully write, send a callback and wait for the read to be done
mCallback->onFilterEvent(mFilterEvent);
freeAvHandle();
mFilterEvent.events.resize(0);
mFilterStatus = DemuxFilterStatus::DATA_READY;
if (mCallback == nullptr) {
ALOGD("[Filter] filter %" PRIu64 " does not hava callback. Ending thread", mFilterId);
break;
}
mCallback->onFilterStatus(mFilterStatus);
break;
}
while (mFilterThreadRunning) {
uint32_t efState = 0;
// We do not wait for the last round of written data to be read to finish the thread
// because the VTS can verify the reading itself.
for (int i = 0; i < SECTION_WRITE_COUNT; i++) {
while (mFilterThreadRunning && mIsUsingFMQ) {
status_t status = mFilterEventFlag->wait(
static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_CONSUMED), &efState,
WAIT_TIMEOUT, true /* retry on spurious wake */);
if (status != OK) {
ALOGD("[Filter] wait for data consumed");
continue;
}
break;
}
maySendFilterStatusCallback();
while (mFilterThreadRunning) {
std::lock_guard<std::mutex> lock(mFilterEventLock);
if (mFilterEvent.events.size() == 0) {
continue;
}
// After successfully write, send a callback and wait for the read to be done
mCallback->onFilterEvent(mFilterEvent);
mFilterEvent.events.resize(0);
break;
}
// We do not wait for the last read to be done
// VTS can verify the read result itself.
if (i == SECTION_WRITE_COUNT - 1) {
ALOGD("[Filter] filter %" PRIu64 " writing done. Ending thread", mFilterId);
break;
}
}
mFilterThreadRunning = false;
}
ALOGD("[Filter] filter thread ended.");
}
void Filter::freeAvHandle() {
if (!mIsMediaFilter) {
return;
}
for (int i = 0; i < mFilterEvent.events.size(); i++) {
::close(mFilterEvent.events[i].media().avMemory.getNativeHandle()->data[0]);
native_handle_close(mFilterEvent.events[i].media().avMemory.getNativeHandle());
}
}
void Filter::maySendFilterStatusCallback() {
if (!mIsUsingFMQ) {
return;
}
std::lock_guard<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) {
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<uint8_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<uint8_t> data) {
std::lock_guard<std::mutex> lock(mRecordFilterOutputLock);
mRecordFilterOutput.insert(mRecordFilterOutput.end(), data.begin(), data.end());
}
Result Filter::startFilterHandler() {
std::lock_guard<std::mutex> lock(mFilterOutputLock);
switch (mType.mainType) {
case DemuxFilterMainType::TS:
switch (mType.subType.tsFilterType()) {
case DemuxTsFilterType::UNDEFINED:
break;
case DemuxTsFilterType::SECTION:
startSectionFilterHandler();
break;
case DemuxTsFilterType::PES:
startPesFilterHandler();
break;
case DemuxTsFilterType::TS:
startTsFilterHandler();
break;
case DemuxTsFilterType::AUDIO:
case DemuxTsFilterType::VIDEO:
startMediaFilterHandler();
break;
case DemuxTsFilterType::PCR:
startPcrFilterHandler();
break;
case DemuxTsFilterType::TEMI:
startTemiFilterHandler();
break;
default:
break;
}
break;
case DemuxFilterMainType::MMTP:
/*mmtpSettings*/
break;
case DemuxFilterMainType::IP:
/*ipSettings*/
break;
case DemuxFilterMainType::TLV:
/*tlvSettings*/
break;
case DemuxFilterMainType::ALP:
/*alpSettings*/
break;
default:
break;
}
return Result::SUCCESS;
}
Result Filter::startSectionFilterHandler() {
if (mFilterOutput.empty()) {
return Result::SUCCESS;
}
if (!writeSectionsAndCreateEvent(mFilterOutput)) {
ALOGD("[Filter] filter %" PRIu64 " fails to write into FMQ. Ending thread", mFilterId);
return Result::UNKNOWN_ERROR;
}
mFilterOutput.clear();
return Result::SUCCESS;
}
Result Filter::startPesFilterHandler() {
std::lock_guard<std::mutex> lock(mFilterEventLock);
if (mFilterOutput.empty()) {
return Result::SUCCESS;
}
for (int i = 0; i < mFilterOutput.size(); i += 188) {
if (mPesSizeLeft == 0) {
uint32_t prefix = (mFilterOutput[i + 4] << 16) | (mFilterOutput[i + 5] << 8) |
mFilterOutput[i + 6];
if (DEBUG_FILTER) {
ALOGD("[Filter] prefix %d", prefix);
}
if (prefix == 0x000001) {
// TODO handle mulptiple Pes filters
mPesSizeLeft = (mFilterOutput[i + 8] << 8) | mFilterOutput[i + 9];
mPesSizeLeft += 6;
if (DEBUG_FILTER) {
ALOGD("[Filter] pes data length %d", mPesSizeLeft);
}
} else {
continue;
}
}
int endPoint = min(184, mPesSizeLeft);
// append data and check size
vector<uint8_t>::const_iterator first = mFilterOutput.begin() + i + 4;
vector<uint8_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 Result::INVALID_STATE;
}
maySendFilterStatusCallback();
DemuxFilterPesEvent pesEvent;
pesEvent = {
// temp dump meta data
.streamId = mPesOutput[3],
.dataLength = static_cast<uint16_t>(mPesOutput.size()),
};
if (DEBUG_FILTER) {
ALOGD("[Filter] assembled pes data length %d", pesEvent.dataLength);
}
int size = mFilterEvent.events.size();
mFilterEvent.events.resize(size + 1);
mFilterEvent.events[size].pes(pesEvent);
mPesOutput.clear();
}
mFilterOutput.clear();
return Result::SUCCESS;
}
Result Filter::startTsFilterHandler() {
// TODO handle starting TS filter
return Result::SUCCESS;
}
Result Filter::startMediaFilterHandler() {
std::lock_guard<std::mutex> lock(mFilterEventLock);
if (mFilterOutput.empty()) {
return Result::SUCCESS;
}
if (mPts) {
return createMediaFilterEventWithIon(mFilterOutput);
}
for (int i = 0; i < mFilterOutput.size(); i += 188) {
if (mPesSizeLeft == 0) {
uint32_t prefix = (mFilterOutput[i + 4] << 16) | (mFilterOutput[i + 5] << 8) |
mFilterOutput[i + 6];
if (DEBUG_FILTER) {
ALOGD("[Filter] prefix %d", prefix);
}
if (prefix == 0x000001) {
// TODO handle mulptiple Pes filters
mPesSizeLeft = (mFilterOutput[i + 8] << 8) | mFilterOutput[i + 9];
mPesSizeLeft += 6;
if (DEBUG_FILTER) {
ALOGD("[Filter] pes data length %d", mPesSizeLeft);
}
} else {
continue;
}
}
int endPoint = min(184, mPesSizeLeft);
// append data and check size
vector<uint8_t>::const_iterator first = mFilterOutput.begin() + i + 4;
vector<uint8_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;
}
createMediaFilterEventWithIon(mPesOutput);
}
mFilterOutput.clear();
return Result::SUCCESS;
}
Result Filter::createMediaFilterEventWithIon(vector<uint8_t> output) {
int av_fd = createAvIonFd(output.size());
if (av_fd == -1) {
return Result::UNKNOWN_ERROR;
}
// copy the filtered data to the buffer
uint8_t* avBuffer = getIonBuffer(av_fd, output.size());
if (avBuffer == NULL) {
return Result::UNKNOWN_ERROR;
}
memcpy(avBuffer, output.data(), output.size() * sizeof(uint8_t));
native_handle_t* nativeHandle = createNativeHandle(av_fd);
if (nativeHandle == NULL) {
return Result::UNKNOWN_ERROR;
}
hidl_handle handle;
handle.setTo(nativeHandle, /*shouldOwn=*/true);
// Create a dataId and add a <dataId, av_fd> pair into the dataId2Avfd map
uint64_t dataId = mLastUsedDataId++ /*createdUID*/;
mDataId2Avfd[dataId] = dup(av_fd);
// Create mediaEvent and send callback
DemuxFilterMediaEvent mediaEvent;
mediaEvent = {
.avMemory = std::move(handle),
.dataLength = static_cast<uint32_t>(output.size()),
.avDataId = dataId,
};
if (mPts) {
mediaEvent.pts = mPts;
mPts = 0;
}
int size = mFilterEvent.events.size();
mFilterEvent.events.resize(size + 1);
mFilterEvent.events[size].media(mediaEvent);
// Clear and log
output.clear();
mAvBufferCopyCount = 0;
::close(av_fd);
if (DEBUG_FILTER) {
ALOGD("[Filter] av data length %d", mediaEvent.dataLength);
}
return Result::SUCCESS;
}
Result Filter::startRecordFilterHandler() {
std::lock_guard<std::mutex> lock(mRecordFilterOutputLock);
if (mRecordFilterOutput.empty()) {
return Result::SUCCESS;
}
if (mDvr == nullptr || !mDvr->writeRecordFMQ(mRecordFilterOutput)) {
ALOGD("[Filter] dvr fails to write into record FMQ.");
return Result::UNKNOWN_ERROR;
}
mRecordFilterOutput.clear();
return Result::SUCCESS;
}
Result Filter::startPcrFilterHandler() {
// TODO handle starting PCR filter
return Result::SUCCESS;
}
Result Filter::startTemiFilterHandler() {
// TODO handle starting TEMI filter
return Result::SUCCESS;
}
bool Filter::writeSectionsAndCreateEvent(vector<uint8_t> data) {
// TODO check how many sections has been read
ALOGD("[Filter] section handler");
std::lock_guard<std::mutex> lock(mFilterEventLock);
if (!writeDataToFilterMQ(data)) {
return false;
}
int size = mFilterEvent.events.size();
mFilterEvent.events.resize(size + 1);
DemuxFilterSectionEvent secEvent;
secEvent = {
// temp dump meta data
.tableId = 0,
.version = 1,
.sectionNum = 1,
.dataLength = static_cast<uint16_t>(data.size()),
};
mFilterEvent.events[size].section(secEvent);
return true;
}
bool Filter::writeDataToFilterMQ(const std::vector<uint8_t>& data) {
std::lock_guard<std::mutex> lock(mWriteLock);
if (mFilterMQ->write(data.data(), data.size())) {
return true;
}
return false;
}
void Filter::attachFilterToRecord(const sp<Dvr> dvr) {
mDvr = dvr;
}
void Filter::detachFilterFromRecord() {
mDvr = nullptr;
}
int Filter::createAvIonFd(int size) {
// Create an ion fd and allocate an av fd mapped to a buffer to it.
int ion_fd = ion_open();
if (ion_fd == -1) {
ALOGE("[Filter] Failed to open ion fd %d", errno);
return -1;
}
int av_fd = -1;
ion_alloc_fd(dup(ion_fd), size, 0 /*align*/, ION_HEAP_SYSTEM_MASK, 0 /*flags*/, &av_fd);
if (av_fd == -1) {
ALOGE("[Filter] Failed to create av fd %d", errno);
return -1;
}
return av_fd;
}
uint8_t* Filter::getIonBuffer(int fd, int size) {
uint8_t* avBuf = static_cast<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) {
// Create a native handle to pass the av fd via the callback event.
native_handle_t* nativeHandle = native_handle_create(/*numFd*/ 1, 0);
if (nativeHandle == NULL) {
ALOGE("[Filter] Failed to create native_handle %d", errno);
return NULL;
}
nativeHandle->data[0] = dup(fd);
return nativeHandle;
}
} // namespace implementation
} // namespace V1_0
} // namespace tuner
} // namespace tv
} // namespace hardware
} // namespace android

View File

@@ -0,0 +1,214 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_HARDWARE_TV_TUNER_V1_1_FILTER_H_
#define ANDROID_HARDWARE_TV_TUNER_V1_1_FILTER_H_
#include <android/hardware/tv/tuner/1.1/IFilter.h>
#include <fmq/MessageQueue.h>
#include <inttypes.h>
#include <ion/ion.h>
#include <math.h>
#include <set>
#include "Demux.h"
#include "Dvr.h"
#include "Frontend.h"
using namespace std;
namespace android {
namespace hardware {
namespace tv {
namespace tuner {
namespace V1_0 {
namespace implementation {
using ::android::hardware::EventFlag;
using ::android::hardware::kSynchronizedReadWrite;
using ::android::hardware::MessageQueue;
using ::android::hardware::MQDescriptorSync;
using FilterMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>;
class Demux;
class Dvr;
class Filter : public V1_1::IFilter {
public:
Filter();
Filter(DemuxFilterType type, uint64_t filterId, uint32_t bufferSize,
const sp<IFilterCallback>& cb, sp<Demux> demux);
~Filter();
virtual Return<void> getId64Bit(getId64Bit_cb _hidl_cb) override;
virtual Return<void> getId(getId_cb _hidl_cb) override;
virtual Return<Result> setDataSource(const sp<V1_0::IFilter>& filter) override;
virtual Return<void> getQueueDesc(getQueueDesc_cb _hidl_cb) override;
virtual Return<Result> configure(const DemuxFilterSettings& settings) override;
virtual Return<Result> start() override;
virtual Return<Result> stop() override;
virtual Return<Result> flush() override;
virtual Return<Result> releaseAvHandle(const hidl_handle& avMemory, uint64_t avDataId) override;
virtual Return<Result> close() override;
/**
* To create a FilterMQ and its Event Flag.
*
* Return false is any of the above processes fails.
*/
bool createFilterMQ();
uint16_t getTpid();
void updateFilterOutput(vector<uint8_t> data);
void updateRecordOutput(vector<uint8_t> data);
void updatePts(uint64_t pts);
Result startFilterHandler();
Result startRecordFilterHandler();
void attachFilterToRecord(const sp<Dvr> dvr);
void detachFilterFromRecord();
void freeAvHandle();
bool isMediaFilter() { return mIsMediaFilter; };
bool isPcrFilter() { return mIsPcrFilter; };
bool isRecordFilter() { return mIsRecordFilter; };
private:
// Tuner service
sp<Demux> mDemux;
// Dvr reference once the filter is attached to any
sp<Dvr> mDvr = nullptr;
/**
* Filter callbacks used on filter events or FMQ status
*/
sp<IFilterCallback> mCallback;
uint64_t mFilterId;
uint32_t mBufferSize;
DemuxFilterType mType;
bool mIsMediaFilter = false;
bool mIsPcrFilter = false;
bool mIsRecordFilter = false;
DemuxFilterSettings mFilterSettings;
uint16_t mTpid;
sp<V1_0::IFilter> mDataSource;
bool mIsDataSourceDemux = true;
vector<uint8_t> mFilterOutput;
vector<uint8_t> mRecordFilterOutput;
uint64_t mPts = 0;
unique_ptr<FilterMQ> mFilterMQ;
bool mIsUsingFMQ = false;
EventFlag* mFilterEventFlag;
DemuxFilterEvent mFilterEvent;
// Thread handlers
pthread_t mFilterThread;
// FMQ status local records
DemuxFilterStatus mFilterStatus;
/**
* If a specific filter's writing loop is still running
*/
bool mFilterThreadRunning;
bool mKeepFetchingDataFromFrontend;
/**
* How many times a filter should write
* TODO make this dynamic/random/can take as a parameter
*/
const uint16_t SECTION_WRITE_COUNT = 10;
bool DEBUG_FILTER = false;
/**
* Filter handlers to handle the data filtering.
* They are also responsible to write the filtered output into the filter FMQ
* and update the filterEvent bound with the same filterId.
*/
Result startSectionFilterHandler();
Result startPesFilterHandler();
Result startTsFilterHandler();
Result startMediaFilterHandler();
Result startPcrFilterHandler();
Result startTemiFilterHandler();
Result startFilterLoop();
void deleteEventFlag();
bool writeDataToFilterMQ(const std::vector<uint8_t>& data);
bool readDataFromMQ();
bool writeSectionsAndCreateEvent(vector<uint8_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.
*/
void startTsFilter(vector<uint8_t> data);
bool startFilterDispatcher();
static void* __threadLoopFilter(void* user);
void filterThreadLoop();
int createAvIonFd(int size);
uint8_t* getIonBuffer(int fd, int size);
native_handle_t* createNativeHandle(int fd);
Result createMediaFilterEventWithIon(vector<uint8_t> output);
/**
* Lock to protect writes to the FMQs
*/
std::mutex mWriteLock;
/**
* Lock to protect writes to the filter event
*/
// TODO make each filter separate event lock
std::mutex mFilterEventLock;
/**
* Lock to protect writes to the input status
*/
std::mutex mFilterStatusLock;
std::mutex mFilterThreadLock;
std::mutex mFilterOutputLock;
std::mutex mRecordFilterOutputLock;
// temp handle single PES filter
// TODO handle mulptiple Pes filters
int mPesSizeLeft = 0;
vector<uint8_t> mPesOutput;
// A map from data id to ion handle
std::map<uint64_t, int> mDataId2Avfd;
uint64_t mLastUsedDataId = 1;
int mAvBufferCopyCount = 0;
};
} // namespace implementation
} // namespace V1_0
} // namespace tuner
} // namespace tv
} // namespace hardware
} // namespace android
#endif // ANDROID_HARDWARE_TV_TUNER_V1_1_FILTER_H_

View File

@@ -0,0 +1,284 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "android.hardware.tv.tuner@1.1-Frontend"
#include "Frontend.h"
#include <android/hardware/tv/tuner/1.0/IFrontendCallback.h>
#include <utils/Log.h>
namespace android {
namespace hardware {
namespace tv {
namespace tuner {
namespace V1_0 {
namespace implementation {
Frontend::Frontend(FrontendType type, FrontendId id, sp<Tuner> tuner) {
mType = type;
mId = id;
mTunerService = tuner;
// Init callback to nullptr
mCallback = nullptr;
}
Frontend::~Frontend() {}
Return<Result> Frontend::close() {
ALOGV("%s", __FUNCTION__);
// Reset callback
mCallback = nullptr;
mIsLocked = false;
return Result::SUCCESS;
}
Return<Result> Frontend::setCallback(const sp<IFrontendCallback>& callback) {
ALOGV("%s", __FUNCTION__);
if (callback == nullptr) {
ALOGW("[ WARN ] Set Frontend callback with nullptr");
return Result::INVALID_ARGUMENT;
}
mCallback = callback;
return Result::SUCCESS;
}
Return<Result> Frontend::tune(const FrontendSettings& /* settings */) {
ALOGV("%s", __FUNCTION__);
if (mCallback == nullptr) {
ALOGW("[ WARN ] Frontend callback is not set when tune");
return Result::INVALID_STATE;
}
mTunerService->frontendStartTune(mId);
mCallback->onEvent(FrontendEventType::LOCKED);
mIsLocked = true;
return Result::SUCCESS;
}
Return<Result> Frontend::stopTune() {
ALOGV("%s", __FUNCTION__);
mTunerService->frontendStopTune(mId);
mIsLocked = false;
return Result::SUCCESS;
}
Return<Result> Frontend::scan(const FrontendSettings& settings, FrontendScanType type) {
ALOGV("%s", __FUNCTION__);
if (mType == FrontendType::ATSC) {
FrontendScanMessage msg;
msg.isLocked(true);
mCallback->onScanMessage(FrontendScanMessageType::LOCKED, msg);
mIsLocked = true;
return Result::SUCCESS;
}
if (mType != FrontendType::DVBT) {
return Result::UNAVAILABLE;
}
FrontendScanMessage msg;
if (mIsLocked) {
msg.isEnd(true);
mCallback->onScanMessage(FrontendScanMessageType::END, msg);
return Result::SUCCESS;
}
uint32_t frequency = settings.dvbt().frequency;
if (type == FrontendScanType::SCAN_BLIND) {
frequency += 100;
}
msg.frequencies({frequency});
mCallback->onScanMessage(FrontendScanMessageType::FREQUENCY, msg);
msg.isLocked(true);
mCallback->onScanMessage(FrontendScanMessageType::LOCKED, msg);
mIsLocked = true;
return Result::SUCCESS;
}
Return<Result> Frontend::stopScan() {
ALOGV("%s", __FUNCTION__);
mIsLocked = false;
return Result::SUCCESS;
}
Return<void> Frontend::getStatus(const hidl_vec<FrontendStatusType>& statusTypes,
getStatus_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
vector<FrontendStatus> statuses;
for (int i = 0; i < statusTypes.size(); i++) {
FrontendStatusType type = statusTypes[i];
FrontendStatus status;
// assign randomly selected values for testing.
switch (type) {
case FrontendStatusType::DEMOD_LOCK: {
status.isDemodLocked(true);
break;
}
case FrontendStatusType::SNR: {
status.snr(221);
break;
}
case FrontendStatusType::BER: {
status.ber(1);
break;
}
case FrontendStatusType::PER: {
status.per(2);
break;
}
case FrontendStatusType::PRE_BER: {
status.preBer(3);
break;
}
case FrontendStatusType::SIGNAL_QUALITY: {
status.signalQuality(4);
break;
}
case FrontendStatusType::SIGNAL_STRENGTH: {
status.signalStrength(5);
break;
}
case FrontendStatusType::SYMBOL_RATE: {
status.symbolRate(6);
break;
}
case FrontendStatusType::FEC: {
status.innerFec(FrontendInnerFec::FEC_2_9); // value = 1 << 7
break;
}
case FrontendStatusType::MODULATION: {
FrontendModulationStatus modulationStatus;
modulationStatus.isdbt(FrontendIsdbtModulation::MOD_16QAM); // value = 1 << 3
status.modulation(modulationStatus);
break;
}
case FrontendStatusType::SPECTRAL: {
status.inversion(FrontendDvbcSpectralInversion::NORMAL);
break;
}
case FrontendStatusType::LNB_VOLTAGE: {
status.lnbVoltage(LnbVoltage::VOLTAGE_5V);
break;
}
case FrontendStatusType::PLP_ID: {
status.plpId(101); // type uint8_t
break;
}
case FrontendStatusType::EWBS: {
status.isEWBS(false);
break;
}
case FrontendStatusType::AGC: {
status.agc(7);
break;
}
case FrontendStatusType::LNA: {
status.isLnaOn(false);
break;
}
case FrontendStatusType::LAYER_ERROR: {
vector<bool> v = {false, true, true};
status.isLayerError(v);
break;
}
case FrontendStatusType::MER: {
status.mer(8);
break;
}
case FrontendStatusType::FREQ_OFFSET: {
status.freqOffset(9);
break;
}
case FrontendStatusType::HIERARCHY: {
status.hierarchy(FrontendDvbtHierarchy::HIERARCHY_1_NATIVE);
break;
}
case FrontendStatusType::RF_LOCK: {
status.isRfLocked(false);
break;
}
case FrontendStatusType::ATSC3_PLP_INFO: {
vector<FrontendStatusAtsc3PlpInfo> v;
FrontendStatusAtsc3PlpInfo info1{
.plpId = 3,
.isLocked = false,
.uec = 313,
};
FrontendStatusAtsc3PlpInfo info2{
.plpId = 5,
.isLocked = true,
.uec = 515,
};
v.push_back(info1);
v.push_back(info2);
status.plpInfo(v);
break;
}
default: {
continue;
}
}
statuses.push_back(status);
}
_hidl_cb(Result::SUCCESS, statuses);
return Void();
}
Return<Result> Frontend::setLna(bool /* bEnable */) {
ALOGV("%s", __FUNCTION__);
return Result::SUCCESS;
}
Return<Result> Frontend::setLnb(uint32_t /* lnb */) {
ALOGV("%s", __FUNCTION__);
if (!supportsSatellite()) {
return Result::INVALID_STATE;
}
return Result::SUCCESS;
}
FrontendType Frontend::getFrontendType() {
return mType;
}
FrontendId Frontend::getFrontendId() {
return mId;
}
bool Frontend::supportsSatellite() {
return mType == FrontendType::DVBS || mType == FrontendType::ISDBS ||
mType == FrontendType::ISDBS3;
}
bool Frontend::isLocked() {
return mIsLocked;
}
} // namespace implementation
} // namespace V1_0
} // namespace tuner
} // namespace tv
} // namespace hardware
} // namespace android

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_HARDWARE_TV_TUNER_V1_1_FRONTEND_H_
#define ANDROID_HARDWARE_TV_TUNER_V1_1_FRONTEND_H_
#include <fstream>
#include <iostream>
#include "Tuner.h"
using namespace std;
namespace android {
namespace hardware {
namespace tv {
namespace tuner {
namespace V1_0 {
namespace implementation {
class Tuner;
class Frontend : public IFrontend {
public:
Frontend(FrontendType type, FrontendId id, sp<Tuner> tuner);
virtual Return<Result> close() override;
virtual Return<Result> setCallback(const sp<IFrontendCallback>& callback) override;
virtual Return<Result> tune(const FrontendSettings& settings) override;
virtual Return<Result> stopTune() override;
virtual Return<Result> scan(const FrontendSettings& settings, FrontendScanType type) override;
virtual Return<Result> stopScan() override;
virtual Return<void> getStatus(const hidl_vec<FrontendStatusType>& statusTypes,
getStatus_cb _hidl_cb) override;
virtual Return<Result> setLna(bool bEnable) override;
virtual Return<Result> setLnb(uint32_t lnb) override;
FrontendType getFrontendType();
FrontendId getFrontendId();
string getSourceFile();
bool isLocked();
private:
virtual ~Frontend();
bool supportsSatellite();
sp<IFrontendCallback> mCallback;
sp<Tuner> mTunerService;
FrontendType mType = FrontendType::UNDEFINED;
FrontendId mId = 0;
bool mIsLocked = false;
std::ifstream mFrontendData;
};
} // namespace implementation
} // namespace V1_0
} // namespace tuner
} // namespace tv
} // namespace hardware
} // namespace android
#endif // ANDROID_HARDWARE_TV_TUNER_V1_1_FRONTEND_H_

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "android.hardware.tv.tuner@1.1-Lnb"
#include "Lnb.h"
#include <utils/Log.h>
namespace android {
namespace hardware {
namespace tv {
namespace tuner {
namespace V1_0 {
namespace implementation {
Lnb::Lnb() {}
Lnb::Lnb(int id) {
mId = id;
}
Lnb::~Lnb() {}
Return<Result> Lnb::setCallback(const sp<ILnbCallback>& /* callback */) {
ALOGV("%s", __FUNCTION__);
return Result::SUCCESS;
}
Return<Result> Lnb::setVoltage(LnbVoltage /* voltage */) {
ALOGV("%s", __FUNCTION__);
return Result::SUCCESS;
}
Return<Result> Lnb::setTone(LnbTone /* tone */) {
ALOGV("%s", __FUNCTION__);
return Result::SUCCESS;
}
Return<Result> Lnb::setSatellitePosition(LnbPosition /* position */) {
ALOGV("%s", __FUNCTION__);
return Result::SUCCESS;
}
Return<Result> Lnb::sendDiseqcMessage(const hidl_vec<uint8_t>& /* diseqcMessage */) {
ALOGV("%s", __FUNCTION__);
return Result::SUCCESS;
}
Return<Result> Lnb::close() {
ALOGV("%s", __FUNCTION__);
return Result::SUCCESS;
}
int Lnb::getId() {
return mId;
}
} // namespace implementation
} // namespace V1_0
} // namespace tuner
} // namespace tv
} // namespace hardware
} // namespace android

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_HARDWARE_TV_TUNER_V1_1_LNB_H_
#define ANDROID_HARDWARE_TV_TUNER_V1_1_LNB_H_
#include <android/hardware/tv/tuner/1.0/ILnb.h>
#include <android/hardware/tv/tuner/1.1/ITuner.h>
using namespace std;
namespace android {
namespace hardware {
namespace tv {
namespace tuner {
namespace V1_0 {
namespace implementation {
class Lnb : public ILnb {
public:
Lnb();
Lnb(int id);
virtual Return<Result> setCallback(const sp<ILnbCallback>& callback) override;
virtual Return<Result> setVoltage(LnbVoltage voltage) override;
virtual Return<Result> setTone(LnbTone tone) override;
virtual Return<Result> setSatellitePosition(LnbPosition position) override;
virtual Return<Result> sendDiseqcMessage(const hidl_vec<uint8_t>& diseqcMessage) override;
virtual Return<Result> close() override;
int getId();
private:
int mId;
virtual ~Lnb();
};
} // namespace implementation
} // namespace V1_0
} // namespace tuner
} // namespace tv
} // namespace hardware
} // namespace android
#endif // ANDROID_HARDWARE_TV_TUNER_V1_1_LNB_H_

View File

@@ -0,0 +1,4 @@
nchalko@google.com
amyjojo@google.com
shubang@google.com
quxiangfang@google.com

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "android.hardware.tv.tuner@1.1-TimeFilter"
#include "TimeFilter.h"
#include <utils/Log.h>
namespace android {
namespace hardware {
namespace tv {
namespace tuner {
namespace V1_0 {
namespace implementation {
TimeFilter::TimeFilter() {}
TimeFilter::TimeFilter(sp<Demux> demux) {
mDemux = demux;
}
TimeFilter::~TimeFilter() {}
Return<Result> TimeFilter::setTimeStamp(uint64_t timeStamp) {
ALOGV("%s", __FUNCTION__);
if (timeStamp == INVALID_TIME_STAMP) {
return Result::INVALID_ARGUMENT;
}
mTimeStamp = timeStamp;
mBeginTime = time(NULL);
return Result::SUCCESS;
}
Return<Result> TimeFilter::clearTimeStamp() {
ALOGV("%s", __FUNCTION__);
mTimeStamp = INVALID_TIME_STAMP;
return Result::SUCCESS;
}
Return<void> TimeFilter::getTimeStamp(getTimeStamp_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
if (mTimeStamp == INVALID_TIME_STAMP) {
_hidl_cb(Result::INVALID_STATE, mTimeStamp);
}
uint64_t currentTimeStamp = mTimeStamp + difftime(time(NULL), mBeginTime) * 900000;
_hidl_cb(Result::SUCCESS, currentTimeStamp);
return Void();
}
Return<void> TimeFilter::getSourceTime(getSourceTime_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
uint64_t time = 0;
_hidl_cb(Result::SUCCESS, time);
return Void();
}
Return<Result> TimeFilter::close() {
ALOGV("%s", __FUNCTION__);
mTimeStamp = INVALID_TIME_STAMP;
return Result::SUCCESS;
}
} // namespace implementation
} // namespace V1_0
} // namespace tuner
} // namespace tv
} // namespace hardware
} // namespace android

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_HARDWARE_TV_TUNER_V1_1_TIMEFILTER_H_
#define ANDROID_HARDWARE_TV_TUNER_V1_1_TIMEFILTER_H_
#include <android/hardware/tv/tuner/1.0/ITimeFilter.h>
#include "Demux.h"
#include "time.h"
using namespace std;
namespace android {
namespace hardware {
namespace tv {
namespace tuner {
namespace V1_0 {
namespace implementation {
using FilterMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>;
#define INVALID_TIME_STAMP -1
class Demux;
class TimeFilter : public ITimeFilter {
public:
TimeFilter();
TimeFilter(sp<Demux> demux);
~TimeFilter();
virtual Return<Result> setTimeStamp(uint64_t timeStamp) override;
virtual Return<Result> clearTimeStamp() override;
virtual Return<void> getTimeStamp(getTimeStamp_cb _hidl_cb) override;
virtual Return<void> getSourceTime(getSourceTime_cb _hidl_cb) override;
virtual Return<Result> close() override;
private:
sp<Demux> mDemux;
uint64_t mTimeStamp = INVALID_TIME_STAMP;
time_t mBeginTime;
};
} // namespace implementation
} // namespace V1_0
} // namespace tuner
} // namespace tv
} // namespace hardware
} // namespace android
#endif // ANDROID_HARDWARE_TV_TUNER_V1_1_TIMEFILTER_H_

View File

@@ -0,0 +1,259 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "android.hardware.tv.tuner@1.1-Tuner"
#include "Tuner.h"
#include <utils/Log.h>
#include "Demux.h"
#include "Descrambler.h"
#include "Frontend.h"
#include "Lnb.h"
namespace android {
namespace hardware {
namespace tv {
namespace tuner {
namespace V1_0 {
namespace implementation {
Tuner::Tuner() {
// Static Frontends array to maintain local frontends information
// Array index matches their FrontendId in the default impl
mFrontendSize = 8;
mFrontends.resize(mFrontendSize);
mFrontends[0] = new Frontend(FrontendType::DVBT, 0, this);
mFrontends[1] = new Frontend(FrontendType::ATSC, 1, this);
mFrontends[2] = new Frontend(FrontendType::DVBC, 2, this);
mFrontends[3] = new Frontend(FrontendType::DVBS, 3, this);
mFrontends[4] = new Frontend(FrontendType::DVBT, 4, this);
mFrontends[5] = new Frontend(FrontendType::ISDBT, 5, this);
mFrontends[6] = new Frontend(FrontendType::ANALOG, 6, this);
mFrontends[7] = new Frontend(FrontendType::ATSC, 7, this);
FrontendInfo::FrontendCapabilities caps;
mFrontendCaps.resize(mFrontendSize);
caps = FrontendInfo::FrontendCapabilities();
caps.dvbtCaps(FrontendDvbtCapabilities());
mFrontendCaps[0] = caps;
caps = FrontendInfo::FrontendCapabilities();
caps.atscCaps(FrontendAtscCapabilities());
mFrontendCaps[1] = caps;
caps = FrontendInfo::FrontendCapabilities();
caps.dvbcCaps(FrontendDvbcCapabilities());
mFrontendCaps[2] = caps;
caps = FrontendInfo::FrontendCapabilities();
caps.dvbsCaps(FrontendDvbsCapabilities());
mFrontendCaps[3] = caps;
caps = FrontendInfo::FrontendCapabilities();
caps.dvbtCaps(FrontendDvbtCapabilities());
mFrontendCaps[4] = caps;
caps = FrontendInfo::FrontendCapabilities();
FrontendIsdbtCapabilities isdbtCaps{
.modeCap = FrontendIsdbtMode::MODE_1 | FrontendIsdbtMode::MODE_2,
.bandwidthCap = (unsigned int)FrontendIsdbtBandwidth::BANDWIDTH_6MHZ,
.modulationCap = (unsigned int)FrontendIsdbtModulation::MOD_16QAM,
// ISDBT shares coderate and guard interval with DVBT
.coderateCap = FrontendDvbtCoderate::CODERATE_4_5 | FrontendDvbtCoderate::CODERATE_6_7,
.guardIntervalCap = (unsigned int)FrontendDvbtGuardInterval::INTERVAL_1_128,
};
caps.isdbtCaps(isdbtCaps);
mFrontendCaps[5] = caps;
caps = FrontendInfo::FrontendCapabilities();
caps.analogCaps(FrontendAnalogCapabilities());
mFrontendCaps[6] = caps;
caps = FrontendInfo::FrontendCapabilities();
caps.atscCaps(FrontendAtscCapabilities());
mFrontendCaps[7] = caps;
mLnbs.resize(2);
mLnbs[0] = new Lnb(0);
mLnbs[1] = new Lnb(1);
}
Tuner::~Tuner() {}
Return<void> Tuner::getFrontendIds(getFrontendIds_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
vector<FrontendId> frontendIds;
frontendIds.resize(mFrontendSize);
for (int i = 0; i < mFrontendSize; i++) {
frontendIds[i] = mFrontends[i]->getFrontendId();
}
_hidl_cb(Result::SUCCESS, frontendIds);
return Void();
}
Return<void> Tuner::openFrontendById(uint32_t frontendId, openFrontendById_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
if (frontendId >= mFrontendSize || frontendId < 0) {
ALOGW("[ WARN ] Frontend with id %d isn't available", frontendId);
_hidl_cb(Result::UNAVAILABLE, nullptr);
return Void();
}
_hidl_cb(Result::SUCCESS, mFrontends[frontendId]);
return Void();
}
Return<void> Tuner::openDemux(openDemux_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
uint32_t demuxId = mLastUsedId + 1;
mLastUsedId += 1;
sp<Demux> demux = new Demux(demuxId, this);
mDemuxes[demuxId] = demux;
_hidl_cb(Result::SUCCESS, demuxId, demux);
return Void();
}
Return<void> Tuner::getDemuxCaps(getDemuxCaps_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
DemuxCapabilities caps;
// IP filter can be an MMTP filter's data source.
caps.linkCaps = {0x00, 0x00, 0x02, 0x00, 0x00};
_hidl_cb(Result::SUCCESS, caps);
return Void();
}
Return<void> Tuner::openDescrambler(openDescrambler_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
sp<V1_0::IDescrambler> descrambler = new Descrambler();
_hidl_cb(Result::SUCCESS, descrambler);
return Void();
}
Return<void> Tuner::getFrontendInfo(FrontendId frontendId, getFrontendInfo_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
FrontendInfo info;
if (frontendId >= mFrontendSize) {
_hidl_cb(Result::INVALID_ARGUMENT, info);
return Void();
}
vector<FrontendStatusType> statusCaps = {
FrontendStatusType::DEMOD_LOCK,
FrontendStatusType::SNR,
FrontendStatusType::FEC,
FrontendStatusType::MODULATION,
FrontendStatusType::PLP_ID,
FrontendStatusType::LAYER_ERROR,
FrontendStatusType::ATSC3_PLP_INFO,
};
// assign randomly selected values for testing.
info = {
.type = mFrontends[frontendId]->getFrontendType(),
.minFrequency = 139,
.maxFrequency = 1139,
.minSymbolRate = 45,
.maxSymbolRate = 1145,
.acquireRange = 30,
.exclusiveGroupId = 57,
.statusCaps = statusCaps,
.frontendCaps = mFrontendCaps[frontendId],
};
_hidl_cb(Result::SUCCESS, info);
return Void();
}
Return<void> Tuner::getLnbIds(getLnbIds_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
vector<V1_0::LnbId> lnbIds;
lnbIds.resize(mLnbs.size());
for (int i = 0; i < lnbIds.size(); i++) {
lnbIds[i] = mLnbs[i]->getId();
}
_hidl_cb(Result::SUCCESS, lnbIds);
return Void();
}
Return<void> Tuner::openLnbById(V1_0::LnbId lnbId, openLnbById_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
if (lnbId >= mLnbs.size()) {
_hidl_cb(Result::INVALID_ARGUMENT, nullptr);
return Void();
}
_hidl_cb(Result::SUCCESS, mLnbs[lnbId]);
return Void();
}
sp<Frontend> Tuner::getFrontendById(uint32_t frontendId) {
ALOGV("%s", __FUNCTION__);
return mFrontends[frontendId];
}
Return<void> Tuner::openLnbByName(const hidl_string& /*lnbName*/, openLnbByName_cb _hidl_cb) {
ALOGV("%s", __FUNCTION__);
sp<V1_0::ILnb> lnb = new Lnb();
_hidl_cb(Result::SUCCESS, 1234, lnb);
return Void();
}
void Tuner::setFrontendAsDemuxSource(uint32_t frontendId, uint32_t demuxId) {
mFrontendToDemux[frontendId] = demuxId;
if (mFrontends[frontendId] != nullptr && mFrontends[frontendId]->isLocked()) {
mDemuxes[demuxId]->startFrontendInputLoop();
}
}
void Tuner::frontendStopTune(uint32_t frontendId) {
map<uint32_t, uint32_t>::iterator it = mFrontendToDemux.find(frontendId);
uint32_t demuxId;
if (it != mFrontendToDemux.end()) {
demuxId = it->second;
mDemuxes[demuxId]->stopFrontendInput();
}
}
void Tuner::frontendStartTune(uint32_t frontendId) {
map<uint32_t, uint32_t>::iterator it = mFrontendToDemux.find(frontendId);
uint32_t demuxId;
if (it != mFrontendToDemux.end()) {
demuxId = it->second;
mDemuxes[demuxId]->startFrontendInputLoop();
}
}
} // namespace implementation
} // namespace V1_0
} // namespace tuner
} // namespace tv
} // namespace hardware
} // namespace android

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_HARDWARE_TV_TUNER_V1_1_TUNER_H_
#define ANDROID_HARDWARE_TV_TUNER_V1_1_TUNER_H_
#include <android/hardware/tv/tuner/1.1/ITuner.h>
#include <map>
#include "Demux.h"
#include "Frontend.h"
#include "Lnb.h"
using namespace std;
namespace android {
namespace hardware {
namespace tv {
namespace tuner {
namespace V1_0 {
namespace implementation {
using ::android::hardware::tv::tuner::V1_1::ITuner;
class Frontend;
class Demux;
class Lnb;
class Tuner : public ITuner {
public:
Tuner();
virtual Return<void> getFrontendIds(getFrontendIds_cb _hidl_cb) override;
virtual Return<void> openFrontendById(uint32_t frontendId,
openFrontendById_cb _hidl_cb) override;
virtual Return<void> openDemux(openDemux_cb _hidl_cb) override;
virtual Return<void> getDemuxCaps(getDemuxCaps_cb _hidl_cb) override;
virtual Return<void> openDescrambler(openDescrambler_cb _hidl_cb) override;
virtual Return<void> getFrontendInfo(uint32_t frontendId, getFrontendInfo_cb _hidl_cb) override;
virtual Return<void> getLnbIds(getLnbIds_cb _hidl_cb) override;
virtual Return<void> openLnbById(uint32_t lnbId, openLnbById_cb _hidl_cb) override;
virtual Return<void> openLnbByName(const hidl_string& lnbName,
openLnbByName_cb _hidl_cb) override;
sp<Frontend> getFrontendById(uint32_t frontendId);
void setFrontendAsDemuxSource(uint32_t frontendId, uint32_t demuxId);
void frontendStartTune(uint32_t frontendId);
void frontendStopTune(uint32_t frontendId);
private:
virtual ~Tuner();
// Static mFrontends array to maintain local frontends information
vector<sp<Frontend>> mFrontends;
vector<FrontendInfo::FrontendCapabilities> mFrontendCaps;
std::map<uint32_t, uint32_t> mFrontendToDemux;
std::map<uint32_t, sp<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.
uint32_t mLastUsedId = -1;
vector<sp<Lnb>> mLnbs;
};
} // namespace implementation
} // namespace V1_0
} // namespace tuner
} // namespace tv
} // namespace hardware
} // namespace android
#endif // ANDROID_HARDWARE_TV_TUNER_V1_1_TUNER_H_

View File

@@ -0,0 +1,9 @@
service vendor.tuner-hal-1-1 /vendor/bin/hw/android.hardware.tv.tuner@1.1-service-lazy
interface android.hardware.tv.tuner@1.1::ITuner default
oneshot
disabled
class hal
user media
group mediadrm drmrpc
ioprio rt 4
writepid /dev/cpuset/foreground/tasks

View File

@@ -0,0 +1,11 @@
<manifest version="1.0" type="device">
<hal format="hidl">
<name>android.hardware.tv.tuner</name>
<transport>hwbinder</transport>
<version>1.1</version>
<interface>
<name>ITuner</name>
<instance>default</instance>
</interface>
</hal>
</manifest>

View File

@@ -0,0 +1,6 @@
service vendor.tuner-hal-1-1 /vendor/bin/hw/android.hardware.tv.tuner@1.1-service
class hal
user media
group mediadrm drmrpc
ioprio rt 4
writepid /dev/cpuset/foreground/tasks

View File

@@ -0,0 +1,11 @@
<manifest version="1.0" type="device">
<hal format="hidl">
<name>android.hardware.tv.tuner</name>
<transport>hwbinder</transport>
<version>1.1</version>
<interface>
<name>ITuner</name>
<instance>default</instance>
</interface>
</hal>
</manifest>

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//#define LOG_NDEBUG 0
#ifdef LAZY_SERVICE
#define LOG_TAG "android.hardware.tv.tuner@1.1-service-lazy"
#else
#define LOG_TAG "android.hardware.tv.tuner@1.1-service"
#endif
#include <hidl/HidlTransportSupport.h>
#include <hidl/LegacySupport.h>
#include "Tuner.h"
using android::hardware::configureRpcThreadpool;
using android::hardware::joinRpcThreadpool;
using android::hardware::LazyServiceRegistrar;
using android::hardware::tv::tuner::V1_0::implementation::Tuner;
using android::hardware::tv::tuner::V1_1::ITuner;
#ifdef LAZY_SERVICE
const bool kLazyService = true;
#else
const bool kLazyService = false;
#endif
int main() {
configureRpcThreadpool(8, true /* callerWillJoin */);
// Setup hwbinder service
android::sp<ITuner> service = new Tuner();
android::status_t status;
if (kLazyService) {
auto serviceRegistrar = LazyServiceRegistrar::getInstance();
status = serviceRegistrar.registerService(service);
} else {
status = service->registerAsService();
}
LOG_ALWAYS_FATAL_IF(status != android::OK, "Error while registering tuner service: %d", status);
joinRpcThreadpool();
return 0;
}

4
tv/tuner/1.1/vts/OWNERS Normal file
View File

@@ -0,0 +1,4 @@
nchalko@google.com
amyjojo@google.com
shubang@google.com
quxiangfang@google.com

View File

@@ -0,0 +1,48 @@
//
// Copyright 2020 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
cc_test {
name: "VtsHalTvTunerV1_1TargetTest",
defaults: ["VtsHalTargetTestDefaults"],
srcs: [
"VtsHalTvTunerV1_1TargetTest.cpp",
"FrontendTests.cpp",
"DemuxTests.cpp",
"FilterTests.cpp",
],
static_libs: [
"android.hardware.cas@1.0",
"android.hardware.cas@1.1",
"android.hardware.cas@1.2",
"android.hardware.tv.tuner@1.0",
"android.hardware.tv.tuner@1.1",
"android.hidl.allocator@1.0",
"android.hidl.memory@1.0",
"libhidlallocatorutils",
"libhidlmemory",
"libcutils",
"libfmq",
],
shared_libs: [
"libbinder",
],
test_suites: [
"general-tests",
"vts",
],
require_root: true,
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "DemuxTests.h"
AssertionResult DemuxTests::openDemux(sp<IDemux>& demux, uint32_t& demuxId) {
Result status;
mService->openDemux([&](Result result, uint32_t id, const sp<IDemux>& demuxSp) {
mDemux = demuxSp;
demux = demuxSp;
demuxId = id;
status = result;
});
return AssertionResult(status == Result::SUCCESS);
}
AssertionResult DemuxTests::setDemuxFrontendDataSource(uint32_t frontendId) {
EXPECT_TRUE(mDemux) << "Test with openDemux first.";
auto status = mDemux->setFrontendDataSource(frontendId);
return AssertionResult(status.isOk());
}
AssertionResult DemuxTests::closeDemux() {
EXPECT_TRUE(mDemux) << "Test with openDemux first.";
auto status = mDemux->close();
mDemux = nullptr;
return AssertionResult(status.isOk());
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <android-base/logging.h>
#include <android/hardware/tv/tuner/1.0/IDemux.h>
#include <android/hardware/tv/tuner/1.0/types.h>
#include <android/hardware/tv/tuner/1.1/IFilter.h>
#include <android/hardware/tv/tuner/1.1/ITuner.h>
#include <binder/MemoryDealer.h>
#include <gtest/gtest.h>
#include <hidl/ServiceManagement.h>
#include <hidl/Status.h>
#include <hidlmemory/FrameworkUtils.h>
#include <utils/Condition.h>
#include <utils/Mutex.h>
#include <map>
using android::sp;
using android::hardware::Return;
using android::hardware::Void;
using android::hardware::tv::tuner::V1_0::IDemux;
using android::hardware::tv::tuner::V1_0::IFilter;
using android::hardware::tv::tuner::V1_0::Result;
using android::hardware::tv::tuner::V1_1::ITuner;
using ::testing::AssertionResult;
class DemuxTests {
public:
void setService(sp<ITuner> tuner) { mService = tuner; }
AssertionResult openDemux(sp<IDemux>& demux, uint32_t& demuxId);
AssertionResult setDemuxFrontendDataSource(uint32_t frontendId);
AssertionResult closeDemux();
protected:
static AssertionResult failure() { return ::testing::AssertionFailure(); }
static AssertionResult success() { return ::testing::AssertionSuccess(); }
sp<ITuner> mService;
sp<IDemux> mDemux;
};

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "FilterTests.h"
AssertionResult FilterTests::openFilterInDemux(DemuxFilterType type, uint32_t bufferSize) {
Result status;
EXPECT_TRUE(mDemux) << "Test with openDemux first.";
// Create demux callback
mFilterCallback = new FilterCallback();
// Add filter to the local demux
mDemux->openFilter(type, bufferSize, mFilterCallback,
[&](Result result, const sp<IFilter>& filter) {
mFilter = filter;
status = result;
});
return AssertionResult(status == Result::SUCCESS);
}
AssertionResult FilterTests::getNewlyOpenedFilterId_64bit(uint64_t& filterId) {
Result status;
EXPECT_TRUE(mDemux) << "Test with openDemux first.";
EXPECT_TRUE(mFilter) << "Test with openFilterInDemux first.";
EXPECT_TRUE(mFilterCallback) << "Test with openFilterInDemux first.";
sp<android::hardware::tv::tuner::V1_1::IFilter> filter_v1_1 =
android::hardware::tv::tuner::V1_1::IFilter::castFrom(mFilter);
if (filter_v1_1 != NULL) {
filter_v1_1->getId64Bit([&](Result result, uint64_t filterId) {
mFilterId = filterId;
status = result;
});
} else {
ALOGW("[vts] Can't cast IFilter into v1_1.");
return failure();
}
if (status == Result::SUCCESS) {
mUsedFilterIds.insert(mUsedFilterIds.end(), mFilterId);
mFilters[mFilterId] = mFilter;
mFilterCallbacks[mFilterId] = mFilterCallback;
filterId = mFilterId;
}
return AssertionResult(status == Result::SUCCESS);
}
AssertionResult FilterTests::configFilter(DemuxFilterSettings setting, uint64_t filterId) {
Result status;
EXPECT_TRUE(mFilters[filterId]) << "Test with getNewlyOpenedFilterId first.";
status = mFilters[filterId]->configure(setting);
return AssertionResult(status == Result::SUCCESS);
}
AssertionResult FilterTests::getFilterMQDescriptor(uint64_t filterId) {
Result status;
EXPECT_TRUE(mFilters[filterId]) << "Test with getNewlyOpenedFilterId first.";
EXPECT_TRUE(mFilterCallbacks[filterId]) << "Test with getNewlyOpenedFilterId first.";
mFilter->getQueueDesc([&](Result result, const MQDesc& filterMQDesc) {
mFilterMQDescriptor = filterMQDesc;
status = result;
});
return AssertionResult(status == Result::SUCCESS);
}
AssertionResult FilterTests::startFilter(uint64_t filterId) {
EXPECT_TRUE(mFilters[filterId]) << "Test with getNewlyOpenedFilterId first.";
Result status = mFilters[filterId]->start();
return AssertionResult(status == Result::SUCCESS);
}
AssertionResult FilterTests::stopFilter(uint64_t filterId) {
EXPECT_TRUE(mFilters[filterId]) << "Test with getNewlyOpenedFilterId first.";
Result status = mFilters[filterId]->stop();
return AssertionResult(status == Result::SUCCESS);
}
AssertionResult FilterTests::closeFilter(uint64_t filterId) {
EXPECT_TRUE(mFilters[filterId]) << "Test with getNewlyOpenedFilterId first.";
Result status = mFilters[filterId]->close();
if (status == Result::SUCCESS) {
for (int i = 0; i < mUsedFilterIds.size(); i++) {
if (mUsedFilterIds[i] == filterId) {
mUsedFilterIds.erase(mUsedFilterIds.begin() + i);
break;
}
}
mFilterCallbacks.erase(filterId);
mFilters.erase(filterId);
}
return AssertionResult(status == Result::SUCCESS);
}

View File

@@ -0,0 +1,168 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <android-base/logging.h>
#include <android/hardware/tv/tuner/1.0/IDemux.h>
#include <android/hardware/tv/tuner/1.0/IFilter.h>
#include <android/hardware/tv/tuner/1.0/IFilterCallback.h>
#include <android/hardware/tv/tuner/1.0/types.h>
#include <android/hardware/tv/tuner/1.1/IFilter.h>
#include <android/hardware/tv/tuner/1.1/ITuner.h>
#include <fmq/MessageQueue.h>
#include <gtest/gtest.h>
#include <hidl/HidlSupport.h>
#include <hidl/HidlTransportSupport.h>
#include <hidl/Status.h>
#include <inttypes.h>
#include <utils/Condition.h>
#include <utils/Mutex.h>
#include <map>
using android::Condition;
using android::Mutex;
using android::sp;
using android::hardware::EventFlag;
using android::hardware::hidl_handle;
using android::hardware::hidl_string;
using android::hardware::hidl_vec;
using android::hardware::kSynchronizedReadWrite;
using android::hardware::MessageQueue;
using android::hardware::MQDescriptorSync;
using android::hardware::Return;
using android::hardware::Void;
using android::hardware::tv::tuner::V1_0::DemuxFilterEvent;
using android::hardware::tv::tuner::V1_0::DemuxFilterMainType;
using android::hardware::tv::tuner::V1_0::DemuxFilterSettings;
using android::hardware::tv::tuner::V1_0::DemuxFilterStatus;
using android::hardware::tv::tuner::V1_0::DemuxFilterType;
using android::hardware::tv::tuner::V1_0::DemuxTsFilterType;
using android::hardware::tv::tuner::V1_0::IDemux;
using android::hardware::tv::tuner::V1_0::IFilter;
using android::hardware::tv::tuner::V1_0::IFilterCallback;
using android::hardware::tv::tuner::V1_0::Result;
using android::hardware::tv::tuner::V1_1::ITuner;
using ::testing::AssertionResult;
using namespace std;
enum FilterEventType : uint8_t {
UNDEFINED,
SECTION,
MEDIA,
PES,
RECORD,
MMTPRECORD,
DOWNLOAD,
TEMI,
};
using FilterMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>;
using MQDesc = MQDescriptorSync<uint8_t>;
#define WAIT_TIMEOUT 3000000000
class FilterCallback : public IFilterCallback {
public:
virtual Return<void> onFilterEvent(const DemuxFilterEvent& /*filterEvent*/) override {
return Void();
}
virtual Return<void> onFilterStatus(const DemuxFilterStatus /*status*/) override {
return Void();
}
};
class FilterTests {
public:
void setService(sp<ITuner> tuner) { mService = tuner; }
void setDemux(sp<IDemux> demux) { mDemux = demux; }
sp<IFilter> getFilterById(uint64_t filterId) { return mFilters[filterId]; }
std::map<uint64_t, sp<FilterCallback>> getFilterCallbacks() { return mFilterCallbacks; }
AssertionResult openFilterInDemux(DemuxFilterType type, uint32_t bufferSize);
AssertionResult getNewlyOpenedFilterId_64bit(uint64_t& filterId);
AssertionResult configFilter(DemuxFilterSettings setting, uint64_t filterId);
AssertionResult getFilterMQDescriptor(uint64_t filterId);
AssertionResult startFilter(uint64_t filterId);
AssertionResult stopFilter(uint64_t filterId);
AssertionResult closeFilter(uint64_t filterId);
FilterEventType getFilterEventType(DemuxFilterType type) {
FilterEventType eventType = FilterEventType::UNDEFINED;
switch (type.mainType) {
case DemuxFilterMainType::TS:
switch (type.subType.tsFilterType()) {
case DemuxTsFilterType::UNDEFINED:
break;
case DemuxTsFilterType::SECTION:
eventType = FilterEventType::SECTION;
break;
case DemuxTsFilterType::PES:
eventType = FilterEventType::PES;
break;
case DemuxTsFilterType::TS:
break;
case DemuxTsFilterType::AUDIO:
case DemuxTsFilterType::VIDEO:
eventType = FilterEventType::MEDIA;
break;
case DemuxTsFilterType::PCR:
break;
case DemuxTsFilterType::RECORD:
eventType = FilterEventType::RECORD;
break;
case DemuxTsFilterType::TEMI:
eventType = FilterEventType::TEMI;
break;
}
break;
case DemuxFilterMainType::MMTP:
/*mmtpSettings*/
break;
case DemuxFilterMainType::IP:
/*ipSettings*/
break;
case DemuxFilterMainType::TLV:
/*tlvSettings*/
break;
case DemuxFilterMainType::ALP:
/*alpSettings*/
break;
default:
break;
}
return eventType;
}
protected:
static AssertionResult failure() { return ::testing::AssertionFailure(); }
static AssertionResult success() { return ::testing::AssertionSuccess(); }
sp<ITuner> mService;
sp<IFilter> mFilter;
sp<IDemux> mDemux;
std::map<uint64_t, sp<IFilter>> mFilters;
std::map<uint64_t, sp<FilterCallback>> mFilterCallbacks;
sp<FilterCallback> mFilterCallback;
MQDesc mFilterMQDescriptor;
vector<uint64_t> mUsedFilterIds;
uint64_t mFilterId = -1;
};

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "FrontendTests.h"
Return<void> FrontendCallback::onEvent(FrontendEventType /*frontendEventType*/) {
return Void();
}
Return<void> FrontendCallback::onScanMessage(FrontendScanMessageType /*type*/,
const FrontendScanMessage& /*message*/) {
return Void();
}
AssertionResult FrontendTests::getFrontendIds() {
Result status;
mService->getFrontendIds([&](Result result, const hidl_vec<FrontendId>& frontendIds) {
status = result;
mFeIds = frontendIds;
});
return AssertionResult(status == Result::SUCCESS);
}
AssertionResult FrontendTests::getFrontendInfo(uint32_t frontendId) {
Result status;
mService->getFrontendInfo(frontendId, [&](Result result, const FrontendInfo& frontendInfo) {
mFrontendInfo = frontendInfo;
status = result;
});
return AssertionResult(status == Result::SUCCESS);
}
AssertionResult FrontendTests::openFrontendById(uint32_t frontendId) {
Result status;
mService->openFrontendById(frontendId, [&](Result result, const sp<IFrontend>& frontend) {
mFrontend = frontend;
status = result;
});
return AssertionResult(status == Result::SUCCESS);
}
AssertionResult FrontendTests::setFrontendCallback() {
EXPECT_TRUE(mFrontend) << "Test with openFrontendById first.";
mFrontendCallback = new FrontendCallback();
auto callbackStatus = mFrontend->setCallback(mFrontendCallback);
return AssertionResult(callbackStatus.isOk());
}
AssertionResult FrontendTests::closeFrontend() {
EXPECT_TRUE(mFrontend) << "Test with openFrontendById first.";
Result status;
status = mFrontend->close();
mFrontend = nullptr;
mFrontendCallback = nullptr;
return AssertionResult(status == Result::SUCCESS);
}
void FrontendTests::getFrontendIdByType(FrontendType feType, uint32_t& feId) {
ASSERT_TRUE(getFrontendIds());
ASSERT_TRUE(mFeIds.size() > 0);
for (size_t i = 0; i < mFeIds.size(); i++) {
ASSERT_TRUE(getFrontendInfo(mFeIds[i]));
if (mFrontendInfo.type != feType) {
continue;
}
feId = mFeIds[i];
return;
}
feId = INVALID_ID;
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <android-base/logging.h>
#include <android/hardware/tv/tuner/1.0/IFrontend.h>
#include <android/hardware/tv/tuner/1.0/IFrontendCallback.h>
#include <android/hardware/tv/tuner/1.0/types.h>
#include <android/hardware/tv/tuner/1.1/ITuner.h>
#include <binder/MemoryDealer.h>
#include <gtest/gtest.h>
#include <hidl/GtestPrinter.h>
#include <hidl/HidlSupport.h>
#include <hidl/HidlTransportSupport.h>
#include <hidl/ServiceManagement.h>
#include <hidl/Status.h>
#include <hidlmemory/FrameworkUtils.h>
#include <utils/Condition.h>
#include <utils/Mutex.h>
#include <map>
#include "VtsHalTvTunerV1_1TestConfigurations.h"
#define WAIT_TIMEOUT 3000000000
#define INVALID_ID -1
using android::Condition;
using android::IMemory;
using android::IMemoryHeap;
using android::MemoryDealer;
using android::Mutex;
using android::sp;
using android::hardware::fromHeap;
using android::hardware::hidl_vec;
using android::hardware::Return;
using android::hardware::Void;
using android::hardware::tv::tuner::V1_0::FrontendEventType;
using android::hardware::tv::tuner::V1_0::FrontendId;
using android::hardware::tv::tuner::V1_0::FrontendInfo;
using android::hardware::tv::tuner::V1_0::FrontendScanMessage;
using android::hardware::tv::tuner::V1_0::FrontendScanMessageType;
using android::hardware::tv::tuner::V1_0::IFrontend;
using android::hardware::tv::tuner::V1_0::IFrontendCallback;
using android::hardware::tv::tuner::V1_0::Result;
using android::hardware::tv::tuner::V1_1::ITuner;
using ::testing::AssertionResult;
using namespace std;
#define INVALID_ID -1
class FrontendCallback : public IFrontendCallback {
public:
virtual Return<void> onEvent(FrontendEventType frontendEventType) override;
virtual Return<void> onScanMessage(FrontendScanMessageType type,
const FrontendScanMessage& message) override;
};
class FrontendTests {
public:
sp<ITuner> mService;
void setService(sp<ITuner> tuner) { mService = tuner; }
AssertionResult getFrontendIds();
AssertionResult getFrontendInfo(uint32_t frontendId);
AssertionResult openFrontendById(uint32_t frontendId);
AssertionResult setFrontendCallback();
AssertionResult closeFrontend();
void getFrontendIdByType(FrontendType feType, uint32_t& feId);
protected:
static AssertionResult failure() { return ::testing::AssertionFailure(); }
static AssertionResult success() { return ::testing::AssertionSuccess(); }
sp<IFrontend> mFrontend;
FrontendInfo mFrontendInfo;
sp<FrontendCallback> mFrontendCallback;
hidl_vec<FrontendId> mFeIds;
bool mIsSoftwareFe = false;
};

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "VtsHalTvTunerV1_1TargetTest.h"
namespace {
void TunerFilterHidlTest::configSingleFilterInDemuxTest(FilterConfig filterConf,
FrontendConfig frontendConf) {
uint32_t feId;
uint32_t demuxId;
sp<IDemux> demux;
uint64_t filterId;
mFrontendTests.getFrontendIdByType(frontendConf.type, feId);
ASSERT_TRUE(feId != INVALID_ID);
ASSERT_TRUE(mFrontendTests.openFrontendById(feId));
ASSERT_TRUE(mFrontendTests.setFrontendCallback());
ASSERT_TRUE(mDemuxTests.openDemux(demux, demuxId));
ASSERT_TRUE(mDemuxTests.setDemuxFrontendDataSource(feId));
mFilterTests.setDemux(demux);
ASSERT_TRUE(mFilterTests.openFilterInDemux(filterConf.type, filterConf.bufferSize));
ASSERT_TRUE(mFilterTests.getNewlyOpenedFilterId_64bit(filterId));
ASSERT_TRUE(mFilterTests.configFilter(filterConf.settings, filterId));
ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId));
ASSERT_TRUE(mFilterTests.startFilter(filterId));
ASSERT_TRUE(mFilterTests.stopFilter(filterId));
ASSERT_TRUE(mFilterTests.closeFilter(filterId));
ASSERT_TRUE(mDemuxTests.closeDemux());
ASSERT_TRUE(mFrontendTests.closeFrontend());
}
TEST_P(TunerFilterHidlTest, StartFilterInDemux) {
description("Open and start a filter in Demux.");
// TODO use parameterized tests
configSingleFilterInDemuxTest(filterArray[TS_VIDEO0], frontendArray[DVBT]);
}
INSTANTIATE_TEST_SUITE_P(
PerInstance, TunerFilterHidlTest,
testing::ValuesIn(android::hardware::getAllHalInstanceNames(ITuner::descriptor)),
android::hardware::PrintInstanceNameToString);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TunerFilterHidlTest);
} // namespace

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "DemuxTests.h"
#include "FilterTests.h"
#include "FrontendTests.h"
namespace {
void initConfiguration() {
initFrontendConfig();
initFilterConfig();
}
class TunerFilterHidlTest : public testing::TestWithParam<std::string> {
public:
virtual void SetUp() override {
mService = ITuner::getService(GetParam());
ASSERT_NE(mService, nullptr);
initConfiguration();
mFrontendTests.setService(mService);
mDemuxTests.setService(mService);
mFilterTests.setService(mService);
}
protected:
static void description(const std::string& description) {
RecordProperty("description", description);
}
void configSingleFilterInDemuxTest(FilterConfig filterConf, FrontendConfig frontendConf);
sp<ITuner> mService;
FrontendTests mFrontendTests;
DemuxTests mDemuxTests;
FilterTests mFilterTests;
};
} // namespace

View File

@@ -0,0 +1,178 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <android/hardware/tv/tuner/1.0/types.h>
#include <binder/MemoryDealer.h>
#include <hidl/HidlSupport.h>
#include <hidl/HidlTransportSupport.h>
#include <hidl/Status.h>
#include <hidlmemory/FrameworkUtils.h>
using android::hardware::tv::tuner::V1_0::DemuxAlpFilterType;
using android::hardware::tv::tuner::V1_0::DemuxFilterEvent;
using android::hardware::tv::tuner::V1_0::DemuxFilterMainType;
using android::hardware::tv::tuner::V1_0::DemuxFilterSettings;
using android::hardware::tv::tuner::V1_0::DemuxFilterType;
using android::hardware::tv::tuner::V1_0::DemuxIpFilterType;
using android::hardware::tv::tuner::V1_0::DemuxMmtpFilterType;
using android::hardware::tv::tuner::V1_0::DemuxRecordScIndexType;
using android::hardware::tv::tuner::V1_0::DemuxTsFilterType;
using android::hardware::tv::tuner::V1_0::FrontendDvbtBandwidth;
using android::hardware::tv::tuner::V1_0::FrontendDvbtCoderate;
using android::hardware::tv::tuner::V1_0::FrontendDvbtConstellation;
using android::hardware::tv::tuner::V1_0::FrontendDvbtGuardInterval;
using android::hardware::tv::tuner::V1_0::FrontendDvbtHierarchy;
using android::hardware::tv::tuner::V1_0::FrontendDvbtSettings;
using android::hardware::tv::tuner::V1_0::FrontendDvbtStandard;
using android::hardware::tv::tuner::V1_0::FrontendDvbtTransmissionMode;
using android::hardware::tv::tuner::V1_0::FrontendSettings;
using android::hardware::tv::tuner::V1_0::FrontendStatus;
using android::hardware::tv::tuner::V1_0::FrontendStatusType;
using android::hardware::tv::tuner::V1_0::FrontendType;
using namespace std;
const uint32_t FMQ_SIZE_1M = 0x100000;
const uint32_t FMQ_SIZE_4M = 0x400000;
const uint32_t FMQ_SIZE_16M = 0x1000000;
typedef enum {
TS_VIDEO0,
TS_VIDEO1,
TS_AUDIO0,
TS_AUDIO1,
TS_PES0,
TS_PCR0,
TS_SECTION0,
TS_TS0,
TS_RECORD0,
FILTER_MAX,
} Filter;
typedef enum {
DVBT,
DVBS,
FRONTEND_MAX,
} Frontend;
struct FilterConfig {
uint32_t bufferSize;
DemuxFilterType type;
DemuxFilterSettings settings;
bool operator<(const FilterConfig& /*c*/) const { return false; }
};
struct FrontendConfig {
bool isSoftwareFe;
FrontendType type;
FrontendSettings settings;
vector<FrontendStatusType> tuneStatusTypes;
vector<FrontendStatus> expectTuneStatuses;
};
static FrontendConfig frontendArray[FILTER_MAX];
static FilterConfig filterArray[FILTER_MAX];
/** Configuration array for the frontend tune test */
inline void initFrontendConfig() {
FrontendDvbtSettings dvbtSettings{
.frequency = 578000,
.transmissionMode = FrontendDvbtTransmissionMode::AUTO,
.bandwidth = FrontendDvbtBandwidth::BANDWIDTH_8MHZ,
.constellation = FrontendDvbtConstellation::AUTO,
.hierarchy = FrontendDvbtHierarchy::AUTO,
.hpCoderate = FrontendDvbtCoderate::AUTO,
.lpCoderate = FrontendDvbtCoderate::AUTO,
.guardInterval = FrontendDvbtGuardInterval::AUTO,
.isHighPriority = true,
.standard = FrontendDvbtStandard::T,
};
frontendArray[DVBT].type = FrontendType::DVBT, frontendArray[DVBT].settings.dvbt(dvbtSettings);
vector<FrontendStatusType> types;
types.push_back(FrontendStatusType::DEMOD_LOCK);
FrontendStatus status;
status.isDemodLocked(true);
vector<FrontendStatus> statuses;
statuses.push_back(status);
frontendArray[DVBT].tuneStatusTypes = types;
frontendArray[DVBT].expectTuneStatuses = statuses;
frontendArray[DVBT].isSoftwareFe = true;
frontendArray[DVBS].type = FrontendType::DVBS;
frontendArray[DVBS].isSoftwareFe = true;
};
/** Configuration array for the filter test */
inline void initFilterConfig() {
// TS VIDEO filter setting for default implementation testing
filterArray[TS_VIDEO0].type.mainType = DemuxFilterMainType::TS;
filterArray[TS_VIDEO0].type.subType.tsFilterType(DemuxTsFilterType::VIDEO);
filterArray[TS_VIDEO0].bufferSize = FMQ_SIZE_16M;
filterArray[TS_VIDEO0].settings.ts().tpid = 256;
filterArray[TS_VIDEO0].settings.ts().filterSettings.av({.isPassthrough = false});
filterArray[TS_VIDEO1].type.mainType = DemuxFilterMainType::TS;
filterArray[TS_VIDEO1].type.subType.tsFilterType(DemuxTsFilterType::VIDEO);
filterArray[TS_VIDEO1].bufferSize = FMQ_SIZE_16M;
filterArray[TS_VIDEO1].settings.ts().tpid = 256;
filterArray[TS_VIDEO1].settings.ts().filterSettings.av({.isPassthrough = false});
// TS AUDIO filter setting
filterArray[TS_AUDIO0].type.mainType = DemuxFilterMainType::TS;
filterArray[TS_AUDIO0].type.subType.tsFilterType(DemuxTsFilterType::AUDIO);
filterArray[TS_AUDIO0].bufferSize = FMQ_SIZE_16M;
filterArray[TS_AUDIO0].settings.ts().tpid = 256;
filterArray[TS_AUDIO0].settings.ts().filterSettings.av({.isPassthrough = false});
filterArray[TS_AUDIO1].type.mainType = DemuxFilterMainType::TS;
filterArray[TS_AUDIO1].type.subType.tsFilterType(DemuxTsFilterType::AUDIO);
filterArray[TS_AUDIO1].bufferSize = FMQ_SIZE_16M;
filterArray[TS_AUDIO1].settings.ts().tpid = 257;
filterArray[TS_AUDIO1].settings.ts().filterSettings.av({.isPassthrough = false});
// TS PES filter setting
filterArray[TS_PES0].type.mainType = DemuxFilterMainType::TS;
filterArray[TS_PES0].type.subType.tsFilterType(DemuxTsFilterType::PES);
filterArray[TS_PES0].bufferSize = FMQ_SIZE_16M;
filterArray[TS_PES0].settings.ts().tpid = 256;
filterArray[TS_PES0].settings.ts().filterSettings.pesData({
.isRaw = false,
.streamId = 0xbd,
});
// TS PCR filter setting
filterArray[TS_PCR0].type.mainType = DemuxFilterMainType::TS;
filterArray[TS_PCR0].type.subType.tsFilterType(DemuxTsFilterType::PCR);
filterArray[TS_PCR0].bufferSize = FMQ_SIZE_16M;
filterArray[TS_PCR0].settings.ts().tpid = 256;
filterArray[TS_PCR0].settings.ts().filterSettings.noinit();
// TS filter setting
filterArray[TS_TS0].type.mainType = DemuxFilterMainType::TS;
filterArray[TS_TS0].type.subType.tsFilterType(DemuxTsFilterType::TS);
filterArray[TS_TS0].bufferSize = FMQ_SIZE_16M;
filterArray[TS_TS0].settings.ts().tpid = 256;
filterArray[TS_TS0].settings.ts().filterSettings.noinit();
// TS SECTION filter setting
filterArray[TS_SECTION0].type.mainType = DemuxFilterMainType::TS;
filterArray[TS_SECTION0].type.subType.tsFilterType(DemuxTsFilterType::SECTION);
filterArray[TS_SECTION0].bufferSize = FMQ_SIZE_16M;
filterArray[TS_SECTION0].settings.ts().tpid = 256;
filterArray[TS_SECTION0].settings.ts().filterSettings.section({
.isRaw = false,
});
// TS RECORD filter setting
filterArray[TS_RECORD0].type.mainType = DemuxFilterMainType::TS;
filterArray[TS_RECORD0].type.subType.tsFilterType(DemuxTsFilterType::RECORD);
filterArray[TS_RECORD0].settings.ts().tpid = 81;
filterArray[TS_RECORD0].settings.ts().filterSettings.record({
.scIndexType = DemuxRecordScIndexType::NONE,
});
};