WebRTC-Android硬编码流程详解

1.硬编解码的一些问题

1.1.方块效应

在我们视频电话时,快速移动摄像头时,会发现画面变出现很多方块。这就是方块效应。

无论是要发送的TCP数据包,还是要编码的图像,都会出现在短时间内出现较大的数据量。TCP面对尖峰,可以选择不为所动,但如果视频编码也对尖峰不为所动,那图像质量就会大打折扣了。因为如果有几帧数据量特别大,但我们仍要把码率控制在原来的水平,那么就会损失比较多的图像信息,图像就会失真。通常的表现是画面出现很多小方块,看上去像是打了马赛克一样,也就是方块效应。

1.2.开关硬编解码

在codec_database.cc文件中

//开关硬解码
#ifdef VIDEO_HARDWARE_DECODE 1

在video_encoder.cc文件中

//开关硬编码
#define VIDEO_HARDWARE_ENCODE 1

如果硬解码配置失败会切换到软解

VideoEncoder* VideoEncoder::Create(VideoEncoder::EncoderType codec_type) {
#if defined(WEBRTC_ANDROID) && defined(VIDEO_HARDWARE_ENCODE)
	VideoEncoder* encoder = NULL;
    LOG(LS_INFO) << "WebRTC(Android) Use Hardware Encode.";
    static webrtc_jni::MediaCodecVideoEncoderFactory* factory = NULL;
	if(!factory) {
        factory = new  webrtc_jni::MediaCodecVideoEncoderFactory;
	}
    switch (codec_type) {
      case kH264:
        LOG(LS_INFO) << "Create H264 Hardware Encode.";
        encoder = factory->CreateVideoEncoder(kVideoCodecH264);
	    break;
      case kUnsupportedCodec:
        RTC_NOTREACHED();
        return nullptr;
    }

	if(encoder) {
        return encoder;
	}
	LOG(LS_INFO) << "WebRTC(Android) Create Hardware Encode Failed. Convert to software encode.";
#endif

  switch (codec_type) {
    case kH264:
      RTC_DCHECK(H264Encoder::IsSupported());
      LOG(LS_INFO) << "Create H264 Software Encode.";
      return H264Encoder::Create();
        ...
    case kUnsupportedCodec:
      RTC_NOTREACHED();
      return nullptr;
  }
  LOG(LS_WARNING) << "No internal encode of this type exists.";
  RTC_NOTREACHED();
  return nullptr;
}

1.3.修改编解码器支持更多机型

修改java层的解码和编码2个文件
./src/webrtc/sdk/android/api/org/webrtc/MediaCodecVideoEncoder.java
./src/webrtc/sdk/android/api/org/webrtc/MediaCodecVideoDecoder.java

private static final String[] supportedH264HwCodecPrefixes = {
    "OMX.qcom.", "OMX.Intel.", "OMX.Exynos."
    ,"OMX.Nvidia.H264."     /*Nexus 7(2012), Nexus 9, Tegra 3, Tegra K1*/
    ,"OMX.ittiam.video."    /*Xiaomi Mi 1s*/
    ,"OMX.SEC.avc."         /*Exynos 3110, Nexus S*/
    ,"OMX.IMG.MSVDX."       /*Huawei Honor 6, Kirin 920*/
    ,"OMX.k3.video."        /*Huawei Honor 3C, Kirin 910*/
    ,"OMX.hisi."            /*Huawei Premium Phones, Kirin 950*/
    ,"OMX.TI.DUCATI1."      /*Galaxy Nexus, Ti OMAP4460*/
    ,"OMX.MTK.VIDEO."       /*no sense*/
    ,"OMX.LG.decoder."      /*no sense*/
    ,"OMX.rk.video_decoder."/*Youku TVBox. our service doesn't need this */
    ,"OMX.amlogic.avc"      /*MiBox1, 1s, 2. our service doesn't need this */
};

2.硬编码

硬编码与java层的通信在androidmediaencoder_jni.cc文件中,java层的实现在MediaCodecVideoEncoder.java文件中。

2.1.硬编码码率控制模式

MediaCodec编码码率的控制模式有2种,1是配置时设置目标码率和码率控制模式,2是动态调整目标码率。

2.1.2.配置时码率模式设置

配置时指定目标码率和码率控制模式:

//设置vbr模式
format.setInteger(MediaFormat.KEY_BITRATE_MODE,MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);

1.CQ表示完全不控制码率,尽最大可能保证图像质量。

2.VBR表示编码器会根据图像内容的复杂度来动态调整输出码率,图像复杂则码率高,图像简单则码率低。VBR输出码率会在一定范围内波动,对于小幅晃动,方块效应会有所改善,但对剧烈晃动仍无能为力,而连续调低码率则会导致码率急剧下降,如果无法接受这个问题,那VBR就不是好的选择。VBR在画面内容保持静止时,码率会降得很低,一旦画面内容开始动起来,码率上升会跟不上,就会导致画面质量很差;VBR上调码率后,有可能导致中间网络路径的丢包/延迟增加,进而导致问题。

3.CBR表示编码器会尽量把输出码率控制为设定值,即我们前面提到的“不为所动”。
WebRTC使用的是CBR,稳定可控是CBR的优点,一旦稳定可控,那我们就可以自己实现比较可靠的控制了。CBR会存在关键帧后的几帧内容模糊的问题,如果关键帧间隔较短,可以观察到明显的呼吸效应」。

WebRTC 使用的方案是CBR+长关键帧间隔,这样「呼吸效应」就不是那么明显,而 CBR 确实能增强画面质量。

2.1.2.动态调整目标码率

在androidmediaencoder_jni.cc文件中

int32_t MediaCodecVideoEncoder::SetRatesOnCodecThread(uint32_t new_bit_rate,
                                                      uint32_t frame_rate) {
  RTC_DCHECK(codec_thread_checker_.CalledOnValidThread());
  //帧率不大于60
  frame_rate = (frame_rate < MAX_ALLOWED_VIDEO_FPS) ?
      frame_rate : MAX_ALLOWED_VIDEO_FPS;
  if (last_set_bitrate_kbps_ == new_bit_rate &&
      last_set_fps_ == frame_rate) {
    return WEBRTC_VIDEO_CODEC_OK;
  }
  if (scale_) {
    quality_scaler_.ReportFramerate(frame_rate);
  }
  JNIEnv* jni = AttachCurrentThreadIfNeeded();
  ScopedLocalRefFrame local_ref_frame(jni);
  if (new_bit_rate > 0) {
    last_set_bitrate_kbps_ = new_bit_rate;
  }
  if (frame_rate > 0) {
    last_set_fps_ = frame_rate;
  }
  //反调java层的动态设置码率函数
  bool ret = jni->CallBooleanMethod(*j_media_codec_video_encoder_,
                                       j_set_rates_method_,
                                       last_set_bitrate_kbps_,
                                       last_set_fps_);
  CHECK_EXCEPTION(jni);
  if (!ret) {
    ResetCodecOnCodecThread();
    return WEBRTC_VIDEO_CODEC_ERROR;
  }
  return WEBRTC_VIDEO_CODEC_OK;
}

动态调整目标码率,在编码中调整目标码率,下面是java层的接口:

Bundle param =new Bundle();
param.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE,bitrate);
mediaCodec.setParameters(param);

2.2.关键帧触发

  1. MediaCodec有两种方式触发输出关键帧,是由配置时设置的 KEY_FRAME_RATE 和 KEY_I_FRAME_INTERVAL参数自动触发。自动触发实际是按照帧数触发的,例如设置帧率为 25 fps,关键帧间隔为 2s,那就会每 50 帧输出一个关键帧,一旦实际帧率低于配置帧率,那就会导致关键帧间隔时间变长。
  2. 运行过程中通过setParameters手动触发输出关键帧。对于H.264编码,WebRTC设置的关键帧间隔时间为20s,显然仅靠自动触发是不可能的,因此它会根据实际输出帧的情况,决定何时手动触发输出一个关键帧,也就是前面提到的checkKeyFrameRequired函数了。而这样做的原因,就是更可控。
    手动触发输出关键帧:
Bundle param = new Bundle();
param.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME,0);
mediaCodec.setParameters(param);

2.3.方块效应优化

VBR的码率存在一个波动范围,因此使用VBR可以在一定程度上优化方块效应,但对于视频内容的剧烈变化,VBR就只能无法处理了。WebRTC 的做法是,获取每个输出帧的 QP 值,如果 QP值过大,就说明图像复杂度太高,如果QP值持续超过上界,那就重启编码器,用更低的输出分辨率来编码;如果 QP 值过低,则说明图像复杂度太低,如果 QP 值持续低于下界,也会重启编码器,用更高的输出分辨率来编码。

你可能感兴趣的:(webrtc)