libcamera: Add MediaDevice class
The MediaDevice object implements handling and configuration of the media graph associated with a media device. The class allows enumeration of all pads, links and entities registered in the media graph. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
This commit is contained in:
parent
f0e16f0389
commit
363824662f
3 changed files with 433 additions and 0 deletions
56
src/libcamera/include/media_device.h
Normal file
56
src/libcamera/include/media_device.h
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
/* 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 <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <linux/media.h>
|
||||||
|
|
||||||
|
#include "media_object.h"
|
||||||
|
|
||||||
|
namespace libcamera {
|
||||||
|
|
||||||
|
class MediaDevice
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MediaDevice(const std::string &devnode);
|
||||||
|
~MediaDevice();
|
||||||
|
|
||||||
|
int open();
|
||||||
|
void close();
|
||||||
|
|
||||||
|
int populate();
|
||||||
|
|
||||||
|
const std::string driver() const { return driver_; }
|
||||||
|
const std::string devnode() const { return devnode_; }
|
||||||
|
const std::vector<MediaEntity *> &entities() const { return entities_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string driver_;
|
||||||
|
std::string devnode_;
|
||||||
|
int fd_;
|
||||||
|
|
||||||
|
std::map<unsigned int, MediaObject *> objects_;
|
||||||
|
MediaObject *object(unsigned int id);
|
||||||
|
int addObject(MediaObject *obj);
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
std::vector<MediaEntity *> entities_;
|
||||||
|
MediaEntity *getEntityByName(const std::string &name);
|
||||||
|
|
||||||
|
void populateEntities(const struct media_v2_topology &topology);
|
||||||
|
int populatePads(const struct media_v2_topology &topology);
|
||||||
|
int populateLinks(const struct media_v2_topology &topology);
|
||||||
|
};
|
||||||
|
|
||||||
|
} /* namespace libcamera */
|
||||||
|
|
||||||
|
#endif /* __LIBCAMERA_MEDIA_DEVICE_H__ */
|
375
src/libcamera/media_device.cpp
Normal file
375
src/libcamera/media_device.cpp
Normal file
|
@ -0,0 +1,375 @@
|
||||||
|
/* 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
|
||||||
|
* \brief Provide a representation of a Linux kernel Media Controller device
|
||||||
|
* that exposes the full graph topology.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace libcamera {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \class MediaDevice
|
||||||
|
* \brief The MediaDevice represents a Media Controller device with its full
|
||||||
|
* graph of connected objects.
|
||||||
|
*
|
||||||
|
* Media devices are created with an empty graph, which must be populated from
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The caller is responsible for opening the MediaDevice explicitly before
|
||||||
|
* operating on it, and shall close it when not needed anymore, as access
|
||||||
|
* to the MediaDevice is exclusive.
|
||||||
|
*
|
||||||
|
* A MediaDevice is created empty and gets populated by inspecting the media
|
||||||
|
* graph topology using the MEDIA_IOC_G_TOPOLOGY ioctls. Representation
|
||||||
|
* of each entity, pad and link described are created using MediaObject
|
||||||
|
* derived classes.
|
||||||
|
*
|
||||||
|
* All MediaObject are stored in a global pool, where they could be retrieved
|
||||||
|
* from by their globally unique id.
|
||||||
|
*
|
||||||
|
* References to MediaEntity registered in the graph are stored in a vector
|
||||||
|
* to allow easier by-name lookup, and the list of MediaEntities is accessible.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Construct a MediaDevice
|
||||||
|
* \param devnode The media device node path
|
||||||
|
*/
|
||||||
|
MediaDevice::MediaDevice(const std::string &devnode)
|
||||||
|
: devnode_(devnode), fd_(-1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Close the media device file descriptor and delete all object
|
||||||
|
*/
|
||||||
|
MediaDevice::~MediaDevice()
|
||||||
|
{
|
||||||
|
if (fd_ != -1)
|
||||||
|
::close(fd_);
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \fn MediaDevice::driver()
|
||||||
|
* \brief Retrieve the media device driver name
|
||||||
|
* \return The name of the kernel driver that handles the MediaDevice
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \fn MediaDevice::devnode()
|
||||||
|
* \brief Retrieve the media device device node path
|
||||||
|
* \return The MediaDevice devnode path
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Delete all media objects in the MediaDevice.
|
||||||
|
*
|
||||||
|
* Delete all MediaEntities; entities will then delete their pads,
|
||||||
|
* and each source pad will delete links.
|
||||||
|
*
|
||||||
|
* After this function has been called, the media graph will be unpopulated
|
||||||
|
* and its media objects deleted. The media device has to be populated
|
||||||
|
* before it could be used again.
|
||||||
|
*/
|
||||||
|
void MediaDevice::clear()
|
||||||
|
{
|
||||||
|
for (auto const &o : objects_)
|
||||||
|
delete o.second;
|
||||||
|
|
||||||
|
objects_.clear();
|
||||||
|
entities_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Open a media device and retrieve informations from it
|
||||||
|
*
|
||||||
|
* The function fails if the media device is already open or if either
|
||||||
|
* open or the media device information retrieval operations fail.
|
||||||
|
* \return 0 for success or a negative error number otherwise
|
||||||
|
*/
|
||||||
|
int MediaDevice::open()
|
||||||
|
{
|
||||||
|
if (fd_ != -1) {
|
||||||
|
LOG(Error) << "MediaDevice already open";
|
||||||
|
return -EBUSY;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = ::open(devnode_.c_str(), O_RDWR);
|
||||||
|
if (ret < 0) {
|
||||||
|
ret = -errno;
|
||||||
|
LOG(Error) << "Failed to open media device at " << devnode_
|
||||||
|
<< ": " << strerror(-ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
fd_ = ret;
|
||||||
|
|
||||||
|
struct media_device_info info = { };
|
||||||
|
ret = ioctl(fd_, MEDIA_IOC_DEVICE_INFO, &info);
|
||||||
|
if (ret) {
|
||||||
|
ret = -errno;
|
||||||
|
LOG(Error) << "Failed to get media device info "
|
||||||
|
<< ": " << strerror(-ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
driver_ = info.driver;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Close the file descriptor associated with the media device.
|
||||||
|
*
|
||||||
|
* After this function has been called, for the MediaDevice to be operated on,
|
||||||
|
* the caller shall open it again.
|
||||||
|
*/
|
||||||
|
void MediaDevice::close()
|
||||||
|
{
|
||||||
|
if (fd_ == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
::close(fd_);
|
||||||
|
fd_ = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \fn MediaDevice::entities()
|
||||||
|
* \brief Retrieve the list of entities in the media graph
|
||||||
|
* \return The list of MediaEntities registered in the MediaDevice
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add a new object to the global objects pool and fail if the object
|
||||||
|
* has already been registered.
|
||||||
|
*/
|
||||||
|
int MediaDevice::addObject(MediaObject *obj)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (objects_.find(obj->id()) != objects_.end()) {
|
||||||
|
LOG(Error) << "Element with id " << obj->id()
|
||||||
|
<< " already enumerated.";
|
||||||
|
return -EEXIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
objects_[obj->id()] = obj;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MediaObject pool lookup by id.
|
||||||
|
*/
|
||||||
|
MediaObject *MediaDevice::object(unsigned int id)
|
||||||
|
{
|
||||||
|
auto it = objects_.find(id);
|
||||||
|
return (it == objects_.end()) ? nullptr : it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Return the MediaEntity with name \a name
|
||||||
|
* \param name The entity name
|
||||||
|
* \return The entity with \a name
|
||||||
|
* \return nullptr if no entity with \a name is found
|
||||||
|
*/
|
||||||
|
MediaEntity *MediaDevice::getEntityByName(const std::string &name)
|
||||||
|
{
|
||||||
|
for (MediaEntity *e : entities_)
|
||||||
|
if (e->name() == name)
|
||||||
|
return e;
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MediaDevice::populateLinks(const struct media_v2_topology &topology)
|
||||||
|
{
|
||||||
|
media_v2_link *mediaLinks = reinterpret_cast<media_v2_link *>
|
||||||
|
(topology.ptr_links);
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < topology.num_links; ++i) {
|
||||||
|
/*
|
||||||
|
* Skip links between entities and interfaces: we only care
|
||||||
|
* about pad-2-pad links here.
|
||||||
|
*/
|
||||||
|
if ((mediaLinks[i].flags & MEDIA_LNK_FL_LINK_TYPE) ==
|
||||||
|
MEDIA_LNK_FL_INTERFACE_LINK)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Store references to source and sink pads in the link. */
|
||||||
|
unsigned int source_id = mediaLinks[i].source_id;
|
||||||
|
MediaPad *source = dynamic_cast<MediaPad *>
|
||||||
|
(object(source_id));
|
||||||
|
if (!source) {
|
||||||
|
LOG(Error) << "Failed to find pad with id: "
|
||||||
|
<< source_id;
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int sink_id = mediaLinks[i].sink_id;
|
||||||
|
MediaPad *sink = dynamic_cast<MediaPad *>
|
||||||
|
(object(sink_id));
|
||||||
|
if (!sink) {
|
||||||
|
LOG(Error) << "Failed to find pad with id: "
|
||||||
|
<< sink_id;
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaLink *link = new MediaLink(&mediaLinks[i], source, sink);
|
||||||
|
source->addLink(link);
|
||||||
|
sink->addLink(link);
|
||||||
|
|
||||||
|
addObject(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int MediaDevice::populatePads(const struct media_v2_topology &topology)
|
||||||
|
{
|
||||||
|
media_v2_pad *mediaPads = reinterpret_cast<media_v2_pad *>
|
||||||
|
(topology.ptr_pads);
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < topology.num_pads; ++i) {
|
||||||
|
unsigned int entity_id = mediaPads[i].entity_id;
|
||||||
|
|
||||||
|
/* Store a reference to this MediaPad in entity. */
|
||||||
|
MediaEntity *mediaEntity = dynamic_cast<MediaEntity *>
|
||||||
|
(object(entity_id));
|
||||||
|
if (!mediaEntity) {
|
||||||
|
LOG(Error) << "Failed to find entity with id: "
|
||||||
|
<< entity_id;
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaPad *pad = new MediaPad(&mediaPads[i], mediaEntity);
|
||||||
|
mediaEntity->addPad(pad);
|
||||||
|
|
||||||
|
addObject(pad);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For each entity in the media graph create a MediaEntity and store a
|
||||||
|
* reference in the MediaObject global pool and in the global vector of
|
||||||
|
* entities.
|
||||||
|
*/
|
||||||
|
void MediaDevice::populateEntities(const struct media_v2_topology &topology)
|
||||||
|
{
|
||||||
|
media_v2_entity *mediaEntities = reinterpret_cast<media_v2_entity *>
|
||||||
|
(topology.ptr_entities);
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < topology.num_entities; ++i) {
|
||||||
|
MediaEntity *entity = new MediaEntity(&mediaEntities[i]);
|
||||||
|
|
||||||
|
addObject(entity);
|
||||||
|
entities_.push_back(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Populate the media graph with media objects
|
||||||
|
*
|
||||||
|
* This function enumerates all media objects in the media device graph and
|
||||||
|
* creates their MediaObject representations. All entities, pads and links are
|
||||||
|
* stored as MediaEntity, MediaPad and MediaLink respectively, with cross-
|
||||||
|
* references between objects. Interfaces are not processed.
|
||||||
|
*
|
||||||
|
* MediaEntities are stored in a global list in the MediaDevice itself to ease
|
||||||
|
* lookup, while MediaPads are accessible from the MediaEntity they belong
|
||||||
|
* to only and MediaLinks from the MediaPad they connect.
|
||||||
|
*
|
||||||
|
* \return 0 on success, a negative error code otherwise
|
||||||
|
*/
|
||||||
|
int MediaDevice::populate()
|
||||||
|
{
|
||||||
|
struct media_v2_topology topology = { };
|
||||||
|
struct media_v2_entity *ents = nullptr;
|
||||||
|
struct media_v2_link *links = nullptr;
|
||||||
|
struct media_v2_pad *pads = nullptr;
|
||||||
|
__u64 version = -1;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Keep calling G_TOPOLOGY until the version number stays stable.
|
||||||
|
*/
|
||||||
|
while (true) {
|
||||||
|
topology.topology_version = 0;
|
||||||
|
topology.ptr_entities = reinterpret_cast<__u64>(ents);
|
||||||
|
topology.ptr_links = reinterpret_cast<__u64>(links);
|
||||||
|
topology.ptr_pads = reinterpret_cast<__u64>(pads);
|
||||||
|
|
||||||
|
ret = ioctl(fd_, MEDIA_IOC_G_TOPOLOGY, &topology);
|
||||||
|
if (ret < 0) {
|
||||||
|
ret = -errno;
|
||||||
|
LOG(Error) << "Failed to enumerate topology: "
|
||||||
|
<< strerror(-ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version == topology.topology_version)
|
||||||
|
break;
|
||||||
|
|
||||||
|
delete[] links;
|
||||||
|
delete[] ents;
|
||||||
|
delete[] pads;
|
||||||
|
|
||||||
|
ents = new media_v2_entity[topology.num_entities];
|
||||||
|
links = new media_v2_link[topology.num_links];
|
||||||
|
pads = new media_v2_pad[topology.num_pads];
|
||||||
|
|
||||||
|
version = topology.topology_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Populate entities, pads and links. */
|
||||||
|
populateEntities(topology);
|
||||||
|
|
||||||
|
ret = populatePads(topology);
|
||||||
|
if (ret)
|
||||||
|
goto error_free_objs;
|
||||||
|
|
||||||
|
ret = populateLinks(topology);
|
||||||
|
error_free_objs:
|
||||||
|
if (ret)
|
||||||
|
clear();
|
||||||
|
|
||||||
|
delete[] links;
|
||||||
|
delete[] ents;
|
||||||
|
delete[] pads;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \var MediaDevice::objects_
|
||||||
|
* \brief Global map of media objects (entities, pads, links) keyed by their
|
||||||
|
* object id.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \var MediaDevice::entities_
|
||||||
|
* \brief Global list of media entities in the media graph
|
||||||
|
*/
|
||||||
|
|
||||||
|
} /* namespace libcamera */
|
|
@ -4,6 +4,7 @@ libcamera_sources = files([
|
||||||
'device_enumerator.cpp',
|
'device_enumerator.cpp',
|
||||||
'log.cpp',
|
'log.cpp',
|
||||||
'main.cpp',
|
'main.cpp',
|
||||||
|
'media_device.cpp',
|
||||||
'media_object.cpp',
|
'media_object.cpp',
|
||||||
'pipeline_handler.cpp',
|
'pipeline_handler.cpp',
|
||||||
])
|
])
|
||||||
|
@ -11,6 +12,7 @@ libcamera_sources = files([
|
||||||
libcamera_headers = files([
|
libcamera_headers = files([
|
||||||
'include/device_enumerator.h',
|
'include/device_enumerator.h',
|
||||||
'include/log.h',
|
'include/log.h',
|
||||||
|
'include/media_device.h',
|
||||||
'include/media_object.h',
|
'include/media_object.h',
|
||||||
'include/pipeline_handler.h',
|
'include/pipeline_handler.h',
|
||||||
'include/utils.h',
|
'include/utils.h',
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue