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
39namespace media = core::ubuntu::media;
41
42using 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
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
155 prom_buff{},
156 fut_buff{prom_buff.get_future()},
157 sock_fd{socket(AF_UNIX, SOCK_DGRAM, 0)},
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 ||
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);
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;
270 EGLImageKHR egl_image;
272 PFNEGLCREATEIMAGEKHRPROC _eglCreateImageKHR;
273 PFNEGLDESTROYIMAGEKHRPROC _eglDestroyImageKHR;
274 PFNGLEGLIMAGETARGETTEXTURE2DOESPROC _glEGLImageTargetTexture2DOES;
275};
276
277function<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
286video::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
296const core::Signal<void>& video::EglSink::frame_available() const
297{
298 return d->frame_available;
299}
300
301bool 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
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}
bool transformation_matrix(float *matrix) const override
Queries the 4x4 transformation matrix for the current frame, placing the data into 'matrix'.
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...
static std::function< video::Sink::Ptr(std::uint32_t)> factory_for_key(const media::Player::PlayerKey &)
bool swap_buffers() const override
Releases the current buffer, and consumes the next buffer in the queue, making it available for consu...
#define MH_ERROR(...)
Definition: logger.h:128
#define MH_WARNING(...)
Definition: logger.h:127
#define MH_DEBUG(...)
Definition: logger.h:123
std::shared_ptr< Sink > Ptr
To save us some typing.
Definition: sink.h:39
promise< BufferData > prom_buff
Definition: egl_sink.cpp:265
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
PFNEGLDESTROYIMAGEKHRPROC _eglDestroyImageKHR
Definition: egl_sink.cpp:273
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC _glEGLImageTargetTexture2DOES
Definition: egl_sink.cpp:274
bool find_extension(const string &extensions, const string &ext)
Definition: egl_sink.cpp:137
core::Signal< void > frame_available
Definition: egl_sink.cpp:267
PFNEGLCREATEIMAGEKHRPROC _eglCreateImageKHR
Definition: egl_sink.cpp:272
Private(uint32_t gl_texture, const media::Player::PlayerKey key)
Definition: egl_sink.cpp:153
static bool receive_buff(int socket, BufferData *data)
Definition: egl_sink.cpp:47
future< BufferData > fut_buff
Definition: egl_sink.cpp:266
bool import_buffer(const BufferData *buf_data)
Definition: egl_sink.cpp:230