summaryrefslogtreecommitdiff
path: root/p2pvr/devices
diff options
context:
space:
mode:
authorrandomdan <randomdan@localhost>2014-03-13 19:42:07 +0000
committerrandomdan <randomdan@localhost>2014-03-13 19:42:07 +0000
commitab1eee942e75874739ce5f0b4ba289aac5cc3faf (patch)
tree6e43828794fe0c0c5c9921ec1911695b67357c50 /p2pvr/devices
parentExpose more of the interface (diff)
downloadp2pvr-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.jam27
-rw-r--r--p2pvr/devices/frontend.cpp39
-rw-r--r--p2pvr/devices/frontend.h32
-rw-r--r--p2pvr/devices/frontends/ofdm.cpp159
-rw-r--r--p2pvr/devices/localDevices.cpp189
-rw-r--r--p2pvr/devices/localDevices.h52
-rw-r--r--p2pvr/devices/pch.hpp24
-rw-r--r--p2pvr/devices/tuner.cpp421
-rw-r--r--p2pvr/devices/tuner.h83
-rw-r--r--p2pvr/devices/tunerSendSi.cpp81
-rw-r--r--p2pvr/devices/tunerSendSi.h24
-rw-r--r--p2pvr/devices/tunerSendTs.cpp77
-rw-r--r--p2pvr/devices/tunerSendTs.h22
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
+