MFC播放声音和录音的实现(二)

上一篇中主要介绍了MCF中声音文件的播放。这一篇根据自己的理解介绍一下录音的实现,最后以实例的形式在Win32 APP中实现 录音并回放。下一篇将介绍一下录音在MFC中的实现,并在MFC中实现一个简单的录音机应用程序。

在介绍录音实现过程之前,先介绍一下数字音频基础知识。

1、数字音频基础知识

Fourier级数: 任何周期的波形可以分解成多个正弦波,这些正弦波的频率都是整数倍。级数中其他正线波的频率是基础频率的整数倍。基础频率称为一级谐波。

PCM: pulse code modulation,脉冲编码调制,即对波形按照固定周期频率采样。为了保证采样后数据质量,采样频率必须是样本声音最高频率的两倍,这就是Nyquist频率。

样本大小:采样后用于存储振幅级的位数,实际就是脉冲编码的阶梯数,位数越大表明精度越高,这一点参考数字逻辑电路。

声音强度: 波形振幅的平方。两个声音强度上的差常以分贝(db)为单位来度量,

计算公式如下: 20*log(A1/A2)分贝。A1,A2为两个声音的振幅。如果采样大小为8位,则采样的动态范围为20*log(256)分贝=48db。如果样本大小为16位,则采样动态范围为20*log(65536)大约是96分贝,接近了人听觉极限和痛苦极限,是再现音乐的理想范围。windows同时支持8位和16位的采样大小。 

另外,常见的声音文件主要有两种,分别对应于单声道和双声道。如下表所示:

表1 声音相关参数对照表

音频数据格式

声道数

采样率KHz

每秒钟的数据量(Byte)

块对齐

Byte

采样值Bit

附加额外格式信息大小(Byte)

单声道

1

11.025

11025*8*1/8=11025B=10.7K

1

8

0

双声道

2

44.1

44100*16*2/8=176400B=172K

4

16

0

声道数是指音频信道数,单声道的数据使用一个通道,立体声数据使用两个通道。对于PCM音频,这个值不能大于2;

采样率是指声音信号在“模→数”转换过程中单位时间内采样的次数。常取值8.0KHz,11.025 KHz,22.05 KHz,44.1 KHz。对于PCM音频,这个值不能大于44.1 KHz。采样率越高,丢失的信息相对越少,声音信号保存相对越完整,但是录音生成的文件也就越大;反之,采样率越低,声音信号保存相对不如采样率高时完整,但是录音生成的文件反而越小。系统提供了两种声道供用户选择,可综合对声音信号的要求高低和系统存储空间等因素灵活选择。

块对齐的字节数,块对齐是最低的数据的wFormatTag格式类型的原子单位。如果wFormatTag WAVE_FORMAT_PCM或WAVE_FORMAT_EXTENSIBLE ,nBlockAlign必须等于NumChannels * BitsPerSample / 8。

附加额外格式信息大小(Byte)以字节为单位,附加额外的格式信息WAVEFORMATEX结构。如果没有额外的信息需要由wFormatTag,这个参数必须被设置为零。最后,声音文件通常存储为.wave格式。

2录音相关API函数,结构,消息

对于录音设备来说,windows 提供了一组wave***的函数,比较重要的有以下几个:

打开录音设备函数

MMRESULT waveInOpen(
  LPHWAVEIN phwi,            //输入设备句柄
  UINT uDeviceID,            //输入设备ID
  LPWAVEFORMATEX pwfx,       //录音格式指针
  DWORD dwCallback,  //处理MM_WIM_***消息的回调函数或窗口句柄,线程ID
  DWORD dwCallbackInstance, 
  DWORD fdwOpen              //处理消息方式的符号位
);

为录音设备准备缓存函数 :

MMRESULT waveInPrepareHeader(  HWAVEIN hwi,  LPWAVEHDR pwh, UINT bwh ); 

给输入设备增加一个缓存 :

MMRESULT waveInAddBuffer(  HWAVEIN hwi, LPWAVEHDR pwh, UINT cbwh ); 

开始录音函数 :

MMRESULT waveInStart(  HWAVEIN hwi  ); 

清除缓存函数 :

MMRESULT waveInUnprepareHeader( HWAVEIN hwi,LPWAVEHDR pwh, UINT cbwh); 

停止录音函数 :

MMRESULT waveInReset( HWAVEIN hwi ); 

关闭录音设备函数:

MMRESULT waveInClose( HWAVEIN hwi ); 
Wave_audio数据格式:
typedef struct {
    WORD  wFormatTag; //数据格式,一般为WAVE_FORMAT_PCM即脉冲编码
    WORD  nChannels; //声道
    DWORD nSamplesPerSec; //采样频率
    DWORD nAvgBytesPerSec; //每秒数据量
    WORD  nBlockAlign; //块大小
    WORD  wBitsPerSample;//样本大小 
    WORD  cbSize; 
} WAVEFORMATEX; 
waveform-audio 缓存格式:
typedef struct {
    LPSTR  lpData; //内存指针
    DWORD  dwBufferLength;//长度 
    DWORD  dwBytesRecorded; //已录音的字节长度
    DWORD  dwUser; 
    DWORD  dwFlags; 
    DWORD  dwLoops; //循环次数
    struct wavehdr_tag * lpNext;
    DWORD  reserved; 
} WAVEHDR; 


相关消息 

MM_WIM_OPEN:打开设备时消息,在此期间我们可以进行一些初始化工作

MM_WIM_DATA:当缓存已满或者停止录音时的消息,处理这个消息可以对缓存进行重新分配,实现不限长度录音

MM_WIM_CLOSE:关闭录音设备时的消息。

相对于录音来说,回放就简单的多了,用到的函数主要有以下几个:

打开回放设备函数 

MMRESULT waveOutOpen(
  LPHWAVEOUT phwo,          
  UINT uDeviceID,           
  LPWAVEFORMATEX pwfx,      
  DWORD dwCallback,         
  DWORD dwCallbackInstance, 
  DWORD fdwOpen             
); 

为回放设备准备内存块函数 

MMRESULT waveOutPrepareHeader(
  HWAVEOUT hwo, 
  LPWAVEHDR pwh,
  UINT cbwh     
);

写数据(放音)函数

MMRESULT waveOutWrite(
  HWAVEOUT hwo, 
  LPWAVEHDR pwh,
  UINT cbwh     
);

相应的也有三个消息,用法跟录音的类似。

 

3.控制台应用程序Win32 

根据上面的信息,简要地写了一个Win32控制台应用程序,可以实现录音和回放。代码如下:

#include "stdafx.h"
#include "stdlib.h"
#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 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); 
// 放音回调函数
void CALLBACK waveOutProc(  HWAVEOUT hwo,UINT uMsg,DWORD_PTR dwInstance,DWORD_PTR dwParam1,DWORD_PTR dwParam2)
{ 
      if (WOM_DONE == uMsg) 
      { 
            buf_count = BUFFER_SIZE; 
      } 
} 
 
// 入口
 
int _tmain(int argc, _TCHAR* argv[])
{
      /* 录音*/ // Device  
      int nReturn = waveInGetNumDevs();//定义输入设备的数目
      printf("输入设备数目:%d\n", nReturn); 
 
      //识别输入的设备
      for (int i=0; i<nReturn; i++) 
      { 
            WAVEINCAPS wic;  //WAVEINCAPS结构描述波形音频输入设备的能力
            waveInGetDevCaps(i, &wic, sizeof(WAVEINCAPS)); //waveInGetDevCaps功能检索一个给定的波形音频输入设备的能力
            printf("#%d\t设备名:%s\n", i, wic.szPname);
      } 
 
      // open  
 
      HWAVEIN hWaveIn;//波形音频数据格式Wave_audio数据格式
      WAVEFORMATEX wavform;//WAVEFORMATEX结构定义了波形音频数据格式。包括在这个结构中唯一的格式信息,共同所有波形音频数据格式。对于需要额外的信息的格式,这个结构包含在另一个结构的第一个成员,以及与其他信息
      wavform.wFormatTag = WAVE_FORMAT_PCM;  //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结构
 
      //打开录音设备函数
      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;
 
            //为录音设备准备缓存函数:
            //MMRESULT waveInPrepareHeader(  HWAVEIN hwi,  LPWAVEHDR pwh, UINT bwh );
            waveInPrepareHeader(hWaveIn, &wh[i], sizeof(WAVEHDR));
 
            //给输入设备增加一个缓存:
            //MMRESULT waveInAddBuffer(  HWAVEIN hwi, LPWAVEHDR pwh, UINT cbwh );
            waveInAddBuffer(hWaveIn, &wh[i], sizeof(WAVEHDR)); 
      } 
       // record  
      printf("Start to Record...\n");  
      buf_count = 0; //刚开始录音的时候缓冲区的个数初始化为
      //开始录音函数
      //MMRESULT waveInStart(  HWAVEIN hwi  );  
      waveInStart(hWaveIn); //开始录音
 
      while (buf_count < BUFFER_SIZE) 
      { 
            Sleep(1); 
      } 
 
      printf("Record Over!\n\n");  
      waveInStop(hWaveIn);//waveInStop功能停止的波形音频输入
      //停止录音函数:
      //MMRESULT waveInReset( HWAVEIN hwi ); 
      waveInReset(hWaveIn);//停止录音
      //清除缓存函数:
      //MMRESULT waveInUnprepareHeader( HWAVEIN hwi,LPWAVEHDR pwh, UINT cbwh);  
      for (int i=0; i<FRAGMENT_NUM; i++) 
      { 
            waveInUnprepareHeader(hWaveIn, &wh[i], sizeof(WAVEHDR)); 
            delete wh[i].lpData; 
      } 
      //关闭录音设备函数:
      //MMRESULT waveInClose( HWAVEIN hwi );
      waveInClose(hWaveIn);  
      //system("pause"); 
      printf("\n"); 
 
      /* 放音*/ 
 
      // Device  
      nReturn = waveOutGetNumDevs();  //定义输出设备的数目
      printf("\n输出设备数目:%d\n", nReturn); 
      for (int i=0; i<nReturn; i++) 
      { 
            WAVEOUTCAPS woc;  //WAVEINCAPS结构描述波形音频输出设备的能力
            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; //WAVEINCAPS结构描述波形音频输出设备的能力
      waveOutGetDevCaps((UINT_PTR)hWaveOut, &woc,sizeof(WAVEOUTCAPS)); 
      printf("打开的输出设备:%s\n", wic.szPname); 
 
      // prepare buffer  
      WAVEHDR wavhdr; 
      wavhdr.lpData = (LPSTR)buffer; 
      wavhdr.dwBufferLength = BUFFER_SIZE;  //MMRESULT waveOutPrepareHeader(HWAVEOUT hwo,LPWAVEHDR pwh,UINT cbwh);
      wavhdr.dwFlags = 0;  //为回放设备准备内存块函数 
      wavhdr.dwLoops = 0;
      waveOutPrepareHeader(hWaveOut, &wavhdr, sizeof(WAVEHDR)); 
      // play  
      printf("Start to Play...\n");  
      buf_count = 0;      
      //MMRESULT waveOutWrite(HWAVEOUT hwo,LPWAVEHDR pwh,UINT cbwh);
      waveOutWrite(hWaveOut, &wavhdr, sizeof(WAVEHDR)); //写数据(放音)函数
      //声音文件还没有放完的话运行不能退出
      while (buf_count < BUFFER_SIZE) 
      { 
            Sleep(1); 
      } 
      // clean  
      waveOutReset(hWaveOut); //停止放音
      //MMRESULT waveOutPrepareHeader(HWAVEOUT hwo,LPWAVEHDR pwh,UINT cbwh);
      waveOutUnprepareHeader(hWaveOut, &wavhdr, sizeof(WAVEHDR)); //为回放设备准备内存块函数 
      waveOutClose(hWaveOut);  //关闭放音设备函数
      printf("Play Over!\n\n");  
      printf("Play Over!\n\n");
      return 0;
}

最后在说明一下,这种方式录音存储的声音数据和播放的数据都是在内存中,没有文件操作,也就是说用这种方式是不能实现录音文件的存储。这种方式将在下一篇中通过MFC来实现。



你可能感兴趣的:(C++,控制台,应用程序,录音)