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>
+#include <vector>
+
+#include <linux/media.h>
+
+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; }
+
+	const std::string name() const { return name_; }
+	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_;
+
+	int setDevice(const std::string &path);
+
+	void addPad(MediaPad *pad);
+
+	const MediaPad *__getPad(std::vector<MediaPad *> &v,
+			   std::function<bool(MediaPad *)> f);
+	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.
+ */
+
+/**
+ * \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
+ */
+
+/**
+ * \fn MediaObject::id()
+ * \brief Return the object's globally unique id /ref id_
+ */
+
+/**
+ * \var MediaObject::id_
+ * \brief The MediaObject unique id as returned by MEDIA_IOC_G_TOPOLOGY
+ */
+
+/**
+ * \class MediaLink
+ * \brief A Media Link object
+ *
+ * A MediaLink object represents a media link between two entities
+ */
+
+/**
+ * \fn MediaLink::MediaLink(const struct media_v2_link *link)
+ * \brief Construct a MediaLink with informations from \a link
+ * \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) { }
+/**
+ * \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
+ */
+MediaPad::MediaPad(const struct media_v2_pad *pad) : MediaObject(pad->id),
+						     index_(pad->index),
+						     entity_(pad->entity_id),
+						     flags_(pad->flags) { }
+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
+ */
+
+/**
+ * \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; });
+}
+
+/**
+ * \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) { }
+
+/**
+ * \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
+ */
+int MediaEntity::setDevice(const std::string &path)
+{
+	/* Make sure the path exists first. */
+	struct stat pstat;
+	int ret = ::stat(const_cast<const char *>(path.c_str()), &pstat);
+	if (ret < 0) {
+		LOG(Error) << "Unable to open: " << path << ": "
+			   << strerror(errno);
+		return -errno;
+	}
+
+	devnode_ = path;
+
+	return 0;
+}
+
+/**
+ * \fn MediaEntity::addPad(MediaPad *pad)
+ * \brief Add pad \a pad to \ref sources_ or \ref sinks_
+ * \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;
+	}
+
+	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
+ */
+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([
