Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/modules/camera/camera_android.cpp
20937 views
1
/**************************************************************************/
2
/* camera_android.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "camera_android.h"
32
33
#include "core/os/os.h"
34
#include "platform/android/display_server_android.h"
35
#include "platform/android/java_godot_io_wrapper.h"
36
#include "platform/android/os_android.h"
37
38
// Scope guard to ensure AImage instances are always deleted.
39
class ScopedAImage {
40
AImage *image = nullptr;
41
42
public:
43
ScopedAImage() = default;
44
~ScopedAImage() {
45
reset();
46
}
47
48
ScopedAImage(const ScopedAImage &) = delete;
49
ScopedAImage &operator=(const ScopedAImage &) = delete;
50
51
AImage **out() {
52
return ℑ
53
}
54
55
AImage *get() const {
56
return image;
57
}
58
59
void reset(AImage *p_image = nullptr) {
60
if (image != nullptr) {
61
AImage_delete(image);
62
}
63
image = p_image;
64
}
65
};
66
67
//////////////////////////////////////////////////////////////////////////
68
// Helper functions
69
//
70
// The following code enables you to view the contents of a media type while
71
// debugging.
72
73
#ifndef IF_EQUAL_RETURN
74
#define MAKE_FORMAT_CONST(suffix) AIMAGE_FORMAT_##suffix
75
#define IF_EQUAL_RETURN(param, val) \
76
if (MAKE_FORMAT_CONST(val) == param) \
77
return #val
78
#endif
79
80
String GetFormatName(const int32_t &format) {
81
IF_EQUAL_RETURN(format, YUV_420_888);
82
IF_EQUAL_RETURN(format, RGB_888);
83
IF_EQUAL_RETURN(format, RGBA_8888);
84
85
return "Unsupported";
86
}
87
88
//////////////////////////////////////////////////////////////////////////
89
// CameraFeedAndroid - Subclass for our camera feed on Android
90
91
CameraFeedAndroid::CameraFeedAndroid(ACameraManager *manager, ACameraMetadata *metadata, const char *id,
92
CameraFeed::FeedPosition position, int32_t orientation) :
93
CameraFeed() {
94
this->manager = manager;
95
this->metadata = metadata;
96
this->orientation = orientation;
97
_add_formats();
98
camera_id = id;
99
set_position(position);
100
101
// Position
102
switch (position) {
103
case CameraFeed::FEED_BACK:
104
name = vformat("%s | BACK", id);
105
break;
106
case CameraFeed::FEED_FRONT:
107
name = vformat("%s | FRONT", id);
108
break;
109
default:
110
name = vformat("%s", id);
111
break;
112
}
113
114
image_y.instantiate();
115
image_uv.instantiate();
116
}
117
118
CameraFeedAndroid::~CameraFeedAndroid() {
119
if (is_active()) {
120
deactivate_feed();
121
}
122
if (metadata != nullptr) {
123
ACameraMetadata_free(metadata);
124
}
125
}
126
127
void CameraFeedAndroid::refresh_camera_metadata() {
128
ERR_FAIL_NULL_MSG(manager, vformat("Camera %s: Cannot refresh metadata, manager is null.", camera_id));
129
130
if (metadata != nullptr) {
131
ACameraMetadata_free(metadata);
132
metadata = nullptr;
133
}
134
135
camera_status_t status = ACameraManager_getCameraCharacteristics(manager, camera_id.utf8().get_data(), &metadata);
136
if (status != ACAMERA_OK || metadata == nullptr) {
137
ERR_FAIL_MSG(vformat("Camera %s: Failed to refresh metadata (status: %d).", camera_id, status));
138
}
139
140
ACameraMetadata_const_entry orientation_entry;
141
status = ACameraMetadata_getConstEntry(metadata, ACAMERA_SENSOR_ORIENTATION, &orientation_entry);
142
if (status == ACAMERA_OK) {
143
orientation = orientation_entry.data.i32[0];
144
print_verbose(vformat("Camera %s: Orientation updated to %d.", camera_id, orientation));
145
} else {
146
ERR_PRINT(vformat("Camera %s: Failed to get sensor orientation after refresh (status: %d).", camera_id, status));
147
}
148
149
formats.clear();
150
_add_formats();
151
152
print_verbose(vformat("Camera %s: Metadata refreshed successfully.", camera_id));
153
}
154
155
void CameraFeedAndroid::_set_rotation() {
156
if (!metadata) {
157
print_verbose(vformat("Camera %s: Metadata is null in _set_rotation, attempting refresh.", camera_id));
158
refresh_camera_metadata();
159
}
160
161
float image_rotation = 0.0f;
162
std::optional<int> result;
163
164
if (metadata) {
165
CameraRotationParams params;
166
params.sensor_orientation = orientation;
167
params.camera_facing = (position == CameraFeed::FEED_FRONT) ? CameraFacing::FRONT : CameraFacing::BACK;
168
params.display_rotation = get_app_orientation();
169
170
result = calculate_rotation(params);
171
} else {
172
ERR_PRINT(vformat("Camera %s: Cannot update rotation, metadata unavailable after refresh, using fallback.", camera_id));
173
}
174
175
if (result.has_value()) {
176
image_rotation = static_cast<float>(result.value());
177
} else {
178
int display_rotation = DisplayServerAndroid::get_singleton()->get_display_rotation();
179
switch (display_rotation) {
180
case 90:
181
display_rotation = 270;
182
break;
183
case 270:
184
display_rotation = 90;
185
break;
186
default:
187
break;
188
}
189
190
int sign = position == CameraFeed::FEED_FRONT ? 1 : -1;
191
image_rotation = (orientation - display_rotation * sign + 360) % 360;
192
}
193
194
transform = Transform2D();
195
transform = transform.rotated(Math::deg_to_rad(image_rotation));
196
}
197
198
void CameraFeedAndroid::_add_formats() {
199
// Get supported formats
200
ACameraMetadata_const_entry formats;
201
camera_status_t status = ACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &formats);
202
203
if (status == ACAMERA_OK) {
204
for (uint32_t f = 0; f < formats.count; f += 4) {
205
// Only support output streams
206
int32_t input = formats.data.i32[f + 3];
207
if (input) {
208
continue;
209
}
210
211
// Get format and resolution
212
int32_t format = formats.data.i32[f + 0];
213
if (format == AIMAGE_FORMAT_YUV_420_888 ||
214
format == AIMAGE_FORMAT_RGBA_8888 ||
215
format == AIMAGE_FORMAT_RGB_888) {
216
CameraFeed::FeedFormat feed_format;
217
feed_format.width = formats.data.i32[f + 1];
218
feed_format.height = formats.data.i32[f + 2];
219
feed_format.format = GetFormatName(format);
220
feed_format.pixel_format = format;
221
this->formats.append(feed_format);
222
}
223
}
224
}
225
}
226
227
bool CameraFeedAndroid::activate_feed() {
228
ERR_FAIL_COND_V_MSG(formats.is_empty(), false, "No camera formats available.");
229
ERR_FAIL_INDEX_V_MSG(selected_format, formats.size(), false,
230
vformat("CameraFeed format needs to be set before activating. Selected format index: %d (formats size: %d)", selected_format, formats.size()));
231
if (is_active()) {
232
deactivate_feed();
233
};
234
235
// Clear deactivation and error flags before starting activation.
236
is_deactivating.clear();
237
device_error_occurred.clear();
238
239
// Request permission
240
if (!OS::get_singleton()->request_permission("CAMERA")) {
241
return false;
242
}
243
244
// Open device
245
device_callbacks = {
246
.context = this,
247
.onDisconnected = onDisconnected,
248
.onError = onError,
249
};
250
camera_status_t c_status = ACameraManager_openCamera(manager, camera_id.utf8().get_data(), &device_callbacks, &device);
251
if (c_status != ACAMERA_OK) {
252
print_error(vformat("Camera %s: Failed to open camera (status: %d)", camera_id, c_status));
253
deactivate_feed();
254
return false;
255
}
256
257
// Create image reader
258
const FeedFormat &feed_format = formats[selected_format];
259
media_status_t m_status = AImageReader_new(feed_format.width, feed_format.height, feed_format.pixel_format, 1, &reader);
260
if (m_status != AMEDIA_OK) {
261
print_error(vformat("Camera %s: Failed to create image reader (status: %d)", camera_id, m_status));
262
deactivate_feed();
263
return false;
264
}
265
266
// Get image listener
267
image_listener = {
268
.context = this,
269
.onImageAvailable = onImage,
270
};
271
m_status = AImageReader_setImageListener(reader, &image_listener);
272
if (m_status != AMEDIA_OK) {
273
print_error(vformat("Camera %s: Failed to set image listener (status: %d)", camera_id, m_status));
274
deactivate_feed();
275
return false;
276
}
277
278
// Get image surface
279
ANativeWindow *surface;
280
m_status = AImageReader_getWindow(reader, &surface);
281
if (m_status != AMEDIA_OK) {
282
print_error(vformat("Camera %s: Failed to get image surface (status: %d)", camera_id, m_status));
283
deactivate_feed();
284
return false;
285
}
286
287
// Prepare session outputs
288
c_status = ACaptureSessionOutput_create(surface, &session_output);
289
if (c_status != ACAMERA_OK) {
290
print_error(vformat("Camera %s: Failed to create session output (status: %d)", camera_id, c_status));
291
deactivate_feed();
292
return false;
293
}
294
295
c_status = ACaptureSessionOutputContainer_create(&session_output_container);
296
if (c_status != ACAMERA_OK) {
297
print_error(vformat("Camera %s: Failed to create session output container (status: %d)", camera_id, c_status));
298
deactivate_feed();
299
return false;
300
}
301
302
c_status = ACaptureSessionOutputContainer_add(session_output_container, session_output);
303
if (c_status != ACAMERA_OK) {
304
print_error(vformat("Camera %s: Failed to add session output to container (status: %d)", camera_id, c_status));
305
deactivate_feed();
306
return false;
307
}
308
309
// Create capture session
310
session_callbacks = {
311
.context = this,
312
.onClosed = onSessionClosed,
313
.onReady = onSessionReady,
314
.onActive = onSessionActive
315
};
316
c_status = ACameraDevice_createCaptureSession(device, session_output_container, &session_callbacks, &session);
317
if (c_status != ACAMERA_OK) {
318
print_error(vformat("Camera %s: Failed to create capture session (status: %d)", camera_id, c_status));
319
deactivate_feed();
320
return false;
321
}
322
323
// Create capture request
324
c_status = ACameraDevice_createCaptureRequest(device, TEMPLATE_PREVIEW, &request);
325
if (c_status != ACAMERA_OK) {
326
print_error(vformat("Camera %s: Failed to create capture request (status: %d)", camera_id, c_status));
327
deactivate_feed();
328
return false;
329
}
330
331
// Set capture target
332
c_status = ACameraOutputTarget_create(surface, &camera_output_target);
333
if (c_status != ACAMERA_OK) {
334
print_error(vformat("Camera %s: Failed to create camera output target (status: %d)", camera_id, c_status));
335
deactivate_feed();
336
return false;
337
}
338
339
c_status = ACaptureRequest_addTarget(request, camera_output_target);
340
if (c_status != ACAMERA_OK) {
341
print_error(vformat("Camera %s: Failed to add target to capture request (status: %d)", camera_id, c_status));
342
deactivate_feed();
343
return false;
344
}
345
346
// Start capture
347
c_status = ACameraCaptureSession_setRepeatingRequest(session, nullptr, 1, &request, nullptr);
348
if (c_status != ACAMERA_OK) {
349
print_error(vformat("Camera %s: Failed to start repeating request (status: %d)", camera_id, c_status));
350
deactivate_feed();
351
return false;
352
}
353
354
return true;
355
}
356
357
bool CameraFeedAndroid::set_format(int p_index, const Dictionary &p_parameters) {
358
ERR_FAIL_COND_V_MSG(active, false, "Feed is active.");
359
ERR_FAIL_INDEX_V_MSG(p_index, formats.size(), false, "Invalid format index.");
360
361
selected_format = p_index;
362
363
// Reset base dimensions to force texture recreation on next frame.
364
// This ensures proper handling when switching between different resolutions.
365
base_width = 0;
366
base_height = 0;
367
368
return true;
369
}
370
371
Array CameraFeedAndroid::get_formats() const {
372
Array result;
373
for (const FeedFormat &feed_format : formats) {
374
Dictionary dictionary;
375
dictionary["width"] = feed_format.width;
376
dictionary["height"] = feed_format.height;
377
dictionary["format"] = feed_format.format;
378
result.push_back(dictionary);
379
}
380
return result;
381
}
382
383
CameraFeed::FeedFormat CameraFeedAndroid::get_format() const {
384
CameraFeed::FeedFormat feed_format = {};
385
ERR_FAIL_INDEX_V_MSG(selected_format, formats.size(), feed_format,
386
vformat("Invalid format index: %d (formats size: %d)", selected_format, formats.size()));
387
return formats[selected_format];
388
}
389
390
void CameraFeedAndroid::handle_pause() {
391
if (is_active()) {
392
was_active_before_pause = true;
393
print_verbose(vformat("Camera %s: Pausing (was active).", camera_id));
394
deactivate_feed();
395
} else {
396
was_active_before_pause = false;
397
}
398
}
399
400
void CameraFeedAndroid::handle_resume() {
401
if (was_active_before_pause) {
402
print_verbose(vformat("Camera %s: Resuming.", camera_id));
403
activate_feed();
404
was_active_before_pause = false;
405
}
406
}
407
408
void CameraFeedAndroid::handle_rotation_change() {
409
if (!is_active()) {
410
return;
411
}
412
413
print_verbose(vformat("Camera %s: Handling rotation change.", camera_id));
414
refresh_camera_metadata();
415
_set_rotation();
416
}
417
418
// In-place stride compaction (handles row padding).
419
void CameraFeedAndroid::compact_stride_inplace(uint8_t *data, size_t width, int height, size_t stride) {
420
if (stride <= width) {
421
return;
422
}
423
424
uint8_t *src_row = data + stride;
425
uint8_t *dst_row = data + width;
426
427
for (int y = 1; y < height; y++) {
428
memmove(dst_row, src_row, width);
429
src_row += stride;
430
dst_row += width;
431
}
432
}
433
434
void CameraFeedAndroid::onImage(void *context, AImageReader *p_reader) {
435
CameraFeedAndroid *feed = static_cast<CameraFeedAndroid *>(context);
436
437
// Check deactivation flag before acquiring mutex to avoid using resources
438
// that may be deleted during deactivate_feed(). This is a racy check but
439
// safe because we only transition is_deactivating from false->true during
440
// deactivation, and back to false only at the start of activation.
441
if (feed->is_deactivating.is_set()) {
442
// Don't try to acquire images - the reader may be deleted.
443
// Android will clean up pending images when the reader is deleted.
444
return;
445
}
446
447
MutexLock lock(feed->callback_mutex);
448
449
// Re-check after acquiring mutex in case deactivation started while waiting.
450
if (feed->is_deactivating.is_set()) {
451
return;
452
}
453
454
// If feed is not active, we must still acquire and discard the image to
455
// free the buffer slot. Otherwise, with maxImages=1, the buffer stays full
456
// and no new frames will arrive (onImage won't be called again).
457
// This can happen when a frame arrives between activate_feed() returning
458
// and active=true being set in the base class.
459
if (!feed->is_active()) {
460
AImage *pending_image = nullptr;
461
if (AImageReader_acquireNextImage(p_reader, &pending_image) == AMEDIA_OK && pending_image != nullptr) {
462
AImage_delete(pending_image);
463
}
464
return;
465
}
466
467
Vector<uint8_t> data_y;
468
Vector<uint8_t> data_uv;
469
470
// Get image
471
ScopedAImage image_guard;
472
media_status_t status = AImageReader_acquireNextImage(p_reader, image_guard.out());
473
ERR_FAIL_COND(status != AMEDIA_OK);
474
AImage *image = image_guard.get();
475
476
// Get image data
477
uint8_t *data = nullptr;
478
int len = 0;
479
int32_t pixel_stride, row_stride;
480
FeedFormat format = feed->get_format();
481
int width = format.width;
482
int height = format.height;
483
484
switch (format.pixel_format) {
485
case AIMAGE_FORMAT_YUV_420_888: {
486
int32_t y_row_stride;
487
AImage_getPlaneRowStride(image, 0, &y_row_stride);
488
AImage_getPlaneData(image, 0, &data, &len);
489
490
if (len <= 0) {
491
return;
492
}
493
494
int64_t y_size = Image::get_image_data_size(width, height, Image::FORMAT_R8, false);
495
496
if (y_row_stride == width && len == y_size) {
497
data_y.resize(y_size);
498
memcpy(data_y.ptrw(), data, y_size);
499
} else {
500
// Validate buffer size before compaction to prevent out-of-bounds read.
501
int64_t required_y_len = (int64_t)y_row_stride * (height - 1) + width;
502
if (len < required_y_len) {
503
return;
504
}
505
if (feed->scratch_y.size() < len) {
506
feed->scratch_y.resize(len);
507
}
508
memcpy(feed->scratch_y.ptrw(), data, len);
509
CameraFeedAndroid::compact_stride_inplace(feed->scratch_y.ptrw(), width, height, y_row_stride);
510
data_y.resize(y_size);
511
memcpy(data_y.ptrw(), feed->scratch_y.ptr(), y_size);
512
}
513
514
AImage_getPlanePixelStride(image, 1, &pixel_stride);
515
AImage_getPlaneRowStride(image, 1, &row_stride);
516
AImage_getPlaneData(image, 1, &data, &len);
517
518
if (len <= 0) {
519
return;
520
}
521
522
int64_t uv_size = Image::get_image_data_size(width / 2, height / 2, Image::FORMAT_RG8, false);
523
524
int uv_width = width / 2;
525
int uv_height = height / 2;
526
527
uint8_t *data_v = nullptr;
528
int32_t v_pixel_stride = 0;
529
int32_t v_row_stride = 0;
530
int len_v = 0;
531
532
if (pixel_stride != 2) {
533
AImage_getPlanePixelStride(image, 2, &v_pixel_stride);
534
AImage_getPlaneRowStride(image, 2, &v_row_stride);
535
AImage_getPlaneData(image, 2, &data_v, &len_v);
536
if (len_v <= 0) {
537
return;
538
}
539
}
540
541
if (pixel_stride == 2 && row_stride == uv_width * 2 && len == uv_size) {
542
data_uv.resize(uv_size);
543
memcpy(data_uv.ptrw(), data, uv_size);
544
} else if (pixel_stride == 2) {
545
// Allow 1-2 byte tolerance for UV buffer (some devices omit final bytes).
546
int64_t required_uv_len = (int64_t)row_stride * (uv_height - 1) + uv_width * 2;
547
const int64_t UV_TOLERANCE = 2;
548
if (len < required_uv_len - UV_TOLERANCE) {
549
return;
550
}
551
552
if (feed->scratch_uv.size() < required_uv_len) {
553
feed->scratch_uv.resize(required_uv_len);
554
}
555
if (len < required_uv_len) {
556
memset(feed->scratch_uv.ptrw() + len, 128, required_uv_len - len);
557
}
558
memcpy(feed->scratch_uv.ptrw(), data, len);
559
if (row_stride != uv_width * 2) {
560
CameraFeedAndroid::compact_stride_inplace(feed->scratch_uv.ptrw(), uv_width * 2, uv_height, row_stride);
561
}
562
data_uv.resize(uv_size);
563
memcpy(data_uv.ptrw(), feed->scratch_uv.ptr(), uv_size);
564
} else {
565
if (data_v && len_v > 0) {
566
data_uv.resize(uv_size);
567
uint8_t *dst = data_uv.ptrw();
568
uint8_t *src_u = data;
569
uint8_t *src_v = data_v;
570
for (int row = 0; row < uv_height; row++) {
571
for (int col = 0; col < uv_width; col++) {
572
dst[col * 2] = src_u[col * pixel_stride];
573
dst[col * 2 + 1] = src_v[col * v_pixel_stride];
574
}
575
dst += uv_width * 2;
576
src_u += row_stride;
577
src_v += v_row_stride;
578
}
579
} else {
580
data_uv.resize(uv_size);
581
memset(data_uv.ptrw(), 128, uv_size);
582
}
583
}
584
585
// Defer to main thread to avoid race conditions with RenderingServer.
586
feed->image_y.instantiate();
587
feed->image_y->initialize_data(width, height, false, Image::FORMAT_R8, data_y);
588
589
feed->image_uv.instantiate();
590
feed->image_uv->initialize_data(width / 2, height / 2, false, Image::FORMAT_RG8, data_uv);
591
592
feed->call_deferred("set_ycbcr_images", feed->image_y, feed->image_uv);
593
break;
594
}
595
case AIMAGE_FORMAT_RGBA_8888: {
596
AImage_getPlaneData(image, 0, &data, &len);
597
if (len <= 0) {
598
return;
599
}
600
int64_t size = Image::get_image_data_size(width, height, Image::FORMAT_RGBA8, false);
601
data_y.resize(len > size ? len : size);
602
memcpy(data_y.ptrw(), data, len);
603
604
feed->image_y.instantiate();
605
feed->image_y->initialize_data(width, height, false, Image::FORMAT_RGBA8, data_y);
606
607
feed->call_deferred("set_rgb_image", feed->image_y);
608
break;
609
}
610
case AIMAGE_FORMAT_RGB_888: {
611
AImage_getPlaneData(image, 0, &data, &len);
612
if (len <= 0) {
613
return;
614
}
615
int64_t size = Image::get_image_data_size(width, height, Image::FORMAT_RGB8, false);
616
data_y.resize(len > size ? len : size);
617
memcpy(data_y.ptrw(), data, len);
618
619
feed->image_y.instantiate();
620
feed->image_y->initialize_data(width, height, false, Image::FORMAT_RGB8, data_y);
621
622
feed->call_deferred("set_rgb_image", feed->image_y);
623
break;
624
}
625
default:
626
return;
627
}
628
629
if (!feed->formats.is_empty()) {
630
if (feed->metadata != nullptr) {
631
feed->_set_rotation();
632
} else {
633
print_verbose(vformat("Camera %s: Metadata invalidated in onImage, attempting refresh.", feed->camera_id));
634
feed->refresh_camera_metadata();
635
if (feed->metadata != nullptr && !feed->formats.is_empty()) {
636
feed->_set_rotation();
637
}
638
}
639
}
640
641
// Release image happens automatically via ScopedAImage.
642
}
643
644
void CameraFeedAndroid::onSessionReady(void *context, ACameraCaptureSession *session) {
645
print_verbose("Capture session ready");
646
}
647
648
void CameraFeedAndroid::onSessionActive(void *context, ACameraCaptureSession *session) {
649
print_verbose("Capture session active");
650
}
651
652
void CameraFeedAndroid::onSessionClosed(void *context, ACameraCaptureSession *p_session) {
653
CameraFeedAndroid *feed = static_cast<CameraFeedAndroid *>(context);
654
// Only post if deactivate_feed() is waiting for us. This prevents
655
// spurious posts from error-triggered session closes that could
656
// desynchronize the semaphore count.
657
if (feed->session_close_pending.is_set()) {
658
feed->session_closed_semaphore.post();
659
}
660
}
661
662
void CameraFeedAndroid::deactivate_feed() {
663
// Signal that deactivation is in progress. This prevents onImage callbacks
664
// from using resources that may be deleted during cleanup.
665
is_deactivating.set();
666
667
// First, remove image listener to prevent new callbacks.
668
if (reader != nullptr) {
669
AImageReader_setImageListener(reader, nullptr);
670
}
671
672
// Stop and close capture session.
673
// Must wait for session to fully close before closing device.
674
if (session != nullptr) {
675
ACameraCaptureSession_stopRepeating(session);
676
677
// If an error occurred, the session may have already been closed by
678
// Android. In that case, ACameraCaptureSession_close() may not trigger
679
// onSessionClosed, so we skip waiting to avoid a deadlock.
680
bool skip_wait = device_error_occurred.is_set();
681
682
if (!skip_wait) {
683
// Set flag before closing to indicate we're waiting for the callback.
684
// This ensures we only wait for the post from THIS close operation.
685
session_close_pending.set();
686
}
687
688
ACameraCaptureSession_close(session);
689
690
if (!skip_wait) {
691
// Wait for onSessionClosed callback to ensure session is fully closed
692
// before proceeding to close the device.
693
session_closed_semaphore.wait();
694
session_close_pending.clear();
695
}
696
697
session = nullptr;
698
}
699
700
// Now safe to acquire lock and clean up resources.
701
// No new callbacks will be triggered after this point.
702
MutexLock lock(callback_mutex);
703
704
if (device != nullptr) {
705
ACameraDevice_close(device);
706
device = nullptr;
707
}
708
709
if (reader != nullptr) {
710
AImageReader_delete(reader);
711
reader = nullptr;
712
}
713
714
if (request != nullptr) {
715
ACaptureRequest_free(request);
716
request = nullptr;
717
}
718
719
if (camera_output_target != nullptr) {
720
ACameraOutputTarget_free(camera_output_target);
721
camera_output_target = nullptr;
722
}
723
724
if (session_output_container != nullptr) {
725
ACaptureSessionOutputContainer_free(session_output_container);
726
session_output_container = nullptr;
727
}
728
729
if (session_output != nullptr) {
730
ACaptureSessionOutput_free(session_output);
731
session_output = nullptr;
732
}
733
}
734
735
void CameraFeedAndroid::onError(void *context, ACameraDevice *p_device, int error) {
736
CameraFeedAndroid *feed = static_cast<CameraFeedAndroid *>(context);
737
print_error(vformat("Camera %s error: %d", feed->camera_id, error));
738
// Mark that an error occurred. This signals to deactivate_feed() that
739
// the session may have been closed by Android, so we shouldn't wait
740
// for onSessionClosed.
741
feed->device_error_occurred.set();
742
onDisconnected(context, p_device);
743
}
744
745
void CameraFeedAndroid::onDisconnected(void *context, ACameraDevice *p_device) {
746
CameraFeedAndroid *feed = static_cast<CameraFeedAndroid *>(context);
747
// Defer to main thread to avoid reentrancy issues when called from
748
// ACameraDevice_close() during deactivate_feed().
749
feed->call_deferred("set_active", false);
750
}
751
752
//////////////////////////////////////////////////////////////////////////
753
// CameraAndroid - Subclass for our camera server on Android
754
755
void CameraAndroid::update_feeds() {
756
ACameraIdList *cameraIds = nullptr;
757
camera_status_t c_status = ACameraManager_getCameraIdList(cameraManager, &cameraIds);
758
ERR_FAIL_COND(c_status != ACAMERA_OK);
759
760
// Deactivate all feeds before removing to ensure proper cleanup.
761
for (int i = 0; i < feeds.size(); i++) {
762
Ref<CameraFeedAndroid> feed = feeds[i];
763
if (feed.is_valid() && feed->is_active()) {
764
feed->deactivate_feed();
765
}
766
}
767
768
// remove existing devices
769
for (int i = feeds.size() - 1; i >= 0; i--) {
770
remove_feed(feeds[i]);
771
}
772
773
for (int c = 0; c < cameraIds->numCameras; ++c) {
774
const char *id = cameraIds->cameraIds[c];
775
ACameraMetadata *metadata = nullptr;
776
ACameraManager_getCameraCharacteristics(cameraManager, id, &metadata);
777
if (!metadata) {
778
continue;
779
}
780
781
// Get sensor orientation
782
ACameraMetadata_const_entry orientation;
783
c_status = ACameraMetadata_getConstEntry(metadata, ACAMERA_SENSOR_ORIENTATION, &orientation);
784
int32_t cameraOrientation;
785
if (c_status == ACAMERA_OK) {
786
cameraOrientation = orientation.data.i32[0];
787
} else {
788
cameraOrientation = 0;
789
print_error(vformat("Unable to get sensor orientation: %s", id));
790
}
791
792
// Get position
793
ACameraMetadata_const_entry lensInfo;
794
CameraFeed::FeedPosition position = CameraFeed::FEED_UNSPECIFIED;
795
camera_status_t status;
796
status = ACameraMetadata_getConstEntry(metadata, ACAMERA_LENS_FACING, &lensInfo);
797
if (status != ACAMERA_OK) {
798
ACameraMetadata_free(metadata);
799
continue;
800
}
801
uint8_t lens_facing = static_cast<acamera_metadata_enum_android_lens_facing_t>(lensInfo.data.u8[0]);
802
if (lens_facing == ACAMERA_LENS_FACING_FRONT) {
803
position = CameraFeed::FEED_FRONT;
804
} else if (lens_facing == ACAMERA_LENS_FACING_BACK) {
805
position = CameraFeed::FEED_BACK;
806
} else {
807
ACameraMetadata_free(metadata);
808
continue;
809
}
810
811
Ref<CameraFeedAndroid> feed = memnew(CameraFeedAndroid(cameraManager, metadata, id, position, cameraOrientation));
812
add_feed(feed);
813
}
814
815
ACameraManager_deleteCameraIdList(cameraIds);
816
emit_signal(SNAME(CameraServer::feeds_updated_signal_name));
817
}
818
819
void CameraAndroid::remove_all_feeds() {
820
// Deactivate all feeds before removing to ensure proper cleanup.
821
// This prevents "Device is closed but session is not notified" warnings
822
// that can occur if feeds are destroyed while still active.
823
for (int i = 0; i < feeds.size(); i++) {
824
Ref<CameraFeedAndroid> feed = feeds[i];
825
if (feed.is_valid() && feed->is_active()) {
826
feed->deactivate_feed();
827
}
828
}
829
830
// remove existing devices
831
for (int i = feeds.size() - 1; i >= 0; i--) {
832
remove_feed(feeds[i]);
833
}
834
835
if (cameraManager != nullptr) {
836
ACameraManager_delete(cameraManager);
837
cameraManager = nullptr;
838
}
839
}
840
841
void CameraAndroid::set_monitoring_feeds(bool p_monitoring_feeds) {
842
if (p_monitoring_feeds == monitoring_feeds) {
843
return;
844
}
845
846
CameraServer::set_monitoring_feeds(p_monitoring_feeds);
847
if (p_monitoring_feeds) {
848
if (cameraManager == nullptr) {
849
cameraManager = ACameraManager_create();
850
}
851
852
// Update feeds
853
update_feeds();
854
} else {
855
remove_all_feeds();
856
}
857
}
858
859
void CameraAndroid::handle_application_pause() {
860
for (int i = 0; i < feeds.size(); i++) {
861
Ref<CameraFeedAndroid> feed = feeds[i];
862
if (feed.is_valid()) {
863
feed->handle_pause();
864
}
865
}
866
}
867
868
void CameraAndroid::handle_application_resume() {
869
for (int i = 0; i < feeds.size(); i++) {
870
Ref<CameraFeedAndroid> feed = feeds[i];
871
if (feed.is_valid()) {
872
feed->handle_resume();
873
}
874
}
875
}
876
877
void CameraAndroid::handle_display_rotation_change(int) {
878
for (int i = 0; i < feeds.size(); i++) {
879
Ref<CameraFeedAndroid> feed = feeds[i];
880
if (feed.is_valid()) {
881
feed->handle_rotation_change();
882
}
883
}
884
}
885
886
CameraAndroid::~CameraAndroid() {
887
remove_all_feeds();
888
}
889
890
std::optional<int> CameraFeedAndroid::calculate_rotation(const CameraRotationParams &p_params) {
891
if (p_params.sensor_orientation < 0 || p_params.sensor_orientation > 270 || p_params.sensor_orientation % 90 != 0) {
892
return std::nullopt;
893
}
894
895
int rotation_angle = p_params.sensor_orientation - p_params.display_rotation;
896
return normalize_angle(rotation_angle);
897
}
898
899
int CameraFeedAndroid::normalize_angle(int p_angle) {
900
while (p_angle < 0) {
901
p_angle += 360;
902
}
903
return p_angle % 360;
904
}
905
906
int CameraFeedAndroid::get_display_rotation() {
907
return DisplayServerAndroid::get_singleton()->get_display_rotation();
908
}
909
910
int CameraFeedAndroid::get_app_orientation() {
911
GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
912
ERR_FAIL_NULL_V(godot_io_java, 0);
913
914
int orientation = godot_io_java->get_screen_orientation();
915
switch (orientation) {
916
case 0: // SCREEN_LANDSCAPE
917
return 90;
918
case 1: // SCREEN_PORTRAIT
919
return 0;
920
case 2: // SCREEN_REVERSE_LANDSCAPE
921
return 270;
922
case 3: // SCREEN_REVERSE_PORTRAIT
923
return 180;
924
case 4: // SCREEN_SENSOR_LANDSCAPE
925
case 5: // SCREEN_SENSOR_PORTRAIT
926
case 6: // SCREEN_SENSOR
927
default:
928
return get_display_rotation();
929
}
930
}
931
932