WebRTC音频系统 之audio技术栈简介-1

WebRTC是Google开源的Web实时音视频通信框架,其提供P2P的音频、视频和一般数据传输协议栈的支持,其音频主要包括:采集播放、众多音频编解码器、语音增强、回声消除、网络均衡和拥塞控制等音频处理单元,其视频主要包括:采集播放,丢包隐藏,视频增强和编解码几个部分,支持的编解码有H264、VP8,VP9,AV1、H265,在网络方面WebRTC提供针对音视频的动态抖动buffer管理和丢包隐藏处理,另外也提供基于STURN和TRUN的P2P的多媒体数据传输。WebRTC的时音视频通信技术栈实现很,有技术含量,这体现在将硬件、流媒体、网络、信令在实时视频会议中进行了抽象归纳解耦,这使得各个模块大部分都可并行开发,本书以WebRTC自带的Native c++例子着重分析音视频处理技术栈,音视频算法原理并不是本书的重点,比如AEC算法,只涉及API的调用而不涉及AEC内部实现的细节。

第一章 WebRTC技术栈简介

标准和源码是研究WebRTC的第一手资料,而标准和源码也是在持续的演进,在编译native源的该时候,可以生成各个模块的测试二进制程序,通过这些二进制程序熟悉各个模块的使用方法,比如APM模块的可执行二进制测试程序是audioproc_f,各个模块的组成见1.4小节,图1-3,测试程序包括了音视频以及网络部分,和native 测试app,其测试程序见下图红色部分:
请添加图片描述
图1-1 MacOS上WebRTC native编译结果

一个完整的p2p native例子可以串起整个WebRTC的处理流程,为了方便多媒体模块的管理和使用,WebRTC抽象出了MediaStream、 Tack、 channel、 engine、 transport等概念。交互式实时音视频会议存在上行和下行数据的概念,因而从硬件上存在source和sink两个方向,source是生成音频和视频内容的源,而sink则是消费音频(如播放接收到音视频),在传输上也存在sendstream和recvstream两个方向,在编解码上也存在send_codec和recv_codec两个方向,上下行统一使用voiceengine和videoengine管理,voiceengine和videoengine是多媒体管理的核心组件。本章简单阐述WebRTC使用到的一些概念和模块,熟悉这些模块的使用方法有助于理解后文整个P2P的串接过程。

1.1 视频会议中常见的服务端架构

在视频会议场景中,主要有三种类型的服务器架构,Mesh,MCU和SFU,WebRTC主推的是无中心的P2P架构,会为每一个端建立一个PeerConnection对象,这就是Mesh架构。

Mesh架构由于不需要多媒体(音视频)服务器,因而成本是最低的,安全性好,然而当人数较多时,可以看到P2P的链接数和带宽需求量变大,这在人数较少(如5人之内)是较为实用的,MCU架构需要服务器进行视频的解码、转码、混合和编码,但是上述视频的处理是比较消耗服务器资源的,因而成本较高,且会引入通信延迟,但是由于连接数量少,带宽压力小,因而在参会人数在数十人的场景中使用到,SFU架构和MCU架构一样都需要中心节点服务器,不同的是改服务器只负责转发,不负责视频处理,这在上百人场景中会用到该架构。

1.2 WebRTC 网络协议栈

Mesh架构下,需要进行透传以便通信双方能够正常进行,这就使用到的STURN和TURN以及ICE技术。WebRTC的信令传输可以慢些但需要可靠,而流媒体则实时性优,可以存在丢包、错包;这就意味着需要两套网络传输协议,一套是基于TCP的可靠传输协议用于信令等传输,一套是基于UDP的TRP协议用于实时多媒体数据的传输。其协议栈如下图所示:
请添加图片描述
图1-2 WebRTC网络技术栈

STUN(Session Traversal Utilities for NAT)(RFC5389),用于获取设备的外网地址;

TURN(Traversal Using Relays around NAT)(RFC 5766),在P2P通信失败后用于中继;

ICE(Interactive Connectivity Establishment)协议(RFC 5245),整合了TURN和STUN。

这三个协议是基于UDP协议建立和维持P2P连接的必要网络组件;DTLS(Datagram Transport Protocol)用于P2P双方数据安全传输。

信令服务器:负责端到端的连接,如SDP,candidate等;

因为由于IPv4地址数量不够用和安全的问题,开会的双方基本都在防火墙和NAT之后,通过运营商接入公网,当在家时手机、电脑、网络电视通过电信路由器上网时,路由器分配给我们的地址就是“192.168.XXX.XXX”,但是在公网上我们的数据头地址被转换成电信服务商提供的地址了,如果双发希望直接通信而不需要公共服务器中转(加大了延迟和丢包的不确定性)数据包,这时需要NAT穿透技术,STUN和TURN就是这种透传协议,ICE是一套整合了这两个协议的框架。

1.3 WebRTC 源码目录结构

WebRTC目录结构如下图所示:
请添加图片描述
图1-3 WebRTC源码目录结构

各个目录的功能如下:

api目录:是对WebRTC功能件的封装,以更方便应用层调用,这里封装的内容包括audio、video、数据通道以及RTP传输,并在create_peerconnection_factory.h文件中定义了P2P通信的核心类PeerConnectionFactoryInterface;

audio目录:这里的audio层是用于发送和接收音频数据流的网络层,真实硬件的采集播放放在adm(audio device module),增强处理放在apm(audio processing module)里,adm和apm并不在这一目录下;

base目录:提供了一些依赖OS的基础函数,比如内存管理等;

build相关目录:使用与编译WebRTC的,在编译小节中会有编译说明;

call目录:从字面可以知道是用于通信用的,主要是RTP和RTCP相关协议的封装一遍WebRTC使用;

common_audio和common_video目录:音视频的各种算法都可能用到的,比如fir滤波,环形缓冲区,窗函数等;

examples:P2P等各个平台各种例子所在的目录;

media:是对video和audio增强和编解码的封装层,即video engine和audio engine。

modules:音视频具体功能实现所在的目录,如音视频编解码实现;音频混音、处理以及设备管理,视频采集播放以及数据发送和占用带宽估计等;

pc(peer connection)目录:P2P连接实现的核心目录;

sdk目录:android平台应用层Java和MAC平台应用层Object C访问natvie层的桥接层;

1.4 client侧技术栈

Android IOS Windows MAC Linux 浏览器。
请添加图片描述
图1-4 WebRTC 客户端技术栈

由于不同平台使用了不同的UI库和编程语言,他们的实现差异很大,但是不同的平台都会支持c/c++,所以为了适配不同的平台,WebRTC提供了SDK层,Linux平台基于使用GTK的c++,MAC和IOS平台基于使用cocoa库的Object c,android平台基于JAVA,为了让这些平台都能够调用c/c++核心函数,WebRTC提供了android和object c的封装层,这称为SDK层,android中使用了JNI机制使得UI层的JAVA程序和实现核心功能的c/c++程序可以互相调用,类似的object c使用了.mm扩展程序是得UI的.m程序可以和c/c++互相调用。由于访问各个平台都提供了C API(为了效率),所有已在音视频以及网络API都可以直接通过包含不同操作系统的头文件来实现跨平台差异化编译。

1.5 WebRTC native编译以及debug

初次上手时,在编译webrtc的使用除了将is_debug=true之外,还可将rtc_base/logging.cc文件下面代码段中的LS_INFO改为LS_VERBOSE,这样可以打出更详细的信息以便于对执行过程的理解。

//rtc_base/logging.cc
/ By default, release builds don't log, debug builds at info level
#if !defined(NDEBUG)
constexpr LoggingSeverity kDefaultLoggingSeverity = LS_INFO;
#else
constexpr LoggingSeverity kDefaultLoggingSeverity = LS_NONE;
#endif

// Note: `g_min_sev` and `g_dbg_sev` can be changed while running.
LoggingSeverity g_min_sev = kDefaultLoggingSeverity;
LoggingSeverity g_dbg_sev = kDefaultLoggingSeverity;

将LS_INFO改为LS_VERBOSE以便打出更为详细的日志信息。这样可以看到如下log信息

1   gsc@gsc240:~/webrtc/src$ ./out/Default/peerconnection_client
1 (field_trial.cc:159): Setting field trial string:
2 (main_wnd.cc:255): SwitchToConnectUI
3 (peer_connection_client.cc:287): Headers received
4 (peer_connection_client.cc:462): OnClose                                                                                     5 (conductor.cc:281): OnPeerConnected                                                                                          6 (conductor.cc:267): OnSignedIn                                                                                               7 (main_wnd.cc:303): SwitchToPeerList                                                                                          8 (conductor.cc:516): SEND_MESSAGE_TO_PEER                                                                                     9 (create_peerconnection_factory.cc:72): CreatePeerConnectionFactory                                                          10 (audio_processing_builder_impl.cc:29): Create                                                                               11 (audio_processing_impl.cc:295): Injected APM submodules:
12 Echo control factory: 0
13 Echo detector: 0                                                                                                            14 Capture analyzer: 0
15 Capture post processor: 0                                                                                                   16 Render pre processor: 0
17 (audio_processing_impl.cc:304): Denormal disabler: supported                                                                18 (webrtc_voice_engine.cc:311): WebRtcVoiceEngine::WebRtcVoiceEngine
19 (webrtc_video_engine.cc:583): WebRtcVideoEngine::WebRtcVideoEngine()                                                        20 (webrtc_voice_engine.cc:333): WebRtcVoiceEngine::Init
21 (webrtc_voice_engine.cc:342): Supported send codecs in order of preference:
22 (webrtc_voice_engine.cc:345): opus/48000/2 { minptime=10 useinbandfec=1 } (111)
23 (webrtc_voice_engine.cc:345): red/48000/2 { =111/111 } (63)
24 (webrtc_voice_engine.cc:345): ISAC/16000/1 (103)
25 (webrtc_voice_engine.cc:345): ISAC/32000/1 (104)                                                                            26 (webrtc_voice_engine.cc:345): G722/8000/1 (9)                                                                               27 (webrtc_voice_engine.cc:345): ILBC/8000/1 (102)
28 (webrtc_voice_engine.cc:345): PCMU/8000/1 (0)
29 (webrtc_voice_engine.cc:345): PCMA/8000/1 (8)
30 (webrtc_voice_engine.cc:345): CN/32000/1 (106)
31 (webrtc_voice_engine.cc:345): CN/16000/1 (105)

如果想在添加额外的日志,则可以使用如下类似的方法:

RTC_LOG(INFO) << __FUNCTION__ << "AudioDeviceBuffer::~dtor";

更多的log API可以参考third_party/webrtc/rtc_base/logging.h文件。

桌面应用程序使用如下方法下载代码:

mkdir webrtc-checkout
cd webrtc-checkout
fetch --nohooks webrtc
gclient sync

其编译使用Ninja,先创建Ninja工程文件,默认工程是debug版本,这在学习的时候建议打开。

gn gen out/Default

如果想看配置参数,可以使用如下命令:

gn args out/Default --list > log_args

编译使用如下命令:

ninja -C out/Default

这样就得到了图1-1中的测试二进制程序以及Peerconnection例子的server端和client端程序,通过单元测试用例和peerconnection例子可以加深WebRTC的理解。

1.6 APM模块

APM(Audio Processing Module)提供了音频处理模块的集合,这里音频处理算法针对的是实时通信场景。APM逐帧对两路音频帧处理,其中一路的音频(near-end,采集信号)将会调用所有音频算法进行处理,这通过调用ProcessStream()实现,而另一路音(far-end,接收到的信号)频调用ProcessReverseStream()处理。

APM模块只接受10ms帧长的PCM数据,其帧长可以通过API AudioProcessing::GetFrameSize()获取,对于int16格式的多通道(channel )输入API数据是交叉存放的,而浮点格式的音频输入则采用非交叉存放。

本篇不会深入算法原理以及相关实现的技巧,对此感兴趣读者可以参考《实时语音处理实践指南》一书。

APM模块使用的方法如下代码,后文native 层peerconnection例子采用的就是这里的方法构建音频处理模块的。

AudioProcessing* apm = AudioProcessingBuilder().Create();

AudioProcessing::Config config;
config.echo_canceller.enabled = true;
config.echo_canceller.mobile_mode = false;

config.gain_controller1.enabled = true;
config.gain_controller1.mode =
AudioProcessing::Config::GainController1::kAdaptiveAnalog;
config.gain_controller1.analog_level_minimum = 0;
config.gain_controller1.analog_level_maximum = 255;

config.gain_controller2.enabled = true;
config.high_pass_filter.enabled = true;

apm->ApplyConfig(config)


apm->noise_reduction()->set_level(kHighSuppression);
apm->noise_reduction()->Enable(true);
//处理远端信号
apm->ProcessReverseStream(render_frame);

//实时通信需要设置音频增益、延迟等参数,采集到的数据调用ProcessStream处理
apm->set_stream_delay_ms(delay_ms);
apm->set_stream_analog_level(analog_level);

apm->ProcessStream(capture_frame);

AudioProcessing是一个接口类,这个接口类包含了Config字段和通用音频处理方法组成的接口类。

 class RTC_EXPORT AudioProcessing : public rtc::RefCountInterface {
    public:

    // Accepts and produces a ~10 ms frame of interleaved 16 bit integer audio as
    // specified in `input_config` and `output_config`. `src` and `dest` may use
    // the same memory, if desired.
    virtual int ProcessStream(const int16_t* const src,
                                const StreamConfig& input_config,
                                const StreamConfig& output_config,
                                int16_t* const dest) = 0;

      // Accepts deinterleaved float audio with the range [-1, 1]. Each element of
      // `src` points to a channel buffer, arranged according to `input_stream`. At
      // output, the channels will be arranged according to `output_stream` in
      // `dest`.
     //
     // The output must have one channel or as many channels as the input. `src`
     // and `dest` may use the same memory, if desired.
     virtual int ProcessStream(const float* const* src,
                               const StreamConfig& input_config,
                               const StreamConfig& output_config,
                               float* const* dest) = 0;

     // Accepts and produces a ~10 ms frame of interleaved 16 bit integer audio for
     // the reverse direction audio stream as specified in `input_config` and
     // `output_config`. `src` and `dest` may use the same memory, if desired.
     virtual int ProcessReverseStream(const int16_t* const src,
                                      const StreamConfig& input_config,
                                      const StreamConfig& output_config,
                                      int16_t* const dest) = 0;

     // Accepts deinterleaved float audio with the range [-1, 1]. Each element of
     // `data` points to a channel buffer, arranged according to `reverse_config`.
     virtual int ProcessReverseStream(const float* const* src,
                                      const StreamConfig& input_config,
                                      const StreamConfig& output_config,
                                      float* const* dest) = 0;

     // Accepts deinterleaved float audio with the range [-1, 1]. Each element
     // of `data` points to a channel buffer, arranged according to
     // `reverse_config`.
     virtual int AnalyzeReverseStream(const float* const* data,
                                      const StreamConfig& reverse_config) = 0;
     virtual int set_stream_delay_ms(int delay) = 0;
     virtual int stream_delay_ms() const = 0;
     static int GetFrameSize(int sample_rate_hz) { return sample_rate_hz / 100; }
  };

APM模块有很多处理算法,包括AEC、NS、AGC等,这里以AEC mobile版(PC版本用aec3文件夹下的)本为例,说明其实如何嵌入到APM模块的,首先AEC mobile算法的核心实现是用c代码实现的,位于modules/audio_processing/aecm/文件夹下。有如下几个文件:

aecm_core.h            aecm_core_mips.cc      aecm_defines.h         echo_control_mobile.h
aecm_core.cc           aecm_core_c.cc         aecm_core_neon.cc      echo_control_mobile.cc

在EchoControlMobileImpl类init时,会创建canceller对象:

class EchoControlMobileImpl::Canceller {
 public:
  Canceller() {
    state_ = WebRtcAecm_Create();
    RTC_CHECK(state_);
  }

  ~Canceller() {
    RTC_DCHECK(state_);
    WebRtcAecm_Free(state_);
  }

  Canceller(const Canceller&) = delete;
  Canceller& operator=(const Canceller&) = delete;

  void* state() {
    RTC_DCHECK(state_);
    return state_;
  }

  void Initialize(int sample_rate_hz) {
    RTC_DCHECK(state_);
    //这里调用echo_control_mobile.cc文件里的方法,这里的state_定义为void*类型,而在.c算法实现层,
    //则是AecMobile*类型的,在具体使用的地方强制转换下类型即可,这样也实现了隔离。
    //int32_t WebRtcAecm_GetBufferFarendError(void* aecmInst,
    //                                    const int16_t* farend,
    //                                    size_t nrOfSamples) {
    // AecMobile* aecm = static_cast<AecMobile*>(aecmInst);
    // ...
    // }
    int error = WebRtcAecm_Init(state_, sample_rate_hz);
    RTC_DCHECK_EQ(AudioProcessing::kNoError, error);
  }

 private:
  void* state_;
};

void EchoControlMobileImpl::Initialize(int sample_rate_hz,
                                       size_t num_reverse_channels,
                                       size_t num_output_channels) {
  low_pass_reference_.resize(num_output_channels);
  for (auto& reference : low_pass_reference_) {
    reference.fill(0);
  }

  stream_properties_.reset(new StreamProperties(
      sample_rate_hz, num_reverse_channels, num_output_channels));

  // AECM only supports 16 kHz or lower sample rates.
  RTC_DCHECK_LE(stream_properties_->sample_rate_hz,
                AudioProcessing::kSampleRate16kHz);

  cancellers_.resize(
      NumCancellersRequired(stream_properties_->num_output_channels,
                            stream_properties_->num_reverse_channels));

  for (auto& canceller : cancellers_) {
    if (!canceller) {
      //调用上面的Canceller函数创建canceller对象,并初始化该对象
      canceller.reset(new Canceller());
    }
    canceller->Initialize(sample_rate_hz);
  }
  Configure();
}
class EchoControlMobileImpl::Canceller {
 public:
  Canceller() {
    state_ = WebRtcAecm_Create();
    RTC_CHECK(state_);
  }

  ~Canceller() {
    RTC_DCHECK(state_);
    WebRtcAecm_Free(state_);
  }

  Canceller(const Canceller&) = delete;
  Canceller& operator=(const Canceller&) = delete;

  void* state() {
    RTC_DCHECK(state_);
    return state_;
  }

  void Initialize(int sample_rate_hz) {
    RTC_DCHECK(state_);
    //这里调用echo_control_mobile.cc文件里的方法,这里的state_定义为void*类型,而在.c算法实现层,
    //则是AecMobile*类型的,在具体使用的地方强制转换下类型即可,这样也实现了隔离。
    //int32_t WebRtcAecm_GetBufferFarendError(void* aecmInst,
    //                                    const int16_t* farend,
    //                                    size_t nrOfSamples) {
    // AecMobile* aecm = static_cast<AecMobile*>(aecmInst);
    // ...
    // }
    int error = WebRtcAecm_Init(state_, sample_rate_hz);
    RTC_DCHECK_EQ(AudioProcessing::kNoError, error);
  }

 private:
  void* state_;
};

void EchoControlMobileImpl::Initialize(int sample_rate_hz,
                                       size_t num_reverse_channels,
                                       size_t num_output_channels) {
  low_pass_reference_.resize(num_output_channels);
  for (auto& reference : low_pass_reference_) {
    reference.fill(0);
  }

  stream_properties_.reset(new StreamProperties(
      sample_rate_hz, num_reverse_channels, num_output_channels));

  // AECM only supports 16 kHz or lower sample rates.
  RTC_DCHECK_LE(stream_properties_->sample_rate_hz,
                AudioProcessing::kSampleRate16kHz);

  cancellers_.resize(
      NumCancellersRequired(stream_properties_->num_output_channels,
                            stream_properties_->num_reverse_channels));

  for (auto& canceller : cancellers_) {
    if (!canceller) {
      //调用上面的Canceller函数创建canceller对象,并初始化该对象
      canceller.reset(new Canceller());
    }
    canceller->Initialize(sample_rate_hz);
  }
  Configure();
}

其它的算法和其套路类似,这样在APM类里定义EchoControlMobileImpl成员变量,这样可以使用成员变量调用具体算法了。AudioProcessingImpl类的submodules_成员就有std::unique_prt<EchoControlMobileImpl> echo_control_mobile这一成员定义。

在capture端会调用config配置的算法完成相应处理。

int AudioProcessingImpl::ProcessStream(const int16_t* const src,
                                       const StreamConfig& input_config,
                                       const StreamConfig& output_config,
                                       int16_t* const dest) {
  TRACE_EVENT0("webrtc", "AudioProcessing::ProcessStream_AudioFrame");
  RETURN_ON_ERR(MaybeInitializeCapture(input_config, output_config));

  MutexLock lock_capture(&mutex_capture_);
  DenormalDisabler denormal_disabler(use_denormal_disabler_);

  capture_.capture_audio->CopyFrom(src, input_config);
  if (capture_.capture_fullband_audio) {
    capture_.capture_fullband_audio->CopyFrom(src, input_config);
  }
  //ProcessCaptureStreamLocked调用算法的核心处理函数
  RETURN_ON_ERR(ProcessCaptureStreamLocked());
  if (submodule_states_.CaptureMultiBandProcessingPresent() ||
      submodule_states_.CaptureFullBandProcessingActive()) {
    if (capture_.capture_fullband_audio) {
      capture_.capture_fullband_audio->CopyTo(output_config, dest);
    } else {
      capture_.capture_audio->CopyTo(output_config, dest);
    }
  }


  return kNoError;
}

int AudioProcessingImpl::ProcessCaptureStreamLocked() 函数有三百多行,精简了和算法调用无关的函数,其主体调用流程如下:

int AudioProcessingImpl::ProcessCaptureStreamLocked() {
  //先对信号进行
     submodules_.high_pass_filter->Process(capture_buffer,
                                          /*use_split_band_data=*/false);
     if (submodules_.noise_suppressor) {
      submodules_.noise_suppressor->Process(capture_buffer);
    }
  //调用mobile 版本AEC算法
    RETURN_ON_ERR(submodules_.echo_control_mobile->ProcessCaptureAudio(
        capture_buffer, stream_delay_ms()));


    if (submodules_.agc_manager) {
      submodules_.agc_manager->Process(capture_buffer);
    }

    if (submodules_.echo_detector) {
        submodules_.echo_detector->AnalyzeCaptureAudio(
        rtc::ArrayView<const float>(capture_buffer->channels()[0],
                                      capture_buffer->num_frames()));
    }

    if (!!submodules_.voice_activity_detector) {
        voice_probability = submodules_.voice_activity_detector->Analyze(
        AudioFrameView<const float>(capture_buffer->channels(),
                                      capture_buffer->num_channels(),
                                      capture_buffer->num_frames()));
    }
}

由上可以看到算法是如何调用的,接下来还剩一个问题,AudioProcessingImpl这个对象的实例上层是如何定义的?任何想使用该类的文件只需要include该头文件#include "modules/audio_processing/include/audio_processing.h"在其类中再定义 rtc::scoped_refptr<AudioProcessing> audio_processing;这样在就可以开发编译代码了,再链接的时候提供该库即可。通常只在WebRtcVoiceEngine中使用,也可以在tranport stream层或者channel层使用,通常还会和audio device module一同存在,因为audio device module采集数据,然后直接调用APM处理,这样的好处是采集是一个线程,每次采集约10ms的数据量,算法也是按照10ms帧长去处理的,这样处理起来紧凑且方便。

关于audio模块之间的组合以及调用见webrtc_voice_engine.h/webrtc_voice_engine.cc

1.7 ADM模块

audio device module,采集和播放声音都需要具体的硬件支持,Linux、Android、iOS、windows、mac都有不同硬件驱动和系统API可供调用,为了屏蔽不同操作系统提供的API差异,类似于ACM模块给编码器提供统一的接口、APM模块给音频处理算法提供统一的接口一样,ADM模块也提供了AudioDeviceModule这一接口类,该类也是通用的音频设备管理模块,该模块定义了音频设备的采集、播放以及管理(选择、枚举、启停等)的通用API,主其主要和具体的平台打交道,其支持的各个平台Audio如下:

```c++
  enum AudioLayer {
    kPlatformDefaultAudio = 0,
    kWindowsCoreAudio,
    kWindowsCoreAudio2,
    kLinuxAlsaAudio,
    kLinuxPulseAudio,
    kAndroidJavaAudio,
    kAndroidOpenSLESAudio,
    kAndroidJavaInputAndOpenSLESOutputAudio,
    kAndroidAAudioAudio,
    kAndroidJavaInputAndAAudioOutputAudio,
    kDummyAudio,
  };

AudioDeviceModule 采集到音频数据后通过回调RecordedDataIsAvailable通知该传输控制层新的数据到来,新到来的数据首先经过RemixAndResample方法进行预处理,这不是APM的处理而是将采集的音频和输出要求的格式对数据按最小计算原则混音和下采样,然后调用APM模块 webrtc::AudioProcessing 的音频数据处理,音频数据处理包括降噪、自动增益控制和回声消除等。数据处理完之后通过webrtc::AudioSender/webrtc::AudioSendStream 调用音频数据编码,如OPUS、AAC 等,RTP 打包和发送控制,打包完之后通过回调回webrtc::AudioTransport 层,webrtc::Transport 再把 webrtc::AudioSender/webrtc::AudioSendStream 得到的 RTP 和 RTCP 包发送给后面的网络接口模块块,cricket::MediaChannel::NetworkInterface 用于实现真正地把 RTP 和 RTCP 包通过底层的网络接口和协议发送,如 UDP 等。

描述设备的接口类实现定义于AudioDeviceModuleImpl,这里使用到了关键成员变量是AudioDeviceBuffer audio_device_buffer_;std::unique_ptr audio_device_;,其中audio_device_是和平台相关的,而audio_device_buffer_则维护了音频设备的缓冲区,而audio_device_则代表了平台硬件设备,采集、播放、音量控制等都通过这个对象的接口实现。即AudioDeviceModuleImpl对象调用相应的采集、播放、音量控制等方法时,就会调用audio_device_对象里的方法完成响应的动作。

class AudioDeviceModuleImpl:public AudioDeviceModule{
   public:
  enum PlatformType {
    kPlatformNotSupported = 0,
    kPlatformWin32 = 1,
    kPlatformWinCe = 2,
    kPlatformLinux = 3,
    kPlatformMac = 4,
    kPlatformAndroid = 5,
    kPlatformIOS = 6
  };
    // Retrieve the currently utilized audio layer
  int32_t ActiveAudioLayer(AudioLayer* audioLayer) const override;

  // Full-duplex transportation of PCM audio
  int32_t RegisterAudioCallback(AudioTransport* audioCallback) override;
    // Device enumeration
  int16_t PlayoutDevices() override;
  int16_t RecordingDevices() override;
  int32_t PlayoutDeviceName(uint16_t index,
                            char name[kAdmMaxDeviceNameSize],
                            char guid[kAdmMaxGuidSize]) override;
  int32_t RecordingDeviceName(uint16_t index,
                              char name[kAdmMaxDeviceNameSize],
                              char guid[kAdmMaxGuidSize]) override;

    // Device selection
  int32_t SetPlayoutDevice(uint16_t index) override;
  int32_t SetPlayoutDevice(WindowsDeviceType device) override;
  int32_t SetRecordingDevice(uint16_t index) override;
  int32_t SetRecordingDevice(WindowsDeviceType device) override;

    // Audio transport initialization
  int32_t PlayoutIsAvailable(bool* available) override;
  int32_t InitPlayout() override;
  bool PlayoutIsInitialized() const override;
  int32_t RecordingIsAvailable(bool* available) override;
  int32_t InitRecording() override;
  bool RecordingIsInitialized() const override;

  // Audio transport control
  int32_t StartPlayout() override;
  int32_t StopPlayout() override;
  bool Playing() const override;
  int32_t StartRecording() override;
  int32_t StopRecording() override;
  bool Recording() const override;
  ...
     private:
  PlatformType Platform() const;
  AudioLayer PlatformAudioLayer() const;

  AudioLayer audio_layer_;
  PlatformType platform_type_ = kPlatformNotSupported;
  bool initialized_ = false;
#if defined(WEBRTC_ANDROID)
  // Should be declared first to ensure that it outlives other resources.
  std::unique_ptr<AudioManager> audio_manager_android_;
#endif
  AudioDeviceBuffer audio_device_buffer_;
  std::unique_ptr<AudioDeviceGeneric> audio_device_;
}

AudioDeviceGeneric这个类描述了设备的通用能力,属于接口类,不同平台会继承这个类,通过多态的方式访问对应平台的方法,因而在AudioDeviceModuleImpl可以使用一个接口而不需要关心不同操作系统提供的不同方法。

int32_t AudioDeviceModuleImpl::CreatePlatformSpecificObjects() {

// Dummy ADM implementations
  audio_device_.reset(new AudioDeviceDummy());
// Dummy File ADM implementations
  audio_device_.reset(FileAudioDeviceFactory::CreateFileAudioDevice());
// Windows ADM implementation.
  audio_device_.reset(new AudioDeviceWindowsCore()

// Java audio for input and AAudio for output audio (i.e. mixed APIs).
  audio_device_.reset(new AudioDeviceTemplate<AudioRecordJni, AAudioPlayer>(
        kAndroidAAudioAudio, audio_manager));
// Linux ADM implementation.
   audio_device_.reset(new AudioDeviceLinuxALSA());
// iOS ADM implementation.
    audio_device_.reset(
        new ios_adm::AudioDeviceIOS(/*bypass_voice_processing=*/false));
// Mac OS X ADM implementation.
   audio_device_.reset(new AudioDeviceMac());

其在webrtc_voice_engine.h中被使用到。

  // The audio device module.
rtc::scoped_refptr<webrtc::AudioDeviceModule> adm_;
rtc::scoped_refptr<webrtc::AudioEncoderFactory> encoder_factory_;
rtc::scoped_refptr<webrtc::AudioDecoderFactory> decoder_factory_;
rtc::scoped_refptr<webrtc::AudioMixer> audio_mixer_;
// The audio processing module.

这样就把ADM、APM关联起来了。
请添加图片描述

rtc::scoped_refptr<AudioDeviceModule> AudioDeviceModule::Create(
    AudioLayer audio_layer,
    TaskQueueFactory* task_queue_factory) {
  RTC_DLOG(LS_INFO) << __FUNCTION__;
  return AudioDeviceModule::CreateForTest(audio_layer, task_queue_factory);
}
// static
rtc::scoped_refptr<AudioDeviceModuleForTest> AudioDeviceModule::CreateForTest(
    AudioLayer audio_layer,
    TaskQueueFactory* task_queue_factory) {
  RTC_DLOG(LS_INFO) << __FUNCTION__;

  // The "AudioDeviceModule::kWindowsCoreAudio2" audio layer has its own
  // dedicated factory method which should be used instead.
  if (audio_layer == AudioDeviceModule::kWindowsCoreAudio2) {
    RTC_LOG(LS_ERROR) << "Use the CreateWindowsCoreAudioAudioDeviceModule() "
                         "factory method instead for this option.";
    return nullptr;
  }

  // Create the generic reference counted (platform independent) implementation.
  //这里会调用AudioDeviceBuffer::ctor创建audio_device_buffer_对象,这是非常核心的一个对象
  auto audio_device = rtc::make_ref_counted<AudioDeviceModuleImpl>(
      audio_layer, task_queue_factory);

  // Ensure that the current platform is supported.
  //这里会输出当前平台,是Ubuntu Linux,所以这里会输出audio_device_impl.cc文件中的current platform is Linux信息。
  if (audio_device->CheckPlatform() == -1) {
    return nullptr;
  }

  // Create the platform-dependent implementation.
  //因为是Linux平台,所以CreatePlatformSpecificObjects()函数主要执行了audio_device_.reset(new AudioDeviceLinuxPulse());
  if (audio_device->CreatePlatformSpecificObjects() == -1) {
    return nullptr;
  }

  // Ensure that the generic audio buffer can communicate with the platform
  // specific parts.
  if (audio_device->AttachAudioBuffer() == -1) {
    return nullptr;
  }

  return audio_device;
}

在创建完audio device对象之后,会对其初始化,这包括输入输出设备初始化、声道数选择等,这一层会调用具体平台的一些方法。

void Init(AudioDeviceModule* adm) {
  RTC_DCHECK(adm);
//这里的adm->Init()实际上只是initialized_ = true;将adm初始化的标志设置成了true。
  RTC_CHECK_EQ(0, adm->Init()) << "Failed to initialize the ADM.";

  // Playout device.
  {
    if (adm->SetPlayoutDevice(AUDIO_DEVICE_ID) != 0) {
      RTC_LOG(LS_ERROR) << "Unable to set playout device.";
      return;
    }
    if (adm->InitSpeaker() != 0) {
      RTC_LOG(LS_ERROR) << "Unable to access speaker.";
    }

    // Set number of channels
    bool available = false;
    if (adm->StereoPlayoutIsAvailable(&available) != 0) {
      RTC_LOG(LS_ERROR) << "Failed to query stereo playout.";
    }
    if (adm->SetStereoPlayout(available) != 0) {
      RTC_LOG(LS_ERROR) << "Failed to set stereo playout mode.";
    }
  }

  // Recording device.
  {
    if (adm->SetRecordingDevice(AUDIO_DEVICE_ID) != 0) {
      RTC_LOG(LS_ERROR) << "Unable to set recording device.";
      return;
    }
    if (adm->InitMicrophone() != 0) {
      RTC_LOG(LS_ERROR) << "Unable to access microphone.";
    }

    // Set number of channels
    bool available = false;
    if (adm->StereoRecordingIsAvailable(&available) != 0) {
      RTC_LOG(LS_ERROR) << "Failed to query stereo recording.";
    }
    if (adm->SetStereoRecording(available) != 0) {
      RTC_LOG(LS_ERROR) << "Failed to set stereo recording mode.";
    }
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shichaog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值