e3ceb5c7d5ef808f4d65a4bb149cdb20ed62fb9e
[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         output_codec_ctx->pix_fmt    = AV_PIX_FMT_YUVJ420P;
215         output_codec_ctx->codec_id   = AV_CODEC_ID_MJPEG;
216         output_codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
217
218         /* Set quality and other VBR settings */
219
220         /* @note: 'quality' is expected to be between 1 and 100, but a value
221          * between 0 to 99 has to be passed when calculating qmin and qmax.
222          * This way qmin and qmax will cover the range 1-FF_QUALITY_SCALE, and
223          * in particular they won't be 0, this is needed because they are used
224          * as divisor somewhere in the encoding process */
225         output_codec_ctx->qmin       = output_codec_ctx->qmax = ((100 - (quality - 1)) * FF_QUALITY_SCALE) / 100;
226         output_codec_ctx->mb_lmin    = output_codec_ctx->qmin * FF_QP2LAMBDA;
227         output_codec_ctx->mb_lmax    = output_codec_ctx->qmax * FF_QP2LAMBDA;
228         output_codec_ctx->flags      |= AV_CODEC_FLAG_QSCALE;
229         output_codec_ctx->global_quality = output_codec_ctx->qmin * FF_QP2LAMBDA;
230
231         /* find the encoder */
232         output_codec = avcodec_find_encoder(output_codec_ctx->codec_id);
233         if (output_codec == NULL) {
234                 fprintf(stderr, "cannot find output codec!\n");
235                 ret = -EINVAL;
236                 goto cleanup;
237         }
238
239         /* open the codec */
240         ret = avcodec_open2(output_codec_ctx, output_codec, NULL);
241         if (ret < 0) {
242                 fprintf(stderr, "could not open output codec!\n");
243                 goto cleanup;
244         }
245
246         output_ctx->codec_ctx = output_codec_ctx;
247         output_ctx->raw_output = 0;
248
249         ret = 0;
250         goto out;
251
252 cleanup:
253         avcodec_free_context(&output_codec_ctx);
254 out:
255         return ret;
256 }
257
258
259 /*
260  * Wrap the new avcodec API from FFMpeg 3.1 to minimize the changes in the
261  * user code.
262  *
263  * If the use of the wrappers were to be made conditional, a check like the
264  * following could be used:
265  *
266  *      #if (LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101))
267  *
268  * As derived from the APIchanges document:
269  * https://github.com/FFmpeg/FFmpeg/blob/master/doc/APIchanges
270  *
271  * The wrapper implementation has been taken from:
272  * https://blogs.gentoo.org/lu_zero/2016/03/29/new-avcodec-api/
273  */
274 static int decode(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacket *pkt)
275 {
276         int ret;
277
278         *got_frame = 0;
279
280         if (pkt) {
281                 ret = avcodec_send_packet(avctx, pkt);
282                 /*
283                  * In particular, we don't expect AVERROR(EAGAIN), because we
284                  * read all decoded frames with avcodec_receive_frame() until
285                  * done.
286                  */
287                 if (ret < 0)
288                         return ret == AVERROR_EOF ? 0 : ret;
289         }
290
291         ret = avcodec_receive_frame(avctx, frame);
292         if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
293                 return ret;
294         if (ret >= 0)
295                 *got_frame = 1;
296
297         return 0;
298 }
299
300 static int encode(AVCodecContext *avctx, AVPacket *pkt, int *got_packet, AVFrame *frame)
301 {
302         int ret;
303
304         *got_packet = 0;
305
306         ret = avcodec_send_frame(avctx, frame);
307         if (ret < 0)
308                 return ret;
309
310         ret = avcodec_receive_packet(avctx, pkt);
311         if (!ret)
312                 *got_packet = 1;
313         if (ret == AVERROR(EAGAIN))
314                 return 0;
315
316         return ret;
317 }
318
319
320 static int am7xxx_play(const char *input_format_string,
321                        AVDictionary **input_options,
322                        const char *input_path,
323                        unsigned int rescale_method,
324                        unsigned int upscale,
325                        unsigned int quality,
326                        am7xxx_image_format image_format,
327                        am7xxx_device *dev,
328                        int dump_frame)
329 {
330         struct video_input_ctx input_ctx;
331         struct video_output_ctx output_ctx;
332         AVFrame *frame_raw;
333         AVFrame *frame_scaled;
334         int out_buf_size;
335         uint8_t *out_buf;
336         int out_frame_size;
337         uint8_t *out_frame;
338         struct SwsContext *sw_scale_ctx;
339         AVPacket in_packet;
340         AVPacket out_packet;
341         int got_frame;
342         int got_packet;
343         int ret;
344
345         ret = video_input_init(&input_ctx, input_format_string, input_path, input_options);
346         if (ret < 0) {
347                 fprintf(stderr, "cannot initialize input\n");
348                 goto out;
349         }
350
351         ret = video_output_init(&output_ctx, &input_ctx, upscale, quality, image_format, dev);
352         if (ret < 0) {
353                 fprintf(stderr, "cannot initialize input\n");
354                 goto cleanup_input;
355         }
356
357         /* allocate an input frame */
358         frame_raw = av_frame_alloc();
359         if (frame_raw == NULL) {
360                 fprintf(stderr, "cannot allocate the raw frame!\n");
361                 ret = -ENOMEM;
362                 goto cleanup_output;
363         }
364
365         /* allocate output frame */
366         frame_scaled = av_frame_alloc();
367         if (frame_scaled == NULL) {
368                 fprintf(stderr, "cannot allocate the scaled frame!\n");
369                 ret = -ENOMEM;
370                 goto cleanup_frame_raw;
371         }
372         frame_scaled->format = (output_ctx.codec_ctx)->pix_fmt;
373         frame_scaled->width = (output_ctx.codec_ctx)->width;
374         frame_scaled->height = (output_ctx.codec_ctx)->height;
375
376         /* calculate the bytes needed for the output image and create buffer for the output image */
377         out_buf_size = av_image_get_buffer_size((output_ctx.codec_ctx)->pix_fmt,
378                                                 (output_ctx.codec_ctx)->width,
379                                                 (output_ctx.codec_ctx)->height,
380                                                 1);
381         out_buf = av_malloc(out_buf_size * sizeof(uint8_t));
382         if (out_buf == NULL) {
383                 fprintf(stderr, "cannot allocate output data buffer!\n");
384                 ret = -ENOMEM;
385                 goto cleanup_frame_scaled;
386         }
387
388         /* assign appropriate parts of buffer to image planes in frame_scaled */
389         av_image_fill_arrays(frame_scaled->data,
390                              frame_scaled->linesize,
391                              out_buf,
392                              (output_ctx.codec_ctx)->pix_fmt,
393                              (output_ctx.codec_ctx)->width,
394                              (output_ctx.codec_ctx)->height,
395                              1);
396
397         sw_scale_ctx = sws_getCachedContext(NULL,
398                                             (input_ctx.codec_ctx)->width,
399                                             (input_ctx.codec_ctx)->height,
400                                             (input_ctx.codec_ctx)->pix_fmt,
401                                             (output_ctx.codec_ctx)->width,
402                                             (output_ctx.codec_ctx)->height,
403                                             (output_ctx.codec_ctx)->pix_fmt,
404                                             rescale_method,
405                                             NULL, NULL, NULL);
406         if (sw_scale_ctx == NULL) {
407                 fprintf(stderr, "cannot set up the rescaling context!\n");
408                 ret = -EINVAL;
409                 goto cleanup_out_buf;
410         }
411
412         got_packet = 0;
413         while (run) {
414                 /* read packet */
415                 ret = av_read_frame(input_ctx.format_ctx, &in_packet);
416                 if (ret < 0) {
417                         if (ret == (int)AVERROR_EOF || input_ctx.format_ctx->pb->eof_reached)
418                                 ret = 0;
419                         else
420                                 fprintf(stderr, "av_read_frame failed, EOF?\n");
421                         run = 0;
422                         goto end_while;
423                 }
424
425                 if (in_packet.stream_index != input_ctx.video_stream_index) {
426                         /* that is more or less a "continue", but there is
427                          * still the packet to free */
428                         goto end_while;
429                 }
430
431                 /* decode */
432                 got_frame = 0;
433                 ret = decode(input_ctx.codec_ctx, frame_raw, &got_frame, &in_packet);
434                 if (ret < 0) {
435                         fprintf(stderr, "cannot decode video\n");
436                         run = 0;
437                         goto end_while;
438                 }
439
440                 /* if we got the complete frame */
441                 if (got_frame) {
442                         /* 
443                          * Rescaling the frame also changes its pixel format
444                          * to the raw format supported by the projector if
445                          * this was set in video_output_init()
446                          */
447                         sws_scale(sw_scale_ctx,
448                                   (const uint8_t * const *)frame_raw->data,
449                                   frame_raw->linesize,
450                                   0,
451                                   (input_ctx.codec_ctx)->height,
452                                   frame_scaled->data,
453                                   frame_scaled->linesize);
454
455                         if (output_ctx.raw_output) {
456                                 out_frame = out_buf;
457                                 out_frame_size = out_buf_size;
458                         } else {
459                                 frame_scaled->quality = (output_ctx.codec_ctx)->global_quality;
460                                 av_init_packet(&out_packet);
461                                 out_packet.data = NULL;
462                                 out_packet.size = 0;
463                                 got_packet = 0;
464                                 ret = encode(output_ctx.codec_ctx,
465                                              &out_packet,
466                                              &got_packet,
467                                              frame_scaled);
468                                 if (ret < 0 || !got_packet) {
469                                         fprintf(stderr, "cannot encode video\n");
470                                         run = 0;
471                                         goto end_while;
472                                 }
473
474                                 out_frame = out_packet.data;
475                                 out_frame_size = out_packet.size;
476                         }
477
478 #ifdef DEBUG
479                         if (dump_frame) {
480                                 char filename[NAME_MAX];
481                                 FILE *file;
482                                 if (!output_ctx.raw_output)
483                                         snprintf(filename, NAME_MAX, "out_q%03d.jpg", quality);
484                                 else
485                                         snprintf(filename, NAME_MAX, "out.raw");
486                                 file = fopen(filename, "wb");
487                                 fwrite(out_frame, 1, out_frame_size, file);
488                                 fclose(file);
489                         }
490 #else
491                         (void) dump_frame;
492 #endif
493
494                         ret = am7xxx_send_image_async(dev,
495                                                       image_format,
496                                                       (output_ctx.codec_ctx)->width,
497                                                       (output_ctx.codec_ctx)->height,
498                                                       out_frame,
499                                                       out_frame_size);
500                         if (ret < 0) {
501                                 perror("am7xxx_send_image_async");
502                                 run = 0;
503                                 goto end_while;
504                         }
505                 }
506 end_while:
507                 if (!output_ctx.raw_output && got_packet)
508                         av_packet_unref(&out_packet);
509                 av_packet_unref(&in_packet);
510         }
511
512         sws_freeContext(sw_scale_ctx);
513 cleanup_out_buf:
514         av_free(out_buf);
515 cleanup_frame_scaled:
516         av_frame_free(&frame_scaled);
517 cleanup_frame_raw:
518         av_frame_free(&frame_raw);
519
520 cleanup_output:
521         /* Freeing the codec context is needed as well,
522          * see https://libav.org/documentation/doxygen/master/group__lavc__core.html#gaf4daa92361efb3523ef5afeb0b54077f
523          */
524         avcodec_close(output_ctx.codec_ctx);
525         avcodec_free_context(&(output_ctx.codec_ctx));
526
527 cleanup_input:
528         avcodec_close(input_ctx.codec_ctx);
529         avcodec_free_context(&(input_ctx.codec_ctx));
530         avformat_close_input(&(input_ctx.format_ctx));
531
532 out:
533         return ret;
534 }
535
536 #ifdef HAVE_XCB
537 #include <xcb/xcb.h>
538 static int x_get_screen_dimensions(const char *displayname, int *width, int *height)
539 {
540         int i, screen_number;
541         xcb_connection_t *connection;
542         const xcb_setup_t *setup;
543         xcb_screen_iterator_t iter;
544
545         connection = xcb_connect(displayname, &screen_number);
546         if (xcb_connection_has_error(connection)) {
547                 fprintf(stderr, "Cannot open a connection to %s\n", displayname);
548                 return -EINVAL;
549         }
550
551         setup = xcb_get_setup(connection);
552         if (setup == NULL) {
553                 fprintf(stderr, "Cannot get setup for %s\n", displayname);
554                 xcb_disconnect(connection);
555                 return -EINVAL;
556         }
557
558         iter = xcb_setup_roots_iterator(setup);
559         for (i = 0; i < screen_number; ++i) {
560                 xcb_screen_next(&iter);
561         }
562
563         xcb_screen_t *screen = iter.data;
564
565         *width = screen->width_in_pixels;
566         *height = screen->height_in_pixels;
567
568         xcb_disconnect(connection);
569
570         return 0;
571 }
572
573 static char *get_x_screen_size(const char *input_path)
574 {
575         int len;
576         int width;
577         int height;
578         char *screen_size;
579         int ret;
580
581         ret = x_get_screen_dimensions(input_path, &width, &height);
582         if (ret < 0) {
583                 fprintf(stderr, "Cannot get screen dimensions for %s\n", input_path);
584                 return NULL;
585         }
586
587         len = snprintf(NULL, 0, "%dx%d", width, height);
588
589         screen_size = malloc((len + 1) * sizeof(char));
590         if (screen_size == NULL) {
591                 perror("malloc");
592                 return NULL;
593         }
594
595         len = snprintf(screen_size, len + 1, "%dx%d", width, height);
596         if (len < 0) {
597                 free(screen_size);
598                 screen_size = NULL;
599                 return NULL;
600         }
601         return screen_size;
602 }
603 #else
604 static char *get_x_screen_size(const char *input_path)
605 {
606         (void) input_path;
607         fprintf(stderr, "%s: fallback implementation, assuming a vga screen\n", __func__);
608         return strdup("vga");
609 }
610 #endif
611
612 static void unset_run(int signo)
613 {
614         (void) signo;
615         run = 0;
616 }
617
618 #ifdef HAVE_SIGACTION
619 static int set_signal_handler(void (*signal_handler)(int))
620 {
621         struct sigaction new_action;
622         struct sigaction old_action;
623         int ret;
624
625         new_action.sa_handler = signal_handler;
626         sigemptyset(&new_action.sa_mask);
627         new_action.sa_flags = 0;
628
629         ret = sigaction(SIGINT, NULL, &old_action);
630         if (ret < 0) {
631                 perror("sigaction on old_action");
632                 goto out;
633         }
634
635         if (old_action.sa_handler != SIG_IGN) {
636                 ret = sigaction(SIGINT, &new_action, NULL);
637                 if (ret < 0) {
638                         perror("sigaction on new_action");
639                         goto out;
640                 }
641         }
642
643 out:
644         return ret;
645 }
646 #else
647 static int set_signal_handler(void (*signal_handler)(int))
648 {
649         (void)signal_handler;
650         fprintf(stderr, "set_signal_handler() not implemented, sigaction not available\n");
651         return 0;
652 }
653 #endif
654
655
656 static void usage(char *name)
657 {
658         printf("usage: %s [OPTIONS]\n\n", name);
659         printf("OPTIONS:\n");
660         printf("\t-d <index>\t\tthe device index (default is 0)\n");
661 #ifdef DEBUG
662         printf("\t-D \t\t\tdump the last frame to a file (only active in DEBUG mode)\n");
663 #endif
664         printf("\t-f <input format>\tthe input device format\n");
665         printf("\t-i <input path>\t\tthe input path\n");
666         printf("\t-o <options>\t\ta comma separated list of input format options\n");
667         printf("\t\t\t\tEXAMPLE:\n");
668         printf("\t\t\t\t\t-o draw_mouse=1,framerate=100,video_size=800x480\n");
669         printf("\t-s <scaling method>\tthe rescaling method (see swscale.h)\n");
670         printf("\t-u \t\t\tupscale the image if smaller than the display dimensions\n");
671         printf("\t-F <format>\t\tthe image format to use (default is JPEG)\n");
672         printf("\t\t\t\tSUPPORTED FORMATS:\n");
673         printf("\t\t\t\t\t1 - JPEG\n");
674         printf("\t\t\t\t\t2 - NV12\n");
675         printf("\t-q <quality>\t\tquality of jpeg sent to the device, between 1 and 100\n");
676         printf("\t-l <log level>\t\tthe verbosity level of libam7xxx output (0-5)\n");
677         printf("\t-p <power mode>\t\tthe power mode of device, between %d (off) and %d (turbo)\n",
678                AM7XXX_POWER_OFF, AM7XXX_POWER_TURBO);
679         printf("\t\t\t\tWARNING: Level 2 and greater require the master AND\n");
680         printf("\t\t\t\t         the slave connector to be plugged in.\n");
681         printf("\t-z <zoom mode>\t\tthe display zoom mode, between %d (original) and %d (tele)\n",
682                AM7XXX_ZOOM_ORIGINAL, AM7XXX_ZOOM_TELE);
683         printf("\t-h \t\t\tthis help message\n");
684         printf("\n\nEXAMPLES OF USE:\n");
685         printf("\t%s -f x11grab -i :0.0 -o video_size=800x480\n", name);
686         printf("\t%s -f fbdev -i /dev/fb0\n", name);
687         printf("\t%s -f video4linux2 -i /dev/video0 -o video_size=320x240,frame_rate=100 -u -q 90\n", name);
688         printf("\t%s -i http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_640x360.m4v\n", name);
689 }
690
691 int main(int argc, char *argv[])
692 {
693         int ret;
694         int opt;
695         char *subopts;
696         char *subopts_saved;
697         char *subopt;
698         char *input_format_string = NULL;
699         AVDictionary *options = NULL;
700         char *input_path = NULL;
701         unsigned int rescale_method = SWS_BICUBIC;
702         unsigned int upscale = 0;
703         unsigned int quality = 95;
704         int log_level = AM7XXX_LOG_INFO;
705         int device_index = 0;
706         int power_mode = AM7XXX_POWER_LOW;
707         int zoom = AM7XXX_ZOOM_ORIGINAL;
708         int format = AM7XXX_IMAGE_FORMAT_JPEG;
709         am7xxx_context *ctx;
710         am7xxx_device *dev;
711         int dump_frame = 0;
712
713         while ((opt = getopt(argc, argv, "d:Df:i:o:s:uF:q:l:p:z:h")) != -1) {
714                 switch (opt) {
715                 case 'd':
716                         device_index = atoi(optarg);
717                         if (device_index < 0) {
718                                 fprintf(stderr, "Unsupported device index\n");
719                                 ret = -EINVAL;
720                                 goto out;
721                         }
722                         break;
723                 case 'D':
724                         dump_frame = 1;
725 #ifndef DEBUG
726                         fprintf(stderr, "Warning: the -D option is only active in DEBUG mode.\n");
727 #endif
728                         break;
729                 case 'f':
730                         input_format_string = strdup(optarg);
731                         break;
732                 case 'i':
733                         input_path = strdup(optarg);
734                         break;
735                 case 'o':
736 #ifdef HAVE_STRTOK_R
737                         /*
738                          * parse suboptions, the expected format is something
739                          * like:
740                          *   draw_mouse=1,framerate=100,video_size=800x480
741                          */
742                         subopts = subopts_saved = strdup(optarg);
743                         while ((subopt = strtok_r(subopts, ",", &subopts))) {
744                                 char *subopt_name = strtok_r(subopt, "=", &subopt);
745                                 char *subopt_value = strtok_r(NULL, "", &subopt);
746                                 if (subopt_value == NULL) {
747                                         fprintf(stderr, "invalid suboption: %s\n", subopt_name);
748                                         continue;
749                                 }
750                                 av_dict_set(&options, subopt_name, subopt_value, 0);
751                         }
752                         free(subopts_saved);
753 #else
754                         fprintf(stderr, "Option '-o' not implemented\n");
755 #endif
756                         break;
757                 case 's':
758                         rescale_method = atoi(optarg);
759                         switch(rescale_method) {
760                         case SWS_FAST_BILINEAR:
761                         case SWS_BILINEAR:
762                         case SWS_BICUBIC:
763                         case SWS_X:
764                         case SWS_POINT:
765                         case SWS_AREA:
766                         case SWS_BICUBLIN:
767                         case SWS_GAUSS:
768                         case SWS_SINC:
769                         case SWS_LANCZOS:
770                         case SWS_SPLINE:
771                                 break;
772                         default:
773                                 fprintf(stderr, "Unsupported rescale method\n");
774                                 ret = -EINVAL;
775                                 goto out;
776                         }
777                         break;
778                 case 'u':
779                         upscale = 1;
780                         break;
781                 case 'F':
782                         format = atoi(optarg);
783                         switch(format) {
784                         case AM7XXX_IMAGE_FORMAT_JPEG:
785                                 fprintf(stdout, "JPEG format\n");
786                                 break;
787                         case AM7XXX_IMAGE_FORMAT_NV12:
788                                 fprintf(stdout, "NV12 format\n");
789                                 break;
790                         default:
791                                 fprintf(stderr, "Unsupported format\n");
792                                 ret = -EINVAL;
793                                 goto out;
794                         }
795                         break;
796                 case 'q':
797                         quality = atoi(optarg);
798                         if (quality < 1 || quality > 100) {
799                                 fprintf(stderr, "Invalid quality value, must be between 1 and 100\n");
800                                 ret = -EINVAL;
801                                 goto out;
802                         }
803                         break;
804                 case 'l':
805                         log_level = atoi(optarg);
806                         if (log_level < AM7XXX_LOG_FATAL || log_level > AM7XXX_LOG_TRACE) {
807                                 fprintf(stderr, "Unsupported log level, falling back to AM7XXX_LOG_ERROR\n");
808                                 log_level = AM7XXX_LOG_ERROR;
809                         }
810                         break;
811                 case 'p':
812                         power_mode = atoi(optarg);
813                         switch(power_mode) {
814                         case AM7XXX_POWER_OFF:
815                         case AM7XXX_POWER_LOW:
816                         case AM7XXX_POWER_MIDDLE:
817                         case AM7XXX_POWER_HIGH:
818                         case AM7XXX_POWER_TURBO:
819                                 fprintf(stdout, "Power mode: %d\n", power_mode);
820                                 break;
821                         default:
822                                 fprintf(stderr, "Invalid power mode value, must be between %d and %d\n",
823                                         AM7XXX_POWER_OFF, AM7XXX_POWER_TURBO);
824                                 ret = -EINVAL;
825                                 goto out;
826                         }
827                         break;
828                 case 'z':
829                         zoom = atoi(optarg);
830                         switch(zoom) {
831                         case AM7XXX_ZOOM_ORIGINAL:
832                         case AM7XXX_ZOOM_H:
833                         case AM7XXX_ZOOM_H_V:
834                         case AM7XXX_ZOOM_TEST:
835                         case AM7XXX_ZOOM_TELE:
836                                 fprintf(stdout, "Zoom: %d\n", zoom);
837                                 break;
838                         default:
839                                 fprintf(stderr, "Invalid zoom mode value, must be between %d and %d\n",
840                                         AM7XXX_ZOOM_ORIGINAL, AM7XXX_ZOOM_TELE);
841                                 ret = -EINVAL;
842                                 goto out;
843                         }
844                         break;
845                 case 'h':
846                         usage(argv[0]);
847                         ret = 0;
848                         goto out;
849                 default: /* '?' */
850                         usage(argv[0]);
851                         ret = -EINVAL;
852                         goto out;
853                 }
854         }
855
856         if (input_path == NULL) {
857                 fprintf(stderr, "The -i option must always be passed\n\n");
858                 usage(argv[0]);
859                 ret = -EINVAL;
860                 goto out;
861         }
862
863         /*
864          * When the input format is 'x11grab' set some useful fallback options
865          * if not supplied by the user, in particular grab full screen
866          */
867         if (input_format_string && strcmp(input_format_string, "x11grab") == 0) {
868                 char *video_size;
869
870                 video_size = get_x_screen_size(input_path);
871
872                 if (!av_dict_get(options, "video_size", NULL, 0))
873                         av_dict_set(&options, "video_size", video_size, 0);
874
875                 if (!av_dict_get(options, "framerate", NULL, 0))
876                         av_dict_set(&options, "framerate", "60", 0);
877
878                 if (!av_dict_get(options, "draw_mouse", NULL, 0))
879                         av_dict_set(&options, "draw_mouse",  "1", 0);
880
881                 free(video_size);
882         }
883
884         ret = set_signal_handler(unset_run);
885         if (ret < 0) {
886                 perror("sigaction");
887                 goto out;
888         }
889
890         ret = am7xxx_init(&ctx);
891         if (ret < 0) {
892                 perror("am7xxx_init");
893                 goto out;
894         }
895
896         am7xxx_set_log_level(ctx, log_level);
897
898         ret = am7xxx_open_device(ctx, &dev, device_index);
899         if (ret < 0) {
900                 perror("am7xxx_open_device");
901                 goto cleanup;
902         }
903
904         ret = am7xxx_set_zoom_mode(dev, zoom);
905         if (ret < 0) {
906                 perror("am7xxx_set_zoom_mode");
907                 goto cleanup;
908         }
909
910         ret = am7xxx_set_power_mode(dev, power_mode);
911         if (ret < 0) {
912                 perror("am7xxx_set_power_mode");
913                 goto cleanup;
914         }
915
916         /* When setting AM7XXX_ZOOM_TEST don't display the actual image */
917         if (zoom == AM7XXX_ZOOM_TEST)
918                 goto cleanup;
919
920         ret = am7xxx_play(input_format_string,
921                           &options,
922                           input_path,
923                           rescale_method,
924                           upscale,
925                           quality,
926                           format,
927                           dev,
928                           dump_frame);
929         if (ret < 0) {
930                 fprintf(stderr, "am7xxx_play failed\n");
931                 goto cleanup;
932         }
933
934 cleanup:
935         am7xxx_shutdown(ctx);
936 out:
937         av_dict_free(&options);
938         free(input_path);
939         free(input_format_string);
940         return ret;
941 }