多线程控制帧率渲染AVFrame的YUV数据

代码

XVideoView.h

#ifndef XVIDEOVIEW_H
#define XVIDEOVIEW_H

#include <mutex>

struct AVFrame;

void MSleep(int ms);

/*
 * 视频渲染接口类
 * 隐藏SDL实现
 * 渲染方案可替代
 * 线程安全
 */
class XVideoView
{
public:
	enum PixelFormat
	{
		RGBA = 0,
		ARGB,
		YUV420P
	};

	enum ViewType
	{
		VIEW_SDL = 0
	};

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

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

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

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

	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:
	int m_width;				// 材质宽高
	int m_height;
	int m_scaleWid;				// 显示大小
	int m_scaleHgh;
	PixelFormat m_fmt;
	std::mutex m_mtx;			// 保证线程安全
	int m_renderFps;            // 显示帧率
	long long m_beginMs;        // 起始时间
	int m_count;                // 一秒的帧数
};

#endif

XVideoView.cpp

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;
		}
	}
}

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

	if (ret)
	{
		m_count++;

		if (m_beginMs == 0)
		{
			m_beginMs = clock();  // 开始计时
		}
		else if (((clock() - m_beginMs) / (CLOCKS_PER_SEC / 1000)) >= 1000)  // 1s
		{
			m_renderFps = m_count;
			m_count = 0;
			m_beginMs = clock();
		}

		switch (frame->format)
		{
			case AV_PIX_FMT_ARGB:
			case AV_PIX_FMT_RGBA:
				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;
}

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);
    ~SdlQtRGB();

signals:
    void viewSignal();

public slots:
    void viewChanged();

private:
    Ui::SdlQtRGBClass ui;
    std::thread m_th;
    bool 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>

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

using namespace std;

static AVFrame* frame = nullptr;;
static int sdl_width = 0;
static int sdl_height = 0;
static int pixel_size = 2;
static ifstream yuv_file;
static long long file_size = 0;
static XVideoView* view = nullptr;
static QLabel* fps_lbl = nullptr;    // 此控件用于显示播放帧率
static QSpinBox* fps_sbx = nullptr;  // 此控件用于调整播放帧率
static int fps = 25;  // 播放帧率

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

    yuv_file.open("400_300_25.yuv", ios::in | ios::binary);

    if (!yuv_file)
    {
        QMessageBox::information(this, "information", "open 400_300_25.yuv failed!");

        return;
    }

    yuv_file.seekg(0, ios::end);
    file_size = yuv_file.tellg();
    yuv_file.seekg(0, ios::beg);

    ui.setupUi(this);

    fps_lbl = new QLabel(this);
    fps_lbl->setText("fps:    ");

    fps_sbx = new QSpinBox(this);  
    fps_sbx->move(200, 0);
    fps_sbx->setValue(25);
    fps_sbx->setRange(1, 300);

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

    sdl_width = 400;
    sdl_height = 300;

    ui.label->resize(400, 300);

    view = XVideoView::create();

    if (view)
    {    
        view->init(sdl_width, sdl_height, XVideoView::YUV420P);
        view->init(sdl_width, sdl_height, XVideoView::YUV420P, (void*)ui.label->winId());
    }
    
    frame = av_frame_alloc();

    frame->width = 400;
    frame->height = 300;
    frame->format = AV_PIX_FMT_YUV420P;

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

    r = av_frame_get_buffer(frame, 0);

    if (r != 0)
    {
        char buf[1024] = {0};

        av_strerror(r, buf, sizeof(buf));

        cerr << buf << endl;
    }

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

void SdlQtRGB::thMain()
{
    while (!isExit)
    {
        viewSignal();  // 触发信号去渲染

        if (fps > 0)
        {
            MSleep(1000 / fps);
        }
        else
        {
            MSleep(10);
        }
    }
}

void SdlQtRGB::viewChanged()
{
    stringstream ss;

    /* AVFrame 中 YUV 是单独存放的,所以需要分开进行读取 */
    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);  // V

    /* 循环播放 */
    if (yuv_file.tellg() == file_size)
    {
        yuv_file.seekg(0, ios::beg);
    }

    if (view)
    {
        ss << "fps: " << view->renderFps();
        fps_lbl->setText(ss.str().c_str());  // 显示帧率

        fps = fps_sbx->value();

        view->drawAVFrame(frame);

        if (view->isExit())
        {
            isExit = true;
            view->close();
            delete view;

            exit(0);
        }
    }
}

void SdlQtRGB::resizeEvent(QResizeEvent* evt)
{
    ui.label->resize(size());
    ui.label->move(0, 0);
}

SdlQtRGB::~SdlQtRGB()
{
    m_th.join();
    av_frame_free(&frame);
}

使用另外一个线程通过信号与槽的方式去渲染 AVFrame 的 YUV 数据,并且可以由用户指定视频播放帧率,并且把播放帧率显示出来。

效果显示

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值