webrtc音频模块(三) windows Core Audio API及声音的播放

在前面介绍了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以下四个功能:

  1. 检索音频回放设备。
  2. 检索音频采集设备。
  3. 使用指定的音频设备回放声音。
  4. 使用指定的音频设备采集声音。
  5. 音频回放。
  6. 音频采集。

类图如下:

它直接管理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:75595740 (audio_device_core_win.cc:1851): Audio Engine's current rendering mix format:

017:75595740 (audio_device_core_win.cc:1853): wFormatTag : 0xfffe (65534)

017:75595740 (audio_device_core_win.cc:1857): nChannels : 2

017:75595740 (audio_device_core_win.cc:1859): nSamplesPerSec : 48000

017:75595740 (audio_device_core_win.cc:1861): nAvgBytesPerSec: 384000

017:75595740 (audio_device_core_win.cc:1863): nBlockAlign : 8

017:75595740 (audio_device_core_win.cc:1865): wBitsPerSample : 32

017:75595740 (audio_device_core_win.cc:1866): cbSize : 22

在webrtc中以采用率及声道数为标准现找一个与需求最贴合的参数,如下信息:

017:80295740 >>>>

017:80295740 (audio_device_core_win.cc:1927): VoE selected this rendering format:

017:80295740 (audio_device_core_win.cc:1928): wFormatTag : 0x1 (1)

017:80295740 (audio_device_core_win.cc:1931): nChannels : 2

017:80295740 (audio_device_core_win.cc:1932): nSamplesPerSec : 48000

017:80295740 (audio_device_core_win.cc:1933): nAvgBytesPerSec : 192000

017:80295740 (audio_device_core_win.cc:1934): nBlockAlign : 4

017:80295740 (audio_device_core_win.cc:1935): wBitsPerSample : 16

017:80295740 (audio_device_core_win.cc:1936): cbSize : 0

017:80295740 (audio_device_core_win.cc:1937): Additional settings:

017:80295740 (audio_device_core_win.cc:1938): _playAudioFrameSize: 4

017:80295740 (audio_device_core_win.cc:1939): _playBlockSize : 480

017:80295740 (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的空间,所以需要程如下几步:

  • 先通过IAudioClientGetBufferSize接口获取buffer大小。
  • 再通过IAudioClientGetCurrentPadding接口,获取buffer待Audio Service的处理的数据。
  • 计算可用空间:buffer size - padding data size 就是buffer中可用的空间。
  • 通过IAudioRenderClientGetBuffer接口获取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()

相关推荐
王维同学31 分钟前
[原创][Windows C++]Explorer Shell 扩展、图标覆盖与 COM 服务器定位
c++·windows·注册表
ALINX技术博客1 小时前
【黑金云课堂】FPGA技术教程Vitis开发:SD卡WAV文件读取音频播放实验
fpga开发·音视频
广州灵眸科技有限公司11 小时前
xfce桌面触摸校准:基于灵眸科技EASY-EAl-Orin-Nano
数据库·windows·科技
Ctrl+Z侠16 小时前
.NET WebApi Windows / Linux Docker 全链路压测与瓶颈定位 0 到 1 教程
linux·windows·.net
王维同学17 小时前
[原创][Windows C++]LSA 认证、安全与通知包的注册表枚举
c++·windows·安全
YCOSA202517 小时前
雨晨 Windows 11 IOT 企业版 LTSC 24H2 轻装 2合1 26100.8968
windows·物联网
沐禾安信18 小时前
AVI 视频打不开?2026 四款转 MP4 工具封神
音视频·电脑录屏
汤姆yu18 小时前
CodeGeeX 4完整安装与实操使用全指南
人工智能·windows·ai·智能体·视频模型
兵叔物联19 小时前
基于FFmpeg的短视频自动剪辑工具
ffmpeg·音视频
AORUO奥偌19 小时前
从“拼凑”到“集成”:音视频系统如何重塑弱电智能化建设逻辑
音视频·公共广播系统·音视频系统·多媒体会议与ip广播系统·多媒体会议系统