1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-04 12:00:25 +00:00

Merge pull request #111871 from shiena/fix/camera_android.2

Android: Stabilize camera lifecycle handling
This commit is contained in:
Thaddeus Crews
2025-10-23 11:03:58 -05:00
6 changed files with 284 additions and 29 deletions

View File

@@ -32,6 +32,8 @@
#include "core/os/os.h"
#include "platform/android/display_server_android.h"
#include "platform/android/java_godot_io_wrapper.h"
#include "platform/android/os_android.h"
//////////////////////////////////////////////////////////////////////////
// Helper functions
@@ -93,23 +95,75 @@ CameraFeedAndroid::~CameraFeedAndroid() {
}
}
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;
void CameraFeedAndroid::refresh_camera_metadata() {
ERR_FAIL_NULL_MSG(manager, vformat("Camera %s: Cannot refresh metadata, manager is null.", camera_id));
if (metadata != nullptr) {
ACameraMetadata_free(metadata);
metadata = nullptr;
}
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)));
camera_status_t status = ACameraManager_getCameraCharacteristics(manager, camera_id.utf8().get_data(), &metadata);
if (status != ACAMERA_OK || metadata == nullptr) {
ERR_FAIL_MSG(vformat("Camera %s: Failed to refresh metadata (status: %d).", camera_id, status));
}
ACameraMetadata_const_entry orientation_entry;
status = ACameraMetadata_getConstEntry(metadata, ACAMERA_SENSOR_ORIENTATION, &orientation_entry);
if (status == ACAMERA_OK) {
orientation = orientation_entry.data.i32[0];
print_verbose(vformat("Camera %s: Orientation updated to %d.", camera_id, orientation));
} else {
ERR_PRINT(vformat("Camera %s: Failed to get sensor orientation after refresh (status: %d).", camera_id, status));
}
formats.clear();
_add_formats();
print_verbose(vformat("Camera %s: Metadata refreshed successfully.", camera_id));
}
void CameraFeedAndroid::_set_rotation() {
if (!metadata) {
print_verbose(vformat("Camera %s: Metadata is null in _set_rotation, attempting refresh.", camera_id));
refresh_camera_metadata();
}
float image_rotation = 0.0f;
std::optional<int> result;
if (metadata) {
CameraRotationParams params;
params.sensor_orientation = orientation;
params.camera_facing = (position == CameraFeed::FEED_FRONT) ? CameraFacing::FRONT : CameraFacing::BACK;
params.display_rotation = get_app_orientation();
result = calculate_rotation(params);
} else {
ERR_PRINT(vformat("Camera %s: Cannot update rotation, metadata unavailable after refresh, using fallback.", camera_id));
}
if (result.has_value()) {
image_rotation = static_cast<float>(result.value());
} else {
int display_rotation = DisplayServerAndroid::get_singleton()->get_display_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;
image_rotation = (orientation - display_rotation * sign + 360) % 360;
}
transform = Transform2D();
transform = transform.rotated(Math::deg_to_rad(image_rotation));
}
void CameraFeedAndroid::_add_formats() {
@@ -142,7 +196,9 @@ void CameraFeedAndroid::_add_formats() {
}
bool CameraFeedAndroid::activate_feed() {
ERR_FAIL_COND_V_MSG(selected_format == -1, false, "CameraFeed format needs to be set before activating.");
ERR_FAIL_COND_V_MSG(formats.is_empty(), false, "No camera formats available.");
ERR_FAIL_INDEX_V_MSG(selected_format, formats.size(), false,
vformat("CameraFeed format needs to be set before activating. Selected format index: %d (formats size: %d)", selected_format, formats.size()));
if (is_active()) {
deactivate_feed();
};
@@ -278,11 +334,52 @@ Array CameraFeedAndroid::get_formats() const {
CameraFeed::FeedFormat CameraFeedAndroid::get_format() const {
CameraFeed::FeedFormat feed_format = {};
return selected_format == -1 ? feed_format : formats[selected_format];
ERR_FAIL_INDEX_V_MSG(selected_format, formats.size(), feed_format,
vformat("Invalid format index: %d (formats size: %d)", selected_format, formats.size()));
return formats[selected_format];
}
void CameraFeedAndroid::handle_pause() {
if (is_active()) {
was_active_before_pause = true;
print_verbose(vformat("Camera %s: Pausing (was active).", camera_id));
deactivate_feed();
} else {
was_active_before_pause = false;
}
}
void CameraFeedAndroid::handle_resume() {
if (was_active_before_pause) {
print_verbose(vformat("Camera %s: Resuming.", camera_id));
activate_feed();
was_active_before_pause = false;
}
}
void CameraFeedAndroid::handle_rotation_change() {
if (!is_active()) {
return;
}
print_verbose(vformat("Camera %s: Handling rotation change.", camera_id));
refresh_camera_metadata();
_set_rotation();
}
void CameraFeedAndroid::onImage(void *context, AImageReader *p_reader) {
CameraFeedAndroid *feed = static_cast<CameraFeedAndroid *>(context);
MutexLock lock(feed->callback_mutex);
if (!feed->is_active()) {
AImage *pending_image = nullptr;
if (AImageReader_acquireNextImage(p_reader, &pending_image) == AMEDIA_OK) {
AImage_delete(pending_image);
}
return;
}
Vector<uint8_t> data_y = feed->data_y;
Vector<uint8_t> data_uv = feed->data_uv;
Ref<Image> image_y = feed->image_y;
@@ -363,8 +460,17 @@ void CameraFeedAndroid::onImage(void *context, AImageReader *p_reader) {
return;
}
// Rotation
feed->_set_rotation();
if (!feed->formats.is_empty()) {
if (feed->metadata != nullptr) {
feed->_set_rotation();
} else {
print_verbose(vformat("Camera %s: Metadata invalidated in onImage, attempting refresh.", feed->camera_id));
feed->refresh_camera_metadata();
if (feed->metadata != nullptr && !feed->formats.is_empty()) {
feed->_set_rotation();
}
}
}
// Release image
AImage_delete(image);
@@ -389,19 +495,27 @@ void CameraFeedAndroid::deactivate_feed() {
session = nullptr;
}
if (request != nullptr) {
ACaptureRequest_free(request);
request = nullptr;
}
if (reader != nullptr) {
AImageReader_delete(reader);
reader = nullptr;
AImageReader_setImageListener(reader, nullptr);
}
if (device != nullptr) {
ACameraDevice_close(device);
device = nullptr;
{
MutexLock lock(callback_mutex);
if (device != nullptr) {
ACameraDevice_close(device);
device = nullptr;
}
if (reader != nullptr) {
AImageReader_delete(reader);
reader = nullptr;
}
if (request != nullptr) {
ACaptureRequest_free(request);
request = nullptr;
}
}
}
@@ -505,6 +619,75 @@ void CameraAndroid::set_monitoring_feeds(bool p_monitoring_feeds) {
}
}
void CameraAndroid::handle_pause() {
for (int i = 0; i < feeds.size(); i++) {
Ref<CameraFeedAndroid> feed = feeds[i];
if (feed.is_valid()) {
feed->handle_pause();
}
}
}
void CameraAndroid::handle_resume() {
for (int i = 0; i < feeds.size(); i++) {
Ref<CameraFeedAndroid> feed = feeds[i];
if (feed.is_valid()) {
feed->handle_resume();
}
}
}
void CameraAndroid::handle_rotation_change() {
for (int i = 0; i < feeds.size(); i++) {
Ref<CameraFeedAndroid> feed = feeds[i];
if (feed.is_valid()) {
feed->handle_rotation_change();
}
}
}
CameraAndroid::~CameraAndroid() {
remove_all_feeds();
}
std::optional<int> CameraFeedAndroid::calculate_rotation(const CameraRotationParams &p_params) {
if (p_params.sensor_orientation < 0 || p_params.sensor_orientation > 270 || p_params.sensor_orientation % 90 != 0) {
return std::nullopt;
}
int rotation_angle = p_params.sensor_orientation - p_params.display_rotation;
return normalize_angle(rotation_angle);
}
int CameraFeedAndroid::normalize_angle(int p_angle) {
while (p_angle < 0) {
p_angle += 360;
}
return p_angle % 360;
}
int CameraFeedAndroid::get_display_rotation() {
return DisplayServerAndroid::get_singleton()->get_display_rotation();
}
int CameraFeedAndroid::get_app_orientation() {
GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
ERR_FAIL_NULL_V(godot_io_java, 0);
int orientation = godot_io_java->get_screen_orientation();
switch (orientation) {
case 0: // SCREEN_LANDSCAPE
return 90;
case 1: // SCREEN_PORTRAIT
return 0;
case 2: // SCREEN_REVERSE_LANDSCAPE
return 270;
case 3: // SCREEN_REVERSE_PORTRAIT
return 180;
case 4: // SCREEN_SENSOR_LANDSCAPE
case 5: // SCREEN_SENSOR_PORTRAIT
case 6: // SCREEN_SENSOR
default:
return get_display_rotation();
}
}

View File

@@ -38,6 +38,18 @@
#include <camera/NdkCameraManager.h>
#include <camera/NdkCameraMetadataTags.h>
#include <media/NdkImageReader.h>
#include <optional>
enum class CameraFacing {
BACK = 0,
FRONT = 1,
};
struct CameraRotationParams {
int sensor_orientation;
CameraFacing camera_facing;
int display_rotation;
};
class CameraFeedAndroid : public CameraFeed {
GDSOFTCLASS(CameraFeedAndroid, CameraFeed);
@@ -56,9 +68,16 @@ private:
AImageReader *reader = nullptr;
ACameraCaptureSession *session = nullptr;
ACaptureRequest *request = nullptr;
Mutex callback_mutex;
bool was_active_before_pause = false;
void _add_formats();
void _set_rotation();
void refresh_camera_metadata();
static std::optional<int> calculate_rotation(const CameraRotationParams &p_params);
static int normalize_angle(int p_angle);
static int get_display_rotation();
static int get_app_orientation();
static void onError(void *context, ACameraDevice *p_device, int error);
static void onDisconnected(void *context, ACameraDevice *p_device);
@@ -74,6 +93,9 @@ public:
bool set_format(int p_index, const Dictionary &p_parameters) override;
Array get_formats() const override;
FeedFormat get_format() const override;
void handle_pause();
void handle_resume();
void handle_rotation_change();
CameraFeedAndroid(ACameraManager *manager, ACameraMetadata *metadata, const char *id,
CameraFeed::FeedPosition position, int32_t orientation);
@@ -91,6 +113,9 @@ private:
public:
void set_monitoring_feeds(bool p_monitoring_feeds) override;
void handle_pause();
void handle_resume();
void handle_rotation_change();
~CameraAndroid();
};