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_ 是如何调整分辨率的

猜你喜欢

转载自blog.csdn.net/tanningzhong/article/details/80535583