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:
sadiqsada 2023-11-02 16:45:31 -07:00
parent 51cc83a7b1
commit 56c98294a2
11 changed files with 645 additions and 62 deletions

View file

@ -23,6 +23,7 @@ cc_defaults {
"TimeFilter.cpp",
"Tuner.cpp",
"service.cpp",
"dtv_plugin.cpp",
],
static_libs: [
"libaidlcommonsupport",

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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;
};

View 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_;
}

View 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_

View 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_