diff --git a/doc/classes/CameraFeed.xml b/doc/classes/CameraFeed.xml index 72b7635e2498..68619fc5e101 100644 --- a/doc/classes/CameraFeed.xml +++ b/doc/classes/CameraFeed.xml @@ -6,7 +6,7 @@ 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]. diff --git a/drivers/apple_embedded/os_apple_embedded.mm b/drivers/apple_embedded/os_apple_embedded.mm index c1dfa5304c01..e624c7e4cddd 100644 --- a/drivers/apple_embedded/os_apple_embedded.mm +++ b/drivers/apple_embedded/os_apple_embedded.mm @@ -48,6 +48,7 @@ #include "drivers/sdl/joypad_sdl.h" #endif #include "main/main.h" +#include "servers/camera/camera_server.h" #import #import @@ -805,6 +806,11 @@ Rect2 fit_keep_aspect_covered(const Vector2 &p_container, const Vector2 &p_rect) 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 @@ Rect2 fit_keep_aspect_covered(const Vector2 &p_container, const Vector2 &p_rect) 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(); + } } } diff --git a/modules/camera/camera_apple.h b/modules/camera/camera_apple.h index 82b5e7dec535..7903163d7584 100644 --- a/modules/camera/camera_apple.h +++ b/modules/camera/camera_apple.h @@ -43,4 +43,7 @@ class CameraApple : public CameraServer { 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; }; diff --git a/modules/camera/camera_apple.mm b/modules/camera/camera_apple.mm index 5943bbd686f6..fd3f835f4c86 100644 --- a/modules/camera/camera_apple.mm +++ b/modules/camera/camera_apple.mm @@ -95,11 +95,27 @@ - (id)initForFeed:(Ref)p_feed andDevice:(AVCaptureDevice *)p_device [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 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CM // 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 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CM } 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 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CM // 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 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CM } 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 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CM // 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 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CM private: AVCaptureDevice *device; MyCaptureSession *capture_session; + bool device_locked; + bool was_active_before_pause = false; public: AVCaptureDevice *get_device() const; @@ -275,6 +289,10 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CM 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 @@ - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CM 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)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CM }; } +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)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CM [capture_session cleanup]; capture_session = nullptr; }; + if (device_locked) { + [device unlockForConfiguration]; + device_locked = false; + } } ////////////////////////////////////////////////////////////////////////// @@ -463,6 +536,18 @@ - (void)dealloc { 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)dealloc { } } +void CameraApple::handle_display_rotation_change(int p_orientation) { + for (int i = 0; i < feeds.size(); i++) { + Ref feed = (Ref)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 feed = (Ref)feeds[i]; + if (feed.is_valid()) { + feed->handle_pause(); + } + } +} + +void CameraApple::handle_application_resume() { + for (int i = 0; i < feeds.size(); i++) { + Ref feed = (Ref)feeds[i]; + if (feed.is_valid()) { + feed->handle_resume(); + } + } +} + #ifdef APPLE_EMBEDDED_ENABLED void register_camera_external_module() {