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 m_display.push_back(data);
810 else
811 {
812 // Replace any existing items under the video with this
813 auto it = m_display.begin();
814 while (it != m_display.end())
815 {
816 MHIImageData *old = *it;
817 if (!old->m_bUnder)
818 ++it;
819 else
820 {
821 it = m_display.erase(it);
822 delete old;
823 }
824 }
825 m_display.push_front(data);
826 }
827}
828
829inline int Roundup(int n, int r)
830{
831 // NB assumes 2's complement arithmetic
832 return n + (-n & (r - 1));
833}
834
835// The videoRect gives the size and position to which the video must be scaled.
836// The displayRect gives the rectangle reserved for the video.
837// e.g. part of the video may be clipped within the displayRect.
838void MHIContext::DrawVideo(const QRect &videoRect, const QRect &dispRect)
839{
840 // tell the video player to resize the video stream
841 if (m_parent->GetPlayer())
842 {
843 QRect vidRect;
844 if (videoRect != QRect(QPoint(0,0),QSize(kStdDisplayWidth,kStdDisplayHeight)))
845 {
846 vidRect = ScaleVideo(videoRect);
847 vidRect.setWidth(Roundup(vidRect.width(), 2));
848 vidRect.setHeight(Roundup(vidRect.height(), 2));
849 }
850 emit m_parent->GetPlayer()->ResizeForInteractiveTV(vidRect);
851 }
852
853 m_videoDisplayRect = Scale(dispRect);
854
855 // Mark all existing items in the display stack as under the video
856 QMutexLocker locker(&m_displayLock);
857 for (auto & it : m_display)
858 it->m_bUnder = true;
859}
860
861// Caller must hold m_channelMutex
863{
865 query.prepare(
866 "SELECT networkid, serviceid, transportid, chanid "
867 "FROM channel, dtv_multiplex "
868 "WHERE channel.deleted IS NULL "
869 " AND channel.mplexid = dtv_multiplex.mplexid "
870 " AND channel.sourceid = dtv_multiplex.sourceid "
871 " AND channel.sourceid = :SOURCEID ;" );
872 query.bindValue(":SOURCEID", m_currentSource);
873 if (!query.exec())
874 {
875 MythDB::DBError("MHIContext::LoadChannelCache", query);
876 return false;
877 }
878 if (!query.isActive())
879 return false;
880 while (query.next())
881 {
882 int nid = query.value(0).toInt();
883 int sid = query.value(1).toInt();
884 int tid = query.value(2).toInt();
885 int cid = query.value(3).toInt();
886 m_channelCache.insert( Key_t(nid, sid), Val_t(tid, cid) );
887 }
888 return true;
889}
890
891// Tuning. Get the index corresponding to a given channel.
892// The format of the service is dvb://netID.[transPortID].serviceID
893// where the IDs are in hex.
894// or rec://svc/lcn/N where N is the "logical channel number"
895// i.e. the Freeview channel.
896// Returns -1 if it cannot find it.
897int MHIContext::GetChannelIndex(const QString &str)
898{
899 int nResult = -1;
900
901 for (int i = 0; i < 1 ; i++) // do once
902 {
903 if (str.startsWith("dvb://"))
904 {
905 QStringList list = str.mid(6).split('.');
906 if (list.size() != 3)
907 break; // Malformed.
908 // The various fields are expressed in hexadecimal.
909 // Convert them to decimal for the DB.
910 bool ok = false;
911 int netID = list[0].toInt(&ok, 16);
912 if (!ok)
913 break;
914 int transportID = !list[1].isEmpty() ? list[1].toInt(&ok, 16) : -1;
915 if (!ok)
916 break;
917 int serviceID = list[2].toInt(&ok, 16);
918 if (!ok)
919 break;
920
921 QMutexLocker locker(&m_channelMutex);
922 if (m_channelCache.isEmpty())
924
925 ChannelCache_t::const_iterator it = m_channelCache.constFind(
926 Key_t(netID,serviceID) );
927 if (it == m_channelCache.constEnd())
928 break;
929 if (transportID < 0)
930 nResult = Cid(it);
931 else
932 {
933 for ( ; it != m_channelCache.constEnd() ; it++)
934 {
935 if (Tid(it) == transportID)
936 {
937 nResult = Cid(it);
938 break;
939 }
940 }
941 }
942 }
943 else if (str.startsWith("rec://svc/lcn/"))
944 {
945 // I haven't seen this yet so this is untested.
946 bool ok = false;
947 int channelNo = str.mid(14).toInt(&ok); // Decimal integer
948 if (!ok)
949 break;
951 query.prepare("SELECT chanid "
952 "FROM channel "
953 "WHERE deleted IS NULL AND "
954 " channum = :CHAN AND "
955 " channel.sourceid = :SOURCEID");
956 query.bindValue(":CHAN", channelNo);
957 query.bindValue(":SOURCEID", m_currentSource);
958 if (query.exec() && query.isActive() && query.next())
959 nResult = query.value(0).toInt();
960 }
961 else if (str == "rec://svc/cur")
962 {
964 }
965 else if (str == "rec://svc/def")
966 {
967 nResult = m_currentChannel;
968 }
969 else
970 {
971 LOG(VB_GENERAL, LOG_WARNING,
972 QString("[mhi] GetChannelIndex -- Unrecognized URL %1")
973 .arg(str));
974 }
975 }
976
977 LOG(VB_MHEG, LOG_INFO, QString("[mhi] GetChannelIndex %1 => %2")
978 .arg(str).arg(nResult));
979 return nResult;
980
981}
982
983// Get netId etc from the channel index. This is the inverse of GetChannelIndex.
984bool MHIContext::GetServiceInfo(int channelId, int &netId, int &origNetId,
985 int &transportId, int &serviceId)
986{
987 QMutexLocker locker(&m_channelMutex);
988 if (m_channelCache.isEmpty())
990
991 for (auto it = m_channelCache.cbegin(); it != m_channelCache.cend(); ++it)
992 {
993 if (Cid(it) == channelId)
994 {
995 transportId = Tid(it);
996 netId = Nid(it);
997 origNetId = netId; // We don't have this in the database.
998 serviceId = Sid(it);
999 LOG(VB_MHEG, LOG_INFO, QString("[mhi] GetServiceInfo %1 => NID=%2 TID=%3 SID=%4")
1000 .arg(channelId).arg(netId).arg(transportId).arg(serviceId));
1001 return true;
1002 }
1003 }
1004
1005 LOG(VB_MHEG, LOG_WARNING, QString("[mhi] GetServiceInfo %1 failed").arg(channelId));
1006 return false;
1007}
1008
1009bool MHIContext::TuneTo(int channel, int tuneinfo)
1010{
1011 if (!m_isLive)
1012 {
1013 LOG(VB_MHEG, LOG_WARNING, QString("[mhi] Can't TuneTo %1 0x%2 while not live")
1014 .arg(channel).arg(tuneinfo,0,16));
1015 return false; // Can't tune if this is a recording.
1016 }
1017
1018 LOG(VB_GENERAL, LOG_INFO, QString("[mhi] TuneTo %1 0x%2")
1019 .arg(channel).arg(tuneinfo,0,16));
1020 m_tuneInfo.append(tuneinfo);
1021
1022 // Post an event requesting a channel change.
1023 MythEvent me(QString("NETWORK_CONTROL CHANID %1").arg(channel));
1025 // Reset the NBI version here to prevent a reboot.
1026 QMutexLocker locker(&m_dsmccLock);
1028 m_nbiData.resize(0);
1029 return true;
1030}
1031
1032
1033// Begin playing the specified stream
1034bool MHIContext::BeginStream(const QString &stream, MHStream *notify)
1035{
1036 LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginStream %1 0x%2")
1037 .arg(stream).arg((quintptr)notify,0,16));
1038
1039 m_audioTag = -1;
1040 m_videoTag = -1;
1041 m_notify = notify;
1042
1043 if (stream.startsWith("http://") || stream.startsWith("https://"))
1044 {
1045 m_currentStream = -1;
1046
1047 // The url is sometimes only http:// during stream startup
1048 if (QUrl(stream).authority().isEmpty())
1049 return false;
1050
1051 emit m_parent->GetPlayer()->SetInteractiveStream(stream);
1052 return !stream.isEmpty();
1053 }
1054
1055 int chan = GetChannelIndex(stream);
1056 if (chan < 0)
1057 return false;
1058 if (VERBOSE_LEVEL_CHECK(VB_MHEG, LOG_ANY))
1059 {
1060 int netId = 0;
1061 int origNetId = 0;
1062 int transportId = 0;
1063 int serviceId = 0;
1064 GetServiceInfo(chan, netId, origNetId, transportId, serviceId);
1065 }
1066
1067 if (chan != m_currentStream)
1068 {
1069 // We have to tune to the channel where the stream is to be found.
1070 // Because the audio and video are both components of an MHEG stream
1071 // they will both be on the same channel.
1072 m_currentStream = chan;
1074 }
1075
1076 return true;
1077}
1078
1080{
1081 LOG(VB_MHEG, LOG_INFO, QString("[mhi] EndStream 0x%1")
1082 .arg((quintptr)m_notify,0,16) );
1083
1084 m_notify = nullptr;
1085 emit m_parent->GetPlayer()->SetInteractiveStream(QString());
1086}
1087
1088// Callback from MythPlayer when a stream starts or stops
1089bool MHIContext::StreamStarted(bool bStarted)
1090{
1091 if (!m_engine || !m_notify)
1092 return false;
1093
1094 LOG(VB_MHEG, LOG_INFO, QString("[mhi] Stream 0x%1 %2")
1095 .arg((quintptr)m_notify,0,16).arg(bStarted ? "started" : "stopped"));
1096
1097 QMutexLocker locker(&m_runLock);
1098 m_engine->StreamStarted(m_notify, bStarted);
1099 if (!bStarted)
1100 m_notify = nullptr;
1101 return m_currentStream == -1; // Return true if it's an http stream
1102}
1103
1104// Begin playing audio
1106{
1107 LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginAudio %1").arg(tag));
1108
1109 if (tag < 0)
1110 return true; // Leave it at the default.
1111
1112 m_audioTag = tag;
1113 if (m_parent->GetPlayer())
1114 return m_parent->GetPlayer()->SetAudioByComponentTag(tag);
1115 return false;
1116 }
1117
1118// Stop playing audio
1120{
1121 // Do nothing at the moment.
1122}
1123
1124// Begin displaying video from the specified stream
1126{
1127 LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginVideo %1").arg(tag));
1128
1129 if (tag < 0)
1130 return true; // Leave it at the default.
1131
1132 m_videoTag = tag;
1133 if (m_parent->GetPlayer())
1134 return m_parent->GetPlayer()->SetVideoByComponentTag(tag);
1135 return false;
1136}
1137
1138 // Stop displaying video
1140{
1141 // Do nothing at the moment.
1142}
1143
1144// Get current stream position, -1 if unknown
1145std::chrono::milliseconds MHIContext::GetStreamPos()
1146{
1147 return m_parent->GetPlayer() ? m_parent->GetPlayer()->GetStreamPos() : -1ms;
1148}
1149
1150// Get current stream size, -1 if unknown
1151std::chrono::milliseconds MHIContext::GetStreamMaxPos()
1152{
1153 return m_parent->GetPlayer() ? m_parent->GetPlayer()->GetStreamMaxPos() : -1ms;
1154}
1155
1156// Set current stream position
1157std::chrono::milliseconds MHIContext::SetStreamPos(std::chrono::milliseconds pos)
1158{
1159 if (m_parent->GetPlayer())
1160 emit m_parent->GetPlayer()->SetInteractiveStreamPos(pos);
1161 // Note: return value is never used
1162 return 0ms;
1163}
1164
1165// Play or pause a stream
1167{
1168 if (m_parent->GetPlayer())
1169 emit m_parent->GetPlayer()->PlayInteractiveStream(play);
1170}
1171
1172// Create a new object to draw dynamic line art.
1174 bool isBoxed, MHRgba lineColour, MHRgba fillColour)
1175{
1176 return new MHIDLA(this, isBoxed, lineColour, fillColour);
1177}
1178
1179// Create a new object to draw text.
1181{
1182 return new MHIText(this);
1183}
1184
1185// Create a new object to draw bitmaps.
1187{
1188 return new MHIBitmap(this, tiled);
1189}
1190
1191// Draw a rectangle. This is complicated if we want to get transparency right.
1192void MHIContext::DrawRect(int xPos, int yPos, int width, int height,
1193 MHRgba colour)
1194{
1195 if (colour.alpha() == 0 || height == 0 || width == 0)
1196 return; // Fully transparent
1197
1198 QImage qImage(width, height, QImage::Format_ARGB32);
1199 qImage.fill(qRgba(colour.red(), colour.green(), colour.blue(), colour.alpha()));
1200
1201 AddToDisplay(qImage, QRect(xPos, yPos, width, height));
1202}
1203
1204// Draw an image at the specified position.
1205// Generally the whole of the image is drawn but sometimes the
1206// image may be clipped. x and y define the origin of the bitmap
1207// and usually that will be the same as the origin of the bounding
1208// box (clipRect).
1209void MHIContext::DrawImage(int x, int y, const QRect clipRect,
1210 const QImage &qImage, bool bScaled, bool bUnder)
1211{
1212 if (qImage.isNull())
1213 return;
1214
1215 QRect imageRect(x, y, qImage.width(), qImage.height());
1216 QRect displayRect = clipRect & imageRect;
1217
1218 if (bScaled || displayRect == imageRect) // No clipping required
1219 {
1220 AddToDisplay(qImage, displayRect, bUnder);
1221 }
1222 else if (!displayRect.isEmpty())
1223 { // We must clip the image.
1224 QImage clipped = qImage.copy(displayRect.translated(-x, -y));
1225 AddToDisplay(clipped, displayRect, bUnder);
1226 }
1227 // Otherwise draw nothing.
1228}
1229
1230// Fill in the background. This is only called if there is some area of
1231// the screen that is not covered with other visibles.
1232void MHIContext::DrawBackground(const QRegion &reg)
1233{
1234 if (reg.isEmpty())
1235 return;
1236
1237 QRect bounds = reg.boundingRect();
1238 DrawRect(bounds.x(), bounds.y(), bounds.width(), bounds.height(),
1239 MHRgba(0, 0, 0, 255)/* black. */);
1240}
1241
1242void MHIText::Draw(int x, int y)
1243{
1244 m_parent->DrawImage(x, y, QRect(x, y, m_width, m_height), m_image);
1245}
1246
1247void MHIText::SetSize(int width, int height)
1248{
1249 m_width = width;
1250 m_height = height;
1251}
1252
1253void MHIText::SetFont(int size, bool isBold, bool isItalic)
1254{
1255 m_fontSize = size;
1256 m_fontItalic = isItalic;
1257 m_fontBold = isBold;
1258 // TODO: Only the size is currently used.
1259 // Bold and Italic are currently ignored.
1260}
1261
1262// FT sizes are in 26.6 fixed point form
1263const int kShift = 6;
1264static inline FT_F26Dot6 Point2FT(int pt)
1265{
1266 return pt << kShift;
1267}
1268
1269static inline int FT2Point(FT_F26Dot6 fp)
1270{
1271 return (fp + (1<<(kShift-1))) >> kShift;
1272}
1273
1274// Return the bounding rectangle for a piece of text drawn in the
1275// current font. If maxSize is non-negative it sets strLen to the
1276// number of characters that will fit in the space and returns the
1277// bounds for those characters.
1278// N.B. The box is relative to the origin so the y co-ordinate will
1279// be negative. It's also possible that the x co-ordinate could be
1280// negative for slanted fonts but that doesn't currently happen.
1281QRect MHIText::GetBounds(const QString &str, int &strLen, int maxSize)
1282{
1283 if (!m_parent->IsFaceLoaded())
1284 return {0,0,0,0};
1285
1286 FT_Face face = m_parent->GetFontFace();
1287 FT_Error error = FT_Set_Char_Size(face, 0, Point2FT(m_fontSize),
1289 if (error)
1290 return {0,0,0,0};
1291
1292 int maxAscent = face->size->metrics.ascender;
1293 int maxDescent = -face->size->metrics.descender;
1294 int width = 0;
1295 FT_Bool useKerning = FT_HAS_KERNING(face);
1296 FT_UInt previous = 0;
1297
1298 for (int n = 0; n < strLen; n++)
1299 {
1300 QChar ch = str.at(n);
1301 FT_UInt glyphIndex = FT_Get_Char_Index(face, ch.unicode());
1302
1303 if (glyphIndex == 0)
1304 {
1305 LOG(VB_MHEG, LOG_INFO, QString("[mhi] Unknown glyph 0x%1")
1306 .arg(static_cast<short>(ch.unicode()),0,16));
1307 previous = 0;
1308 continue;
1309 }
1310
1311 int kerning = 0;
1312
1313 if (useKerning && previous != 0)
1314 {
1315 FT_Vector delta;
1316 FT_Get_Kerning(face, previous, glyphIndex,
1317 FT_KERNING_DEFAULT, &delta);
1318 kerning = delta.x;
1319 }
1320
1321 error = FT_Load_Glyph(face, glyphIndex, 0); // Don't need to render.
1322
1323 if (error)
1324 continue; // ignore errors.
1325
1326 FT_GlyphSlot slot = face->glyph; /* a small shortcut */
1327 FT_Pos advance = slot->metrics.horiAdvance + kerning;
1328
1329 if (maxSize >= 0)
1330 {
1331 if (FT2Point(width + advance) > maxSize)
1332 {
1333 // There isn't enough space for this character.
1334 strLen = n;
1335 break;
1336 }
1337 }
1338 // Calculate the ascent and descent of this glyph.
1339 int descent = slot->metrics.height - slot->metrics.horiBearingY;
1340
1341 maxAscent = std::max<FT_Pos>(slot->metrics.horiBearingY, maxAscent);
1342
1343 maxDescent = std::max(descent, maxDescent);
1344
1345 width += advance;
1346 previous = glyphIndex;
1347 }
1348
1349 return {0, -FT2Point(maxAscent), FT2Point(width), FT2Point(maxAscent + maxDescent)};
1350}
1351
1352// Reset the image and fill it with transparent ink.
1353// The UK MHEG profile says that we should consider the background
1354// as paper and the text as ink. We have to consider these as two
1355// different layers. The background is drawn separately as a rectangle.
1357{
1358 m_image = QImage(m_width, m_height, QImage::Format_ARGB32);
1359 // QImage::fill doesn't set the alpha buffer.
1360 for (int i = 0; i < m_height; i++)
1361 {
1362 for (int j = 0; j < m_width; j++)
1363 {
1364 m_image.setPixel(j, i, qRgba(0, 0, 0, 0));
1365 }
1366 }
1367}
1368
1369// Draw a line of text in the given position within the image.
1370// It would be nice to be able to use TTFFont for this but it doesn't provide
1371// what we want.
1372void MHIText::AddText(int x, int y, const QString &str, MHRgba colour)
1373{
1374 if (!m_parent->IsFaceLoaded()) return;
1375 FT_Face face = m_parent->GetFontFace();
1376
1377 FT_Set_Char_Size(face, 0, Point2FT(m_fontSize),
1379
1380 // X positions are computed to 64ths and rounded.
1381 // Y positions are in pixels
1382 int posX = Point2FT(x);
1383 int pixelY = y;
1384 FT_Bool useKerning = FT_HAS_KERNING(face);
1385 FT_UInt previous = 0;
1386
1387 int len = str.length();
1388 for (int n = 0; n < len; n++)
1389 {
1390 // Load the glyph.
1391 QChar ch = str[n];
1392 FT_UInt glyphIndex = FT_Get_Char_Index(face, ch.unicode());
1393 if (glyphIndex == 0)
1394 {
1395 previous = 0;
1396 continue;
1397 }
1398
1399 if (useKerning && previous != 0)
1400 {
1401 FT_Vector delta;
1402 FT_Get_Kerning(face, previous, glyphIndex,
1403 FT_KERNING_DEFAULT, &delta);
1404 posX += delta.x;
1405 }
1406 FT_Error error = FT_Load_Glyph(face, glyphIndex, FT_LOAD_RENDER);
1407
1408 if (error)
1409 continue; // ignore errors
1410
1411 FT_GlyphSlot slot = face->glyph;
1412 if (slot->format != FT_GLYPH_FORMAT_BITMAP)
1413 continue; // Problem
1414
1415 if ((enum FT_Pixel_Mode_)slot->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY)
1416 continue;
1417
1418 unsigned char *source = slot->bitmap.buffer;
1419 // Get the origin for the bitmap
1420 int baseX = FT2Point(posX) + slot->bitmap_left;
1421 int baseY = pixelY - slot->bitmap_top;
1422 // Copy the bitmap into the image.
1423 for (unsigned int i = 0; i < slot->bitmap.rows; i++)
1424 {
1425 for (unsigned int j = 0; j < slot->bitmap.width; j++)
1426 {
1427 int greyLevel = source[j];
1428 // Set the pixel to the specified colour but scale its
1429 // brightness according to the grey scale of the pixel.
1430 int red = colour.red();
1431 int green = colour.green();
1432 int blue = colour.blue();
1433 int alpha = colour.alpha() *
1434 greyLevel / slot->bitmap.num_grays;
1435 int xBit = j + baseX;
1436 int yBit = i + baseY;
1437
1438 // The bits ought to be inside the bitmap but
1439 // I guess there's the possibility
1440 // that rounding might put it outside.
1441 if (xBit >= 0 && xBit < m_width &&
1442 yBit >= 0 && yBit < m_height)
1443 {
1444 m_image.setPixel(xBit, yBit,
1445 qRgba(red, green, blue, alpha));
1446 }
1447 }
1448 source += slot->bitmap.pitch;
1449 }
1450 posX += slot->advance.x;
1451 previous = glyphIndex;
1452 }
1453}
1454
1455// Internal function to fill a rectangle with a colour
1456void MHIDLA::DrawRect(int x, int y, int width, int height, MHRgba colour)
1457{
1458 QRgb qColour = qRgba(colour.red(), colour.green(),
1459 colour.blue(), colour.alpha());
1460
1461 // Constrain the drawing within the image.
1462 if (x < 0)
1463 {
1464 width += x;
1465 x = 0;
1466 }
1467
1468 if (y < 0)
1469 {
1470 height += y;
1471 y = 0;
1472 }
1473
1474 if (width <= 0 || height <= 0)
1475 return;
1476
1477 int imageWidth = m_image.width();
1478 int imageHeight = m_image.height();
1479 if (x+width > imageWidth)
1480 width = imageWidth - x;
1481
1482 if (y+height > imageHeight)
1483 height = imageHeight - y;
1484
1485 if (width <= 0 || height <= 0)
1486 return;
1487
1488 for (int i = 0; i < height; i++)
1489 {
1490 for (int j = 0; j < width; j++)
1491 {
1492 m_image.setPixel(x+j, y+i, qColour);
1493 }
1494 }
1495}
1496
1497// Reset the drawing.
1499{
1500 if (m_width == 0 || m_height == 0)
1501 {
1502 m_image = QImage();
1503 return;
1504 }
1505 m_image = QImage(m_width, m_height, QImage::Format_ARGB32);
1506 // Fill the image with "transparent colour".
1507 DrawRect(0, 0, m_width, m_height, MHRgba(0, 0, 0, 0));
1508}
1509
1510void MHIDLA::Draw(int x, int y)
1511{
1512 QRect bounds(x, y, m_width, m_height);
1513 if (m_boxed && m_lineWidth != 0)
1514 {
1515 // Draw the lines round the outside.
1516 // These don't form part of the drawing.
1517 m_parent->DrawRect(x, y, m_width,
1519
1522
1526
1530
1531 // Deflate the box to within the border.
1532 bounds = QRect(bounds.x() + m_lineWidth,
1533 bounds.y() + m_lineWidth,
1534 bounds.width() - (2*m_lineWidth),
1535 bounds.height() - (2*m_lineWidth));
1536 }
1537
1538 // Draw the background.
1540 y + m_lineWidth,
1541 m_width - (m_lineWidth * 2),
1542 m_height - (m_lineWidth * 2),
1544
1545 // Now the drawing.
1546 m_parent->DrawImage(x, y, bounds, m_image);
1547}
1548
1549// The UK MHEG profile defines exactly how transparency is supposed to work.
1550// The drawings are made using possibly transparent ink with any crossings
1551// just set to that ink and then the whole drawing is alpha-merged with the
1552// underlying graphics.
1553// DynamicLineArt no longer seems to be used in transmissions in the UK
1554// although it appears that DrawPoly is used in New Zealand. These are
1555// very basic implementations of the functions.
1556
1557// Lines
1558void MHIDLA::DrawLine(int x1, int y1, int x2, int y2)
1559{
1560 // Get the arguments so that the lower x comes first and the
1561 // absolute gradient is less than one.
1562 if (abs(y2-y1) > abs(x2-x1))
1563 {
1564 if (y2 > y1)
1565 DrawLineSub(y1, x1, y2, x2, true);
1566 else
1567 DrawLineSub(y2, x2, y1, x1, true);
1568 }
1569 else
1570 {
1571 if (x2 > x1)
1572 DrawLineSub(x1, y1, x2, y2, false);
1573 else
1574 DrawLineSub(x2, y2, x1, y1, false);
1575 }
1576}
1577
1578// Based on the Bresenham line drawing algorithm but extended to draw
1579// thick lines.
1580void MHIDLA::DrawLineSub(int x1, int y1, int x2, int y2, bool swapped)
1581{
1582 QRgb colour = qRgba(m_lineColour.red(), m_lineColour.green(),
1584 int dx = x2-x1;
1585 int dy = abs(y2-y1);
1586 int yStep = y2 >= y1 ? 1 : -1;
1587 // Adjust the starting positions to take account of the
1588 // line width.
1589 int error2 = dx/2;
1590 for (int k = 0; k < m_lineWidth/2; k++)
1591 {
1592 y1--;
1593 y2--;
1594 error2 += dy;
1595 if (error2*2 > dx)
1596 {
1597 error2 -= dx;
1598 x1 += yStep;
1599 x2 += yStep;
1600 }
1601 }
1602 // Main loop
1603 int y = y1;
1604 int error = dx/2;
1605 for (int x = x1; x <= x2; x++) // Include both endpoints
1606 {
1607 error2 = dx/2;
1608 int j = 0;
1609 // Inner loop also uses the Bresenham algorithm to draw lines
1610 // perpendicular to the principal direction.
1611 for (int i = 0; i < m_lineWidth; i++)
1612 {
1613 if (swapped)
1614 {
1615 if (x+j >= 0 && y+i >= 0 && y+i < m_width && x+j < m_height)
1616 m_image.setPixel(y+i, x+j, colour);
1617 }
1618 else
1619 {
1620 if (x+j >= 0 && y+i >= 0 && x+j < m_width && y+i < m_height)
1621 m_image.setPixel(x+j, y+i, colour);
1622 }
1623 error2 += dy;
1624 if (error2*2 > dx)
1625 {
1626 error2 -= dx;
1627 j -= yStep;
1628 if (i < m_lineWidth-1)
1629 {
1630 // Add another pixel in this case.
1631 if (swapped)
1632 {
1633 if (x+j >= 0 && y+i >= 0 && y+i < m_width && x+j < m_height)
1634 m_image.setPixel(y+i, x+j, colour);
1635 }
1636 else
1637 {
1638 if (x+j >= 0 && y+i >= 0 && x+j < m_width && y+i < m_height)
1639 m_image.setPixel(x+j, y+i, colour);
1640 }
1641 }
1642 }
1643 }
1644 error += dy;
1645 if (error*2 > dx)
1646 {
1647 error -= dx;
1648 y += yStep;
1649 }
1650 }
1651}
1652
1653// Rectangles
1654void MHIDLA::DrawBorderedRectangle(int x, int y, int width, int height)
1655{
1656 if (m_lineWidth != 0)
1657 {
1658 // Draw the lines round the rectangle.
1659 DrawRect(x, y, width, m_lineWidth,
1660 m_lineColour);
1661
1662 DrawRect(x, y + height - m_lineWidth,
1663 width, m_lineWidth,
1664 m_lineColour);
1665
1666 DrawRect(x, y + m_lineWidth,
1667 m_lineWidth, height - (m_lineWidth * 2),
1668 m_lineColour);
1669
1670 DrawRect(x + width - m_lineWidth, y + m_lineWidth,
1671 m_lineWidth, height - (m_lineWidth * 2),
1672 m_lineColour);
1673
1674 // Fill the rectangle.
1676 width - (m_lineWidth * 2), height - (m_lineWidth * 2),
1677 m_fillColour);
1678 }
1679 else
1680 {
1681 DrawRect(x, y, width, height, m_fillColour);
1682 }
1683}
1684
1685// Ovals (ellipses)
1686void MHIDLA::DrawOval(int /*x*/, int /*y*/, int /*width*/, int /*height*/)
1687{
1688 // Not implemented. Not actually used in practice.
1689}
1690
1691// Arcs and sectors
1692void MHIDLA::DrawArcSector(int /*x*/, int /*y*/, int /*width*/, int /*height*/,
1693 int /*start*/, int /*arc*/, bool /*isSector*/)
1694{
1695 // Not implemented. Not actually used in practice.
1696}
1697
1698// Polygons. This is used directly and also to draw other figures.
1699// The UK profile says that MHEG should not contain concave or
1700// self-crossing polygons but we can get the former at least as
1701// a result of rounding when drawing ellipses.
1702struct lineSeg { int m_yBottom, m_yTop, m_xBottom; float m_slope; };
1703
1704void MHIDLA::DrawPoly(bool isFilled, const MHPointVec& xArray, const MHPointVec& yArray)
1705{
1706 int nPoints = xArray.size();
1707 if (nPoints < 2)
1708 return;
1709
1710 if (isFilled)
1711 {
1712 QVector <lineSeg> lineArray(nPoints);
1713 int nLines = 0;
1714 // Initialise the line segment array. Include all lines
1715 // apart from horizontal. Close the polygon by starting
1716 // with the last point in the array.
1717 int lastX = xArray[nPoints-1]; // Last point
1718 int lastY = yArray[nPoints-1];
1719 int yMin = lastY;
1720 int yMax = lastY;
1721 for (int k = 0; k < nPoints; k++)
1722 {
1723 int thisX = xArray[k];
1724 int thisY = yArray[k];
1725 if (lastY != thisY)
1726 {
1727 if (lastY > thisY)
1728 {
1729 lineArray[nLines].m_yBottom = thisY;
1730 lineArray[nLines].m_yTop = lastY;
1731 lineArray[nLines].m_xBottom = thisX;
1732 }
1733 else
1734 {
1735 lineArray[nLines].m_yBottom = lastY;
1736 lineArray[nLines].m_yTop = thisY;
1737 lineArray[nLines].m_xBottom = lastX;
1738 }
1739 lineArray[nLines++].m_slope =
1740 (float)(thisX-lastX) / (float)(thisY-lastY);
1741 }
1742 yMin = std::min(thisY, yMin);
1743 yMax = std::max(thisY, yMax);
1744 lastX = thisX;
1745 lastY = thisY;
1746 }
1747
1748 // Find the intersections of each line in the line segment array
1749 // with the scan line. Because UK MHEG says that figures should be
1750 // convex we only need to consider two intersections.
1751 QRgb fillColour = qRgba(m_fillColour.red(), m_fillColour.green(),
1753 for (int y = yMin; y < yMax; y++)
1754 {
1755 int crossings = 0;
1756 int xMin = 0;
1757 int xMax = 0;
1758 for (int l = 0; l < nLines; l++)
1759 {
1760 if (y >= lineArray[l].m_yBottom && y < lineArray[l].m_yTop)
1761 {
1762 int x = (int)round((float)(y - lineArray[l].m_yBottom) *
1763 lineArray[l].m_slope) + lineArray[l].m_xBottom;
1764 if (crossings == 0 || x < xMin)
1765 xMin = x;
1766 if (crossings == 0 || x > xMax)
1767 xMax = x;
1768 crossings++;
1769 }
1770 }
1771 if (crossings == 2)
1772 {
1773 for (int x = xMin; x <= xMax; x++)
1774 m_image.setPixel(x, y, fillColour);
1775 }
1776 }
1777
1778 // Draw the boundary
1779 int lastXpoint = xArray[nPoints-1]; // Last point
1780 int lastYpoint = yArray[nPoints-1];
1781 for (int i = 0; i < nPoints; i++)
1782 {
1783 DrawLine(xArray[i], yArray[i], lastXpoint, lastYpoint);
1784 lastXpoint = xArray[i];
1785 lastYpoint = yArray[i];
1786 }
1787 }
1788 else // PolyLine - draw lines between the points but don't close it.
1789 {
1790 for (int i = 1; i < nPoints; i++)
1791 {
1792 DrawLine(xArray[i], yArray[i], xArray[i-1], yArray[i-1]);
1793 }
1794 }
1795}
1796
1798 : m_parent(parent), m_tiled(tiled),
1799 m_copyCtx(new MythAVCopy())
1800{
1801}
1802
1804{
1805 delete m_copyCtx;
1806}
1807
1808void MHIBitmap::Draw(int x, int y, QRect rect, bool tiled, bool bUnder)
1809{
1810 if (tiled)
1811 {
1812 if (m_image.width() == 0 || m_image.height() == 0)
1813 return;
1814 // Construct an image the size of the bounding box and tile the
1815 // bitmap over this.
1816 QImage tiledImage = QImage(rect.width(), rect.height(),
1817 QImage::Format_ARGB32);
1818
1819 for (int i = 0; i < rect.width(); i++)
1820 {
1821 for (int j = 0; j < rect.height(); j++)
1822 {
1823 tiledImage.setPixel(i, j, m_image.pixel(i % m_image.width(), j % m_image.height()));
1824 }
1825 }
1826 m_parent->DrawImage(rect.x(), rect.y(), rect, tiledImage, true, bUnder);
1827 }
1828 else
1829 {
1830 // NB THe BBC expects bitmaps to be scaled, not clipped
1831 m_parent->DrawImage(x, y, rect, m_image, true, bUnder);
1832 }
1833}
1834
1835// Create a bitmap from PNG.
1836void MHIBitmap::CreateFromPNG(const unsigned char *data, int length)
1837{
1838 m_image = QImage();
1839
1840 if (!m_image.loadFromData(data, length, "PNG"))
1841 {
1842 m_image = QImage();
1843 return;
1844 }
1845
1846 // Assume that if it has an alpha buffer then it's partly transparent.
1847 m_opaque = ! m_image.hasAlphaChannel();
1848}
1849
1850// Create a bitmap from JPEG.
1851//virtual
1852void MHIBitmap::CreateFromJPEG(const unsigned char *data, int length)
1853{
1854 m_image = QImage();
1855
1856 if (!m_image.loadFromData(data, length, "JPG"))
1857 {
1858 m_image = QImage();
1859 return;
1860 }
1861
1862 // Assume that if it has an alpha buffer then it's partly transparent.
1863 m_opaque = ! m_image.hasAlphaChannel();
1864}
1865
1866// Convert an MPEG I-frame into a bitmap. This is used as the way of
1867// sending still pictures. We convert the image to a QImage even
1868// though that actually means converting it from YUV and eventually
1869// converting it back again but we do this very infrequently so the
1870// cost is outweighed by the simplification.
1871void MHIBitmap::CreateFromMPEG(const unsigned char *data, int length)
1872{
1873 AVCodecContext *c = nullptr;
1874 MythAVFrame picture;
1875 AVPacket pkt;
1876 uint8_t *buff = nullptr;
1877 bool gotPicture = false;
1878 m_image = QImage();
1879
1880 // Find the mpeg2 video decoder.
1881 const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_MPEG2VIDEO);
1882 if (!codec)
1883 return;
1884 if (!picture)
1885 return;
1886
1887 // Automatically clean up memory allocation at function exit
1888 auto cleanup_fn = [&](MHIBitmap */*x*/) {
1889 pkt.data = buff;
1890 av_packet_unref(&pkt);
1891 avcodec_free_context(&c);
1892 };
1893 std::unique_ptr<MHIBitmap,decltype(cleanup_fn)> cleanup { this, cleanup_fn };
1894
1895 c = avcodec_alloc_context3(nullptr);
1896
1897 if (avcodec_open2(c, codec, nullptr) < 0)
1898 return;
1899
1900 // Copy the data into AVPacket
1901 if (av_new_packet(&pkt, length) < 0)
1902 return;
1903
1904 memcpy(pkt.data, data, length);
1905 buff = pkt.data;
1906
1907 // Get a picture from the packet. Allow 9 loops for
1908 // packet to be decoded. It should take only 2-3 loops
1909 for (int limit=0; limit<10 && !gotPicture; limit++)
1910 {
1911 int len = avcodec_receive_frame(c, picture);
1912 if (len == 0)
1913 gotPicture = true;
1914 if (len == AVERROR(EAGAIN))
1915 len = 0;
1916 if (len == 0)
1917 len = avcodec_send_packet(c, &pkt);
1918 if (len == AVERROR(EAGAIN) || len == AVERROR_EOF)
1919 len = 0;
1920 if (len < 0) // Error
1921 {
1922 std::string error;
1923 LOG(VB_GENERAL, LOG_ERR,
1924 QString("[mhi] video decode error: %1 (%2)")
1925 .arg(av_make_error_stdstring(error, len))
1926 .arg(gotPicture));
1927 return;
1928 }
1929
1930 pkt.data = nullptr;
1931 pkt.size = 0;
1932 }
1933
1934 if (gotPicture)
1935 {
1936 int nContentWidth = c->width;
1937 int nContentHeight = c->height;
1938 m_image = QImage(nContentWidth, nContentHeight, QImage::Format_ARGB32);
1939 m_opaque = true; // MPEG images are always opaque.
1940
1941 AVFrame retbuf;
1942 memset(&retbuf, 0, sizeof(AVFrame));
1943
1944 int bufflen = nContentWidth * nContentHeight * 3;
1945 auto *outputbuf = (unsigned char*)av_malloc(bufflen);
1946
1947 av_image_fill_arrays(retbuf.data, retbuf.linesize,
1948 outputbuf, AV_PIX_FMT_RGB24,
1949 nContentWidth, nContentHeight,IMAGE_ALIGN);
1950
1951 AVFrame *tmp = picture;
1952 m_copyCtx->Copy(&retbuf, AV_PIX_FMT_RGB24, tmp, c->pix_fmt,
1953 nContentWidth, nContentHeight);
1954
1955 uint8_t * buf = outputbuf;
1956
1957 // Copy the data a pixel at a time.
1958 // This should handle endianness correctly.
1959 for (int i = 0; i < nContentHeight; i++)
1960 {
1961 for (int j = 0; j < nContentWidth; j++)
1962 {
1963 int red = *buf++;
1964 int green = *buf++;
1965 int blue = *buf++;
1966 m_image.setPixel(j, i, qRgb(red, green, blue));
1967 }
1968 }
1969 av_freep(reinterpret_cast<void*>(&outputbuf));
1970 }
1971}
1972
1973// Scale the bitmap. Only used for image derived from MPEG I-frames.
1974void MHIBitmap::ScaleImage(int newWidth, int newHeight)
1975{
1976 if (m_image.isNull())
1977 return;
1978
1979 if (newWidth == m_image.width() && newHeight == m_image.height())
1980 return;
1981
1982 if (newWidth <= 0 || newHeight <= 0)
1983 { // This would be a bit silly but handle it anyway.
1984 m_image = QImage();
1985 return;
1986 }
1987
1988 m_image = m_image.scaled(newWidth, newHeight,
1989 Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
1990}
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:550
void Reset()
Definition: dsmcc.cpp:541
void ProcessSection(const unsigned char *data, int length, int componentTag, unsigned carouselId, int dataBroadcastId)
Definition: dsmcc.cpp:451
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:1808
bool m_opaque
Definition: mhi.h:307
MHIBitmap(MHIContext *parent, bool tiled)
Definition: mhi.cpp:1797
void ScaleImage(int newWidth, int newHeight) override
Scale the bitmap. Only used for image derived from MPEG I-frames.
Definition: mhi.cpp:1974
void CreateFromMPEG(const unsigned char *data, int length) override
Create bitmap from single I frame MPEG.
Definition: mhi.cpp:1871
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:1836
QImage m_image
Definition: mhi.h:306
void CreateFromJPEG(const unsigned char *data, int length) override
Create bitmap from JPEG.
Definition: mhi.cpp:1852
~MHIBitmap() override
Definition: mhi.cpp:1803
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:1166
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:1180
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:984
std::chrono::milliseconds GetStreamMaxPos() override
Definition: mhi.cpp:1151
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:1145
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:1209
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:1139
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:1157
bool m_faceLoaded
Definition: mhi.h:205
MHBitmapDisplay * CreateBitmap(bool tiled) override
Definition: mhi.cpp:1186
static int Tid(ChannelCache_t::const_iterator it)
Definition: mhi.h:230
bool TuneTo(int channel, int tuneinfo) override
Definition: mhi.cpp:1009
int GetICStatus() override
Definition: mhi.cpp:664
int GetChannelIndex(const QString &str) override
Definition: mhi.cpp:897
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:862
~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:1125
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:1034
QWaitCondition m_engineWait
Definition: mhi.h:197
MHDLADisplay * CreateDynamicLineArt(bool isBoxed, MHRgba lineColour, MHRgba fillColour) override
Creation functions for various visibles.
Definition: mhi.cpp:1173
bool StreamStarted(bool bStarted=true)
Definition: mhi.cpp:1089
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:838
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:1192
bool IsFaceLoaded(void) const
Definition: mhi.h:163
static const int kStdDisplayWidth
Definition: mhi.h:167
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:1079
bool m_updated
Definition: mhi.h:200
bool BeginAudio(int tag) override
Begin playing audio.
Definition: mhi.cpp:1105
QRect m_displayRect
Definition: mhi.h:222
MHInteractionChannel m_ic
Definition: mhi.h:187
void StopAudio() override
Stop playing audio.
Definition: mhi.cpp:1119
void UpdateOSD(InteractiveScreen *osdWindow, MythPainter *osdPainter)
Update the display.
Definition: mhi.cpp:671
void DrawBackground(const QRegion &reg) override
Definition: mhi.cpp:1232
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:168
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:1704
void DrawLine(int x1, int y1, int x2, int y2) override
Definition: mhi.cpp:1558
void DrawBorderedRectangle(int x, int y, int width, int height) override
Definition: mhi.cpp:1654
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:1456
void Clear(void) override
Clear the drawing.
Definition: mhi.cpp:1498
void Draw(int x, int y) override
Draw the completed drawing onto the display.
Definition: mhi.cpp:1510
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:1692
int m_lineWidth
Current line width.
Definition: mhi.h:362
void DrawOval(int x, int y, int width, int height) override
Definition: mhi.cpp:1686
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:1580
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:1253
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:1372
bool m_fontItalic
Definition: mhi.h:257
int m_height
Definition: mhi.h:260
void Clear(void) override
Definition: mhi.cpp:1356
int m_fontSize
Definition: mhi.h:256
void SetSize(int width, int height) override
Definition: mhi.cpp:1247
void Draw(int x, int y) override
Definition: mhi.cpp:1242
QRect GetBounds(const QString &str, int &strLen, int maxSize=-1) override
Definition: mhi.cpp:1281
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:281
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:298
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:267
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:207
unsigned int uint
Definition: compat.h:60
const int kShift
Definition: mhi.cpp:1263
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:1269
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:1264
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:829
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:1702
float m_slope
Definition: mhi.cpp:1702
int m_yBottom
Definition: mhi.cpp:1702
int m_xBottom
Definition: mhi.cpp:1702
#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