Análisis del proceso del código fuente de WebRTC ADM

Introducción : este artículo se basa principalmente en el código fuente de la versión 72 de WebRTC y en la experiencia relevante acumulada por el equipo de audio y video en la nube y analiza principalmente las siguientes preguntas: ¿Cuál es la arquitectura de ADM (Administrador de dispositivos de audio)? ¿Cuál es el proceso de inicio de ADM (Administrador de dispositivos de audio)? ¿Cuál es el flujo de datos de ADM (Administrador de dispositivos de audio)? Este artículo analiza principalmente los procesos centrales relevantes, para que cuando los necesite, pueda ubicar rápidamente los módulos relevantes.

1. La estructura básica de ADM

Análisis de arquitectura de ADM

En WebRTC, el comportamiento de ADM (Administrador de dispositivos de audio) está definido por AudioDeviceModule, que está implementado específicamente por AudioDeviceModuleImpl.

En el diagrama de arquitectura anterior se puede ver que AudioDeviceModule define todos los comportamientos relacionados con ADM (el diagrama anterior solo enumera algunos núcleos; para obtener más detalles, consulte la definición completa en el código fuente). De la definición de AudioDeviceModule, podemos ver que  las principales responsabilidades de AudioDeviceModule son las siguientes:

  • Inicialice el dispositivo de reproducción/captura de audio;

  • Inicie el dispositivo de reproducción/captura de audio;

  • Detenga el dispositivo de reproducción/captura de audio;

  • Cuando el dispositivo de reproducción/captura de audio esté funcionando, utilícelo (por ejemplo: silenciar, ajustar el volumen);

  • Ajuste del interruptor 3A incorporado de la plataforma (principalmente para la plataforma Android);

  • Obtener varios estados relacionados del dispositivo de reproducción/captura de audio actual (no se refleja completamente en el diagrama de clase, consulte el código fuente para obtener más detalles)

AudioDeviceModule está específicamente implementado por AudioDeviceModuleImpl, y también hay un AudioDeviceModuleForTest entre los dos, que principalmente agrega algunas interfaces de prueba, que no tienen ningún efecto en el análisis de este artículo y pueden ignorarse directamente. Hay dos variables miembro muy importantes en AudioDeviceModuleImpl, una es  audio_device_ , su tipo específico es  std::unique_ptr , la otra es  audio_device_buffer_ , su tipo específico es  AudioDeviceBuffer .

Entre ellos, audio_device_ es del tipo AudioDeviceGeneric , y AudioDeviceGeneric es una abstracción del dispositivo específico de captura y reproducción de audio de cada plataforma, que es responsable del funcionamiento de AudioDeviceModuleImpl en el dispositivo específico. Cuando se trata de la operación de un dispositivo específico, AudioDeviceModuleImpl no solo realiza un juicio estatal, sino que también AudioDeviceGeneric realiza la operación específica del dispositivo. Cada plataforma implementa la implementación específica de AudioDeviceGeneric. Por ejemplo, la implementación específica de la plataforma iOS es AudioDeviceIOS, y la implementación específica de la plataforma Android es AudioDeviceTemplate. En cuanto a la implementación específica de cada plataforma, los interesados ​​pueden analizarla individualmente. Permítanme hablar sobre el punto común más importante aquí, de la definición de la implementación específica de cada plataforma, se puede encontrar que todas tienen una variable miembro audio_device_buffer, y esta variable y la otra variable miembro importante audio_device_buffer_ en el mencionado AudioDeviceModuleImpl, de hecho, los dos son el mismo. AudioDeviceModuleImpl pasa su propio objeto audio_device_buffer_ al objeto de implementación de la plataforma específica a través del método AttachAudioBuffer().

El tipo específico de audio_device_buffer_ es AudioDeviceBuffer. Play_buffer_ y rec_buffer_ en AudioDeviceBuffer son búferes de tipo int16_t. El primero se usa como búfer para adquirir y reproducir datos PCM hacia abajo, y el último se usa como búfer para transmitir y recopilar datos PCM. El flujo de datos PCM específico está en El siguiente flujo de datos se analiza en detalle en el capítulo, y otra variable miembro audio_transport_cb_, el tipo es AudioTransport, no es difícil ver su función a partir de los dos métodos principales definidos en la interfaz AudioTransport, uno es bajar los datos PCM de reproducción y almacenarlos en play_buffer_, el otro transmite los datos PCM recopilados y almacenados en rec_buffer_, y el proceso específico posterior se refiere al capítulo sobre flujo de datos.

Envíame un mensaje privado para recibir los últimos y más completos materiales de aprendizaje y mejora de audio y video de C++ , incluidos ( C/C++ , Linux , FFmpeg , webRTC , rtmp , hls , rtsp , ffplay , srs )

Reflexiones sobre el escalado de ADM 

A partir de la implementación de WebRTC ADM, WebRTC solo implementa dispositivos de hardware específicos correspondientes a cada plataforma, y ​​no hay dispositivos virtuales. Sin embargo, en los proyectos reales, a menudo es necesario admitir la entrada/salida de audio externa, es decir, la capa superior de los datos de audio push/pull de la empresa (PCM...) en lugar de iniciar directamente el hardware de la plataforma para la recopilación/reproducción. En este caso, aunque el WebRTC nativo no lo soporta, es muy sencillo de transformar, como el dispositivo virtual no tiene nada que ver con la plataforma, puedes añadir directamente un Virtual Device correspondiente al dispositivo real audio_device_ en AudioDeviceModuleImpl (la variable El nombre se establece tentativamente como virtual_device_), virtual_device_ también es lo mismo que audio_device_, implementa interfaces relacionadas con AudioDeviceGeneric y luego se refiere a la implementación de audio_device_ para realizar la "recolección" de datos (empujar) y "reproducir" (jalar), sin necesidad de conectarse al dispositivo de hardware de una plataforma específica, lo único que necesita ser procesado es Switching o co-working entre el dispositivo físico audio_device_ y el dispositivo virtual virtual_device_.

2. Puesta en marcha de equipos ADM

 Empezar a cronometrar 

No existe un requisito especial para el tiempo de inicio del dispositivo ADM, siempre que se cree el ADM, pero el código fuente nativo de WebRTC verificará si el dispositivo ADM relevante debe iniciarse después de que se complete la negociación SDP, y si es necesario. , se iniciará el dispositivo ADM correspondiente. , el inicio de los dispositivos de adquisición y reproducción son completamente independientes, pero el proceso es similar. Los códigos de activación relevantes son los siguientes, y puede leerlos de arriba a abajo.

El siguiente es el código fuente de activación para el inicio del dispositivo de adquisición (hay otras entradas de activación en los primeros pasos, pero la última es la misma, aquí solo se muestra el proceso principal):

//cricket::VoiceChannelvoid VoiceChannel::UpdateMediaSendRecvState_w() {  //***************    bool send = IsReadyToSendMedia_w();  media_channel()->SetSend(send);  }
// cricket::WebRtcVoiceMediaChannelvoid 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::AudioStatevoid 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.";    }  }}

Se puede ver en el código fuente de activación del inicio del dispositivo de adquisición anterior que si es necesario enviar audio, sin importar si el dispositivo de adquisición anterior está activado o no, después de que se complete la negociación SDP, el dispositivo de adquisición se activará. Si queremos controlar el tiempo de inicio del dispositivo de adquisición en manos del negocio de capa superior, solo necesitamos comentar las líneas de código que inician el dispositivo en el método AddSendingStream anterior y luego iniciar el dispositivo de adquisición a través de ADM cuando necesario.

El siguiente es el código fuente de activación del inicio del dispositivo de reproducción (hay otras entradas de activación en los primeros pasos, pero la última es la misma, aquí solo se muestra el proceso central):

//cricket::VoiceChannelvoid VoiceChannel::UpdateMediaSendRecvState_w() {  //***************    bool recv = IsReadyToReceiveMedia_w();  media_channel()->SetPlayout(recv);  }
// cricket::WebRtcVoiceMediaChannelvoid WebRtcVoiceMediaChannel::SetPlayout(bool playout) { //***************    return ChangePlayout(desired_playout_);}
// cricket::WebRtcVoiceMediaChannelvoid 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::AudioReceiveStreamvoid AudioReceiveStream::Start() {   //***************    audio_state()->AddReceivingStream(this);}
//webrtc::internal::AudioStatevoid 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.";    }  }}

Se puede ver en el código fuente del activador del inicio del dispositivo de reproducción anterior que si es necesario reproducir audio , independientemente de si se inició el dispositivo de reproducción anterior, el dispositivo de reproducción se iniciará después de la negociación SDP. Si queremos controlar el tiempo de inicio del dispositivo de reproducción en manos del negocio de capa superior, solo necesitamos comentar las pocas líneas de código para iniciar el dispositivo en el método AddReceivingStream anterior y luego iniciar el dispositivo de reproducción a través de ADM. cuando sea necesario.

proceso de inicio 

Cuando es necesario iniciar el dispositivo ADM, se llama primero a InitXXX de ADM, seguido de StartXXX de ADM. Por supuesto, se llama a la implementación correspondiente de la plataforma específica a través de las capas de arquitectura anteriores. El proceso detallado es el siguiente :

 Sobre la parada del equipo 

Conociendo el inicio del dispositivo ADM, luego la acción de parada correspondiente, no hace falta decirlo. Si observa el código fuente, encontrará que las acciones y los procesos de detención están básicamente en correspondencia uno a uno con el inicio.

3. Flujo de datos de audio ADM

transmisión de datos de audio 

La figura anterior es el proceso central de la transmisión de datos de audio, principalmente la llamada de funciones centrales y el cambio de subprocesos. Los datos PCM se recopilan desde el dispositivo de hardware, y una encapsulación de datos simple en el hilo de recopilación pronto ingresará al módulo APM para el procesamiento 3A correspondiente.Desde el punto de vista del proceso, el módulo APM está muy cerca de los datos PCM originales, que tiene un gran efecto en el procesamiento de APM, es muy útil y los estudiantes interesados ​​pueden profundizar en el conocimiento relacionado con APM. Después de eso, los datos se encapsularán en una Tarea y se enviarán a un subproceso llamado rtp_send_controller. En este punto, el trabajo del subproceso de recopilación se completa y el subproceso de recopilación también puede comenzar la siguiente ronda de lectura de datos tan pronto como sea posible. que puede minimizar la influencia en la adquisición, lea nuevos datos PCM tan pronto como sea posible para evitar la pérdida de datos PCM o retrasos innecesarios.

Luego, los datos llegan al subproceso rtp_send_controller. El subproceso rtp_send_controller tiene tres funciones principales aquí, una es realizar el control de congestión para el envío rtp, la otra es codificar datos PCM y la tercera es empaquetar los datos codificados en RtpPacketToSend (RtpPacket) formato. Los datos finales de RtpPacket se enviarán a una cola llamada RoundRobinPacketQueue, hasta el momento se completó el trabajo del subproceso rtp_send_controller.

Los siguientes datos de RtpPacket se procesarán en SendControllerThread. SendControllerThread se utiliza principalmente para enviar el estado y el control de congestión de ventanas. Finalmente, los datos se envían al subproceso de red (Network Thread), uno de los tres subprocesos principales de Webrtc, en forma de un mensaje (tipo: MSG_SEND_RTP_PACKET), y luego enviarlo a la red. En este punto finaliza todo el proceso de envío.

 Recepción y reproducción de datos 

La figura anterior es el proceso central de recepción y reproducción de datos de audio. El subproceso de red es responsable de recibir datos RTP de la red y luego descomprimirlos y distribuirlos de forma asincrónica al subproceso de trabajo. Si se reciben múltiples canales de audio, hay múltiples ChannelReceives, cada uno con el mismo flujo de procesamiento, y los datos de audio finales sin decodificar se almacenan en el paquete_búfer_ del módulo NetEq. Al mismo tiempo, el subproceso del dispositivo de reproducción obtiene continuamente datos de audio (10 ms de duración) de todos los ChannelReceives de audio actuales, y luego activa NetEq para solicitar al decodificador la decodificación de audio. Para la decodificación de audio, WebRTC proporciona una interfaz unificada, y el decodificador específico solo necesita implementar la interfaz correspondiente, como el decodificador de audio predeterminado opus de WebRTC. Después de atravesar y decodificar todos los datos en ChannelReceive, el audio se mezcla a través de AudioMixer y luego se entrega al módulo APM para su procesamiento y finalmente se reproduce en el dispositivo. 

Supongo que te gusta

Origin blog.csdn.net/m0_60259116/article/details/124451612
Recomendado
Clasificación