博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从零实现简易播放器-2.opengl渲染yuv图像
阅读量:4071 次
发布时间:2019-05-25

本文共 10962 字,大约阅读时间需要 36 分钟。

opengl渲染yuv图像

作者:史正邮箱:shizheng163@126.com如有错误还请及时指正如果有错误的描述给您带来不便还请见谅如需交流请发送邮件,欢迎联系
  • 我的csdn :
  • 我的github :

文章目录

简述

直接渲染Jpeg等编码后的图像(这里使用的是Jpeg图片)是特别慢的(当张图片>40ms), 常用的做法是直接渲染yuv或者rgb的图像。

这里使用的是使用opengl渲染yuv的图像, 当然也可以通过Qt的绘图库来渲染。不过opengl默认的渲染步骤是在gpu上执行的, 而Qt的绘图库是利于cpu进行计算,gpu进行渲染, 占用cpu的资源比较多。

使用opengl渲染yuv图像的时间单帧图像为(0-2]ms

渲染完成后使主线程等待40-渲染时间, 就会呈现出视频的效果。

这一节的完整代码:

这里给出几个ffmpeg将视频分解为jpeg然后再转换为yuv的命令(由于没有找到视频直接分解为yuv图片帧的办法)

  • ffmpeg -i Suger.mp4 -b 3000k -ss 00:00:10 -t 10 SugerFrames/Frame_%04d.jpeg
  • ffmpeg -y -s 1920x1080 -i SugerFrames/yuv_0001.jpeg SugerYuvs/yuv_0001.yuv

由于没有找到直接将多张jpeg图片直接转换为多张yuv图像的方法, 这里写了一个shell脚本来处理

for((i=1;i<=480;i++))do    index=`printf "%04d" $i`    ffmpeg -y -s 1920x1080 -i SugerFrames/yuv_$index.jpeg SugerYuvs/yuv_$index.yuvdone

ffmpeg 查看yuv图像或视频的方法

  • ffplay.exe -video_size 1920x1080 yuv_0001.yuv

使用QOpenGLWidget进行yuv图片渲染

除了可以选择使用QPainter和标准的OpenGL渲染图形,QOpenGLWidget类提供了在Qt应用程序中显示OpenGL图形的功能。

关于QOpenGLWidget的介绍可以参考以下文章:

笔者对opengl基本上没什么了解, 渲染部分选自以下博客,感谢博主

另外glviewport是opengl渲染的图片显示的位置, 坐标原点为左下角。

使用了opengl渲染之后,再使用QPainter渲染(当时只是想使用这个渲染默认的Jpeg图标)就会导致opengl和QPainter渲染的效果无法使用。

解决方法还没有找到, 所以干脆就把默认图片也转成了yuv图像。

.h文件

#ifndef VIDEOGLWIDGET_H#define VIDEOGLWIDGET_H#include 
#include
#include
#include
#include
#include
#include
#include
#include "fileutil.h"class VideoGLWidget : public QOpenGLWidget, protected QOpenGLFunctions{ QOBJECT_Hpublic: VideoGLWidget(QWidget * pParent = 0); virtual ~VideoGLWidget(); void PictureShow(fileutil::PictureFilePtr pPicture); void DefaultPictureShow();protected: void initializeGL(); void resizeGL(int w, int h); void paintGL();private: //绘图是异步动作, 防止绘图过程中,图片被置为NULL,所以需要加锁 std::mutex m_mutexForShowYuvData; bool m_bIsShowVideoIcon; //是否要显示视频默认图标 fileutil::PictureFilePtr m_pDefaultPict;//默认图片 fileutil::PictureFilePtr m_pYuvPictPtr;//当前需要显示的Yuv数据 //显示Opengl渲染后图像位置 QRect m_drawRect; //渲染yuv图像变量 GLuint textureUniformY; //y纹理数据位置 GLuint textureUniformU; //u纹理数据位置 GLuint textureUniformV; //v纹理数据位置 GLuint id_y; //y纹理对象ID GLuint id_u; //u纹理对象ID GLuint id_v; //v纹理对象ID QOpenGLTexture* m_pTextureY; //y纹理对象 QOpenGLTexture* m_pTextureU; //u纹理对象 QOpenGLTexture* m_pTextureV; //v纹理对象 QOpenGLShader * m_pVSHader; //顶点着色器程序对象 QOpenGLShader * m_pFSHader; //片段着色器对象 QOpenGLShaderProgram *m_pShaderProgram; //着色器程序容器};#endif // VIDEOGLWIDGET_H

PicturePtr结构如下

#include 
#include
#include
struct FileRawData{
FileRawData(){
m_pData = NULL; } FileRawData(FileRawData && filedata) {
m_pData = filedata.m_pData; m_uLen = filedata.m_uLen; m_filename = filedata.m_filename; filedata.m_pData = NULL; filedata.m_uLen = 0; filedata.m_filename.clear(); } ~FileRawData() {
if(m_pData) delete m_pData; m_pData = NULL; m_uLen = 0; } uint8_t * m_pData; uint32_t m_uLen; std::string m_filename;};typedef std::shared_ptr
FileRawDataPtr;struct PictureFile: public FileRawData{
enum PictureFormat {
kFormatYuv, kFormatJpeg }; PictureFile(FileRawData & parent, uint32_t nWeight, uint32_t nHeight, PictureFormat pictFormat) :FileRawData(std::move(parent)) ,m_nWeight(nWeight) ,m_nHeight(nHeight) ,m_pictFormat(pictFormat) {
} uint32_t m_nWeight; uint32_t m_nHeight; PictureFormat m_pictFormat;};typedef std::shared_ptr
PictureFilePtr;

cpp文件

#include "videoglwidget.h"#include 
#include
#include
#include
#include
#include "logutil.h"//opengl 渲染部分参考自Csdn博客:https://blog.csdn.net/su_vast/article/details/52214642using namespace fileutil;#define DEFAULT_ICO_VIEW_WIDTH 48#define DEFAULT_ICO_VIEW_HEIGHT 48#define DEFAULT_ICO_PIX_HEIGHT 512#define DEFAULT_ICO_PIX_WIDTH 512VideoGLWidget::VideoGLWidget(QWidget *pParent) :QOpenGLWidget(pParent){
textureUniformY = 0; textureUniformU = 0; textureUniformV = 0; id_y = 0; id_u = 0; id_v = 0; m_pYuvPictPtr = NULL; m_pVSHader = NULL; m_pFSHader = NULL; m_pShaderProgram = NULL; m_pTextureY = NULL; m_pTextureU = NULL; m_pTextureV = NULL; //默认显示渲染位置为默认视频图标位置 m_drawRect = QRect((width() - DEFAULT_ICO_VIEW_WIDTH)/2, height()/2, DEFAULT_ICO_VIEW_WIDTH, DEFAULT_ICO_VIEW_HEIGHT); this->DefaultPictureShow();}VideoGLWidget::~VideoGLWidget(){
if(m_pVSHader) {
delete m_pVSHader; m_pVSHader = NULL; } if(m_pFSHader) {
delete m_pFSHader; m_pFSHader = NULL; } if(m_pShaderProgram) {
delete m_pShaderProgram; m_pShaderProgram = NULL; } if(m_pTextureY) {
delete m_pTextureY; m_pTextureY = NULL; } if(m_pTextureU) {
delete m_pTextureU; m_pTextureU = NULL; } if(m_pTextureV) {
delete m_pTextureV; m_pTextureV = NULL; }}void VideoGLWidget::PictureShow(PictureFilePtr pPicture){
std::unique_lock
locker(m_mutexForShowYuvData); m_pYuvPictPtr = pPicture; m_drawRect = QRect(0, 0, width(), height()); this->update();}void VideoGLWidget::DefaultPictureShow(){ std::unique_lock
locker(m_mutexForShowYuvData); if(!m_pDefaultPict) { FileRawDataPtr pFileData = fileutil::ReadFileRawData("../../images/video.yuv"); m_pDefaultPict = PictureFilePtr(new PictureFile(*pFileData.get(), DEFAULT_ICO_PIX_WIDTH, DEFAULT_ICO_PIX_HEIGHT, PictureFile::kFormatYuv)); } m_pYuvPictPtr = m_pDefaultPict; m_drawRect = QRect((width() - DEFAULT_ICO_VIEW_WIDTH)/2, (height() - DEFAULT_ICO_VIEW_HEIGHT) /2, DEFAULT_ICO_VIEW_WIDTH, DEFAULT_ICO_VIEW_HEIGHT); this->update();}void VideoGLWidget::initializeGL(){ initializeOpenGLFunctions(); glEnable(GL_DEPTH_TEST); //现代opengl渲染管线依赖着色器来处理传入的数据 //着色器:就是使用openGL着色语言(OpenGL Shading Language, GLSL)编写的一个小函数, //GLSL是构成所有OpenGL着色器的语言,具体的GLSL语言的语法需要读者查找相关资料 //初始化顶点着色器 对象 m_pVSHader = new QOpenGLShader(QOpenGLShader::Vertex, this); const char *vsrc ="\ attribute vec4 vertexIn; \ attribute vec2 textureIn; \ varying vec2 textureOut; \ void main(void) \ { \ gl_Position = vertexIn; \ textureOut = textureIn; \ }"; m_pVSHader->compileSourceCode(vsrc); //初始化片段着色器 功能gpu中yuv转换成rgb m_pFSHader = new QOpenGLShader(QOpenGLShader::Fragment, this); //片段着色器源码 const char *fsrc = "varying vec2 textureOut; \ uniform sampler2D tex_y; \ uniform sampler2D tex_u; \ uniform sampler2D tex_v; \ void main(void) \ { \ vec3 yuv; \ vec3 rgb; \ yuv.x = texture2D(tex_y, textureOut).r; \ yuv.y = texture2D(tex_u, textureOut).r - 0.5; \ yuv.z = texture2D(tex_v, textureOut).r - 0.5; \ rgb = mat3( 1, 1, 1, \ 0, -0.39465, 2.03211, \ 1.13983, -0.58060, 0) * yuv; \ gl_FragColor = vec4(rgb, 1); \ }"; m_pFSHader->compileSourceCode(fsrc);#define PROGRAM_VERTEX_ATTRIBUTE 0#define PROGRAM_TEXCOORD_ATTRIBUTE 1#define ATTRIB_VERTEX 3#define ATTRIB_TEXTURE 4 //创建着色器程序容器 m_pShaderProgram = new QOpenGLShaderProgram; //将片段着色器添加到程序容器 m_pShaderProgram->addShader(m_pFSHader); //将顶点着色器添加到程序容器 m_pShaderProgram->addShader(m_pVSHader); //绑定属性vertexIn到指定位置ATTRIB_VERTEX,该属性在顶点着色源码其中有声明 m_pShaderProgram->bindAttributeLocation("vertexIn", ATTRIB_VERTEX); //绑定属性textureIn到指定位置ATTRIB_TEXTURE,该属性在顶点着色源码其中有声明 m_pShaderProgram->bindAttributeLocation("textureIn", ATTRIB_TEXTURE); //链接所有所有添入到的着色器程序 m_pShaderProgram->link(); //激活所有链接 m_pShaderProgram->bind(); //读取着色器中的数据变量tex_y, tex_u, tex_v的位置,这些变量的声明可以在 //片段着色器源码中可以看到 textureUniformY = m_pShaderProgram->uniformLocation("tex_y"); textureUniformU = m_pShaderProgram->uniformLocation("tex_u"); textureUniformV = m_pShaderProgram->uniformLocation("tex_v"); // 顶点矩阵 static const GLfloat vertexVertices[] = { -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, }; //纹理矩阵 static const GLfloat textureVertices[] = { 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, }; //设置属性ATTRIB_VERTEX的顶点矩阵值以及格式 glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, vertexVertices); //设置属性ATTRIB_TEXTURE的纹理矩阵值以及格式 glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices); //启用ATTRIB_VERTEX属性的数据,默认是关闭的 glEnableVertexAttribArray(ATTRIB_VERTEX); //启用ATTRIB_TEXTURE属性的数据,默认是关闭的 glEnableVertexAttribArray(ATTRIB_TEXTURE); //分别创建y,u,v纹理对象 m_pTextureY = new QOpenGLTexture(QOpenGLTexture::Target2D); m_pTextureU = new QOpenGLTexture(QOpenGLTexture::Target2D); m_pTextureV = new QOpenGLTexture(QOpenGLTexture::Target2D); m_pTextureY->create(); m_pTextureU->create(); m_pTextureV->create(); //获取返回y分量的纹理索引值 id_y = m_pTextureY->textureId(); //获取返回u分量的纹理索引值 id_u = m_pTextureU->textureId(); //获取返回v分量的纹理索引值 id_v = m_pTextureV->textureId(); // glClearColor(0.3,0.3,0.3,0.0);//设置背景色}void VideoGLWidget::resizeGL(int w, int h){ if(h == 0)// 防止被零除 { h = 1;// 将高设为1 } glViewport(0, 0, w, h);}void VideoGLWidget::paintGL(){ std::unique_lock
locker(m_mutexForShowYuvData); if(m_pYuvPictPtr) { // clock_t start = clock(); uint32_t weight = m_pYuvPictPtr->m_nWeight; uint32_t height = m_pYuvPictPtr->m_nHeight; glViewport(m_drawRect.x(), m_drawRect.y(), m_drawRect.width(), m_drawRect.height()); //加载y数据纹理 //激活纹理单元GL_TEXTURE0 glActiveTexture(GL_TEXTURE0); //使用来自y数据生成纹理 glBindTexture(GL_TEXTURE_2D, id_y); //使用内存中m_pBufYuv420p数据创建真正的y数据纹理 glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, weight, height, 0, GL_RED, GL_UNSIGNED_BYTE, m_pYuvPictPtr->m_pData); 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); //加载u数据纹理 glActiveTexture(GL_TEXTURE1);//激活纹理单元GL_TEXTURE1 glBindTexture(GL_TEXTURE_2D, id_u); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, weight/2, height/2, 0, GL_RED, GL_UNSIGNED_BYTE, (char*)m_pYuvPictPtr->m_pData+weight*height); 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); //加载v数据纹理 glActiveTexture(GL_TEXTURE2);//激活纹理单元GL_TEXTURE2 glBindTexture(GL_TEXTURE_2D, id_v); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, weight/2, height/2, 0, GL_RED, GL_UNSIGNED_BYTE, (char*)m_pYuvPictPtr->m_pData+weight*height*5/4); 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); //指定y纹理要使用新值 只能用0,1,2等表示纹理单元的索引,这是opengl不人性化的地方 //0对应纹理单元GL_TEXTURE0 1对应纹理单元GL_TEXTURE1 2对应纹理的单元 glUniform1i(textureUniformY, 0); //指定u纹理要使用新值 glUniform1i(textureUniformU, 1); //指定v纹理要使用新值 glUniform1i(textureUniformV, 2); //使用顶点数组方式绘制图形 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // clock_t end = clock(); }}
你可能感兴趣的文章
程序员最核心的竞争力是什么?
查看>>
Node.js机制及原理理解初步
查看>>
linux CPU个数查看
查看>>
分布式应用开发相关的面试题收集
查看>>
简单理解Socket及TCP/IP、Http、Socket的区别
查看>>
利用HTTP Cache来优化网站
查看>>
利用负载均衡优化和加速HTTP应用
查看>>
消息队列设计精要
查看>>
分布式缓存负载均衡负载均衡的缓存处理:虚拟节点对一致性hash的改进
查看>>
分布式存储系统设计(1)—— 系统架构
查看>>
MySQL数据库的高可用方案总结
查看>>
常用排序算法总结(一) 比较算法总结
查看>>
SSH原理与运用
查看>>
SIGN UP BEC2
查看>>
S3C2440中对LED驱动电路的理解
查看>>
《天亮了》韩红
查看>>
Windows CE下USB摄像头驱动开发(以OV511为例,附带全部源代码以及讲解) [转]
查看>>
出现( linker command failed with exit code 1)错误总结
查看>>
iOS开发中一些常见的并行处理
查看>>
iOS获取手机的Mac地址
查看>>