summaryrefslogtreecommitdiff
path: root/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libcamera/pipeline/uvcvideo/uvcvideo.cpp')
-rw-r--r--src/libcamera/pipeline/uvcvideo/uvcvideo.cpp394
1 files changed, 394 insertions, 0 deletions
diff --git a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
new file mode 100644
index 00000000..ffbddf27
--- /dev/null
+++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
@@ -0,0 +1,394 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * uvcvideo.cpp - Pipeline handler for uvcvideo devices
+ */
+
+#include <algorithm>
+#include <iomanip>
+#include <sys/sysmacros.h>
+#include <tuple>
+
+#include <libcamera/camera.h>
+#include <libcamera/control_ids.h>
+#include <libcamera/controls.h>
+#include <libcamera/request.h>
+#include <libcamera/stream.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(UVC)
+
+class UVCCameraData : public CameraData
+{
+public:
+ UVCCameraData(PipelineHandler *pipe)
+ : CameraData(pipe), video_(nullptr)
+ {
+ }
+
+ ~UVCCameraData()
+ {
+ delete video_;
+ }
+
+ int init(MediaEntity *entity);
+ void bufferReady(FrameBuffer *buffer);
+
+ V4L2VideoDevice *video_;
+ Stream stream_;
+};
+
+class UVCCameraConfiguration : public CameraConfiguration
+{
+public:
+ UVCCameraConfiguration();
+
+ Status validate() override;
+};
+
+class PipelineHandlerUVC : public PipelineHandler
+{
+public:
+ PipelineHandlerUVC(CameraManager *manager);
+
+ CameraConfiguration *generateConfiguration(Camera *camera,
+ const StreamRoles &roles) override;
+ int configure(Camera *camera, CameraConfiguration *config) override;
+
+ int exportFrameBuffers(Camera *camera, Stream *stream,
+ std::vector<std::unique_ptr<FrameBuffer>> *buffers) override;
+
+ int start(Camera *camera) override;
+ void stop(Camera *camera) override;
+
+ int queueRequestDevice(Camera *camera, Request *request) override;
+
+ bool match(DeviceEnumerator *enumerator) override;
+
+private:
+ int processControls(UVCCameraData *data, Request *request);
+
+ UVCCameraData *cameraData(const Camera *camera)
+ {
+ return static_cast<UVCCameraData *>(
+ PipelineHandler::cameraData(camera));
+ }
+};
+
+UVCCameraConfiguration::UVCCameraConfiguration()
+ : CameraConfiguration()
+{
+}
+
+CameraConfiguration::Status UVCCameraConfiguration::validate()
+{
+ Status status = Valid;
+
+ if (config_.empty())
+ return Invalid;
+
+ /* Cap the number of entries to the available streams. */
+ if (config_.size() > 1) {
+ config_.resize(1);
+ status = Adjusted;
+ }
+
+ StreamConfiguration &cfg = config_[0];
+ const StreamFormats &formats = cfg.formats();
+ const PixelFormat pixelFormat = cfg.pixelFormat;
+ const Size size = cfg.size;
+
+ const std::vector<PixelFormat> pixelFormats = formats.pixelformats();
+ auto iter = std::find(pixelFormats.begin(), pixelFormats.end(), pixelFormat);
+ if (iter == pixelFormats.end()) {
+ cfg.pixelFormat = pixelFormats.front();
+ LOG(UVC, Debug)
+ << "Adjusting pixel format from "
+ << pixelFormat.toString() << " to "
+ << cfg.pixelFormat.toString();
+ status = Adjusted;
+ }
+
+ const std::vector<Size> &formatSizes = formats.sizes(cfg.pixelFormat);
+ cfg.size = formatSizes.front();
+ for (const Size &formatsSize : formatSizes) {
+ if (formatsSize > size)
+ break;
+
+ cfg.size = formatsSize;
+ }
+
+ if (cfg.size != size) {
+ LOG(UVC, Debug)
+ << "Adjusting size from " << size.toString()
+ << " to " << cfg.size.toString();
+ status = Adjusted;
+ }
+
+ cfg.bufferCount = 4;
+
+ return status;
+}
+
+PipelineHandlerUVC::PipelineHandlerUVC(CameraManager *manager)
+ : PipelineHandler(manager)
+{
+}
+
+CameraConfiguration *PipelineHandlerUVC::generateConfiguration(Camera *camera,
+ const StreamRoles &roles)
+{
+ UVCCameraData *data = cameraData(camera);
+ CameraConfiguration *config = new UVCCameraConfiguration();
+
+ if (roles.empty())
+ return config;
+
+ std::map<V4L2PixelFormat, std::vector<SizeRange>> v4l2Formats =
+ data->video_->formats();
+ std::map<PixelFormat, std::vector<SizeRange>> deviceFormats;
+ std::transform(v4l2Formats.begin(), v4l2Formats.end(),
+ std::inserter(deviceFormats, deviceFormats.begin()),
+ [&](const decltype(v4l2Formats)::value_type &format) {
+ return decltype(deviceFormats)::value_type{
+ data->video_->toPixelFormat(format.first),
+ format.second
+ };
+ });
+
+ StreamFormats formats(deviceFormats);
+ StreamConfiguration cfg(formats);
+
+ cfg.pixelFormat = formats.pixelformats().front();
+ cfg.size = formats.sizes(cfg.pixelFormat).back();
+ cfg.bufferCount = 4;
+
+ config->addConfiguration(cfg);
+
+ config->validate();
+
+ return config;
+}
+
+int PipelineHandlerUVC::configure(Camera *camera, CameraConfiguration *config)
+{
+ UVCCameraData *data = cameraData(camera);
+ StreamConfiguration &cfg = config->at(0);
+ int ret;
+
+ V4L2DeviceFormat format = {};
+ format.fourcc = data->video_->toV4L2PixelFormat(cfg.pixelFormat);
+ format.size = cfg.size;
+
+ ret = data->video_->setFormat(&format);
+ if (ret)
+ return ret;
+
+ if (format.size != cfg.size ||
+ format.fourcc != data->video_->toV4L2PixelFormat(cfg.pixelFormat))
+ return -EINVAL;
+
+ cfg.setStream(&data->stream_);
+
+ return 0;
+}
+
+int PipelineHandlerUVC::exportFrameBuffers(Camera *camera, Stream *stream,
+ std::vector<std::unique_ptr<FrameBuffer>> *buffers)
+{
+ UVCCameraData *data = cameraData(camera);
+ unsigned int count = stream->configuration().bufferCount;
+
+ return data->video_->exportBuffers(count, buffers);
+}
+
+int PipelineHandlerUVC::start(Camera *camera)
+{
+ UVCCameraData *data = cameraData(camera);
+ unsigned int count = data->stream_.configuration().bufferCount;
+
+ int ret = data->video_->importBuffers(count);
+ if (ret < 0)
+ return ret;
+
+ ret = data->video_->streamOn();
+ if (ret < 0) {
+ data->video_->releaseBuffers();
+ return ret;
+ }
+
+ return 0;
+}
+
+void PipelineHandlerUVC::stop(Camera *camera)
+{
+ UVCCameraData *data = cameraData(camera);
+ data->video_->streamOff();
+ data->video_->releaseBuffers();
+}
+
+int PipelineHandlerUVC::processControls(UVCCameraData *data, Request *request)
+{
+ ControlList controls(data->video_->controls());
+
+ for (auto it : request->controls()) {
+ unsigned int id = it.first;
+ ControlValue &value = it.second;
+
+ if (id == controls::Brightness) {
+ controls.set(V4L2_CID_BRIGHTNESS, value);
+ } else if (id == controls::Contrast) {
+ controls.set(V4L2_CID_CONTRAST, value);
+ } else if (id == controls::Saturation) {
+ controls.set(V4L2_CID_SATURATION, value);
+ } else if (id == controls::ManualExposure) {
+ controls.set(V4L2_CID_EXPOSURE_AUTO, static_cast<int32_t>(1));
+ controls.set(V4L2_CID_EXPOSURE_ABSOLUTE, value);
+ } else if (id == controls::ManualGain) {
+ controls.set(V4L2_CID_GAIN, value);
+ }
+ }
+
+ for (const auto &ctrl : controls)
+ LOG(UVC, Debug)
+ << "Setting control " << utils::hex(ctrl.first)
+ << " to " << ctrl.second.toString();
+
+ int ret = data->video_->setControls(&controls);
+ if (ret) {
+ LOG(UVC, Error) << "Failed to set controls: " << ret;
+ return ret < 0 ? ret : -EINVAL;
+ }
+
+ return ret;
+}
+
+int PipelineHandlerUVC::queueRequestDevice(Camera *camera, Request *request)
+{
+ UVCCameraData *data = cameraData(camera);
+ FrameBuffer *buffer = request->findBuffer(&data->stream_);
+ if (!buffer) {
+ LOG(UVC, Error)
+ << "Attempt to queue request with invalid stream";
+
+ return -ENOENT;
+ }
+
+ int ret = processControls(data, request);
+ if (ret < 0)
+ return ret;
+
+ ret = data->video_->queueBuffer(buffer);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+bool PipelineHandlerUVC::match(DeviceEnumerator *enumerator)
+{
+ MediaDevice *media;
+ DeviceMatch dm("uvcvideo");
+
+ media = acquireMediaDevice(enumerator, dm);
+ if (!media)
+ return false;
+
+ std::unique_ptr<UVCCameraData> data = std::make_unique<UVCCameraData>(this);
+
+ /* Locate and initialise the camera data with the default video node. */
+ const std::vector<MediaEntity *> &entities = media->entities();
+ auto entity = std::find_if(entities.begin(), entities.end(),
+ [](MediaEntity *entity) {
+ return entity->flags() & MEDIA_ENT_FL_DEFAULT;
+ });
+ if (entity == entities.end()) {
+ LOG(UVC, Error) << "Could not find a default video device";
+ return false;
+ }
+
+ if (data->init(*entity))
+ return false;
+
+ dev_t devnum = makedev((*entity)->deviceMajor(), (*entity)->deviceMinor());
+
+ /* Create and register the camera. */
+ std::set<Stream *> streams{ &data->stream_ };
+ std::shared_ptr<Camera> camera = Camera::create(this, media->model(), streams);
+ registerCamera(std::move(camera), std::move(data), devnum);
+
+ /* Enable hot-unplug notifications. */
+ hotplugMediaDevice(media);
+
+ return true;
+}
+
+int UVCCameraData::init(MediaEntity *entity)
+{
+ int ret;
+
+ /* Create and open the video device. */
+ video_ = new V4L2VideoDevice(entity);
+ ret = video_->open();
+ if (ret)
+ return ret;
+
+ video_->bufferReady.connect(this, &UVCCameraData::bufferReady);
+
+ /* Initialise the supported controls. */
+ const ControlInfoMap &controls = video_->controls();
+ ControlInfoMap::Map ctrls;
+
+ for (const auto &ctrl : controls) {
+ const ControlInfo &info = ctrl.second;
+ const ControlId *id;
+
+ switch (ctrl.first->id()) {
+ case V4L2_CID_BRIGHTNESS:
+ id = &controls::Brightness;
+ break;
+ case V4L2_CID_CONTRAST:
+ id = &controls::Contrast;
+ break;
+ case V4L2_CID_SATURATION:
+ id = &controls::Saturation;
+ break;
+ case V4L2_CID_EXPOSURE_ABSOLUTE:
+ id = &controls::ManualExposure;
+ break;
+ case V4L2_CID_GAIN:
+ id = &controls::ManualGain;
+ break;
+ default:
+ continue;
+ }
+
+ ctrls.emplace(id, info);
+ }
+
+ controlInfo_ = std::move(ctrls);
+
+ return 0;
+}
+
+void UVCCameraData::bufferReady(FrameBuffer *buffer)
+{
+ Request *request = buffer->request();
+
+ pipe_->completeBuffer(camera_, request, buffer);
+ pipe_->completeRequest(camera_, request);
+}
+
+REGISTER_PIPELINE_HANDLER(PipelineHandlerUVC);
+
+} /* namespace libcamera */