本文介绍如何使用Windows API来录制语音信号兵保存到wave文件中,主要用到三个结构体和几个wave开头的API函数(在Winmm.lib文件中)。其中三个结构体是WAVEFORMATEX、WAVEHDR、MMTIME,其详细定义都在MMSystem.h中定义, 可以转到定义看其详细内容及每一项的英文注释。用到的API函数的详细用法可以参见MSDN: http://msdn.microsoft.com/en-us/library/windows/desktop/dd743847(v=vs.85).aspx 详细的使用过程请看下文的源代码,这是一个Win32 Application,需要手动添加Winmm.lib的依赖。

实例程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
// ******************* FileName: WinMain.cpp *****************************
// 该源程序需要加入到 VC6 的 Win32 Application 的 empty Project 中
// 对于工程的 Link 选项,至少要包含以下库: msvcrt.lib Winmm.lib

#include <stdio.h>
#include <atlstr.h>
#include <windows.h>
#include <Mmsystem.h>

#pragma comment(lib,"Winmm.lib")

char lpTemp[256];

DWORD FCC(LPSTR lpStr)
{
  DWORD Number = lpStr[0] + lpStr[1] *0x100 + lpStr[2] *0x10000 + lpStr[3] *0x1000000 ;
  return Number;
}

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
  DWORD datasize = 48000;
  
  // 设置录音采样参数
  WAVEFORMATEX waveformat;
  waveformat.wFormatTag=WAVE_FORMAT_PCM; // 指定录音格式
  waveformat.nChannels=1;
  waveformat.nSamplesPerSec=8000;
  waveformat.nBlockAlign=1;
  waveformat.wBitsPerSample=8;
  waveformat.cbSize=0;
  waveformat.nAvgBytesPerSec=waveformat.nChannels*waveformat.nSamplesPerSec*waveformat.wBitsPerSample/8;
  
  sprintf(lpTemp,"WAVEFORMATEX size = %lu", sizeof(WAVEFORMATEX));
  MessageBox(NULL,CString(lpTemp),CString("提示"),MB_OK);

  HWAVEIN m_hWaveIn;
  if ( !waveInGetNumDevs() )
  {
      MessageBox(NULL,CString("没有可以使用的 WaveIn 通道"),CString("提示"),MB_OK);
      return 0;
  }

  // 打开录音设备
  int res = waveInOpen(&m_hWaveIn,WAVE_MAPPER, &waveformat, (DWORD)NULL,0L,CALLBACK_WINDOW);
  if ( res != MMSYSERR_NOERROR )
  {
     sprintf(lpTemp, "打开 waveIn 通道失败,Error_Code = 0x%x", res );
     MessageBox(NULL,CString(lpTemp),CString("提示"),MB_OK);
     return 0;
  }
  
  WAVEHDR m_pWaveHdr;
  m_pWaveHdr.lpData = (char *)GlobalLock( GlobalAlloc(GMEM_MOVEABLE|GMEM_SHARE, datasize) );
  memset(m_pWaveHdr.lpData, 0, datasize );
  m_pWaveHdr.dwBufferLength = datasize;
  m_pWaveHdr.dwBytesRecorded = 0;
  m_pWaveHdr.dwUser = 0;
  m_pWaveHdr.dwFlags = 0;
  m_pWaveHdr.dwLoops = 0;
  sprintf( lpTemp, "WAVEHDR size = %lu", sizeof(WAVEHDR) );
  MessageBox(NULL,CString(lpTemp),CString("提示"),MB_OK);

  // 准备内存块录音
  int resPrepare = waveInPrepareHeader( m_hWaveIn, &m_pWaveHdr, sizeof(WAVEHDR) );
  if ( resPrepare != MMSYSERR_NOERROR)
  {
      sprintf(lpTemp, "不能开辟录音头文件,Error_Code = 0x%03X", resPrepare );
      MessageBox(NULL,CString(lpTemp),CString("提示"),MB_OK);
      return 0;
  }

  resPrepare = waveInAddBuffer( m_hWaveIn, &m_pWaveHdr, sizeof(WAVEHDR) );
  if ( resPrepare != MMSYSERR_NOERROR)
  {
      sprintf(lpTemp, "不能开辟录音用缓冲,Error_Code = 0x%03X", resPrepare );
      MessageBox(NULL,CString(lpTemp),CString("提示"),MB_OK);
      return 0;
  }

  if (! waveInStart(m_hWaveIn) )
  {
      MessageBox(NULL,CString("开始录音"),CString("提示"),MB_OK);
  }
  else
  {
      MessageBox(NULL,CString("开始录音失败"),CString("提示"),MB_OK);
      return 0;
  }
  Sleep(30000);

  MMTIME mmt;
  mmt.wType = TIME_BYTES;
  sprintf( lpTemp, "sizeof(MMTIME) = %d, sizeof(UINT) = %d", sizeof(MMTIME), sizeof(UINT) );
  MessageBox(NULL,CString(lpTemp),CString("提示"),MB_OK);

  if (! waveInGetPosition(m_hWaveIn, &mmt, sizeof(MMTIME)) )
  {
      MessageBox(NULL,CString("取得现在音频位置"),CString("提示"),MB_OK);
  }
  else
  {
      MessageBox(NULL,CString("不能取得音频长度"),CString("提示"),MB_OK);
      return 0;
  }

  if (mmt.wType != TIME_BYTES)
  {
      MessageBox(NULL,CString("指定的 TIME_BYTES 格式音频长度不支持"),CString("提示"),MB_OK);
      return 0;
  }

  if (! waveInStop(m_hWaveIn) )
  {
      MessageBox(NULL,CString("停止录音"),CString("提示"),MB_OK);
  }
  else
  {
      MessageBox(NULL,CString("停止录音失败"),CString("提示"),MB_OK);
  }
  
  if ( waveInReset(m_hWaveIn) )
  {
      MessageBox(NULL,CString("重置内存区失败"),CString("提示"),MB_OK);
      return 0;
  }

  m_pWaveHdr.dwBytesRecorded = mmt.u.cb;
  DWORD NumToWrite=0;
  DWORD dwNumber = 0;
  HANDLE FileHandle = CreateFile( CString("myTest.wav"), GENERIC_WRITE,
      FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

  // memset(m_pWaveHdr.lpData, 0, datasize);
  dwNumber = FCC("RIFF");
  WriteFile(FileHandle, &dwNumber, 4, &NumToWrite, NULL);
  dwNumber = m_pWaveHdr.dwBytesRecorded + 18 + 20;
  WriteFile(FileHandle, &dwNumber, 4, &NumToWrite, NULL);
  dwNumber = FCC("WAVE");
  WriteFile(FileHandle, &dwNumber, 4, &NumToWrite, NULL);
  dwNumber = FCC("fmt ");
  WriteFile(FileHandle, &dwNumber, 4, &NumToWrite, NULL);
  dwNumber = 18L;
  WriteFile(FileHandle, &dwNumber, 4, &NumToWrite, NULL);
  WriteFile(FileHandle, &waveformat, sizeof(WAVEFORMATEX), &NumToWrite, NULL);
  dwNumber = FCC("data");
  WriteFile(FileHandle, &dwNumber, 4, &NumToWrite, NULL);
  dwNumber = m_pWaveHdr.dwBytesRecorded;
  WriteFile(FileHandle, &dwNumber, 4, &NumToWrite, NULL);
  WriteFile(FileHandle, m_pWaveHdr.lpData, m_pWaveHdr.dwBytesRecorded, &NumToWrite, NULL);
  SetEndOfFile(FileHandle);
  CloseHandle( FileHandle );
  FileHandle = INVALID_HANDLE_VALUE; // 收尾关闭句柄
  MessageBox(NULL,CString("应该已生成 myTest.wav 文件"),CString("提示"),MB_OK);

  if ( waveInUnprepareHeader(m_hWaveIn, &m_pWaveHdr, sizeof(WAVEHDR)) )
  {
      MessageBox(NULL,CString("Un_Prepare Header 失败"),CString("提示"),MB_OK);
  }
  else
  {
      MessageBox(NULL,CString("Un_Prepare Header 成功"),CString("提示"),MB_OK);
      return 0;
  }

  if ( GlobalFree(GlobalHandle( m_pWaveHdr.lpData )) )
  {
      MessageBox(NULL,CString("Global Free 失败"),CString("提示"),MB_OK);
  }
  else
  {
      MessageBox(NULL,CString("Global Free 成功"),CString("提示"),MB_OK);
      return 0;
  }

  if (res == MMSYSERR_NOERROR ) // 关闭录音设备
  {
      if (waveInClose(m_hWaveIn)==MMSYSERR_NOERROR)
      {
          MessageBox(NULL,CString("正常关闭录音设备"),CString("提示"),MB_OK);
      }
      else
      {
          MessageBox(NULL,CString("非正常关闭录音设备"),CString("提示"),MB_OK);
          return 0;
      }
  }

  return 0;
}
// ******************* End of File ************************

这里提供的代码有点杂乱,现已整理成一个小的接口,并提供了一个简单的示例,放在GitHub上:https://github.com/ibillxia/Demo/tree/master/DemoSpeechRecord

参考:
[1]MSDN: http://msdn.microsoft.com/en-us/library/windows/desktop/dd743586(v=vs.85).aspx
[2]基于API的录音机程序: http://www.vckbase.com/index.php/wv/664

Original Link: http://ibillxia.github.io/blog/2013/06/04/a-simple-code-for-wave-recording-using-windows-api/
Attribution - NON-Commercial - ShareAlike - Copyright © Bill Xia