ISVCEncoder* encoder_;
int rv = WelsCreateSVCEncoder(&encoder_);
if (0 != rv || !encoder_) {
//error
}
ISVCEncoder提供了一系列的编码有关的接口,如初始化编码器、设置编码参数、编码等;
openh264提供了两个结构体来设置编码参数,SEncParamBase与SEncParamExt, SEncParamBase仅通过了最基础的参数设置, 其定义如下:
typedef struct TagEncParamBase {
EUsageType
iUsageType; ///< application type;1.CAMERA_VIDEO_REAL_TIME:camera video signal; 2.SCREEN_CONTENT_REAL_TIME:screen content signal;
int iPicWidth; ///< width of picture in luminance samples (the maximum of all layers if multiple spatial layers presents)
int iPicHeight; ///< height of picture in luminance samples((the maximum of all layers if multiple spatial layers presents)
int iTargetBitrate; ///< target bitrate desired, in unit of bps
RC_MODES iRCMode; ///< rate control mode
float fMaxFrameRate; ///< maximal input frame rate
} SEncParamBase, *PEncParamBase;
其中参数iUsageType指明应用的类型,类型包括:
typedef enum {
CAMERA_VIDEO_REAL_TIME, ///< camera video for real-time communication
SCREEN_CONTENT_REAL_TIME, ///< screen content signal
CAMERA_VIDEO_NON_REAL_TIME
} EUsageType;
iRCMode指定码率控制的模式, openh264提供的模式如下:
typedef enum {
RC_QUALITY_MODE = 0, ///< quality mode
RC_BITRATE_MODE = 1, ///< bitrate mode
RC_BUFFERBASED_MODE = 2, ///< no bitrate control,only using buffer status,adjust the video quality
RC_TIMESTAMP_MODE = 3, //rate control based timestamp
RC_BITRATE_MODE_POST_SKIP = 4, ///< this is in-building RC MODE, WILL BE DELETED after algorithm tuning!
RC_OFF_MODE = -1, ///< rate control off mode
} RC_MODES;
对编码器的初始化例子如下:
SEncParamBase paramBase;
paramBase.iPicWidth = width_;
paramBase.iPicHeight = height_;
paramBase.fMaxFrameRate = fps_;
paramBase.iTargetBitrate = 10 * width_ * height_;
paramBase.iUsageType = CAMERA_VIDEO_REAL_TIME;
paramBase.iRCMode = RC_BITRATE_MODE;
int ret = encoder_->Initialize(¶mBase);
if (0 != ret) {
//error
}
编码需要用到SSourcePicture和SFrameBSInfo两个结构体, SSourcePicture用来保存需要编码的数据信息, 而SFrameBSInfo会保存编码完成后的数据。
SSourcePicture的定义:
typedef struct Source_Picture_s {
int iColorFormat; ///< color space type
int iStride[4]; ///< stride for each plane pData
unsigned char* pData[4]; ///< plane pData
int iPicWidth; ///< luma picture width in x coordinate
int iPicHeight; ///< luma picture height in y coordinate
long long uiTimeStamp; ///< timestamp of the source picture, unit: millisecond
} SSourcePicture;
typedef struct {
int iLayerNum;
SLayerBSInfo sLayerInfo[MAX_LAYER_NUM_OF_FRAME];
EVideoFrameType eFrameType;
int iFrameSizeInBytes;
long long uiTimeStamp;
} SFrameBSInfo, *PFrameBSInfo;
typedef struct {
unsigned char uiTemporalId;
unsigned char uiSpatialId;
unsigned char uiQualityId;
EVideoFrameType eFrameType;
unsigned char uiLayerType;
/**
* The sub sequence layers are ordered hierarchically based on their dependency on each other so that any picture in a layer shall not be
* predicted from any picture on any higher layer.
*/
int iSubSeqId; ///< refer to D.2.11 Sub-sequence information SEI message semantics
int iNalCount; ///< count number of NAL coded already
int* pNalLengthInByte; ///< length of NAL size in byte from 0 to iNalCount-1
unsigned char* pBsBuf; ///< buffer of bitstream contained
} SLayerBSInfo, *PLayerBSInfo;
SFrameBSInfo结构体比较复杂, 具体使用情况下面再解释。
对于i420数据的编码过程:
SSourcePicture pic;
memset(&pic, 0, sizeof(pic));
pic.iPicWidth = width_;
pic.iPicHeight = height_;
pic.iColorFormat = videoFormatI420;
pic.iStride[0] = pic.iPicWidth;
pic.iStride[1] = pic.iStride[2] = pic.iPicWidth >> 1;
pic.pData[0] = (unsigned char *) i420Buffer;
pic.pData[1] = pic.pData[0] + width_ * height_;
pic.pData[2] = pic.pData[1] + (width_ * height_ >> 2);
SFrameBSInfo info;
memset(&info, 0, sizeof(SFrameBSInfo));
int rv = encoder_->EncodeFrame(&pic, &info);
int retSize = 0;
if (rv != cmResultSuccess) {
//error info
return retSize;
}
if (info.eFrameType != videoFrameTypeSkip) {
int type = info.eFrameType;
for (int i = 0; i < info.iLayerNum; ++i) {
const SLayerBSInfo &layerInfo = info.sLayerInfo[i];
int layerSize = 0;
for (int j = 0; j < layerInfo.iNalCount; ++j) {
layerSize += layerInfo.pNalLengthInByte[j];
}
memcpy((char *) (oBuf + retSize), (char *) layerInfo.pBsBuf, layerSize);
retSize += layerSize;
}
}
其中i420Buffer是指向原始yuv数据的指针,oBuf是指向h264流缓冲区的指针。由上可知,整个过程分为几步:
SFrameBSInfo的参数iLayerNum表示编码后的NAL数量。编码后的h264数据存放在SFrameBSInfo的sLayerInfo结构数组中,其中每个结构体中的pBsBuf表示编码得到的数据,而长度是结构体pNalLengthInByteint数组加起来的和,数组长度由结构体的iNalCount成员表示。
先调用ISVCEncoder的Uninitialize函数,再调用WelsDestroySVCEncoder即可。
在实现OpenH264编码之前,我们先定义一些常用视频编码接口:
根据上面的定义,可以使用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;
};
}
一些注意事项:
头文件定义:
// OpenH264Encoder
#ifndef __VIDEO_OPENH264_ENCODER_H__
#define __VIDEO_OPENH264_ENCODER_H__
#include "video_codec.h"
class ISVCEncoder;
struct TagEncParamExt;
struct Source_Picture_s;
class OpenH264Encoder :public toy::VideoEncoder
{
public:
OpenH264Encoder();
~OpenH264Encoder();
//初始化编解码
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:
void SetOption(const Setting& param);
private:
ISVCEncoder* encoder_;
TagEncParamExt* encode_param_;
Source_Picture_s* picture_;
Setting setting_;
uint8_t* picture_buffer_;
bool is_open_;
bool temporal_;
unsigned int gop_size_;
bool enable_fixed_gop_;
uint64_t encoded_frame_count_;
uint64_t timestamp_;
};
#endif __VIDEO_OPENH264_ENCODER_H__
源文件实现
#include "openh264_encoder.h"
#include "openh264/wels/codec_api.h"
#include
#include
// 最大和最小的码率
#define kVideoBitrateMin 80000
#define kVideoBitrateMax 6000000
OpenH264Encoder::OpenH264Encoder()
{
encoder_ = nullptr;
encode_param_ = new SEncParamExt;
picture_ = new SSourcePicture;
picture_buffer_ = nullptr;
is_open_ = false;
temporal_ = false;
gop_size_ = 400;
enable_fixed_gop_ = true;
encoded_frame_count_ = 0;
timestamp_ = 0;
}
OpenH264Encoder::~OpenH264Encoder()
{
Close();
if (encode_param_)
{
delete encode_param_;
encode_param_ = nullptr;
}
if (picture_)
{
delete picture_;
picture_ = nullptr;
}
}
void OpenH264Encoder::Close()
{
if (encoder_) {
encoder_->Uninitialize();
WelsDestroySVCEncoder(encoder_);
encoder_ = NULL;
}
if (picture_buffer_)
{
delete[] picture_buffer_;
picture_buffer_ = nullptr;
}
is_open_ = false;
}
bool OpenH264Encoder::Open(const Setting& param){
Close();
// 创建编码器对象
int err = WelsCreateSVCEncoder(&encoder_);
if (cmResultSuccess != err) {
Close();
return false;
}
temporal_ = false;
// 获取默认参数
encoder_->GetDefaultParams(encode_param_);
// 复杂度
ECOMPLEXITY_MODE complexityMode = HIGH_COMPLEXITY;
// 码控模式
RC_MODES rc_mode = RC_BITRATE_MODE;
//int iMaxQp = qp_;
//int iMinQp = 0;
bool bEnableAdaptiveQuant = false;
rc_mode = RC_BITRATE_MODE;//RC_QUALITY_MODE
// 其他的参数:分辨率、码率、帧率等等
encode_param_->iPicWidth = param.width;
encode_param_->iPicHeight = param.height;
encode_param_->iTargetBitrate = param.bitrate_bps;
encode_param_->iMaxBitrate = kVideoBitrateMax;
encode_param_->bEnableAdaptiveQuant = bEnableAdaptiveQuant;
encode_param_->iRCMode = rc_mode;
encode_param_->fMaxFrameRate = param.fps;
encode_param_->iComplexityMode = complexityMode;
encode_param_->iNumRefFrame = -1;
encode_param_->bEnableFrameSkip = param.frame_skip;
encode_param_->eSpsPpsIdStrategy = CONSTANT_ID;
encode_param_->iEntropyCodingModeFlag = 0;//1;
//encode_param_->bEnablePsnr = false;
encode_param_->bEnableSSEI = true;
encode_param_->bEnableSceneChangeDetect = true;
// 设置QP,可以根据自己的需要来,QP越大码率越小(图像的质量越差)
encode_param_->iMaxQp = 38;
encode_param_->iMinQp = 16;
// 当前不支持时域编码
if (temporal_)
{
encode_param_->iTemporalLayerNum = 4;
}
SetOption(param);
// 空间层
SSpatialLayerConfig *spaticalLayerCfg = &(encode_param_->sSpatialLayers[SPATIAL_LAYER_0]);
spaticalLayerCfg->iVideoWidth = param.width;
spaticalLayerCfg->iVideoHeight = param.height;
spaticalLayerCfg->fFrameRate = param.fps;
spaticalLayerCfg->iSpatialBitrate = encode_param_->iTargetBitrate;
spaticalLayerCfg->iMaxSpatialBitrate = encode_param_->iMaxBitrate;
//spaticalLayerCfg->uiProfileIdc= PRO_SCALABLE_BASELINE;
// 单slice,有的解码器无法解码多slice
spaticalLayerCfg->sSliceArgument.uiSliceMode = SM_SINGLE_SLICE;
// 初始化
if (cmResultSuccess != (err = encoder_->InitializeExt(encode_param_))) {
Close();
return false;
}
// 日志级别
int log_level = WELS_LOG_ERROR;
encoder_->SetOption(ENCODER_OPTION_TRACE_LEVEL, &log_level);
// 初始化图片对象
memset(picture_, 0, sizeof(SSourcePicture));
picture_->iPicWidth = param.width;
picture_->iPicHeight = param.height;
// 只支持yuv420p
picture_->iColorFormat = videoFormatI420;
picture_->iStride[0] = picture_->iPicWidth;
picture_->iStride[1] = picture_->iStride[2] = picture_->iPicWidth >> 1;
//yuvData = CFDataCreateMutable(kCFAllocatorDefault, inputSize.width * inputSize.height * 3 >> 1);
picture_buffer_ = new uint8_t[picture_->iPicWidth * picture_->iPicHeight * 3 >> 1];
picture_->pData[0] = (unsigned char*)picture_buffer_;//CFDataGetMutableBytePtr(yuvData);
picture_->pData[1] = picture_->pData[0] + picture_->iPicWidth * picture_->iPicHeight;
picture_->pData[2] = picture_->pData[1] + (picture_->iPicWidth * picture_->iPicHeight >> 2);
is_open_ = true;
setting_ = param;
return true;
}
void OpenH264Encoder::SetOption(const Setting& param) {
// 码率重设
if (param.bitrate_bps != setting_.bitrate_bps) {
setting_.bitrate_bps = param.bitrate_bps;
encode_param_->iTargetBitrate = setting_.bitrate_bps;
encode_param_->sSpatialLayers[SPATIAL_LAYER_0].iSpatialBitrate = setting_.bitrate_bps;
SBitrateInfo bitrate_info;
bitrate_info.iLayer = SPATIAL_LAYER_0;
bitrate_info.iBitrate = setting_.bitrate_bps;
int err = encoder_->SetOption(ENCODER_OPTION_BITRATE, &bitrate_info);
if (err != 0) {
// TODO
}
}
// 帧率重设
if (param.fps != setting_.fps) {
int err = 0;
setting_.fps = param.fps;
// 注意:如果没有设置固定的I帧间隔,那么默认一个fps就输出一个I帧
int uiIntraPeriod = param.fps;
if (temporal_)
{
uiIntraPeriod = 16;
if (param.fps > 16)
{
uiIntraPeriod = 32;
}
}
// 如果设置了固定的I帧间隔,那么使用默认的I帧间隔,默认是400个
if (enable_fixed_gop_)
{
uiIntraPeriod = gop_size_;
}
SSpatialLayerConfig *spaticalLayerCfg = &(encode_param_->sSpatialLayers[SPATIAL_LAYER_0]);
// 设置I帧间隔
if (encode_param_->uiIntraPeriod != uiIntraPeriod)
{
encode_param_->uiIntraPeriod = uiIntraPeriod;
err = encoder_->SetOption(ENCODER_OPTION_IDR_INTERVAL, &uiIntraPeriod);
if (err != 0)
{
printf("Apply new video idr interval %d code %d\n", uiIntraPeriod, err);
}
}
// 设置帧率
if (spaticalLayerCfg->fFrameRate != param.fps)
{
float fps = param.fps;
err = encoder_->SetOption(ENCODER_OPTION_FRAME_RATE, (void*)&fps);
if (err != 0)
{
printf("Apply new video frame rate %d code %d\n", (int)param.fps, err);
}
}
encode_param_->uiIntraPeriod = uiIntraPeriod;
spaticalLayerCfg->fFrameRate = setting_.fps;
}
// 跳帧操作
if (encode_param_->bEnableFrameSkip != param.frame_skip)
{
setting_.frame_skip = param.frame_skip;
int err = encoder_->SetOption(ENCODER_OPTION_RC_FRAME_SKIP, &setting_.frame_skip);
if (err != 0)
{
printf("SetFrameSkip %d code %d \r\n", setting_.frame_skip, err);
return;
}
encode_param_->bEnableFrameSkip = setting_.frame_skip;
}
// TODO:QP
}
void OpenH264Encoder::ReConfigure(const Setting& param) {
if (is_open_ == false) {
Open(param);
return;
}
// 分辨率改变了,重启编码器
if (param.width != setting_.width || param.height != setting_.height) {
Close();
if (Open(param) == false) {
// TODO:
}
return;
}
SetOption(param);
setting_ = param;
return ;
}
bool OpenH264Encoder::Encode(
const uint8_t** yuv_data,
uint8_t* pkt,
size_t& pkt_size,
bool& is_keyframe,
bool& got_output )
{
got_output = false;
pkt_size = 0;
if (!is_open_) {
return false;
}
if (!encoder_)
{
return false;
}
encoded_frame_count_++;
picture_->uiTimeStamp = timestamp_++;
int y_size = picture_->iPicWidth * picture_->iPicHeight;
memcpy(picture_->pData[0] + 0, yuv_data[0], y_size);
memcpy(picture_->pData[0] + y_size, yuv_data[1], y_size / 4);
memcpy(picture_->pData[0] + y_size * 5 / 4, yuv_data[2], y_size / 4);
int iFrameSize = 0;
SFrameBSInfo encoded_frame_info;
int err = encoder_->EncodeFrame(picture_, &encoded_frame_info);
if (err) {
Close();
return 0;
}
if (encoded_frame_info.eFrameType == videoFrameTypeInvalid) {
return 0;
}
int temporal_id = 0;
if (encoded_frame_info.eFrameType != videoFrameTypeSkip) {
int iLayer = 0;
while (iLayer < encoded_frame_info.iLayerNum) {
SLayerBSInfo* pLayerBsInfo = &(encoded_frame_info.sLayerInfo[iLayer]);
if (pLayerBsInfo != NULL) {
int iLayerSize = 0;
temporal_id = pLayerBsInfo->uiTemporalId;
int iNalIdx = pLayerBsInfo->iNalCount - 1;
do {
iLayerSize += pLayerBsInfo->pNalLengthInByte[iNalIdx];
--iNalIdx;
} while (iNalIdx >= 0);
memcpy(pkt + iFrameSize, pLayerBsInfo->pBsBuf, iLayerSize);
iFrameSize += iLayerSize;
}
++iLayer;
}
got_output = true;
}
else {
#ifdef _DEBUG
printf("!!!!videoFrameTypeSkip---!\n");
#endif
is_keyframe = false;
}
if (iFrameSize > 0)
{
pkt_size = iFrameSize;
EVideoFrameType ft_temp = encoded_frame_info.eFrameType;
if (ft_temp == 1 || ft_temp == 2)
{
is_keyframe = true;
}
else if (ft_temp == 3)
{
is_keyframe = false;
if (temporal_)
{
if (temporal_id == 0 || temporal_id == 1)
{
is_keyframe = true;
}
}
}
else
{
is_keyframe = false;
}
}
return iFrameSize;
}
bool OpenH264Encoder::Encode(
const uint8_t* yuv_data,
uint8_t* pkt,
size_t& pkt_size,
bool& is_keyframe,
bool& got_output
)
{
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 OpenH264Encoder::ForceKeyframe()
{
if (!is_open_) {
return;
}
encoder_->ForceIntraFrame(true);
}
toy::VideoEncoder::Setting OpenH264Encoder::GetConfig() {
return setting_;
}
//是否使用分层
//bool OpenH264Encoder::IsTemporal()
//{
// return temporal_used_;
//}
转载文章链接:
讲解:https://www.jianshu.com/p/5208f37090a9
使用:https://blog.csdn.net/NB_vol_1/article/details/103376649