window API播放pcm格式音频文件,函数waveOutOpen等

24 篇文章 1 订阅
23 篇文章 1 订阅
之前在我的博客中有一篇关于编写录音器的代码,可保存为pcm和wav格式,说白了其实两者是一个东西,只不过wav比pcm多了一个文件头,这个文件头一共占了44个字节。此处这个不是重点,重点是如何编写程序实现播放pcm音频数据。我下面有这个程序的完整代码,可以通过粘贴复制实现即可使用。

一、 如果要播放pcm格式音频,我们需要几个windows API函数:

1.  waveOutGetNumDevs()函数
函数原型: UINT waveOutGetNumDevs(VOID);
这个函数没有参数,返回设备个数。
函数简单使用:
BOOL getOutNumDrive()//音频数量
{
int count = waveOutGetNumDevs();
return count < 1 ? FALSE : TRUE;//1 音频输入数量
}


2.  waveOutGetDevCaps()函数
函数原型:
MMRESULT waveOutGetDevCaps(
  UINT uDeviceID,    
  LPWAVEOUTCAPS pwoc, 
  UINT cbwoc         
);
功能:这个函数获取输出音频设备的相应信息。

函数的简单使用:
BOOL getOutDevCaps()
{
WAVEOUTCAPS waveOutcaps;
MMRESULT mmResult =waveOutGetDevCaps(0,&waveOutcaps,sizeof(WAVEINCAPS));//2设备描述
if ( MMSYSERR_NOERROR != mmResult )
{
return FALSE;
}
return TRUE;
}

3.  waveOutOpen()函数
函数原型:
MMRESULT waveOutOpen(
  LPHWAVEOUT phwo,         
  UINT uDeviceID,          
  LPWAVEFORMATEX pwfx,      
  DWORD dwCallback,        
  DWORD dwCallbackInstance, 
  DWORD fdwOpen            
);
功能:打开一个音频设备。
函数简单使用:
LPHWAVEOUT phwo, LPCWAVEFORMATEX pwfx;
if (MMSYSERR_NOERROR != waveOutOpen(0, 0, pwfx, 0, 0,WAVE_FORMAT_QUERY))
{
//fprintf(stderr, "不支持文件格式!\n");
return FALSE;
}

4.  waveOutPrepareHeader()函数
函数原型:
waveOutPrepareHeader(
  hWaveOut: HWAVEOUT;    {设备句柄}
  lpWaveOutHdr: PWaveHdr; {TWaveHdr结构的指针}
  uSize: UINT           {TWaveHdr 结构大小}  
): MMRESULT;            {成功返回 0; 可能的错误值见下:}

MMSYSERR_INVALHANDLE = 5; {设备句柄无效}
MMSYSERR_NOMEM      = 7;  {不能分配或锁定内存}
MMSYSERR_HANDLEBUSY  = 12;{其他线程正在使用该设备}

//TWaveHdr 是 wavehdr_tag 结构的重定义
wavehdr_tag = record
  lpData: PChar;        {指向波形数据缓冲区}
  dwBufferLength: DWORD; {波形数据缓冲区的长度}
  dwBytesRecorded: DWORD; {若首部用于输入,指出缓冲区中的数据量}
  dwUser: DWORD;        {指定用户的32位数据}
  dwFlags: DWORD;       {缓冲区标志}
  dwLoops: DWORD;        {循环播放次数,仅用于输出缓冲区}
  lpNext: PWaveHdr;      {保留}
  reserved: DWORD;      {保留}
end;

//TWaveHdr 中的 dwFlags 的可选值:
WHDR_DONE     = $00000001; {设备已使用完缓冲区, 并返回给程序}
WHDR_PREPARED  = $00000002;{waveInPrepareHeader 或 waveOutPrepareHeader 已将缓冲区准备好}
WHDR_BEGINLOOP = $00000004; {缓冲区是循环中的第一个缓冲区,仅用于输出}
WHDR_ENDLOOP   = $00000008;{缓冲区是循环中的最后一个缓冲区, 仅用于输出}
WHDR_INQUEUE   = $00000010; { reserved fordriver }

功能:准备一个波形数据块用于播放。

函数的简单使用:
BOOL writeOutAudioBlock(HWAVEOUT phwo, LPSTR block, DWORDsize)
{
WAVEHDR wHdr = { 0 };
wHdr.dwBufferLength =size;
wHdr.lpData = block;
if (MMSYSERR_NOERROR !=waveOutPrepareHeader(phwo, &wHdr, sizeof(WAVEHDR)))
{
//fprintf(stderr,"文件缓冲区准备失败!\n");
return FALSE;
}
}

5.  waveOutWrite()函数
函数原型:
MMRESULT waveOutWrite(
  HWAVEOUT hwo, 
  LPWAVEHDRpwh, 
  UINT cbwh     
);

函数功能:发送音频数据块到指定设备。

函数使用:
BOOL writeOutAudioBlock(HWAVEOUT phwo, LPSTR block, DWORDsize)
{
WAVEHDR wHdr = { 0 };
wHdr.dwBufferLength = size;
wHdr.lpData = block;
if (MMSYSERR_NOERROR != waveOutWrite(phwo, &wHdr,sizeof(WAVEHDR)) )
{
//fprintf(stderr, "写文件缓冲区失败!\n");
return FALSE;
}
}

6.  waveOutUnprepareHeader()函数
函数原型:
MMRESULT waveOutUnprepareHeader(
  HWAVEOUT hwo, 
  LPWAVEHDRpwh, 
  UINT cbwh     
);
函数功能:清除由 waveOutPrepareHeader 完成的准备
函数使用:
BOOL writeOutAudioBlock(HWAVEOUT phwo, LPSTR block, DWORDsize)
{
WAVEHDR wHdr = { 0 };
wHdr.dwBufferLength = size;
wHdr.lpData = block;
while ( WAVERR_STILLPLAYING == waveOutUnprepareHeader(phwo,&wHdr, sizeof(WAVEHDR)))
{
Sleep(100);
}
return TRUE;
}

7.  waveOutClose()函数的使用
函数原型:
MMRESULT waveOutWrite(
  HWAVEOUT hwo, 
  LPWAVEHDRpwh, 
  UINT cbwh     
);
函数功能:关闭音频设备。
函数使用:
BOOL closeOutAudioBlock(HWAVEOUT phwo)
{
if (MMSYSERR_NOERROR != waveOutClose(phwo))
{
//fprintf(stderr, "写文件缓冲区失败!\n");
return FALSE;
}
return TRUE;
}

要使用以上七个函数才能够完成pcm文件的播放。下面让我们看看代码是怎么写成的。

二、 播放录音,播放pcm文件。

首先我贴出来头文件。文件名为: PlayPcm.h
#ifndef _PLAYPCM_H_
#define _PLAYPCM_H_

#include "stdio.h"//此处写成双引号是因为新浪博客的原因,如果写成尖括号就不能显示。
#include "windows.h"
#include "MMSystem.h"

#define LENGTH 10240

#pragma comment(lib, "winmm.lib")
#pragma warning(disable:4996)

BOOL getOutNumDrive(); //判断是否有输出设备
BOOL getOutDevCaps(); //判断时候能够获取设备描述
//BOOL openOutFile(const char *cFileName, FILE **fp);//打开文件
BOOL initOutDrive(const char *cFileName); //初始化设备
BOOL openOutPcm(LPHWAVEOUT, LPCWAVEFORMATEX, DWORD_PTR );//打开相应的设备
LPSTR loadOutAudioBlock(const char* filename, DWORD*blockSize); //加载相应的文件信息
BOOL writeOutAudioBlock(HWAVEOUT hWaveOut, LPSTR block,DWORD size); //写声音文件到设备
BOOL closeOutAudioBlock(HWAVEOUT phwo); //关闭设备

#endif



然后贴出函数的实现文件: PlayPcm.cpp
#include "PlayPcm.h"

BOOL getOutNumDrive()//音频数量
{
int count = waveOutGetNumDevs();
return count < 1 ? FALSE : TRUE;//1 音频输入数量
}

BOOL getOutDevCaps()
{
WAVEOUTCAPS waveOutcaps;
MMRESULT mmResult =waveOutGetDevCaps(0,&waveOutcaps,sizeof(WAVEINCAPS));//2设备描述
if ( MMSYSERR_NOERROR != mmResult )
{
return FALSE;
}
return TRUE;
}

BOOL openOutFile(const char *cFileName, FILE**fp)
{
FILE *fpRead = NULL;
fpRead = fopen(cFileName, "rb");
if (NULL == fpRead)
{
fp = NULL;
return FALSE;
}
*fp = fpRead;
fclose(fpRead);
return TRUE;
}

BOOL initOutDrive()//BOOL initDrive(const char*cFileName)
{
if ( FALSE == getOutNumDrive() )
{
return FALSE;
}
if ( FALSE == getOutDevCaps() )
{
return FALSE;
}

return TRUE;
}

BOOL openOutPcm(LPHWAVEOUT phwo, LPCWAVEFORMATEX pwfx,DWORD_PTR dwCallback)
{
if (MMSYSERR_NOERROR != waveOutOpen(0, 0, pwfx, 0, 0,WAVE_FORMAT_QUERY))
{
//fprintf(stderr, "不支持文件格式!\n");
return FALSE;
}

if ( MMSYSERR_NOERROR != waveOutOpen(phwo, WAVE_MAPPER,pwfx, 0, 0,CALLBACK_NULL))
{
//fprintf(stderr, "文件打开失败!\n");
return FALSE;
}
return TRUE;
}

LPSTR loadOutAudioBlock(const char*filename, DWORD*blockSize)
{
HANDLE hFile= INVALID_HANDLE_VALUE;
DWORD size = 0;
DWORD readBytes = 0;
void* block = NULL;
if((hFile =CreateFile(filename,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL))== INVALID_HANDLE_VALUE)
{
return NULL;
}
do 
{
if((size = GetFileSize(hFile, NULL)) ==0)
{
break;
}
if((block = HeapAlloc(GetProcessHeap(),0, size)) ==NULL)
{
break;
}
ReadFile(hFile, block, size,&readBytes,NULL);
} while(0);
CloseHandle(hFile);
*blockSize = size;
return (LPSTR)block;
}

BOOL writeOutAudioBlock(HWAVEOUT phwo, LPSTR block, DWORDsize)
{
WAVEHDR wHdr = { 0 };
wHdr.dwBufferLength = size;
wHdr.lpData = block;
if (MMSYSERR_NOERROR != waveOutPrepareHeader(phwo,&wHdr, sizeof(WAVEHDR)))
{
//fprintf(stderr, "文件缓冲区准备失败!\n");
return FALSE;
}


if (MMSYSERR_NOERROR != waveOutWrite(phwo, &wHdr,sizeof(WAVEHDR)) )
{
//fprintf(stderr, "写文件缓冲区失败!\n");
return FALSE;
}
Sleep(500);

while ( WAVERR_STILLPLAYING == waveOutUnprepareHeader(phwo,&wHdr, sizeof(WAVEHDR)))
{
Sleep(100);
}
return TRUE;
}

BOOL closeOutAudioBlock(HWAVEOUT phwo)
{
if (MMSYSERR_NOERROR != waveOutClose(phwo))
{
//fprintf(stderr, "写文件缓冲区失败!\n");
return FALSE;
}
return TRUE;
}

这里贴出main函数所在的文件: demoPlayPcm.cpp
#include "PlayPcm.h"

int main(int argc, char* argv[])
{
const char cFileName[] = "MyRecord.pcm";
LPSTR block;
DWORD blockSize;
if (NULL == (block = loadOutAudioBlock( cFileName ,&blockSize)))
{
fprintf(stderr, "文件初始化失败!\n");
return -1;
}
if (FALSE == initOutDrive( cFileName))
{
fprintf(stderr, "设备初始化失败!\n");
return -1;
}

WAVEFORMATEX pwfx ={WAVE_FORMAT_PCM,//wFormatTag,格式标志
1, // nChannels,通道数,单声道数据用单通道,立体声通道用双通道
16000, // nSamplesPerSec,采样率(HZ),每秒钟采取样本的次数
32000, // nAvgBytesPerSec,每秒转换数据的字节数,forWAVE_FORMAT_PCM,nAvgBytesPerSec = nSamplesPerSec *nBlockAlign,此处的大小与waveInOopen回调函数中写入数据的大小应该一直,否则会出现问题
2, // nBlockAlign,每个样本的字节数,for WAVE_FORMAT_PCM,nBlockAlign= (nChannels × wBitsPerSample) / 8
16, // wBitsPerSample,每个样本的位数,forWAVE_FORMAT_PCM,wBitsPerSample必须等于8或者16
0 // cbSize,附加在该结构体后面的格式信息的大小
};


HWAVEOUT phwo;
if ( FALSE == openOutPcm(&phwo, &pwfx,NULL))
{
fprintf(stderr, "文件打开失败!\n");
return -1;
}

if (FALSE == writeOutAudioBlock(phwo, block,blockSize))
{
fprintf(stderr, "写音频设备失败!\n");
return -1;
}
if ( FALSE == closeOutAudioBlock(phwo))
{
fprintf(stderr, "关闭音频设备失败!\n");
return -1;
}
return 0;
}

这个代码是完全可以用的,但是大家注意,我这是pcm文件名为“MyRecord.pcm”,并且是16K16bit的PCM文件,你在使用时注意这点。把文件名换成你的pcm文件名。然后如果要打开其他格式的pcm,那么就在demoPlayPcm.cpp中设置相应的格式信息(注意设置成你需要的采样信息):
WAVEFORMATEX pwfx ={WAVE_FORMAT_PCM,//wFormatTag,格式标志
1, // nChannels,通道数,单声道数据用单通道,立体声通道用双通道
16000, // nSamplesPerSec,采样率(HZ),每秒钟采取样本的次数
32000, // nAvgBytesPerSec,每秒转换数据的字节数,forWAVE_FORMAT_PCM,nAvgBytesPerSec = nSamplesPerSec *nBlockAlign,此处的大小与waveInOopen回调函数中写入数据的大小应该一直,否则会出现问题
2, // nBlockAlign,每个样本的字节数,for WAVE_FORMAT_PCM,nBlockAlign= (nChannels × wBitsPerSample) / 8
16, // wBitsPerSample,每个样本的位数,forWAVE_FORMAT_PCM,wBitsPerSample必须等于8或者16
0 // cbSize,附加在该结构体后面的格式信息的大小
};
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值