Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >iOS-AVFoundation自定义相机详解

iOS-AVFoundation自定义相机详解

作者头像
用户2215591
发布于 2018-06-29 07:00:59
发布于 2018-06-29 07:00:59
2.7K0
举报
文章被收录于专栏:iOSer成长记录iOSer成长记录

AVFoundation 中关于视频主要的类

  • 目录
    • 相机基本实现步骤
    • 捕捉会话——AVCaptureSession
    • 捕捉输入——AVCaptureDeviceInput
    • 捕捉预览——AVCaptureVideoPreviewLayer/OpenGL ES
    • 捕捉连接——AVCaptureConnection
    • 拍照——AVCaptureStillImageOutput
    • 音频——AVCaptureAudioDataOutput
    • 视频——AVCaptureVideoDataOutput
    • 生成视频文件——AVAssetWriter、AVAssetWriterInput
    • 写入相册——ALAssetsLibrary、PHPhotoLibrary
    • 操作相机
      • 转换摄像头
      • 补光
      • 闪光灯
      • 聚焦
      • 曝光
      • 自动聚焦曝光
    • 视频重力——Video gravity
    • 方向问题——Orientation
    • 项目地址
  • 相机实现步骤,下面对每一会对每一步需要做的事情详解 1.创建session(捕捉会话) 2.创建device input(捕捉设备输入) 3.预览view 4.创建capture output(捕捉的输出) 5.拍照、录视频(元数据转成图片或文件)
  • 捕捉会话——AVCaptureSession AVCaptureSession(捕捉会话管理):它从物理设备得到数据流(比如摄像头和麦克风),输出到一个或多个目的地,它可以通过会话预设值(session preset),来控制捕捉数据的格式和质量 下面是创建一个 session 的代码: AVCaptureSession *captureSession = [[AVCaptureSession alloc]init]; [captureSession setSessionPreset:AVCaptureSessionPresetPhoto]; SessionPreset在iOS中大概有11个 NSString *const AVCaptureSessionPresetPhoto; NSString *const AVCaptureSessionPresetHigh; NSString *const AVCaptureSessionPresetMedium; NSString *const AVCaptureSessionPresetLow; NSString *const AVCaptureSessionPreset352x288; NSString *const AVCaptureSessionPreset640x480; NSString *const AVCaptureSessionPreset1280x720; NSString *const AVCaptureSessionPreset1920x1080; NSString *const AVCaptureSessionPresetiFrame960x540; NSString *const AVCaptureSessionPresetiFrame1280x720; NSString *const AVCaptureSessionPresetInputPriority; 第一个代表高像素图片输出;接下来三种为相对预设(low, medium, high),这些预设的编码配置会因设备不同而不同,如果选择high,那么你选定的相机会提供给你该设备所能支持的最高画质;再后面就是特定分辨率的预设(352x288 VGA, 1920x1080 VGA, 1280x720 VGA, 640x480 VGA, 960x540 iFrame, 1280x720 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]; } 注意:
    1. 它看起来有点像输出,但其实不是,它仅用来预览摄像头捕捉的画面。真正用于输出的是AVCaptureSession(previewLayer拥有session,session拥有outputs);
    2. 它的坐标系和屏幕的坐标系不同,如果点击某区域实现对焦时,我们需要将设备的坐标系转换为实时预览图的坐标;
    3. 它的坐标原点永远都在右上角,这和我们手机的坐标系不同,手机坐标系的原点是不变的。因此拍照或录制视频时,要先得到设备方向(关于方向问题,后面会详解),计算输出的旋转角度。

    捕捉预览除了用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.bounds; // 在视频输出函数中绘制出来 -(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.referenceOrientation; // 在视频元数据的输出函数中,如果捕捉连接是视频连接,则写入视频数据 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 = imageOutput; } // 输出图片 AVCaptureConnection *connection = [_imageOutput connectionWithMediaType:AVMediaTypeVideo]; if (connection.isVideoOrientationSupported) { connection.videoOrientation = [self currentVideoOrientation]; } id takePictureSuccess = ^(CMSampleBufferRef sampleBuffer,NSError *error){ if (sampleBuffer == NULL) { [self showError:error]; return ; } 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 = videoOut; }
  • 生成视频文件——AVAssetWriter、AVAssetWriterInput AVAssetWriter:用于对媒体资源进行编码并讲其写入到容器文件中,比如一个QuickTime文件。 AVAssetWriterInput:用于处理指定的媒体类型,比如音频和视频。 AVAssetWriterInputPixelBufferAdaptor:这个类在生成视频文件时提供最优性能,不过Demo没有使用该类,有兴趣的可以去研究一下 // 初始化一个assetWriter NSError *error; _assetWriter = [[AVAssetWriter alloc] initWithURL:_movieURL fileType:AVFileTypeQuickTimeMovie error:&error]; if (error){ [self showError:error]; } // 配置视频源数据输入 - (BOOL)setupAssetWriterVideoInput:(CMFormatDescriptionRef)currentFormatDescription { CGFloat bitsPerPixel; CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(currentFormatDescription); NSUInteger numPixels = dimensions.width * dimensions.height; NSUInteger bitsPerSecond; if (numPixels < (640 * 480)){ bitsPerPixel = 4.05; } else{ bitsPerPixel = 11.4; } bitsPerSecond = numPixels * bitsPerPixel; 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]; } else{ [self showError:_assetWriter.error]; return NO; } } else{ [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 = nil; if (currentChannelLayout && aclSize > 0 ){ currentChannelLayoutData = [NSData dataWithBytes:currentChannelLayout length:aclSize]; } else{ 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]; } else{ [self showError:_assetWriter.error]; return NO; } } else{ [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{ NSError *error; AVCaptureDevice *videoDevice = [self inactiveCamera]; AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error]; if (videoInput) { AVCaptureFlashMode flashMode = [self flashMode]; // 转换摄像头 [_session beginConfiguration]; [_session removeInput:_deviceInput]; if ([_session canAddInput:videoInput]) { CATransition *animation = [CATransition animation]; animation.type = @"oglFlip"; animation.subtype = kCATransitionFromLeft; animation.duration = 0.5; [self.cameraView.previewView.layer addAnimation:animation forKey:@"flip"]; [_session addInput:videoInput]; _deviceInput = videoInput; } else { [_session addInput:_deviceInput]; } [_session commitConfiguration]; // 完成后需要重新设置视频输出链接 _videoConnection = [_videoOutput connectionWithMediaType:AVMediaTypeVideo]; // 如果后置转前置,系统会自动关闭手电筒,如果之前打开的,需要更新UI if (videoDevice.position == AVCaptureDevicePositionFront) { [self.cameraView changeTorch:NO]; } // 前后摄像头的闪光灯不是同步的,所以在转换摄像头后需要重新设置闪光灯 [self changeFlash:flashMode]; return nil; } return error; }

    • 补光

    AVCaptureDevice *device = [self activeCamera]; if (device.torchMode != torchMode && [device isTorchModeSupported:torchMode]) { NSError *error; if ([device lockForConfiguration:&error]) { device.torchMode = torchMode; [device unlockForConfiguration]; } else{ [self showError:error]; } }

    • 闪光灯

    AVCaptureDevice *device = [self activeCamera]; if (device.flashMode != flashMode && [device isFlashModeSupported:flashMode]) { NSError *error; if ([device lockForConfiguration:&error]) { device.flashMode = flashMode; [device unlockForConfiguration]; } else{ [self showError:error]; } }

    • 聚焦

    - (void)focusAtPoint:(CGPoint)point { AVCaptureDevice *device = [self activeCamera]; if ([self cameraSupportsTapToFocus] && [device isFocusModeSupported:AVCaptureFocusModeAutoFocus]) { NSError *error; if ([device lockForConfiguration:&error]) { device.focusPointOfInterest = point; device.focusMode = AVCaptureFocusModeAutoFocus; [device unlockForConfiguration]; } else{ [self showError:error]; } } }

    • 曝光

    static const NSString *CameraAdjustingExposureContext; - (void)exposeAtPoint:(CGPoint)point{ AVCaptureDevice *device = [self activeCamera]; if ([self cameraSupportsTapToExpose] && [device isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) { NSError *error; if ([device lockForConfiguration:&error]) { device.exposurePointOfInterest = point; device.exposureMode = AVCaptureExposureModeContinuousAutoExposure; if ([device isExposureModeSupported:AVCaptureExposureModeLocked]) { [device addObserver:self forKeyPath:@"adjustingExposure" options:NSKeyValueObservingOptionNew context:&CameraAdjustingExposureContext]; } [device unlockForConfiguration]; } else{ [self showError:error]; } } } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context == &CameraAdjustingExposureContext) { AVCaptureDevice *device = (AVCaptureDevice *)object; if (!device.isAdjustingExposure && [device isExposureModeSupported:AVCaptureExposureModeLocked]) { [object removeObserver:self forKeyPath:@"adjustingExposure" context:&CameraAdjustingExposureContext]; dispatch_async(dispatch_get_main_queue(), ^{ NSError *error; if ([device lockForConfiguration:&error]) { device.exposureMode = AVCaptureExposureModeLocked; [device unlockForConfiguration]; } else{ [self showError:error]; } }); } } else{ [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } }

    • 自动聚焦/曝光

    - (BOOL)resetFocusAndExposureModes{ AVCaptureDevice *device = [self activeCamera]; AVCaptureExposureMode exposureMode = AVCaptureExposureModeContinuousAutoExposure; AVCaptureFocusMode focusMode = AVCaptureFocusModeContinuousAutoFocus; BOOL canResetFocus = [device isFocusPointOfInterestSupported] && [device isFocusModeSupported:focusMode]; BOOL canResetExposure = [device isExposurePointOfInterestSupported] && [device isExposureModeSupported:exposureMode]; CGPoint centerPoint = CGPointMake(0.5f, 0.5f); NSError *error; if ([device lockForConfiguration:&error]) { if (canResetFocus) { device.focusMode = focusMode; device.focusPointOfInterest = centerPoint; } if (canResetExposure) { device.exposureMode = exposureMode; device.exposurePointOfInterest = centerPoint; } [device unlockForConfiguration]; return YES; } else{ [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 1080 1920]> affine [1 0 0 -1 0 1920] extent=[0 0 1080 1920] colormatch "QuickTime 'nclc' Video (1,1,6)"-to-workingspace extent=[0 0 1080 1920] IOSurface 0x15f900008 BGRA8 extent=[0 0 1080 1920] 可以看出选择不同的session preset,会输出不同大小的图片,但是这些图片都是很大的,这么大的图片要显示在手机预览层,必须要缩放,而视频重力其实就是缩放参数。 AVLayerVideoGravityResizeAspect:在预览层区域内缩放视频,保持视频原始宽高比。这是默认值,同时适用大多数情况。使用该参数预览时,有可能不能铺满整个预览视图 AVLayerVideoGravityResizeAspectFill:按照视频的宽高比将视频拉伸填满整个图层。使用该参数时,很可能造成视频预览图片被裁剪,而拍摄输出没有被裁剪,这样就会使预览图和最终拍摄的图不一致。 AVLayerVideoGravityResize:拉伸视频内容以匹配预览层大小,这个是最不常用的,可能造成视频扭曲。
  • 方向问题——Orientation 设备方向device orientation // 设备方向 UIDevice *device = [UIDevice currentDevice] ; switch (device.orientation) { case UIDeviceOrientationFaceUp: NSLog(@"屏幕朝上平躺"); break; case UIDeviceOrientationFaceDown: NSLog(@"屏幕朝下平躺"); break; case UIDeviceOrientationUnknown: NSLog(@"未知方向"); break; case UIDeviceOrientationLandscapeLeft: NSLog(@"屏幕向左橫置"); break; case UIDeviceOrientationLandscapeRight: NSLog(@"屏幕向右橫置"); break; case UIDeviceOrientationPortrait: NSLog(@"屏幕直立"); break; case UIDeviceOrientationPortraitUpsideDown: NSLog(@"屏幕直立,上下顛倒"); break; 从上面可以看到所有的设备方向,而视频方向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 = AVCaptureVideoOrientationPortrait; // 这行代码在设置视频输入方向为默认输入方向 _videoConnection.videoOrientation = AVCaptureVideoOrientationPortrait; // 视频输入类中手动旋转视频方向 _assetVideoInput.transform = [self transformFromCurrentVideoOrientationToOrientation:self.referenceOrientation]; // 旋转视频方向函数实现 - (CGAffineTransform)transformFromCurrentVideoOrientationToOrientation:(AVCaptureVideoOrientation)orientation { CGFloat orientationAngleOffset = [self angleOffsetFromPortraitOrientationToOrientation:orientation]; CGFloat videoOrientationAngleOffset = [self angleOffsetFromPortraitOrientationToOrientation:self.motionManager.videoOrientation]; CGFloat angleOffset; if ([self activeCamera].position == AVCaptureDevicePositionBack) { angleOffset = orientationAngleOffset - videoOrientationAngleOffset; } else{ angleOffset = videoOrientationAngleOffset - orientationAngleOffset + 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; default: break; } return angle; }
  • 项目地址 https://github.com/cdcyd/CCCamera
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2016.08.30 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【Androi】安卓发展历程详解
安卓(Android)操作系统自2008年首次发布以来,迅速发展成为全球最广泛使用的移动操作系统。本文将详细回顾安卓的历史发展过程,探讨其在技术、市场和用户体验方面的演变。
人不走空
2024/06/09
2.8K0
【Androi】安卓发展历程详解
Android 1.5到10.0 都有哪些新特性?
安卓3.0系统主要用于安卓的平板产品,画面动感,可操控性更强,代表有摩托罗拉的平板产品XOOM,3.1也已经发布,也主要用于平板产品。
用户1269200
2019/03/08
2.2K0
B4A编程开发路线001_Android安卓基础
2023年02月04日午夜,在搜索中文可视化编程IDE时无意中发现了B4X的官网:跨平台 RAD 开发工具 |B4X。
用户1549490
2023/07/12
1.2K0
B4A编程开发路线001_Android安卓基础
【Flutter实战】移动技术发展史
老孟导读:大家好,这是【Flutter实战】系列文章的第一篇,这并不是一篇Flutter技术文章,而是介绍智能手机操作系统、跨平台技术的演进以及我对各种跨平台技术看法的文章。
老孟Flutter
2020/09/11
9860
【Flutter实战】移动技术发展史
跟我学Android之一 概述
Android 5更新你的应用程序添加各种新功能,比如在锁屏通知,一个全新的相机API,OpenGL ES 3.1,新材料的设计界面,以及更多。
张哥编程
2024/12/17
1200
跟我学Android之一  概述
操作系统发展史(移动端)
HTML5学堂:手机操作系统发展史。从手机出现到现在,手机发生了翻天地覆的变化,也是经历了几场“大战”。本文主要讲解的诺基亚的时代到现在苹果、安卓的时代的一个演变的过程。 诺基亚: 1. 在2007年
HTML5学堂
2018/03/12
1.9K0
操作系统发展史(移动端)
Android 10.0正在来的路上!
目前,美国 Google公司的 AndroidP (安卓9.0),已经正式全面推出有几个多月了。众多手机品牌厂商也都在积极的进行更新适配 Android 9.0 系统(修改UI界面也算是二次开发,嗯)。不知道各位Android用户是否体验到了这一最新版本的系统呢?
刘盼
2019/05/08
9520
Android 10.0正在来的路上!
安卓入门(八)
如今IT的发展如此之快,从硬件时代到现在物联网时代,Android是基于Linux开发的操作系统,Android本意指“机器人”,由Ascender设计的Logo图标,将Android设计为一个绿色的机器人,是一个功能强大的移动系统,也是一个为手机服务的,开放性系统。
达达前端
2022/04/29
6100
安卓入门(八)
Android9.0新特性曝光,你准备好了吗
Android9.0最早出现在2018年1月25日的谷歌官网上,初步代号已经确定为“Pistachio Ice Cream”(开心果冰淇淋),不过按照Google的惯例,如此长的三个单词代号,通常都只会在安卓新版本开发初期使用,后期会更换为更简单的单个或双个单词代号。那么Android9.0究竟带来了哪些新的特性呢,让我们先来一探究竟。
xiangzhihong
2022/11/30
7740
AndroidO(8.0) 和 Android P(9.0)
2017年8月22日,谷歌正式发布了Android8.0的正式版,其正式名称为:Android Oreo(奥利奥) 。
全栈程序员站长
2022/07/23
9930
AndroidO(8.0) 和 Android P(9.0)
蓝图已经画好了?透过“Q”看未来Android手机发展
5月8日凌晨,Google I/O开发者大会在美国加利福尼亚州山景城举行。本次开发者大会最引人关注的,莫过于安卓Q的正式发布了,因为相比于所谓的“安卓标杆”(然而并不是)Pixel手机本身,安卓的大版本更新似乎更能代表未来一年里安卓手机发展的风向标。
Android技术干货分享
2019/05/15
9150
蓝图已经画好了?透过“Q”看未来Android手机发展
Android和Linux应用综合对比分析
本文介绍了嵌入式Linux操作系统在工业自动化领域中的应用,包括各种工业设备、通信和数据处理等方面。同时,文章还探讨了基于嵌入式Linux的工业自动化技术的未来发展趋势,包括实时性、远程访问、无线通信等方面。
用户1170933
2018/01/05
4.4K0
Android和Linux应用综合对比分析
手机操作系统的沉浮往事(下)
这一年的1月9日,在Macworld 2007大会上,史蒂夫·乔布斯正式发布了第一代iPhone。
鲜枣课堂
2023/08/21
2670
手机操作系统的沉浮往事(下)
新一代Pixel手机,谷歌能否颠覆移动VR?
美国时间10月4日,在谷歌的新品发布会上,推出了手机Pixel 的续品Pixel 2以及VR头显。该款机型与之前爆出的差别不大,标准版Pixel 2屏幕大小为5英寸,而XL款的屏幕大小为6英寸。机身采
VRPinea
2018/05/17
6230
回顾iOS1到iOS15的发展
大家都爱调侃,最近这两年 iOS 的升级越来越安 卓化了,但你有了解过,ios 的历史是怎样的, 它是如何从一个青涩少年变成如今成熟的「大 人」模样?走进i0s 的进化史,看看 ios 从1到 15 都变化了什么! hello i0s 系统发布时间轨迹:iphone os 1 (2007) iPhone OS 2 (2008) iPhone OS 3 (2009) iOS 4 (2010) iOS 5 (2011) iOS 6 (2012) iOS 7 (2013) iOS 8 (2014) iOS9 (2015) iOS 10 (2016) iOS 11 (2017) iOS 12 (2018) iOS 13 (2019) iOS 14 (2020) iOS 15 (2021)
零式的天空
2022/03/28
3.4K0
Google I/O大会:Android 13
以智能手机为场景核心、 扩大智能终端的应用边界以及实现多设备间更好地协同。具体到系统体验层,安卓13将支持图标颜色随主题更换、为不同应用设定使用的语言、新的媒体中心界面等等,同时谷歌也推出了自家的钱包应用(Google Wallet)。
北洋
2022/05/14
5680
Google I/O大会:Android 13
Android 更新:新图标,新命名
当地时间8月22日,在Android Police网站上David Ruddock爆出Android将会使用新的图标及命名规则,在即将发布的新版Android系统上。
陈宇明
2020/12/16
5620
谷歌秋季发布会明天开启,“Pixel”系列会成为移动VR的全新里程碑?
北京时间10月5日凌晨,谷歌将召开2016年度秋季发布会。此次发布会不仅将发布作为Nexus迭代产品的Pixel系列手机,更有可能在该系列手机上,正式推出谷歌基于移动端开发的Daydream产品。 自
VRPinea
2018/05/14
7430
Winter漫聊手机——开篇
所谓漫聊,即无忧虑地聊,且不局限。因而,虽说是聊手机,但Winter也会附带性地聊点电脑、相机、VR及厂商等。时间顺序总是最适合懒人的,那就从2011年聊起吧。
冰之角
2018/09/04
1.3K0
Winter漫聊手机——开篇
谷歌 Android Q Labs技术分享会,腾讯WeTest福利抢先看!!!
腾讯官方的一站式品质开放平台「腾讯WeTest」收到谷歌邀请,参加2019年5月20日由谷歌在深圳举办的Android Q Labs技术分享会。
WeTest质量开放平台团队
2019/05/20
9790
谷歌 Android Q Labs技术分享会,腾讯WeTest福利抢先看!!!
相关推荐
【Androi】安卓发展历程详解
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档