[{"id":32315,"web_url":"https://patchwork.libcamera.org/comment/32315/","msgid":"<c73176525872aaeb965bc4b35febb280a3e2a1ca.camel@ndufresne.ca>","date":"2024-11-20T19:43:22","subject":"Re: [PATCH v4] 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 mercredi 20 novembre 2024 à 19:04 +0900, Hou Qi a écrit :\n> GStreamer video-info calculated stride and offset may differ from\n> those used by the camera.\n> \n> This patch enhances downstream plugin's support for videometa by\n> adding videometa support for libcamerasrc. It ensures that when\n> downstream plugin supports videometa by allocation query, libcamerasrc\n> also attaches videometa to buffer, preventing discrepancies\n> between the stride and offset calculated by video-info and camera.\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   | 57 +++++++++++++++++++++++++++-\n>  src/gstreamer/gstlibcamerapool.h     |  5 ++-\n>  src/gstreamer/gstlibcamerasrc.cpp    | 47 +++++++++++++++++++++--\n>  5 files changed, 141 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..bd1cc473 100644\n> --- a/src/gstreamer/gstlibcamerapool.cpp\n> +++ b/src/gstreamer/gstlibcamerapool.cpp\n> @@ -29,6 +29,8 @@ struct _GstLibcameraPool {\n>  \tstd::deque<GstBuffer *> *queue;\n>  \tGstLibcameraAllocator *allocator;\n>  \tStream *stream;\n> +\tgboolean frame_copy;\n> +\tGstVideoInfo info;\n>  };\n>  \n>  G_DEFINE_TYPE(GstLibcameraPool, gst_libcamera_pool, GST_TYPE_BUFFER_POOL)\n> @@ -135,16 +137,29 @@ 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 stride_mismatch, gboolean has_video_meta)\n>  {\n>  \tauto *pool = GST_LIBCAMERA_POOL(g_object_new(GST_TYPE_LIBCAMERA_POOL, nullptr));\n>  \n>  \tpool->allocator = GST_LIBCAMERA_ALLOCATOR(g_object_ref(allocator));\n>  \tpool->stream = stream;\n> +\tpool->info = *info;\n> +\n> +\tif (stride_mismatch && !has_video_meta)\n> +\t\tpool->frame_copy = true;\n> +\telse\n> +\t\tpool->frame_copy = false;\n>  \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 (stride_mismatch && has_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> +\t\t}\n>  \t\tpool->queue->push_back(buffer);\n>  \t}\n>  \n> @@ -163,3 +178,43 @@ gst_libcamera_buffer_get_frame_buffer(GstBuffer *buffer)\n>  \tGstMemory *mem = gst_buffer_peek_memory(buffer, 0);\n>  \treturn gst_libcamera_memory_get_frame_buffer(mem);\n>  }\n> +\n> +GstBuffer *\n> +gst_libcamera_copy_buffer(GstLibcameraPool *self, GstBuffer *src, FrameBuffer *fb, guint32 stride)\n\nA function that says \"copy_buffer\" should always copy the buffers. The naming\nshould be reconsidered, that being said see comment below.\n\n> +{\n> +\tif (self->frame_copy) {\n> +\t\tGstVideoInfo src_info = self->info;\n> +\t\tgsize size = GST_VIDEO_INFO_SIZE(&self->info);\n> +\t\tGstBuffer *dest = gst_buffer_new_allocate(NULL, size, NULL);\n\nThe copy condition should be moved into the libcamerasrc element code. This way,\nyou can run an allocation query, and keep copy into downstream provided buffer\ninstead of doing dynamic allocation. You can also use a generic GstVideoPool to\navoid dynamic allocations even if downstream did not provide a pool.\n\n> +\t\tGstVideoFrame src_frame, dest_frame;\n> +\t\tint i = 0;\n> +\n> +\t\tfor (const FrameBuffer::Plane &plane : fb->planes()) {\n> +\t\t\tsrc_info.stride[i] = stride;\n> +\t\t\tsrc_info.offset[i] = plane.offset;\n> +\t\t\ti++;\n> +\t\t}\n> +\t\tsrc_info.size = gst_buffer_get_size(src);\n> +\n> +\t\tif (!gst_video_frame_map(&src_frame, &src_info, src, GST_MAP_READ)) {\n> +\t\t\tGST_WARNING(\"fail to map src_frame\");\n> +\t\t\treturn src;\n> +\t\t}\n> +\n> +\t\tif (!gst_video_frame_map(&dest_frame, &self->info, dest, GST_MAP_WRITE)) {\n> +\t\t\tgst_video_frame_unmap(&src_frame);\n> +\t\t\tGST_WARNING(\"fail to map dest_frame\");\n> +\t\t\treturn src;\n> +\t\t}\n\nA map failure would indicate serious issues, we should not ignore these and\ncontinue with the original.\n\n> +\n> +\t\tgst_video_frame_copy(&dest_frame, &src_frame);\n> +\n> +\t\tgst_video_frame_unmap(&src_frame);\n> +\t\tgst_video_frame_unmap(&dest_frame);\n> +\n> +\t\tgst_buffer_unref(src);\n> +\t\treturn dest;\n> +\t}\n> +\n> +\treturn src;\n> +}\n> diff --git a/src/gstreamer/gstlibcamerapool.h b/src/gstreamer/gstlibcamerapool.h\n> index 2a7a9c77..ffbd3173 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,8 +22,10 @@\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 stride_mismatch, gboolean has_video_meta);\n>  \n>  libcamera::Stream *gst_libcamera_pool_get_stream(GstLibcameraPool *self);\n>  \n>  libcamera::FrameBuffer *gst_libcamera_buffer_get_frame_buffer(GstBuffer *buffer);\n> +\n> +GstBuffer *gst_libcamera_copy_buffer(GstLibcameraPool *self, GstBuffer *buffer, libcamera::FrameBuffer *fb, guint32 stride);\n> diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp\n> index 8efa25f4..1d46536a 100644\n> --- a/src/gstreamer/gstlibcamerasrc.cpp\n> +++ b/src/gstreamer/gstlibcamerasrc.cpp\n> @@ -292,12 +292,17 @@ 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\tconst StreamConfiguration &stream_cfg = src_->state->config_->at(i);\n> +\t\tGstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);\n>  \n>  \t\tFrameBuffer *fb = gst_libcamera_buffer_get_frame_buffer(buffer);\n>  \n> +\t\tbuffer = gst_libcamera_copy_buffer(pool, buffer, fb, stream_cfg.stride);\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 +502,45 @@ 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 stride_mismatch = false, has_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\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 = false;\n> +\t\t\tstride_mismatch = 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\thas_video_meta = gst_query_find_allocation_meta(query, GST_VIDEO_META_API_TYPE, NULL);\n> +\t\t\tgst_query_unref(query);\n> +\n> +\t\t\tif (has_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, GST_VIDEO_INFO_HEIGHT(&info));\n> +\t\t\t\t}\n> +\t\t\t}\n> +\t\t}\n>  \n> -\t\tGstLibcameraPool *pool = gst_libcamera_pool_new(self->allocator,\n> -\t\t\t\t\t\t\t\tstream_cfg.stream());\n> +\t\tGstLibcameraPool *pool = gst_libcamera_pool_new(self->allocator, stream_cfg.stream(),\n> +\t\t\t\t\t\t\t\t&info, stride_mismatch, has_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>","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 568C0C32F9\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 20 Nov 2024 19:43:28 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 4DD8265F84;\n\tWed, 20 Nov 2024 20:43:27 +0100 (CET)","from mail-qv1-xf33.google.com (mail-qv1-xf33.google.com\n\t[IPv6:2607:f8b0:4864:20::f33])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 33DEC65F69\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 20 Nov 2024 20:43:25 +0100 (CET)","by mail-qv1-xf33.google.com with SMTP id\n\t6a1803df08f44-6d41b209858so1092316d6.3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 20 Nov 2024 11:43:25 -0800 (PST)","from nicolas-tpx395.localdomain ([2606:6d00:15:862e::7a9])\n\tby smtp.gmail.com with ESMTPSA id\n\t6a1803df08f44-6d4380dbb7csm14345206d6.49.2024.11.20.11.43.23\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tWed, 20 Nov 2024 11:43:23 -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=\"NVMWZyup\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=ndufresne-ca.20230601.gappssmtp.com; s=20230601; t=1732131804;\n\tx=1732736604; 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=J0E8wtixrglEeTxxUQ3+ZUS/vZJuIbwTpWn0X0b+EZk=;\n\tb=NVMWZyupNownvgkzdNTnhjD8ZRM9zoX8AqJdJh75mPYWmC+5tdoLO5/r6LdDEhufX8\n\tYm/MUDe0Qn6qi47LnX8LVB4UN/ornTrSZs1RTdlzxmBskXWU0kZ5Lwb80ZBsjl8+0IJd\n\tLPVuyaomxsFbl9X3OPk6tfOZJXIeE+egjuqqsTmAJckMwx1ys/RKbpLyU8WKzTmWII+B\n\t3p2xpzvtNKpXmAQLgizPBzl3UR/TLYYs+nUyUNTnSftPGkUBVlOcjitdonpv6UnwooEf\n\t1EojW0xqq/ZyCo3LyrqZ3uIMUJsnpVUg361I/xPJisRH/iHEHhMy6fNJHg3dZE6jLhwr\n\t4zmQ==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1732131804; x=1732736604;\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=J0E8wtixrglEeTxxUQ3+ZUS/vZJuIbwTpWn0X0b+EZk=;\n\tb=D61MLfBg4XO4MnIrwUCY7E77FNeglpb3Uf2KMkg13KyYXd5gECB0LMYioNEbTllofG\n\txqdqcquzeMh9JAppAlfH6y06R8mS5bY6GidCPL6cNBgNshUPwe5U9WC8wl9Mw9VjoN8k\n\tCCJfDJ+MpTG7und0XwlvvOAwZWw5jbp5jd9BGGGtdPF/USKYQRsa2gBXphwZdTsHMy9F\n\tD7HwtHdy1ZMOgxH3jt0mH1gQAJH5s5IpYzZhp4hu85nJjus4TyVXBhcZXYXhagxeT2JR\n\t1wKZwG4xb//NayD8QEikeoXwCKCofZCD1JlfDl0+olSxdqiONxsn2t5Ak9pwl47sZwB+\n\tQD2A==","X-Forwarded-Encrypted":"i=1;\n\tAJvYcCUXiQSURrWnPYJJ114VviqnaZQT8aOox9vmzxWYWynzT9ORF+ZIQ9bX+hUgI+oyN2Vg6OHichRHkFLGdyjZxKs=@lists.libcamera.org","X-Gm-Message-State":"AOJu0YynSefUl8V79AaS7te5dGwG4RfFtBA7SiJtEjziKBs8688Vry1j\n\triVERolmeCoBNCoUjraOYxPmAqgbgkwMaJOGT/f3mfDNdB9AfokdFFUGRJp+90edCH43LkdoHwQ\n\to","X-Gm-Gg":"ASbGncs1+ld5aekKwvMK3uhtNX6jY+4huNHSrA3WN/6iFG3XhTj80y1rE+SSvVQVaoU\n\tNxG3DfRA+pQNqD93BXcjxsxjQE/8ECumD3Il2QgffDUJDG+HT3kdARJhIqag6+QGtmNTrSt3aBP\n\thfd7jJsVugQmz31/TMfgmWJmCT9c7A//YaaCxmX3ohTEaIfID6SoNBTv2sPsXycDZBqETnY6S8S\n\taPLKznrOhGn3EIEeHDnp7Z2VfLsyJVVNbg0DDSvwR0SE9o9Yxw=","X-Google-Smtp-Source":"AGHT+IHDKUo6WZKY1kG4uhEOunnB+6JhzAbXQikEMxXxz7kUm4yM25YB+o1511+ccKHITqCWcFTF8A==","X-Received":"by 2002:a05:6214:509d:b0:6d3:fa8f:ae5a with SMTP id\n\t6a1803df08f44-6d4377bd8d2mr49641646d6.14.1732131804014; \n\tWed, 20 Nov 2024 11:43:24 -0800 (PST)","Message-ID":"<c73176525872aaeb965bc4b35febb280a3e2a1ca.camel@ndufresne.ca>","Subject":"Re: [PATCH v4] 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":"Wed, 20 Nov 2024 14:43:22 -0500","In-Reply-To":"<20241120100414.219130-1-qi.hou@nxp.com>","References":"<20241120100414.219130-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":32340,"web_url":"https://patchwork.libcamera.org/comment/32340/","msgid":"<PAXPR04MB82856DC361E86977D018699597232@PAXPR04MB8285.eurprd04.prod.outlook.com>","date":"2024-11-22T04:25:38","subject":"RE: [EXT] Re: [PATCH v4] 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\nI have sent out v5 according to your comment. Please help review. Thanks very much.\n\nRegards,\nQi Hou\n\n-----Original Message-----\nFrom: Nicolas Dufresne <nicolas@ndufresne.ca> \nSent: 2024年11月21日 3:43\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 v4] 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 mercredi 20 novembre 2024 à 19:04 +0900, Hou Qi a écrit :\n> GStreamer video-info calculated stride and offset may differ from \n> those used by the camera.\n>\n> This patch enhances downstream plugin's support for videometa by \n> adding videometa support for libcamerasrc. It ensures that when \n> downstream plugin supports videometa by allocation query, libcamerasrc \n> also attaches videometa to buffer, preventing discrepancies between \n> the stride and offset calculated by video-info and camera.\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   | 57 +++++++++++++++++++++++++++-\n>  src/gstreamer/gstlibcamerapool.h     |  5 ++-\n>  src/gstreamer/gstlibcamerasrc.cpp    | 47 +++++++++++++++++++++--\n>  5 files changed, 141 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..bd1cc473 100644\n> --- a/src/gstreamer/gstlibcamerapool.cpp\n> +++ b/src/gstreamer/gstlibcamerapool.cpp\n> @@ -29,6 +29,8 @@ struct _GstLibcameraPool {\n>       std::deque<GstBuffer *> *queue;\n>       GstLibcameraAllocator *allocator;\n>       Stream *stream;\n> +     gboolean frame_copy;\n> +     GstVideoInfo info;\n>  };\n>\n>  G_DEFINE_TYPE(GstLibcameraPool, gst_libcamera_pool, \n> GST_TYPE_BUFFER_POOL) @@ -135,16 +137,29 @@ \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 stride_mismatch, \n> +gboolean has_video_meta)\n>  {\n>       auto *pool = \n> GST_LIBCAMERA_POOL(g_object_new(GST_TYPE_LIBCAMERA_POOL, nullptr));\n>\n>       pool->allocator = GST_LIBCAMERA_ALLOCATOR(g_object_ref(allocator));\n>       pool->stream = stream;\n> +     pool->info = *info;\n> +\n> +     if (stride_mismatch && !has_video_meta)\n> +             pool->frame_copy = true;\n> +     else\n> +             pool->frame_copy = false;\n>\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 (stride_mismatch && has_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, info->stride);\n> +             }\n>               pool->queue->push_back(buffer);\n>       }\n>\n> @@ -163,3 +178,43 @@ gst_libcamera_buffer_get_frame_buffer(GstBuffer *buffer)\n>       GstMemory *mem = gst_buffer_peek_memory(buffer, 0);\n>       return gst_libcamera_memory_get_frame_buffer(mem);\n>  }\n> +\n> +GstBuffer *\n> +gst_libcamera_copy_buffer(GstLibcameraPool *self, GstBuffer *src, \n> +FrameBuffer *fb, guint32 stride)\n\nA function that says \"copy_buffer\" should always copy the buffers. The naming should be reconsidered, that being said see comment below.\n\n> +{\n> +     if (self->frame_copy) {\n> +             GstVideoInfo src_info = self->info;\n> +             gsize size = GST_VIDEO_INFO_SIZE(&self->info);\n> +             GstBuffer *dest = gst_buffer_new_allocate(NULL, size, \n> +NULL);\n\nThe copy condition should be moved into the libcamerasrc element code. This way, you can run an allocation query, and keep copy into downstream provided buffer instead of doing dynamic allocation. You can also use a generic GstVideoPool to avoid dynamic allocations even if downstream did not provide a pool.\n\n> +             GstVideoFrame src_frame, dest_frame;\n> +             int i = 0;\n> +\n> +             for (const FrameBuffer::Plane &plane : fb->planes()) {\n> +                     src_info.stride[i] = stride;\n> +                     src_info.offset[i] = plane.offset;\n> +                     i++;\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> +                     GST_WARNING(\"fail to map src_frame\");\n> +                     return src;\n> +             }\n> +\n> +             if (!gst_video_frame_map(&dest_frame, &self->info, dest, GST_MAP_WRITE)) {\n> +                     gst_video_frame_unmap(&src_frame);\n> +                     GST_WARNING(\"fail to map dest_frame\");\n> +                     return src;\n> +             }\n\nA map failure would indicate serious issues, we should not ignore these and continue with the original.\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> +             gst_buffer_unref(src);\n> +             return dest;\n> +     }\n> +\n> +     return src;\n> +}\n> diff --git a/src/gstreamer/gstlibcamerapool.h \n> b/src/gstreamer/gstlibcamerapool.h\n> index 2a7a9c77..ffbd3173 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,8 +22,10 @@\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 stride_mismatch, gboolean \n> + has_video_meta);\n>\n>  libcamera::Stream *gst_libcamera_pool_get_stream(GstLibcameraPool \n> *self);\n>\n>  libcamera::FrameBuffer \n> *gst_libcamera_buffer_get_frame_buffer(GstBuffer *buffer);\n> +\n> +GstBuffer *gst_libcamera_copy_buffer(GstLibcameraPool *self, \n> +GstBuffer *buffer, libcamera::FrameBuffer *fb, guint32 stride);\n> diff --git a/src/gstreamer/gstlibcamerasrc.cpp \n> b/src/gstreamer/gstlibcamerasrc.cpp\n> index 8efa25f4..1d46536a 100644\n> --- a/src/gstreamer/gstlibcamerasrc.cpp\n> +++ b/src/gstreamer/gstlibcamerasrc.cpp\n> @@ -292,12 +292,17 @@ 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> +             const StreamConfiguration &stream_cfg = src_->state->config_->at(i);\n> +             GstLibcameraPool *pool = \n> + gst_libcamera_pad_get_pool(srcpad);\n>\n>               FrameBuffer *fb = \n> gst_libcamera_buffer_get_frame_buffer(buffer);\n>\n> +             buffer = gst_libcamera_copy_buffer(pool, buffer, fb, \n> + stream_cfg.stride);\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 +502,45 @@ 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 stride_mismatch = false, has_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> +             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 = false;\n> +                     stride_mismatch = 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> +                             has_video_meta = gst_query_find_allocation_meta(query, GST_VIDEO_META_API_TYPE, NULL);\n> +                     gst_query_unref(query);\n> +\n> +                     if (has_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, GST_VIDEO_INFO_HEIGHT(&info));\n> +                             }\n> +                     }\n> +             }\n>\n> -             GstLibcameraPool *pool = gst_libcamera_pool_new(self->allocator,\n> -                                                             stream_cfg.stream());\n> +             GstLibcameraPool *pool = gst_libcamera_pool_new(self->allocator, stream_cfg.stream(),\n> +                                                             &info, \n> + stride_mismatch, has_video_meta);\n>               g_signal_connect_swapped(pool, \"buffer-notify\",\n>                                        G_CALLBACK(gst_task_resume), \n> self->task);\n>","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 E2BF8C326C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 22 Nov 2024 04:25:41 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 8534A65FD3;\n\tFri, 22 Nov 2024 05:25:41 +0100 (CET)","from EUR05-AM6-obe.outbound.protection.outlook.com\n\t(mail-am6eur05on20607.outbound.protection.outlook.com\n\t[IPv6:2a01:111:f403:2612::607])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id B1BD865FD2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 22 Nov 2024 05:25:40 +0100 (CET)","from PAXPR04MB8285.eurprd04.prod.outlook.com\n\t(2603:10a6:102:1ca::15)\n\tby DU2PR04MB8726.eurprd04.prod.outlook.com (2603:10a6:10:2dd::9) with\n\tMicrosoft SMTP Server (version=TLS1_2,\n\tcipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.8158.23;\n\tFri, 22 Nov 2024 04:25:39 +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%5]) with mapi id 15.20.8158.024;\n\tFri, 22 Nov 2024 04:25:39 +0000"],"Authentication-Results":["lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=nxp.com header.i=@nxp.com header.b=\"EOOQmDpQ\";\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=xou07NdlCp5iRqYTiYBcaXKdq86QWO1xMP4/uT0jmQekxf2Y0VWfyJt1U0H/T2EvO5dTJaeLfpLgQVX0FCwptWEjTqNSkpfdqMbOD4wEW6th/rgQphoMtpnfzNQ6VwFdKeQy8gfZF36PCjnxj8kIBGK60QyWn2dusZTgRcWS3ZycrarJHpxIWd/LqwUShprjwqjJXwRn1Kn7cHsQUxdYHfXMWJfiCsdgscUK4m1ZnzY3nNb/SIiaoUoY1LMuRaYYmQ9DIikBMDrtWYLnwsHLite0+GpvmAHGNu0vAnH97tWM5F1pK7QTyYTRif4WIbqh/SzPqwqQXaYFaHBYt/gdYQ==","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=EHRfhgLmC8HgU5PKLzzCMNB8HsPskgTxTKn8g9F/g7Q=;\n\tb=apVAv7r6Rp0nZnn2sj7vZ1LUE6YhyAmF3u1nGfk2kW3CsqQd67Sz5jIxBr2FaniJlS0nbSh7RyyVuf3dAw3DtU9/lI4mMr5cYu9ECcL+XcLlcS7SEMoLT+eIyR8M5MXX0aUAjmxI+ZnkNVex0eNYQABxFphpvD0HezOxb8XkeR/dnNBI94vufNkhhQf4fLYFnZLCmp4jvZvQf2YTCqh9R9jnkYO9aPKvOKuop0COofUpwelVf5Tz2yQioAC3OuYnzUOblJTtE6NZdaTWrngfl4WSx3eDBpFKsiN6hcQsJF59qe5hRsyL5/X+Y9NEi6MHvE2aFMpGjNE9+fc1SUfSIg==","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=EHRfhgLmC8HgU5PKLzzCMNB8HsPskgTxTKn8g9F/g7Q=;\n\tb=EOOQmDpQ3IPpZdqJ61u3KQsYx0xLBZK/vJwwq7tDuNz8DOnQ834+E50JMGI5BObTwXXDP/rK/gaxy28rj/C35kKqbAvZO+WR93jzEE3nklXLCKL5xzHwSFDRY8hgjTctN24StfD1HBnG9BWe7hYyfJjASbm1DbxSdJexO9YNKlwTQncZ9TNDGdqLdmw75Fgpf298grGMoD+bX49Yc4CKedE//6iyMhbiJjnV5mAorpwoPWfnuXxrA58RT0Xdqtrto/M2YucKRTYjGbHcCHg2URLz4q6jEQP58SmlnpxwOor5Zl+H8wBx2p5ex2sQSoNRp+iJQXsEhemQ0y9TjTjsIg==","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 v4] gstreamer: Add GstVideoMeta support","Thread-Topic":"[EXT] Re: [PATCH v4] gstreamer: Add GstVideoMeta support","Thread-Index":"AQHbOzOadqx5U3ZntkOg17+Ch26/kLLAkoIAgAIj6eA=","Date":"Fri, 22 Nov 2024 04:25:38 +0000","Message-ID":"<PAXPR04MB82856DC361E86977D018699597232@PAXPR04MB8285.eurprd04.prod.outlook.com>","References":"<20241120100414.219130-1-qi.hou@nxp.com>\n\t<c73176525872aaeb965bc4b35febb280a3e2a1ca.camel@ndufresne.ca>","In-Reply-To":"<c73176525872aaeb965bc4b35febb280a3e2a1ca.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=\"EOOQmDpQ\";\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_|DU2PR04MB8726:EE_","x-ms-office365-filtering-correlation-id":"36bb37d1-6def-439e-468e-08dd0aadb812","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?9MgfI+wGz2jRjMM8W4c2j4pnyV3i?=\n\t=?utf-8?q?P+kIQcs+xjBgSxgE4B/wi9vln15xMZ0HAve/IvyEwRHYBLOGg39CSErz?=\n\t=?utf-8?q?5ppP+dnbV7T5lrDb7DVKVrUojrexCXYdCYVbjSDUVJuIuUnf2tiKZWGS?=\n\t=?utf-8?q?Mfym421v2yt4jzinRDB/vpxKZt8C0zNrV2meb0kPOMpfb0ohyo8wpXWv?=\n\t=?utf-8?q?8lesmBhduoL9RieVzs8/qowGncWjCr1R/OwA7A96T6501W5p5/0z9kiw?=\n\t=?utf-8?q?o5Iejw3hY7B26SlXD9NXE22DVtU7drPoPErdCN+Mv5FQfLKOwBfMNzev?=\n\t=?utf-8?q?WGLO3Z4Min3j26qu9m4132SPfYjQGOh3AHbE2zPOOku0cxXHxo2w9+kG?=\n\t=?utf-8?q?mMyYAzDaRksaiB51K1+opgeGewPc6HFxZHyX9/7MTzVFYpPnYtiyxhGd?=\n\t=?utf-8?q?ZJ0f4FpUhhm22BxwcLJ8nnvaHm5K3uaHglxrFpgRu3nAKubwHV8Qt+8W?=\n\t=?utf-8?q?Ezbd0XCo/uHCGbHebOeCSW/afX43U+/iKKobhVO32DDdUnqXEvUbH/+n?=\n\t=?utf-8?q?0KIc0MjDXzcFNDgV+VJt9y0OsIqnoGafkvWcilDPXyQQRWGP/IdDzh6k?=\n\t=?utf-8?q?HetE1qwbU5QMBweImcqaz1jPXLyBfmUiuZ56VbYux4nT/QZcTPlhuayI?=\n\t=?utf-8?q?1cSb2447ZLE2fabT2RjvUWlAzDTNFRjkpoSSKCnyyRgR88WvkbQp52K9?=\n\t=?utf-8?q?sktQ1qyF1gGQmEsiS8KRWRSk0v1G7TvT/OB2b4YBXy9BRCPSxxYmMrke?=\n\t=?utf-8?q?LLEKZyqvvPstgv2vq1nHNJTJTkoe6DbTXsSxbHwyMDZ4V+2pVyBLXmff?=\n\t=?utf-8?q?LsC/h4r6w9iy9RSM1q56Hm0wrCXuL36fXr5DVs0p0TfnlEOCqvwUay9n?=\n\t=?utf-8?q?AmQkf/CeWGouf1y4voq1bBKfPYb/XlwZY16sYW7NcDy5gNRZ24a8F9xB?=\n\t=?utf-8?q?/YVFJ9UCIMU2mb2GnbhkuDlMD7NNCe+1d5/PRyXdVutu9VGe8QCjufoz?=\n\t=?utf-8?q?mFI5q6FTYdtvlCPzR8X8JBnxK7QaZpEFf0nOlUkYfIFudgh5wg8llO8h?=\n\t=?utf-8?q?XVhuxxYPCzyGdaZ70T9wTLnpmNDeQjqh7jGYMFK4jV7IFJ8qEHbXeMBk?=\n\t=?utf-8?q?FGKH1DndOFpng2kqm4AJ8em7tmxyQCw1WY+S2DCA64MoCXNPSN8yMdfq?=\n\t=?utf-8?q?PO+nll5377h0pVlbu+XmXzQ/js6sR6+ed0E+ukOyk7ov5oWi2CWi/XBC?=\n\t=?utf-8?q?X6KoJ0h8KCQHSrQnS7Zu0qD7NdMlFK4vQZZd/DrraxB4EWyAmkpOnwQI?=\n\t=?utf-8?q?kHP54cjPvEMQiGl7y6vQVzEoj5Gob8ecgbJUUY8CDSGKOQ3UKHJCPsek?=\n\t=?utf-8?q?tUFWn4zoi0Pk1QBLqKPSEsGSCPfeGTlnq70Hbqn4IRhndgvkXZzSfYYK?=\n\t=?utf-8?q?B/n8oSRQKmXc/ZU=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?jh01R+M3e3FeocDxqLkMKSFEr?=\n\t=?utf-8?q?BWPZTzNYrF3bfdFrT4hfHX7GUbkm9qz6gcCKLcRnBXpVc+PGNr9vRVaL?=\n\t=?utf-8?q?NnigGLiIjxl1h4sRs2kmwzN+escQIcU8Igjjak1fKrerpyJc5pYvX2g8?=\n\t=?utf-8?q?pRDtFsIR2zbxdlwMUpBjw5XawKZhkaktzYSskMcN4Iw0OiLKwaBVdZt0?=\n\t=?utf-8?q?jxyjF5DOQK9J9tLBFG9GDWEJvopCi31LrcKPh5X0mE+l4KJUh3RdNijq?=\n\t=?utf-8?q?LV3DrdRAyJouFtmf1C1YqUMz4q6ADt/5puDstjl0RO4ZMSxgTpsJrROC?=\n\t=?utf-8?q?TGDF+iDzuZva67zU198X48181emjWhPEI0CcvO9iF4kCgkgkZl6AP8OB?=\n\t=?utf-8?q?qNOag4I7KmYpYax6OygvI+AYQRdvpPdCuYVOGHXIvtuomPlHRdrPAhUD?=\n\t=?utf-8?q?HvyWbm/s4WuXr7iLqf9Xltvlww8dQN4A6hjOraBul5n9wWM8vsh6vk6y?=\n\t=?utf-8?q?0umK0ZwADZePZ/yjKeHahyY+D/H79ESYJ4481RKdbIulDzmJjoSmXvSW?=\n\t=?utf-8?q?XLGDrlURq8ArGbcd9uDZSVkbK5qZBkzslIibVEs2lK9sQzHP3FvGV+ic?=\n\t=?utf-8?q?rlT4HTxCoLL5YgIxFfQiB9bFQi5F1O6Vufee9UPYWP2gzQj8kDrvvun7?=\n\t=?utf-8?q?5AA0x2RasF82WbGHhRH8Ca1i5/qqxEZDM9T212rmHGjmeG89anz3TY0X?=\n\t=?utf-8?q?6xrL45EZKOStPRQbaaj8K/PULCFOQBn0zm6Lk0HtAlr/jxQ4fb9NggMl?=\n\t=?utf-8?q?IB4/riwf5TZx49t5F+JVBqBugQ7zfhsz3/ZhB3IE8tbJnVwAw7DP+aRZ?=\n\t=?utf-8?q?WXBn2DVepRWAttRaNpsDtPDKeOSHcu/fIYfE17j5Tlr8jZYJzw1Io3YM?=\n\t=?utf-8?q?cnO4uLNWqFlg0zQQ5SerBZte/d657nVSWYwehpdhbHEfdIL6Gt8toEnI?=\n\t=?utf-8?q?H9vxxJFevDQXrJ2qKbApdmpA9iAyARIAUyeRzToRCrVqjbU3Rz6VN9FP?=\n\t=?utf-8?q?+jfIC0D13MQ9gxfv5mZpx4/42BOcVYrRr5vHDMNrbvFT7OhrKujMbZ3r?=\n\t=?utf-8?q?T8nBWaVilXei4Hv3kjQueDmrCTvFevVqSg/HmDBRhRxfoITXCq2vkYdO?=\n\t=?utf-8?q?HqoNFqcFMsGhHb8sdE8yETdWAdh3MM+D76r3qD7EjnGlBm1jy3THSx8K?=\n\t=?utf-8?q?kBVXpmu0T2Bif5lARAg+TW5c2MZy/ciCNqrIvkYX57/lIOeCSXONwLj6?=\n\t=?utf-8?q?W5SluLtfScU91eopfQwqc2MMkk4OKTGA5RqsmPfLzjx1NFbamfAxYQ89?=\n\t=?utf-8?q?GChBZWC2OcWD5vL3I4llJvsysrXrEKq1YJ1NBImFMDrzDF/0unKFOkWp?=\n\t=?utf-8?q?aHY/q2FvNGNfLwR6MMvFfAu39WhukMzHmdNtt3RdeaqNABvqNscZWT/a?=\n\t=?utf-8?q?FKCTndeoKbM7IjYwvfpCFnFWStlvzZZQWQy7tUFqyqB/m5z08Q8a/OQY?=\n\t=?utf-8?q?O9Y4BNhPk0pUq9unPfZ4UMP9gdKVQ9ER73k4Y0V78R+pcPtXuHeTjy/g?=\n\t=?utf-8?q?HVAnhC2q5uF5Rmr4CXx00l2MZx7IfhKbDCkDahLK7QGtIuMFBriqx/dF?=\n\t=?utf-8?q?aLiIX8FFYK4eo6ZbLcH3tIrCMxFVbQKL9fUVH5nd0k=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":"36bb37d1-6def-439e-468e-08dd0aadb812","X-MS-Exchange-CrossTenant-originalarrivaltime":"22 Nov 2024 04:25:39.0258\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":"e5BoYB+qUSz7OF4qm3Yg2FDNraITUhLQYYST6XlFWD26mbdT7mHzUskrq4MzkVob","X-MS-Exchange-Transport-CrossTenantHeadersStamped":"DU2PR04MB8726","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>"}}]