diff --git a/Source/OpenTK/Audio/AudioCapture.cs b/Source/OpenTK/Audio/AudioCapture.cs new file mode 100644 index 00000000..427f5577 --- /dev/null +++ b/Source/OpenTK/Audio/AudioCapture.cs @@ -0,0 +1,286 @@ +#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 + } +}