How to Draw Waveform While Recording Using NAudio and WPF

来源:互联网 发布:mac os maven环境变量 编辑:程序博客网 时间:2024/05/18 00:06


How to Draw Waveform While Recording Using NAudio and WPF

<iframe name="I0_1411101395894" width="100%" tabindex="0" title="+1" id="I0_1411101395894" src="https://apis.google.com/se/0/_/+1/fastbutton?usegapi=1&amp;annotation=bubble&amp;origin=http%3A%2F%2Fwww.assembleforce.com&amp;url=http%3A%2F%2Fwww.assembleforce.com%2F2012-07%2Fhow-to-draw-waveform-while-recording-using-naudio-and-wpf.h&amp;gsrc=3p&amp;ic=1&amp;jsh=m%3B%2F_%2Fscs%2Fapps-static%2F_%2Fjs%2Fk%3Doz.gapi.zh_CN.r8jdOAMAuC4.O%2Fm%3D__features__%2Fam%3DAQ%2Frt%3Dj%2Fd%3D1%2Ft%3Dzcms%2Frs%3DAItRSTMD7s0TsiKmjDdTb6IYGaxfOsdDJw#_methods=onPlusOne%2C_ready%2C_close%2C_open%2C_resizeMe%2C_renderstart%2Concircled%2Cdrefresh%2Cerefresh&amp;id=I0_1411101395894&amp;parent=http%3A%2F%2Fwww.assembleforce.com&amp;pfname=&amp;rpctoken=91343777" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" vspace="0" hspace="0" style="margin: 0px; left: 0px; top: 0px; width: 106px; height: 24px; visibility: visible; position: static;" data-gapiattached="true"></iframe>
Share on facebook Share on twitterShare on emailShare on printMore Sharing Services

NAudio is a wonderful library that provides basic functionalities for processing audio files in wav or mp3 format. AndWPF is a hot technique for developing desktop application on Winodws. Combining both of them, we can develop a application that draw waveforms onCanvas while recording sound.

For detailed introduction to NAudio and WPF canvas, you can read the following articles written by me before.

http://www.assembleforce.com/category/csharp/audio-image

Several Ways to Draw While Recording

There are some strategies that can be used to draw curve of waves on Canvas while recording sounds and I will discuss those strategies and then choose one to implement.

The recording process involves several steps, they are:

  1. Register EventHandler for recording process, including DataAvailable handler, which record the data while input device raise the signal for buffering and RecordingStopped handler, which clean the environment and release resource after recoding is stopped by explicitly calling WaveIn.StopRecording() or buffer overflow while setting StopOnBufferOverflow to true.
    //Assuming that we already had instance of WaveInwi.DataAvailable += new EventHandler(wi_DataAvailable);wi.RecordingStopped += new EventHandler(wi_RecordingStopped);
  2. Decide the WaveFormat of the input device should follow by assigning WavaIn.WaveFormat property the new WaveFormat instance.
    wi.WaveFormat = new WaveFormat(44100, 32, 2);
  3. Start recording by invoking WaveIn.StartRecording().
    wi.StartRecording();

What might be flexible happens inside the EventHandlers. So following the framework above, I will show you some different way for drawing curves while recording.

Strategy#1. Draw Curve Inside the DataAvailable EventHandler

Since we need to draw the waveform curve along with the recording process, we need to find a place that will be touched each time new recording data ia available. So the event handler is the first choice. Using this strategy, we can not control the  times that canvas gets updated per second because the invocation of event handler is uncertain for us. However, it is ensured that as new data is available, we will get notified and then we can decide whether we update the canvas. So using this approach, we can respond to the data effectively.

Strategy#2. Write Data into a Buffer and Set a Timer to Update Canvas

The most important factor to use this approach it the decomposition of sampling data and drawing data operations. Sometime, we cannot update the UI element ouside in another thread, this approach gives light to this situation. However,  additional synchronization is needed to ensure the consistency of data. Hence it makes the implementation complicated.

Strategy#3. Another Event Handler to Draw Curve

Actually, I did not try this approach yet, but if two invocation of event handlers share the same data, it shoudl work. It does not solve of problem when UI element cannot be updated in another thread, but it makes the sampling and transfering samples more efficient without being intterupted by additional draw.

Strategy to Display the Curve

There are two ways to display the curve, which are easy to understand. First one is to draw curve from left to right. If it hit the right most side, it goes back to the ledt side and then start to draw from left to right again. The second one is called moving windows. Thnking that I store all the samples in a list, then I define a window inside which the data will be displayed on the Canvas. The window will move as more data is sampled and buffered.

I prefer the second choice because it has a more friendly user experience.

The  Result

I write some codes to implement such an application. It will start recording once the window is initialized. Then it draws the curve from left to rigth. Once it hit the right most side, all the curve will be push from the right to the left (looks like that) and new curve will be drawn upon the right border and then moved towards the left.

Waveform isplayed using NAudio and WPF

Conclusion

In this article, I show how to draw the wave form onthe Canvs using NAudio and WPF technique. This application is of particularity because it can be seen from most of music players. And is you want to develop a voice sampling application, this technique can also be used to enhance the usablity.

If you like this article, please press the retweet buttons.

Appendice: The Complete Code

I have tested the code so you can try it out by yourself. I sould also work on your computer.

using NAudio;using NAudio.Wave;

namespace WPFs{    /// <summary>    /// waveShow.xaml 的交互逻辑    /// </summary>    ///

    public partial class waveShow : Window    {        public waveShow()        {            InitializeComponent();

            StartRecording(50);        }        WaveIn wi;        WaveFileWriter wfw;        Polyline pl;

        double canH = 0;        double canW = 0;        double plH = 0;        double plW = 0;        int time = 0;        double seconds = 0;

                List<byte> totalbytes;        Queue<Point> displaypts;        //Queue<short> displaysht;        Queue<Int32> displaysht;

        long count = 0;        int numtodisplay = 2205;        //sample 1/100, display for 5 seconds

        void StartRecording(int time)        {            wi = new WaveIn();            wi.DataAvailable += new EventHandler<WaveInEventArgs>(wi_DataAvailable);            wi.RecordingStopped += new EventHandler(wi_RecordingStopped);            wi.WaveFormat = new WaveFormat(44100, 32, 2);

            wfw = new WaveFileWriter("E:\\labdata\\record.wav", wi.WaveFormat);

            canH = waveCanvas.Height;            canW = waveCanvas.Width;

            pl = new Polyline();            pl.Stroke = Brushes.Blue;            pl.Name = "waveform";            pl.StrokeThickness = 1;            pl.MaxHeight = canH - 4;            pl.MaxWidth = canW - 4;

            plH = pl.MaxHeight;            plW = pl.MaxWidth;

            this.time = time;

            displaypts = new Queue<Point>();            totalbytes = new List<byte>();            //displaysht = new Queue<short>();            displaysht = new Queue<Int32>();

            wi.StartRecording();        }

        void wi_RecordingStopped(object sender, EventArgs e)        {            wi.Dispose();            wi = null;            wfw.Close();            wfw.Dispose();

            wfw = null;        }

        void wi_DataAvailable(object sender, WaveInEventArgs e)        {            seconds += (double)(1.0*e.BytesRecorded / wi.WaveFormat.AverageBytesPerSecond*1.0);            if (seconds > time)            {                wi.StopRecording();            }

            wfw.Write(e.Buffer, 0, e.BytesRecorded);            totalbytes.AddRange(e.Buffer);

            //byte[] shts = new byte[2];            byte[] shts = new byte[4];

            for (int i = 0; i < e.BytesRecorded-1; i += 100)            {                shts[0] = e.Buffer[i];                shts[1] = e.Buffer[i + 1];                shts[2] = e.Buffer[i + 2];                shts[3] = e.Buffer[i + 3];                if (count < numtodisplay)                {                    displaysht.Enqueue(BitConverter.ToInt32(shts, 0));                    ++count;                }                else                {                    displaysht.Dequeue();                    displaysht.Enqueue(BitConverter.ToInt32(shts, 0));                }            }            this.waveCanvas.Children.Clear();            pl.Points.Clear();            //short[] shts2 = displaysht.ToArray();            Int32[] shts2 = displaysht.ToArray();            for (Int32 x = 0; x < shts2.Length;++x )            {                pl.Points.Add(Normalize(x, shts2[x]));            }

                        this.waveCanvas.Children.Add(pl);

        }

        Point Normalize(Int32 x, Int32 y)        {            Point p = new Point();

            p.X = 1.0*x / numtodisplay * plW;            //p.Y = plH/2.0 - y / (short.MaxValue*1.0) * (plH/2.0);            p.Y = plH / 2.0 - y / (Int32.MaxValue * 1.0) * (plH / 2.0);            return p;        }

    }}

And the XAML is given below.

<Window x:Class="WPFs.waveShow"        xmlns="<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation" "="">http://schemas.microsoft.com/winfx/2006/xaml/presentation"<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation" "="">        xmlns:x="<a href="http://schemas.microsoft.com/winfx/2006/xaml" "="">http://schemas.microsoft.com/winfx/2006/xaml"<a href="http://schemas.microsoft.com/winfx/2006/xaml" "="">        Title="waveShow" Height="300" Width="300">    <Grid>        <Canvas x:Name="waveCanvas" HorizontalAlignment="Left" Height="242"                 Margin="10,10,0,0" VerticalAlignment="Top"                 Width="264" Background="Black" />

    </Grid></Window>

0 0
原创粉丝点击