Streaming directly from mic to speakers using DirectSound
Greetings,
I hope this is the right forum for this question. I'm trying to write a small program that will echo input from the mic directly to the speakers. I've written a program to do this that seems like it should work but when I run it, no sound. I was wondering if there was anything that I'm doing wrong or not doing or whatever?
Its written in C# using VC# Express 2005.
Thanks,
FM
Program source:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using Microsoft.DirectX.DirectSound;
namespace Echo
{
public partial class Echo : Form
{
private Capture _captureDevice;
private CaptureBuffer _captureBuffer;
private Device _playbackDevice;
private SecondaryBuffer _playbackBuffer;
private int _bufferBytes;
private const int _bufferPositions = 2;
private AutoResetEvent _notificationEvent = null;
private BufferPositionNotify[] _positionNotify = new BufferPositionNotify[_bufferPositions + 1];
private Notify _echoNotify = null;
private void EchoThread()
{
_captureBuffer.Start(true);
int capturePos, readPos;
for (; ; ) {
_notificationEvent.WaitOne(Timeout.Infinite, true);
_captureBuffer.GetCurrentPosition(out capturePos, out readPos);
byte[] frame = (byte[]) _captureBuffer.Read(readPos, typeof(byte), LockFlag.None, _bufferBytes);
_playbackBuffer.Write(0, frame, LockFlag.None);
}
}
public Echo()
{
InitializeComponent();
_captureDevice = new Capture();
short channels = 2;
short bitsPerSample = 16;
// 11KHz use 11025 , 22KHz use 22050, 44KHz use 44100, etc.
int samplesPerSecond = 22050;
//Set up the wave format to be captured
WaveFormat waveFormat = new WaveFormat();
waveFormat.Channels = channels;
waveFormat.FormatTag = WaveFormatTag.Pcm;
waveFormat.SamplesPerSecond = samplesPerSecond;
waveFormat.BitsPerSample = bitsPerSample;
waveFormat.BlockAlign = (short) (channels * (bitsPerSample / (short) 8));
waveFormat.AverageBytesPerSecond = waveFormat.BlockAlign * samplesPerSecond;
// Approx. 200 milliseconds
_bufferBytes = waveFormat.AverageBytesPerSecond / 5;
CaptureBufferDescription captureBufferDescription = new CaptureBufferDescription();
captureBufferDescription.BufferBytes = 2 * _bufferBytes;
captureBufferDescription.Format = waveFormat;
_captureBuffer = new CaptureBuffer(captureBufferDescription, _captureDevice);
_playbackDevice = new Device();
_playbackDevice.SetCooperativeLevel(this, CooperativeLevel.Priority);
BufferDescription playbackBufferDescription = new BufferDescription();
playbackBufferDescription.BufferBytes = _bufferBytes;
playbackBufferDescription.Format = waveFormat;
_playbackBuffer = new SecondaryBuffer(playbackBufferDescription, _playbackDevice);
Thread echoThread = new Thread(new ThreadStart(EchoThread));
echoThread.Start();
_notificationEvent = new AutoResetEvent(false);
for (int i = 0; i < _bufferPositions; i++) {
_positionNotify
.Offset = (_bufferBytes * i) + _bufferBytes - 1;
_positionNotify
.EventNotifyHandle = _notificationEvent.Handle;
}
_echoNotify = new Notify(_captureBuffer);
_echoNotify.SetNotificationPositions(_positionNotify, _bufferPositions);
}
}
}
Greetings again,
I've continued to play with this some. The following is my current version. I've tried increasing the number of notification events, I've tried using a memory stream in between the capture buffer read and the secondary buffer write. I've tried using a CopyTo to a buffer[] from the capture buffer before doing the secondary buffer write using the byte[] buffer. In this version, I'm just doing a simple assignment to a byte[] buffer and then using that as a parameter to the secondary buffer write.
The all compile and build fine. They just don't produce any sound. I must be missing something really basic. Still hoping someone can provide me some assistance, perhaps someone from Microsoft?
Thanks,
FM
namespace Echo
{
public partial class Echo : Form
{
private Thread _echoThread;
private Capture _captureDevice;
private CaptureBuffer _captureBuffer;
private Device _playbackDevice;
private SecondaryBuffer _playbackBuffer;
private int _bufferSize;
private const int _bufferPositions = 10;
private AutoResetEvent _notificationEvent = null;
private BufferPositionNotify[] _positionNotify = new BufferPositionNotify[_bufferPositions + 1];
private Notify _echoNotify = null;
private void EchoThread()
{
_captureBuffer.Start(true);
int offset = 0;
for (; ; ) {
_notificationEvent.WaitOne(Timeout.Infinite, true);
byte[] buffer = (byte[]) _captureBuffer.Read(offset, typeof(byte), LockFlag.None, _bufferSize);
_playbackBuffer.Write(offset, buffer, LockFlag.None);
offset = (offset + _bufferSize) % (_bufferPositions * _bufferSize);
if (offset == 0)
console.AppendText(".");
}
}
public Echo()
{
CheckForIllegalCrossThreadCalls = false;
InitializeComponent();
console.AppendText("Echo\r\n");
console.AppendText("Written by Frank W. Miller\r\n");
console.AppendText("\r\n");
_captureDevice = new Capture();
short channels = 2;
console.AppendText("Channels: " + channels.ToString() + "\r\n");
short bitsPerSample = 16;
console.AppendText("Bits per sample: " + bitsPerSample.ToString() + "\r\n");
int samplesPerSecond = 22050;
console.AppendText("Samples per second: " + samplesPerSecond.ToString() + "\r\n");
//Set up the wave format to be captured
WaveFormat waveFormat = new WaveFormat();
waveFormat.Channels = channels;
waveFormat.FormatTag = WaveFormatTag.Pcm;
waveFormat.SamplesPerSecond = samplesPerSecond;
waveFormat.BitsPerSample = bitsPerSample;
waveFormat.BlockAlign = (short) (channels * (bitsPerSample / (short) 8));
waveFormat.AverageBytesPerSecond = waveFormat.BlockAlign * samplesPerSecond;
_bufferSize = waveFormat.AverageBytesPerSecond / 5;
console.AppendText("Buffer size: " + _bufferSize.ToString() + "\r\n");
CaptureBufferDescription captureBufferDescription = new CaptureBufferDescription();
captureBufferDescription.BufferBytes = _bufferPositions * _bufferSize;
captureBufferDescription.Format = waveFormat;
_captureBuffer = new CaptureBuffer(captureBufferDescription, _captureDevice);
_playbackDevice = new Device();
_playbackDevice.SetCooperativeLevel(this, CooperativeLevel.Normal);
BufferDescription playbackBufferDescription = new BufferDescription();
playbackBufferDescription.BufferBytes = _bufferPositions * _bufferSize;
playbackBufferDescription.Format = waveFormat;
_playbackBuffer = new SecondaryBuffer(playbackBufferDescription, _playbackDevice);
_echoThread = new Thread(new ThreadStart(EchoThread));
_echoThread.Start();
_notificationEvent = new AutoResetEvent(false);
for (int i = 0; i < _bufferPositions; i++) {
_positionNotify
.Offset = (_bufferSize * i) + _bufferSize - 1;
_positionNotify
.EventNotifyHandle = _notificationEvent.Handle;
}
_echoNotify = new Notify(_captureBuffer);
_echoNotify.SetNotificationPositions(_positionNotify, _bufferPositions);
}
private void EchoClose(object sender, FormClosingEventArgs e)
{
_echoThread.Abort();
}
}
Although it's a bit out of date, but does your sound card has duplex support (simultaneous record and playback)?
Its a good question. Yes my sound card is full-duplex. I use it with the Linux version of my VoIP client which specifically sets full duplex at the device level.
I really think there's something basic that I'm missing. For example, I'm wondering if the capture buffer is actually pointing into the output buffer of the microphone hardware and whether the secondary buffer points into the actual hardware output buffer for the speakers. If thats the case, then you would need to copy between them. Thats why I tried using CopyTo() to a byte[] buffer between the read and write. But that didnt work. But I may not have done it right.
I really wish somebody from Microsoft would comment on this. It would even be helpful to hear that it just can't be done. That way I could stop banging on it and do what other people do, which is to wrap native interfaces using interop. I want to avoid this mainly because I want to take this code to WM5 at some point and the DLLs that I could use under XP probably won't be there under WM5.
Thanks,
FM
Finally got it to work! It was something really dumb, the playback buffer was not being "Play()"ed. When I added the looping playback call, everything just worked. I've varied the sampling rate, channels, bits per sample, and the number of notification slots and the buffer size. Very interesting to play with.
Enjoy!
FM
OK, here's the working program:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.IO;
using Microsoft.DirectX.DirectSound;
namespace Echo
{
public partial class Echo : Form
{
private Thread _echoThread;
private Capture _captureDevice;
private CaptureBuffer _captureBuffer;
private Device _playbackDevice;
private SecondaryBuffer _playbackBuffer;
private int _bufferSize;
private const int _bufferPositions = 4;
private AutoResetEvent _notificationEvent = null;
private BufferPositionNotify[] _positionNotify = new BufferPositionNotify[_bufferPositions + 1];
private Notify _echoNotify = null;
private void EchoThread()
{
int offset = 0;
_captureBuffer.Start(true);
_playbackBuffer.Play(0, BufferPlayFlags.Looping);
for (; ; ) {
_notificationEvent.WaitOne(Timeout.Infinite, true);
byte[] buffer = (byte[]) _captureBuffer.Read(offset, typeof(byte), LockFlag.None, _bufferSize);
_playbackBuffer.Write(offset, buffer, LockFlag.None);
offset = (offset + _bufferSize) % (_bufferPositions * _bufferSize);
}
}
public Echo()
{
CheckForIllegalCrossThreadCalls = false;
InitializeComponent();
console.AppendText("Echo\r\n");
console.AppendText("Written by Frank W. Miller\r\n");
console.AppendText("\r\n");
_captureDevice = new Capture();
short channels = 2;
console.AppendText("Channels: " + channels.ToString() + "\r\n");
short bitsPerSample = 16;
console.AppendText("Bits per sample: " + bitsPerSample.ToString() + "\r\n");
int samplesPerSecond = 22050;
console.AppendText("Samples per second: " + samplesPerSecond.ToString() + "\r\n");
//Set up the wave format to be captured
WaveFormat waveFormat = new WaveFormat();
waveFormat.Channels = channels;
waveFormat.FormatTag = WaveFormatTag.Pcm;
waveFormat.SamplesPerSecond = samplesPerSecond;
waveFormat.BitsPerSample = bitsPerSample;
waveFormat.BlockAlign = (short) (channels * (bitsPerSample / 8));
waveFormat.AverageBytesPerSecond = waveFormat.BlockAlign * samplesPerSecond;
_bufferSize = waveFormat.AverageBytesPerSecond / 20;
console.AppendText("Buffer size: " + _bufferSize.ToString() + "\r\n");
CaptureBufferDescription captureBufferDescription = new CaptureBufferDescription();
captureBufferDescription.BufferBytes = _bufferPositions * _bufferSize;
captureBufferDescription.Format = waveFormat;
_captureBuffer = new CaptureBuffer(captureBufferDescription, _captureDevice);
_playbackDevice = new Device();
_playbackDevice.SetCooperativeLevel(this, CooperativeLevel.Normal);
BufferDescription playbackBufferDescription = new BufferDescription();
playbackBufferDescription.BufferBytes = _bufferPositions * _bufferSize;
playbackBufferDescription.Format = waveFormat;
_playbackBuffer = new SecondaryBuffer(playbackBufferDescription, _playbackDevice);
_echoThread = new Thread(new ThreadStart(EchoThread));
_echoThread.Start();
_notificationEvent = new AutoResetEvent(false);
for (int i = 0; i < _bufferPositions; i++) {
_positionNotify
.Offset = (_bufferSize * i) + _bufferSize - 1;
_positionNotify
.EventNotifyHandle = _notificationEvent.SafeWaitHandle.DangerousGetHandle();
}
_echoNotify = new Notify(_captureBuffer);
_echoNotify.SetNotificationPositions(_positionNotify, _bufferPositions);
}
private void EchoClose(object sender, FormClosingEventArgs e)
{
_echoThread.Abort();
}
}
}
I'm new to programming with DirectX, however, I have been looking for a program that does exactly what this one does.
But, I'm not sure ifs its a program on its on? is it a console application? is it a class to be used in another application?
I'd appreciate help in getting this code into a running program.
Thanks.
*Edit*
Disregard. I figured it out. My main problem was not having the Microsoft.DirectX reference (only DirectSound), which gave some odd errors.
out at 2007-10-8 >
