[libcamera-devel,v2,1/3] qcam: Allow for a second raw stream to be configured

Message ID 20200501152745.437777-2-niklas.soderlund@ragnatech.se
State Superseded
Headers show
Series
  • qcam: Add RAW capture support
Related show

Commit Message

Niklas Söderlund May 1, 2020, 3:27 p.m. UTC
Allow a second stream to be configured for raw capture. This change only
adds support for configuring and allocating buffers for the second
stream. Later changes are needed to queue the allocated buffers to the
camera when the user wishes to capture a raw frame.

Signed-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
---
* Changes since v1
- Keep doneQueue_ as a QQueue and keep the original processing
  granularity.
---
 src/qcam/main_window.cpp | 125 +++++++++++++++++++++++++++------------
 src/qcam/main_window.h   |   8 ++-
 2 files changed, 94 insertions(+), 39 deletions(-)

Comments

Laurent Pinchart May 1, 2020, 4:50 p.m. UTC | #1
Hi Niklas,

Thank you for the patch.

On Fri, May 01, 2020 at 05:27:43PM +0200, Niklas Söderlund wrote:
> Allow a second stream to be configured for raw capture. This change only
> adds support for configuring and allocating buffers for the second
> stream. Later changes are needed to queue the allocated buffers to the
> camera when the user wishes to capture a raw frame.
> 
> Signed-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
> ---
> * Changes since v1
> - Keep doneQueue_ as a QQueue and keep the original processing
>   granularity.
> ---
>  src/qcam/main_window.cpp | 125 +++++++++++++++++++++++++++------------
>  src/qcam/main_window.h   |   8 ++-
>  2 files changed, 94 insertions(+), 39 deletions(-)
> 
> diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp
> index b9348111dfa28914..dc8824dae4669a7e 100644
> --- a/src/qcam/main_window.cpp
> +++ b/src/qcam/main_window.cpp
> @@ -281,17 +281,30 @@ void MainWindow::toggleCapture(bool start)
>  int MainWindow::startCapture()
>  {
>  	StreamRoles roles = StreamKeyValueParser::roles(options_[OptStream]);
> +	std::vector<Request *> requests;
>  	int ret;
>  
>  	/* Verify roles are supported. */
> -	if (roles.size() != 1) {
> -		qCritical() << "Only one stream supported";
> -		return -EINVAL;
> -	}
> -
> -	if (roles[0] != StreamRole::Viewfinder) {
> -		qCritical() << "Only viewfinder supported";
> -		return -EINVAL;
> +	switch (roles.size()) {
> +	case 1:
> +		if (roles[0] != StreamRole::Viewfinder) {
> +			qWarning() << "Only viewfinder supported for single stream";
> +			return -EINVAL;
> +		}
> +		break;
> +	case 2:
> +		if (roles[0] != StreamRole::Viewfinder ||
> +		    roles[1] != StreamRole::StillCaptureRaw) {
> +			qWarning() << "Only viewfinder + raw supported for dual streams";
> +			return -EINVAL;
> +		}
> +		break;
> +	default:
> +		if (roles.size() != 1) {
> +			qWarning() << "Unsuported stream configuration";
> +			return -EINVAL;
> +		}
> +		break;
>  	}
>  
>  	/* Configure the camera. */
> @@ -301,17 +314,17 @@ int MainWindow::startCapture()
>  		return -EINVAL;
>  	}
>  
> -	StreamConfiguration &cfg = config_->at(0);
> +	StreamConfiguration &vfConfig = config_->at(0);
>  
>  	/* Use a format supported by the viewfinder if available. */
> -	std::vector<PixelFormat> formats = cfg.formats().pixelformats();
> +	std::vector<PixelFormat> formats = vfConfig.formats().pixelformats();
>  	for (const PixelFormat &format : viewfinder_->nativeFormats()) {
>  		auto match = std::find_if(formats.begin(), formats.end(),
>  					  [&](const PixelFormat &f) {
>  						  return f == format;
>  					  });
>  		if (match != formats.end()) {
> -			cfg.pixelFormat = format;
> +			vfConfig.pixelFormat = format;
>  			break;
>  		}
>  	}
> @@ -331,7 +344,7 @@ int MainWindow::startCapture()
>  
>  	if (validation == CameraConfiguration::Adjusted)
>  		qInfo() << "Stream configuration adjusted to "
> -			<< cfg.toString().c_str();
> +			<< vfConfig.toString().c_str();
>  
>  	ret = camera_->configure(config_.get());
>  	if (ret < 0) {
> @@ -339,10 +352,16 @@ int MainWindow::startCapture()
>  		return ret;
>  	}
>  
> +	/* Store stream allocation. */
> +	vfStream_ = config_->at(0).stream();
> +	if (config_->size() == 2)
> +		rawStream_ = config_->at(1).stream();
> +	else
> +		rawStream_ = nullptr;
> +
>  	/* Configure the viewfinder. */
> -	Stream *stream = cfg.stream();
> -	ret = viewfinder_->setFormat(cfg.pixelFormat,
> -				     QSize(cfg.size.width, cfg.size.height));
> +	ret = viewfinder_->setFormat(vfConfig.pixelFormat,
> +				     QSize(vfConfig.size.width, vfConfig.size.height));
>  	if (ret < 0) {
>  		qInfo() << "Failed to set viewfinder format";
>  		return ret;
> @@ -350,16 +369,33 @@ int MainWindow::startCapture()
>  
>  	adjustSize();
>  
> -	/* Allocate buffers and requests. */
> +	/* Allocate and map buffers. */
>  	allocator_ = new FrameBufferAllocator(camera_);
> -	ret = allocator_->allocate(stream);
> -	if (ret < 0) {
> -		qWarning() << "Failed to allocate capture buffers";
> -		return ret;
> +	for (StreamConfiguration &config : *config_) {
> +		Stream *stream = config.stream();
> +
> +		ret = allocator_->allocate(stream);
> +		if (ret < 0) {
> +			qWarning() << "Failed to allocate capture buffers";
> +			goto error;
> +		}
> +
> +		for (const std::unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) {
> +			/* Map memory buffers and cache the mappings. */
> +			const FrameBuffer::Plane &plane = buffer->planes().front();
> +			void *memory = mmap(NULL, plane.length, PROT_READ, MAP_SHARED,
> +					    plane.fd.fd(), 0);
> +			mappedBuffers_[buffer.get()] = { memory, plane.length };
> +
> +			/* Store buffers on the free list. */
> +			freeBuffers_[stream].enqueue(buffer.get());
> +		}
>  	}
>  
> -	std::vector<Request *> requests;
> -	for (const std::unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) {
> +	/* Create requests and fill it with buffers from the viewfinder. */

s/it/them/

> +	while (!freeBuffers_[vfStream_].isEmpty()) {
> +		FrameBuffer *buffer = freeBuffers_[vfStream_].dequeue();
> +
>  		Request *request = camera_->createRequest();
>  		if (!request) {
>  			qWarning() << "Can't create request";
> @@ -367,19 +403,13 @@ int MainWindow::startCapture()
>  			goto error;
>  		}
>  
> -		ret = request->addBuffer(stream, buffer.get());
> +		ret = request->addBuffer(vfStream_, buffer);
>  		if (ret < 0) {
>  			qWarning() << "Can't set buffer for request";
>  			goto error;
>  		}
>  
>  		requests.push_back(request);
> -
> -		/* Map memory buffers and cache the mappings. */
> -		const FrameBuffer::Plane &plane = buffer->planes().front();
> -		void *memory = mmap(NULL, plane.length, PROT_READ, MAP_SHARED,
> -				    plane.fd.fd(), 0);
> -		mappedBuffers_[buffer.get()] = { memory, plane.length };
>  	}
>  
>  	/* Start the title timer and the camera. */
> @@ -424,6 +454,8 @@ error:
>  	}
>  	mappedBuffers_.clear();
>  
> +	freeBuffers_.clear();
> +
>  	delete allocator_;
>  	allocator_ = nullptr;
>  
> @@ -466,6 +498,7 @@ void MainWindow::stopCapture()
>  	 * but not processed yet. Clear the queue of done buffers to avoid
>  	 * racing with the event handler.
>  	 */
> +	freeBuffers_.clear();
>  	doneQueue_.clear();
>  
>  	titleTimer_.stop();
> @@ -505,12 +538,9 @@ void MainWindow::requestComplete(Request *request)
>  	 * are not allowed. Add the buffer to the done queue and post a
>  	 * CaptureEvent for the application thread to handle.
>  	 */
> -	const std::map<Stream *, FrameBuffer *> &buffers = request->buffers();
> -	FrameBuffer *buffer = buffers.begin()->second;
> -
>  	{
>  		QMutexLocker locker(&mutex_);
> -		doneQueue_.enqueue(buffer);
> +		doneQueue_.enqueue(request->buffers());
>  	}
>  
>  	QCoreApplication::postEvent(this, new CaptureEvent);
> @@ -523,16 +553,38 @@ void MainWindow::processCapture()
>  	 * if stopCapture() has been called while a CaptureEvent was posted but
>  	 * not processed yet. Return immediately in that case.
>  	 */
> -	FrameBuffer *buffer;
> +	std::map<Stream *, FrameBuffer *> buffers;
>  
>  	{
>  		QMutexLocker locker(&mutex_);
>  		if (doneQueue_.isEmpty())
>  			return;
>  
> -		buffer = doneQueue_.dequeue();
> +		buffers = doneQueue_.dequeue();
>  	}
>  
> +	/* Process buffers. */
> +	if (buffers.count(vfStream_))
> +		processViewfinder(buffers[vfStream_]);
> +
> +	/*
> +	 * Return buffers so they can be reused. No processing involving
> +	 * a buffer can happen after they are returned to the free list.
> +	 */
> +	for (auto &it : buffers) {
> +		Stream *stream = it.first;
> +		FrameBuffer *buffer = it.second;
> +
> +		/* The ViewFinder manages the viewfinder buffers. */
> +		if (stream == vfStream_)
> +			continue;
> +
> +		freeBuffers_[stream].enqueue(buffer);
> +	}

This looks a bit dodgy as the free buffers list is only used when
starting the stream, but it will get used for raw capture in the next
patches, so it's fine.

Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> +}
> +
> +void MainWindow::processViewfinder(FrameBuffer *buffer)
> +{
>  	framesCaptured_++;
>  
>  	const FrameMetadata &metadata = buffer->metadata();
> @@ -559,8 +611,7 @@ void MainWindow::queueRequest(FrameBuffer *buffer)
>  		return;
>  	}
>  
> -	Stream *stream = config_->at(0).stream();
> -	request->addBuffer(stream, buffer);
> +	request->addBuffer(vfStream_, buffer);
>  
>  	camera_->queueRequest(request);
>  }
> diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h
> index aea1f1dee20fcbb6..4856ecc10729159c 100644
> --- a/src/qcam/main_window.h
> +++ b/src/qcam/main_window.h
> @@ -69,6 +69,7 @@ private:
>  
>  	void requestComplete(Request *request);
>  	void processCapture();
> +	void processViewfinder(FrameBuffer *buffer);
>  
>  	/* UI elements */
>  	QToolBar *toolbar_;
> @@ -95,8 +96,11 @@ private:
>  
>  	/* Capture state, buffers queue and statistics */
>  	bool isCapturing_;
> -	QQueue<FrameBuffer *> doneQueue_;
> -	QMutex mutex_;	/* Protects doneQueue_ */
> +	Stream *vfStream_;
> +	Stream *rawStream_;
> +	std::map<Stream *, QQueue<FrameBuffer *>> freeBuffers_;
> +	QQueue<std::map<Stream *, FrameBuffer *>> doneQueue_;
> +	QMutex mutex_; /* Protects freeBuffers_ and doneQueue_ */
>  
>  	uint64_t lastBufferTime_;
>  	QElapsedTimer frameRateInterval_;

Patch

diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp
index b9348111dfa28914..dc8824dae4669a7e 100644
--- a/src/qcam/main_window.cpp
+++ b/src/qcam/main_window.cpp
@@ -281,17 +281,30 @@  void MainWindow::toggleCapture(bool start)
 int MainWindow::startCapture()
 {
 	StreamRoles roles = StreamKeyValueParser::roles(options_[OptStream]);
+	std::vector<Request *> requests;
 	int ret;
 
 	/* Verify roles are supported. */
-	if (roles.size() != 1) {
-		qCritical() << "Only one stream supported";
-		return -EINVAL;
-	}
-
-	if (roles[0] != StreamRole::Viewfinder) {
-		qCritical() << "Only viewfinder supported";
-		return -EINVAL;
+	switch (roles.size()) {
+	case 1:
+		if (roles[0] != StreamRole::Viewfinder) {
+			qWarning() << "Only viewfinder supported for single stream";
+			return -EINVAL;
+		}
+		break;
+	case 2:
+		if (roles[0] != StreamRole::Viewfinder ||
+		    roles[1] != StreamRole::StillCaptureRaw) {
+			qWarning() << "Only viewfinder + raw supported for dual streams";
+			return -EINVAL;
+		}
+		break;
+	default:
+		if (roles.size() != 1) {
+			qWarning() << "Unsuported stream configuration";
+			return -EINVAL;
+		}
+		break;
 	}
 
 	/* Configure the camera. */
@@ -301,17 +314,17 @@  int MainWindow::startCapture()
 		return -EINVAL;
 	}
 
-	StreamConfiguration &cfg = config_->at(0);
+	StreamConfiguration &vfConfig = config_->at(0);
 
 	/* Use a format supported by the viewfinder if available. */
-	std::vector<PixelFormat> formats = cfg.formats().pixelformats();
+	std::vector<PixelFormat> formats = vfConfig.formats().pixelformats();
 	for (const PixelFormat &format : viewfinder_->nativeFormats()) {
 		auto match = std::find_if(formats.begin(), formats.end(),
 					  [&](const PixelFormat &f) {
 						  return f == format;
 					  });
 		if (match != formats.end()) {
-			cfg.pixelFormat = format;
+			vfConfig.pixelFormat = format;
 			break;
 		}
 	}
@@ -331,7 +344,7 @@  int MainWindow::startCapture()
 
 	if (validation == CameraConfiguration::Adjusted)
 		qInfo() << "Stream configuration adjusted to "
-			<< cfg.toString().c_str();
+			<< vfConfig.toString().c_str();
 
 	ret = camera_->configure(config_.get());
 	if (ret < 0) {
@@ -339,10 +352,16 @@  int MainWindow::startCapture()
 		return ret;
 	}
 
+	/* Store stream allocation. */
+	vfStream_ = config_->at(0).stream();
+	if (config_->size() == 2)
+		rawStream_ = config_->at(1).stream();
+	else
+		rawStream_ = nullptr;
+
 	/* Configure the viewfinder. */
-	Stream *stream = cfg.stream();
-	ret = viewfinder_->setFormat(cfg.pixelFormat,
-				     QSize(cfg.size.width, cfg.size.height));
+	ret = viewfinder_->setFormat(vfConfig.pixelFormat,
+				     QSize(vfConfig.size.width, vfConfig.size.height));
 	if (ret < 0) {
 		qInfo() << "Failed to set viewfinder format";
 		return ret;
@@ -350,16 +369,33 @@  int MainWindow::startCapture()
 
 	adjustSize();
 
-	/* Allocate buffers and requests. */
+	/* Allocate and map buffers. */
 	allocator_ = new FrameBufferAllocator(camera_);
-	ret = allocator_->allocate(stream);
-	if (ret < 0) {
-		qWarning() << "Failed to allocate capture buffers";
-		return ret;
+	for (StreamConfiguration &config : *config_) {
+		Stream *stream = config.stream();
+
+		ret = allocator_->allocate(stream);
+		if (ret < 0) {
+			qWarning() << "Failed to allocate capture buffers";
+			goto error;
+		}
+
+		for (const std::unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) {
+			/* Map memory buffers and cache the mappings. */
+			const FrameBuffer::Plane &plane = buffer->planes().front();
+			void *memory = mmap(NULL, plane.length, PROT_READ, MAP_SHARED,
+					    plane.fd.fd(), 0);
+			mappedBuffers_[buffer.get()] = { memory, plane.length };
+
+			/* Store buffers on the free list. */
+			freeBuffers_[stream].enqueue(buffer.get());
+		}
 	}
 
-	std::vector<Request *> requests;
-	for (const std::unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) {
+	/* Create requests and fill it with buffers from the viewfinder. */
+	while (!freeBuffers_[vfStream_].isEmpty()) {
+		FrameBuffer *buffer = freeBuffers_[vfStream_].dequeue();
+
 		Request *request = camera_->createRequest();
 		if (!request) {
 			qWarning() << "Can't create request";
@@ -367,19 +403,13 @@  int MainWindow::startCapture()
 			goto error;
 		}
 
-		ret = request->addBuffer(stream, buffer.get());
+		ret = request->addBuffer(vfStream_, buffer);
 		if (ret < 0) {
 			qWarning() << "Can't set buffer for request";
 			goto error;
 		}
 
 		requests.push_back(request);
-
-		/* Map memory buffers and cache the mappings. */
-		const FrameBuffer::Plane &plane = buffer->planes().front();
-		void *memory = mmap(NULL, plane.length, PROT_READ, MAP_SHARED,
-				    plane.fd.fd(), 0);
-		mappedBuffers_[buffer.get()] = { memory, plane.length };
 	}
 
 	/* Start the title timer and the camera. */
@@ -424,6 +454,8 @@  error:
 	}
 	mappedBuffers_.clear();
 
+	freeBuffers_.clear();
+
 	delete allocator_;
 	allocator_ = nullptr;
 
@@ -466,6 +498,7 @@  void MainWindow::stopCapture()
 	 * but not processed yet. Clear the queue of done buffers to avoid
 	 * racing with the event handler.
 	 */
+	freeBuffers_.clear();
 	doneQueue_.clear();
 
 	titleTimer_.stop();
@@ -505,12 +538,9 @@  void MainWindow::requestComplete(Request *request)
 	 * are not allowed. Add the buffer to the done queue and post a
 	 * CaptureEvent for the application thread to handle.
 	 */
-	const std::map<Stream *, FrameBuffer *> &buffers = request->buffers();
-	FrameBuffer *buffer = buffers.begin()->second;
-
 	{
 		QMutexLocker locker(&mutex_);
-		doneQueue_.enqueue(buffer);
+		doneQueue_.enqueue(request->buffers());
 	}
 
 	QCoreApplication::postEvent(this, new CaptureEvent);
@@ -523,16 +553,38 @@  void MainWindow::processCapture()
 	 * if stopCapture() has been called while a CaptureEvent was posted but
 	 * not processed yet. Return immediately in that case.
 	 */
-	FrameBuffer *buffer;
+	std::map<Stream *, FrameBuffer *> buffers;
 
 	{
 		QMutexLocker locker(&mutex_);
 		if (doneQueue_.isEmpty())
 			return;
 
-		buffer = doneQueue_.dequeue();
+		buffers = doneQueue_.dequeue();
 	}
 
+	/* Process buffers. */
+	if (buffers.count(vfStream_))
+		processViewfinder(buffers[vfStream_]);
+
+	/*
+	 * Return buffers so they can be reused. No processing involving
+	 * a buffer can happen after they are returned to the free list.
+	 */
+	for (auto &it : buffers) {
+		Stream *stream = it.first;
+		FrameBuffer *buffer = it.second;
+
+		/* The ViewFinder manages the viewfinder buffers. */
+		if (stream == vfStream_)
+			continue;
+
+		freeBuffers_[stream].enqueue(buffer);
+	}
+}
+
+void MainWindow::processViewfinder(FrameBuffer *buffer)
+{
 	framesCaptured_++;
 
 	const FrameMetadata &metadata = buffer->metadata();
@@ -559,8 +611,7 @@  void MainWindow::queueRequest(FrameBuffer *buffer)
 		return;
 	}
 
-	Stream *stream = config_->at(0).stream();
-	request->addBuffer(stream, buffer);
+	request->addBuffer(vfStream_, buffer);
 
 	camera_->queueRequest(request);
 }
diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h
index aea1f1dee20fcbb6..4856ecc10729159c 100644
--- a/src/qcam/main_window.h
+++ b/src/qcam/main_window.h
@@ -69,6 +69,7 @@  private:
 
 	void requestComplete(Request *request);
 	void processCapture();
+	void processViewfinder(FrameBuffer *buffer);
 
 	/* UI elements */
 	QToolBar *toolbar_;
@@ -95,8 +96,11 @@  private:
 
 	/* Capture state, buffers queue and statistics */
 	bool isCapturing_;
-	QQueue<FrameBuffer *> doneQueue_;
-	QMutex mutex_;	/* Protects doneQueue_ */
+	Stream *vfStream_;
+	Stream *rawStream_;
+	std::map<Stream *, QQueue<FrameBuffer *>> freeBuffers_;
+	QQueue<std::map<Stream *, FrameBuffer *>> doneQueue_;
+	QMutex mutex_; /* Protects freeBuffers_ and doneQueue_ */
 
 	uint64_t lastBufferTime_;
 	QElapsedTimer frameRateInterval_;