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_DEVICERTC_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 channelsbool 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 channelsbool 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_; };

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

如何在webrtc中切换音频输出设备相关推荐

  1. mac声音输出设备路径_如何在Mac上切换声音输出设备

    mac声音输出设备路径 If you're not hearing system sound from a certain device connected to your Mac-such as a ...

  2. 如何在cmd中切换python版本总结

    如何在cmd中切换Python版本总结 前言 尝试一:更改系统变量 尝试二.更改解释器名称 前言 由于学习nao机器人编程参加比赛,因此安装了python2.7版本.而之前安装的都为python3.9 ...

  3. android音频系统(7):通话过程中的音频输出设备切换

    前言:由于通话比较特殊,Android对于通话过程中音频输出设备的切换做了特殊处理,它在上层也是通过切换音频播放状态来完成切换操作的,android用CallAudioState来封装通话过程中的音频 ...

  4. excelexportentity中设置null不显示的方法_如何在 Creator3D 中切换模型贴图,超级简单!...

    效果预览 前两天有伙伴在 QQ 上询问,如何在 Creator 3D 中切换模型贴图.Shawn 之前也没尝试过,不过根据之前 Cocos Creator 的经验以及这几天对 Creator 3D 的 ...

  5. 如何在 Creator3D 中切换模型贴图,超级简单

    1 效果预览 前两天有伙伴在 QQ 上询问,如何在 Creator 3D 中切换模型贴图.Shawn 之前也没尝试过,不过根据之前 Cocos Creator 的经验以及这几天对 Creator 3D ...

  6. 如何在 Creator3D 中切换模型贴图,超级简单!

    效果预览 前两天有伙伴在 QQ 上询问,如何在 Creator 3D 中切换模型贴图.Shawn 之前也没尝试过,不过根据之前 Cocos Creator 的经验以及这几天对 Creator 3D 的 ...

  7. git 怎么切换分支命令_如何在Git中切换分支

    本指南向你展示了如何在Git项目中切换分支. 前提条件Git项目访问终端窗口/Linux系统命令行 签出命令以切换分支 访问命令行并使用checkout命令签出要使用的分支:git checkout ...

  8. 【Python】Windows如何在cmd中切换python版本

    相信很多小伙伴都会有像我一样经历,在windows中装了很多python版本,那么如果我们正式使用的时候应该如何切换呢? [方法一]从环境变量中切换python 第一步: 打开环境变量 第二步:打开系 ...

  9. 如何在psql中切换数据库?

    在MySQL中,我使用use database_name; 什么是psql等效项? #1楼 与psql连接时,可以选择数据库. 从脚本中使用它很方便: sudo -u postgres psql -c ...

最新文章

  1. Vue - 表单
  2. 警告!别再使用 TIMESTAMP 作为日期字段~
  3. Java异常有多慢?
  4. db2表结构导出导入,数据库备份
  5. [原+转]CSS hack 小技巧 让你的CSS 兼容ff ie6.0 ie7.0
  6. 云计算,巨头们的背水一战
  7. RocketMq 消费消息的两种方式 pull 和 push
  8. 图像局部特征(二)--Harris角点检测子
  9. 月薪多少最幸福,离你有多远?
  10. UOJ #206. 【APIO2016】Gap
  11. 特征选择算法之Relief算法python实现
  12. 单机 弱联网手游 防破解 金币修改 简单措施
  13. Android 百度图像识别(详细步骤+源码)
  14. 科学计算机使用方法,[转载]科学计算器的使用方法
  15. java高效快速读取CSV文件
  16. SEP(标准必要专利)
  17. 一款高仿微信的app供大家参考
  18. java开发微信公众号入门指引,jsp(java)开发微信公众平台入门
  19. @huangcheng: Fedora 9 GDM开启XDMCP
  20. 开源项目ruoyi-springboot-vue源码分析之LogAspect日志打印

热门文章

  1. oracle表数据如何导出成dbf,怎么将EXCEL导成DBF?《dbf导出excel数据》
  2. java后台批量下载文件并压缩成zip下载
  3. Windows10搭建ASP服务器
  4. asp编程有用的例子
  5. VB“Automation 错误” 或 “无法定位程序输入点 DoOpenPipeStream 于动态链接库ScrRun.dll上”...
  6. bibi黑马MySQL学习笔记之约束
  7. camunda如何清理或归档历史数据
  8. Cocos Creator开发游戏消灭星星——星星生成
  9. windows版本tcping参数详解
  10. tcping扫描所有端口_ping TCP端口的实用小工具tcping