学习笔记- AVFoundation Programming Guide - Still and Video Media Capture

管理相机或麦克风等设备捕捉音视频,生成对象来表示输入输出,并且用 AVCaptureSession 对象来协调他们之间的数据流。需要用到下面几个类:

. AVCaptureDevice 实例,表示输入设备。如:相机或麦克风;

. AVCaptureInput 子类的实例,配置输入设备的端口;

. AVCaptureOutput子类的实例,管理到视频文件或静态图的输出;

. AVCaptureSession 实例,协调从输入到输出的数据流。

为了向用户展示相机正在录取的图像,会用到 AVCaptureVideoPreviewLayer (CALayer 的子类)


通过一个单独的session,可以配置多个输入输出。

当把一个input或一个output加入到一个session中时,session就在所有input端口和output之间建立连接。

你可以使用 capture connection 来开启或禁用来自输入或输出设备上的数据流。


注:media capture 不支持在一个设备上用前,后摄像头同时捕获。


1. 用 Capture Session 来协调数据流


AVCaptureSession 是用来管理数据流的中心协调对象。使用此对象的实例来协调AV inputs 到 outputs的输出。把你想要的capture设备和输出加入到session中,用 startRunning 来开始数据流,用 stopRunning 来停止数据流。

code:

AVCaptureSession *session = [[AVCaptureSession alloc] init];
// Add inputs and outputs.
[session startRunning];


1)配置 Session

可以使用sessionPreset 来指定图像的质量和分辨率。preset是一个常量,在某些情况下,实际的配置是设备特定的。

如果你想设置媒体的帧的大小,你应该检查它是否支持此种设置,如下:

code:

if ([session canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
    session.sessionPreset = AVCaptureSessionPreset1280x720;
}
else {
    // Handle the failure.
}


如果你需要更精确的级别调整session或你改变运行的session,你需要在 beginCOnfiguration 和 commitConfiguration 方法中改变。

beginConfiguration 和 commitConfiguration 确保设备作为一个群体变化,减少不一致的状态。

在beginConfiguration 后,你可以增加或者移除output,改变 sessionPreset属性值,或者配置个人capture 输入或输出属性。只有调用commitConfiguration后,状态才能生效。

code:

[session beginConfiguration];
// Remove an existing capture device.
// Add a new capture device.
// Reset the preset.
[session commitConfiguration];


2)监听Capture Session 的状态

capture session 会发送 notifications,你可以监听这些消息,例如,当它开始或停止运行时,或者中断。

如果发生运行错误,你可以通过注册消息AVCaptureSessionRuntimeErrorNotification来接收。

你也可以查询session的running属性来判断是否允许,interrupter属性来判断是否中断。另外running和interrupted属性是KVO。


2. AVCaptureDevice 对象代表一个输入设备

AVCaptureDevice 对象抽象出一个物理捕捉设备,提供输入数据给 AVCaptureSession。每个输入设备都是一个对象。

你可以找到当前有效的capture 设备,用 AVCaptureDevice 类方法 devices 和 devicesWithMediaType:。有效设备列表可能会改变,你可以注册接收消息  AVCaptureDeviceWasConnectedNotification 和 AVCaptureDeviceWasDisconnectedNotification,来获取当前设备的改变。


1)设备特性

你可以询问设备关于不同的特性。测试设备是否支持媒体类型:hasMediaType;测试设备是否支持给定的catpure session 的预设值:supportsAVCapureSessionPreset:。

下面代码遍历所有可用设备,输出他们的名字,并且视频设备还输出了位置。

code:

NSArray *devices = [AVCaptureDevice devices];
for (AVCaptureDevice *device in devices) {
    NSLog(@"Device name: %@", [device localizedName]);
    if ([device hasMediaType:AVMediaTypeVideo]) {
        if ([device position] == AVCaptureDevicePositionBack) {
            NSLog(@"Device position : back");
        }
        else {
            NSLog(@"Device position : front");
        }
    }
}


2)设置Capture设备

不同设备有不同功能。例如:有些设备支持不同的聚焦或闪关灯模式。

下面展现了在所有视频输入设备中,查找支持手电筒并且支持capture session 预设的:

code:

NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
NSMutableArray *torchDevices = [[NSMutableArray alloc] init];
for (AVCaptureDevice *device in devices) {
    [if ([device hasTorch] &&
         [device supportsAVCaptureSessionPreset:AVCaptureSessionPreset640x480]) {
        [torchDevices addObject:device];
    }
}

用类似的方式使用不同功能。通过询问设备是否支持一个功能。当一个功能改变时,可以观察到要通知的属性。在所有情况下,改变一个特定功能之前应该锁定设备。


a.  对焦模式

三种模式:AVCaptureFocusModeLocked,AVCaptureFocusModeAutoFocus,AVCaptureFocusModeContinuousAutoFocus

isFocusModeSupported:判断是否支持该模式;focusMode:设置模式;

另外,设备有可能支持“感兴趣”的焦点,测试:focusPointOfInterestSupports,如果支持,设置 focusPointOfInterest(左上角CGPoint为{0,0},右下角{1,1})。

你可以使用 adjustingFocus 属性确定设备是否正在对焦。可用通过KVO监听该属性,当对焦开始,停止时。

code:

if ([currentDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
    CGPoint autofocusPoint = CGPointMake(0.5f, 0.5f);
    [currentDevice setFocusPointOfInterest:autofocusPoint];
    [currentDevice setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
}


b.  曝光模式

两种模式:AVCaptureExposureModeContinuousAutoExposure, AVCaptureExposureModeLocked

isExposureModeSupported:是否支持曝光模式;exposureMode:设置;

exposurePointOfInterestSupported :是否支持“感兴趣"曝光;exposurePointOfInterest 设置。

adjustingExposure属性,发生改变时。 可通过KVO 监听曝光开始和结束。

code:

if ([currentDevice isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) {
    CGPoint exposurePoint = CGPointMake(0.5f, 0.5f);
    [currentDevice setExposurePointOfInterest:exposurePoint];
    [currentDevice setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
}


c.  闪光灯模式

三种类型:AVCaptureFlashModeOff,AVCaptureFlashModeOn,AVCaptureFlashModeAuto;

hasFlash:是否有闪光灯,如果有用 isFlashModeSupported 看是否支持该模式;

flashMode:设置模式。


d.  手电筒模式

三种类型:AVCaptureTorchModeOff,AVCaptureTorchModeOn,AVCaptureTorchModeAuto;

方法:hasTorch,isTorchModeSupported,torchMode。


e.  视频稳定

videoStabilizationEnabled 属性:是否支持;

enablesVideoStabilizationWhenAvailable 属性:如果相机支持,设置程序自动开启。


f .  白平衡

两个模式:AVCaptureWhiteBalanceModeLocked,AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance;

isWhiteBalanceModeSupported:是否支持;whiteBalanceMode:设置;

adjustingWhiteBalance属性:是否正在改变;可通过KVO监听开始和停止。


g.  设置定位装置

code:

AVCaptureConnection *captureConnection = <#A capture connection#>;
if ([captureConnection isVideoOrientationSupported])
{
    AVCaptureVideoOrientation orientation = AVCaptureVideoOrientationLandscapeLeft;
    [captureConnection setVideoOrientation:orientation];
}


3)配置设备

在设备上设置capture属性是,必须先lock devices:lockForConfiguration。这样避免在其他应用程序中设置更改导致不兼容。

code:

if ([device isFocusModeSupported:AVCaptureFocusModeLocked]) {
    NSError *error = nil;
    if ([device lockForConfiguration:&error]) {
        device.focusMode = AVCaptureFocusModeLocked;
        [device unlockForConfiguration];
    }
    else {
        // Respond to the failure as appropriate.


尽量不用不必要的锁,否则会降低其他应用程序中的捕获质量。


4)切换设备

code:

AVCaptureSession *session = <#A capture session#>;
[session beginConfiguration];
 
[session removeInput:frontFacingCameraDeviceInput];
[session addInput:backFacingCameraDeviceInput];
 
[session commitConfiguration];


3. 添加Capture Device 到 Session

添加 capture device 到 capture session,你需啊哟创建 AVCaptureDeviceInput 的实例。capture device input 管理 设备的端口。

code:

NSError *error;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
    // Handle the error appropriately.
}

添加inputs 到 session 使用 addInput:,应该检测capture input 是否和存在的 session 是否兼容,canAddInput:,

code:

AVCaptureSession *captureSession = <#Get a capture session#>;
AVCaptureDeviceInput *captureDeviceInput = <#Get a capture device input#>;
if ([captureSession canAddInput:captureDeviceInput]) {
    [captureSession addInput:captureDeviceInput];
}
else {
    // Handle the failure.
}


一个 AVCaptureInput 声明一个或多个流媒体数据。例如,输入设备可以提供音频和视频数据。每个媒体流的输入有 AVCaptureInputPort 对象表示。capture session 使用一个 AVCaptureConnection 对象定义一组 AVCaptureInputPort 对象和 单个AVCaptureOutput 对象之间的映射。


4. 用 Capture Outputs 从 Session 中得到 Output

添加一个或多个 outputs,从 capture session 获取输出。一个 output 是AVCaptureOutput 子类的实例。

. AVCaptureMovieFileOutput  输出视频文件;

. AVCaptureVIdeoDataOutput 如果你想在视频捕捉时处理帧,例如,创建你创建自定义视图层;

. AVCaptureAudioDataOutput 捕捉音频数据是,处理音频数据;

. AVCaptureStillImageOutput 捕捉静态图片;


addOutput:添加输出到capture session;

canAddOutput:检测capture output 是否和已存在的 session 兼容;

session运行时,可以添加和删除输出。


code:

AVCaptureSession *captureSession = <#Get a capture session#>;
AVCaptureMovieFileOutput *movieOutput = <#Create and configure a movie output#>;
if ([captureSession canAddOutput:movieOutput]) {
    [captureSession addOutput:movieOutput];
}
else {
    // Handle the failure.
}


1)保存视频文件 

AVCaptureMovieFileOutput用来保存视频数据到文件。你可以配置视频文件输出的各个方面,例如,拍摄的最大时间,最大文件容量。

当小于给的磁盘空间数量时,也会被禁止记录。

code:

AVCaptureMovieFileOutput *aMovieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
CMTime maxDuration = <#Create a CMTime to represent the maximum duration#>;
aMovieFileOutput.maxRecordedDuration = maxDuration;
aMovieFileOutput.minFreeDiskSpaceLimit = <#An appropriate minimum given the quality of the movie format and the duration#>;


a. 开始记录

用startRecordingToOutputFileURL:recordingDelegate:开始录制视频。URL 不能是一个存在的文件,因为输出的文件不能覆盖存在的资源。还必须有权限写入指定位置。

code:

AVCaptureMovieFileOutput *aMovieFileOutput = <#Get a movie file output#>;
NSURL *fileURL = <#A file URL that identifies the output location#>;
[aMovieFileOutput startRecordingToOutputFileURL:fileURL recordingDelegate:<#The delegate#>];

实现 captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:,委托里可以把视频文件放到Camera Roll album。


b. 确保文件写入成功

code:

- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
        didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
        fromConnections:(NSArray *)connections
        error:(NSError *)error {
 
    BOOL recordedSuccessfully = YES;
    if ([error code] != noErr) {
        // A problem occurred: Find out if the recording was successful.
        id value = [[error userInfo] objectForKey:AVErrorRecordingSuccessfullyFinishedKey];
        if (value) {
            recordedSuccessfully = [value boolValue];
        }
    }
    // Continue as appropriate...

需要检查关键字  AVErrorRecordingSuccessfullyFinishedKey,因为即使得到一个错误,也有可能文件保存成功。

error 表明录制被限制了- 例如, AVErrorMaximumDurationReached orAVErrorMaximumFileSizeReached。其他导致录制停止的原因:

AVErrorDiskFull : 磁盘慢了;

AVErrorDeviceWasDisconnected:录制设备断开;

AVErrorSessionWasInterrupted:session 被中断(例如,手机来电)。


c. 添加元数据到文件

任何时候在视频文件中你都可以设置元数据,即使正在录制。

有种情况,当开始记录时,信息不可用,可能是位置信息。

输出文件元数据由AVMetadataItem对象数组表示;用其子类AVMutableMetadataItem的实例创建自己的元数据。

code:

AVCaptureMovieFileOutput *aMovieFileOutput = <#Get a movie file output#>;
NSArray *existingMetadataArray = aMovieFileOutput.metadata;
NSMutableArray *newMetadataArray = nil;
if (existingMetadataArray) {
    newMetadataArray = [existingMetadataArray mutableCopy];
}
else {
    newMetadataArray = [[NSMutableArray alloc] init];
}
 
AVMutableMetadataItem *item = [[AVMutableMetadataItem alloc] init];
item.keySpace = AVMetadataKeySpaceCommon;
item.key = AVMetadataCommonKeyLocation;
 
CLLocation *location - <#The location to set#>;
item.value = [NSString stringWithFormat:@"%+08.4lf%+09.4lf/"
    location.coordinate.latitude, location.coordinate.longitude];
 
[newMetadataArray addObject:item];
 
aMovieFileOutput.metadata = newMetadataArray;


2)处理视频帧

设置delegate,必须用串行队列,确保帧数据以正确顺序传送到delegate。你可以用队列修改帧数据。

 captureOutput:didOutputSampleBuffer:fromConnection:可以用 videoSettings 属性指定自定义输出格式。视频设置属性是个字典,当前只支持关键字  kCVPixelBufferPixelFormatTypeKey。推荐的像素格式有 availableVideoCVPixelFormatTypes 返回的属性和支持availableVideoCodecTypes 属性返回值。

code:

AVCaptureVideoDataOutput *videoDataOutput = [AVCaptureVideoDataOutput new];
NSDictionary *newSettings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) };
videoDataOutput.videoSettings = newSettings;
 
 // discard if the data output queue is blocked (as we process the still image
[videoDataOutput setAlwaysDiscardsLateVideoFrames:YES];)
 
// create a serial dispatch queue used for the sample buffer delegate as well as when a still image is captured
// a serial dispatch queue must be used to guarantee that video frames will be delivered in order
// see the header doc for setSampleBufferDelegate:queue: for more information
videoDataOutputQueue = dispatch_queue_create("VideoDataOutputQueue", DISPATCH_QUEUE_SERIAL);
[videoDataOutput setSampleBufferDelegate:self queue:videoDataOutputQueue];
 
AVCaptureSession *captureSession = <#The Capture Session#>;
 
if ( [captureSession canAddOutput:videoDataOutput] )
     [captureSession addOutput:videoDataOutput];


处理视频的性能考虑


3)捕捉静态图

AVCaptureStillImageOutput 捕捉静态图片。

像素和编码格式

不同设备支持不同图片格式。你可以用 availableImageDataCVPixelFormatTypes 和 availableImageDataCodecTypes找到支持的像素和编码类型。每个方法返回一个支持的数组。

code:

AVCaptureStillImageOutput *stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
NSDictionary *outputSettings = @{ AVVideoCodecKey : AVVideoCodecJPEG};
[stillImageOutput setOutputSettings:outputSettings];


捕捉JPEG 图像时,通常不用指定压缩格式,应该让静态图像输出设置压缩格式,因为他的压缩是硬件加速。

如果需要一个image 的data,可以用 jpegStillImageNSDataRepresentation: 获取无压缩数据对象,即使你修改了图像的元数据。


捕获图像

当捕获图像时,发送captureStillImageAsynchronouslyFromConnection:completionHandler: message

code:

AVCaptureConnection *videoConnection = nil;
for (AVCaptureConnection *connection in stillImageOutput.connections) {
    for (AVCaptureInputPort *port in [connection inputPorts]) {
        if ([[port mediaType] isEqual:AVMediaTypeVideo] ) {
            videoConnection = connection;
            break;
        }
    }
    if (videoConnection) { break; }
}


[stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:
    ^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
        CFDictionaryRef exifAttachments = CMGetAttachment(imageSampleBuffer, kCGImagePropertyExifDictionary, NULL);
        if (exifAttachments) {
            // Do something with the attachments.
        }
        // Continue as appropriate.
    }];


5. 显示录制

你可以为用户提供一个相机(用preview layer)或麦克风(监听音频通道)录制的预览。

1)视频预览

通过 AVCaptureVideoPreviewLayer 对象可以提供给用户预览录制功能。AVCaptureVideoPreviewLayer  是 CALayer 的子类。不需要任何output到显示预览。

用 AVCaptureVideoDataOutput 类提供访问视频像素的功能。

和capture output 不同的是,视频预览层强引用到session。这时确保layer播放视频时,session不被销毁。

code:

AVCaptureSession *captureSession = <#Get a capture session#>;
CALayer *viewLayer = <#Get a layer from the view in which you want to present the preview#>;
 
AVCaptureVideoPreviewLayer *captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:captureSession];
[viewLayer addSublayer:captureVideoPreviewLayer];


通常情况下,预览层就像渲染树中任何其他CALayer对象一样。你可以缩放图像和执行转换,旋转等待。

不同的是你需要设置层的 orientation 属性,用来指导如何选择相机的图像。

另外,你可以通过 supportsVideoMirroring 属性测试是否支持镜像,可以设置 videoMirrored 属性,但是当 automaticallyAdjustsVideoMirroring 属性设为 YES时(默认),镜像值自动设置层基于session 的配置。


预览层支持三种显示模式:AVLayerVideoGravityResizeAspect,AVLayerVideoGravityResizeAspectFill,AVLayerVideoGravityResize


2)显示音频levels

AVCaptureAudioChannel 对象提供音频通道的平均值和峰值power levels。

音频levels 没有 KVO,所以必须轮询levels来更新用户界面。

code:

AVCaptureAudioDataOutput *audioDataOutput = <#Get the audio data output#>;
NSArray *connections = audioDataOutput.connections;
if ([connections count] > 0) {
    // There should be only one connection to an AVCaptureAudioDataOutput.
    AVCaptureConnection *connection = [connections objectAtIndex:0];
 
    NSArray *audioChannels = connection.audioChannels;
 
    for (AVCaptureAudioChannel *channel in audioChannels) {
        float avg = channel.averagePowerLevel;
        float peak = channel.peakHoldLevel;
        // Update the level meter user interface.
    }
}


6. 捕捉视频帧UIImage 对象

下面的例子说明怎样捕捉视频并且转换帧为UIImage 对象。

. 创建 AVCaptureSession 对象协调数据流从输入设备到输出;

. 通过需要的输入设备类型查找 AVCaptureDevice 对象;

. 通过设备创建 AVCaptureDeviceInput 对象;

. 创建 AVCaptureVideoDataOutput 对象来提供视频帧;

. 实现 AVCaptureVideoDataOutput 的delegate来处理视频帧;

. 实现一个函数转换 CMSampleBuffer 到 UIImage 对象;


1)创建并配置 Capture Session

code:

AVCaptureSession *session = [[AVCaptureSession alloc] init];
session.sessionPreset = AVCaptureSessionPresetMedium;


2)创建并配置Device和Device Input

code:

AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error = nil;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
    // Handle the error appropriately.
}
[session addInput:input];


3) 创建并配置Video Data Output

code:

AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
[session addOutput:output];
output.videoSettings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) };
output.minFrameDuration = CMTimeMake(1, 15);

dispatch_queue_t queue = dispatch_queue_create("MyQueue", NULL);
[output setSampleBufferDelegate:self queue:queue];
dispatch_release(queue);


4) 实现 Sample Buffer Delegate (AVCaptureVideoDataOutputSampleBufferDelegate

code:

- (void) captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
             fromConnection:(AVCaptureConnection *)connection {

    UIImage *image = imageFromSampleBuffer(sampleBuffer);
    // Add your code here that uses the image.

}


5)开始和停止录制

配置完捕获session后,需要确保有相机录制权限。

code:

NSString *mediaType = AVMediaTypeVideo;
[AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) {
    if (granted) {
        //Granted access to mediaType
        [self setDeviceAuthorized:YES];
    }else{
        //Not granted access to mediaType
        dispatch_async(dispatch_get_main_queue(), ^{
        [[[UIAlertView alloc] initWithTitle:@"AVCam!"
                                    message:@"AVCam doesn't have permission to use Camera, please change privacy settings"
                                   delegate:self
                          cancelButtonTitle:@"OK"
                          otherButtonTitles:nil] show];
                [self setDeviceAuthorized:NO];
        });
    }
}];

如果用户有访问相机的权限,发送 startRunning 消息开始录制。

[session startRunning];



7. 高帧率视频捕获

iOS 7.0 引入高帧率视频捕获(也称“SloMo”视频)。

用 AVCaptureDeviceFormat 类确定设备的捕获能力。该类有返回支持的媒体类型,帧速率,视图字段,最大缩放因子,是否支持视频稳定等。













评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值