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#include <stdlib.h>
28#include <limits.h>
29#include <stdio.h>
30#include <string.h>
31#include <signal.h>
32#include "mmalplay.h"
33
34#include "interface/mmal/mmal_logging.h"
35#include "interface/mmal/mmal_encodings.h"
36#include "interface/mmal/util/mmal_util.h"
37
38#define URI_FOR_THREAD_NAME_MAX 16
39#define THREAD_PREFIX "mmal:"
40#define VERSION "0.9"
41
42/* Global options */
43static MMALPLAY_OPTIONS_T options;
44static int unclean_exit;
45
46typedef struct {
47 VCOS_MUTEX_T lock;
48 VCOS_THREAD_T thread;
49 const char *uri;
50 MMALPLAY_T *ctx;
51 MMALPLAY_OPTIONS_T options;
52 MMAL_STATUS_T status;
53 char name[sizeof(THREAD_PREFIX) + URI_FOR_THREAD_NAME_MAX];
54} FILE_PLAY_INFO_T;
55
56#define FILE_PLAY_MAX 16
57static FILE_PLAY_INFO_T play_info[FILE_PLAY_MAX];
58static int play_info_count;
59static uint32_t sleepy_time;
60static unsigned int verbosity;
61
62/* Utility functions used by test program */
63void test_signal_handler(int signum);
64static void *mmal_playback(void *id);
65static int test_parse_cmdline(int argc, const char **argv);
66
67/*****************************************************************************/
68int main(int argc, const char **argv)
69{
70 VCOS_THREAD_ATTR_T attrs;
71 int i;
72
73 vcos_init();
74 signal(SIGINT, test_signal_handler);
75
76 /* coverity[tainted_data] Ignore unnecessary warning about an attacker
77 * being able to pass an arbitrarily long "-vvvvv..." argument */
78 if (test_parse_cmdline(argc, argv))
79 return -1;
80
81 if (verbosity--)
82 {
83 static char value[512];
84 const char *levels[] = {"warn", "info", "trace"};
85 char *env = getenv("VC_LOGLEVEL");
86 if (verbosity >= MMAL_COUNTOF(levels)) verbosity = MMAL_COUNTOF(levels) - 1;
87 snprintf(value, sizeof(value)-1, "mmalplay:%s,mmal:%s,%s",
88 levels[verbosity], levels[verbosity], env ? env : "");
89 setenv("VC_LOGLEVEL", value, 1);
90 }
91
92 vcos_log_register("mmalplay", VCOS_LOG_CATEGORY);
93 LOG_INFO("MMAL Video Playback Test App");
94
95 vcos_thread_attr_init(&attrs);
96 for (i = 0; i < play_info_count; i++)
97 {
98 const char *uri = play_info[i].uri;
99
100 memcpy(play_info[i].name, THREAD_PREFIX, sizeof(THREAD_PREFIX));
101 if (strlen(uri) >= URI_FOR_THREAD_NAME_MAX)
102 uri += strlen(uri) - URI_FOR_THREAD_NAME_MAX;
103 strncat(play_info[i].name, uri, URI_FOR_THREAD_NAME_MAX);
104
105 vcos_mutex_create(&play_info[i].lock, "mmalplay");
106 play_info[i].options.render_layer = i;
107
108 if (vcos_thread_create(&play_info[i].thread, play_info[i].name, &attrs, mmal_playback, &play_info[i]) != VCOS_SUCCESS)
109 {
110 LOG_ERROR("Thread creation failure for URI %s", play_info[i].uri);
111 return -2;
112 }
113 }
114
115 if (sleepy_time != 0)
116 {
117 sleep(sleepy_time);
118 for (i = 0; i < play_info_count; i++)
119 {
120 vcos_mutex_lock(&play_info[i].lock);
121 if (play_info[i].ctx)
122 mmalplay_stop(play_info[i].ctx);
123 vcos_mutex_unlock(&play_info[i].lock);
124 }
125 }
126
127 LOG_TRACE("Waiting for threads to terminate");
128 for (i = 0; i < play_info_count; i++)
129 {
130 vcos_thread_join(&play_info[i].thread, NULL);
131 LOG_TRACE("Joined thread %d (%i)", i, play_info[i].status);
132 }
133
134 LOG_TRACE("Completed");
135
136 /* Check for errors */
137 for (i = 0; i < play_info_count; i++)
138 {
139 if (!play_info[i].status)
140 continue;
141
142 LOG_ERROR("Playback of %s failed (%i, %s)", play_info[i].uri,
143 play_info[i].status,
144 mmal_status_to_string(play_info[i].status));
145 fprintf(stderr, "playback of %s failed (%i, %s)\n", play_info[i].uri,
146 play_info[i].status,
147 mmal_status_to_string(play_info[i].status));
148 return play_info[i].status;
149 }
150
151 return 0;
152}
153
154/*****************************************************************************/
155static void *mmal_playback(void *id)
156{
157 FILE_PLAY_INFO_T *play_info = id;
158 MMALPLAY_OPTIONS_T opts;
159 MMAL_STATUS_T status;
160 MMALPLAY_T *ctx;
161
162 /* Setup the options */
163 opts = options;
164 opts.output_uri = play_info->options.output_uri;
165 opts.render_layer = play_info->options.render_layer;
166
167 vcos_mutex_lock(&play_info->lock);
168 ctx = mmalplay_create(play_info->uri, &opts, &status);
169 play_info->ctx = ctx;
170 vcos_mutex_unlock(&play_info->lock);
171 if (!ctx)
172 goto end;
173
174 if (!opts.disable_playback)
175 status = mmalplay_play(ctx);
176
177 if (unclean_exit)
178 goto end;
179
180 vcos_mutex_lock(&play_info->lock);
181 if (ctx)
182 {
183 /* coverity[use] Suppress ATOMICITY warning - ctx might have changed since
184 * we initialised it above, which is okay */
185 mmalplay_destroy(ctx);
186 }
187 play_info->ctx = 0;
188 vcos_mutex_unlock(&play_info->lock);
189
190 end:
191 LOG_TRACE("Thread %s terminating, result %d", play_info->name, status);
192 play_info->status = status;
193 return NULL;
194}
195
196/*****************************************************************************/
197void test_signal_handler(int signum)
198{
199 static MMAL_BOOL_T stopped_already = 0;
200 int i;
201 MMAL_PARAM_UNUSED(signum);
202
203 if (stopped_already)
204 {
205 LOG_ERROR("Killing program");
206 exit(255);
207 }
208 stopped_already = 1;
209
210 LOG_ERROR("BYE");
211 for (i = 0; i < play_info_count; i++)
212 {
213 vcos_mutex_lock(&play_info[i].lock);
214 if (play_info[i].ctx)
215 mmalplay_stop(play_info[i].ctx);
216 vcos_mutex_unlock(&play_info[i].lock);
217 }
218}
219
220/* Parse a string of the form: "fourcc:widthxheight" */
221static int get_format(const char *name, uint32_t *fourcc, unsigned int *width, unsigned int *height)
222{
223 char *delim, fcc[4] = {' ', ' ', ' ', ' '};
224 unsigned int value_u1, value_u2;
225 size_t size;
226
227 *width = *height = 0;
228 *fourcc = MMAL_ENCODING_UNKNOWN;
229
230 /* Fourcc is the first element */
231 delim = strchr(name, ':');
232 size = delim ? (size_t)(delim - name) : strlen(name);
233 memcpy(fcc, name, MMAL_MIN(size, sizeof(fcc)));
234
235 if (size == sizeof("yuv420")-1 && !memcmp(name, "yuv420", size))
236 *fourcc = MMAL_ENCODING_I420;
237 else if (size == sizeof("yuvuv")-1 && !memcmp(name, "yuvuv", size))
238 *fourcc = MMAL_ENCODING_YUVUV128;
239 else if (size == sizeof("opaque")-1 && !memcmp(name, "opaque", size))
240 *fourcc = MMAL_ENCODING_OPAQUE;
241 else if (size > 0 && size <= 4)
242 *fourcc = MMAL_FOURCC(fcc[0], fcc[1], fcc[2], fcc[3]);
243 else
244 return 1;
245
246 if (!delim)
247 return 0; /* Nothing more to parse */
248
249 /* Width/height are next */
250 /* coverity[secure_coding] Only reading integers, so can't overflow */
251 if (sscanf(delim+1, "%ux%u", &value_u1, &value_u2) != 2)
252 return 1;
253
254 *width = value_u1;
255 *height = value_u2;
256 return 0;
257}
258
259/*****************************************************************************/
260static int test_parse_cmdline(int argc, const char **argv)
261{
262 unsigned int value_u1 = 0, value_u2 = 0;
263 uint32_t color_format;
264 float value_f = 0;
265 int i, j;
266
267 /* Parse the command line arguments */
268 for(i = 1; i < argc; i++)
269 {
270 if(!argv[i]) continue;
271
272 if(argv[i][0] != '-')
273 {
274 /* Not an option argument so will be the input URI */
275 if (play_info_count >= FILE_PLAY_MAX)
276 {
277 fprintf(stderr, "Too many URIs!\n");
278 goto usage;
279 }
280 play_info[play_info_count++].uri = argv[i];
281 continue;
282 }
283
284 /* We are now dealing with command line options */
285 switch(argv[i][1])
286 {
287 case 'V':
288 printf("Version: %s\n", VERSION);
289 exit(0);
290 case 'v':
291 for (j = 1; argv[i][j] == 'v'; j++) verbosity++;
292 break;
293 case 'X':
294 unclean_exit = 1;
295 break;
296 case 'd':
297 options.tunnelling = 1;
298 break;
299 case 's':
300 if (!strcmp(argv[i]+1, "step"))
301 {
302 options.stepping = 1;
303 break;
304 }
305 /* coverity[secure_coding] Only reading numbers, so can't overflow */
306 else if (!argv[i][2] && ++i < argc &&
307 sscanf(argv[i], "%f", &value_f) == 1)
308 {
309 options.seeking = value_f;
310 break;
311 }
312 goto usage;
313 case 'n':
314 switch (argv[i][2])
315 {
316 case 'p': options.disable_playback = 1; break;
317 case 'v':
318 if(argv[i][3] == 'd')
319 options.disable_video_decode = 1;
320 else
321 options.disable_video = 1;
322 break;
323 case 'a': options.disable_audio = 1; break;
324 default: break;
325 }
326 break;
327 case 'e':
328 if (argv[i][2] != 's')
329 goto usage;
330 options.enable_scheduling = 1; break;
331 break;
332 case 't':
333 /* coverity[secure_coding] Only reading integers, so can't overflow */
334 if (++i >= argc || sscanf(argv[i], "%u", &sleepy_time) != 1)
335 goto usage; /* Time missing / invalid */
336 break;
337 case 'x':
338 /* coverity[secure_coding] Only reading integers, so can't overflow */
339 if (++i >= argc || sscanf(argv[i], "%u", &value_u1) != 1)
340 goto usage;
341 options.output_num = value_u1;
342 break;
343 case 'f':
344 if (i + 1 >= argc || get_format(argv[++i], &color_format, &value_u1, &value_u2))
345 goto usage;
346 if (argv[i-1][2] == 0)
347 {
348 options.output_format = color_format;
349 options.output_rect.width = value_u1;
350 options.output_rect.height = value_u2;
351 }
352 else if (argv[i-1][2] == 'r')
353 {
354 options.render_format = color_format;
355 options.render_rect.width = value_u1;
356 options.render_rect.height = value_u2;
357 }
358 else
359 goto usage;
360 break;
361 case 'c':
362 if (!argv[i][2])
363 {
364 options.copy_input = 1;
365 options.copy_output = 1;
366 }
367 else if (argv[i][2] == 'i')
368 options.copy_input = 1;
369 else if (argv[i][2] == 'o')
370 options.copy_output = 1;
371 break;
372 case 'm':
373 if (argv[i][2] == 'v')
374 {
375 if (argv[i][3] == 'r' && i < argc)
376 options.component_video_render = argv[++i];
377 else if (argv[i][3] == 'd' && i < argc)
378 options.component_video_decoder = argv[++i];
379 else if (argv[i][3] == 's' && i < argc)
380 options.component_splitter = argv[++i];
381 else if (argv[i][3] == 'c' && i < argc)
382 options.component_video_converter = argv[++i];
383 else if (argv[i][3] == 'h' && i < argc)
384 options.component_video_scheduler = argv[++i];
385 else
386 goto usage;
387 }
388 else if (argv[i][2] == 'a')
389 {
390 if (argv[i][3] == 'r' && i < argc)
391 options.component_audio_render = argv[++i];
392 else if (argv[i][3] == 'd' && i < argc)
393 options.component_audio_decoder = argv[++i];
394 else
395 goto usage;
396 }
397 else if (argv[i][2] == 'c')
398 {
399 if (argv[i][3] == 'r' && i < argc)
400 options.component_container_reader = argv[++i];
401 else
402 goto usage;
403 }
404 else
405 goto usage;
406 break;
407 case 'o':
408 if (++i >= argc || play_info_count >= FILE_PLAY_MAX)
409 goto usage;
410 play_info[play_info_count].options.output_uri = argv[i];
411 break;
412
413 case 'a':
414 if (++i >= argc)
415 goto usage;
416 options.audio_destination = argv[i];
417 break;
418
419 case 'r':
420 if (i + 1 >= argc)
421 goto usage;
422 if (argv[i][2] == 'a')
423 options.audio_destination = argv[++i];
424 else if (argv[i][2] == 'v')
425 {
426 /* coverity[secure_coding] Only reading integers, so can't overflow */
427 if (sscanf(argv[++i], "%u", &options.video_destination) != 1)
428 goto usage;
429 }
430 else
431 goto usage;
432 break;
433
434 case 'w':
435 options.window = 1;
436 break;
437
438 case 'p':
439 options.audio_passthrough = 1;
440 break;
441
442 case 'h': goto usage;
443 default: goto invalid_option;
444 }
445 continue;
446 }
447
448 /* Sanity check that we have at least an input uri */
449 if(!play_info_count)
450 {
451 fprintf(stderr, "missing uri argument\n");
452 goto usage;
453 }
454
455 return 0;
456
457invalid_option:
458 fprintf(stderr, "invalid command line option (%s)\n", argv[i]);
459
460usage:
461 {
462 const char *program;
463
464 program = strrchr(argv[0], '\\');
465 if (program)
466 program++;
467 if (!program)
468 {
469 program = strrchr(argv[0], '/');
470 if (program)
471 program++;
472 }
473 if (!program)
474 program = argv[0];
475
476 fprintf(stderr, "usage: %s [options] uri0 uri1 ... uriN\n", program);
477 fprintf(stderr, "options list:\n");
478 fprintf(stderr, " -h : help\n");
479 fprintf(stderr, " -V : print version and exit\n");
480 fprintf(stderr, " -v(vv): increase verbosity\n");
481 fprintf(stderr, " -np : disable playback phase\n");
482 fprintf(stderr, " -nv : disable video\n");
483 fprintf(stderr, " -nvd : disable video decode\n");
484 fprintf(stderr, " -na : disable audio\n");
485 fprintf(stderr, " -es : enable scheduling\n");
486 fprintf(stderr, " -t <n>: play URI(s) for <n> seconds then stop\n");
487 fprintf(stderr, " -f <fourcc:widthxheight> : set format used on output of decoder\n");
488 fprintf(stderr, " -fr <fourcc:widthxheight> : set format used for rendering\n");
489 fprintf(stderr, " -c : do full copy of data transferred to videocore\n");
490 fprintf(stderr, " -ci : full copy for input buffers to decoder\n");
491 fprintf(stderr, " -co : full copy for output buffers from decoder\n");
492 fprintf(stderr, " -X : exit without tearing down the mmal pipeline (for testing)\n");
493 fprintf(stderr, " -x <n>: use <n> video render modules \n");
494 fprintf(stderr, " -d : use direct port connections (aka tunnelling)\n");
495 fprintf(stderr, " -step : stepping (displays 1 frame at a time)\n");
496 fprintf(stderr, " -s <f>: seek to <f> seconds into the stream\n");
497 fprintf(stderr, " -o <s>: output uri\n");
498 fprintf(stderr, " -mcr <s>: name of the container reader module to use\n");
499 fprintf(stderr, " -mvr <s>: name of the video render module to use\n");
500 fprintf(stderr, " -mvd <s>: name of the video decoder module to use\n");
501 fprintf(stderr, " -mvc <s>: name of the video converter module to use\n");
502 fprintf(stderr, " -mvh <s>: name of the video scheduler to use\n");
503 fprintf(stderr, " -mvs <s>: name of the splitter module to use\n");
504 fprintf(stderr, " -mar <s>: name of the audio render module to use\n");
505 fprintf(stderr, " -mad <s>: name of the audio decoder module to use\n");
506 fprintf(stderr, " -ra <s>: set audio destination\n");
507 fprintf(stderr, " -rv <n>: set video destination\n");
508 fprintf(stderr, " -p : use audio passthrough\n");
509 fprintf(stderr, " -w : window mode (i.e. not fullscreen)\n");
510 }
511 return 1;
512}
513