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"
22
23#define LOC QString("HDHRSH[%1](%2): ").arg(m_inputId).arg(m_device)
24
25QMap<int,HDHRStreamHandler*> HDHRStreamHandler::s_handlers;
28
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
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
97HDHRStreamHandler::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 {
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 {
188 }
189
190 {
191 QMutexLocker locker(&m_hdhrLock);
192 hdhomerun_device_stream_stop(m_hdhomerunDevice);
193 }
194
195 if (VERBOSE_LEVEL_CHECK(VB_RECORD, LOG_INFO))
196 {
197 struct hdhomerun_video_sock_t* vs = nullptr;
198 struct hdhomerun_video_stats_t stats {};
199 vs = hdhomerun_device_get_video_sock(m_hdhomerunDevice);
200 if (vs)
201 {
202 hdhomerun_video_get_stats(vs, &stats);
203 LOG(VB_RECORD, LOG_INFO, LOC +
204 QString("stream stats: packet_count=%1 "
205 "network_errors=%2 "
206 "transport_errors=%3 "
207 "sequence_errors=%4 "
208 "overflow_errors=%5")
209 .arg(stats.packet_count)
210 .arg(stats.network_error_count)
211 .arg(stats.transport_error_count)
212 .arg(stats.sequence_error_count)
213 .arg(stats.overflow_error_count));
214 }
215 }
216
217 LOG(VB_RECORD, LOG_INFO, LOC + "RunTS(): " + "end");
218
219 SetRunning(false, false, false);
220
221 RunEpilog();
222}
223
224static QString filt_str(uint pid)
225{
226 uint pid0 = (pid / (16*16*16)) % 16;
227 uint pid1 = (pid / (16*16)) % 16;
228 uint pid2 = (pid / (16)) % 16;
229 uint pid3 = pid % 16;
230 return QString("0x%1%2%3%4")
231 .arg(pid0,0,16).arg(pid1,0,16)
232 .arg(pid2,0,16).arg(pid3,0,16);
233}
234
236{
239
241 {
242 LOG(VB_GENERAL, LOG_ERR, LOC +
243 QString("UpdateFilters called in wrong tune mode, %1")
244 .arg(m_tuneMode));
245 return false;
246 }
247
248#ifdef DEBUG_PID_FILTERS
249 LOG(VB_RECORD, LOG_DEBUG, LOC + "UpdateFilters()");
250#endif // DEBUG_PID_FILTERS
251 QMutexLocker locker(&m_pidLock);
252
253 if (!m_filtersChanged)
254 {
255 return true;
256 }
257
258 QString filter = "";
259
260 std::vector<uint> range_min;
261 std::vector<uint> range_max;
262
263 for (auto it = m_pidInfo.cbegin(); it != m_pidInfo.cend(); ++it)
264 {
265 range_min.push_back(it.key());
266 PIDInfoMap::const_iterator eit = it;
267 for (++eit;
268 (eit != m_pidInfo.cend()) && (it.key() + 1 == eit.key());
269 ++it, ++eit);
270 range_max.push_back(it.key());
271 }
272 if (range_min.size() > 16)
273 {
274 range_min.resize(16);
275 uint pid_max = range_max.back();
276 range_max.resize(15);
277 range_max.push_back(pid_max);
278 }
279
280 for (size_t i = 0; i < range_min.size(); i++)
281 {
282 filter += filt_str(range_min[i]);
283 if (range_min[i] != range_max[i])
284 filter += QString("-%1").arg(filt_str(range_max[i]));
285 filter += " ";
286 }
287
288 filter = filter.trimmed();
289
290 QString new_filter = TunerSet("filter", filter);
291
292#ifdef DEBUG_PID_FILTERS
293 QString msg = QString("Filter: '%1'").arg(filter);
294 if (filter != new_filter)
295 msg += QString("\n\t\t\t\t'%2'").arg(new_filter);
296
297 LOG(VB_RECORD, LOG_DEBUG, LOC + msg);
298#endif // DEBUG_PID_FILTERS
299
300 m_filtersChanged = false;
301
302 return filter == new_filter;
303}
304
306{
307 if (Connect())
308 {
309 const char *model = hdhomerun_device_get_model_str(m_hdhomerunDevice);
310 m_tunerTypes.clear();
311 if (QString(model).contains("cablecard", Qt::CaseInsensitive))
312 {
313 QString status_channel = "none";
314 hdhomerun_tuner_status_t t_status {};
315
316 if (hdhomerun_device_get_oob_status(
317 m_hdhomerunDevice, nullptr, &t_status) < 0)
318 {
319 LOG(VB_GENERAL, LOG_ERR, LOC +
320 "Failed to query Cable card OOB channel");
321 }
322 else
323 {
324 status_channel = QString(t_status.channel);
325 LOG(VB_RECORD, LOG_INFO, LOC +
326 QString("Cable card OOB channel is '%1'")
327 .arg(status_channel));
328 }
329
330 if (status_channel == "none")
331 {
332 LOG(VB_RECORD, LOG_INFO, LOC + "Cable card is not present");
334 }
335 else
336 {
337 LOG(VB_RECORD, LOG_INFO, LOC + "Cable card is present");
339 }
340 }
341 else if (QString(model).endsWith("dvbt", Qt::CaseInsensitive))
342 {
344 }
345 else if (QString(model).endsWith("dvbc", Qt::CaseInsensitive))
346 {
348 }
349 else if (QString(model).endsWith("dvbtc", Qt::CaseInsensitive))
350 {
353 }
354 else
355 {
357 }
358
359 return true;
360 }
361 return false;
362}
363
365{
367 {
368 TuneChannel("none");
369 hdhomerun_device_tuner_lockkey_release(m_hdhomerunDevice);
370 m_hdhomerunDevice = nullptr;
371 }
373 {
374 hdhomerun_device_selector_destroy(m_deviceSelector, true);
375 m_deviceSelector = nullptr;
376 }
377}
378
380{
381 m_deviceSelector = hdhomerun_device_selector_create(nullptr);
382 if (!m_deviceSelector)
383 {
384 LOG(VB_GENERAL, LOG_ERR, LOC + "Unable to create device selector");
385 return false;
386 }
387
388 QStringList devices = m_device.split(",");
389 for (const QString& device : std::as_const(devices))
390 {
391 QByteArray ba = device.toUtf8();
392 int n = hdhomerun_device_selector_load_from_str(
393 m_deviceSelector, ba.data());
394 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Added %1 devices from %3")
395 .arg(n).arg(device));
396 }
397
398 m_hdhomerunDevice = hdhomerun_device_selector_choose_and_lock(
399 m_deviceSelector, nullptr);
401 {
402 LOG(VB_GENERAL, LOG_ERR, LOC +
403 QString("Unable to find a free device"));
404 hdhomerun_device_selector_destroy(m_deviceSelector, true);
405 m_deviceSelector = nullptr;
406 return false;
407 }
408
409 m_tuner = hdhomerun_device_get_tuner(m_hdhomerunDevice);
410
411 LOG(VB_GENERAL, LOG_INFO, LOC +
412 QString("Connected to device(%1)")
413 .arg(hdhomerun_device_get_name(m_hdhomerunDevice)));
414
415 return true;
416}
417
418QString HDHRStreamHandler::TunerGet(const QString &name)
419{
420 QMutexLocker locker(&m_hdhrLock);
421
423 {
424 LOG(VB_GENERAL, LOG_ERR, LOC + "Get request failed (not connected)");
425 return {};
426 }
427
428 QString valname = QString("/tuner%1/%2").arg(m_tuner).arg(name);
429 char *value = nullptr;
430 char *error = nullptr;
431 if (hdhomerun_device_get_var(
432 m_hdhomerunDevice, valname.toLocal8Bit().constData(),
433 &value, &error) < 0)
434 {
435 LOG(VB_GENERAL, LOG_ERR, LOC +
436 QString("Get %1 request failed").arg(valname) + ENO);
437 return {};
438 }
439
440 if (error)
441 {
442 LOG(VB_GENERAL, LOG_ERR, LOC + QString("DeviceGet(%1): %2")
443 .arg(name, error));
444 return {};
445 }
446
447 return {value};
448}
449
450QString HDHRStreamHandler::TunerSet(const QString &name, const QString &val)
451{
452 QMutexLocker locker(&m_hdhrLock);
453
455 {
456 LOG(VB_GENERAL, LOG_ERR, LOC + "Set request failed (not connected)");
457 return {};
458 }
459
460 QString valname = QString("/tuner%1/%2").arg(m_tuner).arg(name);
461 char *value = nullptr;
462 char *error = nullptr;
463
464#if 0
465 LOG(VB_CHANSCAN, LOG_DEBUG, LOC + valname + " " + val);
466#endif
467
468 // Receive full transport stream when pid 0x2000 is present
469 QString val2 = val;
470 if (name.contains("filter") && val.contains("0x2000"))
471 {
472 val2 = "0x0000-0x1FFF";
473 LOG(VB_RECORD, LOG_INFO, LOC + valname + " fixup: \"" + val + "\" to \"" +val2 + "\"");
474 }
475
476 if (hdhomerun_device_set_var(
477 m_hdhomerunDevice, valname.toLocal8Bit().constData(),
478 val2.toLocal8Bit().constData(), &value, &error) < 0)
479 {
480 LOG(VB_GENERAL, LOG_ERR, LOC +
481 QString("Set %1 to '%2' request failed").arg(valname, val2) +
482 ENO);
483 return {};
484 }
485
486 if (error)
487 {
488 // Terminate recording when HDHomeRun lost connection
489 if (strstr(error, "ERROR: lock no longer held"))
490 m_bError = true;
491
492 LOG(VB_GENERAL, LOG_ERR, LOC + QString("DeviceSet(%1 %2): %3")
493 .arg(name, val2, error));
494 return {};
495 }
496
497 return {value};
498}
499
500void HDHRStreamHandler::GetTunerStatus(struct hdhomerun_tuner_status_t *status)
501{
502 QMutexLocker locker(&m_hdhrLock);
503
504 hdhomerun_device_get_tuner_status(m_hdhomerunDevice, nullptr, status);
505}
506
508{
509 return (m_hdhomerunDevice != nullptr);
510}
511
512bool HDHRStreamHandler::TuneChannel(const QString &chanid)
513{
515
516 QString current = TunerGet("channel");
517 if (current == chanid)
518 {
519 LOG(VB_RECORD, LOG_INFO, LOC + QString("Not Re-Tuning channel %1")
520 .arg(chanid));
521 return true;
522 }
523
524 LOG(VB_RECORD, LOG_INFO, LOC + QString("Tuning channel %1 (was %2)")
525 .arg(chanid, current));
526 return !TunerSet("channel", chanid).isEmpty();
527}
528
530{
533
535 {
536 LOG(VB_GENERAL, LOG_ERR, LOC +
537 QString("TuneProgram called in wrong tune mode, %1")
538 .arg(m_tuneMode));
539 return false;
540 }
541
542 LOG(VB_RECORD, LOG_INFO, LOC + QString("Tuning program %1")
543 .arg(mpeg_prog_num));
544 return !TunerSet(
545 "program", QString::number(mpeg_prog_num)).isEmpty();
546}
547
548bool HDHRStreamHandler::TuneVChannel(const QString &vchn)
549{
551
552 QString current = TunerGet("vchannel");
553 if (current == vchn)
554 {
555 LOG(VB_RECORD, LOG_INFO, LOC + QString("Not Re-Tuning channel %1")
556 .arg(vchn));
557 return true;
558 }
559 LOG(VB_RECORD, LOG_INFO, LOC + QString("TuneVChannel(%1) from (%2)")
560 .arg(vchn, current));
561
562 LOG(VB_RECORD, LOG_INFO, LOC + QString("Tuning vchannel %1").arg(vchn));
563 return !TunerSet("vchannel", vchn).isEmpty();
564}
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:196
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:209
void setObjectName(const QString &name)
Definition: mthread.cpp:238
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: freesurround.h:24
#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