基于 webrtc 开发嵌入式 linux 实时音视频,其中音频 alsa 模块存在一个不小的 Bug,在较多的声卡上存在播放失败并阻塞的严重问题,问题如下: 存在大概率的声卡打开失败,即 snd_pcm_prepare 失败的概率,在 源码中 webrtc 会尝试通过 ErrorRecovery 恢复声卡至正确的状态,但是执行到 snd_pcm_recover 输出 underrun occurred 错误信息后便阻塞了。

原因在于 snd_pcm_recover 源码中会调动 snd_pcm_prepare 方法尝试恢复声卡状态,但是 snd_pcm_prepare 会首先获取 snd_pcm_t 中的线程锁 pthread_mutex_t,而 webrtc 在 AudioDeviceLinuxALSA::StartPlayout() 方法中是先启动播放线程,然后再调用 snd_pcm_prepare 准备好声卡设备,导致线程有一定概率启动速度早于父线程中对 snd_pcm_prepare 的调用,最终播放线程中在执行 snd_pcm_avail_update 失败,触发 ErrorRecovery 方法,内部有线程锁,导致线程死锁,整个 APP Blocking;

修复方式比较简单,修改 audio_device_alsa_linux.cc 文件中的 StartPlayout 方法如下:

int32_t AudioDeviceLinuxALSA::StartPlayout() {
  if (!_playIsInitialized) {
    return -1;
  }

  if (_playing) {
    return 0;
  }

  _playing = true;

  _playoutFramesLeft = 0;
  if (!_playoutBuffer)
    _playoutBuffer = new int8_t[_playoutBufferSizeIn10MS];
  if (!_playoutBuffer) {
    RTC_LOG(LS_ERROR) << "failed to alloc playout buf";
    _playing = false;
    return -1;
  }

  int errVal = LATE(snd_pcm_prepare)(_handlePlayout);
  if (errVal < 0) {
    RTC_LOG(LS_ERROR) << "playout snd_pcm_prepare failed ("
                      << LATE(snd_strerror)(errVal) << ")\n";
    // just log error
    // if snd_pcm_open fails will return -1
  }
  // 将线程的启动放到 snd_pcm_prepare 之后再启动
  // PLAYOUT
  _ptrThreadPlay.reset(new rtc::PlatformThread(
      PlayThreadFunc, this, "webrtc_audio_module_play_thread"));
  _ptrThreadPlay->Start();
  _ptrThreadPlay->SetPriority(rtc::kRealtimePriority);

  return 0;
}

StartRecording 最好也调整下调用顺序。

版权声明: 如无特别声明,本文版权归 Mr Chen 所有,转载请注明本文链接。

(采用 CC BY-NC-SA 4.0 许可协议进行授权)

本文标题:《 webrtc on linux alsa audio device blocking 》

本文链接:https://gbcpp.github.io/rtc/webrtc-on-linux-alsa-audio-device-blocking.html

本文最后一次更新为 天前,文章中的某些内容可能已过时!

目录