人发出的是声波,而计算机是以数字形式工作的,所以计算机有必要提供一种将声音转换为数字,再把数字转换为声音的机制。
最常见的不压缩数据的方法被称为“脉冲编码调制”(pluse code modulation,PCM)
脉冲编码调制以固定周期频率对波形进行采样,通常每秒几万次。对于每个样品,需要测量波形的幅值,这样分割的很细的话,得到的很多个点再绘制起来就可以还原原来的波形了。模数转换器(analog-digital conventer,ADC)负责将振幅转换为数字,同样数模转换器(digital-to-analog,DAC)负责将数字转换为电子波形。
又由于播放出来的声音和之前输入的声音并不完全相同,因为这样产生的波形在高频部分具有尖锐的边缘,出于这个原因,播放的硬件往往在数模转换器之后再连接一个低通滤波器,该滤波器能剔除高频率,产生平滑的波形,在输入端,低通滤波器防止在ADC之前。
脉冲编码调制有两个参数:采样率(即每秒测量波形幅值的次数)和采样大小(即存储幅值水平的位数),然而也并不是采样率越高,采样大小越大越好,因为那种情况可能超越人类感知的范围了。对于采样大小windows支持8位和16位,分别表示一个字节和两个字节。如果要计算未压缩音频所需空间,只要将声音的长度秒数乘以采样率即可。
下面是函数生成人体感知的波形数据,然后写入波形音频硬件播放:
1.先开辟两块波形数据缓存指针(PBYTE)和两块波形数据头部(PWAVEHDR),用于向波形音频输出硬件写入
2,定义波形音频的格式,指定那个音频格式,声道类型,采样率,采样大小等等
3,waveOutOpen函数打开波形音频播放硬件,该函数可得到波形音频播放硬件句柄,
4,将波形数据缓存和波形数据头部联系起来,同时调用waveOutPrepareHeader准备好数据头部,目的是在写入播放硬件时,只要先获得了波形数据,就可以只传数据头部即可
5.调用函数生成波形音频数据,填充缓存,waveOutWrite传入波形数据头部将数据写入波形音频输出硬件,即可播放。
6,释放开辟的内存
sinewave.c
#include<windows.h>
#include<math.h>
#include "resource.h"
#define SAMPLE_RATE 11025
#define FREQ_MIN 20
#define FREQ_MAX 5000
#define FREQ_INIT 440
#define OUT_BUFFER_SIZE 4096
#define PI 3.14159
BOOL CALLBACK DlgProc(HWNF,UINT,WPARAM,LPARAM);
TCHAR szAppName[]=TEXT("SineWave");
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
PSTR szCmdLine,int iCmdShow)
{
if(-1==DialogBox(hInstance,szAppName,NULL,DlgProc)) //由单纯的窗口变成了对话框
{
MessageBox(NULL,TEXT("unknow error occurs"),
szAppName,MB_ICONERROR);
}
return 0;
}
void FillBuffer(PBYTE pBuffer,int iFreq)
{
static double fAngle;
int i;
for(i=0;i<OUT_BUFFER_SIZE;i++)
{
pBuffer[i]=(BYTE)(127+127*sin(fAngle));
fAngle+=2*PI*iFreq/SAMPLE_RATE;
if(fAngle>2*PI)
fAngle-=2*PI;
}
}
BOOL CALLBACK DlgProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
static BOOL bShutOff,bClosing;
static HWAVEOUT hWaveOut;
static HWND hwndScroll;
static int iFreq=FREQ_INIT;
static PBYTE pBuffer1,pBuffer2;
static PWAVEHDR pWaveHdr1,pWaveHdr2;
static WAVEFORMATEX waveformat;
int iDummy;
switch(message)
{
case WM_INITDIALOG:
hwndScroll=GetDlgItem(hwnd,IDC_SCROLL);
SetScrollRange(hwndScroll,SB_CTL,FREQ_MIN,FREQ_MAX,FALSE);
SetScrollPos(hwndScroll,SB_CTL,FREQ_INIT,TRUE);
SetDlgItemInt(hwnd,IDC_TEXT,FREQ_INIT,FALSE);
return TRUE;
case WM_HSCROLL: //-----------焦点在横行滚动条上的消息
switch(LOWORD(wParam))
{
case SB_LINELEFT: iFreq-=1;break;
case SB_LINERIGHT: iFreq+=1;break;
case SB_PAGELEFT: iFreq/=2;break;
case SB_PAGERIGHT: iFreq*=2;break;
case SB_THUMBTRACK:
iFreq=HIWORD(wParam);
break;
case SB_TOP:
GetScrollRange(hwndScroll,SB_CTL,&iFreq,&iDummy);
break;
}
iFreq=max(FREQ_MIN,min(FREQ_MAX,iFreq));
SetScrollPos(hwndScroll,SB_CTL,iFreq,TRUE);
SetDlgItemInt(hwnd,IDC_TEXT,iFreq,FALSE);
return TRUE;
case WM_COMMAND: //此处接收来自对话框各按钮的消息
switch(LOWORD(wParam))
{
case IDC_ONOFF:
//if turning on waveform,hwaveout is null
if(hWaveOut==NULL)
{
//allocate memory for 2 headers and 2 buffers
pWaveHdr1=malloc(sizeof(WAVEHDR));
pWaveHdr2=malloc(sizeof(WAVEHDR));
pBuffer1=malloc(OUT_BUFFER_SIZE);
pBuffer2=malloc(OUT_BUFFER_SIZE);
if(!pWaveHdr1 || !pWaveHdr2 ||!pBuffer1 ||!pBuffer2) //保证内存都分配成功
{
if(!pWaveHdr1) free(pWaveHdr1);
if(!pWaveHdr2) free(pWaveHdr2);
if(!pBuffer1) free(pBuffer1);
if(!pBuffer2) free(pBuffer2);
MessageBeep(MB_ICONEXCLAMATION);
MessageBox(hwnd,TEXT("ERROR allocating memory"),
szAppName,MB_ICONEXCLAMATION |MB_OK);
return TRUE;
}
//variable to indicate off button pressed
bShutOff=FALSE;
//定义波形音频的格式
waveformat.wFormatTag=WAVE_FORMAT_PCM;
waveformat.nChannels=1;
waveformat.nSamplesPerSec=SAMPLE_RATE;
waveformat.nAvgBytesPerSec=SAMPLE_RATE;
waveformat.nBlockAlign=1;
waveformat.wBitsPerSample=8;
waveformat.cbSize=0;
//open waveform audio for ouput
//hWaveOut 用来获取音频输出设备的句柄,
//就会产生MM_WOM_OPEN
if(waveOutOpen(&hWaveOut,WAVE_MAPPER,&waveformat,
(DWORD)hwnd,0,CALLBACK_WINDOW) //(DWORD)hwnd 定义回调机制,CALL_WINDOW定义回调机制如何被解释
!=MMSYSERR_NOERROR)
{
free(pWaveHdr1);
free(pWaveHdr2);
free(pBuffer1);
free(pBuffer2);
hWaveOut=NULL;
MessageBeep(MB_ICONEXCLAMATION);
MessageBox(hwnd,
TEXT("error opening waveform audio device"),
szAppName,MB_ICONEXCLAMATION |MB_OK);
return TRUE;
}
//set up header1 and prepare them
//该结构是为了通过api向波形音频设备写入数据
pWaveHdr1->lpData=pBuffer1;
pWaveHdr1->dwBufferLength=OUT_BUFFER_SIZE;
pWaveHdr1->dwBytesRecorded=0;
pWaveHdr1->dwUser=0;
pWaveHdr1->dwFlags=0;
pWaveHdr1->dwLoops=1;
pWaveHdr1->lpNext=NULL;
pWaveHdr1->reserved=0;
//准备工作,防止wavehdr和缓冲区从内存被交换到磁盘上去了
waveOutPrepareHeader(hWaveOut,pWaveHdr1,sizeof(WAVEHDR));
pWaveHdr2->lpData=pBuffer2;
pWaveHdr2->dwBufferLength=OUT_BUFFER_SIZE;
pWaveHdr2->dwBytesRecorded=0;
pWaveHdr2->dwUser=0;
pWaveHdr2->dwFlags=0;
pWaveHdr2->dwLoops=1;
pWaveHdr2->lpNext=NULL;
pWaveHdr2->reserved=0;
waveOutPrepareHeader(hWaveOut,pWaveHdr2,sizeof(WAVEHDR));
}
else
{
//if turning off waveform ,reset waveform audio
bShutOff=TRUE;
waveOutReset(hWaveOut);
}
return TRUE;
}
break;
//message generated from waveoutopen call
case MM_WOM_OPEN:
SetDlgItemText(hwnd,IDC_ONOFF,TEXT("Turn Off"));
//send two buffer waveform output device
//数据写入到波形输出硬件,真正开始播放声音
FillBuffer(pBuffer1,iFreq); //注意pBuffer是指针
waveOutWrite(hWaveOut,pWaveHdr1,sizeof(WAVEHDR));
FillBuffer(pBuffer2,iFreq);
waveOutWrite(hWaveOut,pWaveHdr2,sizeof(WAVEHDR));
return TRUE;
//message generated when a buffer is finished
case MM_WOM_DONE:
if(bShutOff)
{
waveOutClose(hWaveOut); //又产生MM_WOM_CLOSE
return TRUE;
}
//fill and send out a new buffer
FillBuffer(((PWAVEHDR)lParam)->lpData,iFreq);
waveOutWrite(hWaveOut,(PWAVEHDR)lParam,sizeof(WAVEHDR));
return TRUE;
case MM_WOM_CLOSE:
//释放准备
waveOutUnprepareHeader(hWaveOut,pWaveHdr1,sizeof(WAVEHDR));
waveOutUnprepareHeader(hWaveOut,pWaveHdr2,sizeof(WAVEHDR));
free(pWaveHdr1);
free(pWaveHdr2);
free(pBuffer1);
free(pBuffer2);
hWaveOut=NULL;
SetDlgItemText(hwnd,IDC_ONOFF,TEXT("Turn On"));
if(bClosing)
EndDialog(hwnd,0);
return TRUE;
case WM_SYSCOMMAND:
switch(wParam)
{
case SC_CLOSE:
if(hWaveOut!=NULL)
{
bShutOff=TRUE;
bClosing=TRUE;
waveOutReset(hWaveOut);
}
else
EndDialog(hwnd,0);
}
break;
}
return FALSE;
}
resource.h
//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by SineWave.rc
//
#define IDC_STATIC -1
#define IDC_SCROLL 1000
#define IDC_TEXT 1001
#define IDC_ONOFF 1002
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1003
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
新建一个resource.rc