SDL播放视频数据

//代码来源:原文链接: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 系统


posted on 2022-11-30 19:21  邗影  阅读(52)  评论(0编辑  收藏  举报

导航