• SDL结合QWidget的简单使用说明


    SDL(Simple DirectMeida Layer)是一个简单的封装媒体库,功能主要涉及了相关于OpenGL或者DirectX的显卡硬件功能和一些鼠标,键盘等外设访问。这里主要只说明一下它的渲染功能

    因为Qt本身不支持YUV流媒体数据显示,且QWidget默认是栅格式渲染(qml是默认GPU),用的是CPU,这样如果渲染方面涉及了反走样,优化等数据计算,对CPU的消耗是比较高的,这里我们期望能用GPU来负责处理渲染

    GPU的处理就必须使用能调用显卡的功能,通常来说在Win下是Opengl和D3D,但是要短时间熟悉使用还是比较麻烦的,这里就需要SDL了;SDL已经在下层封装好了这些显卡相关库的使用,我们只需要按照他定义的更简洁的一系列接口即可实现效果

    这里以YV12为例,因为个人需求,我又加上了在渲染层上再渲染一张png图片和一段文字的效果

    首先在官网下载源代码http://www.libsdl.org/download-2.0.php

    目前最新的源码版本是2.0.8,且主页依然提供了1.0版本的下载。1.0的使用和接口跟2.0有很多不一样的地方,这里建议还是直接用最新版

    下载解压后目录如图

    可以看到SDL支持各大主流平台,这里因为是在Win上,直接进入VisualC目录

    解决方案已经存在,直接打开编译即可。

    成功后在输出目录找到

    这就是我们需要的动态库(另外还有一个SDL2main的库,因为我们是通过Qt创建的窗口,所以这个不需要了,如果完全使用SDL来建立渲染窗口的话,一定要加上这个库)

    下一步,先创建一个QWidget用来做渲染

    #ifndef _SDL_RENDER_WND_H__
    #define _SDL_RENDER_WND_H__
    /********************************************************************
    文件名  :    SDLRenderWnd
    作者    :    @Kaiming
    创建时间:    2018/3/26 11:27
    版本    :     1.0
    文件描述:    SDL YUV420流输出窗口
    *********************************************************************/
    
    #include <QWidget>
    struct SDL_Renderer;
    struct SDL_Texture;
    struct SDL_Window;
    
    class SDLRenderWnd : public QWidget
    {
        Q_OBJECT
    
    public:
        SDLRenderWnd(QWidget *parent = 0);
        ~SDLRenderWnd();
    
        void Clear();
    
    protected:
        virtual void resizeEvent(QResizeEvent *event);
    
    private:
        static void SDL_Related_Init();
        static void SDL_Related_Uninit();
    
    public slots:
    //根据传入数据流显示视频
        void PresentFrame(const unsigned char* pBuffer, int nImageWidth, int nImageHeight);
    private:
        SDL_Renderer*        m_pRender;
        SDL_Texture*         m_pTexture;
        SDL_Window*          m_pWindow;
    
        static int            m_nRef;        //引用计数来确定SDL全局资源的创建和回收
    };
    
    #endif // _SDL_RENDER_WND_H__

    具体实现

    #include "SDLRenderWnd.h"
    extern "C" {
    #include "sdlSDL.h"
    }
    #pragma comment(lib, "SDL2.lib")
    
    int SDLRenderWnd::m_nRef = 0;
    SDLRenderWnd::SDLRenderWnd(QWidget *parent)
        : QWidget(parent),
          m_pTexture(nullptr),
          m_bEmpty(true)
    {
        setUpdatesEnabled(false);
        SDL_Related_Init();
        m_pWindow = SDL_CreateWindowFrom((void*)winId());
        m_pRender = SDL_CreateRenderer(m_pWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    }
    
    SDLRenderWnd::~SDLRenderWnd()
    {
        if (m_pWindow)
            SDL_DestroyWindow(m_pWindow);
        if (m_pRender)
            SDL_DestroyRenderer(m_pRender);
        if (m_pTexture)
            SDL_DestroyTexture(m_pTexture);
        SDL_Related_Uninit();
    }
    
    void SDLRenderWnd::PresentFrame(const unsigned char* pBuffer, int nImageWidth, int nImageHeight)
    {
        if (!m_pRender) {
            printf("Render not Create
    ");
        }
        else {
            int nTextureWidth = 0, nTextureHeight = 0;
        //首先查询当前纹理对象的宽高,如果不符合,那么需要重建纹理对象 SDL_QueryTexture(m_pTexture, nullptr, nullptr,
    &nTextureWidth, &nTextureHeight); if (nTextureWidth != nImageWidth || nTextureHeight != nImageHeight) { if (m_pTexture) SDL_DestroyTexture(m_pTexture);
          //这里指定了渲染的数据格式,访问方式和宽高大小 m_pTexture
    = SDL_CreateTexture(m_pRender, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, nImageWidth, nImageHeight); } } if (!m_pTexture) { printf("YUV Texture Create Failed "); } else {
         //用新数据刷新纹理 SDL_UpdateTexture(m_pTexture, nullptr, pBuffer, nImageWidth);
         //清除当前渲染 SDL_RenderClear(m_pRender);
    //拷贝纹理对象到渲染器中 SDL_RenderCopy(m_pRender, m_pTexture, nullptr, nullptr);
         //最终渲染
         SDL_RenderPresent(m_pRender);
      } }
    void SDLRenderWnd::Clear()
     
    } void SDLRenderWnd::SDL_Related_Init() { if (0 == m_nRef++) { SDL_Init(SDL_INIT_VIDEO); } } void SDLRenderWnd::SDL_Related_Uninit() { if (0 == --m_nRef) { SDL_Quit(); } }

    因为SDL是纯C库,注意加上extern “C";通过SDL_CreateWindowFrom((void*)winId());建立QWidget的窗口句柄和SDL的联系,这之后,Widget本身就没什么操作的了,剩下的工程都交给SDL来负责;然后通过创建的SDL_Window建立SDL_Render渲染器

    渲染过程具体看PresentFrame里面的注释,还有一点要特别注意的是一定要加上setUpdatesEnabled(false);这句是关闭QWidget自身的刷新动作,如果不设置,那么就会存在两个渲染互相刷新,如果窗口不动还好,一旦resize的时候就就会有明显的闪烁效果

    还有一点就是,参看我头文件里是把PresentFrame写成了一个slot,所以刷新动作是放在主线程执行的,并不是说子线程执行不可以,但是从自己的测试来看,子线程执行渲染会出现很多不可预知的问题,比如窗口缩放后会导致渲染无效,崩溃等等。另外提一句老生常谈的话就是调用这个slot注意保护参数,数据流是指针形式,如果异步执行要小心指针无效的情况

    下一篇谈在这个基础上给视频加图片,文字效果的方式

  • 相关阅读:
    tpescript中declare
    vue-router history模式 为什么需要服务端配置以及如何配置
    组件库搭建总结
    Babel
    高德地图实现轨迹围栏
    通过canvas实现描点连线功能
    vue项目使用echarts实现区域地图绘制,且可点击单独区域
    javascript正则表达式学习笔记
    el-loading修改默认样式
    Cannot read property 'resetFields' of undefined 问题及引申
  • 原文地址:https://www.cnblogs.com/KaiMing-Prince/p/8874914.html
Copyright © 2020-2023  润新知