From patchwork Tue Aug 13 09:40:20 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kieran Bingham X-Patchwork-Id: 1805 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0A69E6162D for ; Tue, 13 Aug 2019 11:40:27 +0200 (CEST) Received: from localhost.localdomain (cpc89242-aztw30-2-0-cust488.18-1.cable.virginm.net [86.31.129.233]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6AF4130F; Tue, 13 Aug 2019 11:40:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1565689226; bh=Ix8dnqJYhKVuHRIDeXvP0HFhRVUfJ4/thXTwmeu5bHI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=GcW4aQlgrDv3oqCgnPve+FyGaXRA7eFUjnfNofIYk/CMANJRW7pZijG+KPcIlH10B q9gGkfVXlVRgHKgcF1sT9AvCvdxNd46P/DVm2pCSYNpIeZzPW5wz9iQ8E93OdrxJU0 U30n09muaFo+TEseagxBxwI7ZU7N1/yqI9WSa42M= From: Kieran Bingham To: LibCamera Devel Date: Tue, 13 Aug 2019 10:40:20 +0100 Message-Id: <20190813094020.10277-7-kieran.bingham@ideasonboard.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190813094020.10277-1-kieran.bingham@ideasonboard.com> References: <20190813094020.10277-1-kieran.bingham@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v3 6/6] [PoC/RFC] libcamera: pipeline: Add RaspberryPi handler X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 13 Aug 2019 09:40:27 -0000 Utilise the CameraSensor class and construct a pipeline for a single sensor on the Unicam, routed through the V4L2 Codec ISP interface. Signed-off-by: Kieran Bingham --- src/libcamera/pipeline/meson.build | 1 + src/libcamera/pipeline/raspberrypi.cpp | 453 +++++++++++++++++++++++++ 2 files changed, 454 insertions(+) create mode 100644 src/libcamera/pipeline/raspberrypi.cpp diff --git a/src/libcamera/pipeline/meson.build b/src/libcamera/pipeline/meson.build index 0d466225a72e..7ed7b26f3033 100644 --- a/src/libcamera/pipeline/meson.build +++ b/src/libcamera/pipeline/meson.build @@ -1,4 +1,5 @@ libcamera_sources += files([ + 'raspberrypi.cpp', 'uvcvideo.cpp', 'vimc.cpp', ]) diff --git a/src/libcamera/pipeline/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi.cpp new file mode 100644 index 000000000000..d11b78df2e03 --- /dev/null +++ b/src/libcamera/pipeline/raspberrypi.cpp @@ -0,0 +1,453 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019, Google Inc. + * + * raspberrypi.cpp - Pipeline handler for Raspberry Pi devices + */ + +#include +#include +#include + +#include "camera_sensor.h" +#include "device_enumerator.h" +#include "log.h" +#include "media_device.h" +#include "pipeline_handler.h" +#include "utils.h" +#include "v4l2_controls.h" +#include "v4l2_videodevice.h" + +namespace libcamera { + +LOG_DEFINE_CATEGORY(RPI) + +class RPiCameraData : public CameraData +{ +public: + RPiCameraData(PipelineHandler *pipe) + : CameraData(pipe), sensor_(nullptr), unicam_(nullptr), + isp_(nullptr) + { + } + + ~RPiCameraData() + { + bayerBuffers_.destroyBuffers(); + delete sensor_; + delete unicam_; + delete isp_; + } + + void sensorReady(Buffer *buffer); + void ispOutputReady(Buffer *buffer); + void ispCaptureReady(Buffer *buffer); + + CameraSensor *sensor_; + V4L2VideoDevice *unicam_; + V4L2M2MDevice *isp_; + Stream stream_; + + BufferPool bayerBuffers_; + std::vector> rawBuffers_; +}; + +class RPiCameraConfiguration : public CameraConfiguration +{ +public: + RPiCameraConfiguration(); + + Status validate() override; +}; + +class PipelineHandlerRPi : public PipelineHandler +{ +public: + PipelineHandlerRPi(CameraManager *manager); + ~PipelineHandlerRPi(); + + CameraConfiguration * + generateConfiguration(Camera *camera, + const StreamRoles &roles) override; + int configure(Camera *camera, + CameraConfiguration *config) override; + + int allocateBuffers(Camera *camera, + const std::set &streams) override; + int freeBuffers(Camera *camera, + const std::set &streams) override; + + int start(Camera *camera) override; + void stop(Camera *camera) override; + + int queueRequest(Camera *camera, Request *request) override; + + bool match(DeviceEnumerator *enumerator) override; + +private: + RPiCameraData *cameraData(const Camera *camera) + { + return static_cast( + PipelineHandler::cameraData(camera)); + } + + std::shared_ptr unicam_; + std::shared_ptr codec_; +}; + +RPiCameraConfiguration::RPiCameraConfiguration() + : CameraConfiguration() +{ +} + +CameraConfiguration::Status RPiCameraConfiguration::validate() +{ + Status status = Valid; + + if (config_.empty()) + return Invalid; + + /* todo: Experiment with increased stream support through the ISP. */ + if (config_.size() > 1) { + config_.resize(1); + status = Adjusted; + } + + StreamConfiguration &cfg = config_[0]; + + /* todo: restrict to hardware capabilities. */ + + cfg.bufferCount = 4; + + return status; +} + +PipelineHandlerRPi::PipelineHandlerRPi(CameraManager *manager) + : PipelineHandler(manager), unicam_(nullptr), codec_(nullptr) +{ +} + +PipelineHandlerRPi::~PipelineHandlerRPi() +{ + if (unicam_) + unicam_->release(); + + if (codec_) + codec_->release(); +} + +CameraConfiguration * +PipelineHandlerRPi::generateConfiguration(Camera *camera, + const StreamRoles &roles) +{ + CameraConfiguration *config = new RPiCameraConfiguration(); + RPiCameraData *data = cameraData(camera); + + if (roles.empty()) + return config; + + StreamConfiguration cfg{}; + cfg.pixelFormat = V4L2_PIX_FMT_YUYV; + cfg.size = { 1920, 1080 }; + + LOG(RPI, Debug) << "Sensor Resolution is: " << data->sensor_->resolution().toString(); + + cfg.bufferCount = 4; + + config->addConfiguration(cfg); + + config->validate(); + + return config; +} + +int PipelineHandlerRPi::configure(Camera *camera, CameraConfiguration *config) +{ + RPiCameraData *data = cameraData(camera); + StreamConfiguration &cfg = config->at(0); + int ret; + + Size sensorSize = { 1920, 1080 }; + Size outputSize = { 1920, 1088 }; + + V4L2DeviceFormat format = {}; + format.size = sensorSize; + format.fourcc = V4L2_PIX_FMT_SRGGB10P; + + LOG(RPI, Debug) << "Setting format to " << format.toString(); + + ret = data->unicam_->setFormat(&format); + if (ret) + return ret; + + if (format.size != sensorSize || + format.fourcc != V4L2_PIX_FMT_SRGGB10P) { + LOG(RPI, Error) + << "Failed to set format on Video device: " + << format.toString(); + return -EINVAL; + } + + format.size = outputSize; + + ret = data->isp_->output()->setFormat(&format); + + if (format.size != outputSize || + format.fourcc != V4L2_PIX_FMT_SRGGB10P) { + LOG(RPI, Error) << "Failed to set format on ISP output device: " + << format.toString(); + return -EINVAL; + } + + /* Configure the ISP to generate the requested size and format. */ + format.size = cfg.size; + format.fourcc = cfg.pixelFormat; + + ret = data->isp_->capture()->setFormat(&format); + + if (format.size != cfg.size || + format.fourcc != cfg.pixelFormat) { + LOG(RPI, Error) + << "Failed to set format on ISP capture device: " + << format.toString(); + return -EINVAL; + } + + cfg.setStream(&data->stream_); + + return 0; +} + +int PipelineHandlerRPi::allocateBuffers(Camera *camera, + const std::set &streams) +{ + RPiCameraData *data = cameraData(camera); + Stream *stream = *streams.begin(); + const StreamConfiguration &cfg = stream->configuration(); + int ret; + + /* + * Buffers are allocated on the camera, and the capture pad of the ISP: + * unicam -> isp.output -> isp.capture -> Application + */ + + /* Create a new intermediate buffer pool. */ + data->bayerBuffers_.createBuffers(cfg.bufferCount); + + /* Tie the unicam video buffers to the intermediate pool. */ + ret = data->unicam_->exportBuffers(&data->bayerBuffers_); + if (ret) + return ret; + + ret = data->isp_->output()->importBuffers(&data->bayerBuffers_); + if (ret) + return ret; + + /* Tie the stream buffers to the capture device of the ISP. */ + if (stream->memoryType() == InternalMemory) + ret = data->isp_->capture()->exportBuffers(&stream->bufferPool()); + else + ret = data->isp_->capture()->importBuffers(&stream->bufferPool()); + + return ret; +} + +int PipelineHandlerRPi::freeBuffers(Camera *camera, + const std::set &streams) +{ + RPiCameraData *data = cameraData(camera); + int ret; + + ret = data->unicam_->releaseBuffers(); + if (ret) + return ret; + + ret = data->isp_->output()->releaseBuffers(); + if (ret) + return ret; + + ret = data->isp_->capture()->releaseBuffers(); + if (ret) + return ret; + + data->bayerBuffers_.destroyBuffers(); + + return ret; +} + +int PipelineHandlerRPi::start(Camera *camera) +{ + RPiCameraData *data = cameraData(camera); + int ret; + + data->rawBuffers_ = data->unicam_->queueAllBuffers(); + if (data->rawBuffers_.empty()) { + LOG(RPI, Debug) << "Failed to queue unicam buffers"; + return -EINVAL; + } + + LOG(RPI, Warning) << "Using hard-coded exposure/gain defaults"; + + V4L2ControlList controls; + controls.add(V4L2_CID_EXPOSURE, 1700); + controls.add(V4L2_CID_ANALOGUE_GAIN, 180); + ret = data->unicam_->setControls(&controls); + if (ret) { + LOG(RPI, Error) << "Failed to set controls"; + return ret; + } + + ret = data->isp_->output()->streamOn(); + if (ret) + return ret; + + ret = data->isp_->capture()->streamOn(); + if (ret) + goto output_streamoff; + + ret = data->unicam_->streamOn(); + if (ret) + goto capture_streamoff; + + return 0; + +capture_streamoff: + data->isp_->capture()->streamOff(); +output_streamoff: + data->isp_->output()->streamOff(); + + return ret; +} + +void PipelineHandlerRPi::stop(Camera *camera) +{ + RPiCameraData *data = cameraData(camera); + + data->isp_->capture()->streamOff(); + data->isp_->output()->streamOff(); + data->unicam_->streamOff(); + + data->rawBuffers_.clear(); +} + +int PipelineHandlerRPi::queueRequest(Camera *camera, Request *request) +{ + RPiCameraData *data = cameraData(camera); + Stream *stream = &data->stream_; + + Buffer *buffer = request->findBuffer(stream); + if (!buffer) { + LOG(RPI, Error) + << "Attempt to queue request with invalid stream"; + return -ENOENT; + } + + int ret = data->isp_->capture()->queueBuffer(buffer); + if (ret < 0) + return ret; + + PipelineHandler::queueRequest(camera, request); + + return 0; +} + +bool PipelineHandlerRPi::match(DeviceEnumerator *enumerator) +{ + DeviceMatch unicam("unicam"); + DeviceMatch codec("bcm2835-codec"); + + /* The video node is also named unicam. */ + unicam.add("unicam"); + + /* We explicitly need the ISP device from the MMAL codec driver. */ + codec.add("bcm2835-codec-isp-source"); + + unicam_ = enumerator->search(unicam); + if (!unicam_) + return false; + + codec_ = enumerator->search(codec); + if (!codec_) + return false; + + unicam_->acquire(); + codec_->acquire(); + + std::unique_ptr data = utils::make_unique(this); + + /* Locate and open the unicam video node. */ + data->unicam_ = new V4L2VideoDevice(unicam_->getEntityByName("unicam")); + if (data->unicam_->open()) + return false; + + /* Locate the ISP M2M node */ + MediaEntity *isp = codec_->getEntityByName("bcm2835-codec-isp-source"); + if (!isp) { + LOG(RPI, Error) << "Could not identify the ISP"; + return false; + } + + data->isp_ = new V4L2M2MDevice(isp->deviceNode()); + if (data->isp_->open()) { + LOG(RPI, Error) << "Could not open the ISP device"; + return false; + } + + data->unicam_->bufferReady.connect(data.get(), &RPiCameraData::sensorReady); + data->isp_->output()->bufferReady.connect(data.get(), &RPiCameraData::ispOutputReady); + data->isp_->capture()->bufferReady.connect(data.get(), &RPiCameraData::ispCaptureReady); + + /* Identify the sensor */ + for (MediaEntity *entity : unicam_->entities()) { + if (entity->function() == MEDIA_ENT_F_CAM_SENSOR) { + data->sensor_ = new CameraSensor(entity); + break; + } + } + + if (!data->sensor_) + return false; + + if (data->sensor_->init()) + return false; + + /* Create and register the camera. */ + std::set streams{ &data->stream_ }; + std::shared_ptr camera = + Camera::create(this, data->sensor_->entity()->name(), streams); + registerCamera(std::move(camera), std::move(data)); + + return true; +} + +void RPiCameraData::sensorReady(Buffer *buffer) +{ + /* \todo Handle buffer failures when state is set to BufferError. */ + if (buffer->status() == Buffer::BufferCancelled) + return; + + /* Deliver the frame from the sensor to the ISP. */ + isp_->output()->queueBuffer(buffer); +} + +void RPiCameraData::ispOutputReady(Buffer *buffer) +{ + /* \todo Handle buffer failures when state is set to BufferError. */ + if (buffer->status() == Buffer::BufferCancelled) + return; + + /* Return a completed buffer from the ISP back to the sensor. */ + unicam_->queueBuffer(buffer); +} + +void RPiCameraData::ispCaptureReady(Buffer *buffer) +{ + Request *request = buffer->request(); + + pipe_->completeBuffer(camera_, request, buffer); + pipe_->completeRequest(camera_, request); +} + +REGISTER_PIPELINE_HANDLER(PipelineHandlerRPi); + +} /* namespace libcamera */