ffmpegg功能强大,封装了很多API供我么调用,但是视频帧的渲染及音频帧的播放,ffmpeg就无能为力了,因此需要借助类似sdl库等其他第三方组件来完成。
逻辑流程图如下:
通过线程完成一个YUV播放器,每隔40ms更新窗口显示画面
#include
#include
#include
#define BLOCK_SIZE 4096000
//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 yuv_player()
{
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 = 640, w_height = 480;
const int video_width = 608, video_height = 368;
Uint8* video_pos = NULL;
Uint8* video_end = NULL;
unsigned int remain_len = 0;
unsigned int video_buff_len = 0;
unsigned int blank_space_len = 0;
Uint8* video_buf = NULL;
//const char* path = "f:\\test_data\\crop_jiuzhe_summer.yuv";
const char* path = "f:\\test_data\\out.yuv";
// 一个yuv图片的长度
/*
这个公式用于计算YUV格式视频每帧数据的长度(字节数)。
YUV是一种基于颜色空间的视频格式,其中每个像素点不是一个RGB变量,而是由一个Luma(亮度)和两个色度(Chroma)组成的,通常用YUV 4:2:0的格式存储。在YUV 4:2:0格式中,Y分量的采样率是1,而U和V分量的采样率是1/2,即每四个Y像素只有一个U和V像素。因此,每个像素点需要1.5个字节的存储空间。
根据这个公式,假设视频的宽度是video_width,高度是video_height,每个像素点需要1.5个字节的存储空间,那么一个YUV帧的大小可以表示为:
yuv_frame_len = video_width * video_height * 1.5
这个公式中12/8用于将1.5个字节转化为12位,然后再将结果转化为字节。
*
*/
const unsigned int yuv_frame_len = video_width * video_height * 12 / 8;
unsigned int temp_yuv_frame_len = yuv_frame_len;
if (yuv_frame_len & 0xF) {
temp_yuv_frame_len = (yuv_frame_len & 0xFFF0) + 0x10;
}
//1.初始化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;
//根据渲染器创建纹理
texture = SDL_CreateTexture(renderer,
pixformat,
SDL_TEXTUREACCESS_STREAMING,
video_width,
video_height);
video_buf = (Uint8*)malloc(temp_yuv_frame_len);
//打开yuv文件
video_fd = fopen(path, "r");
if (!video_fd) {
fprintf(stderr, "Failed to open yuv file\n");
goto __FAIL;
}
//读取yuv数据保存到video_buf中
if ((video_buff_len = fread(video_buf, 1, BLOCK_SIZE, video_fd)) <= 0) {
fprintf(stderr, "Failed to read data from yuv file!\n");
goto __FAIL;
}
//set video positon
video_pos = video_buf;
timer_thread = SDL_CreateThread(refresh_video_timer,
NULL,
NULL);
do {
//等待事件
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_RenderCopy(renderer, texture, NULL, &rect);
SDL_RenderPresent(renderer);
if ((video_buff_len = fread(video_buf, 1, yuv_frame_len, video_fd)) <= 0) {
fprintf(stderr, "eof, exit thread!");
thread_exit = 1;
continue;// to wait event for exiting
}
}
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:
//close file
if (video_fd) {
fclose(video_fd);
}
SDL_Quit();
return 0;
}