[libcamera-devel] [RFC 11/12] libcamera: buffer: Switch to new buffer API

Niklas Söderlund niklas.soderlund at ragnatech.se
Mon Oct 28 03:25:24 CET 2019


Switch to the new buffer API where all buffers are treated as external
buffers and are allocated outside the camera. For V4L2 backed cameras a
helper class BufferAllocator is provided to help applications create
buffers.

This patch is quiet large as it need to touch most areas of libcamera to
swap the API. It also restores the buffer sharing test and functionality
which was broken in the previous change. A follow up change to this one
will finalize the transition to the new API by removing code that is
left unused after this change.

Signed-off-by: Niklas Söderlund <niklas.soderlund at ragnatech.se>
---
 include/ipa/ipa_interface.h                   |   2 +-
 include/libcamera/buffer.h                    |  47 +--
 include/libcamera/request.h                   |   2 +-
 src/cam/buffer_writer.cpp                     |   7 +-
 src/cam/capture.cpp                           |  32 ++-
 src/cam/capture.h                             |   3 +-
 src/ipa/rkisp1/rkisp1.cpp                     |  14 +-
 src/libcamera/buffer.cpp                      | 243 +++-------------
 src/libcamera/camera.cpp                      |  47 +--
 src/libcamera/include/v4l2_videodevice.h      |  33 ++-
 src/libcamera/pipeline/ipu3/ipu3.cpp          | 199 ++++---------
 src/libcamera/pipeline/rkisp1/rkisp1.cpp      |  74 ++---
 src/libcamera/pipeline/uvcvideo.cpp           |  16 +-
 src/libcamera/pipeline/vimc.cpp               |  16 +-
 src/libcamera/request.cpp                     |  15 +-
 src/libcamera/v4l2_videodevice.cpp            | 267 ++++++++++--------
 src/qcam/main_window.cpp                      |  43 ++-
 src/qcam/main_window.h                        |   3 +
 test/camera/capture.cpp                       |  29 +-
 test/camera/statemachine.cpp                  |  12 +-
 test/v4l2_videodevice/buffer_sharing.cpp      |  21 +-
 test/v4l2_videodevice/capture_async.cpp       |  12 +-
 test/v4l2_videodevice/request_buffers.cpp     |  11 +-
 test/v4l2_videodevice/stream_on_off.cpp       |   6 +-
 test/v4l2_videodevice/v4l2_m2mdevice.cpp      |  36 +--
 test/v4l2_videodevice/v4l2_videodevice_test.h |   2 +-
 26 files changed, 435 insertions(+), 757 deletions(-)

diff --git a/include/ipa/ipa_interface.h b/include/ipa/ipa_interface.h
index 8fd658af5fdb2e6b..efb86a33f6f427ae 100644
--- a/include/ipa/ipa_interface.h
+++ b/include/ipa/ipa_interface.h
@@ -26,7 +26,7 @@ struct IPAStream {
 
 struct IPABuffer {
 	unsigned int id;
-	BufferMemory memory;
+	std::vector<std::pair<int, unsigned int>> planes;
 };
 
 struct IPAOperationData {
diff --git a/include/libcamera/buffer.h b/include/libcamera/buffer.h
index adb642ad5da072d2..3568c2e0cdf8d95b 100644
--- a/include/libcamera/buffer.h
+++ b/include/libcamera/buffer.h
@@ -22,11 +22,10 @@ class Stream;
 class Plane final
 {
 public:
-	Plane();
+	Plane(int fd, unsigned int length);
 	~Plane();
 
-	int dmabuf() const { return fd_; }
-	int setDmabuf(int fd, unsigned int length);
+	int fd() const { return fd_; }
 
 	void *mem();
 	unsigned int length() const { return length_; }
@@ -67,45 +66,17 @@ private:
 class Buffer final
 {
 public:
-	enum Status {
-		BufferSuccess,
-		BufferError,
-		BufferCancelled,
-	};
-
-	Buffer(unsigned int index = -1, const Buffer *metadata = nullptr);
-	Buffer(const Buffer &) = delete;
-	Buffer &operator=(const Buffer &) = delete;
+	Buffer(std::vector<std::pair<int, unsigned int>> planes);
+	~Buffer();
 
-	unsigned int index() const { return index_; }
-	const std::array<int, 3> &dmabufs() const { return dmabuf_; }
-	BufferMemory *mem() { return mem_; }
-
-	unsigned int bytesused() const { return bytesused_; }
-	uint64_t timestamp() const { return timestamp_; }
-	unsigned int sequence() const { return sequence_; }
+	unsigned int numPlanes() { return planes_.size(); }
+	Plane *plane(unsigned int plane);
 
-	Status status() const { return status_; }
-	Stream *stream() const { return stream_; }
+	std::array<int, 3> fds() const;
+	std::vector<std::pair<int, unsigned int>> description() const;
 
 private:
-	friend class Camera;
-	friend class Request;
-	friend class Stream;
-	friend class V4L2VideoDevice;
-
-	void cancel();
-
-	unsigned int index_;
-	std::array<int, 3> dmabuf_;
-	BufferMemory *mem_;
-
-	unsigned int bytesused_;
-	uint64_t timestamp_;
-	unsigned int sequence_;
-
-	Status status_;
-	Stream *stream_;
+	std::vector<Plane *> planes_;
 };
 
 class BufferInfo
diff --git a/include/libcamera/request.h b/include/libcamera/request.h
index 88ef7bf03fcfb77b..ac2beab46bea6466 100644
--- a/include/libcamera/request.h
+++ b/include/libcamera/request.h
@@ -40,7 +40,7 @@ public:
 	ControlList &metadata() { return *metadata_; }
 	const std::map<Stream *, Buffer *> &buffers() const { return bufferMap_; }
 	const BufferInfo &info(Buffer *frame) const { return info_.find(frame)->second; };
-	int addBuffer(std::unique_ptr<Buffer> buffer);
+	int addBuffer(Stream *stream, Buffer *buffer);
 	Buffer *findBuffer(Stream *stream) const;
 
 	uint64_t cookie() const { return cookie_; }
diff --git a/src/cam/buffer_writer.cpp b/src/cam/buffer_writer.cpp
index 3ee9e82ba216abb6..c9df851f3ecec056 100644
--- a/src/cam/buffer_writer.cpp
+++ b/src/cam/buffer_writer.cpp
@@ -43,10 +43,9 @@ int BufferWriter::write(Buffer *buffer, const BufferInfo &info,
 	if (fd == -1)
 		return -errno;
 
-	BufferMemory *mem = buffer->mem();
-	for (Plane &plane : mem->planes()) {
-		void *data = plane.mem();
-		unsigned int length = plane.length();
+	for (unsigned int i = 0; i < buffer->numPlanes(); i++) {
+		void *data = buffer->plane(i)->mem();
+		unsigned int length = buffer->plane(i)->length();
 
 		ret = ::write(fd, data, length);
 		if (ret < 0) {
diff --git a/src/cam/capture.cpp b/src/cam/capture.cpp
index 251e9f86c86b508d..8bd77dcec2d15f1c 100644
--- a/src/cam/capture.cpp
+++ b/src/cam/capture.cpp
@@ -57,7 +57,10 @@ int Capture::run(EventLoop *loop, const OptionsParser::Options &options)
 			writer_ = new BufferWriter();
 	}
 
-	ret = capture(loop);
+
+	BufferAllocator allocator(camera_);
+
+	ret = capture(loop, allocator);
 
 	if (options.isSet(OptFile)) {
 		delete writer_;
@@ -69,14 +72,21 @@ int Capture::run(EventLoop *loop, const OptionsParser::Options &options)
 	return ret;
 }
 
-int Capture::capture(EventLoop *loop)
+int Capture::capture(EventLoop *loop, BufferAllocator &allocator)
 {
 	int ret;
 
 	/* Identify the stream with the least number of buffers. */
 	unsigned int nbuffers = UINT_MAX;
-	for (StreamConfiguration &cfg : *config_)
-		nbuffers = std::min(nbuffers, cfg.bufferCount);
+	for (StreamConfiguration &cfg : *config_) {
+		ret = allocator.allocate(cfg.stream());
+		if (ret < 0) {
+			std::cerr << "Can't allocate buffers" << std::endl;
+			return -ENOMEM;
+		}
+
+		nbuffers = std::min(nbuffers, static_cast<unsigned int>(ret));
+	}
 
 	/*
 	 * TODO: make cam tool smarter to support still capture by for
@@ -93,9 +103,9 @@ int Capture::capture(EventLoop *loop)
 
 		for (StreamConfiguration &cfg : *config_) {
 			Stream *stream = cfg.stream();
-			std::unique_ptr<Buffer> buffer = stream->createBuffer(i);
+			Buffer *buffer = allocator.get(stream)[i];
 
-			ret = request->addBuffer(std::move(buffer));
+			ret = request->addBuffer(stream, buffer);
 			if (ret < 0) {
 				std::cerr << "Can't set buffer for request"
 					  << std::endl;
@@ -179,15 +189,7 @@ void Capture::requestComplete(Request *request)
 	for (auto it = buffers.begin(); it != buffers.end(); ++it) {
 		Stream *stream = it->first;
 		Buffer *buffer = it->second;
-		unsigned int index = buffer->index();
-
-		std::unique_ptr<Buffer> newBuffer = stream->createBuffer(index);
-		if (!newBuffer) {
-			std::cerr << "Can't create buffer " << index << std::endl;
-			return;
-		}
-
-		request->addBuffer(std::move(newBuffer));
+		request->addBuffer(stream, buffer);
 	}
 
 	camera_->queueRequest(request);
diff --git a/src/cam/capture.h b/src/cam/capture.h
index c692d48918f2de1d..ee5adad7cbaaae47 100644
--- a/src/cam/capture.h
+++ b/src/cam/capture.h
@@ -10,6 +10,7 @@
 #include <chrono>
 #include <memory>
 
+#include <libcamera/buffer.h>
 #include <libcamera/camera.h>
 #include <libcamera/request.h>
 #include <libcamera/stream.h>
@@ -26,7 +27,7 @@ public:
 
 	int run(EventLoop *loop, const OptionsParser::Options &options);
 private:
-	int capture(EventLoop *loop);
+	int capture(EventLoop *loop, libcamera::BufferAllocator &allocator);
 
 	void requestComplete(libcamera::Request *request);
 
diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp
index 9a13f5c7df17f335..42d3379ce84d600a 100644
--- a/src/ipa/rkisp1/rkisp1.cpp
+++ b/src/ipa/rkisp1/rkisp1.cpp
@@ -47,7 +47,7 @@ private:
 	void setControls(unsigned int frame);
 	void metadataReady(unsigned int frame, unsigned int aeState);
 
-	std::map<unsigned int, BufferMemory> bufferInfo_;
+	std::map<unsigned int, Buffer *> bufferInfo_;
 
 	ControlInfoMap ctrls_;
 
@@ -101,15 +101,17 @@ void IPARkISP1::configure(const std::map<unsigned int, IPAStream> &streamConfig,
 void IPARkISP1::mapBuffers(const std::vector<IPABuffer> &buffers)
 {
 	for (const IPABuffer &buffer : buffers) {
-		bufferInfo_[buffer.id] = buffer.memory;
-		bufferInfo_[buffer.id].planes()[0].mem();
+		bufferInfo_[buffer.id] = new Buffer(buffer.planes);
+		bufferInfo_[buffer.id]->plane(0)->mem();
 	}
 }
 
 void IPARkISP1::unmapBuffers(const std::vector<unsigned int> &ids)
 {
-	for (unsigned int id : ids)
+	for (unsigned int id : ids) {
+		delete bufferInfo_[id];
 		bufferInfo_.erase(id);
+	}
 }
 
 void IPARkISP1::processEvent(const IPAOperationData &event)
@@ -120,7 +122,7 @@ void IPARkISP1::processEvent(const IPAOperationData &event)
 		unsigned int bufferId = event.data[1];
 
 		const rkisp1_stat_buffer *stats =
-			static_cast<rkisp1_stat_buffer *>(bufferInfo_[bufferId].planes()[0].mem());
+			static_cast<rkisp1_stat_buffer *>(bufferInfo_[bufferId]->plane(0)->mem());
 
 		updateStatistics(frame, stats);
 		break;
@@ -130,7 +132,7 @@ void IPARkISP1::processEvent(const IPAOperationData &event)
 		unsigned int bufferId = event.data[1];
 
 		rkisp1_isp_params_cfg *params =
-			static_cast<rkisp1_isp_params_cfg *>(bufferInfo_[bufferId].planes()[0].mem());
+			static_cast<rkisp1_isp_params_cfg *>(bufferInfo_[bufferId]->plane(0)->mem());
 
 		queueRequest(frame, params, event.controls[0]);
 		break;
diff --git a/src/libcamera/buffer.cpp b/src/libcamera/buffer.cpp
index fce1ce5e49cbbf42..d00849520bc2b51c 100644
--- a/src/libcamera/buffer.cpp
+++ b/src/libcamera/buffer.cpp
@@ -48,69 +48,44 @@ LOG_DEFINE_CATEGORY(Buffer)
  * an image may or may not be contiguous.
  */
 
-Plane::Plane()
-	: fd_(-1), length_(0), mem_(0)
-{
-}
-
-Plane::~Plane()
-{
-	munmap();
-
-	if (fd_ != -1)
-		close(fd_);
-}
-
-/**
- * \fn Plane::dmabuf()
- * \brief Get the dmabuf file handle backing the buffer
- */
-
 /**
  * \brief Set the dmabuf file handle backing the buffer
  * \param[in] fd The dmabuf file handle
  * \param[in] length The size of the memory region
  *
- * The \a fd dmabuf file handle is duplicated and stored. The caller may close
- * the original file handle.
- *
- * \return 0 on success or a negative error code otherwise
+ * The \a fd dmabuf file handle is duplicated and stored.
  */
-int Plane::setDmabuf(int fd, unsigned int length)
+Plane::Plane(int fd, unsigned int length)
+	: fd_(-1), length_(length), mem_(nullptr)
 {
 	if (fd < 0) {
-		LOG(Buffer, Error) << "Invalid dmabuf fd provided";
-		return -EINVAL;
-	}
-
-	if (fd_ != -1) {
-		close(fd_);
-		fd_ = -1;
+		LOG(Buffer, Fatal) << "Invalid dmabuf fd provided";
+		return;
 	}
 
 	fd_ = dup(fd);
+
 	if (fd_ == -1) {
 		int ret = -errno;
-		LOG(Buffer, Error)
+		LOG(Buffer, Fatal)
 			<< "Failed to duplicate dmabuf: " << strerror(-ret);
-		return ret;
+		return;
 	}
+}
 
-	length_ = length;
+Plane::~Plane()
+{
+	munmap();
 
-	return 0;
+	if (fd_ != -1)
+		close(fd_);
 }
 
 /**
- * \brief Map the plane memory data to a CPU accessible address
- *
- * The file descriptor to map the memory from must be set by a call to
- * setDmaBuf() before calling this function.
- *
- * \sa setDmaBuf()
- *
- * \return 0 on success or a negative error code otherwise
+ * \fn Plane::fd()
+ * \brief Get the dmabuf file handle backing the buffer
  */
+
 int Plane::mmap()
 {
 	void *map;
@@ -131,13 +106,6 @@ int Plane::mmap()
 	return 0;
 }
 
-/**
- * \brief Unmap any existing CPU accessible mapping
- *
- * Unmap the memory mapped by an earlier call to mmap().
- *
- * \return 0 on success or a negative error code otherwise
- */
 int Plane::munmap()
 {
 	int ret = 0;
@@ -236,166 +204,49 @@ void BufferPool::destroyBuffers()
  * \return A vector containing all the buffers in the pool.
  */
 
-/**
- * \class Buffer
- * \brief A buffer handle and dynamic metadata
- *
- * The Buffer class references a buffer memory and associates dynamic metadata
- * related to the frame contained in the buffer. It allows referencing buffer
- * memory through a single interface regardless of whether the memory is
- * allocated internally in libcamera or provided externally through dmabuf.
- *
- * Buffer instances are allocated dynamically for a stream through
- * Stream::createBuffer(), added to a request with Request::addBuffer() and
- * deleted automatically after the request complete handler returns.
- */
-
-/**
- * \enum Buffer::Status
- * Buffer completion status
- * \var Buffer::BufferSuccess
- * The buffer has completed with success and contains valid data. All its other
- * metadata (such as bytesused(), timestamp() or sequence() number) are valid.
- * \var Buffer::BufferError
- * The buffer has completed with an error and doesn't contain valid data. Its
- * other metadata are valid.
- * \var Buffer::BufferCancelled
- * The buffer has been cancelled due to capture stop. Its other metadata are
- * invalid and shall not be used.
- */
-
-/**
- * \brief Construct a buffer not associated with any stream
- *
- * This method constructs an orphaned buffer not associated with any stream. It
- * is not meant to be called by applications, they should instead create buffers
- * for a stream with Stream::createBuffer().
- */
-Buffer::Buffer(unsigned int index, const Buffer *metadata)
-	: index_(index), dmabuf_({ -1, -1, -1 }),
-	  status_(Buffer::BufferSuccess), stream_(nullptr)
+Buffer::Buffer(std::vector<std::pair<int, unsigned int>> planes)
 {
-	if (metadata) {
-		bytesused_ = metadata->bytesused_;
-		sequence_ = metadata->sequence_;
-		timestamp_ = metadata->timestamp_;
-	} else {
-		bytesused_ = 0;
-		sequence_ = 0;
-		timestamp_ = 0;
-	}
+	for (std::pair<int, unsigned int> plane : planes)
+		planes_.push_back(new Plane(plane.first, plane.second));
 }
 
-/**
- * \fn Buffer::index()
- * \brief Retrieve the Buffer index
- * \return The buffer index
- */
+Buffer::~Buffer()
+{
+	for (Plane *plane : planes_)
+		delete plane;
+}
 
-/**
- * \fn Buffer::dmabufs()
- * \brief Retrieve the dmabuf file descriptors for all buffer planes
- *
- * The dmabufs array contains one dmabuf file descriptor per plane. Unused
- * entries are set to -1.
- *
- * \return The dmabuf file descriptors
- */
+Plane *Buffer::plane(unsigned int plane)
+{
+	if (plane > planes_.size()) {
+		LOG(Buffer, Error) << "Plane " << plane << " is out of range";
+		return nullptr;
+	}
 
-/**
- * \fn Buffer::mem()
- * \brief Retrieve the BufferMemory this buffer is associated with
- *
- * The association between the buffer and a BufferMemory instance is valid from
- * the time the request containing this buffer is queued to a camera to the end
- * of that request's completion handler.
- *
- * \return The BufferMemory this buffer is associated with
- */
+	return planes_[plane];
+}
 
-/**
- * \fn Buffer::bytesused()
- * \brief Retrieve the number of bytes occupied by the data in the buffer
- * \return Number of bytes occupied in the buffer
- */
+std::array<int, 3> Buffer::fds() const
+{
+	std::array<int, 3> ret = { -1, -1, -1 };
 
-/**
- * \fn Buffer::timestamp()
- * \brief Retrieve the time when the buffer was processed
- *
- * The timestamp is expressed as a number of nanoseconds since the epoch.
- *
- * \return Timestamp when the buffer was processed
- */
+	unsigned int i = 0;
+	for (const Plane *plane : planes_)
+		ret[i++] = plane->fd();
 
-/**
- * \fn Buffer::sequence()
- * \brief Retrieve the buffer sequence number
- *
- * The sequence number is a monotonically increasing number assigned to the
- * buffer processed by the stream. Gaps in the sequence numbers indicate
- * dropped frames.
- *
- * \return Sequence number of the buffer
- */
-
-/**
- * \fn Buffer::status()
- * \brief Retrieve the buffer status
- *
- * The buffer status reports whether the buffer has completed successfully
- * (BufferSuccess) or if an error occurred (BufferError).
- *
- * \return The buffer status
- */
+	return ret;
+}
 
-/**
- * \fn Buffer::request()
- * \brief Retrieve the request this buffer belongs to
- *
- * The intended callers of this method are buffer completion handlers that
- * need to associate a buffer to the request it belongs to.
- *
- * A Buffer is associated to a request by Request::prepare() and the
- * association is valid until the buffer completes. The returned request
- * pointer is valid only during that interval.
- *
- * \return The Request the Buffer belongs to, or nullptr if the buffer is
- * either completed or not associated with a request
- * \sa Buffer::setRequest()
- */
+std::vector<std::pair<int, unsigned int>> Buffer::description() const
+{
+	std::vector<std::pair<int, unsigned int>> desc;
 
-/**
- * \fn Buffer::stream()
- * \brief Retrieve the stream this buffer is associated with
- *
- * A Buffer is associated to the stream that created it with
- * Stream::createBuffer() and the association is valid until the buffer is
- * destroyed. Buffer instances that are created directly are not associated
- * with any stream.
- *
- * \return The Stream the Buffer is associated with, or nullptr if the buffer
- * is not associated with a stream
- */
+	for (const Plane *plane : planes_)
+		desc.emplace_back(plane->fd(), plane->length());
 
-/**
- * \brief Mark a buffer as cancel by setting its status to BufferCancelled
- */
-void Buffer::cancel()
-{
-	bytesused_ = 0;
-	timestamp_ = 0;
-	sequence_ = 0;
-	status_ = BufferCancelled;
+	return desc;
 }
 
-/**
- * \fn Buffer::setRequest()
- * \brief Set the request this buffer belongs to
- *
- * The intended callers are Request::prepare() and Request::completeBuffer().
- */
-
 BufferAllocator::BufferAllocator(std::shared_ptr<Camera> camera)
 	: camera_(camera)
 {
diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp
index e810fb725d81350d..63944c0199338f39 100644
--- a/src/libcamera/camera.cpp
+++ b/src/libcamera/camera.cpp
@@ -678,12 +678,6 @@ int Camera::configure(CameraConfiguration *config)
 
 		stream->configuration_ = cfg;
 		activeStreams_.insert(stream);
-
-		/*
-		 * Allocate buffer objects in the pool.
-		 * Memory will be allocated and assigned later.
-		 */
-		stream->createBuffers(cfg.memoryType, cfg.bufferCount);
 	}
 
 	state_ = CameraConfigured;
@@ -739,14 +733,6 @@ int Camera::freeBuffers()
 	if (!stateIs(CameraPrepared))
 		return -EACCES;
 
-	for (Stream *stream : activeStreams_) {
-		/*
-		 * All mappings must be destroyed before buffers can be freed
-		 * by the V4L2 device that has allocated them.
-		 */
-		stream->destroyBuffers();
-	}
-
 	state_ = CameraConfigured;
 
 	return pipe_->freeBuffers(this, activeStreams_);
@@ -812,24 +798,11 @@ int Camera::queueRequest(Request *request)
 
 	for (auto const &it : request->buffers()) {
 		Stream *stream = it.first;
-		Buffer *buffer = it.second;
 
 		if (activeStreams_.find(stream) == activeStreams_.end()) {
 			LOG(Camera, Error) << "Invalid request";
 			return -EINVAL;
 		}
-
-		if (stream->memoryType() == ExternalMemory) {
-			int index = stream->mapBuffer(buffer);
-			if (index < 0) {
-				LOG(Camera, Error) << "No buffer memory available";
-				return -ENOMEM;
-			}
-
-			buffer->index_ = index;
-		}
-
-		buffer->mem_ = &stream->buffers()[buffer->index_];
 	}
 
 	int ret = request->prepare();
@@ -856,15 +829,23 @@ int Camera::queueRequest(Request *request)
  */
 int Camera::start()
 {
+	int ret;
+
 	if (disconnected_)
 		return -ENODEV;
 
 	if (!stateIs(CameraPrepared))
 		return -EACCES;
 
+	for (Stream *stream : streams_) {
+		ret = stream->importBuffers(true);
+		if (ret < 0)
+			return ret;
+	}
+
 	LOG(Camera, Debug) << "Starting capture";
 
-	int ret = pipe_->start(this);
+	ret = pipe_->start(this);
 	if (ret)
 		return ret;
 
@@ -899,6 +880,9 @@ int Camera::stop()
 
 	pipe_->stop(this);
 
+	for (Stream *stream : streams_)
+		stream->importBuffers(false);
+
 	return 0;
 }
 
@@ -912,13 +896,6 @@ int Camera::stop()
  */
 void Camera::requestComplete(Request *request)
 {
-	for (auto it : request->buffers()) {
-		Stream *stream = it.first;
-		Buffer *buffer = it.second;
-		if (stream->memoryType() == ExternalMemory)
-			stream->unmapBuffer(buffer);
-	}
-
 	requestCompleted.emit(request);
 	delete request;
 }
diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h
index 01b90ab4654bfba2..cbbb6cd955bf527f 100644
--- a/src/libcamera/include/v4l2_videodevice.h
+++ b/src/libcamera/include/v4l2_videodevice.h
@@ -14,7 +14,9 @@
 
 #include <libcamera/buffer.h>
 #include <libcamera/geometry.h>
+#include <libcamera/request.h>
 #include <libcamera/signal.h>
+#include <libcamera/stream.h>
 
 #include "formats.h"
 #include "log.h"
@@ -22,9 +24,6 @@
 
 namespace libcamera {
 
-class Buffer;
-class BufferMemory;
-class BufferPool;
 class EventNotifier;
 class MediaDevice;
 class MediaEntity;
@@ -161,12 +160,11 @@ public:
 	int setFormat(V4L2DeviceFormat *format);
 	ImageFormats formats();
 
-	int exportBuffers(BufferPool *pool);
-	int importBuffers(BufferPool *pool);
+	int allocateBuffers(unsigned int count, std::vector<Buffer *> *buffers);
+	int importBuffers(unsigned int count);
 	int releaseBuffers();
 
-	int queueBuffer(Buffer *buffer);
-	std::vector<std::unique_ptr<Buffer>> queueAllBuffers();
+	int queueBuffer(Buffer *buffer, const BufferInfo *info = nullptr);
 	Signal<Buffer *, const BufferInfo &> bufferReady;
 
 	int streamOn();
@@ -192,8 +190,8 @@ private:
 	std::vector<SizeRange> enumSizes(unsigned int pixelFormat);
 
 	int requestBuffers(unsigned int count);
-	int createPlane(BufferMemory *buffer, unsigned int index,
-			unsigned int plane, unsigned int length);
+	Buffer *createBuffer(struct v4l2_buffer buf);
+	int expbuf(unsigned int index, unsigned int plane);
 
 	void bufferAvailable(EventNotifier *notifier);
 
@@ -202,7 +200,7 @@ private:
 	enum v4l2_buf_type bufferType_;
 	enum v4l2_memory memoryType_;
 
-	BufferPool *bufferPool_;
+	V4L2BufferCache *cache_;
 	std::map<unsigned int, Buffer *> queuedBuffers_;
 
 	EventNotifier *fdEvent_;
@@ -227,6 +225,21 @@ private:
 	V4L2VideoDevice *capture_;
 };
 
+class V4L2Stream : public Stream
+{
+public:
+	V4L2Stream(V4L2VideoDevice *video, unsigned int bufferCount);
+
+protected:
+	int allocateBuffers(std::vector<Buffer *> *buffers) override;
+	int importBuffers(bool enable) override;
+
+private:
+	V4L2VideoDevice *video_;
+	unsigned int bufferCount_;
+	bool allocated;
+};
+
 } /* namespace libcamera */
 
 #endif /* __LIBCAMERA_V4L2_VIDEODEVICE_H__ */
diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
index 01064ac09859155d..af8e93c04bc40f04 100644
--- a/src/libcamera/pipeline/ipu3/ipu3.cpp
+++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
@@ -43,7 +43,7 @@ public:
 		V4L2VideoDevice *dev;
 		unsigned int pad;
 		std::string name;
-		BufferPool *pool;
+		std::vector<Buffer *> buffers;
 	};
 
 	ImgUDevice()
@@ -69,9 +69,8 @@ public:
 	int configureOutput(ImgUOutput *output,
 			    const StreamConfiguration &cfg);
 
-	int importInputBuffers(BufferPool *pool);
-	int importOutputBuffers(ImgUOutput *output, BufferPool *pool);
-	int exportOutputBuffers(ImgUOutput *output, BufferPool *pool);
+	int importInputBuffers(unsigned int count);
+	int exportOutputBuffers(ImgUOutput *output, unsigned int count);
 	void freeBuffers();
 
 	int start();
@@ -92,10 +91,6 @@ public:
 	ImgUOutput viewfinder_;
 	ImgUOutput stat_;
 	/* \todo Add param video device for 3A tuning */
-
-	BufferPool vfPool_;
-	BufferPool statPool_;
-	BufferPool outPool_;
 };
 
 class CIO2Device
@@ -119,10 +114,10 @@ public:
 	int configure(const Size &size,
 		      V4L2DeviceFormat *outputFormat);
 
-	BufferPool *exportBuffers();
+	int exportBuffers();
 	void freeBuffers();
 
-	int start(std::vector<std::unique_ptr<Buffer>> *buffers);
+	int start();
 	int stop();
 
 	static int mediaBusToFormat(unsigned int code);
@@ -131,14 +126,17 @@ public:
 	V4L2Subdevice *csi2_;
 	CameraSensor *sensor_;
 
-	BufferPool pool_;
+private:
+	std::vector<Buffer *> buffers_;
 };
 
-class IPU3Stream : public Stream
+class IPU3Stream : public V4L2Stream
 {
 public:
-	IPU3Stream(ImgUDevice::ImgUOutput *device, const std::string &name)
-		: active_(false), name_(name), device_(device)
+	IPU3Stream(ImgUDevice::ImgUOutput *device, const std::string &name,
+		   unsigned int count)
+		: V4L2Stream(device->dev, count), active_(false), name_(name),
+		  device_(device)
 	{
 	}
 
@@ -170,8 +168,6 @@ public:
 
 	IPU3Stream *outStream_;
 	IPU3Stream *vfStream_;
-
-	std::vector<std::unique_ptr<Buffer>> rawBuffers_;
 };
 
 class IPU3CameraConfiguration : public CameraConfiguration
@@ -296,8 +292,6 @@ void IPU3CameraConfiguration::adjustStream(StreamConfiguration &cfg, bool scale)
 		cfg.size.width &= ~7;
 		cfg.size.height &= ~3;
 	}
-
-	cfg.bufferCount = IPU3_BUFFER_COUNT;
 }
 
 CameraConfiguration::Status IPU3CameraConfiguration::validate()
@@ -635,70 +629,42 @@ int PipelineHandlerIPU3::allocateBuffers(Camera *camera,
 					 const std::set<Stream *> &streams)
 {
 	IPU3CameraData *data = cameraData(camera);
-	IPU3Stream *outStream = data->outStream_;
-	IPU3Stream *vfStream = data->vfStream_;
-	CIO2Device *cio2 = &data->cio2_;
-	ImgUDevice *imgu = data->imgu_;
 	unsigned int bufferCount;
 	int ret;
 
-	/* Share buffers between CIO2 output and ImgU input. */
-	BufferPool *pool = cio2->exportBuffers();
-	if (!pool)
-		return -ENOMEM;
+	ret = data->cio2_.exportBuffers();
+	if (ret < 0)
+		return ret;
 
-	ret = imgu->importInputBuffers(pool);
-	if (ret)
-		goto error;
+	bufferCount = ret;
 
-	/*
-	 * Use for the stat's internal pool the same number of buffer as
-	 * for the input pool.
-	 * \todo To be revised when we'll actually use the stat node.
-	 */
-	bufferCount = pool->count();
-	imgu->stat_.pool->createBuffers(bufferCount);
-	ret = imgu->exportOutputBuffers(&imgu->stat_, imgu->stat_.pool);
+	ret = data->imgu_->importInputBuffers(bufferCount);
 	if (ret)
 		goto error;
 
-	/* Allocate buffers for each active stream. */
-	for (Stream *s : streams) {
-		IPU3Stream *stream = static_cast<IPU3Stream *>(s);
-		ImgUDevice::ImgUOutput *dev = stream->device_;
-
-		if (stream->memoryType() == InternalMemory)
-			ret = imgu->exportOutputBuffers(dev, &stream->bufferPool());
-		else
-			ret = imgu->importOutputBuffers(dev, &stream->bufferPool());
-		if (ret)
-			goto error;
-	}
+	ret = data->imgu_->exportOutputBuffers(&data->imgu_->stat_, bufferCount);
+	if (ret < 0)
+		goto error;
 
 	/*
 	 * Allocate buffers also on non-active outputs; use the same number
 	 * of buffers as the active ones.
 	 */
-	if (!outStream->active_) {
-		bufferCount = vfStream->configuration().bufferCount;
-		outStream->device_->pool->createBuffers(bufferCount);
-		ret = imgu->exportOutputBuffers(outStream->device_,
-						outStream->device_->pool);
-		if (ret)
+	if (!data->outStream_->active_) {
+		ret = data->imgu_->exportOutputBuffers(data->outStream_->device_,
+						       bufferCount);
+		if (ret < 0)
 			goto error;
 	}
 
-	if (!vfStream->active_) {
-		bufferCount = outStream->configuration().bufferCount;
-		vfStream->device_->pool->createBuffers(bufferCount);
-		ret = imgu->exportOutputBuffers(vfStream->device_,
-						vfStream->device_->pool);
-		if (ret)
+	if (!data->vfStream_->active_) {
+		ret = data->imgu_->exportOutputBuffers(data->vfStream_->device_,
+						       bufferCount);
+		if (ret < 0)
 			goto error;
 	}
 
 	return 0;
-
 error:
 	freeBuffers(camera, streams);
 
@@ -727,7 +693,7 @@ int PipelineHandlerIPU3::start(Camera *camera)
 	 * Start the ImgU video devices, buffers will be queued to the
 	 * ImgU output and viewfinder when requests will be queued.
 	 */
-	ret = cio2->start(&data->rawBuffers_);
+	ret = cio2->start();
 	if (ret)
 		goto error;
 
@@ -743,7 +709,6 @@ int PipelineHandlerIPU3::start(Camera *camera)
 error:
 	LOG(IPU3, Error) << "Failed to start camera " << camera->name();
 
-	data->rawBuffers_.clear();
 	return ret;
 }
 
@@ -757,8 +722,6 @@ void PipelineHandlerIPU3::stop(Camera *camera)
 	if (ret)
 		LOG(IPU3, Warning) << "Failed to stop camera "
 				   << camera->name();
-
-	data->rawBuffers_.clear();
 }
 
 int PipelineHandlerIPU3::queueRequest(Camera *camera, Request *request)
@@ -880,8 +843,8 @@ int PipelineHandlerIPU3::registerCameras()
 		 * second.
 		 */
 		data->imgu_ = numCameras ? &imgu1_ : &imgu0_;
-		data->outStream_ = new IPU3Stream(&data->imgu_->output_, "output");
-		data->vfStream_ = new IPU3Stream(&data->imgu_->viewfinder_, "viewfinder");
+		data->outStream_ = new IPU3Stream(&data->imgu_->output_, "output", 4);
+		data->vfStream_ = new IPU3Stream(&data->imgu_->viewfinder_, "viewfinder", 4);
 
 		/*
 		 * Connect video devices' 'bufferReady' signals to their
@@ -1019,7 +982,6 @@ int ImgUDevice::init(MediaDevice *media, unsigned int index)
 
 	output_.pad = PAD_OUTPUT;
 	output_.name = "output";
-	output_.pool = &outPool_;
 
 	viewfinder_.dev = V4L2VideoDevice::fromEntityName(media,
 							  name_ + " viewfinder");
@@ -1029,7 +991,6 @@ int ImgUDevice::init(MediaDevice *media, unsigned int index)
 
 	viewfinder_.pad = PAD_VF;
 	viewfinder_.name = "viewfinder";
-	viewfinder_.pool = &vfPool_;
 
 	stat_.dev = V4L2VideoDevice::fromEntityName(media, name_ + " 3a stat");
 	ret = stat_.dev->open();
@@ -1038,7 +999,6 @@ int ImgUDevice::init(MediaDevice *media, unsigned int index)
 
 	stat_.pad = PAD_STAT;
 	stat_.name = "stat";
-	stat_.pool = &statPool_;
 
 	return 0;
 }
@@ -1138,65 +1098,25 @@ int ImgUDevice::configureOutput(ImgUOutput *output,
 	return 0;
 }
 
-/**
- * \brief Import buffers from \a pool into the ImgU input
- * \param[in] pool The buffer pool to import
- * \return 0 on success or a negative error code otherwise
- */
-int ImgUDevice::importInputBuffers(BufferPool *pool)
+int ImgUDevice::importInputBuffers(unsigned int count)
 {
-	int ret = input_->importBuffers(pool);
-	if (ret) {
+	int ret = input_->importBuffers(count);
+	if (ret)
 		LOG(IPU3, Error) << "Failed to import ImgU input buffers";
-		return ret;
-	}
 
-	return 0;
+	return ret;
 }
 
-/**
- * \brief Export buffers from \a output to the provided \a pool
- * \param[in] output The ImgU output device
- * \param[in] pool The buffer pool where to export buffers
- *
- * Export memory buffers reserved in the video device memory associated with
- * \a output id to the buffer pool provided as argument.
- *
- * \return 0 on success or a negative error code otherwise
- */
-int ImgUDevice::exportOutputBuffers(ImgUOutput *output, BufferPool *pool)
+int ImgUDevice::exportOutputBuffers(ImgUOutput *output, unsigned int count)
 {
-	int ret = output->dev->exportBuffers(pool);
-	if (ret) {
-		LOG(IPU3, Error) << "Failed to export ImgU "
-				 << output->name << " buffers";
-		return ret;
-	}
-
-	return 0;
-}
+	int ret;
 
-/**
- * \brief Reserve buffers in \a output from the provided \a pool
- * \param[in] output The ImgU output device
- * \param[in] pool The buffer pool used to reserve buffers in \a output
- *
- * Reserve a number of buffers equal to the number of buffers in \a pool
- * in the \a output device.
- *
- * \return 0 on success or a negative error code otherwise
- */
-int ImgUDevice::importOutputBuffers(ImgUOutput *output, BufferPool *pool)
-{
-	int ret = output->dev->importBuffers(pool);
-	if (ret) {
-		LOG(IPU3, Error)
-			<< "Failed to import buffer in " << output->name
-			<< " ImgU device";
-		return ret;
-	}
+	ret = output->dev->allocateBuffers(count, &output->buffers);
+	if (ret < 0)
+		LOG(IPU3, Error) << "Failed to allocate ImgU "
+				 << output->name << " buffers";
 
-	return 0;
+	return ret;
 }
 
 /**
@@ -1451,38 +1371,33 @@ int CIO2Device::configure(const Size &size,
 	return 0;
 }
 
-/**
- * \brief Allocate CIO2 memory buffers and export them in a BufferPool
- *
- * Allocate memory buffers in the CIO2 video device and export them to
- * a buffer pool that can be imported by another device.
- *
- * \return The buffer pool with export buffers on success or nullptr otherwise
- */
-BufferPool *CIO2Device::exportBuffers()
+int CIO2Device::exportBuffers()
 {
-	pool_.createBuffers(CIO2_BUFFER_COUNT);
-
-	int ret = output_->exportBuffers(&pool_);
-	if (ret) {
+	int ret = output_->allocateBuffers(CIO2_BUFFER_COUNT, &buffers_);
+	if (ret < 0)
 		LOG(IPU3, Error) << "Failed to export CIO2 buffers";
-		return nullptr;
-	}
 
-	return &pool_;
+	return ret;
 }
 
 void CIO2Device::freeBuffers()
 {
+	for (Buffer *buffer : buffers_)
+		delete buffer;
+
 	if (output_->releaseBuffers())
 		LOG(IPU3, Error) << "Failed to release CIO2 buffers";
 }
 
-int CIO2Device::start(std::vector<std::unique_ptr<Buffer>> *buffers)
+int CIO2Device::start()
 {
-	*buffers = output_->queueAllBuffers();
-	if (buffers->empty())
-		return -EINVAL;
+	for (Buffer *buffer : buffers_) {
+		int ret = output_->queueBuffer(buffer);
+		if (ret) {
+			LOG(IPU3, Error) << "Failed to queue CIO2 buffer";
+			return ret;
+		}
+	}
 
 	return output_->streamOn();
 }
diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
index 33a058de18b8cf2e..fb224370de19303d 100644
--- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp
+++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
@@ -31,8 +31,7 @@
 #include "v4l2_subdevice.h"
 #include "v4l2_videodevice.h"
 
-#define RKISP1_PARAM_BASE 0x100
-#define RKISP1_STAT_BASE 0x200
+#define RKISP1_BUFFER_COUNT 4
 
 namespace libcamera {
 
@@ -153,8 +152,6 @@ public:
 	const V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }
 
 private:
-	static constexpr unsigned int RKISP1_BUFFER_COUNT = 4;
-
 	/*
 	 * The RkISP1CameraData instance is guaranteed to be valid as long as the
 	 * corresponding Camera instance is valid. In order to borrow a
@@ -213,9 +210,6 @@ private:
 	V4L2VideoDevice *param_;
 	V4L2VideoDevice *stat_;
 
-	BufferPool paramPool_;
-	BufferPool statPool_;
-
 	std::queue<Buffer *> paramBuffers_;
 	std::queue<Buffer *> statBuffers_;
 
@@ -508,8 +502,6 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
 		status = Adjusted;
 	}
 
-	cfg.bufferCount = RKISP1_BUFFER_COUNT;
-
 	return status;
 }
 
@@ -660,46 +652,40 @@ int PipelineHandlerRkISP1::allocateBuffers(Camera *camera,
 					   const std::set<Stream *> &streams)
 {
 	RkISP1CameraData *data = cameraData(camera);
-	Stream *stream = *streams.begin();
+	std::vector<Buffer *> paramBuffers, statBuffers;
 	int ret;
 
-	if (stream->memoryType() == InternalMemory)
-		ret = video_->exportBuffers(&stream->bufferPool());
-	else
-		ret = video_->importBuffers(&stream->bufferPool());
-
-	if (ret)
-		return ret;
 
-	paramPool_.createBuffers(stream->configuration().bufferCount + 1);
-	ret = param_->exportBuffers(&paramPool_);
-	if (ret) {
-		video_->releaseBuffers();
-		return ret;
-	}
+	ret = param_->allocateBuffers(RKISP1_BUFFER_COUNT, &paramBuffers);
+	if (ret < 0)
+		goto err;
 
-	statPool_.createBuffers(stream->configuration().bufferCount + 1);
-	ret = stat_->exportBuffers(&statPool_);
-	if (ret) {
-		param_->releaseBuffers();
-		video_->releaseBuffers();
-		return ret;
-	}
+	ret = stat_->allocateBuffers(RKISP1_BUFFER_COUNT, &statBuffers);
+	if (ret < 0)
+		goto err;
 
-	for (unsigned int i = 0; i < stream->configuration().bufferCount + 1; i++) {
-		data->ipaBuffers_.push_back({ .id = RKISP1_PARAM_BASE | i,
-					      .memory = paramPool_.buffers()[i] });
-		paramBuffers_.push(new Buffer(i));
+	for (Buffer *buffer : paramBuffers) {
+		data->ipaBuffers_.push_back({ .id = static_cast<unsigned int>(buffer->plane(0)->fd()),
+					      .planes = buffer->description() });
+		paramBuffers_.push(buffer);
 	}
 
-	for (unsigned int i = 0; i < stream->configuration().bufferCount + 1; i++) {
-		data->ipaBuffers_.push_back({ .id = RKISP1_STAT_BASE | i,
-					      .memory = statPool_.buffers()[i] });
-		statBuffers_.push(new Buffer(i));
+	for (Buffer *buffer : statBuffers) {
+		data->ipaBuffers_.push_back({ .id = static_cast<unsigned int>(buffer->plane(0)->fd()),
+					      .planes = buffer->description() });
+		statBuffers_.push(buffer);
 	}
 
 	data->ipa_->mapBuffers(data->ipaBuffers_);
 
+	return 0;
+err:
+	for (Buffer *buffer : paramBuffers)
+		delete buffer;
+
+	for (Buffer *buffer : statBuffers)
+		delete buffer;
+
 	return ret;
 }
 
@@ -820,9 +806,11 @@ int PipelineHandlerRkISP1::queueRequest(Camera *camera, Request *request)
 	if (!info)
 		return -ENOENT;
 
+	unsigned int paramid = static_cast<unsigned int>(info->paramBuffer->plane(0)->fd());
+
 	IPAOperationData op;
 	op.operation = RKISP1_IPA_EVENT_QUEUE_REQUEST;
-	op.data = { data->frame_, RKISP1_PARAM_BASE | info->paramBuffer->index() };
+	op.data = { data->frame_, paramid };
 	op.controls = { request->controls() };
 	data->ipa_->processEvent(op);
 
@@ -874,7 +862,7 @@ int PipelineHandlerRkISP1::createCamera(MediaEntity *sensor)
 	std::unique_ptr<RkISP1CameraData> data =
 		utils::make_unique<RkISP1CameraData>(this);
 
-	data->stream_ = new Stream();
+	data->stream_ = new V4L2Stream(video_, RKISP1_BUFFER_COUNT);
 
 	ControlInfoMap::Map ctrls;
 	ctrls.emplace(std::piecewise_construct,
@@ -982,9 +970,9 @@ void PipelineHandlerRkISP1::tryCompleteRequest(Request *request)
 	if (!info->paramDequeued)
 		return;
 
-	completeRequest(activeCamera_, request);
-
 	data->frameInfo_.destroy(info->frame);
+
+	completeRequest(activeCamera_, request);
 }
 
 void PipelineHandlerRkISP1::bufferReady(Buffer *buffer, const BufferInfo &info)
@@ -1023,7 +1011,7 @@ void PipelineHandlerRkISP1::statReady(Buffer *buffer, const BufferInfo &info)
 		return;
 
 	unsigned int frame = rkinfo->frame;
-	unsigned int statid = RKISP1_STAT_BASE | rkinfo->statBuffer->index();
+	unsigned int statid = static_cast<unsigned int>(rkinfo->statBuffer->plane(0)->fd());
 
 	IPAOperationData op;
 	op.operation = RKISP1_IPA_EVENT_SIGNAL_STAT_BUFFER;
diff --git a/src/libcamera/pipeline/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo.cpp
index ef2e8c9734f844ce..21e594075eefffdc 100644
--- a/src/libcamera/pipeline/uvcvideo.cpp
+++ b/src/libcamera/pipeline/uvcvideo.cpp
@@ -136,8 +136,6 @@ CameraConfiguration::Status UVCCameraConfiguration::validate()
 		status = Adjusted;
 	}
 
-	cfg.bufferCount = 4;
-
 	return status;
 }
 
@@ -161,7 +159,6 @@ CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,
 
 	cfg.pixelFormat = formats.pixelformats().front();
 	cfg.size = formats.sizes(cfg.pixelFormat).back();
-	cfg.bufferCount = 4;
 
 	config->addConfiguration(cfg);
 
@@ -196,16 +193,7 @@ int PipelineHandlerUVC::configure(Camera *camera, CameraConfiguration *config)
 int PipelineHandlerUVC::allocateBuffers(Camera *camera,
 					const std::set<Stream *> &streams)
 {
-	UVCCameraData *data = cameraData(camera);
-	Stream *stream = *streams.begin();
-	const StreamConfiguration &cfg = stream->configuration();
-
-	LOG(UVC, Debug) << "Requesting " << cfg.bufferCount << " buffers";
-
-	if (stream->memoryType() == InternalMemory)
-		return data->video_->exportBuffers(&stream->bufferPool());
-	else
-		return data->video_->importBuffers(&stream->bufferPool());
+	return 0;
 }
 
 int PipelineHandlerUVC::freeBuffers(Camera *camera,
@@ -331,7 +319,7 @@ int UVCCameraData::init(MediaEntity *entity)
 	if (ret)
 		return ret;
 
-	stream_ = new Stream();
+	stream_ = new V4L2Stream(video_, 4);
 
 	video_->bufferReady.connect(this, &UVCCameraData::bufferReady);
 
diff --git a/src/libcamera/pipeline/vimc.cpp b/src/libcamera/pipeline/vimc.cpp
index e3eefc49135179f2..a14b7c8834174a5f 100644
--- a/src/libcamera/pipeline/vimc.cpp
+++ b/src/libcamera/pipeline/vimc.cpp
@@ -158,8 +158,6 @@ CameraConfiguration::Status VimcCameraConfiguration::validate()
 		status = Adjusted;
 	}
 
-	cfg.bufferCount = 4;
-
 	return status;
 }
 
@@ -190,7 +188,6 @@ CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera,
 
 	cfg.pixelFormat = V4L2_PIX_FMT_RGB24;
 	cfg.size = { 1920, 1080 };
-	cfg.bufferCount = 4;
 
 	config->addConfiguration(cfg);
 
@@ -263,16 +260,7 @@ int PipelineHandlerVimc::configure(Camera *camera, CameraConfiguration *config)
 int PipelineHandlerVimc::allocateBuffers(Camera *camera,
 					 const std::set<Stream *> &streams)
 {
-	VimcCameraData *data = cameraData(camera);
-	Stream *stream = *streams.begin();
-	const StreamConfiguration &cfg = stream->configuration();
-
-	LOG(VIMC, Debug) << "Requesting " << cfg.bufferCount << " buffers";
-
-	if (stream->memoryType() == InternalMemory)
-		return data->video_->exportBuffers(&stream->bufferPool());
-	else
-		return data->video_->importBuffers(&stream->bufferPool());
+	return 0;
 }
 
 int PipelineHandlerVimc::freeBuffers(Camera *camera,
@@ -419,7 +407,7 @@ int VimcCameraData::init(MediaDevice *media)
 	if (video_->open())
 		return -ENODEV;
 
-	stream_ = new Stream();
+	stream_ = new V4L2Stream(video_, 4);
 
 	video_->bufferReady.connect(this, &VimcCameraData::bufferReady);
 
diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp
index 9a0e439a3d05d780..2bda7a57e8d6381e 100644
--- a/src/libcamera/request.cpp
+++ b/src/libcamera/request.cpp
@@ -75,11 +75,6 @@ Request::Request(Camera *camera, uint64_t cookie)
 
 Request::~Request()
 {
-	for (auto it : bufferMap_) {
-		Buffer *buffer = it.second;
-		delete buffer;
-	}
-
 	delete metadata_;
 	delete controls_;
 	delete validator_;
@@ -125,21 +120,15 @@ Request::~Request()
  * \retval -EEXIST The request already contains a buffer for the stream
  * \retval -EINVAL The buffer does not reference a valid Stream
  */
-int Request::addBuffer(std::unique_ptr<Buffer> buffer)
+int Request::addBuffer(Stream *stream, Buffer *buffer)
 {
-	Stream *stream = buffer->stream();
-	if (!stream) {
-		LOG(Request, Error) << "Invalid stream reference";
-		return -EINVAL;
-	}
-
 	auto it = bufferMap_.find(stream);
 	if (it != bufferMap_.end()) {
 		LOG(Request, Error) << "Buffer already set for stream";
 		return -EEXIST;
 	}
 
-	bufferMap_[stream] = buffer.release();
+	bufferMap_[stream] = buffer;
 
 	return 0;
 }
diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp
index 97c6722b8c4c98cf..873452c9608fc9f1 100644
--- a/src/libcamera/v4l2_videodevice.cpp
+++ b/src/libcamera/v4l2_videodevice.cpp
@@ -19,6 +19,7 @@
 
 #include <libcamera/buffer.h>
 #include <libcamera/event_notifier.h>
+#include <libcamera/stream.h>
 
 #include "log.h"
 #include "media_device.h"
@@ -319,7 +320,7 @@ const std::string V4L2DeviceFormat::toString() const
  * \param[in] deviceNode The file-system path to the video device node
  */
 V4L2VideoDevice::V4L2VideoDevice(const std::string &deviceNode)
-	: V4L2Device(deviceNode), bufferPool_(nullptr), fdEvent_(nullptr)
+	: V4L2Device(deviceNode), cache_(nullptr), fdEvent_(nullptr)
 {
 	/*
 	 * We default to an MMAP based CAPTURE video device, however this will
@@ -879,27 +880,32 @@ int V4L2VideoDevice::requestBuffers(unsigned int count)
 }
 
 /**
- * \brief Request buffers to be allocated from the video device and stored in
- * the buffer pool provided.
- * \param[out] pool BufferPool to populate with buffers
+ * \brief Operate using buffers allocated from local video device
+ * \param[in] count Number of buffers to allocate
+ * \param[out] buffer Vector to store local buffers
  * \return 0 on success or a negative error code otherwise
  */
-int V4L2VideoDevice::exportBuffers(BufferPool *pool)
+int V4L2VideoDevice::allocateBuffers(unsigned int count, std::vector<Buffer *> *buffers)
 {
 	unsigned int i;
 	int ret;
 
+	if (cache_) {
+		LOG(V4L2, Error) << "Can't export buffers with existing cache";
+		return -EINVAL;
+	}
+
+	cache_ = new V4L2BufferCache(count);
+
 	memoryType_ = V4L2_MEMORY_MMAP;
 
-	ret = requestBuffers(pool->count());
-	if (ret)
-		return ret;
+	ret = requestBuffers(count);
+	if (ret < 0)
+		goto err_cache;
 
-	/* Map the buffers. */
-	for (i = 0; i < pool->count(); ++i) {
-		struct v4l2_plane planes[VIDEO_MAX_PLANES] = {};
+	for (i = 0; i < count; ++i) {
 		struct v4l2_buffer buf = {};
-		BufferMemory &buffer = pool->buffers()[i];
+		struct v4l2_plane planes[VIDEO_MAX_PLANES] = {};
 
 		buf.index = i;
 		buf.type = bufferType_;
@@ -912,51 +918,71 @@ int V4L2VideoDevice::exportBuffers(BufferPool *pool)
 			LOG(V4L2, Error)
 				<< "Unable to query buffer " << i << ": "
 				<< strerror(-ret);
-			break;
+			goto err_buf;
 		}
 
-		if (V4L2_TYPE_IS_MULTIPLANAR(buf.type)) {
-			for (unsigned int p = 0; p < buf.length; ++p) {
-				ret = createPlane(&buffer, i, p,
-						  buf.m.planes[p].length);
-				if (ret)
-					break;
-			}
-		} else {
-			ret = createPlane(&buffer, i, 0, buf.length);
+		Buffer *buffer = createBuffer(buf);
+		if (!buffer) {
+			LOG(V4L2, Error) << "Unable to create buffer";
+			ret = -EINVAL;
+			goto err_buf;
 		}
 
-		if (ret) {
-			LOG(V4L2, Error) << "Failed to create plane";
-			break;
-		}
+		buffers->push_back(buffer);
+		cache_->populate(i, buffer->fds());
 	}
 
-	if (ret) {
-		requestBuffers(0);
-		pool->destroyBuffers();
-		return ret;
+	return count;
+err_buf:
+	requestBuffers(0);
+
+	for (Buffer *buffer : *buffers)
+		delete buffer;
+
+	buffers->clear();
+err_cache:
+	delete cache_;
+	cache_ = nullptr;
+
+	return ret;
+}
+
+Buffer *V4L2VideoDevice::createBuffer(struct v4l2_buffer buf)
+{
+	const unsigned int numPlanes = V4L2_TYPE_IS_MULTIPLANAR(buf.type) ? buf.length : 1;
+	std::vector<std::pair<int, unsigned int>> planes;
+	Buffer *frame = nullptr;
+
+	for (unsigned int plane = 0; plane < numPlanes; plane++) {
+		int fd = expbuf(buf.index, plane);
+		if (fd < 0)
+			goto out;
+
+		if (V4L2_TYPE_IS_MULTIPLANAR(buf.type))
+			planes.emplace_back(fd, buf.m.planes[plane].length);
+		else
+			planes.emplace_back(fd, buf.length);
 	}
 
-	bufferPool_ = pool;
+	if (!planes.empty())
+		frame = new Buffer(planes);
+	else
+		LOG(V4L2, Error) << "Failed to get planes";
+out:
+	for (std::pair<int, unsigned int> plane : planes)
+		::close(plane.first);
 
-	return 0;
+	return frame;
 }
 
-int V4L2VideoDevice::createPlane(BufferMemory *buffer, unsigned int index,
-				 unsigned int planeIndex, unsigned int length)
+int V4L2VideoDevice::expbuf(unsigned int index, unsigned int plane)
 {
 	struct v4l2_exportbuffer expbuf = {};
 	int ret;
 
-	LOG(V4L2, Debug)
-		<< "Buffer " << index
-		<< " plane " << planeIndex
-		<< ": length=" << length;
-
 	expbuf.type = bufferType_;
 	expbuf.index = index;
-	expbuf.plane = planeIndex;
+	expbuf.plane = plane;
 	expbuf.flags = O_RDWR;
 
 	ret = ioctl(VIDIOC_EXPBUF, &expbuf);
@@ -966,31 +992,31 @@ int V4L2VideoDevice::createPlane(BufferMemory *buffer, unsigned int index,
 		return ret;
 	}
 
-	buffer->planes().emplace_back();
-	Plane &plane = buffer->planes().back();
-	plane.setDmabuf(expbuf.fd, length);
-	::close(expbuf.fd);
-
-	return 0;
+	return expbuf.fd;
 }
 
 /**
- * \brief Import the externally allocated \a pool of buffers
- * \param[in] pool BufferPool of buffers to import
+ * \brief Operate using buffers imported from external source
+ * \param[in] count Number of buffers to prepare for
  * \return 0 on success or a negative error code otherwise
  */
-int V4L2VideoDevice::importBuffers(BufferPool *pool)
+int V4L2VideoDevice::importBuffers(unsigned int count)
 {
+	/* Only prepare for external buffers if internals not in use. */
+	if (cache_)
+		return 0;
+
 	int ret;
 
 	memoryType_ = V4L2_MEMORY_DMABUF;
 
-	ret = requestBuffers(pool->count());
+	ret = requestBuffers(count);
 	if (ret)
 		return ret;
 
-	LOG(V4L2, Debug) << "provided pool of " << pool->count() << " buffers";
-	bufferPool_ = pool;
+	cache_ = new V4L2BufferCache(count);
+
+	LOG(V4L2, Debug) << "provided for " << count << " external buffers";
 
 	return 0;
 }
@@ -1000,9 +1026,10 @@ int V4L2VideoDevice::importBuffers(BufferPool *pool)
  */
 int V4L2VideoDevice::releaseBuffers()
 {
-	LOG(V4L2, Debug) << "Releasing bufferPool";
+	LOG(V4L2, Debug) << "Releasing buffers";
 
-	bufferPool_ = nullptr;
+	delete cache_;
+	cache_ = nullptr;
 
 	return requestBuffers(0);
 }
@@ -1018,40 +1045,42 @@ int V4L2VideoDevice::releaseBuffers()
  *
  * \return 0 on success or a negative error code otherwise
  */
-int V4L2VideoDevice::queueBuffer(Buffer *buffer)
+int V4L2VideoDevice::queueBuffer(Buffer *buffer, const BufferInfo *info)
 {
-	struct v4l2_plane v4l2Planes[VIDEO_MAX_PLANES] = {};
 	struct v4l2_buffer buf = {};
+	struct v4l2_plane v4l2Planes[VIDEO_MAX_PLANES] = {};
 	int ret;
 
-	buf.index = buffer->index();
+	ASSERT(cache_);
+
+	buf.index = cache_->pop(buffer->fds());
 	buf.type = bufferType_;
 	buf.memory = memoryType_;
 	buf.field = V4L2_FIELD_NONE;
 
-	bool multiPlanar = V4L2_TYPE_IS_MULTIPLANAR(buf.type);
-	BufferMemory *mem = &bufferPool_->buffers()[buf.index];
-	const std::vector<Plane> &planes = mem->planes();
+	if (V4L2_TYPE_IS_MULTIPLANAR(buf.type)) {
+		buf.length = buffer->numPlanes();
+		buf.m.planes = v4l2Planes;
+	}
 
 	if (buf.memory == V4L2_MEMORY_DMABUF) {
-		if (multiPlanar) {
-			for (unsigned int p = 0; p < planes.size(); ++p)
-				v4l2Planes[p].m.fd = planes[p].dmabuf();
-		} else {
-			buf.m.fd = planes[0].dmabuf();
-		}
+		if (V4L2_TYPE_IS_MULTIPLANAR(buf.type))
+			for (unsigned int i = 0; i < buffer->numPlanes(); i++)
+				v4l2Planes[i].m.fd = buffer->plane(i)->fd();
+		else
+			buf.m.fd = buffer->plane(0)->fd();
 	}
 
-	if (multiPlanar) {
-		buf.length = planes.size();
-		buf.m.planes = v4l2Planes;
-	}
+	if (V4L2_TYPE_IS_OUTPUT(bufferType_) && info) {
+		if (V4L2_TYPE_IS_MULTIPLANAR(buf.type))
+			for (unsigned int i = 0; i < buffer->numPlanes(); i++)
+				v4l2Planes[i].bytesused = info->plane(i).bytesused;
+		else
+			buf.bytesused = info->plane(0).bytesused;
 
-	if (V4L2_TYPE_IS_OUTPUT(bufferType_)) {
-		buf.bytesused = buffer->bytesused_;
-		buf.sequence = buffer->sequence_;
-		buf.timestamp.tv_sec = buffer->timestamp_ / 1000000000;
-		buf.timestamp.tv_usec = (buffer->timestamp_ / 1000) % 1000000;
+		buf.sequence = info->sequence();
+		buf.timestamp.tv_sec = info->timestamp() / 1000000000;
+		buf.timestamp.tv_usec = (info->timestamp() / 1000) % 1000000;
 	}
 
 	LOG(V4L2, Debug) << "Queueing buffer " << buf.index;
@@ -1072,52 +1101,6 @@ int V4L2VideoDevice::queueBuffer(Buffer *buffer)
 	return 0;
 }
 
-/**
- * \brief Queue all buffers into the video device
- *
- * When starting video capture users of the video device often need to queue
- * all allocated buffers to the device. This helper method simplifies the
- * implementation of the user by queuing all buffers and returning a vector of
- * Buffer instances for each queued buffer.
- *
- * This method is meant to be used with video capture devices internal to a
- * pipeline handler, such as ISP statistics capture devices, or raw CSI-2
- * receivers. For video capture devices facing applications, buffers shall
- * instead be queued when requests are received, and for video output devices,
- * buffers shall be queued when frames are ready to be output.
- *
- * The caller shall ensure that the returned buffers vector remains valid until
- * all the queued buffers are dequeued, either during capture, or by stopping
- * the video device.
- *
- * Calling this method on an output device or on a device that has buffers
- * already queued is an error and will return an empty vector.
- *
- * \return A vector of queued buffers, which will be empty if an error occurs
- */
-std::vector<std::unique_ptr<Buffer>> V4L2VideoDevice::queueAllBuffers()
-{
-	int ret;
-
-	if (!queuedBuffers_.empty())
-		return {};
-
-	if (V4L2_TYPE_IS_OUTPUT(bufferType_))
-		return {};
-
-	std::vector<std::unique_ptr<Buffer>> buffers;
-
-	for (unsigned int i = 0; i < bufferPool_->count(); ++i) {
-		Buffer *buffer = new Buffer(i);
-		buffers.emplace_back(buffer);
-		ret = queueBuffer(buffer);
-		if (ret)
-			return {};
-	}
-
-	return buffers;
-}
-
 /**
  * \brief Slot to handle completed buffer events from the V4L2 video device
  * \param[in] notifier The event notifier
@@ -1149,6 +1132,7 @@ void V4L2VideoDevice::bufferAvailable(EventNotifier *notifier)
 		return;
 	}
 
+	cache_->push(buf.index);
 	LOG(V4L2, Debug) << "Buffer " << buf.index << " is available";
 
 	auto it = queuedBuffers_.find(buf.index);
@@ -1164,7 +1148,6 @@ void V4L2VideoDevice::bufferAvailable(EventNotifier *notifier)
 		+ buf.timestamp.tv_usec * 1000ULL;
 
 	BufferInfo info(status, buf.sequence, timestamp, { { buf.bytesused } });
-	buffer->index_ = buf.index;
 
 	/* Notify anyone listening to the device. */
 	bufferReady.emit(buffer, info);
@@ -1216,13 +1199,10 @@ int V4L2VideoDevice::streamOff()
 
 	/* Send back all queued buffers. */
 	for (auto it : queuedBuffers_) {
-		unsigned int index = it.first;
 		Buffer *buffer = it.second;
 
 		BufferInfo info(BufferInfo::BufferCancelled, 0, 0, { {} });
 
-		buffer->index_ = index;
-		buffer->cancel();
 		bufferReady.emit(buffer, info);
 	}
 
@@ -1353,4 +1333,41 @@ void V4L2M2MDevice::close()
 	output_->close();
 }
 
+V4L2Stream::V4L2Stream(V4L2VideoDevice *video, unsigned int bufferCount)
+	: Stream(), video_(video), bufferCount_(bufferCount),
+	  allocated(false)
+{
+}
+
+int V4L2Stream::allocateBuffers(std::vector<Buffer *> *buffers)
+{
+	if (buffers) {
+		if (allocated)
+			return -EINVAL;
+
+		allocated = true;
+		return video_->allocateBuffers(bufferCount_, buffers);
+	} else {
+		if (!allocated)
+			return -EINVAL;
+
+		return video_->releaseBuffers();
+	}
+}
+
+int V4L2Stream::importBuffers(bool enable)
+{
+	/*
+	 * No need to prepare for buffer importing if internal buffers
+	 * are allocated.
+	 */
+	if (allocated)
+		return 0;
+
+	if (enable)
+		return video_->importBuffers(bufferCount_);
+	else
+		return video_->releaseBuffers();
+}
+
 } /* namespace libcamera */
diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp
index 2d7f4ba84fbb6838..d17aec42c82bbd98 100644
--- a/src/qcam/main_window.cpp
+++ b/src/qcam/main_window.cpp
@@ -22,7 +22,7 @@
 using namespace libcamera;
 
 MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options)
-	: options_(options), isCapturing_(false)
+	: options_(options), allocator_(nullptr), isCapturing_(false)
 {
 	int ret;
 
@@ -36,8 +36,10 @@ MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options)
 	adjustSize();
 
 	ret = openCamera(cm);
-	if (!ret)
+	if (!ret) {
+		allocator_ = new BufferAllocator(camera_);
 		ret = startCapture();
+	}
 
 	if (ret < 0)
 		QTimer::singleShot(0, QCoreApplication::instance(),
@@ -51,6 +53,8 @@ MainWindow::~MainWindow()
 		camera_->release();
 		camera_.reset();
 	}
+
+	delete allocator_;
 }
 
 void MainWindow::updateTitle()
@@ -175,22 +179,24 @@ int MainWindow::startCapture()
 		return ret;
 	}
 
+	ret = allocator_->allocate(stream);
+	if (ret < 0) {
+		std::cerr << "Failed to allocate internal buffers" << std::endl;
+		return ret;
+	}
+	unsigned int nbuffers = static_cast<unsigned int>(ret);
+
 	std::vector<Request *> requests;
-	for (unsigned int i = 0; i < cfg.bufferCount; ++i) {
+	for (unsigned int i = 0; i < nbuffers; ++i) {
 		Request *request = camera_->createRequest();
-		if (!request) {
+		Buffer *buffer = allocator_->get(stream)[i];
+		if (!request || !buffer) {
 			std::cerr << "Can't create request" << std::endl;
 			ret = -ENOMEM;
 			goto error;
 		}
 
-		std::unique_ptr<Buffer> buffer = stream->createBuffer(i);
-		if (!buffer) {
-			std::cerr << "Can't create buffer " << i << std::endl;
-			goto error;
-		}
-
-		ret = request->addBuffer(std::move(buffer));
+		ret = request->addBuffer(stream, buffer);
 		if (ret < 0) {
 			std::cerr << "Can't set buffer for request" << std::endl;
 			goto error;
@@ -281,15 +287,8 @@ void MainWindow::requestComplete(Request *request)
 	for (auto it = buffers.begin(); it != buffers.end(); ++it) {
 		Stream *stream = it->first;
 		Buffer *buffer = it->second;
-		unsigned int index = buffer->index();
-
-		std::unique_ptr<Buffer> newBuffer = stream->createBuffer(index);
-		if (!newBuffer) {
-			std::cerr << "Can't create buffer " << index << std::endl;
-			return;
-		}
 
-		request->addBuffer(std::move(newBuffer));
+		request->addBuffer(stream, buffer);
 	}
 
 	camera_->queueRequest(request);
@@ -297,12 +296,10 @@ void MainWindow::requestComplete(Request *request)
 
 int MainWindow::display(Buffer *buffer, const BufferInfo &info)
 {
-	BufferMemory *mem = buffer->mem();
-	if (mem->planes().size() != 1)
+	if (buffer->numPlanes() != 1)
 		return -EINVAL;
 
-	Plane &plane = mem->planes().front();
-	unsigned char *raw = static_cast<unsigned char *>(plane.mem());
+	unsigned char *raw = static_cast<unsigned char *>(buffer->plane(0)->mem());
 	viewfinder_->display(raw, info.plane(0).bytesused);
 
 	return 0;
diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h
index 8cbd72eb0d63cbea..eeb28eaae0dfc135 100644
--- a/src/qcam/main_window.h
+++ b/src/qcam/main_window.h
@@ -15,6 +15,7 @@
 #include <QObject>
 #include <QTimer>
 
+#include <libcamera/buffer.h>
 #include <libcamera/camera.h>
 #include <libcamera/camera_manager.h>
 #include <libcamera/stream.h>
@@ -58,6 +59,8 @@ private:
 	const OptionsParser::Options &options_;
 
 	std::shared_ptr<Camera> camera_;
+	BufferAllocator *allocator_;
+
 	bool isCapturing_;
 	std::unique_ptr<CameraConfiguration> config_;
 
diff --git a/test/camera/capture.cpp b/test/camera/capture.cpp
index 3cf5b798448d01db..8d3d612222a3b21e 100644
--- a/test/camera/capture.cpp
+++ b/test/camera/capture.cpp
@@ -41,10 +41,9 @@ protected:
 		/* Create a new request. */
 		Stream *stream = buffers.begin()->first;
 		Buffer *buffer = buffers.begin()->second;
-		std::unique_ptr<Buffer> newBuffer = stream->createBuffer(buffer->index());
 
 		request = camera_->createRequest();
-		request->addBuffer(std::move(newBuffer));
+		request->addBuffer(stream, buffer);
 		camera_->queueRequest(request);
 	}
 
@@ -84,21 +83,25 @@ protected:
 		}
 
 		Stream *stream = cfg.stream();
+
+		BufferAllocator allocator(camera_);
+		int ret = allocator.allocate(stream);
+		if (ret < 0)
+			return TestFail;
+
+		unsigned int nbuffers = static_cast<unsigned int>(ret);
+
 		std::vector<Request *> requests;
-		for (unsigned int i = 0; i < cfg.bufferCount; ++i) {
+		for (unsigned int i = 0; i < nbuffers; ++i) {
 			Request *request = camera_->createRequest();
-			if (!request) {
-				cout << "Failed to create request" << endl;
-				return TestFail;
-			}
+			Buffer *buffer = allocator.get(stream)[i];
 
-			std::unique_ptr<Buffer> buffer = stream->createBuffer(i);
-			if (!buffer) {
-				cout << "Failed to create buffer " << i << endl;
+			if (!request || !buffer) {
+				cout << "Failed to create request" << endl;
 				return TestFail;
 			}
 
-			if (request->addBuffer(std::move(buffer))) {
+			if (request->addBuffer(stream, buffer)) {
 				cout << "Failed to associating buffer with request" << endl;
 				return TestFail;
 			}
@@ -131,10 +134,10 @@ protected:
 		while (timer.isRunning())
 			dispatcher->processEvents();
 
-		if (completeRequestsCount_ <= cfg.bufferCount * 2) {
+		if (completeRequestsCount_ <= nbuffers * 2) {
 			cout << "Failed to capture enough frames (got "
 			     << completeRequestsCount_ << " expected at least "
-			     << cfg.bufferCount * 2 << ")" << endl;
+			     << nbuffers * 2 << ")" << endl;
 			return TestFail;
 		}
 
diff --git a/test/camera/statemachine.cpp b/test/camera/statemachine.cpp
index 12d5e0e1d5343f28..a77dec0f8b986634 100644
--- a/test/camera/statemachine.cpp
+++ b/test/camera/statemachine.cpp
@@ -178,6 +178,12 @@ protected:
 		if (camera_->allocateBuffers())
 			return TestFail;
 
+		/* Use internally allocated buffers. */
+		allocator_ = new BufferAllocator(camera_);
+		Stream *stream = *camera_->streams().begin();
+		if (allocator_->allocate(stream) < 0)
+			return TestFail;
+
 		if (camera_->start())
 			return TestFail;
 
@@ -211,8 +217,7 @@ protected:
 			return TestFail;
 
 		Stream *stream = *camera_->streams().begin();
-		std::unique_ptr<Buffer> buffer = stream->createBuffer(0);
-		if (request->addBuffer(std::move(buffer)))
+		if (request->addBuffer(stream, allocator_->get(stream)[0]))
 			return TestFail;
 
 		if (camera_->queueRequest(request))
@@ -222,6 +227,8 @@ protected:
 		if (camera_->stop())
 			return TestFail;
 
+		delete allocator_;
+
 		if (camera_->freeBuffers())
 			return TestFail;
 
@@ -278,6 +285,7 @@ protected:
 	}
 
 	std::unique_ptr<CameraConfiguration> defconf_;
+	BufferAllocator *allocator_;
 };
 
 } /* namespace */
diff --git a/test/v4l2_videodevice/buffer_sharing.cpp b/test/v4l2_videodevice/buffer_sharing.cpp
index 3609c331cf10e056..f0c4ace2f47cc58b 100644
--- a/test/v4l2_videodevice/buffer_sharing.cpp
+++ b/test/v4l2_videodevice/buffer_sharing.cpp
@@ -73,16 +73,14 @@ protected:
 			return TestFail;
 		}
 
-		pool_.createBuffers(bufferCount);
-
-		ret = capture_->exportBuffers(&pool_);
-		if (ret) {
-			std::cout << "Failed to export buffers" << std::endl;
+		ret = capture_->allocateBuffers(bufferCount, &buffers_);
+		if (ret < 0) {
+			std::cout << "Failed to allocate buffers" << std::endl;
 			return TestFail;
 		}
 
-		ret = output_->importBuffers(&pool_);
-		if (ret) {
+		ret = output_->importBuffers(bufferCount);
+		if (ret < 0) {
 			std::cout << "Failed to import buffers" << std::endl;
 			return TestFail;
 		}
@@ -98,8 +96,7 @@ protected:
 		if (info.status() != BufferInfo::BufferSuccess)
 			return;
 
-		BufferInfo infocopy = info;
-		output_->queueBuffer(buffer);
+		output_->queueBuffer(buffer, &info);
 		framesCaptured_++;
 	}
 
@@ -124,10 +121,8 @@ protected:
 		capture_->bufferReady.connect(this, &BufferSharingTest::captureBufferReady);
 		output_->bufferReady.connect(this, &BufferSharingTest::outputBufferReady);
 
-		std::vector<std::unique_ptr<Buffer>> buffers;
-		buffers = capture_->queueAllBuffers();
-		if (buffers.empty())
-			return TestFail;
+		for (Buffer *buffer : buffers_)
+			capture_->queueBuffer(buffer);
 
 		ret = capture_->streamOn();
 		if (ret) {
diff --git a/test/v4l2_videodevice/capture_async.cpp b/test/v4l2_videodevice/capture_async.cpp
index 26ea1d17fd901ef8..a12c968f36e937ab 100644
--- a/test/v4l2_videodevice/capture_async.cpp
+++ b/test/v4l2_videodevice/capture_async.cpp
@@ -38,18 +38,14 @@ protected:
 		Timer timeout;
 		int ret;
 
-		pool_.createBuffers(bufferCount);
-
-		ret = capture_->exportBuffers(&pool_);
-		if (ret)
+		ret = capture_->allocateBuffers(bufferCount, &buffers_);
+		if (ret < 0)
 			return TestFail;
 
 		capture_->bufferReady.connect(this, &CaptureAsyncTest::receiveBuffer);
 
-		std::vector<std::unique_ptr<Buffer>> buffers;
-		buffers = capture_->queueAllBuffers();
-		if (buffers.empty())
-			return TestFail;
+		for (Buffer *buffer : buffers_)
+			capture_->queueBuffer(buffer);
 
 		ret = capture_->streamOn();
 		if (ret)
diff --git a/test/v4l2_videodevice/request_buffers.cpp b/test/v4l2_videodevice/request_buffers.cpp
index c4aedf7b3cd61e80..2f8dfe1cafb111df 100644
--- a/test/v4l2_videodevice/request_buffers.cpp
+++ b/test/v4l2_videodevice/request_buffers.cpp
@@ -16,17 +16,10 @@ public:
 protected:
 	int run()
 	{
-		/*
-		 * TODO:
-		 *  Test invalid requests
-		 *  Test different buffer allocations
-		 */
 		const unsigned int bufferCount = 8;
 
-		pool_.createBuffers(bufferCount);
-
-		int ret = capture_->exportBuffers(&pool_);
-		if (ret)
+		int ret = capture_->allocateBuffers(bufferCount, &buffers_);
+		if (ret != bufferCount)
 			return TestFail;
 
 		return TestPass;
diff --git a/test/v4l2_videodevice/stream_on_off.cpp b/test/v4l2_videodevice/stream_on_off.cpp
index 7664adc4c1f07046..ce48310aa2b7c3a8 100644
--- a/test/v4l2_videodevice/stream_on_off.cpp
+++ b/test/v4l2_videodevice/stream_on_off.cpp
@@ -17,10 +17,8 @@ protected:
 	{
 		const unsigned int bufferCount = 8;
 
-		pool_.createBuffers(bufferCount);
-
-		int ret = capture_->exportBuffers(&pool_);
-		if (ret)
+		int ret = capture_->allocateBuffers(bufferCount, &buffers_);
+		if (ret < 0)
 			return TestFail;
 
 		ret = capture_->streamOn();
diff --git a/test/v4l2_videodevice/v4l2_m2mdevice.cpp b/test/v4l2_videodevice/v4l2_m2mdevice.cpp
index e6ca90a4604dfcda..def8d82972d754f9 100644
--- a/test/v4l2_videodevice/v4l2_m2mdevice.cpp
+++ b/test/v4l2_videodevice/v4l2_m2mdevice.cpp
@@ -112,17 +112,14 @@ protected:
 			return TestFail;
 		}
 
-		capturePool_.createBuffers(bufferCount);
-		outputPool_.createBuffers(bufferCount);
-
-		ret = capture->exportBuffers(&capturePool_);
-		if (ret) {
+		ret = capture->allocateBuffers(bufferCount, &captureBuffers_);
+		if (ret < 0) {
 			cerr << "Failed to export Capture Buffers" << endl;
 			return TestFail;
 		}
 
-		ret = output->exportBuffers(&outputPool_);
-		if (ret) {
+		ret = output->allocateBuffers(bufferCount, &outputBuffers_);
+		if (ret < 0) {
 			cerr << "Failed to export Output Buffers" << endl;
 			return TestFail;
 		}
@@ -130,24 +127,11 @@ protected:
 		capture->bufferReady.connect(this, &V4L2M2MDeviceTest::receiveCaptureBuffer);
 		output->bufferReady.connect(this, &V4L2M2MDeviceTest::outputBufferComplete);
 
-		std::vector<std::unique_ptr<Buffer>> captureBuffers;
-		captureBuffers = capture->queueAllBuffers();
-		if (captureBuffers.empty()) {
-			cerr << "Failed to queue all Capture Buffers" << endl;
-			return TestFail;
-		}
+		for (Buffer *buffer : captureBuffers_)
+			capture->queueBuffer(buffer);
 
-		/* We can't "queueAllBuffers()" on an output device, so we do it manually */
-		std::vector<std::unique_ptr<Buffer>> outputBuffers;
-		for (unsigned int i = 0; i < outputPool_.count(); ++i) {
-			Buffer *buffer = new Buffer(i);
-			outputBuffers.emplace_back(buffer);
-			ret = output->queueBuffer(buffer);
-			if (ret) {
-				cerr << "Failed to queue output buffer" << i << endl;
-				return TestFail;
-			}
-		}
+		for (Buffer *buffer : outputBuffers_)
+			output->queueBuffer(buffer);
 
 		ret = capture->streamOn();
 		if (ret) {
@@ -202,8 +186,8 @@ private:
 	std::shared_ptr<MediaDevice> media_;
 	V4L2M2MDevice *vim2m_;
 
-	BufferPool capturePool_;
-	BufferPool outputPool_;
+	std::vector<Buffer *> captureBuffers_;
+	std::vector<Buffer *> outputBuffers_;
 
 	unsigned int outputFrames_;
 	unsigned int captureFrames_;
diff --git a/test/v4l2_videodevice/v4l2_videodevice_test.h b/test/v4l2_videodevice/v4l2_videodevice_test.h
index 34dd231c6d9d108d..1b6f4b862fbae020 100644
--- a/test/v4l2_videodevice/v4l2_videodevice_test.h
+++ b/test/v4l2_videodevice/v4l2_videodevice_test.h
@@ -41,7 +41,7 @@ protected:
 	CameraSensor *sensor_;
 	V4L2Subdevice *debayer_;
 	V4L2VideoDevice *capture_;
-	BufferPool pool_;
+	std::vector<Buffer *> buffers_;
 };
 
 #endif /* __LIBCAMERA_V4L2_DEVICE_TEST_H_ */
-- 
2.23.0



More information about the libcamera-devel mailing list