在iOS中给视频添加滤镜的方法示例
「众所周知,视频可以P」,今天我们来学习怎么给视频添加滤镜。
在iOS中,对视频进行图像处理一般有两种方式:GPUImage和AVFoundation。
一、GPUImage
在之前的文章中,我们对GPUImage已经有了一定的了解。之前一般使用它对摄像头采集的图像数据进行处理,然而,它对本地视频的处理也一样方便。
直接看代码:
//movie NSString*path=[[NSBundlemainBundle]pathForResource:@"sample"ofType:@"mp4"]; NSURL*url=[NSURLfileURLWithPath:path]; GPUImageMovie*movie=[[GPUImageMoviealloc]initWithURL:url]; //filter GPUImageSmoothToonFilter*filter=[[GPUImageSmoothToonFilteralloc]init]; //view GPUImageView*imageView=[[GPUImageViewalloc]initWithFrame:CGRectMake(0,80,self.view.frame.size.width,self.view.frame.size.width)]; [self.viewaddSubview:imageView]; //chain [movieaddTarget:filter]; [filteraddTarget:imageView]; //processing [moviestartProcessing];
核心代码一共就几行。GPUImageMovie负责视频文件的读取,GPUImageSmoothToonFilter负责滤镜效果处理,GPUImageView负责最终图像的展示。
通过滤镜链将三者串起来,然后调用GPUImageMovie的startProcessing方法开始处理。
虽然GPUImage在使用上简单,但是存在着没有声音、在非主线程调用UI、导出文件麻烦、无法进行播放控制等诸多缺点。
小结:GPUImage虽然使用很方便,但是存在诸多缺点,不满足生产环境需要。
二、AVFoundation
1、AVPlayer的使用
首先来复习一下AVPlayer最简单的使用方式:
NSURL*url=[[NSBundlemainBundle]URLForResource:@"sample"withExtension:@"mp4"]; AVURLAsset*asset=[AVURLAssetassetWithURL:url]; AVPlayerItem*playerItem=[[AVPlayerItemalloc]initWithAsset:asset]; AVPlayer*player=[[AVPlayeralloc]initWithPlayerItem:playerItem]; AVPlayerLayer*playerLayer=[AVPlayerLayerplayerLayerWithPlayer:player];
第一步先构建AVPlayerItem,然后通过AVPlayerItem创建AVPlayer,最后通过AVPlayer创建AVPlayerLayer。
AVPlayerLayer是CALayer的子类,可以把它添加到任意的Layer上。当AVPlayer调用play方法时,AVPlayerLayer上就能将图像渲染出来。
AVPlayer的使用方式十分简单。但是,按照上面的方式,最终只能在AVPlayerLayer上渲染出最原始的图像。如果我们希望在播放的同时,对原始图像进行处理,则需要修改AVPlayer的渲染过程。
2、修改AVPlayer的渲染过程
修改AVPlayer的渲染过程,要从AVPlayerItem下手,主要分为四步:
第一步:自定义AVVideoCompositing类
AVVideoCompositing是一个协议,我们的自定义类要实现这个协议。在这个自定义类中,可以获取到每一帧的原始图像,进行处理并输出。
在这个协议中,最关键是startVideoCompositionRequest方法的实现:
//CustomVideoCompositing.m -(void)startVideoCompositionRequest:(AVAsynchronousVideoCompositionRequest*)asyncVideoCompositionRequest{ dispatch_async(self.renderingQueue,^{ @autoreleasepool{ if(self.shouldCancelAllRequests){ [asyncVideoCompositionRequestfinishCancelledRequest]; }else{ CVPixelBufferRefresultPixels=[selfnewRenderdPixelBufferForRequest:asyncVideoCompositionRequest]; if(resultPixels){ [asyncVideoCompositionRequestfinishWithComposedVideoFrame:resultPixels]; CVPixelBufferRelease(resultPixels); }else{ //printerror } } } }); }
通过newRenderdPixelBufferForRequest方法从AVAsynchronousVideoCompositionRequest中获取到处理后的CVPixelBufferRef后输出,看下这个方法的实现:
//CustomVideoCompositing.m -(CVPixelBufferRef)newRenderdPixelBufferForRequest:(AVAsynchronousVideoCompositionRequest*)request{ CustomVideoCompositionInstruction*videoCompositionInstruction=(CustomVideoCompositionInstruction*)request.videoCompositionInstruction; NSArray*layerInstructions=videoCompositionInstruction.layerInstructions; CMPersistentTrackIDtrackID=layerInstructions.firstObject.trackID; CVPixelBufferRefsourcePixelBuffer=[requestsourceFrameByTrackID:trackID]; CVPixelBufferRefresultPixelBuffer=[videoCompositionInstructionapplyPixelBuffer:sourcePixelBuffer]; if(!resultPixelBuffer){ CVPixelBufferRefemptyPixelBuffer=[selfcreateEmptyPixelBuffer]; returnemptyPixelBuffer; }else{ returnresultPixelBuffer; } }
在这个方法中,我们通过trackID从AVAsynchronousVideoCompositionRequest中获取到sourcePixelBuffer,也就是当前帧的原始图像。
然后调用videoCompositionInstruction的applyPixelBuffer方法,将sourcePixelBuffer作为输入,得到处理后的结果resultPixelBuffer。也就是说,我们对图像的处理操作,都发生在applyPixelBuffer方法中。
在newRenderdPixelBufferForRequest这个方法中,我们已经拿到了当前帧的原始图像sourcePixelBuffer,其实也可以直接在这个方法中对图像进行处理。
那为什么还需要把处理操作放在CustomVideoCompositionInstruction中呢?
因为在实际渲染的时候,自定义AVVideoCompositing类的实例创建是系统内部完成的。也就是说,我们访问不到最终的AVVideoCompositing对象。所以无法进行一些渲染参数的动态修改。而从AVAsynchronousVideoCompositionRequest中,可以获取到AVVideoCompositionInstruction对象,所以我们需要自定义AVVideoCompositionInstruction,这样就可以间接地通过修改AVVideoCompositionInstruction的属性,来动态修改渲染参数。
第二步:自定义AVVideoCompositionInstruction
这个类的关键点是applyPixelBuffer方法的实现:
//CustomVideoCompositionInstruction.m -(CVPixelBufferRef)applyPixelBuffer:(CVPixelBufferRef)pixelBuffer{ self.filter.pixelBuffer=pixelBuffer; CVPixelBufferRefoutputPixelBuffer=self.filter.outputPixelBuffer; CVPixelBufferRetain(outputPixelBuffer); returnoutputPixelBuffer; }
这里把OpenGLES的处理细节都封装到了filter中。这个类的实现细节可以先忽略,只需要知道它接受原始的CVPixelBufferRef,返回处理后的CVPixelBufferRef。
第三步:构建AVMutableVideoComposition
构建的代码如下:
self.videoComposition=[selfcreateVideoCompositionWithAsset:self.asset]; self.videoComposition.customVideoCompositorClass=[CustomVideoCompositingclass]; -(AVMutableVideoComposition*)createVideoCompositionWithAsset:(AVAsset*)asset{ AVMutableVideoComposition*videoComposition=[AVMutableVideoCompositionvideoCompositionWithPropertiesOfAsset:asset]; NSArray*instructions=videoComposition.instructions; NSMutableArray*newInstructions=[NSMutableArrayarray]; for(AVVideoCompositionInstruction*instructionininstructions){ NSArray*layerInstructions=instruction.layerInstructions; //TrackIDs NSMutableArray*trackIDs=[NSMutableArrayarray]; for(AVVideoCompositionLayerInstruction*layerInstructioninlayerInstructions){ [trackIDsaddObject:@(layerInstruction.trackID)]; } CustomVideoCompositionInstruction*newInstruction=[[CustomVideoCompositionInstructionalloc]initWithSourceTrackIDs:trackIDstimeRange:instruction.timeRange]; newInstruction.layerInstructions=instruction.layerInstructions; [newInstructionsaddObject:newInstruction]; } videoComposition.instructions=newInstructions; returnvideoComposition; }
构建AVMutableVideoComposition的过程主要做两件事情。
第一件事情,把videoComposition的customVideoCompositorClass属性,设置为我们自定义的CustomVideoCompositing。
第二件事情,首先通过系统提供的方法videoCompositionWithPropertiesOfAsset构建出AVMutableVideoComposition对象,然后将它的instructions属性修改为自定义的CustomVideoCompositionInstruction类型。(就像「第一步」提到的,后续可以在CustomVideoCompositing中,拿到CustomVideoCompositionInstruction对象。)
注意:这里可以把CustomVideoCompositionInstruction保存下来,然后通过修改它的属性,去修改渲染参数。
第四步:构建AVPlayerItem
有了AVMutableVideoComposition之后,后面的事情就简单多了。
只需要在创建AVPlayerItem的时候,多赋值一个videoComposition属性。
self.playerItem=[[AVPlayerItemalloc]initWithAsset:self.asset]; self.playerItem.videoComposition=self.videoComposition;
这样,整条链路就串起来了,AVPlayer在播放时,就能在CustomVideoCompositionInstruction的applyPixelBuffer方法中接收到原始图像的CVPixelBufferRef。
3、应用滤镜效果
这一步要做的事情是:在CVPixelBufferRef上添加滤镜效果,并输出处理后的CVPixelBufferRef。
要做到这件事情,有很多种方式。包括但不限定于:OpenGLES、CIImage、Metal、GPUImage等。
为了同样使用前面用到的GPUImageSmoothToonFilter,这里介绍一下GPUImage的方式。
关键代码如下:
-(CVPixelBufferRef)renderByGPUImage:(CVPixelBufferRef)pixelBuffer{ CVPixelBufferRetain(pixelBuffer); __blockCVPixelBufferRefoutput=nil; runSynchronouslyOnVideoProcessingQueue(^{ [GPUImageContextuseImageProcessingContext]; //(1) GLuinttextureID=[self.pixelBufferHelperconvertYUVPixelBufferToTexture:pixelBuffer]; CGSizesize=CGSizeMake(CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer)); [GPUImageContextsetActiveShaderProgram:nil]; //(2) GPUImageTextureInput*textureInput=[[GPUImageTextureInputalloc]initWithTexture:textureIDsize:size]; GPUImageSmoothToonFilter*filter=[[GPUImageSmoothToonFilteralloc]init]; [textureInputaddTarget:filter]; GPUImageTextureOutput*textureOutput=[[GPUImageTextureOutputalloc]init]; [filteraddTarget:textureOutput]; [textureInputprocessTextureWithFrameTime:kCMTimeZero]; //(3) output=[self.pixelBufferHelperconvertTextureToPixelBuffer:textureOutput.texture textureSize:size]; [textureOutputdoneWithTexture]; glDeleteTextures(1,&textureID); }); CVPixelBufferRelease(pixelBuffer); returnoutput; }
(1)一开始读入的视频帧是YUV格式的,首先把YUV格式的CVPixelBufferRef转成OpenGL纹理。
(2)通过GPUImageTextureInput来构造滤镜链起点,GPUImageSmoothToonFilter来添加滤镜效果,GPUImageTextureOutput来构造滤镜链终点,最终也是输出OpenGL纹理。
(3)将处理后的OpenGL纹理转化为CVPixelBufferRef。
另外,由于CIImage使用简单,也顺便提一下用法。
关键代码如下:
-(CVPixelBufferRef)renderByCIImage:(CVPixelBufferRef)pixelBuffer{ CVPixelBufferRetain(pixelBuffer); CGSizesize=CGSizeMake(CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer)); //(1) CIImage*image=[[CIImagealloc]initWithCVPixelBuffer:pixelBuffer]; //(2) CIImage*filterImage=[CIImageimageWithColor:[CIColorcolorWithRed:255.0/255 green:245.0/255 blue:215.0/255 alpha:0.1]]; //(3) image=[filterImageimageByCompositingOverImage:image]; //(4) CVPixelBufferRefoutput=[self.pixelBufferHelpercreatePixelBufferWithSize:size]; [self.contextrender:imagetoCVPixelBuffer:output]; CVPixelBufferRelease(pixelBuffer); returnoutput; }
(1)将CVPixelBufferRef转化为CIImage。
(2)创建一个带透明度的CIImage。
(3)用系统方法将CIImage进行叠加。
(4)将叠加后的CIImage转化为CVPixelBufferRef。
4、导出处理后的视频
视频处理完成后,最终都希望能导出并保存。
导出的代码也很简单:
self.exportSession=[[AVAssetExportSessionalloc]initWithAsset:self.assetpresetName:AVAssetExportPresetHighestQuality]; self.exportSession.videoComposition=self.videoComposition; self.exportSession.outputFileType=AVFileTypeMPEG4; self.exportSession.outputURL=[NSURLfileURLWithPath:self.exportPath]; [self.exportSessionexportAsynchronouslyWithCompletionHandler:^{ //保存到相册 //... }];
这里关键的地方在于将videoComposition设置为前面构造的AVMutableVideoComposition对象,然后设置好输出路径和文件格式后就可以开始导出。导出成功后,可以将视频文件转存到相册中。
小结:AVFoundation虽然使用比较繁琐,但是功能强大,可以很方便地导出视频处理的结果,是用来做视频处理的不二之选。
源码
请到GitHub上查看完整代码。
到此这篇关于在iOS中给视频添加滤镜的方法示例的文章就介绍到这了,更多相关iOS视频添加滤镜内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。