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