WebRTC手记之本地音频采集

本博客转载于:http://www.cnblogs.com/fangkm/p/4374668.html


上一篇博文介绍了本地视频采集,这一篇就介绍下音频采集流程,也是先介绍WebRTC原生的音频采集,再介绍Chromium源码对它的定制。

1. WebRTC原生音频采集

先介绍一下WebRTC中与音频采集貌似相关的接口概念:

结构上看起来是不是和视频Track的结构类似?不过前面提过,如果你以对称的思维,在此结构中找出与视频track相似的采集源和输出源,那就肯定无功而返了,LocalAudioSource对AudioSourceInterface的实现就是一个空实现,没有了音频源,那音频处理接口AudioProcessorInterface和输出接口AudioRenderer都成了无米之炊了。这些接口先摆在这,可能类似于AudioCapturer的框架正在实现的途中,也可能这些接口有别的用处,比如远程音频流的抽象等,这里就暂且搁置,先记下有这回事吧。这里只谈WebRTC本地音频的采集处理。前面介绍音视频接口的时候也提到的,本地音频的采集由AudioDeviceModule接口统一封装:

AudioDeviceModule是个大而全的接口,恨不得将所有音频相关的接口都封装在里面(实际也差不多了),具体包括:枚举音频采集设备(Record)和播放设备(Playout)、设置当前的采集设备/播放设备、开始/停止音频的采集/播放、设置音频增益控制开关(AGC)等。AudioTransport是个关键的对外接口,负责音频数据的传入(调用NeedMorePlayData方法,供Playout使用)和输出(调用RecordedDataIsAvailable方法,数据由Record采集操作产生)。

AudioDeviceModuleImpl实现了AudioDeviceModule接口,创建的时候调用CreatePlatformSpecificObjects方法创建平台相关的AudioDeviceGeneric接口实现。该接口抽象了音频的采集和播放逻辑,在Windows平台下有两种实现方案:

  •   AudioDeviceWindowsWave实现的是传统的Windows Wave APIs方案。
  •  AudioDeviceWindowsCore实现的是Vista之后才支持的Windows Core Audio APIs方案。

此外,AudioDeviceModuleImpl还维护了一个AudioDeviceBuffer对象来管理音频数据的缓冲区,由它直接与对外接口AudioTransport交互。比如:

  •  当AudioDeviceWindowsWave或者AudioDeviceWindowsCore需要播放音频数据的时候,会调用AudioDeviceBuffer的RequestPlayoutData方法请求播放数据,然后通过GetPlayoutData方法来获取刚请求到的数据。AudioDeviceBuffer的RequestPlayoutData就是调用AudioTransport接口的NeedMorePlayData方法来请求待播放的音频流数据。
  •  当AudioDeviceWindowsWave或者AudioDeviceWindowsCore采集到音频数据后,会调用AudioDeviceBuffer的SetRecordedBuffer方法将采集到的音频数据传递进去,然后调用DeliverRecordedData方法来派发出去,该派发方法就是通过调用AudioTransport接口的RecordedDataIsAvailable来实现。

总之,音频采集模块处处都透露出大而全的结构设计。如果可以,真的应该细化一下概念设计,比如将音频采集和音频播放逻辑分离、音频输入和输出的接口拆分等等,那样才能谈得上结构设计。

2. Chromium对WebRTC的音频采集适配

根据WebRTC的本地音频接口设计,Chromium提供了一个WebRtcAudioDeviceImpl类来实现AudioDeviceModule接口,该类对象由PeerConnectionDependencyFactory负责创建和维护,结构如下:

如图所示,WebRtcAudioDeviceImpl摒弃了原生的AudioDeviceModuleImpl实现中大而全的设计,而是将音频采集和音频渲染逻辑分开,分别对应于WebRtcAudioCapturer和WebRtcAudioRenderer。WebRtcAudioRenderer通过WebRtcAudioRendererSource接口的RenderData方法向WebRtcAudioDeviceImpl请求音频流数据来渲染,WebRtcAudioDeviceImpl将该请求转发给前面提到的对外交互接口AudioTransport。WebRtcAudioCapturer封装音频采集逻辑,它将采集到的数据通过WebRtcLocalAudioTrack对象所持有的PeerConnectionAudioSink接口派发出去,WebRtcAudioDeviceImpl正是实现了该接口来接收音频采集数据,然后也是通过AudioTransport接口往外传递。至于WebRtcAudioCapturer对象的持有者MediaStreamAudioSource和WebMediaStreamTrack,这里暂时有个概念就行,它们是Chromium对HTML5媒体流的实现接口。接下来仔细分析一下WebRtcAudioCapturer和WebRtcAudioRenderer两个关键类,毋庸置疑,它们都涉及到了特定平台实现,而且在Chromium中还跨越了Render和Browser进程。和介绍Chromium视频采集的模式一样,由于不是本文重点,这里只列出结构图,不打算详解,如果你有开发上的需要,可以照着该结构图细看源码。

这是WebRtcAudioCapturer采集音频数据的结构,牵涉到跨进程通信,结构还是非常复杂的。WebRtcAudioRenderer的结构就不准备介绍了,因为Chromium的这块设计非常具备对称性,基本上图中类命名中的Input改成Output就差不多是WebRtcAudioRenderer的架构了。


### 如何使用 WebRTC 播放本地音频文件 为了实现在WebRTC中播放本地音频文件,一种有效的方法是将音频文件作为输入源加入到WebRTC音频处理流程中。具体来说,可以采用两种不同的策略: #### 方法一:模拟一路接收流 此方法涉及把本地音乐当作来自远端的一路流来处理。这意味着不需要修改WebRTC的核心代码,而是利用`WebRtcVoiceMediaChannel`对象中的`AddRecvStream`函数添加新的媒体流[^4]。 当准备发送音频包时,需先将其封装成RTP格式的数据包,再通过`OnPacketReceived`回调机制传递给WebRTC引擎进行解码和回放。这种方法的优点在于它保持了原有架构不变,易于集成现有系统之中。 ```javascript // 创建自定义 AudioSource 对象用于读取本地音频文件并转换为 RTP 数据包 class CustomAudioSource { constructor(fileUrl) { this.fileReader = new FileReader(); fetch(fileUrl).then(response => response.arrayBuffer()).then(buffer => { this.fileReader.readAsArrayBuffer(new Blob([buffer])); }); } onPacketReady(packet) { // 将 packet 发送给 WebRTC 引擎 webRTCEngine.onPacketReceived(packet); } } ``` #### 方法二:播放混音 另一种更直接的方式是对现有的音频混合器(`webrtc::AudioMixer`)做文章——即向其注册一个新的音频源。这样做的好处是可以直接操作原始PCM数据流,并且能够更加灵活地控制何时开启/关闭该功能。 一旦完成上述设置,在适当时候调用`AddSource`接口传入待播发的声音片段;而不再需要这些声音时,则应记得及时清理资源,防止内存泄漏等问题发生。 ```cpp void AddLocalAudioFileToMix(const std::string& filePath) { auto audio_source = CreateCustomAudioSource(filePath); // 自定义创建 Audio Source 函数 webrtc::AudioState* audio_state = GetGlobalAudioState(); // 获取全局 Audio State 单例实例 if (audio_state && audio_state->mixer()) { audio_state->mixer()->AddSource(audio_source.get()); } else { LOG(LS_ERROR) << "Failed to get valid Audio Mixer"; } } void RemoveLocalAudioFileFromMix() { webrtc::AudioState* audio_state = GetGlobalAudioState(); if (audio_state && audio_state->mixer()) { audio_state->mixer()->RemoveSource(custom_audio_source_.get()); } } ``` 这两种方式各有优劣,开发者可以根据实际需求选择最适合项目的技术路线。无论采取哪种途径,都需要注意确保音频同步以及可能存在的延迟问题得到妥善解决。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值