diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2761af6..b5c21f9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,17 +11,21 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: submodules: 'recursive' - name: Install apt packages run: | + # x264 and the GStreamer plugins that expose x264enc/avdec_h264 are + # published in Ubuntu's community-maintained universe component. + sudo add-apt-repository -y universe sudo apt-get update sudo apt-get install -y build-essential autoconf automake libtool \ intltool gtk-doc-tools pkg-config python3 ninja-build meson \ libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \ - libglib2.0-dev gettext + libglib2.0-dev gettext gstreamer1.0-plugins-ugly \ + gstreamer1.0-libav gstreamer1.0-plugins-bad - name: Build run: | diff --git a/lib/libv4l-gst/gst-backend-testhooks.c b/lib/libv4l-gst/gst-backend-testhooks.c deleted file mode 100644 index 0ba2f33..0000000 --- a/lib/libv4l-gst/gst-backend-testhooks.c +++ /dev/null @@ -1,42 +0,0 @@ -#include "gst-backend-testhooks.h" - -#ifdef UNIT_TESTS - -GstElement * -test_create_pipeline(const gchar *pipeline_str) -{ - return create_pipeline(pipeline_str); -} - -static void -append_test_format(GArray *formats, guint fourcc, const gchar *description) -{ - struct fmt fmt = { 0, }; - - fmt.fourcc = fourcc; - g_strlcpy(fmt.desc, description, FMTDESC_NAME_LENGTH); - g_array_append_val(formats, fmt); -} - -void -prepare_format_backend_fixture(struct v4l_gst *priv) -{ - if (!priv) - return; - if (!priv->pipeline) - priv->pipeline = create_pipeline("identity"); - append_test_format(priv->supported_cap_fmts, V4L2_PIX_FMT_NV12, "NV12"); - - priv->out_fmt.pixelformat = V4L2_PIX_FMT_H264; - priv->out_fmt.plane_fmt[0].sizeimage = 1024; - priv->cap_fmt.pixelformat = V4L2_PIX_FMT_NV12; - priv->cap_fmt.width = 640; - priv->cap_fmt.height = 480; - priv->cap_fmt.num_planes = 1; - priv->cap_fmt.plane_fmt[0].bytesperline = 640; - priv->cap_fmt.plane_fmt[0].sizeimage = 640 * 480 * 3 / 2; - g_atomic_int_set(&priv->is_cap_fmt_acquirable, 1); - priv->out_cnt = INPUT_BUFFERING_CNT; -} - -#endif diff --git a/lib/libv4l-gst/gst-backend-testhooks.h b/lib/libv4l-gst/gst-backend-testhooks.h deleted file mode 100644 index 3a8b976..0000000 --- a/lib/libv4l-gst/gst-backend-testhooks.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef __GST_BACKEND_TEST_H__ -#define __GST_BACKEND_TEST_H__ - -#include "config.h" - -#ifdef UNIT_TESTS -#include -#include - -struct v4l_gst; - -GstElement *test_create_pipeline(const gchar *pipeline_str); -void prepare_format_backend_fixture(struct v4l_gst *priv); -#endif - -#endif /* __GST_BACKEND_TEST_H__ */ diff --git a/lib/libv4l-gst/gst-backend.c b/lib/libv4l-gst/gst-backend.c deleted file mode 100644 index c01115a..0000000 --- a/lib/libv4l-gst/gst-backend.c +++ /dev/null @@ -1,4272 +0,0 @@ -/* - * Copyright (C) 2015 Renesas Electronics Corporation - * Copyright (C) 2024-2026 ClearCode Inc. - * - * 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "libv4l-gst-bufferpool.h" - -#include "gst-backend.h" -#include "evfd-ctrl.h" -#include "debug.h" -#include "utils.h" - -GST_DEBUG_CATEGORY_STATIC(v4l_gst_debug_category); -#define GST_CAT_DEFAULT v4l_gst_debug_category - -GST_DEBUG_CATEGORY_STATIC(v4l_gst_ioctl_debug_category); -GST_DEBUG_CATEGORY_STATIC(v4l_gst_buffer_debug_category); - -#define DEF_CAP_MIN_BUFFERS 2 -#define INPUT_BUFFERING_CNT 16 // must be <= VIDEO_MAX_FRAME -#define INITIAL_BUFFER_WAIT_TIMEOUT (10 * G_TIME_SPAN_SECOND) - -#define FMTDESC_NAME_LENGTH 32 // The same size as defined in the V4L2 spec - -enum buffer_state { - V4L_GST_BUFFER_QUEUED, - V4L_GST_BUFFER_DEQUEUED, -}; - -struct v4l_gst_buffer { - GstBuffer *gstbuf; - GstMapInfo info; - GstMapFlags flags; - struct v4l2_plane planes[GST_VIDEO_MAX_PLANES]; - struct v4l_gst *priv; - enum buffer_state state; - int plane0_fd; /* See reindex_buffers() */ -}; - -struct fmt { - guint fourcc; - gchar desc[FMTDESC_NAME_LENGTH]; -}; - -typedef enum { - EOS_NONE, - EOS_WAITING_DECODE, - EOS_GOT -} EOSState; - -struct v4l_gst { - int plugin_fd; - gboolean is_non_blocking; - struct event_state *event_state; - - GstElement *pipeline; - GstElement *appsrc; - GstElement *appsink; - GstElement *decoder; - GstPad *video_sink_pad; - - GstVideoInfo src_video_info; - - GstAppSinkCallbacks appsink_cb; - gulong probe_id; - gulong decoder_probe_id; - - void *pool_lib_handle; - struct libv4l_gst_buffer_pool_ops *pool_ops; - - /* - * out (OUTPUT) : Application --> v4l-gst Encoded data like H.264 - * cap (CAPTURE): Application <-- v4l-gst Decoded data like NV12 - */ - GArray *supported_out_fmts; /* struct fmt */ - GArray *supported_cap_fmts; /* struct fmt */ - struct v4l2_pix_format_mplane out_fmt; - struct v4l2_pix_format_mplane cap_fmt; - - GstBufferPool *src_pool; /* for OUTPUT */ - GstBufferPool *sink_pool; /* for CAPTURE */ - - struct v4l_gst_buffer *out_buffers; - gint out_buffers_num; - struct v4l_gst_buffer *cap_buffers; - gint cap_buffers_num; - - int64_t mmap_offset; - - GQueue *req_gstbufs_queue; /* GstBuffer */ - GQueue *cap_gstbufs_queue; /* GstBuffer */ - GMutex queue_mutex; - GCond queue_cond; - - gint returned_out_buffers_num; - - /* To wait for the requested number of buffers on CAPTURE - to be set in pad_probe_query() */ - GMutex cap_reqbuf_mutex; - GCond cap_reqbuf_cond; - gboolean cancel_cap_reqbuf_wait; - int is_cap_fmt_acquirable; - gint out_cnt; - - gboolean is_pipeline_started; - - GstBuffer *eos_gstbuf; - EOSState eos_state; - GstClockTime last_cap_pts; - GstClockTime estimated_cap_duration; - - struct { - gint cap_min_buffers; - gint max_width; - gint max_height; - guint32 preferred_format; - guint32 fixed_pipeline; - GHashTable *pipelines; /* gchar *fourcc, gchar *pipeline */ - gchar *pool_lib_path; - FrameCheckType frame_check; - } config; - - struct { - GMutex mutex; - gint subscribed; - guint32 sequence; - GQueue *queue; - } v4l2events; - - GMutex dev_lock; -}; - -G_DEFINE_QUARK(cap_buf_crc, cap_buf_crc) - -static void -reset_cap_timestamp_state(struct v4l_gst *priv) -{ - priv->last_cap_pts = GST_CLOCK_TIME_NONE; - priv->estimated_cap_duration = GST_CLOCK_TIME_NONE; -} - -static gboolean -parse_config_file(struct v4l_gst *priv) -{ - const gchar *const *sys_conf_dirs; - GKeyFile *conf_key; - const gchar *conf_name = "libv4l-gst.conf"; - const gchar *libv4l_gst_group = "libv4l-gst"; - GError *err = NULL; - gchar **groups; - gsize n_groups; - gint i; - guint n_pipelines = 0; - - sys_conf_dirs = g_get_system_config_dirs(); - - conf_key = g_key_file_new(); - if (!g_key_file_load_from_dirs(conf_key, conf_name, - (const gchar **) sys_conf_dirs, NULL, - G_KEY_FILE_NONE, &err)) { - GST_ERROR("Failed to load %s " - "from the xdg system config directory retrieved from " - "XDG_CONFIG_DIRS (%s)", conf_name, err->message); - g_error_free(err); - goto free_key_file; - } - - GST_DEBUG("libv4l-gst configuration file is found"); - - /* [libv4l-gst] */ - if (g_key_file_has_group(conf_key, libv4l_gst_group)) { - gchar *preferred_format, *fixed_pipeline; - guint preferred_format_len = 0; - const gchar *frame_check; - - /* No need to check if the external bufferpool library is set, - because it is not mandatory for this plugin. */ - priv->config.pool_lib_path - = g_key_file_get_string(conf_key, libv4l_gst_group, - "bufferpool-library", - NULL); - GST_DEBUG("external buffer pool library : %s", - priv->config.pool_lib_path ? priv->config.pool_lib_path : "none"); - - priv->config.cap_min_buffers - = g_key_file_get_integer(conf_key, libv4l_gst_group, - "min-buffers", NULL); - if (priv->config.cap_min_buffers == 0) - priv->config.cap_min_buffers = DEF_CAP_MIN_BUFFERS; - - GST_DEBUG("minimum number of buffers on CAPTURE " - "for the GStreamer pipeline to work : %d", - priv->config.cap_min_buffers); - - priv->config.max_width - = g_key_file_get_integer(conf_key, libv4l_gst_group, - "max-width", NULL); - priv->config.max_height - = g_key_file_get_integer(conf_key, libv4l_gst_group, - "max-height", NULL); - - preferred_format - = g_key_file_get_string(conf_key, libv4l_gst_group, - "preferred-format", - NULL); - if (preferred_format && *preferred_format) - preferred_format_len = strlen(preferred_format); - if (preferred_format_len == 4) { - priv->config.preferred_format - = fourcc_from_string(preferred_format); - } else if (preferred_format_len > 0) { - GST_WARNING("Invalid FourCC for preferred-format: %s", - preferred_format); - } - g_free(preferred_format); - - fixed_pipeline - = g_key_file_get_string(conf_key, libv4l_gst_group, - "fixed-pipeline", - NULL); - if (fixed_pipeline) { - guint32 fourcc = fourcc_from_string(fixed_pipeline); - if (fourcc == V4L2_PIX_FMT_H264 || - fourcc == V4L2_PIX_FMT_HEVC) { - priv->config.fixed_pipeline = fourcc; - } else { - GST_WARNING("Unknown fixed-pipeline: %s", - fixed_pipeline); - } - } - g_free(fixed_pipeline); - - frame_check = getenv("V4L_GST_FRAME_CHECK"); - if (frame_check) { - if (!strcmp("1", frame_check) || - !strcasecmp("light", frame_check) || - !strcasecmp("true", frame_check) || - !strcasecmp("enable", frame_check)) - priv->config.frame_check = FRAME_CHECK_LIGHT; - else if (!strcmp("2", frame_check) || - !strcasecmp("full", frame_check)) - priv->config.frame_check = FRAME_CHECK_FULL; - } - } - - /* [H264], [HEVC], etc... */ - priv->config.pipelines = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, g_free); - groups = g_key_file_get_groups(conf_key, &n_groups); - GST_DEBUG("found %zu section in %s", n_groups, conf_name); - for (i = 0; i < n_groups; i++) { - gchar *pipeline_str; - - if (!g_strcmp0(groups[i], libv4l_gst_group)) - continue; - - /* treat only fourcc */ - if (strlen(groups[i]) != 4) - continue; - - GST_DEBUG("Parse section: [%s]", groups[i]); - pipeline_str = g_key_file_get_string(conf_key, groups[i], - "pipeline", &err); - if (err) { - GST_ERROR("GStreamer pipeline is not specified"); - if (err) g_error_free(err); - err = NULL; - continue; - } - - g_hash_table_insert(priv->config.pipelines, - g_strdup(groups[i]), - pipeline_str); - GST_DEBUG("enabled %s pipeline: %s", groups[i], pipeline_str); - n_pipelines++; - } - - g_strfreev(groups); - free_key_file: - g_key_file_free(conf_key); - - if (n_pipelines == 0) - GST_ERROR("no pipeline!"); - - return n_pipelines > 0; -} - -static GstElement * -create_pipeline(const gchar *pipeline_str) -{ - const gchar *format_str; - gchar *launch_str; - GstElement *pipeline; - GError *err = NULL; - gboolean has_appsrc = !!strstr(pipeline_str, "appsrc"); - gboolean has_appsink = !!strstr(pipeline_str, "appsink"); - - if (has_appsrc && has_appsink) - format_str = "%s"; - else if (has_appsrc) - format_str = "%s ! appsink"; - else if (has_appsink) - format_str = "appsrc ! %s"; - else - format_str = "appsrc ! %s ! appsink"; - - launch_str = g_strdup_printf(format_str, pipeline_str); - - GST_DEBUG("gst_parse_launch: %s", launch_str); - - pipeline = gst_parse_launch(launch_str, &err); - g_free(launch_str); - - if (err) { - GST_ERROR("Couldn't construct pipeline: %s", - err->message); - g_error_free(err); - return NULL; - } - - return pipeline; -} - -static gboolean -get_gst_elements(struct v4l_gst *priv) -{ - GstIterator *it; - gboolean done = FALSE; - GValue data = { 0, }; - GstElement *elem; - GstElementFactory *factory; - const gchar *elem_name; - const gchar *klass; - const gchar *decoder_klass = "Codec/Decoder/Video"; - - priv->appsrc = priv->appsink = priv->decoder = NULL; - - it = gst_bin_iterate_elements(GST_BIN(priv->pipeline)); - while (!done) { - switch (gst_iterator_next(it, &data)) { - case GST_ITERATOR_OK: - elem = g_value_get_object(&data); - - factory = gst_element_get_factory(elem); - elem_name = gst_element_factory_get_metadata - (factory, GST_ELEMENT_METADATA_LONGNAME); - klass = gst_element_factory_get_metadata - (factory, GST_ELEMENT_METADATA_KLASS); - if (!g_strcmp0(elem_name, "AppSrc")) - priv->appsrc = elem; - else if (!g_strcmp0(elem_name, "AppSink")) - priv->appsink = elem; - else if (!strncmp(klass, decoder_klass, - strlen(decoder_klass))) - priv->decoder = elem; - - g_value_reset(&data); - break; - case GST_ITERATOR_DONE: - default: - done = TRUE; - break; - } - } - - g_value_unset(&data); - gst_iterator_free(it); - - if (!priv->appsrc || !priv->appsink) { - GST_ERROR("Failed to get app elements from the pipeline"); - return FALSE; - } - - GST_DEBUG("appsrc and appsink elements are found in the pipeline"); - - return TRUE; -} - -static void -get_buffer_pool_ops(gchar *pool_lib_path, void **pool_lib_handle, - struct libv4l_gst_buffer_pool_ops **pool_ops) -{ - void *handle; - gchar *err; - struct libv4l_gst_buffer_pool_ops *ops; - - /* This dynamic linking will keep loaded even after the plugin has been - closed in order to prevent from the duplicate class registration of - the buffer pool due to the static variable that indicates if - the class has already been registered being deleted when the dynamic - library is unloaded. */ - handle = dlopen(pool_lib_path, RTLD_LAZY); - if (!handle) { - GST_ERROR("dlopen failed (%s)", dlerror()); - return; - } - - dlerror(); /* Clear any existing error */ - - ops = dlsym(handle, "libv4l_gst_bufferpool"); - err = dlerror(); - if (err) { - GST_ERROR("dlsym failed (%s)", err); - dlclose(handle); - return; - } - - *pool_lib_handle = handle; - *pool_ops = ops; - - GST_DEBUG("buffer pool ops is set"); -} - -static GstPad * -get_peer_pad(GstElement *elem, const gchar *pad_name) -{ - GstPad *pad, *peer_pad = NULL; - - pad = gst_element_get_static_pad(elem, pad_name); - if (!pad) - return NULL; - - peer_pad = gst_pad_get_peer(pad); - gst_object_unref(pad); - - return peer_pad; -} - -static GstElement * -get_peer_element(GstElement *elem, const gchar *pad_name) -{ - GstPad *peer_pad; - GstElement *peer_elem; - - peer_pad = get_peer_pad(elem, pad_name); - if (!peer_pad) - return NULL; - - peer_elem = gst_pad_get_parent_element(peer_pad); - gst_object_unref(peer_pad); - - return peer_elem; -} - -static GstCaps * -get_peer_pad_template_caps(GstElement *elem, const gchar *pad_name, GstPad **peer_pad) -{ - GstPad *pad, *first_peer_pad = NULL; - GstCaps *caps = NULL, *first_caps = NULL;; - - gst_object_ref(elem); - - do { - pad = get_peer_pad(elem, pad_name); - gst_object_unref(elem); - if (!pad) - break; - - caps = GST_PAD_TEMPLATE_CAPS(GST_PAD_PAD_TEMPLATE(pad)); - if (!first_caps) { - first_caps = caps; - gst_caps_ref(first_caps); - first_peer_pad = pad; - gst_object_ref(first_peer_pad); - } - - /* Skip meaningless elements such as queue or tee */ - if (gst_caps_is_any(caps)) { - caps = NULL; - elem = gst_pad_get_parent_element(pad); - gst_object_unref(pad); - } else { - gst_caps_ref(caps); - } - } while (!caps && elem && pad); - - if (first_caps) { - /* fallback to first peer if no meaningful caps found */ - if (!caps) { - caps = first_caps; - pad = first_peer_pad; - } else { - gst_caps_unref(first_caps); - gst_object_unref(first_peer_pad); - } - } - - if (peer_pad) - *peer_pad = pad; - else if (pad) - gst_object_unref(pad); - - return caps; -} - -static void -fill_out_fmts_func(gpointer key, gpointer value, gpointer user_data) -{ - GArray *fmts = user_data; - struct fmt fmt; - - fmt.fourcc = fourcc_from_string(key); - g_strlcpy(fmt.desc, key, FMTDESC_NAME_LENGTH); - g_array_append_vals(fmts, &fmt, 1); -} - -static gboolean -fill_config_video_format_out(struct v4l_gst *priv) -{ - gint i; - gchar codecs[256] = {0}; - - g_array_set_size(priv->supported_out_fmts, 0); - g_hash_table_foreach(priv->config.pipelines, - fill_out_fmts_func, - priv->supported_out_fmts); - - for (i = 0; i < priv->supported_out_fmts->len; i++) { - struct fmt *fmts = (struct fmt*)priv->supported_out_fmts->data; - g_strlcat(codecs, fmts[i].desc, sizeof(codecs)); - g_strlcat(codecs, " ", sizeof(codecs)); - } - GST_DEBUG("supported codecs: %s", codecs); - - return priv->supported_out_fmts->len > 0; -} - -static void -fill_config_video_format_cap(struct v4l_gst *priv) -{ - struct fmt color_fmt; - - if (priv->config.preferred_format) { - color_fmt.fourcc = priv->config.preferred_format; - fourcc_to_string(priv->config.preferred_format, color_fmt.desc); - } else { - color_fmt.fourcc = fourcc_from_string("NV12"); - g_strlcpy(color_fmt.desc, "NV12", FMTDESC_NAME_LENGTH); - } - g_array_prepend_vals(priv->supported_cap_fmts, - &color_fmt, 1); -} - -static gboolean -get_supported_video_format_out(struct v4l_gst *priv) -{ - GstCaps *caps; - GstStructure *structure; - const gchar *mime; - guint fourcc; - struct fmt *fmt; - - caps = get_peer_pad_template_caps(priv->appsrc, "src", NULL); - if (!caps) { - GST_ERROR("Failed to get video format for OUTPUT"); - return FALSE; - } - - structure = gst_caps_get_structure(caps, 0); - mime = gst_structure_get_name(structure); - - if (g_strcmp0(mime, GST_VIDEO_CODEC_MIME_H264) == 0) { - fourcc = V4L2_PIX_FMT_H264; - } else if (g_strcmp0(mime, GST_VIDEO_CODEC_MIME_HEVC) == 0) { - fourcc = V4L2_PIX_FMT_HEVC; - } else { - GST_ERROR("Unsupported codec : %s", mime); - gst_caps_unref(caps); - g_array_set_size(priv->supported_out_fmts, 0); - return FALSE; - } - GST_DEBUG("out supported codec : %s", mime); - - g_array_set_size(priv->supported_out_fmts, 1); - fmt = (struct fmt*)priv->supported_out_fmts->data; - - fmt->fourcc = fourcc; - if(fourcc == V4L2_PIX_FMT_H264) - g_strlcpy(fmt->desc, "V4L2_PIX_FMT_H264", FMTDESC_NAME_LENGTH); - else if (fourcc == V4L2_PIX_FMT_HEVC) - g_strlcpy(fmt->desc, "V4L2_PIX_FMT_HEVC", FMTDESC_NAME_LENGTH); - gst_caps_unref(caps); - - return TRUE; -} - -static gboolean -get_supported_video_format_cap(struct v4l_gst *priv) -{ - GstCaps *caps; - GstStructure *structure; - guint structs; - const GValue *val, *list_val; - const gchar *fmt_str; - GstVideoFormat fmt; - guint32 preferred = priv->config.preferred_format; - gboolean preferred_found = FALSE; - guint i, j; - struct fmt color_fmt; - gchar fourcc_str[5]; - - g_array_set_size(priv->supported_cap_fmts, 0); - - caps = get_peer_pad_template_caps(priv->appsink, "sink", - &priv->video_sink_pad); - if (!caps) { - GST_ERROR("Failed to get video format for CAPTURE"); - return FALSE; - } - - /* We treat GST_CAPS_ANY as all video formats support. */ - if (gst_caps_is_any(caps)) { - GST_DEBUG("Use GST_VIDEO_FORMATS_ALL"); - gst_caps_unref(caps); - caps = gst_caps_from_string - ("video/x-raw, format=" GST_VIDEO_FORMATS_ALL); - } - - GST_DEBUG("caps: %" GST_PTR_FORMAT, caps); - - structs = gst_caps_get_size(caps); - - for (j = 0; j < structs; j++) { - gint num_cap_formats; - - structure = gst_caps_get_structure(caps, j); - val = gst_structure_get_value(structure, "format"); - if (!val) - continue; - - num_cap_formats = GST_VALUE_HOLDS_LIST(val) ? - gst_value_list_get_size(val) : 1; - - for (i = 0; i < num_cap_formats; i++) { - list_val = GST_VALUE_HOLDS_LIST(val) ? - gst_value_list_get_value(val, i) : val; - fmt_str = g_value_get_string(list_val); - - fmt = gst_video_format_from_string(fmt_str); - if (fmt == GST_VIDEO_FORMAT_UNKNOWN) { - GST_ERROR("Unknown video format : %s", fmt_str); - continue; - } - - color_fmt.fourcc = fourcc_from_gst_video_format(fmt); - if (color_fmt.fourcc == 0) { - GST_DEBUG("Failed to convert video format " - "from gst to v4l2 : %s", fmt_str); - continue; - } - - GST_DEBUG("cap supported video format : %s", fmt_str); - - g_strlcpy(color_fmt.desc, fmt_str, FMTDESC_NAME_LENGTH); - - if (preferred && color_fmt.fourcc == preferred) { - g_array_prepend_vals(priv->supported_cap_fmts, - &color_fmt, 1); - - fourcc_to_string(preferred, fourcc_str); - GST_DEBUG("Preferred format: %s (0x%x)", - fourcc_str, preferred); - preferred_found = TRUE; - } else { - g_array_append_vals(priv->supported_cap_fmts, - &color_fmt, 1); - } - } - } - - if (preferred) { - if (preferred_found) { - /* TODO: Add a new option to force use this? */ - g_array_set_size(priv->supported_cap_fmts, 1); - } else { - fourcc_to_string(preferred, fourcc_str); - GST_INFO("Preferred format %s (0x%x) isn't supported", - fourcc_str, preferred); - } - } - - gst_caps_unref(caps); - - if (priv->supported_cap_fmts->len == 0) { - GST_ERROR("Failed to get video formats from caps"); - return FALSE; - } - - GST_DEBUG("The total number of cap supported video format : %d", - priv->supported_cap_fmts->len); - - - return TRUE; -} - -static void -create_buffer_pool(struct libv4l_gst_buffer_pool_ops *pool_ops, - GstBufferPool **src_pool, GstBufferPool **sink_pool) -{ - if (pool_ops) { - if (pool_ops->add_external_src_buffer_pool) - *src_pool = pool_ops->add_external_src_buffer_pool(); - - if (pool_ops->add_external_sink_buffer_pool) - *sink_pool = pool_ops->add_external_sink_buffer_pool(); - } - - /* fallback to the default buffer pool */ - if (!*src_pool) - *src_pool = gst_buffer_pool_new(); - if (!*sink_pool) - *sink_pool = gst_buffer_pool_new(); -} - -static void -get_cap_buffer_alignment(struct v4l_gst *priv, GstVideoAlignment *alignment) -{ - guint stride_align, i; - - /* In dma-buffer, ARM Mali requires strict allocation alignment for each - color format (NV12, NV21, YV12, IYUV, I420, IMC1, IMC2, IMC3, IMC4, - P210, P010 require 16-byte alignment. Others require 64-byte alignment) - refs: - https://github.com/renesas-rz/gst-plugins-bad/commit/728304b71301f909142bd2d29099a8fc370e5e25 - https://github.com/renesas-rz/gst-plugins-bad/commit/79f3733bb085d07a78e996bf8d935ad31d43c7b1 - https://github.com/renesas-rz/gst-plugins-bad/commit/e07995fa6e7c24868008826f177d9eaf9c1f88e1 - - GstVideoAlignment::stride_align is a bitmask, not an alignment value. - It must be specified as a bitmask of the form (2^n - 1). - gst_video_info_align_full() checks alignment using: - - (stride & stride_align) == 0 - - e.g.) - 16-byte alignment -> 15 (0b1111) - 64-byte alignment -> 63 (0b111111) */ - switch (priv->cap_fmt.pixelformat) { - case V4L2_PIX_FMT_NV12: - case V4L2_PIX_FMT_NV21: - case V4L2_PIX_FMT_YVU420: - case V4L2_PIX_FMT_YUV420: -#ifdef V4L2_PIX_FMT_P010 - case V4L2_PIX_FMT_P010: -#endif - stride_align = 15; - break; - default: - stride_align = 63; - break; - } - - gst_video_alignment_reset(alignment); - for (i = 0; i < priv->cap_fmt.num_planes; i++) - alignment->stride_align[i] = stride_align; -} - -static void -set_buffer_pool_params(GstBufferPool *pool, GstCaps *caps, guint buf_size, - guint min_buffers, guint max_buffers, - GstVideoAlignment *alignment) -{ - GstStructure *config; - - config = gst_buffer_pool_get_config(pool); - gst_buffer_pool_config_set_params(config, caps, buf_size, min_buffers, - max_buffers); - if (alignment) { - gst_buffer_pool_config_add_option - (config, GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT); - gst_buffer_pool_config_set_video_alignment(config, alignment); - } - gst_buffer_pool_set_config(pool, config); -} - -static void -get_buffer_pool_params(GstBufferPool *pool, GstCaps **caps, guint *buf_size, - guint *min_buffers, guint *max_buffers) -{ - GstStructure *config; - - config = gst_buffer_pool_get_config(pool); - gst_buffer_pool_config_get_params(config, caps, buf_size, min_buffers, - max_buffers); - gst_structure_free(config); -} - -static void -push_source_change_event(struct v4l_gst *priv) -{ - struct v4l2_event *event = g_new0(struct v4l2_event, 1); - - event->type = V4L2_EVENT_SOURCE_CHANGE; - event->u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION; - event->pending = 0; - event->sequence = ++priv->v4l2events.sequence; - event->id = 0; - clock_gettime(CLOCK_REALTIME, &event->timestamp); - - g_mutex_lock(&priv->v4l2events.mutex); - g_queue_push_tail(priv->v4l2events.queue, event); - g_mutex_unlock(&priv->v4l2events.mutex); -} - -static void -retrieve_cap_format_info(struct v4l_gst *priv, GstVideoInfo *info) -{ - gint fourcc; - - priv->cap_fmt.width = info->width; - priv->cap_fmt.height = info->height; - - fourcc = fourcc_from_gst_video_format(info->finfo->format); - if (priv->cap_fmt.pixelformat != 0 && - priv->cap_fmt.pixelformat != fourcc) { - GST_WARNING("Unexpected cap video format"); - } - priv->cap_fmt.pixelformat = fourcc; - - priv->cap_fmt.num_planes = info->finfo->n_planes; -} - -static void -set_pipeline_started(struct v4l_gst *priv, gboolean started) -{ - g_mutex_lock(&priv->cap_reqbuf_mutex); - priv->cancel_cap_reqbuf_wait = !started; - if (!started) - g_cond_broadcast(&priv->cap_reqbuf_cond); - g_mutex_unlock(&priv->cap_reqbuf_mutex); - - g_mutex_lock(&priv->queue_mutex); - priv->is_pipeline_started = started; - if (!started) - g_cond_broadcast(&priv->queue_cond); - g_mutex_unlock(&priv->queue_mutex); -} - -static gboolean -wait_for_cap_reqbuf_invocation(struct v4l_gst *priv) -{ - gboolean succeeded; - gboolean timed_out = FALSE; - gint64 end_time; - - g_mutex_lock(&priv->cap_reqbuf_mutex); - end_time = g_get_monotonic_time() + INITIAL_BUFFER_WAIT_TIMEOUT; - while (!priv->cancel_cap_reqbuf_wait && priv->cap_buffers_num <= 0) { - if (!g_cond_wait_until(&priv->cap_reqbuf_cond, - &priv->cap_reqbuf_mutex, - end_time)) { - timed_out = TRUE; - break; - } - } - succeeded = !priv->cancel_cap_reqbuf_wait && priv->cap_buffers_num > 0; - g_mutex_unlock(&priv->cap_reqbuf_mutex); - - if (timed_out && !succeeded) - GST_WARNING("Timed out waiting VIDIOC_REQBUFS on CAPTURE."); - - return succeeded; -} - -static inline void -release_out_buffer_unlocked(struct v4l_gst *priv, GstBuffer *gstbuf) -{ - GST_TRACE("unref buffer: %p", gstbuf); - gst_buffer_unref(gstbuf); - - set_event(priv->event_state, POLLIN); - - priv->returned_out_buffers_num++; -} - -static inline void -release_out_buffer(struct v4l_gst *priv, GstBuffer *gstbuf) -{ - g_mutex_lock(&priv->queue_mutex); - - release_out_buffer_unlocked(priv, gstbuf); - - g_mutex_unlock(&priv->queue_mutex); -} - -static GstPadProbeReturn -pad_probe_query(GstPad *pad, GstPadProbeInfo *probe_info, gpointer user_data) -{ - struct v4l_gst *priv = user_data; - GstQuery *query; - GstCaps *caps; - GstVideoInfo info; - guint src_width = priv->src_video_info.width; - guint src_height = priv->src_video_info.height; - GstVideoAlignment alignment; - - query = GST_PAD_PROBE_INFO_QUERY (probe_info); - if (GST_QUERY_TYPE (query) == GST_QUERY_ALLOCATION && - GST_PAD_PROBE_INFO_TYPE (probe_info) & GST_PAD_PROBE_TYPE_PUSH) { - GST_DEBUG("parse allocation query"); - gst_query_parse_allocation(query, &caps, NULL); - if (!caps) { - GST_ERROR("No caps in query"); - return GST_PAD_PROBE_OK; - } - - if (!gst_video_info_from_caps(&info, caps)) { - GST_ERROR("Failed to get video info"); - return GST_PAD_PROBE_OK; - } - - if ((src_width && src_width != info.width) || - (src_height && src_height != info.height)) { - /* Sometimes decoder may send interim resolutions that - differ from the original one (typically 16x16 - macroblock based one: e.g. 640x368 vs 640x360) before - sending the final resolution. - Skip such interim resolutions then wait for the original one. - */ - return GST_PAD_PROBE_OK; - } - - retrieve_cap_format_info(priv, &info); - get_cap_buffer_alignment(priv, &alignment); - g_atomic_int_set(&priv->is_cap_fmt_acquirable, 1); - push_source_change_event(priv); - - set_event(priv->event_state, POLLOUT); - - /* Even if a min value is set here, omxvideodec will reset it - with an internally calculated value. It's at least 4 and - possible to become larger than max. When it's calculated as - larger than max, bufferpool will fail to allocate. - To ensure to avoid it, we set the smallest possible value 0 - here. - ref: https://github.com/renesas-rcar/gst-omx/blob/37296f66e3392d8dcdcdae14b89b05dd7507dc39/omx/gstomxvideodec.c#L920 - - On RZ/G2, you can use `num-outbufs` property of omxvideodec - to override it. - e.g.) - `pipeline=h264parse ! omxh264dec no-reorder=true num-outbufs=7` - */ - if (wait_for_cap_reqbuf_invocation(priv)) { - set_buffer_pool_params(priv->sink_pool, caps, info.size, - 0, priv->cap_buffers_num, - &alignment); - gst_query_add_allocation_pool(query, priv->sink_pool, - info.size, - 0, priv->cap_buffers_num); - } else { - GST_WARNING("Failed to wait VIDIOC_REQBUF."); - } - } - - return GST_PAD_PROBE_OK; -} - -static void -appsink_pad_unlinked_cb(GstPad *self, GstPad *peer, gpointer data) -{ - struct v4l_gst *priv = data; - - GST_DEBUG("clear probe_id"); - priv->probe_id = 0; - - g_signal_handlers_disconnect_by_func(self, appsink_pad_unlinked_cb, data); -} - -static GstPadProbeReturn -decoder_sink_pad_probe(GstPad *pad, GstPadProbeInfo *probe_info, gpointer user_data) -{ - struct v4l_gst *priv = user_data; - GstPadProbeType type = GST_PAD_PROBE_INFO_TYPE(probe_info); - GstEvent *event; - GstCaps *caps = NULL; - - if (!(type & GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM)) - return GST_PAD_PROBE_OK; - - event = GST_PAD_PROBE_INFO_EVENT(probe_info); - if (GST_EVENT_TYPE(event) != GST_EVENT_CAPS) - return GST_PAD_PROBE_OK; - - gst_event_parse_caps(event, &caps); - if (!caps) - return GST_PAD_PROBE_OK; - - if (!gst_video_info_from_caps(&priv->src_video_info, caps)) - return GST_PAD_PROBE_OK; - - GST_DEBUG("Source video info: %" GST_PTR_FORMAT, caps); - - return GST_PAD_PROBE_OK; -} - -static void -decoder_pad_unlinked_cb(GstPad *self, GstPad *peer, gpointer data) -{ - struct v4l_gst *priv = data; - - GST_DEBUG("clear decoder_probe_id"); - priv->decoder_probe_id = 0; - g_signal_handlers_disconnect_by_func(self, decoder_pad_unlinked_cb, data); -} - -static gulong -setup_query_pad_probe(struct v4l_gst *priv) -{ - gulong probe_id; - - g_signal_connect(G_OBJECT(priv->video_sink_pad), "unlinked", - G_CALLBACK(appsink_pad_unlinked_cb), priv); - probe_id = gst_pad_add_probe(priv->video_sink_pad, - GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM, - pad_probe_query, - priv, NULL); - - return probe_id; -} - -static GstBuffer * -pull_buffer_from_sample(GstAppSink *appsink) -{ - GstSample *sample; - GstBuffer *gstbuf; - - sample = gst_app_sink_pull_sample(appsink); - gstbuf = gst_sample_get_buffer(sample); - gst_buffer_ref(gstbuf); - gst_sample_unref(sample); - - return gstbuf; -} - -static void -appsink_callback_eos(GstAppSink *appsink, gpointer user_data) -{ - struct v4l_gst *priv = user_data; - if (priv->eos_gstbuf) - release_out_buffer(priv, priv->eos_gstbuf); - g_mutex_lock(&priv->queue_mutex); - GST_DEBUG("EOS: Got from AppSink. Cached buffers: %u", - g_queue_get_length(priv->cap_gstbufs_queue)); - priv->eos_state = EOS_GOT; - if (priv->cap_gstbufs_queue && - !g_queue_is_empty(priv->cap_gstbufs_queue)) { - set_event(priv->event_state, POLLOUT); - } - g_mutex_unlock(&priv->queue_mutex); -} - -static GstFlowReturn -appsink_callback_new_sample(GstAppSink *appsink, gpointer user_data) -{ - struct v4l_gst *priv = user_data; - GstBuffer *gstbuf; - guint len; - GQueue *queue; - - gstbuf = pull_buffer_from_sample(appsink); - - if (priv->cap_buffers && !gst_buffer_n_memory(gstbuf)) { - /* Empty samples cannot be associated with a V4L2 CAPTURE - buffer or dmabuf fd, so do not expose them to clients. */ - GST_WARNING("Drop empty CAPTURE sample: gstbuf=%p, pts=%" - G_GUINT64_FORMAT ", duration=%" G_GUINT64_FORMAT, - gstbuf, (guint64) GST_BUFFER_PTS(gstbuf), - (guint64) GST_BUFFER_DURATION(gstbuf)); - gst_buffer_unref(gstbuf); - return GST_FLOW_OK; - } - - if (priv->config.frame_check && gst_buffer_n_memory(gstbuf)) { - guint32 crc; - - crc = frame_crc32(gstbuf, priv->config.frame_check); - gst_mini_object_set_qdata(GST_MINI_OBJECT(gstbuf), - cap_buf_crc_quark(), - GUINT_TO_POINTER(crc), - NULL); - GST_CAT_DEBUG(v4l_gst_buffer_debug_category, - "pull buffer from appsink:" - " gstbuf=%p, pts=%lu, crc=%u", - gstbuf, GST_BUFFER_PTS(gstbuf) / 1000000, crc); - } else { - GST_CAT_DEBUG(v4l_gst_buffer_debug_category, - "pull buffer from appsink: gstbuf=%p, pts=%lu", - gstbuf, GST_BUFFER_PTS(gstbuf) / 1000000); - } - - if (priv->cap_buffers) - queue = priv->cap_gstbufs_queue; - else - queue = priv->req_gstbufs_queue; - - g_mutex_lock(&priv->queue_mutex); - - g_queue_push_tail(queue, gstbuf); - len = g_queue_get_length(queue); - - if (len > 1 || priv->eos_state == EOS_GOT) { - /* cache 1 buffer to detect EOS */ - if (len == 0 && priv->eos_state == EOS_GOT) - GST_DEBUG("EOS: Flush last frame"); - g_cond_signal(&priv->queue_cond); - set_event(priv->event_state, POLLOUT); - } else if (!priv->cap_buffers) { - g_cond_signal(&priv->queue_cond); - } - - g_mutex_unlock(&priv->queue_mutex); - - return GST_FLOW_OK; -} - -static gboolean -init_app_elements(struct v4l_gst *priv) -{ - /* Get appsrc and appsink elements respectively from the pipeline */ - if (!get_gst_elements(priv)) - return FALSE; - - if (!get_supported_video_format_out(priv)) - return FALSE; - - if (!get_supported_video_format_cap(priv)) - return FALSE; - - /* For queuing buffers received from appsink */ - priv->cap_gstbufs_queue = g_queue_new(); - priv->req_gstbufs_queue = g_queue_new(); - - /* Set the appsrc queue size to unlimited. - The amount of buffers is managed by the buffer pool. */ - gst_app_src_set_max_bytes(GST_APP_SRC(priv->appsrc), 0); - - gst_base_sink_set_sync(GST_BASE_SINK(priv->appsink), FALSE); - - priv->appsink_cb.new_sample = appsink_callback_new_sample; - priv->appsink_cb.eos = appsink_callback_eos; - - gst_app_sink_set_callbacks(GST_APP_SINK(priv->appsink), - &priv->appsink_cb, priv, NULL); - - if (priv->decoder) { - GstPad *pad = gst_element_get_static_pad(priv->decoder, "sink"); - - g_signal_connect(G_OBJECT(pad), "unlinked", - G_CALLBACK(decoder_pad_unlinked_cb), priv); - priv->decoder_probe_id - = gst_pad_add_probe(pad, - GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, - decoder_sink_pad_probe, - priv, NULL); - gst_object_unref(pad); - } - - return TRUE; -} - -static gboolean -init_buffer_pool(struct v4l_gst *priv) -{ - /* Get the external buffer pool when it is specified in - the configuration file */ - if (priv->config.pool_lib_path) { - get_buffer_pool_ops(priv->config.pool_lib_path, - &priv->pool_lib_handle, &priv->pool_ops); - } - - create_buffer_pool(priv->pool_ops, &priv->src_pool, &priv->sink_pool); - - /* To hook allocation queries */ - priv->probe_id = setup_query_pad_probe(priv); - if (priv->probe_id == 0) { - GST_ERROR("Failed to setup query pad probe"); - goto free_pool; - } - - return TRUE; - - /* error cases */ - free_pool: - if (priv->src_pool) - gst_object_unref(priv->src_pool); - if (priv->sink_pool) - gst_object_unref(priv->sink_pool); - - return FALSE; -} - -static gboolean -init_pipeline(struct v4l_gst *priv, guint32 fourcc) -{ - gchar fourcc_str[5]; - const gchar *pipeline; - - fourcc_to_string(fourcc, fourcc_str); - pipeline = g_hash_table_lookup(priv->config.pipelines, fourcc_str); - - if (pipeline) { - GST_DEBUG("create %s pipeline: %s", fourcc_str, pipeline); - priv->pipeline = create_pipeline(pipeline); - } - - if (!priv->pipeline) { - GST_ERROR("Failed to create pipeline for %s", fourcc_str); - goto error; - } - - if (!init_app_elements(priv)) - goto error; - - if (!init_buffer_pool(priv)) - goto error; - - priv->out_fmt.pixelformat = fourcc; - - return TRUE; - - error: - if (priv->cap_gstbufs_queue) { - g_queue_free(priv->cap_gstbufs_queue); - priv->cap_gstbufs_queue = NULL; - } - if (priv->req_gstbufs_queue) { - g_queue_free(priv->req_gstbufs_queue); - priv->req_gstbufs_queue = NULL; - } - if (priv->pipeline) { - gst_object_unref(priv->pipeline); - priv->pipeline = NULL; - } - errno = EINVAL; - return FALSE; -} - -struct v4l_gst* -gst_backend_init(int fd) -{ - static gboolean gstreamer_initialized = FALSE; - struct v4l_gst *priv; - struct stat buf; - int flags; - - if (!gstreamer_initialized) { - gst_init(NULL, NULL); - GST_DEBUG_CATEGORY_INIT(v4l_gst_debug_category, - "v4l-gst", 0, - "debug category for v4l-gst application"); - GST_DEBUG_CATEGORY_INIT(v4l_gst_ioctl_debug_category, - "v4l-gst-ioctl", 0, - "debug category for v4l-gst IOCTL operation"); - GST_DEBUG_CATEGORY_INIT(v4l_gst_buffer_debug_category, - "v4l-gst-buffer", 0, - "debug buffers of v4l-gst"); - gstreamer_initialized = TRUE; - } - - priv = g_new0(struct v4l_gst, 1); - if (!priv) { - GST_ERROR("Couldn't allocate memory for gst-backend"); - return NULL; - } - reset_cap_timestamp_state(priv); - - /* Reject character device */ - fstat(fd, &buf); - if (S_ISCHR(buf.st_mode)) - return NULL; - - flags = fcntl(fd, F_GETFL); - priv->is_non_blocking = (flags & O_NONBLOCK) ? TRUE : FALSE; - GST_DEBUG("non-blocking : %s", (priv->is_non_blocking) ? "on" : "off"); - - /*For handling event state */ - priv->event_state = new_event_state(); - if (!priv->event_state) - goto error; - - if (dup2(event_state_fd(priv->event_state), fd) < 0) { - GST_ERROR("dup2 failed"); - goto error; - } - - priv->plugin_fd = fd; - - if (!parse_config_file(priv)) { - GST_ERROR("pipeline configuration is not found at all"); - goto error; - } - - priv->supported_out_fmts = g_array_new(FALSE, TRUE, sizeof(struct fmt)); - priv->supported_cap_fmts = g_array_new(FALSE, TRUE, sizeof(struct fmt)); - - if (!fill_config_video_format_out(priv)) { - GST_ERROR("Failed to fill in supported video format"); - goto error; - } - fill_config_video_format_cap(priv); - - g_mutex_init(&priv->v4l2events.mutex); - priv->v4l2events.subscribed = 0; - priv->v4l2events.sequence = 0; - priv->v4l2events.queue = g_queue_new(); - - g_mutex_init(&priv->queue_mutex); - g_cond_init(&priv->queue_cond); - g_mutex_init(&priv->cap_reqbuf_mutex); - g_cond_init(&priv->cap_reqbuf_cond); - - g_mutex_init(&priv->dev_lock); - - if (priv->config.fixed_pipeline) { - if (!init_pipeline(priv, priv->config.fixed_pipeline)) - goto error; - } - - GST_DEBUG("Initialized gst backend"); - return priv; - - error: - if (priv->supported_out_fmts) - g_array_free(priv->supported_out_fmts, TRUE); - if (priv->supported_cap_fmts) - g_array_free(priv->supported_cap_fmts, TRUE); - if (priv->config.pipelines) - g_hash_table_destroy(priv->config.pipelines); - g_free(priv->config.pool_lib_path); - if (priv->event_state) - delete_event_state(priv->event_state); - g_free(priv); - - return NULL; -} - -void -gst_backend_deinit(struct v4l_gst *priv) -{ - GST_DEBUG("gst_backend_deinit start"); - - set_pipeline_started(priv, FALSE); - - g_mutex_clear(&priv->dev_lock); - - if (priv->v4l2events.queue) { - g_queue_free_full(priv->v4l2events.queue, - (GDestroyNotify)g_free); - } - priv->v4l2events.subscribed = 0; - g_mutex_clear(&priv->v4l2events.mutex); - - if (priv->decoder_probe_id) { - GstPad *pad = gst_element_get_static_pad(priv->decoder, "sink"); - gst_pad_remove_probe(pad, priv->decoder_probe_id); - gst_object_unref(pad); - } - - if (priv->video_sink_pad) { - if (priv->probe_id) { - gst_pad_remove_probe(priv->video_sink_pad, - priv->probe_id); - } - gst_object_unref(priv->video_sink_pad); - } - - if (priv->out_buffers) - g_free(priv->out_buffers); - - if (priv->cap_buffers) - g_free(priv->cap_buffers); - - if (priv->src_pool) - gst_object_unref(priv->src_pool); - if (priv->sink_pool) - gst_object_unref(priv->sink_pool); - - if (priv->supported_out_fmts) - g_array_free(priv->supported_out_fmts, TRUE); - if (priv->supported_cap_fmts) - g_array_free(priv->supported_cap_fmts, TRUE); - - if (priv->cap_gstbufs_queue) - g_queue_free(priv->cap_gstbufs_queue); - if (priv->req_gstbufs_queue) - g_queue_free(priv->req_gstbufs_queue); - g_mutex_clear(&priv->queue_mutex); - g_cond_clear(&priv->queue_cond); - - g_mutex_clear(&priv->cap_reqbuf_mutex); - g_cond_clear(&priv->cap_reqbuf_cond); - - if (priv->pipeline) - gst_object_unref(priv->pipeline); - - if (priv->config.pipelines) - g_hash_table_destroy(priv->config.pipelines); - - g_free(priv->config.pool_lib_path); - - delete_event_state(priv->event_state); - - g_free(priv); - - GST_DEBUG("gst_backend_deinit end"); -} - -int -querycap_ioctl(struct v4l_gst *priv, struct v4l2_capability *cap) -{ - GST_DEBUG("VIDIOC_QUERYCAP"); - - cap->device_caps = - V4L2_CAP_VIDEO_M2M_MPLANE | - V4L2_CAP_VIDEO_CAPTURE_MPLANE | - V4L2_CAP_VIDEO_OUTPUT_MPLANE | - V4L2_CAP_EXT_PIX_FORMAT | - V4L2_CAP_STREAMING; - - cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; - - g_strlcpy((gchar *)cap->driver, "libv4l-gst", sizeof(cap->driver)); - g_strlcpy((gchar *)cap->card, "gst-dummy", sizeof(cap->card)); - g_strlcpy((gchar *)cap->bus_info, "user-vst-gst-000", sizeof(cap->bus_info)); - memset(cap->reserved, 0, sizeof(cap->reserved)); - - return 0; -} - -static gboolean -is_pix_fmt_supported(struct fmt *fmts, gint fmts_num, guint fourcc) -{ - gint i; - gboolean ret = FALSE; - - for (i = 0; i < fmts_num; i++) { - if (fmts[i].fourcc == fourcc) { - ret = TRUE; - break; - } - } - - return ret; -} - -static void -set_params_as_encoded_stream(struct v4l2_pix_format_mplane *pix_fmt) -{ - /* We set the following parameters assuming that encoded streams are - received on the output buffer type. The values are almost - meaningless. */ - pix_fmt->width = 0; - pix_fmt->height = 0; - pix_fmt->field = V4L2_FIELD_NONE; - pix_fmt->colorspace = 0; - pix_fmt->flags = 0; - pix_fmt->plane_fmt[0].bytesperline = 0; - pix_fmt->num_planes = 1; -} - -static int -set_fmt_ioctl_out(struct v4l_gst *priv, struct v4l2_format *fmt) -{ - struct v4l2_pix_format_mplane *pix_fmt; - gchar fourcc_str[5]; - GArray *cap_fmts = priv->supported_cap_fmts; - - pix_fmt = &fmt->fmt.pix_mp; - fourcc_to_string(pix_fmt->pixelformat, fourcc_str); - - if (!is_pix_fmt_supported((struct fmt*)priv->supported_out_fmts->data, - priv->supported_out_fmts->len, - pix_fmt->pixelformat)) { - GST_ERROR("Unsupported pixelformat on OUTPUT: %s (0x%x)", - fourcc_str, pix_fmt->pixelformat); - errno = EINVAL; - return -1; - } - - if (pix_fmt->plane_fmt[0].sizeimage == 0) { - GST_ERROR("sizeimage field is not specified on OUTPUT"); - errno = EINVAL; - return -1; - } - - if (priv->pipeline) { - if (priv->out_fmt.pixelformat == pix_fmt->pixelformat) { - GST_INFO("Same pixelformat with current: %s", - fourcc_str); - } else { - gchar current[5]; - - fourcc_to_string(priv->out_fmt.pixelformat, current); - GST_ERROR("Different pixelformat with current: " - "pixelformat:%s, current: %s", - fourcc_str, current); - errno = EINVAL; - return -1; - } - } else { - gboolean succeeded = init_pipeline(priv, pix_fmt->pixelformat); - if (!succeeded) - goto error; - } - - priv->out_fmt = *pix_fmt; - - set_params_as_encoded_stream(pix_fmt); - - if (!priv->cap_fmt.pixelformat && cap_fmts->len > 0) - priv->cap_fmt.pixelformat - = ((struct fmt*)cap_fmts->data)[0].fourcc; - - return 0; - - error: - return -1; -} - -static void -init_decoded_frame_params(struct v4l2_pix_format_mplane *pix_fmt) -{ - /* The following parameters will be determined after - the video decoding starts. */ - pix_fmt->width = 0; - pix_fmt->height = 0; - pix_fmt->num_planes = 0; - memset(pix_fmt->plane_fmt, 0, sizeof(pix_fmt->plane_fmt)); -} - -static int -set_fmt_ioctl_cap(struct v4l_gst *priv, struct v4l2_format *fmt) -{ - struct v4l2_pix_format_mplane *pix_fmt; - - pix_fmt = &fmt->fmt.pix_mp; - - if (!is_pix_fmt_supported((struct fmt*)priv->supported_cap_fmts->data, - priv->supported_cap_fmts->len, - pix_fmt->pixelformat)) { - GST_ERROR("Unsupported pixelformat on CAPTURE"); - errno = EINVAL; - return -1; - } - - GST_OBJECT_LOCK(priv->pipeline); - if (GST_STATE(priv->pipeline) == GST_STATE_NULL) { - priv->cap_fmt.pixelformat = pix_fmt->pixelformat; - init_decoded_frame_params(pix_fmt); - } else if (priv->cap_fmt.width != pix_fmt->width || - priv->cap_fmt.height != pix_fmt->height || - priv->cap_fmt.pixelformat != pix_fmt->pixelformat) { - /* TODO: Should check the pix_fmt more strictly. */ - gchar fourcc_str[5]; - fourcc_to_string(pix_fmt->pixelformat, fourcc_str); - GST_ERROR("Changing pixel format during playing isn't supported: " - "width: %u, height: %u, pixelformat: %s (0x%x)", - pix_fmt->width, pix_fmt->height, - fourcc_str, pix_fmt->pixelformat); - errno = EBUSY; - return -1; - } - GST_OBJECT_UNLOCK(priv->pipeline); - - /* set unsupported parameters */ - pix_fmt->field = V4L2_FIELD_NONE; - pix_fmt->colorspace = 0; - pix_fmt->flags = 0; - - return 0; -} - -int -set_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *fmt) -{ - int ret; - - GST_DEBUG("VIDIOC_S_FMT: type: %s (0x%x)", - v4l2_buffer_type_to_string(fmt->type), fmt->type); - - g_mutex_lock(&priv->dev_lock); - - if (fmt->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { - ret = set_fmt_ioctl_out(priv, fmt); - } else if (fmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { - ret = set_fmt_ioctl_cap(priv, fmt); - } else { - GST_ERROR("Invalid buffer type"); - errno = EINVAL; - ret = -1; - } - - g_mutex_unlock(&priv->dev_lock); - - return ret; -} - -static int -get_fmt_ioctl_cap(struct v4l_gst *priv, - struct v4l2_pix_format_mplane *pix_fmt) -{ - gint i; - gchar fourcc_str[5]; - - if (!g_atomic_int_get(&priv->is_cap_fmt_acquirable) || - priv->out_cnt < INPUT_BUFFERING_CNT) { - errno = EINVAL; - return -1; - } - - GST_DEBUG("cap format is acquirable. out_cnt = %d",priv->out_cnt); - - pix_fmt->width = priv->cap_fmt.width; - pix_fmt->height = priv->cap_fmt.height; - pix_fmt->pixelformat = priv->cap_fmt.pixelformat; - pix_fmt->field = V4L2_FIELD_NONE; - pix_fmt->colorspace = 0; - pix_fmt->flags = 0; - pix_fmt->num_planes = priv->cap_fmt.num_planes; - - fourcc_to_string(pix_fmt->pixelformat, fourcc_str); - GST_DEBUG("width:%d height:%d, format: %s (0x%x) num_planes=%d", - pix_fmt->width, pix_fmt->height, - fourcc_str, pix_fmt->pixelformat, - pix_fmt->num_planes); - - if (priv->cap_fmt.plane_fmt[0].sizeimage > 0) { - for (i = 0; i < pix_fmt->num_planes; i++) { - pix_fmt->plane_fmt[i].sizeimage = - priv->cap_fmt.plane_fmt[i].sizeimage; - pix_fmt->plane_fmt[i].bytesperline = - priv->cap_fmt.plane_fmt[i].bytesperline; - } - pix_fmt->num_planes = priv->cap_fmt.num_planes; - } else { - memset(pix_fmt->plane_fmt, 0, sizeof(pix_fmt->plane_fmt)); - } - - return 0; -} - -int -get_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *fmt) -{ - struct v4l2_pix_format_mplane *pix_fmt; - int ret; - - GST_DEBUG("VIDIOC_G_FMT: type: %s (0x%x)", - v4l2_buffer_type_to_string(fmt->type), fmt->type); - - pix_fmt = &fmt->fmt.pix_mp; - - if (fmt->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { - g_mutex_lock(&priv->dev_lock); - *pix_fmt = priv->out_fmt; - g_mutex_unlock(&priv->dev_lock); - set_params_as_encoded_stream(pix_fmt); - ret = 0; - } else if (fmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { - ret = get_fmt_ioctl_cap(priv, pix_fmt); - } else { - GST_ERROR("Invalid buffer type"); - errno = EINVAL; - ret = -1; - } - - return ret; -} - -int -enum_fmt_ioctl(struct v4l_gst *priv, struct v4l2_fmtdesc *desc) -{ - struct fmt *fmts; - gint fmts_num; - gchar fourcc_str[5]; - - GST_DEBUG("VIDIOC_ENUM_FMT: type: %s (0x%x) index: %d", - v4l2_buffer_type_to_string(desc->type), desc->type, desc->index); - - if (desc->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { - fmts = (struct fmt*)priv->supported_out_fmts->data; - fmts_num = priv->supported_out_fmts->len; - desc->flags = V4L2_FMT_FLAG_COMPRESSED; - } else if (desc->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { - fmts = (struct fmt*)priv->supported_cap_fmts->data; - fmts_num = priv->supported_cap_fmts->len; - desc->flags = 0; - } else { - GST_ERROR("Invalid buf type"); - errno = EINVAL; - return -1; - } - - if (fmts_num <= desc->index) { - GST_DEBUG(" Index %u is out of range", desc->index); - errno = EINVAL; - return -1; - } - - desc->pixelformat = fmts[desc->index].fourcc; - g_strlcpy((gchar *)desc->description, fmts[desc->index].desc, - sizeof(desc->description)); - memset(desc->reserved, 0, sizeof(desc->reserved)); - fourcc_to_string(desc->pixelformat, fourcc_str); - GST_DEBUG(" description: %s pixelformat: %s (0x%x)", - desc->description, fourcc_str, desc->pixelformat); - - return 0; -} - -int -enum_framesizes_ioctl(struct v4l_gst *priv, struct v4l2_frmsizeenum *argp) -{ - gchar fourcc_str[5]; - - fourcc_to_string(argp->pixel_format, fourcc_str); - GST_DEBUG("VIDIOC_ENUM_FRAMESIZES:" - " index: %d pixel_format: %s (0x%x)", - argp->index, fourcc_str, argp->pixel_format); - - switch (argp->pixel_format) { - case V4L2_PIX_FMT_GREY: - case V4L2_PIX_FMT_RGB565: - case V4L2_PIX_FMT_RGB24: - case V4L2_PIX_FMT_BGR24: - case V4L2_PIX_FMT_ABGR32: - case V4L2_PIX_FMT_XBGR32: - case V4L2_PIX_FMT_ARGB32: - case V4L2_PIX_FMT_XRGB32: - case V4L2_PIX_FMT_RGB32: - case V4L2_PIX_FMT_BGR32: - case V4L2_PIX_FMT_H264: - case V4L2_PIX_FMT_HEVC: - argp->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; - argp->stepwise.step_width = 1; - argp->stepwise.step_height = 1; - break; - case V4L2_PIX_FMT_NV12: - case V4L2_PIX_FMT_NV21: - case V4L2_PIX_FMT_YUV420: - case V4L2_PIX_FMT_YVU420: - case V4L2_PIX_FMT_NV12MT: - argp->type = V4L2_FRMSIZE_TYPE_STEPWISE; - argp->stepwise.step_width = 2; - argp->stepwise.step_height = 2; - break; - case V4L2_PIX_FMT_NV16: - case V4L2_PIX_FMT_YUYV: - case V4L2_PIX_FMT_UYVY: - case V4L2_PIX_FMT_YVYU: - case V4L2_PIX_FMT_YUV422P: - argp->type = V4L2_FRMSIZE_TYPE_STEPWISE; - argp->stepwise.step_width = 2; - argp->stepwise.step_height = 1; - break; - case V4L2_PIX_FMT_YVU410: - case V4L2_PIX_FMT_YUV410: - argp->type = V4L2_FRMSIZE_TYPE_STEPWISE; - argp->stepwise.step_width = 4; - argp->stepwise.step_height = 4; - break; - case V4L2_PIX_FMT_YUV411P: - argp->type = V4L2_FRMSIZE_TYPE_STEPWISE; - argp->stepwise.step_width = 4; - argp->stepwise.step_height = 1; - break; - } - argp->stepwise.min_width = 16; - argp->stepwise.min_height = 16; - argp->stepwise.max_width = priv->config.max_width ? - priv->config.max_width : 1920; - argp->stepwise.max_height = priv->config.max_height ? - priv->config.max_height : 1080; - - return 0; -} - -int -get_ctrl_ioctl(struct v4l_gst *priv, struct v4l2_control *ctrl) -{ - int ret; - - GST_DEBUG("VIDIOC_G_CTRL: id: 0x%x value: 0x%x", ctrl->id, ctrl->value); - - switch (ctrl->id) { - case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE: - ctrl->value = priv->config.cap_min_buffers; - ret = 0; - break; - default: - GST_ERROR("Invalid control id"); - errno = EINVAL; - ret = -1; - break; - } - - return ret; -} - -int -get_ext_ctrl_ioctl(struct v4l_gst *priv, struct v4l2_ext_controls *ext_ctrls) -{ - unsigned int i; - -#ifdef ENABLE_VIDIOC_DEBUG - char *vidioc_features = getenv(ENV_DISABLE_VIDIOC_FEATURES); - if (vidioc_features && strstr(vidioc_features, "VIDIOC_G_EXT_CTRLS")) { - GST_CAT_ERROR(v4l_gst_ioctl_debug_category, "unsupported VIDIOC_G_EXT_CTRLS"); - errno = ENOTTY; - return 0; - } -#endif - - GST_DEBUG("VIDIOC_G_EXT_CTRLS: count: %d", ext_ctrls->count); - - for (i = 0; i < ext_ctrls->count; i++) { - struct v4l2_ext_control *ext_ctrl = &ext_ctrls->controls[i]; - if (ext_ctrl->id == V4L2_CID_MIN_BUFFERS_FOR_CAPTURE) { - ext_ctrl->value = priv->config.cap_min_buffers; - continue; - } - errno = EINVAL; - return -1; - } - - return 0; -} - -static gboolean -is_supported_memory_io(enum v4l2_memory memory) -{ - if (memory != V4L2_MEMORY_MMAP) { - GST_ERROR("Only V4L2_MEMORY_MMAP is supported"); - errno = EINVAL; - return FALSE; - } - - return TRUE; -} - -static gboolean -get_raw_video_params(GstBufferPool *pool, GstBuffer *gstbuf, GstVideoInfo *info, - GstVideoMeta **meta) -{ - gboolean ret; - GstCaps *caps; - GstVideoInfo vinfo; - GstVideoMeta *vmeta; - - get_buffer_pool_params(pool, &caps, NULL, NULL, NULL); - - ret = gst_video_info_from_caps(&vinfo, caps); - if (!ret || GST_VIDEO_INFO_FORMAT(&vinfo) == GST_VIDEO_FORMAT_ENCODED) - return FALSE; - - vmeta = gst_buffer_get_video_meta(gstbuf); - if (!vmeta) - return FALSE; - - if (info) - memcpy(info, &vinfo, sizeof(GstVideoInfo)); - if (meta) - *meta = vmeta; - - return TRUE; -} - -/* This check passes through the verification of the buffer index. */ -static gboolean -check_no_index_v4l2_buffer(struct v4l2_buffer *v4l2buf, - struct v4l_gst_buffer *buffers, GstBufferPool *pool) -{ - GstVideoMeta *meta; - guint n_planes; - - if (!is_supported_memory_io(v4l2buf->memory)) - return FALSE; - - if (!v4l2buf->m.planes) { - GST_ERROR("This plugin supports only multi-planar " - "buffer type, but planes array is not set"); - errno = EINVAL; - return FALSE; - } - - if (!buffers) { - GST_ERROR("Buffers list is not set"); - errno = EINVAL; - return FALSE; - } - - if (get_raw_video_params(pool, buffers[v4l2buf->index].gstbuf, NULL, - &meta)) - n_planes = meta->n_planes; - else - n_planes = 1; - - if (v4l2buf->length < n_planes || v4l2buf->length > VIDEO_MAX_PLANES) { - GST_ERROR("Incorrect planes array length"); - errno = EINVAL; - return FALSE; - } - - return TRUE; -} - -static gboolean -check_v4l2_buffer(struct v4l2_buffer *v4l2buf, struct v4l_gst_buffer *buffers, - gint buffers_num, GstBufferPool *pool) -{ - if (!check_no_index_v4l2_buffer(v4l2buf, buffers, pool)) - return FALSE; - - if (v4l2buf->index >= buffers_num) { - GST_ERROR("buffer index is out of range"); - errno = EINVAL; - return FALSE; - } - - return TRUE; -} - -static void -notify_unref(gpointer data) -{ - struct v4l_gst_buffer *buffer = data; - - release_out_buffer(buffer->priv, buffer->gstbuf); -} - -static int -qbuf_ioctl_out(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) -{ - GstFlowReturn flow_ret; - GstBuffer *wrapped_gstbuf; - GstMapInfo info; - struct v4l_gst_buffer *buffer; - - if (!check_v4l2_buffer(v4l2buf, priv->out_buffers, priv->out_buffers_num, - priv->src_pool)) - return -1; - - buffer = &priv->out_buffers[v4l2buf->index]; - - if (v4l2buf->m.planes[0].bytesused == 0) { - flow_ret = gst_app_src_end_of_stream(GST_APP_SRC(priv->appsrc)); - if (flow_ret != GST_FLOW_OK) { - GST_ERROR("Failed to send an EOS event"); - errno = EINVAL; - return -1; - } - GST_DEBUG("Send EOS event"); - - gst_buffer_unmap(buffer->gstbuf, &buffer->info); - memset(&buffer->info, 0, sizeof(buffer->info)); - - buffer->state = V4L_GST_BUFFER_QUEUED; - priv->eos_gstbuf = buffer->gstbuf; - - return 0; - } - - if (buffer->state == V4L_GST_BUFFER_QUEUED) { - GST_ERROR("Buffer %u is already queued", v4l2buf->index); - errno = EINVAL; - return -1; - } - - GST_TRACE("queue index=%d buffer=%p", v4l2buf->index, - priv->out_buffers[v4l2buf->index].gstbuf); - - gst_buffer_unmap(buffer->gstbuf, &buffer->info); - memset(&buffer->info, 0, sizeof(buffer->info)); - - /* Rewrap an input buffer with the just size of bytesused - because it will be regarded as having data filled to the entire - buffer size internally in the GStreamer pipeline. - Also set the destructor (notify_unref()). */ - - if (!gst_buffer_map(buffer->gstbuf, &info, GST_MAP_READ)) { - GST_ERROR("Failed to map buffer (%p)", buffer->gstbuf); - errno = EINVAL; - return -1; - } - - wrapped_gstbuf = - gst_buffer_new_wrapped_full(GST_MEMORY_FLAG_READONLY, info.data, - v4l2buf->m.planes[0].bytesused, 0, - v4l2buf->m.planes[0].bytesused, - buffer, notify_unref); - - gst_buffer_unmap(buffer->gstbuf, &info); - - GST_BUFFER_PTS(wrapped_gstbuf) = GST_TIMEVAL_TO_TIME(v4l2buf->timestamp); - GST_CAT_TRACE(v4l_gst_buffer_debug_category, - "QBUF OUT: gstbuf=%p, index=%d, pts=%lu", - wrapped_gstbuf, v4l2buf->index, - GST_BUFFER_PTS(wrapped_gstbuf) / 1000000); - - buffer->state = V4L_GST_BUFFER_QUEUED; - - flow_ret = gst_app_src_push_buffer(GST_APP_SRC(priv->appsrc), - wrapped_gstbuf); - if (flow_ret != GST_FLOW_OK) { - GST_ERROR("Failed to push a buffer to the pipeline on OUTPUT" - "(index=%d)", v4l2buf->index); - errno = EINVAL; - return -1; - } - - if (priv->out_cnt < INPUT_BUFFERING_CNT) - priv->out_cnt++; - - return 0; -} - -static gboolean -push_to_cap_gstbufs_queue(struct v4l_gst *priv, GstBuffer *gstbuf) -{ - gboolean is_empty; - gint index; - - index = g_queue_index(priv->req_gstbufs_queue, gstbuf); - if (index < 0) - return FALSE; - - g_mutex_lock(&priv->queue_mutex); - - is_empty = g_queue_is_empty(priv->cap_gstbufs_queue); - g_queue_push_tail(priv->cap_gstbufs_queue, gstbuf); - - if (is_empty) - g_cond_signal(&priv->queue_cond); - - g_mutex_unlock(&priv->queue_mutex); - - g_queue_pop_nth_link(priv->req_gstbufs_queue, index); - - return TRUE; -} - -static int -qbuf_ioctl_cap(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) -{ - struct v4l_gst_buffer *buffer; - - if (!check_v4l2_buffer(v4l2buf, priv->cap_buffers, priv->cap_buffers_num, - priv->sink_pool)) - return -1; - - buffer = &priv->cap_buffers[v4l2buf->index]; - - if (priv->config.frame_check && gst_buffer_n_memory(buffer->gstbuf)) { - gpointer crc_p; - guint32 crc, crc_dest; - - crc_p = gst_mini_object_get_qdata(GST_MINI_OBJECT(buffer->gstbuf), - cap_buf_crc_quark()); - crc = GPOINTER_TO_UINT(crc_p); - crc_dest = frame_crc32(buffer->gstbuf, - priv->config.frame_check); - GST_CAT_TRACE(v4l_gst_buffer_debug_category, - "QBUF CAP: gstbuf=%p, index=%d, pts=%lu, crc=%u", - buffer->gstbuf, v4l2buf->index, - GST_BUFFER_PTS(buffer->gstbuf) / 1000000, crc); - if (crc && crc_dest != crc) - GST_CAT_WARNING(v4l_gst_buffer_debug_category, - "crc is changed!: src=%u, dest=%u", - crc, crc_dest); - } else { - GST_CAT_TRACE(v4l_gst_buffer_debug_category, - "QBUF CAP: gstbuf=%p, index=%d, pts=%lu", - buffer->gstbuf, v4l2buf->index, - GST_BUFFER_PTS(buffer->gstbuf) / 1000000); - } - - if (buffer->state == V4L_GST_BUFFER_QUEUED) { - GST_ERROR("Buffer %u is already queued", v4l2buf->index); - errno = EINVAL; - return -1; - } - - gst_buffer_unmap(buffer->gstbuf, &buffer->info); - memset(&buffer->info, 0, sizeof(buffer->info)); - - /* The buffers in req_gstbufs_queue, which are pushed by the REQBUF ioctl - on CAPTURE, have already contained decoded frames. - They should not back to the buffer pool and prepare to be - dequeued as they are. */ - if (g_queue_get_length(priv->req_gstbufs_queue) > 0) { - GST_TRACE("push_to_cap_gstbufs_queue index=%d", v4l2buf->index); - if (push_to_cap_gstbufs_queue(priv, buffer->gstbuf)) { - buffer->state =V4L_GST_BUFFER_QUEUED; - return 0; - } - } - - GST_TRACE("unref buffer: %p, index=%d", buffer->gstbuf, v4l2buf->index); - buffer->state = V4L_GST_BUFFER_QUEUED; - - gst_buffer_unref(buffer->gstbuf); - - return 0; -} - -int -qbuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) -{ - int ret = -1; - - GST_TRACE("VIDIOC_QBUF: type: %s (0x%x) index: %d flags: 0x%x", - v4l2_buffer_type_to_string(v4l2buf->type), v4l2buf->type, - v4l2buf->index, v4l2buf->flags); - - g_mutex_lock(&priv->dev_lock); - - if (v4l2buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { - ret = qbuf_ioctl_out(priv, v4l2buf); - } else if (v4l2buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { - ret = qbuf_ioctl_cap(priv, v4l2buf); - } else { - GST_ERROR("Invalid buf type"); - errno = EINVAL; - } - - g_mutex_unlock(&priv->dev_lock); - - return ret; -} - -static inline guint -calc_plane_size(GstVideoInfo *info, GstVideoMeta *meta, gint index) -{ - return meta->stride[index] * GST_VIDEO_INFO_COMP_HEIGHT(info, index); -} - -static void -set_v4l2_buffer_plane_params(struct v4l_gst *priv, - struct v4l_gst_buffer *buffers, guint n_planes, - guint bytesused[], struct timeval *timestamp, - struct v4l2_buffer *v4l2buf) -{ - gint i; - guint32 offset = 0; - - memcpy(v4l2buf->m.planes, buffers[v4l2buf->index].planes, - sizeof(struct v4l2_plane) * n_planes); - - for (i = 0; i < n_planes; i++) { - if (bytesused) - v4l2buf->m.planes[i].bytesused = bytesused[i]; - /* Use `data_offset` to notify offsets of multi-planes combined - in single dmabuf fd. Most applications will ignore this - field, so you'll need to modify such applications. */ - if (gst_buffer_n_memory(buffers[v4l2buf->index].gstbuf) == 1) { - v4l2buf->m.planes[i].data_offset = offset; - offset += v4l2buf->m.planes[i].length; - } - } - - if (timestamp) { - v4l2buf->timestamp.tv_sec = timestamp->tv_sec; - v4l2buf->timestamp.tv_usec = timestamp->tv_usec; - } else { - v4l2buf->timestamp.tv_sec = v4l2buf->timestamp.tv_usec = 0; - } -} - -static int -fill_v4l2_buffer(struct v4l_gst *priv, GstBufferPool *pool, - struct v4l_gst_buffer *buffers, gint buffers_num, - guint bytesused[], struct timeval *timestamp, - struct v4l2_buffer *v4l2buf) -{ - GstVideoMeta *meta = NULL; - guint n_planes; - - get_raw_video_params(pool, buffers[v4l2buf->index].gstbuf, NULL, &meta); - - n_planes = (meta) ? meta->n_planes : 1; - - set_v4l2_buffer_plane_params(priv, buffers, n_planes, bytesused, - timestamp, v4l2buf); - - v4l2buf->flags = 0; - if (v4l2buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && - priv->eos_state == EOS_GOT && - g_queue_is_empty(priv->cap_gstbufs_queue)) { - GST_DEBUG("EOS: Set V4L2_BUF_FLAG_LAST"); - v4l2buf->flags |= V4L2_BUF_FLAG_LAST; - priv->eos_state = EOS_NONE; - clear_event(priv->event_state, POLLOUT); - } - - /* set unused params */ - memset(&v4l2buf->timecode, 0, sizeof(v4l2buf->timecode)); - v4l2buf->sequence = 0; - v4l2buf->field = V4L2_FIELD_NONE; - - v4l2buf->length = n_planes; - - return 0; -} - -static guint -get_v4l2_buffer_index(struct v4l_gst_buffer *buffers, gint buffers_num, - GstBuffer *gstbuf) -{ - gint i; - guint index = G_MAXUINT; - - for (i = 0; i < buffers_num; i++) { - if (buffers[i].gstbuf == gstbuf) { - index = i; - break; - } - } - - return index; -} - -static GstBuffer * -dequeue_blocking(struct v4l_gst *priv, GQueue *queue, GCond *cond) -{ - GstBuffer *gstbuf; - - gstbuf = g_queue_pop_head(queue); - while (!gstbuf && priv->is_pipeline_started) { - g_cond_wait(cond, &priv->queue_mutex); - gstbuf = g_queue_pop_head(queue); - } - - return gstbuf; -} - -static GstBuffer * -dequeue_non_blocking(GQueue *queue) -{ - GstBuffer *gstbuf; - - gstbuf = g_queue_pop_head(queue); - if (!gstbuf) { - GST_TRACE("The buffer pool is empty in " - "the non-blocking mode, return EAGAIN"); - errno = EAGAIN; - } - - return gstbuf; -} - -static GstBuffer * -dequeue_cap_buffer(struct v4l_gst *priv) -{ - GQueue *queue = priv->cap_gstbufs_queue; - GstBuffer *gstbuf = NULL; - guint len; - - g_mutex_lock(&priv->queue_mutex); - - /* Cache 1 buffer to detect EOS */ - len = g_queue_get_length(queue); - if (priv->eos_state != EOS_GOT && len == 1) - goto unlock; - - if (priv->is_non_blocking) - gstbuf = dequeue_non_blocking(queue); - else - gstbuf = dequeue_blocking(priv, queue, &priv->queue_cond); - - if (!gstbuf) - goto unlock; - - /* Cache 1 buffer to detect EOS */ - len = g_queue_get_length(queue); - if ((priv->eos_state != EOS_GOT && len == 1) || len == 0) - clear_event(priv->event_state, POLLOUT); - - unlock: - g_mutex_unlock(&priv->queue_mutex); - - return gstbuf; -} - -static GstBuffer * -acquire_buffer_from_pool(struct v4l_gst *priv, GstBufferPool *pool) -{ - GstFlowReturn flow_ret; - GstBuffer *gstbuf; - GstBufferPoolAcquireParams params = { 0, }; - - if (priv->is_non_blocking) { - params.flags |= GST_BUFFER_POOL_ACQUIRE_FLAG_DONTWAIT; - } else - g_mutex_unlock(&priv->queue_mutex); - - flow_ret = gst_buffer_pool_acquire_buffer(pool, &gstbuf, ¶ms); - if (!priv->is_non_blocking) - g_mutex_lock(&priv->queue_mutex); - - if (priv->is_non_blocking && flow_ret == GST_FLOW_EOS) { - GST_TRACE("The buffer pool is empty in " - "the non-blocking mode, return EAGAIN"); - errno = EAGAIN; - return NULL; - } else if (flow_ret != GST_FLOW_OK) { - GST_ERROR("gst_buffer_pool_acquire_buffer failed"); - errno = EINVAL; - return NULL; - } - - return gstbuf; -} - -/* gst-omx may reassociate GstBuffers and dmabuf-fds without closing them after - flushing. Since clients will associate a dmabuf-fd with a v4l2_buffer's - index, we need to update GstBuffer-index association after flushing to avoid - misalign with clients. */ -static void -reindex_buffers(struct v4l_gst *priv) -{ - int i, j; - gchar fd_str[8], error_fds[128] = {0}; - gchar opened_fds[128] = {0}, initial_fds[128] = {0}; - - for (i = 0; i < priv->cap_buffers_num; ++i) { - struct v4l_gst_buffer *buf1 = &priv->cap_buffers[i]; - GstMemory *mem; - int fd; - - g_snprintf(fd_str, sizeof(fd_str), "%d:%d,", - i, buf1->plane0_fd); - g_strlcat(initial_fds, fd_str, sizeof(initial_fds)); - - if (!buf1->plane0_fd) - continue; - if (buf1->state == V4L_GST_BUFFER_DEQUEUED) - continue; - if (!gst_buffer_n_memory(buf1->gstbuf)) - continue; - - mem = gst_buffer_peek_memory(buf1->gstbuf, 0); - if (!gst_is_dmabuf_memory(mem)) - continue; - - fd = gst_dmabuf_memory_get_fd(mem); - - g_snprintf(fd_str, sizeof(fd_str), "%d:%d,", i, fd); - g_strlcat(opened_fds, fd_str, sizeof(opened_fds)); - - if (buf1->plane0_fd == fd) - continue; - - for (j = 0; j < priv->cap_buffers_num; ++j) { - struct v4l_gst_buffer *buf2 = &priv->cap_buffers[j]; - GstBuffer *gstbuf = buf1->gstbuf; - - if (i == j) - continue; - if (buf2->plane0_fd != fd) - continue; - if (buf2->state == V4L_GST_BUFFER_DEQUEUED) - continue; - - buf1->gstbuf = buf2->gstbuf; - buf2->gstbuf = gstbuf; - i--; - break; - } - if (j >= priv->cap_buffers_num) { - g_snprintf(fd_str, sizeof(fd_str), "%d:%d,", i, fd); - g_strlcat(error_fds, fd_str, sizeof(error_fds)); - } - } - - if (*error_fds) { - GST_WARNING("Failed to reindex dmabuf fd!"); - GST_DEBUG("failed fds: %s / opened_fds: %s / initial_fds: %s", - error_fds, opened_fds, initial_fds); - } -} - -static int -dqbuf_ioctl_out(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) -{ - GstBuffer *gstbuf; - guint index; - - if (!priv->is_pipeline_started) { - GST_ERROR("The pipeline does not start yet."); - errno = EINVAL; - return -1; - } - - if (!check_no_index_v4l2_buffer(v4l2buf, priv->out_buffers, - priv->src_pool)) - return -1; - - g_mutex_lock(&priv->queue_mutex); - - gstbuf = acquire_buffer_from_pool(priv, priv->src_pool); - if (!gstbuf) { - g_mutex_unlock(&priv->queue_mutex); - return -1; - } - - priv->returned_out_buffers_num--; - - if (priv->returned_out_buffers_num == 0) { - clear_event(priv->event_state, POLLIN); - } - - - g_mutex_unlock(&priv->queue_mutex); - - index = get_v4l2_buffer_index(priv->out_buffers, - priv->out_buffers_num, gstbuf); - if (index >= priv->out_buffers_num) { - GST_ERROR("Failed to get a valid buffer index " - "on OUTPUT"); - errno = EINVAL; - return -1; - } - - v4l2buf->index = index; - priv->out_buffers[v4l2buf->index].state = V4L_GST_BUFFER_DEQUEUED; - - GST_CAT_TRACE(v4l_gst_buffer_debug_category, - "DQBUF OUT: gstbuf=%p, index=%d", - gstbuf, index); - - return fill_v4l2_buffer(priv, priv->src_pool, - priv->out_buffers, priv->out_buffers_num, - NULL, NULL, v4l2buf); -} - -static gboolean -get_valid_cap_pts(struct v4l_gst *priv, GstBuffer *gstbuf, - GstClockTime *pts) -{ - GstClockTime buffer_pts = GST_BUFFER_PTS(gstbuf); - GstClockTime buffer_duration = GST_BUFFER_DURATION(gstbuf); - - if (GST_CLOCK_TIME_IS_VALID(buffer_pts)) { - if (GST_CLOCK_TIME_IS_VALID(priv->last_cap_pts) && - buffer_pts > priv->last_cap_pts) { - priv->estimated_cap_duration = - buffer_pts - priv->last_cap_pts; - } else if (!GST_CLOCK_TIME_IS_VALID(priv->estimated_cap_duration) && - GST_CLOCK_TIME_IS_VALID(buffer_duration) && - buffer_duration > 0) { - priv->estimated_cap_duration = buffer_duration; - } - - priv->last_cap_pts = buffer_pts; - *pts = buffer_pts; - return TRUE; - } - - if (GST_CLOCK_TIME_IS_VALID(priv->last_cap_pts) && - GST_CLOCK_TIME_IS_VALID(priv->estimated_cap_duration) && - G_MAXUINT64 - priv->last_cap_pts >= priv->estimated_cap_duration) { - /* Some decoders may output the last CAPTURE buffer without a - valid PTS. Do not expose GST_CLOCK_TIME_NONE as a V4L2 - timestamp because clients can interpret it as a huge future - timestamp and wait indefinitely for playback end. */ - *pts = priv->last_cap_pts + priv->estimated_cap_duration; - GST_WARNING("CAPTURE buffer has invalid PTS; using estimated PTS " - "(last=%" G_GUINT64_FORMAT ", duration=%" - G_GUINT64_FORMAT ", estimated=%" G_GUINT64_FORMAT ")", - (guint64) priv->last_cap_pts, - (guint64) priv->estimated_cap_duration, - (guint64) *pts); - priv->last_cap_pts = *pts; - return TRUE; - } - - GST_WARNING("CAPTURE buffer has invalid PTS and no estimate is available"); - return FALSE; -} - -static int -dqbuf_ioctl_cap(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) -{ - GstBuffer *gstbuf; - guint index; - struct timeval timestamp; - struct timeval *timestamp_ptr = NULL; - GstClockTime pts = GST_CLOCK_TIME_NONE; - guint bytesused[GST_VIDEO_MAX_PLANES]; - gint i; - - if (!check_no_index_v4l2_buffer(v4l2buf, priv->cap_buffers, - priv->sink_pool)) - return -1; - - gstbuf = dequeue_cap_buffer(priv); - if (!gstbuf) - return -1; - - reindex_buffers(priv); - - index = get_v4l2_buffer_index(priv->cap_buffers, - priv->cap_buffers_num, gstbuf); - if (index >= priv->cap_buffers_num) { - GST_ERROR("Failed to get a valid buffer index " - "on CAPTURE"); - errno = EINVAL; - gst_buffer_unref(gstbuf); - return -1; - } - - v4l2buf->index = index; - - for (i = 0; i < priv->cap_fmt.num_planes; i++) - bytesused[i] = priv->cap_fmt.plane_fmt[i].sizeimage; - - if (priv->cap_buffers[index].state == V4L_GST_BUFFER_DEQUEUED) { - /* It might occur when a buffer is unexpectedly queued - after streamoff_ioctl_out(). In this case reference count of - the buffer should already have been incremented, need to - revert it here. */ - GST_WARNING("Already dequeued buffer %u is dequeued again", index); - gst_buffer_unref(priv->cap_buffers[index].gstbuf); - } - priv->cap_buffers[v4l2buf->index].state = V4L_GST_BUFFER_DEQUEUED; - - if (get_valid_cap_pts(priv, gstbuf, &pts)) { - GST_TIME_TO_TIMEVAL(pts, timestamp); - timestamp_ptr = ×tamp; - } - GST_CAT_TRACE(v4l_gst_buffer_debug_category, - "DQBUF CAP: gstbuf=%p, index=%d, pts=%lu, raw_pts=%lu", - gstbuf, index, pts / 1000000, - GST_BUFFER_PTS(gstbuf) / 1000000); - - return fill_v4l2_buffer(priv, priv->sink_pool, - priv->cap_buffers, priv->cap_buffers_num, - bytesused, timestamp_ptr, v4l2buf); -} - -int -dqbuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) -{ - int ret = -1; - - GST_TRACE("VIDIOC_DQBUF: type: %s (0x%x) index: %d flags: 0x%x", - v4l2_buffer_type_to_string(v4l2buf->type), v4l2buf->type, - v4l2buf->index, v4l2buf->flags); - - g_mutex_lock(&priv->dev_lock); - - if (v4l2buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { - ret = dqbuf_ioctl_out(priv, v4l2buf); - } else if (v4l2buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { - ret = dqbuf_ioctl_cap(priv, v4l2buf); - } else { - GST_ERROR("Invalid buf type"); - errno = EINVAL; - } - - g_mutex_unlock(&priv->dev_lock); - - return ret; -} - -int -querybuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) -{ - struct v4l_gst_buffer *buffers; - gint buffers_num; - GstBufferPool *pool; - int ret; - - GST_TRACE("VIDIOC_QUERYBUF: type: %s (0x%x) index: %d flags: 0x%x", - v4l2_buffer_type_to_string(v4l2buf->type), v4l2buf->type, - v4l2buf->index, v4l2buf->flags); - - g_mutex_lock(&priv->dev_lock); - - if (v4l2buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { - buffers = priv->out_buffers; - buffers_num = priv->out_buffers_num; - pool = priv->src_pool; - } else if (v4l2buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { - buffers = priv->cap_buffers; - buffers_num = priv->cap_buffers_num; - pool = priv->sink_pool; - } else { - GST_ERROR("Invalid buf type"); - errno = EINVAL; - g_mutex_unlock(&priv->dev_lock); - return -1; - } - - if (!check_v4l2_buffer(v4l2buf, buffers, buffers_num, pool)) { - g_mutex_unlock(&priv->dev_lock); - return -1; - } - - ret = fill_v4l2_buffer(priv, pool, buffers, buffers_num, - NULL, NULL, v4l2buf); - - g_mutex_unlock(&priv->dev_lock); - - return ret; -} - -static GstCaps * -get_codec_caps_from_fourcc(guint fourcc) -{ - const gchar *mime; - - mime = fourcc_to_mimetype(fourcc); - if (!mime) { - gchar fourcc_str[5]; - fourcc_to_string(fourcc, fourcc_str); - GST_ERROR("Failed to convert from fourcc to mime string: %u (\"%s\")", - fourcc, fourcc_str); - return NULL; - } - - if (g_strcmp0(mime, GST_VIDEO_CODEC_MIME_H264) == 0 || - g_strcmp0(mime, GST_VIDEO_CODEC_MIME_HEVC) == 0) { - return gst_caps_new_simple(mime, "stream-format", - G_TYPE_STRING, "byte-stream", NULL); - } - - return gst_caps_new_empty_simple(mime); -} - -#define PAGE_ALIGN(off, align) ((off + align - 1) & ~(align - 1)) - -static gsize -set_mem_offset(struct v4l_gst_buffer *buffer, GstBufferPool *pool, gsize offset) -{ - GstVideoInfo info; - GstVideoMeta *meta; - static long page_size = -1; - gint i; - - if (page_size < 0) - page_size = sysconf(_SC_PAGESIZE); - - if (!get_raw_video_params(pool, buffer->gstbuf, &info, &meta)) { - /* deal with this as a single plane */ - buffer->planes[0].m.mem_offset = offset; - return PAGE_ALIGN(gst_buffer_get_size(buffer->gstbuf), - page_size) + offset; - } - - if (meta) { - for (i = 0; i < meta->n_planes; i++) { - buffer->planes[i].m.mem_offset = offset; - offset += PAGE_ALIGN(calc_plane_size(&info, meta, i), - page_size); - } - } - - return offset; -} - -static guint -alloc_buffers_from_pool(struct v4l_gst *priv, GstBufferPool *pool, - struct v4l_gst_buffer **buffers) -{ - GstBufferPoolAcquireParams params = { 0, }; - GstFlowReturn flow_ret; - guint actual_max_buffers; - struct v4l_gst_buffer *bufs_list; - gint i; - - if (!gst_buffer_pool_set_active(pool, TRUE)) { - GST_ERROR("Failed to activate buffer pool on OUTPUT"); - errno = EINVAL; - return 0 ; - } - - /* The buffer pool parameters can not be changed after activation, - so it is good time to confirm the number of buffers actually set to - the buffer pool. */ - get_buffer_pool_params(pool, NULL, NULL, NULL, &actual_max_buffers); - if (actual_max_buffers == 0) { - GST_ERROR("Cannot handle the unlimited amount of buffers"); - errno = EINVAL; - goto inactivate_pool; - } - - bufs_list = g_new0(struct v4l_gst_buffer, actual_max_buffers); - - for (i = 0; i < actual_max_buffers; i++) { - flow_ret = gst_buffer_pool_acquire_buffer(pool, - &bufs_list[i].gstbuf, - ¶ms); - if (flow_ret != GST_FLOW_OK) { - GST_ERROR("Failed to acquire a buffer on OUTPUT"); - errno = ENOMEM; - goto free_bufs_list; - } - - bufs_list[i].priv = priv; - bufs_list[i].state = V4L_GST_BUFFER_DEQUEUED; - - GST_DEBUG("out gst_buffer[%d] : %p", i, bufs_list[i].gstbuf); - } - - *buffers = bufs_list; - - GST_DEBUG("The number of buffers actually set to the buffer pool is %d", - actual_max_buffers); - - return actual_max_buffers; - - /* error cases */ - free_bufs_list: - for (i = 0; i < actual_max_buffers; i++) { - if (bufs_list[i].gstbuf) - gst_buffer_unref(bufs_list[i].gstbuf); - } - g_free(bufs_list); - inactivate_pool: - gst_buffer_pool_set_active(pool, FALSE); - - return 0; -} - -static GstFlowReturn -force_dqbuf_from_pool(GstBufferPool *pool, struct v4l_gst_buffer *buffers, - gint buffers_num, gboolean map) -{ - GstFlowReturn flow_ret; - GstBufferPoolAcquireParams params = { 0, }; - GstBuffer *gstbuf; - guint index; - - params.flags = GST_BUFFER_POOL_ACQUIRE_FLAG_DONTWAIT; - - /* force to make buffers available to the V4L2 caller side */ - flow_ret = gst_buffer_pool_acquire_buffer(pool, &gstbuf, ¶ms); - if (flow_ret != GST_FLOW_OK) - return flow_ret; - - index = get_v4l2_buffer_index(buffers, buffers_num, gstbuf); - if (index >= buffers_num) { - GST_ERROR("Failed to get a valid buffer index"); - errno = EINVAL; - return GST_FLOW_ERROR; - } - - buffers[index].state = V4L_GST_BUFFER_DEQUEUED; - - if (!map) - return GST_FLOW_OK; - - if (!gst_buffer_map(gstbuf, &buffers[index].info, - buffers[index].flags)) { - GST_ERROR("Failed to map buffer (%p)", gstbuf); - errno = EINVAL; - return GST_FLOW_ERROR; - } - return GST_FLOW_OK; -} - -static int -force_out_dqbuf(struct v4l_gst *priv) -{ - g_mutex_lock(&priv->queue_mutex); - - while (force_dqbuf_from_pool(priv->src_pool, priv->out_buffers, - priv->out_buffers_num, TRUE) == GST_FLOW_OK) { - priv->returned_out_buffers_num--; - } - - clear_event(priv->event_state, POLLIN); - - g_mutex_unlock(&priv->queue_mutex); - - GST_DEBUG("returned_out_buffers_num : %d", priv->returned_out_buffers_num); - - return 0; -} - -static int -force_cap_dqbuf(struct v4l_gst *priv) -{ - GstBuffer *gstbuf; - guint index; - - do { - g_mutex_lock(&priv->queue_mutex); - gstbuf = dequeue_non_blocking(priv->cap_gstbufs_queue); - /* This function may set errno but not need to expose it to - clients in this case */ - errno = 0; - g_mutex_unlock(&priv->queue_mutex); - - if (!gstbuf) - break; - - index = get_v4l2_buffer_index(priv->cap_buffers, - priv->cap_buffers_num, gstbuf); - if (index >= priv->cap_buffers_num) { - GST_ERROR("Failed to get a valid buffer index " - "on CAPTURE"); - errno = EINVAL; - return -1; - } - - priv->cap_buffers[index].state = V4L_GST_BUFFER_DEQUEUED; - GST_DEBUG("CAPTURE buffer %u is forcedly dequeued", index); - } while (gstbuf); - - clear_event(priv->event_state, POLLOUT); - - for (index = 0; index < priv->cap_buffers_num; index++) { - if (priv->cap_buffers[index].state == V4L_GST_BUFFER_DEQUEUED) - continue; - gst_buffer_ref(priv->cap_buffers[index].gstbuf); - priv->cap_buffers[index].state = V4L_GST_BUFFER_DEQUEUED; - } - - return 0; -} - -static int -flush_pipeline(struct v4l_gst *priv) -{ - GstEvent *event; - - GST_DEBUG("flush start"); - - gst_buffer_pool_set_flushing(priv->src_pool, TRUE); - gst_buffer_pool_set_flushing(priv->sink_pool, TRUE); - - event = gst_event_new_flush_start(); - if (!gst_element_send_event(priv->pipeline, event)) { - GST_ERROR("Failed to send a flush start event"); - errno = EINVAL; - return -1; - } - - GST_DEBUG("flush stop ..."); - - event = gst_event_new_flush_stop(TRUE); - if (!gst_element_send_event(priv->pipeline, event)) { - GST_ERROR("Failed to send a flush stop event"); - errno = EINVAL; - return -1; - } - - gst_buffer_pool_set_flushing(priv->src_pool, FALSE); - gst_buffer_pool_set_flushing(priv->sink_pool, FALSE); - - GST_DEBUG("flush end"); - - return 0; -} - -static int -streamoff_ioctl_out(struct v4l_gst *priv, gboolean steal_ref) -{ - int ret; - - GST_CAT_DEBUG(v4l_gst_buffer_debug_category, - "STREAMOFF OUT begin: dequeue all OUT & CAP buffers ..."); - - set_pipeline_started(priv, FALSE); - - GST_OBJECT_LOCK(priv->pipeline); - if (GST_STATE(priv->pipeline) == GST_STATE_NULL) { - /* No need to flush the pipeline after it has been - the NULL state. */ - GST_OBJECT_UNLOCK(priv->pipeline); - goto flush_buffer_queues; - } - GST_OBJECT_UNLOCK(priv->pipeline); - - - ret = flush_pipeline(priv); - - if (ret < 0) - return ret; - - flush_buffer_queues: - /* Vacate the buffers queues to make them available in the next time */ - ret = force_out_dqbuf(priv); - if (ret < 0) - return ret; - - ret = force_cap_dqbuf(priv); - if (ret < 0) - return ret; - - /* The reference counted up below will be unreffed when calling - the streamon ioctl. This prevents from returning all the buffers - of the OUTPUT bufferpool and freeing them by inactivating - the bufferpool for flushing. */ - if (steal_ref) - gst_buffer_ref(priv->out_buffers[0].gstbuf); - - /* wake up blocking of the OUTPUT buffer acquisition */ - if (!gst_buffer_pool_set_active(priv->src_pool, FALSE)) { - GST_ERROR("Failed to inactivate buffer pool on OUTPUT"); - errno = EINVAL; - return -1; - } - - /* wake up blocking of the CAPTURE buffer acquisition */ - set_pipeline_started(priv, FALSE); - - GST_CAT_DEBUG(v4l_gst_buffer_debug_category, "STREAMOFF OUT end"); - - return 0; -} - -static int -reqbuf_ioctl_out(struct v4l_gst *priv, - struct v4l2_requestbuffers *req) -{ - GstCaps *caps; - guint adjusted_count; - guint allocated_num; - int ret; - guint i; - - if (!is_supported_memory_io(req->memory)) - return -1; - - g_mutex_lock(&priv->dev_lock); - - if (req->count == 0) { - GST_DEBUG("req->count == 0"); - - /* The following function flushes both the OUTPUT and CAPTURE - buffer types because the GStreamer can only flush the whole - of the pipeline, so the buffers of both the buffer types - need to be requeued after this operation. - */ - ret = streamoff_ioctl_out(priv, FALSE); - if (ret < 0) - goto unlock; - - /* Force to return dequeued buffers to the buffer pool. */ - for (i = 0; i < priv->out_buffers_num; i++) { - if (priv->out_buffers[i].state == - V4L_GST_BUFFER_DEQUEUED) { - gst_buffer_unref(priv->out_buffers[i].gstbuf); - } - } - - if (priv->out_buffers) { - g_free(priv->out_buffers); - priv->out_buffers = NULL; - } - - ret = 0; - goto unlock; - } - - if (priv->is_pipeline_started) { - GST_ERROR("The pipeline is already running"); - errno = EBUSY; - ret = -1; - goto unlock; - } - - if (gst_buffer_pool_is_active(priv->src_pool)) { - if (!gst_buffer_pool_set_active(priv->src_pool, FALSE)) { - GST_ERROR("Failed to inactivate buffer pool"); - errno = EBUSY; - ret = -1; - goto unlock; - } - } - - caps = get_codec_caps_from_fourcc(priv->out_fmt.pixelformat); - if (!caps) { - errno = EINVAL; - ret = -1; - goto unlock; - } - - adjusted_count = MAX(req->count, INPUT_BUFFERING_CNT); - adjusted_count = MIN(adjusted_count, VIDEO_MAX_FRAME); - - set_buffer_pool_params(priv->src_pool, caps, - priv->out_fmt.plane_fmt[0].sizeimage, - adjusted_count, adjusted_count, NULL); - - allocated_num = alloc_buffers_from_pool(priv, priv->src_pool, - &priv->out_buffers); - if (allocated_num == 0) { - gst_caps_unref(caps); - ret = -1; - goto unlock; - } - - for (i = 0; i < allocated_num; i++) { - /* Set identifiers for associating a GstBuffer with - a V4L2 buffer in the V4L2 caller side. */ - priv->mmap_offset = - set_mem_offset(&priv->out_buffers[i], - priv->src_pool, - priv->mmap_offset); - - priv->out_buffers[i].planes[0].length = - gst_buffer_get_size(priv->out_buffers[i].gstbuf); - } - - req->count = priv->out_buffers_num = allocated_num; - - GST_DEBUG("buffers count=%d", req->count); - - priv->returned_out_buffers_num = 0; - - ret = 0; - - unlock: - g_mutex_unlock(&priv->dev_lock); - - return ret; -} - -static GstBuffer * -peek_first_cap_buffer(struct v4l_gst *priv) -{ - GstBuffer *gstbuf = NULL; - gboolean timed_out = FALSE; - gint64 end_time; - - g_mutex_lock(&priv->queue_mutex); - gstbuf = g_queue_peek_head(priv->req_gstbufs_queue); - end_time = g_get_monotonic_time() + INITIAL_BUFFER_WAIT_TIMEOUT; - while (!gstbuf && priv->is_pipeline_started) { - if (!g_cond_wait_until(&priv->queue_cond, - &priv->queue_mutex, - end_time)) { - timed_out = TRUE; - break; - } - gstbuf = g_queue_peek_head(priv->req_gstbufs_queue); - } - g_mutex_unlock(&priv->queue_mutex); - - if (timed_out && !gstbuf) - GST_WARNING("Timed out waiting the first CAPTURE buffer."); - - return gstbuf; -} - -static gboolean -wait_for_all_bufs_collected(struct v4l_gst *priv, - guint max_buffers) -{ - GQueue *queue = priv->req_gstbufs_queue; - gboolean succeeded; - gboolean timed_out = FALSE; - gint64 end_time; - - g_mutex_lock(&priv->queue_mutex); - end_time = g_get_monotonic_time() + INITIAL_BUFFER_WAIT_TIMEOUT; - while (g_queue_get_length(queue) < max_buffers && - priv->is_pipeline_started) { - if (!g_cond_wait_until(&priv->queue_cond, - &priv->queue_mutex, - end_time)) { - timed_out = TRUE; - break; - } - } - succeeded = g_queue_get_length(queue) >= max_buffers; - g_mutex_unlock(&priv->queue_mutex); - - if (timed_out && !succeeded) - GST_WARNING("Timed out waiting CAPTURE buffers."); - - return succeeded; -} - -static gboolean -retrieve_cap_frame_info(GstBufferPool *pool, GstBuffer *gstbuf, - struct v4l2_pix_format_mplane *cap_fmt) -{ - GstVideoInfo info; - GstVideoMeta *meta; - gint i; - - if (!get_raw_video_params(pool, gstbuf, &info, &meta)) { - GST_ERROR("Failed to get video meta data"); - return FALSE; - } - - for (i = 0; i < meta->n_planes; i++) { - cap_fmt->plane_fmt[i].sizeimage = - calc_plane_size(&info, meta, i); - cap_fmt->plane_fmt[i].bytesperline = meta->stride[i]; - } - - return TRUE; -} - -static guint -create_cap_buffers_list(struct v4l_gst *priv) -{ - GstBuffer *first_gstbuf; - guint actual_max_buffers; - gint i, j; - gboolean succeeded; - - if (priv->cap_buffers) - /* Cannot realloc the buffers without stopping the pipeline, - so return the same number of the buffers so far. */ - return priv->cap_buffers_num; - - g_mutex_unlock(&priv->dev_lock); - - first_gstbuf = peek_first_cap_buffer(priv); - - g_mutex_lock(&priv->dev_lock); - - if (!first_gstbuf) { - GST_ERROR("Failed to wait for the first CAPTURE buffer."); - errno = EINVAL; - return 0; - } - - if (!first_gstbuf->pool) { - GST_ERROR("Cannot handle buffers not belonging to " - "a bufferpool"); - errno = EINVAL; - return 0; - } - - if (priv->sink_pool != first_gstbuf->pool) { - GST_DEBUG("The buffer pool we prepared is not used by " - "the pipeline, so replace it with the pool that is " - "actually used"); - gst_object_unref(priv->sink_pool); - priv->sink_pool = gst_object_ref(first_gstbuf->pool); - } - - /* Confirm the number of buffers actually set to the buffer pool. */ - get_buffer_pool_params(priv->sink_pool, NULL, NULL, NULL, - &actual_max_buffers); - if (actual_max_buffers == 0) { - GST_ERROR("Cannot handle the unlimited amount of buffers"); - errno = EINVAL; - return 0; - } - - if (!retrieve_cap_frame_info(priv->sink_pool, first_gstbuf, - &priv->cap_fmt)) { - GST_ERROR("Failed to retrieve frame info on CAPTURE"); - errno = EINVAL; - return 0; - } - - /* We wait for buffers from appsink to be collected for - the maximum number of the buffer pool. */ - g_mutex_unlock(&priv->dev_lock); - succeeded = wait_for_all_bufs_collected(priv, actual_max_buffers); - g_mutex_lock(&priv->dev_lock); - - if (!succeeded) { - GST_ERROR("Failed to collect buffers for CAPTURE."); - errno = EINVAL; - return 0; - } - - priv->cap_buffers = g_new0(struct v4l_gst_buffer, actual_max_buffers); - - for (i = 0; i < actual_max_buffers; i++) { - priv->cap_buffers[i].gstbuf = - g_queue_peek_nth(priv->req_gstbufs_queue, i); - - /* Set identifiers for associating a GstBuffer with - a V4L2 buffer in the V4L2 caller side. */ - priv->mmap_offset = set_mem_offset(&priv->cap_buffers[i], - priv->sink_pool, - priv->mmap_offset); - - priv->cap_buffers[i].state = V4L_GST_BUFFER_DEQUEUED; - - /* assume that decoded image data has been filled to - the entire plane size, because the GStreamer buffer - information does not provides how much valid data size - a GstBuffer has. */ - for (j = 0; j < priv->cap_fmt.num_planes; j++) { - priv->cap_buffers[i].planes[j].length = - priv->cap_fmt.plane_fmt[j].sizeimage; - } - - GST_DEBUG("cap gst_buffer[%d] : %p", i, - priv->cap_buffers[i].gstbuf); - } - - GST_DEBUG("The number of buffers actually set to the buffer pool is %d", - actual_max_buffers); - - return actual_max_buffers; -} - -static int -stop_pipeline(struct v4l_gst *priv) -{ - GstStateChangeReturn state_ret; - int ret = 0; - gint i; - - GST_DEBUG("req->count == 0, stop the pipeline"); - - set_pipeline_started(priv, FALSE); - - state_ret = gst_element_set_state(priv->pipeline, - GST_STATE_NULL); - while (state_ret == GST_STATE_CHANGE_ASYNC) { - /* This API blocks up to the ASYNC state change completion. */ - g_mutex_unlock(&priv->dev_lock); - state_ret = gst_element_get_state(priv->pipeline, NULL, - NULL, - GST_CLOCK_TIME_NONE); - g_mutex_lock(&priv->dev_lock); - } - - if (state_ret != GST_STATE_CHANGE_SUCCESS) { - GST_ERROR("Failed to stop pipeline (ret:%s)", - gst_element_state_change_return_get_name(state_ret)); - errno = EINVAL; - ret = -1; - return ret; - } - - g_atomic_int_set(&priv->is_cap_fmt_acquirable, 0); - - for (i = 0; i < priv->cap_buffers_num; i++) { - if (priv->cap_buffers[i].state == - V4L_GST_BUFFER_DEQUEUED) { - gst_buffer_unref(priv->cap_buffers[i].gstbuf); - } - } - - g_queue_clear(priv->req_gstbufs_queue); - g_queue_clear(priv->cap_gstbufs_queue); - reset_cap_timestamp_state(priv); - - set_pipeline_started(priv, FALSE); - - if (priv->cap_buffers) { - g_free(priv->cap_buffers); - priv->cap_buffers = NULL; - } - priv->cap_buffers_num = 0; - init_decoded_frame_params(&priv->cap_fmt); - - return ret; -} - -static int -reqbuf_ioctl_cap(struct v4l_gst *priv, - struct v4l2_requestbuffers *req) -{ - int ret = -1; - - if (!is_supported_memory_io(req->memory)) - return ret; - - g_mutex_lock(&priv->dev_lock); - - if (req->count == 0) { - ret = stop_pipeline(priv); - goto unlock; - } - - if (!priv->is_pipeline_started) { - GST_ERROR("Need to start the pipeline for the buffer request " - "on CAPTURE"); - errno = EINVAL; - goto unlock; - } - - g_mutex_lock(&priv->cap_reqbuf_mutex); - priv->cap_buffers_num = MIN(req->count, VIDEO_MAX_FRAME); - g_cond_signal(&priv->cap_reqbuf_cond); - g_mutex_unlock(&priv->cap_reqbuf_mutex); - - req->count = priv->cap_buffers_num = create_cap_buffers_list(priv); - if (req->count == 0) - goto unlock; - - GST_DEBUG("buffers count=%d", req->count); - - ret = 0; - - unlock: - g_mutex_unlock(&priv->dev_lock); - - return ret; -} - -int -reqbuf_ioctl(struct v4l_gst *priv, struct v4l2_requestbuffers *req) -{ - int ret; - - GST_DEBUG("VIDIOC_REQBUF: type: %s (0x%x) count: %d memory: 0x%x", - v4l2_buffer_type_to_string(req->type), req->type, - req->count, req->memory); - - if (req->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { - ret = reqbuf_ioctl_out(priv, req); - } else if (req->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { - ret = reqbuf_ioctl_cap(priv, req); - } else { - GST_ERROR("Invalid buf type"); - errno = EINVAL; - ret = -1; - } - - return ret; -} - -static gboolean -relink_elements_with_caps_filtered(GstElement *src_elem, GstElement *dest_elem, - GstCaps *caps) -{ - gst_element_unlink(src_elem, dest_elem); - return gst_element_link_filtered(src_elem, dest_elem, caps); -} - -static gboolean -set_out_format_to_pipeline(struct v4l_gst *priv) -{ - GstCaps *caps; - - caps = get_codec_caps_from_fourcc(priv->out_fmt.pixelformat); - if (!caps) { - errno = EINVAL; - return FALSE; - } - - gst_app_src_set_caps(GST_APP_SRC(priv->appsrc), caps); - gst_caps_unref(caps); - - return TRUE; -} - -static gboolean -set_cap_format_to_pipeline(struct v4l_gst *priv) -{ - GstElement *peer_elem; - GstCaps *caps; - GstVideoFormat fmt; - gboolean ret; - - fmt = fourcc_to_gst_video_format(priv->cap_fmt.pixelformat); - if (fmt == GST_VIDEO_FORMAT_UNKNOWN) { - gchar fourcc_str[5]; - fourcc_to_string(priv->cap_fmt.pixelformat, fourcc_str); - GST_ERROR("Invalid format on CAPTURE: %s (0x%x)", - fourcc_str, priv->cap_fmt.pixelformat); - errno = EINVAL; - return FALSE; - } - - caps = gst_caps_new_simple("video/x-raw", "format", G_TYPE_STRING, - gst_video_format_to_string(fmt), NULL); - - peer_elem = get_peer_element(priv->appsink, "sink"); - if (!relink_elements_with_caps_filtered(peer_elem, priv->appsink, - caps)) { - GST_ERROR("Failed to relink elements with " - "the CAPTURE setting (caps=%s)", - gst_caps_to_string(caps)); - errno = EINVAL; - ret = FALSE; - goto free_objects; - } - GST_DEBUG("appsink element is relinked"); - - ret = TRUE; - - free_objects: - gst_caps_unref(caps); - gst_object_unref(peer_elem); - - return ret; -} - -static int -streamon_ioctl_out(struct v4l_gst *priv) -{ - GstState state; - - if (priv->is_pipeline_started) { - GST_ERROR("The pipeline is already running"); - errno = EBUSY; - return -1; - } - - GST_OBJECT_LOCK(priv->pipeline); - state = GST_STATE(priv->pipeline); - GST_OBJECT_UNLOCK(priv->pipeline); - - g_mutex_lock(&priv->dev_lock); - - if (state == GST_STATE_NULL) { - if (!set_out_format_to_pipeline(priv)) - return -1; - if (!set_cap_format_to_pipeline(priv)) - return -1; - } - - if (!gst_buffer_pool_is_active(priv->src_pool)) { - if (!gst_buffer_pool_set_active(priv->src_pool, TRUE)) { - GST_ERROR("Failed to activate buffer pool"); - errno = EINVAL; - return -1; - } - - /* Restore the extra reference counted up in the streamoff */ - gst_buffer_unref(priv->out_buffers[0].gstbuf); - } - - priv->eos_state = EOS_NONE; - reset_cap_timestamp_state(priv); - - set_pipeline_started(priv, TRUE); - - gst_element_set_state(priv->pipeline, GST_STATE_PLAYING); - - g_mutex_unlock(&priv->dev_lock); - - GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(priv->pipeline), - GST_DEBUG_GRAPH_SHOW_ALL, - "v4l-gst.streamon.snapshot"); - - return 0; -} - -int -streamon_ioctl(struct v4l_gst *priv, enum v4l2_buf_type *type) -{ - int ret; - - GST_DEBUG("VIDIOC_STREAMON: type: %s (0x%x)", - v4l2_buffer_type_to_string(*type), *type); - - if (*type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { - ret = streamon_ioctl_out(priv); - } else if (*type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { - /* no processing */ - ret = 0; - } else { - GST_ERROR("Invalid buf type"); - errno = EINVAL; - ret = -1; - } - - return ret; -} - -int -streamoff_ioctl(struct v4l_gst *priv, enum v4l2_buf_type *type) -{ - int ret; - - GST_DEBUG("VIDIOC_STREAMOFF: type: %s (0x%x)", - v4l2_buffer_type_to_string(*type), *type); - - GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(priv->pipeline), - GST_DEBUG_GRAPH_SHOW_ALL, - "v4l-gst.streamoff.snapshot"); - - if (*type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { - g_mutex_lock(&priv->dev_lock); - ret = streamoff_ioctl_out(priv, TRUE); - g_mutex_unlock(&priv->dev_lock); - } else if (*type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { - /* no processing */ - ret = 0; - } else { - GST_ERROR("Invalid buf type"); - errno = EINVAL; - ret = -1; - } - - return ret; -} - -int -subscribe_event_ioctl(struct v4l_gst *priv, - struct v4l2_event_subscription *subscription) -{ - int retval = -1; - - errno = EINVAL; - g_return_val_if_fail(priv, retval); - g_return_val_if_fail(subscription, retval); - - g_mutex_lock(&priv->dev_lock); - - GST_DEBUG("VIDIOC_SUBSCRIBE_EVENT: type: %s (0x%x) id: %d flags: 0x%x", - v4l2_event_type_to_string(subscription->type), subscription->type, - subscription->id, subscription->flags); - - switch (subscription->type) { - case V4L2_EVENT_SOURCE_CHANGE: - /* Chromium supports only this type of v4l2events. */ - priv->v4l2events.subscribed |= (1 << V4L2_EVENT_SOURCE_CHANGE); - errno = 0; - retval = 0; - break; - default: - GST_ERROR("unsupported V4L2_EVENT type: %s (type: 0x%x)", - v4l2_event_type_to_string(subscription->type), - subscription->type); - errno = ENOTTY; - break; - } - - g_mutex_unlock(&priv->dev_lock); - - return retval; -} - -int -dqevent_ioctl(struct v4l_gst *priv, struct v4l2_event *ev) -{ - int retval = -1; - - GST_TRACE("VIDIOC_DQEVENT"); - - errno = EINVAL; - g_return_val_if_fail(ev, retval); - g_return_val_if_fail(priv, retval); - - g_mutex_lock(&priv->dev_lock); - g_mutex_lock(&priv->v4l2events.mutex); - - if (!priv->v4l2events.queue || priv->v4l2events.queue->length == 0) { - errno = EAGAIN; - goto unlock; - } - - if (priv->v4l2events.subscribed & (1 << V4L2_EVENT_SOURCE_CHANGE)) { - struct v4l2_event *next - = g_queue_pop_head(priv->v4l2events.queue); - if (!next) { - GST_WARNING("Failed to pop a v4l2_event."); - errno = EINVAL; - goto unlock; - } - *ev = *next; - ev->pending = priv->v4l2events.queue->length; - g_free(next); - errno = 0; - retval = 0; - GST_DEBUG("Dequeue SOURCE_CHANGE: pending %u, sequence: %u", - ev->pending, ev->sequence); - } - - unlock: - g_mutex_unlock(&priv->v4l2events.mutex); - g_mutex_unlock(&priv->dev_lock); - - return retval; -} - -static int -find_out_buffer_by_offset(struct v4l_gst *priv, int64_t offset) -{ - gint index = -1; - gint i; - - for (i = 0; i < priv->out_buffers_num; i++) { - if (priv->out_buffers[i].planes[0].m.mem_offset == offset) { - index = i; - break; - } - } - - return index; -} - -static void * -map_out_buffer(struct v4l_gst *priv, int index, int prot) -{ - GstMapInfo info; - void *data; - GstMapFlags map_flags; - - map_flags = (prot & PROT_READ) ? GST_MAP_READ : 0; - map_flags |= (prot & PROT_WRITE) ? GST_MAP_WRITE : 0; - - if (!gst_buffer_map(priv->out_buffers[index].gstbuf, &info, - map_flags)) { - GST_ERROR("Failed to map buffer (%p)", - priv->out_buffers[index].gstbuf); - errno = EINVAL; - return MAP_FAILED; - } - - data = info.data; - - gst_buffer_unmap(priv->out_buffers[index].gstbuf, &info); - - priv->out_buffers[index].flags = map_flags; - - return data; -} - -static int -find_cap_buffer_by_offset(struct v4l_gst *priv, - int64_t offset, int *index, int *plane) -{ - gint i, j; - - for (i = 0; i < priv->cap_buffers_num; i++) { - for (j = 0; j < priv->cap_fmt.num_planes; j++) { - if (priv->cap_buffers[i].planes[j].m.mem_offset == - offset) { - *index = i; - *plane = j; - return 0; - } - } - } - - return -1; -} - -static void * -map_cap_buffer(struct v4l_gst *priv, int index, int plane, - int prot) -{ - GstVideoMeta *meta; - GstMapInfo info; - void *data; - GstMapFlags map_flags; - - map_flags = (prot & PROT_READ) ? GST_MAP_READ : 0; - map_flags |= (prot & PROT_WRITE) ? GST_MAP_WRITE : 0; - - if (!gst_buffer_map(priv->cap_buffers[index].gstbuf, &info, - map_flags)) { - GST_ERROR("Failed to map buffer (%p)", - priv->cap_buffers[index].gstbuf); - errno = EINVAL; - return MAP_FAILED; - } - - if (!get_raw_video_params(priv->sink_pool, - priv->cap_buffers[index].gstbuf, - NULL, &meta)) { - GST_ERROR("Failed to get video meta data"); - errno = EINVAL; - gst_buffer_unmap(priv->cap_buffers[index].gstbuf, - &priv->cap_buffers[index].info); - return MAP_FAILED; - } - - data = info.data + meta->offset[plane]; - - gst_buffer_unmap(priv->cap_buffers[index].gstbuf, &info); - - priv->cap_buffers[index].flags = map_flags; - - return data; -} - -void * -gst_backend_mmap(struct v4l_gst *priv, void *start, size_t length, - int prot, int flags, int fd, int64_t offset) -{ - int index; - int plane; - void *map = MAP_FAILED; - int ret; - - /* unused */ - (void)start; - (void)flags; - (void)fd; - - /* The GStreamer memory mapping internally maps - the whole allocated size of a buffer, so the mapping length - does not need to be specified. */ - (void)length; - - g_mutex_lock(&priv->dev_lock); - - index = find_out_buffer_by_offset(priv, offset); - if (index >= 0) { - map = map_out_buffer(priv, index, prot); - goto unlock; - } - - ret = find_cap_buffer_by_offset(priv, offset, &index, &plane); - if (ret == 0) { - map = map_cap_buffer(priv, index, plane, prot); - goto unlock; - } - - unlock: - g_mutex_unlock(&priv->dev_lock); - - GST_DEBUG("Final map = %p", map); - - return map; -} - -int -expbuf_ioctl(struct v4l_gst *priv, struct v4l2_exportbuffer *expbuf) -{ - struct v4l_gst_buffer *buffer; - guint mem_index = 0; - GstMemory *mem = NULL; - - GST_TRACE("VIDIOC_EXPBUF: type: 0x%x index: %d flags: 0x%x", - expbuf->type, expbuf->index, expbuf->flags); - - if (expbuf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && - expbuf->type != V4L2_BUF_TYPE_PRIVATE) { - GST_ERROR("Can only export capture buffers as dmabuf"); - errno = EINVAL; - return -1; - } - - if (expbuf->index >= priv->cap_buffers_num) { - GST_ERROR("Buffer index is out of range!: %d/%d", - expbuf->index, priv->cap_buffers_num); - errno = EINVAL; - return -1; - } - - if (expbuf->plane >= priv->cap_fmt.num_planes) { - GST_ERROR("Plane index is out of range!: %d/%d", - expbuf->plane, priv->cap_fmt.num_planes); - errno = EINVAL; - return -1; - } - - buffer = &priv->cap_buffers[expbuf->index]; - - if (expbuf->plane < gst_buffer_n_memory(buffer->gstbuf)) - mem_index = expbuf->plane; - - mem = gst_buffer_peek_memory(buffer->gstbuf, mem_index); - if (!mem || !gst_is_dmabuf_memory(mem)) { - GST_ERROR("Failed to get dmabuf memory."); - errno = EINVAL; - return -1; - } - - switch(expbuf->type) { - case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: - expbuf->fd = dup(gst_dmabuf_memory_get_fd(mem)); - if (expbuf->plane == 0) { - /* See reindex_buffers() */ - buffer->plane0_fd = gst_dmabuf_memory_get_fd(mem); - } - break; - case V4L2_BUF_TYPE_PRIVATE: - /* For backward compatibility, will be removed. - See also set_v4l2_buffer_plane_params(). */ - expbuf->reserved[0] = 0; - if (gst_buffer_n_memory(buffer->gstbuf) == 1) { - guint i; - for (i = 0; i < expbuf->plane; i++) - expbuf->reserved[0] += buffer->planes[i].length; - } - break; - default: - GST_ERROR("Can only export capture buffers as dmabuf"); - errno = EINVAL; - return -1; - } - - return 0; -} - -int -g_selection_ioctl(struct v4l_gst *priv, struct v4l2_selection *selection) -{ -#ifdef ENABLE_VIDIOC_DEBUG - char *vidioc_features = getenv(ENV_DISABLE_VIDIOC_FEATURES); - if (vidioc_features && strstr(vidioc_features, "VIDIOC_G_SELECTION")) { - GST_CAT_ERROR(v4l_gst_ioctl_debug_category, - "unsupported VIDIOC_G_SELECTION"); - errno = ENOTTY; - return 0; - } -#endif - GST_DEBUG("VIDIOC_G_SELECTION: type: 0x%x target: 0x%x flags: 0x%x", - selection->type, selection->target, selection->flags); - - selection->r.top = selection->r.left = 0; - selection->r.width = priv->cap_fmt.width; - selection->r.height = priv->cap_fmt.height; - - return 0; -} - -/* See https://github.com/JeffyCN/libv4l-rkmpp/blob/master/src/libv4l-rkmpp-dec.c#L740-L776 */ -int -queryctrl_ioctl(struct v4l_gst *priv, struct v4l2_queryctrl *query_ctrl) -{ - gchar fourcc_str[5]; - -#ifdef ENABLE_VIDIOC_DEBUG - char *vidioc_features = getenv(ENV_DISABLE_VIDIOC_FEATURES); - if (vidioc_features && strstr(vidioc_features, "VIDIOC_QUERYCTRL")) { - GST_CAT_ERROR(v4l_gst_ioctl_debug_category, - "unsupported VIDIOC_QUERYCTRL: id: 0x%x", - query_ctrl->id); - errno = ENOTTY; - return 0; - } -#endif - - GST_INFO("unsupported VIDIOC_QUERYCTRL id: 0x%x", query_ctrl->id); - - switch (query_ctrl->id) { - case V4L2_CID_MPEG_VIDEO_H264_PROFILE: - fourcc_to_string(V4L2_PIX_FMT_H264, fourcc_str); - if (g_hash_table_lookup(priv->config.pipelines, fourcc_str)) { - query_ctrl->minimum = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE; - query_ctrl->maximum = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_10; - } else { - GST_ERROR("disabled H264 profile for query_ctrl id: %x", query_ctrl->id); - errno = EINVAL; - return -1; - } - break; - case V4L2_CID_MPEG_VIDEO_HEVC_PROFILE: - fourcc_to_string(V4L2_PIX_FMT_HEVC, fourcc_str); - if (g_hash_table_lookup(priv->config.pipelines, fourcc_str)) { - query_ctrl->minimum = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN; - query_ctrl->maximum = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_10; - } else { - GST_ERROR("disabled H265/HEVC profile for unsupported query_ctrl id: %x", query_ctrl->id); - errno = EINVAL; - return -1; - } - break; -#if 0 // No AV1, VP8, VP9 definition - case V4L2_CID_MPEG_VIDEO_AV1_PROFILE: - query_ctrl->minimum = V4L2_MPEG_VIDEO_AV1_PROFILE_MAIN; - query_ctrl->maximum = query_ctrl->minimum; - break; - case V4L2_CID_MPEG_VIDEO_VP8_PROFILE: - query_ctrl->minimum = V4L2_MPEG_VIDEO_VP8_PROFILE_0; - query_ctrl->maximum = query_ctrl->minimum; - break; - case V4L2_CID_MPEG_VIDEO_VP9_PROFILE: - query_ctrl->minimum = V4L2_MPEG_VIDEO_VP9_PROFILE_0; - query_ctrl->maximum = V4L2_MPEG_VIDEO_VP9_PROFILE_2; - break; -#endif - /* TODO: fill info for other supported ctrls */ - default: - GST_ERROR("unsupported query_ctrl id: %x", query_ctrl->id); - errno = EINVAL; - return -1; - } - return 0; -} - -/* See https://github.com/JeffyCN/libv4l-rkmpp/blob/master/src/libv4l-rkmpp-dec.c#L778-L842 */ -int -querymenu_ioctl(struct v4l_gst *priv, struct v4l2_querymenu *query_menu) -{ - -#ifdef ENABLE_VIDIOC_DEBUG - char *vidioc_features = getenv(ENV_DISABLE_VIDIOC_FEATURES); - if (vidioc_features && strstr(vidioc_features, "VIDIOC_QUERYMENU")) { - GST_CAT_ERROR(v4l_gst_ioctl_debug_category, - "unsupported VIDIOC_QUERYMENU query_menu id: 0x%x", - query_menu->id); - errno = ENOTTY; - return 0; - } -#endif - - GST_DEBUG("VIDIOC_QUERYMENU query_menu id: %x", query_menu->id); - GST_DEBUG("query_menu index: %x", query_menu->index); - - switch (query_menu->id) { - case V4L2_CID_MPEG_VIDEO_H264_PROFILE: - switch (query_menu->index) { - case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE: - case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN: - case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH: - case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_10: - break; - default: - GST_INFO("unsupported H264 profile index: %x", query_menu->index); - errno = EINVAL; - return -1; - } - GST_DEBUG("V4L2_CID_MPEG_VIDEO_H264_PROFILE index: %x", query_menu->index); - break; - case V4L2_CID_MPEG_VIDEO_HEVC_PROFILE: - switch (query_menu->index) { - case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN: - case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE: - case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_10: - break; - default: - GST_INFO("unsupported HEVC profile index: %x", query_menu->index); - errno = EINVAL; - return -1; - } - GST_DEBUG("V4L2_CID_MPEG_VIDEO_HEVC_PROFILE index: %x", query_menu->index); - break; -#if 0 // omit non-supported AV1, VP8, VP9 - case V4L2_CID_MPEG_VIDEO_AV1_PROFILE: - if (query_menu->index != V4L2_MPEG_VIDEO_AV1_PROFILE_MAIN) { - GST_INFO("unsupported VP8 profile index: %x", query_menu->index); - errno = EINVAL; - return -1; - } - GST_DEBUG("V4L2_CID_MPEG_VIDEO_AV1_PROFILE index: %x", query_menu->index); - break; - case V4L2_CID_MPEG_VIDEO_VP8_PROFILE: - if (query_menu->index != V4L2_MPEG_VIDEO_VP8_PROFILE_0) { - GST_INFO("unsupported VP8 profile index: %x", query_menu->index); - errno = EINVAL; - return -1; - } - GST_DEBUG("V4L2_CID_MPEG_VIDEO_VP8_PROFILE index: %x", query_menu->index); - break; - case V4L2_CID_MPEG_VIDEO_VP9_PROFILE: - switch (query_menu->index) { - case V4L2_MPEG_VIDEO_VP9_PROFILE_0: - case V4L2_MPEG_VIDEO_VP9_PROFILE_2: - break; - default: - GST_INFO("unsupported VP9 profile index: %x", query_menu->index); - errno = EINVAL; - return -1; - } - GST_DEBUG("V4L2_CID_MPEG_VIDEO_VP9_PROFILE index: %x", query_menu->index); - break; -#endif - default: - GST_ERROR("unsupported menu: %x", query_menu->id); - errno = EINVAL; - return -1; - } - return 0; -} - -static int -try_fmt_ioctl_out(struct v4l_gst *priv, struct v4l2_format *format) -{ - struct v4l2_pix_format_mplane *pix_fmt = &format->fmt.pix_mp; - gchar fourcc_str[5]; - - fourcc_to_string(pix_fmt->pixelformat, fourcc_str); - - if (!is_pix_fmt_supported((struct fmt*)priv->supported_out_fmts->data, - priv->supported_out_fmts->len, - pix_fmt->pixelformat)) { - GST_ERROR("Unsupported pixelformat on OUTPUT: %s (0x%x)", - fourcc_str, pix_fmt->pixelformat); - - errno = EINVAL; - return -1; - } - - if (priv->pipeline && - priv->out_fmt.pixelformat != pix_fmt->pixelformat) { - gchar current[5]; - - fourcc_to_string(priv->out_fmt.pixelformat, current); - GST_ERROR("Different pixelformat with current: " - "pixelformat:%s, current: %s", - fourcc_str, current); - - errno = EINVAL; - return -1; - } - - set_params_as_encoded_stream(pix_fmt); - - return 0; -} - -static int -try_fmt_ioctl_cap(struct v4l_gst *priv, struct v4l2_format *format) -{ - struct v4l2_pix_format_mplane *pix_fmt = &format->fmt.pix_mp; - - if (!is_pix_fmt_supported((struct fmt*)priv->supported_cap_fmts->data, - priv->supported_cap_fmts->len, - pix_fmt->pixelformat)) { - gchar fourcc_str[5]; - - fourcc_to_string(pix_fmt->pixelformat, fourcc_str); - GST_ERROR("Unsupported pixelformat on CAPTURE"); - - errno = EINVAL; - return -1; - } - - pix_fmt->field = V4L2_FIELD_NONE; - pix_fmt->colorspace = 0; - pix_fmt->flags = 0; - - return 0; -} - -int -try_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *format) -{ - int ret; - gchar fourcc_str[5]; - - fourcc_to_string(format->fmt.pix_mp.pixelformat, fourcc_str); - GST_DEBUG("VIDIOC_TRY_FMT: type: 0x%x, pixelformat: %s (0x%x)", - format->type, fourcc_str, format->fmt.pix_mp.pixelformat); - - g_mutex_lock(&priv->dev_lock); - - if (format->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { - ret = try_fmt_ioctl_out(priv, format); - } else if (format->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { - ret = try_fmt_ioctl_cap(priv, format); - } else { - GST_ERROR("Invalid buffer type"); - errno = EINVAL; - ret = -1; - } - - g_mutex_unlock(&priv->dev_lock); - - return ret; -} - -int -g_crop_ioctl(struct v4l_gst *priv, struct v4l2_crop *crop) -{ - const gchar *buf_type; -#ifdef ENABLE_VIDIOC_DEBUG - char *vidioc_features = getenv(ENV_DISABLE_VIDIOC_FEATURES); - if (vidioc_features && strstr(vidioc_features, "VIDIOC_G_CROP")) { - GST_CAT_ERROR(v4l_gst_ioctl_debug_category, - "unsupported VIDIOC_G_EXT_CTRLS v4l2_crop type: 0x%x", - crop->type); - errno = ENOTTY; - return 0; - } -#endif - - GST_INFO("unsupported VIDIOC_G_CROP v4l2_crop type: 0x%x", crop->type); - - buf_type = v4l2_buffer_type_to_string(crop->type); - if (buf_type) { - GST_DEBUG("v4l2_crop type: V4L2_BUF_TYPE_%s", buf_type); - } else { - GST_DEBUG("unsupported v4l2_crop type: 0x%x", crop->type); - } - GST_DEBUG("v4l2_crop rect: left:%d top:%d width: %u height: %u", - crop->c.left, crop->c.top, crop->c.width, crop->c.height); - - return 0; -} - -#if 0 -static int -set_decoder_cmd_state(struct v4l_gst *priv, GstState state) -{ - int ret = 0; - GstStateChangeReturn state_ret; - g_mutex_lock(&priv->dev_lock); - - switch (state) { - case GST_STATE_PAUSED: - ret = gst_element_set_state(priv->pipeline, state); - while (state_ret == GST_STATE_CHANGE_ASYNC) { - /* This API blocks up to the ASYNC state change completion. */ - g_mutex_unlock(&priv->dev_lock); - state_ret = gst_element_get_state(priv->pipeline, NULL, - NULL, - GST_CLOCK_TIME_NONE); - g_mutex_lock(&priv->dev_lock); - } - - if (state_ret != GST_STATE_CHANGE_SUCCESS) { - GST_ERROR("Failed to stop pipeline (ret:%s)", - gst_element_state_change_return_get_name(state_ret)); - errno = EINVAL; - ret = -1; - } - g_mutex_unlock(&priv->dev_lock); - default: - GST_CAT_ERROR(v4l_gst_ioctl_debug_category, - "unsupported GstState to set %s", - gst_element_state_get_name(state)); - break; - } - return ret; -} -#endif - -int -try_decoder_cmd_ioctl(struct v4l_gst *priv, - struct v4l2_decoder_cmd *decoder_cmd) -{ - int ret = 0; - - switch (decoder_cmd->cmd) { - case V4L2_DEC_CMD_START: - GST_CAT_DEBUG(v4l_gst_ioctl_debug_category, - "v4l2_dec_cmd: V4L2_DEC_CMD_START speed: %d format: %x", - decoder_cmd->start.speed, decoder_cmd->start.format); - break; - case V4L2_DEC_CMD_STOP: - GST_CAT_DEBUG(v4l_gst_ioctl_debug_category, - "v4l2_dec_cmd: V4L2_DEC_CMD_STOP pts: %llu", - decoder_cmd->stop.pts); - break; - case V4L2_DEC_CMD_PAUSE: - GST_CAT_DEBUG(v4l_gst_ioctl_debug_category, - "v4l2_dec_cmd: V4L2_DEC_CMD_PAUSE"); - break; - case V4L2_DEC_CMD_RESUME: - GST_CAT_DEBUG(v4l_gst_ioctl_debug_category, - "v4l2_dec_cmd: V4L2_DEC_CMD_RESUME"); - break; - case V4L2_DEC_CMD_FLUSH: - GST_CAT_DEBUG(v4l_gst_ioctl_debug_category, - "v4l2_dec_cmd: V4L2_DEC_CMD_FLUSH"); - break; - default: - GST_CAT_DEBUG(v4l_gst_ioctl_debug_category, - "unsupported v4l2_decoder_cmd cmd: 0x%x", - decoder_cmd->cmd); - errno = EINVAL; - ret = -1; - break; - } - return ret; -} - -int -unsubscribe_event_ioctl(struct v4l_gst *priv, - struct v4l2_event_subscription *subscription) -{ - int retval = 0; - - GST_INFO("VIDIOC_UNSUBSCRIBE_EVENT: type: 0x%x id: 0x%x flags: 0x%x", - subscription->type, subscription->id, subscription->flags); - - g_mutex_lock(&priv->dev_lock); - - errno = 0; - - switch (subscription->type) { - case V4L2_EVENT_ALL: - /* V4L2_EVENT_ALL is valid only for unsubscribe: - https://www.kernel.org/doc/html/v4.9/media/uapi/v4l/vidioc-dqevent.html#id2 - */ - priv->v4l2events.subscribed = 0; - break; - case V4L2_EVENT_SOURCE_CHANGE: - priv->v4l2events.subscribed &= ~(1 << V4L2_EVENT_SOURCE_CHANGE); - break; - default: - GST_ERROR("unsupported V4L2_EVENT type: %s (type: 0x%x)", - v4l2_event_type_to_string(subscription->type), - subscription->type); - errno = ENOTTY; - retval = -1; - break; - } - - g_mutex_unlock(&priv->dev_lock); - - return retval; -} - -int -decoder_cmd_ioctl(struct v4l_gst *priv, struct v4l2_decoder_cmd *decoder_cmd) -{ - int ret = 0; - - g_mutex_lock(&priv->dev_lock); - - switch (decoder_cmd->cmd) { - case V4L2_DEC_CMD_START: - GST_CAT_DEBUG(v4l_gst_ioctl_debug_category, - "v4l2_decoder_cmd: V4L2_DEC_CMD_START " - "speed: %d format: %x", - decoder_cmd->start.speed, - decoder_cmd->start.format); - break; - case V4L2_DEC_CMD_STOP: - GST_CAT_DEBUG(v4l_gst_ioctl_debug_category, - "v4l2_decoder_cmd: V4L2_DEC_CMD_STOP pts: %llu", - decoder_cmd->stop.pts); - /* Clients send this command after queueing the last incoming - buffer. To detect the last decoded frame, we need to cache - an outgoing buffer and wait EOS event from the pipeline. - ref: https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/dev-decoder.html#drain - */ - if (priv->eos_state == EOS_NONE) { - GST_DEBUG("EOS: Send to AppSrc"); - priv->eos_state = EOS_WAITING_DECODE; - gst_app_src_end_of_stream(GST_APP_SRC(priv->appsrc)); - } - break; - case V4L2_DEC_CMD_PAUSE: - GST_CAT_DEBUG(v4l_gst_ioctl_debug_category, - "v4l2_decoder_cmd: V4L2_DEC_CMD_PAUSE"); - break; - case V4L2_DEC_CMD_RESUME: - GST_CAT_DEBUG(v4l_gst_ioctl_debug_category, - "v4l2_decoder_cmd: V4L2_DEC_CMD_RESUME"); - break; - case V4L2_DEC_CMD_FLUSH: - GST_CAT_DEBUG(v4l_gst_ioctl_debug_category, - "v4l2_decoder_cmd: V4L2_DEC_CMD_FLUSH"); - break; - default: - GST_CAT_ERROR(v4l_gst_ioctl_debug_category, - "unsupported VIDIOC_DECODER_CMD " - "v4l2_decoder_cmd: cmd: 0x%x flags: 0x%x", - decoder_cmd->cmd, decoder_cmd->flags); - errno = EINVAL; - ret = -1; - break; - } - - g_mutex_unlock(&priv->dev_lock); - - return ret; -} - -#include "gst-backend-testhooks.c" diff --git a/lib/libv4l-gst/gst-backend.h b/lib/libv4l-gst/gst-backend.h deleted file mode 100644 index 7fbd39d..0000000 --- a/lib/libv4l-gst/gst-backend.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2015 Renesas Electronics Corporation - * - * 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA - */ - -#ifndef __GST_BACKEND_H__ -#define __GST_BACKEND_H__ - -#include - -struct v4l_gst; - -struct v4l_gst* gst_backend_init(int fd); -void gst_backend_deinit(struct v4l_gst *priv); -int querycap_ioctl(struct v4l_gst *priv, struct v4l2_capability *cap); -int set_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *fmt); -int get_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *fmt); -int enum_fmt_ioctl(struct v4l_gst *priv, struct v4l2_fmtdesc *desc); -int enum_framesizes_ioctl(struct v4l_gst *priv, struct v4l2_frmsizeenum *argp); -int get_ctrl_ioctl(struct v4l_gst *priv, struct v4l2_control *ctrl); -int qbuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *buf); -int dqbuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *buf); -int querybuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *buf); -int reqbuf_ioctl(struct v4l_gst *priv, struct v4l2_requestbuffers *req); -int streamon_ioctl(struct v4l_gst *priv, enum v4l2_buf_type *type); -int streamoff_ioctl(struct v4l_gst *priv, enum v4l2_buf_type *type); -int subscribe_event_ioctl(struct v4l_gst *priv, struct v4l2_event_subscription *sub); -int dqevent_ioctl(struct v4l_gst *priv, struct v4l2_event *ev); -int expbuf_ioctl(struct v4l_gst *priv, struct v4l2_exportbuffer *buf); -void * gst_backend_mmap(struct v4l_gst *priv, void *start, size_t length, int prot, int flags, int fd, int64_t offset); -int get_ext_ctrl_ioctl(struct v4l_gst *priv, struct v4l2_ext_controls *ext_ctrls); -int g_selection_ioctl(struct v4l_gst *priv, struct v4l2_selection *selection); -int queryctrl_ioctl(struct v4l_gst *priv, struct v4l2_queryctrl *query_ctrl); -int querymenu_ioctl(struct v4l_gst *priv, struct v4l2_querymenu *query_menu); -int g_crop_ioctl(struct v4l_gst *priv, struct v4l2_crop *crop); -int try_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *format); -int try_decoder_cmd_ioctl(struct v4l_gst *priv, struct v4l2_decoder_cmd *decoder_cmd); -int unsubscribe_event_ioctl(struct v4l_gst *priv, struct v4l2_event_subscription *subscription); -int decoder_cmd_ioctl(struct v4l_gst *priv, struct v4l2_decoder_cmd *decoder_cmd); - -#define ENV_DISABLE_VIDIOC_FEATURES "DISABLE_VIDIOC_FEATURES" - -#endif /* __GST_BACKEND_H__ */ diff --git a/lib/libv4l-gst/libv4l-gst.c b/lib/libv4l-gst/libv4l-gst.c index 77c5d5e..bf07059 100644 --- a/lib/libv4l-gst/libv4l-gst.c +++ b/lib/libv4l-gst/libv4l-gst.c @@ -22,7 +22,7 @@ #include #include -#include "gst-backend.h" +#include "v4l-gst.h" #include "debug.h" #if HAVE_VISIBILITY @@ -41,7 +41,7 @@ static void *plugin_init(int fd) } fprintf(stderr, "DISABLE_VIDIOC_FEATURES: %s\n", vidioc_features); #endif - return gst_backend_init(fd); + return v4l_gst_init(fd); } static void plugin_close(void *dev_ops_priv) @@ -51,7 +51,7 @@ static void plugin_close(void *dev_ops_priv) if (!priv) return; - gst_backend_deinit(priv); + v4l_gst_deinit(priv); } static int plugin_ioctl(void *dev_ops_priv, int fd, @@ -64,76 +64,76 @@ static int plugin_ioctl(void *dev_ops_priv, int fd, switch (cmd) { case VIDIOC_QUERYCAP: - ret = querycap_ioctl(priv, arg); + ret = v4l_gst_ioctl_querycap(priv, arg); break; case VIDIOC_S_FMT: - ret = set_fmt_ioctl(priv, arg); + ret = v4l_gst_ioctl_set_fmt(priv, arg); break; case VIDIOC_G_FMT: - ret = get_fmt_ioctl(priv, arg); + ret = v4l_gst_ioctl_get_fmt(priv, arg); break; case VIDIOC_ENUM_FMT: - ret = enum_fmt_ioctl(priv, arg); + ret = v4l_gst_ioctl_enum_fmt(priv, arg); break; case VIDIOC_G_CTRL: - ret = get_ctrl_ioctl(priv, arg); + ret = v4l_gst_ioctl_get_ctrl(priv, arg); break; case VIDIOC_G_EXT_CTRLS: - ret = get_ext_ctrl_ioctl(priv, arg); + ret = v4l_gst_ioctl_get_ext_ctrl(priv, arg); break; case VIDIOC_QBUF: - ret = qbuf_ioctl(priv, arg); + ret = v4l_gst_ioctl_qbuf(priv, arg); break; case VIDIOC_DQBUF: - ret = dqbuf_ioctl(priv, arg); + ret = v4l_gst_ioctl_dqbuf(priv, arg); break; case VIDIOC_QUERYBUF: - ret = querybuf_ioctl(priv, arg); + ret = v4l_gst_ioctl_querybuf(priv, arg); break; case VIDIOC_REQBUFS: - ret = reqbuf_ioctl(priv, arg); + ret = v4l_gst_ioctl_reqbuf(priv, arg); break; case VIDIOC_STREAMON: - ret = streamon_ioctl(priv, arg); + ret = v4l_gst_ioctl_streamon(priv, arg); break; case VIDIOC_STREAMOFF: - ret = streamoff_ioctl(priv, arg); + ret = v4l_gst_ioctl_streamoff(priv, arg); break; case VIDIOC_SUBSCRIBE_EVENT: - ret = subscribe_event_ioctl(priv, arg); + ret = v4l_gst_ioctl_subscribe_event(priv, arg); break; case VIDIOC_DQEVENT: - ret = dqevent_ioctl(priv, arg); + ret = v4l_gst_ioctl_dqevent(priv, arg); break; case VIDIOC_EXPBUF: - ret = expbuf_ioctl(priv, arg); + ret = v4l_gst_ioctl_expbuf(priv, arg); break; case VIDIOC_ENUM_FRAMESIZES : - ret = enum_framesizes_ioctl(priv, arg); + ret = v4l_gst_ioctl_enum_framesizes(priv, arg); break; case VIDIOC_G_SELECTION : - ret = g_selection_ioctl(priv, arg); + ret = v4l_gst_ioctl_g_selection(priv, arg); break; case VIDIOC_QUERYCTRL: - ret = queryctrl_ioctl(priv, arg); + ret = v4l_gst_ioctl_queryctrl(priv, arg); break; case VIDIOC_QUERYMENU: - ret = querymenu_ioctl(priv, arg); + ret = v4l_gst_ioctl_querymenu(priv, arg); break; case VIDIOC_G_CROP: - ret = g_crop_ioctl(priv, arg); + ret = v4l_gst_ioctl_g_crop(priv, arg); break; case VIDIOC_TRY_FMT: - ret = try_fmt_ioctl(priv, arg); + ret = v4l_gst_ioctl_try_fmt(priv, arg); break; case VIDIOC_UNSUBSCRIBE_EVENT: - ret = unsubscribe_event_ioctl(priv, arg); + ret = v4l_gst_ioctl_unsubscribe_event(priv, arg); break; case VIDIOC_DECODER_CMD: - ret = decoder_cmd_ioctl(priv, arg); + ret = v4l_gst_ioctl_decoder_cmd(priv, arg); break; case VIDIOC_TRY_DECODER_CMD: - ret = try_decoder_cmd_ioctl(priv, arg); + ret = v4l_gst_ioctl_try_decoder_cmd(priv, arg); break; default: DBG_LOG("unknown ioctl: %lu\n", cmd); @@ -149,7 +149,7 @@ plugin_mmap(void *dev_ops_priv, void *start, size_t length, int prot, int flags, int fd, int64_t offset) { struct v4l_gst *priv = dev_ops_priv; - return gst_backend_mmap(priv, start, length, prot, flags, fd, + return v4l_gst_mmap(priv, start, length, prot, flags, fd, offset); } diff --git a/lib/libv4l-gst/meson.build b/lib/libv4l-gst/meson.build index b3b45c9..e86c8d9 100644 --- a/lib/libv4l-gst/meson.build +++ b/lib/libv4l-gst/meson.build @@ -1,8 +1,11 @@ -# gst-backend-testhooks.c is included into gst-backend.c directly, -# do not compile it separately. plugin_sources = [ 'libv4l-gst.c', - 'gst-backend.c', + 'v4l-gst-core.c', + 'v4l-gst-pipeline.c', + 'v4l-gst-fmt.c', + 'v4l-gst-buf.c', + 'v4l-gst-ctrl.c', + 'v4l-gst-event.c', 'evfd-ctrl.c', 'utils.c', ] diff --git a/lib/libv4l-gst/v4l-gst-buf.c b/lib/libv4l-gst/v4l-gst-buf.c new file mode 100644 index 0000000..849b97f --- /dev/null +++ b/lib/libv4l-gst/v4l-gst-buf.c @@ -0,0 +1,1840 @@ +/* + * Copyright (C) 2015 Renesas Electronics Corporation + * Copyright (C) 2024-2026 ClearCode Inc. + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "libv4l-gst-bufferpool.h" + +#include "v4l-gst.h" +#include "evfd-ctrl.h" +#include "debug.h" +#include "utils.h" +#include "v4l-gst-internal.h" + +static inline void +release_out_buffer_unlocked(struct v4l_gst *priv, GstBuffer *gstbuf) +{ + GST_TRACE("unref buffer: %p", gstbuf); + gst_buffer_unref(gstbuf); + + set_event(priv->event_state, POLLIN); + + priv->out.returned_cnt++; +} + + +void +v4l_gst_buf_release_out_buffer(struct v4l_gst *priv, GstBuffer *gstbuf) +{ + g_mutex_lock(&priv->queue_mutex); + + release_out_buffer_unlocked(priv, gstbuf); + + g_mutex_unlock(&priv->queue_mutex); +} + + +static gboolean +is_supported_memory_io(enum v4l2_memory memory) +{ + if (memory != V4L2_MEMORY_MMAP) { + GST_ERROR("Only V4L2_MEMORY_MMAP is supported"); + errno = EINVAL; + return FALSE; + } + + return TRUE; +} + + +/* This check passes through the verification of the buffer index. */ +static gboolean +check_no_index_v4l2_buffer(struct v4l2_buffer *v4l2buf, + struct v4l_gst_buffer *buffers, GstBufferPool *pool) +{ + GstVideoMeta *meta; + GstVideoInfo info; + guint n_planes; + + if (!is_supported_memory_io(v4l2buf->memory)) + return FALSE; + + if (!v4l2buf->m.planes) { + GST_ERROR("This plugin supports only multi-planar " + "buffer type, but planes array is not set"); + errno = EINVAL; + return FALSE; + } + + if (!buffers) { + GST_ERROR("Buffers list is not set"); + errno = EINVAL; + return FALSE; + } + + if (v4l_gst_pipeline_get_raw_video_params(pool, buffers[v4l2buf->index].gstbuf, + &info, &meta)) + n_planes = meta ? meta->n_planes : GST_VIDEO_INFO_N_PLANES(&info); + else + n_planes = 1; + + if (v4l2buf->length < n_planes || v4l2buf->length > VIDEO_MAX_PLANES) { + GST_ERROR("Incorrect planes array length"); + errno = EINVAL; + return FALSE; + } + + return TRUE; +} + + +static gboolean +check_v4l2_buffer(struct v4l2_buffer *v4l2buf, struct v4l_gst_buffer *buffers, + gint buffers_num, GstBufferPool *pool) +{ + if (!check_no_index_v4l2_buffer(v4l2buf, buffers, pool)) + return FALSE; + + if (v4l2buf->index >= buffers_num) { + GST_ERROR("buffer index is out of range"); + errno = EINVAL; + return FALSE; + } + + return TRUE; +} + + +static void +notify_unref(gpointer data) +{ + struct v4l_gst_buffer *buffer = data; + + v4l_gst_buf_release_out_buffer(buffer->priv, buffer->gstbuf); +} + + +static int +qbuf_ioctl_out(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) +{ + GstFlowReturn flow_ret; + GstBuffer *wrapped_gstbuf; + GstMapInfo info; + struct v4l_gst_buffer *buffer; + + if (!check_v4l2_buffer(v4l2buf, priv->out.buffers, priv->out.buffers_num, + priv->out.pool)) + return -1; + + buffer = &priv->out.buffers[v4l2buf->index]; + + if (v4l2buf->m.planes[0].bytesused == 0) { + flow_ret = gst_app_src_end_of_stream(GST_APP_SRC(priv->appsrc)); + if (flow_ret != GST_FLOW_OK) { + GST_ERROR("Failed to send an EOS event"); + errno = EINVAL; + return -1; + } + GST_DEBUG("Send EOS event"); + + gst_buffer_unmap(buffer->gstbuf, &buffer->info); + memset(&buffer->info, 0, sizeof(buffer->info)); + + buffer->state = V4L_GST_BUFFER_QUEUED; + priv->eos_gstbuf = buffer->gstbuf; + + return 0; + } + + if (buffer->state == V4L_GST_BUFFER_QUEUED) { + GST_ERROR("Buffer %u is already queued", v4l2buf->index); + errno = EINVAL; + return -1; + } + + /* Push the OUTPUT caps to the appsrc before the first buffer so the + base source can negotiate its caps before the first buffer is + pushed. Otherwise the caps sit behind the buffers in the appsrc + queue and the first push fails with NOT_NEGOTIATED. */ + if (!priv->out_caps_set) { + if (!v4l_gst_pipeline_set_out_format(priv)) + return -1; + priv->out_caps_set = TRUE; + } + + GST_TRACE("queue index=%d buffer=%p", v4l2buf->index, + priv->out.buffers[v4l2buf->index].gstbuf); + + gst_buffer_unmap(buffer->gstbuf, &buffer->info); + memset(&buffer->info, 0, sizeof(buffer->info)); + + /* Rewrap an input buffer with the just size of bytesused + because it will be regarded as having data filled to the entire + buffer size internally in the GStreamer pipeline. + Also set the destructor (notify_unref()). */ + + if (!gst_buffer_map(buffer->gstbuf, &info, GST_MAP_READ)) { + GST_ERROR("Failed to map buffer (%p)", buffer->gstbuf); + errno = EINVAL; + return -1; + } + + wrapped_gstbuf = + gst_buffer_new_wrapped_full(GST_MEMORY_FLAG_READONLY, info.data, + v4l2buf->m.planes[0].bytesused, 0, + v4l2buf->m.planes[0].bytesused, + buffer, notify_unref); + + gst_buffer_unmap(buffer->gstbuf, &info); + + GST_BUFFER_PTS(wrapped_gstbuf) = GST_TIMEVAL_TO_TIME(v4l2buf->timestamp); + GST_CAT_TRACE(v4l_gst_buffer_debug_category, + "QBUF OUT: gstbuf=%p, index=%d, pts=%lu", + wrapped_gstbuf, v4l2buf->index, + GST_BUFFER_PTS(wrapped_gstbuf) / 1000000); + + buffer->state = V4L_GST_BUFFER_QUEUED; + + flow_ret = gst_app_src_push_buffer(GST_APP_SRC(priv->appsrc), + wrapped_gstbuf); + if (flow_ret != GST_FLOW_OK) { + GST_ERROR("Failed to push a buffer to the pipeline on OUTPUT" + "(index=%d)", v4l2buf->index); + errno = EINVAL; + return -1; + } + + if (priv->out.cnt < INPUT_BUFFERING_CNT) + priv->out.cnt++; + + return 0; +} + + +static gboolean +push_to_cap_gstbufs_queue(struct v4l_gst *priv, GstBuffer *gstbuf) +{ + gboolean is_empty; + gint index; + + index = g_queue_index(priv->out.gstbufs_queue, gstbuf); + if (index < 0) + return FALSE; + + g_mutex_lock(&priv->queue_mutex); + + is_empty = g_queue_is_empty(priv->cap.gstbufs_queue); + g_queue_push_tail(priv->cap.gstbufs_queue, gstbuf); + + if (is_empty) + g_cond_signal(&priv->queue_cond); + + g_mutex_unlock(&priv->queue_mutex); + + g_queue_pop_nth_link(priv->out.gstbufs_queue, index); + + return TRUE; +} + + +static int +qbuf_ioctl_cap(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) +{ + struct v4l_gst_buffer *buffer; + + if (!check_v4l2_buffer(v4l2buf, priv->cap.buffers, priv->cap.buffers_num, + priv->cap.pool)) + return -1; + + buffer = &priv->cap.buffers[v4l2buf->index]; + + if (priv->config.frame_check && gst_buffer_n_memory(buffer->gstbuf)) { + gpointer crc_p; + guint32 crc, crc_dest; + + crc_p = gst_mini_object_get_qdata(GST_MINI_OBJECT(buffer->gstbuf), + v4l_gst_cap_buf_crc_quark()); + crc = GPOINTER_TO_UINT(crc_p); + crc_dest = frame_crc32(buffer->gstbuf, + priv->config.frame_check); + GST_CAT_TRACE(v4l_gst_buffer_debug_category, + "QBUF CAP: gstbuf=%p, index=%d, pts=%lu, crc=%u", + buffer->gstbuf, v4l2buf->index, + GST_BUFFER_PTS(buffer->gstbuf) / 1000000, crc); + if (crc && crc_dest != crc) + GST_CAT_WARNING(v4l_gst_buffer_debug_category, + "crc is changed!: src=%u, dest=%u", + crc, crc_dest); + } else { + GST_CAT_TRACE(v4l_gst_buffer_debug_category, + "QBUF CAP: gstbuf=%p, index=%d, pts=%lu", + buffer->gstbuf, v4l2buf->index, + GST_BUFFER_PTS(buffer->gstbuf) / 1000000); + } + + if (buffer->state == V4L_GST_BUFFER_QUEUED) { + GST_ERROR("Buffer %u is already queued", v4l2buf->index); + errno = EINVAL; + return -1; + } + + gst_buffer_unmap(buffer->gstbuf, &buffer->info); + memset(&buffer->info, 0, sizeof(buffer->info)); + + /* The buffers in req_gstbufs_queue, which are pushed by the REQBUF ioctl + on CAPTURE, have already contained decoded frames. + They should not back to the buffer pool and prepare to be + dequeued as they are. */ + if (g_queue_get_length(priv->out.gstbufs_queue) > 0) { + GST_TRACE("push_to_cap_gstbufs_queue index=%d", v4l2buf->index); + if (push_to_cap_gstbufs_queue(priv, buffer->gstbuf)) { + buffer->state =V4L_GST_BUFFER_QUEUED; + return 0; + } + } + + GST_TRACE("unref buffer: %p, index=%d", buffer->gstbuf, v4l2buf->index); + buffer->state = V4L_GST_BUFFER_QUEUED; + + gst_buffer_unref(buffer->gstbuf); + + return 0; +} + + +int +v4l_gst_ioctl_qbuf(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) +{ + int ret = -1; + + GST_TRACE("VIDIOC_QBUF: type: %s (0x%x) index: %d flags: 0x%x", + v4l2_buffer_type_to_string(v4l2buf->type), v4l2buf->type, + v4l2buf->index, v4l2buf->flags); + + g_mutex_lock(&priv->dev_lock); + + if (v4l2buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + ret = qbuf_ioctl_out(priv, v4l2buf); + } else if (v4l2buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + ret = qbuf_ioctl_cap(priv, v4l2buf); + } else { + GST_ERROR("Invalid buf type"); + errno = EINVAL; + } + + g_mutex_unlock(&priv->dev_lock); + + return ret; +} + + +static inline guint +calc_plane_size(GstVideoInfo *info, GstVideoMeta *meta, gint index) +{ + guint stride = meta ? meta->stride[index] + : GST_VIDEO_INFO_PLANE_STRIDE(info, index); + return stride * GST_VIDEO_INFO_COMP_HEIGHT(info, index); +} + + +static void +set_v4l2_buffer_plane_params(struct v4l_gst *priv, + struct v4l_gst_buffer *buffers, guint n_planes, + guint bytesused[], struct timeval *timestamp, + struct v4l2_buffer *v4l2buf) +{ + gint i; + guint32 offset = 0; + + memcpy(v4l2buf->m.planes, buffers[v4l2buf->index].planes, + sizeof(struct v4l2_plane) * n_planes); + + for (i = 0; i < n_planes; i++) { + if (bytesused) + v4l2buf->m.planes[i].bytesused = bytesused[i]; + /* Use `data_offset` to notify offsets of multi-planes combined + in single dmabuf fd. Most applications will ignore this + field, so you'll need to modify such applications. */ + if (gst_buffer_n_memory(buffers[v4l2buf->index].gstbuf) == 1) { + v4l2buf->m.planes[i].data_offset = offset; + offset += v4l2buf->m.planes[i].length; + } + } + + if (timestamp) { + v4l2buf->timestamp.tv_sec = timestamp->tv_sec; + v4l2buf->timestamp.tv_usec = timestamp->tv_usec; + } else { + v4l2buf->timestamp.tv_sec = v4l2buf->timestamp.tv_usec = 0; + } +} + + +static int +fill_v4l2_buffer(struct v4l_gst *priv, GstBufferPool *pool, + struct v4l_gst_buffer *buffers, gint buffers_num, + guint bytesused[], struct timeval *timestamp, + struct v4l2_buffer *v4l2buf) +{ + GstVideoMeta *meta = NULL; + GstVideoInfo info; + guint n_planes; + + if (v4l_gst_pipeline_get_raw_video_params(pool, buffers[v4l2buf->index].gstbuf, + &info, &meta)) + n_planes = (meta) ? meta->n_planes + : GST_VIDEO_INFO_N_PLANES(&info); + else + n_planes = 1; + + set_v4l2_buffer_plane_params(priv, buffers, n_planes, bytesused, + timestamp, v4l2buf); + + v4l2buf->flags = 0; + if (v4l2buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + priv->eos_state == EOS_GOT && + g_queue_is_empty(priv->cap.gstbufs_queue)) { + GST_DEBUG("EOS: Set V4L2_BUF_FLAG_LAST"); + v4l2buf->flags |= V4L2_BUF_FLAG_LAST; + priv->eos_state = EOS_NONE; + clear_event(priv->event_state, POLLOUT); + } + + /* set unused params */ + memset(&v4l2buf->timecode, 0, sizeof(v4l2buf->timecode)); + v4l2buf->sequence = 0; + v4l2buf->field = V4L2_FIELD_NONE; + + v4l2buf->length = n_planes; + + return 0; +} + + +static guint +get_v4l2_buffer_index(struct v4l_gst_buffer *buffers, gint buffers_num, + GstBuffer *gstbuf) +{ + gint i; + guint index = G_MAXUINT; + + for (i = 0; i < buffers_num; i++) { + if (buffers[i].gstbuf == gstbuf) { + index = i; + break; + } + } + + return index; +} + + +static GstBuffer * +dequeue_blocking(struct v4l_gst *priv, GQueue *queue, GCond *cond) +{ + GstBuffer *gstbuf; + + gstbuf = g_queue_pop_head(queue); + while (!gstbuf && priv->is_pipeline_started) { + g_cond_wait(cond, &priv->queue_mutex); + gstbuf = g_queue_pop_head(queue); + } + + return gstbuf; +} + + +static GstBuffer * +dequeue_non_blocking(GQueue *queue) +{ + GstBuffer *gstbuf; + + gstbuf = g_queue_pop_head(queue); + if (!gstbuf) { + GST_TRACE("The buffer pool is empty in " + "the non-blocking mode, return EAGAIN"); + errno = EAGAIN; + } + + return gstbuf; +} + + +static GstBuffer * +dequeue_cap_buffer(struct v4l_gst *priv) +{ + GQueue *queue = priv->cap.gstbufs_queue; + GstBuffer *gstbuf = NULL; + guint len; + + g_mutex_lock(&priv->queue_mutex); + + /* Cache 1 buffer to detect EOS. The encoder emits a finite set of + encoded buffers and reports end of stream through the appsrc EOS + event, so holding back the last buffer would stall the client + forever; always dequeue it instead. */ + if (priv->cap.kind != V4L_GST_MEDIA_KIND_CODEC) { + len = g_queue_get_length(queue); + if (priv->eos_state != EOS_GOT && len == 1) + goto unlock; + } + + if (priv->is_non_blocking) + gstbuf = dequeue_non_blocking(queue); + else + gstbuf = dequeue_blocking(priv, queue, &priv->queue_cond); + + if (!gstbuf) + goto unlock; + + /* Cache 1 buffer to detect EOS */ + len = g_queue_get_length(queue); + if ((priv->eos_state != EOS_GOT && len == 1) || len == 0) + clear_event(priv->event_state, POLLOUT); + + unlock: + g_mutex_unlock(&priv->queue_mutex); + + return gstbuf; +} + + +static GstBuffer * +acquire_buffer_from_pool(struct v4l_gst *priv, GstBufferPool *pool) +{ + GstFlowReturn flow_ret; + GstBuffer *gstbuf; + GstBufferPoolAcquireParams params = { 0, }; + + if (priv->is_non_blocking) { + params.flags |= GST_BUFFER_POOL_ACQUIRE_FLAG_DONTWAIT; + } else + g_mutex_unlock(&priv->queue_mutex); + + flow_ret = gst_buffer_pool_acquire_buffer(pool, &gstbuf, ¶ms); + if (!priv->is_non_blocking) + g_mutex_lock(&priv->queue_mutex); + + if (priv->is_non_blocking && flow_ret == GST_FLOW_EOS) { + GST_TRACE("The buffer pool is empty in " + "the non-blocking mode, return EAGAIN"); + errno = EAGAIN; + return NULL; + } else if (flow_ret != GST_FLOW_OK) { + GST_ERROR("gst_buffer_pool_acquire_buffer failed"); + errno = EINVAL; + return NULL; + } + + return gstbuf; +} + + + +/* gst-omx may reassociate GstBuffers and dmabuf-fds without closing them after + flushing. Since clients will associate a dmabuf-fd with a v4l2_buffer's + index, we need to update GstBuffer-index association after flushing to avoid + misalign with clients. */ +static void +reindex_buffers(struct v4l_gst *priv) +{ + int i, j; + gchar fd_str[8], error_fds[128] = {0}; + gchar opened_fds[128] = {0}, initial_fds[128] = {0}; + + for (i = 0; i < priv->cap.buffers_num; ++i) { + struct v4l_gst_buffer *buf1 = &priv->cap.buffers[i]; + GstMemory *mem; + int fd; + + g_snprintf(fd_str, sizeof(fd_str), "%d:%d,", + i, buf1->plane0_fd); + g_strlcat(initial_fds, fd_str, sizeof(initial_fds)); + + if (!buf1->plane0_fd) + continue; + if (buf1->state == V4L_GST_BUFFER_DEQUEUED) + continue; + if (!gst_buffer_n_memory(buf1->gstbuf)) + continue; + + mem = gst_buffer_peek_memory(buf1->gstbuf, 0); + if (!gst_is_dmabuf_memory(mem)) + continue; + + fd = gst_dmabuf_memory_get_fd(mem); + + g_snprintf(fd_str, sizeof(fd_str), "%d:%d,", i, fd); + g_strlcat(opened_fds, fd_str, sizeof(opened_fds)); + + if (buf1->plane0_fd == fd) + continue; + + for (j = 0; j < priv->cap.buffers_num; ++j) { + struct v4l_gst_buffer *buf2 = &priv->cap.buffers[j]; + GstBuffer *gstbuf = buf1->gstbuf; + + if (i == j) + continue; + if (buf2->plane0_fd != fd) + continue; + if (buf2->state == V4L_GST_BUFFER_DEQUEUED) + continue; + + buf1->gstbuf = buf2->gstbuf; + buf2->gstbuf = gstbuf; + i--; + break; + } + if (j >= priv->cap.buffers_num) { + g_snprintf(fd_str, sizeof(fd_str), "%d:%d,", i, fd); + g_strlcat(error_fds, fd_str, sizeof(error_fds)); + } + } + + if (*error_fds) { + GST_WARNING("Failed to reindex dmabuf fd!"); + GST_DEBUG("failed fds: %s / opened_fds: %s / initial_fds: %s", + error_fds, opened_fds, initial_fds); + } +} + + +static int +dqbuf_ioctl_out(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) +{ + GstBuffer *gstbuf; + guint index; + + if (!priv->is_pipeline_started) { + GST_ERROR("The pipeline does not start yet."); + errno = EINVAL; + return -1; + } + + if (!check_no_index_v4l2_buffer(v4l2buf, priv->out.buffers, + priv->out.pool)) + return -1; + + g_mutex_lock(&priv->queue_mutex); + + gstbuf = acquire_buffer_from_pool(priv, priv->out.pool); + if (!gstbuf) { + g_mutex_unlock(&priv->queue_mutex); + return -1; + } + + priv->out.returned_cnt--; + + if (priv->out.returned_cnt == 0) { + clear_event(priv->event_state, POLLIN); + } + + + g_mutex_unlock(&priv->queue_mutex); + + index = get_v4l2_buffer_index(priv->out.buffers, + priv->out.buffers_num, gstbuf); + if (index >= priv->out.buffers_num) { + GST_ERROR("Failed to get a valid buffer index " + "on OUTPUT"); + errno = EINVAL; + return -1; + } + + v4l2buf->index = index; + priv->out.buffers[v4l2buf->index].state = V4L_GST_BUFFER_DEQUEUED; + + GST_CAT_TRACE(v4l_gst_buffer_debug_category, + "DQBUF OUT: gstbuf=%p, index=%d", + gstbuf, index); + + return fill_v4l2_buffer(priv, priv->out.pool, + priv->out.buffers, priv->out.buffers_num, + NULL, NULL, v4l2buf); +} + + +static gboolean +get_valid_cap_pts(struct v4l_gst *priv, GstBuffer *gstbuf, + GstClockTime *pts) +{ + GstClockTime buffer_pts = GST_BUFFER_PTS(gstbuf); + GstClockTime buffer_duration = GST_BUFFER_DURATION(gstbuf); + + if (GST_CLOCK_TIME_IS_VALID(buffer_pts)) { + if (GST_CLOCK_TIME_IS_VALID(priv->last_cap_pts) && + buffer_pts > priv->last_cap_pts) { + priv->estimated_cap_duration = + buffer_pts - priv->last_cap_pts; + } else if (!GST_CLOCK_TIME_IS_VALID(priv->estimated_cap_duration) && + GST_CLOCK_TIME_IS_VALID(buffer_duration) && + buffer_duration > 0) { + priv->estimated_cap_duration = buffer_duration; + } + + priv->last_cap_pts = buffer_pts; + *pts = buffer_pts; + return TRUE; + } + + if (GST_CLOCK_TIME_IS_VALID(priv->last_cap_pts) && + GST_CLOCK_TIME_IS_VALID(priv->estimated_cap_duration) && + G_MAXUINT64 - priv->last_cap_pts >= priv->estimated_cap_duration) { + /* Some decoders may output the last CAPTURE buffer without a + valid PTS. Do not expose GST_CLOCK_TIME_NONE as a V4L2 + timestamp because clients can interpret it as a huge future + timestamp and wait indefinitely for playback end. */ + *pts = priv->last_cap_pts + priv->estimated_cap_duration; + GST_WARNING("CAPTURE buffer has invalid PTS; using estimated PTS " + "(last=%" G_GUINT64_FORMAT ", duration=%" + G_GUINT64_FORMAT ", estimated=%" G_GUINT64_FORMAT ")", + (guint64) priv->last_cap_pts, + (guint64) priv->estimated_cap_duration, + (guint64) *pts); + priv->last_cap_pts = *pts; + return TRUE; + } + + GST_WARNING("CAPTURE buffer has invalid PTS and no estimate is available"); + return FALSE; +} + + +static int +dqbuf_ioctl_cap(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) +{ + GstBuffer *gstbuf; + guint index; + struct timeval timestamp; + struct timeval *timestamp_ptr = NULL; + GstClockTime pts = GST_CLOCK_TIME_NONE; + guint bytesused[GST_VIDEO_MAX_PLANES]; + gint i; + + if (!check_no_index_v4l2_buffer(v4l2buf, priv->cap.buffers, + priv->cap.pool)) + return -1; + + gstbuf = dequeue_cap_buffer(priv); + if (!gstbuf) + return -1; + + reindex_buffers(priv); + + index = get_v4l2_buffer_index(priv->cap.buffers, + priv->cap.buffers_num, gstbuf); + if (index >= priv->cap.buffers_num) { + GST_ERROR("Failed to get a valid buffer index " + "on CAPTURE"); + errno = EINVAL; + gst_buffer_unref(gstbuf); + return -1; + } + + v4l2buf->index = index; + + if (priv->cap.kind == V4L_GST_MEDIA_KIND_CODEC) { + /* For an encoded stream, the amount of valid data is the + actual size of the GStreamer buffer. */ + bytesused[0] = gst_buffer_get_size(gstbuf); + } else { + for (i = 0; i < priv->cap.fmt.num_planes; i++) + bytesused[i] = priv->cap.fmt.plane_fmt[i].sizeimage; + } + + if (priv->cap.buffers[index].state == V4L_GST_BUFFER_DEQUEUED) { + /* It might occur when a buffer is unexpectedly queued + after streamoff_ioctl_out(). In this case reference count of + the buffer should already have been incremented, need to + revert it here. */ + GST_WARNING("Already dequeued buffer %u is dequeued again", index); + gst_buffer_unref(priv->cap.buffers[index].gstbuf); + } + priv->cap.buffers[v4l2buf->index].state = V4L_GST_BUFFER_DEQUEUED; + + if (get_valid_cap_pts(priv, gstbuf, &pts)) { + GST_TIME_TO_TIMEVAL(pts, timestamp); + timestamp_ptr = ×tamp; + } + GST_CAT_TRACE(v4l_gst_buffer_debug_category, + "DQBUF CAP: gstbuf=%p, index=%d, pts=%lu, raw_pts=%lu", + gstbuf, index, pts / 1000000, + GST_BUFFER_PTS(gstbuf) / 1000000); + + return fill_v4l2_buffer(priv, priv->cap.pool, + priv->cap.buffers, priv->cap.buffers_num, + bytesused, timestamp_ptr, v4l2buf); +} + + +int +v4l_gst_ioctl_dqbuf(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) +{ + int ret = -1; + + GST_TRACE("VIDIOC_DQBUF: type: %s (0x%x) index: %d flags: 0x%x", + v4l2_buffer_type_to_string(v4l2buf->type), v4l2buf->type, + v4l2buf->index, v4l2buf->flags); + + g_mutex_lock(&priv->dev_lock); + + if (v4l2buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + ret = dqbuf_ioctl_out(priv, v4l2buf); + } else if (v4l2buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + ret = dqbuf_ioctl_cap(priv, v4l2buf); + } else { + GST_ERROR("Invalid buf type"); + errno = EINVAL; + } + + g_mutex_unlock(&priv->dev_lock); + + return ret; +} + + +int +v4l_gst_ioctl_querybuf(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) +{ + struct v4l_gst_buffer *buffers; + gint buffers_num; + GstBufferPool *pool; + int ret; + + GST_TRACE("VIDIOC_QUERYBUF: type: %s (0x%x) index: %d flags: 0x%x", + v4l2_buffer_type_to_string(v4l2buf->type), v4l2buf->type, + v4l2buf->index, v4l2buf->flags); + + g_mutex_lock(&priv->dev_lock); + + if (v4l2buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + buffers = priv->out.buffers; + buffers_num = priv->out.buffers_num; + pool = priv->out.pool; + } else if (v4l2buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + buffers = priv->cap.buffers; + buffers_num = priv->cap.buffers_num; + pool = priv->cap.pool; + } else { + GST_ERROR("Invalid buf type"); + errno = EINVAL; + g_mutex_unlock(&priv->dev_lock); + return -1; + } + + if (!check_v4l2_buffer(v4l2buf, buffers, buffers_num, pool)) { + g_mutex_unlock(&priv->dev_lock); + return -1; + } + + ret = fill_v4l2_buffer(priv, pool, buffers, buffers_num, + NULL, NULL, v4l2buf); + + g_mutex_unlock(&priv->dev_lock); + + return ret; +} + + + +#define PAGE_ALIGN(off, align) ((off + align - 1) & ~(align - 1)) +static gsize +set_mem_offset(struct v4l_gst_buffer *buffer, GstBufferPool *pool, gsize offset) +{ + GstVideoInfo info; + GstVideoMeta *meta; + static long page_size = -1; + gint i; + + if (page_size < 0) + page_size = sysconf(_SC_PAGESIZE); + + if (!v4l_gst_pipeline_get_raw_video_params(pool, buffer->gstbuf, &info, &meta)) { + /* deal with this as a single plane */ + buffer->planes[0].m.mem_offset = offset; + return PAGE_ALIGN(gst_buffer_get_size(buffer->gstbuf), + page_size) + offset; + } + + { + guint n_planes = meta ? meta->n_planes + : GST_VIDEO_INFO_N_PLANES(&info); + for (i = 0; i < (gint) n_planes; i++) { + buffer->planes[i].m.mem_offset = offset; + offset += PAGE_ALIGN(calc_plane_size(&info, meta, i), + page_size); + } + } + + return offset; +} + + +static guint +alloc_buffers_from_pool(struct v4l_gst *priv, GstBufferPool *pool, + struct v4l_gst_buffer **buffers) +{ + GstBufferPoolAcquireParams params = { 0, }; + GstFlowReturn flow_ret; + guint actual_max_buffers; + struct v4l_gst_buffer *bufs_list; + gint i; + + if (!gst_buffer_pool_set_active(pool, TRUE)) { + GST_ERROR("Failed to activate buffer pool on OUTPUT"); + errno = EINVAL; + return 0 ; + } + + /* The buffer pool parameters can not be changed after activation, + so it is good time to confirm the number of buffers actually set to + the buffer pool. */ + v4l_gst_pipeline_get_buffer_pool_params(pool, NULL, NULL, NULL, &actual_max_buffers); + if (actual_max_buffers == 0) { + GST_ERROR("Cannot handle the unlimited amount of buffers"); + errno = EINVAL; + goto inactivate_pool; + } + + bufs_list = g_new0(struct v4l_gst_buffer, actual_max_buffers); + + for (i = 0; i < actual_max_buffers; i++) { + flow_ret = gst_buffer_pool_acquire_buffer(pool, + &bufs_list[i].gstbuf, + ¶ms); + if (flow_ret != GST_FLOW_OK) { + GST_ERROR("Failed to acquire a buffer on OUTPUT"); + errno = ENOMEM; + goto free_bufs_list; + } + + bufs_list[i].priv = priv; + bufs_list[i].state = V4L_GST_BUFFER_DEQUEUED; + + GST_DEBUG("out gst_buffer[%d] : %p", i, bufs_list[i].gstbuf); + } + + *buffers = bufs_list; + + GST_DEBUG("The number of buffers actually set to the buffer pool is %d", + actual_max_buffers); + + return actual_max_buffers; + + /* error cases */ + free_bufs_list: + for (i = 0; i < actual_max_buffers; i++) { + if (bufs_list[i].gstbuf) + gst_buffer_unref(bufs_list[i].gstbuf); + } + g_free(bufs_list); + inactivate_pool: + gst_buffer_pool_set_active(pool, FALSE); + + return 0; +} + + +static GstFlowReturn +force_dqbuf_from_pool(GstBufferPool *pool, struct v4l_gst_buffer *buffers, + gint buffers_num, gboolean map) +{ + GstFlowReturn flow_ret; + GstBufferPoolAcquireParams params = { 0, }; + GstBuffer *gstbuf; + guint index; + + params.flags = GST_BUFFER_POOL_ACQUIRE_FLAG_DONTWAIT; + + /* force to make buffers available to the V4L2 caller side */ + flow_ret = gst_buffer_pool_acquire_buffer(pool, &gstbuf, ¶ms); + if (flow_ret != GST_FLOW_OK) + return flow_ret; + + index = get_v4l2_buffer_index(buffers, buffers_num, gstbuf); + if (index >= buffers_num) { + GST_ERROR("Failed to get a valid buffer index"); + errno = EINVAL; + return GST_FLOW_ERROR; + } + + buffers[index].state = V4L_GST_BUFFER_DEQUEUED; + + if (!map) + return GST_FLOW_OK; + + if (!gst_buffer_map(gstbuf, &buffers[index].info, + buffers[index].flags)) { + GST_ERROR("Failed to map buffer (%p)", gstbuf); + errno = EINVAL; + return GST_FLOW_ERROR; + } + return GST_FLOW_OK; +} + + +static int +force_out_dqbuf(struct v4l_gst *priv) +{ + g_mutex_lock(&priv->queue_mutex); + + while (force_dqbuf_from_pool(priv->out.pool, priv->out.buffers, + priv->out.buffers_num, TRUE) == GST_FLOW_OK) { + priv->out.returned_cnt--; + } + + clear_event(priv->event_state, POLLIN); + + g_mutex_unlock(&priv->queue_mutex); + + GST_DEBUG("returned_out_buffers_num : %d", priv->out.returned_cnt); + + return 0; +} + + +static int +force_cap_dqbuf(struct v4l_gst *priv) +{ + GstBuffer *gstbuf; + guint index; + + do { + g_mutex_lock(&priv->queue_mutex); + gstbuf = dequeue_non_blocking(priv->cap.gstbufs_queue); + /* This function may set errno but not need to expose it to + clients in this case */ + errno = 0; + g_mutex_unlock(&priv->queue_mutex); + + if (!gstbuf) + break; + + index = get_v4l2_buffer_index(priv->cap.buffers, + priv->cap.buffers_num, gstbuf); + if (index >= priv->cap.buffers_num) { + GST_ERROR("Failed to get a valid buffer index " + "on CAPTURE"); + errno = EINVAL; + return -1; + } + + priv->cap.buffers[index].state = V4L_GST_BUFFER_DEQUEUED; + GST_DEBUG("CAPTURE buffer %u is forcedly dequeued", index); + } while (gstbuf); + + clear_event(priv->event_state, POLLOUT); + + for (index = 0; index < priv->cap.buffers_num; index++) { + if (priv->cap.buffers[index].state == V4L_GST_BUFFER_DEQUEUED) + continue; + gst_buffer_ref(priv->cap.buffers[index].gstbuf); + priv->cap.buffers[index].state = V4L_GST_BUFFER_DEQUEUED; + } + + return 0; +} + + +static int +streamoff_ioctl_out(struct v4l_gst *priv, gboolean steal_ref) +{ + int ret; + + GST_CAT_DEBUG(v4l_gst_buffer_debug_category, + "STREAMOFF OUT begin: dequeue all OUT & CAP buffers ..."); + + v4l_gst_core_set_pipeline_started(priv, FALSE); + + GST_OBJECT_LOCK(priv->pipeline); + if (GST_STATE(priv->pipeline) == GST_STATE_NULL) { + /* No need to flush the pipeline after it has been + the NULL state. */ + GST_OBJECT_UNLOCK(priv->pipeline); + goto flush_buffer_queues; + } + GST_OBJECT_UNLOCK(priv->pipeline); + + + ret = v4l_gst_pipeline_flush(priv); + + if (ret < 0) + return ret; + + flush_buffer_queues: + /* Vacate the buffers queues to make them available in the next time */ + ret = force_out_dqbuf(priv); + if (ret < 0) + return ret; + + ret = force_cap_dqbuf(priv); + if (ret < 0) + return ret; + + /* The reference counted up below will be unreffed when calling + the streamon ioctl. This prevents from returning all the buffers + of the OUTPUT bufferpool and freeing them by inactivating + the bufferpool for flushing. */ + if (steal_ref) + gst_buffer_ref(priv->out.buffers[0].gstbuf); + + /* wake up blocking of the OUTPUT buffer acquisition */ + if (!gst_buffer_pool_set_active(priv->out.pool, FALSE)) { + GST_ERROR("Failed to inactivate buffer pool on OUTPUT"); + errno = EINVAL; + return -1; + } + + /* wake up blocking of the CAPTURE buffer acquisition */ + v4l_gst_core_set_pipeline_started(priv, FALSE); + + GST_CAT_DEBUG(v4l_gst_buffer_debug_category, "STREAMOFF OUT end"); + + return 0; +} + + +static int +reqbuf_ioctl_out(struct v4l_gst *priv, + struct v4l2_requestbuffers *req) +{ + GstCaps *caps; + guint adjusted_count; + guint allocated_num; + int ret; + guint i; + + if (!is_supported_memory_io(req->memory)) + return -1; + + g_mutex_lock(&priv->dev_lock); + + if (req->count == 0) { + GST_DEBUG("req->count == 0"); + + /* The following function flushes both the OUTPUT and CAPTURE + buffer types because the GStreamer can only flush the whole + of the pipeline, so the buffers of both the buffer types + need to be requeued after this operation. + */ + ret = streamoff_ioctl_out(priv, FALSE); + if (ret < 0) + goto unlock; + + /* Force to return dequeued buffers to the buffer pool. */ + for (i = 0; i < priv->out.buffers_num; i++) { + if (priv->out.buffers[i].state == + V4L_GST_BUFFER_DEQUEUED) { + gst_buffer_unref(priv->out.buffers[i].gstbuf); + } + } + + if (priv->out.buffers) { + g_free(priv->out.buffers); + priv->out.buffers = NULL; + } + + ret = 0; + goto unlock; + } + + if (priv->is_pipeline_started) { + GST_ERROR("The pipeline is already running"); + errno = EBUSY; + ret = -1; + goto unlock; + } + + if (gst_buffer_pool_is_active(priv->out.pool)) { + if (!gst_buffer_pool_set_active(priv->out.pool, FALSE)) { + GST_ERROR("Failed to inactivate buffer pool"); + errno = EBUSY; + ret = -1; + goto unlock; + } + } + + if (priv->out.kind == V4L_GST_MEDIA_KIND_RAW) + caps = v4l_gst_pipeline_get_raw_caps_from_fmt(&priv->out.fmt); + else + caps = v4l_gst_pipeline_get_codec_caps_from_fourcc( + priv->out.fmt.pixelformat); + if (!caps) { + errno = EINVAL; + ret = -1; + goto unlock; + } + + adjusted_count = MAX(req->count, INPUT_BUFFERING_CNT); + adjusted_count = MIN(adjusted_count, VIDEO_MAX_FRAME); + + v4l_gst_pipeline_set_buffer_pool_params(priv->out.pool, caps, + priv->out.fmt.plane_fmt[0].sizeimage, + adjusted_count, adjusted_count, NULL); + + allocated_num = alloc_buffers_from_pool(priv, priv->out.pool, + &priv->out.buffers); + if (allocated_num == 0) { + gst_caps_unref(caps); + ret = -1; + goto unlock; + } + + for (i = 0; i < allocated_num; i++) { + /* Set identifiers for associating a GstBuffer with + a V4L2 buffer in the V4L2 caller side. */ + priv->mmap_offset = + set_mem_offset(&priv->out.buffers[i], + priv->out.pool, + priv->mmap_offset); + + priv->out.buffers[i].planes[0].length = + gst_buffer_get_size(priv->out.buffers[i].gstbuf); + } + + req->count = priv->out.buffers_num = allocated_num; + + GST_DEBUG("buffers count=%d", req->count); + + priv->out.returned_cnt = 0; + + ret = 0; + + unlock: + g_mutex_unlock(&priv->dev_lock); + + return ret; +} + + +static GstBuffer * +peek_first_cap_buffer(struct v4l_gst *priv) +{ + GstBuffer *gstbuf = NULL; + gboolean timed_out = FALSE; + gint64 end_time; + + g_mutex_lock(&priv->queue_mutex); + gstbuf = g_queue_peek_head(priv->out.gstbufs_queue); + end_time = g_get_monotonic_time() + INITIAL_BUFFER_WAIT_TIMEOUT; + while (!gstbuf && priv->is_pipeline_started) { + if (!g_cond_wait_until(&priv->queue_cond, + &priv->queue_mutex, + end_time)) { + timed_out = TRUE; + break; + } + gstbuf = g_queue_peek_head(priv->out.gstbufs_queue); + } + g_mutex_unlock(&priv->queue_mutex); + + if (timed_out && !gstbuf) + GST_WARNING("Timed out waiting the first CAPTURE buffer."); + + return gstbuf; +} + + +static gboolean +wait_for_all_bufs_collected(struct v4l_gst *priv, + guint max_buffers) +{ + GQueue *queue = priv->out.gstbufs_queue; + gboolean succeeded; + gboolean timed_out = FALSE; + gint64 end_time; + + g_mutex_lock(&priv->queue_mutex); + end_time = g_get_monotonic_time() + INITIAL_BUFFER_WAIT_TIMEOUT; + while (g_queue_get_length(queue) < max_buffers && + priv->is_pipeline_started) { + if (!g_cond_wait_until(&priv->queue_cond, + &priv->queue_mutex, + end_time)) { + timed_out = TRUE; + break; + } + } + succeeded = g_queue_get_length(queue) >= max_buffers; + g_mutex_unlock(&priv->queue_mutex); + + if (timed_out && !succeeded) + GST_WARNING("Timed out waiting CAPTURE buffers."); + + return succeeded; +} + + +static gboolean +retrieve_cap_frame_info(GstBufferPool *pool, GstBuffer *gstbuf, + struct v4l2_pix_format_mplane *cap_fmt) +{ + GstVideoInfo info; + GstVideoMeta *meta; + gint i; + + if (!v4l_gst_pipeline_get_raw_video_params(pool, gstbuf, &info, &meta)) { + GST_ERROR("Failed to get video meta data"); + return FALSE; + } + + { + guint n_planes = meta ? meta->n_planes + : GST_VIDEO_INFO_N_PLANES(&info); + for (i = 0; i < (gint) n_planes; i++) { + cap_fmt->plane_fmt[i].sizeimage = + calc_plane_size(&info, meta, i); + cap_fmt->plane_fmt[i].bytesperline = + meta ? meta->stride[i] + : GST_VIDEO_INFO_PLANE_STRIDE(&info, i); + } + } + + return TRUE; +} + + +static guint +create_cap_buffers_list(struct v4l_gst *priv) +{ + GstBuffer *first_gstbuf; + guint actual_max_buffers; + gint i, j; + gboolean succeeded; + + if (priv->cap.buffers) + /* Cannot realloc the buffers without stopping the pipeline, + so return the same number of the buffers so far. */ + return priv->cap.buffers_num; + + g_mutex_unlock(&priv->dev_lock); + + first_gstbuf = peek_first_cap_buffer(priv); + + g_mutex_lock(&priv->dev_lock); + + if (!first_gstbuf) { + GST_ERROR("Failed to wait for the first CAPTURE buffer."); + errno = EINVAL; + return 0; + } + + if (priv->cap.kind == V4L_GST_MEDIA_KIND_CODEC) { + /* Encoded stream buffers are allocated dynamically by the + encoder and do not belong to a buffer pool, so use the + number of buffers requested via VIDIOC_REQBUF. The frame + parameters were already fixed by VIDIOC_S_FMT. */ + actual_max_buffers = priv->cap.buffers_num; + } else { + if (!first_gstbuf->pool) { + GST_ERROR("Cannot handle buffers not belonging to " + "a bufferpool"); + errno = EINVAL; + return 0; + } + + if (priv->cap.pool != first_gstbuf->pool) { + GST_DEBUG("The buffer pool we prepared is not used by " + "the pipeline, so replace it with the pool " + "that is actually used"); + gst_object_unref(priv->cap.pool); + priv->cap.pool = gst_object_ref(first_gstbuf->pool); + } + + /* Confirm the number of buffers actually set to the buffer + pool. */ + v4l_gst_pipeline_get_buffer_pool_params(priv->cap.pool, NULL, + NULL, NULL, &actual_max_buffers); + if (actual_max_buffers == 0) { + GST_ERROR("Cannot handle the unlimited amount of " + "buffers"); + errno = EINVAL; + return 0; + } + + /* Raw stream buffers carry a GstVideoMeta, so retrieve the + frame parameters from it. */ + if (!retrieve_cap_frame_info(priv->cap.pool, first_gstbuf, + &priv->cap.fmt)) { + GST_ERROR("Failed to retrieve frame info on CAPTURE"); + errno = EINVAL; + return 0; + } + } + + /* We wait for buffers from appsink to be collected for + the maximum number of the buffer pool. */ + g_mutex_unlock(&priv->dev_lock); + succeeded = wait_for_all_bufs_collected(priv, actual_max_buffers); + g_mutex_lock(&priv->dev_lock); + + if (!succeeded) { + GST_ERROR("Failed to collect buffers for CAPTURE."); + errno = EINVAL; + return 0; + } + + priv->cap.buffers = g_new0(struct v4l_gst_buffer, actual_max_buffers); + + for (i = 0; i < actual_max_buffers; i++) { + priv->cap.buffers[i].gstbuf = + g_queue_peek_nth(priv->out.gstbufs_queue, i); + + /* Set identifiers for associating a GstBuffer with + a V4L2 buffer in the V4L2 caller side. */ + priv->mmap_offset = set_mem_offset(&priv->cap.buffers[i], + priv->cap.pool, + priv->mmap_offset); + + priv->cap.buffers[i].state = V4L_GST_BUFFER_DEQUEUED; + + /* assume that decoded image data has been filled to + the entire plane size, because the GStreamer buffer + information does not provides how much valid data size + a GstBuffer has. */ + for (j = 0; j < priv->cap.fmt.num_planes; j++) { + priv->cap.buffers[i].planes[j].length = + priv->cap.fmt.plane_fmt[j].sizeimage; + } + + GST_DEBUG("cap gst_buffer[%d] : %p", i, + priv->cap.buffers[i].gstbuf); + } + + GST_DEBUG("The number of buffers actually set to the buffer pool is %d", + actual_max_buffers); + + return actual_max_buffers; +} + + +static int +reqbuf_ioctl_cap(struct v4l_gst *priv, + struct v4l2_requestbuffers *req) +{ + int ret = -1; + + if (!is_supported_memory_io(req->memory)) + return ret; + + g_mutex_lock(&priv->dev_lock); + + if (req->count == 0) { + ret = v4l_gst_pipeline_stop(priv); + goto unlock; + } + + if (!priv->is_pipeline_started) { + GST_ERROR("Need to start the pipeline for the buffer request " + "on CAPTURE"); + errno = EINVAL; + goto unlock; + } + + g_mutex_lock(&priv->cap.reqbuf_mutex); + priv->cap.buffers_num = MIN(req->count, VIDEO_MAX_FRAME); + g_cond_signal(&priv->cap.reqbuf_cond); + g_mutex_unlock(&priv->cap.reqbuf_mutex); + + req->count = priv->cap.buffers_num = create_cap_buffers_list(priv); + if (req->count == 0) + goto unlock; + + GST_DEBUG("buffers count=%d", req->count); + + ret = 0; + + unlock: + g_mutex_unlock(&priv->dev_lock); + + return ret; +} + + +int +v4l_gst_ioctl_reqbuf(struct v4l_gst *priv, struct v4l2_requestbuffers *req) +{ + int ret; + + GST_DEBUG("VIDIOC_REQBUF: type: %s (0x%x) count: %d memory: 0x%x", + v4l2_buffer_type_to_string(req->type), req->type, + req->count, req->memory); + + if (req->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + ret = reqbuf_ioctl_out(priv, req); + } else if (req->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + ret = reqbuf_ioctl_cap(priv, req); + } else { + GST_ERROR("Invalid buf type"); + errno = EINVAL; + ret = -1; + } + + return ret; +} + + +static int +streamon_ioctl_out(struct v4l_gst *priv) +{ + GstState state; + + if (priv->is_pipeline_started) { + GST_ERROR("The pipeline is already running"); + errno = EBUSY; + return -1; + } + + GST_OBJECT_LOCK(priv->pipeline); + state = GST_STATE(priv->pipeline); + GST_OBJECT_UNLOCK(priv->pipeline); + + g_mutex_lock(&priv->dev_lock); + + if (state == GST_STATE_NULL) { + if (!priv->out_caps_set && + !v4l_gst_pipeline_set_out_format(priv)) + return -1; + if (!v4l_gst_pipeline_set_cap_format(priv)) + return -1; + } + + if (!gst_buffer_pool_is_active(priv->out.pool)) { + if (!gst_buffer_pool_set_active(priv->out.pool, TRUE)) { + GST_ERROR("Failed to activate buffer pool"); + errno = EINVAL; + return -1; + } + + /* Restore the extra reference counted up in the streamoff */ + gst_buffer_unref(priv->out.buffers[0].gstbuf); + } + + priv->eos_state = EOS_NONE; + v4l_gst_core_reset_cap_timestamp_state(priv); + + v4l_gst_core_set_pipeline_started(priv, TRUE); + + gst_element_set_state(priv->pipeline, GST_STATE_PLAYING); + + g_mutex_unlock(&priv->dev_lock); + + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(priv->pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, + "v4l-gst.streamon.snapshot"); + + return 0; +} + + +int +v4l_gst_ioctl_streamon(struct v4l_gst *priv, enum v4l2_buf_type *type) +{ + int ret; + + GST_DEBUG("VIDIOC_STREAMON: type: %s (0x%x)", + v4l2_buffer_type_to_string(*type), *type); + + if (*type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + ret = streamon_ioctl_out(priv); + } else if (*type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + /* no processing */ + ret = 0; + } else { + GST_ERROR("Invalid buf type"); + errno = EINVAL; + ret = -1; + } + + return ret; +} + + +int +v4l_gst_ioctl_streamoff(struct v4l_gst *priv, enum v4l2_buf_type *type) +{ + int ret; + + GST_DEBUG("VIDIOC_STREAMOFF: type: %s (0x%x)", + v4l2_buffer_type_to_string(*type), *type); + + GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(priv->pipeline), + GST_DEBUG_GRAPH_SHOW_ALL, + "v4l-gst.streamoff.snapshot"); + + if (*type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + g_mutex_lock(&priv->dev_lock); + ret = streamoff_ioctl_out(priv, TRUE); + g_mutex_unlock(&priv->dev_lock); + } else if (*type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + /* no processing */ + ret = 0; + } else { + GST_ERROR("Invalid buf type"); + errno = EINVAL; + ret = -1; + } + + return ret; +} + + +static int +find_out_buffer_by_offset(struct v4l_gst *priv, int64_t offset) +{ + gint index = -1; + gint i; + + for (i = 0; i < priv->out.buffers_num; i++) { + if (priv->out.buffers[i].planes[0].m.mem_offset == offset) { + index = i; + break; + } + } + + return index; +} + + +static void * +map_out_buffer(struct v4l_gst *priv, int index, int prot) +{ + GstMapInfo info; + void *data; + GstMapFlags map_flags; + + map_flags = (prot & PROT_READ) ? GST_MAP_READ : 0; + map_flags |= (prot & PROT_WRITE) ? GST_MAP_WRITE : 0; + + if (!gst_buffer_map(priv->out.buffers[index].gstbuf, &info, + map_flags)) { + GST_ERROR("Failed to map buffer (%p)", + priv->out.buffers[index].gstbuf); + errno = EINVAL; + return MAP_FAILED; + } + + data = info.data; + + gst_buffer_unmap(priv->out.buffers[index].gstbuf, &info); + + priv->out.buffers[index].flags = map_flags; + + return data; +} + + +static int +find_cap_buffer_by_offset(struct v4l_gst *priv, + int64_t offset, int *index, int *plane) +{ + gint i, j; + + for (i = 0; i < priv->cap.buffers_num; i++) { + for (j = 0; j < priv->cap.fmt.num_planes; j++) { + if (priv->cap.buffers[i].planes[j].m.mem_offset == + offset) { + *index = i; + *plane = j; + return 0; + } + } + } + + return -1; +} + + +static void * +map_cap_buffer(struct v4l_gst *priv, int index, int plane, + int prot) +{ + GstVideoMeta *meta; + GstVideoInfo vinfo; + GstMapInfo info; + void *data; + GstMapFlags map_flags; + + map_flags = (prot & PROT_READ) ? GST_MAP_READ : 0; + map_flags |= (prot & PROT_WRITE) ? GST_MAP_WRITE : 0; + + if (!gst_buffer_map(priv->cap.buffers[index].gstbuf, &info, + map_flags)) { + GST_ERROR("Failed to map buffer (%p)", + priv->cap.buffers[index].gstbuf); + errno = EINVAL; + return MAP_FAILED; + } + + if (priv->cap.kind == V4L_GST_MEDIA_KIND_CODEC) { + /* An encoded stream buffer holds the bitstream at the start + of the mapped region, so there is no video meta to consult. */ + data = info.data; + } else { + if (!v4l_gst_pipeline_get_raw_video_params(priv->cap.pool, + priv->cap.buffers[index].gstbuf, + &vinfo, &meta)) { + GST_ERROR("Failed to get video meta data"); + errno = EINVAL; + gst_buffer_unmap(priv->cap.buffers[index].gstbuf, + &info); + return MAP_FAILED; + } + + data = info.data + (meta ? meta->offset[plane] + : GST_VIDEO_INFO_PLANE_OFFSET(&vinfo, plane)); + } + + gst_buffer_unmap(priv->cap.buffers[index].gstbuf, &info); + + priv->cap.buffers[index].flags = map_flags; + + return data; +} + + +void * +v4l_gst_mmap(struct v4l_gst *priv, void *start, size_t length, + int prot, int flags, int fd, int64_t offset) +{ + int index; + int plane; + void *map = MAP_FAILED; + int ret; + + /* unused */ + (void)start; + (void)flags; + (void)fd; + + /* The GStreamer memory mapping internally maps + the whole allocated size of a buffer, so the mapping length + does not need to be specified. */ + (void)length; + + g_mutex_lock(&priv->dev_lock); + + index = find_out_buffer_by_offset(priv, offset); + if (index >= 0) { + map = map_out_buffer(priv, index, prot); + goto unlock; + } + + ret = find_cap_buffer_by_offset(priv, offset, &index, &plane); + if (ret == 0) { + map = map_cap_buffer(priv, index, plane, prot); + goto unlock; + } + + unlock: + g_mutex_unlock(&priv->dev_lock); + + GST_DEBUG("Final map = %p", map); + + return map; +} + + +int +v4l_gst_ioctl_expbuf(struct v4l_gst *priv, struct v4l2_exportbuffer *expbuf) +{ + struct v4l_gst_buffer *buffer; + guint mem_index = 0; + GstMemory *mem = NULL; + + GST_TRACE("VIDIOC_EXPBUF: type: 0x%x index: %d flags: 0x%x", + expbuf->type, expbuf->index, expbuf->flags); + + if (expbuf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + expbuf->type != V4L2_BUF_TYPE_PRIVATE) { + GST_ERROR("Can only export capture buffers as dmabuf"); + errno = EINVAL; + return -1; + } + + if (expbuf->index >= priv->cap.buffers_num) { + GST_ERROR("Buffer index is out of range!: %d/%d", + expbuf->index, priv->cap.buffers_num); + errno = EINVAL; + return -1; + } + + if (expbuf->plane >= priv->cap.fmt.num_planes) { + GST_ERROR("Plane index is out of range!: %d/%d", + expbuf->plane, priv->cap.fmt.num_planes); + errno = EINVAL; + return -1; + } + + buffer = &priv->cap.buffers[expbuf->index]; + + if (expbuf->plane < gst_buffer_n_memory(buffer->gstbuf)) + mem_index = expbuf->plane; + + mem = gst_buffer_peek_memory(buffer->gstbuf, mem_index); + if (!mem || !gst_is_dmabuf_memory(mem)) { + GST_ERROR("Failed to get dmabuf memory."); + errno = EINVAL; + return -1; + } + + switch(expbuf->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + expbuf->fd = dup(gst_dmabuf_memory_get_fd(mem)); + if (expbuf->plane == 0) { + /* See reindex_buffers() */ + buffer->plane0_fd = gst_dmabuf_memory_get_fd(mem); + } + break; + case V4L2_BUF_TYPE_PRIVATE: + /* For backward compatibility, will be removed. + See also set_v4l2_buffer_plane_params(). */ + expbuf->reserved[0] = 0; + if (gst_buffer_n_memory(buffer->gstbuf) == 1) { + guint i; + for (i = 0; i < expbuf->plane; i++) + expbuf->reserved[0] += buffer->planes[i].length; + } + break; + default: + GST_ERROR("Can only export capture buffers as dmabuf"); + errno = EINVAL; + return -1; + } + + return 0; +} diff --git a/lib/libv4l-gst/v4l-gst-core.c b/lib/libv4l-gst/v4l-gst-core.c new file mode 100644 index 0000000..c284adf --- /dev/null +++ b/lib/libv4l-gst/v4l-gst-core.c @@ -0,0 +1,1163 @@ +/* + * Copyright (C) 2015 Renesas Electronics Corporation + * Copyright (C) 2024-2026 ClearCode Inc. + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "libv4l-gst-bufferpool.h" + +#include "v4l-gst.h" +#include "evfd-ctrl.h" +#include "debug.h" +#include "utils.h" +#include "v4l-gst-internal.h" + +GstDebugCategory *v4l_gst_debug_category; +GstDebugCategory *v4l_gst_ioctl_debug_category; +GstDebugCategory *v4l_gst_buffer_debug_category; + +G_DEFINE_QUARK(v4l_gst_cap_buf_crc, v4l_gst_cap_buf_crc) + +void +v4l_gst_core_reset_cap_timestamp_state(struct v4l_gst *priv) +{ + priv->last_cap_pts = GST_CLOCK_TIME_NONE; + priv->estimated_cap_duration = GST_CLOCK_TIME_NONE; +} + + +static gboolean +parse_config_file(struct v4l_gst *priv) +{ + const gchar *const *sys_conf_dirs; + GKeyFile *conf_key; + const gchar *conf_name = "libv4l-gst.conf"; + const gchar *libv4l_gst_group = "libv4l-gst"; + GError *err = NULL; + gchar **groups; + gsize n_groups; + gint i; + guint n_pipelines = 0; + guint n_encode_pipelines = 0; + + sys_conf_dirs = g_get_system_config_dirs(); + + conf_key = g_key_file_new(); + if (!g_key_file_load_from_dirs(conf_key, conf_name, + (const gchar **) sys_conf_dirs, NULL, + G_KEY_FILE_NONE, &err)) { + GST_ERROR("Failed to load %s " + "from the xdg system config directory retrieved from " + "XDG_CONFIG_DIRS (%s)", conf_name, err->message); + g_error_free(err); + goto free_key_file; + } + + GST_DEBUG("libv4l-gst configuration file is found"); + + /* [libv4l-gst] */ + if (g_key_file_has_group(conf_key, libv4l_gst_group)) { + gchar *preferred_format, *fixed_pipeline; + guint preferred_format_len = 0; + const gchar *frame_check; + + /* No need to check if the external bufferpool library is set, + because it is not mandatory for this plugin. */ + priv->config.pool_lib_path + = g_key_file_get_string(conf_key, libv4l_gst_group, + "bufferpool-library", + NULL); + GST_DEBUG("external buffer pool library : %s", + priv->config.pool_lib_path ? priv->config.pool_lib_path : "none"); + + priv->config.cap_min_buffers + = g_key_file_get_integer(conf_key, libv4l_gst_group, + "min-buffers", NULL); + if (priv->config.cap_min_buffers == 0) + priv->config.cap_min_buffers = DEF_CAP_MIN_BUFFERS; + + GST_DEBUG("minimum number of buffers on CAPTURE " + "for the GStreamer pipeline to work : %d", + priv->config.cap_min_buffers); + + priv->config.max_width + = g_key_file_get_integer(conf_key, libv4l_gst_group, + "max-width", NULL); + priv->config.max_height + = g_key_file_get_integer(conf_key, libv4l_gst_group, + "max-height", NULL); + + preferred_format + = g_key_file_get_string(conf_key, libv4l_gst_group, + "preferred-format", + NULL); + if (preferred_format && *preferred_format) + preferred_format_len = strlen(preferred_format); + if (preferred_format_len == 4) { + priv->config.preferred_format + = fourcc_from_string(preferred_format); + } else if (preferred_format_len > 0) { + GST_WARNING("Invalid FourCC for preferred-format: %s", + preferred_format); + } + g_free(preferred_format); + + fixed_pipeline + = g_key_file_get_string(conf_key, libv4l_gst_group, + "fixed-pipeline", + NULL); + if (fixed_pipeline) { + guint32 fourcc = fourcc_from_string(fixed_pipeline); + if (fourcc == V4L2_PIX_FMT_H264 || + fourcc == V4L2_PIX_FMT_HEVC) { + priv->config.fixed_pipeline = fourcc; + } else { + GST_WARNING("Unknown fixed-pipeline: %s", + fixed_pipeline); + } + } + g_free(fixed_pipeline); + + frame_check = getenv("V4L_GST_FRAME_CHECK"); + if (frame_check) { + if (!strcmp("1", frame_check) || + !strcasecmp("light", frame_check) || + !strcasecmp("true", frame_check) || + !strcasecmp("enable", frame_check)) + priv->config.frame_check = FRAME_CHECK_LIGHT; + else if (!strcmp("2", frame_check) || + !strcasecmp("full", frame_check)) + priv->config.frame_check = FRAME_CHECK_FULL; + } + } + + /* [H264], [HEVC], etc... */ + priv->config.pipelines = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + priv->config.encode_pipelines = g_hash_table_new_full(g_str_hash, + g_str_equal, + g_free, g_free); + groups = g_key_file_get_groups(conf_key, &n_groups); + GST_DEBUG("found %zu section in %s", n_groups, conf_name); + for (i = 0; i < n_groups; i++) { + gchar *pipeline_str, *encode_pipeline_str; + + if (!g_strcmp0(groups[i], libv4l_gst_group)) + continue; + + /* treat only fourcc */ + if (strlen(groups[i]) != 4) + continue; + + GST_DEBUG("Parse section: [%s]", groups[i]); + err = NULL; + pipeline_str = g_key_file_get_string(conf_key, groups[i], + "pipeline", &err); + if (err) { + g_error_free(err); + err = NULL; + } + encode_pipeline_str = g_key_file_get_string(conf_key, groups[i], + "encode-pipeline", + &err); + if (err) { + g_error_free(err); + err = NULL; + } + + if (!pipeline_str && !encode_pipeline_str) { + GST_ERROR("Neither pipeline nor encode-pipeline " + "is specified in section [%s]", groups[i]); + continue; + } + + if (pipeline_str) { + g_hash_table_insert(priv->config.pipelines, + g_strdup(groups[i]), + pipeline_str); + GST_DEBUG("enabled %s decode pipeline: %s", + groups[i], pipeline_str); + n_pipelines++; + } + if (encode_pipeline_str) { + g_hash_table_insert(priv->config.encode_pipelines, + g_strdup(groups[i]), + encode_pipeline_str); + GST_DEBUG("enabled %s encode pipeline: %s", + groups[i], encode_pipeline_str); + n_encode_pipelines++; + } + } + + g_strfreev(groups); + free_key_file: + g_key_file_free(conf_key); + + if (n_pipelines == 0 && n_encode_pipelines == 0) + GST_ERROR("no pipeline!"); + + return (n_pipelines + n_encode_pipelines) > 0; +} + + +GstElement * +v4l_gst_core_create_pipeline(const gchar *pipeline_str) +{ + const gchar *format_str; + gchar *launch_str; + GstElement *pipeline; + GError *err = NULL; + gboolean has_appsrc = !!strstr(pipeline_str, "appsrc"); + gboolean has_appsink = !!strstr(pipeline_str, "appsink"); + + if (has_appsrc && has_appsink) + format_str = "%s"; + else if (has_appsrc) + format_str = "%s ! appsink"; + else if (has_appsink) + format_str = "appsrc ! %s"; + else + format_str = "appsrc ! %s ! appsink"; + + launch_str = g_strdup_printf(format_str, pipeline_str); + + GST_DEBUG("gst_parse_launch: %s", launch_str); + + pipeline = gst_parse_launch(launch_str, &err); + g_free(launch_str); + + if (err) { + GST_ERROR("Couldn't construct pipeline: %s", + err->message); + g_error_free(err); + return NULL; + } + + return pipeline; +} + + +static gboolean +get_gst_elements(struct v4l_gst *priv) +{ + GstIterator *it; + gboolean done = FALSE; + GValue data = { 0, }; + GstElement *elem; + GstElementFactory *factory; + const gchar *elem_name; + const gchar *klass; + const gchar *decoder_klass = "Codec/Decoder/Video"; + + priv->appsrc = priv->appsink = priv->decoder = NULL; + + it = gst_bin_iterate_elements(GST_BIN(priv->pipeline)); + while (!done) { + switch (gst_iterator_next(it, &data)) { + case GST_ITERATOR_OK: + elem = g_value_get_object(&data); + + factory = gst_element_get_factory(elem); + elem_name = gst_element_factory_get_metadata + (factory, GST_ELEMENT_METADATA_LONGNAME); + klass = gst_element_factory_get_metadata + (factory, GST_ELEMENT_METADATA_KLASS); + if (!g_strcmp0(elem_name, "AppSrc")) + priv->appsrc = elem; + else if (!g_strcmp0(elem_name, "AppSink")) + priv->appsink = elem; + else if (!strncmp(klass, decoder_klass, + strlen(decoder_klass))) + priv->decoder = elem; + + g_value_reset(&data); + break; + case GST_ITERATOR_DONE: + default: + done = TRUE; + break; + } + } + + g_value_unset(&data); + gst_iterator_free(it); + + if (!priv->appsrc || !priv->appsink) { + GST_ERROR("Failed to get app elements from the pipeline"); + return FALSE; + } + + GST_DEBUG("appsrc and appsink elements are found in the pipeline"); + + return TRUE; +} + + +static void +get_buffer_pool_ops(gchar *pool_lib_path, void **pool_lib_handle, + struct libv4l_gst_buffer_pool_ops **pool_ops) +{ + void *handle; + gchar *err; + struct libv4l_gst_buffer_pool_ops *ops; + + /* This dynamic linking will keep loaded even after the plugin has been + closed in order to prevent from the duplicate class registration of + the buffer pool due to the static variable that indicates if + the class has already been registered being deleted when the dynamic + library is unloaded. */ + handle = dlopen(pool_lib_path, RTLD_LAZY); + if (!handle) { + GST_ERROR("dlopen failed (%s)", dlerror()); + return; + } + + dlerror(); /* Clear any existing error */ + + ops = dlsym(handle, "libv4l_gst_bufferpool"); + err = dlerror(); + if (err) { + GST_ERROR("dlsym failed (%s)", err); + dlclose(handle); + return; + } + + *pool_lib_handle = handle; + *pool_ops = ops; + + GST_DEBUG("buffer pool ops is set"); +} + + +static GstPad * +get_peer_pad(GstElement *elem, const gchar *pad_name) +{ + GstPad *pad, *peer_pad = NULL; + + pad = gst_element_get_static_pad(elem, pad_name); + if (!pad) + return NULL; + + peer_pad = gst_pad_get_peer(pad); + gst_object_unref(pad); + + return peer_pad; +} + + +GstElement * +v4l_gst_core_get_peer_element(GstElement *elem, const gchar *pad_name) +{ + GstPad *peer_pad; + GstElement *peer_elem; + + peer_pad = get_peer_pad(elem, pad_name); + if (!peer_pad) + return NULL; + + peer_elem = gst_pad_get_parent_element(peer_pad); + gst_object_unref(peer_pad); + + return peer_elem; +} + + +static GstCaps * +get_peer_pad_template_caps(GstElement *elem, const gchar *pad_name, GstPad **peer_pad) +{ + GstPad *pad, *first_peer_pad = NULL; + GstCaps *caps = NULL, *first_caps = NULL;; + + gst_object_ref(elem); + + do { + pad = get_peer_pad(elem, pad_name); + gst_object_unref(elem); + if (!pad) + break; + + caps = GST_PAD_TEMPLATE_CAPS(GST_PAD_PAD_TEMPLATE(pad)); + if (!first_caps) { + first_caps = caps; + gst_caps_ref(first_caps); + first_peer_pad = pad; + gst_object_ref(first_peer_pad); + } + + /* Skip meaningless elements such as queue or tee */ + if (gst_caps_is_any(caps)) { + caps = NULL; + elem = gst_pad_get_parent_element(pad); + gst_object_unref(pad); + } else { + gst_caps_ref(caps); + } + } while (!caps && elem && pad); + + if (first_caps) { + /* fallback to first peer if no meaningful caps found */ + if (!caps) { + caps = first_caps; + pad = first_peer_pad; + } else { + gst_caps_unref(first_caps); + gst_object_unref(first_peer_pad); + } + } + + if (peer_pad) + *peer_pad = pad; + else if (pad) + gst_object_unref(pad); + + return caps; +} + + +static void +fill_out_fmts_func(gpointer key, gpointer value, gpointer user_data) +{ + GArray *fmts = user_data; + struct fmt fmt; + + fmt.fourcc = fourcc_from_string(key); + g_strlcpy(fmt.desc, key, FMTDESC_NAME_LENGTH); + g_array_append_vals(fmts, &fmt, 1); +} + + +static void +fill_codec_fmts(GHashTable *pipelines, GArray *fmts) +{ + g_hash_table_foreach(pipelines, + fill_out_fmts_func, + fmts); +} + + +static void +fill_raw_fmts(struct v4l_gst *priv, GArray *fmts) +{ + struct fmt color_fmt; + + if (priv->config.preferred_format) { + color_fmt.fourcc = priv->config.preferred_format; + fourcc_to_string(priv->config.preferred_format, color_fmt.desc); + } else { + color_fmt.fourcc = fourcc_from_string("NV12"); + g_strlcpy(color_fmt.desc, "NV12", FMTDESC_NAME_LENGTH); + } + g_array_append_vals(fmts, &color_fmt, 1); +} + + +static void +debug_supported_fmts(const GArray *fmts) +{ + gint i; + gchar list[256] = {0}; + const struct fmt *data = (const struct fmt*)fmts->data; + + for (i = 0; i < fmts->len; i++) { + g_strlcat(list, data[i].desc, sizeof(list)); + g_strlcat(list, " ", sizeof(list)); + } + GST_DEBUG("supported formats: %s", list); +} + + +gboolean +v4l_gst_core_setup_role(struct v4l_gst *priv) +{ + gboolean has_decode = g_hash_table_size(priv->config.pipelines) > 0; + gboolean has_encode = + g_hash_table_size(priv->config.encode_pipelines) > 0; + + if (has_decode && !has_encode) + priv->role = V4L_GST_ROLE_DECODER; + else if (has_encode && !has_decode) + priv->role = V4L_GST_ROLE_ENCODER; + else + /* fixed by the first OUTPUT format (v4l-gst-fmt.c) */ + priv->role = V4L_GST_ROLE_NONE; + + g_array_set_size(priv->out.supported_fmts, 0); + g_array_set_size(priv->cap.supported_fmts, 0); + + switch (priv->role) { + case V4L_GST_ROLE_DECODER: + priv->out.kind = V4L_GST_MEDIA_KIND_CODEC; + priv->cap.kind = V4L_GST_MEDIA_KIND_RAW; + fill_codec_fmts(priv->config.pipelines, + priv->out.supported_fmts); + fill_raw_fmts(priv, priv->cap.supported_fmts); + break; + case V4L_GST_ROLE_ENCODER: + priv->out.kind = V4L_GST_MEDIA_KIND_RAW; + priv->cap.kind = V4L_GST_MEDIA_KIND_CODEC; + fill_raw_fmts(priv, priv->out.supported_fmts); + fill_codec_fmts(priv->config.encode_pipelines, + priv->cap.supported_fmts); + break; + case V4L_GST_ROLE_NONE: + /* kind is set once the role is fixed */ + fill_codec_fmts(priv->config.pipelines, + priv->out.supported_fmts); + fill_raw_fmts(priv, priv->out.supported_fmts); + fill_raw_fmts(priv, priv->cap.supported_fmts); + fill_codec_fmts(priv->config.encode_pipelines, + priv->cap.supported_fmts); + break; + } + + debug_supported_fmts(priv->out.supported_fmts); + debug_supported_fmts(priv->cap.supported_fmts); + + return priv->out.supported_fmts->len > 0 && + priv->cap.supported_fmts->len > 0; +} + + +static gboolean +get_supported_video_format_out(struct v4l_gst *priv) +{ + GstCaps *caps; + GstStructure *structure; + const gchar *mime; + guint fourcc; + struct fmt *fmt; + + caps = get_peer_pad_template_caps(priv->appsrc, "src", NULL); + if (!caps) { + GST_ERROR("Failed to get video format for OUTPUT"); + return FALSE; + } + + if (priv->out.kind == V4L_GST_MEDIA_KIND_CODEC) { + /* Decoder: OUTPUT carries a codec. Only a single codec is + negotiated by the pipeline. */ + structure = gst_caps_get_structure(caps, 0); + mime = gst_structure_get_name(structure); + + if (g_strcmp0(mime, GST_VIDEO_CODEC_MIME_H264) == 0) { + fourcc = V4L2_PIX_FMT_H264; + } else if (g_strcmp0(mime, GST_VIDEO_CODEC_MIME_HEVC) == 0) { + fourcc = V4L2_PIX_FMT_HEVC; + } else { + GST_ERROR("Unsupported codec : %s", mime); + gst_caps_unref(caps); + g_array_set_size(priv->out.supported_fmts, 0); + return FALSE; + } + GST_DEBUG("out supported codec : %s", mime); + + g_array_set_size(priv->out.supported_fmts, 1); + fmt = (struct fmt*)priv->out.supported_fmts->data; + + fmt->fourcc = fourcc; + if(fourcc == V4L2_PIX_FMT_H264) + g_strlcpy(fmt->desc, "V4L2_PIX_FMT_H264", FMTDESC_NAME_LENGTH); + else if (fourcc == V4L2_PIX_FMT_HEVC) + g_strlcpy(fmt->desc, "V4L2_PIX_FMT_HEVC", FMTDESC_NAME_LENGTH); + gst_caps_unref(caps); + + return TRUE; + } + + /* Encoder: OUTPUT carries raw video. Parse the raw format list from + the caps and map each to a V4L2 fourcc. */ + { + guint structs; + guint i, j; + + structs = gst_caps_get_size(caps); + + for (j = 0; j < structs; j++) { + gint num_out_formats; + const GValue *val, *list_val; + const gchar *fmt_str; + GstVideoFormat gst_fmt; + struct fmt color_fmt; + + structure = gst_caps_get_structure(caps, j); + val = gst_structure_get_value(structure, "format"); + if (!val) + continue; + + num_out_formats = GST_VALUE_HOLDS_LIST(val) ? + gst_value_list_get_size(val) : 1; + + for (i = 0; i < num_out_formats; i++) { + list_val = GST_VALUE_HOLDS_LIST(val) ? + gst_value_list_get_value(val, i) : val; + fmt_str = g_value_get_string(list_val); + + gst_fmt = gst_video_format_from_string(fmt_str); + if (gst_fmt == GST_VIDEO_FORMAT_UNKNOWN) { + GST_ERROR("Unknown video format : %s", + fmt_str); + continue; + } + + color_fmt.fourcc = + fourcc_from_gst_video_format(gst_fmt); + if (color_fmt.fourcc == 0) { + GST_DEBUG("Failed to convert video " + "format from gst to v4l2 : %s", + fmt_str); + continue; + } + + GST_DEBUG("out supported video format : %s", + fmt_str); + g_strlcpy(color_fmt.desc, fmt_str, + FMTDESC_NAME_LENGTH); + g_array_append_vals(priv->out.supported_fmts, + &color_fmt, 1); + } + } + + gst_caps_unref(caps); + + if (priv->out.supported_fmts->len == 0) { + GST_ERROR("Failed to get video formats from caps"); + return FALSE; + } + + GST_DEBUG("The total number of out supported video format : %d", + priv->out.supported_fmts->len); + + return TRUE; + } +} + + +static gboolean +get_supported_video_format_cap(struct v4l_gst *priv) +{ + GstCaps *caps; + GstStructure *structure; + guint structs; + const GValue *val, *list_val; + const gchar *fmt_str; + GstVideoFormat fmt; + guint32 preferred = priv->config.preferred_format; + gboolean preferred_found = FALSE; + guint i, j; + struct fmt color_fmt; + gchar fourcc_str[5]; + + g_array_set_size(priv->cap.supported_fmts, 0); + + caps = get_peer_pad_template_caps(priv->appsink, "sink", + &priv->video_sink_pad); + if (!caps) { + GST_ERROR("Failed to get video format for CAPTURE"); + return FALSE; + } + + if (priv->cap.kind == V4L_GST_MEDIA_KIND_CODEC) { + /* Encoder: CAPTURE carries a codec. A single codec is + negotiated by the pipeline, so map its mime to a fourcc. */ + GstStructure *codec_structure; + const gchar *codec_mime; + guint codec_fourcc; + struct fmt *codec_fmt; + + codec_structure = gst_caps_get_structure(caps, 0); + codec_mime = gst_structure_get_name(codec_structure); + + if (g_strcmp0(codec_mime, GST_VIDEO_CODEC_MIME_H264) == 0) { + codec_fourcc = V4L2_PIX_FMT_H264; + } else if (g_strcmp0(codec_mime, GST_VIDEO_CODEC_MIME_HEVC) == + 0) { + codec_fourcc = V4L2_PIX_FMT_HEVC; + } else { + GST_ERROR("Unsupported codec : %s", codec_mime); + gst_caps_unref(caps); + return FALSE; + } + GST_DEBUG("cap supported codec : %s", codec_mime); + + g_array_set_size(priv->cap.supported_fmts, 1); + codec_fmt = (struct fmt*)priv->cap.supported_fmts->data; + codec_fmt->fourcc = codec_fourcc; + if (codec_fourcc == V4L2_PIX_FMT_H264) + g_strlcpy(codec_fmt->desc, "V4L2_PIX_FMT_H264", + FMTDESC_NAME_LENGTH); + else + g_strlcpy(codec_fmt->desc, "V4L2_PIX_FMT_HEVC", + FMTDESC_NAME_LENGTH); + gst_caps_unref(caps); + + return TRUE; + } + + /* We treat GST_CAPS_ANY as all video formats support. */ + if (gst_caps_is_any(caps)) { + GST_DEBUG("Use GST_VIDEO_FORMATS_ALL"); + gst_caps_unref(caps); + caps = gst_caps_from_string + ("video/x-raw, format=" GST_VIDEO_FORMATS_ALL); + } + + GST_DEBUG("caps: %" GST_PTR_FORMAT, caps); + + structs = gst_caps_get_size(caps); + + for (j = 0; j < structs; j++) { + gint num_cap_formats; + + structure = gst_caps_get_structure(caps, j); + val = gst_structure_get_value(structure, "format"); + if (!val) + continue; + + num_cap_formats = GST_VALUE_HOLDS_LIST(val) ? + gst_value_list_get_size(val) : 1; + + for (i = 0; i < num_cap_formats; i++) { + list_val = GST_VALUE_HOLDS_LIST(val) ? + gst_value_list_get_value(val, i) : val; + fmt_str = g_value_get_string(list_val); + + fmt = gst_video_format_from_string(fmt_str); + if (fmt == GST_VIDEO_FORMAT_UNKNOWN) { + GST_ERROR("Unknown video format : %s", fmt_str); + continue; + } + + color_fmt.fourcc = fourcc_from_gst_video_format(fmt); + if (color_fmt.fourcc == 0) { + GST_DEBUG("Failed to convert video format " + "from gst to v4l2 : %s", fmt_str); + continue; + } + + GST_DEBUG("cap supported video format : %s", fmt_str); + + g_strlcpy(color_fmt.desc, fmt_str, FMTDESC_NAME_LENGTH); + + if (preferred && color_fmt.fourcc == preferred) { + g_array_prepend_vals(priv->cap.supported_fmts, + &color_fmt, 1); + + fourcc_to_string(preferred, fourcc_str); + GST_DEBUG("Preferred format: %s (0x%x)", + fourcc_str, preferred); + preferred_found = TRUE; + } else { + g_array_append_vals(priv->cap.supported_fmts, + &color_fmt, 1); + } + } + } + + if (preferred) { + if (preferred_found) { + /* TODO: Add a new option to force use this? */ + g_array_set_size(priv->cap.supported_fmts, 1); + } else { + fourcc_to_string(preferred, fourcc_str); + GST_INFO("Preferred format %s (0x%x) isn't supported", + fourcc_str, preferred); + } + } + + gst_caps_unref(caps); + + if (priv->cap.supported_fmts->len == 0) { + GST_ERROR("Failed to get video formats from caps"); + return FALSE; + } + + GST_DEBUG("The total number of cap supported video format : %d", + priv->cap.supported_fmts->len); + + + return TRUE; +} + + +static void +create_buffer_pool(struct libv4l_gst_buffer_pool_ops *pool_ops, + GstBufferPool **src_pool, GstBufferPool **sink_pool) +{ + if (pool_ops) { + if (pool_ops->add_external_src_buffer_pool) + *src_pool = pool_ops->add_external_src_buffer_pool(); + + if (pool_ops->add_external_sink_buffer_pool) + *sink_pool = pool_ops->add_external_sink_buffer_pool(); + } + + /* fallback to the default buffer pool */ + if (!*src_pool) + *src_pool = gst_buffer_pool_new(); + if (!*sink_pool) + *sink_pool = gst_buffer_pool_new(); +} + + +void +v4l_gst_core_push_source_change_event(struct v4l_gst *priv) +{ + struct v4l2_event *event = g_new0(struct v4l2_event, 1); + + event->type = V4L2_EVENT_SOURCE_CHANGE; + event->u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION; + event->pending = 0; + event->sequence = ++priv->v4l2events.sequence; + event->id = 0; + clock_gettime(CLOCK_REALTIME, &event->timestamp); + + g_mutex_lock(&priv->v4l2events.mutex); + g_queue_push_tail(priv->v4l2events.queue, event); + g_mutex_unlock(&priv->v4l2events.mutex); +} + + +void +v4l_gst_core_set_pipeline_started(struct v4l_gst *priv, gboolean started) +{ + g_mutex_lock(&priv->cap.reqbuf_mutex); + priv->cap.cancel_reqbuf_wait = !started; + if (!started) + g_cond_broadcast(&priv->cap.reqbuf_cond); + g_mutex_unlock(&priv->cap.reqbuf_mutex); + + g_mutex_lock(&priv->queue_mutex); + priv->is_pipeline_started = started; + if (!started) + g_cond_broadcast(&priv->queue_cond); + g_mutex_unlock(&priv->queue_mutex); +} + + +static gboolean +init_app_elements(struct v4l_gst *priv) +{ + /* Get appsrc and appsink elements respectively from the pipeline */ + if (!get_gst_elements(priv)) + return FALSE; + + if (!get_supported_video_format_out(priv)) + return FALSE; + + if (!get_supported_video_format_cap(priv)) + return FALSE; + + /* For queuing buffers received from appsink */ + priv->cap.gstbufs_queue = g_queue_new(); + priv->out.gstbufs_queue = g_queue_new(); + + if (!v4l_gst_pipeline_setup_app_elements(priv)) + return FALSE; + + return TRUE; +} + + +static gboolean +init_buffer_pool(struct v4l_gst *priv) +{ + /* Get the external buffer pool when it is specified in + the configuration file */ + if (priv->config.pool_lib_path) { + get_buffer_pool_ops(priv->config.pool_lib_path, + &priv->pool_lib_handle, &priv->pool_ops); + } + + create_buffer_pool(priv->pool_ops, &priv->out.pool, &priv->cap.pool); + + /* To hook allocation queries */ + priv->probe_id = v4l_gst_pipeline_setup_query_pad_probe(priv); + if (priv->probe_id == 0) { + GST_ERROR("Failed to setup query pad probe"); + goto free_pool; + } + + return TRUE; + + /* error cases */ + free_pool: + if (priv->out.pool) + gst_object_unref(priv->out.pool); + if (priv->cap.pool) + gst_object_unref(priv->cap.pool); + + return FALSE; +} + + +gboolean +v4l_gst_core_init_pipeline(struct v4l_gst *priv, guint32 fourcc) +{ + gchar fourcc_str[5]; + GHashTable *pipelines; + const gchar *pipeline; + + fourcc_to_string(fourcc, fourcc_str); + + if (priv->role == V4L_GST_ROLE_ENCODER) + pipelines = priv->config.encode_pipelines; + else + pipelines = priv->config.pipelines; + + pipeline = g_hash_table_lookup(pipelines, fourcc_str); + + if (pipeline) { + GST_DEBUG("create %s pipeline: %s", fourcc_str, pipeline); + priv->pipeline = v4l_gst_core_create_pipeline(pipeline); + } + + if (!priv->pipeline) { + GST_ERROR("Failed to create pipeline for %s", fourcc_str); + goto error; + } + + if (!init_app_elements(priv)) + goto error; + + if (!init_buffer_pool(priv)) + goto error; + + if (priv->role == V4L_GST_ROLE_ENCODER) + priv->cap.fmt.pixelformat = fourcc; + else + priv->out.fmt.pixelformat = fourcc; + + return TRUE; + + error: + if (priv->cap.gstbufs_queue) { + g_queue_free(priv->cap.gstbufs_queue); + priv->cap.gstbufs_queue = NULL; + } + if (priv->out.gstbufs_queue) { + g_queue_free(priv->out.gstbufs_queue); + priv->out.gstbufs_queue = NULL; + } + if (priv->pipeline) { + gst_object_unref(priv->pipeline); + priv->pipeline = NULL; + } + errno = EINVAL; + return FALSE; +} + + +struct v4l_gst* +v4l_gst_init(int fd) +{ + static gboolean gstreamer_initialized = FALSE; + struct v4l_gst *priv; + struct stat buf; + int flags; + + if (!gstreamer_initialized) { + gst_init(NULL, NULL); + GST_DEBUG_CATEGORY_INIT(v4l_gst_debug_category, + "v4l-gst", 0, + "debug category for v4l-gst application"); + GST_DEBUG_CATEGORY_INIT(v4l_gst_ioctl_debug_category, + "v4l-gst-ioctl", 0, + "debug category for v4l-gst IOCTL operation"); + GST_DEBUG_CATEGORY_INIT(v4l_gst_buffer_debug_category, + "v4l-gst-buffer", 0, + "debug buffers of v4l-gst"); + gstreamer_initialized = TRUE; + } + + priv = g_new0(struct v4l_gst, 1); + if (!priv) { + GST_ERROR("Couldn't allocate memory for gst-backend"); + return NULL; + } + v4l_gst_core_reset_cap_timestamp_state(priv); + + /* Reject character device */ + fstat(fd, &buf); + if (S_ISCHR(buf.st_mode)) + return NULL; + + flags = fcntl(fd, F_GETFL); + priv->is_non_blocking = (flags & O_NONBLOCK) ? TRUE : FALSE; + GST_DEBUG("non-blocking : %s", (priv->is_non_blocking) ? "on" : "off"); + + /*For handling event state */ + priv->event_state = new_event_state(); + if (!priv->event_state) + goto error; + + if (dup2(event_state_fd(priv->event_state), fd) < 0) { + GST_ERROR("dup2 failed"); + goto error; + } + + priv->plugin_fd = fd; + + if (!parse_config_file(priv)) { + GST_ERROR("pipeline configuration is not found at all"); + goto error; + } + + priv->out.buf_type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + priv->cap.buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + + priv->out.supported_fmts = g_array_new(FALSE, TRUE, sizeof(struct fmt)); + priv->cap.supported_fmts = g_array_new(FALSE, TRUE, sizeof(struct fmt)); + + if (!v4l_gst_core_setup_role(priv)) { + GST_ERROR("Failed to set up the M2M role"); + goto error; + } + + g_mutex_init(&priv->v4l2events.mutex); + priv->v4l2events.subscribed = 0; + priv->v4l2events.sequence = 0; + priv->v4l2events.queue = g_queue_new(); + + g_mutex_init(&priv->queue_mutex); + g_cond_init(&priv->queue_cond); + g_mutex_init(&priv->cap.reqbuf_mutex); + g_cond_init(&priv->cap.reqbuf_cond); + + g_mutex_init(&priv->dev_lock); + + if (priv->config.fixed_pipeline) { + if (priv->role == V4L_GST_ROLE_NONE) { + GST_WARNING("fixed-pipeline is ignored when both " + "pipeline and encode-pipeline are " + "configured"); + } else if (!v4l_gst_core_init_pipeline(priv, + priv->config.fixed_pipeline)) { + goto error; + } + } + + GST_DEBUG("Initialized gst backend"); + return priv; + + error: + if (priv->out.supported_fmts) + g_array_free(priv->out.supported_fmts, TRUE); + if (priv->cap.supported_fmts) + g_array_free(priv->cap.supported_fmts, TRUE); + if (priv->config.pipelines) + g_hash_table_destroy(priv->config.pipelines); + if (priv->config.encode_pipelines) + g_hash_table_destroy(priv->config.encode_pipelines); + g_free(priv->config.pool_lib_path); + if (priv->event_state) + delete_event_state(priv->event_state); + g_free(priv); + + return NULL; +} + + +void +v4l_gst_deinit(struct v4l_gst *priv) +{ + GST_DEBUG("v4l_gst_deinit start"); + + v4l_gst_core_set_pipeline_started(priv, FALSE); + + g_mutex_clear(&priv->dev_lock); + + if (priv->v4l2events.queue) { + g_queue_free_full(priv->v4l2events.queue, + (GDestroyNotify)g_free); + } + priv->v4l2events.subscribed = 0; + g_mutex_clear(&priv->v4l2events.mutex); + + if (priv->decoder_probe_id) { + GstPad *pad = gst_element_get_static_pad(priv->decoder, "sink"); + gst_pad_remove_probe(pad, priv->decoder_probe_id); + gst_object_unref(pad); + } + + if (priv->video_sink_pad) { + if (priv->probe_id) { + gst_pad_remove_probe(priv->video_sink_pad, + priv->probe_id); + } + gst_object_unref(priv->video_sink_pad); + } + + if (priv->out.buffers) + g_free(priv->out.buffers); + + if (priv->cap.buffers) + g_free(priv->cap.buffers); + + if (priv->out.pool) + gst_object_unref(priv->out.pool); + if (priv->cap.pool) + gst_object_unref(priv->cap.pool); + + if (priv->out.supported_fmts) + g_array_free(priv->out.supported_fmts, TRUE); + if (priv->cap.supported_fmts) + g_array_free(priv->cap.supported_fmts, TRUE); + + if (priv->cap.gstbufs_queue) + g_queue_free(priv->cap.gstbufs_queue); + if (priv->out.gstbufs_queue) + g_queue_free(priv->out.gstbufs_queue); + g_mutex_clear(&priv->queue_mutex); + g_cond_clear(&priv->queue_cond); + + g_mutex_clear(&priv->cap.reqbuf_mutex); + g_cond_clear(&priv->cap.reqbuf_cond); + + if (priv->pipeline) + gst_object_unref(priv->pipeline); + + if (priv->config.pipelines) + g_hash_table_destroy(priv->config.pipelines); + if (priv->config.encode_pipelines) + g_hash_table_destroy(priv->config.encode_pipelines); + + g_free(priv->config.pool_lib_path); + + delete_event_state(priv->event_state); + + g_free(priv); + + GST_DEBUG("v4l_gst_deinit end"); +} diff --git a/lib/libv4l-gst/v4l-gst-ctrl.c b/lib/libv4l-gst/v4l-gst-ctrl.c new file mode 100644 index 0000000..e82c74d --- /dev/null +++ b/lib/libv4l-gst/v4l-gst-ctrl.c @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2015 Renesas Electronics Corporation + * Copyright (C) 2024-2026 ClearCode Inc. + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "libv4l-gst-bufferpool.h" + +#include "v4l-gst.h" +#include "evfd-ctrl.h" +#include "debug.h" +#include "utils.h" +#include "v4l-gst-internal.h" + +int +v4l_gst_ioctl_get_ctrl(struct v4l_gst *priv, struct v4l2_control *ctrl) +{ + int ret; + + GST_DEBUG("VIDIOC_G_CTRL: id: 0x%x value: 0x%x", ctrl->id, ctrl->value); + + switch (ctrl->id) { + case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE: + ctrl->value = priv->config.cap_min_buffers; + ret = 0; + break; + default: + GST_ERROR("Invalid control id"); + errno = EINVAL; + ret = -1; + break; + } + + return ret; +} + + +int +v4l_gst_ioctl_get_ext_ctrl(struct v4l_gst *priv, struct v4l2_ext_controls *ext_ctrls) +{ + unsigned int i; + +#ifdef ENABLE_VIDIOC_DEBUG + char *vidioc_features = getenv(ENV_DISABLE_VIDIOC_FEATURES); + if (vidioc_features && strstr(vidioc_features, "VIDIOC_G_EXT_CTRLS")) { + GST_CAT_ERROR(v4l_gst_ioctl_debug_category, "unsupported VIDIOC_G_EXT_CTRLS"); + errno = ENOTTY; + return 0; + } +#endif + + GST_DEBUG("VIDIOC_G_EXT_CTRLS: count: %d", ext_ctrls->count); + + for (i = 0; i < ext_ctrls->count; i++) { + struct v4l2_ext_control *ext_ctrl = &ext_ctrls->controls[i]; + if (ext_ctrl->id == V4L2_CID_MIN_BUFFERS_FOR_CAPTURE) { + ext_ctrl->value = priv->config.cap_min_buffers; + continue; + } + errno = EINVAL; + return -1; + } + + return 0; +} + + +/* + * Whether a pipeline for the given codec fourcc is enabled for the current + * role. While the role is not fixed yet (both decode and encode pipelines + * are configured), a codec counts as enabled when it has either pipeline. + */ +static gboolean +codec_pipeline_enabled(struct v4l_gst *priv, guint32 fourcc) +{ + gchar fourcc_str[5]; + + fourcc_to_string(fourcc, fourcc_str); + + if (priv->role != V4L_GST_ROLE_ENCODER && + g_hash_table_lookup(priv->config.pipelines, fourcc_str)) + return TRUE; + + if (priv->role != V4L_GST_ROLE_DECODER && + g_hash_table_lookup(priv->config.encode_pipelines, fourcc_str)) + return TRUE; + + return FALSE; +} + + +/* See https://github.com/JeffyCN/libv4l-rkmpp/blob/master/src/libv4l-rkmpp-dec.c#L740-L776 */ +int +v4l_gst_ioctl_queryctrl(struct v4l_gst *priv, struct v4l2_queryctrl *query_ctrl) +{ +#ifdef ENABLE_VIDIOC_DEBUG + char *vidioc_features = getenv(ENV_DISABLE_VIDIOC_FEATURES); + if (vidioc_features && strstr(vidioc_features, "VIDIOC_QUERYCTRL")) { + GST_CAT_ERROR(v4l_gst_ioctl_debug_category, + "unsupported VIDIOC_QUERYCTRL: id: 0x%x", + query_ctrl->id); + errno = ENOTTY; + return 0; + } +#endif + + GST_INFO("unsupported VIDIOC_QUERYCTRL id: 0x%x", query_ctrl->id); + + switch (query_ctrl->id) { + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + if (codec_pipeline_enabled(priv, V4L2_PIX_FMT_H264)) { + query_ctrl->minimum = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE; + query_ctrl->maximum = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_10; + } else { + GST_ERROR("disabled H264 profile for query_ctrl id: %x", query_ctrl->id); + errno = EINVAL; + return -1; + } + break; + case V4L2_CID_MPEG_VIDEO_HEVC_PROFILE: + if (codec_pipeline_enabled(priv, V4L2_PIX_FMT_HEVC)) { + query_ctrl->minimum = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN; + query_ctrl->maximum = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_10; + } else { + GST_ERROR("disabled H265/HEVC profile for unsupported query_ctrl id: %x", query_ctrl->id); + errno = EINVAL; + return -1; + } + break; +#if 0 // No AV1, VP8, VP9 definition + case V4L2_CID_MPEG_VIDEO_AV1_PROFILE: + query_ctrl->minimum = V4L2_MPEG_VIDEO_AV1_PROFILE_MAIN; + query_ctrl->maximum = query_ctrl->minimum; + break; + case V4L2_CID_MPEG_VIDEO_VP8_PROFILE: + query_ctrl->minimum = V4L2_MPEG_VIDEO_VP8_PROFILE_0; + query_ctrl->maximum = query_ctrl->minimum; + break; + case V4L2_CID_MPEG_VIDEO_VP9_PROFILE: + query_ctrl->minimum = V4L2_MPEG_VIDEO_VP9_PROFILE_0; + query_ctrl->maximum = V4L2_MPEG_VIDEO_VP9_PROFILE_2; + break; +#endif + /* TODO: fill info for other supported ctrls */ + default: + GST_ERROR("unsupported query_ctrl id: %x", query_ctrl->id); + errno = EINVAL; + return -1; + } + return 0; +} + + +/* See https://github.com/JeffyCN/libv4l-rkmpp/blob/master/src/libv4l-rkmpp-dec.c#L778-L842 */ +int +v4l_gst_ioctl_querymenu(struct v4l_gst *priv, struct v4l2_querymenu *query_menu) +{ + +#ifdef ENABLE_VIDIOC_DEBUG + char *vidioc_features = getenv(ENV_DISABLE_VIDIOC_FEATURES); + if (vidioc_features && strstr(vidioc_features, "VIDIOC_QUERYMENU")) { + GST_CAT_ERROR(v4l_gst_ioctl_debug_category, + "unsupported VIDIOC_QUERYMENU query_menu id: 0x%x", + query_menu->id); + errno = ENOTTY; + return 0; + } +#endif + + GST_DEBUG("VIDIOC_QUERYMENU query_menu id: %x", query_menu->id); + GST_DEBUG("query_menu index: %x", query_menu->index); + + switch (query_menu->id) { + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + switch (query_menu->index) { + case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE: + case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN: + case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH: + case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH_10: + break; + default: + GST_INFO("unsupported H264 profile index: %x", query_menu->index); + errno = EINVAL; + return -1; + } + GST_DEBUG("V4L2_CID_MPEG_VIDEO_H264_PROFILE index: %x", query_menu->index); + break; + case V4L2_CID_MPEG_VIDEO_HEVC_PROFILE: + switch (query_menu->index) { + case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN: + case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE: + case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_10: + break; + default: + GST_INFO("unsupported HEVC profile index: %x", query_menu->index); + errno = EINVAL; + return -1; + } + GST_DEBUG("V4L2_CID_MPEG_VIDEO_HEVC_PROFILE index: %x", query_menu->index); + break; +#if 0 // omit non-supported AV1, VP8, VP9 + case V4L2_CID_MPEG_VIDEO_AV1_PROFILE: + if (query_menu->index != V4L2_MPEG_VIDEO_AV1_PROFILE_MAIN) { + GST_INFO("unsupported VP8 profile index: %x", query_menu->index); + errno = EINVAL; + return -1; + } + GST_DEBUG("V4L2_CID_MPEG_VIDEO_AV1_PROFILE index: %x", query_menu->index); + break; + case V4L2_CID_MPEG_VIDEO_VP8_PROFILE: + if (query_menu->index != V4L2_MPEG_VIDEO_VP8_PROFILE_0) { + GST_INFO("unsupported VP8 profile index: %x", query_menu->index); + errno = EINVAL; + return -1; + } + GST_DEBUG("V4L2_CID_MPEG_VIDEO_VP8_PROFILE index: %x", query_menu->index); + break; + case V4L2_CID_MPEG_VIDEO_VP9_PROFILE: + switch (query_menu->index) { + case V4L2_MPEG_VIDEO_VP9_PROFILE_0: + case V4L2_MPEG_VIDEO_VP9_PROFILE_2: + break; + default: + GST_INFO("unsupported VP9 profile index: %x", query_menu->index); + errno = EINVAL; + return -1; + } + GST_DEBUG("V4L2_CID_MPEG_VIDEO_VP9_PROFILE index: %x", query_menu->index); + break; +#endif + default: + GST_ERROR("unsupported menu: %x", query_menu->id); + errno = EINVAL; + return -1; + } + return 0; +} + diff --git a/lib/libv4l-gst/v4l-gst-event.c b/lib/libv4l-gst/v4l-gst-event.c new file mode 100644 index 0000000..8946862 --- /dev/null +++ b/lib/libv4l-gst/v4l-gst-event.c @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2015 Renesas Electronics Corporation + * Copyright (C) 2024-2026 ClearCode Inc. + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "libv4l-gst-bufferpool.h" + +#include "v4l-gst.h" +#include "evfd-ctrl.h" +#include "debug.h" +#include "utils.h" +#include "v4l-gst-internal.h" + +int +v4l_gst_ioctl_subscribe_event(struct v4l_gst *priv, + struct v4l2_event_subscription *subscription) +{ + int retval = -1; + + errno = EINVAL; + g_return_val_if_fail(priv, retval); + g_return_val_if_fail(subscription, retval); + + g_mutex_lock(&priv->dev_lock); + + GST_DEBUG("VIDIOC_SUBSCRIBE_EVENT: type: %s (0x%x) id: %d flags: 0x%x", + v4l2_event_type_to_string(subscription->type), subscription->type, + subscription->id, subscription->flags); + + switch (subscription->type) { + case V4L2_EVENT_SOURCE_CHANGE: + /* Chromium supports only this type of v4l2events. */ + priv->v4l2events.subscribed |= (1 << V4L2_EVENT_SOURCE_CHANGE); + errno = 0; + retval = 0; + break; + default: + GST_ERROR("unsupported V4L2_EVENT type: %s (type: 0x%x)", + v4l2_event_type_to_string(subscription->type), + subscription->type); + errno = ENOTTY; + break; + } + + g_mutex_unlock(&priv->dev_lock); + + return retval; +} + + +int +v4l_gst_ioctl_dqevent(struct v4l_gst *priv, struct v4l2_event *ev) +{ + int retval = -1; + + GST_TRACE("VIDIOC_DQEVENT"); + + errno = EINVAL; + g_return_val_if_fail(ev, retval); + g_return_val_if_fail(priv, retval); + + g_mutex_lock(&priv->dev_lock); + g_mutex_lock(&priv->v4l2events.mutex); + + if (!priv->v4l2events.queue || priv->v4l2events.queue->length == 0) { + errno = EAGAIN; + goto unlock; + } + + if (priv->v4l2events.subscribed & (1 << V4L2_EVENT_SOURCE_CHANGE)) { + struct v4l2_event *next + = g_queue_pop_head(priv->v4l2events.queue); + if (!next) { + GST_WARNING("Failed to pop a v4l2_event."); + errno = EINVAL; + goto unlock; + } + *ev = *next; + ev->pending = priv->v4l2events.queue->length; + g_free(next); + errno = 0; + retval = 0; + GST_DEBUG("Dequeue SOURCE_CHANGE: pending %u, sequence: %u", + ev->pending, ev->sequence); + } + + unlock: + g_mutex_unlock(&priv->v4l2events.mutex); + g_mutex_unlock(&priv->dev_lock); + + return retval; +} + + +int +v4l_gst_ioctl_unsubscribe_event(struct v4l_gst *priv, + struct v4l2_event_subscription *subscription) +{ + int retval = 0; + + GST_INFO("VIDIOC_UNSUBSCRIBE_EVENT: type: 0x%x id: 0x%x flags: 0x%x", + subscription->type, subscription->id, subscription->flags); + + g_mutex_lock(&priv->dev_lock); + + errno = 0; + + switch (subscription->type) { + case V4L2_EVENT_ALL: + /* V4L2_EVENT_ALL is valid only for unsubscribe: + https://www.kernel.org/doc/html/v4.9/media/uapi/v4l/vidioc-dqevent.html#id2 + */ + priv->v4l2events.subscribed = 0; + break; + case V4L2_EVENT_SOURCE_CHANGE: + priv->v4l2events.subscribed &= ~(1 << V4L2_EVENT_SOURCE_CHANGE); + break; + default: + GST_ERROR("unsupported V4L2_EVENT type: %s (type: 0x%x)", + v4l2_event_type_to_string(subscription->type), + subscription->type); + errno = ENOTTY; + retval = -1; + break; + } + + g_mutex_unlock(&priv->dev_lock); + + return retval; +} + diff --git a/lib/libv4l-gst/v4l-gst-fmt.c b/lib/libv4l-gst/v4l-gst-fmt.c new file mode 100644 index 0000000..8abcc04 --- /dev/null +++ b/lib/libv4l-gst/v4l-gst-fmt.c @@ -0,0 +1,648 @@ +/* + * Copyright (C) 2015 Renesas Electronics Corporation + * Copyright (C) 2024-2026 ClearCode Inc. + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "libv4l-gst-bufferpool.h" + +#include "v4l-gst.h" +#include "evfd-ctrl.h" +#include "debug.h" +#include "utils.h" +#include "v4l-gst-internal.h" + +int +v4l_gst_ioctl_querycap(struct v4l_gst *priv, struct v4l2_capability *cap) +{ + GST_DEBUG("VIDIOC_QUERYCAP"); + + cap->device_caps = + V4L2_CAP_VIDEO_M2M_MPLANE | + V4L2_CAP_VIDEO_CAPTURE_MPLANE | + V4L2_CAP_VIDEO_OUTPUT_MPLANE | + V4L2_CAP_EXT_PIX_FORMAT | + V4L2_CAP_STREAMING; + + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + + g_strlcpy((gchar *)cap->driver, "libv4l-gst", sizeof(cap->driver)); + g_strlcpy((gchar *)cap->card, "gst-dummy", sizeof(cap->card)); + g_strlcpy((gchar *)cap->bus_info, "user-vst-gst-000", sizeof(cap->bus_info)); + memset(cap->reserved, 0, sizeof(cap->reserved)); + + return 0; +} + + +static gboolean +is_pix_fmt_supported(struct fmt *fmts, gint fmts_num, guint fourcc) +{ + gint i; + gboolean ret = FALSE; + + for (i = 0; i < fmts_num; i++) { + if (fmts[i].fourcc == fourcc) { + ret = TRUE; + break; + } + } + + return ret; +} + + +static void +set_params_as_encoded_stream(struct v4l2_pix_format_mplane *pix_fmt) +{ + /* We set the following parameters assuming that encoded streams are + received on the output buffer type. The values are almost + meaningless. */ + pix_fmt->width = 0; + pix_fmt->height = 0; + pix_fmt->field = V4L2_FIELD_NONE; + pix_fmt->colorspace = 0; + pix_fmt->flags = 0; + pix_fmt->plane_fmt[0].bytesperline = 0; + pix_fmt->num_planes = 1; +} + + +static int +set_fmt_ioctl_out(struct v4l_gst *priv, struct v4l2_format *fmt) +{ + struct v4l2_pix_format_mplane *pix_fmt; + gchar fourcc_str[5]; + GArray *cap_fmts = priv->cap.supported_fmts; + + pix_fmt = &fmt->fmt.pix_mp; + fourcc_to_string(pix_fmt->pixelformat, fourcc_str); + + if (!is_pix_fmt_supported((struct fmt*)priv->out.supported_fmts->data, + priv->out.supported_fmts->len, + pix_fmt->pixelformat)) { + GST_ERROR("Unsupported pixelformat on OUTPUT: %s (0x%x)", + fourcc_str, pix_fmt->pixelformat); + errno = EINVAL; + return -1; + } + + if (pix_fmt->plane_fmt[0].sizeimage == 0) { + GST_ERROR("sizeimage field is not specified on OUTPUT"); + errno = EINVAL; + return -1; + } + + if (priv->role == V4L_GST_ROLE_NONE) { + /* Fix the role from the media kind of the OUTPUT format: + a codec selects the decoder, a raw format selects the + encoder. The format is guaranteed to be supported by + the check above. */ + if (g_hash_table_lookup(priv->config.pipelines, + fourcc_str)) { + priv->role = V4L_GST_ROLE_DECODER; + priv->out.kind = V4L_GST_MEDIA_KIND_CODEC; + priv->cap.kind = V4L_GST_MEDIA_KIND_RAW; + } else { + priv->role = V4L_GST_ROLE_ENCODER; + priv->out.kind = V4L_GST_MEDIA_KIND_RAW; + priv->cap.kind = V4L_GST_MEDIA_KIND_CODEC; + } + GST_INFO("role fixed from OUTPUT format %s: %s", + fourcc_str, + priv->role == V4L_GST_ROLE_DECODER ? + "decoder" : "encoder"); + } + + if (priv->role == V4L_GST_ROLE_ENCODER) { + /* The OUTPUT format does not select the GStreamer pipeline. + The encoder pipeline is created when the CAPTURE codec + format is set. */ + } else if (priv->pipeline) { + if (priv->out.fmt.pixelformat == pix_fmt->pixelformat) { + GST_INFO("Same pixelformat with current: %s", + fourcc_str); + } else { + gchar current[5]; + + fourcc_to_string(priv->out.fmt.pixelformat, current); + GST_ERROR("Different pixelformat with current: " + "pixelformat:%s, current: %s", + fourcc_str, current); + errno = EINVAL; + return -1; + } + } else { + gboolean succeeded = v4l_gst_core_init_pipeline(priv, pix_fmt->pixelformat); + if (!succeeded) + goto error; + } + + priv->out.fmt = *pix_fmt; + + if (priv->role != V4L_GST_ROLE_ENCODER) + set_params_as_encoded_stream(pix_fmt); + + if (!priv->cap.fmt.pixelformat && cap_fmts->len > 0) + priv->cap.fmt.pixelformat + = ((struct fmt*)cap_fmts->data)[0].fourcc; + + return 0; + + error: + return -1; +} + + +void +v4l_gst_fmt_init_decoded_frame_params(struct v4l2_pix_format_mplane *pix_fmt) +{ + /* The following parameters will be determined after + the video decoding starts. */ + pix_fmt->width = 0; + pix_fmt->height = 0; + pix_fmt->num_planes = 0; + memset(pix_fmt->plane_fmt, 0, sizeof(pix_fmt->plane_fmt)); +} + + +static int +set_fmt_ioctl_cap(struct v4l_gst *priv, struct v4l2_format *fmt) +{ + struct v4l2_pix_format_mplane *pix_fmt; + + pix_fmt = &fmt->fmt.pix_mp; + + if (priv->role == V4L_GST_ROLE_NONE) { + GST_ERROR("The role is not fixed yet; set the OUTPUT format " + "first"); + errno = EINVAL; + return -1; + } + + if (!is_pix_fmt_supported((struct fmt*)priv->cap.supported_fmts->data, + priv->cap.supported_fmts->len, + pix_fmt->pixelformat)) { + GST_ERROR("Unsupported pixelformat on CAPTURE"); + errno = EINVAL; + return -1; + } + + if (priv->role == V4L_GST_ROLE_ENCODER && + pix_fmt->plane_fmt[0].sizeimage == 0) { + GST_ERROR("sizeimage field is not specified on CAPTURE"); + errno = EINVAL; + return -1; + } + + if (priv->role == V4L_GST_ROLE_ENCODER && !priv->pipeline) { + if (!v4l_gst_core_init_pipeline(priv, pix_fmt->pixelformat)) + return -1; + } + + GST_OBJECT_LOCK(priv->pipeline); + if (priv->role == V4L_GST_ROLE_ENCODER) { + /* Encoded stream on CAPTURE: the stream size is given by the + caller via sizeimage, so store it and mark the format as an + encoded stream. */ + if (GST_STATE(priv->pipeline) == GST_STATE_NULL) { + set_params_as_encoded_stream(&priv->cap.fmt); + priv->cap.fmt.pixelformat = pix_fmt->pixelformat; + priv->cap.fmt.plane_fmt[0].sizeimage = + pix_fmt->plane_fmt[0].sizeimage; + } else if (priv->cap.fmt.pixelformat != + pix_fmt->pixelformat) { + gchar fourcc_str[5]; + fourcc_to_string(pix_fmt->pixelformat, fourcc_str); + GST_ERROR("Changing pixel format during playing isn't " + "supported: pixelformat: %s (0x%x)", + fourcc_str, pix_fmt->pixelformat); + errno = EBUSY; + GST_OBJECT_UNLOCK(priv->pipeline); + return -1; + } + } else if (GST_STATE(priv->pipeline) == GST_STATE_NULL) { + priv->cap.fmt.pixelformat = pix_fmt->pixelformat; + v4l_gst_fmt_init_decoded_frame_params(pix_fmt); + } else if (priv->cap.fmt.width != pix_fmt->width || + priv->cap.fmt.height != pix_fmt->height || + priv->cap.fmt.pixelformat != pix_fmt->pixelformat) { + /* TODO: Should check the pix_fmt more strictly. */ + gchar fourcc_str[5]; + fourcc_to_string(pix_fmt->pixelformat, fourcc_str); + GST_ERROR("Changing pixel format during playing isn't supported: " + "width: %u, height: %u, pixelformat: %s (0x%x)", + pix_fmt->width, pix_fmt->height, + fourcc_str, pix_fmt->pixelformat); + errno = EBUSY; + GST_OBJECT_UNLOCK(priv->pipeline); + return -1; + } + GST_OBJECT_UNLOCK(priv->pipeline); + + /* set unsupported parameters */ + pix_fmt->field = V4L2_FIELD_NONE; + pix_fmt->colorspace = 0; + pix_fmt->flags = 0; + + return 0; +} + + +int +v4l_gst_ioctl_set_fmt(struct v4l_gst *priv, struct v4l2_format *fmt) +{ + int ret; + + GST_DEBUG("VIDIOC_S_FMT: type: %s (0x%x)", + v4l2_buffer_type_to_string(fmt->type), fmt->type); + + g_mutex_lock(&priv->dev_lock); + + if (fmt->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + ret = set_fmt_ioctl_out(priv, fmt); + } else if (fmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + ret = set_fmt_ioctl_cap(priv, fmt); + } else { + GST_ERROR("Invalid buffer type"); + errno = EINVAL; + ret = -1; + } + + g_mutex_unlock(&priv->dev_lock); + + return ret; +} + + +static int +get_fmt_ioctl_cap(struct v4l_gst *priv, + struct v4l2_pix_format_mplane *pix_fmt) +{ + gint i; + gchar fourcc_str[5]; + + if (!g_atomic_int_get(&priv->cap.fmt_acquirable) || + priv->out.cnt < INPUT_BUFFERING_CNT) { + errno = EINVAL; + return -1; + } + + GST_DEBUG("cap format is acquirable. out_cnt = %d",priv->out.cnt); + + pix_fmt->width = priv->cap.fmt.width; + pix_fmt->height = priv->cap.fmt.height; + pix_fmt->pixelformat = priv->cap.fmt.pixelformat; + pix_fmt->field = V4L2_FIELD_NONE; + pix_fmt->colorspace = 0; + pix_fmt->flags = 0; + pix_fmt->num_planes = priv->cap.fmt.num_planes; + + fourcc_to_string(pix_fmt->pixelformat, fourcc_str); + GST_DEBUG("width:%d height:%d, format: %s (0x%x) num_planes=%d", + pix_fmt->width, pix_fmt->height, + fourcc_str, pix_fmt->pixelformat, + pix_fmt->num_planes); + + if (priv->cap.fmt.plane_fmt[0].sizeimage > 0) { + for (i = 0; i < pix_fmt->num_planes; i++) { + pix_fmt->plane_fmt[i].sizeimage = + priv->cap.fmt.plane_fmt[i].sizeimage; + pix_fmt->plane_fmt[i].bytesperline = + priv->cap.fmt.plane_fmt[i].bytesperline; + } + pix_fmt->num_planes = priv->cap.fmt.num_planes; + } else { + memset(pix_fmt->plane_fmt, 0, sizeof(pix_fmt->plane_fmt)); + } + + return 0; +} + + +int +v4l_gst_ioctl_get_fmt(struct v4l_gst *priv, struct v4l2_format *fmt) +{ + struct v4l2_pix_format_mplane *pix_fmt; + int ret; + + GST_DEBUG("VIDIOC_G_FMT: type: %s (0x%x)", + v4l2_buffer_type_to_string(fmt->type), fmt->type); + + pix_fmt = &fmt->fmt.pix_mp; + + if (fmt->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + g_mutex_lock(&priv->dev_lock); + *pix_fmt = priv->out.fmt; + g_mutex_unlock(&priv->dev_lock); + if (priv->role != V4L_GST_ROLE_ENCODER) + set_params_as_encoded_stream(pix_fmt); + ret = 0; + } else if (fmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + ret = get_fmt_ioctl_cap(priv, pix_fmt); + } else { + GST_ERROR("Invalid buffer type"); + errno = EINVAL; + ret = -1; + } + + return ret; +} + + +int +v4l_gst_ioctl_enum_fmt(struct v4l_gst *priv, struct v4l2_fmtdesc *desc) +{ + struct fmt *fmts; + gint fmts_num; + gchar fourcc_str[5]; + + GST_DEBUG("VIDIOC_ENUM_FMT: type: %s (0x%x) index: %d", + v4l2_buffer_type_to_string(desc->type), desc->type, desc->index); + + if (desc->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + fmts = (struct fmt*)priv->out.supported_fmts->data; + fmts_num = priv->out.supported_fmts->len; + desc->flags = V4L2_FMT_FLAG_COMPRESSED; + } else if (desc->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + fmts = (struct fmt*)priv->cap.supported_fmts->data; + fmts_num = priv->cap.supported_fmts->len; + desc->flags = 0; + } else { + GST_ERROR("Invalid buf type"); + errno = EINVAL; + return -1; + } + + if (fmts_num <= desc->index) { + GST_DEBUG(" Index %u is out of range", desc->index); + errno = EINVAL; + return -1; + } + + desc->pixelformat = fmts[desc->index].fourcc; + g_strlcpy((gchar *)desc->description, fmts[desc->index].desc, + sizeof(desc->description)); + memset(desc->reserved, 0, sizeof(desc->reserved)); + fourcc_to_string(desc->pixelformat, fourcc_str); + + /* Flag each entry by its media kind: codec entries are compressed. */ + if (desc->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + desc->flags = g_hash_table_lookup(priv->config.pipelines, + fourcc_str) ? + V4L2_FMT_FLAG_COMPRESSED : 0; + else + desc->flags = g_hash_table_lookup(priv->config.encode_pipelines, + fourcc_str) ? + V4L2_FMT_FLAG_COMPRESSED : 0; + + GST_DEBUG(" description: %s pixelformat: %s (0x%x)", + desc->description, fourcc_str, desc->pixelformat); + + return 0; +} + + +int +v4l_gst_ioctl_enum_framesizes(struct v4l_gst *priv, struct v4l2_frmsizeenum *argp) +{ + gchar fourcc_str[5]; + + fourcc_to_string(argp->pixel_format, fourcc_str); + GST_DEBUG("VIDIOC_ENUM_FRAMESIZES:" + " index: %d pixel_format: %s (0x%x)", + argp->index, fourcc_str, argp->pixel_format); + + switch (argp->pixel_format) { + case V4L2_PIX_FMT_GREY: + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_RGB24: + case V4L2_PIX_FMT_BGR24: + case V4L2_PIX_FMT_ABGR32: + case V4L2_PIX_FMT_XBGR32: + case V4L2_PIX_FMT_ARGB32: + case V4L2_PIX_FMT_XRGB32: + case V4L2_PIX_FMT_RGB32: + case V4L2_PIX_FMT_BGR32: + case V4L2_PIX_FMT_H264: + case V4L2_PIX_FMT_HEVC: + argp->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; + argp->stepwise.step_width = 1; + argp->stepwise.step_height = 1; + break; + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: + case V4L2_PIX_FMT_NV12MT: + argp->type = V4L2_FRMSIZE_TYPE_STEPWISE; + argp->stepwise.step_width = 2; + argp->stepwise.step_height = 2; + break; + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_YUV422P: + argp->type = V4L2_FRMSIZE_TYPE_STEPWISE; + argp->stepwise.step_width = 2; + argp->stepwise.step_height = 1; + break; + case V4L2_PIX_FMT_YVU410: + case V4L2_PIX_FMT_YUV410: + argp->type = V4L2_FRMSIZE_TYPE_STEPWISE; + argp->stepwise.step_width = 4; + argp->stepwise.step_height = 4; + break; + case V4L2_PIX_FMT_YUV411P: + argp->type = V4L2_FRMSIZE_TYPE_STEPWISE; + argp->stepwise.step_width = 4; + argp->stepwise.step_height = 1; + break; + } + argp->stepwise.min_width = 16; + argp->stepwise.min_height = 16; + argp->stepwise.max_width = priv->config.max_width ? + priv->config.max_width : 1920; + argp->stepwise.max_height = priv->config.max_height ? + priv->config.max_height : 1080; + + return 0; +} + + +int +v4l_gst_ioctl_g_selection(struct v4l_gst *priv, struct v4l2_selection *selection) +{ +#ifdef ENABLE_VIDIOC_DEBUG + char *vidioc_features = getenv(ENV_DISABLE_VIDIOC_FEATURES); + if (vidioc_features && strstr(vidioc_features, "VIDIOC_G_SELECTION")) { + GST_CAT_ERROR(v4l_gst_ioctl_debug_category, + "unsupported VIDIOC_G_SELECTION"); + errno = ENOTTY; + return 0; + } +#endif + GST_DEBUG("VIDIOC_G_SELECTION: type: 0x%x target: 0x%x flags: 0x%x", + selection->type, selection->target, selection->flags); + + selection->r.top = selection->r.left = 0; + selection->r.width = priv->cap.fmt.width; + selection->r.height = priv->cap.fmt.height; + + return 0; +} + + +static int +try_fmt_ioctl_out(struct v4l_gst *priv, struct v4l2_format *format) +{ + struct v4l2_pix_format_mplane *pix_fmt = &format->fmt.pix_mp; + gchar fourcc_str[5]; + + fourcc_to_string(pix_fmt->pixelformat, fourcc_str); + + if (!is_pix_fmt_supported((struct fmt*)priv->out.supported_fmts->data, + priv->out.supported_fmts->len, + pix_fmt->pixelformat)) { + GST_ERROR("Unsupported pixelformat on OUTPUT: %s (0x%x)", + fourcc_str, pix_fmt->pixelformat); + + errno = EINVAL; + return -1; + } + + if (priv->pipeline && + priv->out.fmt.pixelformat != pix_fmt->pixelformat) { + gchar current[5]; + + fourcc_to_string(priv->out.fmt.pixelformat, current); + GST_ERROR("Different pixelformat with current: " + "pixelformat:%s, current: %s", + fourcc_str, current); + + errno = EINVAL; + return -1; + } + + if (priv->role != V4L_GST_ROLE_ENCODER) + set_params_as_encoded_stream(pix_fmt); + + return 0; +} + + +static int +try_fmt_ioctl_cap(struct v4l_gst *priv, struct v4l2_format *format) +{ + struct v4l2_pix_format_mplane *pix_fmt = &format->fmt.pix_mp; + + if (!is_pix_fmt_supported((struct fmt*)priv->cap.supported_fmts->data, + priv->cap.supported_fmts->len, + pix_fmt->pixelformat)) { + gchar fourcc_str[5]; + + fourcc_to_string(pix_fmt->pixelformat, fourcc_str); + GST_ERROR("Unsupported pixelformat on CAPTURE"); + + errno = EINVAL; + return -1; + } + + pix_fmt->field = V4L2_FIELD_NONE; + pix_fmt->colorspace = 0; + pix_fmt->flags = 0; + + return 0; +} + + +int +v4l_gst_ioctl_try_fmt(struct v4l_gst *priv, struct v4l2_format *format) +{ + int ret; + gchar fourcc_str[5]; + + fourcc_to_string(format->fmt.pix_mp.pixelformat, fourcc_str); + GST_DEBUG("VIDIOC_TRY_FMT: type: 0x%x, pixelformat: %s (0x%x)", + format->type, fourcc_str, format->fmt.pix_mp.pixelformat); + + g_mutex_lock(&priv->dev_lock); + + if (format->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + ret = try_fmt_ioctl_out(priv, format); + } else if (format->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + ret = try_fmt_ioctl_cap(priv, format); + } else { + GST_ERROR("Invalid buffer type"); + errno = EINVAL; + ret = -1; + } + + g_mutex_unlock(&priv->dev_lock); + + return ret; +} + + +int +v4l_gst_ioctl_g_crop(struct v4l_gst *priv, struct v4l2_crop *crop) +{ + const gchar *buf_type; +#ifdef ENABLE_VIDIOC_DEBUG + char *vidioc_features = getenv(ENV_DISABLE_VIDIOC_FEATURES); + if (vidioc_features && strstr(vidioc_features, "VIDIOC_G_CROP")) { + GST_CAT_ERROR(v4l_gst_ioctl_debug_category, + "unsupported VIDIOC_G_EXT_CTRLS v4l2_crop type: 0x%x", + crop->type); + errno = ENOTTY; + return 0; + } +#endif + + GST_INFO("unsupported VIDIOC_G_CROP v4l2_crop type: 0x%x", crop->type); + + buf_type = v4l2_buffer_type_to_string(crop->type); + if (buf_type) { + GST_DEBUG("v4l2_crop type: V4L2_BUF_TYPE_%s", buf_type); + } else { + GST_DEBUG("unsupported v4l2_crop type: 0x%x", crop->type); + } + GST_DEBUG("v4l2_crop rect: left:%d top:%d width: %u height: %u", + crop->c.left, crop->c.top, crop->c.width, crop->c.height); + + return 0; +} + diff --git a/lib/libv4l-gst/v4l-gst-internal.h b/lib/libv4l-gst/v4l-gst-internal.h new file mode 100644 index 0000000..81fd972 --- /dev/null +++ b/lib/libv4l-gst/v4l-gst-internal.h @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2015 Renesas Electronics Corporation + * Copyright (C) 2024-2026 ClearCode Inc. + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA + */ + +#ifndef __V4L_GST_INTERNAL_H__ +#define __V4L_GST_INTERNAL_H__ + +#include + +#include +#include +#include + +#include "evfd-ctrl.h" +#include "libv4l-gst-bufferpool.h" +#include "utils.h" + +#define DEF_CAP_MIN_BUFFERS 2 +#define INPUT_BUFFERING_CNT 16 // must be <= VIDEO_MAX_FRAME +#define INITIAL_BUFFER_WAIT_TIMEOUT (10 * G_TIME_SPAN_SECOND) + +#define FMTDESC_NAME_LENGTH 32 // The same size as defined in the V4L2 spec + +enum buffer_state { + V4L_GST_BUFFER_QUEUED, + V4L_GST_BUFFER_DEQUEUED, +}; + +struct v4l_gst; + +struct v4l_gst_buffer { + GstBuffer *gstbuf; + GstMapInfo info; + GstMapFlags flags; + struct v4l2_plane planes[GST_VIDEO_MAX_PLANES]; + struct v4l_gst *priv; + enum buffer_state state; + int plane0_fd; /* See reindex_buffers() */ +}; + +struct fmt { + guint fourcc; + gchar desc[FMTDESC_NAME_LENGTH]; +}; + +/* + * Kind of media carried by the buffers of one M2M stream direction: + * + * - V4L_GST_MEDIA_KIND_CODEC: compressed bitstream (H.264, HEVC, ...) + * - V4L_GST_MEDIA_KIND_RAW : uncompressed video frames (NV12, ...) + * + * A decoder has OUTPUT = CODEC and CAPTURE = RAW, + * an encoder has OUTPUT = RAW and CAPTURE = CODEC. + */ +enum v4l_gst_media_kind { + V4L_GST_MEDIA_KIND_CODEC, + V4L_GST_MEDIA_KIND_RAW, +}; + +/* + * M2M role of the plugin: + * + * - V4L_GST_ROLE_NONE : both decode and encode pipelines are configured, + * the role is fixed by the first OUTPUT format + * - V4L_GST_ROLE_DECODER : OUTPUT carries CODEC, CAPTURE carries RAW + * - V4L_GST_ROLE_ENCODER : OUTPUT carries RAW, CAPTURE carries CODEC + */ +enum v4l_gst_role { + V4L_GST_ROLE_NONE, + V4L_GST_ROLE_DECODER, + V4L_GST_ROLE_ENCODER, +}; + +/* + * State of one M2M stream direction (OUTPUT or CAPTURE). + */ +struct v4l_gst_stream { + enum v4l2_buf_type buf_type; + /* valid only once the role is fixed (priv->role != V4L_GST_ROLE_NONE) */ + enum v4l_gst_media_kind kind; + + struct v4l2_pix_format_mplane fmt; + GArray *supported_fmts; /* struct fmt */ + + GstBufferPool *pool; + struct v4l_gst_buffer *buffers; + gint buffers_num; + GQueue *gstbufs_queue; /* GstBuffer */ + + gint cnt; + gint returned_cnt; + + /* Used by the RAW stream (decoder CAPTURE) to wait for the + requested number of buffers to be set in pad_probe_query() */ + GMutex reqbuf_mutex; + GCond reqbuf_cond; + gboolean cancel_reqbuf_wait; + int fmt_acquirable; +}; + +typedef enum { + EOS_NONE, + EOS_WAITING_DECODE, + EOS_GOT +} EOSState; + +struct v4l_gst { + int plugin_fd; + gboolean is_non_blocking; + struct event_state *event_state; + + GstElement *pipeline; + GstElement *appsrc; + GstElement *appsink; + GstElement *decoder; + GstPad *video_sink_pad; + + GstVideoInfo src_video_info; + + GstAppSinkCallbacks appsink_cb; + gulong probe_id; + gulong decoder_probe_id; + + void *pool_lib_handle; + struct libv4l_gst_buffer_pool_ops *pool_ops; + + /* + * out (OUTPUT) : Application --> v4l-gst + * cap (CAPTURE): Application <-- v4l-gst + * + * decoder: out.kind = CODEC (e.g. H.264), cap.kind = RAW (e.g. NV12) + * encoder: out.kind = RAW (e.g. NV12), cap.kind = CODEC (e.g. H.264) + * + * role stays V4L_GST_ROLE_NONE until the first OUTPUT format fixes + * it when both decode and encode pipelines are configured. + */ + enum v4l_gst_role role; + struct v4l_gst_stream out; + struct v4l_gst_stream cap; + + int64_t mmap_offset; + + GMutex queue_mutex; + GCond queue_cond; + + gboolean is_pipeline_started; + + /* The OUTPUT caps have been pushed to the appsrc before any buffer so + that the base source can negotiate before the first buffer is pushed. + Reset when the pipeline is stopped (the appsrc queue is flushed). */ + gboolean out_caps_set; + + GstBuffer *eos_gstbuf; + EOSState eos_state; + GstClockTime last_cap_pts; + GstClockTime estimated_cap_duration; + + struct { + gint cap_min_buffers; + gint max_width; + gint max_height; + guint32 preferred_format; + guint32 fixed_pipeline; + GHashTable *pipelines; /* gchar *fourcc, gchar *pipeline */ + GHashTable *encode_pipelines; /* gchar *fourcc, gchar *encode-pipeline */ + gchar *pool_lib_path; + FrameCheckType frame_check; + } config; + + struct { + GMutex mutex; + gint subscribed; + guint32 sequence; + GQueue *queue; + } v4l2events; + + GMutex dev_lock; +}; + +/* Debug categories (defined in v4l-gst-core.c) */ +extern GstDebugCategory *v4l_gst_debug_category; +extern GstDebugCategory *v4l_gst_ioctl_debug_category; +extern GstDebugCategory *v4l_gst_buffer_debug_category; +#define GST_CAT_DEFAULT v4l_gst_debug_category + +GQuark v4l_gst_cap_buf_crc_quark (void); + +/* + * Functions shared between the v4l-gst-*.c translation units, + * grouped by domain. + */ + +/* core */ +GstElement * v4l_gst_core_create_pipeline (const gchar *pipeline_str); +GstElement * v4l_gst_core_get_peer_element (GstElement *elem, + const gchar *pad_name); +gboolean v4l_gst_core_init_pipeline (struct v4l_gst *priv, + guint32 fourcc); +gboolean v4l_gst_core_setup_role (struct v4l_gst *priv); +void v4l_gst_core_push_source_change_event (struct v4l_gst *priv); +void v4l_gst_core_reset_cap_timestamp_state (struct v4l_gst *priv); +void v4l_gst_core_set_pipeline_started (struct v4l_gst *priv, + gboolean started); + +/* pipeline */ +GstCaps * v4l_gst_pipeline_get_codec_caps_from_fourcc (guint fourcc); +GstCaps * v4l_gst_pipeline_get_raw_caps_from_fmt (struct v4l2_pix_format_mplane *fmt); +gboolean v4l_gst_pipeline_get_raw_video_params (GstBufferPool *pool, + GstBuffer *gstbuf, + GstVideoInfo *info, + GstVideoMeta **meta); +void v4l_gst_pipeline_get_buffer_pool_params (GstBufferPool *pool, + GstCaps **caps, + guint *buf_size, + guint *min_buffers, + guint *max_buffers); +int v4l_gst_pipeline_flush (struct v4l_gst *priv); +gboolean v4l_gst_pipeline_set_out_format (struct v4l_gst *priv); +gboolean v4l_gst_pipeline_set_cap_format (struct v4l_gst *priv); +void v4l_gst_pipeline_set_buffer_pool_params (GstBufferPool *pool, + GstCaps *caps, + guint buf_size, + guint min_buffers, + guint max_buffers, + GstVideoAlignment *alignment); +gboolean v4l_gst_pipeline_setup_app_elements (struct v4l_gst *priv); +gulong v4l_gst_pipeline_setup_query_pad_probe (struct v4l_gst *priv); +int v4l_gst_pipeline_stop (struct v4l_gst *priv); + +/* fmt */ +void v4l_gst_fmt_init_decoded_frame_params (struct v4l2_pix_format_mplane *pix_fmt); + +/* buf */ +void v4l_gst_buf_release_out_buffer (struct v4l_gst *priv, + GstBuffer *gstbuf); + +#endif /* __V4L_GST_INTERNAL_H__ */ diff --git a/lib/libv4l-gst/v4l-gst-pipeline.c b/lib/libv4l-gst/v4l-gst-pipeline.c new file mode 100644 index 0000000..866d141 --- /dev/null +++ b/lib/libv4l-gst/v4l-gst-pipeline.c @@ -0,0 +1,868 @@ +/* + * Copyright (C) 2015 Renesas Electronics Corporation + * Copyright (C) 2024-2026 ClearCode Inc. + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "libv4l-gst-bufferpool.h" + +#include "v4l-gst.h" +#include "evfd-ctrl.h" +#include "debug.h" +#include "utils.h" +#include "v4l-gst-internal.h" + +static void +get_cap_buffer_alignment(struct v4l_gst *priv, GstVideoAlignment *alignment) +{ + guint stride_align, i; + + /* In dma-buffer, ARM Mali requires strict allocation alignment for each + color format (NV12, NV21, YV12, IYUV, I420, IMC1, IMC2, IMC3, IMC4, + P210, P010 require 16-byte alignment. Others require 64-byte alignment) + refs: + https://github.com/renesas-rz/gst-plugins-bad/commit/728304b71301f909142bd2d29099a8fc370e5e25 + https://github.com/renesas-rz/gst-plugins-bad/commit/79f3733bb085d07a78e996bf8d935ad31d43c7b1 + https://github.com/renesas-rz/gst-plugins-bad/commit/e07995fa6e7c24868008826f177d9eaf9c1f88e1 + + GstVideoAlignment::stride_align is a bitmask, not an alignment value. + It must be specified as a bitmask of the form (2^n - 1). + gst_video_info_align_full() checks alignment using: + + (stride & stride_align) == 0 + + e.g.) + 16-byte alignment -> 15 (0b1111) + 64-byte alignment -> 63 (0b111111) */ + switch (priv->cap.fmt.pixelformat) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_YVU420: + case V4L2_PIX_FMT_YUV420: +#ifdef V4L2_PIX_FMT_P010 + case V4L2_PIX_FMT_P010: +#endif + stride_align = 15; + break; + default: + stride_align = 63; + break; + } + + gst_video_alignment_reset(alignment); + for (i = 0; i < priv->cap.fmt.num_planes; i++) + alignment->stride_align[i] = stride_align; +} + + +void +v4l_gst_pipeline_set_buffer_pool_params(GstBufferPool *pool, GstCaps *caps, guint buf_size, + guint min_buffers, guint max_buffers, + GstVideoAlignment *alignment) +{ + GstStructure *config; + + config = gst_buffer_pool_get_config(pool); + gst_buffer_pool_config_set_params(config, caps, buf_size, min_buffers, + max_buffers); + if (alignment) { + gst_buffer_pool_config_add_option + (config, GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT); + gst_buffer_pool_config_set_video_alignment(config, alignment); + } + gst_buffer_pool_set_config(pool, config); +} + + +void +v4l_gst_pipeline_get_buffer_pool_params(GstBufferPool *pool, GstCaps **caps, guint *buf_size, + guint *min_buffers, guint *max_buffers) +{ + GstStructure *config; + + config = gst_buffer_pool_get_config(pool); + gst_buffer_pool_config_get_params(config, caps, buf_size, min_buffers, + max_buffers); + gst_structure_free(config); +} + + +static void +retrieve_cap_format_info(struct v4l_gst *priv, GstVideoInfo *info) +{ + gint fourcc; + + priv->cap.fmt.width = info->width; + priv->cap.fmt.height = info->height; + + fourcc = fourcc_from_gst_video_format(info->finfo->format); + if (priv->cap.fmt.pixelformat != 0 && + priv->cap.fmt.pixelformat != fourcc) { + GST_WARNING("Unexpected cap video format"); + } + priv->cap.fmt.pixelformat = fourcc; + + priv->cap.fmt.num_planes = info->finfo->n_planes; +} + + +static gboolean +wait_for_cap_reqbuf_invocation(struct v4l_gst *priv) +{ + gboolean succeeded; + gboolean timed_out = FALSE; + gint64 end_time; + + g_mutex_lock(&priv->cap.reqbuf_mutex); + end_time = g_get_monotonic_time() + INITIAL_BUFFER_WAIT_TIMEOUT; + while (!priv->cap.cancel_reqbuf_wait && priv->cap.buffers_num <= 0) { + if (!g_cond_wait_until(&priv->cap.reqbuf_cond, + &priv->cap.reqbuf_mutex, + end_time)) { + timed_out = TRUE; + break; + } + } + succeeded = !priv->cap.cancel_reqbuf_wait && priv->cap.buffers_num > 0; + g_mutex_unlock(&priv->cap.reqbuf_mutex); + + if (timed_out && !succeeded) + GST_WARNING("Timed out waiting VIDIOC_REQBUFS on CAPTURE."); + + return succeeded; +} + + +static GstPadProbeReturn +pad_probe_query(GstPad *pad, GstPadProbeInfo *probe_info, gpointer user_data) +{ + struct v4l_gst *priv = user_data; + GstQuery *query; + GstCaps *caps; + GstVideoInfo info; + guint src_width = priv->src_video_info.width; + guint src_height = priv->src_video_info.height; + GstVideoAlignment alignment; + + query = GST_PAD_PROBE_INFO_QUERY (probe_info); + if (GST_QUERY_TYPE (query) == GST_QUERY_ALLOCATION && + GST_PAD_PROBE_INFO_TYPE (probe_info) & GST_PAD_PROBE_TYPE_PUSH) { + GST_DEBUG("parse allocation query"); + gst_query_parse_allocation(query, &caps, NULL); + if (!caps) { + GST_ERROR("No caps in query"); + return GST_PAD_PROBE_OK; + } + + if (priv->cap.kind == V4L_GST_MEDIA_KIND_CODEC) { + /* Encoded stream on CAPTURE: the caps are codec caps + which do not carry an allocatable size, so use the + sizeimage specified via VIDIOC_S_FMT on CAPTURE. No + video meta or alignment is required for an encoded + stream. */ + guint buf_size = priv->cap.fmt.plane_fmt[0].sizeimage; + + g_atomic_int_set(&priv->cap.fmt_acquirable, 1); + v4l_gst_core_push_source_change_event(priv); + + set_event(priv->event_state, POLLOUT); + + if (wait_for_cap_reqbuf_invocation(priv)) { + v4l_gst_pipeline_set_buffer_pool_params( + priv->cap.pool, caps, + buf_size, 0, + priv->cap.buffers_num, NULL); + gst_query_add_allocation_pool(query, + priv->cap.pool, buf_size, + 0, priv->cap.buffers_num); + } else { + GST_WARNING("Failed to wait VIDIOC_REQBUF."); + } + + return GST_PAD_PROBE_OK; + } + + if (!gst_video_info_from_caps(&info, caps)) { + GST_ERROR("Failed to get video info"); + return GST_PAD_PROBE_OK; + } + + if ((src_width && src_width != info.width) || + (src_height && src_height != info.height)) { + /* Sometimes decoder may send interim resolutions that + differ from the original one (typically 16x16 + macroblock based one: e.g. 640x368 vs 640x360) before + sending the final resolution. + Skip such interim resolutions then wait for the original one. + */ + return GST_PAD_PROBE_OK; + } + + retrieve_cap_format_info(priv, &info); + get_cap_buffer_alignment(priv, &alignment); + g_atomic_int_set(&priv->cap.fmt_acquirable, 1); + v4l_gst_core_push_source_change_event(priv); + + set_event(priv->event_state, POLLOUT); + + /* Even if a min value is set here, omxvideodec will reset it + with an internally calculated value. It's at least 4 and + possible to become larger than max. When it's calculated as + larger than max, bufferpool will fail to allocate. + To ensure to avoid it, we set the smallest possible value 0 + here. + ref: https://github.com/renesas-rcar/gst-omx/blob/37296f66e3392d8dcdcdae14b89b05dd7507dc39/omx/gstomxvideodec.c#L920 + + On RZ/G2, you can use `num-outbufs` property of omxvideodec + to override it. + e.g.) + `pipeline=h264parse ! omxh264dec no-reorder=true num-outbufs=7` + */ + if (wait_for_cap_reqbuf_invocation(priv)) { + v4l_gst_pipeline_set_buffer_pool_params(priv->cap.pool, caps, info.size, + 0, priv->cap.buffers_num, + &alignment); + gst_query_add_allocation_pool(query, priv->cap.pool, + info.size, + 0, priv->cap.buffers_num); + } else { + GST_WARNING("Failed to wait VIDIOC_REQBUF."); + } + } + + return GST_PAD_PROBE_OK; +} + + +static void +appsink_pad_unlinked_cb(GstPad *self, GstPad *peer, gpointer data) +{ + struct v4l_gst *priv = data; + + GST_DEBUG("clear probe_id"); + priv->probe_id = 0; + + g_signal_handlers_disconnect_by_func(self, appsink_pad_unlinked_cb, data); +} + + +static GstPadProbeReturn +decoder_sink_pad_probe(GstPad *pad, GstPadProbeInfo *probe_info, gpointer user_data) +{ + struct v4l_gst *priv = user_data; + GstPadProbeType type = GST_PAD_PROBE_INFO_TYPE(probe_info); + GstEvent *event; + GstCaps *caps = NULL; + + if (!(type & GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM)) + return GST_PAD_PROBE_OK; + + event = GST_PAD_PROBE_INFO_EVENT(probe_info); + if (GST_EVENT_TYPE(event) != GST_EVENT_CAPS) + return GST_PAD_PROBE_OK; + + gst_event_parse_caps(event, &caps); + if (!caps) + return GST_PAD_PROBE_OK; + + if (!gst_video_info_from_caps(&priv->src_video_info, caps)) + return GST_PAD_PROBE_OK; + + GST_DEBUG("Source video info: %" GST_PTR_FORMAT, caps); + + return GST_PAD_PROBE_OK; +} + + +static void +decoder_pad_unlinked_cb(GstPad *self, GstPad *peer, gpointer data) +{ + struct v4l_gst *priv = data; + + GST_DEBUG("clear decoder_probe_id"); + priv->decoder_probe_id = 0; + g_signal_handlers_disconnect_by_func(self, decoder_pad_unlinked_cb, data); +} + + +gulong +v4l_gst_pipeline_setup_query_pad_probe(struct v4l_gst *priv) +{ + gulong probe_id; + + g_signal_connect(G_OBJECT(priv->video_sink_pad), "unlinked", + G_CALLBACK(appsink_pad_unlinked_cb), priv); + probe_id = gst_pad_add_probe(priv->video_sink_pad, + GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM, + pad_probe_query, + priv, NULL); + + return probe_id; +} + + +static GstBuffer * +pull_buffer_from_sample(GstAppSink *appsink) +{ + GstSample *sample; + GstBuffer *gstbuf; + + sample = gst_app_sink_pull_sample(appsink); + gstbuf = gst_sample_get_buffer(sample); + gst_buffer_ref(gstbuf); + gst_sample_unref(sample); + + return gstbuf; +} + + +static void +appsink_callback_eos(GstAppSink *appsink, gpointer user_data) +{ + struct v4l_gst *priv = user_data; + if (priv->eos_gstbuf) + v4l_gst_buf_release_out_buffer(priv, priv->eos_gstbuf); + g_mutex_lock(&priv->queue_mutex); + GST_DEBUG("EOS: Got from AppSink. Cached buffers: %u", + g_queue_get_length(priv->cap.gstbufs_queue)); + priv->eos_state = EOS_GOT; + if (priv->cap.gstbufs_queue && + !g_queue_is_empty(priv->cap.gstbufs_queue)) { + set_event(priv->event_state, POLLOUT); + } + g_mutex_unlock(&priv->queue_mutex); +} + + +static GstFlowReturn +appsink_callback_new_sample(GstAppSink *appsink, gpointer user_data) +{ + struct v4l_gst *priv = user_data; + GstBuffer *gstbuf; + guint len; + GQueue *queue; + + gstbuf = pull_buffer_from_sample(appsink); + + if (priv->cap.buffers && !gst_buffer_n_memory(gstbuf)) { + /* Empty samples cannot be associated with a V4L2 CAPTURE + buffer or dmabuf fd, so do not expose them to clients. */ + GST_WARNING("Drop empty CAPTURE sample: gstbuf=%p, pts=%" + G_GUINT64_FORMAT ", duration=%" G_GUINT64_FORMAT, + gstbuf, (guint64) GST_BUFFER_PTS(gstbuf), + (guint64) GST_BUFFER_DURATION(gstbuf)); + gst_buffer_unref(gstbuf); + return GST_FLOW_OK; + } + + if (priv->config.frame_check && gst_buffer_n_memory(gstbuf)) { + guint32 crc; + + crc = frame_crc32(gstbuf, priv->config.frame_check); + gst_mini_object_set_qdata(GST_MINI_OBJECT(gstbuf), + v4l_gst_cap_buf_crc_quark(), + GUINT_TO_POINTER(crc), + NULL); + GST_CAT_DEBUG(v4l_gst_buffer_debug_category, + "pull buffer from appsink:" + " gstbuf=%p, pts=%lu, crc=%u", + gstbuf, GST_BUFFER_PTS(gstbuf) / 1000000, crc); + } else { + GST_CAT_DEBUG(v4l_gst_buffer_debug_category, + "pull buffer from appsink: gstbuf=%p, pts=%lu", + gstbuf, GST_BUFFER_PTS(gstbuf) / 1000000); + } + + if (priv->cap.buffers) + queue = priv->cap.gstbufs_queue; + else + queue = priv->out.gstbufs_queue; + + g_mutex_lock(&priv->queue_mutex); + + g_queue_push_tail(queue, gstbuf); + len = g_queue_get_length(queue); + + if (len > 1 || priv->eos_state == EOS_GOT) { + /* cache 1 buffer to detect EOS */ + if (len == 0 && priv->eos_state == EOS_GOT) + GST_DEBUG("EOS: Flush last frame"); + g_cond_signal(&priv->queue_cond); + set_event(priv->event_state, POLLOUT); + } else if (!priv->cap.buffers) { + g_cond_signal(&priv->queue_cond); + } + + g_mutex_unlock(&priv->queue_mutex); + + return GST_FLOW_OK; +} + + +gboolean +v4l_gst_pipeline_setup_app_elements(struct v4l_gst *priv) +{ + /* Set the appsrc queue size to unlimited. + The amount of buffers is managed by the buffer pool. */ + gst_app_src_set_max_bytes(GST_APP_SRC(priv->appsrc), 0); + + /* Use time-based appsrc segments only for raw encoder input. Decoder + input keeps the previous appsrc behavior to avoid changing the + segment format seen by the codec parser/decoder. */ + if (priv->out.kind == V4L_GST_MEDIA_KIND_RAW) + g_object_set(G_OBJECT(priv->appsrc), "format", GST_FORMAT_TIME, + "do-timestamp", TRUE, NULL); + + gst_base_sink_set_sync(GST_BASE_SINK(priv->appsink), FALSE); + + priv->appsink_cb.new_sample = appsink_callback_new_sample; + priv->appsink_cb.eos = appsink_callback_eos; + + gst_app_sink_set_callbacks(GST_APP_SINK(priv->appsink), + &priv->appsink_cb, priv, NULL); + + if (priv->decoder) { + GstPad *pad = gst_element_get_static_pad(priv->decoder, "sink"); + + g_signal_connect(G_OBJECT(pad), "unlinked", + G_CALLBACK(decoder_pad_unlinked_cb), priv); + priv->decoder_probe_id + = gst_pad_add_probe(pad, + GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, + decoder_sink_pad_probe, + priv, NULL); + gst_object_unref(pad); + } + + return TRUE; +} + + +gboolean +v4l_gst_pipeline_get_raw_video_params(GstBufferPool *pool, GstBuffer *gstbuf, GstVideoInfo *info, + GstVideoMeta **meta) +{ + gboolean ret; + GstCaps *caps; + GstVideoInfo vinfo; + GstVideoMeta *vmeta; + + v4l_gst_pipeline_get_buffer_pool_params(pool, &caps, NULL, NULL, NULL); + + ret = gst_video_info_from_caps(&vinfo, caps); + if (!ret || GST_VIDEO_INFO_FORMAT(&vinfo) == GST_VIDEO_FORMAT_ENCODED) + return FALSE; + + vmeta = gst_buffer_get_video_meta(gstbuf); + + if (info) + memcpy(info, &vinfo, sizeof(GstVideoInfo)); + if (meta) + *meta = vmeta; + + return TRUE; +} + + +GstCaps * +v4l_gst_pipeline_get_codec_caps_from_fourcc(guint fourcc) +{ + const gchar *mime; + + mime = fourcc_to_mimetype(fourcc); + if (!mime) { + gchar fourcc_str[5]; + fourcc_to_string(fourcc, fourcc_str); + GST_ERROR("Failed to convert from fourcc to mime string: %u (\"%s\")", + fourcc, fourcc_str); + return NULL; + } + + if (g_strcmp0(mime, GST_VIDEO_CODEC_MIME_H264) == 0 || + g_strcmp0(mime, GST_VIDEO_CODEC_MIME_HEVC) == 0) { + return gst_caps_new_simple(mime, "stream-format", + G_TYPE_STRING, "byte-stream", NULL); + } + + return gst_caps_new_empty_simple(mime); +} + + +GstCaps * +v4l_gst_pipeline_get_raw_caps_from_fmt(struct v4l2_pix_format_mplane *fmt) +{ + GstVideoFormat gst_fmt; + const gchar *fmt_str; + + gst_fmt = fourcc_to_gst_video_format(fmt->pixelformat); + if (gst_fmt == GST_VIDEO_FORMAT_UNKNOWN) { + gchar fourcc_str[5]; + fourcc_to_string(fmt->pixelformat, fourcc_str); + GST_ERROR("Failed to convert from fourcc to gst video format: %s (0x%x)", + fourcc_str, fmt->pixelformat); + return NULL; + } + + fmt_str = gst_video_format_to_string(gst_fmt); + + /* Provide a framerate so the appsrc can auto-timestamp the + wrapped input buffers, which carry no PTS of their own. */ + return gst_caps_new_simple("video/x-raw", + "format", G_TYPE_STRING, fmt_str, + "width", G_TYPE_INT, (gint) fmt->width, + "height", G_TYPE_INT, (gint) fmt->height, + "framerate", GST_TYPE_FRACTION, 30, 1, + NULL); +} + + +int +v4l_gst_pipeline_flush(struct v4l_gst *priv) +{ + GstEvent *event; + + GST_DEBUG("flush start"); + + gst_buffer_pool_set_flushing(priv->out.pool, TRUE); + gst_buffer_pool_set_flushing(priv->cap.pool, TRUE); + + event = gst_event_new_flush_start(); + if (!gst_element_send_event(priv->pipeline, event)) { + GST_ERROR("Failed to send a flush start event"); + errno = EINVAL; + return -1; + } + + GST_DEBUG("flush stop ..."); + + event = gst_event_new_flush_stop(TRUE); + if (!gst_element_send_event(priv->pipeline, event)) { + GST_ERROR("Failed to send a flush stop event"); + errno = EINVAL; + return -1; + } + + gst_buffer_pool_set_flushing(priv->out.pool, FALSE); + gst_buffer_pool_set_flushing(priv->cap.pool, FALSE); + + GST_DEBUG("flush end"); + + return 0; +} + + +int +v4l_gst_pipeline_stop(struct v4l_gst *priv) +{ + GstStateChangeReturn state_ret; + int ret = 0; + gint i; + + GST_DEBUG("req->count == 0, stop the pipeline"); + + v4l_gst_core_set_pipeline_started(priv, FALSE); + + state_ret = gst_element_set_state(priv->pipeline, + GST_STATE_NULL); + while (state_ret == GST_STATE_CHANGE_ASYNC) { + /* This API blocks up to the ASYNC state change completion. */ + g_mutex_unlock(&priv->dev_lock); + state_ret = gst_element_get_state(priv->pipeline, NULL, + NULL, + GST_CLOCK_TIME_NONE); + g_mutex_lock(&priv->dev_lock); + } + + if (state_ret != GST_STATE_CHANGE_SUCCESS) { + GST_ERROR("Failed to stop pipeline (ret:%s)", + gst_element_state_change_return_get_name(state_ret)); + errno = EINVAL; + ret = -1; + return ret; + } + + g_atomic_int_set(&priv->cap.fmt_acquirable, 0); + + /* The appsrc queue is flushed when the pipeline goes to NULL, so the + OUTPUT caps must be re-pushed before the next first buffer. */ + priv->out_caps_set = FALSE; + + for (i = 0; i < priv->cap.buffers_num; i++) { + if (priv->cap.buffers[i].state == + V4L_GST_BUFFER_DEQUEUED) { + gst_buffer_unref(priv->cap.buffers[i].gstbuf); + } + } + + g_queue_clear(priv->out.gstbufs_queue); + g_queue_clear(priv->cap.gstbufs_queue); + v4l_gst_core_reset_cap_timestamp_state(priv); + + v4l_gst_core_set_pipeline_started(priv, FALSE); + + if (priv->cap.buffers) { + g_free(priv->cap.buffers); + priv->cap.buffers = NULL; + } + priv->cap.buffers_num = 0; + v4l_gst_fmt_init_decoded_frame_params(&priv->cap.fmt); + + return ret; +} + + +static gboolean +relink_elements_with_caps_filtered(GstElement *src_elem, GstElement *dest_elem, + GstCaps *caps) +{ + gst_element_unlink(src_elem, dest_elem); + return gst_element_link_filtered(src_elem, dest_elem, caps); +} + + +gboolean +v4l_gst_pipeline_set_out_format(struct v4l_gst *priv) +{ + GstCaps *caps; + + if (priv->out.kind == V4L_GST_MEDIA_KIND_RAW) + caps = v4l_gst_pipeline_get_raw_caps_from_fmt(&priv->out.fmt); + else + caps = v4l_gst_pipeline_get_codec_caps_from_fourcc( + priv->out.fmt.pixelformat); + if (!caps) { + errno = EINVAL; + return FALSE; + } + + gst_app_src_set_caps(GST_APP_SRC(priv->appsrc), caps); + gst_caps_unref(caps); + + return TRUE; +} + + +gboolean +v4l_gst_pipeline_set_cap_format(struct v4l_gst *priv) +{ + GstElement *peer_elem; + GstCaps *caps; + gboolean ret; + + if (priv->cap.kind == V4L_GST_MEDIA_KIND_CODEC) { + caps = v4l_gst_pipeline_get_codec_caps_from_fourcc( + priv->cap.fmt.pixelformat); + } else { + GstVideoFormat fmt; + + fmt = fourcc_to_gst_video_format(priv->cap.fmt.pixelformat); + if (fmt == GST_VIDEO_FORMAT_UNKNOWN) { + gchar fourcc_str[5]; + fourcc_to_string(priv->cap.fmt.pixelformat, fourcc_str); + GST_ERROR("Invalid format on CAPTURE: %s (0x%x)", + fourcc_str, priv->cap.fmt.pixelformat); + errno = EINVAL; + return FALSE; + } + + caps = gst_caps_new_simple("video/x-raw", "format", + G_TYPE_STRING, + gst_video_format_to_string(fmt), + NULL); + } + + if (!caps) { + errno = EINVAL; + return FALSE; + } + + peer_elem = v4l_gst_core_get_peer_element(priv->appsink, "sink"); + if (!relink_elements_with_caps_filtered(peer_elem, priv->appsink, + caps)) { + GST_ERROR("Failed to relink elements with " + "the CAPTURE setting (caps=%s)", + gst_caps_to_string(caps)); + errno = EINVAL; + ret = FALSE; + goto free_objects; + } + GST_DEBUG("appsink element is relinked"); + + ret = TRUE; + + free_objects: + gst_caps_unref(caps); + gst_object_unref(peer_elem); + + return ret; +} + + + +#if 0 +static int +set_decoder_cmd_state(struct v4l_gst *priv, GstState state) +{ + int ret = 0; + GstStateChangeReturn state_ret; + g_mutex_lock(&priv->dev_lock); + + switch (state) { + case GST_STATE_PAUSED: + ret = gst_element_set_state(priv->pipeline, state); + while (state_ret == GST_STATE_CHANGE_ASYNC) { + /* This API blocks up to the ASYNC state change completion. */ + g_mutex_unlock(&priv->dev_lock); + state_ret = gst_element_get_state(priv->pipeline, NULL, + NULL, + GST_CLOCK_TIME_NONE); + g_mutex_lock(&priv->dev_lock); + } + + if (state_ret != GST_STATE_CHANGE_SUCCESS) { + GST_ERROR("Failed to stop pipeline (ret:%s)", + gst_element_state_change_return_get_name(state_ret)); + errno = EINVAL; + ret = -1; + } + g_mutex_unlock(&priv->dev_lock); + default: + GST_CAT_ERROR(v4l_gst_ioctl_debug_category, + "unsupported GstState to set %s", + gst_element_state_get_name(state)); + break; + } + return ret; +} + + +#endif +int +v4l_gst_ioctl_try_decoder_cmd(struct v4l_gst *priv, + struct v4l2_decoder_cmd *decoder_cmd) +{ + int ret = 0; + + switch (decoder_cmd->cmd) { + case V4L2_DEC_CMD_START: + GST_CAT_DEBUG(v4l_gst_ioctl_debug_category, + "v4l2_dec_cmd: V4L2_DEC_CMD_START speed: %d format: %x", + decoder_cmd->start.speed, decoder_cmd->start.format); + break; + case V4L2_DEC_CMD_STOP: + GST_CAT_DEBUG(v4l_gst_ioctl_debug_category, + "v4l2_dec_cmd: V4L2_DEC_CMD_STOP pts: %llu", + decoder_cmd->stop.pts); + break; + case V4L2_DEC_CMD_PAUSE: + GST_CAT_DEBUG(v4l_gst_ioctl_debug_category, + "v4l2_dec_cmd: V4L2_DEC_CMD_PAUSE"); + break; + case V4L2_DEC_CMD_RESUME: + GST_CAT_DEBUG(v4l_gst_ioctl_debug_category, + "v4l2_dec_cmd: V4L2_DEC_CMD_RESUME"); + break; + case V4L2_DEC_CMD_FLUSH: + GST_CAT_DEBUG(v4l_gst_ioctl_debug_category, + "v4l2_dec_cmd: V4L2_DEC_CMD_FLUSH"); + break; + default: + GST_CAT_DEBUG(v4l_gst_ioctl_debug_category, + "unsupported v4l2_decoder_cmd cmd: 0x%x", + decoder_cmd->cmd); + errno = EINVAL; + ret = -1; + break; + } + return ret; +} + + +int +v4l_gst_ioctl_decoder_cmd(struct v4l_gst *priv, struct v4l2_decoder_cmd *decoder_cmd) +{ + int ret = 0; + + g_mutex_lock(&priv->dev_lock); + + switch (decoder_cmd->cmd) { + case V4L2_DEC_CMD_START: + GST_CAT_DEBUG(v4l_gst_ioctl_debug_category, + "v4l2_decoder_cmd: V4L2_DEC_CMD_START " + "speed: %d format: %x", + decoder_cmd->start.speed, + decoder_cmd->start.format); + break; + case V4L2_DEC_CMD_STOP: + GST_CAT_DEBUG(v4l_gst_ioctl_debug_category, + "v4l2_decoder_cmd: V4L2_DEC_CMD_STOP pts: %llu", + decoder_cmd->stop.pts); + /* Clients send this command after queueing the last incoming + buffer. To detect the last decoded frame, we need to cache + an outgoing buffer and wait EOS event from the pipeline. + ref: https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/dev-decoder.html#drain + */ + if (priv->eos_state == EOS_NONE) { + GST_DEBUG("EOS: Send to AppSrc"); + priv->eos_state = EOS_WAITING_DECODE; + gst_app_src_end_of_stream(GST_APP_SRC(priv->appsrc)); + } + break; + case V4L2_DEC_CMD_PAUSE: + GST_CAT_DEBUG(v4l_gst_ioctl_debug_category, + "v4l2_decoder_cmd: V4L2_DEC_CMD_PAUSE"); + break; + case V4L2_DEC_CMD_RESUME: + GST_CAT_DEBUG(v4l_gst_ioctl_debug_category, + "v4l2_decoder_cmd: V4L2_DEC_CMD_RESUME"); + break; + case V4L2_DEC_CMD_FLUSH: + GST_CAT_DEBUG(v4l_gst_ioctl_debug_category, + "v4l2_decoder_cmd: V4L2_DEC_CMD_FLUSH"); + break; + default: + GST_CAT_ERROR(v4l_gst_ioctl_debug_category, + "unsupported VIDIOC_DECODER_CMD " + "v4l2_decoder_cmd: cmd: 0x%x flags: 0x%x", + decoder_cmd->cmd, decoder_cmd->flags); + errno = EINVAL; + ret = -1; + break; + } + + g_mutex_unlock(&priv->dev_lock); + + return ret; +} diff --git a/lib/libv4l-gst/v4l-gst-testhooks.c b/lib/libv4l-gst/v4l-gst-testhooks.c new file mode 100644 index 0000000..d3659a5 --- /dev/null +++ b/lib/libv4l-gst/v4l-gst-testhooks.c @@ -0,0 +1,158 @@ +#include "config.h" + +#ifdef UNIT_TESTS + +#include "v4l-gst-testhooks.h" +#include "v4l-gst-internal.h" + +GstElement * +test_create_pipeline(const gchar *pipeline_str) +{ + return v4l_gst_core_create_pipeline(pipeline_str); +} + +static void +append_test_format(GArray *formats, guint fourcc, const gchar *description) +{ + struct fmt fmt = { 0, }; + + fmt.fourcc = fourcc; + g_strlcpy(fmt.desc, description, FMTDESC_NAME_LENGTH); + g_array_append_val(formats, fmt); +} + +void +prepare_format_backend_fixture(struct v4l_gst *priv) +{ + if (!priv) + return; + if (!priv->pipeline) + priv->pipeline = v4l_gst_core_create_pipeline("identity"); + append_test_format(priv->cap.supported_fmts, V4L2_PIX_FMT_NV12, "NV12"); + + priv->out.fmt.pixelformat = V4L2_PIX_FMT_H264; + priv->out.fmt.plane_fmt[0].sizeimage = 1024; + priv->cap.fmt.pixelformat = V4L2_PIX_FMT_NV12; + priv->cap.fmt.width = 640; + priv->cap.fmt.height = 480; + priv->cap.fmt.num_planes = 1; + priv->cap.fmt.plane_fmt[0].bytesperline = 640; + priv->cap.fmt.plane_fmt[0].sizeimage = 640 * 480 * 3 / 2; + g_atomic_int_set(&priv->cap.fmt_acquirable, 1); + priv->out.cnt = INPUT_BUFFERING_CNT; +} + +/* + * Reconfigure the backend of a decode-only config into an encoder-only role: + * the decode pipeline is dropped, an encode pipeline is added and the role + * is re-derived. The GStreamer pipeline itself is kept (already created as + * "identity" by prepare_format_backend_fixture()). + */ +void +prepare_encode_only_role_backend_fixture(struct v4l_gst *priv) +{ + g_hash_table_remove(priv->config.pipelines, "H264"); + g_hash_table_insert(priv->config.encode_pipelines, g_strdup("H264"), + g_strdup("identity")); + v4l_gst_core_setup_role(priv); + + priv->out.fmt.pixelformat = V4L2_PIX_FMT_NV12; + priv->out.fmt.width = 640; + priv->out.fmt.height = 480; + priv->out.fmt.num_planes = 1; + priv->out.fmt.plane_fmt[0].bytesperline = 640; + priv->out.fmt.plane_fmt[0].sizeimage = 640 * 480 * 3 / 2; + priv->cap.fmt.pixelformat = V4L2_PIX_FMT_H264; + priv->cap.fmt.plane_fmt[0].sizeimage = 1024; + g_atomic_int_set(&priv->cap.fmt_acquirable, 1); + priv->out.cnt = INPUT_BUFFERING_CNT; +} + +/* + * Reconfigure the backend of a decode-only config into the dual role: both + * the decode and the encode pipeline are configured, so the role stays + * V4L_GST_ROLE_NONE until the first OUTPUT format fixes it. + */ +void +prepare_dual_role_backend_fixture(struct v4l_gst *priv) +{ + g_hash_table_insert(priv->config.encode_pipelines, g_strdup("H264"), + g_strdup("identity")); + v4l_gst_core_setup_role(priv); + + priv->out.fmt.pixelformat = V4L2_PIX_FMT_H264; + priv->out.fmt.plane_fmt[0].sizeimage = 1024; + priv->cap.fmt.pixelformat = V4L2_PIX_FMT_NV12; + priv->cap.fmt.width = 640; + priv->cap.fmt.height = 480; + priv->cap.fmt.num_planes = 1; + priv->cap.fmt.plane_fmt[0].bytesperline = 640; + priv->cap.fmt.plane_fmt[0].sizeimage = 640 * 480 * 3 / 2; + g_atomic_int_set(&priv->cap.fmt_acquirable, 1); + priv->out.cnt = INPUT_BUFFERING_CNT; +} + +void +prepare_pipeline_backend_fixture(struct v4l_gst *priv, + enum v4l_gst_role role, + const gchar *pipeline, + guint32 codec_fourcc, + guint32 raw_fourcc, + guint32 raw_width, + guint32 raw_height, + guint32 raw_sizeimage, + guint32 stream_sizeimage) +{ + gchar fourcc_str[5]; + + fourcc_to_string(codec_fourcc, fourcc_str); + + if (priv->pipeline) { + gst_object_unref(priv->pipeline); + priv->pipeline = NULL; + } + + g_hash_table_remove(priv->config.pipelines, fourcc_str); + g_hash_table_remove(priv->config.encode_pipelines, fourcc_str); + if (role == V4L_GST_ROLE_ENCODER) + g_hash_table_insert(priv->config.encode_pipelines, + g_strdup(fourcc_str), g_strdup(pipeline)); + else + g_hash_table_insert(priv->config.pipelines, + g_strdup(fourcc_str), g_strdup(pipeline)); + v4l_gst_core_setup_role(priv); + + if (role == V4L_GST_ROLE_ENCODER) { + priv->out.fmt.pixelformat = raw_fourcc; + priv->out.fmt.width = raw_width; + priv->out.fmt.height = raw_height; + priv->out.fmt.num_planes = 1; + priv->out.fmt.plane_fmt[0].bytesperline = raw_width; + priv->out.fmt.plane_fmt[0].sizeimage = raw_sizeimage; + priv->cap.fmt.pixelformat = codec_fourcc; + priv->cap.fmt.num_planes = 1; + priv->cap.fmt.plane_fmt[0].sizeimage = stream_sizeimage; + } else { + priv->out.fmt.pixelformat = codec_fourcc; + priv->out.fmt.width = raw_width; + priv->out.fmt.height = raw_height; + priv->out.fmt.num_planes = 1; + priv->out.fmt.plane_fmt[0].sizeimage = stream_sizeimage; + priv->cap.fmt.pixelformat = raw_fourcc; + priv->cap.fmt.width = raw_width; + priv->cap.fmt.height = raw_height; + priv->cap.fmt.num_planes = 1; + priv->cap.fmt.plane_fmt[0].bytesperline = raw_width; + priv->cap.fmt.plane_fmt[0].sizeimage = raw_sizeimage; + } + g_atomic_int_set(&priv->cap.fmt_acquirable, 1); + priv->out.cnt = INPUT_BUFFERING_CNT; +} + +enum v4l_gst_role +get_backend_role(struct v4l_gst *priv) +{ + return priv->role; +} + +#endif diff --git a/lib/libv4l-gst/v4l-gst-testhooks.h b/lib/libv4l-gst/v4l-gst-testhooks.h new file mode 100644 index 0000000..79ef3a1 --- /dev/null +++ b/lib/libv4l-gst/v4l-gst-testhooks.h @@ -0,0 +1,30 @@ +#ifndef __V4L_GST_TESTHOOKS_H__ +#define __V4L_GST_TESTHOOKS_H__ + +#include "config.h" + +#ifdef UNIT_TESTS +#include +#include + +#include "v4l-gst-internal.h" + +struct v4l_gst; + +GstElement *test_create_pipeline(const gchar *pipeline_str); +void prepare_format_backend_fixture(struct v4l_gst *priv); +void prepare_encode_only_role_backend_fixture(struct v4l_gst *priv); +void prepare_dual_role_backend_fixture(struct v4l_gst *priv); +void prepare_pipeline_backend_fixture(struct v4l_gst *priv, + enum v4l_gst_role role, + const gchar *pipeline, + guint32 codec_fourcc, + guint32 raw_fourcc, + guint32 raw_width, + guint32 raw_height, + guint32 raw_sizeimage, + guint32 stream_sizeimage); +enum v4l_gst_role get_backend_role(struct v4l_gst *priv); +#endif + +#endif /* __V4L_GST_TESTHOOKS_H__ */ diff --git a/lib/libv4l-gst/v4l-gst.h b/lib/libv4l-gst/v4l-gst.h new file mode 100644 index 0000000..415cd66 --- /dev/null +++ b/lib/libv4l-gst/v4l-gst.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2015 Renesas Electronics Corporation + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA + */ + +#ifndef __V4L_GST_H__ +#define __V4L_GST_H__ + +#include + +struct v4l_gst; + +/* + * Public backend API, grouped by domain. Each domain is implemented in + * v4l-gst-.c; cross-TU helpers are declared in + * v4l-gst-internal.h. + */ + +/* core: backend lifecycle */ +struct v4l_gst * v4l_gst_init (int fd); +void v4l_gst_deinit (struct v4l_gst *priv); + +/* pipeline: decoder command */ +int v4l_gst_ioctl_try_decoder_cmd (struct v4l_gst *priv, + struct v4l2_decoder_cmd *decoder_cmd); +int v4l_gst_ioctl_decoder_cmd (struct v4l_gst *priv, + struct v4l2_decoder_cmd *decoder_cmd); + +/* fmt: format */ +int v4l_gst_ioctl_querycap (struct v4l_gst *priv, + struct v4l2_capability *cap); +int v4l_gst_ioctl_set_fmt (struct v4l_gst *priv, + struct v4l2_format *fmt); +int v4l_gst_ioctl_get_fmt (struct v4l_gst *priv, + struct v4l2_format *fmt); +int v4l_gst_ioctl_enum_fmt (struct v4l_gst *priv, + struct v4l2_fmtdesc *desc); +int v4l_gst_ioctl_enum_framesizes (struct v4l_gst *priv, + struct v4l2_frmsizeenum *argp); +int v4l_gst_ioctl_g_selection (struct v4l_gst *priv, + struct v4l2_selection *selection); +int v4l_gst_ioctl_g_crop (struct v4l_gst *priv, + struct v4l2_crop *crop); +int v4l_gst_ioctl_try_fmt (struct v4l_gst *priv, + struct v4l2_format *format); + +/* buf: buffer */ +int v4l_gst_ioctl_qbuf (struct v4l_gst *priv, + struct v4l2_buffer *buf); +int v4l_gst_ioctl_dqbuf (struct v4l_gst *priv, + struct v4l2_buffer *buf); +int v4l_gst_ioctl_querybuf (struct v4l_gst *priv, + struct v4l2_buffer *buf); +int v4l_gst_ioctl_reqbuf (struct v4l_gst *priv, + struct v4l2_requestbuffers *req); +int v4l_gst_ioctl_streamon (struct v4l_gst *priv, + enum v4l2_buf_type *type); +int v4l_gst_ioctl_streamoff (struct v4l_gst *priv, + enum v4l2_buf_type *type); +int v4l_gst_ioctl_expbuf (struct v4l_gst *priv, + struct v4l2_exportbuffer *buf); +void * v4l_gst_mmap (struct v4l_gst *priv, + void *start, + size_t length, + int prot, + int flags, + int fd, + int64_t offset); + +/* ctrl: control */ +int v4l_gst_ioctl_get_ctrl (struct v4l_gst *priv, + struct v4l2_control *ctrl); +int v4l_gst_ioctl_get_ext_ctrl (struct v4l_gst *priv, + struct v4l2_ext_controls *ext_ctrls); +int v4l_gst_ioctl_queryctrl (struct v4l_gst *priv, + struct v4l2_queryctrl *query_ctrl); +int v4l_gst_ioctl_querymenu (struct v4l_gst *priv, + struct v4l2_querymenu *query_menu); + +/* event: event */ +int v4l_gst_ioctl_subscribe_event (struct v4l_gst *priv, + struct v4l2_event_subscription *sub); +int v4l_gst_ioctl_dqevent (struct v4l_gst *priv, + struct v4l2_event *ev); +int v4l_gst_ioctl_unsubscribe_event (struct v4l_gst *priv, + struct v4l2_event_subscription *subscription); + +#define ENV_DISABLE_VIDIOC_FEATURES "DISABLE_VIDIOC_FEATURES" + +#endif /* __V4L_GST_H__ */ diff --git a/tests/meson.build b/tests/meson.build index 868c9a7..d914114 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -25,7 +25,13 @@ if cutter_dep.found() test_gst_backend = shared_module('test-gst-backend', 'test-gst-backend.c', plugin_srcdir / 'libv4l-gst.c', - plugin_srcdir / 'gst-backend.c', + plugin_srcdir / 'v4l-gst-core.c', + plugin_srcdir / 'v4l-gst-pipeline.c', + plugin_srcdir / 'v4l-gst-fmt.c', + plugin_srcdir / 'v4l-gst-buf.c', + plugin_srcdir / 'v4l-gst-ctrl.c', + plugin_srcdir / 'v4l-gst-event.c', + plugin_srcdir / 'v4l-gst-testhooks.c', plugin_srcdir / 'evfd-ctrl.c', plugin_srcdir / 'utils.c', include_directories: [inc_buildroot, inc_libinclude, inc_plugin], diff --git a/tests/test-gst-backend.c b/tests/test-gst-backend.c index 888de36..d7304ac 100644 --- a/tests/test-gst-backend.c +++ b/tests/test-gst-backend.c @@ -1,8 +1,9 @@ -#include "gst-backend-testhooks.h" +#include "v4l-gst-testhooks.h" #include #include #include #include +#include #include #include "utils.h" @@ -914,3 +915,901 @@ test_get_fmt_rejects_single_planar_type(void) assert_equal_result_strings(expected_string, actual_string); } + +static const gchar * +role_to_string(enum v4l_gst_role role) +{ + switch (role) { + case V4L_GST_ROLE_NONE: + return "none"; + case V4L_GST_ROLE_DECODER: + return "decoder"; + case V4L_GST_ROLE_ENCODER: + return "encoder"; + } + return "unknown"; +} + +static void +assert_role(enum v4l_gst_role expected) +{ + const gchar *expected_str = role_to_string(expected); + const gchar *actual_str = role_to_string(get_backend_role(backend)); + + cut_assert_equal_string(expected_str, actual_str, + cut_message("expected role %s, got %s", + expected_str, actual_str)); +} + +void +test_role_is_decoder_with_decode_only_config(void) +{ + assert_role(V4L_GST_ROLE_DECODER); +} + +void +test_role_is_encoder_with_encode_only_config(void) +{ + prepare_encode_only_role_backend_fixture(backend); + + assert_role(V4L_GST_ROLE_ENCODER); +} + +void +test_encode_only_enum_fmt_output_is_raw(void) +{ + struct v4l2_fmtdesc desc = { 0, }; + int ret; + struct enum_fmt_result expected = { + .ret = 0, + .pixelformat = V4L2_PIX_FMT_NV12, + .flags = 0, + }; + struct enum_fmt_result actual; + const gchar *expected_string = + cut_take_string(enum_fmt_result_to_string(&expected)); + const gchar *actual_string; + + prepare_encode_only_role_backend_fixture(backend); + + desc.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_ENUM_FMT, &desc); + actual = snapshot_enum_fmt_result(ret, errno, &desc); + actual_string = cut_take_string(enum_fmt_result_to_string(&actual)); + + assert_equal_result_strings(expected_string, actual_string); +} + +void +test_encode_only_enum_fmt_output_rejects_second_index(void) +{ + struct v4l2_fmtdesc desc = { 0, }; + int ret; + struct enum_fmt_result expected = { + .ret = -1, + .error_number = EINVAL, + .flags = V4L2_FMT_FLAG_COMPRESSED, + }; + struct enum_fmt_result actual; + const gchar *expected_string = + cut_take_string(enum_fmt_result_to_string(&expected)); + const gchar *actual_string; + + prepare_encode_only_role_backend_fixture(backend); + + desc.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + desc.index = 1; + + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_ENUM_FMT, &desc); + actual = snapshot_enum_fmt_result(ret, errno, &desc); + actual_string = cut_take_string(enum_fmt_result_to_string(&actual)); + + assert_equal_result_strings(expected_string, actual_string); +} + +void +test_encode_only_enum_fmt_capture_is_compressed(void) +{ + struct v4l2_fmtdesc desc = { 0, }; + int ret; + struct enum_fmt_result expected = { + .ret = 0, + .pixelformat = V4L2_PIX_FMT_H264, + .flags = V4L2_FMT_FLAG_COMPRESSED, + }; + struct enum_fmt_result actual; + const gchar *expected_string = + cut_take_string(enum_fmt_result_to_string(&expected)); + const gchar *actual_string; + + prepare_encode_only_role_backend_fixture(backend); + + desc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_ENUM_FMT, &desc); + actual = snapshot_enum_fmt_result(ret, errno, &desc); + actual_string = cut_take_string(enum_fmt_result_to_string(&actual)); + + assert_equal_result_strings(expected_string, actual_string); +} + +void +test_encode_only_set_fmt_output_keeps_raw_dimensions(void) +{ + struct v4l2_format format = { 0, }; + struct v4l2_pix_format_mplane *pix = &format.fmt.pix_mp; + int ret; + struct pix_format_result expected = { + .ret = 0, + .error_number = 0, + .pixelformat = V4L2_PIX_FMT_NV12, + .width = 640, + .height = 480, + .num_planes = 1, + .bytesperline = 640, + .sizeimage = 640 * 480 * 3 / 2, + }; + struct pix_format_result actual; + const gchar *expected_string; + const gchar *actual_string; + + prepare_encode_only_role_backend_fixture(backend); + + format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + pix->pixelformat = V4L2_PIX_FMT_NV12; + pix->width = 640; + pix->height = 480; + pix->num_planes = 1; + pix->plane_fmt[0].bytesperline = 640; + pix->plane_fmt[0].sizeimage = 640 * 480 * 3 / 2; + + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_S_FMT, &format); + actual = snapshot_pix_format(ret, errno, &format); + expected_string = cut_take_string(pix_format_result_to_string(&expected)); + actual_string = cut_take_string(pix_format_result_to_string(&actual)); + + assert_equal_result_strings(expected_string, actual_string); +} + +void +test_encode_only_set_fmt_capture_accepts_codec(void) +{ + struct v4l2_format format = { 0, }; + int ret; + struct pix_format_result expected = { + .ret = 0, + .error_number = 0, + .pixelformat = V4L2_PIX_FMT_H264, + .sizeimage = 1024, + }; + struct pix_format_result actual; + const gchar *expected_string; + const gchar *actual_string; + + prepare_encode_only_role_backend_fixture(backend); + + format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + format.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_H264; + format.fmt.pix_mp.plane_fmt[0].sizeimage = 1024; + + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_S_FMT, &format); + actual = snapshot_pix_format(ret, errno, &format); + expected_string = cut_take_string(pix_format_result_to_string(&expected)); + actual_string = cut_take_string(pix_format_result_to_string(&actual)); + + assert_equal_result_strings(expected_string, actual_string); +} + +void +test_encode_only_get_fmt_output_keeps_raw_dimensions(void) +{ + struct v4l2_format format = { 0, }; + int ret; + struct pix_format_result expected = { + .ret = 0, + .error_number = 0, + .pixelformat = V4L2_PIX_FMT_NV12, + .width = 640, + .height = 480, + .num_planes = 1, + .bytesperline = 640, + .sizeimage = 640 * 480 * 3 / 2, + }; + struct pix_format_result actual; + const gchar *expected_string; + const gchar *actual_string; + + prepare_encode_only_role_backend_fixture(backend); + + format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_G_FMT, &format); + actual = snapshot_pix_format(ret, errno, &format); + expected_string = cut_take_string(pix_format_result_to_string(&expected)); + actual_string = cut_take_string(pix_format_result_to_string(&actual)); + + assert_equal_result_strings(expected_string, actual_string); +} + +void +test_dual_role_stays_none_until_output_format(void) +{ + prepare_dual_role_backend_fixture(backend); + + assert_role(V4L_GST_ROLE_NONE); +} + +void +test_dual_enum_fmt_output_lists_codec_then_raw(void) +{ + struct v4l2_fmtdesc desc = { 0, }; + int ret; + struct enum_fmt_result expected; + struct enum_fmt_result actual; + const gchar *expected_string; + const gchar *actual_string; + + prepare_dual_role_backend_fixture(backend); + + desc.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + desc.index = 0; + expected = (struct enum_fmt_result) { + .ret = 0, + .pixelformat = V4L2_PIX_FMT_H264, + .flags = V4L2_FMT_FLAG_COMPRESSED, + }; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_ENUM_FMT, &desc); + actual = snapshot_enum_fmt_result(ret, errno, &desc); + expected_string = cut_take_string(enum_fmt_result_to_string(&expected)); + actual_string = cut_take_string(enum_fmt_result_to_string(&actual)); + assert_equal_result_strings(expected_string, actual_string); + + desc.index = 1; + expected = (struct enum_fmt_result) { + .ret = 0, + .pixelformat = V4L2_PIX_FMT_NV12, + .flags = 0, + }; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_ENUM_FMT, &desc); + actual = snapshot_enum_fmt_result(ret, errno, &desc); + expected_string = cut_take_string(enum_fmt_result_to_string(&expected)); + actual_string = cut_take_string(enum_fmt_result_to_string(&actual)); + assert_equal_result_strings(expected_string, actual_string); + + desc.index = 2; + expected = (struct enum_fmt_result) { + .ret = -1, + .error_number = EINVAL, + .pixelformat = V4L2_PIX_FMT_NV12, + .flags = V4L2_FMT_FLAG_COMPRESSED, + }; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_ENUM_FMT, &desc); + actual = snapshot_enum_fmt_result(ret, errno, &desc); + expected_string = cut_take_string(enum_fmt_result_to_string(&expected)); + actual_string = cut_take_string(enum_fmt_result_to_string(&actual)); + assert_equal_result_strings(expected_string, actual_string); +} + +void +test_dual_enum_fmt_capture_lists_raw_then_codec(void) +{ + struct v4l2_fmtdesc desc = { 0, }; + int ret; + struct enum_fmt_result expected; + struct enum_fmt_result actual; + const gchar *expected_string; + const gchar *actual_string; + + prepare_dual_role_backend_fixture(backend); + + desc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + desc.index = 0; + expected = (struct enum_fmt_result) { + .ret = 0, + .pixelformat = V4L2_PIX_FMT_NV12, + .flags = 0, + }; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_ENUM_FMT, &desc); + actual = snapshot_enum_fmt_result(ret, errno, &desc); + expected_string = cut_take_string(enum_fmt_result_to_string(&expected)); + actual_string = cut_take_string(enum_fmt_result_to_string(&actual)); + assert_equal_result_strings(expected_string, actual_string); + + desc.index = 1; + expected = (struct enum_fmt_result) { + .ret = 0, + .pixelformat = V4L2_PIX_FMT_H264, + .flags = V4L2_FMT_FLAG_COMPRESSED, + }; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_ENUM_FMT, &desc); + actual = snapshot_enum_fmt_result(ret, errno, &desc); + expected_string = cut_take_string(enum_fmt_result_to_string(&expected)); + actual_string = cut_take_string(enum_fmt_result_to_string(&actual)); + assert_equal_result_strings(expected_string, actual_string); + + desc.index = 2; + expected = (struct enum_fmt_result) { + .ret = -1, + .error_number = EINVAL, + .pixelformat = V4L2_PIX_FMT_H264, + }; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_ENUM_FMT, &desc); + actual = snapshot_enum_fmt_result(ret, errno, &desc); + expected_string = cut_take_string(enum_fmt_result_to_string(&expected)); + actual_string = cut_take_string(enum_fmt_result_to_string(&actual)); + assert_equal_result_strings(expected_string, actual_string); +} + +void +test_dual_set_fmt_output_codec_fixes_decoder_role(void) +{ + struct v4l2_format format = { 0, }; + struct v4l2_pix_format_mplane *pix = &format.fmt.pix_mp; + int ret; + struct pix_format_result expected = { + .ret = 0, + .error_number = 0, + .pixelformat = V4L2_PIX_FMT_H264, + .width = 0, + .height = 0, + .num_planes = 1, + .sizeimage = 2048, + }; + struct pix_format_result actual; + const gchar *expected_string; + const gchar *actual_string; + + prepare_dual_role_backend_fixture(backend); + + format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + pix->pixelformat = V4L2_PIX_FMT_H264; + pix->plane_fmt[0].sizeimage = 2048; + + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_S_FMT, &format); + actual = snapshot_pix_format(ret, errno, &format); + expected_string = cut_take_string(pix_format_result_to_string(&expected)); + actual_string = cut_take_string(pix_format_result_to_string(&actual)); + + assert_equal_result_strings(expected_string, actual_string); + assert_role(V4L_GST_ROLE_DECODER); +} + +void +test_dual_set_fmt_output_raw_fixes_encoder_role(void) +{ + struct v4l2_format format = { 0, }; + struct v4l2_pix_format_mplane *pix = &format.fmt.pix_mp; + int ret; + struct pix_format_result expected = { + .ret = 0, + .error_number = 0, + .pixelformat = V4L2_PIX_FMT_NV12, + .width = 640, + .height = 480, + .num_planes = 1, + .bytesperline = 640, + .sizeimage = 640 * 480 * 3 / 2, + }; + struct pix_format_result actual; + const gchar *expected_string; + const gchar *actual_string; + + prepare_dual_role_backend_fixture(backend); + + format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + pix->pixelformat = V4L2_PIX_FMT_NV12; + pix->width = 640; + pix->height = 480; + pix->num_planes = 1; + pix->plane_fmt[0].bytesperline = 640; + pix->plane_fmt[0].sizeimage = 640 * 480 * 3 / 2; + + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_S_FMT, &format); + actual = snapshot_pix_format(ret, errno, &format); + expected_string = cut_take_string(pix_format_result_to_string(&expected)); + actual_string = cut_take_string(pix_format_result_to_string(&actual)); + + assert_equal_result_strings(expected_string, actual_string); + assert_role(V4L_GST_ROLE_ENCODER); +} + +void +test_dual_set_fmt_capture_rejected_before_role_fixed(void) +{ + struct v4l2_format format = { 0, }; + int ret; + struct pix_format_result expected = { + .ret = -1, + .error_number = EINVAL, + .pixelformat = V4L2_PIX_FMT_H264, + }; + struct pix_format_result actual; + const gchar *expected_string; + const gchar *actual_string; + + prepare_dual_role_backend_fixture(backend); + + format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + format.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_H264; + + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_S_FMT, &format); + actual = snapshot_pix_format(ret, errno, &format); + expected_string = cut_take_string(pix_format_result_to_string(&expected)); + actual_string = cut_take_string(pix_format_result_to_string(&actual)); + + assert_equal_result_strings(expected_string, actual_string); + assert_role(V4L_GST_ROLE_NONE); +} + +static void +fill_nv12_frame(void *data, guint32 size) +{ + guint8 *p = data; + const guint32 y_size = 640 * 480; + guint32 i; + + for (i = 0; i < y_size && i < size; i++) + p[i] = i & 0xff; + for (i = y_size; i < size; i++) + p[i] = 0x80; +} + +static gboolean +contains_h264_start_code(const guint8 *data, gsize size) +{ + gsize i; + + for (i = 0; i + 4 <= size; i++) { + if (data[i] == 0x00 && data[i + 1] == 0x00 && + data[i + 2] == 0x00 && data[i + 3] == 0x01) + return TRUE; + } + return FALSE; +} + +/* + * Produce a self-contained H264 annex-B byte stream by encoding a few + * synthetic NV12 frames with x264enc. The stream starts with an inline + * SPS/PPS/IDR (key-int-max=1, zerolatency) so that avdec_h264 can decode it + * without an h264parse element. Returns a newly allocated buffer (free with + * g_free()) or NULL on failure. + */ +static guint8 * +generate_h264_sample(guint32 n_frames, guint32 width, guint32 height, + gsize *out_len) +{ + gchar *desc; + GstElement *pipeline; + GstElement *sink; + GstSample *sample; + GstBuffer *buffer; + GstMapInfo map; + GByteArray *stream; + + *out_len = 0; + + desc = g_strdup_printf( + "videotestsrc num-buffers=%u is-live=false " + "! videoconvert ! video/x-raw,format=NV12,width=%u,height=%u,framerate=30/1 " + "! x264enc tune=zerolatency speed-preset=ultrafast key-int-max=1 " + "! appsink name=h264enc_sink", n_frames, width, height); + pipeline = gst_parse_launch(desc, NULL); + g_free(desc); + if (!pipeline) + return NULL; + + sink = gst_bin_get_by_name(GST_BIN(pipeline), "h264enc_sink"); + if (!sink) { + gst_object_unref(pipeline); + return NULL; + } + + if (gst_element_set_state(pipeline, GST_STATE_PLAYING) == + GST_STATE_CHANGE_FAILURE) { + gst_object_unref(sink); + gst_object_unref(pipeline); + return NULL; + } + + stream = g_byte_array_new(); + while ((sample = gst_app_sink_try_pull_sample(GST_APP_SINK(sink), + GST_CLOCK_TIME_NONE)) != NULL) { + buffer = gst_sample_get_buffer(sample); + if (buffer && gst_buffer_map(buffer, &map, GST_MAP_READ)) { + g_byte_array_append(stream, map.data, map.size); + gst_buffer_unmap(buffer, &map); + } + gst_sample_unref(sample); + } + + gst_object_unref(sink); + gst_element_set_state(pipeline, GST_STATE_NULL); + gst_object_unref(pipeline); + + if (stream->len == 0) { + g_byte_array_free(stream, TRUE); + return NULL; + } + *out_len = stream->len; + return g_byte_array_free(stream, FALSE); +} + +void +test_x264enc_encoder_streaming_produces_h264(void) +{ + struct v4l2_format format = { 0, }; + struct v4l2_requestbuffers req = { 0, }; + struct v4l2_buffer buf = { 0, }; + struct v4l2_plane planes[VIDEO_MAX_PLANES]; + enum v4l2_buf_type type; + const guint32 w = 640; + const guint32 h = 480; + const guint32 frame_size = 640 * 480 * 3 / 2; + guint out_count, cap_count, i; + int ret; + + if (!gst_element_factory_find("x264enc")) + cut_pend("x264enc element is not available"); + + prepare_pipeline_backend_fixture(backend, V4L_GST_ROLE_ENCODER, + "x264enc tune=zerolatency " + "speed-preset=ultrafast", + V4L2_PIX_FMT_H264, V4L2_PIX_FMT_NV12, + w, h, frame_size, 1024 * 1024); + assert_role(V4L_GST_ROLE_ENCODER); + + /* Set the OUTPUT format to a raw NV12 frame. */ + format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + format.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12; + format.fmt.pix_mp.width = w; + format.fmt.pix_mp.height = h; + format.fmt.pix_mp.num_planes = 1; + format.fmt.pix_mp.plane_fmt[0].bytesperline = w; + format.fmt.pix_mp.plane_fmt[0].sizeimage = frame_size; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_S_FMT, &format); + cut_assert(ret == 0); + + /* Set the CAPTURE format to an H264 encoded stream. */ + memset(&format, 0, sizeof(format)); + format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + format.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_H264; + format.fmt.pix_mp.plane_fmt[0].sizeimage = 1024 * 1024; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_S_FMT, &format); + cut_assert(ret == 0); + + /* Request OUTPUT (raw) buffers. */ + memset(&req, 0, sizeof(req)); + req.count = INPUT_BUFFERING_CNT; + req.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + req.memory = V4L2_MEMORY_MMAP; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_REQBUFS, &req); + cut_assert(ret == 0); + out_count = req.count; + cut_assert(out_count > 0); + + /* Fill each OUTPUT buffer with an NV12 frame and queue it. */ + for (i = 0; i < out_count; i++) { + void *map; + + memset(&buf, 0, sizeof(buf)); + buf.index = i; + buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + buf.memory = V4L2_MEMORY_MMAP; + buf.m.planes = planes; + buf.length = 2; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_QUERYBUF, &buf); + cut_assert(ret == 0); + + map = libv4l2_plugin.mmap(backend, NULL, + buf.m.planes[0].length, + PROT_READ | PROT_WRITE, + MAP_SHARED, -1, + (int64_t) buf.m.planes[0].m.mem_offset); + cut_assert_not_null(map); + fill_nv12_frame(map, frame_size); + buf.m.planes[0].bytesused = frame_size; + + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_QBUF, &buf); + cut_assert(ret == 0); + } + + /* Start the stream. */ + type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_STREAMON, &type); + cut_assert(ret == 0); + + /* Request CAPTURE buffers; blocks until the first encoded frames are + available from the pipeline. */ + memset(&req, 0, sizeof(req)); + req.count = 4; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + req.memory = V4L2_MEMORY_MMAP; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_REQBUFS, &req); + cut_assert(ret == 0); + cap_count = req.count; + cut_assert(cap_count > 0); + + /* Queue the CAPTURE buffers. */ + for (i = 0; i < cap_count; i++) { + memset(&buf, 0, sizeof(buf)); + buf.index = i; + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + buf.memory = V4L2_MEMORY_MMAP; + buf.m.planes = planes; + buf.length = 1; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_QBUF, &buf); + cut_assert(ret == 0); + } + + /* Dequeue CAPTURE buffers and verify they carry an H264 bitstream. */ + for (i = 0; i < cap_count; i++) { + void *map; + + memset(&buf, 0, sizeof(buf)); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + buf.memory = V4L2_MEMORY_MMAP; + buf.m.planes = planes; + buf.length = 1; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_DQBUF, &buf); + cut_assert(ret == 0); + cut_assert(buf.m.planes[0].bytesused > 0); + + map = libv4l2_plugin.mmap(backend, NULL, + buf.m.planes[0].length, + PROT_READ, MAP_SHARED, -1, + (int64_t) buf.m.planes[0].m.mem_offset); + cut_assert_not_null(map); + cut_assert(contains_h264_start_code( + (const guint8 *) map, + buf.m.planes[0].bytesused)); + } + + /* Stop the stream. */ + type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_STREAMOFF, &type); + cut_assert(ret == 0); +} + +/* + * Decode a self-contained H264 annex-B byte stream (produced by + * generate_h264_sample()) with the real avdec_h264 pipeline and verify that + * the CAPTURE side yields raw NV12 frames with monotonically non-decreasing + * timestamps. VIDIOC_DECODER_CMD with V4L2_DEC_CMD_STOP signals end of + * stream so the decoder is exercised through its EOS/flush path and the + * final CAPTURE buffer is returned with V4L2_BUF_FLAG_LAST. + */ +void +test_h264dec_decoder_streaming_produces_nv12(void) +{ + struct v4l2_format format = { 0, }; + struct v4l2_requestbuffers req = { 0, }; + struct v4l2_buffer buf = { 0, }; + struct v4l2_plane planes[VIDEO_MAX_PLANES]; + enum v4l2_buf_type type; + const guint32 w = 320; + const guint32 h = 240; + const gsize frame_size = w * h * 3 / 2; + /* The CAPTURE request below blocks until the decoder fills its output + buffer pool, so feed more frames than the pool can hold to guarantee + the pool is filled. key-int-max=1 keeps the stream compact. */ + const guint32 n_frames = 100; + gsize stream_len = 0; + guint8 *stream; + guint out_count, cap_count, i; + guint frame_count = 0; + const guint max_frames = 1024; + gboolean got_last = FALSE; + gint64 last_sec = -1, last_usec = 0; + int ret; + + /* The decode pipeline is "h264parse ! avdec_h264 ! videoconvert" + (per README, h264parse extracts the SPS/PPS into the codec_data caps + that avdec_h264 needs; videoconvert converts the decoder's I420 + output to the NV12 CAPTURE format). All four elements are required. */ + if (!gst_element_factory_find("h264parse") || + !gst_element_factory_find("avdec_h264") || + !gst_element_factory_find("videoconvert") || + !gst_element_factory_find("x264enc")) + cut_pend("h264parse, avdec_h264, videoconvert or x264enc " + "element is not available"); + + stream = generate_h264_sample(n_frames, w, h, &stream_len); + if (!stream) + cut_pend("failed to generate an h264 sample stream"); + + prepare_pipeline_backend_fixture(backend, V4L_GST_ROLE_DECODER, + "h264parse ! avdec_h264 ! videoconvert", + V4L2_PIX_FMT_H264, V4L2_PIX_FMT_NV12, + w, h, frame_size, 4 * 1024 * 1024); + assert_role(V4L_GST_ROLE_DECODER); + + /* Set the OUTPUT format to the H264 encoded stream. */ + format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + format.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_H264; + format.fmt.pix_mp.plane_fmt[0].sizeimage = 4 * 1024 * 1024; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_S_FMT, &format); + cut_assert(ret == 0); + + /* Set the CAPTURE format to a raw NV12 frame. */ + memset(&format, 0, sizeof(format)); + format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + format.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12; + format.fmt.pix_mp.width = w; + format.fmt.pix_mp.height = h; + format.fmt.pix_mp.num_planes = 1; + format.fmt.pix_mp.plane_fmt[0].bytesperline = w; + format.fmt.pix_mp.plane_fmt[0].sizeimage = frame_size; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_S_FMT, &format); + cut_assert(ret == 0); + + /* Request OUTPUT buffers; the whole stream is carried in the first + buffer. End of stream is signalled with V4L2_DEC_CMD_STOP. */ + memset(&req, 0, sizeof(req)); + req.count = 1; + req.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + req.memory = V4L2_MEMORY_MMAP; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_REQBUFS, &req); + cut_assert(ret == 0); + out_count = req.count; + cut_assert(out_count >= 1); + + /* Fill the first OUTPUT buffer with the whole H264 stream and queue it. */ + memset(&buf, 0, sizeof(buf)); + buf.index = 0; + buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + buf.memory = V4L2_MEMORY_MMAP; + buf.m.planes = planes; + buf.length = 1; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_QUERYBUF, &buf); + cut_assert(ret == 0); + { + void *map; + + map = libv4l2_plugin.mmap(backend, NULL, + buf.m.planes[0].length, + PROT_READ | PROT_WRITE, + MAP_SHARED, -1, + (int64_t) buf.m.planes[0].m.mem_offset); + cut_assert_not_null(map); + cut_assert(stream_len <= (gsize) buf.m.planes[0].length); + memcpy(map, stream, stream_len); + buf.m.planes[0].bytesused = (unsigned int) stream_len; + } + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_QBUF, &buf); + cut_assert(ret == 0); + + /* Start the stream. */ + type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_STREAMON, &type); + cut_assert(ret == 0); + + /* Request CAPTURE buffers; blocks until the decoder fills the output + buffer pool with decoded frames. */ + memset(&req, 0, sizeof(req)); + req.count = 8; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + req.memory = V4L2_MEMORY_MMAP; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_REQBUFS, &req); + cut_assert(ret == 0); + cap_count = req.count; + cut_assert(cap_count > 0); + + /* Signal end of stream now that the pipeline is running. The decoder + flushes any buffered frames and the final CAPTURE buffer is marked + with V4L2_BUF_FLAG_LAST. */ + { + struct v4l2_decoder_cmd decoder_cmd = { 0, }; + + decoder_cmd.cmd = V4L2_DEC_CMD_STOP; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_DECODER_CMD, &decoder_cmd); + cut_assert(ret == 0); + } + + /* Queue the CAPTURE buffers. */ + for (i = 0; i < cap_count; i++) { + memset(&buf, 0, sizeof(buf)); + buf.index = i; + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + buf.memory = V4L2_MEMORY_MMAP; + buf.m.planes = planes; + buf.length = 2; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_QBUF, &buf); + cut_assert(ret == 0); + } + + /* Dequeue CAPTURE buffers until the decoder reports the final buffer. + Each dequeued buffer is queued again so the decoder can keep + flushing frames into the fixed CAPTURE buffer pool. */ + while (!got_last && frame_count < max_frames) { + void *map; + guint8 non_zero = 0; + gsize j; + + memset(&buf, 0, sizeof(buf)); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + buf.memory = V4L2_MEMORY_MMAP; + buf.m.planes = planes; + buf.length = 2; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_DQBUF, &buf); + if (ret != 0) { + /* The last decoded buffer is held back until the + pipeline EOS is observed; retry until it is + released. */ + g_usleep(1000); + continue; + } + cut_assert(buf.m.planes[0].bytesused > 0); + + map = libv4l2_plugin.mmap(backend, NULL, + buf.m.planes[0].length, + PROT_READ, MAP_SHARED, -1, + (int64_t) buf.m.planes[0].m.mem_offset); + cut_assert_not_null(map); + for (j = 0; j < 4096 && j < buf.m.planes[0].bytesused; j++) + non_zero |= ((guint8 *) map)[j]; + munmap(map, buf.m.planes[0].length); + cut_assert(non_zero != 0); + + cut_assert((buf.timestamp.tv_sec > last_sec) || + (buf.timestamp.tv_sec == last_sec && + buf.timestamp.tv_usec >= last_usec)); + last_sec = buf.timestamp.tv_sec; + last_usec = buf.timestamp.tv_usec; + + frame_count++; + if (buf.flags & V4L2_BUF_FLAG_LAST) { + got_last = TRUE; + break; + } + + buf.flags = 0; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_QBUF, &buf); + cut_assert(ret == 0); + } + + cut_assert(got_last); + cut_assert(frame_count >= cap_count); + + /* Stop the stream. */ + type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + errno = 0; + ret = v4l_gst_ioctl(VIDIOC_STREAMOFF, &type); + cut_assert(ret == 0); + + g_free(stream); +}