在iOS中给视频添加滤镜的方法示例

「众所周知,视频可以 P」,今天我们来学习怎么给视频添加滤镜。

在 iOS 中,对视频进行图像处理一般有两种方式: GPUImage AVFoundation

一、GPUImage

在之前的文章中,我们对 GPUImage 已经有了一定的了解。之前一般使用它对摄像头采集的图像数据进行处理,然而,它对本地视频的处理也一样方便。

直接看代码:

// movie
NSString *path = [[NSBundle mainBundle] pathForResource:@"sample" ofType:@"mp4"];
NSURL *url = [NSURL fileURLWithPath:path];
GPUImageMovie *movie = [[GPUImageMovie alloc] initWithURL:url];

// filter
GPUImageSmoothToonFilter *filter = [[GPUImageSmoothToonFilter alloc] init];

// view
GPUImageView *imageView = [[GPUImageView alloc] initWithFrame:CGRectMake(0, 80, self.view.frame.size.width, self.view.frame.size.width)];
[self.view addSubview:imageView];

// chain
[movie addTarget:filter];
[filter addTarget:imageView];

// processing
[movie startProcessing];

核心代码一共就几行。 GPUImageMovie 负责视频文件的读取, GPUImageSmoothToonFilter 负责滤镜效果处理, GPUImageView 负责最终图像的展示。

通过滤镜链将三者串起来,然后调用 GPUImageMovie 的 startProcessing 方法开始处理。

虽然 GPUImage 在使用上简单,但是存在着 没有声音 、 在非主线程调用 UI 、 导出文件麻烦 、 无法进行播放控制 等诸多缺点。

小结:GPUImage 虽然使用很方便,但是存在诸多缺点,不满足生产环境需要。

二、AVFoundation

1、 AVPlayer 的使用

首先来复习一下 AVPlayer 最简单的使用方式:

NSURL *url = [[NSBundle mainBundle] URLForResource:@"sample" withExtension:@"mp4"];
AVURLAsset *asset = [AVURLAsset assetWithURL:url];
AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithAsset:asset];

AVPlayer *player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer: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) {
        [asyncVideoCompositionRequest finishCancelledRequest];
      } else {
        CVPixelBufferRef resultPixels = [self newRenderdPixelBufferForRequest:asyncVideoCompositionRequest];
        if (resultPixels) {
          [asyncVideoCompositionRequest finishWithComposedVideoFrame:resultPixels];
          CVPixelBufferRelease(resultPixels);
        } else {
          // print error
        }
      }
    }
  });
}

通过 newRenderdPixelBufferForRequest 方法从 AVAsynchronousVideoCompositionRequest 中获取到处理后的 CVPixelBufferRef 后输出,看下这个方法的实现:

// CustomVideoCompositing.m
- (CVPixelBufferRef)newRenderdPixelBufferForRequest:(AVAsynchronousVideoCompositionRequest *)request {
  CustomVideoCompositionInstruction *videoCompositionInstruction = (CustomVideoCompositionInstruction *)request.videoCompositionInstruction;
  NSArray<AVVideoCompositionLayerInstruction *> *layerInstructions = videoCompositionInstruction.layerInstructions;
  CMPersistentTrackID trackID = layerInstructions.firstObject.trackID;

  CVPixelBufferRef sourcePixelBuffer = [request sourceFrameByTrackID:trackID];
  CVPixelBufferRef resultPixelBuffer = [videoCompositionInstruction applyPixelBuffer:sourcePixelBuffer];

  if (!resultPixelBuffer) {
    CVPixelBufferRef emptyPixelBuffer = [self createEmptyPixelBuffer];
    return emptyPixelBuffer;
  } else {
    return resultPixelBuffer;
  }
}

在这个方法中,我们通过 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;
  CVPixelBufferRef outputPixelBuffer = self.filter.outputPixelBuffer;
  CVPixelBufferRetain(outputPixelBuffer);
  return outputPixelBuffer;
}

这里把 OpenGL ES 的处理细节都封装到了 filter 中。这个类的实现细节可以先忽略,只需要知道它接受 原始的 CVPixelBufferRef ,返回 处理后的 CVPixelBufferRef 。

第三步:构建 AVMutableVideoComposition

构建的代码如下:

self.videoComposition = [self createVideoCompositionWithAsset:self.asset];
self.videoComposition.customVideoCompositorClass = [CustomVideoCompositing class];
- (AVMutableVideoComposition *)createVideoCompositionWithAsset:(AVAsset *)asset {
  AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoCompositionWithPropertiesOfAsset:asset];
  NSArray *instructions = videoComposition.instructions;
  NSMutableArray *newInstructions = [NSMutableArray array];
  for (AVVideoCompositionInstruction *instruction in instructions) {
    NSArray *layerInstructions = instruction.layerInstructions;
    // TrackIDs
    NSMutableArray *trackIDs = [NSMutableArray array];
    for (AVVideoCompositionLayerInstruction *layerInstruction in layerInstructions) {
      [trackIDs addObject:@(layerInstruction.trackID)];
    }
    CustomVideoCompositionInstruction *newInstruction = [[CustomVideoCompositionInstruction alloc] initWithSourceTrackIDs:trackIDs timeRange:instruction.timeRange];
    newInstruction.layerInstructions = instruction.layerInstructions;
    [newInstructions addObject:newInstruction];
  }
  videoComposition.instructions = newInstructions;
  return videoComposition;
}

构建 AVMutableVideoComposition 的过程 主要做两件事情 。

第一件事情,把 videoComposition 的 customVideoCompositorClass 属性,设置为我们自定义的 CustomVideoCompositing 。

第二件事情,首先通过系统提供的方法 videoCompositionWithPropertiesOfAsset 构建出 AVMutableVideoComposition 对象,然后将它的 instructions 属性修改为自定义的 CustomVideoCompositionInstruction 类型。(就像「第一步」提到的,后续可以在 CustomVideoCompositing 中,拿到 CustomVideoCompositionInstruction 对象。)

注意:这里可以把 CustomVideoCompositionInstruction 保存下来,然后通过修改它的属性,去修改渲染参数。

第四步:构建 AVPlayerItem

有了 AVMutableVideoComposition 之后,后面的事情就简单多了。

只需要在创建 AVPlayerItem 的时候,多赋值一个 videoComposition 属性。

self.playerItem = [[AVPlayerItem alloc] initWithAsset:self.asset];
self.playerItem.videoComposition = self.videoComposition;

这样,整条链路就串起来了, AVPlayer 在播放时,就能在 CustomVideoCompositionInstruction 的 applyPixelBuffer 方法中接收到 原始图像的 CVPixelBufferRef 。

3、应用滤镜效果

这一步要做的事情是: 在 CVPixelBufferRef 上添加滤镜效果,并输出处理后的 CVPixelBufferRef 。

要做到这件事情,有很多种方式。包括但不限定于: OpenGL ES 、 CIImage 、 Metal 、 GPUImage 等。

为了同样使用前面用到的 GPUImageSmoothToonFilter ,这里介绍一下 GPUImage 的方式。

关键代码如下:

- (CVPixelBufferRef)renderByGPUImage:(CVPixelBufferRef)pixelBuffer {
  CVPixelBufferRetain(pixelBuffer);

  __block CVPixelBufferRef output = nil;
  runSynchronouslyOnVideoProcessingQueue(^{
    [GPUImageContext useImageProcessingContext];

    // (1)
    GLuint textureID = [self.pixelBufferHelper convertYUVPixelBufferToTexture:pixelBuffer];
    CGSize size = CGSizeMake(CVPixelBufferGetWidth(pixelBuffer),
                 CVPixelBufferGetHeight(pixelBuffer));

    [GPUImageContext setActiveShaderProgram:nil];
    // (2)
    GPUImageTextureInput *textureInput = [[GPUImageTextureInput alloc] initWithTexture:textureID size:size];
    GPUImageSmoothToonFilter *filter = [[GPUImageSmoothToonFilter alloc] init];
    [textureInput addTarget:filter];
    GPUImageTextureOutput *textureOutput = [[GPUImageTextureOutput alloc] init];
    [filter addTarget:textureOutput];
    [textureInput processTextureWithFrameTime:kCMTimeZero];

    // (3)
    output = [self.pixelBufferHelper convertTextureToPixelBuffer:textureOutput.texture
                             textureSize:size];

    [textureOutput doneWithTexture];

    glDeleteTextures(1, &textureID);
  });
  CVPixelBufferRelease(pixelBuffer);

  return output;
}

(1)一开始读入的视频帧是 YUV 格式的,首先把 YUV 格式的 CVPixelBufferRef 转成 OpenGL 纹理。

(2)通过 GPUImageTextureInput 来构造滤镜链起点, GPUImageSmoothToonFilter 来添加滤镜效果, GPUImageTextureOutput 来构造滤镜链终点,最终也是输出 OpenGL 纹理。

(3)将处理后的 OpenGL 纹理转化为 CVPixelBufferRef 。

另外,由于 CIImage 使用简单,也顺便提一下用法。

关键代码如下:

- (CVPixelBufferRef)renderByCIImage:(CVPixelBufferRef)pixelBuffer {
  CVPixelBufferRetain(pixelBuffer);

  CGSize size = CGSizeMake(CVPixelBufferGetWidth(pixelBuffer),
               CVPixelBufferGetHeight(pixelBuffer));
  // (1)
  CIImage *image = [[CIImage alloc] initWithCVPixelBuffer:pixelBuffer];
  // (2)
  CIImage *filterImage = [CIImage imageWithColor:[CIColor colorWithRed:255.0 / 255
                                  green:245.0 / 255
                                  blue:215.0 / 255
                                  alpha:0.1]];
  // (3)
  image = [filterImage imageByCompositingOverImage:image]; 

  // (4)
  CVPixelBufferRef output = [self.pixelBufferHelper createPixelBufferWithSize:size];
  [self.context render:image toCVPixelBuffer:output];

  CVPixelBufferRelease(pixelBuffer);
  return output;
}

(1)将 CVPixelBufferRef 转化为 CIImage 。

(2)创建一个带透明度的 CIImage 。

(3)用系统方法将 CIImage 进行叠加。

(4)将叠加后的 CIImage 转化为 CVPixelBufferRef 。

4、导出处理后的视频

视频处理完成后,最终都希望能导出并保存。

导出的代码也很简单:

self.exportSession = [[AVAssetExportSession alloc] initWithAsset:self.asset presetName:AVAssetExportPresetHighestQuality];
self.exportSession.videoComposition = self.videoComposition;
self.exportSession.outputFileType = AVFileTypeMPEG4;
self.exportSession.outputURL = [NSURL fileURLWithPath:self.exportPath];

[self.exportSession exportAsynchronouslyWithCompletionHandler:^{
  // 保存到相册
  // ...
}];

这里关键的地方在于将 videoComposition 设置为前面构造的 AVMutableVideoComposition 对象,然后设置好输出路径和文件格式后就可以开始导出。导出成功后,可以将视频文件转存到相册中。

小结: AVFoundation 虽然使用比较繁琐,但是功能强大,可以很方便地导出视频处理的结果,是用来做视频处理的不二之选。

源码

请到 GitHub上查看完整代码。

到此这篇关于在iOS中给视频添加滤镜的方法示例的文章就介绍到这了,更多相关iOS 视频添加滤镜内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • iOS图片压缩、滤镜、剪切及渲染等详解

    前言 本文主要给大家介绍了关于iOS图片压缩.滤镜.剪切及渲染的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧 主要内容: 1.图片基础知识的介绍 2.图片压缩 简单的回顾一下从相册获取一张图片 直接格式转换压缩:png.jpg.Context 重新绘制 3.图片处理 基于图片像素修改 图片剪切clip 渲染render 截屏 一.图片基础知识的介绍 一张图像是像素点的集合,每一个像素都是一个独立,有自己的颜色.图像一般情况下都存储成数组,可以说是二维数组.当成百上千万

  • iOS给图片添加滤镜&使用openGLES动态渲染图片详解及实例

    iOS给图片添加滤镜&使用openGLES动态渲染图片 给图片增加滤镜有这两种方式: CoreImage / openGLES 下面先说明如何使用CoreImage给图片添加滤镜, 主要为以下步骤: #1.导入CIImage格式的原始图片 #2.创建CIFilter滤镜 #3.用CIContext将滤镜中的图片渲染出来 #4.导出渲染后的图片 参考代码: //导入CIImage CIImage *ciImage = [[CIImage alloc] initWithImage:[UIImage

  • 在iOS中给视频添加滤镜的方法示例

    「众所周知,视频可以 P」,今天我们来学习怎么给视频添加滤镜. 在 iOS 中,对视频进行图像处理一般有两种方式: GPUImage 和 AVFoundation . 一.GPUImage 在之前的文章中,我们对 GPUImage 已经有了一定的了解.之前一般使用它对摄像头采集的图像数据进行处理,然而,它对本地视频的处理也一样方便. 直接看代码: // movie NSString *path = [[NSBundle mainBundle] pathForResource:@"sample&q

  • iOS中Navbar设置渐变色效果的方法示例

    本文主要给大家介绍了关于iOS中Navbar设置渐变色效果的相关内容,分享出来供大家参考学习,下面来看看详细的介绍吧. 设置渐变色 #import "NavigationViewController.h" #define LBColor(r, g, b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1.0] @interface NavigationViewController () @end

  • iOS中设置圆角的几种方法示例

    前言 圆角(RounderCorner)是一种很常见的视图效果,相比于直角,它更加柔和优美,易于接受.但很多人并不清楚如何设置圆角的正确方式和原理.设置圆角会带来一定的性能损耗,如何提高性能是另一个需要重点讨论的话题.我查阅了一些现有的资料,收获良多的同时也发现了一些误导人错误. 1. 使用layer属性 layer.backgroundColor = [UIColor cyanColor].CGColor; // 给图层添加背景色 layer.contents = (id)[UIImage i

  • iOS中精确计算WebView高度的方法示例

    前言 在开发app的过程中难免会遇到将webView加载到tableView的cell上的需求,一般遇到这种问题,通常想到的思路就是在webView的回调方法webViewDidFinishLoad中获取到webView的高度,刷新tableView,将高度赋值给tableView的回调方法heightForRow.看似没有任何问题,但是在实际操作的时候却发现得到的高度并不是webView的实际高度,显然这种方法是行不通的.其实并不是方法不行,而是webViewDidFinishLoad代理方法

  • iOS中金额字符串格式化显示的方法示例

    前言 由于项目中很多地方展现统计金额, 比如在一些金融类的 App 中,对于表示金额类的字符串,通常需要进行格式化后再显示出来.例如: 0 –> 0.00 123 –> 123.00 123.456 –> 123.46 102000 –> 102,000.00 10204500 –> 10,204,500.00 它的规则如下: 个位数起每隔三位数字添加一个逗号,同时保留两位小数,也称为"千分位格式". 我们一开始采取了一种比较笨拙的处理方式如下: 首先根据

  • iOS中UIActionSheet动态添加按钮

    一,效果图. 二,代码. RootViewController.h #import <UIKit/UIKit.h> @interface RootViewController : UIViewController <UIActionSheetDelegate> @end RootViewController.m //点击任何处,弹出UIActionSheet -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)even

  • iOS中常见的几种加密方法总结

    前言 在我们日常开发中,加密是必不可少的一部分,而普通加密方法是讲密码进行加密后保存到用户偏好设置中,钥匙串是以明文形式保存,但是不知道存放的具体位置,下面本文将详细给大家介绍iOS中常见的几种加密方法,下面话不多说了,来一起看看详细的介绍吧. 一. base64加密 base64 编码是现代密码学的基础 基本原理: 原本是 8个bit 一组表示数据,改为 6个bit一组表示数据,不足的部分补零,每 两个0 用 一个 = 表示 用base64 编码之后,数据长度会变大,增加了大约 1/3 左右.

  • android实现http中请求访问添加cookie的方法

    本文实例讲述了android实现http中请求访问添加cookie的方法.分享给大家供大家参考,具体如下: 第一种 HashMap<String, String> map = new HashMap<String, String>(); map.put("cookie","p1u_id=4eb591e73554db0f4d3300cb656113abfb968ef6b0ee2b5de0a35caa5217c51faa028b453576b35c&quo

  • IOS中对Url进行编码和解码示例

    本文主要介绍IOS中对Url进行编码和解码示例,具体如下: 1.非ARC模式下 + (NSString *)encodeToPercentEscapeString: (NSString *) input {    // Encode all the reserved characters, per RFC 3986    // () NSString *outputStr = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAlloc

  • javascript的document中的动态添加标签实现方法

    document的高级篇中提供了节点操作的函数,具体包括:获取节点,改变节点,删除节点,替换节点,创建节点,添加节点,克隆节点等函数.我们可以利用这些函数动态改变html的节点. 1.JavaScript <script type="text/javascript"> function test1(){//对个节点的ID相同时候的情况 var myhref = document.getElementById('same'); window.alert(myhref.inne

随机推荐