SDL显示YUV视频

文章目录

      • [1. **宏定义和初始化**](#1. 宏定义和初始化)
      • [2. **全局变量**](#2. 全局变量)
      • [3. **`refresh_video_timer` 函数**](#3. refresh_video_timer 函数)
      • [4. **`WinMain` 函数**](#4. WinMain 函数)
      • 主要功能及工作流程:
      • 总结:

1. 宏定义和初始化

c 复制代码
#define REFRESH_EVENT   (SDL_USEREVENT + 1)     // 请求画面刷新事件
#define QUIT_EVENT      (SDL_USEREVENT + 2)     // 退出事件
  • REFRESH_EVENTQUIT_EVENT 是自定义的事件类型,用来触发画面刷新和退出操作。

2. 全局变量

c 复制代码
int s_thread_exit = 0;  // 退出标志
  • s_thread_exit 标志用于控制视频播放线程的退出。

3. refresh_video_timer 函数

c 复制代码
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 event
    SDL_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 函数

c 复制代码
int WinMain(int argc, char* argv[])
{
    // 初始化 SDL
    if (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;
}

主要功能及工作流程:

  1. SDL 初始化:

    • SDL_Init(SDL_INIT_VIDEO) 初始化 SDL 的视频模块。
    • 创建一个窗口 (SDL_CreateWindow)、一个渲染器 (SDL_CreateRenderer) 和一个纹理 (SDL_CreateTexture) 来渲染 YUV 数据。
  2. 读取 YUV 数据:

    • 打开 YUV 文件并读取 YUV 数据到缓冲区 video_buf
    • YUV 数据根据视频的分辨率和 YUV420P 格式计算出每一帧的长度。
  3. 视频帧渲染:

    • 每次读取一帧数据后,使用 SDL_UpdateTexture 将 YUV 数据更新到纹理。
    • 通过渲染器 SDL_Renderer 渲染纹理到窗口。
  4. 刷新和退出事件:

    • 创建了一个独立的线程定时触发 REFRESH_EVENT,控制视频的刷新频率。
    • 如果读取到文件结尾或退出事件,程序会退出。
  5. 窗口大小调整:

    • 在窗口大小变化时,程序会自动调整渲染区域的比例,保持视频的宽高比。
  6. 资源释放:

    • 在退出程序时,确保释放所有分配的资源,包括 SDL 相关的资源(窗口、渲染器、纹理)以及 YUV 数据缓冲区。

总结:

这个程序通过 SDL2 创建一个视频播放窗口,读取并显示 YUV 文件的每一帧视频数据。它通过一个单独的线程来周期性触发视频帧的刷新,保持视频播放的流畅。

相关推荐
美狐美颜sdk8 小时前
什么是美颜SDK?美颜SDK安卓与iOS端开发指南
android·人工智能·ios·音视频·美颜sdk·直播美颜sdk
EasyDSS8 小时前
AI视频智能分析网关打造社区/工厂/校园/仓库智慧消防实现精准化安全管控
人工智能·音视频
DisonTangor10 小时前
Baichuan-Omni-1.5:支持文本、图像、视频和音频输入以及文本和音频输出的开源全模式基础模型
音视频
马剑威(威哥爱编程)14 小时前
HarmonyOS 5.0 低时延音视频开发
华为·音视频·harmonyos
木子李一15 小时前
Vue 实现 Hls、Flv 协议视频播放
前端·vue.js·音视频
科技小E16 小时前
EasyRTC嵌入式音视频通信SDK技术,助力工业制造多场景实时监控与音视频通信
人工智能·音视频
人间花木16 小时前
1. 视频基础知识
c++·ffmpeg·音视频·视频基础知识
时光追逐者1 天前
一款基于 .NET 开源的多功能的 B 站视频下载工具
c#·.net·音视频
雾江流1 天前
AfuseKt2.4.2 | 支持阿里云盘、Alist等平台视频播放,具备自动海报墙刮削功能的强大播放器
音视频·软件工程
leeseean891 天前
使用AI 将文本转成视频 工具 介绍
人工智能·音视频