" As the painter needs his framework of parchment, the improvising musical group needs its framework in time" -- Bill Evans(liner notes to Kind of Blue)
Some time ago, Ianier Munoz, an MVP from France, published an article on codeproject.com about using the WaveOut API to handle sound card input / output. His code is excellent and has been re-used by other authors.
However, in the form presented in his article, the code is disjointed and not usable for much more than the demo app. The reason for this is that much of the front end code needed to access the low-level calls is actually in the demo form. This is not OOP - I needed a "Recorder / Player" object, a class library that you could create, set a stream and call the Play or Record method, and it would be completely self-contained. I also needed to ensure that only one instance of my object was used in order to avoid native API exceptions. In addition, I needed a more generic approach. Munoz' code assumes that you want to play a WAV file. I need to be able to pass in a raw PCM audio stream such as input from an RTP stream decoder for VOIP, or load RAW audio off the disk, or even from embedded audio resources in an assembly, such as for DTMF tones. Since Munoz' code uses his WaveStream which parses the RIFF WAV header, that's no good for me.
So what I did was refactor, add two new "front end" classes, Player and Recorder, and make it all more flexible. I'll share my front end class code here; if you have further interest you can refer to Munoz' original article for more details. At the bottom of the page, I have a complete solution with a test windows forms app that will allow you to load and play either a WAV file, or a RAW file with no WAV header and play them. Further, I have a Record button that allows you to pipe the output from the Recorder class (your microphone) directly into the Player class (your headphones or speakers) and hear what you are saying. That's a neat trick, now that I think of it. Usually I haven't got a clue what I'm saying!
I've also included in the download for you to play, an 8000 / 16 bit / MONO WAV file of a portion of Miles Davis' solo on "Freddie Freeloader" from his Kind of Blue album. This best selling jazz record of all time was released 40 years ago and it still sells 5,000 copies a week. It's a universally acknowledged masterpiece, revered as much by rock and classical music fans as by jazz lovers, and this trumpet solo could be the single best trumpet solo on a jazz piece by any artist ever. When I was in my 20's and studying jazz in New York, my flute teacher made me transcribe every note and rest of this solo onto sheet music by ear, and learn to play it perfectly. It really helped my understanding of phrasing and pacing from a true master. Some things are timeless - J.S. Bach has been dead for 255 years, and he's still popular, but most of the junk you see on people's playlists today will be gone forever in about a year! Think about it. Miles died in 1991, but in the hearts of musicians everywhere, he's right here.
Here's the code for my Player class:
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
namespace WaveLib
{
public class Player
{
private Player()
{
}
public static byte[] WavToRawPCM(byte[] waveInput)
{
int data_len = (waveInput.Length - 0x2c);
byte[] data = new byte[data_len];
Buffer.BlockCopy(waveInput, 0x2c, data, 0, data_len);
return data;
}
public static readonly Player Instance = new Player();
private WaveOutPlayer m_Player;
public WaveFormat m_Format;
public Stream m_AudioStream;
public bool IsRunning =false;
private void Filler(IntPtr data, int size)
{
byte[] b = new byte[size];
if (m_AudioStream != null)
{
int pos = 0;
while (pos < size)
{
if(m_AudioStream.Position !=0 || m_AudioStream.Length!=0)
{
int toget = size - pos;
int got = m_AudioStream.Read(b, pos, toget);
if (got < toget)
m_AudioStream.Position = 0; // loop if the file ends
pos += got;
}
else
{
System.Threading.Thread.Sleep(0);
}
}
}
else
{
for (int i = 0; i < b.Length; i++)
b[i] = 0;
}
Marshal.Copy(b, 0, data, size);
// you could play around with these ...
//m_AudioStream.Position =0;
// if(m_AudioStream.Position !=m_AudioStream.Length )m_AudioStream.Position =0;
}
public void Stop()
{
if (m_Player != null)
try
{
m_Player.Dispose();
}
finally
{
m_Player = null;
}
this.IsRunning =false;
}
public void Start()
{
Stop();
if (m_AudioStream != null)
{
m_AudioStream.Position = 0;
m_Player = new WaveOutPlayer(-1, m_Format, 65536, 3, new BufferFillEventHandler(Filler));
this.IsRunning =true;
}
}
public void Start(int bufSize)
{
Stop();
if (m_AudioStream != null)
{
m_AudioStream.Position = 0;
m_Player = new WaveOutPlayer(-1, m_Format, bufSize, 3, new BufferFillEventHandler(Filler));
this.IsRunning =true;
}
}
public void SetInput(MemoryStream input, WaveFormat format)
{
if(this.IsRunning)
this.Stop();
try
{
m_Format = format;
m_AudioStream = input;
}
catch (Exception e)
{
Debug.WriteLine(e.Message + e.StackTrace);
}
}
}
} |
You see above that my Player class is a singleton, as is the recorder, and that it provides a nice "front end" for the more low-level stuff going on "below deck". All you do is stick a memory stream (which could hold the bytes you've loaded off the file system, or a live audio stream of PCM data) into the input, set a WaveFormat so the player knows what to expect, and call the Play method. Note there is also a little utility method, "WavToRawPCM" that strips off the 44 byte WAVE Header from the front of the stream and returns you a raw PCM stream.
Now the Recorder:
using System;
using System.Runtime.InteropServices;
using System.IO;
namespace WaveLib
{
public class Recorder
{
private WaveInRecorder m_Recorder;
public FifoStream m_Fifo = new FifoStream();
public WaveFormat m_Format;
public byte[] m_RecBuffer;
public MemoryStream RecorderOutputStream;
public static bool IsRunning = false;
public bool IAmRunning=false;
private void DataArrived(IntPtr data, int size)
{
try
{
if (m_RecBuffer == null || m_RecBuffer.Length < size)
m_RecBuffer = new byte[size];
Marshal.Copy(data, m_RecBuffer, 0, size);
m_Fifo.Write(m_RecBuffer, 0, m_RecBuffer.Length);
this.RecorderOutputStream.Write(m_RecBuffer,0,m_RecBuffer.Length );
Console.WriteLine("rec: "+m_RecBuffer.Length.ToString());
}
catch(Exception ex)
{
System.Diagnostics.Debug.WriteLine("RECORDER: "+ex.Message+ex.StackTrace);
throw;
}
}
private Recorder()
{
}
public static readonly Recorder Instance = new Recorder();
public void Stop()
{
if (m_Recorder != null)
try
{
m_Recorder.Dispose();
}
finally
{
m_Recorder = null;
}
WaveLib.Recorder.IsRunning =false;
IAmRunning=false;
}
public void SetOutput(MemoryStream outStrm)
{
if(this.IAmRunning)
this.Stop();
this.RecorderOutputStream=outStrm;
}
public void Start()
{
Console.WriteLine("Rec START CALLED");
if(m_Recorder!=null) this.Stop();
Console.WriteLine("Rec Restarted");
m_Recorder = new WaveInRecorder(-1, m_Format, 16384, 3, new BufferDoneEventHandler(DataArrived));
WaveLib.Recorder.IsRunning =true;
IAmRunning=true;
}
public void Start(int bufSize)
{
if(m_Recorder!=null) this.Stop();
m_Recorder = new WaveInRecorder(-1, m_Format, bufSize, 3, new BufferDoneEventHandler(DataArrived));
WaveLib.Recorder.IsRunning =true;
IAmRunning=true;
}
}
} | Note that each class has an IsRunning boolean helper property, and both classes accept the MemoryStream for input or output to make it more generic and re-usable. And here is a sample of how it could be used, from my Tester App which is included in the download below:
private void OpenFile()
{
if (OpenDlg.ShowDialog() == DialogResult.OK)
{
CloseFile();
try
{
FileStream fs=new FileStream(OpenDlg.FileName,System.IO.FileMode.Open);
byte[] tmp=new byte[(int)fs.Length];
fs.Read(tmp,0,(int)fs.Length);
stuff=tmp;
this.PlayButton.Enabled =true;
}
catch(Exception e)
{
CloseFile();
MessageBox.Show(e.Message);
}
}
}
private void PlayButton_Click_1(object sender, System.EventArgs e)
{
m_Player=WaveLib.Player.Instance;
MemoryStream ms = new MemoryStream(stuff);
WaveLib.WaveStream strm = new WaveLib.WaveStream(ms);
WaveLib.WaveFormat fmt;
fmt = strm.Format;
if(fmt==null)
fmt =new WaveLib.WaveFormat(8000,16,1);
if(!chkRecordInput.Checked)
{
m_Player.SetInput(new MemoryStream(stuff),fmt);
}
if(chkRecordInput.Checked )
{
m_Player.SetInput(this.RecordStream ,fmt);
}
m_Player.Start(65536);
} |
At work, I have much more written such as mixers, splitters, encoders, RTP Stacks and so on. That, I can't share. By the way, with minor modifications, this code could be used for the PocketPC. The CoreDll is where the audio native functions are located, but the method signatures are pretty much the same. Enjoy the download.
Download the Visual Studio.NET 2003 solution that accompanies this article
|