1 | /* |
2 | Copyright (c) 2012, Broadcom Europe Ltd |
3 | All rights reserved. |
4 | |
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the following conditions are met: |
7 | * Redistributions of source code must retain the above copyright |
8 | notice, this list of conditions and the following disclaimer. |
9 | * Redistributions in binary form must reproduce the above copyright |
10 | notice, this list of conditions and the following disclaimer in the |
11 | documentation and/or other materials provided with the distribution. |
12 | * Neither the name of the copyright holder nor the |
13 | names of its contributors may be used to endorse or promote products |
14 | derived from this software without specific prior written permission. |
15 | |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY |
20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
26 | */ |
27 | |
28 | #include <stdlib.h> |
29 | #include <math.h> |
30 | #include <EGL/egl.h> |
31 | #include <EGL/eglext.h> |
32 | #include <GLES/gl.h> |
33 | #include <GLES/glext.h> |
34 | #include "applog.h" |
35 | #include "interface/vcos/vcos.h" |
36 | #include "interface/vcos/vcos_stdbool.h" |
37 | #include "interface/khronos/include/EGL/eglext_brcm.h" |
38 | #include "vidtex.h" |
39 | |
40 | /** Max number of simultaneous EGL images supported = max number of distinct video decoder |
41 | * buffers. |
42 | */ |
43 | #define VT_MAX_IMAGES 32 |
44 | |
45 | /** Maximum permitted difference between the number of EGL buffer swaps and number of video |
46 | * frames. |
47 | */ |
48 | #define VT_MAX_FRAME_DISPARITY 2 |
49 | |
50 | /** Mapping of MMAL opaque buffer handle to EGL image */ |
51 | typedef struct VIDTEX_IMAGE_SLOT_T |
52 | { |
53 | /* Decoded video frame, as MMAL opaque buffer handle. NULL => unused slot. */ |
54 | void *video_frame; |
55 | |
56 | /* Corresponding EGL image */ |
57 | EGLImageKHR image; |
58 | } VIDTEX_IMAGE_SLOT_T; |
59 | |
60 | /** |
61 | * Video Texture. Displays video from a URI or camera onto an EGL surface. |
62 | */ |
63 | typedef struct VIDTEX_T |
64 | { |
65 | /** Test options; bitmask of VIDTEX_OPT_XXX values */ |
66 | uint32_t opts; |
67 | |
68 | /* Semaphore to synchronise use of decoded frame (video_frame). */ |
69 | VCOS_SEMAPHORE_T sem_decoded; |
70 | |
71 | /* Semaphore to synchronise drawing of video frame. */ |
72 | VCOS_SEMAPHORE_T sem_drawn; |
73 | |
74 | /* Mutex to guard access to quit field. */ |
75 | VCOS_MUTEX_T mutex; |
76 | |
77 | /* Signal thread to quit. */ |
78 | bool quit; |
79 | |
80 | /* Reason for quitting */ |
81 | uint32_t stop_reason; |
82 | |
83 | /* EGL display/surface/context on which to render the video */ |
84 | EGLDisplay display; |
85 | EGLSurface surface; |
86 | EGLContext context; |
87 | |
88 | /* EGL texture name */ |
89 | GLuint texture; |
90 | |
91 | /* Table of EGL images corresponding to MMAL opaque buffer handles */ |
92 | VIDTEX_IMAGE_SLOT_T slots[VT_MAX_IMAGES]; |
93 | |
94 | /* Current decoded video frame, as MMAL opaque buffer handle. |
95 | * NULL if no buffer currently available. */ |
96 | void *video_frame; |
97 | |
98 | /* Number of EGL buffer swaps */ |
99 | unsigned num_swaps; |
100 | } VIDTEX_T; |
101 | |
102 | /* Vertex co-ordinates: |
103 | * |
104 | * v0----v1 |
105 | * | | |
106 | * | | |
107 | * | | |
108 | * v3----v2 |
109 | */ |
110 | |
111 | static const GLfloat vt_vertices[] = |
112 | { |
113 | #define VT_V0 -0.8, 0.8, 0.8, |
114 | #define VT_V1 0.8, 0.8, 0.8, |
115 | #define VT_V2 0.8, -0.8, 0.8, |
116 | #define VT_V3 -0.8, -0.8, 0.8, |
117 | VT_V0 VT_V3 VT_V2 VT_V2 VT_V1 VT_V0 |
118 | }; |
119 | |
120 | /* Texture co-ordinates: |
121 | * |
122 | * (0,0) b--c |
123 | * | | |
124 | * a--d |
125 | * |
126 | * b,a,d d,c,b |
127 | */ |
128 | static const GLfloat vt_tex_coords[] = |
129 | { |
130 | 0, 0, 0, 1, 1, 1, |
131 | 1, 1, 1, 0, 0, 0 |
132 | }; |
133 | |
134 | /* Local function prototypes */ |
135 | static VIDTEX_T *vidtex_create(EGLNativeWindowType win); |
136 | static void vidtex_destroy(VIDTEX_T *vt); |
137 | static int vidtex_gl_init(VIDTEX_T *vt, EGLNativeWindowType win); |
138 | static void vidtex_gl_term(VIDTEX_T *vt); |
139 | static void vidtex_destroy_images(VIDTEX_T *vt); |
140 | static int vidtex_play(VIDTEX_T *vt, const VIDTEX_PARAMS_T *params); |
141 | static void vidtex_check_gl(VIDTEX_T *vt, uint32_t line); |
142 | static void vidtex_draw(VIDTEX_T *vt, void *video_frame); |
143 | static void vidtex_flush_gl(VIDTEX_T *vt); |
144 | static bool vidtex_set_quit(VIDTEX_T *vt, bool quit); |
145 | static void vidtex_video_frame_cb(void *ctx, void *ob); |
146 | static void vidtex_stop_cb(void *ctx, uint32_t stop_reason); |
147 | |
148 | #define VIDTEX_CHECK_GL(VT) vidtex_check_gl(VT, __LINE__) |
149 | |
150 | /** Create a new vidtex instance */ |
151 | static VIDTEX_T *vidtex_create(EGLNativeWindowType win) |
152 | { |
153 | VIDTEX_T *vt; |
154 | VCOS_STATUS_T st; |
155 | |
156 | vt = vcos_calloc(1, sizeof(*vt), "vidtex" ); |
157 | if (vt == NULL) |
158 | { |
159 | vcos_log_trace("Memory allocation failure" ); |
160 | return NULL; |
161 | } |
162 | |
163 | st = vcos_semaphore_create(&vt->sem_decoded, "vidtex-dec" , 0); |
164 | if (st != VCOS_SUCCESS) |
165 | { |
166 | vcos_log_trace("Error creating semaphore" ); |
167 | goto error_ctx; |
168 | } |
169 | |
170 | st = vcos_semaphore_create(&vt->sem_drawn, "vidtex-drw" , 0); |
171 | if (st != VCOS_SUCCESS) |
172 | { |
173 | vcos_log_trace("Error creating semaphore" ); |
174 | goto error_sem1; |
175 | } |
176 | |
177 | st = vcos_mutex_create(&vt->mutex, "vidtex" ); |
178 | if (st != VCOS_SUCCESS) |
179 | { |
180 | vcos_log_trace("Error creating semaphore" ); |
181 | goto error_sem2; |
182 | } |
183 | |
184 | if (vidtex_gl_init(vt, win) != 0) |
185 | { |
186 | vcos_log_trace("Error initialising EGL" ); |
187 | goto error_mutex; |
188 | } |
189 | |
190 | vt->quit = false; |
191 | vt->stop_reason = 0; |
192 | |
193 | return vt; |
194 | |
195 | error_mutex: |
196 | vcos_mutex_delete(&vt->mutex); |
197 | error_sem2: |
198 | vcos_semaphore_delete(&vt->sem_drawn); |
199 | error_sem1: |
200 | vcos_semaphore_delete(&vt->sem_decoded); |
201 | error_ctx: |
202 | vcos_free(vt); |
203 | return NULL; |
204 | } |
205 | |
206 | /** Destroy a vidtex instance */ |
207 | static void vidtex_destroy(VIDTEX_T *vt) |
208 | { |
209 | vidtex_gl_term(vt); |
210 | vcos_mutex_delete(&vt->mutex); |
211 | vcos_semaphore_delete(&vt->sem_drawn); |
212 | vcos_semaphore_delete(&vt->sem_decoded); |
213 | vcos_free(vt); |
214 | } |
215 | |
216 | /** Init GL using a native window */ |
217 | static int vidtex_gl_init(VIDTEX_T *vt, EGLNativeWindowType win) |
218 | { |
219 | const EGLint attribs[] = |
220 | { |
221 | EGL_RED_SIZE, 8, |
222 | EGL_GREEN_SIZE, 8, |
223 | EGL_BLUE_SIZE, 8, |
224 | EGL_DEPTH_SIZE, 0, |
225 | EGL_NONE |
226 | }; |
227 | EGLConfig config; |
228 | EGLint num_configs; |
229 | |
230 | vt->display = eglGetDisplay(EGL_DEFAULT_DISPLAY); |
231 | eglInitialize(vt->display, 0, 0); |
232 | |
233 | eglChooseConfig(vt->display, attribs, &config, 1, &num_configs); |
234 | |
235 | vt->surface = eglCreateWindowSurface(vt->display, config, win, NULL); |
236 | vt->context = eglCreateContext(vt->display, config, NULL, NULL); |
237 | |
238 | if (!eglMakeCurrent(vt->display, vt->surface, vt->surface, vt->context)) |
239 | { |
240 | vidtex_gl_term(vt); |
241 | return -1; |
242 | } |
243 | |
244 | glGenTextures(1, &vt->texture); |
245 | |
246 | glShadeModel(GL_FLAT); |
247 | glDisable(GL_DITHER); |
248 | glDisable(GL_SCISSOR_TEST); |
249 | glEnable(GL_TEXTURE_EXTERNAL_OES); |
250 | glDisable(GL_TEXTURE_2D); |
251 | |
252 | return 0; |
253 | } |
254 | |
255 | /** Terminate GL */ |
256 | static void vidtex_gl_term(VIDTEX_T *vt) |
257 | { |
258 | vidtex_destroy_images(vt); |
259 | |
260 | /* Delete texture name */ |
261 | glDeleteTextures(1, &vt->texture); |
262 | |
263 | /* Terminate EGL */ |
264 | eglMakeCurrent(vt->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); |
265 | eglDestroyContext(vt->display, vt->context); |
266 | eglDestroySurface(vt->display, vt->surface); |
267 | eglTerminate(vt->display); |
268 | } |
269 | |
270 | /** Destroy all EGL images */ |
271 | static void vidtex_destroy_images(VIDTEX_T *vt) |
272 | { |
273 | VIDTEX_IMAGE_SLOT_T *slot; |
274 | |
275 | for (slot = vt->slots; slot < vt->slots + vcos_countof(vt->slots); slot++) |
276 | { |
277 | slot->video_frame = NULL; |
278 | |
279 | if (slot->image) |
280 | { |
281 | vcos_log_trace("Destroying EGL image %p" , slot->image); |
282 | eglDestroyImageKHR(vt->display, slot->image); |
283 | slot->image = NULL; |
284 | } |
285 | } |
286 | } |
287 | |
288 | /** Play video - from URI or camera - on EGL surface. */ |
289 | static int vidtex_play(VIDTEX_T *vt, const VIDTEX_PARAMS_T *params) |
290 | { |
291 | const char *uri; |
292 | SVP_CALLBACKS_T callbacks; |
293 | SVP_T *svp; |
294 | SVP_OPTS_T opts; |
295 | SVP_STATS_T stats; |
296 | int rv = -1; |
297 | |
298 | uri = (params->uri[0] == '\0') ? NULL : params->uri; |
299 | vt->opts = params->opts; |
300 | callbacks.ctx = vt; |
301 | callbacks.video_frame_cb = vidtex_video_frame_cb; |
302 | callbacks.stop_cb = vidtex_stop_cb; |
303 | opts.duration_ms = params->duration_ms; |
304 | |
305 | svp = svp_create(uri, &callbacks, &opts); |
306 | if (svp) |
307 | { |
308 | /* Reset stats */ |
309 | vt->num_swaps = 0; |
310 | |
311 | /* Run video player until receive quit notification */ |
312 | if (svp_start(svp) == 0) |
313 | { |
314 | while (!vidtex_set_quit(vt, false)) |
315 | { |
316 | vcos_semaphore_wait(&vt->sem_decoded); |
317 | |
318 | if (vt->video_frame) |
319 | { |
320 | vidtex_draw(vt, vt->video_frame); |
321 | vcos_semaphore_post(&vt->sem_drawn); |
322 | } |
323 | } |
324 | |
325 | vcos_semaphore_post(&vt->sem_drawn); |
326 | |
327 | /* Dump stats */ |
328 | svp_get_stats(svp, &stats); |
329 | vcos_log_info("video frames decoded: %6u" , stats.video_frame_count); |
330 | vcos_log_info("EGL buffer swaps: %6u" , vt->num_swaps); |
331 | |
332 | /* Determine status of operation and log errors */ |
333 | if (vt->stop_reason & SVP_STOP_ERROR) |
334 | { |
335 | vcos_log_error("vidtex exiting on error" ); |
336 | } |
337 | else if (vt->num_swaps == 0) |
338 | { |
339 | vcos_log_error("vidtex completed with no EGL buffer swaps" ); |
340 | } |
341 | else if (abs((int)vt->num_swaps - (int)stats.video_frame_count) > VT_MAX_FRAME_DISPARITY) |
342 | { |
343 | vcos_log_error("vidtex completed with %u EGL buffer swaps, but %u video frames" , |
344 | vt->num_swaps, (int)stats.video_frame_count); |
345 | } |
346 | else |
347 | { |
348 | rv = 0; |
349 | } |
350 | } |
351 | |
352 | svp_destroy(svp); |
353 | } |
354 | |
355 | vidtex_flush_gl(vt); |
356 | |
357 | return rv; |
358 | } |
359 | |
360 | /** Check for OpenGL errors - logs any errors and sets quit flag */ |
361 | static void vidtex_check_gl(VIDTEX_T *vt, uint32_t line) |
362 | { |
363 | GLenum error = glGetError(); |
364 | int abort = 0; |
365 | while (error != GL_NO_ERROR) |
366 | { |
367 | vcos_log_error("GL error: line %d error 0x%04x" , line, error); |
368 | abort = 1; |
369 | error = glGetError(); |
370 | } |
371 | if (abort) |
372 | vidtex_stop_cb(vt, SVP_STOP_ERROR); |
373 | } |
374 | |
375 | /* Draw one video frame onto EGL surface. |
376 | * @param vt vidtex instance. |
377 | * @param video_frame MMAL opaque buffer handle for decoded video frame. Can't be NULL. |
378 | */ |
379 | static void vidtex_draw(VIDTEX_T *vt, void *video_frame) |
380 | { |
381 | EGLImageKHR image; |
382 | VIDTEX_IMAGE_SLOT_T *slot; |
383 | static uint32_t frame_num = 0; |
384 | |
385 | vcos_assert(video_frame); |
386 | |
387 | glClearColor(0, 0, 0, 0); |
388 | glClearDepthf(1); |
389 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
390 | glLoadIdentity(); |
391 | |
392 | glBindTexture(GL_TEXTURE_EXTERNAL_OES, vt->texture); |
393 | VIDTEX_CHECK_GL(vt); |
394 | |
395 | /* Lookup or create EGL image corresponding to supplied buffer handle. |
396 | * N.B. Slot array is filled in sequentially, with the images all destroyed together on |
397 | * vidtex termination; it never has holes. */ |
398 | image = EGL_NO_IMAGE_KHR; |
399 | |
400 | for (slot = vt->slots; slot < vt->slots + vcos_countof(vt->slots); slot++) |
401 | { |
402 | if (slot->video_frame == video_frame) |
403 | { |
404 | vcos_assert(slot->image); |
405 | image = slot->image; |
406 | break; |
407 | } |
408 | |
409 | if (slot->video_frame == NULL) |
410 | { |
411 | EGLenum target; |
412 | vcos_assert(slot->image == NULL); |
413 | |
414 | if (vt->opts & VIDTEX_OPT_Y_TEXTURE) |
415 | target = EGL_IMAGE_BRCM_MULTIMEDIA_Y; |
416 | else if (vt->opts & VIDTEX_OPT_U_TEXTURE) |
417 | target = EGL_IMAGE_BRCM_MULTIMEDIA_U; |
418 | else if (vt->opts & VIDTEX_OPT_V_TEXTURE) |
419 | target = EGL_IMAGE_BRCM_MULTIMEDIA_V; |
420 | else |
421 | target = EGL_IMAGE_BRCM_MULTIMEDIA; |
422 | |
423 | image = eglCreateImageKHR(vt->display, EGL_NO_CONTEXT, target, |
424 | (EGLClientBuffer)video_frame, NULL); |
425 | if (image == EGL_NO_IMAGE_KHR) |
426 | { |
427 | vcos_log_error("EGL image conversion error" ); |
428 | } |
429 | else |
430 | { |
431 | vcos_log_trace("Created EGL image %p for buf %p" , image, video_frame); |
432 | slot->video_frame = video_frame; |
433 | slot->image = image; |
434 | } |
435 | VIDTEX_CHECK_GL(vt); |
436 | |
437 | break; |
438 | } |
439 | } |
440 | |
441 | if (slot == vt->slots + vcos_countof(vt->slots)) |
442 | { |
443 | vcos_log_error("Exceeded configured max number of EGL images" ); |
444 | } |
445 | |
446 | /* Draw the EGL image */ |
447 | if (image != EGL_NO_IMAGE_KHR) |
448 | { |
449 | /* Assume 30fps */ |
450 | int frames_per_rev = 30 * 15; |
451 | GLfloat angle = (frame_num * 360) / (GLfloat) frames_per_rev; |
452 | frame_num = (frame_num + 1) % frames_per_rev; |
453 | |
454 | glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image); |
455 | VIDTEX_CHECK_GL(vt); |
456 | |
457 | glRotatef(angle, 0.0, 0.0, 1.0); |
458 | glEnableClientState(GL_VERTEX_ARRAY); |
459 | glVertexPointer(3, GL_FLOAT, 0, vt_vertices); |
460 | glDisableClientState(GL_COLOR_ARRAY); |
461 | glEnableClientState(GL_TEXTURE_COORD_ARRAY); |
462 | glTexCoordPointer(2, GL_FLOAT, 0, vt_tex_coords); |
463 | |
464 | glDrawArrays(GL_TRIANGLES, 0, vcos_countof(vt_tex_coords) / 2); |
465 | |
466 | eglSwapBuffers(vt->display, vt->surface); |
467 | |
468 | if (vt->opts & VIDTEX_OPT_IMG_PER_FRAME) |
469 | { |
470 | vidtex_destroy_images(vt); |
471 | } |
472 | |
473 | vt->num_swaps++; |
474 | } |
475 | |
476 | VIDTEX_CHECK_GL(vt); |
477 | } |
478 | |
479 | /** Do some GL stuff in order to ensure that any multimedia-related GL buffers have been released |
480 | * if they are going to be released. |
481 | */ |
482 | static void vidtex_flush_gl(VIDTEX_T *vt) |
483 | { |
484 | int i; |
485 | |
486 | glFlush(); |
487 | glClearColor(0, 0, 0, 0); |
488 | |
489 | for (i = 0; i < 10; i++) |
490 | { |
491 | glClear(GL_COLOR_BUFFER_BIT); |
492 | eglSwapBuffers(vt->display, vt->surface); |
493 | VIDTEX_CHECK_GL(vt); |
494 | } |
495 | |
496 | glFlush(); |
497 | VIDTEX_CHECK_GL(vt); |
498 | } |
499 | |
500 | /** Set quit flag, with locking. |
501 | * @param quit New value of the quit flag: true - command thread to quit; false - command thread |
502 | * to continue. |
503 | * @return Old value of the quit flag. |
504 | */ |
505 | static bool vidtex_set_quit(VIDTEX_T *vt, bool quit) |
506 | { |
507 | vcos_mutex_lock(&vt->mutex); |
508 | bool old_quit = vt->quit; |
509 | vt->quit = quit; |
510 | vcos_mutex_unlock(&vt->mutex); |
511 | |
512 | return old_quit; |
513 | } |
514 | |
515 | /** Callback to receive decoded video frame */ |
516 | static void vidtex_video_frame_cb(void *ctx, void *ob) |
517 | { |
518 | if (ob) |
519 | { |
520 | VIDTEX_T *vt = ctx; |
521 | /* coverity[missing_lock] Coverity gets confused by the semaphore locking scheme */ |
522 | vt->video_frame = ob; |
523 | vcos_semaphore_post(&vt->sem_decoded); |
524 | vcos_semaphore_wait(&vt->sem_drawn); |
525 | vt->video_frame = NULL; |
526 | } |
527 | } |
528 | |
529 | /** Callback to receive stop notification. Sets quit flag and posts semaphore. |
530 | * @param ctx VIDTEX_T instance. Declared as void * in order to use as SVP callback. |
531 | * @param stop_reason SVP stop reason. |
532 | */ |
533 | static void vidtex_stop_cb(void *ctx, uint32_t stop_reason) |
534 | { |
535 | VIDTEX_T *vt = ctx; |
536 | vt->stop_reason = stop_reason; |
537 | vidtex_set_quit(vt, true); |
538 | vcos_semaphore_post(&vt->sem_decoded); |
539 | } |
540 | |
541 | /* Convenience function to create/play/destroy */ |
542 | int vidtex_run(const VIDTEX_PARAMS_T *params, EGLNativeWindowType win) |
543 | { |
544 | VIDTEX_T *vt; |
545 | int rv = -1; |
546 | |
547 | vt = vidtex_create(win); |
548 | if (vt) |
549 | { |
550 | rv = vidtex_play(vt, params); |
551 | vidtex_destroy(vt); |
552 | } |
553 | |
554 | return rv; |
555 | } |
556 | |