diff --git a/include/libcamera/request.h b/include/libcamera/request.h
index 70f6d7fa7eeb..3353f037945e 100644
--- a/include/libcamera/request.h
+++ b/include/libcamera/request.h
@@ -36,7 +36,8 @@ public:
 	ControlList &controls() { return controls_; }
 	const std::map<Stream *, Buffer *> &buffers() const { return buffers_; }
 	int setBuffers(const std::map<Stream *, Buffer *> &streamMap);
-	Buffer *findBuffer(Stream *stream) const;
+	Buffer *findBuffer(Stream *stream);
+	Buffer *unmapBuffer(Buffer *streamBuffer);
 
 	Status status() const { return status_; }
 
@@ -55,6 +56,7 @@ private:
 	ControlList controls_;
 	std::map<Stream *, Buffer *> buffers_;
 	std::unordered_set<Buffer *> pending_;
+	std::map<Buffer *, Buffer *> bufferMap_;
 
 	Status status_;
 };
diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp
index 67b215483847..a47411ecf345 100644
--- a/src/libcamera/pipeline_handler.cpp
+++ b/src/libcamera/pipeline_handler.cpp
@@ -402,8 +402,10 @@ int PipelineHandler::queueRequest(Camera *camera, Request *request)
 bool PipelineHandler::completeBuffer(Camera *camera, Request *request,
 				     Buffer *buffer)
 {
-	camera->bufferCompleted.emit(request, buffer);
-	return request->completeBuffer(buffer);
+	Buffer *requestBuffer = request->unmapBuffer(buffer);
+
+	camera->bufferCompleted.emit(request, requestBuffer);
+	return request->completeBuffer(requestBuffer);
 }
 
 /**
diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp
index 9ff0abbf119c..0e07d39f8941 100644
--- a/src/libcamera/request.cpp
+++ b/src/libcamera/request.cpp
@@ -105,17 +105,58 @@ int Request::setBuffers(const std::map<Stream *, Buffer *> &streamMap)
  */
 
 /**
- * \brief Return the buffer associated with a stream
+ * \brief Retrieve the stream buffer associated with a stream
  * \param[in] stream The stream the buffer is associated to
+ *
+ * Depending on the configured memory type, the buffers originally provided
+ * by the application might get mapped to streams internal buffers.
+ *
+ * \sa Stream::mapBuffer()
+ *
  * \return The buffer associated with the stream, or nullptr if the stream is
  * not part of this request
  */
-Buffer *Request::findBuffer(Stream *stream) const
+Buffer *Request::findBuffer(Stream *stream)
 {
 	auto it = buffers_.find(stream);
 	if (it == buffers_.end())
 		return nullptr;
 
+	/*
+	 * Streams with internal memory mode do not need to perform any mapping
+	 * between the application provided buffers (part of the request)
+	 * and the one actually used by the Stream.
+	 *
+	 * Streams using externally allocated buffers need to create a mapping
+	 * between the application provided buffers and the one used by pipeline
+	 * handlers.
+	 */
+	Buffer *requestBuffer = it->second;
+	Buffer *mappedBuffer = stream->memoryType() == InternalMemory ?
+			       it->second : stream->mapBuffer(it->second);
+	bufferMap_[mappedBuffer] = requestBuffer;
+
+	return mappedBuffer;
+}
+
+/**
+ * \brief Retrieve the application buffer associated with \a streamBuffer
+ * \param streamBuffer The stream buffer returned from Request::findBuffer()
+ *
+ * This operation is used by the PipelineHandler base class to retrieve the
+ * buffer the application originally associated with the stream in the request.
+ * Depending on the configured memory type, application provided buffers might
+ * be mapped to streams internal buffers at Request::findBuffer() time.
+ *
+ * \sa Stream::mapBuffer()
+ *
+ * \return The application buffer provided to Request::findBuffer()
+ */
+Buffer *Request::unmapBuffer(Buffer *streamBuffer)
+{
+	auto it = bufferMap_.find(streamBuffer);
+	ASSERT(it != bufferMap_.end());
+
 	return it->second;
 }
 
