-
-
Notifications
You must be signed in to change notification settings - Fork 86
/
Copy pathSoundRecorder.cs
345 lines (302 loc) · 15.7 KB
/
SoundRecorder.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
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
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
using System;
using System.Runtime.InteropServices;
using System.Security;
using SFML.System;
namespace SFML.Audio
{
////////////////////////////////////////////////////////////
/// <summary>
/// Base class intended for capturing sound data
/// </summary>
////////////////////////////////////////////////////////////
public abstract class SoundRecorder : ObjectBase
{
////////////////////////////////////////////////////////////
/// <summary>
/// Default constructor
/// </summary>
////////////////////////////////////////////////////////////
public SoundRecorder() :
base(IntPtr.Zero)
{
_startCallback = new StartCallback(StartRecording);
_processCallback = new ProcessCallback(ProcessSamples);
_stopCallback = new StopCallback(StopRecording);
CPointer = sfSoundRecorder_create(_startCallback, _processCallback, _stopCallback, IntPtr.Zero);
}
////////////////////////////////////////////////////////////
/// <summary>
/// Start the capture using the default sample rate (44100 Hz).
///
/// Please note that only one capture can happen at the same time.
/// </summary>
////////////////////////////////////////////////////////////
public bool Start() => Start(44100);
////////////////////////////////////////////////////////////
/// <summary>
/// Start the capture.
///
/// The sampleRate parameter defines the number of audio samples
/// captured per second. The higher, the better the quality
/// (for example, 44100 samples/sec is CD quality).
/// This function uses its own thread so that it doesn't block
/// the rest of the program while the capture runs.
///
/// Please note that only one capture can happen at the same time.
/// </summary>
/// <param name="sampleRate"> Sound frequency; the more samples, the higher the quality (44100 by default = CD quality)</param>
////////////////////////////////////////////////////////////
public bool Start(uint sampleRate) => sfSoundRecorder_start(CPointer, sampleRate);
////////////////////////////////////////////////////////////
/// <summary>
/// Stop the capture
/// </summary>
////////////////////////////////////////////////////////////
public void Stop() => sfSoundRecorder_stop(CPointer);
////////////////////////////////////////////////////////////
/// <summary>
/// Sample rate of the sound recorder.
/// </summary>
///
/// <remarks>
/// The sample rate defines the number of audio samples
/// captured per second. The higher, the better the quality
/// (for example, 44100 samples/sec is CD quality).
/// </remarks>
////////////////////////////////////////////////////////////
public uint SampleRate => sfSoundRecorder_getSampleRate(CPointer);
////////////////////////////////////////////////////////////
/// <summary>
/// Get/Set the channel count of the audio capture device
/// </summary>
///
/// <remarks>
/// This method allows you to specify the number of channels
/// used for recording. Currently only 16-bit mono (1) and
/// 16-bit stereo (2) are supported.
/// </remarks>
////////////////////////////////////////////////////////////
public uint ChannelCount
{
get => sfSoundRecorder_getChannelCount(CPointer);
set => sfSoundRecorder_setChannelCount(CPointer, value);
}
////////////////////////////////////////////////////////////
/// <summary>
/// Get the map of position in sample frame to sound channel
/// <para/>
/// This is used to map a sample in the sample stream to a
/// position during spatialisation.
/// </summary>
////////////////////////////////////////////////////////////
public ReadOnlySpan<SoundChannel> ChannelMap
{
get
{
unsafe
{
var channels = sfSoundRecorder_getChannelMap(CPointer, out var count);
Array.Resize(ref _channels, (int)count);
for (var i = 0; i < _channels.Length; i++)
{
_channels[i] = channels[i];
}
return _channels;
}
}
}
////////////////////////////////////////////////////////////
/// <summary>
/// Check if the system supports audio capture.
/// </summary>
///
/// <remarks>
/// This function should always be called before using
/// the audio capture features. If it returns false, then
/// any attempt to use the SoundRecorder or one of its derived
/// classes will fail.
/// </remarks>
////////////////////////////////////////////////////////////
public static bool IsAvailable => sfSoundRecorder_isAvailable();
////////////////////////////////////////////////////////////
/// <summary>
/// Provide a string describing the object
/// </summary>
/// <returns>String description of the object</returns>
////////////////////////////////////////////////////////////
public override string ToString()
{
if (IsInvalid)
{
return MakeDisposedObjectString();
}
return "[SoundRecorder]" + " SampleRate(" + SampleRate + ")";
}
////////////////////////////////////////////////////////////
/// <summary>
/// Start capturing audio data.
///
/// This virtual function may be overridden by a derived class
/// if something has to be done every time a new capture
/// starts. If not, this function can be ignored; the default
/// implementation does nothing.
/// </summary>
/// <returns>False to abort recording audio data, true to continue</returns>
////////////////////////////////////////////////////////////
protected virtual bool OnStart() =>
// Does nothing by default
true;
////////////////////////////////////////////////////////////
/// <summary>
/// Process a new chunk of recorded samples.
///
/// This virtual function is called every time a new chunk of
/// recorded data is available. The derived class can then do
/// whatever it wants with it (storing it, playing it, sending
/// it over the network, etc.).
/// </summary>
/// <param name="samples">Array of samples to process</param>
/// <returns>False to stop recording audio data, true to continue</returns>
////////////////////////////////////////////////////////////
protected abstract bool OnProcessSamples(ReadOnlySpan<short> samples);
////////////////////////////////////////////////////////////
/// <summary>
/// Stop capturing audio data.
///
/// This virtual function may be overridden by a derived class
/// if something has to be done every time the capture
/// ends. If not, this function can be ignored; the default
/// implementation does nothing.
/// </summary>
////////////////////////////////////////////////////////////
protected virtual void OnStop()
{
// Does nothing by default
}
////////////////////////////////////////////////////////////
/// <summary>
/// Get the list of the names of all available audio capture devices
/// </summary>
////////////////////////////////////////////////////////////
public static ReadOnlySpan<string> AvailableDevices
{
get
{
unsafe
{
var devicesPtr = sfSoundRecorder_getAvailableDevices(out var count);
Array.Resize(ref _availableDevices, (int)count);
for (var i = 0; i < _availableDevices.Length; i++)
{
_availableDevices[i] = Marshal.PtrToStringAnsi(devicesPtr[i]);
}
return _availableDevices;
}
}
}
private static string[] _availableDevices;
////////////////////////////////////////////////////////////
/// <summary>
/// Get the name of the default audio capture device
/// </summary>
////////////////////////////////////////////////////////////
public static string DefaultDevice => Marshal.PtrToStringAnsi(sfSoundRecorder_getDefaultDevice());
////////////////////////////////////////////////////////////
/// <summary>
/// Set the audio capture device
/// </summary>
/// <param name="name">The name of the audio capture device</param>
/// <returns>True, if it was able to set the requested device</returns>
////////////////////////////////////////////////////////////
public bool SetDevice(string name) => sfSoundRecorder_setDevice(CPointer, name);
////////////////////////////////////////////////////////////
/// <summary>
/// Get the name of the current audio capture device
/// </summary>
/// <returns>The name of the current audio capture device</returns>
////////////////////////////////////////////////////////////
public string GetDevice() => Marshal.PtrToStringAnsi(sfSoundRecorder_getDevice(CPointer));
////////////////////////////////////////////////////////////
/// <summary>
/// Handle the destruction of the object
/// </summary>
/// <param name="disposing">Is the GC disposing the object, or is it an explicit call ?</param>
////////////////////////////////////////////////////////////
protected override void Destroy(bool disposing) => sfSoundRecorder_destroy(CPointer);
////////////////////////////////////////////////////////////
/// <summary>
/// Function called directly by the C library ; convert
/// arguments and forward them to the internal virtual function
/// </summary>
/// <param name="userData">User data -- unused</param>
/// <returns>False to stop recording audio data, true to continue</returns>
////////////////////////////////////////////////////////////
private bool StartRecording(IntPtr userData) => OnStart();
////////////////////////////////////////////////////////////
/// <summary>
/// Function called directly by the C library ; convert
/// arguments and forward them to the internal virtual function
/// </summary>
/// <param name="samples">Pointer to the array of samples</param>
/// <param name="nbSamples">Number of samples in the array</param>
/// <param name="userData">User data -- unused</param>
/// <returns>False to stop recording audio data, true to continue</returns>
////////////////////////////////////////////////////////////
private bool ProcessSamples(IntPtr samples, UIntPtr nbSamples, IntPtr userData)
{
unsafe
{
return OnProcessSamples(new ReadOnlySpan<short>((void*)samples, (int)nbSamples));
}
}
////////////////////////////////////////////////////////////
/// <summary>
/// Function called directly by the C library ; convert
/// arguments and forward them to the internal virtual function
/// </summary>
/// <param name="userData">User data -- unused</param>
////////////////////////////////////////////////////////////
private void StopRecording(IntPtr userData) => OnStop();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate bool StartCallback(IntPtr userData);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate bool ProcessCallback(IntPtr samples, UIntPtr nbSamples, IntPtr userData);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void StopCallback(IntPtr userData);
private readonly StartCallback _startCallback;
private readonly ProcessCallback _processCallback;
private readonly StopCallback _stopCallback;
private SoundChannel[] _channels;
#region Imports
[DllImport(CSFML.Audio, CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
private static extern IntPtr sfSoundRecorder_create(StartCallback onStart, ProcessCallback onProcess, StopCallback onStop, IntPtr userData);
[DllImport(CSFML.Audio, CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
private static extern void sfSoundRecorder_destroy(IntPtr soundRecorder);
[DllImport(CSFML.Audio, CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.I1)]
private static extern bool sfSoundRecorder_start(IntPtr soundRecorder, uint sampleRate);
[DllImport(CSFML.Audio, CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
private static extern void sfSoundRecorder_stop(IntPtr soundRecorder);
[DllImport(CSFML.Audio, CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
private static extern uint sfSoundRecorder_getSampleRate(IntPtr soundRecorder);
[DllImport(CSFML.Audio, CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.I1)]
private static extern bool sfSoundRecorder_isAvailable();
[DllImport(CSFML.Audio, CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
private static extern unsafe IntPtr* sfSoundRecorder_getAvailableDevices(out UIntPtr count);
[DllImport(CSFML.Audio, CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
private static extern IntPtr sfSoundRecorder_getDefaultDevice();
[DllImport(CSFML.Audio, CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.I1)]
private static extern bool sfSoundRecorder_setDevice(IntPtr soundRecorder, string name);
[DllImport(CSFML.Audio, CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
private static extern IntPtr sfSoundRecorder_getDevice(IntPtr soundRecorder);
[DllImport(CSFML.Audio, CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
private static extern void sfSoundRecorder_setChannelCount(IntPtr soundRecorder, uint channelCount);
[DllImport(CSFML.Audio, CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
private static extern uint sfSoundRecorder_getChannelCount(IntPtr soundRecorder);
[DllImport(CSFML.Audio, CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
private static extern unsafe SoundChannel* sfSoundRecorder_getChannelMap(IntPtr soundRecorder, out UIntPtr count);
#endregion
}
}