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
15 
16 #include "cardutil.h"
17 #include "dtvsignalmonitor.h"
18 #include "hdhrchannel.h"
19 #include "hdhrstreamhandler.h"
20 #include "mpeg/mpegstreamdata.h"
21 #include "mpeg/streamlisteners.h"
22 
23 #define LOC QString("HDHRSH[%1](%2): ").arg(m_inputId).arg(m_device)
24 
25 QMap<int,HDHRStreamHandler*> HDHRStreamHandler::s_handlers;
28 
29 HDHRStreamHandler *HDHRStreamHandler::Get(const QString &devname,
30  int inputid, int majorid)
31 {
32  QMutexLocker locker(&s_handlersLock);
33 
34  QMap<int,HDHRStreamHandler*>::iterator it = s_handlers.find(majorid);
35 
36  if (it == s_handlers.end())
37  {
38  auto *newhandler = new HDHRStreamHandler(devname, inputid, majorid);
39  newhandler->Open();
40  s_handlers[majorid] = newhandler;
41  s_handlersRefCnt[majorid] = 1;
42 
43  LOG(VB_RECORD, LOG_INFO,
44  QString("HDHRSH[%1]: Creating new stream handler %2 for %3")
45  .arg(inputid).arg(majorid).arg(devname));
46  }
47  else
48  {
49  s_handlersRefCnt[majorid]++;
50  uint rcount = s_handlersRefCnt[majorid];
51  LOG(VB_RECORD, LOG_INFO,
52  QString("HDHRSH[%1]: Using existing stream handler %2 for %3")
53  .arg(inputid).arg(majorid)
54  .arg(devname) + QString(" (%1 in use)").arg(rcount));
55  }
56 
57  return s_handlers[majorid];
58 }
59 
60 void HDHRStreamHandler::Return(HDHRStreamHandler * & ref, int inputid)
61 {
62  QMutexLocker locker(&s_handlersLock);
63 
64  int majorid = ref->m_majorId;
65 
66  QMap<int,uint>::iterator rit = s_handlersRefCnt.find(majorid);
67  if (rit == s_handlersRefCnt.end())
68  return;
69 
70  QMap<int,HDHRStreamHandler*>::iterator it = s_handlers.find(majorid);
71  if (*rit > 1)
72  {
73  ref = nullptr;
74  (*rit)--;
75  return;
76  }
77 
78  if ((it != s_handlers.end()) && (*it == ref))
79  {
80  LOG(VB_RECORD, LOG_INFO, QString("HDHRSH[%1]: Closing handler for %2")
81  .arg(inputid).arg(majorid));
82  ref->Close();
83  delete *it;
84  s_handlers.erase(it);
85  }
86  else
87  {
88  LOG(VB_GENERAL, LOG_ERR,
89  QString("HDHRSH[%1] Error: Couldn't find handler for %2")
90  .arg(inputid).arg(majorid));
91  }
92 
93  s_handlersRefCnt.erase(rit);
94  ref = nullptr;
95 }
96 
97 HDHRStreamHandler::HDHRStreamHandler(const QString &device, int inputid,
98  int majorid)
99  : StreamHandler(device, inputid)
100  , m_majorId(majorid)
101 {
102  setObjectName("HDHRStreamHandler");
103 }
104 
109 {
110  RunProlog();
111 
112  {
113  QMutexLocker locker(&m_hdhrLock);
114 
115  /* Create TS socket. */
116  if (!hdhomerun_device_stream_start(m_hdhomerunDevice))
117  {
118  LOG(VB_GENERAL, LOG_ERR, LOC +
119  "Starting recording (set target failed). Aborting.");
120  m_bError = true;
121  RunEpilog();
122  return;
123  }
124  hdhomerun_device_stream_flush(m_hdhomerunDevice);
125  }
126 
127  SetRunning(true, false, false);
128 
129  LOG(VB_RECORD, LOG_INFO, LOC + "RunTS(): begin");
130 
131  int remainder = 0;
132  QElapsedTimer last_update;
133  while (m_runningDesired && !m_bError)
134  {
135  auto elapsed = !last_update.isValid()
136  ? -1ms : std::chrono::milliseconds(last_update.elapsed());
137  elapsed = (elapsed < 0ms) ? 1s : elapsed;
138  if (elapsed > 100ms)
139  {
142  UpdateFilters();
143  last_update.restart();
144  }
145 
146  size_t read_size = VIDEO_DATA_BUFFER_SIZE_1S / 8; // read up to 1/8s
147  read_size /= VIDEO_DATA_PACKET_SIZE;
148  read_size *= VIDEO_DATA_PACKET_SIZE;
149 
150  size_t data_length = 0;
151  unsigned char *data_buffer = hdhomerun_device_stream_recv(
152  m_hdhomerunDevice, read_size, &data_length);
153 
154  if (!data_buffer)
155  {
156  std::this_thread::sleep_for(20ms);
157  continue;
158  }
159 
160  // Assume data_length is a multiple of 188 (packet size)
161 
162  m_listenerLock.lock();
163 
164  if (m_streamDataList.empty())
165  {
166  m_listenerLock.unlock();
167  continue;
168  }
169 
170  for (auto sit = m_streamDataList.cbegin(); sit != m_streamDataList.cend(); ++sit)
171  remainder = sit.key()->ProcessData(data_buffer, data_length);
172 
173  WriteMPTS(data_buffer, data_length - remainder);
174 
175  m_listenerLock.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  {
188  QMutexLocker locker(&m_hdhrLock);
189  hdhomerun_device_stream_stop(m_hdhomerunDevice);
190  }
191 
192  if (VERBOSE_LEVEL_CHECK(VB_RECORD, LOG_INFO))
193  {
194  struct hdhomerun_video_sock_t* vs = nullptr;
195  struct hdhomerun_video_stats_t stats {};
196  vs = hdhomerun_device_get_video_sock(m_hdhomerunDevice);
197  if (vs)
198  {
199  hdhomerun_video_get_stats(vs, &stats);
200  LOG(VB_RECORD, LOG_INFO, LOC +
201  QString("stream stats: packet_count=%1 "
202  "network_errors=%2 "
203  "transport_errors=%3 "
204  "sequence_errors=%4 "
205  "overflow_errors=%5")
206  .arg(stats.packet_count)
207  .arg(stats.network_error_count)
208  .arg(stats.transport_error_count)
209  .arg(stats.sequence_error_count)
210  .arg(stats.overflow_error_count));
211  }
212  }
213 
214  LOG(VB_RECORD, LOG_INFO, LOC + "RunTS(): " + "end");
215 
216  SetRunning(false, false, false);
217 
218  RunEpilog();
219 }
220 
221 static QString filt_str(uint pid)
222 {
223  uint pid0 = (pid / (16*16*16)) % 16;
224  uint pid1 = (pid / (16*16)) % 16;
225  uint pid2 = (pid / (16)) % 16;
226  uint pid3 = pid % 16;
227  return QString("0x%1%2%3%4")
228  .arg(pid0,0,16).arg(pid1,0,16)
229  .arg(pid2,0,16).arg(pid3,0,16);
230 }
231 
233 {
236 
238  {
239  LOG(VB_GENERAL, LOG_ERR, LOC +
240  "UpdateFilters called in wrong tune mode");
241  return false;
242  }
243 
244 #ifdef DEBUG_PID_FILTERS
245  LOG(VB_RECORD, LOG_DEBUG, LOC + "UpdateFilters()");
246 #endif // DEBUG_PID_FILTERS
247  QMutexLocker locker(&m_pidLock);
248 
249  if (!m_filtersChanged)
250  {
251  return true;
252  }
253 
254  QString filter = "";
255 
256  std::vector<uint> range_min;
257  std::vector<uint> range_max;
258 
259  for (auto it = m_pidInfo.cbegin(); it != m_pidInfo.cend(); ++it)
260  {
261  range_min.push_back(it.key());
262  PIDInfoMap::const_iterator eit = it;
263  for (++eit;
264  (eit != m_pidInfo.cend()) && (it.key() + 1 == eit.key());
265  ++it, ++eit);
266  range_max.push_back(it.key());
267  }
268  if (range_min.size() > 16)
269  {
270  range_min.resize(16);
271  uint pid_max = range_max.back();
272  range_max.resize(15);
273  range_max.push_back(pid_max);
274  }
275 
276  for (size_t i = 0; i < range_min.size(); i++)
277  {
278  filter += filt_str(range_min[i]);
279  if (range_min[i] != range_max[i])
280  filter += QString("-%1").arg(filt_str(range_max[i]));
281  filter += " ";
282  }
283 
284  filter = filter.trimmed();
285 
286  QString new_filter = TunerSet("filter", filter);
287 
288 #ifdef DEBUG_PID_FILTERS
289  QString msg = QString("Filter: '%1'").arg(filter);
290  if (filter != new_filter)
291  msg += QString("\n\t\t\t\t'%2'").arg(new_filter);
292 
293  LOG(VB_RECORD, LOG_DEBUG, LOC + msg);
294 #endif // DEBUG_PID_FILTERS
295 
296  m_filtersChanged = false;
297 
298  return filter == new_filter;
299 }
300 
302 {
303  if (Connect())
304  {
305  const char *model = hdhomerun_device_get_model_str(m_hdhomerunDevice);
306  m_tunerTypes.clear();
307  if (QString(model).contains("cablecard", Qt::CaseInsensitive))
308  {
309  QString status_channel = "none";
310  hdhomerun_tuner_status_t t_status {};
311 
312  if (hdhomerun_device_get_oob_status(
313  m_hdhomerunDevice, nullptr, &t_status) < 0)
314  {
315  LOG(VB_GENERAL, LOG_ERR, LOC +
316  "Failed to query Cable card OOB channel");
317  }
318  else
319  {
320  status_channel = QString(t_status.channel);
321  LOG(VB_RECORD, LOG_INFO, LOC +
322  QString("Cable card OOB channel is '%1'")
323  .arg(status_channel));
324  }
325 
326  if (status_channel == "none")
327  {
328  LOG(VB_RECORD, LOG_INFO, LOC + "Cable card is not present");
330  }
331  else
332  {
333  LOG(VB_RECORD, LOG_INFO, LOC + "Cable card is present");
335  }
336  }
337  else if (QString(model).endsWith("dvbt", Qt::CaseInsensitive))
338  {
340  }
341  else if (QString(model).endsWith("dvbc", Qt::CaseInsensitive))
342  {
344  }
345  else if (QString(model).endsWith("dvbtc", Qt::CaseInsensitive))
346  {
349  }
350  else
351  {
353  }
354 
355  return true;
356  }
357  return false;
358 }
359 
361 {
362  if (m_hdhomerunDevice)
363  {
364  TuneChannel("none");
365  hdhomerun_device_tuner_lockkey_release(m_hdhomerunDevice);
366  m_hdhomerunDevice = nullptr;
367  }
368  if (m_deviceSelector)
369  {
370  hdhomerun_device_selector_destroy(m_deviceSelector, true);
371  m_deviceSelector = nullptr;
372  }
373 }
374 
376 {
377  m_deviceSelector = hdhomerun_device_selector_create(nullptr);
378  if (!m_deviceSelector)
379  {
380  LOG(VB_GENERAL, LOG_ERR, LOC + "Unable to create device selector");
381  return false;
382  }
383 
384  QStringList devices = m_device.split(",");
385  for (const QString& device : std::as_const(devices))
386  {
387  QByteArray ba = device.toUtf8();
388  int n = hdhomerun_device_selector_load_from_str(
389  m_deviceSelector, ba.data());
390  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Added %1 devices from %3")
391  .arg(n).arg(device));
392  }
393 
394  m_hdhomerunDevice = hdhomerun_device_selector_choose_and_lock(
395  m_deviceSelector, nullptr);
396  if (!m_hdhomerunDevice)
397  {
398  LOG(VB_GENERAL, LOG_ERR, LOC +
399  QString("Unable to find a free device"));
400  hdhomerun_device_selector_destroy(m_deviceSelector, true);
401  m_deviceSelector = nullptr;
402  return false;
403  }
404 
405  m_tuner = hdhomerun_device_get_tuner(m_hdhomerunDevice);
406 
407  LOG(VB_GENERAL, LOG_INFO, LOC +
408  QString("Connected to device(%1)")
409  .arg(hdhomerun_device_get_name(m_hdhomerunDevice)));
410 
411  return true;
412 }
413 
414 QString HDHRStreamHandler::TunerGet(const QString &name)
415 {
416  QMutexLocker locker(&m_hdhrLock);
417 
418  if (!m_hdhomerunDevice)
419  {
420  LOG(VB_GENERAL, LOG_ERR, LOC + "Get request failed (not connected)");
421  return {};
422  }
423 
424  QString valname = QString("/tuner%1/%2").arg(m_tuner).arg(name);
425  char *value = nullptr;
426  char *error = nullptr;
427  if (hdhomerun_device_get_var(
428  m_hdhomerunDevice, valname.toLocal8Bit().constData(),
429  &value, &error) < 0)
430  {
431  LOG(VB_GENERAL, LOG_ERR, LOC +
432  QString("Get %1 request failed").arg(valname) + ENO);
433  return {};
434  }
435 
436  if (error)
437  {
438  LOG(VB_GENERAL, LOG_ERR, LOC + QString("DeviceGet(%1): %2")
439  .arg(name, error));
440  return {};
441  }
442 
443  return {value};
444 }
445 
446 QString HDHRStreamHandler::TunerSet(const QString &name, const QString &val)
447 {
448  QMutexLocker locker(&m_hdhrLock);
449 
450  if (!m_hdhomerunDevice)
451  {
452  LOG(VB_GENERAL, LOG_ERR, LOC + "Set request failed (not connected)");
453  return {};
454  }
455 
456  QString valname = QString("/tuner%1/%2").arg(m_tuner).arg(name);
457  char *value = nullptr;
458  char *error = nullptr;
459 
460 #if 0
461  LOG(VB_CHANSCAN, LOG_DEBUG, LOC + valname + " " + val);
462 #endif
463 
464  // Receive full transport stream when pid 0x2000 is present
465  QString val2 = val;
466  if (name.contains("filter") && val.contains("0x2000"))
467  {
468  val2 = "0x0000-0x1FFF";
469  LOG(VB_RECORD, LOG_INFO, LOC + valname + " fixup: \"" + val + "\" to \"" +val2 + "\"");
470  }
471 
472  if (hdhomerun_device_set_var(
473  m_hdhomerunDevice, valname.toLocal8Bit().constData(),
474  val2.toLocal8Bit().constData(), &value, &error) < 0)
475  {
476  LOG(VB_GENERAL, LOG_ERR, LOC +
477  QString("Set %1 to '%2' request failed").arg(valname, val2) +
478  ENO);
479  return {};
480  }
481 
482  if (error)
483  {
484  // Terminate recording when HDHomeRun lost connection
485  if (strstr(error, "ERROR: lock no longer held"))
486  m_bError = true;
487 
488  LOG(VB_GENERAL, LOG_ERR, LOC + QString("DeviceSet(%1 %2): %3")
489  .arg(name, val2, error));
490  return {};
491  }
492 
493  return {value};
494 }
495 
496 void HDHRStreamHandler::GetTunerStatus(struct hdhomerun_tuner_status_t *status)
497 {
498  QMutexLocker locker(&m_hdhrLock);
499 
500  hdhomerun_device_get_tuner_status(m_hdhomerunDevice, nullptr, status);
501 }
502 
504 {
505  return (m_hdhomerunDevice != nullptr);
506 }
507 
508 bool HDHRStreamHandler::TuneChannel(const QString &chanid)
509 {
511 
512  QString current = TunerGet("channel");
513  if (current == chanid)
514  {
515  LOG(VB_RECORD, LOG_INFO, LOC + QString("Not Re-Tuning channel %1")
516  .arg(chanid));
517  return true;
518  }
519 
520  LOG(VB_RECORD, LOG_INFO, LOC + QString("Tuning channel %1 (was %2)")
521  .arg(chanid, current));
522  return !TunerSet("channel", chanid).isEmpty();
523 }
524 
526 {
529 
531  {
532  LOG(VB_GENERAL, LOG_ERR, LOC + "TuneProgram called in wrong tune mode");
533  return false;
534  }
535 
536  LOG(VB_RECORD, LOG_INFO, LOC + QString("Tuning program %1")
537  .arg(mpeg_prog_num));
538  return !TunerSet(
539  "program", QString::number(mpeg_prog_num)).isEmpty();
540 }
541 
542 bool HDHRStreamHandler::TuneVChannel(const QString &vchn)
543 {
545 
546  QString current = TunerGet("vchannel");
547  if (current == vchn)
548  {
549  LOG(VB_RECORD, LOG_INFO, LOC + QString("Not Re-Tuning channel %1")
550  .arg(vchn));
551  return true;
552  }
553  LOG(VB_RECORD, LOG_INFO, LOC + QString("TuneVChannel(%1) from (%2)")
554  .arg(vchn, current));
555 
556  LOG(VB_RECORD, LOG_INFO, LOC + QString("Tuning vchannel %1").arg(vchn));
557  return !TunerSet("vchannel", vchn).isEmpty();
558 }
ENO
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:73
HDHRStreamHandler::Close
void Close(void)
Definition: hdhrstreamhandler.cpp:360
DTVTunerType::kTunerTypeDVBC
static const int kTunerTypeDVBC
Definition: dtvconfparserhelpers.h:95
error
static void error(const char *str,...)
Definition: vbi.cpp:36
HDHRStreamHandler::run
void run(void) override
Reads HDHomeRun socket for tables & data.
Definition: hdhrstreamhandler.cpp:108
StreamHandler::RemoveAllPIDFilters
bool RemoveAllPIDFilters(void)
Definition: streamhandler.cpp:240
StreamHandler::SetRunning
void SetRunning(bool running, bool using_buffering, bool using_section_reader)
Definition: streamhandler.cpp:173
HDHRStreamHandler::TunerSet
QString TunerSet(const QString &name, const QString &value)
Definition: hdhrstreamhandler.cpp:446
StreamHandler::WriteMPTS
void WriteMPTS(const unsigned char *buffer, uint len)
Write out a copy of the raw MPTS.
Definition: streamhandler.cpp:355
HDHRStreamHandler::TunerGet
QString TunerGet(const QString &name)
Definition: hdhrstreamhandler.cpp:414
StreamHandler
Definition: streamhandler.h:56
hdhrTuneModeFrequency
@ hdhrTuneModeFrequency
Definition: hdhrstreamhandler.h:35
VERBOSE_LEVEL_CHECK
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
MThread::setObjectName
void setObjectName(const QString &name)
Definition: mthread.cpp:238
HDHRStreamHandler::m_tunerTypes
std::vector< DTVTunerType > m_tunerTypes
Definition: hdhrstreamhandler.h:90
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MThread::RunProlog
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:196
HDHRStreamHandler::s_handlersRefCnt
static QMap< int, uint > s_handlersRefCnt
Definition: hdhrstreamhandler.h:99
HDHRStreamHandler::m_deviceSelector
hdhomerun_device_selector_t * m_deviceSelector
Definition: hdhrstreamhandler.h:88
HDHRStreamHandler::Connect
bool Connect(void)
Definition: hdhrstreamhandler.cpp:375
LOC
#define LOC
Definition: hdhrstreamhandler.cpp:23
streamlisteners.h
HDHRStreamHandler::TuneVChannel
bool TuneVChannel(const QString &vchn)
Definition: hdhrstreamhandler.cpp:542
StreamHandler::m_filtersChanged
bool m_filtersChanged
Definition: streamhandler.h:135
MythDate::current
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:14
hdhrTuneModeFrequencyPid
@ hdhrTuneModeFrequencyPid
Definition: hdhrstreamhandler.h:36
hdhrTuneModeVChannel
@ hdhrTuneModeVChannel
Definition: hdhrstreamhandler.h:38
StreamHandler::m_listenerLock
QRecursiveMutex m_listenerLock
Definition: streamhandler.h:144
StreamHandler::m_pidInfo
PIDInfoMap m_pidInfo
Definition: streamhandler.h:133
mythlogging.h
HDHRStreamHandler::Return
static void Return(HDHRStreamHandler *&ref, int inputid)
Definition: hdhrstreamhandler.cpp:60
StreamHandler::m_pidLock
QRecursiveMutex m_pidLock
Definition: streamhandler.h:131
StreamHandler::UpdateFiltersFromStreamData
bool UpdateFiltersFromStreamData(void)
Definition: streamhandler.cpp:290
filt_str
static QString filt_str(uint pid)
Definition: hdhrstreamhandler.cpp:221
MThread::RunEpilog
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:209
HDHRStreamHandler::m_hdhomerunDevice
hdhomerun_device_t * m_hdhomerunDevice
Definition: hdhrstreamhandler.h:87
HDHRStreamHandler::IsConnected
bool IsConnected(void) const
Definition: hdhrstreamhandler.cpp:503
uint
unsigned int uint
Definition: compat.h:81
HDHRStreamHandler::m_tuneMode
HDHRTuneMode m_tuneMode
Definition: hdhrstreamhandler.h:91
mpegstreamdata.h
HDHRStreamHandler::HDHRStreamHandler
HDHRStreamHandler(const QString &device, int inputid, int majorid)
Definition: hdhrstreamhandler.cpp:97
hdhrTuneModeFrequencyProgram
@ hdhrTuneModeFrequencyProgram
Definition: hdhrstreamhandler.h:37
HDHRStreamHandler
Definition: hdhrstreamhandler.h:47
HDHRStreamHandler::m_tuner
int m_tuner
Definition: hdhrstreamhandler.h:89
HDHRStreamHandler::Open
bool Open(void)
Definition: hdhrstreamhandler.cpp:301
HDHRStreamHandler::TuneChannel
bool TuneChannel(const QString &chanid)
Definition: hdhrstreamhandler.cpp:508
cardutil.h
StreamHandler::m_runningDesired
volatile bool m_runningDesired
Definition: streamhandler.h:119
HDHRStreamHandler::m_hdhrLock
QRecursiveMutex m_hdhrLock
Definition: hdhrstreamhandler.h:94
HDHRStreamHandler::m_majorId
int m_majorId
Definition: hdhrstreamhandler.h:92
HDHRStreamHandler::GetTunerStatus
void GetTunerStatus(struct hdhomerun_tuner_status_t *status)
Definition: hdhrstreamhandler.cpp:496
HDHRStreamHandler::UpdateFilters
bool UpdateFilters(void) override
Definition: hdhrstreamhandler.cpp:232
DTVTunerType::kTunerTypeOCUR
static const int kTunerTypeOCUR
Definition: dtvconfparserhelpers.h:101
StreamHandler::m_streamDataList
StreamDataList m_streamDataList
Definition: streamhandler.h:145
StreamHandler::m_bError
volatile bool m_bError
Definition: streamhandler.h:124
DTVTunerType::kTunerTypeATSC
static const int kTunerTypeATSC
Definition: dtvconfparserhelpers.h:98
HDHRStreamHandler::TuneProgram
bool TuneProgram(uint mpeg_prog_num)
Definition: hdhrstreamhandler.cpp:525
HDHRStreamHandler::Get
static HDHRStreamHandler * Get(const QString &devname, int inputid, int majorid)
Definition: hdhrstreamhandler.cpp:29
hdhrstreamhandler.h
DTVTunerType::kTunerTypeDVBT
static const int kTunerTypeDVBT
Definition: dtvconfparserhelpers.h:96
HDHRStreamHandler::s_handlersLock
static QMutex s_handlersLock
Definition: hdhrstreamhandler.h:97
hdhrchannel.h
StreamHandler::m_device
QString m_device
Definition: streamhandler.h:111
dtvsignalmonitor.h
HDHRStreamHandler::s_handlers
static QMap< int, HDHRStreamHandler * > s_handlers
Definition: hdhrstreamhandler.h:98