MythTV  master
mhi.cpp
Go to the documentation of this file.
1 #include "mhi.h"
2 
3 #include <QRegion>
4 #include <QVector>
5 #include <QUrl>
6 #include <QPoint> // for QPoint
7 #include <QRgb> // for QRgb
8 #include <QVariant> // for QVariant
9 #include <QByteArray> // for QByteArray
10 #include <QStringList> // for QStringList
11 #include <QTime> // for QTime
12 #include <QHash> // for QHash
13 
14 #include <algorithm> // for min
15 #include <cmath> // for round, sqrt
16 #include <cstdint> // for uint8_t
17 #include <cstring> // for memcpy, memset
18 #include <deque> // for _Deque_iterator, operator!=
19 
20 #include "dsmcc.h" // for Dsmcc
21 #include "interactivetv.h" // for InteractiveTV
22 #include "interactivescreen.h"
23 #include "mythpainter.h"
24 #include "mythimage.h"
25 #include "mythuiimage.h"
26 #include "mythdirs.h"
27 #include "mythlogging.h"
28 #include "mythmainwindow.h"
29 #include "mythavutil.h"
30 #include "mthread.h" // for MThread
31 #include "mythcorecontext.h" // for MythCoreContext, etc
32 #include "mythdb.h" // for MythDB
33 #include "mythdbcon.h" // for MSqlQuery
34 #include "mythevent.h" // for MythEvent
35 #include "mythplayer.h" // for MythPlayer
36 #include "mythrect.h" // for MythRect
37 #include "mythuiactions.h" // for ACTION_0, ACTION_1, etc
38 #include "tv_actions.h" // for ACTION_MENUTEXT, etc
39 
40 extern "C" {
41 #include "libavutil/imgutils.h"
42 }
43 
44 static bool ft_loaded = false;
45 static FT_Library ft_library;
46 
47 #define FONT_WIDTHRES 54
48 #define FONT_HEIGHTRES 72 // 1 pixel per point
49 #define FONT_TO_USE "FreeSans.ttf" // Tiresias Screenfont.ttf is mandated
50 
51 
52 // LifecycleExtension tuneinfo:
53 const unsigned kTuneQuietly = 1U<<0; // b0 tune quietly
54 const unsigned kTuneKeepApp = 1U<<1; // b1 keep app running
55 const unsigned kTuneCarId = 1U<<2; // b2 carousel id in bits 8..16
56 const unsigned kTuneCarReset = 1U<<3; // b3 get carousel id from gateway info
57 //const unsigned kTuneBcastDisa = 1U<<4; // b4 broadcaster_interrupt disable
58 // b5..7 reserverd
59 // b8..15 carousel id
60 const unsigned kTuneKeepChnl = 1U<<16; // Keep current channel
61 // b17..31 reserved
62 
67 {
68  public:
69  QImage m_image;
70  int m_x;
71  int m_y;
72  bool m_bUnder;
73 };
74 
76  : m_parent(parent), m_dsmcc(new Dsmcc()),
77  m_engine(MHCreateEngine(this))
78 {
79  if (!ft_loaded)
80  {
81  FT_Error error = FT_Init_FreeType(&ft_library);
82  if (!error)
83  ft_loaded = true;
84  }
85 
86  if (ft_loaded)
87  {
88  // TODO: We need bold and italic versions.
89  if (LoadFont(FONT_TO_USE))
90  m_face_loaded = true;
91  }
92 }
93 
94 // Load the font. Copied, generally, from OSD::LoadFont.
95 bool MHIContext::LoadFont(const QString& name)
96 {
97  QString fullnameA = GetConfDir() + "/" + name;
98  QByteArray fnameA = fullnameA.toLatin1();
99  FT_Error errorA = FT_New_Face(ft_library, fnameA.constData(), 0, &m_face);
100  if (!errorA)
101  return true;
102 
103  QString fullnameB = GetFontsDir() + name;
104  QByteArray fnameB = fullnameB.toLatin1();
105  FT_Error errorB = FT_New_Face(ft_library, fnameB.constData(), 0, &m_face);
106  if (!errorB)
107  return true;
108 
109  QString fullnameC = GetShareDir() + "themes/" + name;
110  QByteArray fnameC = fullnameC.toLatin1();
111  FT_Error errorC = FT_New_Face(ft_library, fnameC.constData(), 0, &m_face);
112  if (!errorC)
113  return true;
114 
115  const QString& fullnameD = name;
116  QByteArray fnameD = fullnameD.toLatin1();
117  FT_Error errorD = FT_New_Face(ft_library, fnameD.constData(), 0, &m_face);
118  if (!errorD)
119  return true;
120 
121  LOG(VB_GENERAL, LOG_ERR, QString("[mhi] Unable to find font: %1").arg(name));
122  return false;
123 }
124 
126 {
127  StopEngine();
128  delete(m_engine);
129  delete(m_dsmcc);
130  if (m_face_loaded) FT_Done_Face(m_face);
131 
132  ClearDisplay();
133  ClearQueue();
134 }
135 
136 // NB caller must hold m_display_lock
138 {
139  auto it = m_display.begin();
140  for (; it != m_display.end(); ++it)
141  delete *it;
142  m_display.clear();
143  m_videoDisplayRect = QRect();
144 }
145 
146 // NB caller must hold m_dsmccLock
148 {
149  auto it = m_dsmccQueue.begin();
150  for (; it != m_dsmccQueue.end(); ++it)
151  delete *it;
152  m_dsmccQueue.clear();
153 }
154 
155 // Ask the engine to stop and block until it has.
157 {
158  if (nullptr == m_engineThread)
159  return;
160 
161  m_stop = true;
162  m_runLock.lock();
163  m_engine_wait.wakeAll();
164  m_runLock.unlock();
165 
166  m_engineThread->wait();
167  delete m_engineThread;
168  m_engineThread = nullptr;
169 }
170 
171 
172 // Start or restart the MHEG engine.
173 void MHIContext::Restart(int chanid, int sourceid, bool isLive)
174 {
175  int tuneinfo = m_tuneinfo.isEmpty() ? 0 : m_tuneinfo.takeFirst();
176 
177  LOG(VB_MHEG, LOG_INFO,
178  QString("[mhi] Restart ch=%1 source=%2 live=%3 tuneinfo=0x%4")
179  .arg(chanid).arg(sourceid).arg(isLive).arg(tuneinfo,0,16));
180 
181  if (m_currentSource != sourceid)
182  {
183  m_currentSource = sourceid;
184  QMutexLocker locker(&m_channelMutex);
185  m_channelCache.clear();
186  }
187  m_currentStream = (chanid) ? chanid : -1;
188  if (!(tuneinfo & kTuneKeepChnl))
190 
191  if (tuneinfo & kTuneKeepApp)
192  {
193  // We have tuned to the channel in order to find the streams.
194  // Leave the MHEG engine running but restart the DSMCC carousel.
195  // This is a bit of a mess but it's the only way to be able to
196  // select streams from a different channel.
197  {
198  QMutexLocker locker(&m_dsmccLock);
199  if (tuneinfo & kTuneCarReset)
200  m_dsmcc->Reset();
201  ClearQueue();
202  }
203 
204  if (tuneinfo & (kTuneCarReset|kTuneCarId))
205  {
206  QMutexLocker locker(&m_runLock);
207  m_engine->EngineEvent(10); // NonDestructiveTuneOK
208  }
209  }
210  else
211  {
212  StopEngine();
213 
214  m_audioTag = -1;
215  m_videoTag = -1;
216 
217  {
218  QMutexLocker locker(&m_dsmccLock);
219  m_dsmcc->Reset();
220  ClearQueue();
221  }
222 
223  {
224  QMutexLocker locker(&m_keyLock);
225  m_keyQueue.clear();
226  }
227 
228  m_engine->SetBooting();
229  ClearDisplay();
230  m_updated = true;
231  m_stop = false;
232  m_isLive = isLive;
233  // Don't set the NBI version here. Restart is called
234  // after the PMT is processed.
235  m_engineThread = new MThread("MHEG", this);
237  }
238 }
239 
240 void MHIContext::run(void)
241 {
242  QMutexLocker locker(&m_runLock);
243 
244  QTime t; t.start();
245  while (!m_stop)
246  {
247  int toWait;
248  // Dequeue and process any key presses.
249  int key = 0;
250  do
251  {
254  {
255  QMutexLocker locker2(&m_keyLock);
256  key = m_keyQueue.dequeue();
257  }
258 
259  if (key != 0)
261 
262  // Run the engine and find out how long to pause.
263  toWait = m_engine->RunAll();
264  if (toWait < 0)
265  return;
266  } while (key != 0);
267 
268  toWait = (toWait > 1000 || toWait <= 0) ? 1000 : toWait;
269 
270  if (!m_stop && (toWait > 0))
271  m_engine_wait.wait(locker.mutex(), toWait);
272  }
273 }
274 
275 // Dequeue and process any DSMCC packets.
277 {
278  DSMCCPacket *packet = nullptr;
279  do
280  {
281  QMutexLocker locker(&m_dsmccLock);
282  packet = m_dsmccQueue.dequeue();
283  if (packet)
284  {
286  packet->m_data, packet->m_length,
287  packet->m_componentTag, packet->m_carouselId,
288  packet->m_dataBroadcastId);
289 
290  delete packet;
291  }
292  } while (packet);
293 }
294 
296  unsigned char *data, int length, int componentTag,
297  unsigned carouselId, int dataBroadcastId)
298 {
299  auto *dataCopy = (unsigned char*) malloc(length * sizeof(unsigned char));
300 
301  if (dataCopy == nullptr)
302  return;
303 
304  memcpy(dataCopy, data, length*sizeof(unsigned char));
305  {
306  QMutexLocker locker(&m_dsmccLock);
307  m_dsmccQueue.enqueue(new DSMCCPacket(dataCopy, length,
308  componentTag, carouselId,
309  dataBroadcastId));
310  }
311  m_engine_wait.wakeAll();
312 }
313 
314 // A NetworkBootInfo sub-descriptor is present in the PMT.
315 void MHIContext::SetNetBootInfo(const unsigned char *data, uint length)
316 {
317  if (length < 2) // A valid descriptor should always have at least 2 bytes.
318  return;
319 
320  LOG(VB_MHEG, LOG_INFO, QString("[mhi] SetNetBootInfo version %1 mode %2 len %3")
321  .arg(data[0]).arg(data[1]).arg(length));
322 
323  QMutexLocker locker(&m_dsmccLock);
324  // The carousel should be reset now as the stream has changed
325  m_dsmcc->Reset();
326  ClearQueue();
327  // Save the data from the descriptor.
328  m_nbiData.resize(0);
329  m_nbiData.reserve(length);
330  m_nbiData.insert(m_nbiData.begin(), data, data+length);
331  // If there is no Network Boot Info or we're setting it
332  // for the first time just update the "last version".
334  m_lastNbiVersion = data[0];
335  else
336  m_engine_wait.wakeAll();
337 }
338 
339 // Called only by m_engineThread
341 {
342  QMutexLocker locker(&m_dsmccLock);
343  if (m_nbiData.size() >= 2 && m_nbiData[0] != m_lastNbiVersion)
344  {
345  m_lastNbiVersion = m_nbiData[0]; // Update the saved version
346  switch (m_nbiData[1])
347  {
348  case 1:
349  m_dsmcc->Reset();
350  m_engine->SetBooting();
351  locker.unlock();
352  {QMutexLocker locker2(&m_display_lock);
353  ClearDisplay();
354  m_updated = true;}
355  break;
356  case 2:
357  m_engine->EngineEvent(9); // NetworkBootInfo EngineEvent
358  break;
359  default:
360  LOG(VB_MHEG, LOG_INFO, QString("[mhi] Unknown NetworkBoot type %1")
361  .arg(m_nbiData[1]));
362  break;
363  }
364  }
365 }
366 
367 // Called by the engine to check for the presence of an object in the carousel.
368 bool MHIContext::CheckCarouselObject(QString objectPath)
369 {
370  if (objectPath.startsWith("http:") || objectPath.startsWith("https:"))
371  {
372  QByteArray cert;
373 
374  // Verify access to server
375  if (!CheckAccess(objectPath, cert))
376  return false;
377 
378  return m_ic.CheckFile(objectPath, cert);
379  }
380 
381  QStringList path = objectPath.split(QChar('/'), QString::SkipEmptyParts);
382  QByteArray result; // Unused
383  QMutexLocker locker(&m_dsmccLock);
384  int res = m_dsmcc->GetDSMCCObject(path, result);
385  return res == 0; // It's available now.
386 }
387 
388 bool MHIContext::GetDSMCCObject(const QString &objectPath, QByteArray &result)
389 {
390  QStringList path = objectPath.split(QChar('/'), QString::SkipEmptyParts);
391  QMutexLocker locker(&m_dsmccLock);
392  int res = m_dsmcc->GetDSMCCObject(path, result);
393  return (res == 0);
394 }
395 
396 bool MHIContext::CheckAccess(const QString &objectPath, QByteArray &cert)
397 {
398  cert.clear();
399 
400  // Verify access to server
401  QByteArray servers;
402  if (!GetDSMCCObject("/auth.servers", servers))
403  {
404  LOG(VB_MHEG, LOG_INFO, QString(
405  "[mhi] CheckAccess(%1) No auth.servers").arg(objectPath) );
406  return false;
407  }
408 
409  QByteArray host = QUrl(objectPath).host().toLocal8Bit();
410  if (!servers.contains(host))
411  {
412  LOG(VB_MHEG, LOG_INFO, QString("[mhi] CheckAccess(%1) Host not known")
413  .arg(objectPath) );
414  LOG(VB_MHEG, LOG_DEBUG, QString("[mhi] Permitted servers: %1")
415  .arg(servers.constData()) );
416 
417  // BUG: https://securegate.iplayer.bbc.co.uk is not listed
418  if (!objectPath.startsWith("https:"))
419  return false;
420  }
421 
422  if (!objectPath.startsWith("https:"))
423  return true;
424 
425  // Use TLS cert from carousel file auth.tls.<x>
426  if (!GetDSMCCObject("/auth.tls.1", cert))
427  return false;
428 
429  // The cert has a 5 byte header: 16b cert_count + 24b cert_len
430  cert = cert.mid(5);
431  return true;
432 }
433 
434 // Called by the engine to request data from the carousel.
435 // Caller must hold m_runLock
436 bool MHIContext::GetCarouselData(QString objectPath, QByteArray &result)
437 {
438  QByteArray cert;
439  bool const isIC = objectPath.startsWith("http:") || objectPath.startsWith("https:");
440  if (isIC)
441  {
442  // Verify access to server
443  if (!CheckAccess(objectPath, cert))
444  return false;
445  }
446 
447  // Get the path components. The string will normally begin with "//"
448  // since this is an absolute path but that will be removed by split.
449  QStringList path = objectPath.split(QChar('/'), QString::SkipEmptyParts);
450  // Since the DSMCC carousel and the MHEG engine are currently on the
451  // same thread this is safe. Otherwise we need to make a deep copy of
452  // the result.
453 
454  bool bReported = false;
455  QTime t; t.start();
456  while (!m_stop)
457  {
458  if (isIC)
459  {
460  switch (m_ic.GetFile(objectPath, result, cert))
461  {
463  if (bReported)
464  LOG(VB_MHEG, LOG_INFO, QString("[mhi] Received %1").arg(objectPath));
465  return true;
467  if (bReported)
468  LOG(VB_MHEG, LOG_INFO, QString("[mhi] Not found %1").arg(objectPath));
469  return false;
471  break;
472  }
473  }
474  else
475  {
476  QMutexLocker locker(&m_dsmccLock);
477  int res = m_dsmcc->GetDSMCCObject(path, result);
478  if (res == 0)
479  {
480  if (bReported)
481  LOG(VB_MHEG, LOG_INFO, QString("[mhi] Received %1").arg(objectPath));
482  return true; // Found it
483  }
484  // NB don't exit if -1 (not present) is returned as the object may
485  // arrive later. Exiting can cause the inital app to not be found
486  }
487 
488  if (t.elapsed() > 60000) // TODO get this from carousel info
489  {
490  if (bReported)
491  LOG(VB_MHEG, LOG_INFO, QString("[mhi] timed out %1").arg(objectPath));
492  return false; // Not there.
493  }
494  // Otherwise we block.
495  if (!bReported)
496  {
497  bReported = true;
498  LOG(VB_MHEG, LOG_INFO, QString("[mhi] Waiting for %1").arg(objectPath));
499  }
500  // Process DSMCC packets then block for a while or until we receive
501  // some more packets. We should eventually find out if this item is
502  // present.
504  m_engine_wait.wait(&m_runLock, 300);
505  }
506  return false; // Stop has been set. Say the object isn't present.
507 }
508 
509 // Mapping from key name & UserInput register to UserInput EventData
511 {
512  using key_t = QPair< QString, int /*UserInput register*/ >;
513 
514 public:
515  MHKeyLookup();
516 
517  int Find(const QString &name, int reg) const
518  { return m_map.value(key_t(name,reg), 0); }
519 
520 private:
521  void key(const QString &name, int code, int r1,
522  int r2=0, int r3=0, int r4=0, int r5=0, int r6=0, int r7=0, int r8=0, int r9=0);
523 
524  QHash<key_t,int /*EventData*/ > m_map;
525 };
526 
527 void MHKeyLookup::key(const QString &name, int code, int r1,
528  int r2, int r3, int r4, int r5, int r6, int r7, int r8, int r9)
529 {
530  if (r1 > 0)
531  m_map.insert(key_t(name,r1), code);
532  if (r2 > 0)
533  m_map.insert(key_t(name,r2), code);
534  if (r3 > 0)
535  m_map.insert(key_t(name,r3), code);
536  if (r4 > 0)
537  m_map.insert(key_t(name,r4), code);
538  if (r5 > 0)
539  m_map.insert(key_t(name,r5), code);
540  if (r6 > 0)
541  m_map.insert(key_t(name,r6), code);
542  if (r7 > 0)
543  m_map.insert(key_t(name,r7), code);
544  if (r8 > 0)
545  m_map.insert(key_t(name,r8), code);
546  if (r9 > 0)
547  m_map.insert(key_t(name,r9), code);
548 }
549 
551 {
552  // Use a modification of the standard key mapping for RC's with a single
553  // stop button which is used for both Esc and TEXTEXIT (Back).
554  // This mapping doesn't pass Esc to the MHEG app in registers 3 or 5 and
555  // hence allows the user to exit playback when the red button icon is shown
556  QStringList keylist = GET_KEY("TV Playback", "TEXTEXIT").split(QChar(','));
557  bool strict = !keylist.contains("Esc", Qt::CaseInsensitive);
558 
559  // This supports the UK and NZ key profile registers.
560  // The UK uses 3, 4 and 5 and NZ 13, 14 and 15. These are
561  // similar but the NZ profile also provides an EPG key.
562  // ETSI ES 202 184 V2.2.1 (2011-03) adds group 6 for ICE.
563  // The BBC use group 7 for ICE
564  key(ACTION_UP, 1, 4,5,6,7,14,15);
565  key(ACTION_DOWN, 2, 4,5,6,7,14,15);
566  key(ACTION_LEFT, 3, 4,5,6,7,14,15);
567  key(ACTION_RIGHT, 4, 4,5,6,7,14,15);
568  key(ACTION_0, 5, 4,6,7,14);
569  key(ACTION_1, 6, 4,6,7,14);
570  key(ACTION_2, 7, 4,6,7,14);
571  key(ACTION_3, 8, 4,6,7,14);
572  key(ACTION_4, 9, 4,6,7,14);
573  key(ACTION_5, 10, 4,6,7,14);
574  key(ACTION_6, 11, 4,6,7,14);
575  key(ACTION_7, 12, 4,6,7,14);
576  key(ACTION_8, 13, 4,6,7,14);
577  key(ACTION_9, 14, 4,6,7,14);
578  key(ACTION_SELECT, 15, 4,5,6,7,14,15);
579  key(ACTION_TEXTEXIT, 16, strict ? 3 : 0,4,strict ? 5 : 0,6,7,13,14,15); // 16= Cancel
580  // 17= help
581  // 18..99 reserved by DAVIC
582  key(ACTION_MENURED, 100, 3,4,5,6,7,13,14,15);
583  key(ACTION_MENUGREEN, 101, 3,4,5,6,7,13,14,15);
584  key(ACTION_MENUYELLOW, 102, 3,4,5,6,7,13,14,15);
585  key(ACTION_MENUBLUE, 103, 3,4,5,6,7,13,14,15);
586  key(ACTION_MENUTEXT, 104, 3,4,5,6,7);
587  key(ACTION_MENUTEXT, 105, 13,14,15); // NB from original Myth code
588  // 105..119 reserved for future spec
589  key(ACTION_STOP, 120, 6,7);
590  key(ACTION_PLAY, 121, 6,7);
591  key(ACTION_PAUSE, 122, 6,7);
592  key(ACTION_JUMPFFWD, 123, 6,7); // 123= Skip Forward
593  key(ACTION_JUMPRWND, 124, 6,7); // 124= Skip Back
594 #if 0 // These conflict with left & right
595  key(ACTION_SEEKFFWD, 125, 6,7); // 125= Fast Forward
596  key(ACTION_SEEKRWND, 126, 6,7); // 126= Rewind
597 #endif
598  key(ACTION_PLAYBACK, 127, 6,7);
599  // 128..256 reserved for future spec
600  // 257..299 vendor specific
601  key(ACTION_MENUEPG, 300, 13,14,15);
602  // 301.. Vendor specific
603 }
604 
605 // Called from tv_play when a key is pressed.
606 // If it is one in the current profile we queue it for the engine
607 // and return true otherwise we return false.
608 bool MHIContext::OfferKey(const QString& key)
609 {
610  static const MHKeyLookup kKeymap;
611  int action = kKeymap.Find(key, m_keyProfile);
612  if (action == 0)
613  return false;
614 
615  LOG(VB_GENERAL, LOG_INFO, QString("[mhi] Adding MHEG key %1:%2:%3")
616  .arg(key).arg(action).arg(m_keyQueue.size()) );
617  { QMutexLocker locker(&m_keyLock);
619  m_engine_wait.wakeAll();
620  return true;
621 }
622 
623 // Called from MythPlayer::VideoStart and MythPlayer::ReinitOSD
624 void MHIContext::Reinit(const QRect &videoRect, const QRect &dispRect, float aspect)
625 {
626  LOG(VB_MHEG, LOG_INFO,
627  QString("[mhi] Reinit video(y:%1 x:%2 w:%3 h:%4) "
628  "vis(y:%5 x:%6 w:%7 h:%8) aspect=%9")
629  .arg(videoRect.y()).arg(videoRect.x())
630  .arg(videoRect.width()).arg(videoRect.height())
631  .arg(dispRect.y()).arg(dispRect.x())
632  .arg(dispRect.width()).arg(dispRect.height()).arg(aspect));
633  m_videoDisplayRect = QRect();
634 
635  // MHEG presumes square pixels
636  enum { kNone, kHoriz, kBoth };
637  int mode = gCoreContext->GetNumSetting("MhegAspectCorrection", kNone);
638  auto const aspectd = static_cast<double>(aspect);
639  double const vz = (mode == kBoth) ? min(1.15, 1. / sqrt(aspectd)) : 1.;
640  double const hz = (mode > kNone) ? vz * aspectd : 1.;
641 
642  m_displayRect = QRect( int(dispRect.width() * (1 - hz) / 2),
643  int(dispRect.height() * (1 - vz) / 2),
644  int(dispRect.width() * hz), int(dispRect.height() * vz) );
645  m_videoRect = QRect( dispRect.x() + m_displayRect.x(),
646  dispRect.y() + int(dispRect.height() * (1 - hz) / 2),
647  int(dispRect.width() * hz), int(dispRect.height() * hz) );
648 }
649 
651 {
652  LOG(VB_MHEG, LOG_INFO, QString("[mhi] SetInputRegister %1").arg(num));
653  QMutexLocker locker(&m_keyLock);
654  m_keyQueue.clear();
655  m_keyProfile = num;
656 }
657 
659 {
660  // 0= Active, 1= Inactive, 2= Disabled
662 }
663 
664 // Called by the video player to redraw the image.
666  MythPainter *osdPainter)
667 {
668  if (!osdWindow || !osdPainter)
669  return;
670 
671  QMutexLocker locker(&m_display_lock);
672 
673  // In MHEG the video is just another item in the display stack
674  // but when we create the OSD we overlay everything over the video.
675  // We need to cut out anything belowthe video on the display stack
676  // to leave the video area clear.
677  auto it = m_display.begin();
678  for (; it != m_display.end(); ++it)
679  {
680  MHIImageData *data = *it;
681  if (!data->m_bUnder)
682  continue;
683 
684  QRect imageRect(data->m_x, data->m_y,
685  data->m_image.width(), data->m_image.height());
686  if (!m_videoDisplayRect.intersects(imageRect))
687  continue;
688 
689  // Replace this item with a set of cut-outs.
690  it = m_display.erase(it);
691 
692 #if QT_VERSION < QT_VERSION_CHECK(5, 8, 0)
693  QVector<QRect> rects =
694  (QRegion(imageRect) - QRegion(m_videoDisplayRect)).rects();
695  for (uint j = 0; j < (uint)rects.size(); j++)
696  {
697  const QRect &rect = rects[j];
698 #else
699  for (const QRect& rect : QRegion(imageRect)-QRegion(m_videoDisplayRect))
700  {
701 #endif
702  QImage image =
703  data->m_image.copy(rect.x()-data->m_x, rect.y()-data->m_y,
704  rect.width(), rect.height());
705  auto *newData = new MHIImageData;
706  newData->m_image = image;
707  newData->m_x = rect.x();
708  newData->m_y = rect.y();
709  newData->m_bUnder = true;
710  it = m_display.insert(it, newData);
711  ++it;
712  }
713  --it;
714  delete data;
715  }
716 
717  m_updated = false;
718  osdWindow->DeleteAllChildren();
719  // Copy all the display items into the display.
720  it = m_display.begin();
721  for (int count = 0; it != m_display.end(); ++it, count++)
722  {
723  MHIImageData *data = *it;
724  MythImage* image = osdPainter->GetFormatImage();
725  if (!image)
726  continue;
727 
728  image->Assign(data->m_image);
729  auto *uiimage = new MythUIImage(osdWindow, QString("itv%1").arg(count));
730  if (uiimage)
731  {
732  uiimage->SetImage(image);
733  uiimage->SetArea(MythRect(data->m_x, data->m_y,
734  data->m_image.width(), data->m_image.height()));
735  }
736  image->DecrRef();
737  }
738  osdWindow->OptimiseDisplayedArea();
739  // N.B. bypasses OSD class hence no expiry set
740  osdWindow->SetVisible(true);
741 }
742 
743 void MHIContext::GetInitialStreams(int &audioTag, int &videoTag)
744 {
745  audioTag = m_audioTag;
746  videoTag = m_videoTag;
747 }
748 
749 
750 // An area of the screen/image needs to be redrawn.
751 // Called from the MHEG engine.
752 // We always redraw the whole scene.
753 void MHIContext::RequireRedraw(const QRegion & /*region*/)
754 {
755  m_updated = false;
756  m_display_lock.lock();
757  ClearDisplay();
758  m_display_lock.unlock();
759  // Always redraw the whole screen
761  m_updated = true;
762 }
763 
764 inline int MHIContext::ScaleX(int n, bool roundup) const
765 {
766  return (n * m_displayRect.width() + (roundup ? StdDisplayWidth - 1 : 0)) / StdDisplayWidth;
767 }
768 
769 inline int MHIContext::ScaleY(int n, bool roundup) const
770 {
771  return (n * m_displayRect.height() + (roundup ? StdDisplayHeight - 1 : 0)) / StdDisplayHeight;
772 }
773 
774 inline QRect MHIContext::Scale(const QRect &r) const
775 {
776  return { m_displayRect.topLeft() + QPoint(ScaleX(r.x()), ScaleY(r.y())),
777  QSize(ScaleX(r.width(), true), ScaleY(r.height(), true)) };
778 }
779 
780 inline int MHIContext::ScaleVideoX(int n, bool roundup) const
781 {
782  return (n * m_videoRect.width() + (roundup ? StdDisplayWidth - 1 : 0)) / StdDisplayWidth;
783 }
784 
785 inline int MHIContext::ScaleVideoY(int n, bool roundup) const
786 {
787  return (n * m_videoRect.height() + (roundup ? StdDisplayHeight - 1 : 0)) / StdDisplayHeight;
788 }
789 
790 inline QRect MHIContext::ScaleVideo(const QRect &r) const
791 {
792  return { m_videoRect.topLeft() + QPoint(ScaleVideoX(r.x()), ScaleVideoY(r.y())),
793  QSize(ScaleVideoX(r.width(), true), ScaleVideoY(r.height(), true)) };
794 }
795 
796 void MHIContext::AddToDisplay(const QImage &image, const QRect &displayRect, bool bUnder /*=false*/)
797 {
798  const QRect scaledRect = Scale(displayRect);
799 
800  auto *data = new MHIImageData;
801 
802  data->m_image = image.convertToFormat(QImage::Format_ARGB32).scaled(
803  scaledRect.width(), scaledRect.height(),
804  Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
805  data->m_x = scaledRect.x();
806  data->m_y = scaledRect.y();
807  data->m_bUnder = bUnder;
808 
809  QMutexLocker locker(&m_display_lock);
810  if (!bUnder)
811  m_display.push_back(data);
812  else
813  {
814  // Replace any existing items under the video with this
815  auto it = m_display.begin();
816  while (it != m_display.end())
817  {
818  MHIImageData *old = *it;
819  if (!old->m_bUnder)
820  ++it;
821  else
822  {
823  it = m_display.erase(it);
824  delete old;
825  }
826  }
827  m_display.push_front(data);
828  }
829 }
830 
831 inline int Roundup(int n, int r)
832 {
833  // NB assumes 2's complement arithmetic
834  return n + (-n & (r - 1));
835 }
836 
837 // The videoRect gives the size and position to which the video must be scaled.
838 // The displayRect gives the rectangle reserved for the video.
839 // e.g. part of the video may be clipped within the displayRect.
840 void MHIContext::DrawVideo(const QRect &videoRect, const QRect &dispRect)
841 {
842  // tell the video player to resize the video stream
843  if (m_parent->GetNVP())
844  {
845  QRect vidRect;
846  if (videoRect != QRect(QPoint(0,0),QSize(StdDisplayWidth,StdDisplayHeight)))
847  {
848  vidRect = ScaleVideo(videoRect);
849  vidRect.setWidth(Roundup(vidRect.width(), 2));
850  vidRect.setHeight(Roundup(vidRect.height(), 2));
851  }
852  m_parent->GetNVP()->SetVideoResize(vidRect);
853  }
854 
855  m_videoDisplayRect = Scale(dispRect);
856 
857  // Mark all existing items in the display stack as under the video
858  QMutexLocker locker(&m_display_lock);
859  auto it = m_display.begin();
860  for (; it != m_display.end(); ++it)
861  {
862  (*it)->m_bUnder = true;
863  }
864 }
865 
866 // Caller must hold m_channelMutex
868 {
869  MSqlQuery query(MSqlQuery::InitCon());
870  query.prepare(
871  "SELECT networkid, serviceid, transportid, chanid "
872  "FROM channel, dtv_multiplex "
873  "WHERE channel.deleted IS NULL "
874  " AND channel.mplexid = dtv_multiplex.mplexid "
875  " AND channel.sourceid = dtv_multiplex.sourceid "
876  " AND channel.sourceid = :SOURCEID ;" );
877  query.bindValue(":SOURCEID", m_currentSource);
878  if (!query.exec())
879  {
880  MythDB::DBError("MHIContext::LoadChannelCache", query);
881  return false;
882  }
883  if (!query.isActive())
884  return false;
885  while (query.next())
886  {
887  int nid = query.value(0).toInt();
888  int sid = query.value(1).toInt();
889  int tid = query.value(2).toInt();
890  int cid = query.value(3).toInt();
891  m_channelCache.insertMulti( Key_t(nid, sid), Val_t(tid, cid) );
892  }
893  return true;
894 }
895 
896 // Tuning. Get the index corresponding to a given channel.
897 // The format of the service is dvb://netID.[transPortID].serviceID
898 // where the IDs are in hex.
899 // or rec://svc/lcn/N where N is the "logical channel number"
900 // i.e. the Freeview channel.
901 // Returns -1 if it cannot find it.
902 int MHIContext::GetChannelIndex(const QString &str)
903 {
904  int nResult = -1;
905 
906  do
907  {
908  if (str.startsWith("dvb://"))
909  {
910  QStringList list = str.mid(6).split('.');
911  if (list.size() != 3)
912  break; // Malformed.
913  // The various fields are expressed in hexadecimal.
914  // Convert them to decimal for the DB.
915  bool ok;
916  int netID = list[0].toInt(&ok, 16);
917  if (!ok)
918  break;
919  int transportID = !list[1].isEmpty() ? list[1].toInt(&ok, 16) : -1;
920  if (!ok)
921  break;
922  int serviceID = list[2].toInt(&ok, 16);
923  if (!ok)
924  break;
925 
926  QMutexLocker locker(&m_channelMutex);
927  if (m_channelCache.isEmpty())
929 
930  ChannelCache_t::const_iterator it = m_channelCache.find(
931  Key_t(netID,serviceID) );
932  if (it == m_channelCache.end())
933  break;
934  if (transportID < 0)
935  nResult = Cid(it);
936  else do
937  {
938  if (Tid(it) == transportID)
939  {
940  nResult = Cid(it);
941  break;
942  }
943  }
944  while (++it != m_channelCache.end());
945  }
946  else if (str.startsWith("rec://svc/lcn/"))
947  {
948  // I haven't seen this yet so this is untested.
949  bool ok;
950  int channelNo = str.mid(14).toInt(&ok); // Decimal integer
951  if (!ok)
952  break;
953  MSqlQuery query(MSqlQuery::InitCon());
954  query.prepare("SELECT chanid "
955  "FROM channel "
956  "WHERE deleted IS NULL AND "
957  " channum = :CHAN AND "
958  " channel.sourceid = :SOURCEID");
959  query.bindValue(":CHAN", channelNo);
960  query.bindValue(":SOURCEID", m_currentSource);
961  if (query.exec() && query.isActive() && query.next())
962  nResult = query.value(0).toInt();
963  }
964  else if (str == "rec://svc/cur")
966  else if (str == "rec://svc/def")
967  nResult = m_currentChannel;
968  else
969  {
970  LOG(VB_GENERAL, LOG_WARNING,
971  QString("[mhi] GetChannelIndex -- Unrecognized URL %1")
972  .arg(str));
973  }
974  }
975  while (false);
976 
977  LOG(VB_MHEG, LOG_INFO, QString("[mhi] GetChannelIndex %1 => %2")
978  .arg(str).arg(nResult));
979  return nResult;
980 
981 }
982 
983 // Get netId etc from the channel index. This is the inverse of GetChannelIndex.
984 bool MHIContext::GetServiceInfo(int channelId, int &netId, int &origNetId,
985  int &transportId, int &serviceId)
986 {
987  QMutexLocker locker(&m_channelMutex);
988  if (m_channelCache.isEmpty())
990 
991  for ( ChannelCache_t::const_iterator it = m_channelCache.begin();
992  it != m_channelCache.end(); ++it)
993  {
994  if (Cid(it) == channelId)
995  {
996  transportId = Tid(it);
997  netId = Nid(it);
998  origNetId = netId; // We don't have this in the database.
999  serviceId = Sid(it);
1000  LOG(VB_MHEG, LOG_INFO, QString("[mhi] GetServiceInfo %1 => NID=%2 TID=%3 SID=%4")
1001  .arg(channelId).arg(netId).arg(transportId).arg(serviceId));
1002  return true;
1003  }
1004  }
1005 
1006  LOG(VB_MHEG, LOG_WARNING, QString("[mhi] GetServiceInfo %1 failed").arg(channelId));
1007  return false;
1008 }
1009 
1010 bool MHIContext::TuneTo(int channel, int tuneinfo)
1011 {
1012  if (!m_isLive)
1013  {
1014  LOG(VB_MHEG, LOG_WARNING, QString("[mhi] Can't TuneTo %1 0x%2 while not live")
1015  .arg(channel).arg(tuneinfo,0,16));
1016  return false; // Can't tune if this is a recording.
1017  }
1018 
1019  LOG(VB_GENERAL, LOG_INFO, QString("[mhi] TuneTo %1 0x%2")
1020  .arg(channel).arg(tuneinfo,0,16));
1021  m_tuneinfo.append(tuneinfo);
1022 
1023  // Post an event requesting a channel change.
1024  MythEvent me(QString("NETWORK_CONTROL CHANID %1").arg(channel));
1025  gCoreContext->dispatch(me);
1026  // Reset the NBI version here to prevent a reboot.
1027  QMutexLocker locker(&m_dsmccLock);
1029  m_nbiData.resize(0);
1030  return true;
1031 }
1032 
1033 
1034 // Begin playing the specified stream
1035 bool MHIContext::BeginStream(const QString &stream, MHStream *notify)
1036 {
1037  LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginStream %1 0x%2")
1038  .arg(stream).arg((quintptr)notify,0,16));
1039 
1040  m_audioTag = -1;
1041  m_videoTag = -1;
1042  m_notify = notify;
1043 
1044  if (stream.startsWith("http://") || stream.startsWith("https://"))
1045  {
1046  m_currentStream = -1;
1047 
1048  // The url is sometimes only http:// during stream startup
1049  if (QUrl(stream).authority().isEmpty())
1050  return false;
1051 
1052  return m_parent->GetNVP()->SetStream(stream);
1053  }
1054 
1055  int chan = GetChannelIndex(stream);
1056  if (chan < 0)
1057  return false;
1058  if (VERBOSE_LEVEL_CHECK(VB_MHEG, LOG_ANY))
1059  {
1060  int netId;
1061  int origNetId;
1062  int transportId;
1063  int serviceId;
1064  GetServiceInfo(chan, netId, origNetId, transportId, serviceId);
1065  }
1066 
1067  if (chan != m_currentStream)
1068  {
1069  // We have to tune to the channel where the stream is to be found.
1070  // Because the audio and video are both components of an MHEG stream
1071  // they will both be on the same channel.
1072  m_currentStream = chan;
1074  }
1075 
1076  return true;
1077 }
1078 
1080 {
1081  LOG(VB_MHEG, LOG_INFO, QString("[mhi] EndStream 0x%1")
1082  .arg((quintptr)m_notify,0,16) );
1083 
1084  m_notify = nullptr;
1085  (void)m_parent->GetNVP()->SetStream(QString());
1086 }
1087 
1088 // Callback from MythPlayer when a stream starts or stops
1089 bool MHIContext::StreamStarted(bool bStarted)
1090 {
1091  if (!m_engine || !m_notify)
1092  return false;
1093 
1094  LOG(VB_MHEG, LOG_INFO, QString("[mhi] Stream 0x%1 %2")
1095  .arg((quintptr)m_notify,0,16).arg(bStarted ? "started" : "stopped"));
1096 
1097  QMutexLocker locker(&m_runLock);
1098  m_engine->StreamStarted(m_notify, bStarted);
1099  if (!bStarted)
1100  m_notify = nullptr;
1101  return m_currentStream == -1; // Return true if it's an http stream
1102 }
1103 
1104 // Begin playing audio
1106 {
1107  LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginAudio %1").arg(tag));
1108 
1109  if (tag < 0)
1110  return true; // Leave it at the default.
1111 
1112  m_audioTag = tag;
1113  if (m_parent->GetNVP())
1114  return m_parent->GetNVP()->SetAudioByComponentTag(tag);
1115  return false;
1116  }
1117 
1118 // Stop playing audio
1120 {
1121  // Do nothing at the moment.
1122 }
1123 
1124 // Begin displaying video from the specified stream
1126 {
1127  LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginVideo %1").arg(tag));
1128 
1129  if (tag < 0)
1130  return true; // Leave it at the default.
1131 
1132  m_videoTag = tag;
1133  if (m_parent->GetNVP())
1134  return m_parent->GetNVP()->SetVideoByComponentTag(tag);
1135  return false;
1136 }
1137 
1138  // Stop displaying video
1140 {
1141  // Do nothing at the moment.
1142 }
1143 
1144 // Get current stream position, -1 if unknown
1146 {
1147  return m_parent->GetNVP() ? m_parent->GetNVP()->GetStreamPos() : -1;
1148 }
1149 
1150 // Get current stream size, -1 if unknown
1152 {
1153  return m_parent->GetNVP() ? m_parent->GetNVP()->GetStreamMaxPos() : -1;
1154 }
1155 
1156 // Set current stream position
1158 {
1159  return m_parent->GetNVP() ? m_parent->GetNVP()->SetStreamPos(pos) : -1;
1160 }
1161 
1162 // Play or pause a stream
1163 void MHIContext::StreamPlay(bool play)
1164 {
1165  if (m_parent->GetNVP())
1166  m_parent->GetNVP()->StreamPlay(play);
1167 }
1168 
1169 // Create a new object to draw dynamic line art.
1171  bool isBoxed, MHRgba lineColour, MHRgba fillColour)
1172 {
1173  return new MHIDLA(this, isBoxed, lineColour, fillColour);
1174 }
1175 
1176 // Create a new object to draw text.
1178 {
1179  return new MHIText(this);
1180 }
1181 
1182 // Create a new object to draw bitmaps.
1184 {
1185  return new MHIBitmap(this, tiled);
1186 }
1187 
1188 // Draw a rectangle. This is complicated if we want to get transparency right.
1189 void MHIContext::DrawRect(int xPos, int yPos, int width, int height,
1190  MHRgba colour)
1191 {
1192  if (colour.alpha() == 0 || height == 0 || width == 0)
1193  return; // Fully transparent
1194 
1195  QImage qImage(width, height, QImage::Format_ARGB32);
1196  qImage.fill(qRgba(colour.red(), colour.green(), colour.blue(), colour.alpha()));
1197 
1198  AddToDisplay(qImage, QRect(xPos, yPos, width, height));
1199 }
1200 
1201 // Draw an image at the specified position.
1202 // Generally the whole of the image is drawn but sometimes the
1203 // image may be clipped. x and y define the origin of the bitmap
1204 // and usually that will be the same as the origin of the bounding
1205 // box (clipRect).
1206 void MHIContext::DrawImage(int x, int y, const QRect &clipRect,
1207  const QImage &qImage, bool bScaled, bool bUnder)
1208 {
1209  if (qImage.isNull())
1210  return;
1211 
1212  QRect imageRect(x, y, qImage.width(), qImage.height());
1213  QRect displayRect = clipRect & imageRect;
1214 
1215  if (bScaled || displayRect == imageRect) // No clipping required
1216  {
1217  AddToDisplay(qImage, displayRect, bUnder);
1218  }
1219  else if (!displayRect.isEmpty())
1220  { // We must clip the image.
1221  QImage clipped = qImage.copy(displayRect.translated(-x, -y));
1222  AddToDisplay(clipped, displayRect, bUnder);
1223  }
1224  // Otherwise draw nothing.
1225 }
1226 
1227 // Fill in the background. This is only called if there is some area of
1228 // the screen that is not covered with other visibles.
1229 void MHIContext::DrawBackground(const QRegion &reg)
1230 {
1231  if (reg.isEmpty())
1232  return;
1233 
1234  QRect bounds = reg.boundingRect();
1235  DrawRect(bounds.x(), bounds.y(), bounds.width(), bounds.height(),
1236  MHRgba(0, 0, 0, 255)/* black. */);
1237 }
1238 
1239 void MHIText::Draw(int x, int y)
1240 {
1241  m_parent->DrawImage(x, y, QRect(x, y, m_width, m_height), m_image);
1242 }
1243 
1244 void MHIText::SetSize(int width, int height)
1245 {
1246  m_width = width;
1247  m_height = height;
1248 }
1249 
1250 void MHIText::SetFont(int size, bool isBold, bool isItalic)
1251 {
1252  m_fontsize = size;
1253  m_fontItalic = isItalic;
1254  m_fontBold = isBold;
1255  // TODO: Only the size is currently used.
1256  // Bold and Italic are currently ignored.
1257 }
1258 
1259 // FT sizes are in 26.6 fixed point form
1260 const int kShift = 6;
1261 static inline FT_F26Dot6 Point2FT(int pt)
1262 {
1263  return pt << kShift;
1264 }
1265 
1266 static inline int FT2Point(FT_F26Dot6 fp)
1267 {
1268  return (fp + (1<<(kShift-1))) >> kShift;
1269 }
1270 
1271 // Return the bounding rectangle for a piece of text drawn in the
1272 // current font. If maxSize is non-negative it sets strLen to the
1273 // number of characters that will fit in the space and returns the
1274 // bounds for those characters.
1275 // N.B. The box is relative to the origin so the y co-ordinate will
1276 // be negative. It's also possible that the x co-ordinate could be
1277 // negative for slanted fonts but that doesn't currently happen.
1278 QRect MHIText::GetBounds(const QString &str, int &strLen, int maxSize)
1279 {
1280  if (!m_parent->IsFaceLoaded())
1281  return {0,0,0,0};
1282 
1283  FT_Face face = m_parent->GetFontFace();
1284  FT_Error error = FT_Set_Char_Size(face, 0, Point2FT(m_fontsize),
1286  if (error)
1287  return {0,0,0,0};
1288 
1289  int maxAscent = face->size->metrics.ascender;
1290  int maxDescent = -face->size->metrics.descender;
1291  int width = 0;
1292  FT_Bool useKerning = FT_HAS_KERNING(face);
1293  FT_UInt previous = 0;
1294 
1295  for (int n = 0; n < strLen; n++)
1296  {
1297  QChar ch = str.at(n);
1298  FT_UInt glyphIndex = FT_Get_Char_Index(face, ch.unicode());
1299 
1300  if (glyphIndex == 0)
1301  {
1302  LOG(VB_MHEG, LOG_INFO, QString("[mhi] Unknown glyph 0x%1")
1303  .arg(ch.unicode(),0,16));
1304  previous = 0;
1305  continue;
1306  }
1307 
1308  int kerning = 0;
1309 
1310  if (useKerning && previous != 0)
1311  {
1312  FT_Vector delta;
1313  FT_Get_Kerning(face, previous, glyphIndex,
1314  FT_KERNING_DEFAULT, &delta);
1315  kerning = delta.x;
1316  }
1317 
1318  error = FT_Load_Glyph(face, glyphIndex, 0); // Don't need to render.
1319 
1320  if (error)
1321  continue; // ignore errors.
1322 
1323  FT_GlyphSlot slot = face->glyph; /* a small shortcut */
1324  FT_Pos advance = slot->metrics.horiAdvance + kerning;
1325 
1326  if (maxSize >= 0)
1327  {
1328  if (FT2Point(width + advance) > maxSize)
1329  {
1330  // There isn't enough space for this character.
1331  strLen = n;
1332  break;
1333  }
1334  }
1335  // Calculate the ascent and descent of this glyph.
1336  int descent = slot->metrics.height - slot->metrics.horiBearingY;
1337 
1338  if (slot->metrics.horiBearingY > maxAscent)
1339  maxAscent = slot->metrics.horiBearingY;
1340 
1341  if (descent > maxDescent)
1342  maxDescent = descent;
1343 
1344  width += advance;
1345  previous = glyphIndex;
1346  }
1347 
1348  return {0, -FT2Point(maxAscent), FT2Point(width), FT2Point(maxAscent + maxDescent)};
1349 }
1350 
1351 // Reset the image and fill it with transparent ink.
1352 // The UK MHEG profile says that we should consider the background
1353 // as paper and the text as ink. We have to consider these as two
1354 // different layers. The background is drawn separately as a rectangle.
1355 void MHIText::Clear(void)
1356 {
1357  m_image = QImage(m_width, m_height, QImage::Format_ARGB32);
1358  // QImage::fill doesn't set the alpha buffer.
1359  for (int i = 0; i < m_height; i++)
1360  {
1361  for (int j = 0; j < m_width; j++)
1362  {
1363  m_image.setPixel(j, i, qRgba(0, 0, 0, 0));
1364  }
1365  }
1366 }
1367 
1368 // Draw a line of text in the given position within the image.
1369 // It would be nice to be able to use TTFFont for this but it doesn't provide
1370 // what we want.
1371 void MHIText::AddText(int x, int y, const QString &str, MHRgba colour)
1372 {
1373  if (!m_parent->IsFaceLoaded()) return;
1374  FT_Face face = m_parent->GetFontFace();
1375  FT_Error error;
1376 
1377  FT_Set_Char_Size(face, 0, Point2FT(m_fontsize),
1379 
1380  // X positions are computed to 64ths and rounded.
1381  // Y positions are in pixels
1382  int posX = Point2FT(x);
1383  int pixelY = y;
1384  FT_Bool useKerning = FT_HAS_KERNING(face);
1385  FT_UInt previous = 0;
1386 
1387  int len = str.length();
1388  for (int n = 0; n < len; n++)
1389  {
1390  // Load the glyph.
1391  QChar ch = str[n];
1392  FT_UInt glyphIndex = FT_Get_Char_Index(face, ch.unicode());
1393  if (glyphIndex == 0)
1394  {
1395  previous = 0;
1396  continue;
1397  }
1398 
1399  if (useKerning && previous != 0)
1400  {
1401  FT_Vector delta;
1402  FT_Get_Kerning(face, previous, glyphIndex,
1403  FT_KERNING_DEFAULT, &delta);
1404  posX += delta.x;
1405  }
1406  error = FT_Load_Glyph(face, glyphIndex, FT_LOAD_RENDER);
1407 
1408  if (error)
1409  continue; // ignore errors
1410 
1411  FT_GlyphSlot slot = face->glyph;
1412  if (slot->format != FT_GLYPH_FORMAT_BITMAP)
1413  continue; // Problem
1414 
1415  if ((enum FT_Pixel_Mode_)slot->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY)
1416  continue;
1417 
1418  unsigned char *source = slot->bitmap.buffer;
1419  // Get the origin for the bitmap
1420  int baseX = FT2Point(posX) + slot->bitmap_left;
1421  int baseY = pixelY - slot->bitmap_top;
1422  // Copy the bitmap into the image.
1423  for (unsigned int i = 0; i < slot->bitmap.rows; i++)
1424  {
1425  for (unsigned int j = 0; j < slot->bitmap.width; j++)
1426  {
1427  int greyLevel = source[j];
1428  // Set the pixel to the specified colour but scale its
1429  // brightness according to the grey scale of the pixel.
1430  int red = colour.red();
1431  int green = colour.green();
1432  int blue = colour.blue();
1433  int alpha = colour.alpha() *
1434  greyLevel / slot->bitmap.num_grays;
1435  int xBit = j + baseX;
1436  int yBit = i + baseY;
1437 
1438  // The bits ought to be inside the bitmap but
1439  // I guess there's the possibility
1440  // that rounding might put it outside.
1441  if (xBit >= 0 && xBit < m_width &&
1442  yBit >= 0 && yBit < m_height)
1443  {
1444  m_image.setPixel(xBit, yBit,
1445  qRgba(red, green, blue, alpha));
1446  }
1447  }
1448  source += slot->bitmap.pitch;
1449  }
1450  posX += slot->advance.x;
1451  previous = glyphIndex;
1452  }
1453 }
1454 
1455 // Internal function to fill a rectangle with a colour
1456 void MHIDLA::DrawRect(int x, int y, int width, int height, MHRgba colour)
1457 {
1458  QRgb qColour = qRgba(colour.red(), colour.green(),
1459  colour.blue(), colour.alpha());
1460 
1461  // Constrain the drawing within the image.
1462  if (x < 0)
1463  {
1464  width += x;
1465  x = 0;
1466  }
1467 
1468  if (y < 0)
1469  {
1470  height += y;
1471  y = 0;
1472  }
1473 
1474  if (width <= 0 || height <= 0)
1475  return;
1476 
1477  int imageWidth = m_image.width();
1478  int imageHeight = m_image.height();
1479  if (x+width > imageWidth)
1480  width = imageWidth - x;
1481 
1482  if (y+height > imageHeight)
1483  height = imageHeight - y;
1484 
1485  if (width <= 0 || height <= 0)
1486  return;
1487 
1488  for (int i = 0; i < height; i++)
1489  {
1490  for (int j = 0; j < width; j++)
1491  {
1492  m_image.setPixel(x+j, y+i, qColour);
1493  }
1494  }
1495 }
1496 
1497 // Reset the drawing.
1499 {
1500  if (m_width == 0 || m_height == 0)
1501  {
1502  m_image = QImage();
1503  return;
1504  }
1505  m_image = QImage(m_width, m_height, QImage::Format_ARGB32);
1506  // Fill the image with "transparent colour".
1507  DrawRect(0, 0, m_width, m_height, MHRgba(0, 0, 0, 0));
1508 }
1509 
1510 void MHIDLA::Draw(int x, int y)
1511 {
1512  QRect bounds(x, y, m_width, m_height);
1513  if (m_boxed && m_lineWidth != 0)
1514  {
1515  // Draw the lines round the outside.
1516  // These don't form part of the drawing.
1517  m_parent->DrawRect(x, y, m_width,
1518  m_lineWidth, m_boxLineColour);
1519 
1520  m_parent->DrawRect(x, y + m_height - m_lineWidth,
1521  m_width, m_lineWidth, m_boxLineColour);
1522 
1523  m_parent->DrawRect(x, y + m_lineWidth,
1524  m_lineWidth, m_height - m_lineWidth * 2,
1525  m_boxLineColour);
1526 
1527  m_parent->DrawRect(x + m_width - m_lineWidth, y + m_lineWidth,
1528  m_lineWidth, m_height - m_lineWidth * 2,
1529  m_boxLineColour);
1530 
1531  // Deflate the box to within the border.
1532  bounds = QRect(bounds.x() + m_lineWidth,
1533  bounds.y() + m_lineWidth,
1534  bounds.width() - 2*m_lineWidth,
1535  bounds.height() - 2*m_lineWidth);
1536  }
1537 
1538  // Draw the background.
1539  m_parent->DrawRect(x + m_lineWidth,
1540  y + m_lineWidth,
1541  m_width - m_lineWidth * 2,
1542  m_height - m_lineWidth * 2,
1543  m_boxFillColour);
1544 
1545  // Now the drawing.
1546  m_parent->DrawImage(x, y, bounds, m_image);
1547 }
1548 
1549 // The UK MHEG profile defines exactly how transparency is supposed to work.
1550 // The drawings are made using possibly transparent ink with any crossings
1551 // just set to that ink and then the whole drawing is alpha-merged with the
1552 // underlying graphics.
1553 // DynamicLineArt no longer seems to be used in transmissions in the UK
1554 // although it appears that DrawPoly is used in New Zealand. These are
1555 // very basic implementations of the functions.
1556 
1557 // Lines
1558 void MHIDLA::DrawLine(int x1, int y1, int x2, int y2)
1559 {
1560  // Get the arguments so that the lower x comes first and the
1561  // absolute gradient is less than one.
1562  if (abs(y2-y1) > abs(x2-x1))
1563  {
1564  if (y2 > y1)
1565  DrawLineSub(y1, x1, y2, x2, true);
1566  else
1567  DrawLineSub(y2, x2, y1, x1, true);
1568  }
1569  else
1570  {
1571  if (x2 > x1)
1572  DrawLineSub(x1, y1, x2, y2, false);
1573  else
1574  DrawLineSub(x2, y2, x1, y1, false);
1575  }
1576 }
1577 
1578 // Based on the Bresenham line drawing algorithm but extended to draw
1579 // thick lines.
1580 void MHIDLA::DrawLineSub(int x1, int y1, int x2, int y2, bool swapped)
1581 {
1582  QRgb colour = qRgba(m_lineColour.red(), m_lineColour.green(),
1583  m_lineColour.blue(), m_lineColour.alpha());
1584  int dx = x2-x1;
1585  int dy = abs(y2-y1);
1586  int yStep = y2 >= y1 ? 1 : -1;
1587  // Adjust the starting positions to take account of the
1588  // line width.
1589  int error2 = dx/2;
1590  for (int k = 0; k < m_lineWidth/2; k++)
1591  {
1592  y1--;
1593  y2--;
1594  error2 += dy;
1595  if (error2*2 > dx)
1596  {
1597  error2 -= dx;
1598  x1 += yStep;
1599  x2 += yStep;
1600  }
1601  }
1602  // Main loop
1603  int y = y1;
1604  int error = dx/2;
1605  for (int x = x1; x <= x2; x++) // Include both endpoints
1606  {
1607  error2 = dx/2;
1608  int j = 0;
1609  // Inner loop also uses the Bresenham algorithm to draw lines
1610  // perpendicular to the principal direction.
1611  for (int i = 0; i < m_lineWidth; i++)
1612  {
1613  if (swapped)
1614  {
1615  if (x+j >= 0 && y+i >= 0 && y+i < m_width && x+j < m_height)
1616  m_image.setPixel(y+i, x+j, colour);
1617  }
1618  else
1619  {
1620  if (x+j >= 0 && y+i >= 0 && x+j < m_width && y+i < m_height)
1621  m_image.setPixel(x+j, y+i, colour);
1622  }
1623  error2 += dy;
1624  if (error2*2 > dx)
1625  {
1626  error2 -= dx;
1627  j -= yStep;
1628  if (i < m_lineWidth-1)
1629  {
1630  // Add another pixel in this case.
1631  if (swapped)
1632  {
1633  if (x+j >= 0 && y+i >= 0 && y+i < m_width && x+j < m_height)
1634  m_image.setPixel(y+i, x+j, colour);
1635  }
1636  else
1637  {
1638  if (x+j >= 0 && y+i >= 0 && x+j < m_width && y+i < m_height)
1639  m_image.setPixel(x+j, y+i, colour);
1640  }
1641  }
1642  }
1643  }
1644  error += dy;
1645  if (error*2 > dx)
1646  {
1647  error -= dx;
1648  y += yStep;
1649  }
1650  }
1651 }
1652 
1653 // Rectangles
1654 void MHIDLA::DrawBorderedRectangle(int x, int y, int width, int height)
1655 {
1656  if (m_lineWidth != 0)
1657  {
1658  // Draw the lines round the rectangle.
1659  DrawRect(x, y, width, m_lineWidth,
1660  m_lineColour);
1661 
1662  DrawRect(x, y + height - m_lineWidth,
1663  width, m_lineWidth,
1664  m_lineColour);
1665 
1666  DrawRect(x, y + m_lineWidth,
1667  m_lineWidth, height - m_lineWidth * 2,
1668  m_lineColour);
1669 
1670  DrawRect(x + width - m_lineWidth, y + m_lineWidth,
1671  m_lineWidth, height - m_lineWidth * 2,
1672  m_lineColour);
1673 
1674  // Fill the rectangle.
1675  DrawRect(x + m_lineWidth, y + m_lineWidth,
1676  width - m_lineWidth * 2, height - m_lineWidth * 2,
1677  m_fillColour);
1678  }
1679  else
1680  {
1681  DrawRect(x, y, width, height, m_fillColour);
1682  }
1683 }
1684 
1685 // Ovals (ellipses)
1686 void MHIDLA::DrawOval(int /*x*/, int /*y*/, int /*width*/, int /*height*/)
1687 {
1688  // Not implemented. Not actually used in practice.
1689 }
1690 
1691 // Arcs and sectors
1692 void MHIDLA::DrawArcSector(int /*x*/, int /*y*/, int /*width*/, int /*height*/,
1693  int /*start*/, int /*arc*/, bool /*isSector*/)
1694 {
1695  // Not implemented. Not actually used in practice.
1696 }
1697 
1698 // Polygons. This is used directly and also to draw other figures.
1699 // The UK profile says that MHEG should not contain concave or
1700 // self-crossing polygons but we can get the former at least as
1701 // a result of rounding when drawing ellipses.
1702 struct lineSeg { int m_yBottom, m_yTop, m_xBottom; float m_slope; };
1703 
1704 void MHIDLA::DrawPoly(bool isFilled, int nPoints, const int *xArray, const int *yArray)
1705 {
1706  if (nPoints < 2)
1707  return;
1708 
1709  if (isFilled)
1710  {
1711  QVector <lineSeg> lineArray(nPoints);
1712  int nLines = 0;
1713  // Initialise the line segment array. Include all lines
1714  // apart from horizontal. Close the polygon by starting
1715  // with the last point in the array.
1716  int lastX = xArray[nPoints-1]; // Last point
1717  int lastY = yArray[nPoints-1];
1718  int yMin = lastY;
1719  int yMax = lastY;
1720  for (int k = 0; k < nPoints; k++)
1721  {
1722  int thisX = xArray[k];
1723  int thisY = yArray[k];
1724  if (lastY != thisY)
1725  {
1726  if (lastY > thisY)
1727  {
1728  lineArray[nLines].m_yBottom = thisY;
1729  lineArray[nLines].m_yTop = lastY;
1730  lineArray[nLines].m_xBottom = thisX;
1731  }
1732  else
1733  {
1734  lineArray[nLines].m_yBottom = lastY;
1735  lineArray[nLines].m_yTop = thisY;
1736  lineArray[nLines].m_xBottom = lastX;
1737  }
1738  lineArray[nLines++].m_slope =
1739  (float)(thisX-lastX) / (float)(thisY-lastY);
1740  }
1741  if (thisY < yMin)
1742  yMin = thisY;
1743  if (thisY > yMax)
1744  yMax = thisY;
1745  lastX = thisX;
1746  lastY = thisY;
1747  }
1748 
1749  // Find the intersections of each line in the line segment array
1750  // with the scan line. Because UK MHEG says that figures should be
1751  // convex we only need to consider two intersections.
1752  QRgb fillColour = qRgba(m_fillColour.red(), m_fillColour.green(),
1753  m_fillColour.blue(), m_fillColour.alpha());
1754  for (int y = yMin; y < yMax; y++)
1755  {
1756  int crossings = 0;
1757  int xMin = 0;
1758  int xMax = 0;
1759  for (int l = 0; l < nLines; l++)
1760  {
1761  if (y >= lineArray[l].m_yBottom && y < lineArray[l].m_yTop)
1762  {
1763  int x = (int)round((float)(y - lineArray[l].m_yBottom) *
1764  lineArray[l].m_slope) + lineArray[l].m_xBottom;
1765  if (crossings == 0 || x < xMin)
1766  xMin = x;
1767  if (crossings == 0 || x > xMax)
1768  xMax = x;
1769  crossings++;
1770  }
1771  }
1772  if (crossings == 2)
1773  {
1774  for (int x = xMin; x <= xMax; x++)
1775  m_image.setPixel(x, y, fillColour);
1776  }
1777  }
1778 
1779  // Draw the boundary
1780  int lastXpoint = xArray[nPoints-1]; // Last point
1781  int lastYpoint = yArray[nPoints-1];
1782  for (int i = 0; i < nPoints; i++)
1783  {
1784  DrawLine(xArray[i], yArray[i], lastXpoint, lastYpoint);
1785  lastXpoint = xArray[i];
1786  lastYpoint = yArray[i];
1787  }
1788  }
1789  else // PolyLine - draw lines between the points but don't close it.
1790  {
1791  for (int i = 1; i < nPoints; i++)
1792  {
1793  DrawLine(xArray[i], yArray[i], xArray[i-1], yArray[i-1]);
1794  }
1795  }
1796 }
1797 
1798 MHIBitmap::MHIBitmap(MHIContext *parent, bool tiled)
1799  : m_parent(parent), m_tiled(tiled),
1800  m_copyCtx(new MythAVCopy(false))
1801 {
1802 }
1803 
1805 {
1806  delete m_copyCtx;
1807 }
1808 
1809 void MHIBitmap::Draw(int x, int y, QRect rect, bool tiled, bool bUnder)
1810 {
1811  if (tiled)
1812  {
1813  if (m_image.width() == 0 || m_image.height() == 0)
1814  return;
1815  // Construct an image the size of the bounding box and tile the
1816  // bitmap over this.
1817  QImage tiledImage = QImage(rect.width(), rect.height(),
1818  QImage::Format_ARGB32);
1819 
1820  for (int i = 0; i < rect.width(); i++)
1821  {
1822  for (int j = 0; j < rect.height(); j++)
1823  {
1824  tiledImage.setPixel(i, j, m_image.pixel(i % m_image.width(), j % m_image.height()));
1825  }
1826  }
1827  m_parent->DrawImage(rect.x(), rect.y(), rect, tiledImage, true, bUnder);
1828  }
1829  else
1830  {
1831  // NB THe BBC expects bitmaps to be scaled, not clipped
1832  m_parent->DrawImage(x, y, rect, m_image, true, bUnder);
1833  }
1834 }
1835 
1836 // Create a bitmap from PNG.
1837 void MHIBitmap::CreateFromPNG(const unsigned char *data, int length)
1838 {
1839  m_image = QImage();
1840 
1841  if (!m_image.loadFromData(data, length, "PNG"))
1842  {
1843  m_image = QImage();
1844  return;
1845  }
1846 
1847  // Assume that if it has an alpha buffer then it's partly transparent.
1848  m_opaque = ! m_image.hasAlphaChannel();
1849 }
1850 
1851 // Create a bitmap from JPEG.
1852 //virtual
1853 void MHIBitmap::CreateFromJPEG(const unsigned char *data, int length)
1854 {
1855  m_image = QImage();
1856 
1857  if (!m_image.loadFromData(data, length, "JPG"))
1858  {
1859  m_image = QImage();
1860  return;
1861  }
1862 
1863  // Assume that if it has an alpha buffer then it's partly transparent.
1864  m_opaque = ! m_image.hasAlphaChannel();
1865 }
1866 
1867 // Convert an MPEG I-frame into a bitmap. This is used as the way of
1868 // sending still pictures. We convert the image to a QImage even
1869 // though that actually means converting it from YUV and eventually
1870 // converting it back again but we do this very infrequently so the
1871 // cost is outweighed by the simplification.
1872 void MHIBitmap::CreateFromMPEG(const unsigned char *data, int length)
1873 {
1874  AVCodecContext *c = nullptr;
1875  MythAVFrame picture;
1876  AVPacket pkt;
1877  uint8_t *buff = nullptr;
1878  bool gotPicture = false;
1879  int len;
1880  m_image = QImage();
1881 
1882  // Find the mpeg2 video decoder.
1883  AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_MPEG2VIDEO);
1884  if (!codec)
1885  return;
1886  if (!picture)
1887  return;
1888 
1889  c = avcodec_alloc_context3(nullptr);
1890 
1891  if (avcodec_open2(c, codec, nullptr) < 0)
1892  goto Close;
1893 
1894  // Copy the data into AVPacket
1895  if (av_new_packet(&pkt, length) < 0)
1896  goto Close;
1897 
1898  memcpy(pkt.data, data, length);
1899  buff = pkt.data;
1900 
1901  // Get a picture from the packet. Allow 9 loops for
1902  // packet to be decoded. It should take only 2-3 loops
1903  for (int limit=0; limit<10 && !gotPicture; limit++)
1904  {
1905  len = avcodec_receive_frame(c, picture);
1906  if (len == 0)
1907  gotPicture = true;
1908  if (len == AVERROR(EAGAIN))
1909  len = 0;
1910  if (len == 0)
1911  len = avcodec_send_packet(c, &pkt);
1912  if (len == AVERROR(EAGAIN) || len == AVERROR_EOF)
1913  len = 0;
1914  if (len < 0) // Error
1915  {
1916  char error[AV_ERROR_MAX_STRING_SIZE];
1917  LOG(VB_GENERAL, LOG_ERR,
1918  QString("[mhi] video decode error: %1 (%2)")
1919  .arg(av_make_error_string(error, sizeof(error), len))
1920  .arg(gotPicture));
1921  goto Close;
1922  }
1923  else
1924  {
1925  pkt.data = nullptr;
1926  pkt.size = 0;
1927  }
1928  }
1929 
1930  if (gotPicture)
1931  {
1932  int nContentWidth = c->width;
1933  int nContentHeight = c->height;
1934  m_image = QImage(nContentWidth, nContentHeight, QImage::Format_ARGB32);
1935  m_opaque = true; // MPEG images are always opaque.
1936 
1937  AVFrame retbuf;
1938  memset(&retbuf, 0, sizeof(AVFrame));
1939 
1940  int bufflen = nContentWidth * nContentHeight * 3;
1941  auto *outputbuf = (unsigned char*)av_malloc(bufflen);
1942 
1943  av_image_fill_arrays(retbuf.data, retbuf.linesize,
1944  outputbuf, AV_PIX_FMT_RGB24,
1945  nContentWidth, nContentHeight,IMAGE_ALIGN);
1946 
1947  AVFrame *tmp = picture;
1948  m_copyCtx->Copy(&retbuf, AV_PIX_FMT_RGB24, tmp, c->pix_fmt,
1949  nContentWidth, nContentHeight);
1950 
1951  uint8_t * buf = outputbuf;
1952 
1953  // Copy the data a pixel at a time.
1954  // This should handle endianness correctly.
1955  for (int i = 0; i < nContentHeight; i++)
1956  {
1957  for (int j = 0; j < nContentWidth; j++)
1958  {
1959  int red = *buf++;
1960  int green = *buf++;
1961  int blue = *buf++;
1962  m_image.setPixel(j, i, qRgb(red, green, blue));
1963  }
1964  }
1965  av_freep(&outputbuf);
1966  }
1967 
1968 Close:
1969  pkt.data = buff;
1970  av_packet_unref(&pkt);
1971  avcodec_free_context(&c);
1972 }
1973 
1974 // Scale the bitmap. Only used for image derived from MPEG I-frames.
1975 void MHIBitmap::ScaleImage(int newWidth, int newHeight)
1976 {
1977  if (m_image.isNull())
1978  return;
1979 
1980  if (newWidth == m_image.width() && newHeight == m_image.height())
1981  return;
1982 
1983  if (newWidth <= 0 || newHeight <= 0)
1984  { // This would be a bit silly but handle it anyway.
1985  m_image = QImage();
1986  return;
1987  }
1988 
1989  m_image = m_image.scaled(newWidth, newHeight,
1990  Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
1991 }
int ScaleY(int, bool roundup=false) const
Definition: mhi.cpp:769
virtual ~MHIBitmap()
Definition: mhi.cpp:1804
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:782
void start(QThread::Priority=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:294
unsigned m_carouselId
Definition: mhi.h:389
void EndStream() override
Definition: mhi.cpp:1079
long GetStreamPos() override
Definition: mhi.cpp:1145
#define ACTION_6
Definition: mythuiactions.h:10
#define GET_KEY(a, b)
bool CheckFile(const QString &csPath, const QByteArray &cert=QByteArray())
Definition: mhegic.cpp:69
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:46
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:863
bool CheckAccess(const QString &objectPath, QByteArray &cert)
Definition: mhi.cpp:396
void run(void) override
Definition: mhi.cpp:240
int ScaleVideoY(int, bool roundup=false) const
Definition: mhi.cpp:785
bool m_face_loaded
Definition: mhi.h:206
void DrawLine(int x1, int y1, int x2, int y2) override
Definition: mhi.cpp:1558
#define FONT_HEIGHTRES
Definition: mhi.cpp:48
int Copy(VideoFrame *dst, const VideoFrame *src)
Definition: mythavutil.cpp:330
QMutex m_dsmccLock
Definition: mhi.h:185
int m_currentChannel
Definition: mhi.h:210
virtual void EngineEvent(int)=0
ChannelCache_t m_channelCache
Definition: mhi.h:229
const unsigned kTuneKeepApp
Definition: mhi.cpp:54
Data for items in the interactive television display stack.
Definition: mhi.cpp:66
void Draw(int x, int y, QRect rect, bool tiled, bool bUnder) override
Draw the completed drawing onto the display.
Definition: mhi.cpp:1809
#define round(x)
Definition: mythplayer.cpp:65
bool SetVideoByComponentTag(int tag)
Selects the video stream using the DVB component tag.
bool BeginVideo(int tag) override
Begin displaying video.
Definition: mhi.cpp:1125
void ClearQueue(void)
Definition: mhi.cpp:147
static void error(const char *str,...)
Definition: vbi.c:42
MHStream * m_notify
Definition: mhi.h:189
#define ACTION_0
Definition: mythuiactions.h:4
MThread * m_engineThread
Definition: mhi.h:208
int DecrRef(void) override
Decrements reference count and deletes on 0.
Definition: mythimage.cpp:55
#define ACTION_2
Definition: mythuiactions.h:6
Image widget, displays a single image or multiple images in sequence.
Definition: mythuiimage.h:97
void Restart(int chanid, int sourceid, bool isLive)
Restart the MHEG engine.
Definition: mhi.cpp:173
static int Tid(ChannelCache_t::const_iterator it)
Definition: mhi.h:231
int m_currentStream
Definition: mhi.h:211
bool m_bUnder
Definition: mhi.cpp:72
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:125
const int kShift
Definition: mhi.cpp:1260
static int Nid(ChannelCache_t::const_iterator it)
Definition: mhi.h:233
bool wait(unsigned long time=ULONG_MAX)
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:311
Object for displaying Dynamic Line Art.
Definition: mhi.h:315
void DrawRect(int x, int y, int width, int height, MHRgba colour)
Definition: mhi.cpp:1456
MythAVCopy Copy AVFrame<->frame, performing the required conversion if any.
Definition: mythavutil.h:114
#define ACTION_SEEKFFWD
Definition: tv_actions.h:43
bool GetServiceInfo(int channelId, int &netId, int &origNetId, int &transportId, int &serviceId) override
Get netId etc from the channel index.
Definition: mhi.cpp:984
int m_keyProfile
Definition: mhi.h:193
Data for the queued DSMCC tables.
Definition: mhi.h:369
#define ACTION_UP
Definition: mythuiactions.h:16
Definition: dsmcc.h:75
int GetChannelIndex(const QString &str) override
Definition: mhi.cpp:902
struct AVFrame AVFrame
virtual void GenerateUserAction(int nCode)=0
int Roundup(int n, int r)
Definition: mhi.cpp:831
bool m_stop
Definition: mhi.h:199
bool LoadFont(const QString &name)
Definition: mhi.cpp:95
#define ACTION_MENUBLUE
Definition: tv_actions.h:80
const unsigned kTuneCarId
Definition: mhi.cpp:55
EResult GetFile(const QString &csPath, QByteArray &data, const QByteArray &cert=QByteArray())
Definition: mhegic.cpp:103
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
void DeleteAllChildren(void)
Delete all child widgets.
Definition: mythuitype.cpp:204
#define ACTION_MENUGREEN
Definition: tv_actions.h:78
void DrawArcSector(int x, int y, int width, int height, int start, int arc, bool isSector) override
Definition: mhi.cpp:1692
bool m_isLive
Definition: mhi.h:212
void CreateFromPNG(const unsigned char *data, int length) override
Create bitmap from PNG.
Definition: mhi.cpp:1837
#define ACTION_RIGHT
Definition: mythuiactions.h:19
void DrawVideo(const QRect &videoRect, const QRect &dispRect) override
Definition: mhi.cpp:840
QWaitCondition m_engine_wait
Definition: mhi.h:198
QMutex m_display_lock
Definition: mhi.h:200
bool BeginStream(const QString &str, MHStream *notify) override
Begin playing the specified stream.
Definition: mhi.cpp:1035
QRect m_displayRect
Definition: mhi.h:223
static guint32 * tmp
Definition: goom_core.c:35
void enqueue(T d)
Adds item to the back of the list. O(1).
Definition: mythdeque.h:42
static int Sid(ChannelCache_t::const_iterator it)
Definition: mhi.h:234
long GetStreamMaxPos() override
Definition: mhi.cpp:1151
MHIBitmap(const MHIBitmap &)=delete
void DrawImage(int x, int y, const QRect &rect, const QImage &image, bool bScaled=false, bool bUnder=false)
Definition: mhi.cpp:1206
int m_length
Definition: mhi.h:387
#define ACTION_STOP
Definition: tv_actions.h:8
MHKeyLookup()
Definition: mhi.cpp:550
QList< int > m_tuneinfo
Definition: mhi.h:217
QString GetConfDir(void)
Definition: mythdirs.cpp:224
#define ACTION_SELECT
Definition: mythuiactions.h:15
QVariant value(int i) const
Definition: mythdbcon.h:198
unsigned char * m_data
Definition: mhi.h:386
long SetStreamPos(long) override
Definition: mhi.cpp:1157
void SetInputRegister(int num) override
Definition: mhi.cpp:650
int ScaleX(int, bool roundup=false) const
Definition: mhi.cpp:764
bool BeginAudio(int tag) override
Begin playing audio.
Definition: mhi.cpp:1105
QPair< QString, int > key_t
Definition: mhi.cpp:512
void DrawBackground(const QRegion &reg) override
Definition: mhi.cpp:1229
virtual void SetVisible(bool visible)
Contains various utility functions for interactive television.
Definition: mhi.h:50
void NetworkBootRequested(void)
Definition: mhi.cpp:340
#define ACTION_8
Definition: mythuiactions.h:12
void CreateFromJPEG(const unsigned char *data, int length) override
Create bitmap from JPEG.
Definition: mhi.cpp:1853
QRect m_videoDisplayRect
Definition: mhi.h:222
This class is used as a container for messages.
Definition: mythevent.h:16
#define FONT_TO_USE
Definition: mhi.cpp:49
VERBOSE_PREAMBLE false
Definition: verbosedefs.h:85
int m_dataBroadcastId
Definition: mhi.h:390
void QueueDSMCCPacket(unsigned char *data, int length, int componentTag, unsigned carouselId, int dataBroadcastId)
Definition: mhi.cpp:295
void DrawBorderedRectangle(int x, int y, int width, int height) override
Definition: mhi.cpp:1654
void AddText(int x, int y, const QString &, MHRgba colour) override
Definition: mhi.cpp:1371
void DrawPoly(bool isFilled, int nPoints, const int *xArray, const int *yArray) override
Definition: mhi.cpp:1704
static int x2
Definition: mythsocket.cpp:61
void StreamPlay(bool play=true)
#define NBI_VERSION_UNSET
Definition: mhi.h:45
#define ACTION_PLAY
Definition: tv_actions.h:30
#define ACTION_MENUYELLOW
Definition: tv_actions.h:79
#define VERBOSE_LEVEL_CHECK(_MASK_, _LEVEL_)
Definition: mythlogging.h:24
QHash< key_t, int > m_map
Definition: mhi.cpp:524
virtual void DrawDisplay(QRegion toDraw)=0
MythDeque< int > m_keyQueue
Definition: mhi.h:192
virtual int RunAll(void)=0
#define ACTION_1
Definition: mythuiactions.h:5
QPair< int, int > Key_t
Definition: mhi.h:227
T dequeue()
Removes item from front of list and returns a copy. O(1).
Definition: mythdeque.h:32
bool m_opaque
Definition: mhi.h:308
QString GetShareDir(void)
Definition: mythdirs.cpp:222
bool SetStream(const QString &)
bool isActive(void) const
Definition: mythdbcon.h:204
#define ACTION_PAUSE
Definition: tv_actions.h:15
int m_yTop
Definition: mhi.cpp:1702
QMutex m_channelMutex
Definition: mhi.h:230
bool StreamStarted(bool bStarted=true)
Definition: mhi.cpp:1089
Wrapper around QRect allowing us to handle percentage and other relative values for areas in mythui.
Definition: mythrect.h:17
MHIContext(InteractiveTV *parent)
Definition: mhi.cpp:75
uint m_lastNbiVersion
Definition: mhi.h:219
#define ACTION_7
Definition: mythuiactions.h:11
void DrawLineSub(int x1, int y1, int x2, int y2, bool swapped)
Definition: mhi.cpp:1580
virtual ~MHIContext()
Definition: mhi.cpp:125
virtual void StreamStarted(MHStream *, bool bStarted=true)=0
Dsmcc * m_dsmcc
Definition: mhi.h:184
int ScaleVideoX(int, bool roundup=false) const
Definition: mhi.cpp:780
unsigned int uint
Definition: compat.h:140
long SetStreamPos(long)
bool GetCarouselData(QString objectPath, QByteArray &result) override
Definition: mhi.cpp:436
static MSqlQueryInfo InitCon(ConnectionReuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
int GetDSMCCObject(QStringList &objectPath, QByteArray &result)
Definition: dsmcc.cpp:550
static const int StdDisplayHeight
Definition: mhi.h:169
InteractiveTV * m_parent
Definition: mhi.h:181
FT_Face m_face
Definition: mhi.h:205
long GetStreamPos()
static int Cid(ChannelCache_t::const_iterator it)
Definition: mhi.h:232
MHBitmapDisplay * CreateBitmap(bool tiled) override
Definition: mhi.cpp:1183
const unsigned kTuneQuietly
Definition: mhi.cpp:53
void * av_malloc(unsigned int size)
int m_audioTag
Definition: mhi.h:215
QPair< int, int > Val_t
Definition: mhi.h:226
void dispatch(const MythEvent &event)
void Clear(void) override
Clear the drawing.
Definition: mhi.cpp:1498
#define ACTION_MENURED
Definition: tv_actions.h:77
MythPlayer * GetNVP(void)
Definition: interactivetv.h:46
#define ACTION_MENUTEXT
Definition: tv_actions.h:82
QRect m_videoRect
Definition: mhi.h:222
#define ACTION_PLAYBACK
Definition: tv_actions.h:7
QRect ScaleVideo(const QRect &r) const
Definition: mhi.cpp:790
void Reset()
Definition: dsmcc.cpp:540
void Draw(int x, int y) override
Draw the completed drawing onto the display.
Definition: mhi.cpp:1510
vector< unsigned char > m_nbiData
Definition: mhi.h:220
int m_x
Definition: mhi.cpp:70
int GetNumSetting(const QString &key, int defaultval=0)
MHEG * MHCreateEngine(MHContext *context)
Definition: Engine.cpp:43
virtual void SetBooting()=0
MHDLADisplay * CreateDynamicLineArt(bool isBoxed, MHRgba lineColour, MHRgba fillColour) override
Creation functions for various visibles.
Definition: mhi.cpp:1170
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:807
int alpha() const
Definition: freemheg.h:85
void ProcessSection(const unsigned char *data, int length, int componentTag, unsigned carouselId, int dataBroadcastId)
Definition: dsmcc.cpp:447
int Find(const QString &name, int reg) const
Definition: mhi.cpp:517
void Clear(void) override
Definition: mhi.cpp:1355
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
static FT_F26Dot6 Point2FT(int pt)
Definition: mhi.cpp:1261
Definition: mhi.h:238
MHEG * m_engine
Definition: mhi.h:195
int green() const
Definition: freemheg.h:83
void DrawRect(int xPos, int yPos, int width, int height, MHRgba colour) override
Additional drawing functions.
Definition: mhi.cpp:1189
QString GetFontsDir(void)
Definition: mythdirs.cpp:309
QImage m_image
Definition: mhi.h:307
#define ACTION_4
Definition: mythuiactions.h:8
void SetFont(int size, bool isBold, bool isItalic) override
Definition: mhi.cpp:1250
static const int StdDisplayWidth
Definition: mhi.h:168
#define ACTION_9
Definition: mythuiactions.h:13
QRect Scale(const QRect &r) const
Definition: mhi.cpp:774
bool OfferKey(const QString &key)
Definition: mhi.cpp:608
void StreamPlay(bool) override
Definition: mhi.cpp:1163
#define ACTION_JUMPFFWD
Definition: tv_actions.h:44
void CreateFromMPEG(const unsigned char *data, int length) override
Create bitmap from single I frame MPEG.
Definition: mhi.cpp:1872
static bool ft_loaded
Definition: mhi.cpp:44
#define ACTION_SEEKRWND
Definition: tv_actions.h:42
MythAVCopy * m_copyCtx
Definition: mhi.h:309
MHIContext * m_parent
Definition: mhi.h:305
MythDeque< DSMCCPacket * > m_dsmccQueue
Definition: mhi.h:186
void SetSize(int width, int height) override
Definition: mhi.cpp:1244
int m_videoTag
Definition: mhi.h:216
MHInteractionChannel m_ic
Definition: mhi.h:188
long GetStreamMaxPos()
Object for drawing bitmaps.
Definition: mhi.h:267
list< MHIImageData * > m_display
Definition: mhi.h:203
void GetInitialStreams(int &audioTag, int &videoTag)
Get the initial component tags.
Definition: mhi.cpp:743
void ScaleImage(int newWidth, int newHeight) override
Scale the bitmap. Only used for image derived from MPEG I-frames.
Definition: mhi.cpp:1975
bool m_updated
Definition: mhi.h:201
void RequireRedraw(const QRegion &region) override
An area of the screen/image needs to be redrawn.
Definition: mhi.cpp:753
QImage m_image
Definition: mhi.cpp:69
MythAVFrame little utility class that act as a safe way to allocate an AVFrame which can then be allo...
Definition: mythavutil.h:42
bool TuneTo(int channel, int tuneinfo) override
Definition: mhi.cpp:1010
QMutex m_keyLock
Definition: mhi.h:191
int red() const
Definition: freemheg.h:82
#define ACTION_JUMPRWND
Definition: tv_actions.h:45
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
#define ACTION_LEFT
Definition: mythuiactions.h:18
MHTextDisplay * CreateText(void) override
Definition: mhi.cpp:1177
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:179
void UpdateOSD(InteractiveScreen *osdWindow, MythPainter *osdPainter)
Update the display.
Definition: mhi.cpp:665
void Assign(const QImage &img)
Definition: mythimage.cpp:80
#define ACTION_5
Definition: mythuiactions.h:9
#define ACTION_TEXTEXIT
Definition: tv_actions.h:81
int m_currentSource
Definition: mhi.h:213
#define ACTION_3
Definition: mythuiactions.h:7
bool LoadChannelCache()
Definition: mhi.cpp:867
void OptimiseDisplayedArea(void)
QRect GetBounds(const QString &str, int &strLen, int maxSize=-1) override
Definition: mhi.cpp:1278
int m_componentTag
Definition: mhi.h:388
void SetNetBootInfo(const unsigned char *data, uint length)
Definition: mhi.cpp:315
bool SetAudioByComponentTag(int tag)
Selects the audio stream using the DVB component tag.
void StopVideo() override
Stop displaying video.
Definition: mhi.cpp:1139
MythImage * GetFormatImage()
Returns a blank reference counted image in the format required for the Draw functions for this painte...
void ProcessDSMCCQueue(void)
Definition: mhi.cpp:276
void AddToDisplay(const QImage &image, const QRect &rect, bool bUnder=false)
Definition: mhi.cpp:796
void SetVideoResize(const QRect &videoRect)
void Draw(int x, int y) override
Definition: mhi.cpp:1239
#define ACTION_MENUEPG
Definition: tv_actions.h:83
void ClearDisplay(void)
Definition: mhi.cpp:137
const unsigned kTuneCarReset
Definition: mhi.cpp:56
void key(const QString &name, int code, int r1, int r2=0, int r3=0, int r4=0, int r5=0, int r6=0, int r7=0, int r8=0, int r9=0)
Definition: mhi.cpp:527
QMutex m_runLock
Definition: mhi.h:197
int GetICStatus() override
Definition: mhi.cpp:658
#define ACTION_DOWN
Definition: mythuiactions.h:17
This is the interface between an MHEG engine and a MythTV TV object.
Definition: interactivetv.h:12
bool CheckCarouselObject(QString objectPath) override
Definition: mhi.cpp:368
static EStatus status()
Definition: mhegic.cpp:44
void Reinit(const QRect &videoRect, const QRect &dispRect, float aspect)
The display area has changed.
Definition: mhi.cpp:624
static FT_Library ft_library
Definition: mhi.cpp:45
int m_y
Definition: mhi.cpp:71
void StopAudio() override
Stop playing audio.
Definition: mhi.cpp:1119
const unsigned kTuneKeepChnl
Definition: mhi.cpp:60
int blue() const
Definition: freemheg.h:84
static int x1
Definition: mythsocket.cpp:60
bool GetDSMCCObject(const QString &objectPath, QByteArray &result)
Definition: mhi.cpp:388
#define FONT_WIDTHRES
Definition: mhi.cpp:47
void DrawOval(int x, int y, int width, int height) override
Definition: mhi.cpp:1686
static int FT2Point(FT_F26Dot6 fp)
Definition: mhi.cpp:1266
void StopEngine(void)
Stop the MHEG engine if it's running and waits until it has.
Definition: mhi.cpp:156