Merge pull request #118165 from shiena/feature/update-apple-camera

Fix camera docs and port missing features to camera_apple
This commit is contained in:
Thaddeus Crews
2026-04-07 06:44:48 -05:00
4 changed files with 152 additions and 26 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
<description>
A camera feed gives you access to a single physical camera attached to your device. When enabled, Godot will start capturing frames from the camera which can then be used. See also [CameraServer].
[b]Note:[/b] Many cameras will return YCbCr images which are split into two textures and need to be combined in a shader. Godot does this automatically for you if you set the environment to show the camera image in the background.
[b]Note:[/b] This class is currently only implemented on Linux, Android, macOS, and iOS. On other platforms no [CameraFeed]s will be available. To get a [CameraFeed] on iOS, the camera plugin from [url=https://github.com/godotengine/godot-ios-plugins]godot-ios-plugins[/url] is required.
[b]Note:[/b] This class is currently only implemented on Linux, Android, macOS, and iOS. On other platforms no [CameraFeed]s will be available. To get a [CameraFeed] on iOS, enable [member EditorExportPlatformIOS.modules/camera].
</description>
<tutorials>
</tutorials>
@@ -48,6 +48,7 @@
#include "drivers/sdl/joypad_sdl.h"
#endif
#include "main/main.h"
#include "servers/camera/camera_server.h"
#import <AVFoundation/AVFAudio.h>
#import <AudioToolbox/AudioServices.h>
@@ -805,6 +806,11 @@ void OS_AppleEmbedded::on_focus_in() {
void OS_AppleEmbedded::on_enter_background() {
// Do not check for is_focused, because on_focus_out will always be fired first by applicationWillResignActive.
CameraServer *camera_server = CameraServer::get_singleton();
if (camera_server) {
camera_server->handle_application_pause();
}
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_PAUSED);
}
@@ -819,6 +825,11 @@ void OS_AppleEmbedded::on_exit_background() {
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_RESUMED);
}
CameraServer *camera_server = CameraServer::get_singleton();
if (camera_server) {
camera_server->handle_application_resume();
}
}
}
+3
View File
@@ -43,4 +43,7 @@ public:
void update_feeds();
void set_monitoring_feeds(bool p_monitoring_feeds) override;
void handle_display_rotation_change(int p_orientation) override;
void handle_application_pause() override;
void handle_application_resume() override;
};
+137 -25
View File
@@ -95,11 +95,27 @@
[self commitConfiguration];
return nil;
}
if (![self canAddInput:input]) {
print_line("Couldn't add input to capture session");
input = nullptr;
[self commitConfiguration];
return nil;
}
[self addInput:input];
output = [AVCaptureVideoDataOutput new];
if (!output) {
print_line("Couldn't get output device for camera");
[self removeInput:input];
input = nullptr;
[self commitConfiguration];
return nil;
}
if (![self canAddOutput:output]) {
print_line("Couldn't add output to capture session");
[self removeInput:input];
input = nullptr;
output = nullptr;
[self commitConfiguration];
return nil;
}
@@ -193,6 +209,7 @@
// do Y
size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
size_t row_stride = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
if ((width[0] != new_width) || (height[0] != new_height)) {
width[0] = new_width;
@@ -201,7 +218,15 @@
}
uint8_t *w = img_data[0].ptrw();
memcpy(w, dataY, new_width * new_height);
if (new_width == row_stride) {
memcpy(w, dataY, new_width * new_height);
} else {
for (size_t i = 0; i < new_height; i++) {
memcpy(w, dataY, new_width);
w += new_width;
dataY += row_stride;
}
}
img[0].instantiate();
img[0]->set_data(new_width, new_height, 0, Image::FORMAT_R8, img_data[0]);
@@ -211,6 +236,7 @@
// do CbCr
size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
size_t row_stride = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
if ((width[1] != new_width) || (height[1] != new_height)) {
width[1] = new_width;
@@ -219,7 +245,15 @@
}
uint8_t *w = img_data[1].ptrw();
memcpy(w, dataCbCr, 2 * new_width * new_height);
if (new_width * 2 == row_stride) {
memcpy(w, dataCbCr, 2 * new_width * new_height);
} else {
for (size_t i = 0; i < new_height; i++) {
memcpy(w, dataCbCr, new_width * 2);
w += new_width * 2;
dataCbCr += row_stride;
}
}
///TODO OpenGL doesn't support FORMAT_RG8, need to do some form of conversion
img[1].instantiate();
@@ -229,28 +263,6 @@
// set our texture...
feed->set_ycbcr_images(img[0], img[1]);
#ifdef IOS_ENABLED
UIInterfaceOrientation orientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
Transform2D display_transform;
switch (orientation) {
case UIInterfaceOrientationPortrait: {
display_transform = Transform2D(0.0, -1.0, -1.0, 0.0, 1.0, 1.0);
} break;
case UIInterfaceOrientationLandscapeRight: {
display_transform = Transform2D(1.0, 0.0, 0.0, -1.0, 0.0, 1.0);
} break;
case UIInterfaceOrientationLandscapeLeft: {
display_transform = Transform2D(-1.0, 0.0, 0.0, 1.0, 1.0, 0.0);
} break;
default: {
display_transform = Transform2D(0.0, 1.0, 1.0, 0.0, 0.0, 0.0);
} break;
}
feed->set_transform(display_transform);
#endif // IOS_ENABLED
// and unlock
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
}
@@ -266,6 +278,8 @@ class CameraFeedApple : public CameraFeed {
private:
AVCaptureDevice *device;
MyCaptureSession *capture_session;
bool device_locked;
bool was_active_before_pause = false;
public:
AVCaptureDevice *get_device() const;
@@ -275,6 +289,10 @@ public:
void set_device(AVCaptureDevice *p_device);
void handle_rotation_change(int p_orientation);
void handle_pause();
void handle_resume();
bool activate_feed() override;
void deactivate_feed() override;
};
@@ -286,7 +304,8 @@ AVCaptureDevice *CameraFeedApple::get_device() const {
CameraFeedApple::CameraFeedApple() {
device = nullptr;
capture_session = nullptr;
transform = Transform2D(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); /* should re-orientate this based on device orientation */
device_locked = false;
transform = Transform2D(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
}
CameraFeedApple::~CameraFeedApple() {
@@ -309,6 +328,56 @@ void CameraFeedApple::set_device(AVCaptureDevice *p_device) {
};
}
void CameraFeedApple::handle_rotation_change(int p_orientation) {
// UIInterfaceOrientation values:
// 1 = UIInterfaceOrientationPortrait
// 2 = UIInterfaceOrientationPortraitUpsideDown
// 3 = UIInterfaceOrientationLandscapeLeft
// 4 = UIInterfaceOrientationLandscapeRight
int display_rotation = 0;
switch (p_orientation) {
case 1:
display_rotation = 0;
break;
case 2:
display_rotation = 180;
break;
case 3:
display_rotation = 270;
break;
case 4:
display_rotation = 90;
break;
default:
display_rotation = 0;
break;
}
// iOS camera sensor orientation is 90 degrees.
int sensor_orientation = 90;
int sign = position == CameraFeed::FEED_FRONT ? 1 : -1;
int image_rotation = (sensor_orientation - display_rotation * sign + 360) % 360;
transform = Transform2D();
transform = transform.rotated(Math::deg_to_rad((float)image_rotation));
}
void CameraFeedApple::handle_pause() {
if (capture_session) {
was_active_before_pause = true;
deactivate_feed();
} else {
was_active_before_pause = false;
}
}
void CameraFeedApple::handle_resume() {
if (was_active_before_pause) {
activate_feed();
was_active_before_pause = false;
}
}
bool CameraFeedApple::activate_feed() {
if (capture_session) {
// Already recording.
@@ -350,6 +419,10 @@ void CameraFeedApple::deactivate_feed() {
[capture_session cleanup];
capture_session = nullptr;
};
if (device_locked) {
[device unlockForConfiguration];
device_locked = false;
}
}
//////////////////////////////////////////////////////////////////////////
@@ -463,6 +536,18 @@ void CameraApple::update_feeds() {
add_feed(newfeed);
};
};
#ifdef IOS_ENABLED
// Update rotation for all feeds.
UIInterfaceOrientation orientation = UIInterfaceOrientationUnknown;
UIWindow *window = [UIApplication sharedApplication].delegate.window;
UIWindowScene *windowScene = window.windowScene;
if (windowScene) {
orientation = windowScene.interfaceOrientation;
}
handle_display_rotation_change((int)orientation);
#endif // IOS_ENABLED
emit_signal(SNAME(CameraServer::feeds_updated_signal_name));
}
@@ -484,6 +569,33 @@ void CameraApple::set_monitoring_feeds(bool p_monitoring_feeds) {
}
}
void CameraApple::handle_display_rotation_change(int p_orientation) {
for (int i = 0; i < feeds.size(); i++) {
Ref<CameraFeedApple> feed = (Ref<CameraFeedApple>)feeds[i];
if (feed.is_valid()) {
feed->handle_rotation_change(p_orientation);
}
}
}
void CameraApple::handle_application_pause() {
for (int i = 0; i < feeds.size(); i++) {
Ref<CameraFeedApple> feed = (Ref<CameraFeedApple>)feeds[i];
if (feed.is_valid()) {
feed->handle_pause();
}
}
}
void CameraApple::handle_application_resume() {
for (int i = 0; i < feeds.size(); i++) {
Ref<CameraFeedApple> feed = (Ref<CameraFeedApple>)feeds[i];
if (feed.is_valid()) {
feed->handle_resume();
}
}
}
#ifdef APPLE_EMBEDDED_ENABLED
void register_camera_external_module() {