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