Message ID | 20241002212632.2463458-4-kieran.bingham@ideasonboard.com |
---|---|
State | Superseded |
Headers | show |
Series |
|
Related | show |
Quoting Kieran Bingham (2024-10-02 22:26:31) > Provide a MediaPipeline class to help identifing and managing pipelines across > a MediaDevice graph. > > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > > --- > v2: > > - use srcPads to clearly identify which pads are managed > - Only report enabling links when a change is made > > - fix header includes > > - Fix includes > - Remove period at end of briefs > > - Document function parameters > - expand documentation throughout > - Fix debug log capitalisation > > - reduce scope of single use 'ret' > > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com> ... snippity ... > + > +/** > + * \brief Configure the entities of this MediaPipeline > + * \param[in] sensor The configured CameraSensor to propogate How did I miss that. Will add * \param[inout] format The format to propogate through the pipeline > + * > + * Propagate formats through each of the entities of the Pipeline, validating > + * that each one was not adjusted by the driver from the desired format. > + * Maybe I should add * The format is updated with the final accepted format of the last * entity of the pipeline. > + * \return 0 on success or a negative errno otherwise > + */ > +int MediaPipeline::configure(CameraSensor *sensor, V4L2SubdeviceFormat *format) > +{ > + int ret; > + > + for (const Entity &e : entities_) { > + /* The sensor is configured through the CameraSensor */ > + if (!e.sourceLink) > + break; > + > + MediaLink *link = e.sourceLink; > + MediaPad *source = link->source(); > + MediaPad *sink = link->sink(); > + > + /* 'format' already contains the sensor configuration */ > + if (source->entity() != sensor->entity()) { > + /* todo: Add MediaDevice cache to reduce FD pressure */ > + V4L2Subdevice subdev(source->entity()); > + ret = subdev.open(); > + if (ret) > + return ret; > + > + ret = subdev.getFormat(source->index(), format); > + if (ret < 0) > + return ret; > + } > + > + V4L2SubdeviceFormat sourceFormat = *format; > + /* todo: Add MediaDevice cache to reduce FD pressure */ > + V4L2Subdevice subdev(sink->entity()); > + ret = subdev.open(); > + if (ret) > + return ret; > + > + ret = subdev.setFormat(sink->index(), format); > + if (ret < 0) > + return ret; > + > + if (format->code != sourceFormat.code || > + format->size != sourceFormat.size) { > + LOG(MediaPipeline, Debug) > + << "Source '" << *source > + << " produces " << sourceFormat > + << ", sink '" << *sink > + << " requires " << format; > + return -EINVAL; > + } > + > + LOG(MediaPipeline, Debug) > + << "Link " << *link << " configured with format " > + << format; > + } > + > + return 0; > +} > + > +} /* namespace libcamera */ > diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build > index aa9ab0291854..2c0f8603b231 100644 > --- a/src/libcamera/meson.build > +++ b/src/libcamera/meson.build > @@ -41,6 +41,7 @@ libcamera_internal_sources = files([ > 'mapped_framebuffer.cpp', > 'media_device.cpp', > 'media_object.cpp', > + 'media_pipeline.cpp', > 'pipeline_handler.cpp', > 'process.cpp', > 'pub_key.cpp', > -- > 2.34.1 >
Hi Kieran Thank you for the patch. On 03/10/24 2:56 am, Kieran Bingham wrote: > Provide a MediaPipeline class to help identifing and managing pipelines across s/identifing/identifying/ > a MediaDevice graph. > > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > > --- > v2: > > - use srcPads to clearly identify which pads are managed > - Only report enabling links when a change is made > > - fix header includes > > - Fix includes > - Remove period at end of briefs > > - Document function parameters > - expand documentation throughout > - Fix debug log capitalisation > > - reduce scope of single use 'ret' > > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > --- > include/libcamera/internal/media_pipeline.h | 59 ++++ > include/libcamera/internal/meson.build | 1 + > src/libcamera/media_pipeline.cpp | 307 ++++++++++++++++++++ > src/libcamera/meson.build | 1 + > 4 files changed, 368 insertions(+) > create mode 100644 include/libcamera/internal/media_pipeline.h > create mode 100644 src/libcamera/media_pipeline.cpp > > diff --git a/include/libcamera/internal/media_pipeline.h b/include/libcamera/internal/media_pipeline.h > new file mode 100644 > index 000000000000..ca4a86a88a1e > --- /dev/null > +++ b/include/libcamera/internal/media_pipeline.h > @@ -0,0 +1,59 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2024, Ideas on Board Oy > + * > + * Media pipeline handler > + */ > + > +#pragma once > + > +#include <list> > +#include <string> > +#include <vector> > + > +namespace libcamera { > + > +class CameraSensor; > +class MediaEntity; > +class MediaLink; > +class MediaPad; > +struct V4L2SubdeviceFormat; > + > +class MediaPipeline > +{ > +public: > + int init(MediaEntity *source, std::string sink); > + int initLinks(); > + int configure(CameraSensor *sensor, V4L2SubdeviceFormat *); Is skipping of function param name intentional ? I missed this in v1 review. Rest looks good, Reviewed-by: Umang Jain <umang.jain@ideasonboard.com> > + > +private: > + struct Entity { > + /* The media entity, always valid. */ > + MediaEntity *entity; > + /* > + * Whether or not the entity is a subdev that supports the > + * routing API. > + */ > + bool supportsRouting; > + /* > + * The local sink pad connected to the upstream entity, null for > + * the camera sensor at the beginning of the pipeline. > + */ > + const MediaPad *sink; > + /* > + * The local source pad connected to the downstream entity, null > + * for the video node at the end of the pipeline. > + */ > + const MediaPad *source; > + /* > + * The link on the source pad, to the downstream entity, null > + * for the video node at the end of the pipeline. > + */ > + MediaLink *sourceLink; > + }; > + > + std::vector<const MediaPad *> routedSourcePads(MediaPad *sink); > + std::list<Entity> entities_; > +}; > + > +} /* namespace libcamera */ > diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build > index 1c5eef9cab80..60a35d3e0357 100644 > --- a/include/libcamera/internal/meson.build > +++ b/include/libcamera/internal/meson.build > @@ -30,6 +30,7 @@ libcamera_internal_headers = files([ > 'mapped_framebuffer.h', > 'media_device.h', > 'media_object.h', > + 'media_pipeline.h', > 'pipeline_handler.h', > 'process.h', > 'pub_key.h', > diff --git a/src/libcamera/media_pipeline.cpp b/src/libcamera/media_pipeline.cpp > new file mode 100644 > index 000000000000..dba8084ddc97 > --- /dev/null > +++ b/src/libcamera/media_pipeline.cpp > @@ -0,0 +1,307 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2024, Ideas on Board Oy > + * > + * Media pipeline support > + */ > + > +#include "libcamera/internal/media_pipeline.h" > + > +#include <algorithm> > +#include <errno.h> > +#include <memory> > +#include <queue> > +#include <tuple> > +#include <unordered_map> > +#include <unordered_set> > + > +#include <linux/media.h> > + > +#include <libcamera/base/log.h> > + > +#include "libcamera/internal/camera_sensor.h" > +#include "libcamera/internal/media_device.h" > +#include "libcamera/internal/media_object.h" > +#include "libcamera/internal/v4l2_subdevice.h" > + > +/** > + * \file media_pipeline.h > + * \brief Provide a representation of a pipeline of devices using the Media > + * Controller > + */ > + > +namespace libcamera { > + > +LOG_DEFINE_CATEGORY(MediaPipeline) > + > +/** > + * \class MediaPipeline > + * \brief The MediaPipeline represents a set of entities that together form a > + * data path for stream data > + * > + * A MediaPipeline instance is constructed from a sink and a source between > + * two entities in a media graph. > + */ > + > +/** > + * \brief Retrieve all source pads connected to a sink pad through active routes > + * \param[in] sink The sink pad to examine > + * > + * Examine the entity using the V4L2 Subdevice Routing API to collect all the > + * source pads which are connected with an active route to the sink pad. > + * > + * \return A vector of source MediaPads > + */ > +std::vector<const MediaPad *> MediaPipeline::routedSourcePads(MediaPad *sink) > +{ > + MediaEntity *entity = sink->entity(); > + std::unique_ptr<V4L2Subdevice> subdev = > + std::make_unique<V4L2Subdevice>(entity); > + > + int ret = subdev->open(); > + if (ret < 0) > + return {}; > + > + V4L2Subdevice::Routing routing = {}; > + ret = subdev->getRouting(&routing, V4L2Subdevice::ActiveFormat); > + if (ret < 0) > + return {}; > + > + std::vector<const MediaPad *> pads; > + > + for (const V4L2Subdevice::Route &route : routing) { > + if (sink->index() != route.sink.pad || > + !(route.flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE)) > + continue; > + > + const MediaPad *pad = entity->getPadByIndex(route.source.pad); > + if (!pad) { > + LOG(MediaPipeline, Warning) > + << "Entity " << entity->name() > + << " has invalid route source pad " > + << route.source.pad; > + } > + > + pads.push_back(pad); > + } > + > + return pads; > +} > + > +/** > + * \brief Find the path from source to sink > + * \param[in] source The source entity to start from > + * \param[in] sink The sink entity name to search for > + * > + * Starting from a source entity, determine the shortest path to the target > + * described by sink. > + * > + * If sink can not be found, or a route from source to sink can not be achieved, > + * an error of -ENOLINK will be returned. > + * > + * When successful, the MediaPipeline will internally store the representation > + * of entities and links to describe the path between the two entities. > + * > + * It is expected that the Source entity is a sensor represented by the > + * CameraSensor class. > + * > + * \return 0 on success, a negative errno otherwise > + */ > +int MediaPipeline::init(MediaEntity *source, std::string sink) > +{ > + /* > + * Find the shortest path between from the source and the > + * target entity. > + */ > + std::unordered_set<MediaEntity *> visited; > + std::queue<std::tuple<MediaEntity *, MediaPad *>> queue; > + > + /* Remember at each entity where we came from. */ > + std::unordered_map<MediaEntity *, Entity> parents; > + MediaEntity *entity = nullptr; > + MediaEntity *target = nullptr; > + MediaPad *sinkPad; > + > + queue.push({ source, nullptr }); > + > + while (!queue.empty()) { > + std::tie(entity, sinkPad) = queue.front(); > + queue.pop(); > + > + /* Found the target device. */ > + if (entity->name() == sink) { > + LOG(MediaPipeline, Debug) > + << "Found pipeline target " << entity->name(); > + target = entity; > + break; > + } > + > + visited.insert(entity); > + > + /* > + * Add direct downstream entities to the search queue. If the > + * current entity supports the subdev internal routing API, > + * restrict the search to downstream entities reachable through > + * active routes. > + */ > + > + std::vector<const MediaPad *> srcPads; > + bool supportsRouting = false; > + > + if (sinkPad) { > + srcPads = routedSourcePads(sinkPad); > + if (!srcPads.empty()) > + supportsRouting = true; > + } > + > + if (srcPads.empty()) { > + for (const MediaPad *pad : entity->pads()) { > + if (!(pad->flags() & MEDIA_PAD_FL_SOURCE)) > + continue; > + srcPads.push_back(pad); > + } > + } > + > + for (const MediaPad *srcPad : srcPads) { > + for (MediaLink *link : srcPad->links()) { > + MediaEntity *next = link->sink()->entity(); > + if (visited.find(next) == visited.end()) { > + queue.push({ next, link->sink() }); > + > + Entity e{ entity, supportsRouting, sinkPad, > + srcPad, link }; > + parents.insert({ next, e }); > + } > + } > + } > + } > + > + if (!target) { > + LOG(MediaPipeline, Error) << "Failed to connect " << source->name(); > + return -ENOLINK; > + } > + > + /* > + * With the parents, we can follow back our way from the capture device > + * to the sensor. Store all the entities in the pipeline, from the > + * camera sensor to the video node, in entities_. > + */ > + entities_.push_front({ entity, false, sinkPad, nullptr, nullptr }); > + > + for (auto it = parents.find(entity); it != parents.end(); > + it = parents.find(entity)) { > + const Entity &e = it->second; > + entities_.push_front(e); > + entity = e.entity; > + } > + > + LOG(MediaPipeline, Info) > + << "Found pipeline: " > + << utils::join(entities_, " -> ", > + [](const Entity &e) { > + std::string s = "["; > + if (e.sink) > + s += std::to_string(e.sink->index()) + "|"; > + s += e.entity->name(); > + if (e.source) > + s += "|" + std::to_string(e.source->index()); > + s += "]"; > + return s; > + }); > + > + return 0; > +} > + > +/** > + * \brief Initialise and enable all links through the MediaPipeline > + * \return 0 on success, or a negative errno otherwise > + */ > +int MediaPipeline::initLinks() > +{ > + MediaLink *sinkLink = nullptr; > + for (Entity &e : entities_) { > + /* Sensor entities have no connected sink. */ > + if (!sinkLink) { > + sinkLink = e.sourceLink; > + continue; > + } > + > + if (!(sinkLink->flags() & MEDIA_LNK_FL_ENABLED)) { > + LOG(MediaPipeline, Debug) << "Enabling : " << *sinkLink; > + > + int ret = sinkLink->setEnabled(true); > + if (ret < 0) > + return ret; > + } > + > + sinkLink = e.sourceLink; > + } > + > + return 0; > +} > + > +/** > + * \brief Configure the entities of this MediaPipeline > + * \param[in] sensor The configured CameraSensor to propogate > + * > + * Propagate formats through each of the entities of the Pipeline, validating > + * that each one was not adjusted by the driver from the desired format. > + * > + * \return 0 on success or a negative errno otherwise > + */ > +int MediaPipeline::configure(CameraSensor *sensor, V4L2SubdeviceFormat *format) > +{ > + int ret; > + > + for (const Entity &e : entities_) { > + /* The sensor is configured through the CameraSensor */ > + if (!e.sourceLink) > + break; > + > + MediaLink *link = e.sourceLink; > + MediaPad *source = link->source(); > + MediaPad *sink = link->sink(); > + > + /* 'format' already contains the sensor configuration */ > + if (source->entity() != sensor->entity()) { > + /* todo: Add MediaDevice cache to reduce FD pressure */ > + V4L2Subdevice subdev(source->entity()); > + ret = subdev.open(); > + if (ret) > + return ret; > + > + ret = subdev.getFormat(source->index(), format); > + if (ret < 0) > + return ret; > + } > + > + V4L2SubdeviceFormat sourceFormat = *format; > + /* todo: Add MediaDevice cache to reduce FD pressure */ > + V4L2Subdevice subdev(sink->entity()); > + ret = subdev.open(); > + if (ret) > + return ret; > + > + ret = subdev.setFormat(sink->index(), format); > + if (ret < 0) > + return ret; > + > + if (format->code != sourceFormat.code || > + format->size != sourceFormat.size) { > + LOG(MediaPipeline, Debug) > + << "Source '" << *source > + << " produces " << sourceFormat > + << ", sink '" << *sink > + << " requires " << format; > + return -EINVAL; > + } > + > + LOG(MediaPipeline, Debug) > + << "Link " << *link << " configured with format " > + << format; > + } > + > + return 0; > +} > + > +} /* namespace libcamera */ > diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build > index aa9ab0291854..2c0f8603b231 100644 > --- a/src/libcamera/meson.build > +++ b/src/libcamera/meson.build > @@ -41,6 +41,7 @@ libcamera_internal_sources = files([ > 'mapped_framebuffer.cpp', > 'media_device.cpp', > 'media_object.cpp', > + 'media_pipeline.cpp', > 'pipeline_handler.cpp', > 'process.cpp', > 'pub_key.cpp',
Quoting Umang Jain (2024-10-03 07:24:11) > Hi Kieran > > Thank you for the patch. > > On 03/10/24 2:56 am, Kieran Bingham wrote: > > Provide a MediaPipeline class to help identifing and managing pipelines across > > s/identifing/identifying/ > > a MediaDevice graph. > > > > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > > > > --- > > v2: > > > > - use srcPads to clearly identify which pads are managed > > - Only report enabling links when a change is made > > > > - fix header includes > > > > - Fix includes > > - Remove period at end of briefs > > > > - Document function parameters > > - expand documentation throughout > > - Fix debug log capitalisation > > > > - reduce scope of single use 'ret' > > > > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > > --- > > include/libcamera/internal/media_pipeline.h | 59 ++++ > > include/libcamera/internal/meson.build | 1 + > > src/libcamera/media_pipeline.cpp | 307 ++++++++++++++++++++ > > src/libcamera/meson.build | 1 + > > 4 files changed, 368 insertions(+) > > create mode 100644 include/libcamera/internal/media_pipeline.h > > create mode 100644 src/libcamera/media_pipeline.cpp > > > > diff --git a/include/libcamera/internal/media_pipeline.h b/include/libcamera/internal/media_pipeline.h > > new file mode 100644 > > index 000000000000..ca4a86a88a1e > > --- /dev/null > > +++ b/include/libcamera/internal/media_pipeline.h > > @@ -0,0 +1,59 @@ > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > > +/* > > + * Copyright (C) 2024, Ideas on Board Oy > > + * > > + * Media pipeline handler > > + */ > > + > > +#pragma once > > + > > +#include <list> > > +#include <string> > > +#include <vector> > > + > > +namespace libcamera { > > + > > +class CameraSensor; > > +class MediaEntity; > > +class MediaLink; > > +class MediaPad; > > +struct V4L2SubdeviceFormat; > > + > > +class MediaPipeline > > +{ > > +public: > > + int init(MediaEntity *source, std::string sink); > > + int initLinks(); > > + int configure(CameraSensor *sensor, V4L2SubdeviceFormat *); > > Is skipping of function param name intentional ? I missed this in v1 review. No, I'll add it as 'format' to match the function implementation. > > Rest looks good, > > Reviewed-by: Umang Jain <umang.jain@ideasonboard.com> Thanks -- Kieran > > + > > +private: > > + struct Entity { > > + /* The media entity, always valid. */ > > + MediaEntity *entity; > > + /* > > + * Whether or not the entity is a subdev that supports the > > + * routing API. > > + */ > > + bool supportsRouting; > > + /* > > + * The local sink pad connected to the upstream entity, null for > > + * the camera sensor at the beginning of the pipeline. > > + */ > > + const MediaPad *sink; > > + /* > > + * The local source pad connected to the downstream entity, null > > + * for the video node at the end of the pipeline. > > + */ > > + const MediaPad *source; > > + /* > > + * The link on the source pad, to the downstream entity, null > > + * for the video node at the end of the pipeline. > > + */ > > + MediaLink *sourceLink; > > + }; > > + > > + std::vector<const MediaPad *> routedSourcePads(MediaPad *sink); > > + std::list<Entity> entities_; > > +}; > > + > > +} /* namespace libcamera */ > > diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build > > index 1c5eef9cab80..60a35d3e0357 100644 > > --- a/include/libcamera/internal/meson.build > > +++ b/include/libcamera/internal/meson.build > > @@ -30,6 +30,7 @@ libcamera_internal_headers = files([ > > 'mapped_framebuffer.h', > > 'media_device.h', > > 'media_object.h', > > + 'media_pipeline.h', > > 'pipeline_handler.h', > > 'process.h', > > 'pub_key.h', > > diff --git a/src/libcamera/media_pipeline.cpp b/src/libcamera/media_pipeline.cpp > > new file mode 100644 > > index 000000000000..dba8084ddc97 > > --- /dev/null > > +++ b/src/libcamera/media_pipeline.cpp > > @@ -0,0 +1,307 @@ > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > > +/* > > + * Copyright (C) 2024, Ideas on Board Oy > > + * > > + * Media pipeline support > > + */ > > + > > +#include "libcamera/internal/media_pipeline.h" > > + > > +#include <algorithm> > > +#include <errno.h> > > +#include <memory> > > +#include <queue> > > +#include <tuple> > > +#include <unordered_map> > > +#include <unordered_set> > > + > > +#include <linux/media.h> > > + > > +#include <libcamera/base/log.h> > > + > > +#include "libcamera/internal/camera_sensor.h" > > +#include "libcamera/internal/media_device.h" > > +#include "libcamera/internal/media_object.h" > > +#include "libcamera/internal/v4l2_subdevice.h" > > + > > +/** > > + * \file media_pipeline.h > > + * \brief Provide a representation of a pipeline of devices using the Media > > + * Controller > > + */ > > + > > +namespace libcamera { > > + > > +LOG_DEFINE_CATEGORY(MediaPipeline) > > + > > +/** > > + * \class MediaPipeline > > + * \brief The MediaPipeline represents a set of entities that together form a > > + * data path for stream data > > + * > > + * A MediaPipeline instance is constructed from a sink and a source between > > + * two entities in a media graph. > > + */ > > + > > +/** > > + * \brief Retrieve all source pads connected to a sink pad through active routes > > + * \param[in] sink The sink pad to examine > > + * > > + * Examine the entity using the V4L2 Subdevice Routing API to collect all the > > + * source pads which are connected with an active route to the sink pad. > > + * > > + * \return A vector of source MediaPads > > + */ > > +std::vector<const MediaPad *> MediaPipeline::routedSourcePads(MediaPad *sink) > > +{ > > + MediaEntity *entity = sink->entity(); > > + std::unique_ptr<V4L2Subdevice> subdev = > > + std::make_unique<V4L2Subdevice>(entity); > > + > > + int ret = subdev->open(); > > + if (ret < 0) > > + return {}; > > + > > + V4L2Subdevice::Routing routing = {}; > > + ret = subdev->getRouting(&routing, V4L2Subdevice::ActiveFormat); > > + if (ret < 0) > > + return {}; > > + > > + std::vector<const MediaPad *> pads; > > + > > + for (const V4L2Subdevice::Route &route : routing) { > > + if (sink->index() != route.sink.pad || > > + !(route.flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE)) > > + continue; > > + > > + const MediaPad *pad = entity->getPadByIndex(route.source.pad); > > + if (!pad) { > > + LOG(MediaPipeline, Warning) > > + << "Entity " << entity->name() > > + << " has invalid route source pad " > > + << route.source.pad; > > + } > > + > > + pads.push_back(pad); > > + } > > + > > + return pads; > > +} > > + > > +/** > > + * \brief Find the path from source to sink > > + * \param[in] source The source entity to start from > > + * \param[in] sink The sink entity name to search for > > + * > > + * Starting from a source entity, determine the shortest path to the target > > + * described by sink. > > + * > > + * If sink can not be found, or a route from source to sink can not be achieved, > > + * an error of -ENOLINK will be returned. > > + * > > + * When successful, the MediaPipeline will internally store the representation > > + * of entities and links to describe the path between the two entities. > > + * > > + * It is expected that the Source entity is a sensor represented by the > > + * CameraSensor class. > > + * > > + * \return 0 on success, a negative errno otherwise > > + */ > > +int MediaPipeline::init(MediaEntity *source, std::string sink) > > +{ > > + /* > > + * Find the shortest path between from the source and the > > + * target entity. > > + */ > > + std::unordered_set<MediaEntity *> visited; > > + std::queue<std::tuple<MediaEntity *, MediaPad *>> queue; > > + > > + /* Remember at each entity where we came from. */ > > + std::unordered_map<MediaEntity *, Entity> parents; > > + MediaEntity *entity = nullptr; > > + MediaEntity *target = nullptr; > > + MediaPad *sinkPad; > > + > > + queue.push({ source, nullptr }); > > + > > + while (!queue.empty()) { > > + std::tie(entity, sinkPad) = queue.front(); > > + queue.pop(); > > + > > + /* Found the target device. */ > > + if (entity->name() == sink) { > > + LOG(MediaPipeline, Debug) > > + << "Found pipeline target " << entity->name(); > > + target = entity; > > + break; > > + } > > + > > + visited.insert(entity); > > + > > + /* > > + * Add direct downstream entities to the search queue. If the > > + * current entity supports the subdev internal routing API, > > + * restrict the search to downstream entities reachable through > > + * active routes. > > + */ > > + > > + std::vector<const MediaPad *> srcPads; > > + bool supportsRouting = false; > > + > > + if (sinkPad) { > > + srcPads = routedSourcePads(sinkPad); > > + if (!srcPads.empty()) > > + supportsRouting = true; > > + } > > + > > + if (srcPads.empty()) { > > + for (const MediaPad *pad : entity->pads()) { > > + if (!(pad->flags() & MEDIA_PAD_FL_SOURCE)) > > + continue; > > + srcPads.push_back(pad); > > + } > > + } > > + > > + for (const MediaPad *srcPad : srcPads) { > > + for (MediaLink *link : srcPad->links()) { > > + MediaEntity *next = link->sink()->entity(); > > + if (visited.find(next) == visited.end()) { > > + queue.push({ next, link->sink() }); > > + > > + Entity e{ entity, supportsRouting, sinkPad, > > + srcPad, link }; > > + parents.insert({ next, e }); > > + } > > + } > > + } > > + } > > + > > + if (!target) { > > + LOG(MediaPipeline, Error) << "Failed to connect " << source->name(); > > + return -ENOLINK; > > + } > > + > > + /* > > + * With the parents, we can follow back our way from the capture device > > + * to the sensor. Store all the entities in the pipeline, from the > > + * camera sensor to the video node, in entities_. > > + */ > > + entities_.push_front({ entity, false, sinkPad, nullptr, nullptr }); > > + > > + for (auto it = parents.find(entity); it != parents.end(); > > + it = parents.find(entity)) { > > + const Entity &e = it->second; > > + entities_.push_front(e); > > + entity = e.entity; > > + } > > + > > + LOG(MediaPipeline, Info) > > + << "Found pipeline: " > > + << utils::join(entities_, " -> ", > > + [](const Entity &e) { > > + std::string s = "["; > > + if (e.sink) > > + s += std::to_string(e.sink->index()) + "|"; > > + s += e.entity->name(); > > + if (e.source) > > + s += "|" + std::to_string(e.source->index()); > > + s += "]"; > > + return s; > > + }); > > + > > + return 0; > > +} > > + > > +/** > > + * \brief Initialise and enable all links through the MediaPipeline > > + * \return 0 on success, or a negative errno otherwise > > + */ > > +int MediaPipeline::initLinks() > > +{ > > + MediaLink *sinkLink = nullptr; > > + for (Entity &e : entities_) { > > + /* Sensor entities have no connected sink. */ > > + if (!sinkLink) { > > + sinkLink = e.sourceLink; > > + continue; > > + } > > + > > + if (!(sinkLink->flags() & MEDIA_LNK_FL_ENABLED)) { > > + LOG(MediaPipeline, Debug) << "Enabling : " << *sinkLink; > > + > > + int ret = sinkLink->setEnabled(true); > > + if (ret < 0) > > + return ret; > > + } > > + > > + sinkLink = e.sourceLink; > > + } > > + > > + return 0; > > +} > > + > > +/** > > + * \brief Configure the entities of this MediaPipeline > > + * \param[in] sensor The configured CameraSensor to propogate > > + * > > + * Propagate formats through each of the entities of the Pipeline, validating > > + * that each one was not adjusted by the driver from the desired format. > > + * > > + * \return 0 on success or a negative errno otherwise > > + */ > > +int MediaPipeline::configure(CameraSensor *sensor, V4L2SubdeviceFormat *format) > > +{ > > + int ret; > > + > > + for (const Entity &e : entities_) { > > + /* The sensor is configured through the CameraSensor */ > > + if (!e.sourceLink) > > + break; > > + > > + MediaLink *link = e.sourceLink; > > + MediaPad *source = link->source(); > > + MediaPad *sink = link->sink(); > > + > > + /* 'format' already contains the sensor configuration */ > > + if (source->entity() != sensor->entity()) { > > + /* todo: Add MediaDevice cache to reduce FD pressure */ > > + V4L2Subdevice subdev(source->entity()); > > + ret = subdev.open(); > > + if (ret) > > + return ret; > > + > > + ret = subdev.getFormat(source->index(), format); > > + if (ret < 0) > > + return ret; > > + } > > + > > + V4L2SubdeviceFormat sourceFormat = *format; > > + /* todo: Add MediaDevice cache to reduce FD pressure */ > > + V4L2Subdevice subdev(sink->entity()); > > + ret = subdev.open(); > > + if (ret) > > + return ret; > > + > > + ret = subdev.setFormat(sink->index(), format); > > + if (ret < 0) > > + return ret; > > + > > + if (format->code != sourceFormat.code || > > + format->size != sourceFormat.size) { > > + LOG(MediaPipeline, Debug) > > + << "Source '" << *source > > + << " produces " << sourceFormat > > + << ", sink '" << *sink > > + << " requires " << format; > > + return -EINVAL; > > + } > > + > > + LOG(MediaPipeline, Debug) > > + << "Link " << *link << " configured with format " > > + << format; > > + } > > + > > + return 0; > > +} > > + > > +} /* namespace libcamera */ > > diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build > > index aa9ab0291854..2c0f8603b231 100644 > > --- a/src/libcamera/meson.build > > +++ b/src/libcamera/meson.build > > @@ -41,6 +41,7 @@ libcamera_internal_sources = files([ > > 'mapped_framebuffer.cpp', > > 'media_device.cpp', > > 'media_object.cpp', > > + 'media_pipeline.cpp', > > 'pipeline_handler.cpp', > > 'process.cpp', > > 'pub_key.cpp', >
diff --git a/include/libcamera/internal/media_pipeline.h b/include/libcamera/internal/media_pipeline.h new file mode 100644 index 000000000000..ca4a86a88a1e --- /dev/null +++ b/include/libcamera/internal/media_pipeline.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas on Board Oy + * + * Media pipeline handler + */ + +#pragma once + +#include <list> +#include <string> +#include <vector> + +namespace libcamera { + +class CameraSensor; +class MediaEntity; +class MediaLink; +class MediaPad; +struct V4L2SubdeviceFormat; + +class MediaPipeline +{ +public: + int init(MediaEntity *source, std::string sink); + int initLinks(); + int configure(CameraSensor *sensor, V4L2SubdeviceFormat *); + +private: + struct Entity { + /* The media entity, always valid. */ + MediaEntity *entity; + /* + * Whether or not the entity is a subdev that supports the + * routing API. + */ + bool supportsRouting; + /* + * The local sink pad connected to the upstream entity, null for + * the camera sensor at the beginning of the pipeline. + */ + const MediaPad *sink; + /* + * The local source pad connected to the downstream entity, null + * for the video node at the end of the pipeline. + */ + const MediaPad *source; + /* + * The link on the source pad, to the downstream entity, null + * for the video node at the end of the pipeline. + */ + MediaLink *sourceLink; + }; + + std::vector<const MediaPad *> routedSourcePads(MediaPad *sink); + std::list<Entity> entities_; +}; + +} /* namespace libcamera */ diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build index 1c5eef9cab80..60a35d3e0357 100644 --- a/include/libcamera/internal/meson.build +++ b/include/libcamera/internal/meson.build @@ -30,6 +30,7 @@ libcamera_internal_headers = files([ 'mapped_framebuffer.h', 'media_device.h', 'media_object.h', + 'media_pipeline.h', 'pipeline_handler.h', 'process.h', 'pub_key.h', diff --git a/src/libcamera/media_pipeline.cpp b/src/libcamera/media_pipeline.cpp new file mode 100644 index 000000000000..dba8084ddc97 --- /dev/null +++ b/src/libcamera/media_pipeline.cpp @@ -0,0 +1,307 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Ideas on Board Oy + * + * Media pipeline support + */ + +#include "libcamera/internal/media_pipeline.h" + +#include <algorithm> +#include <errno.h> +#include <memory> +#include <queue> +#include <tuple> +#include <unordered_map> +#include <unordered_set> + +#include <linux/media.h> + +#include <libcamera/base/log.h> + +#include "libcamera/internal/camera_sensor.h" +#include "libcamera/internal/media_device.h" +#include "libcamera/internal/media_object.h" +#include "libcamera/internal/v4l2_subdevice.h" + +/** + * \file media_pipeline.h + * \brief Provide a representation of a pipeline of devices using the Media + * Controller + */ + +namespace libcamera { + +LOG_DEFINE_CATEGORY(MediaPipeline) + +/** + * \class MediaPipeline + * \brief The MediaPipeline represents a set of entities that together form a + * data path for stream data + * + * A MediaPipeline instance is constructed from a sink and a source between + * two entities in a media graph. + */ + +/** + * \brief Retrieve all source pads connected to a sink pad through active routes + * \param[in] sink The sink pad to examine + * + * Examine the entity using the V4L2 Subdevice Routing API to collect all the + * source pads which are connected with an active route to the sink pad. + * + * \return A vector of source MediaPads + */ +std::vector<const MediaPad *> MediaPipeline::routedSourcePads(MediaPad *sink) +{ + MediaEntity *entity = sink->entity(); + std::unique_ptr<V4L2Subdevice> subdev = + std::make_unique<V4L2Subdevice>(entity); + + int ret = subdev->open(); + if (ret < 0) + return {}; + + V4L2Subdevice::Routing routing = {}; + ret = subdev->getRouting(&routing, V4L2Subdevice::ActiveFormat); + if (ret < 0) + return {}; + + std::vector<const MediaPad *> pads; + + for (const V4L2Subdevice::Route &route : routing) { + if (sink->index() != route.sink.pad || + !(route.flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE)) + continue; + + const MediaPad *pad = entity->getPadByIndex(route.source.pad); + if (!pad) { + LOG(MediaPipeline, Warning) + << "Entity " << entity->name() + << " has invalid route source pad " + << route.source.pad; + } + + pads.push_back(pad); + } + + return pads; +} + +/** + * \brief Find the path from source to sink + * \param[in] source The source entity to start from + * \param[in] sink The sink entity name to search for + * + * Starting from a source entity, determine the shortest path to the target + * described by sink. + * + * If sink can not be found, or a route from source to sink can not be achieved, + * an error of -ENOLINK will be returned. + * + * When successful, the MediaPipeline will internally store the representation + * of entities and links to describe the path between the two entities. + * + * It is expected that the Source entity is a sensor represented by the + * CameraSensor class. + * + * \return 0 on success, a negative errno otherwise + */ +int MediaPipeline::init(MediaEntity *source, std::string sink) +{ + /* + * Find the shortest path between from the source and the + * target entity. + */ + std::unordered_set<MediaEntity *> visited; + std::queue<std::tuple<MediaEntity *, MediaPad *>> queue; + + /* Remember at each entity where we came from. */ + std::unordered_map<MediaEntity *, Entity> parents; + MediaEntity *entity = nullptr; + MediaEntity *target = nullptr; + MediaPad *sinkPad; + + queue.push({ source, nullptr }); + + while (!queue.empty()) { + std::tie(entity, sinkPad) = queue.front(); + queue.pop(); + + /* Found the target device. */ + if (entity->name() == sink) { + LOG(MediaPipeline, Debug) + << "Found pipeline target " << entity->name(); + target = entity; + break; + } + + visited.insert(entity); + + /* + * Add direct downstream entities to the search queue. If the + * current entity supports the subdev internal routing API, + * restrict the search to downstream entities reachable through + * active routes. + */ + + std::vector<const MediaPad *> srcPads; + bool supportsRouting = false; + + if (sinkPad) { + srcPads = routedSourcePads(sinkPad); + if (!srcPads.empty()) + supportsRouting = true; + } + + if (srcPads.empty()) { + for (const MediaPad *pad : entity->pads()) { + if (!(pad->flags() & MEDIA_PAD_FL_SOURCE)) + continue; + srcPads.push_back(pad); + } + } + + for (const MediaPad *srcPad : srcPads) { + for (MediaLink *link : srcPad->links()) { + MediaEntity *next = link->sink()->entity(); + if (visited.find(next) == visited.end()) { + queue.push({ next, link->sink() }); + + Entity e{ entity, supportsRouting, sinkPad, + srcPad, link }; + parents.insert({ next, e }); + } + } + } + } + + if (!target) { + LOG(MediaPipeline, Error) << "Failed to connect " << source->name(); + return -ENOLINK; + } + + /* + * With the parents, we can follow back our way from the capture device + * to the sensor. Store all the entities in the pipeline, from the + * camera sensor to the video node, in entities_. + */ + entities_.push_front({ entity, false, sinkPad, nullptr, nullptr }); + + for (auto it = parents.find(entity); it != parents.end(); + it = parents.find(entity)) { + const Entity &e = it->second; + entities_.push_front(e); + entity = e.entity; + } + + LOG(MediaPipeline, Info) + << "Found pipeline: " + << utils::join(entities_, " -> ", + [](const Entity &e) { + std::string s = "["; + if (e.sink) + s += std::to_string(e.sink->index()) + "|"; + s += e.entity->name(); + if (e.source) + s += "|" + std::to_string(e.source->index()); + s += "]"; + return s; + }); + + return 0; +} + +/** + * \brief Initialise and enable all links through the MediaPipeline + * \return 0 on success, or a negative errno otherwise + */ +int MediaPipeline::initLinks() +{ + MediaLink *sinkLink = nullptr; + for (Entity &e : entities_) { + /* Sensor entities have no connected sink. */ + if (!sinkLink) { + sinkLink = e.sourceLink; + continue; + } + + if (!(sinkLink->flags() & MEDIA_LNK_FL_ENABLED)) { + LOG(MediaPipeline, Debug) << "Enabling : " << *sinkLink; + + int ret = sinkLink->setEnabled(true); + if (ret < 0) + return ret; + } + + sinkLink = e.sourceLink; + } + + return 0; +} + +/** + * \brief Configure the entities of this MediaPipeline + * \param[in] sensor The configured CameraSensor to propogate + * + * Propagate formats through each of the entities of the Pipeline, validating + * that each one was not adjusted by the driver from the desired format. + * + * \return 0 on success or a negative errno otherwise + */ +int MediaPipeline::configure(CameraSensor *sensor, V4L2SubdeviceFormat *format) +{ + int ret; + + for (const Entity &e : entities_) { + /* The sensor is configured through the CameraSensor */ + if (!e.sourceLink) + break; + + MediaLink *link = e.sourceLink; + MediaPad *source = link->source(); + MediaPad *sink = link->sink(); + + /* 'format' already contains the sensor configuration */ + if (source->entity() != sensor->entity()) { + /* todo: Add MediaDevice cache to reduce FD pressure */ + V4L2Subdevice subdev(source->entity()); + ret = subdev.open(); + if (ret) + return ret; + + ret = subdev.getFormat(source->index(), format); + if (ret < 0) + return ret; + } + + V4L2SubdeviceFormat sourceFormat = *format; + /* todo: Add MediaDevice cache to reduce FD pressure */ + V4L2Subdevice subdev(sink->entity()); + ret = subdev.open(); + if (ret) + return ret; + + ret = subdev.setFormat(sink->index(), format); + if (ret < 0) + return ret; + + if (format->code != sourceFormat.code || + format->size != sourceFormat.size) { + LOG(MediaPipeline, Debug) + << "Source '" << *source + << " produces " << sourceFormat + << ", sink '" << *sink + << " requires " << format; + return -EINVAL; + } + + LOG(MediaPipeline, Debug) + << "Link " << *link << " configured with format " + << format; + } + + return 0; +} + +} /* namespace libcamera */ diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build index aa9ab0291854..2c0f8603b231 100644 --- a/src/libcamera/meson.build +++ b/src/libcamera/meson.build @@ -41,6 +41,7 @@ libcamera_internal_sources = files([ 'mapped_framebuffer.cpp', 'media_device.cpp', 'media_object.cpp', + 'media_pipeline.cpp', 'pipeline_handler.cpp', 'process.cpp', 'pub_key.cpp',