在我们视频电话时,快速移动摄像头时,会发现画面变出现很多方块。这就是方块效应。
无论是要发送的TCP数据包,还是要编码的图像,都会出现在短时间内出现较大的数据量。TCP面对尖峰,可以选择不为所动,但如果视频编码也对尖峰不为所动,那图像质量就会大打折扣了。因为如果有几帧数据量特别大,但我们仍要把码率控制在原来的水平,那么就会损失比较多的图像信息,图像就会失真。通常的表现是画面出现很多小方块,看上去像是打了马赛克一样,也就是方块效应。
在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;
}
修改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 */
};
硬编码与java层的通信在androidmediaencoder_jni.cc文件中,java层的实现在MediaCodecVideoEncoder.java文件中。
MediaCodec编码码率的控制模式有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 确实能增强画面质量。
在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);
Bundle param = new Bundle();
param.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME,0);
mediaCodec.setParameters(param);
VBR的码率存在一个波动范围,因此使用VBR可以在一定程度上优化方块效应,但对于视频内容的剧烈变化,VBR就只能无法处理了。WebRTC 的做法是,获取每个输出帧的 QP 值,如果 QP值过大,就说明图像复杂度太高,如果QP值持续超过上界,那就重启编码器,用更低的输出分辨率来编码;如果 QP 值过低,则说明图像复杂度太低,如果 QP 值持续低于下界,也会重启编码器,用更高的输出分辨率来编码。