|
@@ -29,6 +29,14 @@
|
|
|
#import <AVFoundation/AVFoundation.h>
|
|
#import <AVFoundation/AVFoundation.h>
|
|
|
#import <CoreMedia/CoreMedia.h>
|
|
#import <CoreMedia/CoreMedia.h>
|
|
|
|
|
|
|
|
|
|
+#if defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_TVOS)
|
|
|
|
|
+#define USE_UIKIT_DEVICE_ROTATION
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
|
|
+#ifdef USE_UIKIT_DEVICE_ROTATION
|
|
|
|
|
+#import <UIKit/UIKit.h>
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
/*
|
|
/*
|
|
|
* Need to link with:: CoreMedia CoreVideo
|
|
* Need to link with:: CoreMedia CoreVideo
|
|
|
*
|
|
*
|
|
@@ -77,6 +85,9 @@ static void CoreMediaFormatToSDL(FourCharCode fmt, SDL_PixelFormat *pixel_format
|
|
|
@property(nonatomic, retain) AVCaptureSession *session;
|
|
@property(nonatomic, retain) AVCaptureSession *session;
|
|
|
@property(nonatomic, retain) SDLCaptureVideoDataOutputSampleBufferDelegate *delegate;
|
|
@property(nonatomic, retain) SDLCaptureVideoDataOutputSampleBufferDelegate *delegate;
|
|
|
@property(nonatomic, assign) CMSampleBufferRef current_sample;
|
|
@property(nonatomic, assign) CMSampleBufferRef current_sample;
|
|
|
|
|
+#ifdef USE_UIKIT_DEVICE_ROTATION
|
|
|
|
|
+@property(nonatomic, assign) UIDeviceOrientation last_device_orientation;
|
|
|
|
|
+#endif
|
|
|
@end
|
|
@end
|
|
|
|
|
|
|
|
@implementation SDLPrivateCameraData
|
|
@implementation SDLPrivateCameraData
|
|
@@ -146,7 +157,7 @@ static bool COREMEDIA_WaitDevice(SDL_Camera *device)
|
|
|
return true; // this isn't used atm, since we run our own thread out of Grand Central Dispatch.
|
|
return true; // this isn't used atm, since we run our own thread out of Grand Central Dispatch.
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-static SDL_CameraFrameResult COREMEDIA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS)
|
|
|
|
|
|
|
+static SDL_CameraFrameResult COREMEDIA_AcquireFrame(SDL_Camera *device, SDL_Surface *frame, Uint64 *timestampNS, int *rotation)
|
|
|
{
|
|
{
|
|
|
SDL_CameraFrameResult result = SDL_CAMERA_FRAME_READY;
|
|
SDL_CameraFrameResult result = SDL_CAMERA_FRAME_READY;
|
|
|
SDLPrivateCameraData *hidden = (__bridge SDLPrivateCameraData *) device->hidden;
|
|
SDLPrivateCameraData *hidden = (__bridge SDLPrivateCameraData *) device->hidden;
|
|
@@ -219,6 +230,37 @@ static SDL_CameraFrameResult COREMEDIA_AcquireFrame(SDL_Camera *device, SDL_Surf
|
|
|
|
|
|
|
|
CVPixelBufferUnlockBaseAddress(image, 0);
|
|
CVPixelBufferUnlockBaseAddress(image, 0);
|
|
|
|
|
|
|
|
|
|
+ #ifdef USE_UIKIT_DEVICE_ROTATION
|
|
|
|
|
+ UIDeviceOrientation device_orientation = [[UIDevice currentDevice] orientation];
|
|
|
|
|
+ if (!UIDeviceOrientationIsValidInterfaceOrientation(device_orientation)) {
|
|
|
|
|
+ device_orientation = hidden.last_device_orientation; // possible the phone is laying flat or something went wrong, just stay with the last known-good orientation.
|
|
|
|
|
+ } else {
|
|
|
|
|
+ hidden.last_device_orientation = device_orientation; // update the last known-good orientation for later.
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const UIInterfaceOrientation ui_orientation = [UIApplication sharedApplication].statusBarOrientation;
|
|
|
|
|
+
|
|
|
|
|
+ // there is probably math for this, but this is easy to slap into a table.
|
|
|
|
|
+ // rotation = rotations[uiorientation-1][devorientation-1];
|
|
|
|
|
+ if (device->position == SDL_CAMERA_POSITION_BACK_FACING) {
|
|
|
|
|
+ static const int back_rotations[4][4] = {
|
|
|
|
|
+ { 90, 90, 90, 90 }, // ui portrait
|
|
|
|
|
+ { 270, 270, 270, 270 }, // ui portait upside down
|
|
|
|
|
+ { 0, 0, 0, 0 }, // ui landscape left
|
|
|
|
|
+ { 180, 180, 180, 180 } // ui landscape right
|
|
|
|
|
+ };
|
|
|
|
|
+ *rotation = back_rotations[ui_orientation - 1][device_orientation - 1];
|
|
|
|
|
+ } else {
|
|
|
|
|
+ static const int front_rotations[4][4] = {
|
|
|
|
|
+ { 90, 90, 270, 270 }, // ui portrait
|
|
|
|
|
+ { 270, 270, 90, 90 }, // ui portait upside down
|
|
|
|
|
+ { 0, 0, 180, 180 }, // ui landscape left
|
|
|
|
|
+ { 180, 180, 0, 0 } // ui landscape right
|
|
|
|
|
+ };
|
|
|
|
|
+ *rotation = front_rotations[ui_orientation - 1][device_orientation - 1];
|
|
|
|
|
+ }
|
|
|
|
|
+ #endif
|
|
|
|
|
+
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -231,6 +273,10 @@ static void COREMEDIA_ReleaseFrame(SDL_Camera *device, SDL_Surface *frame)
|
|
|
static void COREMEDIA_CloseDevice(SDL_Camera *device)
|
|
static void COREMEDIA_CloseDevice(SDL_Camera *device)
|
|
|
{
|
|
{
|
|
|
if (device && device->hidden) {
|
|
if (device && device->hidden) {
|
|
|
|
|
+ #ifdef USE_UIKIT_DEVICE_ROTATION
|
|
|
|
|
+ [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
|
|
|
|
|
+ #endif
|
|
|
|
|
+
|
|
|
SDLPrivateCameraData *hidden = (SDLPrivateCameraData *) CFBridgingRelease(device->hidden);
|
|
SDLPrivateCameraData *hidden = (SDLPrivateCameraData *) CFBridgingRelease(device->hidden);
|
|
|
device->hidden = NULL;
|
|
device->hidden = NULL;
|
|
|
|
|
|
|
@@ -358,6 +404,28 @@ static bool COREMEDIA_OpenDevice(SDL_Camera *device, const SDL_CameraSpec *spec)
|
|
|
hidden.session = session;
|
|
hidden.session = session;
|
|
|
hidden.delegate = delegate;
|
|
hidden.delegate = delegate;
|
|
|
hidden.current_sample = NULL;
|
|
hidden.current_sample = NULL;
|
|
|
|
|
+
|
|
|
|
|
+ #ifdef USE_UIKIT_DEVICE_ROTATION
|
|
|
|
|
+ // When using a camera, we turn on device orientation tracking. The docs note that this turns on
|
|
|
|
|
+ // the device's accelerometer, so I assume this burns power, so we don't leave this running all
|
|
|
|
|
+ // the time. These calls nest, so we just need to call the matching `end` message when we close.
|
|
|
|
|
+ // You _can_ get an actual events through this mechanism, but we just want to be able to call
|
|
|
|
|
+ // -[UIDevice orientation], which will update with real info while notificatons are enabled.
|
|
|
|
|
+ UIDevice *uidevice = [UIDevice currentDevice];
|
|
|
|
|
+ [uidevice beginGeneratingDeviceOrientationNotifications];
|
|
|
|
|
+ hidden.last_device_orientation = uidevice.orientation;
|
|
|
|
|
+ if (!UIDeviceOrientationIsValidInterfaceOrientation(hidden.last_device_orientation)) {
|
|
|
|
|
+ // accelerometer isn't ready yet or the phone is laying flat or something. Just try to guess from how the UI is oriented at the moment.
|
|
|
|
|
+ switch ([UIApplication sharedApplication].statusBarOrientation) {
|
|
|
|
|
+ case UIInterfaceOrientationPortrait: hidden.last_device_orientation = UIDeviceOrientationPortrait; break;
|
|
|
|
|
+ case UIInterfaceOrientationPortraitUpsideDown: hidden.last_device_orientation = UIDeviceOrientationPortraitUpsideDown; break;
|
|
|
|
|
+ case UIInterfaceOrientationLandscapeLeft: hidden.last_device_orientation = UIDeviceOrientationLandscapeRight; break; // Apple docs say UI and device orientations are reversed in landscape.
|
|
|
|
|
+ case UIInterfaceOrientationLandscapeRight: hidden.last_device_orientation = UIDeviceOrientationLandscapeLeft; break;
|
|
|
|
|
+ default: hidden.last_device_orientation = UIDeviceOrientationPortrait; break; // oh well.
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ #endif
|
|
|
|
|
+
|
|
|
device->hidden = (struct SDL_PrivateCameraData *)CFBridgingRetain(hidden);
|
|
device->hidden = (struct SDL_PrivateCameraData *)CFBridgingRetain(hidden);
|
|
|
|
|
|
|
|
[session startRunning]; // !!! FIXME: docs say this can block while camera warms up and shouldn't be done on main thread. Maybe push through `queue`?
|
|
[session startRunning]; // !!! FIXME: docs say this can block while camera warms up and shouldn't be done on main thread. Maybe push through `queue`?
|