summaryrefslogtreecommitdiff
path: root/src/libcamera/camera.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libcamera/camera.cpp')
-rw-r--r--src/libcamera/camera.cpp310
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 */