Windows下Core Audio APIs的使用简介

Windows Vista 之后系统,音频系统相比之前的系统有很大的变化,产生了一套新的底层 API 即 Core Audio APIs 。该低层 API 为高层 API( 如 Media Foundation( 将要取代DirectShow 等高层 API) 等 ) 提供服务。该系统API具有低延迟、高可靠性、安全性等特点。

本文主要从实时音视频场景中,简单介绍该API的使用。

Core Audio APIs 的组成:MMDeviceEndpointVolumeWASAPI等。对于实时音视频系统,主要用到的是MMDeviceEndpointVolume这两套API。其在系统中的位置如下图:

我对实时音视频中音频设备的使用简单的分为:

1、设备列表管理

2、设备初始化

3、设备功能管理

4、数据交互

5、音量管理

6、设备终端监听

接下来为大家介绍相关功能的实现:

1、设备列表管理

音频设备的管理,由MMDevice API来实现。

首先我们要创建一个IMMDeviceEnumerator对象来开始相关功能的调用。

IMMDeviceEnumerator* ptrEnumerator;
CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL,                    __uuidof(IMMDeviceEnumerator),                    reinterpret_cast<void**>(&ptrEnumerator));

并通过IMMDeviceEnumerator可以实现:获取系统默认设备GetDefaultAudioEndpoint、获取设备集合IMMDeviceCollection、获取指定设备GetDevice、注册设备监听IMMNotificationClient(监听设备插拔及状态变更)。

通过这些方法,我们能得到系统默认设备、遍历设备列表、打开指定设备并监听设备变更。这样就实现了实时音视频中的设备管理相关的功能。

2、设备初始化

音频设备的启动是整个音频模块的可靠性的重要节点。根据设备类型和设备数据捕获方式,我们可分为3类设备:麦克风采集、扬声器播放、扬声器采集。

首先我们需要一个IMMDevice对象,可以在设备管理的相关功能中获取。

IMMDevice* pDevice;
//GetDefault
ptrEnumerator->GetDefaultAudioEndpoint((EDataFlow)dir,                                              (ERole)role/* eCommunications */,                                              &pDevice);
//Get by path
ptrEnumerator->GetDevice(device_path, &pDevice);
//GetIndex
pCollection->Item(index, &pDevice);

再通过IMMDevice得到IAudioClient,设备的格式设置及初始化通过IAudioClient对象实现。一般都以共享模式打开,其中麦克风采集及扬声器播使用事件驱动方式处理数据,而扬声器采集以回环的方式驱动处理数据。简单示例如下:

//mic capturer
ptrClient->Initialize(
AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK |  
AUDCLNT_STREAMFLAGS_NOPERSIST, 
0, 
0,  
 (WAVEFORMATEX*)&Wfx,
NULL); 
//playout render
ptrClient->Initialize(
AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK, 
0, 
0,  
 (WAVEFORMATEX*)&Wfx,
NULL); 
//playout capturer
ptrClient->Initialize(
AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_LOOPBACK, 
0, 
0,  
 (WAVEFORMATEX*)&Wfx,
NULL);

其中Wfx是设备格式参数,一般为了保证设备的可用性,使用默认格式(通过IAudioClient::GetMixFormat获取),如果需要使用自定义格式,可以通过IAudioClient::IsFormatSupported方法去遍历尝试设备支持格式。

3、设备功能管理

针对麦克风设备,我们通常需要对其进行数据处理。部分硬件设备和系统支持自带的降噪、增益、消回音等功能。但是一般windows系统下设备比较繁杂不可控,大都使用软件算法处理。如果我们需要检测设备是否使用了自带的处理功能及相关参数,需要使用Topology模块的功能。

IDeviceTopology* pTopo;
pDevice->Activate(__uuidof(IDeviceTopology), CLSCTX_INPROC_SERVER, 0,&pTopo);

通过IDeviceTopology,我们能够遍历IConnector对象,获得IAudioAutoGainControlIAudioVolumeLevel等能力对象,并处理相关能力。

注意:IConnector可能是循环嵌套,在遍历IConnectorIPart时需要判别成员对象IPart的类型。

4、数据交互

在设备初始化的时候,我们就根据不同的设备选择了不同的模式进行了启动。不同的设备在各自的模式下,数据驱动也各有不同:

麦克风采集:

扬声器播放:

扬声器采集:


在和设备进行数据交互时,我们需要根据数据获取模式,获取对应的服务对象来获取设备数据。其中采集部分使用IAudioCaptureClient服务用于获取设备数据,播放使用IAudioRenderClient服务获取设备数据传入指针。示例如下:

//capturer
IAudioCaptureClient* ptrCaptureClient;//audioin or audioout
ptrClient->GetService(__uuidof(IAudioCaptureClient), (void**)&ptrCaptureClient);
{//work thread 
   	//Wait Event
	ptrCaptureClient->GetBuffer(
	&pData,            // packet which is ready to be read by used
	&framesAvailable,  // #frames in the captured packet (can be zero)
	&flags,            // support flags (check)
	&recPos,    // device position of first audio frame in data packet
	&recTime);  // value of performance counter at the time of recording
	//pData processing
	ptrCaptureClient->ReleaseBuffer(framesAvailable);
} 
//render
IAudioRenderClient* ptrRenderClient;//audioout
ptrClient->GetService(__uuidof(IAudioRenderClient), (void**)&ptrRenderClient);
{//work thread 
        	BYTE* pData;//form buffer  
        	UINT32 bufferLength = 0;
        	ptrClient->GetBufferSize(&bufferLength);
        	UINT32 playBlockSize = nSamplesPerSec / 100;
        	//Wait Event
        	UINT32 padding = 0;
        	ptrClient->GetCurrentPadding(&padding);
        	if (bufferLength - padding > playBlockSize)
        	{
            		ptrRenderClient->GetBuffer(playBlockSize, &pData);
			//request and getdata
			ptrCaptureClient->ReleaseBuffer(playBlockSize, 0);
		}
}

在实际的数据交互中,需要另开单独线程处理GetBufferReleaseBuffer。其中麦克风采集及扬声器播放时,都是通过设备事件驱动,可以在设备初始化完成后设置响应的事件句柄(IAudioClient::SetEventHandle)。

在整个音视频系统中,设备数据线程还需要统计数据处理时长、采集播放缓存大小等,用户监听检查设备状态及aec延迟计算。

5、音量管理

一般音量管理只在设备选定后处理当前设备的音量,所以一般使用IAudioEndpointVolume,该对象通过设备对象IMMDevice获取:

IAudioEndpointVolume* pVolume;
pDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL,                           reinterpret_cast<void**>(&pVolume));

得到IAudioEndpointVolume对象后,我们能处理当前设备的音量控制:

pVolume->GetMasterVolumeLevelScalar(&fLevel);
pVolume->SetMasterVolumeLevelScalar(fLevel, NULL);

静音控制:

BOOL mute;
pVolume->GetMute(&mute);
pVolume->SetMute(mute, NULL);

以及注册IAudioEndpointVolumeCallback监听音量状态:

IAudioEndpointVolumeCallback* cbSessionVolume;//need to do
pVolume->RegisterControlChangeNotify(cbSessionVolume);

6、设备终端监听

在运行过程中除了设备的插拔等操作,还可能有一些属性变更等,一般用IAudioSessionEvents监听:

展开阅读全文

本文系作者在时代Java发表,未经许可,不得转载。

如有侵权,请联系nowjava@qq.com删除。

编辑于

关注时代Java

关注时代Java