From 562c0c1bb2ef74ccbfda1bae4f84a61828119674 Mon Sep 17 00:00:00 2001 From: Damian Hobson-Garcia Date: Mon, 8 May 2017 12:43:02 +0900 Subject: 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 Signed-off-by: Harunobu Kurokawa --- .../weston/0001-Add-virtual-output-support.patch | 375 ++ ...dd-gst-recorder-for-h264-output-streaming.patch | 4201 ++++++++++++++++++++ ...r-Use-USERPTR-instead-of-DMABUF-for-VSP-o.patch | 261 ++ ...Specify-bytesused-and-length-of-VSP-input.patch | 61 + recipes-graphics/wayland/weston_1.11.0.bbappend | 8 + 5 files changed, 4906 insertions(+) create mode 100644 recipes-graphics/wayland/weston/0001-Add-virtual-output-support.patch create mode 100644 recipes-graphics/wayland/weston/0002-Add-gst-recorder-for-h264-output-streaming.patch create mode 100644 recipes-graphics/wayland/weston/0003-gst-recorder-Use-USERPTR-instead-of-DMABUF-for-VSP-o.patch create mode 100644 recipes-graphics/wayland/weston/0004-gst-record-Specify-bytesused-and-length-of-VSP-input.patch create mode 100644 recipes-graphics/wayland/weston_1.11.0.bbappend 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 +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 +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, ""); ++ 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 = ¤t->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 +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 ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "compositor.h" ++#include "gst-recorder.h" ++ ++/* VSP includes */ ++#include ++#include ++ ++/* Gstreamer includes */ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++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 ++#include ++ ++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 ++ * ++ * 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 . ++ */ ++ ++#include "config.h" ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#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 ++ ++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 ++ * ++ * 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 . ++ */ ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#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 ++ * ++ * 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 . ++ */ ++ ++#ifndef __MEDIA_PRIV_H__ ++#define __MEDIA_PRIV_H__ ++ ++#include ++ ++#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 ++ * ++ * 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 . ++ */ ++ ++#ifndef __MEDIA_H__ ++#define __MEDIA_H__ ++ ++#include ++ ++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 ++ * ++ * 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 . ++ */ ++ ++#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 ++ * ++ * 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 . ++ */ ++ ++#ifndef __SUBDEV_H__ ++#define __SUBDEV_H__ ++ ++#include ++ ++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 +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 +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 \ +" -- cgit 1.2.3-korg