AVFoundatioin-获取静态和动态媒体数据

内容来源

https://developer.apple.com/library/content/documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/04_MediaCapture.html#//apple_ref/doc/uid/TP40010188-CH5-SW4

要管理从设备(比如:摄像或麦克风)捕获的数据,你需要一些类来代表输入和输出。并且还要一个AVCaptureSession的实例来协调input和ouput之间的数据流。
最少你需要以下几点:

  • 一个AVCaptureDevice的实例来表示输入设备,比如摄像头或麦克风。
  • 一个AVCaptureInput子类的实例,去配置输入设备的端口。(AVCaptureInput为抽象类,不能直接实例化)
  • 一个AVCaptureOutput子类的实例,去管理输出为电影文件或是静态图片。(AVCaptureOutput为抽象类,不能直接实例化)
  • 一个AVCaptureSessioin的实例,去协调从输入到输出的数据流。

如果要展示你的摄像头录了什么,你可以用一个AVCaptureVideoPreviewLayerCALayer的子类)的实例

你可以用一个session来协调多个inputs和outputs.如下图:


 A single session can configure multiple inputs and outputs
A single session can configure multiple inputs and outputs

一个session里的一个输入和一个输出之间的联系是AVCaptureConnection对象。inputs(AVCaptureInput实例)有一个或多个端口(AVCaptureInputPort实例)。outputs(AVCaptureOutput实例)能从一个或多个源访问数据(比如:AVCaptureMovieFileOutput 对象可以访问音频和视频数据)。
当你要增加一个输入或输出到session时,session会在所有兼容的输入端口和输出之间形成连接(connections),如下图。输入和输出之间的连接可以用AVCaptureConnection对象表示。

 AVCaptureConnection represents a connection between an input and output
AVCaptureConnection represents a connection between an input and output

你可以用connection去开启或关闭一个从input来的或到output去的数据流。你也可以用一个connection去跟踪一个音频的平均或峰值音量。

媒体捕获设备不支持同时得到前置和后置两个摄像头。

用Capture Session和协调数据流

一个AVCaptureSession对象是你可以用来管理数据捕获的中央协调对象。你可以用一个实例去协调从AV Input devices到输出的数据流。你可以增加你想要的捕获设备和输出到session,然后发送startRunning消息到session去开始数据流,并且你也可以发送stopRunning消息去停止数据流。

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

配置一个session

你可以用preset(预设)到session,来具体指明你想要的图片质量和象素。一个preset是一个常量,它是标识了多个可能配置的其中一个。某些情况下特定的设备是用来特定的配置。

Symbol Resolution Comments
AVCaptureSessionPresetHigh High Highest recording quality.This varies per device.
AVCaptureSessionPresetMedium Medium Suitable for Wi-Fi sharing.The actual values may change.
AVCaptureSessionPresetLow Low Suitable for 3G sharing.The actual values may change.
AVCaptureSessionPreset640x480 640x480 VGA.
AVCaptureSessionPreset1280x720 1280x720 720p HD.
AVCaptureSessionPresetPhoto Photo Full photo resolution.This is not supported for video output.

如果你要设置一个特定尺寸的配置给媒体帧,你应该在设置之前检查它是否支持。如下

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

如果你需要调整session属性到比preset更高的粒层,或你改变一个正在运行的session.你应该把你的改变包含在beginConfigurationcommitConfiguration方法之间。这两个方法确保设备的改变做为一个组,最小的可见性或状态的不一致性。在调用beginConfiguration之后,你可以增加或移除输出,替换sessionPreset属性,或配置单独的输入或输出属性。没有一个改变真正的实现,直到你调用commitConfiguration,每一个它们都是成对出现的。

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

跟踪Capture Session状态

capture session会发送一些你可以观察到的notifications,比如,当它开始或结束运行时,或当它被打断时都会发送notifications。当一个运行时错误产生时,你可以注册去接收一个AVCaptureSessionRuntimeErrorNotification.你也可以查询session的running属性去看一下它是否在运行,它的interrupted属性去看一下它是否中断。额外的,runninginterrupted属性也是兼容key-value observing,它们的notifications也会在主线程发送。


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

一个AVCaptureDevice对象抽象自一个物理捕获设备,这个设备提供了输入数据(比如:音频或视频)到AVCaptureSession对象。每一个输入设备都有一个对象,比如,两个视频输入——一个前置摄像头,一个后置摄像头——还有一个用麦克风的声音输入。
你可以用AVCaptureDevices的class method devicesdevicesWithMediaType:来找出哪个捕获设备是当前有效的。还有,如果需要,你也可以找出iPhone, iPad,iPod提供了什么功能。有效的设备列表是可能会变的。当前有输入设备也有可能变成无效的(如果被其它的app使用)。并且新的输入设备有可能变成有效的(如果它们被其它app放开)。你应该注册并接收AVCaptureDeviceWasConnectedNotificationAVCaptureDeviceWasDisconnectedNotification,当新的有效设备列表改变时,notifications就会通知。


设备特征

你可以得到一个设备的不同特征。你也可以测试它是否提供了特定的媒体类型或它是否支持一个capture sessioin的preset,分别可以hasMediaType:supportsAVCaptureSessionPreset:验证。你可找出设备的位置(前置或后置)和它的本地名字给用户。有一点非常有用,你可以提供一个捕获设备的列表给用户选择。
以下代码遍历了所有的有效设备并找印了它们的名字:

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");
        }
    }
}

额外的,你也可以找出设备的model ID和它的唯一ID


Device Capture Settings

不同的设备有不同的功能。比如:一些支持不同的焦点或闪光模式。一些可能支持聚集一个兴趣点。
以下代码片断展示的你怎么找出支持手电筒模式和一个给定的capture session preset:
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
NSMutableArray *torchDevices = [[NSMutableArray alloc] init];
 
for (AVCaptureDevice *device in devices) {
    [if ([device hasTorch] &&
         [device supportsAVCaptureSessionPreset:AVCaptureSessionPreset640x480]) {
        [torchDevices addObject:device];
    }
}

如果你找到多个符合要求的设备,你可能需要让用户选择哪个是需要的。要展示一个设备的描述,你可以用localizedName属性。
你可以用相似的方式来使用不同的功能。那里有一些常量来表明一些具体的模式,你可以询问一个设备它是否支持那个特定的模式。在一些情况下,当一个功能改变时,你可以通过观察一个属性来得到一个通知。在所有的情况下,当你要改变一个特定功能的模式时,你应该锁定设备。

焦点兴趣点和曝光点是相互排斥的,焦点模式和曝光模式也是相互排斥的(Focus mode and exposure mode)。
Focus Modes(焦点)
Exposure Modes(爆光点)
Flash Modes(闪光点)
Torch Mode(手电筒)
Video Stabilization(视频稳定)
White Balance(白平衡)

设置设备方向

你可以在AVCaptureConnection里设置你想要的方向,去指明在输出AVCaptureOutput(AVCaptureMovieFileOutput, AVCaptureStillImageOutput and AVCaptureVideoDataOutput)上图片的朝向。
AVCaptureConnectionsupportsVideoOrientation属性来查明那个设备是否支持改变视频的方向的改变。还有videoOrientatioin属性表明你要哪个图片朝向到输出端口。
以下代码是设置AVCaptureConnection的朝向到AVCaptureVideoOrientationLandscapeLeft

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

配置一个设备

要设置一个设备的属性,你必须首先使用lockForConfiguration:来获得在这个设备上的。这个就避免了其它App的设置冲突。以下的代码就是展示了一种合理的方式来改变设备上的focus mode。首先查明是否支持这个模式,再去锁这个设备去再配置。只当锁定设备时focus mode才改变,然后立刻释放锁。

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

你应该只在要改变设备属性时保持设备锁,保持设备锁的目地是不被其它App改变属性。如果在不必要是还保持锁,那其它的App就不能改变属性了。


不同设备之间的切换

有时你会要允许用户去切换不同的输入设备——比如:从前置摄像头切换到后置的。为了避免暂停或磕磕绊绊,你要在运行时就重配置session,你应该把你的配置修改用beginConfigurationcommitConfiguration来包裹起来:

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

当最外层的commitConfiguration被调用,所有的改变都会同时发生。这就保证了一个平滑的过渡。


通过Capture Inputs增加Capture Device到一个session里

要增加一个capture device到capture session,你要使用AVCaptureDeviceInput(抽象类AVCaptureInput的具体子类)实例。这个Capture device input要管理设备的端口。

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

一个AVCaptureInput运送一个或多个媒体数据流。比如:input devices 可以提供包括声频和视频数据。输入设备提供的每一个媒体流都可以被AVCaptureInputPort对象来代表。一个capture session使用一个AVCaptureConnection对象来定义一个AVCaptureInputPort对象的集合单个AVCaputreOutput对象的联系。

通过Capture Outputs从Session得到输出

要从Capture Session得到输出,你要增加一个或多个Outputs.一个输出就是AVCaptureOutput具体子类的实例。你使用:

  • AVCaptureMovieFileOutput输出一个电影文件。
  • AVCaptureVideoDataOuput如果你要处理捕获视频里的每一帧,比如:你要创建你自己的view layer.(捕获人脸,活体检测可以用这个)
  • AVCaptureAudioDataOutput可以让你处理捕获的音频数据。
  • AVCaptureStillImageOutput可以让你获取带有元数据的表态图片。

你可以使用addOutput:去增加output到Capture Session。你可以用canAddOutput:检查是否这个output是否兼容这个已存在的session.你也可以在session运行时,按要求增加和移除outputs.

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.
}

保存一个电影文件

你可以使用AVCaptureMovieFileOutput对象来保存电影数据(这个类是抽象类AVCaptureFileOutput的具体子类,这个抽象类定义了一些基础行为)。你可以配置电影文件输出的多个方面,比如最大录制时长,或它的最大文件大小。你可以在磁盘空间不够时停止录制。

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#>;

输出的分辨率和比特率是依赖于capture session的sessioinPreset

视频编码通常是H.264,音频的编码通常是AAC.实际值因设备而异。

开始录制

你可以使用startRecordingToOutputFileURL:recordingDelegate:开始录制一段Quick Time电影。你需要一个基于文件的URL和一个delegate。这个URL不能指向一个已存在的文件,因为电影文件输出不能重写已存在的源。你必须有权限去在特定位置去写东西。这个delegate必须遵守AVCaptureFileOutputRecordingDelegate协议,并必须实现captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:方法。

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:实现里,这个delegate可能会把录制的电影文件写到相册里。它也应该检查任何已经产生的错误。

确保文件已被成功的写入

为了查明你的文件是否被成功的保存,在captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:实现里你不仅要检查error,还要检查在error的user info dictionary里AVErrorRecordingSuccessfullyFinishedKey的值:

- (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...

你应该检查error里的user info dictionary里的AVErrorRecordingSuccessfullyFinishedKey的值。因为这个文件你可能保存成功了,但你还是得到一个error.这个error可能是指向一个你录制的限制,比如:AVErrorMaximumDurationReachedAVErrorMaximumFileSizeReached.其它停止录制的原因:

  • 磁盘满了——AVErrorDiskFull
  • 录制设备失去连接——AVErrorDeviceWasDisconnected
  • session被中断(比如:电话进来了)——AVErrorSessionWasInterrupted

加元数据到文件

你可以在任何时间为一个电影文件设置元数据,即使是录制中。这对有些情况非常有用,比如:当开始录制时信息无效了,可能是位置信息的情况。对于一个文件输出来说Metadata被一个AVMetadataItem的对象数据来表示。你可使用它的可变子类实例AVMutableMetadataItem,去创建你自己的metadata

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;

处理视频帧

一个AVCaptureVideoDataOutput对象使用delegation来运送视频帧。你可以使用setSampleBufferDelegate:queue:来设置delegate。除了设置delegate,你还可以指定一个串行队列来调用这个delegate。你必须使用串行队列去确保视频帧是用合适的顺序派发给delegate的。您可以使用队列来修改提供和处理视频帧的优先级。看一下简单实现的例子 SquareCam
视频帧是是做为一个CMSampleBufferRef类型的实例,被delegate方法captureOutput:didOutputSampleBuffer:fromConnection:里显示的。默认的,这些buffers是被相机以最有效的格式发出。你可以使用videoSettings属性去指定一个自定义的输出格式。这个view settings属性是个dictionary。目前,唯一支持的key是kCVPixelBufferPixelFormatTypeKey。这个availableVideoCVPixelFormatTypes属性返回推荐的象素格式,并且availableVideoCodecTypes属性返回支持的值。Core Graphics和OpenGL都支持BGRA格式:

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];

处理视频的性能考虑

您应该将session output设置为应用程序的最低实际分辨率。 将输出设置为比必要的更高分辨率浪费处理周期,并且不必要地消耗功率。
你必须确保你的captureOutput:didOutputSampleBuffer:fromConnection:实现能够在分配给一个帧的时间量内处理sample buffered。如果你花了太长的时间并且持有视频帧,AV Foundation就会停止递送视频帧,不只是你的delegate,还有其它输入,比如:preview layer(预览)。
你可以使用capture video data ouputs的minFrameDuration属性去确保你有足够的时间去处理一帧,以更小的帧率来代替。你也可能要确保alwaysDiscardsLateVideoFrames属性为true(默认)。这个属性保证任何迟来的视频帧都将被丢弃,而不是处理它。相反的,如果你正在录制,不介意输出帧有点迟并且最好是全部保留,你要设置这个属性为false。这是并不意味着不会丢失帧(帧也是依旧会被丢弃),但是它们不会被过早或过于高效的丢弃。


获取静态图片

如果你想要一个带有metadata的静态图片,你可以使用AVCaptureStillImageOutput输出。这张图片的分辨率依赖于session的preset,还有设备。

象素和编码格式

不同的设备支持不同的图片格式。你可以使用availableImageDataCVPixelFormatTypesavailableImageDataCodecTypes找出这个设备支持什么象素和编解码器类型。每个方法都会返回一个特定设备的支持类型数组。你可以设置outputSettingsdictionary,指明哪个图片格式是你需要的。如下:

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

如果你要获取JPEG图片,你通常不应该指明你自己的压缩格式。相反 ,你应该让静态图片output为你做压缩,因为它的压缩是硬件加速的。如果你需要图片的数据代表,你可以使用jpegStillImageNSDataRepresentation:得到一个NSData对象而不重新压缩数据,即使你修改图片的metadata。

获取图片

当你要获取一张图片时,你要发送captureStillImageAsynchronouslyFromConnection:completionHandler:方法给output。第一个形参是你在capture里用的connection.你需要的是查看一下connection里哪个输入端口是收集视频的:

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; }
}

第二个形参是一个block,它带了两个参数:CMSampleBuffer类型包含了image data,还有一个error。sample buffer本身可以包含诸如EXIF字典的元数据作为附件。您可以根据需要修改附件,但请注意像素和编码格式中讨论的JPEG图像的优化。

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

展示给用户录制了什么

你可以为用户提供录制东西的预览,比如用preview layer预览摄像头录制的东西,或跟踪麦克风录制的声音轨道。

Video Preview

你可以通过AVCaptureVideoPreviewLayer对象预览录制的东西。AVCaptureVideoPreviewLayerCALayer的子类。你不需要任何outputs去展示预览。使用AVCaptureVideoDataOutput类为客户端应用程序提供访问视频像素的能力,然后才能呈现给用户。
不像capture output,一个视频preview layer保持了一个强引用到 相关的session。这就保证了当preview layer还在显示视频时,session不会被释放。这就是初始化preview layer就反映出来了:

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];

通常来说,preview layer在渲染树的行为就像其它的CALayer对象一样(可以查看Core Animation Programming Guide).你可像任何Layer一样缩放图像并执行转换,旋转等操作.一个不同点是你需要设置layer的orientation属性,去表明怎么旋转从摄像头过来的图像.另外,你可以通过查询supportsVideoMirroring属性来测试视频镜像的设备支持.尽管当automaticallyAdjustsVideoMirroring属性被 设为true(默认的),这个镜像值会在session配置的基础上自动设置,但你还是可以在需要时设置videoMirrored属性.


视频重力模式

preview layer支持你用videoGravity设置的三种重力模式:

  • AVLayerVideoGravityResizeAspect:保留高宽比,把视频未填充的地方显示黑条.
  • AVLayerVideoGravityResizeAspectFill:保留高宽比,但填充所有的有效屏幕区域,必要时可以裁剪视频.
  • AVLayerVideoGravityResize:这简单地延伸视频以填充可用的屏幕区域,即使这样做使扭曲图像
在preview里使用"点击聚焦"

实施点对焦与预览图层时,您需要注意。 您必须考虑图层的预览方向和重力,以及可能镜像预览的可能性。 请参阅示例代码项目AVCam-iOS:Using AVFoundation to Capture Images and Movies 以实现此功能


展示声音电平

在capture connection里要跟踪声音频道的平均和峰值电平,你要使用AVCaptureAudioChannel对象.因为声音电平不是KVO(key-value observabel),所以你必须要像更新用户界面一样轮询更新电平(比如:1秒10次).

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.
    }
}


把它们放到集合起来:获取视频帧作为 UIImage 对象

下面的例子展示了你怎么样获取视频并把帧转成你要的UIImage对象.

  • 创建一个AVCaptureSession对象来协调AV输入设备到输出的数据流.
  • 找一个AVCaptureDevice对象作为你想要的输入类型
  • 为输入设备创建一个AVCaptureDeviceInput对象
  • 创建AVCaptureVideoDataOutput对象来产生视频帧
  • 实现AVCaputreVideoDataOutput对象的delegate来处理视频帧
  • 实现一个把从delegate接收到的CMSampleBuffer转成UIImage对象的方法.

创建和配置一个Capture Session

创建一个AVCaptureSession对象来协调AV输入设备到输出的数据流,并配置它来产生中等分辨率的视频帧.

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

创建并配置Device和Device Input

AVCaptureDevice对象来代表Capture devices;这个类提供了一些方法,可以用来接收你想要的输入类型的对象.一个设备可以有多个端口,可以用AVCaptureInput对象来配置.通常,你都会用它的默认配置来配置你使用Capture Input.
找一个视频捕获Device,并创建为这个Device创建一个Device Input,再加到session里.如果找不到 合适的设备,那时deviceInputWithDevice:error:方法就会返回一个error引用

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

创建并配置Video data output

你使用AVCaptureVideoDataOutput对象来处理捕获视频里非压缩的帧.你通常会为output配置多个方面.对于视频来说,比如,你会用VideoSettings属性来指明象素格式,并且会通过设置minFrameDuration属性来限制帧速率.
为一个视频数据创建并配置output,并把它加到session;通过设置minFrameDuration属性为1/15秒,来限制帧速率和15fps:

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

这个data output对象使用delegate来发送视频帧.这个delegate必须采用AVCaptureVideoDataOutputSampleBufferDelegate协议.当你设置data output的delegate时,你必须要提供一个queue来调用回调方法:

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

你可以使用queue来修改正在发送和处理中的视频帧的优先级.

实现Sample Buffer的代理方法

在delegate class里,实现captureOutput:didOutputSampleBuffer:fromConnection:方法,这个方法只有在sample buffer被写入时才被调用.video output对象以CMSampleBuffer类型的形式发送帧,所有你需要把CMSampleBufferopaque type转成UIImage对象.

- (void)captureOutput:(AVCaptureOutput *)captureOutput
         didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
         fromConnection:(AVCaptureConnection *)connection {
 
    UIImage *image = imageFromSampleBuffer(sampleBuffer);
    // Add your code here that uses the image.
}
// Create a UIImage from sample buffer data
- (UIImage *) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer
{
    // Get a CMSampleBuffer's Core Video image buffer for the media data
    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    // Lock the base address of the pixel buffer
    CVPixelBufferLockBaseAddress(imageBuffer, 0);
 
    // Get the number of bytes per row for the pixel buffer
    void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
 
    // Get the number of bytes per row for the pixel buffer
    size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
    // Get the pixel buffer width and height
    size_t width = CVPixelBufferGetWidth(imageBuffer);
    size_t height = CVPixelBufferGetHeight(imageBuffer);
 
    // Create a device-dependent RGB color space
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
 
    // Create a bitmap graphics context with the sample buffer data
    CGContextRef context = CGBitmapContextCreate(baseAddress, width, height, 8,
      bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
    // Create a Quartz image from the pixel data in the bitmap graphics context
    CGImageRef quartzImage = CGBitmapContextCreateImage(context);
    // Unlock the pixel buffer
    CVPixelBufferUnlockBaseAddress(imageBuffer,0);
 
    // Free up the context and color space
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);
 
    // Create an image object from the Quartz image
    UIImage *image = [UIImage imageWithCGImage:quartzImage];
 
    // Release the Quartz image
    CGImageRelease(quartzImage);
 
    return (image);
}

开始和结束录制

在配置capture session结束后,你应该确保相机有权限根据用户的喜好进行录制.

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];
        });
    }
}];

如果摄像机session被配置完成,并且用户允许去访问摄像头(必要时,可以是麦克风),发送一个startRunning消息去开始录制.

重要:这个startRunning方法是一个阻塞式的调用,可能会耗费一点时间.因此,你应该在一个串行队列(serial queue)里执行session的配置,让main queue不阻塞,保持UI的及时响应.权威的实现你可以看AVCam-iOS: Using AVFoundation to Capture Images and Movies
[session startRunning];

要停止录制,你可以发送stopRunning消息给session.

视频采集的高帧速率

整个AVFoundation框架支持高帧速率内容.
你要用AVCaptureDeviceFormat类来查明设备的采集能力.此类具有返回支持的媒体类型,帧速率,视场,最大缩放因子,是否支持视频稳定等的方法.

  • Capture支持全屏720p(1280 x 720像素)分辨率,每秒60帧(fps),包括视频稳定和可放置的P帧(H264编码电影的功能,即使在较慢和较旧的硬件上也能让电影播放顺畅。 )
  • playback增强了对慢速和快速播放的音频支持,从而可以以更慢或更快的速度保留音频的时间间隔
  • 编辑完全支持可变组合中的缩放编辑
  • 当支持60 fps电影时导出提供两个选项。 可以保留可变帧速率,慢速或快速运动,或者将影片转换为任意较慢的帧速率,例如每秒30帧

播放

一个AVPlayer实例通过设置setRate:方法管理着大部分的播放速度.这个值被用作多倍播放速度.值1.0表示正常的播放速度,0.5表示一半的播放速度,5.0表示比正常播放快5倍的速度,等等.
AVPlayerItem对象支持audioTimePitchAlgorithm属性.这属性表明了当电影在多个帧速度下播放时,声音是怎么播放的.使用Time Pitch Algorithm Settings常量.

Time pitch algorithm Quality Snaps to specific frame rate Rate range
AVAudioTimePitchAlgorithmLowQualityZeroLatency Low quality,suitable for fast-forward, rewind, or low quality voice. YES 0.5, 0.666667, 0.8, 1.0, 1.25, 1.5, 2.0 rates.
AVAudioTimePitchAlgorithmTimeDomain Modest quality, less expensive computationally, suitable for voice. NO 0.5–2x rates.
AVAudioTimePitchAlgorithmSpectral Highest quality, most expensive computationally, preserves the pitch of the original item. NO 1/32–32 rates.
AVAudioTimePitchAlgorithmVarispeed High-quality playback with no pitch correction. NO 1/32–32 rates.

Editing

Export

Recording

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容