使用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;//可以获取音频输出设备的信息,比如哪 ...
随机推荐
- windows下常用的一些shell命令
看的视频上都是linux系统的shell命令,和windows区别很多.所以整理了windows常用的一些shell命令. 注意:并不是每个都试验过,使用时还需自己验证下. 学system和os,su ...
- poj3436 Computer Factory
题意: 电脑公司生产电脑有N个机器,每个机器单位时间产量为Qi. 电脑由P个部件组成,每个机器工作时只能把有某些部件的半成品电脑(或什么都没有的空电脑)变成有另一些部件的半成品电脑或完整电脑(也可能移 ...
- Java&Xml教程(十一)JAXB实现XML与Java对象转换
JAXB是Java Architecture for XML Binding的缩写,用于在Java类与XML之间建立映射,能够帮助开发者很方便的將XML和Java对象进行相互转换. 本文以一个简单的例 ...
- 善用Object.defineProperty巧妙找到修改某个变量的准确代码位置
我今天的工作又遇到一个难题.前端UI右下角这个按钮被设置为"禁用(disabled)"状态. 这个按钮的可用状态由属性enabled控制.我通过调试发现,一旦下图第88行代码执行完 ...
- 手把手教你免费把网站IP换成1.1.1.1/1.0.0.1
近日,Cloudflare官方发文,与APNIC官方合作打算用IP1.1.1.1推出速度更快.私密性更强的DNS Cloudflare 运行全球规模最大.速度最快的网络之一. APNIC 是一个非营利 ...
- 在.vue文件中让html代码自动补全的方法(支持vscode)
在.vue文件中让html代码自动补全的方法(支持vscode) https://blog.csdn.net/qq_36529459/article/details/79196763 "fi ...
- 基于VueJS的render渲染函数结合自定义组件打造一款非常强大的IView 的Table
基于VueJS的render渲染函数结合自定义组件打造一款非常强大的IView 的Table https://segmentfault.com/a/1190000015970367
- Python基础3 函数 变量 递归 -DAY3
本节内容 1. 函数基本语法及特性 2. 参数与局部变量 3. 返回值 嵌套函数 4.递归 5.匿名函数 6.函数式编程介绍 7.高阶函数 8.内置函数 温故知新 1. 集合 主要作用: 去重 关系测 ...
- python基础一 day9 函数升阶(1)
函数 可读性强 复用性强def 函数名(): 函数体 return 返回值所有的函数 只定义不调用就一定不执行 先定义后调用 函数名() #不接收返回值返回值 = 函数名() #接收返回值 返回值 没 ...
- 数据库_11_1~10总结回顾+奇怪的NULL
校对集问题: 比较规则:_bin,_cs,_ci利用排序(order by) 另外两种登录方式: 奇怪的NULL: NULL的特殊性: