[libcamera-devel,2/2] libcamera: Add MediaDevice class

Message ID 1545324285-16730-3-git-send-email-jacopo@jmondi.org
State Accepted
Headers show
Series
  • Add MediaDevice and associated MediaObjects
Related show

Commit Message

Jacopo Mondi Dec. 20, 2018, 4:44 p.m. UTC
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@jmondi.org>
---
 src/libcamera/include/media_device.h |  72 +++++
 src/libcamera/media_device.cpp       | 604 +++++++++++++++++++++++++++++++++++
 src/libcamera/meson.build            |   1 +
 3 files changed, 677 insertions(+)
 create mode 100644 src/libcamera/include/media_device.h
 create mode 100644 src/libcamera/media_device.cpp

Patch

diff --git a/src/libcamera/include/media_device.h b/src/libcamera/include/media_device.h
new file mode 100644
index 0000000..3aa562a
--- /dev/null
+++ b/src/libcamera/include/media_device.h
@@ -0,0 +1,72 @@ 
+/* 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();
+
+	std::string name() { return name_; }
+	std::string path() { 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(MediaPad *source, 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..4b3dd2f
--- /dev/null
+++ b/src/libcamera/media_device.cpp
@@ -0,0 +1,604 @@ 
+/* 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.model;
+
+	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)
+{
+	std::map<unsigned int, MediaObject *>::iterator 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)
+{
+	std::vector<MediaEntity *>::iterator 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 == nullptr) {
+			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 == nullptr) {
+			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 == nullptr) {
+			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) {
+		std::map<std::string, std::string>::iterator it;
+
+		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);
+
+	struct media_v2_entity *_ptr_e =
+				new struct media_v2_entity[topology.num_entities];
+	topology.ptr_entities = reinterpret_cast<__u64>(_ptr_e);
+
+	struct media_v2_pad *_ptr_p =
+				new struct media_v2_pad[topology.num_pads];
+	topology.ptr_pads = reinterpret_cast<__u64>(_ptr_p);
+
+	struct media_v2_link *_ptr_l =
+				new struct 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 == nullptr)
+		return;
+
+	MediaEntity *remoteEntity =
+			dynamic_cast<MediaEntity *>
+			(getObject(remotePad->entity()));
+	if (remoteEntity == nullptr)
+		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(MediaPad *source, 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 == nullptr)
+					return -ENOENT;
+
+				/* Also get entity to make sure IDs are ok. */
+				MediaEntity *sinkEntity =
+						dynamic_cast<MediaEntity *>
+						(getObject(sinkPad->entity()));
+				if (sinkEntity == nullptr)
+					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 == nullptr) {
+		LOG(Error) << "Entity name: " << source << "not found";
+		return -ENOENT;
+	}
+
+	MediaEntity *sinkEntity = getEntityByName(sink);
+	if (sinkEntity == nullptr) {
+		LOG(Error) << "Entity name: " << source << "not found";
+		return -ENOENT;
+	}
+
+	MediaPad *sourcePad = sourceEntity->getPadByIndex(sourceIdx);
+	if (sourcePad == nullptr) {
+		LOG(Error) << "Pad " << sourceIdx << "not found in entity "
+			   << sourceEntity->name();
+		return -ENOENT;
+	}
+
+	MediaPad *sinkPad = sinkEntity->getPadByIndex(sinkIdx);
+	if (sinkPad == nullptr) {
+		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 == nullptr) {
+		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([