Show a patch.

GET /api/1.1/patches/27128/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 27128,
    "url": "https://patchwork.libcamera.org/api/1.1/patches/27128/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/27128/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/1.1/projects/1/?format=api",
        "name": "libcamera",
        "link_name": "libcamera",
        "list_id": "libcamera_core",
        "list_email": "libcamera-devel@lists.libcamera.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20260629163017.863145-53-barnabas.pocze@ideasonboard.com>",
    "date": "2026-06-29T16:30:15",
    "name": "[RFC,v1,52/54] android: Use camera buffer pool",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "bb5c608c15fcffc274f0c1d4497d81153f955bd7",
    "submitter": {
        "id": 216,
        "url": "https://patchwork.libcamera.org/api/1.1/people/216/?format=api",
        "name": "Barnabás Pőcze",
        "email": "barnabas.pocze@ideasonboard.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/27128/mbox/",
    "series": [
        {
            "id": 6025,
            "url": "https://patchwork.libcamera.org/api/1.1/series/6025/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=6025",
            "date": "2026-06-29T16:29:23",
            "name": "libcamera: Split requests and buffers",
            "version": 1,
            "mbox": "https://patchwork.libcamera.org/series/6025/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/27128/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/27128/checks/",
    "tags": {},
    "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 41EEDC3261\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 29 Jun 2026 16:31:41 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id CAA6D65FBE;\n\tMon, 29 Jun 2026 18:31:40 +0200 (CEST)",
            "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 272E965F76\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 29 Jun 2026 18:30:34 +0200 (CEST)",
            "from pb-laptop.local (185.221.140.128.nat.pool.zt.hu\n\t[185.221.140.128])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id E51D18D4\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 29 Jun 2026 18:29:50 +0200 (CEST)"
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"iK8xCcNk\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1782750591;\n\tbh=7YWY/lzWSfNS9Eie2jc2jCVI6xr/Oc8SVJB7lCQTPJg=;\n\th=From:To:Subject:Date:In-Reply-To:References:From;\n\tb=iK8xCcNkci743VuOe8KPjQvve206BJehzNns7ZSE0eEtwF4qyiZzYbXpHoWUq/WXn\n\tcJBcumGLE6F5Ua5/BcSVrD06KrxBFkPYwjo2mpJkeQrMyYlZ7IBii7A3XpJcTNArfY\n\thSTmCG03640ioDEe7Fn/1CIxJrXf9m+9UxyZiX9U=",
        "From": "=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Subject": "[RFC PATCH v1 52/54] android: Use camera buffer pool",
        "Date": "Mon, 29 Jun 2026 18:30:15 +0200",
        "Message-ID": "<20260629163017.863145-53-barnabas.pocze@ideasonboard.com>",
        "X-Mailer": "git-send-email 2.54.0",
        "In-Reply-To": "<20260629163017.863145-1-barnabas.pocze@ideasonboard.com>",
        "References": "<20260629163017.863145-1-barnabas.pocze@ideasonboard.com>",
        "MIME-Version": "1.0",
        "Content-Type": "text/plain; charset=UTF-8",
        "Content-Transfer-Encoding": "8bit",
        "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>",
        "Errors-To": "libcamera-devel-bounces@lists.libcamera.org",
        "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"
    },
    "content": "Convert the Android HAL to use the camera buffer pool. This involves switching\nto camera device version 3.6, the newer style of buffer management[0], and\nusing  `{return,request}_stream_buffers()`, and furthermore implementing the\n`signal_stream_flush()` function.\n\nThese changes are largely untested and were written based on the documentation\nin the header files as well as the code in [1].\n\nTODO: this removes numFds and numInt checks, are those important?!\n\n[0]: https://source.android.com/docs/core/camera/buffer-management-api\n[1]: https://android.googlesource.com/platform/hardware/interfaces/+/refs/tags/android-17.0.0_r1/camera/device/3.5/default/CameraDeviceSession.cpp#174\n\nSigned-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n---\n src/android/camera_capabilities.cpp |   3 +\n src/android/camera_device.cpp       | 250 ++++++++++++++++++++++------\n src/android/camera_device.h         |   4 +\n src/android/camera_hal_manager.cpp  |   2 +-\n src/android/camera_ops.cpp          |  14 +-\n src/android/camera_request.cpp      |  20 +--\n src/android/camera_request.h        |   9 +-\n 7 files changed, 227 insertions(+), 75 deletions(-)",
    "diff": "diff --git a/src/android/camera_capabilities.cpp b/src/android/camera_capabilities.cpp\nindex 6b58dd5548..feb1cae490 100644\n--- a/src/android/camera_capabilities.cpp\n+++ b/src/android/camera_capabilities.cpp\n@@ -1453,6 +1453,9 @@ int CameraCapabilities::initializeStaticMetadata()\n \tLOG(HAL, Info)\n \t\t<< \"Hardware level: \" << hwLevelStrings.find(hwLevel_)->second;\n \n+\tstaticMetadata_->addEntry(ANDROID_INFO_SUPPORTED_BUFFER_MANAGEMENT_VERSION,\n+\t\t\t\t  ANDROID_INFO_SUPPORTED_BUFFER_MANAGEMENT_VERSION_HIDL_DEVICE_3_5);\n+\n \tstaticMetadata_->addEntry(ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS,\n \t\t\t\t  std::vector<int32_t>(availableCharacteristicsKeys_.begin(),\n \t\t\t\t\t\t       availableCharacteristicsKeys_.end()));\ndiff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp\nindex 09aa129e0a..97f2e1c515 100644\n--- a/src/android/camera_device.cpp\n+++ b/src/android/camera_device.cpp\n@@ -394,7 +394,7 @@ int CameraDevice::open(const hw_module_t *hardwareModule)\n \n \t/* Initialize the hw_device_t in the instance camera3_module_t. */\n \tcamera3Device_.common.tag = HARDWARE_DEVICE_TAG;\n-\tcamera3Device_.common.version = CAMERA_DEVICE_API_VERSION_3_3;\n+\tcamera3Device_.common.version = CAMERA_DEVICE_API_VERSION_3_6;\n \tcamera3Device_.common.module = (hw_module_t *)hardwareModule;\n \tcamera3Device_.common.close = hal_dev_close;\n \n@@ -434,9 +434,45 @@ void CameraDevice::flush()\n void CameraDevice::stop()\n {\n \tMutexLocker stateLock(stateMutex_);\n+\tstd::vector<Camera3RequestDescriptor::StreamBuffer> streamBuffers;\n+\n+\tcamera_->bufferCompleted.connect(&streamBuffers, [&](libcamera::Request *request, [[maybe_unused]] const libcamera::Stream *stream, libcamera::FrameBuffer *fb) {\n+\t\tif (request)\n+\t\t\treturn;\n+\n+\t\t/* Collect buffers from the pool that were not used for a request. */\n+\n+\t\tstd::unique_ptr<Camera3RequestDescriptor::StreamBuffer> buffer(\n+\t\t\treinterpret_cast<Camera3RequestDescriptor::StreamBuffer *>(fb->cookie()));\n+\t\tfb->setCookie(0);\n+\n+\t\tstreamBuffers.push_back(std::move(*buffer));\n+\t});\n \n \tcamera_->stop();\n \n+\tcamera_->bufferCompleted.disconnect(&streamBuffers);\n+\n+\t/* Return buffers that were unused by requests. */\n+\t{\n+\t\tstd::vector<camera3_stream_buffer_t> returnBuffers;\n+\t\treturnBuffers.reserve(streamBuffers.size());\n+\n+\t\tfor (auto &buffer : streamBuffers) {\n+\t\t\tif (buffer.internalBuffer)\n+\t\t\t\tbuffer.stream->putBuffer(buffer.internalBuffer);\n+\n+\t\t\tbuffer.status = Camera3RequestDescriptor::Status::Error;\n+\t\t\treturnBuffers.push_back(buffer.prepareToReturn());\n+\t\t}\n+\n+\t\tauto bufferPtrs = std::make_unique<const camera3_stream_buffer *[]>(returnBuffers.size());\n+\t\tfor (const auto &[i, buffers] : utils::enumerate(returnBuffers))\n+\t\t\tbufferPtrs[i] = &buffers;\n+\n+\t\tcallbacks_->return_stream_buffers(callbacks_, returnBuffers.size(), bufferPtrs.get());\n+\t}\n+\n \t{\n \t\tMutexLocker descriptorsLock(descriptorsMutex_);\n \t\tdescriptors_ = {};\n@@ -879,7 +915,7 @@ bool CameraDevice::isValidRequest(camera3_capture_request_t *camera3Request) con\n \n \tif (!camera3Request->num_output_buffers ||\n \t    !camera3Request->output_buffers) {\n-\t\tLOG(HAL, Error) << \"No output buffers provided\";\n+\t\tLOG(HAL, Error) << \"No streams requested\";\n \t\treturn false;\n \t}\n \n@@ -892,25 +928,8 @@ bool CameraDevice::isValidRequest(camera3_capture_request_t *camera3Request) con\n \tfor (uint32_t i = 0; i < camera3Request->num_output_buffers; i++) {\n \t\tconst camera3_stream_buffer_t &outputBuffer =\n \t\t\tcamera3Request->output_buffers[i];\n-\t\tif (!outputBuffer.buffer || !(*outputBuffer.buffer)) {\n-\t\t\tLOG(HAL, Error) << \"Invalid native handle\";\n-\t\t\treturn false;\n-\t\t}\n-\n-\t\tconst native_handle_t *handle = *outputBuffer.buffer;\n-\t\tconstexpr int kNativeHandleMaxFds = 1024;\n-\t\tif (handle->numFds < 0 || handle->numFds > kNativeHandleMaxFds) {\n-\t\t\tLOG(HAL, Error)\n-\t\t\t\t<< \"Invalid number of fds (\" << handle->numFds\n-\t\t\t\t<< \") in buffer \" << i;\n-\t\t\treturn false;\n-\t\t}\n-\n-\t\tconstexpr int kNativeHandleMaxInts = 1024;\n-\t\tif (handle->numInts < 0 || handle->numInts > kNativeHandleMaxInts) {\n-\t\t\tLOG(HAL, Error)\n-\t\t\t\t<< \"Invalid number of ints (\" << handle->numInts\n-\t\t\t\t<< \") in buffer \" << i;\n+\t\tif (outputBuffer.buffer || *outputBuffer.buffer) {\n+\t\t\tLOG(HAL, Error) << \"Unexpected native buffer handle\";\n \t\t\treturn false;\n \t\t}\n \n@@ -940,6 +959,11 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n \tif (!isValidRequest(camera3Request))\n \t\treturn -EINVAL;\n \n+\tconst Span<const camera3_stream_buffer_t> buffers{\n+\t\tcamera3Request->output_buffers,\n+\t\tcamera3Request->num_output_buffers\n+\t};\n+\n \t/*\n \t * Save the request descriptors for use at completion time.\n \t * The descriptor and the associated memory reserved here are freed\n@@ -960,7 +984,73 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n \tdescriptor->settings_ = lastSettings_;\n \n \tLOG(HAL, Debug) << \"Queueing request \" << descriptor->request_->cookie()\n-\t\t\t<< \" with \" << descriptor->buffers_.size() << \" streams\";\n+\t\t\t<< \" with \" << buffers.size() << \" streams\";\n+\n+\tstd::unordered_map<CameraStream *, std::unique_ptr<Camera3RequestDescriptor::StreamBuffer>> streamBuffers;\n+\n+\tutils::scope_exit bufferGuard([&] {\n+\t\tstd::vector<camera3_stream_buffer_t> returnBuffers;\n+\n+\t\tfor (auto &[stream, buffer] : streamBuffers) {\n+\t\t\tif (!buffer)\n+\t\t\t\tcontinue;\n+\n+\t\t\treturnBuffers.push_back(buffer->prepareToReturn());\n+\t\t}\n+\n+\t\tauto bufferPtrs = std::make_unique<const camera3_stream_buffer_t *[]>(returnBuffers.size());\n+\t\tfor (const auto &[i, buffer] : utils::enumerate(returnBuffers))\n+\t\t\tbufferPtrs[i] = &buffer;\n+\n+\t\tcallbacks_->return_stream_buffers(callbacks_, returnBuffers.size(), bufferPtrs.get());\n+\t});\n+\n+\t{\n+\t\tuint32_t count = buffers.size();\n+\n+\t\tauto bufferResults = std::make_unique<camera3_stream_buffer_ret_t[]>(count);\n+\t\tauto bufferRequests = std::make_unique<camera3_buffer_request_t[]>(count);\n+\t\tauto returnedBuffers = std::make_unique<camera3_stream_buffer_t[]>(count);\n+\n+\t\tfor (size_t i = 0; i < count; i++) {\n+\t\t\tbufferRequests[i] = {\n+\t\t\t\t.stream = buffers[i].stream,\n+\t\t\t\t.num_buffers_requested = 1,\n+\t\t\t};\n+\n+\t\t\t/* Must provide storage for result. */\n+\t\t\tbufferResults[i].output_buffers = &returnedBuffers[i];\n+\t\t}\n+\n+\t\tauto ret = callbacks_->request_stream_buffers(callbacks_,\n+\t\t\t\t\t\t\t      count, bufferRequests.get(),\n+\t\t\t\t\t\t\t      &count, bufferResults.get());\n+\n+\t\tfor (size_t i = 0; i < count; i++) {\n+\t\t\tauto &result = bufferResults[i];\n+\t\t\tif (result.status != CAMERA3_PS_BUF_REQ_OK)\n+\t\t\t\tcontinue;\n+\n+\t\t\tauto *stream = static_cast<CameraStream *>(result.stream->priv);\n+\t\t\tASSERT(result.num_output_buffers == 1);\n+\n+\t\t\t[[maybe_unused]] auto [it, inserted] = streamBuffers.try_emplace(stream,\n+\t\t\t\tstd::make_unique<Camera3RequestDescriptor::StreamBuffer>(\n+\t\t\t\t\tstream, result.output_buffers[0]\n+\t\t\t\t)\n+\t\t\t);\n+\t\t\tASSERT(inserted);\n+\t\t}\n+\n+\t\tif (ret != CAMERA3_BUF_REQ_OK) {\n+\t\t\t/*\n+\t\t\t * \\todo Improve error handling. For now every stream must get\n+\t\t\t * a buffer successfully. This is checked here so that any successfully\n+\t\t\t * allocated buffer is returned by `bufferGuard`.\n+\t\t\t */\n+\t\t\treturn -ENOBUFS;\n+\t\t}\n+\t}\n \n \t/*\n \t * Process all the Direct and Internal streams first, they map directly\n@@ -970,8 +1060,8 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n \t * Since requestedStreams is an std:set<>, no duplications can happen.\n \t */\n \tstd::set<CameraStream *> requestedStreams;\n-\tfor (const auto &[i, buffer] : utils::enumerate(descriptor->buffers_)) {\n-\t\tCameraStream *cameraStream = buffer.stream;\n+\tfor (const auto &[i, buffer] : utils::enumerate(buffers)) {\n+\t\tauto *cameraStream = static_cast<CameraStream *>(buffer.stream->priv);\n \t\tcamera3_stream_t *camera3Stream = cameraStream->camera3Stream();\n \n \t\tstd::stringstream ss;\n@@ -986,7 +1076,7 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n \t\t * and add them to the Request if required.\n \t\t */\n \t\tFrameBuffer *frameBuffer = nullptr;\n-\t\tUniqueFD acquireFence;\n+\t\tauto &streamBuffer = *streamBuffers.at(cameraStream);\n \n \t\tMutexLocker lock(descriptor->streamsProcessMutex_);\n \n@@ -1002,12 +1092,11 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n \t\t\t * associate it with the Camera3RequestDescriptor for\n \t\t\t * lifetime management only.\n \t\t\t */\n-\t\t\tbuffer.frameBuffer =\n-\t\t\t\tcreateFrameBuffer(*buffer.camera3Buffer,\n+\t\t\tstreamBuffer.frameBuffer =\n+\t\t\t\tcreateFrameBuffer(*streamBuffer.camera3Buffer,\n \t\t\t\t\t\t  cameraStream->configuration().pixelFormat,\n \t\t\t\t\t\t  cameraStream->configuration().size);\n-\t\t\tframeBuffer = buffer.frameBuffer.get();\n-\t\t\tacquireFence = std::move(buffer.fence);\n+\t\t\tframeBuffer = streamBuffer.frameBuffer.get();\n \t\t\tLOG(HAL, Debug) << ss.str() << \" (direct)\";\n \t\t\tbreak;\n \n@@ -1020,11 +1109,10 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n \t\t\t * once it has been processed.\n \t\t\t */\n \t\t\tframeBuffer = cameraStream->getBuffer();\n-\t\t\tbuffer.internalBuffer = frameBuffer;\n+\t\t\tstreamBuffer.internalBuffer = frameBuffer;\n \t\t\tLOG(HAL, Debug) << ss.str() << \" (internal)\";\n \n-\t\t\tdescriptor->pendingStreamsToProcess_.insert(\n-\t\t\t\t{ cameraStream, &buffer });\n+\t\t\tdescriptor->pendingStreamsToProcess_.insert(cameraStream);\n \t\t\tbreak;\n \t\t}\n \n@@ -1033,10 +1121,6 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n \t\t\treturn -ENOMEM;\n \t\t}\n \n-\t\tauto fence = std::make_unique<Fence>(std::move(acquireFence));\n-\t\tdescriptor->request_->addBuffer(cameraStream->stream(),\n-\t\t\t\t\t\tframeBuffer, std::move(fence));\n-\n \t\trequestedStreams.insert(cameraStream);\n \t}\n \n@@ -1045,8 +1129,8 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n \t * because their corresponding direct source stream is not part of this\n \t * particular request, add one here.\n \t */\n-\tfor (const auto &[i, buffer] : utils::enumerate(descriptor->buffers_)) {\n-\t\tCameraStream *cameraStream = buffer.stream;\n+\tfor (const auto &[i, buffer] : utils::enumerate(buffers)) {\n+\t\tauto *cameraStream = static_cast<CameraStream *>(buffer.stream->priv);\n \t\tcamera3_stream_t *camera3Stream = cameraStream->camera3Stream();\n \n \t\tif (cameraStream->type() != CameraStream::Type::Mapped)\n@@ -1060,7 +1144,7 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n \t\t\t\t<< \" (mapped)\";\n \n \t\tMutexLocker lock(descriptor->streamsProcessMutex_);\n-\t\tdescriptor->pendingStreamsToProcess_.insert({ cameraStream, &buffer });\n+\t\tdescriptor->pendingStreamsToProcess_.insert(cameraStream);\n \n \t\t/*\n \t\t * Make sure the CameraStream this stream is mapped on has been\n@@ -1071,15 +1155,14 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n \t\tif (requestedStreams.find(sourceStream) != requestedStreams.end())\n \t\t\tcontinue;\n \n+\t\tauto &streamBuffer = *streamBuffers.at(cameraStream);\n+\n \t\t/*\n \t\t * If that's not the case, we need to add a buffer to the request\n \t\t * for this stream.\n \t\t */\n \t\tFrameBuffer *frameBuffer = cameraStream->getBuffer();\n-\t\tbuffer.internalBuffer = frameBuffer;\n-\n-\t\tdescriptor->request_->addBuffer(sourceStream->stream(),\n-\t\t\t\t\t\tframeBuffer);\n+\t\tstreamBuffer.internalBuffer = frameBuffer;\n \n \t\trequestedStreams.insert(sourceStream);\n \t}\n@@ -1123,6 +1206,38 @@ int CameraDevice::processCaptureRequest(camera3_capture_request_t *camera3Reques\n \t\tstate_ = State::Running;\n \t}\n \n+\tfor (auto *cameraStream : requestedStreams) {\n+\t\tauto &buffer = streamBuffers.at(cameraStream);\n+\t\tconst Stream *stream = nullptr;\n+\t\tFrameBuffer *fb = nullptr;\n+\t\tUniqueFD acquireFence;\n+\n+\t\tswitch (cameraStream->type()) {\n+\t\tcase CameraStream::Type::Direct:\n+\t\t\tstream = cameraStream->stream();\n+\t\t\tfb = buffer->frameBuffer.get();\n+\t\t\tacquireFence = std::move(buffer->fence);\n+\t\t\tbreak;\n+\t\tcase CameraStream::Type::Internal:\n+\t\t\tstream = cameraStream->stream();\n+\t\t\tfb = buffer->internalBuffer;\n+\t\t\tbreak;\n+\t\tcase CameraStream::Type::Mapped:\n+\t\t\tstream = cameraStream->sourceStream()->stream();\n+\t\t\tfb = buffer->internalBuffer;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tASSERT(false);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\n+\t\tfb->setCookie(reinterpret_cast<uint64_t>(buffer.release()));\n+\t\tcamera_->addBuffer(stream, fb, std::make_unique<Fence>(std::move(acquireFence)));\n+\n+\t\tdescriptor->request_->enableStream(stream, true);\n+\t}\n+\n \tRequest *request = descriptor->request_.get();\n \n \t{\n@@ -1140,6 +1255,17 @@ void CameraDevice::requestComplete(Request *request)\n \tCamera3RequestDescriptor *descriptor =\n \t\treinterpret_cast<Camera3RequestDescriptor *>(request->cookie());\n \n+\tstd::vector<std::unique_ptr<Camera3RequestDescriptor::StreamBuffer>> buffers;\n+\n+\tfor (const auto &[stream, fb] : request->buffers()) {\n+\t\tstd::unique_ptr<Camera3RequestDescriptor::StreamBuffer> buffer(\n+\t\t\treinterpret_cast<Camera3RequestDescriptor::StreamBuffer *>(fb->cookie()));\n+\t\tfb->setCookie(0);\n+\n+\t\tbuffer->request = descriptor;\n+\t\tdescriptor->buffers_.push_back(std::move(*buffer));\n+\t}\n+\n \t/*\n \t * Prepare the capture result for the Android camera stack.\n \t *\n@@ -1208,18 +1334,17 @@ void CameraDevice::requestComplete(Request *request)\n \t */\n \tauto iter = descriptor->pendingStreamsToProcess_.begin();\n \twhile (iter != descriptor->pendingStreamsToProcess_.end()) {\n-\t\tCameraStream *stream = iter->first;\n-\t\tCamera3RequestDescriptor::StreamBuffer *buffer = iter->second;\n-\n-\t\tFrameBuffer *src = request->findBuffer(stream->stream());\n-\t\tif (!src) {\n+\t\tCameraStream *stream = *iter;\n+\t\tauto it = std::find_if(descriptor->buffers_.begin(), descriptor->buffers_.end(),\n+\t\t\t\t       [&](const auto &d) { return d.stream == stream; });\n+\t\tif (it == descriptor->buffers_.end()) {\n \t\t\tLOG(HAL, Error) << \"Failed to find a source stream buffer\";\n-\t\t\tsetBufferStatus(*buffer, Camera3RequestDescriptor::Status::Error);\n \t\t\titer = descriptor->pendingStreamsToProcess_.erase(iter);\n \t\t\tcontinue;\n \t\t}\n \n-\t\tbuffer->srcBuffer = src;\n+\t\tauto *buffer = &*it;\n+\t\tbuffer->srcBuffer = buffer->internalBuffer;\n \n \t\t++iter;\n \t\tint ret = stream->process(buffer);\n@@ -1276,6 +1401,8 @@ void CameraDevice::completeDescriptor(Camera3RequestDescriptor *descriptor)\n  */\n void CameraDevice::sendCaptureResults()\n {\n+\tbool notify = false;\n+\n \twhile (!descriptors_.empty() && !descriptors_.front()->isPending()) {\n \t\tauto descriptor = std::move(descriptors_.front());\n \t\tdescriptors_.pop();\n@@ -1301,7 +1428,11 @@ void CameraDevice::sendCaptureResults()\n \t\t\tcaptureResult.partial_result = 1;\n \n \t\tcallbacks_->process_capture_result(callbacks_, &captureResult);\n+\t\tnotify = true;\n \t}\n+\n+\tif (notify)\n+\t\tdescriptorsCv_.notify_all();\n }\n \n void CameraDevice::setBufferStatus(Camera3RequestDescriptor::StreamBuffer &streamBuffer,\n@@ -1357,6 +1488,25 @@ void CameraDevice::streamProcessingComplete(Camera3RequestDescriptor::StreamBuff\n \tcompleteDescriptor(streamBuffer->request);\n }\n \n+void CameraDevice::signalStreamFlush([[maybe_unused]] libcamera::Span<const camera3_stream_t * const> streams)\n+{\n+\t/*\n+\t * `streams` is ignored because it is not possible to selectively\n+\t * retrieve all buffers a given stream.\n+\t */\n+\n+\t{\n+\t\tlibcamera::MutexLocker locker(descriptorsMutex_);\n+\n+\t\tdescriptorsCv_.wait(locker, [&]() LIBCAMERA_TSA_REQUIRES(descriptorsMutex_) {\n+\t\t\treturn descriptors_.empty();\n+\t\t});\n+\t}\n+\n+\t/* \\todo Is stopping ok here? */\n+\tstop();\n+}\n+\n std::string CameraDevice::logPrefix() const\n {\n \treturn \"'\" + camera_->id() + \"'\";\ndiff --git a/src/android/camera_device.h b/src/android/camera_device.h\nindex 194ca30304..c0f8b88ee5 100644\n--- a/src/android/camera_device.h\n+++ b/src/android/camera_device.h\n@@ -18,6 +18,7 @@\n #include <libcamera/base/log.h>\n #include <libcamera/base/message.h>\n #include <libcamera/base/mutex.h>\n+#include <libcamera/base/span.h>\n \n #include <libcamera/camera.h>\n #include <libcamera/framebuffer.h>\n@@ -68,6 +69,8 @@ public:\n \tvoid streamProcessingComplete(Camera3RequestDescriptor::StreamBuffer *bufferStream,\n \t\t\t\t      Camera3RequestDescriptor::Status status);\n \n+\tvoid signalStreamFlush(libcamera::Span<const camera3_stream_t * const> streams);\n+\n protected:\n \tstd::string logPrefix() const override;\n \n@@ -118,6 +121,7 @@ private:\n \tstd::vector<CameraStream> streams_;\n \n \tlibcamera::Mutex descriptorsMutex_ LIBCAMERA_TSA_ACQUIRED_AFTER(stateMutex_);\n+\tlibcamera::ConditionVariable descriptorsCv_;\n \tstd::queue<std::unique_ptr<Camera3RequestDescriptor>> descriptors_\n \t\tLIBCAMERA_TSA_GUARDED_BY(descriptorsMutex_);\n \ndiff --git a/src/android/camera_hal_manager.cpp b/src/android/camera_hal_manager.cpp\nindex a7a2571754..6b9a1007ee 100644\n--- a/src/android/camera_hal_manager.cpp\n+++ b/src/android/camera_hal_manager.cpp\n@@ -259,7 +259,7 @@ int CameraHalManager::getCameraInfo(unsigned int id, struct camera_info *info)\n \n \tinfo->facing = camera->facing();\n \tinfo->orientation = camera->orientation();\n-\tinfo->device_version = CAMERA_DEVICE_API_VERSION_3_3;\n+\tinfo->device_version = CAMERA_DEVICE_API_VERSION_3_6;\n \tinfo->resource_cost = 0;\n \tinfo->static_camera_characteristics = camera->getStaticMetadata();\n \tinfo->conflicting_devices = nullptr;\ndiff --git a/src/android/camera_ops.cpp b/src/android/camera_ops.cpp\nindex 1656fac7ab..d8f0f715b2 100644\n--- a/src/android/camera_ops.cpp\n+++ b/src/android/camera_ops.cpp\n@@ -77,6 +77,18 @@ static int hal_dev_flush(const struct camera3_device *dev)\n \treturn 0;\n }\n \n+static void hal_dev_signal_stream_flush(const struct camera3_device *dev,\n+\t\t\t\t\tuint32_t num_streams,\n+\t\t\t\t\tconst camera3_stream_t* const* streams)\n+{\n+\tif (!dev)\n+\t\treturn;\n+\n+\tCameraDevice *camera = reinterpret_cast<CameraDevice *>(dev->priv);\n+\n+\tcamera->signalStreamFlush({ streams, num_streams });\n+}\n+\n int hal_dev_close(hw_device_t *hw_device)\n {\n \tif (!hw_device)\n@@ -99,7 +111,7 @@ camera3_device_ops hal_dev_ops = {\n \t.get_metadata_vendor_tag_ops = nullptr,\n \t.dump = hal_dev_dump,\n \t.flush = hal_dev_flush,\n-\t.signal_stream_flush = nullptr,\n+\t.signal_stream_flush = hal_dev_signal_stream_flush,\n \t.is_reconfiguration_required = nullptr,\n \t.reserved = {},\n };\ndiff --git a/src/android/camera_request.cpp b/src/android/camera_request.cpp\nindex 6dfe692f09..91bd618006 100644\n--- a/src/android/camera_request.cpp\n+++ b/src/android/camera_request.cpp\n@@ -114,21 +114,6 @@ Camera3RequestDescriptor::Camera3RequestDescriptor(\n {\n \tframeNumber_ = camera3Request->frame_number;\n \n-\t/* Copy the camera3 request stream information for later access. */\n-\tconst Span<const camera3_stream_buffer_t> buffers{\n-\t\tcamera3Request->output_buffers,\n-\t\tcamera3Request->num_output_buffers\n-\t};\n-\n-\tbuffers_.reserve(buffers.size());\n-\n-\tfor (const camera3_stream_buffer_t &buffer : buffers) {\n-\t\tCameraStream *stream =\n-\t\t\tstatic_cast<CameraStream *>(buffer.stream->priv);\n-\n-\t\tbuffers_.emplace_back(stream, buffer, this);\n-\t}\n-\n \t/* Clone the controls associated with the camera3 request. */\n \tsettings_ = CameraMetadata(camera3Request->settings);\n \n@@ -180,10 +165,9 @@ Camera3RequestDescriptor::~Camera3RequestDescriptor() = default;\n  * \\brief Back pointer to the Camera3RequestDescriptor to which the StreamBuffer belongs\n  */\n Camera3RequestDescriptor::StreamBuffer::StreamBuffer(\n-\tCameraStream *cameraStream, const camera3_stream_buffer_t &buffer,\n-\tCamera3RequestDescriptor *requestDescriptor)\n+\tCameraStream *cameraStream, const camera3_stream_buffer_t &buffer)\n \t: stream(cameraStream), camera3Buffer(buffer.buffer),\n-\t  fence(buffer.acquire_fence), request(requestDescriptor)\n+\t  fence(buffer.acquire_fence)\n {\n }\n \ndiff --git a/src/android/camera_request.h b/src/android/camera_request.h\nindex 164b095b28..1a5393b1d6 100644\n--- a/src/android/camera_request.h\n+++ b/src/android/camera_request.h\n@@ -7,8 +7,8 @@\n \n #pragma once\n \n-#include <map>\n #include <memory>\n+#include <unordered_set>\n #include <vector>\n \n #include <libcamera/base/class.h>\n@@ -36,8 +36,7 @@ public:\n \n \tstruct StreamBuffer {\n \t\tStreamBuffer(CameraStream *stream,\n-\t\t\t     const camera3_stream_buffer_t &buffer,\n-\t\t\t     Camera3RequestDescriptor *request);\n+\t\t\t     const camera3_stream_buffer_t &buffer);\n \t\t~StreamBuffer();\n \n \t\tStreamBuffer(StreamBuffer &&);\n@@ -53,14 +52,14 @@ public:\n \t\tlibcamera::FrameBuffer *internalBuffer = nullptr;\n \t\tconst libcamera::FrameBuffer *srcBuffer = nullptr;\n \t\tstd::unique_ptr<CameraBuffer> dstBuffer;\n-\t\tCamera3RequestDescriptor *request;\n+\t\tCamera3RequestDescriptor *request = nullptr;\n \n \tprivate:\n \t\tLIBCAMERA_DISABLE_COPY(StreamBuffer)\n \t};\n \n \t/* Keeps track of streams requiring post-processing. */\n-\tstd::map<CameraStream *, StreamBuffer *> pendingStreamsToProcess_\n+\tstd::unordered_set<CameraStream *> pendingStreamsToProcess_\n \t\tLIBCAMERA_TSA_GUARDED_BY(streamsProcessMutex_);\n \tlibcamera::Mutex streamsProcessMutex_;\n \n",
    "prefixes": [
        "RFC",
        "v1",
        "52/54"
    ]
}