diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
index 586b46d64630..0479e243d392 100644
--- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp
+++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
@@ -39,6 +39,7 @@
 #include "libcamera/internal/ipa_manager.h"
 #include "libcamera/internal/media_device.h"
 #include "libcamera/internal/pipeline_handler.h"
+#include "libcamera/internal/request.h"
 #include "libcamera/internal/v4l2_subdevice.h"
 #include "libcamera/internal/v4l2_videodevice.h"
 
@@ -48,67 +49,63 @@ namespace libcamera {
 
 LOG_DEFINE_CATEGORY(RkISP1)
 
-class PipelineHandlerRkISP1;
-class RkISP1CameraData;
+class RkISP1Request : public Request::Private
+{
+public:
+	RkISP1Request(Camera *camera)
+		: Request::Private(camera)
+	{
+	}
 
-struct RkISP1FrameInfo {
-	unsigned int frame;
-	Request *request;
+	bool hasPendingBuffers(bool isRaw) const;
 
-	FrameBuffer *paramBuffer;
 	FrameBuffer *statBuffer;
-	FrameBuffer *mainPathBuffer;
-	FrameBuffer *selfPathBuffer;
+	FrameBuffer *paramBuffer;
+
+	/* The frame number this request is associated with. */
+	unsigned int frame;
 
 	bool paramDequeued;
 	bool metadataProcessed;
 };
 
-class RkISP1Frames
+bool RkISP1Request::hasPendingBuffers(bool isRaw) const
 {
-public:
-	RkISP1Frames(PipelineHandler *pipe);
-
-	RkISP1FrameInfo *create(const RkISP1CameraData *data, Request *request,
-				bool isRaw);
-	int destroy(unsigned int frame);
-	void clear();
-
-	RkISP1FrameInfo *find(unsigned int frame);
-	RkISP1FrameInfo *find(FrameBuffer *buffer);
-	RkISP1FrameInfo *find(Request *request);
-
-private:
-	PipelineHandlerRkISP1 *pipe_;
-	std::map<unsigned int, RkISP1FrameInfo *> frameInfo_;
-};
+	return Request::Private::hasPendingBuffers() ||
+	       !metadataProcessed || (!isRaw && !paramDequeued);
+}
 
+class PipelineHandlerRkISP1;
 class RkISP1CameraData : public Camera::Private
 {
 public:
 	RkISP1CameraData(PipelineHandler *pipe, RkISP1MainPath *mainPath,
 			 RkISP1SelfPath *selfPath)
-		: Camera::Private(pipe), frame_(0), frameInfo_(pipe),
-		  mainPath_(mainPath), selfPath_(selfPath)
+		: Camera::Private(pipe), frame_(0), mainPath_(mainPath),
+		  selfPath_(selfPath)
 	{
 	}
 
 	PipelineHandlerRkISP1 *pipe();
 	int loadIPA(unsigned int hwRevision);
 
+	void addRequest(RkISP1Request *request);
+
 	Stream mainPathStream_;
 	Stream selfPathStream_;
 	std::unique_ptr<CameraSensor> sensor_;
 	std::unique_ptr<DelayedControls> delayedCtrls_;
 	unsigned int frame_;
 	std::vector<IPABuffer> ipaBuffers_;
-	RkISP1Frames frameInfo_;
 
 	RkISP1MainPath *mainPath_;
 	RkISP1SelfPath *selfPath_;
 
 	std::unique_ptr<ipa::rkisp1::IPAProxyRkISP1> ipa_;
 
+	/* Associate a frame id with a Request. */
+	std::map<unsigned int, RkISP1Request *> requestMap_;
+
 private:
 	void paramFilled(unsigned int frame);
 	void setSensorControls(unsigned int frame,
@@ -157,6 +154,8 @@ public:
 	int start(Camera *camera, const ControlList *controls) override;
 	void stopDevice(Camera *camera) override;
 
+	std::unique_ptr<Request> createRequestDevice(Camera *camera,
+						     uint64_t cookie) override;
 	int queueRequestDevice(Camera *camera, Request *request) override;
 
 	bool match(DeviceEnumerator *enumerator) override;
@@ -169,13 +168,17 @@ private:
 		return static_cast<RkISP1CameraData *>(camera->_d());
 	}
 
+	RkISP1Request *cameraRequest(Request *request)
+	{
+		return static_cast<RkISP1Request *>(request->_d());
+	}
+
 	friend RkISP1CameraData;
-	friend RkISP1Frames;
 
 	int initLinks(Camera *camera, const CameraSensor *sensor,
 		      const RkISP1CameraConfiguration &config);
 	int createCamera(MediaEntity *sensor);
-	void tryCompleteRequest(RkISP1FrameInfo *info);
+	void tryCompleteRequest(RkISP1Request *request);
 	void bufferReady(FrameBuffer *buffer);
 	void paramReady(FrameBuffer *buffer);
 	void statReady(FrameBuffer *buffer);
@@ -206,129 +209,6 @@ private:
 	const MediaPad *ispSink_;
 };
 
-RkISP1Frames::RkISP1Frames(PipelineHandler *pipe)
-	: pipe_(static_cast<PipelineHandlerRkISP1 *>(pipe))
-{
-}
-
-RkISP1FrameInfo *RkISP1Frames::create(const RkISP1CameraData *data, Request *request,
-				      bool isRaw)
-{
-	unsigned int frame = data->frame_;
-
-	FrameBuffer *paramBuffer = nullptr;
-	FrameBuffer *statBuffer = nullptr;
-
-	if (!isRaw) {
-		if (pipe_->availableParamBuffers_.empty()) {
-			LOG(RkISP1, Error) << "Parameters buffer underrun";
-			return nullptr;
-		}
-
-		if (pipe_->availableStatBuffers_.empty()) {
-			LOG(RkISP1, Error) << "Statistic buffer underrun";
-			return nullptr;
-		}
-
-		paramBuffer = pipe_->availableParamBuffers_.front();
-		pipe_->availableParamBuffers_.pop();
-
-		statBuffer = pipe_->availableStatBuffers_.front();
-		pipe_->availableStatBuffers_.pop();
-	}
-
-	FrameBuffer *mainPathBuffer = request->findBuffer(&data->mainPathStream_);
-	FrameBuffer *selfPathBuffer = request->findBuffer(&data->selfPathStream_);
-
-	RkISP1FrameInfo *info = new RkISP1FrameInfo;
-
-	info->frame = frame;
-	info->request = request;
-	info->paramBuffer = paramBuffer;
-	info->mainPathBuffer = mainPathBuffer;
-	info->selfPathBuffer = selfPathBuffer;
-	info->statBuffer = statBuffer;
-	info->paramDequeued = false;
-	info->metadataProcessed = false;
-
-	frameInfo_[frame] = info;
-
-	return info;
-}
-
-int RkISP1Frames::destroy(unsigned int frame)
-{
-	RkISP1FrameInfo *info = find(frame);
-	if (!info)
-		return -ENOENT;
-
-	pipe_->availableParamBuffers_.push(info->paramBuffer);
-	pipe_->availableStatBuffers_.push(info->statBuffer);
-
-	frameInfo_.erase(info->frame);
-
-	delete info;
-
-	return 0;
-}
-
-void RkISP1Frames::clear()
-{
-	for (const auto &entry : frameInfo_) {
-		RkISP1FrameInfo *info = entry.second;
-
-		pipe_->availableParamBuffers_.push(info->paramBuffer);
-		pipe_->availableStatBuffers_.push(info->statBuffer);
-
-		delete info;
-	}
-
-	frameInfo_.clear();
-}
-
-RkISP1FrameInfo *RkISP1Frames::find(unsigned int frame)
-{
-	auto itInfo = frameInfo_.find(frame);
-
-	if (itInfo != frameInfo_.end())
-		return itInfo->second;
-
-	LOG(RkISP1, Fatal) << "Can't locate info from frame";
-
-	return nullptr;
-}
-
-RkISP1FrameInfo *RkISP1Frames::find(FrameBuffer *buffer)
-{
-	for (auto &itInfo : frameInfo_) {
-		RkISP1FrameInfo *info = itInfo.second;
-
-		if (info->paramBuffer == buffer ||
-		    info->statBuffer == buffer ||
-		    info->mainPathBuffer == buffer ||
-		    info->selfPathBuffer == buffer)
-			return info;
-	}
-
-	LOG(RkISP1, Fatal) << "Can't locate info from buffer";
-
-	return nullptr;
-}
-
-RkISP1FrameInfo *RkISP1Frames::find(Request *request)
-{
-	for (auto &itInfo : frameInfo_) {
-		RkISP1FrameInfo *info = itInfo.second;
-
-		if (info->request == request)
-			return info;
-	}
-
-	LOG(RkISP1, Fatal) << "Can't locate info from request";
-
-	return nullptr;
-}
-
 PipelineHandlerRkISP1 *RkISP1CameraData::pipe()
 {
 	return static_cast<PipelineHandlerRkISP1 *>(Camera::Private::pipe());
@@ -379,23 +259,34 @@ int RkISP1CameraData::loadIPA(unsigned int hwRevision)
 	return 0;
 }
 
+void RkISP1CameraData::addRequest(RkISP1Request *request)
+{
+	/* Associate the request and the frame number. */
+	request->frame = frame_;
+	requestMap_[frame_] = request;
+	frame_++;
+}
+
 void RkISP1CameraData::paramFilled(unsigned int frame)
 {
 	PipelineHandlerRkISP1 *pipe = RkISP1CameraData::pipe();
-	RkISP1FrameInfo *info = frameInfo_.find(frame);
-	if (!info)
-		return;
+	RkISP1Request *request = requestMap_.at(frame);
+	ASSERT(request);
 
-	info->paramBuffer->_d()->metadata().planes()[0].bytesused =
+	request->paramBuffer->_d()->metadata().planes()[0].bytesused =
 		sizeof(struct rkisp1_params_cfg);
-	pipe->param_->queueBuffer(info->paramBuffer);
-	pipe->stat_->queueBuffer(info->statBuffer);
-
-	if (info->mainPathBuffer)
-		mainPath_->queueBuffer(info->mainPathBuffer);
-
-	if (selfPath_ && info->selfPathBuffer)
-		selfPath_->queueBuffer(info->selfPathBuffer);
+	pipe->param_->queueBuffer(request->paramBuffer);
+	pipe->stat_->queueBuffer(request->statBuffer);
+
+	FrameBuffer *mainPathBuffer =
+		request->_o<Request>()->findBuffer(&mainPathStream_);
+	if (mainPathBuffer)
+		mainPath_->queueBuffer(mainPathBuffer);
+
+	FrameBuffer *selfPathBuffer =
+		request->_o<Request>()->findBuffer(&selfPathStream_);
+	if (selfPath_ && selfPathBuffer)
+		selfPath_->queueBuffer(selfPathBuffer);
 }
 
 void RkISP1CameraData::setSensorControls([[maybe_unused]] unsigned int frame,
@@ -406,14 +297,13 @@ void RkISP1CameraData::setSensorControls([[maybe_unused]] unsigned int frame,
 
 void RkISP1CameraData::metadataReady(unsigned int frame, const ControlList &metadata)
 {
-	RkISP1FrameInfo *info = frameInfo_.find(frame);
-	if (!info)
-		return;
+	RkISP1Request *request = requestMap_.at(frame);
+	ASSERT(request);
 
-	info->request->metadata().merge(metadata);
-	info->metadataProcessed = true;
+	request->_o<Request>()->metadata().merge(metadata);
+	request->metadataProcessed = true;
 
-	pipe()->tryCompleteRequest(info);
+	pipe()->tryCompleteRequest(request);
 }
 
 /* -----------------------------------------------------------------------------
@@ -1013,34 +903,77 @@ void PipelineHandlerRkISP1::stopDevice(Camera *camera)
 	}
 
 	ASSERT(data->queuedRequests_.empty());
-	data->frameInfo_.clear();
+	data->requestMap_.clear();
 
 	freeBuffers(camera);
 
 	activeCamera_ = nullptr;
 }
 
+std::unique_ptr<Request> PipelineHandlerRkISP1::createRequestDevice(Camera *camera,
+								    uint64_t cookie)
+{
+	auto request = std::make_unique<RkISP1Request>(camera);
+	return Request::create(std::move(request), cookie);
+}
+
 int PipelineHandlerRkISP1::queueRequestDevice(Camera *camera, Request *request)
 {
 	RkISP1CameraData *data = cameraData(camera);
+	RkISP1Request *rkisp1Request = cameraRequest(request);
 
-	RkISP1FrameInfo *info = data->frameInfo_.create(data, request, isRaw_);
-	if (!info)
-		return -ENOENT;
+	data->addRequest(rkisp1Request);
+	data->ipa_->queueRequest(rkisp1Request->frame, request->controls());
 
-	data->ipa_->queueRequest(data->frame_, request->controls());
+	/*
+	 * If we're operating in RAW mode (only one RAW stream is captured)
+	 * then we simply queue buffers to the video devices as we don't
+	 * need to run the IPA.
+	 */
 	if (isRaw_) {
-		if (info->mainPathBuffer)
-			data->mainPath_->queueBuffer(info->mainPathBuffer);
+		FrameBuffer *mainPathBuffer =
+			request->findBuffer(&data->mainPathStream_);
+		if (mainPathBuffer)
+			data->mainPath_->queueBuffer(mainPathBuffer);
 
-		if (data->selfPath_ && info->selfPathBuffer)
-			data->selfPath_->queueBuffer(info->selfPathBuffer);
-	} else {
-		data->ipa_->fillParamsBuffer(data->frame_,
-					     info->paramBuffer->cookie());
+		FrameBuffer *selfPathBuffer =
+			request->findBuffer(&data->selfPathStream_);
+		if (data->selfPath_ && selfPathBuffer)
+			data->selfPath_->queueBuffer(selfPathBuffer);
+
+		return 0;
+	}
+
+	/*
+	 * If we run the IPA we need to associate a parameters and a statistics
+	 * buffer with the Request and associate the request with the current
+	 * frame number.
+	 *
+	 * Associate the stat and frame buffers to a Request (if available)
+	 * and then run the IPA.
+	 */
+	if (availableParamBuffers_.empty()) {
+		LOG(RkISP1, Error) << "Parameters buffer underrun";
+		return -ENOENT;
+	}
+
+	if (availableStatBuffers_.empty()) {
+		LOG(RkISP1, Error) << "Statistic buffer underrun";
+		return -ENOENT;
 	}
 
-	data->frame_++;
+	rkisp1Request->paramBuffer = availableParamBuffers_.front();
+	rkisp1Request->paramDequeued = false;
+	rkisp1Request->paramBuffer->_d()->setRequest(request);
+	availableParamBuffers_.pop();
+
+	rkisp1Request->statBuffer = availableStatBuffers_.front();
+	rkisp1Request->metadataProcessed = false;
+	rkisp1Request->statBuffer->_d()->setRequest(request);
+	availableStatBuffers_.pop();
+
+	data->ipa_->fillParamsBuffer(rkisp1Request->frame,
+				     rkisp1Request->paramBuffer->cookie());
 
 	return 0;
 }
@@ -1232,37 +1165,28 @@ bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator)
  * Buffer Handling
  */
 
-void PipelineHandlerRkISP1::tryCompleteRequest(RkISP1FrameInfo *info)
+void PipelineHandlerRkISP1::tryCompleteRequest(RkISP1Request *request)
 {
 	RkISP1CameraData *data = cameraData(activeCamera_);
-	Request *request = info->request;
 
-	if (request->hasPendingBuffers())
+	if (request->hasPendingBuffers(isRaw_))
 		return;
 
-	if (!info->metadataProcessed)
-		return;
+	/* Return the stat and param buffers to the pipeline. */
+	availableParamBuffers_.push(request->paramBuffer);
+	availableStatBuffers_.push(request->statBuffer);
+	data->requestMap_.erase(request->frame);
 
-	if (!isRaw_ && !info->paramDequeued)
-		return;
-
-	data->frameInfo_.destroy(info->frame);
-
-	completeRequest(request);
+	completeRequest(request->_o<Request>());
 }
 
 void PipelineHandlerRkISP1::bufferReady(FrameBuffer *buffer)
 {
 	ASSERT(activeCamera_);
 	RkISP1CameraData *data = cameraData(activeCamera_);
-
-	RkISP1FrameInfo *info = data->frameInfo_.find(buffer);
-	if (!info)
-		return;
+	RkISP1Request *request = cameraRequest(buffer->request());
 
 	const FrameMetadata &metadata = buffer->metadata();
-	Request *request = buffer->request();
-
 	if (metadata.status != FrameMetadata::FrameCancelled) {
 		/*
 		 * Record the sensor's timestamp in the request metadata.
@@ -1270,55 +1194,48 @@ void PipelineHandlerRkISP1::bufferReady(FrameBuffer *buffer)
 		 * \todo The sensor timestamp should be better estimated by connecting
 		 * to the V4L2Device::frameStart signal.
 		 */
-		request->metadata().set(controls::SensorTimestamp,
-					metadata.timestamp);
+		request->_o<Request>()->metadata().set(controls::SensorTimestamp,
+						       metadata.timestamp);
 
 		if (isRaw_) {
 			const ControlList &ctrls =
 				data->delayedCtrls_->get(metadata.sequence);
-			data->ipa_->processStatsBuffer(info->frame, 0, ctrls);
+			data->ipa_->processStatsBuffer(request->frame, 0, ctrls);
 		}
 	} else {
 		if (isRaw_)
-			info->metadataProcessed = true;
+			request->metadataProcessed = true;
 	}
 
-	completeBuffer(request, buffer);
-	tryCompleteRequest(info);
+	completeBuffer(request->_o<Request>(), buffer);
+	tryCompleteRequest(request);
 }
 
 void PipelineHandlerRkISP1::paramReady(FrameBuffer *buffer)
 {
 	ASSERT(activeCamera_);
-	RkISP1CameraData *data = cameraData(activeCamera_);
+	RkISP1Request *request = cameraRequest(buffer->request());
 
-	RkISP1FrameInfo *info = data->frameInfo_.find(buffer);
-	if (!info)
-		return;
-
-	info->paramDequeued = true;
-	tryCompleteRequest(info);
+	request->paramDequeued = true;
+	tryCompleteRequest(request);
 }
 
 void PipelineHandlerRkISP1::statReady(FrameBuffer *buffer)
 {
 	ASSERT(activeCamera_);
 	RkISP1CameraData *data = cameraData(activeCamera_);
-
-	RkISP1FrameInfo *info = data->frameInfo_.find(buffer);
-	if (!info)
-		return;
+	RkISP1Request *request = cameraRequest(buffer->request());
 
 	if (buffer->metadata().status == FrameMetadata::FrameCancelled) {
-		info->metadataProcessed = true;
-		tryCompleteRequest(info);
+		request->metadataProcessed = true;
+		tryCompleteRequest(request);
 		return;
 	}
 
 	if (data->frame_ <= buffer->metadata().sequence)
 		data->frame_ = buffer->metadata().sequence + 1;
 
-	data->ipa_->processStatsBuffer(info->frame, info->statBuffer->cookie(),
+	data->ipa_->processStatsBuffer(request->frame, request->statBuffer->cookie(),
 				       data->delayedCtrls_->get(buffer->metadata().sequence));
 }
 
