diff --git a/include/libcamera/buffer.h b/include/libcamera/buffer.h
index 260a62e9e77e..f5ba6207bcef 100644
--- a/include/libcamera/buffer.h
+++ b/include/libcamera/buffer.h
@@ -14,6 +14,7 @@ namespace libcamera {
 
 class BufferPool;
 class Request;
+class Stream;
 
 class Plane final
 {
@@ -36,6 +37,30 @@ private:
 	void *mem_;
 };
 
+class BufferMemory final
+{
+public:
+	std::vector<Plane> &planes() { return planes_; }
+
+private:
+	std::vector<Plane> planes_;
+};
+
+class BufferPool final
+{
+public:
+	~BufferPool();
+
+	void createBuffers(unsigned int count);
+	void destroyBuffers();
+
+	unsigned int count() const { return buffers_.size(); }
+	std::vector<BufferMemory> &buffers() { return buffers_; }
+
+private:
+	std::vector<BufferMemory> buffers_;
+};
+
 class Buffer final
 {
 public:
@@ -45,20 +70,24 @@ public:
 		BufferCancelled,
 	};
 
-	Buffer();
+	Buffer(unsigned int index = -1, const Buffer *metadata = nullptr);
+	Buffer(const Buffer &) = delete;
+	Buffer &operator=(const Buffer &) = delete;
 
 	unsigned int index() const { return index_; }
+
 	unsigned int bytesused() const { return bytesused_; }
 	uint64_t timestamp() const { return timestamp_; }
 	unsigned int sequence() const { return sequence_; }
+
 	Status status() const { return status_; }
-	std::vector<Plane> &planes() { return planes_; }
 	Request *request() const { return request_; }
+	Stream *stream() const { return stream_; }
 
 private:
-	friend class BufferPool;
 	friend class PipelineHandler;
 	friend class Request;
+	friend class Stream;
 	friend class V4L2VideoDevice;
 
 	void cancel();
@@ -66,28 +95,14 @@ private:
 	void setRequest(Request *request) { request_ = request; }
 
 	unsigned int index_;
+
 	unsigned int bytesused_;
 	uint64_t timestamp_;
 	unsigned int sequence_;
+
 	Status status_;
-
-	std::vector<Plane> planes_;
 	Request *request_;
-};
-
-class BufferPool final
-{
-public:
-	~BufferPool();
-
-	void createBuffers(unsigned int count);
-	void destroyBuffers();
-
-	unsigned int count() const { return buffers_.size(); }
-	std::vector<Buffer> &buffers() { return buffers_; }
-
-private:
-	std::vector<Buffer> buffers_;
+	Stream *stream_;
 };
 
 } /* namespace libcamera */
diff --git a/include/libcamera/request.h b/include/libcamera/request.h
index dd165bc21c03..7a60be617645 100644
--- a/include/libcamera/request.h
+++ b/include/libcamera/request.h
@@ -8,6 +8,7 @@
 #define __LIBCAMERA_REQUEST_H__
 
 #include <map>
+#include <memory>
 #include <stdint.h>
 #include <unordered_set>
 
@@ -33,10 +34,11 @@ public:
 	Request(Camera *camera, uint64_t cookie = 0);
 	Request(const Request &) = delete;
 	Request &operator=(const Request &) = delete;
+	~Request();
 
 	ControlList &controls() { return controls_; }
 	const std::map<Stream *, Buffer *> &buffers() const { return bufferMap_; }
-	int setBuffers(const std::map<Stream *, Buffer *> &streamMap);
+	int addBuffer(std::unique_ptr<Buffer> buffer);
 	Buffer *findBuffer(Stream *stream) const;
 
 	uint64_t cookie() const { return cookie_; }
diff --git a/include/libcamera/stream.h b/include/libcamera/stream.h
index 5b4fea324ce4..f595a630eb4a 100644
--- a/include/libcamera/stream.h
+++ b/include/libcamera/stream.h
@@ -8,6 +8,7 @@
 #define __LIBCAMERA_STREAM_H__
 
 #include <map>
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -66,6 +67,9 @@ class Stream
 {
 public:
 	Stream();
+
+	std::unique_ptr<Buffer> createBuffer(unsigned int index);
+
 	BufferPool &bufferPool() { return bufferPool_; }
 	const StreamConfiguration &configuration() const { return configuration_; }
 
diff --git a/src/cam/buffer_writer.cpp b/src/cam/buffer_writer.cpp
index e0374ffcb319..b7f2ed4f2d2d 100644
--- a/src/cam/buffer_writer.cpp
+++ b/src/cam/buffer_writer.cpp
@@ -19,7 +19,7 @@ BufferWriter::BufferWriter(const std::string &pattern)
 {
 }
 
-int BufferWriter::write(libcamera::Buffer *buffer,
+int BufferWriter::write(libcamera::Buffer *buffer, libcamera::BufferMemory *mem,
 			const std::string &streamName)
 {
 	std::string filename;
@@ -41,7 +41,7 @@ int BufferWriter::write(libcamera::Buffer *buffer,
 	if (fd == -1)
 		return -errno;
 
-	for (libcamera::Plane &plane : buffer->planes()) {
+	for (libcamera::Plane &plane : mem->planes()) {
 		void *data = plane.mem();
 		unsigned int length = plane.length();
 
diff --git a/src/cam/buffer_writer.h b/src/cam/buffer_writer.h
index 7bf785d1e832..9bea205fac1d 100644
--- a/src/cam/buffer_writer.h
+++ b/src/cam/buffer_writer.h
@@ -16,7 +16,8 @@ class BufferWriter
 public:
 	BufferWriter(const std::string &pattern = "frame-#.bin");
 
-	int write(libcamera::Buffer *buffer, const std::string &streamName);
+	int write(libcamera::Buffer *buffer, libcamera::BufferMemory *mem,
+		  const std::string &streamName);
 
 private:
 	std::string pattern_;
diff --git a/src/cam/capture.cpp b/src/cam/capture.cpp
index 6b842d73390d..1cf59063c93b 100644
--- a/src/cam/capture.cpp
+++ b/src/cam/capture.cpp
@@ -95,13 +95,14 @@ int Capture::capture(EventLoop *loop)
 		std::map<Stream *, Buffer *> map;
 		for (StreamConfiguration &cfg : *config_) {
 			Stream *stream = cfg.stream();
-			map[stream] = &stream->bufferPool().buffers()[i];
-		}
+			std::unique_ptr<Buffer> buffer = stream->createBuffer(i);
 
-		ret = request->setBuffers(map);
-		if (ret < 0) {
-			std::cerr << "Can't set buffers for request" << std::endl;
-			return ret;
+			ret = request->addBuffer(std::move(buffer));
+			if (ret < 0) {
+				std::cerr << "Can't set buffer for request"
+					  << std::endl;
+				return ret;
+			}
 		}
 
 		requests.push_back(request);
@@ -155,6 +156,7 @@ void Capture::requestComplete(Request *request, const std::map<Stream *, Buffer
 	for (auto it = buffers.begin(); it != buffers.end(); ++it) {
 		Stream *stream = it->first;
 		Buffer *buffer = it->second;
+		BufferMemory *mem = &stream->bufferPool().buffers()[buffer->index()];
 		const std::string &name = streamName_[stream];
 
 		info << " " << name
@@ -163,17 +165,34 @@ void Capture::requestComplete(Request *request, const std::map<Stream *, Buffer
 		     << " bytesused: " << buffer->bytesused();
 
 		if (writer_)
-			writer_->write(buffer, name);
+			writer_->write(buffer, mem, name);
 	}
 
 	std::cout << info.str() << std::endl;
 
+	/*
+	 * Create a new request and populate it with one buffer for each
+	 * stream.
+	 */
 	request = camera_->createRequest();
 	if (!request) {
 		std::cerr << "Can't create request" << std::endl;
 		return;
 	}
 
-	request->setBuffers(buffers);
+	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));
+	}
+
 	camera_->queueRequest(request);
 }
diff --git a/src/libcamera/buffer.cpp b/src/libcamera/buffer.cpp
index c0a020942f5c..ecbf25246a55 100644
--- a/src/libcamera/buffer.cpp
+++ b/src/libcamera/buffer.cpp
@@ -173,12 +173,78 @@ void *Plane::mem()
  */
 
 /**
- * \class Buffer
+ * \class BufferMemory
  * \brief A memory buffer to store an image
  *
- * The Buffer class represents the memory buffers used to store a
- * full frame image, which may contain multiple separate memory Plane
- * objects if the image format is multi-planar.
+ * The BufferMemory class represents the memory buffers used to store full frame
+ * images, which may contain multiple separate memory Plane objects if the
+ * image format is multi-planar.
+ */
+
+/**
+ * \fn BufferMemory::planes()
+ * \brief Retrieve the planes within the buffer
+ * \return A reference to a vector holding all Planes within the buffer
+ */
+
+/**
+ * \class BufferPool
+ * \brief A pool of buffers
+ *
+ * The BufferPool class groups together a collection of Buffers to store frames.
+ * The buffers must be exported by a device before they can be imported into
+ * another device for further use.
+ */
+
+BufferPool::~BufferPool()
+{
+	destroyBuffers();
+}
+
+/**
+ * \brief Create buffers in the Pool
+ * \param[in] count The number of buffers to create
+ */
+void BufferPool::createBuffers(unsigned int count)
+{
+	buffers_.resize(count);
+}
+
+/**
+ * \brief Release all buffers from pool
+ *
+ * If no buffers have been created or if buffers have already been released no
+ * operation is performed.
+ */
+void BufferPool::destroyBuffers()
+{
+	buffers_.resize(0);
+}
+
+/**
+ * \fn BufferPool::count()
+ * \brief Retrieve the number of buffers contained within the pool
+ * \return The number of buffers contained in the pool
+ */
+
+/**
+ * \fn BufferPool::buffers()
+ * \brief Retrieve all the buffers in the pool
+ * \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.
  */
 
 /**
@@ -195,9 +261,26 @@ void *Plane::mem()
  * invalid and shall not be used.
  */
 
-Buffer::Buffer()
-	: index_(-1), request_(nullptr)
+/**
+ * \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), status_(Buffer::BufferSuccess), request_(nullptr),
+	  stream_(nullptr)
 {
+	if (metadata) {
+		bytesused_ = metadata->bytesused_;
+		sequence_ = metadata->sequence_;
+		timestamp_ = metadata->timestamp_;
+	} else {
+		bytesused_ = 0;
+		sequence_ = 0;
+		timestamp_ = 0;
+	}
 }
 
 /**
@@ -206,12 +289,6 @@ Buffer::Buffer()
  * \return The buffer index
  */
 
-/**
- * \fn Buffer::planes()
- * \brief Retrieve the planes within the buffer
- * \return A reference to a vector holding all Planes within the buffer
- */
-
 /**
  * \fn Buffer::bytesused()
  * \brief Retrieve the number of bytes occupied by the data in the buffer
@@ -264,6 +341,19 @@ Buffer::Buffer()
  * \sa Buffer::setRequest()
  */
 
+/**
+ * \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
+ */
+
 /**
  * \brief Mark a buffer as cancel by setting its status to BufferCancelled
  */
@@ -282,54 +372,4 @@ void Buffer::cancel()
  * The intended callers are Request::prepare() and Request::completeBuffer().
  */
 
-/**
- * \class BufferPool
- * \brief A pool of buffers
- *
- * The BufferPool class groups together a collection of Buffers to store frames.
- * The buffers must be exported by a device before they can be imported into
- * another device for further use.
- */
-
-BufferPool::~BufferPool()
-{
-	destroyBuffers();
-}
-
-/**
- * \brief Create buffers in the Pool
- * \param[in] count The number of buffers to create
- */
-void BufferPool::createBuffers(unsigned int count)
-{
-	unsigned int index = 0;
-
-	buffers_.resize(count);
-	for (Buffer &buffer : buffers_)
-		buffer.index_ = index++;
-}
-
-/**
- * \brief Release all buffers from pool
- *
- * If no buffers have been created or if buffers have already been released no
- * operation is performed.
- */
-void BufferPool::destroyBuffers()
-{
-	buffers_.resize(0);
-}
-
-/**
- * \fn BufferPool::count()
- * \brief Retrieve the number of buffers contained within the pool
- * \return The number of buffers contained in the pool
- */
-
-/**
- * \fn BufferPool::buffers()
- * \brief Retrieve all the buffers in the pool
- * \return A vector containing all the buffers in the pool.
- */
-
 } /* namespace libcamera */
diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h
index f948a41fb8c1..8238a39b0da4 100644
--- a/src/libcamera/include/v4l2_videodevice.h
+++ b/src/libcamera/include/v4l2_videodevice.h
@@ -7,7 +7,6 @@
 #ifndef __LIBCAMERA_V4L2_VIDEODEVICE_H__
 #define __LIBCAMERA_V4L2_VIDEODEVICE_H__
 
-#include <atomic>
 #include <string>
 #include <vector>
 
@@ -23,6 +22,7 @@
 namespace libcamera {
 
 class Buffer;
+class BufferMemory;
 class BufferPool;
 class EventNotifier;
 class MediaDevice;
@@ -165,8 +165,8 @@ private:
 	std::vector<SizeRange> enumSizes(unsigned int pixelFormat);
 
 	int requestBuffers(unsigned int count);
-	int createPlane(Buffer *buffer, unsigned int plane,
-			unsigned int length);
+	int createPlane(BufferMemory *buffer, unsigned int index,
+			unsigned int plane, unsigned int length);
 
 	Buffer *dequeueBuffer();
 	void bufferAvailable(EventNotifier *notifier);
@@ -177,7 +177,7 @@ private:
 	enum v4l2_memory memoryType_;
 
 	BufferPool *bufferPool_;
-	std::atomic<unsigned int> queuedBuffersCount_;
+	std::map<unsigned int, Buffer *> queuedBuffers_;
 
 	EventNotifier *fdEvent_;
 };
diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp
index 8cf41a43a80e..45e7133febb0 100644
--- a/src/libcamera/request.cpp
+++ b/src/libcamera/request.cpp
@@ -60,6 +60,14 @@ Request::Request(Camera *camera, uint64_t cookie)
 {
 }
 
+Request::~Request()
+{
+	for (auto it : bufferMap_) {
+		Buffer *buffer = it.second;
+		delete buffer;
+	}
+}
+
 /**
  * \fn Request::controls()
  * \brief Retrieve the request's ControlList
@@ -87,19 +95,30 @@ Request::Request(Camera *camera, uint64_t cookie)
  */
 
 /**
- * \brief Set the streams to capture with associated buffers
- * \param[in] streamMap The map of streams to buffers
+ * \brief Store a Buffer with its associated Stream in the Request
+ * \param[in] buffer The Buffer to store in the request
+ *
+ * Ownership of the buffer is passed to the request. It will be deleted when
+ * the request is destroyed after completing.
+ *
+ * A request can only contain one buffer per stream. If a buffer has already
+ * been added to the request for the same stream, this method returns -EEXIST.
+ *
  * \return 0 on success or a negative error code otherwise
- * \retval -EBUSY Buffers have already been set
+ * \retval -EEXIST The request already contains a buffer for the stream
  */
-int Request::setBuffers(const std::map<Stream *, Buffer *> &streamMap)
+int Request::addBuffer(std::unique_ptr<Buffer> buffer)
 {
-	if (!bufferMap_.empty()) {
-		LOG(Request, Error) << "Buffers already set";
-		return -EBUSY;
+	Stream *stream = buffer->stream();
+
+	auto it = bufferMap_.find(stream);
+	if (it != bufferMap_.end()) {
+		LOG(Request, Error) << "Buffer already set for stream";
+		return -EEXIST;
 	}
 
-	bufferMap_ = streamMap;
+	bufferMap_[stream] = buffer.release();
+
 	return 0;
 }
 
diff --git a/src/libcamera/stream.cpp b/src/libcamera/stream.cpp
index d8e87c62281c..6d80646b55cd 100644
--- a/src/libcamera/stream.cpp
+++ b/src/libcamera/stream.cpp
@@ -407,6 +407,30 @@ Stream::Stream()
 {
 }
 
+/**
+ * \brief Create a Buffer instance
+ * \param[in] index The desired buffer index
+ *
+ * This method creates a Buffer instance that references a BufferMemory from
+ * the stream's buffers pool by its \a index. The index shall be lower than the
+ * number of buffers in the pool.
+ *
+ * \return A newly created Buffer on success or nullptr otherwise
+ */
+std::unique_ptr<Buffer> Stream::createBuffer(unsigned int index)
+{
+	if (index >= bufferPool_.count()) {
+		LOG(Stream, Error) << "Invalid buffer index " << index;
+		return nullptr;
+	}
+
+	Buffer *buffer = new Buffer();
+	buffer->index_ = index;
+	buffer->stream_ = this;
+
+	return std::unique_ptr<Buffer>(buffer);
+}
+
 /**
  * \fn Stream::bufferPool()
  * \brief Retrieve the buffer pool for the stream
diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp
index af5ba803de45..65b4098abc05 100644
--- a/src/libcamera/v4l2_videodevice.cpp
+++ b/src/libcamera/v4l2_videodevice.cpp
@@ -268,8 +268,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),
-	  queuedBuffersCount_(0), fdEvent_(nullptr)
+	: V4L2Device(deviceNode), bufferPool_(nullptr), fdEvent_(nullptr)
 {
 	/*
 	 * We default to an MMAP based CAPTURE video device, however this will
@@ -764,7 +763,7 @@ int V4L2VideoDevice::exportBuffers(BufferPool *pool)
 	for (i = 0; i < pool->count(); ++i) {
 		struct v4l2_plane planes[VIDEO_MAX_PLANES] = {};
 		struct v4l2_buffer buf = {};
-		Buffer &buffer = pool->buffers()[i];
+		BufferMemory &buffer = pool->buffers()[i];
 
 		buf.index = i;
 		buf.type = bufferType_;
@@ -782,13 +781,13 @@ int V4L2VideoDevice::exportBuffers(BufferPool *pool)
 
 		if (V4L2_TYPE_IS_MULTIPLANAR(buf.type)) {
 			for (unsigned int p = 0; p < buf.length; ++p) {
-				ret = createPlane(&buffer, p,
+				ret = createPlane(&buffer, i, p,
 						  buf.m.planes[p].length);
 				if (ret)
 					break;
 			}
 		} else {
-			ret = createPlane(&buffer, 0, buf.length);
+			ret = createPlane(&buffer, i, 0, buf.length);
 		}
 
 		if (ret) {
@@ -808,19 +807,19 @@ int V4L2VideoDevice::exportBuffers(BufferPool *pool)
 	return 0;
 }
 
-int V4L2VideoDevice::createPlane(Buffer *buffer, unsigned int planeIndex,
-				 unsigned int length)
+int V4L2VideoDevice::createPlane(BufferMemory *buffer, unsigned int index,
+				 unsigned int planeIndex, unsigned int length)
 {
 	struct v4l2_exportbuffer expbuf = {};
 	int ret;
 
 	LOG(V4L2, Debug)
-		<< "Buffer " << buffer->index()
+		<< "Buffer " << index
 		<< " plane " << planeIndex
 		<< ": length=" << length;
 
 	expbuf.type = bufferType_;
-	expbuf.index = buffer->index();
+	expbuf.index = index;
 	expbuf.plane = planeIndex;
 	expbuf.flags = O_RDWR;
 
@@ -904,7 +903,8 @@ int V4L2VideoDevice::queueBuffer(Buffer *buffer)
 	buf.field = V4L2_FIELD_NONE;
 
 	bool multiPlanar = V4L2_TYPE_IS_MULTIPLANAR(buf.type);
-	const std::vector<Plane> &planes = buffer->planes();
+	BufferMemory *mem = &bufferPool_->buffers()[buf.index];
+	const std::vector<Plane> &planes = mem->planes();
 
 	if (buf.memory == V4L2_MEMORY_DMABUF) {
 		if (multiPlanar) {
@@ -937,9 +937,11 @@ int V4L2VideoDevice::queueBuffer(Buffer *buffer)
 		return ret;
 	}
 
-	if (queuedBuffersCount_++ == 0)
+	if (queuedBuffers_.empty())
 		fdEvent_->setEnabled(true);
 
+	queuedBuffers_[buf.index] = buffer;
+
 	return 0;
 }
 
@@ -971,7 +973,7 @@ int V4L2VideoDevice::queueAllBuffers(std::vector<std::unique_ptr<Buffer>> *buffe
 {
 	int ret;
 
-	if (queuedBuffersCount_)
+	if (!queuedBuffers_.empty())
 		return -EINVAL;
 
 	if (V4L2_TYPE_IS_OUTPUT(bufferType_))
@@ -980,8 +982,7 @@ int V4L2VideoDevice::queueAllBuffers(std::vector<std::unique_ptr<Buffer>> *buffe
 	buffers->clear();
 
 	for (unsigned int i = 0; i < bufferPool_->count(); ++i) {
-		Buffer *buffer = new Buffer();
-		buffer->index_ = i;
+		Buffer *buffer = new Buffer(i);
 		buffers->emplace_back(buffer);
 		ret = queueBuffer(buffer);
 		if (ret)
@@ -1022,11 +1023,14 @@ Buffer *V4L2VideoDevice::dequeueBuffer()
 
 	ASSERT(buf.index < bufferPool_->count());
 
-	if (--queuedBuffersCount_ == 0)
+	auto it = queuedBuffers_.find(buf.index);
+	Buffer *buffer = it->second;
+	queuedBuffers_.erase(it);
+
+	if (queuedBuffers_.empty())
 		fdEvent_->setEnabled(false);
 
-	Buffer *buffer = &bufferPool_->buffers()[buf.index];
-
+	buffer->index_ = buf.index;
 	buffer->bytesused_ = buf.bytesused;
 	buffer->timestamp_ = buf.timestamp.tv_sec * 1000000000ULL
 			   + buf.timestamp.tv_usec * 1000ULL;
@@ -1101,7 +1105,7 @@ int V4L2VideoDevice::streamOff()
 		return ret;
 	}
 
-	queuedBuffersCount_ = 0;
+	queuedBuffers_.clear();
 	fdEvent_->setEnabled(false);
 
 	return 0;
diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp
index 907d2423ed15..6ecf30e33bcf 100644
--- a/src/qcam/main_window.cpp
+++ b/src/qcam/main_window.cpp
@@ -142,8 +142,7 @@ int MainWindow::startCapture()
 
 	BufferPool &pool = stream->bufferPool();
 	std::vector<Request *> requests;
-
-	for (Buffer &buffer : pool.buffers()) {
+	for (unsigned int i = 0; i < pool.count(); ++i) {
 		Request *request = camera_->createRequest();
 		if (!request) {
 			std::cerr << "Can't create request" << std::endl;
@@ -151,11 +150,15 @@ int MainWindow::startCapture()
 			goto error;
 		}
 
-		std::map<Stream *, Buffer *> map;
-		map[stream] = &buffer;
-		ret = request->setBuffers(map);
+		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));
 		if (ret < 0) {
-			std::cerr << "Can't set buffers for request" << std::endl;
+			std::cerr << "Can't set buffer for request" << std::endl;
 			goto error;
 		}
 
@@ -219,6 +222,7 @@ void MainWindow::requestComplete(Request *request,
 
 	framesCaptured_++;
 
+	Stream *stream = buffers.begin()->first;
 	Buffer *buffer = buffers.begin()->second;
 
 	double fps = buffer->timestamp() - lastBufferTime_;
@@ -232,7 +236,8 @@ void MainWindow::requestComplete(Request *request,
 		  << " fps: " << std::fixed << std::setprecision(2) << fps
 		  << std::endl;
 
-	display(buffer);
+	BufferMemory *mem = &stream->bufferPool().buffers()[buffer->index()];
+	display(buffer, mem);
 
 	request = camera_->createRequest();
 	if (!request) {
@@ -240,16 +245,29 @@ void MainWindow::requestComplete(Request *request,
 		return;
 	}
 
-	request->setBuffers(buffers);
+	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));
+	}
+
 	camera_->queueRequest(request);
 }
 
-int MainWindow::display(Buffer *buffer)
+int MainWindow::display(Buffer *buffer, BufferMemory *mem)
 {
-	if (buffer->planes().size() != 1)
+	if (mem->planes().size() != 1)
 		return -EINVAL;
 
-	Plane &plane = buffer->planes().front();
+	Plane &plane = mem->planes().front();
 	unsigned char *raw = static_cast<unsigned char *>(plane.mem());
 	viewfinder_->display(raw, buffer->bytesused());
 
diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h
index f58cb6a65b2b..b4f6f747e16e 100644
--- a/src/qcam/main_window.h
+++ b/src/qcam/main_window.h
@@ -48,7 +48,7 @@ private:
 
 	void requestComplete(Request *request,
 			     const std::map<Stream *, Buffer *> &buffers);
-	int display(Buffer *buffer);
+	int display(Buffer *buffer, BufferMemory *mem);
 
 	QString title_;
 	QTimer titleTimer_;
diff --git a/test/camera/capture.cpp b/test/camera/capture.cpp
index 7ce247cc482d..ff1cbd6cbac0 100644
--- a/test/camera/capture.cpp
+++ b/test/camera/capture.cpp
@@ -34,9 +34,13 @@ protected:
 
 		completeRequestsCount_++;
 
-		/* Reuse the buffers for a new request. */
+		/* 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->setBuffers(buffers);
+		request->addBuffer(std::move(newBuffer));
 		camera_->queueRequest(request);
 	}
 
@@ -78,15 +82,20 @@ protected:
 		Stream *stream = cfg.stream();
 		BufferPool &pool = stream->bufferPool();
 		std::vector<Request *> requests;
-		for (Buffer &buffer : pool.buffers()) {
+		for (unsigned int i = 0; i < pool.count(); ++i) {
 			Request *request = camera_->createRequest();
 			if (!request) {
 				cout << "Failed to create request" << endl;
 				return TestFail;
 			}
 
-			std::map<Stream *, Buffer *> map = { { stream, &buffer } };
-			if (request->setBuffers(map)) {
+			std::unique_ptr<Buffer> buffer = stream->createBuffer(i);
+			if (!buffer) {
+				cout << "Failed to create buffer " << i << endl;
+				return TestFail;
+			}
+
+			if (request->addBuffer(std::move(buffer))) {
 				cout << "Failed to associating buffer with request" << endl;
 				return TestFail;
 			}
diff --git a/test/camera/statemachine.cpp b/test/camera/statemachine.cpp
index 84d2a6fab5f0..12d5e0e1d534 100644
--- a/test/camera/statemachine.cpp
+++ b/test/camera/statemachine.cpp
@@ -211,10 +211,8 @@ protected:
 			return TestFail;
 
 		Stream *stream = *camera_->streams().begin();
-		BufferPool &pool = stream->bufferPool();
-		Buffer &buffer = pool.buffers().front();
-		std::map<Stream *, Buffer *> map = { { stream, &buffer } };
-		if (request->setBuffers(map))
+		std::unique_ptr<Buffer> buffer = stream->createBuffer(0);
+		if (request->addBuffer(std::move(buffer)))
 			return TestFail;
 
 		if (camera_->queueRequest(request))
