|

Introduction
It is no news that the .NET framework does not include any classes for dealing with sound. Some people have worked around this limitation by wrapping high-level system components, such as Windows Media Player or DirectShow, into .NET-friendly libraries. However, most of these libraries are designed in such a way that they cannot be used to manipulate audio samples on the fly because they do not give you access to the actual sound data.
When developing applications that deal with such low-level issues, the most commonly used technologies are DirectSound and the waveout API. This article describes a sample application that uses the waveout API in C# through Interop to play a WAV file in a continuous loop.
Using the code
Most of the work in the sample application is carried out by two classes: WaveStream and WaveOutPlayer.
The WaveStream class extends System.IO.Stream and implements the necessary code to read audio samples from a WAV file. The constructor reads the file header and extracts all the relevant information including the actual length of the stream and the format of the audio samples, which is exposed through the Format property.
One important thing to note is that seeking operations in the WaveStream class are relative to the sound data stream. This way you don't have to care about the length of the header or about those extra bytes at the end of the stream that don't belong to the audio data chunk. Seeking to 0 will cause the next read operation to start at the beginning of the audio data.
The WaveOutPlayer class is where the most interesting things happen. For the sake of simplicity, the interface of this class has been reduced to the strict minimum. There are no Start, Stop or Pause methods. Creating an instance of WaveOutPlayer will cause the system to start streaming immediately.
Let's have a look at the code that creates the WaveOutPlayer instance. As you can see, the constructor takes five parameters: private void Play()
{
Stop();
if (m_AudioStream != null)
{
m_AudioStream.Position = 0;
m_Player = new WaveLib.WaveOutPlayer(-1, m_Format, 16384, 3,
new WaveLib.BufferFillEventHandler(Filler));
}
}
The first parameter is the ID of the wave device that you want to use. The value -1 represents the default system device, but if your system has more than one sound card, then you can pass any number from 0 to the number of installed sound cards minus one to select a particular device.
The second parameter is the format of the audio samples. In this example, the format is taken directly from the wave stream.
The third and forth parameters are the size of the internal wave buffers and the number of buffers to allocate. You should set these to reasonable values. Smaller buffers will give you less latency, but the audio may stutter if your computer is not fast enough.
The fifth and last parameter is a delegate that will be called periodically as internal audio buffers finish playing, so that you can feed them with new sound data. In the sample application we just read audio data from the wave stream, like this: private void Filler(IntPtr data, int size)
{
byte[] b = new byte[size];
if (m_AudioStream != null)
{
int pos = 0;
while (pos < size)
{
int toget = size - pos;
int got = m_AudioStream.Read(b, pos, toget);
if (got < toget)
m_AudioStream.Position = 0;
pos += got;
}
}
else
{
for (int i = 0; i < b.Length; i++)
b[i] = 0;
}
System.Runtime.InteropServices.Marshal.Copy(b, 0, data, size);
}
Please note that this delegate may be called from any internal thread created by the WaveOutPlayer object, so if you want to call into your Windows Forms objects you should use the Invoke mechanism.
To stop playing, just call Dispose on the player object, like this: private void Stop()
{
if (m_Player != null)
try
{
m_Player.Dispose();
}
finally
{
m_Player = null;
}
}
Conclusion
This sample demonstrates how to use the waveout API from C#. This is useful for applications that require more control on the audio stream compared to what other higher level libraries offer. Typical examples are applications that generate or modify audio samples on the fly, such as digital effect processors.
A modified version of this sample that implements support for DirectX plug-ins is included in the Adapt-X SDK, which is a commercial product that can be found at www.chronotron.com.
History
Update 28 Aug 03 - Fixed a bug related to reading the WAV file header.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 142 (Total in Forum: 142) (Refresh) | FirstPrevNext |
|
 |
|
|
 |
|
|
I want to set the max value of a progress bar to the length in seconds of the sound I loaded in System.Media.soundPlayer. how do I do that?
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
Hi. does anyone have a c++ code for a simple wma audio player? any help is greatly appreciated.
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
Hey everybody, By using this code the application I have developed able to play and record audio/music. But it does not have a listener like what is available in socket programming. I need your help on how I will proceed to develop the listener. Thanks in advance!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Problem: to deactivate the loop forces the WaveLib.WaveOutPlayer to stop immediately. that means that the very last part of the stream will not be played.
This is definitively the wrong way.
int got = m_AudioStream.Read(b, pos, toget); //if (got < toget) //{ //m_AudioStream.Position = 0; // loop if the file ends //} pos += got;
A Solution would be really good for me as I want to create a very small application for an Windows Mobile Pro.
Thank you.
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
Hi moserwi,
I wanted to do the same. And found a solution. The purpose is to add a "Loop" checkbox on the MainForm to enable/disable looping feature live.
I have added a member variable m_Loop and its get/set property to WaveOutPlayer. Added the loop argument to constructor.
public class WaveOutPlayer : IDisposable { //... private bool m_Loop;
public bool Loop { get { return m_Loop; } set { m_Loop = value; } }
//..
public WaveOutPlayer(int device, WaveFormat format, int bufferSize, int bufferCount, BufferFillEventHandler fillProc) : this(device, format, bufferSize, bufferCount, true, fillProc) { }
public WaveOutPlayer(int device, WaveFormat format, int bufferSize, int bufferCount, bool loop, BufferFillEventHandler fillProc) { m_Loop = loop; m_zero = format.BitsPerSample == 8 ? (byte)128 : (byte)0; m_FillProc = fillProc; WaveOutHelper.Try(WaveNative.waveOutOpen(out m_WaveOut, device, format, m_BufferProc, 0, WaveNative.CALLBACK_FUNCTION)); AllocateBuffers(bufferSize, bufferCount); m_Thread = new Thread(new ThreadStart(ThreadProc)); m_Thread.Start(); } }
Then the delegated needed to be changed in order to be notified when the stream had finished playing.
internal class WaveOutHelper { // ... public delegate void BufferFillEventHandler(IntPtr data, int size, ref bool finished); //.. }
The Filler proc in MainForm had to match the delegate signature and needed to set "finished" variable to true when playing was done.
private void Filler(IntPtr data, int size, ref bool finished) { byte[] b = new byte[size]; if (m_AudioStream != null) { int pos = 0; while (pos < size) { int toget = size - pos; int got = m_AudioStream.Read(b, pos, toget); if (got < toget) { finished = true; // notify end of playing m_AudioStream.Position = 0; // loop if the file ends } pos += got; } } else { for (int i = 0; i < b.Length; i++) b[i] = 0; } System.Runtime.InteropServices.Marshal.Copy(b, 0, data, size); }
The playing thread had just to set its m_Finished variable to true only when m_Loop was false.
private void ThreadProc() { while (!m_Finished) { Advance(); if (m_FillProc != null && !m_Finished) { m_FillProc(m_CurrentBuffer.Data, m_CurrentBuffer.Size, ref m_Finished); if (m_Finished && m_Loop) m_Finished = false; } else { // zero out buffer byte v = m_zero; byte[] b = new byte[m_CurrentBuffer.Size]; for (int i = 0; i < b.Length; i++) b[i] = v; Marshal.Copy(b, 0, m_CurrentBuffer.Data, b.Length);
} m_CurrentBuffer.Play(); } WaitForAllBuffers(); }
I modified the Play procedure in order to pass the checkbox value to the WaveOutPlayer constructor. And everything works ok.
private void Play() { Stop(); if (m_AudioStream != null) { m_AudioStream.Position = 0; m_Player = new WaveLib.WaveOutPlayer(-1, m_Format, BUFFER_SIZE, 3, chkLoop.Checked, new WaveLib.BufferFillEventHandler(Filler)); } }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
I'm actually having a lot of issues with playing any wav file in a WPF application, so i've been looking around for a demo that plays sound in any form that I can modify for my needs.
When I run your program and open a file and then press play I get the following error;
{"Object reference not set to an instance of an object."} at WaveLib.WaveNative.waveOutOpen(IntPtr& hWaveOut, Int32 uDeviceID, WaveFormat lpFormat, WaveDelegate dwCallback, Int32 dwInstance, Int32 dwFlags) at WaveLib.WaveOutPlayer..ctor(Int32 device, WaveFormat format, Int32 bufferSize, Int32 bufferCount, BufferFillEventHandler fillProc) in C:\Temp\wavplayer\cswavplay\WaveOut.cs:line 148 at cswavplay.MainForm.Play() in C:\Temp\wavplayer\cswavplay\MainForm.cs:line 182 at cswavplay.MainForm.PlayButton_Click(Object sender, EventArgs e) in C:\Temp\wavplayer\cswavplay\MainForm.cs:line 231 at System.Windows.Forms.Control.OnClick(EventArgs e) at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent) at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks) at System.Windows.Forms.Control.WndProc(Message& m) at System.Windows.Forms.ButtonBase.WndProc(Message& m) at System.Windows.Forms.Button.WndProc(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m) at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam) at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg) at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData) at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context) at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context) at cswavplay.MainForm.Main() in C:\Temp\wavplayer\cswavplay\MainForm.cs:line 133 at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args) at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart()
The file plays correctly in my windows media player (11.0.6001.6336). Incidently the file I tried was chimes.wav found in the windows\media folder (although it happens with all files I try).
I'm running on Windows Server 2008 RC0 x64
Any help would be appreated, thanks.
Ps. Before you suggest MediaPlayer in the System.Windows.Media namespace that doesn't work either, you hear no sound but no errors or exceptions, however an entry appears in the windows mixer for the application. Very odd and a little frustrating.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
I have searched these articles and googled for hours but have found no clue to this.
I created to build wmv video from bitmaps and uncompressed PCM audio stream. The XML used for SetProfile is attached below. This profile is set up for PCM audio. Otherwise, if I don't set it up that way, the audio stream fails to be written to the WMV.
My challenge is that the resultant WMV file contains UNCOMPRESSED audio.
1. Isn't WriteSample supposed to compress the audio samples I write in?
2. The audio plays but the filesize indicates that the audio is not compressed and file->properties of WM Player shows NO audio codec.
3. I guess the profile XML is for specifying the format of what data will be written using WriteSample but how do you specify how the sample will be compressed?
Thank you in advance.
name="Video" guid="{045880DC-34B6-4CA9-A326-73557ED143F3}" description="Use this profile for duel view Commercial clips."> streamnumber="1" streamname="Audio Stream" inputname="Audio" bitrate="32000" bufferwindow="-1">
bfixedsizesamples="1" btemporalcompression="0" lsamplesize="2"> nChannels="1" nSamplesPerSec="12000" nAvgBytesPerSec="24000" nBlockAlign="2" wBitsPerSample="16" codecdata="008800001700001E0000"/> streamnumber="2" streamname="Video Stream" inputname="Video" bitrate="256000" bufferwindow="-1"> quality="0"/> bfixedsizesamples="0" btemporalcompression="1" lsamplesize="0"> dwbiterrorrate="0" avgtimeperframe="666666"> top="0" right="640" bottom="264"/> top="0" right="540" bottom="200"/> biheight="200" biplanes="1" bibitcount="24" bicompression="WMV1" bisizeimage="0" bixpelspermeter="0" biypelspermeter="0" biclrused="0" biclrimportant="0"/>
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
Thank you for this! I'm not too familiar with native APIs, so I plugged in your code to create a little thing that tells me how many minutes have passed on so many minutes to the hour (e.g. 5 turns into 5 minutes, so 4:00, 4:05, etc.).
It's funny, because I found out that there's also the System.Speech class for what I really wanted to do.
Still, it's nice that I can play a sound every X minutes (on the hour) AND say some custom text of whatever I want.
Gustavo
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I am having the apppliation for recording..but i can do recording as start and stop. I want the c#.net code for pause functionallity..if anybody knows please help me out..
i am raj
|
| Sign In·View Thread·PermaLink | 2.67/5 (3 votes) |
|
|
|
 |
|
|
Hello,
I have a question about wav using. I am beginner in C#, I never used that language before... Your code is very interesting, but unfortunally for me, he doesn't give me keys for the problem I have. In fact, I want to create an FFT spectral diagram from a wav file. I find the Exocortex.DSP dll which work fine, but I just need to "transform a wave file in a float tab". I am sure it exists a very simple C# functionnaly to do that, but I can't find it...
Can you help me?
Thanks in advance
Matthieu
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
Has anybody ever tried to run this class on Windows Mobile ? You have to change winmm.dll to mscorlib.dll then it should work.
But it doesn't.
After some time of looping a short wave (22050hz, 8Bits, mono): When I click on "start" (sometime the sound becomes first "strange" before the exeption occures , what seems to me like overwritten unmanaged memory. )
-> System.ArgumentException" in mscorlib.dll
System.ArgumentException: ArgumentException at System.PInvoke.EE.Runtime_InteropServices_GCHandle_InternalGet() at System.Runtime.InteropServices.GCHandle.get_Target() at WaveLib.WaveOutBuffer.WaveOutProc()
A strange Bug, my code and this Code seems to me al right. I really don't know why this happens. After installing ".net compact framework service pack 2" I can debug is at least, prior that Visual Studio crashed while debugging.
My Code is simple: --- Snip ---------
void OnPlayNewData(IntPtr data, int size) { byte[] tmpData = new byte[size]; if ((_waveData.Position + size) >= _waveData.Length) { _waveData.Seek(0,SeekOrigin.Begin); //loop } _waveData.Read(tmpData, 0, size); Marshal.Copy(tmpData, 0, data, size); }
private void button1_Click(object sender, EventArgs e) { WaveLib.WaveStream tmpStream = new WaveLib.WaveStream("/debug.wav"); byte[] buff = new byte[tmpStream.Length]; tmpStream.Read(buff, 0, Convert.ToInt32(tmpStream.Length)); _waveData.Write(buff, 0, Convert.ToInt32(tmpStream.Length)); _player = new WaveLib.WaveOutPlayer(-1, _format, 40 * 160, 3, new WaveLib.BufferFillEventHandler(OnPlayNewData)); }
--- Snip----
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
|
What is the magic number 16384 in MainForm.cs
It should be the buffer size but if I make it bigger or smaller It sounds very nasty!
On further investigation it can be made bigger or smaller but must be an even number
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Gilesb,
It's the buffer size in bytes. The buffer size should be set so that it can hold an integer number of samples (i.e. the size must be a multiple of the nBlockAlign field in the WaveLib.WaveFormat structure). Using any power of 2 is convenient because sample sizes are typically also a power of two. In your case, try 32768 or 65536. Regards,
|
| Sign In·View Thread·PermaLink | 1.50/5 (2 votes) |
|
|
|
 |
|
|
dear friends, I am IT student now i am doing a kind of research project."noise filterring system" in c# so, if you know how we can solve this problem plz e-mail me hassan0531@yahoo.com
thank you
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
Hi, I'd like to ask about the refill function what is used for exactly ??? and the last line of code in it specifically ????
Thanks so much.
AdyXP
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi.
First, I thank you so much for this great article  However, I would like to know if is there a way to get current play position and draw the wave form (audio signals) as it's playing.
Thank Mr Ianier Munoz.
-- modified at 15:55 Monday 24th July, 2006
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
Hi, I've got the same question...
I thought a way (I'll tried to explain it... I'm spanish and my english isn't very good): In WaveOutBuffer, we define a boolean called Playing (we set it in true when waveoutWrite(...) is called, and false when OnCompleted() is called). Too, we have to define in WaveOutBuffer an integer (FilePosition) that indicate the actual position in the file. Now, in WaveOutPlayer we define a method called int AudioPointer()... here we look for the playing buffer and return its FilePosition... It works, but I thing that it's too slow (each time we have to look over all the linked list).
Maybe a way using the AutoResetEvent for the synchronizing of threads?
Thanks Mr.Munoz.
P.D:I'm making a multichannel audio software for the university... Erakis, my email is eelioss@hotmail.com (you can add me in your MSN), maybe I can help you with the waveform displaying. Bye!
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
You have to add this lines in the WaveNative.cs
[DllImport(mmdll)] public static extern int waveOutGetPosition(IntPtr hWaveOut, ref MMTIME pmmt, int uSize);
And to define this struct in the same class:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct MMTIME { public int wType; public int u; public int x; }
(I tried to made it of this way http://msdn2.microsoft.com/en-us/library/ms712191.aspx[^] but I wasn't able of create the union. Someone could help me?)
And the following constants: public const int TIME_MS = 0x0001; // time in milliseconds public const int TIME_SAMPLES = 0x0002; // number of wave samples public const int TIME_BYTES = 0x0004; // current byte offset ... So, when you want to get de current position you have to write this:
MMTIME a = new MMTIME(); a.wType = WaveNative.TIME_BYTES; if (m_WaveOut!=IntPtr.Zero) WaveNative.waveOutGetPosition(m_WaveOut,ref a, Marshal.SizeOf(a));
the value "a.u" containt the current position in bytes.
You could to create a property in WaveOutPlayer called Position:
public long Position { get { MMTIME a = new MMTIME(); a.wType = WaveNative.TIME_BYTES; if (m_WaveOut!=IntPtr.Zero) WaveNative.waveOutGetPosition(m_WaveOut,ref a, Marshal.SizeOf(a)); return a.u; } }
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|