webrtc 内部支持 vp8,vp9,h264 视频编码,由于业务需要和出于通用性考虑,我选择了 h264 编码,webrtc集成了openh264,ffmpeg用于h264的编解码。当然在移动平台也集成了硬件编解码,但是测试发现在ios上硬件编码还算可以,android上表现不稳定,差异很大,主要问题出在码率控制,视频质量控制上。动态调整码率可是保证视频流畅的重要技术,但android的mediacodec在编码过程中调整码率,会出现花屏,视频质量下降严重,并且编码的延时也比较大。在windows,android,mac上采用软编码,在ios上采用硬编码。
今天主要看看openh264 是如何动态调整分辨率的
webrtc 的调整流程
openh264 的编码调用位置
src\webrtc\modules\video_coding\codecs\h264\h264_encoder_impl.cc
首先看看几个重要接口、参数
码率调整接口
int32_t H264EncoderImpl::SetRates(uint32_t bitrate, uint32_t framerate) {
if (bitrate <= 0 || framerate <= 0) {
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
}
codec_settings_.targetBitrate = bitrate;
codec_settings_.maxFramerate = framerate;
quality_scaler_.ReportFramerate(framerate);
SBitrateInfo target_bitrate;
memset(&target_bitrate, 0, sizeof(SBitrateInfo));
target_bitrate.iLayer = SPATIAL_LAYER_ALL,
target_bitrate.iBitrate = codec_settings_.targetBitrate * 1000;
openh264_encoder_->SetOption(ENCODER_OPTION_BITRATE,
&target_bitrate);
float max_framerate = static_cast<float>(codec_settings_.maxFramerate);
openh264_encoder_->SetOption(ENCODER_OPTION_FRAME_RATE,
&max_framerate);
return WEBRTC_VIDEO_CODEC_OK;
}
这个函数继承自 H264Encoder, 码率调整后调用此接口通知编码器去调整 输出码率。openh264encoder->SetOption调用
openh264 的几个重要参数
SEncParamExt H264EncoderImpl::CreateEncoderParams() const {
...
//宽
encoder_params.iPicWidth = codec_settings_.width;
//高
encoder_params.iPicHeight = codec_settings_.height;
// |encoder_params| uses bit/s, |codec_settings_| uses kbit/s.
//目标码率
encoder_params.iTargetBitrate = codec_settings_.targetBitrate * 1000;
//最大码率
encoder_params.iMaxBitrate = codec_settings_.maxBitrate * 1000;
// Rate Control mode
encoder_params.iRCMode = RC_BITRATE_MODE;
//最大帧率
encoder_params.fMaxFrameRate =
static_cast<float>(codec_settings_.maxFramerate);
// The following parameters are extension parameters (they're in SEncParamExt,
// not in SEncParamBase).
//当码率不足时,丢弃当前帧
encoder_params.bEnableFrameSkip =
codec_settings_.codecSpecific.H264.frameDroppingOn;
// |uiIntraPeriod| - multiple of GOP size
// |keyFrameInterval| - number of frames
//关键帧间隔
encoder_params.uiIntraPeriod =
codec_settings_.codecSpecific.H264.keyFrameInterval;
...
return encoder_params;
}
重点参数是 bEnableFrameSkip, 当画面剧烈运动时,编码需要的带宽也会增大,但是最大码率限制了输出带宽,当增大qp仍无法控制码率在最大码率范围内时,编码器无法正常编码,此时允许丢弃掉编码帧,稍后会说丢帧会引起调整分辨率。
有了码率调整和编码器参数调整,这两者时怎么关联起来的呢?
通过 QualityScaler quality_scaler_;
看看调用
在初始化里调用了init 设置了码率,宽,高,帧率
quality_scaler_.Init(codec_settings_.codecType, codec_settings_.startBitrate,
codec_settings_.width, codec_settings_.height,
codec_settings_.maxFramerate);
在SetRates 里上报目标帧率
quality_scaler_.ReportFramerate(framerate);
编码完成后会上报qp,或者丢弃后上报droped
if (encoded_image_._length > 0) {
// Deliver encoded image.
CodecSpecificInfo codec_specific;
codec_specific.codecType = kVideoCodecH264;
encoded_image_callback_->Encoded(encoded_image_, &codec_specific,
&frag_header);
// Parse and report QP.
h264_bitstream_parser_.ParseBitstream(encoded_image_._buffer,
encoded_image_._length);
int qp = -1;
if (h264_bitstream_parser_.GetLastSliceQp(&qp))
quality_scaler_.ReportQP(qp);
} else {
quality_scaler_.ReportDroppedFrame();
}
在编码的时候获取调整后的分辨率
quality_scaler_.OnEncodeFrame(input_frame.width(), input_frame.height());
rtc::scoped_refptr<const VideoFrameBuffer> frame_buffer =
quality_scaler_.GetScaledBuffer(input_frame.video_frame_buffer());
if (frame_buffer->width() != codec_settings_.width ||
frame_buffer->height() != codec_settings_.height) {
LOG(LS_INFO) << "Encoder reinitialized from " << codec_settings_.width
<< "x" << codec_settings_.height << " to "
<< frame_buffer->width() << "x" << frame_buffer->height();
codec_settings_.width = frame_buffer->width();
codec_settings_.height = frame_buffer->height();
SEncParamExt encoder_params = CreateEncoderParams();
openh264_encoder_->SetOption(ENCODER_OPTION_SVC_ENCODE_PARAM_EXT,
&encoder_params);
}
quality_scaler_.OnEncodeFrame 上报当前采集帧的大小 quality_scaler_.GetScaledBuffer 根据前面上报的qp droped等计算出当前应该使用的分辨率,并做了缩放处理,返回的frame是经过缩放后的帧。 if 判断分辨率做出了调整,就出重新 CreateEncoderParams(),重置编码器参数,完成调整分辨率
openh264 有个好处就是支持编码过程中调整分辨率
后面会继续分析 quality_scaler_ 是如何调整分辨率的