camera orientation

camera

手机相机录视频或者拍照,需要考虑设备的方向,主要在两方面

  1. 采集过程中,无论手机什么方向采集,采集画面都是正着看,不用侧头
  2. 录制/或者拍照后,视频/图片在正常手持设备的情况下正常显示,不用横着手机

正常手持一般都是竖着拿手机,所以就算横着拍摄,竖着拿手机也需要正常显示,不理解可以参考下系统相机。

所以这里就存在一个方向修正的问题。

device orientation

苹果手机,如果设置里锁定了方向,是没办法通过 [UIDevice currentDevice].orientation这个方法拿到的,同样UIDeviceOrientationDidChangeNotification这个通知也获取不到。同理 [[UIApplication sharedApplication] statusBarOrientation];**UIApplicationDidChangeStatusBarOrientationNotification** 也是不可用的。

可以通过 CMMotionManager 监听设备的方向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
_motionManager = [[CMMotionManager alloc] init];
_motionManager.deviceMotionUpdateInterval = 1/15.0;
if (!_motionManager.deviceMotionAvailable) {
_motionManager = nil;
return self;
}
OBJC_WEAK(self)
[_motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue] withHandler: ^(CMDeviceMotion
*motion, NSError *error){

[weak_self performSelectorOnMainThread:@selector(handleDeviceMotion:) withObject:motion waitUntilDone:YES];
}];

- (void)handleDeviceMotion:(CMDeviceMotion *)deviceMotion {
double x = deviceMotion.gravity.x;
double y = deviceMotion.gravity.y;
if (fabs(y) >= fabs(x)) {
if (y >= 0) {
_deviceOrientation = UIDeviceOrientationPortraitUpsideDown;
_videoOrientation = AVCaptureVideoOrientationPortraitUpsideDown;
} else {
_deviceOrientation = UIDeviceOrientationPortrait;
_videoOrientation = AVCaptureVideoOrientationPortrait;
}
} else {
if (x >= 0) {
_deviceOrientation = UIDeviceOrientationLandscapeRight;
_videoOrientation = AVCaptureVideoOrientationLandscapeRight;
} else {
_deviceOrientation = UIDeviceOrientationLandscapeLeft;
_videoOrientation = AVCaptureVideoOrientationLandscapeLeft;
}
}
}

capture

项目中需要支持设置滤镜、特效等等,所以显示采集的图像需要自己渲染,通过官方的AVCaptureSession配置好采集流程,不管是竖着还是横着采集,获取到的pixelBuffer都是横向的,因为默认就是home键在右边采集图像,但是显示的视图一般都是竖向,所以这里要做一个90°的旋转处理,OPENGL 就是一个旋转矩阵。

1
2
3
4
5
6
7
8
9
10
11
12
void main()
{
// z
mat4 rotationMatrix = mat4(cos(angle) , sin(angle) , 0.0, 0.0,
-sin(angle) , cos(angle) , 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);

vec4 outPosition = rotationMatrix * position;
gl_Position = outPosition;
textureCoordinate = inputTextureCoordinate.xy;
}

如果只是解决采集画面显示,也可以通过设置connection.videoOrientation = [self currentVideoOrientation]; 来解决
项目中没有用这个,因为后面还需要录制视频导出视频文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 当前设备取向
- (AVCaptureVideoOrientation)currentVideoOrientation{
AVCaptureVideoOrientation orientation;
switch (self.motionManager.deviceOrientation) {
case UIDeviceOrientationPortrait:
orientation = AVCaptureVideoOrientationPortrait;
break;
case UIDeviceOrientationLandscapeLeft:
orientation = AVCaptureVideoOrientationLandscapeRight;
break;
case UIDeviceOrientationLandscapeRight:
orientation = AVCaptureVideoOrientationLandscapeLeft;
break;
case UIDeviceOrientationPortraitUpsideDown:
orientation = AVCaptureVideoOrientationPortraitUpsideDown;
break;
default:
orientation = AVCaptureVideoOrientationPortrait;
break;
}
return orientation;
}

Record

录制过程通过AVAssetWriterInput *writerInput = [AVAssetWriterInput assetWriterInputWithMediaType:obj outputSettings:options];[self.mPixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:ts] 来积累pixelBuffer, 为了保障导出的视频可以正常显示,需要记录视频的方向,通过设置writerInput.transform[ = self transformFromCurrentVideoOrientationToOrientation:AVCaptureVideoOrientationPortrait],系统自动帮我们做转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 旋转视频方向函数实现
- (CGAffineTransform)transformFromCurrentVideoOrientationToOrientation:(AVCaptureVideoOrientation)orientation {
CGFloat orientationAngleOffset = [self angleOffsetFromPortraitOrientationToOrientation:orientation];
CGFloat videoOrientationAngleOffset = [self angleOffsetFromPortraitOrientationToOrientation:self.currentOrientation];
CGFloat angleOffset;
if (self.position == AVCaptureDevicePositionBack) {
angleOffset = videoOrientationAngleOffset - orientationAngleOffset + M_PI_2;
} else {
angleOffset = orientationAngleOffset - videoOrientationAngleOffset + M_PI_2;
}
CGAffineTransform transform = CGAffineTransformMakeRotation(angleOffset);
return transform;
}

- (CGFloat)angleOffsetFromPortraitOrientationToOrientation:(AVCaptureVideoOrientation)orientation {
CGFloat angle = 0.0;
switch (orientation) {
case AVCaptureVideoOrientationPortrait:
angle = 0.0;
break;
case AVCaptureVideoOrientationPortraitUpsideDown:
angle = M_PI;
break;
case AVCaptureVideoOrientationLandscapeRight:
angle = -M_PI_2;
break;
case AVCaptureVideoOrientationLandscapeLeft:
angle = M_PI_2;
break;
}
return angle;
}

主要就是通过录制视频时候的设备方向跟显示方向计算一下transform

Pic

拍照生成图片是同样的道理,直接给出方法,主要就是图片方向的旋转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (UIImageOrientation)getImageRotationOrientationFromCaptureVideoOrientation:(AVCaptureVideoOrientation)orientation {
UIImageOrientation imageOrientation;
switch (orientation) {
case AVCaptureVideoOrientationPortrait:
imageOrientation = UIImageOrientationRight;
break;
case AVCaptureVideoOrientationPortraitUpsideDown:
imageOrientation = UIImageOrientationLeft;
break;
case AVCaptureVideoOrientationLandscapeRight:
imageOrientation = UIImageOrientationUp;
break;
case AVCaptureVideoOrientationLandscapeLeft:
imageOrientation = UIImageOrientationDown;
break;
}
return imageOrientation;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
+ (UIImage *)image:(UIImage *)image rotation:(UIImageOrientation)orientation {
long double rotate = 0.0;
CGRect rect;
float translateX = 0;
float translateY = 0;
float scaleX = 1.0;
float scaleY = 1.0;

switch (orientation) {
case UIImageOrientationLeft:
rotate = M_PI_2;
rect = CGRectMake(0, 0, image.size.height, image.size.width);
translateX = 0;
translateY = -rect.size.width;
scaleY = rect.size.width/rect.size.height;
scaleX = rect.size.height/rect.size.width;
break;
case UIImageOrientationRight:
rotate = -M_PI_2;
rect = CGRectMake(0, 0, image.size.height, image.size.width);
translateX = -rect.size.height;
translateY = 0;
scaleY = rect.size.width/rect.size.height;
scaleX = rect.size.height/rect.size.width;
break;
case UIImageOrientationDown:
rotate = M_PI;
rect = CGRectMake(0, 0, image.size.width, image.size.height);
translateX = -rect.size.width;
translateY = -rect.size.height;
break;
default:
rotate = 0.0;
rect = CGRectMake(0, 0, image.size.width, image.size.height);
translateX = 0;
translateY = 0;
break;
}

UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
//做CTM变换
CGContextTranslateCTM(context, 0.0, rect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextRotateCTM(context, rotate);
CGContextTranslateCTM(context, translateX, translateY);

CGContextScaleCTM(context, scaleX, scaleY);
//绘制图片
CGContextDrawImage(context, CGRectMake(0, 0, rect.size.width, rect.size.height), image.CGImage);

UIImage *newPic = UIGraphicsGetImageFromCurrentImageContext();

return newPic;
}