虽然waveout已经过时,但是其api简单,有些时候也还是需要用到。

其实还是自己上msdn查阅相应api最靠谱,waveout也有提供暂停、设置音量等接口的,这里给个链接,需要的可以自己查找:

https://msdn.microsoft.com/en-us/library/windows/desktop/dd743834(v=vs.85).aspx

waveout播放音频流程:

  • 初始化设备并获得句柄,调用waveOutOpen
  • 初始化WAVEHDR结构体,包含了要播放的音频数据,调用waveOutPrepareHeader
  • 播放WAVEHDR指定的数据,调用waveOutWrite
  • 播放结束,调用waveOutClose

函数介绍:

1.waveOutOpen,初始化waveout,指定音频的格式、回调方式等

MMRESULT waveOutOpen(
LPHWAVEOUT phwo,
UINT_PTR uDeviceID,
LPWAVEFORMATEX pwfx,
DWORD_PTR dwCallback,
DWORD_PTR dwCallbackInstance,
DWORD fdwOpen
);

phwo是返回的waveOut的句柄,后面的waveOutWrite等函数都需要传入该句柄。

uDeviceID是播放设备的ID,不知道是什么就填WAVE_MAPPER,系统自动选择。
    pwfx传入一个WAVEFORMATEX结构体,该结构体包含了待播放音频的格式(采样率、声道数等)。

dwCallback表示回调方式,因为在调用waveOutWrite之后,系统会在另外一个线程中播放所传入的音频数据,当系统播放完这一段音频后会通过回调的方式来通知我们进行下一步操作。该值可以是:一个waveOutProc回调函数;一个窗口的句柄;一个线程的ID;一个EVENT句柄;NULL。本次我使用的EVENT句柄,网上的代码都是使用回调函数的方式,其实使用EVENT要更方便。

dwCallbackInstance用于在回调之间传递数据,比如说向回调函数传递waveOut的句柄。

fdwOpen标志,一般用于表示dwCallback的类型,比如CALLBACK_FUNCTION表示dwCallback是回调函数、CALLBACK_EVENT表示dwCallback是EVEN句柄。

返回值:返回MMSYSERR_NOERROR表示成功,其他表示失败。

2.waveOutPrepareHeader,初始化一个WAVEHDR结构体

MMRESULT waveOutPrepareHeader(
HWAVEOUT hwo,
LPWAVEHDR pwh,
UINT cbwh
);

hwo为waveOutOpen返回的的句柄。
    pwh传入一个WAVEHDR结构体,包括了要播放的音频数据以及相应的一些信息。在调用该函数之前需要设置dwFlags(填0),dwBufferLength(待播放音频数据的长度),lpData(待播放的音频数据)这三个字段。需要注意的是:要确保系统在播放这一段音频的过程中该结构体有效并且不要有改动;音频数据的缓存由自己申请,并且在调用播放函数后系统不会对其进行拷贝,所以在此过程中也不要对该缓存进行改动;在释放lpData的内存前需要调用waveOutUnprepareHeader。

cbwh填sizeof(WAVEHDR)即可。

3.waveOutWrite,播放WAVEHDR中指定的音频数据

MMRESULT waveOutWrite(
HWAVEOUT hwo,
LPWAVEHDR pwh,
UINT cbwh
);

hwo为waveOutOpen返回的的句柄。

pwh传入使用waveOutPrepareHeader初始化过的WAVEHEDR结构体。
    cbwh填sizeof(WAVEHDR)即可。

播放的同步:

由于系统播放时是在另一个线程中执行,所以需要用到线程同步相关知识,上面所提到的“回调”就是解决这个问题。本次使用的是EVENT。

EVENT有两种状态:激活、未激活。调用WaitForSingleObject(event, INFINITE)会阻塞进程直至event变为激活状态;调用SetEvent(event)可设置event为激活状态;调用ResetEvent(event)可设置event为未激活状态。当waveout播放完成之后,系统会将我们在waveOutOpen中指定的EVENT置为激活状态

了解event的机制之后,我们可以这样:①设置event为未激活状态;②调用waveOutWrite播放指定音频;③调用WaitForSingleObject等待event被激活(等待播放完成);④回到第“②”步,如此循环。

避免卡顿:

在播放的时候有很重要的一点:播放的缓存至少需要两个。因为在调用waveOutWirite后系统内核会将其加入“播放队列”,与此同时,还有一个播放线程依次从该队列取出数据并播放,并且每播放完一个节点就会调用上面所说的“回调”。只要保持“播放队列”里面至少有两个节点就不会造成卡顿。

因此,我们只需要在开始播放时调用两次waveOutWrite,然后在“回调”中调用一次waveOutWrite。这样也就保持“播放队列”中(几乎)始终会有两个节点。

为此,我设计了一个类WaveOut,下面是完整代码:

 #include <windows.h>
#pragma comment(lib, "winmm.lib") #define MAX_BUFFER_SIZE (1024 * 8 * 16) class WaveOut
{
private:
HANDLE hEventPlay;
HWAVEOUT hWaveOut;
WAVEHDR wvHeader[];
CHAR* bufCaching;
INT bufUsed;
INT iCurPlaying; /* index of current playing in 'wvHeader'. */
BOOL hasBegan;
public:
WaveOut();
~WaveOut();
int open(DWORD nSamplesPerSec, WORD wBitsPerSample, WORD nChannels);
void close();
int push(const CHAR* buf, int size); /* push buffer into 'bufCaching', if fulled, play it. */
int flush(); /* play the buffer in 'bufCaching'. */
private:
int play(const CHAR* buf, int size);
}; WaveOut::WaveOut() : hWaveOut(NULL)
{
wvHeader[].dwFlags = ;
wvHeader[].dwFlags = ;
wvHeader[].lpData = (CHAR*)malloc(MAX_BUFFER_SIZE);
wvHeader[].lpData = (CHAR*)malloc(MAX_BUFFER_SIZE);
wvHeader[].dwBufferLength = MAX_BUFFER_SIZE;
wvHeader[].dwBufferLength = MAX_BUFFER_SIZE; bufCaching = (CHAR*)malloc(MAX_BUFFER_SIZE);
hEventPlay = CreateEvent(NULL, FALSE, FALSE, NULL);
}
WaveOut::~WaveOut()
{
close();
free(wvHeader[].lpData);
free(wvHeader[].lpData);
free(bufCaching);
CloseHandle(hEventPlay);
}
int WaveOut::open(DWORD nSamplesPerSec, WORD wBitsPerSample, WORD nChannels)
{
WAVEFORMATEX wfx; if (!bufCaching || !hEventPlay || !wvHeader[].lpData || !wvHeader[].lpData)
{
return -;
} wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = nChannels;
wfx.nSamplesPerSec = nSamplesPerSec;
wfx.wBitsPerSample = wBitsPerSample;
wfx.cbSize = ;
wfx.nBlockAlign = wfx.wBitsPerSample * wfx.nChannels / ;
wfx.nAvgBytesPerSec = wfx.nChannels * wfx.nSamplesPerSec * wfx.wBitsPerSample / ; /* queries the device if it supports the given format.*/
// if (waveOutOpen(NULL, 0, &wfx, NULL, NULL, WAVE_FORMAT_QUERY))
// {
// return -1;
// }
/* 'waveOutOpen' will call 'SetEvent'. */
if (waveOutOpen(&hWaveOut, WAVE_MAPPER, &wfx, (DWORD_PTR)hEventPlay, , CALLBACK_EVENT))
{
return -;
} waveOutPrepareHeader(hWaveOut, &wvHeader[], sizeof(WAVEHDR));
waveOutPrepareHeader(hWaveOut, &wvHeader[], sizeof(WAVEHDR)); if (!(wvHeader[].dwFlags & WHDR_PREPARED) || !(wvHeader[].dwFlags & WHDR_PREPARED))
{
return -;
} bufUsed = ;
iCurPlaying = ;
hasBegan = ; return ;
}
void WaveOut::close()
{
waveOutUnprepareHeader(hWaveOut, &wvHeader[], sizeof(WAVEHDR));
waveOutUnprepareHeader(hWaveOut, &wvHeader[], sizeof(WAVEHDR));
waveOutClose(hWaveOut);
hWaveOut = NULL;
}
int WaveOut::push(const CHAR* buf, int size)
{
again:
if (bufUsed + size < MAX_BUFFER_SIZE)
{
memcpy(bufCaching + bufUsed, buf, size);
bufUsed += size;
}
else
{
memcpy(bufCaching + bufUsed, buf, MAX_BUFFER_SIZE - bufUsed); if (!hasBegan)
{
if ( == iCurPlaying)
{
memcpy(wvHeader[].lpData, bufCaching, MAX_BUFFER_SIZE);
iCurPlaying = ;
}
else
{
ResetEvent(hEventPlay);
memcpy(wvHeader[].lpData, bufCaching, MAX_BUFFER_SIZE); waveOutWrite(hWaveOut, &wvHeader[], sizeof(WAVEHDR));
waveOutWrite(hWaveOut, &wvHeader[], sizeof(WAVEHDR)); hasBegan = ;
iCurPlaying = ;
}
}
else if (play(bufCaching, MAX_BUFFER_SIZE) < )
{
return -;
} size -= MAX_BUFFER_SIZE - bufUsed;
buf += MAX_BUFFER_SIZE - bufUsed;
bufUsed = ; if (size > ) goto again;
}
return ;
}
int WaveOut::flush()
{
if (bufUsed > && play(bufCaching, bufUsed) < )
{
return -;
}
return ;
}
int WaveOut::play(const CHAR* buf, int size)
{
WaitForSingleObject(hEventPlay, INFINITE); wvHeader[iCurPlaying].dwBufferLength = size;
memcpy(wvHeader[iCurPlaying].lpData, buf, size); if (waveOutWrite(hWaveOut, &wvHeader[iCurPlaying], sizeof(WAVEHDR)))
{
SetEvent(hEventPlay);
return -;
}
iCurPlaying = !iCurPlaying; return ;
}

waveout.h

主函数,从WAV文件读取其采样率、采样位深、声道数并播放该WAV:

 #include <iostream>
#include <fstream>
#include "waveout.h" int main(int argc, char *argv[])
{
char buffer[ * ];
int nRead;
unsigned short channels = ;
unsigned long sampleRate = ;
unsigned short bitsPerSample = ;
std::ifstream ifile("D:\\record\\blow.wav", std::ifstream::binary);
WaveOut wvOut; if (!ifile)
{
std::cout << "failed to open file.\n";
return ;
} ifile.seekg();
ifile.read((char*)&channels, );
ifile.seekg();
ifile.read((char*)&sampleRate, );
ifile.seekg();
ifile.read((char*)&bitsPerSample, );
ifile.seekg(); std::cout << "sample rate: " << sampleRate
<< ", channels: " << channels
<< ", bits per sample: " << bitsPerSample << std::endl; if (wvOut.open(sampleRate, bitsPerSample, channels) < )
{
std::cout << "waveout open failed.\n";
return ;
} while (ifile.read(buffer, sizeof(buffer)))
{
nRead = ifile.gcount();
// std::cout << "read " << nRead << " bytes.\n";
if (wvOut.push(buffer, nRead) < )
std::cout << "play failed.\n";
}
if (wvOut.flush() < )
std::cout << "flush failed\n";
std::cout << "play done.\n"; system("pause");
return ;
}

main.cpp

原创文章,转载请注明。

使用WaveOut API播放WAV音频文件(解决卡顿)的更多相关文章

  1. 用 Qt 的 QAudioOutput 类播放 WAV 音频文件

    用 Qt 的 QAudioOutput 类播放 WAV 音频文件 最近有一个项目,需要同时控制 4 个声卡播放不同的声音,声音文件很简单就是没有任何压缩的 wav 文件. 如果只是播放 wav 文件, ...

  2. S3C2416裸机开发系列19_Fatfs播放录像wav音频文件

    S3C2416裸机开发系列19 Fatfs播放录像wav音频文件 国际象棋男孩    1048272975 多媒体资源,一般都是以文件的形式存储在固化存储器中.Fatfs所支持的fat32为windo ...

  3. Windows Phone 8初学者开发—第20部分:录制Wav音频文件

    原文 Windows Phone 8初学者开发—第20部分:录制Wav音频文件 原文地址:http://channel9.msdn.com/Series/Windows-Phone-8-Develop ...

  4. Windows Phone 8初学者开发—第21部分:永久保存Wav音频文件

    原文 Windows Phone 8初学者开发—第21部分:永久保存Wav音频文件 第21部分:永久保存Wav音频文件 原文地址:http://channel9.msdn.com/Series/Win ...

  5. C语言解析WAV音频文件

    C语言解析WAV音频文件 代码地址: Github : https://github.com/CasterWx/c-wave-master 目录 前言 了解WAV音频文件 什么是二进制文件 WAV的二 ...

  6. 解析WAV音频文件----》生成WAV音频文件头

    前言:请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i WAV音频文件介绍: WAV文件是在PC机平台上很常见的.最经典的多媒体音频文件,最早于1991年8月出现在Windows3.1操作系统 ...

  7. iOS从零开始学习直播之音频1.播放本地音频文件

      现在直播越来越火,俨然已经成为了下一个红海.作为一个资深码农(我只喜欢这样称呼自己,不喜欢别人这样称呼我),我必须赶上时代的潮流,开始研究视频直播.发现视屏直播类的文章上来就讲拉流.推流.采集.美 ...

  8. AVAudioPlayer播放在线音频文件

    AVAudioPlayer播放在线音频文件 一:原里: AVAudioPlayer是不支持播放在线音频的,但是AVAudioPlayer有一个 initWithData的方法:我们可以把在线音频转换为 ...

  9. Qt ------ WAV 音频文件播放

    1.用 QFile 打开 WAV 文件,读出文件头信息,看看是否符合音频播放设备的要求 QAudioDeviceInfo m_audioOutputDevice;//可以获取音频输出设备的信息,比如哪 ...

随机推荐

  1. LeetCode 69 题

    1.题目要求 实现 int sqrt(int x) 函数. 计算并返回 x 的平方根,其中 x 是非负整数. 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去. 示例 1: 输入: 4 输出 ...

  2. 【转】深入理解Android中的SharedPreferences

    SharedPreferences作为Android中数据存储方式的一种,我们经常会用到,它适合用来保存那些少量的数据,特别是键值对数据,比如配置信息,登录信息等.不过要想做到正确使用SharedPr ...

  3. python绘图工具包 matplotlib 中文乱码问题

    环境: python2.7 windows 8.1 解决: 改配置文件,把字体改为支持中文的字体. 找到python安装目录下的 \Lib\site-packages\matplotlib\mpl-d ...

  4. Docker容器相关技术

    docker需要依赖的Linux内核特性:(1)Namespaces 命名空间PID(Process ID) 用来隔离进程NET(Network) 管理网络接口IPC(InterProcess com ...

  5. lavarel功能总结

    详细可参见笔记:laraval学习笔记(二) 路由 route 绑定模型,绑定参数 模版 blade .blade.php后缀,有laravel自己的模版语法 模型 model 如果用create创建 ...

  6. 在Github上删除一个项目

    最近在Github上浏览,不小心fork了一个项目.想删除,现在记录下来. 1.点击选择fork的项目,以gubai为例 2.进入后,点击Settings 3.进入页面后,点击Delete this ...

  7. 计数器:counter

    组成:2属性,1方法 属性1: counter-reset 命名 属性2: counter-increment 启动/自增 方法 : counter()/counters() 调用方法 1.计数器 命 ...

  8. c语言 错误记录

    1.预处理错误 #include <>   //系统内部的 #include ""   // 自定义的 遇到 not find------解决方案:gcc -I 跟查找 ...

  9. vue :class 可以接收 字符串 数组 和 对象 对象里面的key值 根据true或false 显示不显示

    vue :class 可以接收 字符串 数组 和 对象 对象里面的key值 根据true或false 显示不显示 https://cn.vuejs.org/v2/guide/class-and-sty ...

  10. js 时间戳 随机数 new Date().getTime()

    一:时间转时间戳:javascript获得时间戳的方法有四种,都是通过实例化时间对象 new Date() 来进一步获取当前的时间戳 1.var timestamp1 = Date.parse(new ...