[libcamera-devel] [PATCH RESEND v2 1/4] libcamera: Add MediaObject class hierarchy
Laurent Pinchart
laurent.pinchart at ideasonboard.com
Fri Dec 28 11:51:23 CET 2018
Hi Jacopo,
Thank you for the patch.
On Friday, 28 December 2018 09:57:40 EET Jacopo Mondi wrote:
> Add a class hierarcy to represent all media objects a media graph
> represents. Add a base MediaObject class, which retains the global unique
> object id, and define the derived MediaEntity, MediaLink and MediaPad
> classes.
>
> This hierarchy will be used by the MediaDevice objects which represents and
> handles the media graph.
>
> Signed-off-by: Jacopo Mondi <jacopo at jmondi.org>
> ---
> src/libcamera/include/media_object.h | 117 +++++++++++
> src/libcamera/media_object.cpp | 302 +++++++++++++++++++++++++++
> src/libcamera/meson.build | 1 +
> 3 files changed, 420 insertions(+)
> create mode 100644 src/libcamera/include/media_object.h
> create mode 100644 src/libcamera/media_object.cpp
>
> diff --git a/src/libcamera/include/media_object.h
> b/src/libcamera/include/media_object.h new file mode 100644
> index 0000000..99913eb
> --- /dev/null
> +++ b/src/libcamera/include/media_object.h
> @@ -0,0 +1,117 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2018, Google Inc.
> + *
> + * media_object.h - Media Device objects: entities, pads and links.
> + */
> +#ifndef __LIBCAMERA_MEDIA_OBJECT_H__
> +#define __LIBCAMERA_MEDIA_OBJECT_H__
> +
> +#include <functional>
> +#include <string>
> +#include <sstream>
s comes before t :-) I think you can remove <sstream> anyway, it doesn't seem
to be used.
> +#include <vector>
> +
> +#include <linux/media.h>
If this is just for the definition of struct media_v2_entity, can't it be
forward-declared instead ?
> +namespace libcamera {
> +
> +class MediaDevice;
> +class MediaEntity;
> +
> +class MediaObject
> +{
> +public:
> + MediaObject(unsigned int id) : id_(id) { }
> + virtual ~MediaObject() { }
> +
> + unsigned int id() const { return id_; }
> +
> +protected:
> + unsigned int id_;
> +};
> +
> +class MediaLink : public MediaObject
> +{
> + friend class MediaDevice;
> +
> +public:
> + ~MediaLink() { }
> +
> + unsigned int source() const { return source_; }
> + unsigned int sink() const { return sink_; }
> + unsigned int flags() const { return flags_; }
> + void setFlags(unsigned int flags) { flags_ = flags; }
> +
> +private:
> + MediaLink(const struct media_v2_link *link);
> + MediaLink(const MediaLink &) = delete;
> +
> + unsigned int source_;
> + unsigned int sink_;
> + unsigned int flags_;
> +};
> +
> +class MediaPad : public MediaObject
> +{
> + friend class MediaDevice;
> +
> +public:
> + ~MediaPad();
> +
> + unsigned int index() const { return index_; }
> + unsigned int entity() const { return entity_; }
> + unsigned int flags() const { return flags_; }
> + const std::vector<MediaLink *> &links() const { return links_; }
> +
> + void addLink(MediaLink *link);
> +
> +private:
> + MediaPad(const struct media_v2_pad *pad);
> + MediaPad(const MediaPad &) = delete;
> +
> + unsigned int index_;
> + unsigned int entity_;
> + unsigned int flags_;
> +
> + std::vector<MediaLink *> links_;
> +};
> +
> +class MediaEntity : public MediaObject
> +{
> + friend class MediaDevice;
> +
> +public:
> + bool operator==(unsigned int id) const { return this->id_ == id; }
> + bool operator!=(unsigned int id) const { return this->id_ != id; }
> + bool operator==(std::string name) const { return name_ == name; }
As stated before, I wouldn't define those operators. They're more confusing
than typing code explicitly. Compare the two following implementations:
MediaEntity *entity = ...;
if (entity == 3) {
...
} else if (entity == "foo") {
...
}
vs.
MediaEntity *entity = ...;
if (entity->id() == 3) {
...
} else if (entity->name() == "foo") {
...
}
The second is easier to read and more self-explicit, without requiring much
more code in the user of the class.
> + const std::string name() const { return name_; }
You can return a const reference instead of a copy.
> + const std::vector<MediaPad *> &sources() const { return sources_; }
> + const std::vector<MediaPad *> &sinks() const { return sinks_; }
> +
> + const MediaPad *getPadByIndex(unsigned int index);
> + const MediaPad *getPadById(unsigned int id);
> +
> +private:
> + MediaEntity(const struct media_v2_entity *entity);
> + MediaEntity(const MediaEntity &) = delete;
> + ~MediaEntity();
> +
> + std::string name_;
> + std::string devnode_;
> +
> + std::vector<MediaPad *> sources_;
> + std::vector<MediaPad *> sinks_;
I think the implementation would be simpler if you merged all pads in a single
vector. The getPadByIndex() function would just be a vector lookup. The
sources() and sinks() functions would on the other hand need to return
std::vector<> instead of std::vector<>&, but I think that's fine. Depending on
how you use them in the callers, you could create a pads() function instead.
Given that pads could be bidirectional (unless I'm mistaken there's nothing in
the MC API that prevents the source and sink flags being both set) I think
this would be a better API.
> + int setDevice(const std::string &path);
> +
> + void addPad(MediaPad *pad);
> +
> + const MediaPad *__getPad(std::vector<MediaPad *> &v,
> + std::function<bool(MediaPad *)> f);
The __ prefix is reserved in C/C++.
> + const MediaPad *getPad(std::function<bool(MediaPad *)> f);
> +};
> +
> +} /* namespace libcamera */
> +#endif /* __LIBCAMERA_MEDIA_OBJECT_H__ */
> diff --git a/src/libcamera/media_object.cpp b/src/libcamera/media_object.cpp
> new file mode 100644
> index 0000000..65ed421
> --- /dev/null
> +++ b/src/libcamera/media_object.cpp
> @@ -0,0 +1,302 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2018, Google Inc.
> + *
> + * media_object.cpp - Media device objects: entities, pads and links
> + */
> +
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <string.h>
> +#include <sys/stat.h>
> +
> +#include <functional>
> +#include <string>
> +#include <vector>
> +
> +#include <linux/media.h>
> +
> +#include "log.h"
> +#include "media_object.h"
> +
> +/**
> + * \file media_object.h
> + */
> +namespace libcamera {
> +
> +/**
> + * \class MediaObject
> + * \brief Base class for all media object types
> + *
> + * Defines a simple base class for all media objects with a simple
> + * unique id.
You may want to expand this a bit, or possibly the \file documentation block,
to explain that the media objects model their kernel-side counterpart from the
MC API.
> + */
> +
> +/**
> + * \fn MediaObject::MediaObject(unsigned int id)
> + * \brief Construct a MediaObject with id \a id
> + * \param id The globally unique object's id as returned by
> MEDIA_IOC_G_TOPOLOGY
Shouldn't it be "object id" ?
Maybe s/returned by/obtained from/ ?
If you document the fact that these objects model the MC objects in the \file
or \class, I think you can skip the "as returned by MEDIA_IOC_G_TOPOLOGY" here
and in all locations below.
> + */
> +
> +/**
> + * \fn MediaObject::id()
> + * \brief Return the object's globally unique id /ref id_
Ditto.
/ref ?
> + */
> +
> +/**
> + * \var MediaObject::id_
> + * \brief The MediaObject unique id as returned by MEDIA_IOC_G_TOPOLOGY
> + */
Do we need to document the private data members ? I suppose it can be useful,
but there's lots of overlap between the constructor, id() and id_
documentation. I don't want to see documentation being written just to tick a
box, it should be useful if present.
> +/**
> + * \class MediaLink
> + * \brief A Media Link object
> + *
> + * A MediaLink object represents a media link between two entities
Isn't it between two pads ?
> + */
> +
> +/**
> + * \fn MediaLink::MediaLink(const struct media_v2_link *link)
> + * \brief Construct a MediaLink with informations from \a link
s/informations/information/
> + * \param link The media link representation as returned by
> + * MEDIA_IOC_G_TOPOLOGY
> + */
> +MediaLink::MediaLink(const struct media_v2_link *link) :
> MediaObject(link->id),
> + source_(link->source_id),
> + sink_(link->sink_id),
> + flags_(link->flags) { }
The indentation is quite peculiar :-) I think it should be (with spaces
instead of tabs to ensure it won't be mangled by mail clients)
MediaLink::MediaLink(const struct media_v2_link *link)
: MediaObject(link->id), source_(link->source_id),
sink_(link->sink_id), flags_(link->flags)
{
}
> +/**
> + * \fn MediaLink::source()
> + * \brief Return the source pad id
> + */
> +
> +/**
> + * \fn MediaLink::sink()
> + * \brief Return the sink pad id
> + */
> +
> +/**
> + * \fn MediaLink::flags()
> + * \brief Return the link flags
> + */
> +
> +/**
> + * \fn MediaLink::setFlags(unsigned int flags)
> + * \brief Set the link flags to \a flags
> + * \param flags The flags to be applied to the link
> + */
> +
> +/**
> + * \class MediaPad
> + * \brief Media Pad object
> + *
> + * A MediaPad object represents a media pad with its associated links
> + */
> +
> +/**
> + * \fn MediaPad::MediaPad(const struct media_v2_pad *pad)
> + * \brief Create a MediaPad object
> + * \param mediaPad The media pad representation as returned by
> + * MEDIA_IOC_G_TOPOLOGY
The argument name is pad, not mediaPad.
> + */
> +MediaPad::MediaPad(const struct media_v2_pad *pad) : MediaObject(pad->id),
> + index_(pad->index),
> + entity_(pad->entity_id),
> + flags_(pad->flags) { }
Indentation here too.
> +MediaPad::~MediaPad()
> +{
> + links_.clear();
> +}
> +
> +/**
> + * \fn MediaPad::index()
> + * \brief Return the 0-indexed pad index
> + */
> +
> +/**
> + * \fn MediaPad::entity()
> + * \brief Return the entity id this pad belongs to
> + */
> +
> +/**
> + * \fn MediaPad::flags()
> + * \brief Return the pad flags (MEDIA_PAD_FL_*)
> + */
> +
> +/**
> + * \fn MediaPad::links()
> + * \brief Return all outbound and inbound links from/to this pad
> + */
> +
> +/**
> + * \fn MediaPad::addLink(MediaLink *link)
> + * \brief Add a new outbound or inbound link from/to this pad
> + * \param link The new link to add
> + */
> +void MediaPad::addLink(MediaLink *link)
> +{
> + links_.push_back(link);
> +}
> +
> +/**
> + * \class MediaEntity
> + * \brief Media entity object
> + *
> + * A MediaEntity object represents a media entity with its id, name and its
> + * associated pads
> + */
> +
> +/**
> + * \fn MediaEntity::operator==(unsigned int id) const
> + * \brief Compare entities by id (check if they're equal)
> + * \param id The entity id to compare with
> + */
> +
> +/**
> + * \fn MediaEntity::operator!=(unsigned int id) const
> + * \brief Compare entities by id (check if they're not equal)
> + * \param id The entity id to compare with
> + */
> +
> +/**
> + * \fn MediaEntity::operator==(std::string name) const
> + * \brief Compare entities by name (check if they're equal)
> + * \param name The entity name to compare with
> + */
> +
> +/**
> + * \fn MediaEntity::name()
> + * \brief Return the entity name
> + */
> +
> +/**
> + * \fn MediaEntity::sources()
> + * \brief Get all source pads
Some getters are documented as "Return ...", others as "Get ...". Should we
standardize on one of the two ?
> + */
> +
> +/**
> + * \fn MediaEntity::sinks()
> + * \brief Get all sink pads
> + */
> +
> +/**
> + * \fn MediaEntity::getPadByIndex(unsigned int index)
> + * \brief Get a pad in this entity by its index
> + * \param index The pad index (starting from 0)
> + */
> +const MediaPad *MediaEntity::getPadByIndex(unsigned int index)
> +{
> + return getPad([&index](MediaPad *p) -> bool { return p->index() == index;
> });
> +}
> +
> +/**
> + * \fn MediaEntity::getPadById(unsigned int id)
> + * \brief Get a pad in this entity by its id
> + * \param id The pad globally unique id
> + */
> +const MediaPad *MediaEntity::getPadById(unsigned int id)
> +{
> + return getPad([&id](MediaPad *p) -> bool { return p->id() == id; });
Would it be more efficient to store all objects in a map in MediaObject and
have this function look up the pad from there ? Given the limited number of
pads it shouldn't matter too much so I'm OK with this implementation. I don't
like the __getPad() and getPad() with std::function much though, but if you
merge the sink and source vectors like proposed above it would make all this
simpler.
> +}
> +
> +/**
> + * \fn MediaEntity::MediaEntity(const struct media_v2_entity *entity)
> + * \brief Construct a MediaEntity with informations from \a entity
> + * \param entity The media entity representation as returned by
> + * MEDIA_IOC_G_TOPOLOGY
> + */
> +MediaEntity::MediaEntity(const struct media_v2_entity *entity) :
> + MediaObject(entity->id),
> + name_(entity->name) { }
> +
Indentation.
> +/**
> + * \fn MediaEntity::~MediaEntity()
> + * \brief Release memory for all pads and links
> + */
> +MediaEntity::~MediaEntity()
> +{
> + for (MediaPad *s : sources_)
> + delete s;
> + for (MediaPad *s : sinks_)
> + delete s;
> +
> + sources_.clear();
> + sinks_.clear();
> +}
> +
> +/**
> + * \var MediaEntity::sources_
> + * \brief The MediaPad sources vector
> + */
> +
> +/**
> + * \var MediaEntity::sinks_
> + * \brief The MediaPad sinks vector
> + */
> +
> +/**
> + * \fn MediaEntity::setDevice(const std::string &path)
> + * \brief Set the entity video (sub)device node path
> + * \param path The video (sub)device node path associated with this entity
I'd talk about "device node" instead of "video (sub)device node" to keep this
generic, as entities could have non-video nodes. I would then name the
function setDeviceNode().
> + */
> +int MediaEntity::setDevice(const std::string &path)
s/path/devnode/ ?
> +{
> + /* Make sure the path exists first. */
> + struct stat pstat;
> + int ret = ::stat(const_cast<const char *>(path.c_str()), &pstat);
Do you need the cast ?
> + if (ret < 0) {
> + LOG(Error) << "Unable to open: " << path << ": "
> + << strerror(errno);
It's stat(), not open().
I wonder whether the check shouldn't be moved to the caller.
> + return -errno;
> + }
> +
> + devnode_ = path;
> +
> + return 0;
> +}
> +
> +/**
> + * \fn MediaEntity::addPad(MediaPad *pad)
> + * \brief Add pad \a pad to \ref sources_ or \ref sinks_
You can write "Add \a pad", there's no need to duplicate the name. The comment
holds true for various other locations in this patch series.
> + * \param pad The pad to add
> + */
> +void MediaEntity::addPad(MediaPad *pad)
> +{
> + std::vector<MediaPad *> *pads =
> + pad->flags() & MEDIA_PAD_FL_SOURCE ?
> + &sources_ : &sinks_;
> + pads->push_back(pad);
> +}
> +
> +/**
> + * \fn MediaEntity::__getPad(std::vector<MediaPad *> &v,
> + * std::function<bool(MediaPad *)> f)
> + * \brief Find MediaPad the satisfies predicates \a f in the pad vector \v
> + * \param v The std::vector<MediaPad *> to search in
> + * \param f The predicate the pad has to satisfy
> + */
> +const MediaPad *MediaEntity::__getPad(std::vector<MediaPad *> &v,
> + std::function<bool(MediaPad *)> f)
> +{
> + auto it = v.begin();
> + while (it != sources_.end()) {
> + if (f(*it))
> + return *it;
> + ++it;
> + }
How about
for (MediaPad *pad : v) {
if (f(pad))
return pad;
}
> +
> + return nullptr;
> +}
> +
> +/**
> + * \fn MediaEntity::getPad(std::function<bool(MediaPad *)> f)
> + * \brief Run predicate \a f on both \ref sources_ and \ref sinks_
> + * \param f The predicate the pad has to satisfy
As stated above I find this a bit overengineered. Hopefully the code can be
simplified with merging the sink and source vectors. Otherwise you need a more
detailed documentation block to explain what this function does.
> + */
> +const MediaPad *MediaEntity::getPad(std::function<bool(MediaPad *)> f)
> +{
> + const MediaPad *_p = __getPad(sources_, f);
> + return (_p ? _p : __getPad(sinks_, f));
> +}
> +
> +} /* namespace libcamera */
> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
> index f632eb5..da06eba 100644
> --- a/src/libcamera/meson.build
> +++ b/src/libcamera/meson.build
> @@ -1,6 +1,7 @@
> libcamera_sources = files([
> 'log.cpp',
> 'main.cpp',
> + 'media_object.cpp',
> ])
>
> libcamera_headers = files([
--
Regards,
Laurent Pinchart
More information about the libcamera-devel
mailing list