FFMpeg-3、基于QT实现音视频播放显示

这篇具有很好参考价值的文章主要介绍了FFMpeg-3、基于QT实现音视频播放显示。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、音视频播放的基础知识

内容来自雷神博客

1、在Windows平台下的视频播放技术主要有以下三种:GDI,Direct3D和OpenGL;音频播放技术主要是DirectSound。

SDL本身并不具有播放显示的功能,它只是封装了底层播放显示的代码

记录三种视频显示技术:GDI,Direct3D,OpenGL。其中Direct3D包含简单和复杂的两种显示方式:使用Surface和使用Texture;OpenGL也包含简单和复杂的两种显示方式:直接画像素和使用Texture。

GDI 微软显示窗口提供的一套显示机制,他工作的主要流程就是构建BMP文件(原始的RGB数据,构建bmp文件头),调用接口将bmp文件绘制到屏幕上。注意都需要转换为RGB格式数据。

Direct3D微软开发的一套3D绘图API,Direct3D的抽象概念包括:Devices(设备),Swap Chains(交换链)和Resources(资源)。

Device(设备)用于渲染3D场景。例如单色设备就会渲染黑白图片,而彩色设备则会渲染彩色图片。

每一个Device至少要有一个Swap Chain(交换链)。一个Swap Chain由一个或多个Back Buffer Surfaces(后台缓冲表面)组成。渲染在Back Buffer中完成。

Device包含了一系列的Resources(资源),用于定义渲染时候的数据

Direct3D API定义了一组Vertices(顶点), Textures(纹理), Buffers(缓冲区)转换到屏幕上的流程。这样的流程称为Rendering Pipeline(渲染流水线),具体就是从应用程序里读取vertex顶点数据,对每个顶点属性进行着色,最后将着色器处理的数据输出给使用者。

2、视频显示的基础知识

三角形;在绘图中所有平面都可以用三角形绘制出来的,并且显卡的性能强弱都是比较单位时间内画三角形的个数的。

后台缓冲表面,前台表面,交换链,离屏表面。

后台缓冲表面和前台表面的概念总是同时出现的。简单解释一下它们的作用。当我们进行复杂的绘图操作时,画面可能有明显的闪烁。这是由于绘制的东西没有同时出现在屏幕上而导致的。“前台表面”+“后台缓冲表面”的技术可以解决这个问题,就是一个缓冲的作用。

交换链;其实就是一个后台缓冲表面,一个前台表面。所谓的“交换”,即是在需要呈现后台缓冲表面中的内容的时候,交换这两个表面的“地位”。即前台表面变成后台缓冲表面,后台缓冲表面变成前台表面。如此一来,后台缓冲表面的内容就呈现在屏幕上了。原先的前台表面,则扮演起了新的后台缓冲表面的角色,准备进行新的绘图操作。当下一次需要显示画面的时候,这两个表面再次交换,如此循环往复,永不停止。

离屏表面、离屏表面是永远看不到的表面(所谓“离屏”),它通常被用来存放位图,并对其中的数据做一些处理。通常的做法是把离屏表面上的位图复制到后台缓冲表面,后台缓冲表面再显示到前台表面。

使用Direct3D的Surface播放视频一般情况下需要如下步骤:

创建一个窗口(不属于D3D的API)

初始化

创建一个Device

基于Device创建一个Surface(离屏表面)

循环显示画面

清理

一帧视频数据拷贝至 Surface

开始一个Scene

Surface数据拷贝至 后台缓冲表面

结束Scene

显示( 后台缓冲表面-> 前台表面)

3、OpenGL开放图形库,定义了一个跨编程语言、跨平台的应用程序接口(API)的规范,它用于生成二维、三维图像。

OpenGL渲染管线(OpenGL Pipeline)按照特定的顺序对图形信息进行处理,这些图形信息可以分为两个部分:顶点信息(坐标、法向量等)和像素信息(图像、纹理等)。图形信息最终被写入帧缓存中,存储在帧缓存中的数据(图像),可以被应用程序获得(用于保存结果,或作为应用程序的输入等。

关键就是顶点操作和纹理操作。

通过openGL的纹理显示图片

注意通过纹理显示则;输入的YUV420P像素数据通过Shader转换为YUV数据,传送给OpenGL播放。像素的转换YUV到RGB是通过显卡上的GPU完成的,则效率更高视频播放性能更好。

使用Shader通过OpenGL的纹理(Texture)播放视频一般情况下需要如下步骤:

初始化

初始化

创建窗口

设置绘图函数

设置定时器

初始化Shader

初始化Shader的步骤比较多,主要可以分为3步:创建Shader,创建Program,初始化Texture。

(1) 创建一个Shader对象;Shader有点类似于一个程序的编译器

1)编写Vertex Shader和Fragment Shader源码。

2)创建两个shader 实例 。

3)给Shader实例指定源码。

4)在线编译shaer源码。

(2) 创建一个Program对象

1)创建program。

2)绑定shader到program。

3)链接program。

4)使用porgram。

(3) 初始化Texture。可以分为以下步骤。

1)定义定点数组

2)设置顶点数组

3)初始化纹理

进入消息循环

循环显示画面

设置纹理

绘制

显示

2、QT的音频录制 QAudioFormat

QAudioFormat记录音频参数的格式

setSampleRate 样本率

setSampleSize 设置的位数,S16,S8等,这里不区分layout,设置的是位数而不是字节数,则这样要根据不同的样本类型进行转换。

setChannelCount 设置通道数量

setCodec(“audio/pcm”)设置编码格式,暂时可能只支持pcm格式的,与声卡硬件有关的。

setByteOrder(QAudioFormat::LittleEndian)设置大小端模式,注意网络传输一般是采用大端的(低字节在高位),x86windows一般都是小端的(低位在低字节)。

setSampleType(QAudioFormat::UnSignedInt) 设置样本类型

QAudioOutput 打开播放音频设备

其构造函数创建是传入一个设置好的QAudioFormat对象的

QIODevice *start();创建成功后调用开始函数,会返回QIODevice ,这个是QT内部的一个类,做IO输入输出设备的类。

suspend() 挂起暂停

resume() 恢复播放

因为音频不像视频,如果音频丢帧了那么现象会有很严重的失真的,因此需要存在缓冲机制的。

bufferSize() 缓冲的大小

bytesFree() 得到缓冲内部还有多少内存

bufferSize() - bytesFree() 可以得到我们缓冲区里面还有多少内存没有释放的,在做音视频同步的时候需要考虑。

periodSize() 内部硬件读取多大才写入,

QIODevice 音频设备的抽象

qint64 write(const char *data, qint64 len) 写入,这个就是我们解码出来重采样后直接把数据通过write写入就可以播放了。返回的大小才是真正写入的大小,也可以重载一下保证把传入的len都写入进去,还可以采用判断bytesFree缓冲空间足够大的时候才把他写入进去,保证都能写入。

代码实战

//注意如果在QT CRTEAT下编写的话要在pro文件加入QT += multimediawidgets
#include <QCoreApplication>
#include <QAudioFormat>
#include <QAudioOutput>
#include <QThread>
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QAudioFormat fmt;
    fmt.setSampleRate(44100);//样本率
    fmt.setSampleSize(16);  //样本大小 S16
    fmt.setChannelCount(2); //双通道
    fmt.setCodec("audio/pcm"); //设置pcm编码器
    fmt.setByteOrder(QAudioFormat::LittleEndian);//这是字节序
    fmt.setSampleType(QAudioFormat::UnSignedInt);//设置样本类型
    QAudioOutput *out = new QAudioOutput(fmt);//创建QAudioOutput
    QIODevice *io = out->start(); //开始播放

    int size = out->periodSize();//一个周期的大小,硬件设备一次读多大
    char *buf = new char[size];

    FILE *fp = fopen("out.pcm", "rb");
    while (!feof(fp))
    {
        //如果缓冲空间剩余内存不够size,硬件还没取走
        if (out->bytesFree() < size)
        {
            QThread::msleep(1);
            continue;
        }
        int len = fread(buf, 1, size, fp);
        if (len <= 0)break;
        io->write(buf, len);
    }
    fclose(fp);
    delete buf;
    buf = 0;

    return a.exec();
}

总结;QT音频播放基于三个类进行操作

先设置QAudioFormat音频格式,

再通过设置的音频格式创建QAudioOutput 音频输出设备启动设备和设置缓冲,

最后通过QAudioOutput 的启动得到具体声卡设备的抽象对象QIODevice ,通过向QIODevice 进行写入音频数据实现音频播放的。

3、QT视频绘制 QT openGL编程

雷神博客介绍OpenGL基于纹理显示的流程

因为视频显示存在像素格式的转换问题,要从解码出来的YUV格式转化为显示需要的RGB格式,视频的每一帧图像这么多像素点都需要转换,这是一个很大的开销,如果这一部分效率不高,那么对整体的视频播放性能都是十分有影响的,而QTopenGL也是效率很高的,直接操作显卡。

Shader有点类似于一个程序的编译器,

1)编写Vertex Shader和Fragment Shader源码。GLSL语言编写类似与C语言的一样的代码,它可以放在GPU里面被并行运行。

Vertex Shader(如果要存放到文件中就在.vsh)负责搞定像素位置,填写gl_Posizion;

Fragment Shader(Fragment Shader fsh)负责搞定像素外观,填写 gl_FragColor;

Program有点类似于一个程序的链接器。

program对象提供了把需要做的事连接在一起的机制,在一个program中,shader对象可以连接在一起。

QTopenGL显示的几个关键点

QOpenGLWidget (与界面如何交互)

Program GLSL 顶点与片元(如何与显卡交互的,GLSL 是跑在显卡上面的)

材质纹理Texture (如何写入ffmpeg数据的)

顶点和纹理材质

下面有相关介绍

为什么要采用QT的OpenGL三维的来绘制,直接使用QopenWidget、QWight也是可以的,但是如果使用后面的那么其实图像显示和界面的按钮是一套东西,当点击按钮刷新的时候就会存在闪屏的情况。因此采用三维的,QTOpenGL这种就是提供三维绘制,就可以自动在界面上叠加了。

使用的时候需要重载这些函数
void paintGL();//具体绘制在这里面实现
void initializeGL();//初始化
void resizeGL(int width,int height)//当窗口发生变化的时候调用,这个函数

还提供了很大的便利,如如果需要视频放缩的时候就可以直接调用OpenGL里面的函数,不然就需要在ffmpeg进行像素尺寸的转换效率就比较低了并且采用不同的算法效果可能也不一定好。而openGL中就是采用差值的算法来放缩,整体界面就减少失真的情况。

封装了操作OpenGL对象的函数类QOpenGLFunctions

QOpenGL相关的函数是单独有一个类来封装的,是QOpenGLFunctions

类,这里面就有很多与openGL相关的函数了,因此也可以进行QOpenGLFunctions继承来获取操作openGL的相关函数。

QGLShaderProgram编译运行shader和shader交互

shader 着色器

这个程序最终是在显卡上运行的,是通过QGLShaderProgram 来与显卡进行交互的。

QGLShaderProgram 主要封装的函数有

编译运行shader

addShaderFromSourceCode 把shader的源代码加载进来,有两部分代码,是顶点shader和片元shader(又称为像素shader)

bindAttributeLocation 设置传入的属性,只要是设置顶点坐标的属性和材质纹理Texture坐标的属性,后面可以传入顶点坐标或材质坐标然后对应坐标设置什么变量是通过他来设置的。

uniformLocation 获取shader变量 就是将显卡当中变量的一块地址取出来

总结;就是编译运行shader、传入shader程序,设置变量,获取变量

GLSL着色器语言 基于openGL设计的,给显卡用的语言

OpenGL Shader Language,简称GLSL。它是一种类似于C语言的专门为GPU设计的语言,它可以放在GPU里面被并行运行。

顶点着色器,就是针对每个顶点执行一次,用于确定顶点的位置,因为在三维空间中要将所有顶点的参数都获取。一般顶点着色器都是画三角形,如计算显卡的能力都是看他能画出多少个三角形,这里图片的显示我们采用画矩形,也就是两个三角形组合。因此我们这里顶点着色器的代码就是画两个三角形组成矩形。

qt如何使用音视频,ffmpeg,qt,音视频

代码填充就是

float *vertexData = new float[12]{
	-1.0f,-1.0f,0.0f,
    1.0f,-1.0f,0.0f,
     -1.0f, 1.0f,0.0f,
     1.0f,1.0f,0.0f
}
//也可以第三维的0这里不传入。再传入的时候告诉他填充的是二维的要他在内部填充

片元着色器,就是针对一个平面的,针对每个片元(可以理解为每个像素)执行一次,用于确定每个片元(像素)的颜色 。传入YUV的数据过来,至于至于转换可以到显卡自己那边转换。片元着色器他有一个内置变量gl_FragColor,就是在遍历像素点的时候将这个值进行改变,那么他的颜色一会跟着变化的。

流程就是通过顶点着色器获取顶点的位置,再通过片元着色器对这些顶点构成的片元像素进行填充颜色。

GLSL基本语法与C基本相同,这里只是做简单图像显示,真正的GLSL是用来做特效的,涉及算法的,

GLSL 完美支持向量和矩阵操作,因此在像素格式的转换方面十分适合选用,并且提供了大量的内置函数来提供丰富的扩展功能的,

他是通过限定符操作来管理输入输出类型的,例如传入顶点坐标,传出材质纹理等。

材质坐标信息

qt如何使用音视频,ffmpeg,qt,音视频

与顶点不同的是他是二维的并且坐标都是正数。

代码设置;

float *textureVertexData = new float[8]{
	0.0f, 1.0f,
	1.0f, 1.0f,
	0.0f, 0.0f,
	1.0f, 0.0f
}
//也可以第三维的0这里不传入。再传入的时候告诉他填充的是二维的要他在内部填充

三种GLSL变量类型

varying顶点与片元共享,就是有两部分代码顶点着色器代码计算出来的顶点坐标通过这个变量传给片元着色器那段代码,然后就可以获取到材质位置。可见代码理解。

atrribute 顶点使用 由bindAttributeLocation传入,就是我们创建的数组通过他传入过去进行计算

uniform 程序传入 uniformLocation获取地址,就是我们ffmpeg解码出来的YUV数据要传入进去变成uniform。

最后再通过glUniform1i(textureUniformY,0)传入与材质层进行绑定

//自动加双引号 定义常量串都可以使用
#define GET_STR(x) #x
//顶点着色器的shader代码
const char *vString = GET_STR(
	attribute vec4 vertexIn;
	attribute vec2 textureIn;
	varying vec2 textureOut;
	void main(void)
	{
		gl_Position = vertexIn;//传入的顶点坐标记录
		textureOut = textureIn;//传入的材质坐标保存到textureOut
	}
);
//自动加双引号 定义常量串都可以使用
#define GET_STR(x) #x
//片元着色器的shader代码
const char *tString = GET_STR(
	varying vec2 textureOut;//刚刚顶点着色器算出来的坐标
	uniform sampler2D tex_y;//uniform是外部传入的变量
	uniform sampler2D tex_u;
	uniform sampler2D tex_v;
	void main(void)
	{
	//420P是平面存储的,最后都是要转换为每个像素都是有yuv再方便转换
	//用的是灰度图的形式存储的
	//如安装那边硬解码出来的都是yuv420sp,uv是打包存在一起的,则不能使用这套shader代码了,要增加透明度存储方式。
		vec3 yuv;
		vec3 rgb;
		//根据那个坐标把材质坐标计算出来
		//传入材质,和坐标   返回材质当中的颜色rgb,但是用灰度图存储的,都是一样的。
		//三个材质就可以拼出一个yuv的数据
		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.0, 1.0, 1.0,
			0.0, -0.39465, 2.03211,
			1.13983, -0.58060, 0.0) * yuv;
		gl_FragColor = vec4(rgb, 1.0);//转换成显示的颜色,再设置一下
	}

);

创建材质

shader其实就是绘制某一个材质,那么就要看创建材质了。qt也有创建材质的函数,但是openGL这边要简单一点。

glGenTextures(1,t);创建几个材质,传入二维数组存放创建的材质地址

glBindTexture(GL_TEXTURE_2D *t)进行绑定 绑定为2d的图像

glTexPatameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR)设置属性  放大的属性  
glTexPatameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR)设置属性  缩小的属性
GL_TEXTURE_2D操作的是2D纹理
GL_TEXTURE_MAG_FILTER  放大过滤
GL_TEXTURE_MIN_FILTER  缩小过滤
GL_LINEAR用线性差值的方式进行缩放 使用距离当前渲染像素中心最近的四个纹素加权平均值,则最后的图像变化就比较荣和

写入和绘制材质

glActiveTexture(GL_TEXTURE0);激活材质 序号0
glBindTexture(GL_TEXTURE_2D,id_y);绑定之前创建的材质
在显存当中创建材质 
//将openGL里面的创建的材质设置传入到内存当中,就是显存到内存的操作
//GL_LUMINANCE,pixel_w,pixel_h 这个就表示已灰度图的形式存放的,传入宽高
glTexImage2D(GL_TEXTURE_2D,//创建为了
 0, //细节显示 0默认 是拉远拉近摄像机相关的显示
 GL_LUMINANCE,//gpu显卡内部的格式,就是创建的材质是存放到显卡当中,这是灰度图
 pixel_w,pixel_h,
 0,
 GL_LUMINANCE, 数据格式,内存到显存的格式,但是无法转换因此要一致
 GL_UNSIGNED_BYTE,//单个像素的存放格式
 plane[0])
 //因为创建纹理有很大的开销,因此提供了一个修改纹理的函数
 glTexSubImage2D()  修改纹理  存在偏移值的变量就是表示从当前的纹理当中只取一部分

glUniform1i(textureUniformY, 0);材质设完之后与shaeder相关联,通过uniform变量出入到材质0层
glDrawArrays(GL_TRINGLE_STRIP,0,4)//绘制矩形,四个顶点

显示播放整体流程分析

重载QOpenGLWidget的三个函数

//重载那三个函数

//刷新初始化

void paintGL();//具体绘制在这里面实现

//初始化gl

void initializeGL();//初始化

//窗口大小变化

void resizeGL(int width,int height);//当窗口发生变化的时候调用,这个函数

initializeGL 在OPenGL创建的时候就会调用
初始化OpenGL函数
	initializeOpenGLFunctions()
调用着色器program添加shader代码
QGLShaderProgram.addShaderFromSourceCode(QGLShader::Fragment,tString);
QGLShaderProgram.addShaderFromSourceCode(QGLShader::Vertex,vString);
	//QGLShader::Fragment  纹理材质类型   tString,vString常量串是Shader具体源代码
	//这个addShader还可以从文件获取 Fragment是fsh文件,Vertex是vsh文件
	
//给shader绑定属性 给顶点shader的attribute赋值,A_VER、T_VER这个只是标记作用
	program.bindAttributeLocation("vertexIn", A_VER);
	program.bindAttributeLocation("textureIn",T_VER);
	
//编译,绑定shader
	program.link();
	program.bind();
	
//定义顶点和材质纹理的坐标
	static const GLfloat ver[] = {}
	static const GLfloat tex[] = {}

//将顶点和材质纹理的坐标设置到shader当中去,A_VER、T_VER就是之前赋值属性的时候绑定的flag
	glVertexAttribPointer(A_VER,2,GL_FLOAT,0,0,ver);
	glEnableVertexAttribArray(A_VER);
	glVertexAttribPointer(T_VER,2, GL_FLOAT, 0,0,tex);
	glEnableVertexAttribArray(T_VER);

//从shader当中获取uniform sampler2D的位置,之后好传入
	unis[0] = program.uniformLocation("tex_y");
    unis[1] = program.uniformLocation("tex_u");
    unis[2] = program.uniformLocation("tex_v");
	
//创建材质,并绑定类型,属性及创建空间,对yuv都进行操作,注意uv的大小变化
	glGenTextures(3,texs);
	//Y
    glBindTexture(GL_TEXTURE_2D, texs[0]);//绑定2D
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);//设置属性  放大 线性差值的属性
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);//设置属性  放大 线性差值的属性
	glTexImage2D(GL_TEXTURE_2D,0,GL_RED,width,height,0,GL_RED,GL_UNSIGNED_BYTE,0);//创建材质显卡的空间 ,与内存的空间是不一样的
	//width/2 height/2根据yuv420的特性来的 uv是y的四分之一
    //U
    glBindTexture(GL_TEXTURE_2D, texs[1]);//绑定2D
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);//设置属性  放大 线性差值的属性
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);//设置属性  放大 线性差值的属性
    glTexImage2D(GL_TEXTURE_2D,0,GL_RED,width/2,height/2,0,GL_RED,GL_UNSIGNED_BYTE,0);//创建材质显卡的空间 ,与内存的空间是不一样的
    //V
    glBindTexture(GL_TEXTURE_2D, texs[2]);//绑定2D
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);//设置属性  放大 线性差值的属性
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);//设置属性  放大 线性差值的属性
    glTexImage2D(GL_TEXTURE_2D,0,GL_RED,width/2,height/2,0,GL_RED,GL_UNSIGNED_BYTE,0);//创建材质显卡的空间 ,与内存的空间是不一样的

//分配材质的内存空间
    datas[0] = new unsigned char[width*height];
    datas[1] = new unsigned char[width*height/4];
    datas[2] = new unsigned char[width*height/4];
	
//读取文件
	fp = fopen("output240X128.yuv", "rb");
    if(!fp)
    {
        qDebug() << "fopen error";
    }

//启动定时器,绑定到OPenGL的update()函数调用绘画函数
    QTimer *ti = new QTimer(this);
    connect(ti, SIGNAL(timeout()), this, SLOT(update()));//定时器刷新到update()里面取
    ti->start(40);
	
paintGL  绘画时调用
//读取帧数据,存放到分配材质的内存空间
    fread(datas[0],1,width*height, fp);
    fread(datas[1], 1, width*height/4, fp);
    fread(datas[2], 1, width*height/4, fp);
	
//在显卡中创建材质   并绑定到了0层渲染材质
    glActiveTexture(GL_TEXTURE0);//激活第0层
    glBindTexture(GL_TEXTURE_2D, texs[0]);//把0层绑定Y材质
//修改材质(复制内存内存)
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, datas[0]);
//与shader uni变量关联起来
    glUniform1i(unis[0], 0);
	
	重复三次
	glActiveTexture(GL_TEXTURE0+1);//激活第1层
    glBindTexture(GL_TEXTURE_2D, texs[1]);//把1层绑定Y材质
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width/2, height/2, GL_RED, GL_UNSIGNED_BYTE, datas[1]);
    glUniform1i(unis[1], 1);
	
	glActiveTexture(GL_TEXTURE0+2);//激活第0层
    glBindTexture(GL_TEXTURE_2D, texs[2]);//把0层绑定Y材质
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width/2, height/2, GL_RED, GL_UNSIGNED_BYTE, datas[2]);
    glUniform1i(unis[2], 2);
	
最后绘画 //画三角形 从0开始 四个顶点,
	 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);//画三角形 从0开始 四个顶点,

视频播放实践
拿到output240X128.yuv也可以使用命令制作ffmpeg -i 1.mp4 -t 10 -s 240x128 -pix_fmt yuv420p out240x128.yuv

注意几点
控件这里

qt如何使用音视频,ffmpeg,qt,音视频

qt工程文件.pro文件
需添加
QT += opengl
QT += openglextensions

代码

XVideoWidget.h文件

#ifndef XVIDEOWIDGET_H
#define XVIDEOWIDGET_H

#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QGLShaderProgram>
/*
 * //引入这个需要在pro文件添加模块
 * QT += opengl
 * QT += openglextensions
*/

class XVideoWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
    //注意Q_OBJECT这个变量如果没有添加进来则槽函数无法调用
    Q_OBJECT

public:
    XVideoWidget(QWidget *parent);
    ~XVideoWidget();
    //从shader的yuv变量地址
    GLuint unis[3] = {0};
    //opengl的texture地址
    GLuint texs[3] = {0};

    //材质的内存空间
    unsigned char *datas[3] = {0};

    //宽高度
    int width = 240;
    int height = 128;
protected:
     //重载那三个函数
    //刷新初始化
    void paintGL();//具体绘制在这里面实现

    //初始化gl
    void initializeGL();//初始化

    //窗口大小变化
    void resizeGL(int width,int height);//当窗口发生变化的时候调用,这个函数


private:
    QGLShaderProgram program;

};

#endif // XVIDEOWIDGET_H

XVideoWidget.cpp文件

#include "XVideoWidget.h"
#include <QDebug>
#include <QTimer>

FILE *fp = NULL;

//定义一个宏表示数组
#define A_VER 3
#define T_VER 4
//自动加双引号 定义常量串都可以使用
#define GET_STR(x) #x
//顶点着色器的shader代码
const char *vString = GET_STR(
    attribute vec4 vertexIn;
    attribute vec2 textureIn;
    varying vec2 textureOut;//varying顶点和片元shader共享变量
    void main(void)
    {
        gl_Position = vertexIn;//传入的顶点坐标记录
        textureOut = textureIn;//传入的材质坐标保存到textureOut,从而传出去了
    }
);
//片元着色器的shader代码
const char *tString = GET_STR(
    varying vec2 textureOut;//刚刚顶点着色器算出来的坐标  共享的
    uniform sampler2D tex_y;//uniform是外部传入的变量
    uniform sampler2D tex_u;//sampler2D是一个2d图像
    uniform sampler2D tex_v;
    void main(void)
    {
    //420P是平面存储的,最后都是要转换为每个像素都是有yuv再方便转换
    //用的是灰度图的形式存储的
    //如安装那边硬解码出来的都是yuv420sp,uv是打包存在一起的,则不能使用这套shader代码了,要增加透明度存储方式。
        vec3 yuv;
        vec3 rgb;
        //根据那个坐标把材质坐标计算出来
        //传入材质,和坐标   返回材质当中的颜色rgb,但是用灰度图存储的,都是一样的。
        //三个材质就可以拼出一个yuv的数据
        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.0, 1.0, 1.0,
            0.0, -0.39465, 2.03211,
            1.13983, -0.58060, 0.0) * yuv;
        gl_FragColor = vec4(rgb, 1.0);//转换成显示的颜色,再设置一下
    }

);
XVideoWidget::XVideoWidget(QWidget *parent)
    :QOpenGLWidget(parent)//初始化列表调用父类构造方法调用paint画出OpenGLWidget
{

    int a = 1;
}
XVideoWidget::~XVideoWidget()
{

}
//刷新初始化,每次移动都会调用一次,进行刷新
void XVideoWidget::paintGL()
{
    if(feof(fp))
    {
     fseek(fp, 0, SEEK_SET);
    }
    fread(datas[0],1,width*height, fp);
    fread(datas[1], 1, width*height/4, fp);
    fread(datas[2], 1, width*height/4, fp);

    //在显卡中创建材质   并绑定到了0层渲染材质
    glActiveTexture(GL_TEXTURE0);//激活第0层
    glBindTexture(GL_TEXTURE_2D, texs[0]);//把0层绑定Y材质
    //修改材质(复制内存内存)
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, datas[0]);
    //与shader uni变量关联起来
    glUniform1i(unis[0], 0);

    glActiveTexture(GL_TEXTURE0+1);//激活第1层
    glBindTexture(GL_TEXTURE_2D, texs[1]);//把1层绑定Y材质
    //修改材质(复制内存内存)
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width/2, height/2, GL_RED, GL_UNSIGNED_BYTE, datas[1]);
    //与shader uni变量关联起来
    glUniform1i(unis[1], 1);

    glActiveTexture(GL_TEXTURE0+2);//激活第0层
    glBindTexture(GL_TEXTURE_2D, texs[2]);//把0层绑定Y材质
    //修改材质(复制内存内存)
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width/2, height/2, GL_RED, GL_UNSIGNED_BYTE, datas[2]);
    //与shader uni变量关联起来
    glUniform1i(unis[2], 2);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);//画三角形 从0开始 四个顶点,

    qDebug() << "paintGL";
}

//初始化gl
void XVideoWidget::initializeGL()
{
    qDebug() << "initializeGL";

    //初始化 opengl(QOpenGLFunctions继承过来的函数)
    initializeOpenGLFunctions();

    //program(opengl和qt都提供了)加载shader脚本文件 顶点shader 片元shader
    // qt 提供的 QGLShaderProgram
    qDebug() << "addShaderFromSourceCode :" <<program.addShaderFromSourceCode(QGLShader::Fragment,tString);//在源代码中添加好一些,从文件怕有泄露
    qDebug() << "addShaderFromSourceCode :" << program.addShaderFromSourceCode(QGLShader::Vertex,vString);

    //设置顶点坐标的变量
    program.bindAttributeLocation("vertexIn", A_VER);//这个vertexIn变量对应的本地位置 vertexIn是顶点shader定义的 3的位置

    //材质坐标
    program.bindAttributeLocation("textureIn",T_VER);//下标四,等下往4的位置存

    //编译shader
    qDebug() << "program.link():" << program.link();
    //绑定shader
    qDebug() << "program.link():" << program.bind();

    //传递顶点和材质坐标,
    //这个坐标是在这个函数中时候,之后draw的时候还要使用的,只传入二维
    static const GLfloat ver[] = {
        -1.0f,-1.0f,
        1.0f,-1.0f,
        -1.0f, 1.0f,
        1.0f,1.0f
    };
    static const GLfloat tex[] = {
        0.0f, 1.0f,
        1.0f, 1.0f,
        0.0f, 0.0f,
        1.0f, 0.0f
    };

    //将坐标写入GL当中
    glVertexAttribPointer(A_VER,//索引地址
                          2,//表示一点顶点几个元素,
                          GL_FLOAT,//存放的类型,浮点数
                          0,
                          0,
                          ver //顶点地址
                          );
    //顶点坐标生效
    glEnableVertexAttribArray(A_VER);

    //材质  就是shader准备数据
    glVertexAttribPointer(T_VER,2, GL_FLOAT, 0,0,tex);
    glEnableVertexAttribArray(T_VER);

    //从shader当中获取材质
    unis[0] = program.uniformLocation("tex_y");
    unis[1] = program.uniformLocation("tex_u");
    unis[2] = program.uniformLocation("tex_v");

    //创建材质
    glGenTextures(3,texs);//创建三个对象
    //分别对每个材质进行设置

    //Y
    glBindTexture(GL_TEXTURE_2D, texs[0]);//绑定2D
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);//设置属性  放大 线性差值的属性
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);//设置属性  放大 线性差值的属性
    glTexImage2D(GL_TEXTURE_2D,0,GL_RED,width,height,0,GL_RED,GL_UNSIGNED_BYTE,0);//创建材质显卡的空间 ,与内存的空间是不一样的

    //width/2 height/2根据yuv420的特性来的 uv是y的四分之一
    //U
    glBindTexture(GL_TEXTURE_2D, texs[1]);//绑定2D
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);//设置属性  放大 线性差值的属性
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);//设置属性  放大 线性差值的属性
    glTexImage2D(GL_TEXTURE_2D,0,GL_RED,width/2,height/2,0,GL_RED,GL_UNSIGNED_BYTE,0);//创建材质显卡的空间 ,与内存的空间是不一样的

    //V
    glBindTexture(GL_TEXTURE_2D, texs[2]);//绑定2D
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);//设置属性  放大 线性差值的属性
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);//设置属性  放大 线性差值的属性
    glTexImage2D(GL_TEXTURE_2D,0,GL_RED,width/2,height/2,0,GL_RED,GL_UNSIGNED_BYTE,0);//创建材质显卡的空间 ,与内存的空间是不一样的

    //分配材质的内存空间
    datas[0] = new unsigned char[width*height];
    datas[1] = new unsigned char[width*height/4];
    datas[2] = new unsigned char[width*height/4];

    //读取材质并显示
    //再通过opengl接口,实现内存空间和显卡空间的转换
    fp = fopen("output240X128.yuv", "rb");
    if(!fp)
    {
        qDebug() << "fopen error";
    }

    //启动定时器
    QTimer *ti = new QTimer(this);
    connect(ti, SIGNAL(timeout()), this, SLOT(update()));//定时器刷新到update()里面取
    ti->start(40);
}

//窗口大小变化
void XVideoWidget::resizeGL(int width,int height)
{
    qDebug() << "resizeGL width = " << width << "  height = " << height;
}

源码:Git地址

内容参考来自夏曹俊老师的ffmpeg和QT开发

原文 FFMpeg-3、基于QT实现音视频播放显示_雷神 博客 opengl_卖酒的小码农的博客-CSDN博客

 

★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。

见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓文章来源地址https://www.toymoban.com/news/detail-770272.html

到了这里,关于FFMpeg-3、基于QT实现音视频播放显示的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 用Qt开发的ffmpeg流媒体播放器,支持截图、录像,支持音视频播放,支持本地文件播放、网络流播放

    本工程qt用的版本是5.8-32位,ffmpeg用的版本是较新的5.1版本。它支持TCP或UDP方式拉取实时流,实时流我采用的是监控摄像头的RTSP流。音频播放采用的是QAudioOutput,视频经ffmpeg解码并由YUV转RGB后是在QOpenGLWidget下进行渲染显示。本工程的代码有注释,可以通过本博客查看代码或者

    2024年02月03日
    浏览(86)
  • Qt之基于QMediaPlayer的音视频播放器(支持常见音视频格式)

    Qt自带了一个Media Player的例子,如下图所示: 但是运行这个例子机会发现,连最基本的MP4格式视频都播放不了。因为QMediaPlayer是个壳(也可以叫框架),依赖本地解码器,视频这块默认基本上就播放个MP4,甚至连MP4都不能播放,如果要支持其他格式需要下载k-lite或者LAVFilter

    2024年02月02日
    浏览(60)
  • 音视频开发:ffplay使用ffmpeg滤镜实现倍速播放

    曾经为实现倍速播放使用过ffmpeg,对音频使用atempo滤镜即可实现变速不变调。但是当时效果并不是特别好,和soundtouch相比处理后的音质有明显的区别。最近用新版本的ffmpeg滤镜重新实现了倍速播放,发现效果变好,已经达到可接受的程度,所以在此分享具体实现。 ffmpeg倍速

    2024年02月03日
    浏览(91)
  • FFmpeg 播放器实现音视频同步的三种方式

    我们基于 FFmpeg 利用 OpenGL ES 和 OpenSL ES 分别实现了对解码后视频和音频的渲染,本文将实现播放器的最后一个重要功能:音视频同步。 老人们经常说, 播放器对音频和视频的播放没有绝对的静态的同步,只有相对的动态的同步,实际上音视频同步就是一个“你追我赶”的过

    2024年02月06日
    浏览(56)
  • 项目实战——Qt实现FFmpeg音视频转码器

    本文记录使用 Qt 实现 FFmepg 音视频转码器项目的开发过程。 1、首先创建一个 Qt 项目,选择 MSVC2017 32bit 作为其编译器 2、将 FFmpeg 相关库及源文件拷贝到当前目录下 3、注释 prepare_app_arguments 函数(这里方便后面我们运行时可以指定相应的转码参数) 4、将所需的一些 dll 动态库

    2024年01月23日
    浏览(61)
  • opencv+ffmpeg+QOpenGLWidget开发的音视频播放器demo

        本篇文档的demo包含了 1.使用OpenCV对图像进行处理,对图像进行置灰,旋转,抠图,高斯模糊,中值滤波,部分区域清除置黑,背景移除,边缘检测等操作;2.单纯使用opencv播放显示视频;3.使用opencv和openGL播放显示视频;4.在ffmpeg解码后,使用opencv显示视频,并支持对视

    2024年02月12日
    浏览(59)
  • 使用Qt进行音视频播放

      Qt对音视频的播放和控制,相机拍照,收音机等多媒体应用提供了强大的支持。Qt5使用了全新的Qt Multimedia模块来实现多媒体应用,而原来Qt4中用于实现多媒体功能的Phonon模块已经被移除。   新的Qt Multimedia模块提供了丰富的接口,使读者可以轻松地使用平台的多媒体功

    2024年02月03日
    浏览(39)
  • Qt音视频开发38-ffmpeg视频暂停录制的设计

    基本上各种播放器提供的录制视频接口,都是只有开始录制和结束录制两个,当然一般用的最多的也是这两个接口,但是实际使用过程中,还有一种可能需要中途暂停录制,暂停以后再次继续录制,将中间部分视频不需要录制,跳过这部分不需要的视频,而且录制的视频文件

    2023年04月20日
    浏览(68)
  • Qt音视频开发40-ffmpeg采集桌面并录制

    之前用ffmpeg打通了各种视频文件和视频流以及本地摄像头设备的采集,近期有个客户需求要求将整个桌面屏幕采集下来,并可以录制保存成MP4文件,以前也遇到过类似的需求,由于没有搞过,也没有精力去摸索和测试,所以也就一直耽搁着,近期刚好这个需求又来了,定下心

    2023年04月25日
    浏览(57)
  • 浏览器网页内嵌Qt-C++音视频播放器的实现,支持软硬解码,支持音频,支持录像截图,支持多路播放等,提供源码工程下载

        在浏览器中实现播放RTSP实时视频流,⼤体上有如下⼏个⽅案: ⽅案一:浏览器插件⽅案 ActiveX、NPAPI、PPAPI     ActiveX插件适用于IE浏览器,NPAPI与PPAPI插件适用于谷歌浏览器,不过这些插件都已经不被浏览器所支持。 ⽅案二:先转码再转流⽅案     ⼯作原理是架设一

    2024年01月17日
    浏览(79)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包