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()

相关推荐
RTC实战笔记11 天前
Android 实时音视频接入教程:媒体补充增强信息(SEI)
音视频·媒体·rtc
潜创微科技12 天前
HDMI1.3 无线传输芯片方案 空旷 150 米量产级音视频方案
音视频
VidDown12 天前
VidDown 工具站:免费、本地优先的开发者工具箱
javascript·编辑器·音视频·视频编解码·视频
换个昵称都难12 天前
音频格式之WAV
音视频
qq_3692243312 天前
Windows全系通用!ntdll.dll文件丢失、报错、闪退问题的完整排查与修复教程
windows·dll·dll修复·dll丢失·dll错误
AI创界者12 天前
PilotTTS 一键整合包(Win/Mac):8G 显存畅跑,实测解锁情绪与副语言的精准控制
人工智能·macos·aigc·音视频
u1521096484912 天前
S.S.Audio PRO A2音频隔离器
嵌入式硬件·音视频·实时音视频·视频编解码·视频
VidDown12 天前
显卡处理视频技术详解:从硬解码到 NVENC,GPU 如何让视频处理起飞?
javascript·编辑器·音视频·视频编解码·视频
阿米亚波12 天前
【Windows】QEMU 启动 openEuler aarch64/arm64 架构系统 + 离线软件源
linux·windows·经验分享·笔记·架构·arm
EasyDSS12 天前
全能音视频平台/私有化音视频系统EasyDSS!直播/点播/会议/集群对讲一站式落地
音视频