本文讲的比较浅,只适合快速入门,更多可以读下面这几篇文章,讲的很深。对音频我也不是很懂,只是把自己知道的写下来,希望有更了解的大家一起探讨。
点击打开链接:基于WaveX低级音频函数的实时语音通信
windows下获取音频的方法很多,据我了解的只有三种,分别是
1、通过高级音频函数、媒体控制接口MCI设备驱动程序
2、低级音频函数,低级音频设备驱动(WaveX API)
3、directsound方法(这个是directX中的方法)
对这三者的主要评价是:MCI方法简单,但是灵活性差,WaveX方法相对来说难一些,但是控制灵活,directsound可以说是最难的,但是是非常灵活的。我在自己的项目里只做了相关WaveX的相关编程,所以讲自己的体会说一下。
WaveX的主要步骤以及涉及的函数分别为
录音过程
1、设置相关参数(WAVEFORMATEX格式设置)
2、查询音频设备(查看是否有相应的音频设备,waveInGetNumDevs,waveInGetDevCaps两个相关函数)
3、打开或关闭设备驱动程序(waveInOpen,waveInClose,waveOutOpen,waveOutClose)
4、分配和准备数据块,为了录音或者放音(waveInPrepareHeader,waveInAddBuffer)
5、开始音频采集(waveInStart)
6、关闭相关设备(waveInStop,waveInReset,waveInClose)
播放过程(与录音过程基本相同)
这里有一个需要注意的是,waveInOpen,waveOutOpen函数参数需要一个回调函数,该回调函数主要负责对采集的数据进行处理,比如说讲缓冲区内的数据存入定义的文件之类的。
回调函数
void CALLBACK waveInProc(HWAVEIN hwi,UINT uMsg,DWORD_PTR dwInstance,DWORD_PTR dwParam1,DWORD_PTR dwParam2);
void CALLBACK waveOutProc(HWAVEOUT hwo,UINT uMsg,DWORD_PTR dwInstance,DWORD_PTR dwParam1,DWORD_PTR dwParam2);
hwi是输入数据的设备,uMsg采集到信息的标志(WM_WIM_OPEN WM_WIM_DATA WM_WIM_CLOSE),后面的分别为用户实例数据,后面的则是两个消息参数(对应回调函数的消息,window下的消息机制),下面是MSDN的解释
hwi:Handle to the waveform-audio device associated with the callback function.
uMsg:Waveform-audio input message. It can be one of the following messages.
dwInstance:User instance data specified with waveInOpen.
dwParam1:Message parameter.
相关低级音频数据的声明和定义都在mmsystem.h头文件和Winmm.lib库中,因为利用了回调函数之类的,所以还需要windows.h
#include <windows.h>
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")
采集声音数据时,需要设置多个缓冲区,如果不设置也可以,只是录取的声音的质量就不会很好了,原理是这样的,可以理解为多缓冲区可以连续快速进行声音采集,并且可以使声音的播放实时化。主要原因是采集声音的时候只要缓冲区满了以后,就会给系统一个消息,程序响应这个消息的话需要一些时间(几毫秒或几十毫秒,测试过如果缓冲区有12个数组,要采集4秒钟的数据(格式:单声道,采样率16000等),差不多会有20毫秒的延迟,不过这个应该和硬件有关系了。)输入和输出的道理一样,这样录音和播放都会很流畅。
1、设置相关参数
WAVEFORMATEX wavform;
wavform.wFormatTag = WAVE_FORMAT_PCM;
wavform.nChannels = 2;
wavform.nSamplesPerSec = 44100;
wavform.nAvgBytesPerSec = 44100*16*2/8;
wavform.nBlockAlign = 4;
wavform.wBitsPerSample = 16;
wavform.cbSize = 0;
这其中,WAVEFORMATEX定义了声音的格式,第一个参数是音频的格式类型,单声道和多声道这里都是PCM(Pulse-code modulation)格式的。第二个参数是声道,如果是2的话,因为着是立体声,数据比单声道多一倍。第三个参数是采样频率,可以这样理解,每秒要采集这么多个数据(样本),下面是每秒的字节数,是可以计算出来的,声道*样本数(采样率)*每个样本的位数/8bit。每个样本的位数(wBitsPerSample),意味着一个样本需要16位表示(16位的二进制数)。nBlockAlign=4表示,每个样本的字节数,这里是2*16/8,两个声道。最后一个是附加信息的字节大小。
2、查询音频设备,waveInGetNumDevs(),返回输入设备的个数。
3、打开或关闭设备驱动
waveInOpen(&hWaveIn, WAVE_MAPPER, &wavform, (DWORD_PTR)waveInProc, 0, CALLBACK_FUNCTION);
waveOutOpen(&hWaveOut, WAVE_MAPPER, &wavform, (DWORD_PTR)waveOutProc, 0, CALLBACK_FUNCTION);
第一个参数是输入设备的句柄,第二个是采集格式,第三个是回调函数,也就是要处理数据的部分,第四个一般为0,第五个是处理函数的方式,这里是回调函数方式,可以设置为其他。
下面是输入和输出的回调函数
//录音回调函数
void CALLBACK waveInProc(HWAVEIN hwi,UINT uMsg,DWORD_PTR dwInstance,DWORD_PTR dwParam1,DWORD_PTR dwParam2)
{
LPWAVEHDR pwh = (LPWAVEHDR)dwParam1;
if ((WIM_DATA==uMsg) && (buf_count<BUFFER_SIZE))
{
int temp = BUFFER_SIZE - buf_count;
temp = (temp>pwh->dwBytesRecorded) ? pwh->dwBytesRecorded : temp;
memcpy(buffer+buf_count, pwh->lpData, temp);
buf_count += temp;
waveInAddBuffer(hwi, pwh, sizeof(WAVEHDR));
}
}
// 放音回调函数
void CALLBACK waveOutProc(HWAVEOUT hwo,UINT uMsg,DWORD_PTR dwInstance,DWORD_PTR dwParam1,DWORD_PTR dwParam2)
{
if (WOM_DONE == uMsg)
{
buf_count = BUFFER_SIZE;
}
}
这个是后面例子的回调函数,录音的时候将数据放入自己定义的大的缓冲区,放音函数则是在数据使用完毕后,将缓冲区指针设置为缓冲区的大小,主要是提示系统放音完毕。
4、分配和准备数据块
waveInPrepareHeader(hWaveIn, &wh[i], sizeof(WAVEHDR));
waveInAddBuffer(hWaveIn, &wh[i], sizeof(WAVEHDR));
这里只讲两个函数,这两个是在分配和准备数据块的时候连续调用的,第一个设置数据头,第二个加入缓冲区。
5、6、开始录音和关闭设备可以往下看代码了。
不过这里,我曾经做过测试,在单一线程内进行录音和播放是行不通的!!本来想这么做的,因为对多线程很头痛,我的设想是这样的,录音几十毫秒,然后播放几十毫秒,理论上是可以的,但是这么做你会发现,很快程序就死掉了,这里我断点发现是因为频繁进行录音和播放设备的开启关闭出现的问题,可能出现系统问题吧。不过如果是单一线程录音一段时间(秒级以上),然后播放应该是会播放顺畅的,但是频繁的做也是不行的。
单一线程还有一个问题,就是你在进行录音的过程中,要调用sleep函数(挂起和录音时间相同的时间,为的是将程序挂起,等待设别录音,播放相同,如果没有sleep这个操作,则会出现问题,录不了声音,程序返回)
下面看代码吧,这是网上可以直接拿过来用的。
#include <windows.h>
#include <stdio.h>
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")
#define BUFFER_SIZE (44100*16*2/8*5) // 录制声音长度
#define FRAGMENT_SIZE 1024 // 缓存区大小
#define FRAGMENT_NUM 4 // 缓存区个数
static unsigned char buffer[BUFFER_SIZE] = {0};
static int buf_count = 0;
// 函数定义
void CALLBACK waveInProc(HWAVEIN hwi,
UINT uMsg,
DWORD_PTR dwInstance,
DWORD_PTR dwParam1,
DWORD_PTR dwParam2 );
void CALLBACK waveOutProc( HWAVEOUT hwo,
UINT uMsg,
DWORD_PTR dwInstance,
DWORD_PTR dwParam1,
DWORD_PTR dwParam2 );
// 入口
int main()
{
/* 录音 */
// Device
int nReturn = waveInGetNumDevs();
printf("输入设备数目:%d\n", nReturn);
for (int i=0; i<nReturn; i++)
{
WAVEINCAPS wic;
waveInGetDevCaps(i, &wic, sizeof(WAVEINCAPS));
printf("#%d\t设备名:%s\n", i, wic.szPname);
}
// open
HWAVEIN hWaveIn;
WAVEFORMATEX wavform;
wavform.wFormatTag = WAVE_FORMAT_PCM;
wavform.nChannels = 2;
wavform.nSamplesPerSec = 44100;
wavform.nAvgBytesPerSec = 44100*16*2/8;
wavform.nBlockAlign = 4;
wavform.wBitsPerSample = 16;
wavform.cbSize = 0;
waveInOpen(&hWaveIn, WAVE_MAPPER, &wavform, (DWORD_PTR)waveInProc, 0, CALLBACK_FUNCTION);
WAVEINCAPS wic;
waveInGetDevCaps((UINT_PTR)hWaveIn, &wic, sizeof(WAVEINCAPS));
printf("打开的输入设备:%s\n", wic.szPname);
// prepare buffer
static WAVEHDR wh[FRAGMENT_NUM];
for (int i=0; i<FRAGMENT_NUM; i++)
{
wh[i].lpData = new char[FRAGMENT_SIZE];
wh[i].dwBufferLength = FRAGMENT_SIZE;
wh[i].dwBytesRecorded = 0;
wh[i].dwUser = NULL;
wh[i].dwFlags = 0;
wh[i].dwLoops = 1;
wh[i].lpNext = NULL;
wh[i].reserved = 0;
waveInPrepareHeader(hWaveIn, &wh[i], sizeof(WAVEHDR));
waveInAddBuffer(hWaveIn, &wh[i], sizeof(WAVEHDR));
}
// record
printf("Start to Record...\n");
buf_count = 0;
waveInStart(hWaveIn);
while (buf_count < BUFFER_SIZE)
{
Sleep(1);
}
printf("Record Over!\n\n");
// clean
waveInStop(hWaveIn);
waveInReset(hWaveIn);
for (int i=0; i<FRAGMENT_NUM; i++)
{
waveInUnprepareHeader(hWaveIn, &wh[i], sizeof(WAVEHDR));
delete wh[i].lpData;
}
waveInClose(hWaveIn);
system("pause");
printf("\n");
/* 放音 */
// Device
nReturn = waveOutGetNumDevs();
printf("\n输出设备数目:%d\n", nReturn);
for (int i=0; i<nReturn; i++)
{
WAVEOUTCAPS woc;
waveOutGetDevCaps(i, &woc, sizeof(WAVEOUTCAPS));
printf("#%d\t设备名:%s\n", i, wic.szPname);
}
// open
HWAVEOUT hWaveOut;
waveOutOpen(&hWaveOut, WAVE_MAPPER, &wavform, (DWORD_PTR)waveOutProc, 0, CALLBACK_FUNCTION);
WAVEOUTCAPS woc;
waveOutGetDevCaps((UINT_PTR)hWaveOut, &woc, sizeof(WAVEOUTCAPS));
printf("打开的输出设备:%s\n", wic.szPname);
// prepare buffer
WAVEHDR wavhdr;
wavhdr.lpData = (LPSTR)buffer;
wavhdr.dwBufferLength = BUFFER_SIZE;
wavhdr.dwFlags = 0;
wavhdr.dwLoops = 0;
waveOutPrepareHeader(hWaveOut, &wavhdr, sizeof(WAVEHDR));
// play
printf("Start to Play...\n");
buf_count = 0;
waveOutWrite(hWaveOut, &wavhdr, sizeof(WAVEHDR));
while (buf_count < BUFFER_SIZE)
{
Sleep(1);
}
// clean
waveOutReset(hWaveOut);
waveOutUnprepareHeader(hWaveOut, &wavhdr, sizeof(WAVEHDR));
waveOutClose(hWaveOut);
printf("Play Over!\n\n");
return 0;
}
// 录音回调函数
void CALLBACK waveInProc(HWAVEIN hwi,
UINT uMsg,
DWORD_PTR dwInstance,
DWORD_PTR dwParam1,
DWORD_PTR dwParam2 )
{
LPWAVEHDR pwh = (LPWAVEHDR)dwParam1;
if ((WIM_DATA==uMsg) && (buf_count<BUFFER_SIZE))
{
int temp = BUFFER_SIZE - buf_count;
temp = (temp>pwh->dwBytesRecorded) ? pwh->dwBytesRecorded : temp;
memcpy(buffer+buf_count, pwh->lpData, temp);
buf_count += temp;
waveInAddBuffer(hwi, pwh, sizeof(WAVEHDR));
}
}
// 放音回调函数
void CALLBACK waveOutProc( HWAVEOUT hwo,
UINT uMsg,
DWORD_PTR dwInstance,
DWORD_PTR dwParam1,
DWORD_PTR dwParam2 )
{
if (WOM_DONE == uMsg)
{
buf_count = BUFFER_SIZE;
}
}
结贴,以后遇到其他继续奉上。