前言
上一篇短视频批改中的AVFoundation结构(二)资料增加与处理咱们完结了短视频资料的导入,本篇正式开始运用AVFoundation进行短视频批改,从实践的批改功用下手介绍结构中Editing
模块具有的各项能力。
一、 视频拼接 + bgm
咱们首要从最简略的功用下手:完结两(多)段视频的拼接,并为其增加一段布景音乐。
1.1 AVMutableComposition
上一篇咱们说到,AVAssetWriter
在进行写入时不支撑预览(尽管经过AVSampleBufferDisplayLayer
能够显现CMSambuffer
,但这无疑增加了很多的工作量也违反了咱们从宏观视点看待视频批改的初心),而视频播映需求的AVPlayerItem
需求一个AVAsset
实例来初始化,咱们期望有一个类,它承继自AVAsset
,并且能够对其间的AVAssetTrack
进行任意的批改,既能够处理批改,也能够用来在耗时的导出之前进行预览,AVFoundation为咱们供给了这样一个类AVComposition
,其可变子类AVMutableComposition
满足了这些要求。
AVFoundation的Editing模块的很多类都有这样一个特色:不可变的父类具有很多只读的特点,其可变的子类承继父类的特点将部分特点变为了可读可写,这儿留意纷歧定是全部都可读可写。之后咱们直接从”Mutable”可变的类下手进行视频批改,不再一一兼顾不可变类。
在Assets模块的中,咱们说到AVAsset
包括一个或多个AVAssetTrack
,相同作为子类的AVComposition
也包括一个或多个AVCompositionTrack
,而咱们处理的目标正是他们的可变子类AVMutableComposition
和AVMutableCompositionTrack
。
AVMutableComposition
中供给了两个类办法用来创立一个空资源。
+ (instancetype)composition;
+ (instancetype)compositionWithURLAssetInitializationOptions:(nullable NSDictionary<NSString *, id> *)URLAssetInitializationOptions NS_AVAILABLE(10_11, 9_0);
从composition中增加和移除AVMutableCompositionTrack
的办法:
//向 composition 中增加一个指定媒体资源类型的空的AVMutableCompositionTrack
- (AVMutableCompositionTrack *)addMutableTrackWithMediaType:(NSString *)mediaType preferredTrackID:(CMPersistentTrackID)preferredTrackID;
//从 composition 中删除一个指定的 track
- (void)removeTrack:(AVCompositionTrack *)track;
批改AVCompositionTrack
的办法:
//将指守时刻段的 asset 中的一切的 tracks 增加到 composition 中 startTime 处
- (BOOL)insertTimeRange:(CMTimeRange)timeRange ofAsset:(AVAsset *)asset atTime:(CMTime)startTime error:(NSError * _Nullable * _Nullable)outError;
//向 composition 中的一切 tracks 增加空的时刻规模
- (void)insertEmptyTimeRange:(CMTimeRange)timeRange;
//从 composition 的一切 tracks 中删除一段时刻,该操作不会删除 track ,而是会删除与该时刻段相交的 track segment
- (void)removeTimeRange:(CMTimeRange)timeRange
//改动 composition 中的一切的 tracks 的指守时刻规模的时长,该操作会改动 asset 的播映速度
- (void)scaleTimeRange:(CMTimeRange)timeRange toDuration:(CMTime)duration;
从上面供给的办法能够看出,咱们能够对AVMutableComposition
增加空的AVMutableCompositionTrack
轨迹,然后将准备好的AVAsset
的AVAssetTrack
刺进到AVMutableCompositionTrack
轨迹中,视频的拼接便是这样一个简略的操作。
运用AVMutableComposition
咱们现已能够完结视频的拼接了,然后增加一段时长与总时刻相等的AVCompositionAudioTrack
就有了布景音乐,假如要调整多个音频轨迹混合后播映时各个轨迹的音量,咱们还需求另一个类AVAudioMix
,AVPlayerItem
也含有这一特点,在播映时运用混合音频参数。
1.2 AVMutableAudioMix
AVMutableAudioMix
包括一组 AVAudioMixInputParameters
,每个 AVAudioMixInputParameters
对应一个它操控的音频 AVCompositionTrack
。
AVAudioMixInputParameters
包括一个 MTAudioProcessingTap
,用来实时处理音频,一个AVAudioTimePitchAlgorithm
,能够运用它来设置腔调,这两个相对要略微复杂一点,咱们暂时不重视。现在咱们想别离设置原视频和布景音乐轨迹播映时的音量巨细,能够运用AVAudioMixInputParameters
供给的如如下办法:
// 从某个时刻点开始,将音量变为volume
- (void)setVolume:(float)volume atTime:(CMTime)time
// 在timeRange时刻内将音量线性改变,从startVolume逐突变为endVolume
- (void)setVolumeRampFromStartVolume:(float)startVolume toEndVolume:(float)endVolume timeRange:(CMTimeRange)timeRange;
即经过创立每个音频轨迹的AVAudioMixInputParameters
,装备音量,将多个AVAudioMixInputParameters
加入数组,作为AVAudioMix
的inputParameters
特点。
AVAudioMix 并不直接改动音频播映的办法,其仅仅存储了音频播映的办法,
AVVideoComposition同理
。
下面是拼接视频并增加布景音乐的代码示例:
// 1. 创立AVMutableComposition、AVMutableudioMix、和AVAudioMixInputParameters数组
AVMutableComposition *composition = [AVMutableComposition composition];
AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
NSMutableArray *audioMixInputParameters = [NSMutableArray array];
// 2. 刺进空的音视频轨迹
AVMutableCompositionTrack* videoCompositionTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack* audioCompositionTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
// 记录已增加的视频总时刻
CMTime startTime = kCMTimeZero;
CMTime duration = kCMTimeZero;
// 拼接视频
for (int i = 0; i < assetArray.count; i++) {
AVAsset* asset = assetArray[i];
AVAssetTrack* videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] firstObject];
AVAssetTrack* audioTrack = [[asset tracksWithMediaType:AVMediaTypeAudio] firstObject];
// 3. 轨迹中刺进对应的音视频
[videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration) ofTrack:videoTrack atTime:startTime error:nil];
[audioCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration) ofTrack:audioTrack atTime:startTime error:nil];
// 4. 装备原视频的AVMutableAudioMixInputParameters
AVMutableAudioMixInputParameters *audioTrackParameters = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:audioTrack];
// 设置原视频声响音量
[audioTrackParameters setVolume:0.2 atTime:startTime];
[audioMixInputParameters addObject:audioTrackParameters];
// 设置原视频声响音量
[audioTrackParameters setVolume:0.2 atTime:startTime];
[audioMixInputParameters addObject:audioTrackParameters];
// 拼接时刻
startTime = CMTimeAdd(startTime, asset.duration);
};
// 5. 增加BGM音频轨迹
AVAsset *bgmAsset = ...;
AVMutableCompositionTrack *bgmAudioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
AVAssetTrack *bgmAssetAudioTrack = [[bgmAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
[bgmAudioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, duration) ofTrack:bgmAssetAudioTrack atTime:kCMTimeZero error:nil];
AVMutableAudioMixInputParameters *bgAudioTrackParameters = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:bgmAudioTrack];
// 6. 设置布景音乐音量
[bgAudioTrackParameters setVolume:0.8 atTime:kCMTimeZero];
[audioMixArray addObject:bgAudioTrackParameters];
// 7. 设置inputParameters
audioMix.inputParameters = audioMixArray;
// 运用AVPlayerViewController预览
AVPlayerViewController *playerViewController = [[AVPlayerViewController alloc]init];
// 运用AVMutableComposition创立AVPlayerItem
AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithAsset:composition];
// 8. 将音频混合参数传递给AVPlayerItem
playerItem.audioMix = audioMix;
playerViewController.player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
playerViewController.view.frame = self.view.frame;
[playerViewController.player play];
[self presentViewController:playerViewController animated:YES completion:nil];
最简略的视频拼接和增加布景音乐的功用就完结了,可是看起来视频的过渡比较僵硬,咱们期望能像操控音轨音量一样,操控每一段视频轨迹的组成办法,乃至增加视频过渡作用,这时候咱们需求AVVideoComposition
,趁便咱们完结一个”叠化”的视频转场的作用。
二、 视频转场
2.1 AVMutableVideoComposition
AVVideoComposition
从iOS4.0开始支撑,从命名看起来 AVVideoComposition
好像跟 AVComposition
好像是有什么血缘联系,事实并非如此,AVVideoComposition
承继自 NSObject
,咱们能够把它看做与相同承继自 NSObject
的 AVAudioMix
平级,一个担任音频轨迹的组成操控,一个担任视频轨迹的组成操控。
创立AVMutableVideoComposition
的办法如下:
// 回来特点为空的实例
+ (AVMutableVideoComposition *)videoComposition;
// 回来包括了适合的指令的实例
+ (AVMutableVideoComposition *)videoCompositionWithPropertiesOfAsset:(AVAsset *)asset API_AVAILABLE(macos(10.9), ios(6.0), tvos(9.0)) API_UNAVAILABLE(watchos);
// iOS13.0新增,为了在创立时直接设置好布景色
+ (AVMutableVideoComposition *)videoCompositionWithPropertiesOfAsset:(AVAsset *)asset prototypeInstruction:(AVVideoCompositionInstruction *)prototypeInstruction API_AVAILABLE(macos(10.15), ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
AVMutableVideoComposition部分特点列表:
//视频每一帧的改写时刻
@property (nonatomic) CMTime frameDuration;
//视频显现时的巨细规模
@property (nonatomic) CGSize renderSize;
//视频显现规模巨细的缩放份额
@property (nonatomic) float renderScale;
//描绘视频调集中详细视频播映办法信息的调集。
@property (nonatomic, copy) NSArray<id <AVVideoCompositionInstruction>> *instructions;
//这三个特点设置了烘托帧时的视频原色、矩阵、传递函数
@property (nonatomic, nullable) NSString *colorPrimaries;
@property (nonatomic, nullable) NSString *colorYCbCrMatrix;
@property (nonatomic, nullable) NSString *colorTransferFunction;
// iOS 15新增,告知视频组成目标组成的数据样本相关的轨迹ID
@property (nonatomic, copy) NSArray<NSNumber *> *sourceSampleDataTrackIDs;
从特点列表能够看出,从一个 AVMutableVideoComposition
输出视频时,咱们还能够指定输出的尺度renderSize
(裁剪功用)、缩放份额renderScale
、帧率frameDuration
等。一起AVPlayerItem
也含有videoComposition
特点,在播映时依照组成指令显现视频内容。
AVVideoComposition
和AVAudioMix
都没有和AVComposition
强相关,这样做的优点是咱们在预览、导出、获取视频缩略图功用上能够更灵敏的运用。
2.2 AVMutableVideoCompositionInstruction
videoComposition
最重要的一个特点是 instructions
,数组包括一个或多个AVMutableVideoCompositionInstruction
,它具有backgroundColor
特点用来批改视频的布景色,此外最要害的一个特点是timeRange
,它描绘了一段组合办法出现的时刻规模。
AVMutableVideoCompositionInstruction特点和办法列表:
// 指令适用的时刻规模。
@property (nonatomic) CMTimeRange timeRange;
// 视频组成的布景颜色。
@property (nonatomic, retain, nullable) __attribute__((NSObject)) CGColorRef backgroundColor;
// 指定怎么从源轨迹分层和编写视频帧的阐明。
@property (nonatomic, copy) NSArray<AVVideoCompositionLayerInstruction *> *layerInstructions;
// 指示指令是否需求后期处理。
@property (nonatomic) BOOL enablePostProcessing;
// 组成器组成视频帧所遵从的指令需求的轨迹id。
@property (nonatomic) NSArray<NSValue *> *requiredSourceTrackIDs;
// passthrough轨迹id
@property (nonatomic, readonly) CMPersistentTrackID passthroughTrackID;
// iOS15新增,用于视频元数据组成。
@property (nonatomic) NSArray<NSNumber *> *requiredSourceSampleDataTrackIDs;
这儿留意一下
AVMutableVideoCompositionLayerInstruction
具有一个passthroughTrackID
特点,尽管在可变类中也仍是个只读特点。
2.3 AVMutableVideoCompositionLayerInstruction
AVMutableVideoCompositionInstruction
具有一个layerInstructions
特点,数组中是AVMutableVideoCompositionLayerInstruction
类型的实例,经过AVMutableCompositionTrack
创立`,或与之trackID关联,描绘了关于该轨迹的组成办法,供给了用于批改特定的时刻点或许一个时刻规模内线性改变的transform、crop、opacity的办法,如下,可做的工作并不多。
// 突变仿射改换
- (void)setTransformRampFromStartTransform:(CGAffineTransform)startTransform toEndTransform:(CGAffineTransform)endTransform timeRange:(CMTimeRange)timeRange;
// 仿射改换,还能够用来批改视频方向
- (void)setTransform:(CGAffineTransform)transform atTime:(CMTime)time;
// 透明度突变
- (void)setOpacityRampFromStartOpacity:(float)startOpacity toEndOpacity:(float)endOpacity timeRange:(CMTimeRange)timeRange;
// 设置透明度
- (void)setOpacity:(float)opacity atTime:(CMTime)time;
// 裁剪区域突变
- (void)setCropRectangleRampFromStartCropRectangle:(CGRect)startCropRectangle toEndCropRectangle:(CGRect)endCropRectangle timeRange:(CMTimeRange)timeRange;
// 设置裁剪区域
- (void)setCropRectangle:(CGRect)cropRectangle atTime:(CMTime)time;
综上,AVMutableVideoComposition
用于视频组成,对视频组合做一个总体描绘,AVMutableVideoCompositionInstruction
用于规定其包括的AVMutableVideoCompositionLayerInstruction
调集所操控的时刻规模,而AVMutableVideoCompositionLayerInstruction
是描绘详细轨迹混合时的出现办法。那么这儿有一个问题:为什么要这样别离规划三个类?咱们先往下看。
咱们先从创立AVMutableVideoComposition
的榜首种办法+ (AVMutableVideoComposition *)videoComposition
说起,他会回来一个特点根本都是空的实例,咱们从零去创立,这样能够更好了解。
要将两段视频进行混合,首要需求两段视频在时刻线上含有堆叠的区域,之后别离创立各自在混合区域中的出现或消失的指令。苹果官方文档介绍,每一个视频轨迹都会装备一个单独的解码器,不建议增加过多的轨迹,咱们一般运用A/B轨迹法——即创立两段视频轨迹,将avassetTrack交替刺进A/B轨迹中,如下图,咱们需求对段视频增加相应的instruction,不包括堆叠区域的称为pass through,只需求指守时刻规模即可,堆叠区域称为transition,需求一个描绘前一个视频躲藏办法的layerInstruction指令和描绘后一个视频出现办法的layerInstruction指令。
每一个instruction都要设置好操控的时刻规模,一旦出现指令时刻规模没有拼接完好或出现穿插等情况就会发生过错,例如崩溃或许无法正常播映,在组成前咱们能够调用AVVideoComposition的- (BOOL)isValidForAsset: timeRange: validationDelegate:
以查看指令描绘的时刻规模是否可用,其间delegate
要求恪守的AVVideoCompositionValidationHandling
的协议供给的办法给了咱们更多的过错信息描绘,假如不需求也能够传nil
。
transition
部分的AVMutableVideoCompositionInstruction
创立示例(叠化作用):
CMTimeRange atTime_end = kCMTimeRangeZero;
__block CMTimeRange atTime_begin = kCMTimeRangeZero;
NSMutableArray* layerInstructions = [NSMutableArray array];
// 视频组成指令
AVMutableVideoCompositionInstruction *videoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
videoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, totalDuration);
for (int i = 0; i < compositionVideoTracks.count; i++) {
AVMutableCompositionTrack *compositionTrack = compositionVideoTracks[i];
AVAsset *asset = assets[i];
// layerInstruction
AVMutableVideoCompositionLayerInstruction *layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:compositionTrack];
if (compositionVideoTracks.count > 1) {
// 叠化掉过
atTime_begin = atTime_end;
atTime_end = CMTimeRangeMake(CMTimeAdd(CMTimeSubtract(atTime_end.start, transTime), asset.duration), transTime);
CMTimeRangeShow(atTime_begin);
CMTimeRangeShow(atTime_end);
if (i == 0) {
[layerInstruction setOpacityRampFromStartOpacity:1.0 toEndOpacity:0.0 timeRange:atTime_end];
} else if (i == compositionVideoTracks.count - 1) {
[layerInstruction setOpacityRampFromStartOpacity:0.0 toEndOpacity:1.0 timeRange:atTime_begin];
} else{
[layerInstruction setOpacityRampFromStartOpacity:1.0 toEndOpacity:0.0 timeRange:atTime_end];
[layerInstruction setOpacityRampFromStartOpacity:0.0 toEndOpacity:1.0 timeRange:atTime_begin];
}
}
[layerInstructions addObject:layerInstruction];
}
videoCompositionInstruction.layerInstructions = layerInstructions;
转场作用:
2.4 构建视频组成器的其他办法
当然,像上述这样逐一地创立”pass through instruction”和”transition instruction”必定不是咱们想要的,上面介绍的办法是运用+ (AVMutableVideoComposition *)videoComposition;
办法创立AVMutableVideoComposition
,回来的是一个各个特点都为空的目标,所以需求咱们逐一增加指令。
苹果还给咱们供给了一个+ (AVMutableVideoComposition *)videoCompositionWithPropertiesOfAsset:(AVAsset *)asset
办法,传入增加好视频轨迹的AVMutableComposition
,办法回来的AVMutableVideoComposition
实例包括了设置好的特点值和适用于依据其时刻和几许特点以及其轨迹的特点出现指定资源的视频轨迹的指令,简略的说便是instructons和其layerInstructions都现已为咱们创立好了,咱们能够直接从中取出transition时刻段中的fromLayerInstruction和toLayerInstruction,一个消失一个显现,就能够完结视频转场作用了,这两种创立videoComposition的办法称为内置组成器(Bultin-in Compositor),尽管有着发挥空间不足的问题,可是优点是苹果关于这种现已经过封装的接口能够主动针对新的技术或设备的适配,例如WWDC2021说到的对HDR视频文件的适配,内置组成器会将含有HDR视频组成输出一个HDR视频。
苹果在iOS9.0开始又供给了能够对视频运用CIFilter增加相似含糊、颜色等滤镜作用的办法来创立AVMutableVideoComposition
:
+ (AVMutableVideoComposition *)videoCompositionWithAsset:(AVAsset *)asset applyingCIFiltersWithHandler:(void (^)(AVAsynchronousCIImageFilteringRequest *request))applier
,不过这种办法只需运用体系的CIFilter才干支撑HDR的组成导出,不然需求批改参数。
CIFilter *filter = [CIFilter filterWithName:@"CIGaussianBlur"];
AVMutableVideoComposition *videocomposition = [AVMutableVideoComposition videoCompositionWithAsset:asset applyingCIFiltersWithHandler:^(AVAsynchronousCIImageFilteringRequest * _Nonnull request) {
// 获取源ciimage
CIImage *source = request.sourceImage.imageByClampingToExtent;
// 增加滤镜
[filter setValue:source forKey:kCIInputImageKey];
Float64 seconds = CMTimeGetSeconds(request.compositionTime);
CIImage *output = [filter.outputImage imageByCroppingToRect:request.sourceImage.extent];
filter setValue:seconds * 10.0 forKey:kCIInputRadiusKey];
// 提交输出
[request finishWithImage:output context:nil];
}];
留意: 运用该办法创立的AVMutableVideoComposition
实例,其instructions
数组中的数据类型就成了私有类AVCoreImageFilterVideoCompositionInstruction
,官方文档没有任何资料,咱们无法创立或许批改它和它的layerInstructions
,不过咱们能够运用CIAffineTransform
的CIilter
直接调整sourceImage
的方向,批改方向问题。
在iOS13.0又供给了+ (AVMutableVideoComposition *)videoCompositionWithPropertiesOfAsset:(AVAsset *)asset prototypeInstruction:(AVVideoCompositionInstruction *)prototypeInstruction
办法,咱们能够提早创立一个原型指令,将布景色进行设置,之后调用该办法创立AVMutableVideoComposition
就能够得到一个各段instruction布景色都设置好的AVMutableVideoComposition
实例。
可是要完结彻底的自定义转场或许自定义组成,能够做到对每一帧做处理,这些办法仍是不行,苹果在iOS7.0在AVMutableVideoComposition
类中新增了customVideoCompositorClass
特点,它要求一个恪守了AVVideoCompositing
协议的类,留意,这儿需求传的是一个类。
@property (nonatomic, retain, nullable) Class<AVVideoCompositing> customVideoCompositorClass;
AVVideoCompositing协议定义如下:
@protocol AVVideoCompositing<NSObject>
// 源PixelBuffer的特点
@property (nonatomic, nullable) NSDictionary<NSString *, id> *sourcePixelBufferAttributes;
// VideoComposition创立的PixelBuffer的特点
@property (nonatomic) NSDictionary<NSString *, id> *requiredPixelBufferAttributesForRenderContext;
// 告知切换烘托上下文
- (void)renderContextChanged:(AVVideoCompositionRenderContext *)newRenderContext;
// 开始组成恳求,在
- (void)startVideoCompositionRequest:(AVAsynchronousVideoCompositionRequest *)asyncVideoCompositionRequest;
// 撤销组成恳求
- (void)cancelAllPendingVideoCompositionRequests;
其间startVideoCompositionRequest
办法中的AVAsynchronousVideoCompositionRequest
目标,具有- (CVPixelBufferRef)sourceFrameByTrackID:(CMPersistentTrackID)trackID;
办法,能够获某个轨迹此时需求组成的CVPixelBufferRef
,之后咱们就能够自定义组成办法了,结合、core image、opengles或许metal等完结丰富的作用。
创立自定义组成器示例:
// 回来源PixelBuffer的特点
- (NSDictionary *)sourcePixelBufferAttributes {
return @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : [NSNumber numberWithUnsignedInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange],
(NSString*)kCVPixelBufferOpenGLESCompatibilityKey : [NSNumber numberWithBool:YES]};
}
// 回来VideoComposition创立的PixelBuffer的特点
- (NSDictionary *)requiredPixelBufferAttributesForRenderContext {
return @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : [NSNumber numberWithUnsignedInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange],
(NSString*)kCVPixelBufferOpenGLESCompatibilityKey : [NSNumber numberWithBool:YES]};
}
// 告知切换烘托上下文
- (void)renderContextChanged:(nonnull AVVideoCompositionRenderContext *)newRenderContext {
}
// 开始组成恳求
- (void)startVideoCompositionRequest:(nonnull AVAsynchronousVideoCompositionRequest *)request {
@autoreleasepool {
dispatch_async(_renderingQueue, ^{
if (self.shouldCancelAllRequests) {
// 用于撤销组成
[request finishCancelledRequest];
} else {
NSError *err = nil;
CVPixelBufferRef resultPixels = nil;
//获取当时组成指令
AVVideoCompositionInstruction *currentInstruction = request.videoCompositionInstruction;
// 获取指定trackID的轨迹的PixelBuffer
CVPixelBufferRef currentPixelBuffer = [request sourceFrameByTrackID:currentInstruction.trackID];
// 在这儿就能够进行自定义的处理了
CVPixelBuffer resultPixels = [self handleByYourSelf:currentPixelBuffer];
if (resultPixels) {
CFRetain(resultPixels);
// 处理完毕提交处理后的CVPixelBufferRef
[request finishWithComposedVideoFrame:resultPixels];
CFRelease(resultPixels);
} else {
[request finishWithError:err];
}
}
});
}
}
// 撤销组成恳求
- (void)cancelAllPendingVideoCompositionRequests {
_shouldCancelAllRequests = YES;
dispatch_barrier_async(_renderingQueue, ^() {
self.shouldCancelAllRequests = NO;
});
}
流程可分解为:
- AVAsynchronousVideoCompositionRequest绑定了当时时刻的一系列原始帧,以及当时时刻地点的 Instruction。
- 收到startVideoCompositionRequest: 回调,并接收到这个 Request。
- 依据原始帧及Instruction 相关混合参数,烘托得到组成的帧。
- 调用finishWithComposedVideoFrame,交付烘托后的帧。
在创立并运用自定义组成器后,咱们一般不再运用AVVideoCompositionInstruction
,而是恪守AVVideoCompositionInstruction
恪守的AVVideoCompositionInstruction
协议,创立自定义的组成指令。为什么?
由于咱们自定义的组成器类,是作为一个类传递给AVVideoComposition
,而不是一个特点,在实践组成烘托的时候都是在自定义组成器内部进行的,这便是说咱们实践上拿不到自定义组成器的目标,那咱们怎么告知组成器咱们规划的形形色色的组成办法呢?AVAsynchronousVideoCompositionRequest
中能够拿到AVVideoCompositionInstruction
实例,所以咱们只需遵从AVVideoCompositionInstruction
协议创立自己的组成指令类,咱们就能够随意增加参数传递给组成器内,完结数据流转。
AVVideoCompositionInstruction协议内容如下:
@protocol AVVideoCompositionInstruction<NSObject>
@required
// 指令适用的时刻规模
@property (nonatomic, readonly) CMTimeRange timeRange;
// 指示指令是否需求后期处理
@property (nonatomic, readonly) BOOL enablePostProcessing;
// YES表明从相同的源buffer在相同的组合指令下在两个不同的和城市间下烘托帧和可能会发生不同的输出帧。NO值表明两个组合物发生相同的帧。
@property (nonatomic, readonly) BOOL containsTweening;
// 组成器组成视频帧所遵从的指令需求的轨迹id
@property (nonatomic, readonly, nullable) NSArray<NSValue *> *requiredSourceTrackIDs;
// 在不组成的情况下经过源轨迹的标识符
@property (nonatomic, readonly) CMPersistentTrackID passthroughTrackID;
@optional
// iOS15新增,用于视频元数据组成
@property (nonatomic, readonly) NSArray<NSNumber *> *requiredSourceSampleDataTrackIDs;
@end
经过比较能够发现,AVVideoCompositionInstruction
类与AVVideoCompositionInstruction
协议的内容根本共同,仅仅多了backgroundCoclor
和layerInstructions
特点。也能够说苹果仅仅遵从了AVVideoCompositionInstruction
协议,创立了一个咱们看到的”体系的类”AVVideoCompositionInstruction
,而AVVideoCompositionInstruction
类仅仅新增了一个批改布景色的“规划”和一个用于传递组成细节的layerInstructions
特点,为咱们做了一个简略的范例。
那组成器的startVideoCompositionRequest:
办法每次都有必要履行吗?咱们在讲转场的开始将指令分为了”pass through”和”transition”,理论上咱们既然规定了某一段指令归于”pass through”,那就应该直接经过,不用恳求组成,这儿就需求前面的只读特点passthroughTrackID
了,不过在咱们遵从协议创立自己的”CustomMutableVideoCompositionInstruction”后,咱们能够进行批改了,在设置了passthroughTrackID
之后,就不会在需求”pass through”该段轨迹时调用startVideoCompositionRequest:
办法了。
经测试,运用
+ (AVMutableVideoComposition *)videoCompositionWithPropertiesOfAsset:(AVAsset *)asset
办法构建的组成器,其主动为咱们创立好的AVMutableVideoCompositionLayerInstruction
的passthroughTrackID
值是nil。
到这儿,前面的问题就好解说了:为什么要别离设这三个类?
要组成视频首要需求一个组成器(内置或许遵从组成协议自定义),一个遵从组成指令协议的类来指定分段的时刻规模,可是咱们纷歧定需求AVVideoCompositionLayerInstruction
类来操控组成细节,所以这三者是分隔规划,AVVideoCompositionLayerInstruction
仅仅体系的示例为了要传递参数而封装的容器。
综上,一个完好的自定义视频组成器的进程应该是这样的:
- 经过
AVAsset(s)
构建AVMutableComposition
实例composition
; - 经过
composition
创立AVMutableVideoComposition
实例videoComposition
; - 创立自定义组成器和自定义组成指令;
- 设置
videoComposition
的customVideoCompositorClass
设置为自定义组成器; - 将
videoCompositio
n的instructions
中的指令替换为自定义组成指令,并别离装备各段的自定义组成参数。 - 在自定义组成器中的
startVideoCompositionRequest:
办法中取出自定义组成指令,依据指令的组成参数处理每一帧的组成。
终究咱们解说一下iOS15新增的两个特点的作用——时基元数据组成:iOS15支撑自定义时基元数据组成,WWDC举的例子是咱们有一系列GPS数据,并且该数据带有时刻戳并与视频同步,假如期望运用这些GPS数据来影响帧的组合办法,榜首步需求先把GPS数据写入源电影中的守时元数据轨迹(咱们能够运用AVAssetWriter
),之后咱们能够运用AVMutableVideoComposition
新的sourceSampleDataTrackIDs
特点告知组成器需求组成的时基元数据轨迹ID,设置AVMutableVideoCompositionInstruction
的requiredSourceSampleDataTrackIDs
特点以告知它与当时指令相关的轨迹ID,终究在自定义组成器中获取元数据履行自定义组成。
WWDC2021的示例:
func startRequest(_ request: AVAsynchronousVideoCompositionRequest){
for trackID in request.sourceSampleDataTrackIDs {
// 也能够运用sourceSampleBuffer(byTrackID:获取CMSampleBuffer
let metadata: AVTimedMetadataGroup? = request.sourceTimedMetadata(byTrackID: trackID
// 履行自定义组成操作
using metadata, here.
}
request.finish(withComposedVideoFrame: composedFrame)
}
三、 增加文字、贴纸
尽管咱们能够处理CVPixelBuffer
了,要完结文字贴纸都不难了,不过咱们还有更简略的办法,运用咱们了解的Core Animation
结构,AVFoundation为咱们从播映和导出视频别离供给了对接Core Animation
的办法。
3.1 播映-AVSynchronizedLayer
AVFoundation供给了一个专门的CALayer
子类AVSynchronizedLayer
,用于与给定的AVPlaverltem
实例同步时刻。这个图层本身不展示任何内容,仅仅用来与图层子树协一起刻。一般运用AVSynchronizedLayer
时会将其整合到播映器视图的图层承继联系中,AVSynchronizedLayer
直接出现在视频图层之上,这样就能够增加标题、贴纸或许水印到播映视频中,并与播映器的行为坚持同步。
日常运用Core Animation
时,时刻模型取决于体系主机,主机的时刻不会中止,可是视频动画有其自己的时刻线,一起还要支撑中止、暂停、回退或快进等作用,所以不能直接用体系主机的时刻模型向一个视频中增加基于时刻的动画,所以动画的beginTime
不能直接设置为0.0了,由于它会转为CACurrentMediaTime()
代表当时的主机时刻,苹果官方文档还说到,任何具有动画特点的CoreAnimation
层,假如被增加为AVSynchronizedLayer
的子层,应该将动画的beginTime
特点设置为一个非零的正值,这样动画才干在playerItem的时刻轴上被解说。此外咱们有必要设置removedOnCompletion = NO
,不然动画便是一次性的。
咱们直接以gif表情包贴纸为例,能够直接运用上面说到的gif获取每一帧图片和其逗留时刻的代码。
// 创立gif要害帧动画
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];
animation.beginTime = AVCoreAnimationBeginTimeAtZero;
animation.removedOnCompletion = NO;
// 获取gif的图片images和逗留时刻数组times
// 运用上文中的实例代码
// 设置动画时刻点的contents对应的gif图片
animation.keyTimes = times;
animation.values = images;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
animation.duration = totalTime;
animation.repeatCount = HUGE_VALF;
// 创立gif图层
_gifLayer = [CALayer layer];
_gifLayer.frame = CGRectMake(0, 0, 150, 150);
[_gifLayer addAnimation:animation forKey:@"gif"];
// 播映器
AVPlayerViewController *playerVC = [[AVPlayerViewController alloc] init];
AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithAsset:_asset];
AVPlayer *player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
playerVC.player = player;
// 创立AVSynchronizedLayer
AVSynchronizedLayer *asyLayer = [AVSynchronizedLayer synchronizedLayerWithPlayerItem:playerItem];
// 将gif图层增加到asyLayer
[asyLayer addSublayer:_gifLayer];
// 将asyLayer图层增加到播映器图层
[playerVC.view.layer addSublayer:asyLayer];
[player play];
作用如下图:
到这儿咱们能够处理AVAssetWriter导出前的预览问题了,毫无疑问,咱们期望时运用AVComposition,咱们把要转为视频的图片看做一张张贴图,运用Core Animation,为一段视频增加CALayer,在设置的逗留时刻之后替换掉contents特点即可,这段视频咱们能够直接运用没有任何内容的纯黑色视频,作为图片转视频的视频轨迹。(马蜂窝视频批改结构规划及在 iOS 端的事务实践 3.2.2说到了这样的规划)
播映进程增加贴纸文字动画等作用就完结了,导出视频咱们还需求AVVideoCompositionCoreAnimationTool
。
3.2 导出-AVVideoCompositionCoreAnimationTool
AVMutableVideoComposition
具有一个AVVideoCompositionCoreAnimationTool
类型的特点animationTool
,构建AVVideoCompositionCoreAnimationTool
的常用是+ (instancetyp*)videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:(CALayer *)videoLayer inLayer:(CALayer *)animationLayer;
,其间要求咱们传递了两个CALayer
的目标,一个VideoLayer一个animationLayer,苹果官方文档解说,将视频的组成帧与animationLayer一同烘托构成终究的视频帧,videoLayer应该在animationLayer的子图层中,animationLayer不应该来自或被增加到任何其他的图层树中。
//创立一个兼并图层
CALayer *animationLayer = [CALayer layer];
//创立一个视频帧图层,将承载组合的视频帧
CALayer *videoLayer = [CALayer layer];
[animationLayer addSublayer:videoLayer];
[animationLayer addSublayer:gifLayer];
// 创立AVVideoCompositionCoreAnimationTool与videoComposition的animationTool关联
AVVideoCompositionCoreAnimationTool *animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:animationLayer];
self.videoComposition.animationTool = animationTool;
留意:在为videoComposition装备了animationTool之后,就不能再用于播映的playItem了,AVVideoCompositionCoreAnimationTool只能用于AVAssetExportSession和AVAssetReader这种离线烘托,不能用于实时烘托。
四、 导出
4.1 挑选视频封面
一般视频导出前还会有一个挑选视频封面的功用,获取视频封面所用到的类AVAssetImageGenerator
,在基础篇现已介绍过了,不再赘述。
视频的封面默认是榜首帧,在用户挑选了封面之后,能够运用AVAssetWriter将该时刻对应的视频帧刺进到视频的视频最前面一帧逗留数帧时刻,也能够将封面当做贴纸与原视频兼并,AVFoundation的常识现已讲到这儿了,咱们应该能够有很多思路来处理问题了,在实践的测验中去挑选最优或许最合适的方案,挑选封面完结之后便是视频导出的范畴了。
4.2 AVAssetExportSession
导出部分比较简略,只需求把前面创立的组成参数传递给导出用的实例即可。导出部分的中心类是AVAssetExportSession
,创立一个AVAssetExportSession
需求传递一个asset
和一个预设参数presetName
,预设参数支撑H.264
、HEVC
、Apple ProRes
编码,支撑不同的视频分辨率,支撑不同的视频质量级别,不过并非一切presetName
都与一切asset
和文件类型兼容,因此咱们应该导出之前调用下面的放法来查看特定组合的兼容性,查看办法如下:
+ (void)determineCompatibilityOfExportPreset:(NSString *)presetName
withAsset:(AVAsset *)asset
outputFileType:(AVFileType)outputFileType
completionHandler:(void (^)(BOOL compatible))handler;
下面列举了AVAssetExportSession
重要的特点。
// 导出的文件类型,容器格局
@property (nonatomic, copy, nullable) AVFileType outputFileType;
// 导出的路径
@property (nonatomic, copy, nullable) NSURL *outputURL;
// 是否针对网络运用进行优化
@property (nonatomic) BOOL shouldOptimizeForNetworkUse;
// 导出的状态
@property (nonatomic, readonly) AVAssetExportSessionStatus status;
// 音频混合参数
@property (nonatomic, copy, nullable) AVAudioMix *audioMix;
// 视频组成指令
@property (nonatomic, copy, nullable) AVVideoComposition *videoComposition;
// 导出的时刻区间
@property (nonatomic) CMTimeRange timeRange;
// 限制的导出文件巨细
@property (nonatomic) long long fileLengthLimit;
// 限制导出的时长
@property (nonatomic, readonly) CMTime maxDuration;
// 元数据
@property (nonatomic, copy, nullable) NSArray<AVMetadataItem *> *metadata;
// 元数据标识
@property (nonatomic, retain, nullable) AVMetadataItemFilter *metadataItemFilter;
// 导出的进展
@property (nonatomic, readonly) float progress;
从特点列表中咱们能够看到,AVAssetExportSession
除了能够设置文件类型外,还能够设置文件的巨细、时长、规模等特点,并且具有前面介绍的视频组成需求的几个要害特点,音频的混合办法AVAudioMix
、视频的组成办法AVVideoComposition
都能够在导出时运用。
导出是一个相对耗时的操作,AVAssetExportSession
供给了异步导出的接口- (void)exportAsynchronouslyWithCompletionHandler:(void (^)(void))handler
,在block中咱们能够随时获取progress
导出进展,一起依据AVAssetExportSessionStatus
的值,来观察导出结果是否契合预期。
// 经过composition和presetName创立AVAssetExportSession
self.exportSession = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetHEVCHighestQuality];
// 装备组成视频参数
self.exportSession.videoComposition = videoComposition;
// 装备音频混合参数
self.exportSession.audioMix = audioMix;
// 装备输出url地址
self.exportSession.outputURL = [NSURL fileURLWithPath:path];
// 装备输出的文件格局
self.exportSession.outputFileType = AVFileTypeQuickTimeMovie;
// 开始异步导出
[self.exportSession exportAsynchronouslyWithCompletionHandler:^(void){
// 监听导出状态
switch (self.exportSession.status) {
case AVAssetExportSessionStatusCompleted:
if (complete) {
complete();
}
break;
case AVAssetExportSessionStatusFailed:
NSLog(@"%@",self.exportSession.error);
break;
case AVAssetExportSessionStatusCancelled:
NSLog(@"AVAssetExportSessionStatusCancelled");
break;
default:
break;
}
}];
前面咱们还学习了运用AVAssetReader
和AVAssetWriter
合作来重新编码写入文件的办法,其间AVAssetReaderAudioMixOutput
具有audioMix
特点,AVAssetReaderVideoCompositionOutput
具有videoCompositionOutput
特点,这样的话整个composition
的组成装备都能够作为AVAssetReaderOutput
的参数了。
现在咱们现已学习两种导出文件的办法AVAssetExportSession
和AVAssetWriter
。假如只需简略的导出为某种文件格局,不对细节有很高的要求,运用AVAssetExportSession
就足够了, 而运用AVAssetWriter
的优势是能够经过指定比特率、帧率、视频帧尺度、颜色空间、要害帧距离、视频比特率、H.264装备文件、像素宽高比乃至用于导出的视频编码器等等,咱们能够彻底操控导出进程。
总结
终究用一张图片总结一下本文的内容:
demo地址: avfoundationdemo
功用包括:
- 音频:
- AVAudioPlayer音频播映
- AVAudioEngineRecorder音频录制
- 视频:
- 视频拼接组成、增加布景音乐、叠化转场作用
- 视频增加贴纸、文字、gif表情包
- 图片:
- 普通图片转视频
- 实况相片转视频、gif转视频
- 其他:
- 获取文件数据样本格局信息
参阅文档
AVFoundation官方文档
wwdc2021-What’s new in AVFoundation
wwdc2020-Edit and play back HDR video with AVFoundation
wwdc2020-What’s new in camera capture
VideoLab – 高性能且灵敏的 iOS 视频剪辑与特效结构
马蜂窝视频批改结构规划及在 iOS 端的事务实践