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.
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
December 25th, 2008 · Comments (47)
Creating transparent buttons, panels and other control with Compact Framework and putting one into other
In WPF/Silverlight world it’s very simple to make transparent controls and put anything inside anything. However, that’s not the situation in WinForms, and even worth in the world of compact devices with CF. Within this worlds, there is only one way to make controls transparent – to use color masks. Today, we’ll create transparent controls with Compact Framework and put it into panel, which has image background.
So let’s start. First of all, we need create our own control. For this purpose, we have to inherit from Control and override couple of things. More precise: OnPaint and OnPaintBackground. We do not want to paint background for transparent control, so let’s prevent it.
public class TransparentImageButton : Control
protected override void OnPaintBackground(PaintEventArgs e) {
//prevent
}protected override void OnPaint(PaintEventArgs e) {
Next, we have to get graphics, delivered by OnPain event argument and draw our image over it. However, BitBlt (which is used by core graphics system) is not very fast method, so it’s better for us to draw everything first and then copy final image to the device.
Graphics gxOff;
Rectangle imgRect;
var image = (_isPressed && PressedImage != null) ? PressedImage : Image;if (_imgOffscreen == null) {
_imgOffscreen = new Bitmap(ClientSize.Width, ClientSize.Height);
}gxOff = Graphics.FromImage(_imgOffscreen);
gxOff.Clear(this.BackColor);
…
if (image != null) {
var imageLeft = (this.Width – image.Width) / 2;
var imageTop = (this.Height – image.Height) / 2;if (!_isPressed) imgRect = new Rectangle(imageLeft, imageTop, image.Width, image.Height);
else imgRect = new Rectangle(imageLeft + 1, imageTop + 1, image.Width, image.Height);
var imageAttr = new ImageAttributes();
To make images transparent, we have to use (as mentioned earlier) transparency color key (to tell windows what color it should not draw. We can code or provide this value to detect it by hitting any pixel on the image. Just like this:
public static Color BackgroundImageColor(this Bitmap bmp) {
return bmp.GetPixel(0, 0);
}
Now we can keep working.
imageAttr.SetColorKey(image.BackgroundImageColor(), image.BackgroundImageColor());
gxOff.DrawImage(image, imgRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, imageAttr);
} if (_isPressed) {
var rc = this.ClientRectangle;
rc.Width–;
rc.Height–;
gxOff.DrawRectangle(new Pen(Color.Black), rc);
}
e.Graphics.DrawImage(_imgOffscreen, 0, 0);
Also, we have to provide others with possibility to handle this even too, thus we will not forget to add base.OnPaint(e); at the end.
Next step is to detect whether our button is clicked or not. We’ll override keyboard and mouse events to detect this.
protected override void OnKeyDown(KeyEventArgs e) {
_isPressed = this.Focused; this.Invalidate();
base.OnKeyDown(e);
}protected override void OnKeyUp(KeyEventArgs e) {
_isPressed = false; this.Invalidate();
base.OnKeyUp(e);
}protected override void OnMouseDown(MouseEventArgs e) {
_isPressed = this.Focused; this.Invalidate();
base.OnMouseDown(e);
}protected override void OnMouseUp(MouseEventArgs e) {
_isPressed = false; this.Invalidate();
base.OnMouseUp(e);
}
Compile and run to see no problem, when our transparent button lies on solid color control, however, we want to put it into panel with background – just like this one. In this case, you can use real transparent PNG and GIF images, also you can replace transparent color with well known Magenta (or any other color).
public class ImagePanel : Panel {
public Bitmap Image { get; set; }
protected override void OnPaintBackground(PaintEventArgs e) {
e.Graphics.DrawImage(Image, 0, 0);
}
}
When we’ll put it onto anything, that has no background color, we’ll see that our “fake transparency” disappears. Why this happen? To provide transparency Windows uses color masks, also while confederating facts, clipping algorithm within GDI is not very trustful, thus the only thing can be taken into account is color. But what to do if we have an image? We should clip it manually. We cannot just get the handle to parent device surface (see above about trustful GDI), so the only way to do it is by providing something, that we know for sure. For example interface, telling us, that parent has image, which drawn on the screen.
internal interface IHaveImage {
Bitmap Image { get; set; }
}
When we know it, all we have to do is to clip the region of this image (not device context) and draw it as part of our really transparent control.
if (this.Parent is IHaveImage) {
var par = this.Parent as IHaveImage;
gxOff.DrawImage(par.Image.Clip(this.Bounds), 0, 0);
}
The implementation of Image.Clip is very straight forward.
public static Bitmap GetSS(this Graphics grx, Rectangle bounds) {
var res = new Bitmap(bounds.Width, bounds.Height);
var gxc = Graphics.FromImage(res);
IntPtr hdc = grx.GetHdc();
PlatformAPI.BitBlt(gxc.GetHdc(), 0, 0, bounds.Width, bounds.Height, hdc, bounds.Left, bounds.Top, PlatformAPI.SRCCOPY);
grx.ReleaseHdc(hdc);
return res;
}public static Bitmap Clip(this Bitmap source, Rectangle bounds) {
var grx = Graphics.FromImage(source);
return grx.GetSS(bounds);
}
We done. Compiling all together will fake transparency for controls, even when it’s parents background is not pained with solid color brush.
P.S. Do not even try to inherit your custom Button control from framework Button class, dev team “forgot” to expose it’s event for override. So, OnPaint, OnPaintBackground, OnKeyUp, OnKeydown, OnMouseUp and OnMouseDown aside with most of other base events will not work for you, also BaseButton class has no default constructor, so the only class you can inherit from is Control.
Have a nice day and be good people.
November 20th, 2008 · Comments (5)
Discover other tags
My tools
- .NET Framework Detector
- Duplicate images finder
- Exchange Security Policy for Windows Mobile Devices Fix
- Gas Price Windows Vista SideBar gadget
- Israel Traffic Information Windows Vista SideBar gadget
- Localization fix for SAP ES Explorer for Visual Studio
- LocTester
- RTL and LTR in Windows Live Writer
- Silverlight controls library
- Snipping tool integration plugin for WLW
- USB FM receiver library
- Vista Battery Saver
- WebCam control for WPF
- Windows Live SkyDrive attachment for Windows Live Writer
- Wireless Migrator
- WPF Virtual Keyboard



