如何用Avfoundation 实现照相机镜头回不去了拉近拉远

一聚教程网:一个值得你收藏的教程网站
ios开发之Swift使用AVFoundation实现条形码扫描(附:拉近镜头改善读取)
时间: 00:00:00
编辑:简简单单
来源:转载
1,条形码(一维码)的扫描读取
原来写过一篇文章,介绍如何使用摄像头扫描读取:Swift - 二维码QRCode的读取(从图片读取 ,或通过摄像头扫描)要通过摄像头读取条形码,只需要将原来二维码读取代码中 metadataObjectTypes 做如下修改即可:
self.output.metadataObjectTypes = [AVMetadataObjectTypeEAN13Code,
&&&&&&&&AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code,
&&&&&&&&AVMetadataObjectTypeCode39Code,AVMetadataObjectTypeCode93Code]
2,拉近镜头,改善条形码读取效果
有网友反应,如果条码太小的时候(比如iwatch上支付宝的生成的小条码)就会识别不出来。
解决办法是:像支付宝、QQ一样,通过代码拉近镜头焦距,放大内容区域让机器更好的识别。
下面左图是原始大小,右图将画面放大了1.5倍:
import UIKit
import AVFoundation
class ViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate,
UIAlertViewDelegate{
&&&&var scanRectView:UIView!
&&&&var device:AVCaptureDevice!
&&&&var input:AVCaptureDeviceInput!
&&&&var output:AVCaptureMetadataOutput!
&&&&var session:AVCaptureSession!
&&&&var preview:AVCaptureVideoPreviewLayer!
&&&&override func viewDidLoad() {
&&&&&&&&super.viewDidLoad()
&&&&&&&&fromCamera()
&&&&//通过摄像头扫描
&&&&func fromCamera() {
&&&&&&&&do{
&&&&&&&&&&&&self.device = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
&&&&&&&&&&&&&
&&&&&&&&&&&&self.input = try AVCaptureDeviceInput(device: device)
&&&&&&&&&&&&&
&&&&&&&&&&&&self.output = AVCaptureMetadataOutput()
&&&&&&&&&&&&output.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue())
&&&&&&&&&&&&&
&&&&&&&&&&&&self.session = AVCaptureSession()
&&&&&&&&&&&&if UIScreen.mainScreen().bounds.size.height&500 {
&&&&&&&&&&&&&&&&self.session.sessionPreset = AVCaptureSessionPreset640x480
&&&&&&&&&&&&}else{
&&&&&&&&&&&&&&&&self.session.sessionPreset = AVCaptureSessionPresetHigh
&&&&&&&&&&&&}
&&&&&&&&&&&&&
&&&&&&&&&&&&self.session.addInput(self.input)
&&&&&&&&&&&&self.session.addOutput(self.output)
&&&&&&&&&&&&&
&&&&&&&&&&&&self.output.metadataObjectTypes = [AVMetadataObjectTypeEAN13Code,
&&&&&&&&&&&&&&&&AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code,
&&&&&&&&&&&&&&&&AVMetadataObjectTypeCode39Code,AVMetadataObjectTypeCode93Code]
&&&&&&&&&&&&&
&&&&&&&&&&&&//计算中间可探测区域
&&&&&&&&&&&&let windowSize:CGSize = UIScreen.mainScreen().bounds.
&&&&&&&&&&&&let scanSize:CGSize = CGSizeMake(windowSize.width*3/4,
&&&&&&&&&&&&&&&&windowSize.width*3/4);
&&&&&&&&&&&&var scanRect:CGRect = CGRectMake((windowSize.width-scanSize.width)/2,
&&&&&&&&&&&&&&&&(windowSize.height-scanSize.height)/2, scanSize.width, scanSize.height);
&&&&&&&&&&&&//计算rectOfInterest 注意x,y交换位置
&&&&&&&&&&&&scanRect = CGRectMake(scanRect.origin.y/windowSize.height,
&&&&&&&&&&&&&&&&scanRect.origin.x/windowSize.width,
&&&&&&&&&&&&&&&&scanRect.size.height/windowSize.height,
&&&&&&&&&&&&&&&&scanRect.size.width/windowSize.width);
&&&&&&&&&&&&//设置可探测区域
&&&&&&&&&&&&self.output.rectOfInterest = scanRect
&&&&&&&&&&&&&
&&&&&&&&&&&&self.preview = AVCaptureVideoPreviewLayer(session:self.session)
&&&&&&&&&&&&self.preview.videoGravity = AVLayerVideoGravityResizeAspectFill
&&&&&&&&&&&&self.preview.frame = UIScreen.mainScreen().bounds
&&&&&&&&&&&&self.view.layer.insertSublayer(self.preview, atIndex:0)
&&&&&&&&&&&&&
&&&&&&&&&&&&//添加中间的探测区域绿框
&&&&&&&&&&&&self.scanRectView = UIView();
&&&&&&&&&&&&self.view.addSubview(self.scanRectView)
&&&&&&&&&&&&self.scanRectView.frame = CGRectMake(0, 0, scanSize.width, scanSize.height);
&&&&&&&&&&&&self.scanRectView.center = CGPointMake(
&&&&&&&&&&&&&&&&CGRectGetMidX(UIScreen.mainScreen().bounds),
&&&&&&&&&&&&&&&&CGRectGetMidY(UIScreen.mainScreen().bounds));
&&&&&&&&&&&&self.scanRectView.layer.borderColor = UIColor.greenColor().CGColor
&&&&&&&&&&&&self.scanRectView.layer.borderWidth = 1;
&&&&&&&&&&&&&
&&&&&&&&&&&&//开始捕获
&&&&&&&&&&&&self.session.startRunning()
&&&&&&&&&&&&&
&&&&&&&&&&&&//放大
&&&&&&&&&&&&do {
&&&&&&&&&&&&&&&&try self.device!.lockForConfiguration()
&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&NSLog(&Error: lockForConfiguration.&);
&&&&&&&&&&&&}
&&&&&&&&&&&&self.device!.videoZoomFactor = 1.5
&&&&&&&&&&&&self.device!.unlockForConfiguration()
&&&&&&&&&&&&&
&&&&&&&&}catch _ as NSError{
&&&&&&&&&&&&//打印错误消息
&&&&&&&&&&&&let errorAlert = UIAlertView(title: &提醒&,
&&&&&&&&&&&&&&&&message: &请在iPhone的\&设置-隐私-相机\&选项中,允许本程序访问您的相机&,
&&&&&&&&&&&&&&&&delegate: self,
&&&&&&&&&&&&&&&&cancelButtonTitle: &确定&)
&&&&&&&&&&&&errorAlert.show()
&&&&//摄像头捕获
&&&&func captureOutput(captureOutput: AVCaptureOutput!,
&&&&&&&&didOutputMetadataObjects metadataObjects: [AnyObject]!,
&&&&&&&&fromConnection connection: AVCaptureConnection!) {
&&&&&&&&&&&&&
&&&&&&&&&&&&var stringValue:String?
&&&&&&&&&&&&if metadataObjects.count & 0 {
&&&&&&&&&&&&&&&&let metadataObject = metadataObjects[0]
&&&&&&&&&&&&&&&&&&&&as! AVMetadataMachineReadableCodeObject
&&&&&&&&&&&&&&&&stringValue = metadataObject.stringValue
&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&if stringValue != nil{
&&&&&&&&&&&&&&&&&&&&self.session.stopRunning()
&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&}
&&&&&&&&&&&&self.session.stopRunning()
&&&&&&&&&&&&//输出结果
&&&&&&&&&&&&let alertView = UIAlertView(title: &二维码&, message: stringValue,
&&&&&&&&&&&&&&&&delegate: self, cancelButtonTitle: &确定&)
&&&&&&&&&&&&alertView.show()
&&&&//消息框确认后消失
&&&&func alertView(alertView: UIAlertView, willDismissWithButtonIndex buttonIndex: Int) {
&&&&&&&&//继续扫描
&&&&&&&&self.session.startRunning()
&&&&override func didReceiveMemoryWarning() {
&&&&&&&&super.didReceiveMemoryWarning()iOS-AVFoundation自定义相机详解 - 简书
iOS-AVFoundation自定义相机详解
AVFoundation 中关于视频主要的类
先看相机实现步骤,下面是对每一步需要做的事情详解
1.创建session(捕捉会话)2.创建device input(捕捉设备输入)3.预览view4.创建capture output(捕捉的输出)5.拍照、录视频(元数据转成图片或文件)
捕捉会话——AVCaptureSession
AVCaptureSession(捕捉会话管理):它从物理设备得到数据流(比如摄像头和麦克风),输出到一个或多个目的地,它可以通过会话预设值(session preset),来控制捕捉数据的格式和质量下面是创建一个 session 的代码:
AVCaptureSession *captureSession = [[AVCaptureSession alloc]init];
[captureSession setSessionPreset:AVCaptureSessionPresetPhoto];
SessionPreset在iOS中大概有11个
NSString *const AVCaptureSessionPresetP
NSString *const AVCaptureSessionPresetH
NSString *const AVCaptureSessionPresetM
NSString *const AVCaptureSessionPresetL
NSString *const AVCaptureSessionPreset352x288;
NSString *const AVCaptureSessionPreset640x480;
NSString *const AVCaptureSessionPreset;
NSString *const AVCaptureSessionPreset;
NSString *const AVCaptureSessionPresetiFrame960x540;
NSString *const AVCaptureSessionPresetiFrame;
NSString *const AVCaptureSessionPresetInputP
第一个代表高像素图片输出;接下来三种为相对预设(low, medium, high),这些预设的编码配置会因设备不同而不同,如果选择high,那么你选定的相机会提供给你该设备所能支持的最高画质;再后面就是特定分辨率的预设(352x288 VGA,
VGA, 640x480 VGA, 960x540 iFrame,
iFrame);最后一个代表 capture session 不去控制音频与视频输出设置,而是通过已连接的捕获设备的 activeFormat 来反过来控制 capture session 的输出质量等级
注意:所有对 capture session 的调用都是阻塞的,因此建议将它们分配到后台串行队列中,不过这里为了简单,不考虑性能,所以省略了dispatch queue
捕捉输入——AVCaptureDeviceInput
AVCaptureDeviceInput(捕捉设备):它实际上是为摄像头和麦克风等物理设备定义的接口,我们可以通过它来访问或控制这些硬件设备。比如控制摄像头的对焦、曝光等。
该方法会返回当前能够输入视频的全部设备,包括前后摄像头和外接设备
NSArray *videoDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
该方法会返回当前能够输入音频的全部设备
NSArray *videoDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio];
// 获取视频输入设备,该方法默认返回iPhone的后置摄像头
AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
// 将捕捉设备加入到捕捉会话中
AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
if (videoInput) {
if ([_captureSession canAddInput:videoInput]){
[_captureSession addInput:videoInput];
// 音频输入
AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
AVCaptureDeviceInput *audioIn = [[AVCaptureDeviceInput alloc] initWithDevice:audioDevice error:error];
if ([_captureSession canAddInput:audioIn]){
[_captureSession addInput:audioIn];
捕捉预览——AVCaptureVideoPreviewLayer/OpenGL ES
AVCaptureVideoPreviewLayer(捕捉预览):它是CALayer的子类,可被用于自动显示相机产生的实时图像。previewLayer支持视频重力概念,可以控制视频内容渲染的缩放和拉效果(关于视频重力,将在后面进行详解)
// 创建一个previewLayer
AVCaptureVideoPreviewLayer
*previewLayer = [[AVCaptureVideoPreviewLayer alloc] initpWithFrame:self.view.bounds]
[previewLayer.layer setVideoGravity:AVLayerVideoGravityResizeAspect];
[previewLayer.layer setSession:session];
// 将屏幕坐标系的点转换为previewLayer坐标系的点
- (CGPoint)captureDevicePointForPoint:(CGPoint)point {
return [previewLayer.layer captureDevicePointOfInterestForPoint:point];
它看起来有点像输出,但其实不是,它仅用来预览摄像头捕捉的画面。真正用于输出的是AVCaptureSession(previewLayer拥有session,session拥有outputs);
它的坐标系和屏幕的坐标系不同,如果点击某区域实现对焦时,我们需要将设备的坐标系转换为实时预览图的坐标;
它的坐标原点永远都在右上角,这和我们手机的坐标系不同,手机坐标系的原点是不变的。因此拍照或录制视频时,要先得到设备方向(关于方向问题,后面会详解),计算输出的旋转角度。
捕捉预览除了用AVCaptureVideoPreviewLayer外,还可以用OpenGL ES绘制,我们可以从输出数据流捕捉单一的图像帧,并使用 OpenGL ES手动地把它们显示在 view 上。如果我们想对预览视图进行操作,如使用滤镜,我们就必须这样做。这里不做深入研究,下面给出一段简单的实现代码:
// 创建glview
EAGLContext *context = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];
GLKView *glView = [[GLKView alloc]initWithFrame:self.view.bounds context:context];
[EAGLContext setCurrentContext:context];
[self.view addSubview:glView];
glView.transform = CGAffineTransformMakeRotation(M_PI_2);
glView.frame = [UIApplication sharedApplication].keyWindow.
// 在视频输出函数中绘制出来
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection{
if (_glview.context != [EAGLContext currentContext]) {
[EAGLContext setCurrentContext:_glview.context];
CVImageBufferRef imageRef = CMSampleBufferGetImageBuffer(sampleBuffer);
CIImage *image = [CIImage imageWithCVImageBuffer:imageRef];
[_glview bindDrawable];
[_cicontext drawImage:image inRect:image.extent fromRect:image.extent];
[_glview display];
捕捉连接——AVCaptureConnection
捕捉连接负责将捕捉会话接收的媒体类型和输出连接起来,比如AVCaptureAudioDataOutput可以接受音频数据,AVCaptureVideoDataOutput可以接受视频数据。会话通过捕捉连接,确定哪些输入视频,那些输入音频。通过对捕捉连接的访问,可以对信号流进行底层控制,比如禁用某些特定的连接。
// 设置视频捕捉连接
_videoConnection = [videoOutput connectionWithMediaType:AVMediaTypeVideo];
_videoConnection.videoOrientation = self.referenceO
// 在视频元数据的输出函数中,如果捕捉连接是视频连接,则写入视频数据
if (connection == _videoConnection){
if ([self inputsReadyToRecord]){
[self writeSampleBuffer:sampleBuffer ofType:AVMediaTypeVideo];
// 设置音频捕捉连接
_audioConnection = [audioOut connectionWithMediaType:AVMediaTypeAudio];
// 在视频元数据的输出函数中,如果捕捉连接是音频连接,则写入音频数据
if (connection == _audioConnection){
if (_readyToRecordVideo && _readyToRecordAudio){
[self writeSampleBuffer:sampleBuffer ofType:AVMediaTypeAudio];
拍照——AVCaptureStillImageOutput
AVCaptureStillImageOutput会为我们捕捉高分辨率的图像,起设置如下:
// 创建image output 代码
AVCaptureStillImageOutput *imageOutput = [[AVCaptureStillImageOutput alloc] init];
imageOutput.outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG};
if ([_captureSession canAddOutput:imageOutput]) {
[_captureSession addOutput:imageOutput];
_imageOutput = imageO
// 输出图片
AVCaptureConnection *connection = [_imageOutput connectionWithMediaType:AVMediaTypeVideo];
if (connection.isVideoOrientationSupported) {
connection.videoOrientation = [self currentVideoOrientation];
id takePictureSuccess = ^(CMSampleBufferRef sampleBuffer,NSError *error){
if (sampleBuffer == NULL) {
[self showError:error];
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:sampleBuffer];
UIImage *image = [[UIImage alloc]initWithData:imageData];
[_imageOutput captureStillImageAsynchronouslyFromConnection:connection completionHandler:takePictureSuccess];
音频——AVCaptureAudioDataOutput
AVCaptureAudioDataOutput(音频数据输出):它输出硬件实时捕捉的音频数字样本,还有一个音频输出类是AVCaptureAudioFileOutput,不过它只能在录制完成后输出完整的音频文件。
// 音频输出
AVCaptureAudioDataOutput *audioOut = [[AVCaptureAudioDataOutput alloc] init];
[audioOut setSampleBufferDelegate:self queue:captureQueue];
if ([_captureSession canAddOutput:audioOut]){
[_captureSession addOutput:audioOut];
视频——AVCaptureVideoDataOutput
AVCaptureVideoDataOutput(视频数据输出):它输出硬件实时捕捉的视频数字样本,还有一个音频和视频输出类是AVCaptureMovieFileOutput,不过它只能在录制完成后输出完整的视频和音频文件。
// 视频输出
AVCaptureVideoDataOutput *videoOut = [[AVCaptureVideoDataOutput alloc] init];
[videoOut setAlwaysDiscardsLateVideoFrames:YES];
[videoOut setVideoSettings:@{(id)kCVPixelBufferPixelFormatTypeKey : [NSNumber numberWithInt:kCVPixelFormatType_32BGRA]}];
[videoOut setSampleBufferDelegate:self queue:captureQueue];
if ([_captureSession canAddOutput:videoOut]){
[_captureSession addOutput:videoOut];
_videoOutput = videoO
生成视频文件——AVAssetWriter、AVAssetWriterInput
AVAssetWriter:用于对媒体资源进行编码并讲其写入到容器文件中,比如一个QuickTime文件。AVAssetWriterInput:用于处理指定的媒体类型,比如音频和视频。AVAssetWriterInputPixelBufferAdaptor:这个类在生成视频文件时提供最优性能,不过Demo没有使用该类,有兴趣的可以去研究一下
// 初始化一个assetWriter
_assetWriter = [[AVAssetWriter alloc] initWithURL:_movieURL fileType:AVFileTypeQuickTimeMovie error:&error];
if (error){
[self showError:error];
// 配置视频源数据输入
- (BOOL)setupAssetWriterVideoInput:(CMFormatDescriptionRef)currentFormatDescription
CGFloat bitsPerP
CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(currentFormatDescription);
NSUInteger numPixels = dimensions.width * dimensions.
NSUInteger bitsPerS
if (numPixels & (640 * 480)){
bitsPerPixel = 4.05;
bitsPerPixel = 11.4;
bitsPerSecond = numPixels * bitsPerP
NSDictionary *videoCompressionSettings = @{AVVideoCodecKey
: AVVideoCodecH264,
AVVideoWidthKey
: [NSNumber numberWithInteger:dimensions.width],
AVVideoHeightKey : [NSNumber numberWithInteger:dimensions.height],
AVVideoCompressionPropertiesKey:@{AVVideoAverageBitRateKey:[NSNumber numberWithInteger:bitsPerSecond],
AVVideoMaxKeyFrameIntervalKey:[NSNumber numberWithInteger:30]}
if ([_assetWriter canApplyOutputSettings:videoCompressionSettings forMediaType:AVMediaTypeVideo])
_assetVideoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoCompressionSettings];
_assetVideoInput.expectsMediaDataInRealTime = YES;
_assetVideoInput.transform = [self transformFromCurrentVideoOrientationToOrientation:self.referenceOrientation];
if ([_assetWriter canAddInput:_assetVideoInput]){
[_assetWriter addInput:_assetVideoInput];
[self showError:_assetWriter.error];
return NO;
[self showError:_assetWriter.error];
return NO;
return YES;
// 配置音频源数据输入
- (BOOL)setupAssetWriterAudioInput:(CMFormatDescriptionRef)currentFormatDescription
size_t aclSize = 0;
const AudioStreamBasicDescription *currentASBD = CMAudioFormatDescriptionGetStreamBasicDescription(currentFormatDescription);
const AudioChannelLayout *currentChannelLayout = CMAudioFormatDescriptionGetChannelLayout(currentFormatDescription, &aclSize);
NSData *currentChannelLayoutData =
if (currentChannelLayout && aclSize & 0 ){
currentChannelLayoutData = [NSData dataWithBytes:currentChannelLayout length:aclSize];
currentChannelLayoutData = [NSData data];
NSDictionary *audioCompressionSettings = @{AVFormatIDKey : [NSNumber numberWithInteger:kAudioFormatMPEG4AAC],
AVSampleRateKey : [NSNumber numberWithFloat:currentASBD-&mSampleRate],
AVEncoderBitRatePerChannelKey : [NSNumber numberWithInt:64000],
AVNumberOfChannelsKey : [NSNumber numberWithInteger:currentASBD-&mChannelsPerFrame],
AVChannelLayoutKey : currentChannelLayoutData};
if ([_assetWriter canApplyOutputSettings:audioCompressionSettings forMediaType:AVMediaTypeAudio])
_assetAudioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:audioCompressionSettings];
_assetAudioInput.expectsMediaDataInRealTime = YES;
if ([_assetWriter canAddInput:_assetAudioInput]){
[_assetWriter addInput:_assetAudioInput];
[self showError:_assetWriter.error];
return NO;
[self showError:_assetWriter.error];
return NO;
return YES;
通过上面的代码,我们就准备好了一个AVAssetWriter了,就可以用它来生产视频文件,我们可以在视频源数据输出函数中写入
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection{
if (_recording) {
CFRetain(sampleBuffer);
dispatch_async(_movieWritingQueue, ^{
if (_assetWriter)
if (connection == _videoConnection)
if (!_readyToRecordVideo){
_readyToRecordVideo = [self setupAssetWriterVideoInput:CMSampleBufferGetFormatDescription(sampleBuffer)];
if ([self inputsReadyToRecord]){
[self writeSampleBuffer:sampleBuffer ofType:AVMediaTypeVideo];
else if (connection == _audioConnection){
if (!_readyToRecordAudio){
_readyToRecordAudio = [self setupAssetWriterAudioInput:CMSampleBufferGetFormatDescription(sampleBuffer)];
if ([self inputsReadyToRecord]){
[self writeSampleBuffer:sampleBuffer ofType:AVMediaTypeAudio];
CFRelease(sampleBuffer);
写入相册——ALAssetsLibrary、PHPhotoLibrary
iOS9.0以前:
ALAssetsLibrary *lab = [[ALAssetsLibrary alloc]init];
// 保存视频
[lab writeVideoAtPathToSavedPhotosAlbum:_movieURL completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
[self showError:error];
iOS9.0以后[PHPhotoLibrary requestAuthorization:^( PHAuthorizationStatus status ) {
if (status == PHAuthorizationStatusAuthorized) {
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
// 保存视频
PHAssetCreationRequest *videoRequest = [PHAssetCreationRequest creationRequestForAsset];
[videoRequest addResourceWithType:PHAssetResourceTypeVideo fileURL:_movieURL options:nil];
} completionHandler:^( BOOL success, NSError * _Nullable error ) {
if (!success) {
[self showError:error];
相机的操作都是一些固定的代码,我就不多讲了,我们只需要注意以下几点:1.闪光灯和手电筒不能同时开启2.在前置摄像头时不能开启手电筒,所有在转换时,会被强制关闭3.前后摄像头需要分别设置闪光灯的开关,所以我们必须记录当前闪光灯的设置状态,在转换完成之后,还需要重新设置一次4.在转换摄像头时,你之前设置的视频输出就无效了,你需要删除原来的视频输出,再重新添加一个新的视频输出(我也不知道为什么会有这种情况,但是音频源数据是一直都有的,视频源数据每次转换摄像头都需要重新设置视频输出)
转换摄像头
- (BOOL)switchCameras{
if (![self canSwitchCameras]) {
return NO;
AVCaptureDevice *videoDevice = [self inactiveCamera];
AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];
if (videoInput) {
[_captureSession beginConfiguration];
[_captureSession removeInput:_deviceInput];
if ([_captureSession canAddInput:videoInput]) {
[_captureSession addInput:videoInput];
_deviceInput = videoI
[_captureSession addInput:_deviceInput];
[_captureSession commitConfiguration];
// 如果从后置转前置,会关闭手电筒,如果之前打开的,需要通知camera更新UI
if (videoDevice.position == AVCaptureDevicePositionFront) {
[self.cameraView changeTorch:NO];
// 闪关灯,前后摄像头的闪光灯是不一样的,所以在转换摄像头后需要重新设置闪光灯
[self changeFlash:_currentflashMode];
// 转换摄像头时,视频输出就无效了,所以在转换回来时,需要把原来的删除了,在重新加一个新的进去
[self resetupVideoOutput];
[self showError:error];
return NO;
return YES;
-(void)resetupVideoOutput{
[_captureSession beginConfiguration];
[_captureSession removeOutput:_videoOutput];
AVCaptureVideoDataOutput *videoOut = [[AVCaptureVideoDataOutput alloc] init];
[videoOut setAlwaysDiscardsLateVideoFrames:YES];
[videoOut setVideoSettings:@{(id)kCVPixelBufferPixelFormatTypeKey : [NSNumber numberWithInt:kCVPixelFormatType_32BGRA]}];
dispatch_queue_t videoCaptureQueue = dispatch_queue_create("Video Capture Queue", DISPATCH_QUEUE_SERIAL);
[videoOut setSampleBufferDelegate:self queue:videoCaptureQueue];
if ([_captureSession canAddOutput:videoOut]) {
[_captureSession addOutput:videoOut];
_videoOutput = videoO
_videoConnection = [videoOut connectionWithMediaType:AVMediaTypeVideo];
[_captureSession commitConfiguration];
AVCaptureDevice *device = [self activeCamera];
if (device.torchMode != torchMode && [device isTorchModeSupported:torchMode]) {
if ([device lockForConfiguration:&error]) {
device.torchMode = torchM
[device unlockForConfiguration];
[self showError:error];
AVCaptureDevice *device = [self activeCamera];
if (device.flashMode != flashMode && [device isFlashModeSupported:flashMode]) {
if ([device lockForConfiguration:&error]) {
device.flashMode = flashM
[device unlockForConfiguration];
[self showError:error];
- (void)focusAtPoint:(CGPoint)point {
AVCaptureDevice *device = [self activeCamera];
if ([self cameraSupportsTapToFocus] && [device isFocusModeSupported:AVCaptureFocusModeAutoFocus]) {
if ([device lockForConfiguration:&error]) {
device.focusPointOfInterest =
device.focusMode = AVCaptureFocusModeAutoF
[device unlockForConfiguration];
[self showError:error];
static const NSString *CameraAdjustingExposureC
- (void)exposeAtPoint:(CGPoint)point{
AVCaptureDevice *device = [self activeCamera];
if ([self cameraSupportsTapToExpose] && [device isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) {
if ([device lockForConfiguration:&error]) {
device.exposurePointOfInterest =
device.exposureMode = AVCaptureExposureModeContinuousAutoE
if ([device isExposureModeSupported:AVCaptureExposureModeLocked]) {
[device addObserver:self
forKeyPath:@"adjustingExposure"
options:NSKeyValueObservingOptionNew
context:&CameraAdjustingExposureContext];
[device unlockForConfiguration];
[self showError:error];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == &CameraAdjustingExposureContext) {
AVCaptureDevice *device = (AVCaptureDevice *)
if (!device.isAdjustingExposure && [device isExposureModeSupported:AVCaptureExposureModeLocked]) {
[object removeObserver:self
forKeyPath:@"adjustingExposure"
context:&CameraAdjustingExposureContext];
dispatch_async(dispatch_get_main_queue(), ^{
if ([device lockForConfiguration:&error]) {
device.exposureMode = AVCaptureExposureModeL
[device unlockForConfiguration];
[self showError:error];
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
自动聚焦/曝光
- (BOOL)resetFocusAndExposureModes{
AVCaptureDevice *device = [self activeCamera];
AVCaptureExposureMode exposureMode = AVCaptureExposureModeContinuousAutoE
AVCaptureFocusMode focusMode = AVCaptureFocusModeContinuousAutoF
BOOL canResetFocus = [device isFocusPointOfInterestSupported] && [device isFocusModeSupported:focusMode];
BOOL canResetExposure = [device isExposurePointOfInterestSupported] && [device isExposureModeSupported:exposureMode];
CGPoint centerPoint = CGPointMake(0.5f, 0.5f);
if ([device lockForConfiguration:&error]) {
if (canResetFocus) {
device.focusMode = focusM
device.focusPointOfInterest = centerP
if (canResetExposure) {
device.exposureMode = exposureM
device.exposurePointOfInterest = centerP
[device unlockForConfiguration];
return YES;
[self showError:error];
return NO;
视频重力——Video gravity
视频重力:控制视频内容渲染的缩放和拉伸效果。举个例子,在我们设置会话时有一个参数session preset,它是用来控制捕捉数据格式和质量了。我的测试机是6s,当我选择参数AVCaptureSessionPresetPhoto时,输出图片大小如下:
Printing description of image:
&CIImage: 0x12c7bdad0 extent [0 0 750 1000]&
affine [1 0 0 -1 0 1000] extent=[0 0 750 1000]
colormatch "QuickTime 'nclc' Video (1,1,6)"-to-workingspace extent=[0 0 750 1000]
IOSurface 0x12da00008 BGRA8 extent=[0 0 750 1000]
当我选择参数AVCaptureSessionPresetHigh时,输出图片大小如下:
Printing description of image:
&CIImage: 0x15f851680 extent [0 0 ]&
affine [1 0 0 -1 0 1920] extent=[0 0 ]
colormatch "QuickTime 'nclc' Video (1,1,6)"-to-workingspace extent=[0 0 ]
IOSurface 0x15f900008 BGRA8 extent=[0 0 ]
可以看出选择不同的session preset,会输出不同大小的图片,但是这些图片都是很大的,这么大的图片要显示在手机预览层,必须要缩放,而视频重力其实就是缩放参数。AVLayerVideoGravityResizeAspect:在预览层区域内缩放视频,保持视频原始宽高比。这是默认值,同时适用大多数情况。使用该参数预览时,有可能不能铺满整个预览视图AVLayerVideoGravityResizeAspectFill:按照视频的宽高比将视频拉伸填满整个图层。使用该参数时,很可能造成视频预览图片被裁剪,而拍摄输出没有被裁剪,这样就会使预览图和最终拍摄的图不一致。AVLayerVideoGravityResize:拉伸视频内容以匹配预览层大小,这个是最不常用的,可能造成视频扭曲。
方向问题——Orientation
设备方向device orientation
// 设备方向
UIDevice *device = [UIDevice currentDevice] ;
switch (device.orientation) {
case UIDeviceOrientationFaceUp:
NSLog(@"屏幕朝上平躺");
case UIDeviceOrientationFaceDown:
NSLog(@"屏幕朝下平躺");
case UIDeviceOrientationUnknown:
NSLog(@"未知方向");
case UIDeviceOrientationLandscapeLeft:
NSLog(@"屏幕向左橫置");
case UIDeviceOrientationLandscapeRight:
NSLog(@"屏幕向右橫置");
case UIDeviceOrientationPortrait:
NSLog(@"屏幕直立");
case UIDeviceOrientationPortraitUpsideDown:
NSLog(@"屏幕直立,上下顛倒");
从上面可以看到所有的设备方向,而视频方向videoOrientation没有那么多分类,它分为:AVCaptureVideoOrientationPortrait
home健在下AVCaptureVideoOrientationPortraitUpsideDown
home健在上AVCaptureVideoOrientationLandscapeRight
home健在右AVCaptureVideoOrientationLandscapeLeft
home健在左这些视频方向,是视频或拍照时的输入方向,而我们的数据输出时会跟具这些输入方向自动对图片或视频进行矩阵变换,以达到最佳的用户体验。这里以拍照举个例子(视频同理):假如你横着手机拍了一张照片,第一次你在拍照前不传入视频方向,它默认为AVCaptureVideoOrientationPortrait,这是正常手机拿着的姿势,所以到输出时不会对图片进行矩阵变换,当你把图片存入相册时,你会发现,你要正确查看这张图,你也需要横着手机看。如果你是倒着手机拍的,就需要倒着手机看。但是如果你在拍照前传入视频方向,比如你横着手机拍,并且home健在右,就传入参数AVCaptureVideoOrientationLandscapeRight,这时你存入相册的照片就可以以正常拿手机的姿势查看它了。
// 在拍照前通过会话连接,传入当前输入视频方向(视频同理也可以这样做)
AVCaptureConnection *connection = [_imageOutput connectionWithMediaType:AVMediaTypeVideo];
if (connection.isVideoOrientationSupported) {
connection.videoOrientation = [self currentVideoOrientation];
苹果给出的类处理后都是默认正常拿手机的姿势观看,不管是图片还是视频,如果我们想拍出的所有图片或视频都需要横着手机看,我们这时可以不传入视频方向,这样视频到输出时就不会被变换,我们在视频输入类中,手动对视频进行transform变换,这样就可以实现我们想要的查看方式,在本例中,视频就是用的这种处理方式。
// 视频的播放方向,后面计算视频旋转角度使用
_referenceOrientation = AVCaptureVideoOrientationP
// 这行代码在设置视频输入方向为默认输入方向
_videoConnection.videoOrientation = AVCaptureVideoOrientationP
// 视频输入类中手动旋转视频方向
_assetVideoInput.transform = [self transformFromCurrentVideoOrientationToOrientation:self.referenceOrientation];
// 旋转视频方向函数实现
(CGAffineTransform)transformFromCurrentVideoOrientationToOrientation:(AVCaptureVideoOrientation)orientation
CGFloat orientationAngleOffset = [self angleOffsetFromPortraitOrientationToOrientation:orientation];
CGFloat videoOrientationAngleOffset = [self angleOffsetFromPortraitOrientationToOrientation:self.motionManager.videoOrientation];
CGFloat angleO
if ([self activeCamera].position == AVCaptureDevicePositionBack) {
angleOffset = orientationAngleOffset - videoOrientationAngleO
angleOffset = videoOrientationAngleOffset - orientationAngleOffset + M_PI_2;
CGAffineTransform transform = CGAffineTransformMakeRotation(angleOffset);
- (CGFloat)angleOffsetFromPortraitOrientationToOrientation:(AVCaptureVideoOrientation)orientation
CGFloat angle = 0.0;
switch (orientation)
case AVCaptureVideoOrientationPortrait:
angle = 0.0;
case AVCaptureVideoOrientationPortraitUpsideDown:
angle = M_PI;
case AVCaptureVideoOrientationLandscapeRight:
angle = -M_PI_2;
case AVCaptureVideoOrientationLandscapeLeft:
angle = M_PI_2;

我要回帖

更多关于 照相机镜头知识 的文章

 

随机推荐