new file mode 100644
@@ -0,0 +1,105 @@ 
+/*
+ * Copyright (C) 2023, Google Inc.
+ *
+ * info_frame.h - InfoFrame and InfoFramePool
+ */
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <unordered_map>
+#include <vector>
+
+#include <libcamera/framebuffer.h>
+#include <libcamera/geometry.h>
+#include <libcamera/pixel_format.h>
+
+#include "libcamera/internal/dma_buf_allocator.h"
+#include "libcamera/internal/mailbox.h"
+#include "libcamera/internal/pool.h"
+
+namespace libcamera {
+
+class InfoFrame
+{
+public:
+	struct Plane {
+		uint8_t *address;
+	};
+
+	InfoFrame();
+	InfoFrame(const PixelFormat &format, const Size &size, FrameBuffer *buffers,
+		  unsigned int strideAlign = 1, unsigned int scanAlign = 1,
+		  std::array<Plane, 3> planes = {});
+
+	uint8_t *address(unsigned int plane) const;
+
+	PixelFormat format() const { return format_; }
+	Size size() const { return size_; }
+	FrameBuffer *buffer() const { return buffer_; }
+	unsigned int numPlanes() const { return buffer_ ? buffer_->planes().size() : 0; }
+	unsigned int strideAlign() const { return strideAlign_; }
+	unsigned int scanAlign() const { return scanAlign_; }
+
+private:
+	Size size_;
+	PixelFormat format_;
+	FrameBuffer *buffer_ = nullptr;
+
+	unsigned int strideAlign_ = 1;
+	unsigned int scanAlign_ = 1;
+
+	std::array<Plane, 3> planes_;
+};
+
+class InfoFramePool
+{
+public:
+	struct MappedBufferInfo {
+		uint8_t *address = nullptr;
+		size_t dmabufLength = 0;
+	};
+
+	InfoFramePool();
+	~InfoFramePool();
+
+	int createBuffers(DmaBufAllocator *dmaHeap, const PixelFormat &format,
+			  const Size &size, uint32_t count,
+			  unsigned int strideAlign = 1, unsigned scanAlign = 1);
+
+	void release();
+
+	void fetch(SharedMailBox<InfoFrame> &mailBox);
+
+	int mmap();
+	int munmap();
+
+	bool mapped() const { return 0 != mappedBuffers_.size(); }
+
+	size_t size() { return pool_.size(); }
+	std::vector<std::unique_ptr<FrameBuffer>> &content()
+	{
+		return pool_.content();
+	}
+
+private:
+	LIBCAMERA_DISABLE_COPY_AND_MOVE(InfoFramePool)
+
+	void setBuffers(const PixelFormat &format, const Size &size,
+			std::vector<std::unique_ptr<FrameBuffer>> &buffers,
+			unsigned int align, unsigned int scanAlign);
+
+	InfoFrame get();
+	void put(InfoFrame &frameInfo);
+
+	Size size_;
+	PixelFormat format_;
+	Pool<FrameBuffer *, std::unique_ptr<FrameBuffer>> pool_;
+	unsigned int strideAlign_;
+	unsigned int scanAlign_;
+
+	std::unordered_map<int, MappedBufferInfo> mappedBuffers_;
+};
+
+} /* namespace libcamera */
@@ -21,6 +21,7 @@  libcamera_internal_headers = files([
     'dma_buf_allocator.h',
     'formats.h',
     'framebuffer.h',
+    'info_frame.h',
     'ipa_data_serializer.h',
     'ipa_manager.h',
     'ipa_module.h',
new file mode 100644
@@ -0,0 +1,302 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Google Inc.
+ *
+ * info_frame.cpp - InfoFrame and InfoFramePool
+ */
+
+#include "libcamera/internal/info_frame.h"
+
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "libcamera/internal/dma_buf_allocator.h"
+#include "libcamera/internal/formats.h"
+#include "libcamera/internal/pool.h"
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(InfoFrame)
+
+/**
+ * \class InfoFrame
+ * \brief A wrapper class that consists of a FrameBuffer and extra information,
+ * including PixelFormat, size, and memory address if it's mmap'ed.
+ */
+
+/**
+ * \class InfoFrame::Plane
+ * \brief Per-plane frame mmap'ed address
+ *
+ * Frames are stored in memory in one or multiple planes. The
+ * InfoFrame::Plane structure stores per-plane mmap'ed address.
+ */
+
+/**
+ * \var InfoFrame::Plane::address
+ * \brief mmap'ed address of a plane
+ */
+
+InfoFrame::InfoFrame() = default;
+
+/**
+ * \brief Create an InfoFrame instance
+ * \param[in] format PixelFormat of the frame
+ * \param[in] size Size of the frame
+ * \param[in] buffer Pointer to the buffer. InfoFrame doesn't take the ownership
+ * \param[in] strideAlign The stride alignment, in bytes (1 for default alignment)
+ * \param[in] scanAlign The scanline alignment, in bytes (1 for default alignment)
+ * \param[in] planes mmap'ed addresses of planes
+ */
+InfoFrame::InfoFrame(const PixelFormat &format, const Size &size,
+		     FrameBuffer *buffer, unsigned int strideAlign,
+		     unsigned int scanAlign, std::array<Plane, 3> planes)
+	: size_(size), format_(format), buffer_(buffer),
+	  strideAlign_(strideAlign), scanAlign_(scanAlign),
+	  planes_(planes)
+
+{
+}
+
+/**
+ * \brief Get the mmap'ed address of \a plane
+ * \param[in] plane The \a plane'th plane
+ * \return Return the mmap'ed address of the plane
+ */
+uint8_t *InfoFrame::address(unsigned int plane) const
+{
+	return (plane < numPlanes()) ? planes_[plane].address : nullptr;
+}
+
+/**
+ * \fn InfoFrame::format()
+ * \return Return the PixelFormat of the frame
+ */
+
+/**
+ * \fn InfoFrame::size()
+ * \return Return the size of the frame
+ */
+
+/**
+ * \fn InfoFrame::buffer()
+ * \return Return the pointer to the buffer
+ */
+
+/**
+ * \fn InfoFrame::numPlanes()
+ * \return Return the number of planes of the buffer
+ */
+
+/**
+ * \fn InfoFrame::strideAlign()
+ * \return Return the stride alignment, in bytes (1 for default alignment)
+ */
+
+/**
+ * \fn InfoFrame::scanAlign()
+ * \return Return the scanline alignment, in bytes (1 for default alignment)
+ */
+
+/**
+ * \class InfoFramePool
+ * \brief A buffer pool that allows users to allocate and request InfoFrame
+ * buffers from DmaBufAllocator
+ */
+
+/**
+ * \struct InfoFramePool::MappedBufferInfo
+ * \brief Contains the information of the mmap'ed buffers, including address
+ * and length
+ */
+
+/**
+ * \var InfoFramePool::MappedBufferInfo::address
+ * \brief mmap'ed address of a buffer
+ */
+
+/**
+ * \var InfoFramePool::MappedBufferInfo::dmabufLength
+ * \brief Length of the DMA buffer
+ */
+
+InfoFramePool::InfoFramePool() = default;
+
+InfoFramePool::~InfoFramePool()
+{
+	release();
+}
+
+/**
+ * \brief Create DMA buffers
+ * \param[in] dmaHeap The DmaBufAllocator to allocate buffers from
+ * \param[in] format PixelFormat of each frame
+ * \param[in] size Size of each frame
+ * \param[in] count Number of frames to create
+ * \param[in] strideAlign The stride alignment, in bytes (1 for default alignment)
+ * \param[in] scanAlign The scanline alignment, in bytes (1 for default alignment)
+ *
+ * \return 0 on success or a negative error code otherwise
+ */
+int InfoFramePool::createBuffers(DmaBufAllocator *dmaHeap,
+				 const PixelFormat &format, const Size &size,
+				 unsigned int count,
+				 unsigned int strideAlign, unsigned scanAlign)
+{
+	release();
+
+	const PixelFormatInfo &info = PixelFormatInfo::info(format);
+	uint32_t frameSize = info.frameSize(size, strideAlign, scanAlign);
+
+	std::vector<std::unique_ptr<FrameBuffer>> buffers;
+	buffers.reserve(count);
+	for (unsigned int i = 0; i < count; i++) {
+		SharedFD fd(dmaHeap->alloc(("frame-" + std::to_string(i)).c_str(),
+					   frameSize));
+		if (!fd.isValid()) {
+			buffers.clear();
+			return -EBUSY;
+		}
+
+		uint32_t offset = 0;
+		std::vector<FrameBuffer::Plane> planes;
+
+		for (unsigned int j = 0; j < info.numPlanes(); j++) {
+			planes.emplace_back(FrameBuffer::Plane{
+				fd, offset,
+				info.planeSize(size, j, strideAlign, scanAlign),
+				info.stride(size.width, j, strideAlign) });
+			offset += planes.back().length;
+		}
+
+		buffers.emplace_back(std::make_unique<FrameBuffer>(planes));
+	}
+
+	setBuffers(format, size, buffers, strideAlign, scanAlign);
+
+	return 0;
+}
+
+/**
+ * \brief Release all allocated buffers. InfoFramePool can create another set
+ * of buffers again.
+ */
+void InfoFramePool::release()
+{
+	if (munmap())
+		LOG(InfoFrame, Error) << "Failed to unmap mapped buffers";
+
+	pool_.release();
+}
+
+/**
+ * \brief Fetch an available InfoFrame from the pool
+ * \param[out] mailBox The shared pointer of MailBox to store the fetched
+ * InfoFrame
+ *
+ * When \a mailBox is reset, the recycler will return the InfoFrame back to the
+ * InfoFramePool.
+ */
+void InfoFramePool::fetch(SharedMailBox<InfoFrame> &mailBox)
+{
+	auto recycler = [this](InfoFrame &info) {
+		this->put(info);
+	};
+
+	mailBox->put(get(), recycler);
+}
+
+/**
+ * \brief mmap all allocated buffers and set the addresses in `InfoFrame`s
+ */
+int InfoFramePool::mmap()
+{
+	if (!mappedBuffers_.empty())
+		return 0;
+
+	for (auto &buffer : pool_.content()) {
+		for (const FrameBuffer::Plane &plane : buffer->planes()) {
+			const int fd = plane.fd.get();
+			if (mappedBuffers_.find(fd) == mappedBuffers_.end()) {
+				const size_t length = lseek(fd, 0, SEEK_END);
+				mappedBuffers_[fd] = MappedBufferInfo{ nullptr, length };
+			}
+		}
+	}
+
+	for (auto &[fd, info] : mappedBuffers_) {
+		void *address = ::mmap(nullptr, info.dmabufLength,
+				       PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+		info.address = static_cast<uint8_t *>(address);
+	}
+	return 0;
+}
+
+/**
+ * \brief munmap all allocated buffers and reset addresses
+ */
+int InfoFramePool::munmap()
+{
+	if (mappedBuffers_.empty())
+		return 0;
+
+	for (auto &[_, info] : mappedBuffers_) {
+		::munmap(info.address, info.dmabufLength);
+		info.address = nullptr;
+	}
+
+	mappedBuffers_.clear();
+	return 0;
+}
+
+/**
+ * \fn InfoFramePool::mapped()
+ * \return True if mmap() was called on the allocated buffers, false otherwise
+ */
+
+/**
+ * \fn InfoFramePool::size()
+ * \return Return the number of `InfoFrame`s allocated
+ */
+
+/**
+ * \fn InfoFramePool::content()
+ * \return Return frame buffers allocated in the pool
+ */
+
+void InfoFramePool::setBuffers(const PixelFormat &format, const Size &size,
+			       std::vector<std::unique_ptr<FrameBuffer>> &buffers,
+			       unsigned int strideAlign, unsigned int scanAlign)
+{
+	if (munmap())
+		LOG(InfoFrame, Error) << "Failed to unmap buffers";
+
+	size_ = size;
+	format_ = format;
+	pool_.setData(buffers);
+	strideAlign_ = strideAlign;
+	scanAlign_ = scanAlign;
+}
+
+InfoFrame InfoFramePool::get()
+{
+	FrameBuffer *buffer = pool_.get();
+
+	std::array<InfoFrame::Plane, 3> planes;
+	for (size_t i = 0; i < buffer->planes().size() && i < 3; i++) {
+		const int fd = buffer->planes()[i].fd.get();
+		const unsigned int offset = buffer->planes()[i].offset;
+
+		if (mappedBuffers_.count(fd))
+			planes[i].address = mappedBuffers_[fd].address + offset;
+	}
+
+	return InfoFrame(format_, size_, buffer, strideAlign_, scanAlign_, planes);
+}
+
+void InfoFramePool::put(InfoFrame &info)
+{
+	pool_.put(info.buffer());
+}
+
+} /* namespace libcamera */
@@ -29,6 +29,7 @@  libcamera_internal_sources = files([
     'device_enumerator_sysfs.cpp',
     'dma_buf_allocator.cpp',
     'formats.cpp',
+    'info_frame.cpp',
     'ipa_controls.cpp',
     'ipa_data_serializer.cpp',
     'ipa_interface.cpp',