From 3a6c4bd146cc6647daf458bbc048bd861e702f62 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Wed, 12 Jun 2019 13:09:57 +0100 Subject: libcamera: Rename V4L2Device to V4L2VideoDevice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In preparation of creating a new V4L2Device base class, rename V4L2Device to V4L2VideoDevice. This is a project wide rename without any intended functional change. Signed-off-by: Jacopo Mondi Signed-off-by: Kieran Bingham Reviewed-by: Kieran Bingham Reviewed-by: Niklas Söderlund Reviewed-by: Laurent Pinchart --- include/libcamera/buffer.h | 2 +- src/libcamera/include/v4l2_device.h | 189 ---- src/libcamera/include/v4l2_videodevice.h | 189 ++++ src/libcamera/meson.build | 4 +- src/libcamera/pipeline/ipu3/ipu3.cpp | 24 +- src/libcamera/pipeline/rkisp1/rkisp1.cpp | 6 +- src/libcamera/pipeline/uvcvideo.cpp | 6 +- src/libcamera/pipeline/vimc.cpp | 6 +- src/libcamera/v4l2_device.cpp | 1126 ---------------------- src/libcamera/v4l2_subdevice.cpp | 2 +- src/libcamera/v4l2_videodevice.cpp | 1127 +++++++++++++++++++++++ test/meson.build | 2 +- test/v4l2_device/buffer_sharing.cpp | 186 ---- test/v4l2_device/capture_async.cpp | 89 -- test/v4l2_device/double_open.cpp | 41 - test/v4l2_device/formats.cpp | 54 -- test/v4l2_device/meson.build | 18 - test/v4l2_device/request_buffers.cpp | 36 - test/v4l2_device/stream_on_off.cpp | 38 - test/v4l2_device/v4l2_device_test.cpp | 87 -- test/v4l2_device/v4l2_device_test.h | 42 - test/v4l2_videodevice/buffer_sharing.cpp | 186 ++++ test/v4l2_videodevice/capture_async.cpp | 89 ++ test/v4l2_videodevice/double_open.cpp | 41 + test/v4l2_videodevice/formats.cpp | 54 ++ test/v4l2_videodevice/meson.build | 18 + test/v4l2_videodevice/request_buffers.cpp | 36 + test/v4l2_videodevice/stream_on_off.cpp | 38 + test/v4l2_videodevice/v4l2_videodevice_test.cpp | 87 ++ test/v4l2_videodevice/v4l2_videodevice_test.h | 42 + 30 files changed, 1933 insertions(+), 1932 deletions(-) delete mode 100644 src/libcamera/include/v4l2_device.h create mode 100644 src/libcamera/include/v4l2_videodevice.h delete mode 100644 src/libcamera/v4l2_device.cpp create mode 100644 src/libcamera/v4l2_videodevice.cpp delete mode 100644 test/v4l2_device/buffer_sharing.cpp delete mode 100644 test/v4l2_device/capture_async.cpp delete mode 100644 test/v4l2_device/double_open.cpp delete mode 100644 test/v4l2_device/formats.cpp delete mode 100644 test/v4l2_device/meson.build delete mode 100644 test/v4l2_device/request_buffers.cpp delete mode 100644 test/v4l2_device/stream_on_off.cpp delete mode 100644 test/v4l2_device/v4l2_device_test.cpp delete mode 100644 test/v4l2_device/v4l2_device_test.h create mode 100644 test/v4l2_videodevice/buffer_sharing.cpp create mode 100644 test/v4l2_videodevice/capture_async.cpp create mode 100644 test/v4l2_videodevice/double_open.cpp create mode 100644 test/v4l2_videodevice/formats.cpp create mode 100644 test/v4l2_videodevice/meson.build create mode 100644 test/v4l2_videodevice/request_buffers.cpp create mode 100644 test/v4l2_videodevice/stream_on_off.cpp create mode 100644 test/v4l2_videodevice/v4l2_videodevice_test.cpp create mode 100644 test/v4l2_videodevice/v4l2_videodevice_test.h diff --git a/include/libcamera/buffer.h b/include/libcamera/buffer.h index 8f9b42e3..260a62e9 100644 --- a/include/libcamera/buffer.h +++ b/include/libcamera/buffer.h @@ -59,7 +59,7 @@ private: friend class BufferPool; friend class PipelineHandler; friend class Request; - friend class V4L2Device; + friend class V4L2VideoDevice; void cancel(); diff --git a/src/libcamera/include/v4l2_device.h b/src/libcamera/include/v4l2_device.h deleted file mode 100644 index 1a67850a..00000000 --- a/src/libcamera/include/v4l2_device.h +++ /dev/null @@ -1,189 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2019, Google Inc. - * - * v4l2_device.h - V4L2 Device - */ -#ifndef __LIBCAMERA_V4L2_DEVICE_H__ -#define __LIBCAMERA_V4L2_DEVICE_H__ - -#include -#include -#include - -#include - -#include -#include - -#include "formats.h" -#include "log.h" - -namespace libcamera { - -class Buffer; -class BufferPool; -class EventNotifier; -class MediaDevice; -class MediaEntity; - -struct V4L2Capability final : v4l2_capability { - const char *driver() const - { - return reinterpret_cast(v4l2_capability::driver); - } - const char *card() const - { - return reinterpret_cast(v4l2_capability::card); - } - const char *bus_info() const - { - return reinterpret_cast(v4l2_capability::bus_info); - } - unsigned int device_caps() const - { - return capabilities & V4L2_CAP_DEVICE_CAPS - ? v4l2_capability::device_caps - : v4l2_capability::capabilities; - } - bool isMultiplanar() const - { - return device_caps() & (V4L2_CAP_VIDEO_CAPTURE_MPLANE | - V4L2_CAP_VIDEO_OUTPUT_MPLANE); - } - bool isCapture() const - { - return device_caps() & (V4L2_CAP_VIDEO_CAPTURE | - V4L2_CAP_VIDEO_CAPTURE_MPLANE | - V4L2_CAP_META_CAPTURE); - } - bool isOutput() const - { - return device_caps() & (V4L2_CAP_VIDEO_OUTPUT | - V4L2_CAP_VIDEO_OUTPUT_MPLANE | - V4L2_CAP_META_OUTPUT); - } - bool isVideo() const - { - return device_caps() & (V4L2_CAP_VIDEO_CAPTURE | - V4L2_CAP_VIDEO_CAPTURE_MPLANE | - V4L2_CAP_VIDEO_OUTPUT | - V4L2_CAP_VIDEO_OUTPUT_MPLANE); - } - bool isMeta() const - { - return device_caps() & (V4L2_CAP_META_CAPTURE | - V4L2_CAP_META_OUTPUT); - } - bool isVideoCapture() const - { - return isVideo() && isCapture(); - } - bool isVideoOutput() const - { - return isVideo() && isOutput(); - } - bool isMetaCapture() const - { - return isMeta() && isCapture(); - } - bool isMetaOutput() const - { - return isMeta() && isOutput(); - } - bool hasStreaming() const - { - return device_caps() & V4L2_CAP_STREAMING; - } -}; - -class V4L2DeviceFormat -{ -public: - uint32_t fourcc; - Size size; - - struct { - uint32_t size; - uint32_t bpl; - } planes[3]; - unsigned int planesCount; - - const std::string toString() const; -}; - -class V4L2Device : protected Loggable -{ -public: - explicit V4L2Device(const std::string &deviceNode); - explicit V4L2Device(const MediaEntity *entity); - V4L2Device(const V4L2Device &) = delete; - ~V4L2Device(); - - V4L2Device &operator=(const V4L2Device &) = delete; - - int open(); - bool isOpen() const; - void close(); - - const char *driverName() const { return caps_.driver(); } - const char *deviceName() const { return caps_.card(); } - const char *busName() const { return caps_.bus_info(); } - const std::string &deviceNode() const { return deviceNode_; } - - int getFormat(V4L2DeviceFormat *format); - int setFormat(V4L2DeviceFormat *format); - ImageFormats formats(); - - int exportBuffers(BufferPool *pool); - int importBuffers(BufferPool *pool); - int releaseBuffers(); - - int queueBuffer(Buffer *buffer); - Signal bufferReady; - - int streamOn(); - int streamOff(); - - static V4L2Device *fromEntityName(const MediaDevice *media, - const std::string &entity); - -protected: - std::string logPrefix() const; - -private: - int getFormatMeta(V4L2DeviceFormat *format); - int setFormatMeta(V4L2DeviceFormat *format); - - int getFormatMultiplane(V4L2DeviceFormat *format); - int setFormatMultiplane(V4L2DeviceFormat *format); - - int getFormatSingleplane(V4L2DeviceFormat *format); - int setFormatSingleplane(V4L2DeviceFormat *format); - - int requestBuffers(unsigned int count); - int createPlane(Buffer *buffer, unsigned int plane, - unsigned int length); - - std::vector enumPixelformats(); - std::vector enumSizes(unsigned int pixelFormat); - - Buffer *dequeueBuffer(); - void bufferAvailable(EventNotifier *notifier); - - std::string deviceNode_; - int fd_; - V4L2Capability caps_; - - enum v4l2_buf_type bufferType_; - enum v4l2_memory memoryType_; - - BufferPool *bufferPool_; - std::atomic queuedBuffersCount_; - - EventNotifier *fdEvent_; -}; - -} /* namespace libcamera */ - -#endif /* __LIBCAMERA_V4L2_DEVICE_H__ */ diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h new file mode 100644 index 00000000..89565880 --- /dev/null +++ b/src/libcamera/include/v4l2_videodevice.h @@ -0,0 +1,189 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * v4l2_videodevice.h - V4L2 Video Device + */ +#ifndef __LIBCAMERA_V4L2_VIDEODEVICE_H__ +#define __LIBCAMERA_V4L2_VIDEODEVICE_H__ + +#include +#include +#include + +#include + +#include +#include + +#include "formats.h" +#include "log.h" + +namespace libcamera { + +class Buffer; +class BufferPool; +class EventNotifier; +class MediaDevice; +class MediaEntity; + +struct V4L2Capability final : v4l2_capability { + const char *driver() const + { + return reinterpret_cast(v4l2_capability::driver); + } + const char *card() const + { + return reinterpret_cast(v4l2_capability::card); + } + const char *bus_info() const + { + return reinterpret_cast(v4l2_capability::bus_info); + } + unsigned int device_caps() const + { + return capabilities & V4L2_CAP_DEVICE_CAPS + ? v4l2_capability::device_caps + : v4l2_capability::capabilities; + } + bool isMultiplanar() const + { + return device_caps() & (V4L2_CAP_VIDEO_CAPTURE_MPLANE | + V4L2_CAP_VIDEO_OUTPUT_MPLANE); + } + bool isCapture() const + { + return device_caps() & (V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VIDEO_CAPTURE_MPLANE | + V4L2_CAP_META_CAPTURE); + } + bool isOutput() const + { + return device_caps() & (V4L2_CAP_VIDEO_OUTPUT | + V4L2_CAP_VIDEO_OUTPUT_MPLANE | + V4L2_CAP_META_OUTPUT); + } + bool isVideo() const + { + return device_caps() & (V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VIDEO_CAPTURE_MPLANE | + V4L2_CAP_VIDEO_OUTPUT | + V4L2_CAP_VIDEO_OUTPUT_MPLANE); + } + bool isMeta() const + { + return device_caps() & (V4L2_CAP_META_CAPTURE | + V4L2_CAP_META_OUTPUT); + } + bool isVideoCapture() const + { + return isVideo() && isCapture(); + } + bool isVideoOutput() const + { + return isVideo() && isOutput(); + } + bool isMetaCapture() const + { + return isMeta() && isCapture(); + } + bool isMetaOutput() const + { + return isMeta() && isOutput(); + } + bool hasStreaming() const + { + return device_caps() & V4L2_CAP_STREAMING; + } +}; + +class V4L2DeviceFormat +{ +public: + uint32_t fourcc; + Size size; + + struct { + uint32_t size; + uint32_t bpl; + } planes[3]; + unsigned int planesCount; + + const std::string toString() const; +}; + +class V4L2VideoDevice : protected Loggable +{ +public: + explicit V4L2VideoDevice(const std::string &deviceNode); + explicit V4L2VideoDevice(const MediaEntity *entity); + V4L2VideoDevice(const V4L2VideoDevice &) = delete; + ~V4L2VideoDevice(); + + V4L2VideoDevice &operator=(const V4L2VideoDevice &) = delete; + + int open(); + bool isOpen() const; + void close(); + + const char *driverName() const { return caps_.driver(); } + const char *deviceName() const { return caps_.card(); } + const char *busName() const { return caps_.bus_info(); } + const std::string &deviceNode() const { return deviceNode_; } + + int getFormat(V4L2DeviceFormat *format); + int setFormat(V4L2DeviceFormat *format); + ImageFormats formats(); + + int exportBuffers(BufferPool *pool); + int importBuffers(BufferPool *pool); + int releaseBuffers(); + + int queueBuffer(Buffer *buffer); + Signal bufferReady; + + int streamOn(); + int streamOff(); + + static V4L2VideoDevice *fromEntityName(const MediaDevice *media, + const std::string &entity); + +protected: + std::string logPrefix() const; + +private: + int getFormatMeta(V4L2DeviceFormat *format); + int setFormatMeta(V4L2DeviceFormat *format); + + int getFormatMultiplane(V4L2DeviceFormat *format); + int setFormatMultiplane(V4L2DeviceFormat *format); + + int getFormatSingleplane(V4L2DeviceFormat *format); + int setFormatSingleplane(V4L2DeviceFormat *format); + + int requestBuffers(unsigned int count); + int createPlane(Buffer *buffer, unsigned int plane, + unsigned int length); + + std::vector enumPixelformats(); + std::vector enumSizes(unsigned int pixelFormat); + + Buffer *dequeueBuffer(); + void bufferAvailable(EventNotifier *notifier); + + std::string deviceNode_; + int fd_; + V4L2Capability caps_; + + enum v4l2_buf_type bufferType_; + enum v4l2_memory memoryType_; + + BufferPool *bufferPool_; + std::atomic queuedBuffersCount_; + + EventNotifier *fdEvent_; +}; + +} /* namespace libcamera */ + +#endif /* __LIBCAMERA_V4L2_VIDEODEVICE_H__ */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index 1ca1083c..15ab53b1 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -23,8 +23,8 @@ libcamera_sources = files([ 'stream.cpp', 'timer.cpp', 'utils.cpp', - 'v4l2_device.cpp', 'v4l2_subdevice.cpp', + 'v4l2_videodevice.cpp', ]) libcamera_headers = files([ @@ -41,8 +41,8 @@ libcamera_headers = files([ 'include/media_object.h', 'include/pipeline_handler.h', 'include/utils.h', - 'include/v4l2_device.h', 'include/v4l2_subdevice.h', + 'include/v4l2_videodevice.h', ]) libcamera_internal_includes = include_directories('include') diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp index f2bdecba..1c0a9825 100644 --- a/src/libcamera/pipeline/ipu3/ipu3.cpp +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp @@ -22,8 +22,8 @@ #include "media_device.h" #include "pipeline_handler.h" #include "utils.h" -#include "v4l2_device.h" #include "v4l2_subdevice.h" +#include "v4l2_videodevice.h" namespace libcamera { @@ -39,7 +39,7 @@ public: /* ImgU output descriptor: group data specific to an ImgU output. */ struct ImgUOutput { - V4L2Device *dev; + V4L2VideoDevice *dev; unsigned int pad; std::string name; BufferPool *pool; @@ -85,7 +85,7 @@ public: MediaDevice *media_; V4L2Subdevice *imgu_; - V4L2Device *input_; + V4L2VideoDevice *input_; ImgUOutput output_; ImgUOutput viewfinder_; ImgUOutput stat_; @@ -125,7 +125,7 @@ public: static int mediaBusToFormat(unsigned int code); - V4L2Device *output_; + V4L2VideoDevice *output_; V4L2Subdevice *csi2_; CameraSensor *sensor_; @@ -943,7 +943,7 @@ void IPU3CameraData::cio2BufferReady(Buffer *buffer) * Create and open the V4L2 devices and subdevices of the ImgU instance * with \a index. * - * In case of errors the created V4L2Device and V4L2Subdevice instances + * In case of errors the created V4L2VideoDevice and V4L2Subdevice instances * are destroyed at pipeline handler delete time. * * \return 0 on success or a negative error code otherwise @@ -966,12 +966,12 @@ int ImgUDevice::init(MediaDevice *media, unsigned int index) if (ret) return ret; - input_ = V4L2Device::fromEntityName(media, name_ + " input"); + input_ = V4L2VideoDevice::fromEntityName(media, name_ + " input"); ret = input_->open(); if (ret) return ret; - output_.dev = V4L2Device::fromEntityName(media, name_ + " output"); + output_.dev = V4L2VideoDevice::fromEntityName(media, name_ + " output"); ret = output_.dev->open(); if (ret) return ret; @@ -980,8 +980,8 @@ int ImgUDevice::init(MediaDevice *media, unsigned int index) output_.name = "output"; output_.pool = &outPool_; - viewfinder_.dev = V4L2Device::fromEntityName(media, - name_ + " viewfinder"); + viewfinder_.dev = V4L2VideoDevice::fromEntityName(media, + name_ + " viewfinder"); ret = viewfinder_.dev->open(); if (ret) return ret; @@ -990,7 +990,7 @@ int ImgUDevice::init(MediaDevice *media, unsigned int index) viewfinder_.name = "viewfinder"; viewfinder_.pool = &vfPool_; - stat_.dev = V4L2Device::fromEntityName(media, name_ + " 3a stat"); + stat_.dev = V4L2VideoDevice::fromEntityName(media, name_ + " 3a stat"); ret = stat_.dev->open(); if (ret) return ret; @@ -1067,7 +1067,7 @@ int ImgUDevice::configureInput(const Size &size, int ImgUDevice::configureOutput(ImgUOutput *output, const StreamConfiguration &cfg) { - V4L2Device *dev = output->dev; + V4L2VideoDevice *dev = output->dev; unsigned int pad = output->pad; V4L2SubdeviceFormat imguFormat = {}; @@ -1337,7 +1337,7 @@ int CIO2Device::init(const MediaDevice *media, unsigned int index) return ret; std::string cio2Name = "ipu3-cio2 " + std::to_string(index); - output_ = V4L2Device::fromEntityName(media, cio2Name); + output_ = V4L2VideoDevice::fromEntityName(media, cio2Name); ret = output_->open(); if (ret) return ret; diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp index 9b3eea2f..4a5898d2 100644 --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp @@ -23,8 +23,8 @@ #include "media_device.h" #include "pipeline_handler.h" #include "utils.h" -#include "v4l2_device.h" #include "v4l2_subdevice.h" +#include "v4l2_videodevice.h" namespace libcamera { @@ -106,7 +106,7 @@ private: MediaDevice *media_; V4L2Subdevice *dphy_; V4L2Subdevice *isp_; - V4L2Device *video_; + V4L2VideoDevice *video_; Camera *activeCamera_; }; @@ -458,7 +458,7 @@ bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator) return false; /* Locate and open the capture video node. */ - video_ = V4L2Device::fromEntityName(media_, "rkisp1_mainpath"); + video_ = V4L2VideoDevice::fromEntityName(media_, "rkisp1_mainpath"); if (video_->open() < 0) return false; diff --git a/src/libcamera/pipeline/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo.cpp index 7ca9d0ee..2e22523d 100644 --- a/src/libcamera/pipeline/uvcvideo.cpp +++ b/src/libcamera/pipeline/uvcvideo.cpp @@ -16,7 +16,7 @@ #include "media_device.h" #include "pipeline_handler.h" #include "utils.h" -#include "v4l2_device.h" +#include "v4l2_videodevice.h" namespace libcamera { @@ -37,7 +37,7 @@ public: void bufferReady(Buffer *buffer); - V4L2Device *video_; + V4L2VideoDevice *video_; Stream stream_; }; @@ -250,7 +250,7 @@ bool PipelineHandlerUVC::match(DeviceEnumerator *enumerator) /* Locate and open the default video node. */ for (MediaEntity *entity : media->entities()) { if (entity->flags() & MEDIA_ENT_FL_DEFAULT) { - data->video_ = new V4L2Device(entity); + data->video_ = new V4L2VideoDevice(entity); break; } } diff --git a/src/libcamera/pipeline/vimc.cpp b/src/libcamera/pipeline/vimc.cpp index ad4577ac..68332136 100644 --- a/src/libcamera/pipeline/vimc.cpp +++ b/src/libcamera/pipeline/vimc.cpp @@ -20,7 +20,7 @@ #include "media_device.h" #include "pipeline_handler.h" #include "utils.h" -#include "v4l2_device.h" +#include "v4l2_videodevice.h" namespace libcamera { @@ -41,7 +41,7 @@ public: void bufferReady(Buffer *buffer); - V4L2Device *video_; + V4L2VideoDevice *video_; Stream stream_; }; @@ -262,7 +262,7 @@ bool PipelineHandlerVimc::match(DeviceEnumerator *enumerator) std::unique_ptr data = utils::make_unique(this); /* Locate and open the capture video node. */ - data->video_ = new V4L2Device(media->getEntityByName("Raw Capture 1")); + data->video_ = new V4L2VideoDevice(media->getEntityByName("Raw Capture 1")); if (data->video_->open()) return false; diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp deleted file mode 100644 index e14fc610..00000000 --- a/src/libcamera/v4l2_device.cpp +++ /dev/null @@ -1,1126 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Copyright (C) 2019, Google Inc. - * - * v4l2_device.cpp - V4L2 Device - */ - -#include "v4l2_device.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "log.h" -#include "media_device.h" -#include "media_object.h" - -/** - * \file v4l2_device.h - * \brief V4L2 Device API - */ -namespace libcamera { - -LOG_DEFINE_CATEGORY(V4L2) - -/** - * \struct V4L2Capability - * \brief struct v4l2_capability object wrapper and helpers - * - * The V4L2Capability structure manages the information returned by the - * VIDIOC_QUERYCAP ioctl. - */ - -/** - * \fn V4L2Capability::driver() - * \brief Retrieve the driver module name - * \return The string containing the name of the driver module - */ - -/** - * \fn V4L2Capability::card() - * \brief Retrieve the device card name - * \return The string containing the device name - */ - -/** - * \fn V4L2Capability::bus_info() - * \brief Retrieve the location of the device in the system - * \return The string containing the device location - */ - -/** - * \fn V4L2Capability::device_caps() - * \brief Retrieve the capabilities of the device - * \return The device specific capabilities if V4L2_CAP_DEVICE_CAPS is set or - * driver capabilities otherwise - */ - -/** - * \fn V4L2Capability::isMultiplanar() - * \brief Identify if the device implements the V4L2 multiplanar APIs - * \return True if the device supports multiplanar APIs - */ - -/** - * \fn V4L2Capability::isCapture() - * \brief Identify if the device captures data - * \return True if the device can capture data - */ - -/** - * \fn V4L2Capability::isOutput() - * \brief Identify if the device outputs data - * \return True if the device can output data - */ - -/** - * \fn V4L2Capability::isVideo() - * \brief Identify if the device captures or outputs images - * \return True if the device can capture or output images - */ - -/** - * \fn V4L2Capability::isMeta() - * \brief Identify if the device captures or outputs image meta-data - * - * \todo Add support for META_CAPTURE introduced in Linux v5.0 - * - * \return True if the device can capture or output image meta-data - */ - -/** - * \fn V4L2Capability::isVideoCapture() - * \brief Identify if the device captures images - * \return True if the device can capture images - */ - -/** - * \fn V4L2Capability::isVideoOutput() - * \brief Identify if the device outputs images - * \return True if the device can output images - */ - -/** - * \fn V4L2Capability::isMetaCapture() - * \brief Identify if the device captures image meta-data - * \return True if the device can capture image meta-data - */ - -/** - * \fn V4L2Capability::isMetaOutput() - * \brief Identify if the device outputs image meta-data - * \return True if the device can output image meta-data - */ - -/** - * \fn V4L2Capability::hasStreaming() - * \brief Determine if the device can perform Streaming I/O - * \return True if the device provides Streaming I/O IOCTLs - */ - -/** - * \class V4L2DeviceFormat - * \brief The V4L2 device image format and sizes - * - * This class describes the image format and resolution to be programmed on a - * V4L2 video device. The image format is defined by a fourcc code (as specified - * by the V4L2 API with the V4L2_PIX_FMT_* macros), a resolution (width and - * height) and one to three planes with configurable line stride and a total - * per-plane size in bytes. - * - * Image formats, as defined by the V4L2 APIs, are categorised as packed, - * semi-planar and planar, and describe the layout of the image pixel components - * stored in memory. - * - * Packed image formats store pixel components one after the other, in a - * contiguous memory area. Examples of packed image formats are YUYV - * permutations, RGB with different pixel sub-sampling ratios such as RGB565 or - * RGB666 or Raw-Bayer formats such as SRGGB8 or SGRBG12. - * - * Semi-planar and planar image formats store the pixel components in separate - * and possibly non-contiguous memory areas, named planes, whose sizes depend on - * the pixel components sub-sampling ratios, which are defined by the format. - * Semi-planar formats use two planes to store pixel components and notable - * examples of such formats are the NV12 and NV16 formats, while planar formats - * use three planes to store pixel components and notable examples are YUV422 - * and YUV420. - * - * Image formats supported by the V4L2 API are defined and described in Section - * number 2 of the "Part I - Video for Linux API" chapter of the "Linux Media - * Infrastructure userspace API", part of the Linux kernel documentation. - * - * In the context of this document, packed image formats are referred to as - * "packed formats" and semi-planar and planar image formats are referred to as - * "planar formats". - * - * V4L2 also defines two different sets of APIs to work with devices that store - * planes in contiguous or separate memory areas. They are named "Single-plane - * APIs" and "Multi-plane APIs" respectively and are documented in Section 2.1 - * and Section 2.2 of the above mentioned "Part I - Video for Linux API" - * documentation. - * - * The single-plane API allows, among other parameters, the configuration of the - * image resolution, the pixel format and the stride length. In that case the - * stride applies to all planes (possibly sub-sampled). The multi-plane API - * allows configuring the resolution, the pixel format and a per-plane stride - * length and total size. - * - * Packed image formats, which occupy a single memory area, are easily described - * through the single-plane API. When used on a device that implements the - * multi-plane API, only the size and stride information contained in the first - * plane are taken into account. - * - * Planar image formats, which occupy distinct memory areas, are easily - * described through the multi-plane APIs. When used on a device that implements - * the single-plane API, all planes are stored one after the other in a - * contiguous memory area, and it is not possible to configure per-plane stride - * length and size, but only a global stride length which is applied to all - * planes. - * - * The V4L2DeviceFormat class describes both packed and planar image formats, - * regardless of the API type (single or multi plane) implemented by the device - * the format has to be applied to. The total size and bytes per line of images - * represented with packed formats are configured using the first entry of the - * V4L2DeviceFormat::planes array, while the per-plane size and per-plane stride - * length of images represented with planar image formats are configured using - * the opportune number of entries of the V4L2DeviceFormat::planes array, as - * prescribed by the image format definition (semi-planar formats use 2 entries, - * while planar formats use the whole 3 entries). The number of valid entries of - * the V4L2DeviceFormat::planes array is defined by the - * V4L2DeviceFormat::planesCount value. - */ - -/** - * \var V4L2DeviceFormat::size - * \brief The image size in pixels - */ - -/** - * \var V4L2DeviceFormat::fourcc - * \brief The fourcc code describing the pixel encoding scheme - * - * The fourcc code, as defined by the V4L2 API with the V4L2_PIX_FMT_* macros, - * that identifies the image format pixel encoding scheme. - */ - -/** - * \var V4L2DeviceFormat::planes - * \brief The per-plane memory size information - * - * Images are stored in memory in one or more data planes. Each data plane has a - * specific line stride and memory size, which could differ from the image - * visible sizes to accommodate padding at the end of lines and end of planes. - * Only the first \ref planesCount entries are considered valid. - */ - -/** - * \var V4L2DeviceFormat::planesCount - * \brief The number of valid data planes - */ - -/** - * \brief Assemble and return a string describing the format - * \return A string describing the V4L2DeviceFormat - */ -const std::string V4L2DeviceFormat::toString() const -{ - std::stringstream ss; - - ss.fill(0); - ss << size.toString() << "-0x" << std::hex << std::setw(8) << fourcc; - - return ss.str(); -} - -/** - * \class V4L2Device - * \brief V4L2Device object and API - * - * The V4L2 Device API class models an instance of a V4L2 device node. - * It is constructed with the path to a V4L2 video device node. The device node - * is only opened upon a call to open() which must be checked for success. - * - * The device capabilities are validated when the device is opened and the - * device is rejected if it is not a suitable V4L2 capture or output device, or - * if the device does not support streaming I/O. - * - * No API call other than open(), isOpen() and close() shall be called on an - * unopened device instance. - * - * The V4L2Device class tracks queued buffers and handles buffer events. It - * automatically dequeues completed buffers and emits the \ref bufferReady - * signal. - * - * Upon destruction any device left open will be closed, and any resources - * released. - */ - -/** - * \brief Construct a V4L2Device - * \param[in] deviceNode The file-system path to the video device node - */ -V4L2Device::V4L2Device(const std::string &deviceNode) - : deviceNode_(deviceNode), fd_(-1), bufferPool_(nullptr), - queuedBuffersCount_(0), fdEvent_(nullptr) -{ - /* - * We default to an MMAP based CAPTURE device, however this will be - * updated based upon the device capabilities. - */ - bufferType_ = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; - memoryType_ = V4L2_MEMORY_MMAP; -} - -/** - * \brief Construct a V4L2Device from a MediaEntity - * \param[in] entity The MediaEntity to build the device from - * - * Construct a V4L2Device from a MediaEntity's device node path. - */ -V4L2Device::V4L2Device(const MediaEntity *entity) - : V4L2Device(entity->deviceNode()) -{ -} - -V4L2Device::~V4L2Device() -{ - close(); -} - -/** - * \brief Open a V4L2 device and query its capabilities - * \return 0 on success or a negative error code otherwise - */ -int V4L2Device::open() -{ - int ret; - - if (isOpen()) { - LOG(V4L2, Error) << "Device already open"; - return -EBUSY; - } - - ret = ::open(deviceNode_.c_str(), O_RDWR | O_NONBLOCK); - if (ret < 0) { - ret = -errno; - LOG(V4L2, Error) - << "Failed to open V4L2 device: " << strerror(-ret); - return ret; - } - fd_ = ret; - - ret = ioctl(fd_, VIDIOC_QUERYCAP, &caps_); - if (ret < 0) { - ret = -errno; - LOG(V4L2, Error) - << "Failed to query device capabilities: " - << strerror(-ret); - return ret; - } - - LOG(V4L2, Debug) - << "Opened device " << caps_.bus_info() << ": " - << caps_.driver() << ": " << caps_.card(); - - if (!caps_.hasStreaming()) { - LOG(V4L2, Error) << "Device does not support streaming I/O"; - return -EINVAL; - } - - /* - * Set buffer type and wait for read notifications on CAPTURE devices - * (POLLIN), and write notifications for OUTPUT devices (POLLOUT). - */ - if (caps_.isVideoCapture()) { - fdEvent_ = new EventNotifier(fd_, EventNotifier::Read); - bufferType_ = caps_.isMultiplanar() - ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE - : V4L2_BUF_TYPE_VIDEO_CAPTURE; - } else if (caps_.isVideoOutput()) { - fdEvent_ = new EventNotifier(fd_, EventNotifier::Write); - bufferType_ = caps_.isMultiplanar() - ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE - : V4L2_BUF_TYPE_VIDEO_OUTPUT; - } else if (caps_.isMetaCapture()) { - fdEvent_ = new EventNotifier(fd_, EventNotifier::Read); - bufferType_ = V4L2_BUF_TYPE_META_CAPTURE; - } else if (caps_.isMetaOutput()) { - fdEvent_ = new EventNotifier(fd_, EventNotifier::Write); - bufferType_ = V4L2_BUF_TYPE_META_OUTPUT; - } else { - LOG(V4L2, Error) << "Device is not a supported type"; - return -EINVAL; - } - - fdEvent_->activated.connect(this, &V4L2Device::bufferAvailable); - fdEvent_->setEnabled(false); - - return 0; -} - -/** - * \brief Check if device is successfully opened - * \return True if the device is open, false otherwise - */ -bool V4L2Device::isOpen() const -{ - return fd_ != -1; -} - -/** - * \brief Close the device, releasing any resources acquired by open() - */ -void V4L2Device::close() -{ - if (fd_ < 0) - return; - - releaseBuffers(); - delete fdEvent_; - - ::close(fd_); - fd_ = -1; -} - -/** - * \fn V4L2Device::driverName() - * \brief Retrieve the name of the V4L2 device driver - * \return The string containing the driver name - */ - -/** - * \fn V4L2Device::deviceName() - * \brief Retrieve the name of the V4L2 device - * \return The string containing the device name - */ - -/** - * \fn V4L2Device::busName() - * \brief Retrieve the location of the device in the system - * \return The string containing the device location - */ - -/** - * \fn V4L2Device::deviceNode() - * \brief Retrieve the video device node path - * \return The video device device node path - */ - -std::string V4L2Device::logPrefix() const -{ - return deviceNode_ + (V4L2_TYPE_IS_OUTPUT(bufferType_) ? "[out]" : "[cap]"); -} - -/** - * \brief Retrieve the image format set on the V4L2 device - * \param[out] format The image format applied on the device - * \return 0 on success or a negative error code otherwise - */ -int V4L2Device::getFormat(V4L2DeviceFormat *format) -{ - if (caps_.isMeta()) - return getFormatMeta(format); - else if (caps_.isMultiplanar()) - return getFormatMultiplane(format); - else - return getFormatSingleplane(format); -} - -/** - * \brief Configure an image format on the V4L2 device - * \param[inout] format The image format to apply to the device - * - * Apply the supplied \a format to the device, and return the actually - * applied format parameters, as \ref V4L2Device::getFormat would do. - * - * \return 0 on success or a negative error code otherwise - */ -int V4L2Device::setFormat(V4L2DeviceFormat *format) -{ - if (caps_.isMeta()) - return setFormatMeta(format); - else if (caps_.isMultiplanar()) - return setFormatMultiplane(format); - else - return setFormatSingleplane(format); -} - -int V4L2Device::getFormatMeta(V4L2DeviceFormat *format) -{ - struct v4l2_format v4l2Format = {}; - struct v4l2_meta_format *pix = &v4l2Format.fmt.meta; - int ret; - - v4l2Format.type = bufferType_; - ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format); - if (ret) { - ret = -errno; - LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret); - return ret; - } - - format->size.width = 0; - format->size.height = 0; - format->fourcc = pix->dataformat; - format->planesCount = 1; - format->planes[0].bpl = pix->buffersize; - format->planes[0].size = pix->buffersize; - - return 0; -} - -int V4L2Device::setFormatMeta(V4L2DeviceFormat *format) -{ - struct v4l2_format v4l2Format = {}; - struct v4l2_meta_format *pix = &v4l2Format.fmt.meta; - int ret; - - v4l2Format.type = bufferType_; - pix->dataformat = format->fourcc; - pix->buffersize = format->planes[0].size; - ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format); - if (ret) { - ret = -errno; - LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret); - return ret; - } - - /* - * Return to caller the format actually applied on the device, - * which might differ from the requested one. - */ - format->size.width = 0; - format->size.height = 0; - format->fourcc = format->fourcc; - format->planesCount = 1; - format->planes[0].bpl = pix->buffersize; - format->planes[0].size = pix->buffersize; - - return 0; -} - -int V4L2Device::getFormatMultiplane(V4L2DeviceFormat *format) -{ - struct v4l2_format v4l2Format = {}; - struct v4l2_pix_format_mplane *pix = &v4l2Format.fmt.pix_mp; - int ret; - - v4l2Format.type = bufferType_; - ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format); - if (ret) { - ret = -errno; - LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret); - return ret; - } - - format->size.width = pix->width; - format->size.height = pix->height; - format->fourcc = pix->pixelformat; - format->planesCount = pix->num_planes; - - for (unsigned int i = 0; i < format->planesCount; ++i) { - format->planes[i].bpl = pix->plane_fmt[i].bytesperline; - format->planes[i].size = pix->plane_fmt[i].sizeimage; - } - - return 0; -} - -int V4L2Device::setFormatMultiplane(V4L2DeviceFormat *format) -{ - struct v4l2_format v4l2Format = {}; - struct v4l2_pix_format_mplane *pix = &v4l2Format.fmt.pix_mp; - int ret; - - v4l2Format.type = bufferType_; - pix->width = format->size.width; - pix->height = format->size.height; - pix->pixelformat = format->fourcc; - pix->num_planes = format->planesCount; - pix->field = V4L2_FIELD_NONE; - - for (unsigned int i = 0; i < pix->num_planes; ++i) { - pix->plane_fmt[i].bytesperline = format->planes[i].bpl; - pix->plane_fmt[i].sizeimage = format->planes[i].size; - } - - ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format); - if (ret) { - ret = -errno; - LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret); - return ret; - } - - /* - * Return to caller the format actually applied on the device, - * which might differ from the requested one. - */ - format->size.width = pix->width; - format->size.height = pix->height; - format->fourcc = pix->pixelformat; - format->planesCount = pix->num_planes; - for (unsigned int i = 0; i < format->planesCount; ++i) { - format->planes[i].bpl = pix->plane_fmt[i].bytesperline; - format->planes[i].size = pix->plane_fmt[i].sizeimage; - } - - return 0; -} - -int V4L2Device::getFormatSingleplane(V4L2DeviceFormat *format) -{ - struct v4l2_format v4l2Format = {}; - struct v4l2_pix_format *pix = &v4l2Format.fmt.pix; - int ret; - - v4l2Format.type = bufferType_; - ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format); - if (ret) { - ret = -errno; - LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret); - return ret; - } - - format->size.width = pix->width; - format->size.height = pix->height; - format->fourcc = pix->pixelformat; - format->planesCount = 1; - format->planes[0].bpl = pix->bytesperline; - format->planes[0].size = pix->sizeimage; - - return 0; -} - -int V4L2Device::setFormatSingleplane(V4L2DeviceFormat *format) -{ - struct v4l2_format v4l2Format = {}; - struct v4l2_pix_format *pix = &v4l2Format.fmt.pix; - int ret; - - v4l2Format.type = bufferType_; - pix->width = format->size.width; - pix->height = format->size.height; - pix->pixelformat = format->fourcc; - pix->bytesperline = format->planes[0].bpl; - pix->field = V4L2_FIELD_NONE; - ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format); - if (ret) { - ret = -errno; - LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret); - return ret; - } - - /* - * Return to caller the format actually applied on the device, - * which might differ from the requested one. - */ - format->size.width = pix->width; - format->size.height = pix->height; - format->fourcc = pix->pixelformat; - format->planesCount = 1; - format->planes[0].bpl = pix->bytesperline; - format->planes[0].size = pix->sizeimage; - - return 0; -} - -/** - * \brief Enumerate all pixel formats and frame sizes - * - * Enumerate all pixel formats and frame sizes supported by the video device. - * - * \return A list of the supported device formats - */ -ImageFormats V4L2Device::formats() -{ - ImageFormats formats; - - for (unsigned int pixelformat : enumPixelformats()) { - std::vector sizes = enumSizes(pixelformat); - if (sizes.empty()) - return {}; - - if (formats.addFormat(pixelformat, sizes)) { - LOG(V4L2, Error) - << "Could not add sizes for pixel format " - << pixelformat; - return {}; - } - } - - return formats; -} - -int V4L2Device::requestBuffers(unsigned int count) -{ - struct v4l2_requestbuffers rb = {}; - int ret; - - rb.count = count; - rb.type = bufferType_; - rb.memory = memoryType_; - - ret = ioctl(fd_, VIDIOC_REQBUFS, &rb); - if (ret < 0) { - ret = -errno; - LOG(V4L2, Error) - << "Unable to request " << count << " buffers: " - << strerror(-ret); - return ret; - } - - LOG(V4L2, Debug) << rb.count << " buffers requested."; - - return rb.count; -} - -/** - * \brief Request buffers to be allocated from the device and stored in the - * buffer pool provided. - * \param[out] pool BufferPool to populate with buffers - * \return 0 on success or a negative error code otherwise - */ -int V4L2Device::exportBuffers(BufferPool *pool) -{ - unsigned int allocatedBuffers; - unsigned int i; - int ret; - - memoryType_ = V4L2_MEMORY_MMAP; - - ret = requestBuffers(pool->count()); - if (ret < 0) - return ret; - - allocatedBuffers = ret; - if (allocatedBuffers < pool->count()) { - LOG(V4L2, Error) << "Not enough buffers provided by V4L2Device"; - requestBuffers(0); - return -ENOMEM; - } - - /* Map the buffers. */ - for (i = 0; i < pool->count(); ++i) { - struct v4l2_plane planes[VIDEO_MAX_PLANES] = {}; - struct v4l2_buffer buf = {}; - Buffer &buffer = pool->buffers()[i]; - - buf.index = i; - buf.type = bufferType_; - buf.memory = memoryType_; - buf.length = VIDEO_MAX_PLANES; - buf.m.planes = planes; - - ret = ioctl(fd_, VIDIOC_QUERYBUF, &buf); - if (ret < 0) { - ret = -errno; - LOG(V4L2, Error) - << "Unable to query buffer " << i << ": " - << strerror(-ret); - break; - } - - if (V4L2_TYPE_IS_MULTIPLANAR(buf.type)) { - for (unsigned int p = 0; p < buf.length; ++p) { - ret = createPlane(&buffer, p, - buf.m.planes[p].length); - if (ret) - break; - } - } else { - ret = createPlane(&buffer, 0, buf.length); - } - - if (ret) { - LOG(V4L2, Error) << "Failed to create plane"; - break; - } - } - - if (ret) { - requestBuffers(0); - pool->destroyBuffers(); - return ret; - } - - bufferPool_ = pool; - - return 0; -} - -int V4L2Device::createPlane(Buffer *buffer, unsigned int planeIndex, - unsigned int length) -{ - struct v4l2_exportbuffer expbuf = {}; - int ret; - - LOG(V4L2, Debug) - << "Buffer " << buffer->index() - << " plane " << planeIndex - << ": length=" << length; - - expbuf.type = bufferType_; - expbuf.index = buffer->index(); - expbuf.plane = planeIndex; - expbuf.flags = O_RDWR; - - ret = ioctl(fd_, VIDIOC_EXPBUF, &expbuf); - if (ret < 0) { - ret = -errno; - LOG(V4L2, Error) - << "Failed to export buffer: " << strerror(-ret); - return ret; - } - - buffer->planes().emplace_back(); - Plane &plane = buffer->planes().back(); - plane.setDmabuf(expbuf.fd, length); - ::close(expbuf.fd); - - return 0; -} - -std::vector V4L2Device::enumPixelformats() -{ - std::vector formats; - int ret; - - for (unsigned int index = 0; ; index++) { - struct v4l2_fmtdesc pixelformatEnum = {}; - pixelformatEnum.index = index; - pixelformatEnum.type = bufferType_; - - ret = ioctl(fd_, VIDIOC_ENUM_FMT, &pixelformatEnum); - if (ret) - break; - - formats.push_back(pixelformatEnum.pixelformat); - } - - if (ret && errno != EINVAL) { - ret = -errno; - LOG(V4L2, Error) - << "Unable to enumerate pixel formats: " - << strerror(-ret); - return {}; - } - - return formats; -} - -std::vector V4L2Device::enumSizes(unsigned int pixelFormat) -{ - std::vector sizes; - int ret; - - for (unsigned int index = 0;; index++) { - struct v4l2_frmsizeenum frameSize = {}; - frameSize.index = index; - frameSize.pixel_format = pixelFormat; - - ret = ioctl(fd_, VIDIOC_ENUM_FRAMESIZES, &frameSize); - if (ret) - break; - - if (index != 0 && - frameSize.type != V4L2_FRMSIZE_TYPE_DISCRETE) { - LOG(V4L2, Error) - << "Non-zero index for non discrete type"; - return {}; - } - - switch (frameSize.type) { - case V4L2_FRMSIZE_TYPE_DISCRETE: - sizes.emplace_back(frameSize.discrete.width, - frameSize.discrete.height); - break; - case V4L2_FRMSIZE_TYPE_CONTINUOUS: - sizes.emplace_back(frameSize.stepwise.min_width, - frameSize.stepwise.min_height, - frameSize.stepwise.max_width, - frameSize.stepwise.max_height); - break; - case V4L2_FRMSIZE_TYPE_STEPWISE: - sizes.emplace_back(frameSize.stepwise.min_width, - frameSize.stepwise.min_height, - frameSize.stepwise.max_width, - frameSize.stepwise.max_height, - frameSize.stepwise.step_width, - frameSize.stepwise.step_height); - break; - default: - LOG(V4L2, Error) - << "Unknown VIDIOC_ENUM_FRAMESIZES type " - << frameSize.type; - return {}; - } - } - - if (ret && errno != EINVAL) { - ret = -errno; - LOG(V4L2, Error) - << "Unable to enumerate frame sizes: " - << strerror(-ret); - return {}; - } - - return sizes; -} - -/** - * \brief Import the externally allocated \a pool of buffers - * \param[in] pool BufferPool of buffers to import - * \return 0 on success or a negative error code otherwise - */ -int V4L2Device::importBuffers(BufferPool *pool) -{ - unsigned int allocatedBuffers; - int ret; - - memoryType_ = V4L2_MEMORY_DMABUF; - - ret = requestBuffers(pool->count()); - if (ret < 0) - return ret; - - allocatedBuffers = ret; - if (allocatedBuffers < pool->count()) { - LOG(V4L2, Error) - << "Not enough buffers provided by V4L2Device"; - requestBuffers(0); - return -ENOMEM; - } - - LOG(V4L2, Debug) << "provided pool of " << pool->count() << " buffers"; - bufferPool_ = pool; - - return 0; -} - -/** - * \brief Release all internally allocated buffers - */ -int V4L2Device::releaseBuffers() -{ - LOG(V4L2, Debug) << "Releasing bufferPool"; - - bufferPool_ = nullptr; - - return requestBuffers(0); -} - -/** - * \brief Queue a buffer into the device - * \param[in] buffer The buffer to be queued - * - * For capture devices the \a buffer will be filled with data by the device. - * For output devices the \a buffer shall contain valid data and will be - * processed by the device. Once the device has finished processing the buffer, - * it will be available for dequeue. - * - * \return 0 on success or a negative error code otherwise - */ -int V4L2Device::queueBuffer(Buffer *buffer) -{ - struct v4l2_buffer buf = {}; - struct v4l2_plane planes[VIDEO_MAX_PLANES] = {}; - int ret; - - buf.index = buffer->index(); - buf.type = bufferType_; - buf.memory = memoryType_; - buf.field = V4L2_FIELD_NONE; - - bool multiPlanar = V4L2_TYPE_IS_MULTIPLANAR(buf.type); - - if (buf.memory == V4L2_MEMORY_DMABUF) { - if (multiPlanar) { - for (unsigned int p = 0; - p < buffer->planes().size(); - p++) - planes[p].m.fd = buffer->planes()[p].dmabuf(); - } else { - buf.m.fd = buffer->planes()[0].dmabuf(); - } - } - - if (multiPlanar) { - buf.length = buffer->planes().size(); - buf.m.planes = planes; - } - - if (V4L2_TYPE_IS_OUTPUT(bufferType_)) { - buf.bytesused = buffer->bytesused_; - buf.sequence = buffer->sequence_; - buf.timestamp.tv_sec = buffer->timestamp_ / 1000000000; - buf.timestamp.tv_usec = (buffer->timestamp_ / 1000) % 1000000; - } - - LOG(V4L2, Debug) << "Queueing buffer " << buf.index; - - ret = ioctl(fd_, VIDIOC_QBUF, &buf); - if (ret < 0) { - ret = -errno; - LOG(V4L2, Error) - << "Failed to queue buffer " << buf.index << ": " - << strerror(-ret); - return ret; - } - - if (queuedBuffersCount_++ == 0) - fdEvent_->setEnabled(true); - - return 0; -} - -/** - * \brief Dequeue the next available buffer from the device - * - * This method dequeues the next available buffer from the device. If no buffer - * is available to be dequeued it will return nullptr immediately. - * - * \return A pointer to the dequeued buffer on success, or nullptr otherwise - */ -Buffer *V4L2Device::dequeueBuffer() -{ - struct v4l2_buffer buf = {}; - struct v4l2_plane planes[VIDEO_MAX_PLANES] = {}; - int ret; - - buf.type = bufferType_; - buf.memory = memoryType_; - - if (V4L2_TYPE_IS_MULTIPLANAR(buf.type)) { - buf.length = VIDEO_MAX_PLANES; - buf.m.planes = planes; - } - - ret = ioctl(fd_, VIDIOC_DQBUF, &buf); - if (ret < 0) { - ret = -errno; - LOG(V4L2, Error) - << "Failed to dequeue buffer: " << strerror(-ret); - return nullptr; - } - - ASSERT(buf.index < bufferPool_->count()); - - if (--queuedBuffersCount_ == 0) - fdEvent_->setEnabled(false); - - Buffer *buffer = &bufferPool_->buffers()[buf.index]; - - buffer->bytesused_ = buf.bytesused; - buffer->timestamp_ = buf.timestamp.tv_sec * 1000000000ULL - + buf.timestamp.tv_usec * 1000ULL; - buffer->sequence_ = buf.sequence; - buffer->status_ = buf.flags & V4L2_BUF_FLAG_ERROR - ? Buffer::BufferError : Buffer::BufferSuccess; - - return buffer; -} - -/** - * \brief Slot to handle completed buffer events from the V4L2 device - * \param[in] notifier The event notifier - * - * When this slot is called, a Buffer has become available from the device, and - * will be emitted through the bufferReady Signal. - * - * For Capture devices the Buffer will contain valid data. - * For Output devices the Buffer can be considered empty. - */ -void V4L2Device::bufferAvailable(EventNotifier *notifier) -{ - Buffer *buffer = dequeueBuffer(); - if (!buffer) - return; - - LOG(V4L2, Debug) << "Buffer " << buffer->index() << " is available"; - - /* Notify anyone listening to the device. */ - bufferReady.emit(buffer); -} - -/** - * \var V4L2Device::bufferReady - * \brief A Signal emitted when a buffer completes - */ - -/** - * \brief Start the video stream - * \return 0 on success or a negative error code otherwise - */ -int V4L2Device::streamOn() -{ - int ret; - - ret = ioctl(fd_, VIDIOC_STREAMON, &bufferType_); - if (ret < 0) { - ret = -errno; - LOG(V4L2, Error) - << "Failed to start streaming: " << strerror(-ret); - return ret; - } - - return 0; -} - -/** - * \brief Stop the video stream - * - * Buffers that are still queued when the video stream is stopped are - * implicitly dequeued, but no bufferReady signal is emitted for them. - * - * \return 0 on success or a negative error code otherwise - */ -int V4L2Device::streamOff() -{ - int ret; - - ret = ioctl(fd_, VIDIOC_STREAMOFF, &bufferType_); - if (ret < 0) { - ret = -errno; - LOG(V4L2, Error) - << "Failed to stop streaming: " << strerror(-ret); - return ret; - } - - queuedBuffersCount_ = 0; - fdEvent_->setEnabled(false); - - return 0; -} - -/** - * \brief Create a new video device instance from \a entity in media device - * \a media - * \param[in] media The media device where the entity is registered - * \param[in] entity The media entity name - * - * Releasing memory of the newly created instance is responsibility of the - * caller of this function. - * - * \return A newly created V4L2Device on success, nullptr otherwise - */ -V4L2Device *V4L2Device::fromEntityName(const MediaDevice *media, - const std::string &entity) -{ - MediaEntity *mediaEntity = media->getEntityByName(entity); - if (!mediaEntity) - return nullptr; - - return new V4L2Device(mediaEntity); -} - -} /* namespace libcamera */ diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp index c38f95a3..48f1fcb3 100644 --- a/src/libcamera/v4l2_subdevice.cpp +++ b/src/libcamera/v4l2_subdevice.cpp @@ -44,7 +44,7 @@ LOG_DEFINE_CATEGORY(V4L2Subdev) * as the "media bus format", and it is identified by a resolution and a pixel * format identification code, known as the "media bus code", not to be confused * with the fourcc code that identify the format of images when stored in memory - * (see V4L2Device::V4L2DeviceFormat). + * (see V4L2VideoDevice::V4L2DeviceFormat). * * Media Bus formats supported by the V4L2 APIs are described in Section * 4.15.3.4.1 of the "Part I - Video for Linux API" chapter of the "Linux Media diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp new file mode 100644 index 00000000..0e70498c --- /dev/null +++ b/src/libcamera/v4l2_videodevice.cpp @@ -0,0 +1,1127 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * v4l2_videodevice.cpp - V4L2 Video Device + */ + +#include "v4l2_videodevice.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "log.h" +#include "media_device.h" +#include "media_object.h" + +/** + * \file v4l2_videodevice.h + * \brief V4L2 Video Device API + */ +namespace libcamera { + +LOG_DEFINE_CATEGORY(V4L2) + +/** + * \struct V4L2Capability + * \brief struct v4l2_capability object wrapper and helpers + * + * The V4L2Capability structure manages the information returned by the + * VIDIOC_QUERYCAP ioctl. + */ + +/** + * \fn V4L2Capability::driver() + * \brief Retrieve the driver module name + * \return The string containing the name of the driver module + */ + +/** + * \fn V4L2Capability::card() + * \brief Retrieve the device card name + * \return The string containing the device name + */ + +/** + * \fn V4L2Capability::bus_info() + * \brief Retrieve the location of the device in the system + * \return The string containing the device location + */ + +/** + * \fn V4L2Capability::device_caps() + * \brief Retrieve the capabilities of the device + * \return The device specific capabilities if V4L2_CAP_DEVICE_CAPS is set or + * driver capabilities otherwise + */ + +/** + * \fn V4L2Capability::isMultiplanar() + * \brief Identify if the device implements the V4L2 multiplanar APIs + * \return True if the device supports multiplanar APIs + */ + +/** + * \fn V4L2Capability::isCapture() + * \brief Identify if the device captures data + * \return True if the device can capture data + */ + +/** + * \fn V4L2Capability::isOutput() + * \brief Identify if the device outputs data + * \return True if the device can output data + */ + +/** + * \fn V4L2Capability::isVideo() + * \brief Identify if the device captures or outputs images + * \return True if the device can capture or output images + */ + +/** + * \fn V4L2Capability::isMeta() + * \brief Identify if the device captures or outputs image meta-data + * + * \todo Add support for META_CAPTURE introduced in Linux v5.0 + * + * \return True if the device can capture or output image meta-data + */ + +/** + * \fn V4L2Capability::isVideoCapture() + * \brief Identify if the device captures images + * \return True if the device can capture images + */ + +/** + * \fn V4L2Capability::isVideoOutput() + * \brief Identify if the device outputs images + * \return True if the device can output images + */ + +/** + * \fn V4L2Capability::isMetaCapture() + * \brief Identify if the device captures image meta-data + * \return True if the device can capture image meta-data + */ + +/** + * \fn V4L2Capability::isMetaOutput() + * \brief Identify if the device outputs image meta-data + * \return True if the device can output image meta-data + */ + +/** + * \fn V4L2Capability::hasStreaming() + * \brief Determine if the device can perform Streaming I/O + * \return True if the device provides Streaming I/O IOCTLs + */ + +/** + * \class V4L2DeviceFormat + * \brief The V4L2 device image format and sizes + * + * This class describes the image format and resolution to be programmed on a + * V4L2 video device. The image format is defined by a fourcc code (as specified + * by the V4L2 API with the V4L2_PIX_FMT_* macros), a resolution (width and + * height) and one to three planes with configurable line stride and a total + * per-plane size in bytes. + * + * Image formats, as defined by the V4L2 APIs, are categorised as packed, + * semi-planar and planar, and describe the layout of the image pixel components + * stored in memory. + * + * Packed image formats store pixel components one after the other, in a + * contiguous memory area. Examples of packed image formats are YUYV + * permutations, RGB with different pixel sub-sampling ratios such as RGB565 or + * RGB666 or Raw-Bayer formats such as SRGGB8 or SGRBG12. + * + * Semi-planar and planar image formats store the pixel components in separate + * and possibly non-contiguous memory areas, named planes, whose sizes depend on + * the pixel components sub-sampling ratios, which are defined by the format. + * Semi-planar formats use two planes to store pixel components and notable + * examples of such formats are the NV12 and NV16 formats, while planar formats + * use three planes to store pixel components and notable examples are YUV422 + * and YUV420. + * + * Image formats supported by the V4L2 API are defined and described in Section + * number 2 of the "Part I - Video for Linux API" chapter of the "Linux Media + * Infrastructure userspace API", part of the Linux kernel documentation. + * + * In the context of this document, packed image formats are referred to as + * "packed formats" and semi-planar and planar image formats are referred to as + * "planar formats". + * + * V4L2 also defines two different sets of APIs to work with devices that store + * planes in contiguous or separate memory areas. They are named "Single-plane + * APIs" and "Multi-plane APIs" respectively and are documented in Section 2.1 + * and Section 2.2 of the above mentioned "Part I - Video for Linux API" + * documentation. + * + * The single-plane API allows, among other parameters, the configuration of the + * image resolution, the pixel format and the stride length. In that case the + * stride applies to all planes (possibly sub-sampled). The multi-plane API + * allows configuring the resolution, the pixel format and a per-plane stride + * length and total size. + * + * Packed image formats, which occupy a single memory area, are easily described + * through the single-plane API. When used on a device that implements the + * multi-plane API, only the size and stride information contained in the first + * plane are taken into account. + * + * Planar image formats, which occupy distinct memory areas, are easily + * described through the multi-plane APIs. When used on a device that implements + * the single-plane API, all planes are stored one after the other in a + * contiguous memory area, and it is not possible to configure per-plane stride + * length and size, but only a global stride length which is applied to all + * planes. + * + * The V4L2DeviceFormat class describes both packed and planar image formats, + * regardless of the API type (single or multi plane) implemented by the device + * the format has to be applied to. The total size and bytes per line of images + * represented with packed formats are configured using the first entry of the + * V4L2DeviceFormat::planes array, while the per-plane size and per-plane stride + * length of images represented with planar image formats are configured using + * the opportune number of entries of the V4L2DeviceFormat::planes array, as + * prescribed by the image format definition (semi-planar formats use 2 entries, + * while planar formats use the whole 3 entries). The number of valid entries of + * the V4L2DeviceFormat::planes array is defined by the + * V4L2DeviceFormat::planesCount value. + */ + +/** + * \var V4L2DeviceFormat::size + * \brief The image size in pixels + */ + +/** + * \var V4L2DeviceFormat::fourcc + * \brief The fourcc code describing the pixel encoding scheme + * + * The fourcc code, as defined by the V4L2 API with the V4L2_PIX_FMT_* macros, + * that identifies the image format pixel encoding scheme. + */ + +/** + * \var V4L2DeviceFormat::planes + * \brief The per-plane memory size information + * + * Images are stored in memory in one or more data planes. Each data plane has a + * specific line stride and memory size, which could differ from the image + * visible sizes to accommodate padding at the end of lines and end of planes. + * Only the first \ref planesCount entries are considered valid. + */ + +/** + * \var V4L2DeviceFormat::planesCount + * \brief The number of valid data planes + */ + +/** + * \brief Assemble and return a string describing the format + * \return A string describing the V4L2DeviceFormat + */ +const std::string V4L2DeviceFormat::toString() const +{ + std::stringstream ss; + + ss.fill(0); + ss << size.toString() << "-0x" << std::hex << std::setw(8) << fourcc; + + return ss.str(); +} + +/** + * \class V4L2VideoDevice + * \brief V4L2VideoDevice object and API + * + * The V4L2 Device API class models an instance of a V4L2 device node. + * It is constructed with the path to a V4L2 video device node. The device node + * is only opened upon a call to open() which must be checked for success. + * + * The device capabilities are validated when the device is opened and the + * device is rejected if it is not a suitable V4L2 capture or output device, or + * if the device does not support streaming I/O. + * + * No API call other than open(), isOpen() and close() shall be called on an + * unopened device instance. + * + * The V4L2VideoDevice class tracks queued buffers and handles buffer events. It + * automatically dequeues completed buffers and emits the \ref bufferReady + * signal. + * + * Upon destruction any device left open will be closed, and any resources + * released. + */ + +/** + * \brief Construct a V4L2VideoDevice + * \param[in] deviceNode The file-system path to the video device node + */ +V4L2VideoDevice::V4L2VideoDevice(const std::string &deviceNode) + : deviceNode_(deviceNode), fd_(-1), bufferPool_(nullptr), + queuedBuffersCount_(0), fdEvent_(nullptr) +{ + /* + * We default to an MMAP based CAPTURE device, however this will be + * updated based upon the device capabilities. + */ + bufferType_ = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + memoryType_ = V4L2_MEMORY_MMAP; +} + +/** + * \brief Construct a V4L2VideoDevice from a MediaEntity + * \param[in] entity The MediaEntity to build the device from + * + * Construct a V4L2VideoDevice from a MediaEntity's device node path. + */ +V4L2VideoDevice::V4L2VideoDevice(const MediaEntity *entity) + : V4L2VideoDevice(entity->deviceNode()) +{ +} + +V4L2VideoDevice::~V4L2VideoDevice() +{ + close(); +} + +/** + * \brief Open a V4L2 device and query its capabilities + * \return 0 on success or a negative error code otherwise + */ +int V4L2VideoDevice::open() +{ + int ret; + + if (isOpen()) { + LOG(V4L2, Error) << "Device already open"; + return -EBUSY; + } + + ret = ::open(deviceNode_.c_str(), O_RDWR | O_NONBLOCK); + if (ret < 0) { + ret = -errno; + LOG(V4L2, Error) + << "Failed to open V4L2 device: " << strerror(-ret); + return ret; + } + fd_ = ret; + + ret = ioctl(fd_, VIDIOC_QUERYCAP, &caps_); + if (ret < 0) { + ret = -errno; + LOG(V4L2, Error) + << "Failed to query device capabilities: " + << strerror(-ret); + return ret; + } + + LOG(V4L2, Debug) + << "Opened device " << caps_.bus_info() << ": " + << caps_.driver() << ": " << caps_.card(); + + if (!caps_.hasStreaming()) { + LOG(V4L2, Error) << "Device does not support streaming I/O"; + return -EINVAL; + } + + /* + * Set buffer type and wait for read notifications on CAPTURE devices + * (POLLIN), and write notifications for OUTPUT devices (POLLOUT). + */ + if (caps_.isVideoCapture()) { + fdEvent_ = new EventNotifier(fd_, EventNotifier::Read); + bufferType_ = caps_.isMultiplanar() + ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE + : V4L2_BUF_TYPE_VIDEO_CAPTURE; + } else if (caps_.isVideoOutput()) { + fdEvent_ = new EventNotifier(fd_, EventNotifier::Write); + bufferType_ = caps_.isMultiplanar() + ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE + : V4L2_BUF_TYPE_VIDEO_OUTPUT; + } else if (caps_.isMetaCapture()) { + fdEvent_ = new EventNotifier(fd_, EventNotifier::Read); + bufferType_ = V4L2_BUF_TYPE_META_CAPTURE; + } else if (caps_.isMetaOutput()) { + fdEvent_ = new EventNotifier(fd_, EventNotifier::Write); + bufferType_ = V4L2_BUF_TYPE_META_OUTPUT; + } else { + LOG(V4L2, Error) << "Device is not a supported type"; + return -EINVAL; + } + + fdEvent_->activated.connect(this, &V4L2VideoDevice::bufferAvailable); + fdEvent_->setEnabled(false); + + return 0; +} + +/** + * \brief Check if device is successfully opened + * \return True if the device is open, false otherwise + */ +bool V4L2VideoDevice::isOpen() const +{ + return fd_ != -1; +} + +/** + * \brief Close the device, releasing any resources acquired by open() + */ +void V4L2VideoDevice::close() +{ + if (fd_ < 0) + return; + + releaseBuffers(); + delete fdEvent_; + + ::close(fd_); + fd_ = -1; +} + +/** + * \fn V4L2VideoDevice::driverName() + * \brief Retrieve the name of the V4L2 device driver + * \return The string containing the driver name + */ + +/** + * \fn V4L2VideoDevice::deviceName() + * \brief Retrieve the name of the V4L2 device + * \return The string containing the device name + */ + +/** + * \fn V4L2VideoDevice::busName() + * \brief Retrieve the location of the device in the system + * \return The string containing the device location + */ + +/** + * \fn V4L2VideoDevice::deviceNode() + * \brief Retrieve the video device node path + * \return The video device device node path + */ + +std::string V4L2VideoDevice::logPrefix() const +{ + return deviceNode_ + (V4L2_TYPE_IS_OUTPUT(bufferType_) ? "[out]" : "[cap]"); +} + +/** + * \brief Retrieve the image format set on the V4L2 device + * \param[out] format The image format applied on the device + * \return 0 on success or a negative error code otherwise + */ +int V4L2VideoDevice::getFormat(V4L2DeviceFormat *format) +{ + if (caps_.isMeta()) + return getFormatMeta(format); + else if (caps_.isMultiplanar()) + return getFormatMultiplane(format); + else + return getFormatSingleplane(format); +} + +/** + * \brief Configure an image format on the V4L2 device + * \param[inout] format The image format to apply to the device + * + * Apply the supplied \a format to the device, and return the actually + * applied format parameters, as \ref V4L2VideoDevice::getFormat would do. + * + * \return 0 on success or a negative error code otherwise + */ +int V4L2VideoDevice::setFormat(V4L2DeviceFormat *format) +{ + if (caps_.isMeta()) + return setFormatMeta(format); + else if (caps_.isMultiplanar()) + return setFormatMultiplane(format); + else + return setFormatSingleplane(format); +} + +int V4L2VideoDevice::getFormatMeta(V4L2DeviceFormat *format) +{ + struct v4l2_format v4l2Format = {}; + struct v4l2_meta_format *pix = &v4l2Format.fmt.meta; + int ret; + + v4l2Format.type = bufferType_; + ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format); + if (ret) { + ret = -errno; + LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret); + return ret; + } + + format->size.width = 0; + format->size.height = 0; + format->fourcc = pix->dataformat; + format->planesCount = 1; + format->planes[0].bpl = pix->buffersize; + format->planes[0].size = pix->buffersize; + + return 0; +} + +int V4L2VideoDevice::setFormatMeta(V4L2DeviceFormat *format) +{ + struct v4l2_format v4l2Format = {}; + struct v4l2_meta_format *pix = &v4l2Format.fmt.meta; + int ret; + + v4l2Format.type = bufferType_; + pix->dataformat = format->fourcc; + pix->buffersize = format->planes[0].size; + ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format); + if (ret) { + ret = -errno; + LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret); + return ret; + } + + /* + * Return to caller the format actually applied on the device, + * which might differ from the requested one. + */ + format->size.width = 0; + format->size.height = 0; + format->fourcc = format->fourcc; + format->planesCount = 1; + format->planes[0].bpl = pix->buffersize; + format->planes[0].size = pix->buffersize; + + return 0; +} + +int V4L2VideoDevice::getFormatMultiplane(V4L2DeviceFormat *format) +{ + struct v4l2_format v4l2Format = {}; + struct v4l2_pix_format_mplane *pix = &v4l2Format.fmt.pix_mp; + int ret; + + v4l2Format.type = bufferType_; + ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format); + if (ret) { + ret = -errno; + LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret); + return ret; + } + + format->size.width = pix->width; + format->size.height = pix->height; + format->fourcc = pix->pixelformat; + format->planesCount = pix->num_planes; + + for (unsigned int i = 0; i < format->planesCount; ++i) { + format->planes[i].bpl = pix->plane_fmt[i].bytesperline; + format->planes[i].size = pix->plane_fmt[i].sizeimage; + } + + return 0; +} + +int V4L2VideoDevice::setFormatMultiplane(V4L2DeviceFormat *format) +{ + struct v4l2_format v4l2Format = {}; + struct v4l2_pix_format_mplane *pix = &v4l2Format.fmt.pix_mp; + int ret; + + v4l2Format.type = bufferType_; + pix->width = format->size.width; + pix->height = format->size.height; + pix->pixelformat = format->fourcc; + pix->num_planes = format->planesCount; + pix->field = V4L2_FIELD_NONE; + + for (unsigned int i = 0; i < pix->num_planes; ++i) { + pix->plane_fmt[i].bytesperline = format->planes[i].bpl; + pix->plane_fmt[i].sizeimage = format->planes[i].size; + } + + ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format); + if (ret) { + ret = -errno; + LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret); + return ret; + } + + /* + * Return to caller the format actually applied on the device, + * which might differ from the requested one. + */ + format->size.width = pix->width; + format->size.height = pix->height; + format->fourcc = pix->pixelformat; + format->planesCount = pix->num_planes; + for (unsigned int i = 0; i < format->planesCount; ++i) { + format->planes[i].bpl = pix->plane_fmt[i].bytesperline; + format->planes[i].size = pix->plane_fmt[i].sizeimage; + } + + return 0; +} + +int V4L2VideoDevice::getFormatSingleplane(V4L2DeviceFormat *format) +{ + struct v4l2_format v4l2Format = {}; + struct v4l2_pix_format *pix = &v4l2Format.fmt.pix; + int ret; + + v4l2Format.type = bufferType_; + ret = ioctl(fd_, VIDIOC_G_FMT, &v4l2Format); + if (ret) { + ret = -errno; + LOG(V4L2, Error) << "Unable to get format: " << strerror(-ret); + return ret; + } + + format->size.width = pix->width; + format->size.height = pix->height; + format->fourcc = pix->pixelformat; + format->planesCount = 1; + format->planes[0].bpl = pix->bytesperline; + format->planes[0].size = pix->sizeimage; + + return 0; +} + +int V4L2VideoDevice::setFormatSingleplane(V4L2DeviceFormat *format) +{ + struct v4l2_format v4l2Format = {}; + struct v4l2_pix_format *pix = &v4l2Format.fmt.pix; + int ret; + + v4l2Format.type = bufferType_; + pix->width = format->size.width; + pix->height = format->size.height; + pix->pixelformat = format->fourcc; + pix->bytesperline = format->planes[0].bpl; + pix->field = V4L2_FIELD_NONE; + ret = ioctl(fd_, VIDIOC_S_FMT, &v4l2Format); + if (ret) { + ret = -errno; + LOG(V4L2, Error) << "Unable to set format: " << strerror(-ret); + return ret; + } + + /* + * Return to caller the format actually applied on the device, + * which might differ from the requested one. + */ + format->size.width = pix->width; + format->size.height = pix->height; + format->fourcc = pix->pixelformat; + format->planesCount = 1; + format->planes[0].bpl = pix->bytesperline; + format->planes[0].size = pix->sizeimage; + + return 0; +} + +/** + * \brief Enumerate all pixel formats and frame sizes + * + * Enumerate all pixel formats and frame sizes supported by the video device. + * + * \return A list of the supported device formats + */ +ImageFormats V4L2VideoDevice::formats() +{ + ImageFormats formats; + + for (unsigned int pixelformat : enumPixelformats()) { + std::vector sizes = enumSizes(pixelformat); + if (sizes.empty()) + return {}; + + if (formats.addFormat(pixelformat, sizes)) { + LOG(V4L2, Error) + << "Could not add sizes for pixel format " + << pixelformat; + return {}; + } + } + + return formats; +} + +int V4L2VideoDevice::requestBuffers(unsigned int count) +{ + struct v4l2_requestbuffers rb = {}; + int ret; + + rb.count = count; + rb.type = bufferType_; + rb.memory = memoryType_; + + ret = ioctl(fd_, VIDIOC_REQBUFS, &rb); + if (ret < 0) { + ret = -errno; + LOG(V4L2, Error) + << "Unable to request " << count << " buffers: " + << strerror(-ret); + return ret; + } + + LOG(V4L2, Debug) << rb.count << " buffers requested."; + + return rb.count; +} + +/** + * \brief Request buffers to be allocated from the device and stored in the + * buffer pool provided. + * \param[out] pool BufferPool to populate with buffers + * \return 0 on success or a negative error code otherwise + */ +int V4L2VideoDevice::exportBuffers(BufferPool *pool) +{ + unsigned int allocatedBuffers; + unsigned int i; + int ret; + + memoryType_ = V4L2_MEMORY_MMAP; + + ret = requestBuffers(pool->count()); + if (ret < 0) + return ret; + + allocatedBuffers = ret; + if (allocatedBuffers < pool->count()) { + LOG(V4L2, Error) + << "Not enough buffers provided by V4L2VideoDevice"; + requestBuffers(0); + return -ENOMEM; + } + + /* Map the buffers. */ + for (i = 0; i < pool->count(); ++i) { + struct v4l2_plane planes[VIDEO_MAX_PLANES] = {}; + struct v4l2_buffer buf = {}; + Buffer &buffer = pool->buffers()[i]; + + buf.index = i; + buf.type = bufferType_; + buf.memory = memoryType_; + buf.length = VIDEO_MAX_PLANES; + buf.m.planes = planes; + + ret = ioctl(fd_, VIDIOC_QUERYBUF, &buf); + if (ret < 0) { + ret = -errno; + LOG(V4L2, Error) + << "Unable to query buffer " << i << ": " + << strerror(-ret); + break; + } + + if (V4L2_TYPE_IS_MULTIPLANAR(buf.type)) { + for (unsigned int p = 0; p < buf.length; ++p) { + ret = createPlane(&buffer, p, + buf.m.planes[p].length); + if (ret) + break; + } + } else { + ret = createPlane(&buffer, 0, buf.length); + } + + if (ret) { + LOG(V4L2, Error) << "Failed to create plane"; + break; + } + } + + if (ret) { + requestBuffers(0); + pool->destroyBuffers(); + return ret; + } + + bufferPool_ = pool; + + return 0; +} + +int V4L2VideoDevice::createPlane(Buffer *buffer, unsigned int planeIndex, + unsigned int length) +{ + struct v4l2_exportbuffer expbuf = {}; + int ret; + + LOG(V4L2, Debug) + << "Buffer " << buffer->index() + << " plane " << planeIndex + << ": length=" << length; + + expbuf.type = bufferType_; + expbuf.index = buffer->index(); + expbuf.plane = planeIndex; + expbuf.flags = O_RDWR; + + ret = ioctl(fd_, VIDIOC_EXPBUF, &expbuf); + if (ret < 0) { + ret = -errno; + LOG(V4L2, Error) + << "Failed to export buffer: " << strerror(-ret); + return ret; + } + + buffer->planes().emplace_back(); + Plane &plane = buffer->planes().back(); + plane.setDmabuf(expbuf.fd, length); + ::close(expbuf.fd); + + return 0; +} + +std::vector V4L2VideoDevice::enumPixelformats() +{ + std::vector formats; + int ret; + + for (unsigned int index = 0; ; index++) { + struct v4l2_fmtdesc pixelformatEnum = {}; + pixelformatEnum.index = index; + pixelformatEnum.type = bufferType_; + + ret = ioctl(fd_, VIDIOC_ENUM_FMT, &pixelformatEnum); + if (ret) + break; + + formats.push_back(pixelformatEnum.pixelformat); + } + + if (ret && errno != EINVAL) { + ret = -errno; + LOG(V4L2, Error) + << "Unable to enumerate pixel formats: " + << strerror(-ret); + return {}; + } + + return formats; +} + +std::vector V4L2VideoDevice::enumSizes(unsigned int pixelFormat) +{ + std::vector sizes; + int ret; + + for (unsigned int index = 0;; index++) { + struct v4l2_frmsizeenum frameSize = {}; + frameSize.index = index; + frameSize.pixel_format = pixelFormat; + + ret = ioctl(fd_, VIDIOC_ENUM_FRAMESIZES, &frameSize); + if (ret) + break; + + if (index != 0 && + frameSize.type != V4L2_FRMSIZE_TYPE_DISCRETE) { + LOG(V4L2, Error) + << "Non-zero index for non discrete type"; + return {}; + } + + switch (frameSize.type) { + case V4L2_FRMSIZE_TYPE_DISCRETE: + sizes.emplace_back(frameSize.discrete.width, + frameSize.discrete.height); + break; + case V4L2_FRMSIZE_TYPE_CONTINUOUS: + sizes.emplace_back(frameSize.stepwise.min_width, + frameSize.stepwise.min_height, + frameSize.stepwise.max_width, + frameSize.stepwise.max_height); + break; + case V4L2_FRMSIZE_TYPE_STEPWISE: + sizes.emplace_back(frameSize.stepwise.min_width, + frameSize.stepwise.min_height, + frameSize.stepwise.max_width, + frameSize.stepwise.max_height, + frameSize.stepwise.step_width, + frameSize.stepwise.step_height); + break; + default: + LOG(V4L2, Error) + << "Unknown VIDIOC_ENUM_FRAMESIZES type " + << frameSize.type; + return {}; + } + } + + if (ret && errno != EINVAL) { + ret = -errno; + LOG(V4L2, Error) + << "Unable to enumerate frame sizes: " + << strerror(-ret); + return {}; + } + + return sizes; +} + +/** + * \brief Import the externally allocated \a pool of buffers + * \param[in] pool BufferPool of buffers to import + * \return 0 on success or a negative error code otherwise + */ +int V4L2VideoDevice::importBuffers(BufferPool *pool) +{ + unsigned int allocatedBuffers; + int ret; + + memoryType_ = V4L2_MEMORY_DMABUF; + + ret = requestBuffers(pool->count()); + if (ret < 0) + return ret; + + allocatedBuffers = ret; + if (allocatedBuffers < pool->count()) { + LOG(V4L2, Error) + << "Not enough buffers provided by V4L2VideoDevice"; + requestBuffers(0); + return -ENOMEM; + } + + LOG(V4L2, Debug) << "provided pool of " << pool->count() << " buffers"; + bufferPool_ = pool; + + return 0; +} + +/** + * \brief Release all internally allocated buffers + */ +int V4L2VideoDevice::releaseBuffers() +{ + LOG(V4L2, Debug) << "Releasing bufferPool"; + + bufferPool_ = nullptr; + + return requestBuffers(0); +} + +/** + * \brief Queue a buffer into the device + * \param[in] buffer The buffer to be queued + * + * For capture devices the \a buffer will be filled with data by the device. + * For output devices the \a buffer shall contain valid data and will be + * processed by the device. Once the device has finished processing the buffer, + * it will be available for dequeue. + * + * \return 0 on success or a negative error code otherwise + */ +int V4L2VideoDevice::queueBuffer(Buffer *buffer) +{ + struct v4l2_buffer buf = {}; + struct v4l2_plane planes[VIDEO_MAX_PLANES] = {}; + int ret; + + buf.index = buffer->index(); + buf.type = bufferType_; + buf.memory = memoryType_; + buf.field = V4L2_FIELD_NONE; + + bool multiPlanar = V4L2_TYPE_IS_MULTIPLANAR(buf.type); + + if (buf.memory == V4L2_MEMORY_DMABUF) { + if (multiPlanar) { + for (unsigned int p = 0; + p < buffer->planes().size(); + p++) + planes[p].m.fd = buffer->planes()[p].dmabuf(); + } else { + buf.m.fd = buffer->planes()[0].dmabuf(); + } + } + + if (multiPlanar) { + buf.length = buffer->planes().size(); + buf.m.planes = planes; + } + + if (V4L2_TYPE_IS_OUTPUT(bufferType_)) { + buf.bytesused = buffer->bytesused_; + buf.sequence = buffer->sequence_; + buf.timestamp.tv_sec = buffer->timestamp_ / 1000000000; + buf.timestamp.tv_usec = (buffer->timestamp_ / 1000) % 1000000; + } + + LOG(V4L2, Debug) << "Queueing buffer " << buf.index; + + ret = ioctl(fd_, VIDIOC_QBUF, &buf); + if (ret < 0) { + ret = -errno; + LOG(V4L2, Error) + << "Failed to queue buffer " << buf.index << ": " + << strerror(-ret); + return ret; + } + + if (queuedBuffersCount_++ == 0) + fdEvent_->setEnabled(true); + + return 0; +} + +/** + * \brief Dequeue the next available buffer from the device + * + * This method dequeues the next available buffer from the device. If no buffer + * is available to be dequeued it will return nullptr immediately. + * + * \return A pointer to the dequeued buffer on success, or nullptr otherwise + */ +Buffer *V4L2VideoDevice::dequeueBuffer() +{ + struct v4l2_buffer buf = {}; + struct v4l2_plane planes[VIDEO_MAX_PLANES] = {}; + int ret; + + buf.type = bufferType_; + buf.memory = memoryType_; + + if (V4L2_TYPE_IS_MULTIPLANAR(buf.type)) { + buf.length = VIDEO_MAX_PLANES; + buf.m.planes = planes; + } + + ret = ioctl(fd_, VIDIOC_DQBUF, &buf); + if (ret < 0) { + ret = -errno; + LOG(V4L2, Error) + << "Failed to dequeue buffer: " << strerror(-ret); + return nullptr; + } + + ASSERT(buf.index < bufferPool_->count()); + + if (--queuedBuffersCount_ == 0) + fdEvent_->setEnabled(false); + + Buffer *buffer = &bufferPool_->buffers()[buf.index]; + + buffer->bytesused_ = buf.bytesused; + buffer->timestamp_ = buf.timestamp.tv_sec * 1000000000ULL + + buf.timestamp.tv_usec * 1000ULL; + buffer->sequence_ = buf.sequence; + buffer->status_ = buf.flags & V4L2_BUF_FLAG_ERROR + ? Buffer::BufferError : Buffer::BufferSuccess; + + return buffer; +} + +/** + * \brief Slot to handle completed buffer events from the V4L2 device + * \param[in] notifier The event notifier + * + * When this slot is called, a Buffer has become available from the device, and + * will be emitted through the bufferReady Signal. + * + * For Capture devices the Buffer will contain valid data. + * For Output devices the Buffer can be considered empty. + */ +void V4L2VideoDevice::bufferAvailable(EventNotifier *notifier) +{ + Buffer *buffer = dequeueBuffer(); + if (!buffer) + return; + + LOG(V4L2, Debug) << "Buffer " << buffer->index() << " is available"; + + /* Notify anyone listening to the device. */ + bufferReady.emit(buffer); +} + +/** + * \var V4L2VideoDevice::bufferReady + * \brief A Signal emitted when a buffer completes + */ + +/** + * \brief Start the video stream + * \return 0 on success or a negative error code otherwise + */ +int V4L2VideoDevice::streamOn() +{ + int ret; + + ret = ioctl(fd_, VIDIOC_STREAMON, &bufferType_); + if (ret < 0) { + ret = -errno; + LOG(V4L2, Error) + << "Failed to start streaming: " << strerror(-ret); + return ret; + } + + return 0; +} + +/** + * \brief Stop the video stream + * + * Buffers that are still queued when the video stream is stopped are + * implicitly dequeued, but no bufferReady signal is emitted for them. + * + * \return 0 on success or a negative error code otherwise + */ +int V4L2VideoDevice::streamOff() +{ + int ret; + + ret = ioctl(fd_, VIDIOC_STREAMOFF, &bufferType_); + if (ret < 0) { + ret = -errno; + LOG(V4L2, Error) + << "Failed to stop streaming: " << strerror(-ret); + return ret; + } + + queuedBuffersCount_ = 0; + fdEvent_->setEnabled(false); + + return 0; +} + +/** + * \brief Create a new video device instance from \a entity in media device + * \a media + * \param[in] media The media device where the entity is registered + * \param[in] entity The media entity name + * + * Releasing memory of the newly created instance is responsibility of the + * caller of this function. + * + * \return A newly created V4L2VideoDevice on success, nullptr otherwise + */ +V4L2VideoDevice *V4L2VideoDevice::fromEntityName(const MediaDevice *media, + const std::string &entity) +{ + MediaEntity *mediaEntity = media->getEntityByName(entity); + if (!mediaEntity) + return nullptr; + + return new V4L2VideoDevice(mediaEntity); +} + +} /* namespace libcamera */ diff --git a/test/meson.build b/test/meson.build index 654e0089..c36ac247 100644 --- a/test/meson.build +++ b/test/meson.build @@ -5,8 +5,8 @@ subdir('ipa') subdir('media_device') subdir('pipeline') subdir('stream') -subdir('v4l2_device') subdir('v4l2_subdevice') +subdir('v4l2_videodevice') public_tests = [ ['event', 'event.cpp'], diff --git a/test/v4l2_device/buffer_sharing.cpp b/test/v4l2_device/buffer_sharing.cpp deleted file mode 100644 index e63ddff8..00000000 --- a/test/v4l2_device/buffer_sharing.cpp +++ /dev/null @@ -1,186 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (C) 2019, Google Inc. - * - * libcamera V4L2 API tests - * - * Validate the function of exporting buffers from a V4L2Device and - * the ability to import them to another V4L2Device instance. - * Ensure that the Buffers can successfully be queued and dequeued - * between both devices. - */ - -#include - -#include -#include -#include -#include - -#include "v4l2_device_test.h" - -class BufferSharingTest : public V4L2DeviceTest -{ -public: - BufferSharingTest() - : V4L2DeviceTest("vivid", "vivid-000-vid-cap"), - output_(nullptr), framesCaptured_(0), framesOutput_(0) {} - -protected: - int init() - { - int ret = V4L2DeviceTest::init(); - if (ret) - return ret; - - /* media_ already represents VIVID */ - MediaEntity *entity = media_->getEntityByName("vivid-000-vid-out"); - if (!entity) - return TestSkip; - - output_ = new V4L2Device(entity); - if (!output_) { - std::cout << "Failed to create output device" << std::endl; - return TestFail; - } - - ret = output_->open(); - if (ret) { - std::cout << "Failed to open output device" << std::endl; - return TestFail; - } - - V4L2DeviceFormat format = {}; - - ret = capture_->getFormat(&format); - if (ret) { - std::cout << "Failed to get capture format" << std::endl; - return TestFail; - } - - ret = output_->setFormat(&format); - if (ret) { - std::cout << "Failed to set output format" << std::endl; - return TestFail; - } - - pool_.createBuffers(bufferCount); - - ret = capture_->exportBuffers(&pool_); - if (ret) { - std::cout << "Failed to export buffers" << std::endl; - return TestFail; - } - - ret = output_->importBuffers(&pool_); - if (ret) { - std::cout << "Failed to import buffers" << std::endl; - return TestFail; - } - - return 0; - } - - void captureBufferReady(Buffer *buffer) - { - std::cout << "Received capture buffer: " << buffer->index() - << " sequence " << buffer->sequence() << std::endl; - - output_->queueBuffer(buffer); - framesCaptured_++; - } - - void outputBufferReady(Buffer *buffer) - { - std::cout << "Received output buffer: " << buffer->index() - << " sequence " << buffer->sequence() << std::endl; - - capture_->queueBuffer(buffer); - framesOutput_++; - } - - int run() - { - EventDispatcher *dispatcher = CameraManager::instance()->eventDispatcher(); - Timer timeout; - int ret; - - capture_->bufferReady.connect(this, &BufferSharingTest::captureBufferReady); - output_->bufferReady.connect(this, &BufferSharingTest::outputBufferReady); - - /* Queue all the buffers to the capture device. */ - for (Buffer &buffer : pool_.buffers()) { - if (capture_->queueBuffer(&buffer)) - return TestFail; - } - - ret = capture_->streamOn(); - if (ret) { - std::cout << "Failed to start streaming on the capture device" << std::endl; - return TestFail; - } - - ret = output_->streamOn(); - if (ret) { - std::cout << "Failed to start streaming on the output device" << std::endl; - return TestFail; - } - - timeout.start(10000); - while (timeout.isRunning()) { - dispatcher->processEvents(); - if (framesCaptured_ > 30 && framesOutput_ > 30) - break; - } - - if ((framesCaptured_ < 1) || (framesOutput_ < 1)) { - std::cout << "Failed to process any frames within timeout." << std::endl; - return TestFail; - } - - if ((framesCaptured_ < 30) || (framesOutput_ < 30)) { - std::cout << "Failed to process 30 frames within timeout." << std::endl; - return TestFail; - } - - ret = capture_->streamOff(); - if (ret) { - std::cout << "Failed to stop streaming on the capture device" << std::endl; - return TestFail; - } - - ret = output_->streamOff(); - if (ret) { - std::cout << "Failed to stop streaming on the output device" << std::endl; - return TestFail; - } - - return TestPass; - } - - void cleanup() - { - std::cout - << "Captured " << framesCaptured_ << " frames and " - << "output " << framesOutput_ << " frames" - << std::endl; - - output_->streamOff(); - output_->releaseBuffers(); - output_->close(); - - delete output_; - - V4L2DeviceTest::cleanup(); - } - -private: - const unsigned int bufferCount = 4; - - V4L2Device *output_; - - unsigned int framesCaptured_; - unsigned int framesOutput_; -}; - -TEST_REGISTER(BufferSharingTest); diff --git a/test/v4l2_device/capture_async.cpp b/test/v4l2_device/capture_async.cpp deleted file mode 100644 index 69b1d5a1..00000000 --- a/test/v4l2_device/capture_async.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (C) 2019, Google Inc. - * - * libcamera V4L2 API tests - */ - -#include -#include -#include -#include - -#include - -#include "v4l2_device_test.h" - -class CaptureAsyncTest : public V4L2DeviceTest -{ -public: - CaptureAsyncTest() - : V4L2DeviceTest("vimc", "Raw Capture 0"), frames(0) {} - - void receiveBuffer(Buffer *buffer) - { - std::cout << "Received buffer " << buffer->index() << std::endl; - frames++; - - /* Requeue the buffer for further use. */ - capture_->queueBuffer(buffer); - } - -protected: - int run() - { - const unsigned int bufferCount = 8; - - EventDispatcher *dispatcher = CameraManager::instance()->eventDispatcher(); - Timer timeout; - int ret; - - pool_.createBuffers(bufferCount); - - ret = capture_->exportBuffers(&pool_); - if (ret) - return TestFail; - - capture_->bufferReady.connect(this, &CaptureAsyncTest::receiveBuffer); - - /* Queue all the buffers to the device. */ - for (Buffer &b : pool_.buffers()) { - if (capture_->queueBuffer(&b)) - return TestFail; - } - - ret = capture_->streamOn(); - if (ret) - return TestFail; - - timeout.start(10000); - while (timeout.isRunning()) { - dispatcher->processEvents(); - if (frames > 30) - break; - } - - if (frames < 1) { - std::cout << "Failed to capture any frames within timeout." << std::endl; - return TestFail; - } - - if (frames < 30) { - std::cout << "Failed to capture 30 frames within timeout." << std::endl; - return TestFail; - } - - std::cout << "Processed " << frames << " frames" << std::endl; - - ret = capture_->streamOff(); - if (ret) - return TestFail; - - return TestPass; - } - -private: - unsigned int frames; -}; - -TEST_REGISTER(CaptureAsyncTest); diff --git a/test/v4l2_device/double_open.cpp b/test/v4l2_device/double_open.cpp deleted file mode 100644 index 53850620..00000000 --- a/test/v4l2_device/double_open.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (C) 2019, Google Inc. - * - * libcamera V4L2 API tests - */ - -#include - -#include "v4l2_device_test.h" - -namespace { - -class DoubleOpen : public V4L2DeviceTest -{ -public: - DoubleOpen() - : V4L2DeviceTest("vimc", "Raw Capture 0") {} -protected: - int run() - { - int ret; - - /* - * Expect failure: The device has already been opened by the - * V4L2DeviceTest base class - */ - ret = capture_->open(); - if (!ret) { - std::cout << "Double open erroneously succeeded" << std::endl; - capture_->close(); - return TestFail; - } - - return TestPass; - } -}; - -} /* namespace */ - -TEST_REGISTER(DoubleOpen); diff --git a/test/v4l2_device/formats.cpp b/test/v4l2_device/formats.cpp deleted file mode 100644 index 6be045ff..00000000 --- a/test/v4l2_device/formats.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (C) 2019, Google Inc. - * - * libcamera V4L2 device format handling test - */ - -#include -#include - -#include "v4l2_device.h" - -#include "v4l2_device_test.h" - -using namespace std; -using namespace libcamera; - -class Format : public V4L2DeviceTest -{ -public: - Format() - : V4L2DeviceTest("vimc", "Raw Capture 0") {} -protected: - int run() - { - V4L2DeviceFormat format = {}; - - int ret = capture_->getFormat(&format); - if (ret) { - cerr << "Failed to get format" << endl; - return TestFail; - } - - format.size = { UINT_MAX, UINT_MAX }; - ret = capture_->setFormat(&format); - if (ret) { - cerr << "Failed to set format: image resolution is invalid: " - << "(UINT_MAX x UINT_MAX) but setFormat() should not fail." - << endl; - return TestFail; - } - - if (format.size.width == UINT_MAX || - format.size.height == UINT_MAX) { - cerr << "Failed to update image format = (UINT_MAX x UINT_MAX)" - << endl; - return TestFail; - } - - return TestPass; - } -}; - -TEST_REGISTER(Format); diff --git a/test/v4l2_device/meson.build b/test/v4l2_device/meson.build deleted file mode 100644 index de540b1b..00000000 --- a/test/v4l2_device/meson.build +++ /dev/null @@ -1,18 +0,0 @@ -# Tests are listed in order of complexity. -# They are not alphabetically sorted. -v4l2_device_tests = [ - [ 'double_open', 'double_open.cpp' ], - [ 'formats', 'formats.cpp' ], - [ 'request_buffers', 'request_buffers.cpp' ], - [ 'stream_on_off', 'stream_on_off.cpp' ], - [ 'capture_async', 'capture_async.cpp' ], - [ 'buffer_sharing', 'buffer_sharing.cpp' ], -] - -foreach t : v4l2_device_tests - exe = executable(t[0], [t[1], 'v4l2_device_test.cpp'], - dependencies : libcamera_dep, - link_with : test_libraries, - include_directories : test_includes_internal) - test(t[0], exe, suite : 'v4l2_device', is_parallel : false) -endforeach diff --git a/test/v4l2_device/request_buffers.cpp b/test/v4l2_device/request_buffers.cpp deleted file mode 100644 index 7b7b06b2..00000000 --- a/test/v4l2_device/request_buffers.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (C) 2019, Google Inc. - * - * libcamera V4L2 API tests - */ - -#include "v4l2_device_test.h" - -class RequestBuffersTest : public V4L2DeviceTest -{ -public: - RequestBuffersTest() - : V4L2DeviceTest("vimc", "Raw Capture 0") {} - -protected: - int run() - { - /* - * TODO: - * Test invalid requests - * Test different buffer allocations - */ - const unsigned int bufferCount = 8; - - pool_.createBuffers(bufferCount); - - int ret = capture_->exportBuffers(&pool_); - if (ret) - return TestFail; - - return TestPass; - } -}; - -TEST_REGISTER(RequestBuffersTest); diff --git a/test/v4l2_device/stream_on_off.cpp b/test/v4l2_device/stream_on_off.cpp deleted file mode 100644 index b158b8e4..00000000 --- a/test/v4l2_device/stream_on_off.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (C) 2019, Google Inc. - * - * libcamera V4L2 API tests - */ - -#include "v4l2_device_test.h" - -class StreamOnStreamOffTest : public V4L2DeviceTest -{ -public: - StreamOnStreamOffTest() - : V4L2DeviceTest("vimc", "Raw Capture 0") {} -protected: - int run() - { - const unsigned int bufferCount = 8; - - pool_.createBuffers(bufferCount); - - int ret = capture_->exportBuffers(&pool_); - if (ret) - return TestFail; - - ret = capture_->streamOn(); - if (ret) - return TestFail; - - ret = capture_->streamOff(); - if (ret) - return TestFail; - - return TestPass; - } -}; - -TEST_REGISTER(StreamOnStreamOffTest); diff --git a/test/v4l2_device/v4l2_device_test.cpp b/test/v4l2_device/v4l2_device_test.cpp deleted file mode 100644 index baad48f8..00000000 --- a/test/v4l2_device/v4l2_device_test.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (C) 2019, Google Inc. - * - * libcamera V4L2 API tests - */ - -#include -#include - -#include "v4l2_device_test.h" - -#include "device_enumerator.h" -#include "media_device.h" - -using namespace std; -using namespace libcamera; - -bool exists(const std::string &path) -{ - struct stat sb; - - if (stat(path.c_str(), &sb) == 0) - return true; - - return false; -} - -int V4L2DeviceTest::init() -{ - enumerator_ = DeviceEnumerator::create(); - if (!enumerator_) { - cerr << "Failed to create device enumerator" << endl; - return TestFail; - } - - if (enumerator_->enumerate()) { - cerr << "Failed to enumerate media devices" << endl; - return TestFail; - } - - DeviceMatch dm(driver_); - dm.add(entity_); - - media_ = enumerator_->search(dm); - if (!media_) - return TestSkip; - - MediaEntity *entity = media_->getEntityByName(entity_); - if (!entity) - return TestSkip; - - capture_ = new V4L2Device(entity); - if (!capture_) - return TestFail; - - if (!media_->acquire()) - return TestFail; - - int ret = media_->disableLinks(); - media_->release(); - if (ret) - return TestFail; - - if (capture_->open()) - return TestFail; - - V4L2DeviceFormat format = {}; - if (capture_->getFormat(&format)) - return TestFail; - - format.size.width = 640; - format.size.height = 480; - if (capture_->setFormat(&format)) - return TestFail; - - return TestPass; -} - -void V4L2DeviceTest::cleanup() -{ - capture_->streamOff(); - capture_->releaseBuffers(); - capture_->close(); - - delete capture_; -}; diff --git a/test/v4l2_device/v4l2_device_test.h b/test/v4l2_device/v4l2_device_test.h deleted file mode 100644 index 651c005f..00000000 --- a/test/v4l2_device/v4l2_device_test.h +++ /dev/null @@ -1,42 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Copyright (C) 2018, Google Inc. - * - * vl42device_test.h - libcamera v4l2device test base class - */ -#ifndef __LIBCAMERA_V4L2_DEVICE_TEST_H_ -#define __LIBCAMERA_V4L2_DEVICE_TEST_H_ - -#include - -#include - -#include "test.h" - -#include "device_enumerator.h" -#include "media_device.h" -#include "v4l2_device.h" - -using namespace libcamera; - -class V4L2DeviceTest : public Test -{ -public: - V4L2DeviceTest(const char *driver, const char *entity) - : driver_(driver), entity_(entity), capture_(nullptr) - { - } - -protected: - int init(); - void cleanup(); - - std::string driver_; - std::string entity_; - std::unique_ptr enumerator_; - std::shared_ptr media_; - V4L2Device *capture_; - BufferPool pool_; -}; - -#endif /* __LIBCAMERA_V4L2_DEVICE_TEST_H_ */ diff --git a/test/v4l2_videodevice/buffer_sharing.cpp b/test/v4l2_videodevice/buffer_sharing.cpp new file mode 100644 index 00000000..1bc478fe --- /dev/null +++ b/test/v4l2_videodevice/buffer_sharing.cpp @@ -0,0 +1,186 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * libcamera V4L2 API tests + * + * Validate the function of exporting buffers from a V4L2VideoDevice and + * the ability to import them to another V4L2VideoDevice instance. + * Ensure that the Buffers can successfully be queued and dequeued + * between both devices. + */ + +#include + +#include +#include +#include +#include + +#include "v4l2_videodevice_test.h" + +class BufferSharingTest : public V4L2VideoDeviceTest +{ +public: + BufferSharingTest() + : V4L2VideoDeviceTest("vivid", "vivid-000-vid-cap"), + output_(nullptr), framesCaptured_(0), framesOutput_(0) {} + +protected: + int init() + { + int ret = V4L2VideoDeviceTest::init(); + if (ret) + return ret; + + /* media_ already represents VIVID */ + MediaEntity *entity = media_->getEntityByName("vivid-000-vid-out"); + if (!entity) + return TestSkip; + + output_ = new V4L2VideoDevice(entity); + if (!output_) { + std::cout << "Failed to create output device" << std::endl; + return TestFail; + } + + ret = output_->open(); + if (ret) { + std::cout << "Failed to open output device" << std::endl; + return TestFail; + } + + V4L2DeviceFormat format = {}; + + ret = capture_->getFormat(&format); + if (ret) { + std::cout << "Failed to get capture format" << std::endl; + return TestFail; + } + + ret = output_->setFormat(&format); + if (ret) { + std::cout << "Failed to set output format" << std::endl; + return TestFail; + } + + pool_.createBuffers(bufferCount); + + ret = capture_->exportBuffers(&pool_); + if (ret) { + std::cout << "Failed to export buffers" << std::endl; + return TestFail; + } + + ret = output_->importBuffers(&pool_); + if (ret) { + std::cout << "Failed to import buffers" << std::endl; + return TestFail; + } + + return 0; + } + + void captureBufferReady(Buffer *buffer) + { + std::cout << "Received capture buffer: " << buffer->index() + << " sequence " << buffer->sequence() << std::endl; + + output_->queueBuffer(buffer); + framesCaptured_++; + } + + void outputBufferReady(Buffer *buffer) + { + std::cout << "Received output buffer: " << buffer->index() + << " sequence " << buffer->sequence() << std::endl; + + capture_->queueBuffer(buffer); + framesOutput_++; + } + + int run() + { + EventDispatcher *dispatcher = CameraManager::instance()->eventDispatcher(); + Timer timeout; + int ret; + + capture_->bufferReady.connect(this, &BufferSharingTest::captureBufferReady); + output_->bufferReady.connect(this, &BufferSharingTest::outputBufferReady); + + /* Queue all the buffers to the capture device. */ + for (Buffer &buffer : pool_.buffers()) { + if (capture_->queueBuffer(&buffer)) + return TestFail; + } + + ret = capture_->streamOn(); + if (ret) { + std::cout << "Failed to start streaming on the capture device" << std::endl; + return TestFail; + } + + ret = output_->streamOn(); + if (ret) { + std::cout << "Failed to start streaming on the output device" << std::endl; + return TestFail; + } + + timeout.start(10000); + while (timeout.isRunning()) { + dispatcher->processEvents(); + if (framesCaptured_ > 30 && framesOutput_ > 30) + break; + } + + if ((framesCaptured_ < 1) || (framesOutput_ < 1)) { + std::cout << "Failed to process any frames within timeout." << std::endl; + return TestFail; + } + + if ((framesCaptured_ < 30) || (framesOutput_ < 30)) { + std::cout << "Failed to process 30 frames within timeout." << std::endl; + return TestFail; + } + + ret = capture_->streamOff(); + if (ret) { + std::cout << "Failed to stop streaming on the capture device" << std::endl; + return TestFail; + } + + ret = output_->streamOff(); + if (ret) { + std::cout << "Failed to stop streaming on the output device" << std::endl; + return TestFail; + } + + return TestPass; + } + + void cleanup() + { + std::cout + << "Captured " << framesCaptured_ << " frames and " + << "output " << framesOutput_ << " frames" + << std::endl; + + output_->streamOff(); + output_->releaseBuffers(); + output_->close(); + + delete output_; + + V4L2VideoDeviceTest::cleanup(); + } + +private: + const unsigned int bufferCount = 4; + + V4L2VideoDevice *output_; + + unsigned int framesCaptured_; + unsigned int framesOutput_; +}; + +TEST_REGISTER(BufferSharingTest); diff --git a/test/v4l2_videodevice/capture_async.cpp b/test/v4l2_videodevice/capture_async.cpp new file mode 100644 index 00000000..cea4fffb --- /dev/null +++ b/test/v4l2_videodevice/capture_async.cpp @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * libcamera V4L2 API tests + */ + +#include +#include +#include +#include + +#include + +#include "v4l2_videodevice_test.h" + +class CaptureAsyncTest : public V4L2VideoDeviceTest +{ +public: + CaptureAsyncTest() + : V4L2VideoDeviceTest("vimc", "Raw Capture 0"), frames(0) {} + + void receiveBuffer(Buffer *buffer) + { + std::cout << "Received buffer " << buffer->index() << std::endl; + frames++; + + /* Requeue the buffer for further use. */ + capture_->queueBuffer(buffer); + } + +protected: + int run() + { + const unsigned int bufferCount = 8; + + EventDispatcher *dispatcher = CameraManager::instance()->eventDispatcher(); + Timer timeout; + int ret; + + pool_.createBuffers(bufferCount); + + ret = capture_->exportBuffers(&pool_); + if (ret) + return TestFail; + + capture_->bufferReady.connect(this, &CaptureAsyncTest::receiveBuffer); + + /* Queue all the buffers to the device. */ + for (Buffer &b : pool_.buffers()) { + if (capture_->queueBuffer(&b)) + return TestFail; + } + + ret = capture_->streamOn(); + if (ret) + return TestFail; + + timeout.start(10000); + while (timeout.isRunning()) { + dispatcher->processEvents(); + if (frames > 30) + break; + } + + if (frames < 1) { + std::cout << "Failed to capture any frames within timeout." << std::endl; + return TestFail; + } + + if (frames < 30) { + std::cout << "Failed to capture 30 frames within timeout." << std::endl; + return TestFail; + } + + std::cout << "Processed " << frames << " frames" << std::endl; + + ret = capture_->streamOff(); + if (ret) + return TestFail; + + return TestPass; + } + +private: + unsigned int frames; +}; + +TEST_REGISTER(CaptureAsyncTest); diff --git a/test/v4l2_videodevice/double_open.cpp b/test/v4l2_videodevice/double_open.cpp new file mode 100644 index 00000000..5768d404 --- /dev/null +++ b/test/v4l2_videodevice/double_open.cpp @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * libcamera V4L2 API tests + */ + +#include + +#include "v4l2_videodevice_test.h" + +namespace { + +class DoubleOpen : public V4L2VideoDeviceTest +{ +public: + DoubleOpen() + : V4L2VideoDeviceTest("vimc", "Raw Capture 0") {} +protected: + int run() + { + int ret; + + /* + * Expect failure: The device has already been opened by the + * V4L2VideoDeviceTest base class + */ + ret = capture_->open(); + if (!ret) { + std::cout << "Double open erroneously succeeded" << std::endl; + capture_->close(); + return TestFail; + } + + return TestPass; + } +}; + +} /* namespace */ + +TEST_REGISTER(DoubleOpen); diff --git a/test/v4l2_videodevice/formats.cpp b/test/v4l2_videodevice/formats.cpp new file mode 100644 index 00000000..ee7d357d --- /dev/null +++ b/test/v4l2_videodevice/formats.cpp @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * libcamera V4L2 device format handling test + */ + +#include +#include + +#include "v4l2_videodevice.h" + +#include "v4l2_videodevice_test.h" + +using namespace std; +using namespace libcamera; + +class Format : public V4L2VideoDeviceTest +{ +public: + Format() + : V4L2VideoDeviceTest("vimc", "Raw Capture 0") {} +protected: + int run() + { + V4L2DeviceFormat format = {}; + + int ret = capture_->getFormat(&format); + if (ret) { + cerr << "Failed to get format" << endl; + return TestFail; + } + + format.size = { UINT_MAX, UINT_MAX }; + ret = capture_->setFormat(&format); + if (ret) { + cerr << "Failed to set format: image resolution is invalid: " + << "(UINT_MAX x UINT_MAX) but setFormat() should not fail." + << endl; + return TestFail; + } + + if (format.size.width == UINT_MAX || + format.size.height == UINT_MAX) { + cerr << "Failed to update image format = (UINT_MAX x UINT_MAX)" + << endl; + return TestFail; + } + + return TestPass; + } +}; + +TEST_REGISTER(Format); diff --git a/test/v4l2_videodevice/meson.build b/test/v4l2_videodevice/meson.build new file mode 100644 index 00000000..76be5e14 --- /dev/null +++ b/test/v4l2_videodevice/meson.build @@ -0,0 +1,18 @@ +# Tests are listed in order of complexity. +# They are not alphabetically sorted. +v4l2_videodevice_tests = [ + [ 'double_open', 'double_open.cpp' ], + [ 'formats', 'formats.cpp' ], + [ 'request_buffers', 'request_buffers.cpp' ], + [ 'stream_on_off', 'stream_on_off.cpp' ], + [ 'capture_async', 'capture_async.cpp' ], + [ 'buffer_sharing', 'buffer_sharing.cpp' ], +] + +foreach t : v4l2_videodevice_tests + exe = executable(t[0], [t[1], 'v4l2_videodevice_test.cpp'], + dependencies : libcamera_dep, + link_with : test_libraries, + include_directories : test_includes_internal) + test(t[0], exe, suite : 'v4l2_videodevice', is_parallel : false) +endforeach diff --git a/test/v4l2_videodevice/request_buffers.cpp b/test/v4l2_videodevice/request_buffers.cpp new file mode 100644 index 00000000..c4aedf7b --- /dev/null +++ b/test/v4l2_videodevice/request_buffers.cpp @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * libcamera V4L2 API tests + */ + +#include "v4l2_videodevice_test.h" + +class RequestBuffersTest : public V4L2VideoDeviceTest +{ +public: + RequestBuffersTest() + : V4L2VideoDeviceTest("vimc", "Raw Capture 0") {} + +protected: + int run() + { + /* + * TODO: + * Test invalid requests + * Test different buffer allocations + */ + const unsigned int bufferCount = 8; + + pool_.createBuffers(bufferCount); + + int ret = capture_->exportBuffers(&pool_); + if (ret) + return TestFail; + + return TestPass; + } +}; + +TEST_REGISTER(RequestBuffersTest); diff --git a/test/v4l2_videodevice/stream_on_off.cpp b/test/v4l2_videodevice/stream_on_off.cpp new file mode 100644 index 00000000..7664adc4 --- /dev/null +++ b/test/v4l2_videodevice/stream_on_off.cpp @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * libcamera V4L2 API tests + */ + +#include "v4l2_videodevice_test.h" + +class StreamOnStreamOffTest : public V4L2VideoDeviceTest +{ +public: + StreamOnStreamOffTest() + : V4L2VideoDeviceTest("vimc", "Raw Capture 0") {} +protected: + int run() + { + const unsigned int bufferCount = 8; + + pool_.createBuffers(bufferCount); + + int ret = capture_->exportBuffers(&pool_); + if (ret) + return TestFail; + + ret = capture_->streamOn(); + if (ret) + return TestFail; + + ret = capture_->streamOff(); + if (ret) + return TestFail; + + return TestPass; + } +}; + +TEST_REGISTER(StreamOnStreamOffTest); diff --git a/test/v4l2_videodevice/v4l2_videodevice_test.cpp b/test/v4l2_videodevice/v4l2_videodevice_test.cpp new file mode 100644 index 00000000..b26d06ad --- /dev/null +++ b/test/v4l2_videodevice/v4l2_videodevice_test.cpp @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * libcamera V4L2 API tests + */ + +#include +#include + +#include "v4l2_videodevice_test.h" + +#include "device_enumerator.h" +#include "media_device.h" + +using namespace std; +using namespace libcamera; + +bool exists(const std::string &path) +{ + struct stat sb; + + if (stat(path.c_str(), &sb) == 0) + return true; + + return false; +} + +int V4L2VideoDeviceTest::init() +{ + enumerator_ = DeviceEnumerator::create(); + if (!enumerator_) { + cerr << "Failed to create device enumerator" << endl; + return TestFail; + } + + if (enumerator_->enumerate()) { + cerr << "Failed to enumerate media devices" << endl; + return TestFail; + } + + DeviceMatch dm(driver_); + dm.add(entity_); + + media_ = enumerator_->search(dm); + if (!media_) + return TestSkip; + + MediaEntity *entity = media_->getEntityByName(entity_); + if (!entity) + return TestSkip; + + capture_ = new V4L2VideoDevice(entity); + if (!capture_) + return TestFail; + + if (!media_->acquire()) + return TestFail; + + int ret = media_->disableLinks(); + media_->release(); + if (ret) + return TestFail; + + if (capture_->open()) + return TestFail; + + V4L2DeviceFormat format = {}; + if (capture_->getFormat(&format)) + return TestFail; + + format.size.width = 640; + format.size.height = 480; + if (capture_->setFormat(&format)) + return TestFail; + + return TestPass; +} + +void V4L2VideoDeviceTest::cleanup() +{ + capture_->streamOff(); + capture_->releaseBuffers(); + capture_->close(); + + delete capture_; +}; diff --git a/test/v4l2_videodevice/v4l2_videodevice_test.h b/test/v4l2_videodevice/v4l2_videodevice_test.h new file mode 100644 index 00000000..3321b5a4 --- /dev/null +++ b/test/v4l2_videodevice/v4l2_videodevice_test.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2018, Google Inc. + * + * vl42device_test.h - libcamera v4l2device test base class + */ +#ifndef __LIBCAMERA_V4L2_DEVICE_TEST_H_ +#define __LIBCAMERA_V4L2_DEVICE_TEST_H_ + +#include + +#include + +#include "test.h" + +#include "device_enumerator.h" +#include "media_device.h" +#include "v4l2_videodevice.h" + +using namespace libcamera; + +class V4L2VideoDeviceTest : public Test +{ +public: + V4L2VideoDeviceTest(const char *driver, const char *entity) + : driver_(driver), entity_(entity), capture_(nullptr) + { + } + +protected: + int init(); + void cleanup(); + + std::string driver_; + std::string entity_; + std::unique_ptr enumerator_; + std::shared_ptr media_; + V4L2VideoDevice *capture_; + BufferPool pool_; +}; + +#endif /* __LIBCAMERA_V4L2_DEVICE_TEST_H_ */ -- cgit v1.2.1