AVFoundation写的一些简单的视频处理
代码不考虑音频情况,主要分享一下学习结果
视频多路:介绍一个便捷的多视频重叠处理方法
视频内自动放大的视频水印:介绍一下如何给视频添加视频水印并给其做动画
Demo内有AnimationTool的图片静态水印及带动画图片水印
效果视频和源码已经上传到github上
我的demo源码
我参考的前辈们的文档
AVFoundation API介绍 点下面点下面
https://blog.csdn.net/u011374318/article/details/78829096
AVFoundation 水印处理,继续点下面点下面
http://www.cocoachina.com/ios/20141208/10542.html
https://www.jianshu.com/p/9f4f37e5abc6
效果图
9宫格视频播放.png 动态放大视频水印.pngAVFoundation多路
- Talk is cheap, show me the code
@interface MultipathAsset : NSObject
@property (nonatomic) AVAsset *asset;
@property (nonatomic) CGPoint origin;
- (AVPlayerItem *)makePlayerItemWithAssets:(NSArray<MultipathAsset *> *)assets size:(CGSize)size {
AVMutableComposition *comp = [AVMutableComposition composition];
//记录下 每一个trackID对应的哪个video
NSMutableDictionary *trackRecord = [NSMutableDictionary dictionaryWithCapacity:assets.count];
for (MultipathAsset *item in assets) {
AVMutableCompositionTrack *track = [comp addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVAssetTrack *assetTrack = [item.asset tracksWithMediaType:AVMediaTypeVideo].firstObject;
CMTimeRange range = CMTimeRangeMake(kCMTimeZero, item.asset.duration);
[track insertTimeRange:range ofTrack:assetTrack atTime:kCMTimeZero error:nil];
[trackRecord setObject:item forKey:@(track.trackID)];
}
AVMutableVideoComposition *videoComp = [AVMutableVideoComposition videoCompositionWithPropertiesOfAsset:comp];
videoComp.renderSize = size;
for (AVMutableVideoCompositionInstruction *instruction in videoComp.instructions) {
[self adjustInstructionLayers:instruction];
for (AVMutableVideoCompositionLayerInstruction *layerIns in instruction.layerInstructions) {
MultipathAsset *video = [trackRecord objectForKey:@(layerIns.trackID)];
if (video) {
CGAffineTransform trans = video.asset.preferredTransform;
trans = CGAffineTransformTranslate(trans, video.origin.x, video.origin.y);
[layerIns setTransform:trans atTime:kCMTimeZero];
}
}
}
AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:comp];
item.videoComposition = videoComp;
return item;
}
///调整Instrction的layerIns顺序
- (void)adjustInstructionLayers:(AVMutableVideoCompositionInstruction *)instruction {
NSArray *layerInstructions = instruction.layerInstructions;
layerInstructions = [layerInstructions sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
AVMutableVideoCompositionLayerInstruction *layerIns1 = obj1;
AVMutableVideoCompositionLayerInstruction *layerIns2 = obj2;
if (layerIns1.trackID < layerIns2.trackID) {
return NSOrderedDescending;
} else {
return NSOrderedAscending;
}
}];
instruction.layerInstructions = layerInstructions;
}
代码比自己算Instruction和layerInstruction会少很多,也不容易出错
- 用到的核心方法:
AVMutableVideoComposition videoCompositionWithPropertiesOfAsset:
让我们看一下它的说明:意思就是它会帮我们把AVAsset的视频轨道们划分好 Instructions和LayerInstructions。
方法说明.png
做多视频处理最痛苦的事情就是处理各个轨道,尤其是当同一时间视频重叠的时候,我们需要去划分Instructions和其中的layerInstructions,整个过程既繁琐又容易出问题。
现在AVFoundation框架可以帮我们做好这部分工作,我们只需要将对应的layerInstruction一一处理就好了
- 需要注意的点
-
帮我们划分好的LayerInstructions不一定是要我们要的层级关系,最前面的layerInstruction对应的视频会出现在最上层,之后的会叠在下面,以此类推,所以我们需要按照我们需求重新调整一下LayerInstructions数组的顺序
layerInstructions说明.png
- 如下添加视频轨道的方法的trackID 会从1开始 然后再=2 再=3 依次+1,这样就可以跟我们传入的视频数组对应起来了,也可以不使用kCMPersistentTrackID_Invalid而是直接指定轨道ID,但是要连续的从1开始不能中断。
我多路里面用trackRecord记录下每个视频的轨道ID
AVMutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid
AVFoundation动态放大居中视频水印
在多路基础上将第二个视频居中显示就好了,并添加一个放大动画。有兴趣的可以自己改造之前多路demo将每一个视频添加移动放大渐显渐隐效果
AVMutableComposition *comp = [AVMutableComposition composition];
CGSize videoSize = bgAsset.naturalSize;
CGFloat startX,startY,endX,endY;
CGFloat scale = 1.2;
{
startX = videoSize.width / 2 - watermark.naturalSize.width / 2;
startY = videoSize.height / 2 - watermark.naturalSize.height / 2;
endX = videoSize.width / 2 - watermark.naturalSize.width * scale / 2;
endY = videoSize.height / 2 - watermark.naturalSize.height * scale / 2;
}
AVMutableCompositionTrack *bgTrack = [comp addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *wmTrack = [comp addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
CMTimeRange bgRange = CMTimeRangeMake(kCMTimeZero, bgAsset.duration);
AVAssetTrack *bgAssetTrack = [bgAsset tracksWithMediaType:AVMediaTypeVideo].firstObject;
[bgTrack insertTimeRange:bgRange ofTrack:bgAssetTrack atTime:kCMTimeZero error:nil];
CMTimeRange wmRange = CMTimeRangeMake(kCMTimeZero, watermark.duration);
AVAssetTrack *wmAssetTrack = [watermark tracksWithMediaType:AVMediaTypeVideo].firstObject;
[wmTrack insertTimeRange:wmRange ofTrack:wmAssetTrack atTime:kCMTimeZero error:nil];
AVMutableVideoComposition *videoComp = [AVMutableVideoComposition videoCompositionWithPropertiesOfAsset:comp];
videoComp.renderSize = videoSize;
for (AVMutableVideoCompositionInstruction *instruction in videoComp.instructions) {
[self adjustInstructionLayers:instruction];
for (AVMutableVideoCompositionLayerInstruction *layerIns in instruction.layerInstructions) {
if (layerIns.trackID == 1) {
[layerIns setTransform:bgAsset.preferredTransform atTime:kCMTimeZero];
} else {
CGAffineTransform startTrans;
CGAffineTransform endTrans;
CGAffineTransform trans = watermark.preferredTransform;
startTrans = CGAffineTransformTranslate(trans, startX, startY);
trans = CGAffineTransformTranslate(trans, endX, endY);
endTrans = CGAffineTransformScale(trans, scale, scale);
[layerIns setTransformRampFromStartTransform:startTrans toEndTransform:endTrans timeRange:wmRange];
}
}
}
- 需要注意的地方 有一个坑
LayerInstruction的Transform是基于左上角为原点的!!!,而不是我们平常layer那样默认居中锚点,做类似视频放大动画会出现基于左上角不动而向右边放大的情况,不是我们期望的中心不动四周扩散。
明白这点就只需要转换一下,居中放大动画只需要计算出放大后的左上角原点,移动过去,之后再给放大就好了
CGFloat scale = 1.2;
endX = videoSize.width / 2 - watermark.naturalSize.width * scale / 2;
endY = videoSize.height / 2 - watermark.naturalSize.height * scale / 2;
......
CGAffineTransform trans = watermark.preferredTransform;
trans = CGAffineTransformTranslate(trans, endX, endY);
endTrans = CGAffineTransformScale(trans, scale, scale);
然后调用如下方法
layerIns setTransformRampFromStartTransform:startTrans toEndTransform:endTrans timeRange:wmRange
这里额外说明一下LayerInstruction提供了3个动画有关的函数,可以满足我们大部分需求,copy自前辈的API介绍
//获取包含指定时间的仿射变化梯度信息
//startTransform、endTransform 用来接收变化过程的起始值与结束值
//timeRange 用来接收变化的持续时间范围
//返回值表示指定的时间 time 是否在变化时间 timeRange 内
- (BOOL)getTransformRampForTime:(CMTime)time startTransform:(nullable CGAffineTransform *)startTransform endTransform:(nullable CGAffineTransform *)endTransform timeRange:(nullable CMTimeRange *)timeRange;
//获取包含指定时间的透明度变化梯度信息
//startOpacity、endOpacity 用来接收透明度变化过程的起始值与结束值
//timeRange 用来接收变化的持续时间范围
//返回值表示指定的时间 time 是否在变化时间 timeRange 内
- (BOOL)getOpacityRampForTime:(CMTime)time startOpacity:(nullable float *)startOpacity endOpacity:(nullable float *)endOpacity timeRange:(nullable CMTimeRange *)timeRange;
//获取包含指定时间的裁剪区域的变化梯度信息
//startCropRectangle、endCropRectangle 用来接收变化过程的起始值与结束值
//timeRange 用来接收变化的持续时间范围
//返回值表示指定的时间 time 是否在变化时间 timeRange 内
- (BOOL)getCropRectangleRampForTime:(CMTime)time startCropRectangle:(nullable CGRect *)startCropRectangle endCropRectangle:(nullable CGRect *)endCropRectangle timeRange:(nullable CMTimeRange *)timeRange NS_AVAILABLE(10_9, 7_0);
网友评论