mirror of
https://github.com/godotengine/godot.git
synced 2025-10-20 00:13:30 +00:00
Add CameraFeed support for Android
Co-authored-by: KOGA Mitsuhiro <shiena.jp@gmail.com>
This commit is contained in:
parent
209a446e36
commit
296ca79b9d
12 changed files with 666 additions and 2 deletions
|
@ -6,7 +6,7 @@ Import("env_modules")
|
||||||
|
|
||||||
env_camera = env_modules.Clone()
|
env_camera = env_modules.Clone()
|
||||||
|
|
||||||
if env["platform"] in ["windows", "macos", "linuxbsd"]:
|
if env["platform"] in ["windows", "macos", "linuxbsd", "android"]:
|
||||||
env_camera.add_source_files(env.modules_sources, "register_types.cpp")
|
env_camera.add_source_files(env.modules_sources, "register_types.cpp")
|
||||||
|
|
||||||
if env["platform"] == "windows":
|
if env["platform"] == "windows":
|
||||||
|
@ -15,6 +15,10 @@ if env["platform"] == "windows":
|
||||||
elif env["platform"] == "macos":
|
elif env["platform"] == "macos":
|
||||||
env_camera.add_source_files(env.modules_sources, "camera_macos.mm")
|
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":
|
elif env["platform"] == "linuxbsd":
|
||||||
env_camera.add_source_files(env.modules_sources, "camera_linux.cpp")
|
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, "camera_feed_linux.cpp")
|
||||||
|
|
511
modules/camera/camera_android.cpp
Normal file
511
modules/camera/camera_android.cpp
Normal file
|
@ -0,0 +1,511 @@
|
||||||
|
/**************************************************************************/
|
||||||
|
/* 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);
|
||||||
|
|
||||||
|
feed->emit_signal(SNAME("frame_changed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
};
|
|
@ -3,7 +3,7 @@ def can_build(env, platform):
|
||||||
|
|
||||||
if sys.platform.startswith("freebsd"):
|
if sys.platform.startswith("freebsd"):
|
||||||
return False
|
return False
|
||||||
return platform == "macos" or platform == "windows" or platform == "linuxbsd"
|
return platform == "macos" or platform == "windows" or platform == "linuxbsd" or platform == "android"
|
||||||
|
|
||||||
|
|
||||||
def configure(env):
|
def configure(env):
|
||||||
|
|
|
@ -39,6 +39,9 @@
|
||||||
#if defined(MACOS_ENABLED)
|
#if defined(MACOS_ENABLED)
|
||||||
#include "camera_macos.h"
|
#include "camera_macos.h"
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(ANDROID_ENABLED)
|
||||||
|
#include "camera_android.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
void initialize_camera_module(ModuleInitializationLevel p_level) {
|
void initialize_camera_module(ModuleInitializationLevel p_level) {
|
||||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||||
|
@ -54,6 +57,9 @@ void initialize_camera_module(ModuleInitializationLevel p_level) {
|
||||||
#if defined(MACOS_ENABLED)
|
#if defined(MACOS_ENABLED)
|
||||||
CameraServer::make_default<CameraMacOS>();
|
CameraServer::make_default<CameraMacOS>();
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(ANDROID_ENABLED)
|
||||||
|
CameraServer::make_default<CameraAndroid>();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void uninitialize_camera_module(ModuleInitializationLevel p_level) {
|
void uninitialize_camera_module(ModuleInitializationLevel p_level) {
|
||||||
|
|
|
@ -279,6 +279,13 @@ DisplayServer::ScreenOrientation DisplayServerAndroid::screen_get_orientation(in
|
||||||
return (ScreenOrientation)orientation;
|
return (ScreenOrientation)orientation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int DisplayServerAndroid::get_display_rotation() const {
|
||||||
|
GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
|
||||||
|
ERR_FAIL_NULL_V(godot_io_java, 0);
|
||||||
|
|
||||||
|
return godot_io_java->get_display_rotation();
|
||||||
|
}
|
||||||
|
|
||||||
int DisplayServerAndroid::get_screen_count() const {
|
int DisplayServerAndroid::get_screen_count() const {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,6 +144,7 @@ public:
|
||||||
|
|
||||||
virtual void screen_set_orientation(ScreenOrientation p_orientation, int p_screen = SCREEN_OF_MAIN_WINDOW) override;
|
virtual void screen_set_orientation(ScreenOrientation p_orientation, int p_screen = SCREEN_OF_MAIN_WINDOW) override;
|
||||||
virtual ScreenOrientation screen_get_orientation(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
|
virtual ScreenOrientation screen_get_orientation(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
|
||||||
|
int get_display_rotation() const;
|
||||||
|
|
||||||
virtual int get_screen_count() const override;
|
virtual int get_screen_count() const override;
|
||||||
virtual int get_primary_screen() const override;
|
virtual int get_primary_screen() const override;
|
||||||
|
|
|
@ -37,6 +37,12 @@
|
||||||
<!-- Temp removal of the 'REQUEST_INSTALL_PACKAGES' permission as it's currently forbidden by the Horizon OS store -->
|
<!-- Temp removal of the 'REQUEST_INSTALL_PACKAGES' permission as it's currently forbidden by the Horizon OS store -->
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" tools:node="remove" />
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" tools:node="remove" />
|
||||||
|
|
||||||
|
<!-- Passthrough feature -->
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.camera2.any"
|
||||||
|
android:required="false"/>
|
||||||
|
<uses-permission android:name="horizonos.permission.HEADSET_CAMERA"/>
|
||||||
|
|
||||||
<application>
|
<application>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
|
|
|
@ -12,6 +12,9 @@
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:glEsVersion="0x00030000"
|
android:glEsVersion="0x00030000"
|
||||||
android:required="true" />
|
android:required="true" />
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.camera"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||||
|
@ -26,6 +29,7 @@
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.godotengine.godot.error.Error;
|
||||||
import org.godotengine.godot.input.GodotEditText;
|
import org.godotengine.godot.input.GodotEditText;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.ActivityInfo;
|
import android.content.pm.ActivityInfo;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
@ -46,7 +47,9 @@ import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Display;
|
import android.view.Display;
|
||||||
import android.view.DisplayCutout;
|
import android.view.DisplayCutout;
|
||||||
|
import android.view.Surface;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
import androidx.core.content.FileProvider;
|
import androidx.core.content.FileProvider;
|
||||||
|
|
||||||
|
@ -306,6 +309,19 @@ public class GodotIO {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getDisplayRotation() {
|
||||||
|
WindowManager windowManager = (WindowManager)activity.getSystemService(Context.WINDOW_SERVICE);
|
||||||
|
int rotation = windowManager.getDefaultDisplay().getRotation();
|
||||||
|
if (rotation == Surface.ROTATION_90) {
|
||||||
|
return 90;
|
||||||
|
} else if (rotation == Surface.ROTATION_180) {
|
||||||
|
return 180;
|
||||||
|
} else if (rotation == Surface.ROTATION_270) {
|
||||||
|
return 270;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
public void setEdit(GodotEditText _edit) {
|
public void setEdit(GodotEditText _edit) {
|
||||||
edit = _edit;
|
edit = _edit;
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
|
||||||
_set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V");
|
_set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V");
|
||||||
_get_screen_orientation = p_env->GetMethodID(cls, "getScreenOrientation", "()I");
|
_get_screen_orientation = p_env->GetMethodID(cls, "getScreenOrientation", "()I");
|
||||||
_get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(IZ)Ljava/lang/String;");
|
_get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(IZ)Ljava/lang/String;");
|
||||||
|
_get_display_rotation = p_env->GetMethodID(cls, "getDisplayRotation", "()I");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,6 +290,16 @@ String GodotIOJavaWrapper::get_system_dir(int p_dir, bool p_shared_storage) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int GodotIOJavaWrapper::get_display_rotation() {
|
||||||
|
if (_get_display_rotation) {
|
||||||
|
JNIEnv *env = get_jni_env();
|
||||||
|
ERR_FAIL_NULL_V(env, 0);
|
||||||
|
return env->CallIntMethod(godot_io_instance, _get_display_rotation);
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SafeNumeric because it can be changed from non-main thread and we need to
|
// SafeNumeric because it can be changed from non-main thread and we need to
|
||||||
// ensure the change is immediately visible to other threads.
|
// ensure the change is immediately visible to other threads.
|
||||||
static SafeNumeric<int> virtual_keyboard_height;
|
static SafeNumeric<int> virtual_keyboard_height;
|
||||||
|
|
|
@ -49,6 +49,7 @@ private:
|
||||||
jmethodID _get_data_dir = 0;
|
jmethodID _get_data_dir = 0;
|
||||||
jmethodID _get_temp_dir = 0;
|
jmethodID _get_temp_dir = 0;
|
||||||
jmethodID _get_display_cutouts = 0;
|
jmethodID _get_display_cutouts = 0;
|
||||||
|
jmethodID _get_display_rotation = 0;
|
||||||
jmethodID _get_display_safe_area = 0;
|
jmethodID _get_display_safe_area = 0;
|
||||||
jmethodID _get_locale = 0;
|
jmethodID _get_locale = 0;
|
||||||
jmethodID _get_model = 0;
|
jmethodID _get_model = 0;
|
||||||
|
@ -90,4 +91,5 @@ public:
|
||||||
void set_screen_orientation(int p_orient);
|
void set_screen_orientation(int p_orient);
|
||||||
int get_screen_orientation();
|
int get_screen_orientation();
|
||||||
String get_system_dir(int p_dir, bool p_shared_storage);
|
String get_system_dir(int p_dir, bool p_shared_storage);
|
||||||
|
int get_display_rotation();
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue