Message ID | 20200404004438.17992-10-laurent.pinchart@ideasonboard.com |
---|---|
State | Superseded |
Headers | show |
Series |
|
Related | show |
Hi Laurent, Thanks for your work. On 2020-04-04 03:44:36 +0300, Laurent Pinchart wrote: > From: Martijn Braam <martijn@brixit.nl> > > This new pipeline handler aims at supporting any simple device without > requiring any device-specific code. Simple devices are currently defined > as a graph made of one or multiple camera sensors and a single video > node, with each sensor connected to the video node through a linear > pipeline. > > The simple pipeline handler will automatically parse the media graph, > enumerate sensors, build supported stream configurations, and configure > the pipeline, without any device-specific knowledge. It doesn't support > configuration of any processing in the pipeline at the moment, but may > be extended to support simple processing such as format conversion or > scaling in the future. > > The only device-specific information in the pipeline handler is the list > of supported drivers, required for device matching. We may be able to > remove this in the future by matching with the simple pipeline handler > as a last resort option, after all other pipeline handlers have been > tried. > > Signed-off-by: Martijn Braam <martijn@brixit.nl> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se> > --- > Changes since v3: > > - Fix typos > - Improve comments > - Add local variable to increase readability > - Move list of supported drivers to the top of the file > > Changes since v2: > > - Log an error when setupFormats() fail > - Propagate getFormat() and setFormat() errors to the caller of > setupFormats() > - Reorder variable declarations in validate() > - Add \todo comment related to the selection of the default format > - Use log Error instead of Info if pipeline isn't valid > - Rebase on top of V4L2PixelFormat > > Changes since v1: > > - Rebase on top of buffer API rework > - Expose stream formats > - Rework camera data config > --- > meson_options.txt | 2 +- > src/libcamera/pipeline/simple/meson.build | 3 + > src/libcamera/pipeline/simple/simple.cpp | 717 ++++++++++++++++++++++ > 3 files changed, 721 insertions(+), 1 deletion(-) > create mode 100644 src/libcamera/pipeline/simple/meson.build > create mode 100644 src/libcamera/pipeline/simple/simple.cpp > > diff --git a/meson_options.txt b/meson_options.txt > index 6464df837cc3..166429f8583e 100644 > --- a/meson_options.txt > +++ b/meson_options.txt > @@ -14,7 +14,7 @@ option('gstreamer', > > option('pipelines', > type : 'array', > - choices : ['ipu3', 'rkisp1', 'uvcvideo', 'vimc'], > + choices : ['ipu3', 'rkisp1', 'simple', 'uvcvideo', 'vimc'], > description : 'Select which pipeline handlers to include') > > option('test', > diff --git a/src/libcamera/pipeline/simple/meson.build b/src/libcamera/pipeline/simple/meson.build > new file mode 100644 > index 000000000000..4945a3e173cf > --- /dev/null > +++ b/src/libcamera/pipeline/simple/meson.build > @@ -0,0 +1,3 @@ > +libcamera_sources += files([ > + 'simple.cpp', > +]) > diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp > new file mode 100644 > index 000000000000..e4f33f6ff531 > --- /dev/null > +++ b/src/libcamera/pipeline/simple/simple.cpp > @@ -0,0 +1,717 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2020, Laurent Pinchart > + * Copyright (C) 2019, Martijn Braam > + * > + * simple.cpp - Pipeline handler for simple pipelines > + */ > + > +#include <algorithm> > +#include <iterator> > +#include <list> > +#include <map> > +#include <memory> > +#include <set> > +#include <string> > +#include <string.h> > +#include <utility> > +#include <vector> > + > +#include <linux/media-bus-format.h> > + > +#include <libcamera/camera.h> > +#include <libcamera/request.h> > +#include <libcamera/stream.h> > + > +#include "camera_sensor.h" > +#include "device_enumerator.h" > +#include "log.h" > +#include "media_device.h" > +#include "pipeline_handler.h" > +#include "v4l2_subdevice.h" > +#include "v4l2_videodevice.h" > + > +namespace libcamera { > + > +LOG_DEFINE_CATEGORY(SimplePipeline) > + > +class SimplePipelineHandler; > + > +namespace { > + > +static const char * const drivers[] = { > + "imx7-csi", > + "sun6i-csi", > +}; > + > +} /* namespace */ > + > +class SimpleCameraData : public CameraData > +{ > +public: > + SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor, > + MediaEntity *video); > + > + bool isValid() const { return sensor_ != nullptr; } > + std::set<Stream *> streams() { return { &stream_ }; } > + > + int init(); > + int setupLinks(); > + int setupFormats(V4L2SubdeviceFormat *format, > + V4L2Subdevice::Whence whence); > + > + struct Entity { > + MediaEntity *entity; > + MediaLink *link; > + }; > + > + struct Configuration { > + uint32_t code; > + PixelFormat pixelFormat; > + Size size; > + }; > + > + Stream stream_; > + std::unique_ptr<CameraSensor> sensor_; > + std::list<Entity> entities_; > + > + std::vector<Configuration> configs_; > + std::map<PixelFormat, Configuration> formats_; > +}; > + > +class SimpleCameraConfiguration : public CameraConfiguration > +{ > +public: > + SimpleCameraConfiguration(Camera *camera, SimpleCameraData *data); > + > + Status validate() override; > + > + const V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; } > + > +private: > + /* > + * The SimpleCameraData instance is guaranteed to be valid as long as > + * the corresponding Camera instance is valid. In order to borrow a > + * reference to the camera data, store a new reference to the camera. > + */ > + std::shared_ptr<Camera> camera_; > + const SimpleCameraData *data_; > + > + V4L2SubdeviceFormat sensorFormat_; > +}; > + > +class SimplePipelineHandler : public PipelineHandler > +{ > +public: > + SimplePipelineHandler(CameraManager *manager); > + ~SimplePipelineHandler(); > + > + CameraConfiguration *generateConfiguration(Camera *camera, > + const StreamRoles &roles) override; > + int configure(Camera *camera, CameraConfiguration *config) override; > + > + int exportFrameBuffers(Camera *camera, Stream *stream, > + std::vector<std::unique_ptr<FrameBuffer>> *buffers) override; > + > + int start(Camera *camera) override; > + void stop(Camera *camera) override; > + > + bool match(DeviceEnumerator *enumerator) override; > + > + V4L2VideoDevice *video() { return video_; } > + V4L2Subdevice *subdev(const MediaEntity *entity); > + > +protected: > + int queueRequestDevice(Camera *camera, Request *request) override; > + > +private: > + SimpleCameraData *cameraData(const Camera *camera) > + { > + return static_cast<SimpleCameraData *>( > + PipelineHandler::cameraData(camera)); > + } > + > + int initLinks(); > + > + int createCamera(MediaEntity *sensor); > + > + void bufferReady(FrameBuffer *buffer); > + > + MediaDevice *media_; > + V4L2VideoDevice *video_; > + std::map<const MediaEntity *, V4L2Subdevice> subdevs_; > + > + Camera *activeCamera_; > +}; > + > +/* ----------------------------------------------------------------------------- > + * Camera Data > + */ > + > +SimpleCameraData::SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor, > + MediaEntity *video) > + : CameraData(pipe) > +{ > + int ret; > + > + /* > + * Walk the pipeline towards the video node and store all entities > + * along the way. > + */ > + MediaEntity *source = sensor; > + > + while (source) { > + /* If we have reached the video node, we're done. */ > + if (source == video) > + break; > + > + /* Use the first output pad that has links. */ > + MediaPad *sourcePad = nullptr; > + for (MediaPad *pad : source->pads()) { > + if ((pad->flags() & MEDIA_PAD_FL_SOURCE) && > + !pad->links().empty()) { > + sourcePad = pad; > + break; > + } > + } > + > + if (!sourcePad) > + return; > + > + /* Use the first link that isn't immutable and disabled. */ > + MediaLink *sourceLink = nullptr; > + for (MediaLink *link : sourcePad->links()) { > + if ((link->flags() & MEDIA_LNK_FL_ENABLED) || > + !(link->flags() & MEDIA_LNK_FL_IMMUTABLE)) { > + sourceLink = link; > + break; > + } > + } > + > + if (!sourceLink) > + return; > + > + entities_.push_back({ source, sourceLink }); > + > + source = sourceLink->sink()->entity(); > + > + /* Avoid infinite loops. */ > + auto iter = std::find_if(entities_.begin(), entities_.end(), > + [&](const Entity &entity) { > + return entity.entity == source; > + }); > + if (iter != entities_.end()) { > + LOG(SimplePipeline, Info) << "Loop detected in pipeline"; > + return; > + } > + } > + > + /* We have a valid pipeline, create the camera sensor. */ > + sensor_ = std::make_unique<CameraSensor>(sensor); > + ret = sensor_->init(); > + if (ret) { > + sensor_.reset(); > + return; > + } > +} > + > +int SimpleCameraData::init() > +{ > + SimplePipelineHandler *pipe = static_cast<SimplePipelineHandler *>(pipe_); > + V4L2VideoDevice *video = pipe->video(); > + int ret; > + > + /* > + * Enumerate the possible pipeline configurations. For each media bus > + * format supported by the sensor, propagate the formats through the > + * pipeline, and enumerate the corresponding possible V4L2 pixel > + * formats on the video node. > + */ > + for (unsigned int code : sensor_->mbusCodes()) { > + V4L2SubdeviceFormat format{ code, sensor_->resolution() }; > + > + /* > + * Setup links first as some subdev drivers take active links > + * into account to propagate TRY formats. Such is life :-( > + */ > + ret = setupLinks(); > + if (ret < 0) > + return ret; > + > + ret = setupFormats(&format, V4L2Subdevice::TryFormat); > + if (ret < 0) { > + LOG(SimplePipeline, Error) > + << "Failed to setup pipeline for media bus code " > + << utils::hex(code, 4); > + return ret; > + } > + > + std::map<V4L2PixelFormat, std::vector<SizeRange>> videoFormats = > + video->formats(format.mbus_code); > + > + LOG(SimplePipeline, Debug) > + << "Adding configuration for " << format.size.toString() > + << " in pixel formats [ " > + << utils::join(videoFormats, ", ", > + [](const auto &f) { > + return f.first.toString(); > + }) > + << " ]"; > + > + /* > + * Store the configuration in the formats_ map, mapping the > + * PixelFormat to the corresponding configuration. Any > + * previously stored value is overwritten, as the pipeline > + * handler currently doesn't care about how a particular > + * PixelFormat is achieved. > + */ > + for (const auto &videoFormat : videoFormats) { > + PixelFormat pixelFormat = video->toPixelFormat(videoFormat.first); > + if (!pixelFormat) > + continue; > + > + Configuration config; > + config.code = code; > + config.pixelFormat = pixelFormat; > + config.size = format.size; > + > + formats_[pixelFormat] = config; > + } > + } > + > + if (formats_.empty()) { > + LOG(SimplePipeline, Error) << "No valid configuration found"; > + return -EINVAL; > + } > + > + return 0; > +} > + > +int SimpleCameraData::setupLinks() > +{ > + int ret; > + > + /* > + * Configure all links along the pipeline. Some entities may not allow > + * multiple sink links to be enabled together, even on different sink > + * pads. We must thus start by disabling all sink links (but the one we > + * want to enable) before enabling the pipeline link. > + */ > + for (SimpleCameraData::Entity &e : entities_) { > + MediaEntity *remote = e.link->sink()->entity(); > + for (MediaPad *pad : remote->pads()) { > + for (MediaLink *link : pad->links()) { > + if (link == e.link) > + continue; > + > + if ((link->flags() & MEDIA_LNK_FL_ENABLED) && > + !(link->flags() & MEDIA_LNK_FL_IMMUTABLE)) { > + ret = link->setEnabled(false); > + if (ret < 0) > + return ret; > + } > + } > + } > + > + if (!(e.link->flags() & MEDIA_LNK_FL_ENABLED)) { > + ret = e.link->setEnabled(true); > + if (ret < 0) > + return ret; > + } > + } > + > + return 0; > +} > + > +int SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format, > + V4L2Subdevice::Whence whence) > +{ > + SimplePipelineHandler *pipe = static_cast<SimplePipelineHandler *>(pipe_); > + int ret; > + > + /* > + * Configure the format on the sensor output and propagate it through > + * the pipeline. > + */ > + ret = sensor_->setFormat(format); > + if (ret < 0) > + return ret; > + > + for (const Entity &e : entities_) { > + MediaLink *link = e.link; > + MediaPad *source = link->source(); > + MediaPad *sink = link->sink(); > + > + if (source->entity() != sensor_->entity()) { > + V4L2Subdevice *subdev = pipe->subdev(source->entity()); > + ret = subdev->getFormat(source->index(), format, whence); > + if (ret < 0) > + return ret; > + } > + > + if (sink->entity()->function() != MEDIA_ENT_F_IO_V4L) { > + V4L2Subdevice *subdev = pipe->subdev(sink->entity()); > + ret = subdev->setFormat(sink->index(), format, whence); > + if (ret < 0) > + return ret; > + } > + > + LOG(SimplePipeline, Debug) > + << "Link '" << source->entity()->name() > + << "':" << source->index() > + << " -> '" << sink->entity()->name() > + << "':" << sink->index() > + << " configured with format " << format->toString(); > + } > + > + return 0; > +} > + > +/* ----------------------------------------------------------------------------- > + * Camera Configuration > + */ > + > +SimpleCameraConfiguration::SimpleCameraConfiguration(Camera *camera, > + SimpleCameraData *data) > + : CameraConfiguration(), camera_(camera->shared_from_this()), > + data_(data) > +{ > +} > + > +CameraConfiguration::Status SimpleCameraConfiguration::validate() > +{ > + Status status = Valid; > + > + if (config_.empty()) > + return Invalid; > + > + /* Cap the number of entries to the available streams. */ > + if (config_.size() > 1) { > + config_.resize(1); > + status = Adjusted; > + } > + > + StreamConfiguration &cfg = config_[0]; > + > + /* Adjust the pixel format. */ > + auto it = data_->formats_.find(cfg.pixelFormat); > + if (it == data_->formats_.end()) > + it = data_->formats_.begin(); > + > + PixelFormat pixelFormat = it->first; > + if (cfg.pixelFormat != pixelFormat) { > + LOG(SimplePipeline, Debug) << "Adjusting pixel format"; > + cfg.pixelFormat = pixelFormat; > + status = Adjusted; > + } > + > + const SimpleCameraData::Configuration &pipeConfig = it->second; > + if (cfg.size != pipeConfig.size) { > + LOG(SimplePipeline, Debug) > + << "Adjusting size from " << cfg.size.toString() > + << " to " << pipeConfig.size.toString(); > + cfg.size = pipeConfig.size; > + status = Adjusted; > + } > + > + cfg.bufferCount = 3; > + > + return status; > +} > + > +/* ----------------------------------------------------------------------------- > + * Pipeline Handler > + */ > + > +SimplePipelineHandler::SimplePipelineHandler(CameraManager *manager) > + : PipelineHandler(manager), video_(nullptr) > +{ > +} > + > +SimplePipelineHandler::~SimplePipelineHandler() > +{ > + delete video_; > +} > + > +CameraConfiguration *SimplePipelineHandler::generateConfiguration(Camera *camera, > + const StreamRoles &roles) > +{ > + SimpleCameraData *data = cameraData(camera); > + CameraConfiguration *config = > + new SimpleCameraConfiguration(camera, data); > + > + if (roles.empty()) > + return config; > + > + /* Create the formats map. */ > + std::map<PixelFormat, std::vector<SizeRange>> formats; > + std::transform(data->formats_.begin(), data->formats_.end(), > + std::inserter(formats, formats.end()), > + [](const auto &format) -> decltype(formats)::value_type { > + const PixelFormat &pixelFormat = format.first; > + const Size &size = format.second.size; > + return { pixelFormat, { size } }; > + }); > + > + /* > + * Create the stream configuration. Take the first entry in the formats > + * map as the default, for lack of a better option. > + * > + * \todo Implement a better way to pick the default format > + */ > + StreamConfiguration cfg{ StreamFormats{ formats } }; > + cfg.pixelFormat = formats.begin()->first; > + cfg.size = formats.begin()->second[0].max; > + > + config->addConfiguration(cfg); > + > + config->validate(); > + > + return config; > +} > + > +int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c) > +{ > + SimpleCameraConfiguration *config = > + static_cast<SimpleCameraConfiguration *>(c); > + SimpleCameraData *data = cameraData(camera); > + StreamConfiguration &cfg = config->at(0); > + int ret; > + > + /* > + * Configure links on the pipeline and propagate formats from the > + * sensor to the video node. > + */ > + ret = data->setupLinks(); > + if (ret < 0) > + return ret; > + > + const SimpleCameraData::Configuration &pipeConfig = > + data->formats_[cfg.pixelFormat]; > + > + V4L2SubdeviceFormat format{ pipeConfig.code, data->sensor_->resolution() }; > + > + ret = data->setupFormats(&format, V4L2Subdevice::ActiveFormat); > + if (ret < 0) > + return ret; > + > + /* Configure the video node. */ > + V4L2PixelFormat videoFormat = video_->toV4L2PixelFormat(cfg.pixelFormat); > + > + V4L2DeviceFormat outputFormat = {}; > + outputFormat.fourcc = videoFormat; > + outputFormat.size = cfg.size; > + > + ret = video_->setFormat(&outputFormat); > + if (ret) > + return ret; > + > + if (outputFormat.size != cfg.size || outputFormat.fourcc != videoFormat) { > + LOG(SimplePipeline, Error) > + << "Unable to configure capture in " << cfg.toString(); > + return -EINVAL; > + } > + > + cfg.setStream(&data->stream_); > + > + return 0; > +} > + > +int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream, > + std::vector<std::unique_ptr<FrameBuffer>> *buffers) > +{ > + unsigned int count = stream->configuration().bufferCount; > + > + return video_->exportBuffers(count, buffers); > +} > + > +int SimplePipelineHandler::start(Camera *camera) > +{ > + SimpleCameraData *data = cameraData(camera); > + unsigned int count = data->stream_.configuration().bufferCount; > + > + int ret = video_->importBuffers(count); > + if (ret < 0) > + return ret; > + > + ret = video_->streamOn(); > + if (ret < 0) { > + video_->releaseBuffers(); > + return ret; > + } > + > + activeCamera_ = camera; > + > + return 0; > +} > + > +void SimplePipelineHandler::stop(Camera *camera) > +{ > + video_->streamOff(); > + video_->releaseBuffers(); > + activeCamera_ = nullptr; > +} > + > +int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request) > +{ > + SimpleCameraData *data = cameraData(camera); > + Stream *stream = &data->stream_; > + > + FrameBuffer *buffer = request->findBuffer(stream); > + if (!buffer) { > + LOG(SimplePipeline, Error) > + << "Attempt to queue request with invalid stream"; > + return -ENOENT; > + } > + > + return video_->queueBuffer(buffer); > +} > + > +/* ----------------------------------------------------------------------------- > + * Match and Setup > + */ > + > +bool SimplePipelineHandler::match(DeviceEnumerator *enumerator) > +{ > + for (const char *driver : drivers) { > + DeviceMatch dm(driver); > + media_ = acquireMediaDevice(enumerator, dm); > + if (media_) > + break; > + } > + > + if (!media_) > + return false; > + > + /* > + * Locate sensors and video nodes. We only support pipelines with at > + * least one sensor and exactly one video capture node. > + */ > + std::vector<MediaEntity *> sensors; > + std::vector<MediaEntity *> videos; > + > + for (MediaEntity *entity : media_->entities()) { > + switch (entity->function()) { > + case MEDIA_ENT_F_CAM_SENSOR: > + sensors.push_back(entity); > + break; > + > + case MEDIA_ENT_F_IO_V4L: > + if (entity->pads().size() == 1 && > + (entity->pads()[0]->flags() & MEDIA_PAD_FL_SINK)) > + videos.push_back(entity); > + break; > + > + default: > + break; > + } > + } > + > + if (sensors.empty()) { > + LOG(SimplePipeline, Error) << "No sensor found"; > + return false; > + } > + > + if (videos.size() != 1) { > + LOG(SimplePipeline, Error) > + << "Pipeline with " << videos.size() > + << " video capture nodes is not supported"; > + return false; > + } > + > + /* Locate and open the capture video node. */ > + video_ = new V4L2VideoDevice(videos[0]); > + if (video_->open() < 0) > + return false; > + > + if (video_->caps().isMultiplanar()) { > + LOG(SimplePipeline, Error) > + << "V4L2 multiplanar devices are not supported"; > + return false; > + } > + > + video_->bufferReady.connect(this, &SimplePipelineHandler::bufferReady); > + > + /* > + * Create one camera data instance for each sensor and gather all > + * entities in all pipelines. > + */ > + std::vector<std::unique_ptr<SimpleCameraData>> pipelines; > + std::set<MediaEntity *> entities; > + > + pipelines.reserve(sensors.size()); > + > + for (MediaEntity *sensor : sensors) { > + std::unique_ptr<SimpleCameraData> data = > + std::make_unique<SimpleCameraData>(this, sensor, > + videos[0]); > + if (!data->isValid()) { > + LOG(SimplePipeline, Error) > + << "No valid pipeline for sensor '" > + << sensor->name() << "', skipping"; > + continue; > + } > + > + for (SimpleCameraData::Entity &entity : data->entities_) > + entities.insert(entity.entity); > + > + pipelines.push_back(std::move(data)); > + } > + > + if (entities.empty()) > + return false; > + > + /* Create and open V4L2Subdev instances for all the entities. */ > + for (MediaEntity *entity : entities) { > + auto elem = subdevs_.emplace(std::piecewise_construct, > + std::forward_as_tuple(entity), > + std::forward_as_tuple(entity)); > + V4L2Subdevice *subdev = &elem.first->second; > + int ret = subdev->open(); > + if (ret < 0) { > + LOG(SimplePipeline, Error) > + << "Failed to open " << subdev->deviceNode() > + << ": " << strerror(-ret); > + return false; > + } > + } > + > + /* Initialize each pipeline and register a corresponding camera. */ > + for (std::unique_ptr<SimpleCameraData> &data : pipelines) { > + int ret = data->init(); > + if (ret < 0) > + continue; > + > + std::shared_ptr<Camera> camera = > + Camera::create(this, data->sensor_->entity()->name(), > + data->streams()); > + registerCamera(std::move(camera), std::move(data)); > + } > + > + return true; > +} > + > +V4L2Subdevice *SimplePipelineHandler::subdev(const MediaEntity *entity) > +{ > + auto iter = subdevs_.find(entity); > + if (iter == subdevs_.end()) > + return nullptr; > + > + return &iter->second; > +} > + > +/* ----------------------------------------------------------------------------- > + * Buffer Handling > + */ > + > +void SimplePipelineHandler::bufferReady(FrameBuffer *buffer) > +{ > + ASSERT(activeCamera_); > + Request *request = buffer->request(); > + completeBuffer(activeCamera_, request, buffer); > + completeRequest(activeCamera_, request); > +} > + > +REGISTER_PIPELINE_HANDLER(SimplePipelineHandler); > + > +} /* namespace libcamera */ > -- > Regards, > > Laurent Pinchart > > _______________________________________________ > libcamera-devel mailing list > libcamera-devel@lists.libcamera.org > https://lists.libcamera.org/listinfo/libcamera-devel
Hi Laurent, Thanks for your work! On 21.04.2020 18:47, Niklas Söderlund wrote: > Hi Laurent, > > Thanks for your work. > > On 2020-04-04 03:44:36 +0300, Laurent Pinchart wrote: >> From: Martijn Braam <martijn@brixit.nl> >> >> This new pipeline handler aims at supporting any simple device without >> requiring any device-specific code. Simple devices are currently defined >> as a graph made of one or multiple camera sensors and a single video >> node, with each sensor connected to the video node through a linear >> pipeline. >> >> The simple pipeline handler will automatically parse the media graph, >> enumerate sensors, build supported stream configurations, and configure >> the pipeline, without any device-specific knowledge. It doesn't support >> configuration of any processing in the pipeline at the moment, but may >> be extended to support simple processing such as format conversion or >> scaling in the future. >> >> The only device-specific information in the pipeline handler is the list >> of supported drivers, required for device matching. We may be able to >> remove this in the future by matching with the simple pipeline handler >> as a last resort option, after all other pipeline handlers have been >> tried. >> >> Signed-off-by: Martijn Braam <martijn@brixit.nl> >> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> >> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > > Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se> Reviewed-by: Andrey Konovalov <andrey.konovalov@linaro.org> >> --- >> Changes since v3: >> >> - Fix typos >> - Improve comments >> - Add local variable to increase readability >> - Move list of supported drivers to the top of the file >> >> Changes since v2: >> >> - Log an error when setupFormats() fail >> - Propagate getFormat() and setFormat() errors to the caller of >> setupFormats() >> - Reorder variable declarations in validate() >> - Add \todo comment related to the selection of the default format >> - Use log Error instead of Info if pipeline isn't valid >> - Rebase on top of V4L2PixelFormat >> >> Changes since v1: >> >> - Rebase on top of buffer API rework >> - Expose stream formats >> - Rework camera data config >> --- >> meson_options.txt | 2 +- >> src/libcamera/pipeline/simple/meson.build | 3 + >> src/libcamera/pipeline/simple/simple.cpp | 717 ++++++++++++++++++++++ >> 3 files changed, 721 insertions(+), 1 deletion(-) >> create mode 100644 src/libcamera/pipeline/simple/meson.build >> create mode 100644 src/libcamera/pipeline/simple/simple.cpp >> >> diff --git a/meson_options.txt b/meson_options.txt >> index 6464df837cc3..166429f8583e 100644 >> --- a/meson_options.txt >> +++ b/meson_options.txt >> @@ -14,7 +14,7 @@ option('gstreamer', >> >> option('pipelines', >> type : 'array', >> - choices : ['ipu3', 'rkisp1', 'uvcvideo', 'vimc'], >> + choices : ['ipu3', 'rkisp1', 'simple', 'uvcvideo', 'vimc'], >> description : 'Select which pipeline handlers to include') >> >> option('test', >> diff --git a/src/libcamera/pipeline/simple/meson.build b/src/libcamera/pipeline/simple/meson.build >> new file mode 100644 >> index 000000000000..4945a3e173cf >> --- /dev/null >> +++ b/src/libcamera/pipeline/simple/meson.build >> @@ -0,0 +1,3 @@ >> +libcamera_sources += files([ >> + 'simple.cpp', >> +]) >> diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp >> new file mode 100644 >> index 000000000000..e4f33f6ff531 >> --- /dev/null >> +++ b/src/libcamera/pipeline/simple/simple.cpp >> @@ -0,0 +1,717 @@ >> +/* SPDX-License-Identifier: LGPL-2.1-or-later */ >> +/* >> + * Copyright (C) 2020, Laurent Pinchart >> + * Copyright (C) 2019, Martijn Braam >> + * >> + * simple.cpp - Pipeline handler for simple pipelines >> + */ >> + >> +#include <algorithm> >> +#include <iterator> >> +#include <list> >> +#include <map> >> +#include <memory> >> +#include <set> >> +#include <string> >> +#include <string.h> >> +#include <utility> >> +#include <vector> >> + >> +#include <linux/media-bus-format.h> >> + >> +#include <libcamera/camera.h> >> +#include <libcamera/request.h> >> +#include <libcamera/stream.h> >> + >> +#include "camera_sensor.h" >> +#include "device_enumerator.h" >> +#include "log.h" >> +#include "media_device.h" >> +#include "pipeline_handler.h" >> +#include "v4l2_subdevice.h" >> +#include "v4l2_videodevice.h" >> + >> +namespace libcamera { >> + >> +LOG_DEFINE_CATEGORY(SimplePipeline) >> + >> +class SimplePipelineHandler; >> + >> +namespace { >> + >> +static const char * const drivers[] = { >> + "imx7-csi", >> + "sun6i-csi", >> +}; >> + >> +} /* namespace */ >> + >> +class SimpleCameraData : public CameraData >> +{ >> +public: >> + SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor, >> + MediaEntity *video); >> + >> + bool isValid() const { return sensor_ != nullptr; } >> + std::set<Stream *> streams() { return { &stream_ }; } >> + >> + int init(); >> + int setupLinks(); >> + int setupFormats(V4L2SubdeviceFormat *format, >> + V4L2Subdevice::Whence whence); >> + >> + struct Entity { >> + MediaEntity *entity; >> + MediaLink *link; >> + }; >> + >> + struct Configuration { >> + uint32_t code; >> + PixelFormat pixelFormat; >> + Size size; >> + }; >> + >> + Stream stream_; >> + std::unique_ptr<CameraSensor> sensor_; >> + std::list<Entity> entities_; >> + >> + std::vector<Configuration> configs_; >> + std::map<PixelFormat, Configuration> formats_; >> +}; >> + >> +class SimpleCameraConfiguration : public CameraConfiguration >> +{ >> +public: >> + SimpleCameraConfiguration(Camera *camera, SimpleCameraData *data); >> + >> + Status validate() override; >> + >> + const V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; } >> + >> +private: >> + /* >> + * The SimpleCameraData instance is guaranteed to be valid as long as >> + * the corresponding Camera instance is valid. In order to borrow a >> + * reference to the camera data, store a new reference to the camera. >> + */ >> + std::shared_ptr<Camera> camera_; >> + const SimpleCameraData *data_; >> + >> + V4L2SubdeviceFormat sensorFormat_; >> +}; >> + >> +class SimplePipelineHandler : public PipelineHandler >> +{ >> +public: >> + SimplePipelineHandler(CameraManager *manager); >> + ~SimplePipelineHandler(); >> + >> + CameraConfiguration *generateConfiguration(Camera *camera, >> + const StreamRoles &roles) override; >> + int configure(Camera *camera, CameraConfiguration *config) override; >> + >> + int exportFrameBuffers(Camera *camera, Stream *stream, >> + std::vector<std::unique_ptr<FrameBuffer>> *buffers) override; >> + >> + int start(Camera *camera) override; >> + void stop(Camera *camera) override; >> + >> + bool match(DeviceEnumerator *enumerator) override; >> + >> + V4L2VideoDevice *video() { return video_; } >> + V4L2Subdevice *subdev(const MediaEntity *entity); >> + >> +protected: >> + int queueRequestDevice(Camera *camera, Request *request) override; >> + >> +private: >> + SimpleCameraData *cameraData(const Camera *camera) >> + { >> + return static_cast<SimpleCameraData *>( >> + PipelineHandler::cameraData(camera)); >> + } >> + >> + int initLinks(); >> + >> + int createCamera(MediaEntity *sensor); >> + >> + void bufferReady(FrameBuffer *buffer); >> + >> + MediaDevice *media_; >> + V4L2VideoDevice *video_; >> + std::map<const MediaEntity *, V4L2Subdevice> subdevs_; >> + >> + Camera *activeCamera_; >> +}; >> + >> +/* ----------------------------------------------------------------------------- >> + * Camera Data >> + */ >> + >> +SimpleCameraData::SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor, >> + MediaEntity *video) >> + : CameraData(pipe) >> +{ >> + int ret; >> + >> + /* >> + * Walk the pipeline towards the video node and store all entities >> + * along the way. >> + */ >> + MediaEntity *source = sensor; >> + >> + while (source) { >> + /* If we have reached the video node, we're done. */ >> + if (source == video) >> + break; >> + >> + /* Use the first output pad that has links. */ >> + MediaPad *sourcePad = nullptr; >> + for (MediaPad *pad : source->pads()) { >> + if ((pad->flags() & MEDIA_PAD_FL_SOURCE) && >> + !pad->links().empty()) { >> + sourcePad = pad; >> + break; >> + } >> + } >> + >> + if (!sourcePad) >> + return; >> + >> + /* Use the first link that isn't immutable and disabled. */ >> + MediaLink *sourceLink = nullptr; >> + for (MediaLink *link : sourcePad->links()) { >> + if ((link->flags() & MEDIA_LNK_FL_ENABLED) || >> + !(link->flags() & MEDIA_LNK_FL_IMMUTABLE)) { >> + sourceLink = link; >> + break; >> + } >> + } >> + >> + if (!sourceLink) >> + return; >> + >> + entities_.push_back({ source, sourceLink }); >> + >> + source = sourceLink->sink()->entity(); >> + >> + /* Avoid infinite loops. */ >> + auto iter = std::find_if(entities_.begin(), entities_.end(), >> + [&](const Entity &entity) { >> + return entity.entity == source; >> + }); >> + if (iter != entities_.end()) { >> + LOG(SimplePipeline, Info) << "Loop detected in pipeline"; >> + return; >> + } >> + } >> + >> + /* We have a valid pipeline, create the camera sensor. */ >> + sensor_ = std::make_unique<CameraSensor>(sensor); >> + ret = sensor_->init(); >> + if (ret) { >> + sensor_.reset(); >> + return; >> + } >> +} >> + >> +int SimpleCameraData::init() >> +{ >> + SimplePipelineHandler *pipe = static_cast<SimplePipelineHandler *>(pipe_); >> + V4L2VideoDevice *video = pipe->video(); >> + int ret; >> + >> + /* >> + * Enumerate the possible pipeline configurations. For each media bus >> + * format supported by the sensor, propagate the formats through the >> + * pipeline, and enumerate the corresponding possible V4L2 pixel >> + * formats on the video node. >> + */ >> + for (unsigned int code : sensor_->mbusCodes()) { >> + V4L2SubdeviceFormat format{ code, sensor_->resolution() }; >> + >> + /* >> + * Setup links first as some subdev drivers take active links >> + * into account to propagate TRY formats. Such is life :-( >> + */ >> + ret = setupLinks(); >> + if (ret < 0) >> + return ret; >> + >> + ret = setupFormats(&format, V4L2Subdevice::TryFormat); >> + if (ret < 0) { >> + LOG(SimplePipeline, Error) >> + << "Failed to setup pipeline for media bus code " >> + << utils::hex(code, 4); >> + return ret; >> + } >> + >> + std::map<V4L2PixelFormat, std::vector<SizeRange>> videoFormats = >> + video->formats(format.mbus_code); >> + >> + LOG(SimplePipeline, Debug) >> + << "Adding configuration for " << format.size.toString() >> + << " in pixel formats [ " >> + << utils::join(videoFormats, ", ", >> + [](const auto &f) { >> + return f.first.toString(); >> + }) >> + << " ]"; >> + >> + /* >> + * Store the configuration in the formats_ map, mapping the >> + * PixelFormat to the corresponding configuration. Any >> + * previously stored value is overwritten, as the pipeline >> + * handler currently doesn't care about how a particular >> + * PixelFormat is achieved. >> + */ >> + for (const auto &videoFormat : videoFormats) { >> + PixelFormat pixelFormat = video->toPixelFormat(videoFormat.first); >> + if (!pixelFormat) >> + continue; >> + >> + Configuration config; >> + config.code = code; >> + config.pixelFormat = pixelFormat; >> + config.size = format.size; >> + >> + formats_[pixelFormat] = config; >> + } >> + } >> + >> + if (formats_.empty()) { >> + LOG(SimplePipeline, Error) << "No valid configuration found"; >> + return -EINVAL; >> + } >> + >> + return 0; >> +} >> + >> +int SimpleCameraData::setupLinks() >> +{ >> + int ret; >> + >> + /* >> + * Configure all links along the pipeline. Some entities may not allow >> + * multiple sink links to be enabled together, even on different sink >> + * pads. We must thus start by disabling all sink links (but the one we >> + * want to enable) before enabling the pipeline link. >> + */ >> + for (SimpleCameraData::Entity &e : entities_) { >> + MediaEntity *remote = e.link->sink()->entity(); >> + for (MediaPad *pad : remote->pads()) { >> + for (MediaLink *link : pad->links()) { >> + if (link == e.link) >> + continue; >> + >> + if ((link->flags() & MEDIA_LNK_FL_ENABLED) && >> + !(link->flags() & MEDIA_LNK_FL_IMMUTABLE)) { >> + ret = link->setEnabled(false); >> + if (ret < 0) >> + return ret; >> + } >> + } >> + } >> + >> + if (!(e.link->flags() & MEDIA_LNK_FL_ENABLED)) { >> + ret = e.link->setEnabled(true); >> + if (ret < 0) >> + return ret; >> + } >> + } >> + >> + return 0; >> +} >> + >> +int SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format, >> + V4L2Subdevice::Whence whence) >> +{ >> + SimplePipelineHandler *pipe = static_cast<SimplePipelineHandler *>(pipe_); >> + int ret; >> + >> + /* >> + * Configure the format on the sensor output and propagate it through >> + * the pipeline. >> + */ >> + ret = sensor_->setFormat(format); >> + if (ret < 0) >> + return ret; >> + >> + for (const Entity &e : entities_) { >> + MediaLink *link = e.link; >> + MediaPad *source = link->source(); >> + MediaPad *sink = link->sink(); >> + >> + if (source->entity() != sensor_->entity()) { >> + V4L2Subdevice *subdev = pipe->subdev(source->entity()); >> + ret = subdev->getFormat(source->index(), format, whence); >> + if (ret < 0) >> + return ret; >> + } >> + >> + if (sink->entity()->function() != MEDIA_ENT_F_IO_V4L) { >> + V4L2Subdevice *subdev = pipe->subdev(sink->entity()); >> + ret = subdev->setFormat(sink->index(), format, whence); >> + if (ret < 0) >> + return ret; >> + } >> + >> + LOG(SimplePipeline, Debug) >> + << "Link '" << source->entity()->name() >> + << "':" << source->index() >> + << " -> '" << sink->entity()->name() >> + << "':" << sink->index() >> + << " configured with format " << format->toString(); >> + } >> + >> + return 0; >> +} >> + >> +/* ----------------------------------------------------------------------------- >> + * Camera Configuration >> + */ >> + >> +SimpleCameraConfiguration::SimpleCameraConfiguration(Camera *camera, >> + SimpleCameraData *data) >> + : CameraConfiguration(), camera_(camera->shared_from_this()), >> + data_(data) >> +{ >> +} >> + >> +CameraConfiguration::Status SimpleCameraConfiguration::validate() >> +{ >> + Status status = Valid; >> + >> + if (config_.empty()) >> + return Invalid; >> + >> + /* Cap the number of entries to the available streams. */ >> + if (config_.size() > 1) { >> + config_.resize(1); >> + status = Adjusted; >> + } >> + >> + StreamConfiguration &cfg = config_[0]; >> + >> + /* Adjust the pixel format. */ >> + auto it = data_->formats_.find(cfg.pixelFormat); >> + if (it == data_->formats_.end()) >> + it = data_->formats_.begin(); >> + >> + PixelFormat pixelFormat = it->first; >> + if (cfg.pixelFormat != pixelFormat) { >> + LOG(SimplePipeline, Debug) << "Adjusting pixel format"; >> + cfg.pixelFormat = pixelFormat; >> + status = Adjusted; >> + } >> + >> + const SimpleCameraData::Configuration &pipeConfig = it->second; >> + if (cfg.size != pipeConfig.size) { >> + LOG(SimplePipeline, Debug) >> + << "Adjusting size from " << cfg.size.toString() >> + << " to " << pipeConfig.size.toString(); >> + cfg.size = pipeConfig.size; >> + status = Adjusted; >> + } >> + >> + cfg.bufferCount = 3; >> + >> + return status; >> +} >> + >> +/* ----------------------------------------------------------------------------- >> + * Pipeline Handler >> + */ >> + >> +SimplePipelineHandler::SimplePipelineHandler(CameraManager *manager) >> + : PipelineHandler(manager), video_(nullptr) >> +{ >> +} >> + >> +SimplePipelineHandler::~SimplePipelineHandler() >> +{ >> + delete video_; >> +} >> + >> +CameraConfiguration *SimplePipelineHandler::generateConfiguration(Camera *camera, >> + const StreamRoles &roles) >> +{ >> + SimpleCameraData *data = cameraData(camera); >> + CameraConfiguration *config = >> + new SimpleCameraConfiguration(camera, data); >> + >> + if (roles.empty()) >> + return config; >> + >> + /* Create the formats map. */ >> + std::map<PixelFormat, std::vector<SizeRange>> formats; >> + std::transform(data->formats_.begin(), data->formats_.end(), >> + std::inserter(formats, formats.end()), >> + [](const auto &format) -> decltype(formats)::value_type { >> + const PixelFormat &pixelFormat = format.first; >> + const Size &size = format.second.size; >> + return { pixelFormat, { size } }; >> + }); >> + >> + /* >> + * Create the stream configuration. Take the first entry in the formats >> + * map as the default, for lack of a better option. >> + * >> + * \todo Implement a better way to pick the default format >> + */ >> + StreamConfiguration cfg{ StreamFormats{ formats } }; >> + cfg.pixelFormat = formats.begin()->first; >> + cfg.size = formats.begin()->second[0].max; >> + >> + config->addConfiguration(cfg); >> + >> + config->validate(); >> + >> + return config; >> +} >> + >> +int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c) >> +{ >> + SimpleCameraConfiguration *config = >> + static_cast<SimpleCameraConfiguration *>(c); >> + SimpleCameraData *data = cameraData(camera); >> + StreamConfiguration &cfg = config->at(0); >> + int ret; >> + >> + /* >> + * Configure links on the pipeline and propagate formats from the >> + * sensor to the video node. >> + */ >> + ret = data->setupLinks(); >> + if (ret < 0) >> + return ret; >> + >> + const SimpleCameraData::Configuration &pipeConfig = >> + data->formats_[cfg.pixelFormat]; >> + >> + V4L2SubdeviceFormat format{ pipeConfig.code, data->sensor_->resolution() }; >> + >> + ret = data->setupFormats(&format, V4L2Subdevice::ActiveFormat); >> + if (ret < 0) >> + return ret; >> + >> + /* Configure the video node. */ >> + V4L2PixelFormat videoFormat = video_->toV4L2PixelFormat(cfg.pixelFormat); >> + >> + V4L2DeviceFormat outputFormat = {}; >> + outputFormat.fourcc = videoFormat; >> + outputFormat.size = cfg.size; >> + >> + ret = video_->setFormat(&outputFormat); >> + if (ret) >> + return ret; >> + >> + if (outputFormat.size != cfg.size || outputFormat.fourcc != videoFormat) { >> + LOG(SimplePipeline, Error) >> + << "Unable to configure capture in " << cfg.toString(); >> + return -EINVAL; >> + } >> + >> + cfg.setStream(&data->stream_); >> + >> + return 0; >> +} >> + >> +int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream, >> + std::vector<std::unique_ptr<FrameBuffer>> *buffers) >> +{ >> + unsigned int count = stream->configuration().bufferCount; >> + >> + return video_->exportBuffers(count, buffers); >> +} >> + >> +int SimplePipelineHandler::start(Camera *camera) >> +{ >> + SimpleCameraData *data = cameraData(camera); >> + unsigned int count = data->stream_.configuration().bufferCount; >> + >> + int ret = video_->importBuffers(count); >> + if (ret < 0) >> + return ret; >> + >> + ret = video_->streamOn(); >> + if (ret < 0) { >> + video_->releaseBuffers(); >> + return ret; >> + } >> + >> + activeCamera_ = camera; >> + >> + return 0; >> +} >> + >> +void SimplePipelineHandler::stop(Camera *camera) >> +{ >> + video_->streamOff(); >> + video_->releaseBuffers(); >> + activeCamera_ = nullptr; >> +} >> + >> +int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request) >> +{ >> + SimpleCameraData *data = cameraData(camera); >> + Stream *stream = &data->stream_; >> + >> + FrameBuffer *buffer = request->findBuffer(stream); >> + if (!buffer) { >> + LOG(SimplePipeline, Error) >> + << "Attempt to queue request with invalid stream"; >> + return -ENOENT; >> + } >> + >> + return video_->queueBuffer(buffer); >> +} >> + >> +/* ----------------------------------------------------------------------------- >> + * Match and Setup >> + */ >> + >> +bool SimplePipelineHandler::match(DeviceEnumerator *enumerator) >> +{ >> + for (const char *driver : drivers) { >> + DeviceMatch dm(driver); >> + media_ = acquireMediaDevice(enumerator, dm); >> + if (media_) >> + break; >> + } >> + >> + if (!media_) >> + return false; >> + >> + /* >> + * Locate sensors and video nodes. We only support pipelines with at >> + * least one sensor and exactly one video capture node. >> + */ >> + std::vector<MediaEntity *> sensors; >> + std::vector<MediaEntity *> videos; >> + >> + for (MediaEntity *entity : media_->entities()) { >> + switch (entity->function()) { >> + case MEDIA_ENT_F_CAM_SENSOR: >> + sensors.push_back(entity); >> + break; >> + >> + case MEDIA_ENT_F_IO_V4L: >> + if (entity->pads().size() == 1 && >> + (entity->pads()[0]->flags() & MEDIA_PAD_FL_SINK)) >> + videos.push_back(entity); >> + break; >> + >> + default: >> + break; >> + } >> + } >> + >> + if (sensors.empty()) { >> + LOG(SimplePipeline, Error) << "No sensor found"; >> + return false; >> + } >> + >> + if (videos.size() != 1) { >> + LOG(SimplePipeline, Error) >> + << "Pipeline with " << videos.size() >> + << " video capture nodes is not supported"; >> + return false; >> + } >> + >> + /* Locate and open the capture video node. */ >> + video_ = new V4L2VideoDevice(videos[0]); >> + if (video_->open() < 0) >> + return false; >> + >> + if (video_->caps().isMultiplanar()) { >> + LOG(SimplePipeline, Error) >> + << "V4L2 multiplanar devices are not supported"; >> + return false; >> + } >> + >> + video_->bufferReady.connect(this, &SimplePipelineHandler::bufferReady); >> + >> + /* >> + * Create one camera data instance for each sensor and gather all >> + * entities in all pipelines. >> + */ >> + std::vector<std::unique_ptr<SimpleCameraData>> pipelines; >> + std::set<MediaEntity *> entities; >> + >> + pipelines.reserve(sensors.size()); >> + >> + for (MediaEntity *sensor : sensors) { >> + std::unique_ptr<SimpleCameraData> data = >> + std::make_unique<SimpleCameraData>(this, sensor, >> + videos[0]); >> + if (!data->isValid()) { >> + LOG(SimplePipeline, Error) >> + << "No valid pipeline for sensor '" >> + << sensor->name() << "', skipping"; >> + continue; >> + } >> + >> + for (SimpleCameraData::Entity &entity : data->entities_) >> + entities.insert(entity.entity); >> + >> + pipelines.push_back(std::move(data)); >> + } >> + >> + if (entities.empty()) >> + return false; >> + >> + /* Create and open V4L2Subdev instances for all the entities. */ >> + for (MediaEntity *entity : entities) { >> + auto elem = subdevs_.emplace(std::piecewise_construct, >> + std::forward_as_tuple(entity), >> + std::forward_as_tuple(entity)); >> + V4L2Subdevice *subdev = &elem.first->second; >> + int ret = subdev->open(); >> + if (ret < 0) { >> + LOG(SimplePipeline, Error) >> + << "Failed to open " << subdev->deviceNode() >> + << ": " << strerror(-ret); >> + return false; >> + } >> + } >> + >> + /* Initialize each pipeline and register a corresponding camera. */ >> + for (std::unique_ptr<SimpleCameraData> &data : pipelines) { >> + int ret = data->init(); >> + if (ret < 0) >> + continue; >> + >> + std::shared_ptr<Camera> camera = >> + Camera::create(this, data->sensor_->entity()->name(), >> + data->streams()); >> + registerCamera(std::move(camera), std::move(data)); >> + } >> + >> + return true; >> +} >> + >> +V4L2Subdevice *SimplePipelineHandler::subdev(const MediaEntity *entity) >> +{ >> + auto iter = subdevs_.find(entity); >> + if (iter == subdevs_.end()) >> + return nullptr; >> + >> + return &iter->second; >> +} >> + >> +/* ----------------------------------------------------------------------------- >> + * Buffer Handling >> + */ >> + >> +void SimplePipelineHandler::bufferReady(FrameBuffer *buffer) >> +{ >> + ASSERT(activeCamera_); >> + Request *request = buffer->request(); >> + completeBuffer(activeCamera_, request, buffer); >> + completeRequest(activeCamera_, request); >> +} >> + >> +REGISTER_PIPELINE_HANDLER(SimplePipelineHandler); >> + >> +} /* namespace libcamera */ >> -- >> Regards, >> >> Laurent Pinchart >> >> _______________________________________________ >> libcamera-devel mailing list >> libcamera-devel@lists.libcamera.org >> https://lists.libcamera.org/listinfo/libcamera-devel >
diff --git a/meson_options.txt b/meson_options.txt index 6464df837cc3..166429f8583e 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -14,7 +14,7 @@ option('gstreamer', option('pipelines', type : 'array', - choices : ['ipu3', 'rkisp1', 'uvcvideo', 'vimc'], + choices : ['ipu3', 'rkisp1', 'simple', 'uvcvideo', 'vimc'], description : 'Select which pipeline handlers to include') option('test', diff --git a/src/libcamera/pipeline/simple/meson.build b/src/libcamera/pipeline/simple/meson.build new file mode 100644 index 000000000000..4945a3e173cf --- /dev/null +++ b/src/libcamera/pipeline/simple/meson.build @@ -0,0 +1,3 @@ +libcamera_sources += files([ + 'simple.cpp', +]) diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp new file mode 100644 index 000000000000..e4f33f6ff531 --- /dev/null +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -0,0 +1,717 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020, Laurent Pinchart + * Copyright (C) 2019, Martijn Braam + * + * simple.cpp - Pipeline handler for simple pipelines + */ + +#include <algorithm> +#include <iterator> +#include <list> +#include <map> +#include <memory> +#include <set> +#include <string> +#include <string.h> +#include <utility> +#include <vector> + +#include <linux/media-bus-format.h> + +#include <libcamera/camera.h> +#include <libcamera/request.h> +#include <libcamera/stream.h> + +#include "camera_sensor.h" +#include "device_enumerator.h" +#include "log.h" +#include "media_device.h" +#include "pipeline_handler.h" +#include "v4l2_subdevice.h" +#include "v4l2_videodevice.h" + +namespace libcamera { + +LOG_DEFINE_CATEGORY(SimplePipeline) + +class SimplePipelineHandler; + +namespace { + +static const char * const drivers[] = { + "imx7-csi", + "sun6i-csi", +}; + +} /* namespace */ + +class SimpleCameraData : public CameraData +{ +public: + SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor, + MediaEntity *video); + + bool isValid() const { return sensor_ != nullptr; } + std::set<Stream *> streams() { return { &stream_ }; } + + int init(); + int setupLinks(); + int setupFormats(V4L2SubdeviceFormat *format, + V4L2Subdevice::Whence whence); + + struct Entity { + MediaEntity *entity; + MediaLink *link; + }; + + struct Configuration { + uint32_t code; + PixelFormat pixelFormat; + Size size; + }; + + Stream stream_; + std::unique_ptr<CameraSensor> sensor_; + std::list<Entity> entities_; + + std::vector<Configuration> configs_; + std::map<PixelFormat, Configuration> formats_; +}; + +class SimpleCameraConfiguration : public CameraConfiguration +{ +public: + SimpleCameraConfiguration(Camera *camera, SimpleCameraData *data); + + Status validate() override; + + const V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; } + +private: + /* + * The SimpleCameraData instance is guaranteed to be valid as long as + * the corresponding Camera instance is valid. In order to borrow a + * reference to the camera data, store a new reference to the camera. + */ + std::shared_ptr<Camera> camera_; + const SimpleCameraData *data_; + + V4L2SubdeviceFormat sensorFormat_; +}; + +class SimplePipelineHandler : public PipelineHandler +{ +public: + SimplePipelineHandler(CameraManager *manager); + ~SimplePipelineHandler(); + + CameraConfiguration *generateConfiguration(Camera *camera, + const StreamRoles &roles) override; + int configure(Camera *camera, CameraConfiguration *config) override; + + int exportFrameBuffers(Camera *camera, Stream *stream, + std::vector<std::unique_ptr<FrameBuffer>> *buffers) override; + + int start(Camera *camera) override; + void stop(Camera *camera) override; + + bool match(DeviceEnumerator *enumerator) override; + + V4L2VideoDevice *video() { return video_; } + V4L2Subdevice *subdev(const MediaEntity *entity); + +protected: + int queueRequestDevice(Camera *camera, Request *request) override; + +private: + SimpleCameraData *cameraData(const Camera *camera) + { + return static_cast<SimpleCameraData *>( + PipelineHandler::cameraData(camera)); + } + + int initLinks(); + + int createCamera(MediaEntity *sensor); + + void bufferReady(FrameBuffer *buffer); + + MediaDevice *media_; + V4L2VideoDevice *video_; + std::map<const MediaEntity *, V4L2Subdevice> subdevs_; + + Camera *activeCamera_; +}; + +/* ----------------------------------------------------------------------------- + * Camera Data + */ + +SimpleCameraData::SimpleCameraData(PipelineHandler *pipe, MediaEntity *sensor, + MediaEntity *video) + : CameraData(pipe) +{ + int ret; + + /* + * Walk the pipeline towards the video node and store all entities + * along the way. + */ + MediaEntity *source = sensor; + + while (source) { + /* If we have reached the video node, we're done. */ + if (source == video) + break; + + /* Use the first output pad that has links. */ + MediaPad *sourcePad = nullptr; + for (MediaPad *pad : source->pads()) { + if ((pad->flags() & MEDIA_PAD_FL_SOURCE) && + !pad->links().empty()) { + sourcePad = pad; + break; + } + } + + if (!sourcePad) + return; + + /* Use the first link that isn't immutable and disabled. */ + MediaLink *sourceLink = nullptr; + for (MediaLink *link : sourcePad->links()) { + if ((link->flags() & MEDIA_LNK_FL_ENABLED) || + !(link->flags() & MEDIA_LNK_FL_IMMUTABLE)) { + sourceLink = link; + break; + } + } + + if (!sourceLink) + return; + + entities_.push_back({ source, sourceLink }); + + source = sourceLink->sink()->entity(); + + /* Avoid infinite loops. */ + auto iter = std::find_if(entities_.begin(), entities_.end(), + [&](const Entity &entity) { + return entity.entity == source; + }); + if (iter != entities_.end()) { + LOG(SimplePipeline, Info) << "Loop detected in pipeline"; + return; + } + } + + /* We have a valid pipeline, create the camera sensor. */ + sensor_ = std::make_unique<CameraSensor>(sensor); + ret = sensor_->init(); + if (ret) { + sensor_.reset(); + return; + } +} + +int SimpleCameraData::init() +{ + SimplePipelineHandler *pipe = static_cast<SimplePipelineHandler *>(pipe_); + V4L2VideoDevice *video = pipe->video(); + int ret; + + /* + * Enumerate the possible pipeline configurations. For each media bus + * format supported by the sensor, propagate the formats through the + * pipeline, and enumerate the corresponding possible V4L2 pixel + * formats on the video node. + */ + for (unsigned int code : sensor_->mbusCodes()) { + V4L2SubdeviceFormat format{ code, sensor_->resolution() }; + + /* + * Setup links first as some subdev drivers take active links + * into account to propagate TRY formats. Such is life :-( + */ + ret = setupLinks(); + if (ret < 0) + return ret; + + ret = setupFormats(&format, V4L2Subdevice::TryFormat); + if (ret < 0) { + LOG(SimplePipeline, Error) + << "Failed to setup pipeline for media bus code " + << utils::hex(code, 4); + return ret; + } + + std::map<V4L2PixelFormat, std::vector<SizeRange>> videoFormats = + video->formats(format.mbus_code); + + LOG(SimplePipeline, Debug) + << "Adding configuration for " << format.size.toString() + << " in pixel formats [ " + << utils::join(videoFormats, ", ", + [](const auto &f) { + return f.first.toString(); + }) + << " ]"; + + /* + * Store the configuration in the formats_ map, mapping the + * PixelFormat to the corresponding configuration. Any + * previously stored value is overwritten, as the pipeline + * handler currently doesn't care about how a particular + * PixelFormat is achieved. + */ + for (const auto &videoFormat : videoFormats) { + PixelFormat pixelFormat = video->toPixelFormat(videoFormat.first); + if (!pixelFormat) + continue; + + Configuration config; + config.code = code; + config.pixelFormat = pixelFormat; + config.size = format.size; + + formats_[pixelFormat] = config; + } + } + + if (formats_.empty()) { + LOG(SimplePipeline, Error) << "No valid configuration found"; + return -EINVAL; + } + + return 0; +} + +int SimpleCameraData::setupLinks() +{ + int ret; + + /* + * Configure all links along the pipeline. Some entities may not allow + * multiple sink links to be enabled together, even on different sink + * pads. We must thus start by disabling all sink links (but the one we + * want to enable) before enabling the pipeline link. + */ + for (SimpleCameraData::Entity &e : entities_) { + MediaEntity *remote = e.link->sink()->entity(); + for (MediaPad *pad : remote->pads()) { + for (MediaLink *link : pad->links()) { + if (link == e.link) + continue; + + if ((link->flags() & MEDIA_LNK_FL_ENABLED) && + !(link->flags() & MEDIA_LNK_FL_IMMUTABLE)) { + ret = link->setEnabled(false); + if (ret < 0) + return ret; + } + } + } + + if (!(e.link->flags() & MEDIA_LNK_FL_ENABLED)) { + ret = e.link->setEnabled(true); + if (ret < 0) + return ret; + } + } + + return 0; +} + +int SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format, + V4L2Subdevice::Whence whence) +{ + SimplePipelineHandler *pipe = static_cast<SimplePipelineHandler *>(pipe_); + int ret; + + /* + * Configure the format on the sensor output and propagate it through + * the pipeline. + */ + ret = sensor_->setFormat(format); + if (ret < 0) + return ret; + + for (const Entity &e : entities_) { + MediaLink *link = e.link; + MediaPad *source = link->source(); + MediaPad *sink = link->sink(); + + if (source->entity() != sensor_->entity()) { + V4L2Subdevice *subdev = pipe->subdev(source->entity()); + ret = subdev->getFormat(source->index(), format, whence); + if (ret < 0) + return ret; + } + + if (sink->entity()->function() != MEDIA_ENT_F_IO_V4L) { + V4L2Subdevice *subdev = pipe->subdev(sink->entity()); + ret = subdev->setFormat(sink->index(), format, whence); + if (ret < 0) + return ret; + } + + LOG(SimplePipeline, Debug) + << "Link '" << source->entity()->name() + << "':" << source->index() + << " -> '" << sink->entity()->name() + << "':" << sink->index() + << " configured with format " << format->toString(); + } + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Camera Configuration + */ + +SimpleCameraConfiguration::SimpleCameraConfiguration(Camera *camera, + SimpleCameraData *data) + : CameraConfiguration(), camera_(camera->shared_from_this()), + data_(data) +{ +} + +CameraConfiguration::Status SimpleCameraConfiguration::validate() +{ + Status status = Valid; + + if (config_.empty()) + return Invalid; + + /* Cap the number of entries to the available streams. */ + if (config_.size() > 1) { + config_.resize(1); + status = Adjusted; + } + + StreamConfiguration &cfg = config_[0]; + + /* Adjust the pixel format. */ + auto it = data_->formats_.find(cfg.pixelFormat); + if (it == data_->formats_.end()) + it = data_->formats_.begin(); + + PixelFormat pixelFormat = it->first; + if (cfg.pixelFormat != pixelFormat) { + LOG(SimplePipeline, Debug) << "Adjusting pixel format"; + cfg.pixelFormat = pixelFormat; + status = Adjusted; + } + + const SimpleCameraData::Configuration &pipeConfig = it->second; + if (cfg.size != pipeConfig.size) { + LOG(SimplePipeline, Debug) + << "Adjusting size from " << cfg.size.toString() + << " to " << pipeConfig.size.toString(); + cfg.size = pipeConfig.size; + status = Adjusted; + } + + cfg.bufferCount = 3; + + return status; +} + +/* ----------------------------------------------------------------------------- + * Pipeline Handler + */ + +SimplePipelineHandler::SimplePipelineHandler(CameraManager *manager) + : PipelineHandler(manager), video_(nullptr) +{ +} + +SimplePipelineHandler::~SimplePipelineHandler() +{ + delete video_; +} + +CameraConfiguration *SimplePipelineHandler::generateConfiguration(Camera *camera, + const StreamRoles &roles) +{ + SimpleCameraData *data = cameraData(camera); + CameraConfiguration *config = + new SimpleCameraConfiguration(camera, data); + + if (roles.empty()) + return config; + + /* Create the formats map. */ + std::map<PixelFormat, std::vector<SizeRange>> formats; + std::transform(data->formats_.begin(), data->formats_.end(), + std::inserter(formats, formats.end()), + [](const auto &format) -> decltype(formats)::value_type { + const PixelFormat &pixelFormat = format.first; + const Size &size = format.second.size; + return { pixelFormat, { size } }; + }); + + /* + * Create the stream configuration. Take the first entry in the formats + * map as the default, for lack of a better option. + * + * \todo Implement a better way to pick the default format + */ + StreamConfiguration cfg{ StreamFormats{ formats } }; + cfg.pixelFormat = formats.begin()->first; + cfg.size = formats.begin()->second[0].max; + + config->addConfiguration(cfg); + + config->validate(); + + return config; +} + +int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c) +{ + SimpleCameraConfiguration *config = + static_cast<SimpleCameraConfiguration *>(c); + SimpleCameraData *data = cameraData(camera); + StreamConfiguration &cfg = config->at(0); + int ret; + + /* + * Configure links on the pipeline and propagate formats from the + * sensor to the video node. + */ + ret = data->setupLinks(); + if (ret < 0) + return ret; + + const SimpleCameraData::Configuration &pipeConfig = + data->formats_[cfg.pixelFormat]; + + V4L2SubdeviceFormat format{ pipeConfig.code, data->sensor_->resolution() }; + + ret = data->setupFormats(&format, V4L2Subdevice::ActiveFormat); + if (ret < 0) + return ret; + + /* Configure the video node. */ + V4L2PixelFormat videoFormat = video_->toV4L2PixelFormat(cfg.pixelFormat); + + V4L2DeviceFormat outputFormat = {}; + outputFormat.fourcc = videoFormat; + outputFormat.size = cfg.size; + + ret = video_->setFormat(&outputFormat); + if (ret) + return ret; + + if (outputFormat.size != cfg.size || outputFormat.fourcc != videoFormat) { + LOG(SimplePipeline, Error) + << "Unable to configure capture in " << cfg.toString(); + return -EINVAL; + } + + cfg.setStream(&data->stream_); + + return 0; +} + +int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream, + std::vector<std::unique_ptr<FrameBuffer>> *buffers) +{ + unsigned int count = stream->configuration().bufferCount; + + return video_->exportBuffers(count, buffers); +} + +int SimplePipelineHandler::start(Camera *camera) +{ + SimpleCameraData *data = cameraData(camera); + unsigned int count = data->stream_.configuration().bufferCount; + + int ret = video_->importBuffers(count); + if (ret < 0) + return ret; + + ret = video_->streamOn(); + if (ret < 0) { + video_->releaseBuffers(); + return ret; + } + + activeCamera_ = camera; + + return 0; +} + +void SimplePipelineHandler::stop(Camera *camera) +{ + video_->streamOff(); + video_->releaseBuffers(); + activeCamera_ = nullptr; +} + +int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request) +{ + SimpleCameraData *data = cameraData(camera); + Stream *stream = &data->stream_; + + FrameBuffer *buffer = request->findBuffer(stream); + if (!buffer) { + LOG(SimplePipeline, Error) + << "Attempt to queue request with invalid stream"; + return -ENOENT; + } + + return video_->queueBuffer(buffer); +} + +/* ----------------------------------------------------------------------------- + * Match and Setup + */ + +bool SimplePipelineHandler::match(DeviceEnumerator *enumerator) +{ + for (const char *driver : drivers) { + DeviceMatch dm(driver); + media_ = acquireMediaDevice(enumerator, dm); + if (media_) + break; + } + + if (!media_) + return false; + + /* + * Locate sensors and video nodes. We only support pipelines with at + * least one sensor and exactly one video capture node. + */ + std::vector<MediaEntity *> sensors; + std::vector<MediaEntity *> videos; + + for (MediaEntity *entity : media_->entities()) { + switch (entity->function()) { + case MEDIA_ENT_F_CAM_SENSOR: + sensors.push_back(entity); + break; + + case MEDIA_ENT_F_IO_V4L: + if (entity->pads().size() == 1 && + (entity->pads()[0]->flags() & MEDIA_PAD_FL_SINK)) + videos.push_back(entity); + break; + + default: + break; + } + } + + if (sensors.empty()) { + LOG(SimplePipeline, Error) << "No sensor found"; + return false; + } + + if (videos.size() != 1) { + LOG(SimplePipeline, Error) + << "Pipeline with " << videos.size() + << " video capture nodes is not supported"; + return false; + } + + /* Locate and open the capture video node. */ + video_ = new V4L2VideoDevice(videos[0]); + if (video_->open() < 0) + return false; + + if (video_->caps().isMultiplanar()) { + LOG(SimplePipeline, Error) + << "V4L2 multiplanar devices are not supported"; + return false; + } + + video_->bufferReady.connect(this, &SimplePipelineHandler::bufferReady); + + /* + * Create one camera data instance for each sensor and gather all + * entities in all pipelines. + */ + std::vector<std::unique_ptr<SimpleCameraData>> pipelines; + std::set<MediaEntity *> entities; + + pipelines.reserve(sensors.size()); + + for (MediaEntity *sensor : sensors) { + std::unique_ptr<SimpleCameraData> data = + std::make_unique<SimpleCameraData>(this, sensor, + videos[0]); + if (!data->isValid()) { + LOG(SimplePipeline, Error) + << "No valid pipeline for sensor '" + << sensor->name() << "', skipping"; + continue; + } + + for (SimpleCameraData::Entity &entity : data->entities_) + entities.insert(entity.entity); + + pipelines.push_back(std::move(data)); + } + + if (entities.empty()) + return false; + + /* Create and open V4L2Subdev instances for all the entities. */ + for (MediaEntity *entity : entities) { + auto elem = subdevs_.emplace(std::piecewise_construct, + std::forward_as_tuple(entity), + std::forward_as_tuple(entity)); + V4L2Subdevice *subdev = &elem.first->second; + int ret = subdev->open(); + if (ret < 0) { + LOG(SimplePipeline, Error) + << "Failed to open " << subdev->deviceNode() + << ": " << strerror(-ret); + return false; + } + } + + /* Initialize each pipeline and register a corresponding camera. */ + for (std::unique_ptr<SimpleCameraData> &data : pipelines) { + int ret = data->init(); + if (ret < 0) + continue; + + std::shared_ptr<Camera> camera = + Camera::create(this, data->sensor_->entity()->name(), + data->streams()); + registerCamera(std::move(camera), std::move(data)); + } + + return true; +} + +V4L2Subdevice *SimplePipelineHandler::subdev(const MediaEntity *entity) +{ + auto iter = subdevs_.find(entity); + if (iter == subdevs_.end()) + return nullptr; + + return &iter->second; +} + +/* ----------------------------------------------------------------------------- + * Buffer Handling + */ + +void SimplePipelineHandler::bufferReady(FrameBuffer *buffer) +{ + ASSERT(activeCamera_); + Request *request = buffer->request(); + completeBuffer(activeCamera_, request, buffer); + completeRequest(activeCamera_, request); +} + +REGISTER_PIPELINE_HANDLER(SimplePipelineHandler); + +} /* namespace libcamera */