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