使用x264进行视频编码

定义接口

在实现x264编码之前,我们先定义一些常用视频编码接口:

  • 打开编码器:Open
  • 关闭编码器:Close
  • 配置编码器:Configure或者ReConfigure
  • 获取编码器配置信息:GetConfig
  • 编码:Encode
  • 请求关键帧:ForceKeyframe

根据上面的定义,可以使用C++来抽象出一个虚基类

namespace toy {
	// 编码器基类
	class VideoEncoder {
	public:
		// 参数定义
		struct Setting {
			Setting() {
				fps = 15;
				frame_skip = false;
			}
			uint32_t width; // 视频宽
			uint32_t height; // 视频高
			uint32_t bitrate_bps; // 目标码率
			uint32_t fps; // 帧率
			bool frame_skip; // 是否开启跳帧
			uint32_t qp; // qp值
		};
		
		// 创建编码器的静态接口
		static std::shared_ptr<VideoEncoder> Create(VideoCodecType type);

		virtual ~VideoEncoder() {}

		// 打开编码器
		virtual bool Open(const Setting& param) = 0;

		// 重新配置编码器
		virtual void ReConfigure(const Setting& param) = 0;

		// 返回编码器的参数
		virtual Setting GetConfig() = 0;

		// 编码
		virtual bool Encode(
			const uint8_t** yuv_data,// uint8_t* yuv_data[3]
			uint8_t* pkt,
			size_t& pkt_size,
			bool& is_keyframe,
			bool& got_output
		) = 0;

		// 编码
		virtual bool Encode(
			const uint8_t* yuv_data,
			uint8_t* pkt,
			size_t& pkt_size,
			bool& is_keyframe,
			bool& got_output
		) = 0;

		// 请求关键帧
		virtual void ForceKeyframe() = 0;

		// 关闭编码器
		virtual void Close() = 0;
	};
}

x264的一些概念

  • x264_t:表示一个编码器对象
  • x264_param_t:表示编码器参数
  • x264_picture_t:表示一个图像
  • 关键帧:即I帧(其他的两种帧类型是:P帧和B帧)。在实时音视频领域,一般只用到I帧和P帧
  • 图像组(GOP):一般来说,两个关键帧之间即为一个图像组

使用x264进行视频编码

头文件定义:

#ifndef __VIDEO_X264_ENCODER_H__
#define __VIDEO_X264_ENCODER_H__

#include "video_codec.h"

struct x264_t;
struct x264_param_t;
struct x264_picture_t;

class X264Encoder:public toy::VideoEncoder
{
public:
	X264Encoder();
	~X264Encoder();
	
	//初始化编解码
	virtual bool Open(const Setting& param) override;

	//结束释放
	virtual void Close() override;

	//编码
	virtual bool Encode(
		const uint8_t** yuv_data,
		uint8_t* pkt,
		size_t& pkt_size,
		bool& is_keyframe,
		bool& got_output
	) override;

	virtual bool Encode(
		const uint8_t* yuv_data,
		uint8_t* pkt,
		size_t& pkt_size,
		bool& is_keyframe,
		bool& got_output
	) override;

	//调整码率
	virtual void ReConfigure(const Setting& param) override;

	//强制i帧
	virtual void ForceKeyframe() override;

	virtual Setting GetConfig() override;

private:
	Setting setting_;
	x264_t* encoder_;
	x264_param_t* encode_param_;
	x264_picture_t* frame_;
	x264_picture_t* encoded_frame_;
	bool is_open_;
	uint64_t encoded_frame_count_;
	bool enable_fixed_gop_;
	uint32_t gop_size_;
	uint64_t timestamp_;
	bool force_keyframe_;
};

#endif __VIDEO_X264_ENCODER_H__

源文件实现:

#include "x264_encoder.h"
#include "x264/x264.h"
#include <cstdio>
#include <memory>

#define kVideoBitrateMin 80000
#define kVideoBitrateMax 6000000

X264Encoder::X264Encoder()
{
	encoder_ = nullptr;
	encode_param_ = nullptr;
	frame_ = nullptr;
	encoded_frame_ = nullptr;
	is_open_ = false;
	encoded_frame_count_ = 0;
	enable_fixed_gop_ = true;
	gop_size_ = 400;
	timestamp_ = 0;
	force_keyframe_ = false;
}

X264Encoder::~X264Encoder()
{
	Close();
}

void X264Encoder::Close()
{
	if (encoder_) {
		x264_encoder_close(encoder_);
		encoder_ = NULL;
	}
	if (frame_)
	{
		x264_picture_clean(frame_);
		delete frame_;
		frame_ = nullptr;
	}
	if (encoded_frame_)
	{
		delete encoded_frame_;
		encoded_frame_ = nullptr;
	}
	if (encode_param_)
	{
		delete encode_param_;
		encode_param_ = nullptr;
	}
	is_open_ = false;
	encoded_frame_count_ = 0;
}

bool X264Encoder::Open(const Setting& param)
{

	Close();

	encode_param_ = new x264_param_t;
	frame_ = new x264_picture_t;
	encoded_frame_ = new x264_picture_t;

	setting_ = param;

	x264_picture_init(encoded_frame_);
	x264_picture_alloc(frame_, X264_CSP_I420, setting_.width, setting_.height);

	x264_param_default(encode_param_);
	x264_param_default_preset(encode_param_, "veryfast", "zerolatency");//会修改profile
	x264_param_apply_profile(encode_param_, "baseline");//baseline+cabac
	//x264_param_apply_profile_(encode_param_, "main");//老版本1.4的openh264不支持

	encode_param_->i_level_idc = 40;
	encode_param_->i_log_level = X264_LOG_NONE;
	encode_param_->i_width = setting_.width;
	encode_param_->i_height = setting_.height;
	encode_param_->b_repeat_headers = 1;  // 重复SPS/PPS 放到关键帧前面          
	encode_param_->b_cabac = 1;
	encode_param_->i_threads = 1;//多帧编码
	encode_param_->b_sliced_threads = 0;//(webrtc ? 0 : 1);
	encode_param_->i_fps_num = setting_.fps;
	encode_param_->i_fps_den = 1;
	encode_param_->i_keyint_max = setting_.fps;
	if (enable_fixed_gop_)
	{
		encode_param_->i_keyint_max = gop_size_;
		encode_param_->i_keyint_min = gop_size_;
	}
	encode_param_->i_frame_reference = 1;//参考帧

	encode_param_->analyse.b_psnr = 1;

	//码率控制模式有ABR(平均码率)、CQP(恒定质量)、CRF(恒定码率)
	//	ABR模式下调整i_bitrate,vbv开了后就是CBR模式
	//	CQP下调整i_qp_constant调整QP值,范围0~51,值越大图像越模糊,默认23
	//	太细致了人眼也分辨不出来,为了增加编码速度降低数据量还是设大些好,
	//	CRF下调整f_rf_constant和f_rf_constant_max影响编码速度和图像质量(数据量)
	encode_param_->rc.i_rc_method = X264_RC_ABR;
	//encode_param_->rc.i_qp_constant = 40;
	//encode_param_->rc.i_qp_min = 10;
	//encode_param_->rc.i_qp_max = 45;
	//encode_param_->rc.f_rf_constant = 20;
	//encode_param_->rc.f_rf_constant_max = 45;
	//encode_param_->rc.f_rate_tolerance = 0.1;
	encode_param_->rc.i_bitrate = (int)setting_.bitrate_bps / 1000;
	encode_param_->rc.i_vbv_max_bitrate = (int)((setting_.bitrate_bps*1.0) / 1000); // 平均码率模式下,最大瞬时码率,默认0(与-B设置相同)
	encode_param_->rc.i_vbv_buffer_size = (int)setting_.bitrate_bps * 2 / 1000;
	encode_param_->rc.f_ip_factor = 1.12; //调整I、P帧比例,使得码率控制更满足网络传输需要
	encode_param_->rc.f_rate_tolerance = 0.75; //使码率控制更准确

	if ((encoder_ = x264_encoder_open(encode_param_)) == NULL)
	{
		Close();
		return false;
	}

	is_open_ = true;

	return true;
}

void X264Encoder::ReConfigure(const Setting& param) {
	if (!is_open_) {
		return;
	}

	// 分辨率改变,重启编码器
	if (param.width != setting_.width || param.height != setting_.height) {
		Close();
		Open(param);
		return;
	}

	// 如果帧率码率都相同,那么不用处理,直接返回
	if (encode_param_->rc.i_bitrate == param.bitrate_bps / 1000 &&
		encode_param_->i_fps_num == param.fps) {
		return;
	}

	// 码率设置
	if (encode_param_->rc.i_bitrate != param.bitrate_bps / 1000) {
		setting_.bitrate_bps = param.bitrate_bps;

		encode_param_->rc.i_vbv_max_bitrate = (int)((param.bitrate_bps*1.0) / 1000); // 平均码率模式下,最大瞬时码率,默认0(与-B设置相同)
		encode_param_->rc.i_bitrate = (int)param.bitrate_bps / 1000;
		encode_param_->rc.i_vbv_buffer_size = (int)param.bitrate_bps * 2 / 1000;
	}

	// 帧率设置
	if (encode_param_->i_fps_num != param.fps) {
		encode_param_->i_fps_num = param.fps;
		encode_param_->i_fps_den = 1;
		encode_param_->i_keyint_max = param.fps;
		if (enable_fixed_gop_)
		{
			encode_param_->i_keyint_max = gop_size_;
			encode_param_->i_keyint_min = gop_size_;
		}
	}

	// TODO:QP
	
	int err = x264_encoder_reconfig(encoder_, encode_param_);

	if (err != 0)
	{
		printf("x264_encoder_reconfig failed! code %d\n", err);
	}

	setting_ = param;
}


bool X264Encoder::Encode( 
	const uint8_t** yuv_data, 
	uint8_t* pkt, 
	size_t& pkt_size, 
	bool& is_keyframe, 
	bool& got_output )
{
	is_keyframe = false;
	got_output = false;
	pkt_size = 0;

	if (!is_open_) {
		return false;
	}

	encoded_frame_count_++;

	int y_size = setting_.width * setting_.height;
	memcpy(frame_->img.plane[0] + 0, yuv_data[0], y_size);
	memcpy(frame_->img.plane[0] + y_size, yuv_data[1], y_size / 4);
	memcpy(frame_->img.plane[0] + y_size * 5 / 4, yuv_data[2], y_size / 4);


	frame_->i_pts = timestamp_++;

	if (force_keyframe_)
	{
		frame_->i_type = X264_TYPE_IDR;
		force_keyframe_ = false;
	}
	else
	{
		frame_->i_type = X264_TYPE_AUTO;
	}

	int iFrameSize = 0;
	int iNal = 0;
	x264_nal_t* pNals = NULL;

	//编码  
	int frame_size = x264_encoder_encode(encoder_, &pNals, &iNal, frame_, encoded_frame_);
	
	is_keyframe = IS_X264_TYPE_I(encoded_frame_->i_type) ? true : false;

	if (frame_size > 0 && iNal > 0)
	{
		for (int i = 0; i < iNal; ++i)
		{
			int32_t i_num_nal_h = 0;

			if (pNals[i].i_payload > 4)
			{
				while (i_num_nal_h < 5 && pNals[i].p_payload[i_num_nal_h] == 0)
				{
					i_num_nal_h++;
				}
				
				static char nal_head_s[3] = { 0, 0, 0 };

				if (i_num_nal_h < 3)
				{
					memcpy(pkt + iFrameSize, nal_head_s, 3 - i_num_nal_h);
					iFrameSize += (3 - i_num_nal_h);
				}
			}
			
			memcpy(pkt + iFrameSize, pNals[i].p_payload, pNals[i].i_payload);

			iFrameSize += pNals[i].i_payload;
		}

		got_output = true;

		pkt_size = iFrameSize;
	}

	return true;
}

bool X264Encoder::Encode(
	const uint8_t* yuv_data,
	uint8_t* pkt,
	size_t& pkt_size,
	bool& is_keyframe,
	bool& got_output
) 
{
	if (!is_open_) {
		return false;
	}

	const uint8_t* yuv[3] = { 0 };
	if (yuv_data == NULL) {
		return Encode(yuv, pkt, pkt_size, is_keyframe, got_output);
	}
	else {
		int y_size = setting_.width * setting_.height;
		yuv[0] = yuv_data;
		yuv[1] = yuv_data + y_size;
		yuv[2] = yuv_data + y_size * 5 / 4;
		return Encode(yuv, pkt, pkt_size, is_keyframe, got_output);
	}
}

//强制i帧
void X264Encoder::ForceKeyframe()
{
	force_keyframe_ = true;
}

toy::VideoEncoder::Setting X264Encoder::GetConfig() {
	return setting_;
}
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
x264是一款开源的视频编码器,它采用H.264/AVC视频编码标准,被广泛应用于视频压缩、视频传输等领域。下面是对x264视频编码器源码的简要解析。 x264的整体架构分为以下几个模块: 1. 预处理模块 预处理模块主要负责对输入的视频进行预处理,包括色彩空间转换、图像缩放、去噪等处理。在预处理模块中,会首先对输入的YUV格式的视频进行格式转换,转换成x264支持的色彩空间格式,然后再进行其他处理。 2. 分块模块 分块模块将图像划分成多个块,每个块的大小可以由用户指定,在x264中,块大小一般为16x16或者8x8。分块模块将每个块的像素值按照一定的顺序排列,并存储在一个数组中,以便后续操作。 3. 预测模块 预测模块主要负责对图像进行运动估计和帧内预测。运动估计是指在当前帧中,通过比对前一帧或者参考帧中相似块的像素值,预测当前块的像素值。帧内预测是指在当前帧中,通过相邻块的像素值,预测当前块的像素值。 4. 变换模块 变换模块主要负责对预测残差进行离散余弦变换(DCT),将空域中的像素变换到频域中,以便后续的量化操作。在x264中,采用了4x4和8x8两种不同大小的DCT变换。 5. 量化模块 量化模块将DCT系数按照一定的规则进行量化,即将大的DCT系数变小,以减小编码后的比特率。在x264中,采用了不同的量化矩阵,以适应不同的场景需求。 6. 熵编码模块 熵编码模块将量化后的DCT系数进行编码,生成比特流。在x264中,采用了自适应的上下文建模技术,以提高编码效率。 7. 决策模块 决策模块主要负责对编码参数进行优化,以达到最佳的编码效果。在x264中,采用了基于码率失真优化的算法,通过不断调整编码参数,使得编码后的视频质量最好,同时保证视频的比特率不超过预设的值。 总之,x264视频编码器源码的设计非常精妙,涵盖了众多的技术领域,从预处理到量化编码,每个模块都充分考虑到了实际应用场景的需求,使得x264成为了一款高效、稳定、灵活的视频编码器。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值