contrib: add AppStream metadata
[libam7xxx.git] / examples / am7xxx-play.c
1 /*
2  * am7xxx-play - play stuff on an am7xxx device (e.g. Acer C110, PicoPix 1020)
3  *
4  * Copyright (C) 2012-2014  Antonio Ospite <ao2@ao2.it>
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 /**
21  * @example examples/am7xxx-play.c
22  * am7xxx-play uses libavdevice, libavformat, libavcodec and libswscale to
23  * decode the input, encode it to jpeg and display it with libam7xxx.
24  */
25
26 #include <stdio.h>
27 #include <stdint.h>
28 #include <string.h>
29 #include <signal.h>
30 #include <getopt.h>
31
32 #include <libavdevice/avdevice.h>
33 #include <libavformat/avformat.h>
34 #include <libavutil/imgutils.h>
35 #include <libswscale/swscale.h>
36
37 #include <am7xxx.h>
38
39 static unsigned int run = 1;
40
41 struct video_input_ctx {
42         AVFormatContext *format_ctx;
43         AVCodecContext  *codec_ctx;
44         int video_stream_index;
45 };
46
47 static int video_input_init(struct video_input_ctx *input_ctx,
48                             const char *input_format_string,
49                             const char *input_path,
50                             AVDictionary **input_options)
51 {
52         AVInputFormat *input_format = NULL;
53         AVFormatContext *input_format_ctx;
54         AVCodecParameters *input_codec_params;
55         AVCodecContext *input_codec_ctx;
56         AVCodec *input_codec;
57         int video_index;
58         int ret;
59
60         avdevice_register_all();
61         avcodec_register_all();
62         av_register_all();
63
64         if (input_format_string) {
65                 /* find the desired input format */
66                 input_format = av_find_input_format(input_format_string);
67                 if (input_format == NULL) {
68                         fprintf(stderr, "cannot find input format\n");
69                         ret = -ENODEV;
70                         goto out;
71                 }
72         }
73
74         if (input_path == NULL) {
75                 fprintf(stderr, "input_path must not be NULL!\n");
76                 ret = -EINVAL;
77                 goto out;
78         }
79
80         /* open the input format/device */
81         input_format_ctx = NULL;
82         ret = avformat_open_input(&input_format_ctx,
83                                   input_path,
84                                   input_format,
85                                   input_options);
86         if (ret < 0) {
87                 fprintf(stderr, "cannot open input format/device\n");
88                 goto out;
89         }
90
91         /* get information on the input stream (e.g. format, bitrate, framerate) */
92         ret = avformat_find_stream_info(input_format_ctx, NULL);
93         if (ret < 0) {
94                 fprintf(stderr, "cannot get information on the stream\n");
95                 goto cleanup;
96         }
97
98         /* dump what was found */
99         av_dump_format(input_format_ctx, 0, input_path, 0);
100
101         /* look for the first video_stream */
102         video_index = av_find_best_stream(input_format_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &input_codec, 0);
103         if (video_index < 0) {
104                 fprintf(stderr, "cannot find any video streams\n");
105                 ret = -EINVAL;
106                 goto cleanup;
107         }
108
109         input_codec_ctx = avcodec_alloc_context3(input_codec);
110         if (input_codec_ctx == NULL) {
111                 fprintf(stderr, "failed to allocate the input codec context\n");
112                 ret = -ENOMEM;
113                 goto cleanup;
114         }
115
116         input_codec_params = input_format_ctx->streams[video_index]->codecpar;
117         ret = avcodec_parameters_to_context(input_codec_ctx, input_codec_params);
118         if (ret < 0) {
119                 fprintf(stderr, "cannot copy parameters to input codec context\n");
120                 goto cleanup_ctx;
121         }
122
123         /* open the decoder */
124         ret = avcodec_open2(input_codec_ctx, input_codec, NULL);
125         if (ret < 0) {
126                 fprintf(stderr, "cannot open input codec\n");
127                 goto cleanup_ctx;
128         }
129
130         input_ctx->format_ctx = input_format_ctx;
131         input_ctx->codec_ctx = input_codec_ctx;
132         input_ctx->video_stream_index = video_index;
133
134         ret = 0;
135         goto out;
136
137 cleanup_ctx:
138         avcodec_free_context(&input_codec_ctx);
139 cleanup:
140         avformat_close_input(&input_format_ctx);
141 out:
142         av_dict_free(input_options);
143         *input_options = NULL;
144         return ret;
145 }
146
147
148 struct video_output_ctx {
149         AVCodecContext  *codec_ctx;
150         int raw_output;
151 };
152
153 static int video_output_init(struct video_output_ctx *output_ctx,
154                              struct video_input_ctx *input_ctx,
155                              unsigned int upscale,
156                              unsigned int quality,
157                              am7xxx_image_format image_format,
158                              am7xxx_device *dev)
159 {
160         AVCodecContext *output_codec_ctx;
161         AVCodec *output_codec;
162         unsigned int new_output_width;
163         unsigned int new_output_height;
164         int ret;
165
166         if (input_ctx == NULL) {
167                 fprintf(stderr, "input_ctx must not be NULL!\n");
168                 ret = -EINVAL;
169                 goto out;
170         }
171
172         /* create the encoder context */
173         output_codec_ctx = avcodec_alloc_context3(NULL);
174         if (output_codec_ctx == NULL) {
175                 fprintf(stderr, "cannot allocate output codec context!\n");
176                 ret = -ENOMEM;
177                 goto out;
178         }
179
180         /* Calculate the new output dimension so the original frame is shown
181          * in its entirety */
182         ret = am7xxx_calc_scaled_image_dimensions(dev,
183                                                   upscale,
184                                                   (input_ctx->codec_ctx)->width,
185                                                   (input_ctx->codec_ctx)->height,
186                                                   &new_output_width,
187                                                   &new_output_height);
188         if (ret < 0) {
189                 fprintf(stderr, "cannot calculate output dimension\n");
190                 goto cleanup;
191         }
192
193         /* put sample parameters */
194         output_codec_ctx->bit_rate   = (input_ctx->codec_ctx)->bit_rate;
195         output_codec_ctx->width      = new_output_width;
196         output_codec_ctx->height     = new_output_height;
197         output_codec_ctx->time_base.num  =
198                 (input_ctx->format_ctx)->streams[input_ctx->video_stream_index]->time_base.num;
199         output_codec_ctx->time_base.den  =
200                 (input_ctx->format_ctx)->streams[input_ctx->video_stream_index]->time_base.den;
201
202         /* When the raw format is requested we don't actually need to setup
203          * and open a decoder
204          */
205         if (image_format == AM7XXX_IMAGE_FORMAT_NV12) {
206                 fprintf(stdout, "using raw output format\n");
207                 output_codec_ctx->pix_fmt    = AV_PIX_FMT_NV12;
208                 output_ctx->codec_ctx = output_codec_ctx;
209                 output_ctx->raw_output = 1;
210                 ret = 0;
211                 goto out;
212         }
213
214         /* YUVJ420P is deprecated in swscaler, but mjpeg still relies on it. */
215         output_codec_ctx->pix_fmt    = AV_PIX_FMT_YUVJ420P;
216         output_codec_ctx->codec_id   = AV_CODEC_ID_MJPEG;
217         output_codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
218
219         /* Set quality and other VBR settings */
220
221         /* @note: 'quality' is expected to be between 1 and 100, but a value
222          * between 0 to 99 has to be passed when calculating qmin and qmax.
223          * This way qmin and qmax will cover the range 1-FF_QUALITY_SCALE, and
224          * in particular they won't be 0, this is needed because they are used
225          * as divisor somewhere in the encoding process */
226         output_codec_ctx->qmin       = output_codec_ctx->qmax = ((100 - (quality - 1)) * FF_QUALITY_SCALE) / 100;
227         output_codec_ctx->mb_lmin    = output_codec_ctx->qmin * FF_QP2LAMBDA;
228         output_codec_ctx->mb_lmax    = output_codec_ctx->qmax * FF_QP2LAMBDA;
229         output_codec_ctx->flags      |= AV_CODEC_FLAG_QSCALE;
230         output_codec_ctx->global_quality = output_codec_ctx->qmin * FF_QP2LAMBDA;
231
232         /* find the encoder */
233         output_codec = avcodec_find_encoder(output_codec_ctx->codec_id);
234         if (output_codec == NULL) {
235                 fprintf(stderr, "cannot find output codec!\n");
236                 ret = -EINVAL;
237                 goto cleanup;
238         }
239
240         /* open the codec */
241         ret = avcodec_open2(output_codec_ctx, output_codec, NULL);
242         if (ret < 0) {
243                 fprintf(stderr, "could not open output codec!\n");
244                 goto cleanup;
245         }
246
247         output_ctx->codec_ctx = output_codec_ctx;
248         output_ctx->raw_output = 0;
249
250         ret = 0;
251         goto out;
252
253 cleanup:
254         avcodec_free_context(&output_codec_ctx);
255 out:
256         return ret;
257 }
258
259
260 /*
261  * Wrap the new avcodec API from FFMpeg 3.1 to minimize the changes in the
262  * user code.
263  *
264  * If the use of the wrappers were to be made conditional, a check like the
265  * following could be used:
266  *
267  *      #if (LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101))
268  *
269  * As derived from the APIchanges document:
270  * https://github.com/FFmpeg/FFmpeg/blob/master/doc/APIchanges
271  *
272  * The wrapper implementation has been taken from:
273  * https://blogs.gentoo.org/lu_zero/2016/03/29/new-avcodec-api/
274  */
275 static int decode(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacket *pkt)
276 {
277         int ret;
278
279         *got_frame = 0;
280
281         if (pkt) {
282                 ret = avcodec_send_packet(avctx, pkt);
283                 /*
284                  * In particular, we don't expect AVERROR(EAGAIN), because we
285                  * read all decoded frames with avcodec_receive_frame() until
286                  * done.
287                  */
288                 if (ret < 0)
289                         return ret == AVERROR_EOF ? 0 : ret;
290         }
291
292         ret = avcodec_receive_frame(avctx, frame);
293         if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
294                 return ret;
295         if (ret >= 0)
296                 *got_frame = 1;
297
298         return 0;
299 }
300
301 static int encode(AVCodecContext *avctx, AVPacket *pkt, int *got_packet, AVFrame *frame)
302 {
303         int ret;
304
305         *got_packet = 0;
306
307         ret = avcodec_send_frame(avctx, frame);
308         if (ret < 0)
309                 return ret;
310
311         ret = avcodec_receive_packet(avctx, pkt);
312         if (!ret)
313                 *got_packet = 1;
314         if (ret == AVERROR(EAGAIN))
315                 return 0;
316
317         return ret;
318 }
319
320
321 static int am7xxx_play(const char *input_format_string,
322                        AVDictionary **input_options,
323                        const char *input_path,
324                        unsigned int rescale_method,
325                        unsigned int upscale,
326                        unsigned int quality,
327                        am7xxx_image_format image_format,
328                        am7xxx_device *dev,
329                        int dump_frame)
330 {
331         struct video_input_ctx input_ctx;
332         struct video_output_ctx output_ctx;
333         AVFrame *frame_raw;
334         AVFrame *frame_scaled;
335         int out_buf_size;
336         uint8_t *out_buf;
337         int out_frame_size;
338         uint8_t *out_frame;
339         struct SwsContext *sw_scale_ctx;
340         AVPacket in_packet;
341         AVPacket out_packet;
342         int got_frame;
343         int got_packet;
344         int ret;
345
346         ret = video_input_init(&input_ctx, input_format_string, input_path, input_options);
347         if (ret < 0) {
348                 fprintf(stderr, "cannot initialize input\n");
349                 goto out;
350         }
351
352         ret = video_output_init(&output_ctx, &input_ctx, upscale, quality, image_format, dev);
353         if (ret < 0) {
354                 fprintf(stderr, "cannot initialize input\n");
355                 goto cleanup_input;
356         }
357
358         /* allocate an input frame */
359         frame_raw = av_frame_alloc();
360         if (frame_raw == NULL) {
361                 fprintf(stderr, "cannot allocate the raw frame!\n");
362                 ret = -ENOMEM;
363                 goto cleanup_output;
364         }
365
366         /* allocate output frame */
367         frame_scaled = av_frame_alloc();
368         if (frame_scaled == NULL) {
369                 fprintf(stderr, "cannot allocate the scaled frame!\n");
370                 ret = -ENOMEM;
371                 goto cleanup_frame_raw;
372         }
373         frame_scaled->format = (output_ctx.codec_ctx)->pix_fmt;
374         frame_scaled->width = (output_ctx.codec_ctx)->width;
375         frame_scaled->height = (output_ctx.codec_ctx)->height;
376
377         /* calculate the bytes needed for the output image and create buffer for the output image */
378         out_buf_size = av_image_get_buffer_size((output_ctx.codec_ctx)->pix_fmt,
379                                                 (output_ctx.codec_ctx)->width,
380                                                 (output_ctx.codec_ctx)->height,
381                                                 1);
382         out_buf = av_malloc(out_buf_size * sizeof(uint8_t));
383         if (out_buf == NULL) {
384                 fprintf(stderr, "cannot allocate output data buffer!\n");
385                 ret = -ENOMEM;
386                 goto cleanup_frame_scaled;
387         }
388
389         /* assign appropriate parts of buffer to image planes in frame_scaled */
390         av_image_fill_arrays(frame_scaled->data,
391                              frame_scaled->linesize,
392                              out_buf,
393                              (output_ctx.codec_ctx)->pix_fmt,
394                              (output_ctx.codec_ctx)->width,
395                              (output_ctx.codec_ctx)->height,
396                              1);
397
398         sw_scale_ctx = sws_getCachedContext(NULL,
399                                             (input_ctx.codec_ctx)->width,
400                                             (input_ctx.codec_ctx)->height,
401                                             (input_ctx.codec_ctx)->pix_fmt,
402                                             (output_ctx.codec_ctx)->width,
403                                             (output_ctx.codec_ctx)->height,
404                                             (output_ctx.codec_ctx)->pix_fmt,
405                                             rescale_method,
406                                             NULL, NULL, NULL);
407         if (sw_scale_ctx == NULL) {
408                 fprintf(stderr, "cannot set up the rescaling context!\n");
409                 ret = -EINVAL;
410                 goto cleanup_out_buf;
411         }
412
413         got_packet = 0;
414         while (run) {
415                 /* read packet */
416                 ret = av_read_frame(input_ctx.format_ctx, &in_packet);
417                 if (ret < 0) {
418                         if (ret == (int)AVERROR_EOF || input_ctx.format_ctx->pb->eof_reached)
419                                 ret = 0;
420                         else
421                                 fprintf(stderr, "av_read_frame failed, EOF?\n");
422                         run = 0;
423                         goto end_while;
424                 }
425
426                 if (in_packet.stream_index != input_ctx.video_stream_index) {
427                         /* that is more or less a "continue", but there is
428                          * still the packet to free */
429                         goto end_while;
430                 }
431
432                 /* decode */
433                 got_frame = 0;
434                 ret = decode(input_ctx.codec_ctx, frame_raw, &got_frame, &in_packet);
435                 if (ret < 0) {
436                         fprintf(stderr, "cannot decode video\n");
437                         run = 0;
438                         goto end_while;
439                 }
440
441                 /* if we got the complete frame */
442                 if (got_frame) {
443                         /*
444                          * Rescaling the frame also changes its pixel format
445                          * to the raw format supported by the projector if
446                          * this was set in video_output_init()
447                          */
448                         sws_scale(sw_scale_ctx,
449                                   (const uint8_t * const *)frame_raw->data,
450                                   frame_raw->linesize,
451                                   0,
452                                   (input_ctx.codec_ctx)->height,
453                                   frame_scaled->data,
454                                   frame_scaled->linesize);
455
456                         if (output_ctx.raw_output) {
457                                 out_frame = out_buf;
458                                 out_frame_size = out_buf_size;
459                         } else {
460                                 frame_scaled->quality = (output_ctx.codec_ctx)->global_quality;
461                                 av_init_packet(&out_packet);
462                                 out_packet.data = NULL;
463                                 out_packet.size = 0;
464                                 got_packet = 0;
465                                 ret = encode(output_ctx.codec_ctx,
466                                              &out_packet,
467                                              &got_packet,
468                                              frame_scaled);
469                                 if (ret < 0 || !got_packet) {
470                                         fprintf(stderr, "cannot encode video\n");
471                                         run = 0;
472                                         goto end_while;
473                                 }
474
475                                 out_frame = out_packet.data;
476                                 out_frame_size = out_packet.size;
477                         }
478
479 #ifdef DEBUG
480                         if (dump_frame) {
481                                 char filename[NAME_MAX];
482                                 FILE *file;
483                                 if (!output_ctx.raw_output)
484                                         snprintf(filename, NAME_MAX, "out_q%03d.jpg", quality);
485                                 else
486                                         snprintf(filename, NAME_MAX, "out.raw");
487                                 file = fopen(filename, "wb");
488                                 fwrite(out_frame, 1, out_frame_size, file);
489                                 fclose(file);
490                         }
491 #else
492                         (void) dump_frame;
493 #endif
494
495                         ret = am7xxx_send_image_async(dev,
496                                                       image_format,
497                                                       (output_ctx.codec_ctx)->width,
498                                                       (output_ctx.codec_ctx)->height,
499                                                       out_frame,
500                                                       out_frame_size);
501                         if (ret < 0) {
502                                 perror("am7xxx_send_image_async");
503                                 run = 0;
504                                 goto end_while;
505                         }
506                 }
507 end_while:
508                 if (!output_ctx.raw_output && got_packet)
509                         av_packet_unref(&out_packet);
510                 av_packet_unref(&in_packet);
511         }
512
513         sws_freeContext(sw_scale_ctx);
514 cleanup_out_buf:
515         av_free(out_buf);
516 cleanup_frame_scaled:
517         av_frame_free(&frame_scaled);
518 cleanup_frame_raw:
519         av_frame_free(&frame_raw);
520
521 cleanup_output:
522         /* Freeing the codec context is needed as well,
523          * see https://libav.org/documentation/doxygen/master/group__lavc__core.html#gaf4daa92361efb3523ef5afeb0b54077f
524          */
525         avcodec_close(output_ctx.codec_ctx);
526         avcodec_free_context(&(output_ctx.codec_ctx));
527
528 cleanup_input:
529         avcodec_close(input_ctx.codec_ctx);
530         avcodec_free_context(&(input_ctx.codec_ctx));
531         avformat_close_input(&(input_ctx.format_ctx));
532
533 out:
534         return ret;
535 }
536
537 #ifdef HAVE_XCB
538 #include <xcb/xcb.h>
539 static int x_get_screen_dimensions(const char *displayname, int *width, int *height)
540 {
541         int i, screen_number;
542         xcb_connection_t *connection;
543         const xcb_setup_t *setup;
544         xcb_screen_iterator_t iter;
545
546         connection = xcb_connect(displayname, &screen_number);
547         if (xcb_connection_has_error(connection)) {
548                 fprintf(stderr, "Cannot open a connection to %s\n", displayname);
549                 return -EINVAL;
550         }
551
552         setup = xcb_get_setup(connection);
553         if (setup == NULL) {
554                 fprintf(stderr, "Cannot get setup for %s\n", displayname);
555                 xcb_disconnect(connection);
556                 return -EINVAL;
557         }
558
559         iter = xcb_setup_roots_iterator(setup);
560         for (i = 0; i < screen_number; ++i) {
561                 xcb_screen_next(&iter);
562         }
563
564         xcb_screen_t *screen = iter.data;
565
566         *width = screen->width_in_pixels;
567         *height = screen->height_in_pixels;
568
569         xcb_disconnect(connection);
570
571         return 0;
572 }
573
574 static char *get_x_screen_size(const char *input_path)
575 {
576         int len;
577         int width;
578         int height;
579         char *screen_size;
580         int ret;
581
582         ret = x_get_screen_dimensions(input_path, &width, &height);
583         if (ret < 0) {
584                 fprintf(stderr, "Cannot get screen dimensions for %s\n", input_path);
585                 return NULL;
586         }
587
588         len = snprintf(NULL, 0, "%dx%d", width, height);
589
590         screen_size = malloc((len + 1) * sizeof(char));
591         if (screen_size == NULL) {
592                 perror("malloc");
593                 return NULL;
594         }
595
596         len = snprintf(screen_size, len + 1, "%dx%d", width, height);
597         if (len < 0) {
598                 free(screen_size);
599                 screen_size = NULL;
600                 return NULL;
601         }
602         return screen_size;
603 }
604 #else
605 static char *get_x_screen_size(const char *input_path)
606 {
607         (void) input_path;
608         fprintf(stderr, "%s: fallback implementation, assuming a vga screen\n", __func__);
609         return strdup("vga");
610 }
611 #endif
612
613 static void unset_run(int signo)
614 {
615         (void) signo;
616         run = 0;
617 }
618
619 #ifdef HAVE_SIGACTION
620 static int set_signal_handler(void (*signal_handler)(int))
621 {
622         struct sigaction new_action;
623         struct sigaction old_action;
624         int ret;
625
626         new_action.sa_handler = signal_handler;
627         sigemptyset(&new_action.sa_mask);
628         new_action.sa_flags = 0;
629
630         ret = sigaction(SIGINT, NULL, &old_action);
631         if (ret < 0) {
632                 perror("sigaction on old_action");
633                 goto out;
634         }
635
636         if (old_action.sa_handler != SIG_IGN) {
637                 ret = sigaction(SIGINT, &new_action, NULL);
638                 if (ret < 0) {
639                         perror("sigaction on new_action");
640                         goto out;
641                 }
642         }
643
644 out:
645         return ret;
646 }
647 #else
648 static int set_signal_handler(void (*signal_handler)(int))
649 {
650         (void)signal_handler;
651         fprintf(stderr, "set_signal_handler() not implemented, sigaction not available\n");
652         return 0;
653 }
654 #endif
655
656
657 static void usage(char *name)
658 {
659         printf("usage: %s [OPTIONS]\n\n", name);
660         printf("OPTIONS:\n");
661         printf("\t-d <index>\t\tthe device index (default is 0)\n");
662 #ifdef DEBUG
663         printf("\t-D \t\t\tdump the last frame to a file (only active in DEBUG mode)\n");
664 #endif
665         printf("\t-f <input format>\tthe input device format\n");
666         printf("\t-i <input path>\t\tthe input path\n");
667         printf("\t-o <options>\t\ta comma separated list of input format options\n");
668         printf("\t\t\t\tEXAMPLE:\n");
669         printf("\t\t\t\t\t-o draw_mouse=1,framerate=100,video_size=800x480\n");
670         printf("\t-s <scaling method>\tthe rescaling method (see swscale.h)\n");
671         printf("\t-u \t\t\tupscale the image if smaller than the display dimensions\n");
672         printf("\t-F <format>\t\tthe image format to use (default is JPEG)\n");
673         printf("\t\t\t\tSUPPORTED FORMATS:\n");
674         printf("\t\t\t\t\t1 - JPEG\n");
675         printf("\t\t\t\t\t2 - NV12\n");
676         printf("\t-q <quality>\t\tquality of jpeg sent to the device, between 1 and 100\n");
677         printf("\t-l <log level>\t\tthe verbosity level of libam7xxx output (0-5)\n");
678         printf("\t-p <power mode>\t\tthe power mode of device, between %d (off) and %d (turbo)\n",
679                AM7XXX_POWER_OFF, AM7XXX_POWER_TURBO);
680         printf("\t\t\t\tWARNING: Level 2 and greater require the master AND\n");
681         printf("\t\t\t\t         the slave connector to be plugged in.\n");
682         printf("\t-z <zoom mode>\t\tthe display zoom mode, between %d (original) and %d (tele)\n",
683                AM7XXX_ZOOM_ORIGINAL, AM7XXX_ZOOM_TELE);
684         printf("\t-h \t\t\tthis help message\n");
685         printf("\n\nEXAMPLES OF USE:\n");
686         printf("\t%s -f x11grab -i :0.0 -o video_size=800x480\n", name);
687         printf("\t%s -f fbdev -i /dev/fb0\n", name);
688         printf("\t%s -f video4linux2 -i /dev/video0 -o video_size=320x240,frame_rate=100 -u -q 90\n", name);
689         printf("\t%s -i http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_640x360.m4v\n", name);
690 }
691
692 int main(int argc, char *argv[])
693 {
694         int ret;
695         int opt;
696         char *subopts;
697         char *subopts_saved;
698         char *subopt;
699         char *input_format_string = NULL;
700         AVDictionary *options = NULL;
701         char *input_path = NULL;
702         unsigned int rescale_method = SWS_BICUBIC;
703         unsigned int upscale = 0;
704         unsigned int quality = 95;
705         int log_level = AM7XXX_LOG_INFO;
706         int device_index = 0;
707         int power_mode = AM7XXX_POWER_LOW;
708         int zoom = AM7XXX_ZOOM_ORIGINAL;
709         int format = AM7XXX_IMAGE_FORMAT_JPEG;
710         am7xxx_context *ctx;
711         am7xxx_device *dev;
712         int dump_frame = 0;
713
714         while ((opt = getopt(argc, argv, "d:Df:i:o:s:uF:q:l:p:z:h")) != -1) {
715                 switch (opt) {
716                 case 'd':
717                         device_index = atoi(optarg);
718                         if (device_index < 0) {
719                                 fprintf(stderr, "Unsupported device index\n");
720                                 ret = -EINVAL;
721                                 goto out;
722                         }
723                         break;
724                 case 'D':
725                         dump_frame = 1;
726 #ifndef DEBUG
727                         fprintf(stderr, "Warning: the -D option is only active in DEBUG mode.\n");
728 #endif
729                         break;
730                 case 'f':
731                         input_format_string = strdup(optarg);
732                         break;
733                 case 'i':
734                         input_path = strdup(optarg);
735                         break;
736                 case 'o':
737 #ifdef HAVE_STRTOK_R
738                         /*
739                          * parse suboptions, the expected format is something
740                          * like:
741                          *   draw_mouse=1,framerate=100,video_size=800x480
742                          */
743                         subopts = subopts_saved = strdup(optarg);
744                         while ((subopt = strtok_r(subopts, ",", &subopts))) {
745                                 char *subopt_name = strtok_r(subopt, "=", &subopt);
746                                 char *subopt_value = strtok_r(NULL, "", &subopt);
747                                 if (subopt_value == NULL) {
748                                         fprintf(stderr, "invalid suboption: %s\n", subopt_name);
749                                         continue;
750                                 }
751                                 av_dict_set(&options, subopt_name, subopt_value, 0);
752                         }
753                         free(subopts_saved);
754 #else
755                         fprintf(stderr, "Option '-o' not implemented\n");
756 #endif
757                         break;
758                 case 's':
759                         rescale_method = atoi(optarg);
760                         switch(rescale_method) {
761                         case SWS_FAST_BILINEAR:
762                         case SWS_BILINEAR:
763                         case SWS_BICUBIC:
764                         case SWS_X:
765                         case SWS_POINT:
766                         case SWS_AREA:
767                         case SWS_BICUBLIN:
768                         case SWS_GAUSS:
769                         case SWS_SINC:
770                         case SWS_LANCZOS:
771                         case SWS_SPLINE:
772                                 break;
773                         default:
774                                 fprintf(stderr, "Unsupported rescale method\n");
775                                 ret = -EINVAL;
776                                 goto out;
777                         }
778                         break;
779                 case 'u':
780                         upscale = 1;
781                         break;
782                 case 'F':
783                         format = atoi(optarg);
784                         switch(format) {
785                         case AM7XXX_IMAGE_FORMAT_JPEG:
786                                 fprintf(stdout, "JPEG format\n");
787                                 break;
788                         case AM7XXX_IMAGE_FORMAT_NV12:
789                                 fprintf(stdout, "NV12 format\n");
790                                 break;
791                         default:
792                                 fprintf(stderr, "Unsupported format\n");
793                                 ret = -EINVAL;
794                                 goto out;
795                         }
796                         break;
797                 case 'q':
798                         quality = atoi(optarg);
799                         if (quality < 1 || quality > 100) {
800                                 fprintf(stderr, "Invalid quality value, must be between 1 and 100\n");
801                                 ret = -EINVAL;
802                                 goto out;
803                         }
804                         break;
805                 case 'l':
806                         log_level = atoi(optarg);
807                         if (log_level < AM7XXX_LOG_FATAL || log_level > AM7XXX_LOG_TRACE) {
808                                 fprintf(stderr, "Unsupported log level, falling back to AM7XXX_LOG_ERROR\n");
809                                 log_level = AM7XXX_LOG_ERROR;
810                         }
811                         break;
812                 case 'p':
813                         power_mode = atoi(optarg);
814                         switch(power_mode) {
815                         case AM7XXX_POWER_OFF:
816                         case AM7XXX_POWER_LOW:
817                         case AM7XXX_POWER_MIDDLE:
818                         case AM7XXX_POWER_HIGH:
819                         case AM7XXX_POWER_TURBO:
820                                 fprintf(stdout, "Power mode: %d\n", power_mode);
821                                 break;
822                         default:
823                                 fprintf(stderr, "Invalid power mode value, must be between %d and %d\n",
824                                         AM7XXX_POWER_OFF, AM7XXX_POWER_TURBO);
825                                 ret = -EINVAL;
826                                 goto out;
827                         }
828                         break;
829                 case 'z':
830                         zoom = atoi(optarg);
831                         switch(zoom) {
832                         case AM7XXX_ZOOM_ORIGINAL:
833                         case AM7XXX_ZOOM_H:
834                         case AM7XXX_ZOOM_H_V:
835                         case AM7XXX_ZOOM_TEST:
836                         case AM7XXX_ZOOM_TELE:
837                                 fprintf(stdout, "Zoom: %d\n", zoom);
838                                 break;
839                         default:
840                                 fprintf(stderr, "Invalid zoom mode value, must be between %d and %d\n",
841                                         AM7XXX_ZOOM_ORIGINAL, AM7XXX_ZOOM_TELE);
842                                 ret = -EINVAL;
843                                 goto out;
844                         }
845                         break;
846                 case 'h':
847                         usage(argv[0]);
848                         ret = 0;
849                         goto out;
850                 default: /* '?' */
851                         usage(argv[0]);
852                         ret = -EINVAL;
853                         goto out;
854                 }
855         }
856
857         if (input_path == NULL) {
858                 fprintf(stderr, "The -i option must always be passed\n\n");
859                 usage(argv[0]);
860                 ret = -EINVAL;
861                 goto out;
862         }
863
864         /*
865          * When the input format is 'x11grab' set some useful fallback options
866          * if not supplied by the user, in particular grab full screen
867          */
868         if (input_format_string && strcmp(input_format_string, "x11grab") == 0) {
869                 char *video_size;
870
871                 video_size = get_x_screen_size(input_path);
872
873                 if (!av_dict_get(options, "video_size", NULL, 0))
874                         av_dict_set(&options, "video_size", video_size, 0);
875
876                 if (!av_dict_get(options, "framerate", NULL, 0))
877                         av_dict_set(&options, "framerate", "60", 0);
878
879                 if (!av_dict_get(options, "draw_mouse", NULL, 0))
880                         av_dict_set(&options, "draw_mouse",  "1", 0);
881
882                 free(video_size);
883         }
884
885         ret = set_signal_handler(unset_run);
886         if (ret < 0) {
887                 perror("sigaction");
888                 goto out;
889         }
890
891         ret = am7xxx_init(&ctx);
892         if (ret < 0) {
893                 perror("am7xxx_init");
894                 goto out;
895         }
896
897         am7xxx_set_log_level(ctx, log_level);
898
899         ret = am7xxx_open_device(ctx, &dev, device_index);
900         if (ret < 0) {
901                 perror("am7xxx_open_device");
902                 goto cleanup;
903         }
904
905         ret = am7xxx_set_zoom_mode(dev, zoom);
906         if (ret < 0) {
907                 perror("am7xxx_set_zoom_mode");
908                 goto cleanup;
909         }
910
911         ret = am7xxx_set_power_mode(dev, power_mode);
912         if (ret < 0) {
913                 perror("am7xxx_set_power_mode");
914                 goto cleanup;
915         }
916
917         /* When setting AM7XXX_ZOOM_TEST don't display the actual image */
918         if (zoom == AM7XXX_ZOOM_TEST)
919                 goto cleanup;
920
921         ret = am7xxx_play(input_format_string,
922                           &options,
923                           input_path,
924                           rescale_method,
925                           upscale,
926                           quality,
927                           format,
928                           dev,
929                           dump_frame);
930         if (ret < 0) {
931                 fprintf(stderr, "am7xxx_play failed\n");
932                 goto cleanup;
933         }
934
935 cleanup:
936         am7xxx_shutdown(ctx);
937 out:
938         av_dict_free(&options);
939         free(input_path);
940         free(input_format_string);
941         return ret;
942 }