|
// -----------------------------------------------------------------------
// <copyright file="SoundSource.cs" company="None.">
// By Philip R. Braica (HoshiKata@aol.com, VeryMadSci@gmail.com)
//
// Distributed under the The Code Project Open License (CPOL)
// http://www.codeproject.com/info/cpol10.aspx
// </copyright>
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.DirectX.DirectSound;
using Microsoft.Win32.SafeHandles;
namespace SoundFiltering
{
/// <summary>
/// Abstracts a DirectX sound input.
/// </summary>
public class SoundSource : IDisposable
{
#region Public events: ProcessData, ProcessFFTData.
/// <summary>
/// Delegate for process data event.
/// </summary>
/// <param name="data"></param>
public delegate void ProcessData_Delegate(float [] data);
/// <summary>
/// Process data event.
/// </summary>
public event ProcessData_Delegate ProcessData;
/// <summary>
/// Process data FFT of entire buffer.
/// </summary>
public event ProcessData_Delegate ProcessFFTData;
#endregion
#region Protected data.
/// <summary>
/// Disposal race condition lock.
/// </summary>
protected object m_disposeLock = new object();
/// <summary>
/// Is disposed flag.
/// </summary>
protected bool m_disposed = false;
/// <summary>
/// DirectX capture.
/// </summary>
protected Capture m_capture = null;
/// <summary>
/// DirectX buffer.
/// </summary>
protected CaptureBuffer m_buffer = null;
/// <summary>
/// Capture thread.
/// </summary>
protected Thread m_captureThread = null;
/// <summary>
/// Capture buffer length.
/// </summary>
protected int m_captureBufferLength = 0;
/// <summary>
/// Notify.
/// </summary>
protected Notify m_notify;
/// <summary>
/// Event to flag a buffer is ready and full.
/// </summary>
protected AutoResetEvent m_bufferPositionEvent = null;
/// <summary>
/// Safe wait event from the position event used to pass into DirectX.
/// </summary>
protected SafeWaitHandle m_bufferPositionEvent_Handle = null;
/// <summary>
/// Manual event used to cause reset.
/// </summary>
protected ManualResetEvent m_threadTerminateEvent = null;
/// <summary>
/// Maximum buffering to use in seconds.
/// </summary>
const int c_bufferTimeInSeconds = 5;
/// <summary>
/// Notification points in seconds.
/// </summary>
const int c_notifiesInSeconds = 2;
#endregion
#region Public properties.
/// <summary>
/// Sources have a GUID associated with them.
/// </summary>
public Guid Guid { get; set; }
/// <summary>
/// Description of the device.
/// </summary>
public string Description { get; set; }
/// <summary>
/// Is this source the default source?
/// </summary>
public bool IsDefault
{
get { return Guid == Guid.Empty; }
}
/// <summary>
/// Is this capturing?
/// </summary>
public bool IsCapturing { get; protected set; }
/// <summary>
/// Get / set the sample rate.
/// </summary>
public int SampleRateHz { get; set; }
#endregion
#region Constructors and dispose.
/// <summary>
/// Constructor.
/// </summary>
/// <param name="id"></param>
/// <param name="name"></param>
public SoundSource(Guid id, string name)
{
Guid = id;
Description = name;
}
/// <summary>
/// Constructor.
/// </summary>
public SoundSource()
{
CaptureDevicesCollection cdc = new CaptureDevicesCollection();
for (int i = 0; i < cdc.Count; i++)
{
if (cdc[i].DriverGuid == Guid.Empty)
{
Guid = cdc[i].DriverGuid;
Description = cdc[i].Description;
break;
}
}
// Create the events.
m_bufferPositionEvent = new AutoResetEvent(false);
m_bufferPositionEvent_Handle = m_bufferPositionEvent.SafeWaitHandle;
m_threadTerminateEvent = new ManualResetEvent(true);
}
/// <summary>
/// Dispose.
/// </summary>
void IDisposable.Dispose()
{
Dispose(true);
}
/// <summary>
/// Destructor.
/// </summary>
~SoundSource()
{
Dispose(false);
}
/// <summary>
/// Dispose.
/// </summary>
/// <param name="disposing"></param>
public void Dispose(bool disposing)
{
lock (m_disposeLock)
{
if (m_disposed) return;
m_disposed = true;
}
GC.SuppressFinalize(this);
if (IsCapturing)
{
Stop();
}
if (m_bufferPositionEvent_Handle != null)
{
m_bufferPositionEvent_Handle.Dispose();
}
if (m_bufferPositionEvent != null)
{
m_bufferPositionEvent.Close();
}
if (m_threadTerminateEvent != null)
{
m_threadTerminateEvent.Close();
}
}
#endregion
#region Public stuff: GetSources, GetDefaultSource, Start, Stop
/// <summary>
/// Get a list of possible things.
/// </summary>
/// <returns></returns>
public static List<SoundSource> GetSources()
{
CaptureDevicesCollection captureDevices = new CaptureDevicesCollection();
List<SoundSource> deviceList = new List<SoundSource>();
foreach (DeviceInformation captureDevice in captureDevices)
{
deviceList.Add(new SoundSource(captureDevice.DriverGuid, captureDevice.Description));
}
return deviceList;
}
/// <summary>
/// Starts capture process.
/// </summary>
public void Start()
{
if (IsCapturing)
{
return;
}
IsCapturing = true;
m_capture = new Capture(Guid);
WaveFormat waveFormat = new WaveFormat();
// If an exception occurs below it's because one of these things is set wrong.
// format.SamplesPerSecond = capture.Caps.Format96KhzStereo16Bit ? 96000 : capture.Caps.Format48KhzStereo16Bit ? 48000 : capture.Caps.Format44KhzStereo16Bit ? 44000 : 22000;
waveFormat.Channels = (short)m_capture.Caps.Channels; // Read in the channels
waveFormat.BitsPerSample = 16; // 16 bits per sample.
// voice is around 40 - 8000 kHz to get plenty of range over 2x is required so 11k not enough prefer 22k.
waveFormat.SamplesPerSecond = 22000;
waveFormat.FormatTag = WaveFormatTag.Pcm;
waveFormat.BlockAlign = (short)((waveFormat.Channels * waveFormat.BitsPerSample + 7) / 8);
waveFormat.AverageBytesPerSecond = waveFormat.BlockAlign * waveFormat.SamplesPerSecond;
m_captureBufferLength = waveFormat.AverageBytesPerSecond * c_bufferTimeInSeconds;
CaptureBufferDescription captureBufferDescription = new CaptureBufferDescription();
captureBufferDescription.Format = waveFormat;
captureBufferDescription.BufferBytes = m_captureBufferLength;
m_buffer = new CaptureBuffer(captureBufferDescription, m_capture);
int waitHandleCount = c_bufferTimeInSeconds * c_notifiesInSeconds;
BufferPositionNotify[] positions = new BufferPositionNotify[waitHandleCount];
for (int i = 0; i < waitHandleCount; i++)
{
BufferPositionNotify position = new BufferPositionNotify();
position.Offset = (i + 1) * m_captureBufferLength / positions.Length - 1;
position.EventNotifyHandle = m_bufferPositionEvent_Handle.DangerousGetHandle();
positions[i] = position;
}
m_notify = new Notify(m_buffer);
m_notify.SetNotificationPositions(positions);
m_threadTerminateEvent.Reset();
m_captureThread = new Thread(new ThreadStart(ThreadLoop));
m_captureThread.Name = "Sound capture";
m_captureThread.Start();
}
/// <summary>
/// Stops capture process.
/// </summary>
public void Stop()
{
if (IsCapturing)
{
IsCapturing = false;
m_threadTerminateEvent.Set();
m_captureThread.Join();
m_notify.Dispose();
m_buffer.Dispose();
m_capture.Dispose();
}
}
#endregion
#region Worker protected stuff.
private void ThreadLoop()
{
m_buffer.Start(true);
try
{
int nextCapturePosition = 0;
WaitHandle[] handles = new WaitHandle[] {m_threadTerminateEvent, m_bufferPositionEvent };
while (WaitHandle.WaitAny(handles) > 0)
{
int capturePosition, readPosition;
m_buffer.GetCurrentPosition(out capturePosition, out readPosition);
int lockSize = readPosition - nextCapturePosition;
if (lockSize < 0) lockSize += m_captureBufferLength;
if ((lockSize & 1) != 0) lockSize--;
int itemsCount = lockSize >> 1;
short[] data = (short[])m_buffer.Read(nextCapturePosition, typeof(short), LockFlag.None, itemsCount);
processData(data);
nextCapturePosition = (nextCapturePosition + lockSize) % m_captureBufferLength;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
m_buffer.Stop();
}
}
/// <summary>
/// Processes the captured data.
/// </summary>
/// <param name="data">Captured data</param>
protected void processData(short[] data)
{
ProcessData_Delegate pdt = ProcessData;
ProcessData_Delegate pfd = ProcessFFTData;
if ((pdt == null) && (pfd == null)) return;
int size = 2;
while (size < data.Length)
{
size = size * 2;
}
float[] xf = new float[size];
float[] xt = new float[data.Length];
for (int i = 0; i < data.Length; i++)
{
float v = (float)data[i];
xf[i] = v;
xt[i] = v;
}
for (int i = data.Length; i < size; i++)
{
xf[i] = 0;
}
if (pdt != null)
{
pdt(xt);
}
if (pfd != null)
{
float[] fft = new float[xf.Length];
Math.FFT.Forward(xf, fft);
pfd(fft);
}
}
#endregion
}
}
|
By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.
If a file you wish to view isn't highlighted, and is a text file (not binary), please
let us know and we'll add colourisation support for it.
Phil is a Principal Software developer focusing on weird yet practical algorithms that run the gamut of embedded and desktop (PID loops, Kalman filters, FFTs, client-server SOAP bindings, ASIC design, communication protocols, game engines, robotics).
In his personal life he is a part time mad scientist, full time dad, and studies small circle jujitsu, plays guitar and piano.