在前面介绍了ADM(Audio Device Module),它用于抽象音频设备管理和音频数据采集/播放接口。windows的实现是AudioDeviceWinowCode
,它封装了Core Audio APIs
实现了对音频设备的操作。
Core Audio APIs
windows提供了多种音频操作API,比如最常用的是 waveXxx
系列API,DirectSound
等。而Core Audio APIs
是这些API的基础,这些API使用Core Audio APIs
提供了更通用的功能。
如下图是Core Audio APIs的架构图:
Core Audio APIs
是一些高阶API(例如MME,DirectSound等)的基础。- 箭头的方向表示了音频数据的流向。
- 它有两种工作模式,共享模式和独占模式。共享模式就是大家(多个应用程序)同时播放声音(声音被混音),独占模式就是只能有一个程序播放,我一播,就没其他程序的声音了。
- 共享模式下,会有一个Audio Service进行协调各应用程序间的音频数据处理。这个很容易理解,多路声音,总该需要一个大管家来协调使用设备。
- 独占模式下,音频数据就直接到内核的驱动了。
Core Audio APIs
特点是音频处理更高效,延时更低。对webrtc 这种RTC系统来说,正是需要其低延时的保证。
它四类子API
从上图中可以看到Core Audio APIs是一系列API的集合,它包括四类子API。
- MMDevice API(用于检索播放采集设备)
用于应用程序检索音频终端设备,枚举出所有可使用的音频设备属性及确定其功能,并为这些设备创建驱动程序实例,是最基本的Core Audio API,服务于其它3个APIs。
- WASAPI(控制播放和采集流)
应用程序可以通过它管理程序和音频终端设备之间音频数据的流。比如采集,回放音频。
- DeviceTopology API(webrtc中没用到)
应用程序可以遍历音频适配器设备和音频终端设备的内部拓扑,并单步执行将设备链接到另一台设备的连接。通过 DeviceTopology API 中的接口和方法,客户端程序可直接沿着音频适配器 (audio adapters) 的硬件设备里的数据通道进入布局特征(例如,沿着音频终端设备的数据路径上进行音量控制) 。
- EndpointVolume API(控制音量)
应用程序可以控制和监视音频终端设备的音量。
它们都以COM组件的方式提供,应用程序需要创建对应COM组件的实例,获取接口对象,再使用它们提供的方法。
AudioDeviceWindowCore
在webrtc中使用Core Audio APIs以下四个功能:
- 检索音频回放设备。
- 检索音频采集设备。
- 使用指定的音频设备回放声音。
- 使用指定的音频设备采集声音。
- 音频回放。
- 音频采集。
类图如下:
它直接管理Core Audio APIs的COM对象
以**I**
开头的都是对象接口类:
IMMDevice
代表一个音频设备。IMMDeviceCollection
音频设备集。IMMDeviceEnumerator
用于枚举音频设备。IMMEndpoint
代表一个音频终端设备。
功能实现
检索音频设备
如下图,系统中一般都会有扬声器和麦克风,在声音设置中可以看到它们。
在AudioDeviceWindowCore::Init
方法中实现检索回放和采集设备,需要使用的接口对象是IMMDeviceEnumerator
,检索出来的结果保存在 IMMDeviceCollection
对象中。
音频设备有名字,音频参数(如:声道数,采样率等)等属性,这些都会一并获取到。
IMMDeviceCollection* pCollection = NULL;
hr = _ptrEnumerator->EnumAudioEndpoints(
dataFlow, // data-flow direction (input parameter)
DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED | DEVICE_STATE_UNPLUGGED,
&pCollection);
hr = pCollection->GetCount(&count);
for (ULONG i = 0; i < count; i++) {
//遍历每个设备,获取对应的属性
}
调用EnumAudioEndpoints
方法检索指定状态的设备,通过GetCount
获取数量,再遍历设备获取属性。
播放声音
指定回放设备
首先要指定要使用的回放设备,通过序号指定,在IMMDeviceCollection
中检索,通过index获取到IMMDevice
对象,它就代表了一个音频设备。
回放声音需要使用WASAPI
模块的IAudioClient
接口,它通过IMMDevice
获取
hr = _ptrDeviceOut->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL,
(void**)&_ptrClientOut);
根据设备支持的音频参数,确定一个输出格式。
音频,有采样率,声道,位率这些参数,不同的值决定了声音的质量及数据大小。WASAPI
中用这个结构体来描述
在回放声音时,要指定这些参数,就是告诉WASAPI
怎么去播放声音,但是首先要知道的是,音频设备支持怎样的播放参数。
hr = _ptrClientOut->GetMixFormat(&pWfxOut);
获取到的信息如下:
[017:755][95740] (audio_device_core_win.cc:1851): Audio Engine’s current rendering mix format:
[017:755][95740] (audio_device_core_win.cc:1853): wFormatTag : 0xfffe (65534)
[017:755][95740] (audio_device_core_win.cc:1857): nChannels : 2
[017:755][95740] (audio_device_core_win.cc:1859): nSamplesPerSec : 48000
[017:755][95740] (audio_device_core_win.cc:1861): nAvgBytesPerSec: 384000
[017:755][95740] (audio_device_core_win.cc:1863): nBlockAlign : 8
[017:755][95740] (audio_device_core_win.cc:1865): wBitsPerSample : 32
[017:755][95740] (audio_device_core_win.cc:1866): cbSize : 22
在webrtc中以采用率及声道数为标准现找一个与需求最贴合的参数,如下信息:
[017:802][95740] >>>>
[017:802][95740] (audio_device_core_win.cc:1927): VoE selected this rendering format:
[017:802][95740] (audio_device_core_win.cc:1928): wFormatTag : 0x1 (1)
[017:802][95740] (audio_device_core_win.cc:1931): nChannels : 2
[017:802][95740] (audio_device_core_win.cc:1932): nSamplesPerSec : 48000
[017:802][95740] (audio_device_core_win.cc:1933): nAvgBytesPerSec : 192000
[017:802][95740] (audio_device_core_win.cc:1934): nBlockAlign : 4
[017:802][95740] (audio_device_core_win.cc:1935): wBitsPerSample : 16
[017:802][95740] (audio_device_core_win.cc:1936): cbSize : 0
[017:802][95740] (audio_device_core_win.cc:1937): Additional settings:
[017:802][95740] (audio_device_core_win.cc:1938): _playAudioFrameSize: 4
[017:802][95740] (audio_device_core_win.cc:1939): _playBlockSize : 480
[017:802][95740] (audio_device_core_win.cc:1940): _playChannels : 2
确定了这些参数,就可以确定喂入设备的音频数据量大小。
获取流输出控制接口IAudioRenderClient
通过IAudioClient
获取IAudioRenderClient
,它就是控制音频流的接口。
hr = _ptrClientOut->GetService(__uuidof(IAudioRenderClient),
(void**)&_ptrRenderClient);
相关代码在AudioDeviceWindowsCore::InitPlayout
方法中。
播放
在webrtc使用的Core Audio API的共享模式,在共享模式下将会有一个Audio Service(在上面的图中可以看出来),应用程序将通过Enpoint Buffer
与Service交互。
播放声音,就是往这个buffer中写入音频数据,应用程序写入数据,Audio Service读取数据。
一端写,一端读,就需要判断buffer的空间,所以需要程如下几步:
- 先通过
IAudioClient
的GetBufferSize
接口获取buffer大小。 - 再通过
IAudioClient
的GetCurrentPadding
接口,获取buffer待Audio Service的处理的数据。 - 计算可用空间:buffer size - padding data size 就是buffer中可用的空间。
- 通过
IAudioRenderClient
的GetBuffer
接口获取buffer的地址。 - 往buffer中写数据。
完整的流程可以看看AudioDeviceWindowsCore::DoRenderThread()
方法。
需要注意一点,这里的buffer size不是以字节为单位,而是以audio frame为单位,通过API获取的是buffer可存放的audio frame数,及可用的frame空间。
audio frame的大小由采样率和采样时长决定,在webrtc中以10ms作为采样时长,那么48000HZ的采样率,一个audio frame的大小就是480采样点(换算成字节数:每个采样点2个字节,10ms的数据960个字节)。
播放线程
音频数据是不停的往Audio Service的buffer中写入,webrtc通过一个线程实现取应用层音频数据到写入buffer流程,如下流程图:
播放线程不会停,会持续不断的取数据,写入Audio Service Buffer,线程对应的方法为 AudioDeviceWindowsCore::DoRenderThread()
。