文章目录
- 1. **宏定义和初始化**
- 2. **全局变量**
- 3. **`refresh_video_timer` 函数**
- 4. **`WinMain` 函数**
- 主要功能及工作流程:
- 总结:
1. 宏定义和初始化
#define REFRESH_EVENT (SDL_USEREVENT + 1) // 请求画面刷新事件
#define QUIT_EVENT (SDL_USEREVENT + 2) // 退出事件
REFRESH_EVENT
和QUIT_EVENT
是自定义的事件类型,用来触发画面刷新和退出操作。
2. 全局变量
int s_thread_exit = 0; // 退出标志
s_thread_exit
标志用于控制视频播放线程的退出。
3. refresh_video_timer
函数
int refresh_video_timer(void *data)
{while (!s_thread_exit){SDL_Event event;event.type = REFRESH_EVENT;SDL_PushEvent(&event); // 触发刷新事件SDL_Delay(40); // 约每40ms触发一次}s_thread_exit = 0;//push quit eventSDL_Event event;event.type = QUIT_EVENT;SDL_PushEvent(&event);return 0;
}
refresh_video_timer
函数是一个线程函数,每隔 40 毫秒触发一次REFRESH_EVENT
,用于请求刷新画面。线程会一直运行,直到s_thread_exit
被设置为 1。- 当播放完毕后,触发
QUIT_EVENT
以结束程序。
4. WinMain
函数
int WinMain(int argc, char* argv[])
{// 初始化 SDLif (SDL_Init(SDL_INIT_VIDEO)){fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());return -1;}SDL_Event event;SDL_Rect rect;SDL_Window *window = NULL;SDL_Renderer *renderer = NULL;SDL_Texture *texture = NULL;SDL_Thread *timer_thread = NULL; // 请求刷新线程uint32_t pixformat = YUV_FORMAT; // YUV格式// 视频文件路径const char *yuv_path = "H:/SDL/SDL_test/yuv420p_320x240.yuv";FILE *video_fd = NULL;uint8_t *video_buf = NULL; // 存储视频数据uint32_t y_frame_len = video_width * video_height;uint32_t u_frame_len = video_width * video_height / 4;uint32_t v_frame_len = video_width * video_height / 4;uint32_t yuv_frame_len = y_frame_len + u_frame_len + v_frame_len;// 创建窗口window = SDL_CreateWindow("Simplest YUV Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, video_width, video_height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);if (!window){fprintf(stderr, "SDL: could not create window, err:%s\n", SDL_GetError());goto _FAIL;}// 创建渲染器和纹理renderer = SDL_CreateRenderer(window, -1, 0);texture = SDL_CreateTexture(renderer, pixformat, SDL_TEXTUREACCESS_STREAMING, video_width, video_height);// 分配内存用于存储 YUV 数据video_buf = (uint8_t*)malloc(yuv_frame_len);if (!video_buf){fprintf(stderr, "Failed to allocate YUV frame space!\n");goto _FAIL;}// 打开 YUV 文件video_fd = fopen(yuv_path, "rb");if (!video_fd){fprintf(stderr, "Failed to open YUV file\n");goto _FAIL;}// 创建请求刷新线程timer_thread = SDL_CreateThread(refresh_video_timer, NULL, NULL);while (1){SDL_WaitEvent(&event); // 等待事件if (event.type == REFRESH_EVENT) // 画面刷新事件{video_buff_len = fread(video_buf, 1, yuv_frame_len, video_fd);if (video_buff_len <= 0){fprintf(stderr, "Failed to read data from YUV file!\n");goto _FAIL;}// 更新纹理数据SDL_UpdateTexture(texture, NULL, video_buf, video_width);// 设置显示区域的矩形(缩放保持比例)rect.x = 0;rect.y = 0;float w_ratio = win_width * 1.0 / video_width;float h_ratio = win_height * 1.0 / video_height;rect.w = video_width * w_ratio;rect.h = video_height * h_ratio;// 清除当前渲染内容并渲染新帧SDL_RenderClear(renderer);SDL_RenderCopy(renderer, texture, NULL, &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) // 退出事件{s_thread_exit = 1;}else if (event.type == QUIT_EVENT) // 退出视频播放{break;}}_FAIL:s_thread_exit = 1; // 确保线程退出if (timer_thread) SDL_WaitThread(timer_thread, NULL); // 等待线程退出if (video_buf) free(video_buf);if (video_fd) fclose(video_fd);if (texture) SDL_DestroyTexture(texture);if (renderer) SDL_DestroyRenderer(renderer);if (window) SDL_DestroyWindow(window);SDL_Quit();return 0;
}
主要功能及工作流程:
-
SDL 初始化:
SDL_Init(SDL_INIT_VIDEO)
初始化 SDL 的视频模块。- 创建一个窗口 (
SDL_CreateWindow
)、一个渲染器 (SDL_CreateRenderer
) 和一个纹理 (SDL_CreateTexture
) 来渲染 YUV 数据。
-
读取 YUV 数据:
- 打开 YUV 文件并读取 YUV 数据到缓冲区
video_buf
。 - YUV 数据根据视频的分辨率和 YUV420P 格式计算出每一帧的长度。
- 打开 YUV 文件并读取 YUV 数据到缓冲区
-
视频帧渲染:
- 每次读取一帧数据后,使用
SDL_UpdateTexture
将 YUV 数据更新到纹理。 - 通过渲染器
SDL_Renderer
渲染纹理到窗口。
- 每次读取一帧数据后,使用
-
刷新和退出事件:
- 创建了一个独立的线程定时触发
REFRESH_EVENT
,控制视频的刷新频率。 - 如果读取到文件结尾或退出事件,程序会退出。
- 创建了一个独立的线程定时触发
-
窗口大小调整:
- 在窗口大小变化时,程序会自动调整渲染区域的比例,保持视频的宽高比。
-
资源释放:
- 在退出程序时,确保释放所有分配的资源,包括 SDL 相关的资源(窗口、渲染器、纹理)以及 YUV 数据缓冲区。
总结:
这个程序通过 SDL2 创建一个视频播放窗口,读取并显示 YUV 文件的每一帧视频数据。它通过一个单独的线程来周期性触发视频帧的刷新,保持视频播放的流畅。