Message ID | 20190713172351.25452-17-laurent.pinchart@ideasonboard.com |
---|---|
State | Accepted |
Headers | show |
Series |
|
Related | show |
Hi Jacopo, Laurent, Thanks for your patch. On 2019-07-13 20:23:51 +0300, Laurent Pinchart wrote: > From: Jacopo Mondi <jacopo@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@jmondi.org> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Niklas Söderlund <niklas.soderlund@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@lists.libcamera.org > https://lists.libcamera.org/listinfo/libcamera-devel
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' ], ]