/
AudioRecorder.cs
227 lines (190 loc) · 9.14 KB
/
AudioRecorder.cs
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
/* ************************************************************************* *\
INTEL CORPORATION PROPRIETARY INFORMATION
This software is supplied under the terms of a license agreement or
nondisclosure agreement with Intel Corporation and may not be copied
or disclosed except in accordance with the terms of that agreement.
Copyright (C) 2013 Intel Corporation. All Rights Reserved.
\* ************************************************************************* */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
/// The audio recorder application records audio input and stores
/// it in a PCM WAVE file.
namespace InCarConversationRecorder
{
class AudioRecorder
{
const pxcmStatus PXCM_STATUS_NO_ERROR = pxcmStatus.PXCM_STATUS_NO_ERROR; // convenience declaration
const int AUDIO_HEADER_SIZE = 44; // WAVE PCM header is 44 bytes
const int MAX_FRAMES = 160000; // 1600 frames is about 100min
const Int16 BITS_PER_SAMPLE = 16; // 16 bits for each audio sample
const Int16 BYTES_PER_SAMPLE = 2; // 16 bits = 2 bytes
// A struct that will help define part of the WAVE file
struct FmtSubchunk
{
public Int32 size;
public Int16 format;
public Int16 num_channels;
public UInt32 sample_rate;
public UInt32 byte_rate;
public Int16 block_align;
public Int16 bits_per_sample;
}
// Write the header for a PCM WAVE file give the data size, number of channels, and sample rate.
static void WriteAudioHeader(BinaryWriter writer, UInt32 dataSize, Int16 channels, UInt32 sampleRate)
{
//// Audio Header looks like:
//// RIFF descriptor
//// subchunk1 - the 'fmt' chunk
//// subchunk2 - the 'data' chunk
//// RIFF
// Need to get the raw bytes, otherwise Write(char[]) also writes the array length
writer.Write(System.Text.Encoding.ASCII.GetBytes("RIFF"));
UInt32 chunk_size = 36 + dataSize;
writer.Write(chunk_size);
writer.Write(System.Text.Encoding.ASCII.GetBytes("WAVE"));
//// subchunk1
writer.Write(System.Text.Encoding.ASCII.GetBytes("fmt "));
FmtSubchunk subchunk1;
subchunk1.size = 16; // 16 bytes for PCM
subchunk1.format = 1; // 1 means PCM
subchunk1.num_channels = channels;
subchunk1.sample_rate = sampleRate;
subchunk1.byte_rate = sampleRate * (UInt32)(channels * BYTES_PER_SAMPLE);
subchunk1.block_align = (Int16)(channels * BYTES_PER_SAMPLE);
subchunk1.bits_per_sample = BITS_PER_SAMPLE;
// As an alternative to a bunch of writer.Write calls, you could have "used" InteropServices
// and call Marshal.StructureToPtr.
writer.Write(subchunk1.size);
writer.Write(subchunk1.format);
writer.Write(subchunk1.num_channels);
writer.Write(subchunk1.sample_rate);
writer.Write(subchunk1.byte_rate);
writer.Write(subchunk1.block_align);
writer.Write(subchunk1.bits_per_sample);
//// subchunk2
writer.Write(System.Text.Encoding.ASCII.GetBytes("data"));
writer.Write(dataSize);
// the rest of subchunk2 is the actual audio data and gets written elsewhere
}
PXCMSession session;
bool isRecording;
Thread recordingThread;
string timestamp;
public AudioRecorder(PXCMSession session, string timestamp)
{
this.session = session;
this.isRecording = false;
this.recordingThread = new Thread(recording);
this.timestamp = timestamp;
}
public void startRecording()
{
this.recordingThread.Start();
}
public void stopRecording()
{
if (isRecording)
{
this.isRecording = false;
System.Threading.Thread.Sleep(5);
this.recordingThread.Join();
}
}
private void recording()
{
//Default values
string output_file_name = timestamp + ".wav";
// Get a memory stream for the audio data, wrapped with a big try catch for simplicity.
using (MemoryStream writer = new MemoryStream())
{
pxcmStatus status = PXCMSession.CreateInstance(out this.session);
if (status < pxcmStatus.PXCM_STATUS_NO_ERROR)
{
Console.Error.WriteLine("Failed to create the PXCMSession. status = " + status);
return;
}
PXCMCapture.AudioStream.DataDesc request = new PXCMCapture.AudioStream.DataDesc();
request.info.nchannels = 1;
request.info.sampleRate = 44100;
uint subchunk2_data_size = 0;
// Use the capture utility
using (this.session)
using (UtilMCapture capture = new UtilMCapture(this.session))
{
// Locate a stream that meets our request criteria
status = capture.LocateStreams(ref request);
if (status < PXCM_STATUS_NO_ERROR)
{
Console.Error.WriteLine("Unable to locate audio stream. status = " + status);
return;
}
// Set the volume level
status = capture.device.SetProperty(PXCMCapture.Device.Property.PROPERTY_AUDIO_MIX_LEVEL, 0.2f);
if (status < pxcmStatus.PXCM_STATUS_NO_ERROR)
{
Console.Error.WriteLine("Unable to set the volume level. status = " + status);
return;
}
Console.WriteLine("Begin audio recording");
isRecording = true;
// Get the n frames of audio data.
while (isRecording)
{
PXCMScheduler.SyncPoint sp = null;
PXCMAudio audio = null;
// We will asynchronously read the audio stream, which
// will create a synchronization point and a reference
// to an audio object.
status = capture.ReadStreamAsync(out audio, out sp);
if (status < PXCM_STATUS_NO_ERROR)
{
Console.Error.WriteLine("Unable to ReadStreamAsync. status = " + status);
return;
}
using (sp)
using (audio)
{
// For each audio frame
// 1) Synchronize so that you can access to the data
// 2) acquire access
// 3) write data while you have access,
// 4) release access to the data
status = sp.Synchronize();
if (status < PXCM_STATUS_NO_ERROR)
{
Console.Error.WriteLine("Unable to Synchronize. status = " + status);
return;
}
PXCMAudio.AudioData adata;
status = audio.AcquireAccess(PXCMAudio.Access.ACCESS_READ, PXCMAudio.AudioFormat.AUDIO_FORMAT_PCM, out adata);
if (status < PXCM_STATUS_NO_ERROR)
{
Console.Error.WriteLine("Unable to AcquireAccess. status = " + status);
return;
}
byte[] data = adata.ToByteArray();
int len = data.Length;
writer.Write(data, 0, len);
// keep a running total of how much audio data has been captured
subchunk2_data_size += (uint)(adata.dataSize * BYTES_PER_SAMPLE);
audio.ReleaseAccess(ref adata);
}
}
Console.WriteLine("End audio recording");
}
// The header needs to know how much data there is. Now that we are done recording audio
// we know that information and can write out the header and the audio data to a file.
using (BinaryWriter bw = new BinaryWriter(File.Open(output_file_name, FileMode.Create, FileAccess.Write)))
{
bw.Seek(0, SeekOrigin.Begin);
WriteAudioHeader(bw, subchunk2_data_size, (short)request.info.nchannels, request.info.sampleRate);
bw.Write(writer.ToArray());
}
}
}
}
}