MythTV  master
ExternalStreamHandler.cpp
Go to the documentation of this file.
1 // -*- Mode: c++ -*-
2 
3 // POSIX headers
4 #include <thread>
5 #include <iostream>
6 #include <fcntl.h>
7 #include <unistd.h>
8 #include <algorithm>
9 #if !defined( USING_MINGW ) && !defined( _MSC_VER )
10 #include <poll.h>
11 #include <sys/ioctl.h>
12 #endif
13 
14 #include <QtGlobal>
15 #ifdef Q_OS_ANDROID
16 #include <sys/wait.h>
17 #endif
18 
19 // Qt headers
20 #include <QString>
21 #include <QFile>
22 
23 // MythTV headers
24 #include "libmythbase/exitcodes.h"
25 
26 #include "ExternalChannel.h"
27 #include "ExternalStreamHandler.h"
28 //#include "ThreadedFileWriter.h"
29 #include "cardutil.h"
30 #include "dtvsignalmonitor.h"
31 #include "mpeg/mpegstreamdata.h"
32 #include "mpeg/streamlisteners.h"
33 
34 #define LOC QString("ExternSH[%1](%2): ").arg(m_inputId).arg(m_loc)
35 
36 ExternIO::ExternIO(const QString & app,
37  const QStringList & args)
38  : m_app(QFileInfo(app)),
39  m_status(&m_statusBuf, QIODevice::ReadWrite)
40 {
41  if (!m_app.exists())
42  {
43  m_error = QString("ExternIO: '%1' does not exist.").arg(app);
44  return;
45  }
46  if (!m_app.isReadable() || !m_app.isFile())
47  {
48  m_error = QString("ExternIO: '%1' is not readable.")
49  .arg(m_app.canonicalFilePath());
50  return;
51  }
52  if (!m_app.isExecutable())
53  {
54  m_error = QString("ExternIO: '%1' is not executable.")
55  .arg(m_app.canonicalFilePath());
56  return;
57  }
58 
59  m_args = args;
60  m_args.prepend(m_app.baseName());
61 
62  m_status.setString(&m_statusBuf);
63 }
64 
66 {
67  close(m_appIn);
68  close(m_appOut);
69  close(m_appErr);
70 
71  // waitpid(m_pid, &status, 0);
72  delete[] m_buffer;
73 }
74 
75 bool ExternIO::Ready(int fd, std::chrono::milliseconds timeout, const QString & what)
76 {
77 #if !defined( USING_MINGW ) && !defined( _MSC_VER )
78  std::array<struct pollfd,2> m_poll {};
79 
80  m_poll[0].fd = fd;
81  m_poll[0].events = POLLIN | POLLPRI;
82  int ret = poll(m_poll.data(), 1, timeout.count());
83 
84  if (m_poll[0].revents & POLLHUP)
85  {
86  m_error = what + " poll eof (POLLHUP)";
87  return false;
88  }
89  if (m_poll[0].revents & POLLNVAL)
90  {
91  LOG(VB_GENERAL, LOG_ERR, "poll error");
92  return false;
93  }
94  if (m_poll[0].revents & POLLIN)
95  {
96  if (ret > 0)
97  return true;
98 
99  if ((EOVERFLOW == errno))
100  m_error = "poll overflow";
101  return false;
102  }
103 #else
104  Q_UNUSED(fd);
105  Q_UNUSED(timeout);
106  Q_UNUSED(what);
107 #endif // !defined( USING_MINGW ) && !defined( _MSC_VER )
108  return false;
109 }
110 
111 int ExternIO::Read(QByteArray & buffer, int maxlen, std::chrono::milliseconds timeout)
112 {
113  if (Error())
114  {
115  LOG(VB_RECORD, LOG_ERR,
116  QString("ExternIO::Read: already in error state: '%1'")
117  .arg(m_error));
118  return 0;
119  }
120 
121  if (!Ready(m_appOut, timeout, "data"))
122  return 0;
123 
124  if (m_bufSize < maxlen)
125  {
126  m_bufSize = maxlen;
127  delete [] m_buffer;
128  m_buffer = new char[m_bufSize];
129  }
130 
131  int len = read(m_appOut, m_buffer, maxlen);
132 
133  if (len < 0)
134  {
135  if (errno == EAGAIN)
136  {
137  if (++m_errCnt > kMaxErrorCnt)
138  {
139  m_error = "Failed to read from External Recorder: " + ENO;
140  LOG(VB_RECORD, LOG_WARNING,
141  "External Recorder not ready. Giving up.");
142  }
143  else
144  {
145  LOG(VB_RECORD, LOG_WARNING,
146  QString("External Recorder not ready. Will retry (%1/%2).")
147  .arg(m_errCnt).arg(kMaxErrorCnt));
148  std::this_thread::sleep_for(100ms);
149  }
150  }
151  else
152  {
153  m_error = "Failed to read from External Recorder: " + ENO;
154  LOG(VB_RECORD, LOG_ERR, m_error);
155  }
156  }
157  else
158  m_errCnt = 0;
159 
160  if (len == 0)
161  return 0;
162 
163  buffer.append(m_buffer, len);
164 
165  LOG(VB_RECORD, LOG_DEBUG,
166  QString("ExternIO::Read '%1' bytes, buffer size %2")
167  .arg(len).arg(buffer.size()));
168 
169  return len;
170 }
171 
172 QString ExternIO::GetStatus(std::chrono::milliseconds timeout)
173 {
174  if (Error())
175  {
176  LOG(VB_RECORD, LOG_ERR,
177  QString("ExternIO::GetStatus: already in error state: '%1'")
178  .arg(m_error));
179  return QByteArray();
180  }
181 
182  std::chrono::milliseconds waitfor = m_status.atEnd() ? timeout : 0ms;
183  if (Ready(m_appErr, waitfor, "status"))
184  {
185  std::array<char,2048> buffer {};
186  int len = read(m_appErr, buffer.data(), buffer.size());
187  m_status << QString::fromLatin1(buffer.data(), len);
188  }
189 
190  if (m_status.atEnd())
191  return QByteArray();
192 
193  QString msg = m_status.readLine();
194 
195  LOG(VB_RECORD, LOG_DEBUG, QString("ExternIO::GetStatus '%1'")
196  .arg(msg));
197 
198  return msg;
199 }
200 
201 int ExternIO::Write(const QByteArray & buffer)
202 {
203  if (Error())
204  {
205  LOG(VB_RECORD, LOG_ERR,
206  QString("ExternIO::Write: already in error state: '%1'")
207  .arg(m_error));
208  return -1;
209  }
210 
211  LOG(VB_RECORD, LOG_DEBUG, QString("ExternIO::Write('%1')")
212  .arg(QString(buffer).simplified()));
213 
214  int len = write(m_appIn, buffer.constData(), buffer.size());
215  if (len != buffer.size())
216  {
217  if (len > 0)
218  {
219  LOG(VB_RECORD, LOG_WARNING,
220  QString("ExternIO::Write: only wrote %1 of %2 bytes '%3'")
221  .arg(len).arg(buffer.size()).arg(QString(buffer)));
222  }
223  else
224  {
225  m_error = QString("ExternIO: Failed to write '%1' to app's stdin: ")
226  .arg(QString(buffer)) + ENO;
227  return -1;
228  }
229  }
230 
231  return len;
232 }
233 
234 bool ExternIO::Run(void)
235 {
236  LOG(VB_RECORD, LOG_INFO, QString("ExternIO::Run()"));
237 
238  Fork();
239  GetStatus(10ms);
240 
241  return true;
242 }
243 
244 /* Return true if the process is not, or is no longer running */
245 bool ExternIO::KillIfRunning(const QString & cmd)
246 {
247 #if defined(Q_OS_DARWIN) || defined(__FreeBSD__) || defined(__OpenBSD__)
248  Q_UNUSED(cmd);
249  return false;
250 #elif defined USING_MINGW
251  Q_UNUSED(cmd);
252  return false;
253 #elif defined( _MSC_VER )
254  Q_UNUSED(cmd);
255  return false;
256 #else
257  QString grp = QString("pgrep -x -f -- \"%1\" 2>&1 > /dev/null").arg(cmd);
258  QString kil = QString("pkill --signal 15 -x -f -- \"%1\" 2>&1 > /dev/null")
259  .arg(cmd);
260 
261  int res_grp = system(grp.toUtf8().constData());
262  if (WEXITSTATUS(res_grp) == 1)
263  {
264  LOG(VB_RECORD, LOG_DEBUG, QString("'%1' not running.").arg(cmd));
265  return true;
266  }
267 
268  LOG(VB_RECORD, LOG_WARNING, QString("'%1' already running, killing...")
269  .arg(cmd));
270  int res_kil = system(kil.toUtf8().constData());
271  if (WEXITSTATUS(res_kil) == 1)
272  LOG(VB_GENERAL, LOG_WARNING, QString("'%1' failed: %2")
273  .arg(kil, ENO));
274 
275  res_grp = system(grp.toUtf8().constData());
276  if (WEXITSTATUS(res_grp) == 1)
277  {
278  LOG(WEXITSTATUS(res_kil) == 0 ? VB_RECORD : VB_GENERAL, LOG_WARNING,
279  QString("'%1' terminated.").arg(cmd));
280  return true;
281  }
282 
283  std::this_thread::sleep_for(50ms);
284 
285  kil = QString("pkill --signal 9 -x -f \"%1\" 2>&1 > /dev/null").arg(cmd);
286  res_kil = system(kil.toUtf8().constData());
287  if (WEXITSTATUS(res_kil) > 0)
288  LOG(VB_GENERAL, LOG_WARNING, QString("'%1' failed: %2")
289  .arg(kil, ENO));
290 
291  res_grp = system(grp.toUtf8().constData());
292  LOG(WEXITSTATUS(res_kil) == 0 ? VB_RECORD : VB_GENERAL, LOG_WARNING,
293  QString("'%1' %2.")
294  .arg(cmd, WEXITSTATUS(res_grp) == 0 ? "sill running" : "terminated"));
295 
296  return (WEXITSTATUS(res_grp) != 0);
297 #endif
298 }
299 
300 void ExternIO::Fork(void)
301 {
302 #if !defined( USING_MINGW ) && !defined( _MSC_VER )
303  if (Error())
304  {
305  LOG(VB_RECORD, LOG_INFO, QString("ExternIO in bad state: '%1'")
306  .arg(m_error));
307  return;
308  }
309 
310  QString full_command = QString("%1").arg(m_args.join(" "));
311 
312  if (!KillIfRunning(full_command))
313  {
314  // Give it one more chance.
315  std::this_thread::sleep_for(50ms);
316  if (!KillIfRunning(full_command))
317  {
318  m_error = QString("Unable to kill existing '%1'.")
319  .arg(full_command);
320  LOG(VB_GENERAL, LOG_ERR, m_error);
321  return;
322  }
323  }
324 
325 
326  LOG(VB_RECORD, LOG_INFO, QString("ExternIO::Fork '%1'").arg(full_command));
327 
328  std::array<int,2> in = {-1, -1};
329  std::array<int,2> out = {-1, -1};
330  std::array<int,2> err = {-1, -1};
331 
332  if (pipe(in.data()) < 0)
333  {
334  m_error = "pipe(in) failed: " + ENO;
335  return;
336  }
337  if (pipe(out.data()) < 0)
338  {
339  m_error = "pipe(out) failed: " + ENO;
340  close(in[0]);
341  close(in[1]);
342  return;
343  }
344  if (pipe(err.data()) < 0)
345  {
346  m_error = "pipe(err) failed: " + ENO;
347  close(in[0]);
348  close(in[1]);
349  close(out[0]);
350  close(out[1]);
351  return;
352  }
353 
354  m_pid = fork();
355  if (m_pid < 0)
356  {
357  // Failed
358  m_error = "fork() failed: " + ENO;
359  return;
360  }
361  if (m_pid > 0)
362  {
363  // Parent
364  close(in[0]);
365  close(out[1]);
366  close(err[1]);
367  m_appIn = in[1];
368  m_appOut = out[0];
369  m_appErr = err[0];
370 
371  bool error = false;
372  error = (fcntl(m_appIn, F_SETFL, O_NONBLOCK) == -1);
373  error |= (fcntl(m_appOut, F_SETFL, O_NONBLOCK) == -1);
374  error |= (fcntl(m_appErr, F_SETFL, O_NONBLOCK) == -1);
375 
376  if (error)
377  {
378  LOG(VB_GENERAL, LOG_WARNING,
379  "ExternIO::Fork(): Failed to set O_NONBLOCK for FD: " + ENO);
380  std::this_thread::sleep_for(2s);
382  }
383 
384  LOG(VB_RECORD, LOG_INFO, "Spawned");
385  return;
386  }
387 
388  // Child
389  close(in[1]);
390  close(out[0]);
391  close(err[0]);
392  if (dup2( in[0], 0) < 0)
393  {
394  std::cerr << "dup2(stdin) failed: " << strerror(errno);
396  }
397  else if (dup2(out[1], 1) < 0)
398  {
399  std::cerr << "dup2(stdout) failed: " << strerror(errno);
401  }
402  else if (dup2(err[1], 2) < 0)
403  {
404  std::cerr << "dup2(stderr) failed: " << strerror(errno);
406  }
407 
408  /* Close all open file descriptors except stdin/stdout/stderr */
409  for (int i = sysconf(_SC_OPEN_MAX) - 1; i > 2; --i)
410  close(i);
411 
412  /* Set the process group id to be the same as the pid of this
413  * child process. This ensures that any subprocesses launched by this
414  * process can be killed along with the process itself. */
415  if (setpgid(0,0) < 0)
416  {
417  std::cerr << "ExternIO: "
418  << "setpgid() failed: "
419  << strerror(errno) << std::endl;
420  }
421 
422  /* run command */
423  char *command = strdup(m_app.canonicalFilePath()
424  .toUtf8().constData());
425  // Copy QStringList to char**
426  char **arguments = new char*[m_args.size() + 1];
427  for (int i = 0; i < m_args.size(); ++i)
428  {
429  int len = m_args[i].size() + 1;
430  arguments[i] = new char[len];
431  memcpy(arguments[i], m_args[i].toStdString().c_str(), len);
432  }
433  arguments[m_args.size()] = nullptr;
434 
435  if (execv(command, arguments) < 0)
436  {
437  // Can't use LOG due to locking fun.
438  std::cerr << "ExternIO: "
439  << "execv() failed: "
440  << strerror(errno) << std::endl;
441  }
442  else
443  {
444  std::cerr << "ExternIO: "
445  << "execv() should not be here?: "
446  << strerror(errno) << std::endl;
447  }
448 
449 #endif // !defined( USING_MINGW ) && !defined( _MSC_VER )
450 
451  /* Failed to exec */
452  _exit(GENERIC_EXIT_DAEMONIZING_ERROR); // this exit is ok
453 }
454 
455 
456 QMap<int, ExternalStreamHandler*> ExternalStreamHandler::s_handlers;
459 
461  int inputid, int majorid)
462 {
463  QMutexLocker locker(&s_handlersLock);
464 
465  QMap<int, ExternalStreamHandler*>::iterator it = s_handlers.find(majorid);
466 
467  if (it == s_handlers.end())
468  {
469  auto *newhandler = new ExternalStreamHandler(devname, inputid, majorid);
470  s_handlers[majorid] = newhandler;
471  s_handlersRefCnt[majorid] = 1;
472 
473  LOG(VB_RECORD, LOG_INFO,
474  QString("ExternSH[%1:%2]: Creating new stream handler for %3 "
475  "(1 in use)")
476  .arg(inputid).arg(majorid).arg(devname));
477  }
478  else
479  {
480  ++s_handlersRefCnt[majorid];
481  uint rcount = s_handlersRefCnt[majorid];
482  LOG(VB_RECORD, LOG_INFO,
483  QString("ExternSH[%1:%2]: Using existing stream handler for %3")
484  .arg(inputid).arg(majorid).arg(devname) +
485  QString(" (%1 in use)").arg(rcount));
486  }
487 
488  return s_handlers[majorid];
489 }
490 
492  int inputid)
493 {
494  QMutexLocker locker(&s_handlersLock);
495 
496  int majorid = ref->m_majorId;
497 
498  QMap<int, uint>::iterator rit = s_handlersRefCnt.find(majorid);
499  if (rit == s_handlersRefCnt.end())
500  return;
501 
502  QMap<int, ExternalStreamHandler*>::iterator it =
503  s_handlers.find(majorid);
504 
505  if (*rit > 1)
506  {
507  ref = nullptr;
508  --(*rit);
509 
510  LOG(VB_RECORD, LOG_INFO,
511  QString("ExternSH[%1:%2]: Return handler (%3 still in use)")
512  .arg(inputid).arg(majorid).arg(*rit));
513 
514  return;
515  }
516 
517  if ((it != s_handlers.end()) && (*it == ref))
518  {
519  LOG(VB_RECORD, LOG_INFO,
520  QString("ExternSH[%1:%2]: Closing handler (0 in use)")
521  .arg(inputid).arg(majorid));
522  delete *it;
523  s_handlers.erase(it);
524  }
525  else
526  {
527  LOG(VB_GENERAL, LOG_ERR,
528  QString("ExternSH[%1:%2]: Error: No handler to return!")
529  .arg(inputid).arg(majorid));
530  }
531 
532  s_handlersRefCnt.erase(rit);
533  ref = nullptr;
534 }
535 
536 /*
537  ExternalStreamHandler
538  */
539 
541  int inputid,
542  int majorid)
543  : StreamHandler(path, inputid)
544  , m_loc(m_device)
545  , m_majorId(majorid)
546 {
547  setObjectName("ExternSH");
548 
549 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
550  m_args = path.split(' ',QString::SkipEmptyParts) +
551  logPropagateArgs.split(' ', QString::SkipEmptyParts);
552 #else
553  m_args = path.split(' ',Qt::SkipEmptyParts) +
554  logPropagateArgs.split(' ', Qt::SkipEmptyParts);
555 #endif
556  m_app = m_args.first();
557  m_args.removeFirst();
558 
559  // Pass one (and only one) 'quiet'
560  if (!m_args.contains("--quiet") && !m_args.contains("-q"))
561  m_args << "--quiet";
562 
563  m_args << "--inputid" << QString::number(majorid);
564  LOG(VB_RECORD, LOG_INFO, LOC + QString("args \"%1\"")
565  .arg(m_args.join(" ")));
566 
567  if (!OpenApp())
568  {
569  LOG(VB_GENERAL, LOG_ERR, LOC +
570  QString("Failed to start %1").arg(m_device));
571  }
572 }
573 
575 {
576  return m_streamingCnt.loadAcquire();
577 }
578 
580 {
581  QString result;
582  QString ready_cmd;
583  QByteArray buffer;
584  int sz = 0;
585  uint len = 0;
586  uint read_len = 0;
587  uint restart_cnt = 0;
588  MythTimer status_timer;
589  MythTimer nodata_timer;
590 
591  bool good_data = false;
592  uint data_proc_err = 0;
593  uint data_short_err = 0;
594 
595  if (!m_io)
596  {
597  LOG(VB_GENERAL, LOG_ERR, LOC +
598  QString("%1 is not running.").arg(m_device));
599  }
600 
601  status_timer.start();
602 
603  RunProlog();
604 
605  LOG(VB_RECORD, LOG_INFO, LOC + "run(): begin");
606 
607  SetRunning(true, true, false);
608 
609  if (m_pollMode)
610  ready_cmd = "SendBytes";
611  else
612  ready_cmd = "XON";
613 
614  uint remainder = 0;
615  while (m_runningDesired && !m_bError)
616  {
617  if (!IsTSOpen())
618  {
619  LOG(VB_RECORD, LOG_WARNING, LOC + "TS not open yet.");
620  std::this_thread::sleep_for(10ms);
621  continue;
622  }
623 
624  if (StreamingCount() == 0)
625  {
626  std::this_thread::sleep_for(10ms);
627  continue;
628  }
629 
631 
632  if (!m_xon || m_pollMode)
633  {
634  if (buffer.size() > TOO_FAST_SIZE)
635  {
636  LOG(VB_RECORD, LOG_WARNING, LOC +
637  "Internal buffer too full to accept more data from "
638  "external application.");
639  }
640  else
641  {
642  if (!ProcessCommand(ready_cmd, result))
643  {
644  if (result.startsWith("ERR"))
645  {
646  LOG(VB_GENERAL, LOG_ERR, LOC +
647  QString("Aborting: %1 -> %2")
648  .arg(ready_cmd, result));
649  m_bError = true;
650  continue;
651  }
652 
653  if (restart_cnt++)
654  std::this_thread::sleep_for(20s);
655  if (!RestartStream())
656  {
657  LOG(VB_RECORD, LOG_ERR, LOC +
658  "Failed to restart stream.");
659  m_bError = true;
660  }
661  continue;
662  }
663  m_xon = true;
664  }
665  }
666 
667  if (m_xon)
668  {
669  if (status_timer.elapsed() >= 2s)
670  {
671  // Since we may never need to send the XOFF
672  // command, occationally check to see if the
673  // External recorder needs to report an issue.
674  if (CheckForError())
675  {
676  if (restart_cnt++)
677  std::this_thread::sleep_for(20s);
678  if (!RestartStream())
679  {
680  LOG(VB_RECORD, LOG_ERR, LOC + "Failed to restart stream.");
681  m_bError = true;
682  }
683  continue;
684  }
685 
686  status_timer.restart();
687  }
688 
689  if (buffer.size() > TOO_FAST_SIZE)
690  {
691  if (!m_pollMode)
692  {
693  // Data is comming a little too fast, so XOFF
694  // to give us time to process it.
695  if (!ProcessCommand(QString("XOFF"), result))
696  {
697  if (result.startsWith("ERR"))
698  {
699  LOG(VB_GENERAL, LOG_ERR, LOC +
700  QString("Aborting: XOFF -> %2")
701  .arg(result));
702  m_bError = true;
703  }
704  }
705  m_xon = false;
706  }
707  }
708 
709  if (m_io && (sz = PACKET_SIZE - remainder) > 0)
710  read_len = m_io->Read(buffer, sz, 100ms);
711  else
712  read_len = 0;
713  }
714  else
715  read_len = 0;
716 
717  if (read_len == 0)
718  {
719  if (!nodata_timer.isRunning())
720  nodata_timer.start();
721  else
722  {
723  if (nodata_timer.elapsed() >= 50s)
724  {
725  LOG(VB_GENERAL, LOG_WARNING, LOC +
726  "No data for 50 seconds, Restarting stream.");
727  if (!RestartStream())
728  {
729  LOG(VB_RECORD, LOG_ERR, LOC +
730  "Failed to restart stream.");
731  m_bError = true;
732  }
733  nodata_timer.stop();
734  continue;
735  }
736  }
737 
738  std::this_thread::sleep_for(50ms);
739 
740  // HLS type streams may only produce data every ~10 seconds
741  if (nodata_timer.elapsed() < 12s && buffer.size() < TS_PACKET_SIZE)
742  continue;
743  }
744  else
745  {
746  nodata_timer.stop();
747  restart_cnt = 0;
748  }
749 
750  if (m_io == nullptr)
751  {
752  LOG(VB_GENERAL, LOG_ERR, LOC + "I/O thread has disappeared!");
753  m_bError = true;
754  break;
755  }
756  if (m_io->Error())
757  {
758  LOG(VB_GENERAL, LOG_ERR, LOC +
759  QString("Fatal Error from External Recorder: %1")
760  .arg(m_io->ErrorString()));
761  CloseApp();
762  m_bError = true;
763  break;
764  }
765 
766  len = remainder = buffer.size();
767 
768  if (len == 0)
769  continue;
770 
771  if (len < TS_PACKET_SIZE)
772  {
773  if (m_xon && data_short_err++ == 0)
774  LOG(VB_RECORD, LOG_INFO, LOC + "Waiting for a full TS packet.");
775  std::this_thread::sleep_for(50us);
776  continue;
777  }
778  if (data_short_err)
779  {
780  if (data_short_err > 1)
781  {
782  LOG(VB_RECORD, LOG_INFO, LOC +
783  QString("Waited for a full TS packet %1 times.")
784  .arg(data_short_err));
785  }
786  data_short_err = 0;
787  }
788 
789  if (!m_streamLock.tryLock())
790  continue;
791 
792  if (!m_listenerLock.tryLock())
793  continue;
794 
795  for (auto sit = m_streamDataList.cbegin();
796  sit != m_streamDataList.cend(); ++sit)
797  {
798  remainder = sit.key()->ProcessData
799  (reinterpret_cast<const uint8_t *>
800  (buffer.constData()), buffer.size());
801  }
802 
803  m_listenerLock.unlock();
804 
805  if (m_replay)
806  {
807  m_replayBuffer += buffer.left(len - remainder);
808  if (m_replayBuffer.size() > (50 * PACKET_SIZE))
809  {
810  m_replayBuffer.remove(0, len - remainder);
811  LOG(VB_RECORD, LOG_WARNING, LOC +
812  QString("Replay size truncated to %1 bytes")
813  .arg(m_replayBuffer.size()));
814  }
815  }
816 
817  m_streamLock.unlock();
818 
819  if (remainder == 0)
820  {
821  buffer.clear();
822  good_data = (len != 0U);
823  }
824  else if (len > remainder) // leftover bytes
825  {
826  buffer.remove(0, len - remainder);
827  good_data = (len != 0U);
828  }
829  else if (len == remainder)
830  good_data = false;
831 
832  if (good_data)
833  {
834  if (data_proc_err)
835  {
836  if (data_proc_err > 1)
837  {
838  LOG(VB_RECORD, LOG_WARNING, LOC +
839  QString("Failed to process the data received %1 times.")
840  .arg(data_proc_err));
841  }
842  data_proc_err = 0;
843  }
844  }
845  else
846  {
847  if (data_proc_err++ == 0)
848  {
849  LOG(VB_RECORD, LOG_WARNING, LOC +
850  "Failed to process the data received");
851  }
852  }
853  }
854 
855  LOG(VB_RECORD, LOG_INFO, LOC + "run(): " +
856  QString("%1 shutdown").arg(m_bError ? "Error" : "Normal"));
857 
859  SetRunning(false, true, false);
860 
861  LOG(VB_RECORD, LOG_INFO, LOC + "run(): " + "end");
862 
863  RunEpilog();
864 }
865 
867 {
868  QString result;
869 
870  if (ProcessCommand("APIVersion?", result, 10s))
871  {
872 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
873  QStringList tokens = result.split(':', QString::SkipEmptyParts);
874 #else
875  QStringList tokens = result.split(':', Qt::SkipEmptyParts);
876 #endif
877 
878  if (tokens.size() > 1)
879  m_apiVersion = tokens[1].toUInt();
880  m_apiVersion = std::min(m_apiVersion, static_cast<int>(MAX_API_VERSION));
881  if (m_apiVersion < 1)
882  {
883  LOG(VB_RECORD, LOG_ERR, LOC +
884  QString("Bad response to 'APIVersion?' - '%1'. "
885  "Expecting 1 or 2").arg(result));
886  m_apiVersion = 1;
887  }
888 
889  ProcessCommand(QString("APIVersion:%1").arg(m_apiVersion), result);
890  return true;
891  }
892 
893  return false;
894 }
895 
897 {
898  if (m_apiVersion > 1)
899  {
900  QString result;
901 
902  if (ProcessCommand("Description?", result))
903  m_loc = result.mid(3);
904  else
905  m_loc = m_device;
906  }
907 
908  return m_loc;
909 }
910 
912 {
913  {
914  QMutexLocker locker(&m_ioLock);
915 
916  if (m_io)
917  {
918  LOG(VB_RECORD, LOG_WARNING, LOC + "OpenApp: already open!");
919  return true;
920  }
921 
922  m_io = new ExternIO(m_app, m_args);
923 
924  if (m_io == nullptr)
925  {
926  LOG(VB_GENERAL, LOG_ERR, LOC + "ExternIO failed: " + ENO);
927  m_bError = true;
928  }
929  else
930  {
931  LOG(VB_RECORD, LOG_INFO, LOC + QString("Spawn '%1'").arg(m_device));
932  m_io->Run();
933  if (m_io->Error())
934  {
935  LOG(VB_GENERAL, LOG_ERR,
936  "Failed to start External Recorder: " + m_io->ErrorString());
937  delete m_io;
938  m_io = nullptr;
939  m_bError = true;
940  return false;
941  }
942  }
943  }
944 
945  QString result;
946 
947  if (!SetAPIVersion())
948  {
949  // Try again using API version 2
950  m_apiVersion = 2;
951  if (!SetAPIVersion())
952  m_apiVersion = 1;
953  }
954 
955  if (!IsAppOpen())
956  {
957  LOG(VB_RECORD, LOG_ERR, LOC + "Application is not responding.");
958  m_bError = true;
959  return false;
960  }
961 
963 
964  // Gather capabilities
965  if (!ProcessCommand("HasTuner?", result))
966  {
967  LOG(VB_RECORD, LOG_ERR, LOC +
968  QString("Bad response to 'HasTuner?' - '%1'").arg(result));
969  m_bError = true;
970  return false;
971  }
972  m_hasTuner = result.startsWith("OK:Yes");
973 
974  if (!ProcessCommand("HasPictureAttributes?", result))
975  {
976  LOG(VB_RECORD, LOG_ERR, LOC +
977  QString("Bad response to 'HasPictureAttributes?' - '%1'")
978  .arg(result));
979  m_bError = true;
980  return false;
981  }
982  m_hasPictureAttributes = result.startsWith("OK:Yes");
983 
984  /* Operate in "poll" or "xon/xoff" mode */
985  m_pollMode = ProcessCommand("FlowControl?", result) &&
986  result.startsWith("OK:Poll");
987 
988  LOG(VB_RECORD, LOG_INFO, LOC + "App opened successfully");
989  LOG(VB_RECORD, LOG_INFO, LOC +
990  QString("Capabilities: tuner(%1) "
991  "Picture attributes(%2) "
992  "Flow control(%3)")
993  .arg(m_hasTuner ? "yes" : "no",
994  m_hasPictureAttributes ? "yes" : "no",
995  m_pollMode ? "Polling" : "XON/XOFF")
996  );
997 
998  /* Let the external app know how many bytes will read without blocking */
999  ProcessCommand(QString("BlockSize:%1").arg(PACKET_SIZE), result);
1000 
1001  return true;
1002 }
1003 
1005 {
1006  if (m_io == nullptr)
1007  {
1008  LOG(VB_RECORD, LOG_WARNING, LOC +
1009  "WARNING: Unable to communicate with external app.");
1010  return false;
1011  }
1012 
1013  QString result;
1014  return ProcessCommand("Version?", result, 10s);
1015 }
1016 
1018 {
1019  if (m_tsOpen)
1020  return true;
1021 
1022  QString result;
1023 
1024  if (!ProcessCommand("IsOpen?", result))
1025  return false;
1026 
1027  m_tsOpen = true;
1028  return m_tsOpen;
1029 }
1030 
1032 {
1033  m_ioLock.lock();
1034  if (m_io)
1035  {
1036  QString result;
1037 
1038  LOG(VB_RECORD, LOG_INFO, LOC + "CloseRecorder");
1039  m_ioLock.unlock();
1040  ProcessCommand("CloseRecorder", result, 10s);
1041  m_ioLock.lock();
1042 
1043  if (!result.startsWith("OK"))
1044  {
1045  LOG(VB_RECORD, LOG_INFO, LOC +
1046  "CloseRecorder failed, sending kill.");
1047 
1048  QString full_command = QString("%1").arg(m_args.join(" "));
1049 
1050  if (!m_io->KillIfRunning(full_command))
1051  {
1052  // Give it one more chance.
1053  std::this_thread::sleep_for(50ms);
1054  if (!m_io->KillIfRunning(full_command))
1055  {
1056  LOG(VB_GENERAL, LOG_ERR,
1057  QString("Unable to kill existing '%1'.")
1058  .arg(full_command));
1059  return;
1060  }
1061  }
1062  }
1063  delete m_io;
1064  m_io = nullptr;
1065  }
1066  m_ioLock.unlock();
1067 }
1068 
1070 {
1071  bool streaming = (StreamingCount() > 0);
1072 
1073  LOG(VB_RECORD, LOG_INFO, LOC + "Restarting stream.");
1074 
1075  if (streaming)
1076  StopStreaming();
1077 
1078  std::this_thread::sleep_for(1s);
1079 
1080  if (streaming)
1081  return StartStreaming();
1082 
1083  return true;
1084 }
1085 
1087 {
1088  if (m_replay)
1089  {
1090  QString result;
1091 
1092  // Let the external app know that we could be busy for a little while
1093  if (!m_pollMode)
1094  {
1095  ProcessCommand(QString("XOFF"), result);
1096  m_xon = false;
1097  }
1098 
1099  /* If the input is not a 'broadcast' it may only have one
1100  * copy of the SPS right at the beginning of the stream,
1101  * so make sure we don't miss it!
1102  */
1103  QMutexLocker listen_lock(&m_listenerLock);
1104 
1105  if (!m_streamDataList.empty())
1106  {
1107  for (auto sit = m_streamDataList.cbegin();
1108  sit != m_streamDataList.cend(); ++sit)
1109  {
1110  sit.key()->ProcessData(reinterpret_cast<const uint8_t *>
1111  (m_replayBuffer.constData()),
1112  m_replayBuffer.size());
1113  }
1114  }
1115  LOG(VB_RECORD, LOG_INFO, LOC + QString("Replayed %1 bytes")
1116  .arg(m_replayBuffer.size()));
1117  m_replayBuffer.clear();
1118  m_replay = false;
1119 
1120  // Let the external app know that we are ready
1121  if (!m_pollMode)
1122  {
1123  if (ProcessCommand(QString("XON"), result))
1124  m_xon = true;
1125  }
1126  }
1127 }
1128 
1130 {
1131  QString result;
1132 
1133  QMutexLocker locker(&m_streamLock);
1134 
1136 
1137  LOG(VB_RECORD, LOG_INFO, LOC +
1138  QString("StartStreaming with %1 current listeners")
1139  .arg(StreamingCount()));
1140 
1141  if (!IsAppOpen())
1142  {
1143  LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder not started.");
1144  return false;
1145  }
1146 
1147  if (StreamingCount() == 0)
1148  {
1149  if (!ProcessCommand("StartStreaming", result, 15s))
1150  {
1151  LogLevel_t level = LOG_ERR;
1152  if (result.startsWith("warn", Qt::CaseInsensitive))
1153  level = LOG_WARNING;
1154  else
1155  m_bError = true;
1156 
1157  LOG(VB_GENERAL, level, LOC + QString("StartStreaming failed: '%1'")
1158  .arg(result));
1159 
1160  return false;
1161  }
1162 
1163  LOG(VB_RECORD, LOG_INFO, LOC + "Streaming started");
1164  }
1165  else
1166  LOG(VB_RECORD, LOG_INFO, LOC + "Already streaming");
1167 
1168  m_streamingCnt.ref();
1169 
1170  LOG(VB_RECORD, LOG_INFO, LOC +
1171  QString("StartStreaming %1 listeners")
1172  .arg(StreamingCount()));
1173 
1174  return true;
1175 }
1176 
1178 {
1179  QMutexLocker locker(&m_streamLock);
1180 
1181  LOG(VB_RECORD, LOG_INFO, LOC +
1182  QString("StopStreaming %1 listeners")
1183  .arg(StreamingCount()));
1184 
1185  if (StreamingCount() == 0)
1186  {
1187  LOG(VB_RECORD, LOG_INFO, LOC +
1188  "StopStreaming requested, but we are not streaming!");
1189  return true;
1190  }
1191 
1192  if (m_streamingCnt.deref())
1193  {
1194  LOG(VB_RECORD, LOG_INFO, LOC +
1195  QString("StopStreaming delayed, still have %1 listeners")
1196  .arg(StreamingCount()));
1197  return true;
1198  }
1199 
1200  LOG(VB_RECORD, LOG_INFO, LOC + "StopStreaming");
1201 
1202  if (!m_pollMode && m_xon)
1203  {
1204  QString result;
1205  ProcessCommand(QString("XOFF"), result);
1206  m_xon = false;
1207  }
1208 
1209  if (!IsAppOpen())
1210  {
1211  LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder not started.");
1212  return false;
1213  }
1214 
1215  QString result;
1216  if (!ProcessCommand("StopStreaming", result, 10s))
1217  {
1218  LogLevel_t level = LOG_ERR;
1219  if (result.startsWith("warn", Qt::CaseInsensitive))
1220  level = LOG_WARNING;
1221  else
1222  m_bError = true;
1223 
1224  LOG(VB_GENERAL, level, LOC + QString("StopStreaming: '%1'")
1225  .arg(result));
1226 
1227  return false;
1228  }
1229 
1230  PurgeBuffer();
1231  LOG(VB_RECORD, LOG_INFO, LOC + "Streaming stopped");
1232 
1233  return true;
1234 }
1235 
1236 bool ExternalStreamHandler::ProcessCommand(const QString & cmd,
1237  QString & result,
1238  std::chrono::milliseconds timeout,
1239  uint retry_cnt)
1240 {
1241  QMutexLocker locker(&m_processLock);
1242 
1243  if (m_apiVersion == 2)
1244  return ProcessVer2(cmd, result, timeout, retry_cnt);
1245  if (m_apiVersion == 1)
1246  return ProcessVer1(cmd, result, timeout, retry_cnt);
1247 
1248  LOG(VB_RECORD, LOG_ERR, LOC +
1249  QString("Invalid API version %1. Expected 1 or 2").arg(m_apiVersion));
1250  return false;
1251 }
1252 
1253 bool ExternalStreamHandler::ProcessVer1(const QString & cmd,
1254  QString & result,
1255  std::chrono::milliseconds timeout,
1256  uint retry_cnt)
1257 {
1258  LOG(VB_RECORD, LOG_DEBUG, LOC + QString("ProcessVer1('%1')")
1259  .arg(cmd));
1260 
1261  for (uint cnt = 0; cnt < retry_cnt; ++cnt)
1262  {
1263  QMutexLocker locker(&m_ioLock);
1264 
1265  if (!m_io)
1266  {
1267  LOG(VB_RECORD, LOG_ERR, LOC + "External I/O not ready!");
1268  return false;
1269  }
1270 
1271  QByteArray buf(cmd.toUtf8(), cmd.size());
1272  buf += '\n';
1273 
1274  if (m_io->Error())
1275  {
1276  LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder in bad state: " +
1277  m_io->ErrorString());
1278  return false;
1279  }
1280 
1281  /* Try to keep in sync, if External app was too slow in responding
1282  * to previous query, consume the response before sending new query */
1283  m_io->GetStatus(0ms);
1284 
1285  /* Send new query */
1286  m_io->Write(buf);
1287 
1289  while (timer.elapsed() < timeout)
1290  {
1291  result = m_io->GetStatus(timeout);
1292  if (m_io->Error())
1293  {
1294  LOG(VB_GENERAL, LOG_ERR, LOC +
1295  "Failed to read from External Recorder: " +
1296  m_io->ErrorString());
1297  m_bError = true;
1298  return false;
1299  }
1300 
1301  // Out-of-band error message
1302  if (result.startsWith("STATUS:ERR") ||
1303  result.startsWith("0:STATUS:ERR"))
1304  {
1305  LOG(VB_RECORD, LOG_ERR, LOC + result);
1306  result.remove(0, result.indexOf(":ERR") + 1);
1307  return false;
1308  }
1309  // STATUS message are "out of band".
1310  // Ignore them while waiting for a responds to a command
1311  if (!result.startsWith("STATUS") && !result.startsWith("0:STATUS"))
1312  break;
1313  LOG(VB_RECORD, LOG_INFO, LOC +
1314  QString("Ignoring response '%1'").arg(result));
1315  }
1316 
1317  if (result.size() < 1)
1318  {
1319  LOG(VB_GENERAL, LOG_WARNING, LOC +
1320  QString("External Recorder did not respond to '%1'").arg(cmd));
1321  }
1322  else
1323  {
1324  bool okay = result.startsWith("OK");
1325  if (okay || result.startsWith("WARN") || result.startsWith("ERR"))
1326  {
1327  LogLevel_t level = LOG_INFO;
1328 
1329  m_ioErrCnt = 0;
1330  if (!okay)
1331  level = LOG_WARNING;
1332  else if (cmd.startsWith("SendBytes"))
1333  level = LOG_DEBUG;
1334 
1335  LOG(VB_RECORD, level,
1336  LOC + QString("ProcessCommand('%1') = '%2' took %3ms %4")
1337  .arg(cmd, result,
1338  QString::number(timer.elapsed().count()),
1339  okay ? "" : "<-- NOTE"));
1340 
1341  return okay;
1342  }
1343  LOG(VB_GENERAL, LOG_WARNING, LOC +
1344  QString("External Recorder invalid response to '%1': '%2'")
1345  .arg(cmd, result));
1346  }
1347 
1348  if (++m_ioErrCnt > 10)
1349  {
1350  LOG(VB_GENERAL, LOG_ERR, LOC + "Too many I/O errors.");
1351  m_bError = true;
1352  break;
1353  }
1354  }
1355 
1356  return false;
1357 }
1358 
1359 bool ExternalStreamHandler::ProcessVer2(const QString & command,
1360  QString & result,
1361  std::chrono::milliseconds timeout,
1362  uint retry_cnt)
1363 {
1364  QString status;
1365  QString raw;
1366 
1367  for (uint cnt = 0; cnt < retry_cnt; ++cnt)
1368  {
1369  QString cmd = QString("%1:%2").arg(++m_serialNo).arg(command);
1370 
1371  LOG(VB_RECORD, LOG_DEBUG, LOC + QString("ProcessVer2('%1') serial(%2)")
1372  .arg(cmd).arg(m_serialNo));
1373 
1374  QMutexLocker locker(&m_ioLock);
1375 
1376  if (!m_io)
1377  {
1378  LOG(VB_RECORD, LOG_ERR, LOC + "External I/O not ready!");
1379  return false;
1380  }
1381 
1382  QByteArray buf(cmd.toUtf8(), cmd.size());
1383  buf += '\n';
1384 
1385  if (m_io->Error())
1386  {
1387  LOG(VB_GENERAL, LOG_ERR, LOC + "External Recorder in bad state: " +
1388  m_io->ErrorString());
1389  return false;
1390  }
1391 
1392  /* Send query */
1393  m_io->Write(buf);
1394 
1395  QStringList tokens;
1396 
1398  while (timer.elapsed() < timeout)
1399  {
1400  result = m_io->GetStatus(timeout);
1401  if (m_io->Error())
1402  {
1403  LOG(VB_GENERAL, LOG_ERR, LOC +
1404  "Failed to read from External Recorder: " +
1405  m_io->ErrorString());
1406  m_bError = true;
1407  return false;
1408  }
1409 
1410  if (!result.isEmpty())
1411  {
1412  raw = result;
1413 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1414  tokens = result.split(':', QString::SkipEmptyParts);
1415 #else
1416  tokens = result.split(':', Qt::SkipEmptyParts);
1417 #endif
1418 
1419  // Look for result with the serial number of this query
1420  if (tokens.size() > 1 && tokens[0].toUInt() >= m_serialNo)
1421  break;
1422 
1423  /* Other messages are "out of band" */
1424 
1425  // Check for error message missing serial#
1426  if (tokens[0].startsWith("ERR"))
1427  break;
1428 
1429  // Remove serial#
1430  tokens.removeFirst();
1431  result = tokens.join(':');
1432  bool err = (tokens.size() > 1 && tokens[1].startsWith("ERR"));
1433  LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO), LOC + raw);
1434  if (err)
1435  {
1436  // Remove "STATUS"
1437  tokens.removeFirst();
1438  result = tokens.join(':');
1439  return false;
1440  }
1441  }
1442  }
1443 
1444  if (timer.elapsed() >= timeout)
1445  {
1446  LOG(VB_RECORD, LOG_ERR, LOC +
1447  QString("ProcessVer2: Giving up waiting for response for "
1448  "command '%2'").arg(cmd));
1449  }
1450  else if (tokens.size() < 2)
1451  {
1452  LOG(VB_RECORD, LOG_ERR, LOC +
1453  QString("Did not receive a valid response "
1454  "for command '%1', received '%2'").arg(cmd, result));
1455  }
1456  else if (tokens[0].toUInt() > m_serialNo)
1457  {
1458  LOG(VB_RECORD, LOG_ERR, LOC +
1459  QString("ProcessVer2: Looking for serial no %1, "
1460  "but received %2 for command '%2'")
1461  .arg(QString::number(m_serialNo), tokens[0], cmd));
1462  }
1463  else
1464  {
1465  tokens.removeFirst();
1466  status = tokens[0].trimmed();
1467  result = tokens.join(':');
1468 
1469  bool okay = (status == "OK");
1470  if (okay || status.startsWith("WARN") || status.startsWith("ERR"))
1471  {
1472  LogLevel_t level = LOG_INFO;
1473 
1474  m_ioErrCnt = 0;
1475  if (!okay)
1476  level = LOG_WARNING;
1477  else if (command.startsWith("SendBytes") ||
1478  (command.startsWith("TuneStatus") &&
1479  result == "OK:InProgress"))
1480  level = LOG_DEBUG;
1481 
1482  LOG(VB_RECORD, level,
1483  LOC + QString("ProcessV2('%1') = '%2' took %3ms %4")
1484  .arg(cmd, result, QString::number(timer.elapsed().count()),
1485  okay ? "" : "<-- NOTE"));
1486 
1487  return okay;
1488  }
1489  LOG(VB_GENERAL, LOG_WARNING, LOC +
1490  QString("External Recorder invalid response to '%1': '%2'")
1491  .arg(cmd, result));
1492  }
1493 
1494  if (++m_ioErrCnt > 10)
1495  {
1496  LOG(VB_GENERAL, LOG_ERR, LOC + "Too many I/O errors.");
1497  m_bError = true;
1498  break;
1499  }
1500  }
1501 
1502  return false;
1503 }
1504 
1506 {
1507  QString result;
1508  bool err = false;
1509 
1510  QMutexLocker locker(&m_ioLock);
1511 
1512  if (!m_io)
1513  {
1514  LOG(VB_RECORD, LOG_ERR, LOC + "External I/O not ready!");
1515  return true;
1516  }
1517 
1518  if (m_io->Error())
1519  {
1520  LOG(VB_GENERAL, LOG_ERR, "External Recorder in bad state: " +
1521  m_io->ErrorString());
1522  return true;
1523  }
1524 
1525  do
1526  {
1527  result = m_io->GetStatus(0ms);
1528  if (!result.isEmpty())
1529  {
1530  if (m_apiVersion > 1)
1531  {
1532 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1533  QStringList tokens = result.split(':', QString::SkipEmptyParts);
1534 #else
1535  QStringList tokens = result.split(':', Qt::SkipEmptyParts);
1536 #endif
1537 
1538  tokens.removeFirst();
1539  result = tokens.join(':');
1540  for (int idx = 1; idx < tokens.size(); ++idx)
1541  err |= tokens[idx].startsWith("ERR");
1542  }
1543  else
1544  err |= result.startsWith("STATUS:ERR");
1545 
1546  LOG(VB_RECORD, (err ? LOG_WARNING : LOG_INFO), LOC + result);
1547  }
1548  }
1549  while (!result.isEmpty());
1550 
1551  return err;
1552 }
1553 
1555 {
1556  if (m_io)
1557  {
1558  QByteArray buffer;
1559  m_io->Read(buffer, PACKET_SIZE, 1ms);
1560  m_io->GetStatus(1ms);
1561  }
1562 }
1563 
1565 {
1566  // TODO report on buffer overruns, etc.
1567 }
WEXITSTATUS
#define WEXITSTATUS(w)
Definition: compat.h:193
ExternalStreamHandler::ProcessVer1
bool ProcessVer1(const QString &cmd, QString &result, std::chrono::milliseconds timeout, uint retry_cnt)
Definition: ExternalStreamHandler.cpp:1253
ExternalStreamHandler::ExternalStreamHandler
ExternalStreamHandler(const QString &path, int inputid, int majorid)
Definition: ExternalStreamHandler.cpp:540
build_compdb.args
args
Definition: build_compdb.py:11
ExternalStreamHandler::m_pollMode
bool m_pollMode
Definition: ExternalStreamHandler.h:130
MythTimer::elapsed
std::chrono::milliseconds elapsed(void)
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:91
ExternalStreamHandler::IsTSOpen
bool IsTSOpen(void)
Definition: ExternalStreamHandler.cpp:1017
O_NONBLOCK
#define O_NONBLOCK
Definition: compat.h:337
ExternalStreamHandler::m_ioLock
QMutex m_ioLock
Definition: ExternalStreamHandler.h:124
ENO
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:73
hardwareprofile.smolt.timeout
float timeout
Definition: smolt.py:103
ExternalStreamHandler::m_apiVersion
int m_apiVersion
Definition: ExternalStreamHandler.h:132
ExternalStreamHandler::m_serialNo
uint m_serialNo
Definition: ExternalStreamHandler.h:133
error
static void error(const char *str,...)
Definition: vbi.cpp:36
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
ExternalStreamHandler::PriorityEvent
void PriorityEvent(int fd) override
Definition: ExternalStreamHandler.cpp:1564
MythTimer
A QElapsedTimer based timer to replace use of QTime as a timer.
Definition: mythtimer.h:13
StreamHandler
Definition: streamhandler.h:58
ExternalStreamHandler::m_hasPictureAttributes
bool m_hasPictureAttributes
Definition: ExternalStreamHandler.h:135
ExternalStreamHandler::StreamingCount
int StreamingCount(void) const
Definition: ExternalStreamHandler.cpp:574
discid.disc.read
def read(device=None, features=[])
Definition: disc.py:35
ExternIO::~ExternIO
~ExternIO(void)
Definition: ExternalStreamHandler.cpp:65
ExternalStreamHandler::run
void run(void) override
Runs the Qt event loop unless we have a QRunnable, in which case we run the runnable run instead.
Definition: ExternalStreamHandler.cpp:579
mythburn.write
def write(text, progress=True)
Definition: mythburn.py:308
ExternIO::GetStatus
QString GetStatus(std::chrono::milliseconds timeout=2500ms)
Definition: ExternalStreamHandler.cpp:172
ExternalStreamHandler::m_io
ExternIO * m_io
Definition: ExternalStreamHandler.h:125
MThread::setObjectName
void setObjectName(const QString &name)
Definition: mthread.cpp:238
MythTimer::stop
void stop(void)
Stops timer, next call to isRunning() will return false and any calls to elapsed() or restart() will ...
Definition: mythtimer.cpp:78
MythTimer::isRunning
bool isRunning(void) const
Returns true if start() or restart() has been called at least once since construction and since any c...
Definition: mythtimer.cpp:135
ExternalStreamHandler::StopStreaming
bool StopStreaming(void)
Definition: ExternalStreamHandler.cpp:1177
ExternIO::m_error
QString m_error
Definition: ExternalStreamHandler.h:56
ExternalStreamHandler::m_xon
bool m_xon
Definition: ExternalStreamHandler.h:139
MythTimer::start
void start(void)
starts measuring elapsed time.
Definition: mythtimer.cpp:47
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
ExternalStreamHandler::m_processLock
QMutex m_processLock
Definition: ExternalStreamHandler.h:149
streamlisteners.h
ExternIO::ExternIO
ExternIO(const QString &app, const QStringList &args)
Definition: ExternalStreamHandler.cpp:36
ExternalStreamHandler::s_handlers
static QMap< int, ExternalStreamHandler * > s_handlers
Definition: ExternalStreamHandler.h:143
ExternIO::m_bufSize
int m_bufSize
Definition: ExternalStreamHandler.h:58
ExternIO::Error
bool Error(void) const
Definition: ExternalStreamHandler.h:41
ExternIO::m_buffer
char * m_buffer
Definition: ExternalStreamHandler.h:59
ExternalStreamHandler.h
GENERIC_EXIT_PIPE_FAILURE
@ GENERIC_EXIT_PIPE_FAILURE
Error creating I/O pipes.
Definition: exitcodes.h:27
ExternIO
Definition: ExternalStreamHandler.h:28
ExternalStreamHandler::s_handlersRefCnt
static QMap< int, uint > s_handlersRefCnt
Definition: ExternalStreamHandler.h:144
ExternalStreamHandler::TOO_FAST_SIZE
@ TOO_FAST_SIZE
Definition: ExternalStreamHandler.h:73
close
#define close
Definition: compat.h:43
ExternalStreamHandler
Definition: ExternalStreamHandler.h:68
ExternalStreamHandler::m_hasTuner
bool m_hasTuner
Definition: ExternalStreamHandler.h:134
ExternIO::KillIfRunning
static bool KillIfRunning(const QString &cmd)
Definition: ExternalStreamHandler.cpp:245
StreamHandler::m_listenerLock
QRecursiveMutex m_listenerLock
Definition: streamhandler.h:153
ExternalStreamHandler::ReplayStream
void ReplayStream(void)
Definition: ExternalStreamHandler.cpp:1086
ExternalStreamHandler::m_tsOpen
bool m_tsOpen
Definition: ExternalStreamHandler.h:128
ExternIO::Fork
void Fork(void)
Definition: ExternalStreamHandler.cpp:300
ExternalStreamHandler::m_replayBuffer
QByteArray m_replayBuffer
Definition: ExternalStreamHandler.h:137
ExternalStreamHandler::CloseApp
void CloseApp(void)
Definition: ExternalStreamHandler.cpp:1031
ExternIO::m_appErr
int m_appErr
Definition: ExternalStreamHandler.h:54
ExternIO::Ready
bool Ready(int fd, std::chrono::milliseconds timeout, const QString &what)
Definition: ExternalStreamHandler.cpp:75
ExternIO::m_pid
pid_t m_pid
Definition: ExternalStreamHandler.h:55
ExternIO::ErrorString
QString ErrorString(void) const
Definition: ExternalStreamHandler.h:42
StreamHandler::UpdateFiltersFromStreamData
bool UpdateFiltersFromStreamData(void)
Definition: streamhandler.cpp:290
MythTimer::restart
std::chrono::milliseconds restart(void)
Returns milliseconds elapsed since last start() or restart() and resets the count.
Definition: mythtimer.cpp:62
ExternalStreamHandler::PurgeBuffer
void PurgeBuffer(void)
Definition: ExternalStreamHandler.cpp:1554
MThread::RunEpilog
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:209
ExternIO::m_app
QFileInfo m_app
Definition: ExternalStreamHandler.h:50
ExternalStreamHandler::Get
static ExternalStreamHandler * Get(const QString &devname, int inputid, int majorid)
Definition: ExternalStreamHandler.cpp:460
ExternIO::Run
bool Run(void)
Definition: ExternalStreamHandler.cpp:234
ExternalStreamHandler::RestartStream
bool RestartStream(void)
Definition: ExternalStreamHandler.cpp:1069
ExternalStreamHandler::m_majorId
int m_majorId
Definition: ExternalStreamHandler.h:123
ExternalStreamHandler::SetAPIVersion
bool SetAPIVersion(void)
Definition: ExternalStreamHandler.cpp:866
uint
unsigned int uint
Definition: compat.h:79
ExternalStreamHandler::s_handlersLock
static QMutex s_handlersLock
Definition: ExternalStreamHandler.h:142
ExternIO::m_appOut
int m_appOut
Definition: ExternalStreamHandler.h:53
ExternIO::m_appIn
int m_appIn
Definition: ExternalStreamHandler.h:52
ExternalStreamHandler::TS_PACKET_SIZE
@ TS_PACKET_SIZE
Definition: ExternalStreamHandler.h:71
GENERIC_EXIT_DAEMONIZING_ERROR
@ GENERIC_EXIT_DAEMONIZING_ERROR
Error daemonizing or execl.
Definition: exitcodes.h:29
ExternalStreamHandler::StartStreaming
bool StartStreaming(void)
Definition: ExternalStreamHandler.cpp:1129
LOC
#define LOC
Definition: ExternalStreamHandler.cpp:34
ExternalStreamHandler::UpdateDescription
QString UpdateDescription(void)
Definition: ExternalStreamHandler.cpp:896
mpegstreamdata.h
ExternalStreamHandler::IsAppOpen
bool IsAppOpen(void)
Definition: ExternalStreamHandler.cpp:1004
ExternalStreamHandler::CheckForError
bool CheckForError(void)
Definition: ExternalStreamHandler.cpp:1505
ExternalStreamHandler::ProcessVer2
bool ProcessVer2(const QString &command, QString &result, std::chrono::milliseconds timeout, uint retry_cnt)
Definition: ExternalStreamHandler.cpp:1359
ExternalStreamHandler::m_ioErrCnt
int m_ioErrCnt
Definition: ExternalStreamHandler.h:129
ExternalStreamHandler::m_replay
bool m_replay
Definition: ExternalStreamHandler.h:138
MythTimer::kStartRunning
@ kStartRunning
Definition: mythtimer.h:17
cardutil.h
StreamHandler::m_runningDesired
volatile bool m_runningDesired
Definition: streamhandler.h:121
ExternalStreamHandler::MAX_API_VERSION
@ MAX_API_VERSION
Definition: ExternalStreamHandler.h:70
ExternalStreamHandler::PACKET_SIZE
@ PACKET_SIZE
Definition: ExternalStreamHandler.h:72
ExternalChannel.h
ExternIO::Write
int Write(const QByteArray &buffer)
Definition: ExternalStreamHandler.cpp:201
ExternIO::m_statusBuf
QString m_statusBuf
Definition: ExternalStreamHandler.h:61
ExternalStreamHandler::m_args
QStringList m_args
Definition: ExternalStreamHandler.h:126
ExternIO::kMaxErrorCnt
@ kMaxErrorCnt
Definition: ExternalStreamHandler.h:30
StreamHandler::m_streamDataList
StreamDataList m_streamDataList
Definition: streamhandler.h:155
ExternalStreamHandler::ProcessCommand
bool ProcessCommand(const QString &cmd, QString &result, std::chrono::milliseconds timeout=4s, uint retry_cnt=3)
Definition: ExternalStreamHandler.cpp:1236
ExternalStreamHandler::m_streamingCnt
QAtomicInt m_streamingCnt
Definition: ExternalStreamHandler.h:146
StreamHandler::m_bError
volatile bool m_bError
Definition: streamhandler.h:126
logPropagateArgs
QString logPropagateArgs
Definition: logging.cpp:86
ExternalStreamHandler::m_loc
QString m_loc
Definition: ExternalStreamHandler.h:122
ExternIO::Read
int Read(QByteArray &buffer, int maxlen, std::chrono::milliseconds timeout=2500ms)
Definition: ExternalStreamHandler.cpp:111
ExternIO::m_status
QTextStream m_status
Definition: ExternalStreamHandler.h:62
exitcodes.h
StreamHandler::m_device
QString m_device
Definition: streamhandler.h:113
ExternalStreamHandler::m_streamLock
QMutex m_streamLock
Definition: ExternalStreamHandler.h:147
ExternalStreamHandler::Return
static void Return(ExternalStreamHandler *&ref, int inputid)
Definition: ExternalStreamHandler.cpp:491
dtvsignalmonitor.h
ExternalStreamHandler::OpenApp
bool OpenApp(void)
Definition: ExternalStreamHandler.cpp:911
ExternIO::m_args
QStringList m_args
Definition: ExternalStreamHandler.h:51
ExternIO::m_errCnt
int m_errCnt
Definition: ExternalStreamHandler.h:63
ExternalStreamHandler::m_app
QString m_app
Definition: ExternalStreamHandler.h:127