Music Hub  ..
A session-wide music playback service
egl_sink.cpp
Go to the documentation of this file.
1 /*
2  * Copyright © 2017 Canonical Ltd.
3  *
4  * This program is free software: you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License version 3,
6  * as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * Authored by: Alfonso Sanchez-Beato <alfonso.sanchez-beato@canonical.com>
17  */
18 
20 
23 
24 #include <EGL/egl.h>
25 #include <EGL/eglext.h>
26 #include <GLES2/gl2.h>
27 #include <GLES2/gl2ext.h>
28 
29 #include <sys/types.h>
30 #include <sys/socket.h>
31 #include <sys/un.h>
32 
33 #include <sstream>
34 #include <thread>
35 #include <future>
36 #include <cstring>
37 #include <unistd.h>
38 
39 namespace media = core::ubuntu::media;
41 
42 using namespace std;
43 
45 {
46 
47  static bool receive_buff(int socket, BufferData *data)
48  {
49  struct msghdr msg{};
50  struct iovec io = { .iov_base = &data->meta,
51  .iov_len = sizeof data->meta };
52  char c_buffer[256];
53  ssize_t res;
54 
55  msg.msg_iov = &io;
56  msg.msg_iovlen = 1;
57 
58  msg.msg_control = c_buffer;
59  msg.msg_controllen = sizeof c_buffer;
60 
61  if ((res = recvmsg(socket, &msg, 0)) == -1) {
62  MH_ERROR("Failed to receive message");
63  return false;
64  } else if (res == 0) {
65  MH_ERROR("Socket shutdown while receiving buffer data");
66  return false;
67  }
68 
69  struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
70 
71  memmove(&data->fd, CMSG_DATA(cmsg), sizeof data->fd);
72 
73  MH_DEBUG("Extracted fd %d", data->fd);
74  MH_DEBUG("width %d", data->meta.width);
75  MH_DEBUG("height %d", data->meta.height);
76  MH_DEBUG("fourcc 0x%X", data->meta.fourcc);
77  MH_DEBUG("stride %d", data->meta.stride);
78  MH_DEBUG("offset %d", data->meta.offset);
79 
80  return true;
81  }
82 
84  int sock_fd,
85  promise<BufferData>& prom_buff,
86  core::Signal<void>& frame_available)
87  {
88  static const char *consumer_socket = "media-consumer";
89 
90  struct sockaddr_un local;
91  int len;
92  BufferData buff_data;
93 
94  if (sock_fd == -1) {
95  MH_ERROR("Cannot create buffer consumer socket: %s (%d)",
96  strerror(errno), errno);
97  return;
98  }
99 
100  ostringstream sock_name_ss;
101  sock_name_ss << consumer_socket << key;
102  local.sun_family = AF_UNIX;
103  local.sun_path[0] = '\0';
104  strcpy(local.sun_path + 1, sock_name_ss.str().c_str());
105  len = sizeof(local.sun_family) + sock_name_ss.str().length() + 1;
106  if (bind(sock_fd, (struct sockaddr *) &local, len) == -1) {
107  MH_ERROR("Cannot bind consumer socket: %s (%d)",
108  strerror(errno), errno);
109  return;
110  }
111 
112  // Wait for buffer descriptions, pass them to rendering thread
113  if (!receive_buff(sock_fd, &buff_data))
114  return;
115 
116  prom_buff.set_value(buff_data);
117 
118  // Now signal frame syncs
119  while(true) {
120  ssize_t res;
121  char c;
122 
123  res = recv(sock_fd, &c, sizeof c, 0);
124  if (res == -1) {
125  MH_ERROR("while waiting sync: %s (%d)",
126  strerror(errno), errno);
127  return;
128  } else if (res == 0) {
129  MH_DEBUG("Socket shutdown");
130  return;
131  }
132 
133  frame_available();
134  }
135  }
136 
137  bool find_extension(const string& extensions, const string& ext)
138  {
139  size_t len_all = extensions.length();
140  size_t len = ext.length();
141  size_t pos = 0;
142 
143  while ((pos = extensions.find(ext, pos)) != string::npos) {
144  if (pos + len == len_all || extensions[pos + len] == ' ')
145  return true;
146 
147  pos = pos + len;
148  }
149 
150  return false;
151  }
152 
153  Private(uint32_t gl_texture, const media::Player::PlayerKey key)
154  : gl_texture{gl_texture},
155  prom_buff{},
156  fut_buff{prom_buff.get_future()},
157  sock_fd{socket(AF_UNIX, SOCK_DGRAM, 0)},
158  sock_thread{read_sock_events, key, sock_fd,
159  ref(prom_buff), ref(frame_available)},
160  egl_image{EGL_NO_IMAGE_KHR},
161  buf_fd{-1}
162  {
163  const char *extensions;
164  const char *egl_needed[] = {"EGL_KHR_image_base",
165  "EGL_EXT_image_dma_buf_import"};
166  EGLDisplay egl_display = eglGetCurrentDisplay();
167  size_t i;
168 
169  // Retrieve EGL extensions from current display, then make sure the ones
170  // we need are present.
171  extensions = eglQueryString (egl_display, EGL_EXTENSIONS);
172  if (!extensions)
173  throw runtime_error {"Error querying EGL extensions"};
174 
175  for (i = 0; i < sizeof(egl_needed)/sizeof(egl_needed[0]); ++i) {
176  if (!find_extension(extensions, egl_needed[i])) {
177  MH_DEBUG("%s not supported", egl_needed[i]);
178  //ostringstream oss;
179  //oss << egl_needed[i] << " not supported";
180  // TODO: The returned extensions do not really reflect what is
181  // supported by the system, and do not include the ones we need.
182  // It is probably related to how qt initializes EGL, because
183  // mirsink does not show this problem. So we need to
184  // check why extensions is different from es2_info output.
185  //throw runtime_error {oss.str().c_str()};
186  }
187  }
188 
189  // TODO this returns a NULL pointer, probably same issue as with eglQueryString
190  // extensions = reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS));
191  // if (!extensions)
192  // throw runtime_error {"Error querying OpenGL ES extensions"};
193 
194  // if (!find_extension(extensions, "GL_OES_EGL_image_external"))
195  // throw runtime_error {"GL_OES_EGL_image_external is not supported"};
196 
197  // Dynamically load functions from extensions (they are not
198  // automatically exported by EGL library).
199  _eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)
200  eglGetProcAddress("eglCreateImageKHR");
201  _eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)
202  eglGetProcAddress("eglDestroyImageKHR");
203  _glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)
204  eglGetProcAddress("glEGLImageTargetTexture2DOES");
205 
206  if (_eglCreateImageKHR == nullptr || _eglDestroyImageKHR == nullptr ||
207  _glEGLImageTargetTexture2DOES == nullptr)
208  throw runtime_error {"Error when loading extensions"};
209  }
210 
212  {
213  if (sock_fd != -1) {
214  shutdown(sock_fd, SHUT_RDWR);
215  sock_thread.join();
216  close(sock_fd);
217  }
218 
219  if (buf_fd != -1)
220  close(buf_fd);
221 
222  if (egl_image != EGL_NO_IMAGE_KHR)
223  _eglDestroyImageKHR(eglGetCurrentDisplay(), egl_image);
224  }
225 
226  // This imports dma_buf buffers by using the EGL_EXT_image_dma_buf_import
227  // extension. The buffers have been previously exported in mirsink, using
228  // EGL_MESA_image_dma_buf_export extension. After that, we bind the buffer
229  // to the app texture by using GL_OES_EGL_image_external extension.
230  bool import_buffer(const BufferData *buf_data)
231  {
232  GLenum err;
233  EGLDisplay egl_display = eglGetCurrentDisplay();
234  EGLint image_attrs[] = {
235  EGL_WIDTH, buf_data->meta.width,
236  EGL_HEIGHT, buf_data->meta.height,
237  EGL_LINUX_DRM_FOURCC_EXT, buf_data->meta.fourcc,
238  EGL_DMA_BUF_PLANE0_FD_EXT, buf_data->fd,
239  EGL_DMA_BUF_PLANE0_OFFSET_EXT, buf_data->meta.offset,
240  EGL_DMA_BUF_PLANE0_PITCH_EXT, buf_data->meta.stride,
241  EGL_NONE
242  };
243 
244  buf_fd = buf_data->fd;
245  egl_image = _eglCreateImageKHR(egl_display, EGL_NO_CONTEXT,
246  EGL_LINUX_DMA_BUF_EXT, NULL, image_attrs);
247  if (egl_image == EGL_NO_IMAGE_KHR) {
248  MH_ERROR("eglCreateImageKHR error 0x%X", eglGetError());
249  return false;
250  }
251 
252  // TODO Do this when swapping if we end up importing more than one buffer
253  glBindTexture(GL_TEXTURE_2D, gl_texture);
254  _glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, egl_image);
255 
256  while((err = glGetError()) != GL_NO_ERROR)
257  MH_WARNING("OpenGL error 0x%X", err);
258 
259  MH_DEBUG("Image successfully imported");
260 
261  return true;
262  }
263 
264  uint32_t gl_texture;
265  promise<BufferData> prom_buff;
266  future<BufferData> fut_buff;
267  core::Signal<void> frame_available;
268  int sock_fd;
269  thread sock_thread;
270  EGLImageKHR egl_image;
271  int buf_fd;
272  PFNEGLCREATEIMAGEKHRPROC _eglCreateImageKHR;
273  PFNEGLDESTROYIMAGEKHRPROC _eglDestroyImageKHR;
274  PFNGLEGLIMAGETARGETTEXTURE2DOESPROC _glEGLImageTargetTexture2DOES;
275 };
276 
277 function<video::Sink::Ptr(uint32_t)>
279 {
280  return [key](uint32_t texture)
281  {
282  return video::Sink::Ptr{new video::EglSink{texture, key}};
283  };
284 }
285 
286 video::EglSink::EglSink(uint32_t gl_texture,
287  const media::Player::PlayerKey key)
288  : d{new Private{gl_texture, key}}
289 {
290 }
291 
293 {
294 }
295 
296 const core::Signal<void>& video::EglSink::frame_available() const
297 {
298  return d->frame_available;
299 }
300 
301 bool video::EglSink::transformation_matrix(float *matrix) const
302 {
303  // TODO: Can we get orientation on unity8 desktop somehow?
304  static const float identity_4x4[] = { 1, 0, 0, 0,
305  0, 1, 0, 0,
306  0, 0, 1, 0,
307  0, 0, 0, 1 };
308 
309  memcpy(matrix, identity_4x4, sizeof identity_4x4);
310  return true;
311 }
312 
313 bool video::EglSink::swap_buffers() const
314 {
315  // First time called, import buffers
316  if (d->egl_image == EGL_NO_IMAGE_KHR) {
317  BufferData buf_data = d->fut_buff.get();
318  if (!d->import_buffer(&buf_data))
319  return false;
320  }
321 
322  // We need to do nothing here, as the only buffer has already been mapped.
323  // TODO Change when we implement a buffer queue.
324 
325  return true;
326 }
static void read_sock_events(const media::Player::PlayerKey key, int sock_fd, promise< BufferData > &prom_buff, core::Signal< void > &frame_available)
Definition: egl_sink.cpp:83
bool transformation_matrix(float *matrix) const override
Queries the 4x4 transformation matrix for the current frame, placing the data into &#39;matrix&#39;...
STL namespace.
#define MH_ERROR(...)
Definition: logger.h:128
bool find_extension(const string &extensions, const string &ext)
Definition: egl_sink.cpp:137
#define MH_DEBUG(...)
Definition: logger.h:123
static std::function< video::Sink::Ptr(std::uint32_t)> factory_for_key(const media::Player::PlayerKey &)
#define MH_WARNING(...)
Definition: logger.h:127
std::shared_ptr< Sink > Ptr
To save us some typing.
Definition: sink.h:39
bool import_buffer(const BufferData *buf_data)
Definition: egl_sink.cpp:230
PFNEGLCREATEIMAGEKHRPROC _eglCreateImageKHR
Definition: egl_sink.cpp:272
PFNEGLDESTROYIMAGEKHRPROC _eglDestroyImageKHR
Definition: egl_sink.cpp:273
static bool receive_buff(int socket, BufferData *data)
Definition: egl_sink.cpp:47
bool swap_buffers() const override
Releases the current buffer, and consumes the next buffer in the queue, making it available for consu...
const core::Signal< void > & frame_available() const override
The signal is emitted whenever a new frame is available and a subsequent call to swap_buffers will no...
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC _glEGLImageTargetTexture2DOES
Definition: egl_sink.cpp:274
future< BufferData > fut_buff
Definition: egl_sink.cpp:266
promise< BufferData > prom_buff
Definition: egl_sink.cpp:265
Private(uint32_t gl_texture, const media::Player::PlayerKey key)
Definition: egl_sink.cpp:153
core::Signal< void > frame_available
Definition: egl_sink.cpp:267