剑痴乎

剑痴乎
代码为剑,如痴如醉
  1. 首页
  2. WebRTC
  3. 正文

WebRTC研究:Audio level

2023年1月8日 944点热度 1人点赞 0条评论

在Webrtc中存在两种不同的audio level定义,本篇文章我们将介绍下这两种不同的audio level。

AudioDeviceBuffer中的level

在WebRTC中通过AudioDeviceBuffer传递采集播放数据,这里我们将介绍AudioDeviceBuffer中的audio level相关情况。首先先简单介绍下AudioDeviceBuffer在采集播放过程的作用。

音频采集

音频采集模块通过调用AudioDeviceBuffer的SetRecordedBuffer与DeliverRecordedData传递采集数据。

  • SetRecordedBuffer:把采集数据存到AudioDeviceBuffer的缓存中,调用UpdateRecStats进行采集统计
  • DeliverRecordedData:将刚才缓存的数据传递给AudioTransport,从而送到3A以及发送模块

音频播放

音频播放模块通过调用AudioDeviceBuffer的RequestPlayoutData以及GetPlayoutData获取要播放的数据。

  • RequestPlayoutData:通过AudioTransport获取要播放的数据,并存到AudioDeviceBuffer的缓存中,调用UpdatePlayStats进行播放统计
  • GetPlayoutData:获取刚才缓存的播放数据

统计日志

前面提到调用接口都是由采集播放线程驱动的。通过调用接口会同时调用音频的统计。

一般排查音频采集播放问题,我们可以通过AudioDeviceBuffer中的统计日志判断,如下是一个示例日志:

1
2
(audio_device_buffer.cc): [REC : 10000msec, 48kHz] callbacks: 1000, samples: 480000, rate: 48000, rate diff: 0%, level: 500
(audio_device_buffer.cc): [PLAY: 10000msec, 48kHz] callbacks: 1000, samples: 480000, rate: 48000, rate diff: 0%, level: 1000

该日志会打印10s内采集播放情况。

  • callbacks:表示接口调用次数,一般是默认10ms采集播放一次,所以10s内会调用1000次
  • samples:表示实际音频采集播放样本数,由于示例中采样率为48k,所以10ms样本数为480,所以10s采集播放样本数都为480000
  • rate:表示实际采集播放速率,也就是每秒的采集播放样本数
  • rate diff:表示实际采集播放速率跟采集播放设置的采样率差异,百分数表示,这个值不为0,可能是抖动以及丢音情况
  • level:这里的level是通过调用WebRtcSpl_MaxAbsValueW16计算的,WebRtcSpl_MaxAbsValueW16用于计算16bit音频样本数据的最大绝对值,这个level表示10s内采集播放数据的最大绝对值,也就是最大振幅

至此我们介绍完了AudioDeviceBuffer中的level,这个level表示音频数据的最大振幅,一般我们可以通过统计日志中这个level初步判断下音频数据情况,例如是否静音。

报头扩展中的level

这节我们介绍下音频报头扩展中的audio level。

audio level header extension

根据rfc6464,audio level报头扩展的作用是方便会议转发服务器根据携带的audio level信息进行音频选择转发处理,尤其在音频流非常多情况下。转发服务器可以根据audio level信息,转发最active的几路流,一般是选择3路,从而节省带宽等资源。

rfc中的定义

audio level扩展有两种不同的格式。

one-byte header格式:

1
2
3
4
5
          0                   1
          0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         |  ID   | len=0 |V| level       |
         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  • V:1bit,标识该音频包是否包含语音(voice activity),为1含有语音。这个字段是否用到由SDP中是否包含"vad="决定,后面SDP小节介绍
  • level:7bit,范围为0(最大声)~127(静音),单位-dBov,也就是0到-127dBov

db表示分贝,用于度量声音强度,计算公式如下:
\[
L_{p} = 20log_{10}(\frac{p_{rms}}{p_{ref}})db
\]
计算分贝需要参考值Pref,db后面带的后缀表示用到的参考值,dBov的ov后缀表示overload,参考系统的overload point,也就是当前payload格式音频所能忍受的最大声。

two-byte header格式:

1
2
3
4
5
      0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |      ID       |     len=1     |V|    level    |    0 (pad)    |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

WebRTC中默认都是使用one-byte header格式。这里我们看下one-byte header解析以及构造代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// rtp_header_extensions.cc
bool AudioLevel::Parse(rtc::ArrayView<const uint8_t> data,
                       bool* voice_activity,
                       uint8_t* audio_level) {
  // One-byte and two-byte format share the same data definition.
  if (data.size() != 1)
    return false;
  // 得到第一个bit信息
  *voice_activity = (data[0] & 0x80) != 0;
  // 得到后7bit信息
  *audio_level = data[0] & 0x7F;
  return true;
}
 
bool AudioLevel::Write(rtc::ArrayView<uint8_t> data,
                       bool voice_activity,
                       uint8_t audio_level) {
  // One-byte and two-byte format share the same data definition.
  RTC_DCHECK_EQ(data.size(), 1);
  RTC_CHECK_LE(audio_level, 0x7f);
  data[0] = (voice_activity ? 0x80 : 0x00) | audio_level;
  return true;
}

SDP中的audio level header extension

先看下一段音频示例sdp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
m=audio 9 UDP/TLS/RTP/SAVPF 111 96 103 104 9 102 0 8 106 105 13 110 112 113 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:BDVT
a=ice-pwd:ELabP3awhYCel1ebUM3UQNmQ
a=ice-options:trickle
a=fingerprint:sha-256 13:52:46:D0:64:E2:4D:4A:F8:5A:10:AD:C1:69:EF:6D:6D:D0:FB:75:E9:9B:D5:4B:CA:50:76:83:45:CA:50:D5
a=setup:actpass
a=mid:0
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=recvonly
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
a=rtpmap:96 red/48000/2
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000

可以看到其中audio level的描述如下:

1
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level

如果该描述尾部带有:vad=on,也就是:

1
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level vad=on

表示用到了header extension中的V字段,如果是”vad=off“,表示没有用到V字段。没有指定"vad=",默认表示"vad=on",WebRTC生成的SDP中没有指定"vad=",所以WebRTC中默认都是"vad=on"。

Wireshark解析的audio level

根据前面一节的SDP可知audio level扩展的ID为1,根据该ID,我们看下Wireshark中的解析:


根据解析,该音频包audio level扩展数据为0xa8,可知V字段为0xa8 & 0x80 = 1,说明有语音,level字段为:0xa8 & 0x7f = 0x28,也就是audio level为40-dbov。

代码中的audio level计算

audio level通过计算均方根(Root Mean Square,RMS)得到,计算代码位于RmsLevel类中。

均方根,也叫做平方平均数(quadratic mean),是指一组数据的平方的平均数的算术平方根。

首先需要计算所有幅值平方和:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void RmsLevel::Analyze(rtc::ArrayView<const int16_t> data) {
  if (data.empty()) {
    return;
  }
 
  CheckBlockSize(data.size());
 
  const float sum_square =
      std::accumulate(data.begin(), data.end(), 0.f,
                      [](float a, int16_t b) { return a + b * b; });
  RTC_DCHECK_GE(sum_square, 0.f);
  sum_square_ += sum_square;
  sample_count_ += data.size();
 
  max_sum_square_ = std::max(max_sum_square_, sum_square);
}

接着将平方和除以样本数:

C++
1
2
3
4
5
6
7
8
9
10
enum : int { kMinLevelDb = 127 };
 
RmsLevel::Levels RmsLevel::AverageAndPeak() {
  Levels levels = (sample_count_ == 0)
                      ? Levels{RmsLevel::kMinLevelDb, RmsLevel::kMinLevelDb}
                      : Levels{ComputeRms(sum_square_ / sample_count_),
                               ComputeRms(max_sum_square_ / *block_size_)};
  Reset();
  return levels;
}

最后根据平方和的平均数计算均方根:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static constexpr float kMaxSquaredLevel = 32768 * 32768;
// kMinLevel is the level corresponding to kMinLevelDb, that is 10^(-127/10).
static constexpr float kMinLevel = 1.995262314968883e-13f;
 
int ComputeRms(float mean_square) {
  if (mean_square <= kMinLevel * kMaxSquaredLevel) {
    // Very faint; simply return the minimum value.
    return RmsLevel::kMinLevelDb;
  }
  // 根据max level进行标准化处理
  const float mean_square_norm = mean_square / kMaxSquaredLevel;
  // 20log_10(x^0.5) = 10log_10(x)
  const float rms = 10.f * std::log10(mean_square_norm);
  // 返回负值,也就是-dbov单位
  return static_cast<int>(-rms + 0.5f);
}

计算后的audio level通过如下代码流程写到header extension中:

C++
1
2
3
4
5
6
7
8
9
10
int32_t ChannelSend::SendRtpAudio() {
  if (_includeAudioLevelIndication) {
    rtp_sender_audio_->SetAudioLevel(rms_level_.Average());
  }
}
 
bool RTPSenderAudio::SendAudio() {
  packet->SetExtension<AudioLevel>(
      frame_type == AudioFrameType::kAudioFrameSpeech, audio_level_dbov);
}

总结

本文介绍了WebRTC中两种audio level,其中一种表示振幅,另一种表示均方根能量。通过本文可以对音频问题初步分析以及audio level这个报头扩展增加了解。

参考

[1] A Real-time Transport Protocol (RTP) Header Extension for Client-to-Mixer Audio Level Indication.https://datatracker.ietf.org/doc/rfc6464/

本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可
标签: WebRTC
最后更新:2023年9月28日

Jeff

管理员——代码为剑,如痴如醉

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理。

版权声明

为支持原创,创作更好的文章,未经许可,禁止任何形式的转载与抄袭,如需转载请邮件私信!本人保留所有法定权利。违者必究!

文章目录
  • AudioDeviceBuffer中的level
    • 音频采集
    • 音频播放
    • 统计日志
  • 报头扩展中的level
    • audio level header extension
    • SDP中的audio level header extension
    • Wireshark解析的audio level
    • 代码中的audio level计算
  • 总结
  • 参考
最近评论
ztt 发布于 3 周前(04月05日) 你好,想看里面的视频和图片为什么没有显示呢?需要下flash吗还是什么。
huowa222 发布于 4 周前(03月26日) 同问
邱国禄 发布于 2 个月前(02月17日) Receive Delta以0.25ms为单位,reference time以64ms为单位,kDe...
啊非 发布于 4 个月前(12月30日) 大神,请教一个问题: constexpr int kBaseScaleFactor = Tran...
啊非 发布于 4 个月前(12月30日) reference time:3字节,表示参考时间,以64ms为单位,但是 代码里面是 Trans...
相关文章
  • WebRTC资讯:H265支持进展
  • Mac平台WebRTC编译
  • WebRTC研究:RTP时间戳的产生
  • WebRTC研究:统计参数之丢包率
  • WebRTC研究:基于Transport Feedback的早期丢包检测

COPYRIGHT © 2024 jianchihu.net. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang