{"id":1697,"url":"https://patchwork.libcamera.org/api/patches/1697/?format=json","web_url":"https://patchwork.libcamera.org/patch/1697/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20190713172351.25452-17-laurent.pinchart@ideasonboard.com>","date":"2019-07-13T17:23:51","name":"[libcamera-devel,v2,16/16] test: camera: Add buffer import and mapping test","commit_ref":null,"pull_url":null,"state":"accepted","archived":false,"hash":"108a843662148c4db1edc447b741f466a5906fb7","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/?format=json","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/1697/mbox/","series":[{"id":430,"url":"https://patchwork.libcamera.org/api/series/430/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=430","date":"2019-07-13T17:23:35","name":"Add support for external buffers","version":2,"mbox":"https://patchwork.libcamera.org/series/430/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/1697/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/1697/checks/","tags":{},"headers":{"Return-Path":"<laurent.pinchart@ideasonboard.com>","Received":["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 2073660C23\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSat, 13 Jul 2019 19:24:52 +0200 (CEST)","from pendragon.ideasonboard.com (softbank126209254147.bbtec.net\n\t[126.209.254.147])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id CFE1D443\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSat, 13 Jul 2019 19:24:50 +0200 (CEST)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1563038691;\n\tbh=dEx/9rE0de8TZG4UG9BJzrtnnGFClrqNJCCsYS0sLeQ=;\n\th=From:To:Subject:Date:In-Reply-To:References:From;\n\tb=j8P8/rHmxDkHRYmQ9vpXkfDh4LWlF2KiZxTSPpMKMkBskyEABp7eqv8q8eMcIw5Sl\n\tKqP0a60Ahe+tFTB0daiHJbCIypPPdbiSrNiJ/z1l6IN3mlPp6Y75xa2HjsOXT9oJWY\n\tq6uEp+CzulBr0SvoTanthpUMAY6dhRmV/yE7pr7c=","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"libcamera-devel@lists.libcamera.org","Date":"Sat, 13 Jul 2019 20:23:51 +0300","Message-Id":"<20190713172351.25452-17-laurent.pinchart@ideasonboard.com>","X-Mailer":"git-send-email 2.21.0","In-Reply-To":"<20190713172351.25452-1-laurent.pinchart@ideasonboard.com>","References":"<20190713172351.25452-1-laurent.pinchart@ideasonboard.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","Subject":"[libcamera-devel] [PATCH v2 16/16] test: camera: Add buffer import\n\tand mapping test","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.23","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>","X-List-Received-Date":"Sat, 13 Jul 2019 17:24:52 -0000"},"content":"From: Jacopo Mondi <jacopo@jmondi.org>\n\nTest buffer importing and mapping by streaming the VIMC camera to VIVID\nvideo output device performing zero-copy memory sharing using dmabuf\nfile descriptors.\n\nThe test cycle 20 buffers between the camera and the output with a 1:1\nbuffer index to dmabuf fd mapping, then randomises the mapping with the\nsame number of buffers on each side for 20 more frames, to finally\nincrease the number of buffers on the output side for the 20 last\nframes. No remapping of dmabuf fd to buffer index should occur for the\nfirst 40 frames.\n\nSigned-off-by: Jacopo Mondi <jacopo@jmondi.org>\nSigned-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n---\n test/camera/buffer_import.cpp | 425 ++++++++++++++++++++++++++++++++++\n test/camera/meson.build       |   1 +\n 2 files changed, 426 insertions(+)\n create mode 100644 test/camera/buffer_import.cpp","diff":"diff --git a/test/camera/buffer_import.cpp b/test/camera/buffer_import.cpp\nnew file mode 100644\nindex 000000000000..d6e4fd5bf6ad\n--- /dev/null\n+++ b/test/camera/buffer_import.cpp\n@@ -0,0 +1,425 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2019, Google Inc.\n+ *\n+ * libcamera Camera API tests\n+ *\n+ * Test importing buffers exported from the VIVID output device into a Camera\n+ */\n+\n+#include <algorithm>\n+#include <iostream>\n+#include <numeric>\n+#include <random>\n+#include <vector>\n+\n+#include \"device_enumerator.h\"\n+#include \"media_device.h\"\n+#include \"v4l2_videodevice.h\"\n+\n+#include \"camera_test.h\"\n+\n+using namespace libcamera;\n+\n+/* Keep SINK_BUFFER_COUNT > CAMERA_BUFFER_COUNT + 1 */\n+static constexpr unsigned int SINK_BUFFER_COUNT = 8;\n+static constexpr unsigned int CAMERA_BUFFER_COUNT = 4;\n+\n+class FrameSink\n+{\n+public:\n+\tint init()\n+\t{\n+\t\tint ret;\n+\n+\t\t/* Locate and open the video device. */\n+\t\tstd::string videoDeviceName = \"vivid-000-vid-out\";\n+\n+\t\tstd::unique_ptr<DeviceEnumerator> enumerator =\n+\t\t\tDeviceEnumerator::create();\n+\t\tif (!enumerator) {\n+\t\t\tstd::cout << \"Failed to create device enumerator\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif (enumerator->enumerate()) {\n+\t\t\tstd::cout << \"Failed to enumerate media devices\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tDeviceMatch dm(\"vivid\");\n+\t\tdm.add(videoDeviceName);\n+\n+\t\tmedia_ = enumerator->search(dm);\n+\t\tif (!media_) {\n+\t\t\tstd::cout << \"No vivid output device available\" << std::endl;\n+\t\t\treturn TestSkip;\n+\t\t}\n+\n+\t\tvideo_ = V4L2VideoDevice::fromEntityName(media_.get(), videoDeviceName);\n+\t\tif (!video_) {\n+\t\t\tstd::cout << \"Unable to open \" << videoDeviceName << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif (video_->open())\n+\t\t\treturn TestFail;\n+\n+\t\t/* Configure the format. */\n+\t\tret = video_->getFormat(&format_);\n+\t\tif (ret) {\n+\t\t\tstd::cout << \"Failed to get format on output device\" << std::endl;\n+\t\t\treturn ret;\n+\t\t}\n+\n+\t\tformat_.size.width = 640;\n+\t\tformat_.size.height = 480;\n+\t\tformat_.fourcc = V4L2_PIX_FMT_RGB24;\n+\t\tformat_.planesCount = 1;\n+\t\tformat_.planes[0].size = 640 * 480 * 3;\n+\t\tformat_.planes[0].bpl = 640 * 3;\n+\n+\t\tif (video_->setFormat(&format_)) {\n+\t\t\tcleanup();\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\t/* Export the buffers to a pool. */\n+\t\tpool_.createBuffers(SINK_BUFFER_COUNT);\n+\t\tret = video_->exportBuffers(&pool_);\n+\t\tif (ret) {\n+\t\t\tstd::cout << \"Failed to export buffers\" << std::endl;\n+\t\t\tcleanup();\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\t/* Only use the first CAMERA_BUFFER_COUNT buffers to start with. */\n+\t\tavailableBuffers_.resize(CAMERA_BUFFER_COUNT);\n+\t\tstd::iota(availableBuffers_.begin(), availableBuffers_.end(), 0);\n+\n+\t\t/* Connect the buffer ready signal. */\n+\t\tvideo_->bufferReady.connect(this, &FrameSink::bufferComplete);\n+\n+\t\treturn TestPass;\n+\t}\n+\n+\tvoid cleanup()\n+\t{\n+\t\tif (video_) {\n+\t\t\tvideo_->streamOff();\n+\t\t\tvideo_->releaseBuffers();\n+\t\t\tvideo_->close();\n+\t\t\tdelete video_;\n+\t\t}\n+\n+\t\tif (media_)\n+\t\t\tmedia_->release();\n+\t}\n+\n+\tint start()\n+\t{\n+\t\trequestsCount_ = 0;\n+\t\tdone_ = false;\n+\n+\t\tint ret = video_->streamOn();\n+\t\tif (ret < 0)\n+\t\t\treturn ret;\n+\n+\t\t/* Queue all the initial requests. */\n+\t\tfor (unsigned int index = 0; index < CAMERA_BUFFER_COUNT; ++index)\n+\t\t\tqueueRequest(index);\n+\n+\t\treturn 0;\n+\t}\n+\n+\tint stop()\n+\t{\n+\t\treturn video_->streamOff();\n+\t}\n+\n+\tvoid requestComplete(uint64_t cookie, const Buffer *metadata)\n+\t{\n+\t\tunsigned int index = cookie;\n+\n+\t\tBuffer *buffer = new Buffer(index, metadata);\n+\t\tint ret = video_->queueBuffer(buffer);\n+\t\tif (ret < 0)\n+\t\t\tstd::cout << \"Failed to queue buffer to sink\" << std::endl;\n+\t}\n+\n+\tbool done() const { return done_; }\n+\tconst V4L2DeviceFormat &format() const { return format_; }\n+\n+\tSignal<uint64_t, int> requestReady;\n+\n+private:\n+\tvoid queueRequest(unsigned int index)\n+\t{\n+\t\tauto it = std::find(availableBuffers_.begin(),\n+\t\t\t\t    availableBuffers_.end(), index);\n+\t\tavailableBuffers_.erase(it);\n+\n+\t\tuint64_t cookie = index;\n+\t\tBufferMemory &mem = pool_.buffers()[index];\n+\t\tint dmabuf = mem.planes()[0].dmabuf();\n+\n+\t\trequestReady.emit(cookie, dmabuf);\n+\n+\t\trequestsCount_++;\n+\t}\n+\n+\tvoid bufferComplete(Buffer *buffer)\n+\t{\n+\t\tavailableBuffers_.push_back(buffer->index());\n+\n+\t\t/*\n+\t\t * Pick the buffer for the next request among the available\n+\t\t * buffers.\n+\t\t *\n+\t\t * For the first 20 frames, select the buffer that has just\n+\t\t * completed to keep the mapping of dmabuf fds to buffers\n+\t\t * unchanged in the camera.\n+\t\t *\n+\t\t * For the next 20 frames, cycle randomly over the available\n+\t\t * buffers. The mapping should still be kept unchanged, as the\n+\t\t * camera should map using the cached fds.\n+\t\t *\n+\t\t * For the last 20 frames, cycles through all buffers, which\n+\t\t * should trash the mappings.\n+\t\t */\n+\t\tunsigned int index = buffer->index();\n+\t\tdelete buffer;\n+\n+\t\tstd::cout << \"Completed buffer, request=\" << requestsCount_\n+\t\t\t  << \", available buffers=\" << availableBuffers_.size()\n+\t\t\t  << std::endl;\n+\n+\t\tif (requestsCount_ >= 60) {\n+\t\t\tif (availableBuffers_.size() == SINK_BUFFER_COUNT)\n+\t\t\t\tdone_ = true;\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tif (requestsCount_ == 40) {\n+\t\t\t/* Add the remaining of the buffers. */\n+\t\t\tfor (unsigned int i = CAMERA_BUFFER_COUNT;\n+\t\t\t     i < SINK_BUFFER_COUNT; ++i)\n+\t\t\t\tavailableBuffers_.push_back(i);\n+\t\t}\n+\n+\t\tif (requestsCount_ >= 20) {\n+\t\t\t/*\n+\t\t\t * Wait until we have enough buffers to make this\n+\t\t\t * meaningful. Preferably half of the camera buffers,\n+\t\t\t * but no less than 2 in any case.\n+\t\t\t */\n+\t\t\tconst unsigned int min_pool_size =\n+\t\t\t\tstd::min(CAMERA_BUFFER_COUNT / 2, 2U);\n+\t\t\tif (availableBuffers_.size() < min_pool_size)\n+\t\t\t\treturn;\n+\n+\t\t\t/* Pick a buffer at random. */\n+\t\t\tunsigned int pos = random_() % availableBuffers_.size();\n+\t\t\tindex = availableBuffers_[pos];\n+\t\t}\n+\n+\t\tqueueRequest(index);\n+\t}\n+\n+\tstd::shared_ptr<MediaDevice> media_;\n+\tV4L2VideoDevice *video_;\n+\tBufferPool pool_;\n+\tV4L2DeviceFormat format_;\n+\n+\tunsigned int requestsCount_;\n+\tstd::vector<int> availableBuffers_;\n+\tstd::random_device random_;\n+\n+\tbool done_;\n+};\n+\n+class BufferImportTest : public CameraTest\n+{\n+public:\n+\tBufferImportTest()\n+\t\t: CameraTest()\n+\t{\n+\t}\n+\n+\tvoid queueRequest(uint64_t cookie, int dmabuf)\n+\t{\n+\t\tRequest *request = camera_->createRequest(cookie);\n+\n+\t\tstd::unique_ptr<Buffer> buffer = stream_->createBuffer({ dmabuf, -1, -1 });\n+\t\trequest->addBuffer(move(buffer));\n+\t\tcamera_->queueRequest(request);\n+\t}\n+\n+protected:\n+\tvoid bufferComplete(Request *request, Buffer *buffer)\n+\t{\n+\t\tif (buffer->status() != Buffer::BufferSuccess)\n+\t\t\treturn;\n+\n+\t\tunsigned int index = buffer->index();\n+\t\tint dmabuf = buffer->dmabufs()[0];\n+\n+\t\t/* Record dmabuf to index remappings. */\n+\t\tbool remapped = false;\n+\t\tif (bufferMappings_.find(index) != bufferMappings_.end()) {\n+\t\t\tif (bufferMappings_[index] != dmabuf)\n+\t\t\t\tremapped = true;\n+\t\t}\n+\n+\t\tstd::cout << \"Completed request \" << framesCaptured_\n+\t\t\t  << \": dmabuf fd \" << dmabuf\n+\t\t\t  << \" -> index \" << index\n+\t\t\t  << \" (\" << (remapped ? 'R' : '-') << \")\"\n+\t\t\t  << std::endl;\n+\n+\t\tif (remapped)\n+\t\t\tbufferRemappings_.push_back(framesCaptured_);\n+\n+\t\tbufferMappings_[index] = dmabuf;\n+\t\tframesCaptured_++;\n+\n+\t\tsink_.requestComplete(request->cookie(), buffer);\n+\n+\t\tif (framesCaptured_ == 60)\n+\t\t\tsink_.stop();\n+\t}\n+\n+\tint initCamera()\n+\t{\n+\t\tif (camera_->acquire()) {\n+\t\t\tstd::cout << \"Failed to acquire the camera\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\t/*\n+\t\t * Configure the Stream to work with externally allocated\n+\t\t * buffers by setting the memoryType to ExternalMemory.\n+\t\t */\n+\t\tstd::unique_ptr<CameraConfiguration> config;\n+\t\tconfig = camera_->generateConfiguration({ StreamRole::VideoRecording });\n+\t\tif (!config || config->size() != 1) {\n+\t\t\tstd::cout << \"Failed to generate configuration\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tconst V4L2DeviceFormat &format = sink_.format();\n+\n+\t\tStreamConfiguration &cfg = config->at(0);\n+\t\tcfg.size = format.size;\n+\t\tcfg.pixelFormat = format.fourcc;\n+\t\tcfg.bufferCount = CAMERA_BUFFER_COUNT;\n+\t\tcfg.memoryType = ExternalMemory;\n+\n+\t\tif (camera_->configure(config.get())) {\n+\t\t\tstd::cout << \"Failed to set configuration\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tstream_ = cfg.stream();\n+\n+\t\t/* Allocate buffers. */\n+\t\tif (camera_->allocateBuffers()) {\n+\t\t\tstd::cout << \"Failed to allocate buffers\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\t/* Connect the buffer completed signal. */\n+\t\tcamera_->bufferCompleted.connect(this, &BufferImportTest::bufferComplete);\n+\n+\t\treturn TestPass;\n+\t}\n+\n+\tint init()\n+\t{\n+\t\tint ret = CameraTest::init();\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\n+\t\tret = sink_.init();\n+\t\tif (ret != TestPass) {\n+\t\t\tcleanup();\n+\t\t\treturn ret;\n+\t\t}\n+\n+\t\tret = initCamera();\n+\t\tif (ret != TestPass) {\n+\t\t\tcleanup();\n+\t\t\treturn ret;\n+\t\t}\n+\n+\t\tsink_.requestReady.connect(this, &BufferImportTest::queueRequest);\n+\t\treturn TestPass;\n+\t}\n+\n+\tint run()\n+\t{\n+\t\tint ret;\n+\n+\t\tframesCaptured_ = 0;\n+\n+\t\tif (camera_->start()) {\n+\t\t\tstd::cout << \"Failed to start camera\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tret = sink_.start();\n+\t\tif (ret < 0) {\n+\t\t\tstd::cout << \"Failed to start sink\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tEventDispatcher *dispatcher = CameraManager::instance()->eventDispatcher();\n+\n+\t\tTimer timer;\n+\t\ttimer.start(3000);\n+\t\twhile (timer.isRunning() && !sink_.done())\n+\t\t\tdispatcher->processEvents();\n+\n+\t\tstd::cout << framesCaptured_ << \" frames captured, \"\n+\t\t\t  << bufferRemappings_.size() << \" buffers remapped\"\n+\t\t\t  << std::endl;\n+\n+\t\tif (framesCaptured_ < 60) {\n+\t\t\tstd::cout << \"Too few frames captured\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif (bufferRemappings_.empty()) {\n+\t\t\tstd::cout << \"No buffer remappings\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\tif (bufferRemappings_[0] < 40) {\n+\t\t\tstd::cout << \"Early buffer remapping\" << std::endl;\n+\t\t\treturn TestFail;\n+\t\t}\n+\n+\t\treturn TestPass;\n+\t}\n+\n+\tvoid cleanup()\n+\t{\n+\t\tsink_.cleanup();\n+\n+\t\tcamera_->stop();\n+\t\tcamera_->freeBuffers();\n+\n+\t\tCameraTest::cleanup();\n+\t}\n+\n+private:\n+\tStream *stream_;\n+\n+\tstd::map<unsigned int, int> bufferMappings_;\n+\tstd::vector<unsigned int> bufferRemappings_;\n+\tunsigned int framesCaptured_;\n+\n+\tFrameSink sink_;\n+};\n+\n+TEST_REGISTER(BufferImportTest);\ndiff --git a/test/camera/meson.build b/test/camera/meson.build\nindex 35e97ce5de1a..d6fd66b8f89e 100644\n--- a/test/camera/meson.build\n+++ b/test/camera/meson.build\n@@ -3,6 +3,7 @@\n camera_tests = [\n     [ 'configuration_default',  'configuration_default.cpp' ],\n     [ 'configuration_set',      'configuration_set.cpp' ],\n+    [ 'buffer_import',          'buffer_import.cpp' ],\n     [ 'statemachine',           'statemachine.cpp' ],\n     [ 'capture',                'capture.cpp' ],\n ]\n","prefixes":["libcamera-devel","v2","16/16"]}