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(_inputid).arg(_device)
27 
28 QMap<int,HDHRStreamHandler*> HDHRStreamHandler::_handlers;
31 
32 HDHRStreamHandler *HDHRStreamHandler::Get(const QString &devname,
33  int inputid, int majorid)
34 {
35  QMutexLocker locker(&_handlers_lock);
36 
37  QMap<int,HDHRStreamHandler*>::iterator it = _handlers.find(majorid);
38 
39  if (it == _handlers.end())
40  {
41  HDHRStreamHandler *newhandler = new HDHRStreamHandler(devname, inputid,
42  majorid);
43  newhandler->Open();
44  _handlers[majorid] = newhandler;
45  _handlers_refcnt[majorid] = 1;
46 
47  LOG(VB_RECORD, LOG_INFO,
48  QString("HDHRSH[%1]: Creating new stream handler %2 for %3")
49  .arg(inputid).arg(majorid).arg(devname));
50  }
51  else
52  {
53  _handlers_refcnt[majorid]++;
54  uint rcount = _handlers_refcnt[majorid];
55  LOG(VB_RECORD, LOG_INFO,
56  QString("HDHRSH[%1]: Using existing stream handler %2 for %3")
57  .arg(inputid).arg(majorid)
58  .arg(devname) + QString(" (%1 in use)").arg(rcount));
59  }
60 
61  return _handlers[majorid];
62 }
63 
64 void HDHRStreamHandler::Return(HDHRStreamHandler * & ref, int inputid)
65 {
66  QMutexLocker locker(&_handlers_lock);
67 
68  int majorid = ref->_majorid;
69 
70  QMap<int,uint>::iterator rit = _handlers_refcnt.find(majorid);
71  if (rit == _handlers_refcnt.end())
72  return;
73 
74  QMap<int,HDHRStreamHandler*>::iterator it = _handlers.find(majorid);
75  if (*rit > 1)
76  {
77  ref = nullptr;
78  (*rit)--;
79  return;
80  }
81 
82  if ((it != _handlers.end()) && (*it == ref))
83  {
84  LOG(VB_RECORD, LOG_INFO, QString("HDHRSH[%1]: Closing handler for %2")
85  .arg(inputid).arg(majorid));
86  ref->Close();
87  delete *it;
88  _handlers.erase(it);
89  }
90  else
91  {
92  LOG(VB_GENERAL, LOG_ERR,
93  QString("HDHRSH[%1] Error: Couldn't find handler for %2")
94  .arg(inputid).arg(majorid));
95  }
96 
97  _handlers_refcnt.erase(rit);
98  ref = nullptr;
99 }
100 
101 HDHRStreamHandler::HDHRStreamHandler(const QString &device, int inputid,
102  int majorid)
103  : StreamHandler(device, inputid)
104  , _hdhomerun_device(nullptr)
105  , _tuner(-1)
106  , _tune_mode(hdhrTuneModeNone)
107  , _majorid(majorid)
108  , _hdhr_lock(QMutex::Recursive)
109 {
110  setObjectName("HDHRStreamHandler");
111 }
112 
117 {
118  RunProlog();
119 
120  /* Create TS socket. */
121  if (!hdhomerun_device_stream_start(_hdhomerun_device))
122  {
123  LOG(VB_GENERAL, LOG_ERR, LOC +
124  "Starting recording (set target failed). Aborting.");
125  _error = true;
126  RunEpilog();
127  return;
128  }
129  hdhomerun_device_stream_flush(_hdhomerun_device);
130 
131  SetRunning(true, false, false);
132 
133  LOG(VB_RECORD, LOG_INFO, LOC + "RunTS(): begin");
134 
135  int remainder = 0;
136  QTime last_update;
137  while (_running_desired && !_error)
138  {
139  int elapsed = !last_update.isValid() ? -1 : last_update.elapsed();
140  elapsed = (elapsed < 0) ? 1000 : elapsed;
141  if (elapsed > 100)
142  {
145  UpdateFilters();
146  last_update.restart();
147  }
148 
149  size_t read_size = VIDEO_DATA_BUFFER_SIZE_1S / 8; // read up to 1/8s
150  read_size /= VIDEO_DATA_PACKET_SIZE;
151  read_size *= VIDEO_DATA_PACKET_SIZE;
152 
153  size_t data_length;
154  unsigned char *data_buffer = hdhomerun_device_stream_recv(
155  _hdhomerun_device, read_size, &data_length);
156 
157  if (!data_buffer)
158  {
159  std::this_thread::sleep_for(std::chrono::milliseconds(20));
160  continue;
161  }
162 
163  // Assume data_length is a multiple of 188 (packet size)
164 
165  _listener_lock.lock();
166 
167  if (_stream_data_list.empty())
168  {
169  _listener_lock.unlock();
170  continue;
171  }
172 
173  StreamDataList::const_iterator sit = _stream_data_list.begin();
174  for (; sit != _stream_data_list.end(); ++sit)
175  remainder = sit.key()->ProcessData(data_buffer, data_length);
176 
177  WriteMPTS(data_buffer, data_length - remainder);
178 
179  _listener_lock.unlock();
180  if (remainder != 0)
181  {
182  LOG(VB_RECORD, LOG_INFO, LOC +
183  QString("RunTS(): data_length = %1 remainder = %2")
184  .arg(data_length).arg(remainder));
185  }
186  }
187  LOG(VB_RECORD, LOG_INFO, LOC + "RunTS(): " + "shutdown");
188 
190 
191  hdhomerun_device_stream_stop(_hdhomerun_device);
192 
193  if (VERBOSE_LEVEL_CHECK(VB_RECORD, LOG_INFO))
194  {
195  struct hdhomerun_video_sock_t* vs;
196  struct hdhomerun_video_stats_t stats;
197  vs = hdhomerun_device_get_video_sock(_hdhomerun_device);
198  if (vs)
199  {
200  hdhomerun_video_get_stats(vs, &stats);
201  LOG(VB_RECORD, LOG_INFO, LOC +
202  QString("stream stats: packet_count=%1 "
203  "network_errors=%2 "
204  "transport_errors=%3 "
205  "sequence_errors=%4 "
206  "overflow_errors=%5")
207  .arg(stats.packet_count)
208  .arg(stats.network_error_count)
209  .arg(stats.transport_error_count)
210  .arg(stats.sequence_error_count)
211  .arg(stats.overflow_error_count));
212  }
213  }
214 
215  LOG(VB_RECORD, LOG_INFO, LOC + "RunTS(): " + "end");
216 
217  SetRunning(false, false, false);
218 
219  RunEpilog();
220 }
221 
222 static QString filt_str(uint pid)
223 {
224  uint pid0 = (pid / (16*16*16)) % 16;
225  uint pid1 = (pid / (16*16)) % 16;
226  uint pid2 = (pid / (16)) % 16;
227  uint pid3 = pid % 16;
228  return QString("0x%1%2%3%4")
229  .arg(pid0,0,16).arg(pid1,0,16)
230  .arg(pid2,0,16).arg(pid3,0,16);
231 }
232 
234 {
237 
239  {
240  LOG(VB_GENERAL, LOG_ERR, LOC +
241  "UpdateFilters called in wrong tune mode");
242  return false;
243  }
244 
245 #ifdef DEBUG_PID_FILTERS
246  LOG(VB_RECORD, LOG_INFO, LOC + "UpdateFilters()");
247 #endif // DEBUG_PID_FILTERS
248  QMutexLocker locker(&_pid_lock);
249 
250  QString filter = "";
251 
252  vector<uint> range_min;
253  vector<uint> range_max;
254 
255  PIDInfoMap::const_iterator it = _pid_info.begin();
256  for (; it != _pid_info.end(); ++it)
257  {
258  range_min.push_back(it.key());
259  PIDInfoMap::const_iterator eit = it;
260  for (++eit;
261  (eit != _pid_info.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 (uint 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(_hdhomerun_device);
301  _tuner_types.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  _hdhomerun_device, 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().contains("dvb"))
333  {
336  }
337  else
338  {
340  }
341 
342  return true;
343  }
344  return false;
345 }
346 
348 {
349  if (_hdhomerun_device)
350  {
351  TuneChannel("none");
352  hdhomerun_device_tuner_lockkey_release(_hdhomerun_device);
353  _hdhomerun_device = nullptr;
354  }
355  if (_device_selector)
356  {
357  hdhomerun_device_selector_destroy(_device_selector, true);
358  _device_selector = nullptr;
359  }
360 }
361 
363 {
364  _device_selector = hdhomerun_device_selector_create(nullptr);
365  if (!_device_selector)
366  {
367  LOG(VB_GENERAL, LOG_ERR, LOC + "Unable to create device selector");
368  return false;
369  }
370 
371  QStringList devices = _device.split(",");
372  for (int i = 0; i < devices.size(); ++i)
373  {
374  QByteArray ba = devices[i].toUtf8();
375  int n = hdhomerun_device_selector_load_from_str(
376  _device_selector, ba.data());
377  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Added %1 devices from %3")
378  .arg(n).arg(devices[i]));
379  }
380 
381  _hdhomerun_device = hdhomerun_device_selector_choose_and_lock(
382  _device_selector, nullptr);
383  if (!_hdhomerun_device)
384  {
385  LOG(VB_GENERAL, LOG_ERR, LOC +
386  QString("Unable to find a free device"));
387  hdhomerun_device_selector_destroy(_device_selector, true);
388  _device_selector = nullptr;
389  return false;
390  }
391 
392  _tuner = hdhomerun_device_get_tuner(_hdhomerun_device);
393 
394  LOG(VB_GENERAL, LOG_INFO, LOC +
395  QString("Connected to device(%1)")
396  .arg(hdhomerun_device_get_name(_hdhomerun_device)));
397 
398  return true;
399 }
400 
402  const QString &name, bool report_error_return, bool print_error) const
403 {
404  QMutexLocker locker(&_hdhr_lock);
405 
406  if (!_hdhomerun_device)
407  {
408  LOG(VB_GENERAL, LOG_ERR, LOC + "Get request failed (not connected)");
409  return QString();
410  }
411 
412  QString valname = QString("/tuner%1/%2").arg(_tuner).arg(name);
413  char *value = nullptr;
414  char *error = nullptr;
415  if (hdhomerun_device_get_var(
416  _hdhomerun_device, valname.toLocal8Bit().constData(),
417  &value, &error) < 0)
418  {
419  LOG(VB_GENERAL, LOG_ERR, LOC + "Get request failed" + ENO);
420  return QString();
421  }
422 
423  if (report_error_return && error)
424  {
425  if (print_error)
426  {
427  LOG(VB_GENERAL, LOG_ERR, LOC + QString("DeviceGet(%1): %2")
428  .arg(name).arg(error));
429  }
430 
431  return QString();
432  }
433 
434  return QString(value);
435 }
436 
438  const QString &name, const QString &val,
439  bool report_error_return, bool print_error)
440 {
441  QMutexLocker locker(&_hdhr_lock);
442 
443  if (!_hdhomerun_device)
444  {
445  LOG(VB_GENERAL, LOG_ERR, LOC + "Set request failed (not connected)");
446  return QString();
447  }
448 
449 
450  QString valname = QString("/tuner%1/%2").arg(_tuner).arg(name);
451  char *value = nullptr;
452  char *error = nullptr;
453 
454  if (hdhomerun_device_set_var(
455  _hdhomerun_device, valname.toLocal8Bit().constData(),
456  val.toLocal8Bit().constData(), &value, &error) < 0)
457  {
458  LOG(VB_GENERAL, LOG_ERR, LOC + "Set request failed" + ENO);
459 
460  return QString();
461  }
462 
463  if (report_error_return && error)
464  {
465  if (print_error)
466  {
467  LOG(VB_GENERAL, LOG_ERR, LOC + QString("DeviceSet(%1 %2): %3")
468  .arg(name).arg(val).arg(error));
469  }
470 
471  return QString();
472  }
473 
474  return QString(value);
475 }
476 
477 void HDHRStreamHandler::GetTunerStatus(struct hdhomerun_tuner_status_t *status)
478 {
479  hdhomerun_device_get_tuner_status(_hdhomerun_device, nullptr, status);
480 }
481 
483 {
484  return (_hdhomerun_device != nullptr);
485 }
486 
487 bool HDHRStreamHandler::TuneChannel(const QString &chn)
488 {
490 
491  QString current = TunerGet("channel");
492  if (current == chn)
493  {
494  LOG(VB_RECORD, LOG_INFO, LOC + QString("Not Re-Tuning channel %1")
495  .arg(chn));
496  return true;
497  }
498 
499  LOG(VB_RECORD, LOG_INFO, LOC + QString("Tuning channel %1 (was %2)")
500  .arg(chn).arg(current));
501  return !TunerSet("channel", chn).isEmpty();
502 }
503 
505 {
508 
510  {
511  LOG(VB_GENERAL, LOG_ERR, LOC + "TuneProgram called in wrong tune mode");
512  return false;
513  }
514 
515  LOG(VB_RECORD, LOG_INFO, LOC + QString("Tuning program %1")
516  .arg(mpeg_prog_num));
517  return !TunerSet(
518  "program", QString::number(mpeg_prog_num), false).isEmpty();
519 }
520 
521 bool HDHRStreamHandler::TuneVChannel(const QString &vchn)
522 {
524 
525  QString current = TunerGet("vchannel");
526  if (current == vchn)
527  {
528  LOG(VB_RECORD, LOG_INFO, LOC + QString("Not Re-Tuning channel %1")
529  .arg(vchn));
530  return true;
531  }
532  else
533  {
534  LOG(VB_RECORD, LOG_INFO, LOC + QString("TuneVChannel(%1) from (%2)")
535  .arg(vchn).arg(current));
536  }
537 
538  LOG(VB_RECORD, LOG_INFO, LOC + QString("Tuning vchannel %1").arg(vchn));
539  return !TunerSet("vchannel", vchn).isEmpty();
540 }
541 
542 #ifdef NEED_HDHOMERUN_DEVICE_SELECTOR_LOAD_FROM_STR
543 
544 // Provide functions we need that are not included in some versions of
545 // libhdhomerun. These were taken
546 // from version 20180817 and modified as needed.
547 
548 struct hdhomerun_device_selector_t {
549  struct hdhomerun_device_t **hd_list;
550  size_t hd_count;
551  struct hdhomerun_debug_t *dbg;
552 };
553 
554 static int hdhomerun_device_selector_load_from_str_discover(struct hdhomerun_device_selector_t *hds, uint32_t target_ip, uint32_t device_id)
555 {
556  struct hdhomerun_discover_device_t result;
557  int discover_count = hdhomerun_discover_find_devices_custom(target_ip, HDHOMERUN_DEVICE_TYPE_TUNER, device_id, &result, 1);
558  if (discover_count != 1) {
559  return 0;
560  }
561 
562  int count = 0;
563  unsigned int tuner_index;
564  for (tuner_index = 0; tuner_index < result.tuner_count; tuner_index++) {
565  struct hdhomerun_device_t *hd = hdhomerun_device_create(result.device_id, result.ip_addr, tuner_index, hds->dbg);
566  if (!hd) {
567  continue;
568  }
569 
570  hdhomerun_device_selector_add_device(hds, hd);
571  count++;
572  }
573 
574  return count;
575 }
576 
577 static int hdhomerun_device_selector_load_from_str(struct hdhomerun_device_selector_t *hds, char *device_str)
578 {
579  /*
580  * IP address based device_str.
581  */
582  unsigned int a[4];
583  if (sscanf(device_str, "%u.%u.%u.%u", &a[0], &a[1], &a[2], &a[3]) == 4) {
584  uint32_t ip_addr = (uint32_t)((a[0] << 24) | (a[1] << 16) | (a[2] << 8) | (a[3] << 0));
585 
586  /*
587  * IP address + tuner number.
588  */
589  unsigned int tuner;
590  if (sscanf(device_str, "%u.%u.%u.%u-%u", &a[0], &a[1], &a[2], &a[3], &tuner) == 5) {
591  struct hdhomerun_device_t *hd = hdhomerun_device_create(HDHOMERUN_DEVICE_ID_WILDCARD, ip_addr, tuner, hds->dbg);
592  if (!hd) {
593  return 0;
594  }
595 
596  hdhomerun_device_selector_add_device(hds, hd);
597  return 1;
598  }
599 
600  /*
601  * IP address only - discover and add tuners.
602  */
603  return hdhomerun_device_selector_load_from_str_discover(hds, ip_addr, HDHOMERUN_DEVICE_ID_WILDCARD);
604  }
605 
606  /*
607  * Device ID based device_str.
608  */
609  char *end;
610  uint32_t device_id = (uint32_t)strtoul(device_str, &end, 16);
611  if ((end == device_str + 8) && hdhomerun_discover_validate_device_id(device_id)) {
612  /*
613  * IP address + tuner number.
614  */
615  if (*end == '-') {
616  unsigned int tuner = (unsigned int)strtoul(end + 1, NULL, 10);
617  struct hdhomerun_device_t *hd = hdhomerun_device_create(device_id, 0, tuner, hds->dbg);
618  if (!hd) {
619  return 0;
620  }
621 
622  hdhomerun_device_selector_add_device(hds, hd);
623  return 1;
624  }
625 
626  /*
627  * Device ID only - discover and add tuners.
628  */
629  return hdhomerun_device_selector_load_from_str_discover(hds, 0, device_id);
630  }
631 
632  return 0;
633 }
634 
635 #endif
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:216
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:41
bool UpdateFiltersFromStreamData(void)
#define LOC
static const int kTunerTypeOCUR
volatile bool _error
bool TuneVChannel(const QString &vchn)
unsigned int uint
Definition: compat.h:140
HDHRTuneMode _tune_mode
static QMap< int, uint > _handlers_refcnt
void setObjectName(const QString &name)
Definition: mthread.cpp:250
QString TunerGet(const QString &name, bool report_error_return=true, bool print_error=true) const
static void Return(HDHRStreamHandler *&ref, int inputid)
hdhomerun_device_selector_t * _device_selector
void SetRunning(bool running, bool using_buffering, bool using_section_reader)
hdhomerun_device_t * _hdhomerun_device
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
QMutex _listener_lock
volatile bool _running_desired
static QString filt_str(uint pid)
const char * name
Definition: ParseText.cpp:339
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:99
bool IsConnected(void) const
bool TuneChannel(const QString &chanid)
StreamDataList _stream_data_list
static VideoFilter * new_filter(VideoFrameType inpixfmt, VideoFrameType outpixfmt, int *width, int *height, char *options, int threads)
void GetTunerStatus(struct hdhomerun_tuner_status_t *status)
bool UpdateFilters(void) override
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
static QMap< int, HDHRStreamHandler * > _handlers
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:203
QString TunerSet(const QString &name, const QString &value, bool report_error_return=true, bool print_error=true)
static QMutex _handlers_lock
void WriteMPTS(unsigned char *buffer, uint len)
Write out a copy of the raw MPTS.
static HDHRStreamHandler * Get(const QString &devicename, int inputid, int majorid)
vector< DTVTunerType > _tuner_types
bool TuneProgram(uint mpeg_prog_num)
PIDInfoMap _pid_info
bool RemoveAllPIDFilters(void)
HDHRStreamHandler(const QString &, int inputid, int majorid)