1-5、多线程控制帧率渲染

这篇博客介绍了如何使用XVideoView类库进行视频渲染,该库隐藏了SDL实现并支持线程安全。文章详细展示了XVideoView的接口,包括不同格式的视频帧渲染、工厂模式创建渲染器,以及具体的SDL实现类XSDL。同时,还提供了一个使用Qt的示例,展示如何在Qt应用中集成XVideoView进行视频播放。
摘要由CSDN通过智能技术生成

xvideo_view.h

#pragma once

#include <mutex>

/// <summary>
/// 视频渲染接口
/// 1、隐藏SDL实现
/// 2、渲染方式可替代
/// 3、线程安全
/// </summary>
struct AVFrame;

void MSleep(unsigned int ms);

class XVideoView {
public:
    enum Format {
        RGBA = 0,
        ARGB,
        YUV420P
    };

    // 产品类型
    enum RenderType {
        SDL = 0
    };

    // 工厂类
    static XVideoView* Creat(RenderType type = SDL);
    bool DrawFrame(AVFrame* frame);

    // 抽象产品类
    virtual bool Init(int w, int h, Format fmt = RGBA, void* win_id = nullptr) = 0;
    virtual bool Close() = 0;
    virtual bool IsExit() = 0;
    virtual bool Draw(const unsigned char* data, int linesize = 0) = 0;
    virtual bool Draw(const unsigned char* y, int yPitch, const unsigned char* u, int uPitch, const unsigned char* v, int vPitch) = 0;
    //void Scale(int w, int h)
    //{
    //    scale_w_ = w;
    //    scale_h_ = h;
    //}

    int render_fps() {
        return render_fps_;
    }

protected:
    int width_ = 0;
    int height_ = 0;
    Format fmt_ = RGBA;
    std::mutex mtx_;    //确保线程安全(不在头文件中引入命名空间)
    int scale_w_ = 0;   //显示大小
    int scale_h_ = 0;

    int render_fps_ = 0;
    long beg_ms_ = 0;
    int count_ = 0;

};

xvideo_view.cpp

#include "xvideo_view.h"
#include "xsdl.h"
#include <thread>

using namespace std;

extern "C"
{
#include <libavcodec/avcodec.h>
}
#pragma comment(lib, "avutil.lib")

void MSleep(unsigned int ms)
{
    auto beg = clock();
    for (int i = 0; i < ms; i++) {
        this_thread::sleep_for(1ms);
        if ((clock() - beg) >= ms) {
            break;
        }       
    }
}

int CountFPS() 
{
    static int fps = 0;
    static int lastTime = clock();  // ms
    static int frameCount = 0;

    ++frameCount;

    int currTime = clock();
    if (currTime - lastTime > 1000) { // 取固定时间间隔为1秒
        fps = frameCount;
        frameCount = 0;
        lastTime = currTime;
    }

    return fps;
}

XVideoView* XVideoView::Creat(RenderType type)
{
    switch (type) {
    case XVideoView::SDL:
        return new XSDL();
        break;

    default:
        break;
    }

    return nullptr;
}

bool XVideoView::DrawFrame(AVFrame* frame)
{
    if (!frame || !frame->data[0]) {
        return false;
    }

    render_fps_ = CountFPS();   // 计算帧率

    switch (frame->format)
    {
    case AV_PIX_FMT_YUV420P:
        Draw(frame->data[0], frame->linesize[0],// Y
            frame->data[1], frame->linesize[1],	// U
            frame->data[2], frame->linesize[2]	// V
        );
        break;
    case AV_PIX_FMT_BGRA:
        Draw(frame->data[0], frame->linesize[0]);
        break;
    default:
        break;
    }

    return true;
}

xsdl.h

#pragma once

#include "xvideo_view.h"

struct SDL_Window;
struct SDL_Renderer;
struct SDL_Texture;

// 具体产品类
class XSDL :public XVideoView {
public:
    
    /// 初始化渲染窗口 线程安全
    /// @para w 窗口宽度
    /// @para h 窗口高度
    /// @para fmt 绘制的像素格式
    /// @para win_id 窗口句柄,如果为空,创建新窗口
    /// @return 是否创建成功
    bool Init(int w, int h, Format fmt = RGBA, void* win_id = nullptr) override;
    bool Close() override;
    bool IsExit() override;
    //
    /// 渲染图像 线程安全
    ///@para data 渲染的二进制数据
    ///@para linesize 一行数据的字节数,对于YUV420P就是Y一行字节数
    /// linesize<=0 就根据宽度和像素格式自动算出大小
    /// @return 渲染是否成功
    bool Draw(const unsigned char* data, int linesize = 0) override;
    bool Draw(const unsigned char* y, int yPitch,
        const unsigned char* u, int uPitch,
        const unsigned char* v, int vPitch) override;
private:
    SDL_Window* win_ = nullptr;
    SDL_Renderer* render_ = nullptr;
    SDL_Texture* texture_ = nullptr;
};

xsdl.cpp

#include "xsdl.h"
#include <sdl/SDL.h>
#include <iostream>

using namespace std;

#pragma comment(lib,"SDL2.lib")

static bool InitVideo(string& strError)
{
    static bool bOK = false;

    static mutex mux;
    unique_lock<mutex> sdl_lock(mux);

    if (!bOK) {
        if (SDL_Init(SDL_INIT_VIDEO)) {
            strError = SDL_GetError();
            return false;
        }
    }

    // 设定缩放算法(线性插值)
    SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");

    bOK = true;
    return true;
}

bool XSDL::IsExit()
{
    SDL_Event ev;

    SDL_WaitEventTimeout(&ev, 1);
    if (ev.type == SDL_QUIT) {
        return true;
    }

    return false;
}

bool XSDL::Close()
{
    //确保线程安全
    unique_lock<mutex> sdl_lock(mtx_);

    // 依次清理
    if (texture_) {
        SDL_DestroyTexture(texture_);
        texture_ = nullptr;
    }

    if (render_) {
        SDL_DestroyRenderer(render_);
        render_ = nullptr;
    }

    if (win_) {
        SDL_DestroyWindow(win_);
        win_ = nullptr;
    }

    return true;
}

bool XSDL::Init(int w, int h, Format fmt, void* win_id)
{
    string strError = "";

    // 1、初始化视频库
    if (!InitVideo(strError)) {
        return false;
    }

    //确保线程安全
    unique_lock<mutex> sdl_lock(mtx_);
    width_ = w;
    height_ = h;
    fmt_ = fmt;

    if (texture_) {
        SDL_DestroyTexture(texture_);
    }
    if (render_) {
        SDL_DestroyRenderer(render_);
    }

    // 2、创建窗口
    if (!win_) {
        if (win_id) {
            win_ = SDL_CreateWindowFrom(win_id);
            if (!win_) {
                cout << SDL_GetError() << endl;
                return false;
            }
        }
        else {
            win_ = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
                w, h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
            if (!win_) {
                cout << SDL_GetError() << endl;
                return false;
            }
        }
    }
    // 3、创建渲染器
    render_ = SDL_CreateRenderer(win_, -1, SDL_RENDERER_ACCELERATED);
    if (!render_) {
        cout << SDL_GetError() << endl;
        return false;
    }

    // 4、创建材质
    unsigned int sdl_fmt = SDL_PIXELFORMAT_RGBA8888;
    switch (fmt)
    {
    case XVideoView::RGBA:
        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_) {
        cout << SDL_GetError() << endl;
        return false;
    }

    return true;
}

bool XSDL::Draw(const unsigned char* y, int yPitch, const unsigned char* u, int uPitch, const unsigned char* v, int vPitch)
{
    if (!y || !u || !v) {
        cout << "Data Is Empty!" << endl;
        return false;
    }

    unique_lock<mutex> sdl_lock(mtx_);
    if (!win_ || !render_ || !texture_ || width_ <= 0 || height_ <= 0) {
        cout << "Init Is Fail!" << endl;
        return false;
    }

    // 5、渲染
    if (SDL_UpdateYUVTexture(texture_, NULL,
        y, yPitch, u, uPitch, v, vPitch)) {   // 复制内存数据到显存
        cout << SDL_GetError() << endl;
        return false;
    }

    SDL_RenderClear(render_);

    //材质复制到渲染器
    //if (scale_w_ <= 0)scale_w_ = width_;
    //if (scale_h_ <= 0)scale_h_ = height_;

    SDL_Rect sdl_rect;              // 目标尺寸
    sdl_rect.x = 0;
    sdl_rect.y = 0;
    sdl_rect.w = width_;
    sdl_rect.h = height_;
    if (SDL_RenderCopy(render_, texture_, NULL, &sdl_rect)) {
        cout << SDL_GetError() << endl;
        return false;
    }

    SDL_RenderPresent(render_); // 渲染

    return true;
}

bool XSDL::Draw(const unsigned char* data, int linesize)
{
    if (!data) {
        cout << "Data Is Empty!" << endl;
        return false;
    }

    unique_lock<mutex> sdl_lock(mtx_);
    if (!win_ || !render_ || !texture_ || width_ <= 0 || height_ <= 0) {
        cout << "Init Is Fail!" << endl;
        return false;
    }

    if (linesize <= 0) {
        switch (fmt_)
        {
        case XVideoView::RGBA:
        case XVideoView::ARGB:
            linesize = width_ * 4;
            break;
        case XVideoView::YUV420P:
            linesize = width_ * 1;
            break;
        default:
            break;
        }
    }

    // 5、渲染
    if (SDL_UpdateTexture(texture_, NULL, data, linesize)) {   // 复制到显存
        cout << SDL_GetError() << endl;
        return false;
    }

    SDL_RenderClear(render_);

    //材质复制到渲染器
    //if (scale_w_ <= 0)scale_w_ = width_;
    //if (scale_h_ <= 0)scale_h_ = height_;

    SDL_Rect sdl_rect;              // 目标尺寸
    sdl_rect.x = 0;
    sdl_rect.y = 0;
    sdl_rect.w = width_;
    sdl_rect.h = height_;
    if (SDL_RenderCopy(render_, texture_, NULL, &sdl_rect)) {
        cout << SDL_GetError() << endl;
        return false;
    }

    SDL_RenderPresent(render_); // 渲染

    return true;
}

sdlqtrgb.h

#pragma once

#include <QtWidgets/QWidget>
#include "ui_sdlqtrgb.h"
#include <thread>

class SdlQtRgb : public QWidget
{
    Q_OBJECT

public:
    SdlQtRgb(QWidget* parent = Q_NULLPTR);
    ~SdlQtRgb()
    {
        is_exit_ = true;
        //等待渲染线程退出
        th_.join();
    }
    void timerEvent(QTimerEvent* ev) override;  // 重载定时器
    //void resizeEvent(QResizeEvent* ev) override;

    void threadMain(); // 线程函数,用于刷新视频

private:
    Ui::SdlQtRgbClass ui;
    bool is_exit_ = false;
    std::thread th_;


signals:
    void ViewS();

public slots:
    void View();
};

sdlqtrgb.cpp

#include "sdlqtrgb.h"
#include <fstream>
#include <iostream>
#include <QMessageBox>
#include <thread>
#include <sstream>

#include "xvideo_view.h"

extern "C"
{
#include <libavcodec/avcodec.h>
}

using namespace std;

static int sdl_width = 0;
static int sdl_height = 0;
static int pix_size = 2;

static ifstream yuv_file;
static XVideoView* view = nullptr;
static AVFrame* frame = nullptr;
static long long int fileSize = 0;
static QLabel* view_fps = nullptr;

void SdlQtRgb::timerEvent(QTimerEvent* ev)
{
    //yuv_file.read((char*)yuv, sdl_width * sdl_height * 1.5);
    yuv_file.read((char*)frame->data[0], sdl_width * sdl_height); // Y
    yuv_file.read((char*)frame->data[1], sdl_width * sdl_height / 4); // U
    yuv_file.read((char*)frame->data[2], sdl_width * sdl_height / 4); // U

    if (view->IsExit()) {
        view->Close();
        exit(0);
    }

    view->DrawFrame(frame);
}

void SdlQtRgb::View()
{
    //yuv_file.read((char*)yuv, sdl_width * sdl_height * 1.5);
    yuv_file.read((char*)frame->data[0], sdl_width * sdl_height); // Y
    yuv_file.read((char*)frame->data[1], sdl_width * sdl_height / 4); // U
    yuv_file.read((char*)frame->data[2], sdl_width * sdl_height / 4); // U

    if (fileSize == yuv_file.tellg()) {
        yuv_file.seekg(0, ios::beg);
    }

    if (view->IsExit()) {
        view->Close();
        exit(0);
    }

    view->DrawFrame(frame);

    stringstream s;
    s << "fps:" << view->render_fps();
    view_fps->setText(s.str().c_str());

}

void SdlQtRgb::threadMain()
{
    while(!is_exit_) {
        ViewS();
        MSleep(10);
    }
}

//void SdlQtRgb::resizeEvent(QResizeEvent* ev)
//{
//    ui.label->resize(ui.label->size());
//    ui.label->move(0, 0);
//    view->Scale(ui.label->width(), ui.label->height());
//}

SdlQtRgb::SdlQtRgb(QWidget* parent)
    : QWidget(parent)
{
    // 打开yuv文件
    string file = "400_300_25.yuv";
    yuv_file.open(file.c_str(), ios::binary);
    if (!yuv_file) {
        QMessageBox::information(this, "", "Open yuv failed!");
        return;
    }
    
    yuv_file.seekg(0, ios::end);    // 移动到文件末尾
    fileSize = yuv_file.tellg();
    yuv_file.seekg(0, ios::beg);

    ui.setupUi(this);
    connect(this, SIGNAL(ViewS()), this, SLOT(View()));
    view_fps = new QLabel(this);
    view_fps->setText("fps:100");

    sdl_width = ui.label->width();
    sdl_height = ui.label->height();
    ui.label->resize(sdl_width, sdl_height);

    view = XVideoView::Creat();
    view->Init(sdl_width, sdl_height, XVideoView::YUV420P, (void*)ui.label->winId());

    // 创建YUV空间
    frame = av_frame_alloc();
    frame->width = sdl_width;
    frame->height = sdl_height;
    frame->format = AV_PIX_FMT_YUV420P;

    frame->linesize[0] = sdl_width;       //Y
    frame->linesize[1] = sdl_width / 2;   //U
    frame->linesize[2] = sdl_width / 2;   //V

    int err = av_frame_get_buffer(frame, 0);
    if (err) {
        char* buf = { 0 };
        av_strerror(err, buf, sizeof(buf));
        cerr << buf << endl;
    }

    //startTimer(20);

    th_ = std::thread(&SdlQtRgb::threadMain, this);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值