MythTV master
mythdisplayrpi.cpp
Go to the documentation of this file.
1// MythTV
4#include "mythdisplayrpi.h"
5
6// Broadcom
7extern "C" {
8#include "interface/vmcs_host/vc_dispmanx_types.h"
9}
10
11#define LOC QString("DisplayRPI: ")
12#define MAX_MODE_ID (127)
13
29static void MythTVServiceCallback(void *Context, uint32_t Reason, uint32_t Param1, uint32_t Param2)
30{
31 MythDisplayRPI* display = reinterpret_cast<MythDisplayRPI*>(Context);
32 if (display)
33 display->Callback(Reason, Param1, Param2);
34}
35
36static inline QString DisplayToString(int Id)
37{
38 switch (Id)
39 {
40 case DISPMANX_ID_MAIN_LCD: return "MainLCD";
41 case DISPMANX_ID_AUX_LCD: return "AuxLCD";
42 case DISPMANX_ID_HDMI0: return "HDMI1"; // NB Consistency with DRM code
43 case DISPMANX_ID_SDTV: return "SDTV";
44 case DISPMANX_ID_FORCE_LCD: return "ForceLCD";
45 case DISPMANX_ID_FORCE_TV: return "ForceTV";
46 case DISPMANX_ID_FORCE_OTHER: return "ForceOther";
47 case DISPMANX_ID_HDMI1: return "HDMI2";
48 case DISPMANX_ID_FORCE_TV2: return "ForceTV2";
49 }
50 return "Unknown";
51}
52
54 : MythDisplay()
55{
56 // We use the tv service callback to wait for changes
58
59 if (vchi_initialise(&m_vchiInstance) != 0)
60 {
61 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to initialise VCHI");
62 return;
63 }
64
65 if (vchi_connect(nullptr, 0, m_vchiInstance) != 0)
66 {
67 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create VCHI connection");
68 return;
69 }
70
71 VCHI_CONNECTION_T *vchiconnection;
72 vc_vchi_tv_init(m_vchiInstance, &vchiconnection, 1);
73 vc_tv_register_callback(MythTVServiceCallback, this);
74
75 // Raspberry Pi 4 does some strange things when connected to the second HDMI
76 // connector and nothing is connected to the first. For the sake of simplicity
77 // here, only support one display connected to the first connector - and warn
78 // otherwise.
79 TV_ATTACHED_DEVICES_T devices;
80 if (vc_tv_get_attached_devices(&devices) == 0)
81 {
82 // use the first device for now
83 m_deviceId = devices.display_number[0];
84 if (devices.num_attached > 1)
85 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("%1 connected displays - this may not work")
86 .arg(devices.num_attached));
87 }
88
89 // Note: QScreen name will be based on DRM names here - which at first glance
90 // could probably be matched to DISPMANX_IDs (see DisplayToString) but I'm not
91 // sure what the correct naming scheme is for anything other than HDMI.
92 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Connected to display '%1'")
94 Initialise();
95}
96
98{
99 vc_tv_unregister_callback_full(MythTVServiceCallback, this);
100 vc_vchi_tv_stop();
101 vchi_disconnect(m_vchiInstance);
102}
103
104void MythDisplayRPI::Callback(uint32_t Reason, uint32_t, uint32_t)
105{
106 if (Reason == VC_HDMI_DVI || Reason == VC_HDMI_HDMI)
107 m_modeChangeWait.wakeAll();
108}
109
111{
112 TV_DISPLAY_STATE_T tvstate;
113 int ret = (m_deviceId != -1) ? vc_tv_get_display_state_id(m_deviceId, &tvstate) :
114 vc_tv_get_display_state(&tvstate);
115
116 if (ret != 0)
117 {
119 return;
120 }
121
122 // The tvservice code has additional handling for PAL v NTSC rates - both for
123 // getting the current mode and in setting modes.
124 m_refreshRate = static_cast<double>(tvstate.display.hdmi.frame_rate);
125 m_resolution = QSize(tvstate.display.hdmi.width, tvstate.display.hdmi.height);
126 GetEDID();
127 m_edid.Debug();
128 // I can't see any Pi interface for the physical size but it will just be using
129 // the EDID anyway
130 m_physicalSize = m_edid.Valid() ? m_edid.DisplaySize() : QSize(0, 0);
131 m_modeComplete = true;
132}
133
135{
136 QByteArray result;
137 uint8_t buffer[128];
138 int offset = 0;
139 int size = (m_deviceId != -1) ? vc_tv_hdmi_ddc_read_id(m_deviceId, offset, 128, buffer) :
140 vc_tv_hdmi_ddc_read(offset, 128, buffer);
141 if (size != 128)
142 {
143 m_edid = MythEDID();
144 return;
145 }
146
147 result.append(reinterpret_cast<const char *>(buffer), 128);
148 int extensions = buffer[0x7e];
149 // guard against bogus EDID
150 if (extensions > 10) extensions = 10;
151 for (int i = 0; i < extensions; ++i)
152 {
153 offset += 128;
154 size = (m_deviceId != -1) ? vc_tv_hdmi_ddc_read_id(m_deviceId, offset, 128, buffer) :
155 vc_tv_hdmi_ddc_read(offset, 128, buffer);
156 if (size == 128)
157 result.append(reinterpret_cast<const char *>(buffer), 128);
158 else
159 break;
160 }
161 m_edid = MythEDID(result);
162}
163
165{
166 if (gCoreContext)
167 return gCoreContext->GetBoolSetting("UseVideoModes", false);
168 return false;
169}
170
172{
173 if (!m_videoModes.empty())
174 return m_videoModes;
175
176 m_videoModes.clear();
177 m_modeMap.clear();
178 DisplayModeMap screenmap;
179 const HDMI_RES_GROUP_T groups[2] = { HDMI_RES_GROUP_CEA, HDMI_RES_GROUP_DMT };
180
181 for (int i = 0; i < 2; ++i)
182 {
183 HDMI_RES_GROUP_T group = groups[i];
184 TV_SUPPORTED_MODE_NEW_T modes[MAX_MODE_ID];
185 memset(modes, 0, sizeof(modes));
186 HDMI_RES_GROUP_T preferredgroup;
187 uint32_t preferred;
188 int count;
189
190 if (m_deviceId != -1)
191 {
192 count = vc_tv_hdmi_get_supported_modes_new_id(m_deviceId, group, modes,
193 MAX_MODE_ID, &preferredgroup,
194 &preferred);
195 }
196 else
197 {
198 count = vc_tv_hdmi_get_supported_modes_new(group, modes, MAX_MODE_ID,
199 &preferredgroup, &preferred);
200 }
201
202 for (int j = 0; j < count; ++j)
203 {
204 if (modes[j].width != m_resolution.width() || modes[j].height != m_resolution.height())
205 {
206 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Ignoring mode %1x%2 %3 - cannot resize framebuffer")
207 .arg(modes[j].width).arg(modes[j].height).arg(modes[j].frame_rate));
208 continue;
209 }
210
211 if (modes[j].scan_mode)
212 {
213 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Ignoring interlaced mode %1x%2 %3")
214 .arg(modes[j].width).arg(modes[j].height).arg(modes[j].frame_rate));
215 continue;
216 }
217
218 double rate = static_cast<double>(modes[j].frame_rate);
219 QSize resolution(modes[j].width, modes[j].height);
220 uint64_t key = MythDisplayMode::CalcKey(resolution, 0.0);
221 if (screenmap.find(key) == screenmap.end())
222 screenmap[key] = MythDisplayMode(resolution, QSize(), -1.0, rate);
223 else
224 screenmap[key].AddRefreshRate(rate);
225 m_modeMap.insert(MythDisplayMode::CalcKey(resolution, rate),
226 QPair<uint32_t,uint32_t>(modes[j].code, modes[j].group));
227 }
228 }
229
230 for (auto it = screenmap.begin(); screenmap.end() != it; ++it)
231 m_videoModes.push_back(it->second);
232
233 DebugModes();
234 return m_videoModes;
235}
236
237bool MythDisplayRPI::SwitchToVideoMode(QSize Size, double Framerate)
238{
239 auto rate = static_cast<double>(NAN);
240 QSize dummy(0, 0);
241 MythDisplayMode desired(Size, dummy, -1.0, Framerate);
242 int idx = MythDisplayMode::FindBestMatch(m_videoModes, desired, rate);
243
244 if (idx < 0)
245 {
246 LOG(VB_GENERAL, LOG_ERR, LOC + "Desired resolution and frame rate not found.");
247 return false;
248 }
249
250 auto mode = MythDisplayMode::CalcKey(Size, rate);
251 if (!m_modeMap.contains(mode))
252 {
253 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to find mode");
254 return false;
255 }
256
257 HDMI_RES_GROUP_T group = static_cast<HDMI_RES_GROUP_T>(m_modeMap.value(mode).second);
258 uint32_t modeid = m_modeMap.value(mode).first;
259 // This is just a guess...
260 HDMI_MODE_T modetype = (m_edid.Valid() && m_edid.IsHDMI()) ? HDMI_MODE_HDMI : HDMI_MODE_DVI;
261
262 int ret = (m_deviceId != -1) ? vc_tv_hdmi_power_on_explicit_new_id(m_deviceId, modetype, group, modeid) :
263 vc_tv_hdmi_power_on_explicit_new(modetype, group, modeid);
264
265 if (ret == 0)
266 {
267 // We need to wait for the mode change, otherwise we don't get the
268 // correct details in UpdateCurrentMode
269 m_modeChangeLock.lock();
270 if (!m_modeChangeWait.wait(&m_modeChangeLock, 500))
271 LOG(VB_GENERAL, LOG_WARNING, LOC + "Timed out waiting for mode switch");
272 m_modeChangeLock.unlock();
273 }
274
275 return ret == 0;
276}
bool GetBoolSetting(const QString &key, bool defaultval=false)
static int FindBestMatch(const MythDisplayModes &Modes, const MythDisplayMode &Mode, double &TargetRate)
static uint64_t CalcKey(QSize Size, double Rate)
QMutex m_modeChangeLock
void UpdateCurrentMode(void) override
Retrieve screen details.
void Callback(uint32_t Reason, uint32_t, uint32_t)
void GetEDID(void)
const MythDisplayModes & GetVideoModes(void) override
QWaitCondition m_modeChangeWait
QMap< uint64_t, QPair< uint32_t, uint32_t > > m_modeMap
bool SwitchToVideoMode(QSize Size, double Framerate) override
bool UsingVideoModes(void) override
VCHI_INSTANCE_T m_vchiInstance
~MythDisplayRPI() override
QSize m_resolution
Definition: mythdisplay.h:92
virtual void UpdateCurrentMode()
Retrieve screen details.
QSize m_physicalSize
Definition: mythdisplay.h:93
void Initialise()
double m_refreshRate
Definition: mythdisplay.h:90
MythDisplayModes m_videoModes
Definition: mythdisplay.h:98
bool m_waitForModeChanges
Definition: mythdisplay.h:88
MythEDID m_edid
Definition: mythdisplay.h:94
bool m_modeComplete
Definition: mythdisplay.h:89
void DebugModes() const
void Debug() const
Definition: mythedid.cpp:485
QSize DisplaySize() const
Definition: mythedid.cpp:49
bool IsHDMI() const
Definition: mythedid.cpp:69
bool Valid() const
Definition: mythedid.cpp:39
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
std::map< uint64_t, MythDisplayMode > DisplayModeMap
std::vector< MythDisplayMode > MythDisplayModes
#define LOC
static void MythTVServiceCallback(void *Context, uint32_t Reason, uint32_t Param1, uint32_t Param2)
static QString DisplayToString(int Id)
#define MAX_MODE_ID
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39