DirectSound是DirectX组件之一,提供了对音频设备的捕获和播放能力,同时它也是唯一几个支持Xp系统的音频技术之一。 DirectSound主要有以下特点:
优点:
- 播放音频低延迟。
- 硬件资源控制。
- 同时播放多个声音。
- 控制硬件缓冲区的使用优先级(DirectSound使用缓冲区来播放音频)。
- 模拟3D音频环境。
- 动态更改音效(回声、和声等)。
- 捕获音频输入设备声音位wav(多为PCM数据,未经压缩)。
缺点:
- 只能播放wav音频文件。
这里我们说说设备操作这一块儿。
1. 输出设备操作
在DirectSound中,一个设备对象就代表一个音频设备,播放设备对象对应播放设备,输入设备对象对应输入设备。因为DirectSound使用COM协议,因此每个设备对象都用接口来表示。这里IDirectSound8这个接口就代表了一个输出设备对象,应用程序可以对同一个音频设备创建多个设备对象来进行音频输出操作。旧版本的DirectSound使用的是IDirectSound接口,相比前者少了一些功能。
1.1 枚举
HRESULT WINAPI DirectSoundEnumerateW(In LPDSENUMCALLBACKW pDSEnumCallback, In_opt LPVOID pContext);
typedef BOOL (CALLBACK *LPDSENUMCALLBACKW)(LPGUID, LPCWSTR, LPCWSTR, LPVOID);
我们通过DirectSoundEnumerateW这个函数来枚举,该函数需要传入一个回调函数(原型见上),当枚举到一个设备时该回调会被调用。如果我们想继续枚举,需要在这个回调用中返回TRUE来告诉系统,否则返回FALSE。另一个参数pContext
允许用户传入额外的参数,传入回调函数的最后一个实参就是这个pContext
。枚举时,DirectSound会将默认也认作一个单独的设备来对待,因此默认设备会被重复枚举一次。当设备被作为默认设备枚举时,它的GUID和设备描述字符串都为空,需要小心处理,这里我直接跳过了该次枚举:
if (DirectSoundEnumerateW(enumerateCallback, nullptr) != DS_OK) {
...
}
BOOL CALLBACK DirectSoundBasic::enumerateCallback(LPGUID guid,
LPCWSTR deviceDescription,
LPCWSTR deviceDriverModule,
LPVOID context)
{
Q_UNUSED(context);
// if primary device, skip it
if (guid == nullptr) return TRUE;
...
}
1.2 创建设备对象
HRESULT WINAPI DirectSoundCreate8(In_opt LPCGUID pcGuidDevice, Outptr LPDIRECTSOUND8 *ppDS8, Pre_null LPUNKNOWN pUnkOuter);
调用DirectSoundCreate8函数,我们可以创建一个设备对象,通过传入一个枚举设备时获得的GUID,函数会返给我们一个IDirectSound8接口代表设备对象:
IDirectSound8* directSound8;
if (DirectSoundCreate8(guid, &directSound8, NULL) != DS_OK) {
std::wcout << L"[error] DirectSoundCreate8 call error!";
return TRUE; // if error, skip this device
}
1.3 设置设备对象优先级
HRESULT IDirectSound8::SetCooperativeLevel(HWND hwnd, DWORD dwLevel)
在使用设备对象创建缓冲区(用来捕获、播放音频)之前,我们需要设置设备对象的协作级别。这个协作级别相当于用户对设备进行操作的优先级,分为:
DSSCL_EXCLUSIVE: 互斥级别。对于DirectX8.0以前版本,仅播放当前应用的音频数据,其他应用的声音不会被播放;对于DirectX8.0级以后版本,同DSSCL_PRIORITY版本。
- DSSCL_NORMAL: 普通级别,这种级别下的应用程序具有最平滑的多任务和资源共享表现,但是这种应用不能更改主缓冲区音频数据格式,输出音频格式被限制为8位数据。在DirectSound中,次缓冲区用来填充应用程序需要播放的声音,主缓冲区会对多个次缓冲区(可能是本应用的,也可能是其他应用的)进行混音,然后用声卡输出播放。
- DSSCL_PRIORITY: 优先级别,可以更改主缓冲区数据格式。
DSSCL_WRITEPRIMARY:写主缓冲区级别,应用可以直接写入主缓冲区,此时所有次缓冲区不会被播放(如果设备的驱动是DirectSound模拟出来的,则不能设置该级别)。
注意该函数需要传入一个窗口句柄,因为我们今天只介绍DirectSound的基本操作,我直接传入桌面窗口的句柄并设定位DSSCL_NORMAL优先级:
if (directSound8->SetCooperativeLevel(GetDesktopWindow(), DSSCL_NORMAL) != DS_OK) {
std::wcout << L"[error] SetCooperativeLevel call error!";
return TRUE;
}
1.4 设备能力
HRESULT IDirectSound8::GetCaps(LPDSCAPS pDSCaps)
不同的音频播放设备具有不同的能力,DirectSound允许我们查询设备的能力:
- 是否经过Microsoft认证。
- 知否支持最小最大采样率之间的所有采样率。
- 当没有DirectSound驱动时模拟驱动。
- 主次缓冲区格式(16位、8位)。
- 主次缓冲区声道支持(单声道、立体声即多声道)。
- 不精准的数据(某些声卡不支持):
- 缓冲区(静态缓冲区、流缓冲区、3D缓冲区)最大数、空闲数。
- 声卡上的总内存数量、空闲内存数量、最大空闲块大小,
我们传给GetCaps一个DSCAPS结构体地址,然后系统就帮我们填充相应的数据,调用GetCaps前需要将DSCAPS结构体的dwSize设置为DSCAPS的大小:
DSCAPS deviceCapability = { sizeof(deviceCapability) };
if (directSound8->GetCaps(&deviceCapability) != DS_OK) {
std::wcout << L"[error] GetCaps call error!";
return TRUE;
}
1.5 播放器配置
HRESULT IDirectSound8::GetSpeakerConfig(LPDWORD pdwSpeakerConfig)
HRESULT IDirectSound8::SetSpeakerConfig(LPDWORD pdwSpeakerConfig)
播放器配置只能是以下之一:
- DSSPEAKER_5POINT1_SURROUND、DSSPEAKER_5POINT1_BACK: 家庭影院配置,5个环绕扬声器,1个低音炮。
- DSSPEAKER_DIRECTOUT:直接播放。
- DSSPEAKER_HEADPHONE:头戴式耳机。
- DSSPEAKER_MONO:单声道扬声器。
- DSSPEAKER_QUAD:4声道播放器。
- DSSPEAKER_STEREO:立体声播放器。
- DSSPEAKER_SURROUND:环绕播放器。
- DSSPEAKER_7POINT1_WIDE、DSSPEAKER_7POINT1_SURROUND:家庭影院配置,7个环绕扬声器,1个低音炮。
虽然MSDN文档没有写清楚,但是通过查以上宏定义我们发现它们是按大小顺序定义的,因此不可能通过OR|
来包含多种可能,例子中如果调用出错直接返回TRUE表示我们继续枚举设备并继续查询那些设备能力:
DWORD deviceSpeakerConfiguration;
if (directSound8->GetSpeakerConfig(&deviceSpeakerConfiguration) != DS_OK) {
std::wcout << L"[error] GetSpeakerConfig call error!";
return TRUE;
}
2. 运行结果
这次我们用GUI界面来显示实例运行的结果(出于方便考虑,以后我会用控制台来显示示例),为防止用户误操作更改显示的数据我将大部分控件都disable了:
完整代码见链接。