(很好的资料)MCI和波形(转)

一、波形声音
     波形声音就是通过计算机的声音采集设备将现实世界当中的声波通过模数转化之后用数字的方式表示声音波形的一种声音储存方案。在播放声音的时候声卡将以数字储存的声音波形信号转换为一系列的电流信号。
     声音的采集过程当中涉及到两个术语:采样率表示声音采集设备每秒钟采集声音信号的次数,一般有44.1KHZ、22.05KHZ等等;样化位数表示每次采样得到的声音信号用多少位来表示。在声音的采样当中采样率和样化位数越高声音的质量就越高。
     在windows程序设计当中,波形声音的采集和回放有两种方式来实现:一种是使用一系列底层的函数调用来实现;另一种是使用MCI接口来实现。我们首先介绍底层函数的使用。
1.声音的采集
     声音的采集过程在windows程序设计当中从整体上讲可以分为这么几个部分:首先我们要调用waveInOpen函数获取一个声音输入句柄;在获得句柄以后我们要初始化一个WAVEHDR结构,这个结构用来指示API函数声音储存缓冲的位置和大小以及相关信息,并且调用函waveInPrepareHeader将WAVEHDR与声音输入句柄相关联;在完成这些之后我们需要处理由操作系统发送的MM_WIM_OPEN消息,在这个消息当中我们调用函数waveInStart命令硬件开始采集;在采集的过程当中操作系统根据缓冲区的大小在缓冲区满或者采集过程结束时发送MM_WIM_DATA消息,这个消息的LPARAM参数表示一个WAVEHDR,在这个消息当中我们需要把缓冲区当中的数据转存到某块内存当中;最后就是关闭声音的采集,我们可以调用函数waveInReset并且处理消息MM_WIM_CLOSE,在这个消息的处理当中我们需要调用waveInUnprepareHeader函数释放WAVEHDR与声音输入句柄的关联,并且释放所有的缓冲内存。
     获取声音输入句柄需要调用函数waveInOpen,这个函数的原型如下:
MMRESULT waveInOpen(
  LPHWAVEIN       phwi,            //指向声音输入句柄的指针 
  UINT_PTR       uDeviceID,       //设备ID,使用WAVE_MAPPER可以使用系统默认设备
  LPWAVEFORMATEX pwfx,       //WAVEFORMAT结构
  DWORD_PTR      dwCallback,  //发送MM_WIM_**消息的窗口句柄
  DWORD_PTR      dwCallbackInstance,   //设置为NULL
  DWORD          fdwOpen                     //如果设置窗口句柄需要设置为CALLBACK_WINDOW
);
在调用waveInOpen获取声音输入句柄时我们需要一个WAVEFORMAT结构,这个结构用来确定波形声音的采样率、样化大小等信息。其原型如下:
typedef struct { 
    WORD  wFormatTag;                //格式标志,一般设置为WAVE_FORMAT_PCM
    WORD  nChannels;                   //声道数量
    DWORD nSamplesPerSec;          //采样率
    DWORD nAvgBytesPerSec;        //每秒数据流量,以字节为单位
    WORD  nBlockAlign;                 //每次采样的数据量,以字节为单位
} WAVEFORMAT;
      需要注意的是当我们调用函数waveInReset中止声音采集过程时,操作系统首先向程序发送MM_WIM_DATA消息,在这个消息当中我们要将缓冲区当中剩余的数据保存,然后操作系统向程序发送MM_WIM_CLOSE消息。
2.波形声音的回放
      波形声音的回放从总体上可以分为下面几个部分:调用waveOutOpen函数获取声音输出句柄,这个函数的参数与waveInOpen相同;获取句柄以后我们需要初始化一个WAVEHDR结构,并且调用waveOutPrepareHeader函数将WAVEHDR结构与声音输出句柄关联起来;接着系统发送MM_WIM_OPEN消息,在这个处理这个消息当中我们调用函数waveOutWrite命令硬件回放缓冲区当中的数据;当回放完成时操作系统发送MM_WIM_DONE消息,这时我们只需要调用waveOutReset函数来中止回放,此时操作系统发送消息MM_WIM_CLOSE,在这个消息当中我们关闭关联并释放缓冲区。在波形声音的回放过程当中我们可以调用函数waveOutPause来暂停回放,调用waveOutRestart来恢复播放。
下面是一个使用这些底层函数调用来完成一个录音机的程序:
BOOL CALLBACK DlgProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
 static HWND hWndRecBeg,hWndRecEnd,hWndPlay,hWndEnd;
 static bool RecEnd,PlayEnd,Play,Rec,Pause;
 static HWAVEIN hWaveIn;
 static HWAVEOUT hWaveOut;
 static PWAVEHDR wavehdr1,wavehdr2;
 static LPSTR pSave,pNewBuffer,pBuffer1,pBuffer2;
 static int DateLen;
 WAVEFORMATEX waveformat;
 switch (message)
 {
 case WM_INITDIALOG:
  hWndRecBeg = GetDlgItem(hWnd,ID_RECBEG);
  hWndRecEnd = GetDlgItem(hWnd,ID_RECEND);
  hWndPlay = GetDlgItem(hWnd,ID_PLAY);
  hWndEnd = GetDlgItem(hWnd,ID_STOP);
  EnableWindow(hWndRecEnd,FALSE);
  EnableWindow(hWndPlay,FALSE);
  EnableWindow(hWndEnd,FALSE);
  SetFocus(hWndRecBeg);
  return TRUE;
 case WM_COMMAND:
  switch (LOWORD(wParam))
  {
  case ID_RECBEG:
   if (pSave)
    free(pSave);
   wavehdr1 = (PWAVEHDR) malloc(sizeof (WAVEHDR));
   wavehdr2 = (PWAVEHDR) malloc(sizeof (WAVEHDR));
   pBuffer1 = (LPSTR) malloc(BUFFER_LEN);
   pBuffer2 = (LPSTR) malloc(BUFFER_LEN);
   waveformat.nSamplesPerSec = 22050;  
   waveformat.nChannels = 1;
   waveformat.nBlockAlign = 1;
   waveformat.wBitsPerSample = 8;
   waveformat.nAvgBytesPerSec = 22050;
   waveformat.wFormatTag = WAVE_FORMAT_PCM;
   waveformat.cbSize = 0;
   waveInOpen(&hWaveIn,WAVE_MAPPER,&waveformat,(DWORD)hWnd,0,CALLBACK_WINDOW);

   wavehdr1->lpData = pBuffer1;
   wavehdr1->dwBufferLength = BUFFER_LEN;
   wavehdr1->dwBytesRecorded = 0;
   wavehdr1->dwFlags = 0;
   wavehdr1->dwLoops = 1;
   wavehdr1->dwUser = 0;
   wavehdr1->lpNext = NULL;
   wavehdr1->reserved = 0;
   waveInPrepareHeader(hWaveIn,wavehdr1,sizeof (WAVEHDR));

   wavehdr2->lpData = pBuffer2;
   wavehdr2->dwBufferLength = BUFFER_LEN;
   wavehdr2->dwBytesRecorded = 0;
   wavehdr2->dwFlags = 0;
   wavehdr2->dwLoops = 1;
   wavehdr2->dwUser = 0;
   wavehdr2->lpNext = NULL;
   wavehdr2->reserved = 0;
   waveInPrepareHeader(hWaveIn,wavehdr2,sizeof (WAVEHDR));

   EnableWindow(hWndRecBeg,FALSE);
   EnableWindow(hWndRecEnd,TRUE);
   EnableWindow(hWndPlay,FALSE);
   EnableWindow(hWndEnd,FALSE);
   SetFocus(hWndRecEnd);
   Rec = true;
   return TRUE;
  case ID_RECEND:
   EnableWindow(hWndRecEnd,FALSE);
   EnableWindow(hWndRecBeg,TRUE);
   EnableWindow(hWndPlay,TRUE);
   EnableWindow(hWndEnd,FALSE);
   Rec = false;
   RecEnd = true;
   SetFocus(hWndPlay);
   waveInReset(hWaveIn);
   return TRUE;
  case ID_PLAY:
   if (Pause)
   {
    SetWindowText(hWndPlay,TEXT("Pause"));
    Pause = false;
    Play = true;
    waveOutRestart(hWaveOut);
   }
   else if (Play)
   {
    SetWindowText(hWndPlay,TEXT("Play"));
    Pause = true;
    Play = false;
    waveOutPause(hWaveOut);
   }
   else
   {
    EnableWindow(hWndRecBeg,FALSE);
    EnableWindow(hWndRecEnd,FALSE);
    EnableWindow(hWndEnd,TRUE);
    SetWindowText(hWndPlay,TEXT("Pause"));
    Play = true;
    Pause = false;
    wavehdr1 = (PWAVEHDR)malloc(sizeof (WAVEHDR));

    waveformat.nSamplesPerSec = 22050;  
    waveformat.nChannels = 1;
    waveformat.nBlockAlign = 1;
    waveformat.wBitsPerSample = 8;
    waveformat.nAvgBytesPerSec = 22050;
    waveformat.wFormatTag = WAVE_FORMAT_PCM;
    waveformat.cbSize = 0;
    waveOutOpen(&hWaveOut,WAVE_MAPPER,&waveformat,(DWORD) hWnd,0,CALLBACK_WINDOW);

    wavehdr1->lpData = pSave;
    wavehdr1->dwBufferLength = DateLen;
    wavehdr1->dwBytesRecorded = 0;
    wavehdr1->dwFlags = 0;
    wavehdr1->dwLoops = 1;
    wavehdr1->dwUser = 0;
    wavehdr1->lpNext = NULL;
    wavehdr1->reserved = 0;
    waveOutPrepareHeader(hWaveOut,wavehdr1,sizeof (WAVEHDR));    
   }
   return TRUE;
  case ID_STOP:
   EnableWindow(hWndRecBeg,TRUE);
   EnableWindow(hWndRecEnd,FALSE);
   EnableWindow(hWndPlay,TRUE);
   EnableWindow(hWndEnd,FALSE);
   SetWindowText(hWndPlay,TEXT("Play"));
   waveOutReset(hWaveOut);
   return TRUE;
  }
 case MM_WIM_OPEN:
  pSave = (LPSTR) malloc(1);
  waveInAddBuffer(hWaveIn,wavehdr1,sizeof (WAVEHDR));
  waveInAddBuffer(hWaveIn,wavehdr2,sizeof (WAVEHDR));
  waveInStart(hWaveIn);
  DateLen = 0;
  return TRUE;
 case MM_WIM_DATA:
  pNewBuffer = (LPSTR)realloc(pSave,DateLen + ((LPWAVEHDR)lParam)->dwBytesRecorded);
  pSave = pNewBuffer;
  CopyMemory(pSave + DateLen,((LPWAVEHDR)lParam)->lpData,((LPWAVEHDR)lParam)->dwBytesRecorded);
  DateLen += ((LPWAVEHDR)lParam)->dwBytesRecorded;
  if (RecEnd)
  {
   waveInClose(hWaveIn);
   return TRUE;
  }
  waveInAddBuffer(hWaveIn,((LPWAVEHDR)lParam),sizeof (WAVEHDR));
  return TRUE;
 case MM_WIM_CLOSE:
  waveInUnprepareHeader(hWaveIn,wavehdr1,sizeof (WAVEHDR));
  waveInUnprepareHeader(hWaveIn,wavehdr2,sizeof (WAVEHDR));
  free(pBuffer1);
  free(pBuffer2);
  free(wavehdr1);
  free(wavehdr2);
  return TRUE;
 case MM_WOM_OPEN:
  if (pSave)
   waveOutWrite(hWaveOut,wavehdr1,sizeof (WAVEHDR));
  return TRUE;
 case MM_WOM_DONE:
  waveOutReset(hWaveOut);
  SetWindowText(hWndPlay,TEXT("Play"));
  Play = false;
  Pause = false;
  EnableWindow(hWndPlay,TRUE);
  EnableWindow(hWndEnd,FALSE);
  EnableWindow(hWndRecBeg,TRUE);
  EnableWindow(hWndRecEnd,FALSE);
  SetFocus(hWndRecBeg);
  return TRUE;
 case MM_WOM_CLOSE:
  waveOutUnprepareHeader(hWaveOut,wavehdr1,sizeof (WAVEHDR));
  free(wavehdr1);
  free(pSave);
  return TRUE;
 case WM_SYSCOMMAND:
  if (LOWORD(wParam) == SC_CLOSE)
  {
   if (Rec)
    waveInReset(hWaveIn);
   if (Play)
    waveOutReset(hWaveOut);
   EndDialog(hWnd,0);
   return TRUE;
  }
  return FALSE;
 }
 return FALSE;
}
二、MCI接口
     使用MCI接口处理声音任务有两种方式:一种是向MCI发送一系列字符串命令;另一种是向MCI接口发送消息和数据结构。两种方式都能国完成底层接口的功能并且都较底层接口方式简洁。
1.字符串命令方式
     使用MCI接口可以向MCI发送一系列规定格式的字符串命令。发送这些命令需要调用函数mciSendString,这个函数的原型如下:
MCIERROR mciSendString(
  LPCTSTR lpszCommand,        //命令字符串
  LPTSTR lpszReturnString,      //返回信息字符串
  UINT cchReturn,                  //返回信息字符串的最大长度
  HANDLE hwndCallback           //当命令当中包含notify时,MCI发送MM_NOTIFY消息的窗口
);
这个函数返回一个MCIERROR表示函数调用的结果,我们可以通过调用mciGetErrorString函数将这个代码翻译成一个字符串。这个函数的原型如下:
BOOL mciGetErrorString(
  DWORD fdwError,              //错误代码 
  LPTSTR lpszErrorText,        //目标字符串
  UINT cchErrorText             //目标字符串的最大长度
);
     在使用MCI命令时如果包含wait子命令,那么只用当当前命令执行完毕才能够执行下一条命令。如果包含notify之命令那么当执行一条命令时又输入另一条命令时MCI将会向程序发送一个MM_NOTIFY消息。
     使用MCI接口我们也能够完成声音的采集和回放的任务,并且使用MCI接口完成的程序较底层函数调用完成的程序结构和消息处理更为简单。
     MCI接口完成声音采集的过程可以概括为下面几个步骤:
1.打开并获取设备ID;
2.向设备发送MCI_RECORD消息,要求指定设备开始进行声音采集;
3.当用户中止采集过程或者采集过程当中发生错误时,停止采集,保存已采集的数据并关闭设备。
     MCI接口完成声音回放过程可以概括为下面几个步骤:
1.打开并获取设备ID;
2.向设备发送MCI_PLAY消息,要求指定设备开始进行声音回放;
3.当用户中止回放或者回放完毕时,关闭设备。
     所用向MCI接口发送消息调用mciSendCommand函数,这个函数的原型为:
MCIERROR mciSendCommand(
  MCIDEVICEID IDDevice,         //设备ID
  UINT        uMsg,                  //消息ID
  DWORD       fdwCommand,     //消息相应的标志域设置
  DWORD_PTR   dwParam         //数据结构
);
数据结构包括MCI_GENERIC_PARMS、MCI_RECORD_PARMS、MCI_PLAY_PARMS,这些数据结构对应每个MCI操作。
     打开并获取设备ID的方法是,将函数的ID参数设置为0使用一个MCI_OPEN_PARMS数据结构,并且发送MCI_OPEN消息。当调用结束以后MCI_OPEN_PARMS数据结构的wDeviceID域保存了当前设备的ID。设置MCI_OPEN_PARMS数据结构的方法如下:
   mciOpen.dwCallback = 0;
   mciOpen.lpstrAlias = NULL;
   mciOpen.lpstrDeviceType = TEXT("waveaudio");    //设置设备的类型
   mciOpen.lpstrElementName = TEXT("");
   mciOpen.wDeviceID = 0;
这种设置方法一般用在声音采集的过程当中,调用mciSendCommand函数参数的形式为mciSendCommand(0,MCI_OPEN,MCI_OPEN_TYPE | MCI_WAIT | MCI_OPEN_ELEMENT,(DWORD)&mciOpen);
    mciOpen.dwCallback = 0;
    mciOpen.lpstrAlias = NULL;
    mciOpen.lpstrDeviceType = NULL;
    mciOpen.lpstrElementName = FileName;
    mciOpen.wDeviceID = 0;
这种方式用在声音回放的过程当中,这个过程当中程序知道保存回放数据的文件名,MCI可以根据文件名来判断使用设备的类型,因此不用设置设备类型,其对应的函数调用形式是mciSendCommand(0,MCI_OPEN,MCI_WAIT | MCI_OPEN_ELEMENT,(DWORD)&mciOpen);
     我们可以看到中止声音回放和采集的过程有用户中止和回放/采集结束两种形式。对于用户中止的形式我们只需要处理相应的WM_COMMAND消息即可。对于回放/采集结束的形式,MCI接口将根据MCI_RECORD_PARMS和MCI_PLAY_PARMS数据结构当中dwCallback域的设置,向设置的这个窗口发送MM_MCINOTFY消息,这个消息的wParam的低字位位通知码,根据这个通知码我们可以知道是采集结束还是回放结束,根据相应的通知码我们关闭相应的设备。
     下面是一个完整的使用MCI接口完成声音采集和回放任务的例程:
BOOL CALLBACK DlgProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
 static HWND hWndPlay,hWndRecBeg,hWndRecEnd,hWndStop;
 static bool Play,Pause,Rec;
 static WORD wDeviceID;
 static TCHAR FileName[] = "Rec_PCM.wav";
 MCI_GENERIC_PARMS mciGeneric;
 MCI_OPEN_PARMS mciOpen;
 MCI_RECORD_PARMS mciRec;
 MCI_SAVE_PARMS mciSave;
 MCI_PLAY_PARMS mciPlay;
 switch (message)
 {
 case WM_INITDIALOG:
  hWndPlay = GetDlgItem(hWnd,ID_PLAYPAUSE);
  hWndStop = GetDlgItem(hWnd,ID_STOP);
  hWndRecBeg = GetDlgItem(hWnd,ID_RECBEG);
  hWndRecEnd = GetDlgItem(hWnd,ID_RECEND);
  EnableWindow(hWndPlay,FALSE);
  EnableWindow(hWndStop,FALSE);
  EnableWindow(hWndRecEnd,FALSE);
  EnableWindow(hWndRecBeg,TRUE);
  SetFocus(hWndRecBeg);
  return TRUE;
 case WM_COMMAND:
  switch (LOWORD(wParam))
  {
  case ID_RECBEG:
   mciOpen.dwCallback = 0;
   mciOpen.lpstrAlias = NULL;
   mciOpen.lpstrDeviceType = TEXT("waveaudio");
   mciOpen.lpstrElementName = TEXT("");
   mciOpen.wDeviceID = 0;
   mciSendCommand(0,MCI_OPEN,MCI_OPEN_TYPE | MCI_WAIT | MCI_OPEN_ELEMENT,(DWORD)&mciOpen);

   mciRec.dwCallback = (DWORD)hWnd;
   mciRec.dwFrom = 0;
   mciRec.dwTo = 0;
   wDeviceID = mciOpen.wDeviceID;
   mciSendCommand(wDeviceID,MCI_RECORD,MCI_NOTIFY,(DWORD)&mciRec);
   Rec = true;
   EnableWindow(hWndRecBeg,FALSE);
   EnableWindow(hWndRecEnd,TRUE);
   EnableWindow(hWndPlay,FALSE);
   EnableWindow(hWndStop,FALSE);
   return TRUE;
  case ID_RECEND:
   mciGeneric.dwCallback = 0;
   mciSendCommand(wDeviceID,MCI_STOP,MCI_WAIT,(DWORD)&mciGeneric);
   DeleteFile(FileName);
   mciSave.dwCallback = 0;
   mciSave.lpfilename = FileName;
   mciSendCommand(wDeviceID,MCI_SAVE,MCI_WAIT | MCI_SAVE_FILE,(DWORD)&mciSave);

   mciSendCommand(wDeviceID,MCI_CLOSE,MCI_WAIT,(DWORD)&mciGeneric);
   Rec = false;
   EnableWindow(hWndRecBeg,TRUE);
   EnableWindow(hWndPlay,TRUE);
   EnableWindow(hWndRecEnd,FALSE);
   EnableWindow(hWndStop,FALSE);
   return TRUE;
  case ID_PLAYPAUSE:
   if (Play)
   {
    SetWindowText(hWndPlay,TEXT("Play"));
    Play = false;
    Pause = true;
    mciGeneric.dwCallback = 0;
    mciSendCommand(wDeviceID,MCI_PAUSE,MCI_WAIT,(DWORD)&mciGeneric);
    return TRUE;
   }
   else if (Pause)
   {
    SetWindowText(hWndPlay,TEXT("Pause"));
    Play = true;
    Pause = false;
    mciPlay.dwCallback = (DWORD)hWnd;
    mciPlay.dwFrom = 0;
    mciPlay.dwTo = 0;
    mciSendCommand(wDeviceID,MCI_PLAY,MCI_NOTIFY,(DWORD)&mciPlay);
    return TRUE;
   }
   else
   {
    mciOpen.dwCallback = 0;
    mciOpen.lpstrAlias = NULL;
    mciOpen.lpstrDeviceType = NULL;
    mciOpen.lpstrElementName = FileName;
    mciOpen.wDeviceID = 0;
    mciSendCommand(0,MCI_OPEN,MCI_WAIT | MCI_OPEN_ELEMENT,(DWORD)&mciOpen);
    wDeviceID = mciOpen.wDeviceID;
    mciPlay.dwCallback = (DWORD)hWnd;
    mciPlay.dwFrom = 0;
    mciPlay.dwTo = 0;
    mciSendCommand(wDeviceID,MCI_PLAY,MCI_NOTIFY,(DWORD)&mciPlay);
    Play = true;
    EnableWindow(hWndRecBeg,FALSE);
    EnableWindow(hWndRecEnd,FALSE);
    EnableWindow(hWndPlay,TRUE);
    EnableWindow(hWndStop,TRUE);
    SetWindowText(hWndPlay,TEXT("Pause"));
    return TRUE;
   }
  case ID_STOP:
   mciGeneric.dwCallback = 0;
   mciSendCommand(wDeviceID,MCI_STOP,MCI_WAIT,(DWORD)&mciGeneric);
   mciSendCommand(wDeviceID,MCI_CLOSE,MCI_WAIT,(DWORD)&mciGeneric);

   Play = Pause = false;
   EnableWindow(hWndRecBeg,TRUE);
   EnableWindow(hWndRecEnd,FALSE);
   EnableWindow(hWndPlay,TRUE);
   EnableWindow(hWndStop,FALSE);
   SetWindowText(hWndPlay,TEXT("Play"));
   return TRUE;
  }
  return FALSE;
 case MM_MCINOTIFY:
  if (LOWORD(wParam) == MCI_NOTIFY_SUCCESSFUL)
  {
   if (Play || Pause)
    SendMessage(hWnd,WM_COMMAND,ID_STOP,0);
   if (Rec)
    SendMessage(hWnd,WM_COMMAND,ID_RECEND,0);
   return TRUE;
  }
  return FALSE;
 case WM_SYSCOMMAND:
  if (LOWORD(wParam) == SC_CLOSE)
  {
   if (Play || Pause)
    SendMessage(hWnd,WM_COMMAND,ID_STOP,0);
   if (Rec)
    SendMessage(hWnd,WM_COMMAND,ID_RECEND,0);
   DeleteFile(FileName);
   EndDialog(hWnd,0);
   return TRUE;
  }
  return FALSE;
 }
 return FALSE;
}


原文地址:https://www.cnblogs.com/myitm/p/2111372.html