Catalog
  1. 1. 概要
  2. 2. 模块分层图
  3. 3. 创建 AudioDevice
  4. 4. 初始化采集设置
  5. 5. 从采集到编码
  6. 6. 发送编码后的数据
  7. 7. 接收数据包
  8. 8. 解码和播放
  9. 9. 关键模块
    1. 9.1. AudioSourceInterface
    2. 9.2. AudioTrack
    3. 9.3. RtpTransceiver
    4. 9.4. RtpSenderBase
    5. 9.5. WebRtcVoiceMediaChannel
    6. 9.6. WebRtcAudioSendStream
webrtc音频包全过程

概要

本文分析 webrtc 音频全过程(以 linux 平台, opus 为例)。

模块分层图

分成编解码和传输这两个大的模块。

示意图
粗略的分为三个代表性的层。 ADM(AudioDeviceModule) 音频设备模块,负责音频采集和播放。VOE(VoiceEngine) 语音引擎,原则上 ADM 也属于 VOE 的一部分,但是将它拿出来看更清晰一些。VOE 管理会话、音频处理(NS、AGC、AEC)、编解码。它在中间,拿到 ADM 采集的原始数据,它经过一系列的处理,差不多就往下投递。Transceiver, 数据收发器,通信层,管理网络信道。拿到 VOE 投递来的数据,经过封装、加密等操作后发往对应的 peer。接收端则是一个逆过程。

接下来就记录一下音频包的全过程,列举了核心的函数调用序列。

创建 AudioDevice

以 linux 平台为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cricket::CompositeMediaEngine::Init
cricket::WebRtcVoiceEngine::Init
webrtc::AudioDeviceModule::Create
webrtc::AudioDeviceModule::CreateForTest
// 根据平台创建默认的 AudioDevice
webrtc::AudioDeviceModuleImpl::CreatePlatformSpecificObjects
webrtc::AudioDeviceLinuxPulse::AudioDeviceLinuxPulse
...
webrtc::AudioDeviceLinuxPulse::Init

// 启动线程,采集音频 webrtc_audio_module_rec_thread
webrtc::AudioDeviceLinuxPulse::RecThreadFunc
// 启动线程,播放音频 webrtc_audio_module_play_thread
webrtc::AudioDeviceLinuxPulse::PlayThreadFunc

初始化采集设置

以下是 linxu 平台下的初始化过程。经过调试发现,采样率是 44100, 双声道。

1
2
3
4
5
6
7
cricket::VoiceChannel::UpdateMediaSendRecvState_w
cricket::WebRtcVoiceMediaChannel::SetSend
webrtc::AudioDeviceModuleImpl::InitRecording
// 这里面会设置采样的一些参数,比如采样率,采样格式 (pcm 大小端),声道数等等
webrtc::AudioDeviceLinuxPulse::InitRecording
// create a new rec stream, 这是linux平台下音频采集的一种方式,这个部分因平台而异,参考意义不大。
pa_stream_new

从采集到编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// thread webrtc_audio_module_rec_thread, 获取 10ms 的 pcm 数据。
// 这是 linux 平台下的音频设备对象, 不同平台有差异。
webrtc::AudioDeviceLinuxPulse::ReadRecordedData
// 采集完 10ms 的数据之后,跳转到这一步
webrtc::AudioDeviceLinuxPulse::ProcessRecordedData
webrtc::AudioDeviceBuffer::DeliverRecordedData
// 处理采集到的 pcm 音频(采样率 44100, 声道数 2, 这里不清楚为什么是44.1k)
webrtc::AudioTransportImpl::RecordedDataIsAvailable
// 经过处理后的 pcm(经过声道数合并、重采样,输出数据为采样率 48000, 声道数 1)
webrtc::internal::AudioSendStream::SendAudioData
webrtc::voe::(anonymous namespace)::ChannelSend::ProcessAndEncodeAudio

// thread AudioEncoder, post 到专门的线程来编码
// 到这里,pcm 已经经过(如果有必要的话)重采样及合并声道操作,变为 48k 1 声道
webrtc::(anonymous namespace)::AudioCodingModuleImpl::Add10MsData
// 这里会调用编码器接口来编码 pcm 数据。 符合一定条件后触发后面的发送数据包流程。
webrtc::(anonymous namespace)::AudioCodingModuleImpl::Encode
webrtc::AudioEncoder::Encode
webrtc::AudioEncoderOpusImpl::EncodeImpl

发送编码后的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// thread AudioEncoder
// From AudioPacketizationCallback in the ACM
// audio_coding_module.cc packetization_callback_->SendData
// audio/channel_send.cc
webrtc::voe::(anonymous namespace)::ChannelSend::SendData
webrtc::voe::(anonymous namespace)::ChannelSend::SendRtpAudio
// with param absolute_capture_timestamp_ms
webrtc::RTPSenderAudio::SendAudio
webrtc::RTPSender::SendToNetwork
webrtc::voe::(anonymous namespace)::RtpPacketSenderProxy::EnqueuePackets
webrtc::PacedSender::EnqueuePackets
webrtc::PacingController::EnqueuePacket
// 压入队列中
webrtc::PacingController::EnqueuePacketInternal

//另一个线程(PacerThread)取队列数据并发送
webrtc::PacedSender::ModuleProxy::Process
webrtc::PacedSender::Process
webrtc::PacingController::ProcessPackets
webrtc::PacingController::GetPendingPacket
webrtc::PacedSender::SendRtpPacket
webrtc::PacketRouter::SendPacket
webrtc::ModuleRtpRtcpImpl::TrySendPacket
webrtc::RtpSenderEgress::SendPacket
webrtc::RtpSenderEgress::SendPacketToNetwork
cricket::WebRtcVoiceMediaChannel::SendRtp
cricket::MediaChannel::SendPacket
// 持有了 MediaChannel::NetworkInterface 对象(实际上是 BaseChannel)
cricket::MediaChannel::DoSendPacket
// 这里将数据转到 network_thread 继续执行, BaseChannel继承了 MediaChannel::NetworkInterface
cricket::BaseChannel::SendPacket

// network_thread
// 接上一步,仍然是这里,只不过是在另个线程中执行这个函数
cricket::BaseChannel::SendPacket
webrtc::SrtpTransport::SendRtpPacket
webrtc::SrtpTransport::SendPacket
cricket::DtlsTransport::SendPacket
cricket::P2PTransportChannel::SendPacket
cricket::ProxyConnection::Send
cricket::UDPPort::SendTo
// 接下来就是 socket 发送数据
...

接收数据包

接收到网络数据包后,压入 NetEq 队列

1
2
3
4
5
6
7
8
9
10
11
12
13
// in worker_thread
cricket::BaseChannel::OnRtpPacket
cricket::WebRtcVoiceMediaChannel::OnPacketReceived
webrtc::internal::Call::DeliverPacket
webrtc::internal::Call::DeliverRtp
webrtc::RtpStreamReceiverController::OnRtpPacket
webrtc::RtpDemuxer::OnRtpPacket
webrtc::voe::(anonymous namespace)::ChannelReceive::OnRtpPacket
webrtc::voe::(anonymous namespace)::ChannelReceive::ReceivePacket
webrtc::voe::(anonymous namespace)::ChannelReceive::OnReceivedPayloadData
webrtc::acm2::AcmReceiver::InsertPacket
webrtc::NetEqImpl::InsertPacket
webrtc::NetEqImpl::InsertPacketInternal

解码和播放

从 NetEq 队列中读取数据,解码并播放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// in webrtc_audio_module_play_thread
webrtc::AudioDeviceLinuxPulse::PlayThreadFunc
// 这里面会读取 NetEq 数据包,等到解码后的数据,通过音频播放接口播放(这里是 pa_stream_write)
// pulse 初始化参数 48K, s16le, 2channel, 3840 字节缓冲区
webrtc::AudioDeviceLinuxPulse::PlayThreadProcess
// 取 PCM 数据 (10ms 一帧)
webrtc::AudioDeviceBuffer::RequestPlayoutData
// 参数: 480 个采样点(48Khz, 10ms 采样点),每个采样点 2 字节, 2 channel
webrtc::AudioTransportImpl::NeedMorePlayData
webrtc::AudioMixerImpl::Mix
webrtc::AudioMixerImpl::GetAudioFromSources
webrtc::internal::AudioReceiveStream::GetAudioFrameWithInfo
webrtc::voe::(anonymous namespace)::ChannelReceive::GetAudioFrameWithInfo
webrtc::acm2::AcmReceiver::GetAudio
webrtc::NetEqImpl::GetAudio
//
webrtc::NetEqImpl::GetAudioInternal
webrtc::NetEqImpl::Decode
webrtc::NetEqImpl::DecodeLoop
webrtc::OpusFrame::Decode
webrtc::AudioDecoder::Decode
webrtc::AudioDecoderOpusImpl::DecodeInternal

关键模块

AudioSourceInterface

被 AudioTracks 使用。用到的 LocalAudioSource 中仅有一个状态标记和一个 AudioOptions。似乎仅维护状态和语音配置信息, 没有实际的业务逻辑。其中的一个 AddSink 接口还是空实现。

AudioTrack

主要部分是由继承 MediaStreamTrackInterface 和 AudioTrackInterface。细看也没有很多内容,仅有 id 和媒体类型信息。 其中的 AddSink 接口还是调用了 LocalAudioSource 的空 AddSink。另外,它被 AudioRtpSender 持有,这里应该还有点作用。

RtpTransceiver

只有在 unified-plan 下,可以由用户侧创建此对象。它持有 RtpSender(RtpSenderInternel) & RtpReceiver(RtpReceiverInternel), 看名字知道它是一个收发器。这个类管理 RtpSender、RtpReceiver、BaseChannel,这些对象都与 sdp 中的 “m=” 行有关。RtpTransceiver 分为音频和视频,如果是音频,那么他就管理 AudioRtpSender、AudioRtpReceiver、VoiceChannel。

RtpSenderBase

AudioRtpSender 主要继承了这个类。主要用到或管理了 MediaStreamTrackInterface、DtlsTransportInterface、cricket::MediaChannel 对象。

WebRtcVoiceMediaChannel

它的基类是 cricket::MediaChannel, 那这里就和上面的 RtpSenderBase 有联系了。

WebRtcAudioSendStream

Author: 42
Link: http://blog.ikernel.cn/2020/07/07/webrtc%E9%9F%B3%E9%A2%91%E5%8C%85%E5%85%A8%E8%BF%87%E7%A8%8B/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.

Comment