Add gst-recorder implementation for weston2.0.0 67/10967/1
authorHarunobu Kurokawa <harunobu.kurokawa.dn@renesas.com>
Fri, 15 Sep 2017 13:33:47 +0000 (22:33 +0900)
committerHarunobu Kurokawa <harunobu.kurokawa.dn@renesas.com>
Tue, 19 Sep 2017 01:16:12 +0000 (10:16 +0900)
This commit support virtual display and gst-recorder
function for weston2.0.0.

It is update patch for weston 2.0.0 and based on
562c0c1bb2ef74ccbfda1bae4f84a61828119674

Virtual display is additional display like as offscreen
buffer and  it is created from drm-backend.
The gst-recorder is a transfer system using H.264
streaming and Ethernet.

Change-Id: I1e1b07701229d47ab6616e85d78c6aa039a0775b
Signed-off-by: Harunobu Kurokawa <harunobu.kurokawa.dn@renesas.com>
recipes-graphics/wayland/weston/0001-Add-virtual-output-support.patch [new file with mode: 0644]
recipes-graphics/wayland/weston/0002-Add-gst-recorder-for-h264-output-streaming.patch [new file with mode: 0644]
recipes-graphics/wayland/weston/0003-gst-recorder-Use-USERPTR-instead-of-DMABUF-for-VSP-o.patch [new file with mode: 0644]
recipes-graphics/wayland/weston/0004-gst-record-Specify-bytesused-and-length-of-VSP-input.patch [new file with mode: 0644]
recipes-graphics/wayland/weston_%.bbappend [new file with mode: 0644]
recipes-graphics/wayland/weston_2.0.0.bbappend [new file with mode: 0644]

diff --git a/recipes-graphics/wayland/weston/0001-Add-virtual-output-support.patch b/recipes-graphics/wayland/weston/0001-Add-virtual-output-support.patch
new file mode 100644 (file)
index 0000000..211fb73
--- /dev/null
@@ -0,0 +1,470 @@
+From b6d084f68434fea7402d4989d4d8344523b1a939 Mon Sep 17 00:00:00 2001
+From: Harunobu Kurokawa <harunobu.kurokawa.dn@renesas.com>
+Date: Thu, 10 Aug 2017 15:42:38 +0900
+Subject: [PATCH 1/4] Add virtual output support
+
+This patch is ported to weston 2.0.0.
+
+----------
+Author: Damian Hobson-Garcia <dhobsong@igel.co.jp>
+Date:   Thu Apr 27 16:47:00 2017 +0900
+
+Following patch ported to Weston 1.11 with minor updates
+  ----------
+  Author: Grigory Kletsko <grigory.kletsko@cogentembedded.com>
+  Date:   Wed Nov 2 17:14:43 2016 +0300
+
+  To enable virtual output set "virtual" property in core section
+  to desirable number of virtual outputs. Then add settings to
+  each virtual output in output sections. Name of the outputs
+  will be virtual1, virtual2... etc.
+  ------------
+---
+ libweston/compositor-drm.c | 352 +++++++++++++++++++++++++++++++++++++++++++++
+ libweston/compositor-drm.h |   1 +
+ 2 files changed, 353 insertions(+)
+
+diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c
+index 1d38f05..d0f07e9 100644
+--- a/libweston/compositor-drm.c
++++ b/libweston/compositor-drm.c
+@@ -182,6 +182,11 @@ struct drm_output {
+       struct vaapi_recorder *recorder;
+       struct wl_listener recorder_frame_listener;
++
++      /* not real output device */
++      int virtual;
++      /* Timer for updating frame */
++      struct wl_event_source *virtual_finish_frame_timer;
+ };
+ /*
+@@ -211,6 +216,9 @@ struct drm_sprite {
+ static struct gl_renderer_interface *gl_renderer;
++static int
++recorder_enable(struct drm_backend *b, struct drm_output *output);
++
+ static const char default_seat[] = "seat0";
+ static inline struct drm_output *
+@@ -2347,6 +2355,99 @@ connector_get_current_mode(drmModeConnector *connector, int drm_fd,
+ }
+ static int
++virtual_output_set_mode(struct weston_output *base,
++                  enum weston_drm_backend_output_mode mode,
++                  const char *modeline)
++{
++      struct drm_output *output = to_drm_output(base);
++      struct drm_backend *b = to_drm_backend(base->compositor);
++      struct weston_config *config = wet_get_config(b->compositor);
++
++      struct drm_mode *drm_mode, *next, *current;
++      char *s;
++      int valid_mode;
++      int recorded_output;
++      int width, height, scale, fps;
++      struct weston_config_section *section;
++      uint32_t transform;
++      drmModeModeInfo crtc_mode;
++
++      output->base.make = "CogentEmbedded,Inc";
++
++      section = weston_config_get_section(config, "output", "name",
++                                          output->base.name);
++
++      weston_config_section_get_bool(section, "recorder", &recorded_output, 0);
++
++      if (recorded_output) {
++              output->base.model = "Virtual RTP Display";
++      } else {
++              output->base.model = "Virtual Display";
++      }
++
++      output->base.serial_number = "";
++      wl_list_init(&output->base.mode_list);
++
++      if (mode == WESTON_DRM_BACKEND_OUTPUT_PREFERRED) {
++              if (modeline && sscanf(modeline, "%dx%d@%d", &width, &height, &fps) >= 3)
++                      valid_mode = 1;
++      }
++
++      weston_config_section_get_int(section, "scale", &scale, 1);
++      weston_config_section_get_string(section, "transform", &s, "normal");
++      if (weston_parse_transform(s, &transform) < 0)
++              weston_log("Invalid transform \"%s\" for output %s\n",
++                         s, output->base.name);
++      free(s);
++
++      weston_config_section_get_string(section, "seat", &s, "");
++      free(s);
++
++      output->original_crtc = NULL;
++      output->dpms_prop = NULL;
++
++      /* set static mode */
++      if (valid_mode) {
++              /* TODO: calculate proper mode settings to get desirable framerate */
++              drmModeModeInfo static_drm_mode = {
++                      width * height * fps,
++                      width, 0, 0, width, width,
++                      height, 0, 0, height, height,
++                      fps * 1000,
++                      0, //flags
++                      0, //type
++                      "virtual"
++              };
++              drm_mode = drm_output_add_mode(output, &static_drm_mode);
++              if (!drm_mode)
++                      goto err_free;
++
++              drm_mode->base.refresh = fps * 1000;
++      }
++
++      current = drm_output_choose_initial_mode(b, output, mode, &modeline,
++                                               &crtc_mode);
++      if (!current)
++              goto err_free;
++      output->base.current_mode = &current->base;
++      output->base.current_mode->flags |= WL_OUTPUT_MODE_CURRENT;
++
++      return 0;
++
++err_free:
++      drmModeFreeCrtc(output->original_crtc);
++      output->original_crtc = NULL;
++
++      wl_list_for_each_safe(drm_mode, next, &output->base.mode_list,
++                                                      base.link) {
++              wl_list_remove(&drm_mode->base.link);
++              free(drm_mode);
++      }
++
++      return -1;
++}
++
++static int
+ drm_output_set_mode(struct weston_output *base,
+                   enum weston_drm_backend_output_mode mode,
+                   const char *modeline)
+@@ -2357,6 +2458,8 @@ drm_output_set_mode(struct weston_output *base,
+       struct drm_mode *drm_mode, *next, *current;
+       drmModeModeInfo crtc_mode;
+       int i;
++      if ( output->virtual == 1 )
++              return virtual_output_set_mode(base, mode, modeline);
+       output->base.make = "unknown";
+       output->base.model = "unknown";
+@@ -2453,6 +2556,10 @@ drm_output_enable(struct weston_output *base)
+               weston_log("Failed to initialize backlight\n");
+       }
++      /* enable GST recording-streaming */
++      if (b->enable_recorder)
++              recorder_enable(b, output);
++
+       output->base.start_repaint_loop = drm_output_start_repaint_loop;
+       output->base.repaint = drm_output_repaint;
+       output->base.assign_planes = drm_assign_planes;
+@@ -2630,6 +2737,227 @@ create_output_for_connector(struct drm_backend *b,
+ }
+ static void
++virtual_output_deinit(struct weston_output *base)
++{
++      struct drm_output *output = to_drm_output(base);
++      struct drm_backend *b = to_drm_backend(base->compositor);
++
++      if (b->use_pixman)
++              drm_output_fini_pixman(output);
++      else
++              drm_output_fini_egl(output);
++
++      weston_plane_release(&output->fb_plane);
++      weston_plane_release(&output->cursor_plane);
++}
++
++static void
++virtual_output_destroy(struct weston_output *base)
++{
++      struct drm_output *output = to_drm_output(base);
++
++      if (output->base.enabled)
++              virtual_output_deinit(&output->base);
++
++      weston_output_destroy(&output->base);
++
++      free(output);
++}
++
++static void
++virtual_output_start_repaint_loop(struct weston_output *output)
++{
++      struct timespec now;
++
++      weston_compositor_read_presentation_clock(output->compositor, &now);
++      weston_output_finish_frame(output, &now, WP_PRESENTATION_FEEDBACK_INVALID);
++}
++
++
++static int
++virtual_output_repaint(struct weston_output *output_base,
++                 pixman_region32_t *damage)
++{
++      struct drm_output *output = (struct drm_output *) output_base;
++      struct timespec ts;
++      uint32_t msec_next;
++      uint32_t msec_current;
++
++      msec_next = (output->base.frame_time + 1000000UL / output->base.current_mode->refresh) ;
++
++      if (output->disable_pending || output->destroy_pending)
++              return -1;
++
++      if (!output->next)
++              drm_output_render(output, damage);
++      if (!output->next)
++              return -1;
++
++      drm_output_set_cursor(output);
++
++      output->page_flip_pending = 1;
++
++      weston_compositor_read_presentation_clock(output_base->compositor, &ts);
++
++      msec_current = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
++
++      /*
++       * If we somehow late with updating frame, then fireup timer immediately (1 msec)
++       */
++      wl_event_source_timer_update(output->virtual_finish_frame_timer, (msec_next > msec_current)  ?
++                                   msec_next - msec_current : 1);
++
++      return 0;
++}
++
++static int
++virtual_finish_frame_handler(void *data)
++{
++      struct drm_output *output = (struct drm_output *) data;
++      struct timespec ts;
++
++      /* We don't set page_flip_pending on start_repaint_loop, in that case
++       * we just want to page flip to the current buffer to get an accurate
++       * timestamp */
++      if (output->page_flip_pending) {
++              drm_output_release_fb(output, output->current);
++              output->current = output->next;
++              output->next = NULL;
++      }
++
++      output->page_flip_pending = 0;
++
++      if (output->destroy_pending)
++              drm_output_destroy(&output->base);
++      else if (!output->vblank_pending) {
++              weston_compositor_read_presentation_clock(output->base.compositor, &ts);
++
++              weston_output_finish_frame(&output->base, &ts,
++                      WP_PRESENTATION_FEEDBACK_INVALID);
++
++              /* We can't call this from frame_notify, because the output's
++               * repaint needed flag is cleared just after that */
++              if (output->recorder)
++                      weston_output_schedule_repaint(&output->base);
++      }
++
++      return 1;
++}
++
++static int
++virtual_output_enable(struct weston_output *base)
++{
++      struct drm_output *output = to_drm_output(base);
++      struct drm_backend *b = to_drm_backend(base->compositor);
++      struct weston_mode *m;
++
++      if (b->use_pixman) {
++              if (drm_output_init_pixman(output, b) < 0) {
++                      weston_log("Failed to init output pixman state\n");
++                      goto err_free;
++              }
++      } else if (drm_output_init_egl(output, b) < 0) {
++              weston_log("Failed to init output gl state\n");
++              goto err_free;
++      }
++
++      output->base.start_repaint_loop = virtual_output_start_repaint_loop;
++      output->base.repaint = virtual_output_repaint;
++      output->base.assign_planes = NULL;
++      output->base.set_dpms = NULL;
++      output->base.switch_mode = drm_output_switch_mode;
++
++      output->base.gamma_size = 0;
++      output->base.set_gamma = drm_output_set_gamma;
++
++      output->base.subpixel = WL_OUTPUT_SUBPIXEL_NONE; //drm_subpixel_to_wayland(connector->subpixel);
++
++      weston_plane_init(&output->cursor_plane, b->compositor,
++                        INT32_MIN, INT32_MIN);
++      weston_plane_init(&output->fb_plane, b->compositor, 0, 0);
++
++      weston_compositor_stack_plane(b->compositor, &output->cursor_plane, NULL);
++      weston_compositor_stack_plane(b->compositor, &output->fb_plane,
++                                    &b->compositor->primary_plane);
++
++      weston_log("Output %s, ()\n",
++                 output->base.name);
++      wl_list_for_each(m, &output->base.mode_list, link)
++              weston_log_continue(STAMP_SPACE "mode %dx%d@%.1f\n",
++                                  m->width, m->height, m->refresh / 1000.0);
++
++      /* enable GST recording-streaming */
++      if (b->enable_recorder)
++              recorder_enable(b, output);
++
++      return 0;
++
++err_free:
++
++      return -1;
++}
++
++
++static int
++virtual_output_disable(struct weston_output *base)
++{
++      struct drm_output *output = to_drm_output(base);
++
++      if (output->base.enabled)
++              virtual_output_deinit(&output->base);
++
++      output->disable_pending = 0;
++
++      weston_log("Disabling output %s\n", output->base.name);
++
++      return 0;
++}
++
++/*
++ * Virtual output connector that could be used for simulating output
++ * device for clients and/or streaming of video
++ */
++static int
++create_output_for_virtual_connector(struct drm_backend *b,
++                                  struct udev_device *drm_device)
++{
++      struct wl_event_loop *loop;
++      struct drm_output *output;
++      static int virtual_id = 1; /* as other outputs numbered */
++      char name[32], *s;
++
++      output = zalloc(sizeof *output);
++      if (output == NULL)
++              return -1;
++
++      output->pipe = 0;
++      output->connector_id = 0;
++
++      /* this is virtual output */
++      output->virtual = 1;
++
++      output->backlight = NULL;
++
++      loop = wl_display_get_event_loop(b->compositor->wl_display);
++      output->virtual_finish_frame_timer = wl_event_loop_add_timer(loop, virtual_finish_frame_handler, output);
++
++      output->base.enable = virtual_output_enable;
++      output->base.destroy = virtual_output_destroy;
++      output->base.disable = virtual_output_disable;
++
++      output->destroy_pending = 0;
++      output->disable_pending = 0;
++      output->original_crtc = NULL;
++      snprintf(name, 32, "virtual%d", virtual_id++);
++      output->base.name = strdup(name);
++
++      weston_output_init(&output->base, b->compositor);
++      weston_compositor_add_pending_output(&output->base, b->compositor);
++
++      return 0;
++}
++
++static void
+ create_sprites(struct drm_backend *b)
+ {
+       struct drm_sprite *sprite;
+@@ -2701,9 +3029,12 @@ destroy_sprites(struct drm_backend *backend)
+ static int
+ create_outputs(struct drm_backend *b, struct udev_device *drm_device)
+ {
++      struct weston_config_section *section;
++      struct weston_config *config = wet_get_config(b->compositor);
+       drmModeConnector *connector;
+       drmModeRes *resources;
+       int i;
++      int virtual;
+       resources = drmModeGetResources(b->drm.fd);
+       if (!resources) {
+@@ -2735,6 +3066,14 @@ create_outputs(struct drm_backend *b, struct udev_device *drm_device)
+               }
+       }
++      section = weston_config_get_section(config, "core", NULL, NULL);
++      weston_config_section_get_int(section, "virtual", &virtual, 0);
++
++      for (i = 0; i < virtual; i++) {
++              if (create_output_for_virtual_connector(b, drm_device) < 0)
++                      continue;
++      }
++
+       if (wl_list_empty(&b->compositor->output_list) &&
+           wl_list_empty(&b->compositor->pending_output_list))
+               weston_log("No currently active connector found.\n");
+@@ -3181,6 +3520,12 @@ renderer_switch_binding(struct weston_keyboard *keyboard, uint32_t time,
+       switch_to_gl_renderer(b);
+ }
++static const struct weston_drm_output_api virtual_api = {
++      virtual_output_set_mode,
++      drm_output_set_gbm_format,
++      drm_output_set_seat,
++};
++
+ static const struct weston_drm_output_api api = {
+       drm_output_set_mode,
+       drm_output_set_gbm_format,
+@@ -3346,6 +3691,13 @@ drm_backend_create(struct weston_compositor *compositor,
+               goto err_udev_monitor;
+       }
++      ret = weston_plugin_api_register(compositor, WESTON_DRM_VIRTUAL_OUTPUT_API_NAME,
++                                       &virtual_api, sizeof(virtual_api));
++
++      if (ret < 0) {
++              weston_log("Failed to register output API.\n");
++              goto err_udev_monitor;
++      }
+       return b;
+ err_udev_monitor:
+diff --git a/libweston/compositor-drm.h b/libweston/compositor-drm.h
+index 2e2995a..00171c8 100644
+--- a/libweston/compositor-drm.h
++++ b/libweston/compositor-drm.h
+@@ -53,6 +53,7 @@ enum weston_drm_backend_output_mode {
+ };
+ #define WESTON_DRM_OUTPUT_API_NAME "weston_drm_output_api_v1"
++#define WESTON_DRM_VIRTUAL_OUTPUT_API_NAME "weston_virtual_output_api_v1"
+ struct weston_drm_output_api {
+       /** The mode to be used by the output. Refer to the documentation
+-- 
+2.9.2
+
diff --git a/recipes-graphics/wayland/weston/0002-Add-gst-recorder-for-h264-output-streaming.patch b/recipes-graphics/wayland/weston/0002-Add-gst-recorder-for-h264-output-streaming.patch
new file mode 100644 (file)
index 0000000..71a45ff
--- /dev/null
@@ -0,0 +1,4214 @@
+From c953d91f8d7e4bca5dd68246c763fd4c4e354287 Mon Sep 17 00:00:00 2001
+From: Harunobu Kurokawa <harunobu.kurokawa.dn@renesas.com>
+Date: Thu, 10 Aug 2017 15:49:14 +0900
+Subject: [PATCH 2/4] Add gst-recorder for h264 output streaming
+
+This patch is ported to weston 2.0.0.
+--------
+Author: Damian Hobson-Garcia <dhobsong@igel.co.jp>
+Date:   Thu Apr 27 16:47:00 2017 +0900
+
+  Following patch ported to Weston 1.11 with minor updates
+  --------
+  To use gst-recorder run weston with arg --gst-record. Add
+  recorder=true property to output section of one of the outputs
+  (real or virtual). Use properties ip, port to set host of RTP
+  stream. Use property bitrate to set desirable maximum h264 bitrate.
+  --------
+---
+ Makefile.am                         |   17 +
+ compositor/main.c                   |    4 +-
+ configure.ac                        |   13 +
+ libweston/compositor-drm.c          |  203 +++++-
+ libweston/compositor-drm.h          |    3 +
+ libweston/gst-recorder.c            | 1213 +++++++++++++++++++++++++++++++++++
+ libweston/gst-recorder.h            |   58 ++
+ libweston/media-ctl/libmediactl.c   |  955 +++++++++++++++++++++++++++
+ libweston/media-ctl/libv4l2subdev.c |  759 ++++++++++++++++++++++
+ libweston/media-ctl/mediactl-priv.h |   64 ++
+ libweston/media-ctl/mediactl.h      |  423 ++++++++++++
+ libweston/media-ctl/tools.h         |   32 +
+ libweston/media-ctl/v4l2subdev.h    |  258 ++++++++
+ 13 files changed, 4000 insertions(+), 2 deletions(-)
+ create mode 100644 libweston/gst-recorder.c
+ create mode 100644 libweston/gst-recorder.h
+ create mode 100644 libweston/media-ctl/libmediactl.c
+ create mode 100644 libweston/media-ctl/libv4l2subdev.c
+ create mode 100644 libweston/media-ctl/mediactl-priv.h
+ create mode 100644 libweston/media-ctl/mediactl.h
+ create mode 100644 libweston/media-ctl/tools.h
+ create mode 100644 libweston/media-ctl/v4l2subdev.h
+
+diff --git a/Makefile.am b/Makefile.am
+index cdf82ab..9fb9ba0 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -386,6 +386,23 @@ drm_backend_la_LIBADD += $(LIBVA_LIBS)
+ drm_backend_la_LDFLAGS += -pthread
+ drm_backend_la_CFLAGS += $(LIBVA_CFLAGS)
+ endif
++
++if ENABLE_GST_RECORDER
++drm_backend_la_SOURCES +=                     \
++      libweston/gst-recorder.c                        \
++      libweston/gst-recorder.h                        \
++      libweston/v4l2-device.h                 \
++      libweston/media-ctl/libmediactl.c               \
++      libweston/media-ctl/libv4l2subdev.c             \
++      libweston/media-ctl/mediactl-priv.h             \
++      libweston/media-ctl/mediactl.h          \
++      libweston/media-ctl/tools.h                     \
++      libweston/media-ctl/v4l2subdev.h
++drm_backend_la_LIBADD += $(LIBVA_LIBS)                \
++      -lgstallocators-1.0                     \
++      -lgstvideo-1.0
++drm_backend_la_CFLAGS += $(LIBVA_CFLAGS)
++endif
+ endif
+ if ENABLE_WAYLAND_COMPOSITOR
+diff --git a/compositor/main.c b/compositor/main.c
+index 72c3cd1..6a8ca7f 100644
+--- a/compositor/main.c
++++ b/compositor/main.c
+@@ -566,7 +566,8 @@ usage(int error_code)
+               "  --seat=SEAT\t\tThe seat that weston should run on\n"
+               "  --tty=TTY\t\tThe tty to use\n"
+               "  --use-pixman\t\tUse the pixman (CPU) renderer\n"
+-              "  --current-mode\tPrefer current KMS mode over EDID preferred mode\n\n");
++              "  --current-mode\tPrefer current KMS mode over EDID preferred mode\n"
++              "  --gst-record\t\tEnable GStreamer recording\n\n");
+ #endif
+ #if defined(BUILD_FBDEV_COMPOSITOR)
+@@ -1227,6 +1228,7 @@ load_drm_backend(struct weston_compositor *c,
+               { WESTON_OPTION_INTEGER, "tty", 0, &config.tty },
+               { WESTON_OPTION_BOOLEAN, "current-mode", 0, &wet->drm_use_current_mode },
+               { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman },
++              { WESTON_OPTION_BOOLEAN, "gst-record", 0, &config.enable_recorder },
+       };
+       parse_options(options, ARRAY_LENGTH(options), argc, argv);
+diff --git a/configure.ac b/configure.ac
+index a36a516..3cc7501 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -242,6 +242,18 @@ if test x$enable_headless_compositor = xyes; then
+ fi
++AC_ARG_ENABLE(gst-recorder, [  --enable-gst-recorder],,
++            enable_gst_recorder=yes)
++if test x$enable_gst_recorder != xno; then
++  AC_DEFINE([BUILD_GST_RECORDER], [1], [Build the gst recorder])
++  PKG_CHECK_MODULES(GSTREAMER, [gstreamer-1.0 >= 1.0])
++
++  CPPFLAGS="$CPPFLAGS $GSTREAMER_CFLAGS"
++  LIBS="$LIBS $GSTREAMER_LIBS -lgstapp-1.0"
++fi
++AM_CONDITIONAL(ENABLE_GST_RECORDER, test "x$enable_gst_recorder" != xno)
++
++
+ AC_ARG_ENABLE([fbdev-compositor], [  --enable-fbdev-compositor],,
+               enable_fbdev_compositor=yes)
+ AM_CONDITIONAL([ENABLE_FBDEV_COMPOSITOR],
+@@ -724,4 +736,5 @@ AC_MSG_RESULT([
+       libwebp Support                 ${have_webp}
+       libunwind Support               ${have_libunwind}
+       VA H.264 encoding Support       ${have_libva}
++      GStreamer H.264 enc. Support    ${enable_gst_recorder}
+ ])
+diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c
+index d0f07e9..0b9643f 100644
+--- a/libweston/compositor-drm.c
++++ b/libweston/compositor-drm.c
+@@ -58,9 +58,11 @@
+ #include "libinput-seat.h"
+ #include "launcher-util.h"
+ #include "vaapi-recorder.h"
++#include "gst-recorder.h"
+ #include "presentation-time-server-protocol.h"
+ #include "linux-dmabuf.h"
+ #include "linux-dmabuf-unstable-v1-server-protocol.h"
++#include "compositor/weston.h"
+ #ifndef DRM_CAP_TIMESTAMP_MONOTONIC
+ #define DRM_CAP_TIMESTAMP_MONOTONIC 0x6
+@@ -113,6 +115,8 @@ struct drm_backend {
+       int use_pixman;
++      int enable_recorder;
++
+       struct udev_input input;
+       int32_t cursor_width;
+@@ -180,7 +184,12 @@ struct drm_output {
+       int current_image;
+       pixman_region32_t previous_damage;
++#ifdef BUILD_VAAPI_RECORDER
+       struct vaapi_recorder *recorder;
++#endif
++#ifdef BUILD_GST_RECORDER
++      struct gst_recorder *recorder;
++#endif
+       struct wl_listener recorder_frame_listener;
+       /* not real output device */
+@@ -239,6 +248,9 @@ drm_output_set_cursor(struct drm_output *output);
+ static void
+ drm_output_update_msc(struct drm_output *output, unsigned int seq);
++static void
++recorders_enable(struct drm_backend *b);
++
+ static int
+ drm_sprite_crtc_supported(struct drm_output *output, struct drm_sprite *sprite)
+ {
+@@ -2197,6 +2209,23 @@ parse_modeline(const char *s, drmModeModeInfo *mode)
+       return 0;
+ }
++static int parse_crop_rect(const char *s, struct v4l2_rect* crop)
++{
++      crop->left = 0;
++      crop->top = 0;
++      crop->width = 0;
++      crop->height = 0;
++
++      if (sscanf(s, "%dx%d@%dx%d",
++                 &crop->width,
++                 &crop->height,
++                 &crop->top,
++                 &crop->left) != 4)
++              return -1;
++
++      return 0;
++}
++
+ static void
+ setup_output_seat_constraint(struct drm_backend *b,
+                            struct weston_output *output,
+@@ -3457,7 +3486,173 @@ recorder_binding(struct weston_keyboard *keyboard, uint32_t time, uint32_t key,
+               recorder_destroy(output);
+       }
+ }
+-#else
++#endif
++
++#ifdef BUILD_GST_RECORDER
++static void
++recorder_destroy(struct drm_output *output)
++{
++      wl_list_remove(&output->recorder_frame_listener.link);
++
++      gst_recorder_destroy(output->recorder);
++      output->recorder = NULL;
++
++      output->base.disable_planes--;
++
++      weston_log("[gst recorder] done\n");
++}
++
++static void
++recorder_frame_notify(struct wl_listener *listener, void *data)
++{
++      int ret = 0;
++      struct drm_output *output;
++      struct drm_backend *b;
++      int fd;
++
++      output = container_of(listener, struct drm_output,
++                            recorder_frame_listener);
++      b = to_drm_backend(output->base.compositor);
++
++      if (!output->recorder) {
++              weston_log("%s: output have no recorder enabled\n",
++                      output->base.name);
++              return;
++      }
++
++      if (!output->current) {
++              weston_log("%s: frame notify while current frame == NULL\n",
++                         output->base.name);
++              return;
++      }
++
++      ret = drmPrimeHandleToFD(b->drm.fd, output->current->handle,
++                               DRM_CLOEXEC, &fd);
++      if (!ret) {
++              ret = gst_recorder_frame_dmafd(output->recorder, fd,
++                                             output->current->stride);
++      }
++
++      if (ret < 0) {
++              weston_log("[gst recorder] aborted: %m\n");
++              recorder_destroy(output);
++      }
++}
++
++static int
++recorder_enable(struct drm_backend *b, struct drm_output *output)
++{
++      int enable_recorder = 0;
++      struct gst_recorder_settings *settings;
++      struct weston_config_section *section;
++      struct weston_config *config = wet_get_config(b->compositor);
++      char* s;
++      struct v4l2_rect crop = { .width = output->base.current_mode->width,
++                                .height = output->base.current_mode->height,
++                                .top = 0,
++                                .left = 0 };
++
++      if (output->recorder)
++              return -1;
++
++      section = weston_config_get_section(config, "output", "name",
++                                          output->base.name);
++
++      weston_config_section_get_bool(section, "recorder", &enable_recorder, 0);
++
++      if (!enable_recorder)
++              return 0;
++
++      /* TODO: add support for NV16 or NV12 */
++      if (output->gbm_format != GBM_FORMAT_XRGB8888) {
++              weston_log("[gst recorder] %s: "
++                         "output format not supported\n", output->base.name);
++              return -1;
++      }
++
++      settings = malloc(sizeof(* settings));
++      weston_config_section_get_string(section, "ip", &settings->ip, NULL);
++      if (!settings->ip)
++              goto err;
++      weston_config_section_get_int(section, "port", &settings->port, -1);
++      /* default gives about 16 Mbit/s at 1280x720@60FPS */
++      weston_config_section_get_int(section, "bitrate", &settings->bitrate, 300000);
++
++      settings->width = output->base.current_mode->width;
++      settings->height = output->base.current_mode->height;
++
++      settings->crop = crop;
++
++      weston_config_section_get_string(section, "crop", &s, NULL);
++      if (s) {
++              if (parse_crop_rect(s, &settings->crop)) {
++                      weston_log("[gst recorder] %s:"
++                                 " failed to parse crop parameter\n",
++                                 output->base.name);
++                      goto err;
++              }
++      }
++
++
++      output->recorder =
++              gst_recorder_create(settings);
++      if (!output->recorder) {
++              weston_log("[gst recorder] %s:"
++                      " failed to create gst recorder\n",
++                      output->base.name);
++              goto err;
++      }
++
++      output->base.disable_planes++;
++
++      output->recorder_frame_listener.notify = recorder_frame_notify;
++      wl_signal_add(&output->base.frame_signal,
++                    &output->recorder_frame_listener);
++
++      weston_output_schedule_repaint(&output->base);
++
++      weston_log("[gst recorder] %s:"
++              " recorder initialized\n",
++              output->base.name);
++
++      return 0;
++err:
++      weston_log("[gst recorder] %s:"
++              " invalid settings\n",
++              output->base.name);
++      free(settings);
++      return -1;
++}
++
++static void
++recorders_enable(struct drm_backend *b)
++{
++      struct drm_output *output;
++
++      wl_list_for_each(output, &b->compositor->output_list, base.link) {
++              recorder_enable(b, output);
++      }
++}
++
++static void
++recorder_binding(struct weston_keyboard *keyboard, uint32_t time, uint32_t key,
++               void *data)
++{
++      struct drm_backend *b = data;
++      struct drm_output *output;
++
++      /* fix this */
++      wl_list_for_each(output, &b->compositor->output_list, base.link) {
++              if (!output->recorder)
++                      recorder_enable(b, output);
++              else
++                      recorder_destroy(output);
++      }
++}
++#endif
++
++#if !defined(BUILD_VAAPI_RECORDER) && !defined(BUILD_GST_RECORDER)
++
+ static void
+ recorder_binding(struct weston_keyboard *keyboard, uint32_t time, uint32_t key,
+                void *data)
+@@ -3562,6 +3757,7 @@ drm_backend_create(struct weston_compositor *compositor,
+       b->sprites_are_broken = 1;
+       b->compositor = compositor;
+       b->use_pixman = config->use_pixman;
++      b->enable_recorder = config->enable_recorder;
+       if (parse_gbm_format(config->gbm_format, GBM_FORMAT_XRGB8888, &b->gbm_format) < 0)
+               goto err_compositor;
+@@ -3611,6 +3807,11 @@ drm_backend_create(struct weston_compositor *compositor,
+               }
+       }
++#ifdef BUILD_GST_RECORDER
++      if (b->enable_recorder)
++              gst_recorder_init();
++#endif
++
+       b->base.destroy = drm_destroy;
+       b->base.restore = drm_restore;
+diff --git a/libweston/compositor-drm.h b/libweston/compositor-drm.h
+index 00171c8..4eb6097 100644
+--- a/libweston/compositor-drm.h
++++ b/libweston/compositor-drm.h
+@@ -111,6 +111,9 @@ struct weston_drm_backend_config {
+       /** Whether to use the pixman renderer instead of the OpenGL ES renderer. */
+       bool use_pixman;
++      /** Whether to use the v4l2 renderer insted of the OpenGL ES renderer. */
++      bool enable_recorder;
++
+       /** The seat to be used for input and output.
+        *
+        * If NULL the default "seat0" will be used.  The backend will
+diff --git a/libweston/gst-recorder.c b/libweston/gst-recorder.c
+new file mode 100644
+index 0000000..d46d4f0
+--- /dev/null
++++ b/libweston/gst-recorder.c
+@@ -0,0 +1,1213 @@
++/*
++ * Copyright Â© 2016 Cogent Embedded Inc
++ *
++ * Permission to use, copy, modify, distribute, and sell this software and
++ * its documentation for any purpose is hereby granted without fee, provided
++ * that the above copyright notice appear in all copies and that both that
++ * copyright notice and this permission notice appear in supporting
++ * documentation, and that the name of the copyright holders not be used in
++ * advertising or publicity pertaining to distribution of the software
++ * without specific, written prior permission.  The copyright holders make
++ * no representations about the suitability of this software for any
++ * purpose.  It is provided "as is" without express or implied warranty.
++ *
++ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
++ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
++ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
++ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
++ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
++ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
++ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
++ */
++
++/*
++ * TODO:
++ * 1) Add format parameter to virtual display to render in another format
++ * with v4l2-renderer
++ * 2) Add capability to use already NV12 rendered frame
++ */
++#include "config.h"
++
++#include <stdlib.h>
++#include <stdint.h>
++#include <string.h>
++#include <unistd.h>
++#include <assert.h>
++#include <errno.h>
++
++#include <sys/types.h>
++#include <sys/stat.h>
++#include <sys/mman.h>
++#include <sys/ioctl.h>
++#include <fcntl.h>
++
++#include <pthread.h>
++
++#include "compositor.h"
++#include "gst-recorder.h"
++
++/* Gstreamer includes */
++#include <gst/gst.h>
++#include <gst/app/gstappsrc.h>
++#include <gst/rtp/gstrtpbuffer.h>
++
++#include <gst/video/video.h>
++#include <gst/video/gstvideometa.h>
++#include <gst/video/gstvideopool.h>
++#include <gst/allocators/gstdmabuf.h>
++
++struct vsp_data;
++
++#define DEFAULT_FPS           60
++
++typedef enum _vsp_port_n {
++      VSP_PORT_INPUT  = 0,
++      VSP_PORT_INPUT0 = VSP_PORT_INPUT,
++      VSP_PORT_INPUT1,
++      VSP_PORT_INPUT2,
++      VSP_PORT_INPUT3,
++      VSP_PORT_OUTPUT
++} vsp_port_n;
++
++struct gst_recorder {
++      struct gst_recorder_settings *set;
++      int frame_count;
++      int input_count;
++
++      int error;
++      int destroying;
++      pthread_t worker_thread;
++      pthread_mutex_t mutex;
++      pthread_cond_t input_cond;
++
++      struct {
++              int valid;
++              int prime_fd, stride;
++      } input;
++
++      /* GLib */
++      GMainContext *gcontext;
++      /* Gstreamer stuff */
++      GstElement *pipeline;
++      /* AppSrc */
++      GstAppSrc *appsrc;
++      /* ...and source pad */
++      GstPad *appsrc_pad;
++      /* OMX encoder buffer pool */
++      GstBufferPool *omx_pool;
++      /* bus */
++      GstBus *bus;
++      /* timestamp */
++      GstClock   *clock;
++      GstClockTime timestamp;
++      GstClockTime ts_last_frame;
++      /* to be removed */
++      guint callback_tag;
++
++      struct vsp_data *vsp;
++};
++
++/*******************************************************************************
++ * VSP related code
++ ******************************************************************************/
++
++#define VSP_OUTPUT_BUFFERS_PLANE      2
++
++/* #define VSP_OUTPUT_NV16                    1 */
++
++/* ...number of input/output pads (WPF1-3 are not implemented) */
++#define VSP_PADS_NUM                    1
++
++/*******************************************************************************
++ * Local types definition
++ ******************************************************************************/
++
++struct vsp_media_pad
++{
++      struct media_pad           *infmt_pad;
++      struct media_pad           *outfmt_pad;
++      struct media_entity        *entity;
++      int                     fd;
++};
++
++typedef struct vsp_media_pad vsp_media_pad_t;
++
++struct vsp_data
++{
++      /* ...media device */
++      struct media_device *media;
++
++      /* ...VSP input/output pads */
++      vsp_media_pad_t input, output;
++
++      /* mutex */
++      pthread_mutex_t mutex;
++
++      /* user count */
++      int users;
++};
++
++struct vsp_data *vsp_g = NULL;
++
++/* ...type declarations */
++typedef struct vsp_data vsp_data_t;
++
++static int
++gst_recorder_process_dmafd(struct gst_recorder *r, int fd, int stride);
++
++/* ...module initialization (a bit of salami) */
++static vsp_data_t *
++vsp_init(const char *devname)
++{
++      vsp_data_t                     *vsp;
++      struct media_device            *media;
++      const struct media_device_info *info;
++      const char                     *dev;
++      char                            buf[256];
++      char                           *endp, *p;
++      struct media_entity            *entity;
++
++      /* ...create data structure */
++      if ((vsp = malloc(sizeof(*vsp))) == NULL)
++      {
++              weston_log("failed to allocate memory\n");
++              errno = ENOMEM;
++              return NULL;
++      }
++      memset(vsp, 0, sizeof(*vsp));
++
++      pthread_mutex_init(&vsp->mutex, NULL);
++
++      /* ...create media device */
++      if ((vsp->media = media = media_device_new(devname)) == NULL)
++      {
++              weston_log("failed to open device '%s'\n", devname);
++              goto error;
++      }
++      else if ((errno = -media_device_enumerate(media)) != 0)
++      {
++              weston_log("failed to enumerate device '%s'\n", devname);
++              goto error_media;
++      }
++      else if ((info = media_get_info(media)) == NULL)
++      {
++              weston_log("failed to get media info data\n");
++              goto error_media;
++      }
++      else
++      {
++              dev = ((p = strchr(info->bus_info, ':')) ? p + 1 : info->bus_info);
++              weston_log("open media device: %s (%s)\n", info->bus_info, dev);
++      }
++
++      /* ...reset links */
++      if (media_reset_links(media) != 0)
++      {
++              weston_log("failed to reset media device\n");
++              goto error_media;
++      }
++
++      /* ...setup RPF.0:1 -> WPF.0:0 link */
++      snprintf(buf, sizeof(buf), "'%s rpf.0':1 -> '%s wpf.0':0 [1]", dev, dev);
++      if (media_parse_setup_link(media, buf, &endp) != 0)
++      {
++              weston_log("failed to setup link '%s'\n", buf);
++              errno = EINVAL;
++              goto error_media;
++      }
++
++      /* ...setup WPF.0:1 -> WPF.0 output link */
++      snprintf(buf, sizeof(buf), "'%s wpf.0':1 -> '%s wpf.0 output':0 [1]", dev, dev);
++      if (media_parse_setup_link(media, buf, &endp) != 0)
++      {
++              weston_log("failed to setup link '%s'\n", buf);
++              errno = EINVAL;
++              goto error_media;
++      }
++
++      /* ...specify input/output-format of RPF pad */
++      snprintf(buf, sizeof(buf), "'%s rpf.0':0", dev);
++      if ((vsp->input.infmt_pad = media_parse_pad(media, buf, NULL)) == NULL)
++      {
++              weston_log("failed to parse pad '%s'\n", buf);
++              errno = EINVAL;
++              goto error_media;
++      }
++
++      snprintf(buf, sizeof(buf), "'%s rpf.0':1", dev);
++      if ((vsp->input.outfmt_pad = media_parse_pad(media, buf, NULL)) == NULL)
++      {
++              weston_log("failed to parse pad '%s'\n", buf);
++              errno = EINVAL;
++              goto error_media;
++      }
++
++      snprintf(buf, sizeof(buf), "%s rpf.0", dev);
++      if ((vsp->input.entity = media_get_entity_by_name(media, buf, strlen(buf))) == NULL)
++      {
++              weston_log("failed to parse entity '%s'\n", buf);
++              errno = EINVAL;
++              goto error_media;
++      }
++
++      /* ...get input file-descriptor */
++      snprintf(buf, sizeof(buf), "%s rpf.0 input", dev);
++      if ((entity = media_get_entity_by_name(media, buf, strlen(buf))) == NULL)
++      {
++              weston_log("entity '%s' not found\n", buf);
++              errno = EINVAL;
++              goto error_media;
++      }
++      else if (v4l2_subdev_open(entity) != 0)
++      {
++              weston_log("failed to open subdev '%s'\n", buf);
++              goto error_media;
++      }
++      else if ((vsp->input.fd = open(media_entity_get_devname(entity), O_RDWR/* | O_NONBLOCK*/)) < 0)
++      {
++              weston_log("failed to open device '%s'\n", media_entity_get_devname(entity));
++              goto error_media;
++      }
++      else
++      {
++              weston_log("input pad setup ('%s':'%s')\n", buf, media_entity_get_devname(entity));
++      }
++
++      /* ...specify input/output formats of WPF pad */
++      snprintf(buf, sizeof(buf), "'%s wpf.0':0", dev);
++      if ((vsp->output.infmt_pad = media_parse_pad(media, buf, NULL)) == NULL)
++      {
++              weston_log("failed to parse pad '%s'\n", buf);
++              errno = EINVAL;
++              goto error_media;
++      }
++
++      snprintf(buf, sizeof(buf), "'%s wpf.0':1", dev);
++      if ((vsp->output.outfmt_pad = media_parse_pad(media, buf, NULL)) == NULL)
++      {
++              weston_log("failed to parse pad '%s'\n", buf);
++              errno = EINVAL;
++              goto error_media;
++      }
++
++      snprintf(buf, sizeof(buf), "%s wpf.0", dev);
++      if ((vsp->output.entity = media_get_entity_by_name(media, buf, strlen(buf))) == NULL)
++      {
++              weston_log("failed to parse entity '%s'\n", buf);
++              errno = EINVAL;
++              goto error_media;
++      }
++
++      /* ...get a file descriptor for the output */
++      snprintf(buf, sizeof(buf), "%s wpf.0 output", dev);
++      if ((entity = media_get_entity_by_name(media, buf, strlen(buf))) == NULL)
++      {
++              weston_log("failed to get entity '%s'\n", buf);
++              errno = EINVAL;
++              goto error_media;
++      }
++      else if (v4l2_subdev_open(entity) != 0)
++      {
++              weston_log("failed to open subdev '%s'\n", buf);
++              goto error_media;
++      }
++      else if ((vsp->output.fd = open(media_entity_get_devname(entity), O_RDWR | O_NONBLOCK)) < 0)
++      {
++              weston_log("failed to open device '%s'\n", media_entity_get_devname(entity));
++              goto error_media;
++      }
++      else
++      {
++              weston_log("output pad setup (%s:%s)\n", buf, media_entity_get_devname(entity));
++      }
++
++      weston_log("vsp-device '%s' created\n", devname);
++
++      return vsp;
++
++error_media:
++      /* ...destroy media device and all associated structures */
++      media_device_unref(vsp->media);
++
++error:
++      /* ...destroy data structure */
++      free(vsp);
++      return NULL;
++}
++
++static void
++vsp_deinit(vsp_data_t *vsp)
++{
++      /* ...destroy media device and all associated structures */
++      media_device_unref(vsp->media);
++
++      /* ...destroy data structure */
++      free(vsp);
++}
++
++/* ...set V4L2 device format */
++static int
++vsp_set_format(int fd, struct v4l2_format *fmt)
++{
++      /* ...set format */
++      if (ioctl(fd, VIDIOC_S_FMT, fmt) < 0) {
++              weston_log("format set (fd=%d) failed: %d\n",
++                      fd, errno);
++      }
++
++      return 0;
++}
++
++/* ...start streaming on specific V4L2 device */
++static int
++vsp_streaming_enable(vsp_data_t *vsp, vsp_port_n port, int enable)
++{
++      vsp_media_pad_t *pad = (port == VSP_PORT_INPUT) ? &vsp->input : &vsp->output;
++      int fd = pad->fd;
++      int type = (port == VSP_PORT_INPUT) ?
++              V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE : V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
++
++      return ioctl(fd, (enable ? VIDIOC_STREAMON : VIDIOC_STREAMOFF), &type);
++}
++
++/* ...prepare VSP filter for operation */
++static int
++vsp_set_formats(vsp_data_t *vsp, int width, int height, struct v4l2_rect* crop)
++{
++      vsp_media_pad_t *input = &vsp->input, *output = &vsp->output;
++      struct v4l2_mbus_framefmt mfmt = { .width = width, .height = height };
++      struct v4l2_format format;
++
++      /* ...configure RPF input pads; specify pixel format and size (one of YUV variants) */
++      mfmt.width = width;
++      mfmt.height = height;
++      mfmt.code = V4L2_MBUS_FMT_ARGB8888_1X32;
++      if (v4l2_subdev_set_format(input->infmt_pad->entity,
++              &mfmt, input->infmt_pad->index, V4L2_SUBDEV_FORMAT_ACTIVE) < 0) {
++              weston_log("VSP: input pad in format set failed: %d\n", errno);
++              return -1;
++      }
++
++       /* set a crop paramters */
++      if (v4l2_subdev_set_selection(input->infmt_pad->entity, crop, input->infmt_pad->index,
++                                    V4L2_SEL_TGT_CROP, V4L2_SUBDEV_FORMAT_ACTIVE)) {
++              weston_log("set crop parameter failed: %dx%d@(%d,%d).\n",
++                         crop->width, crop->height, crop->left, crop->top);
++              return -1;
++      }
++
++      /* ...output is NV12 or NV16 or I420*/
++      mfmt.width = crop->width;
++      mfmt.height = crop->height;
++      mfmt.code = V4L2_MBUS_FMT_AYUV8_1X32;
++      if (v4l2_subdev_set_format(input->outfmt_pad->entity,
++              &mfmt, input->outfmt_pad->index, V4L2_SUBDEV_FORMAT_ACTIVE) < 0) {
++              weston_log("VSP: input pad out format set failed: %d\n", errno);
++              return -1;
++      }
++
++      /* ...specify input format */
++      memset(&format, 0, sizeof(format));
++      format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
++      format.fmt.pix_mp.width = /* crop-> */width;
++      format.fmt.pix_mp.height = /* crop-> */height;
++      format.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_ABGR32;
++      format.fmt.pix_mp.num_planes = 1;
++      /* ...set input port format */
++      if (vsp_set_format(input->fd, &format) < 0) {
++              weston_log("VSP: input set format failed: %d\n", errno);
++              return -1;
++      }
++
++      /* ...both input and output are ARGB8888 always (now effective) */
++      if (v4l2_subdev_set_format(output->infmt_pad->entity,
++              &mfmt, output->infmt_pad->index, V4L2_SUBDEV_FORMAT_ACTIVE) < 0) {
++              weston_log("VSP: output pad in format set failed: %d\n", errno);
++              return -1;
++      }
++
++      /* ...specify cropping area, probably? - tbd */
++      if (v4l2_subdev_set_format(output->outfmt_pad->entity,
++              &mfmt, output->outfmt_pad->index, V4L2_SUBDEV_FORMAT_ACTIVE) < 0) {
++              weston_log("VSP: output pad in format set failed: %d\n", errno);
++              return -1;
++      }
++
++      /* ...setup output pads */
++      memset(&format, 0, sizeof(format));
++      format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
++      format.fmt.pix_mp.width = crop->width;
++      format.fmt.pix_mp.height = crop->height;
++#ifdef VSP_OUTPUT_NV16
++      format.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV16M;
++#else
++      format.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12M;
++#endif
++      format.fmt.pix_mp.num_planes = VSP_OUTPUT_BUFFERS_PLANE;
++      /* ...set output buffer format */
++      if (vsp_set_format(output->fd, &format) < 0) {
++              weston_log("VSP: output set format failed: %d\n", errno);
++              return -1;
++      }
++
++      return 0;
++}
++
++static int
++vsp_request_buffers(vsp_data_t *vsp, vsp_port_n port, unsigned int num)
++{
++      vsp_media_pad_t *pad = (port == VSP_PORT_INPUT) ? &vsp->input : &vsp->output;
++      struct v4l2_requestbuffers reqbuf;
++      int fd = pad->fd;
++
++      /* ...input buffers are DMA-fd, output buffers allocated by kernel */
++      memset(&reqbuf, 0, sizeof(reqbuf));
++      reqbuf.type = (port == VSP_PORT_INPUT) ?
++              V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE : V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
++      reqbuf.memory = V4L2_MEMORY_DMABUF;
++      reqbuf.count = num;
++      if (ioctl(fd, VIDIOC_REQBUFS, &reqbuf) < 0) {
++              weston_log("VSP: %s REQBUFS failed: %d\n",
++                      (port == VSP_PORT_INPUT) ? "input" : "output", errno);
++              return -1;
++      }
++
++      if (reqbuf.count != num) {
++              weston_log("VSP: %s failed to request %d (!= %d) bufs\n",
++                      (port == VSP_PORT_INPUT) ? "input" : "output", num, reqbuf.count);
++              return -1;
++      }
++      return 0;
++}
++
++
++/* ...enqueue dmafd buffer */
++static int
++vsp_input_buffer_queue_dmafd(vsp_data_t *vsp, int i, int dmafd)
++{
++      vsp_media_pad_t    *pad = &vsp->input;
++      struct v4l2_buffer  buf;
++      struct v4l2_plane   planes[1];
++
++      /* ...set buffer parameters */
++      memset(&buf, 0, sizeof(buf));
++      memset(planes, 0, sizeof(planes));
++      buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
++      buf.memory = V4L2_MEMORY_DMABUF;
++      buf.index = i;
++      buf.m.planes = planes;
++      buf.length = 1;
++      buf.m.planes[0].m.fd = dmafd;
++
++      /* ...submit buffer */
++      if (ioctl(pad->fd, VIDIOC_QBUF, &buf) < 0) {
++              weston_log("VSP: input dmafd (%d) buffer (%i) queue failed: %d\n",
++                      dmafd, i, errno);
++              return -1;
++      }
++
++      return 0;
++}
++
++/* ...dequeue dmafd buffer */
++static int
++vsp_input_buffer_dequeue_dmafd(vsp_data_t *vsp)
++{
++      vsp_media_pad_t    *pad = &vsp->input;
++      struct v4l2_buffer  buf;
++      struct v4l2_plane   planes[1];
++
++      /* ...set buffer parameters */
++      memset(&buf, 0, sizeof(buf));
++      memset(planes, 0, sizeof(planes));
++      buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
++      buf.memory = V4L2_MEMORY_DMABUF;
++      buf.m.planes = planes;
++      buf.length = 1;
++
++      if (ioctl(pad->fd, VIDIOC_DQBUF, &buf) < 0) {
++              weston_log("VSP: input dmafd buffer de-queue failed: %d\n", errno);
++              return -1;
++      }
++
++      /* ...return buffer index */
++      return buf.index;
++}
++
++/* ...enqueue output buffer */
++static int
++vsp_output_buffer_queue_dmafd(vsp_data_t *vsp, int i, int dmafd[])
++{
++      vsp_media_pad_t    *pad = &vsp->output;
++      struct v4l2_plane   planes[2];
++      struct v4l2_buffer  buf;
++
++      /* ...set buffer parameters (single-plane ARGB always) */
++      memset(&buf, 0, sizeof(buf));
++      memset(planes, 0, sizeof(planes));
++      buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
++      buf.memory = V4L2_MEMORY_DMABUF;
++      buf.index = i;
++      buf.m.planes = planes;
++      buf.length = VSP_OUTPUT_BUFFERS_PLANE;
++      buf.m.planes[0].m.fd = dmafd[0];
++      buf.m.planes[1].m.fd = dmafd[1];
++
++      /* ...submit buffer */
++      if (ioctl(pad->fd, VIDIOC_QBUF, &buf) < 0) {
++              weston_log("VSP: output dmafd queue failed: %d\n", errno);
++              return -1;
++      }
++
++      return 0;
++}
++
++/* ...dequeue output buffer */
++static int
++vsp_output_buffer_dequeue_dmafd(vsp_data_t *vsp)
++{
++      vsp_media_pad_t    *pad = &vsp->output;
++      struct v4l2_buffer  buf;
++      struct v4l2_plane   planes[2];
++
++      /* ...set buffer parameters */
++      memset(&buf, 0, sizeof(buf));
++      memset(planes, 0, sizeof(planes));
++      buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
++      buf.memory = V4L2_MEMORY_DMABUF;
++      buf.m.planes = planes;
++      buf.length = VSP_OUTPUT_BUFFERS_PLANE;
++
++      if (ioctl(pad->fd, VIDIOC_DQBUF, &buf) < 0) {
++              weston_log("VSP: output dmafd de-queue failed: %d\n", errno);
++              return -1;
++      }
++
++      /* ...return dequeue buffer index */
++      return buf.index;
++}
++
++/* ...get capturing interface file descriptor */
++static int
++vsp_capture_fd(vsp_data_t *vsp)
++{
++      return vsp->output.fd;
++}
++
++/*******************************************************************************
++ * Gstreamer stuff
++ ******************************************************************************/
++
++static void
++print_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data)
++{
++      int i, num;
++
++      num = gst_tag_list_get_tag_size (list, tag);
++      for (i = 0; i < num; ++i) {
++              const GValue *val;
++
++              /* Note: when looking for specific tags, use the gst_tag_list_get_xyz() API,
++               * we only use the GValue approach here because it is more generic */
++              val = gst_tag_list_get_value_index (list, tag, i);
++              if (G_VALUE_HOLDS_STRING (val)) {
++                      weston_log("\t%20s : %s\n", tag, g_value_get_string (val));
++              } else if (G_VALUE_HOLDS_UINT (val)) {
++                      weston_log("\t%20s : %u\n", tag, g_value_get_uint (val));
++              } else if (G_VALUE_HOLDS_DOUBLE (val)) {
++                      weston_log("\t%20s : %g\n", tag, g_value_get_double (val));
++              } else if (G_VALUE_HOLDS_BOOLEAN (val)) {
++                      weston_log("\t%20s : %s\n", tag,
++                                      (g_value_get_boolean (val)) ? "true" : "false");
++              } else if (GST_VALUE_HOLDS_BUFFER (val)) {
++                      GstBuffer *buf = gst_value_get_buffer (val);
++                      guint buffer_size = gst_buffer_get_size (buf);
++
++                      weston_log("\t%20s : buffer of size %u\n", tag, buffer_size);
++              } else if (GST_VALUE_HOLDS_DATE_TIME (val)) {
++                      GstDateTime *dt = g_value_get_boxed (val);
++                      gchar *dt_str = gst_date_time_to_iso8601_string (dt);
++
++                      weston_log("\t%20s : %s\n", tag, dt_str);
++                      g_free (dt_str);
++              } else {
++                      weston_log("\t%20s : tag of type '%s'\n", tag, G_VALUE_TYPE_NAME (val));
++              }
++      }
++}
++
++static gboolean
++gst_bus_callback(GstBus *bus, GstMessage *message, gpointer user_data)
++{
++      GTimeVal time;
++      struct gst_recorder *r = user_data;
++
++      if (!r->pipeline) {
++              weston_log("gst_pipeline: unexpected gst bus callback event, while pipeline==null\n");
++              return TRUE;
++      }
++
++      g_get_current_time(&time);
++
++      switch (GST_MESSAGE_TYPE(message))
++      {
++              case GST_MESSAGE_QOS:
++                      {
++                              GstFormat format;
++                              guint64 processed;
++                              guint64 dropped;
++
++                              gst_message_parse_qos_stats (message, &format, &processed, &dropped);
++                              weston_log("gst_pipeline: qos from: %s processed %lud, dropped %lud\n",
++                                      GST_OBJECT_NAME (message->src),
++                                      processed, dropped);
++                      }
++              break;
++              case GST_MESSAGE_STREAM_STATUS:
++                      {
++                              const GValue *val;
++
++                              val = gst_message_get_stream_status_object (message);
++                              weston_log("gst_pipeline: stream status type %s, value %p\n",
++                                      G_VALUE_TYPE_NAME(val),
++                                      g_value_get_object(val));
++                      }
++              break;
++              case GST_MESSAGE_TAG:
++                      {
++                              GstTagList *tags = NULL;
++
++                              gst_message_parse_tag (message, &tags);
++                              weston_log("gst_pipeline: tag from element %s:\n",
++                                      GST_OBJECT_NAME (message->src));
++                              gst_tag_list_foreach (tags, print_one_tag, NULL);
++                              weston_log("\n");
++                      }
++              break;
++              case GST_MESSAGE_STATE_CHANGED:
++                      {
++                              GstState oldstate, newstate;
++
++                              gst_message_parse_state_changed(message, &oldstate, &newstate, NULL);
++                              weston_log("gst_pipeline: element %s changed state from %s to %s.\n",
++                                      GST_OBJECT_NAME (message->src),
++                                      gst_element_state_get_name (oldstate),
++                                      gst_element_state_get_name (newstate));
++
++                              /* if gstreamer become ready */
++                              if ((GST_MESSAGE_SRC(message) == GST_OBJECT(r->appsrc)) &&
++                                      (newstate == GST_STATE_PAUSED)) {
++                                      weston_log("gst_pipeline: pipeline ready\n");
++                              }
++                      }
++              break;
++              case GST_MESSAGE_ERROR:
++                      {
++                              GError *err;
++                              gchar *debug_info;
++                              gst_message_parse_error(message, &err, &debug_info);
++                              weston_log("gst_pipeline: error received from element %s: %s\n",
++                                      GST_OBJECT_NAME(message->src), err->message);
++                              weston_log("gst_pipeline: debugging information: %s\n",
++                                      debug_info ? debug_info : "none");
++                              g_clear_error (&err);
++                              g_free (debug_info);
++                      }
++              break;
++              case GST_MESSAGE_WARNING:
++                      {
++                              GError *err;
++                              gchar *debug_info;
++                              gst_message_parse_warning(message, &err, &debug_info);
++                              weston_log("gst_pipeline: warning received from element %s: %s\n",
++                                      GST_OBJECT_NAME(message->src), err->message);
++                              weston_log("gst_pipeline: debugging information: %s\n",
++                                      debug_info ? debug_info : "none");
++                              g_clear_error (&err);
++                              g_free (debug_info);
++                      }
++              break;
++              default:
++                      weston_log("gst_pipeline: %s from %s\n",
++                              GST_MESSAGE_TYPE_NAME(message), GST_OBJECT_NAME (message->src));
++              break;
++      }
++      return TRUE;
++}
++
++static void *
++worker_thread_function(void *data)
++{
++      GstMessage *msg;
++      struct gst_recorder *r = data;
++
++      pthread_mutex_lock(&r->mutex);
++
++      while (!r->destroying) {
++              if (!r->input.valid)
++                      pthread_cond_wait(&r->input_cond, &r->mutex);
++
++              /* If the thread is awaken by destroy_worker_thread(),
++               * there might not be valid input */
++              if (!r->input.valid)
++                      continue;
++
++              /* TODO: move it to separate thread? */
++              g_main_context_iteration(r->gcontext, FALSE);
++
++              do {
++                      msg = gst_bus_pop_filtered(r->bus,
++                                                 GST_MESSAGE_ANY);
++                      if (msg) {
++                              gst_bus_callback(r->bus, msg, r);
++                      }
++              } while (msg);
++
++              /* check input */
++              gst_recorder_process_dmafd(r, r->input.prime_fd, r->input.stride);
++
++              r->input.valid = 0;
++      }
++
++      pthread_mutex_unlock(&r->mutex);
++
++      return NULL;
++}
++
++static int
++setup_worker_thread(struct gst_recorder *r)
++{
++      r->gcontext = g_main_context_new();
++      pthread_mutex_init(&r->mutex, NULL);
++      pthread_cond_init(&r->input_cond, NULL);
++      pthread_create(&r->worker_thread, NULL, worker_thread_function, r);
++
++      return 1;
++}
++
++static void
++destroy_worker_thread(struct gst_recorder *r)
++{
++      /* Make sure the worker thread finishes */
++      r->destroying = 1;
++
++      pthread_cond_signal(&r->input_cond);
++
++      pthread_join(r->worker_thread, NULL);
++
++      pthread_mutex_destroy(&r->mutex);
++      pthread_cond_destroy(&r->input_cond);
++}
++
++void weston_debug_function(GstDebugCategory* category, GstDebugLevel level,
++                          const gchar* file, const char* function,
++                          gint line, GObject* object, GstDebugMessage* message,
++                          gpointer data)
++{
++      weston_log("[GST]:%s %s:%d %s\n", file, function, line, gst_debug_message_get(message));
++}
++
++void
++gst_recorder_init(void)
++{
++      gst_init(NULL, 0);
++
++      /* VSP init */
++      vsp_g = vsp_init("/dev/media0");
++      if (!vsp_g)
++              weston_log("[gst recorder] VSP init failed\n");
++}
++
++static int
++gst_recorder_find_omx_pool(struct gst_recorder *r)
++{
++      int ret = 0;
++      GstCaps *caps;
++      GstQuery *query;
++      GstBufferPool *pool;
++      GstStructure *config;
++      guint size, min, max;
++
++      caps = gst_caps_new_simple (    "video/x-raw",
++#ifdef VSP_OUTPUT_NV16
++                                      "format", G_TYPE_STRING, "NV16",
++#else
++                                      "format", G_TYPE_STRING, "NV12",
++#endif
++                                      "width", G_TYPE_INT, r->set->crop.width,
++                                      "height", G_TYPE_INT, r->set->crop.height,
++                                      "framerate", GST_TYPE_FRACTION, 0, DEFAULT_FPS,
++                                      NULL);
++
++      /* find a pool for the negotiated caps now */
++      query = gst_query_new_allocation (caps, TRUE);
++
++      if (!gst_pad_peer_query (r->appsrc_pad, query)) {
++              /* query failed, not a problem, we use the query defaults */
++              weston_log("allocation query failed\n");
++              ret = -1;
++              goto err;
++      }
++
++      weston_log("goot %d pools\n", gst_query_get_n_allocation_pools (query));
++      if (gst_query_get_n_allocation_pools (query) > 0) {
++              /* we got configuration from our peer, parse them */
++              gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max);
++              weston_log(" pool settings size %d, min %d, max %d\n", size, min, max);
++      } else {
++              weston_log("no pool queried\n");
++              ret = -1;
++              goto err;
++      }
++
++      config = gst_buffer_pool_get_config (pool);
++      gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META);
++      gst_buffer_pool_config_set_params (config, caps, size, min, max);
++      gst_buffer_pool_set_config (pool, config);
++
++      /* and activate */
++      gst_buffer_pool_set_active (pool, TRUE);
++
++      r->omx_pool = pool;
++
++err:
++      gst_query_unref (query);
++      return ret;
++}
++
++static int
++gst_recorder_omx_buffer_acquire(struct gst_recorder *r, GstBuffer **ret_buf, int fd[])
++{
++      unsigned int i;
++      GstFlowReturn ret;
++      GstBuffer *buf;
++      guint n_mem;
++      GstMemory *mem;
++
++      ret = gst_buffer_pool_acquire_buffer(r->omx_pool, &buf, NULL);
++      if (ret != GST_FLOW_OK) {
++              weston_log("OMX buffer acquire failed\n");
++              return -1;
++      }
++
++      n_mem = gst_buffer_n_memory(buf);
++      if (n_mem < 1) {
++              weston_log("Buffer with no mem!\n");
++              goto err_release;
++      }
++
++      for (i = 0; i < n_mem; i++) {
++              mem = gst_buffer_peek_memory (buf, i);
++              if (!gst_is_dmabuf_memory (mem)) {
++                      weston_log("Mem not dmabuf\n");
++                      goto err_release;
++              }
++              fd[i] = gst_dmabuf_memory_get_fd (mem);
++      }
++
++      *ret_buf = buf;
++
++      return 0;
++err_release:
++      gst_buffer_pool_release_buffer(r->omx_pool, buf);
++      return -1;
++}
++
++static int
++gst_recorder_omx_buffer_release(struct gst_recorder *r, GstBuffer *buf)
++{
++      gst_buffer_pool_release_buffer(r->omx_pool, buf);
++
++      return 0;
++}
++
++struct gst_recorder *
++gst_recorder_create(struct gst_recorder_settings *settings)
++{
++      struct gst_recorder *r;
++      char gst_pipe[1024];
++      char *ptr = gst_pipe;
++      GError *perror = NULL;
++
++      weston_log("gst_recorder_create (%dx%d) crop %dx%d at %d,%d\n",
++                 settings->width, settings->height, settings->crop.width,
++                 settings->crop.height, settings->crop.top, settings->crop.left);
++
++      if (!vsp_g) {
++              weston_log("gst_recorder_create: no VSP\n");
++              return NULL;
++      }
++
++      r = calloc(1, sizeof *r);
++      if (!r)
++              return NULL;
++      memset(r, 0, sizeof *r);
++
++      r->set = settings;
++      r->timestamp = 0;
++
++      r->vsp = vsp_g;
++      vsp_g->users++;
++
++      /* GST init */
++      /* source (GST_FORMAT_BYTES) */
++      ptr += sprintf(ptr,
++              "appsrc name=src ! ");
++
++      /* omx */
++      ptr += sprintf(ptr,
++              "omxh264enc target-bitrate=%d control-rate=2 name=my_encoder ! "
++              "video/x-h264,width=%d,height=%d ! ",
++              r->set->bitrate, r->set->crop.width, r->set->crop.height);
++
++      /* rtp payloader */
++      ptr += sprintf(ptr,
++              "rtph264pay config-interval=1 name=my_h264pay ! queue  ! ");
++
++      /* usp sink */
++      ptr += sprintf(ptr,
++              "udpsink host=%s ", r->set->ip);
++      if (r->set->port > 0)
++              ptr += sprintf(ptr,
++                      " port=%d name=my_udpsink", r->set->port);
++
++      weston_log("gst_pipeline: starting: %s\n", gst_pipe);
++
++      /* launch */
++      r->pipeline = gst_parse_launch (gst_pipe, &perror);
++      if (!r->pipeline) {
++              weston_log("gst_pipeline: can not start pipeline: %s\n", perror->message);
++              goto err_gst;
++      }
++
++      /* get appsrc */
++      r->appsrc = gst_bin_get_by_name(GST_BIN (r->pipeline), "src");
++      if (!r->appsrc) {
++              weston_log("gst_pipeline: can not get appsrc\n");
++              goto err_gst;
++      }
++
++      /* get bus */
++      r->bus = gst_pipeline_get_bus (GST_PIPELINE(r->pipeline));
++      if (!r->bus) {
++              weston_log("gst_pipeline: can not get bus\n");
++              goto err_gst;
++      }
++
++      setup_worker_thread(r);
++
++      /* setup caps */
++      g_object_set(G_OBJECT(r->appsrc), "caps",
++              gst_caps_new_simple (   "video/x-raw",
++#ifdef VSP_OUTPUT_NV16
++                                      "format", G_TYPE_STRING, "NV16",
++#else
++                                      "format", G_TYPE_STRING, "NV12",
++#endif
++                                      "width", G_TYPE_INT, r->set->crop.width,
++                                      "height", G_TYPE_INT, r->set->crop.height,
++                                      "framerate", GST_TYPE_FRACTION, 0, DEFAULT_FPS,
++                                      NULL), NULL);
++
++      r->appsrc_pad = gst_element_get_static_pad(GST_ELEMENT_CAST(r->appsrc), "src");
++      if (!r->appsrc_pad)
++              weston_log("Failed to get src0 pad of appsrc\n");
++
++      /* set playing */
++      if (gst_element_set_state (r->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) {
++              weston_log("gst_pipeline: can not change state to PLAYING\n");
++              goto err_gst;
++      }
++
++      if (gst_recorder_find_omx_pool(r) != 0) {
++              weston_log("failed to find OMX buffer pool\n");
++              goto err_gst_stop;
++      }
++
++      /* set clock time */
++      r->clock = gst_element_get_clock (GST_ELEMENT_CAST (r->appsrc));
++
++      weston_log("gst_recorder_create done\n");
++
++      return r;
++
++err_gst_stop:
++      gst_element_set_state (r->pipeline, GST_STATE_NULL);
++      destroy_worker_thread(r);
++err_gst:
++      free(r->pipeline);
++      free(r);
++
++      return NULL;
++}
++
++void
++gst_recorder_destroy(struct gst_recorder *r)
++{
++      r->vsp->users--;
++
++      if (r->pipeline) {
++              gst_element_set_state (r->pipeline, GST_STATE_NULL);
++              gst_object_unref(r->omx_pool);
++
++              destroy_worker_thread(r);
++
++              gst_object_unref(GST_OBJECT(r->bus));
++              gst_object_unref (r->pipeline);
++              r->pipeline = NULL;
++      }
++      free(r);
++}
++
++static int
++gst_recorder_set_timestamp(struct gst_recorder *r, GstBuffer *buffer)
++{
++      GstClockTime cur_time = gst_clock_get_time(r->clock);
++
++      if (r->timestamp == 0) {
++              /* first frame assume around DEFAULT_FPS FPS */
++              GST_BUFFER_DURATION(buffer) = gst_util_uint64_scale_int(1, GST_SECOND, DEFAULT_FPS);
++      } else {
++              GstClockTime delta = cur_time - r->ts_last_frame;
++              /* delta in nS */
++              GST_BUFFER_DURATION(buffer) = delta;
++      }
++
++      r->timestamp += GST_BUFFER_DURATION(buffer);
++      GST_BUFFER_PTS(buffer) = r->timestamp;
++      GST_BUFFER_DTS(buffer) = r->timestamp;
++
++      r->ts_last_frame = cur_time;
++
++      return 0;
++}
++
++
++static int
++gst_recorder_process_dmafd(struct gst_recorder *r, int fd, int stride)
++{
++      int ret;
++      GstBuffer *buf;
++      int omx_fd[2];
++
++        /* get GST buffer */
++      if (gst_recorder_omx_buffer_acquire(r, &buf, omx_fd) < 0) {
++              weston_log("VSP: can not acquire GST buffer, dropping frame\n");
++              return 0;
++      }
++
++      pthread_mutex_lock(&r->vsp->mutex);
++      /* setup vsp */
++      if (vsp_set_formats(r->vsp, r->set->width, r->set->height, &r->set->crop) < 0) {
++              weston_log("VSP: format set failed\n");
++              goto err;
++      }
++
++      /* input */
++      if (vsp_request_buffers(r->vsp, VSP_PORT_INPUT, 1) < 0) {
++              weston_log("VSP: input buffer allocation failed\n");
++              goto err_vsp;
++      }
++
++      /* output */
++      if (vsp_request_buffers(r->vsp, VSP_PORT_OUTPUT, 1) < 0) {
++              weston_log("VSP: output buffer allocation failed\n");
++              goto err_vsp;
++      }
++
++      /* queue output biffer */
++      if (vsp_output_buffer_queue_dmafd(r->vsp, 0, omx_fd) < 0) {
++              weston_log("can not queue OMX buffer %d to VSP\n", 0);
++              gst_recorder_omx_buffer_release(r, buf);
++              goto err_vsp;
++      }
++
++      /* queue input vsp buffer */
++      if (vsp_input_buffer_queue_dmafd(r->vsp, 0, fd) < 0) {
++              weston_log("VSP: failed to queue input buffer\n");
++              goto err_vsp;
++      }
++
++      /* start input */
++      if (vsp_streaming_enable(r->vsp, VSP_PORT_INPUT, 1) < 0) {
++              weston_log("VSP: failed to start input\n");
++              goto err_vsp;
++      }
++
++      /* start output */
++      if (vsp_streaming_enable(r->vsp, VSP_PORT_OUTPUT, 1) < 0) {
++              weston_log("VSP: failed to start output\n");
++              goto err_vsp;
++      }
++
++      /* dequeue input (do we need this?) */
++      if (vsp_input_buffer_dequeue_dmafd(r->vsp) < 0) {
++              weston_log("VSP: failed to dequeue input buffer\n");
++              /* don't care */
++      }
++
++      /* dequeue output */
++      if (vsp_output_buffer_dequeue_dmafd(r->vsp) < 0) {
++              weston_log("VSP: failed to dequeu output buffer\n");
++              gst_recorder_omx_buffer_release(r, buf);
++              /* fall through */
++      } else {
++              /* set timestamp */
++              gst_recorder_set_timestamp(r, buf);
++
++              ret = gst_app_src_push_buffer(r->appsrc, buf);
++              r->frame_count++;
++
++              if (ret != GST_FLOW_OK) {
++                      /* some error, stop sending data */
++                      weston_log("gst_pipeline: some error %d\n", ret);
++              }
++
++      }
++      /* stop input */
++      vsp_streaming_enable(r->vsp, VSP_PORT_INPUT, 0);
++      /* stop output */
++      vsp_streaming_enable(r->vsp, VSP_PORT_OUTPUT, 0);
++
++      /* deinit */
++      vsp_request_buffers(r->vsp, VSP_PORT_INPUT, 0);
++      vsp_request_buffers(r->vsp, VSP_PORT_OUTPUT, 0);
++
++      pthread_mutex_unlock(&r->vsp->mutex);
++      return 0;
++
++err_vsp:
++      /* drop gst buffer */
++      /* finish vsp here */
++err:
++      pthread_mutex_unlock(&r->vsp->mutex);
++      return -1;
++}
++
++int
++gst_recorder_frame_dmafd(struct gst_recorder *r, int fd, int stride)
++{
++      int ret = 0;
++      
++      pthread_mutex_lock(&r->mutex);
++
++      if (r->error) {
++              errno = r->error;
++              ret = -1;
++              goto unlock;
++      }
++              
++      /* The mutex is never released while encoding, so this point should
++       * never be reached if input.valid is true. */
++      assert(!r->input.valid);
++
++      r->input.prime_fd = fd;
++      r->input.stride = stride;
++      r->input.valid = 1;
++      pthread_cond_signal(&r->input_cond);
++
++unlock:
++      pthread_mutex_unlock(&r->mutex);
++
++      return 0;
++}
+diff --git a/libweston/gst-recorder.h b/libweston/gst-recorder.h
+new file mode 100644
+index 0000000..78290c1
+--- /dev/null
++++ b/libweston/gst-recorder.h
+@@ -0,0 +1,58 @@
++/*
++ * Copyright Â© 2016 Cogent Embedded Inc
++ *
++ * Permission to use, copy, modify, distribute, and sell this software and
++ * its documentation for any purpose is hereby granted without fee, provided
++ * that the above copyright notice appear in all copies and that both that
++ * copyright notice and this permission notice appear in supporting
++ * documentation, and that the name of the copyright holders not be used in
++ * advertising or publicity pertaining to distribution of the software
++ * without specific, written prior permission.  The copyright holders make
++ * no representations about the suitability of this software for any
++ * purpose.  It is provided "as is" without express or implied warranty.
++ *
++ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
++ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
++ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
++ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
++ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
++ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
++ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
++ */
++
++#ifndef _GST_RECORDER_H_
++#define _GST_RECORDER_H_
++
++/* VSP includes */
++#include <media-ctl/mediactl.h>
++#include <media-ctl/v4l2subdev.h>
++
++struct gst_recorder;
++
++struct gst_recorder_settings {
++      int width;
++      int height;
++      int bitrate;
++      char *ip;
++      int port;
++      int latency_test;
++      int refresh_ratio;
++
++      /* Cropping */
++      struct v4l2_rect crop;
++};
++
++void
++gst_recorder_init(void);
++struct gst_recorder *
++gst_recorder_create(struct gst_recorder_settings *settings);
++void
++gst_recorder_destroy(struct gst_recorder *r);
++int
++gst_recorder_frame(struct gst_recorder *r, int fd, int stride);
++int
++gst_recorder_frame_mmap(struct gst_recorder *r, void *data, int stride);
++int
++gst_recorder_frame_dmafd(struct gst_recorder *r, int fd, int stride);
++
++#endif /* _GST_RECORDER_H_ */
+diff --git a/libweston/media-ctl/libmediactl.c b/libweston/media-ctl/libmediactl.c
+new file mode 100644
+index 0000000..f15b1a3
+--- /dev/null
++++ b/libweston/media-ctl/libmediactl.c
+@@ -0,0 +1,955 @@
++/*
++ * Media controller interface library
++ *
++ * Copyright (C) 2010-2014 Ideas on board SPRL
++ *
++ * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU Lesser General Public License as published
++ * by the Free Software Foundation; either version 2.1 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#include "config.h"
++
++#include <sys/ioctl.h>
++#include <sys/stat.h>
++#include <sys/types.h>
++
++#include <ctype.h>
++#include <errno.h>
++#include <fcntl.h>
++#include <stdbool.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <unistd.h>
++
++#include <linux/media.h>
++#include <linux/videodev2.h>
++
++#include "mediactl.h"
++#include "mediactl-priv.h"
++#include "tools.h"
++
++/* -----------------------------------------------------------------------------
++ * Graph access
++ */
++
++struct media_pad *media_entity_remote_source(struct media_pad *pad)
++{
++      unsigned int i;
++
++      if (!(pad->flags & MEDIA_PAD_FL_SINK))
++              return NULL;
++
++      for (i = 0; i < pad->entity->num_links; ++i) {
++              struct media_link *link = &pad->entity->links[i];
++
++              if (!(link->flags & MEDIA_LNK_FL_ENABLED))
++                      continue;
++
++              if (link->sink == pad)
++                      return link->source;
++      }
++
++      return NULL;
++}
++
++struct media_entity *media_get_entity_by_name(struct media_device *media,
++                                            const char *name, size_t length)
++{
++      unsigned int i;
++
++      /* A match is impossible if the entity name is longer than the maximum
++       * size we can get from the kernel.
++       */
++      if (length >= FIELD_SIZEOF(struct media_entity_desc, name))
++              return NULL;
++
++      for (i = 0; i < media->entities_count; ++i) {
++              struct media_entity *entity = &media->entities[i];
++
++              if (strncmp(entity->info.name, name, length) == 0 &&
++                  entity->info.name[length] == '\0')
++                      return entity;
++      }
++
++      return NULL;
++}
++
++struct media_entity *media_get_entity_by_id(struct media_device *media,
++                                          __u32 id)
++{
++      bool next = id & MEDIA_ENT_ID_FLAG_NEXT;
++      unsigned int i;
++
++      id &= ~MEDIA_ENT_ID_FLAG_NEXT;
++
++      for (i = 0; i < media->entities_count; ++i) {
++              struct media_entity *entity = &media->entities[i];
++
++              if ((entity->info.id == id && !next) ||
++                  (entity->info.id > id && next))
++                      return entity;
++      }
++
++      return NULL;
++}
++
++unsigned int media_get_entities_count(struct media_device *media)
++{
++      return media->entities_count;
++}
++
++struct media_entity *media_get_entity(struct media_device *media, unsigned int index)
++{
++      if (index >= media->entities_count)
++              return NULL;
++
++      return &media->entities[index];
++}
++
++const struct media_pad *media_entity_get_pad(struct media_entity *entity, unsigned int index)
++{
++      if (index >= entity->info.pads)
++              return NULL;
++
++      return &entity->pads[index];
++}
++
++unsigned int media_entity_get_links_count(struct media_entity *entity)
++{
++      return entity->num_links;
++}
++
++const struct media_link *media_entity_get_link(struct media_entity *entity, unsigned int index)
++{
++      if (index >= entity->num_links)
++              return NULL;
++
++      return &entity->links[index];
++}
++
++const char *media_entity_get_devname(struct media_entity *entity)
++{
++      return entity->devname[0] ? entity->devname : NULL;
++}
++
++struct media_entity *media_get_default_entity(struct media_device *media,
++                                            unsigned int type)
++{
++      switch (type) {
++      case MEDIA_ENT_T_DEVNODE_V4L:
++              return media->def.v4l;
++      case MEDIA_ENT_T_DEVNODE_FB:
++              return media->def.fb;
++      case MEDIA_ENT_T_DEVNODE_ALSA:
++              return media->def.alsa;
++      case MEDIA_ENT_T_DEVNODE_DVB:
++              return media->def.dvb;
++      }
++
++      return NULL;
++}
++
++const struct media_device_info *media_get_info(struct media_device *media)
++{
++      return &media->info;
++}
++
++const char *media_get_devnode(struct media_device *media)
++{
++      return media->devnode;
++}
++
++const struct media_entity_desc *media_entity_get_info(struct media_entity *entity)
++{
++      return &entity->info;
++}
++
++/* -----------------------------------------------------------------------------
++ * Open/close
++ */
++
++static int media_device_open(struct media_device *media)
++{
++      int ret;
++
++      if (media->fd != -1)
++              return 0;
++
++      media_dbg(media, "Opening media device %s\n", media->devnode);
++
++      media->fd = open(media->devnode, O_RDWR);
++      if (media->fd < 0) {
++              ret = -errno;
++              media_dbg(media, "%s: Can't open media device %s\n",
++                        __func__, media->devnode);
++              return ret;
++      }
++
++      return 0;
++}
++
++static void media_device_close(struct media_device *media)
++{
++      if (media->fd != -1) {
++              close(media->fd);
++              media->fd = -1;
++      }
++}
++
++/* -----------------------------------------------------------------------------
++ * Link setup
++ */
++
++int media_setup_link(struct media_device *media,
++                   struct media_pad *source,
++                   struct media_pad *sink,
++                   __u32 flags)
++{
++      struct media_link *link;
++      struct media_link_desc ulink;
++      unsigned int i;
++      int ret;
++
++      ret = media_device_open(media);
++      if (ret < 0)
++              goto done;
++
++      for (i = 0; i < source->entity->num_links; i++) {
++              link = &source->entity->links[i];
++
++              if (link->source->entity == source->entity &&
++                  link->source->index == source->index &&
++                  link->sink->entity == sink->entity &&
++                  link->sink->index == sink->index)
++                      break;
++      }
++
++      if (i == source->entity->num_links) {
++              media_dbg(media, "%s: Link not found\n", __func__);
++              ret = -ENOENT;
++              goto done;
++      }
++
++      /* source pad */
++      ulink.source.entity = source->entity->info.id;
++      ulink.source.index = source->index;
++      ulink.source.flags = MEDIA_PAD_FL_SOURCE;
++
++      /* sink pad */
++      ulink.sink.entity = sink->entity->info.id;
++      ulink.sink.index = sink->index;
++      ulink.sink.flags = MEDIA_PAD_FL_SINK;
++
++      ulink.flags = flags | (link->flags & MEDIA_LNK_FL_IMMUTABLE);
++
++      ret = ioctl(media->fd, MEDIA_IOC_SETUP_LINK, &ulink);
++      if (ret == -1) {
++              ret = -errno;
++              media_dbg(media, "%s: Unable to setup link (%s)\n",
++                        __func__, strerror(errno));
++              goto done;
++      }
++
++      link->flags = ulink.flags;
++      link->twin->flags = ulink.flags;
++
++      ret = 0;
++
++done:
++      return ret;
++}
++
++int media_reset_links(struct media_device *media)
++{
++      unsigned int i, j;
++      int ret;
++
++      for (i = 0; i < media->entities_count; ++i) {
++              struct media_entity *entity = &media->entities[i];
++
++              for (j = 0; j < entity->num_links; j++) {
++                      struct media_link *link = &entity->links[j];
++
++                      if (link->flags & MEDIA_LNK_FL_IMMUTABLE ||
++                          link->source->entity != entity)
++                              continue;
++
++                      ret = media_setup_link(media, link->source, link->sink,
++                                             link->flags & ~MEDIA_LNK_FL_ENABLED);
++                      if (ret < 0)
++                              return ret;
++              }
++      }
++
++      return 0;
++}
++
++/* -----------------------------------------------------------------------------
++ * Entities, pads and links enumeration
++ */
++
++static struct media_link *media_entity_add_link(struct media_entity *entity)
++{
++      if (entity->num_links >= entity->max_links) {
++              struct media_link *links = entity->links;
++              unsigned int max_links = entity->max_links * 2;
++              unsigned int i;
++
++              links = realloc(links, max_links * sizeof *links);
++              if (links == NULL)
++                      return NULL;
++
++              for (i = 0; i < entity->num_links; ++i)
++                      links[i].twin->twin = &links[i];
++
++              entity->max_links = max_links;
++              entity->links = links;
++      }
++
++      return &entity->links[entity->num_links++];
++}
++
++static int media_enum_links(struct media_device *media)
++{
++      __u32 id;
++      int ret = 0;
++
++      for (id = 1; id <= media->entities_count; id++) {
++              struct media_entity *entity = &media->entities[id - 1];
++              struct media_links_enum links;
++              unsigned int i;
++
++              links.entity = entity->info.id;
++              links.pads = calloc(entity->info.pads, sizeof(struct media_pad_desc));
++              links.links = calloc(entity->info.links, sizeof(struct media_link_desc));
++
++              if (ioctl(media->fd, MEDIA_IOC_ENUM_LINKS, &links) < 0) {
++                      ret = -errno;
++                      media_dbg(media,
++                                "%s: Unable to enumerate pads and links (%s).\n",
++                                __func__, strerror(errno));
++                      free(links.pads);
++                      free(links.links);
++                      return ret;
++              }
++
++              for (i = 0; i < entity->info.pads; ++i) {
++                      entity->pads[i].entity = entity;
++                      entity->pads[i].index = links.pads[i].index;
++                      entity->pads[i].flags = links.pads[i].flags;
++              }
++
++              for (i = 0; i < entity->info.links; ++i) {
++                      struct media_link_desc *link = &links.links[i];
++                      struct media_link *fwdlink;
++                      struct media_link *backlink;
++                      struct media_entity *source;
++                      struct media_entity *sink;
++
++                      source = media_get_entity_by_id(media, link->source.entity);
++                      sink = media_get_entity_by_id(media, link->sink.entity);
++
++                      if (source == NULL || sink == NULL) {
++                              media_dbg(media,
++                                        "WARNING entity %u link %u from %u/%u to %u/%u is invalid!\n",
++                                        id, i, link->source.entity,
++                                        link->source.index,
++                                        link->sink.entity,
++                                        link->sink.index);
++                              ret = -EINVAL;
++                      } else {
++                              fwdlink = media_entity_add_link(source);
++                              fwdlink->source = &source->pads[link->source.index];
++                              fwdlink->sink = &sink->pads[link->sink.index];
++                              fwdlink->flags = link->flags;
++
++                              backlink = media_entity_add_link(sink);
++                              backlink->source = &source->pads[link->source.index];
++                              backlink->sink = &sink->pads[link->sink.index];
++                              backlink->flags = link->flags;
++
++                              fwdlink->twin = backlink;
++                              backlink->twin = fwdlink;
++                      }
++              }
++
++              free(links.pads);
++              free(links.links);
++      }
++
++      return ret;
++}
++
++#ifdef HAVE_LIBUDEV
++
++#include <libudev.h>
++
++static inline int media_udev_open(struct udev **udev)
++{
++      *udev = udev_new();
++      if (*udev == NULL)
++              return -ENOMEM;
++      return 0;
++}
++
++static inline void media_udev_close(struct udev *udev)
++{
++      if (udev != NULL)
++              udev_unref(udev);
++}
++
++static int media_get_devname_udev(struct udev *udev,
++              struct media_entity *entity)
++{
++      struct udev_device *device;
++      dev_t devnum;
++      const char *p;
++      int ret = -ENODEV;
++
++      if (udev == NULL)
++              return -EINVAL;
++
++      devnum = makedev(entity->info.v4l.major, entity->info.v4l.minor);
++      media_dbg(entity->media, "looking up device: %u:%u\n",
++                major(devnum), minor(devnum));
++      device = udev_device_new_from_devnum(udev, 'c', devnum);
++      if (device) {
++              p = udev_device_get_devnode(device);
++              if (p) {
++                      strncpy(entity->devname, p, sizeof(entity->devname));
++                      entity->devname[sizeof(entity->devname) - 1] = '\0';
++              }
++              ret = 0;
++      }
++
++      udev_device_unref(device);
++
++      return ret;
++}
++
++#else /* HAVE_LIBUDEV */
++
++struct udev;
++
++static inline int media_udev_open(struct udev **udev) { return 0; }
++
++static inline void media_udev_close(struct udev *udev) { }
++
++static inline int media_get_devname_udev(struct udev *udev,
++              struct media_entity *entity)
++{
++      return -ENOTSUP;
++}
++
++#endif        /* HAVE_LIBUDEV */
++
++static int media_get_devname_sysfs(struct media_entity *entity)
++{
++      struct stat devstat;
++      char devname[32];
++      char sysname[32];
++      char target[1024];
++      char *p;
++      int ret;
++
++      sprintf(sysname, "/sys/dev/char/%u:%u", entity->info.v4l.major,
++              entity->info.v4l.minor);
++      ret = readlink(sysname, target, sizeof(target) - 1);
++      if (ret < 0)
++              return -errno;
++
++      target[ret] = '\0';
++      p = strrchr(target, '/');
++      if (p == NULL)
++              return -EINVAL;
++
++      sprintf(devname, "/dev/%s", p + 1);
++      ret = stat(devname, &devstat);
++      if (ret < 0)
++              return -errno;
++
++      /* Sanity check: udev might have reordered the device nodes.
++       * Make sure the major/minor match. We should really use
++       * libudev.
++       */
++      if (major(devstat.st_rdev) == entity->info.v4l.major &&
++          minor(devstat.st_rdev) == entity->info.v4l.minor)
++              strcpy(entity->devname, devname);
++
++      return 0;
++}
++
++static int media_enum_entities(struct media_device *media)
++{
++      struct media_entity *entity;
++      struct udev *udev;
++      unsigned int size;
++      __u32 id;
++      int ret;
++
++      ret = media_udev_open(&udev);
++      if (ret < 0)
++              media_dbg(media, "Can't get udev context\n");
++
++      for (id = 0, ret = 0; ; id = entity->info.id) {
++              size = (media->entities_count + 1) * sizeof(*media->entities);
++              media->entities = realloc(media->entities, size);
++
++              entity = &media->entities[media->entities_count];
++              memset(entity, 0, sizeof(*entity));
++              entity->fd = -1;
++              entity->info.id = id | MEDIA_ENT_ID_FLAG_NEXT;
++              entity->media = media;
++
++              ret = ioctl(media->fd, MEDIA_IOC_ENUM_ENTITIES, &entity->info);
++              if (ret < 0) {
++                      ret = errno != EINVAL ? -errno : 0;
++                      break;
++              }
++
++              /* Number of links (for outbound links) plus number of pads (for
++               * inbound links) is a good safe initial estimate of the total
++               * number of links.
++               */
++              entity->max_links = entity->info.pads + entity->info.links;
++
++              entity->pads = malloc(entity->info.pads * sizeof(*entity->pads));
++              entity->links = malloc(entity->max_links * sizeof(*entity->links));
++              if (entity->pads == NULL || entity->links == NULL) {
++                      ret = -ENOMEM;
++                      break;
++              }
++
++              media->entities_count++;
++
++              if (entity->info.flags & MEDIA_ENT_FL_DEFAULT) {
++                      switch (entity->info.type) {
++                      case MEDIA_ENT_T_DEVNODE_V4L:
++                              media->def.v4l = entity;
++                              break;
++                      case MEDIA_ENT_T_DEVNODE_FB:
++                              media->def.fb = entity;
++                              break;
++                      case MEDIA_ENT_T_DEVNODE_ALSA:
++                              media->def.alsa = entity;
++                              break;
++                      case MEDIA_ENT_T_DEVNODE_DVB:
++                              media->def.dvb = entity;
++                              break;
++                      }
++              }
++
++              /* Find the corresponding device name. */
++              if (media_entity_type(entity) != MEDIA_ENT_T_DEVNODE &&
++                  media_entity_type(entity) != MEDIA_ENT_T_V4L2_SUBDEV)
++                      continue;
++
++              /* Try to get the device name via udev */
++              if (!media_get_devname_udev(udev, entity))
++                      continue;
++
++              /* Fall back to get the device name via sysfs */
++              media_get_devname_sysfs(entity);
++      }
++
++      media_udev_close(udev);
++      return ret;
++}
++
++int media_device_enumerate(struct media_device *media)
++{
++      int ret;
++
++      if (media->entities)
++              return 0;
++
++      ret = media_device_open(media);
++      if (ret < 0)
++              return ret;
++
++      ret = ioctl(media->fd, MEDIA_IOC_DEVICE_INFO, &media->info);
++      if (ret < 0) {
++              ret = -errno;
++              media_dbg(media, "%s: Unable to retrieve media device "
++                        "information for device %s (%s)\n", __func__,
++                        media->devnode, strerror(errno));
++              goto done;
++      }
++
++      media_dbg(media, "Enumerating entities\n");
++
++      ret = media_enum_entities(media);
++      if (ret < 0) {
++              media_dbg(media,
++                        "%s: Unable to enumerate entities for device %s (%s)\n",
++                        __func__, media->devnode, strerror(-ret));
++              goto done;
++      }
++
++      media_dbg(media, "Found %u entities\n", media->entities_count);
++      media_dbg(media, "Enumerating pads and links\n");
++
++      ret = media_enum_links(media);
++      if (ret < 0) {
++              media_dbg(media,
++                        "%s: Unable to enumerate pads and linksfor device %s\n",
++                        __func__, media->devnode);
++              goto done;
++      }
++
++      ret = 0;
++
++done:
++      return ret;
++}
++
++/* -----------------------------------------------------------------------------
++ * Create/destroy
++ */
++
++static void media_debug_default(void *ptr, ...)
++{
++}
++
++void media_debug_set_handler(struct media_device *media,
++                           void (*debug_handler)(void *, ...),
++                           void *debug_priv)
++{
++      if (debug_handler) {
++              media->debug_handler = debug_handler;
++              media->debug_priv = debug_priv;
++      } else {
++              media->debug_handler = media_debug_default;
++              media->debug_priv = NULL;
++      }
++}
++
++static struct media_device *__media_device_new(void)
++{
++      struct media_device *media;
++
++      media = calloc(1, sizeof(*media));
++      if (media == NULL)
++              return NULL;
++
++      media->fd = -1;
++      media->refcount = 1;
++
++      media_debug_set_handler(media, NULL, NULL);
++
++      return media;
++}
++
++struct media_device *media_device_new(const char *devnode)
++{
++      struct media_device *media;
++
++      media = __media_device_new();
++      if (media == NULL)
++              return NULL;
++
++      media->devnode = strdup(devnode);
++      if (media->devnode == NULL) {
++              media_device_unref(media);
++              return NULL;
++      }
++
++      return media;
++}
++
++struct media_device *media_device_new_emulated(struct media_device_info *info)
++{
++      struct media_device *media;
++
++      media = __media_device_new();
++      if (media == NULL)
++              return NULL;
++
++      media->info = *info;
++
++      return media;
++}
++
++struct media_device *media_device_ref(struct media_device *media)
++{
++      media->refcount++;
++      return media;
++}
++
++void media_device_unref(struct media_device *media)
++{
++      unsigned int i;
++
++      media->refcount--;
++      if (media->refcount > 0)
++              return;
++
++      for (i = 0; i < media->entities_count; ++i) {
++              struct media_entity *entity = &media->entities[i];
++
++              free(entity->pads);
++              free(entity->links);
++              if (entity->fd != -1)
++                      close(entity->fd);
++      }
++
++      free(media->entities);
++      free(media->devnode);
++      free(media);
++}
++
++int media_device_add_entity(struct media_device *media,
++                          const struct media_entity_desc *desc,
++                          const char *devnode)
++{
++      struct media_entity **defent = NULL;
++      struct media_entity *entity;
++      unsigned int size;
++
++      size = (media->entities_count + 1) * sizeof(*media->entities);
++      entity = realloc(media->entities, size);
++      if (entity == NULL)
++              return -ENOMEM;
++
++      media->entities = entity;
++      media->entities_count++;
++
++      entity = &media->entities[media->entities_count - 1];
++      memset(entity, 0, sizeof *entity);
++
++      entity->fd = -1;
++      entity->media = media;
++      strncpy(entity->devname, devnode, sizeof entity->devname);
++      entity->devname[sizeof entity->devname - 1] = '\0';
++
++      entity->info.id = 0;
++      entity->info.type = desc->type;
++      entity->info.flags = 0;
++      memcpy(entity->info.name, desc->name, sizeof entity->info.name);
++
++      switch (entity->info.type) {
++      case MEDIA_ENT_T_DEVNODE_V4L:
++              defent = &media->def.v4l;
++              entity->info.v4l = desc->v4l;
++              break;
++      case MEDIA_ENT_T_DEVNODE_FB:
++              defent = &media->def.fb;
++              entity->info.fb = desc->fb;
++              break;
++      case MEDIA_ENT_T_DEVNODE_ALSA:
++              defent = &media->def.alsa;
++              entity->info.alsa = desc->alsa;
++              break;
++      case MEDIA_ENT_T_DEVNODE_DVB:
++              defent = &media->def.dvb;
++              entity->info.dvb = desc->dvb;
++              break;
++      }
++
++      if (desc->flags & MEDIA_ENT_FL_DEFAULT) {
++              entity->info.flags |= MEDIA_ENT_FL_DEFAULT;
++              if (defent)
++                      *defent = entity;
++      }
++
++      return 0;
++}
++
++struct media_pad *media_parse_pad(struct media_device *media,
++                                const char *p, char **endp)
++{
++      unsigned int entity_id, pad;
++      struct media_entity *entity;
++      char *end;
++
++      /* endp can be NULL. To avoid spreading NULL checks across the function,
++       * set endp to &end in that case.
++       */
++      if (endp == NULL)
++              endp = &end;
++
++      for (; isspace(*p); ++p);
++
++      if (*p == '"' || *p == '\'') {
++              for (end = (char *)p + 1; *end && *end != '"' && *end != '\''; ++end);
++              if (*end != '"' && *end != '\'') {
++                      media_dbg(media, "missing matching '\"'\n");
++                      *endp = end;
++                      return NULL;
++              }
++
++              entity = media_get_entity_by_name(media, p + 1, end - p - 1);
++              if (entity == NULL) {
++                      media_dbg(media, "no such entity \"%.*s\"\n", end - p - 1, p + 1);
++                      *endp = (char *)p + 1;
++                      return NULL;
++              }
++
++              ++end;
++      } else {
++              entity_id = strtoul(p, &end, 10);
++              entity = media_get_entity_by_id(media, entity_id);
++              if (entity == NULL) {
++                      media_dbg(media, "no such entity %d\n", entity_id);
++                      *endp = (char *)p;
++                      return NULL;
++              }
++      }
++      for (; isspace(*end); ++end);
++
++      if (*end != ':') {
++              media_dbg(media, "Expected ':'\n", *end);
++              *endp = end;
++              return NULL;
++      }
++
++      for (p = end + 1; isspace(*p); ++p);
++
++      pad = strtoul(p, &end, 10);
++
++      if (pad >= entity->info.pads) {
++              media_dbg(media, "No pad '%d' on entity \"%s\". Maximum pad number is %d\n",
++                              pad, entity->info.name, entity->info.pads - 1);
++              *endp = (char *)p;
++              return NULL;
++      }
++
++      for (p = end; isspace(*p); ++p);
++      *endp = (char *)p;
++
++      return &entity->pads[pad];
++}
++
++struct media_link *media_parse_link(struct media_device *media,
++                                  const char *p, char **endp)
++{
++      struct media_link *link;
++      struct media_pad *source;
++      struct media_pad *sink;
++      unsigned int i;
++      char *end;
++
++      source = media_parse_pad(media, p, &end);
++      if (source == NULL) {
++              *endp = end;
++              return NULL;
++      }
++
++      if (end[0] != '-' || end[1] != '>') {
++              *endp = end;
++              media_dbg(media, "Expected '->'\n");
++              return NULL;
++      }
++
++      p = end + 2;
++
++      sink = media_parse_pad(media, p, &end);
++      if (sink == NULL) {
++              *endp = end;
++              return NULL;
++      }
++
++      *endp = end;
++
++      for (i = 0; i < source->entity->num_links; i++) {
++              link = &source->entity->links[i];
++
++              if (link->source == source && link->sink == sink)
++                      return link;
++      }
++
++      media_dbg(media, "No link between \"%s\":%d and \"%s\":%d\n",
++                      source->entity->info.name, source->index,
++                      sink->entity->info.name, sink->index);
++      return NULL;
++}
++
++int media_parse_setup_link(struct media_device *media,
++                         const char *p, char **endp)
++{
++      struct media_link *link;
++      __u32 flags;
++      char *end;
++
++      link = media_parse_link(media, p, &end);
++      if (link == NULL) {
++              media_dbg(media,
++                        "%s: Unable to parse link\n", __func__);
++              *endp = end;
++              return -EINVAL;
++      }
++
++      p = end;
++      if (*p++ != '[') {
++              media_dbg(media, "Unable to parse link flags: expected '['.\n");
++              *endp = (char *)p - 1;
++              return -EINVAL;
++      }
++
++      flags = strtoul(p, &end, 10);
++      for (p = end; isspace(*p); p++);
++      if (*p++ != ']') {
++              media_dbg(media, "Unable to parse link flags: expected ']'.\n");
++              *endp = (char *)p - 1;
++              return -EINVAL;
++      }
++
++      for (; isspace(*p); p++);
++      *endp = (char *)p;
++
++      media_dbg(media,
++                "Setting up link %u:%u -> %u:%u [%u]\n",
++                link->source->entity->info.id, link->source->index,
++                link->sink->entity->info.id, link->sink->index,
++                flags);
++
++      return media_setup_link(media, link->source, link->sink, flags);
++}
++
++void media_print_streampos(struct media_device *media, const char *p,
++                         const char *end)
++{
++      int pos;
++
++      pos = end - p + 1;
++
++      if (pos < 0)
++              pos = 0;
++      if (pos > strlen(p))
++              pos = strlen(p);
++
++      media_dbg(media, "\n");
++      media_dbg(media, " %s\n", p);
++      media_dbg(media, " %*s\n", pos, "^");
++}
++
++int media_parse_setup_links(struct media_device *media, const char *p)
++{
++      char *end;
++      int ret;
++
++      do {
++              ret = media_parse_setup_link(media, p, &end);
++              if (ret < 0) {
++                      media_print_streampos(media, p, end);
++                      return ret;
++              }
++
++              p = end + 1;
++      } while (*end == ',');
++
++      return *end ? -EINVAL : 0;
++}
+diff --git a/libweston/media-ctl/libv4l2subdev.c b/libweston/media-ctl/libv4l2subdev.c
+new file mode 100644
+index 0000000..4ede4fa
+--- /dev/null
++++ b/libweston/media-ctl/libv4l2subdev.c
+@@ -0,0 +1,759 @@
++/*
++ * V4L2 subdev interface library
++ *
++ * Copyright (C) 2010-2014 Ideas on board SPRL
++ *
++ * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU Lesser General Public License as published
++ * by the Free Software Foundation; either version 2.1 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#include <sys/ioctl.h>
++#include <sys/stat.h>
++#include <sys/types.h>
++
++#include <ctype.h>
++#include <errno.h>
++#include <fcntl.h>
++#include <stdbool.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <unistd.h>
++
++#include <linux/v4l2-subdev.h>
++
++#include "mediactl.h"
++#include "mediactl-priv.h"
++#include "tools.h"
++#include "v4l2subdev.h"
++
++int v4l2_subdev_open(struct media_entity *entity)
++{
++      if (entity->fd != -1)
++              return 0;
++
++      entity->fd = open(entity->devname, O_RDWR);
++      if (entity->fd == -1) {
++              int ret = -errno;
++              media_dbg(entity->media,
++                        "%s: Failed to open subdev device node %s\n", __func__,
++                        entity->devname);
++              return ret;
++      }
++
++      return 0;
++}
++
++void v4l2_subdev_close(struct media_entity *entity)
++{
++      close(entity->fd);
++      entity->fd = -1;
++}
++
++int v4l2_subdev_get_format(struct media_entity *entity,
++      struct v4l2_mbus_framefmt *format, unsigned int pad,
++      enum v4l2_subdev_format_whence which)
++{
++      struct v4l2_subdev_format fmt;
++      int ret;
++
++      ret = v4l2_subdev_open(entity);
++      if (ret < 0)
++              return ret;
++
++      memset(&fmt, 0, sizeof(fmt));
++      fmt.pad = pad;
++      fmt.which = which;
++
++      ret = ioctl(entity->fd, VIDIOC_SUBDEV_G_FMT, &fmt);
++      if (ret < 0)
++              return -errno;
++
++      *format = fmt.format;
++      return 0;
++}
++
++int v4l2_subdev_set_format(struct media_entity *entity,
++      struct v4l2_mbus_framefmt *format, unsigned int pad,
++      enum v4l2_subdev_format_whence which)
++{
++      struct v4l2_subdev_format fmt;
++      int ret;
++
++      ret = v4l2_subdev_open(entity);
++      if (ret < 0)
++              return ret;
++
++      memset(&fmt, 0, sizeof(fmt));
++      fmt.pad = pad;
++      fmt.which = which;
++      fmt.format = *format;
++
++      ret = ioctl(entity->fd, VIDIOC_SUBDEV_S_FMT, &fmt);
++      if (ret < 0)
++              return -errno;
++
++      *format = fmt.format;
++      return 0;
++}
++
++int v4l2_subdev_get_selection(struct media_entity *entity,
++      struct v4l2_rect *rect, unsigned int pad, unsigned int target,
++      enum v4l2_subdev_format_whence which)
++{
++      union {
++              struct v4l2_subdev_selection sel;
++              struct v4l2_subdev_crop crop;
++      } u;
++      int ret;
++
++      ret = v4l2_subdev_open(entity);
++      if (ret < 0)
++              return ret;
++
++      memset(&u.sel, 0, sizeof(u.sel));
++      u.sel.pad = pad;
++      u.sel.target = target;
++      u.sel.which = which;
++
++      ret = ioctl(entity->fd, VIDIOC_SUBDEV_G_SELECTION, &u.sel);
++      if (ret >= 0) {
++              *rect = u.sel.r;
++              return 0;
++      }
++      if (errno != ENOTTY || target != V4L2_SEL_TGT_CROP)
++              return -errno;
++
++      memset(&u.crop, 0, sizeof(u.crop));
++      u.crop.pad = pad;
++      u.crop.which = which;
++
++      ret = ioctl(entity->fd, VIDIOC_SUBDEV_G_CROP, &u.crop);
++      if (ret < 0)
++              return -errno;
++
++      *rect = u.crop.rect;
++      return 0;
++}
++
++int v4l2_subdev_set_selection(struct media_entity *entity,
++      struct v4l2_rect *rect, unsigned int pad, unsigned int target,
++      enum v4l2_subdev_format_whence which)
++{
++      union {
++              struct v4l2_subdev_selection sel;
++              struct v4l2_subdev_crop crop;
++      } u;
++      int ret;
++
++      ret = v4l2_subdev_open(entity);
++      if (ret < 0)
++              return ret;
++
++      memset(&u.sel, 0, sizeof(u.sel));
++      u.sel.pad = pad;
++      u.sel.target = target;
++      u.sel.which = which;
++      u.sel.r = *rect;
++
++      ret = ioctl(entity->fd, VIDIOC_SUBDEV_S_SELECTION, &u.sel);
++      if (ret >= 0) {
++              *rect = u.sel.r;
++              return 0;
++      }
++      if (errno != ENOTTY || target != V4L2_SEL_TGT_CROP)
++              return -errno;
++
++      memset(&u.crop, 0, sizeof(u.crop));
++      u.crop.pad = pad;
++      u.crop.which = which;
++      u.crop.rect = *rect;
++
++      ret = ioctl(entity->fd, VIDIOC_SUBDEV_S_CROP, &u.crop);
++      if (ret < 0)
++              return -errno;
++
++      *rect = u.crop.rect;
++      return 0;
++}
++
++#if 0
++int v4l2_subdev_get_dv_timings_caps(struct media_entity *entity,
++      struct v4l2_dv_timings_cap *caps)
++{
++      unsigned int pad = caps->pad;
++      int ret;
++
++      ret = v4l2_subdev_open(entity);
++      if (ret < 0)
++              return ret;
++
++      memset(caps, 0, sizeof(*caps));
++      caps->pad = pad;
++
++      ret = ioctl(entity->fd, VIDIOC_SUBDEV_DV_TIMINGS_CAP, caps);
++      if (ret < 0)
++              return -errno;
++
++      return 0;
++}
++
++int v4l2_subdev_query_dv_timings(struct media_entity *entity,
++      struct v4l2_dv_timings *timings)
++{
++      int ret;
++
++      ret = v4l2_subdev_open(entity);
++      if (ret < 0)
++              return ret;
++
++      memset(timings, 0, sizeof(*timings));
++
++      ret = ioctl(entity->fd, VIDIOC_SUBDEV_QUERY_DV_TIMINGS, timings);
++      if (ret < 0)
++              return -errno;
++
++      return 0;
++}
++
++int v4l2_subdev_get_dv_timings(struct media_entity *entity,
++      struct v4l2_dv_timings *timings)
++{
++      int ret;
++
++      ret = v4l2_subdev_open(entity);
++      if (ret < 0)
++              return ret;
++
++      memset(timings, 0, sizeof(*timings));
++
++      ret = ioctl(entity->fd, VIDIOC_SUBDEV_G_DV_TIMINGS, timings);
++      if (ret < 0)
++              return -errno;
++
++      return 0;
++}
++
++int v4l2_subdev_set_dv_timings(struct media_entity *entity,
++      struct v4l2_dv_timings *timings)
++{
++      int ret;
++
++      ret = v4l2_subdev_open(entity);
++      if (ret < 0)
++              return ret;
++
++      ret = ioctl(entity->fd, VIDIOC_SUBDEV_S_DV_TIMINGS, timings);
++      if (ret < 0)
++              return -errno;
++
++      return 0;
++}
++#endif
++
++int v4l2_subdev_get_frame_interval(struct media_entity *entity,
++                                 struct v4l2_fract *interval)
++{
++      struct v4l2_subdev_frame_interval ival;
++      int ret;
++
++      ret = v4l2_subdev_open(entity);
++      if (ret < 0)
++              return ret;
++
++      memset(&ival, 0, sizeof(ival));
++
++      ret = ioctl(entity->fd, VIDIOC_SUBDEV_G_FRAME_INTERVAL, &ival);
++      if (ret < 0)
++              return -errno;
++
++      *interval = ival.interval;
++      return 0;
++}
++
++int v4l2_subdev_set_frame_interval(struct media_entity *entity,
++                                 struct v4l2_fract *interval)
++{
++      struct v4l2_subdev_frame_interval ival;
++      int ret;
++
++      ret = v4l2_subdev_open(entity);
++      if (ret < 0)
++              return ret;
++
++      memset(&ival, 0, sizeof(ival));
++      ival.interval = *interval;
++
++      ret = ioctl(entity->fd, VIDIOC_SUBDEV_S_FRAME_INTERVAL, &ival);
++      if (ret < 0)
++              return -errno;
++
++      *interval = ival.interval;
++      return 0;
++}
++
++static int v4l2_subdev_parse_format(struct media_device *media,
++                                  struct v4l2_mbus_framefmt *format,
++                                  const char *p, char **endp)
++{
++      enum v4l2_mbus_pixelcode code;
++      unsigned int width, height;
++      char *end;
++
++      /*
++       * Compatibility with the old syntax: consider space as valid
++       * separator between the media bus pixel code and the size.
++       */
++      for (; isspace(*p); ++p);
++      for (end = (char *)p;
++           *end != '/' && *end != ' ' && *end != '\0'; ++end);
++
++      code = v4l2_subdev_string_to_pixelcode(p, end - p);
++      if (code == (enum v4l2_mbus_pixelcode)-1) {
++              media_dbg(media, "Invalid pixel code '%.*s'\n", end - p, p);
++              return -EINVAL;
++      }
++
++      p = end + 1;
++      width = strtoul(p, &end, 10);
++      if (*end != 'x') {
++              media_dbg(media, "Expected 'x'\n");
++              return -EINVAL;
++      }
++
++      p = end + 1;
++      height = strtoul(p, &end, 10);
++      *endp = end;
++
++      memset(format, 0, sizeof(*format));
++      format->width = width;
++      format->height = height;
++      format->code = code;
++
++      return 0;
++}
++
++static int v4l2_subdev_parse_rectangle(struct media_device *media,
++                                     struct v4l2_rect *r, const char *p,
++                                     char **endp)
++{
++      char *end;
++
++      if (*p++ != '(') {
++              media_dbg(media, "Expected '('\n");
++              *endp = (char *)p - 1;
++              return -EINVAL;
++      }
++
++      r->left = strtoul(p, &end, 10);
++      if (*end != ',') {
++              media_dbg(media, "Expected ','\n");
++              *endp = end;
++              return -EINVAL;
++      }
++
++      p = end + 1;
++      r->top = strtoul(p, &end, 10);
++      if (*end++ != ')') {
++              media_dbg(media, "Expected ')'\n");
++              *endp = end - 1;
++              return -EINVAL;
++      }
++      if (*end != '/') {
++              media_dbg(media, "Expected '/'\n");
++              *endp = end;
++              return -EINVAL;
++      }
++
++      p = end + 1;
++      r->width = strtoul(p, &end, 10);
++      if (*end != 'x') {
++              media_dbg(media, "Expected 'x'\n");
++              *endp = end;
++              return -EINVAL;
++      }
++
++      p = end + 1;
++      r->height = strtoul(p, &end, 10);
++      *endp = end;
++
++      return 0;
++}
++
++static int v4l2_subdev_parse_frame_interval(struct media_device *media,
++                                          struct v4l2_fract *interval,
++                                          const char *p, char **endp)
++{
++      char *end;
++
++      for (; isspace(*p); ++p);
++
++      interval->numerator = strtoul(p, &end, 10);
++
++      for (p = end; isspace(*p); ++p);
++      if (*p++ != '/') {
++              media_dbg(media, "Expected '/'\n");
++              *endp = (char *)p - 1;
++              return -EINVAL;
++      }
++
++      for (; isspace(*p); ++p);
++      interval->denominator = strtoul(p, &end, 10);
++
++      *endp = end;
++      return 0;
++}
++
++/*
++ * The debate over whether this function should be named icanhasstr() instead
++ * has been strong and heated. If you feel like this would be an important
++ * change, patches are welcome (or not).
++ */
++static bool strhazit(const char *str, const char **p)
++{
++      int len = strlen(str);
++
++      if (strncmp(str, *p, len))
++              return false;
++
++      for (*p += len; isspace(**p); ++*p);
++      return true;
++}
++
++static struct media_pad *v4l2_subdev_parse_pad_format(
++      struct media_device *media, struct v4l2_mbus_framefmt *format,
++      struct v4l2_rect *crop, struct v4l2_rect *compose,
++      struct v4l2_fract *interval, const char *p, char **endp)
++{
++      struct media_pad *pad;
++      bool first;
++      char *end;
++      int ret;
++
++      for (; isspace(*p); ++p);
++
++      pad = media_parse_pad(media, p, &end);
++      if (pad == NULL) {
++              *endp = end;
++              return NULL;
++      }
++
++      for (p = end; isspace(*p); ++p);
++      if (*p++ != '[') {
++              media_dbg(media, "Expected '['\n");
++              *endp = (char *)p - 1;
++              return NULL;
++      }
++
++      for (first = true; ; first = false) {
++              for (; isspace(*p); p++);
++
++              /*
++               * Backward compatibility: if the first property starts with an
++               * uppercase later, process it as a format description.
++               */
++              if (strhazit("fmt:", &p) || (first && isupper(*p))) {
++                      ret = v4l2_subdev_parse_format(media, format, p, &end);
++                      if (ret < 0) {
++                              *endp = end;
++                              return NULL;
++                      }
++
++                      p = end;
++                      continue;
++              }
++
++              /*
++               * Backward compatibility: crop rectangles can be specified
++               * implicitly without the 'crop:' property name.
++               */
++              if (strhazit("crop:", &p) || *p == '(') {
++                      ret = v4l2_subdev_parse_rectangle(media, crop, p, &end);
++                      if (ret < 0) {
++                              *endp = end;
++                              return NULL;
++                      }
++
++                      p = end;
++                      continue;
++              }
++
++              if (strhazit("compose:", &p)) {
++                      ret = v4l2_subdev_parse_rectangle(media, compose, p, &end);
++                      if (ret < 0) {
++                              *endp = end;
++                              return NULL;
++                      }
++
++                      for (p = end; isspace(*p); p++);
++                      continue;
++              }
++
++              if (*p == '@') {
++                      ret = v4l2_subdev_parse_frame_interval(media, interval, ++p, &end);
++                      if (ret < 0) {
++                              *endp = end;
++                              return NULL;
++                      }
++
++                      p = end;
++                      continue;
++              }
++
++              break;
++      }
++
++      if (*p != ']') {
++              media_dbg(media, "Expected ']'\n");
++              *endp = (char *)p;
++              return NULL;
++      }
++
++      *endp = (char *)p + 1;
++      return pad;
++}
++
++static int set_format(struct media_pad *pad,
++                    struct v4l2_mbus_framefmt *format)
++{
++      int ret;
++
++      if (format->width == 0 || format->height == 0)
++              return 0;
++
++      media_dbg(pad->entity->media,
++                "Setting up format %s %ux%u on pad %s/%u\n",
++                v4l2_subdev_pixelcode_to_string(format->code),
++                format->width, format->height,
++                pad->entity->info.name, pad->index);
++
++      ret = v4l2_subdev_set_format(pad->entity, format, pad->index,
++                                   V4L2_SUBDEV_FORMAT_ACTIVE);
++      if (ret < 0) {
++              media_dbg(pad->entity->media,
++                        "Unable to set format: %s (%d)\n",
++                        strerror(-ret), ret);
++              return ret;
++      }
++
++      media_dbg(pad->entity->media,
++                "Format set: %s %ux%u\n",
++                v4l2_subdev_pixelcode_to_string(format->code),
++                format->width, format->height);
++
++      return 0;
++}
++
++static int set_selection(struct media_pad *pad, unsigned int target,
++                       struct v4l2_rect *rect)
++{
++      int ret;
++
++      if (rect->left == -1 || rect->top == -1)
++              return 0;
++
++      media_dbg(pad->entity->media,
++                "Setting up selection target %u rectangle (%u,%u)/%ux%u on pad %s/%u\n",
++                target, rect->left, rect->top, rect->width, rect->height,
++                pad->entity->info.name, pad->index);
++
++      ret = v4l2_subdev_set_selection(pad->entity, rect, pad->index,
++                                      target, V4L2_SUBDEV_FORMAT_ACTIVE);
++      if (ret < 0) {
++              media_dbg(pad->entity->media,
++                        "Unable to set selection rectangle: %s (%d)\n",
++                        strerror(-ret), ret);
++              return ret;
++      }
++
++      media_dbg(pad->entity->media,
++                "Selection rectangle set: (%u,%u)/%ux%u\n",
++                rect->left, rect->top, rect->width, rect->height);
++
++      return 0;
++}
++
++static int set_frame_interval(struct media_entity *entity,
++                            struct v4l2_fract *interval)
++{
++      int ret;
++
++      if (interval->numerator == 0)
++              return 0;
++
++      media_dbg(entity->media,
++                "Setting up frame interval %u/%u on entity %s\n",
++                interval->numerator, interval->denominator,
++                entity->info.name);
++
++      ret = v4l2_subdev_set_frame_interval(entity, interval);
++      if (ret < 0) {
++              media_dbg(entity->media,
++                        "Unable to set frame interval: %s (%d)",
++                        strerror(-ret), ret);
++              return ret;
++      }
++
++      media_dbg(entity->media, "Frame interval set: %u/%u\n",
++                interval->numerator, interval->denominator);
++
++      return 0;
++}
++
++
++static int v4l2_subdev_parse_setup_format(struct media_device *media,
++                                        const char *p, char **endp)
++{
++      struct v4l2_mbus_framefmt format = { 0, 0, 0 };
++      struct media_pad *pad;
++      struct v4l2_rect crop = { -1, -1, -1, -1 };
++      struct v4l2_rect compose = crop;
++      struct v4l2_fract interval = { 0, 0 };
++      unsigned int i;
++      char *end;
++      int ret;
++
++      pad = v4l2_subdev_parse_pad_format(media, &format, &crop, &compose,
++                                         &interval, p, &end);
++      if (pad == NULL) {
++              media_print_streampos(media, p, end);
++              media_dbg(media, "Unable to parse format\n");
++              return -EINVAL;
++      }
++
++      if (pad->flags & MEDIA_PAD_FL_SINK) {
++              ret = set_format(pad, &format);
++              if (ret < 0)
++                      return ret;
++      }
++
++      ret = set_selection(pad, V4L2_SEL_TGT_CROP, &crop);
++      if (ret < 0)
++              return ret;
++
++      ret = set_selection(pad, V4L2_SEL_TGT_COMPOSE, &compose);
++      if (ret < 0)
++              return ret;
++
++      if (pad->flags & MEDIA_PAD_FL_SOURCE) {
++              ret = set_format(pad, &format);
++              if (ret < 0)
++                      return ret;
++      }
++
++      ret = set_frame_interval(pad->entity, &interval);
++      if (ret < 0)
++              return ret;
++
++
++      /* If the pad is an output pad, automatically set the same format on
++       * the remote subdev input pads, if any.
++       */
++      if (pad->flags & MEDIA_PAD_FL_SOURCE) {
++              for (i = 0; i < pad->entity->num_links; ++i) {
++                      struct media_link *link = &pad->entity->links[i];
++                      struct v4l2_mbus_framefmt remote_format;
++
++                      if (!(link->flags & MEDIA_LNK_FL_ENABLED))
++                              continue;
++
++                      if (link->source == pad &&
++                          link->sink->entity->info.type == MEDIA_ENT_T_V4L2_SUBDEV) {
++                              remote_format = format;
++                              set_format(link->sink, &remote_format);
++                      }
++              }
++      }
++
++      *endp = end;
++      return 0;
++}
++
++int v4l2_subdev_parse_setup_formats(struct media_device *media, const char *p)
++{
++      char *end;
++      int ret;
++
++      do {
++              ret = v4l2_subdev_parse_setup_format(media, p, &end);
++              if (ret < 0)
++                      return ret;
++
++              p = end + 1;
++      } while (*end == ',');
++
++      return *end ? -EINVAL : 0;
++}
++
++static struct {
++      const char *name;
++      enum v4l2_mbus_pixelcode code;
++} mbus_formats[] = {
++      { "Y8", V4L2_MBUS_FMT_Y8_1X8},
++      { "Y10", V4L2_MBUS_FMT_Y10_1X10 },
++      { "Y12", V4L2_MBUS_FMT_Y12_1X12 },
++      { "YUYV", V4L2_MBUS_FMT_YUYV8_1X16 },
++      { "YUYV1_5X8", V4L2_MBUS_FMT_YUYV8_1_5X8 },
++      { "YUYV2X8", V4L2_MBUS_FMT_YUYV8_2X8 },
++      { "UYVY", V4L2_MBUS_FMT_UYVY8_1X16 },
++      { "UYVY1_5X8", V4L2_MBUS_FMT_UYVY8_1_5X8 },
++      { "UYVY2X8", V4L2_MBUS_FMT_UYVY8_2X8 },
++      { "SBGGR8", V4L2_MBUS_FMT_SBGGR8_1X8 },
++      { "SGBRG8", V4L2_MBUS_FMT_SGBRG8_1X8 },
++      { "SGRBG8", V4L2_MBUS_FMT_SGRBG8_1X8 },
++      { "SRGGB8", V4L2_MBUS_FMT_SRGGB8_1X8 },
++      { "SBGGR10", V4L2_MBUS_FMT_SBGGR10_1X10 },
++      { "SGBRG10", V4L2_MBUS_FMT_SGBRG10_1X10 },
++      { "SGRBG10", V4L2_MBUS_FMT_SGRBG10_1X10 },
++      { "SRGGB10", V4L2_MBUS_FMT_SRGGB10_1X10 },
++      { "SBGGR10_DPCM8", V4L2_MBUS_FMT_SBGGR10_DPCM8_1X8 },
++      { "SGBRG10_DPCM8", V4L2_MBUS_FMT_SGBRG10_DPCM8_1X8 },
++      { "SGRBG10_DPCM8", V4L2_MBUS_FMT_SGRBG10_DPCM8_1X8 },
++      { "SRGGB10_DPCM8", V4L2_MBUS_FMT_SRGGB10_DPCM8_1X8 },
++      { "SBGGR12", V4L2_MBUS_FMT_SBGGR12_1X12 },
++      { "SGBRG12", V4L2_MBUS_FMT_SGBRG12_1X12 },
++      { "SGRBG12", V4L2_MBUS_FMT_SGRBG12_1X12 },
++      { "SRGGB12", V4L2_MBUS_FMT_SRGGB12_1X12 },
++      { "AYUV32", V4L2_MBUS_FMT_AYUV8_1X32 },
++      { "ARGB32", V4L2_MBUS_FMT_ARGB8888_1X32 },
++};
++
++const char *v4l2_subdev_pixelcode_to_string(enum v4l2_mbus_pixelcode code)
++{
++      unsigned int i;
++
++      for (i = 0; i < ARRAY_SIZE(mbus_formats); ++i) {
++              if (mbus_formats[i].code == code)
++                      return mbus_formats[i].name;
++      }
++
++      return "unknown";
++}
++
++enum v4l2_mbus_pixelcode v4l2_subdev_string_to_pixelcode(const char *string,
++                                                       unsigned int length)
++{
++      unsigned int i;
++
++      for (i = 0; i < ARRAY_SIZE(mbus_formats); ++i) {
++              if (strncmp(mbus_formats[i].name, string, length) == 0)
++                      break;
++      }
++
++      if (i == ARRAY_SIZE(mbus_formats))
++              return (enum v4l2_mbus_pixelcode)-1;
++
++      return mbus_formats[i].code;
++}
+diff --git a/libweston/media-ctl/mediactl-priv.h b/libweston/media-ctl/mediactl-priv.h
+new file mode 100644
+index 0000000..a0d3a55
+--- /dev/null
++++ b/libweston/media-ctl/mediactl-priv.h
+@@ -0,0 +1,64 @@
++/*
++ * Media controller interface library
++ *
++ * Copyright (C) 2010-2014 Ideas on board SPRL
++ *
++ * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU Lesser General Public License as published
++ * by the Free Software Foundation; either version 2.1 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#ifndef __MEDIA_PRIV_H__
++#define __MEDIA_PRIV_H__
++
++#include <linux/media.h>
++
++#include "mediactl.h"
++
++struct media_entity {
++      struct media_device *media;
++      struct media_entity_desc info;
++      struct media_pad *pads;
++      struct media_link *links;
++      unsigned int max_links;
++      unsigned int num_links;
++
++      char devname[32];
++      int fd;
++};
++
++struct media_device {
++      int fd;
++      int refcount;
++      char *devnode;
++
++      struct media_device_info info;
++      struct media_entity *entities;
++      unsigned int entities_count;
++
++      void (*debug_handler)(void *, ...);
++      void *debug_priv;
++
++      struct {
++              struct media_entity *v4l;
++              struct media_entity *fb;
++              struct media_entity *alsa;
++              struct media_entity *dvb;
++      } def;
++};
++
++#define media_dbg(media, ...) \
++      (media)->debug_handler((media)->debug_priv, __VA_ARGS__)
++
++#endif /* __MEDIA_PRIV_H__ */
+diff --git a/libweston/media-ctl/mediactl.h b/libweston/media-ctl/mediactl.h
+new file mode 100644
+index 0000000..77ac182
+--- /dev/null
++++ b/libweston/media-ctl/mediactl.h
+@@ -0,0 +1,423 @@
++/*
++ * Media controller interface library
++ *
++ * Copyright (C) 2010-2014 Ideas on board SPRL
++ *
++ * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU Lesser General Public License as published
++ * by the Free Software Foundation; either version 2.1 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#ifndef __MEDIA_H__
++#define __MEDIA_H__
++
++#include <linux/media.h>
++
++struct media_link {
++      struct media_pad *source;
++      struct media_pad *sink;
++      struct media_link *twin;
++      __u32 flags;
++      __u32 padding[3];
++};
++
++struct media_pad {
++      struct media_entity *entity;
++      __u32 index;
++      __u32 flags;
++      __u32 padding[3];
++};
++
++struct media_device;
++struct media_entity;
++
++/**
++ * @brief Create a new media device.
++ * @param devnode - device node path.
++ *
++ * Create a media device instance for the given device node and return it. The
++ * device node is not accessed by this function, device node access errors will
++ * not be caught and reported here. The media device needs to be enumerated
++ * before it can be accessed, see media_device_enumerate().
++ *
++ * Media devices are reference-counted, see media_device_ref() and
++ * media_device_unref() for more information.
++ *
++ * @return A pointer to the new media device or NULL if memory cannot be
++ * allocated.
++ */
++struct media_device *media_device_new(const char *devnode);
++
++/**
++ * @brief Create a new emulated media device.
++ * @param info - device information.
++ *
++ * Emulated media devices are userspace-only objects not backed by a kernel
++ * media device. They are created for ALSA and V4L2 devices that are not
++ * associated with a media controller device.
++ *
++ * Only device query functions are available for media devices. Enumerating or
++ * setting up links is invalid.
++ *
++ * @return A pointer to the new media device or NULL if memory cannot be
++ * allocated.
++ */
++struct media_device *media_device_new_emulated(struct media_device_info *info);
++
++/**
++ * @brief Take a reference to the device.
++ * @param media - device instance.
++ *
++ * Media devices are reference-counted. Taking a reference to a device prevents
++ * it from being freed until all references are released. The reference count is
++ * initialized to 1 when the device is created.
++ *
++ * @return A pointer to @a media.
++ */
++struct media_device *media_device_ref(struct media_device *media);
++
++/**
++ * @brief Release a reference to the device.
++ * @param media - device instance.
++ *
++ * Release a reference to the media device. When the reference count reaches 0
++ * this function frees the device.
++ */
++void media_device_unref(struct media_device *media);
++
++/**
++ * @brief Add an entity to an existing media device
++ * @param media - device instance.
++ * @param desc - description of the entity to be added
++ * @param devnode - device node corresponding to the entity
++ *
++ * Entities are usually created and added to media devices automatically when
++ * the media device is enumerated through the media controller API. However,
++ * when an emulated media device (thus not backed with a kernel-side media
++ * controller device) is created, entities need to be manually added.
++ *
++ * Entities can also be manually added to a successfully enumerated media device
++ * to group several functions provided by separate kernel devices. The most
++ * common use case is to group the audio and video functions of a USB webcam in
++ * a single media device. Those functions are exposed through separate USB
++ * interfaces and handled through unrelated kernel drivers, they must thus be
++ * manually added to the same media device.
++ *
++ * This function adds a new entity to the given media device and initializes it
++ * from the given entity description and device node name. Only the following
++ * fields of the description are copied over to the new entity:
++ *
++ * - type
++ * - flags (MEDIA_ENT_FL_DEFAULT only)
++ * - name
++ * - v4l, fb, alsa or dvb (depending on the device type)
++ *
++ * All other fields of the newly created entity id are initialized to 0,
++ * including the entity ID.
++ *
++ * @return Zero on success or -ENOMEM if memory cannot be allocated.
++ */
++int media_device_add_entity(struct media_device *media,
++                          const struct media_entity_desc *desc,
++                          const char *devnode);
++
++/**
++ * @brief Set a handler for debug messages.
++ * @param media - device instance.
++ * @param debug_handler - debug message handler
++ * @param debug_priv - first argument to debug message handler
++ *
++ * Set a handler for debug messages that will be called whenever
++ * debugging information is to be printed. The handler expects an
++ * fprintf-like function.
++ */
++void media_debug_set_handler(
++      struct media_device *media, void (*debug_handler)(void *, ...),
++      void *debug_priv);
++
++/**
++ * @brief Enumerate the device topology
++ * @param media - device instance.
++ *
++ * Enumerate the media device entities, pads and links. Calling this function is
++ * mandatory before accessing the media device contents.
++ *
++ * @return Zero on success or a negative error code on failure.
++ */
++int media_device_enumerate(struct media_device *media);
++
++/**
++ * @brief Locate the pad at the other end of a link.
++ * @param pad - sink pad at one end of the link.
++ *
++ * Locate the source pad connected to @a pad through an enabled link. As only one
++ * link connected to a sink pad can be enabled at a time, the connected source
++ * pad is guaranteed to be unique.
++ *
++ * @return A pointer to the connected source pad, or NULL if all links connected
++ * to @a pad are disabled. Return NULL also if @a pad is not a sink pad.
++ */
++struct media_pad *media_entity_remote_source(struct media_pad *pad);
++
++/**
++ * @brief Get information about a media entity
++ * @param entity - media entity.
++ *
++ * The information structure is owned by the media entity object and will be
++ * freed when the object is destroyed.
++ *
++ * @return A pointer to the media entity information
++ */
++const struct media_entity_desc *media_entity_get_info(struct media_entity *entity);
++
++/**
++ * @brief Get an entity pad
++ * @param entity - media entity.
++ * @param index - pad index.
++ *
++ * This function returns a pointer to the pad object identified by its index
++ * for the given entity. If the pad index is out of bounds it will return NULL.
++ *
++ * @return A pointer to the pad
++ */
++const struct media_pad *media_entity_get_pad(struct media_entity *entity,
++                                           unsigned int index);
++
++/**
++ * @brief Get the number of links
++ * @param entity - media entity.
++ *
++ * This function returns the total number of links that originate from or arrive
++ * at the the media entity.
++ *
++ * @return The number of links for the entity
++ */
++unsigned int media_entity_get_links_count(struct media_entity *entity);
++
++/**
++ * @brief Get an entity link
++ * @param entity - media entity.
++ * @param index - link index.
++ *
++ * This function returns a pointer to the link object identified by its index
++ * for the given entity. If the link index is out of bounds it will return NULL.
++ *
++ * @return A pointer to the link
++ */
++const struct media_link *media_entity_get_link(struct media_entity *entity,
++                                             unsigned int index);
++
++/**
++ * @brief Get the device node name for an entity
++ * @param entity - media entity.
++ *
++ * This function returns the full path and name to the device node corresponding
++ * to the given entity.
++ *
++ * @return A pointer to the device node name or NULL if the entity has no
++ * associated device node
++ */
++const char *media_entity_get_devname(struct media_entity *entity);
++
++/**
++ * @brief Get the type of an entity.
++ * @param entity - the entity.
++ *
++ * @return The type of @a entity.
++ */
++static inline unsigned int media_entity_type(struct media_entity *entity)
++{
++      return media_entity_get_info(entity)->type & MEDIA_ENT_TYPE_MASK;
++}
++
++/**
++ * @brief Find an entity by its name.
++ * @param media - media device.
++ * @param name - entity name.
++ * @param length - size of @a name.
++ *
++ * Search for an entity with a name equal to @a name.
++ *
++ * @return A pointer to the entity if found, or NULL otherwise.
++ */
++struct media_entity *media_get_entity_by_name(struct media_device *media,
++      const char *name, size_t length);
++
++/**
++ * @brief Find an entity by its ID.
++ * @param media - media device.
++ * @param id - entity ID.
++ *
++ * This function searches for an entity based on its ID using an exact match or
++ * next ID method based on the given @a id. If @a id is ORed with
++ * MEDIA_ENT_ID_FLAG_NEXT, the function will return the entity with the smallest
++ * ID larger than @a id. Otherwise it will return the entity with an ID equal to
++ * @a id.
++ *
++ * @return A pointer to the entity if found, or NULL otherwise.
++ */
++struct media_entity *media_get_entity_by_id(struct media_device *media,
++      __u32 id);
++
++/**
++ * @brief Get the number of entities
++ * @param media - media device.
++ *
++ * This function returns the total number of entities in the media device. If
++ * entities haven't been enumerated yet it will return 0.
++ *
++ * @return The number of entities in the media device
++ */
++unsigned int media_get_entities_count(struct media_device *media);
++
++/**
++ * @brief Get the entities
++ * @param media - media device.
++ *
++ * This function returns a pointer to the array of entities for the media
++ * device. If entities haven't been enumerated yet it will return NULL.
++ *
++ * The array of entities is owned by the media device object and will be freed
++ * when the media object is destroyed.
++ *
++ * @return A pointer to an array of entities
++ */
++struct media_entity *media_get_entity(struct media_device *media, unsigned int index);
++
++/**
++ * @brief Get the default entity for a given type
++ * @param media - media device.
++ * @param type - entity type.
++ *
++ * This function returns the default entity of the requested type. @a type must
++ * be one of
++ *
++ *    MEDIA_ENT_T_DEVNODE_V4L
++ *    MEDIA_ENT_T_DEVNODE_FB
++ *    MEDIA_ENT_T_DEVNODE_ALSA
++ *    MEDIA_ENT_T_DEVNODE_DVB
++ *
++ * @return A pointer to the default entity for the type if it exists, or NULL
++ * otherwise.
++ */
++struct media_entity *media_get_default_entity(struct media_device *media,
++                                            unsigned int type);
++
++/**
++ * @brief Get the media device information
++ * @param media - media device.
++ *
++ * The information structure is owned by the media device object and will be freed
++ * when the media object is destroyed.
++ *
++ * @return A pointer to the media device information
++ */
++const struct media_device_info *media_get_info(struct media_device *media);
++
++/**
++ * @brief Get the media device node name
++ * @param media - media device.
++ *
++ * The device node name string is owned by the media device object and will be
++ * freed when the media object is destroyed.
++ *
++ * @return A pointer to the media device node name
++ */
++const char *media_get_devnode(struct media_device *media);
++
++/**
++ * @brief Configure a link.
++ * @param media - media device.
++ * @param source - source pad at the link origin.
++ * @param sink - sink pad at the link target.
++ * @param flags - configuration flags.
++ *
++ * Locate the link between @a source and @a sink, and configure it by applying
++ * the new @a flags.
++ *
++ * Only the MEDIA_LINK_FLAG_ENABLED flag is writable.
++ *
++ * @return 0 on success, -1 on failure:
++ *       -ENOENT: link not found
++ *       - other error codes returned by MEDIA_IOC_SETUP_LINK
++ */
++int media_setup_link(struct media_device *media,
++      struct media_pad *source, struct media_pad *sink,
++      __u32 flags);
++
++/**
++ * @brief Reset all links to the disabled state.
++ * @param media - media device.
++ *
++ * Disable all links in the media device. This function is usually used after
++ * opening a media device to reset all links to a known state.
++ *
++ * @return 0 on success, or a negative error code on failure.
++ */
++int media_reset_links(struct media_device *media);
++
++/**
++ * @brief Parse string to a pad on the media device.
++ * @param media - media device.
++ * @param p - input string
++ * @param endp - pointer to string where parsing ended
++ *
++ * Parse NULL terminated string describing a pad and return its struct
++ * media_pad instance.
++ *
++ * @return Pointer to struct media_pad on success, NULL on failure.
++ */
++struct media_pad *media_parse_pad(struct media_device *media,
++                                const char *p, char **endp);
++
++/**
++ * @brief Parse string to a link on the media device.
++ * @param media - media device.
++ * @param p - input string
++ * @param endp - pointer to p where parsing ended
++ *
++ * Parse NULL terminated string p describing a link and return its struct
++ * media_link instance.
++ *
++ * @return Pointer to struct media_link on success, NULL on failure.
++ */
++struct media_link *media_parse_link(struct media_device *media,
++                                  const char *p, char **endp);
++
++/**
++ * @brief Parse string to a link on the media device and set it up.
++ * @param media - media device.
++ * @param p - input string
++ *
++ * Parse NULL terminated string p describing a link and its configuration
++ * and configure the link.
++ *
++ * @return 0 on success, or a negative error code on failure.
++ */
++int media_parse_setup_link(struct media_device *media,
++                         const char *p, char **endp);
++
++/**
++ * @brief Parse string to link(s) on the media device and set it up.
++ * @param media - media device.
++ * @param p - input string
++ *
++ * Parse NULL terminated string p describing link(s) separated by
++ * commas (,) and configure the link(s).
++ *
++ * @return 0 on success, or a negative error code on failure.
++ */
++int media_parse_setup_links(struct media_device *media, const char *p);
++
++#endif
+diff --git a/libweston/media-ctl/tools.h b/libweston/media-ctl/tools.h
+new file mode 100644
+index 0000000..815534c
+--- /dev/null
++++ b/libweston/media-ctl/tools.h
+@@ -0,0 +1,32 @@
++/*
++ * Media controller test application
++ *
++ * Copyright (C) 2010-2014 Ideas on board SPRL
++ *
++ * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU Lesser General Public License as published
++ * by the Free Software Foundation; either version 2.1 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#ifndef __TOOLS_H__
++#define __TOOLS_H__
++
++#define ARRAY_SIZE(array)     (sizeof(array) / sizeof((array)[0]))
++#define FIELD_SIZEOF(t, f)    (sizeof(((t*)0)->f))
++
++void media_print_streampos(struct media_device *media, const char *p,
++                         const char *end);
++
++#endif /* __TOOLS_H__ */
++
+diff --git a/libweston/media-ctl/v4l2subdev.h b/libweston/media-ctl/v4l2subdev.h
+new file mode 100644
+index 0000000..1cb53ff
+--- /dev/null
++++ b/libweston/media-ctl/v4l2subdev.h
+@@ -0,0 +1,258 @@
++/*
++ * V4L2 subdev interface library
++ *
++ * Copyright (C) 2010-2014 Ideas on board SPRL
++ *
++ * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU Lesser General Public License as published
++ * by the Free Software Foundation; either version 2.1 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ * GNU Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#ifndef __SUBDEV_H__
++#define __SUBDEV_H__
++
++#include <linux/v4l2-subdev.h>
++
++struct media_entity;
++
++/**
++ * @brief Open a sub-device.
++ * @param entity - sub-device media entity.
++ *
++ * Open the V4L2 subdev device node associated with @a entity. The file
++ * descriptor is stored in the media_entity structure.
++ *
++ * @return 0 on success, or a negative error code on failure.
++ */
++int v4l2_subdev_open(struct media_entity *entity);
++
++/**
++ * @brief Close a sub-device.
++ * @param entity - sub-device media entity.
++ *
++ * Close the V4L2 subdev device node associated with the @a entity and opened by
++ * a previous call to v4l2_subdev_open() (either explicit or implicit).
++ */
++void v4l2_subdev_close(struct media_entity *entity);
++
++/**
++ * @brief Retrieve the format on a pad.
++ * @param entity - subdev-device media entity.
++ * @param format - format to be filled.
++ * @param pad - pad number.
++ * @param which - identifier of the format to get.
++ *
++ * Retrieve the current format on the @a entity @a pad and store it in the
++ * @a format structure.
++ *
++ * @a which is set to V4L2_SUBDEV_FORMAT_TRY to retrieve the try format stored
++ * in the file handle, of V4L2_SUBDEV_FORMAT_ACTIVE to retrieve the current
++ * active format.
++ *
++ * @return 0 on success, or a negative error code on failure.
++ */
++int v4l2_subdev_get_format(struct media_entity *entity,
++      struct v4l2_mbus_framefmt *format, unsigned int pad,
++      enum v4l2_subdev_format_whence which);
++
++/**
++ * @brief Set the format on a pad.
++ * @param entity - subdev-device media entity.
++ * @param format - format.
++ * @param pad - pad number.
++ * @param which - identifier of the format to set.
++ *
++ * Set the format on the @a entity @a pad to @a format. The driver is allowed to
++ * modify the requested format, in which case @a format is updated with the
++ * modifications.
++ *
++ * @a which is set to V4L2_SUBDEV_FORMAT_TRY to set the try format stored in the
++ * file handle, of V4L2_SUBDEV_FORMAT_ACTIVE to configure the device with an
++ * active format.
++ *
++ * @return 0 on success, or a negative error code on failure.
++ */
++int v4l2_subdev_set_format(struct media_entity *entity,
++      struct v4l2_mbus_framefmt *format, unsigned int pad,
++      enum v4l2_subdev_format_whence which);
++
++/**
++ * @brief Retrieve a selection rectangle on a pad.
++ * @param entity - subdev-device media entity.
++ * @param r - rectangle to be filled.
++ * @param pad - pad number.
++ * @param target - selection target
++ * @param which - identifier of the format to get.
++ *
++ * Retrieve the @a target selection rectangle on the @a entity @a pad
++ * and store it in the @a rect structure.
++ *
++ * @a which is set to V4L2_SUBDEV_FORMAT_TRY to retrieve the try
++ * selection rectangle stored in the file handle, or
++ * V4L2_SUBDEV_FORMAT_ACTIVE to retrieve the current active selection
++ * rectangle.
++ *
++ * @return 0 on success, or a negative error code on failure.
++ */
++int v4l2_subdev_get_selection(struct media_entity *entity,
++      struct v4l2_rect *rect, unsigned int pad, unsigned int target,
++      enum v4l2_subdev_format_whence which);
++
++/**
++ * @brief Set a selection rectangle on a pad.
++ * @param entity - subdev-device media entity.
++ * @param rect - crop rectangle.
++ * @param pad - pad number.
++ * @param target - selection target
++ * @param which - identifier of the format to set.
++ *
++ * Set the @a target selection rectangle on the @a entity @a pad to @a
++ * rect. The driver is allowed to modify the requested rectangle, in
++ * which case @a rect is updated with the modifications.
++ *
++ * @a which is set to V4L2_SUBDEV_FORMAT_TRY to set the try crop rectangle
++ * stored in the file handle, of V4L2_SUBDEV_FORMAT_ACTIVE to configure the
++ * device with an active crop rectangle.
++ *
++ * @return 0 on success, or a negative error code on failure.
++ */
++int v4l2_subdev_set_selection(struct media_entity *entity,
++      struct v4l2_rect *rect, unsigned int pad, unsigned int target,
++      enum v4l2_subdev_format_whence which);
++
++/**
++ * @brief Query the digital video capabilities of a pad.
++ * @param entity - subdev-device media entity.
++ * @param cap - capabilities to be filled.
++ *
++ * Retrieve the digital video capabilities of the @a entity pad specified by
++ * @a cap.pad and store it in the @a cap structure.
++ *
++ * @return 0 on success, or a negative error code on failure.
++ */
++int v4l2_subdev_get_dv_timings_caps(struct media_entity *entity,
++      struct v4l2_dv_timings_cap *caps);
++
++/**
++ * @brief Query the digital video timings of a sub-device
++ * @param entity - subdev-device media entity.
++ * @param timings timings to be filled.
++ *
++ * Retrieve the detected digital video timings for the currently selected input
++ * of @a entity and store them in the @a timings structure.
++ *
++ * @return 0 on success, or a negative error code on failure.
++ */
++int v4l2_subdev_query_dv_timings(struct media_entity *entity,
++      struct v4l2_dv_timings *timings);
++
++/**
++ * @brief Get the current digital video timings of a sub-device
++ * @param entity - subdev-device media entity.
++ * @param timings timings to be filled.
++ *
++ * Retrieve the current digital video timings for the currently selected input
++ * of @a entity and store them in the @a timings structure.
++ *
++ * @return 0 on success, or a negative error code on failure.
++ */
++int v4l2_subdev_get_dv_timings(struct media_entity *entity,
++      struct v4l2_dv_timings *timings);
++
++/**
++ * @brief Set the digital video timings of a sub-device
++ * @param entity - subdev-device media entity.
++ * @param timings timings to be set.
++ *
++ * Set the digital video timings of @a entity to @a timings. The driver is
++ * allowed to modify the requested format, in which case @a timings is updated
++ * with the modifications.
++ *
++ * @return 0 on success, or a negative error code on failure.
++ */
++int v4l2_subdev_set_dv_timings(struct media_entity *entity,
++      struct v4l2_dv_timings *timings);
++
++/**
++ * @brief Retrieve the frame interval on a sub-device.
++ * @param entity - subdev-device media entity.
++ * @param interval - frame interval to be filled.
++ *
++ * Retrieve the current frame interval on subdev @a entity and store it in the
++ * @a interval structure.
++ *
++ * Frame interval retrieving is usually supported only on devices at the
++ * beginning of video pipelines, such as sensors.
++ *
++ * @return 0 on success, or a negative error code on failure.
++ */
++
++int v4l2_subdev_get_frame_interval(struct media_entity *entity,
++      struct v4l2_fract *interval);
++
++/**
++ * @brief Set the frame interval on a sub-device.
++ * @param entity - subdev-device media entity.
++ * @param interval - frame interval.
++ *
++ * Set the frame interval on subdev @a entity to @a interval. The driver is
++ * allowed to modify the requested frame interval, in which case @a interval is
++ * updated with the modifications.
++ *
++ * Frame interval setting is usually supported only on devices at the beginning
++ * of video pipelines, such as sensors.
++ *
++ * @return 0 on success, or a negative error code on failure.
++ */
++int v4l2_subdev_set_frame_interval(struct media_entity *entity,
++      struct v4l2_fract *interval);
++
++/**
++ * @brief Parse a string and apply format, crop and frame interval settings.
++ * @param media - media device.
++ * @param p - input string
++ * @param endp - pointer to string p where parsing ended (return)
++ *
++ * Parse string @a p and apply format, crop and frame interval settings to a
++ * subdev pad specified in @a p. @a endp will be written a pointer where
++ * parsing of @a p ended.
++ *
++ * Format strings are separeted by commas (,).
++ *
++ * @return 0 on success, or a negative error code on failure.
++ */
++int v4l2_subdev_parse_setup_formats(struct media_device *media, const char *p);
++
++/**
++ * @brief Convert media bus pixel code to string.
++ * @param code - input string
++ *
++ * Convert media bus pixel code @a code to a human-readable string.
++ *
++ * @return A pointer to a string on success, NULL on failure.
++ */
++const char *v4l2_subdev_pixelcode_to_string(enum v4l2_mbus_pixelcode code);
++
++/**
++ * @brief Parse string to media bus pixel code.
++ * @param string - input string
++ * @param lenght - length of the string
++ *
++ * Parse human readable string @a string to an media bus pixel code.
++ *
++ * @return media bus pixelcode on success, -1 on failure.
++ */
++enum v4l2_mbus_pixelcode v4l2_subdev_string_to_pixelcode(const char *string,
++                                                       unsigned int length);
++#endif
+-- 
+2.9.2
+
diff --git a/recipes-graphics/wayland/weston/0003-gst-recorder-Use-USERPTR-instead-of-DMABUF-for-VSP-o.patch b/recipes-graphics/wayland/weston/0003-gst-recorder-Use-USERPTR-instead-of-DMABUF-for-VSP-o.patch
new file mode 100644 (file)
index 0000000..6f8d1dc
--- /dev/null
@@ -0,0 +1,261 @@
+From f450d78f49ab55149b6b64251dd4c4f1ad04c9aa Mon Sep 17 00:00:00 2001
+From: Damian Hobson-Garcia <dhobsong@igel.co.jp>
+Date: Thu, 11 May 2017 12:05:56 +0900
+Subject: [PATCH 3/4] gst-recorder: Use USERPTR instead of DMABUF for VSP
+ output
+
+The RCar-Gen3 encoder requires buffers to be allocated and managed
+externally when using dmabuf buffers.  Since different sized buffers
+are required for each output, the VSP cannot allocate these buffers
+externally.  The encoder provides its own buffers in USERPTR mode, so
+switch to that.
+---
+ libweston/gst-recorder.c | 100 +++++++++++++++++++++++++++++------------------
+ 1 file changed, 63 insertions(+), 37 deletions(-)
+
+diff --git a/libweston/gst-recorder.c b/libweston/gst-recorder.c
+index d46d4f0..d2cf8dc 100644
+--- a/libweston/gst-recorder.c
++++ b/libweston/gst-recorder.c
+@@ -311,7 +311,7 @@ vsp_init(const char *devname)
+               weston_log("failed to open subdev '%s'\n", buf);
+               goto error_media;
+       }
+-      else if ((vsp->output.fd = open(media_entity_get_devname(entity), O_RDWR | O_NONBLOCK)) < 0)
++      else if ((vsp->output.fd = open(media_entity_get_devname(entity), O_RDWR )) < 0)
+       {
+               weston_log("failed to open device '%s'\n", media_entity_get_devname(entity));
+               goto error_media;
+@@ -464,7 +464,8 @@ vsp_request_buffers(vsp_data_t *vsp, vsp_port_n port, unsigned int num)
+       memset(&reqbuf, 0, sizeof(reqbuf));
+       reqbuf.type = (port == VSP_PORT_INPUT) ?
+               V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE : V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+-      reqbuf.memory = V4L2_MEMORY_DMABUF;
++      reqbuf.memory = (port == VSP_PORT_INPUT) ?
++              V4L2_MEMORY_DMABUF : V4L2_MEMORY_USERPTR;
+       reqbuf.count = num;
+       if (ioctl(fd, VIDIOC_REQBUFS, &reqbuf) < 0) {
+               weston_log("VSP: %s REQBUFS failed: %d\n",
+@@ -536,7 +537,8 @@ vsp_input_buffer_dequeue_dmafd(vsp_data_t *vsp)
+ /* ...enqueue output buffer */
+ static int
+-vsp_output_buffer_queue_dmafd(vsp_data_t *vsp, int i, int dmafd[])
++vsp_output_buffer_queue_userptr(vsp_data_t *vsp, int i, void * omx_mem,
++              int y_plane_size, int c_plane_size)
+ {
+       vsp_media_pad_t    *pad = &vsp->output;
+       struct v4l2_plane   planes[2];
+@@ -546,16 +548,23 @@ vsp_output_buffer_queue_dmafd(vsp_data_t *vsp, int i, int dmafd[])
+       memset(&buf, 0, sizeof(buf));
+       memset(planes, 0, sizeof(planes));
+       buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+-      buf.memory = V4L2_MEMORY_DMABUF;
++      buf.memory = V4L2_MEMORY_USERPTR;
+       buf.index = i;
+       buf.m.planes = planes;
+       buf.length = VSP_OUTPUT_BUFFERS_PLANE;
+-      buf.m.planes[0].m.fd = dmafd[0];
+-      buf.m.planes[1].m.fd = dmafd[1];
++
++      buf.m.planes[0].m.userptr = (unsigned long) omx_mem;
++      buf.m.planes[1].m.userptr = (unsigned long) omx_mem + y_plane_size;
++
++      buf.m.planes[0].bytesused = y_plane_size;
++      buf.m.planes[0].length = y_plane_size;
++
++      buf.m.planes[1].bytesused = c_plane_size;
++      buf.m.planes[1].length = c_plane_size;
+       /* ...submit buffer */
+       if (ioctl(pad->fd, VIDIOC_QBUF, &buf) < 0) {
+-              weston_log("VSP: output dmafd queue failed: %d\n", errno);
++              weston_log("VSP: output buffer queue failed: %d\n", errno);
+               return -1;
+       }
+@@ -564,7 +573,7 @@ vsp_output_buffer_queue_dmafd(vsp_data_t *vsp, int i, int dmafd[])
+ /* ...dequeue output buffer */
+ static int
+-vsp_output_buffer_dequeue_dmafd(vsp_data_t *vsp)
++vsp_output_buffer_dequeue_userptr(vsp_data_t *vsp)
+ {
+       vsp_media_pad_t    *pad = &vsp->output;
+       struct v4l2_buffer  buf;
+@@ -574,12 +583,12 @@ vsp_output_buffer_dequeue_dmafd(vsp_data_t *vsp)
+       memset(&buf, 0, sizeof(buf));
+       memset(planes, 0, sizeof(planes));
+       buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+-      buf.memory = V4L2_MEMORY_DMABUF;
++      buf.memory = V4L2_MEMORY_USERPTR;
+       buf.m.planes = planes;
+       buf.length = VSP_OUTPUT_BUFFERS_PLANE;
+       if (ioctl(pad->fd, VIDIOC_DQBUF, &buf) < 0) {
+-              weston_log("VSP: output dmafd de-queue failed: %d\n", errno);
++              weston_log("VSP: output buffer de-queue failed: %d\n", errno);
+               return -1;
+       }
+@@ -876,33 +885,25 @@ err:
+ }
+ static int
+-gst_recorder_omx_buffer_acquire(struct gst_recorder *r, GstBuffer **ret_buf, int fd[])
++gst_recorder_omx_buffer_acquire(struct gst_recorder *r, GstBuffer **ret_buf, GstMapInfo *info)
+ {
+-      unsigned int i;
+       GstFlowReturn ret;
+       GstBuffer *buf;
+-      guint n_mem;
+-      GstMemory *mem;
+       ret = gst_buffer_pool_acquire_buffer(r->omx_pool, &buf, NULL);
+       if (ret != GST_FLOW_OK) {
+-              weston_log("OMX buffer acquire failed\n");
+-              return -1;
++              weston_log("GStreamer buffer acquire failed\n");
++              goto err_release;
+       }
+-      n_mem = gst_buffer_n_memory(buf);
+-      if (n_mem < 1) {
+-              weston_log("Buffer with no mem!\n");
++      if (!gst_buffer_is_writable(buf)) {
++              weston_log("GStreamer buffer not writable\n");
+               goto err_release;
+       }
+-      for (i = 0; i < n_mem; i++) {
+-              mem = gst_buffer_peek_memory (buf, i);
+-              if (!gst_is_dmabuf_memory (mem)) {
+-                      weston_log("Mem not dmabuf\n");
+-                      goto err_release;
+-              }
+-              fd[i] = gst_dmabuf_memory_get_fd (mem);
++      if (!gst_buffer_map(buf, info, GST_MAP_WRITE)) {
++              weston_log("Cannot map GStreamer buffer\n");
++              goto err_release;
+       }
+       *ret_buf = buf;
+@@ -956,7 +957,7 @@ gst_recorder_create(struct gst_recorder_settings *settings)
+       /* omx */
+       ptr += sprintf(ptr,
+-              "omxh264enc target-bitrate=%d control-rate=2 name=my_encoder ! "
++              "omxh264enc target-bitrate=%d control-rate=2 no-copy=true name=my_encoder ! "
+               "video/x-h264,width=%d,height=%d ! ",
+               r->set->bitrate, r->set->crop.width, r->set->crop.height);
+@@ -1009,6 +1010,12 @@ gst_recorder_create(struct gst_recorder_settings *settings)
+                                       "framerate", GST_TYPE_FRACTION, 0, DEFAULT_FPS,
+                                       NULL), NULL);
++      g_object_set(G_OBJECT(r->appsrc),
++                      "stream-type", 0,
++                      "format", GST_FORMAT_TIME,
++                      "is-live", TRUE,
++                      NULL);
++
+       r->appsrc_pad = gst_element_get_static_pad(GST_ELEMENT_CAST(r->appsrc), "src");
+       if (!r->appsrc_pad)
+               weston_log("Failed to get src0 pad of appsrc\n");
+@@ -1088,14 +1095,23 @@ gst_recorder_process_dmafd(struct gst_recorder *r, int fd, int stride)
+ {
+       int ret;
+       GstBuffer *buf;
+-      int omx_fd[2];
++      GstMapInfo info;
++      int ysize;
++      int csize;
+         /* get GST buffer */
+-      if (gst_recorder_omx_buffer_acquire(r, &buf, omx_fd) < 0) {
++      if (gst_recorder_omx_buffer_acquire(r, &buf, &info) < 0) {
+               weston_log("VSP: can not acquire GST buffer, dropping frame\n");
+               return 0;
+       }
++      ysize = r->set->crop.width * r->set->crop.height;
++#ifdef VSP_OUTPUT_NV16
++      csize = ysize;
++#else
++      csize = ysize / 2;
++#endif
++
+       pthread_mutex_lock(&r->vsp->mutex);
+       /* setup vsp */
+       if (vsp_set_formats(r->vsp, r->set->width, r->set->height, &r->set->crop) < 0) {
+@@ -1116,7 +1132,7 @@ gst_recorder_process_dmafd(struct gst_recorder *r, int fd, int stride)
+       }
+       /* queue output biffer */
+-      if (vsp_output_buffer_queue_dmafd(r->vsp, 0, omx_fd) < 0) {
++      if (vsp_output_buffer_queue_userptr(r->vsp, 0, info.data, ysize, csize) < 0) {
+               weston_log("can not queue OMX buffer %d to VSP\n", 0);
+               gst_recorder_omx_buffer_release(r, buf);
+               goto err_vsp;
+@@ -1147,11 +1163,15 @@ gst_recorder_process_dmafd(struct gst_recorder *r, int fd, int stride)
+       }
+       /* dequeue output */
+-      if (vsp_output_buffer_dequeue_dmafd(r->vsp) < 0) {
+-              weston_log("VSP: failed to dequeu output buffer\n");
++      if (vsp_output_buffer_dequeue_userptr(r->vsp) < 0) {
++              weston_log("VSP: failed to dequeue output buffer\n");
++              gst_buffer_unmap(buf, &info);
+               gst_recorder_omx_buffer_release(r, buf);
+-              /* fall through */
+       } else {
++
++              gst_buffer_unmap(buf, &info);
++              gst_buffer_set_size(buf, ysize + csize);
++
+               /* set timestamp */
+               gst_recorder_set_timestamp(r, buf);
+@@ -1174,6 +1194,7 @@ gst_recorder_process_dmafd(struct gst_recorder *r, int fd, int stride)
+       vsp_request_buffers(r->vsp, VSP_PORT_OUTPUT, 0);
+       pthread_mutex_unlock(&r->vsp->mutex);
++      close(fd);
+       return 0;
+ err_vsp:
+@@ -1181,6 +1202,7 @@ err_vsp:
+       /* finish vsp here */
+ err:
+       pthread_mutex_unlock(&r->vsp->mutex);
++      close(fd);
+       return -1;
+ }
+@@ -1197,9 +1219,13 @@ gst_recorder_frame_dmafd(struct gst_recorder *r, int fd, int stride)
+               goto unlock;
+       }
+               
+-      /* The mutex is never released while encoding, so this point should
+-       * never be reached if input.valid is true. */
+-      assert(!r->input.valid);
++      /* It is possible that the frame callback can be called mutiple
++       * times before the worker thread wakes up.  In this case
++       * drop all buf the first frame */
++      if(r->input.valid) {
++              close(fd);
++              goto unlock;
++      }
+       r->input.prime_fd = fd;
+       r->input.stride = stride;
+@@ -1209,5 +1235,5 @@ gst_recorder_frame_dmafd(struct gst_recorder *r, int fd, int stride)
+ unlock:
+       pthread_mutex_unlock(&r->mutex);
+-      return 0;
++      return ret;
+ }
+-- 
+2.9.2
+
diff --git a/recipes-graphics/wayland/weston/0004-gst-record-Specify-bytesused-and-length-of-VSP-input.patch b/recipes-graphics/wayland/weston/0004-gst-record-Specify-bytesused-and-length-of-VSP-input.patch
new file mode 100644 (file)
index 0000000..8b0d44a
--- /dev/null
@@ -0,0 +1,61 @@
+From 90a51301038e492875ac02ed48fa86e699849e06 Mon Sep 17 00:00:00 2001
+From: Damian Hobson-Garcia <dhobsong@igel.co.jp>
+Date: Tue, 9 May 2017 18:07:52 +0900
+Subject: [PATCH 4/4] gst-record: Specify bytesused and length of VSP input
+ buffer
+
+The bytesused=0 setting has been deprecated in newer kernels.
+---
+ libweston/gst-recorder.c | 8 ++++++--
+ 1 file changed, 6 insertions(+), 2 deletions(-)
+
+diff --git a/libweston/gst-recorder.c b/libweston/gst-recorder.c
+index d2cf8dc..7dafd95 100644
+--- a/libweston/gst-recorder.c
++++ b/libweston/gst-recorder.c
+@@ -484,7 +484,7 @@ vsp_request_buffers(vsp_data_t *vsp, vsp_port_n port, unsigned int num)
+ /* ...enqueue dmafd buffer */
+ static int
+-vsp_input_buffer_queue_dmafd(vsp_data_t *vsp, int i, int dmafd)
++vsp_input_buffer_queue_dmafd(vsp_data_t *vsp, int i, int dmafd, int bytesused)
+ {
+       vsp_media_pad_t    *pad = &vsp->input;
+       struct v4l2_buffer  buf;
+@@ -499,6 +499,8 @@ vsp_input_buffer_queue_dmafd(vsp_data_t *vsp, int i, int dmafd)
+       buf.m.planes = planes;
+       buf.length = 1;
+       buf.m.planes[0].m.fd = dmafd;
++      buf.m.planes[0].bytesused = bytesused;
++      buf.m.planes[0].length = bytesused;
+       /* ...submit buffer */
+       if (ioctl(pad->fd, VIDIOC_QBUF, &buf) < 0) {
+@@ -1098,6 +1100,7 @@ gst_recorder_process_dmafd(struct gst_recorder *r, int fd, int stride)
+       GstMapInfo info;
+       int ysize;
+       int csize;
++      int rgbsize;
+         /* get GST buffer */
+       if (gst_recorder_omx_buffer_acquire(r, &buf, &info) < 0) {
+@@ -1111,6 +1114,7 @@ gst_recorder_process_dmafd(struct gst_recorder *r, int fd, int stride)
+ #else
+       csize = ysize / 2;
+ #endif
++      rgbsize = stride * r->set->height;
+       pthread_mutex_lock(&r->vsp->mutex);
+       /* setup vsp */
+@@ -1139,7 +1143,7 @@ gst_recorder_process_dmafd(struct gst_recorder *r, int fd, int stride)
+       }
+       /* queue input vsp buffer */
+-      if (vsp_input_buffer_queue_dmafd(r->vsp, 0, fd) < 0) {
++      if (vsp_input_buffer_queue_dmafd(r->vsp, 0, fd, rgbsize) < 0) {
+               weston_log("VSP: failed to queue input buffer\n");
+               goto err_vsp;
+       }
+-- 
+2.9.2
+
diff --git a/recipes-graphics/wayland/weston_%.bbappend b/recipes-graphics/wayland/weston_%.bbappend
new file mode 100644 (file)
index 0000000..38590dc
--- /dev/null
@@ -0,0 +1 @@
+DEPENDS_append_ulcb = " gstreamer1.0-plugins-base"
diff --git a/recipes-graphics/wayland/weston_2.0.0.bbappend b/recipes-graphics/wayland/weston_2.0.0.bbappend
new file mode 100644 (file)
index 0000000..ba1a79d
--- /dev/null
@@ -0,0 +1,9 @@
+FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"
+
+SRC_URI_append_ulcb = " \
+    file://0001-Add-virtual-output-support.patch \
+    file://0002-Add-gst-recorder-for-h264-output-streaming.patch \
+    file://0003-gst-recorder-Use-USERPTR-instead-of-DMABUF-for-VSP-o.patch \
+    file://0004-gst-record-Specify-bytesused-and-length-of-VSP-input.patch \
+"
+