diff --git a/mythplugins/mytharchive/mythburn/themes/Black_-_Multi-episode/Black-Background.png b/mythplugins/mytharchive/mythburn/themes/Black_-_Multi-episode/Black-Background.png new file mode 100644 index 00000000000..3bef2d23066 Binary files /dev/null and b/mythplugins/mytharchive/mythburn/themes/Black_-_Multi-episode/Black-Background.png differ diff --git a/mythplugins/mytharchive/mythburn/themes/Black_-_Multi-episode/description.txt b/mythplugins/mytharchive/mythburn/themes/Black_-_Multi-episode/description.txt new file mode 100644 index 00000000000..81d5cae4ae5 --- /dev/null +++ b/mythplugins/mytharchive/mythburn/themes/Black_-_Multi-episode/description.txt @@ -0,0 +1,2 @@ +Has an intro and contains a summary main menu with 10 recordings per page. +Does not have a chapter selection submenu, recording titles, dates or category. diff --git a/mythplugins/mytharchive/mythburn/themes/Black_-_Multi-episode/intro_preview.png b/mythplugins/mytharchive/mythburn/themes/Black_-_Multi-episode/intro_preview.png new file mode 100644 index 00000000000..09b3a2952db Binary files /dev/null and b/mythplugins/mytharchive/mythburn/themes/Black_-_Multi-episode/intro_preview.png differ diff --git a/mythplugins/mytharchive/mythburn/themes/Black_-_Multi-episode/mainmenu_preview.png b/mythplugins/mytharchive/mythburn/themes/Black_-_Multi-episode/mainmenu_preview.png new file mode 100644 index 00000000000..d0519d3ea3a Binary files /dev/null and b/mythplugins/mytharchive/mythburn/themes/Black_-_Multi-episode/mainmenu_preview.png differ diff --git a/mythplugins/mytharchive/mythburn/themes/Black_-_Multi-episode/preview.png b/mythplugins/mytharchive/mythburn/themes/Black_-_Multi-episode/preview.png new file mode 100644 index 00000000000..d0519d3ea3a Binary files /dev/null and b/mythplugins/mytharchive/mythburn/themes/Black_-_Multi-episode/preview.png differ diff --git a/mythplugins/mytharchive/mythburn/themes/Black_-_Multi-episode/theme.xml b/mythplugins/mytharchive/mythburn/themes/Black_-_Multi-episode/theme.xml new file mode 100644 index 00000000000..82b56179ba8 --- /dev/null +++ b/mythplugins/mytharchive/mythburn/themes/Black_-_Multi-episode/theme.xml @@ -0,0 +1,125 @@ + + + + + + + FreeSans.ttf + FreeSans.ttf + FreeSans.ttf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mythplugins/mythgallery/dcrawplugin/dcrawplugin.cpp b/mythplugins/mythgallery/dcrawplugin/dcrawplugin.cpp index 6f9e3f414b8..2cf059bdedc 100644 --- a/mythplugins/mythgallery/dcrawplugin/dcrawplugin.cpp +++ b/mythplugins/mythgallery/dcrawplugin/dcrawplugin.cpp @@ -38,5 +38,8 @@ QImageIOHandler *DcrawPlugin::create(QIODevice *device, const QByteArray &format return handler; } +// This shouldn't be necessary, but it shuts up the dang compiler warning. +QObject* qt_plugin_instance_dcrawplugin(); + Q_EXPORT_PLUGIN2(dcrawplugin, DcrawPlugin) diff --git a/mythtv/VERSION b/mythtv/VERSION new file mode 100644 index 00000000000..d6f50da2ed4 --- /dev/null +++ b/mythtv/VERSION @@ -0,0 +1 @@ +SOURCE_VERSION="v0.25pre" \ No newline at end of file diff --git a/mythtv/bindings/php/MythBackend.php b/mythtv/bindings/php/MythBackend.php index 6fba69c1aed..441353aa79d 100644 --- a/mythtv/bindings/php/MythBackend.php +++ b/mythtv/bindings/php/MythBackend.php @@ -50,6 +50,12 @@ static function find($host = null, $port = null) { function __construct($host, $port = null) { $this->host = $host; $this->ip = _or(setting('BackendServerIP', $this->host), $host); + + // If the IP contains a ':' It's likely an IPv6 address so enclose it in '[]' + if (strpos($this->ip,":") > 0) { + $this->ip = "[" + $this->ip + "]"; + } + $this->port = _or($port, _or(setting('BackendServerPort', $this->host), 6543)); $this->port_http = _or(setting('BackendStatusPort', $this->host), _or(setting('BackendStatusPort'), 6544)); } diff --git a/mythtv/configure b/mythtv/configure index 60fb08b0e7c..b8475a333bc 100755 --- a/mythtv/configure +++ b/mythtv/configure @@ -115,6 +115,7 @@ Advanced options (experts only): --dvb-path=HDRLOC location of directory containing 'linux/dvb/frontend.h', not the directory with frontend.h [$dvb_path_default] + --disable-asi disable support for ASI recorder --disable-x11 disable X11 support --x11-path=X11LOC location of X11 include files [$x11_path_default] --disable-xrandr disable X11 resolution switching @@ -1344,6 +1345,7 @@ MYTHTV_CONFIG_LIST=' hdpvr iptv ivtv + asi joystick_menu libfftw3 libmpeg2external @@ -1773,6 +1775,7 @@ v4l_deps="backend linux_videodev_h linux_videodev2_h" vdpau_deps="opengl vdpau_vdpau_h vdpau_vdpau_x11_h" xrandr_deps="x11" xv_deps="x11" +asi_deps="backend" <
+#include +int main(void) { return 1; } +EOF + # Check that all MythTV build "requirements" are met: enabled lamemp3 && check_lib2 lame/lame.h lame_init -lmp3lame -lm || die "ERROR! You must have the Lame MP3 encoding library installed to compile MythTV." @@ -4444,6 +4454,7 @@ if enabled backend; then echo "DVB-S2 support ${fe_can_2g_modulation-no}" echo "HDHomeRun support ${hdhomerun-no}" echo "IPTV support ${iptv-no}" + echo "ASI support ${asi-no}" fi if enabled frontend; then diff --git a/mythtv/libs/libmyth/programinfo.cpp b/mythtv/libs/libmyth/programinfo.cpp index b0cc85afbd7..02dd0855cfa 100644 --- a/mythtv/libs/libmyth/programinfo.cpp +++ b/mythtv/libs/libmyth/programinfo.cpp @@ -2032,19 +2032,19 @@ QString ProgramInfo::GetPlaybackURL( (gCoreContext->GetNumSetting("MasterBackendOverride", 0)) && (RemoteCheckFile(this, false))) { - tmpURL = QString("myth://") + - gCoreContext->GetSetting("MasterServerIP") + ':' + - gCoreContext->GetSetting("MasterServerPort") + '/' + basename; + tmpURL = gCoreContext->GenMythURL(gCoreContext->GetSetting("MasterServerIP"), + gCoreContext->GetSetting("MasterServerPort").toInt(), + basename); + VERBOSE(VB_FILE, LOC + QString("GetPlaybackURL: Found @ '%1'").arg(tmpURL)); return tmpURL; } // Fallback to streaming from the backend the recording was created on - tmpURL = QString("myth://") + - gCoreContext->GetSettingOnHost("BackendServerIP", hostname) + ':' + - gCoreContext->GetSettingOnHost("BackendServerPort", hostname) + '/' + - basename; + tmpURL = gCoreContext->GenMythURL(gCoreContext->GetSettingOnHost("BackendServerIP", hostname), + gCoreContext->GetSettingOnHost("BackendServerPort", hostname).toInt(), + basename); VERBOSE(VB_FILE, LOC + QString("GetPlaybackURL: Using default of: '%1'") .arg(tmpURL)); diff --git a/mythtv/libs/libmythbase/msocketdevice.cpp b/mythtv/libs/libmythbase/msocketdevice.cpp index 08747c99c8b..76afc030515 100644 --- a/mythtv/libs/libmythbase/msocketdevice.cpp +++ b/mythtv/libs/libmythbase/msocketdevice.cpp @@ -173,21 +173,23 @@ MSocketDevice::MSocketDevice( int socket, Type type ) The \a type argument must be either MSocketDevice::Stream for a reliable, connection-oriented TCP socket, or \c MSocketDevice::Datagram for an unreliable UDP socket. - - The socket is created as an IPv4 socket. + The socket protocol type is defaulting to unknown leaving it to + connect() to determine if an IPv6 or IPv4 type is required. \sa blocking() protocol() */ MSocketDevice::MSocketDevice( Type type ) : fd( -1 ), t( type ), p( 0 ), pp( 0 ), e( NoError ), - d(new MSocketDevicePrivate(IPv4)) + d(new MSocketDevicePrivate(Unknown)) + + // d(new MSocketDevicePrivate(IPv4)) { #if defined(MSOCKETDEVICE_DEBUG) qDebug( "MSocketDevice: Created MSocketDevice object %p, type %d", this, type ); #endif init(); - setSocket( createNewSocket(), type ); + //setSocket( createNewSocket(), type ); } /*! @@ -276,6 +278,11 @@ MSocketDevice::Protocol MSocketDevice::protocol() const return d->protocol; } +void MSocketDevice::setProtocol( Protocol protocol ) +{ + d->protocol = protocol; +} + /*! Returns the socket number, or -1 if it is an invalid socket. diff --git a/mythtv/libs/libmythbase/msocketdevice.h b/mythtv/libs/libmythbase/msocketdevice.h index 4f3814e5be3..91845b64488 100644 --- a/mythtv/libs/libmythbase/msocketdevice.h +++ b/mythtv/libs/libmythbase/msocketdevice.h @@ -66,6 +66,8 @@ class MBASE_PUBLIC MSocketDevice: public QIODevice Type type() const; Protocol protocol() const; + void setProtocol( Protocol protocol ); + int socket() const; virtual void setSocket( int socket, Type type ); @@ -133,6 +135,7 @@ class MBASE_PUBLIC MSocketDevice: public QIODevice Error error() const; inline bool isSequential() const { return true; } + int createNewSocket(); protected: void setError( Error err ); @@ -160,7 +163,6 @@ class MBASE_PUBLIC MSocketDevice: public QIODevice #endif static void init(); - int createNewSocket(); Protocol getProtocol() const; private: // Disabled copy constructor and operator= diff --git a/mythtv/libs/libmythbase/msocketdevice_unix.cpp b/mythtv/libs/libmythbase/msocketdevice_unix.cpp index f9f5f8c7bcb..787f9f48ea4 100644 --- a/mythtv/libs/libmythbase/msocketdevice_unix.cpp +++ b/mythtv/libs/libmythbase/msocketdevice_unix.cpp @@ -391,7 +391,26 @@ void MSocketDevice::setOption( Option opt, int v ) bool MSocketDevice::connect( const QHostAddress &addr, quint16 port ) { if ( !isValid() ) - return false; + { +#if !defined(QT_NO_IPV6) + if ( addr.protocol() == QAbstractSocket::IPv6Protocol ) { + setProtocol(IPv6); + VERBOSE(VB_SOCKET, "MSocketDevice::connect: setting Protocol to IPv6"); + } + else +#endif + if ( addr.protocol() == QAbstractSocket::IPv4Protocol ) { + setProtocol(IPv4); + VERBOSE(VB_SOCKET, "MSocketDevice::connect: setting Protocol to IPv4"); + } + + VERBOSE(VB_SOCKET, "MSocketDevice::connect: attempting to create new socket"); + setSocket( createNewSocket(), t); + + // If still not valid, give up. + if ( !isValid() ) + return false; + } pa = addr; pp = port; diff --git a/mythtv/libs/libmythbase/msocketdevice_win.cpp b/mythtv/libs/libmythbase/msocketdevice_win.cpp index a84c278d8c3..72920030f51 100644 --- a/mythtv/libs/libmythbase/msocketdevice_win.cpp +++ b/mythtv/libs/libmythbase/msocketdevice_win.cpp @@ -405,7 +405,26 @@ void MSocketDevice::setOption( Option opt, int v ) bool MSocketDevice::connect( const QHostAddress &addr, quint16 port ) { if ( !isValid() ) - return false; + { +#if !defined(QT_NO_IPV6) + if ( addr.protocol() == QAbstractSocket::IPv6Protocol ) { + setProtocol(IPv6); + VERBOSE(VB_SOCKET, "MSocketDevice::connect: setting Protocol to IPv6"); + } + else +#endif + if ( addr.protocol() == QAbstractSocket::IPv4Protocol ) { + setProtocol(IPv4); + VERBOSE(VB_SOCKET, "MSocketDevice::connect: setting Protocol to IPv4"); + } + + VERBOSE(VB_SOCKET, "MSocketDevice::connect: attempting to create new socket"); + setSocket( createNewSocket(), t); + + // If still not valid, give up. + if ( !isValid() ) + return false; + } pa = addr; pp = port; diff --git a/mythtv/libs/libmythbase/mythcorecontext.cpp b/mythtv/libs/libmythbase/mythcorecontext.cpp index 12a0b4aad8a..1f6cc0dee6d 100644 --- a/mythtv/libs/libmythbase/mythcorecontext.cpp +++ b/mythtv/libs/libmythbase/mythcorecontext.cpp @@ -542,19 +542,59 @@ bool MythCoreContext::IsFrontendOnly(void) return !backendOnLocalhost; } +QString MythCoreContext::GenMythURL(QString host, QString port, QString path, QString storageGroup) +{ + return GenMythURL(host,port.toInt(),path,storageGroup); +} + +QString MythCoreContext::GenMythURL(QString host, int port, QString path, QString storageGroup) +{ + QString ret; + + QString m_storageGroup; + QString m_host; + QString m_port; + + QHostAddress addr(host); + + if (!storageGroup.isEmpty()) + m_storageGroup = storageGroup + "@"; + + m_host = host; + +#if !defined(QT_NO_IPV6) + // Basically if it appears to be an IPv6 IP surround the IP with [] otherwise don't bother + if (( addr.protocol() == QAbstractSocket::IPv6Protocol ) || (host.contains(":"))) + m_host = "[" + host + "]"; +#endif + + if (port > 0) + m_port = QString(":%1").arg(port); + else + m_port = ""; + + QString seperator = "/"; + if (path.startsWith("/")) + seperator = ""; + + ret = QString("myth://%1%2%3%4%5").arg(m_storageGroup).arg(m_host).arg(m_port).arg(seperator).arg(path); + + //VERBOSE(VB_GENERAL, LOC + QString("GenMythURL returning %1").arg(ret)); + + return ret; +} + + QString MythCoreContext::GetMasterHostPrefix(QString storageGroup) { QString ret; if (IsMasterHost()) { - if (storageGroup.isEmpty()) - return QString("myth://%1:%2/").arg(GetSetting("MasterServerIP")) - .arg(GetNumSetting("MasterServerPort", 6543)); - else - return QString("myth://%1@%2:%3/").arg(storageGroup) - .arg(GetSetting("MasterServerIP")) - .arg(GetNumSetting("MasterServerPort", 6543)); + return GenMythURL(GetSetting("MasterServerIP"), + GetNumSetting("MasterServerPort", 6543), + "", + storageGroup); } QMutexLocker locker(&d->m_sockLock); @@ -566,15 +606,11 @@ QString MythCoreContext::GetMasterHostPrefix(QString storageGroup) if (d->m_serverSock) { - if (storageGroup.isEmpty()) - ret = QString("myth://%1:%2/") - .arg(d->m_serverSock->peerAddress().toString()) - .arg(d->m_serverSock->peerPort()); - else - ret = QString("myth://%1@%2:%3/") - .arg(storageGroup) - .arg(d->m_serverSock->peerAddress().toString()) - .arg(d->m_serverSock->peerPort()); + + ret = GenMythURL(d->m_serverSock->peerAddress().toString(), + d->m_serverSock->peerPort(), + "", + storageGroup); } return ret; diff --git a/mythtv/libs/libmythbase/mythcorecontext.h b/mythtv/libs/libmythbase/mythcorecontext.h index 3af42e6178a..6c19298ea78 100644 --- a/mythtv/libs/libmythbase/mythcorecontext.h +++ b/mythtv/libs/libmythbase/mythcorecontext.h @@ -81,6 +81,12 @@ class MBASE_PUBLIC MythCoreContext : public MythObservable, public MythSocketCBs uint timeout_ms = kMythSocketLongTimeout, bool error_dialog_desired = false); + QString GenMythURL(QString host = QString(), QString port = QString(), + QString path = QString(), QString storageGroup = QString()); + + QString GenMythURL(QString host = QString(), int port = 0, + QString path = QString(), QString storageGroup = QString()); + QString GetMasterHostPrefix(QString storageGroup = QString()); QString GetMasterHostName(void); QString GetHostName(void); diff --git a/mythtv/libs/libmythbase/storagegroup.cpp b/mythtv/libs/libmythbase/storagegroup.cpp index 35f5948a861..f2b635f8ef4 100644 --- a/mythtv/libs/libmythbase/storagegroup.cpp +++ b/mythtv/libs/libmythbase/storagegroup.cpp @@ -806,9 +806,10 @@ QStringList StorageGroup::getGroupDirs(QString groupname, QString host) * value using QString::fromUtf8() to prevent corruption. */ dirname = QString::fromUtf8(query.value(0) .toByteArray().constData()); - groups += QString("myth://%1@%2%3").arg(groupname) - .arg(query.value(1).toString()) - .arg(dirname); + groups += gCoreContext->GenMythURL(query.value(1).toString(), + 0, + dirname, + groupname); } } diff --git a/mythtv/libs/libmythmetadata/metadataimagedownload.cpp b/mythtv/libs/libmythmetadata/metadataimagedownload.cpp index 31c173cf405..0328d948ae5 100644 --- a/mythtv/libs/libmythmetadata/metadataimagedownload.cpp +++ b/mythtv/libs/libmythmetadata/metadataimagedownload.cpp @@ -398,9 +398,7 @@ QString getStorageGroupURL(ArtworkType type, QString host) else sgroup = "Default"; - return QString("myth://%1@%2:%3/") - .arg(sgroup) - .arg(ip).arg(port); + return gCoreContext->GenMythURL(ip,port,"",sgroup); } void cleanThumbnailCacheDir() diff --git a/mythtv/libs/libmythmetadata/videoutils.h b/mythtv/libs/libmythmetadata/videoutils.h index a90797927ab..1aba6acef8f 100644 --- a/mythtv/libs/libmythmetadata/videoutils.h +++ b/mythtv/libs/libmythmetadata/videoutils.h @@ -67,9 +67,9 @@ inline QString generate_file_url( uint port = gCoreContext->GetSettingOnHost("BackendServerPort", host).toUInt(); - return QString("myth://%1@%2:%3/%4") - .arg(StorageGroup::GetGroupToUse(host, storage_group)) - .arg(ip).arg(port).arg(path); + return gCoreContext->GenMythURL(ip,port,path, + StorageGroup::GetGroupToUse(host, storage_group)); + } #endif // VIDEOUTILS_H_ diff --git a/mythtv/libs/libmythtv/DeviceReadBuffer.cpp b/mythtv/libs/libmythtv/DeviceReadBuffer.cpp index 9dfbf0e9dec..101756ebc13 100644 --- a/mythtv/libs/libmythtv/DeviceReadBuffer.cpp +++ b/mythtv/libs/libmythtv/DeviceReadBuffer.cpp @@ -4,9 +4,10 @@ using namespace std; #include #include "DeviceReadBuffer.h" #include "mythcorecontext.h" +#include "mythbaseutil.h" +#include "mythverbose.h" #include "tspacket.h" #include "compat.h" -#include "mythverbose.h" #include "mythlogging.h" #ifndef USING_MINGW @@ -19,17 +20,18 @@ using namespace std; #define LOC QString("DevRdB(%1): ").arg(videodevice) #define LOC_ERR QString("DevRdB(%1) Error: ").arg(videodevice) -DeviceReadBuffer::DeviceReadBuffer(ReaderPausedCB *cb, bool use_poll) +DeviceReadBuffer::DeviceReadBuffer(DeviceReaderCB *cb, bool use_poll) : videodevice(QString::null), _stream_fd(-1), - readerPausedCB(cb), + readerCB(cb), // Data for managing the device ringbuffer - run(false), running(false), + dorun(false), running(false), eof(false), error(false), request_pause(false), paused(false), using_poll(use_poll), max_poll_wait(2500 /*ms*/), size(0), used(0), + read_quanta(0), dev_read_size(0), min_read(0), buffer(NULL), readPtr(NULL), @@ -39,6 +41,21 @@ DeviceReadBuffer::DeviceReadBuffer(ReaderPausedCB *cb, bool use_poll) max_used(0), avg_used(0), avg_cnt(0) { + for (int i = 0; i < 2; i++) + { + wake_pipe[i] = -1; + wake_pipe_flags[i] = 0; + } + +#ifdef USING_MINGW +#warning mingw DeviceReadBuffer::Poll + if (using_poll) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + + "mingw DeviceReadBuffer::Poll is not implemented"); + using_poll = false; + } +#endif } DeviceReadBuffer::~DeviceReadBuffer() @@ -47,7 +64,8 @@ DeviceReadBuffer::~DeviceReadBuffer() delete[] buffer; } -bool DeviceReadBuffer::Setup(const QString &streamName, int streamfd) +bool DeviceReadBuffer::Setup(const QString &streamName, int streamfd, + uint readQuanta, uint deviceBufferSize) { QMutexLocker locker(&lock); @@ -57,7 +75,7 @@ bool DeviceReadBuffer::Setup(const QString &streamName, int streamfd) videodevice = streamName; _stream_fd = streamfd; - // BEGIN HACK -- see #6897 + // BEGIN HACK -- see #6897, remove after August 2009 max_poll_wait = (videodevice.contains("dvb")) ? 25000 : 2500; // END HACK @@ -67,21 +85,29 @@ bool DeviceReadBuffer::Setup(const QString &streamName, int streamfd) request_pause = false; paused = false; - size = gCoreContext->GetNumSetting("HDRingbufferSize", - 50 * TSPacket::kSize) * 1024; + read_quanta = (readQuanta) ? readQuanta : read_quanta; + size = gCoreContext->GetNumSetting( + "HDRingbufferSize", 50 * read_quanta) * 1024; used = 0; - dev_read_size = TSPacket::kSize * (using_poll ? 256 : 48); - min_read = TSPacket::kSize * 4; + dev_read_size = read_quanta * (using_poll ? 256 : 48); + dev_read_size = (deviceBufferSize) ? + min(dev_read_size, (size_t)deviceBufferSize) : dev_read_size; + min_read = read_quanta * 4; - buffer = new unsigned char[size + TSPacket::kSize]; + buffer = new unsigned char[size + dev_read_size]; readPtr = buffer; writePtr = buffer; endPtr = buffer + size; // Initialize buffer, if it exists if (!buffer) + { + VERBOSE(VB_IMPORTANT, LOC + + QString("Failed to allocate buffer of size %1 = %2 + %3") + .arg(size+dev_read_size).arg(size).arg(dev_read_size)); return false; - memset(buffer, 0xFF, size + TSPacket::kSize); + } + memset(buffer, 0xFF, size + read_quanta); // Initialize statistics max_used = 0; @@ -96,32 +122,31 @@ bool DeviceReadBuffer::Setup(const QString &streamName, int streamfd) void DeviceReadBuffer::Start(void) { - bool was_running; + VERBOSE(VB_RECORD, LOC + "Start() -- begin"); + if (isRunning()) { - QMutexLocker locker(&lock); - was_running = running; - error = false; + { + QMutexLocker locker(&lock); + dorun = false; + } + wait(); } - if (was_running) { - VERBOSE(VB_IMPORTANT, LOC_ERR + "Start(): Already running."); - SetRequestPause(false); - return; + QMutexLocker locker(&lock); + error = false; + eof = false; } - thread.SetBuffer(this); - thread.start(); + start(); - if (!thread.isRunning()) - { - VERBOSE(VB_IMPORTANT, - LOC_ERR + QString("Start(): thread.run() failed.") + ENO); + VERBOSE(VB_RECORD, LOC + "Start() -- middle"); - QMutexLocker locker(&lock); - error = true; - } + while (!IsRunning()) + usleep(5000); + + VERBOSE(VB_RECORD, LOC + "Start() -- end"); } void DeviceReadBuffer::Reset(const QString &streamName, int streamfd) @@ -140,26 +165,24 @@ void DeviceReadBuffer::Reset(const QString &streamName, int streamfd) void DeviceReadBuffer::Stop(void) { - bool was_running = IsRunning(); - - if (!was_running) - { - VERBOSE(VB_IMPORTANT, LOC + "Stop(): Not running."); - return; - } - + VERBOSE(VB_RECORD, LOC + "Stop() -- begin"); { QMutexLocker locker(&lock); - run = false; + dorun = false; } - thread.wait(); + WakePoll(); + VERBOSE(VB_RECORD, LOC + "Stop() -- middle"); + + wait(); + VERBOSE(VB_RECORD, LOC + "Stop() -- end"); } void DeviceReadBuffer::SetRequestPause(bool req) { QMutexLocker locker(&lock); request_pause = req; + WakePoll(); } void DeviceReadBuffer::SetPaused(bool val) @@ -172,6 +195,37 @@ void DeviceReadBuffer::SetPaused(bool val) unpauseWait.wakeAll(); } +// The WakePoll code is copied from MythSocketThread::WakeReadyReadThread() +void DeviceReadBuffer::WakePoll(void) const +{ + char buf[1]; + buf[0] = '0'; + ssize_t wret = 0; + while (running && (wret <= 0) && (wake_pipe[1] >= 0)) + { + wret = ::write(wake_pipe[1], &buf, 1); + if ((wret < 0) && (EAGAIN != errno) && (EINTR != errno)) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "WakePoll failed."); + ClosePipes(); + break; + } + } +} + +void DeviceReadBuffer::ClosePipes(void) const +{ + for (uint i = 0; i < 2; i++) + { + if (wake_pipe[i] >= 0) + { + ::close(wake_pipe[i]); + wake_pipe[i] = -1; + wake_pipe_flags[i] = 0; + } + } +} + bool DeviceReadBuffer::IsPaused(void) const { QMutexLocker locker(&lock); @@ -204,6 +258,18 @@ bool DeviceReadBuffer::IsPauseRequested(void) const return request_pause; } +bool DeviceReadBuffer::IsErrored(void) const +{ + QMutexLocker locker(&lock); + return error; +} + +bool DeviceReadBuffer::IsEOF(void) const +{ + QMutexLocker locker(&lock); + return eof; +} + bool DeviceReadBuffer::IsRunning(void) const { QMutexLocker locker(&lock); @@ -233,7 +299,7 @@ void DeviceReadBuffer::IncrWritePointer(uint len) QMutexLocker locker(&lock); used += len; writePtr += len; - writePtr = (writePtr == endPtr) ? buffer : writePtr; + writePtr = (writePtr >= endPtr) ? buffer + (writePtr - endPtr) : writePtr; #if REPORT_RING_STATS max_used = max(used, max_used); avg_used = ((avg_used * avg_cnt) + used) / ++avg_cnt; @@ -248,26 +314,20 @@ void DeviceReadBuffer::IncrReadPointer(uint len) readPtr = (readPtr == endPtr) ? buffer : readPtr; } -void DRBThread::run(void) -{ - threadRegister("DeviceReadBuffer"); - if (!m_buffer) - return; - - m_buffer->fill_ringbuffer(); - threadDeregister(); -} - -void DeviceReadBuffer::fill_ringbuffer(void) +void DeviceReadBuffer::run(void) { uint errcnt = 0; + threadRegister("DeviceReadBuffer"); lock.lock(); - run = true; + dorun = true; running = true; lock.unlock(); - while (run) + if (using_poll) + setup_pipe(wake_pipe, wake_pipe_flags); + + while (dorun) { if (!HandlePausing()) continue; @@ -291,14 +351,14 @@ void DeviceReadBuffer::fill_ringbuffer(void) } // Limit read size for faster return from read - size_t read_size = - min(dev_read_size, (size_t) WaitForUnused(TSPacket::kSize)); + size_t unused = (size_t) WaitForUnused(read_quanta); + size_t read_size = min(dev_read_size, unused); // if read_size > 0 do the read... if (read_size) { ssize_t len = read(_stream_fd, writePtr, read_size); - if (!CheckForErrors(len, errcnt)) + if (!CheckForErrors(len, read_size, errcnt)) { if (errcnt > 5) break; @@ -306,13 +366,21 @@ void DeviceReadBuffer::fill_ringbuffer(void) continue; } errcnt = 0; + // if we wrote past the official end of the buffer, copy to start + if (writePtr + len > endPtr) + memcpy(buffer, endPtr, writePtr + len - endPtr); IncrWritePointer(len); } } + ClosePipes(); + lock.lock(); running = false; + eof = true; lock.unlock(); + + threadDeregister(); } bool DeviceReadBuffer::HandlePausing(void) @@ -321,8 +389,8 @@ bool DeviceReadBuffer::HandlePausing(void) { SetPaused(true); - if (readerPausedCB) - readerPausedCB->ReaderPaused(_stream_fd); + if (readerCB) + readerCB->ReaderPaused(_stream_fd); usleep(5000); return false; @@ -351,57 +419,112 @@ bool DeviceReadBuffer::Poll(void) const MythTimer timer; timer.start(); + int poll_cnt = 1; + struct pollfd polls[2]; + memset(polls, 0, sizeof(polls)); + + polls[0].fd = _stream_fd; + polls[0].events = POLLIN | POLLPRI; + polls[0].revents = 0; + + if (wake_pipe[0] >= 0) + { + poll_cnt = 2; + polls[1].fd = wake_pipe[0]; + polls[1].events = POLLIN; + polls[1].revents = 0; + } + while (true) { - struct pollfd polls; - polls.fd = _stream_fd; - polls.events = POLLIN; - polls.revents = 0; + polls[0].revents = 0; + polls[1].revents = 0; + poll_cnt = (wake_pipe[0] >= 0) ? poll_cnt : 1; - int ret = poll(&polls, 1 /*number of polls*/, 10 /*msec*/); + int timeout = max((int)max_poll_wait - timer.elapsed(), 10); + timeout = (1 == poll_cnt) ? 10 : timeout; + int ret = poll(polls, poll_cnt, timeout); - if (polls.revents & (POLLHUP | POLLNVAL)) + if (polls[0].revents & (POLLHUP | POLLNVAL)) { VERBOSE(VB_IMPORTANT, LOC + "poll error"); error = true; return true; } - if (!run || !IsOpen() || IsPauseRequested()) + if (!dorun || !IsOpen() || IsPauseRequested()) { retval = false; break; // are we supposed to pause, stop, etc. } - if (ret > 0) - break; // we have data to read :) - else if (ret < 0) + if (polls[0].revents & POLLPRI) { - if ((EOVERFLOW == errno)) - break; // we have an error to handle - - if ((EAGAIN == errno) || (EINTR == errno)) - continue; // errors that tell you to try again - - usleep(2500 /*2.5 ms*/); + readerCB->PriorityEvent(polls[0].fd); } - else // ret == 0 + + if (polls[0].revents & POLLIN) { - if ((uint)timer.elapsed() > max_poll_wait) + if (ret > 0) + break; // we have data to read :) + else if (ret < 0) { - VERBOSE(VB_IMPORTANT, LOC_ERR + "Poll giving up"); - QMutexLocker locker(&lock); - error = true; - return true; + if ((EOVERFLOW == errno)) + break; // we have an error to handle + + if ((EAGAIN == errno) || (EINTR == errno)) + continue; // errors that tell you to try again + + usleep(2500 /*2.5 ms*/); } + else // ret == 0 + { + if ((uint)timer.elapsed() >= max_poll_wait) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "Poll giving up 1"); + QMutexLocker locker(&lock); + error = true; + return true; + } + } + } + + // Clear out any pending pipe reads + if ((poll_cnt > 1) && (polls[1].revents & POLLIN)) + { + char dummy[128]; + int cnt = (wake_pipe_flags[0] & O_NONBLOCK) ? 128 : 1; + cnt = ::read(wake_pipe[0], dummy, cnt); + } + + if ((uint)timer.elapsed() >= max_poll_wait) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "Poll giving up 2"); + QMutexLocker locker(&lock); + error = true; + return true; } } return retval; #endif //!USING_MINGW } -bool DeviceReadBuffer::CheckForErrors(ssize_t len, uint &errcnt) +bool DeviceReadBuffer::CheckForErrors( + ssize_t len, size_t requested_len, uint &errcnt) { + if (len > (ssize_t)requested_len) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + + "Driver is retruning bogus values on read"); + if (++errcnt > 5) + { + VERBOSE(VB_RECORD, LOC + "Too many errors."); + QMutexLocker locker(&lock); + error = true; + } + return false; + } + #ifdef USING_MINGW # ifdef _MSC_VER # pragma message( "mingw DeviceReadBuffer::CheckForErrors" ) @@ -469,7 +592,7 @@ bool DeviceReadBuffer::CheckForErrors(ssize_t len, uint &errcnt) */ uint DeviceReadBuffer::Read(unsigned char *buf, const uint count) { - uint avail = WaitForUsed(min(count, (uint)min_read)); + uint avail = WaitForUsed(min(count, (uint)min_read), 500); size_t cnt = min(count, avail); if (!cnt) @@ -512,41 +635,44 @@ uint DeviceReadBuffer::Read(unsigned char *buf, const uint count) uint DeviceReadBuffer::WaitForUnused(uint needed) const { size_t unused = GetUnused(); - size_t contig = GetContiguousUnused(); - if (contig > TSPacket::kSize) + if (unused > read_quanta) { while (unused < needed) { unused = GetUnused(); - if (IsPauseRequested() || !IsOpen() || !run) + if (IsPauseRequested() || !IsOpen() || !dorun) return 0; usleep(5000); } - if (IsPauseRequested() || !IsOpen() || !run) + if (IsPauseRequested() || !IsOpen() || !dorun) return 0; - contig = GetContiguousUnused(); + unused = GetUnused(); } - return min(contig, unused); + return unused; } -/** \fn DeviceReadBuffer::WaitForUsed(uint) const +/** \fn DeviceReadBuffer::WaitForUsed(uint,uint) const * \param needed Number of bytes we want to read + * \param max_wait Number of milliseconds to wait for the needed data * \return bytes available for reading */ -uint DeviceReadBuffer::WaitForUsed(uint needed) const +uint DeviceReadBuffer::WaitForUsed(uint needed, uint max_wait) const { - size_t avail = GetUsed(); - while ((needed > avail) && running) + QWaitCondition dataWait; + + MythTimer timer; + timer.start(); + + QMutexLocker locker(&lock); + size_t avail = used; + while ((needed > avail) && running && + !request_pause && !error && !eof && + (timer.elapsed() < (int)max_wait)) { - { - QMutexLocker locker(&lock); - avail = used; - if (request_pause || error || eof) - return 0; - } - usleep(5000); + dataWait.wait(locker.mutex(), 10); + avail = used; } return avail; } diff --git a/mythtv/libs/libmythtv/DeviceReadBuffer.h b/mythtv/libs/libmythtv/DeviceReadBuffer.h index eff9a99aec9..4023588b884 100644 --- a/mythtv/libs/libmythtv/DeviceReadBuffer.h +++ b/mythtv/libs/libmythtv/DeviceReadBuffer.h @@ -11,27 +11,16 @@ #include #include +#include "tspacket.h" #include "util.h" -class ReaderPausedCB +class DeviceReaderCB { protected: - virtual ~ReaderPausedCB() {} + virtual ~DeviceReaderCB() {} public: virtual void ReaderPaused(int fd) = 0; -}; - -class DeviceReadBuffer; - -class DRBThread : public QThread -{ - Q_OBJECT - public: - DRBThread(void) : m_buffer(NULL) {}; - void SetBuffer( DeviceReadBuffer *buffer ) { m_buffer = buffer; }; - void run(void); - private: - DeviceReadBuffer *m_buffer; + virtual void PriorityEvent(int fd) = 0; }; /** \class DeviceReadBuffer @@ -41,15 +30,17 @@ class DRBThread : public QThread * of long blocking conditions on writing to disk or accessing the * database. */ -class DeviceReadBuffer +class DeviceReadBuffer : protected QThread { - friend class DRBThread; - public: - DeviceReadBuffer(ReaderPausedCB *callback, bool use_poll = true); + DeviceReadBuffer(DeviceReaderCB *callback, + bool use_poll = true); ~DeviceReadBuffer(); - bool Setup(const QString &streamName, int streamfd); + bool Setup(const QString &streamName, + int streamfd, + uint readQuanta = sizeof(TSPacket), + uint deviceBufferSize = 0); void Start(void); void Reset(const QString &streamName, int streamfd); @@ -60,14 +51,14 @@ class DeviceReadBuffer bool WaitForUnpause(unsigned long timeout); bool WaitForPaused(unsigned long timeout); - bool IsErrored(void) const { return error; } - bool IsEOF(void) const { return eof; } + bool IsErrored(void) const; + bool IsEOF(void) const; bool IsRunning(void) const; uint Read(unsigned char *buf, uint count); private: - void fill_ringbuffer(void); + virtual void run(void); // QThread void SetPaused(bool); void IncrWritePointer(uint len); @@ -75,26 +66,30 @@ class DeviceReadBuffer bool HandlePausing(void); bool Poll(void) const; + void WakePoll(void) const; uint WaitForUnused(uint bytes_needed) const; - uint WaitForUsed (uint bytes_needed) const; + uint WaitForUsed (uint bytes_needed, uint max_wait /*ms*/) const; bool IsPauseRequested(void) const; bool IsOpen(void) const { return _stream_fd >= 0; } + void ClosePipes(void) const; uint GetUnused(void) const; uint GetUsed(void) const; uint GetContiguousUnused(void) const; - bool CheckForErrors(ssize_t read_len, uint &err_cnt); + bool CheckForErrors(ssize_t read_len, size_t requested_len, uint &err_cnt); void ReportStats(void); QString videodevice; int _stream_fd; + mutable int wake_pipe[2]; + mutable long wake_pipe_flags[2]; - ReaderPausedCB *readerPausedCB; + DeviceReaderCB *readerCB; // Data for managing the device ringbuffer mutable QMutex lock; - bool run; + bool dorun; bool running; bool eof; mutable bool error; @@ -105,6 +100,7 @@ class DeviceReadBuffer size_t size; size_t used; + size_t read_quanta; size_t dev_read_size; size_t min_read; unsigned char *buffer; @@ -120,8 +116,6 @@ class DeviceReadBuffer size_t avg_used; size_t avg_cnt; MythTimer lastReport; - - DRBThread thread; }; #endif // _DEVICEREADBUFFER_H_ diff --git a/mythtv/libs/libmythtv/NuppelVideoRecorder.cpp b/mythtv/libs/libmythtv/NuppelVideoRecorder.cpp index acc65ccaf41..5e0db3fbfa7 100644 --- a/mythtv/libs/libmythtv/NuppelVideoRecorder.cpp +++ b/mythtv/libs/libmythtv/NuppelVideoRecorder.cpp @@ -24,7 +24,6 @@ using namespace std; #include "mythcontext.h" #include "mythverbose.h" #include "NuppelVideoRecorder.h" -#include "vbitext/cc.h" #include "channelbase.h" #include "filtermanager.h" #include "recordingprofile.h" @@ -32,10 +31,12 @@ using namespace std; #include "tv_play.h" #include "audioinput.h" #include "mythlogging.h" +#include "vbitext/cc.h" +#include "vbitext/vbi.h" #if HAVE_BIGENDIAN extern "C" { -#include "bswap.h" +#include "byteswap.h" } #endif @@ -53,11 +54,6 @@ extern "C" { #include "videodev_mjpeg.h" #endif -extern "C" { -#include "vbitext/vbi.h" -} -#else // USING_V4l -#define VT_WIDTH 0 #endif // USING_V4l #define KEYFRAMEDIST 30 @@ -84,17 +80,9 @@ void NVRAudioThread::run(void) threadDeregister(); } -void NVRVBIThread::run(void) -{ - threadRegister("NVRVbi"); - m_parent->doVbiThread(); - threadDeregister(); -} - NuppelVideoRecorder::NuppelVideoRecorder(TVRec *rec, ChannelBase *channel) : - RecorderBase(rec), audio_device(NULL), - write_thread(NULL), audio_thread(NULL), - vbi_thread(NULL) + V4LRecorder(rec), audio_device(NULL), + write_thread(NULL), audio_thread(NULL) { channelObj = channel; @@ -360,7 +348,7 @@ void NuppelVideoRecorder::SetOption(const QString &opt, int value) else if (opt == "volume") volume = value; else - RecorderBase::SetOption(opt, value); + V4LRecorder::SetOption(opt, value); } void NuppelVideoRecorder::SetOptionsFromProfile(RecordingProfile *profile, @@ -616,17 +604,10 @@ bool NuppelVideoRecorder::SetupAVCodecVideo(void) mpa_vidctx->prediction_method = FF_PRED_LEFT; if (videocodec.toLower() == "huffyuv" || videocodec.toLower() == "mjpeg") mpa_vidctx->strict_std_compliance = FF_COMPLIANCE_INOFFICIAL; + mpa_vidctx->thread_count = encoding_thread_count; QMutexLocker locker(avcodeclock); -#ifdef USING_FFMPEG_THREADS - if ((encoding_thread_count > 1) && - avcodec_thread_init(mpa_vidctx, encoding_thread_count)) - { - VERBOSE(VB_IMPORTANT, LOC + "FFMPEG couldn't start threading..."); - } -#endif - if (avcodec_open(mpa_vidctx, mpa_vidcodec) < 0) { VERBOSE(VB_IMPORTANT, LOC_ERR + @@ -1007,6 +988,7 @@ void NuppelVideoRecorder::ResizeVideoBuffers(void) void NuppelVideoRecorder::StopRecording(void) { encoding = false; + V4LRecorder::StopRecording(); } void NuppelVideoRecorder::StreamAllocate(void) @@ -1912,11 +1894,8 @@ bool NuppelVideoRecorder::SpawnChildren(void) audio_thread = new NVRAudioThread(this); audio_thread->start(); - if (vbimode) - { - vbi_thread = new NVRVBIThread(this); - vbi_thread->start(); - } + if ((vbimode != VBIMode::None) && (OpenVBIDevice() >= 0)) + vbi_thread = new VBIThread(this); return true; } @@ -1949,11 +1928,6 @@ void NuppelVideoRecorder::KillChildren(void) delete vbi_thread; vbi_thread = NULL; } - -#ifdef USING_FFMPEG_THREADS - if (useavcodec && encoding_thread_count > 1) - avcodec_thread_free(mpa_vidctx); -#endif } void NuppelVideoRecorder::BufferIt(unsigned char *buf, int len, bool forcekey) @@ -2478,14 +2452,7 @@ void NuppelVideoRecorder::doAudioThread(void) } #ifdef USING_V4L -struct VBIData -{ - NuppelVideoRecorder *nvr; - vt_page teletextpage; - bool foundteletextpage; -}; - -void NuppelVideoRecorder::FormatTeletextSubtitles(struct VBIData *vbidata) +void NuppelVideoRecorder::FormatTT(struct VBIData *vbidata) { struct timeval tnow; gettimeofday(&tnow, &tzone); @@ -2652,7 +2619,7 @@ void NuppelVideoRecorder::FormatTeletextSubtitles(struct VBIData *vbidata) textbuffer[act]->freeToEncode = 1; } #else // USING_V4L -void NuppelVideoRecorder::FormatTeletextSubtitles(struct VBIData *vbidata) {} +void NuppelVideoRecorder::FormatTT(struct VBIData*) {} #endif // USING_V4L void NuppelVideoRecorder::FormatCC(struct cc *cc) @@ -2690,198 +2657,6 @@ void NuppelVideoRecorder::AddTextData(unsigned char *buf, int len, textbuffer[act]->freeToEncode = 1; } -#ifdef USING_V4L -static void vbi_event(struct VBIData *data, struct vt_event *ev) -{ - switch (ev->type) - { - case EV_PAGE: - { - struct vt_page *vtp = (struct vt_page *) ev->p1; - if (vtp->flags & PG_SUBTITLE) - { - //printf("subtitle page %x.%x\n", vtp->pgno, vtp->subno); - data->foundteletextpage = true; - memcpy(&(data->teletextpage), vtp, sizeof(vt_page)); - } - } - - case EV_HEADER: - case EV_XPACKET: - break; - } -} - -/* -These are the default values for various VBI drivers -// bttv -vbi_format rate: 28636363 -samples_per_line: 2048 - starts: 10, 273 - counts: 16, 16 - flags: 0x0 -// cx88 -vbi_format rate: 28636363 -samples_per_line: 2048 - starts: 9, 272 - counts: 17, 17 - flags: 0x0 -*/ - -void NuppelVideoRecorder::doVbiThread(void) -{ - //VERBOSE(VB_IMPORTANT, LOC + "vbi begin"); - struct VBIData vbicallbackdata; - struct vbi *pal_tt = NULL; - struct cc *ntsc_cc = NULL; - int vbifd = -1; - char *ptr = NULL; - char *ptr_end = NULL; - - QByteArray vbidev = vbidevice.toAscii(); - if (VBIMode::PAL_TT == vbimode) - { - pal_tt = vbi_open(vbidev.constData(), NULL, 99, -1); - if (pal_tt) - { - vbifd = pal_tt->fd; - vbicallbackdata.nvr = this; - vbi_add_handler(pal_tt, (void*) vbi_event, &vbicallbackdata); - } - } - else if (VBIMode::NTSC_CC == vbimode) - { - ntsc_cc = new struct cc; - memset(ntsc_cc, 0, sizeof(struct cc)); - ntsc_cc->fd = open(vbidev.constData(), O_RDONLY|O_NONBLOCK); - ntsc_cc->code1 = -1; - ntsc_cc->code2 = -1; - - vbifd = ntsc_cc->fd; - ptr = ntsc_cc->buffer; - - if (vbifd < 0) - delete ntsc_cc; - } - else - { - VERBOSE(VB_IMPORTANT, LOC_ERR + "Invalid CC/Teletext mode"); - return; - } - - if (vbifd < 0) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + - QString("Can't open vbi device: '%1'").arg(vbidevice)); - return; - } - - if (VBIMode::NTSC_CC == vbimode) - { - // V4L v1 VBI ioctls - struct vbi_format vfmt; - memset(&vfmt, 0, sizeof(vbi_format)); - if (ioctl(vbifd, VIDIOCGVBIFMT, &vfmt) < 0) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + - "Failed to query vbi capabilities (v4l1)"); - cc_close(ntsc_cc); - return; - } - VERBOSE(VB_RECORD, LOC + - QString("vbi_format rate: %1").arg(vfmt.sampling_rate) + - QString("\n\t\t\tsamples_per_line: %1").arg(vfmt.samples_per_line) + - QString("\n\t\t\t starts: %1, %2").arg(vfmt.start[0]) - .arg(vfmt.start[1]) + - QString("\n\t\t\t counts: %1, %2").arg(vfmt.count[0]) - .arg(vfmt.count[1]) + - QString("n\t\t\t flags: 0x%1").arg(vfmt.flags,0,16)); - uint sz = vfmt.samples_per_line * (vfmt.count[0] + vfmt.count[1]); - ntsc_cc->samples_per_line = vfmt.samples_per_line; - ntsc_cc->start_line = vfmt.start[0]; - ntsc_cc->line_count = vfmt.count[0]; - ntsc_cc->scale0 = (vfmt.sampling_rate + 503488 / 2) / 503488; - ntsc_cc->scale1 = (ntsc_cc->scale0 * 2 + 3) / 5; /* 40% */ - ptr_end = ntsc_cc->buffer + sz; - if (sz > CC_VBIBUFSIZE) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + - "VBI format has too many samples per frame"); - cc_close(ntsc_cc); - return; - } - } - - while (childrenLive) - { - { - QMutexLocker locker(&pauseLock); - if (request_pause) - { - unpauseWait.wait(&pauseLock, 100); - continue; - } - } - - struct timeval tv; - fd_set rdset; - - tv.tv_sec = 0; - tv.tv_usec = 5000; - FD_ZERO(&rdset); - FD_SET(vbifd, &rdset); - - int nr = select(vbifd + 1, &rdset, 0, 0, &tv); - if (nr < 0) - VERBOSE(VB_IMPORTANT, LOC_ERR + "vbi select failed" + ENO); - - if (nr <= 0) - continue; // either failed or timed out.. - - if (VBIMode::PAL_TT == vbimode) - { - vbicallbackdata.foundteletextpage = false; - vbi_handler(pal_tt, pal_tt->fd); - if (vbicallbackdata.foundteletextpage) - { - // decode VBI as teletext subtitles - FormatTeletextSubtitles(&vbicallbackdata); - } - } - else if (VBIMode::NTSC_CC == vbimode) - { - int ret = read(vbifd, ptr, ptr_end - ptr); - ptr = (ret > 0) ? ptr + ret : ptr; - if ((ptr_end - ptr) == 0) - { - cc_decode(ntsc_cc); - FormatCC(ntsc_cc); - ptr = ntsc_cc->buffer; - } - else if (ret < 0) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + "Reading VBI data" + ENO); - } - } - } - //VERBOSE(VB_RECORD, LOC + "vbi shutdown"); - - if (pal_tt) - { - vbi_del_handler(pal_tt, (void*) vbi_event, &vbicallbackdata); - vbi_close(pal_tt); - } - - if (ntsc_cc) - cc_close(ntsc_cc); - - //VERBOSE(VB_RECORD, LOC + "vbi end"); -} - -#else // USING_V4L -void NuppelVideoRecorder::doVbiThread(void) { } -#endif // USING_V4L - void NuppelVideoRecorder::doWriteThread(void) { while (childrenLive && !IsErrored()) @@ -3178,16 +2953,6 @@ void NuppelVideoRecorder::WriteVideo(VideoFrame *frame, bool skipsync, if (freecount < 5) raw = 1; // speed up the encode process - if (raw == 1 || compressthis == 0) - { - if (ringBuffer->IsIOBound()) - { - /* need to compress, the disk can't handle any more bandwidth*/ - raw=0; - compressthis=1; - } - } - if (transcoding) { raw = 0; @@ -3434,18 +3199,25 @@ void NuppelVideoRecorder::WriteText(unsigned char *buf, int len, int timecode, frameheader.frametype = 'T'; // text frame frameheader.timecode = timecode; - if (vbimode == 1) + if (VBIMode::PAL_TT == vbimode) { frameheader.comptype = 'T'; // european teletext - frameheader.packetlength = sizeof(int) + len; - + frameheader.packetlength = len + 4; WriteFrameheader(&frameheader); - ringBuffer->Write(&pagenr, sizeof(int)); + union page_t { + int32_t val32; + struct { int8_t a,b,c,d; } val8; + } v; + v.val32 = pagenr; + ringBuffer->Write(&v.val8.d, sizeof(int8_t)); + ringBuffer->Write(&v.val8.c, sizeof(int8_t)); + ringBuffer->Write(&v.val8.b, sizeof(int8_t)); + ringBuffer->Write(&v.val8.a, sizeof(int8_t)); ringBuffer->Write(buf, len); } - else if (vbimode == 2) + else if (VBIMode::NTSC_CC == vbimode) { - frameheader.comptype = 'C'; // NTSC CC + frameheader.comptype = 'C'; // NTSC CC frameheader.packetlength = len; WriteFrameheader(&frameheader); diff --git a/mythtv/libs/libmythtv/NuppelVideoRecorder.h b/mythtv/libs/libmythtv/NuppelVideoRecorder.h index 336bb68ed5a..9cb954f8f60 100644 --- a/mythtv/libs/libmythtv/NuppelVideoRecorder.h +++ b/mythtv/libs/libmythtv/NuppelVideoRecorder.h @@ -30,7 +30,7 @@ using namespace std; #include // MythTV headers -#include "recorderbase.h" +#include "v4lrecorder.h" #include "format.h" #include "cc608decoder.h" #include "filter.h" @@ -39,8 +39,6 @@ using namespace std; #include "mythtvexp.h" struct video_audio; -struct VBIData; -struct cc; class RTjpeg; class RingBuffer; class ChannelBase; @@ -71,22 +69,10 @@ class NVRAudioThread : public QThread NuppelVideoRecorder *m_parent; }; -class NVRVBIThread : public QThread -{ - Q_OBJECT - public: - NVRVBIThread(NuppelVideoRecorder *parent) : m_parent(parent) {} - virtual ~NVRVBIThread() { wait(); m_parent = NULL; } - virtual void run(void); - private: - NuppelVideoRecorder *m_parent; -}; - -class MTV_PUBLIC NuppelVideoRecorder : public RecorderBase, public CC608Input +class MTV_PUBLIC NuppelVideoRecorder : public V4LRecorder, public CC608Input { friend class NVRWriteThread; friend class NVRAudioThread; - friend class NVRVBIThread; public: NuppelVideoRecorder(TVRec *rec, ChannelBase *channel); ~NuppelVideoRecorder(); @@ -146,7 +132,6 @@ class MTV_PUBLIC NuppelVideoRecorder : public RecorderBase, public CC608Input protected: void doWriteThread(void); void doAudioThread(void); - void doVbiThread(void); private: inline void WriteFrameheader(rtframeheader *fh); @@ -172,10 +157,10 @@ class MTV_PUBLIC NuppelVideoRecorder : public RecorderBase, public CC608Input void DoV4L2(void); void DoMJPEG(void); - void FormatTeletextSubtitles(struct VBIData *vbidata); - void FormatCC(struct cc *cc); - void AddTextData(unsigned char *buf, int len, - int64_t timecode, char type); + virtual void FormatTT(struct VBIData*); // RecorderBase + virtual void FormatCC(struct cc*); // RecorderBase + virtual void AddTextData(unsigned char*,int,int64_t,char); // CC608Decoder + void UpdateResolutions(void); bool encoding; @@ -245,7 +230,6 @@ class MTV_PUBLIC NuppelVideoRecorder : public RecorderBase, public CC608Input NVRWriteThread *write_thread; NVRAudioThread *audio_thread; - NVRVBIThread *vbi_thread; bool recording; bool errored; diff --git a/mythtv/libs/libmythtv/ThreadedFileWriter.cpp b/mythtv/libs/libmythtv/ThreadedFileWriter.cpp index 7ea64468a0d..ac823b90f2a 100644 --- a/mythtv/libs/libmythtv/ThreadedFileWriter.cpp +++ b/mythtv/libs/libmythtv/ThreadedFileWriter.cpp @@ -17,95 +17,18 @@ // MythTV headers #include "ThreadedFileWriter.h" -#include "compat.h" #include "mythverbose.h" #include "mythlogging.h" - -#define LOC QString("TFW(%1): ").arg(fd) -#define LOC_ERR QString("TFW(%1), Error: ").arg(fd) - -const uint ThreadedFileWriter::TFW_DEF_BUF_SIZE = 2*1024*1024; -const uint ThreadedFileWriter::TFW_MAX_WRITE_SIZE = TFW_DEF_BUF_SIZE / 4; -const uint ThreadedFileWriter::TFW_MIN_WRITE_SIZE = TFW_DEF_BUF_SIZE / 32; - -/** \class ThreadedFileWriter - * \brief This class supports the writing of recordings to disk. - * - * This class allows us manage the buffering when writing to - * disk. We write to the kernel image of the disk using one - * thread, and sync the kernel's image of the disk to hardware - * using another thread. The goal here so to block as little as - * possible when the classes using this class want to add data - * to the stream. - */ - -/** \fn safe_write(int, const void*, uint, bool &ok) - * \brief Writes data to disk - * - * This just uses the Standard C write() to write to disk. - * We retry forever on EAGAIN errors, and three times on - * any other error. - * - * \param fd File descriptor - * \param data Pointer to data to write - * \param sz Size of data to write in bytes - */ -static uint safe_write(int fd, const void *data, uint sz, bool &ok) -{ - int ret; - uint tot = 0; - uint errcnt = 0; - - while (tot < sz) - { - ret = write(fd, (char *)data + tot, sz - tot); - if (ret < 0) - { - if (errno == EAGAIN) - { - VERBOSE(VB_IMPORTANT, LOC + "safe_write(): Got EAGAIN."); - continue; - } - if (errno == ENOSPC) - { - VERBOSE(VB_IMPORTANT, LOC + "safe_write(): Got ENOSPC (No space left on device)."); - errcnt = 10; - tot = 0; - break; - } - errcnt++; - VERBOSE(VB_IMPORTANT, LOC_ERR + "safe_write(): File I/O " + - QString(" errcnt: %1").arg(errcnt) + ENO); - - if (errcnt == 3) - break; - } - else - { - tot += ret; - } - - if (tot < sz) - { - VERBOSE(VB_IMPORTANT, LOC + "safe_write(): funky usleep"); - usleep(1000); - } - } - ok = (errcnt < 3); - return tot; -} - -#undef LOC -#undef LOC_ERR -#define LOC QString("TFW(%1:%2): ").arg(filename).arg(fd) -#define LOC_ERR QString("TFW(%1:%2), Error: ").arg(filename).arg(fd) +#include "mythtimer.h" +#include "compat.h" /// \brief Runs ThreadedFileWriter::DiskLoop(void) void TFWWriteThread::run(void) { threadRegister("TFWWrite"); #ifndef USING_MINGW + // don't exit program if file gets larger than quota limit.. signal(SIGXFSZ, SIG_IGN); #endif m_parent->DiskLoop(); @@ -120,6 +43,24 @@ void TFWSyncThread::run(void) threadDeregister(); } +const uint ThreadedFileWriter::kMaxBufferSize = 128 * 1024 * 1024; +const uint ThreadedFileWriter::kMinWriteSize = 64 * 1024; + +/** \class ThreadedFileWriter + * \brief This class supports the writing of recordings to disk. + * + * This class allows us manage the buffering when writing to + * disk. We write to the kernel image of the disk using one + * thread, and sync the kernel's image of the disk to hardware + * using another thread. The goal here so to block as little as + * possible when the classes using this class want to add data + * to the stream. + */ + +#define LOC QString("TFW(%1:%2): ").arg(filename).arg(fd) +#define LOC_WARN QString("TFW(%1:%2), Warning: ").arg(filename).arg(fd) +#define LOC_ERR QString("TFW(%1:%2), Error: ").arg(filename).arg(fd) + /** \fn ThreadedFileWriter::ThreadedFileWriter(const QString&,int,mode_t) * \brief Creates a threaded file writer. */ @@ -128,16 +69,10 @@ ThreadedFileWriter::ThreadedFileWriter(const QString &fname, // file stuff filename(fname), flags(pflags), mode(pmode), fd(-1), - m_file_sync(0), m_file_wpos(0), // state - no_writes(false), flush(false), - write_is_blocked(false), in_dtor(false), - ignore_writes(false), tfw_min_write_size(0), - // buffer position state - rpos(0), wpos(0), - written(0), - // buffer - buf(NULL), tfw_buf_size(0), + flush(false), in_dtor(false), + ignore_writes(false), tfw_min_write_size(kMinWriteSize), + totalBufferUse(0), // threads writeThread(NULL), syncThread(NULL) { @@ -168,17 +103,11 @@ bool ThreadedFileWriter::Open(void) } else { + VERBOSE(VB_FILE, LOC + "Open() successful"); + #ifdef USING_MINGW _setmode(fd, _O_BINARY); #endif - buf = new char[TFW_DEF_BUF_SIZE + 1024]; - memset(buf, 0, TFW_DEF_BUF_SIZE + 64); - - m_file_sync = m_file_wpos = 0; - - tfw_buf_size = TFW_DEF_BUF_SIZE; - tfw_min_write_size = TFW_MIN_WRITE_SIZE; - writeThread = new TFWWriteThread(this); writeThread->start(); syncThread = new TFWSyncThread(this); @@ -193,49 +122,51 @@ bool ThreadedFileWriter::Open(void) */ ThreadedFileWriter::~ThreadedFileWriter() { - no_writes = true; - - if (fd >= 0) - { - Flush(); - in_dtor = true; /* tells child threads to exit */ + Flush(); - buflock.lock(); + { /* tell child threads to exit */ + QMutexLocker locker(&buflock); + in_dtor = true; bufferSyncWait.wakeAll(); bufferHasData.wakeAll(); - buflock.unlock(); + } - syncThread->wait(); + if (writeThread) + { writeThread->wait(); + delete writeThread; + writeThread = NULL; + } - close(fd); - fd = -1; + while (!writeBuffers.empty()) + { + delete writeBuffers.front(); + writeBuffers.pop_front(); } - if (buf) + while (!emptyBuffers.empty()) { - delete [] buf; - buf = NULL; + delete emptyBuffers.front(); + emptyBuffers.pop_front(); } if (syncThread) { + syncThread->wait(); delete syncThread; syncThread = NULL; } - if (writeThread) + if (fd >= 0) { - delete writeThread; - writeThread = NULL; + close(fd); + fd = -1; } } /** \fn ThreadedFileWriter::Write(const void*, uint) * \brief Writes data to the end of the write buffer * - * NOTE: This blocks while buffer is in use by the write to disk thread. - * * \param data pointer to data to write to disk * \param count size of data in bytes */ @@ -244,83 +175,57 @@ uint ThreadedFileWriter::Write(const void *data, uint count) if (count == 0) return 0; - if (count > tfw_buf_size) - { - VERBOSE(VB_IMPORTANT, LOC + - QString("WARNING: count(%1), tfw_buf_size(%2)") - .arg(count).arg(tfw_buf_size)); - } + QMutexLocker locker(&buflock); - uint iobound_cnt = 0; - uint remaining = count; - char *wdata = (char *)data; + if (ignore_writes) + return count; - while (remaining) + if (totalBufferUse + count > kMaxBufferSize) { - bool first = true; - - buflock.lock(); - while (BufFreePriv() == 0) - { - if (first) - { - ++iobound_cnt; - VERBOSE(VB_IMPORTANT, LOC_ERR + "Write() -- IOBOUND begin " + - QString("remaining(%1) free(%2) size(%3) cnt(%4)") - .arg(remaining).arg(BufFreePriv()) - .arg(tfw_buf_size).arg(iobound_cnt)); - first = false; - } - - bufferWroteData.wait(&buflock, 1000); - } - uint twpos = wpos; - uint bytes = (BufFreePriv() < remaining) ? BufFreePriv() : remaining; - buflock.unlock(); - - if (!first) - VERBOSE(VB_IMPORTANT, LOC_ERR + "Write() -- IOBOUND end"); + VERBOSE(VB_IMPORTANT, LOC_ERR + "Maximum buffer size exceeded." + "\n\t\t\tfile will be truncated, no further writing " + "will be done." + "\n\t\t\tThis generally indicates your disk performance " + "\n\t\t\tis insufficient to deal with the number of on-going " + "\n\t\t\trecordings, or you have a disk failure."); + ignore_writes = true; + return count; + } - if (no_writes) - return 0; + TFWBuffer *buf = NULL; - if ((twpos + bytes) > tfw_buf_size) + if (!writeBuffers.empty() && + (writeBuffers.back()->data.size() + count) < kMinWriteSize) + { + buf = writeBuffers.back(); + writeBuffers.pop_back(); + } + else + { + if (!emptyBuffers.empty()) { - int first_chunk_size = tfw_buf_size - twpos; - int second_chunk_size = bytes - first_chunk_size; - memcpy(buf + twpos, wdata, first_chunk_size); - memcpy(buf, wdata + first_chunk_size, - second_chunk_size); + buf = emptyBuffers.front(); + emptyBuffers.pop_front(); + buf->data.clear(); } else { - memcpy(buf + twpos, wdata, bytes); + buf = new TFWBuffer(); } + } - buflock.lock(); - if (twpos == wpos) - { - wpos = (wpos + bytes) % tfw_buf_size; - } - else - { - VERBOSE(VB_IMPORTANT, LOC_ERR + "Programmer Error detected! " - "wpos was changed from under the Write() function."); - } - bufferHasData.wakeAll(); - buflock.unlock(); + totalBufferUse += count; + const char *cdata = (const char*) data; + buf->data.insert(buf->data.end(), cdata, cdata+count); + buf->lastUsed = QDateTime::currentDateTime(); - remaining -= bytes; - wdata += bytes; + writeBuffers.push_back(buf); - if (remaining) - { - buflock.lock(); - if (0 == BufFreePriv()) - bufferWroteData.wait(&buflock, 10000); - buflock.unlock(); - } - } + bufferHasData.wakeAll(); + + VERBOSE(VB_FILE|VB_EXTRA, + LOC + QString("Write(*, %1) total %2 cnt %3") + .arg(count,4).arg(totalBufferUse).arg(writeBuffers.size())); return count; } @@ -338,8 +243,19 @@ uint ThreadedFileWriter::Write(const void *data, uint count) */ long long ThreadedFileWriter::Seek(long long pos, int whence) { - Flush(); - + QMutexLocker locker(&buflock); + flush = true; + while (!writeBuffers.empty()) + { + bufferHasData.wakeAll(); + if (!bufferEmpty.wait(locker.mutex(), 2000)) + { + VERBOSE(VB_IMPORTANT, LOC + + QString("Taking a long time to flush.. buffer size %1") + .arg(totalBufferUse)); + } + } + flush = false; return lseek(fd, pos, whence); } @@ -350,10 +266,15 @@ void ThreadedFileWriter::Flush(void) { QMutexLocker locker(&buflock); flush = true; - while (BufUsedPriv() > 0) + while (!writeBuffers.empty()) { + bufferHasData.wakeAll(); if (!bufferEmpty.wait(locker.mutex(), 2000)) - VERBOSE(VB_IMPORTANT, LOC + "Taking a long time to flush.."); + { + VERBOSE(VB_IMPORTANT, LOC + + QString("Taking a long time to flush.. buffer size %1") + .arg(totalBufferUse)); + } } flush = false; } @@ -393,35 +314,16 @@ void ThreadedFileWriter::Sync(void) } } -/** \fn ThreadedFileWriter::SetWriteBufferSize(uint) - * \brief Sets the total size of the write buffer. - * WARNING: This is not safe when another thread is writing to the buffer. - */ -void ThreadedFileWriter::SetWriteBufferSize(uint newSize) -{ - if (newSize <= 0) - return; - - Flush(); - - QMutexLocker locker(&buflock); - delete [] buf; - rpos = wpos = 0; - buf = new char[newSize + 1024]; - memset(buf, 0, newSize + 64); - tfw_buf_size = newSize; -} - /** \fn ThreadedFileWriter::SetWriteBufferMinWriteSize(uint) * \brief Sets the minumum number of bytes to write to disk in a single write. * This is ignored during a Flush(void) */ void ThreadedFileWriter::SetWriteBufferMinWriteSize(uint newMinSize) { - if (newMinSize <= 0) - return; - - tfw_min_write_size = newMinSize; + QMutexLocker locker(&buflock); + if (newMinSize > 0) + tfw_min_write_size = newMinSize; + bufferHasData.wakeAll(); } /** \fn ThreadedFileWriter::SyncLoop(void) @@ -429,71 +331,139 @@ void ThreadedFileWriter::SetWriteBufferMinWriteSize(uint newMinSize) */ void ThreadedFileWriter::SyncLoop(void) { + QMutexLocker locker(&buflock); while (!in_dtor) { + locker.unlock(); + Sync(); - buflock.lock(); - int mstimeout = (written > tfw_min_write_size) ? 1000 : 100; - bufferSyncWait.wait(&buflock, mstimeout); - buflock.unlock(); + locker.relock(); + bufferSyncWait.wait(&buflock, 1000); } } /** \fn ThreadedFileWriter::DiskLoop(void) - * \brief The thread run method that actually calls safe_write(). + * \brief The thread run method that actually calls writes to disk. */ void ThreadedFileWriter::DiskLoop(void) { - uint size = 0; - written = 0; + QMutexLocker locker(&buflock); + + // Even if the bytes buffered is less than the minimum write + // size we do want to write to the OS buffers periodically. + // This timer makes sure we do. + MythTimer minWriteTimer; + minWriteTimer.start(); - while (!in_dtor || BufUsed() > 0) + while (!in_dtor) { - buflock.lock(); - size = BufUsedPriv(); + if (ignore_writes) + { + while (!writeBuffers.empty()) + { + delete writeBuffers.front(); + writeBuffers.pop_front(); + } + while (!emptyBuffers.empty()) + { + delete emptyBuffers.front(); + emptyBuffers.pop_front(); + } + bufferEmpty.wakeAll(); + bufferHasData.wait(locker.mutex()); + continue; + } - if (size == 0) + if (writeBuffers.empty()) { - buflock.unlock(); bufferEmpty.wakeAll(); - buflock.lock(); + bufferHasData.wait(locker.mutex(), 1000); + TrimEmptyBuffers(); + continue; } - if (!size || (!in_dtor && !flush && - ((size < tfw_min_write_size) && - (written >= tfw_min_write_size)))) + int mwte = minWriteTimer.elapsed(); + if (!flush && (mwte < 250) && (totalBufferUse < kMinWriteSize)) { - bufferHasData.wait(&buflock, 100); - buflock.unlock(); + bufferHasData.wait(locker.mutex(), 250 - mwte); + TrimEmptyBuffers(); continue; } - uint trpos = rpos; - buflock.unlock(); - /* cap the max. write size. Prevents the situation where 90% of the - buffer is valid, and we try to write all of it at once which - takes a long time. During this time, the other thread fills up - the 10% that was free... */ - size = (size > TFW_MAX_WRITE_SIZE) ? TFW_MAX_WRITE_SIZE : size; + TFWBuffer *buf = writeBuffers.front(); + writeBuffers.pop_front(); + totalBufferUse -= buf->data.size(); + minWriteTimer.start(); - bool write_ok; - if (ignore_writes) - ; - else if ((trpos + size) > tfw_buf_size) + ////////////////////////////////////////// + + const void *data = &(buf->data[0]); + uint sz = buf->data.size(); + + bool write_ok = true; + uint tot = 0; + uint errcnt = 0; + + VERBOSE(VB_FILE|VB_EXTRA, LOC + QString("write(%1) cnt %2 total %3") + .arg(sz).arg(writeBuffers.size()) + .arg(totalBufferUse)); + + MythTimer writeTimer; + writeTimer.start(); + + while ((tot < sz) && !in_dtor) { - int first_chunk_size = tfw_buf_size - trpos; - int second_chunk_size = size - first_chunk_size; - size = safe_write(fd, buf + trpos, first_chunk_size, write_ok); - if ((int)size == first_chunk_size && write_ok) - size += safe_write(fd, buf, second_chunk_size, write_ok); + locker.unlock(); + + int ret = write(fd, (char *)data + tot, sz - tot); + + if (ret < 0) + { + if (EAGAIN == errno) + { + VERBOSE(VB_IMPORTANT, LOC + "Got EAGAIN."); + } + else + { + errcnt++; + VERBOSE(VB_IMPORTANT, LOC_ERR + "File I/O " + + QString(" errcnt: %1").arg(errcnt) + ENO); + } + + if ((errcnt >= 3) || (ENOSPC == errno) || (EFBIG == errno)) + { + locker.relock(); + write_ok = false; + break; + } + } + else + { + tot += ret; + } + + locker.relock(); + + if (!in_dtor) + bufferHasData.wait(locker.mutex(), 50); } - else + + ////////////////////////////////////////// + + buf->lastUsed = QDateTime::currentDateTime(); + emptyBuffers.push_back(buf); + + if (writeTimer.elapsed() > 1000) { - size = safe_write(fd, buf + trpos, size, write_ok); + VERBOSE(VB_IMPORTANT, LOC_WARN + + QString("write(%1) cnt %2 total %3 -- " + "took a long time, %4 ms") + .arg(sz).arg(writeBuffers.size()) + .arg(totalBufferUse).arg(writeTimer.elapsed())); } - if (!ignore_writes && !write_ok && ((EFBIG == errno) || (ENOSPC == errno))) + if (!write_ok && ((EFBIG == errno) || (ENOSPC == errno))) { QString msg; switch (errno) @@ -504,7 +474,8 @@ void ThreadedFileWriter::DiskLoop(void) "\n\t\t\t" "You must either change the process ulimits, configure" "\n\t\t\t" - "your operating system with \"Large File\" support, or use" + "your operating system with \"Large File\" support, " + "or use" "\n\t\t\t" "a filesystem which supports 64-bit or 128-bit files." "\n\t\t\t" @@ -514,67 +485,33 @@ void ThreadedFileWriter::DiskLoop(void) msg = "No space left on the device for file '%1'" "\n\t\t\t" - "file will be truncated, no further writing will be done."; + "file will be truncated, no further writing " + "will be done."; break; } - VERBOSE(VB_IMPORTANT, msg.arg(filename)); + VERBOSE(VB_IMPORTANT, LOC_ERR + msg.arg(filename)); ignore_writes = true; } - - if (written <= tfw_min_write_size) - { - written += size; - } - - buflock.lock(); - if (trpos == rpos) - { - rpos = (rpos + size) % tfw_buf_size; - } - else - { - VERBOSE(VB_IMPORTANT, LOC_ERR + "Programmer Error detected! " - "rpos was changed from under the DiskLoop() function."); - } - m_file_wpos += size; - buflock.unlock(); - - bufferWroteData.wakeAll(); } } -/** \fn ThreadedFileWriter::BufUsedPriv(void) const - * \brief Number of bytes queued for write by the write thread. - */ -uint ThreadedFileWriter::BufUsedPriv(void) const -{ - return (wpos >= rpos) ? wpos - rpos : tfw_buf_size - rpos + wpos; -} - -/** \fn ThreadedFileWriter::BufFreePriv(void) const - * \brief Number of bytes that can be written without blocking. - */ -uint ThreadedFileWriter::BufFreePriv(void) const -{ - return ((wpos >= rpos) ? (rpos + tfw_buf_size) : rpos) - wpos - 1; -} - -/** \fn ThreadedFileWriter::BufUsed(void) const - * \brief Number of bytes queued for write by the write thread. With locking. - */ -uint ThreadedFileWriter::BufUsed(void) const +void ThreadedFileWriter::TrimEmptyBuffers(void) { - QMutexLocker locker(&buflock); - return (wpos >= rpos) ? wpos - rpos : tfw_buf_size - rpos + wpos; -} + QDateTime cur = QDateTime::currentDateTime(); + QDateTime cur_m_60 = cur.addSecs(-60); -/** - * \brief Number of bytes that can be written without blocking. With locking. - */ -uint ThreadedFileWriter::BufFree(void) const -{ - QMutexLocker locker(&buflock); - return ((wpos >= rpos) ? (rpos + tfw_buf_size) : rpos) - wpos - 1; + QList::iterator it = emptyBuffers.begin(); + while (it != emptyBuffers.end()) + { + if (((*it)->lastUsed < cur_m_60) || + ((*it)->data.capacity() > 3 * (*it)->data.size() && + (*it)->data.capacity() > 64 * 1024)) + { + delete *it; + it = emptyBuffers.erase(it); + continue; + } + ++it; + } } - diff --git a/mythtv/libs/libmythtv/ThreadedFileWriter.h b/mythtv/libs/libmythtv/ThreadedFileWriter.h index b4b3bb0780d..260a5a36561 100644 --- a/mythtv/libs/libmythtv/ThreadedFileWriter.h +++ b/mythtv/libs/libmythtv/ThreadedFileWriter.h @@ -2,7 +2,11 @@ #ifndef TFW_H_ #define TFW_H_ +#include +using namespace std; + #include +#include #include #include #include @@ -47,11 +51,7 @@ class ThreadedFileWriter long long Seek(long long pos, int whence); uint Write(const void *data, uint count); - void SetWriteBufferSize(uint newSize = TFW_DEF_BUF_SIZE); - void SetWriteBufferMinWriteSize(uint newMinSize = TFW_MIN_WRITE_SIZE); - - uint BufUsed(void) const; - uint BufFree(void) const; + void SetWriteBufferMinWriteSize(uint newMinSize = kMinWriteSize); void Sync(void); void Flush(void); @@ -59,9 +59,7 @@ class ThreadedFileWriter protected: void DiskLoop(void); void SyncLoop(void); - - uint BufUsedPriv(void) const; - uint BufFreePriv(void) const; + void TrimEmptyBuffers(void); private: // file info @@ -69,26 +67,24 @@ class ThreadedFileWriter int flags; mode_t mode; int fd; - uint64_t m_file_sync; ///< offset synced to disk - uint64_t m_file_wpos; ///< offset written to disk // state - volatile bool no_writes; - bool flush; - bool write_is_blocked; - volatile bool in_dtor; - bool ignore_writes; - long long tfw_min_write_size; - - // buffer position state - volatile uint rpos; ///< points to end of data written to disk - volatile uint wpos; ///< points to end of data added to buffer - mutable QMutex buflock; ///< lock needed to update rpos and wpos - long long written; - - // buffer - char *buf; - unsigned long tfw_buf_size; + bool flush; // protected by buflock + bool in_dtor; // protected by buflock + bool ignore_writes; // protected by buflock + uint tfw_min_write_size; // protected by buflock + uint totalBufferUse; // protected by buflock + + // buffers + class TFWBuffer + { + public: + vector data; + QDateTime lastUsed; + }; + mutable QMutex buflock; + QList writeBuffers; // protected by buflock + QList emptyBuffers; // protected by buflock // threads TFWWriteThread *writeThread; @@ -98,16 +94,11 @@ class ThreadedFileWriter QWaitCondition bufferEmpty; QWaitCondition bufferHasData; QWaitCondition bufferSyncWait; - QWaitCondition bufferWroteData; - private: // constants - /// Default buffer size. - static const uint TFW_DEF_BUF_SIZE; - /// Maximum to write to disk in a single write. - static const uint TFW_MAX_WRITE_SIZE; + static const uint kMaxBufferSize; /// Minimum to write to disk in a single write, when not flushing buffer. - static const uint TFW_MIN_WRITE_SIZE; + static const uint kMinWriteSize; }; #endif diff --git a/mythtv/libs/libmythtv/analogsignalmonitor.cpp b/mythtv/libs/libmythtv/analogsignalmonitor.cpp index c7c36bfa0d4..f254bed0eaf 100644 --- a/mythtv/libs/libmythtv/analogsignalmonitor.cpp +++ b/mythtv/libs/libmythtv/analogsignalmonitor.cpp @@ -23,8 +23,13 @@ AnalogSignalMonitor::AnalogSignalMonitor( int videofd = channel->GetFd(); if (videofd >= 0) { - m_usingv4l2 = CardUtil::hasV4L2(videofd); - CardUtil::GetV4LInfo(videofd, m_card, m_driver, m_version); + uint32_t caps; + if (!CardUtil::GetV4LInfo(videofd, m_card, m_driver, m_version, caps)) + { + videofd = -1; + return; + } + m_usingv4l2 = !!(caps & V4L2_CAP_VIDEO_CAPTURE); VERBOSE(VB_RECORD, LOC + QString("card '%1' driver '%2' version '%3'") .arg(m_card).arg(m_driver).arg(m_version)); } @@ -118,16 +123,21 @@ bool AnalogSignalMonitor::handleHDPVR(int videofd) void AnalogSignalMonitor::UpdateValues(void) { - if (!monitor_thread.isRunning() || exit) + SignalMonitor::UpdateValues(); + + { + QMutexLocker locker(&statusLock); + if (!scriptStatus.IsGood()) + return; + } + + if (!running || exit) return; int videofd = channel->GetFd(); if (videofd < 0) return; - if (!IsChannelTuned()) - return; - bool isLocked = false; if (m_usingv4l2) { diff --git a/mythtv/libs/libmythtv/asichannel.cpp b/mythtv/libs/libmythtv/asichannel.cpp new file mode 100644 index 00000000000..de38a36997f --- /dev/null +++ b/mythtv/libs/libmythtv/asichannel.cpp @@ -0,0 +1,50 @@ +/** -*- Mode: c++ -*- + * Class ASIChannel + */ + +// MythTV includes +#include "mythverbose.h" +#include "mpegtables.h" +#include "asichannel.h" + +#define LOC QString("ASIChan(%1): ").arg(GetDevice()) +#define LOC_ERR QString("ASIChan(%1), Error: ").arg(GetDevice()) + +ASIChannel::ASIChannel(TVRec *parent, const QString &device) : + DTVChannel(parent), m_device(device), m_isopen(false) +{ + m_tuner_types.push_back(DTVTunerType::kTunerTypeASI); +} + +ASIChannel::~ASIChannel(void) +{ + if (IsOpen()) + Close(); +} + +bool ASIChannel::Open(void) +{ + VERBOSE(VB_CHANNEL, LOC + "Open()"); + + if (m_device.isEmpty()) + return false; + + if (m_isopen) + return true; + + if (!InitializeInputs()) + return false; + + if (m_inputs.find(m_currentInputID) == m_inputs.end()) + return false; + + m_isopen = true; + + return true; +} + +void ASIChannel::Close() +{ + VERBOSE(VB_CHANNEL, LOC + "Close()"); + m_isopen = false; +} diff --git a/mythtv/libs/libmythtv/asichannel.h b/mythtv/libs/libmythtv/asichannel.h new file mode 100644 index 00000000000..1255f5a23b8 --- /dev/null +++ b/mythtv/libs/libmythtv/asichannel.h @@ -0,0 +1,44 @@ +/// -*- Mode: c++ -*- + +#ifndef _ASI_CHANNEL_H_ +#define _ASI_CHANNEL_H_ + +#include +using namespace std; + +// Qt headers +#include + +// MythTV headers +#include "dtvchannel.h" + +class ASIStreamHandler; +class ASIChannel; + +class ASIChannel : public DTVChannel +{ + public: + ASIChannel(TVRec *parent, const QString &device); + ~ASIChannel(void); + + // Commands + virtual bool Open(void); + virtual void Close(void); + virtual bool Tune(const DTVMultiplex&, QString) { return true; } + virtual bool Tune(const QString&, int) { return true; } + virtual bool Tune(uint64_t, QString) { return true; } + + // Gets + virtual bool IsOpen(void) const { return m_isopen; } + virtual QString GetDevice(void) const { return m_device; } + virtual vector GetTunerTypes(void) const + { return m_tuner_types; } + virtual bool IsPIDTuningSupported(void) const { return true; } + + private: + vector m_tuner_types; + QString m_device; + bool m_isopen; +}; + +#endif // _ASI_CHANNEL_H_ diff --git a/mythtv/libs/libmythtv/asirecorder.cpp b/mythtv/libs/libmythtv/asirecorder.cpp new file mode 100644 index 00000000000..065e0a50fcc --- /dev/null +++ b/mythtv/libs/libmythtv/asirecorder.cpp @@ -0,0 +1,194 @@ +/* -*- Mode: c++ -*- + * Class ASIRecorder + * + * Copyright (C) Daniel Kristjansson 2010 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +// Qt includes +#include + +// MythTV includes +#include "asistreamhandler.h" +#include "asirecorder.h" +#include "asichannel.h" +#include "ringbuffer.h" +#include "tv_rec.h" + +#define LOC QString("ASIRec(%1): ").arg(tvrec->GetCaptureCardNum()) +#define LOC_WARN QString("ASIRec(%1), Warning: ") \ + .arg(tvrec->GetCaptureCardNum()) +#define LOC_ERR QString("ASIRec(%1), Error: ") \ + .arg(tvrec->GetCaptureCardNum()) + +ASIRecorder::ASIRecorder(TVRec *rec, ASIChannel *channel) : + DTVRecorder(rec), m_channel(channel), m_stream_handler(NULL) +{ + SetStreamData(new MPEGStreamData(-1,false)); + if (channel->GetProgramNumber() < 0 || !channel->GetMinorChannel()) + _stream_data->SetListeningDisabled(true); +} + +void ASIRecorder::SetOptionsFromProfile(RecordingProfile *profile, + const QString &videodev, + const QString &audiodev, + const QString &vbidev) +{ + // We don't want to call DTVRecorder::SetOptionsFromProfile() since + // we do not have a "recordingtype" in our profile. + DTVRecorder::SetOption("videodevice", videodev); + DTVRecorder::SetOption("tvformat", gCoreContext->GetSetting("TVFormat")); + SetIntOption(profile, "recordmpts"); +} + +/** \fn ASIRecorder::SetOption(const QString&,int) + * \brief handles the "recordmpts" option. + */ +void ASIRecorder::SetOption(const QString &name, int value) +{ + if (name == "recordmpts") + m_record_mpts = (value == 1); + else + DTVRecorder::SetOption(name, value); +} + +void ASIRecorder::StartRecording(void) +{ + if (!Open()) + { + _error = "Failed to open device"; + VERBOSE(VB_IMPORTANT, LOC_ERR + _error); + return; + } + + if (!_stream_data) + { + _error = "MPEGStreamData pointer has not been set"; + VERBOSE(VB_IMPORTANT, LOC_ERR + _error); + Close(); + return; + } + + _continuity_error_count = 0; + + { + QMutexLocker locker(&pauseLock); + request_recording = true; + recording = true; + recordingWait.wakeAll(); + } + + if (m_channel->HasGeneratedPAT()) + { + const ProgramAssociationTable *pat = m_channel->GetGeneratedPAT(); + const ProgramMapTable *pmt = m_channel->GetGeneratedPMT(); + _stream_data->Reset(pat->ProgramNumber(0)); + _stream_data->HandleTables(MPEG_PAT_PID, *pat); + _stream_data->HandleTables(pat->ProgramPID(0), *pmt); + } + + // Listen for time table on DVB standard streams + if (m_channel && (m_channel->GetSIStandard() == "dvb")) + _stream_data->AddListeningPID(DVB_TDT_PID); + + // Make sure the first things in the file are a PAT & PMT + bool tmp = _wait_for_keyframe_option; + _wait_for_keyframe_option = false; + HandleSingleProgramPAT(_stream_data->PATSingleProgram()); + HandleSingleProgramPMT(_stream_data->PMTSingleProgram()); + _wait_for_keyframe_option = tmp; + + _stream_data->AddAVListener(this); + _stream_data->AddWritingListener(this); + m_stream_handler->AddListener( + _stream_data, false, true, + (m_record_mpts) ? ringBuffer->GetFilename() : QString()); + + while (IsRecordingRequested() && !IsErrored()) + { + if (PauseAndWait()) + continue; + + { // sleep 100 milliseconds unless StopRecording() or Unpause() + // is called, just to avoid running this too often. + QMutexLocker locker(&pauseLock); + if (!request_recording || request_pause) + continue; + unpauseWait.wait(&pauseLock, 100); + } + + if (!_input_pmt) + { + VERBOSE(VB_GENERAL, LOC_WARN + + "Recording will not commence until a PMT is set."); + usleep(5000); + continue; + } + + if (!m_stream_handler->IsRunning()) + { + _error = "Stream handler died unexpectedly."; + VERBOSE(VB_IMPORTANT, LOC_ERR + _error); + } + } + + m_stream_handler->RemoveListener(_stream_data); + _stream_data->RemoveWritingListener(this); + _stream_data->RemoveAVListener(this); + + Close(); + + FinishRecording(); + + QMutexLocker locker(&pauseLock); + recording = false; + recordingWait.wakeAll(); +} + +bool ASIRecorder::Open(void) +{ + if (IsOpen()) + { + VERBOSE(VB_GENERAL, LOC_WARN + "Card already open"); + return true; + } + + memset(_stream_id, 0, sizeof(_stream_id)); + memset(_pid_status, 0, sizeof(_pid_status)); + memset(_continuity_counter, 0xff, sizeof(_continuity_counter)); + _continuity_error_count = 0; + + m_stream_handler = ASIStreamHandler::Get(m_channel->GetDevice()); + + VERBOSE(VB_RECORD, LOC + "Opened successfully"); + + return true; +} + +bool ASIRecorder::IsOpen(void) const +{ + return m_stream_handler; +} + +void ASIRecorder::Close(void) +{ + VERBOSE(VB_RECORD, LOC + "Close() -- begin"); + + if (IsOpen()) + ASIStreamHandler::Return(m_stream_handler); + + VERBOSE(VB_RECORD, LOC + "Close() -- end"); +} diff --git a/mythtv/libs/libmythtv/asirecorder.h b/mythtv/libs/libmythtv/asirecorder.h new file mode 100644 index 00000000000..73437ec59ff --- /dev/null +++ b/mythtv/libs/libmythtv/asirecorder.h @@ -0,0 +1,78 @@ +// -*- Mode: c++ -*- +/* + * Copyright (C) Daniel Kristjansson 2010 + * + * Copyright notice is in asirecorder.cpp of the MythTV project. + */ + +#ifndef _ASI_RECORDER_H_ +#define _ASI_RECORDER_H_ + +// MythTV includes +#include "dtvrecorder.h" + +class ASIStreamHandler; +class RecordingProfile; +class ASIChannel; +class QString; +class TVRec; + +/** \class ASIRecorder + * \brief This is a specialization of DTVRecorder used to + * handle streams from ASI drivers. + * + * This has been written and tested with the DVEO ASI hardware and drivers. + * Those particular drivers do not come with udev rules so you will need + * to create the following rule in /etc/udev/rules.d/99-asi.rules +\verbatim +SUBSYSTEM=="asi", OWNER="mythtv", GROUP="video", MODE="0660", RUN+="/etc/udev/asi.sh" OPTIONS+="last_rule" +\endverbatim +Also, create a /etc/udev/asi.sh file with the following contents: +\htmlonly +
+#!/bin/sh
+
+for VAL in buffers bufsize dev granularity mode null_packets timestamps transport uevent clock_source; do
+  chown mythtv /sys/devices/*/*/*/dvbm/*/asi*/$VAL ;
+  chgrp video /sys/devices/*/*/*/dvbm/*/asi*/$VAL ;
+  chmod g+rw /sys/devices/*/*/*/dvbm/*/asi*/$VAL ;
+  chmod g-x /sys/devices/*/*/*/dvbm/*/asi*/$VAL ;
+  ls -l /sys/devices/*/*/*/dvbm/*/asi*/$VAL ;
+done
+
+\endhtmlonly + * + * Be sure to mark asi.sh as executable with "chmod a+x /etc/udev/asi.sh" + * udev will silently ignore it if it is not marked. + * + * This of course assumes you want MythTV to have access to all asi + * device, if this is not the case, you will need to write a more + * specific rule and modify the asi.sh to only grant permission + * on a specific asi revice + * + * \sa DTVRecorder + */ +class ASIRecorder : public DTVRecorder +{ + public: + ASIRecorder(TVRec *rec, ASIChannel *channel); + + void SetOptionsFromProfile(RecordingProfile *profile, + const QString &videodev, + const QString &audiodev, + const QString &vbidev); + void SetOption(const QString &name, int value); + + void StartRecording(void); + + bool Open(void); + bool IsOpen(void) const; + void Close(void); + + private: + ASIChannel *m_channel; + ASIStreamHandler *m_stream_handler; + bool m_record_mpts; +}; + +#endif // _ASI_RECORDER_H_ diff --git a/mythtv/libs/libmythtv/asisignalmonitor.cpp b/mythtv/libs/libmythtv/asisignalmonitor.cpp new file mode 100644 index 00000000000..1de2e8a9c03 --- /dev/null +++ b/mythtv/libs/libmythtv/asisignalmonitor.cpp @@ -0,0 +1,126 @@ +// -*- Mode: c++ -*- +// Copyright (c) 2006, Daniel Thor Kristjansson + +#include +#include + +#include +#include +#ifndef USING_MINGW +#include +#endif + +#include "mythverbose.h" +#include "mythdbcon.h" +#include "asisignalmonitor.h" +#include "atscstreamdata.h" +#include "mpegtables.h" +#include "atsctables.h" + +#include "asichannel.h" +#include "asirecorder.h" +#include "asistreamhandler.h" + +#define LOC QString("ASISM(%1): ").arg(channel->GetDevice()) +#define LOC_ERR QString("ASISM(%1), Error: ").arg(channel->GetDevice()) + +/** + * \brief Initializes signal lock and signal values. + * + * Start() must be called to actually begin continuous + * signal monitoring. The timeout is set to 3 seconds, + * and the signal threshold is initialized to 0%. + * + * \param db_cardnum Recorder number to monitor, + * if this is less than 0, SIGNAL events will not be + * sent to the frontend even if SetNotifyFrontend(true) + * is called. + * \param _channel ASIChannel for card + * \param _flags Flags to start with + */ +ASISignalMonitor::ASISignalMonitor( + int db_cardnum, ASIChannel *_channel, uint64_t _flags) : + DTVSignalMonitor(db_cardnum, _channel, _flags), + streamHandlerStarted(false), streamHandler(NULL) +{ + VERBOSE(VB_CHANNEL, LOC + "ctor"); + streamHandler = ASIStreamHandler::Get(_channel->GetDevice()); +} + +/** \fn ASISignalMonitor::~ASISignalMonitor() + * \brief Stops signal monitoring and table monitoring threads. + */ +ASISignalMonitor::~ASISignalMonitor() +{ + VERBOSE(VB_CHANNEL, LOC + "dtor"); + Stop(); + ASIStreamHandler::Return(streamHandler); +} + +/** \fn ASISignalMonitor::Stop(void) + * \brief Stop signal monitoring and table monitoring threads. + */ +void ASISignalMonitor::Stop(void) +{ + VERBOSE(VB_CHANNEL, LOC + "Stop() -- begin"); + SignalMonitor::Stop(); + if (GetStreamData()) + streamHandler->RemoveListener(GetStreamData()); + streamHandlerStarted = false; + + VERBOSE(VB_CHANNEL, LOC + "Stop() -- end"); +} + +ASIChannel *ASISignalMonitor::GetASIChannel(void) +{ + return dynamic_cast(channel); +} + +/** \fn ASISignalMonitor::UpdateValues(void) + * \brief Fills in frontend stats and emits status Qt signals. + * + * This is automatically called by MonitorLoop(), after Start() + * has been used to start the signal monitoring thread. + */ +void ASISignalMonitor::UpdateValues(void) +{ + if (!running || exit) + return; + + if (streamHandlerStarted) + { + EmitStatus(); + if (IsAllGood()) + SendMessageAllGood(); + + // TODO dtv signals... + + update_done = true; + return; + } + + // Set SignalMonitorValues from info from card. + bool isLocked = true; + { + QMutexLocker locker(&statusLock); + signalStrength.SetValue(100); + signalLock.SetValue(1); + } + + EmitStatus(); + if (IsAllGood()) + SendMessageAllGood(); + + // Start table monitoring if we are waiting on any table + // and we have a lock. + if (isLocked && GetStreamData() && + HasAnyFlag(kDTVSigMon_WaitForPAT | kDTVSigMon_WaitForPMT | + kDTVSigMon_WaitForMGT | kDTVSigMon_WaitForVCT | + kDTVSigMon_WaitForNIT | kDTVSigMon_WaitForSDT)) + { + streamHandler->AddListener(GetStreamData()); + streamHandlerStarted = true; + } + + update_done = true; +} diff --git a/mythtv/libs/libmythtv/asisignalmonitor.h b/mythtv/libs/libmythtv/asisignalmonitor.h new file mode 100644 index 00000000000..f51c95b98fe --- /dev/null +++ b/mythtv/libs/libmythtv/asisignalmonitor.h @@ -0,0 +1,36 @@ +// -*- Mode: c++ -*- + +#ifndef ASISIGNALMONITOR_H +#define ASISIGNALMONITOR_H + +#include + +#include "dtvsignalmonitor.h" + +class ASIChannel; +class ASIStreamHandler; + +typedef QMap FilterMap; + +class ASISignalMonitor: public DTVSignalMonitor +{ + public: + ASISignalMonitor(int db_cardnum, ASIChannel *_channel, + uint64_t _flags = 0); + virtual ~ASISignalMonitor(); + + void Stop(void); + + protected: + ASISignalMonitor(void); + ASISignalMonitor(const ASISignalMonitor&); + + virtual void UpdateValues(void); + ASIChannel *GetASIChannel(void); + + protected: + bool streamHandlerStarted; + ASIStreamHandler *streamHandler; +}; + +#endif // ASISIGNALMONITOR_H diff --git a/mythtv/libs/libmythtv/asistreamhandler.cpp b/mythtv/libs/libmythtv/asistreamhandler.cpp new file mode 100644 index 00000000000..563b5938eb6 --- /dev/null +++ b/mythtv/libs/libmythtv/asistreamhandler.cpp @@ -0,0 +1,408 @@ +// -*- Mode: c++ -*- + +// POSIX headers +#include +#include +#ifndef USING_MINGW +#include +#include +#endif + +// Qt headers +#include +#include + +// MythTV headers +#include "asistreamhandler.h" +#include "asichannel.h" +#include "ThreadedFileWriter.h" +#include "dtvsignalmonitor.h" +#include "streamlisteners.h" +#include "mpegstreamdata.h" +#include "cardutil.h" + +// DVEO ASI headers +#include +#include + +#define LOC QString("ASISH(%1): ").arg(_device) +#define LOC_WARN QString("ASISH(%1) Warning: ").arg(_device) +#define LOC_ERR QString("ASISH(%1) Error: ").arg(_device) + +QMap ASIStreamHandler::_handlers; +QMap ASIStreamHandler::_handlers_refcnt; +QMutex ASIStreamHandler::_handlers_lock; + +ASIStreamHandler *ASIStreamHandler::Get(const QString &devname) +{ + QMutexLocker locker(&_handlers_lock); + + QString devkey = devname; + + QMap::iterator it = _handlers.find(devkey); + + if (it == _handlers.end()) + { + ASIStreamHandler *newhandler = new ASIStreamHandler(devname); + newhandler->Open(); + _handlers[devkey] = newhandler; + _handlers_refcnt[devkey] = 1; + + VERBOSE(VB_RECORD, + QString("ASISH: Creating new stream handler %1 for %2") + .arg(devkey).arg(devname)); + } + else + { + _handlers_refcnt[devkey]++; + uint rcount = _handlers_refcnt[devkey]; + VERBOSE(VB_RECORD, + QString("ASISH: Using existing stream handler %1 for %2") + .arg(devkey) + .arg(devname) + QString(" (%1 in use)").arg(rcount)); + } + + return _handlers[devkey]; +} + +void ASIStreamHandler::Return(ASIStreamHandler * & ref) +{ + QMutexLocker locker(&_handlers_lock); + + QString devname = ref->_device; + + QMap::iterator rit = _handlers_refcnt.find(devname); + if (rit == _handlers_refcnt.end()) + return; + + if (*rit > 1) + { + ref = NULL; + (*rit)--; + return; + } + + QMap::iterator it = _handlers.find(devname); + if ((it != _handlers.end()) && (*it == ref)) + { + VERBOSE(VB_RECORD, QString("ASISH: Closing handler for %1") + .arg(devname)); + ref->Close(); + delete *it; + _handlers.erase(it); + } + else + { + VERBOSE(VB_IMPORTANT, + QString("ASISH Error: Couldn't find handler for %1") + .arg(devname)); + } + + _handlers_refcnt.erase(rit); + ref = NULL; +} + +ASIStreamHandler::ASIStreamHandler(const QString &device) : + StreamHandler(device), + _device_num(-1), _buf_size(-1), _fd(-1), + _packet_size(TSPacket::kSize), _clock_source(kASIInternalClock), + _rx_mode(kASIRXSyncOnActualConvertTo188), _drb(NULL), _mpts(NULL) +{ +} + +void ASIStreamHandler::SetClockSource(ASIClockSource cs) +{ + _clock_source = cs; + // TODO we should make it possible to set this immediately + // not wait for the next open +} + +void ASIStreamHandler::SetRXMode(ASIRXMode m) +{ + _rx_mode = m; + // TODO we should make it possible to set this immediately + // not wait for the next open +} + +void ASIStreamHandler::SetRunningDesired(bool desired) +{ + if (_drb && _running_desired && !desired) + _drb->Stop(); + StreamHandler::SetRunningDesired(desired); +} + +void ASIStreamHandler::run(void) +{ + VERBOSE(VB_RECORD, LOC + "run(): begin"); + + if (!Open()) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + + QString("Failed to open device %1 : %2") + .arg(_device).arg(strerror(errno))); + _error = true; + return; + } + + DeviceReadBuffer *drb = new DeviceReadBuffer(this); + bool ok = drb->Setup(_device, _fd, _packet_size, _buf_size); + if (!ok) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to allocate DRB buffer"); + delete drb; + drb = NULL; + Close(); + _error = true; + return; + } + + uint buffer_size = _packet_size * 15000; + unsigned char *buffer = new unsigned char[buffer_size]; + if (!buffer) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to allocate buffer"); + delete drb; + drb = NULL; + Close(); + _error = true; + return; + } + memset(buffer, 0, buffer_size); + + SetRunning(true, true, false); + + drb->Start(); + + { + QMutexLocker locker(&_start_stop_lock); + _drb = drb; + } + + int remainder = 0; + while (_running_desired && !_error) + { + UpdateFiltersFromStreamData(); + + ssize_t len = 0; + + len = drb->Read( + &(buffer[remainder]), buffer_size - remainder); + + if (!_running_desired) + break; + + // Check for DRB errors + if (drb->IsErrored()) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "Device error detected"); + _error = true; + } + + if (drb->IsEOF()) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "Device EOF detected"); + _error = true; + } + + if ((0 == len) || (-1 == len)) + { + usleep(100); + continue; + } + + len += remainder; + + if (len < 10) // 10 bytes = 4 bytes TS header + 6 bytes PES header + { + remainder = len; + continue; + } + + if (!_listener_lock.tryLock()) + { + remainder = len; + continue; + } + + if (_stream_data_list.empty()) + { + _listener_lock.unlock(); + continue; + } + + StreamDataList::const_iterator sit = _stream_data_list.begin(); + for (; sit != _stream_data_list.end(); ++sit) + remainder = sit.key()->ProcessData(buffer, len); + + if (_mpts != NULL) + _mpts->Write(buffer, len - remainder); + + _listener_lock.unlock(); + + if (remainder > 0 && (len > remainder)) // leftover bytes + memmove(buffer, &(buffer[len - remainder]), remainder); + } + VERBOSE(VB_RECORD, LOC + "run(): " + "shutdown"); + + RemoveAllPIDFilters(); + + { + QMutexLocker locker(&_start_stop_lock); + _drb = NULL; + } + + if (drb->IsRunning()) + drb->Stop(); + + delete drb; + delete[] buffer; + Close(); + + VERBOSE(VB_RECORD, LOC + "run(): " + "end"); + + SetRunning(false, true, false); +} + +bool ASIStreamHandler::Open(void) +{ + if (_fd >= 0) + return true; + + QString error; + _device_num = CardUtil::GetASIDeviceNumber(_device, &error); + if (_device_num < 0) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + error); + return false; + } + + _buf_size = CardUtil::GetASIBufferSize(_device_num, &error); + if (_buf_size <= 0) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + error); + return false; + } + + if (!CardUtil::SetASIMode(_device_num, (uint)_rx_mode, &error)) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to set RX Mode: " + error); + return false; + } + + // actually open the device + _fd = open(_device.toLocal8Bit().constData(), O_RDONLY, 0); + if (_fd < 0) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + + QString("Failed to open '%1'").arg(_device) + ENO); + return false; + } + + // get the rx capabilities + unsigned int cap; + if (ioctl(_fd, ASI_IOC_RXGETCAP, &cap) < 0) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + + QString("Failed to query capabilities '%1'") + .arg(_device) + ENO); + Close(); + return false; + } + // TODO? do stuff with capabilities.. + + // we need to handle 188 & 204 byte packets.. + switch (_rx_mode) + { + case kASIRXRawMode: + case kASIRXSyncOnActualSize: + _packet_size = TSPacket::kDVBEmissionSize * TSPacket::kSize; + break; + case kASIRXSyncOn204: + _packet_size = TSPacket::kDVBEmissionSize; + break; + case kASIRXSyncOn188: + case kASIRXSyncOnActualConvertTo188: + case kASIRXSyncOn204ConvertTo188: + _packet_size = TSPacket::kSize; + break; + } + + // pid counter? + + return _fd >= 0; +} + +void ASIStreamHandler::Close(void) +{ + if (_fd >= 0) + { + close(_fd); + _fd = -1; + } +} + +typedef ThreadedFileWriter* ThreadedFileWriterP; +static bool named_output_file_common( + const QString &_device, ThreadedFileWriterP &tfw, QMap &files) +{ + if (tfw) + { + delete tfw; + tfw = NULL; + } + + QMap::iterator it = files.begin(); + if (it == files.end()) + return true; + + for (it = files.begin(); it != files.end(); ++it) + (*it)++; + + QString fn = QString("%1.%2.raw") + .arg(files.begin().key()).arg(*files.begin()); + + tfw = new ThreadedFileWriter( + fn, O_WRONLY|O_TRUNC|O_CREAT|O_LARGEFILE, 0644); + if (!tfw->Open()) + { + delete tfw; + tfw = NULL; + return false; + } + + bool ok = true; + const QByteArray ba = fn.toLocal8Bit(); + for (; ok && it != files.end(); ++it) + { + int ret = link(ba.constData(), it.key().toLocal8Bit().constData()); + if (ret < 0) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + + QString("Failed to link '%1' to '%2'") + .arg(it.key()).arg(fn) + ENO); + } + ok &= ret >= 0; + } + + return ok; +} +void ASIStreamHandler::AddNamedOutputFile(const QString &file) +{ + _mpts_files[file] = -1; + named_output_file_common(_device, _mpts, _mpts_files); +} + +void ASIStreamHandler::RemoveNamedOutputFile(const QString &file) +{ + QMap::iterator it = _mpts_files.find(file); + if (it != _mpts_files.end()) + { + _mpts_files.erase(it); + named_output_file_common(_device, _mpts, _mpts_files); + } +} + +void ASIStreamHandler::PriorityEvent(int fd) +{ + // TODO report on buffer overruns, etc. +} diff --git a/mythtv/libs/libmythtv/asistreamhandler.h b/mythtv/libs/libmythtv/asistreamhandler.h new file mode 100644 index 00000000000..c0bf1e21fa7 --- /dev/null +++ b/mythtv/libs/libmythtv/asistreamhandler.h @@ -0,0 +1,97 @@ +// -*- Mode: c++ -*- + +#ifndef _ASISTREAMHANDLER_H_ +#define _ASISTREAMHANDLER_H_ + +#include +using namespace std; + +#include +#include +#include + +#include "streamhandler.h" +#include "util.h" + +class ASIStreamHandler; +class DTVSignalMonitor; +class ASIChannel; +class DeviceReadBuffer; +class ThreadedFileWriter; + +typedef QMap FilterMap; + +typedef enum ASIClockSource +{ + kASIInternalClock = 0, + kASIExternalClock = 1, + kASIRecoveredReceiveClock = 2, + kASIExternalClock2 = 1, +} ASIClockSource; + +typedef enum ASIRXMode +{ + kASIRXRawMode = 0, + kASIRXSyncOn188 = 1, + kASIRXSyncOn204 = 2, + kASIRXSyncOnActualSize = 3, + kASIRXSyncOnActualConvertTo188 = 4, + kASIRXSyncOn204ConvertTo188 = 5, +} ASIRXMode; + + +//#define RETUNE_TIMEOUT 5000 + +// Note : This class always uses a DRB && a TS reader. + +class ASIStreamHandler : public StreamHandler +{ + public: + static ASIStreamHandler *Get(const QString &devicename); + static void Return(ASIStreamHandler * & ref); + + virtual void AddListener(MPEGStreamData *data, + bool allow_section_reader = false, + bool needs_drb = false, + QString output_file = QString()) + { + StreamHandler::AddListener(data, false, true, output_file); + } // StreamHandler + + void SetClockSource(ASIClockSource cs); + void SetRXMode(ASIRXMode m); + + private: + ASIStreamHandler(const QString &); + + bool Open(void); + void Close(void); + + virtual void run(void); // QThread + + virtual void PriorityEvent(int fd); // DeviceReaderCB + + virtual void AddNamedOutputFile(const QString &file); // StreamHandler + virtual void RemoveNamedOutputFile(const QString &file); // StreamHandler + + virtual void SetRunningDesired(bool desired); // StreamHandler + + private: + int _device_num; + int _buf_size; + int _fd; + uint _packet_size; + ASIClockSource _clock_source; + ASIRXMode _rx_mode; + DeviceReadBuffer *_drb; + + ThreadedFileWriter *_mpts; + QMap _mpts_files; + + // for implementing Get & Return + static QMutex _handlers_lock; + static QMap _handlers; + static QMap _handlers_refcnt; +}; + +#endif // _ASISTREAMHANDLER_H_ diff --git a/mythtv/libs/libmythtv/cardutil.cpp b/mythtv/libs/libmythtv/cardutil.cpp index 0c7eb04e848..a6ddc740193 100644 --- a/mythtv/libs/libmythtv/cardutil.cpp +++ b/mythtv/libs/libmythtv/cardutil.cpp @@ -35,10 +35,56 @@ #include "hdhomerun.h" #endif +#ifdef USING_ASI +#include +#include +#endif + #define LOC QString("CardUtil: ") #define LOC_WARN QString("CardUtil, Warning: ") #define LOC_ERR QString("CardUtil, Error: ") +QString CardUtil::GetScanableCardTypes(void) +{ + QString cardTypes = ""; + +#ifdef USING_DVB + cardTypes += "'DVB'"; +#endif // USING_DVB + +#ifdef USING_V4L + if (!cardTypes.isEmpty()) + cardTypes += ","; + cardTypes += "'V4L'"; +# ifdef USING_IVTV + cardTypes += ",'MPEG'"; +# endif // USING_IVTV +#endif // USING_V4L + +#ifdef USING_IPTV + if (!cardTypes.isEmpty()) + cardTypes += ","; + cardTypes += "'FREEBOX'"; +#endif // USING_IPTV + +#ifdef USING_HDHOMERUN + if (!cardTypes.isEmpty()) + cardTypes += ","; + cardTypes += "'HDHOMERUN'"; +#endif // USING_HDHOMERUN + +#ifdef USING_ASI + if (!cardTypes.isEmpty()) + cardTypes += ","; + cardTypes += "'ASI'"; +#endif + + if (cardTypes.isEmpty()) + cardTypes = "'DUMMY'"; + + return QString("(%1)").arg(cardTypes); +} + bool CardUtil::IsTunerShared(uint cardidA, uint cardidB) { VERBOSE(VB_IMPORTANT, QString("IsTunerShared(%1,%2)") @@ -215,6 +261,24 @@ QStringList CardUtil::ProbeVideoDevices(const QString &rawtype) devs.push_back(subit->filePath()); } } + else if (rawtype.toUpper() == "ASI") + { + QDir dir("/dev/", "asirx*", QDir::Name, QDir::System); + const QFileInfoList il = dir.entryInfoList(); + if (il.isEmpty()) + return devs; + + QFileInfoList::const_iterator it = il.begin(); + for (; it != il.end(); ++it) + { + if (GetASIDeviceNumber(it->filePath()) >= 0) + { + devs.push_back(it->filePath()); + continue; + } + break; + } + } #ifdef USING_HDHOMERUN else if (rawtype.toUpper() == "HDHOMERUN") { @@ -1485,10 +1549,12 @@ bool CardUtil::hasV4L2(int videofd) } bool CardUtil::GetV4LInfo( - int videofd, QString &card, QString &driver, uint32_t &version) + int videofd, QString &card, QString &driver, uint32_t &version, + uint32_t &capabilities) { card = driver = QString::null; version = 0; + capabilities = 0; if (videofd < 0) return false; @@ -1502,6 +1568,7 @@ bool CardUtil::GetV4LInfo( card = QString::fromAscii((const char*)capability.card); driver = QString::fromAscii((const char*)capability.driver); version = capability.version; + capabilities = capability.capabilities; } else // Fallback to V4L1 query { @@ -1548,7 +1615,7 @@ InputNames CardUtil::ProbeV4LVideoInputs(int videofd, bool &ok) if (ioctl(videofd, VIDIOCGCAP, &vidcap) != 0) { QString msg = QObject::tr("Could not query inputs."); - VERBOSE(VB_IMPORTANT, msg + ENO); + VERBOSE(VB_IMPORTANT, "ProbeV4LVideoInputs(): Error, " + msg + ENO); list[-1] = msg; vidcap.channels = 0; } @@ -1561,7 +1628,7 @@ InputNames CardUtil::ProbeV4LVideoInputs(int videofd, bool &ok) if (ioctl(videofd, VIDIOCGCHAN, &test) != 0) { - VERBOSE(VB_IMPORTANT, + VERBOSE(VB_IMPORTANT, "ProbeV4LVideoInputs(): Error, " + QString("Could determine name of input #%1" "\n\t\t\tNot adding it to the list.") .arg(test.channel) + ENO); @@ -2032,3 +2099,164 @@ QString CardUtil::GetHDHRdesc(const QString &device) return connectErr; #endif } + +#ifdef USING_ASI +static QString sys_dev(uint device_num, QString dev) +{ + return QString("/sys/class/asi/asirx%1/%2").arg(device_num).arg(dev); +} + +static QString read_sys(QString sys_dev) +{ + QFile f(sys_dev); + f.open(QIODevice::ReadOnly); + QByteArray sdba = f.readAll(); + f.close(); + return sdba; +} + +static bool write_sys(QString sys_dev, QString str) +{ + QFile f(sys_dev); + f.open(QIODevice::WriteOnly); + QByteArray ba = str.toLocal8Bit(); + qint64 offset = 0; + for (uint tries = 0; (offset < ba.size()) && tries < 5; tries++) + { + qint64 written = f.write(ba.data()+offset, ba.size()-offset); + if (written < 0) + return false; + offset += written; + } + return true; +} +#endif + +int CardUtil::GetASIDeviceNumber(const QString &device, QString *error) +{ +#ifdef USING_ASI + // basic confirmation + struct stat statbuf; + memset(&statbuf, 0, sizeof(statbuf)); + if (stat(device.toLocal8Bit().constData(), &statbuf) < 0) + { + if (error) + *error = QString("Unable to stat '%1'").arg(device) + ENO; + return -1; + } + + if (!S_ISCHR(statbuf.st_mode)) + { + if (error) + *error = QString("'%1' is not a character device").arg(device); + return -1; + } + + if (!(statbuf.st_rdev & 0x0080)) + { + if (error) + *error = QString("'%1' not a DVEO ASI receiver").arg(device); + return -1; + } + + int device_num = statbuf.st_rdev & 0x007f; + + // extra confirmation + QString sys_dev_contents = read_sys(sys_dev(device_num, "dev")); + QStringList sys_dev_clist = sys_dev_contents.split(":"); + if (2 != sys_dev_clist.size()) + { + if (error) + { + *error = QString("Unable to read '%1'") + .arg(sys_dev(device_num, "dev")); + } + return -1; + } + if (sys_dev_clist[0].toUInt() != (statbuf.st_rdev>>8)) + { + if (error) + *error = QString("'%1' not a DVEO ASI device").arg(device); + return -1; + } + + return device_num; +#else + (void) device; + if (error) + *error = "Not compiled with ASI support."; + return -1; +#endif +} + +uint CardUtil::GetASIBufferSize(uint device_num, QString *error) +{ +#ifdef USING_ASI + // get the buffer size + QString sys_bufsize_contents = read_sys(sys_dev(device_num, "bufsize")); + bool ok; + uint buf_size = sys_bufsize_contents.toUInt(&ok); + if (!ok) + { + if (error) + { + *error = QString("Failed to read buffer size from '%1'") + .arg(sys_dev(device_num, "bufsize")); + } + return 0; + } + return buf_size; +#else + (void) device_num; + if (error) + *error = "Not compiled with ASI support."; + return 0; +#endif +} + +int CardUtil::GetASIMode(uint device_num, QString *error) +{ +#ifdef USING_ASI + QString sys_bufsize_contents = read_sys(sys_dev(device_num, "mode")); + bool ok; + uint mode = sys_bufsize_contents.toUInt(&ok); + if (!ok) + { + if (error) + { + *error = QString("Failed to read mode from '%1'") + .arg(sys_dev(device_num, "mode")); + } + return -1; + } + return mode; +#else + (void) device_num; + if (error) + *error = "Not compiled with ASI support."; + return -1; +#endif +} + +bool CardUtil::SetASIMode(uint device_num, uint mode, QString *error) +{ +#ifdef USING_ASI + QString sys_bufsize_contents = read_sys(sys_dev(device_num, "mode")); + bool ok; + uint old_mode = sys_bufsize_contents.toUInt(&ok); + if (ok && old_mode == mode) + return true; + ok = write_sys(sys_dev(device_num, "mode"), QString("%1\n").arg(mode)); + if (!ok && error) + { + *error = QString("Failed to set mode to %1 using '%2'") + .arg(mode).arg(sys_dev(device_num, "mode")); + } + return ok; +#else + (void) device_num; + if (error) + *error = "Not compiled with ASI support."; + return false; +#endif +} diff --git a/mythtv/libs/libmythtv/cardutil.h b/mythtv/libs/libmythtv/cardutil.h index 48b3262d26b..851a22eaca9 100644 --- a/mythtv/libs/libmythtv/cardutil.h +++ b/mythtv/libs/libmythtv/cardutil.h @@ -60,6 +60,7 @@ class MTV_PUBLIC CardUtil DVBS2 = 13, IMPORT = 14, DEMO = 15, + ASI = 16, }; static enum CARD_TYPES toCardType(const QString &name) @@ -96,6 +97,8 @@ class MTV_PUBLIC CardUtil return IMPORT; if ("DEMO" == name) return DEMO; + if ("ASI" == name) + return ASI; return ERROR_UNKNOWN; } @@ -104,7 +107,15 @@ class MTV_PUBLIC CardUtil return (rawtype != "DVB") && (rawtype != "FIREWIRE") && (rawtype != "HDHOMERUN") && (rawtype != "FREEBOX") && - (rawtype != "IMPORT") && (rawtype != "DEMO"); + (rawtype != "IMPORT") && (rawtype != "DEMO") && + (rawtype != "ASI"); + } + + static bool IsV4L(const QString &rawtype) + { + return (rawtype == "V4L" || rawtype == "MPEG" || + rawtype == "HDPVR" || rawtype == "GO7007" || + rawtype == "MJPEG"); } static bool IsChannelChangeDiscontinuous(const QString &rawtype) @@ -116,8 +127,10 @@ class MTV_PUBLIC CardUtil { return (rawtype == "FIREWIRE") || (rawtype == "HDPVR") || - (rawtype == "IMPORT") || (rawtype == "DEMO"); + (rawtype == "IMPORT") || (rawtype == "DEMO") || + (rawtype == "GO7007") || (rawtype == "MJPEG"); } + static QString GetScanableCardTypes(void); static bool IsEITCapable(const QString &rawtype) { @@ -127,7 +140,9 @@ class MTV_PUBLIC CardUtil static bool IsTunerSharingCapable(const QString &rawtype) { - return (rawtype == "DVB") || (rawtype == "HDHOMERUN"); + return + (rawtype == "DVB") || (rawtype == "HDHOMERUN") || + (rawtype == "ASI"); } static bool IsTunerShared(uint cardidA, uint cardidB); @@ -135,21 +150,27 @@ class MTV_PUBLIC CardUtil static bool IsTuningDigital(const QString &rawtype) { return - (rawtype == "DVB") || (rawtype == "HDHOMERUN"); + (rawtype == "DVB") || (rawtype == "HDHOMERUN") || + (rawtype == "ASI"); } static bool IsTuningAnalog(const QString &rawtype) { return - (rawtype == "V4L") || (rawtype == "MPEG") || - (rawtype == "HDPVR"); + (rawtype == "V4L") || (rawtype == "MPEG"); + } + + static bool IsTuningVirtual(const QString &rawtype) + { + return + (rawtype == "FIREWIRE") || (rawtype == "HDPVR"); } static bool IsSingleInputCard(const QString &rawtype) { return (rawtype == "FIREWIRE") || (rawtype == "HDHOMERUN") || - (rawtype == "FREEBOX") || + (rawtype == "FREEBOX") || (rawtype == "ASI") || (rawtype == "IMPORT") || (rawtype == "DEMO"); } @@ -282,9 +303,9 @@ class MTV_PUBLIC CardUtil // V4L info static bool hasV4L2(int videofd); static bool GetV4LInfo(int videofd, QString &card, QString &driver, - uint32_t &version); + uint32_t &version, uint32_t &capabilities); static bool GetV4LInfo(int videofd, QString &card, QString &driver) - { uint32_t dummy; return GetV4LInfo(videofd, card, driver, dummy); } + { uint32_t d1,d2; return GetV4LInfo(videofd, card, driver, d1, d2); } static InputNames ProbeV4LVideoInputs(int videofd, bool &ok); static InputNames ProbeV4LAudioInputs(int videofd, bool &ok); @@ -292,6 +313,17 @@ class MTV_PUBLIC CardUtil static bool HDHRdoesDVB(const QString &device); static QString GetHDHRdesc(const QString &device); + // ASI info + static int GetASIDeviceNumber(const QString &device, + QString *error = NULL); + + static uint GetASIBufferSize(uint device_num, + QString *error = NULL); + static int GetASIMode(uint device_num, + QString *error = NULL); + static bool SetASIMode(uint device_num, uint mode, + QString *error = NULL); + private: static QStringList ProbeV4LVideoInputs(QString device); static QStringList ProbeV4LAudioInputs(QString device); diff --git a/mythtv/libs/libmythtv/channelbase.cpp b/mythtv/libs/libmythtv/channelbase.cpp index 9eda6b8f381..bbb7f8e9305 100644 --- a/mythtv/libs/libmythtv/channelbase.cpp +++ b/mythtv/libs/libmythtv/channelbase.cpp @@ -1,5 +1,4 @@ // Std C headers -#include #include #include @@ -15,129 +14,56 @@ #include using namespace std; +// Qt headers +#include +#include + // MythTV headers +#include "firewirechannel.h" +#include "mythcorecontext.h" +#include "dummychannel.h" +#include "tvremoteutil.h" +#include "channelutil.h" #include "channelbase.h" +#include "channelutil.h" #include "frequencies.h" -#include "tv_rec.h" -#include "mythcorecontext.h" -#include "exitcodes.h" -#include "mythdb.h" +#include "hdhrchannel.h" +#include "iptvchannel.h" #include "mythverbose.h" -#include "cardutil.h" -#include "channelutil.h" -#include "tvremoteutil.h" +#include "asichannel.h" +#include "dtvchannel.h" +#include "dvbchannel.h" +#include "v4lchannel.h" #include "sourceutil.h" +#include "exitcodes.h" #include "cardutil.h" #include "compat.h" -#include "mythsystem.h" -#include "mythlogging.h" #define LOC QString("ChannelBase(%1): ").arg(GetCardID()) #define LOC_WARN QString("ChannelBase(%1) Warning: ").arg(GetCardID()) #define LOC_ERR QString("ChannelBase(%1) Error: ").arg(GetCardID()) -/* - * Run the channel change thread, and report the status when done - */ -void ChannelThread::run(void) -{ - threadRegister("Channel"); - VERBOSE(VB_CHANNEL, "ChannelThread::run"); - bool result = tuner->SetChannelByString(channel); - tuner->setStatus(result ? - ChannelBase::changeSuccess : ChannelBase::changeFailed); - threadDeregister(); -} - -ChannelBase::ChannelBase(TVRec *parent) - : +ChannelBase::ChannelBase(TVRec *parent) : m_pParent(parent), m_curchannelname(""), m_currentInputID(-1), m_commfree(false), m_cardid(0), - m_abort_change(false), m_changer(NULL) + m_process_thread(NULL), m_process(NULL), m_process_status(0) { - m_tuneStatus = changeUnknown; - m_tuneThread.tuner = this; } ChannelBase::~ChannelBase(void) { ClearInputMap(); - TeardownAll(); - if( m_changer ) - delete m_changer; -} - -void ChannelBase::TeardownAll(void) -{ - if (m_tuneThread.isRunning()) - { - m_thread_lock.lock(); - m_abort_change = true; - if( m_changer ) - m_changer->Term(true); - - m_tuneCond.wakeAll(); - m_thread_lock.unlock(); - m_tuneThread.wait(); - } -} - -void ChannelBase::SelectChannel(const QString & chan, bool use_sm) -{ - VERBOSE(VB_CHANNEL, LOC + "SelectChannel " + chan); - - TeardownAll(); - if (use_sm) + QMutexLocker locker(&m_process_lock); + if (m_process_thread) { - m_thread_lock.lock(); - m_abort_change = false; - m_tuneStatus = changePending; - m_thread_lock.unlock(); - - m_curchannelname = m_tuneThread.channel = chan; - m_tuneThread.start(); + // better to a risk zombie than risk blocking forever.. + KillScript(500); + m_process_thread->exit(0); + if (m_process_thread->wait()) + delete m_process_thread; + m_process_thread = NULL; } - else - SetChannelByString(chan); -} - -/* - * Returns true of the channel change thread should abort - */ -bool ChannelBase::Aborted(void) -{ - bool result; - - m_thread_lock.lock(); - result = m_abort_change; - m_thread_lock.unlock(); - - return result; -} - -ChannelBase::Status ChannelBase::GetStatus(void) -{ - Status status; - - m_thread_lock.lock(); - status = m_tuneStatus; - m_thread_lock.unlock(); - - return status; -} - -ChannelBase::Status ChannelBase::Wait(void) -{ - m_tuneThread.wait(); - return m_tuneStatus; -} - -void ChannelBase::setStatus(ChannelBase::Status status) -{ - m_thread_lock.lock(); - m_tuneStatus = status; - m_thread_lock.unlock(); } bool ChannelBase::Init(QString &inputname, QString &startchannel, bool setchan) @@ -147,12 +73,9 @@ bool ChannelBase::Init(QString &inputname, QString &startchannel, bool setchan) if (!setchan) ok = inputname.isEmpty() ? false : IsTunable(inputname, startchannel); else if (inputname.isEmpty()) - { - SelectChannel(startchannel, false); - ok = Wait(); - } + ok = SetChannelByString(startchannel); else - ok = SelectInput(inputname, startchannel, false); + ok = SwitchToInput(inputname, startchannel); if (ok) return true; @@ -221,10 +144,12 @@ bool ChannelBase::Init(QString &inputname, QString &startchannel, bool setchan) if (chanid && cit != channels.end()) { if (!setchan) + { ok = IsTunable(*it, (mplexid_restriction) ? (*cit).channum : startchannel); + } else - ok = SelectInput(*it, (*cit).channum, false); + ok = SwitchToInput(*it, (*cit).channum); if (ok) { @@ -424,7 +349,6 @@ int ChannelBase::GetInputByName(const QString &input) const return -1; } -#if 0 // Not used? bool ChannelBase::SwitchToInput(const QString &inputname) { int input = GetInputByName(inputname); @@ -436,27 +360,28 @@ bool ChannelBase::SwitchToInput(const QString &inputname) "%1 on card\n").arg(inputname)); return false; } -#endif -bool ChannelBase::SelectInput(const QString &inputname, const QString &chan, - bool use_sm) +bool ChannelBase::SwitchToInput(const QString &inputname, const QString &chan) { + VERBOSE(VB_CHANNEL, LOC + QString("SwitchToInput(%1,%2)") + .arg(inputname).arg(chan)); + int input = GetInputByName(inputname); + bool ok = false; if (input >= 0) { - if (!SwitchToInput(input, false)) - return false; - SelectChannel(chan, use_sm); + ok = SwitchToInput(input, false); + if (ok) + ok = SetChannelByString(chan); } else { VERBOSE(VB_IMPORTANT, QString("ChannelBase: Could not find input: %1 on card when " "setting channel %2\n").arg(inputname).arg(chan)); - return false; } - return true; + return ok; } bool ChannelBase::SwitchToInput(int newInputNum, bool setstarting) @@ -472,7 +397,7 @@ bool ChannelBase::SwitchToInput(int newInputNum, bool setstarting) // input switching code would go here if (setstarting) - SelectChannel((*it)->startChanNum, true); + return SetChannelByString((*it)->startChanNum); return true; } @@ -583,14 +508,12 @@ static bool is_input_busy( return is_busy; } -bool ChannelBase::IsInputAvailable(int inputid, uint &mplexid_restriction) const +bool ChannelBase::IsInputAvailable( + int inputid, uint &mplexid_restriction) const { if (inputid < 0) return false; - if (!m_pParent) - return false; - // Check each input to make sure it doesn't belong to an // input group which is attached to a busy recorder. QMap busygrp; @@ -712,32 +635,185 @@ DBChanList ChannelBase::GetChannels(const QString &inputname) const return GetChannels(inputid); } -bool ChannelBase::ChangeExternalChannel(const QString &channum) +/// \note m_process_lock must be held when this is called +bool ChannelBase::KillScript(uint timeout_ms) { -#ifdef USING_MINGW - VERBOSE(VB_IMPORTANT, LOC_WARN + - QString("ChangeExternalChannel is not implemented in MinGW.")); - return false; -#else + if (!m_process) + return true; + + if (m_process->state() != QProcess::NotRunning) + { + m_process->terminate(); + if (!m_process->waitForFinished(max(timeout_ms/2,1U))) + { + m_process->kill(); + if (!m_process->waitForFinished(max(timeout_ms/2,1U))) + return false; + } + } + + delete m_process; + m_process = NULL; + return true; +} + +/// \note m_process_lock must NOT be held when this is called +void ChannelBase::HandleScript(const QString &freqid) +{ + QMutexLocker locker(&m_process_lock); + + bool ok = true; + m_process_status = 0; // unknown + InputMap::const_iterator it = m_inputs.find(m_currentInputID); - QString changer = (*it)->externalChanger; + if (it == m_inputs.end()) + { + m_process_status = 2; // failed + HandleScriptEnd(true); + return; + } + + if ((*it)->externalChanger.isEmpty()) + { + m_process_status = 3; // success + HandleScriptEnd(true); + return; + } - if (changer.isEmpty()) + if (freqid.isEmpty()) + { + VERBOSE(VB_IMPORTANT, LOC_WARN + + "A channel changer is set, but the freqid field is empty." + "\n\t\t\tWe will return success to ease setup pains, " + "but no script is will actually run."); + m_process_status = 3; // success + HandleScriptEnd(true); + return; + } + + // It's possible we simply never reaped the process, check status first. + if (m_process) + GetScriptStatus(true); + + // If it's still running, try killing it + if (m_process) + ok = KillScript(50); + + // The GetScriptStatus() call above can reset m_process_status with + // the exit status of the last channel change script invocation, so + // we must set it to pending here. + m_process_status = 1; // pending + + if (!ok) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + + "Can not execute channel changer, previous call to script " + "is still running."); + m_process_status = 2; // failed + HandleScriptEnd(ok); + } + else + { + ok = ChangeExternalChannel((*it)->externalChanger, freqid); + if (!ok) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + + "Can not execute channel changer."); + m_process_status = 2; // failed + HandleScriptEnd(ok); + } + } +} + +bool ChannelBase::ChangeExternalChannel( + const QString &changer, const QString &freqid) +{ + if (m_process) return false; - QString command = QString("%1 %2").arg(changer).arg(channum); + if (changer.isEmpty() || freqid.isEmpty()) + return false; + + QString command = QString("/bin/sh -c \"%1 %2\"").arg(changer).arg(freqid); + VERBOSE(VB_CHANNEL, LOC + QString("Running command: %1").arg(command)); + + if (!m_process_thread) + { + m_process_thread = new ProcessThread(); + m_process_thread->start(); + } + + m_process = m_process_thread->CreateProcess(command); + + return true; +} + +uint ChannelBase::GetScriptStatus(bool holding_lock) +{ + if (!holding_lock) + m_process_lock.lock(); - uint flags = kMSNone; - uint result; + if (m_process && m_process->state() == QProcess::NotRunning) + { + if (m_process->exitStatus() != QProcess::CrashExit && + m_process->exitCode() == 0) + { + m_process_status = 3; // success + } + else + { + m_process_status = 2; // failed + } - // This is being done using the class rather than the myth_system wrapper - // to accomodate use of Term in TeardownAll - m_changer = new MythSystem(command, flags); - m_changer->Run(30); - result = m_changer->Wait(); + delete m_process; + m_process = NULL; - return( result == 0 ); -#endif // !USING_MINGW + VERBOSE(VB_CHANNEL, LOC + QString("GetScriptStatus() %1") + .arg(m_process_status)); + + HandleScriptEnd(3 == m_process_status); + } + else + { + QString ps = "NULL"; + if (m_process != NULL) + { + int s = m_process->state(); + ps = QString::number(s); + switch (s) + { + case QProcess::Running: ps = "running"; break; + case QProcess::NotRunning: ps = "not running"; break; + case QProcess::Starting: ps = "starting"; break; + } + } + VERBOSE(VB_CHANNEL, LOC + QString("GetScriptStatus() %1 (ps %2)") + .arg(m_process_status).arg(ps)); + } + + uint ret = m_process_status; + + if (!holding_lock) + m_process_lock.unlock(); + + return ret; +} + +/// \note m_process_lock must be held when this is called +void ChannelBase::HandleScriptEnd(bool ok) +{ + VERBOSE(VB_CHANNEL, LOC + "Channel change script " + + ((ok) ? "succeeded" : "failed")); + + if (ok) + { + InputMap::const_iterator it = m_inputs.find(m_currentInputID); + if (it != m_inputs.end()) + { + // Set this as the future start channel for this source + (*it)->startChanNum = m_curchannelname; + } + } } /** \fn ChannelBase::GetCardID(void) const @@ -782,7 +858,6 @@ int ChannelBase::GetChanID() const return -1; query.next(); - return query.value(0).toInt(); } @@ -841,6 +916,14 @@ bool ChannelBase::InitializeInputs(void) query.value(0).toUInt(), 0, channels); + if (!IsExternalChannelChangeSupported() && + !m_inputs[query.value(0).toUInt()]->externalChanger.isEmpty()) + { + VERBOSE(VB_IMPORTANT, LOC_WARN + "External Channel changer is " + "set, but this device does not support it."); + m_inputs[query.value(0).toUInt()]->externalChanger.clear(); + } + m_allchannels.insert(m_allchannels.end(), channels.begin(), channels.end()); } @@ -924,25 +1007,7 @@ void ChannelBase::StoreInputChannels(const InputMap &inputs) */ int ChannelBase::GetDefaultInput(uint cardid) { - MSqlQuery query(MSqlQuery::InitCon()); - query.prepare( - "SELECT defaultinput " - "FROM capturecard " - "WHERE cardid = :CARDID"); - query.bindValue(":CARDID", cardid); - - if (!query.exec() || !query.isActive()) - { - MythDB::DBError("GetDefaultInput", query); - return -1; - } - else if (query.size() > 0) - { - query.next(); - // Set initial input to first connected input - return GetInputByName(query.value(0).toString()); - } - return -1; + return GetInputByName(CardUtil::GetDefaultInput(cardid)); } /** \fn ChannelBase::StoreDefaultInput(uint, const QString&) @@ -1055,3 +1120,137 @@ void ChannelBase::ClearInputMap(void) delete *it; m_inputs.clear(); } + +ChannelBase *ChannelBase::CreateChannel( + TVRec *tvrec, + const GeneralDBOptions &genOpt, + const DVBDBOptions &dvbOpt, + const FireWireDBOptions &fwOpt, + const QString &startchannel, + bool enter_power_save_mode, + QString &rbFileExt) +{ + rbFileExt = "mpg"; + + ChannelBase *channel = NULL; + if (genOpt.cardtype == "DVB") + { +#ifdef USING_DVB + channel = new DVBChannel(genOpt.videodev, tvrec); + dynamic_cast(channel)->SetSlowTuning( + dvbOpt.dvb_tuning_delay); +#endif + } + else if (genOpt.cardtype == "FIREWIRE") + { +#ifdef USING_FIREWIRE + channel = new FirewireChannel(tvrec, genOpt.videodev, fwOpt); +#endif + } + else if (genOpt.cardtype == "HDHOMERUN") + { +#ifdef USING_HDHOMERUN + channel = new HDHRChannel(tvrec, genOpt.videodev); +#endif + } + else if ((genOpt.cardtype == "IMPORT") || + (genOpt.cardtype == "DEMO") || + (genOpt.cardtype == "MPEG" && + genOpt.videodev.toLower().left(5) == "file:")) + { + channel = new DummyChannel(tvrec); + } + else if (genOpt.cardtype == "FREEBOX") + { +#ifdef USING_IPTV + channel = new IPTVChannel(tvrec, genOpt.videodev); +#endif + } + else if (genOpt.cardtype == "ASI") + { +#ifdef USING_ASI + channel = new ASIChannel(tvrec, genOpt.videodev); +#endif + } + else if (CardUtil::IsV4L(genOpt.cardtype)) + { +#ifdef USING_V4L + channel = new V4LChannel(tvrec, genOpt.videodev); +#endif + if ((genOpt.cardtype != "MPEG") && (genOpt.cardtype != "HDPVR")) + rbFileExt = "nuv"; + } + + if (!channel) + { + QString msg = QString( + "%1 card configured on video device %2, \n" + "but MythTV was not compiled with %3 support. \n" + "\n" + "Recompile MythTV with %4 support or remove the card \n" + "from the configuration and restart MythTV.") + .arg(genOpt.cardtype).arg(genOpt.videodev) + .arg(genOpt.cardtype).arg(genOpt.cardtype); + VERBOSE(VB_IMPORTANT, "ChannelBase::CreateChannel() Error: \n" + + msg + "\n"); + return NULL; + } + + if (!channel->Open()) + { + VERBOSE(VB_IMPORTANT, "ChannelBase::CreateChannel() Error: " + + QString("Failed to open device %1").arg(genOpt.videodev)); + delete channel; + return NULL; + } + + QString input = genOpt.defaultinput, channum = startchannel; + channel->Init(input, channum, true); + + if (enter_power_save_mode) + { + if (channel && + ((genOpt.cardtype == "DVB" && dvbOpt.dvb_on_demand) || + CardUtil::IsV4L(genOpt.cardtype))) + { + channel->Close(); + } + else + { + DTVChannel *dtvchannel = dynamic_cast(channel); + if (dtvchannel) + dtvchannel->EnterPowerSavingMode(); + } + } + + return channel; +} + +bool ProcessThread::event(QEvent *e) +{ + if (MythEvent::MythEventMessage == e->type()) + { + MythEvent *me = static_cast(e); + if (me->Message() == "CreateProcess") + { + QMutexLocker locker(&m_lock); + m_proc = new QProcess(); + m_proc->start(me->ExtraData(0)); + m_wait.wakeOne(); + return true; + } + } + + return QThread::event(e); +} + +QProcess *ProcessThread::CreateProcess(const QString &command) +{ + QMutexLocker locker(&m_lock); + QStringList cmd(command); + QCoreApplication::postEvent(this, new MythEvent("CreateProcess", cmd)); + m_wait.wait(&m_lock); + QProcess *ret = m_proc; + m_proc = NULL; + return ret; +} diff --git a/mythtv/libs/libmythtv/channelbase.h b/mythtv/libs/libmythtv/channelbase.h index 7993edebc93..65126bd156f 100644 --- a/mythtv/libs/libmythtv/channelbase.h +++ b/mythtv/libs/libmythtv/channelbase.h @@ -4,10 +4,10 @@ #define CHANNELBASE_H // Qt headers +#include #include -#include -#include -#include +#include +#include // MythTV headers #include "channelutil.h" @@ -15,20 +15,13 @@ #include "mythsystem.h" #include "tv.h" -class TVRec; +class FireWireDBOptions; +class GeneralDBOptions; +class ProcessThread; +class DVBDBOptions; class ChannelBase; - -/* - * Thread to run tunning process in - */ -class ChannelThread : public QThread -{ - public: - virtual void run(void); - - QString channel; - ChannelBase *tuner; -}; +class QProcess; +class TVRec; /** \class ChannelBase * \brief Abstract class providing a generic interface to tuning hardware. @@ -40,23 +33,15 @@ class ChannelThread : public QThread class ChannelBase { - friend class ChannelThread; + friend class SignalMonitor; public: - enum Status { changeUnknown = 'U', changePending = 'P', - changeFailed = 'F', changeSuccess = 'S' }; - ChannelBase(TVRec *parent); virtual ~ChannelBase(void); virtual bool Init(QString &inputname, QString &startchannel, bool setchan); virtual bool IsTunable(const QString &input, const QString &channum) const; - virtual void SelectChannel(const QString & chan, bool use_sm); - - Status GetStatus(void); - Status Wait(void); - // Methods that must be implemented. /// \brief Opens the channel changing hardware for use. virtual bool Open(void) = 0; @@ -64,12 +49,17 @@ class ChannelBase virtual void Close(void) = 0; /// \brief Reports whether channel is already open virtual bool IsOpen(void) const = 0; + virtual bool SetChannelByString(const QString &chan) = 0; // Methods that one might want to specialize + virtual void SetFormat(const QString &/*format*/) {} + virtual int SetFreqTable(const QString &/*tablename*/) { return 0; } /// \brief Sets file descriptor. virtual void SetFd(int fd) { (void)fd; }; /// \brief Returns file descriptor, -1 if it does not exist. virtual int GetFd(void) const { return -1; }; + virtual bool Tune(const QString &freqid, int finetune) { return true; } + virtual bool IsExternalChannelChangeSupported(void) { return false; } // Gets virtual uint GetNextChannel(uint chanid, int direction) const; @@ -110,9 +100,8 @@ class ChannelBase const QString &newChanNum); // Input toggling convenience methods -// virtual bool SwitchToInput(const QString &input); // not used? - virtual bool SelectInput(const QString &input, const QString &chan, - bool use_sm); + virtual bool SwitchToInput(const QString &input); + virtual bool SwitchToInput(const QString &input, const QString &chan); virtual bool InitializeInputs(void); @@ -134,28 +123,39 @@ class ChannelBase // \brief Set cardid for scanning void SetCardID(uint _cardid) { m_cardid = _cardid; } - // \brief Set chan name for ringbuffer - void SetChanNum(const QString & chan) { m_curchannelname= chan; } virtual int GetCardID(void) const; - protected: - virtual bool SetChannelByString(const QString &chan) = 0; + static ChannelBase *CreateChannel( + TVRec *tv_rec, + const GeneralDBOptions &genOpt, + const DVBDBOptions &dvbOpt, + const FireWireDBOptions &fwOpt, + const QString &startchannel, + bool enter_power_save_mode, + QString &rbFileExt); + + protected: /// \brief Switches to another input on hardware, /// and sets the channel is setstarting is true. virtual bool SwitchToInput(int inputNum, bool setstarting); virtual bool IsInputAvailable( int inputNum, uint &mplexid_restriction) const; - virtual bool ChangeExternalChannel(const QString &newchan); - static void StoreInputChannels(const InputMap&); - static void StoreDefaultInput(uint cardid, const QString &input); int GetDefaultInput(uint cardid); void ClearInputMap(void); - bool Aborted(); - void setStatus(Status status); - void TeardownAll(void); + static void StoreInputChannels(const InputMap&); + static void StoreDefaultInput(uint cardid, const QString &input); + + protected: + bool KillScript(uint timeout_ms); + void HandleScript(const QString &freqid); + virtual void HandleScriptEnd(bool ok); + uint GetScriptStatus(bool holding_lock = false); + + bool ChangeExternalChannel( + const QString &changer, const QString &newchan); TVRec *m_pParent; QString m_curchannelname; @@ -165,15 +165,30 @@ class ChannelBase InputMap m_inputs; DBChanList m_allchannels; ///< channels across all inputs - QWaitCondition m_tuneCond; + QMutex m_process_lock; + ProcessThread *m_process_thread; + QProcess *m_process; + /// 0 == unknown, 1 == pending, 2 == failed, 4 == success + uint m_process_status; +}; - private: - mutable ChannelThread m_tuneThread; - Status m_tuneStatus; - QMutex m_thread_lock; - bool m_abort_change; - MythSystem *m_changer; +/** \brief ChannelBase helper class + * This is only needed because ChangeExternalChannel is called + * outside of a QThread event thread. If TVRec were converted to + * use QThread w/event thread this could be replaced by + * m_process = new QProcess(); + * m_process->start(command); + */ +class ProcessThread : public QThread +{ + public: + ProcessThread() { QObject::moveToThread(this); } + virtual void run(void) { exec(); } + virtual bool event(QEvent*); + QProcess *CreateProcess(const QString&); + QMutex m_lock; + QWaitCondition m_wait; + QProcess *m_proc; }; #endif - diff --git a/mythtv/libs/libmythtv/channelchangemonitor.cpp b/mythtv/libs/libmythtv/channelchangemonitor.cpp deleted file mode 100644 index 8c437145b2a..00000000000 --- a/mythtv/libs/libmythtv/channelchangemonitor.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// -*- Mode: c++ -*- - -#include -#include - -#include "mythcontext.h" -#include "channelchangemonitor.h" -#include "channelbase.h" - -#define LOC QString("ChannelChangeM: ").arg(channel->GetDevice()) -#define LOC_ERR QString("ChannelChangeM, Error: ").arg(channel->GetDevice()) - -ChannelChangeMonitor::ChannelChangeMonitor( - int db_cardnum, ChannelBase *_channel, uint64_t _flags) : - SignalMonitor(db_cardnum, _channel, _flags) -{ -} - -void ChannelChangeMonitor::UpdateValues(void) -{ - if (!monitor_thread.isRunning() || exit) - return; - - if (!IsChannelTuned()) - return; - - { - QMutexLocker locker(&statusLock); - signalLock.SetValue(true); - signalStrength.SetValue(100); - } - - EmitStatus(); - SendMessageAllGood(); -} diff --git a/mythtv/libs/libmythtv/channelchangemonitor.h b/mythtv/libs/libmythtv/channelchangemonitor.h deleted file mode 100644 index a78416d99b5..00000000000 --- a/mythtv/libs/libmythtv/channelchangemonitor.h +++ /dev/null @@ -1,21 +0,0 @@ -// -*- Mode: c++ -*- -// Copyright (c) 2005, Daniel Thor Kristjansson - -#ifndef _CHANNEL_CHANGE_MONITOR_H_ -#define _CHANNEL_CHANGE_MONITOR_H_ - -// MythTV headers -#include "signalmonitor.h" - -class ChannelBase; - -class ChannelChangeMonitor : public SignalMonitor -{ - public: - ChannelChangeMonitor(int db_cardnum, ChannelBase *_channel, - uint64_t _flags = kSigMon_WaitForSig); - - virtual void UpdateValues(void); -}; - -#endif // _CHANNEL_CHANGE_MONITOR_H_ diff --git a/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp b/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp index d0eb30aa60e..afc195011f0 100644 --- a/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp +++ b/mythtv/libs/libmythtv/channelscan/channelscan_sm.cpp @@ -1660,7 +1660,7 @@ void ChannelScanSM::ScanTransport(const transport_scan_items_it_t transport) } // Start signal monitor for this channel - signalMonitor->Start(false); + signalMonitor->Start(); timer.start(); waitingForTables = (item.tuning.sistandard != "analog"); @@ -1965,6 +1965,25 @@ bool ChannelScanSM::ScanTransport(uint mplexid, bool follow_nit) return false; } +bool ChannelScanSM::ScanCurrentTransport(const QString &sistandard) +{ + scanTransports.clear(); + nextIt = scanTransports.end(); + + signalTimeout = 30000; + QString name; + TransportScanItem item(sourceID, sistandard, name, 0, signalTimeout); + scanTransports.push_back(item); + + timer.start(); + waitingForTables = false; + extend_scan_list = false; + transportsScanned = 0; + nextIt = scanTransports.begin(); + scanning = true; + return true; +} + /** \fn ChannelScanSM::CheckImportedList(const DTVChannelInfoList&,uint,QString&,QString&,QString&) * \brief If we as scanning a dvb-utils import verify channel is in list.. */ diff --git a/mythtv/libs/libmythtv/channelscan/channelscan_sm.h b/mythtv/libs/libmythtv/channelscan/channelscan_sm.h index c4720d53d69..5ef69bcc637 100644 --- a/mythtv/libs/libmythtv/channelscan/channelscan_sm.h +++ b/mythtv/libs/libmythtv/channelscan/channelscan_sm.h @@ -69,6 +69,7 @@ class AnalogSignalHandler : public SignalMonitorListener public: AnalogSignalHandler(ChannelScanSM *_siscan) : siscan(_siscan) { } + public: virtual inline void AllGood(void); virtual void StatusSignalLock(const SignalMonitorValue&) { } virtual void StatusChannelTuned(const SignalMonitorValue&) { } @@ -115,6 +116,7 @@ class ChannelScanSM : public MPEGStreamListener, bool ScanTransportsStartingOn( int sourceid, const QMap &valueMap); bool ScanTransport(uint mplexid, bool follow_nit); + bool ScanCurrentTransport(const QString &sistandard); bool ScanForChannels( uint sourceid, const QString &std, const QString &cardtype, const DTVChannelList&); diff --git a/mythtv/libs/libmythtv/channelscan/channelscanner.cpp b/mythtv/libs/libmythtv/channelscan/channelscanner.cpp index a458fc27ff4..5de95e23b38 100644 --- a/mythtv/libs/libmythtv/channelscan/channelscanner.cpp +++ b/mythtv/libs/libmythtv/channelscan/channelscanner.cpp @@ -28,19 +28,20 @@ */ #include +using namespace std; -#include "channelscanner.h" -#include "cardutil.h" +#include "analogsignalmonitor.h" #include "iptvchannelfetcher.h" +#include "dvbsignalmonitor.h" +#include "scanwizardconfig.h" #include "channelscan_sm.h" +#include "channelscanner.h" +#include "hdhrchannel.h" #include "scanmonitor.h" -#include "scanwizardconfig.h" - -#include "v4lchannel.h" -#include "analogsignalmonitor.h" +#include "asichannel.h" #include "dvbchannel.h" -#include "dvbsignalmonitor.h" -#include "hdhrchannel.h" +#include "v4lchannel.h" +#include "cardutil.h" #define LOC QString("ChScan: ") #define LOC_ERR QString("ChScan, Error: ") @@ -219,6 +220,13 @@ void ChannelScanner::Scan( ok = sigmonScanner->ScanTransport(mplexid, do_follow_nit); } + else if (ScanTypeSetting::CurrentTransportScan == scantype) + { + QString sistandard = "mpeg"; + VERBOSE(VB_CHANSCAN, LOC + "ScanCurrentTransport(" + sistandard + ")"); + ok = sigmonScanner->ScanCurrentTransport(sistandard); + } + if (!ok) { VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to handle tune complete."); @@ -356,6 +364,13 @@ void ChannelScanner::PreScanCommon( } #endif // USING_HDHOMERUN +#ifdef USING_ASI + if ("ASI" == card_type) + { + channel = new ASIChannel(NULL, device); + } +#endif // USING_ASI + if (!channel) { VERBOSE(VB_IMPORTANT, LOC_ERR + "Channel not created"); diff --git a/mythtv/libs/libmythtv/channelscan/scanwizardconfig.cpp b/mythtv/libs/libmythtv/channelscan/scanwizardconfig.cpp index 00fd9d36d49..11b1360f0dd 100644 --- a/mythtv/libs/libmythtv/channelscan/scanwizardconfig.cpp +++ b/mythtv/libs/libmythtv/channelscan/scanwizardconfig.cpp @@ -19,51 +19,13 @@ #include "panedvbutilsimport.h" #include "paneexistingscanimport.h" -static QString card_types(void) -{ - QString cardTypes = ""; - -#ifdef USING_DVB - cardTypes += "'DVB'"; -#endif // USING_DVB - -#ifdef USING_V4L - if (!cardTypes.isEmpty()) - cardTypes += ","; - cardTypes += "'V4L'"; -# ifdef USING_IVTV - cardTypes += ",'MPEG'"; -# endif // USING_IVTV -#endif // USING_V4L - -#ifdef USING_IPTV - if (!cardTypes.isEmpty()) - cardTypes += ","; - cardTypes += "'FREEBOX'"; -#endif // USING_IPTV - -#ifdef USING_HDHOMERUN - if (!cardTypes.isEmpty()) - cardTypes += ","; - cardTypes += "'HDHOMERUN'"; -#endif // USING_HDHOMERUN - - if (cardTypes.isEmpty()) - cardTypes = "'DUMMY'"; - - return QString("(%1)").arg(cardTypes); -} - -//////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////// - ScanWizardConfig::ScanWizardConfig( ScanWizard *_parent, uint default_sourceid, uint default_cardid, QString default_inputname) : VerticalConfigurationGroup(false, true, false, false), videoSource(new VideoSourceSelector( - default_sourceid, card_types(), false)), + default_sourceid, CardUtil::GetScanableCardTypes(), false)), input(new InputSelector(default_cardid, default_inputname)), scanType(new ScanTypeSetting()), scanConfig(new ScanOptionalConfig(scanType)), @@ -212,6 +174,10 @@ void ScanTypeSetting::SetInput(const QString &cardids_inputname) addSelection(tr("M3U Import"), QString::number(IPTVImport), true); return; + case CardUtil::ASI: + addSelection(tr("ASI Scan"), + QString::number(CurrentTransportScan), true); + return; case CardUtil::ERROR_PROBE: addSelection(QObject::tr("Failed to probe the card"), QString::number(Error_Probe), true); @@ -273,6 +239,8 @@ ScanOptionalConfig::ScanOptionalConfig(ScanTypeSetting *_scan_type) : paneSingle); addTarget(QString::number(ScanTypeSetting::FullTransportScan), paneAll); + addTarget(QString::number(ScanTypeSetting::CurrentTransportScan), + new BlankSetting()); addTarget(QString::number(ScanTypeSetting::IPTVImport), new BlankSetting()); addTarget(QString::number(ScanTypeSetting::DVBUtilsImport), diff --git a/mythtv/libs/libmythtv/channelscan/scanwizardconfig.h b/mythtv/libs/libmythtv/channelscan/scanwizardconfig.h index a7384034472..ddfc7967827 100644 --- a/mythtv/libs/libmythtv/channelscan/scanwizardconfig.h +++ b/mythtv/libs/libmythtv/channelscan/scanwizardconfig.h @@ -79,6 +79,8 @@ class ScanTypeSetting : public ComboBoxSetting, public TransientStorage FullTransportScan, // Scan of one transport already in the database TransportScan, + /// Scans the transport when there is no tuner (for ASI) + CurrentTransportScan, // IPTV import of channels from M3U URL IPTVImport, // Imports lists from dvb-utils scanners diff --git a/mythtv/libs/libmythtv/channelsettings.cpp b/mythtv/libs/libmythtv/channelsettings.cpp index da05afdb3dc..e03829f1cd1 100644 --- a/mythtv/libs/libmythtv/channelsettings.cpp +++ b/mythtv/libs/libmythtv/channelsettings.cpp @@ -2,10 +2,11 @@ #include #include "channelsettings.h" -#include "cardutil.h" #include "channelutil.h" #include "programinfo.h" // for COMM_DETECT*, GetPreferredSkipTypeCombinations() +#include "mpegtables.h" #include "mythdirs.h" +#include "cardutil.h" QString ChannelDBStorage::GetWhereClause(MSqlBindings &bindings) const { @@ -536,4 +537,117 @@ ChannelOptionsV4L::ChannelOptionsV4L(const ChannelID& id) : addChild(new Hue(id)); }; +/***************************************************************************** + Channel Options - Video 4 Linux + *****************************************************************************/ + +ChannelOptionsRawTS::ChannelOptionsRawTS(const ChannelID &id) : + VerticalConfigurationGroup(false, true, false, false), cid(id) +{ + setLabel(QObject::tr("Channel Options - Raw Transport Stream")); + setUseLabel(false); + + const uint mx = kMaxPIDs; + pids.resize(mx); + sids.resize(mx); + pcrs.resize(mx); + + for (uint i = 0; i < mx; i++) + { + HorizontalConfigurationGroup *row = + new HorizontalConfigurationGroup(false, false, true, true); + TransLabelSetting *label0 = new TransLabelSetting(); + label0->setLabel(" PID"); + TransLabelSetting *label1 = new TransLabelSetting(); + label1->setLabel(" StreamID"); + TransLabelSetting *label2 = new TransLabelSetting(); + label2->setLabel(" Is PCR"); + row->addChild(label0); + row->addChild((pids[i] = new TransLineEditSetting())); + row->addChild(label1); + row->addChild((sids[i] = new TransComboBoxSetting())); + for (uint j = 0x101; j <= 0x1ff; j++) + { + QString desc = StreamID::GetDescription(j&0xff); + if (!desc.isEmpty()) + sids[i]->addSelection( + QString("%1 (0x%2)") + .arg(desc).arg(j&0xff,2,16,QLatin1Char('0')), + QString::number(j), false); + } + for (uint j = 0x101; j <= 0x1ff; j++) + { + QString desc = StreamID::GetDescription(j&0xff); + if (desc.isEmpty()) + sids[i]->addSelection( + QString("0x%1").arg(j&0xff,2,16,QLatin1Char('0')), + QString::number(j), false); + } +/* we don't allow tables right now, PAT & PMT generated on the fly + for (uint j = 0; j <= 0xff; j++) + { + QString desc = TableID::GetDescription(j); + if (!desc.isEmpty()) + { + sids[i]->addSelection( + QString("%1 (0x%2)").arg(j,0,16,QLatin1Char('0')), + QString::number(j), + false); + } + } +*/ + row->addChild(label2); + row->addChild((pcrs[i] = new TransCheckBoxSetting())); + addChild(row); + } +}; + +void ChannelOptionsRawTS::Load(void) +{ + uint chanid = cid.getValue().toUInt(); + + pid_cache_t pid_cache; + if (!ChannelUtil::GetCachedPids(chanid, pid_cache)) + return; + + pid_cache_t::const_iterator it = pid_cache.begin(); + for (uint i = 0; i < kMaxPIDs && it != pid_cache.end(); ) + { + if (!(it->IsPermanent())) + { + ++it; + continue; + } + + pids[i]->setValue(QString("0x%1") + .arg(it->GetPID(),2,16,QLatin1Char('0'))); + sids[i]->setValue(QString::number(it->GetComposite()&0x1ff)); + pcrs[i]->setValue(it->IsPCRPID()); + + ++it; + ++i; + } +} + +void ChannelOptionsRawTS::Save(void) +{ + uint chanid = cid.getValue().toUInt(); + + pid_cache_t pid_cache; + for (uint i = 0; i < kMaxPIDs; i++) + { + bool ok; + uint pid = pids[i]->getValue().toUInt(&ok, 0); + if (!ok || !sids[i]->getValue().toUInt()) + continue; + + pid_cache.push_back( + pid_cache_item_t( + pid, sids[i]->getValue().toUInt() | 0x10000 | + (pcrs[i]->getValue().toUInt() ? 0x200 : 0x0))); + } + + ChannelUtil::SaveCachedPids(chanid, pid_cache, true /* delete_all */); +} + /* vim: set expandtab tabstop=4 shiftwidth=4: */ diff --git a/mythtv/libs/libmythtv/channelsettings.h b/mythtv/libs/libmythtv/channelsettings.h index ac58c6f664a..e28b379e661 100644 --- a/mythtv/libs/libmythtv/channelsettings.h +++ b/mythtv/libs/libmythtv/channelsettings.h @@ -132,17 +132,38 @@ class MTV_PUBLIC ChannelOptionsCommon: public VerticalConfigurationGroup XmltvID *xmltvID; }; -class MTV_PUBLIC ChannelOptionsFilters: public VerticalConfigurationGroup { +class MTV_PUBLIC ChannelOptionsFilters: public VerticalConfigurationGroup +{ public: ChannelOptionsFilters(const ChannelID& id); }; -class MTV_PUBLIC ChannelOptionsV4L: public VerticalConfigurationGroup { +class MTV_PUBLIC ChannelOptionsV4L: public VerticalConfigurationGroup +{ public: ChannelOptionsV4L(const ChannelID& id); }; -class MTV_PUBLIC ChannelTVFormat : public ComboBoxSetting, public ChannelDBStorage +class MTV_PUBLIC ChannelOptionsRawTS: public VerticalConfigurationGroup +{ + public: + ChannelOptionsRawTS(const ChannelID &id); + + virtual void Load(void); + virtual void Save(void); + + private: + const ChannelID &cid; + + vector pids; + vector sids; + vector pcrs; + + static const uint kMaxPIDs = 10; +}; + +class MTV_PUBLIC ChannelTVFormat : + public ComboBoxSetting, public ChannelDBStorage { public: ChannelTVFormat(const ChannelID &id); diff --git a/mythtv/libs/libmythtv/channelutil.cpp b/mythtv/libs/libmythtv/channelutil.cpp index c9de6461e56..42d62a836fc 100644 --- a/mythtv/libs/libmythtv/channelutil.cpp +++ b/mythtv/libs/libmythtv/channelutil.cpp @@ -843,6 +843,129 @@ int ChannelUtil::GetInputID(int source_id, int card_id) return input_id; } +QStringList ChannelUtil::GetCardTypes(uint chanid) +{ + MSqlQuery query(MSqlQuery::InitCon()); + query.prepare("SELECT cardtype " + "FROM capturecard, cardinput, channel " + "WHERE channel.chanid = :CHANID AND " + " channel.sourceid = cardinput.sourceid AND " + " cardinput.cardid = capturecard.cardid " + "GROUP BY cardtype"); + query.bindValue(":CHANID", chanid); + + QStringList list; + if (!query.exec()) + { + MythDB::DBError("ChannelUtil::GetCardTypes", query); + return list; + } + while (query.next()) + list.push_back(query.value(0).toString()); + return list; +} + +static bool lt_pidcache( + const pid_cache_item_t &a, const pid_cache_item_t &b) +{ + return a.GetPID() < b.GetPID(); +} + +/** \brief Returns cached MPEG PIDs when given a Channel ID. + * + * \param chanid Channel ID to fetch cached pids for. + * \param pid_cache List of PIDs with their TableID + * types is returned in pid_cache. + */ +bool ChannelUtil::GetCachedPids(uint chanid, + pid_cache_t &pid_cache) +{ + MSqlQuery query(MSqlQuery::InitCon()); + QString thequery = QString("SELECT pid, tableid FROM pidcache " + "WHERE chanid='%1'").arg(chanid); + query.prepare(thequery); + + if (!query.exec() || !query.isActive()) + { + MythDB::DBError("GetCachedPids: fetching pids", query); + return false; + } + + while (query.next()) + { + int pid = query.value(0).toInt(), tid = query.value(1).toInt(); + if ((pid >= 0) && (tid >= 0)) + pid_cache.push_back(pid_cache_item_t(pid, tid)); + } + stable_sort(pid_cache.begin(), pid_cache.end(), lt_pidcache); + + return true; +} + +/** \brief Saves PIDs for PSIP tables to database. + * + * \param chanid Channel ID to fetch cached pids for. + * \param pid_cache List of PIDs with their TableID types to be saved. + * \param delete_all If true delete both permanent and transient pids first. + */ +bool ChannelUtil::SaveCachedPids(uint chanid, + const pid_cache_t &_pid_cache, + bool delete_all) +{ + MSqlQuery query(MSqlQuery::InitCon()); + + /// delete + if (delete_all) + query.prepare("DELETE FROM pidcache WHERE chanid = :CHANID"); + else + query.prepare( + "DELETE FROM pidcache " + "WHERE chanid = :CHANID AND tableid < 65536"); + + query.bindValue(":CHANID", chanid); + + if (!query.exec()) + { + MythDB::DBError("GetCachedPids -- delete", query); + return false; + } + + pid_cache_t old_cache; + GetCachedPids(chanid, old_cache); + pid_cache_t pid_cache = _pid_cache; + stable_sort(pid_cache.begin(), pid_cache.end(), lt_pidcache); + + /// insert + query.prepare( + "INSERT INTO pidcache " + "SET chanid = :CHANID, pid = :PID, tableid = :TABLEID"); + query.bindValue(":CHANID", chanid); + + bool ok = true; + pid_cache_t::const_iterator ito = old_cache.begin(); + pid_cache_t::const_iterator itn = pid_cache.begin(); + for (; itn != pid_cache.end(); ++itn) + { + // if old pid smaller than current new pid, skip this old pid + for (; ito != old_cache.end() && ito->GetPID() < itn->GetPID(); ito++); + + // if already in DB, skip DB insert + if (ito != old_cache.end() && ito->GetPID() == itn->GetPID()) + continue; + + query.bindValue(":PID", itn->GetPID()); + query.bindValue(":TABLEID", itn->GetComposite()); + + if (!query.exec()) + { + MythDB::DBError("GetCachedPids -- insert", query); + ok = false; + } + } + + return ok; +} + QString ChannelUtil::GetChannelValueStr(const QString &channel_field, uint cardid, const QString &input, @@ -1759,7 +1882,7 @@ bool ChannelUtil::GetChannelData( mplexid = query.value(5).toUInt(); atsc_major = query.value(6).toUInt(); atsc_minor = query.value(7).toUInt(); - mpeg_prog_num = query.value(8).toUInt(); + mpeg_prog_num = (query.value(8).isNull()) ? -1 : query.value(8).toInt(); if (!mplexid || (mplexid == 32767)) /* 32767 deals with old lineups */ return true; diff --git a/mythtv/libs/libmythtv/channelutil.h b/mythtv/libs/libmythtv/channelutil.h index 085a468afa4..7e044b92309 100644 --- a/mythtv/libs/libmythtv/channelutil.h +++ b/mythtv/libs/libmythtv/channelutil.h @@ -1,3 +1,4 @@ +// -*- Mode: c++ -*- #ifndef CHANUTIL_H #define CHANUTIL_H @@ -19,6 +20,27 @@ using namespace std; class NetworkInformationTable; +class pid_cache_item_t +{ + public: + pid_cache_item_t() : pid(0), sid_tid(0) {} + pid_cache_item_t(uint _pid, uint _sid_tid) : + pid(_pid), sid_tid(_sid_tid) {} + uint GetPID(void) const { return pid; } + uint GetStreamID(void) const + { return (sid_tid&0x100) ? GetID() : 0; } + uint GetTableID(void) const + { return (sid_tid&0x100) ? 0 : GetID(); } + uint GetID(void) const { return sid_tid & 0xff; } + bool IsPCRPID(void) const { return sid_tid&0x200; } + bool IsPermanent(void) const { return sid_tid&0x10000; } + uint GetComposite(void) const { return sid_tid; } + private: + uint pid; + uint sid_tid; +}; +typedef vector pid_cache_t; + /** \class ChannelUtil * \brief Collection of helper utilities for channel DB use */ @@ -223,6 +245,10 @@ class MTV_PUBLIC ChannelUtil static uint GetSourceIDForChannel(uint chanid); static int GetInputID(int sourceid, int cardid); + static QStringList GetCardTypes(uint chandid); + + static bool GetCachedPids(uint chanid, pid_cache_t &pid_cache); + // Misc sets static bool SetChannelValue(const QString &field_name, QString value, @@ -233,6 +259,10 @@ class MTV_PUBLIC ChannelUtil QString value, int chanid); + static bool SaveCachedPids(uint chanid, + const pid_cache_t &pid_cache, + bool delete_all = false); + static const QString kATSCSeparators; private: diff --git a/mythtv/libs/libmythtv/dtvchannel.cpp b/mythtv/libs/libmythtv/dtvchannel.cpp index d98938f54bc..3bbd499851a 100644 --- a/mythtv/libs/libmythtv/dtvchannel.cpp +++ b/mythtv/libs/libmythtv/dtvchannel.cpp @@ -1,8 +1,14 @@ -#include "dtvchannel.h" +// C++ headers +#include +using namespace std; // MythTV headers #include "mythdb.h" +#include "tv_rec.h" #include "cardutil.h" +#include "dtvchannel.h" +#include "mpegtables.h" +#include "mythverbose.h" #define LOC QString("DTVChan(%1): ").arg(GetDevice()) #define LOC_WARN QString("DTVChan(%1) Warning: ").arg(GetDevice()) @@ -17,86 +23,33 @@ DTVChannel::DTVChannel(TVRec *parent) sistandard("mpeg"), tuningMode(QString::null), currentProgramNum(-1), currentATSCMajorChannel(0), currentATSCMinorChannel(0), - currentTransportID(0), currentOriginalNetworkID(0) + currentTransportID(0), currentOriginalNetworkID(0), + genPAT(NULL), genPMT(NULL) { } DTVChannel::~DTVChannel() { - QMutexLocker locker(&master_map_lock); - QMap::iterator it = master_map.begin(); - for (; it != master_map.end(); ++it) - { - if (*it == this) - { - master_map.erase(it); - break; - } - } -} - -/** \fn DTVChannel::GetCachedPids(int, pid_cache_t&) - * \brief Returns cached MPEG PIDs when given a Channel ID. - * - * \param chanid Channel ID to fetch cached pids for. - * \param pid_cache List of PIDs with their TableID - * types is returned in pid_cache. - */ -void DTVChannel::GetCachedPids(int chanid, pid_cache_t &pid_cache) -{ - MSqlQuery query(MSqlQuery::InitCon()); - QString thequery = QString("SELECT pid, tableid FROM pidcache " - "WHERE chanid='%1'").arg(chanid); - query.prepare(thequery); - - if (!query.exec() || !query.isActive()) + if (genPAT) { - MythDB::DBError("GetCachedPids: fetching pids", query); - return; + delete genPAT; + genPAT = NULL; } - while (query.next()) + if (genPMT) { - int pid = query.value(0).toInt(), tid = query.value(1).toInt(); - if ((pid >= 0) && (tid >= 0)) - pid_cache.push_back(pid_cache_item_t(pid, tid)); + delete genPMT; + genPMT = NULL; } -} - -/** \fn DTVChannel::SaveCachedPids(int, const pid_cache_t&) - * \brief Saves PIDs for PSIP tables to database. - * - * \param chanid Channel ID to fetch cached pids for. - * \param pid_cache List of PIDs with their TableID types to be saved. - */ -void DTVChannel::SaveCachedPids(int chanid, const pid_cache_t &pid_cache) -{ - MSqlQuery query(MSqlQuery::InitCon()); - /// delete - QString thequery = - QString("DELETE FROM pidcache WHERE chanid='%1'").arg(chanid); - query.prepare(thequery); - if (!query.exec() || !query.isActive()) - { - MythDB::DBError("GetCachedPids -- delete", query); - return; - } - - /// insert - pid_cache_t::const_iterator it = pid_cache.begin(); - for (; it != pid_cache.end(); ++it) + QMutexLocker locker(&master_map_lock); + QMap::iterator it = master_map.begin(); + for (; it != master_map.end(); ++it) { - thequery = QString("INSERT INTO pidcache " - "SET chanid='%1', pid='%2', tableid='%3'") - .arg(chanid).arg(it->first).arg(it->second); - - query.prepare(thequery); - - if (!query.exec() || !query.isActive()) + if (*it == this) { - MythDB::DBError("GetCachedPids -- insert", query); - return; + master_map.erase(it); + break; } } } @@ -170,6 +123,27 @@ void DTVChannel::SetTuningMode(const QString &tuning_mode) tuningMode.detach(); } +/** \brief Returns cached MPEG PIDs for last tuned channel. + * \param pid_cache List of PIDs with their TableID + * types is returned in pid_cache. + */ +void DTVChannel::GetCachedPids(pid_cache_t &pid_cache) const +{ + int chanid = GetChanID(); + if (chanid > 0) + ChannelUtil::GetCachedPids(chanid, pid_cache); +} + +/** \brief Saves MPEG PIDs to cache to database + * \param pid_cache List of PIDs with their TableID types to be saved. + */ +void DTVChannel::SaveCachedPids(const pid_cache_t &pid_cache) const +{ + int chanid = GetChanID(); + if (chanid > 0) + ChannelUtil::SaveCachedPids(chanid, pid_cache); +} + DTVChannel *DTVChannel::GetMaster(const QString &videodevice) { QMutexLocker locker(&master_map_lock); @@ -197,3 +171,240 @@ const DTVChannel *DTVChannel::GetMaster(const QString &videodevice) const return this; } + +bool DTVChannel::SetChannelByString(const QString &channum) +{ + QString loc = LOC + QString("SetChannelByString(%1)").arg(channum); + QString loc_err = loc + ", Error: "; + VERBOSE(VB_CHANNEL, loc); + + ClearDTVInfo(); + + if (!IsOpen() && !Open()) + { + VERBOSE(VB_IMPORTANT, loc_err + "Channel object " + "will not open, can not change channels."); + + return false; + } + + if (m_inputs.size() > 1) + { + QString inputName; + if (!CheckChannel(channum, inputName)) + { + VERBOSE(VB_IMPORTANT, loc_err + + "CheckChannel failed.\n\t\t\tPlease verify the channel " + "in the 'mythtv-setup' Channel Editor."); + + return false; + } + + // If CheckChannel filled in the inputName then we need to + // change inputs and return, since the act of changing + // inputs will change the channel as well. + if (!inputName.isEmpty()) + return SwitchToInput(inputName, channum); + } + + InputMap::const_iterator it = m_inputs.find(m_currentInputID); + if (it == m_inputs.end()) + return false; + + uint mplexid_restriction; + if (!IsInputAvailable(m_currentInputID, mplexid_restriction)) + { + VERBOSE(VB_IMPORTANT, loc + " " + QString( + "Requested channel '%1' is on input '%2' " + "which is in a busy input group") + .arg(channum).arg(m_currentInputID)); + + return false; + } + + // Fetch tuning data from the database. + QString tvformat, modulation, freqtable, freqid, si_std; + int finetune; + uint64_t frequency; + int mpeg_prog_num; + uint atsc_major, atsc_minor, mplexid, tsid, netid; + + if (!ChannelUtil::GetChannelData( + (*it)->sourceid, channum, + tvformat, modulation, freqtable, freqid, + finetune, frequency, + si_std, mpeg_prog_num, atsc_major, atsc_minor, tsid, netid, + mplexid, m_commfree)) + { + VERBOSE(VB_IMPORTANT, loc_err + + "Unable to find channel in database."); + + return false; + } + + if (mplexid_restriction && (mplexid != mplexid_restriction)) + { + VERBOSE(VB_IMPORTANT, loc + " " + QString( + "Requested channel '%1' is not available because " + "the tuner is currently in use on another transport.") + .arg(channum)); + + return false; + } + + // If the frequency is zeroed out, don't use it directly. + bool isFrequency = (frequency > 10000000); + bool hasTuneToChan = + !(*it)->tuneToChannel.isEmpty() && (*it)->tuneToChannel != "Undefined"; + + // If we are tuning to a freqid, rather than an actual frequency, + // we may need to set the frequency table to use. + if (!isFrequency || hasTuneToChan) + SetFreqTable(freqtable); + + // Set NTSC, PAL, ATSC, etc. + SetFormat(tvformat); + + // If a tuneToChannel is set make sure we're still on it + if (hasTuneToChan) + Tune((*it)->tuneToChannel, 0); + + // Clear out any old PAT or PMT & save version info + uint version = 0; + if (genPAT) + { + version = (genPAT->Version()+1) & 0x1f; + delete genPAT; genPAT = NULL; + delete genPMT; genPMT = NULL; + } + + bool ok = true; + if ((*it)->externalChanger.isEmpty()) + { + if ((*it)->name.contains("composite", Qt::CaseInsensitive) || + (*it)->name.contains("s-video", Qt::CaseInsensitive)) + { + VERBOSE(VB_GENERAL, LOC_WARN + "You have not set " + "an external channel changing" + "\n\t\t\tscript for a composite or s-video " + "input. Channel changing will do nothing."); + } + else if (isFrequency && Tune(frequency, "")) + { + } + else if (isFrequency) + { + // Initialize basic the tuning parameters + DTVMultiplex tuning; + if (!mplexid || !tuning.FillFromDB(tunerType, mplexid)) + { + VERBOSE(VB_IMPORTANT, loc_err + + "Failed to initialize multiplex options"); + ok = false; + } + else + { + // Try to fix any problems with the multiplex + CheckOptions(tuning); + + // Tune to proper multiplex + if (!Tune(tuning, (*it)->name)) + { + VERBOSE(VB_IMPORTANT, loc_err + "Tuning to frequency."); + + ClearDTVInfo(); + ok = false; + } + } + } + else + { + ok = Tune(freqid, finetune); + } + } + + VERBOSE(VB_CHANNEL, loc + " " + ((ok) ? "success" : "failure")); + + if (!ok) + return false; + + if (atsc_minor || (mpeg_prog_num>=0)) + { + SetSIStandard(si_std); + SetDTVInfo(atsc_major, atsc_minor, netid, tsid, mpeg_prog_num); + } + else if (IsPIDTuningSupported()) + { + // We need to pull the pid_cache since there are no tuning tables + pid_cache_t pid_cache; + int chanid = ChannelUtil::GetChanID((*it)->sourceid, channum); + ChannelUtil::GetCachedPids(chanid, pid_cache); + if (pid_cache.empty()) + { + VERBOSE(VB_IMPORTANT, loc_err + "PID cache is empty"); + return false; + } + + // Now we construct the PAT & PMT + vector pnum; pnum.push_back(1); + vector pid; pid.push_back(9999); + genPAT = ProgramAssociationTable::Create(0,version,pnum,pid); + + int pcrpid = -1; + vector pids; + vector types; + pid_cache_t::iterator pit = pid_cache.begin(); + for (; pit != pid_cache.end(); pit++) + { + if (!pit->GetStreamID()) + continue; + pids.push_back(pit->GetPID()); + types.push_back(pit->GetStreamID()); + if (pit->IsPCRPID()) + pcrpid = pit->GetPID(); + if ((pcrpid < 0) && StreamID::IsVideo(pit->GetStreamID())) + pcrpid = pit->GetPID(); + } + if (pcrpid < 0) + pcrpid = pid_cache[0].GetPID(); + + genPMT = ProgramMapTable::Create( + pnum.back(), pid.back(), pcrpid, version, pids, types); + + SetSIStandard("mpeg"); + SetDTVInfo(0,0,0,0,-1); + } + + // Set the current channum to the new channel's channum + m_curchannelname = channum; + + // Setup filters & recording picture attributes for framegrabing recorders + // now that the new curchannelname has been established. + if (m_pParent) + m_pParent->SetVideoFiltersForChannel(GetCurrentSourceID(), channum); + InitPictureAttributes(); + + HandleScript(freqid); + + return ok; +} + +void DTVChannel::HandleScriptEnd(bool ok) +{ + // MAYBE TODO? need to tell signal monitor to throw out any tables + // it saw on the last mux... + + // We do not want to call ChannelBase::HandleScript() as it + // will save the current channel to (*it)->startChanNum +} + +bool DTVChannel::TuneMultiplex(uint mplexid, QString inputname) +{ + DTVMultiplex tuning; + if (!tuning.FillFromDB(tunerType, mplexid)) + return false; + + CheckOptions(tuning); + + return Tune(tuning, inputname); +} diff --git a/mythtv/libs/libmythtv/dtvchannel.h b/mythtv/libs/libmythtv/dtvchannel.h index 938affe65c0..2a824978f1f 100644 --- a/mythtv/libs/libmythtv/dtvchannel.h +++ b/mythtv/libs/libmythtv/dtvchannel.h @@ -19,12 +19,12 @@ using namespace std; #include // MythTV headers -#include "channelbase.h" #include "dtvconfparserhelpers.h" // for DTVTunerType +#include "channelbase.h" +#include "channelutil.h" // for pid_cache_t -typedef pair pid_cache_item_t; -typedef vector pid_cache_t; - +class ProgramAssociationTable; +class ProgramMapTable; class TVRec; /** \class DTVChannel @@ -37,13 +37,39 @@ class DTVChannel : public ChannelBase virtual ~DTVChannel(); // Commands + virtual bool SetChannelByString(const QString &chan); /// \brief To be used by the channel scanner and possibly the EIT scanner. - virtual bool TuneMultiplex(uint mplexid, QString inputname) = 0; - /// \brief To be used by the channel scanner and possibly the EIT scanner. + virtual bool TuneMultiplex(uint mplexid, QString inputname); + /// \brief This performs the actual frequency tuning and in some cases + /// input switching. + /// + /// In rare cases such as ASI this does nothing since all the channels + /// are in the same MPTS stream on the same input. But generally you + /// will need to implement this when adding support for new hardware. virtual bool Tune(const DTVMultiplex &tuning, QString inputname) = 0; /// \brief Enters power saving mode if the card supports it - virtual bool EnterPowerSavingMode(void) { return true; } + virtual bool EnterPowerSavingMode(void) + { + return true; + } + /// \brief This tunes on the frequency Identification parameter for + /// hardware that supports it. + /// + /// This is only called when there is no frequency set. This is used + /// to implement "Channel Numbers" in analog tuning scenarios and to + /// implement "Virtual Channels" in the OCUR and Firewire tuners. + virtual bool Tune(const QString &freqid, int finetune) + { + (void) freqid; (void) finetune; + return false; + } + + virtual bool Tune(uint64_t frequency, QString inputname) + { + (void) frequency; (void) inputname; + return false; + } // Gets @@ -79,26 +105,27 @@ class DTVChannel : public ChannelBase /// \brief Returns a vector of supported tuning types. virtual vector GetTunerTypes(void) const; - /** \brief Returns cached MPEG PIDs for last tuned channel. - * \param pid_cache List of PIDs with their TableID - * types is returned in pid_cache. - */ - virtual void GetCachedPids(pid_cache_t &pid_cache) const - { (void) pid_cache; } + void GetCachedPids(pid_cache_t &pid_cache) const; DTVChannel *GetMaster(const QString &videodevice); const DTVChannel *GetMaster(const QString &videodevice) const; + /// \brief Returns true if this is the first of a number of multi-rec devs + virtual bool IsMaster(void) const { return false; } + + virtual bool IsPIDTuningSupported(void) const { return false; } + + bool HasGeneratedPAT(void) const { return genPAT != NULL; } + bool HasGeneratedPMT(void) const { return genPMT != NULL; } + const ProgramAssociationTable *GetGeneratedPAT(void) const {return genPAT;} + const ProgramMapTable *GetGeneratedPMT(void) const {return genPMT;} + // Sets /// \brief Sets tuning mode: "mpeg", "dvb", "atsc", etc. void SetTuningMode(const QString &tuningmode); - /** \brief Saves MPEG PIDs to cache to database - * \param pid_cache List of PIDs with their TableID types to be saved. - */ - virtual void SaveCachedPids(const pid_cache_t &pid_cache) const - { (void) pid_cache; } + void SaveCachedPids(const pid_cache_t &pid_cache) const; protected: /// \brief Sets PSIP table standard: MPEG, DVB, ATSC, or OpenCable @@ -107,9 +134,9 @@ class DTVChannel : public ChannelBase uint dvb_orig_netid, uint mpeg_tsid, int mpeg_pnum); void ClearDTVInfo(void) { SetDTVInfo(0, 0, 0, 0, -1); } - - static void GetCachedPids(int chanid, pid_cache_t&); - static void SaveCachedPids(int chanid, const pid_cache_t&); + /// \brief Checks tuning for problems, and tries to fix them. + virtual void CheckOptions(DTVMultiplex &tuning) const {} + virtual void HandleScriptEnd(bool ok); protected: mutable QMutex dtvinfo_lock; @@ -123,6 +150,11 @@ class DTVChannel : public ChannelBase uint currentTransportID; uint currentOriginalNetworkID; + /// This is a generated PAT for RAW pid tuning + ProgramAssociationTable *genPAT; + /// This is a generated PMT for RAW pid tuning + ProgramMapTable *genPMT; + static QMutex master_map_lock; static QMap master_map; }; diff --git a/mythtv/libs/libmythtv/dtvconfparserhelpers.cpp b/mythtv/libs/libmythtv/dtvconfparserhelpers.cpp index 8f0b081f295..d6fcacaa234 100644 --- a/mythtv/libs/libmythtv/dtvconfparserhelpers.cpp +++ b/mythtv/libs/libmythtv/dtvconfparserhelpers.cpp @@ -38,11 +38,12 @@ QString DTVParamHelper::toString(const char *strings[], int index, return strings[index]; } -const int DTVTunerType::kTunerTypeDVBS1 = 0x00; -const int DTVTunerType::kTunerTypeDVBS2 = 0x20; -const int DTVTunerType::kTunerTypeDVBC = 0x01; -const int DTVTunerType::kTunerTypeDVBT = 0x02; -const int DTVTunerType::kTunerTypeATSC = 0x03; +const int DTVTunerType::kTunerTypeDVBS1 = 0x0000; +const int DTVTunerType::kTunerTypeDVBS2 = 0x0020; +const int DTVTunerType::kTunerTypeDVBC = 0x0001; +const int DTVTunerType::kTunerTypeDVBT = 0x0002; +const int DTVTunerType::kTunerTypeATSC = 0x0003; +const int DTVTunerType::kTunerTypeASI = 0x1000; const int DTVTunerType::kTunerTypeUnknown = 0x80000000; static QMutex dtv_tt_canonical_str_lock; @@ -55,6 +56,7 @@ void DTVTunerType::initStr(void) dtv_tt_canonical_str[kTunerTypeDVBC] = "QAM"; dtv_tt_canonical_str[kTunerTypeDVBS1] = "QPSK"; dtv_tt_canonical_str[kTunerTypeDVBS2] = "DVB_S2"; + dtv_tt_canonical_str[kTunerTypeASI] = "ASI"; dtv_tt_canonical_str[kTunerTypeUnknown] = "UNKNOWN"; } @@ -74,6 +76,7 @@ const DTVParamHelperStruct DTVTunerType::parseTable[] = { "OFDM", kTunerTypeDVBT }, { "ATSC", kTunerTypeATSC }, { "DVB_S2", kTunerTypeDVBS2 }, + { "ASI", kTunerTypeASI }, { "UNKNOWN", kTunerTypeUnknown }, { NULL, kTunerTypeUnknown }, }; diff --git a/mythtv/libs/libmythtv/dtvconfparserhelpers.h b/mythtv/libs/libmythtv/dtvconfparserhelpers.h index 2bbd6317be9..e6aa1d20682 100644 --- a/mythtv/libs/libmythtv/dtvconfparserhelpers.h +++ b/mythtv/libs/libmythtv/dtvconfparserhelpers.h @@ -91,6 +91,7 @@ class DTVTunerType : public DTVParamHelper static const int kTunerTypeDVBT; // OFDM static const int kTunerTypeATSC; // 8-VSB, 16-VSB, // QAM-16, QAM-64, QAM-256, QPSK + static const int kTunerTypeASI; // baseband static const int kTunerTypeUnknown; // Note: Just because some cards sold in different regions support the same diff --git a/mythtv/libs/libmythtv/dtvrecorder.cpp b/mythtv/libs/libmythtv/dtvrecorder.cpp index 9e483fb93b1..b0345c13cf4 100644 --- a/mythtv/libs/libmythtv/dtvrecorder.cpp +++ b/mythtv/libs/libmythtv/dtvrecorder.cpp @@ -2,23 +2,39 @@ * DTVRecorder -- base class for Digital Televison recorders * Copyright 2003-2004 by Brandon Beattie, Doug Larrick, * Jason Hoos, and Daniel Thor Kristjansson - * Distributed as part of MythTV under GPL v2 and later. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "ringbuffer.h" -#include "programinfo.h" -#include "mpegtables.h" +#include "atscstreamdata.h" #include "mpegstreamdata.h" +#include "dvbstreamdata.h" #include "dtvrecorder.h" -#include "tv_rec.h" +#include "programinfo.h" #include "mythverbose.h" +#include "mpegtables.h" +#include "ringbuffer.h" +#include "tv_rec.h" extern "C" { extern const uint8_t *ff_find_start_code(const uint8_t *p, const uint8_t *end, uint32_t *state); } -#define LOC QString("DTVRec(%1): ").arg(tvrec->GetCaptureCardNum()) -#define LOC_ERR QString("DTVRec(%1) Error: ").arg(tvrec->GetCaptureCardNum()) +#define LOC QString("DTVRec(%1): ").arg(tvrec->GetCaptureCardNum()) +#define LOC_WARN QString("DTVRec(%1) Warning: ").arg(tvrec->GetCaptureCardNum()) +#define LOC_ERR QString("DTVRec(%1) Error: ").arg(tvrec->GetCaptureCardNum()) const uint DTVRecorder::kMaxKeyFrameDistance = 80; @@ -45,27 +61,48 @@ DTVRecorder::DTVRecorder(TVRec *rec) : _pes_synced(false), _seen_sps(false), // settings - _request_recording(false), _wait_for_keyframe_option(true), _has_written_other_keyframe(false), // state - _recording(false), - _error(false), + _error(), _stream_data(NULL), // TS packet buffer - _buffer(0), _buffer_size(0), // keyframe TS buffer _buffer_packets(false), + // general recorder stuff + _pid_lock(QMutex::Recursive), + _input_pat(NULL), + _input_pmt(NULL), + _has_no_av(false), // statistics + _packet_count(0), + _continuity_error_count(0), _frames_seen_count(0), _frames_written_count(0) { SetPositionMapType(MARK_GOP_BYFRAME); _payload_buffer.reserve(TSPacket::kSize * (50 + 1)); + memset(_stream_id, 0, sizeof(_stream_id)); + memset(_pid_status, 0, sizeof(_pid_status)); + memset(_continuity_counter, 0, sizeof(_continuity_counter)); } DTVRecorder::~DTVRecorder() { + StopRecording(); + SetStreamData(NULL); + + if (_input_pat) + { + delete _input_pat; + _input_pat = NULL; + } + + if (_input_pmt) + { + delete _input_pmt; + _input_pmt = NULL; + } } void DTVRecorder::SetOption(const QString &name, const QString &value) @@ -80,33 +117,23 @@ void DTVRecorder::SetOption(const QString &name, const QString &value) } /** \fn DTVRecorder::SetOption(const QString&,int) - * \brief handles the "wait_for_seqstart" and "pkt_buf_size" options. + * \brief handles the "wait_for_seqstart" option. */ void DTVRecorder::SetOption(const QString &name, int value) { if (name == "wait_for_seqstart") _wait_for_keyframe_option = (value == 1); - else if (name == "pkt_buf_size") - { - if (_request_recording) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + - "Attempt made to resize packet buffer while recording."); - return; - } - int newsize = max(value - (value % TSPacket::kSize), - TSPacket::kSize*50); - unsigned char* newbuf = new unsigned char[newsize]; - if (newbuf) { - memcpy(newbuf, _buffer, min(_buffer_size, newsize)); - memset(newbuf+_buffer_size, 0xFF, max(newsize-_buffer_size, 0)); - _buffer = newbuf; - _buffer_size = newsize; - } - else - VERBOSE(VB_IMPORTANT, LOC_ERR + - "Could not allocate new packet buffer."); - } + else + RecorderBase::SetOption(name, value); +} + +void DTVRecorder::SetOptionsFromProfile(RecordingProfile *profile, + const QString &videodev, + const QString&, const QString&) +{ + SetOption("videodevice", videodev); + DTVRecorder::SetOption("tvformat", gCoreContext->GetSetting("TVFormat")); + SetStrOption(profile, "recordingtype"); } /** \fn DTVRecorder::FinishRecording(void) @@ -142,8 +169,16 @@ void DTVRecorder::ResetForNewFile(void) VERBOSE(VB_RECORD, LOC + "ResetForNewFile(void)"); QMutexLocker locker(&positionMapLock); + // _first_keyframe, _seen_psp and m_h264_parser should + // not be reset here. This will only be called just as + // we're seeing the first packet of a new keyframe for + // writing to the new file and anything that makes the + // recorder think we're waiting on another keyframe will + // send significant amounts of good data to /dev/null. + // -- Daniel Kristjansson 2011-02-26 + _start_code = 0xffffffff; - _first_keyframe =-1; + //_first_keyframe _has_written_other_keyframe = false; _last_keyframe_seen = 0; _last_gop_seen = 0; @@ -152,14 +187,18 @@ void DTVRecorder::ResetForNewFile(void) _video_bytes_remaining = 0; _other_bytes_remaining = 0; //_recording - _error = false; - //_buffer - //_buffer_size + _error = QString(); + + memset(_stream_id, 0, sizeof(_stream_id)); + memset(_pid_status, 0, sizeof(_pid_status)); + memset(_continuity_counter, 0xff, sizeof(_continuity_counter)); + + _packet_count = 0; + _continuity_error_count = 0; _frames_seen_count = 0; _frames_written_count = 0; _pes_synced = false; - _seen_sps = false; - m_h264_parser.Reset(); + //_seen_sps positionMap.clear(); positionMapDelta.clear(); _payload_buffer.clear(); @@ -191,6 +230,23 @@ void DTVRecorder::SetStreamData(MPEGStreamData *data) SetStreamData(); } +void DTVRecorder::SetStreamData(void) +{ + _stream_data->AddMPEGSPListener(this); + _stream_data->AddMPEGListener(this); + + DVBStreamData *dvb = dynamic_cast(_stream_data); + if (dvb) + dvb->AddDVBMainListener(this); + + ATSCStreamData *atsc = dynamic_cast(_stream_data); + + if (atsc && atsc->DesiredMinorChannel()) + atsc->SetDesiredChannel(atsc->DesiredMajorChannel(), + atsc->DesiredMinorChannel()); + else if (_stream_data->DesiredProgram() >= 0) + _stream_data->SetDesiredProgram(_stream_data->DesiredProgram()); +} void DTVRecorder::BufferedWrite(const TSPacket &tspacket) { @@ -856,4 +912,221 @@ VERBOSE(VB_GENERAL, QString("idx: %1, rem: %2").arg(idx).arg(rem) ); #endif } +void DTVRecorder::HandlePAT(const ProgramAssociationTable *_pat) +{ + if (!_pat) + { + VERBOSE(VB_RECORD, LOC + "SetPAT(NULL)"); + return; + } + + QMutexLocker change_lock(&_pid_lock); + + int progNum = _stream_data->DesiredProgram(); + uint pmtpid = _pat->FindPID(progNum); + + if (!pmtpid) + { + VERBOSE(VB_RECORD, LOC + "SetPAT(): " + "Ignoring PAT not containing our desired program..."); + return; + } + + VERBOSE(VB_RECORD, LOC + QString("SetPAT(%1 on 0x%2)") + .arg(progNum).arg(pmtpid,0,16)); + + ProgramAssociationTable *oldpat = _input_pat; + _input_pat = new ProgramAssociationTable(*_pat); + delete oldpat; + + // Listen for the other PMTs for faster channel switching + for (uint i = 0; _input_pat && (i < _input_pat->ProgramCount()); i++) + { + uint pmt_pid = _input_pat->ProgramPID(i); + if (!_stream_data->IsListeningPID(pmt_pid)) + _stream_data->AddListeningPID(pmt_pid, kPIDPriorityLow); + } +} + +void DTVRecorder::HandlePMT(uint progNum, const ProgramMapTable *_pmt) +{ + QMutexLocker change_lock(&_pid_lock); + + if ((int)progNum == _stream_data->DesiredProgram()) + { + VERBOSE(VB_RECORD, LOC + QString("SetPMT(%1)").arg(progNum)); + ProgramMapTable *oldpmt = _input_pmt; + _input_pmt = new ProgramMapTable(*_pmt); + + QString sistandard = GetSIStandard(); + + bool has_no_av = true; + for (uint i = 0; i < _input_pmt->StreamCount() && has_no_av; i++) + { + has_no_av &= !_input_pmt->IsVideo(i, sistandard); + has_no_av &= !_input_pmt->IsAudio(i, sistandard); + } + _has_no_av = has_no_av; + + SetCAMPMT(_input_pmt); + delete oldpmt; + } +} + +void DTVRecorder::HandleSingleProgramPAT(ProgramAssociationTable *pat) +{ + if (!pat) + { + VERBOSE(VB_RECORD, LOC + "HandleSingleProgramPAT(NULL)"); + return; + } + + if (!ringBuffer) + return; + + uint next_cc = (pat->tsheader()->ContinuityCounter()+1)&0xf; + pat->tsheader()->SetContinuityCounter(next_cc); + pat->GetAsTSPackets(_scratch, next_cc); + + for (uint i = 0; i < _scratch.size(); i++) + DTVRecorder::BufferedWrite(_scratch[i]); +} + +void DTVRecorder::HandleSingleProgramPMT(ProgramMapTable *pmt) +{ + if (!pmt) + { + VERBOSE(VB_RECORD, LOC + "HandleSingleProgramPMT(NULL)"); + return; + } + + // collect stream types for H.264 (MPEG-4 AVC) keyframe detection + for (uint i = 0; i < pmt->StreamCount(); i++) + _stream_id[pmt->StreamPID(i)] = pmt->StreamType(i); + + if (!ringBuffer) + return; + + uint next_cc = (pmt->tsheader()->ContinuityCounter()+1)&0xf; + pmt->tsheader()->SetContinuityCounter(next_cc); + pmt->GetAsTSPackets(_scratch, next_cc); + + for (uint i = 0; i < _scratch.size(); i++) + DTVRecorder::BufferedWrite(_scratch[i]); +} + +bool DTVRecorder::ProcessTSPacket(const TSPacket &tspacket) +{ + const uint pid = tspacket.PID(); + + if (pid != 0x1fff) + _packet_count++; + + // Check continuity counter + uint old_cnt = _continuity_counter[pid]; + if ((pid != 0x1fff) && !CheckCC(pid, tspacket.ContinuityCounter())) + { + _continuity_error_count++; + double erate = _continuity_error_count * 100.0 / _packet_count; + VERBOSE(VB_RECORD, LOC_WARN + + QString("PID 0x%1 discontinuity detected ((%2+1)%16!=%3) %4\%") + .arg(pid,0,16).arg(old_cnt,2) + .arg(tspacket.ContinuityCounter(),2) + .arg(erate)); + } + + // Only create fake keyframe[s] if there are no audio/video streams + if (_input_pmt && _has_no_av) + { + _buffer_packets = !FindOtherKeyframes(&tspacket); + } + else + { + // There are audio/video streams. Only write the packet + // if audio/video key-frames have been found + if (_wait_for_keyframe_option && _first_keyframe < 0) + return true; + + _buffer_packets = true; + } + + BufferedWrite(tspacket); + + return true; +} + +bool DTVRecorder::ProcessVideoTSPacket(const TSPacket &tspacket) +{ + if (!ringBuffer) + return true; + + uint streamType = _stream_id[tspacket.PID()]; + + // Check for keyframes and count frames + if (streamType == StreamID::H264Video) + { + _buffer_packets = !FindH264Keyframes(&tspacket); + if (_wait_for_keyframe_option && !_seen_sps) + return true; + } + else + { + _buffer_packets = !FindMPEG2Keyframes(&tspacket); + } + + return ProcessAVTSPacket(tspacket); +} + +bool DTVRecorder::ProcessAudioTSPacket(const TSPacket &tspacket) +{ + if (!ringBuffer) + return true; + + _buffer_packets = !FindAudioKeyframes(&tspacket); + return ProcessAVTSPacket(tspacket); +} + +/// Common code for processing either audio or video packets +bool DTVRecorder::ProcessAVTSPacket(const TSPacket &tspacket) +{ + const uint pid = tspacket.PID(); + + if (pid != 0x1fff) + _packet_count++; + + // Check continuity counter + uint old_cnt = _continuity_counter[pid]; + if ((pid != 0x1fff) && !CheckCC(pid, tspacket.ContinuityCounter())) + { + _continuity_error_count++; + double erate = _continuity_error_count * 100.0 / _packet_count; + VERBOSE(VB_RECORD, LOC_WARN + + QString("A/V PID 0x%1 discontinuity detected " + "((%2+1)%16!=%3) %4\%") + .arg(pid,0,16).arg(old_cnt).arg(tspacket.ContinuityCounter()) + .arg(erate,5,'f',2)); + } + + // Sync recording start to first keyframe + if (_wait_for_keyframe_option && _first_keyframe < 0) + return true; + + // Sync streams to the first Payload Unit Start Indicator + // _after_ first keyframe iff _wait_for_keyframe_option is true + if (!(_pid_status[pid] & kPayloadStartSeen) && tspacket.HasPayload()) + { + if (!tspacket.PayloadStart()) + return true; // not payload start - drop packet + + VERBOSE(VB_RECORD, + QString("PID 0x%1 Found Payload Start").arg(pid,0,16)); + + _pid_status[pid] |= kPayloadStartSeen; + } + + BufferedWrite(tspacket); + + return true; +} + /* vim: set expandtab tabstop=4 shiftwidth=4: */ diff --git a/mythtv/libs/libmythtv/dtvrecorder.h b/mythtv/libs/libmythtv/dtvrecorder.h index 55da6ecb709..25b8c7ec8ac 100644 --- a/mythtv/libs/libmythtv/dtvrecorder.h +++ b/mythtv/libs/libmythtv/dtvrecorder.h @@ -14,6 +14,7 @@ using namespace std; #include +#include "streamlisteners.h" #include "recorderbase.h" #include "H264Parser.h" @@ -21,7 +22,14 @@ class MPEGStreamData; class TSPacket; class QTime; -class DTVRecorder: public RecorderBase +class DTVRecorder : + public RecorderBase, + public MPEGStreamListener, + public MPEGSingleProgramStreamListener, + public DVBMainStreamListener, + public ATSCMainStreamListener, + public TSPacketListener, + public TSPacketListenerAV { public: DTVRecorder(TVRec *rec); @@ -29,10 +37,11 @@ class DTVRecorder: public RecorderBase virtual void SetOption(const QString &opt, const QString &value); virtual void SetOption(const QString &name, int value); + virtual void SetOptionsFromProfile( + RecordingProfile *profile, const QString &videodev, + const QString&, const QString&); - virtual void StopRecording(void) { _request_recording = false; } - bool IsRecording(void) { return _recording; } - bool IsErrored(void) { return _error; } + bool IsErrored(void) { return !_error.isEmpty(); } long long GetFramesWritten(void) { return _frames_written_count; } @@ -41,12 +50,42 @@ class DTVRecorder: public RecorderBase int GetVideoFd(void) { return _stream_fd; } virtual void SetNextRecording(const ProgramInfo*, RingBuffer*); - virtual void SetStreamData(void) = 0; + virtual void SetStreamData(void); void SetStreamData(MPEGStreamData* sd); MPEGStreamData *GetStreamData(void) const { return _stream_data; } virtual void Reset(); + // MPEG Stream Listener + void HandlePAT(const ProgramAssociationTable*); + void HandleCAT(const ConditionalAccessTable*) {} + void HandlePMT(uint pid, const ProgramMapTable*); + void HandleEncryptionStatus(uint /*pnum*/, bool /*encrypted*/) { } + + // MPEG Single Program Stream Listener + void HandleSingleProgramPAT(ProgramAssociationTable *pat); + void HandleSingleProgramPMT(ProgramMapTable *pmt); + + // ATSC Main + void HandleSTT(const SystemTimeTable*) { UpdateCAMTimeOffset(); } + void HandleVCT(uint /*tsid*/, const VirtualChannelTable*) {} + void HandleMGT(const MasterGuideTable*) {} + + // DVBMainStreamListener + void HandleTDT(const TimeDateTable*) { UpdateCAMTimeOffset(); } + void HandleNIT(const NetworkInformationTable*) {} + void HandleSDT(uint /*tsid*/, const ServiceDescriptionTable*) {} + + // TSPacketListener + bool ProcessTSPacket(const TSPacket &tspacket); + + // TSPacketListenerAV + bool ProcessVideoTSPacket(const TSPacket& tspacket); + bool ProcessAudioTSPacket(const TSPacket& tspacket); + + // Common audio/visual processing + bool ProcessAVTSPacket(const TSPacket &tspacket); + protected: void FinishRecording(void); void ResetForNewFile(void); @@ -71,6 +110,12 @@ class DTVRecorder: public RecorderBase // For handling other (non audio/video) packets bool FindOtherKeyframes(const TSPacket *tspacket); + inline bool CheckCC(uint pid, uint cc); + + virtual QString GetSIStandard(void) const { return "mpeg"; } + virtual void SetCAMPMT(const ProgramMapTable*) {} + virtual void UpdateCAMTimeOffset(void) {} + // file handle for stream int _stream_fd; @@ -92,30 +137,36 @@ class DTVRecorder: public RecorderBase bool _seen_sps; H264Parser m_h264_parser; - /// True if API call has requested a recording be [re]started - bool _request_recording; /// Wait for the a GOP/SEQ-start before sending data bool _wait_for_keyframe_option; bool _has_written_other_keyframe; // state tracking variables - /// True iff recording is actually being performed - bool _recording; - /// True iff irrecoverable recording error detected - bool _error; + /// non-empty iff irrecoverable recording error detected + QString _error; MPEGStreamData *_stream_data; - // packet buffer - unsigned char* _buffer; - int _buffer_size; - // keyframe finding buffer bool _buffer_packets; vector _payload_buffer; - // statistics + // general recorder stuff + mutable QMutex _pid_lock; + ProgramAssociationTable *_input_pat; ///< PAT on input side + ProgramMapTable *_input_pmt; ///< PMT on input side + bool _has_no_av; + + // TS recorder stuff + unsigned char _stream_id[0x1fff + 1]; + unsigned char _pid_status[0x1fff + 1]; + unsigned char _continuity_counter[0x1fff + 1]; + vector _scratch; + + // Statistics + mutable unsigned long long _packet_count; + mutable unsigned long long _continuity_error_count; unsigned long long _frames_seen_count; unsigned long long _frames_written_count; @@ -124,6 +175,18 @@ class DTVRecorder: public RecorderBase /// detected keyframe exceeds this value, then we begin marking /// random regular frames as keyframes. static const uint kMaxKeyFrameDistance; + static const unsigned char kPayloadStartSeen = 0x2; }; +inline bool DTVRecorder::CheckCC(uint pid, uint new_cnt) +{ + bool ok = ((((_continuity_counter[pid] + 1) & 0xf) == new_cnt) || + (_continuity_counter[pid] == new_cnt) || + (_continuity_counter[pid] == 0xFF)); + + _continuity_counter[pid] = new_cnt & 0xf; + + return ok; +} + #endif // DTVRECORDER_H diff --git a/mythtv/libs/libmythtv/dtvsignalmonitor.cpp b/mythtv/libs/libmythtv/dtvsignalmonitor.cpp index 637de6dd0b5..c3914559e82 100644 --- a/mythtv/libs/libmythtv/dtvsignalmonitor.cpp +++ b/mythtv/libs/libmythtv/dtvsignalmonitor.cpp @@ -25,7 +25,6 @@ DTVSignalMonitor::DTVSignalMonitor(int db_cardnum, uint64_t wait_for_mask) : SignalMonitor(db_cardnum, _channel, wait_for_mask), stream_data(NULL), - channelTuned(QObject::tr("Channel Tuned"), "tuned", 3, true, 0, 3, 0), seenPAT(QObject::tr("Seen")+" PAT", "seen_pat", 1, true, 0, 1, 0), seenPMT(QObject::tr("Seen")+" PMT", "seen_pmt", 1, true, 0, 1, 0), seenMGT(QObject::tr("Seen")+" MGT", "seen_mgt", 1, true, 0, 1, 0), @@ -65,12 +64,6 @@ QStringList DTVSignalMonitor::GetStatusList(bool kick) QStringList list = SignalMonitor::GetStatusList(kick); QMutexLocker locker(&statusLock); - // tuned? - if (flags & kSigMon_Tuned) - { - list< eit_pids; - SignalMonitorValue channelTuned; SignalMonitorValue seenPAT; SignalMonitorValue seenPMT; SignalMonitorValue seenMGT; diff --git a/mythtv/libs/libmythtv/dummychannel.h b/mythtv/libs/libmythtv/dummychannel.h index 143cff38e62..25ed2367feb 100644 --- a/mythtv/libs/libmythtv/dummychannel.h +++ b/mythtv/libs/libmythtv/dummychannel.h @@ -28,7 +28,7 @@ class DummyChannel : public ChannelBase // Sets bool SetChannelByString(const QString &chan) - { m_curchannelname = chan; return true; } + { m_curchannelname = chan; return true; } void SetExternalChanger(void) { return; } // Gets @@ -37,11 +37,6 @@ class DummyChannel : public ChannelBase QString GetCurrentInput(void) const { return curinputname; } uint GetCurrentSourceID(void) const { return 0; } - - // Commands - bool SelectInput(const QString &inputname, const QString &chan, bool use_sm) - { curinputname = inputname; m_curchannelname = chan; return true; } - private: QString curinputname; }; diff --git a/mythtv/libs/libmythtv/dvbchannel.cpp b/mythtv/libs/libmythtv/dvbchannel.cpp index 61e8d47f3f8..278cdc1a9db 100644 --- a/mythtv/libs/libmythtv/dvbchannel.cpp +++ b/mythtv/libs/libmythtv/dvbchannel.cpp @@ -100,7 +100,8 @@ DVBChannel::DVBChannel(const QString &aDevice, TVRec *parent) DVBChannel::~DVBChannel() { - Close(); + if (IsOpen()) + Close(); if (dvbcam && !master) { @@ -182,8 +183,6 @@ bool DVBChannel::Open(DVBChannel *who) return false; } - nextInputID = m_currentInputID; - return true; } @@ -253,8 +252,6 @@ bool DVBChannel::Open(DVBChannel *who) return false; } - nextInputID = m_currentInputID; - if (fd_frontend >= 0) is_open[who] = true; @@ -275,159 +272,32 @@ bool DVBChannel::Init(QString &inputname, QString &startchannel, bool setchan) return ChannelBase::Init(inputname, startchannel, setchan); } -// documented in dtvchannel.h -bool DVBChannel::TuneMultiplex(uint mplexid, QString inputname) -{ - DTVMultiplex tuning; - if (!tuning.FillFromDB(tunerType, mplexid)) - return false; - - CheckOptions(tuning); - - return Tune(tuning, inputname); -} - -bool DVBChannel::SetChannelByString(const QString &channum) -{ - QString tmp = QString("SetChannelByString(%1): ").arg(channum); - QString loc = LOC + tmp; - QString loc_err = LOC_ERR + tmp; - - VERBOSE(VB_CHANNEL, loc); - - if (!IsOpen()) - { - VERBOSE(VB_IMPORTANT, loc_err + "Channel object " - "will not open, cannot change channels."); - - ClearDTVInfo(); - return false; - } - - ClearDTVInfo(); - - QString inputName; - if (!CheckChannel(channum, inputName)) - { - VERBOSE(VB_IMPORTANT, loc_err + - "CheckChannel failed.\n\t\t\tPlease verify the channel " - "in the 'mythtv-setup' Channel Editor."); - - return false; - } - - // If CheckChannel filled in the inputName we need to change inputs. - if (!inputName.isEmpty() && (nextInputID == m_currentInputID)) - nextInputID = GetInputByName(inputName); - - // Get the input data for the channel - int inputid = (nextInputID >= 0) ? nextInputID : m_currentInputID; - InputMap::const_iterator it = m_inputs.find(inputid); - if (it == m_inputs.end()) - return false; - - uint mplexid_restriction; - if (!IsInputAvailable(inputid, mplexid_restriction)) - { - VERBOSE(VB_IMPORTANT, loc_err + "Input is not available"); - return false; - } - - // Get the input data for the channel - QString tvformat, modulation, freqtable, freqid, si_std; - int finetune; - uint64_t frequency; - int mpeg_prog_num; - uint atsc_major, atsc_minor, mplexid, tsid, netid; - - if (!ChannelUtil::GetChannelData( - (*it)->sourceid, channum, - tvformat, modulation, freqtable, freqid, - finetune, frequency, - si_std, mpeg_prog_num, atsc_major, atsc_minor, tsid, netid, - mplexid, m_commfree)) - { - VERBOSE(VB_IMPORTANT, loc_err + - "Unable to find channel in database."); - - return false; - } - - if (mplexid_restriction && (mplexid != mplexid_restriction)) - { - VERBOSE(VB_IMPORTANT, loc_err + "Multiplex is not available"); - return false; - } - - // Initialize basic the tuning parameters - DTVMultiplex tuning; - if (!mplexid || !tuning.FillFromDB(tunerType, mplexid)) - { - VERBOSE(VB_IMPORTANT, loc_err + - "Failed to initialize multiplex options"); - - return false; - } - - SetDTVInfo(atsc_major, atsc_minor, netid, tsid, mpeg_prog_num); - - // Try to fix any problems with the multiplex - CheckOptions(tuning); - - if (!Tune(tuning, inputid)) - { - VERBOSE(VB_IMPORTANT, loc_err + "Tuning to frequency."); - - ClearDTVInfo(); - return false; - } - - QString tmpX = channum; tmpX.detach(); - m_curchannelname = tmpX; - - VERBOSE(VB_CHANNEL, loc + "Tuned to frequency."); - - m_currentInputID = nextInputID; - QString tmpY = m_curchannelname; tmpY.detach(); - m_inputs[m_currentInputID]->startChanNum = tmpY; - - return true; -} - -bool DVBChannel::SelectInput(const QString &inputname, const QString &chan, - bool use_sm) +bool DVBChannel::SwitchToInput(const QString &inputname, const QString &chan) { int input = GetInputByName(inputname); + bool ok = false; if (input >= 0) { - nextInputID = input; - SelectChannel(chan, use_sm); + m_currentInputID = input; + ok = SetChannelByString(chan); } else { VERBOSE(VB_IMPORTANT, QString("DVBChannel: Could not find input: %1 on card when " "setting channel %2\n").arg(inputname).arg(chan)); - return false; } - return true; + return ok; } bool DVBChannel::SwitchToInput(int newInputNum, bool setstarting) { - (void)setstarting; - - InputMap::const_iterator it = m_inputs.find(newInputNum); - if (it == m_inputs.end() || (*it)->startChanNum.isEmpty()) - return false; - - uint mplexid_restriction; - if (!IsInputAvailable(m_currentInputID, mplexid_restriction)) + if (!ChannelBase::SwitchToInput(newInputNum, false)) return false; - nextInputID = newInputNum; - + m_currentInputID = newInputNum; + InputMap::const_iterator it = m_inputs.find(m_currentInputID); return SetChannelByString((*it)->startChanNum); } @@ -449,9 +319,6 @@ void DVBChannel::CheckFrequency(uint64_t frequency) const } } -/** \fn DVBChannel::CheckOptions(DTVMultiplex&) const - * \brief Checks tuning for problems, and tries to fix them. - */ void DVBChannel::CheckOptions(DTVMultiplex &tuning) const { if ((tuning.inversion == DTVInversion::kInversionAuto) && @@ -607,7 +474,8 @@ void DVBChannel::SetTimeOffset(double offset) bool DVBChannel::Tune(const DTVMultiplex &tuning, QString inputname) { - int inputid = inputname.isEmpty() ? m_currentInputID : GetInputByName(inputname); + int inputid = inputname.isEmpty() ? + m_currentInputID : GetInputByName(inputname); if (inputid < 0) { VERBOSE(VB_IMPORTANT, LOC_ERR + QString("Tune(): Invalid input '%1'.") @@ -1038,20 +906,6 @@ int DVBChannel::GetChanID() const return query.value(0).toInt(); } -void DVBChannel::SaveCachedPids(const pid_cache_t &pid_cache) const -{ - int chanid = GetChanID(); - if (chanid > 0) - DTVChannel::SaveCachedPids(chanid, pid_cache); -} - -void DVBChannel::GetCachedPids(pid_cache_t &pid_cache) const -{ - int chanid = GetChanID(); - if (chanid > 0) - DTVChannel::GetCachedPids(chanid, pid_cache); -} - const DiSEqCDevRotor *DVBChannel::GetRotor(void) const { if (diseqc_tree) diff --git a/mythtv/libs/libmythtv/dvbchannel.h b/mythtv/libs/libmythtv/dvbchannel.h index ddc483707d3..ab5a2ebfbea 100644 --- a/mythtv/libs/libmythtv/dvbchannel.h +++ b/mythtv/libs/libmythtv/dvbchannel.h @@ -78,26 +78,15 @@ class DVBChannel : public DTVChannel double GetUncorrectedBlockCount(bool *ok = NULL) const; // Commands -#if 0 bool SwitchToInput(const QString &inputname, const QString &chan); -#else - bool SelectInput(const QString &inputname, const QString &chan, - bool use_sm); -#endif bool SwitchToInput(int newcapchannel, bool setstarting); - bool SetChannelByString(const QString &chan); bool Tune(const DTVMultiplex &tuning, QString inputname); bool Tune(const DTVMultiplex &tuning, uint inputid, bool force_reset = false, bool same_input = false); - bool TuneMultiplex(uint mplexid, QString inputname); bool Retune(void); bool ProbeTuningParams(DTVMultiplex &tuning) const; - // PID caching - void SaveCachedPids(const pid_cache_t&) const; - void GetCachedPids(pid_cache_t&) const; - private: bool Open(DVBChannel*); void Close(DVBChannel*); @@ -146,7 +135,6 @@ class DVBChannel : public DTVChannel int fd_frontend; ///< File descriptor for tuning hardware QString device; ///< DVB Device bool has_crc_bug; ///< true iff our driver munges PMT - int nextInputID; ///< Signal an input change }; #endif diff --git a/mythtv/libs/libmythtv/dvbrecorder.cpp b/mythtv/libs/libmythtv/dvbrecorder.cpp index f94d3721fa4..893b9c83997 100644 --- a/mythtv/libs/libmythtv/dvbrecorder.cpp +++ b/mythtv/libs/libmythtv/dvbrecorder.cpp @@ -1,30 +1,10 @@ /* * Class DVBRecorder * - * Copyright (C) Kenneth Aafloy 2003 + * Copyright (C) Daniel Thor Kristjansson 2010 * - * Description: - * Has the responsibility of opening the Demux device and reading - * data from it. Code for controlling which of the mpeg2 streams - * from the DVB device gets through. In ProcessData there is - * a 'map builder' which saves information about the stream - * to the database. - * - * Author(s): - * Isaac Richards - * - Wrote the original class this work derived from. - * Kenneth Aafloy (ke-aa at frisurf.no) - * - Rewritten Recording Functions. - * - Moved PID handling here and rewritten. - * Ben Bucksch - * - Developed the original implementation. - * Dave Chapman (dave at dchapman.com) - * - The dvbstream library, which some code, - * in ::StartRecording, is based upon. - * Martin Smith (martin at spamcop.net) - * - The signal quality monitor - * David Matthews (dm at prolingua.co.uk) - * - Added video stream for radio channels + * This class glues the DVBStreamHandler which handles the DVB devices + * to the DTVRecorder that handles recordings in MythTV. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -41,49 +21,13 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - -// POSIX includes -#include -#include -#include -#include - -// C++ includes -#include -#include -using namespace std; - // MythTV includes -#include "ringbuffer.h" -#include "programinfo.h" -#include "mpegtables.h" -#include "iso639.h" -#include "dvbstreamdata.h" -#include "atscstreamdata.h" -#include "atsctables.h" -#include "cardutil.h" -#include "tv_rec.h" - -// MythTV DVB includes -#include "dvbtypes.h" -#include "dvbchannel.h" -#include "dvbrecorder.h" #include "dvbstreamhandler.h" - -// AVLib/FFMPEG includes -extern "C" { -#include "libavcodec/avcodec.h" -#include "libavformat/avformat.h" -#include "libavformat/mpegts.h" -} - -const int DVBRecorder::TSPACKETS_BETWEEN_PSIP_SYNC = 20000; -const int DVBRecorder::POLL_INTERVAL = 50; // msec -const int DVBRecorder::POLL_WARNING_TIMEOUT = 500; // msec -const unsigned char DVBRecorder::kPayloadStartSeen; - -#define TS_TICKS_PER_SEC 90000 -#define DUMMY_VIDEO_PID VIDEO_PID(0x20) +#include "mpegstreamdata.h" +#include "dvbrecorder.h" +#include "dvbchannel.h" +#include "tv_rec.h" +#include "mythverbose.h" #define LOC QString("DVBRec(%1:%2): ") \ .arg(tvrec->GetCaptureCardNum()).arg(videodevice) @@ -92,182 +36,10 @@ const unsigned char DVBRecorder::kPayloadStartSeen; #define LOC_ERR QString("DVBRec(%1:%2) Error: ") \ .arg(tvrec->GetCaptureCardNum()).arg(videodevice) -DVBRecorder::DVBRecorder(TVRec *rec, DVBChannel* advbchannel) - : DTVRecorder(rec), - // DVB stuff - dvbchannel(advbchannel), - _stream_handler(NULL), - _pid_lock(QMutex::Recursive), - _input_pat(NULL), - _input_pmt(NULL), - _has_no_av(false), - // Statistics - _continuity_error_count(0), _stream_overflow_count(0), - _bad_packet_count(0) +DVBRecorder::DVBRecorder(TVRec *rec, DVBChannel *channel) + : DTVRecorder(rec), _channel(channel), _stream_handler(NULL) { videodevice = QString::null; - - _buffer_size = (1024*1024 / TSPacket::kSize) * TSPacket::kSize; - - _buffer = new unsigned char[_buffer_size]; - memset(_buffer, 0, _buffer_size); - memset(_stream_id, 0, sizeof(_stream_id)); - memset(_pid_status, 0, sizeof(_pid_status)); - memset(_continuity_counter, 0, sizeof(_continuity_counter)); -} - -DVBRecorder::~DVBRecorder() -{ - TeardownAll(); -} - -void DVBRecorder::TeardownAll(void) -{ - // Make SURE that the device read thread is cleaned up -- John Poet - StopRecording(); - - if (IsOpen()) - Close(); - - if (_buffer) - { - delete[] _buffer; - _buffer = NULL; - } - - if (_input_pat) - { - delete _input_pat; - _input_pat = NULL; - } - - if (_input_pmt) - { - delete _input_pmt; - _input_pmt = NULL; - } -} - -void DVBRecorder::SetOptionsFromProfile(RecordingProfile *profile, - const QString &videodev, - const QString&, const QString&) -{ - SetOption("videodevice", videodev); - DTVRecorder::SetOption("tvformat", gCoreContext->GetSetting("TVFormat")); - SetStrOption(profile, "recordingtype"); -} - -void DVBRecorder::HandleSingleProgramPAT(ProgramAssociationTable *pat) -{ - if (!pat) - { - VERBOSE(VB_RECORD, LOC + "HandleSingleProgramPAT(NULL)"); - return; - } - - if (!ringBuffer) - return; - - uint next_cc = (pat->tsheader()->ContinuityCounter()+1)&0xf; - pat->tsheader()->SetContinuityCounter(next_cc); - pat->GetAsTSPackets(_scratch, next_cc); - - for (uint i = 0; i < _scratch.size(); i++) - DTVRecorder::BufferedWrite(_scratch[i]); -} - -void DVBRecorder::HandleSingleProgramPMT(ProgramMapTable *pmt) -{ - if (!pmt) - { - VERBOSE(VB_RECORD, LOC + "HandleSingleProgramPMT(NULL)"); - return; - } - - // collect stream types for H.264 (MPEG-4 AVC) keyframe detection - for (uint i = 0; i < pmt->StreamCount(); i++) - _stream_id[pmt->StreamPID(i)] = pmt->StreamType(i); - - if (!ringBuffer) - return; - - uint next_cc = (pmt->tsheader()->ContinuityCounter()+1)&0xf; - pmt->tsheader()->SetContinuityCounter(next_cc); - pmt->GetAsTSPackets(_scratch, next_cc); - - for (uint i = 0; i < _scratch.size(); i++) - DTVRecorder::BufferedWrite(_scratch[i]); -} - -void DVBRecorder::HandlePAT(const ProgramAssociationTable *_pat) -{ - if (!_pat) - { - VERBOSE(VB_RECORD, LOC + "SetPAT(NULL)"); - return; - } - - QMutexLocker change_lock(&_pid_lock); - - int progNum = _stream_data->DesiredProgram(); - uint pmtpid = _pat->FindPID(progNum); - - if (!pmtpid) - { - VERBOSE(VB_RECORD, LOC + "SetPAT(): " - "Ignoring PAT not containing our desired program..."); - return; - } - - VERBOSE(VB_RECORD, LOC + QString("SetPAT(%1 on 0x%2)") - .arg(progNum).arg(pmtpid,0,16)); - - ProgramAssociationTable *oldpat = _input_pat; - _input_pat = new ProgramAssociationTable(*_pat); - delete oldpat; - - // Listen for the other PMTs for faster channel switching - for (uint i = 0; _input_pat && (i < _input_pat->ProgramCount()); i++) - { - uint pmt_pid = _input_pat->ProgramPID(i); - if (!_stream_data->IsListeningPID(pmt_pid)) - _stream_data->AddListeningPID(pmt_pid, kPIDPriorityLow); - } -} - -void DVBRecorder::HandlePMT(uint progNum, const ProgramMapTable *_pmt) -{ - QMutexLocker change_lock(&_pid_lock); - - if ((int)progNum == _stream_data->DesiredProgram()) - { - VERBOSE(VB_RECORD, LOC + QString("SetPMT(%1)").arg(progNum)); - ProgramMapTable *oldpmt = _input_pmt; - _input_pmt = new ProgramMapTable(*_pmt); - - QString sistandard = dvbchannel->GetSIStandard(); - - bool has_no_av = true; - for (uint i = 0; i < _input_pmt->StreamCount() && has_no_av; i++) - { - has_no_av &= !_input_pmt->IsVideo(i, sistandard); - has_no_av &= !_input_pmt->IsAudio(i, sistandard); - } - _has_no_av = has_no_av; - - dvbchannel->SetPMT(_input_pmt); - delete oldpmt; - } -} - -void DVBRecorder::HandleSTT(const SystemTimeTable*) -{ - dvbchannel->SetTimeOffset(GetStreamData()->TimeOffset()); -} - -void DVBRecorder::HandleTDT(const TimeDateTable*) -{ - dvbchannel->SetTimeOffset(GetStreamData()->TimeOffset()); } bool DVBRecorder::Open(void) @@ -287,55 +59,45 @@ bool DVBRecorder::Open(void) _stream_handler = DVBStreamHandler::Get(videodevice); - VERBOSE(VB_RECORD, LOC + QString("Card opened successfully fd(%1)") - .arg(_stream_fd)); + VERBOSE(VB_RECORD, LOC + "Card opened successfully"); return true; } -void DVBRecorder::Close(void) +bool DVBRecorder::IsOpen(void) const { - VERBOSE(VB_RECORD, LOC + QString("Close() fd(%1) -- begin").arg(_stream_fd)); - - DVBStreamHandler::Return(_stream_handler); - - VERBOSE(VB_RECORD, LOC + QString("Close() fd(%1) -- end").arg(_stream_fd)); + return (NULL != _stream_handler); } -void DVBRecorder::SetStreamData(void) +void DVBRecorder::Close(void) { - _stream_data->AddMPEGSPListener(this); - _stream_data->AddMPEGListener(this); + VERBOSE(VB_RECORD, LOC + "Close() -- begin"); - DVBStreamData *dvb = dynamic_cast(_stream_data); - if (dvb) - dvb->AddDVBMainListener(this); - - ATSCStreamData *atsc = dynamic_cast(_stream_data); + DVBStreamHandler::Return(_stream_handler); - if (atsc && atsc->DesiredMinorChannel()) - atsc->SetDesiredChannel(atsc->DesiredMajorChannel(), - atsc->DesiredMinorChannel()); - else if (_stream_data->DesiredProgram() >= 0) - _stream_data->SetDesiredProgram(_stream_data->DesiredProgram()); + VERBOSE(VB_RECORD, LOC + "Close() -- end"); } void DVBRecorder::StartRecording(void) { if (!Open()) { - _error = true; + _error = "Failed to open DVB device"; + VERBOSE(VB_IMPORTANT, LOC_ERR + _error); return; } _continuity_error_count = 0; - _stream_overflow_count = 0; - _request_recording = true; - _recording = true; + { + QMutexLocker locker(&pauseLock); + request_recording = true; + recording = true; + recordingWait.wakeAll(); + } // Listen for time table on DVB standard streams - if (dvbchannel && (dvbchannel->GetSIStandard() == "dvb")) + if (_channel && (_channel->GetSIStandard() == "dvb")) _stream_data->AddListeningPID(DVB_TDT_PID); // Make sure the first things in the file are a PAT & PMT @@ -349,13 +111,19 @@ void DVBRecorder::StartRecording(void) _stream_data->AddWritingListener(this); _stream_handler->AddListener(_stream_data, false, true); - while (_request_recording && !_error) + while (IsRecordingRequested() && !IsErrored()) { - usleep(50000); - if (PauseAndWait()) continue; + { // sleep 100 milliseconds unless StopRecording() or Unpause() + // is called, just to avoid running this too often. + QMutexLocker locker(&pauseLock); + if (!request_recording || request_pause) + continue; + unpauseWait.wait(&pauseLock, 100); + } + if (!_input_pmt) { VERBOSE(VB_GENERAL, LOC_WARN + @@ -366,10 +134,8 @@ void DVBRecorder::StartRecording(void) if (!_stream_handler->IsRunning()) { - _error = true; - - VERBOSE(VB_IMPORTANT, LOC_ERR + - "Stream handler died unexpectedly."); + _error = "Stream handler died unexpectedly."; + VERBOSE(VB_IMPORTANT, LOC_ERR + _error); } } @@ -381,27 +147,9 @@ void DVBRecorder::StartRecording(void) FinishRecording(); - _recording = false; -} - -void DVBRecorder::ResetForNewFile(void) -{ - DTVRecorder::ResetForNewFile(); - - memset(_stream_id, 0, sizeof(_stream_id)); - memset(_pid_status, 0, sizeof(_pid_status)); - memset(_continuity_counter, 0xff, sizeof(_continuity_counter)); -} - -void DVBRecorder::StopRecording(void) -{ - _request_recording = false; - while (_recording) - usleep(2000); -} - -void DVBRecorder::ReaderPaused(int /*fd*/) -{ + QMutexLocker locker(&pauseLock); + recording = false; + recordingWait.wakeAll(); } bool DVBRecorder::PauseAndWait(int timeout) @@ -432,128 +180,19 @@ bool DVBRecorder::PauseAndWait(int timeout) return IsPaused(true); } -bool DVBRecorder::ProcessVideoTSPacket(const TSPacket &tspacket) +QString DVBRecorder::GetSIStandard(void) const { - if (!ringBuffer) - return true; - - uint streamType = _stream_id[tspacket.PID()]; - - // Check for keyframes and count frames - if (streamType == StreamID::H264Video) - { - _buffer_packets = !FindH264Keyframes(&tspacket); - if (!_seen_sps) - return true; - } - else - { - _buffer_packets = !FindMPEG2Keyframes(&tspacket); - } - - return ProcessAVTSPacket(tspacket); + return _channel->GetSIStandard(); } -bool DVBRecorder::ProcessAudioTSPacket(const TSPacket &tspacket) +void DVBRecorder::SetCAMPMT(const ProgramMapTable *pmt) { - if (!ringBuffer) - return true; - - _buffer_packets = !FindAudioKeyframes(&tspacket); - return ProcessAVTSPacket(tspacket); + _channel->SetPMT(pmt); } -/// Common code for processing either audio or video packets -bool DVBRecorder::ProcessAVTSPacket(const TSPacket &tspacket) +void DVBRecorder::UpdateCAMTimeOffset(void) { - const uint pid = tspacket.PID(); - - // Check continuity counter - if ((pid != 0x1fff) && !CheckCC(pid, tspacket.ContinuityCounter())) - { - VERBOSE(VB_RECORD, LOC + - QString("PID 0x%1 discontinuity detected").arg(pid,0,16)); - _continuity_error_count++; - } - - // Sync recording start to first keyframe - if (_wait_for_keyframe_option && _first_keyframe < 0) - return true; - - // Sync streams to the first Payload Unit Start Indicator - // _after_ first keyframe iff _wait_for_keyframe_option is true - if (!(_pid_status[pid] & kPayloadStartSeen) && tspacket.HasPayload()) - { - if (!tspacket.PayloadStart()) - return true; // not payload start - drop packet - - VERBOSE(VB_RECORD, - QString("PID 0x%1 Found Payload Start").arg(pid,0,16)); - - _pid_status[pid] |= kPayloadStartSeen; - } - - BufferedWrite(tspacket); - - return true; + _channel->SetTimeOffset(GetStreamData()->TimeOffset()); } -bool DVBRecorder::ProcessTSPacket(const TSPacket &tspacket) -{ - const uint pid = tspacket.PID(); - - // Check continuity counter - if ((pid != 0x1fff) && !CheckCC(pid, tspacket.ContinuityCounter())) - { - VERBOSE(VB_RECORD, LOC + - QString("PID 0x%1 discontinuity detected").arg(pid,0,16)); - _continuity_error_count++; - } - - // Only create fake keyframe[s] if there are no audio/video streams - if (_input_pmt && _has_no_av) - { - _buffer_packets = !FindOtherKeyframes(&tspacket); - } - else - { - // There are audio/video streams. Only write the packet - // if audio/video key-frames have been found - if (_wait_for_keyframe_option && _first_keyframe < 0) - return true; - - _buffer_packets = true; - } - - BufferedWrite(tspacket); - // return value is not used and there is no doc for the return value - // assume true means the packet is successfully processed - return true; -} - -void DVBRecorder::BufferedWrite(const TSPacket &tspacket) -{ - // Care must be taken to make sure that the packet actually gets written - // as the decision to actually write it has already been made - - // Do we have to buffer the packet for exact keyframe detection? - if (_buffer_packets) - { - int idx = _payload_buffer.size(); - _payload_buffer.resize(idx + TSPacket::kSize); - memcpy(&_payload_buffer[idx], tspacket.data(), TSPacket::kSize); - return; - } - - // We are free to write the packet, but if we have buffered packet[s] - // we have to write them first... - if (!_payload_buffer.empty()) - { - if (ringBuffer) - ringBuffer->Write(&_payload_buffer[0], _payload_buffer.size()); - _payload_buffer.clear(); - } - - if (ringBuffer) - ringBuffer->Write(tspacket.data(), TSPacket::kSize); -} +/* vim: set expandtab tabstop=4 shiftwidth=4: */ diff --git a/mythtv/libs/libmythtv/dvbrecorder.h b/mythtv/libs/libmythtv/dvbrecorder.h index 484432050f9..de0a3dfdb5e 100644 --- a/mythtv/libs/libmythtv/dvbrecorder.h +++ b/mythtv/libs/libmythtv/dvbrecorder.h @@ -1,34 +1,16 @@ // -*- Mode: c++ -*- -/* - * Copyright (C) Kenneth Aafloy 2003 - * - * Copyright notice is in dvbrecorder.cpp of the MythTV project. - */ - -#ifndef DVBRECORDER_H -#define DVBRECORDER_H - -// C++ includes -#include -#include -using namespace std; +#ifndef _DVB_RECORDER_H_ +#define _DVB_RECORDER_H_ // Qt includes -#include +#include +// MythTV includes #include "dtvrecorder.h" -#include "tspacket.h" -#include "DeviceReadBuffer.h" -class QString; -class DVBChannel; -class MPEGStreamData; -class ProgramAssociationTable; -class ProgramMapTable; -class TSPacket; class DVBStreamHandler; - -typedef vector uint_vec_t; +class ProgramMapTable; +class DVBChannel; /** \class DVBRecorder * \brief This is a specialization of DTVRecorder used to @@ -36,114 +18,27 @@ typedef vector uint_vec_t; * * \sa DTVRecorder */ -class DVBRecorder : - public DTVRecorder, - public MPEGStreamListener, - public MPEGSingleProgramStreamListener, - public DVBMainStreamListener, - public ATSCMainStreamListener, - public TSPacketListener, - public TSPacketListenerAV, - private ReaderPausedCB +class DVBRecorder : public DTVRecorder { public: - DVBRecorder(TVRec *rec, DVBChannel* dvbchannel); - ~DVBRecorder(); - - void SetOptionsFromProfile(RecordingProfile *profile, - const QString &videodev, - const QString &audiodev, - const QString &vbidev); + DVBRecorder(TVRec*, DVBChannel*); void StartRecording(void); - void ResetForNewFile(void); - void StopRecording(void); bool Open(void); - bool IsOpen(void) const { return _stream_fd >= 0; } + bool IsOpen(void) const; void Close(void); - // MPEG Stream Listener - void HandlePAT(const ProgramAssociationTable*); - void HandleCAT(const ConditionalAccessTable*) {} - void HandlePMT(uint pid, const ProgramMapTable*); - void HandleEncryptionStatus(uint /*pnum*/, bool /*encrypted*/) { } - - // MPEG Single Program Stream Listener - void HandleSingleProgramPAT(ProgramAssociationTable *pat); - void HandleSingleProgramPMT(ProgramMapTable *pmt); - - // ATSC Main - void HandleSTT(const SystemTimeTable*); - void HandleVCT(uint /*tsid*/, const VirtualChannelTable*) {} - void HandleMGT(const MasterGuideTable*) {} - - // DVBMainStreamListener - void HandleTDT(const TimeDateTable*); - void HandleNIT(const NetworkInformationTable*) {} - void HandleSDT(uint /*tsid*/, const ServiceDescriptionTable*) {} - - // TSPacketListener - bool ProcessTSPacket(const TSPacket &tspacket); - - // TSPacketListenerAV - bool ProcessVideoTSPacket(const TSPacket& tspacket); - bool ProcessAudioTSPacket(const TSPacket& tspacket); - - // Common audio/visual processing - bool ProcessAVTSPacket(const TSPacket &tspacket); - - void SetStreamData(void); - - void BufferedWrite(const TSPacket &tspacket); - private: - void TeardownAll(void); - - inline bool CheckCC(uint pid, uint cc); - - void ReaderPaused(int fd); bool PauseAndWait(int timeout = 100); - private: - // DVB stuff - DVBChannel *dvbchannel; - DVBStreamHandler *_stream_handler; - - // general recorder stuff - mutable QMutex _pid_lock; - ProgramAssociationTable *_input_pat; ///< PAT on input side - ProgramMapTable *_input_pmt; ///< PMT on input side - bool _has_no_av; - - // TS recorder stuff - unsigned char _stream_id[0x1fff + 1]; - unsigned char _pid_status[0x1fff + 1]; - unsigned char _continuity_counter[0x1fff + 1]; - vector _scratch; + QString GetSIStandard(void) const; + void SetCAMPMT(const ProgramMapTable*); + void UpdateCAMTimeOffset(void); - // Statistics - mutable uint _continuity_error_count; - mutable uint _stream_overflow_count; - mutable uint _bad_packet_count; - - // Constants - static const int TSPACKETS_BETWEEN_PSIP_SYNC; - static const int POLL_INTERVAL; - static const int POLL_WARNING_TIMEOUT; - - static const unsigned char kPayloadStartSeen = 0x2; + private: + DVBChannel *_channel; + DVBStreamHandler *_stream_handler; }; - -inline bool DVBRecorder::CheckCC(uint pid, uint new_cnt) -{ - bool ok = ((((_continuity_counter[pid] + 1) & 0xf) == new_cnt) || - (_continuity_counter[pid] == 0xFF)); - - _continuity_counter[pid] = new_cnt & 0xf; - - return ok; -} - -#endif +#endif // _DVB_RECORDER_H_ diff --git a/mythtv/libs/libmythtv/dvbsignalmonitor.cpp b/mythtv/libs/libmythtv/dvbsignalmonitor.cpp index d80e62b8f2a..e554088b43b 100644 --- a/mythtv/libs/libmythtv/dvbsignalmonitor.cpp +++ b/mythtv/libs/libmythtv/dvbsignalmonitor.cpp @@ -210,7 +210,7 @@ DVBChannel *DVBSignalMonitor::GetDVBChannel(void) */ void DVBSignalMonitor::UpdateValues(void) { - if (!monitor_thread.isRunning() || exit) + if (!running || exit) return; if (streamHandlerStarted) @@ -232,9 +232,6 @@ void DVBSignalMonitor::UpdateValues(void) return; } - if (!IsChannelTuned()) - return; - AddFlags(kSigMon_WaitForSig); DVBChannel *dvbchannel = GetDVBChannel(); @@ -312,6 +309,7 @@ void DVBSignalMonitor::UpdateValues(void) kDTVSigMon_WaitForMGT | kDTVSigMon_WaitForVCT | kDTVSigMon_WaitForNIT | kDTVSigMon_WaitForSDT)) { + GetStreamData()->AddListeningPID(MPEG_PAT_PID); streamHandler->AddListener(GetStreamData(), true, false); streamHandlerStarted = true; } diff --git a/mythtv/libs/libmythtv/dvbsignalmonitor.h b/mythtv/libs/libmythtv/dvbsignalmonitor.h index 9b51a1d2de9..356ca76b544 100644 --- a/mythtv/libs/libmythtv/dvbsignalmonitor.h +++ b/mythtv/libs/libmythtv/dvbsignalmonitor.h @@ -25,10 +25,10 @@ class DVBSignalMonitor: public DTVSignalMonitor virtual void SetRotorTarget(float target); virtual void GetRotorStatus(bool &was_moving, bool &is_moving); - virtual void SetRotorValue(int) + virtual void SetRotorValue(int val) { QMutexLocker locker(&statusLock); - rotorPosition.SetValue(100); + rotorPosition.SetValue(val); } virtual void EmitStatus(void); diff --git a/mythtv/libs/libmythtv/dvbstreamhandler.cpp b/mythtv/libs/libmythtv/dvbstreamhandler.cpp index 92791f685da..f8b7b7301ca 100644 --- a/mythtv/libs/libmythtv/dvbstreamhandler.cpp +++ b/mythtv/libs/libmythtv/dvbstreamhandler.cpp @@ -20,9 +20,9 @@ #include "diseqc.h" // for rotor retune #include "mythlogging.h" -#define LOC QString("DVBSH(%1): ").arg(_dvb_dev) -#define LOC_WARN QString("DVBSH(%1) Warning: ").arg(_dvb_dev) -#define LOC_ERR QString("DVBSH(%1) Error: ").arg(_dvb_dev) +#define LOC QString("DVBSH(%1): ").arg(_device) +#define LOC_WARN QString("DVBSH(%1) Warning: ").arg(_device) +#define LOC_ERR QString("DVBSH(%1) Error: ").arg(_device) QMap DVBStreamHandler::_rec_supports_ts_monitoring; QMutex DVBStreamHandler::_rec_supports_ts_monitoring_lock; @@ -31,8 +31,6 @@ QMap DVBStreamHandler::_handlers; QMap DVBStreamHandler::_handlers_refcnt; QMutex DVBStreamHandler::_handlers_lock; -//#define DEBUG_PID_FILTERS - DVBStreamHandler *DVBStreamHandler::Get(const QString &devname) { QMutexLocker locker(&_handlers_lock); @@ -57,7 +55,7 @@ void DVBStreamHandler::Return(DVBStreamHandler * & ref) { QMutexLocker locker(&_handlers_lock); - QString devname = ref->_dvb_dev; + QString devname = ref->_device; QMap::iterator rit = _handlers_refcnt.find(devname); if (rit == _handlers_refcnt.end()) @@ -88,170 +86,35 @@ void DVBStreamHandler::Return(DVBStreamHandler * & ref) } DVBStreamHandler::DVBStreamHandler(const QString &dvb_device) : - _dvb_dev(dvb_device), - _dvr_dev_path(CardUtil::GetDeviceName(DVB_DEV_DVR, _dvb_dev)), - _allow_section_reader(false), - _needs_buffering(false), + StreamHandler(dvb_device), + _dvr_dev_path(CardUtil::GetDeviceName(DVB_DEV_DVR, _device)), _allow_retune(false), - _start_stop_lock(QMutex::Recursive), - _using_section_reader(false), - - _device_read_buffer(NULL), _sigmon(NULL), _dvbchannel(NULL), - - _pid_lock(QMutex::Recursive), - _open_pid_filters(0), - _listener_lock(QMutex::Recursive), - _run(false) -{ -} - -DVBStreamHandler::~DVBStreamHandler() + _drb(NULL) { - if (!_stream_data_list.empty()) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + "dtor & _stream_data_list not empty"); - } -} - -void DVBStreamHandler::AddListener(MPEGStreamData *data, - bool allow_section_reader, - bool needs_buffering) -{ - VERBOSE(VB_RECORD, LOC + QString("AddListener(0x%1) -- begin") - .arg((uint64_t)data,0,16)); - if (!data) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + - QString("AddListener(0x%1) -- null data") - .arg((uint64_t)data,0,16)); - return; - } - - _listener_lock.lock(); - - VERBOSE(VB_RECORD, LOC + QString("AddListener(0x%1) -- locked") - .arg((uint64_t)data,0,16)); - - if (_stream_data_list.empty()) - { - _allow_section_reader = allow_section_reader; - _needs_buffering = needs_buffering; - } - else - { - _allow_section_reader &= allow_section_reader; - _needs_buffering |= needs_buffering; - } - - _stream_data_list.push_back(data); - - _listener_lock.unlock(); - - Start(); - - VERBOSE(VB_RECORD, LOC + QString("AddListener(0x%1) -- end") - .arg((uint64_t)data,0,16)); } -void DVBStreamHandler::RemoveListener(MPEGStreamData *data) +void DVBStreamHandler::SetRunningDesired(bool desired) { - VERBOSE(VB_RECORD, LOC + QString("RemoveListener(0x%1) -- begin") - .arg((uint64_t)data,0,16)); - if (!data) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + - QString("RemoveListener(0x%1) -- null data") - .arg((uint64_t)data,0,16)); - return; - } - - _listener_lock.lock(); - - VERBOSE(VB_RECORD, LOC + QString("RemoveListener(0x%1) -- locked") - .arg((uint64_t)data,0,16)); - - vector::iterator it = - find(_stream_data_list.begin(), _stream_data_list.end(), data); - - if (it != _stream_data_list.end()) - _stream_data_list.erase(it); - - if (_stream_data_list.empty()) - { - _allow_section_reader = false; - - _listener_lock.unlock(); - Stop(); - } - else - { - _listener_lock.unlock(); - } - - VERBOSE(VB_RECORD, LOC + QString("RemoveListener(0x%1) -- end") - .arg((uint64_t)data,0,16)); + if (_drb && _running_desired && !desired) + _drb->Stop(); + StreamHandler::SetRunningDesired(desired); } -void DVBReadThread::run(void) +void DVBStreamHandler::run(void) { - if (!m_parent) - return; - threadRegister("DVBRead"); - m_parent->Run(); - threadDeregister(); -} - -void DVBStreamHandler::Start(void) -{ - QMutexLocker locker(&_start_stop_lock); - - _eit_pids.clear(); - - if (IsRunning() && _using_section_reader && !_allow_section_reader) - Stop(); - - if (IsRunning() && _needs_buffering && !_device_read_buffer) - Stop(); + VERBOSE(VB_RECORD, LOC + "run(): begin"); - if (!IsRunning()) - { - _reader_thread.SetParent(this); - _reader_thread.start(); - - if (!_reader_thread.isRunning()) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + "Start: Failed to create thread."); - return; - } - } -} - -void DVBStreamHandler::Stop(void) -{ - QMutexLocker locker(&_start_stop_lock); - - if (IsRunning()) - { - if (_device_read_buffer) - _device_read_buffer->Stop(); - _run = false; - _reader_thread.wait(); - } -} - -void DVBStreamHandler::Run(void) -{ - _using_section_reader = !SupportsTSMonitoring() && _allow_section_reader; - _run = true; - - if (_using_section_reader) + if (!SupportsTSMonitoring() && _allow_section_reader) RunSR(); else RunTS(); + + VERBOSE(VB_RECORD, LOC + "run(): end"); + threadDeregister(); } /** \fn DVBStreamHandler::RunTS(void) @@ -266,16 +129,6 @@ void DVBStreamHandler::Run(void) */ void DVBStreamHandler::RunTS(void) { - if (_needs_buffering) - _device_read_buffer = new DeviceReadBuffer(this); - - int remainder = 0; - int buffer_size = TSPacket::kSize * 15000; - unsigned char *buffer = new unsigned char[buffer_size]; - if (!buffer) - return; - memset(buffer, 0, buffer_size); - QByteArray dvr_dev_path = _dvr_dev_path.toAscii(); int dvr_fd; for (int tries = 1; ; ++tries) @@ -283,61 +136,83 @@ void DVBStreamHandler::RunTS(void) dvr_fd = open(dvr_dev_path.constData(), O_RDONLY | O_NONBLOCK); if (dvr_fd >= 0) break; + VERBOSE(VB_IMPORTANT, LOC_WARN + QString("Opening DVR device %1 failed : %2") .arg(_dvr_dev_path).arg(strerror(errno))); + if (tries >= 20 || (errno != EBUSY && errno != EAGAIN)) { VERBOSE(VB_IMPORTANT, LOC + QString("Failed to open DVR device %1 : %2") .arg(_dvr_dev_path).arg(strerror(errno))); - delete[] buffer; + _error = true; return; } usleep(50000); } - bool _error = false; - if (_device_read_buffer) + int remainder = 0; + int buffer_size = TSPacket::kSize * 15000; + unsigned char *buffer = new unsigned char[buffer_size]; + if (!buffer) { - bool ok = _device_read_buffer->Setup(_dvb_dev, dvr_fd); + VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to allocate memory"); + close(dvr_fd); + _error = true; + return; + } + bzero(buffer, buffer_size); - if (!ok) + DeviceReadBuffer *drb = NULL; + if (_needs_buffering) + { + drb = new DeviceReadBuffer(this); + if (!drb->Setup(_device, dvr_fd)) { VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to allocate DRB buffer"); - _error = true; + delete drb; delete[] buffer; + close(dvr_fd); + _error = true; return; } - _device_read_buffer->Start(); + drb->Start(); + } + + SetRunning(true, _needs_buffering, false); + { + QMutexLocker locker(&_start_stop_lock); + _drb = drb; } VERBOSE(VB_RECORD, LOC + "RunTS(): begin"); + bool _error = false; fd_set fd_select_set; FD_ZERO( &fd_select_set); FD_SET (dvr_fd, &fd_select_set); - while (_run && !_error) + while (_running_desired && !_error) { RetuneMonitor(); UpdateFiltersFromStreamData(); ssize_t len = 0; - if (_device_read_buffer) + if (drb) { - len = _device_read_buffer->Read( + len = drb->Read( &(buffer[remainder]), buffer_size - remainder); // Check for DRB errors - if (_device_read_buffer->IsErrored()) + if (drb->IsErrored()) { VERBOSE(VB_IMPORTANT, LOC_ERR + "Device error detected"); _error = true; } - if (_device_read_buffer->IsEOF()) + if (drb->IsEOF()) { VERBOSE(VB_IMPORTANT, LOC_ERR + "Device EOF detected"); _error = true; @@ -381,10 +256,9 @@ void DVBStreamHandler::RunTS(void) continue; } - for (uint i = 0; i < _stream_data_list.size(); i++) - { - remainder = _stream_data_list[i]->ProcessData(buffer, len); - } + StreamDataList::const_iterator sit = _stream_data_list.begin(); + for (; sit != _stream_data_list.end(); ++sit) + remainder = sit.key()->ProcessData(buffer, len); _listener_lock.unlock(); @@ -395,19 +269,24 @@ void DVBStreamHandler::RunTS(void) RemoveAllPIDFilters(); - if (_device_read_buffer) { - if (_device_read_buffer->IsRunning()) - _device_read_buffer->Stop(); + QMutexLocker locker(&_start_stop_lock); + _drb = NULL; + } - delete _device_read_buffer; - _device_read_buffer = NULL; + if (drb) + { + if (drb->IsRunning()) + drb->Stop(); + delete drb; } close(dvr_fd); delete[] buffer; VERBOSE(VB_RECORD, LOC + "RunTS(): " + "end"); + + SetRunning(false, _needs_buffering, false); } /** \fn DVBStreamHandler::RunSR(void) @@ -421,11 +300,16 @@ void DVBStreamHandler::RunSR(void) int buffer_size = 4192; // maximum size of Section we handle unsigned char *buffer = new unsigned char[buffer_size]; if (!buffer) + { + _error = true; return; + } + + SetRunning(true, _needs_buffering, true); VERBOSE(VB_RECORD, LOC + "RunSR(): begin"); - while (_run) + while (_running_desired && !_error) { RetuneMonitor(); UpdateFiltersFromStreamData(); @@ -448,11 +332,9 @@ void DVBStreamHandler::RunSR(void) if (psip.SectionSyntaxIndicator()) { _listener_lock.lock(); - for (uint i = 0; i < _stream_data_list.size(); i++) - { - _stream_data_list[i]->HandleTables( - fit.key() /* pid */, psip); - } + StreamDataList::const_iterator sit = _stream_data_list.begin(); + for (; sit != _stream_data_list.end(); ++sit) + sit.key()->HandleTables(fit.key() /* pid */, psip); _listener_lock.unlock(); } } @@ -466,22 +348,9 @@ void DVBStreamHandler::RunSR(void) delete[] buffer; - VERBOSE(VB_RECORD, LOC + "RunSR(): " + "end"); -} + SetRunning(false, _needs_buffering, true); -bool DVBStreamHandler::AddPIDFilter(PIDInfo *info) -{ -#ifdef DEBUG_PID_FILTERS - VERBOSE(VB_RECORD, LOC + QString("AddPIDFilter(0x%1) priority %2") - .arg(info->_pid, 0, 16).arg(GetPIDPriority(info->_pid))); -#endif // DEBUG_PID_FILTERS - - QMutexLocker writing_locker(&_pid_lock); - _pid_info[info->_pid] = info; - - CycleFiltersByPriority(); - - return true; + VERBOSE(VB_RECORD, LOC + "RunSR(): " + "end"); } typedef vector pid_list_t; @@ -550,7 +419,7 @@ void DVBStreamHandler::CycleFiltersByPriority(void) if (closed == priority_queue[i].end()) break; // something is broken - if (_pid_info[*closed]->Open(_dvb_dev, _using_section_reader)) + if (_pid_info[*closed]->Open(_device, _using_section_reader)) { _open_pid_filters++; priority_open_cnt[i]++; @@ -572,7 +441,7 @@ void DVBStreamHandler::CycleFiltersByPriority(void) if (!info->IsOpen()) continue; - if (info->Close(_dvb_dev)) + if (info->Close(_device)) freed = true; _open_pid_filters--; @@ -584,7 +453,7 @@ void DVBStreamHandler::CycleFiltersByPriority(void) { // if we can open a filter, just do it if (_pid_info[*closed]->Open( - _dvb_dev, _using_section_reader)) + _device, _using_section_reader)) { _open_pid_filters++; priority_open_cnt[i]++; @@ -601,13 +470,13 @@ void DVBStreamHandler::CycleFiltersByPriority(void) break; // nothing to close.. // close "open" - bool ok = _pid_info[*open]->Close(_dvb_dev); + bool ok = _pid_info[*open]->Close(_device); _open_pid_filters--; priority_open_cnt[i]--; // open "closed" if (ok && _pid_info[*closed]-> - Open(_dvb_dev, _using_section_reader)) + Open(_device, _using_section_reader)) { _open_pid_filters++; priority_open_cnt[i]++; @@ -620,145 +489,6 @@ void DVBStreamHandler::CycleFiltersByPriority(void) _cycle_timer.start(); } -bool DVBStreamHandler::RemovePIDFilter(uint pid) -{ -#ifdef DEBUG_PID_FILTERS - VERBOSE(VB_RECORD, LOC + - QString("RemovePIDFilter(0x%1)").arg(pid, 0, 16)); -#endif // DEBUG_PID_FILTERS - - QMutexLocker write_locker(&_pid_lock); - - PIDInfoMap::iterator it = _pid_info.find(pid); - if (it == _pid_info.end()) - return false; - - PIDInfo *tmp = *it; - _pid_info.erase(it); - - bool ok = true; - if (tmp->IsOpen()) - { - ok = tmp->Close(_dvb_dev); - _open_pid_filters--; - - CycleFiltersByPriority(); - } - - delete tmp; - - return ok; -} - -bool DVBStreamHandler::RemoveAllPIDFilters(void) -{ - QMutexLocker write_locker(&_pid_lock); - -#ifdef DEBUG_PID_FILTERS - VERBOSE(VB_RECORD, LOC + "RemoveAllPIDFilters()"); -#endif // DEBUG_PID_FILTERS - - vector del_pids; - PIDInfoMap::iterator it = _pid_info.begin(); - for (; it != _pid_info.end(); ++it) - del_pids.push_back(it.key()); - - bool ok = true; - vector::iterator dit = del_pids.begin(); - for (; dit != del_pids.end(); ++dit) - ok &= RemovePIDFilter(*dit); - - return ok; -} - -void DVBStreamHandler::UpdateListeningForEIT(void) -{ - vector add_eit, del_eit; - - QMutexLocker read_locker(&_listener_lock); - - for (uint i = 0; i < _stream_data_list.size(); i++) - { - MPEGStreamData *sd = _stream_data_list[i]; - if (sd->HasEITPIDChanges(_eit_pids) && - sd->GetEITPIDChanges(_eit_pids, add_eit, del_eit)) - { - for (uint i = 0; i < del_eit.size(); i++) - { - uint_vec_t::iterator it; - it = find(_eit_pids.begin(), _eit_pids.end(), del_eit[i]); - if (it != _eit_pids.end()) - _eit_pids.erase(it); - sd->RemoveListeningPID(del_eit[i]); - } - - for (uint i = 0; i < add_eit.size(); i++) - { - _eit_pids.push_back(add_eit[i]); - sd->AddListeningPID(add_eit[i]); - } - } - } -} - -bool DVBStreamHandler::UpdateFiltersFromStreamData(void) -{ - UpdateListeningForEIT(); - - pid_map_t pids; - - { - QMutexLocker read_locker(&_listener_lock); - - for (uint i = 0; i < _stream_data_list.size(); i++) - _stream_data_list[i]->GetPIDs(pids); - } - - QMap add_pids; - vector del_pids; - - { - QMutexLocker read_locker(&_pid_lock); - - // PIDs that need to be added.. - pid_map_t::const_iterator lit = pids.constBegin(); - for (; lit != pids.constEnd(); ++lit) - { - if (*lit && (_pid_info.find(lit.key()) == _pid_info.end())) - { - add_pids[lit.key()] = new PIDInfo( - lit.key(), StreamID::PrivSec, DMX_PES_OTHER); - } - } - - // PIDs that need to be removed.. - PIDInfoMap::const_iterator fit = _pid_info.begin(); - for (; fit != _pid_info.end(); ++fit) - { - bool in_pids = pids.find(fit.key()) != pids.end(); - if (!in_pids) - del_pids.push_back(fit.key()); - } - } - - // Remove PIDs - bool ok = true; - vector::iterator dit = del_pids.begin(); - for (; dit != del_pids.end(); ++dit) - ok &= RemovePIDFilter(*dit); - - // Add PIDs - QMap::iterator ait = add_pids.begin(); - for (; ait != add_pids.end(); ++ait) - ok &= AddPIDFilter(*ait); - - // Cycle filters if it's been a while - if (_cycle_timer.elapsed() > 1000) - CycleFiltersByPriority(); - - return ok; -} - void DVBStreamHandler::SetRetuneAllowed( bool allow, DTVSignalMonitor *sigmon, @@ -825,7 +555,7 @@ bool DVBStreamHandler::SupportsTSMonitoring(void) { QMutexLocker locker(&_rec_supports_ts_monitoring_lock); QMap::const_iterator it; - it = _rec_supports_ts_monitoring.find(_dvb_dev); + it = _rec_supports_ts_monitoring.find(_device); if (it != _rec_supports_ts_monitoring.end()) return *it; } @@ -835,12 +565,12 @@ bool DVBStreamHandler::SupportsTSMonitoring(void) if (dvr_fd < 0) { QMutexLocker locker(&_rec_supports_ts_monitoring_lock); - _rec_supports_ts_monitoring[_dvb_dev] = false; + _rec_supports_ts_monitoring[_device] = false; return false; } bool supports_ts = false; - if (AddPIDFilter(new PIDInfo(pat_pid))) + if (AddPIDFilter(new DVBPIDInfo(pat_pid))) { supports_ts = true; RemovePIDFilter(pat_pid); @@ -849,7 +579,7 @@ bool DVBStreamHandler::SupportsTSMonitoring(void) close(dvr_fd); QMutexLocker locker(&_rec_supports_ts_monitoring_lock); - _rec_supports_ts_monitoring[_dvb_dev] = supports_ts; + _rec_supports_ts_monitoring[_device] = supports_ts; return supports_ts; } @@ -862,7 +592,7 @@ bool DVBStreamHandler::SupportsTSMonitoring(void) #define LOC_WARN QString("PIDInfo(%1) Warning: ").arg(dvb_dev) #define LOC_ERR QString("PIDInfo(%1) Error: ").arg(dvb_dev) -bool PIDInfo::Open(const QString &dvb_dev, bool use_section_reader) +bool DVBPIDInfo::Open(const QString &dvb_dev, bool use_section_reader) { if (filter_fd >= 0) { @@ -966,7 +696,7 @@ bool PIDInfo::Open(const QString &dvb_dev, bool use_section_reader) return true; } -bool PIDInfo::Close(const QString &dvb_dev) +bool DVBPIDInfo::Close(const QString &dvb_dev) { VERBOSE(VB_RECORD, LOC + QString("Closing filter for pid 0x%1").arg(_pid, 0, 16)); @@ -989,18 +719,6 @@ bool PIDInfo::Close(const QString &dvb_dev) return true; } -PIDPriority DVBStreamHandler::GetPIDPriority(uint pid) const -{ - QMutexLocker reading_locker(&_listener_lock); - - PIDPriority tmp = kPIDPriorityNone; - - for (uint i = 0; i < _stream_data_list.size(); i++) - tmp = max(tmp, _stream_data_list[i]->GetPIDPriority(pid)); - - return tmp; -} - #if 0 // We don't yet do kernel buffer allocation in dvbstreamhandler.. diff --git a/mythtv/libs/libmythtv/dvbstreamhandler.h b/mythtv/libs/libmythtv/dvbstreamhandler.h index 614171f57d5..bef6b8083a9 100644 --- a/mythtv/libs/libmythtv/dvbstreamhandler.h +++ b/mythtv/libs/libmythtv/dvbstreamhandler.h @@ -6,13 +6,11 @@ #include using namespace std; -#include +#include #include -#include +#include -#include "util.h" -#include "DeviceReadBuffer.h" -#include "mpegstreamdata.h" +#include "streamhandler.h" class QString; class DVBStreamHandler; @@ -24,107 +22,55 @@ typedef QMap FilterMap; //#define RETUNE_TIMEOUT 5000 -class PIDInfo +class DVBPIDInfo : public PIDInfo { public: - PIDInfo() : - _pid(0xffffffff), filter_fd(-1), streamType(0), pesType(-1) {;} - PIDInfo(uint pid) : - _pid(pid), filter_fd(-1), streamType(0), pesType(-1) {;} - PIDInfo(uint pid, uint stream_type, uint pes_type) : - _pid(pid), filter_fd(-1), - streamType(stream_type), pesType(pes_type) {;} - + DVBPIDInfo(uint pid) : PIDInfo(pid) {} + DVBPIDInfo(uint pid, uint stream_type, int pes_type) : + PIDInfo(pid, stream_type, pes_type) {} bool Open(const QString &dvb_dev, bool use_section_reader); bool Close(const QString &dvb_dev); - bool IsOpen(void) const { return filter_fd >= 0; } - - uint _pid; - int filter_fd; ///< Input filter file descriptor - uint streamType; ///< StreamID - int pesType; ///< PESStreamID -}; -typedef QMap PIDInfoMap; - -class DVBReadThread : public QThread -{ - Q_OBJECT - public: - DVBReadThread() : m_parent(NULL) {} - void SetParent(DVBStreamHandler *parent) { m_parent = parent; } - void run(void); - private: - DVBStreamHandler *m_parent; }; -class DVBStreamHandler : public ReaderPausedCB +class DVBStreamHandler : public StreamHandler { - friend class DVBReadThread; - public: static DVBStreamHandler *Get(const QString &dvb_device); static void Return(DVBStreamHandler * & ref); - void AddListener(MPEGStreamData *data, - bool allow_section_reader, - bool needs_buffering); - void RemoveListener(MPEGStreamData *data); + // DVB specific void RetuneMonitor(void); - bool IsRunning(void) const { return _reader_thread.isRunning(); } bool IsRetuneAllowed(void) const { return _allow_retune; } void SetRetuneAllowed(bool allow, DTVSignalMonitor *sigmon, DVBChannel *dvbchan); - // ReaderPausedCB - virtual void ReaderPaused(int fd) { (void) fd; } - private: DVBStreamHandler(const QString &); - ~DVBStreamHandler(); - - void Start(void); - void Stop(void); - void Run(void); + virtual void run(void); // QThread void RunTS(void); void RunSR(void); - void UpdateListeningForEIT(void); - bool UpdateFiltersFromStreamData(void); - bool AddPIDFilter(PIDInfo *info); - bool RemovePIDFilter(uint pid); - bool RemoveAllPIDFilters(void); - void CycleFiltersByPriority(void); + virtual void CycleFiltersByPriority(void); - PIDPriority GetPIDPriority(uint pid) const; bool SupportsTSMonitoring(void); + virtual PIDInfo *CreatePIDInfo(uint pid, uint stream_type, int pes_type) + { return new DVBPIDInfo(pid, stream_type, pes_type); } + + virtual void SetRunningDesired(bool desired); // StreamHandler + private: - QString _dvb_dev; QString _dvr_dev_path; - bool _allow_section_reader; - bool _needs_buffering; - bool _allow_retune; - - mutable QMutex _start_stop_lock; - DVBReadThread _reader_thread; - bool _using_section_reader; - DeviceReadBuffer *_device_read_buffer; + volatile bool _allow_retune; + DTVSignalMonitor *_sigmon; DVBChannel *_dvbchannel; - - mutable QMutex _pid_lock; - vector _eit_pids; - PIDInfoMap _pid_info; - uint _open_pid_filters; - MythTimer _cycle_timer; - - mutable QMutex _listener_lock; - vector _stream_data_list; + DeviceReadBuffer *_drb; // for caching TS monitoring supported value. static QMutex _rec_supports_ts_monitoring_lock; @@ -134,8 +80,6 @@ class DVBStreamHandler : public ReaderPausedCB static QMutex _handlers_lock; static QMap _handlers; static QMap _handlers_refcnt; - - bool _run; }; #endif // _DVBSTREAMHANDLER_H_ diff --git a/mythtv/libs/libmythtv/firewirechannel.cpp b/mythtv/libs/libmythtv/firewirechannel.cpp index a656b400a6b..cfa9da36691 100644 --- a/mythtv/libs/libmythtv/firewirechannel.cpp +++ b/mythtv/libs/libmythtv/firewirechannel.cpp @@ -38,86 +38,6 @@ FirewireChannel::FirewireChannel(TVRec *parent, const QString &_videodevice, #endif // USING_OSX_FIREWIRE } -bool FirewireChannel::SetChannelByString(const QString &channum) -{ - QString loc = LOC + QString("SetChannelByString(%1)").arg(channum); - VERBOSE(VB_CHANNEL, loc); - - InputMap::const_iterator it = m_inputs.find(m_currentInputID); - if (it == m_inputs.end()) - return false; - - uint mplexid_restriction; - if (!IsInputAvailable(m_currentInputID, mplexid_restriction)) - { - VERBOSE(VB_IMPORTANT, loc + " " + QString( - "Requested channel '%1' is on input '%2' " - "which is in a busy input group") - .arg(channum).arg(m_currentInputID)); - - return false; - } - - // Fetch tuning data from the database. - QString tvformat, modulation, freqtable, freqid, dtv_si_std; - int finetune; - uint64_t frequency; - int mpeg_prog_num; - uint atsc_major, atsc_minor, mplexid, tsid, netid; - - if (!ChannelUtil::GetChannelData( - (*it)->sourceid, channum, - tvformat, modulation, freqtable, freqid, - finetune, frequency, - dtv_si_std, mpeg_prog_num, atsc_major, atsc_minor, tsid, netid, - mplexid, m_commfree)) - { - VERBOSE(VB_IMPORTANT, loc + " " + QString( - "Requested channel '%1' is on input '%2' " - "which is in a busy input group") - .arg(channum).arg(m_currentInputID)); - - return false; - } - - if (mplexid_restriction && (mplexid != mplexid_restriction)) - { - VERBOSE(VB_IMPORTANT, loc + " " + QString( - "Requested channel '%1' is not available because " - "the tuner is currently in use on another transport.") - .arg(channum)); - - return false; - } - - bool ok = false; - if (!(*it)->externalChanger.isEmpty()) - { - ok = ChangeExternalChannel(freqid); - SetSIStandard("mpeg"); - SetDTVInfo(0,0,0,0,1); - } - else - { - uint ichan = freqid.toUInt(&ok); - ok = ok && isopen && SetChannelByNumber(ichan); - } - - if (ok) - { - // Set the current channum to the new channel's channum - QString tmp = channum; - tmp.detach(); - m_curchannelname = tmp; - tmp.detach(); - (*it)->startChanNum = tmp; - } - - VERBOSE(VB_CHANNEL, loc + " " + ((ok) ? "success" : "failure")); - - return ok; -} - bool FirewireChannel::Open(void) { VERBOSE(VB_CHANNEL, LOC + "Open()"); @@ -206,32 +126,35 @@ bool FirewireChannel::Retune(void) } if (current_channel) - return SetChannelByNumber(current_channel); + { + QString freqid = QString::number(current_channel); + return Tune(freqid, 0); + } return false; } -bool FirewireChannel::SetChannelByNumber(int channel) +bool FirewireChannel::Tune(const QString &freqid, int /*finetune*/) { - VERBOSE(VB_CHANNEL, QString("SetChannelByNumber(%1)").arg(channel)); - current_channel = channel; + VERBOSE(VB_CHANNEL, QString("Tune(%1)").arg(freqid)); + + bool ok; + uint channel = freqid.toUInt(&ok); + if (!ok) + return false; if (FirewireDevice::kAVCPowerOff == GetPowerState()) { VERBOSE(VB_IMPORTANT, LOC_WARN + "STB is turned off, must be on to set channel."); - SetSIStandard("mpeg"); - SetDTVInfo(0,0,0,0,1); - return true; // signal monitor will call retune later... } if (!device->SetChannel(fw_opts.model, 0, channel)) return false; - SetSIStandard("mpeg"); - SetDTVInfo(0,0,0,0,1); + current_channel = channel; return true; } diff --git a/mythtv/libs/libmythtv/firewirechannel.h b/mythtv/libs/libmythtv/firewirechannel.h index 664f343567d..1f60629dc3f 100644 --- a/mythtv/libs/libmythtv/firewirechannel.h +++ b/mythtv/libs/libmythtv/firewirechannel.h @@ -13,30 +13,31 @@ class FirewireChannel : public DTVChannel { + friend class FirewireSignalMonitor; + friend class FirewireRecorder; + public: FirewireChannel(TVRec *parent, const QString &videodevice, const FireWireDBOptions &firewire_opts); - ~FirewireChannel() { Close(); } // Commands virtual bool Open(void); virtual void Close(void); - virtual bool TuneMultiplex(uint /*mplexid*/, QString /*inputname*/) - { return false; } - virtual bool Tune(const DTVMultiplex &/*tuning*/, QString /*inputname*/) - { return false; } + virtual bool Tune(const DTVMultiplex&, QString) { return false; } + virtual bool Tune(const QString &freqid, int finetune); virtual bool Retune(void); // Sets - virtual bool SetChannelByString(const QString &chan); - virtual bool SetChannelByNumber(int channel); virtual bool SetPowerState(bool on); // Gets virtual bool IsOpen(void) const { return isopen; } - virtual FirewireDevice::PowerState GetPowerState(void) const; virtual QString GetDevice(void) const; + virtual bool IsExternalChannelChangeSupported(void) { return true; } + + private: + virtual FirewireDevice::PowerState GetPowerState(void) const; virtual FirewireDevice *GetFirewireDevice(void) { return device; } protected: diff --git a/mythtv/libs/libmythtv/firewirerecorder.cpp b/mythtv/libs/libmythtv/firewirerecorder.cpp index 4f27810918e..96e21ae57c9 100644 --- a/mythtv/libs/libmythtv/firewirerecorder.cpp +++ b/mythtv/libs/libmythtv/firewirerecorder.cpp @@ -59,30 +59,45 @@ void FirewireRecorder::StartRecording(void) if (!Open()) { - _error = true; + _error = "Failed to open firewire device"; + VERBOSE(VB_IMPORTANT, LOC_ERR + _error); return; } - _request_recording = true; - _recording = true; + _continuity_error_count = 0; + + { + QMutexLocker locker(&pauseLock); + request_recording = true; + recording = true; + recordingWait.wakeAll(); + } StartStreaming(); - while (_request_recording) + while (IsRecordingRequested() && !IsErrored()) { if (PauseAndWait()) continue; - if (!_request_recording) + if (!IsRecordingRequested()) break; - usleep(50 * 1000); + { // sleep 1 seconds unless StopRecording() or Unpause() is called, + // just to avoid running this too often. + QMutexLocker locker(&pauseLock); + if (!request_recording || request_pause) + continue; + unpauseWait.wait(&pauseLock, 1000); + } } StopStreaming(); FinishRecording(); - _recording = false; + QMutexLocker locker(&pauseLock); + recording = false; + recordingWait.wakeAll(); } void FirewireRecorder::AddData(const unsigned char *data, uint len) @@ -127,13 +142,13 @@ void FirewireRecorder::AddData(const unsigned char *data, uint len) return; } -void FirewireRecorder::ProcessTSPacket(const TSPacket &tspacket) +bool FirewireRecorder::ProcessTSPacket(const TSPacket &tspacket) { if (tspacket.TransportError()) - return; + return true; if (tspacket.Scrambled()) - return; + return true; if (tspacket.HasAdaptationField()) GetStreamData()->HandleAdaptationFieldControl(&tspacket); @@ -158,6 +173,8 @@ void FirewireRecorder::ProcessTSPacket(const TSPacket &tspacket) else if (GetStreamData()->IsWritingPID(lpid)) BufferedWrite(tspacket); } + + return true; } void FirewireRecorder::SetOptionsFromProfile(RecordingProfile *profile, @@ -206,23 +223,3 @@ void FirewireRecorder::SetStreamData(void) if (_stream_data->DesiredProgram() >= 0) _stream_data->SetDesiredProgram(_stream_data->DesiredProgram()); } - -void FirewireRecorder::HandleSingleProgramPAT(ProgramAssociationTable *pat) -{ - if (!pat) - return; - - int next = (pat->tsheader()->ContinuityCounter()+1)&0xf; - pat->tsheader()->SetContinuityCounter(next); - BufferedWrite(*(reinterpret_cast(pat->tsheader()))); -} - -void FirewireRecorder::HandleSingleProgramPMT(ProgramMapTable *pmt) -{ - if (!pmt) - return; - - int next = (pmt->tsheader()->ContinuityCounter()+1)&0xf; - pmt->tsheader()->SetContinuityCounter(next); - BufferedWrite(*(reinterpret_cast(pmt->tsheader()))); -} diff --git a/mythtv/libs/libmythtv/firewirerecorder.h b/mythtv/libs/libmythtv/firewirerecorder.h index 83ba0c8d7c8..725cbc35007 100644 --- a/mythtv/libs/libmythtv/firewirerecorder.h +++ b/mythtv/libs/libmythtv/firewirerecorder.h @@ -21,9 +21,9 @@ class FirewireChannel; * * \sa DTVRecorder */ -class FirewireRecorder : public DTVRecorder, - public MPEGSingleProgramStreamListener, - public TSDataListener +class FirewireRecorder : + public DTVRecorder, + public TSDataListener { friend class MPEGStreamData; friend class TSPacketProcessor; @@ -42,8 +42,10 @@ class FirewireRecorder : public DTVRecorder, void StartRecording(void); bool PauseAndWait(int timeout = 100); + // Implements TSDataListener void AddData(const unsigned char *data, uint dataSize); - void ProcessTSPacket(const TSPacket &tspacket); + + bool ProcessTSPacket(const TSPacket &tspacket); // Sets void SetOptionsFromProfile(RecordingProfile *profile, @@ -52,10 +54,6 @@ class FirewireRecorder : public DTVRecorder, const QString &vbidev); void SetStreamData(void); - // MPEG Single Program - void HandleSingleProgramPAT(ProgramAssociationTable*); - void HandleSingleProgramPMT(ProgramMapTable*); - protected: FirewireRecorder(TVRec *rec); diff --git a/mythtv/libs/libmythtv/firewiresignalmonitor.cpp b/mythtv/libs/libmythtv/firewiresignalmonitor.cpp index 8e7bd441362..92ae1a968fa 100644 --- a/mythtv/libs/libmythtv/firewiresignalmonitor.cpp +++ b/mythtv/libs/libmythtv/firewiresignalmonitor.cpp @@ -18,6 +18,13 @@ #define LOC_WARN QString("FireSM(%1), Warning: ").arg(channel->GetDevice()) #define LOC_ERR QString("FireSM(%1), Error: ").arg(channel->GetDevice()) +void FirewireTableMonitorThread::run(void) +{ + threadRegister("FirewireTableMonitor"); + m_parent->RunTableMonitor(); + threadDeregister(); +} + const uint FirewireSignalMonitor::kPowerTimeout = 3000; /* ms */ const uint FirewireSignalMonitor::kBufferTimeout = 5000; /* ms */ @@ -43,6 +50,8 @@ FirewireSignalMonitor::FirewireSignalMonitor( FirewireChannel *_channel, uint64_t _flags) : DTVSignalMonitor(db_cardnum, _channel, _flags), + dtvMonitorRunning(false), + tableMonitorThread(NULL), stb_needs_retune(true), stb_needs_to_wait_for_pat(false), stb_needs_to_wait_for_power(false) @@ -73,10 +82,12 @@ void FirewireSignalMonitor::Stop(void) { VERBOSE(VB_CHANNEL, LOC + "Stop() -- begin"); SignalMonitor::Stop(); - if (table_monitor_thread.isRunning()) + if (tableMonitorThread) { - dtvMonitorRun = false; - table_monitor_thread.wait(); + dtvMonitorRunning = false; + tableMonitorThread->wait(); + delete tableMonitorThread; + tableMonitorThread = NULL; } VERBOSE(VB_CHANNEL, LOC + "Stop() -- end"); } @@ -124,26 +135,20 @@ void FirewireSignalMonitor::HandlePMT(uint pnum, const ProgramMapTable *pmt) DTVSignalMonitor::HandlePMT(pnum, pmt); } -void FWSignalThread::run(void) -{ - if (!m_parent) - return; - - threadRegister("FWSignal"); - m_parent->RunTableMonitor(); - threadDeregister(); -} - void FirewireSignalMonitor::RunTableMonitor(void) { stb_needs_to_wait_for_pat = true; stb_wait_for_pat_timer.start(); + dtvMonitorRunning = true; VERBOSE(VB_CHANNEL, LOC + "RunTableMonitor(): -- begin"); FirewireChannel *lchan = dynamic_cast(channel); if (!lchan) { + VERBOSE(VB_CHANNEL, LOC + "RunTableMonitor(): -- err"); + while (dtvMonitorRunning) + usleep(10000); VERBOSE(VB_CHANNEL, LOC + "RunTableMonitor(): -- err end"); return; } @@ -153,20 +158,23 @@ void FirewireSignalMonitor::RunTableMonitor(void) dev->OpenPort(); dev->AddListener(this); - while (dtvMonitorRun && GetStreamData()) - usleep(100000); + while (dtvMonitorRunning && GetStreamData()) + usleep(10000); VERBOSE(VB_CHANNEL, LOC + "RunTableMonitor(): -- shutdown "); dev->RemoveListener(this); dev->ClosePort(); + while (dtvMonitorRunning) + usleep(10000); + VERBOSE(VB_CHANNEL, LOC + "RunTableMonitor(): -- end"); } void FirewireSignalMonitor::AddData(const unsigned char *data, uint len) { - if (!table_monitor_thread.isRunning()) + if (!dtvMonitorRunning) return; if (GetStreamData()) @@ -185,13 +193,10 @@ void FirewireSignalMonitor::AddData(const unsigned char *data, uint len) */ void FirewireSignalMonitor::UpdateValues(void) { - if (!monitor_thread.isRunning() || exit) - return; - - if (!IsChannelTuned()) + if (!running || exit) return; - if (table_monitor_thread.isRunning()) + if (dtvMonitorRunning) { EmitStatus(); if (IsAllGood()) @@ -256,6 +261,14 @@ void FirewireSignalMonitor::UpdateValues(void) isLocked = stb_needs_retune = false; } + SignalMonitor::UpdateValues(); + + { + QMutexLocker locker(&statusLock); + if (!scriptStatus.IsGood()) + return; + } + // Set SignalMonitorValues from info from card. { QMutexLocker locker(&statusLock); @@ -274,15 +287,13 @@ void FirewireSignalMonitor::UpdateValues(void) kDTVSigMon_WaitForMGT | kDTVSigMon_WaitForVCT | kDTVSigMon_WaitForNIT | kDTVSigMon_WaitForSDT)) { - dtvMonitorRun = true; - table_monitor_thread.SetParent(this); - table_monitor_thread.start(); + tableMonitorThread = new FirewireTableMonitorThread(this); VERBOSE(VB_CHANNEL, LOC + "UpdateValues() -- " "Waiting for table monitor to start"); - while (!table_monitor_thread.isRunning()) - usleep(50); + while (!dtvMonitorRunning) + usleep(5000); VERBOSE(VB_CHANNEL, LOC + "UpdateValues() -- " "Table monitor started"); diff --git a/mythtv/libs/libmythtv/firewiresignalmonitor.h b/mythtv/libs/libmythtv/firewiresignalmonitor.h index 6785e696e9c..b10e78aa42c 100644 --- a/mythtv/libs/libmythtv/firewiresignalmonitor.h +++ b/mythtv/libs/libmythtv/firewiresignalmonitor.h @@ -8,9 +8,9 @@ using namespace std; // Qt headers +#include #include #include -#include // MythTV headers #include "dtvsignalmonitor.h" @@ -21,20 +21,21 @@ class FirewireChannel; class FirewireSignalMonitor; -class FWSignalThread : public QThread +class FirewireTableMonitorThread : public QThread { Q_OBJECT public: - FWSignalThread() : m_parent(NULL) {} - void SetParent(FirewireSignalMonitor *parent) { m_parent = parent; } - void run(void); + FirewireTableMonitorThread(FirewireSignalMonitor *p) : + m_parent(p) { start(); } + virtual ~FirewireTableMonitorThread() { wait(); } + virtual void run(void); private: FirewireSignalMonitor *m_parent; }; class FirewireSignalMonitor : public DTVSignalMonitor, public TSDataListener { - friend class FWSignalThread; + friend class FirewireTableMonitorThread; public: FirewireSignalMonitor(int db_cardnum, FirewireChannel *_channel, uint64_t _flags = kFWSigMon_WaitForPower); @@ -62,8 +63,8 @@ class FirewireSignalMonitor : public DTVSignalMonitor, public TSDataListener static const uint kBufferTimeout; protected: - bool dtvMonitorRun; - FWSignalThread table_monitor_thread; + volatile bool dtvMonitorRunning; + FirewireTableMonitorThread *tableMonitorThread; bool stb_needs_retune; bool stb_needs_to_wait_for_pat; bool stb_needs_to_wait_for_power; diff --git a/mythtv/libs/libmythtv/hdhrchannel.cpp b/mythtv/libs/libmythtv/hdhrchannel.cpp index 98d9fcb757c..d18be4fb6fc 100644 --- a/mythtv/libs/libmythtv/hdhrchannel.cpp +++ b/mythtv/libs/libmythtv/hdhrchannel.cpp @@ -35,10 +35,11 @@ using namespace std; HDHRChannel::HDHRChannel(TVRec *parent, const QString &device) : DTVChannel(parent), - _device_id(device), _stream_handler(NULL), - _lock(QMutex::Recursive), - tune_lock(QMutex::Recursive), hw_lock(QMutex::Recursive) + _device_id(device), _master(NULL), + _stream_handler(NULL) { + _master = dynamic_cast(GetMaster(device)); + _master = (_master == this) ? NULL : _master; } HDHRChannel::~HDHRChannel(void) @@ -50,8 +51,6 @@ bool HDHRChannel::Open(void) { VERBOSE(VB_CHANNEL, LOC + "Opening HDHR channel"); - QMutexLocker locker(&hw_lock); - if (IsOpen()) return true; @@ -93,184 +92,23 @@ bool HDHRChannel::IsOpen(void) const return _stream_handler; } -bool HDHRChannel::Init( - QString &inputname, QString &startchannel, bool setchan) -{ - if (setchan && !IsOpen()) - Open(); - - return ChannelBase::Init(inputname, startchannel, setchan); -} - -bool HDHRChannel::SetChannelByString(const QString &channum) -{ - QString loc = LOC + QString("SetChannelByString(%1)").arg(channum); - QString loc_err = loc + ", Error: "; - VERBOSE(VB_CHANNEL, loc); - - if (!Open()) - { - VERBOSE(VB_IMPORTANT, loc_err + "Channel object " - "will not open, cannot change channels."); - - return false; - } - - QString inputName; - if (!CheckChannel(channum, inputName)) - { - VERBOSE(VB_IMPORTANT, loc_err + - "CheckChannel failed.\n\t\t\tPlease verify the channel " - "in the 'mythtv-setup' Channel Editor."); - - return false; - } - - // If CheckChannel filled in the inputName then we need to - // change inputs and return, since the act of changing - // inputs will change the channel as well. - if (!inputName.isEmpty()) - return SelectInput(inputName, channum, false); - - ClearDTVInfo(); - - InputMap::const_iterator it = m_inputs.find(m_currentInputID); - if (it == m_inputs.end()) - return false; - - uint mplexid_restriction; - if (!IsInputAvailable(m_currentInputID, mplexid_restriction)) - return false; - - if (Aborted()) - return false; - - // Fetch tuning data from the database. - QString tvformat, modulation, freqtable, freqid, si_std; - int finetune; - uint64_t frequency; - int mpeg_prog_num; - uint atsc_major, atsc_minor, mplexid, tsid, netid; - - if (!ChannelUtil::GetChannelData( - (*it)->sourceid, channum, - tvformat, modulation, freqtable, freqid, - finetune, frequency, - si_std, mpeg_prog_num, atsc_major, atsc_minor, tsid, netid, - mplexid, m_commfree)) - { - return false; - } - - if (mplexid_restriction && (mplexid != mplexid_restriction)) - return false; - - // If the frequency is zeroed out, don't use it directly. - bool ok = (frequency > 0); - if (!ok) - { - frequency = (freqid.toInt(&ok) + finetune) * 1000; - mplexid = 0; - } - bool isFrequency = ok && (frequency > 10000000); - - // Tune to proper frequency - if ((*it)->externalChanger.isEmpty()) - { - if (isFrequency) - { - if (!Tune(frequency, inputName, modulation, si_std)) - return false; - } - else - { - if (!_stream_handler->TuneVChannel(channum)) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + - "dtv_multiplex data is required for tuning"); - return false; - } - - SetSIStandard(si_std); - } - } - else if (!ChangeExternalChannel(freqid)) - return false; - - // Set the current channum to the new channel's channum - QString tmp = channum; tmp.detach(); - m_curchannelname = tmp; - - // Set the major and minor channel for any additional multiplex tuning - SetDTVInfo(atsc_major, atsc_minor, netid, tsid, mpeg_prog_num); - - // Set this as the future start channel for this source - QString tmpX = m_curchannelname; tmpX.detach(); - m_inputs[m_currentInputID]->startChanNum = tmpX; - - return true; -} - -// documented in dtvchannel.h -bool HDHRChannel::TuneMultiplex(uint mplexid, QString inputname) +bool HDHRChannel::Tune(const QString &freqid, int /*finetune*/) { - VERBOSE(VB_CHANNEL, LOC + QString("TuneMultiplex(%1)").arg(mplexid)); - - QString modulation; - QString si_std; - uint64_t frequency; - uint transportid; - uint dvb_networkid; - - if (!ChannelUtil::GetTuningParams( - mplexid, modulation, frequency, - transportid, dvb_networkid, si_std)) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + "TuneMultiplex(): " + - QString("Could not find tuning parameters for multiplex %1.") - .arg(mplexid)); - - return false; - } - - if (!Tune(frequency, inputname, modulation, si_std)) - return false; - - return true; + return _stream_handler->TuneVChannel(freqid); } -bool HDHRChannel::Tune(const DTVMultiplex &tuning, QString inputname) +bool HDHRChannel::Tune(const DTVMultiplex &tuning, QString /*inputname*/) { - return Tune(tuning.frequency, inputname, - tuning.modulation.toString(), tuning.sistandard); -} - -bool HDHRChannel::Tune(uint frequency, QString /*input*/, - QString modulation, QString si_std) -{ - QString chan = modulation + ':' + QString::number(frequency); + QString chan = tuning.modulation.toString() + ':' + + QString::number(tuning.frequency); VERBOSE(VB_CHANNEL, LOC + "Tuning to " + chan); if (_stream_handler->TuneChannel(chan)) { - SetSIStandard(si_std); - return true; - } - - - // dtv_multiplex.modulation is from the DB. Could contain almost anything. - // As a fallback, use the HDHR device's automatic scanning: - chan = "auto:" + QString::number(frequency); - - VERBOSE(VB_CHANNEL, LOC + "Failed. Now trying " + chan); - - if (_stream_handler->TuneChannel(chan)) - { - SetSIStandard(si_std); + SetSIStandard(tuning.sistandard); return true; } - return false; } diff --git a/mythtv/libs/libmythtv/hdhrchannel.h b/mythtv/libs/libmythtv/hdhrchannel.h index ee610baa274..f6343571030 100644 --- a/mythtv/libs/libmythtv/hdhrchannel.h +++ b/mythtv/libs/libmythtv/hdhrchannel.h @@ -30,32 +30,25 @@ class HDHRChannel : public DTVChannel void Close(void); bool EnterPowerSavingMode(void); - bool Init(QString &inputname, QString &startchannel, bool setchan); - - // Sets - bool SetChannelByString(const QString &chan); - // Gets bool IsOpen(void) const; QString GetDevice(void) const { return _device_id; } - virtual vector GetTunerTypes(void) const { return _tuner_types; } + virtual vector GetTunerTypes(void) const + { return _tuner_types; } + virtual bool IsMaster(void) const + { return _master == NULL; } // ATSC/DVB scanning/tuning stuff - bool TuneMultiplex(uint mplexid, QString inputname); bool Tune(const DTVMultiplex &tuning, QString inputname); - private: - bool Tune(uint frequency, QString inputname, - QString modulation, QString si_std); + // Virtual tuning + bool Tune(const QString &freqid, int /*finetune*/); private: QString _device_id; + HDHRChannel *_master; HDHRStreamHandler *_stream_handler; vector _tuner_types; - - mutable QMutex _lock; - mutable QMutex tune_lock; - mutable QMutex hw_lock; }; #endif diff --git a/mythtv/libs/libmythtv/hdhrrecorder.cpp b/mythtv/libs/libmythtv/hdhrrecorder.cpp index 05bbb7e41aa..7165a3427fc 100644 --- a/mythtv/libs/libmythtv/hdhrrecorder.cpp +++ b/mythtv/libs/libmythtv/hdhrrecorder.cpp @@ -5,36 +5,13 @@ * Distributed as part of MythTV under GPL v2 and later. */ -// POSIX includes -#include -#include -#include -#include -#ifndef USING_MINGW -#include -#include -#include -#include -#endif -#include - -// C++ includes -#include -#include -using namespace std; - // MythTV includes -#include "ringbuffer.h" -#include "atsctables.h" +#include "hdhrstreamhandler.h" #include "atscstreamdata.h" -#include "dvbstreamdata.h" -#include "eithelper.h" -#include "tv_rec.h" - -// MythTV HDHR includes -#include "hdhrchannel.h" #include "hdhrrecorder.h" -#include "hdhrstreamhandler.h" +#include "hdhrchannel.h" +#include "tv_rec.h" +#include "mythverbose.h" #define LOC QString("HDHRRec(%1): ").arg(tvrec->GetCaptureCardNum()) #define LOC_WARN QString("HDHRRec(%1), Warning: ") \ @@ -43,177 +20,10 @@ using namespace std; .arg(tvrec->GetCaptureCardNum()) HDHRRecorder::HDHRRecorder(TVRec *rec, HDHRChannel *channel) - : DTVRecorder(rec), - _channel(channel), _stream_handler(NULL), - _pid_lock(QMutex::Recursive), - _input_pat(NULL), _input_pmt(NULL), - _has_no_av(false) -{ - memset(_stream_id, 0, sizeof(_stream_id)); - memset(_pid_status, 0, sizeof(_pid_status)); - memset(_continuity_counter, 0, sizeof(_continuity_counter)); -} - -HDHRRecorder::~HDHRRecorder() + : DTVRecorder(rec), _channel(channel), _stream_handler(NULL) { - TeardownAll(); } -void HDHRRecorder::TeardownAll(void) -{ - StopRecording(); - Close(); - - if (_input_pat) - { - delete _input_pat; - _input_pat = NULL; - } - - if (_input_pmt) - { - delete _input_pmt; - _input_pmt = NULL; - } -} - -void HDHRRecorder::SetOptionsFromProfile(RecordingProfile *profile, - const QString &videodev, - const QString &audiodev, - const QString &vbidev) -{ - (void)audiodev; - (void)vbidev; - (void)profile; - - SetOption("videodevice", videodev); - SetOption("tvformat", gCoreContext->GetSetting("TVFormat")); - SetOption("vbiformat", gCoreContext->GetSetting("VbiFormat")); - - // HACK -- begin - // This is to make debugging easier. - SetOption("videodevice", QString::number(tvrec->GetCaptureCardNum())); - // HACK -- end -} - -void HDHRRecorder::HandleSingleProgramPAT(ProgramAssociationTable *pat) -{ - if (!pat) - { - VERBOSE(VB_RECORD, LOC + "HandleSingleProgramPAT(NULL)"); - return; - } - - if (!ringBuffer) - return; - - uint next_cc = (pat->tsheader()->ContinuityCounter()+1)&0xf; - pat->tsheader()->SetContinuityCounter(next_cc); - pat->GetAsTSPackets(_scratch, next_cc); - for (uint i = 0; i < _scratch.size(); i++) - DTVRecorder::BufferedWrite(_scratch[i]); -} - -void HDHRRecorder::HandleSingleProgramPMT(ProgramMapTable *pmt) -{ - if (!pmt) - { - VERBOSE(VB_RECORD, LOC + "HandleSingleProgramPMT(NULL)"); - return; - } - - // collect stream types for H.264 (MPEG-4 AVC) keyframe detection - for (uint i = 0; i < pmt->StreamCount(); i++) - _stream_id[pmt->StreamPID(i)] = pmt->StreamType(i); - - if (!ringBuffer) - return; - - uint next_cc = (pmt->tsheader()->ContinuityCounter()+1)&0xf; - pmt->tsheader()->SetContinuityCounter(next_cc); - pmt->GetAsTSPackets(_scratch, next_cc); - - for (uint i = 0; i < _scratch.size(); i++) - DTVRecorder::BufferedWrite(_scratch[i]); -} - -void HDHRRecorder::HandlePAT(const ProgramAssociationTable *_pat) -{ - if (!_pat) - { - VERBOSE(VB_RECORD, LOC + "SetPAT(NULL)"); - return; - } - - QMutexLocker change_lock(&_pid_lock); - - int progNum = _stream_data->DesiredProgram(); - uint pmtpid = _pat->FindPID(progNum); - - if (!pmtpid) - { - VERBOSE(VB_RECORD, LOC + "SetPAT(): " - "Ignoring PAT not containing our desired program..."); - return; - } - - VERBOSE(VB_RECORD, LOC + QString("SetPAT(%1 on 0x%2)") - .arg(progNum).arg(pmtpid,0,16)); - - ProgramAssociationTable *oldpat = _input_pat; - _input_pat = new ProgramAssociationTable(*_pat); - delete oldpat; - - // Listen for the other PMTs for faster channel switching - for (uint i = 0; _input_pat && (i < _input_pat->ProgramCount()); i++) - { - uint pmt_pid = _input_pat->ProgramPID(i); - if (!_stream_data->IsListeningPID(pmt_pid)) - _stream_data->AddListeningPID(pmt_pid); - } -} - -void HDHRRecorder::HandlePMT(uint progNum, const ProgramMapTable *_pmt) -{ - QMutexLocker change_lock(&_pid_lock); - - if ((int)progNum == _stream_data->DesiredProgram()) - { - VERBOSE(VB_RECORD, LOC + QString("SetPMT(%1)").arg(progNum)); - ProgramMapTable *oldpmt = _input_pmt; - _input_pmt = new ProgramMapTable(*_pmt); - - QString sistandard = _channel->GetSIStandard(); - - bool has_no_av = true; - for (uint i = 0; i < _input_pmt->StreamCount() && has_no_av; i++) - { - has_no_av &= !_input_pmt->IsVideo(i, sistandard); - has_no_av &= !_input_pmt->IsAudio(i, sistandard); - } - _has_no_av = has_no_av; - - delete oldpmt; - } -} - -/** \fn HDHRRecorder::HandleMGT(const MasterGuideTable*) - * \brief Processes Master Guide Table, by enabling the - * scanning of all PIDs listed. - */ -/* -void HDHRRecorder::HandleMGT(const MasterGuideTable *mgt) -{ - VERBOSE(VB_IMPORTANT, LOC + "HandleMGT()"); - for (unsigned int i = 0; i < mgt->TableCount(); i++) - { - GetStreamData()->AddListeningPID(mgt->TablePID(i)); - _channel->AddPID(mgt->TablePID(i), false); - } - _channel->UpdateFilters(); -} -*/ - bool HDHRRecorder::Open(void) { if (IsOpen()) @@ -243,29 +53,6 @@ void HDHRRecorder::Close(void) VERBOSE(VB_RECORD, LOC + "Close() -- end"); } -void HDHRRecorder::SetStreamData(void) -{ - _stream_data->AddMPEGSPListener(this); - _stream_data->AddMPEGListener(this); - - DVBStreamData *dvb = dynamic_cast(_stream_data); - if (dvb) - dvb->AddDVBMainListener(this); - - ATSCStreamData *atsc = dynamic_cast(_stream_data); - - if (atsc && atsc->DesiredMinorChannel()) - atsc->SetDesiredChannel(atsc->DesiredMajorChannel(), - atsc->DesiredMinorChannel()); - else if (_stream_data->DesiredProgram() >= 0) - _stream_data->SetDesiredProgram(_stream_data->DesiredProgram()); -} - -ATSCStreamData *HDHRRecorder::GetATSCStreamData(void) -{ - return dynamic_cast(_stream_data); -} - void HDHRRecorder::StartRecording(void) { VERBOSE(VB_RECORD, LOC + "StartRecording -- begin"); @@ -273,13 +60,19 @@ void HDHRRecorder::StartRecording(void) /* Create video socket. */ if (!Open()) { - _error = true; - VERBOSE(VB_RECORD, LOC + "StartRecording -- end 1"); + _error = "Failed to open HDHRRecorder device"; + VERBOSE(VB_IMPORTANT, LOC_ERR + _error); return; } - _request_recording = true; - _recording = true; + _continuity_error_count = 0; + + { + QMutexLocker locker(&pauseLock); + request_recording = true; + recording = true; + recordingWait.wakeAll(); + } // Make sure the first things in the file are a PAT & PMT bool tmp = _wait_for_keyframe_option; @@ -292,13 +85,22 @@ void HDHRRecorder::StartRecording(void) _stream_data->AddWritingListener(this); _stream_handler->AddListener(_stream_data); - while (_request_recording && !_error) + while (IsRecordingRequested() && !IsErrored()) { - usleep(50000); - if (PauseAndWait()) continue; + if (!IsRecordingRequested()) + break; + + { // sleep 100 milliseconds unless StopRecording() or Unpause() + // is called, just to avoid running this too often. + QMutexLocker locker(&pauseLock); + if (!request_recording || request_pause) + continue; + unpauseWait.wait(&pauseLock, 100); + } + if (!_input_pmt) { VERBOSE(VB_GENERAL, LOC_WARN + @@ -309,10 +111,8 @@ void HDHRRecorder::StartRecording(void) if (!_stream_handler->IsRunning()) { - _error = true; - - VERBOSE(VB_IMPORTANT, LOC_ERR + - "Stream handler died unexpectedly."); + _error = "Stream handler died unexpectedly."; + VERBOSE(VB_IMPORTANT, LOC_ERR + _error); } } @@ -326,27 +126,13 @@ void HDHRRecorder::StartRecording(void) FinishRecording(); - _recording = false; + QMutexLocker locker(&pauseLock); + recording = false; + recordingWait.wakeAll(); VERBOSE(VB_RECORD, LOC + "StartRecording -- end"); } -void HDHRRecorder::ResetForNewFile(void) -{ - DTVRecorder::ResetForNewFile(); - - memset(_stream_id, 0, sizeof(_stream_id)); - memset(_pid_status, 0, sizeof(_pid_status)); - memset(_continuity_counter, 0xff, sizeof(_continuity_counter)); -} - -void HDHRRecorder::StopRecording(void) -{ - _request_recording = false; - while (_recording) - usleep(2000); -} - bool HDHRRecorder::PauseAndWait(int timeout) { QMutexLocker locker(&pauseLock); @@ -375,108 +161,9 @@ bool HDHRRecorder::PauseAndWait(int timeout) return IsPaused(true); } -bool HDHRRecorder::ProcessVideoTSPacket(const TSPacket &tspacket) -{ - if (!ringBuffer) - return true; - - uint streamType = _stream_id[tspacket.PID()]; - - // Check for keyframes and count frames - if (streamType == StreamID::H264Video) - { - _buffer_packets = !FindH264Keyframes(&tspacket); - if (!_seen_sps) - return true; - } - else - { - _buffer_packets = !FindMPEG2Keyframes(&tspacket); - } - - return ProcessAVTSPacket(tspacket); -} - -bool HDHRRecorder::ProcessAudioTSPacket(const TSPacket &tspacket) -{ - if (!ringBuffer) - return true; - - _buffer_packets = !FindAudioKeyframes(&tspacket); - return ProcessAVTSPacket(tspacket); -} - -/// Common code for processing either audio or video packets -bool HDHRRecorder::ProcessAVTSPacket(const TSPacket &tspacket) -{ - const uint pid = tspacket.PID(); - // Sync recording start to first keyframe - if (_wait_for_keyframe_option && _first_keyframe < 0) - return true; - - // Sync streams to the first Payload Unit Start Indicator - // _after_ first keyframe iff _wait_for_keyframe_option is true - if (!(_pid_status[pid] & kPayloadStartSeen) && tspacket.HasPayload()) - { - if (!tspacket.PayloadStart()) - return true; // not payload start - drop packet - - VERBOSE(VB_RECORD, - QString("PID 0x%1 Found Payload Start").arg(pid,0,16)); - - _pid_status[pid] |= kPayloadStartSeen; - } - - BufferedWrite(tspacket); - - return true; -} - -bool HDHRRecorder::ProcessTSPacket(const TSPacket &tspacket) +QString HDHRRecorder::GetSIStandard(void) const { - // Only create fake keyframe[s] if there are no audio/video streams - if (_input_pmt && _has_no_av) - { - _buffer_packets = !FindOtherKeyframes(&tspacket); - } - else - { - // There are audio/video streams. Only write the packet - // if audio/video key-frames have been found - if (_wait_for_keyframe_option && _first_keyframe < 0) - return true; - - _buffer_packets = true; - } - - BufferedWrite(tspacket); - - return true; + return _channel->GetSIStandard(); } -void HDHRRecorder::BufferedWrite(const TSPacket &tspacket) -{ - // Care must be taken to make sure that the packet actually gets written - // as the decision to actually write it has already been made - - // Do we have to buffer the packet for exact keyframe detection? - if (_buffer_packets) - { - int idx = _payload_buffer.size(); - _payload_buffer.resize(idx + TSPacket::kSize); - memcpy(&_payload_buffer[idx], tspacket.data(), TSPacket::kSize); - return; - } - - // We are free to write the packet, but if we have buffered packet[s] - // we have to write them first... - if (!_payload_buffer.empty()) - { - if (ringBuffer) - ringBuffer->Write(&_payload_buffer[0], _payload_buffer.size()); - _payload_buffer.clear(); - } - - if (ringBuffer) - ringBuffer->Write(tspacket.data(), TSPacket::kSize); -} +/* vim: set expandtab tabstop=4 shiftwidth=4: */ diff --git a/mythtv/libs/libmythtv/hdhrrecorder.h b/mythtv/libs/libmythtv/hdhrrecorder.h index ef36a4938eb..839cb8fc615 100644 --- a/mythtv/libs/libmythtv/hdhrrecorder.h +++ b/mythtv/libs/libmythtv/hdhrrecorder.h @@ -8,107 +8,34 @@ #define HDHOMERUNRECORDER_H_ // Qt includes -#include +#include +// MythTV includes #include "dtvrecorder.h" -#include "streamlisteners.h" -#include "eitscanner.h" class HDHRChannel; -class ProgramMapTable; -class MPEGStreamData; class HDHRStreamHandler; -typedef vector uint_vec_t; - -class HDHRRecorder : public DTVRecorder, - public DVBMainStreamListener, - public ATSCMainStreamListener, - public MPEGStreamListener, - public MPEGSingleProgramStreamListener, - public TSPacketListener, - public TSPacketListenerAV +class HDHRRecorder : public DTVRecorder { public: HDHRRecorder(TVRec *rec, HDHRChannel *channel); - ~HDHRRecorder(); - - void SetOptionsFromProfile(RecordingProfile *profile, - const QString &videodev, - const QString &audiodev, - const QString &vbidev); void StartRecording(void); - void ResetForNewFile(void); - void StopRecording(void); bool Open(void); bool IsOpen(void) const { return _stream_handler; } void Close(void); - // MPEG Stream Listener - void HandlePAT(const ProgramAssociationTable*); - void HandleCAT(const ConditionalAccessTable*) {} - void HandlePMT(uint pid, const ProgramMapTable*); - void HandleEncryptionStatus(uint /*pnum*/, bool /*encrypted*/) { } - - // MPEG Single Program Stream Listener - void HandleSingleProgramPAT(ProgramAssociationTable *pat); - void HandleSingleProgramPMT(ProgramMapTable *pmt); - - // ATSC Main - void HandleSTT(const SystemTimeTable*) {} - void HandleVCT(uint /*tsid*/, const VirtualChannelTable*) {} - void HandleMGT(const MasterGuideTable*) {} - - // DVBMainStreamListener - void HandleTDT(const TimeDateTable*) {} - void HandleNIT(const NetworkInformationTable*) {} - void HandleSDT(uint /*tsid*/, const ServiceDescriptionTable*) {} - - // TSPacketListener - bool ProcessTSPacket(const TSPacket &tspacket); - - // TSPacketListenerAV - bool ProcessVideoTSPacket(const TSPacket& tspacket); - bool ProcessAudioTSPacket(const TSPacket& tspacket); - - // Common audio/visual processing - bool ProcessAVTSPacket(const TSPacket &tspacket); - - void SetStreamData(void); - ATSCStreamData *GetATSCStreamData(void); - - void BufferedWrite(const TSPacket &tspacket); + QString GetSIStandard(void) const; private: - void TeardownAll(void); - void ReaderPaused(int fd); bool PauseAndWait(int timeout = 100); private: - HDHRChannel *_channel; - HDHRStreamHandler *_stream_handler; - - // general recorder stuff - mutable QMutex _pid_lock; - ProgramAssociationTable *_input_pat; ///< PAT on input side - ProgramMapTable *_input_pmt; ///< PMT on input side - bool _has_no_av; - - // TS recorder stuff - unsigned char _stream_id[0x1fff + 1]; - unsigned char _pid_status[0x1fff + 1]; - unsigned char _continuity_counter[0x1fff + 1]; - vector _scratch; - - // Constants - static const int TSPACKETS_BETWEEN_PSIP_SYNC; - static const int POLL_INTERVAL; - static const int POLL_WARNING_TIMEOUT; - - static const unsigned char kPayloadStartSeen = 0x2; + HDHRChannel *_channel; + HDHRStreamHandler *_stream_handler; }; #endif diff --git a/mythtv/libs/libmythtv/hdhrsignalmonitor.cpp b/mythtv/libs/libmythtv/hdhrsignalmonitor.cpp index 3d68a81480c..0b4ec631c96 100644 --- a/mythtv/libs/libmythtv/hdhrsignalmonitor.cpp +++ b/mythtv/libs/libmythtv/hdhrsignalmonitor.cpp @@ -89,7 +89,7 @@ HDHRChannel *HDHRSignalMonitor::GetHDHRChannel(void) */ void HDHRSignalMonitor::UpdateValues(void) { - if (!monitor_thread.isRunning() || exit) + if (!running || exit) return; if (streamHandlerStarted) @@ -104,9 +104,6 @@ void HDHRSignalMonitor::UpdateValues(void) return; } - if (!IsChannelTuned()) - return; - struct hdhomerun_tuner_status_t status; streamHandler->GetTunerStatus(&status); diff --git a/mythtv/libs/libmythtv/hdhrstreamhandler.cpp b/mythtv/libs/libmythtv/hdhrstreamhandler.cpp index e834d4f0909..4900a9b57bb 100644 --- a/mythtv/libs/libmythtv/hdhrstreamhandler.cpp +++ b/mythtv/libs/libmythtv/hdhrstreamhandler.cpp @@ -8,9 +8,6 @@ #include #endif -// Qt headers -#include - // MythTV headers #include "hdhrstreamhandler.h" #include "hdhrchannel.h" @@ -18,21 +15,17 @@ #include "streamlisteners.h" #include "mpegstreamdata.h" #include "cardutil.h" +#include "mythverbose.h" #include "mythlogging.h" -#define LOC QString("HDHRSH(%1): ").arg(_devicename) -#define LOC_WARN QString("HDHRSH(%1) Warning: ").arg(_devicename) -#define LOC_ERR QString("HDHRSH(%1) Error: ").arg(_devicename) - -QMap HDHRStreamHandler::_rec_supports_ts_monitoring; -QMutex HDHRStreamHandler::_rec_supports_ts_monitoring_lock; +#define LOC QString("HDHRSH(%1): ").arg(_device) +#define LOC_WARN QString("HDHRSH(%1) Warning: ").arg(_device) +#define LOC_ERR QString("HDHRSH(%1) Error: ").arg(_device) QMap HDHRStreamHandler::_handlers; QMap HDHRStreamHandler::_handlers_refcnt; QMutex HDHRStreamHandler::_handlers_lock; -//#define DEBUG_PID_FILTERS - HDHRStreamHandler *HDHRStreamHandler::Get(const QString &devname) { QMutexLocker locker(&_handlers_lock); @@ -69,7 +62,7 @@ void HDHRStreamHandler::Return(HDHRStreamHandler * & ref) { QMutexLocker locker(&_handlers_lock); - QString devname = ref->_devicename; + QString devname = ref->_device; QMap::iterator rit = _handlers_refcnt.find(devname); if (rit == _handlers_refcnt.end()) @@ -102,178 +95,48 @@ void HDHRStreamHandler::Return(HDHRStreamHandler * & ref) ref = NULL; } -HDHRStreamHandler::HDHRStreamHandler(const QString &devicename) : +HDHRStreamHandler::HDHRStreamHandler(const QString &device) : + StreamHandler(device), _hdhomerun_device(NULL), _tuner(-1), - _devicename(devicename), - _start_stop_lock(QMutex::Recursive), - _run(false), - - _pid_lock(QMutex::Recursive), - _open_pid_filters(0), - _listener_lock(QMutex::Recursive), _hdhr_lock(QMutex::Recursive) { } -HDHRStreamHandler::~HDHRStreamHandler() -{ - if (!_stream_data_list.empty()) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + "dtor & _stream_data_list not empty"); - } -} - -void HDHRStreamHandler::AddListener(MPEGStreamData *data) -{ - VERBOSE(VB_RECORD, LOC + QString("AddListener(0x%1) -- begin") - .arg((uint64_t)data,0,16)); - if (!data) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + - QString("AddListener(0x%1) -- null data") - .arg((uint64_t)data,0,16)); - return; - } - - _listener_lock.lock(); - - VERBOSE(VB_RECORD, LOC + QString("AddListener(0x%1) -- locked") - .arg((uint64_t)data,0,16)); - - _stream_data_list.push_back(data); - - _listener_lock.unlock(); - - Start(); - - VERBOSE(VB_RECORD, LOC + QString("AddListener(0x%1) -- end") - .arg((uint64_t)data,0,16)); -} - -void HDHRStreamHandler::RemoveListener(MPEGStreamData *data) +/** \fn HDHRStreamHandler::run(void) + * \brief Reads HDHomeRun socket for tables & data + */ +void HDHRStreamHandler::run(void) { - VERBOSE(VB_RECORD, LOC + QString("RemoveListener(0x%1) -- begin") - .arg((uint64_t)data,0,16)); - if (!data) + threadRegister("HDHRStreamHandler"); + /* Create TS socket. */ + if (!hdhomerun_device_stream_start(_hdhomerun_device)) { VERBOSE(VB_IMPORTANT, LOC_ERR + - QString("RemoveListener(0x%1) -- null data") - .arg((uint64_t)data,0,16)); - return; - } - - _listener_lock.lock(); - - VERBOSE(VB_RECORD, LOC + QString("RemoveListener(0x%1) -- locked") - .arg((uint64_t)data,0,16)); - - vector::iterator it = - find(_stream_data_list.begin(), _stream_data_list.end(), data); - - if (it != _stream_data_list.end()) - _stream_data_list.erase(it); - - if (_stream_data_list.empty()) - { - _listener_lock.unlock(); - Stop(); - } - else - { - _listener_lock.unlock(); - } - - VERBOSE(VB_RECORD, LOC + QString("RemoveListener(0x%1) -- end") - .arg((uint64_t)data,0,16)); -} - -void HDHRReadThread::run(void) -{ - if (!m_parent) + "Starting recording (set target failed). Aborting."); + _error = true; + threadDeregister(); return; - - threadRegister("HDHRRead"); - m_parent->Run(); - threadDeregister(); -} - -void HDHRStreamHandler::Start(void) -{ - QMutexLocker locker(&_start_stop_lock); - - _eit_pids.clear(); - - if (!IsRunning()) - { - _run = true; - _reader_thread.SetParent(this); - _reader_thread.start(); - - if (!_reader_thread.isRunning()) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + "Start: Failed to create thread."); - return; - } - } -} - -void HDHRStreamHandler::Stop(void) -{ - QMutexLocker locker(&_start_stop_lock); - - if (IsRunning()) - { - _run = false; - _reader_thread.wait(); } -} - -void HDHRStreamHandler::Run(void) -{ - RunTS(); -} + hdhomerun_device_stream_flush(_hdhomerun_device); -/** \fn HDHRStreamHandler::RunTS(void) - * \brief Uses TS filtering devices to read a DVB device for tables & data - * - * This supports all types of MPEG based stream data, but is extreemely - * slow with DVB over USB 1.0 devices which for efficiency reasons buffer - * a stream until a full block transfer buffer full of the requested - * tables is available. This takes a very long time when you are just - * waiting for a PAT or PMT table, and the buffer is hundreds of packets - * in size. - */ -void HDHRStreamHandler::RunTS(void) -{ - int remainder = 0; + SetRunning(true, false, false); /* Calculate buffer size */ uint buffersize = gCoreContext->GetNumSetting( "HDRingbufferSize", 50 * TSPacket::kSize) * 1024; buffersize /= VIDEO_DATA_PACKET_SIZE; buffersize *= VIDEO_DATA_PACKET_SIZE; - - // Buffer should be at least about 1MB.. buffersize = max(49 * TSPacket::kSize * 128, buffersize); - /* Create TS socket. */ - if (!hdhomerun_device_stream_start(_hdhomerun_device)) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + - "Starting recording (set target failed). Aborting."); - return; - } - hdhomerun_device_stream_flush(_hdhomerun_device); - - bool _error = false; - VERBOSE(VB_RECORD, LOC + "RunTS(): begin"); - while (_run && !_error) + int remainder = 0; + while (_running_desired && !_error) { UpdateFiltersFromStreamData(); + UpdateFilters(); size_t read_size = 64 * 1024; // read about 64KB read_size /= VIDEO_DATA_PACKET_SIZE; @@ -299,11 +162,9 @@ void HDHRStreamHandler::RunTS(void) continue; } - for (uint i = 0; i < _stream_data_list.size(); i++) - { - remainder = _stream_data_list[i]->ProcessData( - data_buffer, data_length); - } + StreamDataList::const_iterator sit = _stream_data_list.begin(); + for (; sit != _stream_data_list.end(); ++sit) + remainder = sit.key()->ProcessData(data_buffer, data_length); _listener_lock.unlock(); if (remainder != 0) @@ -319,63 +180,9 @@ void HDHRStreamHandler::RunTS(void) hdhomerun_device_stream_stop(_hdhomerun_device); VERBOSE(VB_RECORD, LOC + "RunTS(): " + "end"); -} - -bool HDHRStreamHandler::AddPIDFilter(uint pid, bool do_update) -{ -#ifdef DEBUG_PID_FILTERS - VERBOSE(VB_RECORD, LOC + QString("AddPIDFilter(0x%1)") - .arg(pid, 0, 16)); -#endif // DEBUG_PID_FILTERS - - QMutexLocker writing_locker(&_pid_lock); - - vector::iterator it; - it = lower_bound(_pid_info.begin(), _pid_info.end(), pid); - if (it != _pid_info.end() && *it == pid) - return true; - - _pid_info.insert(it, pid); - - if (do_update) - return UpdateFilters(); - - return true; -} - -bool HDHRStreamHandler::RemovePIDFilter(uint pid, bool do_update) -{ -#ifdef DEBUG_PID_FILTERS - VERBOSE(VB_RECORD, LOC + - QString("RemovePIDFilter(0x%1)").arg(pid, 0, 16)); -#endif // DEBUG_PID_FILTERS - QMutexLocker write_locker(&_pid_lock); - - vector::iterator it; - it = lower_bound(_pid_info.begin(), _pid_info.end(), pid); - if ((it == _pid_info.end()) || (*it != pid)) - return false; - - _pid_info.erase(it); - - if (do_update) - return UpdateFilters(); - - return true; -} - -bool HDHRStreamHandler::RemoveAllPIDFilters(void) -{ - QMutexLocker write_locker(&_pid_lock); - -#ifdef DEBUG_PID_FILTERS - VERBOSE(VB_RECORD, LOC + "RemoveAllPIDFilters()"); -#endif // DEBUG_PID_FILTERS - - _pid_info.clear(); - - return UpdateFilters(); + SetRunning(false, false, false); + threadDeregister(); } static QString filt_str(uint pid) @@ -389,100 +196,6 @@ static QString filt_str(uint pid) .arg(pid2,0,16).arg(pid3,0,16); } -void HDHRStreamHandler::UpdateListeningForEIT(void) -{ - vector add_eit, del_eit; - - QMutexLocker read_locker(&_listener_lock); - - for (uint i = 0; i < _stream_data_list.size(); i++) - { - MPEGStreamData *sd = _stream_data_list[i]; - if (sd->HasEITPIDChanges(_eit_pids) && - sd->GetEITPIDChanges(_eit_pids, add_eit, del_eit)) - { - for (uint i = 0; i < del_eit.size(); i++) - { - uint_vec_t::iterator it; - it = find(_eit_pids.begin(), _eit_pids.end(), del_eit[i]); - if (it != _eit_pids.end()) - _eit_pids.erase(it); - sd->RemoveListeningPID(del_eit[i]); - } - - for (uint i = 0; i < add_eit.size(); i++) - { - _eit_pids.push_back(add_eit[i]); - sd->AddListeningPID(add_eit[i]); - } - } - } -} - -bool HDHRStreamHandler::UpdateFiltersFromStreamData(void) -{ - UpdateListeningForEIT(); - - pid_map_t pids; - - { - QMutexLocker read_locker(&_listener_lock); - - for (uint i = 0; i < _stream_data_list.size(); i++) - _stream_data_list[i]->GetPIDs(pids); - } - - uint_vec_t add_pids; - vector del_pids; - - { - QMutexLocker read_locker(&_pid_lock); - - // PIDs that need to be added.. - pid_map_t::const_iterator lit = pids.constBegin(); - for (; lit != pids.constEnd(); ++lit) - { - vector::iterator it; - it = lower_bound(_pid_info.begin(), _pid_info.end(), lit.key()); - if (it == _pid_info.end() || *it != lit.key()) - add_pids.push_back(lit.key()); - } - - // PIDs that need to be removed.. - vector::const_iterator fit = _pid_info.begin(); - for (; fit != _pid_info.end(); ++fit) - { - bool in_pids = pids.find(*fit) != pids.end(); - if (!in_pids) - del_pids.push_back(*fit); - } - } - - bool need_update = false; - - // Remove PIDs - bool ok = true; - vector::iterator dit = del_pids.begin(); - for (; dit != del_pids.end(); ++dit) - { - need_update = true; - ok &= RemovePIDFilter(*dit, false); - } - - // Add PIDs - vector::iterator ait = add_pids.begin(); - for (; ait != add_pids.end(); ++ait) - { - need_update = true; - ok &= AddPIDFilter(*ait, false); - } - - if (need_update) - return UpdateFilters() && ok; - - return ok; -} - bool HDHRStreamHandler::UpdateFilters(void) { if (_tune_mode == hdhrTuneModeFrequency) @@ -490,7 +203,8 @@ bool HDHRStreamHandler::UpdateFilters(void) if (_tune_mode != hdhrTuneModeFrequencyPid) { - VERBOSE(VB_IMPORTANT, LOC_ERR + "UpdateFilters called in wrong tune mode"); + VERBOSE(VB_IMPORTANT, LOC_ERR + + "UpdateFilters called in wrong tune mode"); return false; } @@ -504,19 +218,15 @@ bool HDHRStreamHandler::UpdateFilters(void) vector range_min; vector range_max; - for (uint i = 0; i < _pid_info.size(); i++) + PIDInfoMap::const_iterator it = _pid_info.begin(); + for (; it != _pid_info.end(); ++it) { - uint pid_min = _pid_info[i]; - uint pid_max = pid_min; - for (uint j = i + 1; j < _pid_info.size(); j++) - { - if (pid_max + 1 != _pid_info[j]) - break; - pid_max++; - i++; - } - range_min.push_back(pid_min); - range_max.push_back(pid_max); + range_min.push_back(it.key()); + PIDInfoMap::const_iterator eit = it; + for (++eit; + (eit != _pid_info.end()) && (it.key() + 1 == eit.key()); + ++it, ++eit); + range_max.push_back(it.key()); } if (range_min.size() > 16) { @@ -549,18 +259,6 @@ bool HDHRStreamHandler::UpdateFilters(void) return filter == new_filter; } -PIDPriority HDHRStreamHandler::GetPIDPriority(uint pid) const -{ - QMutexLocker reading_locker(&_listener_lock); - - PIDPriority tmp = kPIDPriorityNone; - - for (uint i = 0; i < _stream_data_list.size(); i++) - tmp = max(tmp, _stream_data_list[i]->GetPIDPriority(pid)); - - return tmp; -} - bool HDHRStreamHandler::Open(void) { if (Connect()) @@ -595,7 +293,7 @@ void HDHRStreamHandler::Close(void) bool HDHRStreamHandler::Connect(void) { _hdhomerun_device = hdhomerun_device_create_from_str( - _devicename.toLocal8Bit().constData(), NULL); + _device.toLocal8Bit().constData(), NULL); if (!_hdhomerun_device) { diff --git a/mythtv/libs/libmythtv/hdhrstreamhandler.h b/mythtv/libs/libmythtv/hdhrstreamhandler.h index 7b6f74a5616..af75f0fbde7 100644 --- a/mythtv/libs/libmythtv/hdhrstreamhandler.h +++ b/mythtv/libs/libmythtv/hdhrstreamhandler.h @@ -6,16 +6,16 @@ #include using namespace std; -#include +#include #include -#include +#include #include "util.h" #include "DeviceReadBuffer.h" #include "mpegstreamdata.h" +#include "streamhandler.h" #include "dtvconfparserhelpers.h" -class QString; class HDHRStreamHandler; class DTVSignalMonitor; class HDHRChannel; @@ -38,31 +38,26 @@ enum HDHRTuneMode { typedef QMap FilterMap; -//#define RETUNE_TIMEOUT 5000 +// Note : This class never uses a DRB && always uses a TS reader. -class HDHRReadThread : public QThread -{ - Q_OBJECT - public: - HDHRReadThread() : m_parent(NULL) {} - void SetParent(HDHRStreamHandler *parent) { m_parent = parent; } - void run(void); - private: - HDHRStreamHandler *m_parent; -}; +// locking order +// _pid_lock -> _listener_lock -> _start_stop_lock +// -> _hdhr_lock -class HDHRStreamHandler : public ReaderPausedCB +class HDHRStreamHandler : public StreamHandler { - friend class HDHRReadThread; - public: static HDHRStreamHandler *Get(const QString &devicename); static void Return(HDHRStreamHandler * & ref); - void AddListener(MPEGStreamData *data); - void RemoveListener(MPEGStreamData *data); + virtual void AddListener(MPEGStreamData *data, + bool allow_section_reader = false, + bool needs_drb = false, + QString output_file = QString()) + { + StreamHandler::AddListener(data, false, false, output_file); + } // StreamHandler - bool IsRunning(void) const { return _reader_thread.isRunning(); } void GetTunerStatus(struct hdhomerun_tuner_status_t *status); bool IsConnected(void) const; vector GetTunerTypes(void) const { return _tuner_types; } @@ -73,13 +68,8 @@ class HDHRStreamHandler : public ReaderPausedCB bool TuneVChannel(const QString &vchn); bool EnterPowerSavingMode(void); - - // ReaderPausedCB - virtual void ReaderPaused(int fd) { (void) fd; } - private: HDHRStreamHandler(const QString &); - ~HDHRStreamHandler(); bool Connect(void); @@ -93,47 +83,18 @@ class HDHRStreamHandler : public ReaderPausedCB bool Open(void); void Close(void); - void Start(void); - void Stop(void); - - void Run(void); - void RunTS(void); + virtual void run(void); // QThread - void UpdateListeningForEIT(void); - bool UpdateFiltersFromStreamData(void); - bool AddPIDFilter(uint pid, bool do_update = true); - bool RemovePIDFilter(uint pid, bool do_update = true); - bool RemoveAllPIDFilters(void); - bool UpdateFilters(void); - - PIDPriority GetPIDPriority(uint pid) const; + virtual bool UpdateFilters(void); private: - hdhomerun_device_t *_hdhomerun_device; - uint _tuner; - QString _devicename; - vector _tuner_types; - HDHRTuneMode _tune_mode; // debug self check - - mutable QMutex _start_stop_lock; - HDHRReadThread _reader_thread; - bool _run; - - mutable QMutex _pid_lock; - vector _eit_pids; - vector _pid_info; // kept sorted - uint _open_pid_filters; - MythTimer _cycle_timer; - - mutable QMutex _listener_lock; - vector _stream_data_list; + hdhomerun_device_t *_hdhomerun_device; + uint _tuner; + vector _tuner_types; + HDHRTuneMode _tune_mode; // debug self check mutable QMutex _hdhr_lock; - // for caching TS monitoring supported value. - static QMutex _rec_supports_ts_monitoring_lock; - static QMap _rec_supports_ts_monitoring; - // for implementing Get & Return static QMutex _handlers_lock; static QMap _handlers; diff --git a/mythtv/libs/libmythtv/importrecorder.cpp b/mythtv/libs/libmythtv/importrecorder.cpp index ebfcf6ee9e9..ef259d6d39e 100644 --- a/mythtv/libs/libmythtv/importrecorder.cpp +++ b/mythtv/libs/libmythtv/importrecorder.cpp @@ -83,10 +83,13 @@ void ImportRecorder::StartRecording(void) { VERBOSE(VB_RECORD, LOC + "StartRecording -- begin"); + _continuity_error_count = 0; + { - QMutexLocker locker(&_end_of_recording_lock); - _request_recording = true; - _recording = true; + QMutexLocker locker(&pauseLock); + request_recording = true; + recording = true; + recordingWait.wakeAll(); } VERBOSE(VB_RECORD, LOC + "StartRecording -- " + @@ -94,13 +97,18 @@ void ImportRecorder::StartRecording(void) .arg(curRecording->GetPathname())); // retry opening the file until StopRecording() is called. - while (!Open() && _request_recording && !_error) - usleep(20000); + while (!Open() && IsRecordingRequested() && !IsErrored()) + { // sleep 250 milliseconds unless StopRecording() or Unpause() + // is called, just to avoid running this loop too often. + QMutexLocker locker(&pauseLock); + if (request_recording) + unpauseWait.wait(&pauseLock, 250); + } curRecording->SaveFilesize(ringBuffer->GetRealFileSize()); // build seek table - if (_import_fd && _request_recording && !_error) + if (_import_fd && IsRecordingRequested() && !IsErrored()) { MythCommFlagPlayer *cfp = new MythCommFlagPlayer(); RingBuffer *rb = RingBuffer::Create( @@ -124,21 +132,13 @@ void ImportRecorder::StartRecording(void) FinishRecording(); - QMutexLocker locker(&_end_of_recording_lock); - _recording = false; - _end_of_recording_wait.wakeAll(); + QMutexLocker locker(&pauseLock); + recording = false; + recordingWait.wakeAll(); VERBOSE(VB_RECORD, LOC + "StartRecording -- end"); } -void ImportRecorder::StopRecording(void) -{ - QMutexLocker locker(&_end_of_recording_lock); - _request_recording = false; - while (_recording) - _end_of_recording_wait.wait(&_end_of_recording_lock); -} - bool ImportRecorder::Open(void) { if (_import_fd >= 0) // already open diff --git a/mythtv/libs/libmythtv/importrecorder.h b/mythtv/libs/libmythtv/importrecorder.h index 7d62e4cc522..cf5b3b3f539 100644 --- a/mythtv/libs/libmythtv/importrecorder.h +++ b/mythtv/libs/libmythtv/importrecorder.h @@ -32,7 +32,6 @@ class ImportRecorder : public DTVRecorder const QString &vbidev); void StartRecording(void); - void StopRecording(void); bool Open(void); void Close(void); @@ -41,9 +40,6 @@ class ImportRecorder : public DTVRecorder private: int _import_fd; - - QMutex _end_of_recording_lock; - QWaitCondition _end_of_recording_wait; }; #endif // _IMPORT_RECORDER_H_ diff --git a/mythtv/libs/libmythtv/iptvchannel.cpp b/mythtv/libs/libmythtv/iptvchannel.cpp index e5b5a9b7126..dd8a7446072 100644 --- a/mythtv/libs/libmythtv/iptvchannel.cpp +++ b/mythtv/libs/libmythtv/iptvchannel.cpp @@ -59,9 +59,8 @@ bool IPTVChannel::Open(void) .arg(m_videodev)); } - bool open = IsOpen(); VERBOSE(VB_CHANNEL, LOC + "Open() -- end"); - return open; + return !m_freeboxchannels.empty(); } void IPTVChannel::Close(void) @@ -78,9 +77,8 @@ bool IPTVChannel::IsOpen(void) const VERBOSE(VB_CHANNEL, LOC + "IsOpen() -- begin"); QMutexLocker locker(&m_lock); VERBOSE(VB_CHANNEL, LOC + "IsOpen() -- locked"); - bool open = m_freeboxchannels.size() > 0; VERBOSE(VB_CHANNEL, LOC + "IsOpen() -- end"); - return open; + return !m_freeboxchannels.empty(); } bool IPTVChannel::SetChannelByString(const QString &channum) @@ -106,9 +104,6 @@ bool IPTVChannel::SetChannelByString(const QString &channum) return false; } - if (!(*it)->externalChanger.isEmpty() && !ChangeExternalChannel(channum)) - return false; - // Set the current channum to the new channel's channum QString tmp = channum; tmp.detach(); m_curchannelname = tmp; @@ -118,6 +113,8 @@ bool IPTVChannel::SetChannelByString(const QString &channum) /*netid*/ 0, /*tsid*/ 0, /*mpeg_prog_num*/ 1); + HandleScript(channum /* HACK treat channum as freqid */); + VERBOSE(VB_CHANNEL, LOC + "SetChannelByString() -- end"); return true; } diff --git a/mythtv/libs/libmythtv/iptvchannel.h b/mythtv/libs/libmythtv/iptvchannel.h index 6ea9f5a8bda..f54169eb544 100644 --- a/mythtv/libs/libmythtv/iptvchannel.h +++ b/mythtv/libs/libmythtv/iptvchannel.h @@ -17,31 +17,35 @@ class IPTVFeederWrapper; class IPTVChannel : public DTVChannel { + friend class IPTVSignalMonitor; + friend class IPTVRecorder; + public: IPTVChannel(TVRec *parent, const QString &videodev); ~IPTVChannel(); + // Commands bool Open(void); void Close(void); + bool SetChannelByString(const QString &channum); + // Gets bool IsOpen(void) const; - bool SetChannelByString(const QString &channum); - bool TuneMultiplex(uint /*mplexid*/, QString /*sourceid*/) - { return false; } // TODO - bool Tune(const DTVMultiplex &/*tuning*/, QString /*inputname*/) - { return false; } // TODO + // Channel scanning stuff + bool Tune(const DTVMultiplex&, QString) { return true; } + private: IPTVChannelInfo GetCurrentChanInfo(void) const { return GetChanInfo(m_curchannelname); } IPTVFeederWrapper *GetFeeder(void) { return m_feeder; } const IPTVFeederWrapper *GetFeeder(void) const { return m_feeder; } - private: IPTVChannelInfo GetChanInfo( const QString &channum, uint sourceid = 0) const; + private: QString m_videodev; fbox_chan_map_t m_freeboxchannels; IPTVFeederWrapper *m_feeder; diff --git a/mythtv/libs/libmythtv/iptvrecorder.cpp b/mythtv/libs/libmythtv/iptvrecorder.cpp index ac2d7cdcf1a..1487089f481 100644 --- a/mythtv/libs/libmythtv/iptvrecorder.cpp +++ b/mythtv/libs/libmythtv/iptvrecorder.cpp @@ -47,7 +47,7 @@ bool IPTVRecorder::Open(void) !_channel->GetFeeder()->Open(chaninfo.m_url)); VERBOSE(VB_RECORD, LOC + QString("Open() -- end err(%1)").arg(_error)); - return !_error; + return !IsErrored(); } void IPTVRecorder::Close(void) @@ -81,7 +81,7 @@ bool IPTVRecorder::PauseAndWait(int timeout) { paused = false; - if (_recording && !_channel->GetFeeder()->IsOpen()) + if (recording && !_channel->GetFeeder()->IsOpen()) Open(); if (_stream_data) @@ -103,14 +103,21 @@ void IPTVRecorder::StartRecording(void) } // Start up... - _recording = true; - _request_recording = true; + { + QMutexLocker locker(&pauseLock); + request_recording = true; + recording = true; + recordingWait.wakeAll(); + } - while (_request_recording) + while (IsRecordingRequested() && !IsErrored()) { if (PauseAndWait()) continue; + if (!IsRecordingRequested()) + break; + if (!_channel->GetFeeder()->IsOpen()) { usleep(5000); @@ -125,21 +132,11 @@ void IPTVRecorder::StartRecording(void) FinishRecording(); Close(); - VERBOSE(VB_RECORD, LOC + "StartRecording() -- end"); - _recording = false; -} - -void IPTVRecorder::StopRecording(void) -{ - VERBOSE(VB_RECORD, LOC + "StopRecording() -- begin"); - Pause(); - _channel->GetFeeder()->Close(); - - _request_recording = false; - while (_recording) - usleep(5000); + QMutexLocker locker(&pauseLock); + recording = false; + recordingWait.wakeAll(); - VERBOSE(VB_RECORD, LOC + "StopRecording() -- end"); + VERBOSE(VB_RECORD, LOC + "StartRecording() -- end"); } // =================================================== @@ -210,13 +207,13 @@ void IPTVRecorder::AddData(const unsigned char *data, unsigned int dataSize) } } -void IPTVRecorder::ProcessTSPacket(const TSPacket& tspacket) +bool IPTVRecorder::ProcessTSPacket(const TSPacket& tspacket) { if (!_stream_data) - return; + return true; if (tspacket.TransportError() || tspacket.Scrambled()) - return; + return true; if (tspacket.HasAdaptationField()) _stream_data->HandleAdaptationFieldControl(&tspacket); @@ -249,6 +246,8 @@ void IPTVRecorder::ProcessTSPacket(const TSPacket& tspacket) else if (_stream_data->IsWritingPID(lpid)) BufferedWrite(tspacket); } + + return true; } void IPTVRecorder::SetStreamData(void) @@ -256,24 +255,4 @@ void IPTVRecorder::SetStreamData(void) _stream_data->AddMPEGSPListener(this); } -void IPTVRecorder::HandleSingleProgramPAT(ProgramAssociationTable *pat) -{ - if (!pat) - return; - - int next = (pat->tsheader()->ContinuityCounter()+1)&0xf; - pat->tsheader()->SetContinuityCounter(next); - BufferedWrite(*(reinterpret_cast(pat->tsheader()))); -} - -void IPTVRecorder::HandleSingleProgramPMT(ProgramMapTable *pmt) -{ - if (!pmt) - return; - - int next = (pmt->tsheader()->ContinuityCounter()+1)&0xf; - pmt->tsheader()->SetContinuityCounter(next); - BufferedWrite(*(reinterpret_cast(pmt->tsheader()))); -} - /* vim: set expandtab tabstop=4 shiftwidth=4: */ diff --git a/mythtv/libs/libmythtv/iptvrecorder.h b/mythtv/libs/libmythtv/iptvrecorder.h index edb735cdf5d..db533822b65 100644 --- a/mythtv/libs/libmythtv/iptvrecorder.h +++ b/mythtv/libs/libmythtv/iptvrecorder.h @@ -18,8 +18,7 @@ class IPTVChannel; /** \brief Processes data from a IPTVFeeder and writes it to disk. */ -class IPTVRecorder : public DTVRecorder, public TSDataListener, - public MPEGSingleProgramStreamListener +class IPTVRecorder : public DTVRecorder, public TSDataListener { friend class IPTVMediaSink; @@ -31,24 +30,21 @@ class IPTVRecorder : public DTVRecorder, public TSDataListener, void Close(void); virtual void StartRecording(void); - virtual void StopRecording(void); virtual void SetOptionsFromProfile(RecordingProfile*, const QString&, const QString&, const QString&) {} virtual void SetStreamData(void); + virtual bool IsExternalChannelChangeSupported(void) { return true; } + private: - void ProcessTSPacket(const TSPacket& tspacket); + bool ProcessTSPacket(const TSPacket &tspacket); virtual bool PauseAndWait(int timeout = 100); // implements TSDataListener void AddData(const unsigned char *data, unsigned int dataSize); - // implements MPEGSingleProgramStreamListener - void HandleSingleProgramPAT(ProgramAssociationTable *pat); - void HandleSingleProgramPMT(ProgramMapTable *pmt); - private: IPTVChannel *_channel; diff --git a/mythtv/libs/libmythtv/iptvsignalmonitor.cpp b/mythtv/libs/libmythtv/iptvsignalmonitor.cpp index ddca9760c71..13ba8f005ac 100644 --- a/mythtv/libs/libmythtv/iptvsignalmonitor.cpp +++ b/mythtv/libs/libmythtv/iptvsignalmonitor.cpp @@ -16,6 +16,13 @@ #define LOC QString("IPTVSM(%1): ").arg(channel->GetDevice()) #define LOC_ERR QString("IPTVSM(%1), Error: ").arg(channel->GetDevice()) +void IPTVTableMonitorThread::run(void) +{ + threadRegister("IPTVTableMonitor"); + m_parent->RunTableMonitor(); + threadDeregister(); +} + /** \fn IPTVSignalMonitor::IPTVSignalMonitor(int,IPTVChannel*,uint64_t) * \brief Initializes signal lock and signal values. * @@ -33,7 +40,8 @@ IPTVSignalMonitor::IPTVSignalMonitor(int db_cardnum, IPTVChannel *_channel, uint64_t _flags) : - DTVSignalMonitor(db_cardnum, _channel, _flags) + DTVSignalMonitor(db_cardnum, _channel, _flags), + dtvMonitorRunning(false), tableMonitorThread(NULL) { bool isLocked = false; IPTVChannelInfo chaninfo = GetChannel()->GetCurrentChanInfo(); @@ -69,29 +77,23 @@ void IPTVSignalMonitor::Stop(void) DBG_SM("Stop", "begin"); GetChannel()->GetFeeder()->RemoveListener(this); SignalMonitor::Stop(); - if (table_monitor_thread.isRunning()) + if (tableMonitorThread) { GetChannel()->GetFeeder()->Stop(); - table_monitor_thread.wait(); + dtvMonitorRunning = false; + tableMonitorThread->wait(); + delete tableMonitorThread; + tableMonitorThread = NULL; } DBG_SM("Stop", "end"); } -void IPTVMonitorThread::run(void) -{ - if (!m_parent) - return; - - threadRegister("IPTVMonitor"); - m_parent->RunTableMonitor(); - threadDeregister(); -} - /** \fn IPTVSignalMonitor::RunTableMonitor(void) */ void IPTVSignalMonitor::RunTableMonitor(void) { DBG_SM("Run", "begin"); + dtvMonitorRunning = true; GetStreamData()->AddListeningPID(0); @@ -99,6 +101,9 @@ void IPTVSignalMonitor::RunTableMonitor(void) GetChannel()->GetFeeder()->Run(); GetChannel()->GetFeeder()->RemoveListener(this); + while (dtvMonitorRunning) + usleep(10000); + DBG_SM("Run", "end"); } @@ -116,13 +121,10 @@ void IPTVSignalMonitor::AddData( */ void IPTVSignalMonitor::UpdateValues(void) { - if (!monitor_thread.isRunning() || exit) - return; - - if (!IsChannelTuned()) + if (!running || exit) return; - if (table_monitor_thread.isRunning()) + if (dtvMonitorRunning) { EmitStatus(); if (IsAllGood()) @@ -150,12 +152,10 @@ void IPTVSignalMonitor::UpdateValues(void) kDTVSigMon_WaitForMGT | kDTVSigMon_WaitForVCT | kDTVSigMon_WaitForNIT | kDTVSigMon_WaitForSDT)) { - table_monitor_thread.SetParent(this); - table_monitor_thread.start(); - + tableMonitorThread = new IPTVTableMonitorThread(this); DBG_SM("UpdateValues", "Waiting for table monitor to start"); - while (!table_monitor_thread.isRunning()) - usleep(50); + while (!dtvMonitorRunning) + usleep(5000); DBG_SM("UpdateValues", "Table monitor started"); } diff --git a/mythtv/libs/libmythtv/iptvsignalmonitor.h b/mythtv/libs/libmythtv/iptvsignalmonitor.h index b4ad6a18659..72dcc430a20 100644 --- a/mythtv/libs/libmythtv/iptvsignalmonitor.h +++ b/mythtv/libs/libmythtv/iptvsignalmonitor.h @@ -11,22 +11,22 @@ class IPTVChannel; class IPTVSignalMonitor; -class IPTVMonitorThread : public QThread +class IPTVTableMonitorThread : public QThread { Q_OBJECT public: - IPTVMonitorThread() : m_parent(NULL) {} - void SetParent(IPTVSignalMonitor *parent) { m_parent = parent; } - void run(void); + IPTVTableMonitorThread(IPTVSignalMonitor *p) : m_parent(p) { start(); } + virtual ~IPTVTableMonitorThread() { wait(); } + virtual void run(void); private: IPTVSignalMonitor *m_parent; }; -class IPTVSignalMonitor : public QObject, public DTVSignalMonitor, public TSDataListener +class IPTVSignalMonitor : public DTVSignalMonitor, public TSDataListener { Q_OBJECT - friend class IPTVMonitorThread; + friend class IPTVTableMonitorThread; public: IPTVSignalMonitor(int db_cardnum, IPTVChannel *_channel, uint64_t _flags = 0); @@ -48,7 +48,8 @@ class IPTVSignalMonitor : public QObject, public DTVSignalMonitor, public TSData IPTVChannel *GetChannel(void); protected: - IPTVMonitorThread table_monitor_thread; + volatile bool dtvMonitorRunning; + IPTVTableMonitorThread *tableMonitorThread; }; #endif // _IPTVSIGNALMONITOR_H_ diff --git a/mythtv/libs/libmythtv/libmythtv.pro b/mythtv/libs/libmythtv/libmythtv.pro index 656df67ba31..e9363b07e01 100644 --- a/mythtv/libs/libmythtv/libmythtv.pro +++ b/mythtv/libs/libmythtv/libmythtv.pro @@ -36,7 +36,6 @@ DEPENDPATH += ../libmythlivemedia/UsageEnvironment DEPENDPATH += ../libmythbase ../libmythui DEPENDPATH += ../libmythupnp - INCLUDEPATH += .. ../.. # for avlib headers INCLUDEPATH += ../../external/FFmpeg INCLUDEPATH += $$DEPENDPATH @@ -113,11 +112,11 @@ cygwin:DEFINES += _WIN32 using_valgrind:DEFINES += USING_VALGRIND # old libvbitext (Caption decoder) -using_v4l { +#using_v4l { HEADERS += vbitext/cc.h vbitext/dllist.h vbitext/hamm.h vbitext/lang.h HEADERS += vbitext/vbi.h vbitext/vt.h SOURCES += vbitext/cc.cpp vbitext/vbi.c vbitext/hamm.c vbitext/lang.c -} +#} # mmx macros from avlib contains( HAVE_MMX, yes ) { @@ -149,6 +148,7 @@ HEADERS += filtermanager.h recordingprofile.h HEADERS += remoteencoder.h videosource.h HEADERS += cardutil.h sourceutil.h HEADERS += videometadatautil.h +HEADERS += vbi608extractor.h HEADERS += cc608decoder.h cc608reader.h HEADERS += cc708decoder.h cc708reader.h HEADERS += cc708window.h subtitlereader.h @@ -176,6 +176,7 @@ SOURCES += filtermanager.cpp recordingprofile.cpp SOURCES += remoteencoder.cpp videosource.cpp SOURCES += cardutil.cpp sourceutil.cpp SOURCES += videometadatautil.cpp +SOURCES += vbi608extractor.cpp SOURCES += cc608decoder.cpp cc608reader.cpp SOURCES += cc708decoder.cpp cc708reader.cpp SOURCES += cc708window.cpp subtitlereader.cpp @@ -402,6 +403,7 @@ using_backend { # Channel stuff HEADERS += channelbase.h dtvchannel.h HEADERS += signalmonitor.h dtvsignalmonitor.h + HEADERS += scriptsignalmonitor.h HEADERS += inputinfo.h inputgroupmap.h SOURCES += channelbase.cpp dtvchannel.cpp SOURCES += signalmonitor.cpp dtvsignalmonitor.cpp @@ -483,10 +485,9 @@ using_backend { DEFINES += USING_OSS } - HEADERS += channelchangemonitor.h - SOURCES += channelchangemonitor.cpp - # Support for Video4Linux devices + HEADERS += v4lrecorder.h + SOURCES += v4lrecorder.cpp using_v4l { HEADERS += v4lchannel.h analogsignalmonitor.h SOURCES += v4lchannel.cpp analogsignalmonitor.cpp @@ -550,6 +551,9 @@ using_backend { SOURCES += hdhrsignalmonitor.cpp hdhrchannel.cpp SOURCES += hdhrrecorder.cpp hdhrstreamhandler.cpp + HEADERS *= streamhandler.h + SOURCES *= streamhandler.cpp + DEFINES += USING_HDHOMERUN } @@ -579,6 +583,9 @@ using_backend { HEADERS += dvbrecorder.h dvbstreamhandler.h SOURCES += dvbrecorder.cpp dvbstreamhandler.cpp + HEADERS *= streamhandler.h + SOURCES *= streamhandler.cpp + # Misc HEADERS += dvbdev/dvbci.h SOURCES += dvbdev/dvbci.cpp @@ -586,6 +593,21 @@ using_backend { DEFINES += USING_DVB } + using_asi { + # Channel stuff + HEADERS += asichannel.h asisignalmonitor.h + SOURCES += asichannel.cpp asisignalmonitor.cpp + + # ASI Recorder + HEADERS += asirecorder.h asistreamhandler.h + SOURCES += asirecorder.cpp asistreamhandler.cpp + + HEADERS *= streamhandler.h + SOURCES *= streamhandler.cpp + + DEFINES += USING_ASI + } + DEFINES += USING_BACKEND } diff --git a/mythtv/libs/libmythtv/mpegrecorder.cpp b/mythtv/libs/libmythtv/mpegrecorder.cpp index 043b44305d8..80fd6715242 100644 --- a/mythtv/libs/libmythtv/mpegrecorder.cpp +++ b/mythtv/libs/libmythtv/mpegrecorder.cpp @@ -79,18 +79,15 @@ const char* MpegRecorder::aspectRatio[] = }; MpegRecorder::MpegRecorder(TVRec *rec) : - DTVRecorder(rec), + V4LRecorder(rec), // Debugging variables deviceIsMpegFile(false), bufferSize(0), // Driver info card(QString::null), driver(QString::null), - version(0), usingv4l2(false), - has_buggy_vbi(true), has_v4l2_vbi(false), - requires_special_pause(false), + version(0), + supports_sliced_vbi(false), // State - recording(false), encoding(false), start_stop_encoding_lock(QMutex::Recursive), - recording_wait_lock(), recording_wait(), // Pausing state cleartimeonpause(false), // Encoding info @@ -106,14 +103,8 @@ MpegRecorder::MpegRecorder(TVRec *rec) : high_mpeg4avgbitrate(13500), high_mpeg4peakbitrate(20200), // Input file descriptors chanfd(-1), readfd(-1), - _device_read_buffer(NULL), - // Statistics - _continuity_error_count(0), _stream_overflow_count(0), - _bad_packet_count(0) + _device_read_buffer(NULL) { - memset(_stream_id, 0, sizeof(_stream_id)); - memset(_pid_status, 0, sizeof(_pid_status)); - memset(_continuity_counter, 0, sizeof(_continuity_counter)); } MpegRecorder::~MpegRecorder() @@ -204,7 +195,7 @@ void MpegRecorder::SetOption(const QString &opt, int value) else if (opt.left(4) == "high") high_mpeg4avgbitrate = value; else - RecorderBase::SetOption(opt, value); + V4LRecorder::SetOption(opt, value); } else if (opt.right(17) == "_mpeg4peakbitrate") { @@ -215,10 +206,10 @@ void MpegRecorder::SetOption(const QString &opt, int value) else if (opt.left(4) == "high") high_mpeg4peakbitrate = value; else - RecorderBase::SetOption(opt, value); + V4LRecorder::SetOption(opt, value); } else - RecorderBase::SetOption(opt, value); + V4LRecorder::SetOption(opt, value); } void MpegRecorder::SetOption(const QString &opt, const QString &value) @@ -294,7 +285,7 @@ void MpegRecorder::SetOption(const QString &opt, const QString &value) } else { - RecorderBase::SetOption(opt, value); + V4LRecorder::SetOption(opt, value); } } @@ -319,7 +310,7 @@ void MpegRecorder::SetOptionsFromProfile(RecordingProfile *profile, { SetOption("videodevice", videodev); } - + SetOption("vbidevice", vbidev); SetOption("audiodevice", audiodev); SetOption("tvformat", gCoreContext->GetSetting("TVFormat")); @@ -395,79 +386,56 @@ bool MpegRecorder::OpenV4L2DeviceAsInput(void) return false; } - if (CardUtil::GetV4LInfo(chanfd, card, driver, version)) + bufferSize = 4096; + + bool supports_tuner = false, supports_audio = false; + uint32_t capabilities = 0; + if (CardUtil::GetV4LInfo(chanfd, card, driver, version, capabilities)) { - if (driver == "ivtv") - { - bufferSize = 4096; - usingv4l2 = (version >= IVTV_KERNEL_VERSION(0, 8, 0)); - has_v4l2_vbi = (version >= IVTV_KERNEL_VERSION(0, 3, 8)); - has_buggy_vbi = true; - requires_special_pause = - (version >= IVTV_KERNEL_VERSION(0, 10, 0)); - } - else if (driver == "pvrusb2") - { - bufferSize = 4096; - usingv4l2 = true; - has_v4l2_vbi = true; - has_buggy_vbi = true; - requires_special_pause = false; - } - else if (driver == "hdpvr") + supports_sliced_vbi = !!(capabilities & V4L2_CAP_SLICED_VBI_CAPTURE); + supports_tuner = !!(capabilities & V4L2_CAP_TUNER); + supports_audio = !!(capabilities & V4L2_CAP_AUDIO); + /// Determine hacks needed for specific drivers & driver versions + if (driver == "hdpvr") { bufferSize = 1500 * TSPacket::kSize; - usingv4l2 = true; - requires_special_pause = true; - - memset(_stream_id, 0, sizeof(_stream_id)); - memset(_pid_status, 0, sizeof(_pid_status)); - memset(_continuity_counter, 0xff, sizeof(_continuity_counter)); - m_h264_parser.use_I_forKeyframes(false); } - else - { - bufferSize = 4096; - usingv4l2 = has_v4l2_vbi = true; - has_buggy_vbi = requires_special_pause = false; - } } - VERBOSE(VB_RECORD, LOC + QString("usingv4l2(%1) has_v4l2_vbi(%2) " - "has_buggy_vbi(%3)") - .arg(usingv4l2).arg(has_v4l2_vbi).arg(has_buggy_vbi)); - - - if ((driver != "hdpvr") && !SetFormat(chanfd)) - return false; - - if (driver != "hdpvr") + if (!(capabilities & V4L2_CAP_VIDEO_CAPTURE)) { - SetLanguageMode(chanfd); // we don't care if this fails... - SetRecordingVolume(chanfd); // we don't care if this fails... + VERBOSE(VB_IMPORTANT, LOC_ERR + "V4L version 1, unsupported"); + close(chanfd); + chanfd = -1; + return false; } - bool ok = true; - if (usingv4l2) - ok = SetV4L2DeviceOptions(chanfd); - else + if (!SetVideoCaptureFormat(chanfd)) { - ok = SetIVTVDeviceOptions(chanfd); - if (!ok) - usingv4l2 = ok = SetV4L2DeviceOptions(chanfd); + close(chanfd); + chanfd = -1; + return false; } - if (!ok) + if (supports_tuner) + SetLanguageMode(chanfd); // we don't care if this fails... + + if (supports_audio) + SetRecordingVolume(chanfd); // we don't care if this fails... + + if (!SetV4L2DeviceOptions(chanfd)) return false; - SetVBIOptions(chanfd); + SetVBIOptions(chanfd); // we don't care if this fails... readfd = open(vdevice.constData(), O_RDWR | O_NONBLOCK); if (readfd < 0) { VERBOSE(VB_IMPORTANT, LOC_ERR + "Can't open video device." + ENO); + close(chanfd); + chanfd = -1; return false; } @@ -486,6 +454,10 @@ bool MpegRecorder::OpenV4L2DeviceAsInput(void) { VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to allocate DRB buffer"); _error = true; + close(chanfd); + chanfd = -1; + close(readfd); + readfd = -1; return false; } @@ -493,17 +465,27 @@ bool MpegRecorder::OpenV4L2DeviceAsInput(void) { VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to allocate DRB buffer"); _error = true; + close(chanfd); + chanfd = -1; + close(readfd); + readfd = -1; return false; } VERBOSE(VB_RECORD, LOC + "DRB ready"); + if (vbi_fd >= 0) + vbi_thread = new VBIThread(this); + return true; } -bool MpegRecorder::SetFormat(int chanfd) +bool MpegRecorder::SetVideoCaptureFormat(int chanfd) { + if (driver == "hdpvr") + return true; + struct v4l2_format vfmt; memset(&vfmt, 0, sizeof(vfmt)); @@ -547,10 +529,7 @@ bool MpegRecorder::SetLanguageMode(int chanfd) vt.audmode = V4L2_TUNER_MODE_LANG2; break; case 2: - if (usingv4l2) - vt.audmode = V4L2_TUNER_MODE_LANG1_LANG2; - else - vt.audmode = V4L2_TUNER_MODE_STEREO; + vt.audmode = V4L2_TUNER_MODE_LANG1_LANG2; break; default: vt.audmode = V4L2_TUNER_MODE_LANG1; @@ -579,14 +558,13 @@ bool MpegRecorder::SetRecordingVolume(int chanfd) { // Get volume min/max values struct v4l2_queryctrl qctrl; + memset(&qctrl, 0 , sizeof(struct v4l2_queryctrl)); qctrl.id = V4L2_CID_AUDIO_VOLUME; - if (ioctl(chanfd, VIDIOC_QUERYCTRL, &qctrl) < 0) + if ((ioctl(chanfd, VIDIOC_QUERYCTRL, &qctrl) < 0) || + (qctrl.flags & V4L2_CTRL_FLAG_DISABLED)) { - VERBOSE(VB_IMPORTANT, LOC_WARN + - "Unable to get recording volume parameters(max/min)" + ENO + - "\n\t\t\tusing default range [0,65535]."); - qctrl.maximum = 65535; - qctrl.minimum = 0; + VERBOSE(VB_CHANNEL, LOC_WARN + "Audio volume control not supported."); + return false; } // calculate volume in card units. @@ -687,43 +665,6 @@ uint MpegRecorder::GetFilteredAudioBitRate(uint audio_layer) const ((3 == audio_layer) ? audbitratel3 : max(audbitratel1, 6))); } -bool MpegRecorder::SetIVTVDeviceOptions(int chanfd) -{ - struct ivtv_ioctl_codec ivtvcodec; - memset(&ivtvcodec, 0, sizeof(ivtvcodec)); - - if (ioctl(chanfd, IVTV_IOC_G_CODEC, &ivtvcodec) < 0) - { - // Downgrade to VB_RECORD warning when ioctl isn't supported, - // unless the driver is completely missitng it probably - // supports the Linux v2.6.18 v4l2 mpeg encoder API. - VERBOSE((22 == errno) ? VB_RECORD : VB_IMPORTANT, - ((22 == errno) ? LOC_WARN : LOC_ERR) + - "Error getting codec params using old IVTV ioctl" + ENO); - return false; - } - - uint audio_rate = GetFilteredAudioSampleRate(); - uint audio_layer = GetFilteredAudioLayer(); - uint audbitrate = GetFilteredAudioBitRate(audio_layer); - - ivtvcodec.audio_bitmask = audio_rate | (audio_layer << 2); - ivtvcodec.audio_bitmask |= audbitrate << 4; - ivtvcodec.aspect = aspectratio; - ivtvcodec.bitrate = min(bitrate, maxbitrate) * 1000; - ivtvcodec.bitrate_peak = maxbitrate * 1000; - ivtvcodec.framerate = ntsc_framerate ? 0 : 1; // 1->25fps, 0->30fps - ivtvcodec.stream_type = GetFilteredStreamType(); - - if (ioctl(chanfd, IVTV_IOC_S_CODEC, &ivtvcodec) < 0) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + "Error setting codec params" + ENO); - return false; - } - - return true; -} - static int streamtype_ivtv_to_v4l2(int st) { switch (st) @@ -809,7 +750,7 @@ bool MpegRecorder::SetV4L2DeviceOptions(int chanfd) // Set controls if (driver != "hdpvr") { - if (driver != "saa7164") + if (driver.left(7) != "saa7164") { add_ext_ctrl(ext_ctrls, V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ, GetFilteredAudioSampleRate()); @@ -837,7 +778,7 @@ bool MpegRecorder::SetV4L2DeviceOptions(int chanfd) } maxbitrate = std::max(maxbitrate, bitrate); - if (driver == "hdpvr" || driver == "saa7164") + if (driver == "hdpvr" || driver.left(7) == "saa7164") { add_ext_ctrl(ext_ctrls, V4L2_CID_MPEG_VIDEO_BITRATE_MODE, (maxbitrate == bitrate) ? @@ -899,135 +840,71 @@ bool MpegRecorder::SetV4L2DeviceOptions(int chanfd) bool MpegRecorder::SetVBIOptions(int chanfd) { - if (!vbimode) + if (VBIMode::None == vbimode) return true; - if (driver == "hdpvr" || driver == "saa7164") + if (driver == "hdpvr") return true; - if (has_buggy_vbi) - { - cout<<" *********************** WARNING ***********************"<= 0) - { - VERBOSE(VB_RECORD, LOC + QString( - "VBI service:%1, packet size:%2, io size:%3") - .arg(vbifmt.service_set).arg(vbifmt.packet_size) - .arg(vbifmt.io_size)); - } - } -#endif // IVTV_IOC_S_VBI_EMBED - #ifdef V4L2_CAP_SLICED_VBI_CAPTURE - // used for ivtv driver versions 0.3.8+ - if (has_v4l2_vbi) + if (supports_sliced_vbi) { struct v4l2_format vbifmt; memset(&vbifmt, 0, sizeof(struct v4l2_format)); vbifmt.type = V4L2_BUF_TYPE_SLICED_VBI_CAPTURE; - vbifmt.fmt.sliced.service_set |= (1 == vbimode) ? + vbifmt.fmt.sliced.service_set |= (VBIMode::PAL_TT == vbimode) ? V4L2_SLICED_VBI_625 : V4L2_SLICED_VBI_525; if (ioctl(chanfd, VIDIOC_S_FMT, &vbifmt) < 0) { VERBOSE(VB_IMPORTANT, LOC_WARN + - "Can't enable VBI recording (3)" + ENO); - - return false; + "Unable to enable VBI embedding" + ENO); } - - if (ioctl(chanfd, VIDIOC_G_FMT, &vbifmt) >= 0) + else if (ioctl(chanfd, VIDIOC_G_FMT, &vbifmt) >= 0) { VERBOSE(VB_RECORD, LOC + QString("VBI service: %1, io size: %2") .arg(vbifmt.fmt.sliced.service_set) .arg(vbifmt.fmt.sliced.io_size)); - } - } -#endif // V4L2_CAP_SLICED_VBI_CAPTURE - /****************************************************************/ - /** Second, tell driver to embed the captions in the stream. **/ + struct v4l2_ext_control vbi_ctrl; + vbi_ctrl.id = V4L2_CID_MPEG_STREAM_VBI_FMT; + vbi_ctrl.value = V4L2_MPEG_STREAM_VBI_FMT_IVTV; -#ifdef IVTV_IOC_S_VBI_EMBED - // used for ivtv driver versions 0.2.0-0.7.x - if (!usingv4l2) - { - int embedon = 1; - if (ioctl(chanfd, IVTV_IOC_S_VBI_EMBED, &embedon) < 0) - { - VERBOSE(VB_IMPORTANT, LOC_WARN + - "Can't enable VBI recording (4)" + ENO); - - return false; - } - } -#endif // IVTV_IOC_S_VBI_EMBED + struct v4l2_ext_controls ctrls; + memset(&ctrls, 0, sizeof(struct v4l2_ext_controls)); + ctrls.ctrl_class = V4L2_CTRL_CLASS_MPEG; + ctrls.count = 1; + ctrls.controls = &vbi_ctrl; -#ifdef V4L2_CAP_SLICED_VBI_CAPTURE - // used for ivtv driver versions 0.8.0+ - if (usingv4l2) - { - struct v4l2_ext_control vbi_ctrl; - vbi_ctrl.id = V4L2_CID_MPEG_STREAM_VBI_FMT; - vbi_ctrl.value = V4L2_MPEG_STREAM_VBI_FMT_IVTV; - - struct v4l2_ext_controls ctrls; - memset(&ctrls, 0, sizeof(struct v4l2_ext_controls)); - ctrls.ctrl_class = V4L2_CTRL_CLASS_MPEG; - ctrls.count = 1; - ctrls.controls = &vbi_ctrl; - - if (ioctl(chanfd, VIDIOC_S_EXT_CTRLS, &ctrls) < 0) - { - VERBOSE(VB_IMPORTANT, LOC_WARN + - "Can't enable VBI recording (5)" + ENO); - - return false; + if (ioctl(chanfd, VIDIOC_S_EXT_CTRLS, &ctrls) < 0) + { + VERBOSE(VB_IMPORTANT, LOC_WARN + + "Unable to set VBI embedding format" + ENO); + } + else + { + return true; + } } } #endif // V4L2_CAP_SLICED_VBI_CAPTURE - return true; + return OpenVBIDevice() >= 0; } bool MpegRecorder::Open(void) { - if (deviceIsMpegFile) - return OpenMpegFileAsInput(); - else - return OpenV4L2DeviceAsInput(); + memset(_stream_id, 0, sizeof(_stream_id)); + memset(_pid_status, 0, sizeof(_pid_status)); + memset(_continuity_counter, 0xff, sizeof(_continuity_counter)); + return (deviceIsMpegFile) ? OpenMpegFileAsInput() : OpenV4L2DeviceAsInput(); } void MpegRecorder::StartRecording(void) { if (!Open()) { - _error = true; + _error = "Failed to open V4L device"; return; } @@ -1038,6 +915,7 @@ void MpegRecorder::StartRecording(void) has_select = false; #endif + _continuity_error_count = 0; _start_code = 0xffffffff; _last_gop_seen = 0; _frames_written_count = 0; @@ -1060,9 +938,10 @@ void MpegRecorder::StartRecording(void) } { - QMutexLocker locker(&recording_wait_lock); - encoding = true; + QMutexLocker locker(&pauseLock); + request_recording = true; recording = true; + recordingWait.wakeAll(); } unsigned char *buffer = new unsigned char[bufferSize + 1]; @@ -1090,7 +969,7 @@ void MpegRecorder::StartRecording(void) { VERBOSE(VB_RECORD, LOC + "Initial startup of recorder"); - if (requires_special_pause && !StartEncoding(readfd)) + if (!StartEncoding(readfd)) { VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to start recording"); _error = true; @@ -1100,7 +979,7 @@ void MpegRecorder::StartRecording(void) } QByteArray vdevice = videodevice.toAscii(); - while (encoding && !_error) + while (IsRecordingRequested() && !IsErrored()) { if (PauseAndWait(100)) continue; @@ -1158,7 +1037,8 @@ void MpegRecorder::StartRecording(void) RestartEncoding(); } - else if (_device_read_buffer->IsEOF()) + else if (_device_read_buffer->IsEOF() && + IsRecordingRequested()) { VERBOSE(VB_IMPORTANT, LOC_ERR + "Device EOF detected"); _error = true; @@ -1205,7 +1085,9 @@ void MpegRecorder::StartRecording(void) if (len < 0 && !has_select) { - usleep(25 * 1000); + QMutexLocker locker(&pauseLock); + if (request_recording && !request_pause) + unpauseWait.wait(&pauseLock, 25); continue; } @@ -1222,7 +1104,7 @@ void MpegRecorder::StartRecording(void) if (len <= 0) { - encoding = false; + _error = "Failed to read from video file"; continue; } } @@ -1237,10 +1119,11 @@ void MpegRecorder::StartRecording(void) if (len > 0) { - len += remainder; bytesRead += len; + len += remainder; - if (driver == "hdpvr") { + if (driver == "hdpvr") + { remainder = _stream_data->ProcessData(buffer, len); int start_remain = len - remainder; if (remainder && (start_remain >= remainder)) @@ -1265,18 +1148,30 @@ void MpegRecorder::StartRecording(void) delete _device_read_buffer; _device_read_buffer = NULL; } - if (requires_special_pause) - StopEncoding(readfd); + + StopEncoding(readfd); FinishRecording(); delete[] buffer; - DTVRecorder::SetStreamData(NULL); - encoding = false; - QMutexLocker locker(&recording_wait_lock); + if (driver == "hdpvr") + { + _stream_data->RemoveWritingListener(this); + _stream_data->RemoveAVListener(this); + DTVRecorder::SetStreamData(NULL); + } + + QMutexLocker locker(&pauseLock); recording = false; - recording_wait.wakeAll(); + recordingWait.wakeAll(); +} + +void MpegRecorder::StopRecording(void) +{ + if (_device_read_buffer && _device_read_buffer->IsRunning()) + _device_read_buffer->Stop(); + V4LRecorder::StopRecording(); } bool MpegRecorder::ProcessTSPacket(const TSPacket &tspacket_real) @@ -1292,111 +1187,15 @@ bool MpegRecorder::ProcessTSPacket(const TSPacket &tspacket_real) tspacket_fake->SetContinuityCounter(cc); } - const TSPacket *tspacket = (tspacket_fake) ? - tspacket_fake : &tspacket_real; - - // Check continuity counter - if ((pid != 0x1fff) && !CheckCC(pid, tspacket->ContinuityCounter())) - { - VERBOSE(VB_RECORD, LOC + - QString("PID 0x%1 discontinuity detected").arg(pid,0,16)); - _continuity_error_count++; - } - - // Only write the packet - // if audio/video key-frames have been found - if (!(_wait_for_keyframe_option && _first_keyframe < 0)) - { - _buffer_packets = true; + const TSPacket &tspacket = (tspacket_fake) + ? *tspacket_fake : tspacket_real; - BufferedWrite(*tspacket); - } + bool ret = DTVRecorder::ProcessTSPacket(tspacket); if (tspacket_fake) delete tspacket_fake; - return true; -} - -bool MpegRecorder::ProcessVideoTSPacket(const TSPacket &tspacket) -{ - if (!ringBuffer) - return true; - - _buffer_packets = !FindH264Keyframes(&tspacket); - if (!_seen_sps) - return true; - - return ProcessAVTSPacket(tspacket); -} - -bool MpegRecorder::ProcessAudioTSPacket(const TSPacket &tspacket) -{ - if (!ringBuffer) - return true; - - _buffer_packets = !FindAudioKeyframes(&tspacket); - return ProcessAVTSPacket(tspacket); -} - -/// Common code for processing either audio or video packets -bool MpegRecorder::ProcessAVTSPacket(const TSPacket &tspacket) -{ - const uint pid = tspacket.PID(); - - // Check continuity counter - if ((pid != 0x1fff) && !CheckCC(pid, tspacket.ContinuityCounter())) - { - VERBOSE(VB_RECORD, LOC + - QString("PID 0x%1 discontinuity detected").arg(pid,0,16)); - _continuity_error_count++; - } - - // Sync recording start to first keyframe - if (_wait_for_keyframe_option && _first_keyframe < 0) - return true; - - // Sync streams to the first Payload Unit Start Indicator - // _after_ first keyframe iff _wait_for_keyframe_option is true - if (!(_pid_status[pid] & kPayloadStartSeen) && tspacket.HasPayload()) - { - if (!tspacket.PayloadStart()) - return true; // not payload start - drop packet - - VERBOSE(VB_RECORD, - QString("PID 0x%1 Found Payload Start").arg(pid,0,16)); - - _pid_status[pid] |= kPayloadStartSeen; - } - - BufferedWrite(tspacket); - - return true; -} - -void MpegRecorder::StopRecording(void) -{ - QMutexLocker locker(&recording_wait_lock); - if (recording) - { - encoding = false; // force exit from StartRecording() while loop - recording_wait.wait(&recording_wait_lock); - } -} - -void MpegRecorder::ResetForNewFile(void) -{ - DTVRecorder::ResetForNewFile(); - - memset(_stream_id, 0, sizeof(_stream_id)); - memset(_pid_status, 0, sizeof(_pid_status)); - memset(_continuity_counter, 0xff, sizeof(_continuity_counter)); - - if (driver == "hdpvr") - { - m_h264_parser.Reset(); - _wait_for_keyframe_option = true; - } + return ret; } void MpegRecorder::Reset(void) @@ -1437,10 +1236,7 @@ bool MpegRecorder::PauseAndWait(int timeout) _device_read_buffer->WaitForPaused(4000); } - // Some drivers require streaming to be disabled before - // an input switch and other channel format setting. - if (requires_special_pause) - StopEncoding(readfd); + StopEncoding(readfd); paused = true; pauseWait.wakeAll(); @@ -1463,10 +1259,7 @@ bool MpegRecorder::PauseAndWait(int timeout) _seen_sps = false; } - // Some drivers require streaming to be disabled before - // an input switch and other channel format setting. - if (requires_special_pause) - StartEncoding(readfd); + StartEncoding(readfd); if (_device_read_buffer) _device_read_buffer->SetRequestPause(false); @@ -1488,8 +1281,7 @@ void MpegRecorder::RestartEncoding(void) QMutexLocker locker(&start_stop_encoding_lock); - if (requires_special_pause) - StopEncoding(readfd); + StopEncoding(readfd); // Make sure the next things in the file are a PAT & PMT if (_stream_data && @@ -1501,7 +1293,7 @@ void MpegRecorder::RestartEncoding(void) HandleSingleProgramPMT(_stream_data->PMTSingleProgram()); } - if (requires_special_pause && !StartEncoding(readfd)) + if (!StartEncoding(readfd)) { if (0 != close(readfd)) VERBOSE(VB_IMPORTANT, LOC_ERR + "Close error" + ENO); @@ -1549,7 +1341,8 @@ bool MpegRecorder::StopEncoding(int fd) struct v4l2_encoder_cmd command; memset(&command, 0, sizeof(struct v4l2_encoder_cmd)); - command.cmd = V4L2_ENC_CMD_STOP; + command.cmd = V4L2_ENC_CMD_STOP; + command.flags = V4L2_ENC_CMD_STOP_AT_GOP_END; VERBOSE(VB_RECORD, LOC + "StopEncoding"); @@ -1569,48 +1362,6 @@ void MpegRecorder::SetStreamData(void) _stream_data->SetDesiredProgram(1); } -void MpegRecorder::HandleSingleProgramPAT(ProgramAssociationTable *pat) -{ - if (!pat) - { - VERBOSE(VB_RECORD, LOC + "HandleSingleProgramPAT(NULL)"); - return; - } - - if (!ringBuffer) - return; - - - uint next_cc = (pat->tsheader()->ContinuityCounter()+1)&0xf; - pat->tsheader()->SetContinuityCounter(next_cc); - pat->GetAsTSPackets(_scratch, next_cc); - - for (uint i = 0; i < _scratch.size(); i++) - DTVRecorder::BufferedWrite(_scratch[i]); -} - -void MpegRecorder::HandleSingleProgramPMT(ProgramMapTable *pmt) -{ - if (!pmt) - { - return; - } - - // collect stream types for H.264 (MPEG-4 AVC) keyframe detection - for (uint i = 0; i < pmt->StreamCount(); i++) - _stream_id[pmt->StreamPID(i)] = pmt->StreamType(i); - - if (!ringBuffer) - return; - - uint next_cc = (pmt->tsheader()->ContinuityCounter()+1)&0xf; - pmt->tsheader()->SetContinuityCounter(next_cc); - pmt->GetAsTSPackets(_scratch, next_cc); - - for (uint i = 0; i < _scratch.size(); i++) - DTVRecorder::BufferedWrite(_scratch[i]); -} - void MpegRecorder::SetBitrate(int bitrate, int maxbitrate, const QString & reason) { @@ -1696,3 +1447,14 @@ void MpegRecorder::HandleResolutionChanges(void) SetBitrate(bitrate, maxbitrate, "New"); } } + +void MpegRecorder::FormatCC(uint code1, uint code2) +{ + VERBOSE(VB_VBI, LOC + QString("FormatCC(0x%1,0x%2)") + .arg(code1,0,16).arg(code2,0,16)); + // TODO add to CC data vector + + // TODO find video frames in output and insert cc_data + // as CEA-708 user_data packets containing CEA-608 captions + ///at picture data level +} diff --git a/mythtv/libs/libmythtv/mpegrecorder.h b/mythtv/libs/libmythtv/mpegrecorder.h index 29c2b279a5d..eb56712ce3e 100644 --- a/mythtv/libs/libmythtv/mpegrecorder.h +++ b/mythtv/libs/libmythtv/mpegrecorder.h @@ -3,7 +3,7 @@ #ifndef MPEGRECORDER_H_ #define MPEGRECORDER_H_ -#include "dtvrecorder.h" +#include "v4lrecorder.h" #include "tspacket.h" #include "mpegstreamdata.h" #include "DeviceReadBuffer.h" @@ -11,11 +11,8 @@ struct AVFormatContext; struct AVPacket; -class MpegRecorder : public DTVRecorder, - public MPEGSingleProgramStreamListener, - public TSPacketListener, - public TSPacketListenerAV, - public ReaderPausedCB +class MpegRecorder : public V4LRecorder, + public DeviceReaderCB { public: MpegRecorder(TVRec*); @@ -50,17 +47,9 @@ class MpegRecorder : public DTVRecorder, // TSPacketListener bool ProcessTSPacket(const TSPacket &tspacket); - // TSPacketListenerAV - bool ProcessVideoTSPacket(const TSPacket &tspacket); - bool ProcessAudioTSPacket(const TSPacket &tspacket); - bool ProcessAVTSPacket(const TSPacket &tspacket); - - // Implements MPEGSingleProgramStreamListener - void HandleSingleProgramPAT(ProgramAssociationTable *pat); - void HandleSingleProgramPMT(ProgramMapTable *pmt); - - // ReaderPausedCB + // DeviceReaderCB virtual void ReaderPaused(int fd) { pauseWait.wakeAll(); } + virtual void PriorityEvent(int fd) { } private: void SetIntOption(RecordingProfile *profile, const QString &name); @@ -68,9 +57,8 @@ class MpegRecorder : public DTVRecorder, bool OpenMpegFileAsInput(void); bool OpenV4L2DeviceAsInput(void); - bool SetIVTVDeviceOptions(int chanfd); bool SetV4L2DeviceOptions(int chanfd); - bool SetFormat(int chanfd); + bool SetVideoCaptureFormat(int chanfd); bool SetLanguageMode(int chanfd); bool SetRecordingVolume(int chanfd); bool SetVBIOptions(int chanfd); @@ -83,12 +71,10 @@ class MpegRecorder : public DTVRecorder, bool StartEncoding(int fd); bool StopEncoding(int fd); - void ResetForNewFile(void); - void SetBitrate(int bitrate, int maxbitrate, const QString & reason); void HandleResolutionChanges(void); - inline bool CheckCC(uint pid, uint cc); + virtual void FormatCC(uint code1, uint code2); // RecorderBase bool deviceIsMpegFile; int bufferSize; @@ -97,17 +83,10 @@ class MpegRecorder : public DTVRecorder, QString card; QString driver; uint32_t version; - bool usingv4l2; - bool has_buggy_vbi; - bool has_v4l2_vbi; - bool requires_special_pause; + bool supports_sliced_vbi; // State - bool recording; - bool encoding; mutable QMutex start_stop_encoding_lock; - QMutex recording_wait_lock; - QWaitCondition recording_wait; // Pausing state bool cleartimeonpause; @@ -138,28 +117,6 @@ class MpegRecorder : public DTVRecorder, // Buffer device reads DeviceReadBuffer *_device_read_buffer; - - // TS - unsigned char _stream_id[0x1fff + 1]; - unsigned char _pid_status[0x1fff + 1]; - unsigned char _continuity_counter[0x1fff + 1]; - static const unsigned char kPayloadStartSeen = 0x2; - vector _scratch; - - // Statistics - mutable uint _continuity_error_count; - mutable uint _stream_overflow_count; - mutable uint _bad_packet_count; }; -inline bool MpegRecorder::CheckCC(uint pid, uint new_cnt) -{ - bool ok = ((((_continuity_counter[pid] + 1) & 0xf) == new_cnt) || - (_continuity_counter[pid] == 0xFF)); - - _continuity_counter[pid] = new_cnt & 0xf; - - return ok; -} - #endif diff --git a/mythtv/libs/libmythtv/recorderbase.cpp b/mythtv/libs/libmythtv/recorderbase.cpp index ed0241f95ec..14bbca06db6 100644 --- a/mythtv/libs/libmythtv/recorderbase.cpp +++ b/mythtv/libs/libmythtv/recorderbase.cpp @@ -1,21 +1,33 @@ #include #include // for min -#include using namespace std; +#include "NuppelVideoRecorder.h" +#include "firewirerecorder.h" +#include "recordingprofile.h" +#include "firewirechannel.h" +#include "importrecorder.h" +#include "dummychannel.h" +#include "hdhrrecorder.h" +#include "iptvrecorder.h" +#include "mpegrecorder.h" #include "recorderbase.h" -#include "tv_rec.h" +#include "asirecorder.h" +#include "dvbrecorder.h" +#include "hdhrchannel.h" +#include "iptvchannel.h" #include "mythverbose.h" -#include "ringbuffer.h" -#include "recordingprofile.h" #include "programinfo.h" +#include "asichannel.h" +#include "dtvchannel.h" +#include "dvbchannel.h" +#include "v4lchannel.h" +#include "ringbuffer.h" +#include "cardutil.h" +#include "tv_rec.h" #include "util.h" -#ifndef LONG_LONG_MAX -#define LONG_LONG_MAX ((~((long long)0))>>1) -#endif - #define TVREC_CARDNUM \ ((tvrec != NULL) ? QString::number(tvrec->GetCaptureCardNum()) : "NULL") @@ -29,14 +41,13 @@ using namespace std; RecorderBase::RecorderBase(TVRec *rec) : tvrec(rec), ringBuffer(NULL), weMadeBuffer(true), videocodec("rtjpeg"), - audiodevice("/dev/dsp"), videodevice("/dev/video"), - vbidevice("/dev/vbi"), vbimode(0), ntsc(true), ntsc_framerate(true), video_frame_rate(29.97), m_videoAspect(0), m_videoHeight(0), - m_videoWidth(0), m_frameRate(0), + m_videoWidth(0), m_frameRate(0.0), curRecording(NULL), request_pause(false), paused(false), + request_recording(false), recording(false), nextRingBuffer(NULL), nextRecording(NULL), positionMapType(MARK_GOP_BYFRAME) { @@ -90,12 +101,8 @@ void RecorderBase::SetOption(const QString &name, const QString &value) { if (name == "videocodec") videocodec = value; - else if (name == "audiodevice") - audiodevice = value; else if (name == "videodevice") videodevice = value; - else if (name == "vbidevice") - vbidevice = value; else if (name == "tvformat") { ntsc = false; @@ -121,14 +128,11 @@ void RecorderBase::SetOption(const QString &name, const QString &value) else SetFrameRate(25.00); } - else if (name == "vbiformat") + else { - if (value.toLower() == "pal teletext") - vbimode = 1; - else if (value.toLower().left(4) == "ntsc") - vbimode = 2; - else - vbimode = 0; + VERBOSE(VB_GENERAL, LOC_WARN + + QString("SetOption(%1,%2): Option not recognized") + .arg(name).arg(value)); } } @@ -159,6 +163,42 @@ void RecorderBase::SetStrOption(RecordingProfile *profile, const QString &name) "SetStrOption(...%1): Option not in profile.").arg(name)); } +/** \brief StopRecording() signals to the StartRecording() function that + * it should stop recording and exit cleanly. + * + * This function should block until StartRecording() has finished up. + */ +void RecorderBase::StopRecording(void) +{ + QMutexLocker locker(&pauseLock); + request_recording = false; + unpauseWait.wakeAll(); + while (recording) + { + recordingWait.wait(&pauseLock, 100); + if (request_recording) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + + "Programmer Error: StartRecording called while we were in StopRecording"); + request_recording = false; + } + } +} + +/// \brief Tells whether the StartRecorder() loop is running. +bool RecorderBase::IsRecording(void) +{ + QMutexLocker locker(&pauseLock); + return recording; +} + +/// \brief Tells us if StopRecording() has been called. +bool RecorderBase::IsRecordingRequested(void) +{ + QMutexLocker locker(&pauseLock); + return request_recording; +} + /** \brief Pause tells StartRecording() to pause, it should not block. * * Once paused the recorder calls tvrec->RecorderPaused(). @@ -266,7 +306,7 @@ void RecorderBase::CheckForRingBufferSwitch(void) ResetForNewFile(); m_videoAspect = m_videoWidth = m_videoHeight = 0; - m_frameRate = 0; + m_frameRate = 0.0; SetRingBuffer(nextRingBuffer); SetRecording(nextRecording); @@ -421,4 +461,108 @@ void RecorderBase::SetDuration(uint64_t duration) +RecorderBase *RecorderBase::CreateRecorder( + TVRec *tvrec, + ChannelBase *channel, + const RecordingProfile &profile, + const GeneralDBOptions &genOpt, + const DVBDBOptions &dvbOpt) +{ + if (!channel) + return NULL; + + RecorderBase *recorder = NULL; + if (genOpt.cardtype == "MPEG") + { +#ifdef USING_IVTV + recorder = new MpegRecorder(tvrec); +#endif // USING_IVTV + } + else if (genOpt.cardtype == "HDPVR") + { +#ifdef USING_HDPVR + recorder = new MpegRecorder(tvrec); +#endif // USING_HDPVR + } + else if (genOpt.cardtype == "FIREWIRE") + { +#ifdef USING_FIREWIRE + recorder = new FirewireRecorder( + tvrec, dynamic_cast(channel)); +#endif // USING_FIREWIRE + } + else if (genOpt.cardtype == "HDHOMERUN") + { +#ifdef USING_HDHOMERUN + recorder = new HDHRRecorder( + tvrec, dynamic_cast(channel)); + recorder->SetOption("wait_for_seqstart", genOpt.wait_for_seqstart); +#endif // USING_HDHOMERUN + } + else if (genOpt.cardtype == "DVB") + { +#ifdef USING_DVB + recorder = new DVBRecorder( + tvrec, dynamic_cast(channel)); + recorder->SetOption("wait_for_seqstart", genOpt.wait_for_seqstart); + recorder->SetOption("dvb_on_demand", dvbOpt.dvb_on_demand); +#endif // USING_DVB + } + else if (genOpt.cardtype == "FREEBOX") + { +#ifdef USING_IPTV + recorder = new IPTVRecorder( + tvrec, dynamic_cast(channel)); + recorder->SetOption("mrl", genOpt.videodev); +#endif // USING_IPTV + } + else if (genOpt.cardtype == "ASI") + { +#ifdef USING_ASI + recorder = new ASIRecorder( + tvrec, dynamic_cast(channel)); + recorder->SetOption("wait_for_seqstart", genOpt.wait_for_seqstart); +#endif // USING_ASI + } + else if (genOpt.cardtype == "IMPORT") + { + recorder = new ImportRecorder(tvrec); + } + else if (genOpt.cardtype == "DEMO") + { +#ifdef USING_IVTV + recorder = new MpegRecorder(tvrec); +#else + recorder = new ImportRecorder(tvrec); +#endif + } + else if (CardUtil::IsV4L(genOpt.cardtype)) + { +#ifdef USING_V4L + // V4L/MJPEG/GO7007 from here on + recorder = new NuppelVideoRecorder(tvrec, channel); + recorder->SetOption("skipbtaudio", genOpt.skip_btaudio); +#endif // USING_V4L + } + + if (recorder) + { + recorder->SetOptionsFromProfile( + const_cast(&profile), + genOpt.videodev, genOpt.audiodev, genOpt.vbidev); + // Override the samplerate defined in the profile if this card + // was configured with a fixed rate. + if (genOpt.audiosamplerate) + recorder->SetOption("samplerate", genOpt.audiosamplerate); + } + else + { + QString msg = "Need %1 recorder, but compiled without %2 support!"; + msg = msg.arg(genOpt.cardtype).arg(genOpt.cardtype); + VERBOSE(VB_IMPORTANT, "RecorderBase::CreateRecorder() Error, " + msg); + } + + return recorder; +} + /* vim: set expandtab tabstop=4 shiftwidth=4: */ diff --git a/mythtv/libs/libmythtv/recorderbase.h b/mythtv/libs/libmythtv/recorderbase.h index e9619929392..7e2646d302e 100644 --- a/mythtv/libs/libmythtv/recorderbase.h +++ b/mythtv/libs/libmythtv/recorderbase.h @@ -83,7 +83,7 @@ class MTV_PUBLIC RecorderBase /** \brief Set an specific option. * - * Base options include: codec, audiodevice, videodevice, vbidevice, + * Base options include: codec, videodevice, * tvformat (ntsc,ntsc-jp,pal-m), * vbiformat ("none","pal teletext","ntsc"). */ @@ -137,13 +137,6 @@ class MTV_PUBLIC RecorderBase */ virtual void StartRecording(void) = 0; - /** \brief StopRecording() signals to the StartRecording() function that - * it should stop recording and exit cleanly. - * - * This function should block until StartRecording() has finished up. - */ - virtual void StopRecording(void) = 0; - /** \brief Reset the recorder to the startup state. * * This is used after Pause(bool), WaitForPause() and @@ -151,9 +144,6 @@ class MTV_PUBLIC RecorderBase */ virtual void Reset(void) = 0; - /// \brief Tells whether the StartRecorder() loop is running. - virtual bool IsRecording(void) = 0; - /// \brief Tells us whether an unrecoverable error has been encountered. virtual bool IsErrored(void) = 0; @@ -164,13 +154,6 @@ class MTV_PUBLIC RecorderBase */ virtual long long GetFramesWritten(void) = 0; - /** \brief Open devices needed by recorder. - * - * This is usually called by StartRecording(). - * \return true if device was successfully opened. - */ - virtual bool Open(void) = 0; - /** \brief Returns file descriptor of recorder device. * * This is used by channel when only one open file descriptor @@ -199,6 +182,11 @@ class MTV_PUBLIC RecorderBase bool GetKeyframePositions( int64_t start, int64_t end, frm_pos_map_t&) const; + virtual void StopRecording(void); + virtual bool IsRecording(void); + virtual bool IsRecordingRequested(void); + + // pausing interface virtual void Pause(bool clear = true); virtual void Unpause(void); virtual bool IsPaused(bool holding_lock = false) const; @@ -229,6 +217,13 @@ class MTV_PUBLIC RecorderBase ASPECT_CUSTOM = 0x05, }; + static RecorderBase *CreateRecorder( + TVRec *tvrec, + ChannelBase *channel, + const RecordingProfile &profile, + const GeneralDBOptions &genOpt, + const DVBDBOptions &dvbOpt); + protected: /** \brief Convenience function used to set integer options from a profile. * \sa SetOption(const QString&, int) @@ -269,11 +264,8 @@ class MTV_PUBLIC RecorderBase bool weMadeBuffer; QString videocodec; - QString audiodevice; QString videodevice; - QString vbidevice; - int vbimode; bool ntsc; bool ntsc_framerate; double video_frame_rate; @@ -286,12 +278,18 @@ class MTV_PUBLIC RecorderBase ProgramInfo *curRecording; - // For handling pausing - mutable QMutex pauseLock; + // For handling pausing + stop recording + mutable QMutex pauseLock; // also used for request_recording and recording bool request_pause; bool paused; QWaitCondition pauseWait; QWaitCondition unpauseWait; + /// True if API call has requested a recording be [re]started + bool request_recording; + /// True while recording is actually being performed + bool recording; + QWaitCondition recordingWait; + // For RingBuffer switching QMutex nextRingBufferLock; diff --git a/mythtv/libs/libmythtv/recordinginfo.cpp b/mythtv/libs/libmythtv/recordinginfo.cpp index 89b1c2c2079..76ad9895c58 100644 --- a/mythtv/libs/libmythtv/recordinginfo.cpp +++ b/mythtv/libs/libmythtv/recordinginfo.cpp @@ -923,7 +923,6 @@ bool RecordingInfo::InsertProgram(const RecordingInfo *pg, { MSqlQuery query(MSqlQuery::InitCon()); - //query.prepare("LOCK TABLES recorded WRITE"); if (!query.exec("LOCK TABLES recorded WRITE")) { MythDB::DBError("InsertProgram -- lock", query); @@ -938,13 +937,24 @@ bool RecordingInfo::InsertProgram(const RecordingInfo *pg, query.bindValue(":CHANID", pg->chanid); query.bindValue(":STARTS", pg->recstartts); - if (!query.exec() || query.size()) + bool err = true; + if (!query.exec()) { - if (!query.isActive()) - MythDB::DBError("InsertProgram -- select", query); - else - VERBOSE(VB_IMPORTANT, "recording already exists..."); + MythDB::DBError("InsertProgram -- select", query); + } + else if (query.next()) + { + VERBOSE(VB_IMPORTANT, + QString("RecordingInfo::InsertProgram(%1): ") + .arg(pg->toString()) + "recording already exists..."); + } + else + { + err = false; + } + if (err) + { if (!query.exec("UNLOCK TABLES")) MythDB::DBError("InsertProgram -- unlock tables", query); return false; diff --git a/mythtv/libs/libmythtv/recordingprofile.cpp b/mythtv/libs/libmythtv/recordingprofile.cpp index db2405c25c6..feda835017d 100644 --- a/mythtv/libs/libmythtv/recordingprofile.cpp +++ b/mythtv/libs/libmythtv/recordingprofile.cpp @@ -1008,6 +1008,27 @@ class RecordingType : public ComboBoxSetting, public CodecParamStorage }; }; +class RecordFullTSStream : public ComboBoxSetting, public CodecParamStorage +{ + public: + RecordFullTSStream(const RecordingProfile &parent) : + ComboBoxSetting(this), CodecParamStorage(this, parent, "recordmpts") + { + setLabel(QObject::tr("Recording Full TS")); + + QString msg = QObject::tr( + "When set an extra files will be created for each recording " + "with the name of that recording plus .ts and a number. " + "These will represent that full contents of the transport " + "stream used to generate the recording."); + setHelpText(msg); + + addSelection(QObject::tr("Yes"), "1"); + addSelection(QObject::tr("No"), "0"); + setValue(1); + }; +}; + class TranscodeFilters : public LineEditSetting, public CodecParamStorage { public: @@ -1395,6 +1416,10 @@ void RecordingProfile::CompleteLoad(int profileId, const QString &type, { addChild(new RecordingType(*this)); } + else if (type.toUpper() == "ASI") + { + addChild(new RecordFullTSStream(*this)); + } id->setValue(profileId); Load(); diff --git a/mythtv/libs/libmythtv/ringbuffer.cpp b/mythtv/libs/libmythtv/ringbuffer.cpp index 118b2e1d2e7..23efc5f2dbf 100644 --- a/mythtv/libs/libmythtv/ringbuffer.cpp +++ b/mythtv/libs/libmythtv/ringbuffer.cpp @@ -1192,30 +1192,6 @@ int RingBuffer::Read(void *buf, int count) return ret; } -/** \fn RingBuffer::IsIOBound(void) const - * \brief Returns true if a RingBuffer::Write(void*,int) is likely to block. - */ -bool RingBuffer::IsIOBound(void) const -{ - bool ret = false; - int used, free; - rwlock.lockForRead(); - - if (!tfw) - { - rwlock.unlock(); - return ret; - } - - used = tfw->BufUsed(); - free = tfw->BufFree(); - - ret = (used * 5 > free); - - rwlock.unlock(); - return ret; -} - /** \fn RingBuffer::Write(const void*, uint) * \brief Writes buffer to ThreadedFileWriter::Write(const void*,uint) * \return Bytes written, or -1 on error. @@ -1306,17 +1282,6 @@ void RingBuffer::WriterFlush(void) rwlock.unlock(); } -/** \fn RingBuffer::SetWriteBufferSize(int) - * \brief Calls ThreadedFileWriter::SetWriteBufferSize(int) - */ -void RingBuffer::SetWriteBufferSize(int newSize) -{ - rwlock.lockForRead(); - if (tfw) - tfw->SetWriteBufferSize(newSize); - rwlock.unlock(); -} - /** \fn RingBuffer::SetWriteBufferMinWriteSize(int) * \brief Calls ThreadedFileWriter::SetWriteBufferMinWriteSize(int) */ diff --git a/mythtv/libs/libmythtv/scanwizard.cpp b/mythtv/libs/libmythtv/scanwizard.cpp index 34e55a3e71f..5efb1446b61 100644 --- a/mythtv/libs/libmythtv/scanwizard.cpp +++ b/mythtv/libs/libmythtv/scanwizard.cpp @@ -121,6 +121,7 @@ void ScanWizard::SetPage(const QString &pageTitle) else if ((scantype == ScanTypeSetting::FullScan_ATSC) || (scantype == ScanTypeSetting::FullTransportScan) || (scantype == ScanTypeSetting::TransportScan) || + (scantype == ScanTypeSetting::CurrentTransportScan) || (scantype == ScanTypeSetting::FullScan_DVBC) || (scantype == ScanTypeSetting::FullScan_DVBT) || (scantype == ScanTypeSetting::FullScan_Analog)) diff --git a/mythtv/libs/libmythtv/scriptsignalmonitor.h b/mythtv/libs/libmythtv/scriptsignalmonitor.h new file mode 100644 index 00000000000..d8497922b79 --- /dev/null +++ b/mythtv/libs/libmythtv/scriptsignalmonitor.h @@ -0,0 +1,30 @@ +// -*- Mode: c++ -*- + +#ifndef _SCRIPT_SIGNAL_MONITOR_H_ +#define _SCRIPT_SIGNAL_MONITOR_H_ + +// MythTV headers +#include "signalmonitor.h" + +class ScriptSignalMonitor : public SignalMonitor +{ + public: + ScriptSignalMonitor(int db_cardnum, ChannelBase *_channel, + uint64_t _flags = 0) : + SignalMonitor(db_cardnum, _channel, _flags) + { + signalLock.SetValue(true); + signalStrength.SetValue(100); + } + + virtual void UpdateValues(void) + { + SignalMonitor::UpdateValues(); + + EmitStatus(); + if (IsAllGood()) + SendMessageAllGood(); + } +}; + +#endif // _SCRIPT_SIGNAL_MONITOR_H_ diff --git a/mythtv/libs/libmythtv/signalmonitor.cpp b/mythtv/libs/libmythtv/signalmonitor.cpp index 87fb5449788..0a9df1a8d3a 100644 --- a/mythtv/libs/libmythtv/signalmonitor.cpp +++ b/mythtv/libs/libmythtv/signalmonitor.cpp @@ -7,12 +7,13 @@ #include // MythTV headers -#include "mythcontext.h" -#include "tv_rec.h" +#include "scriptsignalmonitor.h" #include "signalmonitor.h" +#include "mythcontext.h" #include "compat.h" #include "mythverbose.h" #include "mythlogging.h" +#include "tv_rec.h" extern "C" { #include "libavcodec/avcodec.h" @@ -44,7 +45,10 @@ extern "C" { # include "firewirechannel.h" #endif -#include "channelchangemonitor.h" +#ifdef USING_ASI +# include "asisignalmonitor.h" +# include "asichannel.h" +#endif #undef DBG_SM #define DBG_SM(FUNC, MSG) VERBOSE(VB_CHANNEL, \ @@ -132,11 +136,18 @@ SignalMonitor *SignalMonitor::Init(QString cardtype, int db_cardnum, } #endif - if (!signalMonitor) +#ifdef USING_ASI + if (cardtype.toUpper() == "ASI") { - // For anything else - if (channel) - signalMonitor = new ChannelChangeMonitor(db_cardnum, channel); + ASIChannel *fc = dynamic_cast(channel); + if (fc) + signalMonitor = new ASISignalMonitor(db_cardnum, fc); + } +#endif + + if (!signalMonitor && channel) + { + signalMonitor = new ScriptSignalMonitor(db_cardnum, channel); } if (!signalMonitor) @@ -167,17 +178,21 @@ SignalMonitor::SignalMonitor(int _capturecardnum, ChannelBase *_channel, : channel(_channel), capturecardnum(_capturecardnum), flags(wait_for_mask), update_rate(25), minimum_update_rate(5), - exit(false), + running(false), exit(false), update_done(false), notify_frontend(true), - is_tuned(false), tablemon(false), - eit_scan(false), error(""), + eit_scan(false), signalLock (QObject::tr("Signal Lock"), "slock", 1, true, 0, 1, 0), signalStrength(QObject::tr("Signal Power"), "signal", 0, true, 0, 100, 0), - channelTuned("Channel Tuned", "tuned", 3, true, 0, 3, 0), + scriptStatus (QObject::tr("Script Status"), "script", + 3, true, 0, 3, 0), statusLock(QMutex::Recursive) { + if (!channel->IsExternalChannelChangeSupported()) + { + scriptStatus.SetValue(3); + } } /** \fn SignalMonitor::~SignalMonitor() @@ -213,34 +228,16 @@ bool SignalMonitor::HasAnyFlag(uint64_t _flags) const /** \fn SignalMonitor::Start() * \brief Start signal monitoring thread. */ -void SignalMonitor::Start(bool waitfor_tune) +void SignalMonitor::Start() { DBG_SM("Start", "begin"); { QMutexLocker locker(&startStopLock); - // When used for scanning, don't wait for the tuning thread - is_tuned = !waitfor_tune; - if (waitfor_tune) - { - m_channelTimeout = 40000; // 40 seconds (in milliseconds) - m_channelTimer.start(); - } + start(); - if (!monitor_thread.isRunning()) - { - monitor_thread.SetParent(this); - monitor_thread.start(); - - if (!monitor_thread.isRunning()) - { - VERBOSE(VB_IMPORTANT, "Failed to create signal monitor thread"); - return; - } - } - - while (!monitor_thread.isRunning()) - usleep(50); + while (!running) + usleep(5000); } DBG_SM("Start", "end"); } @@ -253,10 +250,10 @@ void SignalMonitor::Stop() DBG_SM("Stop", "begin"); { QMutexLocker locker(&startStopLock); - if (monitor_thread.isRunning()) + if (running) { exit = true; - monitor_thread.wait(); + wait(); } } DBG_SM("Stop", "end"); @@ -292,14 +289,14 @@ void SignalMonitor::Kick() */ QStringList SignalMonitor::GetStatusList(bool kick) { - if (kick && monitor_thread.isRunning()) + if (kick && running) Kick(); - else if (!monitor_thread.isRunning()) + else if (!running) UpdateValues(); QStringList list; statusLock.lock(); - list<dispatch(me); } -} - -/** \fn SignalLoopThread::run(void) - * \brief Runs MonitorLoop() within the monitor_thread pthread. - */ -void SignalLoopThread::run(void) -{ - if (!m_parent) - return; - - threadRegister("SignalLoop"); - m_parent->MonitorLoop(); - threadDeregister(); + running = false; } /** \fn SignalMonitor::WaitForLock(int) @@ -374,36 +360,32 @@ bool SignalMonitor::WaitForLock(int timeout) MythTimer t; t.start(); - if (monitor_thread.isRunning()) + if (running) { - while (t.elapsed()GetStatus(); QMutexLocker locker(&statusLock); - - switch (status) { - case ChannelBase::changePending: - if (HasFlags(SignalMonitor::kDVBSigMon_WaitForPos)) - { - // Still waiting on rotor - m_channelTimer.start(); - channelTuned.SetValue(1); - } - else if (m_channelTimer.elapsed() > m_channelTimeout) - { - // channel change is taking too long - VERBOSE(VB_IMPORTANT, "SignalMonitor: channel change timed-out"); - error = QObject::tr("Error: channel change failed"); - channelTuned.SetValue(2); - } - else - channelTuned.SetValue(1); - break; - case ChannelBase::changeFailed: - VERBOSE(VB_IMPORTANT, "SignalMonitor: channel change failed"); - channelTuned.SetValue(2); - error = QObject::tr("Error: channel change failed"); - break; - case ChannelBase::changeSuccess: - channelTuned.SetValue(3); - break; - } - - EmitStatus(); - - if (status == ChannelBase::changeSuccess) + if (channel->IsExternalChannelChangeSupported() && + (scriptStatus.GetValue() < 2)) { - if (tablemon) - pParent->SetupDTVSignalMonitor(eit_scan); - - is_tuned = true; - return true; + scriptStatus.SetValue(channel->GetScriptStatus()); } - - return false; } void SignalMonitor::SendMessageAllGood(void) @@ -541,7 +483,7 @@ void SignalMonitor::SendMessageAllGood(void) void SignalMonitor::EmitStatus(void) { - SendMessage(kStatusChannelTuned, channelTuned); + SendMessage(kStatusChannelTuned, scriptStatus); SendMessage(kStatusSignalLock, signalLock); if (HasFlags(kSigMon_WaitForSig)) SendMessage(kStatusSignalStrength, signalStrength); diff --git a/mythtv/libs/libmythtv/signalmonitor.h b/mythtv/libs/libmythtv/signalmonitor.h index b3edce1c008..e356122dc57 100644 --- a/mythtv/libs/libmythtv/signalmonitor.h +++ b/mythtv/libs/libmythtv/signalmonitor.h @@ -10,8 +10,8 @@ using namespace std; // Qt headers -#include #include +#include // MythTV headers #include "signalmonitorvalue.h" @@ -26,22 +26,9 @@ using namespace std; inline QString sm_flags_to_string(uint64_t); class TVRec; -class SignalMonitor; -class SignalLoopThread : public QThread +class SignalMonitor : protected QThread { - Q_OBJECT - public: - SignalLoopThread() : m_parent(NULL) {} - void SetParent(SignalMonitor *parent) { m_parent = parent; } - void run(void); - private: - SignalMonitor *m_parent; -}; - -class SignalMonitor -{ - friend class SignalLoopThread; public: /// Returns true iff the card type supports signal monitoring. static inline bool IsRequired(const QString &cardtype); @@ -53,7 +40,7 @@ class SignalMonitor // // // // // // // // // // // // // // // // // // // // // // // // // Control // // // // // // // // // // // // // // // // // // // // - virtual void Start(bool waitfor_tune); + virtual void Start(); virtual void Stop(); virtual void Kick(); virtual bool WaitForLock(int timeout = -1); @@ -77,13 +64,12 @@ class SignalMonitor int GetUpdateRate() const { return update_rate; } virtual QStringList GetStatusList(bool kick = true); - bool IsTuned(void) const { return is_tuned; } - - /// \brief Returns true iff signalLock.IsGood() returns true + /// \brief Returns true iff scriptStatus.IsGood() and signalLock.IsGood() + /// return true bool HasSignalLock(void) const { QMutexLocker locker(&statusLock); - return signalLock.IsGood(); + return scriptStatus.IsGood() && signalLock.IsGood(); } virtual bool IsAllGood(void) const { return HasSignalLock(); } @@ -128,12 +114,11 @@ class SignalMonitor SignalMonitor(int db_cardnum, ChannelBase *_channel, uint64_t wait_for_mask); - virtual void MonitorLoop(); - - bool IsChannelTuned(void); + virtual void run(void) { MonitorLoop(); } // QThread + virtual void MonitorLoop(void); /// \brief This should be overridden to actually do signal monitoring. - virtual void UpdateValues() { ; } + virtual void UpdateValues(void); public: /// We've seen a PAT, @@ -163,8 +148,6 @@ class SignalMonitor /// We've seen something indicating whether the data stream is encrypted static const uint64_t kDTVSigMon_CryptSeen = 0x0000000200ULL; - static const uint64_t kSigMon_Tuned = 0x0000000400ULL; - /// We've seen a PAT matching our requirements static const uint64_t kDTVSigMon_PATMatch = 0x0000001000ULL; /// We've seen a PMT matching our requirements @@ -208,17 +191,16 @@ class SignalMonitor static const uint64_t kDVBSigMon_WaitForPos = 0x8000000000ULL; protected: - SignalLoopThread monitor_thread; ChannelBase *channel; TVRec *pParent; int capturecardnum; - uint64_t flags; + volatile uint64_t flags; int update_rate; uint minimum_update_rate; + bool running; bool exit; bool update_done; bool notify_frontend; - bool is_tuned; bool tablemon; bool eit_scan; QString error; @@ -228,7 +210,7 @@ class SignalMonitor SignalMonitorValue signalLock; SignalMonitorValue signalStrength; - SignalMonitorValue channelTuned; + SignalMonitorValue scriptStatus; vector listeners; @@ -318,19 +300,12 @@ inline QString sm_flags_to_string(uint64_t flags) inline bool SignalMonitor::IsRequired(const QString &cardtype) { - return (CardUtil::IsDVBCardType(cardtype) || - (cardtype.toUpper() == "HDTV") || - (cardtype.toUpper() == "HDHOMERUN") || - (cardtype.toUpper() == "HDPVR") || - (cardtype.toUpper() == "FIREWIRE") || - (cardtype.toUpper() == "FREEBOX")); + return (cardtype != "IMPORT" && cardtype != "DEMO"); } inline bool SignalMonitor::IsSupported(const QString &cardtype) { - return (IsRequired(cardtype) || - (cardtype.toUpper() == "V4L") || - (cardtype.toUpper() == "MPEG")); + return IsRequired(cardtype); } diff --git a/mythtv/libs/libmythtv/sourceutil.cpp b/mythtv/libs/libmythtv/sourceutil.cpp index ba40062ee86..6d7bede915e 100644 --- a/mythtv/libs/libmythtv/sourceutil.cpp +++ b/mythtv/libs/libmythtv/sourceutil.cpp @@ -221,27 +221,25 @@ bool SourceUtil::IsProperlyConnected(uint sourceid, bool strict) counts["ANALOG_TUNING"]++; else if (CardUtil::IsTuningDigital(*it)) counts["DIGITAL_TUNING"]++; - else - counts["OTHER_TUNING"]++; + else if (CardUtil::IsTuningVirtual(*it)) + counts["VIRTUAL_TUNING"]++; } - bool tune_mismatch = counts["ANALOG_TUNING"] && counts["DIGITAL_TUNING"]; - bool enc_mismatch = counts["ENCODER"] && counts["NOT_ENCODER"]; - bool scan_mismatch = counts["SCAN"] && counts["NO_SCAN"]; - bool fw_mismatch = (counts["FIREWIRE"] && - ((int)counts["FIREWIRE"] < types.size())); + bool tune_mismatch = + (counts["ANALOG_TUNING"] && counts["DIGITAL_TUNING"]) || + (counts["VIRTUAL_TUNING"] && counts["DIGITAL_TUNING"]); + bool enc_mismatch = counts["ENCODER"] && counts["NOT_ENCODER"]; + bool scan_mismatch = counts["SCAN"] && counts["NO_SCAN"]; if (tune_mismatch) { uint a = counts["ANALOG_TUNERS"]; uint d = counts["DIGITAL_TUNERS"]; + uint v = counts["VIRTUAL_TUNERS"]; VERBOSE(VB_GENERAL, QString("SourceUtil::IsProperlyConnected(): ") + - QString("Source ID %1 ").arg(sourceid) + - QString("appears to be connected\n\t\t\tto %1 analog tuner%2,") - .arg(a).arg((1 == a) ? "":"s") + - QString("and %1 digital tuner%2.\n\t\t\t") - .arg(d).arg((1 == d) ? "":"s") + - QString("Can't mix analog and digital tuning information.")); + QString("Connected to %1 analog, %2 digital and %3 virtual " + "tuners\n\t\t\t").arg(a).arg(d).arg(v) + + QString("Can not mix digital with other tuning information.")); } if (enc_mismatch) @@ -271,19 +269,10 @@ bool SourceUtil::IsProperlyConnected(uint sourceid, bool strict) QString("This may be a problem.")); } - if (fw_mismatch) - { - VERBOSE(VB_GENERAL, QString("SourceUtil::IsProperlyConnected(): ") + - QString( - "Source ID %1 appears to be connected\n\t\t\t" - "to both firewire and non-firewire inputs. " - "This is probably a bad idea.").arg(sourceid)); - } - if (!strict) return !tune_mismatch; - return !tune_mismatch && !enc_mismatch && !scan_mismatch && !fw_mismatch; + return !tune_mismatch && !enc_mismatch && !scan_mismatch; } bool SourceUtil::IsEncoder(uint sourceid, bool strict) diff --git a/mythtv/libs/libmythtv/streamhandler.cpp b/mythtv/libs/libmythtv/streamhandler.cpp new file mode 100644 index 00000000000..b8afc838a46 --- /dev/null +++ b/mythtv/libs/libmythtv/streamhandler.cpp @@ -0,0 +1,377 @@ +// -*- Mode: c++ -*- + +// MythTV headers +#include "streamhandler.h" + +#define LOC QString("SH(%1): ").arg(_device) +#define LOC_WARN QString("SH(%1) Warning: ").arg(_device) +#define LOC_ERR QString("SH(%1) Error: ").arg(_device) + +StreamHandler::StreamHandler(const QString &device) : + _device(device), + _needs_buffering(false), + _allow_section_reader(false), + + _running_desired(false), + _error(false), + _running(false), + _using_buffering(false), + _using_section_reader(false), + + _pid_lock(QMutex::Recursive), + _open_pid_filters(0), + + _listener_lock(QMutex::Recursive) +{ +} + +StreamHandler::~StreamHandler() +{ + if (!_stream_data_list.empty()) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "dtor & _stream_data_list not empty"); + } + + // This should never be triggered.. just to be safe.. + if (_running) + Stop(); +} + +void StreamHandler::AddListener(MPEGStreamData *data, + bool allow_section_reader, + bool needs_buffering, + QString output_file) +{ + VERBOSE(VB_RECORD, LOC + QString("AddListener(0x%1) -- begin") + .arg((uint64_t)data,0,16)); + if (!data) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + + QString("AddListener(0x%1) -- null data") + .arg((uint64_t)data,0,16)); + return; + } + + _listener_lock.lock(); + + VERBOSE(VB_RECORD, LOC + QString("AddListener(0x%1) -- locked") + .arg((uint64_t)data,0,16)); + + if (_stream_data_list.empty()) + { + QMutexLocker locker(&_start_stop_lock); + _allow_section_reader = allow_section_reader; + _needs_buffering = needs_buffering; + } + else + { + QMutexLocker locker(&_start_stop_lock); + _allow_section_reader &= allow_section_reader; + _needs_buffering |= needs_buffering; + } + + StreamDataList::iterator it = _stream_data_list.find(data); + if (it != _stream_data_list.end()) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "Programmer Error, attempted " + "to add a listener which is already being listened to."); + } + else + { + _stream_data_list[data] = output_file; + } + + if (!output_file.isEmpty()) + AddNamedOutputFile(output_file); + + _listener_lock.unlock(); + + Start(); + + VERBOSE(VB_RECORD, LOC + QString("AddListener(0x%1) -- end") + .arg((uint64_t)data,0,16)); +} + +void StreamHandler::RemoveListener(MPEGStreamData *data) +{ + VERBOSE(VB_RECORD, LOC + QString("RemoveListener(0x%1) -- begin") + .arg((uint64_t)data,0,16)); + if (!data) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + + QString("RemoveListener(0x%1) -- null data") + .arg((uint64_t)data,0,16)); + return; + } + + _listener_lock.lock(); + + VERBOSE(VB_RECORD, LOC + QString("RemoveListener(0x%1) -- locked") + .arg((uint64_t)data,0,16)); + + StreamDataList::iterator it = _stream_data_list.find(data); + + if (it != _stream_data_list.end()) + { + if (!(*it).isEmpty()) + RemoveNamedOutputFile(*it); + _stream_data_list.erase(it); + } + + if (_stream_data_list.empty()) + { + _listener_lock.unlock(); + Stop(); + } + else + { + _listener_lock.unlock(); + } + + VERBOSE(VB_RECORD, LOC + QString("RemoveListener(0x%1) -- end") + .arg((uint64_t)data,0,16)); +} + +void StreamHandler::Start(void) +{ + QMutexLocker locker(&_start_stop_lock); + + if (_running) + { + if ((_using_section_reader && !_allow_section_reader) || + (_needs_buffering && !_using_buffering)) + { + SetRunningDesired(false); + while (!_running_desired && _running) + _running_state_changed.wait(&_start_stop_lock, 100); + } + } + + if (_running) + return; + + _eit_pids.clear(); + + _error = false; + SetRunningDesired(true); + QThread::start(); + + while (!_running && !_error && _running_desired) + _running_state_changed.wait(&_start_stop_lock, 100); + + if (!_running_desired) + { + VERBOSE(VB_IMPORTANT, LOC_WARN + + "Programmer Error: Stop called before Start finished"); + } + + if (_error) + { + VERBOSE(VB_IMPORTANT, LOC_WARN + "Start failed"); + SetRunningDesired(false); + } +} + +void StreamHandler::Stop(void) +{ + QMutexLocker locker(&_start_stop_lock); + + do + { + SetRunningDesired(false); + while (!_running_desired && _running) + _running_state_changed.wait(&_start_stop_lock, 100); + if (_running_desired) + { + VERBOSE(VB_IMPORTANT, LOC_WARN + + "Programmer Error: Start called before Stop finished"); + } + } while (_running_desired); + + wait(); +} + +bool StreamHandler::IsRunning(void) const +{ + QMutexLocker locker(&_start_stop_lock); + return _running; +} + +void StreamHandler::SetRunning(bool is_running, + bool is_using_buffering, + bool is_using_section_reader) +{ + QMutexLocker locker(&_start_stop_lock); + _running = is_running; + _using_buffering = is_using_buffering; + _using_section_reader = is_using_section_reader; + _running_state_changed.wakeAll(); +} + +bool StreamHandler::AddPIDFilter(PIDInfo *info) +{ +#ifdef DEBUG_PID_FILTERS + VERBOSE(VB_RECORD, LOC + QString("AddPIDFilter(0x%1)") + .arg(info->_pid, 0, 16)); +#endif // DEBUG_PID_FILTERS + + QMutexLocker writing_locker(&_pid_lock); + _pid_info[info->_pid] = info; + + CycleFiltersByPriority(); + + return true; +} + +bool StreamHandler::RemovePIDFilter(uint pid) +{ +#ifdef DEBUG_PID_FILTERS + VERBOSE(VB_RECORD, LOC + + QString("RemovePIDFilter(0x%1)").arg(pid, 0, 16)); +#endif // DEBUG_PID_FILTERS + + QMutexLocker write_locker(&_pid_lock); + + PIDInfoMap::iterator it = _pid_info.find(pid); + if (it == _pid_info.end()) + return false; + + PIDInfo *tmp = *it; + _pid_info.erase(it); + + bool ok = true; + if (tmp->IsOpen()) + { + ok = tmp->Close(_device); + _open_pid_filters--; + + CycleFiltersByPriority(); + } + + delete tmp; + + return ok; +} + +bool StreamHandler::RemoveAllPIDFilters(void) +{ + QMutexLocker write_locker(&_pid_lock); + +#ifdef DEBUG_PID_FILTERS + VERBOSE(VB_RECORD, LOC + "RemoveAllPIDFilters()"); +#endif // DEBUG_PID_FILTERS + + vector del_pids; + PIDInfoMap::iterator it = _pid_info.begin(); + for (; it != _pid_info.end(); ++it) + del_pids.push_back(it.key()); + + bool ok = true; + vector::iterator dit = del_pids.begin(); + for (; dit != del_pids.end(); ++dit) + ok &= RemovePIDFilter(*dit); + + return UpdateFilters() && ok; +} + +void StreamHandler::UpdateListeningForEIT(void) +{ + vector add_eit, del_eit; + + QMutexLocker read_locker(&_listener_lock); + + StreamDataList::const_iterator it = _stream_data_list.begin(); + for (; it != _stream_data_list.end(); ++it) + { + MPEGStreamData *sd = it.key(); + if (sd->HasEITPIDChanges(_eit_pids) && + sd->GetEITPIDChanges(_eit_pids, add_eit, del_eit)) + { + for (uint i = 0; i < del_eit.size(); i++) + { + uint_vec_t::iterator it; + it = find(_eit_pids.begin(), _eit_pids.end(), del_eit[i]); + if (it != _eit_pids.end()) + _eit_pids.erase(it); + sd->RemoveListeningPID(del_eit[i]); + } + + for (uint i = 0; i < add_eit.size(); i++) + { + _eit_pids.push_back(add_eit[i]); + sd->AddListeningPID(add_eit[i]); + } + } + } +} + +bool StreamHandler::UpdateFiltersFromStreamData(void) +{ + UpdateListeningForEIT(); + + pid_map_t pids; + + { + QMutexLocker read_locker(&_listener_lock); + StreamDataList::const_iterator it = _stream_data_list.begin(); + for (; it != _stream_data_list.end(); ++it) + it.key()->GetPIDs(pids); + } + + QMap add_pids; + vector del_pids; + + { + QMutexLocker read_locker(&_pid_lock); + + // PIDs that need to be added.. + pid_map_t::const_iterator lit = pids.constBegin(); + for (; lit != pids.constEnd(); ++lit) + { + if (*lit && (_pid_info.find(lit.key()) == _pid_info.end())) + { + add_pids[lit.key()] = CreatePIDInfo( + lit.key(), StreamID::PrivSec, 0); + } + } + + // PIDs that need to be removed.. + PIDInfoMap::const_iterator fit = _pid_info.begin(); + for (; fit != _pid_info.end(); ++fit) + { + bool in_pids = pids.find(fit.key()) != pids.end(); + if (!in_pids) + del_pids.push_back(fit.key()); + } + } + + // Remove PIDs + bool ok = true; + vector::iterator dit = del_pids.begin(); + for (; dit != del_pids.end(); ++dit) + ok &= RemovePIDFilter(*dit); + + // Add PIDs + QMap::iterator ait = add_pids.begin(); + for (; ait != add_pids.end(); ++ait) + ok &= AddPIDFilter(*ait); + + // Cycle filters if it's been a while + if (_cycle_timer.elapsed() > 1000) + CycleFiltersByPriority(); + + return ok; +} + +PIDPriority StreamHandler::GetPIDPriority(uint pid) const +{ + QMutexLocker reading_locker(&_listener_lock); + + PIDPriority tmp = kPIDPriorityNone; + + StreamDataList::const_iterator it = _stream_data_list.begin(); + for (; it != _stream_data_list.end(); ++it) + tmp = max(tmp, it.key()->GetPIDPriority(pid)); + + return tmp; +} diff --git a/mythtv/libs/libmythtv/streamhandler.h b/mythtv/libs/libmythtv/streamhandler.h new file mode 100644 index 00000000000..b8b2d2dac11 --- /dev/null +++ b/mythtv/libs/libmythtv/streamhandler.h @@ -0,0 +1,123 @@ +// -*- Mode: c++ -*- + +#ifndef _STREAM_HANDLER_H_ +#define _STREAM_HANDLER_H_ + +#include +using namespace std; + +#include +#include +#include +#include +#include + +#include "DeviceReadBuffer.h" // for ReaderPausedCB +#include "mpegstreamdata.h" // for PIDPriority +#include "util.h" + +//#define DEBUG_PID_FILTERS + +class PIDInfo +{ + public: + PIDInfo() : + _pid(0xffffffff), filter_fd(-1), streamType(0), pesType(-1) {;} + PIDInfo(uint pid) : + _pid(pid), filter_fd(-1), streamType(0), pesType(-1) {;} + PIDInfo(uint pid, uint stream_type, int pes_type) : + _pid(pid), filter_fd(-1), + streamType(stream_type), pesType(pes_type) {;} + + virtual bool Open(const QString &dev, bool use_section_reader) + { return false; } + virtual bool Close(const QString &dev) { return false; } + bool IsOpen(void) const { return filter_fd >= 0; } + + uint _pid; + int filter_fd; ///< Input filter file descriptor + uint streamType; ///< StreamID + int pesType; ///< PESStreamID +}; +// Please do not change this to hash or other unordered container. +// HDHRStreamHandler::UpdateFilters() relies on the forward +// iterator returning these in order of ascending pid number. +typedef QMap PIDInfoMap; + +// locking order +// _pid_lock -> _listener_lock -> _start_stop_lock + +class StreamHandler : protected QThread, public DeviceReaderCB +{ + public: + virtual void AddListener(MPEGStreamData *data, + bool allow_section_reader = false, + bool needs_drb = false, + QString output_file = QString()); + virtual void RemoveListener(MPEGStreamData *data); + bool IsRunning(void) const; + + protected: + StreamHandler(const QString &device); + ~StreamHandler(); + + void Start(void); + void Stop(void); + + void SetRunning(bool running, + bool using_buffering, + bool using_section_reader); + + bool AddPIDFilter(PIDInfo *info); + bool RemovePIDFilter(uint pid); + bool RemoveAllPIDFilters(void); + + void UpdateListeningForEIT(void); + bool UpdateFiltersFromStreamData(void); + virtual bool UpdateFilters(void) { return true; } + virtual void CycleFiltersByPriority() {} + + PIDPriority GetPIDPriority(uint pid) const; + + // DeviceReaderCB + virtual void ReaderPaused(int fd) { (void) fd; } + virtual void PriorityEvent(int fd) { (void) fd; } + + virtual PIDInfo *CreatePIDInfo(uint pid, uint stream_type, int pes_type) + { return new PIDInfo(pid, stream_type, pes_type); } + + protected: + /// Called with _listener_lock locked just after adding new output file. + virtual void AddNamedOutputFile(const QString &filename) {} + /// Called with _listener_lock locked just before removing old output file. + virtual void RemoveNamedOutputFile(const QString &filename) {} + /// At minimum this sets _running_desired, this may also send + /// signals to anything that might be blocking the run() loop. + /// \note: The _start_stop_lock must be held when this is called. + virtual void SetRunningDesired(bool desired) { _running_desired = desired; } + + protected: + QString _device; + bool _needs_buffering; + bool _allow_section_reader; + + mutable QMutex _start_stop_lock; + volatile bool _running_desired; + volatile bool _error; + bool _running; + bool _using_buffering; + bool _using_section_reader; + QWaitCondition _running_state_changed; + + mutable QMutex _pid_lock; + vector _eit_pids; + PIDInfoMap _pid_info; + uint _open_pid_filters; + MythTimer _cycle_timer; + + typedef QMap StreamDataList; + mutable QMutex _listener_lock; + StreamDataList _stream_data_list; +}; + +#endif // _STREAM_HANDLER_H_ diff --git a/mythtv/libs/libmythtv/tv.h b/mythtv/libs/libmythtv/tv.h index 4bfaa9454a4..c2d35b8dcd3 100644 --- a/mythtv/libs/libmythtv/tv.h +++ b/mythtv/libs/libmythtv/tv.h @@ -7,6 +7,25 @@ #include "mythuiactions.h" #include "tv_actions.h" +class VBIMode +{ + public: + typedef enum + { + None = 0, + PAL_TT = 1, + NTSC_CC = 2, + } vbimode_t; + + static uint Parse(QString vbiformat) + { + QString fmt = vbiformat.toLower().left(3); + vbimode_t mode; + mode = (fmt == "pal") ? PAL_TT : ((fmt == "nts") ? NTSC_CC : None); + return (uint) mode; + } +}; + /** \brief ChannelChangeDirection is an enumeration of possible channel * changing directions. */ diff --git a/mythtv/libs/libmythtv/tv_play.h b/mythtv/libs/libmythtv/tv_play.h index 32f9d50fc95..c009e266726 100644 --- a/mythtv/libs/libmythtv/tv_play.h +++ b/mythtv/libs/libmythtv/tv_play.h @@ -97,25 +97,6 @@ typedef void (*EMBEDRETURNVOIDSCHEDIT) (const ProgramInfo *, void *); // desirable and should be avoided when possible.) // -class VBIMode -{ - public: - typedef enum - { - None = 0, - PAL_TT = 1, - NTSC_CC = 2, - } vbimode_t; - - static uint Parse(QString vbiformat) - { - QString fmt = vbiformat.toLower().left(3); - vbimode_t mode; - mode = (fmt == "pal") ? PAL_TT : ((fmt == "nts") ? NTSC_CC : None); - return (uint) mode; - } -}; - enum scheduleEditTypes { kScheduleProgramGuide = 0, kScheduleProgramFinder, diff --git a/mythtv/libs/libmythtv/tv_rec.cpp b/mythtv/libs/libmythtv/tv_rec.cpp index 330a400603b..b0757f198e5 100644 --- a/mythtv/libs/libmythtv/tv_rec.cpp +++ b/mythtv/libs/libmythtv/tv_rec.cpp @@ -5,63 +5,37 @@ #include #include // for sched_yield -// C++ headers -#include -using namespace std; - // MythTV headers #include "compat.h" #include "previewgeneratorqueue.h" -#include "mythconfig.h" -#include "tv_rec.h" -#include "osd.h" -#include "mythcorecontext.h" -#include "dialogbox.h" -#include "recordingprofile.h" -#include "util.h" -#include "programinfo.h" #include "dtvsignalmonitor.h" -#include "mythdb.h" -#include "jobqueue.h" -#include "recordingrule.h" -#include "eitscanner.h" -#include "ringbuffer.h" -#include "storagegroup.h" -#include "remoteutil.h" -#include "tvremoteutil.h" +#include "recordingprofile.h" +#include "mythcorecontext.h" #include "mythsystemevent.h" - #include "atscstreamdata.h" #include "dvbstreamdata.h" -#include "atsctables.h" - +#include "recordingrule.h" +#include "channelgroup.h" +#include "storagegroup.h" +#include "tvremoteutil.h" +#include "dtvrecorder.h" #include "livetvchain.h" - -#include "channelutil.h" -#include "channelbase.h" -#include "dummychannel.h" +#include "programinfo.h" +#include "atsctables.h" #include "dtvchannel.h" -#include "dvbchannel.h" -#include "hdhrchannel.h" -#include "iptvchannel.h" -#include "firewirechannel.h" - -#include "recorderbase.h" -#include "NuppelVideoRecorder.h" -#include "mpegrecorder.h" -#include "dvbrecorder.h" -#include "hdhrrecorder.h" -#include "iptvrecorder.h" -#include "firewirerecorder.h" -#include "importrecorder.h" +#include "eitscanner.h" +#include "mythconfig.h" +#include "remoteutil.h" +#include "ringbuffer.h" #include "mythlogging.h" - -#include "channelgroup.h" - -#ifdef USING_V4L #include "v4lchannel.h" -#endif +#include "dialogbox.h" +#include "jobqueue.h" +#include "mythdb.h" +#include "tv_rec.h" +#include "util.h" +#include "osd.h" #define DEBUG_CHANNEL_PREFIX 0 /**< set to 1 to channel prefixing */ @@ -78,6 +52,8 @@ static bool is_dishnet_eit(uint cardid); static QString load_profile(QString,void*,RecordingInfo*,RecordingProfile&); static int init_jobs(const RecordingInfo *rec, RecordingProfile &profile, bool on_host, bool transcode_bfr_comm, bool on_line_comm); +static void apply_broken_dvb_driver_crc_hack(ChannelBase*, MPEGStreamData*); + /** \class TVRec * \brief This is the coordinating class of the \ref recorder_subsystem. @@ -129,7 +105,7 @@ TVRec::TVRec(int capturecardnum) triggerEventLoopSignal(false), triggerEventSleepLock(QMutex::NonRecursive), triggerEventSleepSignal(false), - m_switchingBuffer(false), + switchingBuffer(false), m_recStatus(rsUnknown), // Current recording info curRecording(NULL), autoRunJobs(JOB_NONE), @@ -146,93 +122,19 @@ TVRec::TVRec(int capturecardnum) cards[cardid] = this; } -bool TVRec::CreateChannel(const QString &startchannel) +bool TVRec::CreateChannel(const QString &startchannel, + bool enter_power_save_mode) { - rbFileExt = "mpg"; - bool init_run = false; - - if (genOpt.cardtype == "DVB") - { -#ifdef USING_DVB - channel = new DVBChannel(genOpt.videodev, this); - if (!channel->Open()) - return false; - GetDVBChannel()->SetSlowTuning(dvbOpt.dvb_tuning_delay); - InitChannel(genOpt.defaultinput, startchannel); - CloseChannel(); // Close the channel if in dvb_on_demand mode - init_run = true; -#endif - } - else if (genOpt.cardtype == "FIREWIRE") - { -#ifdef USING_FIREWIRE - channel = new FirewireChannel(this, genOpt.videodev, fwOpt); - if (!channel->Open()) - return false; - InitChannel(genOpt.defaultinput, startchannel); - init_run = true; -#endif - } - else if (genOpt.cardtype == "HDHOMERUN") - { -#ifdef USING_HDHOMERUN - channel = new HDHRChannel(this, genOpt.videodev); - if (!channel->Open()) - return false; - InitChannel(genOpt.defaultinput, startchannel); - GetDTVChannel()->EnterPowerSavingMode(); - init_run = true; -#endif - } - else if ((genOpt.cardtype == "IMPORT") || - (genOpt.cardtype == "DEMO") || - (genOpt.cardtype == "MPEG" && - genOpt.videodev.toLower().left(5) == "file:")) - { - channel = new DummyChannel(this); - if (!channel->Open()) - return false; - InitChannel(genOpt.defaultinput, startchannel); - init_run = true; - } - else if (genOpt.cardtype == "FREEBOX") - { -#ifdef USING_IPTV - channel = new IPTVChannel(this, genOpt.videodev); - if (!channel->Open()) - return false; - InitChannel(genOpt.defaultinput, startchannel); - init_run = true; -#endif - } - else // "V4L" or "MPEG", ie, analog TV - { -#ifdef USING_V4L - channel = new V4LChannel(this, genOpt.videodev); - if (!channel->Open()) - return false; - InitChannel(genOpt.defaultinput, startchannel); - CloseChannel(); - init_run = true; -#endif - if ((genOpt.cardtype != "MPEG") && (genOpt.cardtype != "HDPVR")) - rbFileExt = "nuv"; - } + channel = ChannelBase::CreateChannel( + this, genOpt, dvbOpt, fwOpt, + startchannel, enter_power_save_mode, rbFileExt); - if (!init_run) + if (!channel) { - QString msg = QString( - "%1 card configured on video device %2, \n" - "but MythTV was not compiled with %3 support. \n" - "\n" - "Recompile MythTV with %4 support or remove the card \n" - "from the configuration and restart MythTV.") - .arg(genOpt.cardtype).arg(genOpt.videodev) - .arg(genOpt.cardtype).arg(genOpt.cardtype); - VERBOSE(VB_IMPORTANT, LOC_ERR + "\n" + msg + "\n"); SetFlags(kFlagErrored); return false; } + return true; } @@ -254,7 +156,7 @@ bool TVRec::Init(void) // configure the Channel instance QString startchannel = GetStartChannel(cardid, genOpt.defaultinput); - if (!CreateChannel(startchannel)) + if (!CreateChannel(startchannel, true)) return false; transcodeFirst = @@ -495,8 +397,6 @@ void TVRec::CancelNextRecording(bool cancel) * \brief Tells TVRec to Start recording the program "rcinfo" * as soon as possible. * - * \return +1 if the recording started successfully, - * -1 if TVRec is busy doing something else, 0 otherwise. * \sa EncoderLink::StartRecording(const ProgramInfo*) * RecordPending(const ProgramInfo*, int, bool), StopRecording() */ @@ -539,9 +439,8 @@ RecStatusType TVRec::StartRecording(const ProgramInfo *rcinfo) ClearFlags(kFlagCancelNextRecording); - pendingRecLock.lock(); + QMutexLocker locker(&pendingRecLock); m_recStatus = rsRecording; - pendingRecLock.unlock(); return rsRecording; } @@ -656,12 +555,27 @@ RecStatusType TVRec::StartRecording(const ProgramInfo *rcinfo) VERBOSE(VB_RECORD, LOC + "Checking input group recorders - done"); } - // If in post-roll, end recording + bool did_switch = false; if (!cancelNext && (GetState() == kState_RecordingOnly)) { - stateChangeLock.unlock(); - StopRecording(); - stateChangeLock.lock(); + RecordingInfo *ri = SwitchRecordingRingBuffer(*rcinfo); + did_switch = (NULL != ri); + if (did_switch) + { + // Make sure scheduler is allowed to end this recording + ClearFlags(kFlagCancelNextRecording); + + pendingRecLock.lock(); + m_recStatus = rsRecording; + pendingRecLock.unlock(); + } + else + { + // If in post-roll, end recording + stateChangeLock.unlock(); + StopRecording(); + stateChangeLock.lock(); + } } if (!cancelNext && (GetState() == kState_None)) @@ -706,7 +620,7 @@ RecStatusType TVRec::StartRecording(const ProgramInfo *rcinfo) MythEvent me(message, prog); gCoreContext->dispatch(me); } - else + else if (!did_switch) { msg = QString("Wanted to record: %1 %2 %3 %4\n\t\t\t") .arg(rcinfo->GetTitle()).arg(rcinfo->GetChanID()) @@ -742,18 +656,20 @@ RecStatusType TVRec::StartRecording(const ProgramInfo *rcinfo) delete pendingRecordings[i].info; pendingRecordings.clear(); - WaitForEventThreadSleep(); - - RecStatusType status; + if (!did_switch) + { + WaitForEventThreadSleep(); - pendingRecLock.lock(); - if ((curRecording) && (curRecording->GetRecordingStatus() == rsFailed) && - (m_recStatus == rsRecording || m_recStatus == rsTuning)) - m_recStatus = rsFailed; - status = m_recStatus; - pendingRecLock.unlock(); + QMutexLocker locker(&pendingRecLock); + if ((curRecording) && + (curRecording->GetRecordingStatus() == rsFailed) && + (m_recStatus == rsRecording || m_recStatus == rsTuning)) + { + m_recStatus = rsFailed; + } + } - return status; + return m_recStatus; } RecStatusType TVRec::GetRecordingStatus(void) const @@ -873,21 +789,73 @@ void TVRec::FinishedRecording(RecordingInfo *curRec) if (!curRec) return; + // Make sure the recording group is up to date const QString recgrp = curRec->QueryRecordingGroup(); curRec->SetRecordingGroup(recgrp); - VERBOSE(VB_RECORD, LOC + QString("FinishedRecording(%1) in recgroup: %2") - .arg(curRec->GetTitle()).arg(recgrp)); - + RecStatusTypes ors = curRec->GetRecordingStatus(); + // Set the final recording status if (curRec->GetRecordingStatus() == rsRecording) curRec->SetRecordingStatus(rsRecorded); else if (curRec->GetRecordingStatus() != rsRecorded) curRec->SetRecordingStatus(rsFailed); curRec->SetRecordingEndTime(mythCurrentDateTime()); + // Figure out if this was already done for this recording + bool was_finished = false; + static QMutex finRecLock; + static QHash finRecMap; + { + QMutexLocker locker(&finRecLock); + QDateTime now = QDateTime::currentDateTime(); + QDateTime expired = now.addSecs(-60*5); + QHash::iterator it = finRecMap.begin(); + while (it != finRecMap.end()) + { + if ((*it) < expired) + it = finRecMap.erase(it); + else + ++it; + } + QString key = curRec->MakeUniqueKey(); + it = finRecMap.find(key); + if (it != finRecMap.end()) + was_finished = true; + else + finRecMap[key] = now; + } + + // Print something informative to the log + VERBOSE(VB_RECORD, LOC + + QString("FinishedRecording(%1)" + "\n\t\t\tkey: %2\n\t\t\t" + "in recgroup: %3 status: %4:%5 %6 %7") + .arg(curRec->GetTitle()) + .arg(curRec->MakeUniqueKey()) + .arg(recgrp) + .arg(toString(ors, kSingleRecord)) + .arg(toString(curRec->GetRecordingStatus(), kSingleRecord)) + .arg(HasFlags(kFlagDummyRecorderRunning)?"is_dummy":"not_dummy") + .arg(was_finished?"already_finished":"finished_now")); + + // This has already been called on this recording.. + if (was_finished) + return; + + // Notify the frontend watching live tv that this file is final if (tvchain) tvchain->FinishedRecording(curRec); + // if this is a dummy recorder, do no more.. + if (HasFlags(kFlagDummyRecorderRunning)) + return; + + // Get the width and set the videoprops + uint avg_height = curRec->QueryAverageHeight(); + curRec->SaveResolutionProperty( + (avg_height > 1000) ? VID_1080 : + ((avg_height > 700) ? VID_720 : VID_UNKNOWN)); + // Make sure really short recordings have positive run time. if (curRec->GetRecordingEndTime() <= curRec->GetRecordingStartTime()) { @@ -895,12 +863,16 @@ void TVRec::FinishedRecording(RecordingInfo *curRec) curRec->GetRecordingStartTime().addSecs(60)); } - // Round up recording end time (probably should be done in the UI only) - QDateTime recendts = curRec->GetRecordingEndTime(); - recendts.setTime(QTime(recendts.addSecs(30).time().hour(), - recendts.addSecs(30).time().minute())); - curRec->SetRecordingEndTime(recendts); + // Generate a preview + uint64_t fsize = (curRec->GetFilesize() < 1000) ? + curRec->QueryFilesize() : curRec->GetFilesize(); + if (curRec->IsLocal() && (fsize >= 1000) && + (curRec->GetRecordingStatus() == rsRecorded)) + { + PreviewGeneratorQueue::GetPreviewImage(*curRec, ""); + } + // send out UPDATE_RECORDING_STATUS message if (recgrp != "LiveTV") { MythEvent me(QString("UPDATE_RECORDING_STATUS %1 %2 %3 %4 %5") @@ -912,7 +884,34 @@ void TVRec::FinishedRecording(RecordingInfo *curRec) gCoreContext->dispatch(me); } - curRec->FinishedRecording(curRec->GetRecordingStatus() != rsRecorded); + // store recording in recorded table + if (recgrp != "LiveTV") + curRec->FinishedRecording(curRec->GetRecordingStatus() != rsRecorded); + + // send out REC_FINISHED message + SendMythSystemRecEvent("REC_FINISHED", curRecording); + + // send out DONE_RECORDING message + int secsSince = curRec->GetRecordingStartTime() + .secsTo(QDateTime::currentDateTime()); + QString message = QString("DONE_RECORDING %1 %2 %3") + .arg(cardid).arg(secsSince).arg(GetFramesWritten()); + MythEvent me(message); + gCoreContext->dispatch(me); + + // Handle JobQueue + if ((recgrp == "LiveTV") || (fsize < 1000) || + (curRec->GetRecordingStatus() != rsRecorded) || + (curRec->GetRecordingStartTime().secsTo( + QDateTime::currentDateTime()) < 120)) + { + JobQueue::RemoveJobsFromMask(JOB_COMMFLAG, autoRunJobs); + JobQueue::RemoveJobsFromMask(JOB_TRANSCODE, autoRunJobs); + } + if (autoRunJobs) + { + JobQueue::QueueRecordingJobs(*curRec, autoRunJobs); + } } #define TRANSITION(ASTATE,BSTATE) \ @@ -1013,116 +1012,6 @@ void TVRec::ChangeState(TVState nextState) WakeEventLoop(); } -/** \fn TVRec::SetupRecorder(RecordingProfile&) - * \brief Allocates and initializes the RecorderBase instance. - * - * Based on the card type, one of the possible recorders are started. - * If the card type is "MPEG" or "HDPVR" a MpegRecorder is started, - * if the card type is "HDHOMERUN" a HDHRRecorder is started, - * if the card type is "FIREWIRE" a FirewireRecorder is started, - * if the card type is "DVB" a DVBRecorder is started, - * otherwise a NuppelVideoRecorder is started. - * - * If there is any this will return false. - * \sa IsErrored() - */ -bool TVRec::SetupRecorder(RecordingProfile &profile) -{ - recorder = NULL; - if (genOpt.cardtype == "MPEG") - { -#ifdef USING_IVTV - recorder = new MpegRecorder(this); -#endif // USING_IVTV - } - else if (genOpt.cardtype == "HDPVR") - { -#ifdef USING_HDPVR - recorder = new MpegRecorder(this); -#endif // USING_HDPVR - } - else if (genOpt.cardtype == "FIREWIRE") - { -#ifdef USING_FIREWIRE - recorder = new FirewireRecorder(this, GetFirewireChannel()); -#endif // USING_FIREWIRE - } - else if (genOpt.cardtype == "HDHOMERUN") - { -#ifdef USING_HDHOMERUN - recorder = new HDHRRecorder(this, GetHDHRChannel()); - ringBuffer->SetWriteBufferSize(4*1024*1024); - recorder->SetOption("wait_for_seqstart", genOpt.wait_for_seqstart); -#endif // USING_HDHOMERUN - } - else if (genOpt.cardtype == "DVB") - { -#ifdef USING_DVB - recorder = new DVBRecorder(this, GetDVBChannel()); - ringBuffer->SetWriteBufferSize(4*1024*1024); - recorder->SetOption("wait_for_seqstart", genOpt.wait_for_seqstart); - recorder->SetOption("dvb_on_demand", dvbOpt.dvb_on_demand); -#endif // USING_DVB - } - else if (genOpt.cardtype == "FREEBOX") - { -#ifdef USING_IPTV - IPTVChannel *chan = dynamic_cast(channel); - recorder = new IPTVRecorder(this, chan); - ringBuffer->SetWriteBufferSize(4*1024*1024); - recorder->SetOption("mrl", genOpt.videodev); -#endif // USING_IPTV - } - else if (genOpt.cardtype == "IMPORT") - { - recorder = new ImportRecorder(this); - } - else if (genOpt.cardtype == "DEMO") - { -#ifdef USING_IVTV - recorder = new MpegRecorder(this); -#else - recorder = new ImportRecorder(this); -#endif - } - else - { -#ifdef USING_V4L - // V4L/MJPEG/GO7007 from here on - recorder = new NuppelVideoRecorder(this, channel); - recorder->SetOption("skipbtaudio", genOpt.skip_btaudio); -#endif // USING_V4L - } - - if (recorder) - { - recorder->SetOptionsFromProfile( - &profile, genOpt.videodev, genOpt.audiodev, genOpt.vbidev); - // Override the samplerate defined in the profile if this card - // was configured with a fixed rate. - if (genOpt.audiosamplerate) - recorder->SetOption("samplerate", genOpt.audiosamplerate); - recorder->SetRingBuffer(ringBuffer); - recorder->Initialize(); - - if (recorder->IsErrored()) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to initialize recorder!"); - delete recorder; - recorder = NULL; - return false; - } - - return true; - } - - QString msg = "Need %1 recorder, but compiled without %2 support!"; - msg = msg.arg(genOpt.cardtype).arg(genOpt.cardtype); - VERBOSE(VB_IMPORTANT, LOC_ERR + msg); - - return false; -} - /** \fn TVRec::TeardownRecorder(bool) * \brief Tears down the recorder. * @@ -1147,19 +1036,6 @@ void TVRec::TeardownRecorder(bool killFile) if (recorder && HasFlags(kFlagRecorderRunning)) { - // Get the width and set the videoprops - uint avg_height = curRecording->QueryAverageHeight(); - curRecording->SaveResolutionProperty( - (avg_height > 1000) ? VID_1080 : - ((avg_height > 700) ? VID_720 : VID_UNKNOWN)); - - int secsSince = curRecording->GetRecordingStartTime() - .secsTo(QDateTime::currentDateTime()); - QString message = QString("DONE_RECORDING %1 %2 %3") - .arg(cardid).arg(secsSince).arg(GetFramesWritten()); - MythEvent me(message); - gCoreContext->dispatch(me); - recorder->StopRecording(); recorderThread->wait(); delete recorderThread; @@ -1172,6 +1048,7 @@ void TVRec::TeardownRecorder(bool killFile) if (GetV4LChannel()) channel->SetFd(-1); + QMutexLocker locker(&stateChangeLock); delete recorder; recorder = NULL; } @@ -1181,30 +1058,6 @@ void TVRec::TeardownRecorder(bool killFile) if (curRecording) { - if (!killFile) - { - if (curRecording->IsLocal()) - PreviewGeneratorQueue::GetPreviewImage(*curRecording, ""); - - if (!tvchain) - { - int secsSince = curRecording->GetRecordingStartTime() - .secsTo(QDateTime::currentDateTime()); - if (secsSince < 120) - { - JobQueue::RemoveJobsFromMask(JOB_COMMFLAG, autoRunJobs); - JobQueue::RemoveJobsFromMask(JOB_TRANSCODE, autoRunJobs); - } - - if (autoRunJobs) - JobQueue::QueueRecordingJobs(*curRecording, autoRunJobs); - } - } - - FinishedRecording(curRecording); - - SendMythSystemRecEvent("REC_FINISHED", curRecording); - curRecording->MarkAsInUse(false, kRecorderInUseID); delete curRecording; curRecording = NULL; @@ -1216,53 +1069,19 @@ void TVRec::TeardownRecorder(bool killFile) GetDTVChannel()->EnterPowerSavingMode(); } -DVBRecorder *TVRec::GetDVBRecorder(void) -{ -#ifdef USING_DVB - return dynamic_cast(recorder); -#else // if !USING_DVB - return NULL; -#endif // !USING_DVB -} - -HDHRRecorder *TVRec::GetHDHRRecorder(void) -{ -#ifdef USING_HDHOMERUN - return dynamic_cast(recorder); -#else // if !USING_HDHOMERUN - return NULL; -#endif // !USING_HDHOMERUN -} - DTVRecorder *TVRec::GetDTVRecorder(void) { return dynamic_cast(recorder); } -/** \fn TVRec::InitChannel(const QString&, const QString&) - * \brief Performs ChannelBase instance init from database and - * tuner hardware (requires that channel be open). - */ -void TVRec::InitChannel(const QString &inputname, const QString &startchannel) -{ - if (!channel) - return; - - QString input = inputname; - QString channum = startchannel; - - channel->Init(input, channum, true); -} - void TVRec::CloseChannel(void) { - if (!channel) - return; - - if (GetDVBChannel() && !dvbOpt.dvb_on_demand) - return; - - channel->Close(); + if (channel && + ((genOpt.cardtype == "DVB" && dvbOpt.dvb_on_demand) || + CardUtil::IsV4L(genOpt.cardtype))) + { + channel->Close(); + } } DTVChannel *TVRec::GetDTVChannel(void) @@ -1270,33 +1089,6 @@ DTVChannel *TVRec::GetDTVChannel(void) return dynamic_cast(channel); } -HDHRChannel *TVRec::GetHDHRChannel(void) -{ -#ifdef USING_HDHOMERUN - return dynamic_cast(channel); -#else - return NULL; -#endif // USING_HDHOMERUN -} - -DVBChannel *TVRec::GetDVBChannel(void) -{ -#ifdef USING_DVB - return dynamic_cast(channel); -#else - return NULL; -#endif // USING_DVB -} - -FirewireChannel *TVRec::GetFirewireChannel(void) -{ -#ifdef USING_FIREWIRE - return dynamic_cast(channel); -#else - return NULL; -#endif // USING_FIREWIRE -} - V4LChannel *TVRec::GetV4LChannel(void) { #ifdef USING_V4L @@ -1306,7 +1098,7 @@ V4LChannel *TVRec::GetV4LChannel(void) #endif // USING_V4L } -/** \fn TVReEventThread::run(void) +/** \fn TVRecEventThread::run(void) * \brief Thunk that allows event thread to call RunTV(). */ void TVRecEventThread::run(void) @@ -1394,7 +1186,7 @@ void TVRec::RunTV(void) eitScanStartTime = QDateTime::currentDateTime(); // check whether we should use the EITScanner in this TVRec instance if (CardUtil::IsEITCapable(genOpt.cardtype) && - (!GetDVBChannel() || GetDVBChannel()->IsMaster())) + (!GetDTVChannel() || GetDTVChannel()->IsMaster())) { scanner = new EITScanner(cardid); uint timeout = eitCrawlIdleStart; @@ -1438,8 +1230,12 @@ void TVRec::RunTV(void) // If we are recording a program, check if the recording is // over or someone has asked us to finish the recording. - if (GetState() == kState_RecordingOnly && - (QDateTime::currentDateTime() > recordEndTime || + // Add an extra 60 seconds to the recording end time if we + // might want a back to back recording. + QDateTime recEnd = (!pendingRecordings.empty()) ? + recordEndTime.addSecs(60) : recordEndTime; + if ((GetState() == kState_RecordingOnly) && + (QDateTime::currentDateTime() > recEnd || HasFlags(kFlagFinishRecording))) { ChangeState(kState_None); @@ -1486,33 +1282,14 @@ void TVRec::RunTV(void) if (has_rec && (has_finish || (now > recordEndTime))) { - if (pseudoLiveTVRecording && curRecording) - { - int secsSince = curRecording->GetRecordingStartTime() - .secsTo(QDateTime::currentDateTime()); - if (secsSince < 120) - { - JobQueue::RemoveJobsFromMask(JOB_COMMFLAG, - autoRunJobs); - JobQueue::RemoveJobsFromMask(JOB_TRANSCODE, - autoRunJobs); - } - - if (autoRunJobs) - { - JobQueue::QueueRecordingJobs( - *curRecording, autoRunJobs); - } - } - SetPseudoLiveTVRecording(NULL); } else if (!has_rec && !rec_soon && curRecording && (now >= curRecording->GetScheduledEndTime())) { - if (!m_switchingBuffer) + if (!switchingBuffer) { - m_switchingBuffer = true; + switchingBuffer = true; SwitchLiveTVRingBuffer(channel->GetCurrentName(), false, true); @@ -1632,6 +1409,9 @@ bool TVRec::WaitForEventThreadSleep(bool wake, ulong time) while (!ok && ((unsigned long) t.elapsed()) < time) { + MythTimer t2; + t2.start(); + if (wake) WakeEventLoop(); @@ -1650,6 +1430,10 @@ bool TVRec::WaitForEventThreadSleep(bool wake, ulong time) // verify that we were triggered. ok = (tuningRequests.empty() && !changeState); + + int te = t2.elapsed(); + if (!ok && te < 10) + usleep((10-te) * 1000); } return ok; } @@ -1925,17 +1709,17 @@ static bool ApplyCachedPids(DTVSignalMonitor *dtvMon, const DTVChannel* channel) bool vctpid_cached = false; for (; it != pid_cache.end(); ++it) { - if ((it->second == TableID::TVCT) || - (it->second == TableID::CVCT)) + if ((it->GetTableID() == TableID::TVCT) || + (it->GetTableID() == TableID::CVCT)) { vctpid_cached = true; - dtvMon->GetATSCStreamData()->AddListeningPID(it->first); + dtvMon->GetATSCStreamData()->AddListeningPID(it->GetPID()); } } return vctpid_cached; } -/** \fn bool TVRec::SetupDTVSignalMonitor(void) +/** * \brief Tells DTVSignalMonitor what channel to look for. * * If the major and minor channels are set we tell the signal @@ -1946,6 +1730,9 @@ static bool ApplyCachedPids(DTVSignalMonitor *dtvMon, const DTVChannel* channel) * * This method also grabs the ATSCStreamData() from the recorder * if possible, or creates one if needed. + * + * \param EITscan if set we never look for video streams and we + * lock on encrypted streams even if we can't decode them. */ bool TVRec::SetupDTVSignalMonitor(bool EITscan) { @@ -2009,7 +1796,6 @@ bool TVRec::SetupDTVSignalMonitor(bool EITscan) // Check if this is an DVB channel int progNum = dtvchan->GetProgramNumber(); -#ifdef USING_DVB if ((progNum >= 0) && (tuningmode == "dvb")) { int netid = dtvchan->GetOriginalNetworkID(); @@ -2028,11 +1814,7 @@ bool TVRec::SetupDTVSignalMonitor(bool EITscan) QString("DVB service_id %1 on net_id %2 tsid %3") .arg(progNum).arg(netid).arg(tsid)); - // Some DVB devices munge the PMT and/or PAT so the CRC check fails. - // We need to tell the stream data class to not check the CRC on - // these devices. - if (GetDVBChannel()) - sd->SetIgnoreCRC(GetDVBChannel()->HasCRCBug()); + apply_broken_dvb_driver_crc_hack(channel, sd); dsd->Reset(); sm->SetStreamData(sd); @@ -2047,7 +1829,6 @@ bool TVRec::SetupDTVSignalMonitor(bool EITscan) VERBOSE(VB_RECORD, LOC + "Successfully set up DVB table monitoring."); return true; } -#endif // USING_DVB // Check if this is an MPEG channel if (progNum >= 0) @@ -2063,13 +1844,7 @@ bool TVRec::SetupDTVSignalMonitor(bool EITscan) QString msg = QString("MPEG program number: %1").arg(progNum); VERBOSE(VB_RECORD, LOC + msg); -#ifdef USING_DVB - // Some DVB devices munge the PMT and/or PAT so the CRC check fails. - // We need to tell the stream data class to not check the CRC on - // these devices. - if (GetDVBChannel()) - sd->SetIgnoreCRC(GetDVBChannel()->HasCRCBug()); -#endif // USING_DVB + apply_broken_dvb_driver_crc_hack(channel, sd); sd->Reset(); sm->SetStreamData(sd); @@ -2091,17 +1866,38 @@ bool TVRec::SetupDTVSignalMonitor(bool EITscan) return true; } - QString msg = "No valid DTV info, ATSC maj(%1) min(%2), MPEG pn(%3)"; - VERBOSE(VB_IMPORTANT, LOC_ERR + msg.arg(major).arg(minor).arg(progNum)); - return false; + // If this is not an ATSC, DVB or MPEG channel then check to make sure + // that we have permanent pidcache entries. + bool ok = false; + if (GetDTVChannel()) + { + pid_cache_t pid_cache; + GetDTVChannel()->GetCachedPids(pid_cache); + pid_cache_t::const_iterator it = pid_cache.begin(); + for (; !ok && it != pid_cache.end(); ++it) + ok |= it->IsPermanent(); + } + + if (!ok) + { + QString msg = "No valid DTV info, ATSC maj(%1) min(%2), MPEG pn(%3)"; + VERBOSE(VB_IMPORTANT, LOC_ERR + msg.arg(major).arg(minor).arg(progNum)); + } + else + { + VERBOSE(VB_RECORD, LOC + "Successfully set up raw pid monitoring."); + } + + return ok; } /** \fn TVRec::SetupSignalMonitor(bool,bool) * \brief This creates a SignalMonitor instance and * begins signal monitoring. * - * If the channel exists a SignalMonitor instance is created and - * SignalMonitor::Start() is called to start the signal monitoring thread. + * If the channel exists and there is something to monitor a + * SignalMonitor instance is created and SignalMonitor::Start() + * is called to start the signal monitoring thread. * * \param tablemon If set we enable table monitoring * \param notify If set we notify the frontend of the signal values @@ -2127,22 +1923,28 @@ bool TVRec::SetupSignalMonitor(bool tablemon, bool EITscan, bool notify) // make sure statics are initialized SignalMonitorValue::Init(); - if (channel->Open()) - signalMonitor = SignalMonitor::Init(genOpt.cardtype, cardid, - channel); + if (SignalMonitor::IsSupported(genOpt.cardtype) && channel->Open()) + signalMonitor = SignalMonitor::Init(genOpt.cardtype, cardid, channel); if (signalMonitor) { VERBOSE(VB_RECORD, LOC + "Signal monitor successfully created"); + // If this is a monitor for Digital TV, initialize table monitors + if (GetDTVSignalMonitor() && tablemon && + !SetupDTVSignalMonitor(EITscan)) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + + "Failed to setup digital signal monitoring"); + + return false; + } - signalMonitor->SetMonitoring(this, EITscan, - GetDTVSignalMonitor() && tablemon); signalMonitor->AddListener(this); signalMonitor->SetUpdateRate(kSignalMonitoringRate); signalMonitor->SetNotifyFrontend(notify); // Start the monitoring thread - signalMonitor->Start(true); + signalMonitor->Start(); } return true; @@ -2720,9 +2522,8 @@ void TVRec::SpawnLiveTV(LiveTVChain *newchain, bool pip, QString startchan) tvchain = newchain; tvchain->ReloadAll(); - QString hostprefix = QString("myth://%1:%2/") - .arg(gCoreContext->GetSetting("BackendServerIP")) - .arg(gCoreContext->GetSetting("BackendServerPort")); + QString hostprefix = gCoreContext->GenMythURL(gCoreContext->GetSetting("BackendServerIP"), + gCoreContext->GetSetting("BackendServerPort").toInt()); tvchain->SetHostPrefix(hostprefix); tvchain->SetCardType(genOpt.cardtype); @@ -3410,7 +3211,7 @@ void TVRec::SetRingBuffer(RingBuffer *rb) delete rb_old; } - m_switchingBuffer = false; + switchingBuffer = false; } void TVRec::RingBufferChanged(RingBuffer *rb, ProgramInfo *pginfo) @@ -3425,8 +3226,10 @@ void TVRec::RingBufferChanged(RingBuffer *rb, ProgramInfo *pginfo) curRecording->MarkAsInUse(false, kRecorderInUseID); delete curRecording; } + recordEndTime = GetRecordEndTime(pginfo); curRecording = new RecordingInfo(*pginfo); curRecording->MarkAsInUse(true, kRecorderInUseID); + curRecording->SetRecordingStatus(rsRecording); } SetRingBuffer(rb); @@ -3667,19 +3470,33 @@ void TVRec::TuningShutdowns(const TuningRequest &request) if (newCardID || (request.flags & kFlagNoRec)) { + bool finrun = false; if (HasFlags(kFlagDummyRecorderRunning)) { - ClearFlags(kFlagDummyRecorderRunning); + finrun = true; FinishedRecording(curRecording); + ClearFlags(kFlagDummyRecorderRunning); curRecording->MarkAsInUse(false, kRecorderInUseID); } - if (request.flags & kFlagCloseRec) - FinishedRecording(lastTuningRequest.program); + if (!!(request.flags & kFlagCloseRec) && curRecording) + { + if (!finrun) + { + finrun = true; + FinishedRecording(curRecording); + } + curRecording->MarkAsInUse(false, kRecorderInUseID); + } if (HasFlags(kFlagRecorderRunning) || (curRecording && curRecording->GetRecordingStatus() == rsFailed)) { + if (!finrun) + { + FinishedRecording(curRecording); + curRecording->MarkAsInUse(false, kRecorderInUseID); + } stateChangeLock.unlock(); TeardownRecorder(request.flags & kFlagKillRec); stateChangeLock.lock(); @@ -3701,9 +3518,7 @@ void TVRec::TuningShutdowns(const TuningRequest &request) GetDevices(newCardID, genOpt, dvbOpt, fwOpt); genOpt.defaultinput = inputname; - CreateChannel(channum); - if (!(request.flags & kFlagNoRec)) - channel->Open(); + CreateChannel(channum, false); } if (ringBuffer && (request.flags & kFlagKillRingBuffer)) @@ -3736,19 +3551,7 @@ void TVRec::TuningShutdowns(const TuningRequest &request) */ void TVRec::TuningFrequency(const TuningRequest &request) { - if (!channel) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + - "TuningFrequency called without a valid channel object"); - return; - } - DTVChannel *dtvchan = GetDTVChannel(); - bool livetv = request.flags & kFlagLiveTV; - bool antadj = request.flags & kFlagAntennaAdjust; - bool has_dummy = false; - bool ok = true; - if (dtvchan) { MPEGStreamData *mpeg = NULL; @@ -3757,22 +3560,24 @@ void TVRec::TuningFrequency(const TuningRequest &request) mpeg = GetDTVRecorder()->GetStreamData(); const QString tuningmode = (HasFlags(kFlagEITScannerRunning)) ? - dtvchan->GetSIStandard() : - dtvchan->GetSuggestedTuningMode - (kState_WatchingLiveTV == internalState); + dtvchan->GetSIStandard() : + dtvchan->GetSuggestedTuningMode( + kState_WatchingLiveTV == internalState); dtvchan->SetTuningMode(tuningmode); if (request.minorChan && (tuningmode == "atsc")) { - channel->SelectChannel(request.channel, false); + channel->SetChannelByString(request.channel); + ATSCStreamData *atsc = dynamic_cast(mpeg); if (atsc) atsc->SetDesiredChannel(request.majorChan, request.minorChan); } else if (request.progNum >= 0) { - channel->SelectChannel(request.channel, false); + channel->SetChannelByString(request.channel); + if (mpeg) mpeg->SetDesiredProgram(request.progNum); } @@ -3792,11 +3597,54 @@ void TVRec::TuningFrequency(const TuningRequest &request) QString input = request.input; QString channum = request.channel; - channel->Open(); + bool ok = false; + if (channel) + channel->Open(); + else + ok = true; + + if (channel && !channum.isEmpty()) + { + if (!input.isEmpty()) + ok = channel->SwitchToInput(input, channum); + else + ok = channel->SetChannelByString(channum); + } + + if (!ok) + { + if (!(request.flags & kFlagLiveTV) || !(request.flags & kFlagEITScan)) + { + if (curRecording) + curRecording->SetRecordingStatus(rsFailed); + + VERBOSE(VB_IMPORTANT, LOC_ERR + + QString("Failed to set channel to %1. " + "Reverting to kState_None") + .arg(channum)); + if (kState_None != internalState) + ChangeState(kState_None); + else + tuningRequests.enqueue(TuningRequest(kFlagKillRec)); + return; + } + else + { + VERBOSE(VB_IMPORTANT, LOC_ERR + + QString("Failed to set channel to %1.").arg(channum)); + } + } + + bool livetv = request.flags & kFlagLiveTV; + bool antadj = request.flags & kFlagAntennaAdjust; + bool use_sm = SignalMonitor::IsRequired(genOpt.cardtype); + bool use_dr = use_sm && (livetv || antadj); + bool has_dummy = false; - if (livetv || antadj) + if (use_dr) { // We need there to be a ringbuffer for these modes + bool ok; ProgramInfo *tmp = pseudoLiveTVRecording; pseudoLiveTVRecording = NULL; @@ -3819,58 +3667,61 @@ void TVRec::TuningFrequency(const TuningRequest &request) has_dummy = true; } - if (!channum.isEmpty()) + // Start signal monitoring for devices capable of monitoring + if (use_sm) { - if (!input.isEmpty()) - channel->SelectInput(input, channum, true); - else - channel->SelectChannel(channum, true); - } + VERBOSE(VB_RECORD, LOC + "Starting Signal Monitor"); + bool error = false; + if (!SetupSignalMonitor( + !antadj, request.flags & kFlagEITScan, livetv | antadj)) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to setup signal monitor"); + if (signalMonitor) + { + delete signalMonitor; + signalMonitor = NULL; + } + + // pretend the signal monitor is running to prevent segfault + SetFlags(kFlagSignalMonitorRunning); + ClearFlags(kFlagWaitingForSignal); + error = true; + } - // Start signal (or channel change) monitoring - VERBOSE(VB_RECORD, LOC + "Starting Signal Monitor"); - bool error = false; - if (!SetupSignalMonitor(!antadj, request.flags & kFlagEITScan, - livetv | antadj)) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to setup signal monitor"); if (signalMonitor) { - delete signalMonitor; - signalMonitor = NULL; + if (request.flags & kFlagEITScan) + { + GetDTVSignalMonitor()->GetStreamData()-> + SetVideoStreamsRequired(0); + GetDTVSignalMonitor()->IgnoreEncrypted(true); + } + + SetFlags(kFlagSignalMonitorRunning); + ClearFlags(kFlagWaitingForSignal); + if (!antadj) + SetFlags(kFlagWaitingForSignal); } - // pretend the signal monitor is running to prevent segfault - SetFlags(kFlagSignalMonitorRunning); - ClearFlags(kFlagWaitingForSignal); - error = true; - } - else if (signalMonitor) - { - SetFlags(kFlagSignalMonitorRunning); - ClearFlags(kFlagWaitingForSignal); - if (!antadj) - SetFlags(kFlagWaitingForSignal); - } + if (has_dummy && ringBuffer) + { + // Make sure recorder doesn't point to bogus ringbuffer before + // it is potentially restarted without a new ringbuffer, if + // the next channel won't tune and the user exits LiveTV. + if (recorder) + recorder->SetRingBuffer(NULL); - if (has_dummy && ringBuffer) - { - // Make sure recorder doesn't point to bogus ringbuffer before - // it is potentially restarted without a new ringbuffer, if - // the next channel won't tune and the user exits LiveTV. - if (recorder) - recorder->SetRingBuffer(NULL); + SetFlags(kFlagDummyRecorderRunning); + VERBOSE(VB_RECORD, "DummyDTVRecorder -- started"); + SetFlags(kFlagRingBufferReady); + } - SetFlags(kFlagDummyRecorderRunning); - VERBOSE(VB_RECORD, "DummyDTVRecorder -- started"); - SetFlags(kFlagRingBufferReady); + // if we had problems starting the signal monitor, + // we don't want to start the recorder... + if (error) + return; } - // if we had problems starting the signal monitor, - // we don't want to start the recorder... - if (error) - return; - // Request a recorder, if the command is a recording command ClearFlags(kFlagNeedToStartRecorder); if (request.flags & kFlagRec && !antadj) @@ -3886,26 +3737,17 @@ void TVRec::TuningFrequency(const TuningRequest &request) */ MPEGStreamData *TVRec::TuningSignalCheck(void) { + RecStatusType newRecStatus = rsRecording; if (signalMonitor->IsAllGood()) { VERBOSE(VB_RECORD, LOC + "Got good signal"); - - pendingRecLock.lock(); - m_recStatus = rsRecording; - pendingRecLock.unlock(); - if (curRecording) - curRecording->SetRecordingStatus(rsRecording); } else if (signalMonitor->IsErrored()) { VERBOSE(VB_RECORD, LOC_ERR + "SignalMonitor failed"); ClearFlags(kFlagNeedToStartRecorder); - pendingRecLock.lock(); - m_recStatus = rsFailed; - pendingRecLock.unlock(); - if (curRecording) - curRecording->SetRecordingStatus(rsFailed); + newRecStatus = rsFailed; if (HasFlags(kFlagEITScannerRunning)) { @@ -3914,16 +3756,23 @@ MPEGStreamData *TVRec::TuningSignalCheck(void) } } else + { return NULL; + } + + pendingRecLock.lock(); + m_recStatus = newRecStatus; + pendingRecLock.unlock(); if (curRecording) { + curRecording->SetRecordingStatus(newRecStatus); MythEvent me(QString("UPDATE_RECORDING_STATUS %1 %2 %3 %4 %5") - .arg(curRecording->GetCardID()) - .arg(curRecording->GetChanID()) - .arg(curRecording->GetScheduledStartTime(ISODate)) - .arg(curRecording->GetRecordingStatus()) - .arg(curRecording->GetRecordingEndTime(ISODate))); + .arg(curRecording->GetCardID()) + .arg(curRecording->GetChanID()) + .arg(curRecording->GetScheduledStartTime(ISODate)) + .arg(m_recStatus) + .arg(curRecording->GetRecordingEndTime(ISODate))); gCoreContext->dispatch(me); } @@ -4032,8 +3881,8 @@ void TVRec::TuningNewRecorder(MPEGStreamData *streamData) bool had_dummyrec = false; if (HasFlags(kFlagDummyRecorderRunning)) { - ClearFlags(kFlagDummyRecorderRunning); FinishedRecording(curRecording); + ClearFlags(kFlagDummyRecorderRunning); curRecording->MarkAsInUse(false, kRecorderInUseID); had_dummyrec = true; } @@ -4098,7 +3947,22 @@ void TVRec::TuningNewRecorder(MPEGStreamData *streamData) if (channel && genOpt.cardtype == "MJPEG") channel->Close(); // Needed because of NVR::MJPEGInit() - if (!SetupRecorder(profile)) + recorder = RecorderBase::CreateRecorder( + this, channel, profile, genOpt, dvbOpt); + + if (recorder) + { + recorder->SetRingBuffer(ringBuffer); + recorder->Initialize(); + if (recorder->IsErrored()) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "Failed to initialize recorder!"); + delete recorder; + recorder = NULL; + } + } + + if (!recorder) { VERBOSE(VB_IMPORTANT, LOC_ERR + QString( "Failed to start recorder!\n" @@ -4179,30 +4043,17 @@ void TVRec::TuningRestartRecorder(void) VERBOSE(VB_RECORD, LOC + "Restarting Recorder"); bool had_dummyrec = false; - if (HasFlags(kFlagDummyRecorderRunning)) - { - ClearFlags(kFlagDummyRecorderRunning); - had_dummyrec = true; - } if (curRecording) { FinishedRecording(curRecording); curRecording->MarkAsInUse(false, kRecorderInUseID); + } - if (pseudoLiveTVRecording) - { - int secsSince = curRecording->GetRecordingStartTime() - .secsTo(QDateTime::currentDateTime()); - if (secsSince < 120) - { - JobQueue::RemoveJobsFromMask(JOB_COMMFLAG, autoRunJobs); - JobQueue::RemoveJobsFromMask(JOB_TRANSCODE, autoRunJobs); - } - - if (autoRunJobs) - JobQueue::QueueRecordingJobs(*curRecording, autoRunJobs); - } + if (HasFlags(kFlagDummyRecorderRunning)) + { + ClearFlags(kFlagDummyRecorderRunning); + had_dummyrec = true; } SwitchLiveTVRingBuffer(channel->GetCurrentName(), true, !had_dummyrec); @@ -4217,7 +4068,8 @@ void TVRec::TuningRestartRecorder(void) recorder->Reset(); // Set file descriptor of channel from recorder for V4L - channel->SetFd(recorder->GetVideoFd()); + if (GetV4LChannel()) + channel->SetFd(recorder->GetVideoFd()); // Some recorders unpause on Reset, others do not... recorder->Unpause(); @@ -4551,13 +4403,6 @@ bool TVRec::SwitchLiveTVRingBuffer(const QString & channum, RecordingInfo *oldinfo = new RecordingInfo(*pi); delete pi; FinishedRecording(oldinfo); - if (tvchain->GetCardType(-1) != "DUMMY") - { - if (!oldinfo->IsLocal()) - oldinfo->SetPathname(oldinfo->GetPlaybackURL(false,true)); - if (oldinfo->IsLocal()) - PreviewGeneratorQueue::GetPreviewImage(*oldinfo, ""); - } delete oldinfo; } @@ -4589,6 +4434,46 @@ bool TVRec::SwitchLiveTVRingBuffer(const QString & channum, return true; } +RecordingInfo *TVRec::SwitchRecordingRingBuffer(const RecordingInfo &rcinfo) +{ + VERBOSE(VB_RECORD, LOC + "SwitchRecordingRingBuffer()"); + + if (switchingBuffer || !recorder || !curRecording || + (rcinfo.GetChanID() != curRecording->GetChanID())) + { + VERBOSE(VB_RECORD, LOC + "SwitchRecordingRingBuffer() -> false 1"); + return NULL; + } + + PreviewGeneratorQueue::GetPreviewImage(*curRecording, ""); + + RecordingInfo *ri = new RecordingInfo(rcinfo); + ri->MarkAsInUse(true, kRecorderInUseID); + StartedRecording(ri); + + bool write = genOpt.cardtype != "IMPORT"; + RingBuffer *rb = RingBuffer::Create(ri->GetPathname(), write); + if (!rb->IsOpen()) + { + ri->SetRecordingStatus(rsFailed); + FinishedRecording(ri); + ri->MarkAsInUse(false, kRecorderInUseID); + delete ri; + VERBOSE(VB_RECORD, LOC + "SwitchRecordingRingBuffer() -> false 2"); + return NULL; + } + else + { + recorder->SetNextRecording(ri, rb); + SetFlags(kFlagRingBufferReady); + recordEndTime = GetRecordEndTime(ri); + switchingBuffer = true; + ri->SetRecordingStatus(rsRecording); + VERBOSE(VB_RECORD, LOC + "SwitchRecordingRingBuffer() -> true"); + return ri; + } +} + TVRec* TVRec::GetTVRec(uint cardid) { QMutexLocker locker(&cardsLock); @@ -4601,9 +4486,24 @@ TVRec* TVRec::GetTVRec(uint cardid) QString TuningRequest::toString(void) const { return QString("Program(%1) channel(%2) input(%3) flags(%4)") - .arg((program != 0) ? "yes" : "no").arg(channel).arg(input) + .arg((program == NULL) ? QString("NULL") : program->toString()) + .arg(channel).arg(input) .arg(TVRec::FlagToString(flags)); } +#ifdef USING_DVB +#include "dvbchannel.h" +static void apply_broken_dvb_driver_crc_hack(ChannelBase *c, MPEGStreamData *s) +{ + // Some DVB devices munge the PMT and/or PAT so the CRC check fails. + // We need to tell the stream data class to not check the CRC on + // these devices. This can cause segfaults. + if (dynamic_cast(c)) + s->SetIgnoreCRC(dynamic_cast(c)->HasCRCBug()); +} +#else +static void apply_broken_dvb_driver_crc_hack(ChannelBase*, MPEGStreamData*) {} +#endif // USING_DVB + /* vim: set expandtab tabstop=4 shiftwidth=4: */ diff --git a/mythtv/libs/libmythtv/tv_rec.h b/mythtv/libs/libmythtv/tv_rec.h index dc691ea1dc8..8ed742b8762 100644 --- a/mythtv/libs/libmythtv/tv_rec.h +++ b/mythtv/libs/libmythtv/tv_rec.h @@ -21,8 +21,8 @@ // locking order // stateChangeLock -> triggerEventLoopLock +// -> pendingRecLock -class NuppelVideoRecorder; class RingBuffer; class EITScanner; class RecordingProfile; @@ -33,6 +33,7 @@ class RecorderBase; class DTVRecorder; class DVBRecorder; class HDHRRecorder; +class ASIRecorder; class SignalMonitor; class DTVSignalMonitor; @@ -134,7 +135,6 @@ class PendingInfo typedef QMap PendingMap; class TVRec; - class TVRecEventThread : public QThread { Q_OBJECT @@ -149,7 +149,6 @@ class TVRecEventThread : public QThread class MTV_PUBLIC TVRec : public SignalMonitorListener { friend class TuningRequest; - friend class SignalMonitor; friend class TVRecEventThread; friend class TVRecRecordThread; @@ -254,7 +253,6 @@ class MTV_PUBLIC TVRec : public SignalMonitorListener protected: void RunTV(void); bool WaitForEventThreadSleep(bool wake = true, ulong time = ULONG_MAX); - bool SetupDTVSignalMonitor(bool EITscan); private: void SetRingBuffer(RingBuffer *); @@ -269,23 +267,18 @@ class MTV_PUBLIC TVRec : public SignalMonitorListener static QString GetStartChannel(uint cardid, const QString &defaultinput); - bool SetupRecorder(RecordingProfile& profile); void TeardownRecorder(bool killFile = false); DTVRecorder *GetDTVRecorder(void); - HDHRRecorder *GetHDHRRecorder(void); - DVBRecorder *GetDVBRecorder(void); - - bool CreateChannel(const QString &startChanNum); - void InitChannel(const QString &inputname, const QString &startchannel); + + bool CreateChannel(const QString &startChanNum, + bool enter_power_save_mode); void CloseChannel(void); - DTVChannel *GetDTVChannel(void); - HDHRChannel *GetHDHRChannel(void); - DVBChannel *GetDVBChannel(void); - FirewireChannel *GetFirewireChannel(void); - V4LChannel *GetV4LChannel(void); - - bool SetupSignalMonitor(bool enable_table_monitoring, - bool EITscan, bool notify); + DTVChannel *GetDTVChannel(void); + V4LChannel *GetV4LChannel(void); + + bool SetupSignalMonitor( + bool enable_table_monitoring, bool EITscan, bool notify); + bool SetupDTVSignalMonitor(bool EITscan); void TeardownSignalMonitor(void); DTVSignalMonitor *GetDTVSignalMonitor(void); @@ -318,10 +311,12 @@ class MTV_PUBLIC TVRec : public SignalMonitorListener bool WaitForNextLiveTVDir(void); bool GetProgramRingBufferForLiveTV(RecordingInfo **pginfo, RingBuffer **rb, - const QString & channum, int inputID); + const QString &channum, int inputID); bool CreateLiveTVRingBuffer(const QString & channum); bool SwitchLiveTVRingBuffer(const QString & channum, - bool discont, bool set_rec); + bool discont, bool set_rec); + + RecordingInfo *SwitchRecordingRingBuffer(const RecordingInfo &rcinfo); void StartedRecording(RecordingInfo*); void FinishedRecording(RecordingInfo*); @@ -379,7 +374,7 @@ class MTV_PUBLIC TVRec : public SignalMonitorListener mutable QMutex triggerEventSleepLock; QWaitCondition triggerEventSleepWait; bool triggerEventSleepSignal; - bool m_switchingBuffer; + volatile bool switchingBuffer; RecStatusType m_recStatus; // Current recording info diff --git a/mythtv/libs/libmythtv/v4lchannel.cpp b/mythtv/libs/libmythtv/v4lchannel.cpp index 4dc1ae4bffb..9a4d348300f 100644 --- a/mythtv/libs/libmythtv/v4lchannel.cpp +++ b/mythtv/libs/libmythtv/v4lchannel.cpp @@ -29,9 +29,9 @@ using namespace std; #define DEBUG_ATTRIB 1 -#define LOC QString("Channel(%1): ").arg(device) -#define LOC_WARN QString("Channel(%1) Warning: ").arg(device) -#define LOC_ERR QString("Channel(%1) Error: ").arg(device) +#define LOC QString("V4LChannel(%1): ").arg(device) +#define LOC_WARN QString("V4LChannel(%1) Warning: ").arg(device) +#define LOC_ERR QString("V4LChannel(%1) Error: ").arg(device) static int format_to_mode(const QString& fmt, int v4l_version); static QString mode_to_format(int mode, int v4l_version); @@ -39,10 +39,15 @@ static QString mode_to_format(int mode, int v4l_version); V4LChannel::V4LChannel(TVRec *parent, const QString &videodevice) : DTVChannel(parent), device(videodevice), videofd(-1), - device_name(QString::null), driver_name(QString::null), + device_name(), driver_name(), curList(NULL), totalChannels(0), - currentFormat(""), is_dtv(false), - usingv4l2(false), defaultFreqTable(1) + currentFormat(), + usingv4l2(false), + has_stream_io(false), has_std_io(false), + has_async_io(false), + has_tuner(false), has_sliced_vbi(false), + + defaultFreqTable(1) { } @@ -73,17 +78,38 @@ bool V4LChannel::Open(void) videofd = open(ascii_device.constData(), O_RDWR); if (videofd < 0) { - VERBOSE(VB_IMPORTANT, - QString("Channel(%1)::Open(): Can't open video device, " - "error \"%2\"").arg(device).arg(strerror(errno))); + VERBOSE(VB_IMPORTANT, LOC_ERR + "Can't open video device." + ENO); return false; } - usingv4l2 = CardUtil::hasV4L2(videofd); - CardUtil::GetV4LInfo(videofd, device_name, driver_name); + uint32_t version, capabilities; + if (!CardUtil::GetV4LInfo(videofd, device_name, driver_name, + version, capabilities)) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + + "Failed to query capabilities." + ENO); + Close(); + return false; + } + + usingv4l2 = !!(capabilities & V4L2_CAP_VIDEO_CAPTURE); + has_stream_io = !!(capabilities & V4L2_CAP_STREAMING); + has_std_io = !!(capabilities & V4L2_CAP_READWRITE); + has_async_io = !!(capabilities & V4L2_CAP_ASYNCIO); + has_tuner = !!(capabilities & V4L2_CAP_TUNER); + has_sliced_vbi = !!(capabilities & V4L2_CAP_SLICED_VBI_CAPTURE); + VERBOSE(VB_CHANNEL, LOC + QString("Device name '%1' driver '%2'.") .arg(device_name).arg(driver_name)); + VERBOSE(VB_CHANNEL, LOC + QString( + "v4l2: %1 stream io: %2 std io: %3 async io: %4 " + "tuner %5 sliced vbi %6") + .arg(usingv4l2) + .arg(has_stream_io).arg(has_std_io).arg(has_async_io) + .arg(has_tuner).arg(has_sliced_vbi)); + + if (!InitializeInputs()) { Close(); @@ -344,7 +370,6 @@ void V4LChannel::SetFormat(const QString &format) if ((fmt == currentFormat) || SetInputAndFormat(inputNum, fmt)) { currentFormat = fmt; - is_dtv = (fmt == "ATSC"); } } @@ -360,8 +385,12 @@ void V4LChannel::SetFreqTable(const int index) totalChannels = chanlists[index].count; } -int V4LChannel::SetFreqTable(const QString &name) +int V4LChannel::SetFreqTable(const QString &tablename) { + QString name = tablename; + if (name.toLower() == "default" || name.isEmpty()) + name = defaultFreqTable; + int i = 0; char *listname = (char *)chanlists[i].name; @@ -399,204 +428,51 @@ int V4LChannel::GetCurrentChannelNum(const QString &channame) return -1; } -void V4LChannel::SaveCachedPids(const pid_cache_t &pid_cache) const -{ - int chanid = GetChanID(); - if (chanid > 0) - DTVChannel::SaveCachedPids(chanid, pid_cache); -} - -void V4LChannel::GetCachedPids(pid_cache_t &pid_cache) const -{ - int chanid = GetChanID(); - if (chanid > 0) - DTVChannel::GetCachedPids(chanid, pid_cache); -} - -bool V4LChannel::SetChannelByString(const QString &channum) -{ - QString loc = LOC + QString("SetChannelByString(%1)").arg(channum); - QString loc_err = loc + ", Error: "; - VERBOSE(VB_CHANNEL, loc); - - if (!Open()) - { - VERBOSE(VB_IMPORTANT, loc_err + "Channel object " - "will not open, cannot change channels."); - - return false; - } - - QString inputName; - if (!CheckChannel(channum, inputName)) - { - VERBOSE(VB_IMPORTANT, loc_err + - "CheckChannel failed.\n\t\t\tPlease verify the channel " - "in the 'mythtv-setup' Channel Editor."); - - return false; - } - - // If CheckChannel filled in the inputName then we need to - // change inputs and return, since the act of changing - // inputs will change the channel as well. - if (!inputName.isEmpty()) - return ChannelBase::SelectInput(inputName, channum, false); - - ClearDTVInfo(); - - InputMap::const_iterator it = m_inputs.find(m_currentInputID); - if (it == m_inputs.end()) - return false; - - uint mplexid_restriction; - if (!IsInputAvailable(m_currentInputID, mplexid_restriction)) - return false; - - // Fetch tuning data from the database. - QString tvformat, modulation, freqtable, freqid, dtv_si_std; - int finetune; - uint64_t frequency; - int mpeg_prog_num; - uint atsc_major, atsc_minor, mplexid, tsid, netid; - - if (!ChannelUtil::GetChannelData( - (*it)->sourceid, channum, - tvformat, modulation, freqtable, freqid, - finetune, frequency, - dtv_si_std, mpeg_prog_num, atsc_major, atsc_minor, tsid, netid, - mplexid, m_commfree)) - { - return false; - } - - if (mplexid_restriction && (mplexid != mplexid_restriction)) - return false; - - // If the frequency is zeroed out, don't use it directly. - bool ok = (frequency > 0); - - if (!ok) - { - frequency = (freqid.toInt(&ok) + finetune) * 1000; - mplexid = 0; - } - bool isFrequency = ok && (frequency > 10000000); - - // If we are tuning to a freqid, rather than an actual frequency, - // we need to set the frequency table to use. - if (!isFrequency) - { - if (freqtable == "default" || freqtable.isEmpty()) - SetFreqTable(defaultFreqTable); - else - SetFreqTable(freqtable); - } - - // Set NTSC, PAL, ATSC, etc. - SetFormat(tvformat); - - // If a tuneToChannel is set make sure we're still on it - if (!(*it)->tuneToChannel.isEmpty() && (*it)->tuneToChannel != "Undefined") - TuneTo((*it)->tuneToChannel, 0); - - // Tune to proper frequency - if ((*it)->externalChanger.isEmpty()) - { - if ((*it)->name.contains("composite", Qt::CaseInsensitive) || - (*it)->name.contains("s-video", Qt::CaseInsensitive)) - { - VERBOSE(VB_GENERAL, LOC_WARN + "You have not set " - "an external channel changing" - "\n\t\t\tscript for a composite or s-video " - "input. Channel changing will do nothing."); - } - else if (isFrequency) - { - if (!Tune(frequency, "", (is_dtv) ? "8vsb" : "analog", dtv_si_std)) - { - return false; - } - } - else - { - if (!TuneTo(freqid, finetune)) - return false; - } - } - else if (!ChangeExternalChannel(freqid)) - return false; - - // Set the current channum to the new channel's channum - QString tmp = channum; tmp.detach(); - m_curchannelname = tmp; - - // Setup filters & recording picture attributes for framegrabing recorders - // now that the new curchannelname has been established. - if (m_pParent) - m_pParent->SetVideoFiltersForChannel(GetCurrentSourceID(), channum); - InitPictureAttributes(); - - // Set the major and minor channel for any additional multiplex tuning - SetDTVInfo(atsc_major, atsc_minor, netid, tsid, mpeg_prog_num); - - // Set this as the future start channel for this source - QString tmpX = m_curchannelname; tmpX.detach(); - m_inputs[m_currentInputID]->startChanNum = tmpX; - - return true; -} - -bool V4LChannel::TuneTo(const QString &channum, int finetune) +bool V4LChannel::Tune(const QString &freqid, int finetune) { - int i = GetCurrentChannelNum(channum); - VERBOSE(VB_CHANNEL, QString("Channel(%1)::TuneTo(%2): " + int i = GetCurrentChannelNum(freqid); + VERBOSE(VB_CHANNEL, QString("Channel(%1)::Tune(%2): " "curList[%3].freq(%4)") - .arg(device).arg(channum).arg(i) + .arg(device).arg(freqid).arg(i) .arg((i != -1) ? curList[i].freq : -1)); if (i == -1) { - VERBOSE(VB_IMPORTANT, QString("Channel(%1)::TuneTo(%2): Error, " + VERBOSE(VB_IMPORTANT, QString("Channel(%1)::Tune(%2): Error, " "failed to find channel.") - .arg(device).arg(channum)); + .arg(device).arg(freqid)); return false; } int frequency = (curList[i].freq + finetune) * 1000; - return Tune(frequency, "", "analog", "analog"); + return Tune(frequency, ""); } bool V4LChannel::Tune(const DTVMultiplex &tuning, QString inputname) { return Tune(tuning.frequency - 1750000, // to visual carrier - inputname, tuning.modulation.toString(), tuning.sistandard); + inputname); } -/** \fn V4LChannel::Tune(uint,QString,QString,QString) - * \brief Tunes to a specific frequency (Hz) on a particular input, using - * the specified modulation. +/** \brief Tunes to a specific frequency (Hz) on a particular input * - * Note: This function always uses modulator zero. + * \note This function always uses modulator zero. + * \note Unlike digital tuning functions this accepts the visual carrier + * frequency and not the center frequency. * * \param frequency Frequency in Hz, this is divided by 62.5 kHz or 62.5 Hz * depending on the modulator and sent to the hardware. * \param inputname Name of the input (Television, Antenna 1, etc.) * \param modulation "radio", "analog", or "digital" */ -bool V4LChannel::Tune(uint frequency, QString inputname, - QString modulation, QString si_std) +bool V4LChannel::Tune(uint64_t frequency, QString inputname) { - VERBOSE(VB_CHANNEL, LOC + QString("Tune(%1, %2, %3, %4)") - .arg(frequency).arg(inputname).arg(modulation).arg(si_std)); + VERBOSE(VB_CHANNEL, LOC + QString("Tune(%1, %2)") + .arg(frequency).arg(inputname)); int ioctlval = 0; - if (modulation == "8vsb") - SetFormat("ATSC"); - modulation = (is_dtv) ? "digital" : modulation; - int inputnum = GetInputByName(inputname); bool ok = true; @@ -608,20 +484,6 @@ bool V4LChannel::Tune(uint frequency, QString inputname, if (!ok) return false; - // If the frequency is a center frequency and not - // a visual carrier frequency, convert it. - int offset = frequency % 1000000; - offset = (offset > 500000) ? 1000000 - offset : offset; - bool is_visual_carrier = (offset > 150000) && (offset < 350000); - if (!is_visual_carrier && currentFormat == "ATSC") - { - VERBOSE(VB_CHANNEL, QString("Channel(%1): ").arg(device) + - QString("Converting frequency from center frequency " - "(%1 Hz) to visual carrier frequency (%2 Hz).") - .arg(frequency).arg(frequency - 1750000)); - frequency -= 1750000; // convert to visual carrier - } - // Video4Linux version 2 tuning if (usingv4l2) { @@ -644,15 +506,6 @@ bool V4LChannel::Tune(uint frequency, QString inputname, vf.frequency = (isTunerCapLow) ? ((int)(frequency / 62.5)) : (frequency / 62500); - if (modulation.toLower() == "digital") - { - VERBOSE(VB_CHANNEL, "using digital modulation"); - vf.type = V4L2_TUNER_DIGITAL_TV; - if (ioctl(videofd, VIDIOC_S_FREQUENCY, &vf)>=0) - return true; - VERBOSE(VB_CHANNEL, "digital modulation failed"); - } - vf.type = V4L2_TUNER_ANALOG_TV; ioctlval = ioctl(videofd, VIDIOC_S_FREQUENCY, &vf); @@ -688,8 +541,6 @@ bool V4LChannel::Tune(uint frequency, QString inputname, return false; } - SetSIStandard(si_std); - return true; } @@ -730,34 +581,6 @@ bool V4LChannel::Retune(void) return false; } -// documented in dtvchannel.h -bool V4LChannel::TuneMultiplex(uint mplexid, QString inputname) -{ - VERBOSE(VB_CHANNEL, LOC + QString("TuneMultiplex(%1)").arg(mplexid)); - - QString modulation; - QString si_std; - uint64_t frequency; - uint transportid; - uint dvb_networkid; - - if (!ChannelUtil::GetTuningParams( - mplexid, modulation, frequency, - transportid, dvb_networkid, si_std)) - { - VERBOSE(VB_IMPORTANT, LOC_ERR + "TuneMultiplex(): " + - QString("Could not find tuning parameters for multiplex %1.") - .arg(mplexid)); - - return false; - } - - if (!Tune(frequency, inputname, modulation, si_std)) - return false; - - return true; -} - QString V4LChannel::GetFormatForChannel(QString channum, QString inputname) { MSqlQuery query(MSqlQuery::InitCon()); @@ -795,54 +618,64 @@ bool V4LChannel::SetInputAndFormat(int inputNum, QString newFmt) if (usingv4l2) { - VERBOSE(VB_CHANNEL, LOC + msg + "(v4l v2)"); + struct v4l2_input input; + int ioctlval = ioctl(videofd, VIDIOC_G_INPUT, &input); + bool input_switch = (0 != ioctlval || (uint)inputNumV4L != input.index); - int ioctlval = ioctl(videofd, VIDIOC_S_INPUT, &inputNumV4L); + const v4l2_std_id new_vid_mode = format_to_mode(newFmt, 2); + v4l2_std_id cur_vid_mode; + ioctlval = ioctl(videofd, VIDIOC_G_STD, &cur_vid_mode); + bool mode_switch = (0 != ioctlval || new_vid_mode != cur_vid_mode); + bool needs_switch = input_switch || mode_switch; + + VERBOSE(VB_IMPORTANT, LOC + msg + "(v4l v2) " + + QString("input_switch: %1 mode_switch: %2") + .arg(input_switch).arg(mode_switch)); // ConvertX (wis-go7007) requires streaming to be disabled - // before an input switch, do this if initial switch failed. + // before an input switch, do this if CAP_STREAMING is set. bool streamingDisabled = false; int streamType = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if ((ioctlval < 0) && (errno == EBUSY)) + if (needs_switch && has_stream_io) { ioctlval = ioctl(videofd, VIDIOC_STREAMOFF, &streamType); if (ioctlval < 0) { VERBOSE(VB_IMPORTANT, LOC_ERR + msg + "\n\t\t\twhile disabling streaming (v4l v2)" + ENO); - ok = false; - ioctlval = 0; } else { streamingDisabled = true; - - // Resend the input switch ioctl. - ioctlval = ioctl(videofd, VIDIOC_S_INPUT, &inputNumV4L); } } - if (ioctlval < 0) + if (input_switch) { - VERBOSE(VB_IMPORTANT, LOC_ERR + msg + - "\n\t\t\twhile setting input (v4l v2)" + ENO); + // Send the input switch ioctl. + ioctlval = ioctl(videofd, VIDIOC_S_INPUT, &inputNumV4L); + if (ioctlval < 0) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + msg + + "\n\t\t\twhile setting input (v4l v2)" + ENO); - ok = false; + ok = false; + } } - v4l2_std_id vid_mode = format_to_mode(newFmt, 2); - ioctlval = ioctl(videofd, VIDIOC_S_STD, &vid_mode); - if (ioctlval < 0) + if (mode_switch) { - VERBOSE(VB_IMPORTANT, LOC_ERR + msg + - "\n\t\t\twhile setting format (v4l v2)" + ENO); + ioctlval = ioctl(videofd, VIDIOC_S_STD, &new_vid_mode); + if (ioctlval < 0) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + msg + + "\n\t\t\twhile setting format (v4l v2)" + ENO); - ok = false; + ok = false; + } } - // ConvertX (wis-go7007) requires streaming to be disabled - // before an input switch, here we try to re-enable streaming. if (streamingDisabled) { ioctlval = ioctl(videofd, VIDIOC_STREAMON, &streamType); @@ -916,10 +749,6 @@ bool V4LChannel::SwitchToInput(int inputnum, bool setstarting) bool ok = SetInputAndFormat(inputnum, newFmt); - // Try to set ATSC mode if NTSC fails - if (!ok && newFmt == "NTSC") - ok = SetInputAndFormat(inputnum, "ATSC"); - if (!ok) { VERBOSE(VB_IMPORTANT, LOC + "SetInputAndFormat() failed"); @@ -927,12 +756,11 @@ bool V4LChannel::SwitchToInput(int inputnum, bool setstarting) } currentFormat = newFmt; - is_dtv = newFmt == "ATSC"; m_currentInputID = inputnum; m_curchannelname = ""; // this will be set by SetChannelByString if (!tuneFreqId.isEmpty() && tuneFreqId != "Undefined") - ok = TuneTo(tuneFreqId, 0); + ok = Tune(tuneFreqId, 0); if (!ok) return false; @@ -986,7 +814,7 @@ static int get_v4l2_attribute(const QString &db_col_name) bool V4LChannel::InitPictureAttribute(const QString db_col_name) { - if (!m_pParent || is_dtv) + if (!m_pParent) return false; int v4l2_attrib = get_v4l2_attribute(db_col_name); @@ -1251,7 +1079,7 @@ static int set_attribute_value(bool usingv4l2, int videofd, int V4LChannel::ChangePictureAttribute( PictureAdjustType type, PictureAttribute attr, bool up) { - if (!m_pParent || is_dtv) + if (!m_pParent) return -1; QString db_col_name = toDBString(attr); diff --git a/mythtv/libs/libmythtv/v4lchannel.h b/mythtv/libs/libmythtv/v4lchannel.h index b059c646f90..74e1e849734 100644 --- a/mythtv/libs/libmythtv/v4lchannel.h +++ b/mythtv/libs/libmythtv/v4lchannel.h @@ -37,44 +37,32 @@ class V4LChannel : public DTVChannel bool Init(QString &inputname, QString &startchannel, bool setchan); + // Commands + bool SwitchToInput(int newcapchannel, bool setstarting); bool Open(void); void Close(void); + bool Tune(const DTVMultiplex &tuning, QString inputname); + bool Tune(uint64_t frequency, QString inputname); + bool Tune(const QString &freqid, int finetune); + bool Retune(void); // Sets void SetFd(int fd); void SetFormat(const QString &format); int SetDefaultFreqTable(const QString &name); - bool SetChannelByString(const QString &chan); // Gets bool IsOpen(void) const { return GetFd() >= 0; } int GetFd(void) const { return videofd; } QString GetDevice(void) const { return device; } QString GetSIStandard(void) const { return "atsc"; } - - // Commands - bool Retune(void); + virtual bool IsExternalChannelChangeSupported(void) { return true; } // Picture attributes. bool InitPictureAttributes(void); int GetPictureAttribute(PictureAttribute) const; int ChangePictureAttribute(PictureAdjustType, PictureAttribute, bool up); - // PID caching - void SaveCachedPids(const pid_cache_t&) const; - void GetCachedPids(pid_cache_t&) const; - - // Digital scanning stuff - bool TuneMultiplex(uint mplexid, QString inputname); - bool Tune(const DTVMultiplex &tuning, QString inputname); - - // Analog scanning stuff - bool Tune(uint frequency, QString inputname, - QString modulation, QString si_std); - - protected: - bool SwitchToInput(int newcapchannel, bool setstarting); - private: // Helper Sets void SetFreqTable(const int index); @@ -88,7 +76,6 @@ class V4LChannel : public DTVChannel // Helper Commands bool InitPictureAttribute(const QString db_col_name); - bool TuneTo(const QString &chan, int finetune); bool InitializeInputs(void); private: @@ -103,8 +90,13 @@ class V4LChannel : public DTVChannel int totalChannels; QString currentFormat; - bool is_dtv; ///< Set if 'currentFormat' is a DTV format bool usingv4l2; ///< Set to true if tuner accepts v4l2 commands + bool has_stream_io; + bool has_std_io; + bool has_async_io; + bool has_tuner; + bool has_sliced_vbi; + VidModV4L1 videomode_v4l1; ///< Current video mode if 'usingv4l2' is false VidModV4L2 videomode_v4l2; ///< Current video mode if 'usingv4l2' is true diff --git a/mythtv/libs/libmythtv/v4lrecorder.cpp b/mythtv/libs/libmythtv/v4lrecorder.cpp new file mode 100644 index 00000000000..6fe2f5e0750 --- /dev/null +++ b/mythtv/libs/libmythtv/v4lrecorder.cpp @@ -0,0 +1,315 @@ +// -*- Mode: c++ -*- + +#ifdef USING_V4L +#include // for vbi_format +#endif // USING_V4L + +#include // for ioctl +#include // for gettimeofday +#include // for IO_NONBLOCK +#include // for IO_NONBLOCK + +#include "vbi608extractor.h" +#include "mythcontext.h" // for VERBOSE +#include "v4lrecorder.h" +#include "vbitext/vbi.h" +#include "tv_rec.h" +#include "tv.h" // for VBIMode + +#define TVREC_CARDNUM \ + ((tvrec != NULL) ? QString::number(tvrec->GetCaptureCardNum()) : "NULL") + +#define LOC QString("V4LRec(%1:%2): ") \ + .arg(TVREC_CARDNUM).arg(videodevice) +#define LOC_WARN QString("V4LRec(%1:%2) Warning: ") \ + .arg(TVREC_CARDNUM).arg(videodevice) +#define LOC_ERR QString("V4LRec(%1:%2) Error: ") \ + .arg(TVREC_CARDNUM).arg(videodevice) + +V4LRecorder::V4LRecorder(TVRec *tv) : + DTVRecorder(tv), vbimode(VBIMode::None), + pal_vbi_cb(NULL), pal_vbi_tt(NULL), + ntsc_vbi_width(0), ntsc_vbi_start_line(0), + ntsc_vbi_line_count(0), + vbi608(NULL), + vbi_thread(NULL), vbi_fd(-1) +{ +} + +V4LRecorder::~V4LRecorder() +{ + if (vbi_thread) + { + delete vbi_thread; + vbi_thread = NULL; + } +} + +void V4LRecorder::StopRecording(void) +{ + DTVRecorder::StopRecording(); + while (vbi_thread && vbi_thread->isRunning()) + vbi_thread->wait(); +} + +void V4LRecorder::SetOption(const QString &name, const QString &value) +{ + if (name == "audiodevice") + audiodevice = value; + else if (name == "vbidevice") + vbidevice = value; + else if (name == "vbiformat") + vbimode = VBIMode::Parse(value); + else + DTVRecorder::SetOption(name, value); +} + +static void vbi_event(struct VBIData *data, struct vt_event *ev) +{ + switch (ev->type) + { + case EV_PAGE: + { + struct vt_page *vtp = (struct vt_page *) ev->p1; + if (vtp->flags & PG_SUBTITLE) + { + //printf("subtitle page %x.%x\n", vtp->pgno, vtp->subno); + data->foundteletextpage = true; + memcpy(&(data->teletextpage), vtp, sizeof(vt_page)); + } + } + + case EV_HEADER: + case EV_XPACKET: + break; + } +} + +int V4LRecorder::OpenVBIDevice(void) +{ + int fd = -1; + if (vbi_fd >= 0) + return vbi_fd; + + struct VBIData *vbi_cb = NULL; + struct vbi *pal_tt = NULL; + uint width = 0, start_line = 0, line_count = 0; + + QByteArray vbidev = vbidevice.toAscii(); + if (VBIMode::PAL_TT == vbimode) + { + pal_tt = vbi_open(vbidev.constData(), NULL, 99, -1); + if (pal_tt) + { + fd = pal_tt->fd; + vbi_cb = new VBIData; + memset(vbi_cb, 0, sizeof(VBIData)); + vbi_cb->nvr = this; + vbi_add_handler(pal_tt, (void*) vbi_event, vbi_cb); + } + } + else if (VBIMode::NTSC_CC == vbimode) + { + fd = open(vbidev.constData(), O_RDONLY/*|O_NONBLOCK*/); + } + else + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "Invalid CC/Teletext mode"); + return -1; + } + + if (fd < 0) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + + QString("Can't open vbi device: '%1'").arg(vbidevice)); + return -1; + } + + if (VBIMode::NTSC_CC == vbimode) + { +#ifdef USING_V4L + struct v4l2_format fmt; + memset(&fmt, 0, sizeof(fmt)); + fmt.type = V4L2_BUF_TYPE_VBI_CAPTURE; + if (0 != ioctl(fd, VIDIOC_G_FMT, &fmt)) + { + VERBOSE(VB_RECORD, "V4L2 VBI setup failed, trying v1 ioctl"); + // Try V4L v1 VBI ioctls, iff V4L v2 fails + struct vbi_format old_fmt; + memset(&old_fmt, 0, sizeof(vbi_format)); + if (ioctl(fd, VIDIOCGVBIFMT, &old_fmt) < 0) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + + "Failed to query vbi capabilities (V4L1)"); + close(fd); + return -1; + } + fmt.fmt.vbi.sampling_rate = old_fmt.sampling_rate; + fmt.fmt.vbi.offset = 0; + fmt.fmt.vbi.samples_per_line = old_fmt.samples_per_line; + fmt.fmt.vbi.start[0] = old_fmt.start[0]; + fmt.fmt.vbi.start[1] = old_fmt.start[1]; + fmt.fmt.vbi.count[0] = old_fmt.count[0]; + fmt.fmt.vbi.count[1] = old_fmt.count[1]; + fmt.fmt.vbi.flags = old_fmt.flags; + } + VERBOSE(VB_RECORD, LOC + QString("vbi_format rate: %1" + "\n\t\t\t offset: %2" + "\n\t\t\tsamples_per_line: %3" + "\n\t\t\t starts: %4, %5" + "\n\t\t\t counts: %6, %7" + "\n\t\t\t flags: 0x%8") + .arg(fmt.fmt.vbi.sampling_rate) + .arg(fmt.fmt.vbi.offset) + .arg(fmt.fmt.vbi.samples_per_line) + .arg(fmt.fmt.vbi.start[0]) + .arg(fmt.fmt.vbi.start[1]) + .arg(fmt.fmt.vbi.count[0]) + .arg(fmt.fmt.vbi.count[1]) + .arg(fmt.fmt.vbi.flags,0,16)); + + width = fmt.fmt.vbi.samples_per_line; + start_line = fmt.fmt.vbi.start[0]; + line_count = fmt.fmt.vbi.count[0]; + if (line_count != fmt.fmt.vbi.count[1]) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + + "VBI must have the same number of " + "odd and even fields for our decoder"); + close(fd); + return -1; + } + if (start_line > 21 || start_line + line_count < 22) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + + "VBI does not include line 21"); + // TODO We could try to set the VBI format ourselves.. + close(fd); + return -1; + } +#endif // USING_V4L + } + + if (VBIMode::PAL_TT == vbimode) + { + pal_vbi_cb = vbi_cb; + pal_vbi_tt = pal_tt; + } + else if (VBIMode::NTSC_CC == vbimode) + { + ntsc_vbi_width = width; + ntsc_vbi_start_line = start_line; + ntsc_vbi_line_count = line_count; + vbi608 = new VBI608Extractor(); + } + + vbi_fd = fd; + + return fd; +} + +void V4LRecorder::CloseVBIDevice(void) +{ + if (vbi_fd < 0) + return; + + if (pal_vbi_tt) + { + vbi_del_handler(pal_vbi_tt, (void*) vbi_event, pal_vbi_cb); + vbi_close(pal_vbi_tt); + delete pal_vbi_cb; + pal_vbi_cb = NULL; + } + else + { + delete vbi608; vbi608 = NULL; + close(vbi_fd); + } + + vbi_fd = -1; +} + +void V4LRecorder::RunVBIDevice(void) +{ + if (vbi_fd < 0) + return; + + unsigned char *buf = NULL, *ptr = NULL, *ptr_end = NULL; + if (ntsc_vbi_width) + { + uint sz = ntsc_vbi_width * ntsc_vbi_line_count * 2; + buf = ptr = new unsigned char[sz]; + ptr_end = buf + sz; + } + + while (IsRecordingRequested() && !IsErrored()) + { + if (PauseAndWait()) + continue; + + if (!IsRecordingRequested()) + break; + + struct timeval tv; + fd_set rdset; + + tv.tv_sec = 0; + tv.tv_usec = 5000; + FD_ZERO(&rdset); + FD_SET(vbi_fd, &rdset); + + int nr = select(vbi_fd + 1, &rdset, 0, 0, &tv); + if (nr < 0) + VERBOSE(VB_IMPORTANT, LOC_ERR + "vbi select failed" + ENO); + + if (nr <= 0) + { + if (nr==0) + VERBOSE(VB_IMPORTANT, LOC_ERR + "vbi select timed out"); + continue; // either failed or timed out.. + } + if (VBIMode::PAL_TT == vbimode) + { + pal_vbi_cb->foundteletextpage = false; + vbi_handler(pal_vbi_tt, pal_vbi_tt->fd); + if (pal_vbi_cb->foundteletextpage) + { + // decode VBI as teletext subtitles + FormatTT(pal_vbi_cb); + } + } + else if (VBIMode::NTSC_CC == vbimode) + { + int ret = read(vbi_fd, ptr, ptr_end - ptr); + ptr = (ret > 0) ? ptr + ret : ptr; + if ((ptr_end - ptr) == 0) + { + unsigned char *line21_field1 = + buf + ((21 - ntsc_vbi_start_line) * ntsc_vbi_width); + unsigned char *line21_field2 = + buf + ((ntsc_vbi_line_count + 21 - ntsc_vbi_start_line) + * ntsc_vbi_width); + bool cc1 = vbi608->ExtractCC12(line21_field1, ntsc_vbi_width); + bool cc2 = vbi608->ExtractCC34(line21_field2, ntsc_vbi_width); + if (cc1 || cc2) + { + int code1 = vbi608->GetCode1(); + int code2 = vbi608->GetCode2(); + code1 = (0xFFFF==code1) ? -1 : code1; + code2 = (0xFFFF==code2) ? -1 : code2; + FormatCC(code1, code2); + } + ptr = buf; + } + else if (ret < 0) + { + VERBOSE(VB_IMPORTANT, LOC_ERR + "Reading VBI data" + ENO); + } + } + } + + if (buf) + delete [] buf; +} + +/* vim: set expandtab tabstop=4 shiftwidth=4: */ diff --git a/mythtv/libs/libmythtv/v4lrecorder.h b/mythtv/libs/libmythtv/v4lrecorder.h new file mode 100644 index 00000000000..eff84b5047d --- /dev/null +++ b/mythtv/libs/libmythtv/v4lrecorder.h @@ -0,0 +1,83 @@ +// -*- Mode: c++ -*- +#ifndef _V4L_RECORDER_H_ +#define _V4L_RECORDER_H_ + +#include + +#include "dtvrecorder.h" +#include "cc608decoder.h" +#include "vbitext/vt.h" + +class VBI608Extractor; +class VBIThread; +class TVRec; + +struct vbi; +struct VBIData +{ + RecorderBase *nvr; + vt_page teletextpage; + bool foundteletextpage; +}; + +/// Abstract base class for Video4Linux based recorders. +class MPUBLIC V4LRecorder : public DTVRecorder +{ + friend class VBIThread; + public: + V4LRecorder(TVRec *rec); + virtual ~V4LRecorder(); + + virtual void StopRecording(void); // RecorderBase + virtual void SetOption( + const QString &name, const QString &value); // RecorderBase + virtual void SetOption(const QString &name, int value) + { DTVRecorder::SetOption(name, value); } // RecorderBase + + protected: + int OpenVBIDevice(void); + void CloseVBIDevice(void); + void RunVBIDevice(void); + + virtual void FormatTT(struct VBIData *vbidata) {} + virtual void FormatCC(uint code1, uint code2) {} + + protected: + QString audiodevice; + QString vbidevice; + int vbimode; + struct VBIData *pal_vbi_cb; + struct vbi *pal_vbi_tt; + uint ntsc_vbi_width; + uint ntsc_vbi_start_line; + uint ntsc_vbi_line_count; + VBI608Extractor *vbi608; + VBIThread *vbi_thread; + QList textbuffer; + int vbi_fd; +}; + +class VBIThread : public QThread +{ + public: + VBIThread(V4LRecorder *_parent) : parent(_parent) + { start(); } + + virtual ~VBIThread() + { + while (isRunning()) + { + parent->StopRecording(); + wait(1000); + } + } + + virtual void run(void) { parent->RunVBIDevice(); } + + virtual void deleteLater(void) { parent->StopRecording(); } + + private: + V4LRecorder *parent; +}; + +#endif // _V4L_RECORDER_H_ diff --git a/mythtv/libs/libmythtv/vbi608extractor.cpp b/mythtv/libs/libmythtv/vbi608extractor.cpp new file mode 100644 index 00000000000..e3fc019dd44 --- /dev/null +++ b/mythtv/libs/libmythtv/vbi608extractor.cpp @@ -0,0 +1,410 @@ +/* + VBI 608 Extractor, extracts CEA-608 VBI from a line of raw data. + Copyright (C) 2010 Digital Nirvana, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS +#endif + +#include + +#include +using namespace std; + +#include "vbi608extractor.h" +#include "mythverbose.h" + +#define LOC QString("VBI608Extractor: ") +#define LOC_WARN QString("VBI608Extractor, Warning: ") +#define LOC_ERR QString("VBI608Extractor, Error: ") + +static void print( + const QList &raw_minimas, const QList &raw_maximas, + const QList &minimas, const QList &maximas) +{ + QString raw_mins, raw_maxs; + for (uint i = 0; i < uint(raw_minimas.size()); i++) + raw_mins += QString("%1,").arg(raw_minimas[i]); + for (uint i = 0; i < uint(raw_maximas.size()); i++) + raw_maxs += QString("%1,").arg(raw_maximas[i]); + VERBOSE(VB_VBI|VB_EXTRA, QString("raw mins: %1").arg(raw_mins)); + VERBOSE(VB_VBI|VB_EXTRA, QString("raw maxs: %1").arg(raw_maxs)); + + QString mins, maxs; + for (uint i = 0; i < uint(minimas.size()); i++) + mins += QString("%1,").arg(minimas[i]); + for (uint i = 0; i < uint(maximas.size()); i++) + maxs += QString("%1,").arg(maximas[i]); + VERBOSE(VB_VBI|VB_EXTRA, QString("mins: %1 maxs: %2") + .arg(mins).arg(maxs)); +} + +static float find_clock_diff(const QList &list) +{ + float min_diff = INT32_MAX; + float max_diff = 0.0f; + float avg_diff = 0.0f; + for (uint i = 1; i < uint(list.size()); i++) + { + float diff = list[i] - list[i-1]; + min_diff = min(diff, min_diff); + max_diff = max(diff, max_diff); + avg_diff += diff; + } + if (list.size() >= 2) + avg_diff /= (list.size() - 1); + if (avg_diff * 1.15 < max_diff) + { + VERBOSE(VB_VBI|VB_EXTRA, "max_diff too big"); + return 0.0f; + } + if (avg_diff * 0.85 > max_diff) + { + VERBOSE(VB_VBI|VB_EXTRA, "min_diff too small"); + return 0.0f; + } + + return avg_diff; +} + +VBI608Extractor::VBI608Extractor() : start(0.0f), rate(0.0f) +{ + code[0] = UINT16_MAX; + code[1] = UINT16_MAX; +} + +bool VBI608Extractor::FindClocks(const unsigned char *buf, uint width) +{ + raw_minimas.clear(); + raw_maximas.clear(); + maximas.clear(); + minimas.clear(); + + // find our threshold + uint minv = 255; + for (uint j = width / 8; j < width / 4; j++) + minv = min(uint(buf[j]), minv); + uint maxv = 0; + for (uint j = width / 8; j < width / 4; j++) + maxv = max(uint(buf[j]), maxv); + uint avgv = (maxv avgv+10) + raw_maximas.push_back(last_max=i); + else if (last_max>=0 && (i - last_max) <= noise_flr_sm) + raw_maximas.push_back(i); + else if (buf[i] < avgv-10) + raw_minimas.push_back(last_min=i); + else if (last_min>=0 && (i - last_min) <= noise_flr_lg) + raw_minimas.push_back(i); + } + + for (uint i = 0; i < uint(raw_maximas.size()); i++) + { + uint start_idx = raw_maximas[i]; + while ((i+1) < uint(raw_maximas.size()) && + (raw_maximas[i+1] == raw_maximas[i] + 1)) i++; + uint end_idx = raw_maximas[i]; + if (end_idx - start_idx > noise_flr_lg) + maximas.push_back((start_idx + end_idx) * 0.5f); + } + + if (maximas.size() < 7) + { + VERBOSE(VB_VBI|VB_EXTRA, LOC + + QString("FindClocks: maximas %1 < 7").arg(maximas.size())); + print(raw_minimas, raw_maximas, minimas, maximas); + return false; + } + + // drop outliers on edges + bool dropped = true; + while (maximas.size() > 7 && dropped) + { + float min_diff = width * 8; + float max_diff = 0.0f; + float avg_diff = 0.0f; + for (uint i = 1; i < uint(maximas.size()); i++) + { + float diff = maximas[i] - maximas[i-1]; + min_diff = min(diff, min_diff); + max_diff = max(diff, max_diff); + avg_diff += diff; + } + avg_diff -= min_diff; + avg_diff -= max_diff; + avg_diff /= (maximas.size() - 3); + + dropped = false; + if (avg_diff * 1.1f < max_diff) + { + float last_diff = maximas.back() - maximas[maximas.size()-2]; + if (last_diff*1.01f >= max_diff || last_diff > avg_diff * 1.2f) + { + maximas.pop_back(); + dropped = true; + } + float first_diff = maximas[1] - maximas[0]; + if ((maximas.size() > 7) && (first_diff*1.01f >= max_diff)) + { + maximas.pop_front(); + dropped = true; + } + } + + if (avg_diff * 0.9f > min_diff) + { + float last_diff = maximas.back() - maximas[maximas.size()-2]; + if ((maximas.size() > 7) && + (last_diff*0.99f <= min_diff || last_diff < avg_diff * 0.80f)) + { + maximas.pop_back(); + dropped = true; + } + float first_diff = maximas[1] - maximas[0]; + if ((maximas.size() > 7) && (first_diff*0.99f <= min_diff)) + { + maximas.pop_front(); + dropped = true; + } + } + } + + if (maximas.size() != 7) + { + VERBOSE(VB_VBI|VB_EXTRA, LOC + QString("FindClocks: maximas: %1 != 7") + .arg(maximas.size())); + print(raw_minimas, raw_maximas, minimas, maximas); + return false; + } + + // find the minimas + for (uint i = 0; i < uint(raw_minimas.size()); i++) + { + uint start_idx = raw_minimas[i]; + while ((i+1) < uint(raw_minimas.size()) && + (raw_minimas[i+1] == raw_minimas[i] + 1)) i++; + uint end_idx = raw_minimas[i]; + float center = (start_idx + end_idx) * 0.5f; + if (end_idx - start_idx > noise_flr_lg && + center > maximas[0] && center < maximas.back()) + { + minimas.push_back(center); + } + } + + if (minimas.size() != 6) + { + VERBOSE(VB_VBI|VB_EXTRA, LOC + QString("FindClocks: minimas: %1 != 6") + .arg(minimas.size())); + print(raw_minimas, raw_maximas, minimas, maximas); + return false; + } + + // get the average clock rate in samples + float maxima_avg_diff = find_clock_diff(maximas); + float minima_avg_diff = find_clock_diff(minimas); + rate = (maxima_avg_diff * 7 + minima_avg_diff * 6) / 13.0f; + if (maxima_avg_diff == 0.0f || minima_avg_diff == 0.0f) + return false; + + // get the estimated location of the first maxima + // based on the rate and location of all maximas + start = maximas[0]; + for (uint i = 1; i < uint(maximas.size()); i++) + start += maximas[i] - i * rate; + start /= maximas.size(); + // then move it back by a third to make each sample + // more or less in the center of each encoded byte. + start -= rate * 0.33f; + + // if the last bit is after the last sample... + // 7 clocks + 3 bits run in + 16 bits data + if (start+((7+3+8+8-1) * rate) > width) + { + VERBOSE(VB_VBI|VB_EXTRA, LOC + QString("FindClocks: end %1 > width %2") + .arg(start+((7+3+8+8-1) * rate)).arg(width)); + + return false; + } + + //VERBOSE(VB_VBI|VB_EXTRA, LOC + QString("FindClocks: Clock start %1, rate %2") + // .arg(start).arg(rate)); + + return true; +} + +bool VBI608Extractor::ExtractCC(const VideoFrame *picframe, uint max_lines) +{ + int ypitch = picframe->pitches[0]; + int ywidth = picframe->width; + + code[0] = UINT16_MAX; + code[1] = UINT16_MAX; + + // find CC + uint found_cnt = 0; + for (uint i = 0; i < max_lines; i++) + { + const unsigned char *y = picframe->buf + + picframe->offsets[0] + (i * ypitch); + if (FindClocks(y, ywidth)) + { + uint maxv = 0; + for (uint j = 0; j < start + 8 * rate; j++) + maxv = max(uint((y+(i * ypitch))[j]), maxv); + uint avgv = maxv / 2; + + if (y[uint(start + (0+7) * rate)] > avgv || + y[uint(start + (1+7) * rate)] > avgv || + y[uint(start + (2+7) * rate)] < avgv) + { + continue; // need 001 at run in.. + } + + code[found_cnt] = 0; + for (uint j = 0; j < 8+8; j++) + { + bool bit = y[uint(start + (j+7+3) * rate)] > avgv; + code[found_cnt] = + (code[found_cnt]>>1) | (bit?(1<<15):0); + } + + found_cnt++; + if (found_cnt>=2) + break; +#if 0 + unsigned char *Y = const_cast(y); + unsigned char *u = const_cast + (picframe->buf + picframe->offsets[1] + + (i*picframe->pitches[1])); + unsigned char *v = const_cast + (picframe->buf + picframe->offsets[2] + + (i*picframe->pitches[2])); + unsigned uwidth = picframe->pitches[1]; + v[uwidth / 3] = 0x40; + for (uint j = 0; j < 7+3+8+8; j++) + { + uint yloc = uint (start + j * rate + 0.5); + Y[yloc] = 0xFF; + uint uloc = uint (uwidth * (start + j * rate + 0.5) / ywidth); + u[uloc] = 0x40; + } +#endif + } + } + + return found_cnt; +} + +bool VBI608Extractor::ExtractCC12(const unsigned char *buf, uint width) +{ + code[0] = UINT16_MAX; + if (FindClocks(buf, width)) + { + uint maxv = 0; + for (uint j = 0; j < start + 8 * rate; j++) + maxv = max(uint(buf[j]), maxv); + uint avgv = maxv / 2; + + if (buf[uint(start + (0+7) * rate)] > avgv || + buf[uint(start + (1+7) * rate)] > avgv || + buf[uint(start + (2+7) * rate)] < avgv) + { + VERBOSE(VB_VBI|VB_EXTRA, LOC + "did not find VBI 608 header"); + return false; + } + + code[0] = 0; + for (uint j = 0; j < 8+8; j++) + { + bool bit = buf[uint(start + (j+7+3) * rate)] > avgv; + code[0] = (code[0]>>1) | (bit?(1<<15):0); + } + + return true; + } + return false; +} + +bool VBI608Extractor::ExtractCC34(const unsigned char *buf, uint width) +{ + code[1] = UINT16_MAX; + if (FindClocks(buf, width)) + { + uint maxv = 0; + for (uint j = 0; j < start + 8 * rate; j++) + maxv = max(uint(buf[j]), maxv); + uint avgv = maxv / 2; + + if (buf[uint(start + (0+7) * rate)] > avgv || + buf[uint(start + (1+7) * rate)] > avgv || + buf[uint(start + (2+7) * rate)] < avgv) + { + return false; + } + + code[1] = 0; + for (uint j = 0; j < 8+8; j++) + { + bool bit = buf[uint(start + (j+7+3) * rate)] > avgv; + code[1] = (code[1]>>1) | (bit?(1<<15):0); + } + return true; + } + return false; +} + +uint VBI608Extractor::FillCCData(uint8_t cc_data[8]) +{ + uint cc_count = 0; + if (code[0] != UINT16_MAX) + { + cc_data[2] = 0x04; + cc_data[3] = (code[0]) & 0xff; + cc_data[4] = (code[0]>>8) & 0xff; + cc_count++; + } + + if (code[1] != UINT16_MAX) + { + cc_data[2+3*cc_count] = 0x04|0x01; + cc_data[3+3*cc_count] = (code[1]) & 0xff; + cc_data[4+3*cc_count] = (code[1]>>8) & 0xff; + cc_count++; + } + + if (cc_count) + { + cc_data[0] = 0x40 | cc_count; + cc_data[1] = 0x00; + return 2+(3*cc_count); + } + return 0; +} diff --git a/mythtv/libs/libmythtv/vbi608extractor.h b/mythtv/libs/libmythtv/vbi608extractor.h new file mode 100644 index 00000000000..355e1cdbca0 --- /dev/null +++ b/mythtv/libs/libmythtv/vbi608extractor.h @@ -0,0 +1,57 @@ +/* + VBI 608 Extractor, extracts CEA-608 VBI from a line of raw data. + Copyright (C) 2010 Digital Nirvana, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef _VBI_608_EXTRACTOR_H_ +#define _VBI_608_EXTRACTOR_H_ + +#include + +#include + +#include "frame.h" + +class VBI608Extractor +{ + public: + VBI608Extractor(); + + uint16_t GetCode1(void) const { return code[0]; } + uint16_t GetCode2(void) const { return code[1]; } + + bool ExtractCC(const VideoFrame*, uint max_lines = 4); + bool ExtractCC12(const unsigned char *buf, uint width); + bool ExtractCC34(const unsigned char *buf, uint width); + + uint FillCCData(uint8_t cc_data[8]); + + private: + float GetClockStart(void) const { return start; } + float GetClockRate(void) const { return rate; } + bool FindClocks(const unsigned char *buf, uint width); + + QList raw_minimas; + QList raw_maximas; + QList maximas; + QList minimas; + float start; + float rate; + uint16_t code[2]; +}; + +#endif // _VBI_608_EXTRACTOR_H_ diff --git a/mythtv/libs/libmythtv/vbitext/cc.h b/mythtv/libs/libmythtv/vbitext/cc.h index 33fef1cf325..9ab56b23e68 100644 --- a/mythtv/libs/libmythtv/vbitext/cc.h +++ b/mythtv/libs/libmythtv/vbitext/cc.h @@ -1,6 +1,10 @@ #ifndef CC_H #define CC_H +#ifdef __cplusplus +extern "C" { +#endif + #define CC_VBIBUFSIZE 65536*2 //cc is 32 columns per row, this allows for extra characters @@ -19,12 +23,14 @@ struct cc int scale0, scale1; }; -int cc_decode(unsigned char *vbiline); void cc_decode(struct cc *cc); struct cc *cc_open(const char *vbi_name); void cc_close(struct cc *cc); void cc_handler(struct cc *cc); +#ifdef __cplusplus +} #endif +#endif diff --git a/mythtv/libs/libmythtv/vbitext/dllist.h b/mythtv/libs/libmythtv/vbitext/dllist.h index 5494475f1b9..6eecd5e8215 100644 --- a/mythtv/libs/libmythtv/vbitext/dllist.h +++ b/mythtv/libs/libmythtv/vbitext/dllist.h @@ -1,6 +1,10 @@ #ifndef DLLIST_H #define DLLIST_H +#ifdef __cplusplus +extern "C" { +#endif + struct dl_node { struct dl_node *next; @@ -50,5 +54,9 @@ dl_insert_after(struct dl_node *p, struct dl_node *n) #define dl_remove_first(h) dl_remove((h)->first) // mustn't be empty! #define dl_remove_last(h) dl_remove((h)->last) // mustn't be empty! +#ifdef __cplusplus +} +#endif + #endif /* DLLIST_H */ diff --git a/mythtv/libs/libmythtv/vbitext/hamm.h b/mythtv/libs/libmythtv/vbitext/hamm.h index 96d0594db1d..53ea7165c5e 100644 --- a/mythtv/libs/libmythtv/vbitext/hamm.h +++ b/mythtv/libs/libmythtv/vbitext/hamm.h @@ -1,10 +1,18 @@ #ifndef HAMM_H #define HAMM_H +#ifdef __cplusplus +extern "C" { +#endif + int hamm8(unsigned char *p, int *err); int hamm16(unsigned char *p, int *err); int hamm24(unsigned char *p, int *err); int chk_parity(unsigned char *p, int n); +#ifdef __cplusplus +} +#endif + #endif diff --git a/mythtv/libs/libmythtv/vbitext/lang.h b/mythtv/libs/libmythtv/vbitext/lang.h index f285321feba..97eb4237bfc 100644 --- a/mythtv/libs/libmythtv/vbitext/lang.h +++ b/mythtv/libs/libmythtv/vbitext/lang.h @@ -1,6 +1,10 @@ #ifndef LANG_H #define LANG_H +#ifdef __cplusplus +extern "C" { +#endif + #include "vt.h" extern int latin1; @@ -19,5 +23,9 @@ void init_enhance(struct enhance *eh); void add_enhance(struct enhance *eh, int dcode, unsigned int *data); void enhance(struct enhance *eh, struct vt_page *vtp); +#ifdef __cplusplus +} +#endif + #endif // LANG_H diff --git a/mythtv/libs/libmythtv/vbitext/vbi.h b/mythtv/libs/libmythtv/vbitext/vbi.h index 3836c5df2d8..4130d29ac0a 100644 --- a/mythtv/libs/libmythtv/vbitext/vbi.h +++ b/mythtv/libs/libmythtv/vbitext/vbi.h @@ -1,6 +1,10 @@ #ifndef VBI_H #define VBI_H +#ifdef __cplusplus +extern "C" { +#endif + #include "vt.h" #include "dllist.h" #include "lang.h" @@ -56,5 +60,9 @@ void vbi_pll_reset(struct vbi *vbi, int fine_tune); void vbi_handler(struct vbi *vbi, int fd); +#ifdef __cplusplus +} +#endif + #endif diff --git a/mythtv/libs/libmythtv/videosource.cpp b/mythtv/libs/libmythtv/videosource.cpp index b3f808251c6..005eeee6463 100644 --- a/mythtv/libs/libmythtv/videosource.cpp +++ b/mythtv/libs/libmythtv/videosource.cpp @@ -831,17 +831,19 @@ class VBIDevice : public PathSetting, public CaptureCardDBStorage if (!fillSelectionsFromDir(dev, card, driver)) { dev.setPath("/dev"); - fillSelectionsFromDir(dev, card, driver); + if (!fillSelectionsFromDir(dev, card, driver) && + !getValue().isEmpty()) + { + addSelection(getValue(),getValue(),true); + } } } uint fillSelectionsFromDir(const QDir &dir, const QString &card, const QString &driver) { - uint cnt = 0; - + QStringList devices; QFileInfoList il = dir.entryInfoList(); - for( QFileInfoList::iterator it = il.begin(); it != il.end(); ++it ) @@ -859,14 +861,17 @@ class VBIDevice : public PathSetting, public CaptureCardDBStorage (driver.isEmpty() || (dn == driver)) && (card.isEmpty() || (cn == card))) { - addSelection(device); - cnt++; + devices.push_back(device); } close(vbifd); } - return cnt; + QString sel = getValue(); + for (uint i = 0; i < (uint) devices.size(); i++) + addSelection(devices[i], devices[i], devices[i] == sel); + + return (uint) devices.size(); } }; @@ -993,7 +998,7 @@ class DVBCardNum : public ComboBoxSetting, public CaptureCardDBStorage ComboBoxSetting(this), CaptureCardDBStorage(this, parent, "videodevice") { - setLabel(QObject::tr("DVB device number")); + setLabel(QObject::tr("DVB device")); setHelpText( QObject::tr("When you change this setting, the text below " "should change to the name and type of your card. " @@ -1260,6 +1265,8 @@ class FirewireConfigurationGroup : public VerticalConfigurationGroup model(new FirewireModel(parent, dev)) { addChild(dev); + addChild(new EmptyAudioDevice(parent)); + addChild(new EmptyVBIDevice(parent)); addChild(desc); addChild(model); @@ -1551,12 +1558,149 @@ class IPTVConfigurationGroup : public VerticalConfigurationGroup addChild(new IPTVHost(parent)); addChild(new ChannelTimeout(parent, 3000, 1750)); addChild(new SingleCardInput(parent)); + addChild(new EmptyAudioDevice(parent)); + addChild(new EmptyVBIDevice(parent)); }; private: CaptureCard &parent; }; +class ASIDevice : public ComboBoxSetting, public CaptureCardDBStorage +{ + public: + ASIDevice(const CaptureCard &parent) : + ComboBoxSetting(this, true), + CaptureCardDBStorage(this, parent, "videodevice") + { + setLabel(QObject::tr("ASI device")); + fillSelections(QString::null); + }; + + /// \brief Adds all available cards to list + /// If current is >= 0 it will be considered available even + /// if no device exists for it in /dev/dvb/adapter* + void fillSelections(const QString ¤t) + { + clearSelections(); + + // Get devices from filesystem + QStringList sdevs = CardUtil::ProbeVideoDevices("ASI"); + + // Add current if needed + if (!current.isEmpty() && + (find(sdevs.begin(), sdevs.end(), current) == sdevs.end())) + { + stable_sort(sdevs.begin(), sdevs.end()); + } + + // Get devices from DB + QStringList db = CardUtil::GetVideoDevices("ASI"); + + // Figure out which physical devices are already in use + // by another card defined in the DB, and select a device + // for new configs (preferring non-conflicing devices). + QMap in_use; + QString sel = current; + for (uint i = 0; i < (uint)sdevs.size(); i++) + { + const QString dev = sdevs[i]; + in_use[sdevs[i]] = find(db.begin(), db.end(), dev) != db.end(); + if (sel.isEmpty() && !in_use[sdevs[i]]) + sel = dev; + } + + // Unfortunately all devices are conflicted, select first device. + if (sel.isEmpty() && sdevs.size()) + sel = sdevs[0]; + + QString usestr = QString(" -- "); + usestr += QObject::tr("Warning: already in use"); + + // Add the devices to the UI + bool found = false; + for (uint i = 0; i < (uint)sdevs.size(); i++) + { + const QString dev = sdevs[i]; + QString desc = dev + (in_use[sdevs[i]] ? usestr : ""); + desc = (current == sdevs[i]) ? dev : desc; + addSelection(desc, dev, dev == sel); + found |= (dev == sel); + } + + // If a configured device isn't on the list, add it with warning + if (!found && !current.isEmpty()) + { + QString desc = current + " -- " + + QObject::tr("Warning: unable to open"); + addSelection(desc, current, true); + } + } + + virtual void Load(void) + { + clearSelections(); + addSelection(QString::null); + CaptureCardDBStorage::Load(); + fillSelections(getValue()); + } +}; + +ASIConfigurationGroup::ASIConfigurationGroup(CaptureCard& a_parent): + VerticalConfigurationGroup(false, true, false, false), + parent(a_parent), + device(new ASIDevice(parent)), + cardinfo(new TransLabelSetting()), + input(new TunerCardInput(parent, device->getValue(), "ASI")), + instances(new InstanceCount(parent)) +{ + addChild(device); + addChild(new EmptyAudioDevice(parent)); + addChild(new EmptyVBIDevice(parent)); + addChild(cardinfo); + addChild(input); + addChild(instances); + input->setVisible(false); + + connect(device, SIGNAL(valueChanged(const QString&)), + this, SLOT( probeCard( const QString&))); + connect(instances, SIGNAL(valueChanged(int)), + &parent, SLOT( SetInstanceCount(int))); + + probeCard(device->getValue()); +}; + +void ASIConfigurationGroup::probeCard(const QString &device) +{ +#ifdef USING_ASI + if (device.isEmpty()) + { + cardinfo->setValue(""); + return; + } + + if (parent.getCardID() && parent.GetRawCardType() != "ASI") + { + cardinfo->setValue(""); + return; + } + + QString error; + int device_num = CardUtil::GetASIDeviceNumber(device, &error); + if (device_num < 0) + { + cardinfo->setValue(tr("Not a valid DVEO ASI card")); + VERBOSE(VB_IMPORTANT, + "ASIConfigurationGroup::probeCard(), Warning: " + error); + return; + } + cardinfo->setValue(tr("Valid DVEO ASI card")); + input->fillSelections(device); +#else + cardinfo->setValue(QString("Not compiled with ASI support")); +#endif +} + ImportConfigurationGroup::ImportConfigurationGroup(CaptureCard& a_parent): VerticalConfigurationGroup(false, true, false, false), parent(a_parent), @@ -1568,6 +1712,9 @@ ImportConfigurationGroup::ImportConfigurationGroup(CaptureCard& a_parent): " external program to import recording files.")); addChild(device); + addChild(new EmptyAudioDevice(parent)); + addChild(new EmptyVBIDevice(parent)); + info->setLabel(tr("File info")); addChild(info); @@ -1654,6 +1801,8 @@ HDHomeRunConfigurationGroup::HDHomeRunConfigurationGroup deviceid, desc, cardip, cardtuner, &devicelist); addChild(deviceidlist); + addChild(new EmptyAudioDevice(parent)); + addChild(new EmptyVBIDevice(parent)); addChild(deviceid); addChild(desc); addChild(cardip); @@ -1815,7 +1964,8 @@ V4LConfigurationGroup::V4LConfigurationGroup(CaptureCard& a_parent) : cardinfo(new TransLabelSetting()), vbidev(new VBIDevice(parent)), input(new TunerCardInput(parent)) { - VideoDevice *device = new VideoDevice(parent); + QString drv = "(?!ivtv|hdpvr|(saa7164(.*)))"; + VideoDevice *device = new VideoDevice(parent, 0, 15, QString::null, drv); HorizontalConfigurationGroup *audgrp = new HorizontalConfigurationGroup(false, false, true, true); @@ -1859,15 +2009,20 @@ void V4LConfigurationGroup::probeCard(const QString &device) MPEGConfigurationGroup::MPEGConfigurationGroup(CaptureCard &a_parent) : VerticalConfigurationGroup(false, true, false, false), - parent(a_parent), cardinfo(new TransLabelSetting()), + parent(a_parent), + device(NULL), vbidevice(NULL), + cardinfo(new TransLabelSetting()), input(new TunerCardInput(parent)) { - VideoDevice *device = - new VideoDevice(parent, 0, 15, QString::null, "(ivtv|saa7164)"); + QString drv = "ivtv|(saa7164(.*))"; + device = new VideoDevice(parent, 0, 15, QString::null, drv); + vbidevice = new VBIDevice(parent); + vbidevice->setVisible(false); cardinfo->setLabel(tr("Probed info")); addChild(device); + addChild(vbidevice); addChild(cardinfo); addChild(input); addChild(new ChannelTimeout(parent, 12000, 2000)); @@ -1894,6 +2049,8 @@ void MPEGConfigurationGroup::probeCard(const QString &device) } cardinfo->setValue(ci); + vbidevice->setVisible(dn!="ivtv"); + vbidevice->setFilter(cn, dn); input->fillSelections(device); } @@ -1908,6 +2065,9 @@ DemoConfigurationGroup::DemoConfigurationGroup(CaptureCard &a_parent) : device->addSelection("file:/"); addChild(device); + addChild(new EmptyAudioDevice(parent)); + addChild(new EmptyVBIDevice(parent)); + info->setLabel(tr("File info")); addChild(info); @@ -1963,6 +2123,8 @@ HDPVRConfigurationGroup::HDPVRConfigurationGroup(CaptureCard &a_parent) : cardinfo->setLabel(tr("Probed info")); addChild(device); + addChild(new EmptyAudioDevice(parent)); + addChild(new EmptyVBIDevice(parent)); addChild(cardinfo); addChild(videoinput); addChild(audioinput); @@ -2030,6 +2192,10 @@ CaptureCardGroup::CaptureCardGroup(CaptureCard &parent) : addTarget("FREEBOX", new IPTVConfigurationGroup(parent)); #endif // USING_IPTV +#ifdef USING_ASI + addTarget("ASI", new ASIConfigurationGroup(parent)); +#endif // USING_ASI + // for testing without any actual tuner hardware: addTarget("IMPORT", new ImportConfigurationGroup(parent)); addTarget("DEMO", new DemoConfigurationGroup(parent)); @@ -2200,7 +2366,7 @@ void CardType::fillSelections(SelectSetting* setting) QObject::tr("MJPEG capture card (Matrox G200, DC10)"), "MJPEG"); # ifdef USING_IVTV setting->addSelection( - QObject::tr("IVTV MPEG-2 encoder card"), "MPEG"); + QObject::tr("MPEG-2 encoder card"), "MPEG"); # endif // USING_IVTV # ifdef USING_HDPVR setting->addSelection( @@ -2233,6 +2399,10 @@ void CardType::fillSelections(SelectSetting* setting) setting->addSelection(QObject::tr("Network recorder"), "FREEBOX"); #endif // USING_IPTV +#ifdef USING_ASI + setting->addSelection(QObject::tr("DVEO ASI recorder"), "ASI"); +#endif + setting->addSelection(QObject::tr("Import test recorder"), "IMPORT"); setting->addSelection(QObject::tr("Demo test recorder"), "DEMO"); } @@ -3497,8 +3667,8 @@ DVBConfigurationGroup::DVBConfigurationGroup(CaptureCard& a_parent) : addChild(signal_timeout); addChild(channel_timeout); - addChild(new DVBAudioDevice(parent)); - addChild(new DVBVbiDevice(parent)); + addChild(new EmptyAudioDevice(parent)); + addChild(new EmptyVBIDevice(parent)); TransButtonSetting *buttonRecOpt = new TransButtonSetting(); buttonRecOpt->setLabel(tr("Recording Options")); diff --git a/mythtv/libs/libmythtv/videosource.h b/mythtv/libs/libmythtv/videosource.h index 88ddf2796cd..a152128dcf7 100644 --- a/mythtv/libs/libmythtv/videosource.h +++ b/mythtv/libs/libmythtv/videosource.h @@ -344,11 +344,11 @@ class TunerCardAudioInput : public ComboBoxSetting, public CaptureCardDBStorage QString last_cardtype; }; -class DVBAudioDevice : public LineEditSetting, public CaptureCardDBStorage +class EmptyAudioDevice : public LineEditSetting, public CaptureCardDBStorage { Q_OBJECT public: - DVBAudioDevice(const CaptureCard &parent) : + EmptyAudioDevice(const CaptureCard &parent) : LineEditSetting(this), CaptureCardDBStorage(this, parent, "audiodevice") { @@ -369,12 +369,12 @@ class DVBAudioDevice : public LineEditSetting, public CaptureCardDBStorage } }; -class DVBVbiDevice : public LineEditSetting, public CaptureCardDBStorage +class EmptyVBIDevice : public LineEditSetting, public CaptureCardDBStorage { Q_OBJECT public: - DVBVbiDevice(const CaptureCard &parent) : + EmptyVBIDevice(const CaptureCard &parent) : LineEditSetting(this), CaptureCardDBStorage(this, parent, "vbidevice") { @@ -462,6 +462,9 @@ class V4LConfigurationGroup : public VerticalConfigurationGroup TunerCardInput *input; }; +class VideoDevice; +class VBIDevice; + class MPEGConfigurationGroup: public VerticalConfigurationGroup { Q_OBJECT @@ -474,6 +477,8 @@ class MPEGConfigurationGroup: public VerticalConfigurationGroup private: CaptureCard &parent; + VideoDevice *device; + VBIDevice *vbidevice; TransLabelSetting *cardinfo; TunerCardInput *input; }; @@ -495,6 +500,31 @@ class HDPVRConfigurationGroup: public VerticalConfigurationGroup TunerCardAudioInput *audioinput; }; +class TunerCardInput; +class InstanceCount; +class ASIDevice; + +class ASIConfigurationGroup: public VerticalConfigurationGroup +{ + Q_OBJECT + + public: + ASIConfigurationGroup(CaptureCard &parent); + + public slots: + void probeCard(const QString &device); + + private: + CaptureCard &parent; + ASIDevice *device; + TransLabelSetting *cardinfo; + TunerCardInput *input; + InstanceCount *instances; +}; + +class TunerCardInput; +class InstanceCount; + class ImportConfigurationGroup: public VerticalConfigurationGroup { Q_OBJECT @@ -624,7 +654,11 @@ class CaptureCard : public QObject, public ConfigurationWizard virtual void Save(void); uint GetInstanceCount(void) const { return instance_count; } + +public slots: void SetInstanceCount(uint cnt) { instance_count = cnt; } + // this is needed to connect valueChanged() signal from legacy settings + void SetInstanceCount(int cnt) { instance_count = (uint)cnt; } private: diff --git a/mythtv/libs/libmythui/mythudplistener.cpp b/mythtv/libs/libmythui/mythudplistener.cpp index 2cc1cf4cb6f..516ae50880c 100644 --- a/mythtv/libs/libmythui/mythudplistener.cpp +++ b/mythtv/libs/libmythui/mythudplistener.cpp @@ -16,7 +16,7 @@ MythUDPListener::MythUDPListener() m_socket = new QUdpSocket(this); connect(m_socket, SIGNAL(readyRead()), this, SLOT(ReadPending())); - if (m_socket->bind(udp_port)) + if (m_socket->bind(QHostAddress::AnyIPv6,udp_port)) { VERBOSE(VB_GENERAL, LOC + QString("bound to port %1").arg(udp_port)); } diff --git a/mythtv/libs/libmythui/mythuifilebrowser.cpp b/mythtv/libs/libmythui/mythuifilebrowser.cpp index ce7d38387eb..825484b057e 100644 --- a/mythtv/libs/libmythui/mythuifilebrowser.cpp +++ b/mythtv/libs/libmythui/mythuifilebrowser.cpp @@ -176,8 +176,11 @@ void MythUIFileBrowser::Init(const QString &startPath) if (!qurl.path().isEmpty()) { // Force browing of remote SG's to start at their root - m_baseDirectory = QString("myth://%1@%2").arg(qurl.userName()) - .arg(qurl.host()); + m_baseDirectory = gCoreContext->GenMythURL(qurl.host(), + 0, + "", + qurl.userName()); + } else { diff --git a/mythtv/libs/libmythupnp/broadcast.h b/mythtv/libs/libmythupnp/broadcast.h index cc72ed57e3c..927128668a3 100644 --- a/mythtv/libs/libmythupnp/broadcast.h +++ b/mythtv/libs/libmythupnp/broadcast.h @@ -48,6 +48,10 @@ class QBroadcastSocket : public MSocketDevice m_address.setAddress( sAddress ); m_port = nPort; + QByteArray addr = sAddress.toLatin1(); + setProtocol(IPv4); + setSocket(createNewSocket(), MSocketDevice::Datagram); + int one = 1; if ( setsockopt( socket(), SOL_SOCKET, SO_BROADCAST, &one, sizeof( one )) < 0) diff --git a/mythtv/libs/libmythupnp/httpserver.cpp b/mythtv/libs/libmythupnp/httpserver.cpp index 457265b28fe..c273dbffe0d 100644 --- a/mythtv/libs/libmythupnp/httpserver.cpp +++ b/mythtv/libs/libmythupnp/httpserver.cpp @@ -64,7 +64,7 @@ HttpServer::HttpServer() : QTcpServer(), ThreadPool("HTTP") // ---------------------------------------------------------------------- #ifdef USING_MINGW - g_sPlatform = QString( "Windows %1.%1" ) + g_sPlatform = QString( "Windows %1.%2" ) .arg(LOBYTE(LOWORD(GetVersion()))) .arg(HIBYTE(LOWORD(GetVersion()))); #else diff --git a/mythtv/libs/libmythupnp/multicast.cpp b/mythtv/libs/libmythupnp/multicast.cpp index b05a953c13d..86a86b69868 100644 --- a/mythtv/libs/libmythupnp/multicast.cpp +++ b/mythtv/libs/libmythupnp/multicast.cpp @@ -62,6 +62,10 @@ QMulticastSocket::QMulticastSocket( QString sAddress, quint16 nPort, quint8 ttl if (ttl == 0) ttl = 4; + // Force to IPv4 until a proper upnp spec is complete for ipv6 as well. + setProtocol(IPv4); + setSocket(createNewSocket(), MSocketDevice::Datagram); + // ---------------------------------------------------------------------- // Set the numer of subnets to traverse (TTL) // ---------------------------------------------------------------------- diff --git a/mythtv/libs/libmythupnp/ssdp.cpp b/mythtv/libs/libmythupnp/ssdp.cpp index b42e93f2b6a..902c8e2dd30 100644 --- a/mythtv/libs/libmythupnp/ssdp.cpp +++ b/mythtv/libs/libmythupnp/ssdp.cpp @@ -206,6 +206,11 @@ void SSDP::PerformSearch( const QString &sST ) QByteArray sRequest = rRequest.toUtf8(); MSocketDevice *pSocket = m_Sockets[ SocketIdx_Search ]; + if ( !pSocket->isValid() ) + { + pSocket->setProtocol(MSocketDevice::IPv4); + pSocket->setSocket(pSocket->createNewSocket(), MSocketDevice::Datagram); + } QHostAddress address; address.setAddress( SSDP_GROUP ); diff --git a/mythtv/libs/libmythupnp/ssdpcache.cpp b/mythtv/libs/libmythupnp/ssdpcache.cpp index f590f0f6755..afc52ba9d5f 100644 --- a/mythtv/libs/libmythupnp/ssdpcache.cpp +++ b/mythtv/libs/libmythupnp/ssdpcache.cpp @@ -113,6 +113,9 @@ void SSDPCacheEntries::Insert( const QString &sUSN, DeviceLocation *pEntry ) m_mapEntries.insert( sUSN, pEntry ); + VERBOSE(VB_UPNP, QString("SSDP Cache adding USN: %1 Location %2") + .arg(pEntry->m_sUSN).arg(pEntry->m_sLocation)); + Unlock(); } @@ -128,7 +131,11 @@ void SSDPCacheEntries::Remove( const QString &sUSN ) if (it != m_mapEntries.end()) { if (*it) + { + VERBOSE(VB_UPNP, QString("SSDP Cache removing USN: %1 Location %2") + .arg((*it)->m_sUSN).arg((*it)->m_sLocation)); (*it)->Release(); + } // -=>TODO: Need to somehow call SSDPCache::NotifyRemove diff --git a/mythtv/libs/libmythupnp/upnpdevice.cpp b/mythtv/libs/libmythupnp/upnpdevice.cpp index 3d45af4cd9b..d121dcb93e5 100644 --- a/mythtv/libs/libmythupnp/upnpdevice.cpp +++ b/mythtv/libs/libmythupnp/upnpdevice.cpp @@ -33,6 +33,7 @@ #include #include +#include int DeviceLocation::g_nAllocated = 0; // Debugging only @@ -353,6 +354,18 @@ void UPnpDeviceDesc::GetValidXML( { // os.setEncoding( QTextStream::UnicodeUTF8 ); + QString BaseAddr; + QHostAddress addr(sBaseAddress); + +#if !defined(QT_NO_IPV6) + // Basically if it appears to be an IPv6 IP surround the IP with [] otherwise don't bother + if (( addr.protocol() == QAbstractSocket::IPv6Protocol ) || (sBaseAddress.contains(":"))) + BaseAddr = "[" + sBaseAddress + "]"; + else +#endif + if ( addr.protocol() == QAbstractSocket::IPv4Protocol ) + BaseAddr = sBaseAddress; + os << "\n" "\n" "\n" @@ -360,7 +373,7 @@ void UPnpDeviceDesc::GetValidXML( "0\n" "\n" "http://" - << sBaseAddress << ":" << nPort << "/\n"; + << BaseAddr << ":" << nPort << "/\n"; OutputDevice( os, &m_rootDevice, sUserAgent ); diff --git a/mythtv/libs/libmythupnp/upnptasknotify.cpp b/mythtv/libs/libmythupnp/upnptasknotify.cpp index c5138d9193c..f8cde4354a3 100644 --- a/mythtv/libs/libmythupnp/upnptasknotify.cpp +++ b/mythtv/libs/libmythupnp/upnptasknotify.cpp @@ -69,9 +69,9 @@ UPnpNotifyTask::~UPnpNotifyTask() // ///////////////////////////////////////////////////////////////////////////// -void UPnpNotifyTask::SendNotifyMsg( MSocketDevice *pSocket, - QString sNT, - QString sUDN ) +void UPnpNotifyTask::SendNotifyMsg( QMulticastSocket *pSocket, + QString sNT, + QString sUDN ) { QString sUSN; @@ -93,10 +93,11 @@ void UPnpNotifyTask::SendNotifyMsg( MSocketDevice *pSocket, .arg( sUSN ) .arg( m_nMaxAge ); -// VERBOSE(VB_UPNP, QString("UPnpNotifyTask::SendNotifyMsg : %1 : %2 : %3") -// .arg( pSocket->address().toString() ) -// .arg( sNT ) -// .arg( sUSN )); + VERBOSE(VB_UPNP, QString("UPnpNotifyTask::SendNotifyMsg : %1:%2 : %3 : %4") + .arg( pSocket->m_address.toString() ) + .arg( pSocket->m_port ) + .arg( sNT ) + .arg( sUSN )); { QMutexLocker qml(&m_mutex); // for addressList @@ -118,12 +119,18 @@ void UPnpNotifyTask::SendNotifyMsg( MSocketDevice *pSocket, continue; } + QString ipaddress = *it; + + // If this looks like an IPv6 address, then enclose it in []'s + if (ipaddress.contains(":")) + ipaddress = "[" + ipaddress + "]"; + QString sHeader = QString( "NOTIFY * HTTP/1.1\r\n" "HOST: %1:%2\r\n" "LOCATION: http://%3:%4/getDeviceDesc\r\n" ) - .arg( SSDP_GROUP ) // pSocket->address().toString() ) - .arg( SSDP_PORT ) // pSocket->port() ) - .arg( *it ) + .arg( pSocket->m_address.toString() ) + .arg( pSocket->m_port ) + .arg( ipaddress ) .arg( m_nServicePort); QString sPacket = sHeader + sData; @@ -133,9 +140,9 @@ void UPnpNotifyTask::SendNotifyMsg( MSocketDevice *pSocket, // Send Packet to Socket (Send same packet twice) // ------------------------------------------------------------------ - pSocket->writeBlock( scPacket, scPacket.length(), pSocket->address(), pSocket->port() ); + pSocket->writeBlock( scPacket, scPacket.length(), pSocket->m_address, pSocket->m_port ); usleep( rand() % 250000 ); - pSocket->writeBlock( scPacket, scPacket.length(), pSocket->address(), pSocket->port() ); + pSocket->writeBlock( scPacket, scPacket.length(), pSocket->m_address, pSocket->m_port ); } } @@ -148,7 +155,7 @@ void UPnpNotifyTask::SendNotifyMsg( MSocketDevice *pSocket, void UPnpNotifyTask::Execute( TaskQueue *pQueue ) { - MSocketDevice *pMulticast = new QMulticastSocket(SSDP_GROUP, SSDP_PORT); + QMulticastSocket *pMulticast = new QMulticastSocket(SSDP_GROUP, SSDP_PORT); // QSocketDevice *pBroadcast = new QBroadcastSocket( "255.255.255.255", SSDP_PORT ); // ---------------------------------------------------------------------- @@ -191,7 +198,7 @@ void UPnpNotifyTask::Execute( TaskQueue *pQueue ) ///////////////////////////////////////////////////////////////////////////// void UPnpNotifyTask::ProcessDevice( - MSocketDevice *pSocket, UPnpDevice *pDevice) + QMulticastSocket *pSocket, UPnpDevice *pDevice) { // ---------------------------------------------------------------------- // Loop for each device and send the 2 required messages diff --git a/mythtv/libs/libmythupnp/upnptasknotify.h b/mythtv/libs/libmythupnp/upnptasknotify.h index 51c0b98f0f9..580c2524072 100644 --- a/mythtv/libs/libmythupnp/upnptasknotify.h +++ b/mythtv/libs/libmythupnp/upnptasknotify.h @@ -77,8 +77,8 @@ class UPnpNotifyTask : public Task virtual ~UPnpNotifyTask(); - void ProcessDevice( MSocketDevice *pSocket, UPnpDevice *pDevice ); - void SendNotifyMsg( MSocketDevice *pSocket, QString sNT, QString sUDN ); + void ProcessDevice( QMulticastSocket *pSocket, UPnpDevice *pDevice ); + void SendNotifyMsg( QMulticastSocket *pSocket, QString sNT, QString sUDN ); public: diff --git a/mythtv/libs/libmythupnp/upnptasksearch.cpp b/mythtv/libs/libmythupnp/upnptasksearch.cpp index a5a0960ec3e..20d6d8e5afd 100644 --- a/mythtv/libs/libmythupnp/upnptasksearch.cpp +++ b/mythtv/libs/libmythupnp/upnptasksearch.cpp @@ -109,7 +109,7 @@ void UPnpSearchTask::SendMsg( MSocketDevice *pSocket, ++it ) { QString sHeader = QString ( "HTTP/1.1 200 OK\r\n" - "LOCATION: http://%1:%2/getDeviceDesc\r\n" ) + "LOCATION: http://[%1]:%2/getDeviceDesc\r\n" ) .arg( *it ) .arg( m_nServicePort); diff --git a/mythtv/programs/mythbackend/httpconfig.cpp b/mythtv/programs/mythbackend/httpconfig.cpp index cafa8a76974..817a2b23855 100644 --- a/mythtv/programs/mythbackend/httpconfig.cpp +++ b/mythtv/programs/mythbackend/httpconfig.cpp @@ -186,8 +186,14 @@ bool HttpConfig::ProcessRequest(HttpWorkerThread*, HTTPRequest *request) if (dir == "/") dir = ""; QString path = - QString("myth://%1@%2%3%4").arg(storageGroup) - .arg(host).arg(dir).arg(parts[1]); + gCoreContext->GenMythURL(host, + 0, + dir + parts[1], + storageGroup); +//QString MythCoreContext::GenMythURL(QString host, int port, QString path, QString storageGroup) +// +// QString("myth://%1@%2%3%4").arg(storageGroup) +// .arg(host).arg(dir).arg(parts[1]); if (entry.startsWith("sgdir::")) { os << "
  • createElement("Frontends"); + root.appendChild(frontends); + + SSDPCacheEntries* fes = SSDP::Find("urn:schemas-mythtv-org:service:MythFrontend:1"); + if (fes) + { + fes->AddRef(); + fes->Lock(); + EntryMap* map = fes->GetEntryMap(); + frontends.setAttribute( "count", map->size() ); + QMapIterator< QString, DeviceLocation * > i(*map); + while (i.hasNext()) + { + i.next(); + QDomElement fe = pDoc->createElement("Frontend"); + frontends.appendChild(fe); + QUrl url(i.value()->m_sLocation); + fe.setAttribute("name", url.host()); + fe.setAttribute("url", i.value()->m_sLocation); + } + fes->Unlock(); + fes->Release(); + } + + // Other backends + + QDomElement backends = pDoc->createElement("Backends"); + root.appendChild(backends); + + int numbes = 0; + if (!gCoreContext->IsMasterBackend()) + { + numbes++; + QString masterhost = gCoreContext->GetMasterHostName(); + QString masterip = gCoreContext->GetSetting("MasterServerIP"); + QString masterport = gCoreContext->GetSettingOnHost("BackendStatusPort", masterhost, "6544"); + + QDomElement mbe = pDoc->createElement("Backend"); + backends.appendChild(mbe); + mbe.setAttribute("type", "Master"); + mbe.setAttribute("name", masterhost); + mbe.setAttribute("url" , masterip + ":" + masterport); + } + + SSDPCacheEntries* sbes = SSDP::Find("urn:schemas-mythtv-org:device:SlaveMediaServer:1"); + if (sbes) + { + + QString ipaddress = QString(); + if (!UPnp::g_IPAddrList.isEmpty()) + ipaddress = UPnp::g_IPAddrList.at(0); + + sbes->AddRef(); + sbes->Lock(); + EntryMap* map = sbes->GetEntryMap(); + QMapIterator< QString, DeviceLocation * > i(*map); + while (i.hasNext()) + { + i.next(); + QUrl url(i.value()->m_sLocation); + if (url.host() != ipaddress) + { + numbes++; + QDomElement mbe = pDoc->createElement("Backend"); + backends.appendChild(mbe); + mbe.setAttribute("type", "Slave"); + mbe.setAttribute("name", url.host()); + mbe.setAttribute("url" , url.toString(QUrl::RemovePath)); + } + } + sbes->Unlock(); + sbes->Release(); + } + + backends.setAttribute("count", numbes); + // Add Job Queue Entries QDomElement jobqueue = pDoc->createElement("JobQueue"); @@ -579,6 +658,20 @@ void HttpStatus::PrintStatus( QTextStream &os, QDomDocument *pDoc ) if (!node.isNull()) PrintScheduled( os, node.toElement()); + // Frontends + + node = docElem.namedItem( "Frontends" ); + + if (!node.isNull()) + PrintFrontends (os, node.toElement()); + + // Backends + + node = docElem.namedItem( "Backends" ); + + if (!node.isNull()) + PrintBackends (os, node.toElement()); + // Job Queue Entries ----------------------- node = docElem.namedItem( "JobQueue" ); @@ -586,7 +679,6 @@ void HttpStatus::PrintStatus( QTextStream &os, QDomDocument *pDoc ) if (!node.isNull()) PrintJobQueue( os, node.toElement()); - // Machine information --------------------- node = docElem.namedItem( "MachineInfo" ); @@ -881,6 +973,83 @@ int HttpStatus::PrintScheduled( QTextStream &os, QDomElement scheduled ) // ///////////////////////////////////////////////////////////////////////////// +int HttpStatus::PrintFrontends( QTextStream &os, QDomElement frontends ) +{ + if (frontends.isNull()) + return( 0 ); + + int nNumFES= frontends.attribute( "count", "0" ).toInt(); + + if (nNumFES < 1) + return( 0 ); + + + os << " \r\n\r\n"; + + return nNumFES; +} + +///////////////////////////////////////////////////////////////////////////// +// +///////////////////////////////////////////////////////////////////////////// + +int HttpStatus::PrintBackends( QTextStream &os, QDomElement backends ) +{ + if (backends.isNull()) + return( 0 ); + + int nNumBES= backends.attribute( "count", "0" ).toInt(); + + if (nNumBES < 1) + return( 0 ); + + + os << "
    \r\n" + << "

    Other Backends

    \r\n"; + + QDomNode node = backends.firstChild(); + while (!node.isNull()) + { + QDomElement e = node.toElement(); + + if (!e.isNull()) + { + QString type = e.attribute( "type", "" ); + QString name = e.attribute( "name" , "" ); + QString url = e.attribute( "url" , "" ); + os << type << ": " << name << " (Status page)
    "; + } + + node = node.nextSibling(); + } + + os << "
    \r\n\r\n"; + + return nNumBES; +} + +///////////////////////////////////////////////////////////////////////////// +// +///////////////////////////////////////////////////////////////////////////// + int HttpStatus::PrintJobQueue( QTextStream &os, QDomElement jobs ) { QString shortdateformat = gCoreContext->GetSetting("ShortDateFormat", "M/d"); diff --git a/mythtv/programs/mythbackend/httpstatus.h b/mythtv/programs/mythbackend/httpstatus.h index 09a64ee23cc..5e9d2f032ee 100644 --- a/mythtv/programs/mythbackend/httpstatus.h +++ b/mythtv/programs/mythbackend/httpstatus.h @@ -63,6 +63,8 @@ class HttpStatus : public HttpServerExtension void PrintStatus ( QTextStream &os, QDomDocument *pDoc ); int PrintEncoderStatus( QTextStream &os, QDomElement encoders ); int PrintScheduled ( QTextStream &os, QDomElement scheduled ); + int PrintFrontends ( QTextStream &os, QDomElement frontends ); + int PrintBackends ( QTextStream &os, QDomElement backends ); int PrintJobQueue ( QTextStream &os, QDomElement jobs ); int PrintMachineInfo ( QTextStream &os, QDomElement info ); int PrintMiscellaneousInfo ( QTextStream &os, QDomElement info ); diff --git a/mythtv/programs/mythbackend/main_helpers.h b/mythtv/programs/mythbackend/main_helpers.h index 511cfd6457e..d839d2197b6 100644 --- a/mythtv/programs/mythbackend/main_helpers.h +++ b/mythtv/programs/mythbackend/main_helpers.h @@ -1,3 +1,6 @@ +#ifndef _MAIN_HELPERS_H_ +#define _MAIN_HELPERS_H_ + // C++ headers #include #include @@ -12,6 +15,7 @@ bool setup_context(MythBackendCommandLineParser &cmdline); void cleanup(void); void upnp_rebuild(int); void showUsage(const MythBackendCommandLineParser &cmdlineparser, const QString &version); +void setupLogfile(void); bool openPidfile(ofstream &pidfs, const QString &pidfilename); bool setUser(const QString &username); int handle_command(const MythBackendCommandLineParser &cmdline); @@ -41,3 +45,4 @@ namespace }; } +#endif // _MAIN_HELPERS_H_ diff --git a/mythtv/programs/mythbackend/mainserver.cpp b/mythtv/programs/mythbackend/mainserver.cpp index 892170251df..258bd785f69 100644 --- a/mythtv/programs/mythbackend/mainserver.cpp +++ b/mythtv/programs/mythbackend/mainserver.cpp @@ -203,7 +203,7 @@ MainServer::MainServer(bool master, int port, masterBackendOverride = gCoreContext->GetNumSetting("MasterBackendOverride", 0); mythserver = new MythServer(); - if (!mythserver->listen(QHostAddress::Any, port)) + if (!mythserver->listen(QHostAddress::AnyIPv6, port)) { VERBOSE(VB_IMPORTANT, QString("Failed to bind port %1. Exiting.") .arg(port)); @@ -1604,8 +1604,7 @@ void MainServer::HandleQueryRecordings(QString type, PlaybackSock *pbs) if ((proginfo->GetHostname() == gCoreContext->GetHostName()) || (!slave && masterBackendOverride)) { - proginfo->SetPathname(QString("myth://") + ip + ':' + port + - '/' + proginfo->GetBasename()); + proginfo->SetPathname(gCoreContext->GenMythURL(ip,port,proginfo->GetBasename())); if (!proginfo->GetFilesize()) { QString tmpURL = GetPlaybackURL(proginfo); @@ -1669,10 +1668,9 @@ void MainServer::HandleQueryRecordings(QString type, PlaybackSock *pbs) backendPortMap[p->GetHostname()] = gCoreContext->GetSettingOnHost("BackendServerPort", p->GetHostname()); - p->SetPathname(QString("myth://") + - backendIpMap[p->GetHostname()] + ":" + - backendPortMap[p->GetHostname()] + "/" + - p->GetBasename()); + p->SetPathname(gCoreContext->GenMythURL(backendIpMap[p->GetHostname()], + backendPortMap[p->GetHostname()], + p->GetBasename())); } } @@ -1753,8 +1751,7 @@ void MainServer::HandleFillProgramInfo(QStringList &slist, PlaybackSock *pbs) if (playbackhost == gCoreContext->GetHostName()) pginfo.SetPathname(lpath); else - pginfo.SetPathname(QString("myth://") + ip + ":" + port + "/" + - pginfo.GetBasename()); + pginfo.SetPathname(gCoreContext->GenMythURL(ip,port,pginfo.GetBasename())); const QFileInfo info(lpath); pginfo.SetFilesize(info.size()); @@ -1953,10 +1950,12 @@ void MainServer::DeleteRecordedFiles(DeleteStruct *ds) StorageGroup sgroup(storagegroup); QString localFile = sgroup.FindFile(basename); - QString url = QString("myth://%1@%2:%3/%4").arg(storagegroup) - .arg(gCoreContext->GetSettingOnHost("BackendServerIP", hostname)) - .arg(gCoreContext->GetSettingOnHost("BackendServerPort", hostname)) - .arg(basename); + + QString url = gCoreContext->GenMythURL( + gCoreContext->GetSettingOnHost("BackendServerIP", hostname), + gCoreContext->GetSettingOnHost("BackendServerPort", hostname), + basename, + storagegroup); if ((((hostname == gCoreContext->GetHostName()) || (!localFile.isEmpty())) && @@ -3157,8 +3156,8 @@ void MainServer::HandleSGFileQuery(QStringList &sList, bool slaveUnreachable = false; - VERBOSE(VB_FILE, QString("HandleSGFileQuery: myth://%1@%2/%3") - .arg(groupname).arg(wantHost).arg(filename)); + VERBOSE(VB_FILE, QString("HandleSGFileQuery: %1") + .arg(gCoreContext->GenMythURL(wantHost,0,filename,groupname))); if ((wantHost.toLower() == gCoreContext->GetHostName().toLower()) || (wantHost == gCoreContext->GetSetting("BackendServerIP"))) diff --git a/mythtv/programs/mythbackend/mainserver.h b/mythtv/programs/mythbackend/mainserver.h index d539f76cc9e..f1e0d56b115 100644 --- a/mythtv/programs/mythbackend/mainserver.h +++ b/mythtv/programs/mythbackend/mainserver.h @@ -2,12 +2,12 @@ #define MAINSERVER_H_ #include +#include +#include #include #include #include #include -#include -#include #include using namespace std; diff --git a/mythtv/programs/mythbackend/mediaserver.cpp b/mythtv/programs/mythbackend/mediaserver.cpp index 10fef2a70b1..d63c61ca1a1 100644 --- a/mythtv/programs/mythbackend/mediaserver.cpp +++ b/mythtv/programs/mythbackend/mediaserver.cpp @@ -74,7 +74,7 @@ void MediaServer::Init(bool bIsMaster, bool bDisableUPnp /* = FALSE */) if (!m_pHttpServer->isListening()) { - if (!m_pHttpServer->listen(QHostAddress::Any, nPort)) + if (!m_pHttpServer->listen(QHostAddress::AnyIPv6, nPort)) { VERBOSE(VB_IMPORTANT, "MediaServer::HttpServer Create Error"); delete m_pHttpServer; diff --git a/mythtv/programs/mythbackend/scheduler.cpp b/mythtv/programs/mythbackend/scheduler.cpp index d796ca01591..e20a0acf594 100644 --- a/mythtv/programs/mythbackend/scheduler.cpp +++ b/mythtv/programs/mythbackend/scheduler.cpp @@ -335,15 +335,16 @@ static bool comp_timechannel(RecordingInfo *a, RecordingInfo *b) return a->GetChanNum() < b->GetChanNum(); } -bool Scheduler::FillRecordList(bool doLock) +bool Scheduler::FillRecordList(void) { schedMoveHigher = (bool)gCoreContext->GetNumSetting("SchedMoveHigher"); schedTime = QDateTime::currentDateTime(); VERBOSE(VB_SCHEDULE, "BuildWorkList..."); BuildWorkList(); - if (doLock) - schedLock.unlock(); + + schedLock.unlock(); + VERBOSE(VB_SCHEDULE, "AddNewRecords..."); AddNewRecords(); VERBOSE(VB_SCHEDULE, "AddNotListed..."); @@ -364,8 +365,8 @@ bool Scheduler::FillRecordList(bool doLock) SchedPreserveLiveTV(); VERBOSE(VB_SCHEDULE, "ClearListMaps..."); ClearListMaps(); - if (doLock) - schedLock.lock(); + + schedLock.lock(); VERBOSE(VB_SCHEDULE, "Sort by time..."); SORT_RECLIST(worklist, comp_redundant); @@ -418,6 +419,8 @@ void Scheduler::FillRecordListFromDB(int recordid) return; } + QMutexLocker locker(&schedLock); + gettimeofday(&fillstart, NULL); UpdateMatches(recordid); gettimeofday(&fillend, NULL); @@ -425,7 +428,7 @@ void Scheduler::FillRecordListFromDB(int recordid) (fillend.tv_usec - fillstart.tv_usec)) / 1000000.0; gettimeofday(&fillstart, NULL); - FillRecordList(false); + FillRecordList(); gettimeofday(&fillend, NULL); placeTime = ((fillend.tv_sec - fillstart.tv_sec ) * 1000000 + (fillend.tv_usec - fillstart.tv_usec)) / 1000000.0; @@ -1694,35 +1697,8 @@ bool Scheduler::IsBusyRecording(const RecordingInfo *rcinfo) return false; } -void Scheduler::run(void) +void Scheduler::OldRecordedFixups(void) { - threadRegister("Scheduler"); - int prerollseconds = 0; - int wakeThreshold = gCoreContext->GetNumSetting("WakeUpThreshold", 300); - int secsleft; - EncoderLink *nexttv = NULL; - - RecordingInfo *nextRecording = NULL; - QDateTime nextrectime; - QString schedid; - - QDateTime curtime; - QDateTime lastupdate = QDateTime::currentDateTime().addDays(-1); - QDateTime lastSleepCheck = QDateTime::currentDateTime().addDays(-1); - - bool blockShutdown = gCoreContext->GetNumSetting("blockSDWUwithoutClient", 1); - QDateTime idleSince = QDateTime(); - int idleTimeoutSecs = 0; - int idleWaitForRecordingTime = 0; - bool firstRun = true; - - QString sysEventKey; - int sysEventSecs[5] = { 120, 90, 60, 30, 0 }; - QListsysEvents[4]; - - struct timeval fillstart, fillend; - float matchTime, placeTime; - MSqlQuery query(dbConn); // Mark anything that was recording as aborted. @@ -1759,13 +1735,19 @@ void Scheduler::run(void) " endtime < (NOW() - INTERVAL 8 HOUR)"); if (!query.exec()) MythDB::DBError("UpdateFuture", query); +} +void Scheduler::run(void) +{ + threadRegister("Scheduler"); // Notify constructor that we're actually running { QMutexLocker lockit(&schedLock); reschedWait.wakeAll(); } + OldRecordedFixups(); + // wait for slaves to connect sleep(3); @@ -1774,631 +1756,847 @@ void Scheduler::run(void) reschedQueue.clear(); reschedQueue.enqueue(-1); - RecIter startIter = reclist.begin(); + int prerollseconds = 0; + int wakeThreshold = 300; + int idleTimeoutSecs = 0; + int idleWaitForRecordingTime = 15; // in minutes + bool blockShutdown = + gCoreContext->GetNumSetting("blockSDWUwithoutClient", 1); + bool firstRun = true; + QDateTime lastSleepCheck = QDateTime::currentDateTime().addDays(-1); + RecIter startIter = reclist.begin(); + QDateTime idleSince = QDateTime(); + int maxSleep = 60000; // maximum sleep time in milliseconds + int schedRunTime = 30; // max scheduler run time in seconds while (doRun) { - curtime = QDateTime::currentDateTime(); + QDateTime curtime = QDateTime::currentDateTime(); bool statuschanged = false; + int secs_to_next = (startIter != reclist.end()) ? + curtime.secsTo((*startIter)->GetRecordingStartTime()) : 60*60; - if ((startIter != reclist.end() && - curtime.secsTo((*startIter)->GetRecordingStartTime()) < 30)) + // If we're about to start a recording don't do any reschedules... + // instead sleep for a bit + if (secs_to_next < (schedRunTime + 2)) { - reschedWait.wait(&schedLock, 1000); + int msecs = CalcTimeToNextHandleRecordingEvent( + curtime, startIter, reclist, prerollseconds, maxSleep); + VERBOSE(VB_SCHEDULE, QString( + "sleeping for %1 ms (s2n: %2 sr: %3)") + .arg(msecs).arg(secs_to_next).arg(schedRunTime)); + if (msecs < 100) + (void) ::usleep(msecs * 1000); + else + reschedWait.wait(&schedLock, msecs); } else { if (reschedQueue.empty()) - reschedWait.wait(&schedLock, 1000); - if (!doRun) - break; - - if (!reschedQueue.empty()) { - // We might have been inactive for a long time, so make - // sure our DB connection is fresh before continuing. - dbConn = MSqlQuery::SchedCon(); + int sched_sleep = (secs_to_next - schedRunTime - 1) * 1000; + sched_sleep = min(sched_sleep, maxSleep); + VERBOSE(VB_SCHEDULE, + QString("sleeping for %1 ms (interuptable)") + .arg(sched_sleep)); + reschedWait.wait(&schedLock, sched_sleep); + if (!doRun) + break; + } - gettimeofday(&fillstart, NULL); - QString msg; + QTime t; t.start(); + if (!reschedQueue.empty() && HandleReschedule()) + { + statuschanged = true; + startIter = reclist.begin(); - bool deleteFuture = false; + // The master backend is a long lived program, so + // we reload some key settings on each reschedule. + prerollseconds = + gCoreContext->GetNumSetting("RecordPreRoll", 0); + wakeThreshold = + gCoreContext->GetNumSetting("WakeUpThreshold", 300); + idleTimeoutSecs = + gCoreContext->GetNumSetting("idleTimeoutSecs", 0); + idleWaitForRecordingTime = + gCoreContext->GetNumSetting("idleWaitForRecordingTime", 15); + } + int e = t.elapsed(); + if (e > 0) + { + schedRunTime = (firstRun) ? 0 : schedRunTime; + schedRunTime = + max((int)(((e + 999) / 1000) * 1.5f), schedRunTime); + VERBOSE(VB_IMPORTANT, QString("schedRunTime: %1 seconds (e %2)") + .arg(schedRunTime).arg(e)); + } - while (!reschedQueue.empty()) - { - int recordid = reschedQueue.dequeue(); + if (firstRun) + { + blockShutdown &= HandleRunSchedulerStartup( + prerollseconds, idleWaitForRecordingTime); + firstRun = false; + + // HandleRunSchedulerStartup releases the schedLock so the + // reclist may have changed. If it has go to top of loop + // and update secs_to_next... + if (reclist_changed) + continue; + } - VERBOSE(VB_GENERAL, - QString("Reschedule requested for id %1.") - .arg(recordid)); + // Unless a recording is about to start, check for slaves + // that can be put to sleep if it has been at least five + // minutes since we last put slaves to sleep. + curtime = QDateTime::currentDateTime(); + secs_to_next = (startIter != reclist.end()) ? + curtime.secsTo((*startIter)->GetRecordingStartTime()) : 60*60; + if ((secs_to_next > schedRunTime * 1.5f) && + (lastSleepCheck.secsTo(curtime) > 300)) + { + PutInactiveSlavesToSleep(); + lastSleepCheck = QDateTime::currentDateTime(); + } + } - if (recordid != 0) - { - if (recordid == -1) - reschedQueue.clear(); - - deleteFuture = true; - schedLock.unlock(); - recordmatchLock.lock(); - UpdateMatches(recordid); - recordmatchLock.unlock(); - schedLock.lock(); - } - } + // Skip past recordings that are already history + // (i.e. AddHistory() has been called setting oldrecstatus) + for ( ; startIter != reclist.end(); ++startIter) + { + if ((*startIter)->GetRecordingStatus() != + (*startIter)->oldrecstatus) + { + break; + } + } - // Delete future oldrecorded entries that no longer - // match any potential recordings. - if (deleteFuture) - { - query.prepare("DELETE oldrecorded FROM oldrecorded " - "LEFT JOIN recordmatch ON " - " recordmatch.chanid = " - " oldrecorded.chanid AND " - " recordmatch.starttime = " - " oldrecorded.starttime " - "WHERE oldrecorded.future > 0 AND " - " recordmatch.recordid IS NULL"); - if (!query.exec()) - MythDB::DBError("DeleteFuture", query); - } + // Start any recordings that are due to be started + // & call RecordPending for recordings due to start in 30 seconds + // & handle rsTuning updates + bool done = false; + for (RecIter it = startIter; it != reclist.end() && !done; ++it) + done = HandleRecording(**it, statuschanged, prerollseconds); - gettimeofday(&fillend, NULL); + /// Wake any slave backends that need waking + curtime = QDateTime::currentDateTime(); + for (RecIter it = startIter; it != reclist.end() && !done; ++it) + { + int secsleft = curtime.secsTo((*it)->GetRecordingStartTime()); + if ((secsleft - prerollseconds) <= wakeThreshold) + HandleWakeSlave(**it, prerollseconds); + else + break; + } - matchTime = ((fillend.tv_sec - fillstart.tv_sec ) * 1000000 + - (fillend.tv_usec - fillstart.tv_usec)) / 1000000.0; + if (statuschanged) + { + MythEvent me("SCHEDULE_CHANGE"); + gCoreContext->dispatch(me); + idleSince = QDateTime(); + } - gettimeofday(&fillstart, NULL); - bool worklistused = FillRecordList(true); - gettimeofday(&fillend, NULL); - if (worklistused) - { - UpdateNextRecord(); - PrintList(); - } - else - { - VERBOSE(VB_GENERAL, "Reschedule interrupted, will retry"); - reschedQueue.enqueue(0);; - continue; - } + // if idletimeout is 0, the user disabled the auto-shutdown feature + if ((idleTimeoutSecs > 0) && (m_mainServer != NULL)) + { + HandleIdleShutdown(blockShutdown, idleSince, prerollseconds, + idleTimeoutSecs, idleWaitForRecordingTime); + } + } + threadDeregister(); +} - placeTime = ((fillend.tv_sec - fillstart.tv_sec ) * 1000000 + - (fillend.tv_usec - fillstart.tv_usec)) / 1000000.0; +int Scheduler::CalcTimeToNextHandleRecordingEvent( + const QDateTime &curtime, + RecConstIter startIter, const RecList &reclist, + int prerollseconds, int max_sleep /*ms*/) +{ + if (startIter == reclist.end()) + return max_sleep; - msg.sprintf("Scheduled %d items in " - "%.1f = %.2f match + %.2f place", - (int)reclist.size(), - matchTime + placeTime, matchTime, placeTime); + int msecs = max_sleep; + for (RecConstIter i = startIter; i != reclist.end() && (msecs > 0); ++i) + { + // Check on recordings that we've told to start, but have + // not yet started every second or so. + if ((*i)->GetRecordingStatus() == rsTuning) + { + msecs = min(msecs, 1000); + continue; + } - VERBOSE(VB_GENERAL, msg); + // These recordings have already been handled.. + if ((*i)->GetRecordingStatus() == (*i)->oldrecstatus) + continue; - fsInfoCacheFillTime = - QDateTime::currentDateTime().addSecs(-1000); + int secs_to_next = curtime.secsTo((*i)->GetRecordingStartTime()); - lastupdate = curtime; - startIter = reclist.begin(); - statuschanged = true; + if (!recPendingList[(*i)->MakeUniqueSchedulerKey()]) + secs_to_next -= 30; - // Determine if the user wants us to start recording early - // and by how many seconds - prerollseconds = gCoreContext->GetNumSetting("RecordPreRoll"); + if (secs_to_next < 0) + { + msecs = 0; + break; + } - idleTimeoutSecs = - gCoreContext->GetNumSetting("idleTimeoutSecs", 0); - idleWaitForRecordingTime = - gCoreContext->GetNumSetting("idleWaitForRecordingTime", 15); + // This is what normally breaks us out of the loop... + if (secs_to_next > max_sleep) + { + msecs = min(msecs, max_sleep); + break; + } - if (firstRun) - { - firstRun = false; + if (secs_to_next > 31) + { + msecs = min(msecs, 30 * 1000); + continue; + } - //the parameter given to the startup_cmd. "user" means a user - // started the BE, 'auto' means it was started automatically - QString startupParam = "user"; + if ((secs_to_next-1) * 1000 > msecs) + continue; - // find the first recording that WILL be recorded - RecIter firstRunIter = reclist.begin(); - for ( ; firstRunIter != reclist.end(); ++firstRunIter) - if ((*firstRunIter)->GetRecordingStatus() == rsWillRecord) - break; + if (secs_to_next < 15) + { + QTime st = (*i)->GetRecordingStartTime().time(); + int tmp = curtime.time().msecsTo(st); + tmp = (tmp < 0) ? tmp + 86400000 : tmp; + msecs = (tmp > 15*1000) ? 0 : min(msecs, tmp); + } + else + { + msecs = min(msecs, (secs_to_next-1) * 1000); + } + } - // have we been started automatically? - if (WasStartedAutomatically() || - ((firstRunIter != reclist.end()) && - ((curtime.secsTo((*firstRunIter)->GetRecordingStartTime()) - prerollseconds) - < (idleWaitForRecordingTime * 60)))) - { - VERBOSE(VB_IMPORTANT, "AUTO-Startup assumed"); - startupParam = "auto"; + return min(msecs, max_sleep); +} - // Since we've started automatically, don't wait for - // client to connect before allowing shutdown. - blockShutdown = false; - } - else - { - VERBOSE(VB_IMPORTANT, "Seem to be woken up by USER"); - } +bool Scheduler::HandleReschedule(void) +{ + // We might have been inactive for a long time, so make + // sure our DB connection is fresh before continuing. + dbConn = MSqlQuery::SchedCon(); - QString startupCommand = - gCoreContext->GetSetting("startupCommand", ""); - if (!startupCommand.isEmpty()) - { - startupCommand.replace("$status", startupParam); - schedLock.unlock(); - myth_system(startupCommand); - schedLock.lock(); - if (reclist_changed) - continue; - } - } + struct timeval fillstart; + gettimeofday(&fillstart, NULL); + QString msg; + bool deleteFuture = false; - PutInactiveSlavesToSleep(); - lastSleepCheck = QDateTime::currentDateTime(); + while (!reschedQueue.empty()) + { + int recordid = reschedQueue.dequeue(); - // Write changed entries to oldrecorded. - RecIter it = reclist.begin(); - for ( ; it != reclist.end(); ++it) - { - RecordingInfo *p = *it; - if (p->GetRecordingStatus() != p->oldrecstatus) - { - if (p->GetRecordingEndTime() < schedTime) - p->AddHistory(false, false, false); - else if (p->GetRecordingStartTime() < schedTime && - p->GetRecordingStatus() != rsWillRecord) - p->AddHistory(false, false, false); - else - p->AddHistory(false, false, true); - } - else if (p->future) - { - // Force a non-future, oldrecorded entry to - // get written when the time comes. - p->oldrecstatus = rsUnknown; - } - p->future = false; - } + VERBOSE(VB_GENERAL, + QString("Reschedule requested for id %1.") + .arg(recordid)); - SendMythSystemEvent("SCHEDULER_RAN"); - } + if (recordid != 0) + { + if (recordid == -1) + reschedQueue.clear(); + + deleteFuture = true; + schedLock.unlock(); + recordmatchLock.lock(); + UpdateMatches(recordid); + recordmatchLock.unlock(); + schedLock.lock(); } + } - for ( ; startIter != reclist.end(); ++startIter) - if ((*startIter)->GetRecordingStatus() != - (*startIter)->oldrecstatus) - break; + // Delete future oldrecorded entries that no longer + // match any potential recordings. + if (deleteFuture) + { + MSqlQuery query(dbConn); + query.prepare("DELETE oldrecorded FROM oldrecorded " + "LEFT JOIN recordmatch ON " + " recordmatch.chanid = oldrecorded.chanid AND " + " recordmatch.starttime = oldrecorded.starttime " + "WHERE oldrecorded.future > 0 AND " + " recordmatch.recordid IS NULL"); + if (!query.exec()) + MythDB::DBError("DeleteFuture", query); + } - curtime = QDateTime::currentDateTime(); + struct timeval fillend; + gettimeofday(&fillend, NULL); + + float matchTime = ((fillend.tv_sec - fillstart.tv_sec ) * 1000000 + + (fillend.tv_usec - fillstart.tv_usec)) / 1000000.0; + + gettimeofday(&fillstart, NULL); + bool worklistused = FillRecordList(); + gettimeofday(&fillend, NULL); + if (worklistused) + { + UpdateNextRecord(); + PrintList(); + } + else + { + VERBOSE(VB_GENERAL, "Reschedule interrupted, will retry"); + reschedQueue.enqueue(0); + return false; + } + + float placeTime = ((fillend.tv_sec - fillstart.tv_sec ) * 1000000 + + (fillend.tv_usec - fillstart.tv_usec)) / 1000000.0; + + msg.sprintf("Scheduled %d items in " + "%.1f = %.2f match + %.2f place", + (int)reclist.size(), + matchTime + placeTime, matchTime, placeTime); + + VERBOSE(VB_GENERAL, msg); + + fsInfoCacheFillTime = + QDateTime::currentDateTime().addSecs(-1000); - // About every 5 minutes check for slaves that can be put to sleep - if (lastSleepCheck.secsTo(curtime) > 300) + // Write changed entries to oldrecorded. + RecIter it = reclist.begin(); + for ( ; it != reclist.end(); ++it) + { + RecordingInfo *p = *it; + if (p->GetRecordingStatus() != p->oldrecstatus) { - PutInactiveSlavesToSleep(); - lastSleepCheck = QDateTime::currentDateTime(); + if (p->GetRecordingEndTime() < schedTime) + p->AddHistory(false, false, false); + else if (p->GetRecordingStartTime() < schedTime && + p->GetRecordingStatus() != rsWillRecord) + p->AddHistory(false, false, false); + else + p->AddHistory(false, false, true); } - - // Go through the list of recordings starting in the next few minutes - // and wakeup any slaves that are asleep - RecIter recIter = startIter; - for ( ; schedulingEnabled && recIter != reclist.end(); ++recIter) + else if (p->future) { - nextRecording = *recIter; - nextrectime = nextRecording->GetRecordingStartTime(); - secsleft = curtime.secsTo(nextrectime); + // Force a non-future, oldrecorded entry to + // get written when the time comes. + p->oldrecstatus = rsUnknown; + } + p->future = false; + } - if ((secsleft - prerollseconds) > wakeThreshold) - break; + SendMythSystemEvent("SCHEDULER_RAN"); - if (m_tvList->find(nextRecording->GetCardID()) == m_tvList->end()) - continue; + return true; +} - sysEventKey = QString("%1:%2").arg(nextRecording->GetChanID()) - .arg(nextrectime.toString(Qt::ISODate)); - int i = 0; - bool pendingEventSent = false; - while (sysEventSecs[i] != 0) - { - if ((secsleft <= sysEventSecs[i]) && - (!sysEvents[i].contains(sysEventKey))) - { - if (!pendingEventSent) - SendMythSystemRecEvent - (QString("REC_PENDING SECS %1").arg(secsleft), - nextRecording); +bool Scheduler::HandleRunSchedulerStartup( + int prerollseconds, int idleWaitForRecordingTime) +{ + bool blockShutdown = true; - sysEvents[i].append(sysEventKey); - pendingEventSent = true; - } - i++; - } + // The parameter given to the startup_cmd. "user" means a user + // probably started the backend process, "auto" means it was + // started probably automatically. + QString startupParam = "user"; - nexttv = (*m_tvList)[nextRecording->GetCardID()]; + // find the first recording that WILL be recorded + RecIter firstRunIter = reclist.begin(); + for ( ; firstRunIter != reclist.end(); ++firstRunIter) + { + if ((*firstRunIter)->GetRecordingStatus() == rsWillRecord) + break; + } - if (nexttv->IsAsleep() && !nexttv->IsWaking()) - { - VERBOSE(VB_SCHEDULE, QString("Slave Backend %1 is being " - "awakened to record: %2") - .arg(nexttv->GetHostName()) - .arg(nextRecording->GetTitle())); + // have we been started automatically? + QDateTime curtime = QDateTime::currentDateTime(); + if (WasStartedAutomatically() || + ((firstRunIter != reclist.end()) && + ((curtime.secsTo((*firstRunIter)->GetRecordingStartTime()) - + prerollseconds) < (idleWaitForRecordingTime * 60)))) + { + VERBOSE(VB_GENERAL, LOC + "AUTO-Startup assumed"); + startupParam = "auto"; - if (!WakeUpSlave(nexttv->GetHostName())) - { - reschedQueue.enqueue(0);; - continue; - } - } - else if ((nexttv->IsWaking()) && - ((secsleft - prerollseconds) < 210) && - (nexttv->GetSleepStatusTime().secsTo(curtime) < 300) && - (nexttv->GetLastWakeTime().secsTo(curtime) > 10)) + // Since we've started automatically, don't wait for + // client to connect before allowing shutdown. + blockShutdown = false; + } + else + { + VERBOSE(VB_GENERAL, LOC + "Seem to be woken up by USER"); + } + + QString startupCommand = gCoreContext->GetSetting("startupCommand", ""); + if (!startupCommand.isEmpty()) + { + startupCommand.replace("$status", startupParam); + schedLock.unlock(); + myth_system(startupCommand); + schedLock.lock(); + } + + return blockShutdown; +} + +// If a recording is about to start on a backend in a few minutes, wake it... +void Scheduler::HandleWakeSlave(RecordingInfo &ri, int prerollseconds) +{ + static const int sysEventSecs[5] = { 120, 90, 60, 30, 0 }; + QString sysEventKey; + QList sysEvents[4]; + + QDateTime curtime = QDateTime::currentDateTime(); + QDateTime nextrectime = ri.GetRecordingStartTime(); + int secsleft = curtime.secsTo(nextrectime); + + QMap::iterator tvit = m_tvList->find(ri.GetCardID()); + if (tvit == m_tvList->end()) + return; + + sysEventKey = QString("%1:%2").arg(ri.GetChanID()) + .arg(nextrectime.toString(Qt::ISODate)); + + int i = 0; + bool pendingEventSent = false; + while (sysEventSecs[i] != 0) + { + if ((secsleft <= sysEventSecs[i]) && + (!sysEvents[i].contains(sysEventKey))) + { + if (!pendingEventSent) { - VERBOSE(VB_SCHEDULE, QString("Slave Backend %1 not " - "available yet, trying to wake it up again.") - .arg(nexttv->GetHostName())); - if (!WakeUpSlave(nexttv->GetHostName(), false)) - { - reschedQueue.enqueue(0);; - continue; - } + SendMythSystemRecEvent( + QString("REC_PENDING SECS %1").arg(secsleft), &ri); } - else if ((nexttv->IsWaking()) && - ((secsleft - prerollseconds) < 150) && - (nexttv->GetSleepStatusTime().secsTo(curtime) < 300)) - { - VERBOSE(VB_SCHEDULE, - QString("WARNING: Slave Backend %1 has NOT come " - "back from sleep yet in 150 seconds. Setting " - "slave status to unknown and attempting " - "to reschedule around its tuners.") - .arg(nexttv->GetHostName())); - - QMap::Iterator enciter = - m_tvList->begin(); - for (; enciter != m_tvList->end(); ++enciter) - { - EncoderLink *enc = *enciter; - if (enc->GetHostName() == nexttv->GetHostName()) - enc->SetSleepStatus(sStatus_Undefined); - } - reschedQueue.enqueue(0);; - } + sysEvents[i].append(sysEventKey); + pendingEventSent = true; } + i++; + } + + EncoderLink *nexttv = *tvit; - for ( recIter = startIter ; recIter != reclist.end(); ++recIter) + if (nexttv->IsAsleep() && !nexttv->IsWaking()) + { + VERBOSE(VB_SCHEDULE, LOC + + QString("Slave Backend %1 is being awakened to record: %2") + .arg(nexttv->GetHostName()).arg(ri.GetTitle())); + + if (!WakeUpSlave(nexttv->GetHostName())) + reschedQueue.enqueue(0); + } + else if ((nexttv->IsWaking()) && + ((secsleft - prerollseconds) < 210) && + (nexttv->GetSleepStatusTime().secsTo(curtime) < 300) && + (nexttv->GetLastWakeTime().secsTo(curtime) > 10)) + { + VERBOSE(VB_SCHEDULE, LOC + + QString("Slave Backend %1 not available yet, " + "trying to wake it up again.") + .arg(nexttv->GetHostName())); + + if (!WakeUpSlave(nexttv->GetHostName(), false)) + reschedQueue.enqueue(0); + } + else if ((nexttv->IsWaking()) && + ((secsleft - prerollseconds) < 150) && + (nexttv->GetSleepStatusTime().secsTo(curtime) < 300)) + { + VERBOSE(VB_GENERAL, LOC_WARN + + QString("Slave Backend %1 has NOT come " + "back from sleep yet in 150 seconds. Setting " + "slave status to unknown and attempting " + "to reschedule around its tuners.") + .arg(nexttv->GetHostName())); + + QMap::iterator it = m_tvList->begin(); + for (; it != m_tvList->end(); ++it) { - QString msg, details; - int fsID = -1; + if ((*it)->GetHostName() == nexttv->GetHostName()) + (*it)->SetSleepStatus(sStatus_Undefined); + } - nextRecording = *recIter; + reschedQueue.enqueue(0); + } +} - if (nextRecording->GetRecordingStatus() != rsWillRecord) - { - if (nextRecording->GetRecordingStatus() != - nextRecording->oldrecstatus && - nextRecording->GetRecordingStartTime() <= curtime) - nextRecording->AddHistory(false); - continue; - } +bool Scheduler::HandleRecording( + RecordingInfo &ri, bool &statuschanged, int prerollseconds) +{ + if (ri.GetRecordingStatus() == rsTuning) + { + HandleTuning(ri, statuschanged); + return false; + } - nextrectime = nextRecording->GetRecordingStartTime(); - secsleft = curtime.secsTo(nextrectime); - schedid = nextRecording->MakeUniqueSchedulerKey(); + if (ri.GetRecordingStatus() != rsWillRecord) + { + if (ri.GetRecordingStatus() != ri.oldrecstatus && + ri.GetRecordingStartTime() <= QDateTime::currentDateTime()) + { + ri.AddHistory(false); + } + return false; + } - if (secsleft - prerollseconds < 60) - { - if (!recPendingList.contains(schedid)) - { - recPendingList[schedid] = false; + QDateTime nextrectime = ri.GetRecordingStartTime(); + QDateTime curtime = QDateTime::currentDateTime(); + int secsleft = curtime.secsTo(nextrectime); + QString schedid = ri.MakeUniqueSchedulerKey(); - livetvTime = (livetvTime < nextrectime) ? - nextrectime : livetvTime; + if (secsleft - prerollseconds < 60) + { + if (!recPendingList.contains(schedid)) + { + recPendingList[schedid] = false; + + livetvTime = (livetvTime < nextrectime) ? + nextrectime : livetvTime; - reschedQueue.enqueue(0); - } - } + reschedQueue.enqueue(0); + } + } - if (secsleft - prerollseconds > 35) - break; + if (secsleft - prerollseconds > 35) + return true; - if (m_tvList->find(nextRecording->GetCardID()) == m_tvList->end()) - { - msg = QString("invalid cardid (%1) for %2") - .arg(nextRecording->GetCardID()) - .arg(nextRecording->GetTitle()); - VERBOSE(VB_GENERAL, msg); - - nextRecording->SetRecordingStatus(rsTunerBusy); - nextRecording->AddHistory(false); - reschedQueue.enqueue(0); - statuschanged = true; - continue; - } + QMap::iterator tvit = m_tvList->find(ri.GetCardID()); + if (tvit == m_tvList->end()) + { + QString msg = QString("Invalid cardid (%1) for %2") + .arg(ri.GetCardID()).arg(ri.GetTitle()); + VERBOSE(VB_GENERAL, LOC + msg); - nexttv = (*m_tvList)[nextRecording->GetCardID()]; - // cerr << "nexttv = " << nextRecording->GetCardID(); - // cerr << " title: " << nextRecording->GetTitle() << endl; + ri.SetRecordingStatus(rsTunerBusy); + ri.AddHistory(true); + statuschanged = true; + return false; + } - if (nexttv->IsTunerLocked()) - { - msg = QString("SUPPRESSED recording \"%1\" on channel: " + EncoderLink *nexttv = *tvit; + + if (nexttv->IsTunerLocked()) + { + QString msg = QString("SUPPRESSED recording \"%1\" on channel: " "%2 on cardid: %3, sourceid %4. Tuner " "is locked by an external application.") - .arg(nextRecording->GetTitle()) - .arg(nextRecording->GetChanID()) - .arg(nextRecording->GetCardID()) - .arg(nextRecording->GetSourceID()); - QByteArray amsg = msg.toLocal8Bit(); - VERBOSE(VB_GENERAL, amsg.constData()); - - nextRecording->SetRecordingStatus(rsTunerBusy); - nextRecording->AddHistory(false); - reschedQueue.enqueue(0); - statuschanged = true; - continue; - } + .arg(ri.GetTitle()) + .arg(ri.GetChanID()) + .arg(ri.GetCardID()) + .arg(ri.GetSourceID()); + QByteArray amsg = msg.toLocal8Bit(); + VERBOSE(VB_GENERAL, amsg.constData()); + + ri.SetRecordingStatus(rsTunerBusy); + ri.AddHistory(true); + statuschanged = true; + return false; + } + + if ((prerollseconds > 0) && !IsBusyRecording(&ri)) + { + // Will use pre-roll settings only if no other + // program is currently being recorded + secsleft -= prerollseconds; + } - if (!IsBusyRecording(nextRecording)) + //VERBOSE(VB_GENERAL, secsleft << " seconds until " + //<< ri.GetTitle()); + + if (secsleft > 30) + return false; + + if (nexttv->IsWaking()) + { + if (secsleft > 0) + { + VERBOSE(VB_SCHEDULE, + QString("WARNING: Slave Backend %1 has NOT come " + "back from sleep yet. Recording can " + "not begin yet for: %2") + .arg(nexttv->GetHostName()) + .arg(ri.GetTitle())); + } + else if (nexttv->GetLastWakeTime().secsTo(curtime) > 300) + { + VERBOSE(VB_SCHEDULE, + QString("WARNING: Slave Backend %1 has NOT come " + "back from sleep yet. Setting slave " + "status to unknown and attempting " + "to reschedule around its tuners.") + .arg(nexttv->GetHostName())); + + QMap::Iterator enciter = + m_tvList->begin(); + for (; enciter != m_tvList->end(); ++enciter) { - // Will use pre-roll settings only if no other - // program is currently being recorded - secsleft -= prerollseconds; + EncoderLink *enc = *enciter; + if (enc->GetHostName() == nexttv->GetHostName()) + enc->SetSleepStatus(sStatus_Undefined); } - //VERBOSE(VB_GENERAL, secsleft << " seconds until " << nextRecording->GetTitle()); + reschedQueue.enqueue(0); + } - if (secsleft > 30) - continue; + return false; + } - if (nexttv->IsWaking()) - { - if (secsleft > 0) - { - VERBOSE(VB_SCHEDULE, - QString("WARNING: Slave Backend %1 has NOT come " - "back from sleep yet. Recording can " - "not begin yet for: %2") - .arg(nexttv->GetHostName()) - .arg(nextRecording->GetTitle())); - } - else if (nexttv->GetLastWakeTime().secsTo(curtime) > 300) - { - VERBOSE(VB_SCHEDULE, - QString("WARNING: Slave Backend %1 has NOT come " - "back from sleep yet. Setting slave " - "status to unknown and attempting " - "to reschedule around its tuners.") - .arg(nexttv->GetHostName())); - - QMap::Iterator enciter = - m_tvList->begin(); - for (; enciter != m_tvList->end(); ++enciter) - { - EncoderLink *enc = *enciter; - if (enc->GetHostName() == nexttv->GetHostName()) - enc->SetSleepStatus(sStatus_Undefined); - } + int fsID = -1; + if (ri.GetPathname().isEmpty()) + { + QString recording_dir; + fsID = FillRecordingDir( + ri.GetTitle(), + ri.GetHostname(), + ri.GetStorageGroup(), + ri.GetRecordingStartTime(), + ri.GetRecordingEndTime(), + ri.GetCardID(), + recording_dir, + reclist); + ri.SetPathname(recording_dir); + } - reschedQueue.enqueue(0); - } + if (!recPendingList[schedid]) + { + nexttv->RecordPending(&ri, max(secsleft, 0), + hasLaterList.contains(schedid)); + recPendingList[schedid] = true; + } - continue; - } + if (secsleft > 0) + return false; - if (nextRecording->GetPathname().isEmpty()) - { - QString recording_dir; - fsID = FillRecordingDir( - nextRecording->GetTitle(), - nextRecording->GetHostname(), - nextRecording->GetStorageGroup(), - nextRecording->GetRecordingStartTime(), - nextRecording->GetRecordingEndTime(), - nextRecording->GetCardID(), - recording_dir, - reclist); - nextRecording->SetPathname(recording_dir); - } + QDateTime recstartts = mythCurrentDateTime().addSecs(30); + recstartts.setTime( + QTime(recstartts.time().hour(), recstartts.time().minute())); + ri.SetRecordingStartTime(recstartts); - if (!recPendingList[schedid]) - { - nexttv->RecordPending(nextRecording, max(secsleft, 0), - hasLaterList.contains(schedid)); - recPendingList[schedid] = true; - } + QString details = QString("%1: channel %2 on cardid %3, sourceid %4") + .arg(ri.toString(ProgramInfo::kTitleSubtitle)) + .arg(ri.GetChanID()) + .arg(ri.GetCardID()) + .arg(ri.GetSourceID()); - if (secsleft > -2) - continue; + RecStatusTypes recStatus = rsOffLine; + if (schedulingEnabled && nexttv->IsConnected()) + { + if (ri.GetRecordingStatus() == rsWillRecord) + { + recStatus = nexttv->StartRecording(&ri); + ri.AddHistory(false); - QDateTime recstartts = mythCurrentDateTime().addSecs(30); - recstartts.setTime - (QTime(recstartts.time().hour(), recstartts.time().minute())); - nextRecording->SetRecordingStartTime(recstartts); + // activate auto expirer + if (m_expirer) + m_expirer->Update(ri.GetCardID(), fsID, true); + } + } - details = QString("%1: channel %2 on cardid %3, sourceid %4") - .arg(nextRecording->toString(ProgramInfo::kTitleSubtitle)) - .arg(nextRecording->GetChanID()) - .arg(nextRecording->GetCardID()) - .arg(nextRecording->GetSourceID()); + HandleRecordingStatusChange(ri, recStatus, details); + statuschanged = true; - if (schedulingEnabled && nexttv->IsConnected()) - { - nextRecording->SetRecordingStatus - (nexttv->StartRecording(nextRecording)); - nextRecording->SetReactivated(false); + return false; +} - nextRecording->AddHistory(false); - if (m_expirer) - { - // activate auto expirer - m_expirer->Update(nextRecording->GetCardID(), fsID, true); - } - } - else - nextRecording->SetRecordingStatus(rsOffLine); - bool doSchedAfterStart = - (nextRecording->GetRecordingStatus() != rsRecording && - nextRecording->GetRecordingStatus() != rsTuning) || - schedAfterStartMap[nextRecording->GetRecordingRuleID()] || - (nextRecording->GetParentRecordingRuleID() && - schedAfterStartMap[nextRecording->GetParentRecordingRuleID()]); - nextRecording->AddHistory(false); - if (doSchedAfterStart) - reschedQueue.enqueue(0); - - statuschanged = true; - - bool is_rec = (nextRecording->GetRecordingStatus() == rsRecording || - nextRecording->GetRecordingStatus() == rsTuning); - msg = is_rec ? - QString("Started recording") : - QString("Canceled recording (%1)") - .arg(toString(nextRecording->GetRecordingStatus(), - nextRecording->GetRecordingRuleType())); - - VERBOSE(VB_GENERAL, QString("%1: %2").arg(msg).arg(details)); - - if (is_rec) - UpdateNextRecord(); - - if (nextRecording->GetRecordingStatus() == rsFailed) +void Scheduler::HandleRecordingStatusChange( + RecordingInfo &ri, RecStatusTypes recStatus, const QString &details) +{ + if (ri.GetRecordingStatus() == recStatus) + return; + + ri.SetRecordingStatus(recStatus); + + if (rsTuning != recStatus) + { + bool doSchedAfterStart = + ((rsRecording != recStatus) && (rsTuning != recStatus)) || + schedAfterStartMap[ri.GetRecordingRuleID()] || + (ri.GetParentRecordingRuleID() && + schedAfterStartMap[ri.GetParentRecordingRuleID()]); + ri.AddHistory(doSchedAfterStart); + } + + QString msg = (rsRecording == recStatus) ? + QString("Started recording") : + ((rsTuning == recStatus) ? + QString("Tuning recording") : + QString("Canceled recording (%1)") + .arg(toString(ri.GetRecordingStatus(), ri.GetRecordingRuleType()))); + + VERBOSE(VB_GENERAL, QString("%1: %2").arg(msg).arg(details)); + + if ((rsRecording == recStatus) || (rsTuning == recStatus)) + { + UpdateNextRecord(); + } + else if (rsFailed == recStatus) + { + MythEvent me(QString("FORCE_DELETE_RECORDING %1 %2") + .arg(ri.GetChanID()) + .arg(ri.GetRecordingStartTime(ISODate))); + gCoreContext->dispatch(me); + } +} + +void Scheduler::HandleTuning(RecordingInfo &ri, bool &statuschanged) +{ + if (rsTuning != ri.GetRecordingStatus()) + return; + + // Determine current recording status + QMap::iterator tvit = m_tvList->find(ri.GetCardID()); + RecStatusTypes recStatus = rsTunerBusy; + if (tvit == m_tvList->end()) + { + QString msg = QString("Invalid cardid (%1) for %2") + .arg(ri.GetCardID()).arg(ri.GetTitle()); + VERBOSE(VB_GENERAL, LOC + msg); + } + else + { + recStatus = (*tvit)->GetRecordingStatus(); + if (rsTuning == recStatus) + { + // If tuning is still taking place this long after we + // started give up on it so the scheduler can try to + // find another broadcast of the same material. + QDateTime curtime = QDateTime::currentDateTime(); + if ((ri.GetRecordingStartTime().secsTo(curtime) > 180) && + (ri.GetScheduledStartTime().secsTo(curtime) > 180)) { - MythEvent me(QString("FORCE_DELETE_RECORDING %1 %2") - .arg(nextRecording->GetChanID()) - .arg(nextRecording->GetRecordingStartTime(ISODate))); - gCoreContext->dispatch(me); + recStatus = rsFailed; } } + } - if (statuschanged) + // If the status has changed, handle it + if (rsTuning != recStatus) + { + QString details = QString("%1: channel %2 on cardid %3, sourceid %4") + .arg(ri.toString(ProgramInfo::kTitleSubtitle)) + .arg(ri.GetChanID()).arg(ri.GetCardID()).arg(ri.GetSourceID()); + HandleRecordingStatusChange(ri, recStatus, details); + statuschanged = true; + } +} + +void Scheduler::HandleIdleShutdown( + bool &blockShutdown, QDateTime &idleSince, + int prerollseconds, int idleTimeoutSecs, int idleWaitForRecordingTime) +{ + if ((idleTimeoutSecs <= 0) || (m_mainServer == NULL)) + return; + + // we release the block when a client connects + if (blockShutdown) + blockShutdown &= !m_mainServer->isClientConnected(); + else + { + QDateTime curtime = QDateTime::currentDateTime(); + + // find out, if we are currently recording (or LiveTV) + bool recording = false; + QMap::Iterator it; + for (it = m_tvList->begin(); (it != m_tvList->end()) && + !recording; ++it) { - MythEvent me("SCHEDULE_CHANGE"); - gCoreContext->dispatch(me); - idleSince = QDateTime(); + if ((*it)->IsBusy()) + recording = true; } - // if idletimeout is 0, the user disabled the auto-shutdown feature - if ((idleTimeoutSecs > 0) && (m_mainServer != NULL)) + if (!(m_mainServer->isClientConnected()) && !recording) { - // we release the block when a client connects - if (blockShutdown) - blockShutdown &= !m_mainServer->isClientConnected(); - else + // have we received a RESET_IDLETIME message? + resetIdleTime_lock.lock(); + if (resetIdleTime) { - // find out, if we are currently recording (or LiveTV) - bool recording = false; - QMap::Iterator it; - for (it = m_tvList->begin(); (it != m_tvList->end()) && - !recording; ++it) - { - if ((*it)->IsBusy()) - recording = true; - } + // yes - so reset the idleSince time + idleSince = QDateTime(); + resetIdleTime = false; + } + resetIdleTime_lock.unlock(); - if (!(m_mainServer->isClientConnected()) && !recording) + if (!idleSince.isValid()) + { + RecIter idleIter = reclist.begin(); + for ( ; idleIter != reclist.end(); ++idleIter) + if ((*idleIter)->GetRecordingStatus() == + rsWillRecord) + break; + + if (idleIter != reclist.end()) { - // have we received a RESET_IDLETIME message? - resetIdleTime_lock.lock(); - if (resetIdleTime) + if (curtime.secsTo + ((*idleIter)->GetRecordingStartTime()) - + prerollseconds > + (idleWaitForRecordingTime * 60) + + idleTimeoutSecs) { - // yes - so reset the idleSince time - idleSince = QDateTime(); - resetIdleTime = false; + idleSince = curtime; } - resetIdleTime_lock.unlock(); - - if (!idleSince.isValid()) + } + else + idleSince = curtime; + } + else + { + // is the machine already idling the timeout time? + if (idleSince.addSecs(idleTimeoutSecs) < curtime) + { + // are we waiting for shutdown? + if (m_isShuttingDown) { - RecIter idleIter = reclist.begin(); - for ( ; idleIter != reclist.end(); ++idleIter) - if ((*idleIter)->GetRecordingStatus() == - rsWillRecord) - break; - - if (idleIter != reclist.end()) + // if we have been waiting more that 60secs then assume + // something went wrong so reset and try again + if (idleSince.addSecs(idleTimeoutSecs + 60) < + curtime) { - if (curtime.secsTo - ((*idleIter)->GetRecordingStartTime()) - - prerollseconds > - (idleWaitForRecordingTime * 60) + - idleTimeoutSecs) - { - idleSince = curtime; - } + VERBOSE(VB_IMPORTANT, "Waited more than 60" + " seconds for shutdown to complete" + " - resetting idle time"); + idleSince = QDateTime(); + m_isShuttingDown = false; } - else - idleSince = curtime; } - else + else if (!m_isShuttingDown && + CheckShutdownServer(prerollseconds, + idleSince, + blockShutdown)) { - // is the machine already idling the timeout time? - if (idleSince.addSecs(idleTimeoutSecs) < curtime) - { - // are we waiting for shutdown? - if (m_isShuttingDown) - { - // if we have been waiting more that 60secs then assume - // something went wrong so reset and try again - if (idleSince.addSecs(idleTimeoutSecs + 60) < - curtime) - { - VERBOSE(VB_IMPORTANT, "Waited more than 60" - " seconds for shutdown to complete" - " - resetting idle time"); - idleSince = QDateTime(); - m_isShuttingDown = false; - } - } - else if (!m_isShuttingDown && - CheckShutdownServer(prerollseconds, - idleSince, - blockShutdown)) - { - ShutdownServer(prerollseconds, idleSince); - } - } - else - { - int itime = idleSince.secsTo(curtime); - QString msg; - if (itime == 1) - { - msg = QString("I\'m idle now... shutdown will " - "occur in %1 seconds.") - .arg(idleTimeoutSecs); - VERBOSE(VB_IMPORTANT, msg); - MythEvent me(QString("SHUTDOWN_COUNTDOWN %1") - .arg(idleTimeoutSecs)); - gCoreContext->dispatch(me); - } - else if (itime % 10 == 0) - { - msg = QString("%1 secs left to system " - "shutdown!") - .arg(idleTimeoutSecs - itime); - VERBOSE(VB_IDLE, msg); - MythEvent me(QString("SHUTDOWN_COUNTDOWN %1") - .arg(idleTimeoutSecs - itime)); - gCoreContext->dispatch(me); - } - } + ShutdownServer(prerollseconds, idleSince); } } else { - // not idle, make the time invalid - if (idleSince.isValid()) + int itime = idleSince.secsTo(curtime); + QString msg; + if (itime == 1) { - MythEvent me(QString("SHUTDOWN_COUNTDOWN -1")); + msg = QString("I\'m idle now... shutdown will " + "occur in %1 seconds.") + .arg(idleTimeoutSecs); + VERBOSE(VB_IMPORTANT, msg); + MythEvent me(QString("SHUTDOWN_COUNTDOWN %1") + .arg(idleTimeoutSecs)); + gCoreContext->dispatch(me); + } + else if (itime % 10 == 0) + { + msg = QString("%1 secs left to system " + "shutdown!") + .arg(idleTimeoutSecs - itime); + VERBOSE(VB_IDLE, msg); + MythEvent me(QString("SHUTDOWN_COUNTDOWN %1") + .arg(idleTimeoutSecs - itime)); gCoreContext->dispatch(me); } - idleSince = QDateTime(); } } } + else + { + // not idle, make the time invalid + if (idleSince.isValid()) + { + MythEvent me(QString("SHUTDOWN_COUNTDOWN -1")); + gCoreContext->dispatch(me); + } + idleSince = QDateTime(); + } } - threadDeregister(); } //returns true, if the shutdown is not blocked @@ -3582,7 +3780,9 @@ void Scheduler::AddNewRecords(void) if (!p->future && !p->IsReactivated() && p->oldrecstatus != rsAborted && p->oldrecstatus != rsNotListed) + { p->SetRecordingStatus(p->oldrecstatus); + } if (!recTypeRecPriorityMap.contains(p->GetRecordingRuleType())) { diff --git a/mythtv/programs/mythbackend/scheduler.h b/mythtv/programs/mythbackend/scheduler.h index c242da4d736..a7c2e5da4c1 100644 --- a/mythtv/programs/mythbackend/scheduler.h +++ b/mythtv/programs/mythbackend/scheduler.h @@ -93,7 +93,7 @@ class Scheduler : public QThread bool VerifyCards(void); - bool FillRecordList(bool doLock); + bool FillRecordList(void); void UpdateMatches(int recordid); void UpdateManuals(int recordid); void BuildWorkList(void); @@ -147,6 +147,26 @@ class Scheduler : public QThread const RecList &reclist); void FillDirectoryInfoCache(bool force = false); + int CalcTimeToNextHandleRecordingEvent( + const QDateTime &curtime, + RecConstIter startIter, const RecList &reclist, + int prerollseconds, int max_sleep /*ms*/); + void OldRecordedFixups(void); + bool HandleReschedule(void); + bool HandleRunSchedulerStartup( + int prerollseconds, int idleWaitForRecordingTime); + void HandleWakeSlave(RecordingInfo &ri, int prerollseconds); + bool HandleRecording( + RecordingInfo &ri, bool &statuschanged, int prerollseconds); + void HandleTuning( + RecordingInfo &ri, bool &statuschanged); + void HandleRecordingStatusChange( + RecordingInfo &ri, RecStatusTypes recStatus, const QString &details); + void HandleIdleShutdown( + bool &blockShutdown, QDateTime &idleSince, int prerollseconds, + int idleTimeoutSecs, int idleWaitForRecordingTime); + + MythDeque reschedQueue; QMutex schedLock; QMutex recordmatchLock; diff --git a/mythtv/programs/mythfrontend/main.cpp b/mythtv/programs/mythfrontend/main.cpp index ab62dbb9172..e6dd86aa38b 100644 --- a/mythtv/programs/mythfrontend/main.cpp +++ b/mythtv/programs/mythfrontend/main.cpp @@ -1273,7 +1273,7 @@ int main(int argc, char **argv) { int networkPort = gCoreContext->GetNumSetting("NetworkControlPort", 6545); networkControl = new NetworkControl(); - if (!networkControl->listen(QHostAddress::Any,networkPort)) + if (!networkControl->listen(QHostAddress::AnyIPv6,networkPort)) VERBOSE(VB_IMPORTANT, QString("NetworkControl failed to bind to port %1.") .arg(networkPort)); diff --git a/mythtv/programs/mythfrontend/mediarenderer.cpp b/mythtv/programs/mythfrontend/mediarenderer.cpp index 263251f9c51..af56081deb2 100644 --- a/mythtv/programs/mythfrontend/mediarenderer.cpp +++ b/mythtv/programs/mythfrontend/mediarenderer.cpp @@ -32,11 +32,20 @@ class MythFrontendStatus : public HttpServerExtension if (pRequest->m_sBaseUrl != "/") return false; + if (pRequest->m_sMethod == "getDeviceDesc") + return false; + pRequest->m_eResponseType = ResponseTypeHTML; pRequest->m_mapRespHeaders[ "Cache-Control" ] = "no-cache=\"Ext\", max-age = 5000"; + SSDPCacheEntries* cache = NULL; + QString ipaddress = QString(); + if (!UPnp::g_IPAddrList.isEmpty()) + ipaddress = UPnp::g_IPAddrList.at(0); + QString shortdateformat = gCoreContext->GetSetting("ShortDateFormat", "M/d"); QString timeformat = gCoreContext->GetSetting("TimeFormat", "h:mm AP"); + QString hostname = gCoreContext->GetHostName(); QDateTime qdtNow = QDateTime::currentDateTime(); QString masterhost = gCoreContext->GetMasterHostName(); QString masterip = gCoreContext->GetSetting("MasterServerIP"); @@ -53,104 +62,88 @@ class MythFrontendStatus : public HttpServerExtension << "\r\n" << " \r\n" - << " \r\n" << " MythFrontend Status - " << qdtNow.toString(shortdateformat) << " " << qdtNow.toString(timeformat) << " - " << MYTH_BINARY_VERSION << "\r\n" + << " \r\n" + << " \r\n" << "\r\n" << "\r\n\r\n" - << "

    MythFrontend Status

    \r\n"; + << "

    MythFrontend Status

    \r\n"; + // This frontend stream << " \r\n"; + + // Other frontends + + // This will not work with multiple frontends on the same machine (upnp + // setup will fail on a second frontend anyway) and the ip address + // filtering of the current frontend may not work in all situations + + cache = SSDP::Find("urn:schemas-mythtv-org:service:MythFrontend:1"); + if (cache) + { + stream + << " \r\n"; + } + + // Master backend + stream + << "
    \r\n" + << "

    MythTV Backends

    \r\n" + << "Master: " << masterhost << " (Status page)\r\n" + << "\">Status page)\r\n"; + + // Slave backends + cache = SSDP::Find("urn:schemas-mythtv-org:device:SlaveMediaServer:1"); + if (cache) + { + cache->AddRef(); + cache->Lock(); + EntryMap* map = cache->GetEntryMap(); + QMapIterator< QString, DeviceLocation * > i(*map); + while (i.hasNext()) + { + i.next(); + QUrl url(i.value()->m_sLocation); + stream << "
    " << "Slave: " << url.host() << " (Status page)\r\n"; + } + cache->Unlock(); + cache->Release(); + } + + stream << "
    \r\n"; stream << "
    \r\n" - << "

    Services

    \r\n" + << "

    Services

    \r\n" << " Remote Control\r\n" << "
    \r\n"; @@ -159,7 +152,7 @@ class MythFrontendStatus : public HttpServerExtension { stream << "
    \r\n" - << "

    Machine Information

    \r\n" + << "

    Machine Information

    \r\n" << "
    \r\n" << " This machine's load average:" << "\r\n
      \r\n
    • " @@ -211,7 +204,7 @@ MediaRenderer::MediaRenderer() if (!m_pHttpServer) return; - if (!m_pHttpServer->listen(QHostAddress::Any, nPort)) + if (!m_pHttpServer->listen(QHostAddress::AnyIPv6, nPort)) { VERBOSE(VB_IMPORTANT, "MediaRenderer::HttpServer Create Error"); delete m_pHttpServer; @@ -281,14 +274,15 @@ MediaRenderer::MediaRenderer() Start(); + // ensure the frontend is aware of all backends (slave and master) and + // other frontends + SSDP::Instance()->PerformSearch("ssdp:all"); } else { VERBOSE(VB_IMPORTANT, "MediaRenderer::Unable to Initialize UPnp Stack"); } - - VERBOSE(VB_UPNP, QString( "MediaRenderer::End" )); } diff --git a/mythtv/programs/mythfrontend/networkcontrol.h b/mythtv/programs/mythfrontend/networkcontrol.h index c43b5cea275..ee1742d31bf 100644 --- a/mythtv/programs/mythfrontend/networkcontrol.h +++ b/mythtv/programs/mythfrontend/networkcontrol.h @@ -106,7 +106,7 @@ class NetworkControl : public QTcpServer public: NetworkControl(); ~NetworkControl(); - bool listen(const QHostAddress &address = QHostAddress::Any, + bool listen(const QHostAddress &address = QHostAddress::AnyIPv6, quint16 port = 0); private slots: diff --git a/mythtv/programs/mythfrontend/playbackbox.cpp b/mythtv/programs/mythfrontend/playbackbox.cpp index 665f7cd5abf..f0d748a549e 100644 --- a/mythtv/programs/mythfrontend/playbackbox.cpp +++ b/mythtv/programs/mythfrontend/playbackbox.cpp @@ -3842,7 +3842,7 @@ void PlaybackBox::customEvent(QEvent *event) keyevent); } } - else if (message.left(17) == "UPDATE_FILE_SIZE") + else if (message.left(16) == "UPDATE_FILE_SIZE") { QStringList tokens = message.simplified().split(" "); bool ok = false; @@ -3852,7 +3852,7 @@ void PlaybackBox::customEvent(QEvent *event) if (tokens.size() >= 4) { chanid = tokens[1].toUInt(); - recstartts = QDateTime::fromString(tokens[2]); + recstartts = QDateTime::fromString(tokens[2], Qt::ISODate); filesize = tokens[3].toLongLong(&ok); } if (chanid && recstartts.isValid() && ok) diff --git a/mythtv/programs/mythfrontend/playbackboxhelper.cpp b/mythtv/programs/mythfrontend/playbackboxhelper.cpp index 3de027c9690..2da4c24276c 100644 --- a/mythtv/programs/mythfrontend/playbackboxhelper.cpp +++ b/mythtv/programs/mythfrontend/playbackboxhelper.cpp @@ -408,14 +408,12 @@ bool PBHEventHandler::event(QEvent *e) { if (!re.exactMatch(*it)) continue; + foundFile = gCoreContext->GenMythURL( + gCoreContext->GetSettingOnHost("BackendServerIP", host), + gCoreContext->GetSettingOnHost("BackendServerPort", host).toInt(), + *it, + StorageGroup::GetGroupToUse(host, sgroup)); - foundFile = QString("myth://%1@%2:%3/%4") - .arg(StorageGroup::GetGroupToUse(host, sgroup)) - .arg(gCoreContext->GetSettingOnHost( - "BackendServerIP", host)) - .arg(gCoreContext->GetSettingOnHost( - "BackendServerPort", host)) - .arg(*it); break; } } diff --git a/mythtv/programs/mythfrontend/themechooser.cpp b/mythtv/programs/mythfrontend/themechooser.cpp index 942fc5525fb..9d509dd11a3 100644 --- a/mythtv/programs/mythfrontend/themechooser.cpp +++ b/mythtv/programs/mythfrontend/themechooser.cpp @@ -881,8 +881,10 @@ ThemeUpdateChecker::ThemeUpdateChecker() : m_mythVersion.replace(QRegExp("\\.[0-9]{8,}.*"), ""); } - m_infoPackage = QString("myth://Temp@%1/remotethemes/themes.zip") - .arg(gCoreContext->GetSetting("MasterServerIP")); + m_infoPackage = gCoreContext->GenMythURL(gCoreContext->GetSetting("MasterServerIP"), + 0, + "remotethemes/themes.zip", + "Temp"); gCoreContext->SaveSetting("ThemeUpdateStatus", QString()); @@ -911,9 +913,11 @@ void ThemeUpdateChecker::checkForUpdate(void) if (RemoteFile::Exists(m_infoPackage)) { QString remoteThemeDir = - QString("myth://Temp@%1/remotethemes/%2/%3") - .arg(gCoreContext->GetSetting("MasterServerIP")) - .arg(m_mythVersion).arg(GetMythUI()->GetThemeName()); + gCoreContext->GenMythURL(gCoreContext->GetSetting("MasterServerIP"), + 0, + QString("%1/%2").arg(m_mythVersion).arg(GetMythUI()->GetThemeName()), + "Temp"); + QString infoXML = remoteThemeDir; infoXML.append("/themeinfo.xml"); diff --git a/mythtv/programs/mythlcdserver/lcdserver.cpp b/mythtv/programs/mythlcdserver/lcdserver.cpp index db320e42804..e11d5929a2a 100644 --- a/mythtv/programs/mythlcdserver/lcdserver.cpp +++ b/mythtv/programs/mythlcdserver/lcdserver.cpp @@ -90,7 +90,7 @@ LCDServer::LCDServer(int port, QString message, int messageTime) connect(m_serverSocket, SIGNAL(newConnection()), this, SLOT(newConnection())); - if (!m_serverSocket->listen(QHostAddress::Any, port)) + if (!m_serverSocket->listen(QHostAddress::AnyIPv6, port)) { VERBOSE(VB_IMPORTANT, LOC_ERR + QString("Can't bind to server port %1").arg(port) + diff --git a/mythtv/programs/mythtranscode/main.cpp b/mythtv/programs/mythtranscode/main.cpp index fb29a925b4f..6563f281af2 100644 --- a/mythtv/programs/mythtranscode/main.cpp +++ b/mythtv/programs/mythtranscode/main.cpp @@ -550,9 +550,8 @@ static int transUnlink(QString filename, ProgramInfo *pginfo) QString port = gCoreContext->GetSettingOnHost("BackendServerPort", pginfo->GetHostname()); QString basename = filename.section('/', -1); - QString uri = QString("myth://%1@%2:%3/%4") - .arg(pginfo->GetStorageGroup()) - .arg(ip).arg(port).arg(basename); + QString uri = gCoreContext->GenMythURL(ip,port,basename,pginfo->GetStorageGroup()); + VERBOSE(VB_IMPORTANT, QString("Requesting delete for file '%1'.") .arg(uri)); bool ok = RemoteFile::DeleteFile(uri); diff --git a/mythtv/programs/mythtranscode/mpeg2fix.cpp b/mythtv/programs/mythtranscode/mpeg2fix.cpp index e281afa39d4..9db6b16ad6d 100644 --- a/mythtv/programs/mythtranscode/mpeg2fix.cpp +++ b/mythtv/programs/mythtranscode/mpeg2fix.cpp @@ -238,12 +238,14 @@ MPEG2fixup::MPEG2fixup(const QString &inf, const QString &outf, av_register_all(); av_log_set_callback(my_av_print); + pthread_mutex_init(&rx.mutex, NULL); + pthread_cond_init(&rx.cond, NULL); + //await multiplexer initialization (prevent a deadlock race) - rx.mutex.lock(); - rx.thread.SetParent(&rx); - rx.thread.start(); - rx.cond.wait(&rx.mutex); - rx.mutex.unlock(); + pthread_mutex_lock(&rx.mutex); + pthread_create(&thread, NULL, ReplexStart, this); + pthread_cond_wait(&rx.cond, &rx.mutex); + pthread_mutex_unlock(&rx.mutex); //initialize progress stats showprogress = showprog; @@ -437,7 +439,7 @@ static int fill_buffers(void *r, int finish) MPEG2replex::MPEG2replex() : done(0), otype(0), - ext_count(0) + ext_count(0), mplex(0) { memset(&vrbuf, 0, sizeof(vrbuf)); memset(extrbuf, 0, sizeof(extrbuf)); @@ -467,7 +469,7 @@ MPEG2replex::~MPEG2replex() int MPEG2replex::WaitBuffers() { - mutex.lock(); + pthread_mutex_lock( &mutex ); while (1) { int i, ok = 1; @@ -482,22 +484,27 @@ int MPEG2replex::WaitBuffers() if (ok || done) break; - cond.wakeAll(); - cond.wait(&mutex); + pthread_cond_signal(&cond); + pthread_cond_wait(&cond, &mutex); + } + pthread_mutex_unlock(&mutex); + + if (done) + { + finish_mpg(mplex); + pthread_exit(NULL); } - mutex.unlock(); return 0; } -void MPEG2ReplexThread::run(void) +void *MPEG2fixup::ReplexStart(void *data) { - if (!m_parent) - return; - threadRegister("MPEG2Replex"); - m_parent->Start(); + MPEG2fixup *m2f = (MPEG2fixup *) data; + m2f->rx.Start(); threadDeregister(); + return NULL; } void MPEG2replex::Start() @@ -526,23 +533,23 @@ void MPEG2replex::Start() fd_out = open(outfile, O_WRONLY | O_CREAT | O_TRUNC | O_LARGEFILE, 0644); //await buffer fill - mutex.lock(); - cond.wakeAll(); - cond.wait(&mutex); - mutex.unlock(); + pthread_mutex_lock(&mutex); + pthread_cond_signal(&cond); + pthread_cond_wait(&cond, &mutex); + pthread_mutex_unlock(&mutex); + + mplex = &mx; init_multiplex(&mx, &seq_head, extframe, exttype, exttypcnt, video_delay, audio_delay, fd_out, fill_buffers, &vrbuf, &index_vrbuf, extrbuf, index_extrbuf, otype); setup_multiplex(&mx); - while (!done) + while (1) { check_times( &mx, &video_ok, ext_ok, &start); write_out_packs( &mx, video_ok, ext_ok); } - - finish_mpg(&mx); } #define INDEX_BUF (sizeof(index_unit) * 200) @@ -652,7 +659,7 @@ int MPEG2fixup::AddFrame(MPEG2frame *f) iu.active = 1; iu.length = f->pkt.size; iu.pts = f->pkt.pts * 300; - rx.mutex.lock(); + pthread_mutex_lock( &rx.mutex ); FrameInfo(f); while (ring_free(rb) < (unsigned int)f->pkt.size || @@ -681,7 +688,7 @@ int MPEG2fixup::AddFrame(MPEG2frame *f) } if (! ok) { - rx.mutex.unlock(); + pthread_mutex_unlock( &rx.mutex ); //deadlock VERBOSE(MPF_IMPORTANT, "Deadlock detected. One buffer is full when\n" @@ -689,25 +696,25 @@ int MPEG2fixup::AddFrame(MPEG2frame *f) return 1; } - rx.cond.wakeAll(); - rx.cond.wait(&rx.mutex); + pthread_cond_signal(&rx.cond); + pthread_cond_wait(&rx.cond, &rx.mutex); FrameInfo(f); } if (ring_write(rb, f->pkt.data, f->pkt.size)<0){ - rx.mutex.unlock(); + pthread_mutex_unlock( &rx.mutex ); VERBOSE(MPF_IMPORTANT, QString("Ring buffer overflow %1\n").arg(rb->size)); return 1; } if (ring_write(rbi, (uint8_t *)&iu, sizeof(index_unit))<0){ - rx.mutex.unlock(); + pthread_mutex_unlock( &rx.mutex ); VERBOSE(MPF_IMPORTANT, QString("Ring buffer overflow %1\n").arg(rbi->size)); return 1; } - rx.mutex.unlock(); + pthread_mutex_unlock(&rx.mutex); last_written_pos = f->pkt.pos; return 0; } @@ -965,7 +972,7 @@ void MPEG2fixup::WriteFrame(const char *filename, MPEG2frame *f) WriteFrame(filename, &tmpFrame->pkt); framePool.enqueue(tmpFrame); } - + void MPEG2fixup::WriteFrame(const char *filename, AVPacket *pkt) { @@ -2427,10 +2434,10 @@ int MPEG2fixup::Start() } rx.done = 1; - rx.mutex.lock(); - rx.cond.wakeAll(); - rx.mutex.unlock(); - rx.thread.wait(); + pthread_mutex_lock( &rx.mutex ); + pthread_cond_signal(&rx.cond); + pthread_mutex_unlock( &rx.mutex ); + pthread_join(thread, NULL); av_close_input_file(inputFC); inputFC = NULL; diff --git a/mythtv/programs/mythtranscode/mpeg2fix.h b/mythtv/programs/mythtranscode/mpeg2fix.h index 3a2fe272d55..44c703eae50 100644 --- a/mythtv/programs/mythtranscode/mpeg2fix.h +++ b/mythtv/programs/mythtranscode/mpeg2fix.h @@ -1,3 +1,6 @@ +// POSIX +#include + // C #include @@ -24,9 +27,6 @@ extern "C" #include #include #include -#include -#include -#include #include #include @@ -112,23 +112,9 @@ class PTSOffsetQueue int vid_id; }; -class MPEG2replex; - -class MPEG2ReplexThread : public QThread -{ - Q_OBJECT - public: - MPEG2ReplexThread() : m_parent(NULL) {} - void SetParent(MPEG2replex *parent) { m_parent = parent; } - void run(void); - private: - MPEG2replex *m_parent; -}; - //container for all multiplex related variables class MPEG2replex { - friend class MPEG2ReplexThread; public: MPEG2replex(); ~MPEG2replex(); @@ -145,12 +131,13 @@ class MPEG2replex int exttype[N_AUDIO]; int exttypcnt[N_AUDIO]; - QMutex mutex; - QWaitCondition cond; - MPEG2ReplexThread thread; - + pthread_mutex_t mutex; + pthread_cond_t cond; audio_frame_t extframe[N_AUDIO]; sequence_t seq_head; + + private: + multiplex_t *mplex; }; class MPEG2fixup @@ -255,6 +242,8 @@ class MPEG2fixup frm_dir_map_t delMap; frm_dir_map_t saveMap; + pthread_t thread; + AVFormatContext *inputFC; int vid_id; int ext_count; diff --git a/mythtv/programs/mythtv-setup/channeleditor.cpp b/mythtv/programs/mythtv-setup/channeleditor.cpp index d20804ac71e..f48dba5e3d2 100644 --- a/mythtv/programs/mythtv-setup/channeleditor.cpp +++ b/mythtv/programs/mythtv-setup/channeleditor.cpp @@ -1,31 +1,26 @@ - -#include "channeleditor.h" - -#include +// Qt #include -#include "mythwizard.h" - - -// MythUI -#include "mythdb.h" -#include "mythverbose.h" -#include "mythuitext.h" -#include "mythuibutton.h" -#include "mythuibuttonlist.h" -#include "mythuitextedit.h" -#include "mythuicheckbox.h" -#include "mythdialogbox.h" +// MythTV #include "mythprogressdialog.h" - -#include "settings.h" +#include "mythuibuttonlist.h" #include "channelsettings.h" #include "transporteditor.h" -#include "sourceutil.h" +#include "mythuicheckbox.h" +#include "mythuitextedit.h" +#include "channeleditor.h" +#include "mythdialogbox.h" +#include "mythuibutton.h" #include "channelutil.h" - -#include "scanwizard.h" #include "importicons.h" +#include "mythverbose.h" +#include "mythuitext.h" +#include "mythwizard.h" +#include "scanwizard.h" +#include "sourceutil.h" +#include "cardutil.h" +#include "settings.h" +#include "mythdb.h" ChannelWizard::ChannelWizard(int id, int default_sourceid) : ConfigurationWizard() @@ -44,77 +39,19 @@ ChannelWizard::ChannelWizard(int id, int default_sourceid) new ChannelOptionsFilters(*cid); addChild(filters); - int cardtypes = countCardtypes(); - bool hasDVB = cardTypesInclude("DVB"); - - // add v4l options if no dvb or if dvb and some other card type - // present - QString cardtype = getCardtype(); - if (!hasDVB || cardtypes > 1 || id == 0) + QStringList cardtypes = ChannelUtil::GetCardTypes(cid->getValue().toUInt()); + bool all_v4l = !cardtypes.empty(); + bool all_asi = !cardtypes.empty(); + for (uint i = 0; i < (uint) cardtypes.size(); i++) { - ChannelOptionsV4L* v4l = new ChannelOptionsV4L(*cid); - addChild(v4l); + all_v4l &= CardUtil::IsV4L(cardtypes[i]); + all_asi &= cardtypes[i] == "ASI"; } -} - -QString ChannelWizard::getCardtype() -{ - MSqlQuery query(MSqlQuery::InitCon()); - query.prepare("SELECT cardtype" - " FROM capturecard, cardinput, channel" - " WHERE channel.chanid = :CHID " - " AND channel.sourceid = cardinput.sourceid" - " AND cardinput.cardid = capturecard.cardid"); - query.bindValue(":CHID", cid->getValue()); - - if (query.exec() && query.next()) - { - return query.value(0).toString(); - } - - return ""; -} - -bool ChannelWizard::cardTypesInclude(const QString& thecardtype) -{ - MSqlQuery query(MSqlQuery::InitCon()); - query.prepare("SELECT count(cardtype)" - " FROM capturecard, cardinput, channel" - " WHERE channel.chanid= :CHID " - " AND channel.sourceid = cardinput.sourceid" - " AND cardinput.cardid = capturecard.cardid" - " AND capturecard.cardtype= :CARDTYPE "); - query.bindValue(":CHID", cid->getValue()); - query.bindValue(":CARDTYPE", thecardtype); - - if (query.exec() && query.next()) - { - int count = query.value(0).toInt(); - - if (count > 0) - return true; - else - return false; - } else - return false; -} -int ChannelWizard::countCardtypes() -{ - MSqlQuery query(MSqlQuery::InitCon()); - query.prepare("SELECT count(DISTINCT cardtype)" - " FROM capturecard, cardinput, channel" - " WHERE channel.chanid = :CHID " - " AND channel.sourceid = cardinput.sourceid" - " AND cardinput.cardid = capturecard.cardid"); - query.bindValue(":CHID", cid->getValue()); - - if (query.exec() && query.next()) - { - return query.value(0).toInt(); - } - else - return 0; + if (all_v4l) + addChild(new ChannelOptionsV4L(*cid)); + else if (all_asi) + addChild(new ChannelOptionsRawTS(*cid)); } ///////////////////////////////////////////////////////// diff --git a/mythtv/programs/mythtv-setup/channeleditor.h b/mythtv/programs/mythtv-setup/channeleditor.h index dadb552867c..c770911dc33 100644 --- a/mythtv/programs/mythtv-setup/channeleditor.h +++ b/mythtv/programs/mythtv-setup/channeleditor.h @@ -66,9 +66,6 @@ class ChannelWizard : public QObject, public ConfigurationWizard Q_OBJECT public: ChannelWizard(int id, int default_sourceid); - QString getCardtype(); - bool cardTypesInclude(const QString& cardtype); - int countCardtypes(); private: ChannelID *cid; diff --git a/mythtv/programs/programs-libs.pro b/mythtv/programs/programs-libs.pro index d8ae6461119..d27ceaaebac 100644 --- a/mythtv/programs/programs-libs.pro +++ b/mythtv/programs/programs-libs.pro @@ -3,6 +3,8 @@ INCLUDEPATH += ../../libs/libmythtv ../../external/FFmpeg INCLUDEPATH += ../../libs/libmythupnp ../../libs/libmythui ../../libs/libmythmetadata INCLUDEPATH += ../../libs/libmythlivemedia ../../libs/libmythbase ../../libmythhdhomerun INCLUDEPATH += ../../libs/libmythdvdnav ../../libs/libmythbluray ../../libs/libmythsamplerate +INCLUDEPATH += ../../libs/libmythtv/mpeg +INCLUDEPATH += ../../libs/libmythtv/vbitext INCLUDEPATH += ../../libs/libmythservicecontracts LIBS += -L../../libs/libmyth -L../../libs/libmythtv @@ -49,8 +51,10 @@ POST_TARGETDEPS += ../../libs/libmythservicecontracts/libmythservicecontracts-$$ using_live: POST_TARGETDEPS += ../../libs/libmythlivemedia/libmythlivemedia-$${MYTH_SHLIB_EXT} using_hdhomerun: POST_TARGETDEPS += ../../libs/libmythhdhomerun/libmythhdhomerun-$${MYTH_SHLIB_EXT} -DEPENDPATH += ../.. ../../libs ../../libs/libmyth ../../libs/libmyth/audio -DEPENDPATH += ../../libs/libmythtv ../../external/FFmpeg +DEPENDPATH += ../.. ../../libs ../../libs/libmyth +DEPENDPATH += ../../libs/libmythtv +DEPENDPATH += ../../libs/libmythtv/mpeg ../../libs/libmythtv/vbitext +DEPENDPATH += ../../external/FFmpeg DEPENDPATH += ../../libs/libmythupnp ../../libs/libmythui DEPENDPATH += ../../libs/libmythlivemedia ../../libmythbase ../../libmythhdhomerun DEPENDPATH +=../../libs/libmythservicecontracts