[{"id":35123,"web_url":"https://patchwork.libcamera.org/comment/35123/","msgid":"<3835cb7706828318075e2bd339accf03fbca0425.camel@ideasonboard.com>","date":"2025-07-25T10:43:05","subject":"Re: [PATCH 6/9] libcamera: media-device: Introduce SharedMediaDevice","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 SharedMediaDevice class derived from the MediaDevice\n> class, that represents a media device that supports multi-context\n> operations.\n> \n> Disallow direct construction of MediaDevice and introduce the\n> MediaDeviceFactory class that instantiate the correct class from the\n> MediaDevice class hierarchy inspecting the newly introduced\n> media_device_info.flags field.\n> \n> The new SharedMediaDevice class represents a media device that can\n> be concurrently accessed by different processes, for this reason\n> overload the acquire/lock operations to be effectively nops.\n> \n> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n> ---\n>  include/libcamera/internal/media_device.h |  39 +++++++--\n>  src/libcamera/device_enumerator.cpp       |   8 +-\n>  src/libcamera/media_device.cpp            | 139\n> ++++++++++++++++++++++++++++++\n>  3 files changed, 178 insertions(+), 8 deletions(-)\n> \n> diff --git a/include/libcamera/internal/media_device.h\n> b/include/libcamera/internal/media_device.h\n> index\n> b3a48b98d64b019fb42c5224d29105e1d57a3e3c..ea0e9d66009933bbb9613d27423\n> d2f951520c887 100644\n> --- a/include/libcamera/internal/media_device.h\n> +++ b/include/libcamera/internal/media_device.h\n> @@ -21,18 +21,24 @@\n>  \n>  namespace libcamera {\n>  \n> +class MediaDeviceFactory\n> +{\n> +public:\n> +\tstatic std::unique_ptr<MediaDevice>\n> +\tcreateMediaDevice(const std::string &deviceNode);\n> +};\n> +\n>  class MediaDevice : protected Loggable\n>  {\n>  public:\n> -\tMediaDevice(const std::string &deviceNode);\n> -\t~MediaDevice();\n> +\tvirtual ~MediaDevice();\n>  \n> -\tbool acquire();\n> -\tvoid release();\n> -\tbool busy() const { return acquired_; }\n> +\tvirtual bool acquire();\n> +\tvirtual void release();\n> +\tvirtual bool busy() const { return acquired_; }\n>  \n> -\tbool lock();\n> -\tvoid unlock();\n> +\tvirtual bool lock();\n> +\tvirtual void unlock();\n>  \n>  \tint populate();\n>  \tbool isValid() const { return valid_; }\n> @@ -58,9 +64,12 @@ public:\n>  \tstd::vector<MediaEntity *> locateEntities(unsigned int\n> function);\n>  \n\nReviewed-by: Isaac Scott <isaac.scott@ideasonboard.com>\n\n>  protected:\n> +\tMediaDevice(const std::string &deviceNode);\n>  \tstd::string logPrefix() const override;\n>  \n>  private:\n> +\tfriend class MediaDeviceFactory;\n> +\n>  \tint open();\n>  \tvoid close();\n>  \n> @@ -92,4 +101,20 @@ private:\n>  \tstd::vector<MediaEntity *> entities_;\n>  };\n>  \n> +class SharedMediaDevice : public MediaDevice\n> +{\n> +public:\n> +\tbool acquire() override;\n> +\tvoid release() override;\n> +\tbool busy() const override { return false; }\n> +\n> +\tbool lock() override;\n> +\tvoid unlock() override;\n> +\n> +private:\n> +\tfriend class MediaDeviceFactory;\n> +\n> +\tSharedMediaDevice(const std::string &deviceNode);\n> +};\n> +\n>  } /* namespace libcamera */\n> diff --git a/src/libcamera/device_enumerator.cpp\n> b/src/libcamera/device_enumerator.cpp\n> index\n> ae17862f676310ef568e5331106ed661e84b5130..e73bb0050843c5281986e8e05d2\n> 013102905d270 100644\n> --- a/src/libcamera/device_enumerator.cpp\n> +++ b/src/libcamera/device_enumerator.cpp\n> @@ -217,7 +217,13 @@ DeviceEnumerator::~DeviceEnumerator()\n>   */\n>  std::unique_ptr<MediaDevice> DeviceEnumerator::createDevice(const\n> std::string &deviceNode)\n>  {\n> -\tstd::unique_ptr<MediaDevice> media =\n> std::make_unique<MediaDevice>(deviceNode);\n> +\tstd::unique_ptr<MediaDevice> media =\n> MediaDeviceFactory::createMediaDevice(deviceNode);\n> +\tif (!media) {\n> +\t\tLOG(DeviceEnumerator, Info)\n> +\t\t\t<< \"Unable to create a media device for \" <<\n> deviceNode\n> +\t\t\t<< \", skipping\";\n> +\t\treturn nullptr;\n> +\t}\n>  \n>  \tint ret = media->populate();\n>  \tif (ret < 0) {\n> diff --git a/src/libcamera/media_device.cpp\n> b/src/libcamera/media_device.cpp\n> index\n> 353f34a81eca600a4a35594d782d3dc5dd690bdf..70d073f964bde0236585de083ba\n> ca4cd9c30d193 100644\n> --- a/src/libcamera/media_device.cpp\n> +++ b/src/libcamera/media_device.cpp\n> @@ -31,6 +31,56 @@ namespace libcamera {\n>  \n>  LOG_DEFINE_CATEGORY(MediaDevice)\n>  \n> +/**\n> + * \\class MediaDeviceFactory\n> + * \\brief Factory class that instantiates a MediaDevice or\n> SharedMediaDevice\n> + *\n> + * The MediaDevice and SharedMediaDevice classes cannot be\n> instantiated\n> + * directly, but instead the MediaDeviceFactory shall be used to\n> create\n> + * an instance of these classes using the\n> MediaDeviceFactory::createMediaDevice\n> + * function.\n> + */\n> +\n> +/**\n> + * \\brief Create an instance of the MediaDevice class hierarchy\n> + * \\param[in] deviceNode The media device node path\n> + *\n> + * If a media device supports multi-context operations and advertise\n> it through\n> + * the presence of the MEDIA_DEVICE_FL_CONTEXT flag in the\n> + * media_device_info.flags field an instance of SharedMediaDevice\n> will be\n> + * created and returned. If instead the media device doesn's support\n> + * multi-context operations an instance of the MediaDevice class is\n> created and\n> + * returned.\n> + *\n> + * \\return A unique_ptr<> that wraps an instance of\n> SharedMediaDevice or\n> + * MediaDevice\n> + */\n> +std::unique_ptr<MediaDevice>\n> +MediaDeviceFactory::createMediaDevice(const std::string &deviceNode)\n> +{\n> +\t/*\n> +\t * Inspect the media device info flags to decide which class\n> to\n> +\t * instantiate.\n> +\t */\n> +\tauto fd = UniqueFD(::open(deviceNode.c_str(), O_RDWR |\n> O_CLOEXEC));\n> +\tif (!fd.isValid())\n> +\t\treturn {};\n> +\n> +\tstruct media_device_info info = {};\n> +\tint ret = ioctl(fd.get(), MEDIA_IOC_DEVICE_INFO, &info);\n> +\tif (ret) {\n> +\t\tLOG(MediaDevice, Error)\n> +\t\t\t<< \"Failed to get media device info \" <<\n> strerror(-ret);\n> +\t\treturn {};\n> +\t}\n> +\n> +\tif (info.flags & MEDIA_DEVICE_FL_CONTEXT)\n> +\t\treturn std::unique_ptr<SharedMediaDevice>\n> +\t\t\t(new SharedMediaDevice(deviceNode));\n> +\n> +\treturn std::unique_ptr<MediaDevice>(new\n> MediaDevice(deviceNode));\n> +}\n> +\n>  /**\n>   * \\class MediaDevice\n>   * \\brief The MediaDevice represents a Media Controller device with\n> its full\n> @@ -40,6 +90,9 @@ LOG_DEFINE_CATEGORY(MediaDevice)\n>   * created, and that association is kept for the lifetime of the\n> MediaDevice\n>   * instance.\n>   *\n> + * Instances of MediaDevice are created by the MediaDeviceFactory\n> + * createMediaDevice() function and cannot be directly instantiated.\n> + *\n>   * The instance is created with an empty media graph. Before\n> performing any\n>   * other operation, it must be populate by calling populate().\n> Instances of\n>   * MediaEntity, MediaPad and MediaLink are created to model the\n> media graph,\n> @@ -56,6 +109,30 @@ LOG_DEFINE_CATEGORY(MediaDevice)\n>   * managers to claim media devices they support during enumeration.\n>   */\n>  \n> +/**\n> + * \\class SharedMediaDevice\n> + * \\brief The SharedMediaDevice represents a Media Controller device\n> that\n> + * supports multi-context operations\n> + *\n> + * \\sa MediaDevice\n> + * \\sa MediaDeviceFactory\n> + *\n> + * A SharedMediaDevice is associated with a media controller device\n> node that\n> + * supports multi-context operations. Compared to the MediaDevice\n> base class a\n> + * SharedMediaDevice allows multiple open() calls to happen and is\n> never\n> + * uniquely owned as each time the media device gets open a new\n> context gets\n> + * created. This means that the acquire() and release() operations\n> are\n> + * effectively nop (and as a consequence busy() always return\n> false).\n> + *\n> + * Instances of MediaDevice are created by the MediaDeviceFactory\n> + * createMediaDevice() function and cannot be directly instantiated.\n> + *\n> + * Similarly to the MediaDevice class, instances of\n> SharedMediaDevice are\n> + * created with an empty media graph and needs to be populated using\n> the\n> + * populate() function. Media entities enumerated in the graph can\n> be accessed\n> + * through the same functions as the ones provided by the\n> MediaDevice class.\n> + */\n> +\n>  /**\n>   * \\brief Construct a MediaDevice\n>   * \\param[in] deviceNode The media device node path\n> @@ -68,6 +145,17 @@ MediaDevice::MediaDevice(const std::string\n> &deviceNode)\n>  {\n>  }\n>  \n> +/**\n> + * \\brief Construct a SharedMediaDevice\n> + * \\param[in] deviceNode The media device node path\n> + *\n> + * \\copydoc MediaDevice::MediaDevice\n> + */\n> +SharedMediaDevice::SharedMediaDevice(const std::string &deviceNode)\n> +\t: MediaDevice(deviceNode)\n> +{\n> +}\n> +\n>  MediaDevice::~MediaDevice()\n>  {\n>  \tfd_.reset();\n> @@ -113,6 +201,19 @@ bool MediaDevice::acquire()\n>  \treturn true;\n>  }\n>  \n> +/**\n> + * \\brief Acquiring a SharedMediaDevice is a nop\n> + *\n> + * A SharedMediaDevice is designed to be opened multiple times.\n> Acquiring\n> + * a SharedMediaDevice is effectively a nop.\n> + *\n> + * \\return Always return true\n> + */\n> +bool SharedMediaDevice::acquire()\n> +{\n> +\treturn true;\n> +}\n> +\n>  /**\n>   * \\brief Release a device previously claimed for exclusive use\n>   * \\sa acquire(), busy()\n> @@ -123,6 +224,15 @@ void MediaDevice::release()\n>  \tacquired_ = false;\n>  }\n>  \n> +/**\n> + * \\brief Releasing a SharedMediaDevice is a nop\n> + *\n> + * As acquiring a SharedMediaDevice is a nop, releasing it is a nop\n> as well.\n> + */\n> +void SharedMediaDevice::release()\n> +{\n> +}\n> +\n>  /**\n>   * \\brief Lock the device to prevent it from being used by other\n> instances of\n>   * libcamera\n> @@ -151,6 +261,19 @@ bool MediaDevice::lock()\n>  \treturn true;\n>  }\n>  \n> +/**\n> + * \\brief Locking a SharedMediaDevice is a nop\n> + *\n> + * As SharedMediaDevice is designed to be opened multiple times\n> locking\n> + * a SharedMediaDevice is effectively a nop.\n> + *\n> + * \\return Always return true\n> + */\n> +bool SharedMediaDevice::lock()\n> +{\n> +\treturn true;\n> +}\n> +\n>  /**\n>   * \\brief Unlock the device and free it for use for libcamera\n> instances\n>   *\n> @@ -168,6 +291,15 @@ void MediaDevice::unlock()\n>  \tstd::ignore = lockf(fd_.get(), F_ULOCK, 0);\n>  }\n>  \n> +/**\n> + * \\brief Unlocking a SharedMediaDevice is a nop\n> + *\n> + * As locking a SharedMediaDevice is a nop, unlocking it is a nop as\n> well.\n> + */\n> +void SharedMediaDevice::unlock()\n> +{\n> +}\n> +\n>  /**\n>   * \\fn MediaDevice::busy()\n>   * \\brief Check if a device is in use\n> @@ -176,6 +308,13 @@ void MediaDevice::unlock()\n>   * \\sa acquire(), release()\n>   */\n>  \n> +/**\n> + * \\fn SharedMediaDevice::busy()\n> + * \\brief As SharedMediaDevice is designed to be opened multiple\n> times, for this\n> + * reason it is never marked as busy\n> + * \\return Always returns false\n> + */\n> +\n>  /**\n>   * \\brief Populate the MediaDevice with device information and media\n> objects\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 DBAAEBDCC1\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 25 Jul 2025 10:43:10 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id B714F690C3;\n\tFri, 25 Jul 2025 12:43:09 +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 29896690A8\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 25 Jul 2025 12:43:08 +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 5EBB6982;\n\tFri, 25 Jul 2025 12:42:28 +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=\"BmNaKVHw\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1753440148;\n\tbh=aEN5HlJoc6Mcd8pAtXkAfTwWt3aoCS0EV2DDXoNEweI=;\n\th=Subject:From:To:Date:In-Reply-To:References:From;\n\tb=BmNaKVHwHnTGCh+uruefqsL8cYDlv6vVt8gTfTT6ntAEtrOgi0dEMcD2p/qxft3oH\n\tnWvwV7vnHv6+lPJqLcmZ/lVmyC3ItgMDwnyFvS3+ULDMvSchlcKSwDywzbtNJmM8al\n\tDdFsYoDKxaZ0bLgjl/TLczOMcIb72fWqv6+Prmko=","Message-ID":"<3835cb7706828318075e2bd339accf03fbca0425.camel@ideasonboard.com>","Subject":"Re: [PATCH 6/9] libcamera: media-device: Introduce SharedMediaDevice","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:43:05 +0100","In-Reply-To":"<20250725-multicontext-v1-6-ea558291e101@ideasonboard.com>","References":"<20250725-multicontext-v1-0-ea558291e101@ideasonboard.com>\n\t<20250725-multicontext-v1-6-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":35217,"web_url":"https://patchwork.libcamera.org/comment/35217/","msgid":"<175371718573.135557.13405934889160627904@localhost>","date":"2025-07-28T15:39:45","subject":"Re: [PATCH 6/9] libcamera: media-device: Introduce SharedMediaDevice","submitter":{"id":184,"url":"https://patchwork.libcamera.org/api/people/184/","name":"Stefan Klug","email":"stefan.klug@ideasonboard.com"},"content":"Hi Jacopo,\n\nThank you for the patch.\n\nQuoting Jacopo Mondi (2025-07-25 12:33:51)\n> Introduce the SharedMediaDevice class derived from the MediaDevice\n> class, that represents a media device that supports multi-context\n> operations.\n> \n> Disallow direct construction of MediaDevice and introduce the\n> MediaDeviceFactory class that instantiate the correct class from the\n> MediaDevice class hierarchy inspecting the newly introduced\n> media_device_info.flags field.\n> \n> The new SharedMediaDevice class represents a media device that can\n> be concurrently accessed by different processes, for this reason\n> overload the acquire/lock operations to be effectively nops.\n> \n> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n\nI feel like I'm missing something important here, but I need to ask\nanyways. What do we gain by adding the complexity of a factory and two\ndistinct classes? Couldn't we just add a isShared_ flag or something\nsimilar to the MediaDevice and handle the specialties in the existing\nfunctions? As most of the overrides are no-ops, they could also be\nearly-outs in the original functions.\n\nBest regards,\nStefan\n\n> ---\n>  include/libcamera/internal/media_device.h |  39 +++++++--\n>  src/libcamera/device_enumerator.cpp       |   8 +-\n>  src/libcamera/media_device.cpp            | 139 ++++++++++++++++++++++++++++++\n>  3 files changed, 178 insertions(+), 8 deletions(-)\n> \n> diff --git a/include/libcamera/internal/media_device.h b/include/libcamera/internal/media_device.h\n> index b3a48b98d64b019fb42c5224d29105e1d57a3e3c..ea0e9d66009933bbb9613d27423d2f951520c887 100644\n> --- a/include/libcamera/internal/media_device.h\n> +++ b/include/libcamera/internal/media_device.h\n> @@ -21,18 +21,24 @@\n>  \n>  namespace libcamera {\n>  \n> +class MediaDeviceFactory\n> +{\n> +public:\n> +       static std::unique_ptr<MediaDevice>\n> +       createMediaDevice(const std::string &deviceNode);\n> +};\n> +\n>  class MediaDevice : protected Loggable\n>  {\n>  public:\n> -       MediaDevice(const std::string &deviceNode);\n> -       ~MediaDevice();\n> +       virtual ~MediaDevice();\n>  \n> -       bool acquire();\n> -       void release();\n> -       bool busy() const { return acquired_; }\n> +       virtual bool acquire();\n> +       virtual void release();\n> +       virtual bool busy() const { return acquired_; }\n>  \n> -       bool lock();\n> -       void unlock();\n> +       virtual bool lock();\n> +       virtual void unlock();\n>  \n>         int populate();\n>         bool isValid() const { return valid_; }\n> @@ -58,9 +64,12 @@ public:\n>         std::vector<MediaEntity *> locateEntities(unsigned int function);\n>  \n>  protected:\n> +       MediaDevice(const std::string &deviceNode);\n>         std::string logPrefix() const override;\n>  \n>  private:\n> +       friend class MediaDeviceFactory;\n> +\n>         int open();\n>         void close();\n>  \n> @@ -92,4 +101,20 @@ private:\n>         std::vector<MediaEntity *> entities_;\n>  };\n>  \n> +class SharedMediaDevice : public MediaDevice\n> +{\n> +public:\n> +       bool acquire() override;\n> +       void release() override;\n> +       bool busy() const override { return false; }\n> +\n> +       bool lock() override;\n> +       void unlock() override;\n> +\n> +private:\n> +       friend class MediaDeviceFactory;\n> +\n> +       SharedMediaDevice(const std::string &deviceNode);\n> +};\n> +\n>  } /* namespace libcamera */\n> diff --git a/src/libcamera/device_enumerator.cpp b/src/libcamera/device_enumerator.cpp\n> index ae17862f676310ef568e5331106ed661e84b5130..e73bb0050843c5281986e8e05d2013102905d270 100644\n> --- a/src/libcamera/device_enumerator.cpp\n> +++ b/src/libcamera/device_enumerator.cpp\n> @@ -217,7 +217,13 @@ DeviceEnumerator::~DeviceEnumerator()\n>   */\n>  std::unique_ptr<MediaDevice> DeviceEnumerator::createDevice(const std::string &deviceNode)\n>  {\n> -       std::unique_ptr<MediaDevice> media = std::make_unique<MediaDevice>(deviceNode);\n> +       std::unique_ptr<MediaDevice> media = MediaDeviceFactory::createMediaDevice(deviceNode);\n> +       if (!media) {\n> +               LOG(DeviceEnumerator, Info)\n> +                       << \"Unable to create a media device for \" << deviceNode\n> +                       << \", skipping\";\n> +               return nullptr;\n> +       }\n>  \n>         int ret = media->populate();\n>         if (ret < 0) {\n> diff --git a/src/libcamera/media_device.cpp b/src/libcamera/media_device.cpp\n> index 353f34a81eca600a4a35594d782d3dc5dd690bdf..70d073f964bde0236585de083baca4cd9c30d193 100644\n> --- a/src/libcamera/media_device.cpp\n> +++ b/src/libcamera/media_device.cpp\n> @@ -31,6 +31,56 @@ namespace libcamera {\n>  \n>  LOG_DEFINE_CATEGORY(MediaDevice)\n>  \n> +/**\n> + * \\class MediaDeviceFactory\n> + * \\brief Factory class that instantiates a MediaDevice or SharedMediaDevice\n> + *\n> + * The MediaDevice and SharedMediaDevice classes cannot be instantiated\n> + * directly, but instead the MediaDeviceFactory shall be used to create\n> + * an instance of these classes using the MediaDeviceFactory::createMediaDevice\n> + * function.\n> + */\n> +\n> +/**\n> + * \\brief Create an instance of the MediaDevice class hierarchy\n> + * \\param[in] deviceNode The media device node path\n> + *\n> + * If a media device supports multi-context operations and advertise it through\n> + * the presence of the MEDIA_DEVICE_FL_CONTEXT flag in the\n> + * media_device_info.flags field an instance of SharedMediaDevice will be\n> + * created and returned. If instead the media device doesn's support\n> + * multi-context operations an instance of the MediaDevice class is created and\n> + * returned.\n> + *\n> + * \\return A unique_ptr<> that wraps an instance of SharedMediaDevice or\n> + * MediaDevice\n> + */\n> +std::unique_ptr<MediaDevice>\n> +MediaDeviceFactory::createMediaDevice(const std::string &deviceNode)\n> +{\n> +       /*\n> +        * Inspect the media device info flags to decide which class to\n> +        * instantiate.\n> +        */\n> +       auto fd = UniqueFD(::open(deviceNode.c_str(), O_RDWR | O_CLOEXEC));\n> +       if (!fd.isValid())\n> +               return {};\n> +\n> +       struct media_device_info info = {};\n> +       int ret = ioctl(fd.get(), MEDIA_IOC_DEVICE_INFO, &info);\n> +       if (ret) {\n> +               LOG(MediaDevice, Error)\n> +                       << \"Failed to get media device info \" << strerror(-ret);\n> +               return {};\n> +       }\n> +\n> +       if (info.flags & MEDIA_DEVICE_FL_CONTEXT)\n> +               return std::unique_ptr<SharedMediaDevice>\n> +                       (new SharedMediaDevice(deviceNode));\n> +\n> +       return std::unique_ptr<MediaDevice>(new MediaDevice(deviceNode));\n> +}\n> +\n>  /**\n>   * \\class MediaDevice\n>   * \\brief The MediaDevice represents a Media Controller device with its full\n> @@ -40,6 +90,9 @@ LOG_DEFINE_CATEGORY(MediaDevice)\n>   * created, and that association is kept for the lifetime of the MediaDevice\n>   * instance.\n>   *\n> + * Instances of MediaDevice are created by the MediaDeviceFactory\n> + * createMediaDevice() function and cannot be directly instantiated.\n> + *\n>   * The instance is created with an empty media graph. Before performing any\n>   * other operation, it must be populate by calling populate(). Instances of\n>   * MediaEntity, MediaPad and MediaLink are created to model the media graph,\n> @@ -56,6 +109,30 @@ LOG_DEFINE_CATEGORY(MediaDevice)\n>   * managers to claim media devices they support during enumeration.\n>   */\n>  \n> +/**\n> + * \\class SharedMediaDevice\n> + * \\brief The SharedMediaDevice represents a Media Controller device that\n> + * supports multi-context operations\n> + *\n> + * \\sa MediaDevice\n> + * \\sa MediaDeviceFactory\n> + *\n> + * A SharedMediaDevice is associated with a media controller device node that\n> + * supports multi-context operations. Compared to the MediaDevice base class a\n> + * SharedMediaDevice allows multiple open() calls to happen and is never\n> + * uniquely owned as each time the media device gets open a new context gets\n> + * created. This means that the acquire() and release() operations are\n> + * effectively nop (and as a consequence busy() always return false).\n> + *\n> + * Instances of MediaDevice are created by the MediaDeviceFactory\n> + * createMediaDevice() function and cannot be directly instantiated.\n> + *\n> + * Similarly to the MediaDevice class, instances of SharedMediaDevice are\n> + * created with an empty media graph and needs to be populated using the\n> + * populate() function. Media entities enumerated in the graph can be accessed\n> + * through the same functions as the ones provided by the MediaDevice class.\n> + */\n> +\n>  /**\n>   * \\brief Construct a MediaDevice\n>   * \\param[in] deviceNode The media device node path\n> @@ -68,6 +145,17 @@ MediaDevice::MediaDevice(const std::string &deviceNode)\n>  {\n>  }\n>  \n> +/**\n> + * \\brief Construct a SharedMediaDevice\n> + * \\param[in] deviceNode The media device node path\n> + *\n> + * \\copydoc MediaDevice::MediaDevice\n> + */\n> +SharedMediaDevice::SharedMediaDevice(const std::string &deviceNode)\n> +       : MediaDevice(deviceNode)\n> +{\n> +}\n> +\n>  MediaDevice::~MediaDevice()\n>  {\n>         fd_.reset();\n> @@ -113,6 +201,19 @@ bool MediaDevice::acquire()\n>         return true;\n>  }\n>  \n> +/**\n> + * \\brief Acquiring a SharedMediaDevice is a nop\n> + *\n> + * A SharedMediaDevice is designed to be opened multiple times. Acquiring\n> + * a SharedMediaDevice is effectively a nop.\n> + *\n> + * \\return Always return true\n> + */\n> +bool SharedMediaDevice::acquire()\n> +{\n> +       return true;\n> +}\n> +\n>  /**\n>   * \\brief Release a device previously claimed for exclusive use\n>   * \\sa acquire(), busy()\n> @@ -123,6 +224,15 @@ void MediaDevice::release()\n>         acquired_ = false;\n>  }\n>  \n> +/**\n> + * \\brief Releasing a SharedMediaDevice is a nop\n> + *\n> + * As acquiring a SharedMediaDevice is a nop, releasing it is a nop as well.\n> + */\n> +void SharedMediaDevice::release()\n> +{\n> +}\n> +\n>  /**\n>   * \\brief Lock the device to prevent it from being used by other instances of\n>   * libcamera\n> @@ -151,6 +261,19 @@ bool MediaDevice::lock()\n>         return true;\n>  }\n>  \n> +/**\n> + * \\brief Locking a SharedMediaDevice is a nop\n> + *\n> + * As SharedMediaDevice is designed to be opened multiple times locking\n> + * a SharedMediaDevice is effectively a nop.\n> + *\n> + * \\return Always return true\n> + */\n> +bool SharedMediaDevice::lock()\n> +{\n> +       return true;\n> +}\n> +\n>  /**\n>   * \\brief Unlock the device and free it for use for libcamera instances\n>   *\n> @@ -168,6 +291,15 @@ void MediaDevice::unlock()\n>         std::ignore = lockf(fd_.get(), F_ULOCK, 0);\n>  }\n>  \n> +/**\n> + * \\brief Unlocking a SharedMediaDevice is a nop\n> + *\n> + * As locking a SharedMediaDevice is a nop, unlocking it is a nop as well.\n> + */\n> +void SharedMediaDevice::unlock()\n> +{\n> +}\n> +\n>  /**\n>   * \\fn MediaDevice::busy()\n>   * \\brief Check if a device is in use\n> @@ -176,6 +308,13 @@ void MediaDevice::unlock()\n>   * \\sa acquire(), release()\n>   */\n>  \n> +/**\n> + * \\fn SharedMediaDevice::busy()\n> + * \\brief As SharedMediaDevice is designed to be opened multiple times, for this\n> + * reason it is never marked as busy\n> + * \\return Always returns false\n> + */\n> +\n>  /**\n>   * \\brief Populate the MediaDevice with device information and media objects\n>   *\n> \n> -- \n> 2.49.0\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 05015C3237\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 28 Jul 2025 15:39:50 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id A53C9691C0;\n\tMon, 28 Jul 2025 17:39:49 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 5B2BB691B8\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 28 Jul 2025 17:39:48 +0200 (CEST)","from ideasonboard.com\n\t(p200300f89f4c845c8e3f30ec5e1afeb9.dip0.t-ipconnect.de\n\t[IPv6:2003:f8:9f4c:845c:8e3f:30ec:5e1a:feb9])\n\tby perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id 5CC5142B;\n\tMon, 28 Jul 2025 17:39:06 +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=\"bDNsWfBr\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1753717146;\n\tbh=GzscG8zzN78VPcxVm5k406irEFf1SIK6qWkojzQzVw4=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=bDNsWfBrX2WsQmCl0+HdEkhLDqKSqMTRdE8tex9tHSjbWxhdcJFAHfF5rxaatXyO1\n\tI0LsOwgVhMQ8RrczmNG14CheNMrEG6Ea4brcW06BsA6G9sxF82TMXKyaOUCzs0tTUh\n\tNR2siYGlHO7Ym86uK2PWcW3U6LR3Vgkv4CFGU+ds=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20250725-multicontext-v1-6-ea558291e101@ideasonboard.com>","References":"<20250725-multicontext-v1-0-ea558291e101@ideasonboard.com>\n\t<20250725-multicontext-v1-6-ea558291e101@ideasonboard.com>","Subject":"Re: [PATCH 6/9] libcamera: media-device: Introduce SharedMediaDevice","From":"Stefan Klug <stefan.klug@ideasonboard.com>","Cc":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","To":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Mon, 28 Jul 2025 17:39:45 +0200","Message-ID":"<175371718573.135557.13405934889160627904@localhost>","User-Agent":"alot/0.12.dev8+g2c003385c862.d20250602","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":35218,"web_url":"https://patchwork.libcamera.org/comment/35218/","msgid":"<3nm6dxonly35gzlhprr6dod3o47suhuub3lqrtqodz64a2me4u@yo6n5ofgk3gc>","date":"2025-07-28T15:53:11","subject":"Re: [PATCH 6/9] libcamera: media-device: Introduce SharedMediaDevice","submitter":{"id":143,"url":"https://patchwork.libcamera.org/api/people/143/","name":"Jacopo Mondi","email":"jacopo.mondi@ideasonboard.com"},"content":"Hi Stefan\n\nOn Mon, Jul 28, 2025 at 05:39:45PM +0200, Stefan Klug wrote:\n> Hi Jacopo,\n>\n> Thank you for the patch.\n>\n> Quoting Jacopo Mondi (2025-07-25 12:33:51)\n> > Introduce the SharedMediaDevice class derived from the MediaDevice\n> > class, that represents a media device that supports multi-context\n> > operations.\n> >\n> > Disallow direct construction of MediaDevice and introduce the\n> > MediaDeviceFactory class that instantiate the correct class from the\n> > MediaDevice class hierarchy inspecting the newly introduced\n> > media_device_info.flags field.\n> >\n> > The new SharedMediaDevice class represents a media device that can\n> > be concurrently accessed by different processes, for this reason\n> > overload the acquire/lock operations to be effectively nops.\n> >\n> > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n>\n> I feel like I'm missing something important here, but I need to ask\n> anyways. What do we gain by adding the complexity of a factory and two\n> distinct classes? Couldn't we just add a isShared_ flag or something\n> similar to the MediaDevice and handle the specialties in the existing\n> functions? As most of the overrides are no-ops, they could also be\n> early-outs in the original functions.\n\nThanks for the question, that's been my biggest one as well..\n\nI initially went for the option you're suggesting, but handling of the\nfile descriptors was getting ugly, as I had to keep an array of fds\nfor each media device.\n\nLater I introduced MediaContext and I basically moved the file\ndescriptor handling to that class, something that might now make the\n\"single class\" solution less ugly...\n\nI can reconsider that, I'm all but happy with the result of this\nseries, but I wanted something out in the hope people started looking\ninto multi-context :)\n\n>\n> Best regards,\n> Stefan\n>\n> > ---\n> >  include/libcamera/internal/media_device.h |  39 +++++++--\n> >  src/libcamera/device_enumerator.cpp       |   8 +-\n> >  src/libcamera/media_device.cpp            | 139 ++++++++++++++++++++++++++++++\n> >  3 files changed, 178 insertions(+), 8 deletions(-)\n> >\n> > diff --git a/include/libcamera/internal/media_device.h b/include/libcamera/internal/media_device.h\n> > index b3a48b98d64b019fb42c5224d29105e1d57a3e3c..ea0e9d66009933bbb9613d27423d2f951520c887 100644\n> > --- a/include/libcamera/internal/media_device.h\n> > +++ b/include/libcamera/internal/media_device.h\n> > @@ -21,18 +21,24 @@\n> >\n> >  namespace libcamera {\n> >\n> > +class MediaDeviceFactory\n> > +{\n> > +public:\n> > +       static std::unique_ptr<MediaDevice>\n> > +       createMediaDevice(const std::string &deviceNode);\n> > +};\n> > +\n> >  class MediaDevice : protected Loggable\n> >  {\n> >  public:\n> > -       MediaDevice(const std::string &deviceNode);\n> > -       ~MediaDevice();\n> > +       virtual ~MediaDevice();\n> >\n> > -       bool acquire();\n> > -       void release();\n> > -       bool busy() const { return acquired_; }\n> > +       virtual bool acquire();\n> > +       virtual void release();\n> > +       virtual bool busy() const { return acquired_; }\n> >\n> > -       bool lock();\n> > -       void unlock();\n> > +       virtual bool lock();\n> > +       virtual void unlock();\n> >\n> >         int populate();\n> >         bool isValid() const { return valid_; }\n> > @@ -58,9 +64,12 @@ public:\n> >         std::vector<MediaEntity *> locateEntities(unsigned int function);\n> >\n> >  protected:\n> > +       MediaDevice(const std::string &deviceNode);\n> >         std::string logPrefix() const override;\n> >\n> >  private:\n> > +       friend class MediaDeviceFactory;\n> > +\n> >         int open();\n> >         void close();\n> >\n> > @@ -92,4 +101,20 @@ private:\n> >         std::vector<MediaEntity *> entities_;\n> >  };\n> >\n> > +class SharedMediaDevice : public MediaDevice\n> > +{\n> > +public:\n> > +       bool acquire() override;\n> > +       void release() override;\n> > +       bool busy() const override { return false; }\n> > +\n> > +       bool lock() override;\n> > +       void unlock() override;\n> > +\n> > +private:\n> > +       friend class MediaDeviceFactory;\n> > +\n> > +       SharedMediaDevice(const std::string &deviceNode);\n> > +};\n> > +\n> >  } /* namespace libcamera */\n> > diff --git a/src/libcamera/device_enumerator.cpp b/src/libcamera/device_enumerator.cpp\n> > index ae17862f676310ef568e5331106ed661e84b5130..e73bb0050843c5281986e8e05d2013102905d270 100644\n> > --- a/src/libcamera/device_enumerator.cpp\n> > +++ b/src/libcamera/device_enumerator.cpp\n> > @@ -217,7 +217,13 @@ DeviceEnumerator::~DeviceEnumerator()\n> >   */\n> >  std::unique_ptr<MediaDevice> DeviceEnumerator::createDevice(const std::string &deviceNode)\n> >  {\n> > -       std::unique_ptr<MediaDevice> media = std::make_unique<MediaDevice>(deviceNode);\n> > +       std::unique_ptr<MediaDevice> media = MediaDeviceFactory::createMediaDevice(deviceNode);\n> > +       if (!media) {\n> > +               LOG(DeviceEnumerator, Info)\n> > +                       << \"Unable to create a media device for \" << deviceNode\n> > +                       << \", skipping\";\n> > +               return nullptr;\n> > +       }\n> >\n> >         int ret = media->populate();\n> >         if (ret < 0) {\n> > diff --git a/src/libcamera/media_device.cpp b/src/libcamera/media_device.cpp\n> > index 353f34a81eca600a4a35594d782d3dc5dd690bdf..70d073f964bde0236585de083baca4cd9c30d193 100644\n> > --- a/src/libcamera/media_device.cpp\n> > +++ b/src/libcamera/media_device.cpp\n> > @@ -31,6 +31,56 @@ namespace libcamera {\n> >\n> >  LOG_DEFINE_CATEGORY(MediaDevice)\n> >\n> > +/**\n> > + * \\class MediaDeviceFactory\n> > + * \\brief Factory class that instantiates a MediaDevice or SharedMediaDevice\n> > + *\n> > + * The MediaDevice and SharedMediaDevice classes cannot be instantiated\n> > + * directly, but instead the MediaDeviceFactory shall be used to create\n> > + * an instance of these classes using the MediaDeviceFactory::createMediaDevice\n> > + * function.\n> > + */\n> > +\n> > +/**\n> > + * \\brief Create an instance of the MediaDevice class hierarchy\n> > + * \\param[in] deviceNode The media device node path\n> > + *\n> > + * If a media device supports multi-context operations and advertise it through\n> > + * the presence of the MEDIA_DEVICE_FL_CONTEXT flag in the\n> > + * media_device_info.flags field an instance of SharedMediaDevice will be\n> > + * created and returned. If instead the media device doesn's support\n> > + * multi-context operations an instance of the MediaDevice class is created and\n> > + * returned.\n> > + *\n> > + * \\return A unique_ptr<> that wraps an instance of SharedMediaDevice or\n> > + * MediaDevice\n> > + */\n> > +std::unique_ptr<MediaDevice>\n> > +MediaDeviceFactory::createMediaDevice(const std::string &deviceNode)\n> > +{\n> > +       /*\n> > +        * Inspect the media device info flags to decide which class to\n> > +        * instantiate.\n> > +        */\n> > +       auto fd = UniqueFD(::open(deviceNode.c_str(), O_RDWR | O_CLOEXEC));\n> > +       if (!fd.isValid())\n> > +               return {};\n> > +\n> > +       struct media_device_info info = {};\n> > +       int ret = ioctl(fd.get(), MEDIA_IOC_DEVICE_INFO, &info);\n> > +       if (ret) {\n> > +               LOG(MediaDevice, Error)\n> > +                       << \"Failed to get media device info \" << strerror(-ret);\n> > +               return {};\n> > +       }\n> > +\n> > +       if (info.flags & MEDIA_DEVICE_FL_CONTEXT)\n> > +               return std::unique_ptr<SharedMediaDevice>\n> > +                       (new SharedMediaDevice(deviceNode));\n> > +\n> > +       return std::unique_ptr<MediaDevice>(new MediaDevice(deviceNode));\n> > +}\n> > +\n> >  /**\n> >   * \\class MediaDevice\n> >   * \\brief The MediaDevice represents a Media Controller device with its full\n> > @@ -40,6 +90,9 @@ LOG_DEFINE_CATEGORY(MediaDevice)\n> >   * created, and that association is kept for the lifetime of the MediaDevice\n> >   * instance.\n> >   *\n> > + * Instances of MediaDevice are created by the MediaDeviceFactory\n> > + * createMediaDevice() function and cannot be directly instantiated.\n> > + *\n> >   * The instance is created with an empty media graph. Before performing any\n> >   * other operation, it must be populate by calling populate(). Instances of\n> >   * MediaEntity, MediaPad and MediaLink are created to model the media graph,\n> > @@ -56,6 +109,30 @@ LOG_DEFINE_CATEGORY(MediaDevice)\n> >   * managers to claim media devices they support during enumeration.\n> >   */\n> >\n> > +/**\n> > + * \\class SharedMediaDevice\n> > + * \\brief The SharedMediaDevice represents a Media Controller device that\n> > + * supports multi-context operations\n> > + *\n> > + * \\sa MediaDevice\n> > + * \\sa MediaDeviceFactory\n> > + *\n> > + * A SharedMediaDevice is associated with a media controller device node that\n> > + * supports multi-context operations. Compared to the MediaDevice base class a\n> > + * SharedMediaDevice allows multiple open() calls to happen and is never\n> > + * uniquely owned as each time the media device gets open a new context gets\n> > + * created. This means that the acquire() and release() operations are\n> > + * effectively nop (and as a consequence busy() always return false).\n> > + *\n> > + * Instances of MediaDevice are created by the MediaDeviceFactory\n> > + * createMediaDevice() function and cannot be directly instantiated.\n> > + *\n> > + * Similarly to the MediaDevice class, instances of SharedMediaDevice are\n> > + * created with an empty media graph and needs to be populated using the\n> > + * populate() function. Media entities enumerated in the graph can be accessed\n> > + * through the same functions as the ones provided by the MediaDevice class.\n> > + */\n> > +\n> >  /**\n> >   * \\brief Construct a MediaDevice\n> >   * \\param[in] deviceNode The media device node path\n> > @@ -68,6 +145,17 @@ MediaDevice::MediaDevice(const std::string &deviceNode)\n> >  {\n> >  }\n> >\n> > +/**\n> > + * \\brief Construct a SharedMediaDevice\n> > + * \\param[in] deviceNode The media device node path\n> > + *\n> > + * \\copydoc MediaDevice::MediaDevice\n> > + */\n> > +SharedMediaDevice::SharedMediaDevice(const std::string &deviceNode)\n> > +       : MediaDevice(deviceNode)\n> > +{\n> > +}\n> > +\n> >  MediaDevice::~MediaDevice()\n> >  {\n> >         fd_.reset();\n> > @@ -113,6 +201,19 @@ bool MediaDevice::acquire()\n> >         return true;\n> >  }\n> >\n> > +/**\n> > + * \\brief Acquiring a SharedMediaDevice is a nop\n> > + *\n> > + * A SharedMediaDevice is designed to be opened multiple times. Acquiring\n> > + * a SharedMediaDevice is effectively a nop.\n> > + *\n> > + * \\return Always return true\n> > + */\n> > +bool SharedMediaDevice::acquire()\n> > +{\n> > +       return true;\n> > +}\n> > +\n> >  /**\n> >   * \\brief Release a device previously claimed for exclusive use\n> >   * \\sa acquire(), busy()\n> > @@ -123,6 +224,15 @@ void MediaDevice::release()\n> >         acquired_ = false;\n> >  }\n> >\n> > +/**\n> > + * \\brief Releasing a SharedMediaDevice is a nop\n> > + *\n> > + * As acquiring a SharedMediaDevice is a nop, releasing it is a nop as well.\n> > + */\n> > +void SharedMediaDevice::release()\n> > +{\n> > +}\n> > +\n> >  /**\n> >   * \\brief Lock the device to prevent it from being used by other instances of\n> >   * libcamera\n> > @@ -151,6 +261,19 @@ bool MediaDevice::lock()\n> >         return true;\n> >  }\n> >\n> > +/**\n> > + * \\brief Locking a SharedMediaDevice is a nop\n> > + *\n> > + * As SharedMediaDevice is designed to be opened multiple times locking\n> > + * a SharedMediaDevice is effectively a nop.\n> > + *\n> > + * \\return Always return true\n> > + */\n> > +bool SharedMediaDevice::lock()\n> > +{\n> > +       return true;\n> > +}\n> > +\n> >  /**\n> >   * \\brief Unlock the device and free it for use for libcamera instances\n> >   *\n> > @@ -168,6 +291,15 @@ void MediaDevice::unlock()\n> >         std::ignore = lockf(fd_.get(), F_ULOCK, 0);\n> >  }\n> >\n> > +/**\n> > + * \\brief Unlocking a SharedMediaDevice is a nop\n> > + *\n> > + * As locking a SharedMediaDevice is a nop, unlocking it is a nop as well.\n> > + */\n> > +void SharedMediaDevice::unlock()\n> > +{\n> > +}\n> > +\n> >  /**\n> >   * \\fn MediaDevice::busy()\n> >   * \\brief Check if a device is in use\n> > @@ -176,6 +308,13 @@ void MediaDevice::unlock()\n> >   * \\sa acquire(), release()\n> >   */\n> >\n> > +/**\n> > + * \\fn SharedMediaDevice::busy()\n> > + * \\brief As SharedMediaDevice is designed to be opened multiple times, for this\n> > + * reason it is never marked as busy\n> > + * \\return Always returns false\n> > + */\n> > +\n> >  /**\n> >   * \\brief Populate the MediaDevice with device information and media objects\n> >   *\n> >\n> > --\n> > 2.49.0\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 E1E8FBDCC1\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 28 Jul 2025 15:53:18 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 342BF691BF;\n\tMon, 28 Jul 2025 17:53:17 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 9F77C691B8\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 28 Jul 2025 17:53:15 +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 51AFC22B;\n\tMon, 28 Jul 2025 17:52:33 +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=\"RSOpvnSD\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1753717953;\n\tbh=bVNccnubYXkKiAj4a09kd/R7/2HnDodQcEnX87AxqUY=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=RSOpvnSDQzTjPXIvz+n84umTBolzO3KUE8JeA9Bf+AHXCzOEqwvZ5XpeIfB3PhlFU\n\tkcSt5BHcrgwkr55y3AZ9lDmzZUl5VH4oOcaiLlu2Nx6uLYCZyuYdIlerZvv2kgn9Od\n\t6URNSsywYAb1I4wJg2u8HKHfC5a787P5J9ApEMyQ=","Date":"Mon, 28 Jul 2025 17:53:11 +0200","From":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","To":"Stefan Klug <stefan.klug@ideasonboard.com>","Cc":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>, \n\tlibcamera-devel@lists.libcamera.org","Subject":"Re: [PATCH 6/9] libcamera: media-device: Introduce SharedMediaDevice","Message-ID":"<3nm6dxonly35gzlhprr6dod3o47suhuub3lqrtqodz64a2me4u@yo6n5ofgk3gc>","References":"<20250725-multicontext-v1-0-ea558291e101@ideasonboard.com>\n\t<20250725-multicontext-v1-6-ea558291e101@ideasonboard.com>\n\t<175371718573.135557.13405934889160627904@localhost>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<175371718573.135557.13405934889160627904@localhost>","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>"}}]