使用SDL2播放视频时需要通过渲染操作令视频的每一帧在窗口的显示。
SDL播放视频的流程如下图所示:
初始化组件:
SDL_Init(); //初始化SDL
SDL_CreateWindow(); //创建播放器的窗口
SDL_CreateRenderer(); //创建基于窗口的渲染器
SDL_CreateTexture(); //创建基于渲染器与视频格式的纹理
循环渲染:
SDL_UpdateTexture(); //设置纹理的数据
SDL_RenderCopy(); //将纹理复制给渲染器
SDL_RenderPresent(); //使用渲染器进行显示
初始化组件
SDL_Init(SDL_INIT_VIDEO);
由于我们想实现一个播放器,所以SDL的子系统设成视频。
window = SDL_CreateWindow("Simplest YUV Player",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
video_width, video_height,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
窗口主要仅和视频的长和宽有关。
renderer = SDL_CreateRenderer(window, -1, 0);
创建渲染器,渲染器可见也是基于窗口进行初始化的。
texture = SDL_CreateTexture(renderer,
pixformat,
SDL_TEXTUREACCESS_STREAMING,
video_width,
video_height);
创建纹理,可以看到纹理的创建与渲染器renderer,视频的格式pixformat与视频的长宽video_width,video_height相关,实际上从源码中可以看出,纹理的创建使用了渲染器renderer的组件。
循环渲染
SDL_UpdateTexture(texture, NULL, video_buf, video_width);
以上代码实现了纹理的更新,设置纹理的像素数据,这里需要注意一个变量video_buf。
// 分配空间
video_buf = (uint8_t*)malloc(yuv_frame_len);
if (!video_buf)
{
fprintf(stderr, "Failed to alloce yuv frame space!\n");
goto _FAIL;
}
video_buf是特别创建的一个缓冲区,视频会先放在缓冲区内,再交给纹理进行更新。
// 清除当前显示
SDL_RenderClear(renderer);
// 将纹理的数据拷贝给渲染器
SDL_RenderCopy(renderer, texture, NULL, &rect);
// 显示
SDL_RenderPresent(renderer);
纹理更新完毕后会将纹理的数据拷贝给渲染器,交由渲染器进行显示。
在SDL_RenderCopy()函数中,第三个第四个参数需要注意下。
第三个参数:选择输入纹理的一块矩形区域作为输入,设置为null时整个纹理输入。
第四个参数:选择渲染目标的一块矩形区域作为输出,设置为null时整个渲染目标输出。
我们可以理解为纹理就是原视频文件的图像,渲染目标是播放器中显示的输出图像。
因此,一般原视频文件的图像是要全部读取的,而输出的图像大小还需要取决于当前播放器窗口的大小,因此第四个参数通常是可调整的,而第三个参数一般是null。
// 1. YUV的分辨率
int video_width = YUV_WIDTH;
int video_height = YUV_HEIGHT;
// 2.显示窗口的分辨率
int win_width = YUV_WIDTH;
int win_height = YUV_WIDTH;
// 显示区域,可以通过修改w和h进行缩放
rect.x = 0;
rect.y = 0;
float w_ratio = win_width * 1.0 / video_width;
float h_ratio = win_height * 1.0 / video_height;
// 320x240 怎么保持原视频的宽高比例
rect.w = video_width * w_ratio;
rect.h = video_height * h_ratio;
第四个参数使用SDL_Rect类型的rect变量进行表示。
当窗口大小相对视频的长宽大小出现变化时,需要根据变化比例,对渲染输出的目标,即显示的视频长宽也进行相应的调整。