MythTV  master
mythdisplayrpi.cpp
Go to the documentation of this file.
1 // MythTV
2 #include "mythcorecontext.h"
3 #include "mythlogging.h"
4 #include "mythdisplayrpi.h"
5 
6 // Broadcom
7 extern "C" {
8 #include "interface/vmcs_host/vc_dispmanx_types.h"
9 }
10 
11 #define LOC QString("DisplayRPI: ")
12 #define MAX_MODE_ID (127)
13 
29 static 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 
36 static 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
57  m_waitForModeChanges = false;
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 
104 void 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 
171 const vector<MythDisplayMode>& MythDisplayRPI::GetVideoModes(void)
172 {
173  if (!m_videoModes.empty())
174  return m_videoModes;
175 
176  m_videoModes.clear();
177  m_modeMap.clear();
178  DisplayModeMap screenmap;
179  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 
237 bool 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 }
MythDisplay::m_physicalSize
QSize m_physicalSize
Definition: mythdisplay.h:89
MythDisplay::DebugModes
void DebugModes(void) const
Definition: mythdisplay.cpp:1036
MythDisplayRPI::~MythDisplayRPI
~MythDisplayRPI() override
Definition: mythdisplayrpi.cpp:97
MythDisplay::Initialise
void Initialise(void)
Definition: mythdisplay.cpp:504
MythDisplayRPI::SwitchToVideoMode
bool SwitchToVideoMode(QSize Size, double Framerate) override
Definition: mythdisplayrpi.cpp:237
MythDisplay::m_resolution
QSize m_resolution
Definition: mythdisplay.h:88
MAX_MODE_ID
#define MAX_MODE_ID
Definition: mythdisplayrpi.cpp:12
MythDisplayMode::CalcKey
static uint64_t CalcKey(QSize Size, double Rate)
Definition: mythdisplaymode.cpp:126
MythDisplay::UpdateCurrentMode
virtual void UpdateCurrentMode(void)
Retrieve screen details.
Definition: mythdisplay.cpp:442
arg
arg(title).arg(filename).arg(doDelete))
MythDisplayRPI::Callback
void Callback(uint32_t Reason, uint32_t, uint32_t)
Definition: mythdisplayrpi.cpp:104
Context
QHash< QString, Action * > Context
Definition: action.h:77
MythTVServiceCallback
static void MythTVServiceCallback(void *Context, uint32_t Reason, uint32_t Param1, uint32_t Param2)
Definition: mythdisplayrpi.cpp:29
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
MythDisplayRPI
Definition: mythdisplayrpi.h:17
DisplayModeMap
map< uint64_t, MythDisplayMode > DisplayModeMap
Definition: mythdisplaymode.h:19
MythDisplayRPI::UsingVideoModes
bool UsingVideoModes(void) override
Definition: mythdisplayrpi.cpp:164
MythEDID::Valid
bool Valid(void) const
Definition: mythedid.cpp:32
MythDisplay::m_videoModes
vector< MythDisplayMode > m_videoModes
Definition: mythdisplay.h:94
MythDisplay::m_modeComplete
bool m_modeComplete
Definition: mythdisplay.h:85
MythDisplayRPI::m_deviceId
int m_deviceId
Definition: mythdisplayrpi.h:37
DisplayToString
static QString DisplayToString(int Id)
Definition: mythdisplayrpi.cpp:36
MythDisplayRPI::m_modeMap
QMap< uint64_t, QPair< uint32_t, uint32_t > > m_modeMap
Definition: mythdisplayrpi.h:38
mythlogging.h
mythdisplayrpi.h
MythDisplay::m_edid
MythEDID m_edid
Definition: mythdisplay.h:90
MythDisplayRPI::GetVideoModes
const vector< MythDisplayMode > & GetVideoModes(void) override
Definition: mythdisplayrpi.cpp:171
MythEDID::IsHDMI
bool IsHDMI(void) const
Definition: mythedid.cpp:62
MythDisplayRPI::m_modeChangeWait
QWaitCondition m_modeChangeWait
Definition: mythdisplayrpi.h:35
MythDisplay::m_waitForModeChanges
bool m_waitForModeChanges
Definition: mythdisplay.h:84
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:57
MythDisplay
Definition: mythdisplay.h:19
MythDisplayMode
Definition: mythdisplaymode.h:22
MythCoreContext::GetBoolSetting
bool GetBoolSetting(const QString &key, bool defaultval=false)
Definition: mythcorecontext.cpp:924
MythDisplayMode::FindBestMatch
static int FindBestMatch(const vector< MythDisplayMode > &Modes, const MythDisplayMode &Mode, double &TargetRate)
Definition: mythdisplaymode.cpp:139
MythDisplay::m_refreshRate
double m_refreshRate
Definition: mythdisplay.h:86
MythEDID::Debug
void Debug(void) const
Definition: mythedid.cpp:376
mythcorecontext.h
MythEDID::DisplaySize
QSize DisplaySize(void) const
Definition: mythedid.cpp:42
MythDisplayRPI::UpdateCurrentMode
void UpdateCurrentMode(void) override
Retrieve screen details.
Definition: mythdisplayrpi.cpp:110
MythDisplayRPI::GetEDID
void GetEDID(void)
Definition: mythdisplayrpi.cpp:134
MythDisplayRPI::MythDisplayRPI
MythDisplayRPI()
Definition: mythdisplayrpi.cpp:53
LOC
#define LOC
Definition: mythdisplayrpi.cpp:11
MythDisplayRPI::m_modeChangeLock
QMutex m_modeChangeLock
Definition: mythdisplayrpi.h:34
MythEDID
Definition: mythedid.h:18
MythDisplayRPI::m_vchiInstance
VCHI_INSTANCE_T m_vchiInstance
Definition: mythdisplayrpi.h:36