libam7xxx  0.1
Communication library for Actions Micro AM7XXX based USB projectors and DPFs
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 
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 */
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",
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",
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) {
786  fprintf(stdout, "JPEG format\n");
787  break;
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",
825  ret = -EINVAL;
826  goto out;
827  }
828  break;
829  case 'z':
830  zoom = atoi(optarg);
831  switch(zoom) {
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",
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 }
Zoom test screen, the firmware version is shown as well.
Definition: am7xxx.h:126
Zoom 1: H Scale (changes aspect ratio).
Definition: am7xxx.h:124
struct _am7xxx_context am7xxx_context
An opaque data type representing a context.
Definition: am7xxx.h:38
JPEG format.
Definition: am7xxx.h:81
Zoom Tele: available on some PicoPix models.
Definition: am7xxx.h:127
int am7xxx_init(am7xxx_context **ctx)
Initialize the library context and data structures, and scan for devices.
Definition: am7xxx.c:1100
Original Size, as retrieved via am7xxx_device_info.
Definition: am7xxx.h:123
Max brightness and power consumption.
Definition: am7xxx.h:104
Middle level of brightness.
Definition: am7xxx.h:102
am7xxx_image_format
The image formats accepted by the device.
Definition: am7xxx.h:80
Public libam7xxx API.
int am7xxx_open_device(am7xxx_context *ctx, am7xxx_device **dev, unsigned int device_index)
Open an am7xxx_device according to a index.
Definition: am7xxx.c:1168
Zoom 2: H/V Scale (changes aspect ratio).
Definition: am7xxx.h:125
More brightness, but more power consumption.
Definition: am7xxx.h:103
Error messages, typically they describe API functions failures.
Definition: am7xxx.h:70
int am7xxx_set_power_mode(am7xxx_device *dev, am7xxx_power_mode power)
Set the power mode of an am7xxx device.
Definition: am7xxx.c:1402
void am7xxx_set_log_level(am7xxx_context *ctx, am7xxx_log_level log_level)
Set verbosity level of log messages.
Definition: am7xxx.c:1163
int am7xxx_calc_scaled_image_dimensions(am7xxx_device *dev, unsigned int upscale, unsigned int original_width, unsigned int original_height, unsigned int *scaled_width, unsigned int *scaled_height)
Calculate the dimensions of an image to be shown on an am7xxx device.
Definition: am7xxx.c:1269
int am7xxx_send_image_async(am7xxx_device *dev, am7xxx_image_format format, unsigned int width, unsigned int height, unsigned char *image, unsigned int image_size)
Queue transfer of an image for display on an am7xxx device and return immediately.
Informations about the device operations.
Definition: am7xxx.h:72
int am7xxx_set_zoom_mode(am7xxx_device *dev, am7xxx_zoom_mode zoom)
Set the zoom mode of an am7xxx device.
Definition: am7xxx.c:1413
Raw YUV in the NV12 variant.
Definition: am7xxx.h:82
Verbose informations about the communication with the hardware.
Definition: am7xxx.h:74
Low power consumption but also low brightness.
Definition: am7xxx.h:101
void am7xxx_shutdown(am7xxx_context *ctx)
Cleanup the library data structures and free the context.
Definition: am7xxx.c:1140
struct _am7xxx_device am7xxx_device
An opaque data type representing an am7xxx device.
Definition: am7xxx.h:46
Display is powered off, no image shown.
Definition: am7xxx.h:100