[{"id":32342,"web_url":"https://patchwork.libcamera.org/comment/32342/","msgid":"<cf65cd86c0f4e748e7cebdc7039949f1c4b665ef.camel@ndufresne.ca>","date":"2024-11-22T16:06:58","subject":"Re: [PATCH v5] gstreamer: Add GstVideoMeta support","submitter":{"id":30,"url":"https://patchwork.libcamera.org/api/people/30/","name":"Nicolas Dufresne","email":"nicolas@ndufresne.ca"},"content":"Hi,\n\nLe vendredi 22 novembre 2024 à 13:24 +0900, Hou Qi a écrit :\n> GStreamer video-info calculated stride and offset may differ from\n> those used by the camera.\n> \n> For stride and offset mismatch, this patch adds video meta to buffer\n> if downstream supports VideoMeta through allocation query. Otherwise,\n> get downstream provided buffer pool or create internal buffer pool\n> using the caps, and copy video frame to the system memory acquired\n> from the buffer pool.\n> \n> Signed-off-by: Hou Qi <qi.hou@nxp.com>\n> ---\n>  src/gstreamer/gstlibcamera-utils.cpp |  33 +++++++\n>  src/gstreamer/gstlibcamera-utils.h   |   4 +\n>  src/gstreamer/gstlibcamerapool.cpp   |   9 +-\n>  src/gstreamer/gstlibcamerapool.h     |   3 +-\n>  src/gstreamer/gstlibcamerasrc.cpp    | 142 ++++++++++++++++++++++++++-\n>  5 files changed, 186 insertions(+), 5 deletions(-)\n> \n> diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp\n> index 732987ef..09af9204 100644\n> --- a/src/gstreamer/gstlibcamera-utils.cpp\n> +++ b/src/gstreamer/gstlibcamera-utils.cpp\n> @@ -591,6 +591,39 @@ gst_task_resume(GstTask *task)\n>  }\n>  #endif\n>  \n> +#if !GST_CHECK_VERSION(1, 22, 0)\n> +/*\n> + * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>\n> + * Library       <2002> Ronald Bultje <rbultje@ronald.bitfreak.net>\n> + * Copyright (C) <2007> David A. Schleef <ds@schleef.org>\n> + */\n> +/* This function has been imported directly from the gstreamer project to\n> + * support backwards compatibility and should be removed when the older\n> + * version is no longer supported. */\n> +gint gst_video_format_info_extrapolate_stride(const GstVideoFormatInfo *finfo, gint plane, gint stride)\n> +{\n> +\tgint estride;\n> +\tgint comp[GST_VIDEO_MAX_COMPONENTS];\n> +\tgint i;\n> +\n> +\t/* there is nothing to extrapolate on first plane */\n> +\tif (plane == 0)\n> +\t\treturn stride;\n> +\n> +\tgst_video_format_info_component(finfo, plane, comp);\n> +\n> +\t/* For now, all planar formats have a single component on first plane, but\n> +\t* if there was a planar format with more, we'd have to make a ratio of the\n> +\t* number of component on the first plane against the number of component on\n> +\t* the current plane. */\n> +\testride = 0;\n> +\tfor (i = 0; i < GST_VIDEO_MAX_COMPONENTS && comp[i] >= 0; i++)\n> +\t\testride += GST_VIDEO_FORMAT_INFO_SCALE_WIDTH(finfo, comp[i], stride);\n> +\n> +\treturn estride;\n> +}\n> +#endif\n> +\n>  G_LOCK_DEFINE_STATIC(cm_singleton_lock);\n>  static std::weak_ptr<CameraManager> cm_singleton_ptr;\n>  \n> diff --git a/src/gstreamer/gstlibcamera-utils.h b/src/gstreamer/gstlibcamera-utils.h\n> index cab1c814..81149280 100644\n> --- a/src/gstreamer/gstlibcamera-utils.h\n> +++ b/src/gstreamer/gstlibcamera-utils.h\n> @@ -35,6 +35,10 @@ static inline void gst_clear_event(GstEvent **event_ptr)\n>  #if !GST_CHECK_VERSION(1, 17, 1)\n>  gboolean gst_task_resume(GstTask *task);\n>  #endif\n> +\n> +#if !GST_CHECK_VERSION(1, 22, 0)\n> +gint gst_video_format_info_extrapolate_stride(const GstVideoFormatInfo *finfo, gint plane, gint stride);\n> +#endif\n>  std::shared_ptr<libcamera::CameraManager> gst_libcamera_get_camera_manager(int &ret);\n>  \n>  /**\n> diff --git a/src/gstreamer/gstlibcamerapool.cpp b/src/gstreamer/gstlibcamerapool.cpp\n> index 9cd7eccb..04bcaeb1 100644\n> --- a/src/gstreamer/gstlibcamerapool.cpp\n> +++ b/src/gstreamer/gstlibcamerapool.cpp\n> @@ -135,7 +135,8 @@ gst_libcamera_pool_class_init(GstLibcameraPoolClass *klass)\n>  }\n>  \n>  GstLibcameraPool *\n> -gst_libcamera_pool_new(GstLibcameraAllocator *allocator, Stream *stream)\n> +gst_libcamera_pool_new(GstLibcameraAllocator *allocator, Stream *stream,\n> +\t\t       GstVideoInfo *info, gboolean add_video_meta)\n\nValid simplification is to always add the VideoMeta, since when the stride isn't\n\"standard\" we will buffer copy in the context of the lack of VideoMeta\ndownstream.\n\n>  {\n>  \tauto *pool = GST_LIBCAMERA_POOL(g_object_new(GST_TYPE_LIBCAMERA_POOL, nullptr));\n>  \n> @@ -145,6 +146,12 @@ gst_libcamera_pool_new(GstLibcameraAllocator *allocator, Stream *stream)\n>  \tgsize pool_size = gst_libcamera_allocator_get_pool_size(allocator, stream);\n>  \tfor (gsize i = 0; i < pool_size; i++) {\n>  \t\tGstBuffer *buffer = gst_buffer_new();\n> +\t\tif (add_video_meta) {\n> +\t\t\tgst_buffer_add_video_meta_full(buffer, GST_VIDEO_FRAME_FLAG_NONE,\n> +\t\t\t\t\t\t       GST_VIDEO_INFO_FORMAT(info), GST_VIDEO_INFO_WIDTH(info),\n> +\t\t\t\t\t\t       GST_VIDEO_INFO_HEIGHT(info), GST_VIDEO_INFO_N_PLANES(info),\n> +\t\t\t\t\t\t       info->offset, info->stride);\n\nYou need to do GST_META_FLAG_SET (vmeta, GST_META_FLAG_POOLED), otherwise the\nmeta will be removed when the buffer is returned.\n\n> +\t\t}\n>  \t\tpool->queue->push_back(buffer);\n>  \t}\n>  \n> diff --git a/src/gstreamer/gstlibcamerapool.h b/src/gstreamer/gstlibcamerapool.h\n> index 2a7a9c77..b522b4a5 100644\n> --- a/src/gstreamer/gstlibcamerapool.h\n> +++ b/src/gstreamer/gstlibcamerapool.h\n> @@ -14,6 +14,7 @@\n>  #include \"gstlibcameraallocator.h\"\n>  \n>  #include <gst/gst.h>\n> +#include <gst/video/video.h>\n>  \n>  #include <libcamera/stream.h>\n>  \n> @@ -21,7 +22,7 @@\n>  G_DECLARE_FINAL_TYPE(GstLibcameraPool, gst_libcamera_pool, GST_LIBCAMERA, POOL, GstBufferPool)\n>  \n>  GstLibcameraPool *gst_libcamera_pool_new(GstLibcameraAllocator *allocator,\n> -\t\t\t\t\t libcamera::Stream *stream);\n> +\t\t\t\t\t libcamera::Stream *stream, GstVideoInfo *info, gboolean add_video_meta);\n>  \n>  libcamera::Stream *gst_libcamera_pool_get_stream(GstLibcameraPool *self);\n>  \n> diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp\n> index 8efa25f4..eb43f053 100644\n> --- a/src/gstreamer/gstlibcamerasrc.cpp\n> +++ b/src/gstreamer/gstlibcamerasrc.cpp\n> @@ -150,6 +150,10 @@ struct _GstLibcameraSrc {\n>  \tGstLibcameraSrcState *state;\n>  \tGstLibcameraAllocator *allocator;\n>  \tGstFlowCombiner *flow_combiner;\n> +\n> +\tgboolean frame_copy;\n> +\tGstVideoInfo info;\n> +\tGstBufferPool *pool;\n\nYou missed that the libcamerasrc element is a multi-stream element, each pads\ncan run at a different resolution, but a GstBufferPool is a pool if homogeneous\nbuffers.\n\nIn fact, the pools are already stored in the GstPad subclass, and can be\naccessed by gst_libcamera_pad_get_pool(). You can add in there the per-stream\nstate needed, you can even offload the copy into some wrapper around pad push\nplaced into the GstLibcameraPad class.\n\nNicolas\n\n>  };\n>  \n>  enum {\n> @@ -268,6 +272,43 @@ GstLibcameraSrcState::requestCompleted(Request *request)\n>  \tgst_task_resume(src_->task);\n>  }\n>  \n> +static GstFlowReturn\n> +gst_libcamera_video_frame_copy(GstLibcameraSrc *self, GstBuffer *src, GstBuffer *dest, guint32 stride)\n> +{\n> +\tGstVideoInfo src_info = self->info;\n> +\tGstVideoFrame src_frame, dest_frame;\n> +\tgsize offset = 0;\n> +\n> +\tfor (guint i = 0; i < GST_VIDEO_INFO_N_PLANES(&src_info); i++) {\n> +\t\tstride = gst_video_format_info_extrapolate_stride(src_info.finfo, i, stride);\n> +\t\tsrc_info.stride[i] = stride;\n> +\t\tsrc_info.offset[i] = offset;\n> +\t\toffset += stride * GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT(src_info.finfo, i,\n> +\t\t\t\t\t\t\t\t      GST_VIDEO_INFO_HEIGHT(&src_info));\n> +\t}\n> +\tsrc_info.size = gst_buffer_get_size(src);\n> +\n> +\tif (!gst_video_frame_map(&src_frame, &src_info, src, GST_MAP_READ))\n> +\t\tgoto invalid_buffer;\n> +\n> +\tif (!gst_video_frame_map(&dest_frame, &self->info, dest, GST_MAP_WRITE)) {\n> +\t\tgst_video_frame_unmap(&src_frame);\n> +\t\tgoto invalid_buffer;\n> +\t}\n> +\n> +\tgst_video_frame_copy(&dest_frame, &src_frame);\n> +\n> +\tgst_video_frame_unmap(&src_frame);\n> +\tgst_video_frame_unmap(&dest_frame);\n> +\n> +\treturn GST_FLOW_OK;\n> +\n> +invalid_buffer : {\n> +\tGST_ERROR_OBJECT(self, \"Could not map buffer\");\n> +\treturn GST_FLOW_ERROR;\n> +}\n> +}\n> +\n>  /* Must be called with stream_lock held. */\n>  int GstLibcameraSrcState::processRequest()\n>  {\n> @@ -292,12 +333,34 @@ int GstLibcameraSrcState::processRequest()\n>  \tGstFlowReturn ret = GST_FLOW_OK;\n>  \tgst_flow_combiner_reset(src_->flow_combiner);\n>  \n> -\tfor (GstPad *srcpad : srcpads_) {\n> +\tfor (gsize i = 0; i < src_->state->srcpads_.size(); i++) {\n> +\t\tGstPad *srcpad = src_->state->srcpads_[i];\n>  \t\tStream *stream = gst_libcamera_pad_get_stream(srcpad);\n>  \t\tGstBuffer *buffer = wrap->detachBuffer(stream);\n> +\t\tGstBuffer *tmp = NULL;\n\nPlease clarify the buffer names, its not clear what they are.\n\n>  \n> +\t\tconst StreamConfiguration &stream_cfg = src_->state->config_->at(i);\n>  \t\tFrameBuffer *fb = gst_libcamera_buffer_get_frame_buffer(buffer);\n>  \n> +\t\tif (src_->frame_copy) {\n> +\t\t\tret = gst_buffer_pool_acquire_buffer(src_->pool, &tmp, NULL);\n> +\t\t\tif (ret != GST_FLOW_OK) {\n> +\t\t\t\tGST_ERROR(\"Failed to acquire buffer\");\n> +\t\t\t\tgst_buffer_unref(buffer);\n> +\t\t\t\treturn -EPIPE;\n> +\t\t\t}\n> +\n> +\t\t\tret = gst_libcamera_video_frame_copy(src_, buffer, tmp, stream_cfg.stride);\n> +\t\t\tgst_buffer_unref(buffer);\n> +\t\t\tif (ret != GST_FLOW_OK) {\n> +\t\t\t\tGST_ERROR(\"Failed to copy buffer\");\n> +\t\t\t\tgst_buffer_unref(tmp);\n> +\t\t\t\treturn -EPIPE;\n> +\t\t\t}\n> +\n> +\t\t\tbuffer = tmp;\n> +\t\t}\n> +\n>  \t\tif (GST_CLOCK_TIME_IS_VALID(wrap->pts_)) {\n>  \t\t\tGST_BUFFER_PTS(buffer) = wrap->pts_;\n>  \t\t\tgst_libcamera_pad_set_latency(srcpad, wrap->latency_);\n> @@ -497,9 +560,74 @@ gst_libcamera_src_negotiate(GstLibcameraSrc *self)\n>  \tfor (gsize i = 0; i < state->srcpads_.size(); i++) {\n>  \t\tGstPad *srcpad = state->srcpads_[i];\n>  \t\tconst StreamConfiguration &stream_cfg = state->config_->at(i);\n> +\t\tgboolean add_video_meta = false;\n> +\t\tGstVideoInfo info;\n> +\n> +\t\tg_autoptr(GstCaps) caps = gst_libcamera_stream_configuration_to_caps(stream_cfg);\n> +\t\tgst_libcamera_framerate_to_caps(caps, element_caps);\n>  \n> -\t\tGstLibcameraPool *pool = gst_libcamera_pool_new(self->allocator,\n> -\t\t\t\t\t\t\t\tstream_cfg.stream());\n> +\t\tgst_video_info_init(&info);\n> +\t\tgst_video_info_from_caps(&info, caps);\n> +\n> +\t\t/* stride mismatch between camera stride and that calculated by video-info */\n> +\t\tif (static_cast<unsigned int>(info.stride[0]) != stream_cfg.stride &&\n> +\t\t    GST_VIDEO_INFO_FORMAT(&info) != GST_VIDEO_FORMAT_ENCODED) {\n> +\t\t\tGstQuery *query = NULL;\n> +\t\t\tgboolean need_pool = true;\n> +\n> +\t\t\tquery = gst_query_new_allocation(caps, need_pool);\n> +\t\t\tif (!gst_pad_peer_query(srcpad, query))\n> +\t\t\t\tGST_DEBUG_OBJECT(self, \"Didn't get downstream ALLOCATION hints\");\n> +\t\t\telse\n> +\t\t\t\tadd_video_meta = gst_query_find_allocation_meta(query, GST_VIDEO_META_API_TYPE, NULL);\n> +\n> +\t\t\tif (add_video_meta) {\n> +\t\t\t\tguint k, stride;\n> +\t\t\t\tgsize offset = 0;\n> +\n> +\t\t\t\t/* this should be updated if tiled formats get added in the future. */\n> +\t\t\t\tfor (k = 0; k < GST_VIDEO_INFO_N_PLANES(&info); k++) {\n> +\t\t\t\t\tstride = gst_video_format_info_extrapolate_stride(info.finfo, k, stream_cfg.stride);\n> +\t\t\t\t\tinfo.stride[k] = stride;\n> +\t\t\t\t\tinfo.offset[k] = offset;\n> +\t\t\t\t\toffset += stride * GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT(info.finfo, k,\n> +\t\t\t\t\t\t\t\t\t\t\t      GST_VIDEO_INFO_HEIGHT(&info));\n> +\t\t\t\t}\n\nThis is getting too deep. Please make use of helper functions to keep the depth\nsmall and the code readable.\n\nNicolas\n\n> +\t\t\t} else {\n> +\t\t\t\tGstBufferPool *pool = NULL;\n> +\t\t\t\tself->frame_copy = true;\n> +\n> +\t\t\t\tif (gst_query_get_n_allocation_pools(query) > 0)\n> +\t\t\t\t\tgst_query_parse_nth_allocation_pool(query, 0, &pool, NULL, NULL, NULL);\n> +\n> +\t\t\t\tif (self->pool)\n> +\t\t\t\t\tgst_object_unref(self->pool);\n> +\n> +\t\t\t\tif (pool)\n> +\t\t\t\t\tself->pool = pool;\n> +\t\t\t\telse {\n> +\t\t\t\t\tGstStructure *config;\n> +\t\t\t\t\tguint min_buffers = 3;\n> +\t\t\t\t\tself->pool = gst_video_buffer_pool_new();\n> +\n> +\t\t\t\t\tconfig = gst_buffer_pool_get_config(self->pool);\n> +\t\t\t\t\tgst_buffer_pool_config_set_params(config, caps, info.size, min_buffers, 0);\n> +\t\t\t\t\tgst_buffer_pool_set_config(GST_BUFFER_POOL_CAST(self->pool), config);\n> +\t\t\t\t\tGST_DEBUG_OBJECT(self, \"Own pool config is %\" GST_PTR_FORMAT, config);\n> +\t\t\t\t}\n> +\n> +\t\t\t\tif (!gst_buffer_pool_set_active(self->pool, true)) {\n> +\t\t\t\t\tGST_ERROR_OBJECT(self, \"Failed to active buffer pool\");\n> +\t\t\t\t\tgst_caps_unref(caps);\n> +\t\t\t\t\treturn false;\n> +\t\t\t\t}\n> +\t\t\t}\n> +\t\t\tgst_query_unref(query);\n> +\t\t}\n> +\n> +\t\tself->info = info;\n> +\t\tGstLibcameraPool *pool = gst_libcamera_pool_new(self->allocator, stream_cfg.stream(),\n> +\t\t\t\t\t\t\t\t&info, add_video_meta);\n>  \t\tg_signal_connect_swapped(pool, \"buffer-notify\",\n>  \t\t\t\t\t G_CALLBACK(gst_task_resume), self->task);\n>  \n> @@ -842,6 +970,12 @@ gst_libcamera_src_finalize(GObject *object)\n>  \tGObjectClass *klass = G_OBJECT_CLASS(gst_libcamera_src_parent_class);\n>  \tGstLibcameraSrc *self = GST_LIBCAMERA_SRC(object);\n>  \n> +\tif (self->pool) {\n> +\t\tgst_buffer_pool_set_active(self->pool, false);\n> +\t\tgst_object_unref(self->pool);\n> +\t\tself->pool = NULL;\n> +\t}\n> +\n>  \tg_rec_mutex_clear(&self->stream_lock);\n>  \tg_clear_object(&self->task);\n>  \tg_mutex_clear(&self->state->lock_);\n> @@ -875,6 +1009,8 @@ gst_libcamera_src_init(GstLibcameraSrc *self)\n>  \t/* C-style friend. */\n>  \tstate->src_ = self;\n>  \tself->state = state;\n> +\tself->frame_copy = false;\n> +\tself->pool = NULL;\n>  }\n>  \n>  static GstPad *","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 2DA5EC32AF\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 22 Nov 2024 16:07:04 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 6BC5865FDD;\n\tFri, 22 Nov 2024 17:07:03 +0100 (CET)","from mail-qv1-xf34.google.com (mail-qv1-xf34.google.com\n\t[IPv6:2607:f8b0:4864:20::f34])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 321B765F51\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 22 Nov 2024 17:07:01 +0100 (CET)","by mail-qv1-xf34.google.com with SMTP id\n\t6a1803df08f44-6d402ce7aa3so14657236d6.1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 22 Nov 2024 08:07:01 -0800 (PST)","from nicolas-tpx395.localdomain ([2606:6d00:15:862e::7a9])\n\tby smtp.gmail.com with ESMTPSA id\n\t6a1803df08f44-6d451b49c3csm11245156d6.99.2024.11.22.08.06.59\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tFri, 22 Nov 2024 08:06:59 -0800 (PST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=ndufresne-ca.20230601.gappssmtp.com\n\theader.i=@ndufresne-ca.20230601.gappssmtp.com\n\theader.b=\"q/jbt0Kv\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=ndufresne-ca.20230601.gappssmtp.com; s=20230601; t=1732291620;\n\tx=1732896420; darn=lists.libcamera.org; \n\th=mime-version:user-agent:content-transfer-encoding:references\n\t:in-reply-to:date:cc:to:from:subject:message-id:from:to:cc:subject\n\t:date:message-id:reply-to;\n\tbh=qu2CL4fzaBD80ud5Chfz2f90RXkuZq/xTbT/dfx6Z2Y=;\n\tb=q/jbt0KvX5Ak5E+RaWZYlB5xkmvIxTP1TKx73zDlnsOns1etNfu8xec5DaniNdB65a\n\tdG/9vSoQFSmFdd4uL3Yp6pz8Qx+JYlO/8z1okdC4xE7Rs3eu5FAze1EEXTIHEzn333bO\n\tVHO23Hqn/of0l21uBDauUrFs5F1nYVg4jJRoe1N2dOOQx/O3HmRl4VHmMzAGpbBnXzDS\n\tpVLfADfIBc/VVkSMngI3bNB9O0Y/oFJXUMt0byWN44WjFWl872HZXZmkMO4LE51pQjLQ\n\tVZOrO68dxvr/1MZNIM+uI3vtqfro8RFWWYXHVvyxyFVgqOWAAKgoqN6r7CXvOGGJQAPy\n\tOf2A==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1732291620; x=1732896420;\n\th=mime-version:user-agent:content-transfer-encoding:references\n\t:in-reply-to:date:cc:to:from:subject:message-id:x-gm-message-state\n\t:from:to:cc:subject:date:message-id:reply-to;\n\tbh=qu2CL4fzaBD80ud5Chfz2f90RXkuZq/xTbT/dfx6Z2Y=;\n\tb=JvhBZ5yWa0XbZ8FF4pAEAjirSpHL3Df9K3e1gRLsxJD2tYsVe8bu1IWtBP2vBT174D\n\t0crQFONyNrc7rFJ8O1xrzapsV92w2tdl1E282JnQqpe/8d8F6k3xeK7Hj2XB+DOBfE3J\n\t9/lDhuHL+M5kLR+tqwSsg7DleDWtj5VacWsLpfsbm5jBfltkrI75GHS9q65Npuxq3S9o\n\tcWCDSGX38oGyEAQyVt0hnc0hJAmYqP6qHR5epqU8CPFrp0ksGi+QpXHohv4A0PgamSen\n\tOCS8hxvUafCBgd/FEZyZ1Cnn24NcI5QRkeRKmD5c6JJEOIritKI+4+SkSALpU2ZRVYva\n\tX81Q==","X-Forwarded-Encrypted":"i=1;\n\tAJvYcCU6QAU2vi899v3wBOqQa5Un/3jp50x2DARm09Ejp5Z2cwN6h4qd2ygUcPjsV1pwnhCD0yiEkWqrrfoBuOa9l6k=@lists.libcamera.org","X-Gm-Message-State":"AOJu0YzWA9FhbvXOr8E+yFcqcJb7MhxcZSsYHdIANZ/5x4MxJeTNEnF+\n\tHoOqzHD2IANbtgXF6l1J1tU6SmSXrwIfSNrzhqEeuAIBzkpv5xt0hico1KUeoQk=","X-Gm-Gg":"ASbGncvA+oMzxyCqWmpv3QKN227PyEH9l1ZK2z6tokF+aXbfRPxpy8RtXbmpZFOgLii\n\tEeUVmBLaVjzsfEDoLnEdFIn3E2a46JPgXatLsS91ZBVdNwhzzBEzvoPS0aLCoX+FZPtGd/VWXfC\n\tvE9T8c+Sot75XXp39IgxpWRwbIBzMfQdZl0eCLA71b1/i+NA7TzmFutR0p6s0hxXhLHY6c/Thye\n\tpPq6pTx/S6a5xAgDHUkSHX+CbgrrJqdxYjrzaaHkIWj3PhNHq4=","X-Google-Smtp-Source":"AGHT+IEUwHMtIxwpuqCVCYAvDTrsOQtRtFI3QSvzM07WwlRXfBy9r6FHyx6T+pUlUHae6bu2qLoFcw==","X-Received":"by 2002:ad4:574c:0:b0:6cc:11cc:6f36 with SMTP id\n\t6a1803df08f44-6d450e7e250mr45638646d6.3.1732291619985; \n\tFri, 22 Nov 2024 08:06:59 -0800 (PST)","Message-ID":"<cf65cd86c0f4e748e7cebdc7039949f1c4b665ef.camel@ndufresne.ca>","Subject":"Re: [PATCH v5] gstreamer: Add GstVideoMeta support","From":"Nicolas Dufresne <nicolas@ndufresne.ca>","To":"Hou Qi <qi.hou@nxp.com>, libcamera-devel@lists.libcamera.org","Cc":"jared.hu@nxp.com, julien.vuillaumier@nxp.com","Date":"Fri, 22 Nov 2024 11:06:58 -0500","In-Reply-To":"<20241122042404.474292-1-qi.hou@nxp.com>","References":"<20241122042404.474292-1-qi.hou@nxp.com>","Content-Type":"text/plain; charset=\"UTF-8\"","Content-Transfer-Encoding":"quoted-printable","User-Agent":"Evolution 3.54.1 (3.54.1-1.fc41) ","MIME-Version":"1.0","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":32504,"web_url":"https://patchwork.libcamera.org/comment/32504/","msgid":"<PAXPR04MB8285132CE21FBC86DB8DDFDE97372@PAXPR04MB8285.eurprd04.prod.outlook.com>","date":"2024-12-04T09:01:43","subject":"RE: [EXT] Re: [PATCH v5] gstreamer: Add GstVideoMeta support","submitter":{"id":195,"url":"https://patchwork.libcamera.org/api/people/195/","name":"Qi Hou","email":"qi.hou@nxp.com"},"content":"Hi Nicolas,\n\nThanks for your review. I have sent out v6, please help continue to give comments based on v6.\nI replace \"GST_META_FLAG_SET (vmeta, GST_META_FLAG_POOLED)\" with \" GST_META_FLAGS(vmeta) = (GstMetaFlags)(GST_META_FLAGS(vmeta) | GST_META_FLAG_POOLED);\" to fix below build error.\n\n\n| /home/imx-libcamera/build-libcamera/tmp/work/armv8a-poky-linux/libcamera/0.3.0.imx/recipe-sysroot/usr/include/gstreamer-1.0/gst/gstmeta.h:79:71: error: invalid conversion from 'int' to 'GstMetaFlags' [-fpermissive]\n|    79 | #define GST_META_FLAG_SET(meta,flag)           (GST_META_FLAGS (meta) |= (flag))\n|       |                                                                       ^\n|       |                                                                       |\n|       |                                                                       int\n| ../git/src/gstreamer/gstlibcamerapool.cpp:150:9: note: in expansion of macro 'GST_META_FLAG_SET'\n|   150 |         GST_META_FLAG_SET (vmeta, GST_META_FLAG_POOLED);\n|       |         ^~~~~~~~~~~~~~~~~\n\nRegards,\nQi Hou\n\n-----Original Message-----\nFrom: Nicolas Dufresne <nicolas@ndufresne.ca> \nSent: 2024年11月23日 0:07\nTo: Qi Hou <qi.hou@nxp.com>; libcamera-devel@lists.libcamera.org\nCc: Jared Hu <jared.hu@nxp.com>; Julien Vuillaumier <julien.vuillaumier@nxp.com>\nSubject: [EXT] Re: [PATCH v5] gstreamer: Add GstVideoMeta support\n\nCaution: This is an external email. Please take care when clicking links or opening attachments. When in doubt, report the message using the 'Report this email' button\n\n\nHi,\n\nLe vendredi 22 novembre 2024 à 13:24 +0900, Hou Qi a écrit :\n> GStreamer video-info calculated stride and offset may differ from \n> those used by the camera.\n>\n> For stride and offset mismatch, this patch adds video meta to buffer \n> if downstream supports VideoMeta through allocation query. Otherwise, \n> get downstream provided buffer pool or create internal buffer pool \n> using the caps, and copy video frame to the system memory acquired \n> from the buffer pool.\n>\n> Signed-off-by: Hou Qi <qi.hou@nxp.com>\n> ---\n>  src/gstreamer/gstlibcamera-utils.cpp |  33 +++++++\n>  src/gstreamer/gstlibcamera-utils.h   |   4 +\n>  src/gstreamer/gstlibcamerapool.cpp   |   9 +-\n>  src/gstreamer/gstlibcamerapool.h     |   3 +-\n>  src/gstreamer/gstlibcamerasrc.cpp    | 142 ++++++++++++++++++++++++++-\n>  5 files changed, 186 insertions(+), 5 deletions(-)\n>\n> diff --git a/src/gstreamer/gstlibcamera-utils.cpp \n> b/src/gstreamer/gstlibcamera-utils.cpp\n> index 732987ef..09af9204 100644\n> --- a/src/gstreamer/gstlibcamera-utils.cpp\n> +++ b/src/gstreamer/gstlibcamera-utils.cpp\n> @@ -591,6 +591,39 @@ gst_task_resume(GstTask *task)  }  #endif\n>\n> +#if !GST_CHECK_VERSION(1, 22, 0)\n> +/*\n> + * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>\n> + * Library       <2002> Ronald Bultje <rbultje@ronald.bitfreak.net>\n> + * Copyright (C) <2007> David A. Schleef <ds@schleef.org>  */\n> +/* This function has been imported directly from the gstreamer \n> +project to\n> + * support backwards compatibility and should be removed when the \n> +older\n> + * version is no longer supported. */ gint \n> +gst_video_format_info_extrapolate_stride(const GstVideoFormatInfo \n> +*finfo, gint plane, gint stride) {\n> +     gint estride;\n> +     gint comp[GST_VIDEO_MAX_COMPONENTS];\n> +     gint i;\n> +\n> +     /* there is nothing to extrapolate on first plane */\n> +     if (plane == 0)\n> +             return stride;\n> +\n> +     gst_video_format_info_component(finfo, plane, comp);\n> +\n> +     /* For now, all planar formats have a single component on first plane, but\n> +     * if there was a planar format with more, we'd have to make a ratio of the\n> +     * number of component on the first plane against the number of component on\n> +     * the current plane. */\n> +     estride = 0;\n> +     for (i = 0; i < GST_VIDEO_MAX_COMPONENTS && comp[i] >= 0; i++)\n> +             estride += GST_VIDEO_FORMAT_INFO_SCALE_WIDTH(finfo, \n> + comp[i], stride);\n> +\n> +     return estride;\n> +}\n> +#endif\n> +\n>  G_LOCK_DEFINE_STATIC(cm_singleton_lock);\n>  static std::weak_ptr<CameraManager> cm_singleton_ptr;\n>\n> diff --git a/src/gstreamer/gstlibcamera-utils.h \n> b/src/gstreamer/gstlibcamera-utils.h\n> index cab1c814..81149280 100644\n> --- a/src/gstreamer/gstlibcamera-utils.h\n> +++ b/src/gstreamer/gstlibcamera-utils.h\n> @@ -35,6 +35,10 @@ static inline void gst_clear_event(GstEvent \n> **event_ptr)  #if !GST_CHECK_VERSION(1, 17, 1)  gboolean \n> gst_task_resume(GstTask *task);  #endif\n> +\n> +#if !GST_CHECK_VERSION(1, 22, 0)\n> +gint gst_video_format_info_extrapolate_stride(const \n> +GstVideoFormatInfo *finfo, gint plane, gint stride); #endif\n>  std::shared_ptr<libcamera::CameraManager> \n> gst_libcamera_get_camera_manager(int &ret);\n>\n>  /**\n> diff --git a/src/gstreamer/gstlibcamerapool.cpp \n> b/src/gstreamer/gstlibcamerapool.cpp\n> index 9cd7eccb..04bcaeb1 100644\n> --- a/src/gstreamer/gstlibcamerapool.cpp\n> +++ b/src/gstreamer/gstlibcamerapool.cpp\n> @@ -135,7 +135,8 @@ \n> gst_libcamera_pool_class_init(GstLibcameraPoolClass *klass)  }\n>\n>  GstLibcameraPool *\n> -gst_libcamera_pool_new(GstLibcameraAllocator *allocator, Stream \n> *stream)\n> +gst_libcamera_pool_new(GstLibcameraAllocator *allocator, Stream *stream,\n> +                    GstVideoInfo *info, gboolean add_video_meta)\n\nValid simplification is to always add the VideoMeta, since when the stride isn't \"standard\" we will buffer copy in the context of the lack of VideoMeta downstream.\n\n>  {\n>       auto *pool = \n> GST_LIBCAMERA_POOL(g_object_new(GST_TYPE_LIBCAMERA_POOL, nullptr));\n>\n> @@ -145,6 +146,12 @@ gst_libcamera_pool_new(GstLibcameraAllocator *allocator, Stream *stream)\n>       gsize pool_size = gst_libcamera_allocator_get_pool_size(allocator, stream);\n>       for (gsize i = 0; i < pool_size; i++) {\n>               GstBuffer *buffer = gst_buffer_new();\n> +             if (add_video_meta) {\n> +                     gst_buffer_add_video_meta_full(buffer, GST_VIDEO_FRAME_FLAG_NONE,\n> +                                                    GST_VIDEO_INFO_FORMAT(info), GST_VIDEO_INFO_WIDTH(info),\n> +                                                    GST_VIDEO_INFO_HEIGHT(info), GST_VIDEO_INFO_N_PLANES(info),\n> +                                                    info->offset, \n> + info->stride);\n\nYou need to do GST_META_FLAG_SET (vmeta, GST_META_FLAG_POOLED), otherwise the meta will be removed when the buffer is returned.\n\n> +             }\n>               pool->queue->push_back(buffer);\n>       }\n>\n> diff --git a/src/gstreamer/gstlibcamerapool.h \n> b/src/gstreamer/gstlibcamerapool.h\n> index 2a7a9c77..b522b4a5 100644\n> --- a/src/gstreamer/gstlibcamerapool.h\n> +++ b/src/gstreamer/gstlibcamerapool.h\n> @@ -14,6 +14,7 @@\n>  #include \"gstlibcameraallocator.h\"\n>\n>  #include <gst/gst.h>\n> +#include <gst/video/video.h>\n>\n>  #include <libcamera/stream.h>\n>\n> @@ -21,7 +22,7 @@\n>  G_DECLARE_FINAL_TYPE(GstLibcameraPool, gst_libcamera_pool, \n> GST_LIBCAMERA, POOL, GstBufferPool)\n>\n>  GstLibcameraPool *gst_libcamera_pool_new(GstLibcameraAllocator *allocator,\n> -                                      libcamera::Stream *stream);\n> +                                      libcamera::Stream *stream, \n> + GstVideoInfo *info, gboolean add_video_meta);\n>\n>  libcamera::Stream *gst_libcamera_pool_get_stream(GstLibcameraPool \n> *self);\n>\n> diff --git a/src/gstreamer/gstlibcamerasrc.cpp \n> b/src/gstreamer/gstlibcamerasrc.cpp\n> index 8efa25f4..eb43f053 100644\n> --- a/src/gstreamer/gstlibcamerasrc.cpp\n> +++ b/src/gstreamer/gstlibcamerasrc.cpp\n> @@ -150,6 +150,10 @@ struct _GstLibcameraSrc {\n>       GstLibcameraSrcState *state;\n>       GstLibcameraAllocator *allocator;\n>       GstFlowCombiner *flow_combiner;\n> +\n> +     gboolean frame_copy;\n> +     GstVideoInfo info;\n> +     GstBufferPool *pool;\n\nYou missed that the libcamerasrc element is a multi-stream element, each pads can run at a different resolution, but a GstBufferPool is a pool if homogeneous buffers.\n\nIn fact, the pools are already stored in the GstPad subclass, and can be accessed by gst_libcamera_pad_get_pool(). You can add in there the per-stream state needed, you can even offload the copy into some wrapper around pad push placed into the GstLibcameraPad class.\n\nNicolas\n\n>  };\n>\n>  enum {\n> @@ -268,6 +272,43 @@ GstLibcameraSrcState::requestCompleted(Request *request)\n>       gst_task_resume(src_->task);\n>  }\n>\n> +static GstFlowReturn\n> +gst_libcamera_video_frame_copy(GstLibcameraSrc *self, GstBuffer *src, \n> +GstBuffer *dest, guint32 stride) {\n> +     GstVideoInfo src_info = self->info;\n> +     GstVideoFrame src_frame, dest_frame;\n> +     gsize offset = 0;\n> +\n> +     for (guint i = 0; i < GST_VIDEO_INFO_N_PLANES(&src_info); i++) {\n> +             stride = gst_video_format_info_extrapolate_stride(src_info.finfo, i, stride);\n> +             src_info.stride[i] = stride;\n> +             src_info.offset[i] = offset;\n> +             offset += stride * GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT(src_info.finfo, i,\n> +                                                                   GST_VIDEO_INFO_HEIGHT(&src_info));\n> +     }\n> +     src_info.size = gst_buffer_get_size(src);\n> +\n> +     if (!gst_video_frame_map(&src_frame, &src_info, src, GST_MAP_READ))\n> +             goto invalid_buffer;\n> +\n> +     if (!gst_video_frame_map(&dest_frame, &self->info, dest, GST_MAP_WRITE)) {\n> +             gst_video_frame_unmap(&src_frame);\n> +             goto invalid_buffer;\n> +     }\n> +\n> +     gst_video_frame_copy(&dest_frame, &src_frame);\n> +\n> +     gst_video_frame_unmap(&src_frame);\n> +     gst_video_frame_unmap(&dest_frame);\n> +\n> +     return GST_FLOW_OK;\n> +\n> +invalid_buffer : {\n> +     GST_ERROR_OBJECT(self, \"Could not map buffer\");\n> +     return GST_FLOW_ERROR;\n> +}\n> +}\n> +\n>  /* Must be called with stream_lock held. */  int \n> GstLibcameraSrcState::processRequest()\n>  {\n> @@ -292,12 +333,34 @@ int GstLibcameraSrcState::processRequest()\n>       GstFlowReturn ret = GST_FLOW_OK;\n>       gst_flow_combiner_reset(src_->flow_combiner);\n>\n> -     for (GstPad *srcpad : srcpads_) {\n> +     for (gsize i = 0; i < src_->state->srcpads_.size(); i++) {\n> +             GstPad *srcpad = src_->state->srcpads_[i];\n>               Stream *stream = gst_libcamera_pad_get_stream(srcpad);\n>               GstBuffer *buffer = wrap->detachBuffer(stream);\n> +             GstBuffer *tmp = NULL;\n\nPlease clarify the buffer names, its not clear what they are.\n\n>\n> +             const StreamConfiguration &stream_cfg = \n> + src_->state->config_->at(i);\n>               FrameBuffer *fb = \n> gst_libcamera_buffer_get_frame_buffer(buffer);\n>\n> +             if (src_->frame_copy) {\n> +                     ret = gst_buffer_pool_acquire_buffer(src_->pool, &tmp, NULL);\n> +                     if (ret != GST_FLOW_OK) {\n> +                             GST_ERROR(\"Failed to acquire buffer\");\n> +                             gst_buffer_unref(buffer);\n> +                             return -EPIPE;\n> +                     }\n> +\n> +                     ret = gst_libcamera_video_frame_copy(src_, buffer, tmp, stream_cfg.stride);\n> +                     gst_buffer_unref(buffer);\n> +                     if (ret != GST_FLOW_OK) {\n> +                             GST_ERROR(\"Failed to copy buffer\");\n> +                             gst_buffer_unref(tmp);\n> +                             return -EPIPE;\n> +                     }\n> +\n> +                     buffer = tmp;\n> +             }\n> +\n>               if (GST_CLOCK_TIME_IS_VALID(wrap->pts_)) {\n>                       GST_BUFFER_PTS(buffer) = wrap->pts_;\n>                       gst_libcamera_pad_set_latency(srcpad, \n> wrap->latency_); @@ -497,9 +560,74 @@ gst_libcamera_src_negotiate(GstLibcameraSrc *self)\n>       for (gsize i = 0; i < state->srcpads_.size(); i++) {\n>               GstPad *srcpad = state->srcpads_[i];\n>               const StreamConfiguration &stream_cfg = \n> state->config_->at(i);\n> +             gboolean add_video_meta = false;\n> +             GstVideoInfo info;\n> +\n> +             g_autoptr(GstCaps) caps = gst_libcamera_stream_configuration_to_caps(stream_cfg);\n> +             gst_libcamera_framerate_to_caps(caps, element_caps);\n>\n> -             GstLibcameraPool *pool = gst_libcamera_pool_new(self->allocator,\n> -                                                             stream_cfg.stream());\n> +             gst_video_info_init(&info);\n> +             gst_video_info_from_caps(&info, caps);\n> +\n> +             /* stride mismatch between camera stride and that calculated by video-info */\n> +             if (static_cast<unsigned int>(info.stride[0]) != stream_cfg.stride &&\n> +                 GST_VIDEO_INFO_FORMAT(&info) != GST_VIDEO_FORMAT_ENCODED) {\n> +                     GstQuery *query = NULL;\n> +                     gboolean need_pool = true;\n> +\n> +                     query = gst_query_new_allocation(caps, need_pool);\n> +                     if (!gst_pad_peer_query(srcpad, query))\n> +                             GST_DEBUG_OBJECT(self, \"Didn't get downstream ALLOCATION hints\");\n> +                     else\n> +                             add_video_meta = \n> + gst_query_find_allocation_meta(query, GST_VIDEO_META_API_TYPE, \n> + NULL);\n> +\n> +                     if (add_video_meta) {\n> +                             guint k, stride;\n> +                             gsize offset = 0;\n> +\n> +                             /* this should be updated if tiled formats get added in the future. */\n> +                             for (k = 0; k < GST_VIDEO_INFO_N_PLANES(&info); k++) {\n> +                                     stride = gst_video_format_info_extrapolate_stride(info.finfo, k, stream_cfg.stride);\n> +                                     info.stride[k] = stride;\n> +                                     info.offset[k] = offset;\n> +                                     offset += stride * GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT(info.finfo, k,\n> +                                                                                           GST_VIDEO_INFO_HEIGHT(&info));\n> +                             }\n\nThis is getting too deep. Please make use of helper functions to keep the depth small and the code readable.\n\nNicolas\n\n> +                     } else {\n> +                             GstBufferPool *pool = NULL;\n> +                             self->frame_copy = true;\n> +\n> +                             if (gst_query_get_n_allocation_pools(query) > 0)\n> +                                     \n> + gst_query_parse_nth_allocation_pool(query, 0, &pool, NULL, NULL, \n> + NULL);\n> +\n> +                             if (self->pool)\n> +                                     gst_object_unref(self->pool);\n> +\n> +                             if (pool)\n> +                                     self->pool = pool;\n> +                             else {\n> +                                     GstStructure *config;\n> +                                     guint min_buffers = 3;\n> +                                     self->pool = \n> + gst_video_buffer_pool_new();\n> +\n> +                                     config = gst_buffer_pool_get_config(self->pool);\n> +                                     gst_buffer_pool_config_set_params(config, caps, info.size, min_buffers, 0);\n> +                                     gst_buffer_pool_set_config(GST_BUFFER_POOL_CAST(self->pool), config);\n> +                                     GST_DEBUG_OBJECT(self, \"Own pool config is %\" GST_PTR_FORMAT, config);\n> +                             }\n> +\n> +                             if (!gst_buffer_pool_set_active(self->pool, true)) {\n> +                                     GST_ERROR_OBJECT(self, \"Failed to active buffer pool\");\n> +                                     gst_caps_unref(caps);\n> +                                     return false;\n> +                             }\n> +                     }\n> +                     gst_query_unref(query);\n> +             }\n> +\n> +             self->info = info;\n> +             GstLibcameraPool *pool = gst_libcamera_pool_new(self->allocator, stream_cfg.stream(),\n> +                                                             &info, \n> + add_video_meta);\n>               g_signal_connect_swapped(pool, \"buffer-notify\",\n>                                        G_CALLBACK(gst_task_resume), \n> self->task);\n>\n> @@ -842,6 +970,12 @@ gst_libcamera_src_finalize(GObject *object)\n>       GObjectClass *klass = G_OBJECT_CLASS(gst_libcamera_src_parent_class);\n>       GstLibcameraSrc *self = GST_LIBCAMERA_SRC(object);\n>\n> +     if (self->pool) {\n> +             gst_buffer_pool_set_active(self->pool, false);\n> +             gst_object_unref(self->pool);\n> +             self->pool = NULL;\n> +     }\n> +\n>       g_rec_mutex_clear(&self->stream_lock);\n>       g_clear_object(&self->task);\n>       g_mutex_clear(&self->state->lock_);\n> @@ -875,6 +1009,8 @@ gst_libcamera_src_init(GstLibcameraSrc *self)\n>       /* C-style friend. */\n>       state->src_ = self;\n>       self->state = state;\n> +     self->frame_copy = false;\n> +     self->pool = NULL;\n>  }\n>\n>  static GstPad *","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id ACFFCBDB1C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed,  4 Dec 2024 09:01:49 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 633D166086;\n\tWed,  4 Dec 2024 10:01:48 +0100 (CET)","from DB3PR0202CU003.outbound.protection.outlook.com\n\t(mail-northeuropeazlp170110001.outbound.protection.outlook.com\n\t[IPv6:2a01:111:f403:c200::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id AEEF8618B5\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed,  4 Dec 2024 10:01:45 +0100 (CET)","from PAXPR04MB8285.eurprd04.prod.outlook.com\n\t(2603:10a6:102:1ca::15)\n\tby DB9PR04MB9964.eurprd04.prod.outlook.com (2603:10a6:10:4c1::21)\n\twith Microsoft SMTP Server (version=TLS1_2,\n\tcipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.8207.19;\n\tWed, 4 Dec 2024 09:01:43 +0000","from PAXPR04MB8285.eurprd04.prod.outlook.com\n\t([fe80::e003:8fb:64ea:acfd]) by\n\tPAXPR04MB8285.eurprd04.prod.outlook.com\n\t([fe80::e003:8fb:64ea:acfd%6]) with mapi id 15.20.8207.017;\n\tWed, 4 Dec 2024 09:01:43 +0000"],"Authentication-Results":["lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=nxp.com header.i=@nxp.com header.b=\"J339hCuH\";\n\tdkim-atps=neutral","dkim=none (message not signed)\n\theader.d=none;dmarc=none action=none header.from=nxp.com;"],"ARC-Seal":"i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none;\n\tb=TLdLzv3T4VgRo6cSxYVaGYEtdf1EEmFwBmO7KhXLwMjrhpuyPlq7oghh2umXFOtgjemUq9NCka8kKkGko5tmEbd+wHJFgA6aGsWbvcHAeLhwGfN/C+hIbY6Tx9tylZTtjR0goS+xmMRZF/l+5wtgxgAew74vnmpIHfQ+SJ/qTapl1+eV/KJ6DkOWOQ4Ad/d7la9mZdVeQ+lxBxno9XFovKhYt/HsjebCzHQA6O4BMVowZpyljjwYFEA5bNb5iqnZDeXnT099PnVxZ8fscAQsM4zWFXlQuX9XfUcZQGbP8LlC0sfQMpc+BpQRdOKICOlPWFzqQ+wa/JzRs6m2IfmWZw==","ARC-Message-Signature":"i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com;\n\ts=arcselector10001;\n\th=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1;\n\tbh=MS0LU7zujnkAev6OGsJWBw2tITu4mQFK4y3bUyU3MCU=;\n\tb=B/EgAOssS524m12YtuCIs+ge9TIE3naYjtQrFPEY2gmmapGZh3L2gBAYWNZQ73ExuxzHcJMJl0v40bdij1Zkisn9Q/Vqz6FXtBlzPRQLF1Drtl/8ZsG+AdsmVxXiGI1+ehmf6GwUh/waZB5EcOA8KbBmdL0qFZSXhDWpHNbiMBQp09ZOnXy4ewR/vNcG2Vj8Oox8hDwjpHouQdMGRgDRp46ZA8tTmPmuZeyQ3mbAI8/dT/pqQK3Fa/jFmsUOtEgBJMN3iZEo0YjTkp/NUNgcGUbjyYgN8eaDzC3GlVcK7XxYAgef1FtOgPT0JMrvVQ1fAwH4XTXgzioZx5iTyPDNRQ==","ARC-Authentication-Results":"i=1; mx.microsoft.com 1; spf=pass\n\tsmtp.mailfrom=nxp.com; dmarc=pass action=none header.from=nxp.com;\n\tdkim=pass header.d=nxp.com; arc=none","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=nxp.com; s=selector1;\n\th=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck;\n\tbh=MS0LU7zujnkAev6OGsJWBw2tITu4mQFK4y3bUyU3MCU=;\n\tb=J339hCuHWcoUR0DcHwgKTPIqfiG/KkRQWEFP1u+Y7kObufhq6l9H8axowPoZXaz8KSlbwkr031KTLXVORQ1TyD38ro75HKBcrlWhi/tngOY5A0NsW9YDJUlmcDsLG0IYIZF1BmwV5vAfRh2vr9sypLwWs3tsxjFAgubbivjE01AmV+jtT5mY5QfzZyA4qT2FcRALIC4NDyrGo0kJvBJ5uKC/iznzTpbN7yybiGnSLAETnY4i1Mo3Nm91AbDy6DPkvxktnZoGOOa4xbuXTDalD522x7PhZEurlaVSh9uV3OY077VWWkWfUxewIdjkvcc55xEw31jho7VItGd6ZtaxKA==","From":"Qi Hou <qi.hou@nxp.com>","To":"Nicolas Dufresne <nicolas@ndufresne.ca>,\n\t\"libcamera-devel@lists.libcamera.org\"\n\t<libcamera-devel@lists.libcamera.org>","CC":"Jared Hu <jared.hu@nxp.com>, Julien Vuillaumier\n\t<julien.vuillaumier@nxp.com>","Subject":"RE: [EXT] Re: [PATCH v5] gstreamer: Add GstVideoMeta support","Thread-Topic":"[EXT] Re: [PATCH v5] gstreamer: Add GstVideoMeta support","Thread-Index":"AQHbPJZofMEZK5PM10e+oI9VbNiX0LLDd/AAgBJjjjA=","Date":"Wed, 4 Dec 2024 09:01:43 +0000","Message-ID":"<PAXPR04MB8285132CE21FBC86DB8DDFDE97372@PAXPR04MB8285.eurprd04.prod.outlook.com>","References":"<20241122042404.474292-1-qi.hou@nxp.com>\n\t<cf65cd86c0f4e748e7cebdc7039949f1c4b665ef.camel@ndufresne.ca>","In-Reply-To":"<cf65cd86c0f4e748e7cebdc7039949f1c4b665ef.camel@ndufresne.ca>","Accept-Language":"zh-CN, en-US","Content-Language":"en-US","X-MS-Has-Attach":"","X-MS-TNEF-Correlator":"","authentication-results":["lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=nxp.com header.i=@nxp.com header.b=\"J339hCuH\";\n\tdkim-atps=neutral","dkim=none (message not signed)\n\theader.d=none;dmarc=none action=none header.from=nxp.com;"],"x-ms-publictraffictype":"Email","x-ms-traffictypediagnostic":"PAXPR04MB8285:EE_|DB9PR04MB9964:EE_","x-ms-office365-filtering-correlation-id":"abfb3a51-f57a-40fc-a8dd-08dd1442465e","x-ms-exchange-senderadcheck":"1","x-ms-exchange-antispam-relay":"0","x-microsoft-antispam":"BCL:0;\n\tARA:13230040|1800799024|376014|366016|38070700018; ","x-microsoft-antispam-message-info":"=?utf-8?q?TlXmRwJ2nswEZf1g2YBriU9jRKfn?=\n\t=?utf-8?q?bnKr3TbDngzEiLLC62CnMzf0X7nknaeyo7QfutLEAv5jyp8Xxg34NKyR?=\n\t=?utf-8?q?s7a13FPXawE1g/QCEAa1GdYTQmuffV/Qljc5l3JCG80NSDNDlTwTK/nO?=\n\t=?utf-8?q?BcLqmldG+EX4IMIgPIwoqmHWOvX5kR1KF6fYEY+5ZbL70NSxu2qUomEF?=\n\t=?utf-8?q?2wZgORx/CpXGbMIziW+dgHHFM/ZuXva6wK/sYW+YxCc/Aze054eDQOTG?=\n\t=?utf-8?q?tqI71Xbhn5URM2yq4v4Bdswe33NVje/VcUkM7c1e6R2VR8eXWyQPEmur?=\n\t=?utf-8?q?WvpJR/BEplhYwcvJS8i28u226AxHnqY2B49/hbtplvR05OHMuwnpsclE?=\n\t=?utf-8?q?KmA5uBGBqhoV7N/79AcADrg0R44PVNzDTjS81yZKMaamHMyKs2Hled2Y?=\n\t=?utf-8?q?ZIhgBFrlCLzdd3k+hz7mItAEBM5r6A7s9nfnIVScUXAb0lHs62VC25BQ?=\n\t=?utf-8?q?OUpwrh0exfbakwcaxXU+NcGh0WJ9E28QBRkmi3uIsQWN0Mvmizkmb5jx?=\n\t=?utf-8?q?Vq9pX91yw2D1Kc0z7L8e/qV5gonGud7EkUeZ50zZcoqKJ2MJEMPeH4h8?=\n\t=?utf-8?q?RpxKSBod7bHQyB5Y3inzwQa7wahHt/eJwUX0LuyV1WEaPoXuoymYMCgT?=\n\t=?utf-8?q?Yvzky9DotNZwr99eNw0UAKTHtv2Gz4SFAoV26hipXW1ZEEk/islgsZWk?=\n\t=?utf-8?q?lZZt/OQuYshsBTMQy3VmvIxv6/pg6mMnMw81bhU9raj4mPZ7OC2PyVrH?=\n\t=?utf-8?q?Qa67PIi0ZFQhyGoKe/aiAnfHY0bbkMn1xNa6quL4foFyNcblTfdhy96r?=\n\t=?utf-8?q?qNinuerXFpgW1Z9HPonooltG4/ozVxdF2BsnVIiWiyT49MoDNr2Dnttm?=\n\t=?utf-8?q?lCBWBKyq34K04jvnAtbrLuaYv7vyKvH2TLERupayZl1dN3+8LI15j2t2?=\n\t=?utf-8?q?dn0OE1kdRhQBEuTzZP1c7HfNGFwX0qsdLmN+Y0mavHPPj61nteBe2E9y?=\n\t=?utf-8?q?w37EdyXgOJqQElohFNVRF7XdqCRKBzy/5UNlVt5sAwUFKctRRbiRmt3k?=\n\t=?utf-8?q?foJxNE8cooGSPSV9vehDiMGSjuHYkZ0NDwHarWt2F9z0MMRs6JE/FVUh?=\n\t=?utf-8?q?hinyY03v3pU2RwUlPg4VAlTVKYJbF02DHfwNtgTi/CQw3y84mcWQnnO5?=\n\t=?utf-8?q?Cyz/rwHhMWwtS40TGUMe0wI3vRC6le4SukR1xIOMQUOXzAF1JHT6al1p?=\n\t=?utf-8?q?I2sElrzVoW66COQD/HLDYX8940SePSPCOFXNIuDgEQJSX/+RIYDVuo1D?=\n\t=?utf-8?q?DPQKFyIF+bToQUMReP5XQhBdGguIZIVAd0DF+ZhAPXUTB2d9GlkQOI74?=\n\t=?utf-8?q?PYGj1n1+Ejxpjk3cBJV6rzDBXKe9JBqEyShJRGYJEKN7uyFvS4gGAtP1?=\n\t=?utf-8?q?PInaa0dLngyaLvk7c+YF95ad7Zat9rXZYF7HiLC33lpuDvh8Ze5a+kcf?=\n\t=?utf-8?q?Ow=3D=3D?=","x-forefront-antispam-report":"CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:;\n\tIPV:NLI; SFV:NSPM; H:PAXPR04MB8285.eurprd04.prod.outlook.com; PTR:;\n\tCAT:NONE; \n\tSFS:(13230040)(1800799024)(376014)(366016)(38070700018); DIR:OUT;\n\tSFP:1101; ","x-ms-exchange-antispam-messagedata-chunkcount":"1","x-ms-exchange-antispam-messagedata-0":"=?utf-8?q?HS1hL+gXFeQmM0k4xCOFGTJ04?=\n\t=?utf-8?q?SeEbQJoSocHTXtMuOzbklGSePHe3TjmQofiBoSigo132aPdwqP32HK1H?=\n\t=?utf-8?q?l6yzSgQinETIl8kRmm0FKbk4NhH4QCHogIKcLIdM2gK7GgkXil+KwUpu?=\n\t=?utf-8?q?bh7bWNLKtLe8PG4W0LwLMiX/dYW7o6Rp2zaGmbOsbPtwCZtsL+eKfr7P?=\n\t=?utf-8?q?13UI0ac0CpPLPJn/taA3qsxFRN+/7fXUfSYAgUUTkWgkg2wzukzLiOkf?=\n\t=?utf-8?q?CszMib71dP137RMLh62xlryd+Frnl11cQM3ur5bXxVCHxQrli1QIxU0+?=\n\t=?utf-8?q?ftzlzQZTYSwKZUIjOlKNwCeX5RhA43oo75qufdqBoLlV1uKCarYSma4u?=\n\t=?utf-8?q?dDvLBCJtw5HW/OSqYoAdzOaMA678YneeTLIdmBsI4GpuBoX0KlsGbG3s?=\n\t=?utf-8?q?oUriMe40rdwsbzEs3dHEiRJOIxwF7iIsrSnv33xw2nQwRNwmTshoTQeS?=\n\t=?utf-8?q?1n7TJgD1Lqn9NdNFQcxvgFh8+Wj0fqZUvJpa8Nhq3hBa1KZ1Leq+74ZZ?=\n\t=?utf-8?q?5kqp1gCDIWXDTepcK7XL2TgMU3wJ4z1UHBh8FcYIkSf03koHk5/t3E+h?=\n\t=?utf-8?q?P8avxS/quGFJq67sG9WvqztpxnjSYnzb1vfH/7GmWZ2ifhbn3s+pQu2A?=\n\t=?utf-8?q?bjbcOWZ4LBlxO0WGL7RvqPCFFFu6RyPt9uk36eNZ3ICnoS/T4/kaWGia?=\n\t=?utf-8?q?rvQV04UX9VmryHZiVjZJnot5/KSuR+uVE2wKzHRHf+E3nPGjaMZNiVJV?=\n\t=?utf-8?q?8Fv7OyBslGyuk2LhEdYHmLL828N35uCJ/JJ1bJwxkXNcJu54tIPjjBG5?=\n\t=?utf-8?q?iExpbq6FA85AARCy3+1sdxQK4qZG8OkortdkH56KOlxpEY3IKJifKyvu?=\n\t=?utf-8?q?dzHmFvs7T/pi/0Rp/xwmTfPzUmX7KygPjKrBDcVA1+/WP2J/rr16PGAP?=\n\t=?utf-8?q?w2ISXBNWbJHp7q+ZRNySZLW/3mm9XLzJC3KPEML7zGYBJqXXVbcTdfmO?=\n\t=?utf-8?q?wmXuVmXKs707+Uhm9Qm+7ht9fsgPjJ4052IXLRuoB2TAlkfXyrglCUj2?=\n\t=?utf-8?q?dtWTvh4NvXkwAVK3QgooSNNDF9gF56/6jCFR3/jFudyiJbyQFAAeD7NL?=\n\t=?utf-8?q?mvuh2ajyjke7m6wk6KxzLMhdOQrC+7Ebl8jWaBmw+IDoNmF52v/KmjOl?=\n\t=?utf-8?q?P6jA6sluQRMcg7DXjUMmL3Rk76TFtCzjGufIzzyjGwFcRS6Ctg/Y6pX/?=\n\t=?utf-8?q?HXP2aFgaN0N7SeTbOVrW6Xwje8WnXb69jgxifpaybrfFMH2POhB9YFw8?=\n\t=?utf-8?q?vvIekOkZ5aX+7uV3DPofrIQ1ToPkDz0zBMOCg51wTLTzcBmFGplqWGR3?=\n\t=?utf-8?q?exqTM1jy/sOb3E6flyErqCFHpKKP48Wqt/cwACDNosWsjOAGJ0S1AXyt?=\n\t=?utf-8?q?lS7qriHamb8IvKCrJcGdGn8p9POH36spdXoeTxs4mLNhx0NStQvsUpnR?=\n\t=?utf-8?q?U1HfAyzLe0gM+J+2RifMTohko/pemJwc8O8EEwr7FLCrR1rJoE1eCy/o?=\n\t=?utf-8?q?83LH8jQwjQLoz/ntCRj0tawme5pd+YY2RfcnXJTLV8qKpqkrMTY722hz?=\n\t=?utf-8?q?W/DqgGcUlvMsOiOh3CyNuBB04fr3zKE6iIdT9fihLo=3D?=","Content-Type":"text/plain; charset=\"utf-8\"","Content-Transfer-Encoding":"base64","MIME-Version":"1.0","X-OriginatorOrg":"nxp.com","X-MS-Exchange-CrossTenant-AuthAs":"Internal","X-MS-Exchange-CrossTenant-AuthSource":"PAXPR04MB8285.eurprd04.prod.outlook.com","X-MS-Exchange-CrossTenant-Network-Message-Id":"abfb3a51-f57a-40fc-a8dd-08dd1442465e","X-MS-Exchange-CrossTenant-originalarrivaltime":"04 Dec 2024 09:01:43.7015\n\t(UTC)","X-MS-Exchange-CrossTenant-fromentityheader":"Hosted","X-MS-Exchange-CrossTenant-id":"686ea1d3-bc2b-4c6f-a92c-d99c5c301635","X-MS-Exchange-CrossTenant-mailboxtype":"HOSTED","X-MS-Exchange-CrossTenant-userprincipalname":"1yPq/KhtViM7RvCELSAQO1pPwiV2WobdoiPvBrtcPaIwEYcEP8wUATIPkb2v7gmr","X-MS-Exchange-Transport-CrossTenantHeadersStamped":"DB9PR04MB9964","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]