[libcamera-devel] [PATCH RESEND v2 2/4] libcamera: Add MediaDevice class
Jacopo Mondi
jacopo at jmondi.org
Fri Dec 28 08:57:41 CET 2018
The MediaDevice object implements handling and configuration of the media
graph associated with a V4L2 media device.
The class allows enumeration of all pads, links and entities registered in
the media graph, and provides methods to setup and reset media links.
Signed-off-by: Jacopo Mondi <jacopo at jmondi.org>
---
src/libcamera/include/media_device.h | 71 ++++
src/libcamera/media_device.cpp | 596 +++++++++++++++++++++++++++
src/libcamera/meson.build | 1 +
3 files changed, 668 insertions(+)
create mode 100644 src/libcamera/include/media_device.h
create mode 100644 src/libcamera/media_device.cpp
diff --git a/src/libcamera/include/media_device.h b/src/libcamera/include/media_device.h
new file mode 100644
index 0000000..642eea9
--- /dev/null
+++ b/src/libcamera/include/media_device.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2018, Google Inc.
+ *
+ * media_device.h - Media device handler
+ */
+#ifndef __LIBCAMERA_MEDIA_DEVICE_H__
+#define __LIBCAMERA_MEDIA_DEVICE_H__
+
+#include <map>
+#include <string>
+#include <sstream>
+#include <vector>
+
+#include <linux/media.h>
+
+#include "log.h"
+#include "media_object.h"
+
+namespace libcamera {
+
+class MediaDevice
+{
+public:
+ MediaDevice() : fd_(-1) { };
+ ~MediaDevice();
+
+ const std::string name() const { return name_; }
+ const std::string path() const { return path_; }
+
+ int open(const std::string &path);
+ int close();
+ int enumerate(std::map<std::string, std::string> &entitiesMap);
+ void dumpGraph(std::ostream &os);
+
+ int resetLinks();
+ int link(const std::string &source, unsigned int sourceIdx,
+ const std::string &sink, unsigned int sinkIdx,
+ unsigned int flags);
+
+private:
+ /** The media device file descriptor */
+ int fd_;
+ /** The media device name as returned by MEDIA_IOC_DEVICE_INFO */
+ std::string name_;
+ /** The media device path */
+ std::string path_;
+
+ std::map<unsigned int, MediaObject *> mediaObjects_;
+ MediaObject *getObject(unsigned int id);
+ void addObject(MediaObject *obj);
+ void deleteObjects();
+
+ std::vector<MediaEntity *> entities_;
+ MediaEntity *getEntityByName(const std::string &name);
+
+ int enumerateEntities(std::map<std::string, std::string> &entitiesMap,
+ struct media_v2_topology &topology);
+ int enumeratePads(struct media_v2_topology &topology);
+ int enumerateLinks(struct media_v2_topology &topology);
+
+ int setupLink(const MediaPad *source, const MediaPad *sink,
+ MediaLink *link, unsigned int flags);
+
+ void dumpLocal(MediaEntity *e, MediaPad *p, std::ostream &os);
+ void dumpRemote(MediaLink *l, std::ostream &os);
+ void dumpLink(MediaLink *l, std::ostream &os);
+};
+
+} /* namespace libcamera */
+#endif /* __LIBCAMERA_MEDIA_DEVICE_H__ */
diff --git a/src/libcamera/media_device.cpp b/src/libcamera/media_device.cpp
new file mode 100644
index 0000000..b98d62f
--- /dev/null
+++ b/src/libcamera/media_device.cpp
@@ -0,0 +1,596 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2018, Google Inc.
+ *
+ * media_device.cpp - Media device handler
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <linux/media.h>
+
+#include "log.h"
+#include "media_device.h"
+
+/**
+ * \file media_device.h
+ */
+namespace libcamera {
+
+/**
+ * \class MediaDevice
+ * \brief Media device handler
+ *
+ * MediaDevice handles the media graph associated with a V4L2 media device.
+ */
+
+/**
+ * \fn MediaDevice::~MediaDevice()
+ * \brief Close the media device file descriptor and release entities
+ */
+MediaDevice::~MediaDevice()
+{
+ if (fd_ > -1)
+ ::close(fd_);
+ deleteObjects();
+}
+
+/**
+ * \fn MediaDevice::name()
+ * \brief Return the media device name
+ */
+
+/**
+ * \fn MediaDevice::path()
+ * \brief Return the media device path node associated with this MediaDevice
+ */
+
+/**
+ * \fn MediaDevice::deleteObjects()
+ * \brief Delete all registered entities in the MediaDevice object
+ */
+void MediaDevice::deleteObjects()
+{
+ for (auto const &e : mediaObjects_)
+ delete e.second;
+
+ mediaObjects_.clear();
+ entities_.clear();
+}
+
+/**
+ * \fn int MediaDevice::open(std::string)
+ * \brief Open a media device and initialize its components.
+ * \param path The media device path
+ */
+int MediaDevice::open(const std::string &path)
+{
+ fd_ = ::open(path.c_str(), O_RDWR);
+ if (fd_ < 0) {
+ LOG(Error) << "Failed to open media device at " << path
+ << ": " << strerror(errno);
+ return -errno;
+ }
+ path_ = path;
+
+ struct media_device_info info = { };
+ int ret = ioctl(fd_, MEDIA_IOC_DEVICE_INFO, &info);
+ if (ret) {
+ LOG(Error) << "Failed to get media device info "
+ << ": " << strerror(errno);
+ return -errno;
+ }
+
+ name_ = info.driver;
+
+ return 0;
+}
+
+/**
+ * \fn MediaDevice::close()
+ * \brief Close the file descriptor associated with the media device
+ */
+int MediaDevice::close()
+{
+ if (fd_ > -1)
+ return ::close(fd_);
+
+ return 0;
+}
+
+void MediaDevice::addObject(MediaObject *obj)
+{
+
+ if (mediaObjects_.find(obj->id()) != mediaObjects_.end()) {
+ LOG(Error) << "Element with id " << obj->id()
+ << " already enumerated.";
+ return;
+ }
+
+ mediaObjects_[obj->id()] = obj;
+}
+
+MediaObject *MediaDevice::getObject(unsigned int id)
+{
+ auto it = mediaObjects_.find(id);
+ return (it == mediaObjects_.end()) ?
+ nullptr : it->second;
+}
+
+/**
+ * \fn MediaDevice::getEntityByName(std::string)
+ * \brief Return entity with name \a name
+ * \param name The entity name
+ */
+MediaEntity *MediaDevice::getEntityByName(const std::string &name)
+{
+ auto it = entities_.begin();
+
+ while (it != entities_.end()) {
+ MediaEntity *e = *it;
+ if (!(e->name().compare(name)))
+ return e;
+ it++;
+ }
+
+ return nullptr;
+}
+
+/**
+ * \fn MediaDevice::enumerateLinks(struct media_v2_topology &topology)
+ * \brief Enumerate all links in the system and associate them with their
+ * source and sink pads
+ * \param topology The media topology as returned by MEDIA_IOC_G_TOPOLOGY
+ */
+int MediaDevice::enumerateLinks(struct media_v2_topology &topology)
+{
+ struct media_v2_link *link = reinterpret_cast<struct media_v2_link *>
+ (topology.ptr_links);
+
+ for (unsigned int i = 0; i < topology.num_links; i++, link++) {
+ /*
+ * Skip links between entities and interfaces: we only care
+ * about pad-2-pad links here.
+ */
+ if ((link->flags & MEDIA_LNK_FL_LINK_TYPE) ==
+ MEDIA_LNK_FL_INTERFACE_LINK)
+ continue;
+
+ MediaLink *mediaLink = new MediaLink(link);
+ addObject(mediaLink);
+
+ /* Store reference to this mediaLink in the link's source pad. */
+ MediaPad *mediaPad = dynamic_cast<MediaPad *>
+ (getObject(mediaLink->source()));
+ if (!mediaPad) {
+ LOG(Error) << "Failed to find pad with id: "
+ << mediaLink->source();
+ return -ENODEV;
+ }
+ mediaPad->addLink(mediaLink);
+
+ /* Store reference to this mediaLink in the link's sink pad. */
+ mediaPad = dynamic_cast<MediaPad *>(getObject(mediaLink->sink()));
+ if (!mediaPad) {
+ LOG(Error) << "Failed to find pad with id: "
+ << mediaLink->sink();
+ return -ENODEV;
+ }
+ mediaPad->addLink(mediaLink);
+ }
+
+ return 0;
+}
+
+/**
+ * \fn MediaDevice::enumeratePads(struct media_v2_topology &topology)
+ * \brief Enumerate all pads in the system and associate them with the
+ * entity they belong to
+ * \param topology The media topology as returned by MEDIA_IOC_G_TOPOLOGY
+ */
+int MediaDevice::enumeratePads(struct media_v2_topology &topology)
+{
+ struct media_v2_pad *pad = reinterpret_cast<struct media_v2_pad *>
+ (topology.ptr_pads);
+
+ for (unsigned int i = 0; i < topology.num_pads; i++, pad++) {
+ MediaPad *mediaPad = new MediaPad(pad);
+ addObject(mediaPad);
+
+ /* Store a reference to this MediaPad in pad's entity. */
+ MediaEntity *mediaEntity = dynamic_cast<MediaEntity *>
+ (getObject(mediaPad->entity()));
+ if (!mediaEntity) {
+ LOG(Error) << "Failed to find entity with id: "
+ << mediaPad->entity();
+ return -ENODEV;
+ }
+
+ mediaEntity->addPad(mediaPad);
+ }
+
+ return 0;
+}
+
+/**
+ * \fn MediaDevice::enumerateEntities(std::map<std::string, std::string> &,
+ * struct media_v2_topology &topology)
+ * \brief Enumerate and initialize entities in the media graph
+ * \param entitiesMap Map entities names to their video (sub)device node
+ * \param topology The media topology as returned by MEDIA_IOC_G_TOPOLOGY
+ *
+ * Associate the video (sub)device path to the entity name as returned by
+ * MEDIA_IOC_G_TOPOLOGY
+ */
+int MediaDevice::enumerateEntities(std::map<std::string, std::string> &entitiesMap,
+ struct media_v2_topology &topology)
+{
+ struct media_v2_entity *entities = reinterpret_cast<struct media_v2_entity *>
+ (topology.ptr_entities);
+
+ for (unsigned int i = 0; i < topology.num_entities; ++i) {
+ auto it = entitiesMap.find(entities[i].name);
+ if (it == entitiesMap.end()) {
+ LOG(Error) << "Entity " << entities[i].name
+ << " not found in media entities map";
+ return -ENOENT;
+ }
+
+ MediaEntity *entity = new MediaEntity(&entities[i]);
+ if (entity->setDevice(it->second)) {
+ delete entity;
+ goto delete_entities;
+ }
+
+ addObject(entity);
+ entities_.push_back(entity);
+ }
+
+ return 0;
+
+delete_entities:
+ deleteObjects();
+
+ return -errno;
+}
+
+/**
+ * \fn MediaDevice::enumerate(std::map<std::string, std::string>)
+ * \brief Enumerate the media graph topology
+ * \param entitiesMap Map entities names to their video (sub)device node
+ * FIXME: this is statically provided by the caller at the moment.
+ *
+ * This functions enumerates all media objects, registered in the media graph,
+ * through the MEDIA_IOC_G_TOPOLOGY ioctl. For each returned entity,
+ * it creates and store its representation for later reuse.
+ */
+int MediaDevice::enumerate(std::map<std::string, std::string> &entitiesMap)
+{
+ struct media_v2_topology topology = { };
+ unsigned int num_interfaces;
+ unsigned int num_links;
+ unsigned int num_pads;
+ unsigned int num_ent;
+
+ do {
+ num_ent = topology.num_entities;
+ num_pads = topology.num_pads;
+ num_links = topology.num_links;
+ num_interfaces = topology.num_interfaces;
+
+ /* Call G_TOPOLOGY the first time here to enumerate .*/
+ if (ioctl(fd_, MEDIA_IOC_G_TOPOLOGY, &topology)) {
+ LOG(Error) << "Failed to enumerate media topology on"
+ << path_ << ": " << strerror(errno);
+ return -errno;
+ }
+
+ /*
+ * Repeat the call until we don't get a 'stable' number
+ * of media objects.
+ */
+ } while (num_ent != topology.num_entities ||
+ num_pads != topology.num_pads ||
+ num_links != topology.num_links ||
+ num_interfaces != topology.num_interfaces);
+
+ auto *_ptr_e = new media_v2_entity[topology.num_entities];
+ topology.ptr_entities = reinterpret_cast<__u64>(_ptr_e);
+
+ auto *_ptr_p = new media_v2_pad[topology.num_pads];
+ topology.ptr_pads = reinterpret_cast<__u64>(_ptr_p);
+
+ auto *_ptr_l = new media_v2_link[topology.num_links];
+ topology.ptr_links = reinterpret_cast<__u64>(_ptr_l);
+
+ /* Call G_TOPOLOGY again, this time with memory reserved. */
+ int ret = ioctl(fd_, MEDIA_IOC_G_TOPOLOGY, &topology);
+ if (ret < 0) {
+ LOG(Error) << "Failed to enumerate media topology on " << path_
+ << ": " << strerror(errno);
+ ret = -errno;
+ goto error_free_mem;
+ }
+
+ ret = enumerateEntities(entitiesMap, topology);
+ if (ret)
+ goto error_free_mem;
+
+ ret = enumeratePads(topology);
+ if (ret)
+ goto error_free_objs;
+
+ ret = enumerateLinks(topology);
+ if (ret)
+ goto error_free_objs;
+
+ delete[] _ptr_e;
+ delete[] _ptr_p;
+ delete[] _ptr_l;
+
+ return 0;
+
+error_free_objs:
+ deleteObjects();
+
+error_free_mem:
+ delete[] _ptr_e;
+ delete[] _ptr_p;
+ delete[] _ptr_l;
+
+ return ret;
+}
+
+void MediaDevice::dumpLocal(MediaEntity *e, MediaPad *p, std::ostream &os)
+{
+ os << "\t \"" << e->name() << "\"["
+ << p->index() << "]";
+}
+
+void MediaDevice::dumpRemote(MediaLink *l, std::ostream &os)
+{
+
+ MediaPad *remotePad = dynamic_cast<MediaPad *>
+ (getObject(l->sink()));
+ if (!remotePad)
+ return;
+
+ MediaEntity *remoteEntity = dynamic_cast<MediaEntity *>
+ (getObject(remotePad->entity()));
+ if (!remoteEntity)
+ return;
+
+ os << "\"" << remoteEntity->name() << "\"["
+ << remotePad->index() << "]";
+}
+
+void MediaDevice::dumpLink(MediaLink *l, std::ostream &os)
+{
+ unsigned int flags = l->flags();
+
+ os << " [";
+ if (flags) {
+ os << (flags & MEDIA_LNK_FL_ENABLED ? "ENABLED," : "")
+ << (flags & MEDIA_LNK_FL_IMMUTABLE ? "IMMUTABLE" : "");
+ }
+ os << "]\n";
+}
+
+/**
+ * \fn MediaDevice::dumpGraph(std::ostream)
+ * \brief Dump the media device topology in textual form to an output stream
+ * \param os The output stream where to append the printed topology to
+ */
+void MediaDevice::dumpGraph(std::ostream &os)
+{
+ os << "\n" << name_ << " - " << path_ << "\n\n";
+
+ for (auto const &e : entities_) {
+ os << "\"" << e->name() << "\"\n";
+
+ for (auto const &p : e->sinks()) {
+ os << " [" << p->index() << "]" << ": Sink\n";
+ for (auto const &l : p->links()) {
+ dumpLocal(e, p, os);
+ os << " <- ";
+ dumpRemote(l, os);
+ dumpLink(l, os);
+ }
+ os << "\n";
+ }
+
+ for (auto const &p : e->sources()) {
+ os << " [" << p->index() << "]" << ": Source\n";
+ for (auto const &l : p->links()) {
+ dumpLocal(e, p, os);
+ os << " -> ";
+ dumpRemote(l, os);
+ dumpLink(l, os);
+ }
+ os << "\n";
+ }
+ }
+}
+
+/**
+ * \fn MediaDevice::setupLink(MediaPad *source, MediaPad *sink)
+ * \brief Apply \a flags to the link between \a source and \a sink pads
+ * \param source The source MediaPad
+ * \param sink The sink MediaPad
+ * \param link The MediaLink to operate on
+ * \param flags Flags to be applied to the link (MEDIA_LNK_FL_*)
+ */
+int MediaDevice::setupLink(const MediaPad *source, const MediaPad *sink,
+ MediaLink *link, unsigned int flags)
+{
+ struct media_link_desc linkDesc = { };
+
+ linkDesc.source.entity = source->entity();
+ linkDesc.source.index = source->index();
+ linkDesc.source.flags = MEDIA_PAD_FL_SOURCE;
+
+ linkDesc.sink.entity = sink->entity();
+ linkDesc.sink.index = sink->index();
+ linkDesc.sink.flags = MEDIA_PAD_FL_SINK;
+
+ linkDesc.flags = flags;
+
+ if (ioctl(fd_, MEDIA_IOC_SETUP_LINK, &linkDesc)) {
+ LOG(Error) << "Failed to setup link: "
+ << strerror(errno);
+ return -errno;
+ }
+
+ link->setFlags(0);
+
+ return 0;
+}
+
+/**
+ * \fn MediaDevice::resetLinks()
+ * \brief Reset all links on the media graph
+ *
+ * Walk all registered entities, and disable all links from their
+ * source pads to other pads.
+ */
+int MediaDevice::resetLinks()
+{
+ for (MediaEntity *e : entities_) {
+ for (MediaPad *sourcePad : e->sources()) {
+ for (MediaLink *l : sourcePad->links()) {
+ /*
+ * Do not reset links that are not enabled
+ * or immutable.
+ */
+ if (l->flags() & MEDIA_LNK_FL_IMMUTABLE)
+ continue;
+
+ if (!(l->flags() & MEDIA_LNK_FL_ENABLED))
+ continue;
+
+ /* Get the remote sink pad. */
+ MediaPad *sinkPad = dynamic_cast<MediaPad *>
+ (getObject(l->sink()));
+ if (!sinkPad)
+ return -ENOENT;
+
+ /* Also get entity to make sure IDs are ok. */
+ MediaEntity *sinkEntity =
+ dynamic_cast<MediaEntity *>
+ (getObject(sinkPad->entity()));
+ if (!sinkEntity)
+ return -ENOENT;
+
+ int ret = setupLink(sourcePad, sinkPad, l, 0);
+ if (ret) {
+ LOG(Error) << "Link reset failed: "
+ << e->name() << "["
+ << sourcePad->index()
+ << "] -> "
+ << sinkEntity->name() << "["
+ << sinkPad->index() << "]";
+ return ret;
+ }
+
+ LOG(Info) << "Link reset: "
+ << e->name() << "["
+ << sourcePad->index()
+ << "] -> "
+ << sinkEntity->name() << "["
+ << sinkPad->index() << "]";
+ }
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * \fn MediaDevice::link(std::string, unsigned int, std::string, unsigned int)
+ * \brief Setup a link identified by the entities name and their source and
+ * sink pad indexes
+ * \param source The source entity name
+ * \param sourceIdx The source pad index
+ * \param sink The sink entity name
+ * \param sinkIdx The sink pad index
+ * \param flags The link setup flag (see MEDIA_LNK_FL_*)
+ */
+int MediaDevice::link(const std::string &source, unsigned int sourceIdx,
+ const std::string &sink, unsigned int sinkIdx,
+ unsigned int flags)
+{
+
+ /* Make sure the supplied link is well formed. */
+ MediaEntity *sourceEntity = getEntityByName(source);
+ if (!sourceEntity) {
+ LOG(Error) << "Entity name: " << source << "not found";
+ return -ENOENT;
+ }
+
+ MediaEntity *sinkEntity = getEntityByName(sink);
+ if (!sinkEntity) {
+ LOG(Error) << "Entity name: " << source << "not found";
+ return -ENOENT;
+ }
+
+ const MediaPad *sourcePad = sourceEntity->getPadByIndex(sourceIdx);
+ if (!sourcePad) {
+ LOG(Error) << "Pad " << sourceIdx << "not found in entity "
+ << sourceEntity->name();
+ return -ENOENT;
+ }
+
+ const MediaPad *sinkPad = sinkEntity->getPadByIndex(sinkIdx);
+ if (!sinkPad) {
+ LOG(Error) << "Pad " << sinkIdx << "not found in entity "
+ << sinkEntity->name();
+ return -ENOENT;
+ }
+
+ /*
+ * Walk all links in the source and search for an entry matching the
+ * pad ids. If none, the requested link does not exists.
+ */
+ MediaLink *validLink = nullptr;
+ for (MediaLink *link : sourcePad->links()) {
+ if (link->source() != sourcePad->id())
+ continue;
+
+ if (link->sink() != sinkPad->id())
+ continue;
+
+ validLink = link;
+ break;
+ }
+
+ if (!validLink) {
+ LOG(Error) << "Link not found"
+ << "\"" << sourceEntity->name() << "\"["
+ << sourcePad->index() << "] -> "
+ << "\"" << sinkEntity->name() << "\"["
+ << sinkPad->index() << "]";
+ return -EINVAL;
+ }
+
+ int ret = setupLink(sourcePad, sinkPad, validLink, flags);
+ if (ret)
+ return ret;
+
+ LOG(Info) << "Setup link: "
+ << "\"" << sourceEntity->name() << "\"["
+ << sourcePad->index() << "] -> "
+ << "\"" << sinkEntity->name() << "\"["
+ << sinkPad->index() << "]"
+ << " [" << flags << "]";
+
+ return 0;
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index da06eba..4cac687 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -2,6 +2,7 @@ libcamera_sources = files([
'log.cpp',
'main.cpp',
'media_object.cpp',
+ 'media_device.cpp',
])
libcamera_headers = files([
--
2.20.1
More information about the libcamera-devel
mailing list