Windows下Core_Audio_APIs的使用简介

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

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

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

图片 1.png

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

一、设备列表管理安全

二、设备初始化多线程

三、设备功能管理架构

四、数据交互函数

五、音量管理ui

六、设备终端监听spa

接下来为你们介绍相关功能的实现:

一、设备列表管理

音频设备的管理,由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、数据交互

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

麦克风采集:

图片 2.png

扬声器播放:
图片 3.png

扬声器采集:
图片 4.png

在和设备进行数据交互时,咱们须要根据数据获取模式,获取对应的服务对象来获取设备数据。其中采集部分使用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监听:

IAudioSessionControl* ptrSessionControl;

ptrClient->GetService(__uuidof(IAudioSessionControl), (void**)&ptrSessionControl);

IAudioSessionEvents* notify;

ptrSessionControl->RegisterAudioSessionNotification(notify);

该回调监听,能监听该设备的链接工做状态,名称变动等。

一些注意事项:

一、线程优先级

在实际的工程开发过程当中,咱们须要对音频线程的工做线程进行处理。一般经过调用系统模块Avrt.dll,动态调用其下的函数,将调用线程与指定任务(Pro Audio)相关联。上代码:

函数绑定:

avrt_module_ = LoadLibrary(TEXT("Avrt.dll"));

if (avrt_module_)

{

_PAvRevertMmThreadCharacteristics = (PAvRevertMmThreadCharacteristics)GetProcAddress(avrt_module_, "AvRevertMmThreadCharacteristics");

_PAvSetMmThreadCharacteristicsA = (PAvSetMmThreadCharacteristicsA)GetProcAddress(avrt_module_, "AvSetMmThreadCharacteristicsA");

_PAvSetMmThreadPriority = (PAvSetMmThreadPriority)GetProcAddress(avrt_module_, "AvSetMmThreadPriority");

}

在实际的数据处理线程关联:

hMmTask_ = _PAvSetMmThreadCharacteristicsA("Pro Audio", &taskIndex);

if (hMmTask_)

{

_PAvSetMmThreadPriority(hMmTask_, AVRT_PRIORITY_CRITICAL);

}

经过任务绑定,能有效的提高音频数据处理线程的可靠性。

二、工做线程

设备的相关初始化和释放操做,须要在统一的线程处理,部分系统com对象在释放时须要在建立线程释放,否则可能致使释放崩溃。而一些音量选择、监听等的处理能够在用户线程处理,但须要作好多线程安全。

三、设备格式选择

在设备的采样率、声道等格式选择时,若是须要使用自定义的格式,可能出现格式匹配失败或者选择匹配的格式后设备初始化失败的场景。一般此类场景下直接使用默认格式启动。

四、数据处理异常

在数据处理线程处理音频数据时,一般会出现事件响应超时、设备对象异常等状况。一般的处理方法是,先退出数据线程并结束设备,而后检查当前设备是否正常功能,而后从新启动当前设备或选用默认设备。

了解网易云信音视频通话>>>

了解网易云信,来自网易核心架构的通讯与视频云服务>>

更多技术干货,欢迎关注vx公众号“网易智慧企业技术+”。系列课程提早看,精品礼物免费得,还可直接对话CTO。

听网易CTO讲述前沿观察,看最有价值技术干货,学网易最新实践经验。网易智慧企业技术+,陪你从思考者成长为技术专家。

相关文章
相关标签/搜索