From f9b2c5fe886ed1ee77ae424a6c26a22670bbe5db Mon Sep 17 00:00:00 2001 From: Takuro Ashie Date: Mon, 28 Sep 2026 16:07:06 +0900 Subject: [PATCH 01/13] Introduce media_kind and per-direction stream structure Split the per-direction (OUTPUT/CAPTURE) state out of struct v4l_gst into a new struct v4l_gst_stream, held as priv->out and priv->cap, and add enum v4l_gst_media_kind (CODEC/RAW) to describe the kind of media carried by each direction's buffers. A decoder has OUTPUT=CODEC / CAPTURE=RAW, an encoder will have OUTPUT=RAW / CAPTURE=CODEC. This makes the direction-agnostic core shareable and keeps the differences localized to the media kind, preparing the codebase for V4L2 M2M encoder support. Move struct v4l_gst and its helper types into a new v4l-gst-internal.h. This is a pure refactor: only the M2M decoder role is supported and all behavior is unchanged. Assisted-by: Qwen3.8-27B --- lib/libv4l-gst/gst-backend-testhooks.c | 22 +- lib/libv4l-gst/gst-backend.c | 682 +++++++++++-------------- lib/libv4l-gst/v4l-gst-internal.h | 170 ++++++ 3 files changed, 471 insertions(+), 403 deletions(-) create mode 100644 lib/libv4l-gst/v4l-gst-internal.h diff --git a/lib/libv4l-gst/gst-backend-testhooks.c b/lib/libv4l-gst/gst-backend-testhooks.c index 0ba2f33..4de4f66 100644 --- a/lib/libv4l-gst/gst-backend-testhooks.c +++ b/lib/libv4l-gst/gst-backend-testhooks.c @@ -25,18 +25,18 @@ prepare_format_backend_fixture(struct v4l_gst *priv) return; if (!priv->pipeline) priv->pipeline = create_pipeline("identity"); - append_test_format(priv->supported_cap_fmts, V4L2_PIX_FMT_NV12, "NV12"); + 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->is_cap_fmt_acquirable, 1); - priv->out_cnt = INPUT_BUFFERING_CNT; + 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; } #endif diff --git a/lib/libv4l-gst/gst-backend.c b/lib/libv4l-gst/gst-backend.c index c01115a..0f4de7f 100644 --- a/lib/libv4l-gst/gst-backend.c +++ b/lib/libv4l-gst/gst-backend.c @@ -49,119 +49,7 @@ GST_DEBUG_CATEGORY_STATIC(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; -}; +#include "v4l-gst-internal.h" G_DEFINE_QUARK(cap_buf_crc, cap_buf_crc) @@ -543,19 +431,19 @@ 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_array_set_size(priv->out.supported_fmts, 0); g_hash_table_foreach(priv->config.pipelines, fill_out_fmts_func, - priv->supported_out_fmts); + priv->out.supported_fmts); - for (i = 0; i < priv->supported_out_fmts->len; i++) { - struct fmt *fmts = (struct fmt*)priv->supported_out_fmts->data; + for (i = 0; i < priv->out.supported_fmts->len; i++) { + struct fmt *fmts = (struct fmt*)priv->out.supported_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; + return priv->out.supported_fmts->len > 0; } static void @@ -570,7 +458,7 @@ fill_config_video_format_cap(struct v4l_gst *priv) color_fmt.fourcc = fourcc_from_string("NV12"); g_strlcpy(color_fmt.desc, "NV12", FMTDESC_NAME_LENGTH); } - g_array_prepend_vals(priv->supported_cap_fmts, + g_array_prepend_vals(priv->cap.supported_fmts, &color_fmt, 1); } @@ -599,13 +487,13 @@ get_supported_video_format_out(struct v4l_gst *priv) } else { GST_ERROR("Unsupported codec : %s", mime); gst_caps_unref(caps); - g_array_set_size(priv->supported_out_fmts, 0); + g_array_set_size(priv->out.supported_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; + 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) @@ -632,7 +520,7 @@ get_supported_video_format_cap(struct v4l_gst *priv) struct fmt color_fmt; gchar fourcc_str[5]; - g_array_set_size(priv->supported_cap_fmts, 0); + g_array_set_size(priv->cap.supported_fmts, 0); caps = get_peer_pad_template_caps(priv->appsink, "sink", &priv->video_sink_pad); @@ -687,7 +575,7 @@ get_supported_video_format_cap(struct v4l_gst *priv) g_strlcpy(color_fmt.desc, fmt_str, FMTDESC_NAME_LENGTH); if (preferred && color_fmt.fourcc == preferred) { - g_array_prepend_vals(priv->supported_cap_fmts, + g_array_prepend_vals(priv->cap.supported_fmts, &color_fmt, 1); fourcc_to_string(preferred, fourcc_str); @@ -695,7 +583,7 @@ get_supported_video_format_cap(struct v4l_gst *priv) fourcc_str, preferred); preferred_found = TRUE; } else { - g_array_append_vals(priv->supported_cap_fmts, + g_array_append_vals(priv->cap.supported_fmts, &color_fmt, 1); } } @@ -704,7 +592,7 @@ get_supported_video_format_cap(struct v4l_gst *priv) if (preferred) { if (preferred_found) { /* TODO: Add a new option to force use this? */ - g_array_set_size(priv->supported_cap_fmts, 1); + 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", @@ -714,13 +602,13 @@ get_supported_video_format_cap(struct v4l_gst *priv) gst_caps_unref(caps); - if (priv->supported_cap_fmts->len == 0) { + 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->supported_cap_fmts->len); + priv->cap.supported_fmts->len); return TRUE; @@ -767,7 +655,7 @@ get_cap_buffer_alignment(struct v4l_gst *priv, GstVideoAlignment *alignment) e.g.) 16-byte alignment -> 15 (0b1111) 64-byte alignment -> 63 (0b111111) */ - switch (priv->cap_fmt.pixelformat) { + switch (priv->cap.fmt.pixelformat) { case V4L2_PIX_FMT_NV12: case V4L2_PIX_FMT_NV21: case V4L2_PIX_FMT_YVU420: @@ -783,7 +671,7 @@ get_cap_buffer_alignment(struct v4l_gst *priv, GstVideoAlignment *alignment) } gst_video_alignment_reset(alignment); - for (i = 0; i < priv->cap_fmt.num_planes; i++) + for (i = 0; i < priv->cap.fmt.num_planes; i++) alignment->stride_align[i] = stride_align; } @@ -839,27 +727,27 @@ retrieve_cap_format_info(struct v4l_gst *priv, GstVideoInfo *info) { gint fourcc; - priv->cap_fmt.width = info->width; - priv->cap_fmt.height = info->height; + 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) { + 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.pixelformat = fourcc; - priv->cap_fmt.num_planes = info->finfo->n_planes; + 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; + 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_cond_broadcast(&priv->cap.reqbuf_cond); + g_mutex_unlock(&priv->cap.reqbuf_mutex); g_mutex_lock(&priv->queue_mutex); priv->is_pipeline_started = started; @@ -875,18 +763,18 @@ wait_for_cap_reqbuf_invocation(struct v4l_gst *priv) gboolean timed_out = FALSE; gint64 end_time; - g_mutex_lock(&priv->cap_reqbuf_mutex); + 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, + 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->cancel_cap_reqbuf_wait && priv->cap_buffers_num > 0; - g_mutex_unlock(&priv->cap_reqbuf_mutex); + 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."); @@ -902,7 +790,7 @@ release_out_buffer_unlocked(struct v4l_gst *priv, GstBuffer *gstbuf) set_event(priv->event_state, POLLIN); - priv->returned_out_buffers_num++; + priv->out.returned_cnt++; } static inline void @@ -954,7 +842,7 @@ pad_probe_query(GstPad *pad, GstPadProbeInfo *probe_info, gpointer user_data) retrieve_cap_format_info(priv, &info); get_cap_buffer_alignment(priv, &alignment); - g_atomic_int_set(&priv->is_cap_fmt_acquirable, 1); + g_atomic_int_set(&priv->cap.fmt_acquirable, 1); push_source_change_event(priv); set_event(priv->event_state, POLLOUT); @@ -973,12 +861,12 @@ pad_probe_query(GstPad *pad, GstPadProbeInfo *probe_info, gpointer user_data) `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, + set_buffer_pool_params(priv->cap.pool, caps, info.size, + 0, priv->cap.buffers_num, &alignment); - gst_query_add_allocation_pool(query, priv->sink_pool, + gst_query_add_allocation_pool(query, priv->cap.pool, info.size, - 0, priv->cap_buffers_num); + 0, priv->cap.buffers_num); } else { GST_WARNING("Failed to wait VIDIOC_REQBUF."); } @@ -1072,10 +960,10 @@ appsink_callback_eos(GstAppSink *appsink, gpointer user_data) 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)); + 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)) { + 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); @@ -1091,7 +979,7 @@ appsink_callback_new_sample(GstAppSink *appsink, gpointer user_data) gstbuf = pull_buffer_from_sample(appsink); - if (priv->cap_buffers && !gst_buffer_n_memory(gstbuf)) { + 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=%" @@ -1120,10 +1008,10 @@ appsink_callback_new_sample(GstAppSink *appsink, gpointer user_data) gstbuf, GST_BUFFER_PTS(gstbuf) / 1000000); } - if (priv->cap_buffers) - queue = priv->cap_gstbufs_queue; + if (priv->cap.buffers) + queue = priv->cap.gstbufs_queue; else - queue = priv->req_gstbufs_queue; + queue = priv->out.gstbufs_queue; g_mutex_lock(&priv->queue_mutex); @@ -1136,7 +1024,7 @@ appsink_callback_new_sample(GstAppSink *appsink, gpointer user_data) GST_DEBUG("EOS: Flush last frame"); g_cond_signal(&priv->queue_cond); set_event(priv->event_state, POLLOUT); - } else if (!priv->cap_buffers) { + } else if (!priv->cap.buffers) { g_cond_signal(&priv->queue_cond); } @@ -1159,8 +1047,8 @@ init_app_elements(struct v4l_gst *priv) return FALSE; /* For queuing buffers received from appsink */ - priv->cap_gstbufs_queue = g_queue_new(); - priv->req_gstbufs_queue = g_queue_new(); + priv->cap.gstbufs_queue = g_queue_new(); + priv->out.gstbufs_queue = g_queue_new(); /* Set the appsrc queue size to unlimited. The amount of buffers is managed by the buffer pool. */ @@ -1200,7 +1088,7 @@ init_buffer_pool(struct v4l_gst *priv) &priv->pool_lib_handle, &priv->pool_ops); } - create_buffer_pool(priv->pool_ops, &priv->src_pool, &priv->sink_pool); + create_buffer_pool(priv->pool_ops, &priv->out.pool, &priv->cap.pool); /* To hook allocation queries */ priv->probe_id = setup_query_pad_probe(priv); @@ -1213,10 +1101,10 @@ init_buffer_pool(struct v4l_gst *priv) /* error cases */ free_pool: - if (priv->src_pool) - gst_object_unref(priv->src_pool); - if (priv->sink_pool) - gst_object_unref(priv->sink_pool); + if (priv->out.pool) + gst_object_unref(priv->out.pool); + if (priv->cap.pool) + gst_object_unref(priv->cap.pool); return FALSE; } @@ -1246,18 +1134,18 @@ init_pipeline(struct v4l_gst *priv, guint32 fourcc) if (!init_buffer_pool(priv)) goto error; - priv->out_fmt.pixelformat = fourcc; + 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->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->out.gstbufs_queue) { + g_queue_free(priv->out.gstbufs_queue); + priv->out.gstbufs_queue = NULL; } if (priv->pipeline) { gst_object_unref(priv->pipeline); @@ -1322,8 +1210,18 @@ gst_backend_init(int fd) 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)); + /* + * Only the M2M decoder role is supported at the moment: + * OUTPUT carries a compressed bitstream and CAPTURE + * carries decoded raw frames. + */ + priv->out.buf_type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + priv->out.kind = V4L_GST_MEDIA_KIND_CODEC; + priv->cap.buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + priv->cap.kind = V4L_GST_MEDIA_KIND_RAW; + + 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 (!fill_config_video_format_out(priv)) { GST_ERROR("Failed to fill in supported video format"); @@ -1338,8 +1236,8 @@ gst_backend_init(int fd) 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->cap.reqbuf_mutex); + g_cond_init(&priv->cap.reqbuf_cond); g_mutex_init(&priv->dev_lock); @@ -1352,10 +1250,10 @@ gst_backend_init(int fd) 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->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); g_free(priv->config.pool_lib_path); @@ -1396,31 +1294,31 @@ gst_backend_deinit(struct v4l_gst *priv) gst_object_unref(priv->video_sink_pad); } - if (priv->out_buffers) - g_free(priv->out_buffers); + if (priv->out.buffers) + g_free(priv->out.buffers); - if (priv->cap_buffers) - g_free(priv->cap_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->out.pool) + gst_object_unref(priv->out.pool); + if (priv->cap.pool) + gst_object_unref(priv->cap.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->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->req_gstbufs_queue) - g_queue_free(priv->req_gstbufs_queue); + 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); + g_mutex_clear(&priv->cap.reqbuf_mutex); + g_cond_clear(&priv->cap.reqbuf_cond); if (priv->pipeline) gst_object_unref(priv->pipeline); @@ -1495,13 +1393,13 @@ 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; + 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->supported_out_fmts->data, - priv->supported_out_fmts->len, + 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); @@ -1516,13 +1414,13 @@ set_fmt_ioctl_out(struct v4l_gst *priv, struct v4l2_format *fmt) } if (priv->pipeline) { - if (priv->out_fmt.pixelformat == pix_fmt->pixelformat) { + 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); + fourcc_to_string(priv->out.fmt.pixelformat, current); GST_ERROR("Different pixelformat with current: " "pixelformat:%s, current: %s", fourcc_str, current); @@ -1535,12 +1433,12 @@ set_fmt_ioctl_out(struct v4l_gst *priv, struct v4l2_format *fmt) goto error; } - priv->out_fmt = *pix_fmt; + 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 + if (!priv->cap.fmt.pixelformat && cap_fmts->len > 0) + priv->cap.fmt.pixelformat = ((struct fmt*)cap_fmts->data)[0].fourcc; return 0; @@ -1567,8 +1465,8 @@ set_fmt_ioctl_cap(struct v4l_gst *priv, struct v4l2_format *fmt) pix_fmt = &fmt->fmt.pix_mp; - if (!is_pix_fmt_supported((struct fmt*)priv->supported_cap_fmts->data, - priv->supported_cap_fmts->len, + 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; @@ -1577,11 +1475,11 @@ set_fmt_ioctl_cap(struct v4l_gst *priv, struct v4l2_format *fmt) GST_OBJECT_LOCK(priv->pipeline); if (GST_STATE(priv->pipeline) == GST_STATE_NULL) { - priv->cap_fmt.pixelformat = pix_fmt->pixelformat; + 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) { + } 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); @@ -1634,21 +1532,21 @@ get_fmt_ioctl_cap(struct v4l_gst *priv, gint i; gchar fourcc_str[5]; - if (!g_atomic_int_get(&priv->is_cap_fmt_acquirable) || - priv->out_cnt < INPUT_BUFFERING_CNT) { + 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); + 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->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; + 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", @@ -1656,14 +1554,14 @@ get_fmt_ioctl_cap(struct v4l_gst *priv, fourcc_str, pix_fmt->pixelformat, pix_fmt->num_planes); - if (priv->cap_fmt.plane_fmt[0].sizeimage > 0) { + 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; + priv->cap.fmt.plane_fmt[i].sizeimage; pix_fmt->plane_fmt[i].bytesperline = - priv->cap_fmt.plane_fmt[i].bytesperline; + priv->cap.fmt.plane_fmt[i].bytesperline; } - pix_fmt->num_planes = priv->cap_fmt.num_planes; + pix_fmt->num_planes = priv->cap.fmt.num_planes; } else { memset(pix_fmt->plane_fmt, 0, sizeof(pix_fmt->plane_fmt)); } @@ -1684,7 +1582,7 @@ get_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *fmt) if (fmt->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { g_mutex_lock(&priv->dev_lock); - *pix_fmt = priv->out_fmt; + *pix_fmt = priv->out.fmt; g_mutex_unlock(&priv->dev_lock); set_params_as_encoded_stream(pix_fmt); ret = 0; @@ -1710,12 +1608,12 @@ enum_fmt_ioctl(struct v4l_gst *priv, struct v4l2_fmtdesc *desc) 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; + 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->supported_cap_fmts->data; - fmts_num = priv->supported_cap_fmts->len; + fmts = (struct fmt*)priv->cap.supported_fmts->data; + fmts_num = priv->cap.supported_fmts->len; desc->flags = 0; } else { GST_ERROR("Invalid buf type"); @@ -1968,11 +1866,11 @@ qbuf_ioctl_out(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) GstMapInfo info; struct v4l_gst_buffer *buffer; - if (!check_v4l2_buffer(v4l2buf, priv->out_buffers, priv->out_buffers_num, - priv->src_pool)) + if (!check_v4l2_buffer(v4l2buf, priv->out.buffers, priv->out.buffers_num, + priv->out.pool)) return -1; - buffer = &priv->out_buffers[v4l2buf->index]; + 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)); @@ -1999,7 +1897,7 @@ qbuf_ioctl_out(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) } GST_TRACE("queue index=%d buffer=%p", v4l2buf->index, - priv->out_buffers[v4l2buf->index].gstbuf); + priv->out.buffers[v4l2buf->index].gstbuf); gst_buffer_unmap(buffer->gstbuf, &buffer->info); memset(&buffer->info, 0, sizeof(buffer->info)); @@ -2040,8 +1938,8 @@ qbuf_ioctl_out(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) return -1; } - if (priv->out_cnt < INPUT_BUFFERING_CNT) - priv->out_cnt++; + if (priv->out.cnt < INPUT_BUFFERING_CNT) + priv->out.cnt++; return 0; } @@ -2052,21 +1950,21 @@ 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); + 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); + 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); + g_queue_pop_nth_link(priv->out.gstbufs_queue, index); return TRUE; } @@ -2076,11 +1974,11 @@ 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)) + if (!check_v4l2_buffer(v4l2buf, priv->cap.buffers, priv->cap.buffers_num, + priv->cap.pool)) return -1; - buffer = &priv->cap_buffers[v4l2buf->index]; + buffer = &priv->cap.buffers[v4l2buf->index]; if (priv->config.frame_check && gst_buffer_n_memory(buffer->gstbuf)) { gpointer crc_p; @@ -2119,7 +2017,7 @@ qbuf_ioctl_cap(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) 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) { + 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; @@ -2217,7 +2115,7 @@ fill_v4l2_buffer(struct v4l_gst *priv, GstBufferPool *pool, 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)) { + 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; @@ -2283,7 +2181,7 @@ dequeue_non_blocking(GQueue *queue) static GstBuffer * dequeue_cap_buffer(struct v4l_gst *priv) { - GQueue *queue = priv->cap_gstbufs_queue; + GQueue *queue = priv->cap.gstbufs_queue; GstBuffer *gstbuf = NULL; guint len; @@ -2354,8 +2252,8 @@ reindex_buffers(struct v4l_gst *priv) 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]; + for (i = 0; i < priv->cap.buffers_num; ++i) { + struct v4l_gst_buffer *buf1 = &priv->cap.buffers[i]; GstMemory *mem; int fd; @@ -2382,8 +2280,8 @@ reindex_buffers(struct v4l_gst *priv) if (buf1->plane0_fd == fd) continue; - for (j = 0; j < priv->cap_buffers_num; ++j) { - struct v4l_gst_buffer *buf2 = &priv->cap_buffers[j]; + 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) @@ -2398,7 +2296,7 @@ reindex_buffers(struct v4l_gst *priv) i--; break; } - if (j >= priv->cap_buffers_num) { + 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)); } @@ -2423,30 +2321,30 @@ dqbuf_ioctl_out(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) return -1; } - if (!check_no_index_v4l2_buffer(v4l2buf, priv->out_buffers, - priv->src_pool)) + 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->src_pool); + gstbuf = acquire_buffer_from_pool(priv, priv->out.pool); if (!gstbuf) { g_mutex_unlock(&priv->queue_mutex); return -1; } - priv->returned_out_buffers_num--; + priv->out.returned_cnt--; - if (priv->returned_out_buffers_num == 0) { + 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) { + 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; @@ -2454,14 +2352,14 @@ dqbuf_ioctl_out(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) } v4l2buf->index = index; - priv->out_buffers[v4l2buf->index].state = V4L_GST_BUFFER_DEQUEUED; + 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, + return fill_v4l2_buffer(priv, priv->out.pool, + priv->out.buffers, priv->out.buffers_num, NULL, NULL, v4l2buf); } @@ -2521,8 +2419,8 @@ dqbuf_ioctl_cap(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) guint bytesused[GST_VIDEO_MAX_PLANES]; gint i; - if (!check_no_index_v4l2_buffer(v4l2buf, priv->cap_buffers, - priv->sink_pool)) + if (!check_no_index_v4l2_buffer(v4l2buf, priv->cap.buffers, + priv->cap.pool)) return -1; gstbuf = dequeue_cap_buffer(priv); @@ -2531,9 +2429,9 @@ dqbuf_ioctl_cap(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) reindex_buffers(priv); - index = get_v4l2_buffer_index(priv->cap_buffers, - priv->cap_buffers_num, gstbuf); - if (index >= priv->cap_buffers_num) { + 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; @@ -2543,18 +2441,18 @@ dqbuf_ioctl_cap(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) v4l2buf->index = index; - for (i = 0; i < priv->cap_fmt.num_planes; i++) - bytesused[i] = priv->cap_fmt.plane_fmt[i].sizeimage; + 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) { + 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); + gst_buffer_unref(priv->cap.buffers[index].gstbuf); } - priv->cap_buffers[v4l2buf->index].state = V4L_GST_BUFFER_DEQUEUED; + priv->cap.buffers[v4l2buf->index].state = V4L_GST_BUFFER_DEQUEUED; if (get_valid_cap_pts(priv, gstbuf, &pts)) { GST_TIME_TO_TIMEVAL(pts, timestamp); @@ -2565,8 +2463,8 @@ dqbuf_ioctl_cap(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) gstbuf, index, pts / 1000000, GST_BUFFER_PTS(gstbuf) / 1000000); - return fill_v4l2_buffer(priv, priv->sink_pool, - priv->cap_buffers, priv->cap_buffers_num, + return fill_v4l2_buffer(priv, priv->cap.pool, + priv->cap.buffers, priv->cap.buffers_num, bytesused, timestamp_ptr, v4l2buf); } @@ -2610,13 +2508,13 @@ querybuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) 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; + 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->sink_pool; + buffers = priv->cap.buffers; + buffers_num = priv->cap.buffers_num; + pool = priv->cap.pool; } else { GST_ERROR("Invalid buf type"); errno = EINVAL; @@ -2797,16 +2695,16 @@ 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--; + 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->returned_out_buffers_num); + GST_DEBUG("returned_out_buffers_num : %d", priv->out.returned_cnt); return 0; } @@ -2819,7 +2717,7 @@ force_cap_dqbuf(struct v4l_gst *priv) do { g_mutex_lock(&priv->queue_mutex); - gstbuf = dequeue_non_blocking(priv->cap_gstbufs_queue); + 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; @@ -2828,26 +2726,26 @@ force_cap_dqbuf(struct v4l_gst *priv) if (!gstbuf) break; - index = get_v4l2_buffer_index(priv->cap_buffers, - priv->cap_buffers_num, gstbuf); - if (index >= priv->cap_buffers_num) { + 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; + 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) + 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; + gst_buffer_ref(priv->cap.buffers[index].gstbuf); + priv->cap.buffers[index].state = V4L_GST_BUFFER_DEQUEUED; } return 0; @@ -2860,8 +2758,8 @@ flush_pipeline(struct v4l_gst *priv) GST_DEBUG("flush start"); - gst_buffer_pool_set_flushing(priv->src_pool, TRUE); - gst_buffer_pool_set_flushing(priv->sink_pool, TRUE); + 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)) { @@ -2879,8 +2777,8 @@ flush_pipeline(struct v4l_gst *priv) return -1; } - gst_buffer_pool_set_flushing(priv->src_pool, FALSE); - gst_buffer_pool_set_flushing(priv->sink_pool, FALSE); + gst_buffer_pool_set_flushing(priv->out.pool, FALSE); + gst_buffer_pool_set_flushing(priv->cap.pool, FALSE); GST_DEBUG("flush end"); @@ -2927,10 +2825,10 @@ streamoff_ioctl_out(struct v4l_gst *priv, gboolean steal_ref) of the OUTPUT bufferpool and freeing them by inactivating the bufferpool for flushing. */ if (steal_ref) - gst_buffer_ref(priv->out_buffers[0].gstbuf); + 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)) { + if (!gst_buffer_pool_set_active(priv->out.pool, FALSE)) { GST_ERROR("Failed to inactivate buffer pool on OUTPUT"); errno = EINVAL; return -1; @@ -2972,16 +2870,16 @@ reqbuf_ioctl_out(struct v4l_gst *priv, 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 == + 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); + gst_buffer_unref(priv->out.buffers[i].gstbuf); } } - if (priv->out_buffers) { - g_free(priv->out_buffers); - priv->out_buffers = NULL; + if (priv->out.buffers) { + g_free(priv->out.buffers); + priv->out.buffers = NULL; } ret = 0; @@ -2995,8 +2893,8 @@ reqbuf_ioctl_out(struct v4l_gst *priv, goto unlock; } - if (gst_buffer_pool_is_active(priv->src_pool)) { - if (!gst_buffer_pool_set_active(priv->src_pool, FALSE)) { + 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; @@ -3004,7 +2902,7 @@ reqbuf_ioctl_out(struct v4l_gst *priv, } } - caps = get_codec_caps_from_fourcc(priv->out_fmt.pixelformat); + caps = get_codec_caps_from_fourcc(priv->out.fmt.pixelformat); if (!caps) { errno = EINVAL; ret = -1; @@ -3014,12 +2912,12 @@ reqbuf_ioctl_out(struct v4l_gst *priv, 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, + 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->src_pool, - &priv->out_buffers); + allocated_num = alloc_buffers_from_pool(priv, priv->out.pool, + &priv->out.buffers); if (allocated_num == 0) { gst_caps_unref(caps); ret = -1; @@ -3030,19 +2928,19 @@ reqbuf_ioctl_out(struct v4l_gst *priv, /* 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, + 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); + priv->out.buffers[i].planes[0].length = + gst_buffer_get_size(priv->out.buffers[i].gstbuf); } - req->count = priv->out_buffers_num = allocated_num; + req->count = priv->out.buffers_num = allocated_num; GST_DEBUG("buffers count=%d", req->count); - priv->returned_out_buffers_num = 0; + priv->out.returned_cnt = 0; ret = 0; @@ -3060,7 +2958,7 @@ peek_first_cap_buffer(struct v4l_gst *priv) gint64 end_time; g_mutex_lock(&priv->queue_mutex); - gstbuf = g_queue_peek_head(priv->req_gstbufs_queue); + 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, @@ -3069,7 +2967,7 @@ peek_first_cap_buffer(struct v4l_gst *priv) timed_out = TRUE; break; } - gstbuf = g_queue_peek_head(priv->req_gstbufs_queue); + gstbuf = g_queue_peek_head(priv->out.gstbufs_queue); } g_mutex_unlock(&priv->queue_mutex); @@ -3083,7 +2981,7 @@ static gboolean wait_for_all_bufs_collected(struct v4l_gst *priv, guint max_buffers) { - GQueue *queue = priv->req_gstbufs_queue; + GQueue *queue = priv->out.gstbufs_queue; gboolean succeeded; gboolean timed_out = FALSE; gint64 end_time; @@ -3138,10 +3036,10 @@ create_cap_buffers_list(struct v4l_gst *priv) gint i, j; gboolean succeeded; - if (priv->cap_buffers) + 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; + return priv->cap.buffers_num; g_mutex_unlock(&priv->dev_lock); @@ -3162,16 +3060,16 @@ create_cap_buffers_list(struct v4l_gst *priv) return 0; } - if (priv->sink_pool != first_gstbuf->pool) { + 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->sink_pool); - priv->sink_pool = gst_object_ref(first_gstbuf->pool); + 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. */ - get_buffer_pool_params(priv->sink_pool, NULL, NULL, NULL, + 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"); @@ -3179,8 +3077,8 @@ create_cap_buffers_list(struct v4l_gst *priv) return 0; } - if (!retrieve_cap_frame_info(priv->sink_pool, first_gstbuf, - &priv->cap_fmt)) { + 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; @@ -3198,31 +3096,31 @@ create_cap_buffers_list(struct v4l_gst *priv) return 0; } - priv->cap_buffers = g_new0(struct v4l_gst_buffer, actual_max_buffers); + 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); + 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->sink_pool, + 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; + 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; + 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); + priv->cap.buffers[i].gstbuf); } GST_DEBUG("The number of buffers actually set to the buffer pool is %d", @@ -3261,27 +3159,27 @@ stop_pipeline(struct v4l_gst *priv) return ret; } - g_atomic_int_set(&priv->is_cap_fmt_acquirable, 0); + g_atomic_int_set(&priv->cap.fmt_acquirable, 0); - for (i = 0; i < priv->cap_buffers_num; i++) { - if (priv->cap_buffers[i].state == + 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); + gst_buffer_unref(priv->cap.buffers[i].gstbuf); } } - g_queue_clear(priv->req_gstbufs_queue); - g_queue_clear(priv->cap_gstbufs_queue); + g_queue_clear(priv->out.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; + 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); + priv->cap.buffers_num = 0; + init_decoded_frame_params(&priv->cap.fmt); return ret; } @@ -3309,12 +3207,12 @@ reqbuf_ioctl_cap(struct v4l_gst *priv, 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); + 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); + req->count = priv->cap.buffers_num = create_cap_buffers_list(priv); if (req->count == 0) goto unlock; @@ -3363,7 +3261,7 @@ set_out_format_to_pipeline(struct v4l_gst *priv) { GstCaps *caps; - caps = get_codec_caps_from_fourcc(priv->out_fmt.pixelformat); + caps = get_codec_caps_from_fourcc(priv->out.fmt.pixelformat); if (!caps) { errno = EINVAL; return FALSE; @@ -3383,12 +3281,12 @@ set_cap_format_to_pipeline(struct v4l_gst *priv) GstVideoFormat fmt; gboolean ret; - fmt = fourcc_to_gst_video_format(priv->cap_fmt.pixelformat); + 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); + fourcc_to_string(priv->cap.fmt.pixelformat, fourcc_str); GST_ERROR("Invalid format on CAPTURE: %s (0x%x)", - fourcc_str, priv->cap_fmt.pixelformat); + fourcc_str, priv->cap.fmt.pixelformat); errno = EINVAL; return FALSE; } @@ -3441,15 +3339,15 @@ streamon_ioctl_out(struct v4l_gst *priv) return -1; } - if (!gst_buffer_pool_is_active(priv->src_pool)) { - if (!gst_buffer_pool_set_active(priv->src_pool, TRUE)) { + 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); + gst_buffer_unref(priv->out.buffers[0].gstbuf); } priv->eos_state = EOS_NONE; @@ -3603,8 +3501,8 @@ 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) { + for (i = 0; i < priv->out.buffers_num; i++) { + if (priv->out.buffers[i].planes[0].m.mem_offset == offset) { index = i; break; } @@ -3623,19 +3521,19 @@ map_out_buffer(struct v4l_gst *priv, int index, int prot) 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, + if (!gst_buffer_map(priv->out.buffers[index].gstbuf, &info, map_flags)) { GST_ERROR("Failed to map buffer (%p)", - priv->out_buffers[index].gstbuf); + priv->out.buffers[index].gstbuf); errno = EINVAL; return MAP_FAILED; } data = info.data; - gst_buffer_unmap(priv->out_buffers[index].gstbuf, &info); + gst_buffer_unmap(priv->out.buffers[index].gstbuf, &info); - priv->out_buffers[index].flags = map_flags; + priv->out.buffers[index].flags = map_flags; return data; } @@ -3646,9 +3544,9 @@ find_cap_buffer_by_offset(struct v4l_gst *priv, { 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 == + 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; @@ -3672,29 +3570,29 @@ map_cap_buffer(struct v4l_gst *priv, int index, int plane, 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, + if (!gst_buffer_map(priv->cap.buffers[index].gstbuf, &info, map_flags)) { GST_ERROR("Failed to map buffer (%p)", - priv->cap_buffers[index].gstbuf); + priv->cap.buffers[index].gstbuf); errno = EINVAL; return MAP_FAILED; } - if (!get_raw_video_params(priv->sink_pool, - priv->cap_buffers[index].gstbuf, + if (!get_raw_video_params(priv->cap.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); + 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); + gst_buffer_unmap(priv->cap.buffers[index].gstbuf, &info); - priv->cap_buffers[index].flags = map_flags; + priv->cap.buffers[index].flags = map_flags; return data; } @@ -3757,21 +3655,21 @@ expbuf_ioctl(struct v4l_gst *priv, struct v4l2_exportbuffer *expbuf) return -1; } - if (expbuf->index >= priv->cap_buffers_num) { + if (expbuf->index >= priv->cap.buffers_num) { GST_ERROR("Buffer index is out of range!: %d/%d", - expbuf->index, priv->cap_buffers_num); + expbuf->index, priv->cap.buffers_num); errno = EINVAL; return -1; } - if (expbuf->plane >= priv->cap_fmt.num_planes) { + 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); + expbuf->plane, priv->cap.fmt.num_planes); errno = EINVAL; return -1; } - buffer = &priv->cap_buffers[expbuf->index]; + buffer = &priv->cap.buffers[expbuf->index]; if (expbuf->plane < gst_buffer_n_memory(buffer->gstbuf)) mem_index = expbuf->plane; @@ -3826,8 +3724,8 @@ g_selection_ioctl(struct v4l_gst *priv, struct v4l2_selection *selection) 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; + selection->r.width = priv->cap.fmt.width; + selection->r.height = priv->cap.fmt.height; return 0; } @@ -3990,8 +3888,8 @@ try_fmt_ioctl_out(struct v4l_gst *priv, struct v4l2_format *format) 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, + 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); @@ -4001,10 +3899,10 @@ try_fmt_ioctl_out(struct v4l_gst *priv, struct v4l2_format *format) } if (priv->pipeline && - priv->out_fmt.pixelformat != pix_fmt->pixelformat) { + priv->out.fmt.pixelformat != pix_fmt->pixelformat) { gchar current[5]; - fourcc_to_string(priv->out_fmt.pixelformat, current); + fourcc_to_string(priv->out.fmt.pixelformat, current); GST_ERROR("Different pixelformat with current: " "pixelformat:%s, current: %s", fourcc_str, current); @@ -4023,8 +3921,8 @@ 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, + if (!is_pix_fmt_supported((struct fmt*)priv->cap.supported_fmts->data, + priv->cap.supported_fmts->len, pix_fmt->pixelformat)) { gchar fourcc_str[5]; diff --git a/lib/libv4l-gst/v4l-gst-internal.h b/lib/libv4l-gst/v4l-gst-internal.h new file mode 100644 index 0000000..33a514b --- /dev/null +++ b/lib/libv4l-gst/v4l-gst-internal.h @@ -0,0 +1,170 @@ +/* + * 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, +}; + +/* + * State of one M2M stream direction (OUTPUT or CAPTURE). + */ +struct v4l_gst_stream { + enum v4l2_buf_type buf_type; + 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) + */ + struct v4l_gst_stream out; + struct v4l_gst_stream cap; + + int64_t mmap_offset; + + GMutex queue_mutex; + GCond queue_cond; + + 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; +}; + +#endif /* __V4L_GST_INTERNAL_H__ */ From dec360013795059607140ebeb547b2ad98dfe0c0 Mon Sep 17 00:00:00 2001 From: Takuro Ashie Date: Mon, 28 Sep 2026 17:39:05 +0900 Subject: [PATCH 02/13] Split gst-backend.c into per-domain translation units Break the 4170-line gst-backend.c into six translation units grouped by V4L2 ioctl domain: v4l-gst-core.c init/deinit, pipeline creation, config parsing, debug categories, buffer CRC quark v4l-gst-pipeline.c pipeline state control, pad probes, appsink callbacks, decoder command ioctls v4l-gst-fmt.c format ioctls (set/get/enum/try fmt, framesizes, crop, selection) v4l-gst-buf.c buffer ioctls (qbuf/dqbuf/reqbuf/streamon/mmap) v4l-gst-ctrl.c control ioctls v4l-gst-event.c event ioctls Functions keep their original file order. The 21 functions called across translation units become non-static and are declared in v4l-gst-internal.h, together with the extern debug categories and the buffer CRC quark. The GST_DEBUG_CATEGORY_STATIC definitions move to v4l-gst-core.c with GST_CAT_DEFAULT redefined in the internal header. The PAGE_ALIGN helper moves next to its only user in v4l-gst-buf.c. The #if 0'd set_decoder_cmd_state stub stays with the decoder command ioctls in v4l-gst-pipeline.c. Also rename gst-backend.h to v4l-gst.h and gst-backend-testhooks.{c,h} to v4l-gst-testhooks.{c,h}, and update the plugin and test meson sources accordingly. Exported symbols: all previous exports are preserved; the 21 cross-TU functions and 3 debug categories are newly exported as a consequence of the multi-TU layout. Assisted-by: Qwen3.8-27B --- lib/libv4l-gst/gst-backend.c | 4170 ----------------- lib/libv4l-gst/libv4l-gst.c | 2 +- lib/libv4l-gst/meson.build | 9 +- lib/libv4l-gst/v4l-gst-buf.c | 1780 +++++++ lib/libv4l-gst/v4l-gst-core.c | 987 ++++ lib/libv4l-gst/v4l-gst-ctrl.c | 250 + lib/libv4l-gst/v4l-gst-event.c | 164 + lib/libv4l-gst/v4l-gst-fmt.c | 569 +++ lib/libv4l-gst/v4l-gst-internal.h | 41 + lib/libv4l-gst/v4l-gst-pipeline.c | 755 +++ ...ackend-testhooks.c => v4l-gst-testhooks.c} | 5 +- ...ackend-testhooks.h => v4l-gst-testhooks.h} | 6 +- lib/libv4l-gst/{gst-backend.h => v4l-gst.h} | 6 +- tests/meson.build | 8 +- tests/test-gst-backend.c | 2 +- 15 files changed, 4571 insertions(+), 4183 deletions(-) delete mode 100644 lib/libv4l-gst/gst-backend.c create mode 100644 lib/libv4l-gst/v4l-gst-buf.c create mode 100644 lib/libv4l-gst/v4l-gst-core.c create mode 100644 lib/libv4l-gst/v4l-gst-ctrl.c create mode 100644 lib/libv4l-gst/v4l-gst-event.c create mode 100644 lib/libv4l-gst/v4l-gst-fmt.c create mode 100644 lib/libv4l-gst/v4l-gst-pipeline.c rename lib/libv4l-gst/{gst-backend-testhooks.c => v4l-gst-testhooks.c} (92%) rename lib/libv4l-gst/{gst-backend-testhooks.h => v4l-gst-testhooks.h} (70%) rename lib/libv4l-gst/{gst-backend.h => v4l-gst.h} (97%) diff --git a/lib/libv4l-gst/gst-backend.c b/lib/libv4l-gst/gst-backend.c deleted file mode 100644 index 0f4de7f..0000000 --- a/lib/libv4l-gst/gst-backend.c +++ /dev/null @@ -1,4170 +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); - -#include "v4l-gst-internal.h" - -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->out.supported_fmts, 0); - g_hash_table_foreach(priv->config.pipelines, - fill_out_fmts_func, - priv->out.supported_fmts); - - for (i = 0; i < priv->out.supported_fmts->len; i++) { - struct fmt *fmts = (struct fmt*)priv->out.supported_fmts->data; - g_strlcat(codecs, fmts[i].desc, sizeof(codecs)); - g_strlcat(codecs, " ", sizeof(codecs)); - } - GST_DEBUG("supported codecs: %s", codecs); - - return priv->out.supported_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->cap.supported_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->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; -} - -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; - } - - /* 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(); -} - -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->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 -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 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++; -} - -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->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->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); -} - -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->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; -} - -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(); - - /* 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->out.pool, &priv->cap.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->out.pool) - gst_object_unref(priv->out.pool); - if (priv->cap.pool) - gst_object_unref(priv->cap.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->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* -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; - } - - /* - * Only the M2M decoder role is supported at the moment: - * OUTPUT carries a compressed bitstream and CAPTURE - * carries decoded raw frames. - */ - priv->out.buf_type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; - priv->out.kind = V4L_GST_MEDIA_KIND_CODEC; - priv->cap.buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; - priv->cap.kind = V4L_GST_MEDIA_KIND_RAW; - - 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 (!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->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); - 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->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); - - 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->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->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->cap.supported_fmts->data, - priv->cap.supported_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->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->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); - 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->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; - } - - 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), - 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 -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->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; - - 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 -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->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; -} - -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->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 -flush_pipeline(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; -} - -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->out.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->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; - } - } - - 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->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 (!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->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. */ - 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; - } - - 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 -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->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->out.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->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; - 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->cap.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->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; - } - - 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 -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/libv4l-gst.c b/lib/libv4l-gst/libv4l-gst.c index 77c5d5e..502359a 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 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..2085b04 --- /dev/null +++ b/lib/libv4l-gst/v4l-gst-buf.c @@ -0,0 +1,1780 @@ +/* + * 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 +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; + 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->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; + } + + 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), + 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 +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->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; + + 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 +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->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 (!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->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 ..."); + + 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->out.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->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; + } + } + + 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->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 (!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->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. */ + 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; + } + + 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 = 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 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->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; + 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; +} + + +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->cap.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; +} + diff --git a/lib/libv4l-gst/v4l-gst-core.c b/lib/libv4l-gst/v4l-gst-core.c new file mode 100644 index 0000000..d9e25cb --- /dev/null +++ b/lib/libv4l-gst/v4l-gst-core.c @@ -0,0 +1,987 @@ +/* + * 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(cap_buf_crc, cap_buf_crc) + +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; +} + + +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; +} + + +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->out.supported_fmts, 0); + g_hash_table_foreach(priv->config.pipelines, + fill_out_fmts_func, + priv->out.supported_fmts); + + for (i = 0; i < priv->out.supported_fmts->len; i++) { + struct fmt *fmts = (struct fmt*)priv->out.supported_fmts->data; + g_strlcat(codecs, fmts[i].desc, sizeof(codecs)); + g_strlcat(codecs, " ", sizeof(codecs)); + } + GST_DEBUG("supported codecs: %s", codecs); + + return priv->out.supported_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->cap.supported_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->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; +} + + +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; + } + + /* 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 +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 +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(); + + /* 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->out.pool, &priv->cap.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->out.pool) + gst_object_unref(priv->out.pool); + if (priv->cap.pool) + gst_object_unref(priv->cap.pool); + + return FALSE; +} + + +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->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* +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; + } + + /* + * Only the M2M decoder role is supported at the moment: + * OUTPUT carries a compressed bitstream and CAPTURE + * carries decoded raw frames. + */ + priv->out.buf_type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + priv->out.kind = V4L_GST_MEDIA_KIND_CODEC; + priv->cap.buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + priv->cap.kind = V4L_GST_MEDIA_KIND_RAW; + + 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 (!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->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); + 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->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); + + g_free(priv->config.pool_lib_path); + + delete_event_state(priv->event_state); + + g_free(priv); + + GST_DEBUG("gst_backend_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..9e41f2c --- /dev/null +++ b/lib/libv4l-gst/v4l-gst-ctrl.c @@ -0,0 +1,250 @@ +/* + * 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 +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; +} + + +/* 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; +} + diff --git a/lib/libv4l-gst/v4l-gst-event.c b/lib/libv4l-gst/v4l-gst-event.c new file mode 100644 index 0000000..9aafce4 --- /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 +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; +} + + +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; +} + diff --git a/lib/libv4l-gst/v4l-gst-fmt.c b/lib/libv4l-gst/v4l-gst-fmt.c new file mode 100644 index 0000000..55b8dec --- /dev/null +++ b/lib/libv4l-gst/v4l-gst-fmt.c @@ -0,0 +1,569 @@ +/* + * 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 +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->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->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; +} + + +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->cap.supported_fmts->data, + priv->cap.supported_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->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->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); + 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 +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; +} + + +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; + } + + 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 +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; +} + diff --git a/lib/libv4l-gst/v4l-gst-internal.h b/lib/libv4l-gst/v4l-gst-internal.h index 33a514b..0812649 100644 --- a/lib/libv4l-gst/v4l-gst-internal.h +++ b/lib/libv4l-gst/v4l-gst-internal.h @@ -167,4 +167,45 @@ struct v4l_gst { 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 cap_buf_crc_quark(void); + +/* + * Functions shared between the v4l-gst-*.c translation units. + */ +void reset_cap_timestamp_state(struct v4l_gst *priv); +GstElement *create_pipeline(const gchar *pipeline_str); +GstElement *get_peer_element(GstElement *elem, const gchar *pad_name); +void set_buffer_pool_params(GstBufferPool *pool, GstCaps *caps, + guint buf_size, guint min_buffers, + guint max_buffers, GstVideoAlignment *alignment); +void get_buffer_pool_params(GstBufferPool *pool, GstCaps **caps, + guint *buf_size, guint *min_buffers, + guint *max_buffers); +void push_source_change_event(struct v4l_gst *priv); +void set_pipeline_started(struct v4l_gst *priv, gboolean started); +void release_out_buffer(struct v4l_gst *priv, GstBuffer *gstbuf); +GstPadProbeReturn decoder_sink_pad_probe(GstPad *pad, + GstPadProbeInfo *probe_info, + gpointer user_data); +void decoder_pad_unlinked_cb(GstPad *self, GstPad *peer, gpointer data); +gulong setup_query_pad_probe(struct v4l_gst *priv); +void appsink_callback_eos(GstAppSink *appsink, gpointer user_data); +GstFlowReturn appsink_callback_new_sample(GstAppSink *appsink, + gpointer user_data); +gboolean init_pipeline(struct v4l_gst *priv, guint32 fourcc); +void init_decoded_frame_params(struct v4l2_pix_format_mplane *pix_fmt); +gboolean get_raw_video_params(GstBufferPool *pool, GstBuffer *gstbuf, + GstVideoInfo *info, GstVideoMeta **meta); +GstCaps *get_codec_caps_from_fourcc(guint fourcc); +int flush_pipeline(struct v4l_gst *priv); +int stop_pipeline(struct v4l_gst *priv); +gboolean set_out_format_to_pipeline(struct v4l_gst *priv); +gboolean set_cap_format_to_pipeline(struct v4l_gst *priv); + #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..726e9d3 --- /dev/null +++ b/lib/libv4l-gst/v4l-gst-pipeline.c @@ -0,0 +1,755 @@ +/* + * 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 +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 +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 (!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); + 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->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); +} + + +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; +} + + +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 +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; +} + + +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); +} + + +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->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 +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; +} + + +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); +} + + +int +flush_pipeline(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 +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->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->out.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 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 +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; +} + + +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; +} + + + +#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 +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; +} + diff --git a/lib/libv4l-gst/gst-backend-testhooks.c b/lib/libv4l-gst/v4l-gst-testhooks.c similarity index 92% rename from lib/libv4l-gst/gst-backend-testhooks.c rename to lib/libv4l-gst/v4l-gst-testhooks.c index 4de4f66..ce05e4d 100644 --- a/lib/libv4l-gst/gst-backend-testhooks.c +++ b/lib/libv4l-gst/v4l-gst-testhooks.c @@ -1,7 +1,10 @@ -#include "gst-backend-testhooks.h" +#include "config.h" #ifdef UNIT_TESTS +#include "v4l-gst-testhooks.h" +#include "v4l-gst-internal.h" + GstElement * test_create_pipeline(const gchar *pipeline_str) { diff --git a/lib/libv4l-gst/gst-backend-testhooks.h b/lib/libv4l-gst/v4l-gst-testhooks.h similarity index 70% rename from lib/libv4l-gst/gst-backend-testhooks.h rename to lib/libv4l-gst/v4l-gst-testhooks.h index 3a8b976..cf1c6b8 100644 --- a/lib/libv4l-gst/gst-backend-testhooks.h +++ b/lib/libv4l-gst/v4l-gst-testhooks.h @@ -1,5 +1,5 @@ -#ifndef __GST_BACKEND_TEST_H__ -#define __GST_BACKEND_TEST_H__ +#ifndef __V4L_GST_TESTHOOKS_H__ +#define __V4L_GST_TESTHOOKS_H__ #include "config.h" @@ -13,4 +13,4 @@ GstElement *test_create_pipeline(const gchar *pipeline_str); void prepare_format_backend_fixture(struct v4l_gst *priv); #endif -#endif /* __GST_BACKEND_TEST_H__ */ +#endif /* __V4L_GST_TESTHOOKS_H__ */ diff --git a/lib/libv4l-gst/gst-backend.h b/lib/libv4l-gst/v4l-gst.h similarity index 97% rename from lib/libv4l-gst/gst-backend.h rename to lib/libv4l-gst/v4l-gst.h index 7fbd39d..08ff4a7 100644 --- a/lib/libv4l-gst/gst-backend.h +++ b/lib/libv4l-gst/v4l-gst.h @@ -16,8 +16,8 @@ * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */ -#ifndef __GST_BACKEND_H__ -#define __GST_BACKEND_H__ +#ifndef __V4L_GST_H__ +#define __V4L_GST_H__ #include @@ -53,4 +53,4 @@ 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__ */ +#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..0869a4a 100644 --- a/tests/test-gst-backend.c +++ b/tests/test-gst-backend.c @@ -1,4 +1,4 @@ -#include "gst-backend-testhooks.h" +#include "v4l-gst-testhooks.h" #include #include #include From 10bcabf22cbd142e2bf79d640ecb37360180f1e3 Mon Sep 17 00:00:00 2001 From: Takuro Ashie Date: Mon, 28 Sep 2026 21:54:46 +0900 Subject: [PATCH 03/13] Rename backend API with v4l_gst prefix and organize declarations by domain Assisted-by: Qwen3.8-27B --- lib/libv4l-gst/libv4l-gst.c | 54 +++++++++---------- lib/libv4l-gst/v4l-gst-buf.c | 54 +++++++++---------- lib/libv4l-gst/v4l-gst-core.c | 56 ++++++------------- lib/libv4l-gst/v4l-gst-ctrl.c | 8 +-- lib/libv4l-gst/v4l-gst-event.c | 6 +-- lib/libv4l-gst/v4l-gst-fmt.c | 22 ++++---- lib/libv4l-gst/v4l-gst-internal.h | 71 +++++++++++++----------- lib/libv4l-gst/v4l-gst-pipeline.c | 82 +++++++++++++++++++--------- lib/libv4l-gst/v4l-gst-testhooks.c | 4 +- lib/libv4l-gst/v4l-gst.h | 86 ++++++++++++++++++++---------- 10 files changed, 248 insertions(+), 195 deletions(-) diff --git a/lib/libv4l-gst/libv4l-gst.c b/lib/libv4l-gst/libv4l-gst.c index 502359a..1a14904 100644 --- a/lib/libv4l-gst/libv4l-gst.c +++ b/lib/libv4l-gst/libv4l-gst.c @@ -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_querycap_ioctl(priv, arg); break; case VIDIOC_S_FMT: - ret = set_fmt_ioctl(priv, arg); + ret = v4l_gst_set_fmt_ioctl(priv, arg); break; case VIDIOC_G_FMT: - ret = get_fmt_ioctl(priv, arg); + ret = v4l_gst_get_fmt_ioctl(priv, arg); break; case VIDIOC_ENUM_FMT: - ret = enum_fmt_ioctl(priv, arg); + ret = v4l_gst_enum_fmt_ioctl(priv, arg); break; case VIDIOC_G_CTRL: - ret = get_ctrl_ioctl(priv, arg); + ret = v4l_gst_get_ctrl_ioctl(priv, arg); break; case VIDIOC_G_EXT_CTRLS: - ret = get_ext_ctrl_ioctl(priv, arg); + ret = v4l_gst_get_ext_ctrl_ioctl(priv, arg); break; case VIDIOC_QBUF: - ret = qbuf_ioctl(priv, arg); + ret = v4l_gst_qbuf_ioctl(priv, arg); break; case VIDIOC_DQBUF: - ret = dqbuf_ioctl(priv, arg); + ret = v4l_gst_dqbuf_ioctl(priv, arg); break; case VIDIOC_QUERYBUF: - ret = querybuf_ioctl(priv, arg); + ret = v4l_gst_querybuf_ioctl(priv, arg); break; case VIDIOC_REQBUFS: - ret = reqbuf_ioctl(priv, arg); + ret = v4l_gst_reqbuf_ioctl(priv, arg); break; case VIDIOC_STREAMON: - ret = streamon_ioctl(priv, arg); + ret = v4l_gst_streamon_ioctl(priv, arg); break; case VIDIOC_STREAMOFF: - ret = streamoff_ioctl(priv, arg); + ret = v4l_gst_streamoff_ioctl(priv, arg); break; case VIDIOC_SUBSCRIBE_EVENT: - ret = subscribe_event_ioctl(priv, arg); + ret = v4l_gst_subscribe_event_ioctl(priv, arg); break; case VIDIOC_DQEVENT: - ret = dqevent_ioctl(priv, arg); + ret = v4l_gst_dqevent_ioctl(priv, arg); break; case VIDIOC_EXPBUF: - ret = expbuf_ioctl(priv, arg); + ret = v4l_gst_expbuf_ioctl(priv, arg); break; case VIDIOC_ENUM_FRAMESIZES : - ret = enum_framesizes_ioctl(priv, arg); + ret = v4l_gst_enum_framesizes_ioctl(priv, arg); break; case VIDIOC_G_SELECTION : - ret = g_selection_ioctl(priv, arg); + ret = v4l_gst_g_selection_ioctl(priv, arg); break; case VIDIOC_QUERYCTRL: - ret = queryctrl_ioctl(priv, arg); + ret = v4l_gst_queryctrl_ioctl(priv, arg); break; case VIDIOC_QUERYMENU: - ret = querymenu_ioctl(priv, arg); + ret = v4l_gst_querymenu_ioctl(priv, arg); break; case VIDIOC_G_CROP: - ret = g_crop_ioctl(priv, arg); + ret = v4l_gst_g_crop_ioctl(priv, arg); break; case VIDIOC_TRY_FMT: - ret = try_fmt_ioctl(priv, arg); + ret = v4l_gst_try_fmt_ioctl(priv, arg); break; case VIDIOC_UNSUBSCRIBE_EVENT: - ret = unsubscribe_event_ioctl(priv, arg); + ret = v4l_gst_unsubscribe_event_ioctl(priv, arg); break; case VIDIOC_DECODER_CMD: - ret = decoder_cmd_ioctl(priv, arg); + ret = v4l_gst_decoder_cmd_ioctl(priv, arg); break; case VIDIOC_TRY_DECODER_CMD: - ret = try_decoder_cmd_ioctl(priv, arg); + ret = v4l_gst_try_decoder_cmd_ioctl(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/v4l-gst-buf.c b/lib/libv4l-gst/v4l-gst-buf.c index 2085b04..798195e 100644 --- a/lib/libv4l-gst/v4l-gst-buf.c +++ b/lib/libv4l-gst/v4l-gst-buf.c @@ -57,7 +57,7 @@ release_out_buffer_unlocked(struct v4l_gst *priv, GstBuffer *gstbuf) void -release_out_buffer(struct v4l_gst *priv, GstBuffer *gstbuf) +v4l_gst_buf_release_out_buffer(struct v4l_gst *priv, GstBuffer *gstbuf) { g_mutex_lock(&priv->queue_mutex); @@ -104,7 +104,7 @@ check_no_index_v4l2_buffer(struct v4l2_buffer *v4l2buf, return FALSE; } - if (get_raw_video_params(pool, buffers[v4l2buf->index].gstbuf, NULL, + if (v4l_gst_pipeline_get_raw_video_params(pool, buffers[v4l2buf->index].gstbuf, NULL, &meta)) n_planes = meta->n_planes; else @@ -142,7 +142,7 @@ notify_unref(gpointer data) { struct v4l_gst_buffer *buffer = data; - release_out_buffer(buffer->priv, buffer->gstbuf); + v4l_gst_buf_release_out_buffer(buffer->priv, buffer->gstbuf); } @@ -325,7 +325,7 @@ qbuf_ioctl_cap(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) int -qbuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) +v4l_gst_qbuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) { int ret = -1; @@ -399,7 +399,7 @@ fill_v4l2_buffer(struct v4l_gst *priv, GstBufferPool *pool, GstVideoMeta *meta = NULL; guint n_planes; - get_raw_video_params(pool, buffers[v4l2buf->index].gstbuf, NULL, &meta); + v4l_gst_pipeline_get_raw_video_params(pool, buffers[v4l2buf->index].gstbuf, NULL, &meta); n_planes = (meta) ? meta->n_planes : 1; @@ -774,7 +774,7 @@ dqbuf_ioctl_cap(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) int -dqbuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) +v4l_gst_dqbuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) { int ret = -1; @@ -800,7 +800,7 @@ dqbuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) int -querybuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) +v4l_gst_querybuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) { struct v4l_gst_buffer *buffers; gint buffers_num; @@ -855,7 +855,7 @@ set_mem_offset(struct v4l_gst_buffer *buffer, GstBufferPool *pool, gsize offset) if (page_size < 0) page_size = sysconf(_SC_PAGESIZE); - if (!get_raw_video_params(pool, buffer->gstbuf, &info, &meta)) { + 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), @@ -893,7 +893,7 @@ alloc_buffers_from_pool(struct v4l_gst *priv, GstBufferPool *pool, /* 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); + 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; @@ -1048,7 +1048,7 @@ streamoff_ioctl_out(struct v4l_gst *priv, gboolean steal_ref) GST_CAT_DEBUG(v4l_gst_buffer_debug_category, "STREAMOFF OUT begin: dequeue all OUT & CAP buffers ..."); - set_pipeline_started(priv, FALSE); + v4l_gst_core_set_pipeline_started(priv, FALSE); GST_OBJECT_LOCK(priv->pipeline); if (GST_STATE(priv->pipeline) == GST_STATE_NULL) { @@ -1060,7 +1060,7 @@ streamoff_ioctl_out(struct v4l_gst *priv, gboolean steal_ref) GST_OBJECT_UNLOCK(priv->pipeline); - ret = flush_pipeline(priv); + ret = v4l_gst_pipeline_flush(priv); if (ret < 0) return ret; @@ -1090,7 +1090,7 @@ streamoff_ioctl_out(struct v4l_gst *priv, gboolean steal_ref) } /* wake up blocking of the CAPTURE buffer acquisition */ - set_pipeline_started(priv, FALSE); + v4l_gst_core_set_pipeline_started(priv, FALSE); GST_CAT_DEBUG(v4l_gst_buffer_debug_category, "STREAMOFF OUT end"); @@ -1158,7 +1158,7 @@ reqbuf_ioctl_out(struct v4l_gst *priv, } } - caps = get_codec_caps_from_fourcc(priv->out.fmt.pixelformat); + caps = v4l_gst_pipeline_get_codec_caps_from_fourcc(priv->out.fmt.pixelformat); if (!caps) { errno = EINVAL; ret = -1; @@ -1168,7 +1168,7 @@ reqbuf_ioctl_out(struct v4l_gst *priv, adjusted_count = MAX(req->count, INPUT_BUFFERING_CNT); adjusted_count = MIN(adjusted_count, VIDEO_MAX_FRAME); - set_buffer_pool_params(priv->out.pool, caps, + v4l_gst_pipeline_set_buffer_pool_params(priv->out.pool, caps, priv->out.fmt.plane_fmt[0].sizeimage, adjusted_count, adjusted_count, NULL); @@ -1273,7 +1273,7 @@ retrieve_cap_frame_info(GstBufferPool *pool, GstBuffer *gstbuf, GstVideoMeta *meta; gint i; - if (!get_raw_video_params(pool, gstbuf, &info, &meta)) { + if (!v4l_gst_pipeline_get_raw_video_params(pool, gstbuf, &info, &meta)) { GST_ERROR("Failed to get video meta data"); return FALSE; } @@ -1329,7 +1329,7 @@ create_cap_buffers_list(struct v4l_gst *priv) } /* Confirm the number of buffers actually set to the buffer pool. */ - get_buffer_pool_params(priv->cap.pool, NULL, NULL, NULL, + 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"); @@ -1402,7 +1402,7 @@ reqbuf_ioctl_cap(struct v4l_gst *priv, g_mutex_lock(&priv->dev_lock); if (req->count == 0) { - ret = stop_pipeline(priv); + ret = v4l_gst_pipeline_stop(priv); goto unlock; } @@ -1434,7 +1434,7 @@ reqbuf_ioctl_cap(struct v4l_gst *priv, int -reqbuf_ioctl(struct v4l_gst *priv, struct v4l2_requestbuffers *req) +v4l_gst_reqbuf_ioctl(struct v4l_gst *priv, struct v4l2_requestbuffers *req) { int ret; @@ -1474,9 +1474,9 @@ streamon_ioctl_out(struct v4l_gst *priv) g_mutex_lock(&priv->dev_lock); if (state == GST_STATE_NULL) { - if (!set_out_format_to_pipeline(priv)) + if (!v4l_gst_pipeline_set_out_format(priv)) return -1; - if (!set_cap_format_to_pipeline(priv)) + if (!v4l_gst_pipeline_set_cap_format(priv)) return -1; } @@ -1492,9 +1492,9 @@ streamon_ioctl_out(struct v4l_gst *priv) } priv->eos_state = EOS_NONE; - reset_cap_timestamp_state(priv); + v4l_gst_core_reset_cap_timestamp_state(priv); - set_pipeline_started(priv, TRUE); + v4l_gst_core_set_pipeline_started(priv, TRUE); gst_element_set_state(priv->pipeline, GST_STATE_PLAYING); @@ -1509,7 +1509,7 @@ streamon_ioctl_out(struct v4l_gst *priv) int -streamon_ioctl(struct v4l_gst *priv, enum v4l2_buf_type *type) +v4l_gst_streamon_ioctl(struct v4l_gst *priv, enum v4l2_buf_type *type) { int ret; @@ -1532,7 +1532,7 @@ streamon_ioctl(struct v4l_gst *priv, enum v4l2_buf_type *type) int -streamoff_ioctl(struct v4l_gst *priv, enum v4l2_buf_type *type) +v4l_gst_streamoff_ioctl(struct v4l_gst *priv, enum v4l2_buf_type *type) { int ret; @@ -1646,7 +1646,7 @@ map_cap_buffer(struct v4l_gst *priv, int index, int plane, return MAP_FAILED; } - if (!get_raw_video_params(priv->cap.pool, + if (!v4l_gst_pipeline_get_raw_video_params(priv->cap.pool, priv->cap.buffers[index].gstbuf, NULL, &meta)) { GST_ERROR("Failed to get video meta data"); @@ -1667,7 +1667,7 @@ map_cap_buffer(struct v4l_gst *priv, int index, int plane, void * -gst_backend_mmap(struct v4l_gst *priv, void *start, size_t length, +v4l_gst_mmap(struct v4l_gst *priv, void *start, size_t length, int prot, int flags, int fd, int64_t offset) { int index; @@ -1709,7 +1709,7 @@ gst_backend_mmap(struct v4l_gst *priv, void *start, size_t length, int -expbuf_ioctl(struct v4l_gst *priv, struct v4l2_exportbuffer *expbuf) +v4l_gst_expbuf_ioctl(struct v4l_gst *priv, struct v4l2_exportbuffer *expbuf) { struct v4l_gst_buffer *buffer; guint mem_index = 0; diff --git a/lib/libv4l-gst/v4l-gst-core.c b/lib/libv4l-gst/v4l-gst-core.c index d9e25cb..e2990a0 100644 --- a/lib/libv4l-gst/v4l-gst-core.c +++ b/lib/libv4l-gst/v4l-gst-core.c @@ -51,7 +51,7 @@ GstDebugCategory *v4l_gst_buffer_debug_category; G_DEFINE_QUARK(cap_buf_crc, cap_buf_crc) void -reset_cap_timestamp_state(struct v4l_gst *priv) +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; @@ -206,7 +206,7 @@ parse_config_file(struct v4l_gst *priv) GstElement * -create_pipeline(const gchar *pipeline_str) +v4l_gst_core_create_pipeline(const gchar *pipeline_str) { const gchar *format_str; gchar *launch_str; @@ -351,7 +351,7 @@ get_peer_pad(GstElement *elem, const gchar *pad_name) GstElement * -get_peer_element(GstElement *elem, const gchar *pad_name) +v4l_gst_core_get_peer_element(GstElement *elem, const gchar *pad_name) { GstPad *peer_pad; GstElement *peer_elem; @@ -645,7 +645,7 @@ create_buffer_pool(struct libv4l_gst_buffer_pool_ops *pool_ops, void -push_source_change_event(struct v4l_gst *priv) +v4l_gst_core_push_source_change_event(struct v4l_gst *priv) { struct v4l2_event *event = g_new0(struct v4l2_event, 1); @@ -663,7 +663,7 @@ push_source_change_event(struct v4l_gst *priv) void -set_pipeline_started(struct v4l_gst *priv, gboolean started) +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; @@ -696,30 +696,8 @@ init_app_elements(struct v4l_gst *priv) priv->cap.gstbufs_queue = g_queue_new(); priv->out.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); - } + if (!v4l_gst_pipeline_setup_app_elements(priv)) + return FALSE; return TRUE; } @@ -738,7 +716,7 @@ init_buffer_pool(struct v4l_gst *priv) create_buffer_pool(priv->pool_ops, &priv->out.pool, &priv->cap.pool); /* To hook allocation queries */ - priv->probe_id = setup_query_pad_probe(priv); + 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; @@ -758,7 +736,7 @@ init_buffer_pool(struct v4l_gst *priv) gboolean -init_pipeline(struct v4l_gst *priv, guint32 fourcc) +v4l_gst_core_init_pipeline(struct v4l_gst *priv, guint32 fourcc) { gchar fourcc_str[5]; const gchar *pipeline; @@ -768,7 +746,7 @@ init_pipeline(struct v4l_gst *priv, guint32 fourcc) if (pipeline) { GST_DEBUG("create %s pipeline: %s", fourcc_str, pipeline); - priv->pipeline = create_pipeline(pipeline); + priv->pipeline = v4l_gst_core_create_pipeline(pipeline); } if (!priv->pipeline) { @@ -805,7 +783,7 @@ init_pipeline(struct v4l_gst *priv, guint32 fourcc) struct v4l_gst* -gst_backend_init(int fd) +v4l_gst_init(int fd) { static gboolean gstreamer_initialized = FALSE; struct v4l_gst *priv; @@ -831,7 +809,7 @@ gst_backend_init(int fd) GST_ERROR("Couldn't allocate memory for gst-backend"); return NULL; } - reset_cap_timestamp_state(priv); + v4l_gst_core_reset_cap_timestamp_state(priv); /* Reject character device */ fstat(fd, &buf); @@ -891,7 +869,7 @@ gst_backend_init(int fd) g_mutex_init(&priv->dev_lock); if (priv->config.fixed_pipeline) { - if (!init_pipeline(priv, priv->config.fixed_pipeline)) + if (!v4l_gst_core_init_pipeline(priv, priv->config.fixed_pipeline)) goto error; } @@ -915,11 +893,11 @@ gst_backend_init(int fd) void -gst_backend_deinit(struct v4l_gst *priv) +v4l_gst_deinit(struct v4l_gst *priv) { - GST_DEBUG("gst_backend_deinit start"); + GST_DEBUG("v4l_gst_deinit start"); - set_pipeline_started(priv, FALSE); + v4l_gst_core_set_pipeline_started(priv, FALSE); g_mutex_clear(&priv->dev_lock); @@ -982,6 +960,6 @@ gst_backend_deinit(struct v4l_gst *priv) g_free(priv); - GST_DEBUG("gst_backend_deinit end"); + GST_DEBUG("v4l_gst_deinit end"); } diff --git a/lib/libv4l-gst/v4l-gst-ctrl.c b/lib/libv4l-gst/v4l-gst-ctrl.c index 9e41f2c..df7e2f8 100644 --- a/lib/libv4l-gst/v4l-gst-ctrl.c +++ b/lib/libv4l-gst/v4l-gst-ctrl.c @@ -45,7 +45,7 @@ #include "v4l-gst-internal.h" int -get_ctrl_ioctl(struct v4l_gst *priv, struct v4l2_control *ctrl) +v4l_gst_get_ctrl_ioctl(struct v4l_gst *priv, struct v4l2_control *ctrl) { int ret; @@ -68,7 +68,7 @@ get_ctrl_ioctl(struct v4l_gst *priv, struct v4l2_control *ctrl) int -get_ext_ctrl_ioctl(struct v4l_gst *priv, struct v4l2_ext_controls *ext_ctrls) +v4l_gst_get_ext_ctrl_ioctl(struct v4l_gst *priv, struct v4l2_ext_controls *ext_ctrls) { unsigned int i; @@ -99,7 +99,7 @@ get_ext_ctrl_ioctl(struct v4l_gst *priv, struct v4l2_ext_controls *ext_ctrls) /* 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) +v4l_gst_queryctrl_ioctl(struct v4l_gst *priv, struct v4l2_queryctrl *query_ctrl) { gchar fourcc_str[5]; @@ -165,7 +165,7 @@ queryctrl_ioctl(struct v4l_gst *priv, struct v4l2_queryctrl *query_ctrl) /* 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) +v4l_gst_querymenu_ioctl(struct v4l_gst *priv, struct v4l2_querymenu *query_menu) { #ifdef ENABLE_VIDIOC_DEBUG diff --git a/lib/libv4l-gst/v4l-gst-event.c b/lib/libv4l-gst/v4l-gst-event.c index 9aafce4..b133c5f 100644 --- a/lib/libv4l-gst/v4l-gst-event.c +++ b/lib/libv4l-gst/v4l-gst-event.c @@ -45,7 +45,7 @@ #include "v4l-gst-internal.h" int -subscribe_event_ioctl(struct v4l_gst *priv, +v4l_gst_subscribe_event_ioctl(struct v4l_gst *priv, struct v4l2_event_subscription *subscription) { int retval = -1; @@ -82,7 +82,7 @@ subscribe_event_ioctl(struct v4l_gst *priv, int -dqevent_ioctl(struct v4l_gst *priv, struct v4l2_event *ev) +v4l_gst_dqevent_ioctl(struct v4l_gst *priv, struct v4l2_event *ev) { int retval = -1; @@ -126,7 +126,7 @@ dqevent_ioctl(struct v4l_gst *priv, struct v4l2_event *ev) int -unsubscribe_event_ioctl(struct v4l_gst *priv, +v4l_gst_unsubscribe_event_ioctl(struct v4l_gst *priv, struct v4l2_event_subscription *subscription) { int retval = 0; diff --git a/lib/libv4l-gst/v4l-gst-fmt.c b/lib/libv4l-gst/v4l-gst-fmt.c index 55b8dec..1306ee0 100644 --- a/lib/libv4l-gst/v4l-gst-fmt.c +++ b/lib/libv4l-gst/v4l-gst-fmt.c @@ -45,7 +45,7 @@ #include "v4l-gst-internal.h" int -querycap_ioctl(struct v4l_gst *priv, struct v4l2_capability *cap) +v4l_gst_querycap_ioctl(struct v4l_gst *priv, struct v4l2_capability *cap) { GST_DEBUG("VIDIOC_QUERYCAP"); @@ -140,7 +140,7 @@ set_fmt_ioctl_out(struct v4l_gst *priv, struct v4l2_format *fmt) return -1; } } else { - gboolean succeeded = init_pipeline(priv, pix_fmt->pixelformat); + gboolean succeeded = v4l_gst_core_init_pipeline(priv, pix_fmt->pixelformat); if (!succeeded) goto error; } @@ -161,7 +161,7 @@ set_fmt_ioctl_out(struct v4l_gst *priv, struct v4l2_format *fmt) void -init_decoded_frame_params(struct v4l2_pix_format_mplane *pix_fmt) +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. */ @@ -190,7 +190,7 @@ set_fmt_ioctl_cap(struct v4l_gst *priv, struct v4l2_format *fmt) 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); + 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) { @@ -216,7 +216,7 @@ set_fmt_ioctl_cap(struct v4l_gst *priv, struct v4l2_format *fmt) int -set_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *fmt) +v4l_gst_set_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *fmt) { int ret; @@ -287,7 +287,7 @@ get_fmt_ioctl_cap(struct v4l_gst *priv, int -get_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *fmt) +v4l_gst_get_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *fmt) { struct v4l2_pix_format_mplane *pix_fmt; int ret; @@ -316,7 +316,7 @@ get_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *fmt) int -enum_fmt_ioctl(struct v4l_gst *priv, struct v4l2_fmtdesc *desc) +v4l_gst_enum_fmt_ioctl(struct v4l_gst *priv, struct v4l2_fmtdesc *desc) { struct fmt *fmts; gint fmts_num; @@ -358,7 +358,7 @@ enum_fmt_ioctl(struct v4l_gst *priv, struct v4l2_fmtdesc *desc) int -enum_framesizes_ioctl(struct v4l_gst *priv, struct v4l2_frmsizeenum *argp) +v4l_gst_enum_framesizes_ioctl(struct v4l_gst *priv, struct v4l2_frmsizeenum *argp) { gchar fourcc_str[5]; @@ -426,7 +426,7 @@ enum_framesizes_ioctl(struct v4l_gst *priv, struct v4l2_frmsizeenum *argp) int -g_selection_ioctl(struct v4l_gst *priv, struct v4l2_selection *selection) +v4l_gst_g_selection_ioctl(struct v4l_gst *priv, struct v4l2_selection *selection) { #ifdef ENABLE_VIDIOC_DEBUG char *vidioc_features = getenv(ENV_DISABLE_VIDIOC_FEATURES); @@ -511,7 +511,7 @@ try_fmt_ioctl_cap(struct v4l_gst *priv, struct v4l2_format *format) int -try_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *format) +v4l_gst_try_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *format) { int ret; gchar fourcc_str[5]; @@ -539,7 +539,7 @@ try_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *format) int -g_crop_ioctl(struct v4l_gst *priv, struct v4l2_crop *crop) +v4l_gst_g_crop_ioctl(struct v4l_gst *priv, struct v4l2_crop *crop) { const gchar *buf_type; #ifdef ENABLE_VIDIOC_DEBUG diff --git a/lib/libv4l-gst/v4l-gst-internal.h b/lib/libv4l-gst/v4l-gst-internal.h index 0812649..8abee60 100644 --- a/lib/libv4l-gst/v4l-gst-internal.h +++ b/lib/libv4l-gst/v4l-gst-internal.h @@ -176,36 +176,47 @@ extern GstDebugCategory *v4l_gst_buffer_debug_category; GQuark cap_buf_crc_quark(void); /* - * Functions shared between the v4l-gst-*.c translation units. + * Functions shared between the v4l-gst-*.c translation units, + * grouped by domain. */ -void reset_cap_timestamp_state(struct v4l_gst *priv); -GstElement *create_pipeline(const gchar *pipeline_str); -GstElement *get_peer_element(GstElement *elem, const gchar *pad_name); -void set_buffer_pool_params(GstBufferPool *pool, GstCaps *caps, - guint buf_size, guint min_buffers, - guint max_buffers, GstVideoAlignment *alignment); -void get_buffer_pool_params(GstBufferPool *pool, GstCaps **caps, - guint *buf_size, guint *min_buffers, - guint *max_buffers); -void push_source_change_event(struct v4l_gst *priv); -void set_pipeline_started(struct v4l_gst *priv, gboolean started); -void release_out_buffer(struct v4l_gst *priv, GstBuffer *gstbuf); -GstPadProbeReturn decoder_sink_pad_probe(GstPad *pad, - GstPadProbeInfo *probe_info, - gpointer user_data); -void decoder_pad_unlinked_cb(GstPad *self, GstPad *peer, gpointer data); -gulong setup_query_pad_probe(struct v4l_gst *priv); -void appsink_callback_eos(GstAppSink *appsink, gpointer user_data); -GstFlowReturn appsink_callback_new_sample(GstAppSink *appsink, - gpointer user_data); -gboolean init_pipeline(struct v4l_gst *priv, guint32 fourcc); -void init_decoded_frame_params(struct v4l2_pix_format_mplane *pix_fmt); -gboolean get_raw_video_params(GstBufferPool *pool, GstBuffer *gstbuf, - GstVideoInfo *info, GstVideoMeta **meta); -GstCaps *get_codec_caps_from_fourcc(guint fourcc); -int flush_pipeline(struct v4l_gst *priv); -int stop_pipeline(struct v4l_gst *priv); -gboolean set_out_format_to_pipeline(struct v4l_gst *priv); -gboolean set_cap_format_to_pipeline(struct v4l_gst *priv); + +/* 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); +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); +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 index 726e9d3..e21aeac 100644 --- a/lib/libv4l-gst/v4l-gst-pipeline.c +++ b/lib/libv4l-gst/v4l-gst-pipeline.c @@ -88,7 +88,7 @@ get_cap_buffer_alignment(struct v4l_gst *priv, GstVideoAlignment *alignment) void -set_buffer_pool_params(GstBufferPool *pool, GstCaps *caps, guint buf_size, +v4l_gst_pipeline_set_buffer_pool_params(GstBufferPool *pool, GstCaps *caps, guint buf_size, guint min_buffers, guint max_buffers, GstVideoAlignment *alignment) { @@ -107,7 +107,7 @@ set_buffer_pool_params(GstBufferPool *pool, GstCaps *caps, guint buf_size, void -get_buffer_pool_params(GstBufferPool *pool, GstCaps **caps, guint *buf_size, +v4l_gst_pipeline_get_buffer_pool_params(GstBufferPool *pool, GstCaps **caps, guint *buf_size, guint *min_buffers, guint *max_buffers) { GstStructure *config; @@ -205,7 +205,7 @@ pad_probe_query(GstPad *pad, GstPadProbeInfo *probe_info, gpointer user_data) retrieve_cap_format_info(priv, &info); get_cap_buffer_alignment(priv, &alignment); g_atomic_int_set(&priv->cap.fmt_acquirable, 1); - push_source_change_event(priv); + v4l_gst_core_push_source_change_event(priv); set_event(priv->event_state, POLLOUT); @@ -223,7 +223,7 @@ pad_probe_query(GstPad *pad, GstPadProbeInfo *probe_info, gpointer user_data) `pipeline=h264parse ! omxh264dec no-reorder=true num-outbufs=7` */ if (wait_for_cap_reqbuf_invocation(priv)) { - set_buffer_pool_params(priv->cap.pool, caps, info.size, + 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, @@ -250,7 +250,7 @@ appsink_pad_unlinked_cb(GstPad *self, GstPad *peer, gpointer data) } -GstPadProbeReturn +static GstPadProbeReturn decoder_sink_pad_probe(GstPad *pad, GstPadProbeInfo *probe_info, gpointer user_data) { struct v4l_gst *priv = user_data; @@ -278,7 +278,7 @@ decoder_sink_pad_probe(GstPad *pad, GstPadProbeInfo *probe_info, gpointer user_d } -void +static void decoder_pad_unlinked_cb(GstPad *self, GstPad *peer, gpointer data) { struct v4l_gst *priv = data; @@ -290,7 +290,7 @@ decoder_pad_unlinked_cb(GstPad *self, GstPad *peer, gpointer data) gulong -setup_query_pad_probe(struct v4l_gst *priv) +v4l_gst_pipeline_setup_query_pad_probe(struct v4l_gst *priv) { gulong probe_id; @@ -320,12 +320,12 @@ pull_buffer_from_sample(GstAppSink *appsink) } -void +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); + 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)); @@ -338,7 +338,7 @@ appsink_callback_eos(GstAppSink *appsink, gpointer user_data) } -GstFlowReturn +static GstFlowReturn appsink_callback_new_sample(GstAppSink *appsink, gpointer user_data) { struct v4l_gst *priv = user_data; @@ -404,7 +404,39 @@ appsink_callback_new_sample(GstAppSink *appsink, gpointer user_data) gboolean -get_raw_video_params(GstBufferPool *pool, GstBuffer *gstbuf, GstVideoInfo *info, +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); + + 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; @@ -412,7 +444,7 @@ get_raw_video_params(GstBufferPool *pool, GstBuffer *gstbuf, GstVideoInfo *info, GstVideoInfo vinfo; GstVideoMeta *vmeta; - get_buffer_pool_params(pool, &caps, NULL, NULL, NULL); + 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) @@ -432,7 +464,7 @@ get_raw_video_params(GstBufferPool *pool, GstBuffer *gstbuf, GstVideoInfo *info, GstCaps * -get_codec_caps_from_fourcc(guint fourcc) +v4l_gst_pipeline_get_codec_caps_from_fourcc(guint fourcc) { const gchar *mime; @@ -456,7 +488,7 @@ get_codec_caps_from_fourcc(guint fourcc) int -flush_pipeline(struct v4l_gst *priv) +v4l_gst_pipeline_flush(struct v4l_gst *priv) { GstEvent *event; @@ -491,7 +523,7 @@ flush_pipeline(struct v4l_gst *priv) int -stop_pipeline(struct v4l_gst *priv) +v4l_gst_pipeline_stop(struct v4l_gst *priv) { GstStateChangeReturn state_ret; int ret = 0; @@ -499,7 +531,7 @@ stop_pipeline(struct v4l_gst *priv) GST_DEBUG("req->count == 0, stop the pipeline"); - set_pipeline_started(priv, FALSE); + v4l_gst_core_set_pipeline_started(priv, FALSE); state_ret = gst_element_set_state(priv->pipeline, GST_STATE_NULL); @@ -531,16 +563,16 @@ stop_pipeline(struct v4l_gst *priv) g_queue_clear(priv->out.gstbufs_queue); g_queue_clear(priv->cap.gstbufs_queue); - reset_cap_timestamp_state(priv); + v4l_gst_core_reset_cap_timestamp_state(priv); - set_pipeline_started(priv, FALSE); + 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; - init_decoded_frame_params(&priv->cap.fmt); + v4l_gst_fmt_init_decoded_frame_params(&priv->cap.fmt); return ret; } @@ -556,11 +588,11 @@ relink_elements_with_caps_filtered(GstElement *src_elem, GstElement *dest_elem, gboolean -set_out_format_to_pipeline(struct v4l_gst *priv) +v4l_gst_pipeline_set_out_format(struct v4l_gst *priv) { GstCaps *caps; - caps = get_codec_caps_from_fourcc(priv->out.fmt.pixelformat); + caps = v4l_gst_pipeline_get_codec_caps_from_fourcc(priv->out.fmt.pixelformat); if (!caps) { errno = EINVAL; return FALSE; @@ -574,7 +606,7 @@ set_out_format_to_pipeline(struct v4l_gst *priv) gboolean -set_cap_format_to_pipeline(struct v4l_gst *priv) +v4l_gst_pipeline_set_cap_format(struct v4l_gst *priv) { GstElement *peer_elem; GstCaps *caps; @@ -594,7 +626,7 @@ set_cap_format_to_pipeline(struct v4l_gst *priv) 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"); + 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 " @@ -656,7 +688,7 @@ set_decoder_cmd_state(struct v4l_gst *priv, GstState state) #endif int -try_decoder_cmd_ioctl(struct v4l_gst *priv, +v4l_gst_try_decoder_cmd_ioctl(struct v4l_gst *priv, struct v4l2_decoder_cmd *decoder_cmd) { int ret = 0; @@ -697,7 +729,7 @@ try_decoder_cmd_ioctl(struct v4l_gst *priv, int -decoder_cmd_ioctl(struct v4l_gst *priv, struct v4l2_decoder_cmd *decoder_cmd) +v4l_gst_decoder_cmd_ioctl(struct v4l_gst *priv, struct v4l2_decoder_cmd *decoder_cmd) { int ret = 0; diff --git a/lib/libv4l-gst/v4l-gst-testhooks.c b/lib/libv4l-gst/v4l-gst-testhooks.c index ce05e4d..3361e83 100644 --- a/lib/libv4l-gst/v4l-gst-testhooks.c +++ b/lib/libv4l-gst/v4l-gst-testhooks.c @@ -8,7 +8,7 @@ GstElement * test_create_pipeline(const gchar *pipeline_str) { - return create_pipeline(pipeline_str); + return v4l_gst_core_create_pipeline(pipeline_str); } static void @@ -27,7 +27,7 @@ prepare_format_backend_fixture(struct v4l_gst *priv) if (!priv) return; if (!priv->pipeline) - priv->pipeline = create_pipeline("identity"); + 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; diff --git a/lib/libv4l-gst/v4l-gst.h b/lib/libv4l-gst/v4l-gst.h index 08ff4a7..2a06796 100644 --- a/lib/libv4l-gst/v4l-gst.h +++ b/lib/libv4l-gst/v4l-gst.h @@ -23,33 +23,65 @@ 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); +/* + * 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_try_decoder_cmd_ioctl(struct v4l_gst *priv, + struct v4l2_decoder_cmd *decoder_cmd); +int v4l_gst_decoder_cmd_ioctl(struct v4l_gst *priv, + struct v4l2_decoder_cmd *decoder_cmd); + +/* fmt: format */ +int v4l_gst_querycap_ioctl(struct v4l_gst *priv, + struct v4l2_capability *cap); +int v4l_gst_set_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *fmt); +int v4l_gst_get_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *fmt); +int v4l_gst_enum_fmt_ioctl(struct v4l_gst *priv, struct v4l2_fmtdesc *desc); +int v4l_gst_enum_framesizes_ioctl(struct v4l_gst *priv, + struct v4l2_frmsizeenum *argp); +int v4l_gst_g_selection_ioctl(struct v4l_gst *priv, + struct v4l2_selection *selection); +int v4l_gst_g_crop_ioctl(struct v4l_gst *priv, struct v4l2_crop *crop); +int v4l_gst_try_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *format); + +/* buf: buffer */ +int v4l_gst_qbuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *buf); +int v4l_gst_dqbuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *buf); +int v4l_gst_querybuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *buf); +int v4l_gst_reqbuf_ioctl(struct v4l_gst *priv, + struct v4l2_requestbuffers *req); +int v4l_gst_streamon_ioctl(struct v4l_gst *priv, enum v4l2_buf_type *type); +int v4l_gst_streamoff_ioctl(struct v4l_gst *priv, + enum v4l2_buf_type *type); +int v4l_gst_expbuf_ioctl(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_get_ctrl_ioctl(struct v4l_gst *priv, + struct v4l2_control *ctrl); +int v4l_gst_get_ext_ctrl_ioctl(struct v4l_gst *priv, + struct v4l2_ext_controls *ext_ctrls); +int v4l_gst_queryctrl_ioctl(struct v4l_gst *priv, + struct v4l2_queryctrl *query_ctrl); +int v4l_gst_querymenu_ioctl(struct v4l_gst *priv, + struct v4l2_querymenu *query_menu); + +/* event: event */ +int v4l_gst_subscribe_event_ioctl(struct v4l_gst *priv, + struct v4l2_event_subscription *sub); +int v4l_gst_dqevent_ioctl(struct v4l_gst *priv, struct v4l2_event *ev); +int v4l_gst_unsubscribe_event_ioctl(struct v4l_gst *priv, + struct v4l2_event_subscription *subscription); #define ENV_DISABLE_VIDIOC_FEATURES "DISABLE_VIDIOC_FEATURES" From ce2e606793b5243717bca289c99577cc4ca003e0 Mon Sep 17 00:00:00 2001 From: Takuro Ashie Date: Tue, 29 Sep 2026 14:58:59 +0900 Subject: [PATCH 04/13] Align function declarations in GTK style in v4l-gst headers Assisted-by: Qwen3.8-27B --- lib/libv4l-gst/v4l-gst-internal.h | 67 +++++++++++---------- lib/libv4l-gst/v4l-gst.h | 99 ++++++++++++++++++------------- 2 files changed, 92 insertions(+), 74 deletions(-) diff --git a/lib/libv4l-gst/v4l-gst-internal.h b/lib/libv4l-gst/v4l-gst-internal.h index 8abee60..aa6a537 100644 --- a/lib/libv4l-gst/v4l-gst-internal.h +++ b/lib/libv4l-gst/v4l-gst-internal.h @@ -173,7 +173,7 @@ extern GstDebugCategory *v4l_gst_ioctl_debug_category; extern GstDebugCategory *v4l_gst_buffer_debug_category; #define GST_CAT_DEFAULT v4l_gst_debug_category -GQuark cap_buf_crc_quark(void); +GQuark cap_buf_crc_quark (void); /* * Functions shared between the v4l-gst-*.c translation units, @@ -181,42 +181,45 @@ GQuark cap_buf_crc_quark(void); */ /* 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); -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); +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); +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); -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); +GstCaps * v4l_gst_pipeline_get_codec_caps_from_fourcc (guint fourcc); +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); +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); +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.h b/lib/libv4l-gst/v4l-gst.h index 2a06796..2242510 100644 --- a/lib/libv4l-gst/v4l-gst.h +++ b/lib/libv4l-gst/v4l-gst.h @@ -30,58 +30,73 @@ struct v4l_gst; */ /* core: backend lifecycle */ -struct v4l_gst *v4l_gst_init(int fd); -void v4l_gst_deinit(struct v4l_gst *priv); +struct v4l_gst * v4l_gst_init (int fd); +void v4l_gst_deinit (struct v4l_gst *priv); /* pipeline: decoder command */ -int v4l_gst_try_decoder_cmd_ioctl(struct v4l_gst *priv, - struct v4l2_decoder_cmd *decoder_cmd); -int v4l_gst_decoder_cmd_ioctl(struct v4l_gst *priv, - struct v4l2_decoder_cmd *decoder_cmd); +int v4l_gst_try_decoder_cmd_ioctl (struct v4l_gst *priv, + struct v4l2_decoder_cmd *decoder_cmd); +int v4l_gst_decoder_cmd_ioctl (struct v4l_gst *priv, + struct v4l2_decoder_cmd *decoder_cmd); /* fmt: format */ -int v4l_gst_querycap_ioctl(struct v4l_gst *priv, - struct v4l2_capability *cap); -int v4l_gst_set_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *fmt); -int v4l_gst_get_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *fmt); -int v4l_gst_enum_fmt_ioctl(struct v4l_gst *priv, struct v4l2_fmtdesc *desc); -int v4l_gst_enum_framesizes_ioctl(struct v4l_gst *priv, - struct v4l2_frmsizeenum *argp); -int v4l_gst_g_selection_ioctl(struct v4l_gst *priv, - struct v4l2_selection *selection); -int v4l_gst_g_crop_ioctl(struct v4l_gst *priv, struct v4l2_crop *crop); -int v4l_gst_try_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *format); +int v4l_gst_querycap_ioctl (struct v4l_gst *priv, + struct v4l2_capability *cap); +int v4l_gst_set_fmt_ioctl (struct v4l_gst *priv, + struct v4l2_format *fmt); +int v4l_gst_get_fmt_ioctl (struct v4l_gst *priv, + struct v4l2_format *fmt); +int v4l_gst_enum_fmt_ioctl (struct v4l_gst *priv, + struct v4l2_fmtdesc *desc); +int v4l_gst_enum_framesizes_ioctl (struct v4l_gst *priv, + struct v4l2_frmsizeenum *argp); +int v4l_gst_g_selection_ioctl (struct v4l_gst *priv, + struct v4l2_selection *selection); +int v4l_gst_g_crop_ioctl (struct v4l_gst *priv, + struct v4l2_crop *crop); +int v4l_gst_try_fmt_ioctl (struct v4l_gst *priv, + struct v4l2_format *format); /* buf: buffer */ -int v4l_gst_qbuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *buf); -int v4l_gst_dqbuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *buf); -int v4l_gst_querybuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *buf); -int v4l_gst_reqbuf_ioctl(struct v4l_gst *priv, - struct v4l2_requestbuffers *req); -int v4l_gst_streamon_ioctl(struct v4l_gst *priv, enum v4l2_buf_type *type); -int v4l_gst_streamoff_ioctl(struct v4l_gst *priv, - enum v4l2_buf_type *type); -int v4l_gst_expbuf_ioctl(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); +int v4l_gst_qbuf_ioctl (struct v4l_gst *priv, + struct v4l2_buffer *buf); +int v4l_gst_dqbuf_ioctl (struct v4l_gst *priv, + struct v4l2_buffer *buf); +int v4l_gst_querybuf_ioctl (struct v4l_gst *priv, + struct v4l2_buffer *buf); +int v4l_gst_reqbuf_ioctl (struct v4l_gst *priv, + struct v4l2_requestbuffers *req); +int v4l_gst_streamon_ioctl (struct v4l_gst *priv, + enum v4l2_buf_type *type); +int v4l_gst_streamoff_ioctl (struct v4l_gst *priv, + enum v4l2_buf_type *type); +int v4l_gst_expbuf_ioctl (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_get_ctrl_ioctl(struct v4l_gst *priv, - struct v4l2_control *ctrl); -int v4l_gst_get_ext_ctrl_ioctl(struct v4l_gst *priv, - struct v4l2_ext_controls *ext_ctrls); -int v4l_gst_queryctrl_ioctl(struct v4l_gst *priv, - struct v4l2_queryctrl *query_ctrl); -int v4l_gst_querymenu_ioctl(struct v4l_gst *priv, - struct v4l2_querymenu *query_menu); +int v4l_gst_get_ctrl_ioctl (struct v4l_gst *priv, + struct v4l2_control *ctrl); +int v4l_gst_get_ext_ctrl_ioctl (struct v4l_gst *priv, + struct v4l2_ext_controls *ext_ctrls); +int v4l_gst_queryctrl_ioctl (struct v4l_gst *priv, + struct v4l2_queryctrl *query_ctrl); +int v4l_gst_querymenu_ioctl (struct v4l_gst *priv, + struct v4l2_querymenu *query_menu); /* event: event */ -int v4l_gst_subscribe_event_ioctl(struct v4l_gst *priv, - struct v4l2_event_subscription *sub); -int v4l_gst_dqevent_ioctl(struct v4l_gst *priv, struct v4l2_event *ev); -int v4l_gst_unsubscribe_event_ioctl(struct v4l_gst *priv, - struct v4l2_event_subscription *subscription); +int v4l_gst_subscribe_event_ioctl (struct v4l_gst *priv, + struct v4l2_event_subscription *sub); +int v4l_gst_dqevent_ioctl (struct v4l_gst *priv, + struct v4l2_event *ev); +int v4l_gst_unsubscribe_event_ioctl (struct v4l_gst *priv, + struct v4l2_event_subscription *subscription); #define ENV_DISABLE_VIDIOC_FEATURES "DISABLE_VIDIOC_FEATURES" From fdb45f64146b892acd124a8a892eb5ccfafedff6 Mon Sep 17 00:00:00 2001 From: Takuro Ashie Date: Tue, 29 Sep 2026 17:52:34 +0900 Subject: [PATCH 05/13] Add encode-pipeline config and derive M2M role from pipelines Each codec section may now define a decode pipeline ("pipeline") and/or an encode pipeline ("encode-pipeline"). v4l_gst_core_setup_role() determines the M2M role from the configuration and rebuilds the supported format lists accordingly; with both pipelines configured the role is derived from the first OUTPUT format set. enum_fmt flags entries by media kind, queryctrl profile checks are role-aware, and the encoded-stream parameter reset is skipped while the backend acts as an encoder. Adds encode-only and dual-role test fixtures and 14 tests covering role derivation and format contracts. Assisted-by: Qwen3.8-27B --- lib/libv4l-gst/v4l-gst-core.c | 180 +++++++++--- lib/libv4l-gst/v4l-gst-ctrl.c | 32 ++- lib/libv4l-gst/v4l-gst-fmt.c | 59 +++- lib/libv4l-gst/v4l-gst-internal.h | 21 ++ lib/libv4l-gst/v4l-gst-testhooks.c | 56 ++++ lib/libv4l-gst/v4l-gst-testhooks.h | 5 + tests/test-gst-backend.c | 438 +++++++++++++++++++++++++++++ 7 files changed, 737 insertions(+), 54 deletions(-) diff --git a/lib/libv4l-gst/v4l-gst-core.c b/lib/libv4l-gst/v4l-gst-core.c index e2990a0..ff01260 100644 --- a/lib/libv4l-gst/v4l-gst-core.c +++ b/lib/libv4l-gst/v4l-gst-core.c @@ -70,6 +70,7 @@ parse_config_file(struct v4l_gst *priv) gsize n_groups; gint i; guint n_pipelines = 0; + guint n_encode_pipelines = 0; sys_conf_dirs = g_get_system_config_dirs(); @@ -165,10 +166,13 @@ parse_config_file(struct v4l_gst *priv) /* [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; + gchar *pipeline_str, *encode_pipeline_str; if (!g_strcmp0(groups[i], libv4l_gst_group)) continue; @@ -178,30 +182,53 @@ parse_config_file(struct v4l_gst *priv) 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) { - GST_ERROR("GStreamer pipeline is not specified"); - if (err) g_error_free(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; } - 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++; + 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) + if (n_pipelines == 0 && n_encode_pipelines == 0) GST_ERROR("no pipeline!"); - return n_pipelines > 0; + return (n_pipelines + n_encode_pipelines) > 0; } @@ -431,30 +458,17 @@ fill_out_fmts_func(gpointer key, gpointer value, gpointer user_data) } -static gboolean -fill_config_video_format_out(struct v4l_gst *priv) +static void +fill_codec_fmts(GHashTable *pipelines, GArray *fmts) { - gint i; - gchar codecs[256] = {0}; - - g_array_set_size(priv->out.supported_fmts, 0); - g_hash_table_foreach(priv->config.pipelines, + g_hash_table_foreach(pipelines, fill_out_fmts_func, - priv->out.supported_fmts); - - for (i = 0; i < priv->out.supported_fmts->len; i++) { - struct fmt *fmts = (struct fmt*)priv->out.supported_fmts->data; - g_strlcat(codecs, fmts[i].desc, sizeof(codecs)); - g_strlcat(codecs, " ", sizeof(codecs)); - } - GST_DEBUG("supported codecs: %s", codecs); - - return priv->out.supported_fmts->len > 0; + fmts); } static void -fill_config_video_format_cap(struct v4l_gst *priv) +fill_raw_fmts(struct v4l_gst *priv, GArray *fmts) { struct fmt color_fmt; @@ -465,8 +479,74 @@ fill_config_video_format_cap(struct v4l_gst *priv) color_fmt.fourcc = fourcc_from_string("NV12"); g_strlcpy(color_fmt.desc, "NV12", FMTDESC_NAME_LENGTH); } - g_array_prepend_vals(priv->cap.supported_fmts, - &color_fmt, 1); + 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; } @@ -739,10 +819,17 @@ 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); - pipeline = g_hash_table_lookup(priv->config.pipelines, 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); @@ -760,7 +847,10 @@ v4l_gst_core_init_pipeline(struct v4l_gst *priv, guint32 fourcc) if (!init_buffer_pool(priv)) goto error; - priv->out.fmt.pixelformat = fourcc; + if (priv->role == V4L_GST_ROLE_ENCODER) + priv->cap.fmt.pixelformat = fourcc; + else + priv->out.fmt.pixelformat = fourcc; return TRUE; @@ -837,24 +927,16 @@ v4l_gst_init(int fd) goto error; } - /* - * Only the M2M decoder role is supported at the moment: - * OUTPUT carries a compressed bitstream and CAPTURE - * carries decoded raw frames. - */ priv->out.buf_type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; - priv->out.kind = V4L_GST_MEDIA_KIND_CODEC; priv->cap.buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; - priv->cap.kind = V4L_GST_MEDIA_KIND_RAW; 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 (!fill_config_video_format_out(priv)) { - GST_ERROR("Failed to fill in supported video format"); + if (!v4l_gst_core_setup_role(priv)) { + GST_ERROR("Failed to set up the M2M role"); goto error; } - fill_config_video_format_cap(priv); g_mutex_init(&priv->v4l2events.mutex); priv->v4l2events.subscribed = 0; @@ -869,20 +951,28 @@ v4l_gst_init(int fd) g_mutex_init(&priv->dev_lock); if (priv->config.fixed_pipeline) { - if (!v4l_gst_core_init_pipeline(priv, 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: + 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); @@ -953,6 +1043,8 @@ v4l_gst_deinit(struct v4l_gst *priv) 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); diff --git a/lib/libv4l-gst/v4l-gst-ctrl.c b/lib/libv4l-gst/v4l-gst-ctrl.c index df7e2f8..e22869d 100644 --- a/lib/libv4l-gst/v4l-gst-ctrl.c +++ b/lib/libv4l-gst/v4l-gst-ctrl.c @@ -97,12 +97,34 @@ v4l_gst_get_ext_ctrl_ioctl(struct v4l_gst *priv, struct v4l2_ext_controls *ext_c } +/* + * 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_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")) { @@ -118,8 +140,7 @@ v4l_gst_queryctrl_ioctl(struct v4l_gst *priv, struct v4l2_queryctrl *query_ctrl) 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)) { + 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 { @@ -129,8 +150,7 @@ v4l_gst_queryctrl_ioctl(struct v4l_gst *priv, struct v4l2_queryctrl *query_ctrl) } 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)) { + 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 { diff --git a/lib/libv4l-gst/v4l-gst-fmt.c b/lib/libv4l-gst/v4l-gst-fmt.c index 1306ee0..f3df91d 100644 --- a/lib/libv4l-gst/v4l-gst-fmt.c +++ b/lib/libv4l-gst/v4l-gst-fmt.c @@ -125,7 +125,32 @@ set_fmt_ioctl_out(struct v4l_gst *priv, struct v4l2_format *fmt) return -1; } - if (priv->pipeline) { + 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); @@ -147,7 +172,8 @@ set_fmt_ioctl_out(struct v4l_gst *priv, struct v4l2_format *fmt) priv->out.fmt = *pix_fmt; - set_params_as_encoded_stream(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 @@ -179,6 +205,13 @@ set_fmt_ioctl_cap(struct v4l_gst *priv, struct v4l2_format *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)) { @@ -187,6 +220,11 @@ set_fmt_ioctl_cap(struct v4l_gst *priv, struct v4l2_format *fmt) 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 (GST_STATE(priv->pipeline) == GST_STATE_NULL) { priv->cap.fmt.pixelformat = pix_fmt->pixelformat; @@ -301,7 +339,8 @@ v4l_gst_get_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *fmt) g_mutex_lock(&priv->dev_lock); *pix_fmt = priv->out.fmt; g_mutex_unlock(&priv->dev_lock); - set_params_as_encoded_stream(pix_fmt); + 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); @@ -350,6 +389,17 @@ v4l_gst_enum_fmt_ioctl(struct v4l_gst *priv, struct v4l2_fmtdesc *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); @@ -479,7 +529,8 @@ try_fmt_ioctl_out(struct v4l_gst *priv, struct v4l2_format *format) return -1; } - set_params_as_encoded_stream(pix_fmt); + if (priv->role != V4L_GST_ROLE_ENCODER) + set_params_as_encoded_stream(pix_fmt); return 0; } diff --git a/lib/libv4l-gst/v4l-gst-internal.h b/lib/libv4l-gst/v4l-gst-internal.h index aa6a537..f2c996a 100644 --- a/lib/libv4l-gst/v4l-gst-internal.h +++ b/lib/libv4l-gst/v4l-gst-internal.h @@ -72,11 +72,26 @@ enum v4l_gst_media_kind { 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; @@ -130,7 +145,11 @@ struct 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; @@ -153,6 +172,7 @@ struct v4l_gst { 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; @@ -186,6 +206,7 @@ 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, diff --git a/lib/libv4l-gst/v4l-gst-testhooks.c b/lib/libv4l-gst/v4l-gst-testhooks.c index 3361e83..455c6af 100644 --- a/lib/libv4l-gst/v4l-gst-testhooks.c +++ b/lib/libv4l-gst/v4l-gst-testhooks.c @@ -42,4 +42,60 @@ prepare_format_backend_fixture(struct v4l_gst *priv) 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; +} + +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 index cf1c6b8..dd8cbc7 100644 --- a/lib/libv4l-gst/v4l-gst-testhooks.h +++ b/lib/libv4l-gst/v4l-gst-testhooks.h @@ -7,10 +7,15 @@ #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); +enum v4l_gst_role get_backend_role(struct v4l_gst *priv); #endif #endif /* __V4L_GST_TESTHOOKS_H__ */ diff --git a/tests/test-gst-backend.c b/tests/test-gst-backend.c index 0869a4a..4340a69 100644 --- a/tests/test-gst-backend.c +++ b/tests/test-gst-backend.c @@ -914,3 +914,441 @@ 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, + }; + 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); +} From 32cb908e6e7befc77f4684d4921863191848719c Mon Sep 17 00:00:00 2001 From: Takuro Ashie Date: Thu, 1 Oct 2026 22:20:17 +0900 Subject: [PATCH 06/13] Add encoder streaming support for encoded CAPTURE output Support the encoder role end-to-end: OUTPUT raw NV12 frames are pushed to the pipeline and x264enc produces an H264 bitstream on CAPTURE. - Negotiate codec CAPTURE caps and handle encoded buffers (no buffer pool / no video meta) in create_cap_buffers_list, dqbuf, mmap and reqbuf. - Push OUTPUT caps to the appsrc before the first buffer so the base source negotiates before the first push (avoids NOT_NEGOTIATED). - Always dequeue the last encoded CAPTURE buffer; the encoder signals EOS via the appsrc EOS event rather than a held-back buffer. - Add prepare_x264enc_backend_fixture and a streaming test asserting the dequeued CAPTURE buffers carry an H264 start code. Assisted-by: Qwen3.8-27B --- lib/libv4l-gst/v4l-gst-buf.c | 132 ++++++++++++++-------- lib/libv4l-gst/v4l-gst-core.c | 147 +++++++++++++++++++++---- lib/libv4l-gst/v4l-gst-fmt.c | 30 ++++- lib/libv4l-gst/v4l-gst-internal.h | 6 + lib/libv4l-gst/v4l-gst-pipeline.c | 106 ++++++++++++++++-- lib/libv4l-gst/v4l-gst-testhooks.c | 36 ++++++ lib/libv4l-gst/v4l-gst-testhooks.h | 1 + tests/test-gst-backend.c | 170 +++++++++++++++++++++++++++++ 8 files changed, 552 insertions(+), 76 deletions(-) diff --git a/lib/libv4l-gst/v4l-gst-buf.c b/lib/libv4l-gst/v4l-gst-buf.c index 798195e..4b6d75d 100644 --- a/lib/libv4l-gst/v4l-gst-buf.c +++ b/lib/libv4l-gst/v4l-gst-buf.c @@ -184,6 +184,16 @@ qbuf_ioctl_out(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) 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); @@ -485,10 +495,15 @@ dequeue_cap_buffer(struct v4l_gst *priv) 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; + /* 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); @@ -745,8 +760,14 @@ dqbuf_ioctl_cap(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) 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.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 @@ -1158,7 +1179,11 @@ reqbuf_ioctl_out(struct v4l_gst *priv, } } - caps = v4l_gst_pipeline_get_codec_caps_from_fourcc(priv->out.fmt.pixelformat); + 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; @@ -1313,35 +1338,47 @@ create_cap_buffers_list(struct v4l_gst *priv) return 0; } - if (!first_gstbuf->pool) { - GST_ERROR("Cannot handle buffers not belonging to " - "a bufferpool"); - 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); - } + 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; - } + /* 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; + } - 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; + /* 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 @@ -1474,7 +1511,8 @@ streamon_ioctl_out(struct v4l_gst *priv) g_mutex_lock(&priv->dev_lock); if (state == GST_STATE_NULL) { - if (!v4l_gst_pipeline_set_out_format(priv)) + if (!priv->out_caps_set && + !v4l_gst_pipeline_set_out_format(priv)) return -1; if (!v4l_gst_pipeline_set_cap_format(priv)) return -1; @@ -1646,17 +1684,23 @@ map_cap_buffer(struct v4l_gst *priv, int index, int plane, return MAP_FAILED; } - if (!v4l_gst_pipeline_get_raw_video_params(priv->cap.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; - } + 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, + NULL, &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->offset[plane]; + data = info.data + meta->offset[plane]; + } gst_buffer_unmap(priv->cap.buffers[index].gstbuf, &info); diff --git a/lib/libv4l-gst/v4l-gst-core.c b/lib/libv4l-gst/v4l-gst-core.c index ff01260..c829d58 100644 --- a/lib/libv4l-gst/v4l-gst-core.c +++ b/lib/libv4l-gst/v4l-gst-core.c @@ -565,32 +565,102 @@ get_supported_video_format_out(struct v4l_gst *priv) return FALSE; } - structure = gst_caps_get_structure(caps, 0); - mime = gst_structure_get_name(structure); + 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); - 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); + 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); - g_array_set_size(priv->out.supported_fmts, 0); - return FALSE; + + return TRUE; } - GST_DEBUG("out supported codec : %s", mime); - g_array_set_size(priv->out.supported_fmts, 1); - fmt = (struct fmt*)priv->out.supported_fmts->data; + /* 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; - 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); + structs = gst_caps_get_size(caps); - return TRUE; + 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; + } } @@ -618,6 +688,43 @@ get_supported_video_format_cap(struct v4l_gst *priv) 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"); diff --git a/lib/libv4l-gst/v4l-gst-fmt.c b/lib/libv4l-gst/v4l-gst-fmt.c index f3df91d..83d9540 100644 --- a/lib/libv4l-gst/v4l-gst-fmt.c +++ b/lib/libv4l-gst/v4l-gst-fmt.c @@ -220,13 +220,40 @@ set_fmt_ioctl_cap(struct v4l_gst *priv, struct v4l2_format *fmt) 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 (GST_STATE(priv->pipeline) == GST_STATE_NULL) { + 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 || @@ -240,6 +267,7 @@ set_fmt_ioctl_cap(struct v4l_gst *priv, struct v4l2_format *fmt) 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); diff --git a/lib/libv4l-gst/v4l-gst-internal.h b/lib/libv4l-gst/v4l-gst-internal.h index f2c996a..1845168 100644 --- a/lib/libv4l-gst/v4l-gst-internal.h +++ b/lib/libv4l-gst/v4l-gst-internal.h @@ -160,6 +160,11 @@ struct v4l_gst { 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; @@ -214,6 +219,7 @@ void v4l_gst_core_set_pipeline_started (struct v4l_gst *priv, /* 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, diff --git a/lib/libv4l-gst/v4l-gst-pipeline.c b/lib/libv4l-gst/v4l-gst-pipeline.c index e21aeac..7230ef3 100644 --- a/lib/libv4l-gst/v4l-gst-pipeline.c +++ b/lib/libv4l-gst/v4l-gst-pipeline.c @@ -186,6 +186,34 @@ pad_probe_query(GstPad *pad, GstPadProbeInfo *probe_info, gpointer user_data) 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; @@ -410,6 +438,13 @@ v4l_gst_pipeline_setup_app_elements(struct v4l_gst *priv) The amount of buffers is managed by the buffer pool. */ gst_app_src_set_max_bytes(GST_APP_SRC(priv->appsrc), 0); + /* Video frames are timestamped in time, not bytes. The appsrc + "format" property drives the segment format (gst_app_src_start + copies it into the base src), and do-timestamp assigns PTS/DTS + from the caps framerate since the wrapped buffers carry no PTS. */ + 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; @@ -487,6 +522,34 @@ 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) +{ + 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) { @@ -554,6 +617,10 @@ v4l_gst_pipeline_stop(struct v4l_gst *priv) 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) { @@ -592,7 +659,11 @@ v4l_gst_pipeline_set_out_format(struct v4l_gst *priv) { GstCaps *caps; - caps = v4l_gst_pipeline_get_codec_caps_from_fourcc(priv->out.fmt.pixelformat); + 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; @@ -610,22 +681,35 @@ v4l_gst_pipeline_set_cap_format(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); + 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; } - caps = gst_caps_new_simple("video/x-raw", "format", G_TYPE_STRING, - gst_video_format_to_string(fmt), NULL); - peer_elem = v4l_gst_core_get_peer_element(priv->appsink, "sink"); if (!relink_elements_with_caps_filtered(peer_elem, priv->appsink, caps)) { diff --git a/lib/libv4l-gst/v4l-gst-testhooks.c b/lib/libv4l-gst/v4l-gst-testhooks.c index 455c6af..d604eb4 100644 --- a/lib/libv4l-gst/v4l-gst-testhooks.c +++ b/lib/libv4l-gst/v4l-gst-testhooks.c @@ -92,6 +92,42 @@ prepare_dual_role_backend_fixture(struct v4l_gst *priv) priv->out.cnt = INPUT_BUFFERING_CNT; } +/* + * Reconfigure the backend into an encoder role backed by a real x264enc + * GStreamer pipeline, so that OUTPUT raw frames are encoded to an H264 + * stream on CAPTURE. The "identity" placeholder pipeline created by + * prepare_format_backend_fixture() is dropped so that the encoder pipeline + * is created (via VIDIOC_S_FMT on CAPTURE) with a real encoder. + */ +void +prepare_x264enc_backend_fixture(struct v4l_gst *priv) +{ + if (priv->pipeline) { + gst_object_unref(priv->pipeline); + priv->pipeline = NULL; + } + + g_hash_table_remove(priv->config.pipelines, "H264"); + /* Zero-latency tuning disables lookahead/bframes so that each input + frame produces an output frame immediately, which the CAPTURE + buffer flow depends on. */ + g_hash_table_insert(priv->config.encode_pipelines, g_strdup("H264"), + g_strdup("x264enc tune=zerolatency " + "speed-preset=ultrafast")); + 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 * 1024; + 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) { diff --git a/lib/libv4l-gst/v4l-gst-testhooks.h b/lib/libv4l-gst/v4l-gst-testhooks.h index dd8cbc7..e2a7ee5 100644 --- a/lib/libv4l-gst/v4l-gst-testhooks.h +++ b/lib/libv4l-gst/v4l-gst-testhooks.h @@ -15,6 +15,7 @@ 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_x264enc_backend_fixture(struct v4l_gst *priv); enum v4l_gst_role get_backend_role(struct v4l_gst *priv); #endif diff --git a/tests/test-gst-backend.c b/tests/test-gst-backend.c index 4340a69..949ecba 100644 --- a/tests/test-gst-backend.c +++ b/tests/test-gst-backend.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "utils.h" @@ -1082,6 +1083,7 @@ test_encode_only_set_fmt_capture_accepts_codec(void) .ret = 0, .error_number = 0, .pixelformat = V4L2_PIX_FMT_H264, + .sizeimage = 1024, }; struct pix_format_result actual; const gchar *expected_string; @@ -1352,3 +1354,171 @@ test_dual_set_fmt_capture_rejected_before_role_fixed(void) 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; +} + +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_x264enc_backend_fixture(backend); + 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); +} From 0f0feceaec0b982a7fb1c36bd076575673d1752d Mon Sep 17 00:00:00 2001 From: Takuro Ashie Date: Wed, 7 Oct 2026 13:27:51 +0900 Subject: [PATCH 07/13] Rename public ioctl functions to v4l_gst_ioctl_ prefix Assisted-by: Qwen3.8-27B --- lib/libv4l-gst/libv4l-gst.c | 48 +++++++++++++++---------------- lib/libv4l-gst/v4l-gst-buf.c | 14 ++++----- lib/libv4l-gst/v4l-gst-ctrl.c | 8 +++--- lib/libv4l-gst/v4l-gst-event.c | 6 ++-- lib/libv4l-gst/v4l-gst-fmt.c | 16 +++++------ lib/libv4l-gst/v4l-gst-pipeline.c | 4 +-- lib/libv4l-gst/v4l-gst.h | 48 +++++++++++++++---------------- 7 files changed, 72 insertions(+), 72 deletions(-) diff --git a/lib/libv4l-gst/libv4l-gst.c b/lib/libv4l-gst/libv4l-gst.c index 1a14904..bf07059 100644 --- a/lib/libv4l-gst/libv4l-gst.c +++ b/lib/libv4l-gst/libv4l-gst.c @@ -64,76 +64,76 @@ static int plugin_ioctl(void *dev_ops_priv, int fd, switch (cmd) { case VIDIOC_QUERYCAP: - ret = v4l_gst_querycap_ioctl(priv, arg); + ret = v4l_gst_ioctl_querycap(priv, arg); break; case VIDIOC_S_FMT: - ret = v4l_gst_set_fmt_ioctl(priv, arg); + ret = v4l_gst_ioctl_set_fmt(priv, arg); break; case VIDIOC_G_FMT: - ret = v4l_gst_get_fmt_ioctl(priv, arg); + ret = v4l_gst_ioctl_get_fmt(priv, arg); break; case VIDIOC_ENUM_FMT: - ret = v4l_gst_enum_fmt_ioctl(priv, arg); + ret = v4l_gst_ioctl_enum_fmt(priv, arg); break; case VIDIOC_G_CTRL: - ret = v4l_gst_get_ctrl_ioctl(priv, arg); + ret = v4l_gst_ioctl_get_ctrl(priv, arg); break; case VIDIOC_G_EXT_CTRLS: - ret = v4l_gst_get_ext_ctrl_ioctl(priv, arg); + ret = v4l_gst_ioctl_get_ext_ctrl(priv, arg); break; case VIDIOC_QBUF: - ret = v4l_gst_qbuf_ioctl(priv, arg); + ret = v4l_gst_ioctl_qbuf(priv, arg); break; case VIDIOC_DQBUF: - ret = v4l_gst_dqbuf_ioctl(priv, arg); + ret = v4l_gst_ioctl_dqbuf(priv, arg); break; case VIDIOC_QUERYBUF: - ret = v4l_gst_querybuf_ioctl(priv, arg); + ret = v4l_gst_ioctl_querybuf(priv, arg); break; case VIDIOC_REQBUFS: - ret = v4l_gst_reqbuf_ioctl(priv, arg); + ret = v4l_gst_ioctl_reqbuf(priv, arg); break; case VIDIOC_STREAMON: - ret = v4l_gst_streamon_ioctl(priv, arg); + ret = v4l_gst_ioctl_streamon(priv, arg); break; case VIDIOC_STREAMOFF: - ret = v4l_gst_streamoff_ioctl(priv, arg); + ret = v4l_gst_ioctl_streamoff(priv, arg); break; case VIDIOC_SUBSCRIBE_EVENT: - ret = v4l_gst_subscribe_event_ioctl(priv, arg); + ret = v4l_gst_ioctl_subscribe_event(priv, arg); break; case VIDIOC_DQEVENT: - ret = v4l_gst_dqevent_ioctl(priv, arg); + ret = v4l_gst_ioctl_dqevent(priv, arg); break; case VIDIOC_EXPBUF: - ret = v4l_gst_expbuf_ioctl(priv, arg); + ret = v4l_gst_ioctl_expbuf(priv, arg); break; case VIDIOC_ENUM_FRAMESIZES : - ret = v4l_gst_enum_framesizes_ioctl(priv, arg); + ret = v4l_gst_ioctl_enum_framesizes(priv, arg); break; case VIDIOC_G_SELECTION : - ret = v4l_gst_g_selection_ioctl(priv, arg); + ret = v4l_gst_ioctl_g_selection(priv, arg); break; case VIDIOC_QUERYCTRL: - ret = v4l_gst_queryctrl_ioctl(priv, arg); + ret = v4l_gst_ioctl_queryctrl(priv, arg); break; case VIDIOC_QUERYMENU: - ret = v4l_gst_querymenu_ioctl(priv, arg); + ret = v4l_gst_ioctl_querymenu(priv, arg); break; case VIDIOC_G_CROP: - ret = v4l_gst_g_crop_ioctl(priv, arg); + ret = v4l_gst_ioctl_g_crop(priv, arg); break; case VIDIOC_TRY_FMT: - ret = v4l_gst_try_fmt_ioctl(priv, arg); + ret = v4l_gst_ioctl_try_fmt(priv, arg); break; case VIDIOC_UNSUBSCRIBE_EVENT: - ret = v4l_gst_unsubscribe_event_ioctl(priv, arg); + ret = v4l_gst_ioctl_unsubscribe_event(priv, arg); break; case VIDIOC_DECODER_CMD: - ret = v4l_gst_decoder_cmd_ioctl(priv, arg); + ret = v4l_gst_ioctl_decoder_cmd(priv, arg); break; case VIDIOC_TRY_DECODER_CMD: - ret = v4l_gst_try_decoder_cmd_ioctl(priv, arg); + ret = v4l_gst_ioctl_try_decoder_cmd(priv, arg); break; default: DBG_LOG("unknown ioctl: %lu\n", cmd); diff --git a/lib/libv4l-gst/v4l-gst-buf.c b/lib/libv4l-gst/v4l-gst-buf.c index 4b6d75d..86b6b94 100644 --- a/lib/libv4l-gst/v4l-gst-buf.c +++ b/lib/libv4l-gst/v4l-gst-buf.c @@ -335,7 +335,7 @@ qbuf_ioctl_cap(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) int -v4l_gst_qbuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) +v4l_gst_ioctl_qbuf(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) { int ret = -1; @@ -795,7 +795,7 @@ dqbuf_ioctl_cap(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) int -v4l_gst_dqbuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) +v4l_gst_ioctl_dqbuf(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) { int ret = -1; @@ -821,7 +821,7 @@ v4l_gst_dqbuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) int -v4l_gst_querybuf_ioctl(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) +v4l_gst_ioctl_querybuf(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) { struct v4l_gst_buffer *buffers; gint buffers_num; @@ -1471,7 +1471,7 @@ reqbuf_ioctl_cap(struct v4l_gst *priv, int -v4l_gst_reqbuf_ioctl(struct v4l_gst *priv, struct v4l2_requestbuffers *req) +v4l_gst_ioctl_reqbuf(struct v4l_gst *priv, struct v4l2_requestbuffers *req) { int ret; @@ -1547,7 +1547,7 @@ streamon_ioctl_out(struct v4l_gst *priv) int -v4l_gst_streamon_ioctl(struct v4l_gst *priv, enum v4l2_buf_type *type) +v4l_gst_ioctl_streamon(struct v4l_gst *priv, enum v4l2_buf_type *type) { int ret; @@ -1570,7 +1570,7 @@ v4l_gst_streamon_ioctl(struct v4l_gst *priv, enum v4l2_buf_type *type) int -v4l_gst_streamoff_ioctl(struct v4l_gst *priv, enum v4l2_buf_type *type) +v4l_gst_ioctl_streamoff(struct v4l_gst *priv, enum v4l2_buf_type *type) { int ret; @@ -1753,7 +1753,7 @@ v4l_gst_mmap(struct v4l_gst *priv, void *start, size_t length, int -v4l_gst_expbuf_ioctl(struct v4l_gst *priv, struct v4l2_exportbuffer *expbuf) +v4l_gst_ioctl_expbuf(struct v4l_gst *priv, struct v4l2_exportbuffer *expbuf) { struct v4l_gst_buffer *buffer; guint mem_index = 0; diff --git a/lib/libv4l-gst/v4l-gst-ctrl.c b/lib/libv4l-gst/v4l-gst-ctrl.c index e22869d..e82c74d 100644 --- a/lib/libv4l-gst/v4l-gst-ctrl.c +++ b/lib/libv4l-gst/v4l-gst-ctrl.c @@ -45,7 +45,7 @@ #include "v4l-gst-internal.h" int -v4l_gst_get_ctrl_ioctl(struct v4l_gst *priv, struct v4l2_control *ctrl) +v4l_gst_ioctl_get_ctrl(struct v4l_gst *priv, struct v4l2_control *ctrl) { int ret; @@ -68,7 +68,7 @@ v4l_gst_get_ctrl_ioctl(struct v4l_gst *priv, struct v4l2_control *ctrl) int -v4l_gst_get_ext_ctrl_ioctl(struct v4l_gst *priv, struct v4l2_ext_controls *ext_ctrls) +v4l_gst_ioctl_get_ext_ctrl(struct v4l_gst *priv, struct v4l2_ext_controls *ext_ctrls) { unsigned int i; @@ -123,7 +123,7 @@ codec_pipeline_enabled(struct v4l_gst *priv, guint32 fourcc) /* See https://github.com/JeffyCN/libv4l-rkmpp/blob/master/src/libv4l-rkmpp-dec.c#L740-L776 */ int -v4l_gst_queryctrl_ioctl(struct v4l_gst *priv, struct v4l2_queryctrl *query_ctrl) +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); @@ -185,7 +185,7 @@ v4l_gst_queryctrl_ioctl(struct v4l_gst *priv, struct v4l2_queryctrl *query_ctrl) /* See https://github.com/JeffyCN/libv4l-rkmpp/blob/master/src/libv4l-rkmpp-dec.c#L778-L842 */ int -v4l_gst_querymenu_ioctl(struct v4l_gst *priv, struct v4l2_querymenu *query_menu) +v4l_gst_ioctl_querymenu(struct v4l_gst *priv, struct v4l2_querymenu *query_menu) { #ifdef ENABLE_VIDIOC_DEBUG diff --git a/lib/libv4l-gst/v4l-gst-event.c b/lib/libv4l-gst/v4l-gst-event.c index b133c5f..8946862 100644 --- a/lib/libv4l-gst/v4l-gst-event.c +++ b/lib/libv4l-gst/v4l-gst-event.c @@ -45,7 +45,7 @@ #include "v4l-gst-internal.h" int -v4l_gst_subscribe_event_ioctl(struct v4l_gst *priv, +v4l_gst_ioctl_subscribe_event(struct v4l_gst *priv, struct v4l2_event_subscription *subscription) { int retval = -1; @@ -82,7 +82,7 @@ v4l_gst_subscribe_event_ioctl(struct v4l_gst *priv, int -v4l_gst_dqevent_ioctl(struct v4l_gst *priv, struct v4l2_event *ev) +v4l_gst_ioctl_dqevent(struct v4l_gst *priv, struct v4l2_event *ev) { int retval = -1; @@ -126,7 +126,7 @@ v4l_gst_dqevent_ioctl(struct v4l_gst *priv, struct v4l2_event *ev) int -v4l_gst_unsubscribe_event_ioctl(struct v4l_gst *priv, +v4l_gst_ioctl_unsubscribe_event(struct v4l_gst *priv, struct v4l2_event_subscription *subscription) { int retval = 0; diff --git a/lib/libv4l-gst/v4l-gst-fmt.c b/lib/libv4l-gst/v4l-gst-fmt.c index 83d9540..8abcc04 100644 --- a/lib/libv4l-gst/v4l-gst-fmt.c +++ b/lib/libv4l-gst/v4l-gst-fmt.c @@ -45,7 +45,7 @@ #include "v4l-gst-internal.h" int -v4l_gst_querycap_ioctl(struct v4l_gst *priv, struct v4l2_capability *cap) +v4l_gst_ioctl_querycap(struct v4l_gst *priv, struct v4l2_capability *cap) { GST_DEBUG("VIDIOC_QUERYCAP"); @@ -282,7 +282,7 @@ set_fmt_ioctl_cap(struct v4l_gst *priv, struct v4l2_format *fmt) int -v4l_gst_set_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *fmt) +v4l_gst_ioctl_set_fmt(struct v4l_gst *priv, struct v4l2_format *fmt) { int ret; @@ -353,7 +353,7 @@ get_fmt_ioctl_cap(struct v4l_gst *priv, int -v4l_gst_get_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *fmt) +v4l_gst_ioctl_get_fmt(struct v4l_gst *priv, struct v4l2_format *fmt) { struct v4l2_pix_format_mplane *pix_fmt; int ret; @@ -383,7 +383,7 @@ v4l_gst_get_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *fmt) int -v4l_gst_enum_fmt_ioctl(struct v4l_gst *priv, struct v4l2_fmtdesc *desc) +v4l_gst_ioctl_enum_fmt(struct v4l_gst *priv, struct v4l2_fmtdesc *desc) { struct fmt *fmts; gint fmts_num; @@ -436,7 +436,7 @@ v4l_gst_enum_fmt_ioctl(struct v4l_gst *priv, struct v4l2_fmtdesc *desc) int -v4l_gst_enum_framesizes_ioctl(struct v4l_gst *priv, struct v4l2_frmsizeenum *argp) +v4l_gst_ioctl_enum_framesizes(struct v4l_gst *priv, struct v4l2_frmsizeenum *argp) { gchar fourcc_str[5]; @@ -504,7 +504,7 @@ v4l_gst_enum_framesizes_ioctl(struct v4l_gst *priv, struct v4l2_frmsizeenum *arg int -v4l_gst_g_selection_ioctl(struct v4l_gst *priv, struct v4l2_selection *selection) +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); @@ -590,7 +590,7 @@ try_fmt_ioctl_cap(struct v4l_gst *priv, struct v4l2_format *format) int -v4l_gst_try_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *format) +v4l_gst_ioctl_try_fmt(struct v4l_gst *priv, struct v4l2_format *format) { int ret; gchar fourcc_str[5]; @@ -618,7 +618,7 @@ v4l_gst_try_fmt_ioctl(struct v4l_gst *priv, struct v4l2_format *format) int -v4l_gst_g_crop_ioctl(struct v4l_gst *priv, struct v4l2_crop *crop) +v4l_gst_ioctl_g_crop(struct v4l_gst *priv, struct v4l2_crop *crop) { const gchar *buf_type; #ifdef ENABLE_VIDIOC_DEBUG diff --git a/lib/libv4l-gst/v4l-gst-pipeline.c b/lib/libv4l-gst/v4l-gst-pipeline.c index 7230ef3..7982502 100644 --- a/lib/libv4l-gst/v4l-gst-pipeline.c +++ b/lib/libv4l-gst/v4l-gst-pipeline.c @@ -772,7 +772,7 @@ set_decoder_cmd_state(struct v4l_gst *priv, GstState state) #endif int -v4l_gst_try_decoder_cmd_ioctl(struct v4l_gst *priv, +v4l_gst_ioctl_try_decoder_cmd(struct v4l_gst *priv, struct v4l2_decoder_cmd *decoder_cmd) { int ret = 0; @@ -813,7 +813,7 @@ v4l_gst_try_decoder_cmd_ioctl(struct v4l_gst *priv, int -v4l_gst_decoder_cmd_ioctl(struct v4l_gst *priv, struct v4l2_decoder_cmd *decoder_cmd) +v4l_gst_ioctl_decoder_cmd(struct v4l_gst *priv, struct v4l2_decoder_cmd *decoder_cmd) { int ret = 0; diff --git a/lib/libv4l-gst/v4l-gst.h b/lib/libv4l-gst/v4l-gst.h index 2242510..415cd66 100644 --- a/lib/libv4l-gst/v4l-gst.h +++ b/lib/libv4l-gst/v4l-gst.h @@ -34,43 +34,43 @@ struct v4l_gst * v4l_gst_init (int fd); void v4l_gst_deinit (struct v4l_gst *priv); /* pipeline: decoder command */ -int v4l_gst_try_decoder_cmd_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_try_decoder_cmd (struct v4l_gst *priv, struct v4l2_decoder_cmd *decoder_cmd); -int v4l_gst_decoder_cmd_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_decoder_cmd (struct v4l_gst *priv, struct v4l2_decoder_cmd *decoder_cmd); /* fmt: format */ -int v4l_gst_querycap_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_querycap (struct v4l_gst *priv, struct v4l2_capability *cap); -int v4l_gst_set_fmt_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_set_fmt (struct v4l_gst *priv, struct v4l2_format *fmt); -int v4l_gst_get_fmt_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_get_fmt (struct v4l_gst *priv, struct v4l2_format *fmt); -int v4l_gst_enum_fmt_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_enum_fmt (struct v4l_gst *priv, struct v4l2_fmtdesc *desc); -int v4l_gst_enum_framesizes_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_enum_framesizes (struct v4l_gst *priv, struct v4l2_frmsizeenum *argp); -int v4l_gst_g_selection_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_g_selection (struct v4l_gst *priv, struct v4l2_selection *selection); -int v4l_gst_g_crop_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_g_crop (struct v4l_gst *priv, struct v4l2_crop *crop); -int v4l_gst_try_fmt_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_try_fmt (struct v4l_gst *priv, struct v4l2_format *format); /* buf: buffer */ -int v4l_gst_qbuf_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_qbuf (struct v4l_gst *priv, struct v4l2_buffer *buf); -int v4l_gst_dqbuf_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_dqbuf (struct v4l_gst *priv, struct v4l2_buffer *buf); -int v4l_gst_querybuf_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_querybuf (struct v4l_gst *priv, struct v4l2_buffer *buf); -int v4l_gst_reqbuf_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_reqbuf (struct v4l_gst *priv, struct v4l2_requestbuffers *req); -int v4l_gst_streamon_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_streamon (struct v4l_gst *priv, enum v4l2_buf_type *type); -int v4l_gst_streamoff_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_streamoff (struct v4l_gst *priv, enum v4l2_buf_type *type); -int v4l_gst_expbuf_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_expbuf (struct v4l_gst *priv, struct v4l2_exportbuffer *buf); void * v4l_gst_mmap (struct v4l_gst *priv, void *start, @@ -81,21 +81,21 @@ void * v4l_gst_mmap (struct v4l_gst *priv, int64_t offset); /* ctrl: control */ -int v4l_gst_get_ctrl_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_get_ctrl (struct v4l_gst *priv, struct v4l2_control *ctrl); -int v4l_gst_get_ext_ctrl_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_get_ext_ctrl (struct v4l_gst *priv, struct v4l2_ext_controls *ext_ctrls); -int v4l_gst_queryctrl_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_queryctrl (struct v4l_gst *priv, struct v4l2_queryctrl *query_ctrl); -int v4l_gst_querymenu_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_querymenu (struct v4l_gst *priv, struct v4l2_querymenu *query_menu); /* event: event */ -int v4l_gst_subscribe_event_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_subscribe_event (struct v4l_gst *priv, struct v4l2_event_subscription *sub); -int v4l_gst_dqevent_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_dqevent (struct v4l_gst *priv, struct v4l2_event *ev); -int v4l_gst_unsubscribe_event_ioctl (struct v4l_gst *priv, +int v4l_gst_ioctl_unsubscribe_event (struct v4l_gst *priv, struct v4l2_event_subscription *subscription); #define ENV_DISABLE_VIDIOC_FEATURES "DISABLE_VIDIOC_FEATURES" From f783c15c15ac92a56bca21bdadb688b5b685e88f Mon Sep 17 00:00:00 2001 From: Takuro Ashie Date: Thu, 8 Oct 2026 09:02:10 +0900 Subject: [PATCH 08/13] Add H264 decoder streaming test and raw meta fallback Add a decoder streaming test that drives h264parse ! avdec_h264 ! videoconvert through the V4L2 M2M API and drains CAPTURE buffers with V4L2_DEC_CMD_STOP until V4L2_BUF_FLAG_LAST, preserving the existing final-buffer/EOS behavior. In v4l-gst core, raw video buffers may lack GstVideoMeta. Instead of failing or assuming a single plane, callers now fall back to the GstVideoInfo from the buffer pool caps for plane count, strides, offsets and sizes. This fixes plane validation, DQBUF plane information, CAPTURE mmap offsets, and frame size retrieval without changing behavior when video metadata is present. Assisted-by: Qwen3.8-27B --- lib/libv4l-gst/v4l-gst-buf.c | 47 +++-- lib/libv4l-gst/v4l-gst-pipeline.c | 2 - lib/libv4l-gst/v4l-gst-testhooks.c | 41 +++++ lib/libv4l-gst/v4l-gst-testhooks.h | 1 + tests/test-gst-backend.c | 284 +++++++++++++++++++++++++++++ 5 files changed, 358 insertions(+), 17 deletions(-) diff --git a/lib/libv4l-gst/v4l-gst-buf.c b/lib/libv4l-gst/v4l-gst-buf.c index 86b6b94..1e68ae7 100644 --- a/lib/libv4l-gst/v4l-gst-buf.c +++ b/lib/libv4l-gst/v4l-gst-buf.c @@ -86,6 +86,7 @@ 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)) @@ -104,9 +105,9 @@ check_no_index_v4l2_buffer(struct v4l2_buffer *v4l2buf, return FALSE; } - if (v4l_gst_pipeline_get_raw_video_params(pool, buffers[v4l2buf->index].gstbuf, NULL, - &meta)) - n_planes = meta->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; @@ -363,7 +364,9 @@ v4l_gst_ioctl_qbuf(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) static inline guint calc_plane_size(GstVideoInfo *info, GstVideoMeta *meta, gint index) { - return meta->stride[index] * GST_VIDEO_INFO_COMP_HEIGHT(info, index); + guint stride = meta ? meta->stride[index] + : GST_VIDEO_INFO_PLANE_STRIDE(info, index); + return stride * GST_VIDEO_INFO_COMP_HEIGHT(info, index); } @@ -407,11 +410,15 @@ fill_v4l2_buffer(struct v4l_gst *priv, GstBufferPool *pool, struct v4l2_buffer *v4l2buf) { GstVideoMeta *meta = NULL; + GstVideoInfo info; guint n_planes; - v4l_gst_pipeline_get_raw_video_params(pool, buffers[v4l2buf->index].gstbuf, NULL, &meta); - - n_planes = (meta) ? meta->n_planes : 1; + 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); @@ -883,8 +890,10 @@ set_mem_offset(struct v4l_gst_buffer *buffer, GstBufferPool *pool, gsize offset) page_size) + offset; } - if (meta) { - for (i = 0; i < meta->n_planes; i++) { + { + 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); @@ -1303,10 +1312,16 @@ retrieve_cap_frame_info(GstBufferPool *pool, GstBuffer *gstbuf, 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]; + { + 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; @@ -1669,6 +1684,7 @@ map_cap_buffer(struct v4l_gst *priv, int index, int plane, int prot) { GstVideoMeta *meta; + GstVideoInfo vinfo; GstMapInfo info; void *data; GstMapFlags map_flags; @@ -1691,7 +1707,7 @@ map_cap_buffer(struct v4l_gst *priv, int index, int plane, } else { if (!v4l_gst_pipeline_get_raw_video_params(priv->cap.pool, priv->cap.buffers[index].gstbuf, - NULL, &meta)) { + &vinfo, &meta)) { GST_ERROR("Failed to get video meta data"); errno = EINVAL; gst_buffer_unmap(priv->cap.buffers[index].gstbuf, @@ -1699,7 +1715,8 @@ map_cap_buffer(struct v4l_gst *priv, int index, int plane, return MAP_FAILED; } - data = info.data + meta->offset[plane]; + data = info.data + (meta ? meta->offset[plane] + : GST_VIDEO_INFO_PLANE_OFFSET(&vinfo, plane)); } gst_buffer_unmap(priv->cap.buffers[index].gstbuf, &info); diff --git a/lib/libv4l-gst/v4l-gst-pipeline.c b/lib/libv4l-gst/v4l-gst-pipeline.c index 7982502..1de2a8c 100644 --- a/lib/libv4l-gst/v4l-gst-pipeline.c +++ b/lib/libv4l-gst/v4l-gst-pipeline.c @@ -486,8 +486,6 @@ v4l_gst_pipeline_get_raw_video_params(GstBufferPool *pool, GstBuffer *gstbuf, Gs return FALSE; vmeta = gst_buffer_get_video_meta(gstbuf); - if (!vmeta) - return FALSE; if (info) memcpy(info, &vinfo, sizeof(GstVideoInfo)); diff --git a/lib/libv4l-gst/v4l-gst-testhooks.c b/lib/libv4l-gst/v4l-gst-testhooks.c index d604eb4..10eb484 100644 --- a/lib/libv4l-gst/v4l-gst-testhooks.c +++ b/lib/libv4l-gst/v4l-gst-testhooks.c @@ -128,6 +128,47 @@ prepare_x264enc_backend_fixture(struct v4l_gst *priv) priv->out.cnt = INPUT_BUFFERING_CNT; } +/* + * Reconfigure the backend into a decoder role backed by a real + * "h264parse ! avdec_h264 ! videoconvert" GStreamer pipeline, so that OUTPUT + * H264 buffers are decoded to NV12 frames on CAPTURE. The "identity" + * placeholder pipeline created by prepare_format_backend_fixture() is dropped + * so that the decode pipeline (appsrc ! h264parse ! avdec_h264 ! videoconvert + * ! appsink) is created (via VIDIOC_S_FMT on OUTPUT) with a real decoder. + * + * h264parse is required because it extracts the SPS/PPS into the codec_data + * caps that avdec_h264 needs to fix its output format (see README: + * "pipeline=h264parse ! omxh264dec"). videoconvert is required because + * avdec_h264 emits I420 and the CAPTURE appsink is fixed to NV12, so the + * I420->NV12 conversion must happen inside the pipeline. + */ +void +prepare_h264dec_backend_fixture(struct v4l_gst *priv) +{ + if (priv->pipeline) { + gst_object_unref(priv->pipeline); + priv->pipeline = NULL; + } + + g_hash_table_remove(priv->config.pipelines, "H264"); + g_hash_table_insert(priv->config.pipelines, g_strdup("H264"), + g_strdup("h264parse ! avdec_h264 ! videoconvert")); + v4l_gst_core_setup_role(priv); + + priv->out.fmt.pixelformat = V4L2_PIX_FMT_H264; + priv->out.fmt.width = 320; + priv->out.fmt.height = 240; + priv->out.fmt.plane_fmt[0].sizeimage = 4 * 1024 * 1024; + priv->cap.fmt.pixelformat = V4L2_PIX_FMT_NV12; + priv->cap.fmt.width = 320; + priv->cap.fmt.height = 240; + priv->cap.fmt.num_planes = 1; + priv->cap.fmt.plane_fmt[0].bytesperline = 320; + priv->cap.fmt.plane_fmt[0].sizeimage = 320 * 240 * 3 / 2; + 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) { diff --git a/lib/libv4l-gst/v4l-gst-testhooks.h b/lib/libv4l-gst/v4l-gst-testhooks.h index e2a7ee5..1dff2f7 100644 --- a/lib/libv4l-gst/v4l-gst-testhooks.h +++ b/lib/libv4l-gst/v4l-gst-testhooks.h @@ -16,6 +16,7 @@ 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_x264enc_backend_fixture(struct v4l_gst *priv); +void prepare_h264dec_backend_fixture(struct v4l_gst *priv); enum v4l_gst_role get_backend_role(struct v4l_gst *priv); #endif diff --git a/tests/test-gst-backend.c b/tests/test-gst-backend.c index 949ecba..09f1f89 100644 --- a/tests/test-gst-backend.c +++ b/tests/test-gst-backend.c @@ -1381,6 +1381,73 @@ contains_h264_start_code(const guint8 *data, gsize size) 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) { @@ -1522,3 +1589,220 @@ test_x264enc_encoder_streaming_produces_h264(void) 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_h264dec_backend_fixture(backend); + 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); +} From 611bde04b93b5c40d5eea646eab25de6feca828a Mon Sep 17 00:00:00 2001 From: Takuro Ashie Date: Thu, 8 Oct 2026 09:13:57 +0900 Subject: [PATCH 09/13] Consolidate pipeline backend fixtures into one parameterized helper Replace the x264enc and h264dec specific test fixtures with prepare_pipeline_backend_fixture(). The tests now provide the pipeline string, codec/raw fourccs, resolution and buffer sizes, keeping the test hook generic. Assisted-by: Qwen3.8-27B --- lib/libv4l-gst/v4l-gst-testhooks.c | 104 ++++++++++++----------------- lib/libv4l-gst/v4l-gst-testhooks.h | 11 ++- tests/test-gst-backend.c | 11 ++- 3 files changed, 60 insertions(+), 66 deletions(-) diff --git a/lib/libv4l-gst/v4l-gst-testhooks.c b/lib/libv4l-gst/v4l-gst-testhooks.c index 10eb484..d3659a5 100644 --- a/lib/libv4l-gst/v4l-gst-testhooks.c +++ b/lib/libv4l-gst/v4l-gst-testhooks.c @@ -92,79 +92,59 @@ prepare_dual_role_backend_fixture(struct v4l_gst *priv) priv->out.cnt = INPUT_BUFFERING_CNT; } -/* - * Reconfigure the backend into an encoder role backed by a real x264enc - * GStreamer pipeline, so that OUTPUT raw frames are encoded to an H264 - * stream on CAPTURE. The "identity" placeholder pipeline created by - * prepare_format_backend_fixture() is dropped so that the encoder pipeline - * is created (via VIDIOC_S_FMT on CAPTURE) with a real encoder. - */ void -prepare_x264enc_backend_fixture(struct v4l_gst *priv) +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) { - if (priv->pipeline) { - gst_object_unref(priv->pipeline); - priv->pipeline = NULL; - } + gchar fourcc_str[5]; - g_hash_table_remove(priv->config.pipelines, "H264"); - /* Zero-latency tuning disables lookahead/bframes so that each input - frame produces an output frame immediately, which the CAPTURE - buffer flow depends on. */ - g_hash_table_insert(priv->config.encode_pipelines, g_strdup("H264"), - g_strdup("x264enc tune=zerolatency " - "speed-preset=ultrafast")); - v4l_gst_core_setup_role(priv); + fourcc_to_string(codec_fourcc, fourcc_str); - 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 * 1024; - g_atomic_int_set(&priv->cap.fmt_acquirable, 1); - priv->out.cnt = INPUT_BUFFERING_CNT; -} - -/* - * Reconfigure the backend into a decoder role backed by a real - * "h264parse ! avdec_h264 ! videoconvert" GStreamer pipeline, so that OUTPUT - * H264 buffers are decoded to NV12 frames on CAPTURE. The "identity" - * placeholder pipeline created by prepare_format_backend_fixture() is dropped - * so that the decode pipeline (appsrc ! h264parse ! avdec_h264 ! videoconvert - * ! appsink) is created (via VIDIOC_S_FMT on OUTPUT) with a real decoder. - * - * h264parse is required because it extracts the SPS/PPS into the codec_data - * caps that avdec_h264 needs to fix its output format (see README: - * "pipeline=h264parse ! omxh264dec"). videoconvert is required because - * avdec_h264 emits I420 and the CAPTURE appsink is fixed to NV12, so the - * I420->NV12 conversion must happen inside the pipeline. - */ -void -prepare_h264dec_backend_fixture(struct v4l_gst *priv) -{ if (priv->pipeline) { gst_object_unref(priv->pipeline); priv->pipeline = NULL; } - g_hash_table_remove(priv->config.pipelines, "H264"); - g_hash_table_insert(priv->config.pipelines, g_strdup("H264"), - g_strdup("h264parse ! avdec_h264 ! videoconvert")); + 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); - priv->out.fmt.pixelformat = V4L2_PIX_FMT_H264; - priv->out.fmt.width = 320; - priv->out.fmt.height = 240; - priv->out.fmt.plane_fmt[0].sizeimage = 4 * 1024 * 1024; - priv->cap.fmt.pixelformat = V4L2_PIX_FMT_NV12; - priv->cap.fmt.width = 320; - priv->cap.fmt.height = 240; - priv->cap.fmt.num_planes = 1; - priv->cap.fmt.plane_fmt[0].bytesperline = 320; - priv->cap.fmt.plane_fmt[0].sizeimage = 320 * 240 * 3 / 2; + 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; } diff --git a/lib/libv4l-gst/v4l-gst-testhooks.h b/lib/libv4l-gst/v4l-gst-testhooks.h index 1dff2f7..79ef3a1 100644 --- a/lib/libv4l-gst/v4l-gst-testhooks.h +++ b/lib/libv4l-gst/v4l-gst-testhooks.h @@ -15,8 +15,15 @@ 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_x264enc_backend_fixture(struct v4l_gst *priv); -void prepare_h264dec_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 diff --git a/tests/test-gst-backend.c b/tests/test-gst-backend.c index 09f1f89..d7304ac 100644 --- a/tests/test-gst-backend.c +++ b/tests/test-gst-backend.c @@ -1465,7 +1465,11 @@ test_x264enc_encoder_streaming_produces_h264(void) if (!gst_element_factory_find("x264enc")) cut_pend("x264enc element is not available"); - prepare_x264enc_backend_fixture(backend); + 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. */ @@ -1637,7 +1641,10 @@ test_h264dec_decoder_streaming_produces_nv12(void) if (!stream) cut_pend("failed to generate an h264 sample stream"); - prepare_h264dec_backend_fixture(backend); + 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. */ From f0a1d7990914ab28a9322e397b2e1cd5ef287de8 Mon Sep 17 00:00:00 2001 From: Takuro Ashie Date: Thu, 8 Oct 2026 10:23:40 +0900 Subject: [PATCH 10/13] Restrict time-based appsrc setup to encoder OUTPUT Limit appsrc format=GST_FORMAT_TIME and do-timestamp=TRUE to raw encoder input. Decoder pipelines keep the previous appsrc segment behavior to avoid changing the event format seen by the codec parser and decoder. This is a fix for the real-device H.264 decoding stall introduced by 32cb908. Assisted-by: Qwen3.8-27B --- lib/libv4l-gst/v4l-gst-pipeline.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/libv4l-gst/v4l-gst-pipeline.c b/lib/libv4l-gst/v4l-gst-pipeline.c index 1de2a8c..e8e0b70 100644 --- a/lib/libv4l-gst/v4l-gst-pipeline.c +++ b/lib/libv4l-gst/v4l-gst-pipeline.c @@ -438,12 +438,12 @@ v4l_gst_pipeline_setup_app_elements(struct v4l_gst *priv) The amount of buffers is managed by the buffer pool. */ gst_app_src_set_max_bytes(GST_APP_SRC(priv->appsrc), 0); - /* Video frames are timestamped in time, not bytes. The appsrc - "format" property drives the segment format (gst_app_src_start - copies it into the base src), and do-timestamp assigns PTS/DTS - from the caps framerate since the wrapped buffers carry no PTS. */ - g_object_set(G_OBJECT(priv->appsrc), "format", GST_FORMAT_TIME, - "do-timestamp", TRUE, NULL); + /* 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); From ddc6c43e723dc70fd3ec21f0484d415be60c5935 Mon Sep 17 00:00:00 2001 From: Takuro Ashie Date: Thu, 8 Oct 2026 11:17:45 +0900 Subject: [PATCH 11/13] ci: install GStreamer H.264 test plugins --- .github/workflows/tests.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2761af6..126c1f8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,11 +17,15 @@ jobs: - 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: | From 2f20945f10963aebf27752c116e29dd69fc4dc08 Mon Sep 17 00:00:00 2001 From: Takuro Ashie Date: Thu, 8 Oct 2026 11:26:30 +0900 Subject: [PATCH 12/13] CI: Update to actions/checkout@v7 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 126c1f8..b5c21f9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: submodules: 'recursive' From 34cbc4ee7d57e52431a4129d85e39129a0bf7850 Mon Sep 17 00:00:00 2001 From: Takuro Ashie Date: Thu, 8 Oct 2026 12:57:58 +0900 Subject: [PATCH 13/13] refactor: prefix buffer CRC quark --- lib/libv4l-gst/v4l-gst-buf.c | 3 +-- lib/libv4l-gst/v4l-gst-core.c | 3 +-- lib/libv4l-gst/v4l-gst-internal.h | 2 +- lib/libv4l-gst/v4l-gst-pipeline.c | 3 +-- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/libv4l-gst/v4l-gst-buf.c b/lib/libv4l-gst/v4l-gst-buf.c index 1e68ae7..849b97f 100644 --- a/lib/libv4l-gst/v4l-gst-buf.c +++ b/lib/libv4l-gst/v4l-gst-buf.c @@ -286,7 +286,7 @@ qbuf_ioctl_cap(struct v4l_gst *priv, struct v4l2_buffer *v4l2buf) guint32 crc, crc_dest; crc_p = gst_mini_object_get_qdata(GST_MINI_OBJECT(buffer->gstbuf), - cap_buf_crc_quark()); + v4l_gst_cap_buf_crc_quark()); crc = GPOINTER_TO_UINT(crc_p); crc_dest = frame_crc32(buffer->gstbuf, priv->config.frame_check); @@ -1838,4 +1838,3 @@ v4l_gst_ioctl_expbuf(struct v4l_gst *priv, struct v4l2_exportbuffer *expbuf) return 0; } - diff --git a/lib/libv4l-gst/v4l-gst-core.c b/lib/libv4l-gst/v4l-gst-core.c index c829d58..c284adf 100644 --- a/lib/libv4l-gst/v4l-gst-core.c +++ b/lib/libv4l-gst/v4l-gst-core.c @@ -48,7 +48,7 @@ GstDebugCategory *v4l_gst_debug_category; GstDebugCategory *v4l_gst_ioctl_debug_category; GstDebugCategory *v4l_gst_buffer_debug_category; -G_DEFINE_QUARK(cap_buf_crc, cap_buf_crc) +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) @@ -1161,4 +1161,3 @@ v4l_gst_deinit(struct v4l_gst *priv) GST_DEBUG("v4l_gst_deinit end"); } - diff --git a/lib/libv4l-gst/v4l-gst-internal.h b/lib/libv4l-gst/v4l-gst-internal.h index 1845168..81fd972 100644 --- a/lib/libv4l-gst/v4l-gst-internal.h +++ b/lib/libv4l-gst/v4l-gst-internal.h @@ -198,7 +198,7 @@ extern GstDebugCategory *v4l_gst_ioctl_debug_category; extern GstDebugCategory *v4l_gst_buffer_debug_category; #define GST_CAT_DEFAULT v4l_gst_debug_category -GQuark cap_buf_crc_quark (void); +GQuark v4l_gst_cap_buf_crc_quark (void); /* * Functions shared between the v4l-gst-*.c translation units, diff --git a/lib/libv4l-gst/v4l-gst-pipeline.c b/lib/libv4l-gst/v4l-gst-pipeline.c index e8e0b70..866d141 100644 --- a/lib/libv4l-gst/v4l-gst-pipeline.c +++ b/lib/libv4l-gst/v4l-gst-pipeline.c @@ -392,7 +392,7 @@ appsink_callback_new_sample(GstAppSink *appsink, gpointer user_data) crc = frame_crc32(gstbuf, priv->config.frame_check); gst_mini_object_set_qdata(GST_MINI_OBJECT(gstbuf), - cap_buf_crc_quark(), + v4l_gst_cap_buf_crc_quark(), GUINT_TO_POINTER(crc), NULL); GST_CAT_DEBUG(v4l_gst_buffer_debug_category, @@ -866,4 +866,3 @@ v4l_gst_ioctl_decoder_cmd(struct v4l_gst *priv, struct v4l2_decoder_cmd *decoder return ret; } -