From ca5f31b9e4fb1368cf23a7144fd55c445c0bd107 Mon Sep 17 00:00:00 2001
From: Chase Douglas <chase.douglas@canonical.com>
Date: Sun, 31 Jul 2011 10:46:29 -0700
Subject: [PATCH 3/4] Generate PSI packets for broken firmware cable boxes

If the firewire_gen_psip option is set for the capture device, generate
MPEG2-TS PAT and PMT packets (aka Program Specific Information (PSI)
packets).
---
 mythtv/libs/libmythtv/darwinfirewiredevice.cpp |    4 +-
 mythtv/libs/libmythtv/darwinfirewiredevice.h   |    3 +-
 mythtv/libs/libmythtv/firewirechannel.cpp      |    6 +-
 mythtv/libs/libmythtv/firewiredevice.cpp       |  183 +++++++++++++++++++++++-
 mythtv/libs/libmythtv/firewiredevice.h         |   25 +++-
 mythtv/libs/libmythtv/linuxfirewiredevice.cpp  |    4 +-
 mythtv/libs/libmythtv/linuxfirewiredevice.h    |    2 +-
 7 files changed, 215 insertions(+), 12 deletions(-)

diff --git a/mythtv/libs/libmythtv/darwinfirewiredevice.cpp b/mythtv/libs/libmythtv/darwinfirewiredevice.cpp
index 58224c9..8fa57d7 100644
--- a/mythtv/libs/libmythtv/darwinfirewiredevice.cpp
+++ b/mythtv/libs/libmythtv/darwinfirewiredevice.cpp
@@ -114,8 +114,8 @@ class DFDPriv
 };
 
 DarwinFirewireDevice::DarwinFirewireDevice(
-    uint64_t guid, uint subunitid, uint speed) :
-    FirewireDevice(guid, subunitid, speed),
+    uint64_t guid, uint subunitid, uint speed, bool gen_psip) :
+    FirewireDevice(guid, subunitid, speed, gen_psip),
     m_local_node(-1), m_remote_node(-1), m_priv(new DFDPriv())
 {
 
diff --git a/mythtv/libs/libmythtv/darwinfirewiredevice.h b/mythtv/libs/libmythtv/darwinfirewiredevice.h
index 4ed70df..68c1f35 100644
--- a/mythtv/libs/libmythtv/darwinfirewiredevice.h
+++ b/mythtv/libs/libmythtv/darwinfirewiredevice.h
@@ -20,7 +20,8 @@ class DarwinFirewireDevice : public FirewireDevice
 
 
   public:
-    DarwinFirewireDevice(uint64_t guid, uint subunitid, uint speed);
+    DarwinFirewireDevice(uint64_t guid, uint subunitid, uint speed,
+                         bool gen_psip = false);
     ~DarwinFirewireDevice();
 
     virtual bool OpenPort(void);
diff --git a/mythtv/libs/libmythtv/firewirechannel.cpp b/mythtv/libs/libmythtv/firewirechannel.cpp
index adf6e09..a397614 100644
--- a/mythtv/libs/libmythtv/firewirechannel.cpp
+++ b/mythtv/libs/libmythtv/firewirechannel.cpp
@@ -28,11 +28,13 @@ FirewireChannel::FirewireChannel(TVRec *parent, const QString &_videodevice,
 #ifdef USING_LINUX_FIREWIRE
     device = new LinuxFirewireDevice(
         guid, subunitid, fw_opts.speed,
-        LinuxFirewireDevice::kConnectionP2P == (uint) fw_opts.connection);
+        LinuxFirewireDevice::kConnectionP2P == (uint) fw_opts.connection,
+        fw_opts.gen_psip);
 #endif // USING_LINUX_FIREWIRE
 
 #ifdef USING_OSX_FIREWIRE
-    device = new DarwinFirewireDevice(guid, subunitid, fw_opts.speed);
+    device = new DarwinFirewireDevice(guid, subunitid, fw_opts.speed,
+        fw_opts.gen_psip);
 #endif // USING_OSX_FIREWIRE
 }
 
diff --git a/mythtv/libs/libmythtv/firewiredevice.cpp b/mythtv/libs/libmythtv/firewiredevice.cpp
index 2015e57..a074026 100644
--- a/mythtv/libs/libmythtv/firewiredevice.cpp
+++ b/mythtv/libs/libmythtv/firewiredevice.cpp
@@ -1,6 +1,7 @@
 /**
  *  FirewireDevice
  *  Copyright (c) 2005 by Jim Westfall
+ *  Copyright (c) 2011 by Chase Douglas
  *  Distributed as part of MythTV under GPL v2 and later.
  */
 
@@ -15,21 +16,34 @@
 #include "darwinfirewiredevice.h"
 #include "mythlogging.h"
 #include "pespacket.h"
+#include "mpegtables.h"
 
 #define LOC      QString("FireDev(%1): ").arg(guid_to_string(m_guid))
 
+extern "C" {
+extern const uint8_t *ff_find_start_code(const uint8_t *p, const uint8_t *end, uint32_t *state);
+}
+
 static void fw_init(QMap<uint64_t,QString> &id_to_model);
 
 QMap<uint64_t,QString> FirewireDevice::s_id_to_model;
 QMutex                 FirewireDevice::s_static_lock;
 
-FirewireDevice::FirewireDevice(uint64_t guid, uint subunitid, uint speed) :
+FirewireDevice::FirewireDevice(uint64_t guid, uint subunitid, uint speed,
+                               bool gen_psip) :
     m_guid(guid),           m_subunitid(subunitid),
     m_speed(speed),
     m_last_channel(0),      m_last_crc(0),
     m_buffer_cleared(true), m_open_port_cnt(0),
-    m_lock()
+    m_lock(), m_gen_psip(gen_psip),
+    m_vpid(0), m_apid(0), m_pat(NULL), m_pat_cc(0), m_pmt(NULL), m_pmt_cc(0)
+{
+}
+
+FirewireDevice::~FirewireDevice()
 {
+    delete m_pat;
+    delete m_pmt;
 }
 
 void FirewireDevice::AddListener(TSDataListener *listener)
@@ -303,12 +317,177 @@ bool FirewireDevice::SetChannel(const QString &panel_model,
         return true;
     }
 
+    m_apid = 0;
+    m_vpid = 0;
+    m_pat_timer.stop();
+    delete m_pat;
+    m_pat = NULL;
+    m_pmt_timer.stop();
+    delete m_pmt;
+    m_pmt = NULL;
+
+    return false;
+}
+
+void FirewireDevice::SendPAT()
+{
+    vector<TSPacket> m_pat_pkts;
+    m_pat->GetAsTSPackets(m_pat_pkts, m_pat_cc);
+    for (vector<TSPacket>::const_iterator it = m_pat_pkts.begin(); it != m_pat_pkts.end(); ++it)
+        SendDataToListeners(it->data(), TSPacket::kSize);
+    m_pat_cc = (m_pat_pkts.back().ContinuityCounter() + 1) & 0xf;
+}
+
+void FirewireDevice::SendPMT()
+{
+    vector<TSPacket> m_pmt_pkts;
+    m_pmt->GetAsTSPackets(m_pmt_pkts, m_pmt_cc);
+    for (vector<TSPacket>::const_iterator it = m_pmt_pkts.begin(); it != m_pmt_pkts.end(); ++it)
+        SendDataToListeners(it->data(), TSPacket::kSize);
+    m_pmt_cc = (m_pmt_pkts.back().ContinuityCounter() + 1) & 0xf;
+}
+
+void FirewireDevice::GetTSPackets(const unsigned char *data, uint len, vector<const TSPacket*> *packets)
+{
+    uint bufsz = m_buffer.size();
+    if ((SYNC_BYTE == data[0]) && (TSPacket::kSize == len) &&
+        (TSPacket::kSize > bufsz))
+    {
+        m_buffer.clear();
+
+        packets->push_back(reinterpret_cast<const TSPacket*>(data));
+        return;
+    }
+
+    m_buffer.insert(m_buffer.end(), data, data + len);
+    bufsz += len;
+
+    uint sync_at;
+    for (sync_at = 0; sync_at < bufsz; sync_at++)
+        if (m_buffer[sync_at] == SYNC_BYTE)
+            break;
+
+    if (sync_at == bufsz)
+        return;
+
+    while (sync_at + TSPacket::kSize < bufsz)
+    {  
+        packets->push_back(reinterpret_cast<const TSPacket*>(
+                                &m_buffer[0] + sync_at));
+
+        sync_at += TSPacket::kSize;
+    }
+
+    m_buffer.erase(m_buffer.begin(), m_buffer.begin() + sync_at);
+
+    return;
+}
+
+/* Check for MPEG2 video PES packet header */
+static bool IsMPEG2Frame(const TSPacket &tspacket)
+{
+    const uint8_t *bufptr = tspacket.data() + tspacket.AFCOffset();
+    const uint8_t *bufend = tspacket.data() + TSPacket::kSize;
+    uint32_t start_code;
+
+    while (bufptr < bufend)
+    {
+        bufptr = ff_find_start_code(bufptr, bufend, &start_code);
+        if ((start_code & 0xffffff00) == 0x00000100)
+        {
+            const int stream_id = start_code & 0x000000ff;
+
+            if (stream_id == PESStreamID::PictureStartCode ||
+                stream_id == PESStreamID::GOPStartCode)
+                return true;
+        }
+    }
+
     return false;
 }
 
+/* Firewire devices emit one video and one audio elementary stream. We must
+ * figure out which PID is the video stream and which is the audio stream. It's
+ * easy to determine the video stream, so find it first. Then assume the other
+ * is audio.
+ */
+void FirewireDevice::GeneratePSIP(const unsigned char *data, uint dataSize)
+{
+    vector<const TSPacket*> packets;
+
+    /* If PAT packet hasn't been generated, chunk data into packets for analyzing */
+    if (!m_pat)
+        GetTSPackets(data, dataSize, &packets);
+
+    for (vector<const TSPacket*>::const_iterator it = packets.begin(); it != packets.end(); ++it)
+    {
+        const TSPacket *tspacket = *it;
+
+        if (!m_vpid)
+        {
+            if (IsMPEG2Frame(*tspacket))
+                m_vpid = tspacket->PID();
+            continue;
+        }
+
+        if (m_vpid && !m_apid && tspacket->PID() != m_vpid &&
+            tspacket->PID() != TSPacket::kNullPacket->PID())
+        {
+            m_apid = tspacket->PID();
+
+            /* Generate PAT */
+            vector<uint> programs;
+            programs.push_back(1);
+            vector<uint> pids;
+            pids.push_back(10);
+
+            m_pat = ProgramAssociationTable::Create(0, 0, programs, pids);
+            SendPAT();
+            m_pat_timer.start();
+
+            /* Generate PMT */
+            vector<uint> ppids;
+            ppids.push_back(m_vpid);
+            ppids.push_back(m_apid);
+            vector<uint> types;
+            types.push_back(StreamID::MPEG2Video);
+            types.push_back(StreamID::AC3Audio);
+
+            m_pmt = ProgramMapTable::Create(1, 10, 0x1fff, 0, ppids, types);
+            SendPMT();
+            m_pmt_timer.start();
+        }
+    }
+
+    /* The ATSC standard is a reasonable choice to mimic. It says a PAT must
+     * occur at least once every 100 ms. A PMT must occur at least once every
+     * 400 ms. We'll set the frequencies at 75% of the limits. */
+
+    if (m_pat_timer.isRunning() && m_pat_timer.elapsed() >= 75)
+    {
+        SendPAT();
+        m_pat_timer.start();
+    }
+
+    if (m_pmt_timer.isRunning() && m_pmt_timer.elapsed() >= 300)
+    {
+        SendPMT();
+        m_pmt_timer.start();
+    }
+}
+
 void FirewireDevice::BroadcastToListeners(
     const unsigned char *data, uint dataSize)
 {
+    if (m_gen_psip)
+        GeneratePSIP(data, dataSize);
+
+    SendDataToListeners(data, dataSize);
+}
+
+void FirewireDevice::SendDataToListeners(const unsigned char *data,
+                                         uint dataSize)
+{
     if ((dataSize >= TSPacket::kSize) && (data[0] == SYNC_BYTE) &&
         ((data[1] & 0x1f) == 0) && (data[2] == 0))
     {
diff --git a/mythtv/libs/libmythtv/firewiredevice.h b/mythtv/libs/libmythtv/firewiredevice.h
index 4151220..381020b 100644
--- a/mythtv/libs/libmythtv/firewiredevice.h
+++ b/mythtv/libs/libmythtv/firewiredevice.h
@@ -18,6 +18,7 @@ using namespace std;
 // MythTV headers
 #include "streamlisteners.h"
 #include "avcinfo.h"
+#include "mythtimer.h"
 
 class TSPacket;
 
@@ -190,7 +191,7 @@ class FirewireDevice
 
     } IEEE1394PanelPassThroughParam0;
 
-    virtual ~FirewireDevice() { }
+    virtual ~FirewireDevice();
 
     // Commands
     virtual bool OpenPort(void) = 0;
@@ -218,7 +219,7 @@ class FirewireDevice
     static vector<AVCInfo> GetSTBList(void);
 
   protected:
-    FirewireDevice(uint64_t guid, uint subunitid, uint speed);
+    FirewireDevice(uint64_t guid, uint subunitid, uint speed, bool gen_psip);
 
     virtual bool SendAVCCommand(const vector<uint8_t> &cmd,
                                 vector<uint8_t> &result,
@@ -229,6 +230,12 @@ class FirewireDevice
     void ProcessPATPacket(const TSPacket&);
     virtual void BroadcastToListeners(
         const unsigned char *data, uint dataSize);
+    void SendDataToListeners(const unsigned char *data, uint dataSize);
+    void GeneratePSIP(const unsigned char *data, uint dataSize);
+    void GetTSPackets(const unsigned char *data, uint len,
+                      vector<const TSPacket*> *packets);
+    void SendPAT();
+    void SendPMT();
 
     uint64_t                 m_guid;
     uint                     m_subunitid;
@@ -241,6 +248,20 @@ class FirewireDevice
     vector<TSDataListener*>  m_listeners;
     mutable QMutex           m_lock;
 
+    bool                     m_gen_psip;
+    vector<unsigned char>    m_buffer;
+
+    uint                     m_vpid;
+    uint                     m_apid;
+
+    ProgramAssociationTable *m_pat;
+    uint                     m_pat_cc;
+    MythTimer                m_pat_timer;
+
+    ProgramMapTable         *m_pmt;
+    uint                     m_pmt_cc;
+    MythTimer                m_pmt_timer;
+
     /// Vendor ID + Model ID to FirewireDevice STB model string
     static QMap<uint64_t,QString> s_id_to_model;
     static QMutex                 s_static_lock;
diff --git a/mythtv/libs/libmythtv/linuxfirewiredevice.cpp b/mythtv/libs/libmythtv/linuxfirewiredevice.cpp
index 7ef8dd1..1eee67e 100644
--- a/mythtv/libs/libmythtv/linuxfirewiredevice.cpp
+++ b/mythtv/libs/libmythtv/linuxfirewiredevice.cpp
@@ -136,8 +136,8 @@ static int linux_firewire_device_bus_reset_handler(
 
 LinuxFirewireDevice::LinuxFirewireDevice(
     uint64_t guid, uint subunitid,
-    uint speed, bool use_p2p, uint av_buffer_size_in_bytes) :
-    FirewireDevice(guid, subunitid, speed),
+    uint speed, bool use_p2p, bool gen_psip, uint av_buffer_size_in_bytes) :
+    FirewireDevice(guid, subunitid, speed, gen_psip),
     m_bufsz(av_buffer_size_in_bytes),
     m_db_reset_disabled(false),
     m_use_p2p(use_p2p), m_priv(new LFDPriv())
diff --git a/mythtv/libs/libmythtv/linuxfirewiredevice.h b/mythtv/libs/libmythtv/linuxfirewiredevice.h
index adb71aa..9187486 100644
--- a/mythtv/libs/libmythtv/linuxfirewiredevice.h
+++ b/mythtv/libs/libmythtv/linuxfirewiredevice.h
@@ -33,7 +33,7 @@ class LinuxFirewireDevice : public FirewireDevice
 
   public:
     LinuxFirewireDevice(uint64_t guid, uint subunitid,
-                        uint speed, bool use_p2p,
+                        uint speed, bool use_p2p, bool gen_psip = false,
                         uint av_buffer_size_in_bytes = 0);
     ~LinuxFirewireDevice();
 
-- 
1.7.0.4

