使用WaveOut API播放WAV音频文件(解决卡顿)
虽然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音频文件(解决卡顿)的更多相关文章
- 用 Qt 的 QAudioOutput 类播放 WAV 音频文件
用 Qt 的 QAudioOutput 类播放 WAV 音频文件 最近有一个项目,需要同时控制 4 个声卡播放不同的声音,声音文件很简单就是没有任何压缩的 wav 文件. 如果只是播放 wav 文件, ...
- S3C2416裸机开发系列19_Fatfs播放录像wav音频文件
S3C2416裸机开发系列19 Fatfs播放录像wav音频文件 国际象棋男孩 1048272975 多媒体资源,一般都是以文件的形式存储在固化存储器中.Fatfs所支持的fat32为windo ...
- Windows Phone 8初学者开发—第20部分:录制Wav音频文件
原文 Windows Phone 8初学者开发—第20部分:录制Wav音频文件 原文地址:http://channel9.msdn.com/Series/Windows-Phone-8-Develop ...
- Windows Phone 8初学者开发—第21部分:永久保存Wav音频文件
原文 Windows Phone 8初学者开发—第21部分:永久保存Wav音频文件 第21部分:永久保存Wav音频文件 原文地址:http://channel9.msdn.com/Series/Win ...
- C语言解析WAV音频文件
C语言解析WAV音频文件 代码地址: Github : https://github.com/CasterWx/c-wave-master 目录 前言 了解WAV音频文件 什么是二进制文件 WAV的二 ...
- 解析WAV音频文件----》生成WAV音频文件头
前言:请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i WAV音频文件介绍: WAV文件是在PC机平台上很常见的.最经典的多媒体音频文件,最早于1991年8月出现在Windows3.1操作系统 ...
- iOS从零开始学习直播之音频1.播放本地音频文件
现在直播越来越火,俨然已经成为了下一个红海.作为一个资深码农(我只喜欢这样称呼自己,不喜欢别人这样称呼我),我必须赶上时代的潮流,开始研究视频直播.发现视屏直播类的文章上来就讲拉流.推流.采集.美 ...
- AVAudioPlayer播放在线音频文件
AVAudioPlayer播放在线音频文件 一:原里: AVAudioPlayer是不支持播放在线音频的,但是AVAudioPlayer有一个 initWithData的方法:我们可以把在线音频转换为 ...
- Qt ------ WAV 音频文件播放
1.用 QFile 打开 WAV 文件,读出文件头信息,看看是否符合音频播放设备的要求 QAudioDeviceInfo m_audioOutputDevice;//可以获取音频输出设备的信息,比如哪 ...
随机推荐
- expect下命令不能解析通配符*的问题
曾遇到这样一段代码:(Bash脚本) 1 2 3 4 5 6 7 8 9 10 11 12 #!/usr/bin/expect -f set HOST "192.168.102.1" ...
- Java格式规范及注释的用法
/* 需求:演示一个Hello World的Java小程序 思路: 1.定义一个类.因为Java程序都是定义在类中,Java程序都是以类的形式存在的,类的形式其实就是字节码的最终体现 2.定义一个主函 ...
- SQLite – LIMIT子句
SQLite - LIMIT子句 SQLite LIMIT子句是用来限制SELECT语句返回的数据量. 语法: SELECT语句.LIMIT子句的基本语法如下: SELECT column1, col ...
- Android(java)学习笔记164:开发一个多界面的应用程序之不同界面间互相传递数据(短信助手案例)
1.首先我们看看下面这个需求: 这里我们在A界面上,点击这个按钮"选择要发送的短信",开启B界面上获取网络上各种短信祝福语,然后B界面会把这些网络祝福语短信发送给A界面到" ...
- table、tr、td表格的行、单元格等属性说明
table.tr.td表格的行.单元格等属性说明 <table>标签定义HTML表格.简单的HTML表格由table元素以及一个或多个tr.th或td元素组成. tr元素定义表格行,th元 ...
- 关闭Visual Studio 2015 关闭单击打开文件的功能
工具-->选项-->环境-->选项卡和窗口-->预览选项卡 去掉“在解决方案资源管理器中预览选定的文件(在按住Alt的同时单击可避免预览)(X)”的勾选
- android滚动图片
关于广告轮播,大家肯定不会陌生,它在现手机市场各大APP出现的频率极高,它的优点在于"不占屏",可以仅用小小的固定空位来展示几个甚至几十个广告条,而且动态效果很好,具有很好的用户& ...
- 串的基本操作(KMP算法实现)
#include <iostream> #include <math.h> using namespace std; void StrAssign(char *T) { cha ...
- DBLINK引起的SQL性能问题
最近发现报表系统上有一存储过程越来越慢,在数据库中查询后,发现有以下条SQL --优化前:耗时>1h select c.policyno, c.endorseno, r.item_code, s ...
- python基础知识04-散列类型运算优先级和逻辑运算
散列类型 1.集合 定义集合 se = {1,2,3,4} se = set()定义空集合 se = {1,3,5,7} se2 = {1,3,8,9} se & se2 {1,3} 交集 s ...