diff --git a/tv/tuner/aidl/default/Android.bp b/tv/tuner/aidl/default/Android.bp index 65fa82163a..ed97d9cfc7 100644 --- a/tv/tuner/aidl/default/Android.bp +++ b/tv/tuner/aidl/default/Android.bp @@ -23,6 +23,7 @@ cc_defaults { "TimeFilter.cpp", "Tuner.cpp", "service.cpp", + "dtv_plugin.cpp", ], static_libs: [ "libaidlcommonsupport", diff --git a/tv/tuner/aidl/default/Demux.cpp b/tv/tuner/aidl/default/Demux.cpp index 11e7131220..5be819e991 100644 --- a/tv/tuner/aidl/default/Demux.cpp +++ b/tv/tuner/aidl/default/Demux.cpp @@ -20,7 +20,9 @@ #include #include +#include #include +#include #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; +using AidlMQ = AidlMessageQueue; +using AidlMQDesc = MQDescriptor; + #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& in_cb, + std::shared_ptr* _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(Result::INVALID_ARGUMENT)); + } + + set::iterator it; + switch (in_type) { + case DvrType::PLAYBACK: + mDvrPlayback = ndk::SharedRefBase::make(in_type, in_bufferSize, in_cb, + this->ref()); + if (!mDvrPlayback->createDvrMQ()) { + ALOGE("[Demux] cannot create dvr message queue"); + mDvrPlayback = nullptr; + *_aidl_return = mDvrPlayback; + return ::ndk::ScopedAStatus::fromServiceSpecificError( + static_cast(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(Result::UNKNOWN_ERROR)); + } + } + + ALOGI("Playback normal case"); + + *_aidl_return = mDvrPlayback; + return ::ndk::ScopedAStatus::ok(); + case DvrType::RECORD: + mDvrRecord = ndk::SharedRefBase::make(in_type, in_bufferSize, in_cb, + this->ref()); + if (!mDvrRecord->createDvrMQ()) { + mDvrRecord = nullptr; + *_aidl_return = mDvrRecord; + return ::ndk::ScopedAStatus::fromServiceSpecificError( + static_cast(Result::UNKNOWN_ERROR)); + } + + *_aidl_return = mDvrRecord; + return ::ndk::ScopedAStatus::ok(); + default: + *_aidl_return = nullptr; + return ::ndk::ScopedAStatus::fromServiceSpecificError( + static_cast(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 %d 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(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 iptvDvr; + + std::shared_ptr dvrPlaybackCallback = + ::ndk::SharedRefBase::make(); + + ::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(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(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& in_cb, - std::shared_ptr* _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(Result::INVALID_ARGUMENT)); - } - - set::iterator it; - switch (in_type) { - case DvrType::PLAYBACK: - mDvrPlayback = ndk::SharedRefBase::make(in_type, in_bufferSize, in_cb, - this->ref()); - if (!mDvrPlayback->createDvrMQ()) { - mDvrPlayback = nullptr; - *_aidl_return = mDvrPlayback; - return ::ndk::ScopedAStatus::fromServiceSpecificError( - static_cast(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(Result::UNKNOWN_ERROR)); - } - } - - *_aidl_return = mDvrPlayback; - return ::ndk::ScopedAStatus::ok(); - case DvrType::RECORD: - mDvrRecord = ndk::SharedRefBase::make(in_type, in_bufferSize, in_cb, - this->ref()); - if (!mDvrRecord->createDvrMQ()) { - mDvrRecord = nullptr; - *_aidl_return = mDvrRecord; - return ::ndk::ScopedAStatus::fromServiceSpecificError( - static_cast(Result::UNKNOWN_ERROR)); - } - - *_aidl_return = mDvrRecord; - return ::ndk::ScopedAStatus::ok(); - default: - *_aidl_return = nullptr; - return ::ndk::ScopedAStatus::fromServiceSpecificError( - static_cast(Result::INVALID_ARGUMENT)); - } -} - ::ndk::ScopedAStatus Demux::connectCiCam(int32_t in_ciCamId) { ALOGV("%s", __FUNCTION__); diff --git a/tv/tuner/aidl/default/Demux.h b/tv/tuner/aidl/default/Demux.h index 7d7aee4c89..a23063f8ad 100644 --- a/tv/tuner/aidl/default/Demux.h +++ b/tv/tuner/aidl/default/Demux.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include #include @@ -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; +using AidlMQ = AidlMessageQueue; +using AidlMQDesc = MQDescriptor; 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 mFrontendInputThreadRunning; + std::atomic mDemuxIptvReadThreadRunning; std::atomic mKeepFetchingDataFromFrontend; /** diff --git a/tv/tuner/aidl/default/Dvr.cpp b/tv/tuner/aidl/default/Dvr.cpp index c046ae3130..9fad53b70a 100644 --- a/tv/tuner/aidl/default/Dvr.cpp +++ b/tv/tuner/aidl/default/Dvr.cpp @@ -236,6 +236,20 @@ void Dvr::playbackThreadLoop() { ALOGD("[Dvr] playback thread ended."); } +void Dvr::maySendIptvPlaybackStatusCallback() { + lock_guard 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 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 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: %d", mDvrMQ->availableToWrite()); + if (mDvrMQ->write((int8_t*)buf, size)) { + mDvrEventFlag->wake(static_cast(DemuxQueueNotifyBits::DATA_READY)); + ALOGI("availableToWrite: %d", mDvrMQ->availableToWrite()); + maySendIptvPlaybackStatusCallback(); + return DVR_WRITE_SUCCESS; + } + maySendIptvPlaybackStatusCallback(); + return DVR_WRITE_FAILURE_REASON_UNKNOWN; +} + bool Dvr::writeRecordFMQ(const vector& data) { lock_guard lock(mWriteLock); if (mRecordStatus == RecordStatus::OVERFLOW) { diff --git a/tv/tuner/aidl/default/Dvr.h b/tv/tuner/aidl/default/Dvr.h index 293c533cfc..4af187bce4 100644 --- a/tv/tuner/aidl/default/Dvr.h +++ b/tv/tuner/aidl/default/Dvr.h @@ -43,6 +43,19 @@ using ::android::hardware::EventFlag; using DvrMQ = AidlMessageQueue; +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& data); bool addPlaybackFilter(int64_t filterId, std::shared_ptr 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); diff --git a/tv/tuner/aidl/default/Frontend.cpp b/tv/tuner/aidl/default/Frontend.cpp index cd072bfe8d..6bdbac55ff 100644 --- a/tv/tuner/aidl/default/Frontend.cpp +++ b/tv/tuner/aidl/default/Frontend.cpp @@ -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(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(Result::INVALID_ARGUMENT)); + } + bool plugin_loaded = plugin->load(); + if (!plugin_loaded) { + ALOGE("Failed to load plugin"); + return ::ndk::ScopedAStatus::fromServiceSpecificError( + static_cast(Result::INVALID_ARGUMENT)); + } + mIptvPluginInterface = plugin->interface(); + + // validate content_url format + std::string content_url = in_settings.get()->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(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(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; diff --git a/tv/tuner/aidl/default/Frontend.h b/tv/tuner/aidl/default/Frontend.h index 85bd636cc4..17a1aeeb40 100644 --- a/tv/tuner/aidl/default/Frontend.h +++ b/tv/tuner/aidl/default/Frontend.h @@ -21,6 +21,7 @@ #include #include #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); @@ -81,6 +86,10 @@ class Frontend : public BnFrontend { std::ifstream mFrontendData; FrontendCapabilities mFrontendCaps; vector mFrontendStatusCaps; + dtv_plugin* mIptvPluginInterface; + string mIptvTransportDescription; + dtv_streamer* mIptvPluginStreamer; + std::thread mIptvFrontendTuneThread; }; } // namespace tuner diff --git a/tv/tuner/aidl/default/Timer.h b/tv/tuner/aidl/default/Timer.h new file mode 100644 index 0000000000..c6327cbbe7 --- /dev/null +++ b/tv/tuner/aidl/default/Timer.h @@ -0,0 +1,17 @@ +#include +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(current_time - start_time).count(); + } + + private: + time_point start_time; + time_point stop_time; +}; \ No newline at end of file diff --git a/tv/tuner/aidl/default/dtv_plugin.cpp b/tv/tuner/aidl/default/dtv_plugin.cpp new file mode 100644 index 0000000000..4e73ee5878 --- /dev/null +++ b/tv/tuner/aidl/default/dtv_plugin.cpp @@ -0,0 +1,130 @@ +#include "dtv_plugin.h" +#include +#include +#include + +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_; +} diff --git a/tv/tuner/aidl/default/dtv_plugin.h b/tv/tuner/aidl/default/dtv_plugin.h new file mode 100644 index 0000000000..0ee5489d2a --- /dev/null +++ b/tv/tuner/aidl/default/dtv_plugin.h @@ -0,0 +1,31 @@ +#ifndef LIVE_DTV_PLUGIN_H_ +#define LIVE_DTV_PLUGIN_H_ + +#include +#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_ diff --git a/tv/tuner/aidl/default/dtv_plugin_api.h b/tv/tuner/aidl/default/dtv_plugin_api.h new file mode 100644 index 0000000000..8fe7c1d24e --- /dev/null +++ b/tv/tuner/aidl/default/dtv_plugin_api.h @@ -0,0 +1,137 @@ +#ifndef LIVE_DTV_PLUGIN_API_H_ +#define LIVE_DTV_PLUGIN_API_H_ + +#include + +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_