虽然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. AJPFX总结方法重载与方法重写的区别

    方法重载在同一个类中,可以出现同名方法,但是这些同名方法的参数列表必须不同,这样定义方法叫做方法重载.方法重载的特点重载的注意事项重载与返回值无关重载与具体的变量标识符无关重载只与方法名与参数相关重载 ...

  2. [BZOJ2705][SDOI2012]Longge的问题 数学

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2705 首先分析得题目所求$gcd(i,N)$的取值只可能是$N$的因子,则有$$Ans=\ ...

  3. Android自定义zxing扫描界面的实现

    首先,我们需要导入zxing的jar文件,其次复制所需要的资源文件以及放入自己需要添加的资源文件,复制出源码的必要相关类文件.对布局文件进行一定的修改,再修改CaptureActivity与Viewf ...

  4. 《Python基础教程》 读书笔记 第五章(上)条件语句

    5.1 print和import的更多信息 5.1.1使用逗号输出 打印多个表达式也是可行的,只要将它们用逗号隔开就好: >>>print'age:',42 age: 42 要同时输 ...

  5. HTTP 方法:GET 对比 POST 转自w3school

    两种最常用的 HTTP 方法是:GET 和 POST. 什么是 HTTP? 超文本传输协议(HTTP)的设计目的是保证客户机与服务器之间的通信. HTTP 的工作方式是客户机与服务器之间的请求-应答协 ...

  6. echarts简单用法快速上手

    1.html结构 简单说就是一个标签一个图表:2.初始化:var myEcharts = echarts.init(document.getElementById("xxx")): ...

  7. Linux 编译升级 FFmpeg 步骤

    如果服务器已经安装了一个 Ffmpeg 的话,比如已安装在 /usr/local/ffmpeg 目录.Linux下版本升级步骤如下: 1.下载 ffmpeg-*.tar.gz到 Ffmpeg 官网 h ...

  8. PyTorch的十七个损失函数

    本文截取自<PyTorch 模型训练实用教程>,获取全文pdf请点击: tensor-yu/PyTorch_Tutorial​github.com 版权声明:本文为博主原创文章,转载请附上 ...

  9. [CF] 950A Left-handers, Right-handers and Ambidexters

    A. Left-handers, Right-handers and Ambidexters time limit per test1 second memory limit per test256 ...

  10. 编写一个微信小程序

    1.创建项目 2.创建目录及文件,结构如下: