Windows Vista 之后零碎,音频系统相比之前的零碎有很大的变动,产生了一套新的底层 API 即 Core Audio APIs 。该低层 API 为高层 API( 如 Media Foundation( 将要取代DirectShow 等高层 API) 等 ) 提供服务。该零碎API具备低提早、高可靠性、安全性等特点。
本文次要从实时音视频场景中,简略介绍该API的应用。
Core Audio APIs 的组成:MMDevice、EndpointVolume、WASAPI等。对于实时音视频零碎,次要用到的是MMDevice及EndpointVolume这两套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对象,取得IAudioAutoGainControl、IAudioVolumeLevel等能力对象,并解决相干能力。
留神:IConnector可能是循环嵌套,在遍历IConnector的IPart时须要判断成员对象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);
}
}
在理论的数据交互中,须要另开独自线程解决GetBuffer及ReleaseBuffer。其中麦克风采集及扬声器播放时,都是通过设施事件驱动,能够在设施初始化实现后设置响应的事件句柄(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);
该回调监听,能监听该设施的连贯工作状态,名称变更等。
一些注意事项:
1、线程优先级
在理论的工程开发过程中,咱们须要对音频线程的工作线程进行解决。通常通过调用零碎模块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);
}
通过工作绑定,能无效的晋升音频数据处理线程的可靠性。
2、工作线程
设施的相干初始化和开释操作,须要在对立的线程解决,局部零碎com对象在开释时须要在创立线程开释,不然可能导致开释解体。而一些音量抉择、监听等的解决能够在用户线程解决,但须要做好多线程平安。
3、设施格局抉择
在设施的采样率、声道等格局抉择时,如果须要应用自定义的格局,可能呈现格局匹配失败或者抉择匹配的格局后设施初始化失败的场景。通常此类场景下间接应用默认格局启动。
4、数据处理异样
在数据处理线程解决音频数据时,通常会呈现事件响应超时、设施对象异样等状况。通常的解决办法是,先退出数据线程并完结设施,而后查看以后设施是否失常性能,而后重新启动以后设施或选用默认设施。
理解网易云信音视频通话>>>
理解网易云信,来自网易外围架构的通信与视频云服务>>
更多技术干货,欢送关注vx公众号“网易智慧企业技术+”。系列课程提前看,精品礼物收费得,还可直接对话CTO。
听网易CTO讲述前沿察看,看最有价值技术干货,学网易最新实践经验。网易智慧企业技术+,陪你从思考者成长为技术专家。