[libcamera-devel] [PATCH v2 16/16] test: camera: Add buffer import and mapping test
Niklas Söderlund
niklas.soderlund at ragnatech.se
Sun Jul 14 13:07:19 CEST 2019
Hi Jacopo, Laurent,
Thanks for your patch.
On 2019-07-13 20:23:51 +0300, Laurent Pinchart wrote:
> From: Jacopo Mondi <jacopo at jmondi.org>
>
> Test buffer importing and mapping by streaming the VIMC camera to VIVID
> video output device performing zero-copy memory sharing using dmabuf
> file descriptors.
>
> The test cycle 20 buffers between the camera and the output with a 1:1
> buffer index to dmabuf fd mapping, then randomises the mapping with the
> same number of buffers on each side for 20 more frames, to finally
> increase the number of buffers on the output side for the 20 last
> frames. No remapping of dmabuf fd to buffer index should occur for the
> first 40 frames.
>
> Signed-off-by: Jacopo Mondi <jacopo at jmondi.org>
> Signed-off-by: Laurent Pinchart <laurent.pinchart at ideasonboard.com>
Reviewed-by: Niklas Söderlund <niklas.soderlund at ragnatech.se>
> ---
> test/camera/buffer_import.cpp | 425 ++++++++++++++++++++++++++++++++++
> test/camera/meson.build | 1 +
> 2 files changed, 426 insertions(+)
> create mode 100644 test/camera/buffer_import.cpp
>
> diff --git a/test/camera/buffer_import.cpp b/test/camera/buffer_import.cpp
> new file mode 100644
> index 000000000000..d6e4fd5bf6ad
> --- /dev/null
> +++ b/test/camera/buffer_import.cpp
> @@ -0,0 +1,425 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2019, Google Inc.
> + *
> + * libcamera Camera API tests
> + *
> + * Test importing buffers exported from the VIVID output device into a Camera
> + */
> +
> +#include <algorithm>
> +#include <iostream>
> +#include <numeric>
> +#include <random>
> +#include <vector>
> +
> +#include "device_enumerator.h"
> +#include "media_device.h"
> +#include "v4l2_videodevice.h"
> +
> +#include "camera_test.h"
> +
> +using namespace libcamera;
> +
> +/* Keep SINK_BUFFER_COUNT > CAMERA_BUFFER_COUNT + 1 */
> +static constexpr unsigned int SINK_BUFFER_COUNT = 8;
> +static constexpr unsigned int CAMERA_BUFFER_COUNT = 4;
> +
> +class FrameSink
> +{
> +public:
> + int init()
> + {
> + int ret;
> +
> + /* Locate and open the video device. */
> + std::string videoDeviceName = "vivid-000-vid-out";
> +
> + std::unique_ptr<DeviceEnumerator> enumerator =
> + DeviceEnumerator::create();
> + if (!enumerator) {
> + std::cout << "Failed to create device enumerator" << std::endl;
> + return TestFail;
> + }
> +
> + if (enumerator->enumerate()) {
> + std::cout << "Failed to enumerate media devices" << std::endl;
> + return TestFail;
> + }
> +
> + DeviceMatch dm("vivid");
> + dm.add(videoDeviceName);
> +
> + media_ = enumerator->search(dm);
> + if (!media_) {
> + std::cout << "No vivid output device available" << std::endl;
> + return TestSkip;
> + }
> +
> + video_ = V4L2VideoDevice::fromEntityName(media_.get(), videoDeviceName);
> + if (!video_) {
> + std::cout << "Unable to open " << videoDeviceName << std::endl;
> + return TestFail;
> + }
> +
> + if (video_->open())
> + return TestFail;
> +
> + /* Configure the format. */
> + ret = video_->getFormat(&format_);
> + if (ret) {
> + std::cout << "Failed to get format on output device" << std::endl;
> + return ret;
> + }
> +
> + format_.size.width = 640;
> + format_.size.height = 480;
> + format_.fourcc = V4L2_PIX_FMT_RGB24;
> + format_.planesCount = 1;
> + format_.planes[0].size = 640 * 480 * 3;
> + format_.planes[0].bpl = 640 * 3;
> +
> + if (video_->setFormat(&format_)) {
> + cleanup();
> + return TestFail;
> + }
> +
> + /* Export the buffers to a pool. */
> + pool_.createBuffers(SINK_BUFFER_COUNT);
> + ret = video_->exportBuffers(&pool_);
> + if (ret) {
> + std::cout << "Failed to export buffers" << std::endl;
> + cleanup();
> + return TestFail;
> + }
> +
> + /* Only use the first CAMERA_BUFFER_COUNT buffers to start with. */
> + availableBuffers_.resize(CAMERA_BUFFER_COUNT);
> + std::iota(availableBuffers_.begin(), availableBuffers_.end(), 0);
> +
> + /* Connect the buffer ready signal. */
> + video_->bufferReady.connect(this, &FrameSink::bufferComplete);
> +
> + return TestPass;
> + }
> +
> + void cleanup()
> + {
> + if (video_) {
> + video_->streamOff();
> + video_->releaseBuffers();
> + video_->close();
> + delete video_;
> + }
> +
> + if (media_)
> + media_->release();
> + }
> +
> + int start()
> + {
> + requestsCount_ = 0;
> + done_ = false;
> +
> + int ret = video_->streamOn();
> + if (ret < 0)
> + return ret;
> +
> + /* Queue all the initial requests. */
> + for (unsigned int index = 0; index < CAMERA_BUFFER_COUNT; ++index)
> + queueRequest(index);
> +
> + return 0;
> + }
> +
> + int stop()
> + {
> + return video_->streamOff();
> + }
> +
> + void requestComplete(uint64_t cookie, const Buffer *metadata)
> + {
> + unsigned int index = cookie;
> +
> + Buffer *buffer = new Buffer(index, metadata);
> + int ret = video_->queueBuffer(buffer);
> + if (ret < 0)
> + std::cout << "Failed to queue buffer to sink" << std::endl;
> + }
> +
> + bool done() const { return done_; }
> + const V4L2DeviceFormat &format() const { return format_; }
> +
> + Signal<uint64_t, int> requestReady;
> +
> +private:
> + void queueRequest(unsigned int index)
> + {
> + auto it = std::find(availableBuffers_.begin(),
> + availableBuffers_.end(), index);
> + availableBuffers_.erase(it);
> +
> + uint64_t cookie = index;
> + BufferMemory &mem = pool_.buffers()[index];
> + int dmabuf = mem.planes()[0].dmabuf();
> +
> + requestReady.emit(cookie, dmabuf);
> +
> + requestsCount_++;
> + }
> +
> + void bufferComplete(Buffer *buffer)
> + {
> + availableBuffers_.push_back(buffer->index());
> +
> + /*
> + * Pick the buffer for the next request among the available
> + * buffers.
> + *
> + * For the first 20 frames, select the buffer that has just
> + * completed to keep the mapping of dmabuf fds to buffers
> + * unchanged in the camera.
> + *
> + * For the next 20 frames, cycle randomly over the available
> + * buffers. The mapping should still be kept unchanged, as the
> + * camera should map using the cached fds.
> + *
> + * For the last 20 frames, cycles through all buffers, which
> + * should trash the mappings.
> + */
> + unsigned int index = buffer->index();
> + delete buffer;
> +
> + std::cout << "Completed buffer, request=" << requestsCount_
> + << ", available buffers=" << availableBuffers_.size()
> + << std::endl;
> +
> + if (requestsCount_ >= 60) {
> + if (availableBuffers_.size() == SINK_BUFFER_COUNT)
> + done_ = true;
> + return;
> + }
> +
> + if (requestsCount_ == 40) {
> + /* Add the remaining of the buffers. */
> + for (unsigned int i = CAMERA_BUFFER_COUNT;
> + i < SINK_BUFFER_COUNT; ++i)
> + availableBuffers_.push_back(i);
> + }
> +
> + if (requestsCount_ >= 20) {
> + /*
> + * Wait until we have enough buffers to make this
> + * meaningful. Preferably half of the camera buffers,
> + * but no less than 2 in any case.
> + */
> + const unsigned int min_pool_size =
> + std::min(CAMERA_BUFFER_COUNT / 2, 2U);
> + if (availableBuffers_.size() < min_pool_size)
> + return;
> +
> + /* Pick a buffer at random. */
> + unsigned int pos = random_() % availableBuffers_.size();
> + index = availableBuffers_[pos];
> + }
> +
> + queueRequest(index);
> + }
> +
> + std::shared_ptr<MediaDevice> media_;
> + V4L2VideoDevice *video_;
> + BufferPool pool_;
> + V4L2DeviceFormat format_;
> +
> + unsigned int requestsCount_;
> + std::vector<int> availableBuffers_;
> + std::random_device random_;
> +
> + bool done_;
> +};
> +
> +class BufferImportTest : public CameraTest
> +{
> +public:
> + BufferImportTest()
> + : CameraTest()
> + {
> + }
> +
> + void queueRequest(uint64_t cookie, int dmabuf)
> + {
> + Request *request = camera_->createRequest(cookie);
> +
> + std::unique_ptr<Buffer> buffer = stream_->createBuffer({ dmabuf, -1, -1 });
> + request->addBuffer(move(buffer));
> + camera_->queueRequest(request);
> + }
> +
> +protected:
> + void bufferComplete(Request *request, Buffer *buffer)
> + {
> + if (buffer->status() != Buffer::BufferSuccess)
> + return;
> +
> + unsigned int index = buffer->index();
> + int dmabuf = buffer->dmabufs()[0];
> +
> + /* Record dmabuf to index remappings. */
> + bool remapped = false;
> + if (bufferMappings_.find(index) != bufferMappings_.end()) {
> + if (bufferMappings_[index] != dmabuf)
> + remapped = true;
> + }
> +
> + std::cout << "Completed request " << framesCaptured_
> + << ": dmabuf fd " << dmabuf
> + << " -> index " << index
> + << " (" << (remapped ? 'R' : '-') << ")"
> + << std::endl;
> +
> + if (remapped)
> + bufferRemappings_.push_back(framesCaptured_);
> +
> + bufferMappings_[index] = dmabuf;
> + framesCaptured_++;
> +
> + sink_.requestComplete(request->cookie(), buffer);
> +
> + if (framesCaptured_ == 60)
> + sink_.stop();
> + }
> +
> + int initCamera()
> + {
> + if (camera_->acquire()) {
> + std::cout << "Failed to acquire the camera" << std::endl;
> + return TestFail;
> + }
> +
> + /*
> + * Configure the Stream to work with externally allocated
> + * buffers by setting the memoryType to ExternalMemory.
> + */
> + std::unique_ptr<CameraConfiguration> config;
> + config = camera_->generateConfiguration({ StreamRole::VideoRecording });
> + if (!config || config->size() != 1) {
> + std::cout << "Failed to generate configuration" << std::endl;
> + return TestFail;
> + }
> +
> + const V4L2DeviceFormat &format = sink_.format();
> +
> + StreamConfiguration &cfg = config->at(0);
> + cfg.size = format.size;
> + cfg.pixelFormat = format.fourcc;
> + cfg.bufferCount = CAMERA_BUFFER_COUNT;
> + cfg.memoryType = ExternalMemory;
> +
> + if (camera_->configure(config.get())) {
> + std::cout << "Failed to set configuration" << std::endl;
> + return TestFail;
> + }
> +
> + stream_ = cfg.stream();
> +
> + /* Allocate buffers. */
> + if (camera_->allocateBuffers()) {
> + std::cout << "Failed to allocate buffers" << std::endl;
> + return TestFail;
> + }
> +
> + /* Connect the buffer completed signal. */
> + camera_->bufferCompleted.connect(this, &BufferImportTest::bufferComplete);
> +
> + return TestPass;
> + }
> +
> + int init()
> + {
> + int ret = CameraTest::init();
> + if (ret)
> + return ret;
> +
> + ret = sink_.init();
> + if (ret != TestPass) {
> + cleanup();
> + return ret;
> + }
> +
> + ret = initCamera();
> + if (ret != TestPass) {
> + cleanup();
> + return ret;
> + }
> +
> + sink_.requestReady.connect(this, &BufferImportTest::queueRequest);
> + return TestPass;
> + }
> +
> + int run()
> + {
> + int ret;
> +
> + framesCaptured_ = 0;
> +
> + if (camera_->start()) {
> + std::cout << "Failed to start camera" << std::endl;
> + return TestFail;
> + }
> +
> + ret = sink_.start();
> + if (ret < 0) {
> + std::cout << "Failed to start sink" << std::endl;
> + return TestFail;
> + }
> +
> + EventDispatcher *dispatcher = CameraManager::instance()->eventDispatcher();
> +
> + Timer timer;
> + timer.start(3000);
> + while (timer.isRunning() && !sink_.done())
> + dispatcher->processEvents();
> +
> + std::cout << framesCaptured_ << " frames captured, "
> + << bufferRemappings_.size() << " buffers remapped"
> + << std::endl;
> +
> + if (framesCaptured_ < 60) {
> + std::cout << "Too few frames captured" << std::endl;
> + return TestFail;
> + }
> +
> + if (bufferRemappings_.empty()) {
> + std::cout << "No buffer remappings" << std::endl;
> + return TestFail;
> + }
> +
> + if (bufferRemappings_[0] < 40) {
> + std::cout << "Early buffer remapping" << std::endl;
> + return TestFail;
> + }
> +
> + return TestPass;
> + }
> +
> + void cleanup()
> + {
> + sink_.cleanup();
> +
> + camera_->stop();
> + camera_->freeBuffers();
> +
> + CameraTest::cleanup();
> + }
> +
> +private:
> + Stream *stream_;
> +
> + std::map<unsigned int, int> bufferMappings_;
> + std::vector<unsigned int> bufferRemappings_;
> + unsigned int framesCaptured_;
> +
> + FrameSink sink_;
> +};
> +
> +TEST_REGISTER(BufferImportTest);
> diff --git a/test/camera/meson.build b/test/camera/meson.build
> index 35e97ce5de1a..d6fd66b8f89e 100644
> --- a/test/camera/meson.build
> +++ b/test/camera/meson.build
> @@ -3,6 +3,7 @@
> camera_tests = [
> [ 'configuration_default', 'configuration_default.cpp' ],
> [ 'configuration_set', 'configuration_set.cpp' ],
> + [ 'buffer_import', 'buffer_import.cpp' ],
> [ 'statemachine', 'statemachine.cpp' ],
> [ 'capture', 'capture.cpp' ],
> ]
> --
> Regards,
>
> Laurent Pinchart
>
> _______________________________________________
> libcamera-devel mailing list
> libcamera-devel at lists.libcamera.org
> https://lists.libcamera.org/listinfo/libcamera-devel
--
Regards,
Niklas Söderlund
More information about the libcamera-devel
mailing list