Skip to content

Commit

Permalink
Merge pull request #224 from jss2a98aj/backport-linux-camera-support
Browse files Browse the repository at this point in the history
[backport] Linux camera support
  • Loading branch information
Bioblaze authored Jan 10, 2025
2 parents 85845ba + ace5801 commit 5590b18
Show file tree
Hide file tree
Showing 20 changed files with 1,142 additions and 12 deletions.
26 changes: 26 additions & 0 deletions doc/classes/CameraFeed.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@
Returns the position of camera on the device.
</description>
</method>
<method name="set_format">
<return type="bool" />
<param index="0" name="index" type="int" />
<param index="1" name="parameters" type="Dictionary" />
<description>
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:
[code]separate[/code] will result in FEED_YCBCR_SEP
[code]grayscale[/code] will result in desaturated FEED_RGB
[code]copy[/code] will result in FEED_YCBCR
</description>
</method>
</methods>
<members>
<member name="feed_is_active" type="bool" setter="set_active" getter="is_active" default="false">
Expand All @@ -42,7 +53,22 @@
<member name="feed_transform" type="Transform2D" setter="set_transform" getter="get_transform" default="Transform2D(1, 0, 0, -1, 0, 1)">
The transform applied to the camera's image.
</member>
<member name="formats" type="Array" setter="" getter="get_formats" default="[]">
Formats supported by the feed. Each entry is a [Dictionary] describing format parameters.
</member>
</members>
<signals>
<signal name="format_changed">
<description>
Emitted when the format has changed.
</description>
</signal>
<signal name="frame_changed">
<description>
Emitted when a new frame is available.
</description>
</signal>
</signals>
<constants>
<constant name="FEED_NOIMAGE" value="0" enum="FeedDataType">
No image set for the feed.
Expand Down
2 changes: 1 addition & 1 deletion doc/classes/CameraServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<description>
The [CameraServer] keeps track of different cameras accessible in Godot. These are external cameras such as webcams or the cameras on your phone.
It is notably used to provide AR modules with a video feed from the camera.
[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.
[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.
</description>
<tutorials>
</tutorials>
Expand Down
10 changes: 8 additions & 2 deletions modules/camera/SCsub
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@ Import("env_modules")

env_camera = env_modules.Clone()

if env["platform"] == "windows":
if env["platform"] in ["windows", "macos", "linuxbsd"]:
env_camera.add_source_files(env.modules_sources, "register_types.cpp")

if env["platform"] == "windows":
env_camera.add_source_files(env.modules_sources, "camera_win.cpp")

elif env["platform"] == "macos":
env_camera.add_source_files(env.modules_sources, "register_types.cpp")
env_camera.add_source_files(env.modules_sources, "camera_macos.mm")

elif env["platform"] == "linuxbsd":
env_camera.add_source_files(env.modules_sources, "camera_linux.cpp")
env_camera.add_source_files(env.modules_sources, "camera_feed_linux.cpp")
env_camera.add_source_files(env.modules_sources, "buffer_decoder.cpp")
212 changes: 212 additions & 0 deletions modules/camera/buffer_decoder.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/**************************************************************************/
/* buffer_decoder.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/

#include "buffer_decoder.h"

#include "servers/camera/camera_feed.h"

#include <linux/videodev2.h>

BufferDecoder::BufferDecoder(CameraFeed *p_camera_feed) {
camera_feed = p_camera_feed;
width = camera_feed->get_format().width;
height = camera_feed->get_format().height;
image.instantiate();
}

AbstractYuyvBufferDecoder::AbstractYuyvBufferDecoder(CameraFeed *p_camera_feed) :
BufferDecoder(p_camera_feed) {
switch (camera_feed->get_format().pixel_format) {
case V4L2_PIX_FMT_YYUV:
component_indexes = new int[4]{ 0, 1, 2, 3 };
break;
case V4L2_PIX_FMT_YVYU:
component_indexes = new int[4]{ 0, 2, 3, 1 };
break;
case V4L2_PIX_FMT_UYVY:
component_indexes = new int[4]{ 1, 3, 0, 2 };
break;
case V4L2_PIX_FMT_VYUY:
component_indexes = new int[4]{ 1, 3, 2, 0 };
break;
default:
component_indexes = new int[4]{ 0, 2, 1, 3 };
}
}

AbstractYuyvBufferDecoder::~AbstractYuyvBufferDecoder() {
delete[] component_indexes;
}

SeparateYuyvBufferDecoder::SeparateYuyvBufferDecoder(CameraFeed *p_camera_feed) :
AbstractYuyvBufferDecoder(p_camera_feed) {
y_image_data.resize(width * height);
cbcr_image_data.resize(width * height);
y_image.instantiate();
cbcr_image.instantiate();
}

void SeparateYuyvBufferDecoder::decode(StreamingBuffer p_buffer) {
uint8_t *y_dst = (uint8_t *)y_image_data.ptrw();
uint8_t *uv_dst = (uint8_t *)cbcr_image_data.ptrw();
uint8_t *src = (uint8_t *)p_buffer.start;
uint8_t *y0_src = src + component_indexes[0];
uint8_t *y1_src = src + component_indexes[1];
uint8_t *u_src = src + component_indexes[2];
uint8_t *v_src = src + component_indexes[3];

for (int i = 0; i < width * height; i += 2) {
*y_dst++ = *y0_src;
*y_dst++ = *y1_src;
*uv_dst++ = *u_src;
*uv_dst++ = *v_src;

y0_src += 4;
y1_src += 4;
u_src += 4;
v_src += 4;
}

if (y_image.is_valid()) {
y_image->set_data(width, height, false, Image::FORMAT_L8, y_image_data);
} else {
y_image.instantiate(width, height, false, Image::FORMAT_RGB8, y_image_data);
}
if (cbcr_image.is_valid()) {
cbcr_image->set_data(width, height, false, Image::FORMAT_L8, cbcr_image_data);
} else {
cbcr_image.instantiate(width, height, false, Image::FORMAT_RGB8, cbcr_image_data);
}

camera_feed->set_YCbCr_imgs(y_image, cbcr_image);
}

YuyvToGrayscaleBufferDecoder::YuyvToGrayscaleBufferDecoder(CameraFeed *p_camera_feed) :
AbstractYuyvBufferDecoder(p_camera_feed) {
image_data.resize(width * height);
}

void YuyvToGrayscaleBufferDecoder::decode(StreamingBuffer p_buffer) {
uint8_t *dst = (uint8_t *)image_data.ptrw();
uint8_t *src = (uint8_t *)p_buffer.start;
uint8_t *y0_src = src + component_indexes[0];
uint8_t *y1_src = src + component_indexes[1];

for (int i = 0; i < width * height; i += 2) {
*dst++ = *y0_src;
*dst++ = *y1_src;

y0_src += 4;
y1_src += 4;
}

if (image.is_valid()) {
image->set_data(width, height, false, Image::FORMAT_L8, image_data);
} else {
image.instantiate(width, height, false, Image::FORMAT_RGB8, image_data);
}

camera_feed->set_RGB_img(image);
}

YuyvToRgbBufferDecoder::YuyvToRgbBufferDecoder(CameraFeed *p_camera_feed) :
AbstractYuyvBufferDecoder(p_camera_feed) {
image_data.resize(width * height * 3);
}

void YuyvToRgbBufferDecoder::decode(StreamingBuffer p_buffer) {
uint8_t *src = (uint8_t *)p_buffer.start;
uint8_t *y0_src = src + component_indexes[0];
uint8_t *y1_src = src + component_indexes[1];
uint8_t *u_src = src + component_indexes[2];
uint8_t *v_src = src + component_indexes[3];
uint8_t *dst = (uint8_t *)image_data.ptrw();

for (int i = 0; i < width * height; i += 2) {
int u = *u_src;
int v = *v_src;
int u1 = (((u - 128) << 7) + (u - 128)) >> 6;
int rg = (((u - 128) << 1) + (u - 128) + ((v - 128) << 2) + ((v - 128) << 1)) >> 3;
int v1 = (((v - 128) << 1) + (v - 128)) >> 1;

*dst++ = CLAMP(*y0_src + v1, 0, 255);
*dst++ = CLAMP(*y0_src - rg, 0, 255);
*dst++ = CLAMP(*y0_src + u1, 0, 255);

*dst++ = CLAMP(*y1_src + v1, 0, 255);
*dst++ = CLAMP(*y1_src - rg, 0, 255);
*dst++ = CLAMP(*y1_src + u1, 0, 255);

y0_src += 4;
y1_src += 4;
u_src += 4;
v_src += 4;
}

if (image.is_valid()) {
image->set_data(width, height, false, Image::FORMAT_RGB8, image_data);
} else {
image.instantiate(width, height, false, Image::FORMAT_RGB8, image_data);
}

camera_feed->set_RGB_img(image);
}

CopyBufferDecoder::CopyBufferDecoder(CameraFeed *p_camera_feed, bool p_rgba) :
BufferDecoder(p_camera_feed) {
rgba = p_rgba;
image_data.resize(width * height * (rgba ? 4 : 2));
}

void CopyBufferDecoder::decode(StreamingBuffer p_buffer) {
uint8_t *dst = (uint8_t *)image_data.ptrw();
memcpy(dst, p_buffer.start, p_buffer.length);

if (image.is_valid()) {
image->set_data(width, height, false, rgba ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8, image_data);
} else {
image.instantiate(width, height, false, rgba ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8, image_data);
}

camera_feed->set_RGB_img(image);
}

JpegBufferDecoder::JpegBufferDecoder(CameraFeed *p_camera_feed) :
BufferDecoder(p_camera_feed) {
}

void JpegBufferDecoder::decode(StreamingBuffer p_buffer) {
image_data.resize(p_buffer.length);
uint8_t *dst = (uint8_t *)image_data.ptrw();
memcpy(dst, p_buffer.start, p_buffer.length);
if (image->load_jpg_from_buffer(image_data) == OK) {
camera_feed->set_RGB_img(image);
}
}
116 changes: 116 additions & 0 deletions modules/camera/buffer_decoder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/**************************************************************************/
/* buffer_decoder.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/

#ifndef BUFFER_DECODER_H
#define BUFFER_DECODER_H

#include "core/io/image.h"
#include "core/templates/vector.h"

class CameraFeed;

struct StreamingBuffer {
void *start = nullptr;
size_t length = 0;
};

class BufferDecoder {
protected:
CameraFeed *camera_feed = nullptr;
Ref<Image> image;
int width = 0;
int height = 0;

public:
virtual void decode(StreamingBuffer p_buffer) = 0;

BufferDecoder(CameraFeed *p_camera_feed);
virtual ~BufferDecoder() {}
};

class AbstractYuyvBufferDecoder : public BufferDecoder {
protected:
int *component_indexes = nullptr;

public:
AbstractYuyvBufferDecoder(CameraFeed *p_camera_feed);
~AbstractYuyvBufferDecoder();
};

class SeparateYuyvBufferDecoder : public AbstractYuyvBufferDecoder {
private:
Vector<uint8_t> y_image_data;
Vector<uint8_t> cbcr_image_data;
Ref<Image> y_image;
Ref<Image> cbcr_image;

public:
SeparateYuyvBufferDecoder(CameraFeed *p_camera_feed);
virtual void decode(StreamingBuffer p_buffer) override;
};

class YuyvToGrayscaleBufferDecoder : public AbstractYuyvBufferDecoder {
private:
Vector<uint8_t> image_data;

public:
YuyvToGrayscaleBufferDecoder(CameraFeed *p_camera_feed);
virtual void decode(StreamingBuffer p_buffer) override;
};

class YuyvToRgbBufferDecoder : public AbstractYuyvBufferDecoder {
private:
Vector<uint8_t> image_data;

public:
YuyvToRgbBufferDecoder(CameraFeed *p_camera_feed);
virtual void decode(StreamingBuffer p_buffer) override;
};

class CopyBufferDecoder : public BufferDecoder {
private:
Vector<uint8_t> image_data;
bool rgba = false;

public:
CopyBufferDecoder(CameraFeed *p_camera_feed, bool p_rgba);
virtual void decode(StreamingBuffer p_buffer) override;
};

class JpegBufferDecoder : public BufferDecoder {
private:
Vector<uint8_t> image_data;

public:
JpegBufferDecoder(CameraFeed *p_camera_feed);
virtual void decode(StreamingBuffer p_buffer) override;
};

#endif // BUFFER_DECODER_H
Loading

0 comments on commit 5590b18

Please sign in to comment.