之前在我的博客中有一篇关于编写录音器的代码,可保存为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,附加在该结构体后面的格式信息的大小
};

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

  1. AudioTrack播放pcm格式音频

    AudioTrack播放pcm格式音频 package com.zero.demo;import android.content.Context; import android.media.Audio ...

  2. Java如何播放MP3格式音频文件,以及如何循环播放音频?

    首选,感谢你能在百忙之中阅读我的博客,在这里我告诉大家2中播放MP3格式音频的方法.第一种,使用自带的JavaFX技术来播放(现再最新的JDK版本中已被移除).第二种我们使用第三方库来进行播放. 点击 ...

  3. 【转】PCM Audio,PCM格式音频文件 详解

    PCM文件:模拟音频信号经模数转换(A/D变换)直接形成的二进制序列,该文件没有附加的文件头和文件结束标志.Windows的Convert工具可以把PCM音频格式的文件转换成Microsoft的WAV ...

  4. java播放mp3格式音频文件

    下载第三方jar包,网址:http://www.javazoom.net/javalayer/javalayer.html 下载完成之后解压提取jl1.0.0.1.jar 将jl1.0.0.1.jar ...

  5. Qt播放WAV格式音频文件的两种方法

    这两种方法都需要在.pro文件中加入multimedia模块. 方法一.使用QAudioOutput #include <QApplication> #include <QFile& ...

  6. Java实现PCM格式音频转MP3或WAV

    最近做语音合成的项目,需要把PCM格式的音频文件转换成MP3或WAV,记录一下. Java实现的PCM格式音频文件转换MP3格式 import java.io.FileInputStream; imp ...

  7. Android 音视频开发(一):PCM 格式音频的播放与采集

    什么是 PCM 格式 声音从模拟信号转化为数字信号的技术,经过采样.量化.编码三个过程将模拟信号数字化. 采样 顾名思义,对模拟信号采集样本,该过程是从时间上对信号进行数字化,例如每秒采集 44100 ...

  8. vue.js用benz-amr-recorder实现播放amr格式音频

    vue.js实现播放amr格式音频 安装 引用 实操 html部分 初始化对象 调用方法 预览效果 纯前端解码.播放.录音.编码 AMR 音频,无须服务器支持,基于 [amr.js] 注意:由于使用了 ...

  9. java 播放amr_amr格式转mp3和直接播放amr格式的文件-sunziren

    原创文章,转载请注明出处! 前言: amr作为一种高压缩比的音频格式,受到很多客户的青睐.本文主要涉及两部分的内容,一是amr如何转为mp3格式,二是如何直接播放amr格式的文件. 1. 如何使用Ja ...

最新文章

  1. 深度学习Anchor Boxes原理与实战技术
  2. bug带来的兄弟感情
  3. mfc 制作不同的文档模板mdi不同的子窗体_制作ACK集群自定义节点镜像的正确姿势...
  4. java对象赋值给另一个对象_java面向对象编程
  5. js错误:对象不支持此属性或方法
  6. 【Boost】boost库中智能指针——shared_ptr
  7. vector作为参数传递到dll问题
  8. PyCharm调试错误
  9. The word is not correctly spelled问题解决
  10. Qt使用QPainter实现雷达图(玫瑰图)
  11. java数字处理_Java数字处理类(上)
  12. 实验二 固件设计(小组)
  13. MS08-067漏洞渗透测试
  14. PPT文件太大?如何压缩PPT?这几招教你搞定
  15. 面试中单例模式有几种写法?
  16. 如何使用Redis Streams
  17. 主编推荐 | 深度学习如何影响运筹学?
  18. (android文档原创翻译)管理Activity的生命周期一
  19. 实现百度第三方登陆详细解答
  20. 互联网常用的网络用语

热门文章

  1. JavaScript高级编程语言基础语法
  2. 【企业架构设计实战】业务架构设计
  3. 全志h3芯片刷机包_全志h3详细刷机教程
  4. 可商用字体在哪里找?2022可商用字体汇总
  5. OpenFeign中动态URl、动态传递接口地址
  6. Kademlia算法 理解 总结
  7. 国债是什么?国债逆回购是什么?国债逆回购是低风险高回报的投资方式之一
  8. 基于8255A接口芯片的跑马灯程序
  9. 【贪心算法】Leetcode 714. 买卖股票的最佳时机含手续费
  10. lambda表达式与6种方法引用格式