Skip to content

Commit

Permalink
VBOX: Add multi-record support to the vbox recorder
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
MikeB2013 authored and Paul Harrison committed Feb 19, 2017
1 parent 57d61f7 commit d4c0f13
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 27 deletions.
72 changes: 70 additions & 2 deletions mythtv/libs/libmythtv/cardutil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@

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

#ifdef USING_ASI
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
}
4 changes: 2 additions & 2 deletions mythtv/libs/libmythtv/cardutil.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -209,7 +209,7 @@ class MTV_PUBLIC CardUtil
return !(rawtype == "FREEBOX" || rawtype == "VBOX");
}


static bool IsVBoxPresent(uint inputid);

// Card creation and deletion

Expand Down
35 changes: 27 additions & 8 deletions mythtv/libs/libmythtv/channelscan/vboxchannelfetcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
}
Expand Down
8 changes: 6 additions & 2 deletions mythtv/libs/libmythtv/channelscan/vboxchannelfetcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
}

Expand All @@ -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;

Expand Down
14 changes: 14 additions & 0 deletions mythtv/libs/libmythtv/iptvtuningdata.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions mythtv/libs/libmythtv/recorders/iptvchannel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
9 changes: 6 additions & 3 deletions mythtv/libs/libmythtv/recorders/vboxutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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);
}

Expand Down
1 change: 1 addition & 0 deletions mythtv/libs/libmythtv/recorders/vboxutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
18 changes: 17 additions & 1 deletion mythtv/libs/libmythtv/tv_rec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

Expand Down Expand Up @@ -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__);
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;

Expand All @@ -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;
Expand Down
19 changes: 10 additions & 9 deletions mythtv/libs/libmythtv/videosource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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&)));
Expand Down

0 comments on commit d4c0f13

Please sign in to comment.