Path: blob/master/modules/camera/camera_feed_linux.cpp
10277 views
/**************************************************************************/1/* camera_feed_linux.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "camera_feed_linux.h"3132#include "servers/rendering_server.h"3334#include <fcntl.h>35#include <sys/ioctl.h>36#include <sys/mman.h>37#include <unistd.h>3839void CameraFeedLinux::update_buffer_thread_func(void *p_func) {40if (p_func) {41CameraFeedLinux *camera_feed_linux = (CameraFeedLinux *)p_func;42camera_feed_linux->_update_buffer();43}44}4546void CameraFeedLinux::_update_buffer() {47while (!exit_flag.is_set()) {48_read_frame();49usleep(10000);50}51}5253void CameraFeedLinux::_query_device(const String &p_device_name) {54file_descriptor = open(p_device_name.ascii().get_data(), O_RDWR | O_NONBLOCK, 0);55ERR_FAIL_COND_MSG(file_descriptor == -1, vformat("Cannot open file descriptor for %s. Error: %d.", p_device_name, errno));5657struct v4l2_capability capability;58if (ioctl(file_descriptor, VIDIOC_QUERYCAP, &capability) == -1) {59ERR_FAIL_MSG(vformat("Cannot query device. Error: %d.", errno));60}61name = String((char *)capability.card);6263for (int index = 0;; index++) {64struct v4l2_fmtdesc fmtdesc;65memset(&fmtdesc, 0, sizeof(fmtdesc));66fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;67fmtdesc.index = index;6869if (ioctl(file_descriptor, VIDIOC_ENUM_FMT, &fmtdesc) == -1) {70break;71}7273for (int res_index = 0;; res_index++) {74struct v4l2_frmsizeenum frmsizeenum;75memset(&frmsizeenum, 0, sizeof(frmsizeenum));76frmsizeenum.pixel_format = fmtdesc.pixelformat;77frmsizeenum.index = res_index;7879if (ioctl(file_descriptor, VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) == -1) {80break;81}8283for (int framerate_index = 0;; framerate_index++) {84struct v4l2_frmivalenum frmivalenum;85memset(&frmivalenum, 0, sizeof(frmivalenum));86frmivalenum.pixel_format = fmtdesc.pixelformat;87frmivalenum.width = frmsizeenum.discrete.width;88frmivalenum.height = frmsizeenum.discrete.height;89frmivalenum.index = framerate_index;9091if (ioctl(file_descriptor, VIDIOC_ENUM_FRAMEINTERVALS, &frmivalenum) == -1) {92if (framerate_index == 0) {93_add_format(fmtdesc, frmsizeenum.discrete, -1, 1);94}95break;96}9798_add_format(fmtdesc, frmsizeenum.discrete, frmivalenum.discrete.numerator, frmivalenum.discrete.denominator);99}100}101}102103close(file_descriptor);104}105106void CameraFeedLinux::_add_format(v4l2_fmtdesc p_description, v4l2_frmsize_discrete p_size, int p_frame_numerator, int p_frame_denominator) {107FeedFormat feed_format;108feed_format.width = p_size.width;109feed_format.height = p_size.height;110feed_format.format = String((char *)p_description.description);111feed_format.frame_numerator = p_frame_numerator;112feed_format.frame_denominator = p_frame_denominator;113feed_format.pixel_format = p_description.pixelformat;114print_verbose(vformat("%s %dx%d@%d/%dfps", (char *)p_description.description, p_size.width, p_size.height, p_frame_denominator, p_frame_numerator));115formats.push_back(feed_format);116}117118bool CameraFeedLinux::_request_buffers() {119struct v4l2_requestbuffers requestbuffers;120121memset(&requestbuffers, 0, sizeof(requestbuffers));122requestbuffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;123requestbuffers.memory = V4L2_MEMORY_MMAP;124requestbuffers.count = 4;125126if (ioctl(file_descriptor, VIDIOC_REQBUFS, &requestbuffers) == -1) {127ERR_FAIL_V_MSG(false, vformat("ioctl(VIDIOC_REQBUFS) error: %d.", errno));128}129130ERR_FAIL_COND_V_MSG(requestbuffers.count < 2, false, "Not enough buffers granted.");131132buffer_count = requestbuffers.count;133buffers = new StreamingBuffer[buffer_count];134135for (unsigned int i = 0; i < buffer_count; i++) {136struct v4l2_buffer buffer;137138memset(&buffer, 0, sizeof(buffer));139buffer.type = requestbuffers.type;140buffer.memory = V4L2_MEMORY_MMAP;141buffer.index = i;142143if (ioctl(file_descriptor, VIDIOC_QUERYBUF, &buffer) == -1) {144delete[] buffers;145ERR_FAIL_V_MSG(false, vformat("ioctl(VIDIOC_QUERYBUF) error: %d.", errno));146}147148buffers[i].length = buffer.length;149buffers[i].start = mmap(nullptr, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, file_descriptor, buffer.m.offset);150151if (buffers[i].start == MAP_FAILED) {152for (unsigned int b = 0; b < i; b++) {153_unmap_buffers(i);154}155delete[] buffers;156ERR_FAIL_V_MSG(false, "Mapping buffers failed.");157}158}159160return true;161}162163bool CameraFeedLinux::_start_capturing() {164for (unsigned int i = 0; i < buffer_count; i++) {165struct v4l2_buffer buffer;166167memset(&buffer, 0, sizeof(buffer));168buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;169buffer.memory = V4L2_MEMORY_MMAP;170buffer.index = i;171172if (ioctl(file_descriptor, VIDIOC_QBUF, &buffer) == -1) {173ERR_FAIL_V_MSG(false, vformat("ioctl(VIDIOC_QBUF) error: %d.", errno));174}175}176177enum v4l2_buf_type type;178type = V4L2_BUF_TYPE_VIDEO_CAPTURE;179180if (ioctl(file_descriptor, VIDIOC_STREAMON, &type) == -1) {181ERR_FAIL_V_MSG(false, vformat("ioctl(VIDIOC_STREAMON) error: %d.", errno));182}183184return true;185}186187void CameraFeedLinux::_read_frame() {188struct v4l2_buffer buffer;189memset(&buffer, 0, sizeof(buffer));190buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;191buffer.memory = V4L2_MEMORY_MMAP;192193if (ioctl(file_descriptor, VIDIOC_DQBUF, &buffer) == -1) {194if (errno != EAGAIN) {195print_error(vformat("ioctl(VIDIOC_DQBUF) error: %d.", errno));196exit_flag.set();197}198return;199}200201buffer_decoder->decode(buffers[buffer.index]);202203if (ioctl(file_descriptor, VIDIOC_QBUF, &buffer) == -1) {204print_error(vformat("ioctl(VIDIOC_QBUF) error: %d.", errno));205}206}207208void CameraFeedLinux::_stop_capturing() {209enum v4l2_buf_type type;210type = V4L2_BUF_TYPE_VIDEO_CAPTURE;211212if (ioctl(file_descriptor, VIDIOC_STREAMOFF, &type) == -1) {213print_error(vformat("ioctl(VIDIOC_STREAMOFF) error: %d.", errno));214}215}216217void CameraFeedLinux::_unmap_buffers(unsigned int p_count) {218for (unsigned int i = 0; i < p_count; i++) {219munmap(buffers[i].start, buffers[i].length);220}221}222223void CameraFeedLinux::_start_thread() {224exit_flag.clear();225thread = memnew(Thread);226thread->start(CameraFeedLinux::update_buffer_thread_func, this);227}228229String CameraFeedLinux::get_device_name() const {230return device_name;231}232233bool CameraFeedLinux::activate_feed() {234ERR_FAIL_COND_V_MSG(selected_format == -1, false, "CameraFeed format needs to be set before activating.");235file_descriptor = open(device_name.ascii().get_data(), O_RDWR | O_NONBLOCK, 0);236if (_request_buffers() && _start_capturing()) {237buffer_decoder = _create_buffer_decoder();238_start_thread();239return true;240}241ERR_FAIL_V_MSG(false, "Could not activate feed.");242}243244BufferDecoder *CameraFeedLinux::_create_buffer_decoder() {245switch (formats[selected_format].pixel_format) {246case V4L2_PIX_FMT_MJPEG:247case V4L2_PIX_FMT_JPEG:248return memnew(JpegBufferDecoder(this));249case V4L2_PIX_FMT_YUYV:250case V4L2_PIX_FMT_YYUV:251case V4L2_PIX_FMT_YVYU:252case V4L2_PIX_FMT_UYVY:253case V4L2_PIX_FMT_VYUY: {254String output = parameters["output"];255if (output == "separate") {256return memnew(SeparateYuyvBufferDecoder(this));257}258if (output == "grayscale") {259return memnew(YuyvToGrayscaleBufferDecoder(this));260}261if (output == "copy") {262return memnew(CopyBufferDecoder(this, false));263}264return memnew(YuyvToRgbBufferDecoder(this));265}266default:267return memnew(CopyBufferDecoder(this, true));268}269}270271void CameraFeedLinux::deactivate_feed() {272exit_flag.set();273thread->wait_to_finish();274memdelete(thread);275_stop_capturing();276_unmap_buffers(buffer_count);277delete[] buffers;278memdelete(buffer_decoder);279for (int i = 0; i < CameraServer::FEED_IMAGES; i++) {280RID placeholder = RenderingServer::get_singleton()->texture_2d_placeholder_create();281RenderingServer::get_singleton()->texture_replace(texture[i], placeholder);282}283base_width = 0;284base_height = 0;285close(file_descriptor);286287emit_signal(SNAME("format_changed"));288}289290Array CameraFeedLinux::get_formats() const {291Array result;292for (const FeedFormat &format : formats) {293Dictionary dictionary;294dictionary["width"] = format.width;295dictionary["height"] = format.height;296dictionary["format"] = format.format;297dictionary["frame_numerator"] = format.frame_numerator;298dictionary["frame_denominator"] = format.frame_denominator;299result.push_back(dictionary);300}301return result;302}303304CameraFeed::FeedFormat CameraFeedLinux::get_format() const {305FeedFormat feed_format = {};306return selected_format == -1 ? feed_format : formats[selected_format];307}308309bool CameraFeedLinux::set_format(int p_index, const Dictionary &p_parameters) {310ERR_FAIL_COND_V_MSG(active, false, "Feed is active.");311ERR_FAIL_INDEX_V_MSG(p_index, formats.size(), false, "Invalid format index.");312313FeedFormat feed_format = formats[p_index];314315file_descriptor = open(device_name.ascii().get_data(), O_RDWR | O_NONBLOCK, 0);316317struct v4l2_format format;318memset(&format, 0, sizeof(format));319format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;320format.fmt.pix.width = feed_format.width;321format.fmt.pix.height = feed_format.height;322format.fmt.pix.pixelformat = feed_format.pixel_format;323324if (ioctl(file_descriptor, VIDIOC_S_FMT, &format) == -1) {325close(file_descriptor);326ERR_FAIL_V_MSG(false, vformat("Cannot set format, error: %d.", errno));327}328329if (feed_format.frame_numerator > 0) {330struct v4l2_streamparm param;331memset(¶m, 0, sizeof(param));332333param.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;334param.parm.capture.capability = V4L2_CAP_TIMEPERFRAME;335param.parm.capture.timeperframe.numerator = feed_format.frame_numerator;336param.parm.capture.timeperframe.denominator = feed_format.frame_denominator;337338if (ioctl(file_descriptor, VIDIOC_S_PARM, ¶m) == -1) {339close(file_descriptor);340ERR_FAIL_V_MSG(false, vformat("Cannot set framerate, error: %d.", errno));341}342}343close(file_descriptor);344345parameters = p_parameters.duplicate();346selected_format = p_index;347emit_signal(SNAME("format_changed"));348349return true;350}351352CameraFeedLinux::CameraFeedLinux(const String &p_device_name) :353CameraFeed() {354device_name = p_device_name;355_query_device(device_name);356}357358CameraFeedLinux::~CameraFeedLinux() {359if (is_active()) {360deactivate_feed();361}362}363364365