diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp
index ae693664..50b017a3 100644
--- a/src/android/camera_device.cpp
+++ b/src/android/camera_device.cpp
@@ -287,15 +287,17 @@ CameraDevice::Camera3RequestDescriptor::Camera3RequestDescriptor(

 	/*
 	 * Create the libcamera::Request unique_ptr<> to tie its lifetime
-	 * to the descriptor's one. Set the descriptor's address as the
-	 * request's cookie to retrieve it at completion time.
+	 * to the descriptor's one.
 	 */
-	request_ = std::make_unique<CaptureRequest>(camera,
-						    reinterpret_cast<uint64_t>(this));
+	request_ = std::make_unique<CaptureRequest>(camera);
 }

+CameraDevice::Camera3RequestDescriptor::Camera3RequestDescriptor() = default;
 CameraDevice::Camera3RequestDescriptor::~Camera3RequestDescriptor() = default;

+CameraDevice::Camera3RequestDescriptor&
+CameraDevice::Camera3RequestDescriptor::operator=(Camera3RequestDescriptor&&) = default;
+
 /*
  * \class CameraDevice
  *
@@ -1811,8 +1813,8 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques
 	 * The descriptor and the associated memory reserved here are freed
 	 * at request complete time.
 	 */
-	Camera3RequestDescriptor *descriptor =
-		new Camera3RequestDescriptor(camera_.get(), camera3Request);
+	Camera3RequestDescriptor descriptor(camera_.get(), camera3Request);
+
 	/*
 	 * \todo The Android request model is incremental, settings passed in
 	 * previous requests are to be effective until overridden explicitly in
@@ -1822,12 +1824,12 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques
 	if (camera3Request->settings)
 		lastSettings_ = camera3Request->settings;
 	else
-		descriptor->settings_ = lastSettings_;
+		descriptor.settings_ = lastSettings_;

-	LOG(HAL, Debug) << "Queueing request " << descriptor->request_->cookie()
-			<< " with " << descriptor->buffers_.size() << " streams";
-	for (unsigned int i = 0; i < descriptor->buffers_.size(); ++i) {
-		const camera3_stream_buffer_t &camera3Buffer = descriptor->buffers_[i];
+	LOG(HAL, Debug) << "Queueing request " << descriptor.request_->cookie()
+			<< " with " << descriptor.buffers_.size() << " streams";
+	for (unsigned int i = 0; i < descriptor.buffers_.size(); ++i) {
+		const camera3_stream_buffer_t &camera3Buffer = descriptor.buffers_[i];
 		camera3_stream *camera3Stream = camera3Buffer.stream;
 		CameraStream *cameraStream = static_cast<CameraStream *>(camera3Stream->priv);

@@ -1860,7 +1862,7 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques
 			 * lifetime management only.
 			 */
 			buffer = createFrameBuffer(*camera3Buffer.buffer);
-			descriptor->frameBuffers_.emplace_back(buffer);
+			descriptor.frameBuffers_.emplace_back(buffer);
 			LOG(HAL, Debug) << ss.str() << " (direct)";
 			break;

@@ -1879,11 +1881,10 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques

 		if (!buffer) {
 			LOG(HAL, Error) << "Failed to create buffer";
-			delete descriptor;
 			return -ENOMEM;
 		}

-		descriptor->request_->addBuffer(cameraStream->stream(), buffer,
+		descriptor.request_->addBuffer(cameraStream->stream(), buffer,
 						camera3Buffer.acquire_fence);
 	}

@@ -1891,11 +1892,12 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques
 	 * Translate controls from Android to libcamera and queue the request
 	 * to the CameraWorker thread.
 	 */
-	int ret = processControls(descriptor);
+	int ret = processControls(&descriptor);
 	if (ret)
 		return ret;

-	worker_.queueRequest(descriptor->request_.get());
+	worker_.queueRequest(descriptor.request_.get());
+	descriptors_[descriptor.request_->cookie()] = std::move(descriptor);

 	return 0;
 }
@@ -1905,8 +1907,13 @@ void CameraDevice::requestComplete(Request *request)
 	const Request::BufferMap &buffers = request->buffers();
 	camera3_buffer_status status = CAMERA3_BUFFER_STATUS_OK;
 	std::unique_ptr<CameraMetadata> resultMetadata;
-	Camera3RequestDescriptor *descriptor =
-		reinterpret_cast<Camera3RequestDescriptor *>(request->cookie());
+	auto node = descriptors_.extract(request->cookie());
+	if (node.empty()) {
+		LOG(HAL, Error) << "Unknown request: " << request->cookie();
+		status = CAMERA3_BUFFER_STATUS_ERROR;
+		return;
+	}
+	Camera3RequestDescriptor& descriptor = node.mapped();

 	if (request->status() != Request::RequestComplete) {
 		LOG(HAL, Error) << "Request not successfully completed: "
@@ -1915,7 +1922,7 @@ void CameraDevice::requestComplete(Request *request)
 	}

 	LOG(HAL, Debug) << "Request " << request->cookie() << " completed with "
-			<< descriptor->buffers_.size() << " streams";
+			<< descriptor.buffers_.size() << " streams";

 	/*
 	 * \todo The timestamp used for the metadata is currently always taken
@@ -1924,10 +1931,10 @@ void CameraDevice::requestComplete(Request *request)
 	 * pipeline handlers) timestamp in the Request itself.
 	 */
 	uint64_t timestamp = buffers.begin()->second->metadata().timestamp;
-	resultMetadata = getResultMetadata(*descriptor, timestamp);
+	resultMetadata = getResultMetadata(descriptor, timestamp);

 	/* Handle any JPEG compression. */
-	for (camera3_stream_buffer_t &buffer : descriptor->buffers_) {
+	for (camera3_stream_buffer_t &buffer : descriptor.buffers_) {
 		CameraStream *cameraStream =
 			static_cast<CameraStream *>(buffer.stream->priv);

@@ -1942,7 +1949,7 @@ void CameraDevice::requestComplete(Request *request)

 		int ret = cameraStream->process(*src,
 						*buffer.buffer,
-						descriptor->settings_,
+						descriptor.settings_,
 						resultMetadata.get());
 		if (ret) {
 			status = CAMERA3_BUFFER_STATUS_ERROR;
@@ -1959,17 +1966,17 @@ void CameraDevice::requestComplete(Request *request)

 	/* Prepare to call back the Android camera stack. */
 	camera3_capture_result_t captureResult = {};
-	captureResult.frame_number = descriptor->frameNumber_;
-	captureResult.num_output_buffers = descriptor->buffers_.size();
-	for (camera3_stream_buffer_t &buffer : descriptor->buffers_) {
+	captureResult.frame_number = descriptor.frameNumber_;
+	captureResult.num_output_buffers = descriptor.buffers_.size();
+	for (camera3_stream_buffer_t &buffer : descriptor.buffers_) {
 		buffer.acquire_fence = -1;
 		buffer.release_fence = -1;
 		buffer.status = status;
 	}
-	captureResult.output_buffers = descriptor->buffers_.data();
+	captureResult.output_buffers = descriptor.buffers_.data();

 	if (status == CAMERA3_BUFFER_STATUS_OK) {
-		notifyShutter(descriptor->frameNumber_, timestamp);
+		notifyShutter(descriptor.frameNumber_, timestamp);

 		captureResult.partial_result = 1;
 		captureResult.result = resultMetadata->get();
@@ -1982,13 +1989,11 @@ void CameraDevice::requestComplete(Request *request)
 		 * is here signalled. Make sure the error path plays well with
 		 * the camera stack state machine.
 		 */
-		notifyError(descriptor->frameNumber_,
-			    descriptor->buffers_[0].stream);
+		notifyError(descriptor.frameNumber_,
+			    descriptor.buffers_[0].stream);
 	}

 	callbacks_->process_capture_result(callbacks_, &captureResult);
-
-	delete descriptor;
 }

 std::string CameraDevice::logPrefix() const
diff --git a/src/android/camera_device.h b/src/android/camera_device.h
index 11bdfec8..54d7c319 100644
--- a/src/android/camera_device.h
+++ b/src/android/camera_device.h
@@ -69,9 +69,11 @@ private:
 	CameraDevice(unsigned int id, std::shared_ptr<libcamera::Camera> camera);

 	struct Camera3RequestDescriptor {
+		Camera3RequestDescriptor();
+		~Camera3RequestDescriptor();
 		Camera3RequestDescriptor(libcamera::Camera *camera,
 					 const camera3_capture_request_t *camera3Request);
-		~Camera3RequestDescriptor();
+		Camera3RequestDescriptor& operator=(Camera3RequestDescriptor&&);

 		uint32_t frameNumber_;
 		std::vector<camera3_stream_buffer_t> buffers_;
@@ -122,6 +124,8 @@ private:
 	std::map<int, libcamera::PixelFormat> formatsMap_;
 	std::vector<CameraStream> streams_;

+	std::map<uint64_t, Camera3RequestDescriptor> descriptors_;
+
 	std::string maker_;
 	std::string model_;

diff --git a/src/android/camera_worker.cpp b/src/android/camera_worker.cpp
index df436460..300ddde0 100644
--- a/src/android/camera_worker.cpp
+++ b/src/android/camera_worker.cpp
@@ -26,10 +26,10 @@ LOG_DECLARE_CATEGORY(HAL)
  * by the CameraWorker which queues it to the libcamera::Camera after handling
  * fences.
  */
-CaptureRequest::CaptureRequest(libcamera::Camera *camera, uint64_t cookie)
+CaptureRequest::CaptureRequest(libcamera::Camera *camera)
 	: camera_(camera)
 {
-	request_ = camera_->createRequest(cookie);
+	request_ = camera_->createRequest(reinterpret_cast<uint64_t>(this));
 }

 void CaptureRequest::addBuffer(Stream *stream, FrameBuffer *buffer, int fence)
diff --git a/src/android/camera_worker.h b/src/android/camera_worker.h
index d7060576..64b1658b 100644
--- a/src/android/camera_worker.h
+++ b/src/android/camera_worker.h
@@ -22,7 +22,7 @@ class CameraDevice;
 class CaptureRequest
 {
 public:
-	CaptureRequest(libcamera::Camera *camera, uint64_t cookie);
+	CaptureRequest(libcamera::Camera *camera);

 	const std::vector<int> &fences() const { return acquireFences_; }
 	libcamera::ControlList &controls() { return request_->controls(); }
