[{"id":35124,"web_url":"https://patchwork.libcamera.org/comment/35124/","msgid":"<dbe3fa3ff4397fb4490cd0e57f57291b09c7c658.camel@ideasonboard.com>","date":"2025-07-25T10:50:03","subject":"Re: [PATCH 7/9] libcamera: media-device: Introduce MediaContext","submitter":{"id":215,"url":"https://patchwork.libcamera.org/api/people/215/","name":"Isaac Scott","email":"isaac.scott@ideasonboard.com"},"content":"Hi Jacopo,\n\nThank you for the patch,\n\nOn Fri, 2025-07-25 at 12:33 +0200, Jacopo Mondi wrote:\n> Introduce the MediaContext class that represents a media device\n> context to which video devices and subdevices can be bound to.\n> \n> A MediaContext can be created from a MediaDevice (which does not\n> support multi-context) and from SharedMediaDevice (which supports\n> multi-context).\n> \n> Binding devices to a MediaContext created from MediaDevice is a nop,\n> while MediaContext created from SharedMediaDevice perform proper\n> bindings.\n> \n> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> ---\n>  include/libcamera/internal/media_device.h |  23 ++++-\n>  src/libcamera/media_device.cpp            | 135\n> ++++++++++++++++++++++++++++++\n>  2 files changed, 157 insertions(+), 1 deletion(-)\n> \n> diff --git a/include/libcamera/internal/media_device.h\n> b/include/libcamera/internal/media_device.h\n> index\n> ea0e9d66009933bbb9613d27423d2f951520c887..2dbfcc447d84bb6a1eee4ed598d\n> 22b5cc45adbe9 100644\n> --- a/include/libcamera/internal/media_device.h\n> +++ b/include/libcamera/internal/media_device.h\n> @@ -21,6 +21,9 @@\n>  \n>  namespace libcamera {\n>  \n> +class V4L2VideoDevice;\n> +class V4L2Subdevice;\n> +\n>  class MediaDeviceFactory\n>  {\n>  public:\n> @@ -28,6 +31,19 @@ public:\n>  \tcreateMediaDevice(const std::string &deviceNode);\n>  };\n>  \n> +class MediaContext\n> +{\n> +public:\n> +\tMediaContext();\n> +\tMediaContext(UniqueFD &&fd);\n> +\n> +\tint bindDevice(V4L2VideoDevice *dev);\n> +\tint bindDevice(V4L2Subdevice *dev);\n> +\n> +private:\n> +\tUniqueFD fd_;\n> +};\n> +\n>  class MediaDevice : protected Loggable\n>  {\n>  public:\n> @@ -63,10 +79,14 @@ public:\n>  \n>  \tstd::vector<MediaEntity *> locateEntities(unsigned int\n> function);\n>  \n> +\tvirtual std::unique_ptr<MediaContext> createContext();\n> +\n>  protected:\n>  \tMediaDevice(const std::string &deviceNode);\n>  \tstd::string logPrefix() const override;\n>  \n> +\tstd::string deviceNode_;\n> +\n>  private:\n>  \tfriend class MediaDeviceFactory;\n>  \n> @@ -88,7 +108,6 @@ private:\n>  \tint setupLink(const MediaLink *link, unsigned int flags);\n>  \n>  \tstd::string driver_;\n> -\tstd::string deviceNode_;\n>  \tstd::string model_;\n>  \tunsigned int version_;\n>  \tunsigned int hwRevision_;\n> @@ -111,6 +130,8 @@ public:\n>  \tbool lock() override;\n>  \tvoid unlock() override;\n>  \n> +\tstd::unique_ptr<MediaContext> createContext() override;\n> +\n>  private:\n>  \tfriend class MediaDeviceFactory;\n>  \n> diff --git a/src/libcamera/media_device.cpp\n> b/src/libcamera/media_device.cpp\n> index\n> 70d073f964bde0236585de083baca4cd9c30d193..2b7df346ad9916cf22641ea770e\n> 0a1507921d89d 100644\n> --- a/src/libcamera/media_device.cpp\n> +++ b/src/libcamera/media_device.cpp\n> @@ -21,6 +21,9 @@\n>  \n>  #include <libcamera/base/log.h>\n>  \n> +#include \"libcamera/internal/v4l2_subdevice.h\"\n> +#include \"libcamera/internal/v4l2_videodevice.h\"\n> +\n>  /**\n>   * \\file media_device.h\n>   * \\brief Provide a representation of a Linux kernel Media\n> Controller device\n> @@ -81,6 +84,99 @@ MediaDeviceFactory::createMediaDevice(const\n> std::string &deviceNode)\n>  \treturn std::unique_ptr<MediaDevice>(new\n> MediaDevice(deviceNode));\n>  }\n>  \n> +/**\n> + * \\class MediaContext\n> + * \\brief Represents a media device context\n> + *\n> + * A media device context is obtained from a MediaDevice or a\n> SharedMediaDevice\n> + * instance with the MediaDevice::createContext() and\n> + * SharedMediaDevice::createContext() functions.\n> + *\n> + * MediaContext instances obtained from a MediaDevice that does not\n> support\n> + * multi-context operations will not have a valid file descriptor\n> associated as\n> + * the media device they are created from doesn't support multiple\n> open. For\n> + * this reason binding a device to a context that is obtained from a\n> MediaDevice\n> + * is a nop.\n> + *\n> + * MediaContext instances obtained from a SharedMediaDevice that\n> supports\n> + * multi-context operations owns the file descriptor that identifies\n> the media\n> + * context, obtained by opening the media device multiple times and\n> provided to\n> + * the context at creation time. Binding devices to a context\n> obtained from a\n> + * SharedMediaDevice associates the devices to an execution context\n> which\n> + * remains valid until the last bound device stays valid.\n> + *\n> + * Users of this class can bind devices on valid and invalid\n> MultiContext\n> + * instances alike. If the kernel driver supports multi-context\n> operations\n> + * then the device can be opened multiple times and multiple\n> contexts can be\n> + * created to which devices can be bound to. If the kernel driver\n> doesn't\n> + * support multi-context operations then it can be opened a single\n> time only\n> + * and a creating contexts and bindings devices to it is effectively\n> a nop.\n> + */\n> +\n> +/**\n> + * \\brief Constructs a MediaContext not associated to any media\n> device context\n> + *\n> + * Constructs a media device context not associated to any valid\n> media device\n> + * context file descriptor. Binding devices to such media device\n> context\n> + * is effectively a nop.\n> + */\n> +MediaContext::MediaContext()\n> +{\n> +}\n> +\n> +/**\n> + * \\brief Construct a MediaContext associated to a valid media\n> device context\n> + * \\param[in] fd The file descriptor that identifies a media device\n> context\n> + *\n> + * Construct a media device context to which devices can be bound\n> to.\n> + *\n> + * Constructing a MediaContext with an associated valid media device\n> context\n> + * allows to create isolated execution contexts by binding devices\n> to it.\n> + *\n> + * The file descriptor \\a fd obtained by opening the media device\n> and that\n> + * identifies the media device context is moved in the newly\n> constructed\n> + * MediaContext which maintains its ownership and automatically\n> closes it at\n> + * destruction time.\n> + */\n> +MediaContext::MediaContext(UniqueFD &&fd)\n> +\t: fd_(std::move(fd))\n> +{\n> +}\n> +\n> +/**\n> + * \\brief Bind a V4L2 video device to a media device context\n> + * \\param[in] dev The video device to bind\n> + *\n> + * If the MediaContext has been created by a MediaDevice that does\n> not\n> + * support multi-context operations, this function is effectively a\n> nop.\n> + *\n> + * \\return 0 on success or a negative error code otherwise\n> + */\n> +int MediaContext::bindDevice(V4L2VideoDevice *dev)\n> +{\n> +\tif (fd_.get() == -1)\n> +\t\treturn 0;\n> +\n> +\treturn dev->bindContext(fd_.get());\n> +}\n> +\n> +/**\n> + * \\brief Bind a V4L2 subdevice to a media device context\n> + * \\param[in] dev The subdevice to bind\n> + *\n> + * If the MediaContext has been created by a MediaDevice that does\n> not\n> + * support multi-context operations, this function is effectively a\n> nop.\n> + *\n> + * \\return 0 on success or a negative error code otherwise\n> + */\n> +int MediaContext::bindDevice(V4L2Subdevice *dev)\n> +{\n> +\tif (fd_.get() == -1)\n> +\t\treturn 0;\n> +\n> +\treturn dev->bindContext(fd_.get());\n> +}\n> +\n>  /**\n>   * \\class MediaDevice\n>   * \\brief The MediaDevice represents a Media Controller device with\n> its full\n> @@ -603,6 +699,45 @@ int MediaDevice::disableLinks()\n>   * driver unloading for most devices. The media device is passed as\n> a parameter.\n>   */\n>  \n> +/**\n> + * \\brief Create a MediaContext not associated with a media device\n> context\n> + * \\return A MediaContext to which bindings devices is a nop\n> + */\n> +std::unique_ptr<MediaContext> MediaDevice::createContext()\n> +{\n> +\treturn std::make_unique<MediaContext>();\n> +}\n> +\n> +/**\n> + * \\brief Create a MediaContext associated with a media device\n> context\n> + *\n> + * Create a MediaContext by opening the media device and create a\n> media\n> + * device context using the obtained file descriptor.\n> + *\n> + * The open file descriptor is moved to the MediaContext whose\n> ownership is\n> + * moved to the caller of this function.\n> + *\n> + * \\return A MediaContext to which is possible to bind devices to\n> + */\n\ns/to which is possible to bind devices to/which devices can be bound\nto/\n\nOtherwise,\n\nReviewed-by: Isaac Scott <isaac.scott@ideasonboard.com>\n\n> +std::unique_ptr<MediaContext> SharedMediaDevice::createContext()\n> +{\n> +\tauto fd = UniqueFD(::open(deviceNode_.c_str(), O_RDWR |\n> O_CLOEXEC));\n> +\tif (!fd.isValid()) {\n> +\t\tint ret = -errno;\n> +\t\tLOG(MediaDevice, Error)\n> +\t\t\t<< \"Failed to open media device at \"\n> +\t\t\t<< deviceNode_ << \": \" << strerror(-ret);\n> +\t\treturn {};\n> +\t}\n> +\n> +\treturn std::make_unique<MediaContext>(std::move(fd));\n> +}\n> +\n> +/**\n> + * \\var MediaDevice::deviceNode_\n> + * \\brief The media device device node path\n> + */\n> +\n>  /**\n>   * \\brief Open the media device\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 5A960C3237\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 25 Jul 2025 10:50:09 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 0F194690B3;\n\tFri, 25 Jul 2025 12:50:08 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id DB5CA690A6\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 25 Jul 2025 12:50:06 +0200 (CEST)","from isaac-ThinkPad-T16-Gen-2.lan\n\t(cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id F2B0EC0B;\n\tFri, 25 Jul 2025 12:49:26 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"iQT57udy\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1753440567;\n\tbh=hROr5mXLw3GyYl10OkGV68CCL/0vuzVHIvw+A2Kwhag=;\n\th=Subject:From:To:Date:In-Reply-To:References:From;\n\tb=iQT57udyh1w172JGufVYYN2AOInHnE5uIGNYqdEaHobH5Jmqr+wgM7pjc7LD3JLzx\n\tNV4LZdEDFWP3oPY3CGDNFI+V6I5URXR5esc1pv31czdvfWrPVqZNTDOGPEC+q5njRo\n\ti1mBXJmdTbt0l2JLcuYaE1ZrG1MhXEVMvj+bchlc=","Message-ID":"<dbe3fa3ff4397fb4490cd0e57f57291b09c7c658.camel@ideasonboard.com>","Subject":"Re: [PATCH 7/9] libcamera: media-device: Introduce MediaContext","From":"Isaac Scott <isaac.scott@ideasonboard.com>","To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>, \n\tlibcamera-devel@lists.libcamera.org","Date":"Fri, 25 Jul 2025 11:50:03 +0100","In-Reply-To":"<20250725-multicontext-v1-7-ea558291e101@ideasonboard.com>","References":"<20250725-multicontext-v1-0-ea558291e101@ideasonboard.com>\n\t<20250725-multicontext-v1-7-ea558291e101@ideasonboard.com>","Content-Type":"text/plain; charset=\"UTF-8\"","Content-Transfer-Encoding":"quoted-printable","User-Agent":"Evolution 3.56.2 (by Flathub.org) ","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":35214,"web_url":"https://patchwork.libcamera.org/comment/35214/","msgid":"<306da0bc-f316-4cee-b054-994aa8320bd6@ideasonboard.com>","date":"2025-07-28T15:29:39","subject":"Re: [PATCH 7/9] libcamera: media-device: Introduce MediaContext","submitter":{"id":216,"url":"https://patchwork.libcamera.org/api/people/216/","name":"Barnabás Pőcze","email":"barnabas.pocze@ideasonboard.com"},"content":"Hi\n\n2025. 07. 25. 12:33 keltezéssel, Jacopo Mondi írta:\n> Introduce the MediaContext class that represents a media device\n> context to which video devices and subdevices can be bound to.\n> \n> A MediaContext can be created from a MediaDevice (which does not\n> support multi-context) and from SharedMediaDevice (which supports\n> multi-context).\n> \n> Binding devices to a MediaContext created from MediaDevice is a nop,\n> while MediaContext created from SharedMediaDevice perform proper\n> bindings.\n> \n> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> ---\n>   include/libcamera/internal/media_device.h |  23 ++++-\n>   src/libcamera/media_device.cpp            | 135 ++++++++++++++++++++++++++++++\n>   2 files changed, 157 insertions(+), 1 deletion(-)\n> \n> diff --git a/include/libcamera/internal/media_device.h b/include/libcamera/internal/media_device.h\n> index ea0e9d66009933bbb9613d27423d2f951520c887..2dbfcc447d84bb6a1eee4ed598d22b5cc45adbe9 100644\n> --- a/include/libcamera/internal/media_device.h\n> +++ b/include/libcamera/internal/media_device.h\n> @@ -21,6 +21,9 @@\n>   \n>   namespace libcamera {\n>   \n> +class V4L2VideoDevice;\n> +class V4L2Subdevice;\n> +\n>   class MediaDeviceFactory\n>   {\n>   public:\n> @@ -28,6 +31,19 @@ public:\n>   \tcreateMediaDevice(const std::string &deviceNode);\n>   };\n>   \n> +class MediaContext\n> +{\n> +public:\n> +\tMediaContext();\n> +\tMediaContext(UniqueFD &&fd);\n> +\n> +\tint bindDevice(V4L2VideoDevice *dev);\n> +\tint bindDevice(V4L2Subdevice *dev);\n> +\n> +private:\n> +\tUniqueFD fd_;\n> +};\n> +\n>   class MediaDevice : protected Loggable\n>   {\n>   public:\n> @@ -63,10 +79,14 @@ public:\n>   \n>   \tstd::vector<MediaEntity *> locateEntities(unsigned int function);\n>   \n> +\tvirtual std::unique_ptr<MediaContext> createContext();\n> +\n>   protected:\n>   \tMediaDevice(const std::string &deviceNode);\n>   \tstd::string logPrefix() const override;\n>   \n> +\tstd::string deviceNode_;\n> +\n>   private:\n>   \tfriend class MediaDeviceFactory;\n>   \n> @@ -88,7 +108,6 @@ private:\n>   \tint setupLink(const MediaLink *link, unsigned int flags);\n>   \n>   \tstd::string driver_;\n> -\tstd::string deviceNode_;\n>   \tstd::string model_;\n>   \tunsigned int version_;\n>   \tunsigned int hwRevision_;\n> @@ -111,6 +130,8 @@ public:\n>   \tbool lock() override;\n>   \tvoid unlock() override;\n>   \n> +\tstd::unique_ptr<MediaContext> createContext() override;\n> +\n>   private:\n>   \tfriend class MediaDeviceFactory;\n>   \n> diff --git a/src/libcamera/media_device.cpp b/src/libcamera/media_device.cpp\n> index 70d073f964bde0236585de083baca4cd9c30d193..2b7df346ad9916cf22641ea770e0a1507921d89d 100644\n> --- a/src/libcamera/media_device.cpp\n> +++ b/src/libcamera/media_device.cpp\n> @@ -21,6 +21,9 @@\n>   \n>   #include <libcamera/base/log.h>\n>   \n> +#include \"libcamera/internal/v4l2_subdevice.h\"\n> +#include \"libcamera/internal/v4l2_videodevice.h\"\n> +\n>   /**\n>    * \\file media_device.h\n>    * \\brief Provide a representation of a Linux kernel Media Controller device\n> @@ -81,6 +84,99 @@ MediaDeviceFactory::createMediaDevice(const std::string &deviceNode)\n>   \treturn std::unique_ptr<MediaDevice>(new MediaDevice(deviceNode));\n>   }\n>   \n> +/**\n> + * \\class MediaContext\n> + * \\brief Represents a media device context\n> + *\n> + * A media device context is obtained from a MediaDevice or a SharedMediaDevice\n> + * instance with the MediaDevice::createContext() and\n> + * SharedMediaDevice::createContext() functions.\n> + *\n> + * MediaContext instances obtained from a MediaDevice that does not support\n> + * multi-context operations will not have a valid file descriptor associated as\n> + * the media device they are created from doesn't support multiple open. For\n> + * this reason binding a device to a context that is obtained from a MediaDevice\n> + * is a nop.\n> + *\n> + * MediaContext instances obtained from a SharedMediaDevice that supports\n> + * multi-context operations owns the file descriptor that identifies the media\n> + * context, obtained by opening the media device multiple times and provided to\n> + * the context at creation time. Binding devices to a context obtained from a\n> + * SharedMediaDevice associates the devices to an execution context which\n> + * remains valid until the last bound device stays valid.\n> + *\n> + * Users of this class can bind devices on valid and invalid MultiContext\n> + * instances alike. If the kernel driver supports multi-context operations\n> + * then the device can be opened multiple times and multiple contexts can be\n> + * created to which devices can be bound to. If the kernel driver doesn't\n> + * support multi-context operations then it can be opened a single time only\n> + * and a creating contexts and bindings devices to it is effectively a nop.\n> + */\n> +\n> +/**\n> + * \\brief Constructs a MediaContext not associated to any media device context\n> + *\n> + * Constructs a media device context not associated to any valid media device\n> + * context file descriptor. Binding devices to such media device context\n> + * is effectively a nop.\n> + */\n> +MediaContext::MediaContext()\n> +{\n> +}\n> +\n> +/**\n> + * \\brief Construct a MediaContext associated to a valid media device context\n> + * \\param[in] fd The file descriptor that identifies a media device context\n> + *\n> + * Construct a media device context to which devices can be bound to.\n> + *\n> + * Constructing a MediaContext with an associated valid media device context\n> + * allows to create isolated execution contexts by binding devices to it.\n> + *\n> + * The file descriptor \\a fd obtained by opening the media device and that\n> + * identifies the media device context is moved in the newly constructed\n> + * MediaContext which maintains its ownership and automatically closes it at\n> + * destruction time.\n> + */\n> +MediaContext::MediaContext(UniqueFD &&fd)\n> +\t: fd_(std::move(fd))\n> +{\n> +}\n> +\n> +/**\n> + * \\brief Bind a V4L2 video device to a media device context\n> + * \\param[in] dev The video device to bind\n> + *\n> + * If the MediaContext has been created by a MediaDevice that does not\n> + * support multi-context operations, this function is effectively a nop.\n> + *\n> + * \\return 0 on success or a negative error code otherwise\n> + */\n> +int MediaContext::bindDevice(V4L2VideoDevice *dev)\n> +{\n> +\tif (fd_.get() == -1)\n\n   if (!fd_.isValid())\n\n\n> +\t\treturn 0;\n> +\n> +\treturn dev->bindContext(fd_.get());\n> +}\n> +\n> +/**\n> + * \\brief Bind a V4L2 subdevice to a media device context\n> + * \\param[in] dev The subdevice to bind\n> + *\n> + * If the MediaContext has been created by a MediaDevice that does not\n> + * support multi-context operations, this function is effectively a nop.\n> + *\n> + * \\return 0 on success or a negative error code otherwise\n> + */\n> +int MediaContext::bindDevice(V4L2Subdevice *dev)\n> +{\n> +\tif (fd_.get() == -1)\n> +\t\treturn 0;\n> +\n> +\treturn dev->bindContext(fd_.get());\n> +}\n> +\n>   /**\n>    * \\class MediaDevice\n>    * \\brief The MediaDevice represents a Media Controller device with its full\n> @@ -603,6 +699,45 @@ int MediaDevice::disableLinks()\n>    * driver unloading for most devices. The media device is passed as a parameter.\n>    */\n>   \n> +/**\n> + * \\brief Create a MediaContext not associated with a media device context\n> + * \\return A MediaContext to which bindings devices is a nop\n> + */\n> +std::unique_ptr<MediaContext> MediaDevice::createContext()\n> +{\n> +\treturn std::make_unique<MediaContext>();\n> +}\n> +\n> +/**\n> + * \\brief Create a MediaContext associated with a media device context\n> + *\n> + * Create a MediaContext by opening the media device and create a media\n> + * device context using the obtained file descriptor.\n> + *\n> + * The open file descriptor is moved to the MediaContext whose ownership is\n> + * moved to the caller of this function.\n> + *\n> + * \\return A MediaContext to which is possible to bind devices to\n> + */\n> +std::unique_ptr<MediaContext> SharedMediaDevice::createContext()\n\nI am wondering if a simple std::optional<> would be sufficient here?\nOr do you foresee that `MediaContext` will become an abstract base class\nor something that would necessitate the heap allocation?\n\n\nRegards,\nBarnabás Pőcze\n\n\n> +{\n> +\tauto fd = UniqueFD(::open(deviceNode_.c_str(), O_RDWR | O_CLOEXEC));\n> +\tif (!fd.isValid()) {\n> +\t\tint ret = -errno;\n> +\t\tLOG(MediaDevice, Error)\n> +\t\t\t<< \"Failed to open media device at \"\n> +\t\t\t<< deviceNode_ << \": \" << strerror(-ret);\n> +\t\treturn {};\n> +\t}\n> +\n> +\treturn std::make_unique<MediaContext>(std::move(fd));\n> +}\n> +\n> +/**\n> + * \\var MediaDevice::deviceNode_\n> + * \\brief The media device device node path\n> + */\n> +\n>   /**\n>    * \\brief Open the media device\n>    *\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 536B3C3237\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 28 Jul 2025 15:29:46 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 60A44691B6;\n\tMon, 28 Jul 2025 17:29:45 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 6912369158\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 28 Jul 2025 17:29:44 +0200 (CEST)","from [192.168.33.18] (185.221.140.39.nat.pool.zt.hu\n\t[185.221.140.39])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 2DC2F465;\n\tMon, 28 Jul 2025 17:29:02 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"Ibt9aJls\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1753716542;\n\tbh=IOaFsTExKVVfJ36ZQxOBG/zchwVw03jpXNKuklpojbY=;\n\th=Date:Subject:To:References:From:In-Reply-To:From;\n\tb=Ibt9aJls1GWfo5HF7keABxaNkC02sRTRBMHgZVMMCHmG/AiU1spS6dJCteANtU0RF\n\taIQIvV269v+lWs674+s591t4y3jGqxw7E00yPqf+kbv48dnO3r8WeylOv5bi3ggCZd\n\tBJV9lyJKMuMoKOE5Hd0VHuzIKkoUB8OyxV+6oh9g=","Message-ID":"<306da0bc-f316-4cee-b054-994aa8320bd6@ideasonboard.com>","Date":"Mon, 28 Jul 2025 17:29:39 +0200","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH 7/9] libcamera: media-device: Introduce MediaContext","To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20250725-multicontext-v1-0-ea558291e101@ideasonboard.com>\n\t<20250725-multicontext-v1-7-ea558291e101@ideasonboard.com>","From":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Content-Language":"en-US, hu-HU","In-Reply-To":"<20250725-multicontext-v1-7-ea558291e101@ideasonboard.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"8bit","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":35229,"web_url":"https://patchwork.libcamera.org/comment/35229/","msgid":"<f2c7zdqcmrsene6apgwes3qgozrq3gp6f2q5tg4ayvffy5x644@ao5fuq7766fg>","date":"2025-07-29T10:35:42","subject":"Re: [PATCH 7/9] libcamera: media-device: Introduce MediaContext","submitter":{"id":143,"url":"https://patchwork.libcamera.org/api/people/143/","name":"Jacopo Mondi","email":"jacopo.mondi@ideasonboard.com"},"content":"Hi Barnabás\n\nOn Mon, Jul 28, 2025 at 05:29:39PM +0200, Barnabás Pőcze wrote:\n> Hi\n>\n> 2025. 07. 25. 12:33 keltezéssel, Jacopo Mondi írta:\n> > Introduce the MediaContext class that represents a media device\n> > context to which video devices and subdevices can be bound to.\n> >\n> > A MediaContext can be created from a MediaDevice (which does not\n> > support multi-context) and from SharedMediaDevice (which supports\n> > multi-context).\n> >\n> > Binding devices to a MediaContext created from MediaDevice is a nop,\n> > while MediaContext created from SharedMediaDevice perform proper\n> > bindings.\n> >\n> > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> > ---\n> >   include/libcamera/internal/media_device.h |  23 ++++-\n> >   src/libcamera/media_device.cpp            | 135 ++++++++++++++++++++++++++++++\n> >   2 files changed, 157 insertions(+), 1 deletion(-)\n> >\n> > diff --git a/include/libcamera/internal/media_device.h b/include/libcamera/internal/media_device.h\n> > index ea0e9d66009933bbb9613d27423d2f951520c887..2dbfcc447d84bb6a1eee4ed598d22b5cc45adbe9 100644\n> > --- a/include/libcamera/internal/media_device.h\n> > +++ b/include/libcamera/internal/media_device.h\n> > @@ -21,6 +21,9 @@\n> >   namespace libcamera {\n> > +class V4L2VideoDevice;\n> > +class V4L2Subdevice;\n> > +\n> >   class MediaDeviceFactory\n> >   {\n> >   public:\n> > @@ -28,6 +31,19 @@ public:\n> >   \tcreateMediaDevice(const std::string &deviceNode);\n> >   };\n> > +class MediaContext\n> > +{\n> > +public:\n> > +\tMediaContext();\n> > +\tMediaContext(UniqueFD &&fd);\n> > +\n> > +\tint bindDevice(V4L2VideoDevice *dev);\n> > +\tint bindDevice(V4L2Subdevice *dev);\n> > +\n> > +private:\n> > +\tUniqueFD fd_;\n> > +};\n> > +\n> >   class MediaDevice : protected Loggable\n> >   {\n> >   public:\n> > @@ -63,10 +79,14 @@ public:\n> >   \tstd::vector<MediaEntity *> locateEntities(unsigned int function);\n> > +\tvirtual std::unique_ptr<MediaContext> createContext();\n> > +\n> >   protected:\n> >   \tMediaDevice(const std::string &deviceNode);\n> >   \tstd::string logPrefix() const override;\n> > +\tstd::string deviceNode_;\n> > +\n> >   private:\n> >   \tfriend class MediaDeviceFactory;\n> > @@ -88,7 +108,6 @@ private:\n> >   \tint setupLink(const MediaLink *link, unsigned int flags);\n> >   \tstd::string driver_;\n> > -\tstd::string deviceNode_;\n> >   \tstd::string model_;\n> >   \tunsigned int version_;\n> >   \tunsigned int hwRevision_;\n> > @@ -111,6 +130,8 @@ public:\n> >   \tbool lock() override;\n> >   \tvoid unlock() override;\n> > +\tstd::unique_ptr<MediaContext> createContext() override;\n> > +\n> >   private:\n> >   \tfriend class MediaDeviceFactory;\n> > diff --git a/src/libcamera/media_device.cpp b/src/libcamera/media_device.cpp\n> > index 70d073f964bde0236585de083baca4cd9c30d193..2b7df346ad9916cf22641ea770e0a1507921d89d 100644\n> > --- a/src/libcamera/media_device.cpp\n> > +++ b/src/libcamera/media_device.cpp\n> > @@ -21,6 +21,9 @@\n> >   #include <libcamera/base/log.h>\n> > +#include \"libcamera/internal/v4l2_subdevice.h\"\n> > +#include \"libcamera/internal/v4l2_videodevice.h\"\n> > +\n> >   /**\n> >    * \\file media_device.h\n> >    * \\brief Provide a representation of a Linux kernel Media Controller device\n> > @@ -81,6 +84,99 @@ MediaDeviceFactory::createMediaDevice(const std::string &deviceNode)\n> >   \treturn std::unique_ptr<MediaDevice>(new MediaDevice(deviceNode));\n> >   }\n> > +/**\n> > + * \\class MediaContext\n> > + * \\brief Represents a media device context\n> > + *\n> > + * A media device context is obtained from a MediaDevice or a SharedMediaDevice\n> > + * instance with the MediaDevice::createContext() and\n> > + * SharedMediaDevice::createContext() functions.\n> > + *\n> > + * MediaContext instances obtained from a MediaDevice that does not support\n> > + * multi-context operations will not have a valid file descriptor associated as\n> > + * the media device they are created from doesn't support multiple open. For\n> > + * this reason binding a device to a context that is obtained from a MediaDevice\n> > + * is a nop.\n> > + *\n> > + * MediaContext instances obtained from a SharedMediaDevice that supports\n> > + * multi-context operations owns the file descriptor that identifies the media\n> > + * context, obtained by opening the media device multiple times and provided to\n> > + * the context at creation time. Binding devices to a context obtained from a\n> > + * SharedMediaDevice associates the devices to an execution context which\n> > + * remains valid until the last bound device stays valid.\n> > + *\n> > + * Users of this class can bind devices on valid and invalid MultiContext\n> > + * instances alike. If the kernel driver supports multi-context operations\n> > + * then the device can be opened multiple times and multiple contexts can be\n> > + * created to which devices can be bound to. If the kernel driver doesn't\n> > + * support multi-context operations then it can be opened a single time only\n> > + * and a creating contexts and bindings devices to it is effectively a nop.\n> > + */\n> > +\n> > +/**\n> > + * \\brief Constructs a MediaContext not associated to any media device context\n> > + *\n> > + * Constructs a media device context not associated to any valid media device\n> > + * context file descriptor. Binding devices to such media device context\n> > + * is effectively a nop.\n> > + */\n> > +MediaContext::MediaContext()\n> > +{\n> > +}\n> > +\n> > +/**\n> > + * \\brief Construct a MediaContext associated to a valid media device context\n> > + * \\param[in] fd The file descriptor that identifies a media device context\n> > + *\n> > + * Construct a media device context to which devices can be bound to.\n> > + *\n> > + * Constructing a MediaContext with an associated valid media device context\n> > + * allows to create isolated execution contexts by binding devices to it.\n> > + *\n> > + * The file descriptor \\a fd obtained by opening the media device and that\n> > + * identifies the media device context is moved in the newly constructed\n> > + * MediaContext which maintains its ownership and automatically closes it at\n> > + * destruction time.\n> > + */\n> > +MediaContext::MediaContext(UniqueFD &&fd)\n> > +\t: fd_(std::move(fd))\n> > +{\n> > +}\n> > +\n> > +/**\n> > + * \\brief Bind a V4L2 video device to a media device context\n> > + * \\param[in] dev The video device to bind\n> > + *\n> > + * If the MediaContext has been created by a MediaDevice that does not\n> > + * support multi-context operations, this function is effectively a nop.\n> > + *\n> > + * \\return 0 on success or a negative error code otherwise\n> > + */\n> > +int MediaContext::bindDevice(V4L2VideoDevice *dev)\n> > +{\n> > +\tif (fd_.get() == -1)\n>\n>   if (!fd_.isValid())\n>\n\nack!\n\n>\n> > +\t\treturn 0;\n> > +\n> > +\treturn dev->bindContext(fd_.get());\n> > +}\n> > +\n> > +/**\n> > + * \\brief Bind a V4L2 subdevice to a media device context\n> > + * \\param[in] dev The subdevice to bind\n> > + *\n> > + * If the MediaContext has been created by a MediaDevice that does not\n> > + * support multi-context operations, this function is effectively a nop.\n> > + *\n> > + * \\return 0 on success or a negative error code otherwise\n> > + */\n> > +int MediaContext::bindDevice(V4L2Subdevice *dev)\n> > +{\n> > +\tif (fd_.get() == -1)\n> > +\t\treturn 0;\n> > +\n> > +\treturn dev->bindContext(fd_.get());\n> > +}\n> > +\n> >   /**\n> >    * \\class MediaDevice\n> >    * \\brief The MediaDevice represents a Media Controller device with its full\n> > @@ -603,6 +699,45 @@ int MediaDevice::disableLinks()\n> >    * driver unloading for most devices. The media device is passed as a parameter.\n> >    */\n> > +/**\n> > + * \\brief Create a MediaContext not associated with a media device context\n> > + * \\return A MediaContext to which bindings devices is a nop\n> > + */\n> > +std::unique_ptr<MediaContext> MediaDevice::createContext()\n> > +{\n> > +\treturn std::make_unique<MediaContext>();\n> > +}\n> > +\n> > +/**\n> > + * \\brief Create a MediaContext associated with a media device context\n> > + *\n> > + * Create a MediaContext by opening the media device and create a media\n> > + * device context using the obtained file descriptor.\n> > + *\n> > + * The open file descriptor is moved to the MediaContext whose ownership is\n> > + * moved to the caller of this function.\n> > + *\n> > + * \\return A MediaContext to which is possible to bind devices to\n> > + */\n> > +std::unique_ptr<MediaContext> SharedMediaDevice::createContext()\n>\n> I am wondering if a simple std::optional<> would be sufficient here?\n> Or do you foresee that `MediaContext` will become an abstract base class\n> or something that would necessitate the heap allocation?\n\nNo, at this time I don't expect to subclass `MediaContext`.\n\nThe idea behind using unique_ptr<> was to convey that the ownership of\nthe context was moved to the caller.\n\nI guess returning an instance wrapped in an std::optional<> has the\nsame effect indeed.\n\nAs long as:\n\nstd::optional<MediaContext> SharedMediaDevice::createContext()\n{\n\tauto fd = UniqueFD(::open(deviceNode_.c_str(), O_RDWR | O_CLOEXEC));\n\n\treturn MediaContext(std::move(fd));\n}\n\n\nstd::optional<MediaContext> mediaContext = createContext();\n\nmoves the returned instance into the caller I guess I can indeed use\nan optional<>\n\nThanks\n  j\n\n>\n>\n> Regards,\n> Barnabás Pőcze\n>\n>\n> > +{\n> > +\tauto fd = UniqueFD(::open(deviceNode_.c_str(), O_RDWR | O_CLOEXEC));\n> > +\tif (!fd.isValid()) {\n> > +\t\tint ret = -errno;\n> > +\t\tLOG(MediaDevice, Error)\n> > +\t\t\t<< \"Failed to open media device at \"\n> > +\t\t\t<< deviceNode_ << \": \" << strerror(-ret);\n> > +\t\treturn {};\n> > +\t}\n> > +\n> > +\treturn std::make_unique<MediaContext>(std::move(fd));\n> > +}\n> > +\n> > +/**\n> > + * \\var MediaDevice::deviceNode_\n> > + * \\brief The media device device node path\n> > + */\n> > +\n> >   /**\n> >    * \\brief Open the media device\n> >    *\n> >\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 2130EBDC71\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 29 Jul 2025 10:35:52 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id D1DF9691DA;\n\tTue, 29 Jul 2025 12:35:50 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id BFF6E69052\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 29 Jul 2025 12:35:48 +0200 (CEST)","from ideasonboard.com (mob-5-90-139-29.net.vodafone.it\n\t[5.90.139.29])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 9BDD2C6E;\n\tTue, 29 Jul 2025 12:35:05 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"LpQY6Oe4\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1753785305;\n\tbh=wB5SVLEixW7WbapxKcYZHu861BilczN0FJNn0ueZ8Q8=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=LpQY6Oe48jCojyUNTCackQXkgtg8tynD1Zb6dvqf3dUHUcPgCdBHGe83ECxTzih1N\n\t267Xee9VhZI8Kc+g1MfEE55KHYdNOv5FKNwZbMZrCXxbdfEhqZT4qkAFe07qksAB4T\n\tX1Fb0RlX910fXk0oazs1HwE2YIn+MGbhCvTqt5ZY=","Date":"Tue, 29 Jul 2025 12:35:42 +0200","From":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","To":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Cc":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>, \n\tlibcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH 7/9] libcamera: media-device: Introduce MediaContext","Message-ID":"<f2c7zdqcmrsene6apgwes3qgozrq3gp6f2q5tg4ayvffy5x644@ao5fuq7766fg>","References":"<20250725-multicontext-v1-0-ea558291e101@ideasonboard.com>\n\t<20250725-multicontext-v1-7-ea558291e101@ideasonboard.com>\n\t<306da0bc-f316-4cee-b054-994aa8320bd6@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<306da0bc-f316-4cee-b054-994aa8320bd6@ideasonboard.com>","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>"}}]