webrtc 視訊編碼之 h264 自動調節解析度一
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_ 是如何調整解析度的