[libcamera-devel,v2,02/13] libcamera: camera_manager: Move private data members to private implementation

Message ID 20200122205723.8865-3-laurent.pinchart@ideasonboard.com
State Accepted
Headers show
Series
  • Initial libcamera threading model
Related show

Commit Message

Laurent Pinchart Jan. 22, 2020, 8:57 p.m. UTC
Use the d-pointer idiom ([1], [2]) to hide the private data members from
the CameraManager class interface. This will ease maintaining ABI
compatibility, and prepares for the implementation of the CameraManager
class threading model.

[1] https://wiki.qt.io/D-Pointer
[2] https://en.cppreference.com/w/cpp/language/pimpl

libcamera: camera_manager: Run the camera manager in a thread:q

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 Documentation/Doxyfile.in          |   1 +
 include/libcamera/camera_manager.h |  13 +-
 src/libcamera/camera_manager.cpp   | 218 ++++++++++++++++++-----------
 3 files changed, 143 insertions(+), 89 deletions(-)

Comments

Kieran Bingham Jan. 23, 2020, 2:35 p.m. UTC | #1
Hi Laurent,

On 22/01/2020 20:57, Laurent Pinchart wrote:
> Use the d-pointer idiom ([1], [2]) to hide the private data members from
> the CameraManager class interface. This will ease maintaining ABI
> compatibility, and prepares for the implementation of the CameraManager
> class threading model.
> 
> [1] https://wiki.qt.io/D-Pointer
> [2] https://en.cppreference.com/w/cpp/language/pimpl
> 
> libcamera: camera_manager: Run the camera manager in a thread:q

Trying to escape vim? ":q" ?
Perhaps this was a squashed commit or a fixup?

Otherwise, It's certainly interesting moving things to an internal
private class. The public objects then just become lightweight wrappers.

Seems like it might end up generating a lot of boilerplate wrapping
around classes, but getting some ABI stability will be a good thing...


It looks like the actual code is only moved, so there's no functional
changes intended here (except of course for the allocations being
different and wrapped).

So a generalised:

Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>



> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  Documentation/Doxyfile.in          |   1 +
>  include/libcamera/camera_manager.h |  13 +-
>  src/libcamera/camera_manager.cpp   | 218 ++++++++++++++++++-----------
>  3 files changed, 143 insertions(+), 89 deletions(-)
> 
> diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in
> index 8e6fbdbb92b6..1f746393568a 100644
> --- a/Documentation/Doxyfile.in
> +++ b/Documentation/Doxyfile.in
> @@ -877,6 +877,7 @@ EXCLUDE_SYMBOLS        = libcamera::BoundMethodArgs \
>                           libcamera::BoundMethodPackBase \
>                           libcamera::BoundMethodStatic \
>                           libcamera::SignalBase \
> +                         libcamera::*::Private \

I think I saw another patch from you that used ::details for something
similar to this.

Will ::Private be the preferred naming? Or will both end up being used?

>                           std::*
>  
>  # The EXAMPLE_PATH tag can be used to specify one or more files or directories
> diff --git a/include/libcamera/camera_manager.h b/include/libcamera/camera_manager.h
> index 094197668698..068afd58762f 100644
> --- a/include/libcamera/camera_manager.h
> +++ b/include/libcamera/camera_manager.h
> @@ -7,7 +7,6 @@
>  #ifndef __LIBCAMERA_CAMERA_MANAGER_H__
>  #define __LIBCAMERA_CAMERA_MANAGER_H__
>  
> -#include <map>
>  #include <memory>
>  #include <string>
>  #include <sys/types.h>
> @@ -18,9 +17,7 @@
>  namespace libcamera {
>  
>  class Camera;
> -class DeviceEnumerator;
>  class EventDispatcher;
> -class PipelineHandler;
>  
>  class CameraManager : public Object
>  {
> @@ -33,7 +30,7 @@ public:
>  	int start();
>  	void stop();
>  
> -	const std::vector<std::shared_ptr<Camera>> &cameras() const { return cameras_; }
> +	const std::vector<std::shared_ptr<Camera>> &cameras() const;
>  	std::shared_ptr<Camera> get(const std::string &name);
>  	std::shared_ptr<Camera> get(dev_t devnum);
>  
> @@ -46,13 +43,11 @@ public:
>  	EventDispatcher *eventDispatcher();
>  
>  private:
> -	std::unique_ptr<DeviceEnumerator> enumerator_;
> -	std::vector<std::shared_ptr<PipelineHandler>> pipes_;
> -	std::vector<std::shared_ptr<Camera>> cameras_;
> -	std::map<dev_t, std::weak_ptr<Camera>> camerasByDevnum_;
> -
>  	static const std::string version_;
>  	static CameraManager *self_;
> +
> +	class Private;
> +	std::unique_ptr<Private> p_;
>  };
>  
>  } /* namespace libcamera */
> diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
> index a71caf8536bb..e0a07ec557d3 100644
> --- a/src/libcamera/camera_manager.cpp
> +++ b/src/libcamera/camera_manager.cpp
> @@ -7,6 +7,8 @@
>  
>  #include <libcamera/camera_manager.h>
>  
> +#include <map>
> +
>  #include <libcamera/camera.h>
>  #include <libcamera/event_dispatcher.h>
>  
> @@ -26,6 +28,124 @@ namespace libcamera {
>  
>  LOG_DEFINE_CATEGORY(Camera)
>  
> +class CameraManager::Private
> +{
> +public:
> +	Private(CameraManager *cm);
> +
> +	int start();
> +	void stop();
> +
> +	void addCamera(std::shared_ptr<Camera> &camera, dev_t devnum);
> +	void removeCamera(Camera *camera);
> +
> +	std::vector<std::shared_ptr<Camera>> cameras_;
> +	std::map<dev_t, std::weak_ptr<Camera>> camerasByDevnum_;
> +
> +private:
> +	CameraManager *cm_;
> +
> +	std::vector<std::shared_ptr<PipelineHandler>> pipes_;
> +	std::unique_ptr<DeviceEnumerator> enumerator_;
> +};
> +
> +CameraManager::Private::Private(CameraManager *cm)

A private private? :-D (I see it's the constructor it just reads oddly
because of the namespacing.)



> +	: cm_(cm)
> +{
> +}
> +
> +int CameraManager::Private::start()
> +{
> +	enumerator_ = DeviceEnumerator::create();
> +	if (!enumerator_ || enumerator_->enumerate())
> +		return -ENODEV;
> +
> +	/*
> +	 * TODO: Try to read handlers and order from configuration
> +	 * file and only fallback on all handlers if there is no
> +	 * configuration file.
> +	 */
> +	std::vector<PipelineHandlerFactory *> &factories = PipelineHandlerFactory::factories();
> +
> +	for (PipelineHandlerFactory *factory : factories) {
> +		/*
> +		 * Try each pipeline handler until it exhaust
> +		 * all pipelines it can provide.
> +		 */
> +		while (1) {
> +			std::shared_ptr<PipelineHandler> pipe = factory->create(cm_);
> +			if (!pipe->match(enumerator_.get()))
> +				break;
> +
> +			LOG(Camera, Debug)
> +				<< "Pipeline handler \"" << factory->name()
> +				<< "\" matched";
> +			pipes_.push_back(std::move(pipe));
> +		}
> +	}
> +
> +	/* TODO: register hot-plug callback here */
> +
> +	return 0;
> +}
> +
> +void CameraManager::Private::stop()
> +{
> +	/* TODO: unregister hot-plug callback here */
> +
> +	/*
> +	 * Release all references to cameras and pipeline handlers to ensure
> +	 * they all get destroyed before the device enumerator deletes the
> +	 * media devices.
> +	 */
> +	pipes_.clear();
> +	cameras_.clear();
> +
> +	enumerator_.reset(nullptr);
> +}
> +
> +void CameraManager::Private::addCamera(std::shared_ptr<Camera> &camera,
> +				       dev_t devnum)
> +{
> +	for (std::shared_ptr<Camera> c : cameras_) {
> +		if (c->name() == camera->name()) {
> +			LOG(Camera, Warning)
> +				<< "Registering camera with duplicate name '"
> +				<< camera->name() << "'";
> +			break;
> +		}
> +	}
> +
> +	cameras_.push_back(std::move(camera));
> +
> +	if (devnum) {
> +		unsigned int index = cameras_.size() - 1;
> +		camerasByDevnum_[devnum] = cameras_[index];
> +	}
> +}
> +
> +void CameraManager::Private::removeCamera(Camera *camera)
> +{
> +	auto iter = std::find_if(cameras_.begin(), cameras_.end(),
> +				 [camera](std::shared_ptr<Camera> &c) {
> +					 return c.get() == camera;
> +				 });
> +	if (iter == cameras_.end())
> +		return;
> +
> +	LOG(Camera, Debug)
> +		<< "Unregistering camera '" << camera->name() << "'";
> +
> +	auto iter_d = std::find_if(camerasByDevnum_.begin(), camerasByDevnum_.end(),
> +				   [camera](const std::pair<dev_t, std::weak_ptr<Camera>> &p) {
> +					   return p.second.lock().get() == camera;
> +				   });
> +	if (iter_d != camerasByDevnum_.end())
> +		camerasByDevnum_.erase(iter_d);
> +
> +	cameras_.erase(iter);
> +}
> +
>  /**
>   * \class CameraManager
>   * \brief Provide access and manage all cameras in the system
> @@ -62,7 +182,7 @@ LOG_DEFINE_CATEGORY(Camera)
>  CameraManager *CameraManager::self_ = nullptr;
>  
>  CameraManager::CameraManager()
> -	: enumerator_(nullptr)
> +	: p_(new CameraManager::Private(this))

The std::unique_ptr<> will take care of free-ing this allocation right?

>  {
>  	if (self_)
>  		LOG(Camera, Fatal)
> @@ -73,6 +193,8 @@ CameraManager::CameraManager()
>  
>  CameraManager::~CameraManager()
>  {
> +	stop();
> +
>  	self_ = nullptr;
>  }
>  
> @@ -88,42 +210,14 @@ CameraManager::~CameraManager()
>   */
>  int CameraManager::start()
>  {
> -	if (enumerator_)
> -		return -EBUSY;
> -
>  	LOG(Camera, Info) << "libcamera " << version_;
>  
> -	enumerator_ = DeviceEnumerator::create();
> -	if (!enumerator_ || enumerator_->enumerate())
> -		return -ENODEV;
> -
> -	/*
> -	 * TODO: Try to read handlers and order from configuration
> -	 * file and only fallback on all handlers if there is no
> -	 * configuration file.
> -	 */
> -	std::vector<PipelineHandlerFactory *> &factories = PipelineHandlerFactory::factories();
> +	int ret = p_->start();
> +	if (ret)
> +		LOG(Camera, Error) << "Failed to start camera manager: "
> +				   << strerror(-ret);
>  
> -	for (PipelineHandlerFactory *factory : factories) {
> -		/*
> -		 * Try each pipeline handler until it exhaust
> -		 * all pipelines it can provide.
> -		 */
> -		while (1) {
> -			std::shared_ptr<PipelineHandler> pipe = factory->create(this);
> -			if (!pipe->match(enumerator_.get()))
> -				break;
> -
> -			LOG(Camera, Debug)
> -				<< "Pipeline handler \"" << factory->name()
> -				<< "\" matched";
> -			pipes_.push_back(std::move(pipe));
> -		}
> -	}
> -
> -	/* TODO: register hot-plug callback here */
> -
> -	return 0;
> +	return ret;
>  }
>  
>  /**
> @@ -138,17 +232,7 @@ int CameraManager::start()
>   */
>  void CameraManager::stop()
>  {
> -	/* TODO: unregister hot-plug callback here */
> -
> -	/*
> -	 * Release all references to cameras and pipeline handlers to ensure
> -	 * they all get destroyed before the device enumerator deletes the
> -	 * media devices.
> -	 */
> -	pipes_.clear();
> -	cameras_.clear();
> -
> -	enumerator_.reset(nullptr);
> +	p_->stop();
>  }
>  
>  /**
> @@ -160,6 +244,10 @@ void CameraManager::stop()
>   *
>   * \return List of all available cameras
>   */
> +const std::vector<std::shared_ptr<Camera>> &CameraManager::cameras() const
> +{
> +	return p_->cameras_;
> +}
>  
>  /**
>   * \brief Get a camera based on name
> @@ -172,7 +260,7 @@ void CameraManager::stop()
>   */
>  std::shared_ptr<Camera> CameraManager::get(const std::string &name)
>  {
> -	for (std::shared_ptr<Camera> camera : cameras_) {
> +	for (std::shared_ptr<Camera> camera : p_->cameras_) {
>  		if (camera->name() == name)
>  			return camera;
>  	}
> @@ -196,8 +284,8 @@ std::shared_ptr<Camera> CameraManager::get(const std::string &name)
>   */
>  std::shared_ptr<Camera> CameraManager::get(dev_t devnum)
>  {
> -	auto iter = camerasByDevnum_.find(devnum);
> -	if (iter == camerasByDevnum_.end())
> +	auto iter = p_->camerasByDevnum_.find(devnum);
> +	if (iter == p_->camerasByDevnum_.end())
>  		return nullptr;
>  
>  	return iter->second.lock();
> @@ -217,21 +305,8 @@ std::shared_ptr<Camera> CameraManager::get(dev_t devnum)
>   */
>  void CameraManager::addCamera(std::shared_ptr<Camera> camera, dev_t devnum)
>  {
> -	for (std::shared_ptr<Camera> c : cameras_) {
> -		if (c->name() == camera->name()) {
> -			LOG(Camera, Warning)
> -				<< "Registering camera with duplicate name '"
> -				<< camera->name() << "'";
> -			break;
> -		}
> -	}
>  
> -	cameras_.push_back(std::move(camera));
> -
> -	if (devnum) {
> -		unsigned int index = cameras_.size() - 1;
> -		camerasByDevnum_[devnum] = cameras_[index];
> -	}
> +	p_->addCamera(camera, devnum);
>  }
>  
>  /**
> @@ -244,24 +319,7 @@ void CameraManager::addCamera(std::shared_ptr<Camera> camera, dev_t devnum)
>   */
>  void CameraManager::removeCamera(Camera *camera)
>  {
> -	auto iter = std::find_if(cameras_.begin(), cameras_.end(),
> -				 [camera](std::shared_ptr<Camera> &c) {
> -					 return c.get() == camera;
> -				 });
> -	if (iter == cameras_.end())
> -		return;
> -
> -	LOG(Camera, Debug)
> -		<< "Unregistering camera '" << camera->name() << "'";
> -
> -	auto iter_d = std::find_if(camerasByDevnum_.begin(), camerasByDevnum_.end(),
> -				   [camera](const std::pair<dev_t, std::weak_ptr<Camera>> &p) {
> -					   return p.second.lock().get() == camera;
> -				   });
> -	if (iter_d != camerasByDevnum_.end())
> -		camerasByDevnum_.erase(iter_d);
> -
> -	cameras_.erase(iter);
> +	p_->removeCamera(camera);
>  }
>  
>  /**
>
Niklas Söderlund Jan. 23, 2020, 8:30 p.m. UTC | #2
Hi Laurent,

Thanks for your patch.

On 2020-01-22 22:57:12 +0200, Laurent Pinchart wrote:
> Use the d-pointer idiom ([1], [2]) to hide the private data members from
> the CameraManager class interface. This will ease maintaining ABI
> compatibility, and prepares for the implementation of the CameraManager
> class threading model.
> 
> [1] https://wiki.qt.io/D-Pointer
> [2] https://en.cppreference.com/w/cpp/language/pimpl
> 
> libcamera: camera_manager: Run the camera manager in a thread:q

As Kieran points out I assume this line is not meant to be here :-)

> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

With that fixed,

Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>

> ---
>  Documentation/Doxyfile.in          |   1 +
>  include/libcamera/camera_manager.h |  13 +-
>  src/libcamera/camera_manager.cpp   | 218 ++++++++++++++++++-----------
>  3 files changed, 143 insertions(+), 89 deletions(-)
> 
> diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in
> index 8e6fbdbb92b6..1f746393568a 100644
> --- a/Documentation/Doxyfile.in
> +++ b/Documentation/Doxyfile.in
> @@ -877,6 +877,7 @@ EXCLUDE_SYMBOLS        = libcamera::BoundMethodArgs \
>                           libcamera::BoundMethodPackBase \
>                           libcamera::BoundMethodStatic \
>                           libcamera::SignalBase \
> +                         libcamera::*::Private \
>                           std::*
>  
>  # The EXAMPLE_PATH tag can be used to specify one or more files or directories
> diff --git a/include/libcamera/camera_manager.h b/include/libcamera/camera_manager.h
> index 094197668698..068afd58762f 100644
> --- a/include/libcamera/camera_manager.h
> +++ b/include/libcamera/camera_manager.h
> @@ -7,7 +7,6 @@
>  #ifndef __LIBCAMERA_CAMERA_MANAGER_H__
>  #define __LIBCAMERA_CAMERA_MANAGER_H__
>  
> -#include <map>
>  #include <memory>
>  #include <string>
>  #include <sys/types.h>
> @@ -18,9 +17,7 @@
>  namespace libcamera {
>  
>  class Camera;
> -class DeviceEnumerator;
>  class EventDispatcher;
> -class PipelineHandler;
>  
>  class CameraManager : public Object
>  {
> @@ -33,7 +30,7 @@ public:
>  	int start();
>  	void stop();
>  
> -	const std::vector<std::shared_ptr<Camera>> &cameras() const { return cameras_; }
> +	const std::vector<std::shared_ptr<Camera>> &cameras() const;
>  	std::shared_ptr<Camera> get(const std::string &name);
>  	std::shared_ptr<Camera> get(dev_t devnum);
>  
> @@ -46,13 +43,11 @@ public:
>  	EventDispatcher *eventDispatcher();
>  
>  private:
> -	std::unique_ptr<DeviceEnumerator> enumerator_;
> -	std::vector<std::shared_ptr<PipelineHandler>> pipes_;
> -	std::vector<std::shared_ptr<Camera>> cameras_;
> -	std::map<dev_t, std::weak_ptr<Camera>> camerasByDevnum_;
> -
>  	static const std::string version_;
>  	static CameraManager *self_;
> +
> +	class Private;
> +	std::unique_ptr<Private> p_;
>  };
>  
>  } /* namespace libcamera */
> diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
> index a71caf8536bb..e0a07ec557d3 100644
> --- a/src/libcamera/camera_manager.cpp
> +++ b/src/libcamera/camera_manager.cpp
> @@ -7,6 +7,8 @@
>  
>  #include <libcamera/camera_manager.h>
>  
> +#include <map>
> +
>  #include <libcamera/camera.h>
>  #include <libcamera/event_dispatcher.h>
>  
> @@ -26,6 +28,124 @@ namespace libcamera {
>  
>  LOG_DEFINE_CATEGORY(Camera)
>  
> +class CameraManager::Private
> +{
> +public:
> +	Private(CameraManager *cm);
> +
> +	int start();
> +	void stop();
> +
> +	void addCamera(std::shared_ptr<Camera> &camera, dev_t devnum);
> +	void removeCamera(Camera *camera);
> +
> +	std::vector<std::shared_ptr<Camera>> cameras_;
> +	std::map<dev_t, std::weak_ptr<Camera>> camerasByDevnum_;
> +
> +private:
> +	CameraManager *cm_;
> +
> +	std::vector<std::shared_ptr<PipelineHandler>> pipes_;
> +	std::unique_ptr<DeviceEnumerator> enumerator_;
> +};
> +
> +CameraManager::Private::Private(CameraManager *cm)
> +	: cm_(cm)
> +{
> +}
> +
> +int CameraManager::Private::start()
> +{
> +	enumerator_ = DeviceEnumerator::create();
> +	if (!enumerator_ || enumerator_->enumerate())
> +		return -ENODEV;
> +
> +	/*
> +	 * TODO: Try to read handlers and order from configuration
> +	 * file and only fallback on all handlers if there is no
> +	 * configuration file.
> +	 */
> +	std::vector<PipelineHandlerFactory *> &factories = PipelineHandlerFactory::factories();
> +
> +	for (PipelineHandlerFactory *factory : factories) {
> +		/*
> +		 * Try each pipeline handler until it exhaust
> +		 * all pipelines it can provide.
> +		 */
> +		while (1) {
> +			std::shared_ptr<PipelineHandler> pipe = factory->create(cm_);
> +			if (!pipe->match(enumerator_.get()))
> +				break;
> +
> +			LOG(Camera, Debug)
> +				<< "Pipeline handler \"" << factory->name()
> +				<< "\" matched";
> +			pipes_.push_back(std::move(pipe));
> +		}
> +	}
> +
> +	/* TODO: register hot-plug callback here */
> +
> +	return 0;
> +}
> +
> +void CameraManager::Private::stop()
> +{
> +	/* TODO: unregister hot-plug callback here */
> +
> +	/*
> +	 * Release all references to cameras and pipeline handlers to ensure
> +	 * they all get destroyed before the device enumerator deletes the
> +	 * media devices.
> +	 */
> +	pipes_.clear();
> +	cameras_.clear();
> +
> +	enumerator_.reset(nullptr);
> +}
> +
> +void CameraManager::Private::addCamera(std::shared_ptr<Camera> &camera,
> +				       dev_t devnum)
> +{
> +	for (std::shared_ptr<Camera> c : cameras_) {
> +		if (c->name() == camera->name()) {
> +			LOG(Camera, Warning)
> +				<< "Registering camera with duplicate name '"
> +				<< camera->name() << "'";
> +			break;
> +		}
> +	}
> +
> +	cameras_.push_back(std::move(camera));
> +
> +	if (devnum) {
> +		unsigned int index = cameras_.size() - 1;
> +		camerasByDevnum_[devnum] = cameras_[index];
> +	}
> +}
> +
> +void CameraManager::Private::removeCamera(Camera *camera)
> +{
> +	auto iter = std::find_if(cameras_.begin(), cameras_.end(),
> +				 [camera](std::shared_ptr<Camera> &c) {
> +					 return c.get() == camera;
> +				 });
> +	if (iter == cameras_.end())
> +		return;
> +
> +	LOG(Camera, Debug)
> +		<< "Unregistering camera '" << camera->name() << "'";
> +
> +	auto iter_d = std::find_if(camerasByDevnum_.begin(), camerasByDevnum_.end(),
> +				   [camera](const std::pair<dev_t, std::weak_ptr<Camera>> &p) {
> +					   return p.second.lock().get() == camera;
> +				   });
> +	if (iter_d != camerasByDevnum_.end())
> +		camerasByDevnum_.erase(iter_d);
> +
> +	cameras_.erase(iter);
> +}
> +
>  /**
>   * \class CameraManager
>   * \brief Provide access and manage all cameras in the system
> @@ -62,7 +182,7 @@ LOG_DEFINE_CATEGORY(Camera)
>  CameraManager *CameraManager::self_ = nullptr;
>  
>  CameraManager::CameraManager()
> -	: enumerator_(nullptr)
> +	: p_(new CameraManager::Private(this))
>  {
>  	if (self_)
>  		LOG(Camera, Fatal)
> @@ -73,6 +193,8 @@ CameraManager::CameraManager()
>  
>  CameraManager::~CameraManager()
>  {
> +	stop();
> +
>  	self_ = nullptr;
>  }
>  
> @@ -88,42 +210,14 @@ CameraManager::~CameraManager()
>   */
>  int CameraManager::start()
>  {
> -	if (enumerator_)
> -		return -EBUSY;
> -
>  	LOG(Camera, Info) << "libcamera " << version_;
>  
> -	enumerator_ = DeviceEnumerator::create();
> -	if (!enumerator_ || enumerator_->enumerate())
> -		return -ENODEV;
> -
> -	/*
> -	 * TODO: Try to read handlers and order from configuration
> -	 * file and only fallback on all handlers if there is no
> -	 * configuration file.
> -	 */
> -	std::vector<PipelineHandlerFactory *> &factories = PipelineHandlerFactory::factories();
> +	int ret = p_->start();
> +	if (ret)
> +		LOG(Camera, Error) << "Failed to start camera manager: "
> +				   << strerror(-ret);
>  
> -	for (PipelineHandlerFactory *factory : factories) {
> -		/*
> -		 * Try each pipeline handler until it exhaust
> -		 * all pipelines it can provide.
> -		 */
> -		while (1) {
> -			std::shared_ptr<PipelineHandler> pipe = factory->create(this);
> -			if (!pipe->match(enumerator_.get()))
> -				break;
> -
> -			LOG(Camera, Debug)
> -				<< "Pipeline handler \"" << factory->name()
> -				<< "\" matched";
> -			pipes_.push_back(std::move(pipe));
> -		}
> -	}
> -
> -	/* TODO: register hot-plug callback here */
> -
> -	return 0;
> +	return ret;
>  }
>  
>  /**
> @@ -138,17 +232,7 @@ int CameraManager::start()
>   */
>  void CameraManager::stop()
>  {
> -	/* TODO: unregister hot-plug callback here */
> -
> -	/*
> -	 * Release all references to cameras and pipeline handlers to ensure
> -	 * they all get destroyed before the device enumerator deletes the
> -	 * media devices.
> -	 */
> -	pipes_.clear();
> -	cameras_.clear();
> -
> -	enumerator_.reset(nullptr);
> +	p_->stop();
>  }
>  
>  /**
> @@ -160,6 +244,10 @@ void CameraManager::stop()
>   *
>   * \return List of all available cameras
>   */
> +const std::vector<std::shared_ptr<Camera>> &CameraManager::cameras() const
> +{
> +	return p_->cameras_;
> +}
>  
>  /**
>   * \brief Get a camera based on name
> @@ -172,7 +260,7 @@ void CameraManager::stop()
>   */
>  std::shared_ptr<Camera> CameraManager::get(const std::string &name)
>  {
> -	for (std::shared_ptr<Camera> camera : cameras_) {
> +	for (std::shared_ptr<Camera> camera : p_->cameras_) {
>  		if (camera->name() == name)
>  			return camera;
>  	}
> @@ -196,8 +284,8 @@ std::shared_ptr<Camera> CameraManager::get(const std::string &name)
>   */
>  std::shared_ptr<Camera> CameraManager::get(dev_t devnum)
>  {
> -	auto iter = camerasByDevnum_.find(devnum);
> -	if (iter == camerasByDevnum_.end())
> +	auto iter = p_->camerasByDevnum_.find(devnum);
> +	if (iter == p_->camerasByDevnum_.end())
>  		return nullptr;
>  
>  	return iter->second.lock();
> @@ -217,21 +305,8 @@ std::shared_ptr<Camera> CameraManager::get(dev_t devnum)
>   */
>  void CameraManager::addCamera(std::shared_ptr<Camera> camera, dev_t devnum)
>  {
> -	for (std::shared_ptr<Camera> c : cameras_) {
> -		if (c->name() == camera->name()) {
> -			LOG(Camera, Warning)
> -				<< "Registering camera with duplicate name '"
> -				<< camera->name() << "'";
> -			break;
> -		}
> -	}
>  
> -	cameras_.push_back(std::move(camera));
> -
> -	if (devnum) {
> -		unsigned int index = cameras_.size() - 1;
> -		camerasByDevnum_[devnum] = cameras_[index];
> -	}
> +	p_->addCamera(camera, devnum);
>  }
>  
>  /**
> @@ -244,24 +319,7 @@ void CameraManager::addCamera(std::shared_ptr<Camera> camera, dev_t devnum)
>   */
>  void CameraManager::removeCamera(Camera *camera)
>  {
> -	auto iter = std::find_if(cameras_.begin(), cameras_.end(),
> -				 [camera](std::shared_ptr<Camera> &c) {
> -					 return c.get() == camera;
> -				 });
> -	if (iter == cameras_.end())
> -		return;
> -
> -	LOG(Camera, Debug)
> -		<< "Unregistering camera '" << camera->name() << "'";
> -
> -	auto iter_d = std::find_if(camerasByDevnum_.begin(), camerasByDevnum_.end(),
> -				   [camera](const std::pair<dev_t, std::weak_ptr<Camera>> &p) {
> -					   return p.second.lock().get() == camera;
> -				   });
> -	if (iter_d != camerasByDevnum_.end())
> -		camerasByDevnum_.erase(iter_d);
> -
> -	cameras_.erase(iter);
> +	p_->removeCamera(camera);
>  }
>  
>  /**
> -- 
> Regards,
> 
> Laurent Pinchart
> 
> _______________________________________________
> libcamera-devel mailing list
> libcamera-devel@lists.libcamera.org
> https://lists.libcamera.org/listinfo/libcamera-devel
Laurent Pinchart Jan. 23, 2020, 8:35 p.m. UTC | #3
Hi Kieran,

On Thu, Jan 23, 2020 at 02:35:54PM +0000, Kieran Bingham wrote:
> On 22/01/2020 20:57, Laurent Pinchart wrote:
> > Use the d-pointer idiom ([1], [2]) to hide the private data members from
> > the CameraManager class interface. This will ease maintaining ABI
> > compatibility, and prepares for the implementation of the CameraManager
> > class threading model.
> > 
> > [1] https://wiki.qt.io/D-Pointer
> > [2] https://en.cppreference.com/w/cpp/language/pimpl
> > 
> > libcamera: camera_manager: Run the camera manager in a thread:q
> 
> Trying to escape vim? ":q" ?

Oops :-)

> Perhaps this was a squashed commit or a fixup?
> 
> Otherwise, It's certainly interesting moving things to an internal
> private class. The public objects then just become lightweight wrappers.

In the CameraManager case I need an internal class inheriting from
Thread for a subsequent patch in this series, so it makes sense to use
it to hide the details from the public API and help with ABI stability.

> Seems like it might end up generating a lot of boilerplate wrapping
> around classes, but getting some ABI stability will be a good thing...
> 
> It looks like the actual code is only moved, so there's no functional
> changes intended here (except of course for the allocations being
> different and wrapped).

Correct.

> So a generalised:
> 
> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> 
> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> > ---
> >  Documentation/Doxyfile.in          |   1 +
> >  include/libcamera/camera_manager.h |  13 +-
> >  src/libcamera/camera_manager.cpp   | 218 ++++++++++++++++++-----------
> >  3 files changed, 143 insertions(+), 89 deletions(-)
> > 
> > diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in
> > index 8e6fbdbb92b6..1f746393568a 100644
> > --- a/Documentation/Doxyfile.in
> > +++ b/Documentation/Doxyfile.in
> > @@ -877,6 +877,7 @@ EXCLUDE_SYMBOLS        = libcamera::BoundMethodArgs \
> >                           libcamera::BoundMethodPackBase \
> >                           libcamera::BoundMethodStatic \
> >                           libcamera::SignalBase \
> > +                         libcamera::*::Private \
> 
> I think I saw another patch from you that used ::details for something
> similar to this.
> 
> Will ::Private be the preferred naming? Or will both end up being used?

Qt would have used CameraManagerPrivate. I like CameraManager::Private
because it's shorter in contexts where CameraManager is the active name
space. details:: come from C++ STL and is used to hide implementation
details from the std:: namespace. I'd rather use it for the same purpose
when needed (to support templates with helpers that need to be present
in public headers but are not part of the official API), but I'm open to
discussing this. In any case this is a class, so it would need to be
write CameraManager::Details, and in that case I think Private is a
better match (but I may be biased).

> >                           std::*
> >  
> >  # The EXAMPLE_PATH tag can be used to specify one or more files or directories
> > diff --git a/include/libcamera/camera_manager.h b/include/libcamera/camera_manager.h
> > index 094197668698..068afd58762f 100644
> > --- a/include/libcamera/camera_manager.h
> > +++ b/include/libcamera/camera_manager.h
> > @@ -7,7 +7,6 @@
> >  #ifndef __LIBCAMERA_CAMERA_MANAGER_H__
> >  #define __LIBCAMERA_CAMERA_MANAGER_H__
> >  
> > -#include <map>
> >  #include <memory>
> >  #include <string>
> >  #include <sys/types.h>
> > @@ -18,9 +17,7 @@
> >  namespace libcamera {
> >  
> >  class Camera;
> > -class DeviceEnumerator;
> >  class EventDispatcher;
> > -class PipelineHandler;
> >  
> >  class CameraManager : public Object
> >  {
> > @@ -33,7 +30,7 @@ public:
> >  	int start();
> >  	void stop();
> >  
> > -	const std::vector<std::shared_ptr<Camera>> &cameras() const { return cameras_; }
> > +	const std::vector<std::shared_ptr<Camera>> &cameras() const;
> >  	std::shared_ptr<Camera> get(const std::string &name);
> >  	std::shared_ptr<Camera> get(dev_t devnum);
> >  
> > @@ -46,13 +43,11 @@ public:
> >  	EventDispatcher *eventDispatcher();
> >  
> >  private:
> > -	std::unique_ptr<DeviceEnumerator> enumerator_;
> > -	std::vector<std::shared_ptr<PipelineHandler>> pipes_;
> > -	std::vector<std::shared_ptr<Camera>> cameras_;
> > -	std::map<dev_t, std::weak_ptr<Camera>> camerasByDevnum_;
> > -
> >  	static const std::string version_;
> >  	static CameraManager *self_;
> > +
> > +	class Private;
> > +	std::unique_ptr<Private> p_;
> >  };
> >  
> >  } /* namespace libcamera */
> > diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
> > index a71caf8536bb..e0a07ec557d3 100644
> > --- a/src/libcamera/camera_manager.cpp
> > +++ b/src/libcamera/camera_manager.cpp
> > @@ -7,6 +7,8 @@
> >  
> >  #include <libcamera/camera_manager.h>
> >  
> > +#include <map>
> > +
> >  #include <libcamera/camera.h>
> >  #include <libcamera/event_dispatcher.h>
> >  
> > @@ -26,6 +28,124 @@ namespace libcamera {
> >  
> >  LOG_DEFINE_CATEGORY(Camera)
> >  
> > +class CameraManager::Private
> > +{
> > +public:
> > +	Private(CameraManager *cm);
> > +
> > +	int start();
> > +	void stop();
> > +
> > +	void addCamera(std::shared_ptr<Camera> &camera, dev_t devnum);
> > +	void removeCamera(Camera *camera);
> > +
> > +	std::vector<std::shared_ptr<Camera>> cameras_;
> > +	std::map<dev_t, std::weak_ptr<Camera>> camerasByDevnum_;
> > +
> > +private:
> > +	CameraManager *cm_;
> > +
> > +	std::vector<std::shared_ptr<PipelineHandler>> pipes_;
> > +	std::unique_ptr<DeviceEnumerator> enumerator_;
> > +};
> > +
> > +CameraManager::Private::Private(CameraManager *cm)
> 
> A private private? :-D (I see it's the constructor it just reads oddly
> because of the namespacing.)

:-) I think CameraManagerPrivate::CameraManagerPrivate is a bit worse
(at the very least it's longer), but I could again be biased.

> > +	: cm_(cm)
> > +{
> > +}
> > +
> > +int CameraManager::Private::start()
> > +{
> > +	enumerator_ = DeviceEnumerator::create();
> > +	if (!enumerator_ || enumerator_->enumerate())
> > +		return -ENODEV;
> > +
> > +	/*
> > +	 * TODO: Try to read handlers and order from configuration
> > +	 * file and only fallback on all handlers if there is no
> > +	 * configuration file.
> > +	 */
> > +	std::vector<PipelineHandlerFactory *> &factories = PipelineHandlerFactory::factories();
> > +
> > +	for (PipelineHandlerFactory *factory : factories) {
> > +		/*
> > +		 * Try each pipeline handler until it exhaust
> > +		 * all pipelines it can provide.
> > +		 */
> > +		while (1) {
> > +			std::shared_ptr<PipelineHandler> pipe = factory->create(cm_);
> > +			if (!pipe->match(enumerator_.get()))
> > +				break;
> > +
> > +			LOG(Camera, Debug)
> > +				<< "Pipeline handler \"" << factory->name()
> > +				<< "\" matched";
> > +			pipes_.push_back(std::move(pipe));
> > +		}
> > +	}
> > +
> > +	/* TODO: register hot-plug callback here */
> > +
> > +	return 0;
> > +}
> > +
> > +void CameraManager::Private::stop()
> > +{
> > +	/* TODO: unregister hot-plug callback here */
> > +
> > +	/*
> > +	 * Release all references to cameras and pipeline handlers to ensure
> > +	 * they all get destroyed before the device enumerator deletes the
> > +	 * media devices.
> > +	 */
> > +	pipes_.clear();
> > +	cameras_.clear();
> > +
> > +	enumerator_.reset(nullptr);
> > +}
> > +
> > +void CameraManager::Private::addCamera(std::shared_ptr<Camera> &camera,
> > +				       dev_t devnum)
> > +{
> > +	for (std::shared_ptr<Camera> c : cameras_) {
> > +		if (c->name() == camera->name()) {
> > +			LOG(Camera, Warning)
> > +				<< "Registering camera with duplicate name '"
> > +				<< camera->name() << "'";
> > +			break;
> > +		}
> > +	}
> > +
> > +	cameras_.push_back(std::move(camera));
> > +
> > +	if (devnum) {
> > +		unsigned int index = cameras_.size() - 1;
> > +		camerasByDevnum_[devnum] = cameras_[index];
> > +	}
> > +}
> > +
> > +void CameraManager::Private::removeCamera(Camera *camera)
> > +{
> > +	auto iter = std::find_if(cameras_.begin(), cameras_.end(),
> > +				 [camera](std::shared_ptr<Camera> &c) {
> > +					 return c.get() == camera;
> > +				 });
> > +	if (iter == cameras_.end())
> > +		return;
> > +
> > +	LOG(Camera, Debug)
> > +		<< "Unregistering camera '" << camera->name() << "'";
> > +
> > +	auto iter_d = std::find_if(camerasByDevnum_.begin(), camerasByDevnum_.end(),
> > +				   [camera](const std::pair<dev_t, std::weak_ptr<Camera>> &p) {
> > +					   return p.second.lock().get() == camera;
> > +				   });
> > +	if (iter_d != camerasByDevnum_.end())
> > +		camerasByDevnum_.erase(iter_d);
> > +
> > +	cameras_.erase(iter);
> > +}
> > +
> >  /**
> >   * \class CameraManager
> >   * \brief Provide access and manage all cameras in the system
> > @@ -62,7 +182,7 @@ LOG_DEFINE_CATEGORY(Camera)
> >  CameraManager *CameraManager::self_ = nullptr;
> >  
> >  CameraManager::CameraManager()
> > -	: enumerator_(nullptr)
> > +	: p_(new CameraManager::Private(this))
> 
> The std::unique_ptr<> will take care of free-ing this allocation right?

Correct.

> >  {
> >  	if (self_)
> >  		LOG(Camera, Fatal)
> > @@ -73,6 +193,8 @@ CameraManager::CameraManager()
> >  
> >  CameraManager::~CameraManager()
> >  {
> > +	stop();
> > +
> >  	self_ = nullptr;
> >  }
> >  
> > @@ -88,42 +210,14 @@ CameraManager::~CameraManager()
> >   */
> >  int CameraManager::start()
> >  {
> > -	if (enumerator_)
> > -		return -EBUSY;
> > -
> >  	LOG(Camera, Info) << "libcamera " << version_;
> >  
> > -	enumerator_ = DeviceEnumerator::create();
> > -	if (!enumerator_ || enumerator_->enumerate())
> > -		return -ENODEV;
> > -
> > -	/*
> > -	 * TODO: Try to read handlers and order from configuration
> > -	 * file and only fallback on all handlers if there is no
> > -	 * configuration file.
> > -	 */
> > -	std::vector<PipelineHandlerFactory *> &factories = PipelineHandlerFactory::factories();
> > +	int ret = p_->start();
> > +	if (ret)
> > +		LOG(Camera, Error) << "Failed to start camera manager: "
> > +				   << strerror(-ret);
> >  
> > -	for (PipelineHandlerFactory *factory : factories) {
> > -		/*
> > -		 * Try each pipeline handler until it exhaust
> > -		 * all pipelines it can provide.
> > -		 */
> > -		while (1) {
> > -			std::shared_ptr<PipelineHandler> pipe = factory->create(this);
> > -			if (!pipe->match(enumerator_.get()))
> > -				break;
> > -
> > -			LOG(Camera, Debug)
> > -				<< "Pipeline handler \"" << factory->name()
> > -				<< "\" matched";
> > -			pipes_.push_back(std::move(pipe));
> > -		}
> > -	}
> > -
> > -	/* TODO: register hot-plug callback here */
> > -
> > -	return 0;
> > +	return ret;
> >  }
> >  
> >  /**
> > @@ -138,17 +232,7 @@ int CameraManager::start()
> >   */
> >  void CameraManager::stop()
> >  {
> > -	/* TODO: unregister hot-plug callback here */
> > -
> > -	/*
> > -	 * Release all references to cameras and pipeline handlers to ensure
> > -	 * they all get destroyed before the device enumerator deletes the
> > -	 * media devices.
> > -	 */
> > -	pipes_.clear();
> > -	cameras_.clear();
> > -
> > -	enumerator_.reset(nullptr);
> > +	p_->stop();
> >  }
> >  
> >  /**
> > @@ -160,6 +244,10 @@ void CameraManager::stop()
> >   *
> >   * \return List of all available cameras
> >   */
> > +const std::vector<std::shared_ptr<Camera>> &CameraManager::cameras() const
> > +{
> > +	return p_->cameras_;
> > +}
> >  
> >  /**
> >   * \brief Get a camera based on name
> > @@ -172,7 +260,7 @@ void CameraManager::stop()
> >   */
> >  std::shared_ptr<Camera> CameraManager::get(const std::string &name)
> >  {
> > -	for (std::shared_ptr<Camera> camera : cameras_) {
> > +	for (std::shared_ptr<Camera> camera : p_->cameras_) {
> >  		if (camera->name() == name)
> >  			return camera;
> >  	}
> > @@ -196,8 +284,8 @@ std::shared_ptr<Camera> CameraManager::get(const std::string &name)
> >   */
> >  std::shared_ptr<Camera> CameraManager::get(dev_t devnum)
> >  {
> > -	auto iter = camerasByDevnum_.find(devnum);
> > -	if (iter == camerasByDevnum_.end())
> > +	auto iter = p_->camerasByDevnum_.find(devnum);
> > +	if (iter == p_->camerasByDevnum_.end())
> >  		return nullptr;
> >  
> >  	return iter->second.lock();
> > @@ -217,21 +305,8 @@ std::shared_ptr<Camera> CameraManager::get(dev_t devnum)
> >   */
> >  void CameraManager::addCamera(std::shared_ptr<Camera> camera, dev_t devnum)
> >  {
> > -	for (std::shared_ptr<Camera> c : cameras_) {
> > -		if (c->name() == camera->name()) {
> > -			LOG(Camera, Warning)
> > -				<< "Registering camera with duplicate name '"
> > -				<< camera->name() << "'";
> > -			break;
> > -		}
> > -	}
> >  
> > -	cameras_.push_back(std::move(camera));
> > -
> > -	if (devnum) {
> > -		unsigned int index = cameras_.size() - 1;
> > -		camerasByDevnum_[devnum] = cameras_[index];
> > -	}
> > +	p_->addCamera(camera, devnum);
> >  }
> >  
> >  /**
> > @@ -244,24 +319,7 @@ void CameraManager::addCamera(std::shared_ptr<Camera> camera, dev_t devnum)
> >   */
> >  void CameraManager::removeCamera(Camera *camera)
> >  {
> > -	auto iter = std::find_if(cameras_.begin(), cameras_.end(),
> > -				 [camera](std::shared_ptr<Camera> &c) {
> > -					 return c.get() == camera;
> > -				 });
> > -	if (iter == cameras_.end())
> > -		return;
> > -
> > -	LOG(Camera, Debug)
> > -		<< "Unregistering camera '" << camera->name() << "'";
> > -
> > -	auto iter_d = std::find_if(camerasByDevnum_.begin(), camerasByDevnum_.end(),
> > -				   [camera](const std::pair<dev_t, std::weak_ptr<Camera>> &p) {
> > -					   return p.second.lock().get() == camera;
> > -				   });
> > -	if (iter_d != camerasByDevnum_.end())
> > -		camerasByDevnum_.erase(iter_d);
> > -
> > -	cameras_.erase(iter);
> > +	p_->removeCamera(camera);
> >  }
> >  
> >  /**

Patch

diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in
index 8e6fbdbb92b6..1f746393568a 100644
--- a/Documentation/Doxyfile.in
+++ b/Documentation/Doxyfile.in
@@ -877,6 +877,7 @@  EXCLUDE_SYMBOLS        = libcamera::BoundMethodArgs \
                          libcamera::BoundMethodPackBase \
                          libcamera::BoundMethodStatic \
                          libcamera::SignalBase \
+                         libcamera::*::Private \
                          std::*
 
 # The EXAMPLE_PATH tag can be used to specify one or more files or directories
diff --git a/include/libcamera/camera_manager.h b/include/libcamera/camera_manager.h
index 094197668698..068afd58762f 100644
--- a/include/libcamera/camera_manager.h
+++ b/include/libcamera/camera_manager.h
@@ -7,7 +7,6 @@ 
 #ifndef __LIBCAMERA_CAMERA_MANAGER_H__
 #define __LIBCAMERA_CAMERA_MANAGER_H__
 
-#include <map>
 #include <memory>
 #include <string>
 #include <sys/types.h>
@@ -18,9 +17,7 @@ 
 namespace libcamera {
 
 class Camera;
-class DeviceEnumerator;
 class EventDispatcher;
-class PipelineHandler;
 
 class CameraManager : public Object
 {
@@ -33,7 +30,7 @@  public:
 	int start();
 	void stop();
 
-	const std::vector<std::shared_ptr<Camera>> &cameras() const { return cameras_; }
+	const std::vector<std::shared_ptr<Camera>> &cameras() const;
 	std::shared_ptr<Camera> get(const std::string &name);
 	std::shared_ptr<Camera> get(dev_t devnum);
 
@@ -46,13 +43,11 @@  public:
 	EventDispatcher *eventDispatcher();
 
 private:
-	std::unique_ptr<DeviceEnumerator> enumerator_;
-	std::vector<std::shared_ptr<PipelineHandler>> pipes_;
-	std::vector<std::shared_ptr<Camera>> cameras_;
-	std::map<dev_t, std::weak_ptr<Camera>> camerasByDevnum_;
-
 	static const std::string version_;
 	static CameraManager *self_;
+
+	class Private;
+	std::unique_ptr<Private> p_;
 };
 
 } /* namespace libcamera */
diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
index a71caf8536bb..e0a07ec557d3 100644
--- a/src/libcamera/camera_manager.cpp
+++ b/src/libcamera/camera_manager.cpp
@@ -7,6 +7,8 @@ 
 
 #include <libcamera/camera_manager.h>
 
+#include <map>
+
 #include <libcamera/camera.h>
 #include <libcamera/event_dispatcher.h>
 
@@ -26,6 +28,124 @@  namespace libcamera {
 
 LOG_DEFINE_CATEGORY(Camera)
 
+class CameraManager::Private
+{
+public:
+	Private(CameraManager *cm);
+
+	int start();
+	void stop();
+
+	void addCamera(std::shared_ptr<Camera> &camera, dev_t devnum);
+	void removeCamera(Camera *camera);
+
+	std::vector<std::shared_ptr<Camera>> cameras_;
+	std::map<dev_t, std::weak_ptr<Camera>> camerasByDevnum_;
+
+private:
+	CameraManager *cm_;
+
+	std::vector<std::shared_ptr<PipelineHandler>> pipes_;
+	std::unique_ptr<DeviceEnumerator> enumerator_;
+};
+
+CameraManager::Private::Private(CameraManager *cm)
+	: cm_(cm)
+{
+}
+
+int CameraManager::Private::start()
+{
+	enumerator_ = DeviceEnumerator::create();
+	if (!enumerator_ || enumerator_->enumerate())
+		return -ENODEV;
+
+	/*
+	 * TODO: Try to read handlers and order from configuration
+	 * file and only fallback on all handlers if there is no
+	 * configuration file.
+	 */
+	std::vector<PipelineHandlerFactory *> &factories = PipelineHandlerFactory::factories();
+
+	for (PipelineHandlerFactory *factory : factories) {
+		/*
+		 * Try each pipeline handler until it exhaust
+		 * all pipelines it can provide.
+		 */
+		while (1) {
+			std::shared_ptr<PipelineHandler> pipe = factory->create(cm_);
+			if (!pipe->match(enumerator_.get()))
+				break;
+
+			LOG(Camera, Debug)
+				<< "Pipeline handler \"" << factory->name()
+				<< "\" matched";
+			pipes_.push_back(std::move(pipe));
+		}
+	}
+
+	/* TODO: register hot-plug callback here */
+
+	return 0;
+}
+
+void CameraManager::Private::stop()
+{
+	/* TODO: unregister hot-plug callback here */
+
+	/*
+	 * Release all references to cameras and pipeline handlers to ensure
+	 * they all get destroyed before the device enumerator deletes the
+	 * media devices.
+	 */
+	pipes_.clear();
+	cameras_.clear();
+
+	enumerator_.reset(nullptr);
+}
+
+void CameraManager::Private::addCamera(std::shared_ptr<Camera> &camera,
+				       dev_t devnum)
+{
+	for (std::shared_ptr<Camera> c : cameras_) {
+		if (c->name() == camera->name()) {
+			LOG(Camera, Warning)
+				<< "Registering camera with duplicate name '"
+				<< camera->name() << "'";
+			break;
+		}
+	}
+
+	cameras_.push_back(std::move(camera));
+
+	if (devnum) {
+		unsigned int index = cameras_.size() - 1;
+		camerasByDevnum_[devnum] = cameras_[index];
+	}
+}
+
+void CameraManager::Private::removeCamera(Camera *camera)
+{
+	auto iter = std::find_if(cameras_.begin(), cameras_.end(),
+				 [camera](std::shared_ptr<Camera> &c) {
+					 return c.get() == camera;
+				 });
+	if (iter == cameras_.end())
+		return;
+
+	LOG(Camera, Debug)
+		<< "Unregistering camera '" << camera->name() << "'";
+
+	auto iter_d = std::find_if(camerasByDevnum_.begin(), camerasByDevnum_.end(),
+				   [camera](const std::pair<dev_t, std::weak_ptr<Camera>> &p) {
+					   return p.second.lock().get() == camera;
+				   });
+	if (iter_d != camerasByDevnum_.end())
+		camerasByDevnum_.erase(iter_d);
+
+	cameras_.erase(iter);
+}
+
 /**
  * \class CameraManager
  * \brief Provide access and manage all cameras in the system
@@ -62,7 +182,7 @@  LOG_DEFINE_CATEGORY(Camera)
 CameraManager *CameraManager::self_ = nullptr;
 
 CameraManager::CameraManager()
-	: enumerator_(nullptr)
+	: p_(new CameraManager::Private(this))
 {
 	if (self_)
 		LOG(Camera, Fatal)
@@ -73,6 +193,8 @@  CameraManager::CameraManager()
 
 CameraManager::~CameraManager()
 {
+	stop();
+
 	self_ = nullptr;
 }
 
@@ -88,42 +210,14 @@  CameraManager::~CameraManager()
  */
 int CameraManager::start()
 {
-	if (enumerator_)
-		return -EBUSY;
-
 	LOG(Camera, Info) << "libcamera " << version_;
 
-	enumerator_ = DeviceEnumerator::create();
-	if (!enumerator_ || enumerator_->enumerate())
-		return -ENODEV;
-
-	/*
-	 * TODO: Try to read handlers and order from configuration
-	 * file and only fallback on all handlers if there is no
-	 * configuration file.
-	 */
-	std::vector<PipelineHandlerFactory *> &factories = PipelineHandlerFactory::factories();
+	int ret = p_->start();
+	if (ret)
+		LOG(Camera, Error) << "Failed to start camera manager: "
+				   << strerror(-ret);
 
-	for (PipelineHandlerFactory *factory : factories) {
-		/*
-		 * Try each pipeline handler until it exhaust
-		 * all pipelines it can provide.
-		 */
-		while (1) {
-			std::shared_ptr<PipelineHandler> pipe = factory->create(this);
-			if (!pipe->match(enumerator_.get()))
-				break;
-
-			LOG(Camera, Debug)
-				<< "Pipeline handler \"" << factory->name()
-				<< "\" matched";
-			pipes_.push_back(std::move(pipe));
-		}
-	}
-
-	/* TODO: register hot-plug callback here */
-
-	return 0;
+	return ret;
 }
 
 /**
@@ -138,17 +232,7 @@  int CameraManager::start()
  */
 void CameraManager::stop()
 {
-	/* TODO: unregister hot-plug callback here */
-
-	/*
-	 * Release all references to cameras and pipeline handlers to ensure
-	 * they all get destroyed before the device enumerator deletes the
-	 * media devices.
-	 */
-	pipes_.clear();
-	cameras_.clear();
-
-	enumerator_.reset(nullptr);
+	p_->stop();
 }
 
 /**
@@ -160,6 +244,10 @@  void CameraManager::stop()
  *
  * \return List of all available cameras
  */
+const std::vector<std::shared_ptr<Camera>> &CameraManager::cameras() const
+{
+	return p_->cameras_;
+}
 
 /**
  * \brief Get a camera based on name
@@ -172,7 +260,7 @@  void CameraManager::stop()
  */
 std::shared_ptr<Camera> CameraManager::get(const std::string &name)
 {
-	for (std::shared_ptr<Camera> camera : cameras_) {
+	for (std::shared_ptr<Camera> camera : p_->cameras_) {
 		if (camera->name() == name)
 			return camera;
 	}
@@ -196,8 +284,8 @@  std::shared_ptr<Camera> CameraManager::get(const std::string &name)
  */
 std::shared_ptr<Camera> CameraManager::get(dev_t devnum)
 {
-	auto iter = camerasByDevnum_.find(devnum);
-	if (iter == camerasByDevnum_.end())
+	auto iter = p_->camerasByDevnum_.find(devnum);
+	if (iter == p_->camerasByDevnum_.end())
 		return nullptr;
 
 	return iter->second.lock();
@@ -217,21 +305,8 @@  std::shared_ptr<Camera> CameraManager::get(dev_t devnum)
  */
 void CameraManager::addCamera(std::shared_ptr<Camera> camera, dev_t devnum)
 {
-	for (std::shared_ptr<Camera> c : cameras_) {
-		if (c->name() == camera->name()) {
-			LOG(Camera, Warning)
-				<< "Registering camera with duplicate name '"
-				<< camera->name() << "'";
-			break;
-		}
-	}
 
-	cameras_.push_back(std::move(camera));
-
-	if (devnum) {
-		unsigned int index = cameras_.size() - 1;
-		camerasByDevnum_[devnum] = cameras_[index];
-	}
+	p_->addCamera(camera, devnum);
 }
 
 /**
@@ -244,24 +319,7 @@  void CameraManager::addCamera(std::shared_ptr<Camera> camera, dev_t devnum)
  */
 void CameraManager::removeCamera(Camera *camera)
 {
-	auto iter = std::find_if(cameras_.begin(), cameras_.end(),
-				 [camera](std::shared_ptr<Camera> &c) {
-					 return c.get() == camera;
-				 });
-	if (iter == cameras_.end())
-		return;
-
-	LOG(Camera, Debug)
-		<< "Unregistering camera '" << camera->name() << "'";
-
-	auto iter_d = std::find_if(camerasByDevnum_.begin(), camerasByDevnum_.end(),
-				   [camera](const std::pair<dev_t, std::weak_ptr<Camera>> &p) {
-					   return p.second.lock().get() == camera;
-				   });
-	if (iter_d != camerasByDevnum_.end())
-		camerasByDevnum_.erase(iter_d);
-
-	cameras_.erase(iter);
+	p_->removeCamera(camera);
 }
 
 /**