Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

[camera] Added orientation metadata to iOS recorded videos. #1452

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/camera/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
1 change: 1 addition & 0 deletions packages/camera/example/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
Expand Down
5 changes: 1 addition & 4 deletions packages/camera/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,7 @@ class _CameraExampleHomeState extends State<CameraExampleHome> {
),
);
} else {
return AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: CameraPreview(controller),
);
return CameraPreview(controller);
}
}

Expand Down
46 changes: 41 additions & 5 deletions packages/camera/ios/Classes/CameraPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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
Expand All @@ -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];
Expand Down Expand Up @@ -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 ()
Expand Down
28 changes: 25 additions & 3 deletions packages/camera/lib/camera.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

import 'dart:async';
import 'dart:io';
import 'dart:typed_data';

import 'package:flutter/foundation.dart';
Expand Down Expand Up @@ -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();
}
}
Expand Down Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion packages/camera/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 <[email protected]>
- Luigi Agosti <[email protected]>
Expand Down