1/*
2Copyright (c) 2013, Broadcom Europe Ltd
3Copyright (c) 2013, Tim Gover
4All rights reserved.
5
6Redistribution and use in source and binary forms, with or without
7modification, are permitted provided that the following conditions are met:
8 * Redistributions of source code must retain the above copyright
9 notice, this list of conditions and the following disclaimer.
10 * Redistributions in binary form must reproduce the above copyright
11 notice, this list of conditions and the following disclaimer in the
12 documentation and/or other materials provided with the distribution.
13 * Neither the name of the copyright holder nor the
14 names of its contributors may be used to endorse or promote products
15 derived from this software without specific prior written permission.
16
17THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
21DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27*/
28
29#include "RaspiTex.h"
30#include "RaspiCLI.h"
31#include <math.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <sys/time.h>
36#include <EGL/egl.h>
37#include <EGL/eglext.h>
38#include <GLES/gl.h>
39#include <GLES/glext.h>
40#include "RaspiTexUtil.h"
41#include "interface/vcos/vcos.h"
42#include "interface/mmal/mmal_buffer.h"
43#include "interface/mmal/util/mmal_util.h"
44#include "interface/mmal/util/mmal_util_params.h"
45#include "tga.h"
46
47#include "gl_scenes/mirror.h"
48#include "gl_scenes/sobel.h"
49#include "gl_scenes/square.h"
50#include "gl_scenes/teapot.h"
51#include "gl_scenes/vcsm_square.h"
52#include "gl_scenes/yuv.h"
53
54/**
55 * \file RaspiTex.c
56 *
57 * A simple framework for extending a MMAL application to render buffers via
58 * OpenGL.
59 *
60 * MMAL buffers are often in YUV colour space and in either a planar or
61 * tile format which is not supported directly by V3D. Instead of copying
62 * the buffer from the GPU and doing a colour space / pixel format conversion
63 * the GL_OES_EGL_image_external is used. This allows an EGL image to be
64 * created from GPU buffer handle (MMAL opaque buffer handle). The EGL image
65 * may then be used to create a texture (glEGLImageTargetTexture2DOES) and
66 * drawn by either OpenGL ES 1.0 or 2.0 contexts.
67 *
68 * Notes:
69 * 1) GL_OES_EGL_image_external textures always return pixels in RGBA format.
70 * This is also the case when used from a fragment shader.
71 *
72 * 2) The driver implementation creates a new RGB_565 buffer and does the color
73 * space conversion from YUV. This happens in GPU memory using the vector
74 * processor.
75 *
76 * 3) Each EGL external image in use will consume GPU memory for the RGB 565
77 * buffer. In addition, the GL pipeline might require more than one EGL image
78 * to be retained in GPU memory until the drawing commands are flushed.
79 *
80 * Typically 128 MB of GPU memory is sufficient for 720p viewfinder and 720p
81 * GL surface. If both the viewfinder and the GL surface are 1080p then
82 * 256MB of GPU memory is recommended, otherwise, for non-trivial scenes
83 * the system can run out of GPU memory whilst the camera is running.
84 *
85 * 4) It is important to make sure that the MMAL opaque buffer is not returned
86 * to MMAL before the GL driver has completed the asynchronous call to
87 * glEGLImageTargetTexture2DOES. Deferring destruction of the EGL image and
88 * the buffer return to MMAL until after eglSwapBuffers is the recommended.
89 *
90 * See also: http://www.khronos.org/registry/gles/extensions/OES/OES_EGL_image_external.txt
91 */
92
93#define DEFAULT_WIDTH 640
94#define DEFAULT_HEIGHT 480
95
96enum
97{
98 CommandGLScene,
99 CommandGLWin
100};
101
102static COMMAND_LIST cmdline_commands[] =
103{
104 { CommandGLScene, "-glscene", "gs", "GL scene square,teapot,mirror,yuv,sobel,vcsm_square", 1 },
105 { CommandGLWin, "-glwin", "gw", "GL window settings <'x,y,w,h'>", 1 },
106};
107
108static int cmdline_commands_size = sizeof(cmdline_commands) / sizeof(cmdline_commands[0]);
109
110/**
111 * Parse a possible command pair - command and parameter
112 * @param arg1 Command
113 * @param arg2 Parameter (could be NULL)
114 * @return How many parameters were used, 0,1,2
115 */
116int raspitex_parse_cmdline(RASPITEX_STATE *state,
117 const char *arg1, const char *arg2)
118{
119 int command_id, used = 0, num_parameters;
120
121 if (!arg1)
122 return 0;
123
124 command_id = raspicli_get_command_id(cmdline_commands,
125 cmdline_commands_size, arg1, &num_parameters);
126
127 // If invalid command, or we are missing a parameter, drop out
128 if (command_id==-1 || (command_id != -1 && num_parameters > 0 && arg2 == NULL))
129 return 0;
130
131 switch (command_id)
132 {
133 case CommandGLWin: // Allows a GL window to be different to preview-res
134 {
135 int tmp;
136 tmp = sscanf(arg2, "%d,%d,%d,%d",
137 &state->x, &state->y, &state->width, &state->height);
138 if (tmp != 4)
139 {
140 // Default to safe size on parse error
141 state->x = state->y = 0;
142 state->width = DEFAULT_WIDTH;
143 state->height = DEFAULT_HEIGHT;
144 }
145 else
146 {
147 state->gl_win_defined = 1;
148 }
149
150 used = 2;
151 break;
152 }
153
154 case CommandGLScene: // Selects the GL scene
155 {
156 if (strcmp(arg2, "square") == 0)
157 state->scene_id = RASPITEX_SCENE_SQUARE;
158 else if (strcmp(arg2, "teapot") == 0)
159 state->scene_id = RASPITEX_SCENE_TEAPOT;
160 else if (strcmp(arg2, "mirror") == 0)
161 state->scene_id = RASPITEX_SCENE_MIRROR;
162 else if (strcmp(arg2, "yuv") == 0)
163 state->scene_id = RASPITEX_SCENE_YUV;
164 else if (strcmp(arg2, "sobel") == 0)
165 state->scene_id = RASPITEX_SCENE_SOBEL;
166 else if (strcmp(arg2, "vcsm_square") == 0)
167 state->scene_id = RASPITEX_SCENE_VCSM_SQUARE;
168 else
169 vcos_log_error("Unknown scene %s", arg2);
170
171 used = 2;
172 break;
173 }
174 }
175 return used;
176}
177
178/**
179 * Display help for command line options
180 */
181void raspitex_display_help()
182{
183 fprintf(stdout, "\nGL parameter commands\n\n");
184 raspicli_display_help(cmdline_commands, cmdline_commands_size);
185}
186
187static void update_fps()
188{
189 static int frame_count = 0;
190 static long long time_start = 0;
191 long long time_now;
192 struct timeval te;
193 float fps;
194
195 frame_count++;
196
197 gettimeofday(&te, NULL);
198 time_now = te.tv_sec * 1000LL + te.tv_usec / 1000;
199
200 if (time_start == 0)
201 {
202 time_start = time_now;
203 }
204 else if (time_now - time_start > 5000)
205 {
206 fps = (float) frame_count / ((time_now - time_start) / 1000.0);
207 frame_count = 0;
208 time_start = time_now;
209 vcos_log_error("%3.2f FPS", fps);
210 }
211}
212
213/**
214 * Captures the frame-buffer if requested.
215 * @param state RASPITEX STATE
216 * @return Zero if successful.
217 */
218static void raspitex_do_capture(RASPITEX_STATE *state)
219{
220 uint8_t *buffer = NULL;
221 size_t size = 0;
222
223 if (state->capture.request)
224 {
225 if (state->ops.capture(state, &buffer, &size) == 0)
226 {
227 /* Pass ownership of buffer to main thread via capture state */
228 state->capture.buffer = buffer;
229 state->capture.size = size;
230 }
231 else
232 {
233 state->capture.buffer = NULL; // Null indicates an error
234 state->capture.size = 0;
235 }
236
237 state->capture.request = 0; // Always clear request and post sem
238 vcos_semaphore_post(&state->capture.completed_sem);
239 }
240}
241
242/**
243 * Checks if there is at least one valid EGL image.
244 * @param state RASPITEX STATE
245 * @return Zero if successful.
246 */
247static int check_egl_image(RASPITEX_STATE *state)
248{
249 if (state->egl_image == EGL_NO_IMAGE_KHR &&
250 state->y_egl_image == EGL_NO_IMAGE_KHR &&
251 state->u_egl_image == EGL_NO_IMAGE_KHR &&
252 state->v_egl_image == EGL_NO_IMAGE_KHR)
253 return -1;
254 else
255 return 0;
256}
257
258/**
259 * Draws the next preview frame. If a new preview buffer is available then the
260 * preview texture is updated first.
261 *
262 * @param state RASPITEX STATE
263 * @param video_frame MMAL buffer header containing the opaque buffer handle.
264 * @return Zero if successful.
265 */
266static int raspitex_draw(RASPITEX_STATE *state, MMAL_BUFFER_HEADER_T *buf)
267{
268 int rc = 0;
269
270 /* If buf is non-NULL then there is a new viewfinder frame available
271 * from the camera so the texture should be updated.
272 *
273 * Although it's possible to have multiple textures mapped to different
274 * viewfinder frames this can consume a lot of GPU memory for high-resolution
275 * viewfinders.
276 */
277 if (buf)
278 {
279 /* Update the texture to the new viewfinder image which should */
280 if (state->ops.update_texture)
281 {
282 rc = state->ops.update_texture(state, (EGLClientBuffer) buf->data);
283 if (rc != 0)
284 {
285 vcos_log_error("%s: Failed to update RGBX texture %d",
286 VCOS_FUNCTION, rc);
287 goto end;
288 }
289 }
290
291 if (state->ops.update_y_texture)
292 {
293 rc = state->ops.update_y_texture(state, (EGLClientBuffer) buf->data);
294 if (rc != 0)
295 {
296 vcos_log_error("%s: Failed to update Y' plane texture %d", VCOS_FUNCTION, rc);
297 goto end;
298 }
299 }
300
301 if (state->ops.update_u_texture)
302 {
303 rc = state->ops.update_u_texture(state, (EGLClientBuffer) buf->data);
304 if (rc != 0)
305 {
306 vcos_log_error("%s: Failed to update U plane texture %d", VCOS_FUNCTION, rc);
307 goto end;
308 }
309 }
310
311 if (state->ops.update_v_texture)
312 {
313 rc = state->ops.update_v_texture(state, (EGLClientBuffer) buf->data);
314 if (rc != 0)
315 {
316 vcos_log_error("%s: Failed to update V texture %d", VCOS_FUNCTION, rc);
317 goto end;
318 }
319 }
320
321 /* Now return the PREVIOUS MMAL buffer header back to the camera preview. */
322 if (state->preview_buf)
323 mmal_buffer_header_release(state->preview_buf);
324
325 state->preview_buf = buf;
326 }
327
328 /* Do the drawing */
329 if (check_egl_image(state) == 0)
330 {
331 rc = state->ops.update_model(state);
332 if (rc != 0)
333 goto end;
334
335 rc = state->ops.redraw(state);
336 if (rc != 0)
337 goto end;
338
339 raspitex_do_capture(state);
340
341 eglSwapBuffers(state->display, state->surface);
342 update_fps();
343 }
344 else
345 {
346 // vcos_log_trace("%s: No preview image", VCOS_FUNCTION);
347 }
348
349end:
350 return rc;
351}
352
353/**
354 * Process preview buffers.
355 *
356 * Dequeue each available preview buffer in order and call current redraw
357 * function. If no new buffers are available then the render function is
358 * invoked anyway.
359 * @param state The GL preview window state.
360 * @return Zero if successful.
361 */
362static int preview_process_returned_bufs(RASPITEX_STATE* state)
363{
364 MMAL_BUFFER_HEADER_T *buf;
365 int new_frame = 0;
366 int rc = 0;
367
368 while ((buf = mmal_queue_get(state->preview_queue)) != NULL)
369 {
370 if (state->preview_stop == 0)
371 {
372 new_frame = 1;
373 rc = raspitex_draw(state, buf);
374 if (rc != 0)
375 {
376 vcos_log_error("%s: Error drawing frame. Stopping.", VCOS_FUNCTION);
377 state->preview_stop = 1;
378 return rc;
379 }
380 }
381 }
382
383 /* If there were no new frames then redraw the scene again with the previous
384 * texture. Otherwise, go round the loop again to see if any new buffers
385 * are returned.
386 */
387 if (! new_frame)
388 rc = raspitex_draw(state, NULL);
389 return rc;
390}
391
392/** Preview worker thread.
393 * Ensures camera preview is supplied with buffers and sends preview frames to GL.
394 * @param arg Pointer to state.
395 * @return NULL always.
396 */
397static void *preview_worker(void *arg)
398{
399 RASPITEX_STATE* state = arg;
400 MMAL_PORT_T *preview_port = state->preview_port;
401 MMAL_BUFFER_HEADER_T *buf;
402 MMAL_STATUS_T st;
403 int rc;
404
405 vcos_log_trace("%s: port %p", VCOS_FUNCTION, preview_port);
406
407 rc = state->ops.create_native_window(state);
408 if (rc != 0)
409 goto end;
410
411 rc = state->ops.gl_init(state);
412 if (rc != 0)
413 goto end;
414
415 while (state->preview_stop == 0)
416 {
417 /* Send empty buffers to camera preview port */
418 while ((buf = mmal_queue_get(state->preview_pool->queue)) != NULL)
419 {
420 st = mmal_port_send_buffer(preview_port, buf);
421 if (st != MMAL_SUCCESS)
422 {
423 vcos_log_error("Failed to send buffer to %s", preview_port->name);
424 }
425 }
426 /* Process returned buffers */
427 if (preview_process_returned_bufs(state) != 0)
428 {
429 vcos_log_error("Preview error. Exiting.");
430 state->preview_stop = 1;
431 }
432 }
433
434end:
435 /* Make sure all buffers are returned on exit */
436 while ((buf = mmal_queue_get(state->preview_queue)) != NULL)
437 mmal_buffer_header_release(buf);
438
439 /* Tear down GL */
440 state->ops.gl_term(state);
441 vcos_log_trace("Exiting preview worker");
442 return NULL;
443}
444
445/**
446 * MMAL Callback from camera preview output port.
447 * @param port The camera preview port.
448 * @param buf The new preview buffer.
449 **/
450static void preview_output_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf)
451{
452 RASPITEX_STATE *state = (RASPITEX_STATE*) port->userdata;
453
454 if (buf->length == 0)
455 {
456 vcos_log_trace("%s: zero-length buffer => EOS", port->name);
457 state->preview_stop = 1;
458 mmal_buffer_header_release(buf);
459 }
460 else if (buf->data == NULL)
461 {
462 vcos_log_trace("%s: zero buffer handle", port->name);
463 mmal_buffer_header_release(buf);
464 }
465 else
466 {
467 /* Enqueue the preview frame for rendering and return to
468 * avoid blocking MMAL core.
469 */
470 mmal_queue_put(state->preview_queue, buf);
471 }
472}
473
474/* Registers a callback on the camera preview port to receive
475 * notifications of new frames.
476 * This must be called before rapitex_start and may not be called again
477 * without calling raspitex_destroy first.
478 *
479 * @param state Pointer to the GL preview state.
480 * @param port Pointer to the camera preview port
481 * @return Zero if successful.
482 */
483int raspitex_configure_preview_port(RASPITEX_STATE *state,
484 MMAL_PORT_T *preview_port)
485{
486 MMAL_STATUS_T status;
487 vcos_log_trace("%s port %p", VCOS_FUNCTION, preview_port);
488
489 /* Enable ZERO_COPY mode on the preview port which instructs MMAL to only
490 * pass the 4-byte opaque buffer handle instead of the contents of the opaque
491 * buffer.
492 * The opaque handle is resolved on VideoCore by the GL driver when the EGL
493 * image is created.
494 */
495 status = mmal_port_parameter_set_boolean(preview_port,
496 MMAL_PARAMETER_ZERO_COPY, MMAL_TRUE);
497 if (status != MMAL_SUCCESS)
498 {
499 vcos_log_error("Failed to enable zero copy on camera preview port");
500 goto end;
501 }
502
503 status = mmal_port_format_commit(preview_port);
504 if (status != MMAL_SUCCESS)
505 {
506 vcos_log_error("camera viewfinder format couldn't be set");
507 goto end;
508 }
509
510 /* For GL a pool of opaque buffer handles must be allocated in the client.
511 * These buffers are used to create the EGL images.
512 */
513 state->preview_port = preview_port;
514 preview_port->buffer_num = preview_port->buffer_num_recommended;
515 preview_port->buffer_size = preview_port->buffer_size_recommended;
516
517 vcos_log_trace("Creating buffer pool for GL renderer num %d size %d",
518 preview_port->buffer_num, preview_port->buffer_size);
519
520 /* Pool + queue to hold preview frames */
521 state->preview_pool = mmal_port_pool_create(preview_port,
522 preview_port->buffer_num, preview_port->buffer_size);
523
524 if (! state->preview_pool)
525 {
526 vcos_log_error("Error allocating pool");
527 status = MMAL_ENOMEM;
528 goto end;
529 }
530
531 /* Place filled buffers from the preview port in a queue to render */
532 state->preview_queue = mmal_queue_create();
533 if (! state->preview_queue)
534 {
535 vcos_log_error("Error allocating queue");
536 status = MMAL_ENOMEM;
537 goto end;
538 }
539
540 /* Enable preview port callback */
541 preview_port->userdata = (struct MMAL_PORT_USERDATA_T*) state;
542 status = mmal_port_enable(preview_port, preview_output_cb);
543 if (status != MMAL_SUCCESS)
544 {
545 vcos_log_error("Failed to camera preview port");
546 goto end;
547 }
548end:
549 return (status == MMAL_SUCCESS ? 0 : -1);
550}
551
552/* Initialises GL preview state and creates the dispmanx native window.
553 * @param state Pointer to the GL preview state.
554 * @return Zero if successful.
555 */
556int raspitex_init(RASPITEX_STATE *state)
557{
558 VCOS_STATUS_T status;
559 int rc;
560 vcos_init();
561
562 vcos_log_register("RaspiTex", VCOS_LOG_CATEGORY);
563 vcos_log_set_level(VCOS_LOG_CATEGORY,
564 state->verbose ? VCOS_LOG_INFO : VCOS_LOG_WARN);
565 vcos_log_trace("%s", VCOS_FUNCTION);
566
567 status = vcos_semaphore_create(&state->capture.start_sem,
568 "glcap_start_sem", 1);
569 if (status != VCOS_SUCCESS)
570 goto error;
571
572 status = vcos_semaphore_create(&state->capture.completed_sem,
573 "glcap_completed_sem", 0);
574 if (status != VCOS_SUCCESS)
575 goto error;
576
577 switch (state->scene_id)
578 {
579 case RASPITEX_SCENE_SQUARE:
580 rc = square_open(state);
581 break;
582 case RASPITEX_SCENE_MIRROR:
583 rc = mirror_open(state);
584 break;
585 case RASPITEX_SCENE_TEAPOT:
586 rc = teapot_open(state);
587 break;
588 case RASPITEX_SCENE_YUV:
589 rc = yuv_open(state);
590 break;
591 case RASPITEX_SCENE_SOBEL:
592 rc = sobel_open(state);
593 break;
594 case RASPITEX_SCENE_VCSM_SQUARE:
595 rc = vcsm_square_open(state);
596 break;
597 default:
598 rc = -1;
599 break;
600 }
601 if (rc != 0)
602 goto error;
603
604 return 0;
605
606error:
607 vcos_log_error("%s: failed", VCOS_FUNCTION);
608 return -1;
609}
610
611/* Destroys the pools of buffers used by the GL renderer.
612 * @param state Pointer to the GL preview state.
613 */
614void raspitex_destroy(RASPITEX_STATE *state)
615{
616 vcos_log_trace("%s", VCOS_FUNCTION);
617 if (state->preview_pool)
618 {
619 mmal_pool_destroy(state->preview_pool);
620 state->preview_pool = NULL;
621 }
622
623 if (state->preview_queue)
624 {
625 mmal_queue_destroy(state->preview_queue);
626 state->preview_queue = NULL;
627 }
628
629 if (state->ops.destroy_native_window)
630 state->ops.destroy_native_window(state);
631
632 if (state->ops.close)
633 state->ops.close(state);
634
635 vcos_semaphore_delete(&state->capture.start_sem);
636 vcos_semaphore_delete(&state->capture.completed_sem);
637}
638
639/* Initialise the GL / window state to sensible defaults.
640 * Also initialise any rendering parameters e.g. the scene
641 *
642 * @param state Pointer to the GL preview state.
643 * @return Zero if successful.
644 */
645void raspitex_set_defaults(RASPITEX_STATE *state)
646{
647 memset(state, 0, sizeof(*state));
648 state->version_major = RASPITEX_VERSION_MAJOR;
649 state->version_minor = RASPITEX_VERSION_MINOR;
650 state->display = EGL_NO_DISPLAY;
651 state->surface = EGL_NO_SURFACE;
652 state->context = EGL_NO_CONTEXT;
653 state->egl_image = EGL_NO_IMAGE_KHR;
654 state->y_egl_image = EGL_NO_IMAGE_KHR;
655 state->u_egl_image = EGL_NO_IMAGE_KHR;
656 state->v_egl_image = EGL_NO_IMAGE_KHR;
657 state->opacity = 255;
658 state->width = DEFAULT_WIDTH;
659 state->height = DEFAULT_HEIGHT;
660 state->scene_id = RASPITEX_SCENE_SQUARE;
661
662 state->ops.create_native_window = raspitexutil_create_native_window;
663 state->ops.gl_init = raspitexutil_gl_init_1_0;
664 state->ops.update_model = raspitexutil_update_model;
665 state->ops.redraw = raspitexutil_redraw;
666 state->ops.capture = raspitexutil_capture_bgra;
667 state->ops.gl_term = raspitexutil_gl_term;
668 state->ops.destroy_native_window = raspitexutil_destroy_native_window;
669 state->ops.close = raspitexutil_close;
670}
671
672/* Stops the rendering loop and destroys MMAL resources
673 * @param state Pointer to the GL preview state.
674 */
675void raspitex_stop(RASPITEX_STATE *state)
676{
677 if (! state->preview_stop)
678 {
679 vcos_log_trace("Stopping GL preview");
680 state->preview_stop = 1;
681 vcos_thread_join(&state->preview_thread, NULL);
682 }
683}
684
685/**
686 * Starts the worker / GL renderer thread.
687 * @pre raspitex_init was successful
688 * @pre raspitex_configure_preview_port was successful
689 * @param state Pointer to the GL preview state.
690 * @return Zero on success, otherwise, -1 is returned
691 * */
692int raspitex_start(RASPITEX_STATE *state)
693{
694 VCOS_STATUS_T status;
695
696 vcos_log_trace("%s", VCOS_FUNCTION);
697 status = vcos_thread_create(&state->preview_thread, "preview-worker",
698 NULL, preview_worker, state);
699
700 if (status != VCOS_SUCCESS)
701 vcos_log_error("%s: Failed to start worker thread %d",
702 VCOS_FUNCTION, status);
703
704 return (status == VCOS_SUCCESS ? 0 : -1);
705}
706
707/**
708 * Writes the next GL frame-buffer to a RAW .ppm formatted file
709 * using the specified file-handle.
710 * @param state Pointer to the GL preview state.
711 * @param outpt_file Output file handle for the ppm image.
712 * @return Zero on success.
713 */
714int raspitex_capture(RASPITEX_STATE *state, FILE *output_file)
715{
716 int rc = 0;
717 uint8_t *buffer = NULL;
718 size_t size = 0;
719
720 vcos_log_trace("%s: state %p file %p", VCOS_FUNCTION,
721 state, output_file);
722
723 if (state && output_file)
724 {
725 /* Only request one capture at a time */
726 vcos_semaphore_wait(&state->capture.start_sem);
727 state->capture.request = 1;
728
729 /* Wait for capture to start */
730 vcos_semaphore_wait(&state->capture.completed_sem);
731
732 /* Take ownership of the captured buffer */
733 buffer = state->capture.buffer;
734 size = state->capture.size;
735
736 state->capture.request = 0;
737 state->capture.buffer = 0;
738 state->capture.size = 0;
739
740 /* Allow another capture to be requested */
741 vcos_semaphore_post(&state->capture.start_sem);
742 }
743 if (size == 0 || ! buffer)
744 {
745 vcos_log_error("%s: capture failed", VCOS_FUNCTION);
746 rc = -1;
747 goto end;
748 }
749
750 raspitexutil_brga_to_rgba(buffer, size);
751 rc = write_tga(output_file, state->width, state->height, buffer, size);
752 fflush(output_file);
753
754end:
755 free(buffer);
756 return rc;
757}
758