0.前要
- 下载一个文件,名字为 400_300_25.mp4,我们用ffmplay.exe将其转化为yuv文件,具体操作如下:
- 进入cmd控制台,进入ffmplay.exe文件的目录下,输入ffmpeg -i 文件名.mp4 文件名.yuv 回车,会生成一个yuv文件
-
IYUV 是 YUV420 格式的一种变体,其中 Y 分量在内存中是连续存储的,而 U 和 V 分量是交错存储的。因此,一个 IYUV 像素点的大小与 YUV420 相同。
在 IYUV 中,一个像素的大小通常为:
Y 分量:8 位(1 字节)
U 分量:8 位/4 = 2 位(0.25 字节)
V 分量:8 位/4 = 2 位(0.25 字节)
- 400*300的图像,指的是 400列 300行,每行400个像素点
1.代码
#include "sdlqtrgb.h"
#include <sdl/SDL.h>
#include <fstream>
#include <QMessageBox>
using namespace std;
#pragma comment(lib,"SDL2.lib")
static SDL_Window* sdl_win = NULL;
static SDL_Renderer* sdl_render = NULL;
static SDL_Texture* sdl_texture = NULL;
static int sdl_width = 0;
static int sdl_height = 0;
static unsigned char* yuv = NULL;
static int pix_size = 2;
static ifstream yuv_file;
void SdlQtRGB::timerEvent(QTimerEvent* ev)
{
yuv_file.read((char*)yuv, sdl_width * sdl_height * 1.5);
//yuv 平面存储存储
// yyyyyyyy uu vv
SDL_UpdateTexture(sdl_texture, NULL, yuv,
sdl_width //一行 y的字节数
);
SDL_RenderClear(sdl_render);
SDL_Rect rect;
rect.x = 0;
rect.y = 0;
rect.w = sdl_width;
rect.h = sdl_height;
SDL_RenderCopy(sdl_render, sdl_texture, NULL, &rect);
SDL_RenderPresent(sdl_render);
}
SdlQtRGB::SdlQtRGB(QWidget* parent)
: QWidget(parent)
{
//打开yuv文件
yuv_file.open("D:\\lesson\\code\\bin\\x86\\400_300_25.yuv", ios::binary);
if (!yuv_file.is_open())
{
QMessageBox::information(this, "", "open yuv failed!");
return;
}
ui.setupUi(this);
sdl_width = 400;
sdl_height = 300;
ui.label->resize(sdl_width, sdl_height);
//初始化SDL
SDL_Init(SDL_INIT_VIDEO);
//创建窗口
sdl_win = SDL_CreateWindowFrom((void*)ui.label->winId());
//创建渲染器
sdl_render = SDL_CreateRenderer(sdl_win, -1, SDL_RENDERER_ACCELERATED);
//创建材质 支持YUV
sdl_texture = SDL_CreateTexture(sdl_render,
SDL_PIXELFORMAT_IYUV,
SDL_TEXTUREACCESS_STREAMING,
sdl_width,
sdl_height
);
yuv = new unsigned char[sdl_width * sdl_height * pix_size];
startTimer(10);
}
2.代码解析
1.构造函数解析
- 打开了一个YUV文件,文件路径是指定的路径。
ios::binary
表示以二进制模式打开文件,注意yuv文件名用绝对路径,且用双斜杠,不然报错!!我因为这个试过多种排错,比如打印当前工作目录,没问题,换了一个yuv文件怀疑我的yuv文件损坏了,也是一样的问题,最后加了双斜杠才能打开这个文件,再windows下可能\后跟一个字母会被误认为一个操作,比如\n是换行,那如果一个文件名为nnn.yuv呢 就会出现歧义,反正我的环境要打双斜杠 - 设置了窗口的宽度和高度,并将界面上的
label
控件的大小设置为这些值。这个控件将用于显示YUV视频帧,其实可以直接再QT的UI可视化界面上进行操作,直接拖动一个label到wiget上设置为400*300的大小即可,用代码有点烦喏 - 调用
SDL_Init
函数来初始化SDL库的视频子系统。这是必要的,因为我们将使用SDL来处理图形。 - 调用
SDL_CreateWindowFrom
函数来创建一个SDL窗口,并将其与界面上的label
控件关联起来。 - 调用
SDL_CreateRenderer
函数来创建一个SDL渲染器,用于在窗口上进行绘制。 - 调用
SDL_CreateTexture
函数来创建一个SDL纹理。参数包括渲染器、纹理格式(SDL_PIXELFORMAT_IYUV
表示YUV格式)、纹理访问方式(SDL_TEXTUREACCESS_STREAMING
表示数据将被频繁更新)、宽度和高度。 - 动态分配了一个大小为
sdl_width * sdl_height * pix_size
字节的内存块,并将指针存储在yuv
变量中。这个内存块用于存储YUV视频帧的数据。 - 调用
startTimer
函数,启动一个定时器,每隔10毫秒触发一次定时器事件。定时器事件在之前的代码中已经被重载,用于读取YUV数据、更新纹理和刷新屏幕显示。
这样,构造函数完成了打开YUV文件、设置窗口大小、初始化SDL、创建窗口、渲染器和纹理,以及分配内存并启动定时器的一系列操作。整个流程为在SDL窗口中显示YUV视频提供了基础。
2.定时器函数解析
- 使用
read
函数从yuv_file
文件流中读取数据,并将其存储到yuv
数组中。这个数组通常用于存储YUV格式的视频帧数据。数据的大小为sdl_width * sdl_height * 1.5
字节,这是因为YUV格式中,每个像素有一个Y分量和两个色度分量(U和V),所以总共需要sdl_width * sdl_height * 1.5
字节的空间来存储整个图像的数据。 yuv
数据的存储方式。在这个示例中,Y分量依次存储在数组的前面,接着是U分量,最后是V分量。yyyyyyyy uu vv yyyyyyyy uu vv.......(IYUV像素点存储格式)一直循环 对比 BGRABGRABGRA........(ARGB像素点的存储格式)
yyyyyyyy
uu
vv
yyyyyyyy
uu
vv
yyyyyyyy
uu
vv
...
...
...
- 调用
SDL_UpdateTexture
函数来更新SDL纹理中的数据。参数包括要更新的纹理、要更新的矩形区域(在这里是整个纹理)、指向新数据的指针(yuv
数组),以及一行Y分量的字节数。由于YUV数据是按行存储的,所以传递一行Y分量的字节数可以帮助SDL正确地解析数据。在SDL_UpdateTexture
函数中,第四个参数表示每一行的像素大小,为什么本代码传的是sdl_width而对ARGB格式处理的时候传的是sdl_width*像素大小呢?因为YUV420P中每个Y分量大小为1B,对YUV处理的时候默认传一行Y所占字节数也就是sdl_width*1B=sdl_width。 - 调用
SDL_RenderClear
函数来清空渲染器的内容,准备绘制新的帧。 - 定义了一个
SDL_Rect
结构体变量rect
,用于表示要绘制的矩形区域的位置和大小。在这个例子中,矩形的位置是窗口的左上角,大小是整个窗口的大小。 - 调用
SDL_RenderCopy
函数来将纹理复制到渲染器中。参数包括渲染器、要复制的纹理、源矩形(在这里是整个纹理,即NULL
),以及目标矩形(在这里是整个窗口的矩形区域)。 - 调用
SDL_RenderPresent
函数来更新屏幕显示,将渲染器中的内容呈现到屏幕上。
这样,timerEvent
函数完成了读取YUV数据、更新纹理、清空渲染器、复制纹理到渲染器中以及更新屏幕显示的一系列操作。这些操作使得YUV视频帧得以在SDL窗口中实时显示。
3.运行结果展示
giao giao giao