1. 程式人生 > >webrtc 視訊編碼之 h264 自動調節解析度一

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_ 是如何調整解析度的