[libcamera-devel] [PATCH 25/30] libcamera: allocator: Add BufferAllocator to help applications allocate buffers
Laurent Pinchart
laurent.pinchart at ideasonboard.com
Sun Dec 15 04:12:16 CET 2019
Hi Niklas,
Thank you for the patch.
On Mon, Dec 02, 2019 at 02:16:24PM +0100, Jacopo Mondi wrote:
> On Wed, Nov 27, 2019 at 12:36:15AM +0100, Niklas Söderlund wrote:
> > The FrameBuffer interface is based on the idea that all buffers are
> > allocated outside libcamera and are only used by it. This is done to
s/outside/externally to/
> > make the API simpler and so that both internal and external buffers are
> > handled in the same way.
"This is meant to create a simpler API centered around usage of buffers,
regardless of where they come from."
> > As V4L2 do not provide a way to allocate buffers without the help of a
> > video device add an application facing helper which hide this. This
> > allow applications to allocate buffers and then use them with cameras.
"Linux however lacks a centralized allocator at the moment, and not all
users of libcamera are expected to use another device that could provide
suitable buffers for the camera. This patch thus adds a helper class to
allocate buffers internally in libcamera, in a way that matches the
needs of the FrameBuffer-based API."
>
> I'm sorry but I really fail to see why we need this abstraction.
>
> The BufferAllocator
> - is Constructed with a camera
> - allocate buffers from Streams with a StreamConfiguration
> - maintain a list of allocated buffers, indexed by stream
>
> The usage I see in cam is
>
> allocator = new Allocator(Camera);
> for (streamConfig : camera->config())
> allocator.allocate(streamConfig->stream(),
> streamConfig);
>
> for (nbufs : numBuffers)
> request = new Request()
>
> for (streamConfig : camera->config())
> buffer = allocator.buffer(streamConfig->stream())[nbufs];
> request->addBuffer(streamConfig->stream(), buffer);
>
> My first question would be
> - if allocator.allocate takes a Stream and a config (the stream could
> be accessed from the config, but that's a different issue) and just
> calls 'stream.allocateBuffers(config)' what prevents application from
> calling stream.allocateBuffers() themselves ?
Stream::allocateBuffers() is an internal API. The idea behind the buffer
allocator and the rest of the buffer rework is to create a single API
centered around buffer usage, but this isn't possible due to the lack of
a suitable centralized allocator at the moment. We thus need to provide
a way for applications to allocate buffers internally in the camera, and
we have decided to do so through a single helper class to avoid exposing
any other allocation-related API to applications. The helper could then
be dropped later when a centralized allocator will be available, or
converted to use the centralized allocator transparently.
> - The whole point of allocator is to maintain a map to Stream to
> vectors of Buffers ? So can't that list live in the Stream itself ?
The main point of the allocator is to handle as much of the accounting
needed for buffer allocation internally to avoid spreading it through
libcamera. We can't achieve 100% of this goal as we still need to
allocate the buffers on V4L2 video nodes.
> Can't the map live in the camera if we want to ? They need to be put
> together when adding a buffer-stream pair to a request, and the
> whole dance of allocation and buffer access which all needs a Stream
> to work seems like quite a complex and API for applications.
> The above portion of code could be simplified to
>
> for (stream : camera->streams()) {
> StreamConfiguration *config = camera->streamConfig(stream);
> ^- this is easy to implement
> stream->allocate(config);
>
> for (nbufs : numBuffers)
> request = new Request()
>
> for (streamConfig : camera->config())
> buffer = streamConfig->stream().buffer(i);
> request->addBuffer(streamConfig->stream(), buffer);
>
> Isn't this less convoluted ?
>
> How do we expect the buffer importing to look like, as I am missing
> how this series handles it ?
>
> > Signed-off-by: Niklas Söderlund <niklas.soderlund at ragnatech.se>
> > ---
> > include/libcamera/allocator.h | 39 ++++++++++
> > include/libcamera/meson.build | 1 +
> > include/libcamera/stream.h | 1 +
> > src/libcamera/allocator.cpp | 141 ++++++++++++++++++++++++++++++++++
> > src/libcamera/meson.build | 1 +
> > 5 files changed, 183 insertions(+)
> > create mode 100644 include/libcamera/allocator.h
> > create mode 100644 src/libcamera/allocator.cpp
> >
> > diff --git a/include/libcamera/allocator.h b/include/libcamera/allocator.h
> > new file mode 100644
> > index 0000000000000000..36fce38491b5f3ef
> > --- /dev/null
> > +++ b/include/libcamera/allocator.h
> > @@ -0,0 +1,39 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2019, Google Inc.
> > + *
> > + * allocator.h - Buffer allocator
buffer_allocator.h ? Or maybe even better framebuffer_allocator.h ? Same
for the .cpp file.
s/Buffer/FrameBuffer/
> > + */
> > +#ifndef __LIBCAMERA_ALLOCATOR_H__
And __LIBCAMERA_BUFFER_ALLOCATOR_H__ (or FRAMEBUFFER as appropriate) ?
> > +#define __LIBCAMERA_ALLOCATOR_H__
> > +
> > +#include <map>
> > +#include <memory>
> > +#include <vector>
> > +
> > +namespace libcamera {
> > +
> > +class Camera;
> > +class FrameBuffer;
> > +class Stream;
> > +struct StreamConfiguration;
> > +
> > +class BufferAllocator
FrameBufferAllocator ?
> > +{
> > +public:
> > + BufferAllocator(std::shared_ptr<Camera> camera);
> > + ~BufferAllocator();
> > +
> > + int allocate(Stream *stream, const StreamConfiguration &config);
> > + void release(Stream *stream);
> > +
> > + const std::vector<FrameBuffer *> &buffers(Stream *stream) const;
> > +
> > +private:
> > + std::shared_ptr<Camera> camera_;
> > + std::map<Stream *, std::vector<FrameBuffer *>> buffers_;
> > +};
> > +
> > +} /* namespace libcamera */
> > +
> > +#endif /* __LIBCAMERA_ALLOCATOR_H__ */
> > diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build
> > index 99abf06099407c1f..0d0ba2248fd8a679 100644
> > --- a/include/libcamera/meson.build
> > +++ b/include/libcamera/meson.build
> > @@ -1,4 +1,5 @@
> > libcamera_api = files([
> > + 'allocator.h',
> > 'bound_method.h',
> > 'buffer.h',
> > 'camera.h',
> > diff --git a/include/libcamera/stream.h b/include/libcamera/stream.h
> > index a3c692c347340382..8e653408fd54cf74 100644
> > --- a/include/libcamera/stream.h
> > +++ b/include/libcamera/stream.h
> > @@ -85,6 +85,7 @@ public:
> > MemoryType memoryType() const { return memoryType_; }
> >
> > protected:
> > + friend class BufferAllocator; /* To allocate and release buffers. */
> > friend class Camera;
> >
> > virtual int allocateBuffers(const StreamConfiguration &config,
> > diff --git a/src/libcamera/allocator.cpp b/src/libcamera/allocator.cpp
> > new file mode 100644
> > index 0000000000000000..8b517c809c05cbcd
> > --- /dev/null
> > +++ b/src/libcamera/allocator.cpp
> > @@ -0,0 +1,141 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2019, Google Inc.
> > + *
> > + * allocator.cpp - Buffer allocator
s/Buffer/FrameBuffer/ (and similar changes below where appropriate)
> > + */
> > +
> > +#include <libcamera/allocator.h>
> > +
> > +#include <errno.h>
> > +#include <string.h>
> > +#include <sys/mman.h>
> > +#include <unistd.h>
I think you only need errno.h.
> > +#include <libcamera/camera.h>
> > +#include <libcamera/stream.h>
You should also include <libcamera/buffer.h>.
> > +#include "log.h"
> > +
> > +/**
> > + * \file allocator.h
> > + * \brief Buffer allocator
> > + */
> > +
> > +namespace libcamera {
> > +
> > +LOG_DEFINE_CATEGORY(Allocator)
> > +
> > +/**
> > + * \class BufferAllocator
> > + * \brief Buffer allocator for applications
> > + *
> > + * All buffers are to be treated as if they where allocated outside of the
> > + * camera. In some situations however the only source applications can allocate
> > + * buffers from is the camera. The BufferAllocator is the interface applications
> > + * shall use in these situations.
> > + *
> > + * The buffer allocator creates buffers using resources from a camera and/or
> > + * a stream. The buffers allocated this way are owned by the application and it
> > + * is responsible for their lifetime management. But just as buffers allocated
> > + * external from cameras and streams it's not valid to free a buffer while it's
> > + * queue to a camera.
> > + *
> > + * All buffers allocator are automatically freed when the allocator object is
> > + * deleted. It is the applications responsibility to make sure this do not
> > + * happen while one or more of the buffers are queued to a camera.
> > + *
> > + * If buffers are allocated outside the scope of libcamera by the application it
> > + * do not need to interact with the buffer allocator.
* The libcamera API is designed to consume buffers provided by applications as
* FrameBuffer instances. This makes libcamera a user of buffers exported by
* other devices (such as displays or video encoders), or allocated from an
* external allocator (such as ION on Android platforms). In some situations,
* applications do not have any mean to allocate or get hold of suitable
* buffers, for instance when no other device is involved, on Linux platforms
* that lack a centralized allocator. The FrameBufferAllocator class provides a
* buffer allocator that can be used in these situations.
*
* Applications create a framebuffer allocator for a Camera, and use it to
* allocate buffers for streams of a CameraConfiguration with allocate(). They
* control which streams to allocate buffers for, and can thus use external
* buffers for a subset of the streams if desired.
*
* Buffers are deleted for a stream with release(), and destroying the allocator
* automatically deletes all allocated buffers. Applications own the buffers
* allocated by the FrameBufferAllocator and are responsible for ensuring the
* buffers are not deleted while they are in use (part of a Request that has
* been queued and hasn't completed yet).
*
* Usage of the FrameBufferAllocator is optional, if all buffers for a camera
* are provided externally applications shall not use this class.
> > + */
> > +
> > +/**
> > + * \brief Create a BufferAllocator serving a camera
> > + * \param[in] camera Camera the allocator shall serve
s/Camera/The camera/
and maybe just
* \param[in] camera The camera
> > + */
> > +
> > +BufferAllocator::BufferAllocator(std::shared_ptr<Camera> camera)
> > + : camera_(camera)
> > +{
> > +}
> > +
> > +BufferAllocator::~BufferAllocator()
> > +{
> > + for (auto &value : buffers_) {
> > + Stream *stream = value.first;
> > + std::vector<FrameBuffer *> &buffers = value.second;
> > +
> > + for (FrameBuffer *buffer : buffers)
> > + delete buffer;
> > +
> > + buffers.clear();
> > +
> > + stream->releaseBuffers();
> > + }
> > +
> > + buffers_.clear();
> > +}
> > +
> > +/**
> > + * \brief Allocate buffers from a stream
* \brief Allocate buffers for a stream with a configuration
> > + * \param[in] stream The stream to allocate buffers from
s/from/for/
> > + * \param[in] config The configuration described the buffers to be allocated
The configuration doesn't describe the buffers to be allocated. Unless
someone has a better proposal, I would just go for
* \param[in] config The stream configuration
> > + *
> > + * Allocate buffers matching exactly what is described in \a config. If buffers
> > + * matching \a config can't be allocated an error is returned and no buffers are
> > + * allocated.
How about
* Allocate buffers suitable for capturing frames from the \a stream configured
* as described by \a config. The configuration shall be valid for the stream as
* checked CameraConfiguration::validate().
*
* Upon successful allocation, the allocated buffers can be retrieved with the
* buffers() method.
I would drop the second sentence.
Doesn't the camera need to have been configured too ? I think the
documentation needs to be expanded to explain when allocating buffers is
allowed. I also wonder what happens when reconfiguring a camera for
which buffers have been allocated. Is this supported, and if so, in what
conditions ? If you explain how this works I can help expanding the
documentation.
> > + *
> > + * \return 0 on success or a negative error code otherwise
How about enumerating the error codes ? I think we should return -EINVAL
if the stream doesn't belong to the camera of the configuration isn't
valid, -ENOMEM if the buffers can't be allocated and -EBUSY if they are
already allocated. But this would require guaranteeing that
Stream::allocateBuffers() only return those codes. I think it's doable,
but should be documented.
> > + */
> > +int BufferAllocator::allocate(Stream *stream, const StreamConfiguration &config)
> > +{
> > + auto iter = camera_->streams().find(stream);
> > + if (iter != camera_->streams().end())
> > + return stream->allocateBuffers(config, &buffers_[stream]);
> > +
> > + LOG(Allocator, Error) << "Stream do not belong to " << camera_->name();
s/do/does/
> > + return -EINVAL;
I think you should handle the errors as they are detected.
auto iter = camera_->streams().find(stream);
if (iter == camera_->streams().end()) {
LOG(Allocator, Error)
<< "Stream does not belong to " << camera_->name();
return -EINVAL;
}
return stream->allocateBuffers(config, &buffers_[stream]);
Should you also protect against double allocation with
if (buffers_.count(stream)) {
LOG(Allocator, Error) << "Buffers already allocated for stream";
return -EBUSY;
}
> > +}
> > +
> > +/**
> > + * \brief Free allocated buffers
* \brief Free buffers previously allocated for a \a stream
> > + * \param[in] stream The stream to free buffers for
> > + *
> > + * Free buffers allocated with allocate().
Add "This invalidates the buffers returned by buffers()."
> > + */
> > +void BufferAllocator::release(Stream *stream)
> > +{
> > + auto iter = buffers_.find(stream);
> > + if (iter == buffers_.end())
> > + return;
> > +
> > + std::vector<FrameBuffer *> &buffers = iter->second;
> > +
> > + for (FrameBuffer *buffer : buffers)
> > + delete buffer;
> > +
> > + buffers.clear();
> > +
> > + stream->releaseBuffers();
> > +
> > + buffers_.erase(iter);
> > +}
> > +
> > +/**
> > + * \brief Retrive array of allocated buffers
* \brief Retrieve the buffers allocated for a \a stream
> > + * \param[in] stream The stream to retrive buffers for
s/retrive/retrieve/
*
* This method shall only be called after successfully allocating buffers for
* \a stream with allocate(). The returned buffers are valid until the earlier
* of a call to release() for the same stream or the destruction of the
* FrameBufferPool instance.
*
> > + *
> > + * \return Array of buffers
* \return The buffers allocated for the \a stream
> > + */
> > +const std::vector<FrameBuffer *> &BufferAllocator::buffers(Stream *stream) const
> > +{
> > + static std::vector<FrameBuffer *> empty;
> > +
> > + auto iter = buffers_.find(stream);
> > + if (iter == buffers_.end())
> > + return empty;
> > +
> > + return iter->second;
> > +}
> > +
> > +} /* namespace libcamera */
> > diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
> > index c4f965bd7413b37e..b7041e003fdb1d49 100644
> > --- a/src/libcamera/meson.build
> > +++ b/src/libcamera/meson.build
> > @@ -1,4 +1,5 @@
> > libcamera_sources = files([
> > + 'allocator.cpp',
> > 'bound_method.cpp',
> > 'buffer.cpp',
> > 'byte_stream_buffer.cpp',
--
Regards,
Laurent Pinchart
More information about the libcamera-devel
mailing list