zoukankan      html  css  js  c++  java
  • 音视频技术应用(11) 基于SDL QT 的一个简易的视频播放器

    总结之前的内容:

    sdlqtrgb.h

    #pragma once
    
    #include <QtWidgets/QWidget>
    #include "ui_sdlqtrgb.h"
    
    class SDLQtRGB : public QWidget
    {
        Q_OBJECT
    
    public:
        SDLQtRGB(QWidget *parent = Q_NULLPTR);
    
        // 重写QT的一个定时器函数,在该函数里执行定时操作
        void timerEvent(QTimerEvent *ev) override;
    
        // 重写QT的resizeEvent()接口,监听窗口变化
        void resizeEvent(QResizeEvent* event) override;
    
    private:
        Ui::SDLQtRGBClass ui;
    };

    sdlqtrgb.cpp

    #include "sdlqtrgb.h"
    
    #include "xvideo_view.h"
    
    
    #include <iostream>
    #include <fstream>
    #include <qmessagebox.h>
    
    #include <sdl/SDL.h>
    
    #pragma comment(lib, "SDL2.lib")
    
    using namespace std;
    
    static int sdl_width = 0;
    static int sdl_height = 0;
    
    static SDL_Window* sdl_window = NULL;
    static SDL_Renderer* sdl_render = NULL;
    static SDL_Texture* sdl_texture = NULL;
    
    static int pixel_size = 2;                  // 在YUV420p采样格式下,单个像素的大小是1.5个字节,这里使用2个字节,便于容纳,这里的单个像素大小可以大于1.5,但是绝对不能小于1.5 
    static unsigned char* yuv = NULL;
    
    static ifstream yuv_file;
    
    static XVideoView* view;
    
    SDLQtRGB::SDLQtRGB(QWidget *parent)
        : QWidget(parent)
    {
        ui.setupUi(this);
    
        // 打开YUV文件
        yuv_file.open("400_300_25.yuv", ios::binary);
        if (!yuv_file)
        {
            QMessageBox::information(this, "", "open yuv failed!");
            return;
        }
    
        // 设定窗口和label的宽高, 这个视频的大小是源视频的宽度和高度
        sdl_width = 400;
        sdl_height = 300;
    
        // 重新设定label的大小, 因为yuv视频文件的分辨率是400*300, 所以这里更新label的大小,以便能够正常显示YUV图像
        ui.label->resize(sdl_width, sdl_height);
    
        view = XVideoView::Create(XVideoView::SDL);
        // view->Init(sdl_width, sdl_height, XVideoView::YUV420P);
        view->Init(sdl_width, sdl_height, XVideoView::YUV420P, (void*)ui.label->winId());
    
        // 4. 根据label控件的宽高来创建材质
        // 这里的像素格式要与YUV的图像格式相对应,之前生成的YUV的图像格式是YUV420p, 所以对应的材质的格式必须是 SDL_PIXELFORMAT_IYUV
    
        // 申请一块内存空间用于存放RGB数据
        yuv = new unsigned char[sdl_width * sdl_height * pixel_size];
    
        // 每隔10ms调用一次timerEvent函数
        startTimer(10);
    }
    
    
    void SDLQtRGB::timerEvent(QTimerEvent* ev)
    {
       
        if (view->isExit())
        {
            view->Close();
            exit(0);
            return;
        }
    
        // 读取yuv数据信息(一次读取一帧)
        // 1.5 代表单个YUV像素点的大小
        yuv_file.read((char *)yuv, sdl_width * sdl_height * 1.5);
    
        view->Draw(yuv, 0);
    
    }
    
    
    void SDLQtRGB::resizeEvent(QResizeEvent* event)
    {
        ui.label->resize(size());
        ui.label->move(0, 0);
    
        view->Scale(width(), height());
    }

    xvideo_view.h

    #ifndef XVIDEO_VIEW_H
    #define XVIDEO_VIEW_H
    
    #include <mutex>
    
    class XVideoView
    {
    public:
        enum Format
        {
            RGBA = 0,
            ARGB,
            YUV420P
        };
    
        enum RenderType
        {
            SDL = 0
        };
    
        static XVideoView* Create(RenderType type = SDL);
    
        /**
         * @brief                    初始化渲染接口(线程安全)        可以多次调用
         * @param w                    窗口宽度
         * @param h                    窗口高度
         * @param fmt                绘制的像素格式(要绘制的图像的像素格式)
         * @param win_id            窗口句柄,如果为空,则创建窗口
         * @return                    是否创建成功
        */
        virtual bool Init(int w, int h, Format fmt = RGBA, void *win_id = nullptr) = 0;
    
        /**
         * @brief 清理所有申请的资源,包括关闭窗口            线程安全
        */
        virtual void Close() = 0;
    
        /**
         * @brief                    渲染图像(线程安全)
         * @param data                渲染的二进制数据
         * @param linesize            一行数据的字节数,对于YUV420P的数据格式,就是Y一行的字节数, 如果linesize <= 0, 则根据宽度和像素格式自动算出大小
         * @return                    是否渲染成功
        */
        virtual bool Draw(const unsigned char *data, int linesize = 0) = 0;
    
        /**
         * @brief 提供一个接口用于显示缩放
         * @param w 实际显示的宽度
         * @param h 实际显示的高度
        */
        void Scale(int w, int h)
        {
            scale_w_ = w;
            scale_h_ = h;
        }
    
    
        /**
         * @brief 处理窗口退出事件
         * @return 是否已退出
        */
        virtual bool isExit() = 0;
    
    protected:
        int width_ = 0;                // 材质的宽度
        int height_ = 0;            // 材质的高度
        Format fmt_ = RGBA;            // 像素格式
    
        std::mutex    mtx_;            // 用于确保线程安全
    
        int scale_w_ = 0;            // 实际显示的宽度
        int scale_h_ = 0;            // 实际显示的高度
    };
    
    
    #endif 

    xvideo_view.cpp

    #include "xvideo_view.h"
    #include "xsdl.h"
    
    XVideoView* XVideoView::Create(RenderType type)
    {
        switch (type)
        {
        case XVideoView::SDL:
            return new XSDL();
            break;
        default:
            break;
        }
    
    
        // 如果不支持的话,就直接返回nullptr
        return nullptr;
    }

    xsdl.h

    #ifndef XSDL_H
    #define XSDL_H
    
    #include "xvideo_view.h"
    
    struct SDL_Window;
    struct SDL_Renderer;
    struct SDL_Texture;
    class XSDL : public XVideoView
    {
    
    public:
        /**
        * @brief                    初始化渲染接口(线程安全)
        * @param w                    窗口宽度
        * @param h                    窗口高度
        * @param fmt                绘制的像素格式(要绘制的图像的像素格式)
        * @param win_id                窗口句柄,如果为空,则创建窗口
        * @return                    是否创建成功
        */
        bool Init(int w, int h, Format fmt = RGBA, void* win_id = nullptr) override;
    
        /**
         * @brief                    清理的接口
        */
        void Close() override;
    
        /**
         * @brief                    渲染图像(线程安全)
         * @param data                渲染的二进制数据
         * @param linesize            一行数据的字节数,对于YUV420P的数据格式,就是Y一行的字节数, 如果linesize <= 0, 则根据宽度和像素格式自动算出大小
         * @return                    是否渲染成功
        */
        bool Draw(const unsigned char* data, int linesize = 0) override;
    
        /**
         * @brief 处理窗口退出事件
         * @return 是否已退出
        */
        bool isExit() override;
    
    private:
        SDL_Window* win_ = nullptr;
        SDL_Renderer* render_ = nullptr;
        SDL_Texture* texture_ = nullptr;
    };
    
    
    #endif

    xsdl.cpp

    #include "xsdl.h"
    
    #include <iostream>
    #include <sdl/SDL.h>
    
    #pragma comment(lib, "SDL2.lib")
    
    using namespace std;
    
    /**
     * @brief 初始化视频库
     * @return 初始化结果
    */
    static bool InitVideo()
    {
        static bool is_first = true;
    
        // 这里使用的是静态变量,表示多次进来使用的是同一个对象
        static mutex mux;
        unique_lock<mutex> sdl_lock(mux);
    
        // 表示已经初始化过了
        if (!is_first) return true;
        
        if (SDL_Init(SDL_INIT_VIDEO))
        {
            cout << SDL_GetError() << endl;
            return false;
        }
    
        // 设定缩放算法, 解决锯齿问题,这里采用的是线性插值算法
        // "0": 临近插值算法
        // "1": 线性插值算法
        // "2":目前与线性插值算法一致
        SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
        return true;
    }
    
    bool XSDL::Init(int w, int h, Format fmt, void* win_id)
    {
        // 判断输入的参数是否合法
        if (w <= 0 || h <= 0)
        {
            cout << "input width or height is error " << endl;
            return false;
        }
    
    
        // 初始化SDL Video 库
        if (!InitVideo())
        {
            cout << "init video failed" << endl;
            return false;
        }
    
        // 确保线程安全
        // unique_lock 相当于是在栈中分配了一个对象 sdl_lock, 只要这个对象一出栈,
        // 就会自动调用析构函数,它在析构函数中会调用 mtx_的 unlock()方法
        unique_lock<mutex> sdl_lock(mtx_);
    
        width_ = w;
        height_ = h;
        fmt_ = fmt;
    
        // 如果再次初始化时发现材质和渲染器已存在,则先对其进行销毁
        // 这样做的目的是为了保证多次初始化能够成功
        if (texture_)
        {
            SDL_DestroyTexture(texture_);
        }
    
        if (render_)
        {
            SDL_DestroyRenderer(render_);
        }
    
        // 1. 创建SDL窗口
        if (!win_)
        {
            if (!win_id)
            {
                // 如果用户没有给出创建窗口的句柄,那我们就自行创建一个新的窗口
                win_ = SDL_CreateWindow("", 
                    SDL_WINDOWPOS_UNDEFINED, 
                    SDL_WINDOWPOS_UNDEFINED,
                    width_,
                    height_,
                    SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE
                );
            }
            else
            {
                // 如果用户给定了创建窗口的句柄,则将画面渲染到用户的给定的控件窗口
                win_ = SDL_CreateWindowFrom(win_id);
            }
        }
    
        if (!win_)
        {
            cerr << SDL_GetError() << endl;
            return false;
        }
    
        // 2. 创建渲染器
        render_ = SDL_CreateRenderer(win_, -1, SDL_RENDERER_ACCELERATED);
        if (!render_)
        {
            cerr << SDL_GetError() << endl;
            return false;
        }
    
        // 3. 创建材质 (存在于显存当中)
        unsigned int sdl_fmt = SDL_PIXELFORMAT_RGBA8888;
        switch (fmt)
        {
        case XVideoView::RGBA:
            sdl_fmt = SDL_PIXELFORMAT_RGBA8888;
            break;
        case XVideoView::ARGB:
            sdl_fmt = SDL_PIXELFORMAT_ARGB32;
            break;
        case XVideoView::YUV420P:
            sdl_fmt = SDL_PIXELFORMAT_IYUV;
            break;
        default:
            break;
        }
    
        texture_ = SDL_CreateTexture(render_, 
            sdl_fmt,                                            // 像素格式
            SDL_TEXTUREACCESS_STREAMING,                        // 频繁修改的渲染
            w, h                                                // 材质的宽高
        );
        if (!texture_)
        {
            cerr << SDL_GetError() << endl;
            return false;
        }
    
        
        // cout << "XSDL init success" << endl;
    
        return true;
    }
    
    bool XSDL::Draw(const unsigned char* data, int linesize)
    {
        if (!data)
        {
            cout << "input data is null" << endl;
            return false;
        }
    
        // 保证线程同步
        unique_lock<mutex> sdl_lock(mtx_);
        
        if (!texture_ || !render_ || !win_ || width_ <= 0 || height_ <= 0)
        {
            cout << "draw failed, param error" << endl;
            return false;
        }
    
        if (linesize <= 0)
        {
            switch (fmt_)
            {
            case XVideoView::RGBA:
            case XVideoView::ARGB:
                linesize = width_ * height_ * 4;
                break;
            case XVideoView::YUV420P:
                linesize = width_;
                break;
            default:
                break;
            }
        }
    
        if (linesize <= 0)
        {
            cout << "linesize is error" << endl;
            return false;
        }
    
        // 复制内存到显存
        auto re = SDL_UpdateTexture(texture_, NULL, data, linesize);
        if (re)
        {
            cout << "update texture failed" << endl;
            return false;
        }
    
        // 清理渲染器
        SDL_RenderClear(render_);
    
        // 如果用户手动设置了缩放,就按照用户设置的大小显示
        // 如果用户没有设置,就传递null, 采用默认的窗口大小
        SDL_Rect *prect = nullptr;
        if (scale_w_ > 0 || scale_h_ > 0)
        {
            SDL_Rect rect;
            rect.x = 0;
            rect.y = 0;
            rect.w = scale_w_;
            rect.h = scale_h_;
            prect = &rect;
        }
        
        
        // 拷贝材质到渲染器
        re = SDL_RenderCopy(render_, texture_, NULL, prect);
        if (re)
        {
            cout << "copy texture failed" << endl;
            return false;
        }
    
        // 显示
        SDL_RenderPresent(render_);
    
        return true;
    }
    
    
    void XSDL::Close()
    {
    
    
        // 保证线程安全
        unique_lock<mutex> sdl_lock(mtx_);
    
    
        // 注意!!! 一定要先清理Texture, 再清理Render, 因为Texture是绑定在Render当中的,
        // 如果先清理render, 再清理Texture, 可能会有问题
        if (texture_)
        {
            SDL_DestroyTexture(texture_);
            texture_ = nullptr;
        }
    
        if (render_)
        {
            SDL_DestroyRenderer(render_);
            render_ = nullptr;
        }
    
        if (win_)
        {
            SDL_DestroyWindow(win_);
            win_ = nullptr;
        }
    
        // cout << "do Close()" << endl;
    }
    
    
    bool XSDL::isExit()
    {
        // 创建一个event用于接收事件
        SDL_Event ev;
        SDL_WaitEventTimeout(&ev, 1);        // 等待1ms, 避免阻塞
        if (ev.type == SDL_QUIT)
        {
            return true;
        }
    
        return false;
    }

    <完>

  • 相关阅读:
    事件的基本概念
    c# 语法 字符串内插
    tcp 的编程例子
    Action 语法的简介
    wcf 的小介绍
    Socket编程
    c# base new 等关键字基础
    虚函数和继承的关系
    arraylist,list ,数组区别
    Python爬虫-selenium模拟豆瓣电影鼠标下拉
  • 原文地址:https://www.cnblogs.com/yongdaimi/p/15591210.html
Copyright © 2011-2022 走看看