如何在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_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指针是关键,通过学习,我总结了两种方法:
- 外部传入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();}}
- 获取内部的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中切换音频输出设备相关推荐
- mac声音输出设备路径_如何在Mac上切换声音输出设备
mac声音输出设备路径 If you're not hearing system sound from a certain device connected to your Mac-such as a ...
- 如何在cmd中切换python版本总结
如何在cmd中切换Python版本总结 前言 尝试一:更改系统变量 尝试二.更改解释器名称 前言 由于学习nao机器人编程参加比赛,因此安装了python2.7版本.而之前安装的都为python3.9 ...
- android音频系统(7):通话过程中的音频输出设备切换
前言:由于通话比较特殊,Android对于通话过程中音频输出设备的切换做了特殊处理,它在上层也是通过切换音频播放状态来完成切换操作的,android用CallAudioState来封装通话过程中的音频 ...
- excelexportentity中设置null不显示的方法_如何在 Creator3D 中切换模型贴图,超级简单!...
效果预览 前两天有伙伴在 QQ 上询问,如何在 Creator 3D 中切换模型贴图.Shawn 之前也没尝试过,不过根据之前 Cocos Creator 的经验以及这几天对 Creator 3D 的 ...
- 如何在 Creator3D 中切换模型贴图,超级简单
1 效果预览 前两天有伙伴在 QQ 上询问,如何在 Creator 3D 中切换模型贴图.Shawn 之前也没尝试过,不过根据之前 Cocos Creator 的经验以及这几天对 Creator 3D ...
- 如何在 Creator3D 中切换模型贴图,超级简单!
效果预览 前两天有伙伴在 QQ 上询问,如何在 Creator 3D 中切换模型贴图.Shawn 之前也没尝试过,不过根据之前 Cocos Creator 的经验以及这几天对 Creator 3D 的 ...
- git 怎么切换分支命令_如何在Git中切换分支
本指南向你展示了如何在Git项目中切换分支. 前提条件Git项目访问终端窗口/Linux系统命令行 签出命令以切换分支 访问命令行并使用checkout命令签出要使用的分支:git checkout ...
- 【Python】Windows如何在cmd中切换python版本
相信很多小伙伴都会有像我一样经历,在windows中装了很多python版本,那么如果我们正式使用的时候应该如何切换呢? [方法一]从环境变量中切换python 第一步: 打开环境变量 第二步:打开系 ...
- 如何在psql中切换数据库?
在MySQL中,我使用use database_name; 什么是psql等效项? #1楼 与psql连接时,可以选择数据库. 从脚本中使用它很方便: sudo -u postgres psql -c ...
最新文章
- Vue - 表单
- 警告!别再使用 TIMESTAMP 作为日期字段~
- Java异常有多慢?
- db2表结构导出导入,数据库备份
- [原+转]CSS hack 小技巧 让你的CSS 兼容ff ie6.0 ie7.0
- 云计算,巨头们的背水一战
- RocketMq 消费消息的两种方式 pull 和 push
- 图像局部特征(二)--Harris角点检测子
- 月薪多少最幸福,离你有多远?
- UOJ #206. 【APIO2016】Gap
- 特征选择算法之Relief算法python实现
- 单机 弱联网手游 防破解 金币修改 简单措施
- Android 百度图像识别(详细步骤+源码)
- 科学计算机使用方法,[转载]科学计算器的使用方法
- java高效快速读取CSV文件
- SEP(标准必要专利)
- 一款高仿微信的app供大家参考
- java开发微信公众号入门指引,jsp(java)开发微信公众平台入门
- @huangcheng: Fedora 9 GDM开启XDMCP
- 开源项目ruoyi-springboot-vue源码分析之LogAspect日志打印
热门文章
- oracle表数据如何导出成dbf,怎么将EXCEL导成DBF?《dbf导出excel数据》
- java后台批量下载文件并压缩成zip下载
- Windows10搭建ASP服务器
- asp编程有用的例子
- VB“Automation 错误” 或 “无法定位程序输入点 DoOpenPipeStream 于动态链接库ScrRun.dll上”...
- bibi黑马MySQL学习笔记之约束
- camunda如何清理或归档历史数据
- Cocos Creator开发游戏消灭星星——星星生成
- windows版本tcping参数详解
- tcping扫描所有端口_ping TCP端口的实用小工具tcping