#region License // // The Open Toolkit Library License // // Copyright (c) 2006 - 2009 the Open Toolkit library. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do // so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // #endregion using System; using System.Collections.Generic; using System.Diagnostics; namespace OpenTK.Audio { /// /// Provides methods to instantiate, use and destroy an audio device for recording. /// Static methods are provided to list available devices known by the driver. /// public sealed class AudioCapture : IDisposable { #region private fields /// This must stay private info so the end-user cannot call any Alc commands for the recording device. private IntPtr Handle; /// Alc.CaptureStop should be called prior to device shutdown, this keeps track of Alc.CaptureStart/Stop calls. private bool _isrecording = false; #endregion private fields #region Device Name private string device_name; /// The name of the device associated with this instance. public string CurrentDeviceName { get { return device_name; } } #endregion Device Name #region public static properties /// Returns a list of strings containing all known recording devices. public static IList AvailableDevices { get { return AudioDeviceEnumerator.AvailableRecordingDevices; } } /// Returns the name of the device that will be used as recording default. public static string Default { get { return AudioDeviceEnumerator.DefaultRecordingDevice; } } #endregion public static properties #region Constructor static AudioCapture() { if (AudioDeviceEnumerator.IsOpenALSupported) // forces enumeration { } } #region Internal Error handling private string ErrorMessage(string devicename, uint frequency, ALFormat bufferformat, int buffersize) { string alcerrmsg; AlcError alcerrcode = Alc.GetError(IntPtr.Zero); switch (alcerrcode) { case AlcError.OutOfMemory: alcerrmsg = alcerrcode.ToString() + ": The specified device is invalid, or can not capture audio."; break; case AlcError.InvalidValue: alcerrmsg = alcerrcode.ToString() + ": One of the parameters has an invalid value."; break; default: alcerrmsg = alcerrcode.ToString(); break; } return "The handle returned by Alc.CaptureOpenDevice is null." + "\nAlc Error: " + alcerrmsg + "\nDevice Name: " + devicename + "\nCapture frequency: " + frequency + "\nBuffer format: " + bufferformat + "\nBuffer Size: " + buffersize; } private List ErrorMessages = new List(); #endregion Internal Error handling /// /// Opens the default device for audio recording. /// Implicitly set parameters are: 22050Hz, 16Bit Mono, 4096 samples ringbuffer. /// public AudioCapture():this(AudioCapture.Default, 22050, ALFormat.Mono16, 4096) { } /// Opens a device for audio recording. /// The device name. /// The frequency that the data should be captured at. /// The requested capture buffer format. /// The size of OpenAL's capture internal ring-buffer. This value expects number of samples, not bytes. public AudioCapture(string devicename, int frequency, ALFormat bufferformat, int buffersize) : this(devicename, (uint)frequency, bufferformat, buffersize) { } /// Opens a device for audio recording. /// The device name. /// The frequency that the data should be captured at. /// The requested capture buffer format. /// The size of OpenAL's capture internal ring-buffer. This value expects number of samples, not bytes. [CLSCompliant(false)] public AudioCapture(string devicename, uint frequency, ALFormat bufferformat, int buffersize) { if (!AudioDeviceEnumerator.IsOpenALSupported) throw new DllNotFoundException("openal32.dll"); device_name = devicename; Handle = Alc.CaptureOpenDevice(devicename, frequency, bufferformat, buffersize); if (Handle == IntPtr.Zero) { ErrorMessages.Add(ErrorMessage(devicename, frequency, bufferformat, buffersize)); device_name = "IntPtr.Zero"; Handle = Alc.CaptureOpenDevice(null, frequency, bufferformat, buffersize); } if (Handle == IntPtr.Zero) { ErrorMessages.Add(ErrorMessage("IntPtr.Zero", frequency, bufferformat, buffersize)); device_name = AudioDeviceEnumerator.DefaultRecordingDevice; Handle = Alc.CaptureOpenDevice(AudioDeviceEnumerator.DefaultRecordingDevice, frequency, bufferformat, buffersize); } if (Handle == IntPtr.Zero) { ErrorMessages.Add(ErrorMessage(AudioDeviceEnumerator.DefaultRecordingDevice, frequency, bufferformat, buffersize)); // everything failed device_name = "None"; foreach (string s in ErrorMessages) Debug.WriteLine(s); throw new AudioDeviceException("All attempts to open capture devices returned IntPtr.Zero. See debug log for verbose list."); } // handle is not null, check for some Alc Error AlcError err = this.CurrentAlcError; switch (err) { case AlcError.NoError: // everything went fine, do nothing break; case AlcError.OutOfMemory: throw new AudioDeviceException("Alc.CaptureOpenDevice (Handle: " + Handle + ") reports Alc Error (" + err.ToString() + ") The specified device is invalid, or can not capture audio."); case AlcError.InvalidValue: throw new AudioDeviceException("Alc.CaptureOpenDevice (Handle: " + Handle + ") reports Alc Error (" + err.ToString() + ") One of the parameters has an invalid value."); default: throw new AudioDeviceException("Alc.CaptureOpenDevice (Handle: " + Handle + ") reports Alc Error: " + err.ToString()); } } #endregion Constructor #region Destructor ~AudioCapture() { Dispose(); } private bool IsDisposed; /// Closes the device and disposes the instance. public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool manual) { if (!this.IsDisposed) { if (this.Handle != IntPtr.Zero) { if (this._isrecording) this.Stop(); Alc.CaptureCloseDevice(this.Handle); } this.IsDisposed = true; } } #endregion Destructor #region Error Checking /// Returns the first encountered Alc Error by this device. public AlcError CurrentAlcError { get { return Alc.GetError(Handle); } } #endregion Error Checking #region Start & Stop Capture /// /// Start recording samples. /// The number of available samples can be obtained through the AvailableSamples property. /// The data can be queried with any GetSamples() method. /// public void Start() { Alc.CaptureStart(Handle); _isrecording = true; } /// Stop recording samples. This will not clear previously recorded samples. public void Stop() { Alc.CaptureStop(Handle); _isrecording = false; } #endregion Start & Stop Capture #region Available samples property /// Returns the number of available samples for capture. public int AvailableSamples { get { // TODO: Investigate inconsistency between documentation and actual usage. // Doc claims the 3rd param is Number-of-Bytes, but it appears to be Number-of-Int32s int result; Alc.GetInteger(Handle, AlcGetInteger.CaptureSamples, 1, out result); return result; } } #endregion Available samples property #region Capture previously recorded samples /// This function will write previously recorded samples to a location in memory. It does not block. /// A pointer to a previously initialized and pinned array. /// The number of samples to be written to the buffer. public void GetSamples(IntPtr buffer, int samplecount) { Alc.CaptureSamples(Handle, buffer, samplecount); } #endregion Capture previously recorded samples } }