[{"id":13041,"web_url":"https://patchwork.libcamera.org/comment/13041/","msgid":"<20201007094331.2dl4xwdeqzf4isix@uno.localdomain>","date":"2020-10-07T09:43:31","subject":"Re: [libcamera-devel] [PATCH v6] libcamera, android, cam, gstreamer,\n\tqcam, v4l2: Reuse Request","submitter":{"id":3,"url":"https://patchwork.libcamera.org/api/people/3/","name":"Jacopo Mondi","email":"jacopo@jmondi.org"},"content":"Hi Paul,\n\nOn Wed, Oct 07, 2020 at 04:34:18PM +0900, Paul Elder wrote:\n> Allow reuse of the Request object by implementing reuse(). This means\n> the applications now have the responsibility of freeing the Request\n> objects, so make all libcamera users (cam, qcam, v4l2-compat, gstreamer,\n> android) do so.\n\nMaybe not a question to ask at v6, I've gone through the previous\niterations and I see the proposed API has been discussed already, so\nfeel free to ignore, but: I'm not sure I like the flags passed to\nreuse() :)\n\nTo be more precise, I don't see much much value in ReuseBuffers.\n\nHow usual is it that the same [Stream|buffer] pair is repeated ? I\nunderstand we might want to capture from the same set of Streams, but\napplication would more likely cycle buffers, don't they ?\n\nI understand the ReuseBuffers flag might be used to model a\n'repeating' request mechanism, but I don't think our API is currently\nready to support such a feature. All our requests are one-shot, they\nget queued, notified when completed, and need to be re-queued. There's\nnothing like a mechanism that let's you create one request, queue it\nonce, and have it running until you don't stop you (without the need\nfor a requeue. For reference [1]). Implementing something like this\nwill need some more thinking, in example how to make it possible for a\nrepeating request to cycle on a set of buffers automatically.\n\nI don't think we should try to emulate a repeating mechanism by\nallowing application to re-use the same stream-buffer pair. It has a\nlimited use (in my understanding) and will confuse application as they\nneed anyway to re-queue it.\n\nDon't get me wrong, I think moving ownership of Request to application\nis correct and more clear, but I don't see much use for the flag [*]\n\nI would prefer if we keep thinking of our request as one shot, so they\ncould either be reset everytime, and application will have to\naddBuffers() again as they will anyway have to cycle buffers.\n\nThanks\n  j\n\n[*] This makes me unconfortable for another reason. I understand we\nmight want flags for, say, Controls handling and other possible tuning\n(have you though about other possible flags ?). I don't think the\noutput buffer handling lives in the same category as low level control\nof the Request, it's a primary Request feature and should not be expressed\nwith flags as in example \"Use template XYZ for the Request's controls\"\n\n[1] https://medium.com/androiddevelopers/understanding-android-camera-capture-sessions-and-requests-4e54d9150295\n\n>\n> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n>\n> ---\n> Changes in v6:\n> - fix doxygen\n> - rename enum ReuseBuffer to ReuseFlag, and change to flags\n> - move request->reuse() in v4l2-compat from qbuf time to completion\n>   time\n>   - streamOff when the stream is already off needs to also\n>     request->reuse(), so do that too\n> - update documentation\n>\n> Changes in v5:\n> - rename reset() to reuse()\n> - make reuse()'s reuseBuffers parameter into enum instead of bool\n> - fix qcam qt assertion error on the first queueRequest, where\n>   freeQueue_ is empty\n>\n> Changes in v4:\n> - no more implicit calls of anything that we added in this patch\n> - make reset() take a reuseBuffers boolean parameters\n>   - use transient request - delete request\n>   - reuse request, reset buffers - reset()\n>   - reuse request, reuse buffesr - reset(true)\n> - update apps and tests and documentation accordingly\n>\n> Changes in v3:\n> - reset() is called in Camera::queueRequest()\n> - apps that use transient request (android, gstreamer) delete the\n>   request at the end of the completion handler\n> - apps that want to reuse the buffers (cam) use reAddBuffers()\n> - apps that need to change the buffers (qcam, v4l2) use clearBuffers()\n> - update the documentation accordingly\n>\n> Changes in v2:\n> - clear controls_ and metadata_ and validator_ in Request::reset()\n> - use unique_ptr on application side, prior to queueRequest, and use\n>   regular pointer for completion handler\n> - make qcam's reuse request nicer\n> - update Camera::queueRequest() and Camera::createRequest() documentation\n> - add documentation for Request::reset()\n> - make v4l2-compat reuse request\n> - make gstreamer and android use the new createRequest API, though they\n>   do not actually reuse the requests\n> ---\n>  include/libcamera/camera.h        |  2 +-\n>  include/libcamera/request.h       |  7 +++++\n>  src/android/camera_device.cpp     | 14 +++++++---\n>  src/cam/capture.cpp               | 29 +++++---------------\n>  src/cam/capture.h                 |  3 +++\n>  src/gstreamer/gstlibcamerasrc.cpp | 14 ++++++----\n>  src/libcamera/camera.cpp          | 14 ++++------\n>  src/libcamera/request.cpp         | 38 ++++++++++++++++++++++++++\n>  src/qcam/main_window.cpp          | 42 ++++++++++++++++-------------\n>  src/qcam/main_window.h            | 26 +++++-------------\n>  src/v4l2/v4l2_camera.cpp          | 44 ++++++++++++++++++++++---------\n>  src/v4l2/v4l2_camera.h            |  4 ++-\n>  test/camera/buffer_import.cpp     | 13 ++++-----\n>  test/camera/capture.cpp           | 13 ++++-----\n>  test/camera/statemachine.cpp      |  9 +++----\n>  15 files changed, 163 insertions(+), 109 deletions(-)\n>\n> diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> index a2ee4e7e..79ff8d6b 100644\n> --- a/include/libcamera/camera.h\n> +++ b/include/libcamera/camera.h\n> @@ -96,7 +96,7 @@ public:\n>  \tstd::unique_ptr<CameraConfiguration> generateConfiguration(const StreamRoles &roles = {});\n>  \tint configure(CameraConfiguration *config);\n>\n> -\tRequest *createRequest(uint64_t cookie = 0);\n> +\tstd::unique_ptr<Request> createRequest(uint64_t cookie = 0);\n>  \tint queueRequest(Request *request);\n>\n>  \tint start();\n> diff --git a/include/libcamera/request.h b/include/libcamera/request.h\n> index 5976ac50..655b1324 100644\n> --- a/include/libcamera/request.h\n> +++ b/include/libcamera/request.h\n> @@ -31,6 +31,11 @@ public:\n>  \t\tRequestCancelled,\n>  \t};\n>\n> +\tenum ReuseFlag {\n> +\t\tDefault = 0,\n> +\t\tReuseBuffers = (1 << 0),\n> +\t};\n> +\n>  \tusing BufferMap = std::map<const Stream *, FrameBuffer *>;\n>\n>  \tRequest(Camera *camera, uint64_t cookie = 0);\n> @@ -38,6 +43,8 @@ public:\n>  \tRequest &operator=(const Request &) = delete;\n>  \t~Request();\n>\n> +\tvoid reuse(ReuseFlag flags = Default);\n> +\n>  \tControlList &controls() { return *controls_; }\n>  \tControlList &metadata() { return *metadata_; }\n>  \tconst BufferMap &buffers() const { return bufferMap_; }\n> diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp\n> index 751699cd..052c9292 100644\n> --- a/src/android/camera_device.cpp\n> +++ b/src/android/camera_device.cpp\n> @@ -1395,8 +1395,12 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n>  \t\tnew Camera3RequestDescriptor(camera3Request->frame_number,\n>  \t\t\t\t\t     camera3Request->num_output_buffers);\n>\n> -\tRequest *request =\n> +\tstd::unique_ptr<Request> request =\n>  \t\tcamera_->createRequest(reinterpret_cast<uint64_t>(descriptor));\n> +\tif (!request) {\n> +\t\tLOG(HAL, Error) << \"Failed to create request\";\n> +\t\treturn -ENOMEM;\n> +\t}\n>\n>  \tfor (unsigned int i = 0; i < descriptor->numBuffers; ++i) {\n>  \t\tCameraStream *cameraStream =\n> @@ -1422,7 +1426,6 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n>  \t\tFrameBuffer *buffer = createFrameBuffer(*camera3Buffers[i].buffer);\n>  \t\tif (!buffer) {\n>  \t\t\tLOG(HAL, Error) << \"Failed to create buffer\";\n> -\t\t\tdelete request;\n>  \t\t\tdelete descriptor;\n>  \t\t\treturn -ENOMEM;\n>  \t\t}\n> @@ -1434,14 +1437,16 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n>  \t\trequest->addBuffer(stream, buffer);\n>  \t}\n>\n> -\tint ret = camera_->queueRequest(request);\n> +\tint ret = camera_->queueRequest(request.get());\n>  \tif (ret) {\n>  \t\tLOG(HAL, Error) << \"Failed to queue request\";\n> -\t\tdelete request;\n>  \t\tdelete descriptor;\n>  \t\treturn ret;\n>  \t}\n>\n> +\t/* The request will be deleted in the completion handler. */\n> +\trequest.release();\n> +\n>  \treturn 0;\n>  }\n>\n> @@ -1593,6 +1598,7 @@ void CameraDevice::requestComplete(Request *request)\n>  \tcallbacks_->process_capture_result(callbacks_, &captureResult);\n>\n>  \tdelete descriptor;\n> +\tdelete request;\n>  }\n>\n>  std::string CameraDevice::logPrefix() const\n> diff --git a/src/cam/capture.cpp b/src/cam/capture.cpp\n> index 5510c009..8c8faa4b 100644\n> --- a/src/cam/capture.cpp\n> +++ b/src/cam/capture.cpp\n> @@ -65,6 +65,8 @@ int Capture::run(const OptionsParser::Options &options)\n>  \t\twriter_ = nullptr;\n>  \t}\n>\n> +\trequests_.clear();\n> +\n>  \tdelete allocator;\n>\n>  \treturn ret;\n> @@ -92,9 +94,8 @@ int Capture::capture(FrameBufferAllocator *allocator)\n>  \t * example pushing a button. For now run all streams all the time.\n>  \t */\n>\n> -\tstd::vector<Request *> requests;\n>  \tfor (unsigned int i = 0; i < nbuffers; i++) {\n> -\t\tRequest *request = camera_->createRequest();\n> +\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n>  \t\tif (!request) {\n>  \t\t\tstd::cerr << \"Can't create request\" << std::endl;\n>  \t\t\treturn -ENOMEM;\n> @@ -117,7 +118,7 @@ int Capture::capture(FrameBufferAllocator *allocator)\n>  \t\t\t\twriter_->mapBuffer(buffer.get());\n>  \t\t}\n>\n> -\t\trequests.push_back(request);\n> +\t\trequests_.push_back(std::move(request));\n>  \t}\n>\n>  \tret = camera_->start();\n> @@ -126,8 +127,8 @@ int Capture::capture(FrameBufferAllocator *allocator)\n>  \t\treturn ret;\n>  \t}\n>\n> -\tfor (Request *request : requests) {\n> -\t\tret = camera_->queueRequest(request);\n> +\tfor (std::unique_ptr<Request> &request : requests_) {\n> +\t\tret = camera_->queueRequest(request.get());\n>  \t\tif (ret < 0) {\n>  \t\t\tstd::cerr << \"Can't queue request\" << std::endl;\n>  \t\t\tcamera_->stop();\n> @@ -202,22 +203,6 @@ void Capture::requestComplete(Request *request)\n>  \t\treturn;\n>  \t}\n>\n> -\t/*\n> -\t * Create a new request and populate it with one buffer for each\n> -\t * stream.\n> -\t */\n> -\trequest = camera_->createRequest();\n> -\tif (!request) {\n> -\t\tstd::cerr << \"Can't create request\" << std::endl;\n> -\t\treturn;\n> -\t}\n> -\n> -\tfor (auto it = buffers.begin(); it != buffers.end(); ++it) {\n> -\t\tconst Stream *stream = it->first;\n> -\t\tFrameBuffer *buffer = it->second;\n> -\n> -\t\trequest->addBuffer(stream, buffer);\n> -\t}\n> -\n> +\trequest->reuse(Request::ReuseBuffers);\n>  \tcamera_->queueRequest(request);\n>  }\n> diff --git a/src/cam/capture.h b/src/cam/capture.h\n> index 0aebdac9..45e5e8a9 100644\n> --- a/src/cam/capture.h\n> +++ b/src/cam/capture.h\n> @@ -9,6 +9,7 @@\n>\n>  #include <memory>\n>  #include <stdint.h>\n> +#include <vector>\n>\n>  #include <libcamera/buffer.h>\n>  #include <libcamera/camera.h>\n> @@ -43,6 +44,8 @@ private:\n>  \tEventLoop *loop_;\n>  \tunsigned int captureCount_;\n>  \tunsigned int captureLimit_;\n> +\n> +\tstd::vector<std::unique_ptr<libcamera::Request>> requests_;\n>  };\n>\n>  #endif /* __CAM_CAPTURE_H__ */\n> diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp\n> index 1bfc2e2f..5001083a 100644\n> --- a/src/gstreamer/gstlibcamerasrc.cpp\n> +++ b/src/gstreamer/gstlibcamerasrc.cpp\n> @@ -74,6 +74,8 @@ RequestWrap::~RequestWrap()\n>  \t\tif (item.second)\n>  \t\t\tgst_buffer_unref(item.second);\n>  \t}\n> +\n> +\tdelete request_;\n>  }\n>\n>  void RequestWrap::attachBuffer(GstBuffer *buffer)\n> @@ -266,8 +268,8 @@ gst_libcamera_src_task_run(gpointer user_data)\n>  \tGstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);\n>  \tGstLibcameraSrcState *state = self->state;\n>\n> -\tRequest *request = state->cam_->createRequest();\n> -\tauto wrap = std::make_unique<RequestWrap>(request);\n> +\tstd::unique_ptr<Request> request = state->cam_->createRequest();\n> +\tauto wrap = std::make_unique<RequestWrap>(request.get());\n>  \tfor (GstPad *srcpad : state->srcpads_) {\n>  \t\tGstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);\n>  \t\tGstBuffer *buffer;\n> @@ -280,8 +282,7 @@ gst_libcamera_src_task_run(gpointer user_data)\n>  \t\t\t * RequestWrap does not take ownership, and we won't be\n>  \t\t\t * queueing this one due to lack of buffers.\n>  \t\t\t */\n> -\t\t\tdelete request;\n> -\t\t\trequest = nullptr;\n> +\t\t\trequest.reset();\n>  \t\t\tbreak;\n>  \t\t}\n>\n> @@ -291,8 +292,11 @@ gst_libcamera_src_task_run(gpointer user_data)\n>  \tif (request) {\n>  \t\tGLibLocker lock(GST_OBJECT(self));\n>  \t\tGST_TRACE_OBJECT(self, \"Requesting buffers\");\n> -\t\tstate->cam_->queueRequest(request);\n> +\t\tstate->cam_->queueRequest(request.get());\n>  \t\tstate->requests_.push(std::move(wrap));\n> +\n> +\t\t/* The request will be deleted in the completion handler. */\n> +\t\trequest.release();\n>  \t}\n>\n>  \tGstFlowReturn ret = GST_FLOW_OK;\n> diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> index fb76077f..9590ab72 100644\n> --- a/src/libcamera/camera.cpp\n> +++ b/src/libcamera/camera.cpp\n> @@ -847,21 +847,22 @@ int Camera::configure(CameraConfiguration *config)\n>   * handler, and is completely opaque to libcamera.\n>   *\n>   * The ownership of the returned request is passed to the caller, which is\n> - * responsible for either queueing the request or deleting it.\n> + * responsible for deleting it. The request may be deleted in the completion\n> + * handler, or reused after resetting its state with Request::reuse().\n>   *\n>   * \\context This function is \\threadsafe. It may only be called when the camera\n>   * is in the Configured or Running state as defined in \\ref camera_operation.\n>   *\n>   * \\return A pointer to the newly created request, or nullptr on error\n>   */\n> -Request *Camera::createRequest(uint64_t cookie)\n> +std::unique_ptr<Request> Camera::createRequest(uint64_t cookie)\n>  {\n>  \tint ret = p_->isAccessAllowed(Private::CameraConfigured,\n>  \t\t\t\t      Private::CameraRunning);\n>  \tif (ret < 0)\n>  \t\treturn nullptr;\n>\n> -\treturn new Request(this, cookie);\n> +\treturn std::make_unique<Request>(this, cookie);\n>  }\n>\n>  /**\n> @@ -877,9 +878,6 @@ Request *Camera::createRequest(uint64_t cookie)\n>   * Once the request has been queued, the camera will notify its completion\n>   * through the \\ref requestCompleted signal.\n>   *\n> - * Ownership of the request is transferred to the camera. It will be deleted\n> - * automatically after it completes.\n> - *\n>   * \\context This function is \\threadsafe. It may only be called when the camera\n>   * is in the Running state as defined in \\ref camera_operation.\n>   *\n> @@ -988,13 +986,11 @@ int Camera::stop()\n>   * \\param[in] request The request that has completed\n>   *\n>   * This function is called by the pipeline handler to notify the camera that\n> - * the request has completed. It emits the requestCompleted signal and deletes\n> - * the request.\n> + * the request has completed. It emits the requestCompleted signal.\n>   */\n>  void Camera::requestComplete(Request *request)\n>  {\n>  \trequestCompleted.emit(request);\n> -\tdelete request;\n>  }\n>\n>  } /* namespace libcamera */\n> diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp\n> index 60b30692..ae8b1660 100644\n> --- a/src/libcamera/request.cpp\n> +++ b/src/libcamera/request.cpp\n> @@ -37,6 +37,15 @@ LOG_DEFINE_CATEGORY(Request)\n>   * The request has been cancelled due to capture stop\n>   */\n>\n> +/**\n> + * \\enum Request::ReuseFlag\n> + * Flags to control the behavior of Request::reuse()\n> + * \\var Request::Default\n> + * Don't reuse buffers\n> + * \\var Request::ReuseBuffers\n> + * Reuse the buffers that were previously added by addBuffer()\n> + */\n> +\n>  /**\n>   * \\typedef Request::BufferMap\n>   * \\brief A map of Stream to FrameBuffer pointers\n> @@ -85,6 +94,35 @@ Request::~Request()\n>  \tdelete validator_;\n>  }\n>\n> +/**\n> + * \\brief Reset the request for reuse\n> + * \\param[in] flags Indicate whether or not to reuse the buffers\n> + *\n> + * Reset the status and controls associated with the request, to allow it to\n> + * be reused and requeued without destruction. This function shall be called\n> + * prior to queueing the request to the camera, in lieu of constructing a new\n> + * request. The application can reuse the buffers that were previously added\n> + * to the request via addBuffer() by setting \\a flags to ReuseBuffers.\n> + */\n> +void Request::reuse(ReuseFlag flags)\n> +{\n> +\tpending_.clear();\n> +\tif (flags & ReuseBuffers) {\n> +\t\tfor (auto pair : bufferMap_) {\n> +\t\t\tpair.second->request_ = this;\n> +\t\t\tpending_.insert(pair.second);\n> +\t\t}\n> +\t} else {\n> +\t\tbufferMap_.clear();\n> +\t}\n> +\n> +\tstatus_ = RequestPending;\n> +\tcancelled_ = false;\n> +\n> +\tcontrols_->clear();\n> +\tmetadata_->clear();\n> +}\n> +\n>  /**\n>   * \\fn Request::controls()\n>   * \\brief Retrieve the request's ControlList\n> diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp\n> index ecb9dd66..0cbdab9a 100644\n> --- a/src/qcam/main_window.cpp\n> +++ b/src/qcam/main_window.cpp\n> @@ -367,7 +367,6 @@ void MainWindow::toggleCapture(bool start)\n>  int MainWindow::startCapture()\n>  {\n>  \tStreamRoles roles = StreamKeyValueParser::roles(options_[OptStream]);\n> -\tstd::vector<Request *> requests;\n>  \tint ret;\n>\n>  \t/* Verify roles are supported. */\n> @@ -486,7 +485,7 @@ int MainWindow::startCapture()\n>  \twhile (!freeBuffers_[vfStream_].isEmpty()) {\n>  \t\tFrameBuffer *buffer = freeBuffers_[vfStream_].dequeue();\n>\n> -\t\tRequest *request = camera_->createRequest();\n> +\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n>  \t\tif (!request) {\n>  \t\t\tqWarning() << \"Can't create request\";\n>  \t\t\tret = -ENOMEM;\n> @@ -499,7 +498,7 @@ int MainWindow::startCapture()\n>  \t\t\tgoto error;\n>  \t\t}\n>\n> -\t\trequests.push_back(request);\n> +\t\trequests_.push_back(std::move(request));\n>  \t}\n>\n>  \t/* Start the title timer and the camera. */\n> @@ -518,8 +517,8 @@ int MainWindow::startCapture()\n>  \tcamera_->requestCompleted.connect(this, &MainWindow::requestComplete);\n>\n>  \t/* Queue all requests. */\n> -\tfor (Request *request : requests) {\n> -\t\tret = camera_->queueRequest(request);\n> +\tfor (std::unique_ptr<Request> &request : requests_) {\n> +\t\tret = camera_->queueRequest(request.get());\n>  \t\tif (ret < 0) {\n>  \t\t\tqWarning() << \"Can't queue request\";\n>  \t\t\tgoto error_disconnect;\n> @@ -535,8 +534,7 @@ error_disconnect:\n>  \tcamera_->stop();\n>\n>  error:\n> -\tfor (Request *request : requests)\n> -\t\tdelete request;\n> +\trequests_.clear();\n>\n>  \tfor (auto &iter : mappedBuffers_) {\n>  \t\tconst MappedBuffer &buffer = iter.second;\n> @@ -580,6 +578,8 @@ void MainWindow::stopCapture()\n>  \t}\n>  \tmappedBuffers_.clear();\n>\n> +\trequests_.clear();\n> +\n>  \tdelete allocator_;\n>\n>  \tisCapturing_ = false;\n> @@ -701,7 +701,7 @@ void MainWindow::requestComplete(Request *request)\n>  \t */\n>  \t{\n>  \t\tQMutexLocker locker(&mutex_);\n> -\t\tdoneQueue_.enqueue({ request->buffers(), request->metadata() });\n> +\t\tdoneQueue_.enqueue(request);\n>  \t}\n>\n>  \tQCoreApplication::postEvent(this, new CaptureEvent);\n> @@ -714,8 +714,7 @@ void MainWindow::processCapture()\n>  \t * if stopCapture() has been called while a CaptureEvent was posted but\n>  \t * not processed yet. Return immediately in that case.\n>  \t */\n> -\tCaptureRequest request;\n> -\n> +\tRequest *request;\n>  \t{\n>  \t\tQMutexLocker locker(&mutex_);\n>  \t\tif (doneQueue_.isEmpty())\n> @@ -725,11 +724,15 @@ void MainWindow::processCapture()\n>  \t}\n>\n>  \t/* Process buffers. */\n> -\tif (request.buffers_.count(vfStream_))\n> -\t\tprocessViewfinder(request.buffers_[vfStream_]);\n> +\tif (request->buffers().count(vfStream_))\n> +\t\tprocessViewfinder(request->buffers().at(vfStream_));\n>\n> -\tif (request.buffers_.count(rawStream_))\n> -\t\tprocessRaw(request.buffers_[rawStream_], request.metadata_);\n> +\tif (request->buffers().count(rawStream_))\n> +\t\tprocessRaw(request->buffers().at(rawStream_), request->metadata());\n> +\n> +\trequest->reuse();\n> +\tQMutexLocker locker(&mutex_);\n> +\tfreeQueue_.enqueue(request);\n>  }\n>\n>  void MainWindow::processViewfinder(FrameBuffer *buffer)\n> @@ -754,10 +757,13 @@ void MainWindow::processViewfinder(FrameBuffer *buffer)\n>\n>  void MainWindow::queueRequest(FrameBuffer *buffer)\n>  {\n> -\tRequest *request = camera_->createRequest();\n> -\tif (!request) {\n> -\t\tqWarning() << \"Can't create request\";\n> -\t\treturn;\n> +\tRequest *request;\n> +\t{\n> +\t\tQMutexLocker locker(&mutex_);\n> +\t\tif (freeQueue_.isEmpty())\n> +\t\t\treturn;\n> +\n> +\t\trequest = freeQueue_.dequeue();\n>  \t}\n>\n>  \trequest->addBuffer(vfStream_, buffer);\n> diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h\n> index 5c61a4df..64bcfebc 100644\n> --- a/src/qcam/main_window.h\n> +++ b/src/qcam/main_window.h\n> @@ -8,6 +8,7 @@\n>  #define __QCAM_MAIN_WINDOW_H__\n>\n>  #include <memory>\n> +#include <vector>\n>\n>  #include <QElapsedTimer>\n>  #include <QIcon>\n> @@ -22,6 +23,7 @@\n>  #include <libcamera/camera_manager.h>\n>  #include <libcamera/controls.h>\n>  #include <libcamera/framebuffer_allocator.h>\n> +#include <libcamera/request.h>\n>  #include <libcamera/stream.h>\n>\n>  #include \"../cam/stream_options.h\"\n> @@ -41,23 +43,6 @@ enum {\n>  \tOptStream = 's',\n>  };\n>\n> -class CaptureRequest\n> -{\n> -public:\n> -\tCaptureRequest()\n> -\t{\n> -\t}\n> -\n> -\tCaptureRequest(const Request::BufferMap &buffers,\n> -\t\t       const ControlList &metadata)\n> -\t\t: buffers_(buffers), metadata_(metadata)\n> -\t{\n> -\t}\n> -\n> -\tRequest::BufferMap buffers_;\n> -\tControlList metadata_;\n> -};\n> -\n>  class MainWindow : public QMainWindow\n>  {\n>  \tQ_OBJECT\n> @@ -128,13 +113,16 @@ private:\n>  \tStream *vfStream_;\n>  \tStream *rawStream_;\n>  \tstd::map<const Stream *, QQueue<FrameBuffer *>> freeBuffers_;\n> -\tQQueue<CaptureRequest> doneQueue_;\n> -\tQMutex mutex_; /* Protects freeBuffers_ and doneQueue_ */\n> +\tQQueue<Request *> doneQueue_;\n> +\tQQueue<Request *> freeQueue_;\n> +\tQMutex mutex_; /* Protects freeBuffers_, doneQueue_, and freeQueue_ */\n>\n>  \tuint64_t lastBufferTime_;\n>  \tQElapsedTimer frameRateInterval_;\n>  \tuint32_t previousFrames_;\n>  \tuint32_t framesCaptured_;\n> +\n> +\tstd::vector<std::unique_ptr<Request>> requests_;\n>  };\n>\n>  #endif /* __QCAM_MAIN_WINDOW__ */\n> diff --git a/src/v4l2/v4l2_camera.cpp b/src/v4l2/v4l2_camera.cpp\n> index 3565f369..35d3beda 100644\n> --- a/src/v4l2/v4l2_camera.cpp\n> +++ b/src/v4l2/v4l2_camera.cpp\n> @@ -49,6 +49,8 @@ int V4L2Camera::open(StreamConfiguration *streamConfig)\n>\n>  void V4L2Camera::close()\n>  {\n> +\trequestPool_.clear();\n> +\n>  \tdelete bufferAllocator_;\n>  \tbufferAllocator_ = nullptr;\n>\n> @@ -96,6 +98,7 @@ void V4L2Camera::requestComplete(Request *request)\n>  \tif (ret != sizeof(data))\n>  \t\tLOG(V4L2Compat, Error) << \"Failed to signal eventfd POLLIN\";\n>\n> +\trequest->reuse();\n>  \t{\n>  \t\tMutexLocker locker(bufferMutex_);\n>  \t\tbufferAvailableCount_++;\n> @@ -154,16 +157,30 @@ int V4L2Camera::validateConfiguration(const PixelFormat &pixelFormat,\n>  \treturn 0;\n>  }\n>\n> -int V4L2Camera::allocBuffers([[maybe_unused]] unsigned int count)\n> +int V4L2Camera::allocBuffers(unsigned int count)\n>  {\n>  \tStream *stream = config_->at(0).stream();\n>\n> -\treturn bufferAllocator_->allocate(stream);\n> +\tint ret = bufferAllocator_->allocate(stream);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tfor (unsigned int i = 0; i < count; i++) {\n> +\t\tstd::unique_ptr<Request> request = camera_->createRequest(i);\n> +\t\tif (!request) {\n> +\t\t\trequestPool_.clear();\n> +\t\t\treturn -ENOMEM;\n> +\t\t}\n> +\t\trequestPool_.push_back(std::move(request));\n> +\t}\n> +\n> +\treturn ret;\n>  }\n>\n>  void V4L2Camera::freeBuffers()\n>  {\n>  \tpendingRequests_.clear();\n> +\trequestPool_.clear();\n>\n>  \tStream *stream = config_->at(0).stream();\n>  \tbufferAllocator_->free(stream);\n> @@ -192,9 +209,9 @@ int V4L2Camera::streamOn()\n>\n>  \tisRunning_ = true;\n>\n> -\tfor (std::unique_ptr<Request> &req : pendingRequests_) {\n> +\tfor (Request *req : pendingRequests_) {\n>  \t\t/* \\todo What should we do if this returns -EINVAL? */\n> -\t\tret = camera_->queueRequest(req.release());\n> +\t\tret = camera_->queueRequest(req);\n>  \t\tif (ret < 0)\n>  \t\t\treturn ret == -EACCES ? -EBUSY : ret;\n>  \t}\n> @@ -206,8 +223,12 @@ int V4L2Camera::streamOn()\n>\n>  int V4L2Camera::streamOff()\n>  {\n> -\tif (!isRunning_)\n> +\tif (!isRunning_) {\n> +\t\tfor (std::unique_ptr<Request> &req : requestPool_)\n> +\t\t\treq->reuse();\n> +\n>  \t\treturn 0;\n> +\t}\n>\n>  \tpendingRequests_.clear();\n>\n> @@ -226,12 +247,11 @@ int V4L2Camera::streamOff()\n>\n>  int V4L2Camera::qbuf(unsigned int index)\n>  {\n> -\tstd::unique_ptr<Request> request =\n> -\t\tstd::unique_ptr<Request>(camera_->createRequest(index));\n> -\tif (!request) {\n> -\t\tLOG(V4L2Compat, Error) << \"Can't create request\";\n> -\t\treturn -ENOMEM;\n> +\tif (index >= requestPool_.size()) {\n> +\t\tLOG(V4L2Compat, Error) << \"Invalid index\";\n> +\t\treturn -EINVAL;\n>  \t}\n> +\tRequest *request = requestPool_[index].get();\n>\n>  \tStream *stream = config_->at(0).stream();\n>  \tFrameBuffer *buffer = bufferAllocator_->buffers(stream)[index].get();\n> @@ -242,11 +262,11 @@ int V4L2Camera::qbuf(unsigned int index)\n>  \t}\n>\n>  \tif (!isRunning_) {\n> -\t\tpendingRequests_.push_back(std::move(request));\n> +\t\tpendingRequests_.push_back(request);\n>  \t\treturn 0;\n>  \t}\n>\n> -\tret = camera_->queueRequest(request.release());\n> +\tret = camera_->queueRequest(request);\n>  \tif (ret < 0) {\n>  \t\tLOG(V4L2Compat, Error) << \"Can't queue request\";\n>  \t\treturn ret == -EACCES ? -EBUSY : ret;\n> diff --git a/src/v4l2/v4l2_camera.h b/src/v4l2/v4l2_camera.h\n> index 1fc5ebef..a6c35a2e 100644\n> --- a/src/v4l2/v4l2_camera.h\n> +++ b/src/v4l2/v4l2_camera.h\n> @@ -76,7 +76,9 @@ private:\n>  \tstd::mutex bufferLock_;\n>  \tFrameBufferAllocator *bufferAllocator_;\n>\n> -\tstd::deque<std::unique_ptr<Request>> pendingRequests_;\n> +\tstd::vector<std::unique_ptr<Request>> requestPool_;\n> +\n> +\tstd::deque<Request *> pendingRequests_;\n>  \tstd::deque<std::unique_ptr<Buffer>> completedBuffers_;\n>\n>  \tint efd_;\n> diff --git a/test/camera/buffer_import.cpp b/test/camera/buffer_import.cpp\n> index 64e96264..72ce7b79 100644\n> --- a/test/camera/buffer_import.cpp\n> +++ b/test/camera/buffer_import.cpp\n> @@ -58,7 +58,7 @@ protected:\n>  \t\tconst Stream *stream = buffers.begin()->first;\n>  \t\tFrameBuffer *buffer = buffers.begin()->second;\n>\n> -\t\trequest = camera_->createRequest();\n> +\t\trequest->reuse();\n>  \t\trequest->addBuffer(stream, buffer);\n>  \t\tcamera_->queueRequest(request);\n>  \t}\n> @@ -98,9 +98,8 @@ protected:\n>  \t\tif (ret != TestPass)\n>  \t\t\treturn ret;\n>\n> -\t\tstd::vector<Request *> requests;\n>  \t\tfor (const std::unique_ptr<FrameBuffer> &buffer : source.buffers()) {\n> -\t\t\tRequest *request = camera_->createRequest();\n> +\t\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n>  \t\t\tif (!request) {\n>  \t\t\t\tstd::cout << \"Failed to create request\" << std::endl;\n>  \t\t\t\treturn TestFail;\n> @@ -111,7 +110,7 @@ protected:\n>  \t\t\t\treturn TestFail;\n>  \t\t\t}\n>\n> -\t\t\trequests.push_back(request);\n> +\t\t\trequests_.push_back(std::move(request));\n>  \t\t}\n>\n>  \t\tcompleteRequestsCount_ = 0;\n> @@ -125,8 +124,8 @@ protected:\n>  \t\t\treturn TestFail;\n>  \t\t}\n>\n> -\t\tfor (Request *request : requests) {\n> -\t\t\tif (camera_->queueRequest(request)) {\n> +\t\tfor (std::unique_ptr<Request> &request : requests_) {\n> +\t\t\tif (camera_->queueRequest(request.get())) {\n>  \t\t\t\tstd::cout << \"Failed to queue request\" << std::endl;\n>  \t\t\t\treturn TestFail;\n>  \t\t\t}\n> @@ -160,6 +159,8 @@ protected:\n>  \t}\n>\n>  private:\n> +\tstd::vector<std::unique_ptr<Request>> requests_;\n> +\n>  \tunsigned int completeBuffersCount_;\n>  \tunsigned int completeRequestsCount_;\n>  \tstd::unique_ptr<CameraConfiguration> config_;\n> diff --git a/test/camera/capture.cpp b/test/camera/capture.cpp\n> index 51bbd258..c0770801 100644\n> --- a/test/camera/capture.cpp\n> +++ b/test/camera/capture.cpp\n> @@ -52,7 +52,7 @@ protected:\n>  \t\tconst Stream *stream = buffers.begin()->first;\n>  \t\tFrameBuffer *buffer = buffers.begin()->second;\n>\n> -\t\trequest = camera_->createRequest();\n> +\t\trequest->reuse();\n>  \t\trequest->addBuffer(stream, buffer);\n>  \t\tcamera_->queueRequest(request);\n>  \t}\n> @@ -98,9 +98,8 @@ protected:\n>  \t\tif (ret < 0)\n>  \t\t\treturn TestFail;\n>\n> -\t\tstd::vector<Request *> requests;\n>  \t\tfor (const std::unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) {\n> -\t\t\tRequest *request = camera_->createRequest();\n> +\t\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n>  \t\t\tif (!request) {\n>  \t\t\t\tcout << \"Failed to create request\" << endl;\n>  \t\t\t\treturn TestFail;\n> @@ -111,7 +110,7 @@ protected:\n>  \t\t\t\treturn TestFail;\n>  \t\t\t}\n>\n> -\t\t\trequests.push_back(request);\n> +\t\t\trequests_.push_back(std::move(request));\n>  \t\t}\n>\n>  \t\tcompleteRequestsCount_ = 0;\n> @@ -125,8 +124,8 @@ protected:\n>  \t\t\treturn TestFail;\n>  \t\t}\n>\n> -\t\tfor (Request *request : requests) {\n> -\t\t\tif (camera_->queueRequest(request)) {\n> +\t\tfor (std::unique_ptr<Request> &request : requests_) {\n> +\t\t\tif (camera_->queueRequest(request.get())) {\n>  \t\t\t\tcout << \"Failed to queue request\" << endl;\n>  \t\t\t\treturn TestFail;\n>  \t\t\t}\n> @@ -161,6 +160,8 @@ protected:\n>  \t\treturn TestPass;\n>  \t}\n>\n> +\tstd::vector<std::unique_ptr<Request>> requests_;\n> +\n>  \tstd::unique_ptr<CameraConfiguration> config_;\n>  \tFrameBufferAllocator *allocator_;\n>  };\n> diff --git a/test/camera/statemachine.cpp b/test/camera/statemachine.cpp\n> index 28faeb91..e63ab298 100644\n> --- a/test/camera/statemachine.cpp\n> +++ b/test/camera/statemachine.cpp\n> @@ -101,13 +101,10 @@ protected:\n>  \t\t\treturn TestFail;\n>\n>  \t\t/* Test operations which should pass. */\n> -\t\tRequest *request2 = camera_->createRequest();\n> +\t\tstd::unique_ptr<Request> request2 = camera_->createRequest();\n>  \t\tif (!request2)\n>  \t\t\treturn TestFail;\n>\n> -\t\t/* Never handed to hardware so need to manually delete it. */\n> -\t\tdelete request2;\n> -\n>  \t\t/* Test valid state transitions, end in Running state. */\n>  \t\tif (camera_->release())\n>  \t\t\treturn TestFail;\n> @@ -146,7 +143,7 @@ protected:\n>  \t\t\treturn TestFail;\n>\n>  \t\t/* Test operations which should pass. */\n> -\t\tRequest *request = camera_->createRequest();\n> +\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n>  \t\tif (!request)\n>  \t\t\treturn TestFail;\n>\n> @@ -154,7 +151,7 @@ protected:\n>  \t\tif (request->addBuffer(stream, allocator_->buffers(stream)[0].get()))\n>  \t\t\treturn TestFail;\n>\n> -\t\tif (camera_->queueRequest(request))\n> +\t\tif (camera_->queueRequest(request.get()))\n>  \t\t\treturn TestFail;\n>\n>  \t\t/* Test valid state transitions, end in Available state. */\n> --\n> 2.27.0\n>\n> _______________________________________________\n> libcamera-devel mailing list\n> libcamera-devel@lists.libcamera.org\n> https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 2BFEBBEEE0\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed,  7 Oct 2020 09:39:34 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id C126863CAF;\n\tWed,  7 Oct 2020 11:39:33 +0200 (CEST)","from relay11.mail.gandi.net (relay11.mail.gandi.net\n\t[217.70.178.231])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id C93006035F\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed,  7 Oct 2020 11:39:31 +0200 (CEST)","from uno.localdomain (93-34-118-233.ip49.fastwebnet.it\n\t[93.34.118.233]) (Authenticated sender: jacopo@jmondi.org)\n\tby relay11.mail.gandi.net (Postfix) with ESMTPSA id 1F509100008;\n\tWed,  7 Oct 2020 09:39:30 +0000 (UTC)"],"Date":"Wed, 7 Oct 2020 11:43:31 +0200","From":"Jacopo Mondi <jacopo@jmondi.org>","To":"Paul Elder <paul.elder@ideasonboard.com>","Message-ID":"<20201007094331.2dl4xwdeqzf4isix@uno.localdomain>","References":"<20201007073418.512656-1-paul.elder@ideasonboard.com>","MIME-Version":"1.0","Content-Disposition":"inline","In-Reply-To":"<20201007073418.512656-1-paul.elder@ideasonboard.com>","Subject":"Re: [libcamera-devel] [PATCH v6] libcamera, android, cam, gstreamer,\n\tqcam, v4l2: Reuse Request","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Cc":"libcamera-devel@lists.libcamera.org","Content-Type":"text/plain; charset=\"us-ascii\"","Content-Transfer-Encoding":"7bit","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":13092,"web_url":"https://patchwork.libcamera.org/comment/13092/","msgid":"<20201007225906.fr2sjwevvoflatfj@oden.dyn.berto.se>","date":"2020-10-07T22:59:06","subject":"Re: [libcamera-devel] [PATCH v6] libcamera, android, cam, gstreamer,\n\tqcam, v4l2: Reuse Request","submitter":{"id":5,"url":"https://patchwork.libcamera.org/api/people/5/","name":"Niklas Söderlund","email":"niklas.soderlund@ragnatech.se"},"content":"Hi Paul,\n\nThanks for your work and thanks Laurent for explaining it to me on IRC \n:-P\n\nOn 2020-10-07 16:34:18 +0900, Paul Elder wrote:\n> Allow reuse of the Request object by implementing reuse(). This means\n> the applications now have the responsibility of freeing the Request\n> objects, so make all libcamera users (cam, qcam, v4l2-compat, gstreamer,\n> android) do so.\n> \n> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n\nReviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\n\n> \n> ---\n> Changes in v6:\n> - fix doxygen\n> - rename enum ReuseBuffer to ReuseFlag, and change to flags\n> - move request->reuse() in v4l2-compat from qbuf time to completion\n>   time\n>   - streamOff when the stream is already off needs to also\n>     request->reuse(), so do that too\n> - update documentation\n> \n> Changes in v5:\n> - rename reset() to reuse()\n> - make reuse()'s reuseBuffers parameter into enum instead of bool\n> - fix qcam qt assertion error on the first queueRequest, where\n>   freeQueue_ is empty\n> \n> Changes in v4:\n> - no more implicit calls of anything that we added in this patch\n> - make reset() take a reuseBuffers boolean parameters\n>   - use transient request - delete request\n>   - reuse request, reset buffers - reset()\n>   - reuse request, reuse buffesr - reset(true)\n> - update apps and tests and documentation accordingly\n> \n> Changes in v3:\n> - reset() is called in Camera::queueRequest()\n> - apps that use transient request (android, gstreamer) delete the\n>   request at the end of the completion handler\n> - apps that want to reuse the buffers (cam) use reAddBuffers()\n> - apps that need to change the buffers (qcam, v4l2) use clearBuffers()\n> - update the documentation accordingly\n> \n> Changes in v2:\n> - clear controls_ and metadata_ and validator_ in Request::reset()\n> - use unique_ptr on application side, prior to queueRequest, and use\n>   regular pointer for completion handler\n> - make qcam's reuse request nicer\n> - update Camera::queueRequest() and Camera::createRequest() documentation\n> - add documentation for Request::reset()\n> - make v4l2-compat reuse request\n> - make gstreamer and android use the new createRequest API, though they\n>   do not actually reuse the requests\n> ---\n>  include/libcamera/camera.h        |  2 +-\n>  include/libcamera/request.h       |  7 +++++\n>  src/android/camera_device.cpp     | 14 +++++++---\n>  src/cam/capture.cpp               | 29 +++++---------------\n>  src/cam/capture.h                 |  3 +++\n>  src/gstreamer/gstlibcamerasrc.cpp | 14 ++++++----\n>  src/libcamera/camera.cpp          | 14 ++++------\n>  src/libcamera/request.cpp         | 38 ++++++++++++++++++++++++++\n>  src/qcam/main_window.cpp          | 42 ++++++++++++++++-------------\n>  src/qcam/main_window.h            | 26 +++++-------------\n>  src/v4l2/v4l2_camera.cpp          | 44 ++++++++++++++++++++++---------\n>  src/v4l2/v4l2_camera.h            |  4 ++-\n>  test/camera/buffer_import.cpp     | 13 ++++-----\n>  test/camera/capture.cpp           | 13 ++++-----\n>  test/camera/statemachine.cpp      |  9 +++----\n>  15 files changed, 163 insertions(+), 109 deletions(-)\n> \n> diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> index a2ee4e7e..79ff8d6b 100644\n> --- a/include/libcamera/camera.h\n> +++ b/include/libcamera/camera.h\n> @@ -96,7 +96,7 @@ public:\n>  \tstd::unique_ptr<CameraConfiguration> generateConfiguration(const StreamRoles &roles = {});\n>  \tint configure(CameraConfiguration *config);\n>  \n> -\tRequest *createRequest(uint64_t cookie = 0);\n> +\tstd::unique_ptr<Request> createRequest(uint64_t cookie = 0);\n>  \tint queueRequest(Request *request);\n>  \n>  \tint start();\n> diff --git a/include/libcamera/request.h b/include/libcamera/request.h\n> index 5976ac50..655b1324 100644\n> --- a/include/libcamera/request.h\n> +++ b/include/libcamera/request.h\n> @@ -31,6 +31,11 @@ public:\n>  \t\tRequestCancelled,\n>  \t};\n>  \n> +\tenum ReuseFlag {\n> +\t\tDefault = 0,\n> +\t\tReuseBuffers = (1 << 0),\n> +\t};\n> +\n>  \tusing BufferMap = std::map<const Stream *, FrameBuffer *>;\n>  \n>  \tRequest(Camera *camera, uint64_t cookie = 0);\n> @@ -38,6 +43,8 @@ public:\n>  \tRequest &operator=(const Request &) = delete;\n>  \t~Request();\n>  \n> +\tvoid reuse(ReuseFlag flags = Default);\n> +\n>  \tControlList &controls() { return *controls_; }\n>  \tControlList &metadata() { return *metadata_; }\n>  \tconst BufferMap &buffers() const { return bufferMap_; }\n> diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp\n> index 751699cd..052c9292 100644\n> --- a/src/android/camera_device.cpp\n> +++ b/src/android/camera_device.cpp\n> @@ -1395,8 +1395,12 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n>  \t\tnew Camera3RequestDescriptor(camera3Request->frame_number,\n>  \t\t\t\t\t     camera3Request->num_output_buffers);\n>  \n> -\tRequest *request =\n> +\tstd::unique_ptr<Request> request =\n>  \t\tcamera_->createRequest(reinterpret_cast<uint64_t>(descriptor));\n> +\tif (!request) {\n> +\t\tLOG(HAL, Error) << \"Failed to create request\";\n> +\t\treturn -ENOMEM;\n> +\t}\n>  \n>  \tfor (unsigned int i = 0; i < descriptor->numBuffers; ++i) {\n>  \t\tCameraStream *cameraStream =\n> @@ -1422,7 +1426,6 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n>  \t\tFrameBuffer *buffer = createFrameBuffer(*camera3Buffers[i].buffer);\n>  \t\tif (!buffer) {\n>  \t\t\tLOG(HAL, Error) << \"Failed to create buffer\";\n> -\t\t\tdelete request;\n>  \t\t\tdelete descriptor;\n>  \t\t\treturn -ENOMEM;\n>  \t\t}\n> @@ -1434,14 +1437,16 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n>  \t\trequest->addBuffer(stream, buffer);\n>  \t}\n>  \n> -\tint ret = camera_->queueRequest(request);\n> +\tint ret = camera_->queueRequest(request.get());\n>  \tif (ret) {\n>  \t\tLOG(HAL, Error) << \"Failed to queue request\";\n> -\t\tdelete request;\n>  \t\tdelete descriptor;\n>  \t\treturn ret;\n>  \t}\n>  \n> +\t/* The request will be deleted in the completion handler. */\n> +\trequest.release();\n> +\n>  \treturn 0;\n>  }\n>  \n> @@ -1593,6 +1598,7 @@ void CameraDevice::requestComplete(Request *request)\n>  \tcallbacks_->process_capture_result(callbacks_, &captureResult);\n>  \n>  \tdelete descriptor;\n> +\tdelete request;\n>  }\n>  \n>  std::string CameraDevice::logPrefix() const\n> diff --git a/src/cam/capture.cpp b/src/cam/capture.cpp\n> index 5510c009..8c8faa4b 100644\n> --- a/src/cam/capture.cpp\n> +++ b/src/cam/capture.cpp\n> @@ -65,6 +65,8 @@ int Capture::run(const OptionsParser::Options &options)\n>  \t\twriter_ = nullptr;\n>  \t}\n>  \n> +\trequests_.clear();\n> +\n>  \tdelete allocator;\n>  \n>  \treturn ret;\n> @@ -92,9 +94,8 @@ int Capture::capture(FrameBufferAllocator *allocator)\n>  \t * example pushing a button. For now run all streams all the time.\n>  \t */\n>  \n> -\tstd::vector<Request *> requests;\n>  \tfor (unsigned int i = 0; i < nbuffers; i++) {\n> -\t\tRequest *request = camera_->createRequest();\n> +\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n>  \t\tif (!request) {\n>  \t\t\tstd::cerr << \"Can't create request\" << std::endl;\n>  \t\t\treturn -ENOMEM;\n> @@ -117,7 +118,7 @@ int Capture::capture(FrameBufferAllocator *allocator)\n>  \t\t\t\twriter_->mapBuffer(buffer.get());\n>  \t\t}\n>  \n> -\t\trequests.push_back(request);\n> +\t\trequests_.push_back(std::move(request));\n>  \t}\n>  \n>  \tret = camera_->start();\n> @@ -126,8 +127,8 @@ int Capture::capture(FrameBufferAllocator *allocator)\n>  \t\treturn ret;\n>  \t}\n>  \n> -\tfor (Request *request : requests) {\n> -\t\tret = camera_->queueRequest(request);\n> +\tfor (std::unique_ptr<Request> &request : requests_) {\n> +\t\tret = camera_->queueRequest(request.get());\n>  \t\tif (ret < 0) {\n>  \t\t\tstd::cerr << \"Can't queue request\" << std::endl;\n>  \t\t\tcamera_->stop();\n> @@ -202,22 +203,6 @@ void Capture::requestComplete(Request *request)\n>  \t\treturn;\n>  \t}\n>  \n> -\t/*\n> -\t * Create a new request and populate it with one buffer for each\n> -\t * stream.\n> -\t */\n> -\trequest = camera_->createRequest();\n> -\tif (!request) {\n> -\t\tstd::cerr << \"Can't create request\" << std::endl;\n> -\t\treturn;\n> -\t}\n> -\n> -\tfor (auto it = buffers.begin(); it != buffers.end(); ++it) {\n> -\t\tconst Stream *stream = it->first;\n> -\t\tFrameBuffer *buffer = it->second;\n> -\n> -\t\trequest->addBuffer(stream, buffer);\n> -\t}\n> -\n> +\trequest->reuse(Request::ReuseBuffers);\n>  \tcamera_->queueRequest(request);\n>  }\n> diff --git a/src/cam/capture.h b/src/cam/capture.h\n> index 0aebdac9..45e5e8a9 100644\n> --- a/src/cam/capture.h\n> +++ b/src/cam/capture.h\n> @@ -9,6 +9,7 @@\n>  \n>  #include <memory>\n>  #include <stdint.h>\n> +#include <vector>\n>  \n>  #include <libcamera/buffer.h>\n>  #include <libcamera/camera.h>\n> @@ -43,6 +44,8 @@ private:\n>  \tEventLoop *loop_;\n>  \tunsigned int captureCount_;\n>  \tunsigned int captureLimit_;\n> +\n> +\tstd::vector<std::unique_ptr<libcamera::Request>> requests_;\n>  };\n>  \n>  #endif /* __CAM_CAPTURE_H__ */\n> diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp\n> index 1bfc2e2f..5001083a 100644\n> --- a/src/gstreamer/gstlibcamerasrc.cpp\n> +++ b/src/gstreamer/gstlibcamerasrc.cpp\n> @@ -74,6 +74,8 @@ RequestWrap::~RequestWrap()\n>  \t\tif (item.second)\n>  \t\t\tgst_buffer_unref(item.second);\n>  \t}\n> +\n> +\tdelete request_;\n>  }\n>  \n>  void RequestWrap::attachBuffer(GstBuffer *buffer)\n> @@ -266,8 +268,8 @@ gst_libcamera_src_task_run(gpointer user_data)\n>  \tGstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);\n>  \tGstLibcameraSrcState *state = self->state;\n>  \n> -\tRequest *request = state->cam_->createRequest();\n> -\tauto wrap = std::make_unique<RequestWrap>(request);\n> +\tstd::unique_ptr<Request> request = state->cam_->createRequest();\n> +\tauto wrap = std::make_unique<RequestWrap>(request.get());\n>  \tfor (GstPad *srcpad : state->srcpads_) {\n>  \t\tGstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);\n>  \t\tGstBuffer *buffer;\n> @@ -280,8 +282,7 @@ gst_libcamera_src_task_run(gpointer user_data)\n>  \t\t\t * RequestWrap does not take ownership, and we won't be\n>  \t\t\t * queueing this one due to lack of buffers.\n>  \t\t\t */\n> -\t\t\tdelete request;\n> -\t\t\trequest = nullptr;\n> +\t\t\trequest.reset();\n>  \t\t\tbreak;\n>  \t\t}\n>  \n> @@ -291,8 +292,11 @@ gst_libcamera_src_task_run(gpointer user_data)\n>  \tif (request) {\n>  \t\tGLibLocker lock(GST_OBJECT(self));\n>  \t\tGST_TRACE_OBJECT(self, \"Requesting buffers\");\n> -\t\tstate->cam_->queueRequest(request);\n> +\t\tstate->cam_->queueRequest(request.get());\n>  \t\tstate->requests_.push(std::move(wrap));\n> +\n> +\t\t/* The request will be deleted in the completion handler. */\n> +\t\trequest.release();\n>  \t}\n>  \n>  \tGstFlowReturn ret = GST_FLOW_OK;\n> diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> index fb76077f..9590ab72 100644\n> --- a/src/libcamera/camera.cpp\n> +++ b/src/libcamera/camera.cpp\n> @@ -847,21 +847,22 @@ int Camera::configure(CameraConfiguration *config)\n>   * handler, and is completely opaque to libcamera.\n>   *\n>   * The ownership of the returned request is passed to the caller, which is\n> - * responsible for either queueing the request or deleting it.\n> + * responsible for deleting it. The request may be deleted in the completion\n> + * handler, or reused after resetting its state with Request::reuse().\n>   *\n>   * \\context This function is \\threadsafe. It may only be called when the camera\n>   * is in the Configured or Running state as defined in \\ref camera_operation.\n>   *\n>   * \\return A pointer to the newly created request, or nullptr on error\n>   */\n> -Request *Camera::createRequest(uint64_t cookie)\n> +std::unique_ptr<Request> Camera::createRequest(uint64_t cookie)\n>  {\n>  \tint ret = p_->isAccessAllowed(Private::CameraConfigured,\n>  \t\t\t\t      Private::CameraRunning);\n>  \tif (ret < 0)\n>  \t\treturn nullptr;\n>  \n> -\treturn new Request(this, cookie);\n> +\treturn std::make_unique<Request>(this, cookie);\n>  }\n>  \n>  /**\n> @@ -877,9 +878,6 @@ Request *Camera::createRequest(uint64_t cookie)\n>   * Once the request has been queued, the camera will notify its completion\n>   * through the \\ref requestCompleted signal.\n>   *\n> - * Ownership of the request is transferred to the camera. It will be deleted\n> - * automatically after it completes.\n> - *\n>   * \\context This function is \\threadsafe. It may only be called when the camera\n>   * is in the Running state as defined in \\ref camera_operation.\n>   *\n> @@ -988,13 +986,11 @@ int Camera::stop()\n>   * \\param[in] request The request that has completed\n>   *\n>   * This function is called by the pipeline handler to notify the camera that\n> - * the request has completed. It emits the requestCompleted signal and deletes\n> - * the request.\n> + * the request has completed. It emits the requestCompleted signal.\n>   */\n>  void Camera::requestComplete(Request *request)\n>  {\n>  \trequestCompleted.emit(request);\n> -\tdelete request;\n>  }\n>  \n>  } /* namespace libcamera */\n> diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp\n> index 60b30692..ae8b1660 100644\n> --- a/src/libcamera/request.cpp\n> +++ b/src/libcamera/request.cpp\n> @@ -37,6 +37,15 @@ LOG_DEFINE_CATEGORY(Request)\n>   * The request has been cancelled due to capture stop\n>   */\n>  \n> +/**\n> + * \\enum Request::ReuseFlag\n> + * Flags to control the behavior of Request::reuse()\n> + * \\var Request::Default\n> + * Don't reuse buffers\n> + * \\var Request::ReuseBuffers\n> + * Reuse the buffers that were previously added by addBuffer()\n> + */\n> +\n>  /**\n>   * \\typedef Request::BufferMap\n>   * \\brief A map of Stream to FrameBuffer pointers\n> @@ -85,6 +94,35 @@ Request::~Request()\n>  \tdelete validator_;\n>  }\n>  \n> +/**\n> + * \\brief Reset the request for reuse\n> + * \\param[in] flags Indicate whether or not to reuse the buffers\n> + *\n> + * Reset the status and controls associated with the request, to allow it to\n> + * be reused and requeued without destruction. This function shall be called\n> + * prior to queueing the request to the camera, in lieu of constructing a new\n> + * request. The application can reuse the buffers that were previously added\n> + * to the request via addBuffer() by setting \\a flags to ReuseBuffers.\n> + */\n> +void Request::reuse(ReuseFlag flags)\n> +{\n> +\tpending_.clear();\n> +\tif (flags & ReuseBuffers) {\n> +\t\tfor (auto pair : bufferMap_) {\n> +\t\t\tpair.second->request_ = this;\n> +\t\t\tpending_.insert(pair.second);\n> +\t\t}\n> +\t} else {\n> +\t\tbufferMap_.clear();\n> +\t}\n> +\n> +\tstatus_ = RequestPending;\n> +\tcancelled_ = false;\n> +\n> +\tcontrols_->clear();\n> +\tmetadata_->clear();\n> +}\n> +\n>  /**\n>   * \\fn Request::controls()\n>   * \\brief Retrieve the request's ControlList\n> diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp\n> index ecb9dd66..0cbdab9a 100644\n> --- a/src/qcam/main_window.cpp\n> +++ b/src/qcam/main_window.cpp\n> @@ -367,7 +367,6 @@ void MainWindow::toggleCapture(bool start)\n>  int MainWindow::startCapture()\n>  {\n>  \tStreamRoles roles = StreamKeyValueParser::roles(options_[OptStream]);\n> -\tstd::vector<Request *> requests;\n>  \tint ret;\n>  \n>  \t/* Verify roles are supported. */\n> @@ -486,7 +485,7 @@ int MainWindow::startCapture()\n>  \twhile (!freeBuffers_[vfStream_].isEmpty()) {\n>  \t\tFrameBuffer *buffer = freeBuffers_[vfStream_].dequeue();\n>  \n> -\t\tRequest *request = camera_->createRequest();\n> +\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n>  \t\tif (!request) {\n>  \t\t\tqWarning() << \"Can't create request\";\n>  \t\t\tret = -ENOMEM;\n> @@ -499,7 +498,7 @@ int MainWindow::startCapture()\n>  \t\t\tgoto error;\n>  \t\t}\n>  \n> -\t\trequests.push_back(request);\n> +\t\trequests_.push_back(std::move(request));\n>  \t}\n>  \n>  \t/* Start the title timer and the camera. */\n> @@ -518,8 +517,8 @@ int MainWindow::startCapture()\n>  \tcamera_->requestCompleted.connect(this, &MainWindow::requestComplete);\n>  \n>  \t/* Queue all requests. */\n> -\tfor (Request *request : requests) {\n> -\t\tret = camera_->queueRequest(request);\n> +\tfor (std::unique_ptr<Request> &request : requests_) {\n> +\t\tret = camera_->queueRequest(request.get());\n>  \t\tif (ret < 0) {\n>  \t\t\tqWarning() << \"Can't queue request\";\n>  \t\t\tgoto error_disconnect;\n> @@ -535,8 +534,7 @@ error_disconnect:\n>  \tcamera_->stop();\n>  \n>  error:\n> -\tfor (Request *request : requests)\n> -\t\tdelete request;\n> +\trequests_.clear();\n>  \n>  \tfor (auto &iter : mappedBuffers_) {\n>  \t\tconst MappedBuffer &buffer = iter.second;\n> @@ -580,6 +578,8 @@ void MainWindow::stopCapture()\n>  \t}\n>  \tmappedBuffers_.clear();\n>  \n> +\trequests_.clear();\n> +\n>  \tdelete allocator_;\n>  \n>  \tisCapturing_ = false;\n> @@ -701,7 +701,7 @@ void MainWindow::requestComplete(Request *request)\n>  \t */\n>  \t{\n>  \t\tQMutexLocker locker(&mutex_);\n> -\t\tdoneQueue_.enqueue({ request->buffers(), request->metadata() });\n> +\t\tdoneQueue_.enqueue(request);\n>  \t}\n>  \n>  \tQCoreApplication::postEvent(this, new CaptureEvent);\n> @@ -714,8 +714,7 @@ void MainWindow::processCapture()\n>  \t * if stopCapture() has been called while a CaptureEvent was posted but\n>  \t * not processed yet. Return immediately in that case.\n>  \t */\n> -\tCaptureRequest request;\n> -\n> +\tRequest *request;\n>  \t{\n>  \t\tQMutexLocker locker(&mutex_);\n>  \t\tif (doneQueue_.isEmpty())\n> @@ -725,11 +724,15 @@ void MainWindow::processCapture()\n>  \t}\n>  \n>  \t/* Process buffers. */\n> -\tif (request.buffers_.count(vfStream_))\n> -\t\tprocessViewfinder(request.buffers_[vfStream_]);\n> +\tif (request->buffers().count(vfStream_))\n> +\t\tprocessViewfinder(request->buffers().at(vfStream_));\n>  \n> -\tif (request.buffers_.count(rawStream_))\n> -\t\tprocessRaw(request.buffers_[rawStream_], request.metadata_);\n> +\tif (request->buffers().count(rawStream_))\n> +\t\tprocessRaw(request->buffers().at(rawStream_), request->metadata());\n> +\n> +\trequest->reuse();\n> +\tQMutexLocker locker(&mutex_);\n> +\tfreeQueue_.enqueue(request);\n>  }\n>  \n>  void MainWindow::processViewfinder(FrameBuffer *buffer)\n> @@ -754,10 +757,13 @@ void MainWindow::processViewfinder(FrameBuffer *buffer)\n>  \n>  void MainWindow::queueRequest(FrameBuffer *buffer)\n>  {\n> -\tRequest *request = camera_->createRequest();\n> -\tif (!request) {\n> -\t\tqWarning() << \"Can't create request\";\n> -\t\treturn;\n> +\tRequest *request;\n> +\t{\n> +\t\tQMutexLocker locker(&mutex_);\n> +\t\tif (freeQueue_.isEmpty())\n> +\t\t\treturn;\n> +\n> +\t\trequest = freeQueue_.dequeue();\n>  \t}\n>  \n>  \trequest->addBuffer(vfStream_, buffer);\n> diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h\n> index 5c61a4df..64bcfebc 100644\n> --- a/src/qcam/main_window.h\n> +++ b/src/qcam/main_window.h\n> @@ -8,6 +8,7 @@\n>  #define __QCAM_MAIN_WINDOW_H__\n>  \n>  #include <memory>\n> +#include <vector>\n>  \n>  #include <QElapsedTimer>\n>  #include <QIcon>\n> @@ -22,6 +23,7 @@\n>  #include <libcamera/camera_manager.h>\n>  #include <libcamera/controls.h>\n>  #include <libcamera/framebuffer_allocator.h>\n> +#include <libcamera/request.h>\n>  #include <libcamera/stream.h>\n>  \n>  #include \"../cam/stream_options.h\"\n> @@ -41,23 +43,6 @@ enum {\n>  \tOptStream = 's',\n>  };\n>  \n> -class CaptureRequest\n> -{\n> -public:\n> -\tCaptureRequest()\n> -\t{\n> -\t}\n> -\n> -\tCaptureRequest(const Request::BufferMap &buffers,\n> -\t\t       const ControlList &metadata)\n> -\t\t: buffers_(buffers), metadata_(metadata)\n> -\t{\n> -\t}\n> -\n> -\tRequest::BufferMap buffers_;\n> -\tControlList metadata_;\n> -};\n> -\n>  class MainWindow : public QMainWindow\n>  {\n>  \tQ_OBJECT\n> @@ -128,13 +113,16 @@ private:\n>  \tStream *vfStream_;\n>  \tStream *rawStream_;\n>  \tstd::map<const Stream *, QQueue<FrameBuffer *>> freeBuffers_;\n> -\tQQueue<CaptureRequest> doneQueue_;\n> -\tQMutex mutex_; /* Protects freeBuffers_ and doneQueue_ */\n> +\tQQueue<Request *> doneQueue_;\n> +\tQQueue<Request *> freeQueue_;\n> +\tQMutex mutex_; /* Protects freeBuffers_, doneQueue_, and freeQueue_ */\n>  \n>  \tuint64_t lastBufferTime_;\n>  \tQElapsedTimer frameRateInterval_;\n>  \tuint32_t previousFrames_;\n>  \tuint32_t framesCaptured_;\n> +\n> +\tstd::vector<std::unique_ptr<Request>> requests_;\n>  };\n>  \n>  #endif /* __QCAM_MAIN_WINDOW__ */\n> diff --git a/src/v4l2/v4l2_camera.cpp b/src/v4l2/v4l2_camera.cpp\n> index 3565f369..35d3beda 100644\n> --- a/src/v4l2/v4l2_camera.cpp\n> +++ b/src/v4l2/v4l2_camera.cpp\n> @@ -49,6 +49,8 @@ int V4L2Camera::open(StreamConfiguration *streamConfig)\n>  \n>  void V4L2Camera::close()\n>  {\n> +\trequestPool_.clear();\n> +\n>  \tdelete bufferAllocator_;\n>  \tbufferAllocator_ = nullptr;\n>  \n> @@ -96,6 +98,7 @@ void V4L2Camera::requestComplete(Request *request)\n>  \tif (ret != sizeof(data))\n>  \t\tLOG(V4L2Compat, Error) << \"Failed to signal eventfd POLLIN\";\n>  \n> +\trequest->reuse();\n>  \t{\n>  \t\tMutexLocker locker(bufferMutex_);\n>  \t\tbufferAvailableCount_++;\n> @@ -154,16 +157,30 @@ int V4L2Camera::validateConfiguration(const PixelFormat &pixelFormat,\n>  \treturn 0;\n>  }\n>  \n> -int V4L2Camera::allocBuffers([[maybe_unused]] unsigned int count)\n> +int V4L2Camera::allocBuffers(unsigned int count)\n>  {\n>  \tStream *stream = config_->at(0).stream();\n>  \n> -\treturn bufferAllocator_->allocate(stream);\n> +\tint ret = bufferAllocator_->allocate(stream);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tfor (unsigned int i = 0; i < count; i++) {\n> +\t\tstd::unique_ptr<Request> request = camera_->createRequest(i);\n> +\t\tif (!request) {\n> +\t\t\trequestPool_.clear();\n> +\t\t\treturn -ENOMEM;\n> +\t\t}\n> +\t\trequestPool_.push_back(std::move(request));\n> +\t}\n> +\n> +\treturn ret;\n>  }\n>  \n>  void V4L2Camera::freeBuffers()\n>  {\n>  \tpendingRequests_.clear();\n> +\trequestPool_.clear();\n>  \n>  \tStream *stream = config_->at(0).stream();\n>  \tbufferAllocator_->free(stream);\n> @@ -192,9 +209,9 @@ int V4L2Camera::streamOn()\n>  \n>  \tisRunning_ = true;\n>  \n> -\tfor (std::unique_ptr<Request> &req : pendingRequests_) {\n> +\tfor (Request *req : pendingRequests_) {\n>  \t\t/* \\todo What should we do if this returns -EINVAL? */\n> -\t\tret = camera_->queueRequest(req.release());\n> +\t\tret = camera_->queueRequest(req);\n>  \t\tif (ret < 0)\n>  \t\t\treturn ret == -EACCES ? -EBUSY : ret;\n>  \t}\n> @@ -206,8 +223,12 @@ int V4L2Camera::streamOn()\n>  \n>  int V4L2Camera::streamOff()\n>  {\n> -\tif (!isRunning_)\n> +\tif (!isRunning_) {\n> +\t\tfor (std::unique_ptr<Request> &req : requestPool_)\n> +\t\t\treq->reuse();\n> +\n>  \t\treturn 0;\n> +\t}\n>  \n>  \tpendingRequests_.clear();\n>  \n> @@ -226,12 +247,11 @@ int V4L2Camera::streamOff()\n>  \n>  int V4L2Camera::qbuf(unsigned int index)\n>  {\n> -\tstd::unique_ptr<Request> request =\n> -\t\tstd::unique_ptr<Request>(camera_->createRequest(index));\n> -\tif (!request) {\n> -\t\tLOG(V4L2Compat, Error) << \"Can't create request\";\n> -\t\treturn -ENOMEM;\n> +\tif (index >= requestPool_.size()) {\n> +\t\tLOG(V4L2Compat, Error) << \"Invalid index\";\n> +\t\treturn -EINVAL;\n>  \t}\n> +\tRequest *request = requestPool_[index].get();\n>  \n>  \tStream *stream = config_->at(0).stream();\n>  \tFrameBuffer *buffer = bufferAllocator_->buffers(stream)[index].get();\n> @@ -242,11 +262,11 @@ int V4L2Camera::qbuf(unsigned int index)\n>  \t}\n>  \n>  \tif (!isRunning_) {\n> -\t\tpendingRequests_.push_back(std::move(request));\n> +\t\tpendingRequests_.push_back(request);\n>  \t\treturn 0;\n>  \t}\n>  \n> -\tret = camera_->queueRequest(request.release());\n> +\tret = camera_->queueRequest(request);\n>  \tif (ret < 0) {\n>  \t\tLOG(V4L2Compat, Error) << \"Can't queue request\";\n>  \t\treturn ret == -EACCES ? -EBUSY : ret;\n> diff --git a/src/v4l2/v4l2_camera.h b/src/v4l2/v4l2_camera.h\n> index 1fc5ebef..a6c35a2e 100644\n> --- a/src/v4l2/v4l2_camera.h\n> +++ b/src/v4l2/v4l2_camera.h\n> @@ -76,7 +76,9 @@ private:\n>  \tstd::mutex bufferLock_;\n>  \tFrameBufferAllocator *bufferAllocator_;\n>  \n> -\tstd::deque<std::unique_ptr<Request>> pendingRequests_;\n> +\tstd::vector<std::unique_ptr<Request>> requestPool_;\n> +\n> +\tstd::deque<Request *> pendingRequests_;\n>  \tstd::deque<std::unique_ptr<Buffer>> completedBuffers_;\n>  \n>  \tint efd_;\n> diff --git a/test/camera/buffer_import.cpp b/test/camera/buffer_import.cpp\n> index 64e96264..72ce7b79 100644\n> --- a/test/camera/buffer_import.cpp\n> +++ b/test/camera/buffer_import.cpp\n> @@ -58,7 +58,7 @@ protected:\n>  \t\tconst Stream *stream = buffers.begin()->first;\n>  \t\tFrameBuffer *buffer = buffers.begin()->second;\n>  \n> -\t\trequest = camera_->createRequest();\n> +\t\trequest->reuse();\n>  \t\trequest->addBuffer(stream, buffer);\n>  \t\tcamera_->queueRequest(request);\n>  \t}\n> @@ -98,9 +98,8 @@ protected:\n>  \t\tif (ret != TestPass)\n>  \t\t\treturn ret;\n>  \n> -\t\tstd::vector<Request *> requests;\n>  \t\tfor (const std::unique_ptr<FrameBuffer> &buffer : source.buffers()) {\n> -\t\t\tRequest *request = camera_->createRequest();\n> +\t\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n>  \t\t\tif (!request) {\n>  \t\t\t\tstd::cout << \"Failed to create request\" << std::endl;\n>  \t\t\t\treturn TestFail;\n> @@ -111,7 +110,7 @@ protected:\n>  \t\t\t\treturn TestFail;\n>  \t\t\t}\n>  \n> -\t\t\trequests.push_back(request);\n> +\t\t\trequests_.push_back(std::move(request));\n>  \t\t}\n>  \n>  \t\tcompleteRequestsCount_ = 0;\n> @@ -125,8 +124,8 @@ protected:\n>  \t\t\treturn TestFail;\n>  \t\t}\n>  \n> -\t\tfor (Request *request : requests) {\n> -\t\t\tif (camera_->queueRequest(request)) {\n> +\t\tfor (std::unique_ptr<Request> &request : requests_) {\n> +\t\t\tif (camera_->queueRequest(request.get())) {\n>  \t\t\t\tstd::cout << \"Failed to queue request\" << std::endl;\n>  \t\t\t\treturn TestFail;\n>  \t\t\t}\n> @@ -160,6 +159,8 @@ protected:\n>  \t}\n>  \n>  private:\n> +\tstd::vector<std::unique_ptr<Request>> requests_;\n> +\n>  \tunsigned int completeBuffersCount_;\n>  \tunsigned int completeRequestsCount_;\n>  \tstd::unique_ptr<CameraConfiguration> config_;\n> diff --git a/test/camera/capture.cpp b/test/camera/capture.cpp\n> index 51bbd258..c0770801 100644\n> --- a/test/camera/capture.cpp\n> +++ b/test/camera/capture.cpp\n> @@ -52,7 +52,7 @@ protected:\n>  \t\tconst Stream *stream = buffers.begin()->first;\n>  \t\tFrameBuffer *buffer = buffers.begin()->second;\n>  \n> -\t\trequest = camera_->createRequest();\n> +\t\trequest->reuse();\n>  \t\trequest->addBuffer(stream, buffer);\n>  \t\tcamera_->queueRequest(request);\n>  \t}\n> @@ -98,9 +98,8 @@ protected:\n>  \t\tif (ret < 0)\n>  \t\t\treturn TestFail;\n>  \n> -\t\tstd::vector<Request *> requests;\n>  \t\tfor (const std::unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) {\n> -\t\t\tRequest *request = camera_->createRequest();\n> +\t\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n>  \t\t\tif (!request) {\n>  \t\t\t\tcout << \"Failed to create request\" << endl;\n>  \t\t\t\treturn TestFail;\n> @@ -111,7 +110,7 @@ protected:\n>  \t\t\t\treturn TestFail;\n>  \t\t\t}\n>  \n> -\t\t\trequests.push_back(request);\n> +\t\t\trequests_.push_back(std::move(request));\n>  \t\t}\n>  \n>  \t\tcompleteRequestsCount_ = 0;\n> @@ -125,8 +124,8 @@ protected:\n>  \t\t\treturn TestFail;\n>  \t\t}\n>  \n> -\t\tfor (Request *request : requests) {\n> -\t\t\tif (camera_->queueRequest(request)) {\n> +\t\tfor (std::unique_ptr<Request> &request : requests_) {\n> +\t\t\tif (camera_->queueRequest(request.get())) {\n>  \t\t\t\tcout << \"Failed to queue request\" << endl;\n>  \t\t\t\treturn TestFail;\n>  \t\t\t}\n> @@ -161,6 +160,8 @@ protected:\n>  \t\treturn TestPass;\n>  \t}\n>  \n> +\tstd::vector<std::unique_ptr<Request>> requests_;\n> +\n>  \tstd::unique_ptr<CameraConfiguration> config_;\n>  \tFrameBufferAllocator *allocator_;\n>  };\n> diff --git a/test/camera/statemachine.cpp b/test/camera/statemachine.cpp\n> index 28faeb91..e63ab298 100644\n> --- a/test/camera/statemachine.cpp\n> +++ b/test/camera/statemachine.cpp\n> @@ -101,13 +101,10 @@ protected:\n>  \t\t\treturn TestFail;\n>  \n>  \t\t/* Test operations which should pass. */\n> -\t\tRequest *request2 = camera_->createRequest();\n> +\t\tstd::unique_ptr<Request> request2 = camera_->createRequest();\n>  \t\tif (!request2)\n>  \t\t\treturn TestFail;\n>  \n> -\t\t/* Never handed to hardware so need to manually delete it. */\n> -\t\tdelete request2;\n> -\n>  \t\t/* Test valid state transitions, end in Running state. */\n>  \t\tif (camera_->release())\n>  \t\t\treturn TestFail;\n> @@ -146,7 +143,7 @@ protected:\n>  \t\t\treturn TestFail;\n>  \n>  \t\t/* Test operations which should pass. */\n> -\t\tRequest *request = camera_->createRequest();\n> +\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n>  \t\tif (!request)\n>  \t\t\treturn TestFail;\n>  \n> @@ -154,7 +151,7 @@ protected:\n>  \t\tif (request->addBuffer(stream, allocator_->buffers(stream)[0].get()))\n>  \t\t\treturn TestFail;\n>  \n> -\t\tif (camera_->queueRequest(request))\n> +\t\tif (camera_->queueRequest(request.get()))\n>  \t\t\treturn TestFail;\n>  \n>  \t\t/* Test valid state transitions, end in Available state. */\n> -- \n> 2.27.0\n> \n> _______________________________________________\n> libcamera-devel mailing list\n> libcamera-devel@lists.libcamera.org\n> https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id A9B65BEEDF\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed,  7 Oct 2020 22:59:11 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 0EF6060542;\n\tThu,  8 Oct 2020 00:59:11 +0200 (CEST)","from mail-lj1-x22a.google.com (mail-lj1-x22a.google.com\n\t[IPv6:2a00:1450:4864:20::22a])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 22E8E60396\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu,  8 Oct 2020 00:59:09 +0200 (CEST)","by mail-lj1-x22a.google.com with SMTP id y16so2525145ljk.1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 07 Oct 2020 15:59:09 -0700 (PDT)","from localhost (h-209-203.A463.priv.bahnhof.se. [155.4.209.203])\n\tby smtp.gmail.com with ESMTPSA id\n\tr132sm542892lff.167.2020.10.07.15.59.06\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tWed, 07 Oct 2020 15:59:07 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n\tunprotected) header.d=ragnatech-se.20150623.gappssmtp.com\n\theader.i=@ragnatech-se.20150623.gappssmtp.com\n\theader.b=\"V7mtK2Ti\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=ragnatech-se.20150623.gappssmtp.com; s=20150623;\n\th=date:from:to:cc:subject:message-id:references:mime-version\n\t:content-disposition:content-transfer-encoding:in-reply-to;\n\tbh=D6/cp1iv8EZecFvHNdd766jcST5lsxAnlZUcwzrBCtc=;\n\tb=V7mtK2Tiu8gzv7LMOge8U5oP7y+pT/YIrauW11KKIJbW3022OPZEICOw97cBOpaj/+\n\tsfwBh0/hqcncaEwjaRBMfqeHzRbpA8UgucwAMCgSedYlxVHQC9CN4rRhrbwl7/hNLigX\n\tGh9fWDjw5odr9t18RCUji27JqTqKnQCbCWbHlyhAVpWOQfCjw6Qxvy3/UmCsH5OI4rjV\n\t8zK+xomAhNYXIG82rpDA5g5vRF9guRLFEqwBNC6bW/sJjCltsRq/UDnlgErmskMLUIXn\n\tyd+nxrAF6MZTNdOsJOu1n9e/IYuQji8dKBRBeqgbAZekL5+pDvYjLJMdcplDTH567VK8\n\t1Sew==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20161025;\n\th=x-gm-message-state:date:from:to:cc:subject:message-id:references\n\t:mime-version:content-disposition:content-transfer-encoding\n\t:in-reply-to;\n\tbh=D6/cp1iv8EZecFvHNdd766jcST5lsxAnlZUcwzrBCtc=;\n\tb=DeJECuUfXL8HoLl8WwO/PKY1EpdCAoqmJ2sMwWxtCHN89OB7Ulsl+kbITCo7xDevbt\n\tANnpIM6RhPnUIsQMdspb/W9Sydn22VcvlkxlN049ny94iWg8j3uuqPn6YXH6TEgkD1sx\n\tiTLO8iJ+vKtUsNX7YHNbKUjzb8ptebVuhTO1JHcvdXoByMFzscr/Z1bdVL+iJxtPhyQt\n\t3KjNb1ktlzMdqL7qJQaeis1I0/yZAdSWuWmbZbov+q91kdg9NPef3oPBESWwCBW0JUV4\n\tYaZnr8KjyXEX9ATRdhZ5uKJzdmDXgHeL3eIMoAHlPtLkjuFJ2VQxMOa3zNqo9YE+deJi\n\trClg==","X-Gm-Message-State":"AOAM531rTJ1SvyT3y6bHhCOcT9UTwLxZxuZbAiHsJdwnWMYhKuoihZ8g\n\t76/JaF1qbVT2RGM/hQQN1Y2LK/9F0WeR8w==","X-Google-Smtp-Source":"ABdhPJzhWscCbOrMGH7tpwP2z7hviOfgj3tjl8Y6yA/M16g/JB586jcKtSz6iQo5OOElgsrqgeVxQg==","X-Received":"by 2002:a2e:b4d0:: with SMTP id\n\tr16mr2182608ljm.470.1602111547830; \n\tWed, 07 Oct 2020 15:59:07 -0700 (PDT)","Date":"Thu, 8 Oct 2020 00:59:06 +0200","From":"Niklas =?utf-8?q?S=C3=B6derlund?= <niklas.soderlund@ragnatech.se>","To":"Paul Elder <paul.elder@ideasonboard.com>","Message-ID":"<20201007225906.fr2sjwevvoflatfj@oden.dyn.berto.se>","References":"<20201007073418.512656-1-paul.elder@ideasonboard.com>","MIME-Version":"1.0","Content-Disposition":"inline","In-Reply-To":"<20201007073418.512656-1-paul.elder@ideasonboard.com>","Subject":"Re: [libcamera-devel] [PATCH v6] libcamera, android, cam, gstreamer,\n\tqcam, v4l2: Reuse Request","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Cc":"libcamera-devel@lists.libcamera.org","Content-Type":"text/plain; charset=\"iso-8859-1\"","Content-Transfer-Encoding":"quoted-printable","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":13093,"web_url":"https://patchwork.libcamera.org/comment/13093/","msgid":"<20201007230554.GQ3937@pendragon.ideasonboard.com>","date":"2020-10-07T23:05:54","subject":"Re: [libcamera-devel] [PATCH v6] libcamera, android, cam, gstreamer,\n\tqcam, v4l2: Reuse Request","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"On Thu, Oct 08, 2020 at 12:59:06AM +0200, Niklas Söderlund wrote:\n> Hi Paul,\n> \n> Thanks for your work and thanks Laurent for explaining it to me on IRC \n> :-P\n\nFor the record as that discussion wasn't public, we should, on top of\nthis, attempt to rework the HAL code (and possibly the gstreamer code)\nto reuse requests.\n\n> On 2020-10-07 16:34:18 +0900, Paul Elder wrote:\n> > Allow reuse of the Request object by implementing reuse(). This means\n> > the applications now have the responsibility of freeing the Request\n> > objects, so make all libcamera users (cam, qcam, v4l2-compat, gstreamer,\n> > android) do so.\n> > \n> > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> \n> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\n> \n> > ---\n> > Changes in v6:\n> > - fix doxygen\n> > - rename enum ReuseBuffer to ReuseFlag, and change to flags\n> > - move request->reuse() in v4l2-compat from qbuf time to completion\n> >   time\n> >   - streamOff when the stream is already off needs to also\n> >     request->reuse(), so do that too\n> > - update documentation\n> > \n> > Changes in v5:\n> > - rename reset() to reuse()\n> > - make reuse()'s reuseBuffers parameter into enum instead of bool\n> > - fix qcam qt assertion error on the first queueRequest, where\n> >   freeQueue_ is empty\n> > \n> > Changes in v4:\n> > - no more implicit calls of anything that we added in this patch\n> > - make reset() take a reuseBuffers boolean parameters\n> >   - use transient request - delete request\n> >   - reuse request, reset buffers - reset()\n> >   - reuse request, reuse buffesr - reset(true)\n> > - update apps and tests and documentation accordingly\n> > \n> > Changes in v3:\n> > - reset() is called in Camera::queueRequest()\n> > - apps that use transient request (android, gstreamer) delete the\n> >   request at the end of the completion handler\n> > - apps that want to reuse the buffers (cam) use reAddBuffers()\n> > - apps that need to change the buffers (qcam, v4l2) use clearBuffers()\n> > - update the documentation accordingly\n> > \n> > Changes in v2:\n> > - clear controls_ and metadata_ and validator_ in Request::reset()\n> > - use unique_ptr on application side, prior to queueRequest, and use\n> >   regular pointer for completion handler\n> > - make qcam's reuse request nicer\n> > - update Camera::queueRequest() and Camera::createRequest() documentation\n> > - add documentation for Request::reset()\n> > - make v4l2-compat reuse request\n> > - make gstreamer and android use the new createRequest API, though they\n> >   do not actually reuse the requests\n> > ---\n> >  include/libcamera/camera.h        |  2 +-\n> >  include/libcamera/request.h       |  7 +++++\n> >  src/android/camera_device.cpp     | 14 +++++++---\n> >  src/cam/capture.cpp               | 29 +++++---------------\n> >  src/cam/capture.h                 |  3 +++\n> >  src/gstreamer/gstlibcamerasrc.cpp | 14 ++++++----\n> >  src/libcamera/camera.cpp          | 14 ++++------\n> >  src/libcamera/request.cpp         | 38 ++++++++++++++++++++++++++\n> >  src/qcam/main_window.cpp          | 42 ++++++++++++++++-------------\n> >  src/qcam/main_window.h            | 26 +++++-------------\n> >  src/v4l2/v4l2_camera.cpp          | 44 ++++++++++++++++++++++---------\n> >  src/v4l2/v4l2_camera.h            |  4 ++-\n> >  test/camera/buffer_import.cpp     | 13 ++++-----\n> >  test/camera/capture.cpp           | 13 ++++-----\n> >  test/camera/statemachine.cpp      |  9 +++----\n> >  15 files changed, 163 insertions(+), 109 deletions(-)\n> > \n> > diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> > index a2ee4e7e..79ff8d6b 100644\n> > --- a/include/libcamera/camera.h\n> > +++ b/include/libcamera/camera.h\n> > @@ -96,7 +96,7 @@ public:\n> >  \tstd::unique_ptr<CameraConfiguration> generateConfiguration(const StreamRoles &roles = {});\n> >  \tint configure(CameraConfiguration *config);\n> >  \n> > -\tRequest *createRequest(uint64_t cookie = 0);\n> > +\tstd::unique_ptr<Request> createRequest(uint64_t cookie = 0);\n> >  \tint queueRequest(Request *request);\n> >  \n> >  \tint start();\n> > diff --git a/include/libcamera/request.h b/include/libcamera/request.h\n> > index 5976ac50..655b1324 100644\n> > --- a/include/libcamera/request.h\n> > +++ b/include/libcamera/request.h\n> > @@ -31,6 +31,11 @@ public:\n> >  \t\tRequestCancelled,\n> >  \t};\n> >  \n> > +\tenum ReuseFlag {\n> > +\t\tDefault = 0,\n> > +\t\tReuseBuffers = (1 << 0),\n> > +\t};\n> > +\n> >  \tusing BufferMap = std::map<const Stream *, FrameBuffer *>;\n> >  \n> >  \tRequest(Camera *camera, uint64_t cookie = 0);\n> > @@ -38,6 +43,8 @@ public:\n> >  \tRequest &operator=(const Request &) = delete;\n> >  \t~Request();\n> >  \n> > +\tvoid reuse(ReuseFlag flags = Default);\n> > +\n> >  \tControlList &controls() { return *controls_; }\n> >  \tControlList &metadata() { return *metadata_; }\n> >  \tconst BufferMap &buffers() const { return bufferMap_; }\n> > diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp\n> > index 751699cd..052c9292 100644\n> > --- a/src/android/camera_device.cpp\n> > +++ b/src/android/camera_device.cpp\n> > @@ -1395,8 +1395,12 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n> >  \t\tnew Camera3RequestDescriptor(camera3Request->frame_number,\n> >  \t\t\t\t\t     camera3Request->num_output_buffers);\n> >  \n> > -\tRequest *request =\n> > +\tstd::unique_ptr<Request> request =\n> >  \t\tcamera_->createRequest(reinterpret_cast<uint64_t>(descriptor));\n> > +\tif (!request) {\n> > +\t\tLOG(HAL, Error) << \"Failed to create request\";\n> > +\t\treturn -ENOMEM;\n> > +\t}\n> >  \n> >  \tfor (unsigned int i = 0; i < descriptor->numBuffers; ++i) {\n> >  \t\tCameraStream *cameraStream =\n> > @@ -1422,7 +1426,6 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n> >  \t\tFrameBuffer *buffer = createFrameBuffer(*camera3Buffers[i].buffer);\n> >  \t\tif (!buffer) {\n> >  \t\t\tLOG(HAL, Error) << \"Failed to create buffer\";\n> > -\t\t\tdelete request;\n> >  \t\t\tdelete descriptor;\n> >  \t\t\treturn -ENOMEM;\n> >  \t\t}\n> > @@ -1434,14 +1437,16 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n> >  \t\trequest->addBuffer(stream, buffer);\n> >  \t}\n> >  \n> > -\tint ret = camera_->queueRequest(request);\n> > +\tint ret = camera_->queueRequest(request.get());\n> >  \tif (ret) {\n> >  \t\tLOG(HAL, Error) << \"Failed to queue request\";\n> > -\t\tdelete request;\n> >  \t\tdelete descriptor;\n> >  \t\treturn ret;\n> >  \t}\n> >  \n> > +\t/* The request will be deleted in the completion handler. */\n> > +\trequest.release();\n> > +\n> >  \treturn 0;\n> >  }\n> >  \n> > @@ -1593,6 +1598,7 @@ void CameraDevice::requestComplete(Request *request)\n> >  \tcallbacks_->process_capture_result(callbacks_, &captureResult);\n> >  \n> >  \tdelete descriptor;\n> > +\tdelete request;\n> >  }\n> >  \n> >  std::string CameraDevice::logPrefix() const\n> > diff --git a/src/cam/capture.cpp b/src/cam/capture.cpp\n> > index 5510c009..8c8faa4b 100644\n> > --- a/src/cam/capture.cpp\n> > +++ b/src/cam/capture.cpp\n> > @@ -65,6 +65,8 @@ int Capture::run(const OptionsParser::Options &options)\n> >  \t\twriter_ = nullptr;\n> >  \t}\n> >  \n> > +\trequests_.clear();\n> > +\n> >  \tdelete allocator;\n> >  \n> >  \treturn ret;\n> > @@ -92,9 +94,8 @@ int Capture::capture(FrameBufferAllocator *allocator)\n> >  \t * example pushing a button. For now run all streams all the time.\n> >  \t */\n> >  \n> > -\tstd::vector<Request *> requests;\n> >  \tfor (unsigned int i = 0; i < nbuffers; i++) {\n> > -\t\tRequest *request = camera_->createRequest();\n> > +\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> >  \t\tif (!request) {\n> >  \t\t\tstd::cerr << \"Can't create request\" << std::endl;\n> >  \t\t\treturn -ENOMEM;\n> > @@ -117,7 +118,7 @@ int Capture::capture(FrameBufferAllocator *allocator)\n> >  \t\t\t\twriter_->mapBuffer(buffer.get());\n> >  \t\t}\n> >  \n> > -\t\trequests.push_back(request);\n> > +\t\trequests_.push_back(std::move(request));\n> >  \t}\n> >  \n> >  \tret = camera_->start();\n> > @@ -126,8 +127,8 @@ int Capture::capture(FrameBufferAllocator *allocator)\n> >  \t\treturn ret;\n> >  \t}\n> >  \n> > -\tfor (Request *request : requests) {\n> > -\t\tret = camera_->queueRequest(request);\n> > +\tfor (std::unique_ptr<Request> &request : requests_) {\n> > +\t\tret = camera_->queueRequest(request.get());\n> >  \t\tif (ret < 0) {\n> >  \t\t\tstd::cerr << \"Can't queue request\" << std::endl;\n> >  \t\t\tcamera_->stop();\n> > @@ -202,22 +203,6 @@ void Capture::requestComplete(Request *request)\n> >  \t\treturn;\n> >  \t}\n> >  \n> > -\t/*\n> > -\t * Create a new request and populate it with one buffer for each\n> > -\t * stream.\n> > -\t */\n> > -\trequest = camera_->createRequest();\n> > -\tif (!request) {\n> > -\t\tstd::cerr << \"Can't create request\" << std::endl;\n> > -\t\treturn;\n> > -\t}\n> > -\n> > -\tfor (auto it = buffers.begin(); it != buffers.end(); ++it) {\n> > -\t\tconst Stream *stream = it->first;\n> > -\t\tFrameBuffer *buffer = it->second;\n> > -\n> > -\t\trequest->addBuffer(stream, buffer);\n> > -\t}\n> > -\n> > +\trequest->reuse(Request::ReuseBuffers);\n> >  \tcamera_->queueRequest(request);\n> >  }\n> > diff --git a/src/cam/capture.h b/src/cam/capture.h\n> > index 0aebdac9..45e5e8a9 100644\n> > --- a/src/cam/capture.h\n> > +++ b/src/cam/capture.h\n> > @@ -9,6 +9,7 @@\n> >  \n> >  #include <memory>\n> >  #include <stdint.h>\n> > +#include <vector>\n> >  \n> >  #include <libcamera/buffer.h>\n> >  #include <libcamera/camera.h>\n> > @@ -43,6 +44,8 @@ private:\n> >  \tEventLoop *loop_;\n> >  \tunsigned int captureCount_;\n> >  \tunsigned int captureLimit_;\n> > +\n> > +\tstd::vector<std::unique_ptr<libcamera::Request>> requests_;\n> >  };\n> >  \n> >  #endif /* __CAM_CAPTURE_H__ */\n> > diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp\n> > index 1bfc2e2f..5001083a 100644\n> > --- a/src/gstreamer/gstlibcamerasrc.cpp\n> > +++ b/src/gstreamer/gstlibcamerasrc.cpp\n> > @@ -74,6 +74,8 @@ RequestWrap::~RequestWrap()\n> >  \t\tif (item.second)\n> >  \t\t\tgst_buffer_unref(item.second);\n> >  \t}\n> > +\n> > +\tdelete request_;\n> >  }\n> >  \n> >  void RequestWrap::attachBuffer(GstBuffer *buffer)\n> > @@ -266,8 +268,8 @@ gst_libcamera_src_task_run(gpointer user_data)\n> >  \tGstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);\n> >  \tGstLibcameraSrcState *state = self->state;\n> >  \n> > -\tRequest *request = state->cam_->createRequest();\n> > -\tauto wrap = std::make_unique<RequestWrap>(request);\n> > +\tstd::unique_ptr<Request> request = state->cam_->createRequest();\n> > +\tauto wrap = std::make_unique<RequestWrap>(request.get());\n> >  \tfor (GstPad *srcpad : state->srcpads_) {\n> >  \t\tGstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);\n> >  \t\tGstBuffer *buffer;\n> > @@ -280,8 +282,7 @@ gst_libcamera_src_task_run(gpointer user_data)\n> >  \t\t\t * RequestWrap does not take ownership, and we won't be\n> >  \t\t\t * queueing this one due to lack of buffers.\n> >  \t\t\t */\n> > -\t\t\tdelete request;\n> > -\t\t\trequest = nullptr;\n> > +\t\t\trequest.reset();\n> >  \t\t\tbreak;\n> >  \t\t}\n> >  \n> > @@ -291,8 +292,11 @@ gst_libcamera_src_task_run(gpointer user_data)\n> >  \tif (request) {\n> >  \t\tGLibLocker lock(GST_OBJECT(self));\n> >  \t\tGST_TRACE_OBJECT(self, \"Requesting buffers\");\n> > -\t\tstate->cam_->queueRequest(request);\n> > +\t\tstate->cam_->queueRequest(request.get());\n> >  \t\tstate->requests_.push(std::move(wrap));\n> > +\n> > +\t\t/* The request will be deleted in the completion handler. */\n> > +\t\trequest.release();\n> >  \t}\n> >  \n> >  \tGstFlowReturn ret = GST_FLOW_OK;\n> > diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> > index fb76077f..9590ab72 100644\n> > --- a/src/libcamera/camera.cpp\n> > +++ b/src/libcamera/camera.cpp\n> > @@ -847,21 +847,22 @@ int Camera::configure(CameraConfiguration *config)\n> >   * handler, and is completely opaque to libcamera.\n> >   *\n> >   * The ownership of the returned request is passed to the caller, which is\n> > - * responsible for either queueing the request or deleting it.\n> > + * responsible for deleting it. The request may be deleted in the completion\n> > + * handler, or reused after resetting its state with Request::reuse().\n> >   *\n> >   * \\context This function is \\threadsafe. It may only be called when the camera\n> >   * is in the Configured or Running state as defined in \\ref camera_operation.\n> >   *\n> >   * \\return A pointer to the newly created request, or nullptr on error\n> >   */\n> > -Request *Camera::createRequest(uint64_t cookie)\n> > +std::unique_ptr<Request> Camera::createRequest(uint64_t cookie)\n> >  {\n> >  \tint ret = p_->isAccessAllowed(Private::CameraConfigured,\n> >  \t\t\t\t      Private::CameraRunning);\n> >  \tif (ret < 0)\n> >  \t\treturn nullptr;\n> >  \n> > -\treturn new Request(this, cookie);\n> > +\treturn std::make_unique<Request>(this, cookie);\n> >  }\n> >  \n> >  /**\n> > @@ -877,9 +878,6 @@ Request *Camera::createRequest(uint64_t cookie)\n> >   * Once the request has been queued, the camera will notify its completion\n> >   * through the \\ref requestCompleted signal.\n> >   *\n> > - * Ownership of the request is transferred to the camera. It will be deleted\n> > - * automatically after it completes.\n> > - *\n> >   * \\context This function is \\threadsafe. It may only be called when the camera\n> >   * is in the Running state as defined in \\ref camera_operation.\n> >   *\n> > @@ -988,13 +986,11 @@ int Camera::stop()\n> >   * \\param[in] request The request that has completed\n> >   *\n> >   * This function is called by the pipeline handler to notify the camera that\n> > - * the request has completed. It emits the requestCompleted signal and deletes\n> > - * the request.\n> > + * the request has completed. It emits the requestCompleted signal.\n> >   */\n> >  void Camera::requestComplete(Request *request)\n> >  {\n> >  \trequestCompleted.emit(request);\n> > -\tdelete request;\n> >  }\n> >  \n> >  } /* namespace libcamera */\n> > diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp\n> > index 60b30692..ae8b1660 100644\n> > --- a/src/libcamera/request.cpp\n> > +++ b/src/libcamera/request.cpp\n> > @@ -37,6 +37,15 @@ LOG_DEFINE_CATEGORY(Request)\n> >   * The request has been cancelled due to capture stop\n> >   */\n> >  \n> > +/**\n> > + * \\enum Request::ReuseFlag\n> > + * Flags to control the behavior of Request::reuse()\n> > + * \\var Request::Default\n> > + * Don't reuse buffers\n> > + * \\var Request::ReuseBuffers\n> > + * Reuse the buffers that were previously added by addBuffer()\n> > + */\n> > +\n> >  /**\n> >   * \\typedef Request::BufferMap\n> >   * \\brief A map of Stream to FrameBuffer pointers\n> > @@ -85,6 +94,35 @@ Request::~Request()\n> >  \tdelete validator_;\n> >  }\n> >  \n> > +/**\n> > + * \\brief Reset the request for reuse\n> > + * \\param[in] flags Indicate whether or not to reuse the buffers\n> > + *\n> > + * Reset the status and controls associated with the request, to allow it to\n> > + * be reused and requeued without destruction. This function shall be called\n> > + * prior to queueing the request to the camera, in lieu of constructing a new\n> > + * request. The application can reuse the buffers that were previously added\n> > + * to the request via addBuffer() by setting \\a flags to ReuseBuffers.\n> > + */\n> > +void Request::reuse(ReuseFlag flags)\n> > +{\n> > +\tpending_.clear();\n> > +\tif (flags & ReuseBuffers) {\n> > +\t\tfor (auto pair : bufferMap_) {\n> > +\t\t\tpair.second->request_ = this;\n> > +\t\t\tpending_.insert(pair.second);\n> > +\t\t}\n> > +\t} else {\n> > +\t\tbufferMap_.clear();\n> > +\t}\n> > +\n> > +\tstatus_ = RequestPending;\n> > +\tcancelled_ = false;\n> > +\n> > +\tcontrols_->clear();\n> > +\tmetadata_->clear();\n> > +}\n> > +\n> >  /**\n> >   * \\fn Request::controls()\n> >   * \\brief Retrieve the request's ControlList\n> > diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp\n> > index ecb9dd66..0cbdab9a 100644\n> > --- a/src/qcam/main_window.cpp\n> > +++ b/src/qcam/main_window.cpp\n> > @@ -367,7 +367,6 @@ void MainWindow::toggleCapture(bool start)\n> >  int MainWindow::startCapture()\n> >  {\n> >  \tStreamRoles roles = StreamKeyValueParser::roles(options_[OptStream]);\n> > -\tstd::vector<Request *> requests;\n> >  \tint ret;\n> >  \n> >  \t/* Verify roles are supported. */\n> > @@ -486,7 +485,7 @@ int MainWindow::startCapture()\n> >  \twhile (!freeBuffers_[vfStream_].isEmpty()) {\n> >  \t\tFrameBuffer *buffer = freeBuffers_[vfStream_].dequeue();\n> >  \n> > -\t\tRequest *request = camera_->createRequest();\n> > +\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> >  \t\tif (!request) {\n> >  \t\t\tqWarning() << \"Can't create request\";\n> >  \t\t\tret = -ENOMEM;\n> > @@ -499,7 +498,7 @@ int MainWindow::startCapture()\n> >  \t\t\tgoto error;\n> >  \t\t}\n> >  \n> > -\t\trequests.push_back(request);\n> > +\t\trequests_.push_back(std::move(request));\n> >  \t}\n> >  \n> >  \t/* Start the title timer and the camera. */\n> > @@ -518,8 +517,8 @@ int MainWindow::startCapture()\n> >  \tcamera_->requestCompleted.connect(this, &MainWindow::requestComplete);\n> >  \n> >  \t/* Queue all requests. */\n> > -\tfor (Request *request : requests) {\n> > -\t\tret = camera_->queueRequest(request);\n> > +\tfor (std::unique_ptr<Request> &request : requests_) {\n> > +\t\tret = camera_->queueRequest(request.get());\n> >  \t\tif (ret < 0) {\n> >  \t\t\tqWarning() << \"Can't queue request\";\n> >  \t\t\tgoto error_disconnect;\n> > @@ -535,8 +534,7 @@ error_disconnect:\n> >  \tcamera_->stop();\n> >  \n> >  error:\n> > -\tfor (Request *request : requests)\n> > -\t\tdelete request;\n> > +\trequests_.clear();\n> >  \n> >  \tfor (auto &iter : mappedBuffers_) {\n> >  \t\tconst MappedBuffer &buffer = iter.second;\n> > @@ -580,6 +578,8 @@ void MainWindow::stopCapture()\n> >  \t}\n> >  \tmappedBuffers_.clear();\n> >  \n> > +\trequests_.clear();\n> > +\n> >  \tdelete allocator_;\n> >  \n> >  \tisCapturing_ = false;\n> > @@ -701,7 +701,7 @@ void MainWindow::requestComplete(Request *request)\n> >  \t */\n> >  \t{\n> >  \t\tQMutexLocker locker(&mutex_);\n> > -\t\tdoneQueue_.enqueue({ request->buffers(), request->metadata() });\n> > +\t\tdoneQueue_.enqueue(request);\n> >  \t}\n> >  \n> >  \tQCoreApplication::postEvent(this, new CaptureEvent);\n> > @@ -714,8 +714,7 @@ void MainWindow::processCapture()\n> >  \t * if stopCapture() has been called while a CaptureEvent was posted but\n> >  \t * not processed yet. Return immediately in that case.\n> >  \t */\n> > -\tCaptureRequest request;\n> > -\n> > +\tRequest *request;\n> >  \t{\n> >  \t\tQMutexLocker locker(&mutex_);\n> >  \t\tif (doneQueue_.isEmpty())\n> > @@ -725,11 +724,15 @@ void MainWindow::processCapture()\n> >  \t}\n> >  \n> >  \t/* Process buffers. */\n> > -\tif (request.buffers_.count(vfStream_))\n> > -\t\tprocessViewfinder(request.buffers_[vfStream_]);\n> > +\tif (request->buffers().count(vfStream_))\n> > +\t\tprocessViewfinder(request->buffers().at(vfStream_));\n> >  \n> > -\tif (request.buffers_.count(rawStream_))\n> > -\t\tprocessRaw(request.buffers_[rawStream_], request.metadata_);\n> > +\tif (request->buffers().count(rawStream_))\n> > +\t\tprocessRaw(request->buffers().at(rawStream_), request->metadata());\n> > +\n> > +\trequest->reuse();\n> > +\tQMutexLocker locker(&mutex_);\n> > +\tfreeQueue_.enqueue(request);\n> >  }\n> >  \n> >  void MainWindow::processViewfinder(FrameBuffer *buffer)\n> > @@ -754,10 +757,13 @@ void MainWindow::processViewfinder(FrameBuffer *buffer)\n> >  \n> >  void MainWindow::queueRequest(FrameBuffer *buffer)\n> >  {\n> > -\tRequest *request = camera_->createRequest();\n> > -\tif (!request) {\n> > -\t\tqWarning() << \"Can't create request\";\n> > -\t\treturn;\n> > +\tRequest *request;\n> > +\t{\n> > +\t\tQMutexLocker locker(&mutex_);\n> > +\t\tif (freeQueue_.isEmpty())\n> > +\t\t\treturn;\n> > +\n> > +\t\trequest = freeQueue_.dequeue();\n> >  \t}\n> >  \n> >  \trequest->addBuffer(vfStream_, buffer);\n> > diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h\n> > index 5c61a4df..64bcfebc 100644\n> > --- a/src/qcam/main_window.h\n> > +++ b/src/qcam/main_window.h\n> > @@ -8,6 +8,7 @@\n> >  #define __QCAM_MAIN_WINDOW_H__\n> >  \n> >  #include <memory>\n> > +#include <vector>\n> >  \n> >  #include <QElapsedTimer>\n> >  #include <QIcon>\n> > @@ -22,6 +23,7 @@\n> >  #include <libcamera/camera_manager.h>\n> >  #include <libcamera/controls.h>\n> >  #include <libcamera/framebuffer_allocator.h>\n> > +#include <libcamera/request.h>\n> >  #include <libcamera/stream.h>\n> >  \n> >  #include \"../cam/stream_options.h\"\n> > @@ -41,23 +43,6 @@ enum {\n> >  \tOptStream = 's',\n> >  };\n> >  \n> > -class CaptureRequest\n> > -{\n> > -public:\n> > -\tCaptureRequest()\n> > -\t{\n> > -\t}\n> > -\n> > -\tCaptureRequest(const Request::BufferMap &buffers,\n> > -\t\t       const ControlList &metadata)\n> > -\t\t: buffers_(buffers), metadata_(metadata)\n> > -\t{\n> > -\t}\n> > -\n> > -\tRequest::BufferMap buffers_;\n> > -\tControlList metadata_;\n> > -};\n> > -\n> >  class MainWindow : public QMainWindow\n> >  {\n> >  \tQ_OBJECT\n> > @@ -128,13 +113,16 @@ private:\n> >  \tStream *vfStream_;\n> >  \tStream *rawStream_;\n> >  \tstd::map<const Stream *, QQueue<FrameBuffer *>> freeBuffers_;\n> > -\tQQueue<CaptureRequest> doneQueue_;\n> > -\tQMutex mutex_; /* Protects freeBuffers_ and doneQueue_ */\n> > +\tQQueue<Request *> doneQueue_;\n> > +\tQQueue<Request *> freeQueue_;\n> > +\tQMutex mutex_; /* Protects freeBuffers_, doneQueue_, and freeQueue_ */\n> >  \n> >  \tuint64_t lastBufferTime_;\n> >  \tQElapsedTimer frameRateInterval_;\n> >  \tuint32_t previousFrames_;\n> >  \tuint32_t framesCaptured_;\n> > +\n> > +\tstd::vector<std::unique_ptr<Request>> requests_;\n> >  };\n> >  \n> >  #endif /* __QCAM_MAIN_WINDOW__ */\n> > diff --git a/src/v4l2/v4l2_camera.cpp b/src/v4l2/v4l2_camera.cpp\n> > index 3565f369..35d3beda 100644\n> > --- a/src/v4l2/v4l2_camera.cpp\n> > +++ b/src/v4l2/v4l2_camera.cpp\n> > @@ -49,6 +49,8 @@ int V4L2Camera::open(StreamConfiguration *streamConfig)\n> >  \n> >  void V4L2Camera::close()\n> >  {\n> > +\trequestPool_.clear();\n> > +\n> >  \tdelete bufferAllocator_;\n> >  \tbufferAllocator_ = nullptr;\n> >  \n> > @@ -96,6 +98,7 @@ void V4L2Camera::requestComplete(Request *request)\n> >  \tif (ret != sizeof(data))\n> >  \t\tLOG(V4L2Compat, Error) << \"Failed to signal eventfd POLLIN\";\n> >  \n> > +\trequest->reuse();\n> >  \t{\n> >  \t\tMutexLocker locker(bufferMutex_);\n> >  \t\tbufferAvailableCount_++;\n> > @@ -154,16 +157,30 @@ int V4L2Camera::validateConfiguration(const PixelFormat &pixelFormat,\n> >  \treturn 0;\n> >  }\n> >  \n> > -int V4L2Camera::allocBuffers([[maybe_unused]] unsigned int count)\n> > +int V4L2Camera::allocBuffers(unsigned int count)\n> >  {\n> >  \tStream *stream = config_->at(0).stream();\n> >  \n> > -\treturn bufferAllocator_->allocate(stream);\n> > +\tint ret = bufferAllocator_->allocate(stream);\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\tfor (unsigned int i = 0; i < count; i++) {\n> > +\t\tstd::unique_ptr<Request> request = camera_->createRequest(i);\n> > +\t\tif (!request) {\n> > +\t\t\trequestPool_.clear();\n> > +\t\t\treturn -ENOMEM;\n> > +\t\t}\n> > +\t\trequestPool_.push_back(std::move(request));\n> > +\t}\n> > +\n> > +\treturn ret;\n> >  }\n> >  \n> >  void V4L2Camera::freeBuffers()\n> >  {\n> >  \tpendingRequests_.clear();\n> > +\trequestPool_.clear();\n> >  \n> >  \tStream *stream = config_->at(0).stream();\n> >  \tbufferAllocator_->free(stream);\n> > @@ -192,9 +209,9 @@ int V4L2Camera::streamOn()\n> >  \n> >  \tisRunning_ = true;\n> >  \n> > -\tfor (std::unique_ptr<Request> &req : pendingRequests_) {\n> > +\tfor (Request *req : pendingRequests_) {\n> >  \t\t/* \\todo What should we do if this returns -EINVAL? */\n> > -\t\tret = camera_->queueRequest(req.release());\n> > +\t\tret = camera_->queueRequest(req);\n> >  \t\tif (ret < 0)\n> >  \t\t\treturn ret == -EACCES ? -EBUSY : ret;\n> >  \t}\n> > @@ -206,8 +223,12 @@ int V4L2Camera::streamOn()\n> >  \n> >  int V4L2Camera::streamOff()\n> >  {\n> > -\tif (!isRunning_)\n> > +\tif (!isRunning_) {\n> > +\t\tfor (std::unique_ptr<Request> &req : requestPool_)\n> > +\t\t\treq->reuse();\n> > +\n> >  \t\treturn 0;\n> > +\t}\n> >  \n> >  \tpendingRequests_.clear();\n> >  \n> > @@ -226,12 +247,11 @@ int V4L2Camera::streamOff()\n> >  \n> >  int V4L2Camera::qbuf(unsigned int index)\n> >  {\n> > -\tstd::unique_ptr<Request> request =\n> > -\t\tstd::unique_ptr<Request>(camera_->createRequest(index));\n> > -\tif (!request) {\n> > -\t\tLOG(V4L2Compat, Error) << \"Can't create request\";\n> > -\t\treturn -ENOMEM;\n> > +\tif (index >= requestPool_.size()) {\n> > +\t\tLOG(V4L2Compat, Error) << \"Invalid index\";\n> > +\t\treturn -EINVAL;\n> >  \t}\n> > +\tRequest *request = requestPool_[index].get();\n> >  \n> >  \tStream *stream = config_->at(0).stream();\n> >  \tFrameBuffer *buffer = bufferAllocator_->buffers(stream)[index].get();\n> > @@ -242,11 +262,11 @@ int V4L2Camera::qbuf(unsigned int index)\n> >  \t}\n> >  \n> >  \tif (!isRunning_) {\n> > -\t\tpendingRequests_.push_back(std::move(request));\n> > +\t\tpendingRequests_.push_back(request);\n> >  \t\treturn 0;\n> >  \t}\n> >  \n> > -\tret = camera_->queueRequest(request.release());\n> > +\tret = camera_->queueRequest(request);\n> >  \tif (ret < 0) {\n> >  \t\tLOG(V4L2Compat, Error) << \"Can't queue request\";\n> >  \t\treturn ret == -EACCES ? -EBUSY : ret;\n> > diff --git a/src/v4l2/v4l2_camera.h b/src/v4l2/v4l2_camera.h\n> > index 1fc5ebef..a6c35a2e 100644\n> > --- a/src/v4l2/v4l2_camera.h\n> > +++ b/src/v4l2/v4l2_camera.h\n> > @@ -76,7 +76,9 @@ private:\n> >  \tstd::mutex bufferLock_;\n> >  \tFrameBufferAllocator *bufferAllocator_;\n> >  \n> > -\tstd::deque<std::unique_ptr<Request>> pendingRequests_;\n> > +\tstd::vector<std::unique_ptr<Request>> requestPool_;\n> > +\n> > +\tstd::deque<Request *> pendingRequests_;\n> >  \tstd::deque<std::unique_ptr<Buffer>> completedBuffers_;\n> >  \n> >  \tint efd_;\n> > diff --git a/test/camera/buffer_import.cpp b/test/camera/buffer_import.cpp\n> > index 64e96264..72ce7b79 100644\n> > --- a/test/camera/buffer_import.cpp\n> > +++ b/test/camera/buffer_import.cpp\n> > @@ -58,7 +58,7 @@ protected:\n> >  \t\tconst Stream *stream = buffers.begin()->first;\n> >  \t\tFrameBuffer *buffer = buffers.begin()->second;\n> >  \n> > -\t\trequest = camera_->createRequest();\n> > +\t\trequest->reuse();\n> >  \t\trequest->addBuffer(stream, buffer);\n> >  \t\tcamera_->queueRequest(request);\n> >  \t}\n> > @@ -98,9 +98,8 @@ protected:\n> >  \t\tif (ret != TestPass)\n> >  \t\t\treturn ret;\n> >  \n> > -\t\tstd::vector<Request *> requests;\n> >  \t\tfor (const std::unique_ptr<FrameBuffer> &buffer : source.buffers()) {\n> > -\t\t\tRequest *request = camera_->createRequest();\n> > +\t\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> >  \t\t\tif (!request) {\n> >  \t\t\t\tstd::cout << \"Failed to create request\" << std::endl;\n> >  \t\t\t\treturn TestFail;\n> > @@ -111,7 +110,7 @@ protected:\n> >  \t\t\t\treturn TestFail;\n> >  \t\t\t}\n> >  \n> > -\t\t\trequests.push_back(request);\n> > +\t\t\trequests_.push_back(std::move(request));\n> >  \t\t}\n> >  \n> >  \t\tcompleteRequestsCount_ = 0;\n> > @@ -125,8 +124,8 @@ protected:\n> >  \t\t\treturn TestFail;\n> >  \t\t}\n> >  \n> > -\t\tfor (Request *request : requests) {\n> > -\t\t\tif (camera_->queueRequest(request)) {\n> > +\t\tfor (std::unique_ptr<Request> &request : requests_) {\n> > +\t\t\tif (camera_->queueRequest(request.get())) {\n> >  \t\t\t\tstd::cout << \"Failed to queue request\" << std::endl;\n> >  \t\t\t\treturn TestFail;\n> >  \t\t\t}\n> > @@ -160,6 +159,8 @@ protected:\n> >  \t}\n> >  \n> >  private:\n> > +\tstd::vector<std::unique_ptr<Request>> requests_;\n> > +\n> >  \tunsigned int completeBuffersCount_;\n> >  \tunsigned int completeRequestsCount_;\n> >  \tstd::unique_ptr<CameraConfiguration> config_;\n> > diff --git a/test/camera/capture.cpp b/test/camera/capture.cpp\n> > index 51bbd258..c0770801 100644\n> > --- a/test/camera/capture.cpp\n> > +++ b/test/camera/capture.cpp\n> > @@ -52,7 +52,7 @@ protected:\n> >  \t\tconst Stream *stream = buffers.begin()->first;\n> >  \t\tFrameBuffer *buffer = buffers.begin()->second;\n> >  \n> > -\t\trequest = camera_->createRequest();\n> > +\t\trequest->reuse();\n> >  \t\trequest->addBuffer(stream, buffer);\n> >  \t\tcamera_->queueRequest(request);\n> >  \t}\n> > @@ -98,9 +98,8 @@ protected:\n> >  \t\tif (ret < 0)\n> >  \t\t\treturn TestFail;\n> >  \n> > -\t\tstd::vector<Request *> requests;\n> >  \t\tfor (const std::unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) {\n> > -\t\t\tRequest *request = camera_->createRequest();\n> > +\t\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> >  \t\t\tif (!request) {\n> >  \t\t\t\tcout << \"Failed to create request\" << endl;\n> >  \t\t\t\treturn TestFail;\n> > @@ -111,7 +110,7 @@ protected:\n> >  \t\t\t\treturn TestFail;\n> >  \t\t\t}\n> >  \n> > -\t\t\trequests.push_back(request);\n> > +\t\t\trequests_.push_back(std::move(request));\n> >  \t\t}\n> >  \n> >  \t\tcompleteRequestsCount_ = 0;\n> > @@ -125,8 +124,8 @@ protected:\n> >  \t\t\treturn TestFail;\n> >  \t\t}\n> >  \n> > -\t\tfor (Request *request : requests) {\n> > -\t\t\tif (camera_->queueRequest(request)) {\n> > +\t\tfor (std::unique_ptr<Request> &request : requests_) {\n> > +\t\t\tif (camera_->queueRequest(request.get())) {\n> >  \t\t\t\tcout << \"Failed to queue request\" << endl;\n> >  \t\t\t\treturn TestFail;\n> >  \t\t\t}\n> > @@ -161,6 +160,8 @@ protected:\n> >  \t\treturn TestPass;\n> >  \t}\n> >  \n> > +\tstd::vector<std::unique_ptr<Request>> requests_;\n> > +\n> >  \tstd::unique_ptr<CameraConfiguration> config_;\n> >  \tFrameBufferAllocator *allocator_;\n> >  };\n> > diff --git a/test/camera/statemachine.cpp b/test/camera/statemachine.cpp\n> > index 28faeb91..e63ab298 100644\n> > --- a/test/camera/statemachine.cpp\n> > +++ b/test/camera/statemachine.cpp\n> > @@ -101,13 +101,10 @@ protected:\n> >  \t\t\treturn TestFail;\n> >  \n> >  \t\t/* Test operations which should pass. */\n> > -\t\tRequest *request2 = camera_->createRequest();\n> > +\t\tstd::unique_ptr<Request> request2 = camera_->createRequest();\n> >  \t\tif (!request2)\n> >  \t\t\treturn TestFail;\n> >  \n> > -\t\t/* Never handed to hardware so need to manually delete it. */\n> > -\t\tdelete request2;\n> > -\n> >  \t\t/* Test valid state transitions, end in Running state. */\n> >  \t\tif (camera_->release())\n> >  \t\t\treturn TestFail;\n> > @@ -146,7 +143,7 @@ protected:\n> >  \t\t\treturn TestFail;\n> >  \n> >  \t\t/* Test operations which should pass. */\n> > -\t\tRequest *request = camera_->createRequest();\n> > +\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> >  \t\tif (!request)\n> >  \t\t\treturn TestFail;\n> >  \n> > @@ -154,7 +151,7 @@ protected:\n> >  \t\tif (request->addBuffer(stream, allocator_->buffers(stream)[0].get()))\n> >  \t\t\treturn TestFail;\n> >  \n> > -\t\tif (camera_->queueRequest(request))\n> > +\t\tif (camera_->queueRequest(request.get()))\n> >  \t\t\treturn TestFail;\n> >  \n> >  \t\t/* Test valid state transitions, end in Available state. */","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id D9FAFBEEE0\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed,  7 Oct 2020 23:06:39 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 6A03C60455;\n\tThu,  8 Oct 2020 01:06:39 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 74F9960396\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu,  8 Oct 2020 01:06:37 +0200 (CEST)","from pendragon.ideasonboard.com (62-78-145-57.bb.dnainternet.fi\n\t[62.78.145.57])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 880DC9DA;\n\tThu,  8 Oct 2020 01:06:36 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"gQ5HJAd8\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1602111996;\n\tbh=6usXswrknGetfc5fdPC9xZ0FIv/TS4vZG2SsPTynyFg=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=gQ5HJAd87jxQ7/wkUxh/AFU6Zg4/2KGD+s6kem0W5PrIoFzNMTHy9AydrZ0Jhr4O7\n\tcpelr8jq7VMZwSCr6mVNAw4TFyzn2HXy9s0+CApx8KFPClJRPs9j02PqydGMwLQSzw\n\tgA23DzIYRfFTzfW6CpZBFiwAt2mjtl/wuHeM+i4Q=","Date":"Thu, 8 Oct 2020 02:05:54 +0300","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Niklas =?utf-8?q?S=C3=B6derlund?= <niklas.soderlund@ragnatech.se>","Message-ID":"<20201007230554.GQ3937@pendragon.ideasonboard.com>","References":"<20201007073418.512656-1-paul.elder@ideasonboard.com>\n\t<20201007225906.fr2sjwevvoflatfj@oden.dyn.berto.se>","MIME-Version":"1.0","Content-Disposition":"inline","In-Reply-To":"<20201007225906.fr2sjwevvoflatfj@oden.dyn.berto.se>","Subject":"Re: [libcamera-devel] [PATCH v6] libcamera, android, cam, gstreamer,\n\tqcam, v4l2: Reuse Request","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Cc":"libcamera-devel@lists.libcamera.org","Content-Type":"text/plain; charset=\"utf-8\"","Content-Transfer-Encoding":"base64","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":13094,"web_url":"https://patchwork.libcamera.org/comment/13094/","msgid":"<20201008024540.GT3937@pendragon.ideasonboard.com>","date":"2020-10-08T02:45:40","subject":"Re: [libcamera-devel] [PATCH v6] libcamera, android, cam, gstreamer,\n\tqcam, v4l2: Reuse Request","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Jacopo,\n\nOn Wed, Oct 07, 2020 at 11:43:31AM +0200, Jacopo Mondi wrote:\n> Hi Paul,\n> \n> On Wed, Oct 07, 2020 at 04:34:18PM +0900, Paul Elder wrote:\n> > Allow reuse of the Request object by implementing reuse(). This means\n> > the applications now have the responsibility of freeing the Request\n> > objects, so make all libcamera users (cam, qcam, v4l2-compat, gstreamer,\n> > android) do so.\n> \n> Maybe not a question to ask at v6, I've gone through the previous\n> iterations and I see the proposed API has been discussed already, so\n> feel free to ignore, but: I'm not sure I like the flags passed to\n> reuse() :)\n> \n> To be more precise, I don't see much much value in ReuseBuffers.\n> \n> How usual is it that the same [Stream|buffer] pair is repeated ? I\n> understand we might want to capture from the same set of Streams, but\n> application would more likely cycle buffers, don't they ?\n>\n> I understand the ReuseBuffers flag might be used to model a\n> 'repeating' request mechanism, but I don't think our API is currently\n> ready to support such a feature. All our requests are one-shot, they\n> get queued, notified when completed, and need to be re-queued. There's\n> nothing like a mechanism that let's you create one request, queue it\n> once, and have it running until you don't stop you (without the need\n> for a requeue. For reference [1]). Implementing something like this\n> will need some more thinking, in example how to make it possible for a\n> repeating request to cycle on a set of buffers automatically.\n\nI agree, but that's not the use case at hand. Request::reuse() has to be\ncalled each time a request completes, it doesn't repeat requests\nautomatically.\n\n> I don't think we should try to emulate a repeating mechanism by\n> allowing application to re-use the same stream-buffer pair. It has a\n> limited use (in my understanding) and will confuse application as they\n> need anyway to re-queue it.\n\nThe use case seems pretty common to me. If you look at the cam\napplication for instance, we allocate a set of buffers, and the same\nnumber of requests. The buffers are cycled through, with one buffer per\nrequest. There's no need to keep separate pools of buffers and requests,\na 1:1 mapping is the simplest. We can add one buffer to each request,\nand then cycle through the requests reusing the buffers.\n\n> Don't get me wrong, I think moving ownership of Request to application\n> is correct and more clear, but I don't see much use for the flag [*]\n> \n> I would prefer if we keep thinking of our request as one shot, so they\n> could either be reset everytime, and application will have to\n> addBuffers() again as they will anyway have to cycle buffers.\n> \n> [*] This makes me unconfortable for another reason. I understand we\n> might want flags for, say, Controls handling and other possible tuning\n> (have you though about other possible flags ?). I don't think the\n> output buffer handling lives in the same category as low level control\n> of the Request, it's a primary Request feature and should not be expressed\n> with flags as in example \"Use template XYZ for the Request's controls\"\n> \n> [1] https://medium.com/androiddevelopers/understanding-android-camera-capture-sessions-and-requests-4e54d9150295\n> \n> >\n> > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> >\n> > ---\n> > Changes in v6:\n> > - fix doxygen\n> > - rename enum ReuseBuffer to ReuseFlag, and change to flags\n> > - move request->reuse() in v4l2-compat from qbuf time to completion\n> >   time\n> >   - streamOff when the stream is already off needs to also\n> >     request->reuse(), so do that too\n> > - update documentation\n> >\n> > Changes in v5:\n> > - rename reset() to reuse()\n> > - make reuse()'s reuseBuffers parameter into enum instead of bool\n> > - fix qcam qt assertion error on the first queueRequest, where\n> >   freeQueue_ is empty\n> >\n> > Changes in v4:\n> > - no more implicit calls of anything that we added in this patch\n> > - make reset() take a reuseBuffers boolean parameters\n> >   - use transient request - delete request\n> >   - reuse request, reset buffers - reset()\n> >   - reuse request, reuse buffesr - reset(true)\n> > - update apps and tests and documentation accordingly\n> >\n> > Changes in v3:\n> > - reset() is called in Camera::queueRequest()\n> > - apps that use transient request (android, gstreamer) delete the\n> >   request at the end of the completion handler\n> > - apps that want to reuse the buffers (cam) use reAddBuffers()\n> > - apps that need to change the buffers (qcam, v4l2) use clearBuffers()\n> > - update the documentation accordingly\n> >\n> > Changes in v2:\n> > - clear controls_ and metadata_ and validator_ in Request::reset()\n> > - use unique_ptr on application side, prior to queueRequest, and use\n> >   regular pointer for completion handler\n> > - make qcam's reuse request nicer\n> > - update Camera::queueRequest() and Camera::createRequest() documentation\n> > - add documentation for Request::reset()\n> > - make v4l2-compat reuse request\n> > - make gstreamer and android use the new createRequest API, though they\n> >   do not actually reuse the requests\n> > ---\n> >  include/libcamera/camera.h        |  2 +-\n> >  include/libcamera/request.h       |  7 +++++\n> >  src/android/camera_device.cpp     | 14 +++++++---\n> >  src/cam/capture.cpp               | 29 +++++---------------\n> >  src/cam/capture.h                 |  3 +++\n> >  src/gstreamer/gstlibcamerasrc.cpp | 14 ++++++----\n> >  src/libcamera/camera.cpp          | 14 ++++------\n> >  src/libcamera/request.cpp         | 38 ++++++++++++++++++++++++++\n> >  src/qcam/main_window.cpp          | 42 ++++++++++++++++-------------\n> >  src/qcam/main_window.h            | 26 +++++-------------\n> >  src/v4l2/v4l2_camera.cpp          | 44 ++++++++++++++++++++++---------\n> >  src/v4l2/v4l2_camera.h            |  4 ++-\n> >  test/camera/buffer_import.cpp     | 13 ++++-----\n> >  test/camera/capture.cpp           | 13 ++++-----\n> >  test/camera/statemachine.cpp      |  9 +++----\n> >  15 files changed, 163 insertions(+), 109 deletions(-)\n> >\n> > diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> > index a2ee4e7e..79ff8d6b 100644\n> > --- a/include/libcamera/camera.h\n> > +++ b/include/libcamera/camera.h\n> > @@ -96,7 +96,7 @@ public:\n> >  \tstd::unique_ptr<CameraConfiguration> generateConfiguration(const StreamRoles &roles = {});\n> >  \tint configure(CameraConfiguration *config);\n> >\n> > -\tRequest *createRequest(uint64_t cookie = 0);\n> > +\tstd::unique_ptr<Request> createRequest(uint64_t cookie = 0);\n> >  \tint queueRequest(Request *request);\n> >\n> >  \tint start();\n> > diff --git a/include/libcamera/request.h b/include/libcamera/request.h\n> > index 5976ac50..655b1324 100644\n> > --- a/include/libcamera/request.h\n> > +++ b/include/libcamera/request.h\n> > @@ -31,6 +31,11 @@ public:\n> >  \t\tRequestCancelled,\n> >  \t};\n> >\n> > +\tenum ReuseFlag {\n> > +\t\tDefault = 0,\n> > +\t\tReuseBuffers = (1 << 0),\n> > +\t};\n> > +\n> >  \tusing BufferMap = std::map<const Stream *, FrameBuffer *>;\n> >\n> >  \tRequest(Camera *camera, uint64_t cookie = 0);\n> > @@ -38,6 +43,8 @@ public:\n> >  \tRequest &operator=(const Request &) = delete;\n> >  \t~Request();\n> >\n> > +\tvoid reuse(ReuseFlag flags = Default);\n> > +\n> >  \tControlList &controls() { return *controls_; }\n> >  \tControlList &metadata() { return *metadata_; }\n> >  \tconst BufferMap &buffers() const { return bufferMap_; }\n> > diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp\n> > index 751699cd..052c9292 100644\n> > --- a/src/android/camera_device.cpp\n> > +++ b/src/android/camera_device.cpp\n> > @@ -1395,8 +1395,12 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n> >  \t\tnew Camera3RequestDescriptor(camera3Request->frame_number,\n> >  \t\t\t\t\t     camera3Request->num_output_buffers);\n> >\n> > -\tRequest *request =\n> > +\tstd::unique_ptr<Request> request =\n> >  \t\tcamera_->createRequest(reinterpret_cast<uint64_t>(descriptor));\n> > +\tif (!request) {\n> > +\t\tLOG(HAL, Error) << \"Failed to create request\";\n> > +\t\treturn -ENOMEM;\n> > +\t}\n> >\n> >  \tfor (unsigned int i = 0; i < descriptor->numBuffers; ++i) {\n> >  \t\tCameraStream *cameraStream =\n> > @@ -1422,7 +1426,6 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n> >  \t\tFrameBuffer *buffer = createFrameBuffer(*camera3Buffers[i].buffer);\n> >  \t\tif (!buffer) {\n> >  \t\t\tLOG(HAL, Error) << \"Failed to create buffer\";\n> > -\t\t\tdelete request;\n> >  \t\t\tdelete descriptor;\n> >  \t\t\treturn -ENOMEM;\n> >  \t\t}\n> > @@ -1434,14 +1437,16 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n> >  \t\trequest->addBuffer(stream, buffer);\n> >  \t}\n> >\n> > -\tint ret = camera_->queueRequest(request);\n> > +\tint ret = camera_->queueRequest(request.get());\n> >  \tif (ret) {\n> >  \t\tLOG(HAL, Error) << \"Failed to queue request\";\n> > -\t\tdelete request;\n> >  \t\tdelete descriptor;\n> >  \t\treturn ret;\n> >  \t}\n> >\n> > +\t/* The request will be deleted in the completion handler. */\n> > +\trequest.release();\n> > +\n> >  \treturn 0;\n> >  }\n> >\n> > @@ -1593,6 +1598,7 @@ void CameraDevice::requestComplete(Request *request)\n> >  \tcallbacks_->process_capture_result(callbacks_, &captureResult);\n> >\n> >  \tdelete descriptor;\n> > +\tdelete request;\n> >  }\n> >\n> >  std::string CameraDevice::logPrefix() const\n> > diff --git a/src/cam/capture.cpp b/src/cam/capture.cpp\n> > index 5510c009..8c8faa4b 100644\n> > --- a/src/cam/capture.cpp\n> > +++ b/src/cam/capture.cpp\n> > @@ -65,6 +65,8 @@ int Capture::run(const OptionsParser::Options &options)\n> >  \t\twriter_ = nullptr;\n> >  \t}\n> >\n> > +\trequests_.clear();\n> > +\n> >  \tdelete allocator;\n> >\n> >  \treturn ret;\n> > @@ -92,9 +94,8 @@ int Capture::capture(FrameBufferAllocator *allocator)\n> >  \t * example pushing a button. For now run all streams all the time.\n> >  \t */\n> >\n> > -\tstd::vector<Request *> requests;\n> >  \tfor (unsigned int i = 0; i < nbuffers; i++) {\n> > -\t\tRequest *request = camera_->createRequest();\n> > +\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> >  \t\tif (!request) {\n> >  \t\t\tstd::cerr << \"Can't create request\" << std::endl;\n> >  \t\t\treturn -ENOMEM;\n> > @@ -117,7 +118,7 @@ int Capture::capture(FrameBufferAllocator *allocator)\n> >  \t\t\t\twriter_->mapBuffer(buffer.get());\n> >  \t\t}\n> >\n> > -\t\trequests.push_back(request);\n> > +\t\trequests_.push_back(std::move(request));\n> >  \t}\n> >\n> >  \tret = camera_->start();\n> > @@ -126,8 +127,8 @@ int Capture::capture(FrameBufferAllocator *allocator)\n> >  \t\treturn ret;\n> >  \t}\n> >\n> > -\tfor (Request *request : requests) {\n> > -\t\tret = camera_->queueRequest(request);\n> > +\tfor (std::unique_ptr<Request> &request : requests_) {\n> > +\t\tret = camera_->queueRequest(request.get());\n> >  \t\tif (ret < 0) {\n> >  \t\t\tstd::cerr << \"Can't queue request\" << std::endl;\n> >  \t\t\tcamera_->stop();\n> > @@ -202,22 +203,6 @@ void Capture::requestComplete(Request *request)\n> >  \t\treturn;\n> >  \t}\n> >\n> > -\t/*\n> > -\t * Create a new request and populate it with one buffer for each\n> > -\t * stream.\n> > -\t */\n> > -\trequest = camera_->createRequest();\n> > -\tif (!request) {\n> > -\t\tstd::cerr << \"Can't create request\" << std::endl;\n> > -\t\treturn;\n> > -\t}\n> > -\n> > -\tfor (auto it = buffers.begin(); it != buffers.end(); ++it) {\n> > -\t\tconst Stream *stream = it->first;\n> > -\t\tFrameBuffer *buffer = it->second;\n> > -\n> > -\t\trequest->addBuffer(stream, buffer);\n> > -\t}\n> > -\n> > +\trequest->reuse(Request::ReuseBuffers);\n> >  \tcamera_->queueRequest(request);\n> >  }\n> > diff --git a/src/cam/capture.h b/src/cam/capture.h\n> > index 0aebdac9..45e5e8a9 100644\n> > --- a/src/cam/capture.h\n> > +++ b/src/cam/capture.h\n> > @@ -9,6 +9,7 @@\n> >\n> >  #include <memory>\n> >  #include <stdint.h>\n> > +#include <vector>\n> >\n> >  #include <libcamera/buffer.h>\n> >  #include <libcamera/camera.h>\n> > @@ -43,6 +44,8 @@ private:\n> >  \tEventLoop *loop_;\n> >  \tunsigned int captureCount_;\n> >  \tunsigned int captureLimit_;\n> > +\n> > +\tstd::vector<std::unique_ptr<libcamera::Request>> requests_;\n> >  };\n> >\n> >  #endif /* __CAM_CAPTURE_H__ */\n> > diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp\n> > index 1bfc2e2f..5001083a 100644\n> > --- a/src/gstreamer/gstlibcamerasrc.cpp\n> > +++ b/src/gstreamer/gstlibcamerasrc.cpp\n> > @@ -74,6 +74,8 @@ RequestWrap::~RequestWrap()\n> >  \t\tif (item.second)\n> >  \t\t\tgst_buffer_unref(item.second);\n> >  \t}\n> > +\n> > +\tdelete request_;\n> >  }\n> >\n> >  void RequestWrap::attachBuffer(GstBuffer *buffer)\n> > @@ -266,8 +268,8 @@ gst_libcamera_src_task_run(gpointer user_data)\n> >  \tGstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);\n> >  \tGstLibcameraSrcState *state = self->state;\n> >\n> > -\tRequest *request = state->cam_->createRequest();\n> > -\tauto wrap = std::make_unique<RequestWrap>(request);\n> > +\tstd::unique_ptr<Request> request = state->cam_->createRequest();\n> > +\tauto wrap = std::make_unique<RequestWrap>(request.get());\n> >  \tfor (GstPad *srcpad : state->srcpads_) {\n> >  \t\tGstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);\n> >  \t\tGstBuffer *buffer;\n> > @@ -280,8 +282,7 @@ gst_libcamera_src_task_run(gpointer user_data)\n> >  \t\t\t * RequestWrap does not take ownership, and we won't be\n> >  \t\t\t * queueing this one due to lack of buffers.\n> >  \t\t\t */\n> > -\t\t\tdelete request;\n> > -\t\t\trequest = nullptr;\n> > +\t\t\trequest.reset();\n> >  \t\t\tbreak;\n> >  \t\t}\n> >\n> > @@ -291,8 +292,11 @@ gst_libcamera_src_task_run(gpointer user_data)\n> >  \tif (request) {\n> >  \t\tGLibLocker lock(GST_OBJECT(self));\n> >  \t\tGST_TRACE_OBJECT(self, \"Requesting buffers\");\n> > -\t\tstate->cam_->queueRequest(request);\n> > +\t\tstate->cam_->queueRequest(request.get());\n> >  \t\tstate->requests_.push(std::move(wrap));\n> > +\n> > +\t\t/* The request will be deleted in the completion handler. */\n> > +\t\trequest.release();\n> >  \t}\n> >\n> >  \tGstFlowReturn ret = GST_FLOW_OK;\n> > diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> > index fb76077f..9590ab72 100644\n> > --- a/src/libcamera/camera.cpp\n> > +++ b/src/libcamera/camera.cpp\n> > @@ -847,21 +847,22 @@ int Camera::configure(CameraConfiguration *config)\n> >   * handler, and is completely opaque to libcamera.\n> >   *\n> >   * The ownership of the returned request is passed to the caller, which is\n> > - * responsible for either queueing the request or deleting it.\n> > + * responsible for deleting it. The request may be deleted in the completion\n> > + * handler, or reused after resetting its state with Request::reuse().\n> >   *\n> >   * \\context This function is \\threadsafe. It may only be called when the camera\n> >   * is in the Configured or Running state as defined in \\ref camera_operation.\n> >   *\n> >   * \\return A pointer to the newly created request, or nullptr on error\n> >   */\n> > -Request *Camera::createRequest(uint64_t cookie)\n> > +std::unique_ptr<Request> Camera::createRequest(uint64_t cookie)\n> >  {\n> >  \tint ret = p_->isAccessAllowed(Private::CameraConfigured,\n> >  \t\t\t\t      Private::CameraRunning);\n> >  \tif (ret < 0)\n> >  \t\treturn nullptr;\n> >\n> > -\treturn new Request(this, cookie);\n> > +\treturn std::make_unique<Request>(this, cookie);\n> >  }\n> >\n> >  /**\n> > @@ -877,9 +878,6 @@ Request *Camera::createRequest(uint64_t cookie)\n> >   * Once the request has been queued, the camera will notify its completion\n> >   * through the \\ref requestCompleted signal.\n> >   *\n> > - * Ownership of the request is transferred to the camera. It will be deleted\n> > - * automatically after it completes.\n> > - *\n> >   * \\context This function is \\threadsafe. It may only be called when the camera\n> >   * is in the Running state as defined in \\ref camera_operation.\n> >   *\n> > @@ -988,13 +986,11 @@ int Camera::stop()\n> >   * \\param[in] request The request that has completed\n> >   *\n> >   * This function is called by the pipeline handler to notify the camera that\n> > - * the request has completed. It emits the requestCompleted signal and deletes\n> > - * the request.\n> > + * the request has completed. It emits the requestCompleted signal.\n> >   */\n> >  void Camera::requestComplete(Request *request)\n> >  {\n> >  \trequestCompleted.emit(request);\n> > -\tdelete request;\n> >  }\n> >\n> >  } /* namespace libcamera */\n> > diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp\n> > index 60b30692..ae8b1660 100644\n> > --- a/src/libcamera/request.cpp\n> > +++ b/src/libcamera/request.cpp\n> > @@ -37,6 +37,15 @@ LOG_DEFINE_CATEGORY(Request)\n> >   * The request has been cancelled due to capture stop\n> >   */\n> >\n> > +/**\n> > + * \\enum Request::ReuseFlag\n> > + * Flags to control the behavior of Request::reuse()\n> > + * \\var Request::Default\n> > + * Don't reuse buffers\n> > + * \\var Request::ReuseBuffers\n> > + * Reuse the buffers that were previously added by addBuffer()\n> > + */\n> > +\n> >  /**\n> >   * \\typedef Request::BufferMap\n> >   * \\brief A map of Stream to FrameBuffer pointers\n> > @@ -85,6 +94,35 @@ Request::~Request()\n> >  \tdelete validator_;\n> >  }\n> >\n> > +/**\n> > + * \\brief Reset the request for reuse\n> > + * \\param[in] flags Indicate whether or not to reuse the buffers\n> > + *\n> > + * Reset the status and controls associated with the request, to allow it to\n> > + * be reused and requeued without destruction. This function shall be called\n> > + * prior to queueing the request to the camera, in lieu of constructing a new\n> > + * request. The application can reuse the buffers that were previously added\n> > + * to the request via addBuffer() by setting \\a flags to ReuseBuffers.\n> > + */\n> > +void Request::reuse(ReuseFlag flags)\n> > +{\n> > +\tpending_.clear();\n> > +\tif (flags & ReuseBuffers) {\n> > +\t\tfor (auto pair : bufferMap_) {\n> > +\t\t\tpair.second->request_ = this;\n> > +\t\t\tpending_.insert(pair.second);\n> > +\t\t}\n> > +\t} else {\n> > +\t\tbufferMap_.clear();\n> > +\t}\n> > +\n> > +\tstatus_ = RequestPending;\n> > +\tcancelled_ = false;\n> > +\n> > +\tcontrols_->clear();\n> > +\tmetadata_->clear();\n> > +}\n> > +\n> >  /**\n> >   * \\fn Request::controls()\n> >   * \\brief Retrieve the request's ControlList\n> > diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp\n> > index ecb9dd66..0cbdab9a 100644\n> > --- a/src/qcam/main_window.cpp\n> > +++ b/src/qcam/main_window.cpp\n> > @@ -367,7 +367,6 @@ void MainWindow::toggleCapture(bool start)\n> >  int MainWindow::startCapture()\n> >  {\n> >  \tStreamRoles roles = StreamKeyValueParser::roles(options_[OptStream]);\n> > -\tstd::vector<Request *> requests;\n> >  \tint ret;\n> >\n> >  \t/* Verify roles are supported. */\n> > @@ -486,7 +485,7 @@ int MainWindow::startCapture()\n> >  \twhile (!freeBuffers_[vfStream_].isEmpty()) {\n> >  \t\tFrameBuffer *buffer = freeBuffers_[vfStream_].dequeue();\n> >\n> > -\t\tRequest *request = camera_->createRequest();\n> > +\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> >  \t\tif (!request) {\n> >  \t\t\tqWarning() << \"Can't create request\";\n> >  \t\t\tret = -ENOMEM;\n> > @@ -499,7 +498,7 @@ int MainWindow::startCapture()\n> >  \t\t\tgoto error;\n> >  \t\t}\n> >\n> > -\t\trequests.push_back(request);\n> > +\t\trequests_.push_back(std::move(request));\n> >  \t}\n> >\n> >  \t/* Start the title timer and the camera. */\n> > @@ -518,8 +517,8 @@ int MainWindow::startCapture()\n> >  \tcamera_->requestCompleted.connect(this, &MainWindow::requestComplete);\n> >\n> >  \t/* Queue all requests. */\n> > -\tfor (Request *request : requests) {\n> > -\t\tret = camera_->queueRequest(request);\n> > +\tfor (std::unique_ptr<Request> &request : requests_) {\n> > +\t\tret = camera_->queueRequest(request.get());\n> >  \t\tif (ret < 0) {\n> >  \t\t\tqWarning() << \"Can't queue request\";\n> >  \t\t\tgoto error_disconnect;\n> > @@ -535,8 +534,7 @@ error_disconnect:\n> >  \tcamera_->stop();\n> >\n> >  error:\n> > -\tfor (Request *request : requests)\n> > -\t\tdelete request;\n> > +\trequests_.clear();\n> >\n> >  \tfor (auto &iter : mappedBuffers_) {\n> >  \t\tconst MappedBuffer &buffer = iter.second;\n> > @@ -580,6 +578,8 @@ void MainWindow::stopCapture()\n> >  \t}\n> >  \tmappedBuffers_.clear();\n> >\n> > +\trequests_.clear();\n> > +\n> >  \tdelete allocator_;\n> >\n> >  \tisCapturing_ = false;\n> > @@ -701,7 +701,7 @@ void MainWindow::requestComplete(Request *request)\n> >  \t */\n> >  \t{\n> >  \t\tQMutexLocker locker(&mutex_);\n> > -\t\tdoneQueue_.enqueue({ request->buffers(), request->metadata() });\n> > +\t\tdoneQueue_.enqueue(request);\n> >  \t}\n> >\n> >  \tQCoreApplication::postEvent(this, new CaptureEvent);\n> > @@ -714,8 +714,7 @@ void MainWindow::processCapture()\n> >  \t * if stopCapture() has been called while a CaptureEvent was posted but\n> >  \t * not processed yet. Return immediately in that case.\n> >  \t */\n> > -\tCaptureRequest request;\n> > -\n> > +\tRequest *request;\n> >  \t{\n> >  \t\tQMutexLocker locker(&mutex_);\n> >  \t\tif (doneQueue_.isEmpty())\n> > @@ -725,11 +724,15 @@ void MainWindow::processCapture()\n> >  \t}\n> >\n> >  \t/* Process buffers. */\n> > -\tif (request.buffers_.count(vfStream_))\n> > -\t\tprocessViewfinder(request.buffers_[vfStream_]);\n> > +\tif (request->buffers().count(vfStream_))\n> > +\t\tprocessViewfinder(request->buffers().at(vfStream_));\n> >\n> > -\tif (request.buffers_.count(rawStream_))\n> > -\t\tprocessRaw(request.buffers_[rawStream_], request.metadata_);\n> > +\tif (request->buffers().count(rawStream_))\n> > +\t\tprocessRaw(request->buffers().at(rawStream_), request->metadata());\n> > +\n> > +\trequest->reuse();\n> > +\tQMutexLocker locker(&mutex_);\n> > +\tfreeQueue_.enqueue(request);\n> >  }\n> >\n> >  void MainWindow::processViewfinder(FrameBuffer *buffer)\n> > @@ -754,10 +757,13 @@ void MainWindow::processViewfinder(FrameBuffer *buffer)\n> >\n> >  void MainWindow::queueRequest(FrameBuffer *buffer)\n> >  {\n> > -\tRequest *request = camera_->createRequest();\n> > -\tif (!request) {\n> > -\t\tqWarning() << \"Can't create request\";\n> > -\t\treturn;\n> > +\tRequest *request;\n> > +\t{\n> > +\t\tQMutexLocker locker(&mutex_);\n> > +\t\tif (freeQueue_.isEmpty())\n> > +\t\t\treturn;\n> > +\n> > +\t\trequest = freeQueue_.dequeue();\n> >  \t}\n> >\n> >  \trequest->addBuffer(vfStream_, buffer);\n> > diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h\n> > index 5c61a4df..64bcfebc 100644\n> > --- a/src/qcam/main_window.h\n> > +++ b/src/qcam/main_window.h\n> > @@ -8,6 +8,7 @@\n> >  #define __QCAM_MAIN_WINDOW_H__\n> >\n> >  #include <memory>\n> > +#include <vector>\n> >\n> >  #include <QElapsedTimer>\n> >  #include <QIcon>\n> > @@ -22,6 +23,7 @@\n> >  #include <libcamera/camera_manager.h>\n> >  #include <libcamera/controls.h>\n> >  #include <libcamera/framebuffer_allocator.h>\n> > +#include <libcamera/request.h>\n> >  #include <libcamera/stream.h>\n> >\n> >  #include \"../cam/stream_options.h\"\n> > @@ -41,23 +43,6 @@ enum {\n> >  \tOptStream = 's',\n> >  };\n> >\n> > -class CaptureRequest\n> > -{\n> > -public:\n> > -\tCaptureRequest()\n> > -\t{\n> > -\t}\n> > -\n> > -\tCaptureRequest(const Request::BufferMap &buffers,\n> > -\t\t       const ControlList &metadata)\n> > -\t\t: buffers_(buffers), metadata_(metadata)\n> > -\t{\n> > -\t}\n> > -\n> > -\tRequest::BufferMap buffers_;\n> > -\tControlList metadata_;\n> > -};\n> > -\n> >  class MainWindow : public QMainWindow\n> >  {\n> >  \tQ_OBJECT\n> > @@ -128,13 +113,16 @@ private:\n> >  \tStream *vfStream_;\n> >  \tStream *rawStream_;\n> >  \tstd::map<const Stream *, QQueue<FrameBuffer *>> freeBuffers_;\n> > -\tQQueue<CaptureRequest> doneQueue_;\n> > -\tQMutex mutex_; /* Protects freeBuffers_ and doneQueue_ */\n> > +\tQQueue<Request *> doneQueue_;\n> > +\tQQueue<Request *> freeQueue_;\n> > +\tQMutex mutex_; /* Protects freeBuffers_, doneQueue_, and freeQueue_ */\n> >\n> >  \tuint64_t lastBufferTime_;\n> >  \tQElapsedTimer frameRateInterval_;\n> >  \tuint32_t previousFrames_;\n> >  \tuint32_t framesCaptured_;\n> > +\n> > +\tstd::vector<std::unique_ptr<Request>> requests_;\n> >  };\n> >\n> >  #endif /* __QCAM_MAIN_WINDOW__ */\n> > diff --git a/src/v4l2/v4l2_camera.cpp b/src/v4l2/v4l2_camera.cpp\n> > index 3565f369..35d3beda 100644\n> > --- a/src/v4l2/v4l2_camera.cpp\n> > +++ b/src/v4l2/v4l2_camera.cpp\n> > @@ -49,6 +49,8 @@ int V4L2Camera::open(StreamConfiguration *streamConfig)\n> >\n> >  void V4L2Camera::close()\n> >  {\n> > +\trequestPool_.clear();\n> > +\n> >  \tdelete bufferAllocator_;\n> >  \tbufferAllocator_ = nullptr;\n> >\n> > @@ -96,6 +98,7 @@ void V4L2Camera::requestComplete(Request *request)\n> >  \tif (ret != sizeof(data))\n> >  \t\tLOG(V4L2Compat, Error) << \"Failed to signal eventfd POLLIN\";\n> >\n> > +\trequest->reuse();\n> >  \t{\n> >  \t\tMutexLocker locker(bufferMutex_);\n> >  \t\tbufferAvailableCount_++;\n> > @@ -154,16 +157,30 @@ int V4L2Camera::validateConfiguration(const PixelFormat &pixelFormat,\n> >  \treturn 0;\n> >  }\n> >\n> > -int V4L2Camera::allocBuffers([[maybe_unused]] unsigned int count)\n> > +int V4L2Camera::allocBuffers(unsigned int count)\n> >  {\n> >  \tStream *stream = config_->at(0).stream();\n> >\n> > -\treturn bufferAllocator_->allocate(stream);\n> > +\tint ret = bufferAllocator_->allocate(stream);\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\tfor (unsigned int i = 0; i < count; i++) {\n> > +\t\tstd::unique_ptr<Request> request = camera_->createRequest(i);\n> > +\t\tif (!request) {\n> > +\t\t\trequestPool_.clear();\n> > +\t\t\treturn -ENOMEM;\n> > +\t\t}\n> > +\t\trequestPool_.push_back(std::move(request));\n> > +\t}\n> > +\n> > +\treturn ret;\n> >  }\n> >\n> >  void V4L2Camera::freeBuffers()\n> >  {\n> >  \tpendingRequests_.clear();\n> > +\trequestPool_.clear();\n> >\n> >  \tStream *stream = config_->at(0).stream();\n> >  \tbufferAllocator_->free(stream);\n> > @@ -192,9 +209,9 @@ int V4L2Camera::streamOn()\n> >\n> >  \tisRunning_ = true;\n> >\n> > -\tfor (std::unique_ptr<Request> &req : pendingRequests_) {\n> > +\tfor (Request *req : pendingRequests_) {\n> >  \t\t/* \\todo What should we do if this returns -EINVAL? */\n> > -\t\tret = camera_->queueRequest(req.release());\n> > +\t\tret = camera_->queueRequest(req);\n> >  \t\tif (ret < 0)\n> >  \t\t\treturn ret == -EACCES ? -EBUSY : ret;\n> >  \t}\n> > @@ -206,8 +223,12 @@ int V4L2Camera::streamOn()\n> >\n> >  int V4L2Camera::streamOff()\n> >  {\n> > -\tif (!isRunning_)\n> > +\tif (!isRunning_) {\n> > +\t\tfor (std::unique_ptr<Request> &req : requestPool_)\n> > +\t\t\treq->reuse();\n> > +\n> >  \t\treturn 0;\n> > +\t}\n> >\n> >  \tpendingRequests_.clear();\n> >\n> > @@ -226,12 +247,11 @@ int V4L2Camera::streamOff()\n> >\n> >  int V4L2Camera::qbuf(unsigned int index)\n> >  {\n> > -\tstd::unique_ptr<Request> request =\n> > -\t\tstd::unique_ptr<Request>(camera_->createRequest(index));\n> > -\tif (!request) {\n> > -\t\tLOG(V4L2Compat, Error) << \"Can't create request\";\n> > -\t\treturn -ENOMEM;\n> > +\tif (index >= requestPool_.size()) {\n> > +\t\tLOG(V4L2Compat, Error) << \"Invalid index\";\n> > +\t\treturn -EINVAL;\n> >  \t}\n> > +\tRequest *request = requestPool_[index].get();\n> >\n> >  \tStream *stream = config_->at(0).stream();\n> >  \tFrameBuffer *buffer = bufferAllocator_->buffers(stream)[index].get();\n> > @@ -242,11 +262,11 @@ int V4L2Camera::qbuf(unsigned int index)\n> >  \t}\n> >\n> >  \tif (!isRunning_) {\n> > -\t\tpendingRequests_.push_back(std::move(request));\n> > +\t\tpendingRequests_.push_back(request);\n> >  \t\treturn 0;\n> >  \t}\n> >\n> > -\tret = camera_->queueRequest(request.release());\n> > +\tret = camera_->queueRequest(request);\n> >  \tif (ret < 0) {\n> >  \t\tLOG(V4L2Compat, Error) << \"Can't queue request\";\n> >  \t\treturn ret == -EACCES ? -EBUSY : ret;\n> > diff --git a/src/v4l2/v4l2_camera.h b/src/v4l2/v4l2_camera.h\n> > index 1fc5ebef..a6c35a2e 100644\n> > --- a/src/v4l2/v4l2_camera.h\n> > +++ b/src/v4l2/v4l2_camera.h\n> > @@ -76,7 +76,9 @@ private:\n> >  \tstd::mutex bufferLock_;\n> >  \tFrameBufferAllocator *bufferAllocator_;\n> >\n> > -\tstd::deque<std::unique_ptr<Request>> pendingRequests_;\n> > +\tstd::vector<std::unique_ptr<Request>> requestPool_;\n> > +\n> > +\tstd::deque<Request *> pendingRequests_;\n> >  \tstd::deque<std::unique_ptr<Buffer>> completedBuffers_;\n> >\n> >  \tint efd_;\n> > diff --git a/test/camera/buffer_import.cpp b/test/camera/buffer_import.cpp\n> > index 64e96264..72ce7b79 100644\n> > --- a/test/camera/buffer_import.cpp\n> > +++ b/test/camera/buffer_import.cpp\n> > @@ -58,7 +58,7 @@ protected:\n> >  \t\tconst Stream *stream = buffers.begin()->first;\n> >  \t\tFrameBuffer *buffer = buffers.begin()->second;\n> >\n> > -\t\trequest = camera_->createRequest();\n> > +\t\trequest->reuse();\n> >  \t\trequest->addBuffer(stream, buffer);\n> >  \t\tcamera_->queueRequest(request);\n> >  \t}\n> > @@ -98,9 +98,8 @@ protected:\n> >  \t\tif (ret != TestPass)\n> >  \t\t\treturn ret;\n> >\n> > -\t\tstd::vector<Request *> requests;\n> >  \t\tfor (const std::unique_ptr<FrameBuffer> &buffer : source.buffers()) {\n> > -\t\t\tRequest *request = camera_->createRequest();\n> > +\t\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> >  \t\t\tif (!request) {\n> >  \t\t\t\tstd::cout << \"Failed to create request\" << std::endl;\n> >  \t\t\t\treturn TestFail;\n> > @@ -111,7 +110,7 @@ protected:\n> >  \t\t\t\treturn TestFail;\n> >  \t\t\t}\n> >\n> > -\t\t\trequests.push_back(request);\n> > +\t\t\trequests_.push_back(std::move(request));\n> >  \t\t}\n> >\n> >  \t\tcompleteRequestsCount_ = 0;\n> > @@ -125,8 +124,8 @@ protected:\n> >  \t\t\treturn TestFail;\n> >  \t\t}\n> >\n> > -\t\tfor (Request *request : requests) {\n> > -\t\t\tif (camera_->queueRequest(request)) {\n> > +\t\tfor (std::unique_ptr<Request> &request : requests_) {\n> > +\t\t\tif (camera_->queueRequest(request.get())) {\n> >  \t\t\t\tstd::cout << \"Failed to queue request\" << std::endl;\n> >  \t\t\t\treturn TestFail;\n> >  \t\t\t}\n> > @@ -160,6 +159,8 @@ protected:\n> >  \t}\n> >\n> >  private:\n> > +\tstd::vector<std::unique_ptr<Request>> requests_;\n> > +\n> >  \tunsigned int completeBuffersCount_;\n> >  \tunsigned int completeRequestsCount_;\n> >  \tstd::unique_ptr<CameraConfiguration> config_;\n> > diff --git a/test/camera/capture.cpp b/test/camera/capture.cpp\n> > index 51bbd258..c0770801 100644\n> > --- a/test/camera/capture.cpp\n> > +++ b/test/camera/capture.cpp\n> > @@ -52,7 +52,7 @@ protected:\n> >  \t\tconst Stream *stream = buffers.begin()->first;\n> >  \t\tFrameBuffer *buffer = buffers.begin()->second;\n> >\n> > -\t\trequest = camera_->createRequest();\n> > +\t\trequest->reuse();\n> >  \t\trequest->addBuffer(stream, buffer);\n> >  \t\tcamera_->queueRequest(request);\n> >  \t}\n> > @@ -98,9 +98,8 @@ protected:\n> >  \t\tif (ret < 0)\n> >  \t\t\treturn TestFail;\n> >\n> > -\t\tstd::vector<Request *> requests;\n> >  \t\tfor (const std::unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) {\n> > -\t\t\tRequest *request = camera_->createRequest();\n> > +\t\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> >  \t\t\tif (!request) {\n> >  \t\t\t\tcout << \"Failed to create request\" << endl;\n> >  \t\t\t\treturn TestFail;\n> > @@ -111,7 +110,7 @@ protected:\n> >  \t\t\t\treturn TestFail;\n> >  \t\t\t}\n> >\n> > -\t\t\trequests.push_back(request);\n> > +\t\t\trequests_.push_back(std::move(request));\n> >  \t\t}\n> >\n> >  \t\tcompleteRequestsCount_ = 0;\n> > @@ -125,8 +124,8 @@ protected:\n> >  \t\t\treturn TestFail;\n> >  \t\t}\n> >\n> > -\t\tfor (Request *request : requests) {\n> > -\t\t\tif (camera_->queueRequest(request)) {\n> > +\t\tfor (std::unique_ptr<Request> &request : requests_) {\n> > +\t\t\tif (camera_->queueRequest(request.get())) {\n> >  \t\t\t\tcout << \"Failed to queue request\" << endl;\n> >  \t\t\t\treturn TestFail;\n> >  \t\t\t}\n> > @@ -161,6 +160,8 @@ protected:\n> >  \t\treturn TestPass;\n> >  \t}\n> >\n> > +\tstd::vector<std::unique_ptr<Request>> requests_;\n> > +\n> >  \tstd::unique_ptr<CameraConfiguration> config_;\n> >  \tFrameBufferAllocator *allocator_;\n> >  };\n> > diff --git a/test/camera/statemachine.cpp b/test/camera/statemachine.cpp\n> > index 28faeb91..e63ab298 100644\n> > --- a/test/camera/statemachine.cpp\n> > +++ b/test/camera/statemachine.cpp\n> > @@ -101,13 +101,10 @@ protected:\n> >  \t\t\treturn TestFail;\n> >\n> >  \t\t/* Test operations which should pass. */\n> > -\t\tRequest *request2 = camera_->createRequest();\n> > +\t\tstd::unique_ptr<Request> request2 = camera_->createRequest();\n> >  \t\tif (!request2)\n> >  \t\t\treturn TestFail;\n> >\n> > -\t\t/* Never handed to hardware so need to manually delete it. */\n> > -\t\tdelete request2;\n> > -\n> >  \t\t/* Test valid state transitions, end in Running state. */\n> >  \t\tif (camera_->release())\n> >  \t\t\treturn TestFail;\n> > @@ -146,7 +143,7 @@ protected:\n> >  \t\t\treturn TestFail;\n> >\n> >  \t\t/* Test operations which should pass. */\n> > -\t\tRequest *request = camera_->createRequest();\n> > +\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> >  \t\tif (!request)\n> >  \t\t\treturn TestFail;\n> >\n> > @@ -154,7 +151,7 @@ protected:\n> >  \t\tif (request->addBuffer(stream, allocator_->buffers(stream)[0].get()))\n> >  \t\t\treturn TestFail;\n> >\n> > -\t\tif (camera_->queueRequest(request))\n> > +\t\tif (camera_->queueRequest(request.get()))\n> >  \t\t\treturn TestFail;\n> >\n> >  \t\t/* Test valid state transitions, end in Available state. */","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 7D84FBEEDF\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu,  8 Oct 2020 02:46:24 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id F33B660455;\n\tThu,  8 Oct 2020 04:46:23 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 5727060363\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu,  8 Oct 2020 04:46:23 +0200 (CEST)","from pendragon.ideasonboard.com (62-78-145-57.bb.dnainternet.fi\n\t[62.78.145.57])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 9D0D959E;\n\tThu,  8 Oct 2020 04:46:22 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"ripP+B5x\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1602125182;\n\tbh=efBdFDCkO6X0NsC76dCel8HXuJetT2kvM1Jy6m0UbeU=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=ripP+B5xgUET9t67rzaG/9VZpAq7m3+5YcHR047pP8Yi/uQN4zKcQS4atLM1gBl3n\n\tt0bbozDv1Qaz25+Plf3AZJJxb3F51MtFxMzsELXkkDXsZ15dadpLUINDe88bBFjOQ9\n\tWvMqhuAgKUFhBtPNVSezu6W4SKAOzmx5vLEVfhmE=","Date":"Thu, 8 Oct 2020 05:45:40 +0300","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Jacopo Mondi <jacopo@jmondi.org>","Message-ID":"<20201008024540.GT3937@pendragon.ideasonboard.com>","References":"<20201007073418.512656-1-paul.elder@ideasonboard.com>\n\t<20201007094331.2dl4xwdeqzf4isix@uno.localdomain>","MIME-Version":"1.0","Content-Disposition":"inline","In-Reply-To":"<20201007094331.2dl4xwdeqzf4isix@uno.localdomain>","Subject":"Re: [libcamera-devel] [PATCH v6] libcamera, android, cam, gstreamer,\n\tqcam, v4l2: Reuse Request","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Cc":"libcamera-devel@lists.libcamera.org","Content-Type":"text/plain; charset=\"us-ascii\"","Content-Transfer-Encoding":"7bit","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":13098,"web_url":"https://patchwork.libcamera.org/comment/13098/","msgid":"<20201008062920.GI45948@pyrite.rasen.tech>","date":"2020-10-08T06:29:20","subject":"Re: [libcamera-devel] [PATCH v6] libcamera, android, cam, gstreamer,\n\tqcam, v4l2: Reuse Request","submitter":{"id":17,"url":"https://patchwork.libcamera.org/api/people/17/","name":"Paul Elder","email":"paul.elder@ideasonboard.com"},"content":"Hi Jacopo,\n\nOn Wed, Oct 07, 2020 at 11:43:31AM +0200, Jacopo Mondi wrote:\n> Hi Paul,\n> \n> On Wed, Oct 07, 2020 at 04:34:18PM +0900, Paul Elder wrote:\n> > Allow reuse of the Request object by implementing reuse(). This means\n> > the applications now have the responsibility of freeing the Request\n> > objects, so make all libcamera users (cam, qcam, v4l2-compat, gstreamer,\n> > android) do so.\n> \n> Maybe not a question to ask at v6, I've gone through the previous\n> iterations and I see the proposed API has been discussed already, so\n> feel free to ignore, but: I'm not sure I like the flags passed to\n> reuse() :)\n> \n> To be more precise, I don't see much much value in ReuseBuffers.\n> \n> How usual is it that the same [Stream|buffer] pair is repeated ? I\n> understand we might want to capture from the same set of Streams, but\n> application would more likely cycle buffers, don't they ?\n\nIt depends on the application. In cam we have:\n\n-       /*\n-        * Create a new request and populate it with one buffer for each\n-        * stream.\n-        */\n-       request = camera_->createRequest();\n-       if (!request) {\n-               std::cerr << \"Can't create request\" << std::endl;\n-               return;\n-       }\n-\n-       for (auto it = buffers.begin(); it != buffers.end(); ++it) {\n-               const Stream *stream = it->first;\n-               FrameBuffer *buffer = it->second;\n-\n-               request->addBuffer(stream, buffer);\n-       }\n-\n+       request->reuse(Request::ReuseBuffers);\n\nWithout ReuseBuffers, we'd have:\n\n-       /*\n-        * Create a new request and populate it with one buffer for each\n-        * stream.\n-        */\n-       request = camera_->createRequest();\n-       if (!request) {\n-               std::cerr << \"Can't create request\" << std::endl;\n-               return;\n-       }\n-\n+       request->reuse();\n        for (auto it = buffers.begin(); it != buffers.end(); ++it) {\n                const Stream *stream = it->first;\n                FrameBuffer *buffer = it->second;\n \n                request->addBuffer(stream, buffer);\n        }\n\nWhich still isn't that bad imo but somebody requested ReuseBuffers.\n\n> I understand the ReuseBuffers flag might be used to model a\n> 'repeating' request mechanism, but I don't think our API is currently\n> ready to support such a feature. All our requests are one-shot, they\n> get queued, notified when completed, and need to be re-queued. There's\n> nothing like a mechanism that let's you create one request, queue it\n> once, and have it running until you don't stop you (without the need\n> for a requeue. For reference [1]). Implementing something like this\n> will need some more thinking, in example how to make it possible for a\n> repeating request to cycle on a set of buffers automatically.\n> \n> I don't think we should try to emulate a repeating mechanism by\n> allowing application to re-use the same stream-buffer pair. It has a\n> limited use (in my understanding) and will confuse application as they\n> need anyway to re-queue it.\n\nAh, I see. I guess this is more of convenience for the specific use case\nof cam, and whatever applications that will have a similar demand.\n\n> Don't get me wrong, I think moving ownership of Request to application\n> is correct and more clear, but I don't see much use for the flag [*]\n\nI think the flag is for future-proofing... if we want to add any other\noptions to resetting the reuqest.\n\n> I would prefer if we keep thinking of our request as one shot, so they\n> could either be reset everytime, and application will have to\n> addBuffers() again as they will anyway have to cycle buffers.\n\nI think we still preserve that. The application can use the request as\none-shot if it wants to, or it can reset it everytime. We're just\nproviding an option to automatically re-addBuffers().\n\n\nPaul\n\n> [*] This makes me unconfortable for another reason. I understand we\n> might want flags for, say, Controls handling and other possible tuning\n> (have you though about other possible flags ?). I don't think the\n> output buffer handling lives in the same category as low level control\n> of the Request, it's a primary Request feature and should not be expressed\n> with flags as in example \"Use template XYZ for the Request's controls\"\n> \n> [1] https://medium.com/androiddevelopers/understanding-android-camera-capture-sessions-and-requests-4e54d9150295\n> \n> >\n> > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> >\n> > ---\n> > Changes in v6:\n> > - fix doxygen\n> > - rename enum ReuseBuffer to ReuseFlag, and change to flags\n> > - move request->reuse() in v4l2-compat from qbuf time to completion\n> >   time\n> >   - streamOff when the stream is already off needs to also\n> >     request->reuse(), so do that too\n> > - update documentation\n> >\n> > Changes in v5:\n> > - rename reset() to reuse()\n> > - make reuse()'s reuseBuffers parameter into enum instead of bool\n> > - fix qcam qt assertion error on the first queueRequest, where\n> >   freeQueue_ is empty\n> >\n> > Changes in v4:\n> > - no more implicit calls of anything that we added in this patch\n> > - make reset() take a reuseBuffers boolean parameters\n> >   - use transient request - delete request\n> >   - reuse request, reset buffers - reset()\n> >   - reuse request, reuse buffesr - reset(true)\n> > - update apps and tests and documentation accordingly\n> >\n> > Changes in v3:\n> > - reset() is called in Camera::queueRequest()\n> > - apps that use transient request (android, gstreamer) delete the\n> >   request at the end of the completion handler\n> > - apps that want to reuse the buffers (cam) use reAddBuffers()\n> > - apps that need to change the buffers (qcam, v4l2) use clearBuffers()\n> > - update the documentation accordingly\n> >\n> > Changes in v2:\n> > - clear controls_ and metadata_ and validator_ in Request::reset()\n> > - use unique_ptr on application side, prior to queueRequest, and use\n> >   regular pointer for completion handler\n> > - make qcam's reuse request nicer\n> > - update Camera::queueRequest() and Camera::createRequest() documentation\n> > - add documentation for Request::reset()\n> > - make v4l2-compat reuse request\n> > - make gstreamer and android use the new createRequest API, though they\n> >   do not actually reuse the requests\n> > ---\n> >  include/libcamera/camera.h        |  2 +-\n> >  include/libcamera/request.h       |  7 +++++\n> >  src/android/camera_device.cpp     | 14 +++++++---\n> >  src/cam/capture.cpp               | 29 +++++---------------\n> >  src/cam/capture.h                 |  3 +++\n> >  src/gstreamer/gstlibcamerasrc.cpp | 14 ++++++----\n> >  src/libcamera/camera.cpp          | 14 ++++------\n> >  src/libcamera/request.cpp         | 38 ++++++++++++++++++++++++++\n> >  src/qcam/main_window.cpp          | 42 ++++++++++++++++-------------\n> >  src/qcam/main_window.h            | 26 +++++-------------\n> >  src/v4l2/v4l2_camera.cpp          | 44 ++++++++++++++++++++++---------\n> >  src/v4l2/v4l2_camera.h            |  4 ++-\n> >  test/camera/buffer_import.cpp     | 13 ++++-----\n> >  test/camera/capture.cpp           | 13 ++++-----\n> >  test/camera/statemachine.cpp      |  9 +++----\n> >  15 files changed, 163 insertions(+), 109 deletions(-)\n> >\n> > diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> > index a2ee4e7e..79ff8d6b 100644\n> > --- a/include/libcamera/camera.h\n> > +++ b/include/libcamera/camera.h\n> > @@ -96,7 +96,7 @@ public:\n> >  \tstd::unique_ptr<CameraConfiguration> generateConfiguration(const StreamRoles &roles = {});\n> >  \tint configure(CameraConfiguration *config);\n> >\n> > -\tRequest *createRequest(uint64_t cookie = 0);\n> > +\tstd::unique_ptr<Request> createRequest(uint64_t cookie = 0);\n> >  \tint queueRequest(Request *request);\n> >\n> >  \tint start();\n> > diff --git a/include/libcamera/request.h b/include/libcamera/request.h\n> > index 5976ac50..655b1324 100644\n> > --- a/include/libcamera/request.h\n> > +++ b/include/libcamera/request.h\n> > @@ -31,6 +31,11 @@ public:\n> >  \t\tRequestCancelled,\n> >  \t};\n> >\n> > +\tenum ReuseFlag {\n> > +\t\tDefault = 0,\n> > +\t\tReuseBuffers = (1 << 0),\n> > +\t};\n> > +\n> >  \tusing BufferMap = std::map<const Stream *, FrameBuffer *>;\n> >\n> >  \tRequest(Camera *camera, uint64_t cookie = 0);\n> > @@ -38,6 +43,8 @@ public:\n> >  \tRequest &operator=(const Request &) = delete;\n> >  \t~Request();\n> >\n> > +\tvoid reuse(ReuseFlag flags = Default);\n> > +\n> >  \tControlList &controls() { return *controls_; }\n> >  \tControlList &metadata() { return *metadata_; }\n> >  \tconst BufferMap &buffers() const { return bufferMap_; }\n> > diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp\n> > index 751699cd..052c9292 100644\n> > --- a/src/android/camera_device.cpp\n> > +++ b/src/android/camera_device.cpp\n> > @@ -1395,8 +1395,12 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n> >  \t\tnew Camera3RequestDescriptor(camera3Request->frame_number,\n> >  \t\t\t\t\t     camera3Request->num_output_buffers);\n> >\n> > -\tRequest *request =\n> > +\tstd::unique_ptr<Request> request =\n> >  \t\tcamera_->createRequest(reinterpret_cast<uint64_t>(descriptor));\n> > +\tif (!request) {\n> > +\t\tLOG(HAL, Error) << \"Failed to create request\";\n> > +\t\treturn -ENOMEM;\n> > +\t}\n> >\n> >  \tfor (unsigned int i = 0; i < descriptor->numBuffers; ++i) {\n> >  \t\tCameraStream *cameraStream =\n> > @@ -1422,7 +1426,6 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n> >  \t\tFrameBuffer *buffer = createFrameBuffer(*camera3Buffers[i].buffer);\n> >  \t\tif (!buffer) {\n> >  \t\t\tLOG(HAL, Error) << \"Failed to create buffer\";\n> > -\t\t\tdelete request;\n> >  \t\t\tdelete descriptor;\n> >  \t\t\treturn -ENOMEM;\n> >  \t\t}\n> > @@ -1434,14 +1437,16 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n> >  \t\trequest->addBuffer(stream, buffer);\n> >  \t}\n> >\n> > -\tint ret = camera_->queueRequest(request);\n> > +\tint ret = camera_->queueRequest(request.get());\n> >  \tif (ret) {\n> >  \t\tLOG(HAL, Error) << \"Failed to queue request\";\n> > -\t\tdelete request;\n> >  \t\tdelete descriptor;\n> >  \t\treturn ret;\n> >  \t}\n> >\n> > +\t/* The request will be deleted in the completion handler. */\n> > +\trequest.release();\n> > +\n> >  \treturn 0;\n> >  }\n> >\n> > @@ -1593,6 +1598,7 @@ void CameraDevice::requestComplete(Request *request)\n> >  \tcallbacks_->process_capture_result(callbacks_, &captureResult);\n> >\n> >  \tdelete descriptor;\n> > +\tdelete request;\n> >  }\n> >\n> >  std::string CameraDevice::logPrefix() const\n> > diff --git a/src/cam/capture.cpp b/src/cam/capture.cpp\n> > index 5510c009..8c8faa4b 100644\n> > --- a/src/cam/capture.cpp\n> > +++ b/src/cam/capture.cpp\n> > @@ -65,6 +65,8 @@ int Capture::run(const OptionsParser::Options &options)\n> >  \t\twriter_ = nullptr;\n> >  \t}\n> >\n> > +\trequests_.clear();\n> > +\n> >  \tdelete allocator;\n> >\n> >  \treturn ret;\n> > @@ -92,9 +94,8 @@ int Capture::capture(FrameBufferAllocator *allocator)\n> >  \t * example pushing a button. For now run all streams all the time.\n> >  \t */\n> >\n> > -\tstd::vector<Request *> requests;\n> >  \tfor (unsigned int i = 0; i < nbuffers; i++) {\n> > -\t\tRequest *request = camera_->createRequest();\n> > +\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> >  \t\tif (!request) {\n> >  \t\t\tstd::cerr << \"Can't create request\" << std::endl;\n> >  \t\t\treturn -ENOMEM;\n> > @@ -117,7 +118,7 @@ int Capture::capture(FrameBufferAllocator *allocator)\n> >  \t\t\t\twriter_->mapBuffer(buffer.get());\n> >  \t\t}\n> >\n> > -\t\trequests.push_back(request);\n> > +\t\trequests_.push_back(std::move(request));\n> >  \t}\n> >\n> >  \tret = camera_->start();\n> > @@ -126,8 +127,8 @@ int Capture::capture(FrameBufferAllocator *allocator)\n> >  \t\treturn ret;\n> >  \t}\n> >\n> > -\tfor (Request *request : requests) {\n> > -\t\tret = camera_->queueRequest(request);\n> > +\tfor (std::unique_ptr<Request> &request : requests_) {\n> > +\t\tret = camera_->queueRequest(request.get());\n> >  \t\tif (ret < 0) {\n> >  \t\t\tstd::cerr << \"Can't queue request\" << std::endl;\n> >  \t\t\tcamera_->stop();\n> > @@ -202,22 +203,6 @@ void Capture::requestComplete(Request *request)\n> >  \t\treturn;\n> >  \t}\n> >\n> > -\t/*\n> > -\t * Create a new request and populate it with one buffer for each\n> > -\t * stream.\n> > -\t */\n> > -\trequest = camera_->createRequest();\n> > -\tif (!request) {\n> > -\t\tstd::cerr << \"Can't create request\" << std::endl;\n> > -\t\treturn;\n> > -\t}\n> > -\n> > -\tfor (auto it = buffers.begin(); it != buffers.end(); ++it) {\n> > -\t\tconst Stream *stream = it->first;\n> > -\t\tFrameBuffer *buffer = it->second;\n> > -\n> > -\t\trequest->addBuffer(stream, buffer);\n> > -\t}\n> > -\n> > +\trequest->reuse(Request::ReuseBuffers);\n> >  \tcamera_->queueRequest(request);\n> >  }\n> > diff --git a/src/cam/capture.h b/src/cam/capture.h\n> > index 0aebdac9..45e5e8a9 100644\n> > --- a/src/cam/capture.h\n> > +++ b/src/cam/capture.h\n> > @@ -9,6 +9,7 @@\n> >\n> >  #include <memory>\n> >  #include <stdint.h>\n> > +#include <vector>\n> >\n> >  #include <libcamera/buffer.h>\n> >  #include <libcamera/camera.h>\n> > @@ -43,6 +44,8 @@ private:\n> >  \tEventLoop *loop_;\n> >  \tunsigned int captureCount_;\n> >  \tunsigned int captureLimit_;\n> > +\n> > +\tstd::vector<std::unique_ptr<libcamera::Request>> requests_;\n> >  };\n> >\n> >  #endif /* __CAM_CAPTURE_H__ */\n> > diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp\n> > index 1bfc2e2f..5001083a 100644\n> > --- a/src/gstreamer/gstlibcamerasrc.cpp\n> > +++ b/src/gstreamer/gstlibcamerasrc.cpp\n> > @@ -74,6 +74,8 @@ RequestWrap::~RequestWrap()\n> >  \t\tif (item.second)\n> >  \t\t\tgst_buffer_unref(item.second);\n> >  \t}\n> > +\n> > +\tdelete request_;\n> >  }\n> >\n> >  void RequestWrap::attachBuffer(GstBuffer *buffer)\n> > @@ -266,8 +268,8 @@ gst_libcamera_src_task_run(gpointer user_data)\n> >  \tGstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);\n> >  \tGstLibcameraSrcState *state = self->state;\n> >\n> > -\tRequest *request = state->cam_->createRequest();\n> > -\tauto wrap = std::make_unique<RequestWrap>(request);\n> > +\tstd::unique_ptr<Request> request = state->cam_->createRequest();\n> > +\tauto wrap = std::make_unique<RequestWrap>(request.get());\n> >  \tfor (GstPad *srcpad : state->srcpads_) {\n> >  \t\tGstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);\n> >  \t\tGstBuffer *buffer;\n> > @@ -280,8 +282,7 @@ gst_libcamera_src_task_run(gpointer user_data)\n> >  \t\t\t * RequestWrap does not take ownership, and we won't be\n> >  \t\t\t * queueing this one due to lack of buffers.\n> >  \t\t\t */\n> > -\t\t\tdelete request;\n> > -\t\t\trequest = nullptr;\n> > +\t\t\trequest.reset();\n> >  \t\t\tbreak;\n> >  \t\t}\n> >\n> > @@ -291,8 +292,11 @@ gst_libcamera_src_task_run(gpointer user_data)\n> >  \tif (request) {\n> >  \t\tGLibLocker lock(GST_OBJECT(self));\n> >  \t\tGST_TRACE_OBJECT(self, \"Requesting buffers\");\n> > -\t\tstate->cam_->queueRequest(request);\n> > +\t\tstate->cam_->queueRequest(request.get());\n> >  \t\tstate->requests_.push(std::move(wrap));\n> > +\n> > +\t\t/* The request will be deleted in the completion handler. */\n> > +\t\trequest.release();\n> >  \t}\n> >\n> >  \tGstFlowReturn ret = GST_FLOW_OK;\n> > diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> > index fb76077f..9590ab72 100644\n> > --- a/src/libcamera/camera.cpp\n> > +++ b/src/libcamera/camera.cpp\n> > @@ -847,21 +847,22 @@ int Camera::configure(CameraConfiguration *config)\n> >   * handler, and is completely opaque to libcamera.\n> >   *\n> >   * The ownership of the returned request is passed to the caller, which is\n> > - * responsible for either queueing the request or deleting it.\n> > + * responsible for deleting it. The request may be deleted in the completion\n> > + * handler, or reused after resetting its state with Request::reuse().\n> >   *\n> >   * \\context This function is \\threadsafe. It may only be called when the camera\n> >   * is in the Configured or Running state as defined in \\ref camera_operation.\n> >   *\n> >   * \\return A pointer to the newly created request, or nullptr on error\n> >   */\n> > -Request *Camera::createRequest(uint64_t cookie)\n> > +std::unique_ptr<Request> Camera::createRequest(uint64_t cookie)\n> >  {\n> >  \tint ret = p_->isAccessAllowed(Private::CameraConfigured,\n> >  \t\t\t\t      Private::CameraRunning);\n> >  \tif (ret < 0)\n> >  \t\treturn nullptr;\n> >\n> > -\treturn new Request(this, cookie);\n> > +\treturn std::make_unique<Request>(this, cookie);\n> >  }\n> >\n> >  /**\n> > @@ -877,9 +878,6 @@ Request *Camera::createRequest(uint64_t cookie)\n> >   * Once the request has been queued, the camera will notify its completion\n> >   * through the \\ref requestCompleted signal.\n> >   *\n> > - * Ownership of the request is transferred to the camera. It will be deleted\n> > - * automatically after it completes.\n> > - *\n> >   * \\context This function is \\threadsafe. It may only be called when the camera\n> >   * is in the Running state as defined in \\ref camera_operation.\n> >   *\n> > @@ -988,13 +986,11 @@ int Camera::stop()\n> >   * \\param[in] request The request that has completed\n> >   *\n> >   * This function is called by the pipeline handler to notify the camera that\n> > - * the request has completed. It emits the requestCompleted signal and deletes\n> > - * the request.\n> > + * the request has completed. It emits the requestCompleted signal.\n> >   */\n> >  void Camera::requestComplete(Request *request)\n> >  {\n> >  \trequestCompleted.emit(request);\n> > -\tdelete request;\n> >  }\n> >\n> >  } /* namespace libcamera */\n> > diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp\n> > index 60b30692..ae8b1660 100644\n> > --- a/src/libcamera/request.cpp\n> > +++ b/src/libcamera/request.cpp\n> > @@ -37,6 +37,15 @@ LOG_DEFINE_CATEGORY(Request)\n> >   * The request has been cancelled due to capture stop\n> >   */\n> >\n> > +/**\n> > + * \\enum Request::ReuseFlag\n> > + * Flags to control the behavior of Request::reuse()\n> > + * \\var Request::Default\n> > + * Don't reuse buffers\n> > + * \\var Request::ReuseBuffers\n> > + * Reuse the buffers that were previously added by addBuffer()\n> > + */\n> > +\n> >  /**\n> >   * \\typedef Request::BufferMap\n> >   * \\brief A map of Stream to FrameBuffer pointers\n> > @@ -85,6 +94,35 @@ Request::~Request()\n> >  \tdelete validator_;\n> >  }\n> >\n> > +/**\n> > + * \\brief Reset the request for reuse\n> > + * \\param[in] flags Indicate whether or not to reuse the buffers\n> > + *\n> > + * Reset the status and controls associated with the request, to allow it to\n> > + * be reused and requeued without destruction. This function shall be called\n> > + * prior to queueing the request to the camera, in lieu of constructing a new\n> > + * request. The application can reuse the buffers that were previously added\n> > + * to the request via addBuffer() by setting \\a flags to ReuseBuffers.\n> > + */\n> > +void Request::reuse(ReuseFlag flags)\n> > +{\n> > +\tpending_.clear();\n> > +\tif (flags & ReuseBuffers) {\n> > +\t\tfor (auto pair : bufferMap_) {\n> > +\t\t\tpair.second->request_ = this;\n> > +\t\t\tpending_.insert(pair.second);\n> > +\t\t}\n> > +\t} else {\n> > +\t\tbufferMap_.clear();\n> > +\t}\n> > +\n> > +\tstatus_ = RequestPending;\n> > +\tcancelled_ = false;\n> > +\n> > +\tcontrols_->clear();\n> > +\tmetadata_->clear();\n> > +}\n> > +\n> >  /**\n> >   * \\fn Request::controls()\n> >   * \\brief Retrieve the request's ControlList\n> > diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp\n> > index ecb9dd66..0cbdab9a 100644\n> > --- a/src/qcam/main_window.cpp\n> > +++ b/src/qcam/main_window.cpp\n> > @@ -367,7 +367,6 @@ void MainWindow::toggleCapture(bool start)\n> >  int MainWindow::startCapture()\n> >  {\n> >  \tStreamRoles roles = StreamKeyValueParser::roles(options_[OptStream]);\n> > -\tstd::vector<Request *> requests;\n> >  \tint ret;\n> >\n> >  \t/* Verify roles are supported. */\n> > @@ -486,7 +485,7 @@ int MainWindow::startCapture()\n> >  \twhile (!freeBuffers_[vfStream_].isEmpty()) {\n> >  \t\tFrameBuffer *buffer = freeBuffers_[vfStream_].dequeue();\n> >\n> > -\t\tRequest *request = camera_->createRequest();\n> > +\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> >  \t\tif (!request) {\n> >  \t\t\tqWarning() << \"Can't create request\";\n> >  \t\t\tret = -ENOMEM;\n> > @@ -499,7 +498,7 @@ int MainWindow::startCapture()\n> >  \t\t\tgoto error;\n> >  \t\t}\n> >\n> > -\t\trequests.push_back(request);\n> > +\t\trequests_.push_back(std::move(request));\n> >  \t}\n> >\n> >  \t/* Start the title timer and the camera. */\n> > @@ -518,8 +517,8 @@ int MainWindow::startCapture()\n> >  \tcamera_->requestCompleted.connect(this, &MainWindow::requestComplete);\n> >\n> >  \t/* Queue all requests. */\n> > -\tfor (Request *request : requests) {\n> > -\t\tret = camera_->queueRequest(request);\n> > +\tfor (std::unique_ptr<Request> &request : requests_) {\n> > +\t\tret = camera_->queueRequest(request.get());\n> >  \t\tif (ret < 0) {\n> >  \t\t\tqWarning() << \"Can't queue request\";\n> >  \t\t\tgoto error_disconnect;\n> > @@ -535,8 +534,7 @@ error_disconnect:\n> >  \tcamera_->stop();\n> >\n> >  error:\n> > -\tfor (Request *request : requests)\n> > -\t\tdelete request;\n> > +\trequests_.clear();\n> >\n> >  \tfor (auto &iter : mappedBuffers_) {\n> >  \t\tconst MappedBuffer &buffer = iter.second;\n> > @@ -580,6 +578,8 @@ void MainWindow::stopCapture()\n> >  \t}\n> >  \tmappedBuffers_.clear();\n> >\n> > +\trequests_.clear();\n> > +\n> >  \tdelete allocator_;\n> >\n> >  \tisCapturing_ = false;\n> > @@ -701,7 +701,7 @@ void MainWindow::requestComplete(Request *request)\n> >  \t */\n> >  \t{\n> >  \t\tQMutexLocker locker(&mutex_);\n> > -\t\tdoneQueue_.enqueue({ request->buffers(), request->metadata() });\n> > +\t\tdoneQueue_.enqueue(request);\n> >  \t}\n> >\n> >  \tQCoreApplication::postEvent(this, new CaptureEvent);\n> > @@ -714,8 +714,7 @@ void MainWindow::processCapture()\n> >  \t * if stopCapture() has been called while a CaptureEvent was posted but\n> >  \t * not processed yet. Return immediately in that case.\n> >  \t */\n> > -\tCaptureRequest request;\n> > -\n> > +\tRequest *request;\n> >  \t{\n> >  \t\tQMutexLocker locker(&mutex_);\n> >  \t\tif (doneQueue_.isEmpty())\n> > @@ -725,11 +724,15 @@ void MainWindow::processCapture()\n> >  \t}\n> >\n> >  \t/* Process buffers. */\n> > -\tif (request.buffers_.count(vfStream_))\n> > -\t\tprocessViewfinder(request.buffers_[vfStream_]);\n> > +\tif (request->buffers().count(vfStream_))\n> > +\t\tprocessViewfinder(request->buffers().at(vfStream_));\n> >\n> > -\tif (request.buffers_.count(rawStream_))\n> > -\t\tprocessRaw(request.buffers_[rawStream_], request.metadata_);\n> > +\tif (request->buffers().count(rawStream_))\n> > +\t\tprocessRaw(request->buffers().at(rawStream_), request->metadata());\n> > +\n> > +\trequest->reuse();\n> > +\tQMutexLocker locker(&mutex_);\n> > +\tfreeQueue_.enqueue(request);\n> >  }\n> >\n> >  void MainWindow::processViewfinder(FrameBuffer *buffer)\n> > @@ -754,10 +757,13 @@ void MainWindow::processViewfinder(FrameBuffer *buffer)\n> >\n> >  void MainWindow::queueRequest(FrameBuffer *buffer)\n> >  {\n> > -\tRequest *request = camera_->createRequest();\n> > -\tif (!request) {\n> > -\t\tqWarning() << \"Can't create request\";\n> > -\t\treturn;\n> > +\tRequest *request;\n> > +\t{\n> > +\t\tQMutexLocker locker(&mutex_);\n> > +\t\tif (freeQueue_.isEmpty())\n> > +\t\t\treturn;\n> > +\n> > +\t\trequest = freeQueue_.dequeue();\n> >  \t}\n> >\n> >  \trequest->addBuffer(vfStream_, buffer);\n> > diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h\n> > index 5c61a4df..64bcfebc 100644\n> > --- a/src/qcam/main_window.h\n> > +++ b/src/qcam/main_window.h\n> > @@ -8,6 +8,7 @@\n> >  #define __QCAM_MAIN_WINDOW_H__\n> >\n> >  #include <memory>\n> > +#include <vector>\n> >\n> >  #include <QElapsedTimer>\n> >  #include <QIcon>\n> > @@ -22,6 +23,7 @@\n> >  #include <libcamera/camera_manager.h>\n> >  #include <libcamera/controls.h>\n> >  #include <libcamera/framebuffer_allocator.h>\n> > +#include <libcamera/request.h>\n> >  #include <libcamera/stream.h>\n> >\n> >  #include \"../cam/stream_options.h\"\n> > @@ -41,23 +43,6 @@ enum {\n> >  \tOptStream = 's',\n> >  };\n> >\n> > -class CaptureRequest\n> > -{\n> > -public:\n> > -\tCaptureRequest()\n> > -\t{\n> > -\t}\n> > -\n> > -\tCaptureRequest(const Request::BufferMap &buffers,\n> > -\t\t       const ControlList &metadata)\n> > -\t\t: buffers_(buffers), metadata_(metadata)\n> > -\t{\n> > -\t}\n> > -\n> > -\tRequest::BufferMap buffers_;\n> > -\tControlList metadata_;\n> > -};\n> > -\n> >  class MainWindow : public QMainWindow\n> >  {\n> >  \tQ_OBJECT\n> > @@ -128,13 +113,16 @@ private:\n> >  \tStream *vfStream_;\n> >  \tStream *rawStream_;\n> >  \tstd::map<const Stream *, QQueue<FrameBuffer *>> freeBuffers_;\n> > -\tQQueue<CaptureRequest> doneQueue_;\n> > -\tQMutex mutex_; /* Protects freeBuffers_ and doneQueue_ */\n> > +\tQQueue<Request *> doneQueue_;\n> > +\tQQueue<Request *> freeQueue_;\n> > +\tQMutex mutex_; /* Protects freeBuffers_, doneQueue_, and freeQueue_ */\n> >\n> >  \tuint64_t lastBufferTime_;\n> >  \tQElapsedTimer frameRateInterval_;\n> >  \tuint32_t previousFrames_;\n> >  \tuint32_t framesCaptured_;\n> > +\n> > +\tstd::vector<std::unique_ptr<Request>> requests_;\n> >  };\n> >\n> >  #endif /* __QCAM_MAIN_WINDOW__ */\n> > diff --git a/src/v4l2/v4l2_camera.cpp b/src/v4l2/v4l2_camera.cpp\n> > index 3565f369..35d3beda 100644\n> > --- a/src/v4l2/v4l2_camera.cpp\n> > +++ b/src/v4l2/v4l2_camera.cpp\n> > @@ -49,6 +49,8 @@ int V4L2Camera::open(StreamConfiguration *streamConfig)\n> >\n> >  void V4L2Camera::close()\n> >  {\n> > +\trequestPool_.clear();\n> > +\n> >  \tdelete bufferAllocator_;\n> >  \tbufferAllocator_ = nullptr;\n> >\n> > @@ -96,6 +98,7 @@ void V4L2Camera::requestComplete(Request *request)\n> >  \tif (ret != sizeof(data))\n> >  \t\tLOG(V4L2Compat, Error) << \"Failed to signal eventfd POLLIN\";\n> >\n> > +\trequest->reuse();\n> >  \t{\n> >  \t\tMutexLocker locker(bufferMutex_);\n> >  \t\tbufferAvailableCount_++;\n> > @@ -154,16 +157,30 @@ int V4L2Camera::validateConfiguration(const PixelFormat &pixelFormat,\n> >  \treturn 0;\n> >  }\n> >\n> > -int V4L2Camera::allocBuffers([[maybe_unused]] unsigned int count)\n> > +int V4L2Camera::allocBuffers(unsigned int count)\n> >  {\n> >  \tStream *stream = config_->at(0).stream();\n> >\n> > -\treturn bufferAllocator_->allocate(stream);\n> > +\tint ret = bufferAllocator_->allocate(stream);\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\tfor (unsigned int i = 0; i < count; i++) {\n> > +\t\tstd::unique_ptr<Request> request = camera_->createRequest(i);\n> > +\t\tif (!request) {\n> > +\t\t\trequestPool_.clear();\n> > +\t\t\treturn -ENOMEM;\n> > +\t\t}\n> > +\t\trequestPool_.push_back(std::move(request));\n> > +\t}\n> > +\n> > +\treturn ret;\n> >  }\n> >\n> >  void V4L2Camera::freeBuffers()\n> >  {\n> >  \tpendingRequests_.clear();\n> > +\trequestPool_.clear();\n> >\n> >  \tStream *stream = config_->at(0).stream();\n> >  \tbufferAllocator_->free(stream);\n> > @@ -192,9 +209,9 @@ int V4L2Camera::streamOn()\n> >\n> >  \tisRunning_ = true;\n> >\n> > -\tfor (std::unique_ptr<Request> &req : pendingRequests_) {\n> > +\tfor (Request *req : pendingRequests_) {\n> >  \t\t/* \\todo What should we do if this returns -EINVAL? */\n> > -\t\tret = camera_->queueRequest(req.release());\n> > +\t\tret = camera_->queueRequest(req);\n> >  \t\tif (ret < 0)\n> >  \t\t\treturn ret == -EACCES ? -EBUSY : ret;\n> >  \t}\n> > @@ -206,8 +223,12 @@ int V4L2Camera::streamOn()\n> >\n> >  int V4L2Camera::streamOff()\n> >  {\n> > -\tif (!isRunning_)\n> > +\tif (!isRunning_) {\n> > +\t\tfor (std::unique_ptr<Request> &req : requestPool_)\n> > +\t\t\treq->reuse();\n> > +\n> >  \t\treturn 0;\n> > +\t}\n> >\n> >  \tpendingRequests_.clear();\n> >\n> > @@ -226,12 +247,11 @@ int V4L2Camera::streamOff()\n> >\n> >  int V4L2Camera::qbuf(unsigned int index)\n> >  {\n> > -\tstd::unique_ptr<Request> request =\n> > -\t\tstd::unique_ptr<Request>(camera_->createRequest(index));\n> > -\tif (!request) {\n> > -\t\tLOG(V4L2Compat, Error) << \"Can't create request\";\n> > -\t\treturn -ENOMEM;\n> > +\tif (index >= requestPool_.size()) {\n> > +\t\tLOG(V4L2Compat, Error) << \"Invalid index\";\n> > +\t\treturn -EINVAL;\n> >  \t}\n> > +\tRequest *request = requestPool_[index].get();\n> >\n> >  \tStream *stream = config_->at(0).stream();\n> >  \tFrameBuffer *buffer = bufferAllocator_->buffers(stream)[index].get();\n> > @@ -242,11 +262,11 @@ int V4L2Camera::qbuf(unsigned int index)\n> >  \t}\n> >\n> >  \tif (!isRunning_) {\n> > -\t\tpendingRequests_.push_back(std::move(request));\n> > +\t\tpendingRequests_.push_back(request);\n> >  \t\treturn 0;\n> >  \t}\n> >\n> > -\tret = camera_->queueRequest(request.release());\n> > +\tret = camera_->queueRequest(request);\n> >  \tif (ret < 0) {\n> >  \t\tLOG(V4L2Compat, Error) << \"Can't queue request\";\n> >  \t\treturn ret == -EACCES ? -EBUSY : ret;\n> > diff --git a/src/v4l2/v4l2_camera.h b/src/v4l2/v4l2_camera.h\n> > index 1fc5ebef..a6c35a2e 100644\n> > --- a/src/v4l2/v4l2_camera.h\n> > +++ b/src/v4l2/v4l2_camera.h\n> > @@ -76,7 +76,9 @@ private:\n> >  \tstd::mutex bufferLock_;\n> >  \tFrameBufferAllocator *bufferAllocator_;\n> >\n> > -\tstd::deque<std::unique_ptr<Request>> pendingRequests_;\n> > +\tstd::vector<std::unique_ptr<Request>> requestPool_;\n> > +\n> > +\tstd::deque<Request *> pendingRequests_;\n> >  \tstd::deque<std::unique_ptr<Buffer>> completedBuffers_;\n> >\n> >  \tint efd_;\n> > diff --git a/test/camera/buffer_import.cpp b/test/camera/buffer_import.cpp\n> > index 64e96264..72ce7b79 100644\n> > --- a/test/camera/buffer_import.cpp\n> > +++ b/test/camera/buffer_import.cpp\n> > @@ -58,7 +58,7 @@ protected:\n> >  \t\tconst Stream *stream = buffers.begin()->first;\n> >  \t\tFrameBuffer *buffer = buffers.begin()->second;\n> >\n> > -\t\trequest = camera_->createRequest();\n> > +\t\trequest->reuse();\n> >  \t\trequest->addBuffer(stream, buffer);\n> >  \t\tcamera_->queueRequest(request);\n> >  \t}\n> > @@ -98,9 +98,8 @@ protected:\n> >  \t\tif (ret != TestPass)\n> >  \t\t\treturn ret;\n> >\n> > -\t\tstd::vector<Request *> requests;\n> >  \t\tfor (const std::unique_ptr<FrameBuffer> &buffer : source.buffers()) {\n> > -\t\t\tRequest *request = camera_->createRequest();\n> > +\t\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> >  \t\t\tif (!request) {\n> >  \t\t\t\tstd::cout << \"Failed to create request\" << std::endl;\n> >  \t\t\t\treturn TestFail;\n> > @@ -111,7 +110,7 @@ protected:\n> >  \t\t\t\treturn TestFail;\n> >  \t\t\t}\n> >\n> > -\t\t\trequests.push_back(request);\n> > +\t\t\trequests_.push_back(std::move(request));\n> >  \t\t}\n> >\n> >  \t\tcompleteRequestsCount_ = 0;\n> > @@ -125,8 +124,8 @@ protected:\n> >  \t\t\treturn TestFail;\n> >  \t\t}\n> >\n> > -\t\tfor (Request *request : requests) {\n> > -\t\t\tif (camera_->queueRequest(request)) {\n> > +\t\tfor (std::unique_ptr<Request> &request : requests_) {\n> > +\t\t\tif (camera_->queueRequest(request.get())) {\n> >  \t\t\t\tstd::cout << \"Failed to queue request\" << std::endl;\n> >  \t\t\t\treturn TestFail;\n> >  \t\t\t}\n> > @@ -160,6 +159,8 @@ protected:\n> >  \t}\n> >\n> >  private:\n> > +\tstd::vector<std::unique_ptr<Request>> requests_;\n> > +\n> >  \tunsigned int completeBuffersCount_;\n> >  \tunsigned int completeRequestsCount_;\n> >  \tstd::unique_ptr<CameraConfiguration> config_;\n> > diff --git a/test/camera/capture.cpp b/test/camera/capture.cpp\n> > index 51bbd258..c0770801 100644\n> > --- a/test/camera/capture.cpp\n> > +++ b/test/camera/capture.cpp\n> > @@ -52,7 +52,7 @@ protected:\n> >  \t\tconst Stream *stream = buffers.begin()->first;\n> >  \t\tFrameBuffer *buffer = buffers.begin()->second;\n> >\n> > -\t\trequest = camera_->createRequest();\n> > +\t\trequest->reuse();\n> >  \t\trequest->addBuffer(stream, buffer);\n> >  \t\tcamera_->queueRequest(request);\n> >  \t}\n> > @@ -98,9 +98,8 @@ protected:\n> >  \t\tif (ret < 0)\n> >  \t\t\treturn TestFail;\n> >\n> > -\t\tstd::vector<Request *> requests;\n> >  \t\tfor (const std::unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) {\n> > -\t\t\tRequest *request = camera_->createRequest();\n> > +\t\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> >  \t\t\tif (!request) {\n> >  \t\t\t\tcout << \"Failed to create request\" << endl;\n> >  \t\t\t\treturn TestFail;\n> > @@ -111,7 +110,7 @@ protected:\n> >  \t\t\t\treturn TestFail;\n> >  \t\t\t}\n> >\n> > -\t\t\trequests.push_back(request);\n> > +\t\t\trequests_.push_back(std::move(request));\n> >  \t\t}\n> >\n> >  \t\tcompleteRequestsCount_ = 0;\n> > @@ -125,8 +124,8 @@ protected:\n> >  \t\t\treturn TestFail;\n> >  \t\t}\n> >\n> > -\t\tfor (Request *request : requests) {\n> > -\t\t\tif (camera_->queueRequest(request)) {\n> > +\t\tfor (std::unique_ptr<Request> &request : requests_) {\n> > +\t\t\tif (camera_->queueRequest(request.get())) {\n> >  \t\t\t\tcout << \"Failed to queue request\" << endl;\n> >  \t\t\t\treturn TestFail;\n> >  \t\t\t}\n> > @@ -161,6 +160,8 @@ protected:\n> >  \t\treturn TestPass;\n> >  \t}\n> >\n> > +\tstd::vector<std::unique_ptr<Request>> requests_;\n> > +\n> >  \tstd::unique_ptr<CameraConfiguration> config_;\n> >  \tFrameBufferAllocator *allocator_;\n> >  };\n> > diff --git a/test/camera/statemachine.cpp b/test/camera/statemachine.cpp\n> > index 28faeb91..e63ab298 100644\n> > --- a/test/camera/statemachine.cpp\n> > +++ b/test/camera/statemachine.cpp\n> > @@ -101,13 +101,10 @@ protected:\n> >  \t\t\treturn TestFail;\n> >\n> >  \t\t/* Test operations which should pass. */\n> > -\t\tRequest *request2 = camera_->createRequest();\n> > +\t\tstd::unique_ptr<Request> request2 = camera_->createRequest();\n> >  \t\tif (!request2)\n> >  \t\t\treturn TestFail;\n> >\n> > -\t\t/* Never handed to hardware so need to manually delete it. */\n> > -\t\tdelete request2;\n> > -\n> >  \t\t/* Test valid state transitions, end in Running state. */\n> >  \t\tif (camera_->release())\n> >  \t\t\treturn TestFail;\n> > @@ -146,7 +143,7 @@ protected:\n> >  \t\t\treturn TestFail;\n> >\n> >  \t\t/* Test operations which should pass. */\n> > -\t\tRequest *request = camera_->createRequest();\n> > +\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> >  \t\tif (!request)\n> >  \t\t\treturn TestFail;\n> >\n> > @@ -154,7 +151,7 @@ protected:\n> >  \t\tif (request->addBuffer(stream, allocator_->buffers(stream)[0].get()))\n> >  \t\t\treturn TestFail;\n> >\n> > -\t\tif (camera_->queueRequest(request))\n> > +\t\tif (camera_->queueRequest(request.get()))\n> >  \t\t\treturn TestFail;\n> >\n> >  \t\t/* Test valid state transitions, end in Available state. */\n> > --\n> > 2.27.0\n> >\n> > _______________________________________________\n> > libcamera-devel mailing list\n> > libcamera-devel@lists.libcamera.org\n> > https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 89518BEEDF\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu,  8 Oct 2020 06:29:31 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id E95EA605B7;\n\tThu,  8 Oct 2020 08:29:30 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 82E7A60356\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu,  8 Oct 2020 08:29:29 +0200 (CEST)","from pyrite.rasen.tech (unknown\n\t[IPv6:2400:4051:61:600:2c71:1b79:d06d:5032])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 9D39259E;\n\tThu,  8 Oct 2020 08:29:27 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"UdcK6myx\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1602138569;\n\tbh=n4B5DtJ1bU5P4Tsgxx6zK+Jhk4s3TjmkO8UMFs0RF/Q=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=UdcK6myx5g7jDYyIQ5fTlN9xB2zQxxNRsNVFRPFEyGcr3WXJFj64m4MJrG6HlI9Kf\n\tk82B1Uk2bkVtlRAhRDPpbYss5xIjhAHK8m8oDdmp2Kgfv4nm7i04cX79TX/40UoHsI\n\tBtA1eY1JrF3NiB9kPUmg+fo97hlc9ePS0LRyGOMU=","Date":"Thu, 8 Oct 2020 15:29:20 +0900","From":"paul.elder@ideasonboard.com","To":"Jacopo Mondi <jacopo@jmondi.org>","Message-ID":"<20201008062920.GI45948@pyrite.rasen.tech>","References":"<20201007073418.512656-1-paul.elder@ideasonboard.com>\n\t<20201007094331.2dl4xwdeqzf4isix@uno.localdomain>","MIME-Version":"1.0","Content-Disposition":"inline","In-Reply-To":"<20201007094331.2dl4xwdeqzf4isix@uno.localdomain>","Subject":"Re: [libcamera-devel] [PATCH v6] libcamera, android, cam, gstreamer,\n\tqcam, v4l2: Reuse Request","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Cc":"libcamera-devel@lists.libcamera.org","Content-Type":"text/plain; charset=\"us-ascii\"","Content-Transfer-Encoding":"7bit","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":13131,"web_url":"https://patchwork.libcamera.org/comment/13131/","msgid":"<20201009124530.mizyxnce7kbbq2yz@uno.localdomain>","date":"2020-10-09T12:45:30","subject":"Re: [libcamera-devel] [PATCH v6] libcamera, android, cam, gstreamer,\n\tqcam, v4l2: Reuse Request","submitter":{"id":3,"url":"https://patchwork.libcamera.org/api/people/3/","name":"Jacopo Mondi","email":"jacopo@jmondi.org"},"content":"Hi Laurent,\n\nOn Thu, Oct 08, 2020 at 05:45:40AM +0300, Laurent Pinchart wrote:\n> Hi Jacopo,\n>\n> On Wed, Oct 07, 2020 at 11:43:31AM +0200, Jacopo Mondi wrote:\n> > Hi Paul,\n> >\n> > On Wed, Oct 07, 2020 at 04:34:18PM +0900, Paul Elder wrote:\n> > > Allow reuse of the Request object by implementing reuse(). This means\n> > > the applications now have the responsibility of freeing the Request\n> > > objects, so make all libcamera users (cam, qcam, v4l2-compat, gstreamer,\n> > > android) do so.\n> >\n> > Maybe not a question to ask at v6, I've gone through the previous\n> > iterations and I see the proposed API has been discussed already, so\n> > feel free to ignore, but: I'm not sure I like the flags passed to\n> > reuse() :)\n> >\n> > To be more precise, I don't see much much value in ReuseBuffers.\n> >\n> > How usual is it that the same [Stream|buffer] pair is repeated ? I\n> > understand we might want to capture from the same set of Streams, but\n> > application would more likely cycle buffers, don't they ?\n> >\n> > I understand the ReuseBuffers flag might be used to model a\n> > 'repeating' request mechanism, but I don't think our API is currently\n> > ready to support such a feature. All our requests are one-shot, they\n> > get queued, notified when completed, and need to be re-queued. There's\n> > nothing like a mechanism that let's you create one request, queue it\n> > once, and have it running until you don't stop you (without the need\n> > for a requeue. For reference [1]). Implementing something like this\n> > will need some more thinking, in example how to make it possible for a\n> > repeating request to cycle on a set of buffers automatically.\n>\n> I agree, but that's not the use case at hand. Request::reuse() has to be\n> called each time a request completes, it doesn't repeat requests\n> automatically.\n>\n> > I don't think we should try to emulate a repeating mechanism by\n> > allowing application to re-use the same stream-buffer pair. It has a\n> > limited use (in my understanding) and will confuse application as they\n> > need anyway to re-queue it.\n>\n> The use case seems pretty common to me. If you look at the cam\n> application for instance, we allocate a set of buffers, and the same\n> number of requests. The buffers are cycled through, with one buffer per\n> request. There's no need to keep separate pools of buffers and requests,\n> a 1:1 mapping is the simplest. We can add one buffer to each request,\n> and then cycle through the requests reusing the buffers.\n>\n\nMy confusion was generated as I thought we were aiming to have 1\nrepeating request emulated with this mechanism.\n\n> > Don't get me wrong, I think moving ownership of Request to application\n> > is correct and more clear, but I don't see much use for the flag [*]\n> >\n> > I would prefer if we keep thinking of our request as one shot, so they\n> > could either be reset everytime, and application will have to\n> > addBuffers() again as they will anyway have to cycle buffers.\n> >\n> > [*] This makes me unconfortable for another reason. I understand we\n> > might want flags for, say, Controls handling and other possible tuning\n> > (have you though about other possible flags ?). I don't think the\n> > output buffer handling lives in the same category as low level control\n> > of the Request, it's a primary Request feature and should not be expressed\n> > with flags as in example \"Use template XYZ for the Request's controls\"\n\nMy concern remains, I'm a bit afraid we'll end up mixing buffer\nhandling and other Request features with flags, but that's indeed not\na blocker.\n\nThanks\n  j\n\n> >\n> > [1] https://medium.com/androiddevelopers/understanding-android-camera-capture-sessions-and-requests-4e54d9150295\n> >\n> > >\n> > > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> > > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> > >\n> > > ---\n> > > Changes in v6:\n> > > - fix doxygen\n> > > - rename enum ReuseBuffer to ReuseFlag, and change to flags\n> > > - move request->reuse() in v4l2-compat from qbuf time to completion\n> > >   time\n> > >   - streamOff when the stream is already off needs to also\n> > >     request->reuse(), so do that too\n> > > - update documentation\n> > >\n> > > Changes in v5:\n> > > - rename reset() to reuse()\n> > > - make reuse()'s reuseBuffers parameter into enum instead of bool\n> > > - fix qcam qt assertion error on the first queueRequest, where\n> > >   freeQueue_ is empty\n> > >\n> > > Changes in v4:\n> > > - no more implicit calls of anything that we added in this patch\n> > > - make reset() take a reuseBuffers boolean parameters\n> > >   - use transient request - delete request\n> > >   - reuse request, reset buffers - reset()\n> > >   - reuse request, reuse buffesr - reset(true)\n> > > - update apps and tests and documentation accordingly\n> > >\n> > > Changes in v3:\n> > > - reset() is called in Camera::queueRequest()\n> > > - apps that use transient request (android, gstreamer) delete the\n> > >   request at the end of the completion handler\n> > > - apps that want to reuse the buffers (cam) use reAddBuffers()\n> > > - apps that need to change the buffers (qcam, v4l2) use clearBuffers()\n> > > - update the documentation accordingly\n> > >\n> > > Changes in v2:\n> > > - clear controls_ and metadata_ and validator_ in Request::reset()\n> > > - use unique_ptr on application side, prior to queueRequest, and use\n> > >   regular pointer for completion handler\n> > > - make qcam's reuse request nicer\n> > > - update Camera::queueRequest() and Camera::createRequest() documentation\n> > > - add documentation for Request::reset()\n> > > - make v4l2-compat reuse request\n> > > - make gstreamer and android use the new createRequest API, though they\n> > >   do not actually reuse the requests\n> > > ---\n> > >  include/libcamera/camera.h        |  2 +-\n> > >  include/libcamera/request.h       |  7 +++++\n> > >  src/android/camera_device.cpp     | 14 +++++++---\n> > >  src/cam/capture.cpp               | 29 +++++---------------\n> > >  src/cam/capture.h                 |  3 +++\n> > >  src/gstreamer/gstlibcamerasrc.cpp | 14 ++++++----\n> > >  src/libcamera/camera.cpp          | 14 ++++------\n> > >  src/libcamera/request.cpp         | 38 ++++++++++++++++++++++++++\n> > >  src/qcam/main_window.cpp          | 42 ++++++++++++++++-------------\n> > >  src/qcam/main_window.h            | 26 +++++-------------\n> > >  src/v4l2/v4l2_camera.cpp          | 44 ++++++++++++++++++++++---------\n> > >  src/v4l2/v4l2_camera.h            |  4 ++-\n> > >  test/camera/buffer_import.cpp     | 13 ++++-----\n> > >  test/camera/capture.cpp           | 13 ++++-----\n> > >  test/camera/statemachine.cpp      |  9 +++----\n> > >  15 files changed, 163 insertions(+), 109 deletions(-)\n> > >\n> > > diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> > > index a2ee4e7e..79ff8d6b 100644\n> > > --- a/include/libcamera/camera.h\n> > > +++ b/include/libcamera/camera.h\n> > > @@ -96,7 +96,7 @@ public:\n> > >  \tstd::unique_ptr<CameraConfiguration> generateConfiguration(const StreamRoles &roles = {});\n> > >  \tint configure(CameraConfiguration *config);\n> > >\n> > > -\tRequest *createRequest(uint64_t cookie = 0);\n> > > +\tstd::unique_ptr<Request> createRequest(uint64_t cookie = 0);\n> > >  \tint queueRequest(Request *request);\n> > >\n> > >  \tint start();\n> > > diff --git a/include/libcamera/request.h b/include/libcamera/request.h\n> > > index 5976ac50..655b1324 100644\n> > > --- a/include/libcamera/request.h\n> > > +++ b/include/libcamera/request.h\n> > > @@ -31,6 +31,11 @@ public:\n> > >  \t\tRequestCancelled,\n> > >  \t};\n> > >\n> > > +\tenum ReuseFlag {\n> > > +\t\tDefault = 0,\n> > > +\t\tReuseBuffers = (1 << 0),\n> > > +\t};\n> > > +\n> > >  \tusing BufferMap = std::map<const Stream *, FrameBuffer *>;\n> > >\n> > >  \tRequest(Camera *camera, uint64_t cookie = 0);\n> > > @@ -38,6 +43,8 @@ public:\n> > >  \tRequest &operator=(const Request &) = delete;\n> > >  \t~Request();\n> > >\n> > > +\tvoid reuse(ReuseFlag flags = Default);\n> > > +\n> > >  \tControlList &controls() { return *controls_; }\n> > >  \tControlList &metadata() { return *metadata_; }\n> > >  \tconst BufferMap &buffers() const { return bufferMap_; }\n> > > diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp\n> > > index 751699cd..052c9292 100644\n> > > --- a/src/android/camera_device.cpp\n> > > +++ b/src/android/camera_device.cpp\n> > > @@ -1395,8 +1395,12 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n> > >  \t\tnew Camera3RequestDescriptor(camera3Request->frame_number,\n> > >  \t\t\t\t\t     camera3Request->num_output_buffers);\n> > >\n> > > -\tRequest *request =\n> > > +\tstd::unique_ptr<Request> request =\n> > >  \t\tcamera_->createRequest(reinterpret_cast<uint64_t>(descriptor));\n> > > +\tif (!request) {\n> > > +\t\tLOG(HAL, Error) << \"Failed to create request\";\n> > > +\t\treturn -ENOMEM;\n> > > +\t}\n> > >\n> > >  \tfor (unsigned int i = 0; i < descriptor->numBuffers; ++i) {\n> > >  \t\tCameraStream *cameraStream =\n> > > @@ -1422,7 +1426,6 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n> > >  \t\tFrameBuffer *buffer = createFrameBuffer(*camera3Buffers[i].buffer);\n> > >  \t\tif (!buffer) {\n> > >  \t\t\tLOG(HAL, Error) << \"Failed to create buffer\";\n> > > -\t\t\tdelete request;\n> > >  \t\t\tdelete descriptor;\n> > >  \t\t\treturn -ENOMEM;\n> > >  \t\t}\n> > > @@ -1434,14 +1437,16 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n> > >  \t\trequest->addBuffer(stream, buffer);\n> > >  \t}\n> > >\n> > > -\tint ret = camera_->queueRequest(request);\n> > > +\tint ret = camera_->queueRequest(request.get());\n> > >  \tif (ret) {\n> > >  \t\tLOG(HAL, Error) << \"Failed to queue request\";\n> > > -\t\tdelete request;\n> > >  \t\tdelete descriptor;\n> > >  \t\treturn ret;\n> > >  \t}\n> > >\n> > > +\t/* The request will be deleted in the completion handler. */\n> > > +\trequest.release();\n> > > +\n> > >  \treturn 0;\n> > >  }\n> > >\n> > > @@ -1593,6 +1598,7 @@ void CameraDevice::requestComplete(Request *request)\n> > >  \tcallbacks_->process_capture_result(callbacks_, &captureResult);\n> > >\n> > >  \tdelete descriptor;\n> > > +\tdelete request;\n> > >  }\n> > >\n> > >  std::string CameraDevice::logPrefix() const\n> > > diff --git a/src/cam/capture.cpp b/src/cam/capture.cpp\n> > > index 5510c009..8c8faa4b 100644\n> > > --- a/src/cam/capture.cpp\n> > > +++ b/src/cam/capture.cpp\n> > > @@ -65,6 +65,8 @@ int Capture::run(const OptionsParser::Options &options)\n> > >  \t\twriter_ = nullptr;\n> > >  \t}\n> > >\n> > > +\trequests_.clear();\n> > > +\n> > >  \tdelete allocator;\n> > >\n> > >  \treturn ret;\n> > > @@ -92,9 +94,8 @@ int Capture::capture(FrameBufferAllocator *allocator)\n> > >  \t * example pushing a button. For now run all streams all the time.\n> > >  \t */\n> > >\n> > > -\tstd::vector<Request *> requests;\n> > >  \tfor (unsigned int i = 0; i < nbuffers; i++) {\n> > > -\t\tRequest *request = camera_->createRequest();\n> > > +\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> > >  \t\tif (!request) {\n> > >  \t\t\tstd::cerr << \"Can't create request\" << std::endl;\n> > >  \t\t\treturn -ENOMEM;\n> > > @@ -117,7 +118,7 @@ int Capture::capture(FrameBufferAllocator *allocator)\n> > >  \t\t\t\twriter_->mapBuffer(buffer.get());\n> > >  \t\t}\n> > >\n> > > -\t\trequests.push_back(request);\n> > > +\t\trequests_.push_back(std::move(request));\n> > >  \t}\n> > >\n> > >  \tret = camera_->start();\n> > > @@ -126,8 +127,8 @@ int Capture::capture(FrameBufferAllocator *allocator)\n> > >  \t\treturn ret;\n> > >  \t}\n> > >\n> > > -\tfor (Request *request : requests) {\n> > > -\t\tret = camera_->queueRequest(request);\n> > > +\tfor (std::unique_ptr<Request> &request : requests_) {\n> > > +\t\tret = camera_->queueRequest(request.get());\n> > >  \t\tif (ret < 0) {\n> > >  \t\t\tstd::cerr << \"Can't queue request\" << std::endl;\n> > >  \t\t\tcamera_->stop();\n> > > @@ -202,22 +203,6 @@ void Capture::requestComplete(Request *request)\n> > >  \t\treturn;\n> > >  \t}\n> > >\n> > > -\t/*\n> > > -\t * Create a new request and populate it with one buffer for each\n> > > -\t * stream.\n> > > -\t */\n> > > -\trequest = camera_->createRequest();\n> > > -\tif (!request) {\n> > > -\t\tstd::cerr << \"Can't create request\" << std::endl;\n> > > -\t\treturn;\n> > > -\t}\n> > > -\n> > > -\tfor (auto it = buffers.begin(); it != buffers.end(); ++it) {\n> > > -\t\tconst Stream *stream = it->first;\n> > > -\t\tFrameBuffer *buffer = it->second;\n> > > -\n> > > -\t\trequest->addBuffer(stream, buffer);\n> > > -\t}\n> > > -\n> > > +\trequest->reuse(Request::ReuseBuffers);\n> > >  \tcamera_->queueRequest(request);\n> > >  }\n> > > diff --git a/src/cam/capture.h b/src/cam/capture.h\n> > > index 0aebdac9..45e5e8a9 100644\n> > > --- a/src/cam/capture.h\n> > > +++ b/src/cam/capture.h\n> > > @@ -9,6 +9,7 @@\n> > >\n> > >  #include <memory>\n> > >  #include <stdint.h>\n> > > +#include <vector>\n> > >\n> > >  #include <libcamera/buffer.h>\n> > >  #include <libcamera/camera.h>\n> > > @@ -43,6 +44,8 @@ private:\n> > >  \tEventLoop *loop_;\n> > >  \tunsigned int captureCount_;\n> > >  \tunsigned int captureLimit_;\n> > > +\n> > > +\tstd::vector<std::unique_ptr<libcamera::Request>> requests_;\n> > >  };\n> > >\n> > >  #endif /* __CAM_CAPTURE_H__ */\n> > > diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp\n> > > index 1bfc2e2f..5001083a 100644\n> > > --- a/src/gstreamer/gstlibcamerasrc.cpp\n> > > +++ b/src/gstreamer/gstlibcamerasrc.cpp\n> > > @@ -74,6 +74,8 @@ RequestWrap::~RequestWrap()\n> > >  \t\tif (item.second)\n> > >  \t\t\tgst_buffer_unref(item.second);\n> > >  \t}\n> > > +\n> > > +\tdelete request_;\n> > >  }\n> > >\n> > >  void RequestWrap::attachBuffer(GstBuffer *buffer)\n> > > @@ -266,8 +268,8 @@ gst_libcamera_src_task_run(gpointer user_data)\n> > >  \tGstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);\n> > >  \tGstLibcameraSrcState *state = self->state;\n> > >\n> > > -\tRequest *request = state->cam_->createRequest();\n> > > -\tauto wrap = std::make_unique<RequestWrap>(request);\n> > > +\tstd::unique_ptr<Request> request = state->cam_->createRequest();\n> > > +\tauto wrap = std::make_unique<RequestWrap>(request.get());\n> > >  \tfor (GstPad *srcpad : state->srcpads_) {\n> > >  \t\tGstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);\n> > >  \t\tGstBuffer *buffer;\n> > > @@ -280,8 +282,7 @@ gst_libcamera_src_task_run(gpointer user_data)\n> > >  \t\t\t * RequestWrap does not take ownership, and we won't be\n> > >  \t\t\t * queueing this one due to lack of buffers.\n> > >  \t\t\t */\n> > > -\t\t\tdelete request;\n> > > -\t\t\trequest = nullptr;\n> > > +\t\t\trequest.reset();\n> > >  \t\t\tbreak;\n> > >  \t\t}\n> > >\n> > > @@ -291,8 +292,11 @@ gst_libcamera_src_task_run(gpointer user_data)\n> > >  \tif (request) {\n> > >  \t\tGLibLocker lock(GST_OBJECT(self));\n> > >  \t\tGST_TRACE_OBJECT(self, \"Requesting buffers\");\n> > > -\t\tstate->cam_->queueRequest(request);\n> > > +\t\tstate->cam_->queueRequest(request.get());\n> > >  \t\tstate->requests_.push(std::move(wrap));\n> > > +\n> > > +\t\t/* The request will be deleted in the completion handler. */\n> > > +\t\trequest.release();\n> > >  \t}\n> > >\n> > >  \tGstFlowReturn ret = GST_FLOW_OK;\n> > > diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> > > index fb76077f..9590ab72 100644\n> > > --- a/src/libcamera/camera.cpp\n> > > +++ b/src/libcamera/camera.cpp\n> > > @@ -847,21 +847,22 @@ int Camera::configure(CameraConfiguration *config)\n> > >   * handler, and is completely opaque to libcamera.\n> > >   *\n> > >   * The ownership of the returned request is passed to the caller, which is\n> > > - * responsible for either queueing the request or deleting it.\n> > > + * responsible for deleting it. The request may be deleted in the completion\n> > > + * handler, or reused after resetting its state with Request::reuse().\n> > >   *\n> > >   * \\context This function is \\threadsafe. It may only be called when the camera\n> > >   * is in the Configured or Running state as defined in \\ref camera_operation.\n> > >   *\n> > >   * \\return A pointer to the newly created request, or nullptr on error\n> > >   */\n> > > -Request *Camera::createRequest(uint64_t cookie)\n> > > +std::unique_ptr<Request> Camera::createRequest(uint64_t cookie)\n> > >  {\n> > >  \tint ret = p_->isAccessAllowed(Private::CameraConfigured,\n> > >  \t\t\t\t      Private::CameraRunning);\n> > >  \tif (ret < 0)\n> > >  \t\treturn nullptr;\n> > >\n> > > -\treturn new Request(this, cookie);\n> > > +\treturn std::make_unique<Request>(this, cookie);\n> > >  }\n> > >\n> > >  /**\n> > > @@ -877,9 +878,6 @@ Request *Camera::createRequest(uint64_t cookie)\n> > >   * Once the request has been queued, the camera will notify its completion\n> > >   * through the \\ref requestCompleted signal.\n> > >   *\n> > > - * Ownership of the request is transferred to the camera. It will be deleted\n> > > - * automatically after it completes.\n> > > - *\n> > >   * \\context This function is \\threadsafe. It may only be called when the camera\n> > >   * is in the Running state as defined in \\ref camera_operation.\n> > >   *\n> > > @@ -988,13 +986,11 @@ int Camera::stop()\n> > >   * \\param[in] request The request that has completed\n> > >   *\n> > >   * This function is called by the pipeline handler to notify the camera that\n> > > - * the request has completed. It emits the requestCompleted signal and deletes\n> > > - * the request.\n> > > + * the request has completed. It emits the requestCompleted signal.\n> > >   */\n> > >  void Camera::requestComplete(Request *request)\n> > >  {\n> > >  \trequestCompleted.emit(request);\n> > > -\tdelete request;\n> > >  }\n> > >\n> > >  } /* namespace libcamera */\n> > > diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp\n> > > index 60b30692..ae8b1660 100644\n> > > --- a/src/libcamera/request.cpp\n> > > +++ b/src/libcamera/request.cpp\n> > > @@ -37,6 +37,15 @@ LOG_DEFINE_CATEGORY(Request)\n> > >   * The request has been cancelled due to capture stop\n> > >   */\n> > >\n> > > +/**\n> > > + * \\enum Request::ReuseFlag\n> > > + * Flags to control the behavior of Request::reuse()\n> > > + * \\var Request::Default\n> > > + * Don't reuse buffers\n> > > + * \\var Request::ReuseBuffers\n> > > + * Reuse the buffers that were previously added by addBuffer()\n> > > + */\n> > > +\n> > >  /**\n> > >   * \\typedef Request::BufferMap\n> > >   * \\brief A map of Stream to FrameBuffer pointers\n> > > @@ -85,6 +94,35 @@ Request::~Request()\n> > >  \tdelete validator_;\n> > >  }\n> > >\n> > > +/**\n> > > + * \\brief Reset the request for reuse\n> > > + * \\param[in] flags Indicate whether or not to reuse the buffers\n> > > + *\n> > > + * Reset the status and controls associated with the request, to allow it to\n> > > + * be reused and requeued without destruction. This function shall be called\n> > > + * prior to queueing the request to the camera, in lieu of constructing a new\n> > > + * request. The application can reuse the buffers that were previously added\n> > > + * to the request via addBuffer() by setting \\a flags to ReuseBuffers.\n> > > + */\n> > > +void Request::reuse(ReuseFlag flags)\n> > > +{\n> > > +\tpending_.clear();\n> > > +\tif (flags & ReuseBuffers) {\n> > > +\t\tfor (auto pair : bufferMap_) {\n> > > +\t\t\tpair.second->request_ = this;\n> > > +\t\t\tpending_.insert(pair.second);\n> > > +\t\t}\n> > > +\t} else {\n> > > +\t\tbufferMap_.clear();\n> > > +\t}\n> > > +\n> > > +\tstatus_ = RequestPending;\n> > > +\tcancelled_ = false;\n> > > +\n> > > +\tcontrols_->clear();\n> > > +\tmetadata_->clear();\n> > > +}\n> > > +\n> > >  /**\n> > >   * \\fn Request::controls()\n> > >   * \\brief Retrieve the request's ControlList\n> > > diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp\n> > > index ecb9dd66..0cbdab9a 100644\n> > > --- a/src/qcam/main_window.cpp\n> > > +++ b/src/qcam/main_window.cpp\n> > > @@ -367,7 +367,6 @@ void MainWindow::toggleCapture(bool start)\n> > >  int MainWindow::startCapture()\n> > >  {\n> > >  \tStreamRoles roles = StreamKeyValueParser::roles(options_[OptStream]);\n> > > -\tstd::vector<Request *> requests;\n> > >  \tint ret;\n> > >\n> > >  \t/* Verify roles are supported. */\n> > > @@ -486,7 +485,7 @@ int MainWindow::startCapture()\n> > >  \twhile (!freeBuffers_[vfStream_].isEmpty()) {\n> > >  \t\tFrameBuffer *buffer = freeBuffers_[vfStream_].dequeue();\n> > >\n> > > -\t\tRequest *request = camera_->createRequest();\n> > > +\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> > >  \t\tif (!request) {\n> > >  \t\t\tqWarning() << \"Can't create request\";\n> > >  \t\t\tret = -ENOMEM;\n> > > @@ -499,7 +498,7 @@ int MainWindow::startCapture()\n> > >  \t\t\tgoto error;\n> > >  \t\t}\n> > >\n> > > -\t\trequests.push_back(request);\n> > > +\t\trequests_.push_back(std::move(request));\n> > >  \t}\n> > >\n> > >  \t/* Start the title timer and the camera. */\n> > > @@ -518,8 +517,8 @@ int MainWindow::startCapture()\n> > >  \tcamera_->requestCompleted.connect(this, &MainWindow::requestComplete);\n> > >\n> > >  \t/* Queue all requests. */\n> > > -\tfor (Request *request : requests) {\n> > > -\t\tret = camera_->queueRequest(request);\n> > > +\tfor (std::unique_ptr<Request> &request : requests_) {\n> > > +\t\tret = camera_->queueRequest(request.get());\n> > >  \t\tif (ret < 0) {\n> > >  \t\t\tqWarning() << \"Can't queue request\";\n> > >  \t\t\tgoto error_disconnect;\n> > > @@ -535,8 +534,7 @@ error_disconnect:\n> > >  \tcamera_->stop();\n> > >\n> > >  error:\n> > > -\tfor (Request *request : requests)\n> > > -\t\tdelete request;\n> > > +\trequests_.clear();\n> > >\n> > >  \tfor (auto &iter : mappedBuffers_) {\n> > >  \t\tconst MappedBuffer &buffer = iter.second;\n> > > @@ -580,6 +578,8 @@ void MainWindow::stopCapture()\n> > >  \t}\n> > >  \tmappedBuffers_.clear();\n> > >\n> > > +\trequests_.clear();\n> > > +\n> > >  \tdelete allocator_;\n> > >\n> > >  \tisCapturing_ = false;\n> > > @@ -701,7 +701,7 @@ void MainWindow::requestComplete(Request *request)\n> > >  \t */\n> > >  \t{\n> > >  \t\tQMutexLocker locker(&mutex_);\n> > > -\t\tdoneQueue_.enqueue({ request->buffers(), request->metadata() });\n> > > +\t\tdoneQueue_.enqueue(request);\n> > >  \t}\n> > >\n> > >  \tQCoreApplication::postEvent(this, new CaptureEvent);\n> > > @@ -714,8 +714,7 @@ void MainWindow::processCapture()\n> > >  \t * if stopCapture() has been called while a CaptureEvent was posted but\n> > >  \t * not processed yet. Return immediately in that case.\n> > >  \t */\n> > > -\tCaptureRequest request;\n> > > -\n> > > +\tRequest *request;\n> > >  \t{\n> > >  \t\tQMutexLocker locker(&mutex_);\n> > >  \t\tif (doneQueue_.isEmpty())\n> > > @@ -725,11 +724,15 @@ void MainWindow::processCapture()\n> > >  \t}\n> > >\n> > >  \t/* Process buffers. */\n> > > -\tif (request.buffers_.count(vfStream_))\n> > > -\t\tprocessViewfinder(request.buffers_[vfStream_]);\n> > > +\tif (request->buffers().count(vfStream_))\n> > > +\t\tprocessViewfinder(request->buffers().at(vfStream_));\n> > >\n> > > -\tif (request.buffers_.count(rawStream_))\n> > > -\t\tprocessRaw(request.buffers_[rawStream_], request.metadata_);\n> > > +\tif (request->buffers().count(rawStream_))\n> > > +\t\tprocessRaw(request->buffers().at(rawStream_), request->metadata());\n> > > +\n> > > +\trequest->reuse();\n> > > +\tQMutexLocker locker(&mutex_);\n> > > +\tfreeQueue_.enqueue(request);\n> > >  }\n> > >\n> > >  void MainWindow::processViewfinder(FrameBuffer *buffer)\n> > > @@ -754,10 +757,13 @@ void MainWindow::processViewfinder(FrameBuffer *buffer)\n> > >\n> > >  void MainWindow::queueRequest(FrameBuffer *buffer)\n> > >  {\n> > > -\tRequest *request = camera_->createRequest();\n> > > -\tif (!request) {\n> > > -\t\tqWarning() << \"Can't create request\";\n> > > -\t\treturn;\n> > > +\tRequest *request;\n> > > +\t{\n> > > +\t\tQMutexLocker locker(&mutex_);\n> > > +\t\tif (freeQueue_.isEmpty())\n> > > +\t\t\treturn;\n> > > +\n> > > +\t\trequest = freeQueue_.dequeue();\n> > >  \t}\n> > >\n> > >  \trequest->addBuffer(vfStream_, buffer);\n> > > diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h\n> > > index 5c61a4df..64bcfebc 100644\n> > > --- a/src/qcam/main_window.h\n> > > +++ b/src/qcam/main_window.h\n> > > @@ -8,6 +8,7 @@\n> > >  #define __QCAM_MAIN_WINDOW_H__\n> > >\n> > >  #include <memory>\n> > > +#include <vector>\n> > >\n> > >  #include <QElapsedTimer>\n> > >  #include <QIcon>\n> > > @@ -22,6 +23,7 @@\n> > >  #include <libcamera/camera_manager.h>\n> > >  #include <libcamera/controls.h>\n> > >  #include <libcamera/framebuffer_allocator.h>\n> > > +#include <libcamera/request.h>\n> > >  #include <libcamera/stream.h>\n> > >\n> > >  #include \"../cam/stream_options.h\"\n> > > @@ -41,23 +43,6 @@ enum {\n> > >  \tOptStream = 's',\n> > >  };\n> > >\n> > > -class CaptureRequest\n> > > -{\n> > > -public:\n> > > -\tCaptureRequest()\n> > > -\t{\n> > > -\t}\n> > > -\n> > > -\tCaptureRequest(const Request::BufferMap &buffers,\n> > > -\t\t       const ControlList &metadata)\n> > > -\t\t: buffers_(buffers), metadata_(metadata)\n> > > -\t{\n> > > -\t}\n> > > -\n> > > -\tRequest::BufferMap buffers_;\n> > > -\tControlList metadata_;\n> > > -};\n> > > -\n> > >  class MainWindow : public QMainWindow\n> > >  {\n> > >  \tQ_OBJECT\n> > > @@ -128,13 +113,16 @@ private:\n> > >  \tStream *vfStream_;\n> > >  \tStream *rawStream_;\n> > >  \tstd::map<const Stream *, QQueue<FrameBuffer *>> freeBuffers_;\n> > > -\tQQueue<CaptureRequest> doneQueue_;\n> > > -\tQMutex mutex_; /* Protects freeBuffers_ and doneQueue_ */\n> > > +\tQQueue<Request *> doneQueue_;\n> > > +\tQQueue<Request *> freeQueue_;\n> > > +\tQMutex mutex_; /* Protects freeBuffers_, doneQueue_, and freeQueue_ */\n> > >\n> > >  \tuint64_t lastBufferTime_;\n> > >  \tQElapsedTimer frameRateInterval_;\n> > >  \tuint32_t previousFrames_;\n> > >  \tuint32_t framesCaptured_;\n> > > +\n> > > +\tstd::vector<std::unique_ptr<Request>> requests_;\n> > >  };\n> > >\n> > >  #endif /* __QCAM_MAIN_WINDOW__ */\n> > > diff --git a/src/v4l2/v4l2_camera.cpp b/src/v4l2/v4l2_camera.cpp\n> > > index 3565f369..35d3beda 100644\n> > > --- a/src/v4l2/v4l2_camera.cpp\n> > > +++ b/src/v4l2/v4l2_camera.cpp\n> > > @@ -49,6 +49,8 @@ int V4L2Camera::open(StreamConfiguration *streamConfig)\n> > >\n> > >  void V4L2Camera::close()\n> > >  {\n> > > +\trequestPool_.clear();\n> > > +\n> > >  \tdelete bufferAllocator_;\n> > >  \tbufferAllocator_ = nullptr;\n> > >\n> > > @@ -96,6 +98,7 @@ void V4L2Camera::requestComplete(Request *request)\n> > >  \tif (ret != sizeof(data))\n> > >  \t\tLOG(V4L2Compat, Error) << \"Failed to signal eventfd POLLIN\";\n> > >\n> > > +\trequest->reuse();\n> > >  \t{\n> > >  \t\tMutexLocker locker(bufferMutex_);\n> > >  \t\tbufferAvailableCount_++;\n> > > @@ -154,16 +157,30 @@ int V4L2Camera::validateConfiguration(const PixelFormat &pixelFormat,\n> > >  \treturn 0;\n> > >  }\n> > >\n> > > -int V4L2Camera::allocBuffers([[maybe_unused]] unsigned int count)\n> > > +int V4L2Camera::allocBuffers(unsigned int count)\n> > >  {\n> > >  \tStream *stream = config_->at(0).stream();\n> > >\n> > > -\treturn bufferAllocator_->allocate(stream);\n> > > +\tint ret = bufferAllocator_->allocate(stream);\n> > > +\tif (ret < 0)\n> > > +\t\treturn ret;\n> > > +\n> > > +\tfor (unsigned int i = 0; i < count; i++) {\n> > > +\t\tstd::unique_ptr<Request> request = camera_->createRequest(i);\n> > > +\t\tif (!request) {\n> > > +\t\t\trequestPool_.clear();\n> > > +\t\t\treturn -ENOMEM;\n> > > +\t\t}\n> > > +\t\trequestPool_.push_back(std::move(request));\n> > > +\t}\n> > > +\n> > > +\treturn ret;\n> > >  }\n> > >\n> > >  void V4L2Camera::freeBuffers()\n> > >  {\n> > >  \tpendingRequests_.clear();\n> > > +\trequestPool_.clear();\n> > >\n> > >  \tStream *stream = config_->at(0).stream();\n> > >  \tbufferAllocator_->free(stream);\n> > > @@ -192,9 +209,9 @@ int V4L2Camera::streamOn()\n> > >\n> > >  \tisRunning_ = true;\n> > >\n> > > -\tfor (std::unique_ptr<Request> &req : pendingRequests_) {\n> > > +\tfor (Request *req : pendingRequests_) {\n> > >  \t\t/* \\todo What should we do if this returns -EINVAL? */\n> > > -\t\tret = camera_->queueRequest(req.release());\n> > > +\t\tret = camera_->queueRequest(req);\n> > >  \t\tif (ret < 0)\n> > >  \t\t\treturn ret == -EACCES ? -EBUSY : ret;\n> > >  \t}\n> > > @@ -206,8 +223,12 @@ int V4L2Camera::streamOn()\n> > >\n> > >  int V4L2Camera::streamOff()\n> > >  {\n> > > -\tif (!isRunning_)\n> > > +\tif (!isRunning_) {\n> > > +\t\tfor (std::unique_ptr<Request> &req : requestPool_)\n> > > +\t\t\treq->reuse();\n> > > +\n> > >  \t\treturn 0;\n> > > +\t}\n> > >\n> > >  \tpendingRequests_.clear();\n> > >\n> > > @@ -226,12 +247,11 @@ int V4L2Camera::streamOff()\n> > >\n> > >  int V4L2Camera::qbuf(unsigned int index)\n> > >  {\n> > > -\tstd::unique_ptr<Request> request =\n> > > -\t\tstd::unique_ptr<Request>(camera_->createRequest(index));\n> > > -\tif (!request) {\n> > > -\t\tLOG(V4L2Compat, Error) << \"Can't create request\";\n> > > -\t\treturn -ENOMEM;\n> > > +\tif (index >= requestPool_.size()) {\n> > > +\t\tLOG(V4L2Compat, Error) << \"Invalid index\";\n> > > +\t\treturn -EINVAL;\n> > >  \t}\n> > > +\tRequest *request = requestPool_[index].get();\n> > >\n> > >  \tStream *stream = config_->at(0).stream();\n> > >  \tFrameBuffer *buffer = bufferAllocator_->buffers(stream)[index].get();\n> > > @@ -242,11 +262,11 @@ int V4L2Camera::qbuf(unsigned int index)\n> > >  \t}\n> > >\n> > >  \tif (!isRunning_) {\n> > > -\t\tpendingRequests_.push_back(std::move(request));\n> > > +\t\tpendingRequests_.push_back(request);\n> > >  \t\treturn 0;\n> > >  \t}\n> > >\n> > > -\tret = camera_->queueRequest(request.release());\n> > > +\tret = camera_->queueRequest(request);\n> > >  \tif (ret < 0) {\n> > >  \t\tLOG(V4L2Compat, Error) << \"Can't queue request\";\n> > >  \t\treturn ret == -EACCES ? -EBUSY : ret;\n> > > diff --git a/src/v4l2/v4l2_camera.h b/src/v4l2/v4l2_camera.h\n> > > index 1fc5ebef..a6c35a2e 100644\n> > > --- a/src/v4l2/v4l2_camera.h\n> > > +++ b/src/v4l2/v4l2_camera.h\n> > > @@ -76,7 +76,9 @@ private:\n> > >  \tstd::mutex bufferLock_;\n> > >  \tFrameBufferAllocator *bufferAllocator_;\n> > >\n> > > -\tstd::deque<std::unique_ptr<Request>> pendingRequests_;\n> > > +\tstd::vector<std::unique_ptr<Request>> requestPool_;\n> > > +\n> > > +\tstd::deque<Request *> pendingRequests_;\n> > >  \tstd::deque<std::unique_ptr<Buffer>> completedBuffers_;\n> > >\n> > >  \tint efd_;\n> > > diff --git a/test/camera/buffer_import.cpp b/test/camera/buffer_import.cpp\n> > > index 64e96264..72ce7b79 100644\n> > > --- a/test/camera/buffer_import.cpp\n> > > +++ b/test/camera/buffer_import.cpp\n> > > @@ -58,7 +58,7 @@ protected:\n> > >  \t\tconst Stream *stream = buffers.begin()->first;\n> > >  \t\tFrameBuffer *buffer = buffers.begin()->second;\n> > >\n> > > -\t\trequest = camera_->createRequest();\n> > > +\t\trequest->reuse();\n> > >  \t\trequest->addBuffer(stream, buffer);\n> > >  \t\tcamera_->queueRequest(request);\n> > >  \t}\n> > > @@ -98,9 +98,8 @@ protected:\n> > >  \t\tif (ret != TestPass)\n> > >  \t\t\treturn ret;\n> > >\n> > > -\t\tstd::vector<Request *> requests;\n> > >  \t\tfor (const std::unique_ptr<FrameBuffer> &buffer : source.buffers()) {\n> > > -\t\t\tRequest *request = camera_->createRequest();\n> > > +\t\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> > >  \t\t\tif (!request) {\n> > >  \t\t\t\tstd::cout << \"Failed to create request\" << std::endl;\n> > >  \t\t\t\treturn TestFail;\n> > > @@ -111,7 +110,7 @@ protected:\n> > >  \t\t\t\treturn TestFail;\n> > >  \t\t\t}\n> > >\n> > > -\t\t\trequests.push_back(request);\n> > > +\t\t\trequests_.push_back(std::move(request));\n> > >  \t\t}\n> > >\n> > >  \t\tcompleteRequestsCount_ = 0;\n> > > @@ -125,8 +124,8 @@ protected:\n> > >  \t\t\treturn TestFail;\n> > >  \t\t}\n> > >\n> > > -\t\tfor (Request *request : requests) {\n> > > -\t\t\tif (camera_->queueRequest(request)) {\n> > > +\t\tfor (std::unique_ptr<Request> &request : requests_) {\n> > > +\t\t\tif (camera_->queueRequest(request.get())) {\n> > >  \t\t\t\tstd::cout << \"Failed to queue request\" << std::endl;\n> > >  \t\t\t\treturn TestFail;\n> > >  \t\t\t}\n> > > @@ -160,6 +159,8 @@ protected:\n> > >  \t}\n> > >\n> > >  private:\n> > > +\tstd::vector<std::unique_ptr<Request>> requests_;\n> > > +\n> > >  \tunsigned int completeBuffersCount_;\n> > >  \tunsigned int completeRequestsCount_;\n> > >  \tstd::unique_ptr<CameraConfiguration> config_;\n> > > diff --git a/test/camera/capture.cpp b/test/camera/capture.cpp\n> > > index 51bbd258..c0770801 100644\n> > > --- a/test/camera/capture.cpp\n> > > +++ b/test/camera/capture.cpp\n> > > @@ -52,7 +52,7 @@ protected:\n> > >  \t\tconst Stream *stream = buffers.begin()->first;\n> > >  \t\tFrameBuffer *buffer = buffers.begin()->second;\n> > >\n> > > -\t\trequest = camera_->createRequest();\n> > > +\t\trequest->reuse();\n> > >  \t\trequest->addBuffer(stream, buffer);\n> > >  \t\tcamera_->queueRequest(request);\n> > >  \t}\n> > > @@ -98,9 +98,8 @@ protected:\n> > >  \t\tif (ret < 0)\n> > >  \t\t\treturn TestFail;\n> > >\n> > > -\t\tstd::vector<Request *> requests;\n> > >  \t\tfor (const std::unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) {\n> > > -\t\t\tRequest *request = camera_->createRequest();\n> > > +\t\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> > >  \t\t\tif (!request) {\n> > >  \t\t\t\tcout << \"Failed to create request\" << endl;\n> > >  \t\t\t\treturn TestFail;\n> > > @@ -111,7 +110,7 @@ protected:\n> > >  \t\t\t\treturn TestFail;\n> > >  \t\t\t}\n> > >\n> > > -\t\t\trequests.push_back(request);\n> > > +\t\t\trequests_.push_back(std::move(request));\n> > >  \t\t}\n> > >\n> > >  \t\tcompleteRequestsCount_ = 0;\n> > > @@ -125,8 +124,8 @@ protected:\n> > >  \t\t\treturn TestFail;\n> > >  \t\t}\n> > >\n> > > -\t\tfor (Request *request : requests) {\n> > > -\t\t\tif (camera_->queueRequest(request)) {\n> > > +\t\tfor (std::unique_ptr<Request> &request : requests_) {\n> > > +\t\t\tif (camera_->queueRequest(request.get())) {\n> > >  \t\t\t\tcout << \"Failed to queue request\" << endl;\n> > >  \t\t\t\treturn TestFail;\n> > >  \t\t\t}\n> > > @@ -161,6 +160,8 @@ protected:\n> > >  \t\treturn TestPass;\n> > >  \t}\n> > >\n> > > +\tstd::vector<std::unique_ptr<Request>> requests_;\n> > > +\n> > >  \tstd::unique_ptr<CameraConfiguration> config_;\n> > >  \tFrameBufferAllocator *allocator_;\n> > >  };\n> > > diff --git a/test/camera/statemachine.cpp b/test/camera/statemachine.cpp\n> > > index 28faeb91..e63ab298 100644\n> > > --- a/test/camera/statemachine.cpp\n> > > +++ b/test/camera/statemachine.cpp\n> > > @@ -101,13 +101,10 @@ protected:\n> > >  \t\t\treturn TestFail;\n> > >\n> > >  \t\t/* Test operations which should pass. */\n> > > -\t\tRequest *request2 = camera_->createRequest();\n> > > +\t\tstd::unique_ptr<Request> request2 = camera_->createRequest();\n> > >  \t\tif (!request2)\n> > >  \t\t\treturn TestFail;\n> > >\n> > > -\t\t/* Never handed to hardware so need to manually delete it. */\n> > > -\t\tdelete request2;\n> > > -\n> > >  \t\t/* Test valid state transitions, end in Running state. */\n> > >  \t\tif (camera_->release())\n> > >  \t\t\treturn TestFail;\n> > > @@ -146,7 +143,7 @@ protected:\n> > >  \t\t\treturn TestFail;\n> > >\n> > >  \t\t/* Test operations which should pass. */\n> > > -\t\tRequest *request = camera_->createRequest();\n> > > +\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> > >  \t\tif (!request)\n> > >  \t\t\treturn TestFail;\n> > >\n> > > @@ -154,7 +151,7 @@ protected:\n> > >  \t\tif (request->addBuffer(stream, allocator_->buffers(stream)[0].get()))\n> > >  \t\t\treturn TestFail;\n> > >\n> > > -\t\tif (camera_->queueRequest(request))\n> > > +\t\tif (camera_->queueRequest(request.get()))\n> > >  \t\t\treturn TestFail;\n> > >\n> > >  \t\t/* Test valid state transitions, end in Available state. */\n>\n> --\n> Regards,\n>\n> Laurent Pinchart","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 25F93BEEDF\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri,  9 Oct 2020 12:41:34 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id A899A6073B;\n\tFri,  9 Oct 2020 14:41:33 +0200 (CEST)","from relay11.mail.gandi.net (relay11.mail.gandi.net\n\t[217.70.178.231])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 2493E60358\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  9 Oct 2020 14:41:32 +0200 (CEST)","from uno.localdomain (93-34-118-233.ip49.fastwebnet.it\n\t[93.34.118.233]) (Authenticated sender: jacopo@jmondi.org)\n\tby relay11.mail.gandi.net (Postfix) with ESMTPSA id 45FC010000F;\n\tFri,  9 Oct 2020 12:41:30 +0000 (UTC)"],"Date":"Fri, 9 Oct 2020 14:45:30 +0200","From":"Jacopo Mondi <jacopo@jmondi.org>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Message-ID":"<20201009124530.mizyxnce7kbbq2yz@uno.localdomain>","References":"<20201007073418.512656-1-paul.elder@ideasonboard.com>\n\t<20201007094331.2dl4xwdeqzf4isix@uno.localdomain>\n\t<20201008024540.GT3937@pendragon.ideasonboard.com>","MIME-Version":"1.0","Content-Disposition":"inline","In-Reply-To":"<20201008024540.GT3937@pendragon.ideasonboard.com>","Subject":"Re: [libcamera-devel] [PATCH v6] libcamera, android, cam, gstreamer,\n\tqcam, v4l2: Reuse Request","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Cc":"libcamera-devel@lists.libcamera.org","Content-Type":"text/plain; charset=\"us-ascii\"","Content-Transfer-Encoding":"7bit","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":13132,"web_url":"https://patchwork.libcamera.org/comment/13132/","msgid":"<20201009124713.xlc7ksaa3xmahw6h@uno.localdomain>","date":"2020-10-09T12:47:13","subject":"Re: [libcamera-devel] [PATCH v6] libcamera, android, cam, gstreamer,\n\tqcam, v4l2: Reuse Request","submitter":{"id":3,"url":"https://patchwork.libcamera.org/api/people/3/","name":"Jacopo Mondi","email":"jacopo@jmondi.org"},"content":"Hi Paul,\n\nOn Thu, Oct 08, 2020 at 03:29:20PM +0900, paul.elder@ideasonboard.com wrote:\n> Hi Jacopo,\n>\n> On Wed, Oct 07, 2020 at 11:43:31AM +0200, Jacopo Mondi wrote:\n> > Hi Paul,\n> >\n> > On Wed, Oct 07, 2020 at 04:34:18PM +0900, Paul Elder wrote:\n> > > Allow reuse of the Request object by implementing reuse(). This means\n> > > the applications now have the responsibility of freeing the Request\n> > > objects, so make all libcamera users (cam, qcam, v4l2-compat, gstreamer,\n> > > android) do so.\n> >\n> > Maybe not a question to ask at v6, I've gone through the previous\n> > iterations and I see the proposed API has been discussed already, so\n> > feel free to ignore, but: I'm not sure I like the flags passed to\n> > reuse() :)\n> >\n> > To be more precise, I don't see much much value in ReuseBuffers.\n> >\n> > How usual is it that the same [Stream|buffer] pair is repeated ? I\n> > understand we might want to capture from the same set of Streams, but\n> > application would more likely cycle buffers, don't they ?\n>\n> It depends on the application. In cam we have:\n>\n> -       /*\n> -        * Create a new request and populate it with one buffer for each\n> -        * stream.\n> -        */\n> -       request = camera_->createRequest();\n> -       if (!request) {\n> -               std::cerr << \"Can't create request\" << std::endl;\n> -               return;\n> -       }\n> -\n> -       for (auto it = buffers.begin(); it != buffers.end(); ++it) {\n> -               const Stream *stream = it->first;\n> -               FrameBuffer *buffer = it->second;\n> -\n> -               request->addBuffer(stream, buffer);\n> -       }\n> -\n> +       request->reuse(Request::ReuseBuffers);\n>\n> Without ReuseBuffers, we'd have:\n>\n> -       /*\n> -        * Create a new request and populate it with one buffer for each\n> -        * stream.\n> -        */\n> -       request = camera_->createRequest();\n> -       if (!request) {\n> -               std::cerr << \"Can't create request\" << std::endl;\n> -               return;\n> -       }\n> -\n> +       request->reuse();\n>         for (auto it = buffers.begin(); it != buffers.end(); ++it) {\n>                 const Stream *stream = it->first;\n>                 FrameBuffer *buffer = it->second;\n>\n>                 request->addBuffer(stream, buffer);\n>         }\n>\n> Which still isn't that bad imo but somebody requested ReuseBuffers.\n>\n> > I understand the ReuseBuffers flag might be used to model a\n> > 'repeating' request mechanism, but I don't think our API is currently\n> > ready to support such a feature. All our requests are one-shot, they\n> > get queued, notified when completed, and need to be re-queued. There's\n> > nothing like a mechanism that let's you create one request, queue it\n> > once, and have it running until you don't stop you (without the need\n> > for a requeue. For reference [1]). Implementing something like this\n> > will need some more thinking, in example how to make it possible for a\n> > repeating request to cycle on a set of buffers automatically.\n> >\n> > I don't think we should try to emulate a repeating mechanism by\n> > allowing application to re-use the same stream-buffer pair. It has a\n> > limited use (in my understanding) and will confuse application as they\n> > need anyway to re-queue it.\n>\n> Ah, I see. I guess this is more of convenience for the specific use case\n> of cam, and whatever applications that will have a similar demand.\n>\n> > Don't get me wrong, I think moving ownership of Request to application\n> > is correct and more clear, but I don't see much use for the flag [*]\n>\n> I think the flag is for future-proofing... if we want to add any other\n> options to resetting the reuqest.\n>\n> > I would prefer if we keep thinking of our request as one shot, so they\n> > could either be reset everytime, and application will have to\n> > addBuffers() again as they will anyway have to cycle buffers.\n>\n> I think we still preserve that. The application can use the request as\n> one-shot if it wants to, or it can reset it everytime. We're just\n> providing an option to automatically re-addBuffers().\n>\n\nThanks for the clarifications. I'm still not in love with the flags,\nbut indeed that's not a blocker.\n\nPlease add:\nReviewed-by: Jacopo Mondi <jacopo@jmondi.org>\n\nThanks\n   j\n\n>\n> Paul\n>\n> > [*] This makes me unconfortable for another reason. I understand we\n> > might want flags for, say, Controls handling and other possible tuning\n> > (have you though about other possible flags ?). I don't think the\n> > output buffer handling lives in the same category as low level control\n> > of the Request, it's a primary Request feature and should not be expressed\n> > with flags as in example \"Use template XYZ for the Request's controls\"\n> >\n> > [1] https://medium.com/androiddevelopers/understanding-android-camera-capture-sessions-and-requests-4e54d9150295\n> >\n> > >\n> > > Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> > > Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> > >\n> > > ---\n> > > Changes in v6:\n> > > - fix doxygen\n> > > - rename enum ReuseBuffer to ReuseFlag, and change to flags\n> > > - move request->reuse() in v4l2-compat from qbuf time to completion\n> > >   time\n> > >   - streamOff when the stream is already off needs to also\n> > >     request->reuse(), so do that too\n> > > - update documentation\n> > >\n> > > Changes in v5:\n> > > - rename reset() to reuse()\n> > > - make reuse()'s reuseBuffers parameter into enum instead of bool\n> > > - fix qcam qt assertion error on the first queueRequest, where\n> > >   freeQueue_ is empty\n> > >\n> > > Changes in v4:\n> > > - no more implicit calls of anything that we added in this patch\n> > > - make reset() take a reuseBuffers boolean parameters\n> > >   - use transient request - delete request\n> > >   - reuse request, reset buffers - reset()\n> > >   - reuse request, reuse buffesr - reset(true)\n> > > - update apps and tests and documentation accordingly\n> > >\n> > > Changes in v3:\n> > > - reset() is called in Camera::queueRequest()\n> > > - apps that use transient request (android, gstreamer) delete the\n> > >   request at the end of the completion handler\n> > > - apps that want to reuse the buffers (cam) use reAddBuffers()\n> > > - apps that need to change the buffers (qcam, v4l2) use clearBuffers()\n> > > - update the documentation accordingly\n> > >\n> > > Changes in v2:\n> > > - clear controls_ and metadata_ and validator_ in Request::reset()\n> > > - use unique_ptr on application side, prior to queueRequest, and use\n> > >   regular pointer for completion handler\n> > > - make qcam's reuse request nicer\n> > > - update Camera::queueRequest() and Camera::createRequest() documentation\n> > > - add documentation for Request::reset()\n> > > - make v4l2-compat reuse request\n> > > - make gstreamer and android use the new createRequest API, though they\n> > >   do not actually reuse the requests\n> > > ---\n> > >  include/libcamera/camera.h        |  2 +-\n> > >  include/libcamera/request.h       |  7 +++++\n> > >  src/android/camera_device.cpp     | 14 +++++++---\n> > >  src/cam/capture.cpp               | 29 +++++---------------\n> > >  src/cam/capture.h                 |  3 +++\n> > >  src/gstreamer/gstlibcamerasrc.cpp | 14 ++++++----\n> > >  src/libcamera/camera.cpp          | 14 ++++------\n> > >  src/libcamera/request.cpp         | 38 ++++++++++++++++++++++++++\n> > >  src/qcam/main_window.cpp          | 42 ++++++++++++++++-------------\n> > >  src/qcam/main_window.h            | 26 +++++-------------\n> > >  src/v4l2/v4l2_camera.cpp          | 44 ++++++++++++++++++++++---------\n> > >  src/v4l2/v4l2_camera.h            |  4 ++-\n> > >  test/camera/buffer_import.cpp     | 13 ++++-----\n> > >  test/camera/capture.cpp           | 13 ++++-----\n> > >  test/camera/statemachine.cpp      |  9 +++----\n> > >  15 files changed, 163 insertions(+), 109 deletions(-)\n> > >\n> > > diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> > > index a2ee4e7e..79ff8d6b 100644\n> > > --- a/include/libcamera/camera.h\n> > > +++ b/include/libcamera/camera.h\n> > > @@ -96,7 +96,7 @@ public:\n> > >  \tstd::unique_ptr<CameraConfiguration> generateConfiguration(const StreamRoles &roles = {});\n> > >  \tint configure(CameraConfiguration *config);\n> > >\n> > > -\tRequest *createRequest(uint64_t cookie = 0);\n> > > +\tstd::unique_ptr<Request> createRequest(uint64_t cookie = 0);\n> > >  \tint queueRequest(Request *request);\n> > >\n> > >  \tint start();\n> > > diff --git a/include/libcamera/request.h b/include/libcamera/request.h\n> > > index 5976ac50..655b1324 100644\n> > > --- a/include/libcamera/request.h\n> > > +++ b/include/libcamera/request.h\n> > > @@ -31,6 +31,11 @@ public:\n> > >  \t\tRequestCancelled,\n> > >  \t};\n> > >\n> > > +\tenum ReuseFlag {\n> > > +\t\tDefault = 0,\n> > > +\t\tReuseBuffers = (1 << 0),\n> > > +\t};\n> > > +\n> > >  \tusing BufferMap = std::map<const Stream *, FrameBuffer *>;\n> > >\n> > >  \tRequest(Camera *camera, uint64_t cookie = 0);\n> > > @@ -38,6 +43,8 @@ public:\n> > >  \tRequest &operator=(const Request &) = delete;\n> > >  \t~Request();\n> > >\n> > > +\tvoid reuse(ReuseFlag flags = Default);\n> > > +\n> > >  \tControlList &controls() { return *controls_; }\n> > >  \tControlList &metadata() { return *metadata_; }\n> > >  \tconst BufferMap &buffers() const { return bufferMap_; }\n> > > diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp\n> > > index 751699cd..052c9292 100644\n> > > --- a/src/android/camera_device.cpp\n> > > +++ b/src/android/camera_device.cpp\n> > > @@ -1395,8 +1395,12 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n> > >  \t\tnew Camera3RequestDescriptor(camera3Request->frame_number,\n> > >  \t\t\t\t\t     camera3Request->num_output_buffers);\n> > >\n> > > -\tRequest *request =\n> > > +\tstd::unique_ptr<Request> request =\n> > >  \t\tcamera_->createRequest(reinterpret_cast<uint64_t>(descriptor));\n> > > +\tif (!request) {\n> > > +\t\tLOG(HAL, Error) << \"Failed to create request\";\n> > > +\t\treturn -ENOMEM;\n> > > +\t}\n> > >\n> > >  \tfor (unsigned int i = 0; i < descriptor->numBuffers; ++i) {\n> > >  \t\tCameraStream *cameraStream =\n> > > @@ -1422,7 +1426,6 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n> > >  \t\tFrameBuffer *buffer = createFrameBuffer(*camera3Buffers[i].buffer);\n> > >  \t\tif (!buffer) {\n> > >  \t\t\tLOG(HAL, Error) << \"Failed to create buffer\";\n> > > -\t\t\tdelete request;\n> > >  \t\t\tdelete descriptor;\n> > >  \t\t\treturn -ENOMEM;\n> > >  \t\t}\n> > > @@ -1434,14 +1437,16 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n> > >  \t\trequest->addBuffer(stream, buffer);\n> > >  \t}\n> > >\n> > > -\tint ret = camera_->queueRequest(request);\n> > > +\tint ret = camera_->queueRequest(request.get());\n> > >  \tif (ret) {\n> > >  \t\tLOG(HAL, Error) << \"Failed to queue request\";\n> > > -\t\tdelete request;\n> > >  \t\tdelete descriptor;\n> > >  \t\treturn ret;\n> > >  \t}\n> > >\n> > > +\t/* The request will be deleted in the completion handler. */\n> > > +\trequest.release();\n> > > +\n> > >  \treturn 0;\n> > >  }\n> > >\n> > > @@ -1593,6 +1598,7 @@ void CameraDevice::requestComplete(Request *request)\n> > >  \tcallbacks_->process_capture_result(callbacks_, &captureResult);\n> > >\n> > >  \tdelete descriptor;\n> > > +\tdelete request;\n> > >  }\n> > >\n> > >  std::string CameraDevice::logPrefix() const\n> > > diff --git a/src/cam/capture.cpp b/src/cam/capture.cpp\n> > > index 5510c009..8c8faa4b 100644\n> > > --- a/src/cam/capture.cpp\n> > > +++ b/src/cam/capture.cpp\n> > > @@ -65,6 +65,8 @@ int Capture::run(const OptionsParser::Options &options)\n> > >  \t\twriter_ = nullptr;\n> > >  \t}\n> > >\n> > > +\trequests_.clear();\n> > > +\n> > >  \tdelete allocator;\n> > >\n> > >  \treturn ret;\n> > > @@ -92,9 +94,8 @@ int Capture::capture(FrameBufferAllocator *allocator)\n> > >  \t * example pushing a button. For now run all streams all the time.\n> > >  \t */\n> > >\n> > > -\tstd::vector<Request *> requests;\n> > >  \tfor (unsigned int i = 0; i < nbuffers; i++) {\n> > > -\t\tRequest *request = camera_->createRequest();\n> > > +\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> > >  \t\tif (!request) {\n> > >  \t\t\tstd::cerr << \"Can't create request\" << std::endl;\n> > >  \t\t\treturn -ENOMEM;\n> > > @@ -117,7 +118,7 @@ int Capture::capture(FrameBufferAllocator *allocator)\n> > >  \t\t\t\twriter_->mapBuffer(buffer.get());\n> > >  \t\t}\n> > >\n> > > -\t\trequests.push_back(request);\n> > > +\t\trequests_.push_back(std::move(request));\n> > >  \t}\n> > >\n> > >  \tret = camera_->start();\n> > > @@ -126,8 +127,8 @@ int Capture::capture(FrameBufferAllocator *allocator)\n> > >  \t\treturn ret;\n> > >  \t}\n> > >\n> > > -\tfor (Request *request : requests) {\n> > > -\t\tret = camera_->queueRequest(request);\n> > > +\tfor (std::unique_ptr<Request> &request : requests_) {\n> > > +\t\tret = camera_->queueRequest(request.get());\n> > >  \t\tif (ret < 0) {\n> > >  \t\t\tstd::cerr << \"Can't queue request\" << std::endl;\n> > >  \t\t\tcamera_->stop();\n> > > @@ -202,22 +203,6 @@ void Capture::requestComplete(Request *request)\n> > >  \t\treturn;\n> > >  \t}\n> > >\n> > > -\t/*\n> > > -\t * Create a new request and populate it with one buffer for each\n> > > -\t * stream.\n> > > -\t */\n> > > -\trequest = camera_->createRequest();\n> > > -\tif (!request) {\n> > > -\t\tstd::cerr << \"Can't create request\" << std::endl;\n> > > -\t\treturn;\n> > > -\t}\n> > > -\n> > > -\tfor (auto it = buffers.begin(); it != buffers.end(); ++it) {\n> > > -\t\tconst Stream *stream = it->first;\n> > > -\t\tFrameBuffer *buffer = it->second;\n> > > -\n> > > -\t\trequest->addBuffer(stream, buffer);\n> > > -\t}\n> > > -\n> > > +\trequest->reuse(Request::ReuseBuffers);\n> > >  \tcamera_->queueRequest(request);\n> > >  }\n> > > diff --git a/src/cam/capture.h b/src/cam/capture.h\n> > > index 0aebdac9..45e5e8a9 100644\n> > > --- a/src/cam/capture.h\n> > > +++ b/src/cam/capture.h\n> > > @@ -9,6 +9,7 @@\n> > >\n> > >  #include <memory>\n> > >  #include <stdint.h>\n> > > +#include <vector>\n> > >\n> > >  #include <libcamera/buffer.h>\n> > >  #include <libcamera/camera.h>\n> > > @@ -43,6 +44,8 @@ private:\n> > >  \tEventLoop *loop_;\n> > >  \tunsigned int captureCount_;\n> > >  \tunsigned int captureLimit_;\n> > > +\n> > > +\tstd::vector<std::unique_ptr<libcamera::Request>> requests_;\n> > >  };\n> > >\n> > >  #endif /* __CAM_CAPTURE_H__ */\n> > > diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp\n> > > index 1bfc2e2f..5001083a 100644\n> > > --- a/src/gstreamer/gstlibcamerasrc.cpp\n> > > +++ b/src/gstreamer/gstlibcamerasrc.cpp\n> > > @@ -74,6 +74,8 @@ RequestWrap::~RequestWrap()\n> > >  \t\tif (item.second)\n> > >  \t\t\tgst_buffer_unref(item.second);\n> > >  \t}\n> > > +\n> > > +\tdelete request_;\n> > >  }\n> > >\n> > >  void RequestWrap::attachBuffer(GstBuffer *buffer)\n> > > @@ -266,8 +268,8 @@ gst_libcamera_src_task_run(gpointer user_data)\n> > >  \tGstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);\n> > >  \tGstLibcameraSrcState *state = self->state;\n> > >\n> > > -\tRequest *request = state->cam_->createRequest();\n> > > -\tauto wrap = std::make_unique<RequestWrap>(request);\n> > > +\tstd::unique_ptr<Request> request = state->cam_->createRequest();\n> > > +\tauto wrap = std::make_unique<RequestWrap>(request.get());\n> > >  \tfor (GstPad *srcpad : state->srcpads_) {\n> > >  \t\tGstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);\n> > >  \t\tGstBuffer *buffer;\n> > > @@ -280,8 +282,7 @@ gst_libcamera_src_task_run(gpointer user_data)\n> > >  \t\t\t * RequestWrap does not take ownership, and we won't be\n> > >  \t\t\t * queueing this one due to lack of buffers.\n> > >  \t\t\t */\n> > > -\t\t\tdelete request;\n> > > -\t\t\trequest = nullptr;\n> > > +\t\t\trequest.reset();\n> > >  \t\t\tbreak;\n> > >  \t\t}\n> > >\n> > > @@ -291,8 +292,11 @@ gst_libcamera_src_task_run(gpointer user_data)\n> > >  \tif (request) {\n> > >  \t\tGLibLocker lock(GST_OBJECT(self));\n> > >  \t\tGST_TRACE_OBJECT(self, \"Requesting buffers\");\n> > > -\t\tstate->cam_->queueRequest(request);\n> > > +\t\tstate->cam_->queueRequest(request.get());\n> > >  \t\tstate->requests_.push(std::move(wrap));\n> > > +\n> > > +\t\t/* The request will be deleted in the completion handler. */\n> > > +\t\trequest.release();\n> > >  \t}\n> > >\n> > >  \tGstFlowReturn ret = GST_FLOW_OK;\n> > > diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> > > index fb76077f..9590ab72 100644\n> > > --- a/src/libcamera/camera.cpp\n> > > +++ b/src/libcamera/camera.cpp\n> > > @@ -847,21 +847,22 @@ int Camera::configure(CameraConfiguration *config)\n> > >   * handler, and is completely opaque to libcamera.\n> > >   *\n> > >   * The ownership of the returned request is passed to the caller, which is\n> > > - * responsible for either queueing the request or deleting it.\n> > > + * responsible for deleting it. The request may be deleted in the completion\n> > > + * handler, or reused after resetting its state with Request::reuse().\n> > >   *\n> > >   * \\context This function is \\threadsafe. It may only be called when the camera\n> > >   * is in the Configured or Running state as defined in \\ref camera_operation.\n> > >   *\n> > >   * \\return A pointer to the newly created request, or nullptr on error\n> > >   */\n> > > -Request *Camera::createRequest(uint64_t cookie)\n> > > +std::unique_ptr<Request> Camera::createRequest(uint64_t cookie)\n> > >  {\n> > >  \tint ret = p_->isAccessAllowed(Private::CameraConfigured,\n> > >  \t\t\t\t      Private::CameraRunning);\n> > >  \tif (ret < 0)\n> > >  \t\treturn nullptr;\n> > >\n> > > -\treturn new Request(this, cookie);\n> > > +\treturn std::make_unique<Request>(this, cookie);\n> > >  }\n> > >\n> > >  /**\n> > > @@ -877,9 +878,6 @@ Request *Camera::createRequest(uint64_t cookie)\n> > >   * Once the request has been queued, the camera will notify its completion\n> > >   * through the \\ref requestCompleted signal.\n> > >   *\n> > > - * Ownership of the request is transferred to the camera. It will be deleted\n> > > - * automatically after it completes.\n> > > - *\n> > >   * \\context This function is \\threadsafe. It may only be called when the camera\n> > >   * is in the Running state as defined in \\ref camera_operation.\n> > >   *\n> > > @@ -988,13 +986,11 @@ int Camera::stop()\n> > >   * \\param[in] request The request that has completed\n> > >   *\n> > >   * This function is called by the pipeline handler to notify the camera that\n> > > - * the request has completed. It emits the requestCompleted signal and deletes\n> > > - * the request.\n> > > + * the request has completed. It emits the requestCompleted signal.\n> > >   */\n> > >  void Camera::requestComplete(Request *request)\n> > >  {\n> > >  \trequestCompleted.emit(request);\n> > > -\tdelete request;\n> > >  }\n> > >\n> > >  } /* namespace libcamera */\n> > > diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp\n> > > index 60b30692..ae8b1660 100644\n> > > --- a/src/libcamera/request.cpp\n> > > +++ b/src/libcamera/request.cpp\n> > > @@ -37,6 +37,15 @@ LOG_DEFINE_CATEGORY(Request)\n> > >   * The request has been cancelled due to capture stop\n> > >   */\n> > >\n> > > +/**\n> > > + * \\enum Request::ReuseFlag\n> > > + * Flags to control the behavior of Request::reuse()\n> > > + * \\var Request::Default\n> > > + * Don't reuse buffers\n> > > + * \\var Request::ReuseBuffers\n> > > + * Reuse the buffers that were previously added by addBuffer()\n> > > + */\n> > > +\n> > >  /**\n> > >   * \\typedef Request::BufferMap\n> > >   * \\brief A map of Stream to FrameBuffer pointers\n> > > @@ -85,6 +94,35 @@ Request::~Request()\n> > >  \tdelete validator_;\n> > >  }\n> > >\n> > > +/**\n> > > + * \\brief Reset the request for reuse\n> > > + * \\param[in] flags Indicate whether or not to reuse the buffers\n> > > + *\n> > > + * Reset the status and controls associated with the request, to allow it to\n> > > + * be reused and requeued without destruction. This function shall be called\n> > > + * prior to queueing the request to the camera, in lieu of constructing a new\n> > > + * request. The application can reuse the buffers that were previously added\n> > > + * to the request via addBuffer() by setting \\a flags to ReuseBuffers.\n> > > + */\n> > > +void Request::reuse(ReuseFlag flags)\n> > > +{\n> > > +\tpending_.clear();\n> > > +\tif (flags & ReuseBuffers) {\n> > > +\t\tfor (auto pair : bufferMap_) {\n> > > +\t\t\tpair.second->request_ = this;\n> > > +\t\t\tpending_.insert(pair.second);\n> > > +\t\t}\n> > > +\t} else {\n> > > +\t\tbufferMap_.clear();\n> > > +\t}\n> > > +\n> > > +\tstatus_ = RequestPending;\n> > > +\tcancelled_ = false;\n> > > +\n> > > +\tcontrols_->clear();\n> > > +\tmetadata_->clear();\n> > > +}\n> > > +\n> > >  /**\n> > >   * \\fn Request::controls()\n> > >   * \\brief Retrieve the request's ControlList\n> > > diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp\n> > > index ecb9dd66..0cbdab9a 100644\n> > > --- a/src/qcam/main_window.cpp\n> > > +++ b/src/qcam/main_window.cpp\n> > > @@ -367,7 +367,6 @@ void MainWindow::toggleCapture(bool start)\n> > >  int MainWindow::startCapture()\n> > >  {\n> > >  \tStreamRoles roles = StreamKeyValueParser::roles(options_[OptStream]);\n> > > -\tstd::vector<Request *> requests;\n> > >  \tint ret;\n> > >\n> > >  \t/* Verify roles are supported. */\n> > > @@ -486,7 +485,7 @@ int MainWindow::startCapture()\n> > >  \twhile (!freeBuffers_[vfStream_].isEmpty()) {\n> > >  \t\tFrameBuffer *buffer = freeBuffers_[vfStream_].dequeue();\n> > >\n> > > -\t\tRequest *request = camera_->createRequest();\n> > > +\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> > >  \t\tif (!request) {\n> > >  \t\t\tqWarning() << \"Can't create request\";\n> > >  \t\t\tret = -ENOMEM;\n> > > @@ -499,7 +498,7 @@ int MainWindow::startCapture()\n> > >  \t\t\tgoto error;\n> > >  \t\t}\n> > >\n> > > -\t\trequests.push_back(request);\n> > > +\t\trequests_.push_back(std::move(request));\n> > >  \t}\n> > >\n> > >  \t/* Start the title timer and the camera. */\n> > > @@ -518,8 +517,8 @@ int MainWindow::startCapture()\n> > >  \tcamera_->requestCompleted.connect(this, &MainWindow::requestComplete);\n> > >\n> > >  \t/* Queue all requests. */\n> > > -\tfor (Request *request : requests) {\n> > > -\t\tret = camera_->queueRequest(request);\n> > > +\tfor (std::unique_ptr<Request> &request : requests_) {\n> > > +\t\tret = camera_->queueRequest(request.get());\n> > >  \t\tif (ret < 0) {\n> > >  \t\t\tqWarning() << \"Can't queue request\";\n> > >  \t\t\tgoto error_disconnect;\n> > > @@ -535,8 +534,7 @@ error_disconnect:\n> > >  \tcamera_->stop();\n> > >\n> > >  error:\n> > > -\tfor (Request *request : requests)\n> > > -\t\tdelete request;\n> > > +\trequests_.clear();\n> > >\n> > >  \tfor (auto &iter : mappedBuffers_) {\n> > >  \t\tconst MappedBuffer &buffer = iter.second;\n> > > @@ -580,6 +578,8 @@ void MainWindow::stopCapture()\n> > >  \t}\n> > >  \tmappedBuffers_.clear();\n> > >\n> > > +\trequests_.clear();\n> > > +\n> > >  \tdelete allocator_;\n> > >\n> > >  \tisCapturing_ = false;\n> > > @@ -701,7 +701,7 @@ void MainWindow::requestComplete(Request *request)\n> > >  \t */\n> > >  \t{\n> > >  \t\tQMutexLocker locker(&mutex_);\n> > > -\t\tdoneQueue_.enqueue({ request->buffers(), request->metadata() });\n> > > +\t\tdoneQueue_.enqueue(request);\n> > >  \t}\n> > >\n> > >  \tQCoreApplication::postEvent(this, new CaptureEvent);\n> > > @@ -714,8 +714,7 @@ void MainWindow::processCapture()\n> > >  \t * if stopCapture() has been called while a CaptureEvent was posted but\n> > >  \t * not processed yet. Return immediately in that case.\n> > >  \t */\n> > > -\tCaptureRequest request;\n> > > -\n> > > +\tRequest *request;\n> > >  \t{\n> > >  \t\tQMutexLocker locker(&mutex_);\n> > >  \t\tif (doneQueue_.isEmpty())\n> > > @@ -725,11 +724,15 @@ void MainWindow::processCapture()\n> > >  \t}\n> > >\n> > >  \t/* Process buffers. */\n> > > -\tif (request.buffers_.count(vfStream_))\n> > > -\t\tprocessViewfinder(request.buffers_[vfStream_]);\n> > > +\tif (request->buffers().count(vfStream_))\n> > > +\t\tprocessViewfinder(request->buffers().at(vfStream_));\n> > >\n> > > -\tif (request.buffers_.count(rawStream_))\n> > > -\t\tprocessRaw(request.buffers_[rawStream_], request.metadata_);\n> > > +\tif (request->buffers().count(rawStream_))\n> > > +\t\tprocessRaw(request->buffers().at(rawStream_), request->metadata());\n> > > +\n> > > +\trequest->reuse();\n> > > +\tQMutexLocker locker(&mutex_);\n> > > +\tfreeQueue_.enqueue(request);\n> > >  }\n> > >\n> > >  void MainWindow::processViewfinder(FrameBuffer *buffer)\n> > > @@ -754,10 +757,13 @@ void MainWindow::processViewfinder(FrameBuffer *buffer)\n> > >\n> > >  void MainWindow::queueRequest(FrameBuffer *buffer)\n> > >  {\n> > > -\tRequest *request = camera_->createRequest();\n> > > -\tif (!request) {\n> > > -\t\tqWarning() << \"Can't create request\";\n> > > -\t\treturn;\n> > > +\tRequest *request;\n> > > +\t{\n> > > +\t\tQMutexLocker locker(&mutex_);\n> > > +\t\tif (freeQueue_.isEmpty())\n> > > +\t\t\treturn;\n> > > +\n> > > +\t\trequest = freeQueue_.dequeue();\n> > >  \t}\n> > >\n> > >  \trequest->addBuffer(vfStream_, buffer);\n> > > diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h\n> > > index 5c61a4df..64bcfebc 100644\n> > > --- a/src/qcam/main_window.h\n> > > +++ b/src/qcam/main_window.h\n> > > @@ -8,6 +8,7 @@\n> > >  #define __QCAM_MAIN_WINDOW_H__\n> > >\n> > >  #include <memory>\n> > > +#include <vector>\n> > >\n> > >  #include <QElapsedTimer>\n> > >  #include <QIcon>\n> > > @@ -22,6 +23,7 @@\n> > >  #include <libcamera/camera_manager.h>\n> > >  #include <libcamera/controls.h>\n> > >  #include <libcamera/framebuffer_allocator.h>\n> > > +#include <libcamera/request.h>\n> > >  #include <libcamera/stream.h>\n> > >\n> > >  #include \"../cam/stream_options.h\"\n> > > @@ -41,23 +43,6 @@ enum {\n> > >  \tOptStream = 's',\n> > >  };\n> > >\n> > > -class CaptureRequest\n> > > -{\n> > > -public:\n> > > -\tCaptureRequest()\n> > > -\t{\n> > > -\t}\n> > > -\n> > > -\tCaptureRequest(const Request::BufferMap &buffers,\n> > > -\t\t       const ControlList &metadata)\n> > > -\t\t: buffers_(buffers), metadata_(metadata)\n> > > -\t{\n> > > -\t}\n> > > -\n> > > -\tRequest::BufferMap buffers_;\n> > > -\tControlList metadata_;\n> > > -};\n> > > -\n> > >  class MainWindow : public QMainWindow\n> > >  {\n> > >  \tQ_OBJECT\n> > > @@ -128,13 +113,16 @@ private:\n> > >  \tStream *vfStream_;\n> > >  \tStream *rawStream_;\n> > >  \tstd::map<const Stream *, QQueue<FrameBuffer *>> freeBuffers_;\n> > > -\tQQueue<CaptureRequest> doneQueue_;\n> > > -\tQMutex mutex_; /* Protects freeBuffers_ and doneQueue_ */\n> > > +\tQQueue<Request *> doneQueue_;\n> > > +\tQQueue<Request *> freeQueue_;\n> > > +\tQMutex mutex_; /* Protects freeBuffers_, doneQueue_, and freeQueue_ */\n> > >\n> > >  \tuint64_t lastBufferTime_;\n> > >  \tQElapsedTimer frameRateInterval_;\n> > >  \tuint32_t previousFrames_;\n> > >  \tuint32_t framesCaptured_;\n> > > +\n> > > +\tstd::vector<std::unique_ptr<Request>> requests_;\n> > >  };\n> > >\n> > >  #endif /* __QCAM_MAIN_WINDOW__ */\n> > > diff --git a/src/v4l2/v4l2_camera.cpp b/src/v4l2/v4l2_camera.cpp\n> > > index 3565f369..35d3beda 100644\n> > > --- a/src/v4l2/v4l2_camera.cpp\n> > > +++ b/src/v4l2/v4l2_camera.cpp\n> > > @@ -49,6 +49,8 @@ int V4L2Camera::open(StreamConfiguration *streamConfig)\n> > >\n> > >  void V4L2Camera::close()\n> > >  {\n> > > +\trequestPool_.clear();\n> > > +\n> > >  \tdelete bufferAllocator_;\n> > >  \tbufferAllocator_ = nullptr;\n> > >\n> > > @@ -96,6 +98,7 @@ void V4L2Camera::requestComplete(Request *request)\n> > >  \tif (ret != sizeof(data))\n> > >  \t\tLOG(V4L2Compat, Error) << \"Failed to signal eventfd POLLIN\";\n> > >\n> > > +\trequest->reuse();\n> > >  \t{\n> > >  \t\tMutexLocker locker(bufferMutex_);\n> > >  \t\tbufferAvailableCount_++;\n> > > @@ -154,16 +157,30 @@ int V4L2Camera::validateConfiguration(const PixelFormat &pixelFormat,\n> > >  \treturn 0;\n> > >  }\n> > >\n> > > -int V4L2Camera::allocBuffers([[maybe_unused]] unsigned int count)\n> > > +int V4L2Camera::allocBuffers(unsigned int count)\n> > >  {\n> > >  \tStream *stream = config_->at(0).stream();\n> > >\n> > > -\treturn bufferAllocator_->allocate(stream);\n> > > +\tint ret = bufferAllocator_->allocate(stream);\n> > > +\tif (ret < 0)\n> > > +\t\treturn ret;\n> > > +\n> > > +\tfor (unsigned int i = 0; i < count; i++) {\n> > > +\t\tstd::unique_ptr<Request> request = camera_->createRequest(i);\n> > > +\t\tif (!request) {\n> > > +\t\t\trequestPool_.clear();\n> > > +\t\t\treturn -ENOMEM;\n> > > +\t\t}\n> > > +\t\trequestPool_.push_back(std::move(request));\n> > > +\t}\n> > > +\n> > > +\treturn ret;\n> > >  }\n> > >\n> > >  void V4L2Camera::freeBuffers()\n> > >  {\n> > >  \tpendingRequests_.clear();\n> > > +\trequestPool_.clear();\n> > >\n> > >  \tStream *stream = config_->at(0).stream();\n> > >  \tbufferAllocator_->free(stream);\n> > > @@ -192,9 +209,9 @@ int V4L2Camera::streamOn()\n> > >\n> > >  \tisRunning_ = true;\n> > >\n> > > -\tfor (std::unique_ptr<Request> &req : pendingRequests_) {\n> > > +\tfor (Request *req : pendingRequests_) {\n> > >  \t\t/* \\todo What should we do if this returns -EINVAL? */\n> > > -\t\tret = camera_->queueRequest(req.release());\n> > > +\t\tret = camera_->queueRequest(req);\n> > >  \t\tif (ret < 0)\n> > >  \t\t\treturn ret == -EACCES ? -EBUSY : ret;\n> > >  \t}\n> > > @@ -206,8 +223,12 @@ int V4L2Camera::streamOn()\n> > >\n> > >  int V4L2Camera::streamOff()\n> > >  {\n> > > -\tif (!isRunning_)\n> > > +\tif (!isRunning_) {\n> > > +\t\tfor (std::unique_ptr<Request> &req : requestPool_)\n> > > +\t\t\treq->reuse();\n> > > +\n> > >  \t\treturn 0;\n> > > +\t}\n> > >\n> > >  \tpendingRequests_.clear();\n> > >\n> > > @@ -226,12 +247,11 @@ int V4L2Camera::streamOff()\n> > >\n> > >  int V4L2Camera::qbuf(unsigned int index)\n> > >  {\n> > > -\tstd::unique_ptr<Request> request =\n> > > -\t\tstd::unique_ptr<Request>(camera_->createRequest(index));\n> > > -\tif (!request) {\n> > > -\t\tLOG(V4L2Compat, Error) << \"Can't create request\";\n> > > -\t\treturn -ENOMEM;\n> > > +\tif (index >= requestPool_.size()) {\n> > > +\t\tLOG(V4L2Compat, Error) << \"Invalid index\";\n> > > +\t\treturn -EINVAL;\n> > >  \t}\n> > > +\tRequest *request = requestPool_[index].get();\n> > >\n> > >  \tStream *stream = config_->at(0).stream();\n> > >  \tFrameBuffer *buffer = bufferAllocator_->buffers(stream)[index].get();\n> > > @@ -242,11 +262,11 @@ int V4L2Camera::qbuf(unsigned int index)\n> > >  \t}\n> > >\n> > >  \tif (!isRunning_) {\n> > > -\t\tpendingRequests_.push_back(std::move(request));\n> > > +\t\tpendingRequests_.push_back(request);\n> > >  \t\treturn 0;\n> > >  \t}\n> > >\n> > > -\tret = camera_->queueRequest(request.release());\n> > > +\tret = camera_->queueRequest(request);\n> > >  \tif (ret < 0) {\n> > >  \t\tLOG(V4L2Compat, Error) << \"Can't queue request\";\n> > >  \t\treturn ret == -EACCES ? -EBUSY : ret;\n> > > diff --git a/src/v4l2/v4l2_camera.h b/src/v4l2/v4l2_camera.h\n> > > index 1fc5ebef..a6c35a2e 100644\n> > > --- a/src/v4l2/v4l2_camera.h\n> > > +++ b/src/v4l2/v4l2_camera.h\n> > > @@ -76,7 +76,9 @@ private:\n> > >  \tstd::mutex bufferLock_;\n> > >  \tFrameBufferAllocator *bufferAllocator_;\n> > >\n> > > -\tstd::deque<std::unique_ptr<Request>> pendingRequests_;\n> > > +\tstd::vector<std::unique_ptr<Request>> requestPool_;\n> > > +\n> > > +\tstd::deque<Request *> pendingRequests_;\n> > >  \tstd::deque<std::unique_ptr<Buffer>> completedBuffers_;\n> > >\n> > >  \tint efd_;\n> > > diff --git a/test/camera/buffer_import.cpp b/test/camera/buffer_import.cpp\n> > > index 64e96264..72ce7b79 100644\n> > > --- a/test/camera/buffer_import.cpp\n> > > +++ b/test/camera/buffer_import.cpp\n> > > @@ -58,7 +58,7 @@ protected:\n> > >  \t\tconst Stream *stream = buffers.begin()->first;\n> > >  \t\tFrameBuffer *buffer = buffers.begin()->second;\n> > >\n> > > -\t\trequest = camera_->createRequest();\n> > > +\t\trequest->reuse();\n> > >  \t\trequest->addBuffer(stream, buffer);\n> > >  \t\tcamera_->queueRequest(request);\n> > >  \t}\n> > > @@ -98,9 +98,8 @@ protected:\n> > >  \t\tif (ret != TestPass)\n> > >  \t\t\treturn ret;\n> > >\n> > > -\t\tstd::vector<Request *> requests;\n> > >  \t\tfor (const std::unique_ptr<FrameBuffer> &buffer : source.buffers()) {\n> > > -\t\t\tRequest *request = camera_->createRequest();\n> > > +\t\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> > >  \t\t\tif (!request) {\n> > >  \t\t\t\tstd::cout << \"Failed to create request\" << std::endl;\n> > >  \t\t\t\treturn TestFail;\n> > > @@ -111,7 +110,7 @@ protected:\n> > >  \t\t\t\treturn TestFail;\n> > >  \t\t\t}\n> > >\n> > > -\t\t\trequests.push_back(request);\n> > > +\t\t\trequests_.push_back(std::move(request));\n> > >  \t\t}\n> > >\n> > >  \t\tcompleteRequestsCount_ = 0;\n> > > @@ -125,8 +124,8 @@ protected:\n> > >  \t\t\treturn TestFail;\n> > >  \t\t}\n> > >\n> > > -\t\tfor (Request *request : requests) {\n> > > -\t\t\tif (camera_->queueRequest(request)) {\n> > > +\t\tfor (std::unique_ptr<Request> &request : requests_) {\n> > > +\t\t\tif (camera_->queueRequest(request.get())) {\n> > >  \t\t\t\tstd::cout << \"Failed to queue request\" << std::endl;\n> > >  \t\t\t\treturn TestFail;\n> > >  \t\t\t}\n> > > @@ -160,6 +159,8 @@ protected:\n> > >  \t}\n> > >\n> > >  private:\n> > > +\tstd::vector<std::unique_ptr<Request>> requests_;\n> > > +\n> > >  \tunsigned int completeBuffersCount_;\n> > >  \tunsigned int completeRequestsCount_;\n> > >  \tstd::unique_ptr<CameraConfiguration> config_;\n> > > diff --git a/test/camera/capture.cpp b/test/camera/capture.cpp\n> > > index 51bbd258..c0770801 100644\n> > > --- a/test/camera/capture.cpp\n> > > +++ b/test/camera/capture.cpp\n> > > @@ -52,7 +52,7 @@ protected:\n> > >  \t\tconst Stream *stream = buffers.begin()->first;\n> > >  \t\tFrameBuffer *buffer = buffers.begin()->second;\n> > >\n> > > -\t\trequest = camera_->createRequest();\n> > > +\t\trequest->reuse();\n> > >  \t\trequest->addBuffer(stream, buffer);\n> > >  \t\tcamera_->queueRequest(request);\n> > >  \t}\n> > > @@ -98,9 +98,8 @@ protected:\n> > >  \t\tif (ret < 0)\n> > >  \t\t\treturn TestFail;\n> > >\n> > > -\t\tstd::vector<Request *> requests;\n> > >  \t\tfor (const std::unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) {\n> > > -\t\t\tRequest *request = camera_->createRequest();\n> > > +\t\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> > >  \t\t\tif (!request) {\n> > >  \t\t\t\tcout << \"Failed to create request\" << endl;\n> > >  \t\t\t\treturn TestFail;\n> > > @@ -111,7 +110,7 @@ protected:\n> > >  \t\t\t\treturn TestFail;\n> > >  \t\t\t}\n> > >\n> > > -\t\t\trequests.push_back(request);\n> > > +\t\t\trequests_.push_back(std::move(request));\n> > >  \t\t}\n> > >\n> > >  \t\tcompleteRequestsCount_ = 0;\n> > > @@ -125,8 +124,8 @@ protected:\n> > >  \t\t\treturn TestFail;\n> > >  \t\t}\n> > >\n> > > -\t\tfor (Request *request : requests) {\n> > > -\t\t\tif (camera_->queueRequest(request)) {\n> > > +\t\tfor (std::unique_ptr<Request> &request : requests_) {\n> > > +\t\t\tif (camera_->queueRequest(request.get())) {\n> > >  \t\t\t\tcout << \"Failed to queue request\" << endl;\n> > >  \t\t\t\treturn TestFail;\n> > >  \t\t\t}\n> > > @@ -161,6 +160,8 @@ protected:\n> > >  \t\treturn TestPass;\n> > >  \t}\n> > >\n> > > +\tstd::vector<std::unique_ptr<Request>> requests_;\n> > > +\n> > >  \tstd::unique_ptr<CameraConfiguration> config_;\n> > >  \tFrameBufferAllocator *allocator_;\n> > >  };\n> > > diff --git a/test/camera/statemachine.cpp b/test/camera/statemachine.cpp\n> > > index 28faeb91..e63ab298 100644\n> > > --- a/test/camera/statemachine.cpp\n> > > +++ b/test/camera/statemachine.cpp\n> > > @@ -101,13 +101,10 @@ protected:\n> > >  \t\t\treturn TestFail;\n> > >\n> > >  \t\t/* Test operations which should pass. */\n> > > -\t\tRequest *request2 = camera_->createRequest();\n> > > +\t\tstd::unique_ptr<Request> request2 = camera_->createRequest();\n> > >  \t\tif (!request2)\n> > >  \t\t\treturn TestFail;\n> > >\n> > > -\t\t/* Never handed to hardware so need to manually delete it. */\n> > > -\t\tdelete request2;\n> > > -\n> > >  \t\t/* Test valid state transitions, end in Running state. */\n> > >  \t\tif (camera_->release())\n> > >  \t\t\treturn TestFail;\n> > > @@ -146,7 +143,7 @@ protected:\n> > >  \t\t\treturn TestFail;\n> > >\n> > >  \t\t/* Test operations which should pass. */\n> > > -\t\tRequest *request = camera_->createRequest();\n> > > +\t\tstd::unique_ptr<Request> request = camera_->createRequest();\n> > >  \t\tif (!request)\n> > >  \t\t\treturn TestFail;\n> > >\n> > > @@ -154,7 +151,7 @@ protected:\n> > >  \t\tif (request->addBuffer(stream, allocator_->buffers(stream)[0].get()))\n> > >  \t\t\treturn TestFail;\n> > >\n> > > -\t\tif (camera_->queueRequest(request))\n> > > +\t\tif (camera_->queueRequest(request.get()))\n> > >  \t\t\treturn TestFail;\n> > >\n> > >  \t\t/* Test valid state transitions, end in Available state. */\n> > > --\n> > > 2.27.0\n> > >\n> > > _______________________________________________\n> > > libcamera-devel mailing list\n> > > libcamera-devel@lists.libcamera.org\n> > > https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 4A9ECBEEE0\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri,  9 Oct 2020 12:43:16 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 1FF886073B;\n\tFri,  9 Oct 2020 14:43:16 +0200 (CEST)","from relay6-d.mail.gandi.net (relay6-d.mail.gandi.net\n\t[217.70.183.198])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 6931F60358\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  9 Oct 2020 14:43:14 +0200 (CEST)","from uno.localdomain (93-34-118-233.ip49.fastwebnet.it\n\t[93.34.118.233]) (Authenticated sender: jacopo@jmondi.org)\n\tby relay6-d.mail.gandi.net (Postfix) with ESMTPSA id E41F5C000E;\n\tFri,  9 Oct 2020 12:43:13 +0000 (UTC)"],"X-Originating-IP":"93.34.118.233","Date":"Fri, 9 Oct 2020 14:47:13 +0200","From":"Jacopo Mondi <jacopo@jmondi.org>","To":"paul.elder@ideasonboard.com","Message-ID":"<20201009124713.xlc7ksaa3xmahw6h@uno.localdomain>","References":"<20201007073418.512656-1-paul.elder@ideasonboard.com>\n\t<20201007094331.2dl4xwdeqzf4isix@uno.localdomain>\n\t<20201008062920.GI45948@pyrite.rasen.tech>","MIME-Version":"1.0","Content-Disposition":"inline","In-Reply-To":"<20201008062920.GI45948@pyrite.rasen.tech>","Subject":"Re: [libcamera-devel] [PATCH v6] libcamera, android, cam, gstreamer,\n\tqcam, v4l2: Reuse Request","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Cc":"libcamera-devel@lists.libcamera.org","Content-Type":"text/plain; charset=\"us-ascii\"","Content-Transfer-Encoding":"7bit","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]