diff options
Diffstat (limited to 'src/libcamera/camera.cpp')
-rw-r--r-- | src/libcamera/camera.cpp | 310 |
1 files changed, 214 insertions, 96 deletions
diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp index fb76077f..c8858e71 100644 --- a/src/libcamera/camera.cpp +++ b/src/libcamera/camera.cpp @@ -7,38 +7,99 @@ #include <libcamera/camera.h> +#include <array> #include <atomic> #include <iomanip> +#include <libcamera/base/log.h> +#include <libcamera/base/thread.h> + #include <libcamera/framebuffer_allocator.h> #include <libcamera/request.h> #include <libcamera/stream.h> -#include "libcamera/internal/log.h" #include "libcamera/internal/pipeline_handler.h" -#include "libcamera/internal/thread.h" -#include "libcamera/internal/utils.h" /** * \file camera.h * \brief Camera device handling * - * At the core of libcamera is the camera device, combining one image source - * with processing hardware able to provide one or multiple image streams. The - * Camera class represents a camera device. - * - * A camera device contains a single image source, and separate camera device - * instances relate to different image sources. For instance, a phone containing - * front and back image sensors will be modelled with two camera devices, one - * for each sensor. When multiple streams can be produced from the same image - * source, all those streams are guaranteed to be part of the same camera - * device. - * - * While not sharing image sources, separate camera devices can share other - * system resources, such as an ISP. For this reason camera device instances may - * not be fully independent, in which case usage restrictions may apply. For - * instance, a phone with a front and a back camera device may not allow usage - * of the two devices simultaneously. + * \page camera-model Camera Model + * + * libcamera acts as a middleware between applications and camera hardware. It + * provides a solution to an unsolvable problem: reconciling applications, + * which need to run on different systems without dealing with device-specific + * details, and camera hardware, which exhibits a wide variety of features, + * limitations and architecture variations. In order to do so, it creates an + * abstract camera model that hides the camera hardware from applications. The + * model is designed to strike the right balance between genericity, to please + * generic applications, and flexibility, to expose even the most specific + * hardware features to the most demanding applications. + * + * In libcamera, a Camera is defined as a device that can capture frames + * continuously from a camera sensor and store them in memory. If supported by + * the device and desired by the application, the camera may store each + * captured frame in multiple copies, possibly in different formats and sizes. + * Each of these memory outputs of the camera is called a Stream. + * + * A camera contains a single image source, and separate camera instances + * relate to different image sources. For instance, a phone containing front + * and back image sensors will be modelled with two cameras, one for each + * sensor. When multiple streams can be produced from the same image source, + * all those streams are guaranteed to be part of the same camera. + * + * While not sharing image sources, separate cameras can share other system + * resources, such as ISPs. For this reason camera instances may not be fully + * independent, in which case usage restrictions may apply. For instance, a + * phone with a front and a back camera may not allow usage of the two cameras + * simultaneously. + * + * The camera model defines an implicit pipeline, whose input is the camera + * sensor, and whose outputs are the streams. Along the pipeline, the frames + * produced by the camera sensor are transformed by the camera into a format + * suitable for applications, with image processing that improves the quality + * of the captured frames. The camera exposes a set of controls that + * applications may use to manually control the processing steps. This + * high-level camera model is the minimum baseline that all cameras must + * conform to. + * + * \section camera-pipeline-model Pipeline Model + * + * Camera hardware differs in the supported image processing operations and the + * order in which they are applied. The libcamera pipelines abstract the + * hardware differences and expose a logical view of the processing operations + * with a fixed order. This offers low-level control of those operations to + * applications, while keeping application code generic. + * + * Starting from the camera sensor, a pipeline applies the following + * operations, in that order. + * + * - Pixel exposure + * - Analog to digital conversion and readout + * - Black level subtraction + * - Defective pixel correction + * - Lens shading correction + * - Spatial noise filtering + * - Per-channel gains (white balance) + * - Demosaicing (color filter array interpolation) + * - Color correction matrix (typically RGB to RGB) + * - Gamma correction + * - Color space transformation (typically RGB to YUV) + * - Cropping + * - Scaling + * + * Not all cameras implement all operations, and they are not necessarily + * implemented in the above order at the hardware level. The libcamera pipeline + * handlers translate the pipeline model to the real hardware configuration. + * + * \subsection digital-zoom Digital Zoom + * + * Digital zoom is implemented as a combination of the cropping and scaling + * stages of the pipeline. Cropping is controlled explicitly through the + * controls::ScalerCrop control, while scaling is controlled implicitly based + * on the crop rectangle and the output stream size. The crop rectangle is + * expressed relatively to the full pixel array size and indicates how the field + * of view is affected by the pipeline. */ namespace libcamera { @@ -270,23 +331,29 @@ std::size_t CameraConfiguration::size() const * \brief The vector of stream configurations */ -class Camera::Private +class Camera::Private : public Extensible::Private { + LIBCAMERA_DECLARE_PUBLIC(Camera) + public: enum State { CameraAvailable, CameraAcquired, CameraConfigured, + CameraStopping, CameraRunning, }; - Private(PipelineHandler *pipe, const std::string &id, + Private(Camera *camera, PipelineHandler *pipe, const std::string &id, const std::set<Stream *> &streams); ~Private(); - int isAccessAllowed(State state, bool allowDisconnected = false) const; + bool isRunning() const; + int isAccessAllowed(State state, bool allowDisconnected = false, + const char *from = __builtin_FUNCTION()) const; int isAccessAllowed(State low, State high, - bool allowDisconnected = false) const; + bool allowDisconnected = false, + const char *from = __builtin_FUNCTION()) const; void disconnect(); void setState(State state); @@ -301,10 +368,11 @@ private: std::atomic<State> state_; }; -Camera::Private::Private(PipelineHandler *pipe, const std::string &id, +Camera::Private::Private(Camera *camera, PipelineHandler *pipe, + const std::string &id, const std::set<Stream *> &streams) - : pipe_(pipe->shared_from_this()), id_(id), streams_(streams), - disconnected_(false), state_(CameraAvailable) + : Extensible::Private(camera), pipe_(pipe->shared_from_this()), id_(id), + streams_(streams), disconnected_(false), state_(CameraAvailable) { } @@ -318,10 +386,17 @@ static const char *const camera_state_names[] = { "Available", "Acquired", "Configured", + "Stopping", "Running", }; -int Camera::Private::isAccessAllowed(State state, bool allowDisconnected) const +bool Camera::Private::isRunning() const +{ + return state_.load(std::memory_order_acquire) == CameraRunning; +} + +int Camera::Private::isAccessAllowed(State state, bool allowDisconnected, + const char *from) const { if (!allowDisconnected && disconnected_) return -ENODEV; @@ -330,17 +405,18 @@ int Camera::Private::isAccessAllowed(State state, bool allowDisconnected) const if (currentState == state) return 0; - ASSERT(static_cast<unsigned int>(state) < ARRAY_SIZE(camera_state_names)); + ASSERT(static_cast<unsigned int>(state) < std::size(camera_state_names)); - LOG(Camera, Debug) << "Camera in " << camera_state_names[currentState] - << " state trying operation requiring state " + LOG(Camera, Error) << "Camera in " << camera_state_names[currentState] + << " state trying " << from << "() requiring state " << camera_state_names[state]; return -EACCES; } int Camera::Private::isAccessAllowed(State low, State high, - bool allowDisconnected) const + bool allowDisconnected, + const char *from) const { if (!allowDisconnected && disconnected_) return -ENODEV; @@ -349,11 +425,12 @@ int Camera::Private::isAccessAllowed(State low, State high, if (currentState >= low && currentState <= high) return 0; - ASSERT(static_cast<unsigned int>(low) < ARRAY_SIZE(camera_state_names) && - static_cast<unsigned int>(high) < ARRAY_SIZE(camera_state_names)); + ASSERT(static_cast<unsigned int>(low) < std::size(camera_state_names) && + static_cast<unsigned int>(high) < std::size(camera_state_names)); - LOG(Camera, Debug) << "Camera in " << camera_state_names[currentState] - << " state trying operation requiring state between " + LOG(Camera, Error) << "Camera in " << camera_state_names[currentState] + << " state trying " << from + << "() requiring state between " << camera_state_names[low] << " and " << camera_state_names[high]; @@ -424,6 +501,7 @@ void Camera::Private::setState(State state) * node [shape = doublecircle ]; Available; * node [shape = circle ]; Acquired; * node [shape = circle ]; Configured; + * node [shape = circle ]; Stopping; * node [shape = circle ]; Running; * * Available -> Available [label = "release()"]; @@ -436,7 +514,8 @@ void Camera::Private::setState(State state) * Configured -> Configured [label = "configure(), createRequest()"]; * Configured -> Running [label = "start()"]; * - * Running -> Configured [label = "stop()"]; + * Running -> Stopping [label = "stop()"]; + * Stopping -> Configured; * Running -> Running [label = "createRequest(), queueRequest()"]; * } * \enddot @@ -456,6 +535,12 @@ void Camera::Private::setState(State state) * release() the camera and to get back to the Available state or start() * it to progress to the Running state. * + * \subsubsection Stopping + * The camera has been asked to stop. Pending requests are being completed or + * cancelled, and no new requests are permitted to be queued. The camera will + * transition to the Configured state when all queued requests have been + * returned to the application. + * * \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 @@ -519,7 +604,7 @@ std::shared_ptr<Camera> Camera::create(PipelineHandler *pipe, */ const std::string &Camera::id() const { - return p_->id_; + return _d()->id_; } /** @@ -547,7 +632,7 @@ const std::string &Camera::id() const Camera::Camera(PipelineHandler *pipe, const std::string &id, const std::set<Stream *> &streams) - : p_(new Private(pipe, id, streams)) + : Extensible(new Private(this, pipe, id, streams)) { } @@ -571,26 +656,28 @@ void Camera::disconnect() { LOG(Camera, Debug) << "Disconnecting camera " << id(); - p_->disconnect(); + _d()->disconnect(); disconnected.emit(this); } int Camera::exportFrameBuffers(Stream *stream, std::vector<std::unique_ptr<FrameBuffer>> *buffers) { - int ret = p_->isAccessAllowed(Private::CameraConfigured); + Private *const d = _d(); + + int ret = d->isAccessAllowed(Private::CameraConfigured); if (ret < 0) return ret; if (streams().find(stream) == streams().end()) return -EINVAL; - if (p_->activeStreams_.find(stream) == p_->activeStreams_.end()) + if (d->activeStreams_.find(stream) == d->activeStreams_.end()) return -EINVAL; - return p_->pipe_->invokeMethod(&PipelineHandler::exportFrameBuffers, - ConnectionTypeBlocking, this, stream, - buffers); + return d->pipe_->invokeMethod(&PipelineHandler::exportFrameBuffers, + ConnectionTypeBlocking, this, stream, + buffers); } /** @@ -619,21 +706,23 @@ int Camera::exportFrameBuffers(Stream *stream, */ int Camera::acquire() { + Private *const d = _d(); + /* * No manual locking is required as PipelineHandler::lock() is * thread-safe. */ - int ret = p_->isAccessAllowed(Private::CameraAvailable); + int ret = d->isAccessAllowed(Private::CameraAvailable); if (ret < 0) return ret == -EACCES ? -EBUSY : ret; - if (!p_->pipe_->lock()) { + if (!d->pipe_->lock()) { LOG(Camera, Info) << "Pipeline handler in use by another process"; return -EBUSY; } - p_->setState(Private::CameraAcquired); + d->setState(Private::CameraAcquired); return 0; } @@ -654,14 +743,16 @@ int Camera::acquire() */ int Camera::release() { - int ret = p_->isAccessAllowed(Private::CameraAvailable, - Private::CameraConfigured, true); + Private *const d = _d(); + + int ret = d->isAccessAllowed(Private::CameraAvailable, + Private::CameraConfigured, true); if (ret < 0) return ret == -EACCES ? -EBUSY : ret; - p_->pipe_->unlock(); + d->pipe_->unlock(); - p_->setState(Private::CameraAvailable); + d->setState(Private::CameraAvailable); return 0; } @@ -678,7 +769,7 @@ int Camera::release() */ const ControlInfoMap &Camera::controls() const { - return p_->pipe_->controls(this); + return _d()->pipe_->controls(this); } /** @@ -691,7 +782,7 @@ const ControlInfoMap &Camera::controls() const */ const ControlList &Camera::properties() const { - return p_->pipe_->properties(this); + return _d()->pipe_->properties(this); } /** @@ -707,7 +798,7 @@ const ControlList &Camera::properties() const */ const std::set<Stream *> &Camera::streams() const { - return p_->streams_; + return _d()->streams_; } /** @@ -728,15 +819,17 @@ const std::set<Stream *> &Camera::streams() const */ std::unique_ptr<CameraConfiguration> Camera::generateConfiguration(const StreamRoles &roles) { - int ret = p_->isAccessAllowed(Private::CameraAvailable, - Private::CameraRunning); + Private *const d = _d(); + + int ret = d->isAccessAllowed(Private::CameraAvailable, + Private::CameraRunning); if (ret < 0) return nullptr; if (roles.size() > streams().size()) return nullptr; - CameraConfiguration *config = p_->pipe_->generateConfiguration(this, roles); + CameraConfiguration *config = d->pipe_->generateConfiguration(this, roles); if (!config) { LOG(Camera, Debug) << "Pipeline handler failed to generate configuration"; @@ -787,8 +880,10 @@ std::unique_ptr<CameraConfiguration> Camera::generateConfiguration(const StreamR */ int Camera::configure(CameraConfiguration *config) { - int ret = p_->isAccessAllowed(Private::CameraAcquired, - Private::CameraConfigured); + Private *const d = _d(); + + int ret = d->isAccessAllowed(Private::CameraAcquired, + Private::CameraConfigured); if (ret < 0) return ret; @@ -810,26 +905,26 @@ int Camera::configure(CameraConfiguration *config) LOG(Camera, Info) << msg.str(); - ret = p_->pipe_->invokeMethod(&PipelineHandler::configure, - ConnectionTypeBlocking, this, config); + ret = d->pipe_->invokeMethod(&PipelineHandler::configure, + ConnectionTypeBlocking, this, config); if (ret) return ret; - p_->activeStreams_.clear(); + d->activeStreams_.clear(); for (const StreamConfiguration &cfg : *config) { Stream *stream = cfg.stream(); if (!stream) { LOG(Camera, Fatal) << "Pipeline handler failed to update stream configuration"; - p_->activeStreams_.clear(); + d->activeStreams_.clear(); return -EINVAL; } stream->configuration_ = cfg; - p_->activeStreams_.insert(stream); + d->activeStreams_.insert(stream); } - p_->setState(Private::CameraConfigured); + d->setState(Private::CameraConfigured); return 0; } @@ -847,21 +942,22 @@ int Camera::configure(CameraConfiguration *config) * handler, and is completely opaque to libcamera. * * The ownership of the returned request is passed to the caller, which is - * responsible for either queueing the request or deleting it. + * responsible for deleting it. The request may be deleted in the completion + * handler, or reused after resetting its state with Request::reuse(). * * \context This function is \threadsafe. It may only be called when the camera * is in the Configured or Running state as defined in \ref camera_operation. * * \return A pointer to the newly created request, or nullptr on error */ -Request *Camera::createRequest(uint64_t cookie) +std::unique_ptr<Request> Camera::createRequest(uint64_t cookie) { - int ret = p_->isAccessAllowed(Private::CameraConfigured, - Private::CameraRunning); + int ret = _d()->isAccessAllowed(Private::CameraConfigured, + Private::CameraRunning); if (ret < 0) return nullptr; - return new Request(this, cookie); + return std::make_unique<Request>(this, cookie); } /** @@ -877,9 +973,6 @@ Request *Camera::createRequest(uint64_t cookie) * Once the request has been queued, the camera will notify its completion * through the \ref requestCompleted signal. * - * Ownership of the request is transferred to the camera. It will be deleted - * automatically after it completes. - * * \context This function is \threadsafe. It may only be called when the camera * is in the Running state as defined in \ref camera_operation. * @@ -891,12 +984,14 @@ Request *Camera::createRequest(uint64_t cookie) */ int Camera::queueRequest(Request *request) { - int ret = p_->isAccessAllowed(Private::CameraRunning); + Private *const d = _d(); + + int ret = d->isAccessAllowed(Private::CameraRunning); if (ret < 0) return ret; /* - * The camera state may chance until the end of the function. No locking + * The camera state may change until the end of the function. No locking * is however needed as PipelineHandler::queueRequest() will handle * this. */ @@ -909,22 +1004,26 @@ int Camera::queueRequest(Request *request) for (auto const &it : request->buffers()) { const Stream *stream = it.first; - if (p_->activeStreams_.find(stream) == p_->activeStreams_.end()) { + if (d->activeStreams_.find(stream) == d->activeStreams_.end()) { LOG(Camera, Error) << "Invalid request"; return -EINVAL; } } - return p_->pipe_->invokeMethod(&PipelineHandler::queueRequest, - ConnectionTypeQueued, this, request); + d->pipe_->invokeMethod(&PipelineHandler::queueRequest, + ConnectionTypeQueued, request); + + return 0; } /** * \brief Start capture from camera + * \param[in] controls Controls to be applied before starting the Camera * - * Start the camera capture session. Once the camera is started the application - * can queue requests to the camera to process and return to the application - * until the capture session is terminated with \a stop(). + * Start the camera capture session, optionally providing a list of controls to + * apply before starting. Once the camera is started the application can queue + * requests to the camera to process and return to the application until the + * capture session is terminated with \a stop(). * * \context This function may only be called when the camera is in the * Configured state as defined in \ref camera_operation, and shall be @@ -935,20 +1034,22 @@ int Camera::queueRequest(Request *request) * \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(const ControlList *controls) { - int ret = p_->isAccessAllowed(Private::CameraConfigured); + Private *const d = _d(); + + int ret = d->isAccessAllowed(Private::CameraConfigured); if (ret < 0) return ret; LOG(Camera, Debug) << "Starting capture"; - ret = p_->pipe_->invokeMethod(&PipelineHandler::start, - ConnectionTypeBlocking, this); + ret = d->pipe_->invokeMethod(&PipelineHandler::start, + ConnectionTypeBlocking, this, controls); if (ret) return ret; - p_->setState(Private::CameraRunning); + d->setState(Private::CameraRunning); return 0; } @@ -959,9 +1060,10 @@ int Camera::start() * This method stops capturing and processing requests immediately. All pending * requests are cancelled and complete synchronously in an error state. * - * \context This function may only be called when the camera is in the Running - * state as defined in \ref camera_operation, and shall be synchronized by the - * caller with other functions that affect the camera state. + * \context This function may be called in any camera state as defined in \ref + * camera_operation, and shall be synchronized by the caller with other + * functions that affect the camera state. If called when the camera isn't + * running, it is a no-op. * * \return 0 on success or a negative error code otherwise * \retval -ENODEV The camera has been disconnected from the system @@ -969,16 +1071,29 @@ int Camera::start() */ int Camera::stop() { - int ret = p_->isAccessAllowed(Private::CameraRunning); + Private *const d = _d(); + + /* + * \todo Make calling stop() when not in 'Running' part of the state + * machine rather than take this shortcut + */ + if (!d->isRunning()) + return 0; + + int ret = d->isAccessAllowed(Private::CameraRunning); if (ret < 0) return ret; LOG(Camera, Debug) << "Stopping capture"; - p_->setState(Private::CameraConfigured); + d->setState(Private::CameraStopping); - p_->pipe_->invokeMethod(&PipelineHandler::stop, ConnectionTypeBlocking, - this); + d->pipe_->invokeMethod(&PipelineHandler::stop, ConnectionTypeBlocking, + this); + + ASSERT(!d->pipe_->hasPendingRequests(this)); + + d->setState(Private::CameraConfigured); return 0; } @@ -988,13 +1103,16 @@ int Camera::stop() * \param[in] request The request that has completed * * This function is called by the pipeline handler to notify the camera that - * the request has completed. It emits the requestCompleted signal and deletes - * the request. + * the request has completed. It emits the requestCompleted signal. */ void Camera::requestComplete(Request *request) { + /* Disconnected cameras are still able to complete requests. */ + if (_d()->isAccessAllowed(Private::CameraStopping, Private::CameraRunning, + true)) + LOG(Camera, Fatal) << "Trying to complete a request when stopped"; + requestCompleted.emit(request); - delete request; } } /* namespace libcamera */ |