转自 zhanghui_cuc :http://blog.csdn.net/nonmarking/article/details/50574733

把自己这两天学习VoiceEngine的成果分享出来,供大家参考,有什么问题也欢迎大家指出,一起学习一起进步。

本文将对VoiceEngine的基本结构做一个分析,分析的方法是自底向上的:看一个音频编码器是如何一层层被封装到VoiceEngine中的。

首先我们来看一下VoiceEngine的核心API,基本上就在webrtc\voiceengine\include的几个头文件中了。
具体来说,
voe_base
-支持G.711编码的、RTP传输的全双工VoIP应用,若要支持其他编码器,则需要VoECodec的支持
-初始化和销毁VoiceEngine实例
-通过文本或回调函数记录trace信息
-支持多channel(mixing或发送至多目标地址)

voe_codec
-支持其他编码器
-Voice Activity检测
-Possibility to specify how to map received payload types to codecs.

voe_dtmf
-Telephone event transmission.
-DTMF tone generation.

voe_errors
-错误信息

voe_external_media
-注册额外的音频处理功能

voe_file
-文件的播放、录制、转换

voe_hardware
-音频设备的操作
-设备信息
-CPU负载监控

voe_neteq_stats
-获取网络信息和音频解码信息

voe_network
-额外协议的支持
-Packet timeout notification.
-Dead-or-Alive connection observations.

voe_rtp_rtcp
- Callbacks for RTP and RTCP events such as modified SSRC or CSRC.
- SSRC handling.
- Transmission of RTCP sender reports.
- Obtaining RTCP data from incoming RTCP sender reports.
- RTP and RTCP statistics (jitter, packet loss, RTT etc.).
- Redundant Coding (RED)
- Writing RTP and RTCP packets to binary files for off-line analysis of the call quality.

voe_video_sync
-RTP header modification (time stamp and sequence number fields).
-Playout delay tuning to synchronize the voice with video.
-Playout delay monitoring.

voe_volume_control
-扬声器、麦克风音量控制
-静音

voe_audio_processing
-噪声抑制Noise Suppression)
-自动增益控制AGC
-回声消除EC
-接收端的VAD、NS、AGC
-语音、噪声、回音level的测量
-audio processing调试信息的生成与记录
-检测键盘动作

而各类音频编码器都在webrtc\modules下的各个项目中,包括了g711,g722,ilbc,isac,red(redundant audio coding),pcm16b,用于噪音生成的CNG,以及位于third_party目录下的opus。以G722音频编码器为例,在webrtc\modules\audio_coding\codecs\g722\g722_enc_dec.h文件中定义了编解码过程中的两个关键结构体G722EncoderState和G722DecoderState,如下

[cpp] view plaincopy
  1. typedef struct
  2. {
  3. /*! TRUE if the operating in the special ITU test mode, with the band split filters
  4. disabled. */
  5. int itu_test_mode;
  6. /*! TRUE if the G.722 data is packed */
  7. int packed;
  8. /*! TRUE if encode from 8k samples/second */
  9. int eight_k;
  10. /*! 6 for 48000kbps, 7 for 56000kbps, or 8 for 64000kbps. */
  11. int bits_per_sample;
  12. /*! Signal history for the QMF */
  13. int x[24];
  14. struct
  15. {
  16. int s;
  17. int sp;
  18. int sz;
  19. int r[3];
  20. int a[3];
  21. int ap[3];
  22. int p[3];
  23. int d[7];
  24. int b[7];
  25. int bp[7];
  26. int sg[7];
  27. int nb;
  28. int det;
  29. } band[2];
  30. unsigned int in_buffer;
  31. int in_bits;
  32. unsigned int out_buffer;
  33. int out_bits;
  34. } G722EncoderState;
  35. typedef struct
  36. {
  37. /*! TRUE if the operating in the special ITU test mode, with the band split filters
  38. disabled. */
  39. int itu_test_mode;
  40. /*! TRUE if the G.722 data is packed */
  41. int packed;
  42. /*! TRUE if decode to 8k samples/second */
  43. int eight_k;
  44. /*! 6 for 48000kbps, 7 for 56000kbps, or 8 for 64000kbps. */
  45. int bits_per_sample;
  46. /*! Signal history for the QMF */
  47. int x[24];
  48. struct
  49. {
  50. int s;
  51. int sp;
  52. int sz;
  53. int r[3];
  54. int a[3];
  55. int ap[3];
  56. int p[3];
  57. int d[7];
  58. int b[7];
  59. int bp[7];
  60. int sg[7];
  61. int nb;
  62. int det;
  63. } band[2];
  64. unsigned int in_buffer;
  65. int in_bits;
  66. unsigned int out_buffer;
  67. int out_bits;
  68. } G722DecoderState;

对应的G722编解码流程图如下

图中左侧的WebRtcG722xxx函数都定义于g722_interface.h中,而右边的WebRtc_g722xx函数都定义于g722_enc_dec.h中,真正的编解码功能都在WebRtc_g722_encode\decode中实现。图中的G722EncInst\DecInst其实就是前面说到的G722EncoderState和G722DecoderState。

像上面这样的一个G722音频编码器被封装到了AudioEncoderG722类中,此类继承了AudioEncoder。类似的还有G711编码器被封装到了AudioEncoderPcm类中,此类同样继承了AudioEncoder;iLBC被封装到了AudioEncoderIlbc类中,此类同样继承了AudioEncoder。AudioEncoder的定义位于webrtc\modules\audio_encoder_interface,包含了一个音频编码器的基本参数和接口。AudioEncoder进一步被AudioCoding类调用,此类位于webrtc\modules\audio_coding_module,顾名思义,AudioCoding是一个专门负责音频编码的类,具体来说,是在如下两个接口之中调用了AudioEncoder类

[cpp] view plaincopy
  1. virtual bool RegisterSendCodec(AudioEncoder* send_codec) = 0;
  2. virtual const AudioEncoder* GetSenderInfo() const = 0;

这两个接口的具体实现则在AudioCodingImpl类中,这个类同样继承了AudioCoding,但是当我们去看它们的具体实现时,却发现

[cpp] view plaincopy
  1. bool AudioCodingImpl::RegisterSendCodec(AudioEncoder* send_codec) {
  2. FATAL() << "Not implemented yet.";
  3. return false;
  4. }
  5. const AudioEncoder* AudioCodingImpl::GetSenderInfo() const {
  6. FATAL() << "Not implemented yet.";
  7. return reinterpret_cast<const AudioEncoder*>(NULL);
  8. }

转而实现的是

[cpp] view plaincopy
  1. bool AudioCodingImpl::RegisterSendCodec(int encoder_type,
  2. uint8_t payload_type,
  3. int frame_size_samples) {
  4. std::string codec_name;
  5. int sample_rate_hz;
  6. int channels;
  7. if (!MapCodecTypeToParameters(
  8. encoder_type, &codec_name, &sample_rate_hz, &channels)) {
  9. return false;
  10. }
  11. webrtc::CodecInst codec;
  12. AudioCodingModule::Codec(
  13. codec_name.c_str(), &codec, sample_rate_hz, channels);
  14. codec.pltype = payload_type;
  15. if (frame_size_samples > 0) {
  16. codec.pacsize = frame_size_samples;
  17. }
  18. return acm_old_->RegisterSendCodec(codec) == 0;
  19. }

[cpp] view plaincopy
  1. const CodecInst* AudioCodingImpl::GetSenderCodecInst() {
  2. if (acm_old_->SendCodec(&current_send_codec_) != 0) {
  3. return NULL;
  4. }
  5. return &current_send_codec_;
  6. }

调用的是acm_old_中的一些方法,而这个acm_old_的定义也在AudioCodingImpl类中,如下

[cpp] view plaincopy
  1. // TODO(henrik.lundin): All members below this line are temporary and should
  2. // be removed after refactoring is completed.
  3. rtc::scoped_ptr<acm2::AudioCodingModuleImpl> acm_old_;
  4. CodecInst current_send_codec_;

可以看到,都是一些将来可能会被取消掉的类,但是我们现在还是要看一下它们的内容,前面看到acm_old_是一个AudioCodingModuleImpl类的对象,继承的是AudioCodingModule类,这个类和AudioCoding的功能类似,只不过没有使用AudioEncoder来表示各个编码器,而是使用了CodecInst结构体。这都是在老版的webrtc中就有的东西。

[cpp] view plaincopy
  1. // Each codec supported can be described by this structure.
  2. ruct CodecInst {
  3. int pltype;
  4. char plname[RTP_PAYLOAD_NAME_SIZE];
  5. int plfreq;
  6. int pacsize;
  7. int channels;
  8. int rate;  // bits/sec unlike {start,min,max}Bitrate elsewhere in this file!
  9. bool operator==(const CodecInst& other) const {
  10. return pltype == other.pltype &&
  11. (STR_CASE_CMP(plname, other.plname) == 0) &&
  12. plfreq == other.plfreq &&
  13. pacsize == other.pacsize &&
  14. channels == other.channels &&
  15. rate == other.rate;
  16. }
  17. bool operator!=(const CodecInst& other) const {
  18. return !(*this == other);
  19. }

再接着来看AudioCodingModuleImpl中RegisterSendCodec和SendCodec的实现

[cpp] view plaincopy
  1. // Can be called multiple times for Codec, CNG, RED.
  2. int AudioCodingModuleImpl::RegisterSendCodec(const CodecInst& send_codec) {
  3. CriticalSectionScoped lock(acm_crit_sect_);
  4. return codec_manager_.RegisterEncoder(send_codec);
  5. }
  6. // Get current send codec.
  7. int AudioCodingModuleImpl::SendCodec(CodecInst* current_codec) const {
  8. CriticalSectionScoped lock(acm_crit_sect_);
  9. return codec_manager_.GetCodecInst(current_codec);
  10. }

可以看到调用的是codec_manager_中的对应方法,它是一个CodecManager类的对象,定义同样位于webrtc\modules\audio_coding_module中。总结来说,新版webrtc中提出了利用AudioCoding对编码器进行管理的新思路,但是具体实现还没有完成,目前还是使用老办法。
具体来看看codec_manager_.RegisterEncoder(send_codec),除了一些基本的检查之外,可以看到主要调用的是codec_owner_.SetEncoders()方法。
codec_owner_是一个CodecOwner类的对象,位于webrtc\modules\audio_coding_module中。codec_owner_.SetEncoders()中调用的是CreateSpeechEncoder方法。目前来看的话,就是把具体的编码器的指针做一个赋值操作,不像ffmpeg那样维护一个编码器链表。

回过头来,再看看AudioCodingModule类都被谁调用了,我们的目标就是一路找到VoiceEngine中对应的调用。
比较引人注目的是两个类对它的调用,一是voe命名空间中的Channel类,这一个信道方面的类我们以后再看,另一个就是VoECodecImpl类中对其的调用,这个类继承了VoECodec。VoECodecImpl类被VoiceEngineImpl类直接继承,VoECodec则是VoiceEngine的核心成员之一。

以上分析对应于下图,图中的红线代表继承关系、绿线代表调用关系

以上都是编码器的部分,中间还注意到一些解码器的部分。例如与webrtc\modules\audio_encoder_interface对应的webrtc\modules\audio_decoder_interface,其中的AudioDecoder类,也同样被很多编码器对应的解码器类所集成,例如AudioDecoderG722类等等,这些都在webrtc\module\neteq目录中,我们暂且按下不表,接着来说音频相关内容中的发送端。

来看一下诸如回音消除AEC、增益控制AGC、高通滤波、噪声抑制DeNoise(Noise Suppression)、静音检测VAD、等等的预处理步骤。
这里以回音消除技术为例进行说明,webrtc中的回音消除有针对移动端和非移动端的两个实现,分别在webrtc\modules\audio_processing的aec和aecm两个目录中,除此之外,对于支持SSE的x86平台,还有对应的SSE实现,位于webrtc\modules\audio_processing_sse2目录下。
具体的回音消除代码被封装到了EchoCancellationImpl类中,除了具体的实现之外,EchoCancellationImpl类中还包含一些handle操作的内容,这部分则继承自ProcessingComponent类。而EchoCancellationImpl的父类则是EchoCancellation,这一父类的对象直接在VoEAudioProcessingImpl类中被调用,其定义位于webrtc\voice_engine下。VoEAudioProcessingImpl的父类就是VoEAudioProcessing,同样是VoiceEngine的核心成员之一。其中的关系已经非常明了了。
这些预处理相关的内容相对于编码器的部分来说更加简单了。

下面再来看看音频解码的部分。前面说到具体的解码器类都在webrtc\module\neteq目录中,实际上解码端的其他一些内容,诸如抖动缓冲区等,都在这个目录下。而解码端的另一项重要功能,即混音功能,则在webrtc\modules\audio_conference_mixer目录下。
neteq模块即当年GIPS的核心技术,将解码、自适应抖动缓冲和丢包隐藏结合在一起,实现优秀的语音质量和低延时。
还是以我最熟悉的、单纯的解码模块来入手,前面提到的AudioDecoder类和AudioEncoder类相似,也在audio_coding_module、codec_manager、codec_owner中得到了调用,除此之外,在neteq的一干涉及到解码的内容之中都能看到他的身影。
neteq作为音频接收端的一个子模块,接下来在AcmReceiver类中得到了调用,而这个类是AudioCodingModuleImpl的成员之一,由此,即可追溯得到它与VoiceEngine的关系。

以上完成了VoiceEngine中音频编解码、预处理、neteq内容的分析总结。

下面再看看音频设备部分,这部分代码位于webrtc\modules\audio_devices,相关的类是AudioDeviceModule,其中支持了多个平台的音频设备,也包括了很多功能,例如麦克风和扬声器的音量控制、静音控制、采样率选择、立体声、混音等等。具体的实现我们暂且不看,只看它与VoiceEngine的接口关系。
需要注意的是,尽管VoiceEngine里面有一个VoEHardware类,但是对音频设备的初始化却是在VoEBase类中实现的。

[cpp] view plaincopy
  1. virtual int Init(AudioDeviceModule* external_adm = NULL,
  2. AudioProcessing* audioproc = NULL) = 0;

混音在AudioConferenceMixer类中,受VoiceEngine的OutputMixer类调用
其他一些辅助的类:AcmDump用于输出调试信息;MediaFile类用于音频文件的输入输出,同样受VoiceEngine的OutputMixer类的调用(中间经过了FileRecorder和FilePlayer)

rtp\rtcp中包含了RTP、RTCP传输中的全部内容,例如网络信道数据的报告、重传请求、视频关键帧的request、码率控制等等。暂且按下不表,后续再进行详细分析。跟它配套的还有remote_bitrate_estimator\paced_sender\bitrate_controler。
而RtpRtcp这个类也很自然地被VoiceEngine的Channel类调用了。

至此,webrtc\module下与音频传输的模块基本都分析了一遍,也看了一下它们是怎么与VoiceEngine联系到一起的,

在下一篇文章中,将使用VoiceEngine完成一个语音通话示例。

WebRTC VoiceEngine综合应用示例(一)——基本结构分析相关推荐

  1. Cisco Easy ***综合配置示例

    以下内容摘自正在全面热销的最新网络设备图书"豪华四件套"之一<Cisco路由器配置与管理完全手册>(第二版)(其余三本分别是:<Cisco交换机配置与管理完全手册 ...

  2. webrtc入门:14.pion webrtc中Data Channels示例

    Data Channels 在pion webrtc 中有非常多的示例,Data Channels 就是其中的一个,当我们第一次打开pion webrtc的示例时,可能会有点不知所措,不知道他要让我们 ...

  3. 结合WebSocket编写WebGL综合场景示例

    在WebGL场景中导入多个Babylon骨骼模型,在局域网用WebSocket实现多用户交互控制. 首先是场景截图: 上图在场景中导入一个Babylon骨骼模型,使用asdw.空格.鼠标控制加速度移动 ...

  4. ffmpeg综合应用示例(一)——摄像头直播

    本文的示例将实现:读取PC摄像头视频数据并以RTMP协议发送为直播流.示例包含了 1.ffmpeg的libavdevice的使用 2.视频解码.编码.推流的基本流程 具有较强的综合性. 要使用liba ...

  5. ffmpeg综合应用示例(二)——为直播流添加特效

    在上一篇文章中,讲解了如何利用ffmpeg实现摄像头直播,本文将在此基础上,实现一个可以选择各种视频滤镜的摄像头直播示例.本文包含以下内容 1.AVFilter的基本介绍 2.如何利用ffmpeg命令 ...

  6. DataRowState、AcceptChanges、RejectChanges综合使用示例:实现DataGridView数据的增、删、改...

    下面的示例中,通过一个程序,演示使用DataRowState.AcceptChanges.RejectChanges,实现DataGridView数据的增.删.改. 一.界面设计 二.代码实现 1 u ...

  7. ffmpeg综合应用示例(三)——安卓手机摄像头编码

    本文的示例将实现:读取安卓手机摄像头数据并使用H.264编码格式实时编码保存为flv文件.示例包含了 1.编译适用于安卓平台的ffmpeg库 2.在java中通过JNI使用ffmpeg 3.读取安卓摄 ...

  8. android开发技术探索,《android开发艺术探索》读书笔记(十三)--综合技术(示例代码)...

    No1: No2: 在Android中单个dex文件所能够包含的最大方法数为65536,这包含Android FrameWork.依赖的jar包以及应用本身的代码中的所有方法. No3: 使用mult ...

  9. 机器学习之多元线性回归问题综合应用示例:简单案例+解决红酒质量的判断问题

    补博客持续更新中

最新文章

  1. Could not find 'cudnn64_6.dll'
  2. WinCE内核裁减(中文字体)及字库和内核的分离(转)
  3. RocketMQ-Spring 毕业两周年,为什么能成为 Spring 生态中最受欢迎的 messaging 实现?
  4. 如何制作linux系统硬盘,教你制作Linux操作系统的Boot/Root盘
  5. linux笔记之 搭建本地yum源,网卡的基本操作
  6. 互联网移动支付技术_安全架构图(安全技术/安全协议/加密技术)——转载图片...
  7. @Autowired和构造方法执行的顺序解析
  8. 数学建模学习笔记(一)——层次分析模型
  9. spark基础之调度器运行机制简述
  10. 弹出窗弹出两次的原因
  11. python中os模块详解_Python OS模块(常见文件操作示例)
  12. 微信小程序 wx.previewImage 预览分享图片结束之后 执行事件
  13. 【源码】从Ansys导入mesh到MATLAB的小程序
  14. 《Gradient-based learning applied to document recognition》翻译
  15. 高中计算机听课总结,信息技术听课心得.doc
  16. 单证与双证高级证书与普通证书的区别与联系
  17. 服务器双因素认证微软Ad,12-双因素认证(MIX)配置举例
  18. Python第三方库之MedPy
  19. 基于词典的细粒度情感分析
  20. 如何在 JavaScript 中获取当前日期?

热门文章

  1. python关键词_Python批量挖掘百度凤巢关键词
  2. 2018年上半年总结
  3. mysql8.0.13驱动包_MYSQL ODBC驱动包-mysql connector odbc x64位版下载V8.0.13 官方安装版-odbc西西软件下载...
  4. 用 Signal Processing Toolbox 软件对数据进行滤波
  5. 【CV】虹膜识别源代码下载,分别基于MATLAB、C++、OpenCV
  6. 计算机专业大专还学语数外吗,大学越学越秃的5大专业:计算机排第五,数学落榜,榜首实至名归...
  7. Perfetto —— 靠谱的trace利器
  8. Hive中ORDER BY、SORT BY和DISTRIBUTE BY
  9. [转]REBOL 语言简介
  10. PHP正则匹配img并处理src