前言
上一篇短视频修正中的AVFoundation结构(一)结构概述与根底咱们介绍了对AVFoundation有了开端的知道,本篇正式开端介绍短视频修正的第一步增加和处理资料。
短视频修正的资料一般来自相册或者摄影,苹果的PhotosKit
供给了管理相册资源的接口,而AVFoundation中的Capture
模块则担任相机摄影的部分。
一、 摄影
1.1 根底摄影录制功用
咱们首先经过完成一个简略的摄影和录制视频(不支撑暂停持续)的功用来知道摄影模块的运用办法,需求运用的中心类如下:
-
AVCaptureSession
:AVCaptureSession是管理摄影活动的开端和中止,并和谐从输入设备到输出数据流的目标,接纳来自摄像头和麦克风等录制设备的输入数据,将数据和谐至适当的输出进行处理,最终生成视频、相片或元数据。 -
AVCaptureDevice
:一个AVCaptureDevice目标表明一个物理录制设备和与该设备相关联的特点(曝光形式、聚集形式等)。录制设备向AVCaptureSession目标供给输入数据,不过AVCaptureDevice
不能直接增加至AVCaptureSession
中,而是需求封装为AVCaptureDeviceInput
目标,来作为AVCaptureSession
的输入源。 -
AVCaptureOutput
:决议了录制会话数据流的输出办法,一般咱们运用其子类来决议输出什么样的数据格式,其间AVCaptureMetadataOutput
用于处理定时元数据的输出,包括了人脸检测或机器码辨认的数据;AVCapturePhotoOutput
用于静态相片、实况相片的输出;AVCaptureVideoDataOutput
用于记载视频并供给对视频帧进行处理的录制输出。AVCaptureMovieFileOutput
承继自AVCaptureFileOutput
:将视频和音频记载到QuickTime电影文件的录制输出。AVCaptureDepthDataOutput
在兼容的摄像机设备上记载场景深度信息的录制输出。 -
AVCaptureConnection
:用于衔接AVCaptureSession中输入和输出的目标,要求音频和视频要对应。 -
AVCaptureVideoPreviewLayer
:CALayer的子类,能够对录制视频数据进行实时预览。
学习AVFoundation相机摄影功用最好的代码实例是苹果官方的demo-AVCam,苹果每年在相机功用方面进行改进的同时也会对该demo坚持更新,这儿不再附加实例代码。不过有些需求留意的点仍是要提一下:
- 相机和麦克风作为用户隐私功用,咱们首先需求在info.plist中装备相应的拜访说明,运用前也要查看设备授权状况
AVCaptureDeviceAVAuthorizationStatus
。 - 增加
AVCaptureInput和AVCaptureOutput
前都要进行canAddxx的判别。 - 由于相机和麦克风设备或许不止一个应用程序在运用,对相机的闪光形式、曝光形式、聚集形式等装备的修正需求放在
[device lockForConfiguration:&error]
和[device unLockForConfiguration:&error]
之间,修正前还需求判别当前设备是否支撑行将切换的装备。。 -
AVCaptureSession
要运行在独自的线程,防止阻塞主线程。 - 由于摄影期间或许会被电话或其他意外状况打断,最好注册
AVCaptureSessionWasInterruptedNotification
以做出相应的处理。 - 相机是一个CPU占用较高的硬件,假如设备承受过大的压力(例如过热),摄影也或许会中止,最好经过KVO监听
KeyPath"videoDeviceInput.device.systemPressureState"
,依据AVCaptureSystemPressureState
调整相机功用。
上图是包括了最根底的相片摄影和视频录制写入URLPath基本功用的流程。可是运用AVCaptureMovieFileOutput
作为输出不能控制录制的暂停和持续。
1.2 控制录制进程
要控制录制写入进程,咱们需求引进AVFoundaton中Assets模块的另一个类AVAssetWriter
与AVCaptureVideoDataOutput
和AVCaptureAudioDataOutput
合作运用。
1.2.1 AVAssetReader
AVAsserReader
用于从AVAsset实例中读取媒体样本,一般AVAsset包括多个轨迹,所以有必要给AVAsserReader装备一个或多个AVAssetReaderOutput
实例,然后经过调用copyNextSampleBuffer
持续拜访音频样本或视频帧。AVAssetReaderOutput
是一个抽象类,一般运用其子类来从不同来源读取数据,其间常用的有AVAssetReaderTrackOutput
用于从资源的单个轨迹读取媒体数据的目标;
AVAssetReaderAudioMixOutput
用于读取一个或多个轨迹混合音频发生的音频样本的目标;AVAssetReaderVideoCompositionOutput
用于从资源的一个或多个轨迹读取组合视频帧的目标。
留意:
- AVAsserReader在开端读取前能够设置读取的规模,开端读取后不能够进行修正,只能次序向后读,不过能够在output中设置
supportsRandomAccess = YES
之后能够重置读取规模。尽管AVAssetReader的创立需求一个AVAsset实例,可是咱们能够经过将多个AVAsset组合成一个AVAsset的子类AVComposition
进行多个文件的读取,AVComposition
会在视频修正中具体介绍。 - AVAssetReader不适合读取实时媒体数据,例如HLS实时数据流。
下面是读取一个视频轨迹数据的示例:
AVAsset *asset = ...;
// 获取视频轨迹
AVAssetTrack *track = [[asset tracksWithMediaType:AVMediaTypeVideo]firstObject];
// 经过asset创立读取器
AVAssetReader *assetReader = [[AVAssetReader alloc] initWithAsset:asset error:nil];
// 装备outsettings
NSDictionary *readerOutputSettings = @{
(id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA)
};
AVAssetReaderOutput *trackOutput = [[AVAssetReaderTrackOutput alloc] initWithTrack:track outputSettings:readerOutputSettings];
[assetReader addOutput:trackOutput];
// 调用开端读取,之后不断获取下一帧直到没有数据返回
[assetReader startReading];
while (assetReader.status == AVAssetReaderStatusReading && !completedOrFailed) {
CMSampleBufferRef sampleBuffer = [trackOutput copyNextSampleBuffer];
if (sampleBuffer) {
CMBlockBufferRef blockBufferRef =
CMSampleBufferGetDataBuffer(sampleBuffer);
size_t length = CMBlockBufferGetDataLength(blockBufferRef);
SInt16 sampleBytes[length];
CMBlockBufferCopyDataBytes(blockBufferRef, 0, length, sampleBytes);
// 你的处理xxx例如重新编码写入
CMSampleBufferInvalidate(sampleBuffer);
CFRelease(sampleBuffer);
}
}
if (assetReader.status == AVAssetReaderStatusCompleted) {
// Completed
completedOrFailed = YES;
}
1.2.2 AVAssetWriter
AVAssetWriter
用于对资源进行编码并将其写入到容器文件中。它由一个或多个AVAssetWriterInput
目标装备,用于附加媒体样本的CMSampleBuffer
。在咱们运用AVAssetWriter的时分,经常会用到AVAssetWriterInputPixelBufferAdaptor
,用于将打包为像素缓冲区的视频样本追加到AVAssetWriter输入的缓冲区,用于把缓冲池中的像素打包追加到视频样本上,举例来说,当咱们要将摄像头获取的原数据(一般是CMSampleBufferRef)写入文件的时分,需求将CMSampleBuffer
转成CVPixelBuffer
,而这个转化是在CVPixelBufferPool
中完结的,AVAssetWriterInputPixelBufferAdaptor
的实例供给了一个CVPixelBufferPool
,可用于分配像素缓冲区来写入输出数据,苹果文档介绍,运用它供给的像素缓冲池进行缓冲区分配一般比运用额定创立的缓冲区更高效。
AVAssetWriter运用示例:
NSURL *outputURL = ...;
// 经过一个空文件的ur来创立写入器
AVAssetWriter *assetWriter = [[AVAssetWriter alloc] initWithURL:outputURL fileType:AVFileTypeQuickTimeMovie error:nil];
// 装备outsettings
NSDictionary *writerOutputSettings = @{
AVVideoCodecKey : AVVideoCodecH264,
AVVideoWidthKey : @1080,
AVVideoHeightKey : @1920,
AVVideoCompressionPropertiesKey : @{
AVVideoMaxKeyFrameIntervalKey : @1,
AVVideoAverageBitRateKey : @10500000,
AVVideoProfileLevelKey : AVVideoProfileLevelH264Main31
}
};
// 运用视频格式文件作为输入
AVAssetWriterInput *writerInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo outputSettings:writerOutputSettings];
[assetWriter addInput:writerInput];
// 开端写入
[assetWriter startWriting];
AVAssetWriter可用于实时操作和离线操作两种状况,关于每个场景有不同的办法将样本buffer增加到写入目标的输入中:
实时:处理实时资源时,例如从AVCaptureVideoDataOutput
写入录制的样本时,AVAssetWriterInput
应该设置expectsMediaDataInRealTime
特点为YES来保证isReadyForMoreMediaData
值被正确设置,不过在写入开端后,无法再修正此特点。
离线: 当从离线资源读取媒体资源时,比如从AVAssetReader读取样本buffer,在附加样本前仍然需求调查写入的readyForMoreMediaData
特点的状况,不过能够运用requestMediaDataWhenReadyOnQueue:usingBlock:
办法控制数据的供给。传到这个办法中的代码会随写入器输入预备附加更多的样本而不断被调用,增加样本时开发者需求检索数据并从资源中找到下一个样本进行增加。
AVAssetReaderOutput和AVAssetWriterInput都能够装备outputSettings,outputSettings正是控制解、编码视频的中心。
AVVideoSettings
- AVVideoCodecKey 编码办法
- AVVideoWidthKey 像素宽
- AVVideoHeightKey 像素高
- AVVideoCompressionPropertiesKey 紧缩设置:
- AVVideoAverageBitRateKey 平均比特率
- AVVideoProfileLevelKey 画质级别
- AVVideoMaxKeyFrameIntervalKey 要害帧最大间隔
AVAudioSettings
- AVFormatIDKey 音频格式
- AVNumberOfChannelsKey 采样通道数
- AVSampleRateKey 采样率
- AVEncoderBitRateKey 编码码率 更多的设置,参见苹果官方文档Video Settings
读取时,outputSetting 传入nil,得到的将是未解码的数据。
AVAssetReader
能够看做解码器,与AVAssetReaderOutput
配套运用,决议以什么样的装备解码成buffer数据;AVAssetWriter
能够看做编码器,与AVAssetWriterInput
配套运用,决议将数据以什么装备编码成视频,CMSampleBuffer
为编码的数据,视频经AVAssetReader
后输出CMSampleBuffer
,经AVAssetWriter
能够重新将CMSampleBuffer
编码成视频。
下面是AVAssetReader和AVAssetWriter成对运用用作视频转码示例:
- (BOOL)startAssetReaderAndWriter {
// 测验开端读取
BOOL success = [self.assetReader startReading];
if (success){
// 测验开端写
success = [self.assetWriter startWriting];
}
if (success) {
// 敞开写入session
self.dispatchGroup = dispatch_group_create();
[self.assetWriter startSessionAtSourceTime:kCMTimeZero];
self.videoFinished = NO;
if (self.assetWriterVideoInput) {
dispatch_group_enter(self.dispatchGroup);
[self.assetWriterVideoInput requestMediaDataWhenReadyOnQueue:self.rwVideoSerializationQueue usingBlock:^{
BOOL completedOrFailed = NO;
// WriterVideoInput预备好元数据时开端读写
while ([self.assetWriterVideoInput isReadyForMoreMediaData] && !completedOrFailed) {
// 获取视频下一帧 加入 output中
CMSampleBufferRef sampleBuffer = [self.assetReaderVideoOutput copyNextSampleBuffer];
if (sampleBuffer != NULL) {
BOOL success = [self.assetWriterVideoInput appendSampleBuffer:sampleBuffer];
CFRelease(sampleBuffer);
sampleBuffer = NULL;
completedOrFailed = !success;
} else {
completedOrFailed = YES;
}
}
if (completedOrFailed) {
// 符号写入结束
BOOL oldFinished = self.videoFinished;
self.videoFinished = YES;
if (oldFinished == NO) {
[self.assetWriterVideoInput markAsFinished];
}
dispatch_group_leave(self.dispatchGroup);
}
}];
}
// 监听读取写入完结状况
dispatch_group_notify(self.dispatchGroup, self.mainSerializationQueue, ^{
BOOL finalSuccess = YES;
if ([self.assetReader status] == AVAssetReaderStatusFailed) {
finalSuccess = NO;
}
// 完结写入
if (finalSuccess) {
finalSuccess = [self.assetWriter finishWriting];
}
// 处理写入完结
[self readingAndWritingDidFinishSuccessfully:finalSuccess];
});
}
return success;
}
可是两者并不要求必定成对运用,AVAssetWriter要处理的数据是前面介绍的CMSampleBuffer
,CMSampleBuffer
能够从相机摄影视频时获取实时流,也能够经过图片数据转化得来(图片转视频)。
下面是经过AVAssetWiter将AVCaptureVideoDataOutput的署理办法中的CMSampleBuffer写入文件的中心代码。
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
[_writer startWriting];
[_writer startSessionAtSourceTime:startTime];
if(captureOutput == self.videoDataOutput) {
//视频输入是否预备承受更多的媒体数据
if (_videoInput.readyForMoreMediaData == YES) {
//拼接视频数据
[_videoInput appendSampleBuffer:sampleBuffer];
}
} else {
//音频输入是否预备承受更多的媒体数据
if (_audioInput.readyForMoreMediaData) {
//拼接音频数据
[_audioInput appendSampleBuffer:sampleBuffer];
}
}
}
至此咱们能够完成视频录制的暂停与持续,基本上现已介绍了大多数app相机模块完成的首要功用架构,如下:
1.3 相机的其他功用
苹果每年都会对设备的相机功用进行优化或扩展,除了简略的摄影和录像,咱们还能够运用Capture模块得到更多数据。
1.3.1 人脸、身体和机器可读码
AVFoundation中的人脸检测AVMetadataFaceObject
功用在iOS6.0就开端支撑,iOS13.0增加了对身体的检测,包括人体AVMetadataHumanBodyObject
、猫身体AVMetadataCatBodyObject
、狗身体AVMetadataDogBodyObject
。他们都承继自AVMetadataObject
,除了各自增加了比如faceID
、bodyID
这样的特点外,他们的特点首要来自AVMetadataObject
,其间bounds是检测到的目标的概括,当然,人脸检测弥补了沿着z轴旋转的人脸视点rollAngle
和是沿着y轴旋转的人脸视点yawAngle
。
假如咱们想要检测人脸的要害点数据,能够运用
Vision
结构中的VNDetectFaceRectanglesRequest
和ARKit
结构中的ARFaceTrackingConfiguration
,都能够吊起相机获取人脸要害点的数据。
iOS7.0增加了机器可读码(AVMetadataMachineReadableCodeObject
)的辨认功用,返回了包括表明机器码的字符意义的stringValue数据,在WWDC2021What’s new in camera capture中提到了辅助可读码辨认功用一个重要的特点minimumFocusDistance
,是指镜头能够合焦的最近摄影间隔,一切摄像头都会包括该参数,仅仅苹果在iOS15.0才揭露该特点,咱们能够运用该特点调整相机的扩大倍数,以解决低于最近辨认间隔后无法辨认的问题,具体源码可参考官网的demoAVCam条码:检测条码和人脸。
这儿把这些并不相关的检测放在一块介绍是由于从API的视点,他们都以AVCaptureMetadataOutput
作为输出,AVCaptureMetadataOutput
供给了一个metadataObjectTypes
数组特点,咱们能够传入一个或多个要检测的类型,完成AVCaptureMetadataOutputObjectsDelegate
协议的- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection;
办法,从metadataObjects
中获取想要的数据。
1.3.2 Live photo
live photo是iOS10.0推出的功用,体系相机app中挑选“相片”项右上角的live标志控制是否敞开摄影live photo功用。敞开live photo功用会摄影下用户点击摄影按钮前后各0-1.5秒(官网说的是1.5秒)的视频,取中间的一帧作为静态图片和一个3秒内的视频一起保存下来,在相册中长按相片能够播映其间的视频。
运用live photo摄影API,需求运用AVCapturePhotoOutput
的isLivePhotoCaptureSupported
特点判别是否支撑该功用,将photoOutput的livePhotoCaptureEnabled
特点设为YES,然后创立一个视频保存的路径为photoSettings的livePhotoMovieFileURL
特点赋值,其他跟静态相片摄影相同。
留意 live photo只能运行在AVCaptureSessionPresetPhoto
预设形式下,且不能和AVCaptureMovieFileOutput
共存。
live photo的摄影有自己的两个回调办法:
// 现已完结整段视频的录制,还没写入沙盒
- (void) captureOutput:(AVCapturePhotoOutput *)captureOutput didFinishRecordingLivePhotoMovieForEventualFileAtURL:(NSURL *)outputFileURL resolvedSettings:(AVCaptureResolvedPhotoSetting s *)resolvedSettings;
// 视频现已写入沙盒
- (void) captureOutput:(AVCapturePhotoOutput *)captureOutput didFinishProcessingLivePhotoToMovieFileAtURL:(NSURL *)outputFileURL duration:(CMTime)duration photoDisplayTime:(CMTime)photoDisplayTime resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings error:(NSError *)error;
留意:保存Live Photo有必要和图片运用同一个PHAssetCreationRequest
目标,才能将两者关联起来,要展示实况相片,需求运用PHLivePhotoView
,它默认增加了长按播映实况相片的手势。
1.3.3 景深
苹果在相机方面的功用和Capture模块的API每年都会有许多的更新,可是像深度数据这样,推出以来从图片修正到视频修正从软件到硬件,不断优化不断拓宽应用领域和深度得并不多,景深数据值得咱们持续关注。
景深是指摄像头摄影时获取到图片中的物体在现实世界的远近数据,苹果在iOS11.0在具有双摄像头的设备中推出了带有景深数据的人像形式,最初后置摄像头的景深数据是运用跳眼法经过两个摄像头的数据依据类似三角形原理核算得来,前置摄像头经过红外线勘探,后来苹果引进了LiDAR模组,经过光线勘探测距能够得到准确的景深数据,它对AR模块也有很大帮助。
用来描绘景深数据的是AVDepthData
类,其包括的中心特点如下:
depthDataType: 景深数据的数据类型,kCVPixelFormatType_DisparityX表明的是视差数据,kCVPixelFormatType_DepthX表明的是深度数据,能够转化。
depthDataMap: 景深的数据缓冲区,能够转成UIImage
isDepthDataFiltered: 是否发动插值
depthDataAccuracy: 景深数据的准确度
留意:经过UIImge创立的imag不会包括景深数据,需求运用photosKit结构读取。
在AVFoundation的Capture模块,景深数据录制分为静态景深录制和实时景深录制。
- 静态景深录制:静态景深录制只需求装备
AVCapturePhotoOutput
和AVCapturePhotoSettings
的isDepthDataDeliveryEnabled
为YES,在署理办法中即可获取photo.depthData
数据,咱们能够将景深数据中的depthDataMap转为图片存相册,也能够将数据写入原图,保存为一张带有景深数据的人像图。 - 实时景深:望文生义,要有数据流的支撑,需求同时运用
AVCaptureVideoDataOutput
和景深输出AVCaptureDepthDataOutput
,可是景深输出的帧率和分辨率都远低于视频数据输出(功用考虑),为解决这一问题,苹果专门引进了AVCaptureDataOutputSynchronizer
来和谐各个流的输出。
self.dataOutputSynchronizer = [[AVCaptureDataOutputSynchronizer alloc] initWithDataOutputs:@[self.videoOutput, self.depthOutput]];
[self.dataOutputSynchronizer setDelegate:self queue: self.cameraProcessingQueue];
然后咱们就能够在署理办法中得到AVCaptureSynchronizedDataCollection
实例
- (void)dataOutputSynchronizer:(AVCaptureDataOutputSynchronizer *)synchronizer didOutputSynchronizedDataCollection:(AVCaptureSynchronizedDataCollection *)synchronizedDataCollection{
AVCaptureSynchronizedDepthData *depthData = (AVCaptureSynchronizedDepthData *)[synchronizedDataCollection synchronizedDataForCaptureOutput:self.depthOutput];
}
有了深度数据,咱们能够运用Core Image
供给的各种遮罩、滤镜、更改焦点等作用,让相片显现出不同的作用的同时仍然坚持层次感,具体的应用能够参考Video Depth Maps Tutorial for iOS。
在增加了实时景深输出后,相机的架构变成了这样:
AVFoundation的Capture模块为咱们供给了自定义相机的摄影、录像、实况相片、景深人像形式、人脸身体检测、机器码辨认等等,此外比如多相机摄影、图像切割(头发、牙齿、眼镜、皮肤)。。。不再深化介绍。
二、 相册
相册是视频剪辑资料的另一个来源,苹果的体系相册能够保存图片、视频、实况相片、gif动图等,剪映、快影和wink等视频剪辑app关于从相册中挑选的资料都统一转为了一段视频,下面别离介绍转为视频的办法。
2.1 静态图片转视频
静态图片转视频的功用所运用的中心类 AVAssetWriter
前面现已学习过了,和视频录制写入文件的差别在于数据的来源变成了相册中的图片,缺点是运用 AVAssetWriter
写入文件进程中不支撑预览,这个问题咱们会在视频修正部分解决。
2.2 实况相片转视频
前面现已介绍了怎么运用自定义相机摄影和保存实况相片,而大多app从相册中直接获取去运用交给UIImage的往往是一张静态图片,要转为视频进行修正,咱们需求运用PhotosKit供给的API。
// 创立实况相片请求装备
PHLivePhotoRequestOptions* options = [[PHLivePhotoRequestOptions alloc] init];
options.deliveryMode = PHImageRequestOptionsDeliveryModeFastFormat;
[[PHImageManager defaultManager] requestLivePhotoForAsset:phAsset targetSize:[UIScreen mainScreen].bounds.size contentMode:PHImageContentModeDefault options:options resultHandler:^(PHLivePhoto * _Nullable livePhoto, NSDictionary * _Nullable info) {
NSArray* assetResources = [PHAssetResource assetResourcesForLivePhoto:livePhoto];
PHAssetResource* videoResource = nil;
// 判别是否含有视频资源
for(PHAssetResource* resource in assetResources){
if (resource.type == PHAssetResourceTypePairedVideo) {
videoResource = resource;
break;
}
if(videoResource){
// 将视频资源写入指定路径
[[PHAssetResourceManager defaultManager] writeDataForAssetResource:videoResource toFile:fileUrl options:nil completionHandler:^(NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
// 去运用视频资源
[self handleVideoWithPath:self.outPath];
});
}];
值得一提的是,PHAsset
还有一个私有办法fileURLForVideoComplementFile
能够直接获取实况相片中视频文件的URL地址,不过私有API要防止在线上运用。
2.3 gif动图转视频
gif由多张图片组合,利用视觉暂留原理构成动画作用,要把gif转为视频的要害是获取gif中保存的单帧和每帧停留的时刻,ImageIO.framework供给了相关的接口。
// 从相册读取gif
PHImageManager *manager = [PHImageManager defaultManager];
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
[manager requestImageDataAndOrientationForAsset:asset options:options resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, CGImagePropertyOrientation orientation, NSDictionary * _Nullable info) {
CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)imageData, NULL);
CFRetain(imageSource);
// 获取gif包括的帧数
NSUInteger numberOfFrames = CGImageSourceGetCount(imageSource);
NSDictionary *imageProperties = CFBridgingRelease(CGImageSourceCopyProperties(imageSource, NULL));
NSDictionary *gifProperties = [imageProperties objectForKey:(NSString *)kCGImagePropertyGIFDictionary];
NSTimeInterval totalDuratoin = 0;//开辟空间
NSTimeInterval *frameDurations = (NSTimeInterval *)malloc(numberOfFrames * sizeof(NSTimeInterval));
//读取循环次数
NSUInteger loopCount = [gifProperties[(NSString *)kCGImagePropertyGIFLoopCount] unsignedIntegerValue];
//创立一切图片的数值
NSMutableArray *images = [NSMutableArray arrayWithCapacity:numberOfFrames];
for (NSUInteger i = 0; i < numberOfFrames; ++i) {
//读取每张的显现时刻,增加到数组中,并核算总时刻
CGImageRef image = CGImageSourceCreateImageAtIndex(imageSource, i, NULL);
[images addObject:[UIImage imageWithCGImage:image scale:1.0 orientation:UIImageOrientationUp]];
CFRelease(image);
NSTimeInterval frameDuration = [self getGifFrameDelayImageSourceRef:imageSource index:i];
frameDurations[i] = frameDuration;
totalDuratoin += frameDuration;
}
CFRelease(imageSource);
}];
单帧的停留时刻,保存在kCGImagePropertyGIFDictionary
字典中,仅仅其间包括了两个看起来很类似的key:kCGImagePropertyGIFUnclampedDelayTime
:数值能够为0,kCGImagePropertyGIFDelayTime
:值不会小于100毫秒。许多gif图片为了得到最快的显现速度会把duration设置为0, 浏览器在显现他们的时分为了功用考虑就会给他们减速(clamp),一般咱们会取先获取 kCGImagePropertyGIFUnclampedDelayTime
的值,假如没有就取 kCGImagePropertyGIFDelayTime
的值, 假如这个值太小就设置为0.1,由于gif的规范中对这一数值有约束,不能太小。
- (NSTimeInterval)getGifFrameDelayImageSourceRef:(CGImageSourceRef)imageSource index:(NSUInteger)index
{
NSTimeInterval frameDuration = 0;
CFDictionaryRef theImageProperties;
if ((theImageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, index, NULL))) {
CFDictionaryRef gifProperties;
if (CFDictionaryGetValueIfPresent(theImageProperties, kCGImagePropertyGIFDictionary, (const void **)&gifProperties)) {
const void *frameDurationValue;
// 先获取kCGImagePropertyGIFUnclampedDelayTime的值
if (CFDictionaryGetValueIfPresent(gifProperties, kCGImagePropertyGIFUnclampedDelayTime, &frameDurationValue)) {
frameDuration = [(__bridge NSNumber *)frameDurationValue doubleValue];
// 假如值不可用,获取kCGImagePropertyGIFDelayTime的值
if (frameDuration <= 0) {
if (CFDictionaryGetValueIfPresent(gifProperties, kCGImagePropertyGIFDelayTime, &frameDurationValue)) {
frameDuration = [(__bridge NSNumber *)frameDurationValue doubleValue];
}
}
}
}
CFRelease(theImageProperties);
}
// 假如值太小,则设置为0.1
if (frameDuration < 0.02 - FLT_EPSILON) {
frameDuration = 0.1;
}
return frameDuration;
}
在获取了一切的图片和图片停留时刻后,咱们就能够运用图片转视频的办法进行处理了。这部分内容咱们在修正部分对视频中增加gif表情包也会用到。
至此,无论是从相机摄影仍是相册获取,咱们都能结合AVFoundation结构得到咱们想要的视频文件来作为视频修正的主资料,能够正式开端咱们的修正了。
总结
本篇从介绍了录制和相册两个途径进行短视频修正需求的资料的增加与处理,为控制录制的进程,穿插了AVAssetReader和AVAssetWriter的介绍,在以后的视频修正中咱们还会深化讲解相关的应用。下一篇咱们正式开端介绍运用AVFoundation进行短视频修正。
参考链接
AVFoundation
构建一个相机app-苹果官方
wwdc2021-What’s new in camera capture