视频采集网站源码-技术宝典|WebRTCADM源码流程分析

1. ADM的基本结构 1.1 ADM的结构分析

在WebRTC中视频采集网站源码,ADM(AudioDeviceManager)的行为由AudioDeviceModule定义,具体由AudioDeviceModuleImpl实现。

从前面的结构图可以看出,AudioDeviceModule定义了所有与ADM相关的行为(上图只列出了部分核心,更多细节请参考源码中的完整定义)。 从AudioDeviceModule的定义可以看出,AudioDeviceModule的主要职责如下:

初始化音频播放/采集设备

启动音频播放/采集设备

停止音频播放/捕获设备;

当音频播放/采集设备工作时,对其进行操作(例如:静音、调节音量);

平台上外接3A开关的调整(主要针对Android平台);

获取当前音频播放/采集设备的各种相关状态(类图中未完全展示,具体请参考源码)

AudioDeviceModule具体是由AudioDeviceModuleImpl实现的,两者之间还有一个AudioDeviceModuleForTest,主要是增加了一些测试套接字,对本文的分析没有影响,可以直接忽略。 AudioDeviceModuleImpl中有两个特别重要的成员变量,一个是audio_device,它的具体类型是std::unique_ptr,另一个是audio_device_buffer,它的具体类型是AudioDeviceBuffer。

其中,audio_device_是AudioDeviceGeneric的类型,AudioDeviceGeneric是各个平台具体音频采集和播放设备的具体表示,负责AudioDeviceModuleImpl对具体设备的操作。 当涉及到具体设备的操作时,AudioDeviceModuleImpl不仅仅做一些状态判断,设备的具体操作都是由AudioDeviceGeneric来完成的。 AudioDeviceGeneric的具体实现由各个平台自行实现。 比如iOS平台的具体实现是AudioDeviceIOS,Android平台的具体实现是AudioDeviceTemplate。 至于各个平台的具体实现,有兴趣的可以单独分析。 我们在这里谈谈最重要的共同点。 从各个平台的具体实现的定义可以发现,它们都有一个audio_device_buffer成员变量,而这个变量和上面提到的AudioDeviceModuleImpl中的另一个重要的成员变量audio_device_buffer是一样的,虽然两者是同一个。 AudioDeviceModuleImpl通过AttachAudioBuffer()方法将自己的audio_device_buffer对象传递给具体平台实现对象。

audio_device_buffer_的具体类型是AudioDeviceBuffer。 AudioDeviceBuffer中的play_buffer和rec_buffer是int16_t类型的缓冲区。 后者用作向上获取和播放PCM数据的Buffer。 前者作为Buffer向上传输和收集PCM数据。 本章详细分析了数据流向,还有另一个成员变量audio_transport_cb,类型为AudioTransport,从AudioTransport套接字中定义的两个核心技巧不难看出它的作用。 将收集并存储在rec_buffer_中的PCM数据向上传递,后续具体过程参考数据流章节。

1.2 ADM扩容的思考

从WebRTCADM的实现来看,WebRTC只实现了各个平台对应的具体硬件设备,并没有虚拟设备。 而且在实际项目中,往往需要支持外部音频输入/输出,即从业务下层推/拉音频数据(PCM...),而不是直接启动平台硬件进行采集/播放。 这些情况下,虽然原生WebRTC不支持,而且修复起来也很简单,因为虚拟设备与平台无关,可以直接在AudioDeviceModuleImpl中减少一个与真实设备audio_device_对应的VirtualDevice(变量名暂定为 virtual_device ),virtual_device也和audio_device_一样,实现了AudioDeviceGeneric相关socket,然后引用audio_device_的实现来实现数据“采集”(push)和“播放”(pull),无需连接特定平台硬件设备,唯一需要处理的就是化学设备audio_device_和虚拟设备virtual_device_之间的切换或者协同工作。

【免费分享】音视频学习资料包、大厂笔试题、技术视频和学习路线图,资料包括(C/C++、Linux、FFmpegwebRTCrtmphlsrtspffplaysrs等)如有需要可以在邮件中减1免费发送背景~

2. ADM设备的启动 2.1 启动时序

ADM设备的启动时机没有特殊要求,只要创建ADM即可,但是WebRTC Native源码会在SDP协商完成后检查是否需要启动相关的ADM设备。 如有必要,将启动相关的ADM设备。 采集和播放设备的启动是完全独立的,但过程略有不同。 相关触发代码如下,从上到下阅读即可。

以下是采集设备启动的触发源码(后面几步还有其他触发入口,但前面的都是一样的,这里只展示核心流程):

//cricket::VoiceChannel
void VoiceChannel::UpdateMediaSendRecvState_w() {
  //***************  
  bool send = IsReadyToSendMedia_w();
  media_channel()->SetSend(send);
  }
// cricket::WebRtcVoiceMediaChannel
void WebRtcVoiceMediaChannel::SetSend(bool send) {
   //***************
  for (auto& kv : send_streams_) {
    kv.second->SetSend(send);
  }
}
//cricket::WebRtcVoiceMediaChannel::WebRtcAudioSendStream
  void SetSend(bool send) {
   //***************
    UpdateSendState();
  }
//cricket::WebRtcVoiceMediaChannel::WebRtcAudioSendStream
  void UpdateSendState() {
   //*************** 
    if (send_ && source_ != nullptr && rtp_parameters_.encodings[0].active) {
      stream_->Start();
    } else {  // !send || source_ = nullptr
      stream_->Stop();
    }
  }
  
  // webrtc::internal::WebRtcAudioSendStream
  void AudioSendStream::Start() {
  //***************
  audio_state()->AddSendingStream(this, encoder_sample_rate_hz_,
                                  encoder_num_channels_);
}
// webrtc::internal::AudioState
void AudioState::AddSendingStream(webrtc::AudioSendStream* stream,
                                  int sample_rate_hz,
                                  size_t num_channels) {
  //***************
  //检查下采集设备是否已经启动,如果没有,那么在这启动
  auto* adm = config_.audio_device_module.get();
  if (!adm->Recording()) {
    if (adm->InitRecording() == 0) {
      if (recording_enabled_) {
        adm->StartRecording();
      }
    } else {
      RTC_DLOG_F(LS_ERROR) << "Failed to initialize recording.";
    }
  }
}

从采集设备启动的触发源码可以看出,如果需要发送音频,无论后面是否启动采集设备,在SDP协商完成后,采集设备肯定会启动。 如果我们想把采集设备的启动时机控制在下层业务手中,只需要注释掉AddSendingStream方法中启动设备的那几行代码,然后通过ADM启动采集设备即可必要时。

以下是启动播放设备的触发源码(后面几步还有其他触发入口,但前面的都是一样的,这里只展示核心流程):

//cricket::VoiceChannel
void VoiceChannel::UpdateMediaSendRecvState_w() {
  //***************  
  bool recv = IsReadyToReceiveMedia_w();
  media_channel()->SetPlayout(recv);
  }
// cricket::WebRtcVoiceMediaChannel
void WebRtcVoiceMediaChannel::SetPlayout(bool playout) {
 //***************  
  return ChangePlayout(desired_playout_);
}
// cricket::WebRtcVoiceMediaChannel
void WebRtcVoiceMediaChannel::ChangePlayout(bool playout) {
//***************  
  for (const auto& kv : recv_streams_) {
    kv.second->SetPlayout(playout);
  }
}
//cricket::WebRtcVoiceMediaChannel::WebRtcAudioReceiveStream
  void SetPlayout(bool playout) {
   //***************  
    if (playout) {
      stream_->Start();
    } else {
      stream_->Stop();
    }
  }
//  webrtc::internal::AudioReceiveStream
void AudioReceiveStream::Start() {
   //***************  
  audio_state()->AddReceivingStream(this);
}
//webrtc::internal::AudioState
void AudioState::AddReceivingStream(webrtc::AudioReceiveStream* stream) {
  //***************  
  // //检查下播放设备是否已经启动,如果没有,那么在这启动
  auto* adm = config_.audio_device_module.get();
  if (!adm->Playing()) {
    if (adm->InitPlayout() == 0) {
      if (playout_enabled_) {
        adm->StartPlayout();
      }
    } else {
      RTC_DLOG_F(LS_ERROR) << "Failed to initialize playout.";
    }
  }
}

从播放设备启动的触发源码可以看出,如果需要播放音频,无论后面是否启动播放设备,SDP协商完成后视频采集网站源码,肯定会启动播放设备。 如果我们想要控制下层业务手中的播放设备的启动时机,只需要在AddReceivingStream方法中注释掉启动设备的代码行,然后通过ADM启动播放设备即可必要时。

2.2 启动流程

当需要启动ADM设备时,首先调用ADM的InitXXX,然后调用ADM的StartXXX。 实际上是通过内部框架的层层调用具体平台对应的实现。 详细流程如右图所示:

关于设备停产

了解了ADM设备的启动之后,相应的停止动作就不用多说了。 如果你看源码,你会发现看似停止和启动的动作和进程基本上是一一对应的。

3、ADM音频数据流发送至3.1音频数据

上图展示了音频数据传输的核心流程,主要是核心函数的调用和线程的切换。 PCM数据是从硬件设备采集的,采集线程中一些简单的数据封装会快速进入APM模块进行相应的3A处理。 从流程上来看,APM模块非常接近原始PCM数据,这对于APM处理非常重要。 疗效特别有帮助,有兴趣的朋友可以深入研究APM相关知识。 以后数据会被封装成一个Task,传递给一个叫rtp_send_controller的线程。 至此,采集线程的工作就完成了,采集线程也可以尽快开始下一轮数据读取,这样可以尽量减少对采集尽快读取新PCM数据的影响以避免 PCM 数据丢失或任何必要的延迟。

然后数据到达rtp_send_controller线程。 rtp_send_controller线程这里有三个主要功能。 一是控制rtp发送的串扰,二是对PCM数据进行编码,三是将编码后的数据打包成RtpPacketToSend(RtpPacket)格式。 最终的RtpPacket数据会被投递到一个名为RoundRobinPacketQueue的队列中,rtp_send_controller线程的工作就完成了。

旁边的RtpPacket数据会在SendControllerThread中处理。 SendControllerThread主要用于控制发送状态和窗口串扰。 最后将数据通过消息(类型:MSG_SEND_RTP_PACKET)发送到Webrtc三大线程之一的网络线程(NetworkThread),然后发送到网络。 至此整个发送过程结束。

3.2 数据接收与回放

上图是音频数据接收和播放的核心流程。 网络线程(NetworkThread)负责从网络接收RTP数据,然后异步解包分发给工作线程(WorkThread)。 如果接收到多路音频,则会有多个ChannelReceive,每个ChannelReceive的处理流程相同,最终未解码的音频数据存放在NetEq模块的packet_buffer_中。 同时,播放设备线程不断从当前所有音频ChannelReceive中获取音频数据(10ms宽度),从而触发NetEq请求解码器进行音频解码。 对于音频解码,WebRTC提供了统一的socket,具体解码器只需要实现对应的socket即可,比如WebRTC默认的音频解码器opus。 遍历并解码ChannelReceive中的所有数据后,会被旁边的AudioMixer混合并记录。 混音后会交给APM模块进行处理,最后处理后在设备上播放。

原文链接:技术宝典|WebRTCADM源码流程分析

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 网站源码 视频采集网站源码-技术宝典|WebRTCADM源码流程分析 https://www.wkzy.net/game/159698.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务