MythTV  master
hdhrstreamhandler.cpp
Go to the documentation of this file.
1 // -*- Mode: c++ -*-
2 
3 // POSIX headers
4 #include <fcntl.h>
5 #include <unistd.h>
6 #ifndef _WIN32
7 #include <sys/select.h>
8 #include <sys/ioctl.h>
9 #endif
10 #include <chrono> // for milliseconds
11 #include <thread> // for sleep_for
12 
13 // MythTV headers
14 #include "hdhrstreamhandler.h"
15 #include "hdhrchannel.h"
16 #include "dtvsignalmonitor.h"
17 #include "streamlisteners.h"
18 #include "mpegstreamdata.h"
19 #include "cardutil.h"
20 #include "mythlogging.h"
21 
22 #ifdef NEED_HDHOMERUN_DEVICE_SELECTOR_LOAD_FROM_STR
23 static int hdhomerun_device_selector_load_from_str(struct hdhomerun_device_selector_t *hds, char *device_str);
24 #endif
25 
26 #define LOC QString("HDHRSH[%1](%2): ").arg(m_inputId).arg(m_device)
27 
28 QMap<int,HDHRStreamHandler*> HDHRStreamHandler::s_handlers;
31 
32 HDHRStreamHandler *HDHRStreamHandler::Get(const QString &devname,
33  int inputid, int majorid)
34 {
35  QMutexLocker locker(&s_handlersLock);
36 
37  QMap<int,HDHRStreamHandler*>::iterator it = s_handlers.find(majorid);
38 
39  if (it == s_handlers.end())
40  {
41  auto *newhandler = new HDHRStreamHandler(devname, inputid, majorid);
42  newhandler->Open();
43  s_handlers[majorid] = newhandler;
44  s_handlersRefCnt[majorid] = 1;
45 
46  LOG(VB_RECORD, LOG_INFO,
47  QString("HDHRSH[%1]: Creating new stream handler %2 for %3")
48  .arg(inputid).arg(majorid).arg(devname));
49  }
50  else
51  {
52  s_handlersRefCnt[majorid]++;
53  uint rcount = s_handlersRefCnt[majorid];
54  LOG(VB_RECORD, LOG_INFO,
55  QString("HDHRSH[%1]: Using existing stream handler %2 for %3")
56  .arg(inputid).arg(majorid)
57  .arg(devname) + QString(" (%1 in use)").arg(rcount));
58  }
59 
60  return s_handlers[majorid];
61 }
62 
63 void HDHRStreamHandler::Return(HDHRStreamHandler * & ref, int inputid)
64 {
65  QMutexLocker locker(&s_handlersLock);
66 
67  int majorid = ref->m_majorId;
68 
69  QMap<int,uint>::iterator rit = s_handlersRefCnt.find(majorid);
70  if (rit == s_handlersRefCnt.end())
71  return;
72 
73  QMap<int,HDHRStreamHandler*>::iterator it = s_handlers.find(majorid);
74  if (*rit > 1)
75  {
76  ref = nullptr;
77  (*rit)--;
78  return;
79  }
80 
81  if ((it != s_handlers.end()) && (*it == ref))
82  {
83  LOG(VB_RECORD, LOG_INFO, QString("HDHRSH[%1]: Closing handler for %2")
84  .arg(inputid).arg(majorid));
85  ref->Close();
86  delete *it;
87  s_handlers.erase(it);
88  }
89  else
90  {
91  LOG(VB_GENERAL, LOG_ERR,
92  QString("HDHRSH[%1] Error: Couldn't find handler for %2")
93  .arg(inputid).arg(majorid));
94  }
95 
96  s_handlersRefCnt.erase(rit);
97  ref = nullptr;
98 }
99 
100 HDHRStreamHandler::HDHRStreamHandler(const QString &device, int inputid,
101  int majorid)
102  : StreamHandler(device, inputid)
103  , m_majorId(majorid)
104 {
105  setObjectName("HDHRStreamHandler");
106 }
107 
112 {
113  RunProlog();
114 
115  {
116  QMutexLocker locker(&m_hdhrLock);
117 
118  /* Create TS socket. */
119  if (!hdhomerun_device_stream_start(m_hdhomerunDevice))
120  {
121  LOG(VB_GENERAL, LOG_ERR, LOC +
122  "Starting recording (set target failed). Aborting.");
123  m_bError = true;
124  RunEpilog();
125  return;
126  }
127  hdhomerun_device_stream_flush(m_hdhomerunDevice);
128  }
129 
130  SetRunning(true, false, false);
131 
132  LOG(VB_RECORD, LOG_INFO, LOC + "RunTS(): begin");
133 
134  int remainder = 0;
135  QElapsedTimer last_update;
136  while (m_runningDesired && !m_bError)
137  {
138  int elapsed = !last_update.isValid() ? -1 : last_update.elapsed();
139  elapsed = (elapsed < 0) ? 1000 : elapsed;
140  if (elapsed > 100)
141  {
144  UpdateFilters();
145  last_update.restart();
146  }
147 
148  size_t read_size = VIDEO_DATA_BUFFER_SIZE_1S / 8; // read up to 1/8s
149  read_size /= VIDEO_DATA_PACKET_SIZE;
150  read_size *= VIDEO_DATA_PACKET_SIZE;
151 
152  size_t data_length = 0;
153  unsigned char *data_buffer = hdhomerun_device_stream_recv(
154  m_hdhomerunDevice, read_size, &data_length);
155 
156  if (!data_buffer)
157  {
158  std::this_thread::sleep_for(std::chrono::milliseconds(20));
159  continue;
160  }
161 
162  // Assume data_length is a multiple of 188 (packet size)
163 
164  m_listenerLock.lock();
165 
166  if (m_streamDataList.empty())
167  {
168  m_listenerLock.unlock();
169  continue;
170  }
171 
172  for (auto sit = m_streamDataList.cbegin(); sit != m_streamDataList.cend(); ++sit)
173  remainder = sit.key()->ProcessData(data_buffer, data_length);
174 
175  WriteMPTS(data_buffer, data_length - remainder);
176 
177  m_listenerLock.unlock();
178  if (remainder != 0)
179  {
180  LOG(VB_RECORD, LOG_INFO, LOC +
181  QString("RunTS(): data_length = %1 remainder = %2")
182  .arg(data_length).arg(remainder));
183  }
184  }
185  LOG(VB_RECORD, LOG_INFO, LOC + "RunTS(): " + "shutdown");
186 
188 
189  {
190  QMutexLocker locker(&m_hdhrLock);
191  hdhomerun_device_stream_stop(m_hdhomerunDevice);
192  }
193 
194  if (VERBOSE_LEVEL_CHECK(VB_RECORD, LOG_INFO))
195  {
196  struct hdhomerun_video_sock_t* vs = nullptr;
197  struct hdhomerun_video_stats_t stats {};
198  vs = hdhomerun_device_get_video_sock(m_hdhomerunDevice);
199  if (vs)
200  {
201  hdhomerun_video_get_stats(vs, &stats);
202  LOG(VB_RECORD, LOG_INFO, LOC +
203  QString("stream stats: packet_count=%1 "
204  "network_errors=%2 "
205  "transport_errors=%3 "
206  "sequence_errors=%4 "
207  "overflow_errors=%5")
208  .arg(stats.packet_count)
209  .arg(stats.network_error_count)
210  .arg(stats.transport_error_count)
211  .arg(stats.sequence_error_count)
212  .arg(stats.overflow_error_count));
213  }
214  }
215 
216  LOG(VB_RECORD, LOG_INFO, LOC + "RunTS(): " + "end");
217 
218  SetRunning(false, false, false);
219 
220  RunEpilog();
221 }
222 
223 static QString filt_str(uint pid)
224 {
225  uint pid0 = (pid / (16*16*16)) % 16;
226  uint pid1 = (pid / (16*16)) % 16;
227  uint pid2 = (pid / (16)) % 16;
228  uint pid3 = pid % 16;
229  return QString("0x%1%2%3%4")
230  .arg(pid0,0,16).arg(pid1,0,16)
231  .arg(pid2,0,16).arg(pid3,0,16);
232 }
233 
235 {
238 
240  {
241  LOG(VB_GENERAL, LOG_ERR, LOC +
242  "UpdateFilters called in wrong tune mode");
243  return false;
244  }
245 
246 #ifdef DEBUG_PID_FILTERS
247  LOG(VB_RECORD, LOG_INFO, LOC + "UpdateFilters()");
248 #endif // DEBUG_PID_FILTERS
249  QMutexLocker locker(&m_pidLock);
250 
251  QString filter = "";
252 
253  vector<uint> range_min;
254  vector<uint> range_max;
255 
256  for (auto it = m_pidInfo.cbegin(); it != m_pidInfo.cend(); ++it)
257  {
258  range_min.push_back(it.key());
259  PIDInfoMap::const_iterator eit = it;
260  for (++eit;
261  (eit != m_pidInfo.end()) && (it.key() + 1 == eit.key());
262  ++it, ++eit);
263  range_max.push_back(it.key());
264  }
265  if (range_min.size() > 16)
266  {
267  range_min.resize(16);
268  uint pid_max = range_max.back();
269  range_max.resize(15);
270  range_max.push_back(pid_max);
271  }
272 
273  for (size_t i = 0; i < range_min.size(); i++)
274  {
275  filter += filt_str(range_min[i]);
276  if (range_min[i] != range_max[i])
277  filter += QString("-%1").arg(filt_str(range_max[i]));
278  filter += " ";
279  }
280 
281  filter = filter.trimmed();
282 
283  QString new_filter = TunerSet("filter", filter);
284 
285 #ifdef DEBUG_PID_FILTERS
286  QString msg = QString("Filter: '%1'").arg(filter);
287  if (filter != new_filter)
288  msg += QString("\n\t\t\t\t'%2'").arg(new_filter);
289 
290  LOG(VB_RECORD, LOG_INFO, LOC + msg);
291 #endif // DEBUG_PID_FILTERS
292 
293  return filter == new_filter;
294 }
295 
297 {
298  if (Connect())
299  {
300  const char *model = hdhomerun_device_get_model_str(m_hdhomerunDevice);
301  m_tunerTypes.clear();
302  if (QString(model).toLower().contains("cablecard"))
303  {
304  QString status_channel = "none";
305  hdhomerun_tuner_status_t t_status {};
306 
307  if (hdhomerun_device_get_oob_status(
308  m_hdhomerunDevice, nullptr, &t_status) < 0)
309  {
310  LOG(VB_GENERAL, LOG_ERR, LOC +
311  "Failed to query Cable card OOB channel");
312  }
313  else
314  {
315  status_channel = QString(t_status.channel);
316  LOG(VB_RECORD, LOG_INFO, LOC +
317  QString("Cable card OOB channel is '%1'")
318  .arg(status_channel));
319  }
320 
321  if (status_channel == "none")
322  {
323  LOG(VB_RECORD, LOG_INFO, LOC + "Cable card is not present");
325  }
326  else
327  {
328  LOG(VB_RECORD, LOG_INFO, LOC + "Cable card is present");
330  }
331  }
332  else if (QString(model).toLower().endsWith("dvbt"))
333  {
335  }
336  else if (QString(model).toLower().endsWith("dvbc"))
337  {
339  }
340  else if (QString(model).toLower().endsWith("dvbtc"))
341  {
344  }
345  else
346  {
348  }
349 
350  return true;
351  }
352  return false;
353 }
354 
356 {
357  if (m_hdhomerunDevice)
358  {
359  TuneChannel("none");
360  hdhomerun_device_tuner_lockkey_release(m_hdhomerunDevice);
361  m_hdhomerunDevice = nullptr;
362  }
363  if (m_deviceSelector)
364  {
365  hdhomerun_device_selector_destroy(m_deviceSelector, true);
366  m_deviceSelector = nullptr;
367  }
368 }
369 
371 {
372  m_deviceSelector = hdhomerun_device_selector_create(nullptr);
373  if (!m_deviceSelector)
374  {
375  LOG(VB_GENERAL, LOG_ERR, LOC + "Unable to create device selector");
376  return false;
377  }
378 
379  QStringList devices = m_device.split(",");
380  for (int i = 0; i < devices.size(); ++i)
381  {
382  QByteArray ba = devices[i].toUtf8();
383  int n = hdhomerun_device_selector_load_from_str(
384  m_deviceSelector, ba.data());
385  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Added %1 devices from %3")
386  .arg(n).arg(devices[i]));
387  }
388 
389  m_hdhomerunDevice = hdhomerun_device_selector_choose_and_lock(
390  m_deviceSelector, nullptr);
391  if (!m_hdhomerunDevice)
392  {
393  LOG(VB_GENERAL, LOG_ERR, LOC +
394  QString("Unable to find a free device"));
395  hdhomerun_device_selector_destroy(m_deviceSelector, true);
396  m_deviceSelector = nullptr;
397  return false;
398  }
399 
400  m_tuner = hdhomerun_device_get_tuner(m_hdhomerunDevice);
401 
402  LOG(VB_GENERAL, LOG_INFO, LOC +
403  QString("Connected to device(%1)")
404  .arg(hdhomerun_device_get_name(m_hdhomerunDevice)));
405 
406  return true;
407 }
408 
410  const QString &name, bool report_error_return, bool print_error) const
411 {
412  QMutexLocker locker(&m_hdhrLock);
413 
414  if (!m_hdhomerunDevice)
415  {
416  LOG(VB_GENERAL, LOG_ERR, LOC + "Get request failed (not connected)");
417  return QString();
418  }
419 
420  QString valname = QString("/tuner%1/%2").arg(m_tuner).arg(name);
421  char *value = nullptr;
422  char *error = nullptr;
423  if (hdhomerun_device_get_var(
424  m_hdhomerunDevice, valname.toLocal8Bit().constData(),
425  &value, &error) < 0)
426  {
427  LOG(VB_GENERAL, LOG_ERR, LOC +
428  QString("Get %1 request failed").arg(valname) + ENO);
429  return QString();
430  }
431 
432  if (report_error_return && error)
433  {
434  if (print_error)
435  {
436  LOG(VB_GENERAL, LOG_ERR, LOC + QString("DeviceGet(%1): %2")
437  .arg(name).arg(error));
438  }
439 
440  return QString();
441  }
442 
443  return QString(value);
444 }
445 
447  const QString &name, const QString &val,
448  bool report_error_return, bool print_error)
449 {
450  QMutexLocker locker(&m_hdhrLock);
451 
452  if (!m_hdhomerunDevice)
453  {
454  LOG(VB_GENERAL, LOG_ERR, LOC + "Set request failed (not connected)");
455  return QString();
456  }
457 
458 
459  QString valname = QString("/tuner%1/%2").arg(m_tuner).arg(name);
460  char *value = nullptr;
461  char *error = nullptr;
462 
463  if (hdhomerun_device_set_var(
464  m_hdhomerunDevice, valname.toLocal8Bit().constData(),
465  val.toLocal8Bit().constData(), &value, &error) < 0)
466  {
467  LOG(VB_GENERAL, LOG_ERR, LOC +
468  QString("Set %1 to '%2' request failed").arg(valname).arg(val) +
469  ENO);
470 
471  return QString();
472  }
473 
474  if (report_error_return && error)
475  {
476  if (print_error)
477  {
478  LOG(VB_GENERAL, LOG_ERR, LOC + QString("DeviceSet(%1 %2): %3")
479  .arg(name).arg(val).arg(error));
480  }
481 
482  return QString();
483  }
484 
485  return QString(value);
486 }
487 
488 void HDHRStreamHandler::GetTunerStatus(struct hdhomerun_tuner_status_t *status)
489 {
490  QMutexLocker locker(&m_hdhrLock);
491 
492  hdhomerun_device_get_tuner_status(m_hdhomerunDevice, nullptr, status);
493 }
494 
496 {
497  return (m_hdhomerunDevice != nullptr);
498 }
499 
500 bool HDHRStreamHandler::TuneChannel(const QString &chanid)
501 {
503 
504  QString current = TunerGet("channel");
505  if (current == chanid)
506  {
507  LOG(VB_RECORD, LOG_INFO, LOC + QString("Not Re-Tuning channel %1")
508  .arg(chanid));
509  return true;
510  }
511 
512  LOG(VB_RECORD, LOG_INFO, LOC + QString("Tuning channel %1 (was %2)")
513  .arg(chanid).arg(current));
514  return !TunerSet("channel", chanid).isEmpty();
515 }
516 
518 {
521 
523  {
524  LOG(VB_GENERAL, LOG_ERR, LOC + "TuneProgram called in wrong tune mode");
525  return false;
526  }
527 
528  LOG(VB_RECORD, LOG_INFO, LOC + QString("Tuning program %1")
529  .arg(mpeg_prog_num));
530  return !TunerSet(
531  "program", QString::number(mpeg_prog_num), false).isEmpty();
532 }
533 
534 bool HDHRStreamHandler::TuneVChannel(const QString &vchn)
535 {
537 
538  QString current = TunerGet("vchannel");
539  if (current == vchn)
540  {
541  LOG(VB_RECORD, LOG_INFO, LOC + QString("Not Re-Tuning channel %1")
542  .arg(vchn));
543  return true;
544  }
545  LOG(VB_RECORD, LOG_INFO, LOC + QString("TuneVChannel(%1) from (%2)")
546  .arg(vchn).arg(current));
547 
548  LOG(VB_RECORD, LOG_INFO, LOC + QString("Tuning vchannel %1").arg(vchn));
549  return !TunerSet("vchannel", vchn).isEmpty();
550 }
551 
552 #ifdef NEED_HDHOMERUN_DEVICE_SELECTOR_LOAD_FROM_STR
553 
554 // Provide functions we need that are not included in some versions of
555 // libhdhomerun. These were taken
556 // from version 20180817 and modified as needed.
557 
558 struct hdhomerun_device_selector_t {
559  struct hdhomerun_device_t **hd_list;
560  size_t hd_count;
561  struct hdhomerun_debug_t *dbg;
562 };
563 
564 static int hdhomerun_device_selector_load_from_str_discover(struct hdhomerun_device_selector_t *hds, uint32_t target_ip, uint32_t device_id)
565 {
566  struct hdhomerun_discover_device_t result;
567  int discover_count = hdhomerun_discover_find_devices_custom(target_ip, HDHOMERUN_DEVICE_TYPE_TUNER, device_id, &result, 1);
568  if (discover_count != 1) {
569  return 0;
570  }
571 
572  int count = 0;
573  unsigned int tuner_index;
574  for (tuner_index = 0; tuner_index < result.tuner_count; tuner_index++) {
575  struct hdhomerun_device_t *hd = hdhomerun_device_create(result.device_id, result.ip_addr, tuner_index, hds->dbg);
576  if (!hd) {
577  continue;
578  }
579 
580  hdhomerun_device_selector_add_device(hds, hd);
581  count++;
582  }
583 
584  return count;
585 }
586 
587 static int hdhomerun_device_selector_load_from_str(struct hdhomerun_device_selector_t *hds, char *device_str)
588 {
589  /*
590  * IP address based device_str.
591  */
592  unsigned int a[4];
593  if (sscanf(device_str, "%u.%u.%u.%u", &a[0], &a[1], &a[2], &a[3]) == 4) {
594  uint32_t ip_addr = (uint32_t)((a[0] << 24) | (a[1] << 16) | (a[2] << 8) | (a[3] << 0));
595 
596  /*
597  * IP address + tuner number.
598  */
599  unsigned int tuner;
600  if (sscanf(device_str, "%u.%u.%u.%u-%u", &a[0], &a[1], &a[2], &a[3], &tuner) == 5) {
601  struct hdhomerun_device_t *hd = hdhomerun_device_create(HDHOMERUN_DEVICE_ID_WILDCARD, ip_addr, tuner, hds->dbg);
602  if (!hd) {
603  return 0;
604  }
605 
606  hdhomerun_device_selector_add_device(hds, hd);
607  return 1;
608  }
609 
610  /*
611  * IP address only - discover and add tuners.
612  */
613  return hdhomerun_device_selector_load_from_str_discover(hds, ip_addr, HDHOMERUN_DEVICE_ID_WILDCARD);
614  }
615 
616  /*
617  * Device ID based device_str.
618  */
619  char *end;
620  uint32_t device_id = (uint32_t)strtoul(device_str, &end, 16);
621  if ((end == device_str + 8) && hdhomerun_discover_validate_device_id(device_id)) {
622  /*
623  * IP address + tuner number.
624  */
625  if (*end == '-') {
626  unsigned int tuner = (unsigned int)strtoul(end + 1, NULL, 10);
627  struct hdhomerun_device_t *hd = hdhomerun_device_create(device_id, 0, tuner, hds->dbg);
628  if (!hd) {
629  return 0;
630  }
631 
632  hdhomerun_device_selector_add_device(hds, hd);
633  return 1;
634  }
635 
636  /*
637  * Device ID only - discover and add tuners.
638  */
639  return hdhomerun_device_selector_load_from_str_discover(hds, 0, device_id);
640  }
641 
642  return 0;
643 }
644 
645 #endif
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:215
void run(void) override
Reads HDHomeRun socket for tables & data.
static const int kTunerTypeDVBT
static const int kTunerTypeATSC
#define NULL
Definition: H264Parser.h:62
static void error(const char *str,...)
Definition: vbi.c:42
bool UpdateFiltersFromStreamData(void)
QString m_device
#define LOC
static const int kTunerTypeOCUR
bool TuneVChannel(const QString &vchn)
static QMap< int, uint > s_handlersRefCnt
hdhomerun_device_selector_t * m_deviceSelector
void setObjectName(const QString &name)
Definition: mthread.cpp:249
QString TunerGet(const QString &name, bool report_error_return=true, bool print_error=true) const
static void Return(HDHRStreamHandler *&ref, int inputid)
void SetRunning(bool running, bool using_buffering, bool using_section_reader)
static const int kTunerTypeDVBC
#define VERBOSE_LEVEL_CHECK(_MASK_, _LEVEL_)
Definition: mythlogging.h:24
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
hdhomerun_device_t * m_hdhomerunDevice
QMutex m_listenerLock
static QString filt_str(uint pid)
unsigned int uint
Definition: compat.h:140
HDHRStreamHandler(const QString &device, int inputid, int majorid)
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:99
bool IsConnected(void) const
volatile bool m_runningDesired
bool TuneChannel(const QString &chanid)
HDHRTuneMode m_tuneMode
void GetTunerStatus(struct hdhomerun_tuner_status_t *status)
bool UpdateFilters(void) override
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:202
volatile bool m_bError
QString TunerSet(const QString &name, const QString &value, bool report_error_return=true, bool print_error=true)
static QMutex s_handlersLock
void WriteMPTS(unsigned char *buffer, uint len)
Write out a copy of the raw MPTS.
StreamDataList m_streamDataList
bool TuneProgram(uint mpeg_prog_num)
static HDHRStreamHandler * Get(const QString &devname, int inputid, int majorid)
PIDInfoMap m_pidInfo
static QMap< int, HDHRStreamHandler * > s_handlers
bool RemoveAllPIDFilters(void)
vector< DTVTunerType > m_tunerTypes