Skip to content

Commit b88585a

Browse files
committedSep 25, 2024·
Add linux camera support
1 parent c3e16cd commit b88585a

16 files changed

+1108
-10
lines changed
 

‎doc/classes/CameraFeed.xml

+26
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@
3434
Returns the position of camera on the device.
3535
</description>
3636
</method>
37+
<method name="set_format">
38+
<return type="bool" />
39+
<param index="0" name="index" type="int" />
40+
<param index="1" name="parameters" type="Dictionary" />
41+
<description>
42+
Sets the feed format parameters for the given index in the [member formats] array. Returns [code]true[/code] on success. By default YUYV encoded stream is transformed to FEED_RGB. YUYV encoded stream output format can be changed with [param parameters].output value:
43+
[code]separate[/code] will result in FEED_YCBCR_SEP
44+
[code]grayscale[/code] will result in desaturated FEED_RGB
45+
[code]copy[/code] will result in FEED_YCBCR
46+
</description>
47+
</method>
3748
</methods>
3849
<members>
3950
<member name="feed_is_active" type="bool" setter="set_active" getter="is_active" default="false">
@@ -42,7 +53,22 @@
4253
<member name="feed_transform" type="Transform2D" setter="set_transform" getter="get_transform" default="Transform2D(1, 0, 0, -1, 0, 1)">
4354
The transform applied to the camera's image.
4455
</member>
56+
<member name="formats" type="Array" setter="" getter="get_formats" default="[]">
57+
Formats supported by the feed. Each entry is a [Dictionary] describing format parameters.
58+
</member>
4559
</members>
60+
<signals>
61+
<signal name="format_changed">
62+
<description>
63+
Emitted when the format has changed.
64+
</description>
65+
</signal>
66+
<signal name="frame_changed">
67+
<description>
68+
Emitted when a new frame is available.
69+
</description>
70+
</signal>
71+
</signals>
4672
<constants>
4773
<constant name="FEED_NOIMAGE" value="0" enum="FeedDataType">
4874
No image set for the feed.

‎doc/classes/CameraServer.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<description>
77
The [CameraServer] keeps track of different cameras accessible in Godot. These are external cameras such as webcams or the cameras on your phone.
88
It is notably used to provide AR modules with a video feed from the camera.
9-
[b]Note:[/b] This class is currently only implemented on macOS and iOS. To get a [CameraFeed] on iOS, the camera plugin from [url=https://github.com/godotengine/godot-ios-plugins]godot-ios-plugins[/url] is required. On other platforms, no [CameraFeed]s will be available.
9+
[b]Note:[/b] This class is currently only implemented on Linux, macOS, and iOS, on other platforms no [CameraFeed]s will be available. To get a [CameraFeed] on iOS, the camera plugin from [url=https://github.com/godotengine/godot-ios-plugins]godot-ios-plugins[/url] is required.
1010
</description>
1111
<tutorials>
1212
</tutorials>

‎modules/camera/SCsub

+8-2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,16 @@ Import("env_modules")
55

66
env_camera = env_modules.Clone()
77

8-
if env["platform"] == "windows":
8+
if env["platform"] in ["windows", "macos", "linuxbsd"]:
99
env_camera.add_source_files(env.modules_sources, "register_types.cpp")
10+
11+
if env["platform"] == "windows":
1012
env_camera.add_source_files(env.modules_sources, "camera_win.cpp")
1113

1214
elif env["platform"] == "macos":
13-
env_camera.add_source_files(env.modules_sources, "register_types.cpp")
1415
env_camera.add_source_files(env.modules_sources, "camera_macos.mm")
16+
17+
elif env["platform"] == "linuxbsd":
18+
env_camera.add_source_files(env.modules_sources, "camera_linux.cpp")
19+
env_camera.add_source_files(env.modules_sources, "camera_feed_linux.cpp")
20+
env_camera.add_source_files(env.modules_sources, "buffer_decoder.cpp")

‎modules/camera/buffer_decoder.cpp

+212
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
/**************************************************************************/
2+
/* buffer_decoder.cpp */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#include "buffer_decoder.h"
32+
33+
#include "servers/camera/camera_feed.h"
34+
35+
#include <linux/videodev2.h>
36+
37+
BufferDecoder::BufferDecoder(CameraFeed *p_camera_feed) {
38+
camera_feed = p_camera_feed;
39+
width = camera_feed->get_format().width;
40+
height = camera_feed->get_format().height;
41+
image.instantiate();
42+
}
43+
44+
AbstractYuyvBufferDecoder::AbstractYuyvBufferDecoder(CameraFeed *p_camera_feed) :
45+
BufferDecoder(p_camera_feed) {
46+
switch (camera_feed->get_format().pixel_format) {
47+
case V4L2_PIX_FMT_YYUV:
48+
component_indexes = new int[4]{ 0, 1, 2, 3 };
49+
break;
50+
case V4L2_PIX_FMT_YVYU:
51+
component_indexes = new int[4]{ 0, 2, 3, 1 };
52+
break;
53+
case V4L2_PIX_FMT_UYVY:
54+
component_indexes = new int[4]{ 1, 3, 0, 2 };
55+
break;
56+
case V4L2_PIX_FMT_VYUY:
57+
component_indexes = new int[4]{ 1, 3, 2, 0 };
58+
break;
59+
default:
60+
component_indexes = new int[4]{ 0, 2, 1, 3 };
61+
}
62+
}
63+
64+
AbstractYuyvBufferDecoder::~AbstractYuyvBufferDecoder() {
65+
delete[] component_indexes;
66+
}
67+
68+
SeparateYuyvBufferDecoder::SeparateYuyvBufferDecoder(CameraFeed *p_camera_feed) :
69+
AbstractYuyvBufferDecoder(p_camera_feed) {
70+
y_image_data.resize(width * height);
71+
cbcr_image_data.resize(width * height);
72+
y_image.instantiate();
73+
cbcr_image.instantiate();
74+
}
75+
76+
void SeparateYuyvBufferDecoder::decode(StreamingBuffer p_buffer) {
77+
uint8_t *y_dst = (uint8_t *)y_image_data.ptrw();
78+
uint8_t *uv_dst = (uint8_t *)cbcr_image_data.ptrw();
79+
uint8_t *src = (uint8_t *)p_buffer.start;
80+
uint8_t *y0_src = src + component_indexes[0];
81+
uint8_t *y1_src = src + component_indexes[1];
82+
uint8_t *u_src = src + component_indexes[2];
83+
uint8_t *v_src = src + component_indexes[3];
84+
85+
for (int i = 0; i < width * height; i += 2) {
86+
*y_dst++ = *y0_src;
87+
*y_dst++ = *y1_src;
88+
*uv_dst++ = *u_src;
89+
*uv_dst++ = *v_src;
90+
91+
y0_src += 4;
92+
y1_src += 4;
93+
u_src += 4;
94+
v_src += 4;
95+
}
96+
97+
if (y_image.is_valid()) {
98+
y_image->set_data(width, height, false, Image::FORMAT_L8, y_image_data);
99+
} else {
100+
y_image.instantiate(width, height, false, Image::FORMAT_RGB8, y_image_data);
101+
}
102+
if (cbcr_image.is_valid()) {
103+
cbcr_image->set_data(width, height, false, Image::FORMAT_L8, cbcr_image_data);
104+
} else {
105+
cbcr_image.instantiate(width, height, false, Image::FORMAT_RGB8, cbcr_image_data);
106+
}
107+
108+
camera_feed->set_YCbCr_imgs(y_image, cbcr_image);
109+
}
110+
111+
YuyvToGrayscaleBufferDecoder::YuyvToGrayscaleBufferDecoder(CameraFeed *p_camera_feed) :
112+
AbstractYuyvBufferDecoder(p_camera_feed) {
113+
image_data.resize(width * height);
114+
}
115+
116+
void YuyvToGrayscaleBufferDecoder::decode(StreamingBuffer p_buffer) {
117+
uint8_t *dst = (uint8_t *)image_data.ptrw();
118+
uint8_t *src = (uint8_t *)p_buffer.start;
119+
uint8_t *y0_src = src + component_indexes[0];
120+
uint8_t *y1_src = src + component_indexes[1];
121+
122+
for (int i = 0; i < width * height; i += 2) {
123+
*dst++ = *y0_src;
124+
*dst++ = *y1_src;
125+
126+
y0_src += 4;
127+
y1_src += 4;
128+
}
129+
130+
if (image.is_valid()) {
131+
image->set_data(width, height, false, Image::FORMAT_L8, image_data);
132+
} else {
133+
image.instantiate(width, height, false, Image::FORMAT_RGB8, image_data);
134+
}
135+
136+
camera_feed->set_RGB_img(image);
137+
}
138+
139+
YuyvToRgbBufferDecoder::YuyvToRgbBufferDecoder(CameraFeed *p_camera_feed) :
140+
AbstractYuyvBufferDecoder(p_camera_feed) {
141+
image_data.resize(width * height * 3);
142+
}
143+
144+
void YuyvToRgbBufferDecoder::decode(StreamingBuffer p_buffer) {
145+
uint8_t *src = (uint8_t *)p_buffer.start;
146+
uint8_t *y0_src = src + component_indexes[0];
147+
uint8_t *y1_src = src + component_indexes[1];
148+
uint8_t *u_src = src + component_indexes[2];
149+
uint8_t *v_src = src + component_indexes[3];
150+
uint8_t *dst = (uint8_t *)image_data.ptrw();
151+
152+
for (int i = 0; i < width * height; i += 2) {
153+
int u = *u_src;
154+
int v = *v_src;
155+
int u1 = (((u - 128) << 7) + (u - 128)) >> 6;
156+
int rg = (((u - 128) << 1) + (u - 128) + ((v - 128) << 2) + ((v - 128) << 1)) >> 3;
157+
int v1 = (((v - 128) << 1) + (v - 128)) >> 1;
158+
159+
*dst++ = CLAMP(*y0_src + v1, 0, 255);
160+
*dst++ = CLAMP(*y0_src - rg, 0, 255);
161+
*dst++ = CLAMP(*y0_src + u1, 0, 255);
162+
163+
*dst++ = CLAMP(*y1_src + v1, 0, 255);
164+
*dst++ = CLAMP(*y1_src - rg, 0, 255);
165+
*dst++ = CLAMP(*y1_src + u1, 0, 255);
166+
167+
y0_src += 4;
168+
y1_src += 4;
169+
u_src += 4;
170+
v_src += 4;
171+
}
172+
173+
if (image.is_valid()) {
174+
image->set_data(width, height, false, Image::FORMAT_RGB8, image_data);
175+
} else {
176+
image.instantiate(width, height, false, Image::FORMAT_RGB8, image_data);
177+
}
178+
179+
camera_feed->set_RGB_img(image);
180+
}
181+
182+
CopyBufferDecoder::CopyBufferDecoder(CameraFeed *p_camera_feed, bool p_rgba) :
183+
BufferDecoder(p_camera_feed) {
184+
rgba = p_rgba;
185+
image_data.resize(width * height * (rgba ? 4 : 2));
186+
}
187+
188+
void CopyBufferDecoder::decode(StreamingBuffer p_buffer) {
189+
uint8_t *dst = (uint8_t *)image_data.ptrw();
190+
memcpy(dst, p_buffer.start, p_buffer.length);
191+
192+
if (image.is_valid()) {
193+
image->set_data(width, height, false, rgba ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8, image_data);
194+
} else {
195+
image.instantiate(width, height, false, rgba ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8, image_data);
196+
}
197+
198+
camera_feed->set_RGB_img(image);
199+
}
200+
201+
JpegBufferDecoder::JpegBufferDecoder(CameraFeed *p_camera_feed) :
202+
BufferDecoder(p_camera_feed) {
203+
}
204+
205+
void JpegBufferDecoder::decode(StreamingBuffer p_buffer) {
206+
image_data.resize(p_buffer.length);
207+
uint8_t *dst = (uint8_t *)image_data.ptrw();
208+
memcpy(dst, p_buffer.start, p_buffer.length);
209+
if (image->load_jpg_from_buffer(image_data) == OK) {
210+
camera_feed->set_RGB_img(image);
211+
}
212+
}

‎modules/camera/buffer_decoder.h

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/**************************************************************************/
2+
/* buffer_decoder.h */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#ifndef BUFFER_DECODER_H
32+
#define BUFFER_DECODER_H
33+
34+
#include "core/io/image.h"
35+
#include "core/templates/vector.h"
36+
37+
class CameraFeed;
38+
39+
struct StreamingBuffer {
40+
void *start = nullptr;
41+
size_t length = 0;
42+
};
43+
44+
class BufferDecoder {
45+
protected:
46+
CameraFeed *camera_feed = nullptr;
47+
Ref<Image> image;
48+
int width = 0;
49+
int height = 0;
50+
51+
public:
52+
virtual void decode(StreamingBuffer p_buffer) = 0;
53+
54+
BufferDecoder(CameraFeed *p_camera_feed);
55+
virtual ~BufferDecoder() {}
56+
};
57+
58+
class AbstractYuyvBufferDecoder : public BufferDecoder {
59+
protected:
60+
int *component_indexes = nullptr;
61+
62+
public:
63+
AbstractYuyvBufferDecoder(CameraFeed *p_camera_feed);
64+
~AbstractYuyvBufferDecoder();
65+
};
66+
67+
class SeparateYuyvBufferDecoder : public AbstractYuyvBufferDecoder {
68+
private:
69+
Vector<uint8_t> y_image_data;
70+
Vector<uint8_t> cbcr_image_data;
71+
Ref<Image> y_image;
72+
Ref<Image> cbcr_image;
73+
74+
public:
75+
SeparateYuyvBufferDecoder(CameraFeed *p_camera_feed);
76+
virtual void decode(StreamingBuffer p_buffer) override;
77+
};
78+
79+
class YuyvToGrayscaleBufferDecoder : public AbstractYuyvBufferDecoder {
80+
private:
81+
Vector<uint8_t> image_data;
82+
83+
public:
84+
YuyvToGrayscaleBufferDecoder(CameraFeed *p_camera_feed);
85+
virtual void decode(StreamingBuffer p_buffer) override;
86+
};
87+
88+
class YuyvToRgbBufferDecoder : public AbstractYuyvBufferDecoder {
89+
private:
90+
Vector<uint8_t> image_data;
91+
92+
public:
93+
YuyvToRgbBufferDecoder(CameraFeed *p_camera_feed);
94+
virtual void decode(StreamingBuffer p_buffer) override;
95+
};
96+
97+
class CopyBufferDecoder : public BufferDecoder {
98+
private:
99+
Vector<uint8_t> image_data;
100+
bool rgba = false;
101+
102+
public:
103+
CopyBufferDecoder(CameraFeed *p_camera_feed, bool p_rgba);
104+
virtual void decode(StreamingBuffer p_buffer) override;
105+
};
106+
107+
class JpegBufferDecoder : public BufferDecoder {
108+
private:
109+
Vector<uint8_t> image_data;
110+
111+
public:
112+
JpegBufferDecoder(CameraFeed *p_camera_feed);
113+
virtual void decode(StreamingBuffer p_buffer) override;
114+
};
115+
116+
#endif // BUFFER_DECODER_H

‎modules/camera/camera_feed_linux.cpp

+363
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
/**************************************************************************/
2+
/* camera_feed_linux.cpp */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#include "camera_feed_linux.h"
32+
33+
#include <fcntl.h>
34+
#include <sys/ioctl.h>
35+
#include <sys/mman.h>
36+
#include <unistd.h>
37+
38+
void CameraFeedLinux::update_buffer_thread_func(void *p_func) {
39+
if (p_func) {
40+
CameraFeedLinux *camera_feed_linux = (CameraFeedLinux *)p_func;
41+
camera_feed_linux->_update_buffer();
42+
}
43+
}
44+
45+
void CameraFeedLinux::_update_buffer() {
46+
while (!exit_flag.is_set()) {
47+
_read_frame();
48+
usleep(10000);
49+
}
50+
}
51+
52+
void CameraFeedLinux::_query_device(const String &p_device_name) {
53+
file_descriptor = open(p_device_name.ascii(), O_RDWR | O_NONBLOCK, 0);
54+
ERR_FAIL_COND_MSG(file_descriptor == -1, vformat("Cannot open file descriptor for %s. Error: %d.", p_device_name, errno));
55+
56+
struct v4l2_capability capability;
57+
if (ioctl(file_descriptor, VIDIOC_QUERYCAP, &capability) == -1) {
58+
ERR_FAIL_MSG(vformat("Cannot query device. Error: %d.", errno));
59+
}
60+
name = String((char *)capability.card);
61+
62+
for (int index = 0;; index++) {
63+
struct v4l2_fmtdesc fmtdesc;
64+
memset(&fmtdesc, 0, sizeof(fmtdesc));
65+
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
66+
fmtdesc.index = index;
67+
68+
if (ioctl(file_descriptor, VIDIOC_ENUM_FMT, &fmtdesc) == -1) {
69+
break;
70+
}
71+
72+
for (int res_index = 0;; res_index++) {
73+
struct v4l2_frmsizeenum frmsizeenum;
74+
memset(&frmsizeenum, 0, sizeof(frmsizeenum));
75+
frmsizeenum.pixel_format = fmtdesc.pixelformat;
76+
frmsizeenum.index = res_index;
77+
78+
if (ioctl(file_descriptor, VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) == -1) {
79+
break;
80+
}
81+
82+
for (int framerate_index = 0;; framerate_index++) {
83+
struct v4l2_frmivalenum frmivalenum;
84+
memset(&frmivalenum, 0, sizeof(frmivalenum));
85+
frmivalenum.pixel_format = fmtdesc.pixelformat;
86+
frmivalenum.width = frmsizeenum.discrete.width;
87+
frmivalenum.height = frmsizeenum.discrete.height;
88+
frmivalenum.index = framerate_index;
89+
90+
if (ioctl(file_descriptor, VIDIOC_ENUM_FRAMEINTERVALS, &frmivalenum) == -1) {
91+
if (framerate_index == 0) {
92+
_add_format(fmtdesc, frmsizeenum.discrete, -1, 1);
93+
}
94+
break;
95+
}
96+
97+
_add_format(fmtdesc, frmsizeenum.discrete, frmivalenum.discrete.numerator, frmivalenum.discrete.denominator);
98+
}
99+
}
100+
}
101+
102+
close(file_descriptor);
103+
}
104+
105+
void CameraFeedLinux::_add_format(v4l2_fmtdesc p_description, v4l2_frmsize_discrete p_size, int p_frame_numerator, int p_frame_denominator) {
106+
FeedFormat feed_format;
107+
feed_format.width = p_size.width;
108+
feed_format.height = p_size.height;
109+
feed_format.format = String((char *)p_description.description);
110+
feed_format.frame_numerator = p_frame_numerator;
111+
feed_format.frame_denominator = p_frame_denominator;
112+
feed_format.pixel_format = p_description.pixelformat;
113+
print_verbose(vformat("%s %dx%d@%d/%dfps", (char *)p_description.description, p_size.width, p_size.height, p_frame_denominator, p_frame_numerator));
114+
formats.push_back(feed_format);
115+
}
116+
117+
bool CameraFeedLinux::_request_buffers() {
118+
struct v4l2_requestbuffers requestbuffers;
119+
120+
memset(&requestbuffers, 0, sizeof(requestbuffers));
121+
requestbuffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
122+
requestbuffers.memory = V4L2_MEMORY_MMAP;
123+
requestbuffers.count = 4;
124+
125+
if (ioctl(file_descriptor, VIDIOC_REQBUFS, &requestbuffers) == -1) {
126+
ERR_FAIL_V_MSG(false, vformat("ioctl(VIDIOC_REQBUFS) error: %d.", errno));
127+
}
128+
129+
ERR_FAIL_COND_V_MSG(requestbuffers.count < 2, false, "Not enough buffers granted.");
130+
131+
buffer_count = requestbuffers.count;
132+
buffers = new StreamingBuffer[buffer_count];
133+
134+
for (unsigned int i = 0; i < buffer_count; i++) {
135+
struct v4l2_buffer buffer;
136+
137+
memset(&buffer, 0, sizeof(buffer));
138+
buffer.type = requestbuffers.type;
139+
buffer.memory = V4L2_MEMORY_MMAP;
140+
buffer.index = i;
141+
142+
if (ioctl(file_descriptor, VIDIOC_QUERYBUF, &buffer) == -1) {
143+
delete[] buffers;
144+
ERR_FAIL_V_MSG(false, vformat("ioctl(VIDIOC_QUERYBUF) error: %d.", errno));
145+
}
146+
147+
buffers[i].length = buffer.length;
148+
buffers[i].start = mmap(NULL, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, file_descriptor, buffer.m.offset);
149+
150+
if (buffers[i].start == MAP_FAILED) {
151+
for (unsigned int b = 0; b < i; b++) {
152+
_unmap_buffers(i);
153+
}
154+
delete[] buffers;
155+
ERR_FAIL_V_MSG(false, "Mapping buffers failed.");
156+
}
157+
}
158+
159+
return true;
160+
}
161+
162+
bool CameraFeedLinux::_start_capturing() {
163+
for (unsigned int i = 0; i < buffer_count; i++) {
164+
struct v4l2_buffer buffer;
165+
166+
memset(&buffer, 0, sizeof(buffer));
167+
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
168+
buffer.memory = V4L2_MEMORY_MMAP;
169+
buffer.index = i;
170+
171+
if (ioctl(file_descriptor, VIDIOC_QBUF, &buffer) == -1) {
172+
ERR_FAIL_V_MSG(false, vformat("ioctl(VIDIOC_QBUF) error: %d.", errno));
173+
}
174+
}
175+
176+
enum v4l2_buf_type type;
177+
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
178+
179+
if (ioctl(file_descriptor, VIDIOC_STREAMON, &type) == -1) {
180+
ERR_FAIL_V_MSG(false, vformat("ioctl(VIDIOC_STREAMON) error: %d.", errno));
181+
}
182+
183+
return true;
184+
}
185+
186+
void CameraFeedLinux::_read_frame() {
187+
struct v4l2_buffer buffer;
188+
memset(&buffer, 0, sizeof(buffer));
189+
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
190+
buffer.memory = V4L2_MEMORY_MMAP;
191+
192+
if (ioctl(file_descriptor, VIDIOC_DQBUF, &buffer) == -1) {
193+
if (errno != EAGAIN) {
194+
print_error(vformat("ioctl(VIDIOC_DQBUF) error: %d.", errno));
195+
exit_flag.set();
196+
}
197+
return;
198+
}
199+
200+
buffer_decoder->decode(buffers[buffer.index]);
201+
202+
if (ioctl(file_descriptor, VIDIOC_QBUF, &buffer) == -1) {
203+
print_error(vformat("ioctl(VIDIOC_QBUF) error: %d.", errno));
204+
}
205+
206+
emit_signal(SNAME("frame_changed"));
207+
}
208+
209+
void CameraFeedLinux::_stop_capturing() {
210+
enum v4l2_buf_type type;
211+
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
212+
213+
if (ioctl(file_descriptor, VIDIOC_STREAMOFF, &type) == -1) {
214+
print_error(vformat("ioctl(VIDIOC_STREAMOFF) error: %d.", errno));
215+
}
216+
}
217+
218+
void CameraFeedLinux::_unmap_buffers(unsigned int p_count) {
219+
for (unsigned int i = 0; i < p_count; i++) {
220+
munmap(buffers[i].start, buffers[i].length);
221+
}
222+
}
223+
224+
void CameraFeedLinux::_start_thread() {
225+
exit_flag.clear();
226+
thread = memnew(Thread);
227+
thread->start(CameraFeedLinux::update_buffer_thread_func, this);
228+
}
229+
230+
String CameraFeedLinux::get_device_name() const {
231+
return device_name;
232+
}
233+
234+
bool CameraFeedLinux::activate_feed() {
235+
file_descriptor = open(device_name.ascii(), O_RDWR | O_NONBLOCK, 0);
236+
if (_request_buffers() && _start_capturing()) {
237+
buffer_decoder = _create_buffer_decoder();
238+
_start_thread();
239+
return true;
240+
}
241+
ERR_FAIL_V_MSG(false, "Could not activate feed.");
242+
}
243+
244+
BufferDecoder *CameraFeedLinux::_create_buffer_decoder() {
245+
switch (formats[selected_format].pixel_format) {
246+
case V4L2_PIX_FMT_MJPEG:
247+
case V4L2_PIX_FMT_JPEG:
248+
return memnew(JpegBufferDecoder(this));
249+
case V4L2_PIX_FMT_YUYV:
250+
case V4L2_PIX_FMT_YYUV:
251+
case V4L2_PIX_FMT_YVYU:
252+
case V4L2_PIX_FMT_UYVY:
253+
case V4L2_PIX_FMT_VYUY: {
254+
String output = parameters["output"];
255+
if (output == "separate") {
256+
return memnew(SeparateYuyvBufferDecoder(this));
257+
}
258+
if (output == "grayscale") {
259+
return memnew(YuyvToGrayscaleBufferDecoder(this));
260+
}
261+
if (output == "copy") {
262+
return memnew(CopyBufferDecoder(this, false));
263+
}
264+
return memnew(YuyvToRgbBufferDecoder(this));
265+
}
266+
default:
267+
return memnew(CopyBufferDecoder(this, true));
268+
}
269+
}
270+
271+
void CameraFeedLinux::deactivate_feed() {
272+
exit_flag.set();
273+
thread->wait_to_finish();
274+
memdelete(thread);
275+
_stop_capturing();
276+
_unmap_buffers(buffer_count);
277+
delete[] buffers;
278+
memdelete(buffer_decoder);
279+
for (int i = 0; i < CameraServer::FEED_IMAGES; i++) {
280+
RID placeholder = RenderingServer::get_singleton()->texture_2d_placeholder_create();
281+
RenderingServer::get_singleton()->texture_replace(texture[i], placeholder);
282+
}
283+
base_width = 0;
284+
base_height = 0;
285+
close(file_descriptor);
286+
287+
emit_signal(SNAME("format_changed"));
288+
}
289+
290+
Array CameraFeedLinux::get_formats() const {
291+
Array result;
292+
for (const FeedFormat &format : formats) {
293+
Dictionary dictionary;
294+
dictionary["width"] = format.width;
295+
dictionary["height"] = format.height;
296+
dictionary["format"] = format.format;
297+
dictionary["frame_numerator"] = format.frame_numerator;
298+
dictionary["frame_denominator"] = format.frame_denominator;
299+
result.push_back(dictionary);
300+
}
301+
return result;
302+
}
303+
304+
CameraFeed::FeedFormat CameraFeedLinux::get_format() const {
305+
return formats[selected_format];
306+
}
307+
308+
bool CameraFeedLinux::set_format(int p_index, const Dictionary &p_parameters) {
309+
ERR_FAIL_COND_V_MSG(active, false, "Feed is active.");
310+
ERR_FAIL_INDEX_V_MSG(p_index, formats.size(), false, "Invalid format index.");
311+
312+
parameters = p_parameters.duplicate();
313+
selected_format = p_index;
314+
315+
FeedFormat feed_format = formats[p_index];
316+
317+
file_descriptor = open(device_name.ascii(), O_RDWR | O_NONBLOCK, 0);
318+
319+
struct v4l2_format format;
320+
memset(&format, 0, sizeof(format));
321+
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
322+
format.fmt.pix.width = feed_format.width;
323+
format.fmt.pix.height = feed_format.height;
324+
format.fmt.pix.pixelformat = feed_format.pixel_format;
325+
326+
if (ioctl(file_descriptor, VIDIOC_S_FMT, &format) == -1) {
327+
close(file_descriptor);
328+
ERR_FAIL_V_MSG(false, vformat("Cannot set format, error: %d.", errno));
329+
}
330+
331+
if (feed_format.frame_numerator > 0) {
332+
struct v4l2_streamparm param;
333+
memset(&param, 0, sizeof(param));
334+
335+
param.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
336+
param.parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
337+
param.parm.capture.timeperframe.numerator = feed_format.frame_numerator;
338+
param.parm.capture.timeperframe.denominator = feed_format.frame_denominator;
339+
340+
if (ioctl(file_descriptor, VIDIOC_S_PARM, &param) == -1) {
341+
close(file_descriptor);
342+
ERR_FAIL_V_MSG(false, vformat("Cannot set framerate, error: %d.", errno));
343+
}
344+
}
345+
close(file_descriptor);
346+
347+
emit_signal(SNAME("format_changed"));
348+
349+
return true;
350+
}
351+
352+
CameraFeedLinux::CameraFeedLinux(const String &p_device_name) :
353+
CameraFeed() {
354+
device_name = p_device_name;
355+
_query_device(device_name);
356+
set_format(0, Dictionary());
357+
}
358+
359+
CameraFeedLinux::~CameraFeedLinux() {
360+
if (is_active()) {
361+
deactivate_feed();
362+
}
363+
}

‎modules/camera/camera_feed_linux.h

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**************************************************************************/
2+
/* camera_feed_linux.h */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#ifndef CAMERA_FEED_LINUX_H
32+
#define CAMERA_FEED_LINUX_H
33+
34+
#include "buffer_decoder.h"
35+
36+
#include "core/os/thread.h"
37+
#include "servers/camera/camera_feed.h"
38+
39+
#include <linux/videodev2.h>
40+
41+
struct StreamingBuffer;
42+
43+
class CameraFeedLinux : public CameraFeed {
44+
private:
45+
SafeFlag exit_flag;
46+
Thread *thread = nullptr;
47+
String device_name;
48+
int file_descriptor = -1;
49+
StreamingBuffer *buffers = nullptr;
50+
unsigned int buffer_count = 0;
51+
BufferDecoder *buffer_decoder = nullptr;
52+
53+
static void update_buffer_thread_func(void *p_func);
54+
55+
void _update_buffer();
56+
void _query_device(const String &p_device_name);
57+
void _add_format(v4l2_fmtdesc description, v4l2_frmsize_discrete size, int frame_numerator, int frame_denominator);
58+
bool _request_buffers();
59+
bool _start_capturing();
60+
void _read_frame();
61+
void _stop_capturing();
62+
void _unmap_buffers(unsigned int p_count);
63+
BufferDecoder *_create_buffer_decoder();
64+
void _start_thread();
65+
66+
public:
67+
String get_device_name() const;
68+
bool activate_feed();
69+
void deactivate_feed();
70+
bool set_format(int p_index, const Dictionary &p_parameters);
71+
Array get_formats() const;
72+
FeedFormat get_format() const;
73+
74+
CameraFeedLinux(const String &p_device_name);
75+
virtual ~CameraFeedLinux();
76+
};
77+
78+
#endif // CAMERA_FEED_LINUX_H

‎modules/camera/camera_linux.cpp

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/**************************************************************************/
2+
/* camera_linux.cpp */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#include "camera_linux.h"
32+
33+
#include "camera_feed_linux.h"
34+
35+
#include <dirent.h>
36+
#include <fcntl.h>
37+
#include <sys/ioctl.h>
38+
#include <sys/stat.h>
39+
#include <unistd.h>
40+
41+
void CameraLinux::camera_thread_func(void *p_camera_linux) {
42+
if (p_camera_linux) {
43+
CameraLinux *camera_linux = (CameraLinux *)p_camera_linux;
44+
camera_linux->_update_devices();
45+
}
46+
}
47+
48+
void CameraLinux::_update_devices() {
49+
while (!exit_flag.is_set()) {
50+
{
51+
MutexLock lock(camera_mutex);
52+
53+
for (int i = feeds.size() - 1; i >= 0; i--) {
54+
Ref<CameraFeedLinux> feed = (Ref<CameraFeedLinux>)feeds[i];
55+
String device_name = feed->get_device_name();
56+
if (!_is_active(device_name)) {
57+
remove_feed(feed);
58+
}
59+
}
60+
61+
DIR *devices = opendir("/dev");
62+
63+
if (devices) {
64+
struct dirent *device;
65+
66+
while ((device = readdir(devices)) != nullptr) {
67+
if (strncmp(device->d_name, "video", 5) != 0) {
68+
continue;
69+
}
70+
String device_name = String("/dev/") + String(device->d_name);
71+
if (!_has_device(device_name)) {
72+
_add_device(device_name);
73+
}
74+
}
75+
}
76+
77+
closedir(devices);
78+
}
79+
80+
usleep(1000000);
81+
}
82+
}
83+
84+
bool CameraLinux::_has_device(const String &p_device_name) {
85+
for (int i = 0; i < feeds.size(); i++) {
86+
Ref<CameraFeedLinux> feed = (Ref<CameraFeedLinux>)feeds[i];
87+
if (feed->get_device_name() == p_device_name) {
88+
return true;
89+
}
90+
}
91+
return false;
92+
}
93+
94+
void CameraLinux::_add_device(const String &p_device_name) {
95+
int file_descriptor = _open_device(p_device_name);
96+
97+
if (file_descriptor != -1) {
98+
if (_is_video_capture_device(file_descriptor)) {
99+
Ref<CameraFeedLinux> feed = memnew(CameraFeedLinux(p_device_name));
100+
add_feed(feed);
101+
}
102+
}
103+
104+
close(file_descriptor);
105+
}
106+
107+
int CameraLinux::_open_device(const String &p_device_name) {
108+
struct stat s;
109+
110+
if (stat(p_device_name.ascii(), &s) == -1) {
111+
return -1;
112+
}
113+
114+
if (!S_ISCHR(s.st_mode)) {
115+
return -1;
116+
}
117+
118+
return open(p_device_name.ascii(), O_RDWR | O_NONBLOCK, 0);
119+
}
120+
121+
// TODO any cheaper/cleaner way to check if file descriptor is invalid?
122+
bool CameraLinux::_is_active(const String &p_device_name) {
123+
struct v4l2_capability capability;
124+
bool result = false;
125+
int file_descriptor = _open_device(p_device_name);
126+
if (file_descriptor != -1 && ioctl(file_descriptor, VIDIOC_QUERYCAP, &capability) != -1) {
127+
result = true;
128+
}
129+
close(file_descriptor);
130+
return result;
131+
}
132+
133+
bool CameraLinux::_is_video_capture_device(int p_file_descriptor) {
134+
struct v4l2_capability capability;
135+
136+
if (ioctl(p_file_descriptor, VIDIOC_QUERYCAP, &capability) == -1) {
137+
print_verbose("Cannot query device");
138+
return false;
139+
}
140+
141+
if (!(capability.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
142+
print_verbose(vformat("%s is no video capture device\n", String((char *)capability.card)));
143+
return false;
144+
}
145+
146+
if (!(capability.capabilities & V4L2_CAP_STREAMING)) {
147+
print_verbose(vformat("%s does not support streaming", String((char *)capability.card)));
148+
return false;
149+
}
150+
151+
return _can_query_format(p_file_descriptor, V4L2_BUF_TYPE_VIDEO_CAPTURE);
152+
}
153+
154+
bool CameraLinux::_can_query_format(int p_file_descriptor, int p_type) {
155+
struct v4l2_format format;
156+
memset(&format, 0, sizeof(format));
157+
format.type = p_type;
158+
159+
return ioctl(p_file_descriptor, VIDIOC_G_FMT, &format) != -1;
160+
}
161+
162+
CameraLinux::CameraLinux() {
163+
camera_thread.start(CameraLinux::camera_thread_func, this);
164+
};
165+
166+
CameraLinux::~CameraLinux() {
167+
exit_flag.set();
168+
camera_thread.wait_to_finish();
169+
}

‎modules/camera/camera_linux.h

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**************************************************************************/
2+
/* camera_linux.h */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#ifndef CAMERA_LINUX_H
32+
#define CAMERA_LINUX_H
33+
34+
#include "core/os/mutex.h"
35+
#include "core/os/thread.h"
36+
#include "servers/camera_server.h"
37+
38+
class CameraLinux : public CameraServer {
39+
private:
40+
SafeFlag exit_flag;
41+
Thread camera_thread;
42+
Mutex camera_mutex;
43+
44+
static void camera_thread_func(void *p_camera_linux);
45+
46+
void _update_devices();
47+
bool _has_device(const String &p_device_name);
48+
void _add_device(const String &p_device_name);
49+
void _remove_device(const String &p_device_name);
50+
int _open_device(const String &p_device_name);
51+
bool _is_active(const String &p_device_name);
52+
bool _is_video_capture_device(int p_file_descriptor);
53+
bool _can_query_format(int p_file_descriptor, int p_type);
54+
55+
public:
56+
CameraLinux();
57+
~CameraLinux();
58+
};
59+
60+
#endif // CAMERA_LINUX_H

‎modules/camera/config.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
def can_build(env, platform):
2-
return platform == "macos" or platform == "windows"
2+
return platform == "macos" or platform == "windows" or platform == "linuxbsd"
33

44

55
def configure(env):

‎modules/camera/register_types.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030

3131
#include "register_types.h"
3232

33+
#if defined(LINUXBSD_ENABLED)
34+
#include "camera_linux.h"
35+
#endif
3336
#if defined(WINDOWS_ENABLED)
3437
#include "camera_win.h"
3538
#endif
@@ -42,6 +45,9 @@ void initialize_camera_module(ModuleInitializationLevel p_level) {
4245
return;
4346
}
4447

48+
#if defined(LINUXBSD_ENABLED)
49+
CameraServer::make_default<CameraLinux>();
50+
#endif
4551
#if defined(WINDOWS_ENABLED)
4652
CameraServer::make_default<CameraWindows>();
4753
#endif

‎pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ ignore-words-list = """\
7070
numer,
7171
ot,
7272
outin,
73+
parm,
7374
requestor,
7475
te,
7576
textin,

‎scene/resources/camera_texture.cpp

+22-2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ void CameraTexture::_bind_methods() {
4747
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "camera_is_active"), "set_camera_active", "get_camera_active");
4848
}
4949

50+
void CameraTexture::_on_format_changed() {
51+
// FIXME: `emit_changed` is more appropriate, but causes errors for some reason.
52+
callable_mp((Resource *)this, &Resource::emit_changed).call_deferred();
53+
}
54+
5055
int CameraTexture::get_width() const {
5156
Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id);
5257
if (feed.is_valid()) {
@@ -82,13 +87,26 @@ RID CameraTexture::get_rid() const {
8287
}
8388

8489
Ref<Image> CameraTexture::get_image() const {
85-
// not (yet) supported
86-
return Ref<Image>();
90+
return RenderingServer::get_singleton()->texture_2d_get(get_rid());
8791
}
8892

8993
void CameraTexture::set_camera_feed_id(int p_new_id) {
94+
Ref<CameraFeed> feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id);
95+
if (feed.is_valid()) {
96+
if (feed->is_connected("format_changed", callable_mp(this, &CameraTexture::_on_format_changed))) {
97+
feed->disconnect("format_changed", callable_mp(this, &CameraTexture::_on_format_changed));
98+
}
99+
}
100+
90101
camera_feed_id = p_new_id;
102+
103+
feed = CameraServer::get_singleton()->get_feed_by_id(camera_feed_id);
104+
if (feed.is_valid()) {
105+
feed->connect("format_changed", callable_mp(this, &CameraTexture::_on_format_changed));
106+
}
107+
91108
notify_property_list_changed();
109+
callable_mp((Resource *)this, &Resource::emit_changed).call_deferred();
92110
}
93111

94112
int CameraTexture::get_camera_feed_id() const {
@@ -98,6 +116,7 @@ int CameraTexture::get_camera_feed_id() const {
98116
void CameraTexture::set_which_feed(CameraServer::FeedImage p_which) {
99117
which_feed = p_which;
100118
notify_property_list_changed();
119+
callable_mp((Resource *)this, &Resource::emit_changed).call_deferred();
101120
}
102121

103122
CameraServer::FeedImage CameraTexture::get_which_feed() const {
@@ -109,6 +128,7 @@ void CameraTexture::set_camera_active(bool p_active) {
109128
if (feed.is_valid()) {
110129
feed->set_active(p_active);
111130
notify_property_list_changed();
131+
callable_mp((Resource *)this, &Resource::emit_changed).call_deferred();
112132
}
113133
}
114134

‎scene/resources/camera_texture.h

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class CameraTexture : public Texture2D {
4343

4444
protected:
4545
static void _bind_methods();
46+
void _on_format_changed();
4647

4748
public:
4849
virtual int get_width() const override;

‎servers/camera/camera_feed.cpp

+26-2
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,16 @@ void CameraFeed::_bind_methods() {
5656

5757
ClassDB::bind_method(D_METHOD("get_datatype"), &CameraFeed::get_datatype);
5858

59+
ClassDB::bind_method(D_METHOD("get_formats"), &CameraFeed::get_formats);
60+
ClassDB::bind_method(D_METHOD("set_format", "index", "parameters"), &CameraFeed::set_format);
61+
62+
ADD_SIGNAL(MethodInfo("frame_changed"));
63+
ADD_SIGNAL(MethodInfo("format_changed"));
64+
5965
ADD_GROUP("Feed", "feed_");
6066
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "feed_is_active"), "set_active", "is_active");
6167
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "feed_transform"), "set_transform", "get_transform");
68+
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "formats"), "", "get_formats");
6269

6370
BIND_ENUM_CONSTANT(FEED_NOIMAGE);
6471
BIND_ENUM_CONSTANT(FEED_RGB);
@@ -84,13 +91,11 @@ void CameraFeed::set_active(bool p_is_active) {
8491
} else if (p_is_active) {
8592
// attempt to activate this feed
8693
if (activate_feed()) {
87-
print_line("Activate " + name);
8894
active = true;
8995
}
9096
} else {
9197
// just deactivate it
9298
deactivate_feed();
93-
print_line("Deactivate " + name);
9499
active = false;
95100
}
96101
}
@@ -183,6 +188,8 @@ void CameraFeed::set_RGB_img(const Ref<Image> &p_rgb_img) {
183188

184189
RID new_texture = RenderingServer::get_singleton()->texture_2d_create(p_rgb_img);
185190
RenderingServer::get_singleton()->texture_replace(texture[CameraServer::FEED_RGBA_IMAGE], new_texture);
191+
192+
emit_signal(SNAME("format_changed"));
186193
} else {
187194
RenderingServer::get_singleton()->texture_2d_update(texture[CameraServer::FEED_RGBA_IMAGE], p_rgb_img);
188195
}
@@ -204,6 +211,8 @@ void CameraFeed::set_YCbCr_img(const Ref<Image> &p_ycbcr_img) {
204211

205212
RID new_texture = RenderingServer::get_singleton()->texture_2d_create(p_ycbcr_img);
206213
RenderingServer::get_singleton()->texture_replace(texture[CameraServer::FEED_RGBA_IMAGE], new_texture);
214+
215+
emit_signal(SNAME("format_changed"));
207216
} else {
208217
RenderingServer::get_singleton()->texture_2d_update(texture[CameraServer::FEED_RGBA_IMAGE], p_ycbcr_img);
209218
}
@@ -235,6 +244,8 @@ void CameraFeed::set_YCbCr_imgs(const Ref<Image> &p_y_img, const Ref<Image> &p_c
235244
RID new_texture = RenderingServer::get_singleton()->texture_2d_create(p_cbcr_img);
236245
RenderingServer::get_singleton()->texture_replace(texture[CameraServer::FEED_CBCR_IMAGE], new_texture);
237246
}
247+
248+
emit_signal(SNAME("format_changed"));
238249
} else {
239250
RenderingServer::get_singleton()->texture_2d_update(texture[CameraServer::FEED_Y_IMAGE], p_y_img);
240251
RenderingServer::get_singleton()->texture_2d_update(texture[CameraServer::FEED_CBCR_IMAGE], p_cbcr_img);
@@ -252,3 +263,16 @@ bool CameraFeed::activate_feed() {
252263
void CameraFeed::deactivate_feed() {
253264
// nothing to do here
254265
}
266+
267+
bool CameraFeed::set_format(int p_index, const Dictionary &p_parameters) {
268+
return false;
269+
}
270+
271+
Array CameraFeed::get_formats() const {
272+
return Array();
273+
}
274+
275+
CameraFeed::FeedFormat CameraFeed::get_format() const {
276+
FeedFormat feed_format = {};
277+
return feed_format;
278+
}

‎servers/camera/camera_feed.h

+18-2
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,26 @@ class CameraFeed : public RefCounted {
6060

6161
private:
6262
int id; // unique id for this, for internal use in case feeds are removed
63-
int base_width;
64-
int base_height;
6563

6664
protected:
65+
struct FeedFormat {
66+
int width = 0;
67+
int height = 0;
68+
String format;
69+
int frame_numerator = 0;
70+
int frame_denominator = 0;
71+
uint32_t pixel_format = 0;
72+
};
73+
6774
String name; // name of our camera feed
6875
FeedDataType datatype; // type of texture data stored
6976
FeedPosition position; // position of camera on the device
7077
Transform2D transform; // display transform
78+
int base_width = 0;
79+
int base_height = 0;
80+
Vector<FeedFormat> formats;
81+
Dictionary parameters;
82+
int selected_format = -1;
7183

7284
bool active; // only when active do we actually update the camera texture each frame
7385
RID texture[CameraServer::FEED_IMAGES]; // texture images needed for this
@@ -102,6 +114,10 @@ class CameraFeed : public RefCounted {
102114
void set_YCbCr_img(const Ref<Image> &p_ycbcr_img);
103115
void set_YCbCr_imgs(const Ref<Image> &p_y_img, const Ref<Image> &p_cbcr_img);
104116

117+
virtual bool set_format(int p_index, const Dictionary &p_parameters);
118+
virtual Array get_formats() const;
119+
virtual FeedFormat get_format() const;
120+
105121
virtual bool activate_feed();
106122
virtual void deactivate_feed();
107123
};

0 commit comments

Comments
 (0)
Please sign in to comment.