Use character literals instead of single character long strings. The main purpose of this change is to work around a GCC bug that results in `-Wrestrict` warnings at certain optimization levels in C++20. libstdc++ 13 has been adjusted to avoid triggering the compiler warning. Link: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105329 Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Naushir Patuck <naush@raspberrypi.com>
351 lines
8.7 KiB
C++
351 lines
8.7 KiB
C++
/* 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 <queue>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
#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.
|
|
*/
|
|
|
|
/**
|
|
* \struct MediaPipeline::Entity
|
|
* \brief A node composing the media pipeline
|
|
*
|
|
* The MediaPipeline::Entity structure stores how a MediaEntity composing a
|
|
* media pipeline is connected to other media entities. It stores pointers
|
|
* to the source pad, the sink pad and the media link traversed by the media
|
|
* pipeline, as well as a flag that reports if the entity supports internal
|
|
* routing.
|
|
*/
|
|
|
|
/**
|
|
* \var MediaPipeline::Entity::entity
|
|
* \brief Pointer to the libcamera::MediaEntity, always valid
|
|
*/
|
|
|
|
/**
|
|
* \var MediaPipeline::Entity::supportsRouting
|
|
* \brief Whether or not the entity is a subdev that supports the routing API
|
|
*/
|
|
|
|
/**
|
|
* \var MediaPipeline::Entity::sink
|
|
* \brief The local libcamera::MediaPad sink pad connected to the upstream entity,
|
|
* null for the camera sensor at the beginning of the pipeline
|
|
*/
|
|
|
|
/**
|
|
* \var MediaPipeline::Entity::source
|
|
* \brief The local libcamera::MediaPad source pad connected to the upstream entity,
|
|
* null for the last node at the end of the pipeline
|
|
*/
|
|
|
|
/**
|
|
* \var MediaPipeline::Entity::sourceLink
|
|
* \brief The link on the libcamera::MediaLink source pad, to the downstream entity,
|
|
* null for the last node at the end of the pipeline
|
|
*/
|
|
|
|
/**
|
|
* \fn MediaPipeline::entities()
|
|
* \brief Retrieve list of entities composing the media pipeline
|
|
* \return The list of MediaPipeline::Entity entities composing the media
|
|
* pipeline
|
|
*/
|
|
|
|
/**
|
|
* \brief Retrieve all source pads connected to a sink pad through active routes
|
|
*
|
|
* 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
|
|
*/
|
|
static std::vector<const MediaPad *> 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, Error)
|
|
<< "Entity " << entity->name()
|
|
<< " has invalid route source pad "
|
|
<< route.source.pad;
|
|
return {};
|
|
}
|
|
|
|
pads.push_back(pad);
|
|
}
|
|
|
|
return pads;
|
|
}
|
|
|
|
/**
|
|
* \brief Find the path from source to sink
|
|
*
|
|
* Starting from a source entity, determine the shortest path to the target
|
|
* described by \a sink.
|
|
*
|
|
* If \a 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.
|
|
*
|
|
* \return 0 on success, a negative errno otherwise
|
|
*/
|
|
int MediaPipeline::init(MediaEntity *source, std::string_view sink)
|
|
{
|
|
/*
|
|
* Find the shortest path between from the Camera Sensor 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 *> pads;
|
|
bool supportsRouting = false;
|
|
|
|
if (sinkPad) {
|
|
pads = routedSourcePads(sinkPad);
|
|
if (!pads.empty())
|
|
supportsRouting = true;
|
|
}
|
|
|
|
if (pads.empty()) {
|
|
for (const MediaPad *pad : entity->pads()) {
|
|
if (!(pad->flags() & MEDIA_PAD_FL_SOURCE))
|
|
continue;
|
|
pads.push_back(pad);
|
|
}
|
|
}
|
|
|
|
for (const MediaPad *pad : pads) {
|
|
for (MediaLink *link : pad->links()) {
|
|
MediaEntity *next = link->sink()->entity();
|
|
if (visited.find(next) == visited.end()) {
|
|
queue.push({ next, link->sink() });
|
|
|
|
Entity e{ entity, supportsRouting, sinkPad, pad, link };
|
|
parents.insert({ next, e });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!target) {
|
|
LOG(MediaPipeline, Error)
|
|
<< "Failed to connect " << source->name()
|
|
<< " to " << sink;
|
|
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()
|
|
{
|
|
int ret = 0;
|
|
|
|
MediaLink *sinkLink = nullptr;
|
|
for (Entity &e : entities_) {
|
|
/* Sensor entities have no connected sink. */
|
|
if (!sinkLink) {
|
|
sinkLink = e.sourceLink;
|
|
continue;
|
|
}
|
|
|
|
LOG(MediaPipeline, Debug) << "Enabling : " << *sinkLink;
|
|
|
|
if (!(sinkLink->flags() & MEDIA_LNK_FL_ENABLED)) {
|
|
ret = sinkLink->setEnabled(true);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
sinkLink = e.sourceLink;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* \brief Configure the entities of this MediaPipeline
|
|
*
|
|
* 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 */
|