libcamera: camera: add state machine to control access from applications

There is a need to better control the order of operations an application
performs on a camera for it to function correctly. Add a basic state
machine to ensure applications perform operations on the camera in good
order.

Internal to the Camera states are added; Available, Acquired,
Configured, Prepared and Running. Each state represents a higher state
of configuration of the camera ultimately leading to the highest state
where the camera is capturing frames. Each state supports a subset of
operations the application may perform.

Signed-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
This commit is contained in:
Niklas Söderlund 2019-02-26 01:37:53 +01:00
parent 132ce9c1cf
commit 77100a7578
2 changed files with 241 additions and 46 deletions

View file

@ -39,7 +39,7 @@ public:
Signal<Camera *> disconnected; Signal<Camera *> disconnected;
int acquire(); int acquire();
void release(); int release();
const std::set<Stream *> &streams() const; const std::set<Stream *> &streams() const;
std::map<Stream *, StreamConfiguration> std::map<Stream *, StreamConfiguration>
@ -47,7 +47,7 @@ public:
int configureStreams(std::map<Stream *, StreamConfiguration> &config); int configureStreams(std::map<Stream *, StreamConfiguration> &config);
int allocateBuffers(); int allocateBuffers();
void freeBuffers(); int freeBuffers();
Request *createRequest(); Request *createRequest();
int queueRequest(Request *request); int queueRequest(Request *request);
@ -56,20 +56,30 @@ public:
int stop(); int stop();
private: private:
enum State {
CameraAvailable,
CameraAcquired,
CameraConfigured,
CameraPrepared,
CameraRunning,
};
Camera(PipelineHandler *pipe, const std::string &name); Camera(PipelineHandler *pipe, const std::string &name);
~Camera(); ~Camera();
bool stateBetween(State low, State high) const;
bool stateIs(State state) const;
friend class PipelineHandler; friend class PipelineHandler;
void disconnect(); void disconnect();
int exclusiveAccess();
std::shared_ptr<PipelineHandler> pipe_; std::shared_ptr<PipelineHandler> pipe_;
std::string name_; std::string name_;
std::set<Stream *> streams_; std::set<Stream *> streams_;
std::set<Stream *> activeStreams_; std::set<Stream *> activeStreams_;
bool acquired_;
bool disconnected_; bool disconnected_;
State state_;
}; };
} /* namespace libcamera */ } /* namespace libcamera */

View file

@ -11,6 +11,7 @@
#include "log.h" #include "log.h"
#include "pipeline_handler.h" #include "pipeline_handler.h"
#include "utils.h"
/** /**
* \file camera.h * \file camera.h
@ -42,6 +43,9 @@ LOG_DECLARE_CATEGORY(Camera)
* \class Camera * \class Camera
* \brief Camera device * \brief Camera device
* *
* \todo Add documentation for camera start timings. What exactly does the
* camera expect the pipeline handler to do when start() is called?
*
* The Camera class models a camera capable of producing one or more image * The Camera class models a camera capable of producing one or more image
* streams from a single image source. It provides the main interface to * streams from a single image source. It provides the main interface to
* configuring and controlling the device, and capturing image streams. It is * configuring and controlling the device, and capturing image streams. It is
@ -52,6 +56,78 @@ LOG_DECLARE_CATEGORY(Camera)
* created with the create() function which returns a shared pointer. The * created with the create() function which returns a shared pointer. The
* Camera constructors and destructor are private, to prevent instances from * Camera constructors and destructor are private, to prevent instances from
* being constructed and destroyed manually. * being constructed and destroyed manually.
*
* \section camera_operation Operating the Camera
*
* An application needs to perform a sequence of operations on a camera before
* it is ready to process requests. The camera needs to be acquired, configured
* and resources allocated or imported to prepare the camera for capture. Once
* started the camera can process requests until it is stopped. When an
* application is done with a camera all resources allocated need to be freed
* and the camera released.
*
* An application may start and stop a camera multiple times as long as it is
* not released. The camera may also be reconfigured provided that all
* resources allocated are freed prior to the reconfiguration.
*
* \subsection Camera States
*
* To help manage the sequence of operations needed to control the camera a set
* of states are defined. Each state describes which operations may be performed
* on the camera. Operations not listed in the state diagram are allowed in all
* states.
*
* \dot
* digraph camera_state_machine {
* node [shape = doublecircle ]; Available;
* node [shape = circle ]; Acquired;
* node [shape = circle ]; Configured;
* node [shape = circle ]; Prepared;
* node [shape = circle ]; Running;
*
* Available -> Available [label = "release()"];
* Available -> Acquired [label = "acquire()"];
*
* Acquired -> Available [label = "release()"];
* Acquired -> Configured [label = "configureStreams()"];
*
* Configured -> Available [label = "release()"];
* Configured -> Configured [label = "configureStreams()"];
* Configured -> Prepared [label = "allocateBuffers()"];
*
* Prepared -> Configured [label = "freeBuffers()"];
* Prepared -> Prepared [label = "createRequest()"];
* Prepared -> Running [label = "start()"];
*
* Running -> Prepared [label = "stop()"];
* Running -> Running [label = "createRequest(), queueRequest()"];
* }
* \enddot
*
* \subsubsection Available
* The base state of a camera, an application can inspect the properties of the
* camera to determine if it wishes to use it. If an application wishes to use
* a camera it should acquire() it to proceed to the Acquired state.
*
* \subsubsection Acquired
* In the acquired state an application has exclusive access to the camera and
* may modify the camera's parameters to configure it and proceed to the
* Configured state.
*
* \subsubsection Configured
* The camera is configured and ready for the application to prepare it with
* resources. The camera may be reconfigured multiple times until resources
* are provided and the state progresses to Prepared.
*
* \subsubsection Prepared
* The camera has been configured and provided with resources and is ready to be
* started. The application may free the camera's resources to get back to the
* Configured state or start() it to progress to the Running state.
*
* \subsubsection Running
* The camera is running and ready to process requests queued by the
* application. The camera remains in this state until it is stopped and moved
* to the Prepared state.
*/ */
/** /**
@ -116,17 +192,55 @@ const std::string &Camera::name() const
*/ */
Camera::Camera(PipelineHandler *pipe, const std::string &name) Camera::Camera(PipelineHandler *pipe, const std::string &name)
: pipe_(pipe->shared_from_this()), name_(name), acquired_(false), : pipe_(pipe->shared_from_this()), name_(name), disconnected_(false),
disconnected_(false) state_(CameraAvailable)
{ {
} }
Camera::~Camera() Camera::~Camera()
{ {
if (acquired_) if (!stateIs(CameraAvailable))
LOG(Camera, Error) << "Removing camera while still in use"; LOG(Camera, Error) << "Removing camera while still in use";
} }
static const char *const camera_state_names[] = {
"Available",
"Acquired",
"Configured",
"Prepared",
"Running",
};
bool Camera::stateBetween(State low, State high) const
{
if (state_ >= low && state_ <= high)
return true;
ASSERT(static_cast<unsigned int>(low) < ARRAY_SIZE(camera_state_names) &&
static_cast<unsigned int>(high) < ARRAY_SIZE(camera_state_names));
LOG(Camera, Debug) << "Camera in " << camera_state_names[state_]
<< " state trying operation requiring state between "
<< camera_state_names[low] << " and "
<< camera_state_names[high];
return false;
}
bool Camera::stateIs(State state) const
{
if (state_ == state)
return true;
ASSERT(static_cast<unsigned int>(state) < ARRAY_SIZE(camera_state_names));
LOG(Camera, Debug) << "Camera in " << camera_state_names[state_]
<< " state trying operation requiring state "
<< camera_state_names[state];
return false;
}
/** /**
* \brief Notify camera disconnection * \brief Notify camera disconnection
* *
@ -135,11 +249,24 @@ Camera::~Camera()
* instance notifies the application by emitting the #disconnected signal, and * instance notifies the application by emitting the #disconnected signal, and
* ensures that all new calls to the application-facing Camera API return an * ensures that all new calls to the application-facing Camera API return an
* error immediately. * error immediately.
*
* \todo Deal with pending requests if the camera is disconnected in a
* running state.
* \todo Update comment about Running state when importing buffers as well as
* allocating them are supported.
*/ */
void Camera::disconnect() void Camera::disconnect()
{ {
LOG(Camera, Debug) << "Disconnecting camera " << name_; LOG(Camera, Debug) << "Disconnecting camera " << name_;
/*
* If the camera was running when the hardware was removed force the
* state to Prepared to allow applications to call freeBuffers() and
* release() before deleting the camera.
*/
if (state_ == CameraRunning)
state_ = CameraPrepared;
disconnected_ = true; disconnected_ = true;
disconnected.emit(this); disconnected.emit(this);
} }
@ -155,16 +282,24 @@ void Camera::disconnect()
* Once exclusive access isn't needed anymore, the device should be released * Once exclusive access isn't needed anymore, the device should be released
* with a call to the release() function. * with a call to the release() function.
* *
* This function affects the state of the camera, see \ref camera_operation.
*
* \todo Implement exclusive access across processes. * \todo Implement exclusive access across processes.
* *
* \return 0 on success or a negative error code otherwise * \return 0 on success or a negative error code otherwise
* \retval -ENODEV The camera has been disconnected from the system
* \retval -EBUSY The camera is not free and can't be acquired by the caller
*/ */
int Camera::acquire() int Camera::acquire()
{ {
if (acquired_) if (disconnected_)
return -ENODEV;
if (!stateIs(CameraAvailable))
return -EBUSY; return -EBUSY;
acquired_ = true; state_ = CameraAcquired;
return 0; return 0;
} }
@ -173,10 +308,20 @@ int Camera::acquire()
* *
* Releasing the camera device allows other users to acquire exclusive access * Releasing the camera device allows other users to acquire exclusive access
* with the acquire() function. * with the acquire() function.
*
* This function affects the state of the camera, see \ref camera_operation.
*
* \return 0 on success or a negative error code otherwise
* \retval -EBUSY The camera is running and can't be released
*/ */
void Camera::release() int Camera::release()
{ {
acquired_ = false; if (!stateBetween(CameraAvailable, CameraConfigured))
return -EBUSY;
state_ = CameraAvailable;
return 0;
} }
/** /**
@ -235,18 +380,22 @@ Camera::streamConfiguration(std::set<Stream *> &streams)
* Exclusive access to the camera shall be ensured by a call to acquire() prior * Exclusive access to the camera shall be ensured by a call to acquire() prior
* to calling this function, otherwise an -EACCES error will be returned. * to calling this function, otherwise an -EACCES error will be returned.
* *
* This function affects the state of the camera, see \ref camera_operation.
*
* \return 0 on success or a negative error code otherwise * \return 0 on success or a negative error code otherwise
* \retval -ENODEV The camera is not connected to any hardware * \retval -ENODEV The camera has been disconnected from the system
* \retval -EACCES The user has not acquired exclusive access to the camera * \retval -EACCES The camera is not in a state where it can be configured
* \retval -EINVAL The configuration is not valid * \retval -EINVAL The configuration is not valid
*/ */
int Camera::configureStreams(std::map<Stream *, StreamConfiguration> &config) int Camera::configureStreams(std::map<Stream *, StreamConfiguration> &config)
{ {
int ret; int ret;
ret = exclusiveAccess(); if (disconnected_)
if (ret) return -ENODEV;
return ret;
if (!stateBetween(CameraAvailable, CameraConfigured))
return -EACCES;
if (!config.size()) { if (!config.size()) {
LOG(Camera, Error) LOG(Camera, Error)
@ -273,20 +422,28 @@ int Camera::configureStreams(std::map<Stream *, StreamConfiguration> &config)
stream->bufferPool().createBuffers(cfg.bufferCount); stream->bufferPool().createBuffers(cfg.bufferCount);
} }
state_ = CameraConfigured;
return 0; return 0;
} }
/** /**
* \brief Allocate buffers for all configured streams * \brief Allocate buffers for all configured streams
*
* This function affects the state of the camera, see \ref camera_operation.
*
* \return 0 on success or a negative error code otherwise * \return 0 on success or a negative error code otherwise
* \retval -ENODEV The camera has been disconnected from the system
* \retval -EACCES The camera is not in a state where buffers can be allocated
* \retval -EINVAL The configuration is not valid
*/ */
int Camera::allocateBuffers() int Camera::allocateBuffers()
{ {
int ret; if (disconnected_)
return -ENODEV;
ret = exclusiveAccess(); if (!stateIs(CameraConfigured))
if (ret) return -EACCES;
return ret;
if (activeStreams_.empty()) { if (activeStreams_.empty()) {
LOG(Camera, Error) LOG(Camera, Error)
@ -295,7 +452,7 @@ int Camera::allocateBuffers()
} }
for (Stream *stream : activeStreams_) { for (Stream *stream : activeStreams_) {
ret = pipe_->allocateBuffers(this, stream); int ret = pipe_->allocateBuffers(this, stream);
if (ret) { if (ret) {
LOG(Camera, Error) << "Failed to allocate buffers"; LOG(Camera, Error) << "Failed to allocate buffers";
freeBuffers(); freeBuffers();
@ -303,14 +460,24 @@ int Camera::allocateBuffers()
} }
} }
state_ = CameraPrepared;
return 0; return 0;
} }
/** /**
* \brief Release all buffers from allocated pools in each stream * \brief Release all buffers from allocated pools in each stream
*
* This function affects the state of the camera, see \ref camera_operation.
*
* \return 0 on success or a negative error code otherwise
* \retval -EACCES The camera is not in a state where buffers can be freed
*/ */
void Camera::freeBuffers() int Camera::freeBuffers()
{ {
if (!stateIs(CameraPrepared))
return -EACCES;
for (Stream *stream : activeStreams_) { for (Stream *stream : activeStreams_) {
if (!stream->bufferPool().count()) if (!stream->bufferPool().count())
continue; continue;
@ -318,6 +485,10 @@ void Camera::freeBuffers()
pipe_->freeBuffers(this, stream); pipe_->freeBuffers(this, stream);
stream->bufferPool().destroyBuffers(); stream->bufferPool().destroyBuffers();
} }
state_ = CameraConfigured;
return 0;
} }
/** /**
@ -329,11 +500,14 @@ void Camera::freeBuffers()
* The ownership of the returned request is passed to the caller, which is * The ownership of the returned request is passed to the caller, which is
* responsible for either queueing the request or deleting it. * responsible for either queueing the request or deleting it.
* *
* This function shall only be called when the camera is in the Prepared
* or Running state, see \ref camera_operation.
*
* \return A pointer to the newly created request, or nullptr on error * \return A pointer to the newly created request, or nullptr on error
*/ */
Request *Camera::createRequest() Request *Camera::createRequest()
{ {
if (exclusiveAccess()) if (disconnected_ || !stateBetween(CameraPrepared, CameraRunning))
return nullptr; return nullptr;
return new Request(this); return new Request(this);
@ -351,16 +525,18 @@ Request *Camera::createRequest()
* automatically after it completes. * automatically after it completes.
* *
* \return 0 on success or a negative error code otherwise * \return 0 on success or a negative error code otherwise
* \retval -ENODEV The camera has been disconnected from the system
* \retval -EACCES The camera is not running so requests can't be queued
*/ */
int Camera::queueRequest(Request *request) int Camera::queueRequest(Request *request)
{ {
int ret; if (disconnected_)
return -ENODEV;
ret = exclusiveAccess(); if (!stateIs(CameraRunning))
if (ret) return -EACCES;
return ret;
ret = request->prepare(); int ret = request->prepare();
if (ret) { if (ret) {
LOG(Camera, Error) << "Failed to prepare request"; LOG(Camera, Error) << "Failed to prepare request";
return ret; return ret;
@ -376,17 +552,29 @@ int Camera::queueRequest(Request *request)
* can queue requests to the camera to process and return to the application * can queue requests to the camera to process and return to the application
* until the capture session is terminated with \a stop(). * until the capture session is terminated with \a stop().
* *
* This function affects the state of the camera, see \ref camera_operation.
*
* \return 0 on success or a negative error code otherwise * \return 0 on success or a negative error code otherwise
* \retval -ENODEV The camera has been disconnected from the system
* \retval -EACCES The camera is not in a state where it can be started
*/ */
int Camera::start() int Camera::start()
{ {
int ret = exclusiveAccess(); if (disconnected_)
if (ret) return -ENODEV;
return ret;
if (!stateIs(CameraPrepared))
return -EACCES;
LOG(Camera, Debug) << "Starting capture"; LOG(Camera, Debug) << "Starting capture";
return pipe_->start(this); int ret = pipe_->start(this);
if (ret)
return ret;
state_ = CameraRunning;
return 0;
} }
/** /**
@ -395,30 +583,27 @@ int Camera::start()
* This method stops capturing and processing requests immediately. All pending * This method stops capturing and processing requests immediately. All pending
* requests are cancelled and complete synchronously in an error state. * requests are cancelled and complete synchronously in an error state.
* *
* This function affects the state of the camera, see \ref camera_operation.
*
* \return 0 on success or a negative error code otherwise * \return 0 on success or a negative error code otherwise
* \retval -ENODEV The camera has been disconnected from the system
* \retval -EACCES The camera is not running so can't be stopped
*/ */
int Camera::stop() int Camera::stop()
{ {
int ret = exclusiveAccess(); if (disconnected_)
if (ret) return -ENODEV;
return ret;
if (!stateIs(CameraRunning))
return -EACCES;
LOG(Camera, Debug) << "Stopping capture"; LOG(Camera, Debug) << "Stopping capture";
state_ = CameraPrepared;
pipe_->stop(this); pipe_->stop(this);
return 0; return 0;
} }
int Camera::exclusiveAccess()
{
if (disconnected_)
return -ENODEV;
if (!acquired_)
return -EACCES;
return 0;
}
} /* namespace libcamera */ } /* namespace libcamera */