camera example from libcamera git

This commit is contained in:
shinya 2026-05-20 12:22:09 +02:00
parent 85a1e21307
commit addb823f0c
4 changed files with 635 additions and 0 deletions

402
camera_example/camera.cpp Normal file
View File

@ -0,0 +1,402 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2020, Ideas on Board Oy.
*
* A simple libcamera capture example
*/
#include <iomanip>
#include <iostream>
#include <memory>
#include <libcamera/libcamera.h>
#include "event_loop.hpp"
#define TIMEOUT_SEC 3
using namespace libcamera;
static std::shared_ptr<Camera> camera;
static EventLoop loop;
/*
* --------------------------------------------------------------------
* Handle RequestComplete
*
* For each Camera::requestCompleted Signal emitted from the Camera the
* connected Slot is invoked.
*
* The Slot is invoked in the CameraManager's thread, hence one should avoid
* any heavy processing here. The processing of the request shall be re-directed
* to the application's thread instead, so as not to block the CameraManager's
* thread for large amount of time.
*
* The Slot receives the Request as a parameter.
*/
static void processRequest(Request *request);
static void requestComplete(Request *request)
{
if (request->status() == Request::RequestCancelled)
return;
loop.callLater(std::bind(&processRequest, request));
}
static void processRequest(Request *request)
{
std::cout << std::endl
<< "Request completed: " << request->toString() << std::endl;
/*
* When a request has completed, it is populated with a metadata control
* list that allows an application to determine various properties of
* the completed request. This can include the timestamp of the Sensor
* capture, or its gain and exposure values, or properties from the IPA
* such as the state of the 3A algorithms.
*
* ControlValue types have a toString, so to examine each request, print
* all the metadata for inspection. A custom application can parse each
* of these items and process them according to its needs.
*/
const ControlList &requestMetadata = request->metadata();
for (const auto &ctrl : requestMetadata) {
const ControlId *id = controls::controls.at(ctrl.first);
const ControlValue &value = ctrl.second;
std::cout << "\t" << id->name() << " = " << value.toString()
<< std::endl;
}
/*
* Each buffer has its own FrameMetadata to describe its state, or the
* usage of each buffer. While in our simple capture we only provide one
* buffer per request, a request can have a buffer for each stream that
* is established when configuring the camera.
*
* This allows a viewfinder and a still image to be processed at the
* same time, or to allow obtaining the RAW capture buffer from the
* sensor along with the image as processed by the ISP.
*/
const Request::BufferMap &buffers = request->buffers();
for (auto bufferPair : buffers) {
// (Unused) Stream *stream = bufferPair.first;
FrameBuffer *buffer = bufferPair.second;
const FrameMetadata &metadata = buffer->metadata();
/* Print some information about the buffer which has completed. */
std::cout << " seq: " << std::setw(6) << std::setfill('0') << metadata.sequence
<< " timestamp: " << metadata.timestamp
<< " bytesused: ";
unsigned int nplane = 0;
for (const FrameMetadata::Plane &plane : metadata.planes())
{
std::cout << plane.bytesused;
if (++nplane < metadata.planes().size())
std::cout << "/";
}
std::cout << std::endl;
/*
* Image data can be accessed here, but the FrameBuffer
* must be mapped by the application
*/
}
/* Re-queue the Request to the camera. */
request->reuse(Request::ReuseBuffers);
camera->queueRequest(request);
}
int main()
{
/*
* --------------------------------------------------------------------
* Create a Camera Manager.
*
* The Camera Manager is responsible for enumerating all the Camera
* in the system, by associating Pipeline Handlers with media entities
* registered in the system.
*
* The CameraManager provides a list of available Cameras that
* applications can operate on.
*
* When the CameraManager is no longer to be used, it should be deleted.
* We use a unique_ptr here to manage the lifetime automatically during
* the scope of this function.
*
* There can only be a single CameraManager constructed within any
* process space.
*/
std::unique_ptr<CameraManager> cm = std::make_unique<CameraManager>();
cm->start();
/*
* Just as a test, generate names of the Cameras registered in the
* system, and list them.
*/
for (auto const &camera : cm->cameras())
std::cout << " - " << camera.get()->id() << std::endl;
/*
* --------------------------------------------------------------------
* Camera
*
* Camera are entities created by pipeline handlers, inspecting the
* entities registered in the system and reported to applications
* by the CameraManager.
*
* In general terms, a Camera corresponds to a single image source
* available in the system, such as an image sensor.
*
* Application lock usage of Camera by 'acquiring' them.
* Once done with it, application shall similarly 'release' the Camera.
*
* As an example, use the first available camera in the system after
* making sure that at least one camera is available.
*
* Cameras can be obtained by their ID or their index, to demonstrate
* this, the following code gets the ID of the first camera; then gets
* the camera associated with that ID (which is of course the same as
* cm->cameras()[0]).
*/
if (cm->cameras().empty()) {
std::cout << "No cameras were identified on the system."
<< std::endl;
cm->stop();
return EXIT_FAILURE;
}
std::string cameraId = cm->cameras()[0]->id();
camera = cm->get(cameraId);
camera->acquire();
/*
* Stream
*
* Each Camera supports a variable number of Stream. A Stream is
* produced by processing data produced by an image source, usually
* by an ISP.
*
* +-------------------------------------------------------+
* | Camera |
* | +-----------+ |
* | +--------+ | |------> [ Main output ] |
* | | Image | | | |
* | | |---->| ISP |------> [ Viewfinder ] |
* | | Source | | | |
* | +--------+ | |------> [ Still Capture ] |
* | +-----------+ |
* +-------------------------------------------------------+
*
* The number and capabilities of the Stream in a Camera are
* a platform dependent property, and it's the pipeline handler
* implementation that has the responsibility of correctly
* report them.
*/
/*
* --------------------------------------------------------------------
* Camera Configuration.
*
* Camera configuration is tricky! It boils down to assign resources
* of the system (such as DMA engines, scalers, format converters) to
* the different image streams an application has requested.
*
* Depending on the system characteristics, some combinations of
* sizes, formats and stream usages might or might not be possible.
*
* A Camera produces a CameraConfigration based on a set of intended
* roles for each Stream the application requires.
*/
std::unique_ptr<CameraConfiguration> config =
camera->generateConfiguration( { StreamRole::Viewfinder } );
/*
* The CameraConfiguration contains a StreamConfiguration instance
* for each StreamRole requested by the application, provided
* the Camera can support all of them.
*
* Each StreamConfiguration has default size and format, assigned
* by the Camera depending on the Role the application has requested.
*/
StreamConfiguration &streamConfig = config->at(0);
std::cout << "Default viewfinder configuration is: "
<< streamConfig.toString() << std::endl;
/*
* Each StreamConfiguration parameter which is part of a
* CameraConfiguration can be independently modified by the
* application.
*
* In order to validate the modified parameter, the CameraConfiguration
* should be validated -before- the CameraConfiguration gets applied
* to the Camera.
*
* The CameraConfiguration validation process adjusts each
* StreamConfiguration to a valid value.
*/
/*
* Validating a CameraConfiguration -before- applying it will adjust it
* to a valid configuration which is as close as possible to the one
* requested.
*/
config->validate();
std::cout << "Validated viewfinder configuration is: "
<< streamConfig.toString() << std::endl;
/*
* Once we have a validated configuration, we can apply it to the
* Camera.
*/
camera->configure(config.get());
/*
* --------------------------------------------------------------------
* Buffer Allocation
*
* Now that a camera has been configured, it knows all about its
* Streams sizes and formats. The captured images need to be stored in
* framebuffers which can either be provided by the application to the
* library, or allocated in the Camera and exposed to the application
* by libcamera.
*
* An application may decide to allocate framebuffers from elsewhere,
* for example in memory allocated by the display driver that will
* render the captured frames. The application will provide them to
* libcamera by constructing FrameBuffer instances to capture images
* directly into.
*
* Alternatively libcamera can help the application by exporting
* buffers allocated in the Camera using a FrameBufferAllocator
* instance and referencing a configured Camera to determine the
* appropriate buffer size and types to create.
*/
FrameBufferAllocator *allocator = new FrameBufferAllocator(camera);
for (StreamConfiguration &cfg : *config) {
int ret = allocator->allocate(cfg.stream());
if (ret < 0) {
std::cerr << "Can't allocate buffers" << std::endl;
return EXIT_FAILURE;
}
size_t allocated = allocator->buffers(cfg.stream()).size();
std::cout << "Allocated " << allocated << " buffers for stream" << std::endl;
}
/*
* --------------------------------------------------------------------
* Frame Capture
*
* libcamera frames capture model is based on the 'Request' concept.
* For each frame a Request has to be queued to the Camera.
*
* A Request refers to (at least one) Stream for which a Buffer that
* will be filled with image data shall be added to the Request.
*
* A Request is associated with a list of Controls, which are tunable
* parameters (similar to v4l2_controls) that have to be applied to
* the image.
*
* Once a request completes, all its buffers will contain image data
* that applications can access and for each of them a list of metadata
* properties that reports the capture parameters applied to the image.
*/
Stream *stream = streamConfig.stream();
const std::vector<std::unique_ptr<FrameBuffer>> &buffers = allocator->buffers(stream);
std::vector<std::unique_ptr<Request>> requests;
for (unsigned int i = 0; i < buffers.size(); ++i) {
std::unique_ptr<Request> request = camera->createRequest();
if (!request)
{
std::cerr << "Can't create request" << std::endl;
return EXIT_FAILURE;
}
const std::unique_ptr<FrameBuffer> &buffer = buffers[i];
int ret = request->addBuffer(stream, buffer.get());
if (ret < 0)
{
std::cerr << "Can't set buffer for request"
<< std::endl;
return EXIT_FAILURE;
}
/*
* Controls can be added to a request on a per frame basis.
*/
ControlList &controls = request->controls();
controls.set(controls::Brightness, 0.5);
requests.push_back(std::move(request));
}
/*
* --------------------------------------------------------------------
* Signal&Slots
*
* libcamera uses a Signal&Slot based system to connect events to
* callback operations meant to handle them, inspired by the QT graphic
* toolkit.
*
* Signals are events 'emitted' by a class instance.
* Slots are callbacks that can be 'connected' to a Signal.
*
* A Camera exposes Signals, to report the completion of a Request and
* the completion of a Buffer part of a Request to support partial
* Request completions.
*
* In order to receive the notification for request completions,
* applications shall connecte a Slot to the Camera 'requestCompleted'
* Signal before the camera is started.
*/
camera->requestCompleted.connect(requestComplete);
/*
* --------------------------------------------------------------------
* Start Capture
*
* In order to capture frames the Camera has to be started and
* Request queued to it. Enough Request to fill the Camera pipeline
* depth have to be queued before the Camera start delivering frames.
*
* For each delivered frame, the Slot connected to the
* Camera::requestCompleted Signal is called.
*/
camera->start();
for (std::unique_ptr<Request> &request : requests)
camera->queueRequest(request.get());
/*
* --------------------------------------------------------------------
* Run an EventLoop
*
* In order to dispatch events received from the video devices, such
* as buffer completions, an event loop has to be run.
*/
loop.timeout(TIMEOUT_SEC);
int ret = loop.exec();
std::cout << "Capture ran for " << TIMEOUT_SEC << " seconds and "
<< "stopped with exit status: " << ret << std::endl;
/*
* --------------------------------------------------------------------
* Clean Up
*
* Stop the Camera, release resources and stop the CameraManager.
* libcamera has now released all resources it owned.
*/
camera->stop();
allocator->free(stream);
delete allocator;
camera->release();
camera.reset();
cm->stop();
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,99 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2020, Google Inc.
*
* event_loop.cpp - Event loop based on cam
*/
#include "event_loop.hpp"
#include <assert.h>
#include <event2/event.h>
#include <event2/thread.h>
EventLoop *EventLoop::instance_ = nullptr;
EventLoop::EventLoop()
{
assert(!instance_);
evthread_use_pthreads();
event_ = event_base_new();
instance_ = this;
}
EventLoop::~EventLoop()
{
instance_ = nullptr;
event_base_free(event_);
libevent_global_shutdown();
}
int EventLoop::exec()
{
exitCode_ = -1;
exit_.store(false, std::memory_order_release);
while (!exit_.load(std::memory_order_acquire)) {
dispatchCalls();
event_base_loop(event_, EVLOOP_NO_EXIT_ON_EMPTY);
}
return exitCode_;
}
void EventLoop::exit(int code)
{
exitCode_ = code;
exit_.store(true, std::memory_order_release);
interrupt();
}
void EventLoop::interrupt()
{
event_base_loopbreak(event_);
}
void EventLoop::timeoutTriggered(int fd, short event, void *arg)
{
EventLoop *self = static_cast<EventLoop *>(arg);
self->exit();
}
void EventLoop::timeout(unsigned int sec)
{
struct event *ev;
struct timeval tv;
tv.tv_sec = sec;
tv.tv_usec = 0;
ev = evtimer_new(event_, &timeoutTriggered, this);
evtimer_add(ev, &tv);
}
void EventLoop::callLater(const std::function<void()> &func)
{
{
std::unique_lock<std::mutex> locker(lock_);
calls_.push_back(func);
}
interrupt();
}
void EventLoop::dispatchCalls()
{
std::unique_lock<std::mutex> locker(lock_);
for (auto iter = calls_.begin(); iter != calls_.end(); ) {
std::function<void()> call = std::move(*iter);
iter = calls_.erase(iter);
locker.unlock();
call();
locker.lock();
}
}

View File

@ -0,0 +1,45 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2020, Google Inc.
*
* event_loop.h - Event loop based on cam's
*/
#ifndef __SIMPLE_CAM_EVENT_LOOP_H__
#define __SIMPLE_CAM_EVENT_LOOP_H__
#include <atomic>
#include <functional>
#include <list>
#include <mutex>
struct event_base;
class EventLoop
{
public:
EventLoop();
~EventLoop();
void exit(int code = 0);
int exec();
void timeout(unsigned int sec);
void callLater(const std::function<void()> &func);
private:
static EventLoop *instance_;
static void timeoutTriggered(int fd, short event, void *arg);
struct event_base *event_;
std::atomic<bool> exit_;
int exitCode_;
std::list<std::function<void()>> calls_;
std::mutex lock_;
void interrupt();
void dispatchCalls();
};
#endif /* __SIMPLE_CAM_EVENT_LOOP_H__ */

89
camera_example/flake.nix Normal file
View File

@ -0,0 +1,89 @@
{
description = "Cross compile libcamera app for Raspberry Pi Zero";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = import nixpkgs {
inherit system;
crossSystem = {
config = "armv6l-unknown-linux-gnueabihf";
};
};
in
{
devShells.${system}.default = pkgs.mkShell {
nativeBuildInputs = [
pkgs.pkgsBuildBuild.pkg-config
];
buildInputs = with pkgs; [
libcamera
libGL
libglvnd
mesa
];
shellHook = ''
export CC=${pkgs.stdenv.cc.targetPrefix}gcc
export CXX=${pkgs.stdenv.cc.targetPrefix}g++
export PKG_CONFIG_ALLOW_CROSS=1
echo "Cross shell ready"
'';
};
packages.${system}.default = pkgs.stdenv.mkDerivation {
pname = "rpi-zero-libcamera-app";
version = "0.1.0";
src = ./.;
nativeBuildInputs = [
pkgs.pkgsBuildBuild.pkg-config
];
buildInputs = with pkgs; [
libcamera
libevent
libGL
libglvnd
mesa
];
buildPhase = ''
runHook preBuild
export PKG_CONFIG_ALLOW_CROSS=1
mkdir -p build
${pkgs.stdenv.cc.targetPrefix}g++ \
camera.cpp \
event_loop.cpp \
-o build/camera \
$(pkg-config --cflags --libs libcamera libevent) \
-levent -levent_pthreads -lpthread
runHook postBuild
'';
installPhase = ''
mkdir -p $out/bin
cp build/camera $out/bin/camera
'';
};
apps.${system}.default = {
type = "app";
program = "${self.packages.${system}.default}/bin/camera";
};
};
}