回顾
GPUImage源码解析、图片模糊、视频滤镜、视频水印都已经介绍过,这次带来的是给视频添加文字水印、动态图像水印。
效果展示
“我是水印”的文字,还有心形气泡组成的水印。

处理中的动态图,上面是进度,下面是文字水印:“我是水印”,动态图像水印:心形气泡。

核心思路
- 1、UIView上面有UILabel(文字水印)和UIImageView(图片水印),再通过GPUImageUIElement把UIView对象转换成纹理对象,进入响应链;
- 2、视频文件的图像数据通过GPUImageMovie进入响应链;
- 3、GPUImageDissolveBlenderFilter合并水印图像和视频,把数据传给响应链的终点GPUImageView以显示到UI和GPUImageMovieWriter以写入临时文件;
- 4、视频文件的音频数据通过GPUImageMovie传给GPUImageMovieWriter以写入临时文件;
-
5、最后临时文件通过ALAssetsLibrary写入系统库。
具体细节
1、GPUImageUIElement
GPUImageUIElement继承GPUImageOutput类,作为响应链的源头。通过CoreGraphics把UIView渲染到图像,并通过glTexImage2D绑定到outputFramebuffer指定的纹理,最后通知targets纹理就绪。
2、GPUImageOutput和GPUImageFilter
本次demo主要用到了frameProcessingCompletionBlock属性,当GPUImageFilter渲染完纹理后,会调用frameProcessingCompletionBlock回调。
3、响应链解析
- 1、当GPUImageMovie的纹理就绪时,会通知GPUImageFilter处理图像;
- 2、GPUImageFilter会调用frameProcessingCompletionBlock回调;
- 3、GPUImageUIElement在回调中渲染图像,纹理就绪后通知
GPUImageDissolveBlendFilter; - 4、frameProcessingCompletionBlock回调结束后,通知
GPUImageDissolveBlendFilter纹理就绪; -
5、GPUImageDissolveBlendFilter收到两个纹理后开始渲染,纹理就绪后通知GPUImageMovieWriter;
如图
总结
本篇的内容与上一篇视频水印有类似的地方。GPUImageUIElement是新的知识点,但是如果对CoreGraphics和OpenGL ES熟悉可以秒懂。
附上代码
思考题
思考1:响应链解析中的GPUImageFilter有什么作用?是否可以去掉?
思考2:frameProcessingCompletionBlock里面需要做什么样的操作?为什么?
思考3:能否对图像水印进行复杂的位置变换?
答案
思考1:目的是每帧回调;去掉会导致图像无法显示。
思考2:回调需要调用update操作;因为update只会输出一次纹理信息,只适用于一帧。
思考3:在回调中对UIView进行操作即可;或者使用GPUImageTransformFilter。
网友评论
Showing Recent Messages
/Users/will/Library/Developer/Xcode/DerivedData/LearnOpenGLESWithGPUImage-emtldcpktwiegbasnftesypspzac/Build/Intermediates.noindex/GPUImage.build/Debug-iphoneos/Documentation.build/Script-BC552B3A1558C6FC001F3FFA.sh: line 5: /usr/local/bin/appledoc: No such file or directory
glsl可以公用。
runAsynchronouslyOnVideoProcessingQueue(^{
[weakOverlay update];
});
这样写好后水印贴上去了视频奔溃在这里
void runSynchronouslyOnVideoProcessingQueue(void (^block)(void))
{
dispatch_queue_t videoProcessingQueue = [GPUImageContext sharedContextQueue];
#if !OS_OBJECT_USE_OBJC
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if (dispatch_get_current_queue() == videoProcessingQueue)
#pragma clang diagnostic pop
#else
if (dispatch_get_specific([GPUImageContext contextKey]))
#endif
{
block();
}
else
{
dispatch_sync(videoProcessingQueue, block); //这个地方奔溃
}
}
不知所以
if (size.width > 0 && size.height > 0)
{
_self.capImageView.frame = CGRectMake(rect.origin.x + (rect.size.width - size.width)/2, rect.origin.y - size.height, 160, 160);
_self.capImageView.layer.transform = CATransform3DMakeRotation(-M_PI*(strongSelf.faceAnagle/180), 0, 0, 1);
}
[weakUIElementInput updateWithTimestamp:time];
但是View确实发生想要的偏转但是View的宽高却发生了改变,这个应该咋办捏
_movieFile = [[GPUImageMovie alloc] initWithURL:videoURL];
[_movieFile addTarget:_filterGroup]; // _filterGroup 里面有一些其他的滤镜
GPUImageUIElement *uiElement = [[GPUImageUIElement alloc] initWithView:testLabel];
GPUImageAlphaBlendFilter *blendFilter = [[GPUImageAlphaBlendFilter alloc] init];
blendFilter.mix = 1.0f;
[uiElement addTarget:blendFilter];
[_filterGroup addTarget:blendFilter];
[blendFilter addTarget:_movieWriter]; // _movieWriter 是往文件里写的实例
还有些其他代码没贴 现在出来的视频总是空白的 但是把加ui元素那块代码去掉,滤镜组里的滤镜效果就出来了
NSURL *sampleURL = [[NSBundle mainBundle] URLForResource:@"abc" withExtension:@"mp4"];
AVAsset *asset = [AVAsset assetWithURL:sampleURL];
CGSize size = self.view.bounds.size;
movieFile = [[GPUImageMovie alloc] initWithAsset:asset];
movieFile.runBenchmark = YES;
movieFile.playAtActualSpeed = YES;
// 水印
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
label.text = @"我是水印";
label.font = [UIFont systemFontOfSize:30];
label.textColor = [UIColor redColor];
[label sizeToFit];
UIImage *image = [UIImage imageNamed:@"watermark.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
UIView *subView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
subView.backgroundColor = [UIColor clearColor];
imageView.center = CGPointMake(subView.bounds.size.width / 2, subView.bounds.size.height / 2);
[subView addSubview:imageView];
[subView addSubview:label];
GPUImageUIElement *uielement = [[GPUImageUIElement alloc] initWithView:subView];
这里,GPUImage的 blend filter 有局限性,例如你DEMO的代码,水印subView size 跟视频的size是一样,,那如果size不一样而且宽高比也不一样,就会出问题了。
GPUImage的 需要高级点的 blend filter,,干脆叫 MCU filter,哈哈,可以支持多个输入画面布局变动
2、GPUImageFilter会调用frameProcessingCompletionBlock回调;
3、GPUImageUIElement在回调中渲染图像,纹理就绪后通知
GPUImageDissolveBlendFilter;
// __unsafe_unretained GPUImageUIElement *weakOverlay = overlay;
// [dummyFilter setFrameProcessingCompletionBlock:^(GPUImageOutput *filter, CMTime frameTime){
// [weakOverlay update];
// }];
但是这样会导致CPU变高,另外考虑到水印(可能是静态图像可能是动态的gif),摄像头输出是的帧率比较高30+,所以setFrameProcessingCompletionBlock会被回调很多次,后来我修改注释掉,而是水印需要更新的时候才去update
runAsynchronouslyOnVideoProcessingQueue(^{
[overlay update];
});
[blendFilter disableSecondFrameCheck];就可以,这样不检查就一直用上一次的画面。
另外在外面update 有个坑,见:https://github.com/BradLarson/GPUImage/issues/2211
后面回复我有贴到代码