在许多视频开发的项目中,需要我们自己对视频数据进行渲染,我们可以使用系统的框架进行渲染,我们也可以使用OpenGL进行渲染。我们在项目中使用的是OpenGL进行的渲染,总结一下渲染过程,后附demo代码。
1、创建UIView子类
使用opengles时,我们使用一个UIView作为载体,对opengles进行控制和加载数据的媒介。
我们自定义一个UIView的子类。
在iOS中使用opengles时,需要和CAEAGLLayer进行绑定使用,我们可以自定义我们的View的Layer的类型,代码如下:
+ (Class)layerClass {
return [CAEAGLLayer class];
}
我们在创建UIView的时候,UIView的layer则为CAEAGLLayer类型。
2、设置layer属性
我们需要设置layer的颜色属性,只有两个属性可以设置,代码如下:
//设置layer属性
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
NSDictionary *dict = @{kEAGLDrawablePropertyRetainedBacking:@(NO),
kEAGLDrawablePropertyColorFormat:kEAGLColorFormatRGBA8
};
[eaglLayer setDrawableProperties:dict];
3、设置OpenGL上下文
在iOS中,我们需要创建一个渲染的上下文环境,并绑定到当前线程。代码如下:
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (self.context == nil) {
NSLog(@"create context failed!");
return;
}
BOOL result = [EAGLContext setCurrentContext:self.context];
if (result == NO) {
NSLog(@"set context failed!");
}
4、设置GPU着色器程序
我们以常见的YUV数据GPU着色器程序为例,当我们需要渲染YUV数据到屏幕上时,需要经过显卡的处理,也就是GPU程序的处理,然后提交到显示的缓冲中去进行显示。我们需要把数据转换成可以显示的数据。我们的GPU着色器程序需要我们自己编写。GPU分为顶点着色器程序和纹理着色器程序。我们创建两个文件分别写入着色器代码,顶点着色器文件名命名为:vertexshader_YUV.vtsd,纹理着色器文件名为:fragmentshader_YUV.fmsd。可自定义,与程序中的文件名相对应即可。
顶点着色器程序文件代码如下:
attribute vec4 position;
attribute vec2 vTexCords;
varying vec2 yuvTexCoord;
void main() {
gl_Position=position;
yuvTexCoord=vTexCords;
}
纹理着色器程序代码如下:
precision highp float;
varying highp vec2 yuvTexCoord;
uniform sampler2D s_texture_y;
uniform sampler2D s_texture_u;
uniform sampler2D s_texture_v;
void main() {
highp float y = texture2D(s_texture_y,yuvTexCoord).r;
highp float u = texture2D(s_texture_u,yuvTexCoord).r - 0.5;
highp float v = texture2D(s_texture_v,yuvTexCoord).r - 0.5;
highp float r = y + 1.402 * v;
highp float g = y - 0.344 * u - 0.714 * v;
highp float b = y + 1.772 * u;
gl_FragColor=vec4(r,g,b,1.0);
}
GPU程序的使用流程:编译程序->链接程序->使用程序->绑定变量;
代码如下:
- (void)setupYUVGPUProgram {
//编译顶点着色器、纹理着色器
GLuint vertexShader = [self compileShader:@"vertexshader_YUV.vtsd" withType:GL_VERTEX_SHADER];
GLuint fragmentShader = [self compileShader:@"fragmentshader_YUV.fmsd" withType:GL_FRAGMENT_SHADER];
//绑定链接程序
GLuint programHandle = glCreateProgram();
glAttachShader(programHandle, vertexShader);
glAttachShader(programHandle, fragmentShader);
glLinkProgram(programHandle);
GLint linkSuccess;
glGetProgramiv(programHandle, GL_LINK_STATUS, &linkSuccess);
if (linkSuccess == GL_FALSE) {
GLchar message[256];
glGetProgramInfoLog(programHandle, sizeof(message), 0, &message[0]);
NSString *messageStr = [NSString stringWithUTF8String:message];
NSLog(@"%@", messageStr);
return;
}
//使用程序
glUseProgram(programHandle);
self.mYUVGLProgId = programHandle;
//绑定变量
_mYUVGLPosition = glGetAttribLocation(programHandle, "position");
glEnableVertexAttribArray(_mYUVGLPosition);
_mYUVGLTextureCoords = glGetAttribLocation(programHandle, "vTexCords");
glEnableVertexAttribArray(_mYUVGLTextureCoords);
_s_texture_y = glGetUniformLocation(programHandle, "s_texture_y");
_s_texture_u = glGetUniformLocation(programHandle, "s_texture_u");
_s_texture_v = glGetUniformLocation(programHandle, "s_texture_v");
glUniform1i(_s_texture_y, 0);
glUniform1i(_s_texture_u, 1);
glUniform1i(_s_texture_v, 2);
}
- (GLuint)compileShader:(NSString *)shaderName withType:(GLenum)shaderType {
NSString *shaderPath = [[NSBundle mainBundle] pathForResource:shaderName ofType:nil];
NSError *error;
NSString *shaderString = [NSString stringWithContentsOfFile:shaderPath encoding:NSUTF8StringEncoding error:&error];
if (!shaderString)
{
NSLog(@"Error loading shader: %@", error.localizedDescription);
return 0;
}
// create ID for shader
GLuint shaderHandle = glCreateShader(shaderType);
// define shader text
const char * shaderStringUTF8 = [shaderString UTF8String];
int shaderStringLength = (int)[shaderString length];
glShaderSource(shaderHandle, 1, &shaderStringUTF8, &shaderStringLength);
// compile shader
glCompileShader(shaderHandle);
// verify the compiling
GLint compileSucess;
glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSucess);
if (compileSucess == GL_FALSE)
{
GLchar message[256];
glGetShaderInfoLog(shaderHandle, sizeof(message), 0, &message[0]);
NSString *messageStr = [NSString stringWithUTF8String:message];
NSLog(@"----%@", messageStr);
return 0;
}
return shaderHandle;
}
5、创建绑定framebuffer和renderbuffer缓冲区
我们需要预先创建framebuffer和renderbuffer缓冲区,准备渲染数据,代码如下:
- (void)setupBuffers {
//创建帧缓冲区
glGenFramebuffers(1, &_frameBuffer);
//绑定缓冲区
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
//创建绘制缓冲区
glGenRenderbuffers(1, &_renderBuffer);
//绑定缓冲区
glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
//为绘制缓冲区分配内存
[self.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];
//获取绘制缓冲区像素高度/宽度
GLint width;
GLint height;
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);
self.viewWidth = width;
self.viewHeight = height;
//将绘制缓冲区绑定到帧缓冲区
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
//检查状态
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
NSLog(@"failed to make complete frame buffer object!");
return;
}
GLenum glError = glGetError();
if (GL_NO_ERROR != glError) {
NSLog(@"failed to setup GL %x", glError);
}
}
6、渲染YUV数据
渲染YUV数据流程:设置当前OpenGL上下文到当前线程->删除已经存在的纹理数据->清屏->创建绑定YUV纹理对象->设置渲染的范围->绑定YUV纹理的坐标数据->绘制当前YUV纹理数据->提交当前缓冲进行渲染->删除已使用的纹理,解绑纹理。
每个线程的OpenGL上下文是需要绑定到当前渲染线程的,不然会崩溃。如果已经不在使用的纹理不进行删除会导致内存泄漏。具体代码如下:
- (void)loadYUV420PDataWithYData:(NSData *)yData uData:(NSData *)uData vData:(NSData *)vData width:(NSInteger)width height:(NSInteger)height {
dispatch_sync(self.openglesQueue, ^{
BOOL result = [EAGLContext setCurrentContext:self.context];
if (result == NO) {
NSLog(@"set context failed!");
}
if (self.ytexture) {
glDeleteTextures(1, &_ytexture);
}
if (self.utexture) {
glDeleteTextures(1, &_utexture);
}
if (self.vtexture) {
glDeleteTextures(1, &_vtexture);
}
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
//创建纹理
[self createTexWithYUVDataWithYData:yData uData:uData vData:vData width:(int)width height:(int)height];
//调整画面宽度
CGFloat x = 0;
CGFloat y = 0;
CGFloat w = 0;
CGFloat h = 0;
//获取控件宽高比,与视频宽高比
if (self.viewWidth / self.viewHeight * 1.0 > width / height) {
h = self.viewHeight;
w = width * h / height;
x = (self.viewWidth - w) / 2;
glViewport(x, y, w, h);
}else {
w = self.viewWidth;
h = height * w / width;
y = (self.viewHeight - h) / 2;
glViewport(x, y, w, h);
}
//设置物体坐标
GLfloat vertices[] = {
-1.0,-1.0,
1.0,-1.0,
-1.0,1.0,
1.0,1.0
};
// glEnableVertexAttribArray(_mYUVGLPosition);
glVertexAttribPointer(_mYUVGLPosition, 2, GL_FLOAT, 0, 0, vertices);
//设置纹理坐标
GLfloat texCoords2[] = {
0,1,
1,1,
0,0,
1,0
};
// glEnableVertexAttribArray(_mYUVGLTextureCoords);
glVertexAttribPointer(_mYUVGLTextureCoords, 2, GL_FLOAT, 0, 0, texCoords2);
//执行绘制操作
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
[self.context presentRenderbuffer:GL_RENDERBUFFER];
//删除不使用纹理
glDeleteTextures(1, &_ytexture);
glDeleteTextures(1, &_utexture);
glDeleteTextures(1, &_vtexture);
//解绑纹理
glBindTexture(GL_TEXTURE_2D, 0);
});
}
- (void)createTexWithYUVDataWithYData:(NSData *)YData uData:(NSData *)uData vData:(NSData *)vData width:(int)width height:(int)height {
void *ydata = (void *)[YData bytes];
//传递纹理对象
//创建纹理
glActiveTexture(GL_TEXTURE0);
glGenTextures(1, &_ytexture);
//绑定纹理
glBindTexture(GL_TEXTURE_2D, _ytexture);
[self createYUVTextureWithData:ydata width:width height:height texture:&_ytexture];
void *udata = (void *)[uData bytes];
//创建纹理
glActiveTexture(GL_TEXTURE1);
glGenTextures(1, &_utexture);
//绑定纹理
glBindTexture(GL_TEXTURE_2D, _utexture);
[self createYUVTextureWithData:udata width:width / 2 height:height / 2 texture:&_utexture];
void *vdata = (void *)[vData bytes];
//创建纹理
glActiveTexture(GL_TEXTURE2);
glGenTextures(1, &_vtexture);
//绑定纹理
glBindTexture(GL_TEXTURE_2D, _vtexture);
[self createYUVTextureWithData:vdata width:width / 2 height:height / 2 texture:&_vtexture];
if (!_ytexture || !_ytexture || !_vtexture)
{
NSLog(@"glGenTextures faild.");
return;
}
}
- (void)createYUVTextureWithData:(void *)data width:(int)width height:(int)height texture:(GLuint *)texture {
//设置过滤参数
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
//设置映射规则
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
}
7、使用完毕内存释放
在我们不需要使用OpenGL渲染时,我们需要释放OpenGL相关的内存,我们可以在View的dealloc里面进行释放,也可以自定义其他方法在有需要的时候进行释放,需要解除OpenGLES上下文、删除GPU程序、删除framebuffers和renderbuffers、删除绑定的纹理内存。代码如下:
- (void)dealloc {
NSLog(@"%@====%s",self,__FUNCTION__);
if(self.context) {
[EAGLContext setCurrentContext:nil];
}
if (self.mYUVGLProgId) {
glDeleteProgram(self.mYUVGLProgId);
}
if (self.mGLProgId) {
glDeleteProgram(self.mGLProgId);
}
if (_frameBuffer) {
glDeleteFramebuffers(1, &_frameBuffer);
}
if (_renderBuffer) {
glDeleteRenderbuffers(1, &_renderBuffer);
}
if (_texture) {
glDeleteTextures(1, &_texture);
}
if (_ytexture) {
glDeleteTextures(1, &_ytexture);
}
if (_utexture) {
glDeleteTextures(1, &_utexture);
}
if (_vtexture) {
glDeleteTextures(1, &_vtexture);
}
}
至此,我们使用OpenGLES渲染的过程已经完成。其中OpenGL部分不是非常详细,如果需要详细了解,可留意讨论,如果需要深入理解,建议学习一下OpenGL相关方面的专业知识,那样就容易理解。
OpenGLES渲染YUV数据的demo地址:https://github.com/XMSECODE/ESCOpenGLESShowYUVDataDemo
OpenGLES渲染RGB图片的demo地址:https://github.com/XMSECODE/ESCOpenGLESShowImageDemo
网友评论