diff options
author | randomdan <randomdan@localhost> | 2014-03-13 19:42:07 +0000 |
---|---|---|
committer | randomdan <randomdan@localhost> | 2014-03-13 19:42:07 +0000 |
commit | ab1eee942e75874739ce5f0b4ba289aac5cc3faf (patch) | |
tree | 6e43828794fe0c0c5c9921ec1911695b67357c50 /p2pvr/devices | |
parent | Expose more of the interface (diff) | |
download | p2pvr-ab1eee942e75874739ce5f0b4ba289aac5cc3faf.tar.bz2 p2pvr-ab1eee942e75874739ce5f0b4ba289aac5cc3faf.tar.xz p2pvr-ab1eee942e75874739ce5f0b4ba289aac5cc3faf.zip |
Restructure into more sensibly arranged libs
Diffstat (limited to 'p2pvr/devices')
-rw-r--r-- | p2pvr/devices/Jamfile.jam | 27 | ||||
-rw-r--r-- | p2pvr/devices/frontend.cpp | 39 | ||||
-rw-r--r-- | p2pvr/devices/frontend.h | 32 | ||||
-rw-r--r-- | p2pvr/devices/frontends/ofdm.cpp | 159 | ||||
-rw-r--r-- | p2pvr/devices/localDevices.cpp | 189 | ||||
-rw-r--r-- | p2pvr/devices/localDevices.h | 52 | ||||
-rw-r--r-- | p2pvr/devices/pch.hpp | 24 | ||||
-rw-r--r-- | p2pvr/devices/tuner.cpp | 421 | ||||
-rw-r--r-- | p2pvr/devices/tuner.h | 83 | ||||
-rw-r--r-- | p2pvr/devices/tunerSendSi.cpp | 81 | ||||
-rw-r--r-- | p2pvr/devices/tunerSendSi.h | 24 | ||||
-rw-r--r-- | p2pvr/devices/tunerSendTs.cpp | 77 | ||||
-rw-r--r-- | p2pvr/devices/tunerSendTs.h | 22 |
13 files changed, 1230 insertions, 0 deletions
diff --git a/p2pvr/devices/Jamfile.jam b/p2pvr/devices/Jamfile.jam new file mode 100644 index 0000000..a3452a4 --- /dev/null +++ b/p2pvr/devices/Jamfile.jam @@ -0,0 +1,27 @@ +lib boost_system ; +lib boost_filesystem ; + +cpp-pch pch : pch.hpp : + <library>boost_system + <library>boost_filesystem + <library>..//p2common + <implicit-dependency>../ice//p2pvrice +; + +lib p2pvrdevices : + pch + [ glob-tree *.cpp ] + : + <library>boost_system + <library>boost_filesystem + <library>../dvb//p2pvrdvb + <library>../ice//p2pvrice + <library>../lib//p2pvrlib + <library>..//p2common + <implicit-dependency>../ice//p2pvrice + : : + <library>boost_filesystem + <implicit-dependency>../ice//p2pvrice + <library>boost_system + <include>. + ; diff --git a/p2pvr/devices/frontend.cpp b/p2pvr/devices/frontend.cpp new file mode 100644 index 0000000..54870a1 --- /dev/null +++ b/p2pvr/devices/frontend.cpp @@ -0,0 +1,39 @@ +#include <pch.hpp> +#include "frontend.h" +#include "tuner.h" +#include <logger.h> +#include <sys/ioctl.h> +#include <linux/dvb/frontend.h> +#include <instanceStore.impl.h> + +Frontend::Frontend(Tuner * t, int fd, const struct dvb_frontend_info & i) : + tuner(t), + frontendFD(fd), + fe_info(i) +{ +} + +Frontend::~Frontend() +{ + close(frontendFD); +} + +const struct dvb_frontend_info & +Frontend::Info() const +{ + return fe_info; +} + +fe_status +Frontend::GetStatus() const +{ + fe_status_t status; + if (ioctl(frontendFD, FE_READ_STATUS, &status) < 0) { + Logger()->messagebf(LOG_ERR, "Reading frontend %s status failed (%s:%d)", tuner->Device(), strerror(errno), errno); + throw P2PVR::DeviceError(tuner->Device(), strerror(errno), errno); + } + return status; +} + +INSTANTIATESTORE(fe_type, FrontendLoader); + diff --git a/p2pvr/devices/frontend.h b/p2pvr/devices/frontend.h new file mode 100644 index 0000000..d33353d --- /dev/null +++ b/p2pvr/devices/frontend.h @@ -0,0 +1,32 @@ +#ifndef P2PVR_FRONTEND_H +#define P2PVR_FRONTEND_H + +#include <linux/dvb/frontend.h> +#include <genLoader.h> +#include <dvb.h> + +class Tuner; + +class Frontend { + public: + typedef boost::function<bool(long)> OnFrequencyFound; + Frontend(Tuner *, int fd, const struct dvb_frontend_info &); + virtual ~Frontend(); + + fe_status_t GetStatus() const; + virtual void TuneTo(const DVBSI::DeliveryPtr &) const = 0; + virtual void FrequencyScan(const OnFrequencyFound & off) const = 0; + virtual std::string Type() const = 0; + const struct dvb_frontend_info & Info() const; + + protected: + const Tuner * tuner; + const int frontendFD; + const struct dvb_frontend_info fe_info; +}; + +typedef GenLoader<Frontend, fe_type, Tuner *, int, const struct dvb_frontend_info &> FrontendLoader; +typedef boost::shared_ptr<Frontend> FrontendPtr; + +#endif + diff --git a/p2pvr/devices/frontends/ofdm.cpp b/p2pvr/devices/frontends/ofdm.cpp new file mode 100644 index 0000000..93e0d86 --- /dev/null +++ b/p2pvr/devices/frontends/ofdm.cpp @@ -0,0 +1,159 @@ +#include <pch.hpp> +#include "../frontend.h" +#include "../tuner.h" +#include <sys/ioctl.h> +#include <logger.h> +#include <linux/dvb/frontend.h> + +#define FREQ_OFFSET_MIN 0 +#define FREQ_OFFSET_MAX 4 + +class Frontend_OFDM : public Frontend { + public: + Frontend_OFDM(Tuner * t, int fd, const struct dvb_frontend_info & i) : Frontend(t, fd, i) { } + + void TuneTo(const DVBSI::DeliveryPtr & mp) const + { + auto td = DVBSI::TerrestrialDeliveryPtr::dynamicCast(mp); + if (!td) { + throw P2PVR::IncorrectDeliveryType(); + } + dvb_frontend_parameters feparams; + memset(&feparams, 0, sizeof(dvb_frontend_parameters)); + feparams.frequency = td->Frequency; + feparams.inversion = INVERSION_OFF; + feparams.u.ofdm.bandwidth = (fe_bandwidth)td->Bandwidth; + feparams.u.ofdm.code_rate_HP = (fe_code_rate_t)td->CodeRateHP; + feparams.u.ofdm.code_rate_LP = (fe_code_rate_t)td->CodeRateLP; + feparams.u.ofdm.constellation = (fe_modulation_t)td->Constellation; + feparams.u.ofdm.transmission_mode = (fe_transmit_mode)td->TransmissionMode; + feparams.u.ofdm.guard_interval = (fe_guard_interval_t)td->GuardInterval; + feparams.u.ofdm.hierarchy_information = (fe_hierarchy_t)td->Hierarchy; + SetParameters(feparams); + WaitForLock(); + } + + dvb_frontend_parameters GetParameters() const + { + dvb_frontend_parameters feparams; + memset(&feparams, 0, sizeof(dvb_frontend_parameters)); + if (ioctl(frontendFD, FE_GET_FRONTEND, &feparams) < 0) { + Logger()->messagebf(LOG_ERR, "Reading frontend parameters failed (%s:%d)", tuner->Device(), strerror(errno), errno); + throw P2PVR::DeviceError(tuner->Device(), strerror(errno), errno); + } + return feparams; + } + + void WaitForLock() const + { + fe_status_t status = (fe_status_t)0; + // Wait for something + for (int x = Tuner::TuningTimeout / 10; x > 0 && (status = GetStatus()) == 0; x -= 1) { + usleep(10000); + } + // Was it useful? + if (!(status & (FE_HAS_SIGNAL | FE_HAS_CARRIER))) { + Logger()->messagebf(LOG_ERR, "Tuning of device %s failed (No signal or carrier: 0x%02x)", tuner->Device(), status); + throw P2PVR::DeviceError(tuner->Device(), "No carrier", 0); + } + // Wait for lock + for (int x = Tuner::LockTimeout / 10; x > 0 && ((status = GetStatus()) & FE_HAS_LOCK) == 0; x -= 1) { + usleep(10000); + } + if (!(status & FE_HAS_LOCK)) { + Logger()->messagebf(LOG_ERR, "Tuning of device %s failed (%s)", tuner->Device(), "No lock"); + throw P2PVR::DeviceError(tuner->Device(), "No lock", 0); + } + } + + void SetParameters(const dvb_frontend_parameters & feparams) const + { + if (ioctl(frontendFD, FE_SET_FRONTEND, &feparams) < 0) { + Logger()->messagebf(LOG_ERR, "Tuning of device %s failed (%s:%d)", tuner->Device(), strerror(errno), errno); + throw P2PVR::DeviceError(tuner->Device(), strerror(errno), errno); + } + } + + std::string Type() const + { + return "OFDM (DVB-T)"; + } + + enum Country { DVBT_AU, DVBT_DE, DVBT_FR, DVBT_GB }; + + static uint32_t FrequencyForCountry(Country country, int channel) + { + switch (country) { + case DVBT_AU: //AUSTRALIA, 7MHz step list + switch (channel) { + case 5 ... 12: return 142500000; + case 21 ... 69: return 333500000; + } + case DVBT_DE: //GERMANY + case DVBT_FR: //FRANCE, +/- offset 166kHz & +offset 332kHz & +offset 498kHz + case DVBT_GB: //UNITED KINGDOM, +/- offset + switch (channel) { + case 5 ... 12: return 142500000; // VHF unused in FRANCE, skip those in offset loop + case 21 ... 69: return 306000000; + } + } + return 0; + } + static uint32_t FrequencyStepForCountry(Country country, int channel) + { + switch (country) { + case DVBT_AU: + return 7000000; // dvb-t australia, 7MHz step + case DVBT_DE: + case DVBT_FR: + case DVBT_GB: + switch (channel) { // dvb-t europe, 7MHz VHF ch5..12, all other 8MHz + case 5 ... 12: return 7000000; + case 21 ... 69: return 8000000; + } + } + return 0; + } + static uint32_t ChannelFrequencyForCountry(Country country, int channel, int) + { + return FrequencyForCountry(country, channel) + (channel * FrequencyStepForCountry(country, channel)); + } + + void FrequencyScan(const OnFrequencyFound & onFrequencyFound) const + { + struct dvb_frontend_parameters feparams; + memset(&feparams, 0, sizeof(dvb_frontend_parameters)); + feparams.inversion = (fe_info.caps & FE_CAN_INVERSION_AUTO ? INVERSION_AUTO : INVERSION_OFF); + feparams.u.ofdm.constellation = (fe_info.caps & FE_CAN_QAM_AUTO ? QAM_AUTO : QAM_64); + feparams.u.ofdm.hierarchy_information = HIERARCHY_NONE; + + for (int channel = 0; channel < 134; channel += 1) { + for (uint32_t offset = FREQ_OFFSET_MIN; offset <= 0/*FREQ_OFFSET_MAX*/; offset += 1) { + feparams.frequency = ChannelFrequencyForCountry(DVBT_GB, channel, offset); + if (feparams.frequency == 0) { + continue; + } + if (fe_info.frequency_min > feparams.frequency || fe_info.frequency_max < feparams.frequency) { + Logger()->messagebf(LOG_WARNING, "Channel %d, freq (%d Hz) outside card range", channel, feparams.frequency); + continue; + } + try { + Logger()->messagebf(LOG_DEBUG, "Channel %d, Frequency %d Hz", channel, feparams.frequency); + SetParameters(feparams); + WaitForLock(); + Logger()->messagebf(LOG_INFO, "Found multiplex at %d Hz", feparams.frequency); + Logger()->messagebf(LOG_DEBUG, "frequency %d", feparams.frequency); + if (onFrequencyFound(feparams.frequency)) { + return; + } + } + catch (const P2PVR::DeviceError &) { + // Moving on... + } + } + } + } +}; + +DECLARE_GENERIC_LOADER(FE_OFDM, FrontendLoader, Frontend_OFDM); + diff --git a/p2pvr/devices/localDevices.cpp b/p2pvr/devices/localDevices.cpp new file mode 100644 index 0000000..7a43a15 --- /dev/null +++ b/p2pvr/devices/localDevices.cpp @@ -0,0 +1,189 @@ +#include <pch.hpp> +#include "localDevices.h" +#include <Ice/Ice.h> +#include "tuner.h" +#include "bindTimerTask.h" +#include <logger.h> + +LocalDevices::Devices LocalDevices::devices; +std::mutex LocalDevices::lock; + +DECLARE_OPTIONS(LocalDevices, "P2PVR Devices") +("p2pvr.localdevices.frontend", + Options::functions( + [](const VariableType & df) { devices.insert(Devices::value_type(df.as<std::string>(), OpenTunerPtr())); }, + []{ devices.clear(); }), + "Frontend of DVB devices to use (/dev/dvb/adapterX/frontendY)") +END_OPTIONS(LocalDevices); + +LocalDevices::LocalDevices(Ice::ObjectAdapterPtr adapter, IceUtil::TimerPtr t) : + timer(t), + clientCheck(new BindTimerTask(boost::bind(&LocalDevices::ClientCheck, this, adapter))) +{ + Logger()->message(LOG_DEBUG, __PRETTY_FUNCTION__); + timer->scheduleRepeated(clientCheck, IceUtil::Time::seconds(30)); +} + +LocalDevices::~LocalDevices() +{ + Logger()->message(LOG_DEBUG, __PRETTY_FUNCTION__); + timer->cancel(clientCheck); +} + +void +LocalDevices::ClientCheck(Ice::ObjectAdapterPtr adapter) +{ + std::lock_guard<std::mutex> g(lock); + BOOST_FOREACH(auto & device, devices) { + if (device.second && device.second->tuner->GetLastUsedTime() < time(NULL) - 30) { + Logger()->messagebf(LOG_DEBUG, "%s: Device %s no longer in use", __PRETTY_FUNCTION__, device.first); + auto id = device.second->tuner->ice_getIdentity(); + if (adapter->find(id)) { + adapter->remove(id); + } + device.second.reset(); + } + } +} + +P2PVR::TunerPrx +LocalDevices::GetTunerSpecific(const DVBSI::DeliveryPtr & delivery, const Ice::Current & ice) +{ + std::lock_guard<std::mutex> g(lock); + Logger()->messagebf(LOG_DEBUG, "%s: Searching for an open sharable tuner (frequency %d)", __PRETTY_FUNCTION__, delivery->Frequency); + auto openTuner = std::find_if(devices.begin(), devices.end(), [delivery](const Devices::value_type & ot) { + return ot.second && !ot.second->openedPrivate && ot.second->delivery && ot.second->delivery->Frequency == delivery->Frequency; + }); + if (openTuner != devices.end()) { + openTuner->second->clients += 1; + return openTuner->second->tuner; + } + + openTuner = std::find_if(devices.begin(), devices.end(), [](const Devices::value_type & ot) { return !ot.second; }); + if (openTuner == devices.end()) { + Logger()->messagebf(LOG_DEBUG, "%s: None suitable and none free (frequency %d)", + __PRETTY_FUNCTION__, delivery->Frequency); + throw P2PVR::NoSuitableDeviceAvailable(); + } + + Logger()->messagebf(LOG_DEBUG, "%s: Opening a sharable tuner (frequency %d, frontend %s)", + __PRETTY_FUNCTION__, delivery->Frequency, openTuner->first); + P2PVR::PrivateTunerPtr t = new Tuner(openTuner->first); + t->TuneTo(delivery, ice); + auto tuner = P2PVR::PrivateTunerPrx::checkedCast(ice.adapter->addWithUUID(t)); + openTuner->second = OpenTunerPtr(new OpenTuner(delivery, tuner, false)); + + Logger()->messagebf(LOG_DEBUG, "%s: Tuned, returning (frequency %d, frontend %s)", + __PRETTY_FUNCTION__, delivery->Frequency, openTuner->first); + return tuner; +} + +P2PVR::TunerPrx +LocalDevices::GetTunerAny(short , const DVBSI::DeliveryPtr & delivery, const Ice::Current & ice) +{ + std::lock_guard<std::mutex> g(lock); + Logger()->messagebf(LOG_DEBUG, "%s: Searching for an open sharable tuner any frequency", __PRETTY_FUNCTION__); + auto openTuner = std::find_if(devices.begin(), devices.end(), [delivery](const Devices::value_type & ot) { + return ot.second && !ot.second->openedPrivate && ot.second->delivery; + }); + if (openTuner != devices.end()) { + openTuner->second->clients += 1; + return openTuner->second->tuner; + } + + openTuner = std::find_if(devices.begin(), devices.end(), [](const Devices::value_type & ot) { return !ot.second; }); + if (openTuner == devices.end()) { + Logger()->messagebf(LOG_DEBUG, "%s: None suitable and none free (frequency %d)", + __PRETTY_FUNCTION__, delivery->Frequency); + throw P2PVR::NoSuitableDeviceAvailable(); + } + + Logger()->messagebf(LOG_DEBUG, "%s: Opening a sharable tuner (frequency %d, frontend %s)", + __PRETTY_FUNCTION__, delivery->Frequency, openTuner->first); + P2PVR::PrivateTunerPtr t = new Tuner(openTuner->first); + t->TuneTo(delivery, ice); + auto tuner = P2PVR::PrivateTunerPrx::checkedCast(ice.adapter->addWithUUID(t)); + openTuner->second = OpenTunerPtr(new OpenTuner(delivery, tuner, false)); + + Logger()->messagebf(LOG_DEBUG, "%s: Tuned, returning (frequency %d, frontend %s)", + __PRETTY_FUNCTION__, delivery->Frequency, openTuner->first); + return tuner; +} + +P2PVR::PrivateTunerPrx +LocalDevices::GetPrivateTuner(short , const Ice::Current & ice) +{ + std::lock_guard<std::mutex> g(lock); + Logger()->messagebf(LOG_DEBUG, "%s: Opening a private tuner", __PRETTY_FUNCTION__); + auto openTuner = std::find_if(devices.begin(), devices.end(), [](const Devices::value_type & ot) { return !ot.second; }); + if (openTuner == devices.end()) { + Logger()->messagebf(LOG_DEBUG, "%s: None free", __PRETTY_FUNCTION__); + throw P2PVR::NoSuitableDeviceAvailable(); + } + + Logger()->messagebf(LOG_DEBUG, "%s: Opening a private tuner (frontend %s)", + __PRETTY_FUNCTION__, openTuner->first); + auto tuner = P2PVR::PrivateTunerPrx::checkedCast(ice.adapter->addWithUUID(new Tuner(openTuner->first))); + openTuner->second = OpenTunerPtr(new OpenTuner(NULL, tuner, true)); + + return tuner; +} + +void +LocalDevices::ReleaseTuner(const P2PVR::TunerPrx & tuner, const Ice::Current & ice) +{ + std::lock_guard<std::mutex> g(lock); + Logger()->messagebf(LOG_DEBUG, "%s", __PRETTY_FUNCTION__); + auto openTuner = std::find_if(devices.begin(), devices.end(), [tuner](const Devices::value_type & ot) { + return ot.second && ot.second->tuner == tuner; + }); + if (openTuner == devices.end()) { + Logger()->messagebf(LOG_DEBUG, "%s: Not one of mine", __PRETTY_FUNCTION__); + return; + } + Logger()->messagebf(LOG_DEBUG, "%s: Locally owned deivce %s", __PRETTY_FUNCTION__, openTuner->first); + openTuner->second->clients -= 1; + if (openTuner->second->clients == 0) { + auto id = tuner->ice_getIdentity(); + if (ice.adapter->find(id)) { + ice.adapter->remove(id); + } + openTuner->second.reset(); + } +} + +int +LocalDevices::TunerCount(const Ice::Current &) +{ + std::lock_guard<std::mutex> g(lock); + return devices.size(); +} + +void +LocalDevices::Scan(const Ice::Current &) +{ + std::lock_guard<std::mutex> g(lock); +} + +void +LocalDevices::Add(const std::string & frontend, const Ice::Current &) +{ + std::lock_guard<std::mutex> g(lock); + devices.insert(Devices::value_type(frontend, OpenTunerPtr())); +} + +void +LocalDevices::Remove(const std::string & frontend, const Ice::Current &) +{ + std::lock_guard<std::mutex> g(lock); + devices.erase(frontend); +} + +LocalDevices::OpenTuner::OpenTuner(DVBSI::DeliveryPtr d, P2PVR::PrivateTunerPrx t, bool op) : + openedPrivate(op), + delivery(d), + tuner(t), + clients(1) +{ +} + diff --git a/p2pvr/devices/localDevices.h b/p2pvr/devices/localDevices.h new file mode 100644 index 0000000..5521f8d --- /dev/null +++ b/p2pvr/devices/localDevices.h @@ -0,0 +1,52 @@ +#ifndef LOCALDEVICES_H +#define LOCALDEVICES_H + +// Local devices implements a device collection (P2PVR::Devices) for any devices physically +// attached to the local machine; that is, can be accessed directly through /dev/dvb/adapterX + +#include <dvb.h> +#include <options.h> +#include <mutex> + +class LocalDevices : public P2PVR::LocalDevices { + public: + LocalDevices(Ice::ObjectAdapterPtr adapter, IceUtil::TimerPtr); + ~LocalDevices(); + + P2PVR::TunerPrx GetTunerSpecific(const DVBSI::DeliveryPtr &, const Ice::Current &); + P2PVR::TunerPrx GetTunerAny(short type, const DVBSI::DeliveryPtr &, const Ice::Current &); + P2PVR::PrivateTunerPrx GetPrivateTuner(short type, const Ice::Current &); + void ReleaseTuner(const P2PVR::TunerPrx &, const Ice::Current &); + int TunerCount(const Ice::Current &); + + void Scan(const Ice::Current &); + void Add(const std::string & frontend, const Ice::Current &); + void Remove(const std::string & frontend, const Ice::Current &); + + INITOPTIONS; + private: + // Reference to global timer + IceUtil::TimerPtr timer; + IceUtil::TimerTaskPtr clientCheck; + + // Check that registered clients haven't silently gone away + void ClientCheck(Ice::ObjectAdapterPtr adapter); + + class OpenTuner { + public: + OpenTuner(DVBSI::DeliveryPtr, P2PVR::PrivateTunerPrx, bool); + + const bool openedPrivate; + const DVBSI::DeliveryPtr delivery; + const P2PVR::PrivateTunerPrx tuner; + + unsigned int clients; + }; + typedef boost::shared_ptr<OpenTuner> OpenTunerPtr; + typedef std::map<std::string, OpenTunerPtr> Devices; + static Devices devices; + static std::mutex lock; +}; + +#endif + diff --git a/p2pvr/devices/pch.hpp b/p2pvr/devices/pch.hpp new file mode 100644 index 0000000..2eeeea5 --- /dev/null +++ b/p2pvr/devices/pch.hpp @@ -0,0 +1,24 @@ +#ifdef BOOST_BUILD_PCH_ENABLED +#ifndef P2PVRLIB_PCH +#define P2PVRLIB_PCH + +#include <Ice/Ice.h> +#include <boost/bind.hpp> +#include <boost/foreach.hpp> +#include <boost/function.hpp> +#include <boost/intrusive_ptr.hpp> +#include <boost/shared_ptr.hpp> + +#include <list> +#include <map> +#include <set> +#include <string> +#include <vector> +#include <thread> + +#include <options.h> +#include <plugable.h> + +#endif +#endif + diff --git a/p2pvr/devices/tuner.cpp b/p2pvr/devices/tuner.cpp new file mode 100644 index 0000000..fe90231 --- /dev/null +++ b/p2pvr/devices/tuner.cpp @@ -0,0 +1,421 @@ +#include <pch.hpp> +#include "tuner.h" +#include <fcntl.h> +#include <Ice/Ice.h> +#include <sys/ioctl.h> +#include <poll.h> +#include <logger.h> +#include <misc.h> +#include <plugable.h> +#include <linux/dvb/frontend.h> +#include <linux/dvb/dmx.h> +#include <boost/tuple/tuple.hpp> +#include "fileHandle.h" +#include <cxxabi.h> +#include "tunerSendSi.h" +#include "tunerSendTs.h" + +class FrontendNotSupported : public NotSupported { + public: + FrontendNotSupported(fe_type t) : NotSupported(stringbf("Frontend not supported: %s", t)) + { } +}; + +Tuner::Tuner(const boost::filesystem::path & df) : + deviceFrontend(df), + deviceRoot(df.branch_path()), + backgroundThread(NULL), + lastUsedTime(time(NULL)) +{ + int fd = open(deviceFrontend.string().c_str(), O_RDWR); + if (fd < 0) { + throw P2PVR::DeviceError(deviceFrontend.string(), strerror(errno), errno); + } + try { + struct dvb_frontend_info fe_info; + if (ioctl(fd, FE_GET_INFO, &fe_info) < 0) { + throw P2PVR::DeviceError(deviceFrontend.string(), strerror(errno), errno); + } + frontend = FrontendPtr(FrontendLoader::createNew<FrontendNotSupported>(fe_info.type, this, fd, fe_info)); + } + catch (...) { + close(fd); + throw; + } + Logger()->messagebf(LOG_INFO, "%s: Attached to %s (%s, type %s)", __PRETTY_FUNCTION__, + deviceRoot, frontend->Info().name, frontend->Type()); +} + +Tuner::~Tuner() +{ + { + std::lock_guard<std::mutex> g(lock); + while (!backgroundClients.empty()) { + close(backgroundClients.begin()->first); + backgroundClients.erase(backgroundClients.begin()); + } + } + if (backgroundThread) { + backgroundThread->join(); + delete backgroundThread; + } +} + +void +Tuner::TuneTo(const DVBSI::DeliveryPtr & mp, const Ice::Current&) +{ + frontend->TuneTo(mp); +} + +int +Tuner::GetStatus(const Ice::Current &) +{ + time(&lastUsedTime); + return frontend->GetStatus(); +} + +std::string +Tuner::Device() const +{ + time(&lastUsedTime); + return deviceRoot.string(); +} + +int +Tuner::OpenDemux() const +{ + int demux = open((deviceRoot / "demux0").string().c_str(), O_RDWR | O_NONBLOCK); + if (demux < 0) { + throw P2PVR::DeviceError(deviceRoot.string(), strerror(errno), errno); + } + return demux; +} + +void +Tuner::ScanAndSendNetworkInformation(const P2PVR::RawDataClientPrx & client, const Ice::Current & ice) +{ + time(&lastUsedTime); + frontend->FrequencyScan([this, &client, &ice](long) { + try { + return (SendPID(0x10, client, ice) > 0); + } + catch (const std::exception & ex) { + char * buf = __cxxabiv1::__cxa_demangle(typeid(ex).name(), NULL, NULL, NULL); + Logger()->messagebf(LOG_DEBUG, "%s: frequency scan lock event failed %s:%s", __PRETTY_FUNCTION__, buf, ex.what()); + free(buf); + return false; + } + catch (...) { + Logger()->messagebf(LOG_DEBUG, "%s: frequency scan lock event failed", __PRETTY_FUNCTION__); + return false; + } + }); +} + +void +Tuner::SendNetworkInformation(const P2PVR::RawDataClientPrx & client, const Ice::Current & ice) +{ + SendPID(0x10, client, ice); +} + +void +Tuner::SendBouquetAssociations(const P2PVR::RawDataClientPrx & client, const Ice::Current & ice) +{ + SendPID(0x11, client, ice); +} + +void +Tuner::SendServiceDescriptions(const P2PVR::RawDataClientPrx & client, const Ice::Current & ice) +{ + SendPID(0x11, client, ice); +} + +void +Tuner::SendProgramMap(Ice::Int pid, const P2PVR::RawDataClientPrx & client, const Ice::Current & ice) +{ + SendPID(pid, client, ice); +} + +void +Tuner::SendProgramAssociationTable(const P2PVR::RawDataClientPrx & client, const Ice::Current & ice) +{ + SendPID(0x00, client, ice); +} + +void +Tuner::SendEventInformation(const P2PVR::RawDataClientPrx & client, const Ice::Current & ice) +{ + SendPID(0x12, client, ice); +} + +uint64_t +Tuner::SendPID(int pid, const P2PVR::RawDataClientPrx & client, const Ice::Current & ice) const +{ + time(&lastUsedTime); + Logger()->messagebf(LOG_DEBUG, "%s: pid = 0x%x", __PRETTY_FUNCTION__, pid); + + if (ice.con) { + ice.con->createProxy(client->ice_getIdentity()); + } + FileHandle demux(OpenDemux()); + RequestPID(pid, demux); + return ReadDemuxAndSend(demux, client); +} + +void +Tuner::RequestPID(int pid, int demux) +{ + setBufferSize(demux, DemuxTableBufferSize); + struct dmx_sct_filter_params sctFilterParams; + memset(&sctFilterParams, 0, sizeof(dmx_sct_filter_params)); + sctFilterParams.pid = pid; + sctFilterParams.flags = DMX_IMMEDIATE_START; + + if (ioctl(demux, DMX_SET_FILTER, &sctFilterParams) < 0) { + throw P2PVR::DeviceError("demux", strerror(errno), errno); + } +} + +uint64_t +Tuner::ReadDemuxAndSend(int demux, const P2PVR::RawDataClientPrx & _client) const +{ + Logger()->messagebf(LOG_DEBUG, "%s: begin", __PRETTY_FUNCTION__); + struct pollfd ufd; + memset(&ufd, 0, sizeof(pollfd)); + ufd.fd = demux; + ufd.events = POLLIN | POLLPRI; + BackgroundClient client = BackgroundClient(new SendSi(_client)); + do { + // Wait for data to appear + switch (poll(&ufd, 1, DemuxReadTimeout)) { + case -1: + Logger()->messagebf(LOG_DEBUG, "%s: poll error reading demux (%d:%s)", __PRETTY_FUNCTION__, errno, strerror(errno)); + throw P2PVR::DeviceError("demux", strerror(errno), errno); + case 0: + auto status = frontend->GetStatus(); + Logger()->messagebf(LOG_DEBUG, "%s: Timed out waiting for data (device status 0x%02x)", __PRETTY_FUNCTION__, status); + throw P2PVR::DeviceError("demux", "timeout", 0); + } + + // Read it + P2PVR::Data buf(1 << 12); + int nr = read(demux, &buf.front(), buf.size()); + if (nr < 0) { + Logger()->messagebf(LOG_DEBUG, "%s: error reading demux (%d:%s) status 0x%02x", + __PRETTY_FUNCTION__, errno, strerror(errno), frontend->GetStatus()); + throw P2PVR::DeviceError("demux", strerror(errno), errno); + } + size_t n = nr; + buf.resize(n); + + client->NewData(buf); + time(&lastUsedTime); + + } while (!client->IsFinished()); + auto packetsSent = client->PacketsSent(); + client.reset(); + Logger()->messagebf(LOG_DEBUG, "%s: end (sent %d packets)", __PRETTY_FUNCTION__, packetsSent); + return packetsSent; +} + +int +Tuner::StartSendingSection(int pid, const P2PVR::RawDataClientPrx & client, const Ice::Current &) +{ + time(&lastUsedTime); + Logger()->message(LOG_DEBUG, __PRETTY_FUNCTION__); + + std::lock_guard<std::mutex> g(lock); + int demux = backgroundClients.insert(BackgroundClients::value_type(OpenDemux(), + BackgroundClient(new SendSi(client)))).first->first; + RequestPID(pid, demux); + startSenderThread(); + return demux; +} + +int +Tuner::StartSendingTS(const P2PVR::PacketIds & pids, const P2PVR::RawDataClientPrx & client, const Ice::Current & ice) +{ + time(&lastUsedTime); + Logger()->message(LOG_DEBUG, __PRETTY_FUNCTION__); + if (pids.empty()) { + throw P2PVR::DeviceError("demux", "Packet Id list cannot be empty", 0); + } + + if (ice.con) { + ice.con->createProxy(client->ice_getIdentity()); + } + std::lock_guard<std::mutex> g(lock); + int demux = backgroundClients.insert(BackgroundClients::value_type(OpenDemux(), + BackgroundClient(new SendTs(client)))).first->first; + + struct dmx_pes_filter_params pesFilterParams; + memset(&pesFilterParams, 0, sizeof(struct dmx_pes_filter_params)); + Logger()->messagebf(LOG_ERR, "%s: DMX_SET_PES_FILTER for pid %d", __PRETTY_FUNCTION__, pids[0]); + pesFilterParams.pid = pids[0]; + pesFilterParams.input = DMX_IN_FRONTEND; + pesFilterParams.output = DMX_OUT_TSDEMUX_TAP; + pesFilterParams.pes_type = DMX_PES_OTHER; + pesFilterParams.flags = 0; + + if (ioctl(demux, DMX_SET_PES_FILTER, &pesFilterParams) < 0) { + backgroundClients.erase(demux); + Logger()->messagebf(LOG_ERR, "%s: DMX_SET_PES_FILTER failed (%d: %s)", __PRETTY_FUNCTION__, errno, strerror(errno)); + throw P2PVR::DeviceError("demux", strerror(errno), errno); + } + + for (unsigned int x = 1; x < pids.size(); x += 1) { + __u16 p = pids[x]; + Logger()->messagebf(LOG_ERR, "%s: DMX_ADD_PID for pid %d", __PRETTY_FUNCTION__, p); + if (ioctl(demux, DMX_ADD_PID, &p) < 0) { + backgroundClients.erase(demux); + Logger()->messagebf(LOG_ERR, "%s: DMX_ADD_PID failed (%d: %s)", __PRETTY_FUNCTION__, errno, strerror(errno)); + throw P2PVR::DeviceError("demux", strerror(errno), errno); + } + } + + setBufferSize(demux, DemuxStreamBufferSize); + if (ioctl(demux, DMX_START) < 0) { + backgroundClients.erase(demux); + Logger()->messagebf(LOG_ERR, "%s: DMX_START failed (%d: %s)", __PRETTY_FUNCTION__, errno, strerror(errno)); + throw P2PVR::DeviceError("demux", strerror(errno), errno); + } + + startSenderThread(); + return demux; +} + +void +Tuner::setBufferSize(int demux, unsigned long size) +{ + if (ioctl(demux, DMX_SET_BUFFER_SIZE, size)) { + Logger()->messagebf(LOG_ERR, "%s: DMX_SET_BUFFER_SIZE to %d failed (%d: %s)", __PRETTY_FUNCTION__, size, errno, strerror(errno)); + throw P2PVR::DeviceError("demux", strerror(errno), errno); + } + Logger()->messagebf(LOG_DEBUG, "%s: DMX_SET_BUFFER_SIZE to %d", __PRETTY_FUNCTION__, size); +} + +void +Tuner::StopSending(int handle, const Ice::Current &) +{ + time(&lastUsedTime); + Logger()->message(LOG_DEBUG, __PRETTY_FUNCTION__); + std::lock_guard<std::mutex> g(lock); + if (backgroundClients.find(handle) != backgroundClients.end()) { + close(handle); + backgroundClients.erase(handle); + } +} + +void +Tuner::startSenderThread() +{ + if (!backgroundThread) { + backgroundThread = new std::thread(&Tuner::senderThread, this); + } +} + +void +Tuner::senderThread() +{ + lock.lock(); + while (!backgroundClients.empty()) { + int n = backgroundClients.rbegin()->first + 1; + fd_set rfds; + FD_ZERO(&rfds); + BOOST_FOREACH(const auto & c, backgroundClients) { + FD_SET(c.first, &rfds); + } + lock.unlock(); + time(&lastUsedTime); + + struct timeval tv { 2, 0 }; + switch (select(n, &rfds, NULL, NULL, &tv)) { + case -1: // error + Logger()->messagebf(LOG_DEBUG, "%s: select failed (%d:%s)", __PRETTY_FUNCTION__, errno, strerror(errno)); + case 0: // nothing to read, but all is well + break; + default: + { // stuff to do + std::lock_guard<std::mutex> g(lock); + for (auto c = backgroundClients.begin(); c != backgroundClients.end(); ) { + if (FD_ISSET(c->first, &rfds)) { + // Read it + P2PVR::Data buf(1 << 16); + int nr = read(c->first, &buf.front(), buf.size()); + if (nr < 0) { + Logger()->messagebf(LOG_DEBUG, "%s: read failed (%d:%s)", __PRETTY_FUNCTION__, errno, strerror(errno)); + close(c->first); + c = backgroundClients.erase(c); + } + else { + size_t n = nr; + buf.resize(n); + c->second->NewData(buf); + c++; + } + } + else { + c++; + } + } + } + break; + } + // Clean up finished async requests + lock.lock(); + for (auto client = backgroundClients.begin(); client != backgroundClients.end(); ) { + if (client->second->IsFinished()) { + close(client->first); + client = backgroundClients.erase(client); + } + else { + client++; + } + } + } + backgroundThread = NULL; + Logger()->messagebf(LOG_DEBUG, "%s: Unlocking", __PRETTY_FUNCTION__); + lock.unlock(); +} + +Ice::Long +Tuner::GetLastUsedTime(const Ice::Current &) +{ + return lastUsedTime; +} + +Tuner::IDataSender::IDataSender(const P2PVR::RawDataClientPrx & c) : + _packetsSent(0), + client(c) +{ +} + +Tuner::IDataSender::~IDataSender() +{ +} + +uint64_t +Tuner::IDataSender::PacketsSent() const +{ + return _packetsSent; +} + +int Tuner::TuningTimeout; +int Tuner::LockTimeout; +int Tuner::DemuxReadTimeout; +int Tuner::DemuxTableBufferSize; +int Tuner::DemuxStreamBufferSize; + +DECLARE_OPTIONS(Tuner, "P2PVR Tuner Options") +("p2pvr.tuner.tuningtimeout", Options::value(&TuningTimeout, 500), + "Timeout for a DVB frontend to tune (ms, default 500ms)") +("p2pvr.tuner.locktimeout", Options::value(&LockTimeout, 2000), + "Timeout for a DVB frontend to acquire lock (ms, default 2000ms)") +("p2pvr.tuner.demuxreadtimeout", Options::value(&DemuxReadTimeout, 20000), + "Timeout when reading from a demux device (ms, default 20s)") +("p2pvr.tuner.demuxtablebuffersize", Options::value(&DemuxTableBufferSize, 256*1024), + "Kernel buffer size for demux table data (bytes, default 256KB)") +("p2pvr.tuner.demuxstreambuffersize", Options::value(&DemuxStreamBufferSize, 1024*1024), + "Kernel buffer size for demux stream data (bytes, default 1MB)") +END_OPTIONS(Tuner); + diff --git a/p2pvr/devices/tuner.h b/p2pvr/devices/tuner.h new file mode 100644 index 0000000..670a17d --- /dev/null +++ b/p2pvr/devices/tuner.h @@ -0,0 +1,83 @@ +#ifndef P2PVR_TUNER_H +#define P2PVR_TUNER_H + +#include <dvb.h> +#include <boost/filesystem/path.hpp> +#include "frontend.h" +#include <map> +#include <thread> +#include <mutex> +#include <boost/bind.hpp> +#include <boost/function.hpp> +#include <boost/tuple/tuple.hpp> +#include <options.h> + +class Tuner : public P2PVR::PrivateTuner { + public: + class IDataSender { + public: + IDataSender(const P2PVR::RawDataClientPrx &); + virtual ~IDataSender() = 0; + + virtual void NewData(const P2PVR::Data &) = 0; + virtual bool IsFinished() = 0; + uint64_t PacketsSent() const; + + protected: + uint64_t _packetsSent; + const P2PVR::RawDataClientPrx client; + }; + typedef boost::shared_ptr<IDataSender> BackgroundClient; + typedef std::map<int, BackgroundClient> BackgroundClients; + + Tuner(const boost::filesystem::path & deviceFrontend); + ~Tuner(); + + void TuneTo(const DVBSI::DeliveryPtr &, const Ice::Current&); + int GetStatus(const Ice::Current&); + std::string Device() const; + + void ScanAndSendNetworkInformation(const P2PVR::RawDataClientPrx & client, const Ice::Current&); + void SendNetworkInformation(const P2PVR::RawDataClientPrx & client, const Ice::Current&); + void SendBouquetAssociations(const P2PVR::RawDataClientPrx & client, const Ice::Current&); + void SendServiceDescriptions(const P2PVR::RawDataClientPrx & client, const Ice::Current&); + void SendProgramMap(Ice::Int pid, const P2PVR::RawDataClientPrx & client, const Ice::Current&); + void SendProgramAssociationTable(const P2PVR::RawDataClientPrx & client, const Ice::Current&); + void SendEventInformation(const P2PVR::RawDataClientPrx & client, const Ice::Current&); + + int StartSendingTS(const P2PVR::PacketIds & pids, const P2PVR::RawDataClientPrx & client, const Ice::Current &); + int StartSendingSection(Ice::Int pid, const P2PVR::RawDataClientPrx & client, const Ice::Current &); + void StopSending(int handle, const Ice::Current &); + + Ice::Long GetLastUsedTime(const Ice::Current&); + + INITOPTIONS; + + private: + int OpenDemux() const; + uint64_t SendPID(int pid, const P2PVR::RawDataClientPrx & client, const Ice::Current &) const; + static void RequestPID(int pid, int fd); + uint64_t ReadDemuxAndSend(int fd, const P2PVR::RawDataClientPrx & client) const; + void startSenderThread(); + void senderThread(); + static void setBufferSize(int fd, unsigned long bytes); + + const boost::filesystem::path deviceFrontend; + const boost::filesystem::path deviceRoot; + BackgroundClients backgroundClients; + std::thread * backgroundThread; + std::mutex lock; + + mutable time_t lastUsedTime; + FrontendPtr frontend; + + public: + static int TuningTimeout; + static int LockTimeout; + static int DemuxReadTimeout; + static int DemuxTableBufferSize; + static int DemuxStreamBufferSize; +}; + +#endif + diff --git a/p2pvr/devices/tunerSendSi.cpp b/p2pvr/devices/tunerSendSi.cpp new file mode 100644 index 0000000..e1f4637 --- /dev/null +++ b/p2pvr/devices/tunerSendSi.cpp @@ -0,0 +1,81 @@ +#include <pch.hpp> +#include "tunerSendSi.h" +#include <logger.h> +#include <boost/crc.hpp> +#include "siParsers/table.h" + +SendSi::SendSi(const P2PVR::RawDataClientPrx & c) : + Tuner::IDataSender(c->ice_collocationOptimized(false)) +{ +} + +SendSi::~SendSi() +{ +} + +void +SendSi::NewData(const P2PVR::Data & buf) +{ + if (!IsValidSection(buf)) { + return; + } + _packetsSent += 1; + asyncs.insert(client->begin_NewData(buf)); +} + +bool +SendSi::IsFinished() +{ + try { + for (auto c = asyncs.begin(); c != asyncs.end(); ) { + if ((*c)->isCompleted()) { + if (client->end_NewData(*c)) { + return true; + } + c = asyncs.erase(c); + } + else { + c++; + } + } + return false; + } + catch (const std::exception & ex) { + Logger()->messagebf(LOG_DEBUG, "%s: Client transmit error (%s)", __PRETTY_FUNCTION__, ex.what()); + return true; + } +} + +bool +SendSi::IsValidSection(const P2PVR::Data & buf) +{ + auto n = buf.size(); + if (n < sizeof(SiTableHeader)) { + Logger()->messagebf(LOG_WARNING, "Received data too small to be an SI table."); + return false; + } + auto * tab = (const SiTableHeader *)(&buf.front()); + size_t l = sizeof(SiTableHeaderBase) + HILO(tab->section_length); + if (n < l) { + Logger()->messagebf(LOG_WARNING, "Received data shorter than its defined length."); + return false; + } + if (n > l) { + Logger()->messagebf(LOG_WARNING, "Received data longer than its defined length."); + return false; + } + if (!crc32(buf)) { + Logger()->messagebf(LOG_WARNING, "Received data is corrupted (crc32 failed)."); + return false; + } + return true; +} + +bool +SendSi::crc32(const P2PVR::Data & buf) +{ + boost::crc_optimal<32, 0x0, 0xFFFFFFFF, 0x0, true, false> crc; + crc.process_bytes(&buf.front(), buf.size()); + return crc.checksum() == 0; +} + diff --git a/p2pvr/devices/tunerSendSi.h b/p2pvr/devices/tunerSendSi.h new file mode 100644 index 0000000..df48f43 --- /dev/null +++ b/p2pvr/devices/tunerSendSi.h @@ -0,0 +1,24 @@ +#ifndef TUNER_SENDSI_H +#define TUNER_SENDSI_H + +#include "tuner.h" + +class SendSi : public Tuner::IDataSender { + public: + SendSi(const P2PVR::RawDataClientPrx &); + ~SendSi(); + + void NewData(const P2PVR::Data &); + bool IsFinished(); + + private: + static bool crc32(const P2PVR::Data &); + static bool IsValidSection(const P2PVR::Data &); + + std::set<Ice::AsyncResultPtr> asyncs; + bool finish; +}; + +#endif + + diff --git a/p2pvr/devices/tunerSendTs.cpp b/p2pvr/devices/tunerSendTs.cpp new file mode 100644 index 0000000..70b6670 --- /dev/null +++ b/p2pvr/devices/tunerSendTs.cpp @@ -0,0 +1,77 @@ +#include <pch.hpp> +#include "tunerSendTs.h" +#include <logger.h> + +// ~64kb of TS packets +#define TARGET_BUFFER_SIZE (350 * 188) +// About the ICE message size limit +#define TARGET_BUFFER_LIMIT 512 * 1024 + +SendTs::SendTs(const P2PVR::RawDataClientPrx & c) : + Tuner::IDataSender(c->ice_collocationOptimized(false)) +{ + buffer.reserve(TARGET_BUFFER_SIZE); +} + +SendTs::~SendTs() +{ + try { + if (async) { + if (client->end_NewData(async)) return; + } + while (!buffer.empty()) { + sendBufferChunk(); + if (client->end_NewData(async)) return; + } + } + catch (...) { + } +} + +void +SendTs::NewData(const P2PVR::Data & buf) +{ + buffer.insert(buffer.end(), buf.begin(), buf.end()); + if (!async && buffer.size() >= TARGET_BUFFER_SIZE) { + sendBufferChunk(); + } +} + +void +SendTs::sendBufferChunk() +{ + if (buffer.size() > TARGET_BUFFER_LIMIT) { + auto breakPoint = buffer.begin() + TARGET_BUFFER_LIMIT; + async = client->begin_NewData(P2PVR::Data(buffer.begin(), breakPoint)); + buffer.erase(buffer.begin(), breakPoint); + } + else { + async = client->begin_NewData(buffer); + buffer.clear(); + buffer.reserve(TARGET_BUFFER_SIZE); + } + _packetsSent += 1; +} + +bool +SendTs::IsFinished() +{ + try { + if (async && async->isCompleted()) { + auto finished = client->end_NewData(async); + async = NULL; + if (finished) { + buffer.clear(); + } + return finished; + } + return false; + } + catch (const std::exception & ex) { + async = NULL; + Logger()->messagebf(LOG_DEBUG, "%s: Client transmit error (%s)", __PRETTY_FUNCTION__, ex.what()); + return true; + } +} + + diff --git a/p2pvr/devices/tunerSendTs.h b/p2pvr/devices/tunerSendTs.h new file mode 100644 index 0000000..ecf32fd --- /dev/null +++ b/p2pvr/devices/tunerSendTs.h @@ -0,0 +1,22 @@ +#ifndef TUNER_SENDTS_H +#define TUNER_SENDTS_H + +#include "tuner.h" + +class SendTs : public Tuner::IDataSender { + public: + SendTs(const P2PVR::RawDataClientPrx &); + ~SendTs(); + + void NewData(const P2PVR::Data &); + bool IsFinished(); + + private: + void sendBufferChunk(); + + Ice::AsyncResultPtr async; + P2PVR::Data buffer; +}; + +#endif + |