前言
今天向大家介绍一下如何通过 SDL 实现一个YUV视频播放器。它与上次介绍的音频播放器一样,也是一个简单的不能再简单的播放器了。只不过一个是播放的音频PCM数据,另一个播放的时视频YUV数据。
该播放器不涉及到视频的解复用,解码等工作。我们只需要定时的刷新视频帧就可以了,而且还可以支持视频的倍速与慢放。在下面的列子中我将向你演示,使用 SDL 做这样一个播放器是何等的简单。
当然这个看似简单的播放器其实是由许多的理论基础在底层支持着的。如果在这方面没有什么基础的同学可以通过下面的链接去自行学习。
另外,我在慕课网分享了,有兴趣的同学可以去观看。
实现视频播放的原理
YUV播放器其实比较简单,就是设置一个定时间,每隔一段时间就渲染一帧数据。大家小时候都干过一件事儿,就是在自已的编习本上画几张连续的图,用手一翻就可以看到动画效果。一般情况下,每秒达到 25 帧就可以看到连续的效果,如果是 30帧以上动作就非常平滑。像现在的高清电影一般每秒达到60帧以上。
另外,如果原来每秒25帧的视频,现在你按每秒50帧播放就会起到倍速播放的效果。如果每秒 12帧,就会有慢动作的效果。
理解YUV
下面我们就来看一下代码吧。
例子
下面这个例子如果看了我前面的几篇文章就会觉得很简单了。都是一些常用的 SDL 的API调用做渲染和事件处理。
在这个例子中唯一需要说明的是,它在主线程中做渲染工作,同时启动了一个子线程做定时间。也就是说每 40ms 子线程就发送一个REFRESH 事件,发送完就去睡40ms。
主线程收到REFRESH事件后,就去做一次纹理渲染。渲染完成后再从文件中读一帧的数据。
如果想做倍速播放,你可以调整一下 delay时间,如果从 40ms 减为 20ms 播放速度就会快一倍。如果40ms调整为 80ms播放速度就会慢一倍。
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 - %s\n", 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, %s\n",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 file\n");
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视频倍速播放与慢速播放。
希望本文能对你所有帮助,谢谢!
共同学习,写下你的评论
评论加载中...
作者其他优质文章