XEocode编码封装

XEocode编码封装,第1张

XEncode.h

#pragma once

#include 
#include 

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

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

	//
	/// 设置对象的编码器上下文 上下文传递到对象中,资源由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();

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

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

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

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

XEncode.cpp

#include "XEncode.h"
#include 

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;

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

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

AVCodecContext* XEncode::Create(int codec_id)
{
	AVCodec* codec = nullptr;
	AVCodecContext* context = nullptr;

	codec = avcodec_find_encoder(static_cast(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 XEncode::setContext(AVCodecContext* context)
{
	if (context != nullptr)
	{
		unique_lock context_mtx(m_mtx);

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

		m_context = context;
	}
}

bool XEncode::setOpt(const char* key, const char* value)
{
	bool ret = false;
	int err = 0;
	unique_lock 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 XEncode::setOpt(const char* key, int value)
{
	bool ret = false;
	int err = 0;
	unique_lock 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 XEncode::open()
{
	bool ret = false;
	int err = 0;
	unique_lock 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;
}

AVPacket* XEncode::encode(AVFrame* frame)
{
	AVPacket* ret = nullptr;
	int err = 0;
	unique_lock 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;
}

AVFrame* XEncode::createFrame()
{
	AVFrame* ret = nullptr;
	int err = 0;
	unique_lock 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;
}

vector XEncode::end()
{
	vector ret;
	int err = 0;
	unique_lock 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;
}

116_test_xencode.cpp

#include 
#include 
#include "XEncode.h"

using namespace std;

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

int main(int argc, char* argv[])
{
	XEncode en;
	AVCodecContext* context = nullptr;
	AVFrame* frame = nullptr;
	AVPacket* packet = nullptr;
	vector vec;
	AVCodecID codec_id = AV_CODEC_ID_H264;
	ofstream ofs;
	string file_name = "400_300_25_preset";
	int cnt = 0;  // 写入文件的帧数 SPS PPS IDR放在一帧中
	int r = 0;

	if (argc > 1)
	{
		string suffix = argv[1];

		cout << "suffix = " << suffix << endl;

		if ((suffix == "h265") || (suffix == "hevc"))
		{
			codec_id = AV_CODEC_ID_HEVC;
		}
	}

	if (codec_id == AV_CODEC_ID_H264)
	{
		file_name += ".h264";
	}
	else if (codec_id == AV_CODEC_ID_HEVC)
	{
		file_name += ".h265";
	}

	ofs.open(file_name, ios::out | ios::binary);

	if (!ofs)
	{
		cout << "open " << file_name << " failed!" << endl;

		return -1;
	}

	context = en.Create(codec_id);
	context->width = 400;
	context->height = 300;
	en.setContext(context);
	en.setOpt("crf", 18);	// 恒定速率因子(CRF)

	if (!en.open())
	{
		cout << "open context failed!" << endl;
	}

	frame = en.createFrame();

	if (frame == nullptr)
	{
		return -1;
	}

	/* 十秒视频 250帧 */
	for (int i = 0; i < 250; i++)
	{
		// 生成AVFrame 数据 每帧数据不同
		// Y
		for (int h = 0; h < frame->height; h++)
		{
			for (int w = 0; w < frame->width; w++)
			{
				frame->data[0][frame->linesize[0] * h + w] = h + w + i * 3;
			}
		}

		// UV
		for (int h = 0; h < frame->height / 2; h++)
		{
			for (int w = 0; w < frame->width / 2; w++)
			{
				frame->data[1][frame->linesize[1] * h + w] = 128 + h + i * 2;
				frame->data[2][frame->linesize[2] * h + w] = 64 + w + i * 5;
			}
		}

		frame->pts = i;  // 显示的时间
		packet = en.encode(frame);

		if (packet != nullptr)
		{
			cnt++;
			ofs.write((char*)packet->data, packet->size);
			av_packet_free(&packet);
		}
	}

	vec = en.end();  // 获取之前还没编码好的 AVPacket

	for (int i = 0; i < vec.size(); i++)
	{
		packet = vec[i];
		cnt++;
		ofs.write((char*)packet->data, packet->size);
		av_packet_free(&packet);
	}

	cout << "cnt = " << cnt << endl;

	ofs.close();
	av_frame_free(&frame);
	avcodec_free_context(&context);

	return 0;
}

我们使用了 XEncode 类对 ffmpeg 的编码进行了简单的封装。

测试结果

我们成功将250帧的原始数据进行编码,然后将编码后的码流数据写入到文件中。 

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/676378.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-19
下一篇 2022-04-19

发表评论

登录后才能评论

评论列表(0条)

保存