SDL是一套开源跨平台多媒体开发库,使用 C 语言写成。,提供了数种控制 图像、声音、输出 入的函数。YUV分别表示:Y(亮度),U(蓝色投影)和V(红色投影),一般用UV表示色度。一般视频解码出来后是一帧一帧的YUV数据(因为大部分编解码算法都是基于YUV而不是RGB),而屏幕显示图像需要的是RGB数据,从YUV到RGB的转换就是视频渲染过程。
1.1 常用函数
SDL_Init ():初始化 SDL 系统
SDL_CreateWindow ():创建窗口 SDL_Window
SDL_CreateRenderer ():创建渲染器 SDL_Renderer
SDL_CreateTexture ():创建纹理 SDL_Texture
SDL_UpdateTexture ():设置纹理的数据
SDL_RenderCopy ():将纹理的数据拷贝给渲染器
SDL_RenderPresent ():开始渲染并显示
SDL_WaitEvent():阻塞等待事件到来
SDL_Delay ():工具函数,用于延时
SDL_Quit ():退出 SDL 系统
1.2 渲染流程
初始化: 初始化SDL库;创建一个Windows窗口;创建纹理(Texture)和渲染器(Render);给缓冲区分配内存空间;打开YUV视频文件;创建线程并指定线程处理函数。
线程处理函数: 定时(此处是40ms,即每秒25帧)发送一次刷新事件。
渲染: 循环调用SDL_WaitEvent()等待接收事件。
如果是刷新事件,则从YUV文件里读取一帧数据,然后调用SDL_UpdateTexture()将这一帧数据更新到纹理上,再调用SDL_RenderClear()清除当前显示的画面,然后调用SDL_RenderCopy()将纹理拷贝给渲染器,最后调用SDL_RenderPresent()更新当前显示画面。
如果是窗口事件,则根据窗口大小更新视频显示的宽和高。
如果是退出事件,则结束渲染,并通知事件处理线程退出。
本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
以下代码已再QT4.14.0上运行OK,功能时播放一个YUV420p的视频,首先需要在运行目录下放置一个yuv420p_320x240.yuv文件(可以用ffmpeg工具生成),然后运行此代码。
#include
#include
#include
//自定义消息类型
#define USR_REFRESH_EVENT (SDL_USEREVENT + 1) // 请求画面刷新事件
#define USR_QUIT_EVENT (SDL_USEREVENT + 2) // 退出事件
int g_thread_exit_flag = 1; // 线程退出标志 = 1则退出
int refresh_video_timer(void *data) //定时发送刷新事件给主线程处理
{
SDL_Event event;
while (g_thread_exit_flag)
{
event.type = USR_REFRESH_EVENT;
SDL_PushEvent(&event); // 发送刷新事件
SDL_Delay(40); // 40ms刷新一次,即每秒25帧
}
event.type = USR_QUIT_EVENT; // 退出信号
SDL_PushEvent(&event); // 发送退出信号,通知main函数退出
return 0;
}
#undef main // SDL里有main函数,防止重复定义编译报错
int main()
{
SDL_Event event; // 事件
SDL_Rect display_rect = { 0 }; // 画面显示矩形
SDL_Window *window = NULL; // 窗口
SDL_Renderer *renderer = NULL; // 渲染器
SDL_Texture *texture = NULL; // 纹理
SDL_Thread *timer_thread = NULL; // 请求刷新线程
uint32_t pixformat = SDL_PIXELFORMAT_IYUV; // YUV420P
FILE *fd = NULL; // YUV文件句柄
uint8_t *video_buf = NULL; // 读取数据后先把放到buffer里面
const char *file_path = "yuv420p_320x240.yuv"; // YUV文件路径
int video_width = 320; // YUV的宽
int video_height = 240; // YUV的高
int win_width = video_width; // 窗口的宽
int win_height = video_height; // 窗口的高
uint32_t y_frame_len = video_width * video_height; // 每帧Y数据大小
uint32_t u_frame_len = video_width * video_height / 4; // 每帧U数据大小
uint32_t v_frame_len = video_width * video_height / 4; // 每帧V数据大小
uint32_t yuv_frame_len = y_frame_len + u_frame_len + v_frame_len; // 每帧YUV数据总大小
size_t video_buff_len = 0;
if(SDL_Init(SDL_INIT_VIDEO)){ // SDL初始化
fprintf( stderr, "SDL初始化失败:%s\n", SDL_GetError());
return -1;
}
//创建窗口
window = SDL_CreateWindow("YUV Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
video_width, video_height, // 窗口的初始宽高和视频的宽高保持一致
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
if(!window){
fprintf(stderr, "窗口创建失败:%s\n",SDL_GetError());
goto _FAIL;
}
renderer = SDL_CreateRenderer(window, -1, 0); // 创建渲染器
texture = SDL_CreateTexture(renderer, pixformat, // 创建纹理
SDL_TEXTUREACCESS_STREAMING,
video_width, video_height); //显示的宽高就是视频的宽高
video_buf = (uint8_t*)malloc(yuv_frame_len); // 为帧数据分配空间
if(!video_buf){
fprintf(stderr, "堆空间分配失败!\n");
goto _FAIL;
}
fd = fopen(file_path, "rb"); // 打开YUV文件
if( !fd ){
fprintf(stderr, "YUV文件打开失败\n");
goto _FAIL;
}
timer_thread = SDL_CreateThread(refresh_video_timer, NULL, NULL); // 创建请求刷新线程
for (;;) //循环接收并处理事件
{
SDL_WaitEvent(&event); // 阻塞等待事件到来
if(event.type == USR_REFRESH_EVENT) // 画面刷新事件
{
video_buff_len = fread(video_buf, 1, yuv_frame_len, fd); //一帧一帧渲染
if(video_buff_len <= 0){
fprintf(stderr, "YUV文件读取失败!\n");
goto _FAIL;
}
float w_ratio = win_width * 1.0 /video_width; // 视频的宽缩放比例,窗口大小可能发生改变
float h_ratio = win_height * 1.0 /video_height; // 视频的高缩放比例
display_rect.w = video_width * w_ratio; // 显示的宽,位置坐标x,y为0,0
display_rect.h = video_height * h_ratio; // 显示的高
SDL_UpdateTexture(texture, NULL, video_buf, video_width); // 更新纹理
SDL_RenderClear(renderer); // 清除当前显示
SDL_RenderCopy(renderer, texture, NULL, &display_rect); // 将纹理的数据拷贝给渲染器
SDL_RenderPresent(renderer); // 显示新纹理
}
else if(event.type == SDL_WINDOWEVENT)
{
SDL_GetWindowSize(window, &win_width, &win_height); //窗口大小发生变化
printf("SDL_WINDOWEVENT win_width:%d, win_height:%d\n", win_width, win_height );
}
else if(event.type == SDL_QUIT) //退出事件
{
fprintf(stderr, "SDL_QUIT");
g_thread_exit_flag = 0;
}
else if(event.type == USR_QUIT_EVENT)
{
break;
}
}
_FAIL: // 释放资源
g_thread_exit_flag = 0;
if(timer_thread)
SDL_WaitThread(timer_thread, NULL); // 等待线程退出
if(video_buf)
free(video_buf);
if(fd)
fclose(fd);
if(texture)
SDL_DestroyTexture(texture);
if(renderer)
SDL_DestroyRenderer(renderer);
if(window)
SDL_DestroyWindow(window);
video_buf = NULL;
SDL_Quit();
return 0;
}
本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓