1/*
2Copyright (c) 2012, Broadcom Europe Ltd
3All rights reserved.
4
5Redistribution and use in source and binary forms, with or without
6modification, 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
16THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
20DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23ON 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
25SOFTWARE, 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 */
51typedef 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 */
63typedef 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
111static 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 */
128static 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 */
135static VIDTEX_T *vidtex_create(EGLNativeWindowType win);
136static void vidtex_destroy(VIDTEX_T *vt);
137static int vidtex_gl_init(VIDTEX_T *vt, EGLNativeWindowType win);
138static void vidtex_gl_term(VIDTEX_T *vt);
139static void vidtex_destroy_images(VIDTEX_T *vt);
140static int vidtex_play(VIDTEX_T *vt, const VIDTEX_PARAMS_T *params);
141static void vidtex_check_gl(VIDTEX_T *vt, uint32_t line);
142static void vidtex_draw(VIDTEX_T *vt, void *video_frame);
143static void vidtex_flush_gl(VIDTEX_T *vt);
144static bool vidtex_set_quit(VIDTEX_T *vt, bool quit);
145static void vidtex_video_frame_cb(void *ctx, void *ob);
146static 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 */
151static 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
195error_mutex:
196 vcos_mutex_delete(&vt->mutex);
197error_sem2:
198 vcos_semaphore_delete(&vt->sem_drawn);
199error_sem1:
200 vcos_semaphore_delete(&vt->sem_decoded);
201error_ctx:
202 vcos_free(vt);
203 return NULL;
204}
205
206/** Destroy a vidtex instance */
207static 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 */
217static 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 */
256static 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 */
271static 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. */
289static 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 */
361static 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 */
379static 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 */
482static 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 */
505static 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 */
516static 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 */
533static 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 */
542int 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