diff --git a/mythtv/libs/libmythtv/iptvtuningdata.h b/mythtv/libs/libmythtv/iptvtuningdata.h index d8d9cc53750..0ebe2ff3980 100644 --- a/mythtv/libs/libmythtv/iptvtuningdata.h +++ b/mythtv/libs/libmythtv/iptvtuningdata.h @@ -74,6 +74,8 @@ class MTV_PUBLIC IPTVTuningData QString GetDeviceKey(void) const { const QUrl u = GetDataURL(); + if (IsHLS()) + return u.toString(); return QString("%1:%2:%3") .arg(u.host()).arg(u.userInfo()).arg(u.port()).toLower(); } @@ -131,7 +133,15 @@ class MTV_PUBLIC IPTVTuningData uint GetURLCount(void) const { return 3; } - bool IsValid(void) const { return m_data_url.port() != -1; } + bool IsHLS(void) const + { + return m_data_url.scheme().startsWith("http") && m_data_url.isValid(); + } + + bool IsValid(void) const + { + return IsHLS() || m_data_url.port() != -1; + } public: QUrl m_data_url; diff --git a/mythtv/libs/libmythtv/libmythtv.pro b/mythtv/libs/libmythtv/libmythtv.pro index fcf11707084..de19f69d50b 100644 --- a/mythtv/libs/libmythtv/libmythtv.pro +++ b/mythtv/libs/libmythtv/libmythtv.pro @@ -29,7 +29,6 @@ DEPENDPATH += ../libmythdvdnav/ DEPENDPATH += ./mpeg ./channelscan ./visualisations DEPENDPATH += ./recorders DEPENDPATH += ./recorders/dvbdev -DEPENDPATH += ./recorders/HLS DEPENDPATH += ./recorders/rtp DEPENDPATH += ./recorders/vbitext DEPENDPATH += ../libmythlivemedia/BasicUsageEnvironment/include @@ -651,6 +650,10 @@ using_backend { SOURCES += recorders/rtp/packetbuffer.cpp SOURCES += recorders/rtp/rtppacketbuffer.cpp + # Suppport for HLS recorder + HEADERS += recorders/hlsstreamhandler.h + SOURCES += recorders/hlsstreamhandler.cpp + DEFINES += USING_IPTV # Support for HDHomeRun box diff --git a/mythtv/libs/libmythtv/recorders/hlsstreamhandler.cpp b/mythtv/libs/libmythtv/recorders/hlsstreamhandler.cpp new file mode 100644 index 00000000000..3d908cf6624 --- /dev/null +++ b/mythtv/libs/libmythtv/recorders/hlsstreamhandler.cpp @@ -0,0 +1,164 @@ +/** -*- Mode: c++ -*- + * HLSStreamHandler + * Copyright (c) 2013 Bubblestuff Pty Ltd + * based on IPTVStreamHandler + * Distributed as part of MythTV under GPL v2 and later. + */ + +// MythTV headers +#include "hlsstreamhandler.h" +#include "mythlogging.h" +#include "HLS/httplivestreambuffer.h" + +#define LOC QString("HLSSH(%1): ").arg(_device) + +// BUFFER_SIZE is a multiple of TS_SIZE +#define TS_SIZE 188 +#define BUFFER_SIZE (128 * TS_SIZE) + +QMap HLSStreamHandler::s_handlers; +QMap HLSStreamHandler::s_handlers_refcnt; +QMutex HLSStreamHandler::s_handlers_lock; + +HLSStreamHandler* HLSStreamHandler::Get(const IPTVTuningData& tuning) +{ + QMutexLocker locker(&s_handlers_lock); + + QString devkey = tuning.GetDeviceKey(); + + QMap::iterator it = s_handlers.find(devkey); + + if (it == s_handlers.end()) + { + HLSStreamHandler* newhandler = new HLSStreamHandler(tuning); + newhandler->Start(); + s_handlers[devkey] = newhandler; + s_handlers_refcnt[devkey] = 1; + + LOG(VB_RECORD, LOG_INFO, + QString("HLSSH: Creating new stream handler %1 for %2") + .arg(devkey).arg(tuning.GetDeviceName())); + } + else + { + s_handlers_refcnt[devkey]++; + uint rcount = s_handlers_refcnt[devkey]; + LOG(VB_RECORD, LOG_INFO, + QString("HLSSH: Using existing stream handler %1 for %2") + .arg(devkey).arg(tuning.GetDeviceName()) + + QString(" (%1 in use)").arg(rcount)); + } + + return s_handlers[devkey]; +} + +void HLSStreamHandler::Return(HLSStreamHandler* & ref) +{ + QMutexLocker locker(&s_handlers_lock); + + QString devname = ref->_device; + + QMap::iterator rit = s_handlers_refcnt.find(devname); + if (rit == s_handlers_refcnt.end()) + return; + + LOG(VB_RECORD, LOG_INFO, QString("HLSSH: Return(%1) has %2 handlers") + .arg(devname).arg(*rit)); + + if (*rit > 1) + { + ref = NULL; + (*rit)--; + return; + } + + QMap::iterator it = s_handlers.find(devname); + if ((it != s_handlers.end()) && (*it == ref)) + { + LOG(VB_RECORD, LOG_INFO, QString("HLSSH: Closing handler for %1") + .arg(devname)); + ref->Stop(); + LOG(VB_RECORD, LOG_DEBUG, QString("HLSSH: handler for %1 stopped") + .arg(devname)); + delete *it; + s_handlers.erase(it); + } + else + { + LOG(VB_GENERAL, LOG_ERR, + QString("HLSSH Error: Couldn't find handler for %1") + .arg(devname)); + } + + s_handlers_refcnt.erase(rit); + ref = NULL; +} + +HLSStreamHandler::HLSStreamHandler(const IPTVTuningData& tuning) : + IPTVStreamHandler(tuning), + m_tuning(tuning) +{ + m_hls = new HLSRingBuffer(m_tuning.GetURL(0).toString(), false); + m_buffer = new uint8_t[BUFFER_SIZE]; +} + +HLSStreamHandler::~HLSStreamHandler(void) +{ + Stop(); + m_hls->Interrupt(); + delete m_hls; + delete[] m_buffer; +} + +void HLSStreamHandler::run(void) +{ + RunProlog(); + + LOG(VB_GENERAL, LOG_INFO, LOC + "run()"); + + SetRunning(true, false, false); + + // TODO Error handling.. + + if (!m_hls->IsOpen()) + { + m_hls->OpenFile(m_tuning.GetURL(0).toString()); + } + + while (m_hls->IsOpen() && _running_desired) + { + int size = m_hls->Read((void*)m_buffer, BUFFER_SIZE); + + if (size < 0) + { + break; // error + } + if (m_buffer[0] != 0x47) + { + LOG(VB_RECORD, LOG_INFO, LOC + + QString("Packet not starting with SYNC Byte (got 0x%1)") + .arg((char)m_buffer[0], 2, QLatin1Char('0'))); + } + + int remainder = 0; + { + QMutexLocker locker(&_listener_lock); + HLSStreamHandler::StreamDataList::const_iterator sit; + sit = _stream_data_list.begin(); + for (; sit != _stream_data_list.end(); ++sit) + { + remainder = sit.key()->ProcessData(m_buffer, size); + } + } + + if (remainder != 0) + { + LOG(VB_RECORD, LOG_INFO, LOC + + QString("data_length = %1 remainder = %2") + .arg(size).arg(remainder)); + } + } + + SetRunning(false, false, false); + RunEpilog(); +} diff --git a/mythtv/libs/libmythtv/recorders/hlsstreamhandler.h b/mythtv/libs/libmythtv/recorders/hlsstreamhandler.h new file mode 100644 index 00000000000..6593fb6bdb5 --- /dev/null +++ b/mythtv/libs/libmythtv/recorders/hlsstreamhandler.h @@ -0,0 +1,47 @@ +/** -*- Mode: c++ -*- + * HLSStreamHandler + * Copyright (c) 2013 Bubblestuff Pty Ltd + * based on IPTVStreamHandler + * Distributed as part of MythTV under GPL v2 and later. + */ + +#ifndef _HLSSTREAMHANDLER_H_ +#define _HLSSTREAMHANDLER_H_ + +#include +using namespace std; + +#include +#include +#include + +#include "channelutil.h" +#include "iptvstreamhandler.h" + +class MPEGStreamData; +class HLSRingBuffer; + +class HLSStreamHandler : public IPTVStreamHandler +{ + public: + static HLSStreamHandler* Get(const IPTVTuningData& tuning); + static void Return(HLSStreamHandler* & ref); + + protected: + HLSStreamHandler(const IPTVTuningData &tuning); + virtual ~HLSStreamHandler(void); + + virtual void run(void); // MThread + + protected: + IPTVTuningData m_tuning; + HLSRingBuffer* m_hls; + uint8_t* m_buffer; + + // for implementing Get & Return + static QMutex s_handlers_lock; + static QMap s_handlers; + static QMap s_handlers_refcnt; +}; + +#endif // _HLSSTREAMHANDLER_H_ diff --git a/mythtv/libs/libmythtv/recorders/iptvchannel.cpp b/mythtv/libs/libmythtv/recorders/iptvchannel.cpp index 941aa6eb85f..184b0ccf2ea 100644 --- a/mythtv/libs/libmythtv/recorders/iptvchannel.cpp +++ b/mythtv/libs/libmythtv/recorders/iptvchannel.cpp @@ -3,6 +3,7 @@ * Copyright (c) 2006-2009 Silicondust Engineering Ltd, and * Daniel Thor Kristjansson * Copyright (c) 2012 Digital Nirvana, Inc. + * Copyright (c) 2013 Bubblestuff Pty Ltd * Distributed as part of MythTV under GPL v2 and later. */ @@ -11,6 +12,7 @@ // MythTV headers #include "iptvstreamhandler.h" +#include "hlsstreamhandler.h" #include "iptvrecorder.h" #include "iptvchannel.h" #include "mythlogging.h" @@ -19,16 +21,25 @@ #define LOC QString("IPTVChan[%1]: ").arg(GetCardID()) IPTVChannel::IPTVChannel(TVRec *rec, const QString&) : - DTVChannel(rec), m_open(false), - m_stream_handler(NULL), m_stream_data(NULL) + DTVChannel(rec), m_open(false), m_firsttune(true), + m_stream_handler(NULL), m_stream_data(NULL), m_timer(0) { LOG(VB_CHANNEL, LOG_INFO, LOC + "ctor"); } +void IPTVChannel::KillTimer(void) +{ + if (m_timer) + { + killTimer(m_timer); + m_timer = 0; + } +} + IPTVChannel::~IPTVChannel() { LOG(VB_CHANNEL, LOG_INFO, LOC + "dtor"); - m_stream_data = NULL; + Close(); } bool IPTVChannel::Open(void) @@ -38,16 +49,21 @@ bool IPTVChannel::Open(void) if (IsOpen()) return true; - QMutexLocker locker(&m_lock); + m_lock.lock(); if (!InitializeInputs()) { + m_lock.unlock(); Close(); return false; } if (m_stream_data) - SetStreamDataInternal(m_stream_data); + SetStreamDataInternal(m_stream_data, true); + + m_open = true; + + m_lock.unlock(); return true; } @@ -55,63 +71,115 @@ bool IPTVChannel::Open(void) void IPTVChannel::SetStreamData(MPEGStreamData *sd) { QMutexLocker locker(&m_lock); - SetStreamDataInternal(sd); + SetStreamDataInternal(sd, false); } -void IPTVChannel::SetStreamDataInternal(MPEGStreamData *sd) +void IPTVChannel::SetStreamDataInternal(MPEGStreamData *sd, bool closeimmediately) { - LOG(VB_CHANNEL, LOG_INFO, LOC + QString("SetStreamData(0x%1)") - .arg((intptr_t)sd,0,16)); + LOG(VB_CHANNEL, LOG_INFO, LOC + QString("SetStreamData(0x%1) StreamHandler(0x%2) Close(%3)") + .arg((intptr_t)sd,0,16).arg((intptr_t)m_stream_handler,0,16).arg(closeimmediately)); if (m_stream_data == sd) return; - if (m_stream_data) + if (m_stream_handler) { - if (sd) + KillTimer(); + + if (m_stream_data) { m_stream_handler->RemoveListener(m_stream_data); + } + if (sd) + { m_stream_handler->AddListener(sd); } else { - m_stream_handler->RemoveListener(m_stream_data); - IPTVStreamHandler::Return(m_stream_handler); + if (!closeimmediately) + { + // streamdata is zero + // mark stream handler for deletion in 5s + // so we can re-use it shortly if need be + LOG(VB_CHANNEL, LOG_DEBUG, LOC + + QString("Scheduling for deletion: StreamHandler(0x%1)") + .arg((intptr_t)m_stream_handler,0,16)); + m_timer = startTimer(5000); + } + else + { + CloseStreamHandler(); + } } } else { - assert(m_stream_handler == NULL); - if (m_stream_handler) - IPTVStreamHandler::Return(m_stream_handler); - if (sd) { - if (m_last_tuning.IsValid()) - m_stream_handler = IPTVStreamHandler::Get(m_last_tuning); - if (m_stream_handler) - m_stream_handler->AddListener(sd); + OpenStreamHandler(); + m_stream_handler->AddListener(sd); } } m_stream_data = sd; } +void IPTVChannel::timerEvent(QTimerEvent*) +{ + LOG(VB_CHANNEL, LOG_INFO, LOC + "timerEvent()"); + + CloseStreamHandler(); +} + void IPTVChannel::Close(void) { LOG(VB_CHANNEL, LOG_INFO, LOC + "Close()"); QMutexLocker locker(&m_lock); + + CloseStreamHandler(); + + m_open = false; +} + +void IPTVChannel::OpenStreamHandler(void) +{ + if (m_last_tuning.IsHLS()) + { + m_stream_handler = HLSStreamHandler::Get(m_last_tuning); + } + else + { + m_stream_handler = IPTVStreamHandler::Get(m_last_tuning); + } +} + +void IPTVChannel::CloseStreamHandler(void) +{ + KillTimer(); + if (m_stream_handler) { if (m_stream_data) m_stream_handler->RemoveListener(m_stream_data); - IPTVStreamHandler::Return(m_stream_handler); + + HLSStreamHandler* hsh = dynamic_cast(m_stream_handler); + if (hsh) + { + HLSStreamHandler::Return(hsh); + m_stream_handler = hsh; + } + else + { + IPTVStreamHandler::Return(m_stream_handler); + } } } bool IPTVChannel::IsOpen(void) const { QMutexLocker locker(&m_lock); + LOG(VB_CHANNEL, LOG_DEBUG, LOC + QString("IsOpen(%1)") + .arg(m_last_tuning.GetDeviceName())); return m_open; } @@ -135,17 +203,34 @@ bool IPTVChannel::Tune(const IPTVTuningData &tuning) } if (m_last_tuning == tuning) + { + LOG(VB_CHANNEL, LOG_DEBUG, LOC + QString("Already tuned to %1") + .arg(tuning.GetDeviceName())); return true; + } m_last_tuning = tuning; - if (m_stream_data) + if (!m_firsttune) // for historical reason, an initial tune is requested at startup + // so don't open the stream handler just yet + // it will be opened after the next Tune or SetStreamData) { MPEGStreamData *tmp = m_stream_data; - SetStreamDataInternal(NULL); - SetStreamDataInternal(tmp); + + CloseStreamHandler(); + + if (tmp) + { + SetStreamDataInternal(tmp, true); + } + else + { + OpenStreamHandler(); + } } + m_firsttune = false; + return true; } diff --git a/mythtv/libs/libmythtv/recorders/iptvchannel.h b/mythtv/libs/libmythtv/recorders/iptvchannel.h index 69ee1f21edf..f51a6a0f509 100644 --- a/mythtv/libs/libmythtv/recorders/iptvchannel.h +++ b/mythtv/libs/libmythtv/recorders/iptvchannel.h @@ -3,6 +3,7 @@ * Copyright (c) 2006-2009 Silicondust Engineering Ltd, and * Daniel Thor Kristjansson * Copyright (c) 2012 Digital Nirvana, Inc. + * Copyright (c) 2013 Bubblestuff Pty Ltd * Distributed as part of MythTV under GPL v2 and later. */ @@ -18,16 +19,19 @@ class IPTVStreamHandler; class IPTVTuningData; class IPTVRecorder; +class MPEGStreamData; -class IPTVChannel : public DTVChannel +class IPTVChannel : QObject, public DTVChannel { + Q_OBJECT + public: IPTVChannel(TVRec*, const QString&); ~IPTVChannel(); // Commands virtual bool Open(void); - virtual void Close(void); + virtual void Close(void); using DTVChannel::Tune; virtual bool Tune(const IPTVTuningData&); @@ -41,14 +45,19 @@ class IPTVChannel : public DTVChannel virtual bool IsIPTV(void) const { return true; } // DTVChannel private: - void SetStreamDataInternal(MPEGStreamData*); + void SetStreamDataInternal(MPEGStreamData*, bool closeimmediately); + void timerEvent(QTimerEvent*); + void KillTimer(void); + void OpenStreamHandler(void); + void CloseStreamHandler(void); private: mutable QMutex m_lock; - volatile bool m_open; + volatile bool m_open, m_firsttune; IPTVTuningData m_last_tuning; IPTVStreamHandler *m_stream_handler; MPEGStreamData *m_stream_data; + int m_timer; }; #endif // _IPTV_CHANNEL_H_