iOS基于GPUImage带Alpha的mp4播放方案

mp4本身并不带透明通道,使用普通的视频播放器(比如AVPlayerLayer)只能播放一个不透明的视频,如果我们需要播放带透明度的视频(比如直播中的大礼物动画),就需要自己去写一个视频播放器。

原理

基于OPENGLES的渲染流程,我们拿到视频每一帧的数据,即每个像素的RGB值,然后再拿到该像素点对应的Alpha值,组成一个新的RGBA,通过shader渲染成新的视频。这里的关键点在怎么将每个像素的Alpha值传给shader,这里采用的方式是在导出视频的时候导出两个视频,一个正常导出,另一个只导出透明度,同时为了避免取值的时候不同步,再将这两个视频合成为一个视频。最后播放的视频可能长下面这样:

实现

GPUImage提供了很多的Filter,但是都不太满足这里的需求,所有需要自定义一个Filter,核心代码如下:

shader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
NSString *const kGPUImageVideoAlphaShaderString = SHADER_STRING
(
varying highp vec2 textureCoordinate;//这个对应下面的ST坐标,即右边视频的坐标
uniform sampler2D inputImageTexture;//这个是完整视频的纹理
const lowp vec2 leftW = vec2(-0.5,0.0);
void main()
{
//从右边的视频中拿RGB值,从左边的视频中拿Alpha值,然后返回一个新的RGBA.
lowp vec4 rightTextureColor = texture2D(inputImageTexture, textureCoordinate);
lowp vec2 leftTextureCoordinate = leftW + textureCoordinate;
lowp vec4 leftTextureColor = texture2D(inputImageTexture, leftTextureCoordinate);
gl_FragColor = vec4(rightTextureColor.rgb,leftTextureColor.r);
}
);
opengl相关设置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates;
{
static const GLfloat posVertices[] = {
// x , y
-1, 1,
1, 1,
-1, -1,
1, -1,
};
static const GLfloat textVertices[] = {
// s , t
0.5, 1.0,
1.0, 1.0,
0.5, 0.0,
1.0, 0.0
};
// 向缓冲区对象写入刚定义的顶点数据
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// glBlendFunc(GL_ONE, GL_ZERO);
[super renderToTextureWithVertices:posVertices textureCoordinates:textVertices];
glDisable(GL_BLEND);
}

ST坐标这里,我们只取右边的部分进行展示,然后就是需要设置GL_BLEND和glBlendFunc的透明度混合模式,这里的模式有很多种,每个模式的具体含义参考这篇文章

播放

播放的代码比较简单,可以参考下面的demo,最后的播放效果大概是这样:

demo

总结

这个方案需要对opengl和shader有一些了解,比如顶点坐标系和纹理坐标系之间如何映射,shader中如何获取每个像素点的RGB值,包括向量的偏移计算,shader的变量类型如何声明等等。。。使用视频播放大礼物比直接用序列帧播放在性能和内存占用上都有明显的优势,应该算一种不错的优化方案了。之前我在iOS性能优化中提起过,做性能优化需要程序员本身有一定的技术积累,所以,程序员学习的脚步不能停止,不知道哪天你现在学的东西就会体现出它的价值。