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