AVFoundation.framework学习(2)

AVFoundation.framework学习(1)是对AVFoundation的基本介绍,第二使用Asset和播放功能。
接下来我们开始学习编辑等功能

编辑

AVFoundation 框架提供了一组功能丰富的类,以便于编辑asset。AVFoundation编辑的核心是组合。一个composition 是一个或者多个asset的tracks的集合。
AVMutableComposition类提供用于插入和删除track以及管理时间顺序的接口。

AVFoundation.framework学习(2)_第1张图片

用AVMutableAudioMix 类,我们可以处理在合成的中的音频。目前,我们可以设置音轨指定的最大音量或者设置音量斜坡。

AVFoundation.framework学习(2)_第2张图片

看上面的图,其实AVMutableAudioMix 相当于插件一样。来设置音频的相关参数而已。

我们可以使用AVMutableVideoComposition类直接编辑视频的tracks.

AVFoundation.framework学习(2)_第3张图片

这个图中的AVMutableVideoCompositionInstruction,应该这么理解,AVMutableVideoCompositionInstruction是AVMutableVideoComposition类自带属性。

对只有一个视频的composition,我们可以输出视频指定所需要渲染的大小以及比例或者帧持续时间。通过AVMutableVideoCompositionInstruction类,我们可以修改视频的背景颜色并应用到layer层。
AVMutableVideoCompositionLayerInstruction类可以用于变换,变换斜坡,不透明不透明斜坡用于track中。

我们知道composition是一个音视频的组合体。打包在一起还是需要AVAssetExportSession类。

AVFoundation.framework学习(2)_第4张图片

创建AVMutableComposition

AVMutableComposition *mutableComposition = [AVMutableComposition composition];
// Create the video composition track.
AVMutableCompositionTrack *mutableCompositionVideoTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
// Create the audio composition track.
AVMutableCompositionTrack *mutableCompositionAudioTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];

创建很简单,但是要是想要给AVMutableComposition添加媒体数据,必须添加一个活多个合成track。用AVMutableCompositionTrack标示。

初始化的可选参数

当我们需要添加track到AVMutableComposition对象中的时候,我们必须提供track的媒体类型和track ID.虽然音频和视频是最常用的媒体类型,但是我们也可以指定其他的媒体类型,例如:AVMediaTypeSubtitle 和AVMediaTypeText

每个track都有唯一标识符。如果我们指定kCMPersistentTrackID_Invalid作为首选的track id,那么会自动生成与track关联的唯一标识符。

AVMutableComposition 对象添加数据

一旦我们拥有带有一个或者多个track的AVMutableComposition,我们就可以将媒体数据添加到响应的track中。要将媒体数据添加到相应的track中,我们就需要访问asset中的track数据。我们可以使用可变的组合track接口将同一类型的多个track放在同一个track上。

// You can retrieve AVAssets from a number of places, like the camera roll for example.
AVAsset *videoAsset = <#AVAsset with at least one video track#>;
AVAsset *anotherVideoAsset = <#another AVAsset with at least one video track#>;
// Get the first video track from each asset.
AVAssetTrack *videoAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVAssetTrack *anotherVideoAssetTrack = [[anotherVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
// Add them both to the composition.
[mutableCompositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,videoAssetTrack.timeRange.duration) ofTrack:videoAssetTrack atTime:kCMTimeZero error:nil];
[mutableCompositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,anotherVideoAssetTrack.timeRange.duration) ofTrack:anotherVideoAssetTrack atTime:videoAssetTrack.timeRange.duration error:nil];

上面的例子只是实现了两个视频资源的连接播放。

track对象的兼容性检查

一般情况下,每种媒体类型只有一个合成轨道。兼容的asset track可以降低资源的使用。我们在合并媒体类型的时候,应该将相同类型的track放在同一个track上进行合并。我们可以查询AVMutableComposition 对象是否存在可以与所需asset相兼容的track。

AVMutableCompositionTrack *compatibleCompositionTrack = [mutableComposition mutableTrackCompatibleWithTrack:<#the AVAssetTrack you want to insert#>];
if (compatibleCompositionTrack) {
    // Implementation continues.
}

注意:在同一个轨道上放置多个视频片段可能会导致视频片段之间转换播放过程中丢帧,尤其是在嵌入式设备上。

生成声音ramp(滑坡)- 我个人理解就是调整音量大小

单个AVMutableAudioMix对象可以单独的将所有的track进行自定义处理。我们可以使用audioMix类方法创建音频混合,使用AVMutableAudioMixInputParameters类的实例将音频混合与合成中特定track相关联。音频混合可用于改变track的音量。

AVMutableAudioMix *mutableAudioMix = [AVMutableAudioMix audioMix];
// Create the audio mix input parameters object.
AVMutableAudioMixInputParameters *mixParameters = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:mutableCompositionAudioTrack];
// Set the volume ramp to slowly fade the audio out over the duration of the composition.
[mixParameters setVolumeRampFromStartVolume:1.f toEndVolume:0.f timeRange:CMTimeRangeMake(kCMTimeZero, mutableComposition.duration)];
// Attach the input parameters to the audio mix.
mutableAudioMix.inputParameters = @[mixParameters];

执行自定义视频处理

与音频混合一样,我们只需要一个AVMutableVideoComposition对象即可,在合成的视频track上执行所有的自定义视频处理即可。使用视频的和传承,我们可以直接为合成的视频track设置适当的渲染大小,比例和帧速率。

更改构图的背景颜色

所有的视频合成必须具有包含一个视频的合成指令的AVVideoCompositionInstruction对象的数组。我们可以使用AVMutableVideoCompositionInstruction类来创建自己的视频合成指令。使用视频合成指令,我们可以徐工合成的背景颜色,指定是否需要后期处理或者应用layer 指令。

AVMutableVideoCompositionInstruction *mutableVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
mutableVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, mutableComposition.duration);
mutableVideoCompositionInstruction.backgroundColor = [[UIColor redColor] CGColor];
应用不透明ramps

视频合成指令也可以用于用于视频合成层指令。AVMutableVideoCompositionLayerInstruction对象可以使用transform,transform ramps,不透明,不透明的ramp,将其合成到某个视频的track。视频合成指令的layerInstructions数组中的层指令的顺序决定了如何在该合成指令的持续时间内对来自源track的视频帧进行分层和组合。

AVAsset *firstVideoAssetTrack = <#AVAssetTrack representing the first video segment played in the composition#>;
AVAsset *secondVideoAssetTrack = <#AVAssetTrack representing the second video segment played in the composition#>;
// Create the first video composition instruction.
AVMutableVideoCompositionInstruction *firstVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
// Set its time range to span the duration of the first video track.
firstVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration);
// Create the layer instruction and associate it with the composition video track.
AVMutableVideoCompositionLayerInstruction *firstVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:mutableCompositionVideoTrack];
// Create the opacity ramp to fade out the first video track over its entire duration.
[firstVideoLayerInstruction setOpacityRampFromStartOpacity:1.f toEndOpacity:0.f timeRange:CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration)];
// Create the second video composition instruction so that the second video track isn't transparent.
AVMutableVideoCompositionInstruction *secondVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
// Set its time range to span the duration of the second video track.
secondVideoCompositionInstruction.timeRange = CMTimeRangeMake(firstVideoAssetTrack.timeRange.duration, CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration));
// Create the second layer instruction and associate it with the composition video track.
AVMutableVideoCompositionLayerInstruction *secondVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:mutableCompositionVideoTrack];
// Attach the first layer instruction to the first video composition instruction.
firstVideoCompositionInstruction.layerInstructions = @[firstVideoLayerInstruction];
// Attach the second layer instruction to the second video composition instruction.
secondVideoCompositionInstruction.layerInstructions = @[secondVideoLayerInstruction];
// Attach both of the video composition instructions to the video composition.
AVMutableVideoComposition *mutableVideoComposition = [AVMutableVideoComposition videoComposition];
mutableVideoComposition.instructions = @[firstVideoCompositionInstruction, secondVideoCompositionInstruction];

以下代码片段显示如何在转换到第二个视频之前设置不透明度渐变以慢慢淡出合成中的第一个视频

结合core Animation 效果

视频合成可以通过animationTool属性将CoreAnimation的强大功能添加到合成中。通过此动画工具,我们可以完成诸如水印视频和添加标题活动画叠加层等任务。核心动画可以通过通过两种不同的方式用于视频合成:我们可以将核心动画的layer添加到期自己的合成轨迹,或者我们可以直接将合成动画效果渲染到合成的视频帧中。

CALayer *watermarkLayer = <#CALayer representing your desired watermark image#>;
CALayer *parentLayer = [CALayer layer];
CALayer *videoLayer = [CALayer layer];
parentLayer.frame = CGRectMake(0, 0, mutableVideoComposition.renderSize.width, mutableVideoComposition.renderSize.height);
videoLayer.frame = CGRectMake(0, 0, mutableVideoComposition.renderSize.width, mutableVideoComposition.renderSize.height);
[parentLayer addSublayer:videoLayer];
watermarkLayer.position = CGPointMake(mutableVideoComposition.renderSize.width/2, mutableVideoComposition.renderSize.height/4);
[parentLayer addSublayer:watermarkLayer];
mutableVideoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];

将上面的技术组合成完全的例子

我们将两个视频asset track和一个音频 asset track组合来创建单个视频,步骤如下:

  • 1.创建AVMutableComposition对象并添加多个AVMutableCompositionTrack对象
  • 2.将AVAssetTrack对象的时间范围添加到组合的track中
  • 3.检查视频asset track的preferredTransform属性以确定视频的方向
    1. AVMutableVideoCompositionLayerInstruction对象将transform应用于合成的视频track。
  • 5.为视频合成的renderSize和frameDuration设置适当的值导出到视频文件时,将合成与视频合成结合使用
  • 6.保存视频文件到相机胶卷,也可以是本地啦。

下列是完整代码

-(void)edit{
    
    ///1
    AVMutableComposition *mutableComposition = [AVMutableComposition composition];
    AVMutableCompositionTrack *videoCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
    AVMutableCompositionTrack *audioCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
    ///2
    AVURLAsset *firstVideoAsset = [self getAVAssetA];
    AVURLAsset *secondVideoAsset = [self getAVAssetABC];
    AVURLAsset * audioAsset = [self getAVASssetAAA];
    AVAssetTrack *firstVideoAssetTrack = [[firstVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
    AVAssetTrack *secondVideoAssetTrack = [[secondVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
    AVAssetTrack *audioAssetTrack = [[audioAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
    
    [videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration) ofTrack:firstVideoAssetTrack atTime:kCMTimeZero error:nil];
    [videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, secondVideoAssetTrack.timeRange.duration) ofTrack:secondVideoAssetTrack atTime:firstVideoAssetTrack.timeRange.duration error:nil];
    [audioCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration)) ofTrack:audioAssetTrack atTime:kCMTimeZero error:nil];
    ///3
    BOOL isFirstVideoPortrait = NO;
    CGAffineTransform firstTransform = firstVideoAssetTrack.preferredTransform;
    // Check the first video track's preferred transform to determine if it was recorded in portrait mode.
    if (firstTransform.a == 0 && firstTransform.d == 0 && (firstTransform.b == 1.0 || firstTransform.b == -1.0) && (firstTransform.c == 1.0 || firstTransform.c == -1.0)) {
        isFirstVideoPortrait = YES;
    }
    BOOL isSecondVideoPortrait = NO;
    CGAffineTransform secondTransform = secondVideoAssetTrack.preferredTransform;
    // Check the second video track's preferred transform to determine if it was recorded in portrait mode.
    if (secondTransform.a == 0 && secondTransform.d == 0 && (secondTransform.b == 1.0 || secondTransform.b == -1.0) && (secondTransform.c == 1.0 || secondTransform.c == -1.0)) {
        isSecondVideoPortrait = YES;
    }
    if ((isFirstVideoPortrait && !isSecondVideoPortrait) || (!isFirstVideoPortrait && isSecondVideoPortrait)) {
        UIAlertView *incompatibleVideoOrientationAlert = [[UIAlertView alloc] initWithTitle:@"Error!" message:@"Cannot combine a video shot in portrait mode with a video shot in landscape mode." delegate:self cancelButtonTitle:@"Dismiss" otherButtonTitles:nil];
        [incompatibleVideoOrientationAlert show];
        return;
    }
    ///4
    AVMutableVideoCompositionInstruction *firstVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    // Set the time range of the first instruction to span the duration of the first video track.
    firstVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration);
    AVMutableVideoCompositionInstruction * secondVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    // Set the time range of the second instruction to span the duration of the second video track.
    secondVideoCompositionInstruction.timeRange = CMTimeRangeMake(firstVideoAssetTrack.timeRange.duration, CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration));
    
    AVMutableVideoCompositionLayerInstruction *firstVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoCompositionTrack];
    // Set the transform of the first layer instruction to the preferred transform of the first video track.
    [firstVideoLayerInstruction setTransform:firstTransform atTime:kCMTimeZero];
    AVMutableVideoCompositionLayerInstruction *secondVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoCompositionTrack];
    // Set the transform of the second layer instruction to the preferred transform of the second video track.
    [secondVideoLayerInstruction setTransform:secondTransform atTime:firstVideoAssetTrack.timeRange.duration];
    firstVideoCompositionInstruction.layerInstructions = @[firstVideoLayerInstruction];
    secondVideoCompositionInstruction.layerInstructions = @[secondVideoLayerInstruction];
    AVMutableVideoComposition *mutableVideoComposition = [AVMutableVideoComposition videoComposition];
    mutableVideoComposition.instructions = @[firstVideoCompositionInstruction, secondVideoCompositionInstruction];
    ///5
    CGSize naturalSizeFirst, naturalSizeSecond;
    // If the first video asset was shot in portrait mode, then so was the second one if we made it here.
    if (isFirstVideoPortrait) {
        // Invert the width and height for the video tracks to ensure that they display properly.
        naturalSizeFirst = CGSizeMake(firstVideoAssetTrack.naturalSize.height, firstVideoAssetTrack.naturalSize.width);
        naturalSizeSecond = CGSizeMake(secondVideoAssetTrack.naturalSize.height, secondVideoAssetTrack.naturalSize.width);
    }
    else {
        // If the videos weren't shot in portrait mode, we can just use their natural sizes.
        naturalSizeFirst = firstVideoAssetTrack.naturalSize;
        naturalSizeSecond = secondVideoAssetTrack.naturalSize;
    }
    float renderWidth, renderHeight;
    // Set the renderWidth and renderHeight to the max of the two videos widths and heights.
    if (naturalSizeFirst.width > naturalSizeSecond.width) {
        renderWidth = naturalSizeFirst.width;
    }
    else {
        renderWidth = naturalSizeSecond.width;
    }
    if (naturalSizeFirst.height > naturalSizeSecond.height) {
        renderHeight = naturalSizeFirst.height;
    }
    else {
        renderHeight = naturalSizeSecond.height;
    }
    mutableVideoComposition.renderSize = CGSizeMake(renderWidth, renderHeight);
    // Set the frame duration to an appropriate value (i.e. 30 frames per second for video).
    mutableVideoComposition.frameDuration = CMTimeMake(1,30);
    ///6
    // Create a static date formatter so we only have to initialize it once.
    static NSDateFormatter *kDateFormatter;
    if (!kDateFormatter) {
        kDateFormatter = [[NSDateFormatter alloc] init];
        kDateFormatter.dateStyle = NSDateFormatterMediumStyle;
        kDateFormatter.timeStyle = NSDateFormatterShortStyle;
    }
    // Create the export session with the composition and set the preset to the highest quality.
    AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mutableComposition presetName:AVAssetExportPresetHighestQuality];
    // Set the desired output URL for the file created by the export process.
    exporter.outputURL = [NSURL fileURLWithPath:self.path];
    // Set the output file type to be a QuickTime movie.
    exporter.outputFileType = AVFileTypeQuickTimeMovie;
    exporter.shouldOptimizeForNetworkUse = YES;
    exporter.videoComposition = mutableVideoComposition;
    // Asynchronously export the composition to a video file and save this file to the camera roll once export completes.
    [exporter exportAsynchronouslyWithCompletionHandler:^{
        switch ([exporter status]) {
            case AVAssetExportSessionStatusFailed:
                NSLog(@"%@",[exporter error]);
                NSLog(@"Export failed: %@", [[exporter error] localizedDescription]);
                break;
            case AVAssetExportSessionStatusCancelled:
                NSLog(@"Export canceled");
                break;
            case AVAssetExportSessionStatusCompleted:{
                dispatch_async(dispatch_get_main_queue(), ^{
         
                });
            }
            default:
                
                break;
        }
    }];
}

上述代码亲测有效,不过就是导出到文件的过程很慢,大家在导出的回调打断点可能需要几分钟的时间才能回调回来
我用了两个mp4 文件和一个mp3 文件
这里必须保持mp4 文件的方向一样才行

总结下上述代码结构


AVFoundation.framework学习(2)_第5张图片

上述代码没涉及到水印,和音频编辑。


静态和视频媒体捕获

上一篇大概只是简单的讲解了这部分,下面详细讲解下

要管理来自设备(如摄像头或者麦克风)的捕获,您需要组装对象以表示输入和输出,并使用AVCaptureSession来协调他们之间的数据流。基本流程如下:

  • 1.初始化对象 AVCaptureDevice,该对象代表输入设备,例如摄像头和麦克风
  • 2.AVCaptureInput的子类用于配置输入设备的端口
  • 3.AVCaptureOutput 的具体子类,用于管理电影文件或者静止图像的输出
  • 4.初始化AVCaptureSession对象,用于协调从输入到输出的数据流。
AVFoundation.framework学习(2)_第6张图片
配置多个输入输出源的效果图

我们可以用一个捕获连接来开启或者关闭从输入到输出设备的数据流。我们还可以使用连接来监控音频通道中的平均功率和峰值功率。

媒体捕获不能同时支持前置摄像头和后置摄像头

使用捕获会话来协调数据流

一个AVCaptureSession对象用于管理数据捕获的中央协调对象,理解为指挥员吧。我们可以用该对象来协调从输入设备到输出设备的数据流。我们只需要将所需要的捕获设备和输出设备添加到AVCaptureSession对象中,然后通过调用startRunning来启动数据流,发送stopRunning 来停止数据流。

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

配置 AVCaptureSession对象

我们通过AVCaptureSession对象可以设置图像的质量和分辨率。预设是一个常数,用于便是多种配置中的一种;具体配置是根据需求而来的。

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

如果需要调整比预设更精细级别的会话参数,或者您需要对正在运行的会话进行更改,则可以使用beginConfiguration和commitConfiguration方法来包围更改。beginConfiguration和commitConfiguration必须成对出现。调用beginConfiguration方法,我们可以添加或者删除输出,更改sessionPreset属性或者配置单个捕获输入和输出属性。在调用commitConfiguration之前其实不会进行任何更改,调用该方法就是将其一起应用。

[session beginConfiguration];
// Remove an existing capture device.
// Add a new capture device.
// Reset the preset.
[session commitConfiguration];
监控捕获会话的状态

捕获会话会发送通知,我们可以增加观察者接收,例如:启动和停止运行或者中断的通知。如果发生运行时错误,我们可以注册AVCaptureSessionRuntimeErrorNotification通知。我们还可以询问会话的运行属性以查明它是否正在运行,以及它的中断属性以查明它是否被中断。除此之外,运行和中断属性都符合KVO,并且通知在主线程上。

AVCaptureDevice对象代表输入设备

AVCaptureDevice对象可以抽象为物理捕获设备,该设备向AVCaptureSession对象提供输入数据,例如音频或者视频。每个输入设备都有一个与之对应的对象。

您可以使用AVCaptureDevice 的device和devicesWithMediaType方法来获取当前可用的捕获设备。但是,有时候设备列表可能发生变化。单钱的输入设备可能变的不可用(比如被另一个设备使用),并且新的输入设备可能变的可用(如果被另一个设备放弃)。我们应该注册接收AVCaptureDeviceWasConnectedNotification和AVCaptureDeviceWasDisconnectedNotification通知,以便在可用设备列表更改时候就到通知。

设备特征

我们可以向设备询问其不同的特征。我们也可以使用hasMediaType:和supportsAVaptureSessionPreset来测试输入设备是否提供特定媒体类型或者支持给定的捕获会话预设。要向用户提供信息,您可以找到捕获设备的位置,以及其本地化化的名称。如果要显示捕获设备列表以允许用户选择一个捕获设备列表,这可能很有用。


AVFoundation.framework学习(2)_第7张图片
前置摄像头和后置摄像头
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");
        }
    }
}
设备捕获功能

不同的设备具有不同的功能,例如,有些设备可能支持不同的焦点或者闪光功能;有些人可能会对一个聚焦到一点感兴趣。

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属性。

我们以类似的方式使用各种不同的功能。有常量指定的特定模式,我们可以询问设备是否支持特定模式。在某些情况下,我们可以观察在要素发生变化时要通知的属性。不管在什么情况下,我们应该在更改特定功能的模式之前锁定设备,如配置设备中的描述。

聚焦模式

有三种聚焦模式

1.AVCaptureFocusModeLocked焦点位置是固定的。
当想要允许用户撰写场景然后锁定焦点,这非常有用。

  1. AVCaptureFocusModeAutoFocus 相机执行单次扫描,然后恢复锁定。
    这适用于想要选择要聚焦的特定项目然后保持对该项目的焦点的情况,即使它不在场景的中心
  2. AVCaptureFocusModeContinuousAutoFocus相机根据需要连续自动对焦

可以使用isFocusModeSupported:方法确定设备是否支持给定的聚焦模式,然后使用focusMode属性设置模式。

此外,设备可以支持关注焦点。我们可以使用focusPointOfInterestSupported测试设备是否支持。如果支持,则使用focusPointOfInterest设置焦点。如果我们传入CGPoint={0,0},那么标示图片区域的左上角,{1,1}表示横向模式的右下角。

我们可以使用adjustmentFocus属性来判断当前设备是否正在聚焦。我们可以使用KVO来观察属性,一遍在设备启动和停止聚焦的时候收到通知。

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

曝光模式

有两种曝光模式

1.AVCaptureExposureModeContinuousAutoExposure设备会根据需要自动调整曝光级别
2.AVCaptureExposureModeLocked 曝光级别固定为当前级别
使用isExposureModeSupported 方法确定设备是否支持给定的曝光模式,然后使用exposureMode属性设置模式

此外,设备可能支持感兴趣的曝光点。我们可以使用exposurePointOfInterestSupported测试是否支持。如果支持,那么使用exposurePointOfInterest设置曝光点。如果我们传入CGPoint={0,0},那么标示图片区域的左上角,{1,1}表示横向模式的右下角。

我们可以使用adjustExposure属性来判断当前设备是否正在更改曝光点。我们可以使用KVO来观察属性,以便在设备启动和停止更改曝光点时候收到通知。

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

有三种闪光模式

  • AVCaptureFlashModeOff 不开启闪光
  • AVCaptureFlashModeOn 始终开启闪光
  • AVCaptureFlashModeAuto闪光灯将根据环境光线条件反射
    我们可以使用hasFlash 函数测试设备是否有闪光功能。如果是YES,就是有闪光功能。然后使用flashMode设置相应的闪光模式
火炬模式(手电筒)

有三种模式

  • AVCaptureTorchModeOff手电筒关
  • AVCaptureTorchModeOn 手电筒开
  • AVCaptureTorchModeAuto 根据需要自动打开或者关闭手电筒
    我们可以使用hasTorch来确定设备是否具有手电筒功能。使用isTorchModeSupported判断是否支持给定的手电筒模式。使用torchMode模式来设置手电筒

视频稳定(去除抖动)

电影视频稳定功能适用于视频操作的连接,具体取决于特定的设备硬件。即便如此,也不是所有的源格式和视频分辨率都支持。

启用电影视频稳定功能还可能会在视频捕获管道中引入额外的延迟,要检测什么时候使用视频稳定,使用videoStabilizationEnabled属性。enableVideoStabilizationWhenAvailable 属性允许app在歙县头支持的情况下自动启用视频稳定功能。默认情况下,该功能是被禁止的。

白平衡

有两种白平衡

  • 1.AVCaptureWhiteBalanceModeLocked 白平时固定的
  • 2.AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance 相机根据需要自动平衡

这个具体有啥用大家可以自已搜索。

白平衡,字面上的理解是白色的平衡。白平衡是描述显示器中红、绿、蓝三基色混合生成后白色精确度的一项指标。白平衡是电视摄像领域一个非常重要的概念,通过它可以解决色彩还原和色调处理的一系列问题。白平衡是随着电子影像再现色彩真实而产生的,在专业摄像领域白平衡应用的较早, [1] 现在家用电子产品(家用摄像机、数码照相机)中也广泛地使用,然而技术的发展使得白平衡调整变得越来越简单容易,但许多使用者还不甚了解白平衡的工作原理,理解上存在诸多误区。它是实现摄像机图像能精确反映被摄物的色彩状况,有手动白平衡和自动白平衡等方式。许多人在使用数码摄像机拍摄的时候都会遇到这样的问题:在日光灯的房间里拍摄的影像会显得发绿,在室内钨丝灯光下拍摄出来的景物就会偏黄,而在日光阴影处拍摄到的照片则莫名其妙地偏蓝,其原因就在于白平衡的设置上。 [1]

我们可以使用isWhiteBalanceModeSupported 方法确定设备是否支持白平衡模式。然后用whiteBalanceMode设置白平衡

我们可以使用adjustWhiteBalance属性来确定当前是否正在更改其白平衡设置。我们可以使用KVO来观察属性,以便在设备启动时通知并停止更改其白平衡设置。

设置设备方向

我们可以在AVCaptureConnection上设置所需要的方向,已指定我们希望如何在AVCaptureOutput中定向图像以进行连接。

使用AVCaptureConnectionsupportsVideoOrientation属性确定设备是否支持更改视频的方向,使用videoOrientation属性指定您希望图像在输出端口中的方向。

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

要在设备上设置捕获属性,必须首选使用lockForConfiguration获取设备上的锁。这样可以避免其他程序正在更改该设置。

if ([device isFocusModeSupported:AVCaptureFocusModeLocked]) {
    NSError *error = nil;
    if ([device lockForConfiguration:&error]) {
        device.focusMode = AVCaptureFocusModeLocked;
        [device unlockForConfiguration];
    }
    else {

}
}
设备切换

有时候我们希望允许用户在输入设备之前切换。例如,从使用前置摄像头切换到后置摄像头。为了避免暂停,我们可以在会话运行时进行重新配置会话,这里我们需要使用beginConfiguration 和commitConfiguration 来组合配置更改

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

这里在调用commitConfiguration的时候,所有的更新才能生效。

使用捕获输入将捕获设备添加到会话(session)中

要将捕获设备添加到捕获会话中,我们应该使用AVCaptureDeviceInput的实例。捕获设备输入管理设备的端口。

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

我们调用addInput 向会话中添加输入。添加之前最好调用下canAddInput判断是否能增加

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对象表示。捕获会话使用AVCaptureConnection对象来定义一组AVCaptureInputPort 对象与单个AVCaptureOutput之间的映射

使用捕获输出从会话中获取输出

要从捕获会话获取输出,请添加一个或者多个输出。输出的类是AVCaptureOutput

  • AVCaptureMovieFileOutput输出一个电影文件
  • AVCaptureVideoDataOutput如果我们要处理正在捕获的视频中帧,例如创建自己的自定义视图层。
  • AVCaptureAudioDataOutput 如果我们要处理正在捕获的音频数据
  • AVCaptureStillImageOutput 如果要捕获包含元数据的静止图像

我们可以使用addOutput:将输出添加到AVCaptureSession对象中。我们可以通过使用canAddOutput 来判断输出对象是否与当前会话兼容。我们也可以在会话运行时根据需要添加和删除输出。

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对象将影片数据保存到文件。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#>;

输出的分辨率和比特率取决于捕获会话的sessionPreset。视频编码通常是H.264,音频编码通常是AAC。实际值因设备而已。

开始录制

我们开始使用startRecordingToOutputFileURL:recordingDelegate录制一个QuickTime影片。我们需要提供基于文件的url和委托。url不能表示现有的文件,因为电影文件输出不会覆盖现有资源。我们还必须具有写入指定位置的权限才行。代理必须符合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的实现中,委托可能会将生成的影片写入到相机相册中。我们应该检查可能发生的错误。

确保文件写入成功

要确保文件是否已成功保存,请执行captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error。我们在改回调函数中不仅要检查错误,还需要检查错误的用户信息字典中的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的信息字典中的AVErrorRecordingSuccessfullyFinishedKeykey值,因为即使我们收到错误,文件也可能保存成功。该错误可能表示已经到达我们设置的摸个记录约束。例如:AVErrorMaximumDurationReached或者AVErrorMaximumFileSizeReached。
可能导致录制停止的原因如下:

  • 磁盘满了 - AVErrorDiskFull
  • 录音设备已断开连接-AVErrorDeviceWasDisconnected
  • 会话被中断(比如收到电话)- AVErrorSessionWasInterrupted
将元数据添加到文件

我们可以随时设置电影文件的元数据,即使在录制时也是如此。这对于在记录开始时信息不可能用的情况很有用,如位置信息的情况。文件输出的元数据由AVMetadataItem对象数组表示;我们可以使用其可变子类AVMutableMetadataItem的实例来创建自己的元数据。

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对象使用委托来销售视频帧。您可以使用setSampleBufferDelegate:queue:设置委托。除了设置委托外,还指定了一个调用他们委派方法的串行队列。您必须使用串行队列来确保以正确的顺序将帧传递给委托。您可以使用队列来修改交付和处理视频帧的优先级。

这些帧在captureOutput:didOutputSampleBuffer:fromConnection中回调,作为CMSampleBufferRef 不透明类型的实例。默认情况下,缓存区是以最有效的格式发出的。我们可以使用videoSettings属性指定自定义输出格式。视频设置属性是字典;目前唯一指定的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];
处理视频的性能注意事项

我们应该将会话输出设置为应用程序的最低实际分辨率。将输出设置设置为高于必要的分辨率会浪费处理周期并且不比要的消耗功率。

我们必须保证captureOutput:didOutputSampleBuffer:fromConnection的实现能够在分配帧的时间内处理样本缓冲区。如果花费太长时间来保存视频帧,avfoundation会停止提供帧,不仅你的delegate还有其他输出也会停止,如:预览图层。

我们可以使用捕获视频数据输出的minFrameDuration属性来确保我们有足够的时间来处理视频帧,代价就是帧速率低于其他情况。我们可以设置alwaysDiscardsLateVideoFrames是yes.(默认值也是yes).这可以保证丢弃掉任何延迟的视频帧而不是传递给我们进行处理。或者如果我们正在录制并且输出的帧有点晚但是你希望获取得到所有的这些,我们应该设置这个属性为NO。这并不意味这帧不会丢失,这只能保证他们可能不会被提前或者有效的丢弃。

拍照(捕获静态图像)

如果想要捕获元数据的静态图像,请使用AVCaptureStillImageOutput输出。图像的分辨率取决于会话的预设以及设备

像素和编码格式

不同的设备不同的图片格式。沃恩可以使用availableImageDataCVPixelFormatTypes和availableImageDataCodecTypes 找出设备支持的像素和编解码器类型。每个方法返回特定设备支持的值的数组。我们可以设置outputSettings字典以指定所需的图像格式。

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

如果我们捕获jpeg图像,通常不应指定自己的压缩格式。相反的,我们应该让静止图像输出为我们进行压缩,因为这样的压缩是硬件加速的。如果需要图像的data数据,尽管修改了图像的元数据,但是也应该用jpegStillImageNSDataRepresentation:方法来获取data数据。

拍照

如果要拍照,我们应该向输出发送captureStillImageAsynchronouslyFromConnection:completionHandler:message函数。第一个参数是您用于捕获的连接。这里我们需要查找连接视频的端口。

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

captureStillImageAsynchronouslyFromConnection:completionHandler: 的第二个参数一个block,这个block带有两个参数,一个参数是CMSampleBuffer对象,该对象包含静态图片,另一个参数是NSError对象。样本缓存区包含元数据,例如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.
    }];

向用户展示正在录制的内容

我们可以为用户提供相机或者麦克风录制内容的预览。

视频预览

我们可以使用AVCaptureVideoPreviewLayer对象向用户提供正在录制的内容的预览。AVCaptureVideoPreviewLayer是CALayer的子类。

使用AVCaptureVideoDataOutput类为app提供提供了将视频像素呈现给用户之前访问他们的能力。

与捕获输出不同,视频预览层维护对其他关联的会话的强引用。这是为了确保在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];

通常,预览layer的行为与渲染树中的任何其他的CAlayer对象相同。我们可以对推向缩放或者变换旋转等操作。这里的区别在于,我们可能需要设置layer的方向属性以指定应该如何旋转来自相机的图像。除此之外,我们可以查询supportsVideoMirroring属性来测试视频镜像的设备支持。我们可以根据需要设置videoMirrored属性,但是当automaticAdjustsVideoMirroring设置为yes(默认值是yes),将会根据会话配置自动设置镜像值。

视频权重模式

预览layer支持权重模式:

    1. AVLayerVideoGravityResizeAspect 保留纵横比,在视频为填充区域留下黑条
    1. AVLayerVideoGravityResizeAspectFill 保留纵横比,会充满整个屏幕,必要时会裁剪视频
    1. AVLayerVideoGravityResize 充满全屏,图像可能发生形变。
使用点击聚焦预览

与预览图层一起实现的点击聚焦是需要注意。我们必须考虑layer的预览方向和权重,以及可能镜像预览的可能性。

显示音频级别

要监视捕获连接中音频通道的平均功率和峰值功率级别,使用AVCaptureAudioChannel对象。音频级别不是KVO可以观察的,因此沃恩需要轮训更新级别,以便更新用户界面。(例如每秒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.
    }
}

整体应用

步骤

  • 1.创建AVCaptureSession对象以协调输入设备到输出设备的数据流。
  • 2.找到所需输入类型的AVCaptureDevice对象。
  • 3.为设备创建AVCaptureDeviceInput对象
  • 4.创建AVCaptureVideoDataOutput对象已生成真视频
  • 5.给AVCaptureVideoDataOutput设置一个代理对象
  • 6.实现代理对象的代理函数,将代理对象接受的CMSampleBuffer对象转换成UIImage对象。
-(void)capture{
    ///1
    AVCaptureSession *session = [[AVCaptureSession alloc] init];
    session.sessionPreset = AVCaptureSessionPresetMedium;
    ///2 3视频对象
   AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    [device lockForConfiguration:nil];
    [device setActiveVideoMinFrameDuration: CMTimeMake(1, 15)];
    [device unlockForConfiguration];
    NSError *error = nil;
    AVCaptureDeviceInput *input =[AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
    if (!input) {
        // Handle the error appropriately.
        return;
    }
    [session addInput:input];
    ///4
    AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
    [session addOutput:output];
    output.videoSettings =
    @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) };
//    output.minFrameDuration = CMTimeMake(1, 15);///这里设置的是一秒15帧
    ///5
    dispatch_queue_t queue = dispatch_queue_create("MyQueue", NULL);
    [output setSampleBufferDelegate:self queue:queue];

    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];
    self.captureSession = session;
}


- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
       fromConnection:(AVCaptureConnection *)connection {
    UIImage *image = [self imageFromSampleBuffer:sampleBuffer];
    dispatch_async(dispatch_get_main_queue(), ^{
 
    static UIImageView *imageView=nil;
    if (!imageView) {
       imageView =[[UIImageView alloc]initWithFrame:self.view.bounds];
        [self.view addSubview:imageView];
    }
    
//        CIImage *ciImage = [CIImage imageWithCVPixelBuffer:imageBuffer];

        imageView.image = image;
        
    });
    
    // Add your code here that uses the image.
}


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

- (void)viewDidLoad {
    [super viewDidLoad];
    [self capture];
}

亲自测试有效

这里遇到的坑, UIImage *image = [self imageFromSampleBuffer:sampleBuffer];调用不能转移到主队列中,因为到主队列中,sampleBuffer buffer就被释放掉了。导致崩溃
我们一定要保持对AVCaptureSession对象的强制引用

本来想在做些具体的demo呢。篇幅限制,部分内容的demo移动到下一篇的结尾部分。

你可能感兴趣的:(AVFoundation.framework学习(2))