1.头文件:

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 Main();

signals:
    void ViewS();   //信号函数,将任务放入列表
public slots:
    void View();    //显示的槽函数
    void Open1();
    void Open2();
    void Open(int i);
private:
    std::thread th_;
    bool is_exit_ = false;//处理线程退出
    Ui::SdlQtRGBClass ui;
};

xvideoview.h



#ifndef XVIDEO_VIEW_H
#define XVIDEO_VIEW_H
#include <mutex>
#include <fstream>
struct AVFrame;

void MSleep(unsigned int ms);

//获取当前时间戳 毫秒
long long NowMs();


/// 视频渲染接口类
/// 隐藏SDL实现
/// 渲染方案可替代
// 线程安全
class XVideoView
{
public:
    enum Format  //枚举的值和ffmpeg中一致
    {
        YUV420P = 0,
        ARGB = 25,
        RGBA = 26,
        BGRA = 28
    };
    enum RenderType
    {
        SDL = 0
    };
    static XVideoView* Create(RenderType type = SDL);

    
    /// 初始化渲染窗口 线程安全 可多次调用
    /// @para w 窗口宽度
    /// @para h 窗口高度
    /// @para fmt 绘制的像素格式
    /// @para win_id 窗口句柄,如果为空,创建新窗口
    /// @return 是否创建成功
    virtual bool Init(int w, int h,
        Format fmt = RGBA) = 0;

    //清理所有申请的资源,包括关闭窗口
    virtual void Close() = 0;

    //处理窗口退出事件
    virtual bool IsExit() = 0;

    //
    /// 渲染图像 线程安全
    ///@para data 渲染的二进制数据
    ///@para linesize 一行数据的字节数,对于YUV420P就是Y一行字节数
    /// linesize<=0 就根据宽度和像素格式自动算出大小
    /// @return 渲染是否成功
    virtual bool Draw(const unsigned  char* data, int linesize = 0) = 0;
    virtual bool Draw(
        const unsigned  char* y, int y_pitch,
        const unsigned  char* u, int u_pitch,
        const unsigned  char* v, int v_pitch
    ) = 0;


    //显示缩放
    void Scale(int w, int h)
    {
        scale_w_ = w;
        scale_h_ = h;
    }

    bool DrawFrame(AVFrame* frame);

    int render_fps() { return render_fps_; }

    //打开文件
    bool Open(std::string filepath);


    //
    /// 读取一帧数据,并维护AVFrame空间
    /// 每次调用会覆盖上一次数据
    AVFrame* Read();
    void set_win_id(void* win) { win_id_ = win; }
protected:
    void* win_id_ = nullptr; //窗口句柄
    int render_fps_ = 0;       //显示帧率
    int width_ = 0;             //材质宽高
    int height_ = 0;
    Format fmt_ = RGBA;         //像素格式
    std::mutex mtx_;            //确保线程安全
    int scale_w_ = 0;           //显示大小
    int scale_h_ = 0;
    long long beg_ms_ = 0;       //计时开始时间
    int count_ = 0;              //统计显示次数
private:
    std::ifstream ifs_;
    AVFrame* frame_ = nullptr;
};

#endif

xsdl.h



#pragma once


#include "xvideo_view.h"
struct SDL_Window;
struct SDL_Renderer;
struct SDL_Texture;
class XSDL :public XVideoView
{
public:
    void Close() override;
    
    /// 初始化渲染窗口 线程安全
    /// @para w 窗口宽度
    /// @para h 窗口高度
    /// @para fmt 绘制的像素格式
    /// @para win_id 窗口句柄,如果为空,创建新窗口
    /// @return 是否创建成功
    bool Init(int w, int h,
        Format fmt = RGBA) 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 y_pitch,
        const unsigned  char* u, int u_pitch,
        const unsigned  char* v, int v_pitch
    ) override;
    bool IsExit() override;
private:
    SDL_Window* win_ = nullptr;
    SDL_Renderer* render_ = nullptr;
    SDL_Texture* texture_ = nullptr;
};

2.源文件:

sdlqtrgb.cpp


#include "sdlqtrgb.h"
#include <fstream>
#include <iostream>
#include <QMessageBox>
#include <thread>
#include <sstream>
#include <iostream>
#include <QSpinBox>
#include <QFileDialog>
#include "xvideo_view.h"
#include <vector>
#include <sstream>
using namespace std;

static std::vector<XVideoView*> views;
SdlQtRGB::SdlQtRGB(QWidget* parent)
    : QWidget(parent)
{
    ui.setupUi(this);

    //绑定渲染信号槽
    connect(this, SIGNAL(ViewS()), this, SLOT(View()));
    views.push_back(XVideoView::Create());// 创建一个对象并用vector 存储
    views.push_back(XVideoView::Create());
    views[0]->set_win_id((void*)ui.video1->winId());//将video1的窗口句柄赋值给xvideoview对象的窗口句柄
    views[1]->set_win_id((void*)ui.video2->winId());
    th_ = std::thread(&SdlQtRGB::Main, this);
}

void SdlQtRGB::timerEvent(QTimerEvent* ev)
{
}

void SdlQtRGB::View()
{

    static int last_pts[32] = { 0 };//last_pts 用于存放每个视图上次渲染的时间戳,lase_pts[0]表示第一个播放窗口
    static int fps_arr[32] = { 0 };//用于存放每个视图的帧率值。
    fps_arr[0] = ui.set_fps1->value();//获取第一个播放窗口的fps,用fps_arr[0]存储
    fps_arr[1] = ui.set_fps2->value();

    for (int i = 0; i < views.size(); i++)//依次渲染容器中的xvideoview对象
    {
        if (fps_arr[i] <= 0) continue;//invaluable data then pass
        //需要间隔时间
        int ms = 1000 / fps_arr[i];// video1's fps = 100,then ms = 10,意思是100fps,一秒渲染100次,每10ms渲染一次

        //判断是否到了可渲染时间
        if (NowMs() - last_pts[i] < ms)//一秒渲染100次,每10ms渲染一次,若时间没达到10ms那么不继续进行渲染
            continue;
        last_pts[i] = NowMs();//记录下此次的渲染时间

        auto frame = views[i]->Read();//read()执行完后 返回读取了yuv数据的avframe对象给frame,此时frame中已经进行了文件读取有了yuv数据,参考read函数即可
        if (!frame)continue;
        views[i]->DrawFrame(frame);//计算了当前帧率并且进行了渲染
        //显示fps
        stringstream ss;
        ss << "fps:" << views[i]->render_fps();
        if (i == 0)
            ui.fps1->setText(ss.str().c_str());
        else
            ui.fps2->setText(ss.str().c_str());

    }
}

void SdlQtRGB::Main()
{
    while (!is_exit_)
    {
        ViewS();//调用一次view槽函数,再没进行Open函数之前是不能把view槽函数执行完的,因为进行draw之前要进行材质的初始化 Init函数,在Open函数之中
                //一旦执行了Open函数,Init即进行完毕,则一直在执行的线程函数Main则会调用view函数,此时view函数中的draw函数可以进行渲染了

        MSleep(10);//每调用一次槽函数就休眠10ms,用于控制帧率
    }
}

void SdlQtRGB::Open1()
{
    Open(0);
}
void SdlQtRGB::Open2()
{
    Open(1);
}
void SdlQtRGB::Open(int i)
{
    QFileDialog fd;
    auto filename = fd.getOpenFileName();
    if (filename.isEmpty())return;
    cout << filename.toLocal8Bit().data() << endl;
    //打开文件
    if (!views[i]->Open(filename.toLocal8Bit().toStdString()))
    {
        return;
    }
    int w = 0;
    int h = 0;
    QString pix = 0;  //YUV420P RGBA
    if (i == 0)//下拉框的下标0,表示YUV420P,下标1表示RGBA以此类推
    {
        w = ui.width1->value();
        h = ui.height1->value();
        pix = ui.pix1->currentText(); //像素格式
    }
    else
    {
        w = ui.width2->value();
        h = ui.height2->value();
        pix = ui.pix2->currentText();
    }
    XVideoView::Format fmt = XVideoView::YUV420P;
    if (pix == "YUV420P")
    {

    }
    else if (pix == "RGBA")
    {
        fmt = XVideoView::RGBA;
    }
    else if (pix == "ARGB")
    {
        fmt = XVideoView::ARGB;
    }
    else if (pix == "BGRA")
    {
        fmt = XVideoView::BGRA;
    }
    //初始化窗口和材质
    views[i]->Init(w, h, fmt);
}




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

xvideoview.cpp



#include "xsdl.h"
#include <thread>
#include <iostream>
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) / (CLOCKS_PER_SEC / 1000) >= ms)
			break;
	}
}
long long NowMs()
{
	return clock() / (CLOCKS_PER_SEC / 1000);//获取当前时间戳 毫秒
}
AVFrame* XVideoView::Read()
{
	if (width_ <= 0 || height_ <= 0 || !ifs_)return NULL;
	//AVFrame空间已经申请,如果参数发生变化,需要释放空间
	if (frame_)//若分配了
	{
		if (frame_->width != width_
			|| frame_->height != height_
			|| frame_->format != fmt_)
		{
			//释放AVFrame对象空间,和buf引用计数减一
			av_frame_free(&frame_);
		}
	}
	if (!frame_)//若没分配则分配
	{
		//分配对象空间和像素空间
		frame_ = av_frame_alloc();
		frame_->width = width_;
		frame_->height = height_;
		frame_->format = fmt_;
		frame_->linesize[0] = width_ * 4;
		if (frame_->format == AV_PIX_FMT_YUV420P)//YUV420P单独处理,因为他不是平面存储是多层存储需要三个数组存储三个平面
		{
			frame_->linesize[0] = width_; // Y
			frame_->linesize[1] = width_ / 2;//U
			frame_->linesize[2] = width_ / 2;//V
		}

		//生成AVFrame空间,使用默认对齐
		auto re = av_frame_get_buffer(frame_, 0);
		if (re != 0)
		{
			char buf[1024] = { 0 };
			av_strerror(re, buf, sizeof(buf) - 1);
			cout << buf << endl;
			av_frame_free(&frame_);
			return NULL;

		}
	}
	if (!frame_)return NULL;

	//读取一帧数据
	if (frame_->format == AV_PIX_FMT_YUV420P)
	{
		ifs_.read((char*)frame_->data[0],
			frame_->linesize[0] * height_);	//Y
		ifs_.read((char*)frame_->data[1],
			frame_->linesize[1] * height_ / 2);	//U
		ifs_.read((char*)frame_->data[2],
			frame_->linesize[2] * height_ / 2);	//V
	}//执行完后 avframe中就保存了从2.yuv中读取的yuv数据了
	else	//RGBA ARGB BGRA 32
	{
		ifs_.read((char*)frame_->data[0], frame_->linesize[0] * height_);
	}

	if (ifs_.gcount() == 0)
		return NULL;
	return frame_;




}

//打开文件
bool XVideoView::Open(std::string filepath)
{
	if (ifs_.is_open())
	{
		ifs_.close();
	}
	ifs_.open(filepath, ios::binary);
	return ifs_.is_open();//ifs_绑定了所选的文件 如 2.yuv
}
XVideoView* XVideoView::Create(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;
	count_++;
	if (beg_ms_ <= 0)
	{
		beg_ms_ = clock();
	}
	//计算显示帧率
	else if ((clock() - beg_ms_) / (CLOCKS_PER_SEC / 1000) >= 1000) //一秒计算一次fps
	{
		render_fps_ = count_;
		count_ = 0;
		beg_ms_ = clock();
	}

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

xsdl.cpp



#include "xsdl.h"
#include "sdl/SDL.h"
#include <iostream>
using namespace std;
#pragma comment(lib,"SDL2.lib")
static bool InitVideo()
{
    static bool is_first = true;
    static mutex mux;
    unique_lock<mutex> sdl_lock(mux);
    if (!is_first)return true;
    is_first = false;
    if (SDL_Init(SDL_INIT_VIDEO))
    {
        cout << SDL_GetError() << endl;
        return false;
    }
    //设定缩放算法,解决锯齿问题,线性插值算法
    SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
    return true;
}
bool XSDL::IsExit()
{
    SDL_Event ev;
    SDL_WaitEventTimeout(&ev, 1);
    if (ev.type == SDL_QUIT)
        return true;
    return false;
}
void 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;
    }
}

bool XSDL::Init(int w, int h, Format fmt)
{
    if (w <= 0 || h <= 0)return false;
    //初始化SDL 视频库
    InitVideo();

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

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

    ///1 创建窗口
    if (!win_)
    {
        if (!win_id_)
        {
            //新建窗口
            win_ = SDL_CreateWindow("",
                SDL_WINDOWPOS_UNDEFINED,
                SDL_WINDOWPOS_UNDEFINED,
                w, h, 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;
    }
    //创建材质 (显存)
    unsigned int sdl_fmt = SDL_PIXELFORMAT_RGBA8888;
    switch (fmt)
    {
    case XVideoView::RGBA:
        sdl_fmt = SDL_PIXELFORMAT_RGBA32;
        break;
    case XVideoView::BGRA:
        sdl_fmt = SDL_PIXELFORMAT_BGRA32;
        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;
    }
    return true;
}
bool XSDL::Draw(
    const unsigned  char* y, int y_pitch,
    const unsigned  char* u, int u_pitch,
    const unsigned  char* v, int v_pitch
)
{
    //参数检查
    if (!y || !u || !v)return false;
    unique_lock<mutex> sdl_lock(mtx_);
    if (!texture_ || !render_ || !win_ || width_ <= 0 || height_ <= 0)
        return false;

    //复制内存到显显存
    auto re = SDL_UpdateYUVTexture(texture_,
        NULL,
        y, y_pitch,
        u, u_pitch,
        v, v_pitch);
    if (re != 0)
    {
        cout << SDL_GetError() << endl;
        return false;
    }
    //清空屏幕
    SDL_RenderClear(render_);

    //材质复制到渲染器

    SDL_Rect rect;
    SDL_Rect* prect = nullptr;
    if (scale_w_ > 0)  //用户手动设置缩放
    {
        rect.x = 0; rect.y = 0;
        rect.w = scale_w_;//渲染的宽高,可缩放
        rect.h = scale_w_;
        prect = &rect;
    }
    re = SDL_RenderCopy(render_, texture_, NULL, prect);
    if (re != 0)
    {
        cout << SDL_GetError() << endl;
        return false;
    }
    SDL_RenderPresent(render_);
    return true;
}
bool XSDL::Draw(const unsigned char* data, int linesize)
{
    if (!data)return false;
    unique_lock<mutex> sdl_lock(mtx_);
    if (!texture_ || !render_ || !win_ || width_ <= 0 || height_ <= 0)
        return false;
    if (linesize <= 0)
    {
        switch (fmt_)
        {
        case XVideoView::RGBA:
        case XVideoView::ARGB:
            linesize = width_ * 4;
            break;
        case XVideoView::YUV420P:
            linesize = width_;
            break;
        default:
            break;
        }
    }
    if (linesize <= 0)
        return false;
    //复制内存到显显存
    auto re = SDL_UpdateTexture(texture_, NULL, data, linesize);
    if (re != 0)
    {
        cout << SDL_GetError() << endl;
        return false;
    }
    //清空屏幕
    SDL_RenderClear(render_);

    //材质复制到渲染器

    SDL_Rect rect;
    SDL_Rect* prect = nullptr;
    if (scale_w_ > 0)  //用户手动设置缩放
    {
        rect.x = 0; rect.y = 0;
        rect.w = scale_w_;//渲染的宽高,可缩放
        rect.h = scale_w_;
        prect = &rect;
    }
    re = SDL_RenderCopy(render_, texture_, NULL, prect);
    if (re != 0)
    {
        cout << SDL_GetError() << endl;
        return false;
    }
    SDL_RenderPresent(render_);
    return true;
}

main.cpp



#include "sdlqtrgb.h"
#include <QtWidgets/QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    SdlQtRGB w;
    w.show();
    return a.exec();
}

3.运行结果:

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部