summaryrefslogtreecommitdiff
path: root/test/camera/buffer_import.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'test/camera/buffer_import.cpp')
-rw-r--r--test/camera/buffer_import.cpp425
1 files changed, 425 insertions, 0 deletions
diff --git a/test/camera/buffer_import.cpp b/test/camera/buffer_import.cpp
new file mode 100644
index 00000000..d6e4fd5b
--- /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);