Add IPTV default implementation
Frontend::tune(): create a streamer using plugin interface to read a byte and return LOCKED event if byte is read Demux::setFrontendDataSource():open a new stream to read data from the socket and push the data read to DVR FMQ. Test: atest VtsHalTvTunerTargetTest Bug: 288170590 Change-Id: Iaf2eae7b4dc9e7d69b1f7b3a367d24f6acdd68be
This commit is contained in:
parent
51cc83a7b1
commit
56c98294a2
11 changed files with 645 additions and 62 deletions
|
@ -23,6 +23,7 @@ cc_defaults {
|
|||
"TimeFilter.cpp",
|
||||
"Tuner.cpp",
|
||||
"service.cpp",
|
||||
"dtv_plugin.cpp",
|
||||
],
|
||||
static_libs: [
|
||||
"libaidlcommonsupport",
|
||||
|
|
|
@ -20,7 +20,9 @@
|
|||
#include <aidl/android/hardware/tv/tuner/DemuxQueueNotifyBits.h>
|
||||
#include <aidl/android/hardware/tv/tuner/Result.h>
|
||||
|
||||
#include <fmq/AidlMessageQueue.h>
|
||||
#include <utils/Log.h>
|
||||
#include <thread>
|
||||
#include "Demux.h"
|
||||
|
||||
namespace aidl {
|
||||
|
@ -29,6 +31,15 @@ namespace hardware {
|
|||
namespace tv {
|
||||
namespace tuner {
|
||||
|
||||
using ::aidl::android::hardware::common::fmq::MQDescriptor;
|
||||
using ::aidl::android::hardware::common::fmq::SynchronizedReadWrite;
|
||||
using ::android::AidlMessageQueue;
|
||||
using ::android::hardware::EventFlag;
|
||||
|
||||
using FilterMQ = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
|
||||
using AidlMQ = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
|
||||
using AidlMQDesc = MQDescriptor<int8_t, SynchronizedReadWrite>;
|
||||
|
||||
#define WAIT_TIMEOUT 3000000000
|
||||
|
||||
Demux::Demux(int32_t demuxId, uint32_t filterTypes) {
|
||||
|
@ -45,6 +56,111 @@ Demux::~Demux() {
|
|||
close();
|
||||
}
|
||||
|
||||
::ndk::ScopedAStatus Demux::openDvr(DvrType in_type, int32_t in_bufferSize,
|
||||
const std::shared_ptr<IDvrCallback>& in_cb,
|
||||
std::shared_ptr<IDvr>* _aidl_return) {
|
||||
ALOGV("%s", __FUNCTION__);
|
||||
|
||||
if (in_cb == nullptr) {
|
||||
ALOGW("[Demux] DVR callback can't be null");
|
||||
*_aidl_return = nullptr;
|
||||
return ::ndk::ScopedAStatus::fromServiceSpecificError(
|
||||
static_cast<int32_t>(Result::INVALID_ARGUMENT));
|
||||
}
|
||||
|
||||
set<int64_t>::iterator it;
|
||||
switch (in_type) {
|
||||
case DvrType::PLAYBACK:
|
||||
mDvrPlayback = ndk::SharedRefBase::make<Dvr>(in_type, in_bufferSize, in_cb,
|
||||
this->ref<Demux>());
|
||||
if (!mDvrPlayback->createDvrMQ()) {
|
||||
ALOGE("[Demux] cannot create dvr message queue");
|
||||
mDvrPlayback = nullptr;
|
||||
*_aidl_return = mDvrPlayback;
|
||||
return ::ndk::ScopedAStatus::fromServiceSpecificError(
|
||||
static_cast<int32_t>(Result::UNKNOWN_ERROR));
|
||||
}
|
||||
|
||||
for (it = mPlaybackFilterIds.begin(); it != mPlaybackFilterIds.end(); it++) {
|
||||
if (!mDvrPlayback->addPlaybackFilter(*it, mFilters[*it])) {
|
||||
ALOGE("[Demux] Can't get filter info for DVR playback");
|
||||
mDvrPlayback = nullptr;
|
||||
*_aidl_return = mDvrPlayback;
|
||||
return ::ndk::ScopedAStatus::fromServiceSpecificError(
|
||||
static_cast<int32_t>(Result::UNKNOWN_ERROR));
|
||||
}
|
||||
}
|
||||
|
||||
ALOGI("Playback normal case");
|
||||
|
||||
*_aidl_return = mDvrPlayback;
|
||||
return ::ndk::ScopedAStatus::ok();
|
||||
case DvrType::RECORD:
|
||||
mDvrRecord = ndk::SharedRefBase::make<Dvr>(in_type, in_bufferSize, in_cb,
|
||||
this->ref<Demux>());
|
||||
if (!mDvrRecord->createDvrMQ()) {
|
||||
mDvrRecord = nullptr;
|
||||
*_aidl_return = mDvrRecord;
|
||||
return ::ndk::ScopedAStatus::fromServiceSpecificError(
|
||||
static_cast<int32_t>(Result::UNKNOWN_ERROR));
|
||||
}
|
||||
|
||||
*_aidl_return = mDvrRecord;
|
||||
return ::ndk::ScopedAStatus::ok();
|
||||
default:
|
||||
*_aidl_return = nullptr;
|
||||
return ::ndk::ScopedAStatus::fromServiceSpecificError(
|
||||
static_cast<int32_t>(Result::INVALID_ARGUMENT));
|
||||
}
|
||||
}
|
||||
|
||||
void Demux::readIptvThreadLoop(dtv_plugin* interface, dtv_streamer* streamer, void* buf,
|
||||
size_t buf_size, int timeout_ms, int buffer_timeout) {
|
||||
Timer *timer, *fullBufferTimer;
|
||||
while (mDemuxIptvReadThreadRunning) {
|
||||
if (mIsIptvDvrFMQFull && fullBufferTimer->get_elapsed_time_ms() > buffer_timeout) {
|
||||
ALOGE("DVR FMQ has not been flushed within timeout of %d ms", buffer_timeout);
|
||||
delete fullBufferTimer;
|
||||
break;
|
||||
}
|
||||
timer = new Timer();
|
||||
ssize_t bytes_read = interface->read_stream(streamer, buf, buf_size, timeout_ms);
|
||||
if (bytes_read == 0) {
|
||||
double elapsed_time = timer->get_elapsed_time_ms();
|
||||
if (elapsed_time > timeout_ms) {
|
||||
ALOGE("[Demux] timeout reached - elapsed_time: %f, timeout: %d", elapsed_time,
|
||||
timeout_ms);
|
||||
}
|
||||
ALOGE("[Demux] Cannot read data from the socket");
|
||||
delete timer;
|
||||
break;
|
||||
}
|
||||
|
||||
delete timer;
|
||||
ALOGI("Number of bytes read: %zd", bytes_read);
|
||||
int result = mDvrPlayback->writePlaybackFMQ(buf, bytes_read);
|
||||
|
||||
switch (result) {
|
||||
case DVR_WRITE_FAILURE_REASON_FMQ_FULL:
|
||||
if (!mIsIptvDvrFMQFull) {
|
||||
mIsIptvDvrFMQFull = true;
|
||||
fullBufferTimer = new Timer();
|
||||
}
|
||||
ALOGI("Waiting for client to flush DVR FMQ.");
|
||||
break;
|
||||
case DVR_WRITE_FAILURE_REASON_UNKNOWN:
|
||||
ALOGE("Failed to write data into DVR FMQ for unknown reason");
|
||||
break;
|
||||
case DVR_WRITE_SUCCESS:
|
||||
ALOGI("Wrote %zd bytes to DVR FMQ", bytes_read);
|
||||
break;
|
||||
default:
|
||||
ALOGI("Invalid DVR Status");
|
||||
}
|
||||
}
|
||||
mDemuxIptvReadThreadRunning = false;
|
||||
}
|
||||
|
||||
::ndk::ScopedAStatus Demux::setFrontendDataSource(int32_t in_frontendId) {
|
||||
ALOGV("%s", __FUNCTION__);
|
||||
|
||||
|
@ -52,7 +168,6 @@ Demux::~Demux() {
|
|||
return ::ndk::ScopedAStatus::fromServiceSpecificError(
|
||||
static_cast<int32_t>(Result::NOT_INITIALIZED));
|
||||
}
|
||||
|
||||
mFrontend = mTuner->getFrontendById(in_frontendId);
|
||||
if (mFrontend == nullptr) {
|
||||
return ::ndk::ScopedAStatus::fromServiceSpecificError(
|
||||
|
@ -61,6 +176,58 @@ Demux::~Demux() {
|
|||
|
||||
mTuner->setFrontendAsDemuxSource(in_frontendId, mDemuxId);
|
||||
|
||||
// if mFrontend is an IPTV frontend, create streamer to read TS data from socket
|
||||
if (mFrontend->getFrontendType() == FrontendType::IPTV) {
|
||||
// create a DVR instance on the demux
|
||||
shared_ptr<IDvr> iptvDvr;
|
||||
|
||||
std::shared_ptr<IDvrCallback> dvrPlaybackCallback =
|
||||
::ndk::SharedRefBase::make<DvrPlaybackCallback>();
|
||||
|
||||
::ndk::ScopedAStatus status =
|
||||
openDvr(DvrType::PLAYBACK, IPTV_BUFFER_SIZE, dvrPlaybackCallback, &iptvDvr);
|
||||
if (status.isOk()) {
|
||||
ALOGI("DVR instance created");
|
||||
}
|
||||
|
||||
// get plugin interface from frontend
|
||||
dtv_plugin* interface = mFrontend->getIptvPluginInterface();
|
||||
if (interface == nullptr) {
|
||||
ALOGE("[Demux] getIptvPluginInterface(): plugin interface is null");
|
||||
return ::ndk::ScopedAStatus::fromServiceSpecificError(
|
||||
static_cast<int32_t>(Result::INVALID_STATE));
|
||||
}
|
||||
ALOGI("[Demux] getIptvPluginInterface(): plugin interface is not null");
|
||||
|
||||
// get streamer object from Frontend instance
|
||||
dtv_streamer* streamer = mFrontend->getIptvPluginStreamer();
|
||||
if (streamer == nullptr) {
|
||||
ALOGE("[Demux] getIptvPluginStreamer(): streamer is null");
|
||||
return ::ndk::ScopedAStatus::fromServiceSpecificError(
|
||||
static_cast<int32_t>(Result::INVALID_STATE));
|
||||
}
|
||||
ALOGI("[Demux] getIptvPluginStreamer(): streamer is not null");
|
||||
|
||||
// get transport description from frontend
|
||||
string transport_desc = mFrontend->getIptvTransportDescription();
|
||||
ALOGI("[Demux] getIptvTransportDescription(): transport_desc: %s", transport_desc.c_str());
|
||||
|
||||
// call read_stream on the socket to populate the buffer with TS data
|
||||
// while thread is alive, keep reading data
|
||||
int timeout_ms = 20;
|
||||
int buffer_timeout = 10000; // 10s
|
||||
void* buf = malloc(sizeof(char) * IPTV_BUFFER_SIZE);
|
||||
if (buf == nullptr) ALOGI("malloc buf failed");
|
||||
ALOGI("[ INFO ] Allocated buffer of size %d", IPTV_BUFFER_SIZE);
|
||||
ALOGI("Getting FMQ from DVR instance to write socket data");
|
||||
mDemuxIptvReadThreadRunning = true;
|
||||
mDemuxIptvReadThread = std::thread(&Demux::readIptvThreadLoop, this, interface, streamer,
|
||||
buf, IPTV_BUFFER_SIZE, timeout_ms, buffer_timeout);
|
||||
if (mDemuxIptvReadThread.joinable()) {
|
||||
mDemuxIptvReadThread.join();
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
return ::ndk::ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
|
@ -193,61 +360,6 @@ Demux::~Demux() {
|
|||
return ::ndk::ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
::ndk::ScopedAStatus Demux::openDvr(DvrType in_type, int32_t in_bufferSize,
|
||||
const std::shared_ptr<IDvrCallback>& in_cb,
|
||||
std::shared_ptr<IDvr>* _aidl_return) {
|
||||
ALOGV("%s", __FUNCTION__);
|
||||
|
||||
if (in_cb == nullptr) {
|
||||
ALOGW("[Demux] DVR callback can't be null");
|
||||
*_aidl_return = nullptr;
|
||||
return ::ndk::ScopedAStatus::fromServiceSpecificError(
|
||||
static_cast<int32_t>(Result::INVALID_ARGUMENT));
|
||||
}
|
||||
|
||||
set<int64_t>::iterator it;
|
||||
switch (in_type) {
|
||||
case DvrType::PLAYBACK:
|
||||
mDvrPlayback = ndk::SharedRefBase::make<Dvr>(in_type, in_bufferSize, in_cb,
|
||||
this->ref<Demux>());
|
||||
if (!mDvrPlayback->createDvrMQ()) {
|
||||
mDvrPlayback = nullptr;
|
||||
*_aidl_return = mDvrPlayback;
|
||||
return ::ndk::ScopedAStatus::fromServiceSpecificError(
|
||||
static_cast<int32_t>(Result::UNKNOWN_ERROR));
|
||||
}
|
||||
|
||||
for (it = mPlaybackFilterIds.begin(); it != mPlaybackFilterIds.end(); it++) {
|
||||
if (!mDvrPlayback->addPlaybackFilter(*it, mFilters[*it])) {
|
||||
ALOGE("[Demux] Can't get filter info for DVR playback");
|
||||
mDvrPlayback = nullptr;
|
||||
*_aidl_return = mDvrPlayback;
|
||||
return ::ndk::ScopedAStatus::fromServiceSpecificError(
|
||||
static_cast<int32_t>(Result::UNKNOWN_ERROR));
|
||||
}
|
||||
}
|
||||
|
||||
*_aidl_return = mDvrPlayback;
|
||||
return ::ndk::ScopedAStatus::ok();
|
||||
case DvrType::RECORD:
|
||||
mDvrRecord = ndk::SharedRefBase::make<Dvr>(in_type, in_bufferSize, in_cb,
|
||||
this->ref<Demux>());
|
||||
if (!mDvrRecord->createDvrMQ()) {
|
||||
mDvrRecord = nullptr;
|
||||
*_aidl_return = mDvrRecord;
|
||||
return ::ndk::ScopedAStatus::fromServiceSpecificError(
|
||||
static_cast<int32_t>(Result::UNKNOWN_ERROR));
|
||||
}
|
||||
|
||||
*_aidl_return = mDvrRecord;
|
||||
return ::ndk::ScopedAStatus::ok();
|
||||
default:
|
||||
*_aidl_return = nullptr;
|
||||
return ::ndk::ScopedAStatus::fromServiceSpecificError(
|
||||
static_cast<int32_t>(Result::INVALID_ARGUMENT));
|
||||
}
|
||||
}
|
||||
|
||||
::ndk::ScopedAStatus Demux::connectCiCam(int32_t in_ciCamId) {
|
||||
ALOGV("%s", __FUNCTION__);
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <aidl/android/hardware/tv/tuner/BnDemux.h>
|
||||
#include <aidl/android/hardware/tv/tuner/BnDvrCallback.h>
|
||||
|
||||
#include <fmq/AidlMessageQueue.h>
|
||||
#include <math.h>
|
||||
|
@ -28,7 +29,9 @@
|
|||
#include "Filter.h"
|
||||
#include "Frontend.h"
|
||||
#include "TimeFilter.h"
|
||||
#include "Timer.h"
|
||||
#include "Tuner.h"
|
||||
#include "dtv_plugin.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -44,6 +47,8 @@ using ::android::AidlMessageQueue;
|
|||
using ::android::hardware::EventFlag;
|
||||
|
||||
using FilterMQ = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
|
||||
using AidlMQ = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
|
||||
using AidlMQDesc = MQDescriptor<int8_t, SynchronizedReadWrite>;
|
||||
|
||||
class Dvr;
|
||||
class Filter;
|
||||
|
@ -51,6 +56,19 @@ class Frontend;
|
|||
class TimeFilter;
|
||||
class Tuner;
|
||||
|
||||
class DvrPlaybackCallback : public BnDvrCallback {
|
||||
public:
|
||||
virtual ::ndk::ScopedAStatus onPlaybackStatus(PlaybackStatus status) override {
|
||||
ALOGD("demux.h: playback status %d", status);
|
||||
return ndk::ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
virtual ::ndk::ScopedAStatus onRecordStatus(RecordStatus status) override {
|
||||
ALOGD("Record Status %hhd", status);
|
||||
return ndk::ScopedAStatus::ok();
|
||||
}
|
||||
};
|
||||
|
||||
class Demux : public BnDemux {
|
||||
public:
|
||||
Demux(int32_t demuxId, uint32_t filterTypes);
|
||||
|
@ -85,6 +103,8 @@ class Demux : public BnDemux {
|
|||
void setIsRecording(bool isRecording);
|
||||
bool isRecording();
|
||||
void startFrontendInputLoop();
|
||||
void readIptvThreadLoop(dtv_plugin* interface, dtv_streamer* streamer, void* buf, size_t size,
|
||||
int timeout_ms, int buffer_timeout);
|
||||
|
||||
/**
|
||||
* A dispatcher to read and dispatch input data to all the started filters.
|
||||
|
@ -167,11 +187,16 @@ class Demux : public BnDemux {
|
|||
|
||||
// Thread handlers
|
||||
std::thread mFrontendInputThread;
|
||||
std::thread mDemuxIptvReadThread;
|
||||
|
||||
// track whether the DVR FMQ for IPTV Playback is full
|
||||
bool mIsIptvDvrFMQFull = false;
|
||||
|
||||
/**
|
||||
* If a specific filter's writing loop is still running
|
||||
*/
|
||||
std::atomic<bool> mFrontendInputThreadRunning;
|
||||
std::atomic<bool> mDemuxIptvReadThreadRunning;
|
||||
std::atomic<bool> mKeepFetchingDataFromFrontend;
|
||||
|
||||
/**
|
||||
|
|
|
@ -236,6 +236,20 @@ void Dvr::playbackThreadLoop() {
|
|||
ALOGD("[Dvr] playback thread ended.");
|
||||
}
|
||||
|
||||
void Dvr::maySendIptvPlaybackStatusCallback() {
|
||||
lock_guard<mutex> lock(mPlaybackStatusLock);
|
||||
int availableToRead = mDvrMQ->availableToRead();
|
||||
int availableToWrite = mDvrMQ->availableToWrite();
|
||||
|
||||
PlaybackStatus newStatus = checkPlaybackStatusChange(availableToWrite, availableToRead,
|
||||
IPTV_PLAYBACK_STATUS_THRESHOLD_HIGH,
|
||||
IPTV_PLAYBACK_STATUS_THRESHOLD_LOW);
|
||||
if (mPlaybackStatus != newStatus) {
|
||||
mCallback->onPlaybackStatus(newStatus);
|
||||
mPlaybackStatus = newStatus;
|
||||
}
|
||||
}
|
||||
|
||||
void Dvr::maySendPlaybackStatusCallback() {
|
||||
lock_guard<mutex> lock(mPlaybackStatusLock);
|
||||
int availableToRead = mDvrMQ->availableToRead();
|
||||
|
@ -443,6 +457,24 @@ bool Dvr::startFilterDispatcher(bool isVirtualFrontend, bool isRecording) {
|
|||
return true;
|
||||
}
|
||||
|
||||
int Dvr::writePlaybackFMQ(void* buf, size_t size) {
|
||||
lock_guard<mutex> lock(mWriteLock);
|
||||
ALOGI("Playback status: %d", mPlaybackStatus);
|
||||
if (mPlaybackStatus == PlaybackStatus::SPACE_FULL) {
|
||||
ALOGW("[Dvr] stops writing and wait for the client side flushing.");
|
||||
return DVR_WRITE_FAILURE_REASON_FMQ_FULL;
|
||||
}
|
||||
ALOGI("availableToWrite before: %zu", mDvrMQ->availableToWrite());
|
||||
if (mDvrMQ->write((int8_t*)buf, size)) {
|
||||
mDvrEventFlag->wake(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY));
|
||||
ALOGI("availableToWrite: %zu", mDvrMQ->availableToWrite());
|
||||
maySendIptvPlaybackStatusCallback();
|
||||
return DVR_WRITE_SUCCESS;
|
||||
}
|
||||
maySendIptvPlaybackStatusCallback();
|
||||
return DVR_WRITE_FAILURE_REASON_UNKNOWN;
|
||||
}
|
||||
|
||||
bool Dvr::writeRecordFMQ(const vector<int8_t>& data) {
|
||||
lock_guard<mutex> lock(mWriteLock);
|
||||
if (mRecordStatus == RecordStatus::OVERFLOW) {
|
||||
|
|
|
@ -43,6 +43,19 @@ using ::android::hardware::EventFlag;
|
|||
|
||||
using DvrMQ = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
|
||||
|
||||
const int DVR_WRITE_SUCCESS = 0;
|
||||
const int DVR_WRITE_FAILURE_REASON_FMQ_FULL = 1;
|
||||
const int DVR_WRITE_FAILURE_REASON_UNKNOWN = 2;
|
||||
|
||||
const int TS_SIZE = 188;
|
||||
const int IPTV_BUFFER_SIZE = TS_SIZE * 7 * 8; // defined in service_streamer_udp in cbs v3 project
|
||||
|
||||
// Thresholds are defined to indicate how full the buffers are.
|
||||
const double HIGH_THRESHOLD_PERCENT = 0.90;
|
||||
const double LOW_THRESHOLD_PERCENT = 0.15;
|
||||
const int IPTV_PLAYBACK_STATUS_THRESHOLD_HIGH = IPTV_BUFFER_SIZE * HIGH_THRESHOLD_PERCENT;
|
||||
const int IPTV_PLAYBACK_STATUS_THRESHOLD_LOW = IPTV_BUFFER_SIZE * LOW_THRESHOLD_PERCENT;
|
||||
|
||||
struct MediaEsMetaData {
|
||||
bool isAudio;
|
||||
int startIndex;
|
||||
|
@ -80,6 +93,7 @@ class Dvr : public BnDvr {
|
|||
* Return false is any of the above processes fails.
|
||||
*/
|
||||
bool createDvrMQ();
|
||||
int writePlaybackFMQ(void* buf, size_t size);
|
||||
bool writeRecordFMQ(const std::vector<int8_t>& data);
|
||||
bool addPlaybackFilter(int64_t filterId, std::shared_ptr<IFilter> filter);
|
||||
bool removePlaybackFilter(int64_t filterId);
|
||||
|
@ -102,6 +116,7 @@ class Dvr : public BnDvr {
|
|||
bool readDataFromMQ();
|
||||
void getMetaDataValue(int& index, int8_t* dataOutputBuffer, int& value);
|
||||
void maySendPlaybackStatusCallback();
|
||||
void maySendIptvPlaybackStatusCallback();
|
||||
void maySendRecordStatusCallback();
|
||||
PlaybackStatus checkPlaybackStatusChange(uint32_t availableToWrite, uint32_t availableToRead,
|
||||
int64_t highThreshold, int64_t lowThreshold);
|
||||
|
|
|
@ -213,20 +213,82 @@ Frontend::~Frontend() {
|
|||
return ::ndk::ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
::ndk::ScopedAStatus Frontend::tune(const FrontendSettings& /* in_settings */) {
|
||||
ALOGV("%s", __FUNCTION__);
|
||||
void Frontend::readTuneByte(dtv_streamer* streamer, void* buf, size_t buf_size, int timeout_ms) {
|
||||
ssize_t bytes_read = mIptvPluginInterface->read_stream(streamer, buf, buf_size, timeout_ms);
|
||||
if (bytes_read == 0) {
|
||||
ALOGI("[ ERROR ] Tune byte couldn't be read.");
|
||||
return;
|
||||
}
|
||||
mCallback->onEvent(FrontendEventType::LOCKED);
|
||||
mIsLocked = true;
|
||||
}
|
||||
|
||||
::ndk::ScopedAStatus Frontend::tune(const FrontendSettings& in_settings) {
|
||||
if (mCallback == nullptr) {
|
||||
ALOGW("[ WARN ] Frontend callback is not set when tune");
|
||||
ALOGW("[ WARN ] Frontend callback is not set for tunin0g");
|
||||
return ::ndk::ScopedAStatus::fromServiceSpecificError(
|
||||
static_cast<int32_t>(Result::INVALID_STATE));
|
||||
}
|
||||
|
||||
if (mType != FrontendType::IPTV) {
|
||||
mTuner->frontendStartTune(mId);
|
||||
}
|
||||
mCallback->onEvent(FrontendEventType::LOCKED);
|
||||
mIsLocked = true;
|
||||
} else {
|
||||
// This is a reference implementation for IPTV. It uses an additional socket buffer.
|
||||
// Vendors can use hardware memory directly to make the implementation more performant.
|
||||
ALOGI("[ INFO ] Frontend type is set to IPTV, tag = %d id=%d", in_settings.getTag(),
|
||||
mId);
|
||||
|
||||
mCallback->onEvent(FrontendEventType::LOCKED);
|
||||
mIsLocked = true;
|
||||
// load udp plugin for reading TS data
|
||||
const char* path = "/vendor/lib/iptv_udp_plugin.so";
|
||||
DtvPlugin* plugin = new DtvPlugin(path);
|
||||
if (!plugin) {
|
||||
ALOGE("Failed to create DtvPlugin, plugin_path is invalid");
|
||||
return ::ndk::ScopedAStatus::fromServiceSpecificError(
|
||||
static_cast<int32_t>(Result::INVALID_ARGUMENT));
|
||||
}
|
||||
bool plugin_loaded = plugin->load();
|
||||
if (!plugin_loaded) {
|
||||
ALOGE("Failed to load plugin");
|
||||
return ::ndk::ScopedAStatus::fromServiceSpecificError(
|
||||
static_cast<int32_t>(Result::INVALID_ARGUMENT));
|
||||
}
|
||||
mIptvPluginInterface = plugin->interface();
|
||||
|
||||
// validate content_url format
|
||||
std::string content_url = in_settings.get<FrontendSettings::Tag::iptv>()->contentUrl;
|
||||
std::string transport_desc = "{ \"uri\": \"" + content_url + "\"}";
|
||||
ALOGI("[ INFO ] transport_desc: %s", transport_desc.c_str());
|
||||
bool is_transport_desc_valid = plugin->validate(transport_desc.c_str());
|
||||
if (!is_transport_desc_valid) { // not of format protocol://ip:port
|
||||
ALOGE("[ INFO ] transport_desc is not valid");
|
||||
return ::ndk::ScopedAStatus::fromServiceSpecificError(
|
||||
static_cast<int32_t>(Result::INVALID_ARGUMENT));
|
||||
}
|
||||
mIptvTransportDescription = transport_desc;
|
||||
|
||||
// create a streamer and open it for reading data
|
||||
dtv_streamer* streamer = mIptvPluginInterface->create_streamer();
|
||||
mIptvPluginStreamer = streamer;
|
||||
int open_fd = mIptvPluginInterface->open_stream(streamer, transport_desc.c_str());
|
||||
if (open_fd < 0) {
|
||||
ALOGE("[ INFO ] could not open stream");
|
||||
return ::ndk::ScopedAStatus::fromServiceSpecificError(
|
||||
static_cast<int32_t>(Result::INVALID_ARGUMENT));
|
||||
}
|
||||
ALOGI("[ INFO ] open_stream successful, open_fd=%d", open_fd);
|
||||
|
||||
size_t buf_size = 1;
|
||||
int timeout_ms = 2000;
|
||||
void* buf = malloc(sizeof(char) * buf_size);
|
||||
if (buf == nullptr) ALOGI("malloc buf failed [TUNE]");
|
||||
ALOGI("[ INFO ] [Tune] Allocated buffer of size %zu", buf_size);
|
||||
mIptvFrontendTuneThread =
|
||||
std::thread(&Frontend::readTuneByte, this, streamer, buf, buf_size, timeout_ms);
|
||||
if (mIptvFrontendTuneThread.joinable()) mIptvFrontendTuneThread.join();
|
||||
free(buf);
|
||||
}
|
||||
|
||||
return ::ndk::ScopedAStatus::ok();
|
||||
}
|
||||
|
@ -1002,6 +1064,18 @@ int32_t Frontend::getFrontendId() {
|
|||
return mId;
|
||||
}
|
||||
|
||||
dtv_plugin* Frontend::getIptvPluginInterface() {
|
||||
return mIptvPluginInterface;
|
||||
}
|
||||
|
||||
string Frontend::getIptvTransportDescription() {
|
||||
return mIptvTransportDescription;
|
||||
}
|
||||
|
||||
dtv_streamer* Frontend::getIptvPluginStreamer() {
|
||||
return mIptvPluginStreamer;
|
||||
}
|
||||
|
||||
bool Frontend::supportsSatellite() {
|
||||
return mType == FrontendType::DVBS || mType == FrontendType::ISDBS ||
|
||||
mType == FrontendType::ISDBS3;
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <iostream>
|
||||
#include <thread>
|
||||
#include "Tuner.h"
|
||||
#include "dtv_plugin.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -60,6 +61,10 @@ class Frontend : public BnFrontend {
|
|||
FrontendType getFrontendType();
|
||||
int32_t getFrontendId();
|
||||
string getSourceFile();
|
||||
dtv_plugin* getIptvPluginInterface();
|
||||
string getIptvTransportDescription();
|
||||
dtv_streamer* getIptvPluginStreamer();
|
||||
void readTuneByte(dtv_streamer* streamer, void* buf, size_t size, int timeout_ms);
|
||||
bool isLocked();
|
||||
void getFrontendInfo(FrontendInfo* _aidl_return);
|
||||
void setTunerService(std::shared_ptr<Tuner> tuner);
|
||||
|
@ -81,6 +86,10 @@ class Frontend : public BnFrontend {
|
|||
std::ifstream mFrontendData;
|
||||
FrontendCapabilities mFrontendCaps;
|
||||
vector<FrontendStatusType> mFrontendStatusCaps;
|
||||
dtv_plugin* mIptvPluginInterface;
|
||||
string mIptvTransportDescription;
|
||||
dtv_streamer* mIptvPluginStreamer;
|
||||
std::thread mIptvFrontendTuneThread;
|
||||
};
|
||||
|
||||
} // namespace tuner
|
||||
|
|
17
tv/tuner/aidl/default/Timer.h
Normal file
17
tv/tuner/aidl/default/Timer.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
#include <chrono>
|
||||
using namespace std::chrono;
|
||||
class Timer {
|
||||
public:
|
||||
Timer() { start_time = steady_clock::now(); }
|
||||
|
||||
~Timer() { stop_time = steady_clock::now(); }
|
||||
|
||||
double get_elapsed_time_ms() {
|
||||
auto current_time = std::chrono::steady_clock::now();
|
||||
return duration_cast<milliseconds>(current_time - start_time).count();
|
||||
}
|
||||
|
||||
private:
|
||||
time_point<steady_clock> start_time;
|
||||
time_point<steady_clock> stop_time;
|
||||
};
|
130
tv/tuner/aidl/default/dtv_plugin.cpp
Normal file
130
tv/tuner/aidl/default/dtv_plugin.cpp
Normal file
|
@ -0,0 +1,130 @@
|
|||
#include "dtv_plugin.h"
|
||||
#include <dlfcn.h>
|
||||
#include <libgen.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
DtvPlugin::DtvPlugin(const char* plugin_path) {
|
||||
path_ = plugin_path;
|
||||
basename_ = basename(path_);
|
||||
module_ = NULL;
|
||||
interface_ = NULL;
|
||||
loaded_ = false;
|
||||
}
|
||||
|
||||
DtvPlugin::~DtvPlugin() {
|
||||
if (module_ != NULL) {
|
||||
if (dlclose(module_)) ALOGE("DtvPlugin: Failed to close plugin '%s'", basename_);
|
||||
}
|
||||
}
|
||||
|
||||
bool DtvPlugin::load() {
|
||||
ALOGI("Loading plugin '%s' from path '%s'", basename_, path_);
|
||||
|
||||
module_ = dlopen(path_, RTLD_LAZY);
|
||||
if (module_ == NULL) {
|
||||
ALOGE("DtvPlugin::Load::Failed to load plugin '%s'", basename_);
|
||||
ALOGE("dlopen error: %s", dlerror());
|
||||
return false;
|
||||
}
|
||||
|
||||
interface_ = (dtv_plugin*)dlsym(module_, "plugin_entry");
|
||||
|
||||
if (interface_ == NULL) {
|
||||
ALOGE("plugin_entry is NULL.");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!interface_->get_transport_types || !interface_->get_streamer_count ||
|
||||
!interface_->validate || !interface_->create_streamer || !interface_->destroy_streamer ||
|
||||
!interface_->open_stream || !interface_->close_stream || !interface_->read_stream) {
|
||||
ALOGW("Plugin: missing one or more callbacks");
|
||||
goto error;
|
||||
}
|
||||
|
||||
loaded_ = true;
|
||||
|
||||
return true;
|
||||
|
||||
error:
|
||||
if (dlclose(module_)) ALOGE("Failed to close plugin '%s'", basename_);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int DtvPlugin::getStreamerCount() {
|
||||
if (!loaded_) {
|
||||
ALOGE("DtvPlugin::GetStreamerCount: Plugin '%s' not loaded!", basename_);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return interface_->get_streamer_count();
|
||||
}
|
||||
|
||||
bool DtvPlugin::isTransportTypeSupported(const char* transport_type) {
|
||||
const char** transport;
|
||||
|
||||
if (!loaded_) {
|
||||
ALOGE("Plugin '%s' not loaded!", basename_);
|
||||
return false;
|
||||
}
|
||||
|
||||
transport = interface_->get_transport_types();
|
||||
if (transport == NULL) return false;
|
||||
|
||||
while (*transport) {
|
||||
if (strcmp(transport_type, *transport) == 0) return true;
|
||||
transport++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DtvPlugin::validate(const char* transport_desc) {
|
||||
if (!loaded_) {
|
||||
ALOGE("Plugin '%s' is not loaded!", basename_);
|
||||
return false;
|
||||
}
|
||||
|
||||
return interface_->validate(transport_desc);
|
||||
}
|
||||
|
||||
bool DtvPlugin::getProperty(const char* key, void* value, int* size) {
|
||||
if (!loaded_) {
|
||||
ALOGE("Plugin '%s' is not loaded!", basename_);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!interface_->get_property) return false;
|
||||
|
||||
*size = interface_->get_property(NULL, key, value, *size);
|
||||
|
||||
return *size < 0 ? false : true;
|
||||
}
|
||||
|
||||
bool DtvPlugin::setProperty(const char* key, const void* value, int size) {
|
||||
int ret;
|
||||
|
||||
if (!loaded_) {
|
||||
ALOGE("Plugin '%s': not loaded!", basename_);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!interface_->set_property) return false;
|
||||
|
||||
ret = interface_->set_property(NULL, key, value, size);
|
||||
|
||||
return ret < 0 ? false : true;
|
||||
}
|
||||
|
||||
struct dtv_plugin* DtvPlugin::interface() {
|
||||
if (!loaded_) {
|
||||
ALOGE("Plugin '%s' is not loaded!", basename_);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return interface_;
|
||||
}
|
||||
|
||||
const char* DtvPlugin::pluginBasename() {
|
||||
return basename_;
|
||||
}
|
31
tv/tuner/aidl/default/dtv_plugin.h
Normal file
31
tv/tuner/aidl/default/dtv_plugin.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
#ifndef LIVE_DTV_PLUGIN_H_
|
||||
#define LIVE_DTV_PLUGIN_H_
|
||||
|
||||
#include <fstream>
|
||||
#include "dtv_plugin_api.h"
|
||||
|
||||
class DtvPlugin {
|
||||
public:
|
||||
DtvPlugin(const char* plugin_path);
|
||||
~DtvPlugin();
|
||||
|
||||
bool load();
|
||||
int getStreamerCount();
|
||||
bool validate(const char* transport_desc);
|
||||
bool isTransportTypeSupported(const char* transport_type);
|
||||
// /* plugin-wide properties */
|
||||
bool getProperty(const char* key, void* value, int* size);
|
||||
bool setProperty(const char* key, const void* value, int size);
|
||||
|
||||
struct dtv_plugin* interface();
|
||||
const char* pluginBasename();
|
||||
|
||||
protected:
|
||||
const char* path_;
|
||||
char* basename_;
|
||||
void* module_;
|
||||
struct dtv_plugin* interface_;
|
||||
bool loaded_;
|
||||
};
|
||||
|
||||
#endif // LIVE_DTV_PLUGIN_H_
|
137
tv/tuner/aidl/default/dtv_plugin_api.h
Normal file
137
tv/tuner/aidl/default/dtv_plugin_api.h
Normal file
|
@ -0,0 +1,137 @@
|
|||
#ifndef LIVE_DTV_PLUGIN_API_H_
|
||||
#define LIVE_DTV_PLUGIN_API_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct dtv_streamer;
|
||||
|
||||
struct dtv_plugin {
|
||||
uint32_t version;
|
||||
|
||||
/**
|
||||
* get_transport_types() - Retrieve a list of supported transport types.
|
||||
*
|
||||
* Return: A NULL-terminated list of supported transport types.
|
||||
*/
|
||||
const char** (*get_transport_types)(void);
|
||||
|
||||
/**
|
||||
* get_streamer_count() - Get number of streamers that can be created.
|
||||
*
|
||||
* Return: The number of streamers that can be created.
|
||||
*/
|
||||
int (*get_streamer_count)(void);
|
||||
|
||||
/**
|
||||
* validate() - Check if transport description is valid.
|
||||
* @transport_desc: NULL-terminated transport description in json format.
|
||||
*
|
||||
* Return: 1 if valid, 0 otherwise.
|
||||
*/
|
||||
int (*validate)(const char* transport_desc);
|
||||
|
||||
/**
|
||||
* create_streamer() - Create a streamer object.
|
||||
*
|
||||
* Return: A pointer to a new streamer object.
|
||||
*/
|
||||
struct dtv_streamer* (*create_streamer)(void);
|
||||
|
||||
/**
|
||||
* destroy_streamer() - Free a streamer object and all associated resources.
|
||||
* @st: Pointer to a streamer object
|
||||
*/
|
||||
void (*destroy_streamer)(struct dtv_streamer* streamer);
|
||||
|
||||
/**
|
||||
* set_property() - Set a key/value pair property.
|
||||
* @streamer: Pointer to a streamer object (may be NULL for plugin-wide properties).
|
||||
* @key: NULL-terminated property name.
|
||||
* @value: Property value.
|
||||
* @size: Property value size.
|
||||
*
|
||||
* Return: 0 if success, -1 otherwise.
|
||||
*/
|
||||
int (*set_property)(struct dtv_streamer* streamer, const char* key, const void* value,
|
||||
size_t size);
|
||||
|
||||
/**
|
||||
* get_property() - Get a property's value.
|
||||
* @streamer: Pointer to a streamer (may be NULL for plugin-wide properties).
|
||||
* @key: NULL-terminated property name.
|
||||
* @value: Property value.
|
||||
* @size: Property value size.
|
||||
*
|
||||
* Return: >= 0 if success, -1 otherwise.
|
||||
*
|
||||
* If size is 0, get_property will return the size needed to hold the value.
|
||||
*/
|
||||
int (*get_property)(struct dtv_streamer* streamer, const char* key, void* value, size_t size);
|
||||
|
||||
/**
|
||||
* add_pid() - Add a TS filter on a given pid.
|
||||
* @streamer: The streamer that outputs the TS.
|
||||
* @pid: The pid to add to the TS output.
|
||||
*
|
||||
* Return: 0 if success, -1 otherwise.
|
||||
*
|
||||
* This function is optional but can be useful if a hardware remux is
|
||||
* available.
|
||||
*/
|
||||
int (*add_pid)(struct dtv_streamer* streamer, int pid);
|
||||
|
||||
/**
|
||||
* remove_pid() - Remove a TS filter on a given pid.
|
||||
* @streamer: The streamer that outputs the TS.
|
||||
* @pid: The pid to remove from the TS output.
|
||||
*
|
||||
* Return: 0 if success, -1 otherwise.
|
||||
*
|
||||
* This function is optional.
|
||||
*/
|
||||
int (*remove_pid)(struct dtv_streamer* streamer, int pid);
|
||||
|
||||
/**
|
||||
* open_stream() - Open a stream from a transport description.
|
||||
* @streamer: The streamer which will handle the stream.
|
||||
* @transport_desc: NULL-terminated transport description in json format.
|
||||
*
|
||||
* The streamer will allocate the resources and make the appropriate
|
||||
* connections to handle this transport.
|
||||
* This function returns a file descriptor that can be polled for events.
|
||||
*
|
||||
* Return: A file descriptor if success, -1 otherwise.
|
||||
*/
|
||||
int (*open_stream)(struct dtv_streamer* streamer, const char* transport_desc);
|
||||
|
||||
/**
|
||||
* close_stream() - Release an open stream.
|
||||
* @streamer: The streamer from which the stream should be released.
|
||||
*/
|
||||
void (*close_stream)(struct dtv_streamer* streamer);
|
||||
|
||||
/**
|
||||
* read_stream() - Read stream data.
|
||||
* @streamer: The streamer to read from.
|
||||
* @buf: The destination buffer.
|
||||
* @count: The number of bytes to read.
|
||||
* @timeout_ms: Timeout in ms.
|
||||
*
|
||||
* Return: The number of bytes read, -1 if error.
|
||||
*/
|
||||
ssize_t (*read_stream)(struct dtv_streamer* streamer, void* buf, size_t count, int timeout_ms);
|
||||
};
|
||||
|
||||
struct dtv_plugin_event {
|
||||
int id;
|
||||
char data[0];
|
||||
};
|
||||
|
||||
enum {
|
||||
DTV_PLUGIN_EVENT_SIGNAL_LOST = 1,
|
||||
DTV_PLUGIN_EVENT_SIGNAL_READY,
|
||||
};
|
||||
|
||||
#define PROPERTY_STATISTICS "statistics"
|
||||
|
||||
#endif // LIVE_DTV_PLUGIN_API_H_
|
Loading…
Reference in a new issue