From 0793000a6b4e9f141dd2a18e9336a39cf98b4a07 Mon Sep 17 00:00:00 2001 From: Quentin Date: Fri, 5 Apr 2019 16:44:01 +0100 Subject: [PATCH] [camera] Added orientation metadata to iOS recorded videos. --- packages/camera/CHANGELOG.md | 6 +++ packages/camera/example/ios/Runner/Info.plist | 1 + packages/camera/example/lib/main.dart | 5 +- packages/camera/ios/Classes/CameraPlugin.m | 46 +++++++++++++++++-- packages/camera/lib/camera.dart | 28 +++++++++-- packages/camera/pubspec.yaml | 2 +- 6 files changed, 75 insertions(+), 13 deletions(-) diff --git a/packages/camera/CHANGELOG.md b/packages/camera/CHANGELOG.md index 4f4f160cf85a..59b704cad517 100644 --- a/packages/camera/CHANGELOG.md +++ b/packages/camera/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.4.4 + +* Added rotation metadata to iOS recorded videos. +* **Breaking change**. The `aspectRatio` parameter now returns width/height instead of height/width as aspect ratio is always width:height. +* **Breaking change**. Due to platform specific handling of Texture objects, the `CameraPreview` now use an `AspectRatio` and `RotatedBox` widget internally to display the preview with the correct ratio and rotation. Users should not wrap `CameraPreview` in a `AspectRatio` anymore. + ## 0.4.3+2 * Bump the minimum Flutter version to 1.2.0. diff --git a/packages/camera/example/ios/Runner/Info.plist b/packages/camera/example/ios/Runner/Info.plist index f389a129e028..bc69da2fb686 100644 --- a/packages/camera/example/ios/Runner/Info.plist +++ b/packages/camera/example/ios/Runner/Info.plist @@ -41,6 +41,7 @@ UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortraitUpsideDown UISupportedInterfaceOrientations~ipad diff --git a/packages/camera/example/lib/main.dart b/packages/camera/example/lib/main.dart index ca01a7ac0f57..f8ef8bdd8bd5 100644 --- a/packages/camera/example/lib/main.dart +++ b/packages/camera/example/lib/main.dart @@ -94,10 +94,7 @@ class _CameraExampleHomeState extends State { ), ); } else { - return AspectRatio( - aspectRatio: controller.value.aspectRatio, - child: CameraPreview(controller), - ); + return CameraPreview(controller); } } diff --git a/packages/camera/ios/Classes/CameraPlugin.m b/packages/camera/ios/Classes/CameraPlugin.m index 7d5d28d3a3a7..74e4cad0e3f4 100644 --- a/packages/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/ios/Classes/CameraPlugin.m @@ -96,6 +96,7 @@ - (UIImageOrientation)getImageRotation { float yxAtan = (atan2(_motionManager.accelerometerData.acceleration.y, _motionManager.accelerometerData.acceleration.x)) * 180 / M_PI; + if (isNearValue(-90.0, yxAtan)) { return UIImageOrientationRight; } else if (isNearValueABS(180.0, yxAtan)) { @@ -184,14 +185,15 @@ - (instancetype)initWithCameraName:(NSString *)cameraName @{(NSString *)kCVPixelBufferPixelFormatTypeKey : @(videoFormat)}; [_captureVideoOutput setAlwaysDiscardsLateVideoFrames:YES]; [_captureVideoOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()]; - AVCaptureConnection *connection = [AVCaptureConnection connectionWithInputPorts:_captureVideoInput.ports output:_captureVideoOutput]; + if ([_captureDevice position] == AVCaptureDevicePositionFront) { connection.videoMirrored = YES; } - connection.videoOrientation = AVCaptureVideoOrientationPortrait; + connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight; + [_captureSession addInputWithNoConnections:_captureVideoInput]; [_captureSession addOutputWithNoConnections:_captureVideoOutput]; [_captureSession addConnection:connection]; @@ -536,21 +538,28 @@ - (BOOL)setupWriterForPath:(NSString *)path { [self setUpCaptureSessionForAudio]; } _videoWriter = [[AVAssetWriter alloc] initWithURL:outputURL - fileType:AVFileTypeQuickTimeMovie + fileType:AVFileTypeMPEG4 error:&error]; NSParameterAssert(_videoWriter); if (error) { _eventSink(@{@"event" : @"error", @"errorDescription" : error.description}); return NO; } + NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:AVVideoCodecH264, AVVideoCodecKey, - [NSNumber numberWithInt:_previewSize.height], AVVideoWidthKey, - [NSNumber numberWithInt:_previewSize.width], AVVideoHeightKey, + [NSNumber numberWithInt:_previewSize.width], AVVideoWidthKey, + [NSNumber numberWithInt:_previewSize.height], AVVideoHeightKey, nil]; + _videoWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings]; + NSParameterAssert(_videoWriterInput); + // Add orientation metadata. + CGFloat rotationDegrees = [self getDeviceRotation]; + _videoWriterInput.transform = CGAffineTransformMakeRotation(rotationDegrees * M_PI / 180); + _videoWriterInput.expectsMediaDataInRealTime = YES; // Add the audio input @@ -568,6 +577,7 @@ - (BOOL)setupWriterForPath:(NSString *)path { _audioWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:audioOutputSettings]; _audioWriterInput.expectsMediaDataInRealTime = YES; + [_videoWriter addInput:_videoWriterInput]; [_videoWriter addInput:_audioWriterInput]; [_captureVideoOutput setSampleBufferDelegate:self queue:_dispatchQueue]; @@ -603,6 +613,32 @@ - (void)setUpCaptureSessionForAudio { } } } + +- (float)getDeviceRotation { + float const threshold = 45.0; + BOOL (^isNearValue)(float value1, float value2) = ^BOOL(float value1, float value2) { + return fabsf(value1 - value2) < threshold; + }; + BOOL (^isNearValueABS)(float value1, float value2) = ^BOOL(float value1, float value2) { + return isNearValue(fabsf(value1), fabsf(value2)); + }; + float yxAtan = (atan2(_motionManager.accelerometerData.acceleration.y, + _motionManager.accelerometerData.acceleration.x)) * + 180 / M_PI; + if (isNearValue(-90.0, yxAtan)) { + return 90; + } else if (isNearValueABS(180.0, yxAtan)) { + return 0; + } else if (isNearValueABS(0.0, yxAtan)) { + return 180; + } else if (isNearValue(90.0, yxAtan)) { + return 270; + } + // If none of the above, then the device is likely facing straight down or straight up -- just + // pick something arbitrary + return 0; +} + @end @interface CameraPlugin () diff --git a/packages/camera/lib/camera.dart b/packages/camera/lib/camera.dart index 2db347092f53..3461a29c97ce 100644 --- a/packages/camera/lib/camera.dart +++ b/packages/camera/lib/camera.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/foundation.dart'; @@ -115,8 +116,27 @@ class CameraPreview extends StatelessWidget { @override Widget build(BuildContext context) { + // ISSUE: The Texture seems to return a buffer in portrait in Android (width and height are inverted) and a buffer in landscape on iOS (which respects the aspect ratio). + // The ideal code (if the Texture was oriented properly in landscape for both Android and iOS) would be: + /* + RotatedBox( + quarterTurns: controller.description.sensorOrientation ~/ 90, + child: AspectRatio( + aspectRatio: controller.value.aspectRatio, + child: Texture(textureId: controller._textureId), + ), + ) + */ return controller.value.isInitialized - ? Texture(textureId: controller._textureId) + ? RotatedBox( + quarterTurns: Platform.isAndroid ? 0 : 1, + child: AspectRatio( + aspectRatio: Platform.isAndroid + ? 1 / controller.value.aspectRatio + : controller.value.aspectRatio, + child: Texture(textureId: controller._textureId), + ), + ) : Container(); } } @@ -156,12 +176,14 @@ class CameraValue { /// The size of the preview in pixels. /// /// Is `null` until [isInitialized] is `true`. + /// + /// The preview size may be smaller than the size of the recorded video (for performance issues). But the aspect ratio will be preserved. final Size previewSize; - /// Convenience getter for `previewSize.height / previewSize.width`. + /// Convenience getter for `previewSize.width / previewSize.height`. /// /// Can only be called when [initialize] is done. - double get aspectRatio => previewSize.height / previewSize.width; + double get aspectRatio => previewSize.width / previewSize.height; bool get hasError => errorDescription != null; diff --git a/packages/camera/pubspec.yaml b/packages/camera/pubspec.yaml index 4649e80bb429..5d7794bed80c 100644 --- a/packages/camera/pubspec.yaml +++ b/packages/camera/pubspec.yaml @@ -2,7 +2,7 @@ name: camera description: A Flutter plugin for getting information about and controlling the camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video, and streaming image buffers to dart. -version: 0.4.3+2 +version: 0.4.4 authors: - Flutter Team - Luigi Agosti