diff --git a/src/libcamera/include/v4l2_videodevice.h b/src/libcamera/include/v4l2_videodevice.h
index f4cbcfbd26ea540c..0e94c3f71d0b1208 100644
--- a/src/libcamera/include/v4l2_videodevice.h
+++ b/src/libcamera/include/v4l2_videodevice.h
@@ -16,6 +16,7 @@
 #include <libcamera/geometry.h>
 #include <libcamera/pixelformats.h>
 #include <libcamera/signal.h>
+#include <libcamera/stream.h>
 
 #include "formats.h"
 #include "log.h"
@@ -236,6 +237,29 @@ private:
 	V4L2VideoDevice *capture_;
 };
 
+class V4L2Stream : public Stream
+{
+public:
+	V4L2Stream(V4L2VideoDevice *video);
+
+protected:
+	int allocateBuffers(const StreamConfiguration &config,
+			    std::vector<FrameBuffer *> *buffers) override;
+	void releaseBuffers() override;
+	int start() override;
+	void stop() override;
+
+private:
+	enum BufferType {
+		NoBuffers,
+		AllocatedBuffers,
+		ExternalBuffers,
+	};
+
+	V4L2VideoDevice *video_;
+	BufferType bufferType_;
+};
+
 } /* namespace libcamera */
 
 #endif /* __LIBCAMERA_V4L2_VIDEODEVICE_H__ */
diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp
index 9fe66018ec502626..f5810956b2040ce6 100644
--- a/src/libcamera/v4l2_videodevice.cpp
+++ b/src/libcamera/v4l2_videodevice.cpp
@@ -1711,4 +1711,94 @@ void V4L2M2MDevice::close()
 	output_->close();
 }
 
+/**
+ * \class V4L2Stream
+ * \brief V4L2 Video stream for a camera
+ *
+ * The V4l2Stream implemets the interfaces mandated by Stream to allow buffers
+ * to be allocoated from a video device and presented to the user as if they
+ * where allocated externaly.
+ */
+
+/**
+ * \brief Create a stream around a V4L2 video device
+ * \param[in] video V4L2 video device to serve
+ *
+ * Create a stream which will manage the application visible buffers for a V4L2
+ * video device.
+ */
+V4L2Stream::V4L2Stream(V4L2VideoDevice *video)
+	: Stream(), video_(video), bufferType_(NoBuffers)
+{
+}
+
+int V4L2Stream::allocateBuffers(const StreamConfiguration &config,
+				std::vector<FrameBuffer *> *buffers)
+{
+	if (bufferType_ != NoBuffers)
+		return -EINVAL;
+
+	if (!buffers)
+		return -EINVAL;
+
+	/*
+	 * \todo: Extend V4L2VideoDevice to support VIDIOC_CREATE_BUFS which
+	 * can take the whole configuration into account, until then use as
+	 * much as possible (number of buffers) with VIDIOC_QUERYBUF.
+	 *
+	 * For this reason check that the format of the video device is the
+	 * same as the one request or fail. This will allow for a applicaiton
+	 * facing API that will work with VIDIOC_CREATE_BUFS.
+	 */
+	V4L2DeviceFormat format;
+	int ret = video_->getFormat(&format);
+	if (ret)
+		return ret;
+
+	if (config.size != format.size ||
+	    config.pixelFormat != V4L2VideoDevice::toPixelFormat(format.fourcc))
+		return -EINVAL;
+
+	ret = video_->allocateBuffers(config.bufferCount, buffers);
+	if (ret)
+		return ret;
+
+	bufferType_ = AllocatedBuffers;
+
+	return ret;
+}
+
+void V4L2Stream::releaseBuffers()
+{
+	if (bufferType_ == NoBuffers)
+		return;
+
+	video_->releaseBuffers();
+	bufferType_ = NoBuffers;
+}
+
+int V4L2Stream::start()
+{
+	/* If internal buffers is already allocated nothing to do. */
+	if (bufferType_ == AllocatedBuffers)
+		return 0;
+
+	int ret = video_->externalBuffers(configuration_.bufferCount);
+	if (ret)
+		return ret;
+
+	bufferType_ = ExternalBuffers;
+
+	return ret;
+}
+
+void V4L2Stream::stop()
+{
+	/* Only need to clean up if external buffers is used. */
+	if (bufferType_ != ExternalBuffers)
+		return;
+
+	releaseBuffers();
+}
+
 } /* namespace libcamera */
