diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h
index 1c0ee07c2a22..bf70255a6a5e 100644
--- a/include/libcamera/camera.h
+++ b/include/libcamera/camera.h
@@ -18,6 +18,7 @@ namespace libcamera {
 
 class Buffer;
 class PipelineHandler;
+class Request;
 class Stream;
 class StreamConfiguration;
 
@@ -44,6 +45,15 @@ public:
 	streamConfiguration(std::vector<Stream *> &streams);
 	int configureStreams(std::map<Stream *, StreamConfiguration> &config);
 
+	int allocateBuffers();
+	void freeBuffers();
+
+	Request *createRequest();
+	int queueRequest(Request *request);
+
+	int start();
+	int stop();
+
 private:
 	Camera(PipelineHandler *pipe, const std::string &name);
 	~Camera();
diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp
index 3f7b805b09a2..1acb399c80a6 100644
--- a/src/libcamera/camera.cpp
+++ b/src/libcamera/camera.cpp
@@ -6,6 +6,7 @@
  */
 
 #include <libcamera/camera.h>
+#include <libcamera/request.h>
 #include <libcamera/stream.h>
 
 #include "log.h"
@@ -264,11 +265,151 @@ int Camera::configureStreams(std::map<Stream *, StreamConfiguration> &config)
 
 		stream->configuration_ = cfg;
 		activeStreams_.push_back(stream);
+
+		/*
+		 * Allocate buffer objects in the pool.
+		 * Memory will be allocated and assigned later.
+		 */
+		stream->bufferPool().createBuffers(cfg.bufferCount);
 	}
 
 	return 0;
 }
 
+/**
+ * \brief Allocate buffers for all configured streams
+ * \return 0 on success or a negative error code otherwise
+ */
+int Camera::allocateBuffers()
+{
+	int ret;
+
+	ret = exclusiveAccess();
+	if (ret)
+		return ret;
+
+	if (activeStreams_.empty()) {
+		LOG(Camera, Error)
+			<< "Can't allocate buffers without streams";
+		return -EINVAL;
+	}
+
+	for (Stream *stream : activeStreams_) {
+		ret = pipe_->allocateBuffers(this, stream);
+		if (ret) {
+			LOG(Camera, Error) << "Failed to allocate buffers";
+			freeBuffers();
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * \brief Release all buffers from allocated pools in each stream
+ */
+void Camera::freeBuffers()
+{
+	for (Stream *stream : activeStreams_) {
+		if (!stream->bufferPool().count())
+			continue;
+
+		pipe_->freeBuffers(this, stream);
+		stream->bufferPool().destroyBuffers();
+	}
+}
+
+/**
+ * \brief Create a request object for the camera
+ *
+ * This method creates an empty request for the application to fill with
+ * buffers and paramaters, and queue for capture.
+ *
+ * The ownership of the returned request is passed to the caller, which is
+ * responsible for either queueing the request or deleting it.
+ *
+ * \return A pointer to the newly created request, or nullptr on error
+ */
+Request *Camera::createRequest()
+{
+	if (exclusiveAccess())
+		return nullptr;
+
+	return new Request(this);
+}
+
+/**
+ * \brief Queue a request to the camera
+ * \param[in] request The request to queue to the camera
+ *
+ * This method queues a \a request allocated with createRequest() to the camera
+ * for capture. Once the request has been queued, the camera will notify its
+ * completion through the \ref requestCompleted signal.
+ *
+ * Ownership of the request is transferred to the camera. It will be deleted
+ * automatically after it completes.
+ *
+ * \return 0 on success or a negative error code on error
+ */
+int Camera::queueRequest(Request *request)
+{
+	int ret;
+
+	ret = exclusiveAccess();
+	if (ret)
+		return ret;
+
+	ret = request->prepare();
+	if (ret) {
+		LOG(Camera, Error) << "Failed to prepare request";
+		return ret;
+	}
+
+	return pipe_->queueRequest(this, request);
+}
+
+/**
+ * \brief Start capture from camera
+ *
+ * Start the camera capture session. Once the camera is started the application
+ * can queue requests to the camera to process and return to the application
+ * until the capture session is terminated with \a stop().
+ *
+ * \return 0 on success or a negative error code on error
+ */
+int Camera::start()
+{
+	int ret = exclusiveAccess();
+	if (ret)
+		return ret;
+
+	LOG(Camera, Debug) << "Starting capture";
+
+	return pipe_->start(this);
+}
+
+/**
+ * \brief Stop capture from camera
+ *
+ * This method stops capturing and processing requests immediately. All pending
+ * requests are cancelled and complete synchronously in an error state.
+ *
+ * \return 0 on success or a negative error code on error
+ */
+int Camera::stop()
+{
+	int ret = exclusiveAccess();
+	if (ret)
+		return ret;
+
+	LOG(Camera, Debug) << "Stopping capture";
+
+	pipe_->stop(this);
+
+	return 0;
+}
+
 int Camera::exclusiveAccess()
 {
 	if (disconnected_)
diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp
index 922682a32188..d76db24de0e2 100644
--- a/src/libcamera/request.cpp
+++ b/src/libcamera/request.cpp
@@ -104,7 +104,8 @@ int Request::prepare()
  * data.
  *
  * The request completes when all the buffers it contains are ready to be
- * presented to the application.
+ * presented to the application. It then emits the Camera::requestCompleted
+ * signal and is automatically deleted.
  */
 void Request::bufferCompleted(Buffer *buffer)
 {
@@ -113,10 +114,12 @@ void Request::bufferCompleted(Buffer *buffer)
 	int ret = pending_.erase(buffer);
 	ASSERT(ret == 1);
 
-	if (pending_.empty()) {
-		std::map<Stream *, Buffer *> buffers(std::move(bufferMap_));
-		camera_->requestCompleted.emit(this, buffers);
-	}
+	if (!pending_.empty())
+		return;
+
+	std::map<Stream *, Buffer *> buffers(std::move(bufferMap_));
+	camera_->requestCompleted.emit(this, buffers);
+	delete this;
 }
 
 } /* namespace libcamera */
