音视频技术应用(10)- 使用SDL 直接播放和渲染YUV文件
本节记录下如何使用SDL直接播放和渲染RGB文件
1. 首先准备好需要播放的YUV文件
这里准备了一个mp4文件,我们要利用ffmpeg将该mp4文件直接转换成YUV文件,另外需要注意的是,由于YUV文件是未经压缩的文件,不同于mp4, 如果转换前的mp4文件时间很长,那么转换后的YUV文件将会很大,所以我们这里只取一小段的mp4文件(分辨率为400*300,帧率为25fps)进行格式转换。转换需要用到ffmpeg.exe, 可以从这里下载,
链接: https://pan.baidu.com/s/1g18PLIrA-6DhYLnaSwx9Qw 提取码: umby
下载完毕后,将ffmpeg.exe和需要转换的mp4文件放置到指定的目录
然后执行:
ffmpeg -i 400_300_25.mp4 400_300_25.yuv
生成完毕后的YUV文件如下:
可以看到,生成的YUV文件还是很大的。
另外需要注意的是,我们这里并没有给ffmpeg添加任何参数,所以ffmpeg是按照内部默认的格式进行转换的,它转换后的YUV的采样格式是 yuv420p , 这点可以从刚才的转换窗口中看出来:
2. 准备coding
修改之前编写的qt_rgb 解决方案,修改cpp code 如下:
#include "sdlqtrgb.h" #include <iostream> #include <fstream> #include <qmessagebox.h> #include <sdl/SDL.h> #pragma comment(lib, "SDL2.lib") using namespace std; static int sdl_width = 0; static int sdl_height = 0; static SDL_Window* sdl_window = NULL; static SDL_Renderer* sdl_render = NULL; static SDL_Texture* sdl_texture = NULL; static int pixel_size = 2; // 在YUV420p采样格式下,单个像素的大小是1.5个字节,这里使用2个字节,便于容纳,这里的单个像素大小可以大于1.5,但是绝对不能小于1.5 static unsigned char* yuv = NULL; static ifstream yuv_file; SDLQtRGB::SDLQtRGB(QWidget *parent) : QWidget(parent) { ui.setupUi(this); // 打开YUV文件 yuv_file.open("400_300_25.yuv", ios::binary); if (!yuv_file) { QMessageBox::information(this, "", "open yuv failed!"); return; } // 设定窗口和label的宽高 sdl_width = 400; sdl_height = 300; // 1. 初始化SDL if (SDL_Init(SDL_INIT_VIDEO)) { cout << SDL_GetError() << endl; return; } // 2. 创建窗口, 这里取得label所对应的窗口句柄 sdl_window = SDL_CreateWindowFrom((void*)ui.label->winId()); if (!sdl_window) { cout << SDL_GetError() << endl; return; } // 3. 创建渲染器 sdl_render = SDL_CreateRenderer(sdl_window, -1, SDL_RENDERER_ACCELERATED); if (!sdl_render) { cout << SDL_GetError() << endl; return; } // 重新设定label的大小, 因为yuv视频文件的分辨率是400*300, 所以这里更新label的大小,以便能够正常显示YUV图像 ui.label->resize(sdl_width, sdl_height); // 4. 根据label控件的宽高来创建材质 // 这里的像素格式要与YUV的图像格式相对应,之前生成的YUV的图像格式是YUV420p, 所以对应的材质的格式必须是 SDL_PIXELFORMAT_IYUV sdl_texture = SDL_CreateTexture(sdl_render, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, sdl_width, sdl_height ); if (!sdl_texture) { cout << SDL_GetError() << endl; return; } // 申请一块内存空间用于存放RGB数据 yuv = new unsigned char[sdl_width * sdl_height * pixel_size]; // 每隔10ms调用一次timerEvent函数 startTimer(10); } void SDLQtRGB::timerEvent(QTimerEvent* ev) { // 读取yuv数据信息(一次读取一帧) // 1.5 代表单个YUV像素点的大小 yuv_file.read((char *)yuv, sdl_width * sdl_height * 1.5); // 5. 动态更新材质信息 // 这里宽度为什么可以设置成sdl_width? // 这是因为当前的采样格式是YUV420p, 是以平面格式进行存储的,也就是yyyy uu vv, // 所以每一行只需要传入y的字节数即可,而yuv420p 采样格式,y的字节大小为1,所以y的字节数为sdl_width if (SDL_UpdateTexture(sdl_texture, NULL, yuv, sdl_width)) { cout << SDL_GetError() << endl; return; } // 6. 清理屏幕 if (SDL_RenderClear(sdl_render)) { cout << SDL_GetError() << endl; return; } // 7. 复制材质到渲染器对象 SDL_Rect rect; rect.x = 0; rect.y = 0; rect.w = sdl_width; rect.h = sdl_height; if (SDL_RenderCopy(sdl_render, sdl_texture, NULL, &rect)) { cout << SDL_GetError() << endl; return; } // 8. 执行渲染操作 SDL_RenderPresent(sdl_render); }
运行:
可以看到yuv文件已经可以顺利渲染到指定的窗口上。
<完>