声音的播放在Windows API就有这样的支持函数。PlaySound是Windows用于播放音乐的API函数。在vs2010以上版本需要加入#pragma comment(lib, "winmm.lib")才能使用PlaySound。PlaySound函数原型为 BOOL PlaySound(LPCSTR pszSound, HMODULE hmod,DWORD fdwSound)。参数pszSound是指定了要播放声音的字符串,该参数可以是WAVE文件。参数hmod是应用程序的实例句柄,设置为NULL即可。参数fdwSound是预定义值的组合。
WAVE文件格式是一种由微软和IBM联合开发的用于音频数字存储的标准,它采用RIFF文件格式结构。WAVE文件作为最经典的Windows多媒体音频格式,应用非常广泛,它使用三个参数来表示声音:采样位数、采样频率和声道数。WAVE对音频流的编码没有硬性规定,通常采用的音频编码方式是脉冲编码调制(PCM)。PCM音频数据是未经压缩的音频采样数据裸流,它是由模拟信号经过采样、量化、编码转换成的标准的数字音频数据。目前在线播放、播放器播放以及下载音频格式中最常见的就是MP3。MP3是一种音频压缩技术,其全称是动态影像专家压缩标准音频层面3,简称为MP3。WAVE是录音时用的标准的WINDOWS文件格式,文件的扩展名为“WAV”,数据本身的格式为PCM或压缩型,属于无损音乐格式的一种。所以WAV文件相对于MP3文件比较大。MP3被设计用来大幅度地降低音频数据量,将音乐以1:10 甚至 1:12 的压缩率,压缩成容量较小的文件。MP3是一种有损音乐格式,而WAV则是一种无损音乐格式。MP3一般是用于我们普通用户听歌,而WAV文件通常用于录音室录音和专业音频项目。
在早期的DirectX中对音频操作的API有两个DirectMusic和DirectSound。可自从DirectX 9.0c之后,DXMusic和DirectSound就被XAudio2给取代了。使用XAudio2的基本流程如下:
1. 初始化 XAudio2 引擎
2. 加载wave文件
3. 通过CreateSourceVoice 方法来创建源语音
4. 使用 SubmitSourceBuffer函数将 XAUDIO2 _ 缓冲区提交到源语音
5. 使用 start 函数启动源语音。
我们使用VS2019新建项目“D3D_09_Sound”,首先我们要创建 “DXUT.h”,“ SDKwavefile.h”和“SDKwavefile.cpp”。其中“DXUT.h”内如如下:
#pragma once
#ifndef SAFE_DELETE
#define SAFE_DELETE(p) { if (p) { delete (p); (p)=NULL; } }
#endif
#ifndef SAFE_DELETE_ARRAY
#define SAFE_DELETE_ARRAY(p) { if (p) { delete[] (p); (p)=NULL; } }
#endif
#ifndef SAFE_RELEASE
#define SAFE_RELEASE(p) { if (p) { (p)->Release(); (p)=NULL; } }
#endif
它定义了三个宏,主要用来释放对象的。然后就是SDKwavefile文件,这个文件来源于DirectX安装目录下Samples\C++\DXUT\Optional下面,主要就是对WAVE音频文件的封装。有了这个文件,我们播放声音就非常的方便了。接下来,我们开始介绍main.cpp文件内容。使用XAudio2来播放声音,需要先引入xaudio2.h头文件。其实XAudio2的使用和DirectX没有直接关系,但是为了我们的整体代码的设计,我们就将播放声音的代码统一放置到initScene函数中,代码如下:
// 初始化 XAudio2
CoInitializeEx(NULL, COINIT_MULTITHREADED);
// 创建 XAudio2 对象
IXAudio2* pXAudio2 = NULL;
XAudio2Create(&pXAudio2, 0);
// 创建一个主声音
IXAudio2MasteringVoice* pMasteringVoice = NULL;
pXAudio2->CreateMasteringVoice(&pMasteringVoice);
// 读取wave文件
CWaveFile wav;
wav.Open((LPWSTR)L"MusicSurround.wav", NULL, WAVEFILE_READ);
// 获取文件格式
WAVEFORMATEX* pwfx = wav.GetFormat();
// 声音数据字节大小
DWORD cbWaveSize = wav.GetSize();
// 声音数据字节数组
BYTE* pbWaveData = new BYTE[cbWaveSize];
wav.Read(pbWaveData, cbWaveSize, &cbWaveSize);
// 创建一个源声音
IXAudio2SourceVoice* pSourceVoice;
pXAudio2->CreateSourceVoice(&pSourceVoice, pwfx);
// 填充 WAVEFORMATEXTENSIBLE 结构
XAUDIO2_BUFFER buffer = { 0 };
buffer.Flags = XAUDIO2_END_OF_STREAM;// 可以设为0或XAUDIO2_END_OF_STREAM,当设为后者时,将使XAudio2播放完该数据块后自动停止,不再播放下一个数据块
buffer.AudioBytes = cbWaveSize; // 音频数据的长度,按字节算
buffer.pAudioData = pbWaveData; // 具体音频数据的地址,unsigned char pBuffer[]
buffer.PlayBegin = 0; // 起始播放地址
buffer.PlayLength = 0; // 播放长度,0为整数据块
buffer.LoopBegin = 0; // 循环起始位置
buffer.LoopLength = 0; // 循环长度,按字节算
buffer.LoopCount = 0; // 循环次数,0为不循环,255为无限循环
buffer.pContext = NULL; // 这里的pContext用来标识该数据块,供回调用,可以是NULL
// 提交 XAUDIO2_BUFFER
pSourceVoice->SubmitSourceBuffer(&buffer);
// 播放声音
pSourceVoice->Start(0, XAUDIO2_COMMIT_NOW);
// Cleanup XAudio2
//SAFE_DELETE_ARRAY(pbWaveData);
//pSourceVoice->DestroyVoice();
//pMasteringVoice->DestroyVoice();
//pXAudio2->Release();
//pXAudio2 = NULL;
//CoUninitialize();
XAudio2共有三种声音,源音,混音和主音,XAudio2通过他们来操作和播放音频数据。源声音代表音频原始数据,它需要把数据传递给其他声音(混音或主音)。混音是对声音的进步一处理,它需要把数据传递给其他声音(混音或主音)。主音主要是音频数据发送给硬件。声音数据从源声音开始,可以选择经过一个或多个混音,最后发送到主音,由主音将因音频数据发送给硬件。使用XAudio2设置合适的声音锥可以模拟3D立体声。3D立体声和2D声不同的地方是它是会随着距离衰减的,距离越近声音越大,距离越远声音越小。在游戏中,通常我们需要控制整个游戏的背景音乐,同时单独控制一些特效(攻击、爆炸之类)音乐。所谓混音就是将多路音频混合成一路进行输出。
本课程的所有代码案例下载地址:
备注:这是我们游戏开发系列教程的第二个课程,这个课程主要使用C++语言和DirectX来讲解游戏开发中的一些基础理论知识。学习目标主要依理解理论知识为主,附带的C++代码能够看懂且运行成功即可,不要求我们使用DirectX来开发游戏。课程中如果有一些错误的地方,请大家留言指正,感激不尽!