ios opengl 播放 yuv数据
来源:互联网 发布:网络成瘾的原因和影响 编辑:程序博客网 时间:2024/05/18 00:55
android opengl 播放 yuv数据:
http://blog.csdn.net/m0_37677536/article/details/78783267
yuv格式数据是怎么来的呢:
Kr = 0.2126
Kb = 0.0722
从 RGB 到 YUV 转换的定义以下列内容开始:L = Kr * R + Kb * B + (1 – Kr – Kb) * G然后,按照下列方式获得 YUV 值:
Y = floor(2^(M-8) * (219*(L–Z)/S + 16) + 0.5)
U = clip3(0, 2^M-1, floor(2^(M-8) * (112*(B-L) / ((1-Kb)*S) + 128) + 0.5))
V = clip3(0, 2^M-1, floor(2^(M-8) * (112*(R-L) / ((1-Kr)*S) + 128) + 0.5))
就是这么定义计算出来的值,那么要还原为rgb也是通过计算得到,是一种定好的标准,大家不要纠结为什么这么算。
现在我们要说的怎么用显卡给把yuv绘制出来。
要绘制yuv需要opengl的可编程管线技术,也就是把yuv数据传到gpu通过gpu计算得到RGB再绘制出来。计算方法需要我们编写,计算的代码也需要我们传到gpu上去运行。计算的方法就是shader里面的内容。shader就是要在gpu上运行的程序
总之需要这么几个步骤:编写shader->编译shader->链成gpu程序(代码中的program)->分别创建yuv纹理对象->找到yuv纹理对象对应的显卡插槽(也就是要给gpu中运行的纹理对象传数据的地址)->给yuv纹理对象绑定数据->绘图。
下面代码中:
setupYUVTexture 这个方法就是创建纹理对象
loadShader 这个方法就是创建一个gpu程序
更细节的里面注释挺详细的,总体安装上面的步骤去理解就容易懂了。
有完整的播放器,并且有完整源码下载:
http://blog.csdn.net/m0_37677536/article/details/78769362
opengl代码:
glView.h
#import <UIKit/UIKit.h>#import <QuartzCore/QuartzCore.h>#import <OpenGLES/ES2/gl.h>#import <OpenGLES/ES2/glext.h>#import <OpenGLES/EAGL.h>#include <sys/time.h>#import "YUV_GL_DATA.h"@interface OpenglView : UIView{ /** OpenGL绘图上下文 */ EAGLContext *_glContext; /** 帧缓冲区 */ GLuint _framebuffer; /** 渲染缓冲区 */ GLuint _renderBuffer; /** 着色器句柄 */ GLuint _program; /** YUV纹理数组 */ GLuint _textureYUV[3]; /** 视频宽度 */ GLuint _videoW; /** 视频高度 */ GLuint _videoH; GLsizei _viewScale; //void *_pYuvData;#ifdef DEBUG struct timeval _time; NSInteger _frameRate;#endif}#pragma mark - 接口- (void)displayYUV420pData:(H264YUV_Frame *) frame;- (void)setVideoSize:(GLuint)width height:(GLuint)height;/** 清除画面 */- (void)clearFrame;@end
glView.m
//// OpenglView.m// FFmpeg-project//// Created by huizai on 2017/9/22.// Copyright © 2017年 huizai. All rights reserved.//#import "OpenglView.h"enum AttribEnum{ ATTRIB_VERTEX, ATTRIB_TEXTURE, ATTRIB_COLOR,};//YUV数据枚举enum TextureType{ TEXY = 0, TEXU, TEXV, TEXC};@implementation OpenglView#pragma mark - 初始化等操作- (BOOL)doInit{ //用来显示opengl的图形 CAEAGLLayer *eaglLayer = (CAEAGLLayer*) self.layer; //eaglLayer.opaque = YES; //设为不透明 eaglLayer.opaque = YES; eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGB565, kEAGLDrawablePropertyColorFormat, //[NSNumber numberWithBool:YES], kEAGLDrawablePropertyRetainedBacking, nil]; //设置分辨率 self.contentScaleFactor = [UIScreen mainScreen].scale; _viewScale = [UIScreen mainScreen].scale; //创建上下文 _glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; //[self debugGlError]; //上下文创建失败则直接返回no if(!_glContext || ![EAGLContext setCurrentContext:_glContext]) { return NO; } //创建纹理 [self setupYUVTexture]; //加载着色器 [self loadShader]; //像素数据对齐,第二个参数默认为4,一般为1或4 glPixelStorei(GL_UNPACK_ALIGNMENT, 4); //使用着色器 glUseProgram(_program); //获取一致变量的存储位置 GLuint textureUniformY = glGetUniformLocation(_program, "SamplerY"); GLuint textureUniformU = glGetUniformLocation(_program, "SamplerU"); GLuint textureUniformV = glGetUniformLocation(_program, "SamplerV"); //对几个纹理采样器变量进行设置 glUniform1i(textureUniformY, 0); glUniform1i(textureUniformU, 1); glUniform1i(textureUniformV, 2); return YES;}-(instancetype)initWithFrame:(CGRect)frame{ self = [super initWithFrame:frame]; if (self) { //没有初始化成功 if (![self doInit]) { self = nil; } } return self;}-(instancetype)initWithCoder:(NSCoder *)aDecoder{ self = [super initWithCoder:aDecoder]; if (self) { //没有初始化成功 if (![self doInit]) { self = nil; } } return self;}-(void)layoutSubviews{ dispatch_async(dispatch_get_global_queue(0, 0), ^{ //互斥锁 @synchronized (self) { [EAGLContext setCurrentContext:_glContext]; //清除缓冲区 [self destoryFrameAndRenderBuffer]; //创建缓冲区 [self createFrameAndRenderBuffer]; } //把数据显示在这个视窗上 glViewport(1, 1, self.bounds.size.width*_viewScale - 2, self.bounds.size.height*_viewScale - 2); });}#pragma mark - 设置opengl/** 不写的话,设置描绘属性会崩溃 @return <#return value description#> */+ (Class)layerClass{ return [CAEAGLLayer class];}/** 创建缓冲区 @return <#return value description#> */- (BOOL)createFrameAndRenderBuffer{ //创建帧缓冲绑定 glGenFramebuffers(1, &_framebuffer); //创建渲染缓冲 glGenRenderbuffers(1, &_renderBuffer); //将之前用glGenFramebuffers创建的帧缓冲绑定为当前的Framebuffer(绑定到context上?). glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer); //Renderbuffer绑定到context上,此时当前Framebuffer完全由renderbuffer控制 glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer); //分配空间 if (![_glContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer]) { NSLog(@"attach渲染缓冲区失败"); } //这个函数看起来有点复杂,但其实它很好理解的。它要做的全部工作就是把把前面我们生成的深度缓存对像与当前的FBO对像进行绑定,当然我们要注意一个FBO有多个不同绑定点,这里是要绑定在FBO的深度缓冲绑定点上。 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer); //检查当前帧缓存的关联图像和帧缓存参数 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { NSLog(@"创建缓冲区错误 0x%x", glCheckFramebufferStatus(GL_FRAMEBUFFER)); return NO; } return YES;}/** 清除缓冲区 */- (void)destoryFrameAndRenderBuffer{ if (_framebuffer) { //删除FBO glDeleteFramebuffers(1, &_framebuffer); } if (_renderBuffer) { //删除渲染缓冲区 glDeleteRenderbuffers(1, &_renderBuffer); } _framebuffer = 0; _renderBuffer = 0;}/** 创建纹理 */- (void)setupYUVTexture{ if (_textureYUV[TEXY]) { //删除纹理 glDeleteTextures(3, _textureYUV); } //生成纹理 glGenTextures(3, _textureYUV); if (!_textureYUV[TEXY] || !_textureYUV[TEXU] || !_textureYUV[TEXV]) { NSLog(@"<<<<<<<<<<<<纹理创建失败!>>>>>>>>>>>>"); return; } //选择当前活跃单元 glActiveTexture(GL_TEXTURE0); //绑定Y纹理 glBindTexture(GL_TEXTURE_2D, _textureYUV[TEXY]); //纹理过滤函数 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);//垂直方向 glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, _textureYUV[TEXU]); 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); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, _textureYUV[TEXV]); 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);}#define FSH @"varying lowp vec2 TexCoordOut;\\uniform sampler2D SamplerY;\uniform sampler2D SamplerU;\uniform sampler2D SamplerV;\\void main(void)\{\mediump vec3 yuv;\lowp vec3 rgb;\\yuv.x = texture2D(SamplerY, TexCoordOut).r;\yuv.y = texture2D(SamplerU, TexCoordOut).r - 0.5;\yuv.z = texture2D(SamplerV, TexCoordOut).r - 0.5;\\rgb = mat3( 1, 1, 1,\0, -0.39465, 2.03211,\1.13983, -0.58060, 0) * yuv;\\gl_FragColor = vec4(rgb, 1);\\}"#define VSH @"attribute vec4 position;\attribute vec2 TexCoordIn;\varying vec2 TexCoordOut;\\void main(void)\{\gl_Position = position;\TexCoordOut = TexCoordIn;\}"/** 加载着色器 */- (void)loadShader{ /** 1 编译着色 */ GLuint vertexShader = [self compileShader:VSH withType:GL_VERTEX_SHADER]; GLuint fragmentShader = [self compileShader:FSH withType:GL_FRAGMENT_SHADER]; /** 2 */ //创建程序容器 _program = glCreateProgram(); //绑定shader到program glAttachShader(_program, vertexShader); glAttachShader(_program, fragmentShader); /** 绑定需要在link之前 */ //把顶点属性索引绑定到顶点属性名 glBindAttribLocation(_program, ATTRIB_VERTEX, "position"); glBindAttribLocation(_program, ATTRIB_TEXTURE, "TexCoordIn"); //链接 glLinkProgram(_program); /** 3 */ GLint linkSuccess; //查询相关信息,并将数据返回到linkSuccess glGetProgramiv(_program, GL_LINK_STATUS, &linkSuccess); if (linkSuccess == GL_FALSE) { GLchar messages[256]; glGetProgramInfoLog(_program, sizeof(messages), 0, &messages[0]); NSString *messageString = [NSString stringWithUTF8String:messages]; NSLog(@"<<<<着色器连接失败 %@>>>", messageString); //exit(1); } if (vertexShader) /* 释放内存不能立刻删除 If a shader object to be deleted is attached to a program object, it will be flagged for deletion, but it will not be deleted until it is no longer attached to any program object… shader 删除该着色器对象(如果一个着色器对象在删除前已经链接到程序对象中,那么当执行glDeleteShader函数时不会立即被删除,而是该着色器对象将被标记为删除,器内存被释放一次,它不再链接到其他任何程序对象) */ glDeleteShader(vertexShader); if (fragmentShader) glDeleteShader(fragmentShader);}/** 编译着色代码,使用着色器 @param shaderString 代码 @param shaderType 类型 @return 成功返回着色器,失败返回-1 */- (GLuint)compileShader:(NSString*)shaderString withType:(GLenum)shaderType{ /** 1 */ if (!shaderString) { // NSLog(@"Error loading shader: %@", error.localizedDescription); exit(1); } else { //NSLog(@"shader code-->%@", shaderString); } /** 2 分别创建一个顶点着色器对象和一个片段着色器对象 */ GLuint shaderHandle = glCreateShader(shaderType); /** 3 分别将顶点着色程序的源代码字符数组绑定到顶点着色器对象,将片段着色程序的源代码字符数组绑定到片段着色器对象 */ const char * shaderStringUTF8 = [shaderString UTF8String]; int shaderStringLength = (int)[shaderString length]; glShaderSource(shaderHandle, 1, &shaderStringUTF8, &shaderStringLength); /** 4 分别编译顶点着色器对象和片段着色器对象 */ glCompileShader(shaderHandle); /** 5 */ GLint compileSuccess; //获取编译情况 glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess); if (compileSuccess == GL_FALSE) { GLchar messages[256]; glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]); NSString *messageString = [NSString stringWithUTF8String:messages]; NSLog(@"%@", messageString); exit(1); } return shaderHandle;}#pragma mark - 接口/** 设置大小 @param width 界面宽 @param height 界面高 */-(void)setVideoSize:(GLuint)width height:(GLuint)height{ //给宽高赋值 _videoH = height; _videoW = width; //开辟内存空间 //为什么乘1.5而不是1: width * hight =Y(总和) U = Y / 4 V = Y / 4 void *blackData = malloc(width * height * 1.5); if (blackData) { /** 对内存空间清零,作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法 @param __b#> 源数据 description#> @param __c#> 填充数据 description#> @param __len#> 长度 description#> @return <#return value description#> */ memset(blackData, 0x0, width * height * 1.5); } /* Apple平台不允许直接对Surface进行操作.这也就意味着在Apple中,不能通过调用eglSwapBuffers函数来直接实现渲染结果在目标surface上的更新. 在Apple平台中,首先要创建一个EAGLContext对象来替代EGLContext (不能通过eglCreateContext来生成), EAGLContext的作用与EGLContext是类似的. 然后,再创建相应的Framebuffer和Renderbuffer. Framebuffer象一个Renderbuffer集(它可以包含多个Renderbuffer对象). Renderbuffer有三种: color Renderbuffer, depth Renderbuffer, stencil Renderbuffer. 渲染结果是先输出到Framebuffer中,然后通过调用context的presentRenderbuffer,将Framebuffer上的内容提交给之前的CustumView. */ //设置当前上下文 [EAGLContext setCurrentContext:_glContext]; /* target —— 纹理被绑定的目标,它只能取值GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D或者GL_TEXTURE_CUBE_MAP; texture —— 纹理的名称,并且,该纹理的名称在当前的应用中不能被再次使用。 glBindTexture可以让你创建或使用一个已命名的纹理,调用glBindTexture方法,将target设置为GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D或者GL_TEXTURE_CUBE_MAP,并将texture设置为你想要绑定的新纹理的名称,即可将纹理名绑定至当前活动纹理单元目标。当一个纹理与目标绑定时,该目标之前的绑定关系将自动被打破。纹理的名称是一个无符号的整数。在每个纹理目标中,0被保留用以代表默认纹理。纹理名称与相应的纹理内容位于当前GL rendering上下文的共享对象空间中。 */ //绑定Y纹理 glBindTexture(GL_TEXTURE_2D, _textureYUV[TEXY]); /** 根据像素数据,加载纹理 @param target#> 指定目标纹理,这个值必须是GL_TEXTURE_2D。 description#> @param level#> 执行细节级别。0是最基本的图像级别,n表示第N级贴图细化级别 description#> @param internalformat#> 指定纹理中的颜色格式。可选的值有GL_ALPHA,GL_RGB,GL_RGBA,GL_LUMINANCE, GL_LUMINANCE_ALPHA 等几种。 description#> @param width#> 纹理的宽度 description#> @param height#> 高度 description#> @param border#> 纹理的边框宽度,必须为0 description#> @param format#> 像素数据的颜色格式, 不需要和internalformatt取值必须相同。可选的值参考internalformat。 description#> @param type#> 指定像素数据的数据类型。可以使用的值有GL_UNSIGNED_BYTE,GL_UNSIGNED_SHORT_5_6_5,GL_UNSIGNED_SHORT_4_4_4_4,GL_UNSIGNED_SHORT_5_5_5_1等。 description#> @param pixels#> 指定内存中指向图像数据的指针 description#> @return <#return value description#> */ glTexImage2D(GL_TEXTURE_2D, 0, GL_RED_EXT, width, height, 0, GL_RED_EXT, GL_UNSIGNED_BYTE, blackData); //绑定U纹理 glBindTexture(GL_TEXTURE_2D, _textureYUV[TEXU]); //加载纹理 glTexImage2D(GL_TEXTURE_2D, 0, GL_RED_EXT, width/2, height/2, 0, GL_RED_EXT, GL_UNSIGNED_BYTE, blackData + width * height); //绑定V数据 glBindTexture(GL_TEXTURE_2D, _textureYUV[TEXV]); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED_EXT, width/2, height/2, 0, GL_RED_EXT, GL_UNSIGNED_BYTE, blackData + width * height * 5 / 4); //释放malloc分配的内存空间 free(blackData);}/** 清除画面 */-(void)clearFrame{ if ([self window]) { [EAGLContext setCurrentContext:_glContext]; glClearColor(0.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer); [_glContext presentRenderbuffer:GL_RENDERBUFFER]; }}/** 显示YUV数据 */- (void)displayYUV420pData:(H264YUV_Frame *) frame{ if (!self.window) { return; } int w = frame->width; int h = frame->height; //加互斥锁,防止其他线程访问 @synchronized (self) { if (w != _videoW || h != _videoH) { [self setVideoSize:(GLuint)w height:(GLuint)h]; } //设置当前上下文 [EAGLContext setCurrentContext:_glContext]; //绑定 glBindTexture(GL_TEXTURE_2D, _textureYUV[TEXY]); /** 更新纹理 @param target#> 指定目标纹理,这个值必须是GL_TEXTURE_2D。 description#> @param level#> 执行细节级别。0是最基本的图像级别,n表示第N级贴图细化级别 description#> @param xoffset#> 纹理数据的偏移x值 description#> @param yoffset#> 纹理数据的偏移y值 description#> @param width#> 更新到现在的纹理中的纹理数据的规格宽 description#> @param height#> 高 description#> @param format#> 像素数据的颜色格式, 不需要和internalformatt取值必须相同。可选的值参考internalformat。 description#> @param type#> 颜色分量的数据类型 description#> @param pixels#> 指定内存中指向图像数据的指针 description#> @return <#return value description#> */ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, (GLsizei)w, (GLsizei)h, GL_RED_EXT, GL_UNSIGNED_BYTE, frame->luma.dataBuffer); glBindTexture(GL_TEXTURE_2D, _textureYUV[TEXU]); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, (GLsizei)w/2, (GLsizei)h/2, GL_RED_EXT, GL_UNSIGNED_BYTE, frame->chromaB.dataBuffer); glBindTexture(GL_TEXTURE_2D, _textureYUV[TEXV]); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, (GLsizei)w/2, (GLsizei)h/2, GL_RED_EXT, GL_UNSIGNED_BYTE, frame->chromaR.dataBuffer); //渲染 [self render]; }#ifdef DEBUG GLenum err = glGetError(); if (err != GL_NO_ERROR) { printf("GL_ERROR=======>%d\n", err); } struct timeval nowtime; gettimeofday(&nowtime, NULL); if (nowtime.tv_sec != _time.tv_sec) { printf("视频 %ld 帧率: %ld\n", self.tag, (long)_frameRate); memcpy(&_time, &nowtime, sizeof(struct timeval)); _frameRate = 1; } else { _frameRate++; }#endif}/** 渲染 */-(void)render{ //设置上下文 [EAGLContext setCurrentContext:_glContext]; CGSize size = self.bounds.size; //把数据显示在这个视窗上 glViewport(1, 1, size.width * _viewScale -2, size.height * _viewScale -2); /* 我们如果选定(0, 0), (0, 1), (1, 0), (1, 1)四个纹理坐标的点对纹理图像映射的话,就是映射的整个纹理图片。如果我们选择(0, 0), (0, 1), (0.5, 0), (0.5, 1) 四个纹理坐标的点对纹理图像映射的话,就是映射左半边的纹理图片(相当于右半边图片不要了),相当于取了一张320x480的图片。但是有一点需要注意,映射的纹理图片不一定是“矩形”的。实际上可以指定任意形状的纹理坐标进行映射。下面这张图就是映射了一个梯形的纹理到目标物体表面。这也是纹理(Texture)比上一篇文章中记录的表面(Surface)更加灵活的地方。 */ static const GLfloat squareVertices[] = { -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, }; static const GLfloat coordVertices[] = { 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, }; //更新属性值 glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, squareVertices); //开启定点属性数组 glEnableVertexAttribArray(ATTRIB_VERTEX); glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, coordVertices); glEnableVertexAttribArray(ATTRIB_TEXTURE); //绘制 //当采用顶点数组方式绘制图形时,使用该函数。该函数根据顶点数组中的坐标数据和指定的模式,进行绘制。 //绘制方式,从数组的哪一个点开始绘制(一般为0),顶点个数 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); //将该渲染缓冲区对象绑定到管线上 glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer); //把缓冲区(render buffer和color buffer)的颜色呈现到UIView上 [_glContext presentRenderbuffer:GL_RENDERBUFFER];}- (void)dealloc{ NSLog(@"gl dealloc"); [self clearFrame]; if (_glContext) { // _glContext = nil; } [self destoryFrameAndRenderBuffer];}@end
数据结构体.h
#ifndef YUV_GL_DATA_h#define YUV_GL_DATA_h#pragma pack(push, 1)typedef struct H264FrameDef{ unsigned int length; unsigned char* dataBuffer;}H264Frame;typedef struct H264YUVDef{ unsigned int width; unsigned int height; H264Frame luma; H264Frame chromaB; H264Frame chromaR;}H264YUV_Frame;#pragma pack(pop)#endif /* YUV_GL_DATA_h */
- ios opengl 播放 yuv数据
- android opengl 播放 yuv数据
- iOS OpenGL渲染YUV数据
- OpenGL播放yuv视频
- OpenGL播放yuv视频
- OpenGL播放yuv视频
- OpenGL播放yuv视频
- OpenGL播放yuv数据流(着色器SHADER)-IOS(一)
- 利用Qt + OpenGL 渲染 YUV数据,播放视频 mac版
- yuv视频用opengl播放
- OpenGL渲染YUV数据
- SDL播放yuv数据
- iOS 新版FFmpeg+opengl播放yuv+openal 快放 慢放 视频播放器
- c++ OpenGL显示YUV数据
- mplayer 播放yuv,raw数据
- iOS中OpenGL-ES渲染YUV视频
- qt采用opengl显示yuv视频数据
- 使用SDL播放YUV图像数据
- 有没有会使用Imlab源码的
- scrapy命令行详解
- C语言:_I 的解析
- 双线性插值理论与代码实例
- Java中对象的创建、内存分配和销毁
- ios opengl 播放 yuv数据
- pyqt Qlabel显示opencv 自适应大小
- Android Multidex解决类过多的问题
- jenkins运行selenium时不显示浏览器的几种解决思路
- python总结1-2
- SQL truncate 、delete与drop区别,以及一个实际案例
- Mysql join方式的结果集
- OpenGLES2.0基础(4)
- Jumpserver跳板机Web操作教程