or Connect
AVS › AVS Forum › Video Components › Home Theater Computers › HTPC - Linux Chat › Recording With MythTV - How Are the Kids Doing it These Days?
New Posts  All Forums:Forum Nav:

Recording With MythTV - How Are the Kids Doing it These Days? - Page 2

post #31 of 40
Quote:
Originally Posted by quantumstate View Post

Correct. So?
Hopper is the second instance of a major product (Replay was the first), and the third instance of a commercial product (Sage is the other) which allows automatic commercial skipping. From experience I can tell you that for the Myth devs it's not actually about legal merit or hazard. It is just a political issue based in emotion. Well fortunately we've had a few talented souls who can make the R5000 work in Myth, and we can enjoy the benefits anyway.

You completely missed the point on how Dish was doing it. They are likely using out of band information, which they have access to because they sell commercial air time for the networks, on where the commercials are located. This is why it is different than the other DVRs and where the networks may have a case. Obviously pure DVR providers, such as MythTV, have no such advantage (and issue) because they can only look at the broadcast stream to determine where the commercials are located. The end user will not have access to the out of band info. At one time commercial signaling was in band by using telco MF, but this has been gone for many years.
Edited by blackcat6 - 8/5/12 at 7:23am
post #32 of 40
Thread Starter 
So the Hopper uses out-of-band to signaling, which I have no access to, at this time, correct.

I am using the standard Myth methods of determining where commercials are, which is the same as Replay, which is the same as Sage. Even if I were using out-of-band, once again I would not be defeating their encryption in violation of the DMCA. I have paid for this content, in fact I would have paid twice through my subscription and through the commercials.
post #33 of 40
Quote:
Originally Posted by quantumstate View Post

S... Even if I were using out-of-band, once again I would not be defeating their encryption in violation of the DMCA. I have paid for this content, in fact I would have paid twice through my subscription and through the commercials.
I completely agree but again this isn't the point. It is DISH that is being taken to court for having done this, not the end users.
post #34 of 40
Thread Starter 
Sure, Dish has been sued for this auto commercial skip. But you have to understand that they will win. The reason is, when Replay was sued for this, investors lost confidence and the company collapsed. Big Media did not however, finish the case to get precedent; they just dropped it. Now, as I say, every DVR has done commercial skip (and some auto), and Big Media has failed to pursue this, and that that in and of itself is a legal precedent. Dish will win. (and get $millions in legal fees) You'll see.
post #35 of 40
Well, I said it may be a while.....

smile.gif

I successfully modified Alan Nisota's patch for MythTV 0.24 to make it work with 0.26.

I am not a C++ programmer, and this is the first time I've ever used MythTV, so this hasn't been extensively tested.

However, it does compile and I was able to schedule a recording successfully.

I got 5 minutes of a movie on HBO, and it looks fine.

I emailed the patch to Alan, but I haven't heard back from him yet.

Here's my patch:
http://pastebin.com/FKji0PTR

You'll need to checkout his SVN repository and copy it over to your own repository. I didn't write my patch to work with his "build patch" script, but you can apply it to your repository, then make sure to copy his /r5000 folder from /src to /libs/libmythtv/ in your own repository.

Follow his instructions to make sure the firmware is loaded (I'm using the udev method), then configure, make, make install.

Hope it works for you.

-Wes
post #36 of 40
Quote:
Originally Posted by waldo22 View Post

Well, I said it may be a while.....
smile.gif
I successfully modified Alan Nisota's patch for MythTV 0.24 to make it work with 0.26.
I am not a C++ programmer, and this is the first time I've ever used MythTV, so this hasn't been extensively tested.
However, it does compile and I was able to schedule a recording successfully.
I got 5 minutes of a movie on HBO, and it looks fine.
I emailed the patch to Alan, but I haven't heard back from him yet.
Here's my patch:
http://pastebin.com/FKji0PTR
You'll need to checkout his SVN repository and copy it over to your own repository. I didn't write my patch to work with his "build patch" script, but you can apply it to your repository, then make sure to copy his /r5000 folder from /src to /libs/libmythtv/ in your own repository.
Follow his instructions to make sure the firmware is loaded (I'm using the udev method), then configure, make, make install.
Hope it works for you.
-Wes

Thank You!!!!

Won't be able to try this right away but at least now I don't have to worry about it...
post #37 of 40
So,

I set up a GitHub repository to make this easier:
https://github.com/waldo22/mythtv/tree/fixes/0.26

This is MythTV 0.26, pre-patched, for your convenience. wink.gif

You'll still need to download and install the firmware and udev rules from Alan Nisota's Google Code page, until I can add those to my GitHub.
http://code.google.com/p/r5000-for-linux/source/browse/?r=18#svn%2Ftrunk%2Ffirmware

Installation steps for MythTV 0.26:
install git
git clone https://github.com/waldo22/mythtv.git
git checkout fixes/0.26
./configure --enable-proc-opt
make -j 2
make install

For Debian, you'll need to install the following libs before you run ./configure:
Code:
libusb-dev libgdb-dev qt4-qmake yasm uuid-dev libfreetype6-dev zlib1g-dev libmp3lame-dev libqtwebkit-dev libxxf86vm-dev x11proto-xf86vidmode-dev libxinerama-dev libxinerama1 x11proto-xinerama-dev mysql-server pkg-config python-mysqldb libxslt1.1 python-lxml python-pycurl python-urlgrabber libsocket6-perl libio-socket-inet6-perl libnet-upnp-perl fxload

For Ubuntu, it should be much easier, as you should be able to install build-depends.

I had to create a bunch of folders by hand (maybe 6 or 7) to get "make install" to work, but otherwise it went fine.

-Wes
Edited by waldo22 - 4/13/13 at 8:44am
post #38 of 40
Thread Starter 
Holy sh*t. (looks for that bowing-down icon)

I never dreamed I could update from .24.

I'll be flanging it in shortly.

In case I haven't said it, THANK YOU WALDO!!! tongue.gif
post #39 of 40
Thread Starter 
Well I wasn't able to compile Waldo's git and I can't seem to reach him, but I did git the latest MythTV source (0.27) and modify Waldo's patch to work with that. Working great!

I can't seem to attach a file to this post, so will list my version of the patch below. It's important to remember that you must still add Alan's r5000 directory to the Myth source before compiling. In order to get all the ./configure options enabled you'll need to search for and install lots of lib*-dev files, the least apparent being libgles* and libupnp6*. Might also like to compile mythplugins, for all the official plugins.

r5000recorder.patch
Code:
Index: libs/libmythtv/r5000recorder.cpp
===================================================================
--- libs/libmythtv/r5000recorder.cpp    (revision 0)
+++ libs/libmythtv/r5000recorder.cpp    (revision 4)
@@ -0,0 +1,285 @@
+/**
+ *  R5000Recorder
+ *  Copyright (c) 2005 by Jim Westfall and Dave Abrahams
+ *  Distributed as part of MythTV under GPL v2 and later.
+ */
+
+// MythTV includes
+#include "r5000recorder.h"
+#include "r5000channel.h"
+#include "mythcontext.h"
+#include "mpegtables.h"
+#include "mpegstreamdata.h"
+#include "tv_rec.h"
+
+#define LOC QString("R5000RecBase(%1): ").arg(channel->GetDevice())
+#define LOC_ERR QString("R5000RecBase(%1), Error: ").arg(channel->GetDevice())
+
+R5000Recorder::R5000Recorder(TVRec *rec, R5000Channel *chan) :
+    DTVRecorder(rec),
+    channel(chan), isopen(false)
+{
+    //_wait_for_keyframe_option = false;
+}
+
+R5000Recorder::~R5000Recorder()
+{
+    Close();
+}
+
+bool R5000Recorder::Open(void)
+{
+    if (!isopen)
+        isopen = channel->GetR5000Device()->OpenPort();
+
+    return isopen;
+}
+
+void R5000Recorder::Close(void)
+{
+    if (isopen)
+    {
+        channel->GetR5000Device()->ClosePort();
+        isopen = false;
+    }
+}
+
+void R5000Recorder::StartStreaming(void)
+{
+    channel->GetR5000Device()->AddListener(this);
+}
+
+void R5000Recorder::StopStreaming(void)
+{
+    channel->GetR5000Device()->RemoveListener(this);
+}
+
+void R5000Recorder::StartRecording(void)
+{
+    LOG(VB_RECORD, LOG_DEBUG, LOC + "StartRecording");
+
+    if (!Open())
+    {
+        _error = true;
+        return;
+    }
+
+    request_recording = true;
+    recording = true;
+
+    StartStreaming();
+
+    while (request_recording)
+    {
+        if (!PauseAndWait())
+            usleep(50 * 1000);
+    }
+
+    StopStreaming();
+    FinishRecording();
+
+    recording = false;
+}
+
+void R5000Recorder::run(void)
+{
+    LOG(VB_RECORD, LOG_INFO, LOC + "run");
+
+    if (!Open())
+    {
+        _error = "Failed to open R5000 device";
+        LOG(VB_GENERAL, LOG_ERR, LOC + _error);
+        return;
+    }
+
+    {
+        QMutexLocker locker(&pauseLock);
+        request_recording = true;
+        recording = true;
+        recordingWait.wakeAll();
+    }
+
+    StartStreaming();
+
+    while (IsRecordingRequested() && !IsErrored())
+    {
+        if (PauseAndWait())
+            continue;
+
+        if (!IsRecordingRequested())
+            break;
+
+        {   // 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();
+
+    QMutexLocker locker(&pauseLock);
+    recording = false;
+    recordingWait.wakeAll();
+}
+
+void R5000Recorder::AddData(const unsigned char *data, uint len)
+{
+    uint bufsz = buffer.size();
+    if ((SYNC_BYTE == data[0]) && (TSPacket::kSize == len) &&
+        (TSPacket::kSize > bufsz))
+    {
+        if (bufsz)
+            buffer.clear();
+
+        ProcessTSPacket(*(reinterpret_cast<const TSPacket*>(data)));
+        return;
+    }
+
+    buffer.insert(buffer.end(), data, data + len);
+    bufsz += len;
+
+    int sync_at = -1;
+    for (uint i = 0; (i < bufsz) && (sync_at < 0); i++)
+    {
+        if (buffer[i] == SYNC_BYTE)
+            sync_at = i;
+    }
+
+    if (sync_at < 0)
+        return;
+
+    if (bufsz < 30 * TSPacket::kSize)
+        return; // build up a little buffer
+
+    while (sync_at + TSPacket::kSize < bufsz)
+    {
+        ProcessTSPacket(*(reinterpret_cast<const TSPacket*>(
+                              &buffer[0] + sync_at)));
+
+        sync_at += TSPacket::kSize;
+    }
+
+    buffer.erase(buffer.begin(), buffer.begin() + sync_at);
+
+    return;
+}
+
+bool R5000Recorder::ProcessTSPacket(const TSPacket &tspacket)
+{
+    if (tspacket.TransportError())
+        return true;
+
+    if (tspacket.Scrambled())
+        return true;
+
+    if (! GetStreamData())
+        return true;
+    if (tspacket.HasAdaptationField())
+        GetStreamData()->HandleAdaptationFieldControl(&tspacket);
+
+    if (tspacket.HasPayload())
+    {
+        const unsigned int lpid = tspacket.PID();
+        // Pass or reject packets based on PID, and parse info from them
+        if (lpid == GetStreamData()->VideoPIDSingleProgram())
+        {
+            ProgramMapTable *pmt = GetStreamData()->PMTSingleProgram();
+            uint video_stream_type = pmt->StreamType(pmt->FindPID(lpid));
+
+            if (video_stream_type == StreamID::H264Video)
+                _buffer_packets = !FindH264Keyframes(&tspacket);
+            else if (StreamID::IsVideo(video_stream_type))
+                _buffer_packets = !FindMPEG2Keyframes(&tspacket);
+
+            if ((video_stream_type != StreamID::H264Video) || _seen_sps)
+                BufferedWrite(tspacket);
+        }
+        else if (GetStreamData()->IsAudioPID(lpid))
+        {
+            _buffer_packets = !FindAudioKeyframes(&tspacket);
+            BufferedWrite(tspacket);
+        }
+        else if (GetStreamData()->IsListeningPID(lpid))
+            GetStreamData()->HandleTSTables(&tspacket);
+        else if (GetStreamData()->IsWritingPID(lpid))
+            BufferedWrite(tspacket);
+    }
+
+    return true;
+}
+
+void R5000Recorder::SetOptionsFromProfile(RecordingProfile *profile,
+                                                 const QString &videodev,
+                                                 const QString &audiodev,
+                                                 const QString &vbidev)
+{
+    (void)videodev;
+    (void)audiodev;
+    (void)vbidev;
+    (void)profile;
+}
+
+// documented in recorderbase.cpp
+bool R5000Recorder::PauseAndWait(int timeout)
+{
+    if (request_pause)
+    {
+        LOG(VB_RECORD, LOG_INFO, LOC +
+            QString("PauseAndWait(%1) -- pause").arg(timeout));
+        if (!IsPaused(true))
+        {
+            StopStreaming();
+            paused = true;
+            pauseWait.wakeAll();
+            if (tvrec)
+                tvrec->RecorderPaused();
+        }
+        QMutex unpause_lock;
+        unpause_lock.lock();
+        unpauseWait.wait(&unpause_lock, timeout);
+    }
+    if (!request_pause && IsPaused(true))
+    {
+        LOG(VB_RECORD, LOG_INFO, LOC +
+            QString("PauseAndWait(%1) -- unpause").arg(timeout));
+        StartStreaming();
+        paused = false;
+    }
+    return paused;
+}
+
+void R5000Recorder::SetStreamData(void)
+{
+    _stream_data->AddMPEGSPListener(this);
+
+    if (_stream_data->DesiredProgram() >= 0)
+        _stream_data->SetDesiredProgram(_stream_data->DesiredProgram());
+}
+
+void R5000Recorder::HandleSingleProgramPAT(ProgramAssociationTable *pat)
+{
+    if (!pat)
+    {
+        LOG(VB_RECORD, LOG_DEBUG, LOC + "HandleSingleProgramPAT(NULL)");
+        return;
+    }
+    int next = (pat->tsheader()->ContinuityCounter()+1)&0xf;
+    pat->tsheader()->SetContinuityCounter(next);
+    BufferedWrite(*(reinterpret_cast<const TSPacket*>(pat->tsheader())));
+}
+
+void R5000Recorder::HandleSingleProgramPMT(ProgramMapTable *pmt)
+{
+    if (!pmt)
+    {
+        LOG(VB_RECORD, LOG_DEBUG, LOC + "HandleSingleProgramPMT(NULL)");
+        return;
+    }
+    int next = (pmt->tsheader()->ContinuityCounter()+1)&0xf;
+    pmt->tsheader()->SetContinuityCounter(next);
+    BufferedWrite(*(reinterpret_cast<const TSPacket*>(pmt->tsheader())));
+}
Index: libs/libmythtv/tv_rec.h
===================================================================
--- libs/libmythtv/tv_rec.h     (revision 1)
+++ libs/libmythtv/tv_rec.h     (revision 4)
@@ -46,6 +46,7 @@
 class V4LChannel;
 class HDHRChannel;
 class CetonChannel;
+class R5000Channel;
 
 class MPEGStreamData;
 class ProgramMapTable;
@@ -264,6 +265,7 @@
                        bool enter_power_save_mode);
     void CloseChannel(void);
     DTVChannel *GetDTVChannel(void);
+    R5000Channel *GetR5000Channel(void);
     V4LChannel *GetV4LChannel(void);
 
     bool SetupSignalMonitor(
Index: libs/libmythtv/r5000signalmonitor.cpp
===================================================================
--- libs/libmythtv/r5000signalmonitor.cpp       (revision 0)
+++ libs/libmythtv/r5000signalmonitor.cpp       (revision 4)
@@ -0,0 +1,294 @@
+// -*- Mode: c++ -*-
+// Copyright (c) 2006, Daniel Thor Kristjansson
+
+#include <pthread.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/select.h>
+
+#include "mythcontext.h"
+#include "mythdbcon.h"
+#include "atscstreamdata.h"
+#include "mpegtables.h"
+#include "atsctables.h"
+#include "r5000channel.h"
+#include "r5000signalmonitor.h"
+
+#define LOC QString("R5kSM(%1): ").arg(channel->GetDevice())
+#define LOC_WARN QString("R5kSM(%1), Warning: ").arg(channel->GetDevice())
+#define LOC_ERR QString("R5kSM(%1), Error: ").arg(channel->GetDevice())
+
+const uint R5000SignalMonitor::kPowerTimeout  = 3000; /* ms */
+const uint R5000SignalMonitor::kBufferTimeout = 5000; /* ms */
+
+QMap<void*,uint> R5000SignalMonitor::pat_keys;
+QMutex           R5000SignalMonitor::pat_keys_lock;
+
+/** \fn R5000SignalMonitor::R5000SignalMonitor(int,R5000Channel*,uint,const char*)
+ *  \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 R5000Channel for card
+ *  \param _flags   Flags to start with
+ *  \param _name    Name for Qt signal debugging
+ */
+R5000SignalMonitor::R5000SignalMonitor(
+    int db_cardnum,
+    R5000Channel *_channel,
+    uint64_t _flags, const char *_name) :
+    DTVSignalMonitor(db_cardnum, _channel, _flags),
+    dtvMonitorRunning(false),
+    stb_needs_retune(true),
+    stb_needs_to_wait_for_pat(false),
+    stb_needs_to_wait_for_power(false)
+{
+    LOG(VB_CHANNEL, LOG_DEBUG, LOC + "ctor");
+
+    signalStrength.SetThreshold(65);
+
+    AddFlags(kSigMon_WaitForSig);
+
+    stb_needs_retune =
+        (R5000Device::kAVCPowerOff == _channel->GetPowerState());
+}
+
+/** \fn R5000SignalMonitor::~R5000SignalMonitor()
+ *  \brief Stops signal monitoring and table monitoring threads.
+ */
+R5000SignalMonitor::~R5000SignalMonitor()
+{
+    LOG(VB_CHANNEL, LOG_DEBUG, LOC + "dtor");
+    Stop();
+}
+
+/** \fn R5000SignalMonitor::Stop(void)
+ *  \brief Stop signal monitoring and table monitoring threads.
+ */
+void R5000SignalMonitor::Stop(void)
+{
+    LOG(VB_CHANNEL, LOG_DEBUG, LOC + "Stop() -- begin");
+    SignalMonitor::Stop();
+    if (dtvMonitorRunning)
+    {
+        dtvMonitorRunning = false;
+        pthread_join(table_monitor_thread, NULL);
+    }
+    LOG(VB_CHANNEL, LOG_DEBUG, LOC + "Stop() -- end");
+}
+
+void R5000SignalMonitor::HandlePAT(const ProgramAssociationTable *pat)
+{
+    AddFlags(kDTVSigMon_PATSeen);
+
+    R5000Channel *fwchan = dynamic_cast<R5000Channel*>(channel);
+    if (!fwchan)
+        return;
+
+    bool crc_bogus = !fwchan->GetR5000Device()->IsSTBBufferCleared();
+    if (crc_bogus && stb_needs_to_wait_for_pat &&
+        (stb_wait_for_pat_timer.elapsed() < (int)kBufferTimeout))
+    {
+        LOG(VB_CHANNEL, LOG_DEBUG, LOC + "HandlePAT() ignoring PAT");
+        uint tsid = pat->TransportStreamID();
+        GetStreamData()->SetVersionPAT(tsid, -1,0);
+        return;
+    }
+
+    if (crc_bogus && stb_needs_to_wait_for_pat)
+    {
+        LOG(VB_GENERAL, LOG_DEBUG, LOC_WARN + "Wait for valid PAT timed out");
+        stb_needs_to_wait_for_pat = false;
+    }
+
+    DTVSignalMonitor::HandlePAT(pat);
+}
+
+void R5000SignalMonitor::HandlePMT(uint pnum, const ProgramMapTable *pmt)
+{
+    LOG(VB_CHANNEL, LOG_DEBUG, LOC + "HandlePMT()");
+
+    AddFlags(kDTVSigMon_PMTSeen);
+
+    if (!HasFlags(kDTVSigMon_PATMatch))
+    {
+        GetStreamData()->SetVersionPMT(pnum, -1,0);
+        LOG(VB_CHANNEL, LOG_DEBUG, LOC + "HandlePMT() ignoring PMT");
+        return;
+    }
+
+    DTVSignalMonitor::HandlePMT(pnum, pmt);
+}
+
+void *R5000SignalMonitor::TableMonitorThread(void *param)
+{
+    R5000SignalMonitor *mon = (R5000SignalMonitor*) param;
+    mon->RunTableMonitor();
+    return NULL;
+}
+
+void R5000SignalMonitor::RunTableMonitor(void)
+{
+    stb_needs_to_wait_for_pat = true;
+    stb_wait_for_pat_timer.start();
+    dtvMonitorRunning = true;
+
+    LOG(VB_CHANNEL, LOG_DEBUG, LOC + "RunTableMonitor(): -- begin");
+
+    R5000Channel *lchan = dynamic_cast<R5000Channel*>(channel);
+    if (!lchan)
+    {
+        LOG(VB_CHANNEL, LOG_DEBUG, LOC + "RunTableMonitor(): -- err end");
+        dtvMonitorRunning = false;
+        return;
+    }
+
+    R5000Device *dev = lchan->GetR5000Device();
+
+    dev->OpenPort();
+    dev->AddListener(this);
+
+    while (dtvMonitorRunning && GetStreamData())
+        usleep(100000);
+
+    LOG(VB_CHANNEL, LOG_DEBUG, LOC + "RunTableMonitor(): -- shutdown ");
+
+    dev->RemoveListener(this);
+    dev->ClosePort();
+
+    dtvMonitorRunning = false;
+
+    LOG(VB_CHANNEL, LOG_DEBUG, LOC + "RunTableMonitor(): -- end");
+}
+
+void R5000SignalMonitor::AddData(const unsigned char *data, uint len)
+{
+    if (!dtvMonitorRunning)
+        return;
+
+    if (GetStreamData())
+        GetStreamData()->ProcessData((unsigned char *)data, len);
+}
+
+/** \fn R5000SignalMonitor::UpdateValues(void)
+ *  \brief Fills in frontend stats and emits status Qt signals.
+ *
+ *   This function uses five ioctl's FE_READ_SNR, FE_READ_SIGNAL_STRENGTH
+ *   FE_READ_BER, FE_READ_UNCORRECTED_BLOCKS, and FE_READ_STATUS to obtain
+ *   statistics from the frontend.
+ *
+ *   This is automatically called by MonitorLoop(), after Start()
+ *   has been used to start the signal monitoring thread.
+ */
+void R5000SignalMonitor::UpdateValues(void)
+{
+    if (!running || exit)
+        return;
+
+    //if (!IsChannelTuned())
+      //  return;
+
+    if (dtvMonitorRunning)
+    {
+        EmitStatus();
+        if (IsAllGood())
+            SendMessageAllGood();
+        // TODO dtv signals...
+
+        update_done = true;
+        return;
+    }
+
+    if (stb_needs_to_wait_for_power &&
+        (stb_wait_for_power_timer.elapsed() < (int)kPowerTimeout))
+    {
+        return;
+    }
+    stb_needs_to_wait_for_power = false;
+
+    R5000Channel *fwchan = dynamic_cast<R5000Channel*>(channel);
+    if (!fwchan)
+        return;
+
+    if (HasFlags(kFWSigMon_WaitForPower) && !HasFlags(kFWSigMon_PowerMatch))
+    {
+        bool retried = false;
+        while (true)
+        {
+            R5000Device::PowerState power = fwchan->GetPowerState();
+            if (R5000Device::kAVCPowerOn == power)
+            {
+                AddFlags(kFWSigMon_PowerSeen | kFWSigMon_PowerMatch);
+            }
+            else if (R5000Device::kAVCPowerOff == power)
+            {
+                AddFlags(kFWSigMon_PowerSeen);
+                fwchan->SetPowerState(true);
+                stb_wait_for_power_timer.start();
+                stb_needs_to_wait_for_power = true;
+            }
+            else
+            {
+                bool qfailed = (R5000Device::kAVCPowerQueryFailed == power);
+                if (qfailed && !retried)
+                {
+                    retried = true;
+                    continue;
+                }
+
+                LOG(VB_RECORD, LOG_INFO, "Can't determine if STB is power on, "
+                        "assuming it is...");
+                AddFlags(kFWSigMon_PowerSeen | kFWSigMon_PowerMatch);
+            }
+            break;
+        }
+    }
+
+    bool isLocked = !HasFlags(kFWSigMon_WaitForPower) ||
+        HasFlags(kFWSigMon_WaitForPower | kFWSigMon_PowerMatch);
+
+    if (isLocked && stb_needs_retune)
+    {
+        fwchan->Retune();
+        isLocked = stb_needs_retune = false;
+    }
+
+    // Set SignalMonitorValues from info from card.
+    {
+        QMutexLocker locker(&statusLock);
+        signalStrength.SetValue(isLocked ? 100 : 0);
+        signalLock.SetValue(isLocked ? 1 : 0);
+    }
+
+    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))
+    {
+        pthread_create(&table_monitor_thread, NULL,
+                       TableMonitorThread, this);
+
+        LOG(VB_CHANNEL, LOG_DEBUG, LOC + "UpdateValues() -- "
+                "Waiting for table monitor to start");
+
+        while (!dtvMonitorRunning)
+            usleep(50);
+
+        LOG(VB_CHANNEL, LOG_DEBUG, LOC + "UpdateValues() -- "
+                "Table monitor started");
+    }
+
+    update_done = true;
+}
Index: libs/libmythtv/tv_rec.cpp
===================================================================
--- libs/libmythtv/tv_rec.cpp   (revision 1)
+++ libs/libmythtv/tv_rec.cpp   (revision 4)
@@ -29,6 +29,7 @@
 #include "remoteutil.h"
 #include "ringbuffer.h"
 #include "mythlogging.h"
+#include "r5000channel.h"
 #include "v4lchannel.h"
 #include "dialogbox.h"
 #include "jobqueue.h"
@@ -1155,6 +1156,15 @@
     return dynamic_cast<DTVChannel*>(channel);
 }
 
+R5000Channel *TVRec::GetR5000Channel(void)
+{
+#ifdef USING_R5000
+    return dynamic_cast<R5000Channel*>(channel);
+#else
+    return NULL;
+#endif // USING_R5000
+}
+
 V4LChannel *TVRec::GetV4LChannel(void)
 {
 #ifdef USING_V4L2
Index: libs/libmythtv/libmythtv.pro
===================================================================
--- libs/libmythtv/libmythtv.pro        (revision 1)
+++ libs/libmythtv/libmythtv.pro        (revision 4)
@@ -664,6 +664,25 @@
         DEFINES += USING_ASI
     }
 
+    #Support for R5000 usb device
+    using_r5000 {
+        HEADERS += r5000channel.h           r5000recorder.h
+        HEADERS += r5000signalmonitor.h     r5000device.h
+       HEADERS += r5000/r5000.h            r5000/libusb_augment.h
+       HEADERS += r5000/r5000_internal.h   r5000/r5000init.h
+
+        SOURCES += r5000channel.cpp         r5000recorder.cpp
+        SOURCES += r5000signalmonitor.cpp   r5000device.cpp
+       SOURCES += r5000/r5000.c            r5000/libusb_augment.c
+       SOURCES += r5000/r5k_vip.c          r5000/r5k_pes.c
+       SOURCES += r5000/r5k_sat.c          r5000/r5k_misc.c
+       SOURCES += r5000/r5k_vip_buttons.c  r5000/r5k_directv_buttons.c
+       SOURCES += r5000/r5k_dish6000_buttons.c
+
+       LIBS += -lusb
+        DEFINES += USING_R5000
+    }
+
     DEFINES += USING_BACKEND
 }
 
Index: libs/libmythtv/transporteditor.cpp
===================================================================
--- libs/libmythtv/transporteditor.cpp  (revision 1)
+++ libs/libmythtv/transporteditor.cpp  (revision 4)
@@ -739,7 +739,8 @@
         left->addChild(new Modulation(id, nType));
     }
     else if ((CardUtil::FIREWIRE == nType) ||
-             (CardUtil::FREEBOX  == nType))
+             (CardUtil::FREEBOX  == nType) ||
+             (CardUtil::R5000    == nType))
     {
         left->addChild(new DTVStandard(id, true, true));
     }
Index: libs/libmythtv/r5000device.cpp
===================================================================
--- libs/libmythtv/r5000device.cpp      (revision 0)
+++ libs/libmythtv/r5000device.cpp      (revision 4)
@@ -0,0 +1,464 @@
+/**
+ *  R5000Device
+ *  Copyright (c) 2008 by Alan Nisota
+ *  Copyright (c) 2005 by Jim Westfall
+ *  Distributed as part of MythTV under GPL v2 and later.
+ */
+
+// C++ headers
+#include <algorithm>
+
+// Qt headers
+#include <QMap>
+
+// for usleep
+#include <unistd.h> 
+
+// MythTV headers
+#include "r5000device.h"
+#include "mythcontext.h"
+#include "mythlogging.h"
+#include "pespacket.h"
+#include "mthread.h"
+#include "mythtimer.h"
+
+#define LOC      QString("R5kDev: ")
+#define LOC_WARN QString("R5kDev, Warning: ")
+#define LOC_ERR  QString("R5kDev, Error: ")
+
+static int r5k_init = 0;
+QMap<uint64_t,QString> R5000Device::s_id_to_model;
+QMutex                 R5000Device::s_static_lock;
+
+unsigned int r5000_device_tspacket_handler(unsigned char *tspacket, int len, void *callback_data)
+{
+    R5000Device *fw = (R5000Device*) callback_data;
+    if (! fw)
+        return 0;
+    if (len > 0)
+        fw->BroadcastToListeners(tspacket, len);
+    return 1;
+}
+
+void r5000_msg(char * msg)
+{
+    LOG(VB_GENERAL, LOG_INFO, LOC +
+        QString("R5kLib: ") + msg);
+}
+
+class R5kPriv
+{
+  public:
+    R5kPriv() :
+        reset_timer_on(false),
+        run_port_handler(false), is_port_handler_running(false),
+        channel(-1),
+        is_streaming(false)
+    {
+    }
+
+    bool             reset_timer_on;
+    MythTimer        reset_timer;
+
+    bool             run_port_handler;
+    bool             is_port_handler_running;
+    QMutex           start_stop_port_handler_lock;
+
+    int              channel;
+
+    bool             is_streaming;
+
+    QDateTime        stop_streaming_timer;
+    pthread_t        port_handler_thread;
+
+    static QMutex           s_lock;
+};
+QMutex          R5kPriv::s_lock;
+
+//callback functions
+void *r5000_device_port_handler_thunk(void *param);
+
+R5000Device::R5000Device(int type, QString serial) :
+    m_type(type),
+    m_serial(serial),
+    m_last_channel(""),      m_last_crc(0),
+    m_buffer_cleared(true), m_open_port_cnt(0),
+    m_lock(),          m_priv(new R5kPriv())
+{
+    QMutexLocker locker(&s_static_lock);
+    usbdev = NULL;
+    if (! r5k_init)
+      r5k_init = r5000_init(r5000_msg);
+}
+
+R5000Device::~R5000Device()
+{
+    if (usbdev)
+    {
+        LOG(VB_GENERAL, LOG_DEBUG, LOC_ERR + "ctor called with open port");
+        while (usbdev)
+            ClosePort();
+    }
+
+    if (m_priv)
+    {
+        delete m_priv;
+        m_priv = NULL;
+    }
+}
+
+void R5000Device::AddListener(TSDataListener *listener)
+{
+    QMutexLocker locker(&m_lock);
+    if (listener)
+    {
+        vector<TSDataListener*>::iterator it =
+            find(m_listeners.begin(), m_listeners.end(), listener);
+
+        if (it == m_listeners.end())
+            m_listeners.push_back(listener);
+    }
+
+    LOG(VB_RECORD, LOG_INFO, LOC +
+        QString("AddListener() %1").arg(m_listeners.size()));
+    if (!m_listeners.empty())
+    {
+        StartStreaming();
+    }
+}
+
+void R5000Device::RemoveListener(TSDataListener *listener)
+{
+    QMutexLocker locker(&m_lock);
+    vector<TSDataListener*>::iterator it = m_listeners.end();
+
+    do
+    {
+        it = find(m_listeners.begin(), m_listeners.end(), listener);
+        if (it != m_listeners.end())
+            m_listeners.erase(it);
+    }
+    while (it != m_listeners.end());
+
+    LOG(VB_RECORD, LOG_INFO, LOC +
+        QString("RemoveListener() %1").arg(m_listeners.size()));
+    if (m_listeners.empty())
+    {
+        StopStreaming();
+    }
+}
+
+bool R5000Device::StartStreaming(void)
+{
+    if (m_priv->is_streaming)
+        return m_priv->is_streaming;
+
+    if (! usbdev)
+    {
+        LOG(VB_GENERAL, LOG_DEBUG, LOC_ERR + "Device not open");
+        return false;
+    }
+    if (r5000_start_stream(usbdev))
+    {
+        m_priv->is_streaming = true;
+    }
+    else
+    {
+        LOG(VB_GENERAL, LOG_DEBUG, LOC_ERR + "Starting A/V streaming ");
+    }
+
+    LOG(VB_RECORD, LOG_DEBUG, LOC + "Starting A/V streaming -- done");
+
+    return m_priv->is_streaming;
+}
+
+bool R5000Device::StopStreaming(void)
+{
+    if (m_priv->is_streaming)
+    {
+        LOG(VB_RECORD, LOG_DEBUG, LOC + "Stopping A/V streaming -- really");
+
+        m_priv->is_streaming = false;
+
+        r5000_stop_stream(usbdev);
+    }
+
+    LOG(VB_RECORD, LOG_DEBUG, LOC + "Stopped A/V streaming");
+
+    return true;
+}
+
+bool R5000Device::SetPowerState(bool on)
+{
+    QMutexLocker locker(&m_lock);
+    QString cmdStr = (on) ? "on" : "off";
+    LOG(VB_RECORD, LOG_DEBUG, LOC + QString("Powering %1").arg(cmdStr));
+    r5000_power_on_off(usbdev, on);
+    return true;
+}
+
+R5000Device::PowerState R5000Device::GetPowerState(void)
+{
+    QMutexLocker locker(&m_lock);
+    int on_off;
+
+    LOG(VB_CHANNEL, LOG_DEBUG, LOC + "Requesting STB Power State");
+    on_off = r5000_get_power_state(usbdev);
+    LOG(VB_CHANNEL, LOG_DEBUG, LOC + (on_off ? "On" : "Off"));
+    return on_off ? kAVCPowerOn : kAVCPowerOff;
+}
+
+bool R5000Device::SetChannel(const QString &panel_model,
+                                const QString &channel, uint mpeg_prog)
+{
+    LOG(VB_CHANNEL, LOG_DEBUG, QString("SetChannel(model %1, chan %2 mpeg_prog %3)")
+            .arg(panel_model).arg(channel).arg(mpeg_prog));
+
+    QMutexLocker locker(&m_lock);
+    LOG(VB_CHANNEL, LOG_DEBUG, "SetChannel() -- locked");
+    if (! r5000_change_channel(usbdev, channel.toAscii().constData(), mpeg_prog))
+        LOG(VB_GENERAL, LOG_DEBUG, LOC + "Failed to set channel");
+    return true;
+}
+
+void R5000Device::BroadcastToListeners(
+    const unsigned char *data, uint dataSize)
+{
+    QMutexLocker locker(&m_lock);
+    if ((dataSize >= TSPacket::kSize) && (data[0] == SYNC_BYTE) &&
+        ((data[1] & 0x1f) == 0) && (data[2] == 0))
+    {
+        ProcessPATPacket(*((const TSPacket*)data));
+    }
+
+    vector<TSDataListener*>::iterator it = m_listeners.begin();
+    for (; it != m_listeners.end(); ++it)
+        (*it)->AddData(data, dataSize);
+}
+
+void R5000Device::SetLastChannel(const QString &channel)
+{
+    m_buffer_cleared = (channel == m_last_channel);
+    m_last_channel   = channel;
+
+    LOG(VB_GENERAL, LOG_DEBUG, QString("SetLastChannel(%1): cleared: %2")
+            .arg(channel).arg(m_buffer_cleared ? "yes" : "no"));
+}
+
+void R5000Device::ProcessPATPacket(const TSPacket &tspacket)
+{
+    if (!tspacket.TransportError() && !tspacket.Scrambled() &&
+        tspacket.HasPayload() && tspacket.PayloadStart() && !tspacket.PID())
+    {
+        PESPacket pes = PESPacket::View(tspacket);
+        uint crc = pes.CalcCRC();
+        m_buffer_cleared |= (crc != m_last_crc);
+        m_last_crc = crc;
+#if 0
+        LOG(VB_RECORD, LOG_DEBUG, LOC +
+                QString("ProcessPATPacket: CRC 0x%1 cleared: %2")
+                .arg(crc,0,16).arg(m_buffer_cleared ? "yes" : "no"));
+#endif
+    }
+    else
+    {
+        LOG(VB_GENERAL, LOG_DEBUG, LOC_ERR + "Can't handle large PAT's");
+    }
+}
+
+QString R5000Device::GetModelName(uint vendor_id, uint model_id)
+{
+    QMutexLocker locker(&s_static_lock);
+/*
+    if (s_id_to_model.empty())
+        fw_init(s_id_to_model);
+
+    QString ret = s_id_to_model[(((uint64_t) vendor_id) << 32) | model_id];
+
+    if (ret.isEmpty())
+        return "GENERIC";
+*/
+    return "R5000";
+}
+
+bool R5000Device::IsSTBSupported(const QString &panel_model)
+{
+    return true;
+}
+
+bool R5000Device::OpenPort(void)
+{
+    LOG(VB_RECORD, LOG_DEBUG, LOC + "Starting Port Handler Thread");
+    QMutexLocker mlocker(&m_lock);
+    LOG(VB_RECORD, LOG_DEBUG, LOC + "Starting Port Handler Thread -- locked");
+    if (usbdev)
+    {
+        m_open_port_cnt++;
+        return true;
+    }
+
+    if (m_serial.length())
+    {
+      LOG(VB_RECORD, LOG_DEBUG, LOC + QString("Opening R5000 device type %1 with serial#: "+ m_serial).arg(m_type));
+      usbdev = r5000_open((r5ktype_t)m_type, r5000_device_tspacket_handler, this, m_serial.toAscii().constData());
+    }
+    else
+    {
+      LOG(VB_RECORD, LOG_DEBUG, LOC + "Opening R5000 device with unknown serial#");
+      usbdev = r5000_open((r5ktype_t)m_type, r5000_device_tspacket_handler, this, NULL);
+    }
+    if (! usbdev)
+    {
+        LOG(VB_GENERAL, LOG_DEBUG, LOC + "Failed to open R5000 device");
+        return false;
+    }
+
+    LOG(VB_RECORD, LOG_DEBUG, LOC + "Starting port handler thread");
+    m_priv->run_port_handler = true;
+    pthread_create(&m_priv->port_handler_thread, NULL,
+                   r5000_device_port_handler_thunk, this);
+
+    LOG(VB_RECORD, LOG_DEBUG, LOC + "Waiting for port handler thread to start");
+    while (!m_priv->is_port_handler_running)
+    {
+        m_lock.unlock();
+        usleep(5000);
+        m_lock.lock();
+    }
+
+    LOG(VB_RECORD, LOG_DEBUG, LOC + "Port handler thread started");
+
+    m_open_port_cnt++;
+
+    return true;
+}
+
+bool R5000Device::ClosePort(void)
+{
+    LOG(VB_RECORD, LOG_DEBUG, LOC + "Stopping Port Handler Thread");
+    QMutexLocker locker(&m_priv->start_stop_port_handler_lock);
+    LOG(VB_RECORD, LOG_DEBUG, LOC + "Stopping Port Handler Thread -- locked");
+
+    QMutexLocker mlocker(&m_lock);
+
+    LOG(VB_RECORD, LOG_DEBUG, LOC + "ClosePort()");
+
+    if (m_open_port_cnt < 1)
+        return false;
+
+    m_open_port_cnt--;
+
+    if (m_open_port_cnt != 0)
+        return true;
+
+    if (!usbdev)
+        return false;
+
+    LOG(VB_RECORD, LOG_DEBUG, LOC + "Waiting for port handler thread to stop");
+    m_priv->run_port_handler = false;
+    while (m_priv->is_port_handler_running)
+    {
+        m_lock.unlock();
+        usleep(5000);
+        m_lock.lock();
+    }
+    LOG(VB_RECORD, LOG_DEBUG, LOC + "Joining port handler thread");
+    pthread_join(m_priv->port_handler_thread, NULL);
+
+    r5000_close(usbdev);
+    usbdev = NULL;
+
+    return true;
+}
+
+void *r5000_device_port_handler_thunk(void *param)
+{
+    R5000Device *mon = (R5000Device*) param;
+    mon->RunPortHandler();
+    return NULL;
+}
+
+void R5000Device::RunPortHandler(void)
+{
+    LOG(VB_RECORD, LOG_DEBUG, LOC + "RunPortHandler -- start");
+    m_lock.lock();
+    LOG(VB_RECORD, LOG_DEBUG, LOC + "RunPortHandler -- got first lock");
+    m_priv->is_port_handler_running = true;
+    m_lock.unlock();
+
+    while (m_priv->run_port_handler)
+    {
+        if (m_priv->is_streaming)
+        {
+            // This will timeout after 10ms regardless of data availability
+            r5000_loop_iterate(usbdev, 10);
+        }
+        else
+        {
+            usleep(10000);
+        }
+    }
+
+    m_lock.lock();
+    m_priv->is_port_handler_running = false;
+    m_lock.unlock();
+    LOG(VB_RECORD, LOG_DEBUG, LOC + "RunPortHandler -- end");
+}
+
+QStringList R5000Device::GetSTBList(void)
+{
+    QStringList STBList;
+    int i;
+    r5kenum_t r5k_stbs;
+    if (! r5k_init)
+    {
+        r5k_init = r5000_init(r5000_msg);
+        if (! r5k_init)
+            return STBList;
+    }
+    if (! r5000_find_stbs(&r5k_stbs))
+        LOG(VB_GENERAL, LOG_DEBUG, LOC + "Locating R5000 devices failed."
+                                    "  This may be a permission problem");
+
+    for (i = 0; i < r5k_stbs.count; i++)
+        STBList.append((char *)r5k_stbs.serial[i]);
+    return STBList;
+}
+
+int R5000Device::GetDeviceType(const QString &r5ktype)
+{
+    QString type = r5ktype.toUpper();
+    if (type == "DIRECTV")
+    {
+        return R5K_STB_DIRECTV;
+    }
+    else if ("VIP211/VIP622/DISH411" == type ||
+               "VIP211/VIP422"         == type ||
+               "VIP211"                == type ||
+               "VIP411"                == type)
+    {
+        return R5K_STB_VIP211;
+    }
+    else if ("STARCHOICE/DSR" == type)
+    {
+        return R5K_STB_DSR;
+    }
+    else if ("HDD-200" == type)
+    {
+        return R5K_STB_HDD;
+    }
+    else if ("VIP622"  == type ||
+               "VIP722"  == type ||
+               "BEV9242" == type)
+    {
+        return R5K_STB_VIP622;
+    }
+    else if ("DISH6000" == type)
+    {
+        return R5K_STB_DISH6000;
+    }
+    else
+    {
+      return R5K_STB_VIP211;
+    }
+}
Index: libs/libmythtv/cardutil.h
===================================================================
--- libs/libmythtv/cardutil.h   (revision 1)
+++ libs/libmythtv/cardutil.h   (revision 4)
@@ -66,6 +66,7 @@
         DEMO      = 15,
         ASI       = 16,
         CETON     = 17,
+       R5000     = 18,
     };
 
     static enum CARD_TYPES toCardType(const QString &name)
@@ -106,6 +107,8 @@
             return ASI;
         if ("CETON" == name)
             return CETON;
+       if ("R5000" == name)
+            return R5000;
         return ERROR_UNKNOWN;
     }
 
@@ -115,7 +118,8 @@
             (rawtype != "DVB")       && (rawtype != "FIREWIRE") &&
             (rawtype != "HDHOMERUN") && (rawtype != "FREEBOX")  &&
             (rawtype != "IMPORT")    && (rawtype != "DEMO")     &&
-            (rawtype != "ASI")       && (rawtype != "CETON");
+            (rawtype != "ASI")       && (rawtype != "CETON") &&
+            (rawtype != "R5000") ;
     }
 
     static bool         IsV4L(const QString &rawtype)
@@ -135,7 +139,8 @@
         return
             (rawtype == "FIREWIRE")  || (rawtype == "HDPVR") ||
             (rawtype == "IMPORT")    || (rawtype == "DEMO")  ||
-            (rawtype == "GO7007")    || (rawtype == "MJPEG");
+            (rawtype == "GO7007")    || (rawtype == "MJPEG") ||
+            (rawtype == "R5000");
     }
     static QString      GetScanableCardTypes(void);
 
@@ -182,7 +187,7 @@
             (rawtype == "FIREWIRE")  || (rawtype == "HDHOMERUN") ||
             (rawtype == "FREEBOX")   || (rawtype == "ASI")       ||
             (rawtype == "IMPORT")    || (rawtype == "DEMO")      ||
-            (rawtype == "CETON");
+            (rawtype == "CETON")     || (rawtype == "R5000");
     }
 
     // Card creation and deletion
Index: libs/libmythtv/r5000channel.cpp
===================================================================
--- libs/libmythtv/r5000channel.cpp     (revision 0)
+++ libs/libmythtv/r5000channel.cpp     (revision 4)
@@ -0,0 +1,215 @@
+/**
+ *  R5000Channel
+ *  Copyright (c) 2005 by Jim Westfall, Dave Abrahams
+ *  Copyright (c) 2006 by Daniel Kristjansson
+ *  Distributed as part of MythTV under GPL v2 and later.
+ */
+
+#include "mythcontext.h"
+#include "tv_rec.h"
+#include "r5000channel.h"
+
+#define LOC QString("R5kChan(%1): ").arg(GetDevice())
+#define LOC_WARN QString("R5kChan(%1), Warning: ").arg(GetDevice())
+#define LOC_ERR QString("R5kChan(%1), Error: ").arg(GetDevice())
+
+R5000Channel::R5000Channel(TVRec *parent, const QString &_videodevice,const QString &_r5ktype, bool pocc) :
+    DTVChannel(parent),
+    videodevice(_videodevice),
+    power_on_channel_change(pocc),
+    device(NULL),
+    current_channel(""),
+    current_mpeg_prog(0),
+    isopen(false),
+    tuning_delay(0)
+{
+    int type = R5000Device::GetDeviceType(_r5ktype);
+    device = new R5000Device(type, videodevice);
+
+    InitializeInputs();
+}
+
+bool R5000Channel::SetChannelByString(const QString &channum)
+{
+    QString loc = LOC + QString("SetChannelByString(%1)").arg(channum);
+    bool ok = false;
+    LOG(VB_CHANNEL, LOG_DEBUG, LOC);
+
+    InputMap::const_iterator it = m_inputs.find(m_currentInputID);
+    if (it == m_inputs.end())
+        return false;
+
+    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))
+    {
+        LOG(VB_GENERAL, LOG_DEBUG, LOC + " " + QString(
+                    "Requested channel '%1' is on input '%2' "
+                    "which is in a busy input group")
+                .arg(channum).arg(m_currentInputID));
+
+        return false;
+    }
+    uint mplexid_restriction;
+    if (!IsInputAvailable(m_currentInputID, mplexid_restriction))
+    {
+        LOG(VB_GENERAL, LOG_DEBUG, LOC + " " + QString(
+                    "Requested channel '%1' is on input '%2' "
+                    "which is in a busy input group")
+                .arg(channum).arg(m_currentInputID));
+
+        return false;
+    }
+
+    if (!(*it)->externalChanger.isEmpty())
+    {
+        ok = ChangeExternalChannel((*it)->externalChanger, freqid);
+        // -1 resets any state without executing a channel change
+        device->SetChannel(fw_opts.model, 0, mpeg_prog_num);
+        SetSIStandard("mpeg");
+        SetDTVInfo(0,0,0,0,1);
+    }
+    else
+    {
+        ok = isopen && SetChannelByNumber(freqid, mpeg_prog_num);
+    }
+
+    if (ok)
+    {
+        if (tuning_delay) {
+            LOG(VB_CHANNEL, LOG_DEBUG, LOC + " " + QString(
+                    "Adding additional delay: %1ms").arg(tuning_delay));
+            usleep(tuning_delay * 1000);
+        }
+        // Set the current channum to the new channel's channum
+        QString tmp = channum;
+        tmp.detach();
+        m_curchannelname = tmp;
+        tmp.detach();
+        (*it)->startChanNum = tmp;
+    }
+
+    LOG(VB_CHANNEL, LOG_DEBUG, LOC + " " + ((ok) ? "success" : "failure"));
+
+    return ok;
+}
+
+bool R5000Channel::Open(void)
+{
+    LOG(VB_CHANNEL, LOG_DEBUG, LOC + "Open()");
+
+    if (m_inputs.find(m_currentInputID) == m_inputs.end())
+        return false;
+
+    if (!device)
+        return false;
+
+    if (isopen)
+        return true;
+
+    if (!device->OpenPort())
+        return false;
+
+    isopen = true;
+
+    return true;
+}
+
+void R5000Channel::Close(void)
+{
+    LOG(VB_CHANNEL, LOG_DEBUG, LOC + "Close()");
+    if (isopen)
+    {
+        device->ClosePort();
+        isopen = false;
+    }
+}
+
+QString R5000Channel::GetDevice(void) const
+{
+    return videodevice;
+}
+
+bool R5000Channel::SetPowerState(bool on)
+{
+    if (!isopen)
+    {
+        LOG(VB_GENERAL, LOG_DEBUG, LOC_ERR +
+                "SetPowerState() called on closed R5000Channel.");
+
+        return false;
+    }
+    if (power_on_channel_change)
+        return R5000Device::kAVCPowerOn;
+
+    return device->SetPowerState(on);
+}
+
+R5000Device::PowerState R5000Channel::GetPowerState(void) const
+{
+    if (!isopen)
+    {
+        LOG(VB_GENERAL, LOG_DEBUG, LOC_ERR +
+                "GetPowerState() called on closed R5000Channel.");
+
+        return R5000Device::kAVCPowerQueryFailed;
+    }
+
+    if (power_on_channel_change)
+        return R5000Device::kAVCPowerOn;
+
+    return device->GetPowerState();
+}
+
+bool R5000Channel::Retune(void)
+{
+    LOG(VB_CHANNEL, LOG_DEBUG, LOC + "Retune()");
+
+    if (! power_on_channel_change && R5000Device::kAVCPowerOff == GetPowerState())
+    {
+        LOG(VB_GENERAL, LOG_DEBUG, LOC_ERR +
+                "STB is turned off, must be on to retune.");
+
+        return false;
+    }
+
+    if (current_channel.length())
+        return SetChannelByNumber(current_channel, current_mpeg_prog);
+
+    return false;
+}
+
+bool R5000Channel::SetChannelByNumber(const QString &channel, int mpeg_prog)
+{
+    LOG(VB_CHANNEL, LOG_DEBUG, QString("SetChannelByNumber(%1)").arg(channel));
+    current_channel = channel;
+    current_mpeg_prog = mpeg_prog;
+
+    if (! power_on_channel_change && R5000Device::kAVCPowerOff == GetPowerState())
+    {
+        LOG(VB_GENERAL, LOG_DEBUG, 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...
+    }
+
+    QString tmpchan = (power_on_channel_change ? "P" : "") + channel;
+    if (! device->SetChannel(fw_opts.model, tmpchan, mpeg_prog))
+        return false;
+
+    SetSIStandard("mpeg");
+    SetDTVInfo(0,0,0,0,1);
+
+    return true;
+}
Index: libs/libmythtv/channelbase.cpp
===================================================================
--- libs/libmythtv/recorders/channelbase.cpp    (revision 1)
+++ libs/libmythtv/recorders/channelbase.cpp    (revision 4)
@@ -26,6 +26,7 @@
 #include "firewirechannel.h"
 #include "mythcorecontext.h"
 #include "cetonchannel.h"
+#include "r5000channel.h"
 #include "dummychannel.h"
 #include "tvremoteutil.h"
 #include "channelutil.h"
@@ -1197,6 +1198,15 @@
         channel = new CetonChannel(tvrec, genOpt.videodev);
 #endif
     }
+    else if (genOpt.cardtype == "R5000")
+    {
+#ifdef USING_R5000
+        channel = new R5000Channel(tvrec, genOpt.videodev, fwOpt.model, dvbOpt.dvb_on_demand);
+        dynamic_cast<R5000Channel*>(channel)->SetSlowTuning(
+            dvbOpt.dvb_tuning_delay);
+#endif
+    }
+
     else if (CardUtil::IsV4L(genOpt.cardtype))
     {
 #ifdef USING_V4L2
Index: libs/libmythtv/r5000recorder.h
===================================================================
--- libs/libmythtv/r5000recorder.h      (revision 0)
+++ libs/libmythtv/r5000recorder.h      (revision 4)
@@ -0,0 +1,69 @@
+/**
+ *  R5000Recorder
+ *  Copyright (c) 2005 by Jim Westfall
+ *  Distributed as part of MythTV under GPL v2 and later.
+ */
+
+#ifndef _R5000RECORDER_H_
+#define _R5000RECORDER_H_
+
+// MythTV headers
+#include "dtvrecorder.h"
+#include "tspacket.h"
+#include "streamlisteners.h"
+
+class TVRec;
+class R5000Channel;
+
+/** \class R5000Recorder
+ *  \brief This is a specialization of DTVRecorder used to
+ *         handle DVB and ATSC streams from a firewire input.
+ *
+ *  \sa DTVRecorder
+ */
+class R5000Recorder : public DTVRecorder,
+//                         public MPEGSingleProgramStreamListener,
+                         public TSDataListener
+{
+    friend class MPEGStreamData;
+    friend class TSPacketProcessor;
+
+  public:
+    R5000Recorder(TVRec *rec, R5000Channel *chan);
+    virtual ~R5000Recorder();
+
+    // Commands
+    bool Open(void);
+    void Close(void);
+
+    void StartStreaming(void);
+    void StopStreaming(void);
+
+    void StartRecording(void);
+    void run(void);
+    bool PauseAndWait(int timeout = 100);
+
+    void AddData(const unsigned char *data, uint dataSize);
+    bool ProcessTSPacket(const TSPacket &tspacket);
+
+    // Sets
+    void SetOptionsFromProfile(RecordingProfile *profile,
+                               const QString &videodev,
+                               const QString &audiodev,
+                               const QString &vbidev);
+    void SetStreamData(void);
+
+    // MPEG Single Program
+    void HandleSingleProgramPAT(ProgramAssociationTable*);
+    void HandleSingleProgramPMT(ProgramMapTable*);
+
+  protected:
+    R5000Recorder(TVRec *rec);
+
+  private:
+    R5000Channel       *channel;
+    bool                   isopen;
+    vector<unsigned char>  buffer;
+};
+
+#endif //  _R5000RECORDER_H_
Index: libs/libmythtv/r5000channel.h
===================================================================
--- libs/libmythtv/r5000channel.h       (revision 0)
+++ libs/libmythtv/r5000channel.h       (revision 4)
@@ -0,0 +1,56 @@
+/**
+ *  R5000Channel
+ *  Copyright (c) 2008 by Alan Nisota
+ *  Copyright (c) 2005 by Jim Westfall and Dave Abrahams
+ *  Distributed as part of MythTV under GPL v2 and later.
+ */
+
+#ifndef _R5000CHANNEL_H_
+#define _R5000CHANNEL_H_
+
+#include "tv_rec.h"
+#include "dtvchannel.h"
+#include "r5000device.h"
+
+class R5000Channel : public DTVChannel
+{
+  public:
+    R5000Channel(TVRec *parent, const QString &videodevice,
+                    const QString &_r5ktype, bool pocc);
+    ~R5000Channel() { 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 Retune(void);
+
+    // Sets
+    virtual bool SetChannelByString(const QString &chan);
+    virtual bool SetChannelByNumber(const QString &channel, int mpeg_prog);
+    virtual bool SetPowerState(bool on);
+    void SetSlowTuning(uint how_slow_in_ms)
+        { tuning_delay = how_slow_in_ms; }
+
+    // Gets
+    virtual bool IsOpen(void) const { return isopen; }
+    virtual R5000Device::PowerState GetPowerState(void) const;
+    virtual QString GetDevice(void) const;
+    virtual R5000Device *GetR5000Device(void) { return device; }
+
+  protected:
+    QString            videodevice;
+    FireWireDBOptions  fw_opts;
+    bool               power_on_channel_change;
+    R5000Device        *device;
+    QString            current_channel;
+    uint               current_mpeg_prog;
+    bool               isopen;
+    uint               tuning_delay;///< Extra delay to add
+};
+
+#endif // _R5000CHANNEL_H_
Index: libs/libmythtv/r5000signalmonitor.h
===================================================================
--- libs/libmythtv/r5000signalmonitor.h (revision 0)
+++ libs/libmythtv/r5000signalmonitor.h (revision 4)
@@ -0,0 +1,61 @@
+// -*- Mode: c++ -*-
+
+#ifndef _R5000SIGNALMONITOR_H_
+#define _R5000SIGNALMONITOR_H_
+
+#include <qmap.h>
+#include <qmutex.h>
+#include <qdatetime.h>
+
+#include "dtvsignalmonitor.h"
+#include "r5000device.h"
+//#include "util.h"
+
+class R5000Channel;
+
+class R5000SignalMonitor : public DTVSignalMonitor, public TSDataListener
+{
+  public:
+    R5000SignalMonitor(int db_cardnum, R5000Channel *_channel,
+                          uint64_t _flags = kFWSigMon_WaitForPower,
+                          const char *_name = "R5000SignalMonitor");
+
+    virtual void HandlePAT(const ProgramAssociationTable*);
+    virtual void HandlePMT(uint, const ProgramMapTable*);
+
+    void Stop(void);
+
+  protected:
+    R5000SignalMonitor(void);
+    R5000SignalMonitor(const R5000SignalMonitor&);
+    virtual ~R5000SignalMonitor();
+
+    virtual void UpdateValues(void);
+
+    static void *TableMonitorThread(void *param);
+    void RunTableMonitor(void);
+
+    bool SupportsTSMonitoring(void);
+
+    void AddData(const unsigned char *data, uint dataSize);
+
+  public:
+    static const uint kPowerTimeout;
+    static const uint kBufferTimeout;
+
+  protected:
+    bool               dtvMonitorRunning;
+    pthread_t          table_monitor_thread;
+    bool               stb_needs_retune;
+    bool               stb_needs_to_wait_for_pat;
+    bool               stb_needs_to_wait_for_power;
+    MythTimer          stb_wait_for_pat_timer;
+    MythTimer          stb_wait_for_power_timer;
+
+    vector<unsigned char> buffer;
+
+    static QMap<void*,uint> pat_keys;
+    static QMutex           pat_keys_lock;
+};
+
+#endif // _R5000SIGNALMONITOR_H_
Index: libs/libmythtv/signalmonitor.cpp
===================================================================
--- libs/libmythtv/recorders/signalmonitor.cpp  (revision 1)
+++ libs/libmythtv/recorders/signalmonitor.cpp  (revision 4)
@@ -54,6 +54,11 @@
 #   include "cetonchannel.h"
 #endif
 
+#ifdef USING_R5000
+#   include "r5000signalmonitor.h"
+#   include "r5000channel.h"
+#endif
+
 #undef DBG_SM
 #define DBG_SM(FUNC, MSG) LOG(VB_CHANNEL, LOG_DEBUG, \
     QString("SM(%1)::%2: %3").arg(channel->GetDevice()).arg(FUNC).arg(MSG))
@@ -154,6 +159,15 @@
     }
 #endif
 
+#ifdef USING_R5000
+    if (cardtype.toUpper() == "R5000")
+    {
+        R5000Channel *fc = dynamic_cast<R5000Channel*>(channel);
+        if (fc)
+            signalMonitor = new R5000SignalMonitor(db_cardnum, fc);
+    }
+#endif
+
     if (!signalMonitor && channel)
     {
         signalMonitor = new ScriptSignalMonitor(db_cardnum, channel);
Index: libs/libmythtv/videosource.cpp
===================================================================
--- libs/libmythtv/videosource.cpp      (revision 1)
+++ libs/libmythtv/videosource.cpp      (revision 4)
@@ -35,6 +35,7 @@
 #include "frequencies.h"
 #include "diseqcsettings.h"
 #include "firewiredevice.h"
+#include "r5000device.h"
 #include "compat.h"
 #include "mythdb.h"
 #include "mythdirs.h"
@@ -1530,6 +1531,101 @@
     _oldValue = v;
 };
 
+class R5000TuningDelay : public SpinBoxSetting, public CaptureCardDBStorage
+{
+  public:
+    R5000TuningDelay(const CaptureCard &parent) :
+        SpinBoxSetting(this, 0, 2000, 25),
+        CaptureCardDBStorage(this, parent, "dvb_tuning_delay")
+    {
+        setLabel(QObject::tr("R5000 Tuning Delay (msec)"));
+        setHelpText(
+            QObject::tr("Some STBS (for example the ViP boxes) require "
+                        "additional time after setting the channel before starting recording.  "
+                        "This is especially necessary if different channels use different codecs."));
+    };
+};
+
+class R5000SendPowerBeforeChannel : public CheckBoxSetting, public CaptureCardDBStorage
+{
+  public:
+    R5000SendPowerBeforeChannel(const CaptureCard &parent) :
+        CheckBoxSetting(this),
+        CaptureCardDBStorage(this, parent, "dvb_on_demand")
+    {
+        setValue(false);
+        setLabel(QObject::tr("Turn on before Channel Change"));
+        setHelpText(QObject::tr(
+                        "On some STBs klike the ViP211, the power on/off "
+                        "detection isn't reliable if you let the box go into "
+                        "standby.  This forces a 'power-on' command before "
+                        "changing channels.  This will very likely do the "
+                        "wrong thing for non ViP boxes."));
+
+    };
+};
+
+class R5000Serial : public ComboBoxSetting, public CaptureCardDBStorage
+{
+  public:
+    R5000Serial(const CaptureCard &parent) :
+        ComboBoxSetting(this),
+        CaptureCardDBStorage(this, parent, "videodevice")
+    {
+        setLabel(QObject::tr("Serial #"));
+#ifdef USING_R5000
+        QStringList serials = R5000Device::GetSTBList();
+        for (int i = 0; i < serials.size(); i++)
+        {
+            addSelection(serials[i]);
+        }
+#endif // USING_R5000
+    }
+};
+
+class R5000Model : public ComboBoxSetting, public CaptureCardDBStorage
+{
+  public:
+    R5000Model(const CaptureCard  &parent) :
+      ComboBoxSetting(this),
+      CaptureCardDBStorage(this, parent, "firewire_model")
+    {
+        setLabel(QObject::tr("R5000 STB type"));
+        addSelection("VIP211");
+        addSelection("VIP411");
+        addSelection("VIP622");
+        addSelection("VIP722");
+        addSelection("BEV9242");
+        addSelection("DISH6000");
+        addSelection("DIRECTV");
+        addSelection("STARCHOICE/DSR");
+        addSelection("HDD-200");
+        QString help = QObject::tr(
+            "Choose the type of R5000 enabled STB you are using.");
+        setHelpText(help);
+    }
+};
+
+class R5000ConfigurationGroup : public VerticalConfigurationGroup
+{
+  public:
+    R5000ConfigurationGroup(CaptureCard& a_parent):
+       VerticalConfigurationGroup(false, true, false, false),
+       parent(a_parent)
+    {
+        setUseLabel(false);
+        addChild(new R5000SendPowerBeforeChannel(parent));
+        addChild(new R5000TuningDelay(parent));
+        addChild(new R5000Serial(parent));
+        addChild(new R5000Model(parent));
+        addChild(new EmptyAudioDevice(parent));
+        addChild(new EmptyVBIDevice(parent));
+    };
+
+  private:
+    CaptureCard &parent;
+};
+
 class IPTVHost : public LineEditSetting, public CaptureCardDBStorage
 {
   public:
@@ -2296,6 +2392,10 @@
     addTarget("CETON",     new CetonConfigurationGroup(parent));
 #endif // USING_CETON
 
+#ifdef USING_R5000
+    addTarget("R5000",     new R5000ConfigurationGroup(parent));
+#endif // USING_R5000
+
     // for testing without any actual tuner hardware:
     addTarget("IMPORT",    new ImportConfigurationGroup(parent));
     addTarget("DEMO",      new DemoConfigurationGroup(parent));
@@ -2509,6 +2609,10 @@
         QObject::tr("Ceton Cablecard tuner "), "CETON");
 #endif // USING_CETON
 
+#ifdef USING_R5000
+    setting->addSelection(QObject::tr("R5000 Capable STB"), "R5000");
+#endif // USING_R5000
+
     setting->addSelection(QObject::tr("Import test recorder"), "IMPORT");
     setting->addSelection(QObject::tr("Demo test recorder"),   "DEMO");
 }
Index: libs/libmythtv/r5000device.h
===================================================================
--- libs/libmythtv/r5000device.h        (revision 0)
+++ libs/libmythtv/r5000device.h        (revision 4)
@@ -0,0 +1,110 @@
+/**
+ *  R5000Device
+ *  Copyright (c) 2005 by Jim Westfall
+ *  Distributed as part of MythTV under GPL v2 and later.
+ */
+
+#ifndef _R5000_DEVICE_H_
+#define _R5000_DEVICE_H_
+
+// C++ headers
+#include <vector>
+using namespace std;
+
+// Qt headers
+#include <qstring.h>
+#include <qmutex.h>
+
+// MythTV headers
+#include "streamlisteners.h"
+
+extern "C" {
+#include "r5000/r5000.h"
+}
+
+class TSPacket;
+class R5kPriv;
+class R5000Device
+{
+  public:
+
+    // Public enums
+    typedef enum
+    {
+        kAVCPowerOn,
+        kAVCPowerOff,
+        kAVCPowerUnknown,
+        kAVCPowerQueryFailed,
+    } PowerState;
+
+
+    // AVC param 0
+    typedef enum
+    {
+        kAVCPowerStateOn           = 0x70,
+        kAVCPowerStateOff          = 0x60,
+        kAVCPowerStateQuery        = 0x7f,
+    } IEEE1394UnitPowerParam0;
+
+    R5000Device(int type, QString serial);
+    ~R5000Device();
+
+    bool OpenPort(void);
+    bool ClosePort(void);
+    void RunPortHandler(void);
+
+    // Commands
+    virtual bool ResetBus(void) { return false; }
+
+    virtual void AddListener(TSDataListener*);
+    virtual void RemoveListener(TSDataListener*);
+
+    // Sets
+    virtual bool SetPowerState(bool on);
+    virtual bool SetChannel(const QString &panel_model,
+                            const QString &channel, uint mpeg_prog);
+
+    // Gets
+    bool IsSTBBufferCleared(void) const { return m_buffer_cleared; }
+
+    // non-const Gets
+    virtual PowerState GetPowerState(void);
+
+    // Statics
+    static bool IsSTBSupported(const QString &model);
+    static QString GetModelName(uint vendorid, uint modelid);
+    static QStringList GetSTBList(void);
+    static int GetDeviceType(const QString &r5ktype);
+    void BroadcastToListeners(
+        const unsigned char *data, uint dataSize);
+
+  protected:
+
+    bool GetSubunitInfo(uint8_t table[32]);
+
+    void SetLastChannel(const QString &channel);
+    void ProcessPATPacket(const TSPacket&);
+    bool StartStreaming(void);
+    bool StopStreaming(void);
+
+    int                      m_type;
+    QString                  m_serial;
+    uint                     m_subunitid;
+    uint                     m_speed;
+    QString                  m_last_channel;
+    uint                     m_last_crc;
+    bool                     m_buffer_cleared;
+
+    uint                     m_open_port_cnt;
+    vector<TSDataListener*>  m_listeners;
+    mutable QMutex           m_lock;
+
+    /// Vendor ID + Model ID to R5000Device STB model string
+    static QMap<uint64_t,QString> s_id_to_model;
+    static QMutex                 s_static_lock;
+private:
+    r5kdev_t                 *usbdev;
+    R5kPriv                  *m_priv;
+};
+
+#endif // _FIREWIRE_DEVICE_H_
Index: libs/libmythtv/recorderbase.cpp
===================================================================
--- libs/libmythtv/recorders/recorderbase.cpp   (revision 1)
+++ libs/libmythtv/recorders/recorderbase.cpp   (revision 4)
@@ -9,12 +9,14 @@
 #include "firewirechannel.h"
 #include "importrecorder.h"
 #include "cetonrecorder.h"
+#include "r5000recorder.h"
 #include "dummychannel.h"
 #include "hdhrrecorder.h"
 #include "iptvrecorder.h"
 #include "mpegrecorder.h"
 #include "recorderbase.h"
 #include "cetonchannel.h"
+#include "r5000channel.h"
 #include "asirecorder.h"
 #include "dvbrecorder.h"
 #include "hdhrchannel.h"
@@ -578,6 +580,13 @@
         recorder = new ImportRecorder(tvrec);
 #endif
     }
+    else if (genOpt.cardtype == "R5000")
+    {
+#ifdef USING_R5000
+        recorder = new R5000Recorder(
+            tvrec, dynamic_cast<R5000Channel*>(channel));
+#endif // USING_R5000
+    }
     else if (CardUtil::IsV4L(genOpt.cardtype))
     {
 #ifdef USING_V4L2
Index: configure
===================================================================
--- configure   (revision 1)
+++ configure   (revision 4)
@@ -119,6 +119,7 @@
   --disable-ivtv           disable ivtv support (PVR-x50) req. v4l2 support
   --disable-hdpvr          disable HD-PVR support
   --disable-dvb            disable DVB support
+  --disable-r5000          disable support for R5000 USB STBs
   --dvb-path=HDRLOC        location of directory containing
                            'linux/dvb/frontend.h', not the
                            directory with frontend.h [$dvb_path_default]
@@ -1514,6 +1515,7 @@
     hdpvr
     iptv
     ivtv
+    r5000
     asi
     joystick_menu
     libcec
@@ -2239,5 +2242,6 @@
 enable hdpvr
 enable ivtv
+enable r5000
 enable asi
 enable lamemp3
 enable libass
@@ -2286,5 +2289,6 @@
 audio_oss_deps_any="soundcard_h sys_soundcard_h"
 dvb_deps="backend"
 firewire_deps="backend"
+r5000_deps="backend"
 ivtv_deps="backend v4l2"
 hdpvr_deps="backend v4l2"
@@ -5707,5 +5711,6 @@
   echo "HDHomeRun support         ${hdhomerun-no}"
   echo "Ceton support             ${ceton-no}"
+  echo "R5000 support             ${r5000-no}"
   echo "ASI support               ${asi-no}"
 fi
post #40 of 40
I realized my original git wasn't set up correctly; my git only contained the MythTV folder, which got all of my changes, but missed a bunch of external stuff.

It worked on my computer because all the other folders and files were there, they just weren't part of my git, so I didn't realize there was a problem.

It's now using a clone of the official MythTV git that I've modified, so it works out-of-the-box.

Right now, it's only patched for Myth 0.26, since that's the current release. It looks like quantumstate's patch above will work for upcoming 0.27, though.

I tested this by setting up a fresh computer, cloning my git, and compiling/installing on Debian.

I updated my instructions in the post above to reflect the change, and I've deleted the other repository.

I also included a list of Debian libs you'll need in order to make it build.

Thanks to quantumstate for pointing out the error, sorry for the delays.

-Wes
New Posts  All Forums:Forum Nav:
  Return Home
  Back to Forum: HTPC - Linux Chat
AVS › AVS Forum › Video Components › Home Theater Computers › HTPC - Linux Chat › Recording With MythTV - How Are the Kids Doing it These Days?