虽然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. android开发学习 ------- 【转】Genymotion 小白安装

    参考 https://www.cnblogs.com/whycxb/p/6850454.html 很详细,全面

  2. 【学习笔记】Sass入门指南

    本文将介绍Sass的一些基本概念,比如说“变量”.“混合参数”.“嵌套”和“选择器继承”等.著作权归作者所有. 什么是Sass? Sass是一门非常优秀的CSS预处语言,他是由Hampton Catl ...

  3. ag-grid-vue的 行默认选中

    that.$nextTick(() => { that.gridListOptions.api.onGroupExpandedOrCollapsed(); that.$nextTick(() = ...

  4. WARN警告:Going to buffer response body of large or unknown size. Using getResponseBodyAsStream instead is recommended

    使用Apache HttpClient发送请求,有大量WARN警告:Going to buffer response body of large or unknown size. Using getR ...

  5. java中异常处理finally和return语句的执行顺序

    finally代码块的语句在return之前一定会得到执行 如果try块中有return语句,finally代码块没有return语句,那么try块中的return语句在返回之前会先将要返回的值保存, ...

  6. leetcode_268.missing number

    给定一个数组nums,其中包含0--n中的n个数,找到数组中没有出现的那个数. 解法一:cyclic swapping algorithm class Solution { public: int m ...

  7. how to get many stars on Github?

    some key points: 1: make a beautiful README file2: use some GIF (google some tools to convert videos ...

  8. 骑芯供应链(T 面试)

    1.目前市面上主流的团队开发模式是什么? 正解:DevOps,https://blog.csdn.net/bntX2jSQfEHy7/article/details/79168865 2.你觉得什么是 ...

  9. golang结构体排序 - 根据下载时间重命名本地文件

    喜M拉Y下载音频到手机,使用ximalaya.exe 解密[.x2m]为[.m4a]根据文件下载创建时间,顺序重命名文件,方便后续播放. 源码如下:package main import ( &quo ...

  10. C++ 类中的static成员的初始化和特点

    C++ 类中的static成员的初始化和特点 #include <iostream> using namespace std; class Test { public: Test() : ...