硬件GPU加速编码DXVA2并进行渲染

解码硬件加速 DXVA2

硬件GPU加速编码DXVA2并进行渲染_第1张图片

XVideoView.cpp

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:
			case AV_PIX_FMT_RGB24:
			case AV_PIX_FMT_BGR24:
				ret = draw(frame->data[0], frame->linesize[0]);
				break;
			case AV_PIX_FMT_NV12:
				if (m_cache == nullptr)
				{
					m_cache = new unsigned char[4096 * 2000 * 1.5];  // 分配足够大的空间,使之适应于绝大多数帧,不需要每次都重新分配空间
				}

				if (frame->linesize[0] == frame->width)
				{
					memcpy(m_cache, frame->data[0], frame->linesize[0] * frame->height);
					memcpy(m_cache + frame->linesize[0] * frame->height, frame->data[1], frame->linesize[1] * frame->height / 2);
				}
				else  // 需要处理字节对齐的问题, 逐行复制
				{
					for (int i = 0; i < frame->height; i++)
					{
						memcpy(m_cache + i * frame->width, frame->data[0] + i * frame->linesize[0], frame->width);
					}

					for (int i = 0; i < frame->height / 2; i++)
					{
						memcpy(m_cache + frame->width * frame->height + i * frame->width, frame->data[1] + i * frame->linesize[1], frame->width);
					}
				}

				ret = draw(m_cache, 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;
}

 119_test_decode_view _hw.cpp

#include 
#include 
#include "XVideoView.h"
#include "XSDL.h"

using namespace std;

extern "C"  // 指定函数是 C 语言函数,函数目标名不包含重载标识,C++ 中调用 C 函数需要使用 extern "C"
{
	// 引用 ffmpeg 头文件
	#include "libavcodec/avcodec.h"
}

// 预处理指令导入库
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avutil.lib")

int main()
{
	//1 分割h264 存入AVPacket
	// ffmpeg -i v1080.mp4 -s 400x300 test.h264
	string file_name = "test.h264";
	ifstream ifs;
	AVCodec* codec = nullptr;
	AVCodecID codec_id = AV_CODEC_ID_H264;
	AVCodecContext* context = nullptr;
	AVCodecParserContext* parser = nullptr;
	AVPacket* packet = nullptr;
	AVFrame* frame = nullptr;
	AVFrame* hw_frame = nullptr;
	AVFrame* p_frame = nullptr;  // 为了同时支持硬解码和软解码
	unsigned char inbuf[4096] = { 0 };
	int inbuf_len = 0;
	int ret = 0;
	long long begin = 0;
	int cnt = 0;  // 解码的帧率
	XVideoView* view = XVideoView::Create();
	bool first_init = false;
	AVHWDeviceType dev_type = AV_HWDEVICE_TYPE_DXVA2;  // 硬件加速格式 DXVA2
	AVBufferRef* ctx_ref = nullptr;

	ifs.open(file_name, ios::in | ios::binary);

	if (!ifs)
	{
		return -1;
	}

	//1 找解码器
	codec = avcodec_find_decoder(codec_id);

	if (codec == nullptr)
	{
		cerr << "avcodec_find_decoder failed!" << endl;

		return -1;
	}

	//2 创建上下文
	context = avcodec_alloc_context3(codec);

	if (context == nullptr)
	{
		cerr << "avcodec_alloc_context3 failed!" << endl;

		return -1;
	}

	context->thread_count = 16;

	
	/// 打印所有支持的硬件加速方式
	for (int i = 0; ; i++)
	{
		auto config = avcodec_get_hw_config(codec, i);

		if (config == nullptr)
		{
			break;
		}

		if (config->device_type)
		{
			cout << av_hwdevice_get_type_name(config->device_type) << endl;
		}
	}

	// 初始化硬件加速上下文
	ret = av_hwdevice_ctx_create(&ctx_ref, dev_type, nullptr, nullptr, 0);

	if (ret < 0)
	{
		cerr << "av_hwdevice_ctx_create failed!" << endl;

		return -1;
	}

	// 设定硬件GPU加速
	context->hw_device_ctx = av_buffer_ref(ctx_ref);


	//3 打开上下文
	ret = avcodec_open2(context, nullptr, nullptr);

	if (ret < 0)
	{
		cerr << "avcodec_open2 failed!" << endl;

		return -1;
	}

	// 分割上下文
	parser = av_parser_init(codec_id);

	if (parser == nullptr)
	{
		cerr << "av_parser_init failed!" << endl;

		return -1;
	}

	packet = av_packet_alloc();

	frame = av_frame_alloc();
	hw_frame = av_frame_alloc();
	p_frame = frame;

	begin = NowMs();

	while (!ifs.eof())
	{
		int len = 0;
		unsigned char* data = inbuf;

		ifs.read((char*)inbuf, sizeof(inbuf));
		len = ifs.gcount();  // 读取的字节数

		if (ifs.eof())
		{
			ifs.clear();
			ifs.seekg(0, ios::beg);
		}

		while (len > 0)
		{
			// 通过 0001 截断输出到AVPacket 如果满足一个 AVPackret 则返回这个 AVPacket 的大小 不足一个AVPactet 则返回余下的数据大小 不足一个AVPactet的数据会被缓存起来
			int in_len = av_parser_parse2(parser, context,
				&packet->data, &packet->size,  // 输出
				data, len,					   // 输入
				AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0
			);

			data += in_len;
			len -= in_len;

			if (packet->size > 0)
			{
				// cout << packet->size << " " << flush;

				//发送packet到解码线程
				ret = avcodec_send_packet(context, packet);

				if (ret != 0)
				{
					cerr << "avcodec_send_packet failed!" << endl;
					break;
				}

				/* 获取多帧解码数据 */ 
				ret = avcodec_receive_frame(context, frame);

				while (ret == 0)
				{
					long long curr = NowMs();

					cnt++;

					if (curr - begin >= 1000)
					{
						cout << "FPS = " << cnt << endl;
						cnt = 0;
						begin = curr;
					}

					if (context->hw_device_ctx != nullptr)  // 硬解码
					{
						// 硬解码转换GPU =》CPU 显存=》内存
						// AV_PIX_FMT_NV12,      ///< planar YUV 4:2:0, 12bpp, 1 plane for Y and 1 plane for the UV components, which are interleaved (first byte U and the following byte V)
						av_hwframe_transfer_data(hw_frame, frame, 0);
						p_frame = hw_frame;
					}

					if (!first_init)
					{
						view->init(p_frame->width, p_frame->height, static_cast(p_frame->format));
						first_init = true;
					}

					view->drawAVFrame(p_frame);  // 渲染

					//每次会调用av_frame_unref 
					ret = avcodec_receive_frame(context, frame);
				}
			}
		}
	}

	/* 取出缓存数据 */
	avcodec_send_packet(context, nullptr);

	ret = avcodec_receive_frame(context, frame);

	while (ret == 0)
	{
		view->drawAVFrame(p_frame);  // 渲染
		ret = avcodec_receive_frame(context, frame);
	}

	av_frame_free(&hw_frame);
	av_frame_free(&frame);
	av_packet_free(&packet);
	av_parser_close(parser);
	avcodec_free_context(&context);
	ifs.close();
	view->close();
	delete view;

	return 0;
}

硬件解码出来的 frame 不在内存中,而是在显存中,所以需要使用 av_hwframe_transfer_data 函数将 frame 拷贝到内存中去,此时的 frame 的像素格式为 NV12,我们给 XVideoView 新增了 NV12 的像素格式,使之支持 NV12 的渲染。

使用硬件解码所使用的资源如下图所示:

硬件GPU加速编码DXVA2并进行渲染_第2张图片

可以看出硬件解码不使用 CPU 的资源,而是使用 GPU 的资源。

使用软件解码所使用的资源如下图所示:

硬件GPU加速编码DXVA2并进行渲染_第3张图片

 可以看出软件解码使用的是 CPU 的资源。

使用硬件解码 400x300 分辨率的帧率如下图所示:

硬件GPU加速编码DXVA2并进行渲染_第4张图片

使用软件解码 400x300 分辨率 16线程数 的帧率如下图所示:

硬件GPU加速编码DXVA2并进行渲染_第5张图片

可以看出使用16线程软件解码的速度比硬件解码的速度快,但是硬件解码不占用 CPU 的资源。 

你可能感兴趣的:(音视频,音视频)