-
Notifications
You must be signed in to change notification settings - Fork 3
/
WaveRec.cpp
290 lines (230 loc) · 8.2 KB
/
WaveRec.cpp
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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
//
// Copyright (c) 2016 Vyacheslav Napadovsky.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
//
// Software sound recorder for OS Windows.
//
#define _CRT_SECURE_NO_WARNINGS
#include <tchar.h>
#include <math.h>
#include <Windows.h>
#include <Mmdeviceapi.h>
#include <Audioclient.h>
#include <stdexcept> // std::runtime_error
#include <fstream> // std::ofstream
#include <iterator> // std::ostream_iterator
// ATL is for CComPtr
#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS
#include <atlbase.h>
#include <atlstr.h>
class IAudioWriter {
public:
// Set format of output audio. Called once before CopyData.
virtual void SetFormat(WAVEFORMATEX* fmt) abstract;
// Actually writes data. If return value is 'false' then breaks from recording loop.
virtual bool CopyData(BYTE* data, UINT32 nFrames) abstract;
};
void RecordAudioStream(IAudioWriter& writer) {
// Code in this function is a refactored version of code from here:
// https://msdn.microsoft.com/ru-ru/library/windows/desktop/dd370800(v=vs.85).aspx
//-----------------------------------------------------------
// Record an audio stream from the default audio capture
// device. The RecordAudioStream function allocates a shared
// buffer big enough to hold one second of PCM audio data.
// The function uses this buffer to stream data from the
// capture device. The main loop runs every 1/2 second.
//-----------------------------------------------------------
// REFERENCE_TIME time units per second and per millisecond
constexpr auto REFTIMES_PER_SEC = 10000000;
constexpr auto REFTIMES_PER_MILLISEC = 10000;
CComPtr<IMMDeviceEnumerator> pEnumerator;
if (FAILED(pEnumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL)))
throw std::runtime_error("Can't receive DeviceEnumerator instance");
CComPtr<IMMDevice> pDevice;
// if you want to capture data from microphone use 'eCapture' instead 'eRender'
if (FAILED(pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pDevice)))
throw std::runtime_error("Can't get default audio endpoint");
CComPtr<IAudioClient> pAudioClient;
if (FAILED(pDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void**)&pAudioClient)))
throw std::runtime_error("Can't activate device");
WAVEFORMATEX *pwfx = nullptr;
if (FAILED(pAudioClient->GetMixFormat(&pwfx)))
throw std::runtime_error("Can't get audio format");
// Notify the audio sink which format to use.
writer.SetFormat(pwfx);
const REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC;
REFERENCE_TIME hnsActualDuration;
UINT32 bufferFrameCount;
if (FAILED(
pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_LOOPBACK,
hnsRequestedDuration, 0, pwfx, nullptr)
))
throw std::runtime_error("Device initialization failed");
// Get the size of the allocated buffer.
if (FAILED(pAudioClient->GetBufferSize(&bufferFrameCount)))
throw std::runtime_error("GetBufferSize failed");
// Calculate the actual duration of the allocated buffer.
hnsActualDuration = (REFERENCE_TIME)((double)hnsRequestedDuration * bufferFrameCount / pwfx->nSamplesPerSec);
CComPtr<IAudioCaptureClient> pCaptureClient;
if (FAILED(pAudioClient->GetService(__uuidof(IAudioCaptureClient), (void**)&pCaptureClient)))
throw std::runtime_error("GetService failed");
// Start recording.
if (FAILED(pAudioClient->Start()))
throw std::runtime_error("failed to start recording");
// Each loop fills about half of the shared buffer.
bool bContinue = true;
while (bContinue) {
UINT32 packetLength = 0;
if (FAILED(pCaptureClient->GetNextPacketSize(&packetLength)))
throw std::runtime_error("GetNextPacketSize failed");
if (packetLength == 0) {
// Sleep for half the buffer duration.
Sleep((DWORD)(hnsActualDuration / REFTIMES_PER_MILLISEC / 2));
continue;
}
// Get the available data in the shared buffer.
BYTE *pData;
UINT32 numFramesAvailable;
DWORD flags;
if (FAILED(pCaptureClient->GetBuffer(&pData, &numFramesAvailable, &flags, nullptr, nullptr)))
throw std::runtime_error("GetBuffer failed");
if ((flags & AUDCLNT_BUFFERFLAGS_SILENT) != 0) {
pData = nullptr; // Tell CopyData to write silence.
}
// Copy the available capture data to the audio sink.
bContinue = writer.CopyData(pData, numFramesAvailable);
if (FAILED(pCaptureClient->ReleaseBuffer(numFramesAvailable)))
throw std::runtime_error("ReleaseBuffer failed");
}
// Stop recording.
if (FAILED(pAudioClient->Stop()))
throw std::runtime_error("Stop recording failed");
CoTaskMemFree(pwfx);
}
volatile bool flag_stop = false;
HANDLE hEventCompleted = nullptr;
BOOL CtrlHandler(DWORD fdwCtrlType) {
switch (fdwCtrlType) {
case CTRL_C_EVENT:
case CTRL_CLOSE_EVENT:
flag_stop = true;
WaitForSingleObject(hEventCompleted, INFINITE);
return TRUE;
default:
return FALSE;
}
}
class AudioWriter : public IAudioWriter {
public:
AudioWriter(LPCTSTR fn) :
_f(fn, std::ios::binary),
_header(nullptr),
_fmt(nullptr),
_framesCount(0)
{
if (!_f.good())
throw std::runtime_error("Can't open file for write");
}
~AudioWriter() {
WriteHeader();
delete[] _header;
}
void SetFormat(WAVEFORMATEX* fmt) override {
DWORD fmt_size = sizeof(WAVEFORMATEX) + fmt->cbSize;
_headersize = sizeof(WAVEHEADER) + fmt_size + sizeof(CHUNKHEADER);
_header = new char[_headersize];
_waveheader = (WAVEHEADER*)_header;
_waveheader->dwChunkID = FOURCC_RIFF;
_waveheader->dwFormat = mmioFOURCC('W', 'A', 'V', 'E');
_waveheader->header.dwSubchunk1ID = mmioFOURCC('f', 'm', 't', ' ');
_waveheader->header.dwSubchunk1Size = fmt_size;
_fmt = (WAVEFORMATEX*)(_header + sizeof(WAVEHEADER));
memcpy(_fmt, fmt, fmt_size);
_dataheader = (CHUNKHEADER*)(((char*)_fmt) + fmt_size);
_dataheader->dwSubchunk1ID = mmioFOURCC('d', 'a', 't', 'a');
_dataheader->dwSubchunk1Size = 0;
_waveheader->dwChunkSize = sizeof(FOURCC) +
(sizeof(CHUNKHEADER) + _waveheader->header.dwSubchunk1Size) +
(sizeof(CHUNKHEADER) + _dataheader->dwSubchunk1Size);
_f.write(_header, _headersize);
}
bool CopyData(BYTE* data, UINT32 nFrames) override {
_framesCount += nFrames;
UINT32 bytes = nFrames * _fmt->nBlockAlign;
if (data == nullptr)
std::fill_n(std::ostream_iterator<char>(_f), bytes, 0);
else
_f.write((char*)data, bytes);
_f.flush();
printInfo();
// Check if termination via Ctrl+C handler initiated.
return !flag_stop;
}
private:
struct CHUNKHEADER {
FOURCC dwSubchunk1ID;
DWORD dwSubchunk1Size;
};
struct WAVEHEADER {
FOURCC dwChunkID;
DWORD dwChunkSize;
FOURCC dwFormat;
CHUNKHEADER header;
};
std::ofstream _f;
char *_header;
DWORD _headersize;
DWORD _framesCount;
WAVEFORMATEX *_fmt;
WAVEHEADER *_waveheader;
CHUNKHEADER *_dataheader;
void WriteHeader() {
_f.seekp(0);
DWORD datasize = _framesCount * _fmt->nBlockAlign;
_waveheader->dwChunkSize += datasize;
_dataheader->dwSubchunk1Size += datasize;
_f.write(_header, _headersize);
_f.seekp(0, std::ios::end);
_f.flush();
}
void printInfo() {
float sec = (float)_framesCount / _fmt->nSamplesPerSec;
int ms = (int)fmod(sec * 1000, 1000);
int s = (int)sec;
int m = s / 60;
int h = m / 60;
s %= 60; m %= 60;
printf("\rRecording: %02d:%02d:%02d.%03d", h, m, s, ms);
}
};
int _tmain(int argc, TCHAR* argv[])
{
SYSTEMTIME st;
GetLocalTime(&st);
TCHAR buffer[128];
_stprintf(buffer, TEXT("output_%04d%02d%02d-%02d%02d%02d_%03d.wav"),
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
try {
if (FAILED(CoInitialize(nullptr)))
throw std::runtime_error("CoInitialize call failed");
// Create writer
AudioWriter writer(buffer);
_tprintf(TEXT("Output filename: %s\n"), buffer);
// Install Ctrl+C handler
hEventCompleted = CreateEvent(nullptr, true, false, nullptr);
if (hEventCompleted == nullptr)
throw std::runtime_error("Can't create event");
if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE))
throw std::runtime_error("Can't set Ctrl+C handler");
// Prevent PC from sleep
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED);
RecordAudioStream(writer);
}
catch (const std::exception& e) {
printf("ERROR: %s\n", e.what());
}
CoUninitialize();
if (hEventCompleted != nullptr)
SetEvent(hEventCompleted);
return 0;
}