[libcamera-devel,RFC,8/8] test: v4l2_videodevice: Add buffer import test

Message ID 20190630181049.9548-9-jacopo@jmondi.org
State Superseded
Headers show
Series
  • libcamera: Add support for importing external memory buffers
Related show

Commit Message

Jacopo Mondi June 30, 2019, 6:10 p.m. UTC
Test buffer importing by streaming the camera to a video output device
performing zero-copy memory sharing using dmabuf file descriptors.

Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
---
 test/v4l2_videodevice/buffer_import.cpp | 234 ++++++++++++++++++++++++
 test/v4l2_videodevice/meson.build       |   1 +
 2 files changed, 235 insertions(+)
 create mode 100644 test/v4l2_videodevice/buffer_import.cpp

Comments

Laurent Pinchart July 1, 2019, 11:57 p.m. UTC | #1
Hi Jacopo,

Thank you for the patch.

On Sun, Jun 30, 2019 at 08:10:49PM +0200, Jacopo Mondi wrote:
> Test buffer importing by streaming the camera to a video output device
> performing zero-copy memory sharing using dmabuf file descriptors.
> 
> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> ---
>  test/v4l2_videodevice/buffer_import.cpp | 234 ++++++++++++++++++++++++
>  test/v4l2_videodevice/meson.build       |   1 +
>  2 files changed, 235 insertions(+)
>  create mode 100644 test/v4l2_videodevice/buffer_import.cpp

I'll do a more in-depth review, but I think you could already move this
test to test/camera as it's not a v4l2_videodevice test (unlike the
buffer sharing test that tests buffer sharing between V4L2 video
devices).

> diff --git a/test/v4l2_videodevice/buffer_import.cpp b/test/v4l2_videodevice/buffer_import.cpp
> new file mode 100644
> index 000000000000..0a294b055af5
> --- /dev/null
> +++ b/test/v4l2_videodevice/buffer_import.cpp
> @@ -0,0 +1,234 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Copyright (C) 2019, Google Inc.
> + *
> + * libcamera V4L2 API tests
> + *
> + * Test importing buffers exported from an output device into a camera
> + */
> +
> +#include <iostream>
> +
> +#include <libcamera/buffer.h>
> +#include <libcamera/camera.h>
> +#include <libcamera/camera_manager.h>
> +#include <libcamera/event_dispatcher.h>
> +#include <libcamera/timer.h>
> +
> +#include "v4l2_videodevice_test.h"
> +
> +using namespace libcamera;
> +using namespace std;
> +
> +class BufferImportTest : public V4L2VideoDeviceTest
> +{
> +public:
> +	BufferImportTest()
> +		: V4L2VideoDeviceTest("vivid", "vivid-000-vid-out")
> +	{
> +	}
> +
> +protected:
> +	void cameraBufferComplete(Request *request, Buffer *buffer)
> +	{
> +		if (buffer->status() != Buffer::BufferSuccess)
> +			return;
> +
> +		capture_->queueBuffer(buffer);
> +	}
> +
> +	void requestComplete(Request *request, const std::map<Stream *, Buffer *> &buffers)
> +	{
> +		if (request->status() != Request::RequestComplete)
> +			return;
> +
> +		/* Reuse the buffers for a new request. */
> +		request = camera_->createRequest();
> +		request->setBuffers(buffers);
> +		camera_->queueRequest(request);
> +	}
> +
> +	int init()
> +	{
> +		constexpr unsigned int bufferCount = 4;
> +
> +		/* Get a camera where to capture frames from. */
> +		cm_ = CameraManager::instance();
> +
> +		if (cm_->start()) {
> +			cout << "Failed to start camera manager" << endl;
> +			return TestFail;
> +		}
> +
> +		camera_ = cm_->get("Integrated Camera: Integrated C");
> +		if (!camera_) {
> +			cout << "Can not find VIMC camera" << endl;
> +			return TestSkip;
> +		}
> +
> +		if (camera_->acquire()) {
> +			cout << "Failed to acquire the camera" << endl;
> +			return TestFail;
> +		}
> +
> +		/*
> +		 * Initialize the output device and export buffers in a pool.
> +		 * The 'output' device is actually called capture_ by the base
> +		 * class.
> +		 */
> +		int ret = V4L2VideoDeviceTest::init();
> +		if (ret) {
> +			cerr << "Failed to initialize output device" << endl;
> +			return ret;
> +		}
> +
> +		/*
> +		 * Set a known format on the output devices, then apply it
> +		 * to the camera.
> +		 */
> +		V4L2DeviceFormat format = {};
> +		if (capture_->getFormat(&format)) {
> +			cleanup();
> +			return TestFail;
> +		}
> +
> +		format.size.width = 640;
> +		format.size.height = 480;
> +		format.fourcc = V4L2_PIX_FMT_YUYV;
> +		format.planesCount = 1;
> +		format.planes[0].size = 640 * 480 * 2;
> +		format.planes[0].bpl = 640 * 2;
> +		if (capture_->setFormat(&format)) {
> +			cleanup();
> +			return TestFail;
> +		}
> +
> +		cout << "Output format: " << format.toString();
> +
> +		config_ = camera_->generateConfiguration({ StreamRole::VideoRecording });
> +		if (!config_ || config_->size() != 1) {
> +			cout << "Failed to generate default configuration" << endl;
> +			cleanup();
> +			return TestFail;
> +		}
> +
> +		/*
> +		 * Configure the Stream to work with externally allocated
> +		 * buffers by setting the memoryType to ExternalMemory.
> +		 */
> +		StreamConfiguration &cfg = config_->at(0);
> +		cfg.size = format.size;
> +		cfg.pixelFormat = format.fourcc;
> +		cfg.memoryType = ExternalMemory;
> +
> +		if (camera_->configure(config_.get())) {
> +			cout << "Failed to set modified configuration" << endl;
> +			cleanup();
> +			return TestFail;
> +		}
> +		cout << "Capture format: " << format.toString();
> +
> +		/*
> +		 * Export the output buffers to a pool and then import
> +		 * them before setting up buffers in the Camera.
> +		 */
> +		pool_.createBuffers(bufferCount);
> +		ret = capture_->exportBuffers(&pool_);
> +		if (ret) {
> +			std::cout << "Failed to export buffers" << std::endl;
> +			cleanup();
> +			return TestFail;
> +		}
> +
> +		if (camera_->allocateBuffers()) {
> +			cout << "Failed to allocate buffers" << endl;
> +			return TestFail;
> +		}
> +
> +		return TestPass;
> +	}
> +
> +	int run()
> +	{
> +		std::vector<Request *> requests;
> +		StreamConfiguration &cfg = config_->at(0);
> +		Stream *stream = cfg.stream();
> +		/* Create one request for each output video buffer. */
> +		for (Buffer &buffer : pool_.buffers()) {
> +			Request *request = camera_->createRequest();
> +			if (!request) {
> +				cout << "Failed to create request" << endl;
> +				return TestFail;
> +			}
> +
> +			std::map<Stream *, Buffer *> map = { { stream, &buffer } };
> +			if (request->setBuffers(map)) {
> +				cout << "Failed to associating buffer with request" << endl;
> +				return TestFail;
> +			}
> +
> +			requests.push_back(request);
> +		}
> +
> +		/* Connect the buffer ready signals of camera and output */
> +		camera_->bufferCompleted.connect(this,
> +				&BufferImportTest::cameraBufferComplete);
> +
> +		/* Connect the request ready signal to re-queue requests. */
> +		camera_->requestCompleted.connect(this,
> +				&BufferImportTest::requestComplete);
> +
> +		capture_->streamOn();
> +		if (camera_->start()) {
> +			cout << "Failed to start camera" << endl;
> +			return TestFail;
> +		}
> +
> +		for (Request *request : requests) {
> +			if (camera_->queueRequest(request)) {
> +				cout << "Failed to queue request" << endl;
> +				camera_->stop();
> +				capture_->streamOff();
> +				cleanup();
> +				return TestFail;
> +			}
> +		}
> +
> +		EventDispatcher *dispatcher = CameraManager::instance()->eventDispatcher();
> +
> +		Timer timer;
> +		timer.start(2000);
> +		while (timer.isRunning())
> +			dispatcher->processEvents();
> +
> +		if (camera_->stop()) {
> +			cout << "Failed to stop camera" << endl;
> +			return TestFail;
> +		}
> +
> +		capture_->streamOff();
> +
> +		return TestPass;
> +	}
> +
> +	void cleanup()
> +	{
> +		camera_->freeBuffers();
> +
> +		if (camera_) {
> +			camera_->release();
> +			camera_.reset();
> +		}
> +
> +		cm_->stop();
> +
> +		V4L2VideoDeviceTest::cleanup();
> +	}
> +
> +private:
> +	CameraManager *cm_;
> +	std::shared_ptr<Camera> camera_;
> +	std::unique_ptr<CameraConfiguration> config_;
> +};
> +
> +TEST_REGISTER(BufferImportTest);
> diff --git a/test/v4l2_videodevice/meson.build b/test/v4l2_videodevice/meson.build
> index 76be5e142bb6..15169abe48d3 100644
> --- a/test/v4l2_videodevice/meson.build
> +++ b/test/v4l2_videodevice/meson.build
> @@ -7,6 +7,7 @@ v4l2_videodevice_tests = [
>      [ 'stream_on_off',      'stream_on_off.cpp' ],
>      [ 'capture_async',      'capture_async.cpp' ],
>      [ 'buffer_sharing',     'buffer_sharing.cpp' ],
> +    [ 'buffer_import',      'buffer_import.cpp' ],
>  ]
>  
>  foreach t : v4l2_videodevice_tests

Patch

diff --git a/test/v4l2_videodevice/buffer_import.cpp b/test/v4l2_videodevice/buffer_import.cpp
new file mode 100644
index 000000000000..0a294b055af5
--- /dev/null
+++ b/test/v4l2_videodevice/buffer_import.cpp
@@ -0,0 +1,234 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * libcamera V4L2 API tests
+ *
+ * Test importing buffers exported from an output device into a camera
+ */
+
+#include <iostream>
+
+#include <libcamera/buffer.h>
+#include <libcamera/camera.h>
+#include <libcamera/camera_manager.h>
+#include <libcamera/event_dispatcher.h>
+#include <libcamera/timer.h>
+
+#include "v4l2_videodevice_test.h"
+
+using namespace libcamera;
+using namespace std;
+
+class BufferImportTest : public V4L2VideoDeviceTest
+{
+public:
+	BufferImportTest()
+		: V4L2VideoDeviceTest("vivid", "vivid-000-vid-out")
+	{
+	}
+
+protected:
+	void cameraBufferComplete(Request *request, Buffer *buffer)
+	{
+		if (buffer->status() != Buffer::BufferSuccess)
+			return;
+
+		capture_->queueBuffer(buffer);
+	}
+
+	void requestComplete(Request *request, const std::map<Stream *, Buffer *> &buffers)
+	{
+		if (request->status() != Request::RequestComplete)
+			return;
+
+		/* Reuse the buffers for a new request. */
+		request = camera_->createRequest();
+		request->setBuffers(buffers);
+		camera_->queueRequest(request);
+	}
+
+	int init()
+	{
+		constexpr unsigned int bufferCount = 4;
+
+		/* Get a camera where to capture frames from. */
+		cm_ = CameraManager::instance();
+
+		if (cm_->start()) {
+			cout << "Failed to start camera manager" << endl;
+			return TestFail;
+		}
+
+		camera_ = cm_->get("Integrated Camera: Integrated C");
+		if (!camera_) {
+			cout << "Can not find VIMC camera" << endl;
+			return TestSkip;
+		}
+
+		if (camera_->acquire()) {
+			cout << "Failed to acquire the camera" << endl;
+			return TestFail;
+		}
+
+		/*
+		 * Initialize the output device and export buffers in a pool.
+		 * The 'output' device is actually called capture_ by the base
+		 * class.
+		 */
+		int ret = V4L2VideoDeviceTest::init();
+		if (ret) {
+			cerr << "Failed to initialize output device" << endl;
+			return ret;
+		}
+
+		/*
+		 * Set a known format on the output devices, then apply it
+		 * to the camera.
+		 */
+		V4L2DeviceFormat format = {};
+		if (capture_->getFormat(&format)) {
+			cleanup();
+			return TestFail;
+		}
+
+		format.size.width = 640;
+		format.size.height = 480;
+		format.fourcc = V4L2_PIX_FMT_YUYV;
+		format.planesCount = 1;
+		format.planes[0].size = 640 * 480 * 2;
+		format.planes[0].bpl = 640 * 2;
+		if (capture_->setFormat(&format)) {
+			cleanup();
+			return TestFail;
+		}
+
+		cout << "Output format: " << format.toString();
+
+		config_ = camera_->generateConfiguration({ StreamRole::VideoRecording });
+		if (!config_ || config_->size() != 1) {
+			cout << "Failed to generate default configuration" << endl;
+			cleanup();
+			return TestFail;
+		}
+
+		/*
+		 * Configure the Stream to work with externally allocated
+		 * buffers by setting the memoryType to ExternalMemory.
+		 */
+		StreamConfiguration &cfg = config_->at(0);
+		cfg.size = format.size;
+		cfg.pixelFormat = format.fourcc;
+		cfg.memoryType = ExternalMemory;
+
+		if (camera_->configure(config_.get())) {
+			cout << "Failed to set modified configuration" << endl;
+			cleanup();
+			return TestFail;
+		}
+		cout << "Capture format: " << format.toString();
+
+		/*
+		 * Export the output buffers to a pool and then import
+		 * them before setting up buffers in the Camera.
+		 */
+		pool_.createBuffers(bufferCount);
+		ret = capture_->exportBuffers(&pool_);
+		if (ret) {
+			std::cout << "Failed to export buffers" << std::endl;
+			cleanup();
+			return TestFail;
+		}
+
+		if (camera_->allocateBuffers()) {
+			cout << "Failed to allocate buffers" << endl;
+			return TestFail;
+		}
+
+		return TestPass;
+	}
+
+	int run()
+	{
+		std::vector<Request *> requests;
+		StreamConfiguration &cfg = config_->at(0);
+		Stream *stream = cfg.stream();
+		/* Create one request for each output video buffer. */
+		for (Buffer &buffer : pool_.buffers()) {
+			Request *request = camera_->createRequest();
+			if (!request) {
+				cout << "Failed to create request" << endl;
+				return TestFail;
+			}
+
+			std::map<Stream *, Buffer *> map = { { stream, &buffer } };
+			if (request->setBuffers(map)) {
+				cout << "Failed to associating buffer with request" << endl;
+				return TestFail;
+			}
+
+			requests.push_back(request);
+		}
+
+		/* Connect the buffer ready signals of camera and output */
+		camera_->bufferCompleted.connect(this,
+				&BufferImportTest::cameraBufferComplete);
+
+		/* Connect the request ready signal to re-queue requests. */
+		camera_->requestCompleted.connect(this,
+				&BufferImportTest::requestComplete);
+
+		capture_->streamOn();
+		if (camera_->start()) {
+			cout << "Failed to start camera" << endl;
+			return TestFail;
+		}
+
+		for (Request *request : requests) {
+			if (camera_->queueRequest(request)) {
+				cout << "Failed to queue request" << endl;
+				camera_->stop();
+				capture_->streamOff();
+				cleanup();
+				return TestFail;
+			}
+		}
+
+		EventDispatcher *dispatcher = CameraManager::instance()->eventDispatcher();
+
+		Timer timer;
+		timer.start(2000);
+		while (timer.isRunning())
+			dispatcher->processEvents();
+
+		if (camera_->stop()) {
+			cout << "Failed to stop camera" << endl;
+			return TestFail;
+		}
+
+		capture_->streamOff();
+
+		return TestPass;
+	}
+
+	void cleanup()
+	{
+		camera_->freeBuffers();
+
+		if (camera_) {
+			camera_->release();
+			camera_.reset();
+		}
+
+		cm_->stop();
+
+		V4L2VideoDeviceTest::cleanup();
+	}
+
+private:
+	CameraManager *cm_;
+	std::shared_ptr<Camera> camera_;
+	std::unique_ptr<CameraConfiguration> config_;
+};
+
+TEST_REGISTER(BufferImportTest);
diff --git a/test/v4l2_videodevice/meson.build b/test/v4l2_videodevice/meson.build
index 76be5e142bb6..15169abe48d3 100644
--- a/test/v4l2_videodevice/meson.build
+++ b/test/v4l2_videodevice/meson.build
@@ -7,6 +7,7 @@  v4l2_videodevice_tests = [
     [ 'stream_on_off',      'stream_on_off.cpp' ],
     [ 'capture_async',      'capture_async.cpp' ],
     [ 'buffer_sharing',     'buffer_sharing.cpp' ],
+    [ 'buffer_import',      'buffer_import.cpp' ],
 ]
 
 foreach t : v4l2_videodevice_tests