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 <stddef.h>
29#include "applog.h"
30#include "interface/vcos/vcos_stdbool.h"
31#include "interface/vcos/vcos_inttypes.h"
32#include "interface/mmal/mmal.h"
33#include "interface/mmal/mmal_logging.h"
34#include "interface/mmal/util/mmal_connection.h"
35#include "interface/mmal/util/mmal_util.h"
36#include "interface/mmal/util/mmal_default_components.h"
37#include "interface/mmal/util/mmal_util_params.h"
38#include "svp.h"
39
40#define CHECK_STATUS(s, m) \
41 if ((s) != MMAL_SUCCESS) { \
42 LOG_ERROR("%s: %s", (m), mmal_status_to_string((s))); \
43 goto error; \
44 }
45
46/* Flags specifying fields of SVP_T struct */
47#define SVP_CREATED_SEM (1 << 0)
48#define SVP_CREATED_THREAD (1 << 1)
49#define SVP_CREATED_MUTEX (1 << 2)
50#define SVP_CREATED_TIMER (1 << 3)
51#define SVP_CREATED_WD_TIMER (1 << 4)
52
53/* Hard-coded camera parameters */
54#if 0
55#define SVP_CAMERA_WIDTH 1920
56#define SVP_CAMERA_HEIGHT 1080
57#else
58#define SVP_CAMERA_WIDTH 1280
59#define SVP_CAMERA_HEIGHT 720
60#endif
61#define SVP_CAMERA_FRAMERATE 30
62#define SVP_CAMERA_DURATION_MS 10000
63
64/* Watchdog timeout - elapsed time to allow for no video frames received */
65#define SVP_WATCHDOG_TIMEOUT_MS 5000
66
67/** Simple video player instance */
68struct SVP_T
69{
70 /* Player options */
71 SVP_OPTS_T opts;
72
73 /* Bitmask of SVP_CREATED_XXX values indicating which fields have been created.
74 * Only used for those fields which can't be (portably) determined from their
75 * own value.
76 */
77 uint32_t created;
78
79 /* Semaphore used for synchronising buffer handling for decoded frames */
80 VCOS_SEMAPHORE_T sema;
81
82 /* User supplied callbacks */
83 SVP_CALLBACKS_T callbacks;
84
85 /* Container reader component */
86 MMAL_COMPONENT_T *reader;
87
88 /* Video decoder component */
89 MMAL_COMPONENT_T *video_decode;
90
91 /* Camera component */
92 MMAL_COMPONENT_T *camera;
93
94 /* Connection: container reader -> video decoder */
95 MMAL_CONNECTION_T *connection;
96
97 /* Output port from video decoder or camera */
98 MMAL_PORT_T *video_output;
99
100 /* Pool of buffers for video decoder output */
101 MMAL_POOL_T *out_pool;
102
103 /* Queue to hold decoded video frames */
104 MMAL_QUEUE_T *queue;
105
106 /* Worker thread */
107 VCOS_THREAD_T thread;
108
109 /* Timer to trigger stop in camera preview case */
110 VCOS_TIMER_T timer;
111
112 /* Watchdog timer */
113 VCOS_TIMER_T wd_timer;
114
115 /* Mutex to synchronise access to all following fields */
116 VCOS_MUTEX_T mutex;
117
118 /* Stop control: 0 to process stream; bitmask of SVP_STOP_XXX values to stop */
119 uint32_t stop;
120
121 /* Player stats */
122 SVP_STATS_T stats;
123};
124
125/* Local function prototypes */
126static MMAL_STATUS_T svp_setup_reader(MMAL_COMPONENT_T *reader, const char *uri,
127 MMAL_PORT_T **video_port);
128static void svp_timer_cb(void *ctx);
129static void svp_watchdog_cb(void *ctx);
130static MMAL_STATUS_T svp_port_enable(SVP_T *svp, MMAL_PORT_T *port, MMAL_PORT_BH_CB_T cb);
131static void *svp_worker(void *arg);
132static void svp_bh_control_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer);
133static void svp_bh_output_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer);
134static void svp_reset_stop(SVP_T *svp);
135static void svp_set_stop(SVP_T *svp, uint32_t stop_flags);
136static uint32_t svp_get_stop(SVP_T *svp);
137
138/* Create Simple Video Player instance. */
139SVP_T *svp_create(const char *uri, SVP_CALLBACKS_T *callbacks, const SVP_OPTS_T *opts)
140{
141 SVP_T *svp;
142 MMAL_STATUS_T st;
143 VCOS_STATUS_T vst;
144 MMAL_PORT_T *reader_output = NULL;
145 MMAL_COMPONENT_T *video_decode = NULL;
146 MMAL_PORT_T *video_output = NULL;
147
148 LOG_TRACE("Creating player for %s", (uri ? uri : "camera preview"));
149
150 vcos_assert(callbacks->video_frame_cb);
151 vcos_assert(callbacks->stop_cb);
152
153 svp = vcos_calloc(1, sizeof(*svp), "svp");
154 CHECK_STATUS((svp ? MMAL_SUCCESS : MMAL_ENOMEM), "Failed to allocate context");
155
156 svp->opts = *opts;
157 svp->callbacks = *callbacks;
158
159 /* Semaphore used for synchronising buffer handling for decoded frames */
160 vst = vcos_semaphore_create(&svp->sema, "svp-sem", 0);
161 CHECK_STATUS((vst == VCOS_SUCCESS ? MMAL_SUCCESS : MMAL_ENOMEM), "Failed to create semaphore");
162 svp->created |= SVP_CREATED_SEM;
163
164 vst = vcos_mutex_create(&svp->mutex, "svp-mutex");
165 CHECK_STATUS((vst == VCOS_SUCCESS ? MMAL_SUCCESS : MMAL_ENOMEM), "Failed to create mutex");
166 svp->created |= SVP_CREATED_MUTEX;
167
168 vst = vcos_timer_create(&svp->timer, "svp-timer", svp_timer_cb, svp);
169 CHECK_STATUS((vst == VCOS_SUCCESS ? MMAL_SUCCESS : MMAL_ENOMEM), "Failed to create timer");
170 svp->created |= SVP_CREATED_TIMER;
171
172 vst = vcos_timer_create(&svp->wd_timer, "svp-wd-timer", svp_watchdog_cb, svp);
173 CHECK_STATUS((vst == VCOS_SUCCESS ? MMAL_SUCCESS : MMAL_ENOMEM), "Failed to create timer");
174 svp->created |= SVP_CREATED_WD_TIMER;
175
176 /* Create components */
177 svp->reader = NULL;
178 svp->video_decode = NULL;
179 svp->camera = NULL;
180 svp->connection = NULL;
181
182 if (uri)
183 {
184 /* Video from URI: setup container_reader -> video_decode */
185
186 /* Create and set up container reader */
187 st = mmal_component_create(MMAL_COMPONENT_DEFAULT_CONTAINER_READER, &svp->reader);
188 CHECK_STATUS(st, "Failed to create container reader");
189
190 st = svp_setup_reader(svp->reader, uri, &reader_output);
191 if (st != MMAL_SUCCESS)
192 goto error;
193
194 st = mmal_component_enable(svp->reader);
195 CHECK_STATUS(st, "Failed to enable container reader");
196
197 st = svp_port_enable(svp, svp->reader->control, svp_bh_control_cb);
198 CHECK_STATUS(st, "Failed to enable container reader control port");
199
200 /* Create and set up video decoder */
201 st = mmal_component_create(MMAL_COMPONENT_DEFAULT_VIDEO_DECODER, &svp->video_decode);
202 CHECK_STATUS(st, "Failed to create video decoder");
203
204 video_decode = svp->video_decode;
205 video_output = video_decode->output[0];
206
207 st = mmal_component_enable(video_decode);
208 CHECK_STATUS(st, "Failed to enable video decoder");
209
210 st = svp_port_enable(svp, video_decode->control, svp_bh_control_cb);
211 CHECK_STATUS(st, "Failed to enable video decoder control port");
212 }
213 else
214 {
215 /* Camera preview */
216 st = mmal_component_create(MMAL_COMPONENT_DEFAULT_CAMERA, &svp->camera);
217 CHECK_STATUS(st, "Failed to create camera");
218
219 st = mmal_component_enable(svp->camera);
220 CHECK_STATUS(st, "Failed to enable camera");
221
222 st = svp_port_enable(svp, svp->camera->control, svp_bh_control_cb);
223 CHECK_STATUS(st, "Failed to enable camera control port");
224
225 video_output = svp->camera->output[0]; /* Preview port */
226 }
227
228 st = mmal_port_parameter_set_boolean(video_output, MMAL_PARAMETER_ZERO_COPY, MMAL_TRUE);
229 CHECK_STATUS((st == MMAL_ENOSYS ? MMAL_SUCCESS : st), "Failed to enable zero copy");
230
231 if (uri)
232 {
233 /* Create connection: container_reader -> video_decoder */
234 st = mmal_connection_create(&svp->connection, reader_output, video_decode->input[0],
235 MMAL_CONNECTION_FLAG_TUNNELLING);
236 CHECK_STATUS(st, "Failed to create connection");
237 }
238
239 /* Set video output port format.
240 * Opaque encoding ensures we get buffer data as handles to relocatable heap. */
241 video_output->format->encoding = MMAL_ENCODING_OPAQUE;
242
243 if (!uri)
244 {
245 /* Set video format for camera preview */
246 MMAL_VIDEO_FORMAT_T *vfmt = &video_output->format->es->video;
247
248 CHECK_STATUS((video_output->format->type == MMAL_ES_TYPE_VIDEO) ? MMAL_SUCCESS : MMAL_EINVAL,
249 "Output port isn't video format");
250
251 vfmt->width = SVP_CAMERA_WIDTH;
252 vfmt->height = SVP_CAMERA_HEIGHT;
253 vfmt->crop.x = 0;
254 vfmt->crop.y = 0;
255 vfmt->crop.width = vfmt->width;
256 vfmt->crop.height = vfmt->height;
257 vfmt->frame_rate.num = SVP_CAMERA_FRAMERATE;
258 vfmt->frame_rate.den = 1;
259 }
260
261 st = mmal_port_format_commit(video_output);
262 CHECK_STATUS(st, "Failed to set output port format");
263
264 /* Finally, set buffer num/size. N.B. For container_reader/video_decode, must be after
265 * connection created, in order for port format to propagate.
266 * Don't enable video output port until want to produce frames. */
267 video_output->buffer_num = video_output->buffer_num_recommended;
268 video_output->buffer_size = video_output->buffer_size_recommended;
269
270 /* Pool + queue to hold decoded video frames */
271 svp->out_pool = mmal_port_pool_create(video_output, video_output->buffer_num,
272 video_output->buffer_size);
273 CHECK_STATUS((svp->out_pool ? MMAL_SUCCESS : MMAL_ENOMEM), "Error allocating pool");
274 svp->queue = mmal_queue_create();
275 CHECK_STATUS((svp ? MMAL_SUCCESS : MMAL_ENOMEM), "Error allocating queue");
276
277 svp->video_output = video_output;
278
279 return svp;
280
281error:
282 svp_destroy(svp);
283 return NULL;
284}
285
286/**
287 * Setup container reader component.
288 * Sets URI, to initialize processing, and finds a video track.
289 * @param reader Container reader component.
290 * @param uri Media URI.
291 * @param video_port On success, the output port for the first video track is returned here.
292 * @return MMAL_SUCCESS if the container reader was successfully set up and a video track located.
293 */
294static MMAL_STATUS_T svp_setup_reader(MMAL_COMPONENT_T *reader, const char *uri,
295 MMAL_PORT_T **video_port)
296{
297 MMAL_STATUS_T st;
298 uint32_t track;
299
300 st = mmal_util_port_set_uri(reader->control, uri);
301 if (st != MMAL_SUCCESS)
302 {
303 LOG_ERROR("%s: couldn't open uri %s", reader->name, uri);
304 return st;
305 }
306
307 /* Find a video track */
308 for (track = 0; track < reader->output_num; track++)
309 {
310 if (reader->output[track]->format->type == MMAL_ES_TYPE_VIDEO)
311 {
312 break;
313 }
314 }
315
316 if (track == reader->output_num)
317 {
318 LOG_ERROR("%s: no video track", uri);
319 return MMAL_EINVAL;
320 }
321
322 *video_port = reader->output[track];
323 return MMAL_SUCCESS;
324}
325
326/* Destroy SVP instance. svp may be NULL. */
327void svp_destroy(SVP_T *svp)
328{
329 if (svp)
330 {
331 MMAL_COMPONENT_T *components[] = { svp->reader, svp->video_decode, svp->camera };
332 MMAL_COMPONENT_T **comp;
333
334 /* Stop thread, disable connection and components */
335 svp_stop(svp);
336
337 for (comp = components; comp < components + vcos_countof(components); comp++)
338 {
339 mmal_component_disable(*comp);
340 }
341
342 /* Destroy connection + components */
343 if (svp->connection)
344 {
345 mmal_connection_destroy(svp->connection);
346 }
347
348 for (comp = components; comp < components + vcos_countof(components); comp++)
349 {
350 mmal_component_destroy(*comp);
351 }
352
353 /* Free remaining resources */
354 if (svp->out_pool)
355 {
356 mmal_pool_destroy(svp->out_pool);
357 }
358
359 if (svp->queue)
360 {
361 mmal_queue_destroy(svp->queue);
362 }
363
364 if (svp->created & SVP_CREATED_WD_TIMER)
365 {
366 vcos_timer_delete(&svp->wd_timer);
367 }
368
369 if (svp->created & SVP_CREATED_TIMER)
370 {
371 vcos_timer_delete(&svp->timer);
372 }
373
374 if (svp->created & SVP_CREATED_MUTEX)
375 {
376 vcos_mutex_delete(&svp->mutex);
377 }
378
379 if (svp->created & SVP_CREATED_SEM)
380 {
381 vcos_semaphore_delete(&svp->sema);
382 }
383
384 vcos_free(svp);
385 }
386}
387
388/* Start SVP. Enables MMAL connection + creates worker thread. */
389int svp_start(SVP_T *svp)
390{
391 MMAL_STATUS_T st;
392 VCOS_STATUS_T vst;
393
394 /* Ensure SVP is stopped first */
395 svp_stop(svp);
396
397 /* Reset the worker thread stop status, before enabling ports that might trigger a stop */
398 svp_reset_stop(svp);
399
400 if (svp->connection)
401 {
402 /* Enable reader->decoder connection */
403 st = mmal_connection_enable(svp->connection);
404 CHECK_STATUS(st, "Failed to create connection");
405 }
406
407 /* Enable video output port */
408 st = svp_port_enable(svp, svp->video_output, svp_bh_output_cb);
409 CHECK_STATUS(st, "Failed to enable output port");
410
411 /* Reset stats */
412 svp->stats.video_frame_count = 0;
413
414 /* Create worker thread */
415 vst = vcos_thread_create(&svp->thread, "svp-worker", NULL, svp_worker, svp);
416 CHECK_STATUS((vst == VCOS_SUCCESS ? MMAL_SUCCESS : MMAL_ENOMEM), "Failed to create connection");
417
418 svp->created |= SVP_CREATED_THREAD;
419
420 /* Set timer */
421 if (svp->camera)
422 {
423 unsigned ms = svp->opts.duration_ms;
424 vcos_timer_set(&svp->timer, ((ms == 0) ? SVP_CAMERA_DURATION_MS : ms));
425 }
426
427 /* Start watchdog timer */
428 vcos_timer_set(&svp->wd_timer, SVP_WATCHDOG_TIMEOUT_MS);
429
430 return 0;
431
432error:
433 return -1;
434}
435
436/* Stop SVP. Stops worker thread + disables MMAL connection. */
437void svp_stop(SVP_T *svp)
438{
439 vcos_timer_cancel(&svp->wd_timer);
440 vcos_timer_cancel(&svp->timer);
441
442 /* Stop worker thread */
443 if (svp->created & SVP_CREATED_THREAD)
444 {
445 svp_set_stop(svp, SVP_STOP_USER);
446 vcos_semaphore_post(&svp->sema);
447 vcos_thread_join(&svp->thread, NULL);
448 svp->created &= ~SVP_CREATED_THREAD;
449 }
450
451 if (svp->connection)
452 {
453 mmal_connection_disable(svp->connection);
454 }
455
456 mmal_port_disable(svp->video_output);
457}
458
459/* Get stats since last call to svp_start() */
460void svp_get_stats(SVP_T *svp, SVP_STATS_T *stats)
461{
462 vcos_mutex_lock(&svp->mutex);
463 *stats = svp->stats;
464 vcos_mutex_unlock(&svp->mutex);
465}
466
467/** Timer callback - stops playback */
468static void svp_timer_cb(void *ctx)
469{
470 SVP_T *svp = ctx;
471 svp_set_stop(svp, SVP_STOP_TIMEUP);
472 vcos_semaphore_post(&svp->sema);
473}
474
475/** Watchdog timer callback - stops playback */
476static void svp_watchdog_cb(void *ctx)
477{
478 SVP_T *svp = ctx;
479 LOG_ERROR("%s: no frames received for %d ms, aborting", svp->video_output->name,
480 SVP_WATCHDOG_TIMEOUT_MS);
481 svp_set_stop(svp, SVP_STOP_ERROR);
482 vcos_semaphore_post(&svp->sema);
483}
484
485/** Enable MMAL port, setting SVP instance as port userdata. */
486static MMAL_STATUS_T svp_port_enable(SVP_T *svp, MMAL_PORT_T *port, MMAL_PORT_BH_CB_T cb)
487{
488 port->userdata = (struct MMAL_PORT_USERDATA_T *)svp;
489 return mmal_port_enable(port, cb);
490}
491
492/** Process decoded buffers queued by video decoder output callback */
493static void svp_process_returned_bufs(SVP_T *svp)
494{
495 SVP_CALLBACKS_T *callbacks = &svp->callbacks;
496 MMAL_BUFFER_HEADER_T *buf;
497
498 while ((buf = mmal_queue_get(svp->queue)) != NULL)
499 {
500 if ((svp_get_stop(svp) & SVP_STOP_ERROR) == 0)
501 {
502 callbacks->video_frame_cb(callbacks->ctx, buf->data);
503 }
504
505 svp->stats.video_frame_count++;
506 mmal_buffer_header_release(buf);
507 }
508}
509
510/** Worker thread. Ensures video decoder output is supplied with buffers and sends decoded frames
511 * via user-supplied callback.
512 * @param arg Pointer to SVP instance.
513 * @return NULL always.
514 */
515static void *svp_worker(void *arg)
516{
517 SVP_T *svp = arg;
518 MMAL_PORT_T *video_output = svp->video_output;
519 SVP_CALLBACKS_T *callbacks = &svp->callbacks;
520 MMAL_BUFFER_HEADER_T *buf;
521 MMAL_STATUS_T st;
522 uint32_t stop;
523
524 while (svp_get_stop(svp) == 0)
525 {
526 /* Send empty buffers to video decoder output port */
527 while ((buf = mmal_queue_get(svp->out_pool->queue)) != NULL)
528 {
529 st = mmal_port_send_buffer(video_output, buf);
530 if (st != MMAL_SUCCESS)
531 {
532 LOG_ERROR("Failed to send buffer to %s", video_output->name);
533 }
534 }
535
536 /* Process returned buffers */
537 svp_process_returned_bufs(svp);
538
539 /* Block for buffer release */
540 vcos_semaphore_wait(&svp->sema);
541 }
542
543 /* Might have the last few buffers queued */
544 svp_process_returned_bufs(svp);
545
546 /* Notify caller if we stopped unexpectedly */
547 stop = svp_get_stop(svp);
548 LOG_TRACE("Worker thread exiting: stop=0x%x", (unsigned)stop);
549 callbacks->stop_cb(callbacks->ctx, stop);
550
551 return NULL;
552}
553
554/** Callback from a control port. */
555static void svp_bh_control_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf)
556{
557 SVP_T *svp = (SVP_T *)port->userdata;
558
559 switch (buf->cmd)
560 {
561 case MMAL_EVENT_EOS:
562 LOG_TRACE("%s: EOS", port->name);
563 svp_set_stop(svp, SVP_STOP_EOS);
564 break;
565
566 case MMAL_EVENT_ERROR:
567 LOG_ERROR("%s: MMAL error: %s", port->name,
568 mmal_status_to_string(*(MMAL_STATUS_T *)buf->data));
569 svp_set_stop(svp, SVP_STOP_ERROR);
570 break;
571
572 default:
573 LOG_TRACE("%s: buf %p, event %4.4s", port->name, buf, (char *)&buf->cmd);
574 break;
575 }
576
577 mmal_buffer_header_release(buf);
578
579 vcos_semaphore_post(&svp->sema);
580}
581
582/** Callback from video decode output port. */
583static void svp_bh_output_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buf)
584{
585 SVP_T *svp = (SVP_T *)port->userdata;
586
587 if (buf->length == 0)
588 {
589 LOG_TRACE("%s: zero-length buffer => EOS", port->name);
590 svp_set_stop(svp, SVP_STOP_EOS); // This shouldn't be necessary, but it is ...
591 mmal_buffer_header_release(buf);
592 }
593 else if (buf->data == NULL)
594 {
595 LOG_ERROR("%s: zero buffer handle", port->name);
596 mmal_buffer_header_release(buf);
597 }
598 else
599 {
600 /* Reset watchdog timer */
601 vcos_timer_set(&svp->wd_timer, SVP_WATCHDOG_TIMEOUT_MS);
602
603 /* Enqueue the decoded frame so we can return quickly to MMAL core */
604 mmal_queue_put(svp->queue, buf);
605 }
606
607 /* Notify worker */
608 vcos_semaphore_post(&svp->sema);
609}
610
611/** Reset svp->stop to 0, with locking. */
612static void svp_reset_stop(SVP_T *svp)
613{
614 vcos_mutex_lock(&svp->mutex);
615 svp->stop = 0;
616 vcos_mutex_unlock(&svp->mutex);
617}
618
619/** Set additional flags in svp->stop, with locking. */
620static void svp_set_stop(SVP_T *svp, uint32_t stop_flags)
621{
622 vcos_mutex_lock(&svp->mutex);
623 svp->stop |= stop_flags;
624 vcos_mutex_unlock(&svp->mutex);
625}
626
627/** Get value of svp->stop, with locking. */
628static uint32_t svp_get_stop(SVP_T *svp)
629{
630 uint32_t stop;
631
632 vcos_mutex_lock(&svp->mutex);
633 stop = svp->stop;
634 vcos_mutex_unlock(&svp->mutex);
635
636 return stop;
637}
638