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_handlers_lock);
36 
37  QMap<int,HDHRStreamHandler*>::iterator it = s_handlers.find(majorid);
38 
39  if (it == s_handlers.end())
40  {
41  HDHRStreamHandler *newhandler = new HDHRStreamHandler(devname, inputid,
42  majorid);
43  newhandler->Open();
44  s_handlers[majorid] = newhandler;
45  s_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  s_handlers_refcnt[majorid]++;
54  uint rcount = s_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 s_handlers[majorid];
62 }
63 
64 void HDHRStreamHandler::Return(HDHRStreamHandler * & ref, int inputid)
65 {
66  QMutexLocker locker(&s_handlers_lock);
67 
68  int majorid = ref->m_majorid;
69 
70  QMap<int,uint>::iterator rit = s_handlers_refcnt.find(majorid);
71  if (rit == s_handlers_refcnt.end())
72  return;
73 
74  QMap<int,HDHRStreamHandler*>::iterator it = s_handlers.find(majorid);
75  if (*rit > 1)
76  {
77  ref = nullptr;
78  (*rit)--;
79  return;
80  }
81 
82  if ((it != s_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  s_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  s_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  , m_majorid(majorid)
105 {
106  setObjectName("HDHRStreamHandler");
107 }
108 
113 {
114  RunProlog();
115 
116  /* Create TS socket. */
117  if (!hdhomerun_device_stream_start(m_hdhomerun_device))
118  {
119  LOG(VB_GENERAL, LOG_ERR, LOC +
120  "Starting recording (set target failed). Aborting.");
121  m_bError = true;
122  RunEpilog();
123  return;
124  }
125  hdhomerun_device_stream_flush(m_hdhomerun_device);
126 
127  SetRunning(true, false, false);
128 
129  LOG(VB_RECORD, LOG_INFO, LOC + "RunTS(): begin");
130 
131  int remainder = 0;
132  QTime last_update;
133  while (m_running_desired && !m_bError)
134  {
135  int elapsed = !last_update.isValid() ? -1 : last_update.elapsed();
136  elapsed = (elapsed < 0) ? 1000 : elapsed;
137  if (elapsed > 100)
138  {
141  UpdateFilters();
142  last_update.restart();
143  }
144 
145  size_t read_size = VIDEO_DATA_BUFFER_SIZE_1S / 8; // read up to 1/8s
146  read_size /= VIDEO_DATA_PACKET_SIZE;
147  read_size *= VIDEO_DATA_PACKET_SIZE;
148 
149  size_t data_length;
150  unsigned char *data_buffer = hdhomerun_device_stream_recv(
151  m_hdhomerun_device, read_size, &data_length);
152 
153  if (!data_buffer)
154  {
155  std::this_thread::sleep_for(std::chrono::milliseconds(20));
156  continue;
157  }
158 
159  // Assume data_length is a multiple of 188 (packet size)
160 
161  m_listener_lock.lock();
162 
163  if (m_stream_data_list.empty())
164  {
165  m_listener_lock.unlock();
166  continue;
167  }
168 
169  StreamDataList::const_iterator sit = m_stream_data_list.begin();
170  for (; sit != m_stream_data_list.end(); ++sit)
171  remainder = sit.key()->ProcessData(data_buffer, data_length);
172 
173  WriteMPTS(data_buffer, data_length - remainder);
174 
175  m_listener_lock.unlock();
176  if (remainder != 0)
177  {
178  LOG(VB_RECORD, LOG_INFO, LOC +
179  QString("RunTS(): data_length = %1 remainder = %2")
180  .arg(data_length).arg(remainder));
181  }
182  }
183  LOG(VB_RECORD, LOG_INFO, LOC + "RunTS(): " + "shutdown");
184 
186 
187  hdhomerun_device_stream_stop(m_hdhomerun_device);
188 
189  if (VERBOSE_LEVEL_CHECK(VB_RECORD, LOG_INFO))
190  {
191  struct hdhomerun_video_sock_t* vs;
192  struct hdhomerun_video_stats_t stats {};
193  vs = hdhomerun_device_get_video_sock(m_hdhomerun_device);
194  if (vs)
195  {
196  hdhomerun_video_get_stats(vs, &stats);
197  LOG(VB_RECORD, LOG_INFO, LOC +
198  QString("stream stats: packet_count=%1 "
199  "network_errors=%2 "
200  "transport_errors=%3 "
201  "sequence_errors=%4 "
202  "overflow_errors=%5")
203  .arg(stats.packet_count)
204  .arg(stats.network_error_count)
205  .arg(stats.transport_error_count)
206  .arg(stats.sequence_error_count)
207  .arg(stats.overflow_error_count));
208  }
209  }
210 
211  LOG(VB_RECORD, LOG_INFO, LOC + "RunTS(): " + "end");
212 
213  SetRunning(false, false, false);
214 
215  RunEpilog();
216 }
217 
218 static QString filt_str(uint pid)
219 {
220  uint pid0 = (pid / (16*16*16)) % 16;
221  uint pid1 = (pid / (16*16)) % 16;
222  uint pid2 = (pid / (16)) % 16;
223  uint pid3 = pid % 16;
224  return QString("0x%1%2%3%4")
225  .arg(pid0,0,16).arg(pid1,0,16)
226  .arg(pid2,0,16).arg(pid3,0,16);
227 }
228 
230 {
233 
235  {
236  LOG(VB_GENERAL, LOG_ERR, LOC +
237  "UpdateFilters called in wrong tune mode");
238  return false;
239  }
240 
241 #ifdef DEBUG_PID_FILTERS
242  LOG(VB_RECORD, LOG_INFO, LOC + "UpdateFilters()");
243 #endif // DEBUG_PID_FILTERS
244  QMutexLocker locker(&m_pid_lock);
245 
246  QString filter = "";
247 
248  vector<uint> range_min;
249  vector<uint> range_max;
250 
251  PIDInfoMap::const_iterator it = m_pid_info.begin();
252  for (; it != m_pid_info.end(); ++it)
253  {
254  range_min.push_back(it.key());
255  PIDInfoMap::const_iterator eit = it;
256  for (++eit;
257  (eit != m_pid_info.end()) && (it.key() + 1 == eit.key());
258  ++it, ++eit);
259  range_max.push_back(it.key());
260  }
261  if (range_min.size() > 16)
262  {
263  range_min.resize(16);
264  uint pid_max = range_max.back();
265  range_max.resize(15);
266  range_max.push_back(pid_max);
267  }
268 
269  for (size_t i = 0; i < range_min.size(); i++)
270  {
271  filter += filt_str(range_min[i]);
272  if (range_min[i] != range_max[i])
273  filter += QString("-%1").arg(filt_str(range_max[i]));
274  filter += " ";
275  }
276 
277  filter = filter.trimmed();
278 
279  QString new_filter = TunerSet("filter", filter);
280 
281 #ifdef DEBUG_PID_FILTERS
282  QString msg = QString("Filter: '%1'").arg(filter);
283  if (filter != new_filter)
284  msg += QString("\n\t\t\t\t'%2'").arg(new_filter);
285 
286  LOG(VB_RECORD, LOG_INFO, LOC + msg);
287 #endif // DEBUG_PID_FILTERS
288 
289  return filter == new_filter;
290 }
291 
293 {
294  if (Connect())
295  {
296  const char *model = hdhomerun_device_get_model_str(m_hdhomerun_device);
297  m_tuner_types.clear();
298  if (QString(model).toLower().contains("cablecard"))
299  {
300  QString status_channel = "none";
301  hdhomerun_tuner_status_t t_status {};
302 
303  if (hdhomerun_device_get_oob_status(
304  m_hdhomerun_device, nullptr, &t_status) < 0)
305  {
306  LOG(VB_GENERAL, LOG_ERR, LOC +
307  "Failed to query Cable card OOB channel");
308  }
309  else
310  {
311  status_channel = QString(t_status.channel);
312  LOG(VB_RECORD, LOG_INFO, LOC +
313  QString("Cable card OOB channel is '%1'")
314  .arg(status_channel));
315  }
316 
317  if (status_channel == "none")
318  {
319  LOG(VB_RECORD, LOG_INFO, LOC + "Cable card is not present");
321  }
322  else
323  {
324  LOG(VB_RECORD, LOG_INFO, LOC + "Cable card is present");
326  }
327  }
328  else if (QString(model).toLower().endsWith("dvbt"))
329  {
331  }
332  else if (QString(model).toLower().endsWith("dvbc"))
333  {
335  }
336  else if (QString(model).toLower().endsWith("dvbtc"))
337  {
340  }
341  else
342  {
344  }
345 
346  return true;
347  }
348  return false;
349 }
350 
352 {
353  if (m_hdhomerun_device)
354  {
355  TuneChannel("none");
356  hdhomerun_device_tuner_lockkey_release(m_hdhomerun_device);
357  m_hdhomerun_device = nullptr;
358  }
359  if (m_device_selector)
360  {
361  hdhomerun_device_selector_destroy(m_device_selector, true);
362  m_device_selector = nullptr;
363  }
364 }
365 
367 {
368  m_device_selector = hdhomerun_device_selector_create(nullptr);
369  if (!m_device_selector)
370  {
371  LOG(VB_GENERAL, LOG_ERR, LOC + "Unable to create device selector");
372  return false;
373  }
374 
375  QStringList devices = m_device.split(",");
376  for (int i = 0; i < devices.size(); ++i)
377  {
378  QByteArray ba = devices[i].toUtf8();
379  int n = hdhomerun_device_selector_load_from_str(
380  m_device_selector, ba.data());
381  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Added %1 devices from %3")
382  .arg(n).arg(devices[i]));
383  }
384 
385  m_hdhomerun_device = hdhomerun_device_selector_choose_and_lock(
386  m_device_selector, nullptr);
387  if (!m_hdhomerun_device)
388  {
389  LOG(VB_GENERAL, LOG_ERR, LOC +
390  QString("Unable to find a free device"));
391  hdhomerun_device_selector_destroy(m_device_selector, true);
392  m_device_selector = nullptr;
393  return false;
394  }
395 
396  m_tuner = hdhomerun_device_get_tuner(m_hdhomerun_device);
397 
398  LOG(VB_GENERAL, LOG_INFO, LOC +
399  QString("Connected to device(%1)")
400  .arg(hdhomerun_device_get_name(m_hdhomerun_device)));
401 
402  return true;
403 }
404 
406  const QString &name, bool report_error_return, bool print_error) const
407 {
408  QMutexLocker locker(&m_hdhr_lock);
409 
410  if (!m_hdhomerun_device)
411  {
412  LOG(VB_GENERAL, LOG_ERR, LOC + "Get request failed (not connected)");
413  return QString();
414  }
415 
416  QString valname = QString("/tuner%1/%2").arg(m_tuner).arg(name);
417  char *value = nullptr;
418  char *error = nullptr;
419  if (hdhomerun_device_get_var(
420  m_hdhomerun_device, valname.toLocal8Bit().constData(),
421  &value, &error) < 0)
422  {
423  LOG(VB_GENERAL, LOG_ERR, LOC + "Get request failed" + ENO);
424  return QString();
425  }
426 
427  if (report_error_return && error)
428  {
429  if (print_error)
430  {
431  LOG(VB_GENERAL, LOG_ERR, LOC + QString("DeviceGet(%1): %2")
432  .arg(name).arg(error));
433  }
434 
435  return QString();
436  }
437 
438  return QString(value);
439 }
440 
442  const QString &name, const QString &val,
443  bool report_error_return, bool print_error)
444 {
445  QMutexLocker locker(&m_hdhr_lock);
446 
447  if (!m_hdhomerun_device)
448  {
449  LOG(VB_GENERAL, LOG_ERR, LOC + "Set request failed (not connected)");
450  return QString();
451  }
452 
453 
454  QString valname = QString("/tuner%1/%2").arg(m_tuner).arg(name);
455  char *value = nullptr;
456  char *error = nullptr;
457 
458  if (hdhomerun_device_set_var(
459  m_hdhomerun_device, valname.toLocal8Bit().constData(),
460  val.toLocal8Bit().constData(), &value, &error) < 0)
461  {
462  LOG(VB_GENERAL, LOG_ERR, LOC + "Set request failed" + ENO);
463 
464  return QString();
465  }
466 
467  if (report_error_return && error)
468  {
469  if (print_error)
470  {
471  LOG(VB_GENERAL, LOG_ERR, LOC + QString("DeviceSet(%1 %2): %3")
472  .arg(name).arg(val).arg(error));
473  }
474 
475  return QString();
476  }
477 
478  return QString(value);
479 }
480 
481 void HDHRStreamHandler::GetTunerStatus(struct hdhomerun_tuner_status_t *status)
482 {
483  hdhomerun_device_get_tuner_status(m_hdhomerun_device, nullptr, status);
484 }
485 
487 {
488  return (m_hdhomerun_device != nullptr);
489 }
490 
491 bool HDHRStreamHandler::TuneChannel(const QString &chanid)
492 {
494 
495  QString current = TunerGet("channel");
496  if (current == chanid)
497  {
498  LOG(VB_RECORD, LOG_INFO, LOC + QString("Not Re-Tuning channel %1")
499  .arg(chanid));
500  return true;
501  }
502 
503  LOG(VB_RECORD, LOG_INFO, LOC + QString("Tuning channel %1 (was %2)")
504  .arg(chanid).arg(current));
505  return !TunerSet("channel", chanid).isEmpty();
506 }
507 
509 {
512 
514  {
515  LOG(VB_GENERAL, LOG_ERR, LOC + "TuneProgram called in wrong tune mode");
516  return false;
517  }
518 
519  LOG(VB_RECORD, LOG_INFO, LOC + QString("Tuning program %1")
520  .arg(mpeg_prog_num));
521  return !TunerSet(
522  "program", QString::number(mpeg_prog_num), false).isEmpty();
523 }
524 
525 bool HDHRStreamHandler::TuneVChannel(const QString &vchn)
526 {
528 
529  QString current = TunerGet("vchannel");
530  if (current == vchn)
531  {
532  LOG(VB_RECORD, LOG_INFO, LOC + QString("Not Re-Tuning channel %1")
533  .arg(vchn));
534  return true;
535  }
536  LOG(VB_RECORD, LOG_INFO, LOC + QString("TuneVChannel(%1) from (%2)")
537  .arg(vchn).arg(current));
538 
539  LOG(VB_RECORD, LOG_INFO, LOC + QString("Tuning vchannel %1").arg(vchn));
540  return !TunerSet("vchannel", vchn).isEmpty();
541 }
542 
543 #ifdef NEED_HDHOMERUN_DEVICE_SELECTOR_LOAD_FROM_STR
544 
545 // Provide functions we need that are not included in some versions of
546 // libhdhomerun. These were taken
547 // from version 20180817 and modified as needed.
548 
549 struct hdhomerun_device_selector_t {
550  struct hdhomerun_device_t **hd_list;
551  size_t hd_count;
552  struct hdhomerun_debug_t *dbg;
553 };
554 
555 static int hdhomerun_device_selector_load_from_str_discover(struct hdhomerun_device_selector_t *hds, uint32_t target_ip, uint32_t device_id)
556 {
557  struct hdhomerun_discover_device_t result;
558  int discover_count = hdhomerun_discover_find_devices_custom(target_ip, HDHOMERUN_DEVICE_TYPE_TUNER, device_id, &result, 1);
559  if (discover_count != 1) {
560  return 0;
561  }
562 
563  int count = 0;
564  unsigned int tuner_index;
565  for (tuner_index = 0; tuner_index < result.tuner_count; tuner_index++) {
566  struct hdhomerun_device_t *hd = hdhomerun_device_create(result.device_id, result.ip_addr, tuner_index, hds->dbg);
567  if (!hd) {
568  continue;
569  }
570 
571  hdhomerun_device_selector_add_device(hds, hd);
572  count++;
573  }
574 
575  return count;
576 }
577 
578 static int hdhomerun_device_selector_load_from_str(struct hdhomerun_device_selector_t *hds, char *device_str)
579 {
580  /*
581  * IP address based device_str.
582  */
583  unsigned int a[4];
584  if (sscanf(device_str, "%u.%u.%u.%u", &a[0], &a[1], &a[2], &a[3]) == 4) {
585  uint32_t ip_addr = (uint32_t)((a[0] << 24) | (a[1] << 16) | (a[2] << 8) | (a[3] << 0));
586 
587  /*
588  * IP address + tuner number.
589  */
590  unsigned int tuner;
591  if (sscanf(device_str, "%u.%u.%u.%u-%u", &a[0], &a[1], &a[2], &a[3], &tuner) == 5) {
592  struct hdhomerun_device_t *hd = hdhomerun_device_create(HDHOMERUN_DEVICE_ID_WILDCARD, ip_addr, tuner, hds->dbg);
593  if (!hd) {
594  return 0;
595  }
596 
597  hdhomerun_device_selector_add_device(hds, hd);
598  return 1;
599  }
600 
601  /*
602  * IP address only - discover and add tuners.
603  */
604  return hdhomerun_device_selector_load_from_str_discover(hds, ip_addr, HDHOMERUN_DEVICE_ID_WILDCARD);
605  }
606 
607  /*
608  * Device ID based device_str.
609  */
610  char *end;
611  uint32_t device_id = (uint32_t)strtoul(device_str, &end, 16);
612  if ((end == device_str + 8) && hdhomerun_discover_validate_device_id(device_id)) {
613  /*
614  * IP address + tuner number.
615  */
616  if (*end == '-') {
617  unsigned int tuner = (unsigned int)strtoul(end + 1, NULL, 10);
618  struct hdhomerun_device_t *hd = hdhomerun_device_create(device_id, 0, tuner, hds->dbg);
619  if (!hd) {
620  return 0;
621  }
622 
623  hdhomerun_device_selector_add_device(hds, hd);
624  return 1;
625  }
626 
627  /*
628  * Device ID only - discover and add tuners.
629  */
630  return hdhomerun_device_selector_load_from_str_discover(hds, 0, device_id);
631  }
632 
633  return 0;
634 }
635 
636 #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
volatile bool m_running_desired
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)
hdhomerun_device_t * m_hdhomerun_device
unsigned int uint
Definition: compat.h:140
QMutex m_listener_lock
static QMap< int, uint > s_handlers_refcnt
void setObjectName(const QString &name)
Definition: mthread.cpp:249
QString TunerGet(const QString &name, bool report_error_return=true, bool print_error=true) const
vector< DTVTunerType > m_tuner_types
static void Return(HDHRStreamHandler *&ref, int inputid)
static QMutex s_handlers_lock
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
HDHRTuneMode m_tune_mode
static QString filt_str(uint pid)
PIDInfoMap m_pid_info
static VideoFilter * new_filter(VideoFrameType inpixfmt, VideoFrameType outpixfmt, const int *width, const int *height, const char *options, int threads)
const char * name
Definition: ParseText.cpp:328
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:99
bool IsConnected(void) const
bool TuneChannel(const QString &chanid)
void GetTunerStatus(struct hdhomerun_tuner_status_t *status)
bool UpdateFilters(void) override
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
StreamDataList m_stream_data_list
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)
void WriteMPTS(unsigned char *buffer, uint len)
Write out a copy of the raw MPTS.
bool TuneProgram(uint mpeg_prog_num)
static HDHRStreamHandler * Get(const QString &devname, int inputid, int majorid)
hdhomerun_device_selector_t * m_device_selector
static QMap< int, HDHRStreamHandler * > s_handlers
bool RemoveAllPIDFilters(void)
HDHRStreamHandler(const QString &, int inputid, int majorid)