如何在webrtc中切换音频输出设备

webrtc中音频模块由虚拟基类AudioDeviceModule 管理,在调用webrtc::CreatePeerConnectionFactory创建peerconnectionFactory实例时会传入音频模块管理指针,如下

peer_connection_factory_ = webrtc::CreatePeerConnectionFactory(
	  networkThread.release() /* network_thread */, workerThread.release() /* worker_thread */,
	  signalingThread /* signaling_thread */, nullptr /* default_adm */,
      webrtc::CreateBuiltinAudioEncoderFactory(),
      webrtc::CreateBuiltinAudioDecoderFactory(),
      webrtc::CreateBuiltinVideoEncoderFactory(),
      webrtc::CreateBuiltinVideoDecoderFactory(), nullptr /* audio_mixer */,
      nullptr /* audio_processing */)

创建连接工厂实例时会外部传入默认的音频管理模块adm,默认我们传入空指针,如果传入的是空指针,在音频引擎初始化时会在工作线程内创建adm,并选择默认的音频输出和音频输入设备。

void WebRtcVoiceEngine::Init() {
  RTC_DCHECK(worker_thread_checker_.IsCurrent());
  RTC_LOG(LS_INFO) << "WebRtcVoiceEngine::Init";

  // TaskQueue expects to be created/destroyed on the same thread.
  low_priority_worker_queue_.reset(
      new rtc::TaskQueue(task_queue_factory_->CreateTaskQueue(
          "rtc-low-prio", webrtc::TaskQueueFactory::Priority::LOW)));

  // Load our audio codec lists.
  RTC_LOG(LS_VERBOSE) << "Supported send codecs in order of preference:";
  send_codecs_ = CollectCodecs(encoder_factory_->GetSupportedEncoders());
  for (const AudioCodec& codec : send_codecs_) {
    RTC_LOG(LS_VERBOSE) << ToString(codec);
  }

  RTC_LOG(LS_VERBOSE) << "Supported recv codecs in order of preference:";
  recv_codecs_ = CollectCodecs(decoder_factory_->GetSupportedDecoders());
  for (const AudioCodec& codec : recv_codecs_) {
    RTC_LOG(LS_VERBOSE) << ToString(codec);
  }

#if defined(WEBRTC_INCLUDE_INTERNAL_AUDIO_DEVICE)
  // No ADM supplied? Create a default one.
  if (!adm_) {
    adm_ = webrtc::AudioDeviceModule::Create(
        webrtc::AudioDeviceModule::kPlatformDefaultAudio, task_queue_factory_);
  }
#endif  // WEBRTC_INCLUDE_INTERNAL_AUDIO_DEVICE
  RTC_CHECK(adm());
  webrtc::adm_helpers::Init(adm());

首先音频引擎在初始化时判断外部传入的adm是否空,如果是空,则创建一个音频设备,调用webrtc::AudioDeviceModule::Create创建。音频的实际操作类为AudioDeviceModuleImpl,实现了音频的输入设置,停止,音频的渲染停止,初始化,设置等。因在在webrtc::AudioDeviceModule::Create中是实例化AudioDeviceModuleImpl类,返回基类指针。
    adm实例建立后,需要初始化操作

void Init(AudioDeviceModule* adm) {
  RTC_DCHECK(adm);

  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.";
    }
  }
}

首先进行adm初始化,依次设置扬声器设备(此处为默认的设备),初始化扬声器,判断扬声器支持的音频声道,设置声道;设置麦克风输入设备(默认),初始化麦克风,判断麦克风支持的音频声道,设置声道。
   如果需要我们外面手动设置音频输出设备,第一步我们需要获取到adm实例指针,然后通过实例中的方法SetPlayoutDevice(uint16_t index)SetPlayoutDevice(WindowsDeviceType device)设置,如何获取adm指针是关键,通过学习,我总结了两种方法:

  1. 外部传入adm实例
       webrtc中有非常严格的线程模型,对不同的模块划分到不同的线程中,有些实例的建立初始化等工作必须在指定的线程中完成,否则webrtc内部断言就会出错导致程序非正常结束。adm的接口创建和初始化步骤都在工作线程中完成,所以传入到webrtc::CreatePeerConnectionFactory之前,在工作线程中创建好adm实例,然后再将adm参数传入,因此前提是我们也需要创建工作线程作为参数传入,否则工作线程与webrtc内部工作线程不同也没用,下面的代码是我在webrtc自带历程peerconnectionclient中修改的:
  std::unique_ptr<rtc::Thread> networkThread = rtc::Thread::CreateWithSocketServer();
  rtc::Thread* signalingThread = rtc::Thread::Current();
  std::unique_ptr<rtc::Thread> workerThread = rtc::Thread::Create();
  networkThread->SetName("network_thread", nullptr);
  signalingThread->SetName("signaling_thread", nullptr);
  workerThread->SetName("worker_thread", nullptr);
  if (!networkThread->Start() || !workerThread->Start())
  {
	  int a = 0;
	  //MSC_THROW_INVALID_STATE_ERROR("thread start errored");
  }
  
 rtc::scoped_refptr<webrtc::AudioDeviceModule> adm= workerThread->Invoke<rtc::scoped_refptr<webrtc::AudioDeviceModule>>(
	  RTC_FROM_HERE, [=] { return Conductor::SetAdm(); });

    SetAdm是我定义的一个函数,用来外部生成音频管理模块adm:

rtc::scoped_refptr<webrtc::AudioDeviceModule> Conductor::SetAdm()
{
	rtc::scoped_refptr<webrtc::AudioDeviceModule> adm_ = webrtc::AudioDeviceModule::Create(
		webrtc::AudioDeviceModule::kPlatformDefaultAudio, webrtc::CreateDefaultTaskQueueFactory().get());
	if (adm_)
	{
		webrtc::adm_helpers::Init(adm_.get());
	}
	return adm_;
}

首先和webrtc::WebRtcVoiceEngine中创建一样的方法创建adm,然后调用初始化即可。可以通过adm查到本机有多少个音频输出模块,以及每个音频输出对应的索引:

 void SetAudioDeviceOut(int indx)
 {
 		char name[128];
		char guid[128]; 
		if (_adm)
		{
			int16_t numAudioOut = 0;
			numAudioOut = _adm->PlayoutDevices();
			for (int i = 0; i < numAudioOut; i++)
			{
				_adm->PlayoutDeviceName(i, name, guid);
				std::string admName(name);
				if (adm_out.find(i) == adm_out.end())
				{
					adm_out.insert(make_pair(i, admName));
				}
			}
		}
 }

然后根据对应的设备index来设置音频输出:

    void SetAudioDeviceOut(int indx)
	{
		 rtc::scoped_refptr<webrtc::AudioDeviceModule>adm= peerConnectionFactory->GetAdmPtr();
		 if (adm)
		 {
			 adm->StopPlayout();
			 adm->SetPlayoutDevice(indx);
			
			 if (adm->InitSpeaker() != 0) {
				 RTC_LOG(LS_ERROR) << "Unable to access speaker.";
			 }
			 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.";
			 }
			 adm->InitPlayout();
			 adm->StartPlayout();
		 }
	}
  1. 获取内部的adm实例
       另一种方法是通过改动webrtc源码,增加几个简单的接口,来获取内部的adm,我认为这种方法是最有效且最安全的,我的修改如下:

webrtc::PeerConnectionFactoryInterface类中添加虚函数

 virtual rtc::scoped_refptr<webrtc::AudioDeviceModule> GetAdmPtr() = 0;

并在src\pc\peer_connection_factory.h的实现类中实现这个函数

rtc::scoped_refptr<webrtc::AudioDeviceModule> PeerConnectionFactory::GetAdmPtr()
{
	 return  this->channel_manager_->media_engine()->voice().GetAdm();
}

此处的GetAdm()是我在VoiceEngineInterface基类中添加的虚函数,

 virtual rtc::scoped_refptr <webrtc::AudioDeviceModule> GetAdm() = 0;

WebRtcVoiceEngine中实现

 rtc::scoped_refptr <webrtc::AudioDeviceModule> GetAdm() { return adm_; };

后者如何设置音频输出,和方法一一样。
总结:
   方法一适用有局限性,可能会引起线程崩溃,不安全
   方法二是最有稳定的方法,不会引起其他未知的问题

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

爱钻研技术的小羊

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

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

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

打赏作者

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

抵扣说明:

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

余额充值