From bdd49a2aa13c6671dc08ccc323648ba9661be0c1 Mon Sep 17 00:00:00 2001 From: the_fiddler Date: Fri, 17 Jul 2009 22:59:50 +0000 Subject: [PATCH] Added SampleFormat and SampleFrequency properties. Added CheckErrors method. Renamed GetSamples to ReadSamples and added generic overload. Improved error checking code. --- Source/OpenTK/Audio/AudioCapture.cs | 305 ++++++++++++++++++---------- 1 file changed, 199 insertions(+), 106 deletions(-) diff --git a/Source/OpenTK/Audio/AudioCapture.cs b/Source/OpenTK/Audio/AudioCapture.cs index 427f5577..398c15b9 100644 --- a/Source/OpenTK/Audio/AudioCapture.cs +++ b/Source/OpenTK/Audio/AudioCapture.cs @@ -27,7 +27,8 @@ using System; using System.Collections.Generic; -using System.Diagnostics; +using System.Diagnostics; +using System.Runtime.InteropServices; namespace OpenTK.Audio { @@ -38,13 +39,18 @@ namespace OpenTK.Audio /// 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; + #region Fields - /// Alc.CaptureStop should be called prior to device shutdown, this keeps track of Alc.CaptureStart/Stop calls. - private bool _isrecording = false; - #endregion private fields + // This must stay private info so the end-user cannot call any Alc commands for the recording device. + IntPtr Handle; + + // Alc.CaptureStop should be called prior to device shutdown, this keeps track of Alc.CaptureStart/Stop calls. + bool _isrecording = false; + + ALFormat sample_format; + int sample_frequency; + + #endregion #region Device Name private string device_name; @@ -68,8 +74,8 @@ namespace OpenTK.Audio } } - /// Returns the name of the device that will be used as recording default. - public static string Default + /// Returns the name of the device that will be used as recording default. + public static string DefaultDevice { get { @@ -87,106 +93,62 @@ namespace OpenTK.Audio } } - #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) + public AudioCapture() + : this(AudioCapture.DefaultDevice, 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) + /// 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 sampleFormat, int bufferSize) { if (!AudioDeviceEnumerator.IsOpenALSupported) throw new DllNotFoundException("openal32.dll"); - device_name = devicename; - Handle = Alc.CaptureOpenDevice(devicename, frequency, bufferformat, buffersize); + // Try to open specified device. If it fails, try to open default device. + device_name = deviceName; + Handle = Alc.CaptureOpenDevice(deviceName, frequency, sampleFormat, bufferSize); + if (Handle == IntPtr.Zero) { - ErrorMessages.Add(ErrorMessage(devicename, frequency, bufferformat, buffersize)); + Debug.WriteLine(ErrorMessage(deviceName, frequency, sampleFormat, bufferSize)); device_name = "IntPtr.Zero"; - Handle = Alc.CaptureOpenDevice(null, frequency, bufferformat, buffersize); + Handle = Alc.CaptureOpenDevice(null, frequency, sampleFormat, bufferSize); } + if (Handle == IntPtr.Zero) - { - ErrorMessages.Add(ErrorMessage("IntPtr.Zero", frequency, bufferformat, buffersize)); + { + Debug.WriteLine(ErrorMessage("IntPtr.Zero", frequency, sampleFormat, bufferSize)); device_name = AudioDeviceEnumerator.DefaultRecordingDevice; - Handle = Alc.CaptureOpenDevice(AudioDeviceEnumerator.DefaultRecordingDevice, frequency, bufferformat, buffersize); + Handle = Alc.CaptureOpenDevice(AudioDeviceEnumerator.DefaultRecordingDevice, frequency, sampleFormat, bufferSize); } + if (Handle == IntPtr.Zero) - { - ErrorMessages.Add(ErrorMessage(AudioDeviceEnumerator.DefaultRecordingDevice, frequency, bufferformat, buffersize)); - // everything failed + { + // Everything we tried failed. Capture may not be supported, bail out. + Debug.WriteLine(ErrorMessage(AudioDeviceEnumerator.DefaultRecordingDevice, frequency, sampleFormat, bufferSize)); 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()); - } + // handle is not null, check for some Alc Error + CheckErrors(); + + SampleFormat = sampleFormat; + SampleFrequency = frequency; } #endregion Constructor - #region Destructor + #region IDisposable Members ~AudioCapture() { @@ -217,12 +179,30 @@ namespace OpenTK.Audio } } - #endregion Destructor + #endregion Destructor + + #region Public Members + + #region CheckErrors + + /// + /// Checks for ALC error conditions. + /// + /// Raised when an out of memory error is detected. + /// Raised when an invalid value is detected. + /// Raised when an invalid device is detected. + /// Raised when an invalid context is detected. + public void CheckErrors() + { + new AudioDeviceErrorChecker(Handle).Dispose(); + } - #region Error Checking - - /// Returns the first encountered Alc Error by this device. - public AlcError CurrentAlcError + #endregion + + #region CurrentError + + /// Returns the ALC error code for this device. + public AlcError CurrentError { get { @@ -230,14 +210,14 @@ namespace OpenTK.Audio } } - #endregion Error Checking + #endregion - #region Start & Stop Capture + #region Start & Stop /// /// Start recording samples. - /// The number of available samples can be obtained through the AvailableSamples property. - /// The data can be queried with any GetSamples() method. + /// The number of available samples can be obtained through the property. + /// The data can be queried with any method. /// public void Start() { @@ -252,10 +232,10 @@ namespace OpenTK.Audio _isrecording = false; } - #endregion Start & Stop Capture - - #region Available samples property - + #endregion Start & Stop Capture + + #region AvailableSamples + /// Returns the number of available samples for capture. public int AvailableSamples { @@ -269,18 +249,131 @@ namespace OpenTK.Audio } } - #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 Available samples property + + #region ReadSamples + + /// 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. + /// A pointer to a previously initialized and pinned array. + /// The number of samples to be written to the buffer. + public void ReadSamples(IntPtr buffer, int sampleCount) + { + Alc.CaptureSamples(Handle, buffer, sampleCount); + } + + /// 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. + /// The buffer to fill. + /// The number of samples to be written to the buffer. + /// Raised when buffer is null. + /// Raised when sampleCount is larger than the buffer. + public void ReadSamples(TBuffer[] buffer, int sampleCount) + where TBuffer : struct + { + if (buffer == null) + throw new ArgumentNullException("buffer"); + + int buffer_size = BlittableValueType.Stride * buffer.Length; + // This is more of a heuristic than a 100% valid check. However, it will work + // correctly for 99.9% of all use cases. + // This should never produce a false positive, but a false negative might + // be produced with compressed sample formats (which are very rare). + // Still, this is better than no check at all. + if (sampleCount * GetSampleSize(SampleFormat) > buffer_size) + throw new ArgumentOutOfRangeException("sampleCount"); + + GCHandle buffer_ptr = GCHandle.Alloc(buffer, GCHandleType.Pinned); + try { ReadSamples(buffer_ptr.AddrOfPinnedObject(), sampleCount); } + finally { buffer_ptr.Free(); } } - #endregion Capture previously recorded samples + #endregion + + #region SampleFormat & SampleFrequency + + /// + /// Gets the OpenTK.Audio.ALFormat for this instance. + /// + public ALFormat SampleFormat + { + get { return sample_format; } + private set { sample_format = value; } + } + + /// + /// Gets the sampling rate for this instance. + /// + public int SampleFrequency + { + get { return sample_frequency; } + private set { sample_frequency = value; } + } + + #endregion + + #endregion + + #region Private Members + + // Retrieves the sample size in bytes for various ALFormats. + // Compressed formats always return 1. + static int GetSampleSize(ALFormat format) + { + switch (format) + { + case ALFormat.Mono8: return 1; + case ALFormat.Mono16: return 2; + case ALFormat.Stereo8: return 2; + case ALFormat.Stereo16: return 4; + case ALFormat.MonoFloat32Ext: return 4; + case ALFormat.MonoDoubleExt: return 8; + case ALFormat.StereoFloat32Ext: return 8; + case ALFormat.StereoDoubleExt: return 16; + + case ALFormat.MultiQuad8Ext: return 4; + case ALFormat.MultiQuad16Ext: return 8; + case ALFormat.MultiQuad32Ext: return 16; + + case ALFormat.Multi51Chn8Ext: return 6; + case ALFormat.Multi51Chn16Ext: return 12; + case ALFormat.Multi51Chn32Ext: return 24; + + case ALFormat.Multi61Chn8Ext: return 7; + case ALFormat.Multi71Chn16Ext: return 14; + case ALFormat.Multi71Chn32Ext: return 28; + + case ALFormat.MultiRear8Ext: return 1; + case ALFormat.MultiRear16Ext: return 2; + case ALFormat.MultiRear32Ext: return 4; + + default: return 1; // Unknown sample size. + } + } + + // Converts an error code to an error string with additional information. + string ErrorMessage(string devicename, int frequency, ALFormat bufferformat, int buffersize) + { + string alcerrmsg; + AlcError alcerrcode = CurrentError; + 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; + } + + #endregion } }