[libcamera-devel,v4,11/11] libcamera: pipeline: simple: Integrate converter support

Message ID 20200404004438.17992-12-laurent.pinchart@ideasonboard.com
State Superseded
Headers show
Series
  • Simple pipeline handler
Related show

Commit Message

Laurent Pinchart April 4, 2020, 12:44 a.m. UTC
Add support for an optional format converter, supported by the
SimpleConverter class. If a converter is available for the pipeline, it
will be used to expose additional pixel formats.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
Changes since v2:

- Rebase on top of V4L2PixelFormat
- Don't crash if the device has no converter
---
 src/libcamera/pipeline/simple/simple.cpp | 196 ++++++++++++++++++++---
 1 file changed, 178 insertions(+), 18 deletions(-)

Comments

Niklas Söderlund April 21, 2020, 4:23 p.m. UTC | #1
Hi Laurent,

Thanks for your work.

On 2020-04-04 03:44:38 +0300, Laurent Pinchart wrote:
> Add support for an optional format converter, supported by the
> SimpleConverter class. If a converter is available for the pipeline, it
> will be used to expose additional pixel formats.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>

> ---
> Changes since v2:
> 
> - Rebase on top of V4L2PixelFormat
> - Don't crash if the device has no converter
> ---
>  src/libcamera/pipeline/simple/simple.cpp | 196 ++++++++++++++++++++---
>  1 file changed, 178 insertions(+), 18 deletions(-)
> 
> diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
> index e4f33f6ff531..8e7dd091b4ab 100644
> --- a/src/libcamera/pipeline/simple/simple.cpp
> +++ b/src/libcamera/pipeline/simple/simple.cpp
> @@ -11,6 +11,7 @@
>  #include <list>
>  #include <map>
>  #include <memory>
> +#include <queue>
>  #include <set>
>  #include <string>
>  #include <string.h>
> @@ -31,17 +32,24 @@
>  #include "v4l2_subdevice.h"
>  #include "v4l2_videodevice.h"
>  
> +#include "converter.h"
> +
>  namespace libcamera {
>  
>  LOG_DEFINE_CATEGORY(SimplePipeline)
>  
>  class SimplePipelineHandler;
>  
> +struct SimplePipelineInfo {
> +	const char *driver;
> +	const char *converter;
> +};
> +
>  namespace {
>  
> -static const char * const drivers[] = {
> -	"imx7-csi",
> -	"sun6i-csi",
> +static const SimplePipelineInfo supportedDevices[] = {
> +	{ "imx7-csi", "pxp" },
> +	{ "sun6i-csi", nullptr },
>  };
>  
>  } /* namespace */
> @@ -88,6 +96,8 @@ public:
>  
>  	const V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }
>  
> +	bool needConversion() const { return needConversion_; }
> +
>  private:
>  	/*
>  	 * The SimpleCameraData instance is guaranteed to be valid as long as
> @@ -98,6 +108,7 @@ private:
>  	const SimpleCameraData *data_;
>  
>  	V4L2SubdeviceFormat sensorFormat_;
> +	bool needConversion_;
>  };
>  
>  class SimplePipelineHandler : public PipelineHandler
> @@ -120,6 +131,7 @@ public:
>  
>  	V4L2VideoDevice *video() { return video_; }
>  	V4L2Subdevice *subdev(const MediaEntity *entity);
> +	SimpleConverter *converter() { return converter_; }
>  
>  protected:
>  	int queueRequestDevice(Camera *camera, Request *request) override;
> @@ -136,11 +148,17 @@ private:
>  	int createCamera(MediaEntity *sensor);
>  
>  	void bufferReady(FrameBuffer *buffer);
> +	void converterDone(FrameBuffer *input, FrameBuffer *output);
>  
>  	MediaDevice *media_;
>  	V4L2VideoDevice *video_;
>  	std::map<const MediaEntity *, V4L2Subdevice> subdevs_;
>  
> +	SimpleConverter *converter_;
> +	bool useConverter_;
> +	std::vector<std::unique_ptr<FrameBuffer>> converterBuffers_;
> +	std::queue<FrameBuffer *> converterQueue_;
> +
>  	Camera *activeCamera_;
>  };
>  
> @@ -219,6 +237,7 @@ int SimpleCameraData::init()
>  {
>  	SimplePipelineHandler *pipe = static_cast<SimplePipelineHandler *>(pipe_);
>  	V4L2VideoDevice *video = pipe->video();
> +	SimpleConverter *converter = pipe->converter();
>  	int ret;
>  
>  	/*
> @@ -275,7 +294,13 @@ int SimpleCameraData::init()
>  			config.pixelFormat = pixelFormat;
>  			config.size = format.size;
>  
> -			formats_[pixelFormat] = config;
> +			if (!converter) {
> +				formats_[pixelFormat] = config;
> +				continue;
> +			}
> +
> +			for (PixelFormat format : converter->formats(pixelFormat))
> +				formats_[format] = config;
>  		}
>  	}
>  
> @@ -414,6 +439,8 @@ CameraConfiguration::Status SimpleCameraConfiguration::validate()
>  		status = Adjusted;
>  	}
>  
> +	needConversion_ = cfg.pixelFormat != pipeConfig.pixelFormat;
> +
>  	cfg.bufferCount = 3;
>  
>  	return status;
> @@ -424,13 +451,14 @@ CameraConfiguration::Status SimpleCameraConfiguration::validate()
>   */
>  
>  SimplePipelineHandler::SimplePipelineHandler(CameraManager *manager)
> -	: PipelineHandler(manager), video_(nullptr)
> +	: PipelineHandler(manager), video_(nullptr), converter_(nullptr)
>  {
>  }
>  
>  SimplePipelineHandler::~SimplePipelineHandler()
>  {
>  	delete video_;
> +	delete converter_;
>  }
>  
>  CameraConfiguration *SimplePipelineHandler::generateConfiguration(Camera *camera,
> @@ -496,22 +524,37 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c)
>  		return ret;
>  
>  	/* Configure the video node. */
> -	V4L2PixelFormat videoFormat = video_->toV4L2PixelFormat(cfg.pixelFormat);
> +	V4L2PixelFormat videoFormat = video_->toV4L2PixelFormat(pipeConfig.pixelFormat);
>  
> -	V4L2DeviceFormat outputFormat = {};
> -	outputFormat.fourcc = videoFormat;
> -	outputFormat.size = cfg.size;
> +	V4L2DeviceFormat captureFormat = {};
> +	captureFormat.fourcc = videoFormat;
> +	captureFormat.size = cfg.size;
>  
> -	ret = video_->setFormat(&outputFormat);
> +	ret = video_->setFormat(&captureFormat);
>  	if (ret)
>  		return ret;
>  
> -	if (outputFormat.size != cfg.size || outputFormat.fourcc != videoFormat) {
> +	if (captureFormat.fourcc != videoFormat || captureFormat.size != cfg.size) {
>  		LOG(SimplePipeline, Error)
>  			<< "Unable to configure capture in " << cfg.toString();
>  		return -EINVAL;
>  	}
>  
> +	/* Configure the converter if required. */
> +	useConverter_ = config->needConversion();
> +
> +	if (useConverter_) {
> +		int ret = converter_->configure(pipeConfig.pixelFormat,
> +						cfg.pixelFormat, cfg.size);
> +		if (ret < 0) {
> +			LOG(SimplePipeline, Error)
> +				<< "Unable to configure converter";
> +			return ret;
> +		}
> +
> +		LOG(SimplePipeline, Debug) << "Using format converter";
> +	}
> +
>  	cfg.setStream(&data->stream_);
>  
>  	return 0;
> @@ -522,24 +565,47 @@ int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream,
>  {
>  	unsigned int count = stream->configuration().bufferCount;
>  
> -	return video_->exportBuffers(count, buffers);
> +	/*
> +	 * Export buffers on the converter or capture video node, depending on
> +	 * whether the converter is used or not.
> +	 */
> +	if (useConverter_)
> +		return converter_->exportBuffers(count, buffers);
> +	else
> +		return video_->exportBuffers(count, buffers);
>  }
>  
>  int SimplePipelineHandler::start(Camera *camera)
>  {
>  	SimpleCameraData *data = cameraData(camera);
>  	unsigned int count = data->stream_.configuration().bufferCount;
> +	int ret;
>  
> -	int ret = video_->importBuffers(count);
> +	if (useConverter_)
> +		ret = video_->allocateBuffers(count, &converterBuffers_);
> +	else
> +		ret = video_->importBuffers(count);
>  	if (ret < 0)
>  		return ret;
>  
>  	ret = video_->streamOn();
>  	if (ret < 0) {
> -		video_->releaseBuffers();
> +		stop(camera);
>  		return ret;
>  	}
>  
> +	if (useConverter_) {
> +		ret = converter_->start(count);
> +		if (ret < 0) {
> +			stop(camera);
> +			return ret;
> +		}
> +
> +		/* Queue all internal buffers for capture. */
> +		for (std::unique_ptr<FrameBuffer> &buffer : converterBuffers_)
> +			video_->queueBuffer(buffer.get());
> +	}
> +
>  	activeCamera_ = camera;
>  
>  	return 0;
> @@ -547,8 +613,13 @@ int SimplePipelineHandler::start(Camera *camera)
>  
>  void SimplePipelineHandler::stop(Camera *camera)
>  {
> +	if (useConverter_)
> +		converter_->stop();
> +
>  	video_->streamOff();
>  	video_->releaseBuffers();
> +
> +	converterBuffers_.clear();
>  	activeCamera_ = nullptr;
>  }
>  
> @@ -564,6 +635,15 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request)
>  		return -ENOENT;
>  	}
>  
> +	/*
> +	 * If conversion is needed, push the buffer to the converter queue, it
> +	 * will be handed to the converter in the capture completion handler.
> +	 */
> +	if (useConverter_) {
> +		converterQueue_.push(buffer);
> +		return 0;
> +	}
> +
>  	return video_->queueBuffer(buffer);
>  }
>  
> @@ -573,11 +653,20 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request)
>  
>  bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)
>  {
> -	for (const char *driver : drivers) {
> -		DeviceMatch dm(driver);
> +	MediaDevice *converter = nullptr;
> +
> +	for (const SimplePipelineInfo &info : supportedDevices) {
> +		DeviceMatch dm(info.driver);
>  		media_ = acquireMediaDevice(enumerator, dm);
> -		if (media_)
> +		if (!media_)
> +			continue;
> +
> +		if (!info.converter)
>  			break;
> +
> +		DeviceMatch converterMatch(info.converter);
> +		converter = acquireMediaDevice(enumerator, converterMatch);
> +		break;
>  	}
>  
>  	if (!media_)
> @@ -632,6 +721,19 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)
>  
>  	video_->bufferReady.connect(this, &SimplePipelineHandler::bufferReady);
>  
> +	/* Open the converter, if any. */
> +	if (converter) {
> +		converter_ = new SimpleConverter(converter);
> +		if (converter_->open() < 0) {
> +			LOG(SimplePipeline, Warning)
> +				<< "Failed to open converter, disabling format conversion";
> +			delete converter_;
> +			converter_ = nullptr;
> +		}
> +
> +		converter_->bufferReady.connect(this, &SimplePipelineHandler::converterDone);
> +	}
> +
>  	/*
>  	 * Create one camera data instance for each sensor and gather all
>  	 * entities in all pipelines.
> @@ -706,12 +808,70 @@ V4L2Subdevice *SimplePipelineHandler::subdev(const MediaEntity *entity)
>  
>  void SimplePipelineHandler::bufferReady(FrameBuffer *buffer)
>  {
> -	ASSERT(activeCamera_);
> +	/*
> +	 * If an error occurred during capture, or if the buffer was cancelled,
> +	 * complete the request, even if the converter is in use as there's no
> +	 * point converting an erroneous buffer.
> +	 */
> +	if (buffer->metadata().status != FrameMetadata::FrameSuccess) {
> +		if (useConverter_) {
> +			/* Requeue the buffer for capture. */
> +			video_->queueBuffer(buffer);
> +
> +			/*
> +			 * Get the next user-facing buffer to complete the
> +			 * request.
> +			 */
> +			if (converterQueue_.empty())
> +				return;
> +
> +			buffer = converterQueue_.front();
> +			converterQueue_.pop();
> +		}
> +
> +		Request *request = buffer->request();
> +		completeBuffer(activeCamera_, request, buffer);
> +		completeRequest(activeCamera_, request);
> +		return;
> +	}
> +
> +	/*
> +	 * Queue the captured and the request buffer to the converter if format
> +	 * conversion is needed. If there's no queued request, just requeue the
> +	 * captured buffer for capture.
> +	 */
> +	if (useConverter_) {
> +		if (converterQueue_.empty()) {
> +			video_->queueBuffer(buffer);
> +			return;
> +		}
> +
> +		FrameBuffer *output = converterQueue_.front();
> +		converterQueue_.pop();
> +
> +		converter_->queueBuffers(buffer, output);
> +		return;
> +	}
> +
> +	/* Otherwise simply complete the request. */
>  	Request *request = buffer->request();
>  	completeBuffer(activeCamera_, request, buffer);
>  	completeRequest(activeCamera_, request);
>  }
>  
> +void SimplePipelineHandler::converterDone(FrameBuffer *input,
> +					  FrameBuffer *output)
> +{
> +	/* Complete the request. */
> +	ASSERT(activeCamera_);
> +	Request *request = output->request();
> +	completeBuffer(activeCamera_, request, output);
> +	completeRequest(activeCamera_, request);
> +
> +	/* Queue the input buffer back for capture. */
> +	video_->queueBuffer(input);
> +}
> +
>  REGISTER_PIPELINE_HANDLER(SimplePipelineHandler);
>  
>  } /* namespace libcamera */
> -- 
> Regards,
> 
> Laurent Pinchart
> 
> _______________________________________________
> libcamera-devel mailing list
> libcamera-devel@lists.libcamera.org
> https://lists.libcamera.org/listinfo/libcamera-devel

Patch

diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
index e4f33f6ff531..8e7dd091b4ab 100644
--- a/src/libcamera/pipeline/simple/simple.cpp
+++ b/src/libcamera/pipeline/simple/simple.cpp
@@ -11,6 +11,7 @@ 
 #include <list>
 #include <map>
 #include <memory>
+#include <queue>
 #include <set>
 #include <string>
 #include <string.h>
@@ -31,17 +32,24 @@ 
 #include "v4l2_subdevice.h"
 #include "v4l2_videodevice.h"
 
+#include "converter.h"
+
 namespace libcamera {
 
 LOG_DEFINE_CATEGORY(SimplePipeline)
 
 class SimplePipelineHandler;
 
+struct SimplePipelineInfo {
+	const char *driver;
+	const char *converter;
+};
+
 namespace {
 
-static const char * const drivers[] = {
-	"imx7-csi",
-	"sun6i-csi",
+static const SimplePipelineInfo supportedDevices[] = {
+	{ "imx7-csi", "pxp" },
+	{ "sun6i-csi", nullptr },
 };
 
 } /* namespace */
@@ -88,6 +96,8 @@  public:
 
 	const V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; }
 
+	bool needConversion() const { return needConversion_; }
+
 private:
 	/*
 	 * The SimpleCameraData instance is guaranteed to be valid as long as
@@ -98,6 +108,7 @@  private:
 	const SimpleCameraData *data_;
 
 	V4L2SubdeviceFormat sensorFormat_;
+	bool needConversion_;
 };
 
 class SimplePipelineHandler : public PipelineHandler
@@ -120,6 +131,7 @@  public:
 
 	V4L2VideoDevice *video() { return video_; }
 	V4L2Subdevice *subdev(const MediaEntity *entity);
+	SimpleConverter *converter() { return converter_; }
 
 protected:
 	int queueRequestDevice(Camera *camera, Request *request) override;
@@ -136,11 +148,17 @@  private:
 	int createCamera(MediaEntity *sensor);
 
 	void bufferReady(FrameBuffer *buffer);
+	void converterDone(FrameBuffer *input, FrameBuffer *output);
 
 	MediaDevice *media_;
 	V4L2VideoDevice *video_;
 	std::map<const MediaEntity *, V4L2Subdevice> subdevs_;
 
+	SimpleConverter *converter_;
+	bool useConverter_;
+	std::vector<std::unique_ptr<FrameBuffer>> converterBuffers_;
+	std::queue<FrameBuffer *> converterQueue_;
+
 	Camera *activeCamera_;
 };
 
@@ -219,6 +237,7 @@  int SimpleCameraData::init()
 {
 	SimplePipelineHandler *pipe = static_cast<SimplePipelineHandler *>(pipe_);
 	V4L2VideoDevice *video = pipe->video();
+	SimpleConverter *converter = pipe->converter();
 	int ret;
 
 	/*
@@ -275,7 +294,13 @@  int SimpleCameraData::init()
 			config.pixelFormat = pixelFormat;
 			config.size = format.size;
 
-			formats_[pixelFormat] = config;
+			if (!converter) {
+				formats_[pixelFormat] = config;
+				continue;
+			}
+
+			for (PixelFormat format : converter->formats(pixelFormat))
+				formats_[format] = config;
 		}
 	}
 
@@ -414,6 +439,8 @@  CameraConfiguration::Status SimpleCameraConfiguration::validate()
 		status = Adjusted;
 	}
 
+	needConversion_ = cfg.pixelFormat != pipeConfig.pixelFormat;
+
 	cfg.bufferCount = 3;
 
 	return status;
@@ -424,13 +451,14 @@  CameraConfiguration::Status SimpleCameraConfiguration::validate()
  */
 
 SimplePipelineHandler::SimplePipelineHandler(CameraManager *manager)
-	: PipelineHandler(manager), video_(nullptr)
+	: PipelineHandler(manager), video_(nullptr), converter_(nullptr)
 {
 }
 
 SimplePipelineHandler::~SimplePipelineHandler()
 {
 	delete video_;
+	delete converter_;
 }
 
 CameraConfiguration *SimplePipelineHandler::generateConfiguration(Camera *camera,
@@ -496,22 +524,37 @@  int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c)
 		return ret;
 
 	/* Configure the video node. */
-	V4L2PixelFormat videoFormat = video_->toV4L2PixelFormat(cfg.pixelFormat);
+	V4L2PixelFormat videoFormat = video_->toV4L2PixelFormat(pipeConfig.pixelFormat);
 
-	V4L2DeviceFormat outputFormat = {};
-	outputFormat.fourcc = videoFormat;
-	outputFormat.size = cfg.size;
+	V4L2DeviceFormat captureFormat = {};
+	captureFormat.fourcc = videoFormat;
+	captureFormat.size = cfg.size;
 
-	ret = video_->setFormat(&outputFormat);
+	ret = video_->setFormat(&captureFormat);
 	if (ret)
 		return ret;
 
-	if (outputFormat.size != cfg.size || outputFormat.fourcc != videoFormat) {
+	if (captureFormat.fourcc != videoFormat || captureFormat.size != cfg.size) {
 		LOG(SimplePipeline, Error)
 			<< "Unable to configure capture in " << cfg.toString();
 		return -EINVAL;
 	}
 
+	/* Configure the converter if required. */
+	useConverter_ = config->needConversion();
+
+	if (useConverter_) {
+		int ret = converter_->configure(pipeConfig.pixelFormat,
+						cfg.pixelFormat, cfg.size);
+		if (ret < 0) {
+			LOG(SimplePipeline, Error)
+				<< "Unable to configure converter";
+			return ret;
+		}
+
+		LOG(SimplePipeline, Debug) << "Using format converter";
+	}
+
 	cfg.setStream(&data->stream_);
 
 	return 0;
@@ -522,24 +565,47 @@  int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream,
 {
 	unsigned int count = stream->configuration().bufferCount;
 
-	return video_->exportBuffers(count, buffers);
+	/*
+	 * Export buffers on the converter or capture video node, depending on
+	 * whether the converter is used or not.
+	 */
+	if (useConverter_)
+		return converter_->exportBuffers(count, buffers);
+	else
+		return video_->exportBuffers(count, buffers);
 }
 
 int SimplePipelineHandler::start(Camera *camera)
 {
 	SimpleCameraData *data = cameraData(camera);
 	unsigned int count = data->stream_.configuration().bufferCount;
+	int ret;
 
-	int ret = video_->importBuffers(count);
+	if (useConverter_)
+		ret = video_->allocateBuffers(count, &converterBuffers_);
+	else
+		ret = video_->importBuffers(count);
 	if (ret < 0)
 		return ret;
 
 	ret = video_->streamOn();
 	if (ret < 0) {
-		video_->releaseBuffers();
+		stop(camera);
 		return ret;
 	}
 
+	if (useConverter_) {
+		ret = converter_->start(count);
+		if (ret < 0) {
+			stop(camera);
+			return ret;
+		}
+
+		/* Queue all internal buffers for capture. */
+		for (std::unique_ptr<FrameBuffer> &buffer : converterBuffers_)
+			video_->queueBuffer(buffer.get());
+	}
+
 	activeCamera_ = camera;
 
 	return 0;
@@ -547,8 +613,13 @@  int SimplePipelineHandler::start(Camera *camera)
 
 void SimplePipelineHandler::stop(Camera *camera)
 {
+	if (useConverter_)
+		converter_->stop();
+
 	video_->streamOff();
 	video_->releaseBuffers();
+
+	converterBuffers_.clear();
 	activeCamera_ = nullptr;
 }
 
@@ -564,6 +635,15 @@  int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request)
 		return -ENOENT;
 	}
 
+	/*
+	 * If conversion is needed, push the buffer to the converter queue, it
+	 * will be handed to the converter in the capture completion handler.
+	 */
+	if (useConverter_) {
+		converterQueue_.push(buffer);
+		return 0;
+	}
+
 	return video_->queueBuffer(buffer);
 }
 
@@ -573,11 +653,20 @@  int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request)
 
 bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)
 {
-	for (const char *driver : drivers) {
-		DeviceMatch dm(driver);
+	MediaDevice *converter = nullptr;
+
+	for (const SimplePipelineInfo &info : supportedDevices) {
+		DeviceMatch dm(info.driver);
 		media_ = acquireMediaDevice(enumerator, dm);
-		if (media_)
+		if (!media_)
+			continue;
+
+		if (!info.converter)
 			break;
+
+		DeviceMatch converterMatch(info.converter);
+		converter = acquireMediaDevice(enumerator, converterMatch);
+		break;
 	}
 
 	if (!media_)
@@ -632,6 +721,19 @@  bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)
 
 	video_->bufferReady.connect(this, &SimplePipelineHandler::bufferReady);
 
+	/* Open the converter, if any. */
+	if (converter) {
+		converter_ = new SimpleConverter(converter);
+		if (converter_->open() < 0) {
+			LOG(SimplePipeline, Warning)
+				<< "Failed to open converter, disabling format conversion";
+			delete converter_;
+			converter_ = nullptr;
+		}
+
+		converter_->bufferReady.connect(this, &SimplePipelineHandler::converterDone);
+	}
+
 	/*
 	 * Create one camera data instance for each sensor and gather all
 	 * entities in all pipelines.
@@ -706,12 +808,70 @@  V4L2Subdevice *SimplePipelineHandler::subdev(const MediaEntity *entity)
 
 void SimplePipelineHandler::bufferReady(FrameBuffer *buffer)
 {
-	ASSERT(activeCamera_);
+	/*
+	 * If an error occurred during capture, or if the buffer was cancelled,
+	 * complete the request, even if the converter is in use as there's no
+	 * point converting an erroneous buffer.
+	 */
+	if (buffer->metadata().status != FrameMetadata::FrameSuccess) {
+		if (useConverter_) {
+			/* Requeue the buffer for capture. */
+			video_->queueBuffer(buffer);
+
+			/*
+			 * Get the next user-facing buffer to complete the
+			 * request.
+			 */
+			if (converterQueue_.empty())
+				return;
+
+			buffer = converterQueue_.front();
+			converterQueue_.pop();
+		}
+
+		Request *request = buffer->request();
+		completeBuffer(activeCamera_, request, buffer);
+		completeRequest(activeCamera_, request);
+		return;
+	}
+
+	/*
+	 * Queue the captured and the request buffer to the converter if format
+	 * conversion is needed. If there's no queued request, just requeue the
+	 * captured buffer for capture.
+	 */
+	if (useConverter_) {
+		if (converterQueue_.empty()) {
+			video_->queueBuffer(buffer);
+			return;
+		}
+
+		FrameBuffer *output = converterQueue_.front();
+		converterQueue_.pop();
+
+		converter_->queueBuffers(buffer, output);
+		return;
+	}
+
+	/* Otherwise simply complete the request. */
 	Request *request = buffer->request();
 	completeBuffer(activeCamera_, request, buffer);
 	completeRequest(activeCamera_, request);
 }
 
+void SimplePipelineHandler::converterDone(FrameBuffer *input,
+					  FrameBuffer *output)
+{
+	/* Complete the request. */
+	ASSERT(activeCamera_);
+	Request *request = output->request();
+	completeBuffer(activeCamera_, request, output);
+	completeRequest(activeCamera_, request);
+
+	/* Queue the input buffer back for capture. */
+	video_->queueBuffer(input);
+}
+
 REGISTER_PIPELINE_HANDLER(SimplePipelineHandler);
 
 } /* namespace libcamera */