//代码来源:原文链接:https://blog.csdn.net/weixin_49406295/article/details/121569759 // Created 2022/11/29. // #include <iostream> #include <stdio.h> #include <string.h> #include <SDL2/SDL.h> //自定义消息类型 #define USR_REFRESH_EVENT (SDL_USEREVENT + 1) // 请求画面刷新事件 #define USR_QUIT_EVENT (SDL_USEREVENT + 2) // 退出事件 int g_thread_exit_flag = 1; // 线程退出标志 int refresh_video_timer(void *data) //定时发送刷新事件给主线程处理 { SDL_Event event; while (g_thread_exit_flag) { event.type = USR_REFRESH_EVENT; SDL_PushEvent(&event); // 发送刷新事件 SDL_Delay(1); // 40ms刷新一次,即每秒25帧 } event.type = USR_QUIT_EVENT; // 退出信号 SDL_PushEvent(&event); // 发送退出信号,通知main函数退出 return 0; } #undef main // SDL里有main函数,防止重复定义编译报错 int main() { SDL_Event event; // 事件 SDL_Rect display_rect = { 0 }; // 画面显示矩形 SDL_Window *window = NULL; // 窗口 SDL_Renderer *renderer = NULL; // 渲染器 SDL_Texture *texture = NULL; // 纹理 SDL_Thread *timer_thread = NULL; // 请求刷新线程 uint32_t pixformat = SDL_PIXELFORMAT_IYUV; // YUV420P FILE *fd = NULL; // YUV文件句柄 uint8_t *video_buf = NULL; // 读取数据后先把放到buffer里面 const char *file_path = "/test/outputyuv.yuv"; // YUV文件路径 int video_width = 960; // YUV的宽 int video_height = 540; // YUV的高 int win_width = video_width; // 窗口的宽 int win_height = video_height; // 窗口的高 uint32_t y_frame_len = video_width * video_height; // 每帧Y数据大小 uint32_t u_frame_len = video_width * video_height / 4; // 每帧U数据大小 uint32_t v_frame_len = video_width * video_height / 4; // 每帧V数据大小 uint32_t yuv_frame_len = y_frame_len + u_frame_len + v_frame_len; // 每帧YUV数据总大小 size_t video_buff_len = 0; if(SDL_Init(SDL_INIT_VIDEO)){ // SDL初始化 fprintf( stderr, "SDL初始化失败:%s\n", SDL_GetError()); return -1; } //创建窗口 window = SDL_CreateWindow("YUV Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, video_width, video_height, // 窗口的初始宽高和视频的宽高保持一致 SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); if(!window){ fprintf(stderr, "窗口创建失败:%s\n",SDL_GetError()); goto _FAIL; } renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC); // 创建渲染器 texture = SDL_CreateTexture(renderer, pixformat, // 创建纹理 SDL_TEXTUREACCESS_STREAMING, video_width, video_height); //显示的宽高就是视频的宽高 video_buf = (uint8_t*)malloc(yuv_frame_len); // 为帧数据分配空间 if(!video_buf){ fprintf(stderr, "堆空间分配失败!\n"); goto _FAIL; } fd = fopen(file_path, "rb"); // 打开YUV文件 if( !fd ){ fprintf(stderr, "YUV文件打开失败\n"); goto _FAIL; } timer_thread = SDL_CreateThread(refresh_video_timer, NULL, NULL); // 创建请求刷新线程 static auto lastmark = std::chrono::high_resolution_clock::now(); for (;;) //循环接收并处理事件 { SDL_WaitEvent(&event); // 阻塞等待事件到来 if(event.type == USR_REFRESH_EVENT) // 画面刷新事件 { video_buff_len = fread(video_buf, 1, yuv_frame_len, fd); //一帧一帧渲染 if(video_buff_len <= 0){ fprintf(stderr, "YUV文件读取失败!\n"); goto _FAIL; } float w_ratio = win_width * 1.0 /video_width; // 视频的宽缩放比例,窗口大小可能发生改变 float h_ratio = win_height * 1.0 /video_height; // 视频的高缩放比例 display_rect.w = video_width * w_ratio; // 显示的宽,位置坐标x,y为0,0 display_rect.h = video_height * h_ratio; // 显示的高 SDL_UpdateTexture(texture, NULL, video_buf, video_width); // 更新纹理 SDL_RenderClear(renderer); // 清除当前显示 SDL_RenderCopy(renderer, texture, NULL, &display_rect); // 将纹理的数据拷贝给渲染器 SDL_RenderPresent(renderer); // 显示新纹理 auto now = std::chrono::high_resolution_clock::now(); auto duration = static_cast<std::chrono::duration<double, std::micro>>(now - lastmark); lastmark = now; std::cout<< __FUNCTION__ << "micro=" << static_cast<long long>(duration.count())<<'\n'; } 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) //退出事件 { fprintf(stderr, "SDL_QUIT"); g_thread_exit_flag = 0; } else if(event.type == USR_QUIT_EVENT) { break; } } _FAIL: // 释放资源 g_thread_exit_flag = 0; if(timer_thread) SDL_WaitThread(timer_thread, NULL); // 等待线程退出 if(video_buf) free(video_buf); if(fd) fclose(fd); if(texture) SDL_DestroyTexture(texture); if(renderer) SDL_DestroyRenderer(renderer); if(window) SDL_DestroyWindow(window); video_buf = NULL; SDL_Quit(); return 0; }
1 SDL视频播放一般伴随条件变量的唤醒,进而主线程进行渲染显示;如果是直接while循环读YUV,进行SDL渲染,可能一帧都显示不出来;
2 安装SDL;MAC配置动态库加载: https://www.cnblogs.com/8335IT/p/8444359.html
3:
SDL_Init ():初始化 SDL 系统
SDL_CreateWindow ():创建窗口 SDL_Window
SDL_CreateRenderer ():创建渲染器 SDL_Renderer
SDL_CreateTexture ():创建纹理 SDL_Texture
SDL_UpdateTexture ():设置纹理的数据
SDL_RenderCopy ():将纹理的数据拷贝给渲染器
SDL_RenderPresent ():开始渲染并显示
SDL_WaitEvent():阻塞等待事件到来
SDL_Delay ():工具函数,用于延时
SDL_Quit ():退出 SDL 系统