目录
概述
本文记录一个最简单的基于libx264的H.264视频编码器;直接调用libx264完成编码。因此项目的体积非常小巧。该编码器可以将输入的YUV数据编码为H.264码流文件。
流程中主要的函数如下所示:
x264_param_default():设置参数集结构体x264_param_t的缺省值。
x264_picture_alloc():为图像结构体x264_picture_t分配内存。
x264_encoder_open():打开编码器。
x264_encoder_encode():编码一帧图像。
x264_encoder_close():关闭编码器。
x264_picture_clean():释放x264_picture_alloc()申请的资源。
其中存储数据的结构体如下所示:
x264_picture_t:存储压缩编码前的像素数据。
x264_nal_t:存储压缩编码后的码流数据。
此外流程图中还包括一个“flush_encoder”模块,该模块使用的函数和编码模块是一样的。唯一的不同在于不再输入视频像素数据。它的作用在于输出编码器中剩余的码流数据。
源代码介绍
基于libx264的H.264视频编码器封装成simplest_encoder_x264类;里面包含对外暴露接口如下:
1、long Init(encoder_x264_cfg* cfg);
这个是用于编码初始化的,具体暴露参数参照结构体:encoder_x264_cfg;返回:0代表成功。
2、long Encoder(char* inbuff, long long& pts, long long& dts, char* outbuff, int& out_buff_len);
这个函数用于编码处理的;参数解释如下:
inbuff:输入buff,目前代码中只实现I420,如需要可自行扩展;
pts:输入的时间戳;
outbuff:编程成功后返回的数据buff,应由调用的人负责申请和释放;
out_buff_len:传输进行的最大buff,编程成功返回outbuff里的数据长度;
函数返回值:0代表成功;否则失败。
3、long Flush(long long pts, char* outbuff, int& out_buff_len);
这个函数用于编码处理的;参数解释如下:
pts:输入的时间戳;
outbuff:编程成功后返回的数据buff,应由调用的人负责申请和释放;
out_buff_len :传输进行的最大buff,编程成功返回outbuff里的数据长度;
函数返回值:0代表成功;否则失败。当返回成功应该继续调用Flush函数。
4、long AdjustBitrate(int bitrate);
这个函数用于编码处理的;参数解释如下:
bitrate:更换的比特率,单位应该对应比特率*1000;
函数返回值:0代表成功;否则失败。
5、 long MandatoryKeyFrame();
函数解释:这个代表强制输出关键帧,即IDR帧;
函数返回值:0代表成功;否则失败。
6、long UnInit();
函数解释:代表释放libx264相关资源;
函数返回值:0代表成功;否则失败。
对应源码头文件:simplest_encoder_x264.h
/**
* 简单的x264视频编码器
*Simple x264 video encoder
*
* 梁启东 qidong.liang
* 18088708700@163.com
* https://blog.youkuaiyun.com/u011645307
*
*
* 本程序实现了简单的x264视频编码功能;
* 支持强制输出关键帧
* 支持动态修改码率
* 目前只开放了I420数据格式编码
*This program realizes a simple x264 video coding function;
*Support forced output of key frames
*Support dynamic modification of bit rate
*At present, only i420 data format coding is open
*
*/
#ifndef SIMPLEST_ENCODER_X264
#define SIMPLEST_ENCODER_X264
#if defined ( __cplusplus)
#include <stdint.h>
#include <map>
#include <functional>
extern "C"
{
#endif
#include "x264/include/x264.h"
#if defined ( __cplusplus)
};
#endif
typedef enum x264_rc_mode
{
x264_rc_mode_cqp, //恒定质量,
x264_rc_mode_crf, //恒定码率,
x264_rc_mode_abr, //平均码率
}x264_rc_mode;
typedef enum x264_raw_format
{
x264_raw_format_I420,
}x264_raw_format;
typedef struct encoder_x264_cfg
{
int sourceWidth; //Width
int sourceHeight; //Height
x264_raw_format internalCsp; //源颜色空间
int bRepeatHeaders; //关键帧前是否有sps pps
int threads; //编码线程数
int bframe; //设置B帧数量;当为0的时候不含B帧
int bitrate; // 设置码率, 单位是 kbps*1000
int gop; //关键帧间隔
x264_rc_mode rcmode; // rc_mode
encoder_x264_cfg()
{
sourceWidth = 0;
sourceHeight = 0;
bframe = 0;
gop = 15;
threads = 1;
bRepeatHeaders = 1;
bitrate = 1500000;
internalCsp = x264_raw_format_I420;
rcmode = x264_rc_mode_cqp;
}
}encoder_x264_cfg;
class simplest_encoder_x264
{
public:
simplest_encoder_x264();
~simplest_encoder_x264();
long Init(encoder_x264_cfg* cfg);
long Encoder(char* inbuff, long long& pts, long long& dts, char* outbuff, int& out_buff_len);
long Flush(long long pts, char* outbuff, int& out_buff_len);
long AdjustBitrate(int bitrate);
long MandatoryKeyFrame();
long UnInit();
private:
long I420ToX264PictureT(char* inbuff, int width, int height, x264_picture_t* pPic_in);
private:
bool is_key_frame_;
int iNal_ ;
x264_nal_t* pNals_ ;
x264_t* pHandle_ ;
x264_picture_t* pPic_in_;
x264_picture_t* pPic_out_;
x264_param_t* pParam_;
std::map<x264_raw_format, int> internal_csp_map_;
std::map<x264_rc_mode, int> x264_rc_mode_map_;
std::map<int, std::function<long(char* inbuff, int width,int height,x264_picture_t* pPic_in)>> csp_fun_map_;
};
#endif // SIMPLEST_ENCODER_X264
对应源码源文件:simplest_encoder_x264.cpp
#include "simplest_encoder_x264.h"
#include <iostream>
simplest_encoder_x264::simplest_encoder_x264()
{
pHandle_ = nullptr;
is_key_frame_ = false;
pPic_in_ = (x264_picture_t*)malloc(sizeof(x264_picture_t));
pPic_out_ = (x264_picture_t*)malloc(sizeof(x264_picture_t));
pParam_ = (x264_param_t*)malloc(sizeof(x264_param_t));
internal_csp_map_[x264_raw_format_I420] = X264_CSP_I420;
csp_fun_map_[X264_CSP_I420] = std::bind(&simplest_encoder_x264::I420ToX264PictureT,this,std::placeholders::_1,std::placeholders::_2, std::placeholders::_3, std::placeholders::_4);
x264_rc_mode_map_[x264_rc_mode_cqp] = X264_RC_CQP;
x264_rc_mode_map_[x264_rc_mode_crf] = X264_RC_CRF;
x264_rc_mode_map_[x264_rc_mode_abr] = X264_RC_ABR;
}
simplest_encoder_x264::~simplest_encoder_x264()
{
if (pPic_in_)
{
free(pPic_in_);
}
pPic_in_ = nullptr;
if (pPic_out_)
{
free(pPic_out_);
}
pPic_out_ = nullptr;
if (pParam_)
{
free(pParam_);
}
pParam_ = nullptr;
}
long simplest_encoder_x264::Init(encoder_x264_cfg* cfg)
{
if (nullptr == cfg)
{
return -1;
}
auto iter = internal_csp_map_.find(cfg->internalCsp);
if (internal_csp_map_.end() == iter)
{
return -1;
}
auto mode_iter = x264_rc_mode_map_.find(cfg->rcmode);
if (x264_rc_mode_map_.end() == mode_iter)
{
return -1;
}
x264_param_default(pParam_);
pParam_->b_repeat_headers = cfg->bRepeatHeaders;
pParam_->i_width = cfg->sourceWidth;
pParam_->i_height = cfg->sourceHeight;
pParam_->i_csp = iter->second;
pParam_->rc.i_rc_method = mode_iter->second;
pParam_->i_bframe = cfg->bframe;
// 设置码率, 单位是 kbps
pParam_->rc.i_bitrate = cfg->bitrate / 1000;
// 设置最大码率, 单位 kbps, 该配置与 i_vbv_buffer_size 配套使用
pParam_->rc.i_vbv_max_bitrate = cfg->bitrate / 1000 * 1.2;
// 该配置与 i_vbv_max_bitrate 配置配套使用, 码率控制缓冲区大小
pParam_->rc.i_vbv_buffer_size = cfg->bitrate / 1000;
pParam_->i_keyint_max = cfg->gop; //关键帧间隔
x264_param_apply_profile(pParam_, x264_profile_names[5]);
pHandle_ = x264_encoder_open(pParam_);
x264_picture_init(pPic_out_);
x264_picture_alloc(pPic_in_, pParam_->i_csp, pParam_->i_width, pParam_->i_height);
return 0;
}
long simplest_encoder_x264::Encoder(char* inbuff, long long& pts, long long& dts, char* outbuff,int& out_buff_len)
{
if (!pParam_)
{
return -1;
}
auto fun_iter = csp_fun_map_.find(pParam_->i_csp);
if (csp_fun_map_.end() == fun_iter)
{
return -2;
}
fun_iter->second(inbuff, pParam_->i_width, pParam_->i_height, pPic_in_);
pPic_in_->i_pts = pts;
int iNal = 0;
pPic_in_->i_type = X264_TYPE_AUTO;
if (is_key_frame_)
{
pPic_in_->i_type = X264_TYPE_KEYFRAME;
is_key_frame_ = false;
}
int ret = x264_encoder_encode(pHandle_, &pNals_, &iNal, pPic_in_, pPic_out_);
if (ret < 0) {
printf("x264_encoder_encode Error.\n");
return -3;
}
out_buff_len = 0;
for (int j = 0; j < iNal; ++j)
{
memcpy(outbuff+ out_buff_len, pNals_[j].p_payload, pNals_[j].i_payload);
out_buff_len += pNals_[j].i_payload;
}
return 0;
}
long simplest_encoder_x264::Flush(long long pts, char* outbuff, int& out_buff_len)
{
int iNal = 0;
out_buff_len = 0;
int ret = x264_encoder_encode(pHandle_, &pNals_, &iNal, NULL, pPic_out_);
if (ret == 0) {
return 0;
}
printf("Flush 1 frame.\n");
for (int j = 0; j < iNal; ++j)
{
memcpy(outbuff + out_buff_len, pNals_[j].p_payload, pNals_[j].i_payload);
out_buff_len += pNals_[j].i_payload;
}
return 0;
}
long simplest_encoder_x264::AdjustBitrate(int bitrate)
{
if (nullptr == pParam_ ||
nullptr == pHandle_)
{
return -1;
}
pParam_->rc.i_rc_method = X264_RC_ABR;
pParam_->rc.i_vbv_max_bitrate = bitrate/1000;
pParam_->rc.i_bitrate = bitrate / 1000;
pParam_->rc.i_vbv_buffer_size = bitrate / 1000;
int nRes = x264_encoder_reconfig(pHandle_, pParam_);
if (0 != nRes )
{
printf("x264_encoder_reconfig:%d\n", nRes);
return -1;
}
return 0;
}
long simplest_encoder_x264::MandatoryKeyFrame()
{
is_key_frame_ = true;
return 0;
}
long simplest_encoder_x264::UnInit()
{
if (pPic_in_)
{
x264_picture_clean(pPic_in_);
}
if (pHandle_)
{
x264_encoder_close(pHandle_);
}
pHandle_ = nullptr;
return 0;
}
long simplest_encoder_x264::I420ToX264PictureT(char* inbuff, int width, int height, x264_picture_t* pPic_in)
{
int y_size = width * height;
memcpy(pPic_in->img.plane[0], inbuff, y_size); //Y
memcpy(pPic_in->img.plane[1], inbuff + y_size, y_size / 4); //U
memcpy(pPic_in->img.plane[2], inbuff + y_size + y_size / 4, y_size / 4); //V
return 0;
}
对应的测试函数 main.cpp
/**
* 简单的x264视频编码器测试程序
*Simple x264 video encoder
*
* 梁启东 qidong.liang
* 18088708700@163.com
* https://blog.youkuaiyun.com/u011645307
*
*
* 本程序测试simplest_encoder_x264视频编码功能;
* 测试simplest_encoder_x264强制输出关键帧的功能
* 测试simplest_encoder_x264动态修改码率的功能
* 测试simplest_encoder_x264I420数据格式编码的功能
* This program tests simplest_ encoder_ X264 video coding function;
* Test simplest_ encoder_ X264 forced output keyframe function
* Test simplest_ encoder_ X264 function of dynamically modifying code rate
* Test simplest_ encoder_ X264i420 data format coding function
*
*/
#include "simplest_encoder_x264.h"
#include <iostream>
int main()
{
FILE* out_file = nullptr;
FILE* in_file = nullptr;
int w = 0;
int h = 0;
int buffLen = 0;
fopen_s(&in_file, "../inData/i420_480x272.yuv", "rb+");
fopen_s(&out_file, "../outData/i420_480x272.h264", "wb+");
w = 480;
h = 272;
buffLen = w * h * 3 / 2;
if (!in_file)
{
printf("in_file is nullptr\n");
exit(-1);
}
encoder_x264_cfg cfg;
simplest_encoder_x264* x264 = new (std::nothrow)simplest_encoder_x264;
if (!x264)
{
printf("x264 is nullptr\n");
delete x264;
x264 = nullptr;
if (in_file)
{
fclose(in_file);
}
in_file = nullptr;
if (out_file)
{
fclose(out_file);
}
out_file = nullptr;
exit(-1);
}
cfg.bRepeatHeaders = 1;
cfg.internalCsp = x264_raw_format_I420;
cfg.sourceHeight = h;
cfg.sourceWidth = w;
cfg.bframe = 0;
if (0 != x264->Init(&cfg))
{
printf("x264.Init error\n");
exit(-1);
}
char* inbuff = new char[buffLen];
char* outbuff = new char[buffLen];
long long i = 0;
long long d = 0;
int outLen = buffLen;
while (true)
{
i++;
outLen = buffLen;
if (fread(inbuff, 1, buffLen, in_file) != buffLen)
{
break;
}
if (100 == i )
{
x264->AdjustBitrate(80000);
}
if (i==10)
{
x264->MandatoryKeyFrame();
}
x264->Encoder(inbuff, i, d, outbuff, outLen);
fwrite(outbuff, 1, outLen, out_file);
}
while (true)
{
x264->Flush(i, outbuff, outLen);
fwrite(outbuff, 1, outLen, out_file);
if (!outLen)
{
break;
}
}
if (0 != x264->UnInit())
{
printf("x264.UnInit error\n");
}
delete x264;
x264 = nullptr;
if (in_file)
{
fclose(in_file);
}
in_file = nullptr;
if (out_file)
{
fclose(out_file);
}
out_file = nullptr;
std::cout << "Hello World!\n";
}