SDL多线程渲染YUV视频

摘要:

SDL是一套开源跨平台多媒体开发库,使用 C 语言写成。,提供了数种控制 图像、声音、输出 入的函数。YUV分别表示:Y(亮度),U(蓝色投影)和V(红色投影),一般用UV表示色度。一般视频解码出来后是一帧一帧的YUV数据(因为大部分编解码算法都是基于YUV而不是RGB),而屏幕显示图像需要的是RGB数据,从YUV到RGB的转换就是视频渲染过程。

1、SDL视频播放流程

1.1 常用函数

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 系统

1.2 渲染流程

初始化: 初始化SDL库;创建一个Windows窗口;创建纹理(Texture)和渲染器(Render);给缓冲区分配内存空间;打开YUV视频文件;创建线程并指定线程处理函数。

线程处理函数: 定时(此处是40ms,即每秒25帧)发送一次刷新事件。

渲染: 循环调用SDL_WaitEvent()等待接收事件。

如果是刷新事件,则从YUV文件里读取一帧数据,然后调用SDL_UpdateTexture()将这一帧数据更新到纹理上,再调用SDL_RenderClear()清除当前显示的画面,然后调用SDL_RenderCopy()将纹理拷贝给渲染器,最后调用SDL_RenderPresent()更新当前显示画面。

如果是窗口事件,则根据窗口大小更新视频显示的宽和高。

如果是退出事件,则结束渲染,并通知事件处理线程退出。

本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

2、示例代码

以下代码已再QT4.14.0上运行OK,功能时播放一个YUV420p的视频,首先需要在运行目录下放置一个yuv420p_320x240.yuv文件(可以用ffmpeg工具生成),然后运行此代码。

#include 
#include 
#include 

//自定义消息类型
#define USR_REFRESH_EVENT   (SDL_USEREVENT + 1)     // 请求画面刷新事件
#define USR_QUIT_EVENT      (SDL_USEREVENT + 2)     // 退出事件

int g_thread_exit_flag = 1;  // 线程退出标志 = 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(40);           // 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     = "yuv420p_320x240.yuv";  // YUV文件路径
    int          video_width    = 320;                    // YUV的宽
    int          video_height   = 240;                    // 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, 0);     // 创建渲染器
    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);  // 创建请求刷新线程

    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);                              // 显示新纹理
        }
        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;
}

本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

你可能感兴趣的:(音视频开发进阶,音视频,服务器,视频编解码,实时音视频,webrtc)