多路YUV_RGB文件播放器

渲染接口设计说明

界面端说明

代码 

XVideoView.h

#ifndef XVIDEOVIEW_H
#define XVIDEOVIEW_H

#include <mutex>
#include <fstream>

struct AVFrame;

void MSleep(int ms);
long long NowMs();

/*
 * 视频渲染接口类
 * 隐藏SDL实现
 * 渲染方案可替代
 * 线程安全
 */
class XVideoView
{
public:
	// 和 ffmpeg 中的像素格式的值保持一致
	enum PixelFormat
	{
		YUV420P = 0,
		ARGB = 25,
		RGBA = 26,
		BGRA = 28,
	};

	enum ViewType
	{
		VIEW_SDL = 0
	};

	XVideoView();
	static XVideoView* create(ViewType type = VIEW_SDL);

	/*
	 * 初始化渲染窗口 线程安全 支持重复初始化
	 * @param w 窗口宽度
	 * @param h 窗口高度
	 * @param fmt 渲染图像所使用的像素格式
	 * @return 是否创建成功
	 */
	virtual bool init(int w, int h, PixelFormat fmt = RGBA) = 0;

	void setWinId(void* winId = nullptr);

	// 清理所有需要清理的资源,包括关闭窗口
	virtual void close() = 0;

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

	// 打开文件
	bool open(std::string filePath);

	// 读取一帧数据
	AVFrame* read();

	bool drawAVFrame(AVFrame* frame);

	/*
	 * 渲染图像 线程安全
	 * @param data 要渲染的二进制数据
	 * @param lineSize 一行数据的字节数,对于 YUV420P 就是Y一行字节数
	 * linesize <= 0 就根据宽度和像素格式自动算出大小
	 * return 0为成功
	 */
	virtual bool draw(const unsigned char* data, int lineSize = 0) = 0;
	virtual bool draw(const unsigned char* Yplane, int Ypitch,
		const unsigned char* Uplane, int Upitch,
		const unsigned char* Vplane, int Vpitch) = 0;
	void scale(int w, int h);
	int renderFps();

protected:
	void* m_winId;				// 窗口句柄
	int m_width;				// 材质宽高
	int m_height;
	int m_scaleWid;				// 显示大小
	int m_scaleHgh;
	PixelFormat m_fmt;
	std::mutex m_mtx;			// 保证线程安全
	long long m_beginMs = 0;    // 起始时间
	int m_count = 0;            // 一秒的帧数
	int m_renderFps;            // 显示帧率

private:
	std::ifstream m_ifs;
	AVFrame* m_frame;
};

#endif

XVideoView.cpp

#include <thread>
#include <iostream>
#include "XVideoView.h"
#include "XSDL.h"

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

using namespace std;

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

void MSleep(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));
}

XVideoView::XVideoView()
{
	m_winId = nullptr;
	m_width = 0;
	m_height = 0;
	m_fmt = ARGB;
	m_scaleWid = 0;
	m_scaleHgh = 0;
	m_beginMs = 0;
	m_count = 0;
	m_renderFps = 0;
	m_frame = nullptr;
}

void XVideoView::setWinId(void* winId)
{
	m_winId = winId;
}

XVideoView* XVideoView::create(ViewType type)
{
	XVideoView* ret = nullptr;

	switch (type)
	{
		case VIEW_SDL:
			ret = new XSDL();  // 使用 SDL 方式进行渲染
			break;
		default:
			break;
	}

	return ret;
}

bool XVideoView::open(std::string filePath)
{
	bool ret = true;

	if (m_ifs.is_open())
	{
		m_ifs.close();
	}

	m_ifs.open(filePath, ios::in | ios::binary);
	ret = m_ifs.is_open();

	return ret;
}

AVFrame* XVideoView::read()
{
	AVFrame* ret = nullptr;
	int r = 0;

	if (m_ifs.is_open() && (m_width > 0) && (m_height > 0)) 
	{
		if (m_frame != nullptr)
		{
			/* 需要重新创建 frame */
			if ((m_frame->width != m_width) || (m_frame->height != m_height) || (m_frame->format != m_fmt))
			{
				av_frame_free(&m_frame);
			}
			else
			{
				ret = m_frame;
			}
		}

		if (m_frame == nullptr)
		{
			m_frame = av_frame_alloc();
			m_frame->width = m_width;
			m_frame->height = m_height;
			m_frame->format = m_fmt;

			if (m_fmt == PixelFormat::YUV420P)
			{
				m_frame->linesize[0] = m_width;
				m_frame->linesize[1] = m_width / 2;
				m_frame->linesize[2] = m_width / 2;
			}
			else
			{
				m_frame->linesize[0] = m_width * 4;
			}

			r = av_frame_get_buffer(m_frame, 0);

			if (r == 0)
			{
				ret = m_frame;
			}
			else
			{
				char buf[1024] = { 0 };

				av_strerror(r, buf, sizeof(buf) - 1);
				cerr << buf << endl;
			}
		}

		if (ret != nullptr)
		{
			if (m_fmt == PixelFormat::YUV420P)
			{
				m_ifs.read((char*)m_frame->data[0], m_frame->width * m_frame->height);
				m_ifs.read((char*)m_frame->data[1], m_frame->width * m_frame->height / 4);
				m_ifs.read((char*)m_frame->data[2], m_frame->width * m_frame->height / 4);
			}
			else
			{
				m_ifs.read((char*)m_frame->data[0], m_width * m_height * 4);
			}

			if(m_ifs.gcount() == 0)
			{
				ret = nullptr;
			}
		}
	}

	return ret;
}

bool XVideoView::drawAVFrame(AVFrame* frame)
{
	bool ret = (frame != nullptr);

	if (ret)
	{
		m_count++;

		if (m_beginMs == 0)
		{
			m_beginMs = NowMs();  // 开始计时
		}
		else if ((NowMs() - m_beginMs >= 1000))  // 1s 刷新了多少帧
		{
			m_renderFps = m_count;
			m_count = 0;
			m_beginMs = NowMs();
		}

		switch (frame->format)
		{
			case AV_PIX_FMT_ARGB:
			case AV_PIX_FMT_RGBA:
			case AV_PIX_FMT_BGRA:
				ret = draw(frame->data[0], frame->linesize[0]);
				break;
			case AV_PIX_FMT_YUV420P:
				ret = draw(frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1], frame->data[2], frame->linesize[2]);
				break;
			default:
				ret = false;
				break;
		}
	}

	return ret;
}

void XVideoView::scale(int w, int h)
{
	m_scaleWid = w;
	m_scaleHgh = h;
}

int XVideoView::renderFps()
{
	return m_renderFps;
}

XSDL.h

#ifndef XSDL_H
#define XSDL_H

#include "XVideoView.h"

class SDL_Window;
class SDL_Renderer;
class SDL_Texture;

class XSDL : public XVideoView
{
public:
	XSDL();

	/*
	 * 初始化渲染窗口 线程安全
	 * @param w 窗口宽度
	 * @param h 窗口高度
	 * @param fmt 渲染图像所使用的像素格式
	 * @param winId 窗口的句柄,为空则创建新一个窗口
	 * @return 是否创建成功
	 */
	bool init(int w, int h, PixelFormat fmt = RGBA) override;

	// 清理所有清理的资源,包括关闭窗口
	void close() override;

	// 处理窗口退出事件
	bool isExit() override;

	/*
	 * 渲染图像 线程安全
	 * @param data 要渲染的二进制数据
	 * @param lineSize 一行数据的字节数,对于 YUV420P 就是Y一行字节数
	 * linesize <= 0 就根据宽度和像素格式自动算出大小
	 * return 0为成功
	 */
	bool draw(const unsigned char* data, int lineSize = 0) override;
	bool draw(const unsigned char* Yplane, int Ypitch,
		const unsigned char* Uplane, int Upitch,
		const unsigned char* Vplane, int Vpitch) override;

private:
	bool initVideo();

	SDL_Window* m_win;
	SDL_Renderer* m_render;
	SDL_Texture* m_texture;
};

#endif

XSDL.cpp

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

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

using namespace std;

XSDL::XSDL()
{
	m_win = nullptr;
	m_render = nullptr;
	m_texture = nullptr;
}

bool XSDL::initVideo()
{
	static bool first_init = false;
	static mutex mtx;

	unique_lock<mutex> sdl_lock(mtx);  // sdl_lock 在创建时自动加锁,生命周期结束时自动解锁,为了解决锁还没释放函数就返回可能导致的死锁问题

	if (!first_init)
	{
		first_init = (SDL_Init(SDL_INIT_VIDEO) == 0);

		if (first_init)
		{
			//设定缩放算法,解决锯齿问题,线性插值算法
			SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
		}
		else
		{
			cerr << SDL_GetError() << endl;
		}
	}

	return first_init;
}

bool XSDL::init(int w, int h, PixelFormat fmt)
{
	bool ret = ((w > 0) && (h > 0) && initVideo());
	
	if (ret)
	{

		// 确保线程安全
		unique_lock<mutex> sdl_lock(m_mtx);  

		/* 可能已经初始化,先进行清理操作 */
		if (m_texture)
		{
			SDL_DestroyTexture(m_texture);
			m_texture = nullptr;
		}

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

		m_width = w;
		m_height = h;
		m_fmt = fmt;

		if (m_win == nullptr)
		{
			/* 创建窗口 */
			if (m_winId)
			{
				// 渲染到控件窗口
				m_win = SDL_CreateWindowFrom(m_winId);
			}
			else
			{
				// 新建窗口
				m_win = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w, h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
			}
		}

		if (!m_win)
		{
			cerr << SDL_GetError() << endl;
			ret = false;
		}

		/* 创建渲染器 */
		if (ret)
		{
			m_render = SDL_CreateRenderer(m_win, -1, SDL_RENDERER_ACCELERATED);

			if (!m_render)
			{
				cerr << SDL_GetError() << endl;
				ret = false;
			}
		}

		/* 创建材质 (显存) */
		if (ret)
		{
			unsigned int pixFmt = SDL_PIXELFORMAT_ARGB32;

			switch (fmt)
			{
				case XVideoView::ARGB:
					break;
				case XVideoView::RGBA:
					pixFmt = SDL_PIXELFORMAT_RGBA32;
					break;
				case XVideoView::BGRA:
					pixFmt = SDL_PIXELFORMAT_BGRA32;
                    break;
				case XVideoView::YUV420P:
					pixFmt = SDL_PIXELFORMAT_IYUV;
					break;
			}

			m_texture = SDL_CreateTexture(m_render, pixFmt, SDL_TEXTUREACCESS_STREAMING, w, h);  // 这里的 w, h 为分辨率

			if (!m_texture)
			{
				cerr << SDL_GetError() << endl;
				ret = false;
			}
		}	
	}

	return ret;
}

void XSDL::close()
{
	unique_lock<mutex> sdl_lock(m_mtx);

	if (m_texture)
	{
		SDL_DestroyTexture(m_texture);
		m_texture = nullptr;
	}

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

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

bool XSDL::isExit()
{
	bool ret = false;
	SDL_Event evt;
	SDL_WaitEventTimeout(&evt, 1);

	if (evt.type == SDL_QUIT)
	{
		ret = true;
	}

	return ret;
}

bool XSDL::draw(const unsigned char* data, int lineSize)
{
	unique_lock<mutex> sdl_lock(m_mtx);
	bool ret = (data && (m_width > 0) && (m_height > 0) &&  m_win && m_render && m_texture);

	if (ret)
	{
		if (lineSize <= 0)
		{
			switch (m_fmt)
			{
				case XVideoView::RGBA:
				case XVideoView::ARGB:
				case XVideoView::BGRA:
					lineSize = m_width * 4;
					break;
				case XVideoView::YUV420P:
					lineSize = m_width;
					break;
				default:
					break;
			}

			if (lineSize <= 0)
			{
				cerr << "Param lineSize is invalid ...\n" << endl;
				ret = false;
			}
		}

		if (ret)
		{
			// 复制内存到显存中
			ret = (SDL_UpdateTexture(m_texture, nullptr, data, lineSize) == 0);

			if (ret)
			{
				SDL_Rect rect;
				SDL_Rect* pr = nullptr;

				rect.x = 0;
				rect.y = 0;
				rect.w = m_scaleWid;  // rect.w, rect.h 为显示的尺寸大小
				rect.h = m_scaleHgh;

				/* 默认渲染尺寸为窗口大小,若用户设置了缩放尺寸,则使用用户设定的尺寸来渲染 */
				if ((m_scaleWid > 0) && (m_scaleHgh > 0))
				{
					pr = &rect;
				}

				// 清空屏幕
				SDL_RenderClear(m_render);

				// 将材质复制到渲染器
				ret = (SDL_RenderCopy(m_render, m_texture, nullptr, pr) == 0);

				if (ret)
				{
					SDL_RenderPresent(m_render);
				}
				else
				{
					cerr << SDL_GetError() << endl;
				}
			}
			else
			{
				cerr << SDL_GetError() << endl;
			}
		}
	}

	return ret;
}

bool XSDL::draw(const unsigned char* Yplane, int Ypitch,
	const unsigned char* Uplane, int Upitch,
	const unsigned char* Vplane, int Vpitch)
{
	bool ret = (Yplane && (Ypitch > 0) && Uplane && (Upitch > 0) && Vplane && (Vpitch > 0) && (m_width > 0) && (m_height > 0) && m_win && m_render && m_texture);

	if (ret)
	{
		// 复制内存到显存中
		ret = (SDL_UpdateYUVTexture(m_texture, nullptr, Yplane, Ypitch, Uplane, Upitch, Vplane, Vpitch) == 0);

		if (ret)
		{
			SDL_Rect rect;
			SDL_Rect* pr = nullptr;

			rect.x = 0;
			rect.y = 0;
			rect.w = m_scaleWid;  // rect.w, rect.h 为显示的尺寸大小
			rect.h = m_scaleHgh;

			/* 默认渲染尺寸为窗口大小,若用户设置了缩放尺寸,则使用用户设定的尺寸来渲染 */
			if ((m_scaleWid > 0) && (m_scaleHgh > 0))
			{
				pr = &rect;
			}

			// 清空屏幕
			SDL_RenderClear(m_render);

			// 将材质复制到渲染器
			ret = (SDL_RenderCopy(m_render, m_texture, nullptr, pr) == 0);

			if (ret)
			{
				SDL_RenderPresent(m_render);
			}
			else
			{
				cerr << SDL_GetError() << endl;
			}
		}
		else
		{
			cerr << SDL_GetError() << endl;
		}
	}

	return ret;
}

SdlQtRGB.h

#pragma once

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

class SdlQtRGB : public QWidget
{
    Q_OBJECT

public:
    SdlQtRGB(QWidget *parent = Q_NULLPTR);
    void open(int i);
    ~SdlQtRGB();

signals:
    void viewSignal();

public slots:
    void open1();
    void open2();
    void viewChanged();

private:
    Ui::SdlQtRGBClass ui;
    std::thread m_th;
    bool m_isExit;

    void resizeEvent(QResizeEvent* evt) override;
    void thMain();
};

SdlQtRGB.cpp

#include "SdlQtRGB.h"
#include "XVideoView.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <QMessageBox>
#include <QSpinBox>
#include <QFileDialog>
#include <vector>

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

using namespace std;

static vector<XVideoView*> views;

SdlQtRGB::SdlQtRGB(QWidget *parent)
    : QWidget(parent), m_isExit(false)
{
    int r = 0;

    ui.setupUi(this);

    connect(this, &SdlQtRGB::viewSignal, this, &SdlQtRGB::viewChanged);  // 绑定渲染信号槽

    // ui.video1->resize(ui.width1->value(), ui.height1->value());
    // ui.video2->resize(ui.width2->value(), ui.height2->value());

    /* 2路渲染 */
    views.push_back(XVideoView::create());
    views.push_back(XVideoView::create());
    views[0]->setWinId((void*)ui.video1->winId());
    views[1]->setWinId((void*)ui.video2->winId());

    m_th = std::thread(&SdlQtRGB::thMain, this);  // 子线程去渲染
}

void SdlQtRGB::open(int i)
{
    QString file_path = QFileDialog::getOpenFileName();
    int w = 0;
    int h = 0;
    QString pix = "";
    XVideoView::PixelFormat fmt = XVideoView::YUV420P;

    if (!file_path.isEmpty())
    {
        /* 打开文件 */
        if (views[i]->open(file_path.toStdString()))
        {
            if (i == 0)
            {
                w = ui.width1->value();
                h = ui.height1->value();
                pix = ui.pixfmt1->currentText();
            }
            else if (i == 1)
            {
                w = ui.width2->value();
                h = ui.height2->value();
                pix = ui.pixfmt2->currentText();
            }

            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);
        }
        else
        {
            QMessageBox::critical(this, "open file error", "Can not open file!");
        }
    }
}

void SdlQtRGB::open1()
{
    open(0);
}

void SdlQtRGB::open2()
{
    open(1);
}

void SdlQtRGB::thMain()
{
    while (!m_isExit)
    {
        viewSignal();  // 触发信号去渲染
        MSleep(1);    
    }
}

void SdlQtRGB::viewChanged()
{
    static int fps[32] = { 0 };         // 实际的帧率
    static int last_pst[32] = { 0 };    // 上一次渲染的时间,单位为 ms

    for (int i = 0; i < views.size(); i++)
    {
        AVFrame* frame = nullptr;
        stringstream ss;

        ss << "fps: " << views[i]->renderFps();

        /* 显示实际的帧率并且获取用户设置的帧率 */
        if (i == 0)
        {
            ui.show_fps1->setText(ss.str().c_str());
            fps[0] = ui.set_fps1->value();
        }
        else if (i == 1)
        {
            ui.show_fps2->setText(ss.str().c_str());
            fps[1] = ui.set_fps2->value();
        }

        if ((NowMs() - last_pst[i]) >= (1000 / fps[i]))  // 到了需要渲染的时间
        {
            last_pst[i] = NowMs();
            frame = views[i]->read();

            if (frame != nullptr)
            {
                views[i]->drawAVFrame(frame);
            }
        }
    }
}

void SdlQtRGB::resizeEvent(QResizeEvent* evt)
{

}

SdlQtRGB::~SdlQtRGB()
{
    m_isExit = true;
    m_th.join();
}

效果

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值