写点学习过程中学到的,也写点初学者方便学的,至少对于我来讲是方便的
想用waveout接口主要需要这几个函数:
打开设备接口 waveOutOpen;
准备缓冲区 waveOutPrepareHeader;
向设备写入缓冲区数据 waveOutWrite;
清理缓冲区 waveOutUnprepareHeader;
关闭设备接口 waveOutClose;
首先关于打开设备接口的
waveOutOpen( LPHWAVEOUT phwo, UINT uDeviceID, LPCWAVEFORMATEX pwfx, DWORD_PTR dwCallback, DWORD_PTR dwInstance, DWORD fdwOpen );
具体请参考:waveOutOpen 函数 (mmeapi.h) - Win32 apps | Microsoft Learn
由于我们现在也不使用CALLBACK回调函数,所以只需要管前三项参数就行了
第一个参数phwo为指向缓冲区的指针,该缓冲区接收标识打开的波形音频输出设备的句柄,我们直接HWAVEOUT device就行,函数执行完会往device里放入打开设备的句柄。
第二个参数uDeviceID是要打开的波形音频输出设备的标识符。我们可以用WAVE_MAPPER来让设备自动选择合适的波形音频输出设备。
第三个pwfx参数是指向 WAVEFORMATEX 结构的指针,该结构标识要发送到设备的波形音频数据的格式。具体请参考WAVEFORMATEX 结构 (Windows) |Microsoft 学习
这个结构体的内容需要我们自己设置,或者你也可以读取并解析完文件后再把格式复制过去。我们在这这么写:
WAVEFORMATEX wfx; //结构体初始化设置
wfx.nSamplesPerSec = 44100 ; //采样频率
wfx.wBitsPerSample = 16; //采样位深
wfx.nChannels = 2; //音道
wfx.cbSize = 0; //附加信息
wfx.wFormatTag = WAVE_FORMAT_PCM; //PCM编码格式,也可以赋1
wfx.nBlockAlign = wfx.wBitsPerSample * wfx.nChannels / 8; //单个数据块长度
wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec; //数据传输速率
你会发现其实只需要设置采样频率,位深和声道就行了,没错,就是这么简单。
自此,waveOutOpen函数的设置就完成了,完整版为:
HWAVEOUT device; //设备句柄
WAVEFORMATEX wfx; //结构体初始化设置
wfx.nSamplesPerSec = 44100; //采样频率
wfx.wBitsPerSample = 16; //采样位深
wfx.nChannels = 2; //音道
wfx.cbSize = 0; //附加信息
wfx.wFormatTag = WAVE_FORMAT_PCM; //PCM编码格式,也可以赋1
wfx.nBlockAlign = wfx.wBitsPerSample * wfx.nChannels / 8;
wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
//尝试打开设备,WAVE_MAPPER是mmsystem.h中定义的常量,指向系统上的默认波形设备
if (waveOutOpen(&device,WAVE_MAPPER,&wfx,0,0,CALLBACK_NULL)!=MMSYSERR_NOERROR)
{
fprintf(stderr, "unable to open WAVE_MAPPER device\n");
ExitProcess(1);
}
printf("The Wave Mapper device was opened successfully!\n");
接下来是向设备写入,这一部分需要用waveOutPrepareHeader;waveOutWrite;waveOutUnprepareHeader;这三个函数。
首先要准备缓冲区函数waveOutPrepareHeader(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh);具体参考:waveOutPrepareHeader 函数 (mmeapi.h) - Win32 apps | Microsoft Learn
第一个参数hwo就是我们之前让waveOutOpen写好的device了。
第二个参数pwh为指向 WAVEHDR结构的指针,该结构标识要准备的数据块。这个结构体成员非常多,这里就不详解了,大伙想了解点链接看就行。这里由于我们目标是能播放就行,所以其实只需要设置结构体中两个成员参数就够了:LPSTR lpData 和 DWORD dwBufferLength;前者为缓冲区数据的头指针,后者为缓冲区数据长度。我们此时可以打开要播放的音乐文件来准备这两个参数,由于是刚开始,我们就直接读取完整个文件吧,后面再考虑如何一次只读取一小部分。所以可以这么写:
//读取音频文件数据
char* loadAudioBlock(char filename[], unsigned long* buffsize)
{
FILE* file;
unsigned long filesize = 0; //文件大小
char* data = NULL; //块地址
//打开文件
printf("%s\n", filename);
file = fopen(filename, "rb+");
if (file == NULL)
{
printf("无法打开文件\n");
return 0;
}
fseek(file, 0, SEEK_END); //获取文件大小
filesize = ftell(file) - 44;
data = (unsigned char*)malloc(filesize);
fseek(file, 44, SEEK_SET); //读取头文件后的数据部分
fread(data, sizeof(unsigned char), filesize, file);
*buffsize = filesize; //缓冲区长度为文件大小
fclose(file);
return (char*)data; //将申请的内存的头指针传递出去
}
第三个参数为WAVEHDR结构体的大小,sizeof(WAVEHDR)就行,没什么好说的。
后面的waveOutWrite和waveOutUnprepareHeader格式和waveOutPrepareHeader一模一样,在此就不赘述了。至此,我们关于设备写入数据并输出部分的完整代码为:
WAVEHDR wave_buff; //wave缓存区设置
LPSTR buff; //缓冲区指针
DWORD buffsize; //缓冲区大小
//将文件内容输入缓存并把缓存输出到设备
if ((buff = loadAudioBlock("text.wav",&buffsize)) == NULL)
{
fprintf(stderr,"Unable to load file\n");
return 0;
}
wave_buff.dwBufferLength = buffsize;
wave_buff.lpData = buff;
//准备要播放的buff
waveOutPrepareHeader(device, &wave_buff, sizeof(WAVEHDR));
// 将buff写入设备。waveOutWrite 会立即返回除非使用同步驱动程序(不经常)。
waveOutWrite(device, &wave_buff, sizeof(WAVEHDR));
// 等待buff播放完后清理缓冲区
Sleep(500);
while (waveOutUnprepareHeader(device, &wave_buff, sizeof(WAVEHDR)) == WAVERR_STILLPLAYING)
Sleep(100);
如果你不在意后面的内存释放,设备关闭之类的小细节,那么我们此时已经完成了全部工作了。
整个项目完整代码为(当然,我还是在意上面提到的那些细节的):
#include <stdio.h>
#include <windows.h>
#include <mmsystem.h>
#pragma comment(lib,"Winmm.lib")
//读取音频文件数据
char* loadAudioBlock(char filename[], unsigned long* buffsize)
{
FILE* file;
unsigned long filesize = 0; //文件大小
char* data = NULL; //块地址
//打开文件
printf("%s\n", filename);
file = fopen(filename, "rb+");
if (file == NULL)
{
printf("无法打开文件\n");
return 0;
}
fseek(file, 0, SEEK_END);
filesize = ftell(file) - 44;
data = (unsigned char*)malloc(filesize);
fseek(file, 44, SEEK_SET);
fread(data, sizeof(unsigned char), filesize, file);
*buffsize = filesize;
fclose(file);
return (char*)data;
}
int main()
{
HWAVEOUT device; //设备句柄
WAVEFORMATEX wfx; //结构体初始化设置
WAVEHDR wave_buff; //wave缓存区设置
LPSTR buff; //缓冲区指针
DWORD buffsize; //缓冲区大小
wfx.nSamplesPerSec = 44100 ; //采样频率
wfx.wBitsPerSample = 16; //采样位深
wfx.nChannels = 2; //音道
wfx.cbSize = 0; //附加信息
wfx.wFormatTag = WAVE_FORMAT_PCM; //PCM编码格式,也可以赋1
wfx.nBlockAlign = wfx.wBitsPerSample * wfx.nChannels / 8;
wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
if (waveOutOpen(&device, WAVE_MAPPER, &wfx, 0, 0, CALLBACK_NULL) != MMSYSERR_NOERROR)
{
fprintf(stderr, "unable to open WAVE_MAPPER device\n");
return 0;
}
printf("The Wave Mapper device was opened successfully!\n");
//将文件内容输入缓存并把缓存输出到设备,文件放在和代码同文件夹内
if ((buff = loadAudioBlock("text.wav", &buffsize)) == NULL)
{
fprintf(stderr, "Unable to load file\n");
return 0;
}
wave_buff.dwBufferLength = buffsize;
wave_buff.lpData = buff;
//准备要播放的buff
waveOutPrepareHeader(device, &wave_buff, sizeof(WAVEHDR));
// 将buff写入设备。waveOutWrite 会立即返回除非使用同步驱动程序(不经常)。
waveOutWrite(device, &wave_buff, sizeof(WAVEHDR));
// 直到buff播放完毕,然后开始清理缓冲区
Sleep(500);
while (waveOutUnprepareHeader(device, &wave_buff, sizeof(WAVEHDR)) == WAVERR_STILLPLAYING)
Sleep(100);
waveOutClose(device);
free(buff);
return 0;
}
按道理来讲,放好音乐文件后直接运行就可以播放了,至少我是可以成功运行的,有什么问题再私信我吧,反正这是学习笔记,有问题的地方肯定还是有很多的。作为初学者,在刚开始接触waveOut的时候不可谓不头疼,晦涩难懂的代码,全是搬运的内容,完全无法运行的下载的代码以及清一色严格的匈牙利命名法,都让学习寸步难行,好在最后还是硬啃读下来了,不得不说,这段时间学到了很多。好!最后希望与各位共勉吧。