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