[6/9] libcamera: media-device: Introduce SharedMediaDevice
diff mbox series

Message ID 20250725-multicontext-v1-6-ea558291e101@ideasonboard.com
State New
Headers show
Series
  • libcamera: Support for multi-context operations
Related show

Commit Message

Jacopo Mondi July 25, 2025, 10:33 a.m. UTC
Introduce the SharedMediaDevice class derived from the MediaDevice
class, that represents a media device that supports multi-context
operations.

Disallow direct construction of MediaDevice and introduce the
MediaDeviceFactory class that instantiate the correct class from the
MediaDevice class hierarchy inspecting the newly introduced
media_device_info.flags field.

The new SharedMediaDevice class represents a media device that can
be concurrently accessed by different processes, for this reason
overload the acquire/lock operations to be effectively nops.

Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
---
 include/libcamera/internal/media_device.h |  39 +++++++--
 src/libcamera/device_enumerator.cpp       |   8 +-
 src/libcamera/media_device.cpp            | 139 ++++++++++++++++++++++++++++++
 3 files changed, 178 insertions(+), 8 deletions(-)

Comments

Isaac Scott July 25, 2025, 10:43 a.m. UTC | #1
Hi Jacopo,

Thank you for the patch!

On Fri, 2025-07-25 at 12:33 +0200, Jacopo Mondi wrote:
> Introduce the SharedMediaDevice class derived from the MediaDevice
> class, that represents a media device that supports multi-context
> operations.
> 
> Disallow direct construction of MediaDevice and introduce the
> MediaDeviceFactory class that instantiate the correct class from the
> MediaDevice class hierarchy inspecting the newly introduced
> media_device_info.flags field.
> 
> The new SharedMediaDevice class represents a media device that can
> be concurrently accessed by different processes, for this reason
> overload the acquire/lock operations to be effectively nops.
> 
> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> ---
>  include/libcamera/internal/media_device.h |  39 +++++++--
>  src/libcamera/device_enumerator.cpp       |   8 +-
>  src/libcamera/media_device.cpp            | 139
> ++++++++++++++++++++++++++++++
>  3 files changed, 178 insertions(+), 8 deletions(-)
> 
> diff --git a/include/libcamera/internal/media_device.h
> b/include/libcamera/internal/media_device.h
> index
> b3a48b98d64b019fb42c5224d29105e1d57a3e3c..ea0e9d66009933bbb9613d27423
> d2f951520c887 100644
> --- a/include/libcamera/internal/media_device.h
> +++ b/include/libcamera/internal/media_device.h
> @@ -21,18 +21,24 @@
>  
>  namespace libcamera {
>  
> +class MediaDeviceFactory
> +{
> +public:
> +	static std::unique_ptr<MediaDevice>
> +	createMediaDevice(const std::string &deviceNode);
> +};
> +
>  class MediaDevice : protected Loggable
>  {
>  public:
> -	MediaDevice(const std::string &deviceNode);
> -	~MediaDevice();
> +	virtual ~MediaDevice();
>  
> -	bool acquire();
> -	void release();
> -	bool busy() const { return acquired_; }
> +	virtual bool acquire();
> +	virtual void release();
> +	virtual bool busy() const { return acquired_; }
>  
> -	bool lock();
> -	void unlock();
> +	virtual bool lock();
> +	virtual void unlock();
>  
>  	int populate();
>  	bool isValid() const { return valid_; }
> @@ -58,9 +64,12 @@ public:
>  	std::vector<MediaEntity *> locateEntities(unsigned int
> function);
>  

Reviewed-by: Isaac Scott <isaac.scott@ideasonboard.com>

>  protected:
> +	MediaDevice(const std::string &deviceNode);
>  	std::string logPrefix() const override;
>  
>  private:
> +	friend class MediaDeviceFactory;
> +
>  	int open();
>  	void close();
>  
> @@ -92,4 +101,20 @@ private:
>  	std::vector<MediaEntity *> entities_;
>  };
>  
> +class SharedMediaDevice : public MediaDevice
> +{
> +public:
> +	bool acquire() override;
> +	void release() override;
> +	bool busy() const override { return false; }
> +
> +	bool lock() override;
> +	void unlock() override;
> +
> +private:
> +	friend class MediaDeviceFactory;
> +
> +	SharedMediaDevice(const std::string &deviceNode);
> +};
> +
>  } /* namespace libcamera */
> diff --git a/src/libcamera/device_enumerator.cpp
> b/src/libcamera/device_enumerator.cpp
> index
> ae17862f676310ef568e5331106ed661e84b5130..e73bb0050843c5281986e8e05d2
> 013102905d270 100644
> --- a/src/libcamera/device_enumerator.cpp
> +++ b/src/libcamera/device_enumerator.cpp
> @@ -217,7 +217,13 @@ DeviceEnumerator::~DeviceEnumerator()
>   */
>  std::unique_ptr<MediaDevice> DeviceEnumerator::createDevice(const
> std::string &deviceNode)
>  {
> -	std::unique_ptr<MediaDevice> media =
> std::make_unique<MediaDevice>(deviceNode);
> +	std::unique_ptr<MediaDevice> media =
> MediaDeviceFactory::createMediaDevice(deviceNode);
> +	if (!media) {
> +		LOG(DeviceEnumerator, Info)
> +			<< "Unable to create a media device for " <<
> deviceNode
> +			<< ", skipping";
> +		return nullptr;
> +	}
>  
>  	int ret = media->populate();
>  	if (ret < 0) {
> diff --git a/src/libcamera/media_device.cpp
> b/src/libcamera/media_device.cpp
> index
> 353f34a81eca600a4a35594d782d3dc5dd690bdf..70d073f964bde0236585de083ba
> ca4cd9c30d193 100644
> --- a/src/libcamera/media_device.cpp
> +++ b/src/libcamera/media_device.cpp
> @@ -31,6 +31,56 @@ namespace libcamera {
>  
>  LOG_DEFINE_CATEGORY(MediaDevice)
>  
> +/**
> + * \class MediaDeviceFactory
> + * \brief Factory class that instantiates a MediaDevice or
> SharedMediaDevice
> + *
> + * The MediaDevice and SharedMediaDevice classes cannot be
> instantiated
> + * directly, but instead the MediaDeviceFactory shall be used to
> create
> + * an instance of these classes using the
> MediaDeviceFactory::createMediaDevice
> + * function.
> + */
> +
> +/**
> + * \brief Create an instance of the MediaDevice class hierarchy
> + * \param[in] deviceNode The media device node path
> + *
> + * If a media device supports multi-context operations and advertise
> it through
> + * the presence of the MEDIA_DEVICE_FL_CONTEXT flag in the
> + * media_device_info.flags field an instance of SharedMediaDevice
> will be
> + * created and returned. If instead the media device doesn's support
> + * multi-context operations an instance of the MediaDevice class is
> created and
> + * returned.
> + *
> + * \return A unique_ptr<> that wraps an instance of
> SharedMediaDevice or
> + * MediaDevice
> + */
> +std::unique_ptr<MediaDevice>
> +MediaDeviceFactory::createMediaDevice(const std::string &deviceNode)
> +{
> +	/*
> +	 * Inspect the media device info flags to decide which class
> to
> +	 * instantiate.
> +	 */
> +	auto fd = UniqueFD(::open(deviceNode.c_str(), O_RDWR |
> O_CLOEXEC));
> +	if (!fd.isValid())
> +		return {};
> +
> +	struct media_device_info info = {};
> +	int ret = ioctl(fd.get(), MEDIA_IOC_DEVICE_INFO, &info);
> +	if (ret) {
> +		LOG(MediaDevice, Error)
> +			<< "Failed to get media device info " <<
> strerror(-ret);
> +		return {};
> +	}
> +
> +	if (info.flags & MEDIA_DEVICE_FL_CONTEXT)
> +		return std::unique_ptr<SharedMediaDevice>
> +			(new SharedMediaDevice(deviceNode));
> +
> +	return std::unique_ptr<MediaDevice>(new
> MediaDevice(deviceNode));
> +}
> +
>  /**
>   * \class MediaDevice
>   * \brief The MediaDevice represents a Media Controller device with
> its full
> @@ -40,6 +90,9 @@ LOG_DEFINE_CATEGORY(MediaDevice)
>   * created, and that association is kept for the lifetime of the
> MediaDevice
>   * instance.
>   *
> + * Instances of MediaDevice are created by the MediaDeviceFactory
> + * createMediaDevice() function and cannot be directly instantiated.
> + *
>   * The instance is created with an empty media graph. Before
> performing any
>   * other operation, it must be populate by calling populate().
> Instances of
>   * MediaEntity, MediaPad and MediaLink are created to model the
> media graph,
> @@ -56,6 +109,30 @@ LOG_DEFINE_CATEGORY(MediaDevice)
>   * managers to claim media devices they support during enumeration.
>   */
>  
> +/**
> + * \class SharedMediaDevice
> + * \brief The SharedMediaDevice represents a Media Controller device
> that
> + * supports multi-context operations
> + *
> + * \sa MediaDevice
> + * \sa MediaDeviceFactory
> + *
> + * A SharedMediaDevice is associated with a media controller device
> node that
> + * supports multi-context operations. Compared to the MediaDevice
> base class a
> + * SharedMediaDevice allows multiple open() calls to happen and is
> never
> + * uniquely owned as each time the media device gets open a new
> context gets
> + * created. This means that the acquire() and release() operations
> are
> + * effectively nop (and as a consequence busy() always return
> false).
> + *
> + * Instances of MediaDevice are created by the MediaDeviceFactory
> + * createMediaDevice() function and cannot be directly instantiated.
> + *
> + * Similarly to the MediaDevice class, instances of
> SharedMediaDevice are
> + * created with an empty media graph and needs to be populated using
> the
> + * populate() function. Media entities enumerated in the graph can
> be accessed
> + * through the same functions as the ones provided by the
> MediaDevice class.
> + */
> +
>  /**
>   * \brief Construct a MediaDevice
>   * \param[in] deviceNode The media device node path
> @@ -68,6 +145,17 @@ MediaDevice::MediaDevice(const std::string
> &deviceNode)
>  {
>  }
>  
> +/**
> + * \brief Construct a SharedMediaDevice
> + * \param[in] deviceNode The media device node path
> + *
> + * \copydoc MediaDevice::MediaDevice
> + */
> +SharedMediaDevice::SharedMediaDevice(const std::string &deviceNode)
> +	: MediaDevice(deviceNode)
> +{
> +}
> +
>  MediaDevice::~MediaDevice()
>  {
>  	fd_.reset();
> @@ -113,6 +201,19 @@ bool MediaDevice::acquire()
>  	return true;
>  }
>  
> +/**
> + * \brief Acquiring a SharedMediaDevice is a nop
> + *
> + * A SharedMediaDevice is designed to be opened multiple times.
> Acquiring
> + * a SharedMediaDevice is effectively a nop.
> + *
> + * \return Always return true
> + */
> +bool SharedMediaDevice::acquire()
> +{
> +	return true;
> +}
> +
>  /**
>   * \brief Release a device previously claimed for exclusive use
>   * \sa acquire(), busy()
> @@ -123,6 +224,15 @@ void MediaDevice::release()
>  	acquired_ = false;
>  }
>  
> +/**
> + * \brief Releasing a SharedMediaDevice is a nop
> + *
> + * As acquiring a SharedMediaDevice is a nop, releasing it is a nop
> as well.
> + */
> +void SharedMediaDevice::release()
> +{
> +}
> +
>  /**
>   * \brief Lock the device to prevent it from being used by other
> instances of
>   * libcamera
> @@ -151,6 +261,19 @@ bool MediaDevice::lock()
>  	return true;
>  }
>  
> +/**
> + * \brief Locking a SharedMediaDevice is a nop
> + *
> + * As SharedMediaDevice is designed to be opened multiple times
> locking
> + * a SharedMediaDevice is effectively a nop.
> + *
> + * \return Always return true
> + */
> +bool SharedMediaDevice::lock()
> +{
> +	return true;
> +}
> +
>  /**
>   * \brief Unlock the device and free it for use for libcamera
> instances
>   *
> @@ -168,6 +291,15 @@ void MediaDevice::unlock()
>  	std::ignore = lockf(fd_.get(), F_ULOCK, 0);
>  }
>  
> +/**
> + * \brief Unlocking a SharedMediaDevice is a nop
> + *
> + * As locking a SharedMediaDevice is a nop, unlocking it is a nop as
> well.
> + */
> +void SharedMediaDevice::unlock()
> +{
> +}
> +
>  /**
>   * \fn MediaDevice::busy()
>   * \brief Check if a device is in use
> @@ -176,6 +308,13 @@ void MediaDevice::unlock()
>   * \sa acquire(), release()
>   */
>  
> +/**
> + * \fn SharedMediaDevice::busy()
> + * \brief As SharedMediaDevice is designed to be opened multiple
> times, for this
> + * reason it is never marked as busy
> + * \return Always returns false
> + */
> +
>  /**
>   * \brief Populate the MediaDevice with device information and media
> objects
>   *

Patch
diff mbox series

diff --git a/include/libcamera/internal/media_device.h b/include/libcamera/internal/media_device.h
index b3a48b98d64b019fb42c5224d29105e1d57a3e3c..ea0e9d66009933bbb9613d27423d2f951520c887 100644
--- a/include/libcamera/internal/media_device.h
+++ b/include/libcamera/internal/media_device.h
@@ -21,18 +21,24 @@ 
 
 namespace libcamera {
 
+class MediaDeviceFactory
+{
+public:
+	static std::unique_ptr<MediaDevice>
+	createMediaDevice(const std::string &deviceNode);
+};
+
 class MediaDevice : protected Loggable
 {
 public:
-	MediaDevice(const std::string &deviceNode);
-	~MediaDevice();
+	virtual ~MediaDevice();
 
-	bool acquire();
-	void release();
-	bool busy() const { return acquired_; }
+	virtual bool acquire();
+	virtual void release();
+	virtual bool busy() const { return acquired_; }
 
-	bool lock();
-	void unlock();
+	virtual bool lock();
+	virtual void unlock();
 
 	int populate();
 	bool isValid() const { return valid_; }
@@ -58,9 +64,12 @@  public:
 	std::vector<MediaEntity *> locateEntities(unsigned int function);
 
 protected:
+	MediaDevice(const std::string &deviceNode);
 	std::string logPrefix() const override;
 
 private:
+	friend class MediaDeviceFactory;
+
 	int open();
 	void close();
 
@@ -92,4 +101,20 @@  private:
 	std::vector<MediaEntity *> entities_;
 };
 
+class SharedMediaDevice : public MediaDevice
+{
+public:
+	bool acquire() override;
+	void release() override;
+	bool busy() const override { return false; }
+
+	bool lock() override;
+	void unlock() override;
+
+private:
+	friend class MediaDeviceFactory;
+
+	SharedMediaDevice(const std::string &deviceNode);
+};
+
 } /* namespace libcamera */
diff --git a/src/libcamera/device_enumerator.cpp b/src/libcamera/device_enumerator.cpp
index ae17862f676310ef568e5331106ed661e84b5130..e73bb0050843c5281986e8e05d2013102905d270 100644
--- a/src/libcamera/device_enumerator.cpp
+++ b/src/libcamera/device_enumerator.cpp
@@ -217,7 +217,13 @@  DeviceEnumerator::~DeviceEnumerator()
  */
 std::unique_ptr<MediaDevice> DeviceEnumerator::createDevice(const std::string &deviceNode)
 {
-	std::unique_ptr<MediaDevice> media = std::make_unique<MediaDevice>(deviceNode);
+	std::unique_ptr<MediaDevice> media = MediaDeviceFactory::createMediaDevice(deviceNode);
+	if (!media) {
+		LOG(DeviceEnumerator, Info)
+			<< "Unable to create a media device for " << deviceNode
+			<< ", skipping";
+		return nullptr;
+	}
 
 	int ret = media->populate();
 	if (ret < 0) {
diff --git a/src/libcamera/media_device.cpp b/src/libcamera/media_device.cpp
index 353f34a81eca600a4a35594d782d3dc5dd690bdf..70d073f964bde0236585de083baca4cd9c30d193 100644
--- a/src/libcamera/media_device.cpp
+++ b/src/libcamera/media_device.cpp
@@ -31,6 +31,56 @@  namespace libcamera {
 
 LOG_DEFINE_CATEGORY(MediaDevice)
 
+/**
+ * \class MediaDeviceFactory
+ * \brief Factory class that instantiates a MediaDevice or SharedMediaDevice
+ *
+ * The MediaDevice and SharedMediaDevice classes cannot be instantiated
+ * directly, but instead the MediaDeviceFactory shall be used to create
+ * an instance of these classes using the MediaDeviceFactory::createMediaDevice
+ * function.
+ */
+
+/**
+ * \brief Create an instance of the MediaDevice class hierarchy
+ * \param[in] deviceNode The media device node path
+ *
+ * If a media device supports multi-context operations and advertise it through
+ * the presence of the MEDIA_DEVICE_FL_CONTEXT flag in the
+ * media_device_info.flags field an instance of SharedMediaDevice will be
+ * created and returned. If instead the media device doesn's support
+ * multi-context operations an instance of the MediaDevice class is created and
+ * returned.
+ *
+ * \return A unique_ptr<> that wraps an instance of SharedMediaDevice or
+ * MediaDevice
+ */
+std::unique_ptr<MediaDevice>
+MediaDeviceFactory::createMediaDevice(const std::string &deviceNode)
+{
+	/*
+	 * Inspect the media device info flags to decide which class to
+	 * instantiate.
+	 */
+	auto fd = UniqueFD(::open(deviceNode.c_str(), O_RDWR | O_CLOEXEC));
+	if (!fd.isValid())
+		return {};
+
+	struct media_device_info info = {};
+	int ret = ioctl(fd.get(), MEDIA_IOC_DEVICE_INFO, &info);
+	if (ret) {
+		LOG(MediaDevice, Error)
+			<< "Failed to get media device info " << strerror(-ret);
+		return {};
+	}
+
+	if (info.flags & MEDIA_DEVICE_FL_CONTEXT)
+		return std::unique_ptr<SharedMediaDevice>
+			(new SharedMediaDevice(deviceNode));
+
+	return std::unique_ptr<MediaDevice>(new MediaDevice(deviceNode));
+}
+
 /**
  * \class MediaDevice
  * \brief The MediaDevice represents a Media Controller device with its full
@@ -40,6 +90,9 @@  LOG_DEFINE_CATEGORY(MediaDevice)
  * created, and that association is kept for the lifetime of the MediaDevice
  * instance.
  *
+ * Instances of MediaDevice are created by the MediaDeviceFactory
+ * createMediaDevice() function and cannot be directly instantiated.
+ *
  * The instance is created with an empty media graph. Before performing any
  * other operation, it must be populate by calling populate(). Instances of
  * MediaEntity, MediaPad and MediaLink are created to model the media graph,
@@ -56,6 +109,30 @@  LOG_DEFINE_CATEGORY(MediaDevice)
  * managers to claim media devices they support during enumeration.
  */
 
+/**
+ * \class SharedMediaDevice
+ * \brief The SharedMediaDevice represents a Media Controller device that
+ * supports multi-context operations
+ *
+ * \sa MediaDevice
+ * \sa MediaDeviceFactory
+ *
+ * A SharedMediaDevice is associated with a media controller device node that
+ * supports multi-context operations. Compared to the MediaDevice base class a
+ * SharedMediaDevice allows multiple open() calls to happen and is never
+ * uniquely owned as each time the media device gets open a new context gets
+ * created. This means that the acquire() and release() operations are
+ * effectively nop (and as a consequence busy() always return false).
+ *
+ * Instances of MediaDevice are created by the MediaDeviceFactory
+ * createMediaDevice() function and cannot be directly instantiated.
+ *
+ * Similarly to the MediaDevice class, instances of SharedMediaDevice are
+ * created with an empty media graph and needs to be populated using the
+ * populate() function. Media entities enumerated in the graph can be accessed
+ * through the same functions as the ones provided by the MediaDevice class.
+ */
+
 /**
  * \brief Construct a MediaDevice
  * \param[in] deviceNode The media device node path
@@ -68,6 +145,17 @@  MediaDevice::MediaDevice(const std::string &deviceNode)
 {
 }
 
+/**
+ * \brief Construct a SharedMediaDevice
+ * \param[in] deviceNode The media device node path
+ *
+ * \copydoc MediaDevice::MediaDevice
+ */
+SharedMediaDevice::SharedMediaDevice(const std::string &deviceNode)
+	: MediaDevice(deviceNode)
+{
+}
+
 MediaDevice::~MediaDevice()
 {
 	fd_.reset();
@@ -113,6 +201,19 @@  bool MediaDevice::acquire()
 	return true;
 }
 
+/**
+ * \brief Acquiring a SharedMediaDevice is a nop
+ *
+ * A SharedMediaDevice is designed to be opened multiple times. Acquiring
+ * a SharedMediaDevice is effectively a nop.
+ *
+ * \return Always return true
+ */
+bool SharedMediaDevice::acquire()
+{
+	return true;
+}
+
 /**
  * \brief Release a device previously claimed for exclusive use
  * \sa acquire(), busy()
@@ -123,6 +224,15 @@  void MediaDevice::release()
 	acquired_ = false;
 }
 
+/**
+ * \brief Releasing a SharedMediaDevice is a nop
+ *
+ * As acquiring a SharedMediaDevice is a nop, releasing it is a nop as well.
+ */
+void SharedMediaDevice::release()
+{
+}
+
 /**
  * \brief Lock the device to prevent it from being used by other instances of
  * libcamera
@@ -151,6 +261,19 @@  bool MediaDevice::lock()
 	return true;
 }
 
+/**
+ * \brief Locking a SharedMediaDevice is a nop
+ *
+ * As SharedMediaDevice is designed to be opened multiple times locking
+ * a SharedMediaDevice is effectively a nop.
+ *
+ * \return Always return true
+ */
+bool SharedMediaDevice::lock()
+{
+	return true;
+}
+
 /**
  * \brief Unlock the device and free it for use for libcamera instances
  *
@@ -168,6 +291,15 @@  void MediaDevice::unlock()
 	std::ignore = lockf(fd_.get(), F_ULOCK, 0);
 }
 
+/**
+ * \brief Unlocking a SharedMediaDevice is a nop
+ *
+ * As locking a SharedMediaDevice is a nop, unlocking it is a nop as well.
+ */
+void SharedMediaDevice::unlock()
+{
+}
+
 /**
  * \fn MediaDevice::busy()
  * \brief Check if a device is in use
@@ -176,6 +308,13 @@  void MediaDevice::unlock()
  * \sa acquire(), release()
  */
 
+/**
+ * \fn SharedMediaDevice::busy()
+ * \brief As SharedMediaDevice is designed to be opened multiple times, for this
+ * reason it is never marked as busy
+ * \return Always returns false
+ */
+
 /**
  * \brief Populate the MediaDevice with device information and media objects
  *