今天为你们介绍一下 iOS 下 WebRTC是如何渲染视频的。在iOS中有两种加速渲染视频的方法。一种是使用OpenGL;另外一种是使用 Metal。bash
OpenGL的好处是跨平台,推出时间比较长,所以比较稳定。兼容性也比较好。而Metal是iOS最近才推出的技术,理论上来讲比OpenGL ES效率更高。app
WebRTC中这两种渲染方式都支持。它首先会判断当前iOS系统是否支持Metal,若是支持的话,优先使用Metal。若是不支持的话,就使用 OpenGL ES。框架
咱们今天介绍的是 OpenGL ES的方案。ide
在iOS中使用OpenGL ES作视频渲染时,首先要建立EAGLContext对象。这是由于,EAGLContext管理着 OpengGL ES 渲染上下文。该上下文中,包括了状态信息,渲染命令以及OpenGL ES绘制资源(如纹理和renderbuffers)。为了执行OpenGL ES命令,你须要将建立的EAGLContext设置为当前渲染上下文。函数
EAGLContext并不直接管理绘制资源,它经过与上下文相关的EAGLSharegroup对象来管理。当建立EAGLContext时,你能够选择建立一个新的sharegroup或与以前建立的EAGLContext共享EAGLSharegroup。ui
EAGLContext与EAGLSharegroup的关系以下图所示:spa
WebRTC中并无使用共享EAGLSharegroup的状况,因此对于这种状况咱们这里就不作特别讲解了。有兴趣的同窗能够在网上查找相关资料。3d
目前,OpenGL ES有3个版本,主要使用版本2和版本3 。因此咱们在建立时要对其做判断。首先看是否支持版本3,若是不支持咱们就使用版本2。代理
代码以下:code
//首先使用版本3,若是不支持则使用版本2
EAGLContext *glContext =
[[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
if (!glContext) {
glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
}
if (!glContext) {
RTCLogError(@"Failed to create EAGLContext");
return NO;
}
复制代码
建立完上下文后,咱们还要将它设置为当前上下文,这样它才能真正起做用。
代码以下:
//若是当前上下文不是OpenGL上下文,则将OpenGL上下文设置为当前上下文。
if ([EAGLContext currentContext] != _glContext) {
[EAGLContext setCurrentContext:_glContext];
}
复制代码
须要注意的是,因为应用切换到后台后,上下文就发生了切换。因此当它切换到前台时,也要作上面那个判断。
OpenGL ES上下文建立好后,下面咱们看一下如何建立View。
在iOS中,有两种展现层,一种是 GLKView,另外一种是 CAEAGLLayer。WebRTC中使用GLKView进行展现。CAEAGLLayer暂不作介绍。
GLKit框架提供了View和View Controller类以减小创建和维护绘制 OpenGL ES
内容的代码。GLKView类用于管理展现部分;GLKViewController类用于管理绘制的内容。它们都是继承自UIKit。GLKView的好处是,开发人员能够将本身的精力聚焦在OpenGL ES渲染的工做上。
GLKView展现的基本流程以下:
如上图所示,绘制 OpenGL ES
内容有三步:
OpenGL ES
环境;GLKView类本身实现了第一步和第三步。第二步由开发人员来完成,也就是要实现drawRect函数。GLKView之因此能为OpenGL ES
提供简单的绘制接口,是由于它管理了OpenGL ES
渲染过程的标准部分:
在调用绘制方法以前:
在绘制方法返回以后:
使用GLKView有两种方法,一种是实现一个类,直接继承自GLKView,并实现drawRect方法。另外一种是实现GLKView的代理,也就是GLKViewDelegate,并实现drawInRect方法。
在WebRTC中,使用的是第二种方法。RTCEAGLVideoView 是GLKView的包裹类,而且继承自GLKViewDelegate。
首先,建立GLKView.
// GLKView manages a framebuffer for us.
//建立GLKView,在建立时,就将 EAGLContext 设置好。
_glkView = [[GLKView alloc] initWithFrame:CGRectZero
context:_glContext];
_glkView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
_glkView.drawableDepthFormat = GLKViewDrawableDepthFormatNone;
_glkView.drawableStencilFormat = GLKViewDrawableStencilFormatNone;
_glkView.drawableMultisample = GLKViewDrawableMultisampleNone;
//设置GLKView的delegate
_glkView.delegate = self;
_glkView.layer.masksToBounds = YES;
//将该值设置为NO,这样咱们就能够本身控制OpenGL的展现了
_glkView.enableSetNeedsDisplay = NO;
[self addSubview:_glkView];
复制代码
建立好GLKView后,须要将glkView.delegate设置为RTCEAGLVideoView,这样就能够将绘制工做交由RTCEAGLVideoView来完成了。另外,glkView.enableSetNeedsDisplay 设置为 NO,由咱们本身来控制什么时候进行绘制。
而后,实现drawInRect方法。
...
if (!_nv12TextureCache) {
_nv12TextureCache = [[RTCNV12TextureCache alloc] initWithContext:_glContext];
}
if (_nv12TextureCache) {
[_nv12TextureCache uploadFrameToTextures:frame];
[_shader applyShadingForFrameWithWidth:frame.width
height:frame.height
rotation:frame.rotation
yPlane:_nv12TextureCache.yTexture
uvPlane:_nv12TextureCache.uvTexture];
[_nv12TextureCache releaseTextures];
}
...
复制代码
上面的代码就是经过Shader来绘制NV12的YUV数据到View中。这段代码的基本意思是将一个解码后的视频帧分解成Y数据纹理,UV数据纹理。而后调用Shader程序将纹理转成rgb数据,最终渲染到View中。
OpenGL ES 有两种 Shader。一种是顶点(Vetex)Shader; 另外一种是片元(fragment )Shader。
Vetex Shader
Vetex Shader用于绘制图形的顶点。咱们都知道,不管是2D仍是3D图形,它们都是由顶点构成的。
在OpenGL ES中,有三种基本图元,分别是点,线,三角形。由它们再构成更复杂的图形。而点、线、三角形又都是由点组成的。
视频是在一个矩形里显示,因此咱们要经过基本图元构建一个矩形。理论上,距形能够经过点、线绘制出来,但这样作的话,OpenGL ES就要绘制四次。而经过三角形绘制只须要两次,因此使用三角形执行速度更快。
下面的代码就是 WebRTC 中的Vetex Shader程序。该程序的做用是每一个顶点执行一次,将用户输入的顶点输出到 gl_Position中,并将顶点的纹理做标点转做为 Fragment Shader 的输入。
- OpenGL坐标原点是屏幕的中心。纹理坐标的原点是左下角。
- gl_Position是Shader的内部变量,存放一个项点的坐标。
// Vertex shader doesn't do anything except pass coordinates through. const char kRTCVertexShaderSource[] = SHADER_VERSION VERTEX_SHADER_IN " vec2 position;\n" VERTEX_SHADER_IN " vec2 texcoord;\n" VERTEX_SHADER_OUT " vec2 v_texcoord;\n" "void main() {\n" " gl_Position = vec4(position.x, position.y, 0.0, 1.0);\n" " v_texcoord = texcoord;\n" "}\n"; 复制代码
OpenGL ES Shader语法请见个人另外一篇文章着色器
fragment Shader
fragment Shader程序是对片元着色,每一个片元执行一次。片元与像素差很少。能够简单的把片元理解为像素。
下面的代码是WebRTC中的 fragment Shader程序。WebRTC收到远端传来的H264视频帧后,解码成YUV数据。以后,对YUV数据进行分解,如移动端使用的YUV数据格式为NV12, 因此就被分红了两部分,一部分是Y数据纹理,另外一部分是UV数据纹理。
YUV有多种格式,能够参见个人另外一篇文章YUV。
在代码中,使用FRAGMENT_SHADER_TEXTURE命令,也就是OpenGL ES中的 texture2D 函数,分别从 Y 数据纹理中取出 y值,从 UV 数据纹理中取出 uv值,而后经过公式计算出每一个像素(实际是片元)的 rgb值。
static const char kNV12FragmentShaderSource[] =
SHADER_VERSION
"precision mediump float;"
FRAGMENT_SHADER_IN " vec2 v_texcoord;\n"
"uniform lowp sampler2D s_textureY;\n"
"uniform lowp sampler2D s_textureUV;\n"
FRAGMENT_SHADER_OUT
"void main() {\n"
" mediump float y;\n"
" mediump vec2 uv;\n"
" y = " FRAGMENT_SHADER_TEXTURE "(s_textureY, v_texcoord).r;\n"
" uv = " FRAGMENT_SHADER_TEXTURE "(s_textureUV, v_texcoord).ra -\n"
" vec2(0.5, 0.5);\n"
" " FRAGMENT_SHADER_COLOR " = vec4(y + 1.403 * uv.y,\n"
" y - 0.344 * uv.x - 0.714 * uv.y,\n"
" y + 1.770 * uv.x,\n"
" 1.0);\n"
" }\n";
复制代码
有了顶点数据和片元的RGB值后,就能够调用OpenGL ES的 draw 方法进行视频的绘制了。
上面介绍了 WebRTC下 Vetex Shader 和 Fragment Shader程序。要想让程序运行起来,还要额外作一些工做。
OpenGL ES的 shader程序与C程序差很少。想像一下C程序,要想让一个C程序运行起来,要有如下几个步骤:
Shader程序的运行也是如此。咱们看看 WebRTC是如何作的。
...
GLuint shader = glCreateShader(type);
if (!shader) {
return 0;
}
glShaderSource(shader, 1, &source, NULL);
glCompileShader(shader);
GLint compileStatus = GL_FALSE;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
if (compileStatus == GL_FALSE) {
...
}
...
复制代码
它首先建立一个 Shader, 而后将上面的 Shader 程序与 Shader 绑定。以后编译 Shader。
...
GLuint program = glCreateProgram();
if (!program) {
return 0;
}
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
GLint linkStatus = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
if (linkStatus == GL_FALSE) {
...
}
...
复制代码
编译成功后,建立 program 对象。 将以前建立的 Shader 与program绑定到一块儿。以后作连接工做。一切准备就绪后,就可使用Shader程序绘制视频了。
...
glUseProgram(_nv12Program);
...
glDrawArrays(GL_TRIANGLE_FAN, 0, 4)
...
复制代码
本文对 WebRTC 中 OpenGL ES 渲染作了介绍。经过本篇文章你们能够了解到WebRTC是如何将视频渲染出来的。包括:
对于 OpenGL ES 是一个至关大的主题,若是没有相应的基础,看本篇文章仍是比较因难的。你们能够参考我前面写的几篇关于 OpenGL 的文章。
谢谢!