MythTV  master
cetonstreamhandler.cpp
Go to the documentation of this file.
1 
8 // POSIX headers
9 #include <fcntl.h>
10 #include <unistd.h>
11 #ifndef _WIN32
12 #include <sys/select.h>
13 #include <sys/ioctl.h>
14 #endif
15 
16 // Qt headers
17 #include <QCoreApplication>
18 #include <QUrl>
19 #include <QUrlQuery>
20 
21 // MythTV headers
22 #include "cetonstreamhandler.h"
23 #include "streamlisteners.h"
24 #include "mpegstreamdata.h"
25 #include "cetonchannel.h"
26 #include "mythlogging.h"
27 #include "cardutil.h"
28 #include "mythdownloadmanager.h"
29 
30 #define LOC QString("CetonSH[%1](%2): ").arg(m_inputid).arg(m_device)
31 
32 QMap<QString,CetonStreamHandler*> CetonStreamHandler::s_handlers;
33 QMap<QString,uint> CetonStreamHandler::s_handlers_refcnt;
35 QMap<QString, bool> CetonStreamHandler::s_info_queried;
36 
38  int inputid)
39 {
40  QMutexLocker locker(&s_handlers_lock);
41 
42  QString devkey = devname.toUpper();
43 
44  QMap<QString,CetonStreamHandler*>::iterator it = s_handlers.find(devkey);
45 
46  if (it == s_handlers.end())
47  {
48  CetonStreamHandler *newhandler = new CetonStreamHandler(devkey, inputid);
49  newhandler->Open();
50  s_handlers[devkey] = newhandler;
51  s_handlers_refcnt[devkey] = 1;
52 
53  LOG(VB_RECORD, LOG_INFO,
54  QString("CetonSH[%1]: Creating new stream handler %2 for %3")
55  .arg(inputid).arg(devkey).arg(devname));
56  }
57  else
58  {
59  s_handlers_refcnt[devkey]++;
60  uint rcount = s_handlers_refcnt[devkey];
61  LOG(VB_RECORD, LOG_INFO,
62  QString("CetonSH[%1]: Using existing stream handler %2 for %3")
63  .arg(inputid).arg(devkey)
64  .arg(devname) + QString(" (%1 in use)").arg(rcount));
65  }
66 
67  return s_handlers[devkey];
68 }
69 
71 {
72  QMutexLocker locker(&s_handlers_lock);
73 
74  QString devname = ref->m_device;
75 
76  QMap<QString,uint>::iterator rit = s_handlers_refcnt.find(devname);
77  if (rit == s_handlers_refcnt.end())
78  return;
79 
80  QMap<QString,CetonStreamHandler*>::iterator it = s_handlers.find(devname);
81 
82  if (*rit > 1)
83  {
84  ref = nullptr;
85  (*rit)--;
86  return;
87  }
88 
89  if ((it != s_handlers.end()) && (*it == ref))
90  {
91  LOG(VB_RECORD, LOG_INFO, QString("CetonSH[%1]: Closing handler for %2")
92  .arg(inputid).arg(devname));
93  ref->Close();
94  delete *it;
95  s_handlers.erase(it);
96  }
97  else
98  {
99  LOG(VB_GENERAL, LOG_ERR,
100  QString("CetonSH[%1] Error: Couldn't find handler for %2")
101  .arg(inputid).arg(devname));
102  }
103 
104  s_handlers_refcnt.erase(rit);
105  ref = nullptr;
106 }
107 
108 CetonStreamHandler::CetonStreamHandler(const QString &device, int inputid)
110  "", 0, "", 0), inputid)
111 {
112  setObjectName("CetonStreamHandler");
113 
114  QStringList parts = device.split("-");
115  if (parts.size() != 2)
116  {
117  LOG(VB_GENERAL, LOG_ERR, LOC +
118  QString("Invalid device id %1").arg(m_device));
119  return;
120  }
121  m_ip_address = parts.at(0);
122 
123  QStringList tuner_parts = parts.at(1).split(".");
124  if (tuner_parts.size() == 2)
125  {
126  m_card = tuner_parts.at(0).toUInt();
127  m_tuner = tuner_parts.at(1).toUInt();
128  }
129  else
130  {
131  LOG(VB_GENERAL, LOG_ERR, LOC +
132  QString("Invalid device id %1").arg(m_device));
133  return;
134  }
135 
136  if (GetVar("diag", "Host_IP_Address") == "")
137  {
138  LOG(VB_GENERAL, LOG_ERR, LOC +
139  "Ceton tuner does not seem to be available at IP");
140  return;
141  }
142 
143  int rtspPort = 8554;
144  QString url = QString("rtsp://%1:%2/cetonmpeg%3")
145  .arg(m_ip_address).arg(rtspPort).arg(m_tuner);
146  m_tuning = IPTVTuningData(url, 0, IPTVTuningData::kNone, "", 0, "", 0);
147  m_use_rtp_streaming = true;
148 
149  m_valid = true;
150 
151  QString cardstatus = GetVar("cas", "CardStatus");
152  m_using_cablecard = cardstatus == "Inserted";
153 
154  if (!s_info_queried.contains(m_ip_address))
155  {
156  QString sernum = GetVar("diag", "Host_Serial_Number");
157  QString firmware_ver = GetVar("diag", "Host_Firmware");
158  QString hardware_ver = GetVar("diag", "Hardware_Revision");
159 
160  LOG(VB_RECORD, LOG_INFO, LOC +
161  QString("Ceton device %1 initialized. SN: %2, "
162  "Firmware ver. %3, Hardware ver. %4")
163  .arg(m_ip_address).arg(sernum).arg(firmware_ver).arg(hardware_ver));
164 
165  if (m_using_cablecard)
166  {
167  QString brand = GetVar("cas", "CardManufacturer");
168  QString auth = GetVar("cas", "CardAuthorization");
169 
170  LOG(VB_RECORD, LOG_INFO, LOC +
171  QString("Cable card installed (%1) - %2").arg(brand).arg(auth));
172  }
173  else
174  {
175  LOG(VB_RECORD, LOG_INFO, LOC +
176  "Cable card NOT installed (operating in QAM tuner mode)");
177  }
178 
179  s_info_queried.insert(m_ip_address, true);
180  }
181 }
182 
184 {
185  return Connect();
186 }
187 
189 {
190  if (m_connected)
191  {
192  TunerOff();
193  m_connected = false;
194  }
195 }
196 
198 {
199  if (!m_valid)
200  return false;
201 
202  m_connected = true;
203  return true;
204 }
205 
207 {
208  QMutexLocker locker(&m_listener_lock);
209 
210  if (!m_stream_data_list.empty())
211  {
212  LOG(VB_RECORD, LOG_INFO, LOC +
213  "Ignoring request - video streaming active");
214  return false;
215  }
216 
217  locker.unlock(); // _listener_lock
218  TunerOff();
219  return true;
220 }
221 
223 {
224  return m_connected;
225 }
226 
228 {
229  if (IsCableCardInstalled())
230  {
231  uint prog = GetVar("mux", "ProgramNumber").toUInt();
232  if (prog == 0)
233  {
234  LOG(VB_RECORD, LOG_WARNING, LOC +
235  "VerifyTuning detected program = 0");
236  return false;
237  }
238  }
239  else
240  {
241  uint frequency = GetVar("tuner", "Frequency").toUInt();
242  if (frequency != m_last_frequency)
243  {
244  LOG(VB_RECORD, LOG_WARNING, LOC +
245  "VerifyTuning detected wrong frequency");
246  return false;
247  }
248 
249  QString modulation = GetVar("tuner", "Modulation");
250  if (modulation.toUpper() != m_last_modulation.toUpper())
251  {
252  LOG(VB_RECORD, LOG_WARNING, LOC +
253  "VerifyTuning detected wrong modulation");
254  return false;
255  }
256 
257  uint program = GetVar("mux", "ProgramNumber").toUInt();
258  if (program != m_last_program)
259  {
260  LOG(VB_RECORD, LOG_WARNING, LOC +
261  "VerifyTuning detected wrong program");
262  return false;
263  }
264  }
265 
266  QString carrier_lock = GetVar("tuner", "CarrierLock");
267  if (carrier_lock != "1")
268  {
269  LOG(VB_RECORD, LOG_WARNING, LOC +
270  "VerifyTuning detected no carrier lock");
271  return false;
272  }
273 
274  QString pcr_lock = GetVar("tuner", "PCRLock");
275  if (pcr_lock != "1")
276  {
277  LOG(VB_RECORD, LOG_WARNING, LOC +
278  "VerifyTuning detected no PCR lock");
279  return false;
280  }
281 
282  return true;
283 }
284 
286 {
287  if (IsCableCardInstalled())
288  {
290  }
291  else
292  {
295  }
296 }
297 
299 {
300  bool result;
301  if (m_using_cablecard)
302  result = TuneVChannel("0");
303  else
304  result = TuneFrequency(0, "qam_256");
305 
306  return result;
307 }
308 
310  uint frequency, const QString &modulation)
311 {
312  LOG(VB_RECORD, LOG_INFO, LOC + QString("TuneFrequency(%1, %2)")
313  .arg(frequency).arg(modulation));
314 
315  if (frequency >= 100000000)
316  frequency /= 1000;
317 
318  QString modulation_id = (modulation == "qam_256") ? "2" :
319  (modulation == "qam_64") ? "0" :
320  (modulation == "ntsc-m") ? "4" :
321  (modulation == "8vsb") ? "6" :
322  "";
323  if (modulation_id == "")
324  return false;
325 
326  m_last_frequency = frequency;
327  m_last_modulation = modulation;
328 
329  QUrlQuery params;
330  params.addQueryItem("instance_id", QString::number(m_tuner));
331  params.addQueryItem("frequency", QString::number(frequency));
332  params.addQueryItem("modulation",modulation_id);
333  params.addQueryItem("tuner","1");
334  params.addQueryItem("demod","1");
335  params.addQueryItem("rst_chnl","0");
336  params.addQueryItem("force_tune","0");
337 
338  QString response;
339  uint status;
340  bool result = HttpRequest(
341  "POST", "/tune_request.cgi", params, response, status);
342 
343  if (!result)
344  {
345  LOG(VB_GENERAL, LOG_ERR, LOC +
346  QString("TuneFrequency() - HTTP status = %1 - response = %2")
347  .arg(status).arg(response));
348  }
349 
350  return result;
351 }
352 
354 {
355  LOG(VB_RECORD, LOG_INFO, LOC + QString("TuneProgram(%1)").arg(program));
356 
357  QStringList program_list = GetProgramList();
358  if (!program_list.contains(QString::number(program)))
359  {
360  LOG(VB_GENERAL, LOG_ERR, LOC +
361  QString("TuneProgram(%1) - Requested program not in the program list")
362  .arg(program));
363  return false;
364  };
365 
366 
367  m_last_program = program;
368 
369  QUrlQuery params;
370  params.addQueryItem("instance_id", QString::number(m_tuner));
371  params.addQueryItem("program", QString::number(program));
372 
373  QString response;
374  uint status;
375  bool result = HttpRequest(
376  "POST", "/program_request.cgi", params, response, status);
377 
378  if (!result)
379  {
380  LOG(VB_GENERAL, LOG_ERR, LOC +
381  QString("TuneProgram() - HTTP status = %1 - response = %2")
382  .arg(status).arg(response));
383  }
384 
385  return result;
386 }
387 
388 bool CetonStreamHandler::PerformTuneVChannel(const QString &vchannel)
389 {
390  LOG(VB_RECORD, LOG_INFO, LOC + QString("PerformTuneVChannel(%1)")
391  .arg(vchannel));
392 
393  QUrlQuery params;
394  params.addQueryItem("instance_id", QString::number(m_tuner));
395  params.addQueryItem("channel", vchannel);
396 
397  QString response;
398  uint status;
399  bool result = HttpRequest(
400  "POST", "/channel_request.cgi", params, response, status);
401 
402  if (!result)
403  {
404  LOG(VB_GENERAL, LOG_ERR, LOC +
405  QString("PerformTuneVChannel() - HTTP status = %1 - response = %2")
406  .arg(status).arg(response));
407  }
408 
409  return result;
410 }
411 
412 
413 bool CetonStreamHandler::TuneVChannel(const QString &vchannel)
414 {
415  if (GetVar("cas", "VirtualChannelNumber") == vchannel)
416  {
417  LOG(VB_RECORD, LOG_INFO, LOC + QString("Not Re-Tuning channel %1")
418  .arg(vchannel));
419  return true;
420  }
421 
422  if ((vchannel != "0") && (m_last_vchannel != "0"))
424 
425  LOG(VB_RECORD, LOG_INFO, LOC + QString("TuneVChannel(%1)").arg(vchannel));
426 
427  m_last_vchannel = vchannel;
428 
429  return PerformTuneVChannel(vchannel);
430 }
431 
433 {
434  LOG(VB_RECORD, LOG_INFO, LOC + QString("ClearProgramNumber()"));
435  PerformTuneVChannel("0");
436  for(int i=0; i<50; i++)
437  {
438  if (GetVar("mux", "ProgramNumber") == "0")
439  return;
440  usleep(20000);
441  };
442 
443  LOG(VB_GENERAL, LOG_ERR, LOC + "Program number failed to clear");
444 }
445 
447 {
448  for(int i = 1; i <= 30; i++)
449  {
450  QString prog = GetVar("mux", "ProgramNumber");
451  LOG(VB_RECORD, LOG_INFO, LOC +
452  QString("GetProgramNumber() got %1 on attempt %2")
453  .arg(prog).arg(i));
454 
455  uint prognum = prog.toUInt();
456  if (prognum != 0)
457  return prognum;
458 
459  usleep(100000);
460  };
461 
462  LOG(VB_GENERAL, LOG_ERR, LOC +
463  "GetProgramNumber() failed to get a non-zero program number");
464 
465  return 0;
466 }
467 
469  const QString &section, const QString &variable) const
470 {
471  QString loc = LOC + QString("DoGetVar(%1,%2,%3,%4) - ")
472  .arg(m_ip_address).arg(m_tuner).arg(section,variable);
473 
474  QUrlQuery params;
475  params.addQueryItem("i", QString::number(m_tuner));
476  params.addQueryItem("s", section);
477  params.addQueryItem("v", variable);
478 
479  QString response;
480  uint status;
481  if (!HttpRequest("GET", "/get_var.json", params, response, status))
482  {
483  LOG(VB_GENERAL, LOG_ERR, loc +
484  QString("HttpRequest failed - %1").arg(response));
485  return QString();
486  }
487 
488  QRegExp regex("^\\{ \"?result\"?: \"(.*)\" \\}$");
489  if (regex.indexIn(response) == -1)
490  {
491  LOG(VB_GENERAL, LOG_ERR, loc +
492  QString("unexpected http response: -->%1<--").arg(response));
493  return QString();
494  }
495 
496  QString result = regex.cap(1);
497  LOG(VB_RECORD, LOG_DEBUG, loc + QString("got: -->%1<--").arg(result));
498  return result;
499 }
500 
502 {
503  QString loc = LOC + QString("CetonHTTP: DoGetProgramList(%1,%2) - ")
504  .arg(m_ip_address).arg(m_tuner);
505 
506  QUrlQuery params;
507  params.addQueryItem("i", QString::number(m_tuner));
508 
509  QString response;
510  uint status;
511  if (!HttpRequest("GET", "/get_pat.json", params, response, status))
512  {
513  LOG(VB_GENERAL, LOG_ERR,
514  loc + QString("HttpRequest failed - %1").arg(response));
515  return QStringList();
516  }
517 
518  QRegExp regex(
519  "^\\{ \"?length\"?: \\d+(, \"?results\"?: \\[ (.*) \\])? \\}$");
520 
521  if (regex.indexIn(response) == -1)
522  {
523  LOG(VB_GENERAL, LOG_ERR,
524  loc + QString("returned unexpected output: -->%1<--")
525  .arg(response));
526  return QStringList();
527  }
528 
529  LOG(VB_RECORD, LOG_DEBUG, loc + QString("got: -->%1<--").arg(regex.cap(2)));
530  return regex.cap(2).split(", ");
531 }
532 
534  const QString &method, const QString &script,
535  const QUrlQuery &params,
536  QString &response, uint &status_code) const
537 {
538  QUrl url;
539  QNetworkRequest *request = new QNetworkRequest();
540  QByteArray data;
542 
543  url.setScheme("http");
544  url.setHost(m_ip_address);
545  url.setPath(script);
546 
547  // Specify un-cached access to the device
548  // The next two lines are automagically added by Qt
549  // request->setRawHeader("Cache-Control", "no-cache");
550  // request->setRawHeader("Pragma", "no-cache");
551  request->setAttribute(QNetworkRequest::CacheLoadControlAttribute,
552  QNetworkRequest::AlwaysNetwork);
553 
554  if ("GET" == method)
555  {
556  url.setQuery(params);
557  request->setUrl(url);
558  if (manager->download(request, &data))
559  {
560  response = QString(data);
561  status_code = 200;
562  return true;
563  }
564 
565  response = "Download failed";
566  status_code = 500;
567  return false;
568  }
569  if ("POST" == method)
570  {
571  request->setUrl(url);
572  request->setHeader(QNetworkRequest::ContentTypeHeader,
573  "application/x-www-form-urlencoded");
574  data = params.query(QUrl::FullyEncoded).toUtf8();
575  // Next line automagically added by Qt
576  // request->setHeader(QNetworkRequest::ContentLengthHeader, data.size());
577  if (manager->post(request, &data))
578  {
579  response = QString(data);
580  status_code = 200;
581  return true;
582  }
583 
584  response = "Download failed";
585  status_code = 500;
586  return false;
587  }
588 
589  delete request;
590 
591  response = "Unsupported HttpRequest method";
592  status_code = 500;
593  return false;
594 }
QString GetVar(const QString &section, const QString &variable) const
IPTVTuningData m_tuning
CetonStreamHandler(const QString &, int inputid)
bool HttpRequest(const QString &method, const QString &script, const QUrlQuery &params, QString &response, uint &status_code) const
bool IsCableCardInstalled() const
bool TuneFrequency(uint frequency, const QString &modulation)
QString m_device
static QMap< QString, bool > s_info_queried
unsigned int uint
Definition: compat.h:140
QMutex m_listener_lock
static QMap< QString, uint > s_handlers_refcnt
void setObjectName(const QString &name)
Definition: mthread.cpp:249
QStringList GetProgramList()
static QMutex s_handlers_lock
static CetonStreamHandler * Get(const QString &devname, int inputid)
bool PerformTuneVChannel(const QString &vchannel)
uint GetProgramNumber(void) const
#define LOC
-*- Mode: c++ -*- CetonStreamHandler Copyright (c) 2011 Ronald Frazier Copyright (c) 2009-2011 Daniel...
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
static QMap< QString, CetonStreamHandler * > s_handlers
static MythSystemLegacyManager * manager
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
StreamDataList m_stream_data_list
static void usleep(unsigned long time)
Definition: mthread.cpp:348
bool TuneProgram(uint program)
bool TuneVChannel(const QString &vchannel)
bool IsConnected(void) const
static void Return(CetonStreamHandler *&ref, int inputid)