summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDamian Hobson-Garcia <dhobsong@igel.co.jp>2017-05-08 12:43:02 +0900
committerJan-Simon Moeller <jsmoeller@linuxfoundation.org>2017-06-15 09:44:22 +0000
commit562c0c1bb2ef74ccbfda1bae4f84a61828119674 (patch)
tree7dfffefb524a015734f4075c9b2c93b1c8bfd1f6
parent49da88808c7ce9c5632702279b261218f02eacf2 (diff)
Add gst-recorder implementation
Adapted from the Gen2 implementation to work on Gen3. Bug-AGL: SPEC-559 Change-Id: I8ec71354b0dee04277c6bf74c62956280b84b4fb Signed-off-by: Damian Hobson-Garcia <dhobsong@igel.co.jp> Signed-off-by: Harunobu Kurokawa <harunobu.kurokawa.dn@renesas.com>
-rw-r--r--recipes-graphics/wayland/weston/0001-Add-virtual-output-support.patch375
-rw-r--r--recipes-graphics/wayland/weston/0002-Add-gst-recorder-for-h264-output-streaming.patch4201
-rw-r--r--recipes-graphics/wayland/weston/0003-gst-recorder-Use-USERPTR-instead-of-DMABUF-for-VSP-o.patch261
-rw-r--r--recipes-graphics/wayland/weston/0004-gst-record-Specify-bytesused-and-length-of-VSP-input.patch61
-rw-r--r--recipes-graphics/wayland/weston_1.11.0.bbappend8
5 files changed, 4906 insertions, 0 deletions
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
index 00000000..6373f94e
--- /dev/null
+++ b/recipes-graphics/wayland/weston/0001-Add-virtual-output-support.patch
@@ -0,0 +1,375 @@
+From c0bb07ba816524d69de22c22fcb7f7b9b95fbb11 Mon Sep 17 00:00:00 2001
+From: Damian Hobson-Garcia <dhobsong@igel.co.jp>
+Date: Thu, 27 Apr 2017 16:47:00 +0900
+Subject: [PATCH 1/3] Add virtual output support
+
+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.
+------------
+---
+ src/compositor-drm.c | 310 +++++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 310 insertions(+)
+
+diff --git a/src/compositor-drm.c b/src/compositor-drm.c
+index abc9408..fc5a2ff 100644
+--- a/src/compositor-drm.c
++++ b/src/compositor-drm.c
+@@ -199,6 +199,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;
+ };
+
+ /*
+@@ -1474,6 +1479,33 @@ drm_output_destroy(struct weston_output *output_base)
+ free(output);
+ }
+
++static void
++virtual_output_destroy(struct weston_output *output_base)
++{
++ struct drm_output *output = (struct drm_output *) output_base;
++ struct drm_backend *c =
++ (struct drm_backend *) output->base.compositor;
++
++ c->crtc_allocator &= ~(1 << output->crtc_id);
++ c->connector_allocator &= ~(1 << output->connector_id);
++
++ if (c->use_pixman) {
++ drm_output_fini_pixman(output);
++ } else {
++ gl_renderer->output_destroy(output_base);
++ gbm_surface_destroy(output->gbm_surface);
++ }
++
++ weston_plane_release(&output->fb_plane);
++ weston_plane_release(&output->cursor_plane);
++
++ weston_output_destroy(&output->base);
++
++
++ wl_event_source_remove(output->virtual_finish_frame_timer);
++ free(output);
++}
++
+ /**
+ * Find the closest-matching mode for a given target
+ *
+@@ -2649,6 +2681,270 @@ err_free:
+ }
+
+ 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->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;
++}
++
++/*
++ * 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,
++ int x, int y, struct udev_device *drm_device)
++{
++ struct wl_event_loop *loop;
++ static int virtual_id = 1; /* as other outputs numbered */
++ struct drm_output *output;
++ struct drm_mode *drm_mode, *next, *current;
++ struct weston_mode *m;
++ struct weston_config_section *section;
++ int width, height, scale, fps;
++ int recorded_output;
++ char name[32], *s;
++ enum weston_drm_backend_output_mode mode;
++ struct weston_drm_backend_output_config config = {{ 0 }};
++ uint32_t transform;
++ int valid_mode;
++ drmModeModeInfo crtc_mode;
++
++ output = zalloc(sizeof *output);
++ if (output == NULL)
++ return -1;
++
++ output->base.subpixel = WL_OUTPUT_SUBPIXEL_NONE; //drm_subpixel_to_wayland(connector->subpixel);
++ output->base.make = "CogentEmbedded,Inc";
++ output->base.serial_number = "";
++ wl_list_init(&output->base.mode_list);
++
++ snprintf(name, 32, "virtual%d", virtual_id++);
++ output->base.name = strdup(name);
++
++ section = weston_config_get_section(b->compositor->config, "output", "name",
++ output->base.name);
++
++ weston_config_section_get_bool(section, "recorder", &recorded_output, 0);
++ if (recorded_output) {
++ char model[64];
++ char *ip;
++ int port;
++
++ weston_config_section_get_string(section, "ip", &ip, "<nil>");
++ weston_config_section_get_int(section, "port", &port, -1);
++ snprintf(model, 64, "Virtual RTP %s:%d", ip, port);
++ output->base.model = strdup(model);
++ } else {
++ output->base.model = "Virtual Display";
++ }
++
++ mode = b->configure_output(b->compositor, b->use_current_mode,
++ output->base.name, &config);
++
++ if (mode == WESTON_DRM_BACKEND_OUTPUT_PREFERRED) {
++ if (config.modeline && sscanf(config.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);
++
++ if (parse_gbm_format(config.gbm_format, b->gbm_format, &output->gbm_format) == -1)
++ output->gbm_format = b->gbm_format;
++
++ weston_config_section_get_string(section, "seat", &s, "");
++ setup_output_seat_constraint(b, &output->base, s);
++ free(s);
++
++ output->pipe = 0;
++ b->crtc_allocator |= (1 << output->crtc_id);
++ output->connector_id = 0;
++ b->connector_allocator |= (1 << output->connector_id);
++
++ /* this is virtual output */
++ output->virtual = 1;
++
++
++ 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;
++ }
++
++ if (mode == WESTON_DRM_BACKEND_OUTPUT_OFF) {
++ weston_log("Disabling output %s\n", output->base.name);
++ drmModeSetCrtc(b->drm.fd, output->crtc_id,
++ 0, 0, 0, 0, 0, NULL);
++ goto err_free;
++ }
++
++ current = drm_output_choose_initial_mode(b, output, mode, &config,
++ &crtc_mode);
++ if (!current)
++ goto err_free;
++ output->base.current_mode = &current->base;
++ output->base.current_mode->flags |= WL_OUTPUT_MODE_CURRENT;
++
++ weston_output_init(&output->base, b->compositor, x, y,
++ 100, 100 * height / width, /* FIXME: calculate proper mm_width and mm_height */
++ config.base.transform, config.base.scale);
++ if (b->use_pixman) {
++ if (drm_output_init_pixman(output, b) < 0) {
++ weston_log("Failed to init output pixman state\n");
++ goto err_output;
++ }
++ } else if (drm_output_init_egl(output, b) < 0) {
++ weston_log("Failed to init output gl state\n");
++ goto err_output;
++ }
++
++ output->backlight = NULL;
++
++ weston_compositor_add_output(b->compositor, &output->base);
++
++ output->base.connection_internal = 1;
++
++ 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.start_repaint_loop = virtual_output_start_repaint_loop;
++ output->base.repaint = virtual_output_repaint;
++ output->base.destroy = virtual_output_destroy;
++ output->base.assign_planes = NULL;
++ output->base.set_backlight = 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;
++
++ weston_plane_init(&output->cursor_plane, b->compositor, 0, 0);
++ 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);
++
++ return 0;
++
++err_output:
++ weston_output_destroy(&output->base);
++err_free:
++ wl_list_for_each_safe(drm_mode, next, &output->base.mode_list,
++ base.link) {
++ wl_list_remove(&drm_mode->base.link);
++ free(drm_mode);
++ }
++
++ b->crtc_allocator &= ~(1 << output->crtc_id);
++ b->connector_allocator &= ~(1 << output->connector_id);
++ free(output);
++
++ return -1;
++}
++
++static void
+ create_sprites(struct drm_backend *b)
+ {
+ struct drm_sprite *sprite;
+@@ -2721,10 +3017,12 @@ static int
+ create_outputs(struct drm_backend *b, uint32_t option_connector,
+ struct udev_device *drm_device)
+ {
++ struct weston_config_section *section;
+ drmModeConnector *connector;
+ drmModeRes *resources;
+ int i;
+ int x = 0, y = 0;
++ int virtual;
+
+ resources = drmModeGetResources(b->drm.fd);
+ if (!resources) {
+@@ -2770,6 +3068,18 @@ create_outputs(struct drm_backend *b, uint32_t option_connector,
+ drmModeFreeConnector(connector);
+ }
+
++ section = weston_config_get_section(b->compositor->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, x, y,
++ drm_device) < 0)
++ continue;
++ x += container_of(b->compositor->output_list.prev,
++ struct weston_output,
++ link)->width;
++ }
++
+ if (wl_list_empty(&b->compositor->output_list)) {
+ weston_log("No currently active connector found.\n");
+ drmModeFreeResources(resources);
+--
+1.9.1
+
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
index 00000000..36c4d93e
--- /dev/null
+++ b/recipes-graphics/wayland/weston/0002-Add-gst-recorder-for-h264-output-streaming.patch
@@ -0,0 +1,4201 @@
+From 3ff09fecbe55d2f84f9a83f8965dffb913c00db7 Mon Sep 17 00:00:00 2001
+From: Damian Hobson-Garcia <dhobsong@igel.co.jp>
+Date: Thu, 27 Apr 2017 16:47:00 +0900
+Subject: [PATCH 2/3] Add gst-recorder for h264 output streaming
+
+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 +
+ configure.ac | 12 +
+ src/compositor-drm.c | 199 ++++++-
+ src/compositor-drm.h | 3 +
+ src/gst-recorder.c | 1213 +++++++++++++++++++++++++++++++++++++++++
+ src/gst-recorder.h | 58 ++
+ src/main.c | 4 +-
+ src/media-ctl/libmediactl.c | 955 ++++++++++++++++++++++++++++++++
+ src/media-ctl/libv4l2subdev.c | 759 ++++++++++++++++++++++++++
+ src/media-ctl/mediactl-priv.h | 64 +++
+ src/media-ctl/mediactl.h | 423 ++++++++++++++
+ src/media-ctl/tools.h | 32 ++
+ src/media-ctl/v4l2subdev.h | 258 +++++++++
+ 13 files changed, 3995 insertions(+), 2 deletions(-)
+ create mode 100644 src/gst-recorder.c
+ create mode 100644 src/gst-recorder.h
+ create mode 100644 src/media-ctl/libmediactl.c
+ create mode 100644 src/media-ctl/libv4l2subdev.c
+ create mode 100644 src/media-ctl/mediactl-priv.h
+ create mode 100644 src/media-ctl/mediactl.h
+ create mode 100644 src/media-ctl/tools.h
+ create mode 100644 src/media-ctl/v4l2subdev.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 98cd683..301c444 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -328,6 +328,23 @@ drm_backend_la_SOURCES += src/vaapi-recorder.c src/vaapi-recorder.h
+ drm_backend_la_LIBADD += $(LIBVA_LIBS)
+ drm_backend_la_CFLAGS += $(LIBVA_CFLAGS)
+ endif
++
++if ENABLE_GST_RECORDER
++drm_backend_la_SOURCES += \
++ src/gst-recorder.c \
++ src/gst-recorder.h \
++ src/v4l2-device.h \
++ src/media-ctl/libmediactl.c \
++ src/media-ctl/libv4l2subdev.c \
++ src/media-ctl/mediactl-priv.h \
++ src/media-ctl/mediactl.h \
++ src/media-ctl/tools.h \
++ src/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/configure.ac b/configure.ac
+index 1d11864..c0717b3 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -249,6 +249,17 @@ if test "x$enable_rpi_compositor" = "xyes"; then
+ fi
+ AM_CONDITIONAL(INSTALL_RPI_COMPOSITOR, test "x$have_bcm_host" = "xyes")
+
++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)
+@@ -734,4 +745,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/src/compositor-drm.c b/src/compositor-drm.c
+index fc5a2ff..8b65637 100644
+--- a/src/compositor-drm.c
++++ b/src/compositor-drm.c
+@@ -57,6 +57,7 @@
+ #include "v4l2-renderer.h"
+ #include "launcher-util.h"
+ #include "vaapi-recorder.h"
++#include "gst-recorder.h"
+ #include "presentation-time-server-protocol.h"
+ #include "linux-dmabuf.h"
+
+@@ -117,6 +118,8 @@ struct drm_backend {
+
+ int use_v4l2;
+
++ int enable_recorder;
++
+ uint32_t prev_state;
+
+ struct udev_input input;
+@@ -197,7 +200,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 */
+@@ -2350,6 +2358,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,
+@@ -3478,7 +3503,171 @@ 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_backend *c, 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 *c;
++ int fd;
++
++ output = container_of(listener, struct drm_output,
++ recorder_frame_listener);
++ c = (struct drm_backend *) output->base.compositor->backend;
++
++ 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(c->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(c, output);
++ }
++}
++
++static int
++recorder_enable(struct drm_backend *c, struct drm_output *output)
++{
++ int enable_recorder = 0;
++ struct gst_recorder_settings *settings;
++ struct weston_config_section *section;
++ 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(c->compositor->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 *c)
++{
++ struct drm_output *output;
++
++ wl_list_for_each(output, &c->compositor->output_list, base.link) {
++ recorder_enable(c, output);
++ }
++}
++
++static void
++recorder_binding(struct weston_keyboard *keyboard, uint32_t time, uint32_t key,
++ void *data)
++{
++ struct drm_backend *c = data;
++ struct drm_output *output;
++
++ /* fix this */
++ wl_list_for_each(output, &c->compositor->output_list, base.link) {
++ if (!output->recorder)
++ recorder_enable(c, output);
++ else
++ recorder_destroy(c, 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)
+@@ -3571,6 +3760,7 @@ drm_backend_create(struct weston_compositor *compositor,
+ b->compositor = compositor;
+ b->use_pixman = config->use_pixman;
+ b->use_v4l2 = config->use_v4l2;
++ b->enable_recorder = config->enable_recorder;
+ b->configure_output = config->configure_output;
+ b->use_current_mode = config->use_current_mode;
+
+@@ -3627,6 +3817,10 @@ drm_backend_create(struct weston_compositor *compositor,
+ }
+ }
+
++#ifdef BUILD_GST_RECORDER
++ gst_recorder_init();
++#endif
++
+ b->base.destroy = drm_destroy;
+ b->base.restore = drm_restore;
+
+@@ -3688,6 +3882,9 @@ drm_backend_create(struct weston_compositor *compositor,
+ recorder_binding, b);
+ weston_compositor_add_debug_binding(compositor, KEY_W,
+ renderer_switch_binding, b);
++ /* enable GST recording-streaming */
++ if (b->enable_recorder)
++ recorders_enable(b);
+
+ if (compositor->renderer->import_dmabuf) {
+ if (linux_dmabuf_setup(compositor) < 0)
+diff --git a/src/compositor-drm.h b/src/compositor-drm.h
+index d3d15af..65305d9 100644
+--- a/src/compositor-drm.h
++++ b/src/compositor-drm.h
+@@ -90,6 +90,9 @@ struct weston_drm_backend_config {
+ /** Whether to use the v4l2 renderer insted of the OpenGL ES renderer. */
+ bool use_v4l2;
+
++ /** 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/src/gst-recorder.c b/src/gst-recorder.c
+new file mode 100644
+index 0000000..2e3b359
+--- /dev/null
++++ b/src/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"
++
++/* VSP includes */
++#include <media-ctl/mediactl.h>
++#include <media-ctl/v4l2subdev.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 */
++ GstClockTime timestamp;
++ uint32_t 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 %lld, dropped %lld\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;
++ }
++
++ 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)
++{
++ uint32_t cur_time = weston_compositor_get_time();
++
++ 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 {
++ uint32_t delta = cur_time - r->ts_last_frame;
++ /* delta in mS */
++ GST_BUFFER_DURATION(buffer) = gst_util_uint64_scale_int(delta, GST_SECOND, 1000);
++ }
++
++ 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/src/gst-recorder.h b/src/gst-recorder.h
+new file mode 100644
+index 0000000..e1c53ff
+--- /dev/null
++++ b/src/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/src/main.c b/src/main.c
+index c2168eb..e287a88 100644
+--- a/src/main.c
++++ b/src/main.c
+@@ -278,7 +278,8 @@ usage(int error_code)
+ " --tty=TTY\t\tThe tty to use\n"
+ " --use-pixman\t\tUse the pixman (CPU) renderer\n"
+ " --use-v4l2\t\tUse the v4l2 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)
+@@ -752,6 +753,7 @@ load_drm_backend(struct weston_compositor *c, const char *backend,
+ { WESTON_OPTION_BOOLEAN, "current-mode", 0, &config.use_current_mode },
+ { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &config.use_pixman },
+ { WESTON_OPTION_BOOLEAN, "use-v4l2", 0, &config.use_v4l2 },
++ { WESTON_OPTION_BOOLEAN, "gst-record", 0, &config.enable_recorder },
+ };
+
+ parse_options(options, ARRAY_LENGTH(options), argc, argv);
+diff --git a/src/media-ctl/libmediactl.c b/src/media-ctl/libmediactl.c
+new file mode 100644
+index 0000000..f15b1a3
+--- /dev/null
++++ b/src/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/src/media-ctl/libv4l2subdev.c b/src/media-ctl/libv4l2subdev.c
+new file mode 100644
+index 0000000..4ede4fa
+--- /dev/null
++++ b/src/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/src/media-ctl/mediactl-priv.h b/src/media-ctl/mediactl-priv.h
+new file mode 100644
+index 0000000..a0d3a55
+--- /dev/null
++++ b/src/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/src/media-ctl/mediactl.h b/src/media-ctl/mediactl.h
+new file mode 100644
+index 0000000..77ac182
+--- /dev/null
++++ b/src/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/src/media-ctl/tools.h b/src/media-ctl/tools.h
+new file mode 100644
+index 0000000..815534c
+--- /dev/null
++++ b/src/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/src/media-ctl/v4l2subdev.h b/src/media-ctl/v4l2subdev.h
+new file mode 100644
+index 0000000..1cb53ff
+--- /dev/null
++++ b/src/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
+--
+1.9.1
+
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
index 00000000..095507c1
--- /dev/null
+++ b/recipes-graphics/wayland/weston/0003-gst-recorder-Use-USERPTR-instead-of-DMABUF-for-VSP-o.patch
@@ -0,0 +1,261 @@
+From ab3cf8044667bff4a9e2e5108c55dd198fa353be 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.
+---
+ src/gst-recorder.c | 100 +++++++++++++++++++++++++++++++++--------------------
+ 1 file changed, 63 insertions(+), 37 deletions(-)
+
+diff --git a/src/gst-recorder.c b/src/gst-recorder.c
+index 2e3b359..271fb69 100644
+--- a/src/gst-recorder.c
++++ b/src/gst-recorder.c
+@@ -314,7 +314,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;
+@@ -467,7 +467,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",
+@@ -539,7 +540,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];
+@@ -549,16 +551,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;
+ }
+
+@@ -567,7 +576,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;
+@@ -577,12 +586,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;
+ }
+
+@@ -879,33 +888,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;
+@@ -959,7 +960,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);
+
+@@ -1012,6 +1013,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;
+ }
+--
+1.9.1
+
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
index 00000000..925d3a43
--- /dev/null
+++ b/recipes-graphics/wayland/weston/0004-gst-record-Specify-bytesused-and-length-of-VSP-input.patch
@@ -0,0 +1,61 @@
+From 28a72656562930d27ef8117a489f8299537bdfb8 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] gst-record: Specify bytesused and length of VSP input buffer
+
+The bytesused=0 setting has been deprecated in newer kernels.
+
+---
+ src/gst-recorder.c | 8 ++++++--
+ 1 file changed, 6 insertions(+), 2 deletions(-)
+
+diff --git a/src/gst-recorder.c b/src/gst-recorder.c
+index f6d12f8..de74250 100644
+--- a/src/gst-recorder.c
++++ b/src/gst-recorder.c
+@@ -487,7 +487,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;
+@@ -502,6 +502,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;
+ }
+--
+1.9.1
+
diff --git a/recipes-graphics/wayland/weston_1.11.0.bbappend b/recipes-graphics/wayland/weston_1.11.0.bbappend
new file mode 100644
index 00000000..25cf7916
--- /dev/null
+++ b/recipes-graphics/wayland/weston_1.11.0.bbappend
@@ -0,0 +1,8 @@
+FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"
+
+SRC_URI_append_m3ulcb = " \
+ 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 \
+"