Subversion Repositories AndroidProjects

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1452 chris 1
#region License
2
//
3
// The Open Toolkit Library License
4
//
5
// Copyright (c) 2006 - 2009 the Open Toolkit library.
6
//
7
// Permission is hereby granted, free of charge, to any person obtaining a copy
8
// of this software and associated documentation files (the "Software"), to deal
9
// in the Software without restriction, including without limitation the rights to 
10
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
11
// the Software, and to permit persons to whom the Software is furnished to do
12
// so, subject to the following conditions:
13
//
14
// The above copyright notice and this permission notice shall be included in all
15
// copies or substantial portions of the Software.
16
//
17
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24
// OTHER DEALINGS IN THE SOFTWARE.
25
//
26
#endregion
27
 
28
using System;
29
using System.Collections.Generic;
30
using System.Diagnostics;
31
using System.Runtime.InteropServices;
32
 
33
using OpenTK.Audio.OpenAL;
34
 
35
namespace OpenTK.Audio
36
{
37
 
38
    /// <summary>
39
    /// Provides methods to instantiate, use and destroy an audio device for recording.
40
    /// Static methods are provided to list available devices known by the driver.
41
    /// </summary>
42
    public sealed class AudioCapture : IDisposable
43
    {
44
        #region Fields
45
 
46
        // This must stay private info so the end-user cannot call any Alc commands for the recording device.
47
        IntPtr Handle;
48
 
49
        // Alc.CaptureStop should be called prior to device shutdown, this keeps track of Alc.CaptureStart/Stop calls.
50
        bool _isrecording = false;
51
 
52
        ALFormat sample_format;
53
        int sample_frequency;
54
 
55
        #endregion 
56
 
57
        #region Constructors
58
 
59
        static AudioCapture()
60
        {
61
            if (AudioDeviceEnumerator.IsOpenALSupported) // forces enumeration
62
            {
63
            }
64
        }
65
 
66
        /// <summary>
67
        /// Opens the default device for audio recording.
68
        /// Implicitly set parameters are: 22050Hz, 16Bit Mono, 4096 samples ringbuffer.
69
        /// </summary>
70
        public AudioCapture()
71
            : this(AudioCapture.DefaultDevice, 22050, ALFormat.Mono16, 4096)
72
        {
73
        }
74
 
75
        /// <summary>Opens a device for audio recording.</summary>
76
        /// <param name="deviceName">The device name.</param>
77
        /// <param name="frequency">The frequency that the data should be captured at.</param>
78
        /// <param name="sampleFormat">The requested capture buffer format.</param>
79
        /// <param name="bufferSize">The size of OpenAL's capture internal ring-buffer. This value expects number of samples, not bytes.</param>
80
        public AudioCapture(string deviceName, int frequency, ALFormat sampleFormat, int bufferSize)
81
        {
82
            if (!AudioDeviceEnumerator.IsOpenALSupported)
83
                throw new DllNotFoundException("openal32.dll");
84
            if (frequency <= 0)
85
                throw new ArgumentOutOfRangeException("frequency");
86
            if (bufferSize <= 0)
87
                throw new ArgumentOutOfRangeException("bufferSize");
88
 
89
            // Try to open specified device. If it fails, try to open default device.
90
            device_name = deviceName;
91
            Handle = Alc.CaptureOpenDevice(deviceName, frequency, sampleFormat, bufferSize);
92
 
93
            if (Handle == IntPtr.Zero)
94
            {
95
                Debug.WriteLine(ErrorMessage(deviceName, frequency, sampleFormat, bufferSize));
96
                device_name = "IntPtr.Zero";
97
                Handle = Alc.CaptureOpenDevice(null, frequency, sampleFormat, bufferSize);
98
            }
99
 
100
            if (Handle == IntPtr.Zero)
101
            {
102
                Debug.WriteLine(ErrorMessage("IntPtr.Zero", frequency, sampleFormat, bufferSize));
103
                device_name = AudioDeviceEnumerator.DefaultRecordingDevice;
104
                Handle = Alc.CaptureOpenDevice(AudioDeviceEnumerator.DefaultRecordingDevice, frequency, sampleFormat, bufferSize);
105
            }
106
 
107
            if (Handle == IntPtr.Zero)
108
            {
109
                // Everything we tried failed. Capture may not be supported, bail out.
110
                Debug.WriteLine(ErrorMessage(AudioDeviceEnumerator.DefaultRecordingDevice, frequency, sampleFormat, bufferSize));
111
                device_name = "None";
112
 
113
                throw new AudioDeviceException("All attempts to open capture devices returned IntPtr.Zero. See debug log for verbose list.");
114
            }
115
 
116
            // handle is not null, check for some Alc Error
117
            CheckErrors();
118
 
119
            SampleFormat = sampleFormat;
120
            SampleFrequency = frequency;
121
        }
122
 
123
        #endregion Constructor
124
 
125
        #region Public Members
126
 
127
        #region CurrentDevice
128
 
129
        private string device_name;
130
 
131
        /// <summary>
132
        /// The name of the device associated with this instance.
133
        /// </summary>
134
        public string CurrentDevice
135
        {
136
            get
137
            {
138
                return device_name;
139
            }
140
        }
141
 
142
        #endregion
143
 
144
        #region AvailableDevices
145
 
146
        /// <summary>
147
        /// Returns a list of strings containing all known recording devices.
148
        /// </summary>
149
        public static IList<string> AvailableDevices
150
        {
151
            get
152
            {
153
                return AudioDeviceEnumerator.AvailableRecordingDevices;
154
            }
155
        }
156
 
157
        #endregion
158
 
159
        #region DefaultDevice
160
 
161
        /// <summary>
162
        /// Returns the name of the device that will be used as recording default.
163
        /// </summary>
164
        public static string DefaultDevice
165
        {
166
            get
167
            {
168
                return AudioDeviceEnumerator.DefaultRecordingDevice;
169
            }
170
        }
171
 
172
        #endregion
173
 
174
        #region CheckErrors
175
 
176
        /// <summary>
177
        /// Checks for ALC error conditions.
178
        /// </summary>
179
        /// <exception cref="OutOfMemoryException">Raised when an out of memory error is detected.</exception>
180
        /// <exception cref="AudioValueException">Raised when an invalid value is detected.</exception>
181
        /// <exception cref="AudioDeviceException">Raised when an invalid device is detected.</exception>
182
        /// <exception cref="AudioContextException">Raised when an invalid context is detected.</exception>
183
        public void CheckErrors()
184
        {
185
            new AudioDeviceErrorChecker(Handle).Dispose();
186
        }
187
 
188
        #endregion
189
 
190
        #region CurrentError
191
 
192
        /// <summary>Returns the ALC error code for this device.</summary>
193
        public AlcError CurrentError
194
        {
195
            get
196
            {
197
                return Alc.GetError(Handle);
198
            }
199
        }
200
 
201
        #endregion
202
 
203
        #region Start & Stop
204
 
205
        /// <summary>
206
        /// Start recording samples.
207
        /// The number of available samples can be obtained through the <see cref="AvailableSamples"/> property.
208
        /// The data can be queried with any <see cref="ReadSamples(IntPtr, int)"/> method.
209
        /// </summary>
210
        public void Start()
211
        {
212
            Alc.CaptureStart(Handle);
213
            _isrecording = true;
214
        }
215
 
216
        /// <summary>Stop recording samples. This will not clear previously recorded samples.</summary>
217
        public void Stop()
218
        {
219
            Alc.CaptureStop(Handle);
220
            _isrecording = false;
221
        }
222
 
223
        #endregion Start & Stop Capture
224
 
225
        #region AvailableSamples
226
 
227
        /// <summary>Returns the number of available samples for capture.</summary>
228
        public int AvailableSamples
229
        {
230
            get
231
            {
232
                // TODO: Investigate inconsistency between documentation and actual usage.
233
                // Doc claims the 3rd param is Number-of-Bytes, but it appears to be Number-of-Int32s
234
                int result;
235
                Alc.GetInteger(Handle, AlcGetInteger.CaptureSamples, 1, out result);
236
                return result;
237
            }
238
        }
239
 
240
        #endregion Available samples property
241
 
242
        #region ReadSamples
243
 
244
        /// <summary>Fills the specified buffer with samples from the internal capture ring-buffer. This method does not block: it is an error to specify a sampleCount larger than AvailableSamples.</summary>
245
        /// <param name="buffer">A pointer to a previously initialized and pinned array.</param>
246
        /// <param name="sampleCount">The number of samples to be written to the buffer.</param>
247
        public void ReadSamples(IntPtr buffer, int sampleCount)
248
        {
249
            Alc.CaptureSamples(Handle, buffer, sampleCount);
250
        }
251
 
252
        /// <summary>Fills the specified buffer with samples from the internal capture ring-buffer. This method does not block: it is an error to specify a sampleCount larger than AvailableSamples.</summary>
253
        /// <param name="buffer">The buffer to fill.</param>
254
        /// <param name="sampleCount">The number of samples to be written to the buffer.</param>
255
        /// <exception cref="System.ArgumentNullException">Raised when buffer is null.</exception>
256
        /// <exception cref="System.ArgumentOutOfRangeException">Raised when sampleCount is larger than the buffer.</exception>
257
        public void ReadSamples<TBuffer>(TBuffer[] buffer, int sampleCount)
258
            where TBuffer : struct
259
        {
260
            if (buffer == null)
261
                throw new ArgumentNullException("buffer");
262
 
263
            int buffer_size = BlittableValueType<TBuffer>.Stride * buffer.Length;
264
            // This is more of a heuristic than a 100% valid check. However, it will work
265
            // correctly for 99.9% of all use cases.
266
            // This should never produce a false positive, but a false negative might
267
            // be produced with compressed sample formats (which are very rare).
268
            // Still, this is better than no check at all.
269
            if (sampleCount * GetSampleSize(SampleFormat) > buffer_size)
270
                throw new ArgumentOutOfRangeException("sampleCount");
271
 
272
            GCHandle buffer_ptr = GCHandle.Alloc(buffer, GCHandleType.Pinned);
273
            try { ReadSamples(buffer_ptr.AddrOfPinnedObject(), sampleCount); }
274
            finally { buffer_ptr.Free(); }
275
        }
276
 
277
        #endregion
278
 
279
        #region SampleFormat & SampleFrequency
280
 
281
        /// <summary>
282
        /// Gets the OpenTK.Audio.ALFormat for this instance.
283
        /// </summary>
284
        public ALFormat SampleFormat
285
        {
286
            get { return sample_format; }
287
            private set { sample_format = value; }
288
        }
289
 
290
        /// <summary>
291
        /// Gets the sampling rate for this instance.
292
        /// </summary>
293
        public int SampleFrequency
294
        {
295
            get { return sample_frequency; }
296
            private set { sample_frequency = value; }
297
        }
298
 
299
        #endregion
300
 
301
        #region IsRunning
302
 
303
        /// <summary>
304
        /// Gets a value indicating whether this instance is currently capturing samples.
305
        /// </summary>
306
        public bool IsRunning
307
        {
308
            get { return _isrecording; }
309
        }
310
 
311
        #endregion
312
 
313
        #endregion
314
 
315
        #region Private Members
316
 
317
        // Retrieves the sample size in bytes for various ALFormats.
318
        // Compressed formats always return 1.
319
        static int GetSampleSize(ALFormat format)
320
        {
321
            switch (format)
322
            {
323
                case ALFormat.Mono8: return 1;
324
                case ALFormat.Mono16: return 2;
325
                case ALFormat.Stereo8: return 2;
326
                case ALFormat.Stereo16: return 4;
327
                case ALFormat.MonoFloat32Ext: return 4;
328
                case ALFormat.MonoDoubleExt: return 8;
329
                case ALFormat.StereoFloat32Ext: return 8;
330
                case ALFormat.StereoDoubleExt: return 16;
331
 
332
                case ALFormat.MultiQuad8Ext: return 4;
333
                case ALFormat.MultiQuad16Ext: return 8;
334
                case ALFormat.MultiQuad32Ext: return 16;
335
 
336
                case ALFormat.Multi51Chn8Ext: return 6;
337
                case ALFormat.Multi51Chn16Ext: return 12;
338
                case ALFormat.Multi51Chn32Ext: return 24;
339
 
340
                case ALFormat.Multi61Chn8Ext: return 7;
341
                case ALFormat.Multi71Chn16Ext: return 14;
342
                case ALFormat.Multi71Chn32Ext: return 28;
343
 
344
                case ALFormat.MultiRear8Ext: return 1;
345
                case ALFormat.MultiRear16Ext: return 2;
346
                case ALFormat.MultiRear32Ext: return 4;
347
 
348
                default: return 1; // Unknown sample size.
349
            }
350
        }
351
 
352
        // Converts an error code to an error string with additional information.
353
        string ErrorMessage(string devicename, int frequency, ALFormat bufferformat, int buffersize)
354
        {
355
            string alcerrmsg;
356
            AlcError alcerrcode = CurrentError;
357
            switch (alcerrcode)
358
            {
359
                case AlcError.OutOfMemory:
360
                    alcerrmsg = alcerrcode.ToString() + ": The specified device is invalid, or can not capture audio.";
361
                    break;
362
                case AlcError.InvalidValue:
363
                    alcerrmsg = alcerrcode.ToString() + ": One of the parameters has an invalid value.";
364
                    break;
365
                default:
366
                    alcerrmsg = alcerrcode.ToString();
367
                    break;
368
            }
369
            return "The handle returned by Alc.CaptureOpenDevice is null." +
370
                   "\nAlc Error: " + alcerrmsg +
371
                   "\nDevice Name: " + devicename +
372
                   "\nCapture frequency: " + frequency +
373
                   "\nBuffer format: " + bufferformat +
374
                   "\nBuffer Size: " + buffersize;
375
        }
376
 
377
        #endregion
378
 
379
        #region IDisposable Members
380
 
381
        /// <summary>
382
        /// Finalizes this instance.
383
        /// </summary>
384
        ~AudioCapture()
385
        {
386
            Dispose();
387
        }
388
 
389
        private bool IsDisposed;
390
 
391
        /// <summary>Closes the device and disposes the instance.</summary>
392
        public void Dispose()
393
        {
394
            this.Dispose(true);
395
            GC.SuppressFinalize(this);
396
        }
397
 
398
        private void Dispose(bool manual)
399
        {
400
            if (!this.IsDisposed)
401
            {
402
                if (this.Handle != IntPtr.Zero)
403
                {
404
                    if (this._isrecording)
405
                        this.Stop();
406
 
407
                    Alc.CaptureCloseDevice(this.Handle);
408
                }
409
                this.IsDisposed = true;
410
            }
411
        }
412
 
413
        #endregion Destructor
414
    }
415
}