Capturing and streaming sound by using DirectSound with C#

25 בדצמבר 2008

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/capturing-and-streaming-sound-by-using-directsound-with-c/]


I already wrote a little about managed way to use DirectX DirectSound. Today we’ll speak about how to get sound from your microphone or any other DirectSound capturing device (such as FM receiver) and stream it out to your PC speakers and any other DirectSound Output device. So, let’s start creating our first echo service by using managed DirectX.

image

First of all we should decide what Wave format we want to use for capturing and recording. So, let’s choose anything reasonable :)

var format = new WaveFormat {
            SamplesPerSecond = 96000,
            BitsPerSample = 16,
            Channels = 2,
            FormatTag = WaveFormatTag.Pcm
         };

Now, we should calculate block align and average byte per second value for this format. I’m wondering why it cannot be done automatically…

format.BlockAlign = (short)(format.Channels * (format.BitsPerSample / 8));
format.AverageBytesPerSecond = format.SamplesPerSecond * format.BlockAlign;

Next step is to set the size of two buffers – one for input and other for output. Generally those buffers are circular, and capturing one should be twice bigger, then output. Why? Because we choose two channels to use. Also, we should decide about chunk size of the buffer, we want to signal when filled.

_dwNotifySize = Math.Max(4096, format.AverageBytesPerSecond / 8);
_dwNotifySize -= _dwNotifySize % format.BlockAlign;
_dwCaptureBufferSize = NUM_BUFFERS * _dwNotifySize;
_dwOutputBufferSize = NUM_BUFFERS * _dwNotifySize / 2;

Next step is to create CaptureBufferDescriptor and actual capturing buffer. We’ll enumerate all devices and choose one, satisfies given string (captureDescriptor) – for example “Mic” :)

var cap = default(Capture);
var cdc = new CaptureDevicesCollection();
for (int i = 0; i < cdc.Count; i++) {
   if (cdc[i].Description.ToLower().Contains(captureDescriptor.ToLower())) {
      cap = new Capture(cdc[i].DriverGuid);
      break;
   }
}
var capDesc = new CaptureBufferDescription {
   Format = format,
   BufferBytes = _dwCaptureBufferSize
};
_dwCapBuffer = new CaptureBuffer(capDesc, cap);

Then we’ll create output device and buffer. To simplify program, we will use default speakers to output, however, you can choose output device the same way we did for capturing. Also, because DirectSound uses any window as it’s message pump, we have to use SetCooperativeLevel method. In my case (windowless application), I’ll use desktop window as message broker. This why you will have to add Windows.Forms as reference for your project, even if it console application. Also, do not forget to set GlobalFocus value to True, if you want to play echo, even if desktop window is not focused.

var dev = new Device();
dev.SetCooperativeLevel(Native.GetDesktopWindow(), CooperativeLevel.Priority);

var devDesc = new BufferDescription {
   BufferBytes = _dwOutputBufferSize,
   Format = format,
   DeferLocation = true,
   GlobalFocus = true
};
_dwDevBuffer = new SecondaryBuffer(devDesc, dev);

Now, we will subscribe to buffer notifications and set autoResetEvent to be fired when it filled up.

var _resetEvent = new AutoResetEvent(false);
var _notify = new Notify(_dwCapBuffer);
//half&half
var bpn1 = new BufferPositionNotify();
bpn1.Offset = _dwCapBuffer.Caps.BufferBytes / 2 – 1;
bpn1.EventNotifyHandle = _resetEvent.SafeWaitHandle.DangerousGetHandle();
var bpn2 = new BufferPositionNotify();
bpn2.Offset = _dwCapBuffer.Caps.BufferBytes – 1;
bpn2.EventNotifyHandle = _resetEvent.SafeWaitHandle.DangerousGetHandle();

_notify.SetNotificationPositions(new BufferPositionNotify[] { bpn1, bpn2 });

Almost done, the only thing we should do is to fire worker thread to take care on messages

int offset = 0;
_dwCaptureThread = new Thread((ThreadStart)delegate {
   _dwCapBuffer.Start(true);

   while (IsReady) {
      _resetEvent.WaitOne();
      var read = _dwCapBuffer.Read(offset, typeof(byte), LockFlag.None, _dwOutputBufferSize);
      _dwDevBuffer.Write(0, read, LockFlag.EntireBuffer);
      offset = (offset + _dwOutputBufferSize) % _dwCaptureBufferSize;
      _dwDevBuffer.SetCurrentPosition(0);
      _dwDevBuffer.Play(0, BufferPlayFlags.Default);
   }
   _dwCapBuffer.Stop();
});
_dwCaptureThread.Start();

That’s it. Compile and run. Now if you’ll speak, you can hear your echo from PC speakers.

Merry Christmas for whom concerns and be good people – do not scare your co-workers with strange sounds – be polite and make the volume lower :)

הוסף תגובה
facebook linkedin twitter email

כתיבת תגובה

האימייל לא יוצג באתר. (*) שדות חובה מסומנים