iOS视频流采集概述(AVCaptureSession)
需求:需要采集到视频帧数据从而可以进行一系列处理(如: 裁剪,旋转,美颜,特效....). 所以,必须采集到视频帧数据.
阅读前提:
- 使用AVFoundation框架
- 采集音视频帧数据
Overview
AVCaptureSession:使用相机或麦克风实时采集音视频数据流.
- AVCaptureSession : 管理输入输出音视频流
- AVCaptureDevice : 相机硬件的接口,用于控制硬件特性,诸如镜头的位置(前后摄像头)、曝光、闪光灯等。
- AVCaptureInput : 配置输入设备,提供来自设备的数据
- AVCaptureOutput : 管理输出的结果(音视频数据流)
- AVCaptureConnection: 表示输入与输出的连接
- AVCaptureVideoPreviewLayer: 显示当前相机正在采集的状况
文章图片
1.AVCaptureSession 下图展示了向session中添加输入输出后的连接情况
文章图片
2.AVCaptureConnection 授权
首先需要在Info.plist文件中添加键
Privacy - Camera Usage Description
以请求相机权限.注意: 如果不添加,程序crash,如果用户不给权限,则会显示全黑的相机画面.1. 使用Capture Session管理数据流
AVCaptureSession *session = [[AVCaptureSession alloc] init];
// Add inputs and outputs.
[session startRunning];
1.1. 使用preset配置分辨率,帧率
- canSetSessionPreset:检查是否支持指定分辨率
- setActiveVideoMinFrameDuration: 设置帧率最小值
- setActiveVideoMaxFrameDuration: 设置帧率最大值
CMTimeMake: 分子为1,即每秒钟来多少帧.
- 在低帧率下(帧率<=30)可以用如下方式设置
- (void)setCameraResolutionByPresetWithHeight:(int)height session:(AVCaptureSession *)session {
[session beginConfiguration];
session.sessionPreset = preset;
[session commitConfiguration];
}- (void)setCameraForLFRWithFrameRate:(int)frameRate {
// Only for frame rate <= 30
AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
[captureDevice lockForConfiguration:NULL];
[captureDevice setActiveVideoMinFrameDuration:CMTimeMake(1, frameRate)];
[captureDevice setActiveVideoMaxFrameDuration:CMTimeMake(1, frameRate)];
[captureDevice unlockForConfiguration];
}
- 高帧率下设置分辨率(帧率>30)
setActiveVideoMinFrameDuration
与setActiveVideoMaxFrameDuration
是无法做到的,Apple规定我们需要使用新的方法设置帧率setActiveVideoMinFrameDuration
与setActiveVideoMaxFrameDuration
,并且该方法必须配合新的设置分辨率activeFormat
的方法一起使用.新的设置分辨率的方法
activeFormat
与sessionPreset
是互斥的,如果使用了一个, 另一个会失效,建议直接使用高帧率的设置方法,废弃低帧率下设置方法,避免产生兼容问题。Apple在更新方法后将原先分离的分辨率与帧率的设置方法合二为一,原先是单独设置相机分辨率与帧率,而现在则需要一起设置,即每个分辨率有其对应支持的帧率范围,每个帧率也有其支持的分辨率,需要我们遍历来查询,所以原先统一的单独的设置分辨率与帧率的方法在高帧率模式下相当于弃用,可以根据项目需求选择,如果确定项目不会支持高帧率(fps>30),可以使用以前的方法,简单且有效.
注意: 使用具体设置方法参考另一篇文章:iOS相机设置实战activeFormat
方法后,之前使用sessionPreset
方法设置的分辨率将自动变为AVCaptureSessionPresetInputPriority
,所以如果项目之前有用canSetSessionPreset
比较的if语句也都将失效,建议如果项目必须支持高帧率则彻底启用sessionPreset
方法.
注意: 在将session配置为使用用于高分辨率静态拍摄的活动格式并将以下一个或多个操作应用于AVCaptureVideoDataOutput时,系统可能无法满足目标帧速率:缩放,方向更改,格式转换。1.2. 更改相机设置 如果你需要在开启相机后进一步调节相机参数,在
beginConfiguration
和commitConfiguration
中写入更改的代码.调用beginConfiguration
后可以添加移除输入输出,更改分辨率,配置个别的输入输出属性,直到调用commitConfiguration
所有的更改才会生效.[session beginConfiguration];
// Remove an existing capture device.
// Add a new capture device.
// Reset the preset.
[session commitConfiguration];
1.3. 监听Session状态 可以使用通知监听相机当前状态,如开始,停止,意外中断等等...
- 监听掉帧
- (void)captureOutput:(AVCaptureOutput *)output didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
- 处理相机运行中突然出错
[kTVUNotification addObserver:self selector:@selector(handleCameraRuntimeError)
name:AVCaptureSessionRuntimeErrorNotification
object:nil];
[kTVUNotification addObserver:self selector:@selector(handleCameraInterruptionEndedError)
name:AVCaptureSessionInterruptionEndedNotification
object:nil];
[kTVUNotification addObserver:self selector:@selector(handleCameraWasInterruptedError)
name:AVCaptureSessionWasInterruptedNotification
object:nil];
2. AVCaptureDevice表示输入设备
2.1. 定义 AVCaptureDevice对象是关于相机硬件的接口,用于控制硬件特性,诸如镜头的位置、曝光、闪光灯等。
2.2. 获取设备 使用AVCaptureDevice的
devices
和 devicesWithMediaType:
方法可以找到我们需要的设备, 可用设备列表可能会发生变化, 如它们被别的应用使用,或一个新的输入设备接入(如耳机),通过注册AVCaptureDeviceWasConnectedNotification,AVCaptureDeviceWasDisconnectedNotification可以在设备变化时得到通知.2.3. 设备特性 可以通过代码获取当前输入设备的位置(前后置摄像头)以及其他硬件相关信息.
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.4. 相机功能设置 不同设备具有不同的功能,如果需要可以开启对应的功能
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
NSMutableArray *torchDevices = [[NSMutableArray alloc] init];
for (AVCaptureDevice *device in devices) {
[if ([device hasTorch] &&
[device supportsAVCaptureSessionPreset:AVCaptureSessionPreset640x480]) {
[torchDevices addObject:device];
}
}
注意:在设置相机属性前,总是先通过API查询当前设备是否支持该功能,再进行相应处理
- 聚焦模式-Focus Modes
- AVCaptureFocusModeLocked: 设置一个固定的聚焦点
- AVCaptureFocusModeAutoFocus: 首次自动对焦然后锁定一个聚焦点
- AVCaptureFocusModeContinuousAutoFocus: 指当场景改变,相机会自动重新对焦到画面的中心点
isFocusModeSupported: 查询设备是否支持.
adjustingFocus: 判断一个设备是否正在改变对焦点
使用
focusPointOfInterestSupported
测试设备是否支持设置对焦点,如果支持,使用focusPointOfInterest
设置聚焦点,{0,0}代表画面左上角坐标,{1,1}代表右下角坐标.if ([currentDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
CGPoint autofocusPoint = CGPointMake(0.5f, 0.5f);
[currentDevice setFocusPointOfInterest:autofocusPoint];
[currentDevice setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
}
- 曝光模式-Exposure Modes
- AVCaptureExposureModeContinuousAutoExposure: 自动调节曝光模式
- AVCaptureExposureModeLocked: 固定的曝光模式
isExposureModeSupported:是否支持某个曝光模式
adjustingExposure:判断一个设备是否正在改变曝光值
if ([currentDevice isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) {
CGPoint exposurePoint = CGPointMake(0.5f, 0.5f);
[currentDevice setExposurePointOfInterest:exposurePoint];
[currentDevice setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
}
- 闪光灯模式-Flash Modes
- AVCaptureFlashModeOff: 永不开启
- AVCaptureFlashModeOn: 总是开启
- AVCaptureFlashModeAuto: 自动开启,根据光线判断
hasFlash:是否有闪光灯
isFlashModeSupported:是否支持闪光灯模式
- 手电筒模式-Torch Mode
- AVCaptureTorchModeOff
- AVCaptureTorchModeOn
- AVCaptureTorchModeAuto
hasTorch: 是否有手电筒
isTorchModeSupported: 是否支持手电筒模式
手电筒只有在相机开启时才能打开
- 视频稳定性-Video Stabilization
- videoStabilizationEnabled
- enablesVideoStabilizationWhenAvailable
开启该功能可能造成画面延迟
- 白平衡-White Balance
- AVCaptureWhiteBalanceModeLocked
- AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance
isWhiteBalanceModeSupported: 是否支持白平衡模式
adjustingWhiteBalance: 是否正在调整白平衡
相机为了适应不同类型的光照条件需要补偿。这意味着在冷光线的条件下,传感器应该增强红色部分,而在暖光线下增强蓝色部分。在 iPhone 相机中,设备会自动决定合适的补光,但有时也会被场景的颜色所混淆失效。幸运地是,iOS 8 可以里手动控制白平衡。
自动模式工作方式和对焦、曝光的方式一样,但是没有“感兴趣的点”,整张图像都会被纳入考虑范围。在手动模式,我们可以通过开尔文所表示的温度来调节色温和色彩。典型的色温值在 2000-3000K (类似蜡烛或灯泡的暖光源) 到 8000K (纯净的蓝色天空) 之间。色彩范围从最小的 -150 (偏绿) 到 150 (偏品红)。
- 设置设备方向
- AVCaptureConnectionsupportsVideoOrientation:
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 {
// Respond to the failure as appropriate.
- 切换设备
AVCaptureSession *session = <#A capture session#>;
[session beginConfiguration];
[session removeInput:frontFacingCameraDeviceInput];
[session addInput:backFacingCameraDeviceInput];
[session commitConfiguration];
3. 配置Capture Inputs添加到Session中
一个AVCaptureInput代表一种或多种媒体数据,比如,输入设备可以同时提供视频和音频数据.每种媒体流代表一个AVCaptureInputPort对象.使用AVCaptureConnection可以将AVCaptureInputPort与AVCaptureOutput连接起来.
NSError *error;
AVCaptureDeviceInput *input =
[AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
// Handle the error appropriately.
}AVCaptureSession *captureSession = <#Get a capture session#>;
AVCaptureDeviceInput *captureDeviceInput = <#Get a capture device input#>;
if ([captureSession canAddInput:captureDeviceInput]) {
[captureSession addInput:captureDeviceInput];
}
else {
// Handle the failure.
}
4. 使用Capture Outputs从Session中获取输出流
AVCaptureOutput: 从session中获取输出流.
- AVCaptureMovieFileOutput: 将数据写入文件
- AVCaptureVideoDataOutput: 将视频数据以回调形式输出视频帧
- AVCaptureAudioDataOutput: 将音频数据以回调形式输出音频帧
- AVCaptureStillImageOutput: 捕捉静态图片
addOutput: 添加输出
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.
}
4.1. AVCaptureMovieFileOutput 4.1.1. 写入文件 AVCaptureMovieFileOutput: 使用此类作为输出.可以配置录制最长时间,文件大小以及禁止在磁盘空间不足时继续录制等等.
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#>;
4.1.2. 开始录制 你需要提供一个文件保存地址的URL以及代理去监听状态,这个代理是AVCaptureFileOutputRecordingDelegate, 必须实现
captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:
代理方法URL不能是已经存在的文件,因为无法重写.
AVCaptureMovieFileOutput *aMovieFileOutput = <#Get a movie file output#>;
NSURL *fileURL = <#A file URL that identifies the output location#>;
[aMovieFileOutput startRecordingToOutputFileURL:fileURL recordingDelegate:<#The delegate#>];
4.1.3. 确保文件写入成功 通过代理方法可以检查是否写入成功
需要检查写入失败的原因AVErrorRecordingSuccessfullyFinishedKey
的值,因为可能写入没有错误,但由于磁盘内存不足导致最终写入失败.
- 磁盘内存不足(AVErrorDiskFull)
- 录制设备失去连接(AVErrorDeviceWasDisconnected)
- session意外中断(AVErrorSessionWasInterrupted)
- (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 = https://www.it610.com/article/[[error userInfo] objectForKey:AVErrorRecordingSuccessfullyFinishedKey];
if (value) {
recordedSuccessfully = [value boolValue];
}
}
// Continue as appropriate...
4.1.4. 添加Metadata到文件 可以在任意时间设置输出文件的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 = https://www.it610.com/article/[NSString stringWithFormat:@"%+08.4lf%+09.4lf/"
location.coordinate.latitude, location.coordinate.longitude];
[newMetadataArray addObject:item];
aMovieFileOutput.metadata = https://www.it610.com/article/newMetadataArray;
4.2 AVCaptureVideoDataOutput 4.2.1. 获取视频帧数据 AVCaptureVideoDataOutput对象可以通过代理(
setSampleBufferDelegate:queue:
)获取实时的视频帧数据.同时需要指定一个接受视频帧的串行队列.必须使用串行队列,因为要保证视频帧是按顺序传输给代理方法在
captureOutput:didOutputSampleBuffer:fromConnection:
代理方法中接受视频帧,每个视频帧被存放在CMSampleBufferRef引用对象中, 默认这些buffers以相机最有效的格式发出,我们也可以通过videoSettings
指定输出相机的格式.需要将要指定的格式设置为kCVPixelBufferPixelFormatTypeKey
的value,使用availableVideoCodecTypes
可以查询当前支持的相机格式.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];
4.3. AVCaptureStillImageOutput 如果要使用附带metadata元数据的静止图像,需要使用
AVCaptureStillImageOutput
.- 像素与编码格式
availableImageDataCVPixelFormatTypes, availableImageDataCodecTypes
获取当前支持的格式,以便于查询是否支持你想要设置的格式.AVCaptureStillImageOutput *stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
NSDictionary *outputSettings = @{ AVVideoCodecKey : AVVideoCodecJPEG};
[stillImageOutput setOutputSettings:outputSettings];
- 采集图像
captureStillImageAsynchronouslyFromConnection:completionHandler:
消息以采集一张图像.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. 展示预览图
如果相机的session已经开始工作,我们可以为用户创建一个预览图展示当前相机采集的状况(即就像系统相机拍摄视频时的预览界面)
5.1. Video Preview
- AVCaptureVideoPreviewLayer: 展示相机预览情况,CALayer的子类.
- 使用
AVCaptureVideoDataOutput
可以将像素层呈现给用户
a video preview layer保持对它关联session的强引用,为了确保在图层尝试显示视频时不会被释放
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的子类,因此它具有CALayer的行为,你可以对这一图层执行转换,旋转等操作.
5.1.1. 视频重力感应模式
- AVLayerVideoGravityResizeAspect: 保持分辨率的原始尺寸,即横纵比,未填充的屏幕区域会有黑条
- AVLayerVideoGravityResizeAspectFill: 保持横纵比,铺满屏幕时可以牺牲部分像素
- AVLayerVideoGravityResize: 拉伸视频以充满屏幕,图像会失真
5.2. 显示Audio Levels
注意:一般采集音频不使用AVCaptureSession, 而是用更底层的AudioQueue, AudioUnit, 如需帮助请参考另一篇文章: 音频采集使用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.
}
}
6. 总结
下面将介绍如何采集视频帧并将其转换为UIImage对象.
6.1. 流程
- 创建AVCaptureSession对象管理输入输出流
- 创建AVCaptureDevice对象管理当前硬件支持的所有设备,可以遍历找到我们需要的设备
- 创建AVCaptureDeviceInput对象表示具体的的输入端的硬件设备
- 创建AVCaptureVideoDataOutput对象管理输出视频帧
- 实现AVCaptureVideoDataOutput代理方法以产生视频帧
- 将视频帧从CMSampleBuffer格式转为UIImage格式
- 创建并配置session对象
AVCaptureSession *session = [[AVCaptureSession alloc] init];
session.sessionPreset = AVCaptureSessionPresetMedium;
- 创建并配置设备的输入端
AVCaptureDevice *device =
[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error = nil;
AVCaptureDeviceInput *input =
[AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
// Handle the error appropriately.
}
[session addInput:input];
- 创建并配置输出端
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);
- 实现代理方法
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection {
UIImage *image = imageFromSampleBuffer(sampleBuffer);
// Add your code here that uses the image.
}
- 开始/停止录制
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];
[session stopRunning];
注意: startRunning是一个同步的方法,它可能会花一些时间,因此可能阻塞线程(可以在同步队列中执行避免主线程阻塞).7. 补充
iOS7.0 介绍了高帧率视频采集,我们需要使用AVCaptureDeviceFormat类,该类具有返回支持的图像类型,帧率,缩放比例,是否支持稳定性等等.
- 支持720p, 60帧,同时保证视频稳定性
- 兼容音频的倍速播放
- 编辑支持可变组合中的缩放编辑 (Editing has full support for scaled edits in mutable compositions.)
- 导出可以支持可变帧率的60fps或者将其转为较低帧率如30fps
AVPlayerItem对象支持audioTimePitchAlgorithm属性。此属性允许您指定在使用“时间间距算法设置”常量以各种帧速率播放影片时播放音频的方式。
7.2. 编辑 使用AVMutableComposition对象完成编辑操作
7.3. 导出 使用
AVAssetExportSession
导出60fps的视频文件- AVAssetExportPresetPassthrough: 避免重新编码视频。它将媒体的部分标记为部分60 fps,部分减速或部分加速.
- frameDuration: 使用恒定帧速率导出以获得最大的播放兼容性,可以使用audioTimePitchAlgorithm指定时间.
assetWriterInput.expectsMediaDataInRealTime=YES;
推荐阅读
- live|live to inspire 一个普通上班族的流水账0723
- 2020-04-07vue中Axios的封装和API接口的管理
- 流转
- Guava|Guava RateLimiter与限流算法
- iOS中的Block
- 4月23日海军节,我在青岛等你,一起看强大的中国海军。(如图如视频)
- 视频转换器哪种好用()
- 不懂法,害人终害己
- (小说)月流水几亿的火爆游戏养成记
- 谁还用剩米饭做蛋炒饭啊,现在流行这么吃!