前言
今天向大家介绍一下如何通过 SDL 实现一个YUV视频播放器。它与上次介绍的音频播放器一样,也是一个简单的不能再简单的播放器了。只不过一个是播放的音频PCM数据,另一个播放的时视频YUV数据。
该播放器不涉及到视频的解复用,解码等工作。我们只需要定时的刷新视频帧就可以了,而且还可以支持视频的倍速与慢放。在下面的列子中我将向你演示,使用 SDL 做这样一个播放器是何等的简单。
实现视频播放的原理
YUV播放器其实比较简单,就是设置一个定时间,每隔一段时间就渲染一帧数据。大家小时候都干过一件事儿,就是在自已的编习本上画几张连续的图,用手一翻就可以看到动画效果。一般情况下,每秒达到 25 帧就可以看到连续的效果,如果是 30帧以上动作就非常平滑。像现在的高清电影一般每秒达到60帧以上。
另外,如果原来每秒25帧的视频,现在你按每秒50帧播放就会起到倍速播放的效果。如果每秒 12帧,就会有慢动作的效果。
理解YUV
在我们开始介绍代码之前,你要先了解一下什么是YUV。YUV与RGB是什么关系呢?大家可以去看我的另一篇文件YUV视频格式详解,或看我在慕课网发布的音视频免费入门课程。
下面我们就来看一下代码吧。
例子
下面这个例子如果看了我前面的几篇文章就会觉得很简单了。都是一些常用的 SDL 的API调用做渲染和事件处理。
在这个例子中唯一需要说明的是,它在主线程中做渲染工作,同时启动了一个子线程做定时间。也就是说每 40ms 子线程就发送一个REFRESH 事件,发送完就去睡40ms。
主线程收到REFRESH事件后,就去做一次纹理渲染。渲染完成后再从文件中读一帧的数据。
如果想做倍速播放,你可以调整一下 delay时间,如果从 40ms 减为 20ms 播放速度就会快一倍。如果40ms调整为 80ms播放速度就会慢一倍。
代码语言:javascript复制include <stdio.h>
#include <string.h>
#include <SDL.h>
//event message
#define REFRESH_EVENT (SDL_USEREVENT 1)
#define QUIT_EVENT (SDL_USEREVENT 2)
int thread_exit=0;
int refresh_video_timer(void *udata){
thread_exit=0;
while (!thread_exit) {
SDL_Event event;
event.type = REFRESH_EVENT;
SDL_PushEvent(&event);
SDL_Delay(40);
}
thread_exit=0;
//push quit event
SDL_Event event;
event.type = QUIT_EVENT;
SDL_PushEvent(&event);
return 0;
}
int main(int argc, char* argv[])
{
FILE *video_fd = NULL;
SDL_Event event;
SDL_Rect rect;
Uint32 pixformat = 0;
SDL_Window *win = NULL;
SDL_Renderer *renderer = NULL;
SDL_Texture *texture = NULL;
SDL_Thread *timer_thread = NULL;
int w_width = 608, w_height = 368;
Uint8 *video_pos = NULL;
Uint8 *video_end = NULL;
unsigned int remain_len = 0;
size_t video_buff_len = 0;
size_t blank_space_len = 0;
Uint8 *video_buf = NULL;
const char *path = "1.yuv";
const unsigned int yuv_frame_len = video_width * video_height * 12 / 8;
unsigned int tmp_yuv_frame_len = yuv_frame_len;
if (yuv_frame_len & 0xF) {
tmp_yuv_frame_len = (yuv_frame_len & 0xFFF0) 0x10;
}
//initialize SDL
if(SDL_Init(SDL_INIT_VIDEO)) {
fprintf( stderr, "Could not initialize SDL - %sn", SDL_GetError());
return -1;
}
//creat window from SDL
win = SDL_CreateWindow("YUV Player",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
w_width, w_height,
SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE);
if(!win) {
fprintf(stderr, "Failed to create window, %sn",SDL_GetError());
goto __FAIL;
}
renderer = SDL_CreateRenderer(win, -1, 0);
//IYUV: Y U V (3 planes)
//YV12: Y V U (3 planes)
pixformat= SDL_PIXELFORMAT_IYUV;
//create texture for render
texture = SDL_CreateTexture(renderer,
pixformat,
SDL_TEXTUREACCESS_STREAMING,
video_width,
video_height);
//alloc space
video_buf = (Uint8*)malloc(tmp_yuv_frame_len);
if(!video_buf){
fprintf(stderr, "Failed to alloce yuv frame space!n");
goto __FAIL;
}
//open yuv file
video_fd = fopen(path, "r");
if( !video_fd ){
fprintf(stderr, "Failed to open yuv filen");
goto __FAIL;
}
if((video_buff_len = fread(video_buf, 1, yuv_frame_len, video_fd)) <= 0){
fprintf(stderr, "Failed to read data from yuv file!n");
goto __FAIL;
}
//set video positon
video_pos = video_buf;
/*
video_end = video_buf video_buff_len;
blank_space_len = BLOCK_SIZE - video_buff_len;
*/
timer_thread = SDL_CreateThread(refresh_video_timer,
NULL,
NULL);
do {
//Wait
SDL_WaitEvent(&event);
if(event.type==REFRESH_EVENT){
SDL_UpdateTexture( texture, NULL, video_pos, video_width);
//FIX: If window is resize
rect.x = 0;
rect.y = 0;
rect.w = w_width;
rect.h = w_height;
SDL_RenderClear( renderer );
SDL_RenderCopy( renderer, texture, NULL, &rect);
SDL_RenderPresent( renderer );
//read block data
if((video_buff_len = fread(video_buf, 1, yuv_frame_len, video_fd)) <= 0){
thread_exit =1;
continue;
}
//memset(video_buf (video_width*video_height), 0, (video_width*video_height)/2);
}else if(event.type==SDL_WINDOWEVENT){
//If Resize
SDL_GetWindowSize(win, &w_width, &w_height);
}else if(event.type==SDL_QUIT){
thread_exit=1;
}else if(event.type==QUIT_EVENT){
break;
}
}while ( 1 );
__FAIL:
if(video_buf){
free(video_buf);
}
//close file
if(video_fd){
fclose(video_fd);
}
SDL_Quit();
return 0;
}
小结
本文件介绍了一个简单的YUV播放器是如何实现的。同时还介绍了如何让YUV视频倍速播放与慢速播放。
希望本文能对你所有帮助,谢谢!