| /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
| /* |
| * Copyright (C) 2019, Google Inc. |
| * |
| * v4l2_camera.cpp - V4L2 compatibility camera |
| */ |
| |
| #include "v4l2_camera.h" |
| |
| #include <errno.h> |
| #include <unistd.h> |
| |
| #include <libcamera/base/log.h> |
| |
| using namespace libcamera; |
| |
| LOG_DECLARE_CATEGORY(V4L2Compat) |
| |
| V4L2Camera::V4L2Camera(std::shared_ptr<Camera> camera) |
| : camera_(camera), isRunning_(false), bufferAllocator_(nullptr), |
| efd_(-1), bufferAvailableCount_(0) |
| { |
| camera_->requestCompleted.connect(this, &V4L2Camera::requestComplete); |
| } |
| |
| V4L2Camera::~V4L2Camera() |
| { |
| close(); |
| } |
| |
| int V4L2Camera::open(StreamConfiguration *streamConfig) |
| { |
| if (camera_->acquire() < 0) { |
| LOG(V4L2Compat, Error) << "Failed to acquire camera"; |
| return -EINVAL; |
| } |
| |
| config_ = camera_->generateConfiguration({ StreamRole::Viewfinder }); |
| if (!config_) { |
| camera_->release(); |
| return -EINVAL; |
| } |
| |
| bufferAllocator_ = new FrameBufferAllocator(camera_); |
| |
| *streamConfig = config_->at(0); |
| return 0; |
| } |
| |
| void V4L2Camera::close() |
| { |
| requestPool_.clear(); |
| |
| delete bufferAllocator_; |
| bufferAllocator_ = nullptr; |
| |
| camera_->release(); |
| } |
| |
| void V4L2Camera::bind(int efd) |
| { |
| efd_ = efd; |
| } |
| |
| void V4L2Camera::unbind() |
| { |
| efd_ = -1; |
| } |
| |
| std::vector<V4L2Camera::Buffer> V4L2Camera::completedBuffers() |
| { |
| std::vector<Buffer> v; |
| |
| bufferLock_.lock(); |
| for (std::unique_ptr<Buffer> &metadata : completedBuffers_) |
| v.push_back(*metadata.get()); |
| completedBuffers_.clear(); |
| bufferLock_.unlock(); |
| |
| return v; |
| } |
| |
| void V4L2Camera::requestComplete(Request *request) |
| { |
| if (request->status() == Request::RequestCancelled) |
| return; |
| |
| /* We only have one stream at the moment. */ |
| bufferLock_.lock(); |
| FrameBuffer *buffer = request->buffers().begin()->second; |
| std::unique_ptr<Buffer> metadata = |
| std::make_unique<Buffer>(request->cookie(), buffer->metadata()); |
| completedBuffers_.push_back(std::move(metadata)); |
| bufferLock_.unlock(); |
| |
| uint64_t data = 1; |
| int ret = ::write(efd_, &data, sizeof(data)); |
| if (ret != sizeof(data)) |
| LOG(V4L2Compat, Error) << "Failed to signal eventfd POLLIN"; |
| |
| request->reuse(); |
| { |
| MutexLocker locker(bufferMutex_); |
| bufferAvailableCount_++; |
| } |
| bufferCV_.notify_all(); |
| } |
| |
| int V4L2Camera::configure(StreamConfiguration *streamConfigOut, |
| const Size &size, const PixelFormat &pixelformat, |
| unsigned int bufferCount) |
| { |
| StreamConfiguration &streamConfig = config_->at(0); |
| streamConfig.size.width = size.width; |
| streamConfig.size.height = size.height; |
| streamConfig.pixelFormat = pixelformat; |
| streamConfig.bufferCount = bufferCount; |
| /* \todo memoryType (interval vs external) */ |
| |
| CameraConfiguration::Status validation = config_->validate(); |
| if (validation == CameraConfiguration::Invalid) { |
| LOG(V4L2Compat, Debug) << "Configuration invalid"; |
| return -EINVAL; |
| } |
| if (validation == CameraConfiguration::Adjusted) |
| LOG(V4L2Compat, Debug) << "Configuration adjusted"; |
| |
| LOG(V4L2Compat, Debug) << "Validated configuration is: " |
| << streamConfig.toString(); |
| |
| int ret = camera_->configure(config_.get()); |
| if (ret < 0) |
| return ret; |
| |
| *streamConfigOut = config_->at(0); |
| |
| return 0; |
| } |
| |
| int V4L2Camera::validateConfiguration(const PixelFormat &pixelFormat, |
| const Size &size, |
| StreamConfiguration *streamConfigOut) |
| { |
| std::unique_ptr<CameraConfiguration> config = |
| camera_->generateConfiguration({ StreamRole::Viewfinder }); |
| StreamConfiguration &cfg = config->at(0); |
| cfg.size = size; |
| cfg.pixelFormat = pixelFormat; |
| cfg.bufferCount = 1; |
| |
| CameraConfiguration::Status validation = config->validate(); |
| if (validation == CameraConfiguration::Invalid) |
| return -EINVAL; |
| |
| *streamConfigOut = cfg; |
| |
| return 0; |
| } |
| |
| int V4L2Camera::allocBuffers(unsigned int count) |
| { |
| Stream *stream = config_->at(0).stream(); |
| |
| int ret = bufferAllocator_->allocate(stream); |
| if (ret < 0) |
| return ret; |
| |
| for (unsigned int i = 0; i < count; i++) { |
| std::unique_ptr<Request> request = camera_->createRequest(i); |
| if (!request) { |
| requestPool_.clear(); |
| return -ENOMEM; |
| } |
| requestPool_.push_back(std::move(request)); |
| } |
| |
| return ret; |
| } |
| |
| void V4L2Camera::freeBuffers() |
| { |
| pendingRequests_.clear(); |
| requestPool_.clear(); |
| |
| Stream *stream = config_->at(0).stream(); |
| bufferAllocator_->free(stream); |
| } |
| |
| int V4L2Camera::getBufferFd(unsigned int index) |
| { |
| Stream *stream = config_->at(0).stream(); |
| const std::vector<std::unique_ptr<FrameBuffer>> &buffers = |
| bufferAllocator_->buffers(stream); |
| |
| if (buffers.size() <= index) |
| return -1; |
| |
| return buffers[index]->planes()[0].fd.get(); |
| } |
| |
| int V4L2Camera::streamOn() |
| { |
| if (isRunning_) |
| return 0; |
| |
| int ret = camera_->start(); |
| if (ret < 0) |
| return ret == -EACCES ? -EBUSY : ret; |
| |
| isRunning_ = true; |
| |
| for (Request *req : pendingRequests_) { |
| /* \todo What should we do if this returns -EINVAL? */ |
| ret = camera_->queueRequest(req); |
| if (ret < 0) |
| return ret == -EACCES ? -EBUSY : ret; |
| } |
| |
| pendingRequests_.clear(); |
| |
| return 0; |
| } |
| |
| int V4L2Camera::streamOff() |
| { |
| if (!isRunning_) { |
| for (std::unique_ptr<Request> &req : requestPool_) |
| req->reuse(); |
| |
| return 0; |
| } |
| |
| pendingRequests_.clear(); |
| |
| int ret = camera_->stop(); |
| if (ret < 0) |
| return ret == -EACCES ? -EBUSY : ret; |
| |
| { |
| MutexLocker locker(bufferMutex_); |
| isRunning_ = false; |
| } |
| bufferCV_.notify_all(); |
| |
| return 0; |
| } |
| |
| int V4L2Camera::qbuf(unsigned int index) |
| { |
| if (index >= requestPool_.size()) { |
| LOG(V4L2Compat, Error) << "Invalid index"; |
| return -EINVAL; |
| } |
| Request *request = requestPool_[index].get(); |
| |
| Stream *stream = config_->at(0).stream(); |
| FrameBuffer *buffer = bufferAllocator_->buffers(stream)[index].get(); |
| int ret = request->addBuffer(stream, buffer); |
| if (ret < 0) { |
| LOG(V4L2Compat, Error) << "Can't set buffer for request"; |
| return -ENOMEM; |
| } |
| |
| if (!isRunning_) { |
| pendingRequests_.push_back(request); |
| return 0; |
| } |
| |
| ret = camera_->queueRequest(request); |
| if (ret < 0) { |
| LOG(V4L2Compat, Error) << "Can't queue request"; |
| return ret == -EACCES ? -EBUSY : ret; |
| } |
| |
| return 0; |
| } |
| |
| void V4L2Camera::waitForBufferAvailable() |
| { |
| MutexLocker locker(bufferMutex_); |
| bufferCV_.wait(locker, [&] { |
| return bufferAvailableCount_ >= 1 || !isRunning_; |
| }); |
| if (isRunning_) |
| bufferAvailableCount_--; |
| } |
| |
| bool V4L2Camera::isBufferAvailable() |
| { |
| MutexLocker locker(bufferMutex_); |
| if (bufferAvailableCount_ < 1) |
| return false; |
| |
| bufferAvailableCount_--; |
| return true; |
| } |
| |
| bool V4L2Camera::isRunning() |
| { |
| return isRunning_; |
| } |