注:本文代码基于WebRTC 4472(对应Chromium 91),不保证适用于所有WebRTC版本。
在Chrome的 webrtc-internals 里可以看到很多关于媒体流的度量指标。目前Chrome(我用的版本是94)保留了传统(legacy)和WebRTC标准(standard)两种体系的度量数据。其中legacy的数据已经不再更新,也许未来某个时间就会从Chrome中消失了。无论legacy还是standard,对我们日常分析客户端侧的音视频质量数据还是非常重要的。
度量数据众多,本文只介绍度量指标中的一个:视频解码帧间隔。在legacy的webrtc-internals中存在一个帧间隔最大值,显示为googInterframeDelayMax
。这个度量指标在standard中目前应该是不存在了,取而代之的是另外2个指标:totalInterFrameDelay
和totalSquaredInterFrameDelay
。
让我们来看看这三个指标来自哪里。
通过上层 getStats 调用我们很容易就找到了这些度量值全部来自于
ReceiveStatisticsProxy
这个类 (video\receive_statistics_proxy.cc
)。其中,最大帧间隔来自于该类的成员变量 interframe_delay_max_moving_
的Max方法。
我们先来分析一下 interframe_delay_max_moving_ :
mutable rtc::MovingMaxCounter<int> interframe_delay_max_moving_ RTC_GUARDED_BY(mutex_);
它的类型是rtc::MovingMaxCounter
,实现文件:rtc_base\numerics\moving_max_counter.h
,它的实现比较简单,就4个public方法:构造函数、Add、Max、Reset。其中最终要的是 Add 和 Max 这两个方法。
rtc::MovingMaxCounter维护着一个双向队列 std::deque。通过查看它的实现,Add会将小于待添加元素的末尾元素执行pop_back,全部删除掉。例如,假设队列中有199、64、55三个数值,再添加一个124,因为队列末尾的55、64小于124,执行Add后,队列中就变为199,124,而55和64将被删除。
rtc::MovingMaxCounter的Max方法,则按照构造函数传入的范围(或者叫滑动窗口),先删除不在范围内的元素(实现在RollWindow函数里),然后直接将队首的值返回(因为按照Add的实现方法,就保证了队首是最大值)。窗口默认值定义为 kMovingMaxWindowMs(1000),单位是ms。即只保留1000ms内的数值。
下图是RollWindow
方法的一个示意图,画的比较潦草。
就是当添加新的元素⑥时,会根据设置的window长度(默认1000ms),将1000ms以前的队列元素全部删掉。
OK,再回到 ReceiveStatisticsProxy。
在ReceiveStatisticsProxy::OnDecodedFrame
中我们看到,每解码一个视频帧出来,会用当前时间减去上一次解码时间,得到两帧之间的时间间隔:
void ReceiveStatisticsProxy::OnDecodedFrame(const VideoFrame& frame,
absl::optional<uint8_t> qp,
int32_t decode_time_ms,
VideoContentType content_type) {
MutexLock lock(&mutex_);
uint64_t now_ms = clock_->TimeInMilliseconds();
......此处省略一些代码
if (last_decoded_frame_time_ms_) {
int64_t interframe_delay_ms = now_ms - *last_decoded_frame_time_ms_;
RTC_DCHECK_GE(interframe_delay_ms, 0);
double interframe_delay = interframe_delay_ms / 1000.0;
stats_.total_inter_frame_delay += interframe_delay;
stats_.total_squared_inter_frame_delay +=
interframe_delay * interframe_delay;
interframe_delay_max_moving_.Add(interframe_delay_ms, now_ms);
content_specific_stats->interframe_delay_counter.Add(interframe_delay_ms);
content_specific_stats->interframe_delay_percentiles.Add(
interframe_delay_ms);
content_specific_stats->flow_duration_ms += interframe_delay_ms;
}
if (stats_.frames_decoded == 1) {
first_decoded_frame_time_ms_.emplace(now_ms);
}
last_decoded_frame_time_ms_.emplace(now_ms);
}
可以看到,每解码一帧出来,会计算两帧间隔时间interframe_delay_ms
,添加到interframe_delay_max_moving_
,另外÷1000.0换算成秒(interframe_delay
),累加到total_inter_frame_delay
,而interframe_delay
的平方,累加到total_squared_inter_frame_delay
。
最后看一下 ReceiveStatisticsProxy::GetStats()
,这个是给外面返回度量指标的方法,其中,最大帧间隔来自于:
stats_.interframe_delay_max_ms =
interframe_delay_max_moving_.Max(now_ms).value_or(-1);
通过我们上面对 rtc::MovingMaxCounter
的说明,可以得知,最大帧间隔就是过去1000ms内,解码帧间隔的最大值。
OK,最后总结一下:
googInterframeDelayMax :过去1000ms内,解码帧间隔的最大值,单位为毫秒
totalInterFrameDelay : 解码帧间隔的累加值,单位为秒
totalSquaredInterFrameDelay : 每次解码帧间隔平方的累加值,单位为秒