重构XEncode添加基类XCodec并完成XDecode封装

XCodec 封装重构

XDecode 封装

XCodec.h

#pragma once

#include <mutex>
#include <vector>

struct AVCodec;
struct AVCodecContext;
struct AVFrame;
struct AVPacket;

void PrintError(int err);

class XCodec
{
public:
	XCodec();
	//
	/// 创建编码上下文
	/// @para codec_id 编码器ID号,对应ffmpeg
	/// @return 编码上下文 ,失败返回nullptr
	static AVCodecContext* Create(int codec_id, bool is_encoder = true);

	//
	/// 设置对象的编码器上下文 上下文传递到对象中,资源由XEncode维护
	/// 加锁 线程安全
	/// @para context 编码器上下文 如果context不为nullptr,则先清理资源
	void setContext(AVCodecContext* context);

	/
	/// 设置编码参数,线程安全
	bool setOpt(const char* key, const char* value);
	bool setOpt(const char* key, int value);

	//
	/// 打开编码器 线程安全
	bool open();

	///
	//根据AVCodecContext 创建一个AVFrame,需要调用者释放av_frame_free
	AVFrame* createFrame();

protected:
	std::mutex m_mtx;			// 编码器上下文锁
	AVCodecContext* m_context;	// 编码器上下文
};

 XCodec.cpp

#include "XCodec.h"
#include <iostream>

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

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

using namespace std;

void PrintError(int err)
{
	char buf[1024] = { 0 };

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

XCodec::XCodec()
{
	m_context = nullptr;
}

AVCodecContext* XCodec::Create(int codec_id, bool is_encoder)
{
	AVCodec* codec = nullptr;
	AVCodecContext* context = nullptr;

	if (is_encoder)
	{
		codec = avcodec_find_encoder(static_cast<AVCodecID>(codec_id));
	}
	else
	{
		codec = avcodec_find_decoder(static_cast<AVCodecID>(codec_id));
	}

	if (codec != nullptr)
	{
		context = avcodec_alloc_context3(codec);

		if (context != nullptr)
		{
			context->pix_fmt = AV_PIX_FMT_YUV420P;  // 源数据像素格式,与编码算法相关
			context->time_base = { 1, 25 };  // 帧时间戳的时间单位  pts*time_base = 播放时间(秒) 分数 1/25
			context->thread_count = 16;  // 编码线程数,可以通过调用系统接口获取cpu核心数量
		}
		else
		{
			cerr << "avcodec_alloc_context3 failed!" << endl;
		}
	}
	else
	{
		cerr << "avcodec_find_encoder failed! " << "avcodec_id = " << codec_id << endl;
	}

	return context;
}

void XCodec::setContext(AVCodecContext* context)
{
	if (context != nullptr)
	{
		unique_lock<mutex> context_mtx(m_mtx);

		if (m_context != nullptr)
		{
			avcodec_free_context(&m_context);
		}

		m_context = context;
	}
}

bool XCodec::setOpt(const char* key, const char* value)
{
	bool ret = false;
	int err = 0;
	unique_lock<mutex> context_mtx(m_mtx);

	if ((m_context != nullptr) && (key != nullptr) && (value != nullptr))
	{
		err = av_opt_set(m_context->priv_data, key, value, 0);

		if (err == 0)
		{
			ret = true;
		}
		else
		{
			PrintError(err);
		}
	}

	return ret;
}

bool XCodec::setOpt(const char* key, int value)
{
	bool ret = false;
	int err = 0;
	unique_lock<mutex> context_mtx(m_mtx);

	if ((m_context != nullptr) && (key != nullptr))
	{
		err = av_opt_set_int(m_context->priv_data, key, value, 0);

		if (err == 0)
		{
			ret = true;
		}
		else
		{
			PrintError(err);
		}
	}

	return ret;
}

bool XCodec::open()
{
	bool ret = false;
	int err = 0;
	unique_lock<mutex> context_mtx(m_mtx);

	if (m_context != nullptr)
	{
		err = avcodec_open2(m_context, nullptr, nullptr);;

		if (err == 0)
		{
			ret = true;
		}
		else
		{
			PrintError(err);
		}
	}

	return ret;
}

AVFrame* XCodec::createFrame()
{
	AVFrame* ret = nullptr;
	int err = 0;
	unique_lock<mutex> context_mtx(m_mtx);

	if (m_context != nullptr)
	{
		ret = av_frame_alloc();
		ret->width = m_context->width;
		ret->height = m_context->height;
		ret->format = m_context->pix_fmt;
		err = av_frame_get_buffer(ret, 0);

		if (err != 0)
		{
			PrintError(err);
			av_frame_free(&ret);
		}
	}

	return ret;
}

XEncode.h

#pragma once

#include "XCodec.h"

struct AVCodec;
struct AVCodecContext;
struct AVFrame;
struct AVPacket;

class XEncode : public XCodec
{
public:
	//
	/// 编码数据 线程安全 每次新创建AVPacket
	/// @para frame 空间由用户维护
	/// @return 失败范围nullptr 返回的AVPacket用户需要通过av_packet_free 清理
	AVPacket* encode(AVFrame* frame);

	//
	//返回所有编码缓存中AVPacket
	std::vector<AVPacket*> end();
};

XEncode.cpp

#include "XEncode.h"
#include <iostream>

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

using namespace std;

AVPacket* XEncode::encode(AVFrame* frame)
{
	AVPacket* ret = nullptr;
	int err = 0;
	unique_lock<mutex> context_mtx(m_mtx);

	if ((m_context != nullptr))
	{	
		err = avcodec_send_frame(m_context, frame);

		if (err == 0)
		{
			ret = av_packet_alloc();
			err = avcodec_receive_packet(m_context, ret);


			// 没能够获取编码数据
			if (err != 0)
			{
				if ((err != AVERROR(EAGAIN)) && (err != AVERROR_EOF))
				{
					PrintError(err);
				}

				av_packet_free(&ret);
			}
		}
		else
		{
			PrintError(err);
		}
	}

	return ret;
}

vector<AVPacket*> XEncode::end()
{
	vector<AVPacket*> ret;
	int err = 0;
	unique_lock<mutex> context_mtx(m_mtx);

	if (m_context != nullptr)
	{
		AVPacket* packet = nullptr;

		err = avcodec_send_frame(m_context, nullptr);  // 发送NULL 获取缓冲

		if (err == 0)
		{
			while (err == 0)
			{
				packet = av_packet_alloc();
				err = avcodec_receive_packet(m_context, packet);

				if (err == 0)
				{
					ret.push_back(packet);
				}
				else
				{
					if ((err != AVERROR(EAGAIN)) && (err != AVERROR_EOF))
					{
						PrintError(err);
					}

					av_packet_free(&packet);
				}
			}
		}
		else
		{
			PrintError(err);
		}
	}

	return ret;
}

XDecode.h

#pragma once
#include "XCodec.h"
#include <vector>

struct AVFrame;
struct AVPacket;

class XDecode : public XCodec
{
public:

	
	 初始化硬件加速  4 AV_HWDEVICE_TYPE_DXVA2
	bool initHW(int type = 4);
	bool send(AVPacket* packet);
	bool recv(AVFrame* frame);
	std::vector<AVFrame*> end();
};

XDecode.cpp

#include "XDecode.h"
#include <iostream>

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

using namespace std;

bool XDecode::initHW(int type)
{
	bool ret = false;
	int err = 0;
	AVBufferRef* ctx_ref = nullptr;
	unique_lock<mutex> context_mtx(m_mtx);

	err = av_hwdevice_ctx_create(&ctx_ref, static_cast<AVHWDeviceType>(type), nullptr, nullptr, 0);

	if (err == 0)
	{
		m_context->hw_device_ctx = ctx_ref;
	}
	else
	{
		PrintError(err);
	}

	return ret;
}

bool XDecode::send(AVPacket* packet)
{
	bool ret = false;
	int err = 0;
	unique_lock<mutex> context_mtx(m_mtx);

	if (m_context != nullptr)
	{
		err = avcodec_send_packet(m_context, packet);

		if (err == 0)
		{
			ret = true;
		}
		else
		{
			PrintError(err);
		}
	}

	return ret;
}

bool XDecode::recv(AVFrame* frame)
{
	bool ret = false;
	int err = 0;
	unique_lock<mutex> context_mtx(m_mtx);

	if ((m_context != nullptr) && (frame != nullptr))
	{

		if (m_context->hw_device_ctx == nullptr)
		{
			err = avcodec_receive_frame(m_context, frame);
		}
		else  // GPU 解码
		{
			AVFrame* tmp = av_frame_alloc();

			err = avcodec_receive_frame(m_context, tmp);

			if (err == 0)
			{
				err = av_hwframe_transfer_data(frame, tmp, 0);  // 显存转内存 GPU =》 CPU
			}

			av_frame_free(&tmp);
		}

		if (err == 0)
		{
			ret = true;
		}
		else
		{
			if ((err != AVERROR(EAGAIN)) && (err != AVERROR_EOF))
			{
				PrintError(err);
			}
		}
	}

	return ret;
}

std::vector<AVFrame*> XDecode::end()
{
	vector<AVFrame*> ret;
	int err = 0;
	unique_lock<mutex> context_mtx(m_mtx);

	if (m_context != nullptr)
	{
		AVFrame* frame = nullptr;

		err = avcodec_send_packet(m_context, nullptr);  // 发送NULL 获取缓冲

		if (err == 0)
		{
			while (err == 0)
			{
				frame = av_frame_alloc();

				if (m_context->hw_device_ctx == nullptr)
				{
					err = avcodec_receive_frame(m_context, frame);
				}
				else  // GPU 解码
				{
					AVFrame* tmp = av_frame_alloc();

					err = avcodec_receive_frame(m_context, tmp);

					if (err == 0)
					{
						err = av_hwframe_transfer_data(frame, tmp, 0);
					}

					av_frame_free(&tmp);
				}

				if (err == 0)
				{
					ret.push_back(frame);
				}
				else
				{
					if ((err != AVERROR(EAGAIN)) && (err != AVERROR_EOF))
					{
						PrintError(err);
					}

					av_frame_free(&frame);
				}
			}
		}
		else
		{
			PrintError(err);
		}
	}

	return ret;
}

120_test_xdecode.cpp

#include <iostream>
#include <fstream>
#include "XVideoView.h"
#include "XSDL.h"
#include "XDecode.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;
	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
	XDecode de;
	vector<AVFrame*> vec;

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

	if (!ifs)
	{
		return -1;
	}

	context = de.Create(codec_id, false);

	if (context != nullptr)
	{
		de.setContext(context);
		de.initHW();
		de.open();
	}

	// 分割上下文
	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();

	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到解码线程
				if (!de.send(packet))
				{
					cerr << "avcodec_send_packet failed!" << endl;
					break;
				}

				/* 获取多帧解码数据 */
				while (de.recv(frame))
				{
					long long curr = NowMs();

					cnt++;

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

					if (!first_init)
					{
						view->init(frame->width, frame->height, static_cast<XVideoView::PixelFormat>(frame->format));
						first_init = true;
					}

					view->drawAVFrame(frame);  // 渲染
				}
			}
		}
	}

	/* 取出缓存数据 */
	vec = de.end();

	for (int i = 0; i < vec.size(); i++)
	{
		view->drawAVFrame(vec[i]);
		av_frame_free(&vec[i]);
	}

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

	return 0;
}

该测试程序将 .h264 文件解析出一个个 AVPacket,然后解码出 AVFrame,并进行渲染。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值