initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
This commit is contained in:
25
modules/camera/SCsub
Normal file
25
modules/camera/SCsub
Normal file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
Import("env_modules")
|
||||
|
||||
env_camera = env_modules.Clone()
|
||||
|
||||
if env["platform"] in ["windows", "macos", "linuxbsd", "android"]:
|
||||
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, "camera_macos.mm")
|
||||
|
||||
elif env["platform"] == "android":
|
||||
env_camera.add_source_files(env.modules_sources, "camera_android.cpp")
|
||||
env.Append(LIBS=["camera2ndk", "mediandk"])
|
||||
|
||||
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
modules/camera/buffer_decoder.cpp
Normal file
212
modules/camera/buffer_decoder.cpp
Normal file
@@ -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_images(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_image(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_image(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_image(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_image(image);
|
||||
}
|
||||
}
|
113
modules/camera/buffer_decoder.h
Normal file
113
modules/camera/buffer_decoder.h
Normal file
@@ -0,0 +1,113 @@
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#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;
|
||||
};
|
510
modules/camera/camera_android.cpp
Normal file
510
modules/camera/camera_android.cpp
Normal file
@@ -0,0 +1,510 @@
|
||||
/**************************************************************************/
|
||||
/* camera_android.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 "camera_android.h"
|
||||
|
||||
#include "core/os/os.h"
|
||||
#include "platform/android/display_server_android.h"
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Helper functions
|
||||
//
|
||||
// The following code enables you to view the contents of a media type while
|
||||
// debugging.
|
||||
|
||||
#ifndef IF_EQUAL_RETURN
|
||||
#define MAKE_FORMAT_CONST(suffix) AIMAGE_FORMAT_##suffix
|
||||
#define IF_EQUAL_RETURN(param, val) \
|
||||
if (MAKE_FORMAT_CONST(val) == param) \
|
||||
return #val
|
||||
#endif
|
||||
|
||||
String GetFormatName(const int32_t &format) {
|
||||
IF_EQUAL_RETURN(format, YUV_420_888);
|
||||
IF_EQUAL_RETURN(format, RGB_888);
|
||||
IF_EQUAL_RETURN(format, RGBA_8888);
|
||||
|
||||
return "Unsupported";
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// CameraFeedAndroid - Subclass for our camera feed on Android
|
||||
|
||||
CameraFeedAndroid::CameraFeedAndroid(ACameraManager *manager, ACameraMetadata *metadata, const char *id,
|
||||
CameraFeed::FeedPosition position, int32_t orientation) :
|
||||
CameraFeed() {
|
||||
this->manager = manager;
|
||||
this->metadata = metadata;
|
||||
this->orientation = orientation;
|
||||
_add_formats();
|
||||
camera_id = id;
|
||||
set_position(position);
|
||||
|
||||
// Position
|
||||
switch (position) {
|
||||
case CameraFeed::FEED_BACK:
|
||||
name = vformat("%s | BACK", id);
|
||||
break;
|
||||
case CameraFeed::FEED_FRONT:
|
||||
name = vformat("%s | FRONT", id);
|
||||
break;
|
||||
default:
|
||||
name = vformat("%s", id);
|
||||
break;
|
||||
}
|
||||
|
||||
image_y.instantiate();
|
||||
image_uv.instantiate();
|
||||
}
|
||||
|
||||
CameraFeedAndroid::~CameraFeedAndroid() {
|
||||
if (is_active()) {
|
||||
deactivate_feed();
|
||||
}
|
||||
if (metadata != nullptr) {
|
||||
ACameraMetadata_free(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
void CameraFeedAndroid::_set_rotation() {
|
||||
int display_rotation = DisplayServerAndroid::get_singleton()->get_display_rotation();
|
||||
// reverse rotation
|
||||
switch (display_rotation) {
|
||||
case 90:
|
||||
display_rotation = 270;
|
||||
break;
|
||||
case 270:
|
||||
display_rotation = 90;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
int sign = position == CameraFeed::FEED_FRONT ? 1 : -1;
|
||||
float imageRotation = (orientation - display_rotation * sign + 360) % 360;
|
||||
transform.set_rotation(real_t(Math::deg_to_rad(imageRotation)));
|
||||
}
|
||||
|
||||
void CameraFeedAndroid::_add_formats() {
|
||||
// Get supported formats
|
||||
ACameraMetadata_const_entry formats;
|
||||
camera_status_t status = ACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &formats);
|
||||
|
||||
if (status == ACAMERA_OK) {
|
||||
for (uint32_t f = 0; f < formats.count; f += 4) {
|
||||
// Only support output streams
|
||||
int32_t input = formats.data.i32[f + 3];
|
||||
if (input) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get format and resolution
|
||||
int32_t format = formats.data.i32[f + 0];
|
||||
if (format == AIMAGE_FORMAT_YUV_420_888 ||
|
||||
format == AIMAGE_FORMAT_RGBA_8888 ||
|
||||
format == AIMAGE_FORMAT_RGB_888) {
|
||||
CameraFeed::FeedFormat feed_format;
|
||||
feed_format.width = formats.data.i32[f + 1];
|
||||
feed_format.height = formats.data.i32[f + 2];
|
||||
feed_format.format = GetFormatName(format);
|
||||
feed_format.pixel_format = format;
|
||||
this->formats.append(feed_format);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CameraFeedAndroid::activate_feed() {
|
||||
ERR_FAIL_COND_V_MSG(selected_format == -1, false, "CameraFeed format needs to be set before activating.");
|
||||
if (is_active()) {
|
||||
deactivate_feed();
|
||||
};
|
||||
|
||||
// Request permission
|
||||
if (!OS::get_singleton()->request_permission("CAMERA")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open device
|
||||
static ACameraDevice_stateCallbacks deviceCallbacks = {
|
||||
.context = this,
|
||||
.onDisconnected = onDisconnected,
|
||||
.onError = onError,
|
||||
};
|
||||
camera_status_t c_status = ACameraManager_openCamera(manager, camera_id.utf8().get_data(), &deviceCallbacks, &device);
|
||||
if (c_status != ACAMERA_OK) {
|
||||
onError(this, device, c_status);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create image reader
|
||||
const FeedFormat &feed_format = formats[selected_format];
|
||||
media_status_t m_status = AImageReader_new(feed_format.width, feed_format.height, feed_format.pixel_format, 1, &reader);
|
||||
if (m_status != AMEDIA_OK) {
|
||||
onError(this, device, m_status);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get image listener
|
||||
static AImageReader_ImageListener listener{
|
||||
.context = this,
|
||||
.onImageAvailable = onImage,
|
||||
};
|
||||
m_status = AImageReader_setImageListener(reader, &listener);
|
||||
if (m_status != AMEDIA_OK) {
|
||||
onError(this, device, m_status);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get image surface
|
||||
ANativeWindow *surface;
|
||||
m_status = AImageReader_getWindow(reader, &surface);
|
||||
if (m_status != AMEDIA_OK) {
|
||||
onError(this, device, m_status);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prepare session outputs
|
||||
ACaptureSessionOutput *output = nullptr;
|
||||
c_status = ACaptureSessionOutput_create(surface, &output);
|
||||
if (c_status != ACAMERA_OK) {
|
||||
onError(this, device, c_status);
|
||||
return false;
|
||||
}
|
||||
|
||||
ACaptureSessionOutputContainer *outputs = nullptr;
|
||||
c_status = ACaptureSessionOutputContainer_create(&outputs);
|
||||
if (c_status != ACAMERA_OK) {
|
||||
onError(this, device, c_status);
|
||||
return false;
|
||||
}
|
||||
|
||||
c_status = ACaptureSessionOutputContainer_add(outputs, output);
|
||||
if (c_status != ACAMERA_OK) {
|
||||
onError(this, device, c_status);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create capture session
|
||||
static ACameraCaptureSession_stateCallbacks sessionStateCallbacks{
|
||||
.context = this,
|
||||
.onClosed = onSessionClosed,
|
||||
.onReady = onSessionReady,
|
||||
.onActive = onSessionActive
|
||||
};
|
||||
c_status = ACameraDevice_createCaptureSession(device, outputs, &sessionStateCallbacks, &session);
|
||||
if (c_status != ACAMERA_OK) {
|
||||
onError(this, device, c_status);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create capture request
|
||||
c_status = ACameraDevice_createCaptureRequest(device, TEMPLATE_PREVIEW, &request);
|
||||
if (c_status != ACAMERA_OK) {
|
||||
onError(this, device, c_status);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set capture target
|
||||
ACameraOutputTarget *imageTarget = nullptr;
|
||||
c_status = ACameraOutputTarget_create(surface, &imageTarget);
|
||||
if (c_status != ACAMERA_OK) {
|
||||
onError(this, device, c_status);
|
||||
return false;
|
||||
}
|
||||
|
||||
c_status = ACaptureRequest_addTarget(request, imageTarget);
|
||||
if (c_status != ACAMERA_OK) {
|
||||
onError(this, device, c_status);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start capture
|
||||
c_status = ACameraCaptureSession_setRepeatingRequest(session, nullptr, 1, &request, nullptr);
|
||||
if (c_status != ACAMERA_OK) {
|
||||
onError(this, device, c_status);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CameraFeedAndroid::set_format(int p_index, const Dictionary &p_parameters) {
|
||||
ERR_FAIL_COND_V_MSG(active, false, "Feed is active.");
|
||||
ERR_FAIL_INDEX_V_MSG(p_index, formats.size(), false, "Invalid format index.");
|
||||
|
||||
selected_format = p_index;
|
||||
return true;
|
||||
}
|
||||
|
||||
Array CameraFeedAndroid::get_formats() const {
|
||||
Array result;
|
||||
for (const FeedFormat &feed_format : formats) {
|
||||
Dictionary dictionary;
|
||||
dictionary["width"] = feed_format.width;
|
||||
dictionary["height"] = feed_format.height;
|
||||
dictionary["format"] = feed_format.format;
|
||||
result.push_back(dictionary);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
CameraFeed::FeedFormat CameraFeedAndroid::get_format() const {
|
||||
CameraFeed::FeedFormat feed_format = {};
|
||||
return selected_format == -1 ? feed_format : formats[selected_format];
|
||||
}
|
||||
|
||||
void CameraFeedAndroid::onImage(void *context, AImageReader *p_reader) {
|
||||
CameraFeedAndroid *feed = static_cast<CameraFeedAndroid *>(context);
|
||||
Vector<uint8_t> data_y = feed->data_y;
|
||||
Vector<uint8_t> data_uv = feed->data_uv;
|
||||
Ref<Image> image_y = feed->image_y;
|
||||
Ref<Image> image_uv = feed->image_uv;
|
||||
|
||||
// Get image
|
||||
AImage *image = nullptr;
|
||||
media_status_t status = AImageReader_acquireNextImage(p_reader, &image);
|
||||
ERR_FAIL_COND(status != AMEDIA_OK);
|
||||
|
||||
// Get image data
|
||||
uint8_t *data = nullptr;
|
||||
int len = 0;
|
||||
int32_t pixel_stride, row_stride;
|
||||
FeedFormat format = feed->get_format();
|
||||
int width = format.width;
|
||||
int height = format.height;
|
||||
switch (format.pixel_format) {
|
||||
case AIMAGE_FORMAT_YUV_420_888:
|
||||
AImage_getPlaneData(image, 0, &data, &len);
|
||||
if (len <= 0) {
|
||||
return;
|
||||
}
|
||||
if (len != data_y.size()) {
|
||||
int64_t size = Image::get_image_data_size(width, height, Image::FORMAT_R8, false);
|
||||
data_y.resize(len > size ? len : size);
|
||||
}
|
||||
memcpy(data_y.ptrw(), data, len);
|
||||
|
||||
AImage_getPlanePixelStride(image, 1, &pixel_stride);
|
||||
AImage_getPlaneRowStride(image, 1, &row_stride);
|
||||
AImage_getPlaneData(image, 1, &data, &len);
|
||||
if (len <= 0) {
|
||||
return;
|
||||
}
|
||||
if (len != data_uv.size()) {
|
||||
int64_t size = Image::get_image_data_size(width / 2, height / 2, Image::FORMAT_RG8, false);
|
||||
data_uv.resize(len > size ? len : size);
|
||||
}
|
||||
memcpy(data_uv.ptrw(), data, len);
|
||||
|
||||
image_y->initialize_data(width, height, false, Image::FORMAT_R8, data_y);
|
||||
image_uv->initialize_data(width / 2, height / 2, false, Image::FORMAT_RG8, data_uv);
|
||||
|
||||
feed->set_ycbcr_images(image_y, image_uv);
|
||||
break;
|
||||
case AIMAGE_FORMAT_RGBA_8888:
|
||||
AImage_getPlaneData(image, 0, &data, &len);
|
||||
if (len <= 0) {
|
||||
return;
|
||||
}
|
||||
if (len != data_y.size()) {
|
||||
int64_t size = Image::get_image_data_size(width, height, Image::FORMAT_RGBA8, false);
|
||||
data_y.resize(len > size ? len : size);
|
||||
}
|
||||
memcpy(data_y.ptrw(), data, len);
|
||||
|
||||
image_y->initialize_data(width, height, false, Image::FORMAT_RGBA8, data_y);
|
||||
|
||||
feed->set_rgb_image(image_y);
|
||||
break;
|
||||
case AIMAGE_FORMAT_RGB_888:
|
||||
AImage_getPlaneData(image, 0, &data, &len);
|
||||
if (len <= 0) {
|
||||
return;
|
||||
}
|
||||
if (len != data_y.size()) {
|
||||
int64_t size = Image::get_image_data_size(width, height, Image::FORMAT_RGB8, false);
|
||||
data_y.resize(len > size ? len : size);
|
||||
}
|
||||
memcpy(data_y.ptrw(), data, len);
|
||||
|
||||
image_y->initialize_data(width, height, false, Image::FORMAT_RGB8, data_y);
|
||||
|
||||
feed->set_rgb_image(image_y);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// Rotation
|
||||
feed->_set_rotation();
|
||||
|
||||
// Release image
|
||||
AImage_delete(image);
|
||||
}
|
||||
|
||||
void CameraFeedAndroid::onSessionReady(void *context, ACameraCaptureSession *session) {
|
||||
print_verbose("Capture session ready");
|
||||
}
|
||||
|
||||
void CameraFeedAndroid::onSessionActive(void *context, ACameraCaptureSession *session) {
|
||||
print_verbose("Capture session active");
|
||||
}
|
||||
|
||||
void CameraFeedAndroid::onSessionClosed(void *context, ACameraCaptureSession *session) {
|
||||
print_verbose("Capture session closed");
|
||||
}
|
||||
|
||||
void CameraFeedAndroid::deactivate_feed() {
|
||||
if (session != nullptr) {
|
||||
ACameraCaptureSession_stopRepeating(session);
|
||||
ACameraCaptureSession_close(session);
|
||||
session = nullptr;
|
||||
}
|
||||
|
||||
if (request != nullptr) {
|
||||
ACaptureRequest_free(request);
|
||||
request = nullptr;
|
||||
}
|
||||
|
||||
if (reader != nullptr) {
|
||||
AImageReader_delete(reader);
|
||||
reader = nullptr;
|
||||
}
|
||||
|
||||
if (device != nullptr) {
|
||||
ACameraDevice_close(device);
|
||||
device = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void CameraFeedAndroid::onError(void *context, ACameraDevice *p_device, int error) {
|
||||
print_error(vformat("Camera error: %d", error));
|
||||
onDisconnected(context, p_device);
|
||||
}
|
||||
|
||||
void CameraFeedAndroid::onDisconnected(void *context, ACameraDevice *p_device) {
|
||||
print_verbose("Camera disconnected");
|
||||
auto *feed = static_cast<CameraFeedAndroid *>(context);
|
||||
feed->set_active(false);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// CameraAndroid - Subclass for our camera server on Android
|
||||
|
||||
void CameraAndroid::update_feeds() {
|
||||
ACameraIdList *cameraIds = nullptr;
|
||||
camera_status_t c_status = ACameraManager_getCameraIdList(cameraManager, &cameraIds);
|
||||
ERR_FAIL_COND(c_status != ACAMERA_OK);
|
||||
|
||||
// remove existing devices
|
||||
for (int i = feeds.size() - 1; i >= 0; i--) {
|
||||
remove_feed(feeds[i]);
|
||||
}
|
||||
|
||||
for (int c = 0; c < cameraIds->numCameras; ++c) {
|
||||
const char *id = cameraIds->cameraIds[c];
|
||||
ACameraMetadata *metadata = nullptr;
|
||||
ACameraManager_getCameraCharacteristics(cameraManager, id, &metadata);
|
||||
if (!metadata) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get sensor orientation
|
||||
ACameraMetadata_const_entry orientation;
|
||||
c_status = ACameraMetadata_getConstEntry(metadata, ACAMERA_SENSOR_ORIENTATION, &orientation);
|
||||
int32_t cameraOrientation;
|
||||
if (c_status == ACAMERA_OK) {
|
||||
cameraOrientation = orientation.data.i32[0];
|
||||
} else {
|
||||
cameraOrientation = 0;
|
||||
print_error(vformat("Unable to get sensor orientation: %s", id));
|
||||
}
|
||||
|
||||
// Get position
|
||||
ACameraMetadata_const_entry lensInfo;
|
||||
CameraFeed::FeedPosition position = CameraFeed::FEED_UNSPECIFIED;
|
||||
camera_status_t status;
|
||||
status = ACameraMetadata_getConstEntry(metadata, ACAMERA_LENS_FACING, &lensInfo);
|
||||
if (status != ACAMERA_OK) {
|
||||
ACameraMetadata_free(metadata);
|
||||
continue;
|
||||
}
|
||||
uint8_t lens_facing = static_cast<acamera_metadata_enum_android_lens_facing_t>(lensInfo.data.u8[0]);
|
||||
if (lens_facing == ACAMERA_LENS_FACING_FRONT) {
|
||||
position = CameraFeed::FEED_FRONT;
|
||||
} else if (lens_facing == ACAMERA_LENS_FACING_BACK) {
|
||||
position = CameraFeed::FEED_BACK;
|
||||
} else {
|
||||
ACameraMetadata_free(metadata);
|
||||
continue;
|
||||
}
|
||||
|
||||
Ref<CameraFeedAndroid> feed = memnew(CameraFeedAndroid(cameraManager, metadata, id, position, cameraOrientation));
|
||||
add_feed(feed);
|
||||
}
|
||||
|
||||
ACameraManager_deleteCameraIdList(cameraIds);
|
||||
emit_signal(SNAME(CameraServer::feeds_updated_signal_name));
|
||||
}
|
||||
|
||||
void CameraAndroid::remove_all_feeds() {
|
||||
// remove existing devices
|
||||
for (int i = feeds.size() - 1; i >= 0; i--) {
|
||||
remove_feed(feeds[i]);
|
||||
}
|
||||
|
||||
if (cameraManager != nullptr) {
|
||||
ACameraManager_delete(cameraManager);
|
||||
cameraManager = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void CameraAndroid::set_monitoring_feeds(bool p_monitoring_feeds) {
|
||||
if (p_monitoring_feeds == monitoring_feeds) {
|
||||
return;
|
||||
}
|
||||
|
||||
CameraServer::set_monitoring_feeds(p_monitoring_feeds);
|
||||
if (p_monitoring_feeds) {
|
||||
if (cameraManager == nullptr) {
|
||||
cameraManager = ACameraManager_create();
|
||||
}
|
||||
|
||||
// Update feeds
|
||||
update_feeds();
|
||||
} else {
|
||||
remove_all_feeds();
|
||||
}
|
||||
}
|
||||
|
||||
CameraAndroid::~CameraAndroid() {
|
||||
remove_all_feeds();
|
||||
}
|
96
modules/camera/camera_android.h
Normal file
96
modules/camera/camera_android.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/**************************************************************************/
|
||||
/* camera_android.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "servers/camera/camera_feed.h"
|
||||
#include "servers/camera_server.h"
|
||||
|
||||
#include <camera/NdkCameraDevice.h>
|
||||
#include <camera/NdkCameraError.h>
|
||||
#include <camera/NdkCameraManager.h>
|
||||
#include <camera/NdkCameraMetadataTags.h>
|
||||
#include <media/NdkImageReader.h>
|
||||
|
||||
class CameraFeedAndroid : public CameraFeed {
|
||||
GDSOFTCLASS(CameraFeedAndroid, CameraFeed);
|
||||
|
||||
private:
|
||||
String camera_id;
|
||||
int32_t orientation;
|
||||
Ref<Image> image_y;
|
||||
Ref<Image> image_uv;
|
||||
Vector<uint8_t> data_y;
|
||||
Vector<uint8_t> data_uv;
|
||||
|
||||
ACameraManager *manager = nullptr;
|
||||
ACameraMetadata *metadata = nullptr;
|
||||
ACameraDevice *device = nullptr;
|
||||
AImageReader *reader = nullptr;
|
||||
ACameraCaptureSession *session = nullptr;
|
||||
ACaptureRequest *request = nullptr;
|
||||
|
||||
void _add_formats();
|
||||
void _set_rotation();
|
||||
|
||||
static void onError(void *context, ACameraDevice *p_device, int error);
|
||||
static void onDisconnected(void *context, ACameraDevice *p_device);
|
||||
static void onImage(void *context, AImageReader *p_reader);
|
||||
static void onSessionReady(void *context, ACameraCaptureSession *session);
|
||||
static void onSessionActive(void *context, ACameraCaptureSession *session);
|
||||
static void onSessionClosed(void *context, ACameraCaptureSession *session);
|
||||
|
||||
protected:
|
||||
public:
|
||||
bool activate_feed() override;
|
||||
void deactivate_feed() override;
|
||||
bool set_format(int p_index, const Dictionary &p_parameters) override;
|
||||
Array get_formats() const override;
|
||||
FeedFormat get_format() const override;
|
||||
|
||||
CameraFeedAndroid(ACameraManager *manager, ACameraMetadata *metadata, const char *id,
|
||||
CameraFeed::FeedPosition position, int32_t orientation);
|
||||
~CameraFeedAndroid() override;
|
||||
};
|
||||
|
||||
class CameraAndroid : public CameraServer {
|
||||
GDSOFTCLASS(CameraAndroid, CameraServer);
|
||||
|
||||
private:
|
||||
ACameraManager *cameraManager = nullptr;
|
||||
|
||||
void update_feeds();
|
||||
void remove_all_feeds();
|
||||
|
||||
public:
|
||||
void set_monitoring_feeds(bool p_monitoring_feeds) override;
|
||||
|
||||
~CameraAndroid();
|
||||
};
|
363
modules/camera/camera_feed_linux.cpp
Normal file
363
modules/camera/camera_feed_linux.cpp
Normal file
@@ -0,0 +1,363 @@
|
||||
/**************************************************************************/
|
||||
/* camera_feed_linux.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 "camera_feed_linux.h"
|
||||
|
||||
#include "servers/rendering_server.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void CameraFeedLinux::update_buffer_thread_func(void *p_func) {
|
||||
if (p_func) {
|
||||
CameraFeedLinux *camera_feed_linux = (CameraFeedLinux *)p_func;
|
||||
camera_feed_linux->_update_buffer();
|
||||
}
|
||||
}
|
||||
|
||||
void CameraFeedLinux::_update_buffer() {
|
||||
while (!exit_flag.is_set()) {
|
||||
_read_frame();
|
||||
usleep(10000);
|
||||
}
|
||||
}
|
||||
|
||||
void CameraFeedLinux::_query_device(const String &p_device_name) {
|
||||
file_descriptor = open(p_device_name.ascii().get_data(), O_RDWR | O_NONBLOCK, 0);
|
||||
ERR_FAIL_COND_MSG(file_descriptor == -1, vformat("Cannot open file descriptor for %s. Error: %d.", p_device_name, errno));
|
||||
|
||||
struct v4l2_capability capability;
|
||||
if (ioctl(file_descriptor, VIDIOC_QUERYCAP, &capability) == -1) {
|
||||
ERR_FAIL_MSG(vformat("Cannot query device. Error: %d.", errno));
|
||||
}
|
||||
name = String((char *)capability.card);
|
||||
|
||||
for (int index = 0;; index++) {
|
||||
struct v4l2_fmtdesc fmtdesc;
|
||||
memset(&fmtdesc, 0, sizeof(fmtdesc));
|
||||
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
fmtdesc.index = index;
|
||||
|
||||
if (ioctl(file_descriptor, VIDIOC_ENUM_FMT, &fmtdesc) == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (int res_index = 0;; res_index++) {
|
||||
struct v4l2_frmsizeenum frmsizeenum;
|
||||
memset(&frmsizeenum, 0, sizeof(frmsizeenum));
|
||||
frmsizeenum.pixel_format = fmtdesc.pixelformat;
|
||||
frmsizeenum.index = res_index;
|
||||
|
||||
if (ioctl(file_descriptor, VIDIOC_ENUM_FRAMESIZES, &frmsizeenum) == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (int framerate_index = 0;; framerate_index++) {
|
||||
struct v4l2_frmivalenum frmivalenum;
|
||||
memset(&frmivalenum, 0, sizeof(frmivalenum));
|
||||
frmivalenum.pixel_format = fmtdesc.pixelformat;
|
||||
frmivalenum.width = frmsizeenum.discrete.width;
|
||||
frmivalenum.height = frmsizeenum.discrete.height;
|
||||
frmivalenum.index = framerate_index;
|
||||
|
||||
if (ioctl(file_descriptor, VIDIOC_ENUM_FRAMEINTERVALS, &frmivalenum) == -1) {
|
||||
if (framerate_index == 0) {
|
||||
_add_format(fmtdesc, frmsizeenum.discrete, -1, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
_add_format(fmtdesc, frmsizeenum.discrete, frmivalenum.discrete.numerator, frmivalenum.discrete.denominator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(file_descriptor);
|
||||
}
|
||||
|
||||
void CameraFeedLinux::_add_format(v4l2_fmtdesc p_description, v4l2_frmsize_discrete p_size, int p_frame_numerator, int p_frame_denominator) {
|
||||
FeedFormat feed_format;
|
||||
feed_format.width = p_size.width;
|
||||
feed_format.height = p_size.height;
|
||||
feed_format.format = String((char *)p_description.description);
|
||||
feed_format.frame_numerator = p_frame_numerator;
|
||||
feed_format.frame_denominator = p_frame_denominator;
|
||||
feed_format.pixel_format = p_description.pixelformat;
|
||||
print_verbose(vformat("%s %dx%d@%d/%dfps", (char *)p_description.description, p_size.width, p_size.height, p_frame_denominator, p_frame_numerator));
|
||||
formats.push_back(feed_format);
|
||||
}
|
||||
|
||||
bool CameraFeedLinux::_request_buffers() {
|
||||
struct v4l2_requestbuffers requestbuffers;
|
||||
|
||||
memset(&requestbuffers, 0, sizeof(requestbuffers));
|
||||
requestbuffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
requestbuffers.memory = V4L2_MEMORY_MMAP;
|
||||
requestbuffers.count = 4;
|
||||
|
||||
if (ioctl(file_descriptor, VIDIOC_REQBUFS, &requestbuffers) == -1) {
|
||||
ERR_FAIL_V_MSG(false, vformat("ioctl(VIDIOC_REQBUFS) error: %d.", errno));
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V_MSG(requestbuffers.count < 2, false, "Not enough buffers granted.");
|
||||
|
||||
buffer_count = requestbuffers.count;
|
||||
buffers = new StreamingBuffer[buffer_count];
|
||||
|
||||
for (unsigned int i = 0; i < buffer_count; i++) {
|
||||
struct v4l2_buffer buffer;
|
||||
|
||||
memset(&buffer, 0, sizeof(buffer));
|
||||
buffer.type = requestbuffers.type;
|
||||
buffer.memory = V4L2_MEMORY_MMAP;
|
||||
buffer.index = i;
|
||||
|
||||
if (ioctl(file_descriptor, VIDIOC_QUERYBUF, &buffer) == -1) {
|
||||
delete[] buffers;
|
||||
ERR_FAIL_V_MSG(false, vformat("ioctl(VIDIOC_QUERYBUF) error: %d.", errno));
|
||||
}
|
||||
|
||||
buffers[i].length = buffer.length;
|
||||
buffers[i].start = mmap(nullptr, buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, file_descriptor, buffer.m.offset);
|
||||
|
||||
if (buffers[i].start == MAP_FAILED) {
|
||||
for (unsigned int b = 0; b < i; b++) {
|
||||
_unmap_buffers(i);
|
||||
}
|
||||
delete[] buffers;
|
||||
ERR_FAIL_V_MSG(false, "Mapping buffers failed.");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CameraFeedLinux::_start_capturing() {
|
||||
for (unsigned int i = 0; i < buffer_count; i++) {
|
||||
struct v4l2_buffer buffer;
|
||||
|
||||
memset(&buffer, 0, sizeof(buffer));
|
||||
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buffer.memory = V4L2_MEMORY_MMAP;
|
||||
buffer.index = i;
|
||||
|
||||
if (ioctl(file_descriptor, VIDIOC_QBUF, &buffer) == -1) {
|
||||
ERR_FAIL_V_MSG(false, vformat("ioctl(VIDIOC_QBUF) error: %d.", errno));
|
||||
}
|
||||
}
|
||||
|
||||
enum v4l2_buf_type type;
|
||||
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
|
||||
if (ioctl(file_descriptor, VIDIOC_STREAMON, &type) == -1) {
|
||||
ERR_FAIL_V_MSG(false, vformat("ioctl(VIDIOC_STREAMON) error: %d.", errno));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CameraFeedLinux::_read_frame() {
|
||||
struct v4l2_buffer buffer;
|
||||
memset(&buffer, 0, sizeof(buffer));
|
||||
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buffer.memory = V4L2_MEMORY_MMAP;
|
||||
|
||||
if (ioctl(file_descriptor, VIDIOC_DQBUF, &buffer) == -1) {
|
||||
if (errno != EAGAIN) {
|
||||
print_error(vformat("ioctl(VIDIOC_DQBUF) error: %d.", errno));
|
||||
exit_flag.set();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
buffer_decoder->decode(buffers[buffer.index]);
|
||||
|
||||
if (ioctl(file_descriptor, VIDIOC_QBUF, &buffer) == -1) {
|
||||
print_error(vformat("ioctl(VIDIOC_QBUF) error: %d.", errno));
|
||||
}
|
||||
}
|
||||
|
||||
void CameraFeedLinux::_stop_capturing() {
|
||||
enum v4l2_buf_type type;
|
||||
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
|
||||
if (ioctl(file_descriptor, VIDIOC_STREAMOFF, &type) == -1) {
|
||||
print_error(vformat("ioctl(VIDIOC_STREAMOFF) error: %d.", errno));
|
||||
}
|
||||
}
|
||||
|
||||
void CameraFeedLinux::_unmap_buffers(unsigned int p_count) {
|
||||
for (unsigned int i = 0; i < p_count; i++) {
|
||||
munmap(buffers[i].start, buffers[i].length);
|
||||
}
|
||||
}
|
||||
|
||||
void CameraFeedLinux::_start_thread() {
|
||||
exit_flag.clear();
|
||||
thread = memnew(Thread);
|
||||
thread->start(CameraFeedLinux::update_buffer_thread_func, this);
|
||||
}
|
||||
|
||||
String CameraFeedLinux::get_device_name() const {
|
||||
return device_name;
|
||||
}
|
||||
|
||||
bool CameraFeedLinux::activate_feed() {
|
||||
ERR_FAIL_COND_V_MSG(selected_format == -1, false, "CameraFeed format needs to be set before activating.");
|
||||
file_descriptor = open(device_name.ascii().get_data(), O_RDWR | O_NONBLOCK, 0);
|
||||
if (_request_buffers() && _start_capturing()) {
|
||||
buffer_decoder = _create_buffer_decoder();
|
||||
_start_thread();
|
||||
return true;
|
||||
}
|
||||
ERR_FAIL_V_MSG(false, "Could not activate feed.");
|
||||
}
|
||||
|
||||
BufferDecoder *CameraFeedLinux::_create_buffer_decoder() {
|
||||
switch (formats[selected_format].pixel_format) {
|
||||
case V4L2_PIX_FMT_MJPEG:
|
||||
case V4L2_PIX_FMT_JPEG:
|
||||
return memnew(JpegBufferDecoder(this));
|
||||
case V4L2_PIX_FMT_YUYV:
|
||||
case V4L2_PIX_FMT_YYUV:
|
||||
case V4L2_PIX_FMT_YVYU:
|
||||
case V4L2_PIX_FMT_UYVY:
|
||||
case V4L2_PIX_FMT_VYUY: {
|
||||
String output = parameters["output"];
|
||||
if (output == "separate") {
|
||||
return memnew(SeparateYuyvBufferDecoder(this));
|
||||
}
|
||||
if (output == "grayscale") {
|
||||
return memnew(YuyvToGrayscaleBufferDecoder(this));
|
||||
}
|
||||
if (output == "copy") {
|
||||
return memnew(CopyBufferDecoder(this, false));
|
||||
}
|
||||
return memnew(YuyvToRgbBufferDecoder(this));
|
||||
}
|
||||
default:
|
||||
return memnew(CopyBufferDecoder(this, true));
|
||||
}
|
||||
}
|
||||
|
||||
void CameraFeedLinux::deactivate_feed() {
|
||||
exit_flag.set();
|
||||
thread->wait_to_finish();
|
||||
memdelete(thread);
|
||||
_stop_capturing();
|
||||
_unmap_buffers(buffer_count);
|
||||
delete[] buffers;
|
||||
memdelete(buffer_decoder);
|
||||
for (int i = 0; i < CameraServer::FEED_IMAGES; i++) {
|
||||
RID placeholder = RenderingServer::get_singleton()->texture_2d_placeholder_create();
|
||||
RenderingServer::get_singleton()->texture_replace(texture[i], placeholder);
|
||||
}
|
||||
base_width = 0;
|
||||
base_height = 0;
|
||||
close(file_descriptor);
|
||||
|
||||
emit_signal(SNAME("format_changed"));
|
||||
}
|
||||
|
||||
Array CameraFeedLinux::get_formats() const {
|
||||
Array result;
|
||||
for (const FeedFormat &format : formats) {
|
||||
Dictionary dictionary;
|
||||
dictionary["width"] = format.width;
|
||||
dictionary["height"] = format.height;
|
||||
dictionary["format"] = format.format;
|
||||
dictionary["frame_numerator"] = format.frame_numerator;
|
||||
dictionary["frame_denominator"] = format.frame_denominator;
|
||||
result.push_back(dictionary);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
CameraFeed::FeedFormat CameraFeedLinux::get_format() const {
|
||||
FeedFormat feed_format = {};
|
||||
return selected_format == -1 ? feed_format : formats[selected_format];
|
||||
}
|
||||
|
||||
bool CameraFeedLinux::set_format(int p_index, const Dictionary &p_parameters) {
|
||||
ERR_FAIL_COND_V_MSG(active, false, "Feed is active.");
|
||||
ERR_FAIL_INDEX_V_MSG(p_index, formats.size(), false, "Invalid format index.");
|
||||
|
||||
FeedFormat feed_format = formats[p_index];
|
||||
|
||||
file_descriptor = open(device_name.ascii().get_data(), O_RDWR | O_NONBLOCK, 0);
|
||||
|
||||
struct v4l2_format format;
|
||||
memset(&format, 0, sizeof(format));
|
||||
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
format.fmt.pix.width = feed_format.width;
|
||||
format.fmt.pix.height = feed_format.height;
|
||||
format.fmt.pix.pixelformat = feed_format.pixel_format;
|
||||
|
||||
if (ioctl(file_descriptor, VIDIOC_S_FMT, &format) == -1) {
|
||||
close(file_descriptor);
|
||||
ERR_FAIL_V_MSG(false, vformat("Cannot set format, error: %d.", errno));
|
||||
}
|
||||
|
||||
if (feed_format.frame_numerator > 0) {
|
||||
struct v4l2_streamparm param;
|
||||
memset(¶m, 0, sizeof(param));
|
||||
|
||||
param.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
param.parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
|
||||
param.parm.capture.timeperframe.numerator = feed_format.frame_numerator;
|
||||
param.parm.capture.timeperframe.denominator = feed_format.frame_denominator;
|
||||
|
||||
if (ioctl(file_descriptor, VIDIOC_S_PARM, ¶m) == -1) {
|
||||
close(file_descriptor);
|
||||
ERR_FAIL_V_MSG(false, vformat("Cannot set framerate, error: %d.", errno));
|
||||
}
|
||||
}
|
||||
close(file_descriptor);
|
||||
|
||||
parameters = p_parameters.duplicate();
|
||||
selected_format = p_index;
|
||||
emit_signal(SNAME("format_changed"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CameraFeedLinux::CameraFeedLinux(const String &p_device_name) :
|
||||
CameraFeed() {
|
||||
device_name = p_device_name;
|
||||
_query_device(device_name);
|
||||
}
|
||||
|
||||
CameraFeedLinux::~CameraFeedLinux() {
|
||||
if (is_active()) {
|
||||
deactivate_feed();
|
||||
}
|
||||
}
|
77
modules/camera/camera_feed_linux.h
Normal file
77
modules/camera/camera_feed_linux.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/**************************************************************************/
|
||||
/* camera_feed_linux.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "buffer_decoder.h"
|
||||
|
||||
#include "core/os/thread.h"
|
||||
#include "servers/camera/camera_feed.h"
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
struct StreamingBuffer;
|
||||
|
||||
class CameraFeedLinux : public CameraFeed {
|
||||
GDSOFTCLASS(CameraFeedLinux, CameraFeed);
|
||||
|
||||
private:
|
||||
SafeFlag exit_flag;
|
||||
Thread *thread = nullptr;
|
||||
String device_name;
|
||||
int file_descriptor = -1;
|
||||
StreamingBuffer *buffers = nullptr;
|
||||
unsigned int buffer_count = 0;
|
||||
BufferDecoder *buffer_decoder = nullptr;
|
||||
|
||||
static void update_buffer_thread_func(void *p_func);
|
||||
|
||||
void _update_buffer();
|
||||
void _query_device(const String &p_device_name);
|
||||
void _add_format(v4l2_fmtdesc description, v4l2_frmsize_discrete size, int frame_numerator, int frame_denominator);
|
||||
bool _request_buffers();
|
||||
bool _start_capturing();
|
||||
void _read_frame();
|
||||
void _stop_capturing();
|
||||
void _unmap_buffers(unsigned int p_count);
|
||||
BufferDecoder *_create_buffer_decoder();
|
||||
void _start_thread();
|
||||
|
||||
public:
|
||||
String get_device_name() const;
|
||||
bool activate_feed() override;
|
||||
void deactivate_feed() override;
|
||||
bool set_format(int p_index, const Dictionary &p_parameters) override;
|
||||
Array get_formats() const override;
|
||||
FeedFormat get_format() const override;
|
||||
|
||||
CameraFeedLinux(const String &p_device_name);
|
||||
~CameraFeedLinux() override;
|
||||
};
|
188
modules/camera/camera_linux.cpp
Normal file
188
modules/camera/camera_linux.cpp
Normal file
@@ -0,0 +1,188 @@
|
||||
/**************************************************************************/
|
||||
/* camera_linux.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 "camera_linux.h"
|
||||
|
||||
#include "camera_feed_linux.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void CameraLinux::camera_thread_func(void *p_camera_linux) {
|
||||
if (p_camera_linux) {
|
||||
CameraLinux *camera_linux = (CameraLinux *)p_camera_linux;
|
||||
camera_linux->_update_devices();
|
||||
}
|
||||
}
|
||||
|
||||
void CameraLinux::_update_devices() {
|
||||
while (!exit_flag.is_set()) {
|
||||
{
|
||||
MutexLock lock(camera_mutex);
|
||||
|
||||
for (int i = feeds.size() - 1; i >= 0; i--) {
|
||||
Ref<CameraFeedLinux> feed = (Ref<CameraFeedLinux>)feeds[i];
|
||||
if (feed.is_null()) {
|
||||
continue;
|
||||
}
|
||||
String device_name = feed->get_device_name();
|
||||
if (!_is_active(device_name)) {
|
||||
remove_feed(feed);
|
||||
}
|
||||
}
|
||||
|
||||
struct dirent **devices;
|
||||
int count = scandir("/dev", &devices, nullptr, alphasort);
|
||||
|
||||
if (count != -1) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
struct dirent *device = devices[i];
|
||||
if (strncmp(device->d_name, "video", 5) == 0) {
|
||||
String device_name = String("/dev/") + String(device->d_name);
|
||||
if (!_has_device(device_name)) {
|
||||
_add_device(device_name);
|
||||
}
|
||||
}
|
||||
free(device);
|
||||
}
|
||||
}
|
||||
|
||||
free(devices);
|
||||
}
|
||||
|
||||
call_deferred("emit_signal", SNAME(CameraServer::feeds_updated_signal_name));
|
||||
usleep(1000000);
|
||||
}
|
||||
}
|
||||
|
||||
bool CameraLinux::_has_device(const String &p_device_name) {
|
||||
for (int i = 0; i < feeds.size(); i++) {
|
||||
Ref<CameraFeedLinux> feed = (Ref<CameraFeedLinux>)feeds[i];
|
||||
if (feed.is_null()) {
|
||||
continue;
|
||||
}
|
||||
if (feed->get_device_name() == p_device_name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CameraLinux::_add_device(const String &p_device_name) {
|
||||
int file_descriptor = _open_device(p_device_name);
|
||||
|
||||
if (file_descriptor != -1) {
|
||||
if (_is_video_capture_device(file_descriptor)) {
|
||||
Ref<CameraFeedLinux> feed = memnew(CameraFeedLinux(p_device_name));
|
||||
add_feed(feed);
|
||||
}
|
||||
}
|
||||
|
||||
close(file_descriptor);
|
||||
}
|
||||
|
||||
int CameraLinux::_open_device(const String &p_device_name) {
|
||||
struct stat s;
|
||||
|
||||
if (stat(p_device_name.ascii().get_data(), &s) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!S_ISCHR(s.st_mode)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return open(p_device_name.ascii().get_data(), O_RDWR | O_NONBLOCK, 0);
|
||||
}
|
||||
|
||||
// TODO any cheaper/cleaner way to check if file descriptor is invalid?
|
||||
bool CameraLinux::_is_active(const String &p_device_name) {
|
||||
struct v4l2_capability capability;
|
||||
bool result = false;
|
||||
int file_descriptor = _open_device(p_device_name);
|
||||
if (file_descriptor != -1 && ioctl(file_descriptor, VIDIOC_QUERYCAP, &capability) != -1) {
|
||||
result = true;
|
||||
}
|
||||
close(file_descriptor);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CameraLinux::_is_video_capture_device(int p_file_descriptor) {
|
||||
struct v4l2_capability capability;
|
||||
|
||||
if (ioctl(p_file_descriptor, VIDIOC_QUERYCAP, &capability) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(capability.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(capability.capabilities & V4L2_CAP_STREAMING)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return _can_query_format(p_file_descriptor, V4L2_BUF_TYPE_VIDEO_CAPTURE);
|
||||
}
|
||||
|
||||
bool CameraLinux::_can_query_format(int p_file_descriptor, int p_type) {
|
||||
struct v4l2_format format;
|
||||
memset(&format, 0, sizeof(format));
|
||||
format.type = p_type;
|
||||
|
||||
return ioctl(p_file_descriptor, VIDIOC_G_FMT, &format) != -1;
|
||||
}
|
||||
|
||||
inline void CameraLinux::set_monitoring_feeds(bool p_monitoring_feeds) {
|
||||
if (p_monitoring_feeds == monitoring_feeds) {
|
||||
return;
|
||||
}
|
||||
|
||||
CameraServer::set_monitoring_feeds(p_monitoring_feeds);
|
||||
if (p_monitoring_feeds) {
|
||||
exit_flag.clear();
|
||||
camera_thread.start(CameraLinux::camera_thread_func, this);
|
||||
} else {
|
||||
exit_flag.set();
|
||||
if (camera_thread.is_started()) {
|
||||
camera_thread.wait_to_finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CameraLinux::~CameraLinux() {
|
||||
exit_flag.set();
|
||||
if (camera_thread.is_started()) {
|
||||
camera_thread.wait_to_finish();
|
||||
}
|
||||
}
|
59
modules/camera/camera_linux.h
Normal file
59
modules/camera/camera_linux.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/**************************************************************************/
|
||||
/* camera_linux.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/os/mutex.h"
|
||||
#include "core/os/thread.h"
|
||||
#include "servers/camera_server.h"
|
||||
|
||||
class CameraLinux : public CameraServer {
|
||||
private:
|
||||
SafeFlag exit_flag;
|
||||
Thread camera_thread;
|
||||
Mutex camera_mutex;
|
||||
|
||||
static void camera_thread_func(void *p_camera_linux);
|
||||
|
||||
void _update_devices();
|
||||
bool _has_device(const String &p_device_name);
|
||||
void _add_device(const String &p_device_name);
|
||||
void _remove_device(const String &p_device_name);
|
||||
int _open_device(const String &p_device_name);
|
||||
bool _is_active(const String &p_device_name);
|
||||
bool _is_video_capture_device(int p_file_descriptor);
|
||||
bool _can_query_format(int p_file_descriptor, int p_type);
|
||||
|
||||
public:
|
||||
CameraLinux() = default;
|
||||
~CameraLinux();
|
||||
|
||||
void set_monitoring_feeds(bool p_monitoring_feeds) override;
|
||||
};
|
46
modules/camera/camera_macos.h
Normal file
46
modules/camera/camera_macos.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/**************************************************************************/
|
||||
/* camera_macos.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
///@TODO this is a near duplicate of CameraIOS, we should find a way to combine those to minimize code duplication!!!!
|
||||
// If you fix something here, make sure you fix it there as well!
|
||||
|
||||
#include "servers/camera_server.h"
|
||||
|
||||
class CameraMacOS : public CameraServer {
|
||||
GDSOFTCLASS(CameraMacOS, CameraServer);
|
||||
|
||||
public:
|
||||
CameraMacOS() = default;
|
||||
|
||||
void update_feeds();
|
||||
void set_monitoring_feeds(bool p_monitoring_feeds) override;
|
||||
};
|
385
modules/camera/camera_macos.mm
Normal file
385
modules/camera/camera_macos.mm
Normal file
@@ -0,0 +1,385 @@
|
||||
/**************************************************************************/
|
||||
/* camera_macos.mm */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
///@TODO this is a near duplicate of CameraIOS, we should find a way to combine those to minimize code duplication!!!!
|
||||
// If you fix something here, make sure you fix it there as well!
|
||||
|
||||
#import "camera_macos.h"
|
||||
|
||||
#include "servers/camera/camera_feed.h"
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// MyCaptureSession - This is a little helper class so we can capture our frames
|
||||
|
||||
@interface MyCaptureSession : AVCaptureSession <AVCaptureVideoDataOutputSampleBufferDelegate> {
|
||||
Ref<CameraFeed> feed;
|
||||
size_t width[2];
|
||||
size_t height[2];
|
||||
Vector<uint8_t> img_data[2];
|
||||
|
||||
AVCaptureDeviceInput *input;
|
||||
AVCaptureVideoDataOutput *output;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MyCaptureSession
|
||||
|
||||
- (id)initForFeed:(Ref<CameraFeed>)p_feed andDevice:(AVCaptureDevice *)p_device {
|
||||
if (self = [super init]) {
|
||||
NSError *error;
|
||||
feed = p_feed;
|
||||
width[0] = 0;
|
||||
height[0] = 0;
|
||||
width[1] = 0;
|
||||
height[1] = 0;
|
||||
|
||||
[self beginConfiguration];
|
||||
|
||||
input = [AVCaptureDeviceInput deviceInputWithDevice:p_device error:&error];
|
||||
if (!input) {
|
||||
print_line("Couldn't get input device for camera");
|
||||
} else {
|
||||
[self addInput:input];
|
||||
}
|
||||
|
||||
output = [AVCaptureVideoDataOutput new];
|
||||
if (!output) {
|
||||
print_line("Couldn't get output device for camera");
|
||||
} else {
|
||||
NSDictionary *settings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) };
|
||||
output.videoSettings = settings;
|
||||
|
||||
// discard if the data output queue is blocked (as we process the still image)
|
||||
[output setAlwaysDiscardsLateVideoFrames:YES];
|
||||
|
||||
// now set ourselves as the delegate to receive new frames.
|
||||
[output setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
|
||||
|
||||
// this takes ownership
|
||||
[self addOutput:output];
|
||||
}
|
||||
|
||||
[self commitConfiguration];
|
||||
|
||||
// kick off our session..
|
||||
[self startRunning];
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)cleanup {
|
||||
// stop running
|
||||
[self stopRunning];
|
||||
|
||||
// cleanup
|
||||
[self beginConfiguration];
|
||||
|
||||
// remove input
|
||||
if (input) {
|
||||
[self removeInput:input];
|
||||
// don't release this
|
||||
input = nullptr;
|
||||
}
|
||||
|
||||
// free up our output
|
||||
if (output) {
|
||||
[self removeOutput:output];
|
||||
[output setSampleBufferDelegate:nil queue:nullptr];
|
||||
output = nullptr;
|
||||
}
|
||||
|
||||
[self commitConfiguration];
|
||||
}
|
||||
|
||||
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
|
||||
// This gets called every time our camera has a new image for us to process.
|
||||
// May need to investigate in a way to throttle this if we get more images then we're rendering frames..
|
||||
|
||||
// For now, version 1, we're just doing the bare minimum to make this work...
|
||||
CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
|
||||
// int _width = CVPixelBufferGetWidth(pixelBuffer);
|
||||
// int _height = CVPixelBufferGetHeight(pixelBuffer);
|
||||
|
||||
// It says that we need to lock this on the documentation pages but it's not in the samples
|
||||
// need to lock our base address so we can access our pixel buffers, better safe then sorry?
|
||||
CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||
|
||||
// get our buffers
|
||||
unsigned char *dataY = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
|
||||
unsigned char *dataCbCr = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
|
||||
if (dataY == nullptr) {
|
||||
print_line("Couldn't access Y pixel buffer data");
|
||||
} else if (dataCbCr == nullptr) {
|
||||
print_line("Couldn't access CbCr pixel buffer data");
|
||||
} else {
|
||||
Ref<Image> img[2];
|
||||
|
||||
{
|
||||
// do Y
|
||||
size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
|
||||
size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
|
||||
|
||||
if ((width[0] != new_width) || (height[0] != new_height)) {
|
||||
width[0] = new_width;
|
||||
height[0] = new_height;
|
||||
img_data[0].resize(new_width * new_height);
|
||||
}
|
||||
|
||||
uint8_t *w = img_data[0].ptrw();
|
||||
memcpy(w, dataY, new_width * new_height);
|
||||
|
||||
img[0].instantiate();
|
||||
img[0]->set_data(new_width, new_height, 0, Image::FORMAT_R8, img_data[0]);
|
||||
}
|
||||
|
||||
{
|
||||
// do CbCr
|
||||
size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
|
||||
size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
|
||||
|
||||
if ((width[1] != new_width) || (height[1] != new_height)) {
|
||||
width[1] = new_width;
|
||||
height[1] = new_height;
|
||||
img_data[1].resize(2 * new_width * new_height);
|
||||
}
|
||||
|
||||
uint8_t *w = img_data[1].ptrw();
|
||||
memcpy(w, dataCbCr, 2 * new_width * new_height);
|
||||
|
||||
///TODO OpenGL doesn't support FORMAT_RG8, need to do some form of conversion
|
||||
img[1].instantiate();
|
||||
img[1]->set_data(new_width, new_height, 0, Image::FORMAT_RG8, img_data[1]);
|
||||
}
|
||||
|
||||
// set our texture...
|
||||
feed->set_ycbcr_images(img[0], img[1]);
|
||||
}
|
||||
|
||||
// and unlock
|
||||
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// CameraFeedMacOS - Subclass for camera feeds in macOS
|
||||
|
||||
class CameraFeedMacOS : public CameraFeed {
|
||||
GDSOFTCLASS(CameraFeedMacOS, CameraFeed);
|
||||
|
||||
private:
|
||||
AVCaptureDevice *device;
|
||||
MyCaptureSession *capture_session;
|
||||
|
||||
public:
|
||||
AVCaptureDevice *get_device() const;
|
||||
|
||||
CameraFeedMacOS();
|
||||
|
||||
void set_device(AVCaptureDevice *p_device);
|
||||
|
||||
bool activate_feed() override;
|
||||
void deactivate_feed() override;
|
||||
};
|
||||
|
||||
AVCaptureDevice *CameraFeedMacOS::get_device() const {
|
||||
return device;
|
||||
}
|
||||
|
||||
CameraFeedMacOS::CameraFeedMacOS() {
|
||||
device = nullptr;
|
||||
capture_session = nullptr;
|
||||
}
|
||||
|
||||
void CameraFeedMacOS::set_device(AVCaptureDevice *p_device) {
|
||||
device = p_device;
|
||||
|
||||
// get some info
|
||||
NSString *device_name = p_device.localizedName;
|
||||
name = String::utf8(device_name.UTF8String);
|
||||
position = CameraFeed::FEED_UNSPECIFIED;
|
||||
if ([p_device position] == AVCaptureDevicePositionBack) {
|
||||
position = CameraFeed::FEED_BACK;
|
||||
} else if ([p_device position] == AVCaptureDevicePositionFront) {
|
||||
position = CameraFeed::FEED_FRONT;
|
||||
};
|
||||
}
|
||||
|
||||
bool CameraFeedMacOS::activate_feed() {
|
||||
if (capture_session) {
|
||||
// Already recording!
|
||||
} else {
|
||||
// Start camera capture, check permission.
|
||||
if (@available(macOS 10.14, *)) {
|
||||
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
|
||||
if (status == AVAuthorizationStatusAuthorized) {
|
||||
capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device];
|
||||
} else if (status == AVAuthorizationStatusNotDetermined) {
|
||||
// Request permission.
|
||||
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo
|
||||
completionHandler:^(BOOL granted) {
|
||||
if (granted) {
|
||||
capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device];
|
||||
}
|
||||
}];
|
||||
}
|
||||
} else {
|
||||
capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device];
|
||||
}
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CameraFeedMacOS::deactivate_feed() {
|
||||
// end camera capture if we have one
|
||||
if (capture_session) {
|
||||
[capture_session cleanup];
|
||||
capture_session = nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// MyDeviceNotifications - This is a little helper class gets notifications
|
||||
// when devices are connected/disconnected
|
||||
|
||||
@interface MyDeviceNotifications : NSObject {
|
||||
CameraMacOS *camera_server;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MyDeviceNotifications
|
||||
|
||||
- (void)devices_changed:(NSNotification *)notification {
|
||||
camera_server->update_feeds();
|
||||
}
|
||||
|
||||
- (id)initForServer:(CameraMacOS *)p_server {
|
||||
if (self = [super init]) {
|
||||
camera_server = p_server;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasConnectedNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasDisconnectedNotification object:nil];
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
// remove notifications
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasConnectedNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasDisconnectedNotification object:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
MyDeviceNotifications *device_notifications = nil;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// CameraMacOS - Subclass for our camera server on macOS
|
||||
|
||||
void CameraMacOS::update_feeds() {
|
||||
NSArray<AVCaptureDevice *> *devices = nullptr;
|
||||
#if defined(__x86_64__)
|
||||
if (@available(macOS 10.15, *)) {
|
||||
#endif
|
||||
AVCaptureDeviceDiscoverySession *session;
|
||||
if (@available(macOS 14.0, *)) {
|
||||
session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeExternal, AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeContinuityCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified];
|
||||
} else {
|
||||
session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeExternalUnknown, AVCaptureDeviceTypeBuiltInWideAngleCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified];
|
||||
}
|
||||
devices = session.devices;
|
||||
#if defined(__x86_64__)
|
||||
} else {
|
||||
devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
|
||||
}
|
||||
#endif
|
||||
|
||||
// remove devices that are gone..
|
||||
for (int i = feeds.size() - 1; i >= 0; i--) {
|
||||
Ref<CameraFeedMacOS> feed = (Ref<CameraFeedMacOS>)feeds[i];
|
||||
if (feed.is_null()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (![devices containsObject:feed->get_device()]) {
|
||||
// remove it from our array, this will also destroy it ;)
|
||||
remove_feed(feed);
|
||||
};
|
||||
};
|
||||
|
||||
for (AVCaptureDevice *device in devices) {
|
||||
bool found = false;
|
||||
for (int i = 0; i < feeds.size() && !found; i++) {
|
||||
Ref<CameraFeedMacOS> feed = (Ref<CameraFeedMacOS>)feeds[i];
|
||||
if (feed.is_null()) {
|
||||
continue;
|
||||
}
|
||||
if (feed->get_device() == device) {
|
||||
found = true;
|
||||
};
|
||||
};
|
||||
|
||||
if (!found) {
|
||||
Ref<CameraFeedMacOS> newfeed;
|
||||
newfeed.instantiate();
|
||||
newfeed->set_device(device);
|
||||
|
||||
// assume display camera so inverse
|
||||
Transform2D transform = Transform2D(-1.0, 0.0, 0.0, -1.0, 1.0, 1.0);
|
||||
newfeed->set_transform(transform);
|
||||
|
||||
add_feed(newfeed);
|
||||
};
|
||||
};
|
||||
emit_signal(SNAME(CameraServer::feeds_updated_signal_name));
|
||||
}
|
||||
|
||||
void CameraMacOS::set_monitoring_feeds(bool p_monitoring_feeds) {
|
||||
if (p_monitoring_feeds == monitoring_feeds) {
|
||||
return;
|
||||
}
|
||||
|
||||
CameraServer::set_monitoring_feeds(p_monitoring_feeds);
|
||||
if (p_monitoring_feeds) {
|
||||
// Find available cameras we have at this time.
|
||||
update_feeds();
|
||||
|
||||
// Get notified on feed changes.
|
||||
device_notifications = [[MyDeviceNotifications alloc] initForServer:this];
|
||||
} else {
|
||||
// Stop monitoring feed changes.
|
||||
device_notifications = nil;
|
||||
}
|
||||
}
|
94
modules/camera/camera_win.cpp
Normal file
94
modules/camera/camera_win.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
/**************************************************************************/
|
||||
/* camera_win.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 "camera_win.h"
|
||||
|
||||
///@TODO sorry guys, I got about 80% through implementing this using DirectShow only
|
||||
// to find out Microsoft deprecated half the API and its replacement is as confusing
|
||||
// as they could make it. Joey suggested looking into libuvc which offers a more direct
|
||||
// route to webcams over USB and this is very promising but it wouldn't compile on
|
||||
// windows for me...I've gutted the classes I implemented DirectShow in just to have
|
||||
// a skeleton for someone to work on, mail me for more details or if you want a copy....
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// CameraFeedWindows - Subclass for our camera feed on windows
|
||||
|
||||
/// @TODO need to implement this
|
||||
|
||||
class CameraFeedWindows : public CameraFeed {
|
||||
private:
|
||||
protected:
|
||||
public:
|
||||
CameraFeedWindows();
|
||||
virtual ~CameraFeedWindows();
|
||||
|
||||
bool activate_feed();
|
||||
void deactivate_feed();
|
||||
};
|
||||
|
||||
CameraFeedWindows::CameraFeedWindows() {
|
||||
///@TODO implement this, should store information about our available camera
|
||||
}
|
||||
|
||||
CameraFeedWindows::~CameraFeedWindows() {
|
||||
// make sure we stop recording if we are!
|
||||
if (is_active()) {
|
||||
deactivate_feed();
|
||||
};
|
||||
|
||||
///@TODO free up anything used by this
|
||||
}
|
||||
|
||||
bool CameraFeedWindows::activate_feed() {
|
||||
///@TODO this should activate our camera and start the process of capturing frames
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///@TODO we should probably have a callback method here that is being called by the
|
||||
// camera API which provides frames and call back into the CameraServer to update our texture
|
||||
|
||||
void CameraFeedWindows::deactivate_feed() {
|
||||
///@TODO this should deactivate our camera and stop the process of capturing frames
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// CameraWindows - Subclass for our camera server on windows
|
||||
|
||||
void CameraWindows::add_active_cameras() {
|
||||
///@TODO scan through any active cameras and create CameraFeedWindows objects for them
|
||||
}
|
||||
|
||||
CameraWindows::CameraWindows() {
|
||||
// Find cameras active right now
|
||||
add_active_cameras();
|
||||
|
||||
// need to add something that will react to devices being connected/removed...
|
||||
}
|
43
modules/camera/camera_win.h
Normal file
43
modules/camera/camera_win.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/**************************************************************************/
|
||||
/* camera_win.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "servers/camera/camera_feed.h"
|
||||
#include "servers/camera_server.h"
|
||||
|
||||
class CameraWindows : public CameraServer {
|
||||
private:
|
||||
void add_active_cameras();
|
||||
|
||||
public:
|
||||
CameraWindows();
|
||||
~CameraWindows() {}
|
||||
};
|
10
modules/camera/config.py
Normal file
10
modules/camera/config.py
Normal file
@@ -0,0 +1,10 @@
|
||||
def can_build(env, platform):
|
||||
import sys
|
||||
|
||||
if sys.platform.startswith("freebsd") or sys.platform.startswith("openbsd"):
|
||||
return False
|
||||
return platform == "macos" or platform == "windows" or platform == "linuxbsd" or platform == "android"
|
||||
|
||||
|
||||
def configure(env):
|
||||
pass
|
69
modules/camera/register_types.cpp
Normal file
69
modules/camera/register_types.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
/**************************************************************************/
|
||||
/* register_types.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 "register_types.h"
|
||||
|
||||
#if defined(LINUXBSD_ENABLED)
|
||||
#include "camera_linux.h"
|
||||
#endif
|
||||
#if defined(WINDOWS_ENABLED)
|
||||
#include "camera_win.h"
|
||||
#endif
|
||||
#if defined(MACOS_ENABLED)
|
||||
#include "camera_macos.h"
|
||||
#endif
|
||||
#if defined(ANDROID_ENABLED)
|
||||
#include "camera_android.h"
|
||||
#endif
|
||||
|
||||
void initialize_camera_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(LINUXBSD_ENABLED)
|
||||
CameraServer::make_default<CameraLinux>();
|
||||
#endif
|
||||
#if defined(WINDOWS_ENABLED)
|
||||
CameraServer::make_default<CameraWindows>();
|
||||
#endif
|
||||
#if defined(MACOS_ENABLED)
|
||||
CameraServer::make_default<CameraMacOS>();
|
||||
#endif
|
||||
#if defined(ANDROID_ENABLED)
|
||||
CameraServer::make_default<CameraAndroid>();
|
||||
#endif
|
||||
}
|
||||
|
||||
void uninitialize_camera_module(ModuleInitializationLevel p_level) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
}
|
36
modules/camera/register_types.h
Normal file
36
modules/camera/register_types.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/**************************************************************************/
|
||||
/* register_types.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "modules/register_module_types.h"
|
||||
|
||||
void initialize_camera_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_camera_module(ModuleInitializationLevel p_level);
|
Reference in New Issue
Block a user