ai fixed all our issues for now
This commit is contained in:
parent
addb823f0c
commit
b26a6ea7dd
@ -1,402 +1,277 @@
|
|||||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
#include <atomic>
|
||||||
/*
|
#include <csignal>
|
||||||
* Copyright (C) 2020, Ideas on Board Oy.
|
#include <cstdint>
|
||||||
*
|
#include <errno.h>
|
||||||
* A simple libcamera capture example
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <iomanip>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <turbojpeg.h>
|
||||||
#include <libcamera/libcamera.h>
|
#include <libcamera/libcamera.h>
|
||||||
|
#include <libcamera/control_ids.h>
|
||||||
|
#include <libcamera/formats.h>
|
||||||
|
|
||||||
#include "event_loop.hpp"
|
#include "event_loop.hpp"
|
||||||
|
|
||||||
#define TIMEOUT_SEC 3
|
|
||||||
|
|
||||||
using namespace libcamera;
|
using namespace libcamera;
|
||||||
static std::shared_ptr<Camera> camera;
|
|
||||||
static EventLoop loop;
|
|
||||||
|
|
||||||
/*
|
// --- knobs -------------------------------------------------------------------
|
||||||
* --------------------------------------------------------------------
|
// Goal: throughput >= latency >= quality. Tune here and rebuild.
|
||||||
* 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);
|
constexpr int kPort = 5000;
|
||||||
|
constexpr int kWidth = 640;
|
||||||
|
constexpr int kHeight = 480;
|
||||||
|
constexpr int kTargetFps = 30; // 0 = leave to camera defaults
|
||||||
|
constexpr int kBufferCount = 4; // pipeline depth
|
||||||
|
constexpr int kJpegQuality = 80;
|
||||||
|
constexpr int kJpegSubsamp = TJSAMP_420;
|
||||||
|
constexpr int kSendBufBytes = 1 << 20; // 1 MiB SO_SNDBUF (throughput)
|
||||||
|
|
||||||
static void requestComplete(Request *request)
|
// Autofocus: true → continuous AF; false → fixed lens at kLensDiopters
|
||||||
|
// diopters: 0 = infinity, ~10 = closest. Silently ignored on cameras
|
||||||
|
// without AF (Pi cam v1/v2). Pi cam v3 supports it.
|
||||||
|
constexpr bool kAutoFocus = true;
|
||||||
|
constexpr float kLensDiopters = 0.0f;
|
||||||
|
|
||||||
|
// Pi viewfinder native format: XRGB8888 — DRM byte order, so memory layout
|
||||||
|
// is B,G,R,X. Feed turbojpeg TJPF_BGRX so it walks 4 B/pixel and ignores X.
|
||||||
|
// No software color conversion, no row copy.
|
||||||
|
constexpr int kTjPixelFmt = TJPF_BGRX;
|
||||||
|
|
||||||
|
// --- state -------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::shared_ptr<Camera> camera;
|
||||||
|
EventLoop loop;
|
||||||
|
|
||||||
|
std::atomic<int> client_fd{-1};
|
||||||
|
|
||||||
|
struct Mapped { uint8_t *data; size_t size; };
|
||||||
|
std::unordered_map<FrameBuffer *, Mapped> mapped;
|
||||||
|
|
||||||
|
tjhandle tj_handle = nullptr;
|
||||||
|
uint8_t *jpeg_buf = nullptr;
|
||||||
|
unsigned long jpeg_cap = 0;
|
||||||
|
|
||||||
|
// --- net ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
bool sendAll(int fd, const uint8_t *p, size_t n)
|
||||||
{
|
{
|
||||||
if (request->status() == Request::RequestCancelled)
|
while (n) {
|
||||||
return;
|
ssize_t r = ::send(fd, p, n, MSG_NOSIGNAL);
|
||||||
|
if (r <= 0) return false;
|
||||||
loop.callLater(std::bind(&processRequest, request));
|
p += r;
|
||||||
|
n -= r;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void processRequest(Request *request)
|
void closeClient()
|
||||||
{
|
{
|
||||||
std::cout << std::endl
|
int fd = client_fd.exchange(-1);
|
||||||
<< "Request completed: " << request->toString() << std::endl;
|
if (fd >= 0) ::close(fd);
|
||||||
|
|
||||||
/*
|
|
||||||
* 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void serverThread()
|
||||||
|
{
|
||||||
|
int srv = ::socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||||
|
int yes = 1;
|
||||||
|
::setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
|
||||||
|
|
||||||
|
sockaddr_in addr{};
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_port = htons(kPort);
|
||||||
|
addr.sin_addr.s_addr = INADDR_ANY;
|
||||||
|
|
||||||
|
if (::bind(srv, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)) < 0
|
||||||
|
|| ::listen(srv, 4) < 0) {
|
||||||
|
perror("bind/listen");
|
||||||
|
::close(srv);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "tcp://0.0.0.0:" << kPort << " waiting...\n";
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
int c = ::accept(srv, nullptr, nullptr);
|
||||||
|
if (c < 0) {
|
||||||
|
if (errno == EINTR) continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int one = 1, sndbuf = kSendBufBytes;
|
||||||
|
::setsockopt(c, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
|
||||||
|
::setsockopt(c, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
|
||||||
|
int old = client_fd.exchange(c);
|
||||||
|
if (old >= 0) ::close(old);
|
||||||
|
std::cerr << "client connected\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- camera ------------------------------------------------------------------
|
||||||
|
|
||||||
|
void processRequest(Request *request);
|
||||||
|
|
||||||
|
void requestComplete(Request *request)
|
||||||
|
{
|
||||||
|
if (request->status() == Request::RequestCancelled)
|
||||||
|
return;
|
||||||
|
loop.callLater([request] { processRequest(request); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void processRequest(Request *request)
|
||||||
|
{
|
||||||
|
auto it = request->buffers().begin();
|
||||||
|
const Stream *stream = it->first;
|
||||||
|
FrameBuffer *buffer = it->second;
|
||||||
|
|
||||||
|
int fd = client_fd.load(std::memory_order_acquire);
|
||||||
|
if (fd >= 0 && buffer->metadata().status == FrameMetadata::FrameSuccess) {
|
||||||
|
const StreamConfiguration &cfg = stream->configuration();
|
||||||
|
const Mapped &m = mapped[buffer];
|
||||||
|
|
||||||
|
unsigned long size = jpeg_cap;
|
||||||
|
if (tjCompress2(tj_handle,
|
||||||
|
m.data, cfg.size.width, cfg.stride, cfg.size.height,
|
||||||
|
kTjPixelFmt,
|
||||||
|
&jpeg_buf, &size,
|
||||||
|
kJpegSubsamp, kJpegQuality,
|
||||||
|
TJFLAG_FASTDCT | TJFLAG_NOREALLOC) == 0) {
|
||||||
|
uint32_t len = htonl(static_cast<uint32_t>(size));
|
||||||
|
if (!sendAll(fd, reinterpret_cast<uint8_t *>(&len), sizeof(len))
|
||||||
|
|| !sendAll(fd, jpeg_buf, size)) {
|
||||||
|
std::cerr << "client gone\n";
|
||||||
|
closeClient();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::cerr << "tjCompress2: " << tjGetErrorStr2(tj_handle) << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request->reuse(Request::ReuseBuffers);
|
||||||
|
camera->queueRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// --- main --------------------------------------------------------------------
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
/*
|
std::signal(SIGPIPE, SIG_IGN);
|
||||||
* --------------------------------------------------------------------
|
std::thread(serverThread).detach();
|
||||||
* 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();
|
|
||||||
|
|
||||||
/*
|
auto cm = std::make_unique<CameraManager>();
|
||||||
* Just as a test, generate names of the Cameras registered in the
|
cm->start();
|
||||||
* system, and list them.
|
if (cm->cameras().empty()) {
|
||||||
*/
|
std::cerr << "no cameras\n";
|
||||||
for (auto const &camera : cm->cameras())
|
cm->stop();
|
||||||
std::cout << " - " << camera.get()->id() << std::endl;
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
camera = cm->get(cm->cameras()[0]->id());
|
||||||
* --------------------------------------------------------------------
|
camera->acquire();
|
||||||
* 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();
|
auto config = camera->generateConfiguration({ StreamRole::Viewfinder });
|
||||||
camera = cm->get(cameraId);
|
StreamConfiguration &sc = config->at(0);
|
||||||
camera->acquire();
|
sc.pixelFormat = formats::XRGB8888;
|
||||||
|
sc.size = { kWidth, kHeight };
|
||||||
|
sc.bufferCount = kBufferCount;
|
||||||
|
|
||||||
/*
|
if (config->validate() == CameraConfiguration::Invalid
|
||||||
* Stream
|
|| sc.pixelFormat != formats::XRGB8888) {
|
||||||
*
|
std::cerr << "got " << sc.pixelFormat.toString()
|
||||||
* Each Camera supports a variable number of Stream. A Stream is
|
<< ", need XRGB8888\n";
|
||||||
* produced by processing data produced by an image source, usually
|
return 1;
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
std::cerr << "stream " << sc.size.toString()
|
||||||
* --------------------------------------------------------------------
|
<< " " << sc.pixelFormat.toString()
|
||||||
* Camera Configuration.
|
<< " stride " << sc.stride
|
||||||
*
|
<< " buffers " << sc.bufferCount << "\n";
|
||||||
* 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 } );
|
|
||||||
|
|
||||||
/*
|
if (camera->configure(config.get()) < 0) {
|
||||||
* The CameraConfiguration contains a StreamConfiguration instance
|
std::cerr << "configure failed\n";
|
||||||
* for each StreamRole requested by the application, provided
|
return 1;
|
||||||
* 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;
|
|
||||||
|
|
||||||
/*
|
auto *allocator = new FrameBufferAllocator(camera);
|
||||||
* Each StreamConfiguration parameter which is part of a
|
Stream *stream = sc.stream();
|
||||||
* CameraConfiguration can be independently modified by the
|
if (allocator->allocate(stream) < 0) {
|
||||||
* application.
|
std::cerr << "alloc failed\n";
|
||||||
*
|
return 1;
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
for (const std::unique_ptr<FrameBuffer> &b : allocator->buffers(stream)) {
|
||||||
* Validating a CameraConfiguration -before- applying it will adjust it
|
const FrameBuffer::Plane &p = b->planes()[0];
|
||||||
* to a valid configuration which is as close as possible to the one
|
void *m = mmap(nullptr, p.length, PROT_READ, MAP_SHARED,
|
||||||
* requested.
|
p.fd.get(), p.offset);
|
||||||
*/
|
if (m == MAP_FAILED) { perror("mmap"); return 1; }
|
||||||
config->validate();
|
mapped[b.get()] = { static_cast<uint8_t *>(m), p.length };
|
||||||
std::cout << "Validated viewfinder configuration is: "
|
}
|
||||||
<< streamConfig.toString() << std::endl;
|
|
||||||
|
|
||||||
/*
|
tj_handle = tjInitCompress();
|
||||||
* Once we have a validated configuration, we can apply it to the
|
jpeg_cap = tjBufSize(sc.size.width, sc.size.height, kJpegSubsamp);
|
||||||
* Camera.
|
jpeg_buf = tjAlloc(jpeg_cap);
|
||||||
*/
|
|
||||||
camera->configure(config.get());
|
|
||||||
|
|
||||||
/*
|
std::vector<std::unique_ptr<Request>> requests;
|
||||||
* --------------------------------------------------------------------
|
for (const std::unique_ptr<FrameBuffer> &b : allocator->buffers(stream)) {
|
||||||
* Buffer Allocation
|
std::unique_ptr<Request> r = camera->createRequest();
|
||||||
*
|
if (!r || r->addBuffer(stream, b.get()) < 0) {
|
||||||
* Now that a camera has been configured, it knows all about its
|
std::cerr << "request setup failed\n";
|
||||||
* Streams sizes and formats. The captured images need to be stored in
|
return 1;
|
||||||
* framebuffers which can either be provided by the application to the
|
}
|
||||||
* library, or allocated in the Camera and exposed to the application
|
requests.push_back(std::move(r));
|
||||||
* 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) {
|
// Initial controls: AF + frame-rate cap.
|
||||||
int ret = allocator->allocate(cfg.stream());
|
ControlList ctrls(camera->controls());
|
||||||
if (ret < 0) {
|
const ControlInfoMap &caps = camera->controls();
|
||||||
std::cerr << "Can't allocate buffers" << std::endl;
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t allocated = allocator->buffers(cfg.stream()).size();
|
if (caps.count(&controls::AfMode)) {
|
||||||
std::cout << "Allocated " << allocated << " buffers for stream" << std::endl;
|
if (kAutoFocus) {
|
||||||
}
|
ctrls.set(controls::AfMode, controls::AfModeContinuous);
|
||||||
|
} else {
|
||||||
|
ctrls.set(controls::AfMode, controls::AfModeManual);
|
||||||
|
ctrls.set(controls::LensPosition, kLensDiopters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (kTargetFps > 0) {
|
||||||
|
int64_t dur = 1000000 / kTargetFps;
|
||||||
|
ctrls.set(controls::FrameDurationLimits,
|
||||||
|
Span<const int64_t, 2>({ dur, dur }));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
camera->requestCompleted.connect(requestComplete);
|
||||||
* --------------------------------------------------------------------
|
if (camera->start(&ctrls) < 0) {
|
||||||
* Frame Capture
|
std::cerr << "camera start failed\n";
|
||||||
*
|
return 1;
|
||||||
* libcamera frames capture model is based on the 'Request' concept.
|
}
|
||||||
* For each frame a Request has to be queued to the Camera.
|
for (std::unique_ptr<Request> &r : requests)
|
||||||
*
|
camera->queueRequest(r.get());
|
||||||
* 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];
|
std::cerr << "streaming, Ctrl+C to stop\n";
|
||||||
int ret = request->addBuffer(stream, buffer.get());
|
loop.exec();
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
std::cerr << "Can't set buffer for request"
|
|
||||||
<< std::endl;
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
camera->stop();
|
||||||
* Controls can be added to a request on a per frame basis.
|
camera->requestCompleted.disconnect();
|
||||||
*/
|
for (auto &kv : mapped) munmap(kv.second.data, kv.second.size);
|
||||||
ControlList &controls = request->controls();
|
allocator->free(stream);
|
||||||
controls.set(controls::Brightness, 0.5);
|
delete allocator;
|
||||||
|
camera->release();
|
||||||
requests.push_back(std::move(request));
|
camera.reset();
|
||||||
}
|
cm->stop();
|
||||||
|
if (jpeg_buf) tjFree(jpeg_buf);
|
||||||
/*
|
if (tj_handle) tjDestroy(tj_handle);
|
||||||
* --------------------------------------------------------------------
|
closeClient();
|
||||||
* Signal&Slots
|
return 0;
|
||||||
*
|
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user