Skip to content
Permalink
Browse files

VBOX: Add multi-record support to the vbox recorder

This allows the vbox recorder to record more than one channel using a single
tuner so long as they are on the same multiplex.

You need to uncheck 'Limit single channel streaming per UPnP device' setting
on the Vbox for this to work and firmware >= 2.53 is recommended.

Also Myth needs to know which channels share a multiplex so a retune of the
Vbox recorder in Myth is necessary in order to populate this information into
the database.

This patch also contains work arounds for #12856 and #12773.

Signed-off-by: Paul Harrison <pharrison@mythtv.org>
  • Loading branch information...
MikeB2013 authored and Paul Harrison committed Feb 19, 2017
1 parent 57d61f7 commit d4c0f13aede582183aed7f13019c85846b7bfe4b
@@ -41,6 +41,7 @@

#ifdef USING_VBOX
#include "vboxutils.h"
#include "mythmiscutil.h"
#endif

#ifdef USING_ASI
@@ -158,7 +159,7 @@ bool CardUtil::IsCableCardPresent(uint inputid,
bool CardUtil::HasTuner(const QString &rawtype, const QString & device)
{
if (rawtype == "DVB" || rawtype == "HDHOMERUN" ||
rawtype == "FREEBOX" || rawtype == "CETON")
rawtype == "FREEBOX" || rawtype == "CETON" || rawtype == "VBOX")
return true;

#ifdef USING_V4L2
@@ -2006,7 +2007,8 @@ bool CardUtil::DeleteAllCards(void)
return (query.exec("TRUNCATE TABLE inputgroup") &&
query.exec("TRUNCATE TABLE diseqc_config") &&
query.exec("TRUNCATE TABLE diseqc_tree") &&
query.exec("TRUNCATE TABLE capturecard"));
query.exec("TRUNCATE TABLE capturecard") &&
query.exec("TRUNCATE TABLE iptv_channel"));
}

vector<uint> CardUtil::GetInputList(void)
@@ -2413,3 +2415,69 @@ bool CardUtil::SetASIMode(uint device_num, uint mode, QString *error)
return false;
#endif
}

/** \fn CardUtil::IsVBoxPresent(uint inputid)
* \brief Returns true if the VBox responds to a ping
* \param inputid Inputid as used in DB capturecard table
*/
bool CardUtil::IsVBoxPresent(uint inputid)
{
// should only be called if inputtype == VBOX
if (!inputid )
{
LOG(VB_GENERAL, LOG_ERR, QString("VBOX inputid (%1) not valid, redo mythtv-setup")
.arg(inputid));
return false;
}

// get sourceid and startchan from table capturecard for inputid
uint chanid = 0;
chanid = ChannelUtil::GetChannelValueInt("chanid",GetSourceID(inputid),GetStartingChannel(inputid));
if (!chanid)
{
// no chanid, presume bad setup
LOG(VB_GENERAL, LOG_ERR, QString("VBOX chanid (%1) not found for inputid (%2) , redo mythtv-setup")
.arg(chanid).arg(inputid));
return false;
}

// get timeouts for inputid
uint signal_timeout = 0;
uint tuning_timeout = 0;
if (!GetTimeouts(inputid,signal_timeout,tuning_timeout))
{
LOG(VB_GENERAL, LOG_ERR, QString("Failed to get timeouts for inputid (%1)")
.arg(inputid));
return false;
}

signal_timeout = signal_timeout/1000; //convert to seconds

// now get url from iptv_channel table
QUrl url;
MSqlQuery query(MSqlQuery::InitCon());
query.prepare("SELECT url "
"FROM iptv_channel "
"WHERE chanid = :CHANID");
query.bindValue(":CHANID", chanid);

if (!query.exec())
MythDB::DBError("CardUtil::IsVBoxPresent url", query);
else if (query.next())
url = query.value(0).toString();

//now get just the IP address from the url
QString ip ="";
ip = url.host();
LOG(VB_GENERAL, LOG_INFO, QString("VBOX IP found (%1) for inputid (%2)")
.arg(ip).arg(inputid));

if (!ping(ip,signal_timeout))
{
LOG(VB_GENERAL, LOG_ERR, QString("VBOX at IP (%1) failed to respond to network ping for inputid (%2) timeout (%3)")
.arg(ip).arg(inputid).arg(signal_timeout));
return false;
}

return true;
}
@@ -166,7 +166,7 @@ class MTV_PUBLIC CardUtil
(rawtype == "DVB") || (rawtype == "HDHOMERUN") ||
(rawtype == "ASI") || (rawtype == "FREEBOX") ||
(rawtype == "CETON") || (rawtype == "EXTERNAL") ||
(rawtype == "V4L2ENC");
(rawtype == "VBOX") || (rawtype == "V4L2ENC");
}

static bool HasTuner(const QString &rawtype, const QString & device);
@@ -209,7 +209,7 @@ class MTV_PUBLIC CardUtil
return !(rawtype == "FREEBOX" || rawtype == "VBOX");
}


static bool IsVBoxPresent(uint inputid);

// Card creation and deletion

@@ -176,13 +176,15 @@ void VBoxChannelFetcher::run(void)
bool fta = (*it).m_fta;
QString chanType = (*it).m_channelType;
QString transType = (*it).m_transType;
uint networkID = (*it).m_networkID;
uint transportID = (*it).m_transportID;

//: %1 is the channel number, %2 is the channel name
QString msg = tr("Channel #%1 : %2").arg(channum).arg(name);

LOG(VB_CHANNEL, LOG_INFO, QString("Handling channel %1 %2")
.arg(channum).arg(name));

uint mplexID = 0;
if (_ftaOnly && !fta)
{
// ignore this encrypted channel
@@ -210,30 +212,47 @@ void VBoxChannelFetcher::run(void)
else
{
int chanid = ChannelUtil::GetChanID(_sourceid, channum);

if (chanid <= 0)
{
if (_scan_monitor)
{
_scan_monitor->ScanAppendTextToLog(tr("Adding %1").arg(msg));
}
chanid = ChannelUtil::CreateChanID(_sourceid, channum);
ChannelUtil::CreateChannel(0, _sourceid, chanid, name, name,

// mplexID will be created if necessary
// inversion, bandwidth, transmission_mode, polarity, hierarchy, mod_sys and roll_off are given values, but not used
// this is to ensure services API Channel/GetVideoMultiplexList returns a valid list
mplexID = ChannelUtil::CreateMultiplex(_sourceid, "dvb", 0, QString::null, transportID, networkID, 0,
'a', 'v', 'a', 'a', QString::null, QString::null, 'a', QString::null,
QString::null, QString::null, "UNDEFINED", "0.35");

ChannelUtil::CreateChannel(mplexID, _sourceid, chanid, name, name,
channum, serviceID, 0, 0,
false, false, false, QString::null,
QString::null, "Default", xmltvid);

ChannelUtil::CreateIPTVTuningData(chanid, (*it).m_tuning);
}
else
{
if (_scan_monitor)
{
_scan_monitor->ScanAppendTextToLog(
tr("Updating %1").arg(msg));
_scan_monitor->ScanAppendTextToLog(tr("Updating %1").arg(msg));
}
ChannelUtil::UpdateChannel(0, _sourceid, chanid, name, name,
channum, serviceID, 0, 0,
false, false, false, QString::null,
QString::null, "Default", xmltvid);

// mplexID will be created if necessary
mplexID = ChannelUtil::CreateMultiplex(_sourceid, "dvb", 0, QString::null, transportID, networkID, 0,
'a', 'v', 'a', 'a', QString::null, QString::null, 'a', QString::null,
QString::null, QString::null, "UNDEFINED", "0.35");

// xmltvid parameter is set to null, user may have changed it, so do not overwrite as we are only updating
ChannelUtil::UpdateChannel(mplexID, _sourceid, chanid, name, name,
channum, serviceID, 0, 0,
false, false, false, QString::null,
QString::null, "Default", QString::null);

ChannelUtil::UpdateIPTVTuningData(chanid, (*it).m_tuning);
}
}
@@ -29,10 +29,12 @@ class VBoxChannelInfo
bool fta,
const QString &chanType,
const QString &transType,
uint serviceID) :
uint serviceID,
uint networkID,
uint transportID):
m_name(name), m_xmltvid(xmltvid), m_serviceID(serviceID),
m_fta(fta), m_channelType(chanType), m_transType(transType),
m_tuning(data_url, IPTVTuningData::http_ts)
m_tuning(data_url, IPTVTuningData::http_ts), m_networkID(networkID), m_transportID(transportID)
{
}

@@ -49,6 +51,8 @@ class VBoxChannelInfo
QString m_channelType; // TV/Radio
QString m_transType; // T/T2/S/S2/C/A
IPTVTuningData m_tuning;
uint m_networkID; // Network ID from triplet
uint m_transportID; // Transport ID from triplet
};
typedef QMap<QString,VBoxChannelInfo> vbox_chan_map_t;

@@ -227,6 +227,20 @@ class MTV_PUBLIC IPTVTuningData
}

QString url = m_data_url.toString();

// check url is valid for a playlist before downloading (see trac ticket #12856)
if(url.endsWith(".m3u8", Qt::CaseInsensitive) ||
url.endsWith(".m3u", Qt::CaseInsensitive))
{
LOG(VB_RECORD, LOG_INFO, QString("IsHLSPlaylist url ends with either .m3u8 or .m3u %1").arg(url));
}
else
{
// not a valid playlist so just return false
LOG(VB_RECORD, LOG_INFO, QString("IsHLSPlaylist url does not end with either .m3u8 or .m3u %1").arg(url));
return false;
}

QByteArray buffer;

MythSingleDownload downloader;
@@ -123,7 +123,10 @@ void IPTVChannel::CloseStreamHandler(void)
if (m_stream_handler)
{
if (m_stream_data)
{
m_stream_handler->RemoveListener(m_stream_data);
m_stream_data = NULL; //see trac ticket #12773
}

HLSStreamHandler* hsh = dynamic_cast<HLSStreamHandler*>(m_stream_handler);
HTTPTSStreamHandler* httpsh = dynamic_cast<HTTPTSStreamHandler*>(m_stream_handler);
@@ -224,7 +224,7 @@ bool VBox::checkVersion(QString &version)
QStringList sList = requiredVersion.split('.');

// sanity check this looks like a VBox version string
if (sList.count() != 3 || !requiredVersion.startsWith("VB."))
if (sList.count() < 3 || !requiredVersion.startsWith("V"))
{
LOG(VB_GENERAL, LOG_INFO, LOC + QString("Failed to parse required version from %1").arg(requiredVersion));
version = "UNKNOWN";
@@ -247,7 +247,7 @@ bool VBox::checkVersion(QString &version)
sList = version.split('.');

// sanity check this looks like a VBox version string
if (sList.count() != 3 || !(version.startsWith("VB.") || version.startsWith("VJ.")))
if (sList.count() < 3 || !(version.startsWith("VB.") || version.startsWith("VJ.")))
{
LOG(VB_GENERAL, LOG_INFO, LOC + QString("Failed to parse version from %1").arg(version));
delete xmlDoc;
@@ -327,6 +327,9 @@ vbox_chan_map_t *VBox::getChannels(void)

QString transType = "UNKNOWN";
QStringList slist = triplet.split('-');
uint networkID = slist[2].left(4).toUInt(0, 16);
uint transportID = slist[2].mid(4, 4).toUInt(0, 16);
LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("NIT/TID/SID %1 %2 %3)").arg(networkID).arg(transportID).arg(serviceID));

//sanity check - the triplet should look something like this: T-GER-111100020001
// where T is the tuner type, GER is the country, and the numbers are the NIT/TID/SID
@@ -349,7 +352,7 @@ vbox_chan_map_t *VBox::getChannels(void)
url = urlElem.attribute("src", "");
}

VBoxChannelInfo chanInfo(name, xmltvid, url, fta, chanType, transType, serviceID);
VBoxChannelInfo chanInfo(name, xmltvid, url, fta, chanType, transType, serviceID, networkID, transportID);
result->insert(lcn, chanInfo);
}

@@ -19,6 +19,7 @@ class VBox
static QStringList probeDevices(void);
static QString getIPFromVideoDevice(const QString &dev);

bool isConnected(void);
bool checkConnection(void);
bool checkVersion(QString &version);
QDomDocument *getBoardInfo(void);
@@ -39,6 +39,7 @@
#include "tv_rec.h"
#include "mythdate.h"
#include "osd.h"
#include "../vboxutils.h"

#define DEBUG_CHANNEL_PREFIX 0 /**< set to 1 to channel prefixing */

@@ -136,6 +137,17 @@ bool TVRec::CreateChannel(const QString &startchannel,
this, genOpt, dvbOpt, fwOpt,
startchannel, enter_power_save_mode, rbFileExt);

if (genOpt.inputtype == "VBOX")
{
if (!CardUtil::IsVBoxPresent(inputid))
{
// VBOX presence failed recorder is marked errored
LOG(VB_GENERAL, LOG_ERR, LOC + QString("CreateChannel(%1) failed due to VBOX not responding "
"to network check on inputid (%2)").arg(startchannel).arg(inputid));
channel = NULL;
}
}

if (!channel)
{
SetFlags(kFlagErrored, __FILE__, __LINE__);
@@ -1903,7 +1915,7 @@ bool TVRec::SetupDTVSignalMonitor(bool EITscan)

// Check if this is an DVB channel
int progNum = dtvchan->GetProgramNumber();
if ((progNum >= 0) && (tuningmode == "dvb"))
if ((progNum >= 0) && (tuningmode == "dvb") && (genOpt.inputtype != "VBOX"))
{
int netid = dtvchan->GetOriginalNetworkID();
int tsid = dtvchan->GetTransportID();
@@ -3545,6 +3557,8 @@ uint TVRec::TuningCheckForHWChange(const TuningRequest &request,
QString &channum,
QString &inputname)
{
LOG(VB_RECORD, LOG_INFO, LOC + QString("request (%1) channum (%2) inputname (%3)")
.arg(request.toString()).arg(channum).arg(inputname));
if (!channel)
return 0;

@@ -3568,6 +3582,8 @@ uint TVRec::TuningCheckForHWChange(const TuningRequest &request,

if (curInputID != newInputID || !CardUtil::IsChannelReusable(genOpt.inputtype))
{
LOG(VB_RECORD, LOG_INFO, LOC + QString("Inputtype HW Tuner newinputid channum curinputid: %1->%2 %3")
.arg(curInputID).arg(newInputID).arg(channum));
if (channum.isEmpty())
channum = GetStartChannel(newInputID);
return newInputID;
@@ -2255,8 +2255,8 @@ VBoxExtra::VBoxExtra(VBoxConfigurationGroup &parent)
rec->setLabel(QObject::tr("Recorder Options"));
rec->setUseLabel(false);

rec->addChild(new SignalTimeout(parent.parent, 1000, 250));
rec->addChild(new ChannelTimeout(parent.parent, 3000, 1750));
rec->addChild(new SignalTimeout(parent.parent, 7000, 250));
rec->addChild(new ChannelTimeout(parent.parent, 10000, 1750));

addChild(rec);
}
@@ -2286,13 +2286,14 @@ VBoxConfigurationGroup::VBoxConfigurationGroup
addChild(desc);
addChild(cardip);
addChild(cardtuner);

TransButtonSetting *buttonRecOpt = new TransButtonSetting();
buttonRecOpt->setLabel(tr("Recording Options"));
addChild(buttonRecOpt);

connect(buttonRecOpt, SIGNAL(pressed()),
this, SLOT( VBoxExtraPanel()));
addChild(new SignalTimeout(parent, 7000, 1000));
addChild(new ChannelTimeout(parent, 10000, 1750));
// TransButtonSetting *buttonRecOpt = new TransButtonSetting();
// buttonRecOpt->setLabel(tr("Recording Options"));
// addChild(buttonRecOpt);

// connect(buttonRecOpt, SIGNAL(pressed()),
// this, SLOT( VBoxExtraPanel()));

connect(cardip, SIGNAL(NewIP(const QString&)),
deviceid, SLOT( SetIP(const QString&)));

0 comments on commit d4c0f13

Please sign in to comment.
You can’t perform that action at this time.