summaryrefslogtreecommitdiff
path: root/src/android/camera_capabilities.cpp
AgeCommit message (Expand)Author
2022-11-25libcamera: stream: Turn StreamRole into scoped enumerationLaurent Pinchart
2022-10-28android: libcamera: add useful debug printsNicholas Roth
2022-07-25android: camera_capabilities: Adjust minimum frame duration to match FPSHan-Lin Chen
2022-07-25android: camera_capabilities: Add (1600x1200) and (1280x960) resolutionsHan-Lin Chen
2022-07-19libcamera: controls: Avoid double lookupsLaurent Pinchart
2022-07-19libcamera: controls: Use std::optional to handle invalid control valuesChristian Rauch
2022-05-04libcamera: Replace toString with operator<<() for format classesLaurent Pinchart
2022-05-04libcamera: Replace toString with operator<<() for geometry classesLaurent Pinchart
2021-12-22android: camera_capabilities: Fix the type of the capability vectorPaul Elder
2021-12-22android: camera_capabilities: Set read sensor settings capabilityPaul Elder
2021-12-22android: camera_capabilities: Add messages for lack of FULL supportPaul Elder
2021-12-07android: Apply 1% tolerance to minFrameDuration cappingUmang Jain
2021-10-15android: capabilities: Cap frame rate to 30 FPSJacopo Mondi
2021-10-15android: capabilties: Fix ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGESJacopo Mondi
2021-10-15android: Populate streams and duration in the same loopJacopo Mondi
2021-10-15android: capabilities: Print output stream listJacopo Mondi
2021-10-15android: Filter preview streams on FPSJacopo Mondi
2021-10-15android: capabilities: Collect absolute max frame durationsJacopo Mondi
2021-10-15android: capabilties: Correctly populate STALL durationsJacopo Mondi
2021-10-15android: capabilities: Use per-configuration durationsJacopo Mondi
2021-10-15android: capabilties: Assume controls::FrameDurationLimits is supportedJacopo Mondi
2021-10-15android: capabilities: Initialize camera state when building propertiesJacopo Mondi
2021-10-15android: capabilities: Collect per-stream frame durationsJacopo Mondi
2021-10-13android: Check if Stream configurations were generated correctlyJavier Martinez Canillas
2021-09-27android: Fix generation of thumbnail for EXIF dataUmang Jain
2021-09-27android: camera_capabilities: Clarify CameraMetadata allocationUmang Jain
2021-09-06android: Cleanup libcamera namespace usageUmang Jain
2021-08-31android: Refuse Manual template if not supportedJacopo Mondi
2021-08-02android, controls: Add and plumb MaxLatency controlPaul Elder
2021-08-02android: Add skeletal still and manual request templatesPaul Elder
2021-08-02android: capabilities: Make keys list into set and member variablePaul Elder
2021-08-02android: Add helpers for setting android metadata from libcamera controlsPaul Elder
2021-08-02android: Add infrastructure for determining capabilities and hardware levelPaul Elder
2021-07-27android: capabilities: Centralize RAW support checkJacopo Mondi
2021-07-27android: capabilties: Rationalize get[YUV|Raw]Resolution namesJacopo Mondi
2021-07-27android: capabilities: Use a throw-away config for YUV stream buildingJacopo Mondi
2021-06-25libcamera/base: Move extended base functionalityKieran Bingham
2021-06-23android: Introduce CameraCapabilties classJacopo Mondi
#n389'>389 390 391
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2019, Google Inc.
 *
 * camera_session.cpp - Camera capture session
 */

#include <iomanip>
#include <iostream>
#include <limits.h>
#include <sstream>

#include <libcamera/control_ids.h>
#include <libcamera/property_ids.h>

#include "camera_session.h"
#include "event_loop.h"
#include "file_sink.h"
#include "main.h"
#include "stream_options.h"

using namespace libcamera;

CameraSession::CameraSession(CameraManager *cm,
			     const std::string &cameraId,
			     unsigned int cameraIndex,
			     const OptionsParser::Options &options)
	: options_(options), cameraIndex_(cameraIndex), last_(0),
	  queueCount_(0), captureCount_(0), captureLimit_(0),
	  printMetadata_(false)
{
	char *endptr;
	unsigned long index = strtoul(cameraId.c_str(), &endptr, 10);
	if (*endptr == '\0' && index > 0 && index <= cm->cameras().size())
		camera_ = cm->cameras()[index - 1];
	else
		camera_ = cm->get(cameraId);

	if (!camera_) {
		std::cerr << "Camera " << cameraId << " not found" << std::endl;
		return;
	}

	if (camera_->acquire()) {
		std::cerr << "Failed to acquire camera " << cameraId
			  << std::endl;
		return;
	}

	StreamRoles roles = StreamKeyValueParser::roles(options_[OptStream]);

	std::unique_ptr<CameraConfiguration> config =
		camera_->generateConfiguration(roles);
	if (!config || config->size() != roles.size()) {
		std::cerr << "Failed to get default stream configuration"
			  << std::endl;
		return;
	}

	/* Apply configuration if explicitly requested. */
	if (StreamKeyValueParser::updateConfiguration(config.get(),
						      options_[OptStream])) {
		std::cerr << "Failed to update configuration" << std::endl;
		return;
	}

	bool strictFormats = options_.isSet(OptStrictFormats);

	switch (config->validate()) {
	case CameraConfiguration::Valid:
		break;

	case CameraConfiguration::Adjusted:
		if (strictFormats) {
			std::cout << "Adjusting camera configuration disallowed by --strict-formats argument"
				  << std::endl;
			return;
		}
		std::cout << "Camera configuration adjusted" << std::endl;
		break;

	case CameraConfiguration::Invalid:
		std::cout << "Camera configuration invalid" << std::endl;
		return;
	}

	config_ = std::move(config);
}

CameraSession::~CameraSession()
{
	if (camera_)
		camera_->release();
}

void CameraSession::listControls() const
{
	for (const auto &ctrl : camera_->controls()) {
		const ControlId *id = ctrl.first;
		const ControlInfo &info = ctrl.second;

		std::cout << "Control: " << id->name() << ": "
			  << info.toString() << std::endl;
	}
}

void CameraSession::listProperties() const
{
	for (const auto &prop : camera_->properties()) {
		const ControlId *id = properties::properties.at(prop.first);
		const ControlValue &value = prop.second;

		std::cout << "Property: " << id->name() << " = "
			  << value.toString() << std::endl;
	}
}

void CameraSession::infoConfiguration() const
{
	unsigned int index = 0;
	for (const StreamConfiguration &cfg : *config_) {
		std::cout << index << ": " << cfg.toString() << std::endl;

		const StreamFormats &formats = cfg.formats();
		for (PixelFormat pixelformat : formats.pixelformats()) {
			std::cout << " * Pixelformat: "
				  << pixelformat.toString() << " "
				  << formats.range(pixelformat).toString()
				  << std::endl;

			for (const Size &size : formats.sizes(pixelformat))
				std::cout << "  - " << size.toString()
					  << std::endl;
		}

		index++;
	}
}

int CameraSession::start()
{
	int ret;

	queueCount_ = 0;
	captureCount_ = 0;
	captureLimit_ = options_[OptCapture].toInteger();
	printMetadata_ = options_.isSet(OptMetadata);

	ret = camera_->configure(config_.get());
	if (ret < 0) {
		std::cout << "Failed to configure camera" << std::endl;
		return ret;
	}

	streamName_.clear();
	for (unsigned int index = 0; index < config_->size(); ++index) {
		StreamConfiguration &cfg = config_->at(index);
		streamName_[cfg.stream()] = "cam" + std::to_string(cameraIndex_)
					  + "-stream" + std::to_string(index);
	}

	camera_->requestCompleted.connect(this, &CameraSession::requestComplete);

	if (options_.isSet(OptFile)) {
		if (!options_[OptFile].toString().empty())
			sink_ = std::make_unique<FileSink>(options_[OptFile]);
		else
			sink_ = std::make_unique<FileSink>();
	}

	if (sink_) {
		ret = sink_->configure(*config_);
		if (ret < 0) {
			std::cout << "Failed to configure frame sink"
				  << std::endl;
			return ret;
		}

		sink_->requestProcessed.connect(this, &CameraSession::sinkRelease);
	}

	allocator_ = std::make_unique<FrameBufferAllocator>(camera_);

	return startCapture();
}

void CameraSession::stop()
{
	int ret = camera_->stop();
	if (ret)
		std::cout << "Failed to stop capture" << std::endl;

	if (sink_) {
		ret = sink_->stop();
		if (ret)
			std::cout << "Failed to stop frame sink" << std::endl;
	}

	sink_.reset();

	requests_.clear();

	allocator_.reset();
}

int CameraSession::startCapture()
{
	int ret;

	/* Identify the stream with the least number of buffers. */
	unsigned int nbuffers = UINT_MAX;
	for (StreamConfiguration &cfg : *config_) {
		ret = allocator_->allocate(cfg.stream());
		if (ret < 0) {
			std::cerr << "Can't allocate buffers" << std::endl;
			return -ENOMEM;
		}

		unsigned int allocated = allocator_->buffers(cfg.stream()).size();
		nbuffers = std::min(nbuffers, allocated);
	}

	/*
	 * TODO: make cam tool smarter to support still capture by for
	 * example pushing a button. For now run all streams all the time.
	 */

	for (unsigned int i = 0; i < nbuffers; i++) {
		std::unique_ptr<Request> request = camera_->createRequest();
		if (!request) {
			std::cerr << "Can't create request" << std::endl;
			return -ENOMEM;
		}

		for (StreamConfiguration &cfg : *config_) {
			Stream *stream = cfg.stream();
			const std::vector<std::unique_ptr<FrameBuffer>> &buffers =
				allocator_->buffers(stream);
			const std::unique_ptr<FrameBuffer> &buffer = buffers[i];

			ret = request->addBuffer(stream, buffer.get());
			if (ret < 0) {
				std::cerr << "Can't set buffer for request"
					  << std::endl;
				return ret;
			}

			if (sink_)
				sink_->mapBuffer(buffer.get());
		}

		requests_.push_back(std::move(request));
	}

	if (sink_) {
		ret = sink_->start();
		if (ret) {
			std::cout << "Failed to start frame sink" << std::endl;
			return ret;
		}
	}

	ret = camera_->start();
	if (ret) {
		std::cout << "Failed to start capture" << std::endl;
		if (sink_)
			sink_->stop();
		return ret;
	}

	for (std::unique_ptr<Request> &request : requests_) {
		ret = queueRequest(request.get());
		if (ret < 0) {
			std::cerr << "Can't queue request" << std::endl;
			camera_->stop();
			if (sink_)
				sink_->stop();
			return ret;
		}
	}

	if (captureLimit_)
		std::cout << "cam" << cameraIndex_
			  << ": Capture " << captureLimit_ << " frames"
			  << std::endl;
	else
		std::cout << "cam" << cameraIndex_
			  << ": Capture until user interrupts by SIGINT"
			  << std::endl;

	return 0;
}

int CameraSession::queueRequest(Request *request)
{
	if (captureLimit_ && queueCount_ >= captureLimit_)
		return 0;

	queueCount_++;

	return camera_->queueRequest(request);
}

void CameraSession::requestComplete(Request *request)
{
	if (request->status() == Request::RequestCancelled)
		return;

	/*
	 * Defer processing of the completed request to the event loop, to avoid
	 * blocking the camera manager thread.
	 */
	EventLoop::instance()->callLater([=]() { processRequest(request); });
}

void CameraSession::processRequest(Request *request)
{
	const Request::BufferMap &buffers = request->buffers();

	/*
	 * Compute the frame rate. The timestamp is arbitrarily retrieved from
	 * the first buffer, as all buffers should have matching timestamps.
	 */
	uint64_t ts = buffers.begin()->second->metadata().timestamp;
	double fps = ts - last_;
	fps = last_ != 0 && fps ? 1000000000.0 / fps : 0.0;
	last_ = ts;

	bool requeue = true;

	std::stringstream info;
	info << ts / 1000000000 << "."
	     << std::setw(6) << std::setfill('0') << ts / 1000 % 1000000
	     << " (" << std::fixed << std::setprecision(2) << fps << " fps)";

	for (auto it = buffers.begin(); it != buffers.end(); ++it) {
		const Stream *stream = it->first;
		FrameBuffer *buffer = it->second;

		const FrameMetadata &metadata = buffer->metadata();

		info << " " << streamName_[stream]
		     << " seq: " << std::setw(6) << std::setfill('0') << metadata.sequence
		     << " bytesused: ";

		unsigned int nplane = 0;
		for (const FrameMetadata::Plane &plane : metadata.planes) {
			info << plane.bytesused;
			if (++nplane < metadata.planes.size())
				info << "/";
		}
	}

	if (sink_) {
		if (!sink_->processRequest(request))
			requeue = false;
	}

	std::cout << info.str() << std::endl;

	if (printMetadata_) {
		const ControlList &requestMetadata = request->metadata();
		for (const auto &ctrl : requestMetadata) {
			const ControlId *id = controls::controls.at(ctrl.first);
			std::cout << "\t" << id->name() << " = "
				  << ctrl.second.toString() << std::endl;
		}
	}

	captureCount_++;
	if (captureLimit_ && captureCount_ >= captureLimit_) {
		captureDone.emit();
		return;
	}

	/*
	 * If the frame sink holds on the request, we'll requeue it later in the
	 * complete handler.
	 */
	if (!requeue)
		return;

	request->reuse(Request::ReuseBuffers);
	camera_->queueRequest(request);
}

void CameraSession::sinkRelease(Request *request)
{
	request->reuse(Request::ReuseBuffers);
	queueRequest(request);
}