• 音视频从入门到精通——超简单的基于FFMPEG+SDL的视频播放器(二)


    SDL显示YUV流程

    音视频从入门到精通——超简单的基于FFMPEG+SDL的视频播放器(一)
    音视频从入门到精通——超简单的基于FFMPEG+SDL的视频播放器(二)

    SDL播放一个视频代码流程大体如下
    初始化:

    SDL_Init(): 初始化SDL。 
    SDL_CreateWindow(): 创建窗口(Window)。 
    SDL_CreateRenderer(): 基于窗口创建渲染器(Render)。 
    SDL_CreateTexture(): 创建纹理(Texture)。 
    

    循环渲染数据:

    SDL_UpdateTexture(): 设置纹理的数据。 
    SDL_RenderCopy(): 纹理复制给渲染器。 
    SDL_RenderPresent(): 显示。
    

    所以SDL显示渲染流程如下
    在这里插入图片描述

    简单解释各个变量的作用:

    SDL_Window就是使用SDL的时候弹出的那个窗口。在SDL1.x版本中,只可以创建一个一个窗口。在SDL2.0版本中,可以创建多个窗口。
    SDL_Texture用于显示YUV数据。一个SDL_Texture对应一帧YUV数据。
    SDL_Renderer用于渲染SDL_Texture至SDL_Window。
    SDL_Rect用于确定SDL_Texture显示的位置。注意:一个SDL_Texture可以指定多个不同的SDL_Rect,这样就可以在SDL_Window不同位置显示相同的内容(使用SDL_RenderCopy()函数)。

    SDL显示yuv代码

    main.c

    #include 
    #include 
    
    #include 
    
    //自定义消息类型
    #define REFRESH_EVENT   (SDL_USEREVENT + 1)     // 请求画面刷新事件
    #define QUIT_EVENT      (SDL_USEREVENT + 2)     // 退出事件
    
    //定义分辨率
    // YUV像素分辨率
    #define YUV_WIDTH   320
    #define YUV_HEIGHT  240
    //定义YUV格式
    #define YUV_FORMAT  SDL_PIXELFORMAT_IYUV
    
    int s_thread_exit = 0;  // 退出标志 = 1则退出
    
    int refresh_video_timer(void *data)
    {
        while (!s_thread_exit)
        {
            SDL_Event event;
            event.type = REFRESH_EVENT;
            SDL_PushEvent(&event);
            SDL_Delay(40);
        }
    
        s_thread_exit = 0;
    
        //push quit event
        SDL_Event event;
        event.type = QUIT_EVENT;
        SDL_PushEvent(&event);
    
        return 0;
    }
    #undef main
    int main(int argc, char* argv[])
    {
        //初始化 SDL
        if(SDL_Init(SDL_INIT_VIDEO))
        {
            fprintf( stderr, "Could not initialize SDL - %s\n", SDL_GetError());
            return -1;
        }
    
        // SDL
        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;            // YUV420P,即是SDL_PIXELFORMAT_IYUV
    
        // 分辨率
        // 1. YUV的分辨率
        int video_width = YUV_WIDTH;
        int video_height = YUV_HEIGHT;
        // 2.显示窗口的分辨率
        int win_width = YUV_WIDTH;
        int win_height = YUV_WIDTH;
    
        // YUV文件句柄
        FILE *video_fd = NULL;
        const char *yuv_path = "yuv420p_320x240.yuv";
    
        size_t video_buff_len = 0;
    
        uint8_t *video_buf = NULL; //读取数据后先把放到buffer里面
    
        // 我们测试的文件是YUV420P格式
        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);
    
        // 分配空间
        video_buf = (uint8_t*)malloc(yuv_frame_len);
        if(!video_buf)
        {
            fprintf(stderr, "Failed to alloce 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系统里面的事件
            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;
                }
                // 设置纹理的数据 video_width = 320, plane
                SDL_UpdateTexture(texture, NULL, video_buf, video_width);
    
                // 显示区域,可以通过修改w和h进行缩放
                rect.x = 0;
                rect.y = 0;
                float w_ratio = win_width * 1.0 /video_width;
                float h_ratio = win_height * 1.0 /video_height;
                // 320x240 怎么保持原视频的宽高比例
                rect.w = video_width * w_ratio;
                rect.h = video_height * h_ratio;
    //            rect.w = video_width * 0.5;
    //            rect.h = video_height * 0.5;
    
                // 清除当前显示
                SDL_RenderClear(renderer);
                // 将纹理的数据拷贝给渲染器
                SDL_RenderCopy(renderer, texture, NULL, &rect);
                // 显示
                SDL_RenderPresent(renderer);
            }
            else if(event.type == SDL_WINDOWEVENT)
            {
                //If Resize
                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;
    
    }
    
    

    FFmpeg的解码流程

    FFmpeg结构体

    参考

    SDL2源代码分析1:初始化(SDL_Init())

    SDL2源代码分析2:窗口(SDL_Window)

    SDL2源代码分析3:渲染器(SDL_Renderer)

    SDL2源代码分析4:纹理(SDL_Texture)

    SDL2源代码分析5:更新纹理(SDL_UpdateTexture())

    SDL2源代码分析6:复制到渲染器(SDL_RenderCopy())

    100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)

    最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0)

  • 相关阅读:
    JS -正则表达式
    网络信息通信的安全问题以及解决方法
    Docker笔记
    ACL(知识讲解+案列应用)
    android 多产品项目搭建与变体的使用
    超实用!win10网页录屏的3种方法
    开发信群发技巧有哪些?外贸邮件怎么群发?
    Camera基础(从底层到应用)
    06.封装为组件库
    Springboot毕设项目公共台账管理系统5d5ba(java+VUE+Mybatis+Maven+Mysql)
  • 原文地址:https://blog.csdn.net/e891377/article/details/126954880