From 5a8271ad70fb30460aa0aefb85fbaca484fceb01 Mon Sep 17 00:00:00 2001 From: Xavier Roumegue Date: Wed, 14 Dec 2022 16:52:18 +0100 Subject: libcamera: pipeline: simple: converter: Use generic converter interface Move the simple converter implementation to a generic V4L2 M2M class derived from the converter interface. This latter could be used by other pipeline implementations and as base class for customized V4L2 M2M converters. Signed-off-by: Xavier Roumegue Reviewed-by: Jacopo Mondi Reviewed-by: Kieran Bingham Signed-off-by: Kieran Bingham --- src/libcamera/converter/converter_v4l2_m2m.cpp | 453 +++++++++++++++++++++++++ 1 file changed, 453 insertions(+) create mode 100644 src/libcamera/converter/converter_v4l2_m2m.cpp (limited to 'src/libcamera/converter/converter_v4l2_m2m.cpp') diff --git a/src/libcamera/converter/converter_v4l2_m2m.cpp b/src/libcamera/converter/converter_v4l2_m2m.cpp new file mode 100644 index 00000000..2a4d1d99 --- /dev/null +++ b/src/libcamera/converter/converter_v4l2_m2m.cpp @@ -0,0 +1,453 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Laurent Pinchart + * Copyright 2022 NXP + * + * converter_v4l2_m2m.cpp - V4L2 M2M Format converter + */ + +#include "libcamera/internal/converter/converter_v4l2_m2m.h" + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "libcamera/internal/media_device.h" +#include "libcamera/internal/v4l2_videodevice.h" + +/** + * \file internal/converter/converter_v4l2_m2m.h + * \brief V4L2 M2M based converter + */ + +namespace libcamera { + +LOG_DECLARE_CATEGORY(Converter) + +/* ----------------------------------------------------------------------------- + * V4L2M2MConverter::Stream + */ + +V4L2M2MConverter::Stream::Stream(V4L2M2MConverter *converter, unsigned int index) + : converter_(converter), index_(index) +{ + m2m_ = std::make_unique(converter->deviceNode()); + + m2m_->output()->bufferReady.connect(this, &Stream::outputBufferReady); + m2m_->capture()->bufferReady.connect(this, &Stream::captureBufferReady); + + int ret = m2m_->open(); + if (ret < 0) + m2m_.reset(); +} + +int V4L2M2MConverter::Stream::configure(const StreamConfiguration &inputCfg, + const StreamConfiguration &outputCfg) +{ + V4L2PixelFormat videoFormat = + m2m_->output()->toV4L2PixelFormat(inputCfg.pixelFormat); + + V4L2DeviceFormat format; + format.fourcc = videoFormat; + format.size = inputCfg.size; + format.planesCount = 1; + format.planes[0].bpl = inputCfg.stride; + + int ret = m2m_->output()->setFormat(&format); + if (ret < 0) { + LOG(Converter, Error) + << "Failed to set input format: " << strerror(-ret); + return ret; + } + + if (format.fourcc != videoFormat || format.size != inputCfg.size || + format.planes[0].bpl != inputCfg.stride) { + LOG(Converter, Error) + << "Input format not supported (requested " + << inputCfg.size << "-" << videoFormat + << ", got " << format << ")"; + return -EINVAL; + } + + /* Set the pixel format and size on the output. */ + videoFormat = m2m_->capture()->toV4L2PixelFormat(outputCfg.pixelFormat); + format = {}; + format.fourcc = videoFormat; + format.size = outputCfg.size; + + ret = m2m_->capture()->setFormat(&format); + if (ret < 0) { + LOG(Converter, Error) + << "Failed to set output format: " << strerror(-ret); + return ret; + } + + if (format.fourcc != videoFormat || format.size != outputCfg.size) { + LOG(Converter, Error) + << "Output format not supported"; + return -EINVAL; + } + + inputBufferCount_ = inputCfg.bufferCount; + outputBufferCount_ = outputCfg.bufferCount; + + return 0; +} + +int V4L2M2MConverter::Stream::exportBuffers(unsigned int count, + std::vector> *buffers) +{ + return m2m_->capture()->exportBuffers(count, buffers); +} + +int V4L2M2MConverter::Stream::start() +{ + int ret = m2m_->output()->importBuffers(inputBufferCount_); + if (ret < 0) + return ret; + + ret = m2m_->capture()->importBuffers(outputBufferCount_); + if (ret < 0) { + stop(); + return ret; + } + + ret = m2m_->output()->streamOn(); + if (ret < 0) { + stop(); + return ret; + } + + ret = m2m_->capture()->streamOn(); + if (ret < 0) { + stop(); + return ret; + } + + return 0; +} + +void V4L2M2MConverter::Stream::stop() +{ + m2m_->capture()->streamOff(); + m2m_->output()->streamOff(); + m2m_->capture()->releaseBuffers(); + m2m_->output()->releaseBuffers(); +} + +int V4L2M2MConverter::Stream::queueBuffers(FrameBuffer *input, FrameBuffer *output) +{ + int ret = m2m_->output()->queueBuffer(input); + if (ret < 0) + return ret; + + ret = m2m_->capture()->queueBuffer(output); + if (ret < 0) + return ret; + + return 0; +} + +std::string V4L2M2MConverter::Stream::logPrefix() const +{ + return "stream" + std::to_string(index_); +} + +void V4L2M2MConverter::Stream::outputBufferReady(FrameBuffer *buffer) +{ + auto it = converter_->queue_.find(buffer); + if (it == converter_->queue_.end()) + return; + + if (!--it->second) { + converter_->inputBufferReady.emit(buffer); + converter_->queue_.erase(it); + } +} + +void V4L2M2MConverter::Stream::captureBufferReady(FrameBuffer *buffer) +{ + converter_->outputBufferReady.emit(buffer); +} + +/* ----------------------------------------------------------------------------- + * V4L2M2MConverter + */ + +/** + * \class libcamera::V4L2M2MConverter + * \brief The V4L2 M2M converter implements the converter interface based on + * V4L2 M2M device. +*/ + +/** + * \fn V4L2M2MConverter::V4L2M2MConverter + * \brief Construct a V4L2M2MConverter instance + * \param[in] media The media device implementing the converter + */ + +V4L2M2MConverter::V4L2M2MConverter(MediaDevice *media) + : Converter(media) +{ + if (deviceNode().empty()) + return; + + m2m_ = std::make_unique(deviceNode()); + int ret = m2m_->open(); + if (ret < 0) { + m2m_.reset(); + return; + } +} + +/** + * \fn libcamera::V4L2M2MConverter::loadConfiguration + * \details \copydetails libcamera::Converter::loadConfiguration + */ + +/** + * \fn libcamera::V4L2M2MConverter::isValid + * \details \copydetails libcamera::Converter::isValid + */ + +/** + * \fn libcamera::V4L2M2MConverter::formats + * \details \copydetails libcamera::Converter::formats + */ +std::vector V4L2M2MConverter::formats(PixelFormat input) +{ + if (!m2m_) + return {}; + + /* + * Set the format on the input side (V4L2 output) of the converter to + * enumerate the conversion capabilities on its output (V4L2 capture). + */ + V4L2DeviceFormat v4l2Format; + v4l2Format.fourcc = m2m_->output()->toV4L2PixelFormat(input); + v4l2Format.size = { 1, 1 }; + + int ret = m2m_->output()->setFormat(&v4l2Format); + if (ret < 0) { + LOG(Converter, Error) + << "Failed to set format: " << strerror(-ret); + return {}; + } + + if (v4l2Format.fourcc != m2m_->output()->toV4L2PixelFormat(input)) { + LOG(Converter, Debug) + << "Input format " << input << " not supported."; + return {}; + } + + std::vector pixelFormats; + + for (const auto &format : m2m_->capture()->formats()) { + PixelFormat pixelFormat = format.first.toPixelFormat(); + if (pixelFormat) + pixelFormats.push_back(pixelFormat); + } + + return pixelFormats; +} + +/** + * \copydoc libcamera::Converter::sizes + */ +SizeRange V4L2M2MConverter::sizes(const Size &input) +{ + if (!m2m_) + return {}; + + /* + * Set the size on the input side (V4L2 output) of the converter to + * enumerate the scaling capabilities on its output (V4L2 capture). + */ + V4L2DeviceFormat format; + format.fourcc = V4L2PixelFormat(); + format.size = input; + + int ret = m2m_->output()->setFormat(&format); + if (ret < 0) { + LOG(Converter, Error) + << "Failed to set format: " << strerror(-ret); + return {}; + } + + SizeRange sizes; + + format.size = { 1, 1 }; + ret = m2m_->capture()->setFormat(&format); + if (ret < 0) { + LOG(Converter, Error) + << "Failed to set format: " << strerror(-ret); + return {}; + } + + sizes.min = format.size; + + format.size = { UINT_MAX, UINT_MAX }; + ret = m2m_->capture()->setFormat(&format); + if (ret < 0) { + LOG(Converter, Error) + << "Failed to set format: " << strerror(-ret); + return {}; + } + + sizes.max = format.size; + + return sizes; +} + +/** + * \copydoc libcamera::Converter::strideAndFrameSize + */ +std::tuple +V4L2M2MConverter::strideAndFrameSize(const PixelFormat &pixelFormat, + const Size &size) +{ + V4L2DeviceFormat format; + format.fourcc = m2m_->capture()->toV4L2PixelFormat(pixelFormat); + format.size = size; + + int ret = m2m_->capture()->tryFormat(&format); + if (ret < 0) + return std::make_tuple(0, 0); + + return std::make_tuple(format.planes[0].bpl, format.planes[0].size); +} + +/** + * \copydoc libcamera::Converter::configure + */ +int V4L2M2MConverter::configure(const StreamConfiguration &inputCfg, + const std::vector> &outputCfgs) +{ + int ret = 0; + + streams_.clear(); + streams_.reserve(outputCfgs.size()); + + for (unsigned int i = 0; i < outputCfgs.size(); ++i) { + Stream &stream = streams_.emplace_back(this, i); + + if (!stream.isValid()) { + LOG(Converter, Error) + << "Failed to create stream " << i; + ret = -EINVAL; + break; + } + + ret = stream.configure(inputCfg, outputCfgs[i]); + if (ret < 0) + break; + } + + if (ret < 0) { + streams_.clear(); + return ret; + } + + return 0; +} + +/** + * \copydoc libcamera::Converter::exportBuffers + */ +int V4L2M2MConverter::exportBuffers(unsigned int output, unsigned int count, + std::vector> *buffers) +{ + if (output >= streams_.size()) + return -EINVAL; + + return streams_[output].exportBuffers(count, buffers); +} + +/** + * \copydoc libcamera::Converter::start + */ +int V4L2M2MConverter::start() +{ + int ret; + + for (Stream &stream : streams_) { + ret = stream.start(); + if (ret < 0) { + stop(); + return ret; + } + } + + return 0; +} + +/** + * \copydoc libcamera::Converter::stop + */ +void V4L2M2MConverter::stop() +{ + for (Stream &stream : utils::reverse(streams_)) + stream.stop(); +} + +/** + * \copydoc libcamera::Converter::queueBuffers + */ +int V4L2M2MConverter::queueBuffers(FrameBuffer *input, + const std::map &outputs) +{ + unsigned int mask = 0; + int ret; + + /* + * Validate the outputs as a sanity check: at least one output is + * required, all outputs must reference a valid stream and no two + * outputs can reference the same stream. + */ + if (outputs.empty()) + return -EINVAL; + + for (auto [index, buffer] : outputs) { + if (!buffer) + return -EINVAL; + if (index >= streams_.size()) + return -EINVAL; + if (mask & (1 << index)) + return -EINVAL; + + mask |= 1 << index; + } + + /* Queue the input and output buffers to all the streams. */ + for (auto [index, buffer] : outputs) { + ret = streams_[index].queueBuffers(input, buffer); + if (ret < 0) + return ret; + } + + /* + * Add the input buffer to the queue, with the number of streams as a + * reference count. Completion of the input buffer will be signalled by + * the stream that releases the last reference. + */ + queue_.emplace(std::piecewise_construct, + std::forward_as_tuple(input), + std::forward_as_tuple(outputs.size())); + + return 0; +} + +static std::initializer_list compatibles = { + "pxp", +}; + +REGISTER_CONVERTER("v4l2_m2m", V4L2M2MConverter, compatibles) + +} /* namespace libcamera */ -- cgit v1.2.1