定义接口
在实现OpenH264编码之前,我们先定义一些常用视频编码接口:
- 打开编码器: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;
};
}
OpenH264的一些概念
- ISVCEncoder:表示一个编码器
- TagEncParamExt:表示编码器参数
- Source_Picture_s:表示一个图像
- 关键帧:即I帧(其他的两种帧类型是:P帧和B帧)。在实时音视频领域,一般只用到I帧和P帧
- 图像组(GOP):一般来说,两个关键帧之间即为一个图像组
使用OpenH264进行视频编码
一些注意事项:
- 本实现没有使用时域编码(如果你需要svc,那么可以开启);也没有使用空间分层(即只有一个分辨率)
- GOP设置得大一些,目的是节省码率,因为I帧通常比较大;有需要的时候编码器会自行检测场景切换然后编码一个关键帧;当然也可以调用接口强制编码一个I帧
头文件定义:
// 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 <cstdio>
#include <memory>
// 最大和最小的码率
#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_;
//}