Core Audio APIs 技术笔记一(扬声器音量设置)

时间:2021-06-20 03:25:17

        最近在做winsows上音频方面的程序,用到Core Audio APIs系列API来设置相关音频设备参数,所以对用到这方面的知识做一个总结。Core Audio APIs是Windows Vista家族后提供一套新的底层API,主要有以下几个内容

 Multimedia Device (MMDevice) API:用来枚举操作系统自带的音频终端设备对象。
 Windows Audio Session API (WASAPI):根据第一步枚举处理终端设备对象创建和管理音频流。
 DeviceTopology API:使用此类API可以直接访问音频适配器中的硬件数据通路的拓扑特性(如音量控制,多路复用器等)。
 EndpointVolume API:使用此类API可以直接访问音频设备的声音控制,这类API主要是给那些毒战模式管理音频流的应用程序。

下面让我们用代码事例设置麦克风和扬声器音量大小,以及设置麦克风加强来说明相关用法:

  1. 首先我们定义相关COM接口如下,后面要用到:

CComPtr<IMMDeviceEnumerator >	m_pIMMEnumerator;	//主要用于枚举设备接口
CComPtr<IAudioEndpointVolume>	m_pRenderEndptVol;	//扬声器音量控制接口
CComPtr<ISimpleAudioVolume>	m_pRenderSimpleVol;	//扬声器的会话音量控制接口

 2.创建IMMDeviceEnumerator接口,IMMDeviceEnumerator接口是获取后面设备对象的根本,所以先要创建这个接口,用常用创建COM接口的CoCreateInstance()来创建,指定相应的参数,很基本用法,不做过多说明。

HRESULT hr = S_OK;
hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, (void**)&m_pIMMEnumerator);

pIMMEnumerator对象创建好,我们可以看看这个接口可以用的方法:

class IMMDeviceEnumerator : public IUnknown
{
	public:
	virtual HRESULT STDMETHODCALLTYPE EnumAudioEndpoints(EDataFlow dataFlow, DWORD dwStateMask, IMMDeviceCollection **ppDevices) = 0;
	virtual HRESULT STDMETHODCALLTYPE GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, IMMDevice **ppEndpoint) = 0;
	virtual HRESULT STDMETHODCALLTYPE GetDevice(LPCWSTR pwstrId, IMMDevice **ppDevice) = 0;
	virtual HRESULT STDMETHODCALLTYPE RegisterEndpointNotificationCallback(IMMNotificationClient *pClient) = 0;
	virtual HRESULT STDMETHODCALLTYPE UnregisterEndpointNotificationCallback(IMMNotificationClient *pClient) = 0;
};

可以看到除过IUnKonwn接口方法外有属于自己的五个方法,分别如下:

//枚举系统所有设备
EnumAudioEndpoints(EDataFlow dataFlow, DWORD dwStateMask, IMMDeviceCollection **ppDevices);
//获取系统默认音频设备,类似主音频设备
GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, IMMDevice **ppEndpoint);
//根据设备ID获取指定的设备,设备ID可以通过EnumAudioEndpoints()获取,也可以通过DSound函数DirectSoundEnumerate()等获取。
GetDevice(LPCWSTR pwstrId, IMMDevice **ppDevice);
//注册给终端设备一些通知事件,注册后比如当设备拨出、插入、有变化时会收到相应的通知事件。
RegisterEndpointNotificationCallback(IMMNotificationClient *pClient);
//注销掉之前注册的通知回调。
UnregisterEndpointNotificationCallback();

本次我们主要是用GetDefaultAudioEndpoint()和GetDevice()这两个接口方法。

4.获取IAudioEndpointVolume扬声器m_pRenderEndptVol接口,首先我们要定义一个IMMDevice对象,根据刚刚创建的m_pIMMEnumerator对象获取相应的IMMDevice对象,然后用获取的IMMDevice对象激活m_pRenderEndptVol接口。

如果之前没有获取终端ID则可以使用

//EDataFlow/有三个常用的变量:
//eAll : 会列举出系统中所有的音频设备包括Render --- 扬声器, Capture --- Microphone, Stereo Mixer;
//eRender: 会列举出系统中所有的音频播放设备;
//eCapture: 会列举出系统中所有音频采集设备;
//ERole 设备角色,假如系统中有多个音频设备,那么一个设备可能用户是用来播放电影的,另一个可能是用来玩游戏的,因此就引入了角色的概念。
//eConsole : 与计算机交互;    eCommunications : 与他人的声音交流;     eMultimedia : 播放或者录制电影和音乐,一般默认使用eConsole。
//ppEndpoint:需要接收获取设备对象指针。
HRESULT GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, IMMDevice **ppEndpoint);

如果之前已经枚举出设备ID,想要根据设备ID过去指定的设备对象,可以使用

//pwstrId: 已经获取的设备ID;
//ppDevice:需要接收获取设备对象二重指针;
HRESULT GetDevice(LPCWSTR pwstrId, IMMDevice **ppDevice)

示例代码如下:

	HRESULT hr = S_OK;
	CComPtr<IMMDevice> pIMMDeivce = NULL;
	if(strEndPointID.empty())
	{
		hr = m_pIMMEnumerator->GetDefaultAudioEndpoint(eRender,eConsole,&pIMMDeivce);
	}
	else
	{
		hr = m_pIMMEnumerator->GetDevice(strEndPointID.c_s(),&pIMMDeivce);
	}
获取pIMMDeivce对象后,就可以用这个对象接口激活自己想要控制的相应音量设备的接口了,先看下IMMDevice接口拥有属于自己的方法:
class IMMDevice : public IUnknown
{
	public:
	virtual HRESULT STDMETHODCALLTYPE Activate(REFIID iid, DWORD dwClsCtx, PROPVARIANT *pActivationParams, void **ppInterface) = 0;
	virtual HRESULT STDMETHODCALLTYPE OpenPropertyStore(DWORD stgmAccess,IPropertyStore **ppProperties) = 0;
	virtual HRESULT STDMETHODCALLTYPE GetId(LPWSTR *ppstrId) = 0;
	virtual HRESULT STDMETHODCALLTYPE GetState(DWORD *pdwState) = 0;
};
简单注释下这个几个函数意义:
//激活这个设备下指定接口的COM对象;
//iid:指定获取接口的GUID;
//dwClsCtx:执行上下文,一般指定CLSCTX_ALL获取所有信息;
//pActivationParams:对于IID_IAudioEndpointVolume接口设置为NULL。
//ppInterface:需要接收获取设备对象二重指针;
HRESULT Activate(REFIID iid, DWORD dwClsCtx, PROPVARIANT *pActivationParams, void **ppInterface)
//打开一个设备的属性存储接口;
HRESULT OpenPropertyStore(DWORD stgmAccess,IPropertyStore **ppProperties)
//获取设备ID;
HRESULT GetId(LPWSTR *ppstrId)
//获取设备状态信息; 
HRESULT GetState(DWORD *pdwState)  
我们主要用的是Activate()方法,首先我们获取扬声器音量控制接口m_pRenderEndptVol,如下
//主声音设备
hr = pIMMDeivce->Activate(IID_IAudioEndpointVolume, CLSCTX_ALL, NULL, (void**)&m_pRenderEndptVol);
获取到m_pRenderEndptVol接口后我们就可以用这个接口设置系统扬声器音量了。
5.设置系统扬声器音量,也就是主音量,先看看刚刚我们获取m_pRenderEndptVol接口拥有属于自己的方法:
class IAudioEndpointVolume : public IUnknown
{
    public:
        virtual HRESULT STDMETHODCALLTYPE RegisterControlChangeNotify(IAudioEndpointVolumeCallback *pNotify) = 0;
        virtual HRESULT STDMETHODCALLTYPE UnregisterControlChangeNotify(IAudioEndpointVolumeCallback *pNotify) = 0;
        virtual HRESULT STDMETHODCALLTYPE GetChannelCount(UINT *pnChannelCount) = 0;
        virtual HRESULT STDMETHODCALLTYPE SetMasterVolumeLevel(float fLevelDB,LPCGUID pguidEventContext) = 0;
        virtual HRESULT STDMETHODCALLTYPE SetMasterVolumeLevelScalar(float fLevel,LPCGUID pguidEventContext) = 0;
        virtual HRESULT STDMETHODCALLTYPE GetMasterVolumeLevel(float *pfLevelDB) = 0;
        virtual HRESULT STDMETHODCALLTYPE GetMasterVolumeLevelScalar(float *pfLevel) = 0;
        virtual HRESULT STDMETHODCALLTYPE SetChannelVolumeLevel(UINT nChannel,float fLevelDB,LPCGUID pguidEventContext) = 0;
        virtual HRESULT STDMETHODCALLTYPE SetChannelVolumeLevelScalar(UINT nChannel,float fLevel,LPCGUID pguidEventContext) = 0;
        virtual HRESULT STDMETHODCALLTYPE GetChannelVolumeLevel(UINT nChannel,float *pfLevelDB) = 0;
        virtual HRESULT STDMETHODCALLTYPE GetChannelVolumeLevelScalar(UINT nChannel,float *pfLevel) = 0;
        virtual HRESULT STDMETHODCALLTYPE SetMute(BOOL bMute,LPCGUID pguidEventContext) = 0;
        virtual HRESULT STDMETHODCALLTYPE GetMute(BOOL *pbMute) = 0;
        virtual HRESULT STDMETHODCALLTYPE GetVolumeStepInfo(UINT *pnStep,UINT *pnStepCount) = 0;
        virtual HRESULT STDMETHODCALLTYPE VolumeStepUp(LPCGUID pguidEventContext) = 0;
        virtual HRESULT STDMETHODCALLTYPE VolumeStepDown(LPCGUID pguidEventContext) = 0;
        virtual HRESULT STDMETHODCALLTYPE QueryHardwareSupport(DWORD *pdwHardwareSupportMask) = 0;
        virtual HRESULT STDMETHODCALLTYPE GetVolumeRange(float *pflVolumeMindB,float *pflVolumeMaxdB,float *pflVolumeIncrementdB) = 0;  
};
这个IAudioEndpointVolume接口方法很多,不仔细介绍每一个了,只介绍我们需要用到的几个方法,大家可以根据自己的需要,顾名思义找到自己需要用的方法。
/*
设置音频终端的主音量电;
fLevel:主音量数值级别,这个值范围在0到1之间;
pguidEventContext):此参数指向一个事件上下文的GUID,如果SetMasterVolumeLevelScalar调用更改端点的音量,所有已注册客户IAudioEndpointVolumeCallback接口与端点将接收通知。
*/
HRESULT SetMasterVolumeLevelScalar(float fLevel,LPCGUID pguidEventContext);
//获取音频终端的主音量电平;
HRESULT GetMasterVolumeLevelScalar(float *pfLevel);
//设置音频终端是否为静音状态;
HRESULT SetMute(BOOL bMute,LPCGUID pguidEventContext);
//获取音频终端是静音状态;
HRESULT GetMute(BOOL *pbMute);
其余几个参数很简单,看名字就知道什么意思,不作过多介绍。

设置系统扬声器音量代码示例:

float level = 0.5f;//这个值可以跟进相应比例换算所得
hr = m_pRenderEndptVol->SetMasterVolumeLevelScalar(level, NULL);
获取系统扬声器音量代码示例:
float level = 0.0f;
hr = m_pRenderEndptVol->GetMasterVolumeLevelScalar(&level);
注意level这个值一般根据界面滑动条最大值按比例换算所得。
设置系统扬声器静音示例:
hr = m_pRenderSimpleVol->SetMute(TRUE, NULL);

获取系统扬声器静音状态示例: 

BOOL bMute = 0;
hr = m_pRenderSimpleVol->GetMute(&bMute);
这只是简单的几种参数设置,大家可以根据自己的需要来选择不同方法达到自己目的。

6.设置应用程序会话音量
        上面设置的都是系统扬声器的音量,即相应的设置会影响系统上所有软件的音量,下面我们来设置属于应用程序本身的会话音量,所谓会话音量即时这个应用程序本身的音量,设置会话音量的大小只会影响应用程序本身的音量大小,不会影响系统其它程序音量的大小。首先我们要获取IAudioSessionManager接口对象,方法是通过已经获取IMMDevice接口对象来激活IAudioSessionManager接口对象,然后就可以通过IAudioSessionManager接口来获取ISimpleAudioVolume接口对象了,最后通过ISimpleAudioVolume接口对象就可以操作会话音量的大小了。示例代码如下:

//合成器界面
CComPtr<IAudioSessionManager> pSessionManager = NULL;
hr = pIMMDeivce->Activate(IID_IAudioSessionManager, CLSCTX_INPROC_SERVER, NULL, (void **)(&pSessionManager));
if(FAILED(hr)) return;
hr = pSessionManager->GetSimpleAudioVolume(NULL, FALSE, &m_pRenderSimpleVol);

获取到ISimpleAudioVolume接口对象后就可以使用其具有的方法操作具体的功能了,看下这个接口有的方法:

class ISimpleAudioVolume : public IUnknown
{
    public:
        virtual HRESULT STDMETHODCALLTYPE SetMasterVolume(float fLevel, LPCGUID EventContext) = 0;
        virtual HRESULT STDMETHODCALLTYPE GetMasterVolume(float *pfLevel) = 0;
        virtual HRESULT STDMETHODCALLTYPE SetMute(const BOOL bMute, LPCGUID EventContext) = 0;
        virtual HRESULT STDMETHODCALLTYPE GetMute(BOOL *pbMute) = 0;
};
所有方法和上面IAudioEndpointVolume接口方法基本一样,直接贴代码说明
设置和获取会话音量的大小:
//设置扬声器会话音量大小
float fVol = 0.5f;
hr = m_pRenderSimpleVol->SetMasterVolume(fVol, NULL);
//获取扬声器会话音量大小
float fVol = 0.0f;
hr = m_pRenderSimpleVol->GetMasterVolume(&fVol);
设置和获取会话音量的静音状态:
//设置扬声器会话音量静音状态
BOOL bMute = TRUE;
hr = m_pRenderSimpleVol->SetMute(bMute , NULL);
//获取扬声器静音状态
hr = m_pRenderSimpleVol->GetMute(&bMute);
        扬声器里面的功能设置还有很多方法,介绍只是最基本的方法,大家可以根据自己需要挖掘更深层的设置方法,这一节就先介绍到这里吧,下一节介绍系统麦克风方面一些简单的设置。