Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sending playing Audio using NAudio

I have managed to send audio from a microphone using the code found here.

However I have not been able to do this using NAudio.

The code from CodeProject has explicit code to encode and decode such as:

G711.Encode_aLaw
G711.Decode_uLaw

to translate and return bytes to send across the network.

Is it possible to get some sample code for NAudio for the CodeProject application above?

like image 438
TheWommies Avatar asked Nov 14 '22 03:11

TheWommies


1 Answers

Here's a quick C# Console App that I wrote using NAudio, microphone input, speaker output, with u-Law or A-Law encoding. The NAudio.Codecs namespace contains A-Law and u-Law encoders and decoders.

This program does not send data across the network (it's not hard to do, I just didn't feel like doing it here). I'll leave that to you. Instead, it contains a "Sender" thread and a "Receiver" thread.

The microphone DataAvailable event handler just drops the byte buffer into a queue (it makes a copy of the buffer - you don't want to hold on to the actual buffer from the event). The "Sender" thread grabs the queued buffers, converts the PCM data to g.711 and drops it into a second queue. This "drops into a second queue" part is where you'd send to a remote UDP destination for your particular app.

The "Receiver" thread reads the data from the second queue, converts it back to PCM, and feeds it to a BufferedWaveProvider that's being used by the WaveOut (speaker) device. You would replace this input with a UDP socket receive for your networked application.

Note that the program guarantees that the PCM input and output (microphone and speaker) are using the same WaveFormat. That's something that you'd also have to do for networked endpoints.

Anyway, it works. So here's the code. I won't go into too much detail. There are lots of comments to try to help understand what's going on:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using NAudio.Wave;
using NAudio.Codecs;

namespace G711MicStream
{
    class Program
    {
        delegate byte EncoderMethod( short _raw );
        delegate short DecoderMethod( byte _encoded );

        // Change these to their ALaw equivalent if you want.
        static EncoderMethod Encoder = MuLawEncoder.LinearToMuLawSample;
        static DecoderMethod Decoder = MuLawDecoder.MuLawToLinearSample;



        static void Main(string[] args)
        {
            // Fire off our Sender thread.
            Thread sender = new Thread(new ThreadStart(Sender));
            sender.Start();

            // And receiver...
            Thread receiver = new Thread(new ThreadStart(Receiver));
            receiver.Start();

            // We're going to try for 16-bit PCM, 8KHz sampling, 1 channel.
            // This should align nicely with u-law
            CommonFormat = new WaveFormat(16000, 16, 1);

            // Prep the input.
            IWaveIn wavein = new WaveInEvent();
            wavein.WaveFormat = CommonFormat;
            wavein.DataAvailable += new EventHandler<WaveInEventArgs>(wavein_DataAvailable);
            wavein.StartRecording();

            // Prep the output.  The Provider gets the same formatting.
            WaveOut waveout = new WaveOut();
            OutProvider = new BufferedWaveProvider(CommonFormat);
            waveout.Init(OutProvider);
            waveout.Play();


            // Now we can just run until the user hits the <X> button.
            Console.WriteLine("Running g.711 audio test.  Hit <X> to quit.");
            for( ; ; )
            {
                Thread.Sleep(100);
                if( !Console.KeyAvailable ) continue;
                ConsoleKeyInfo info = Console.ReadKey(false);
                if( (info.Modifiers & ConsoleModifiers.Alt) != 0 ) continue;
                if( (info.Modifiers & ConsoleModifiers.Control) != 0 ) continue;

                // Quit looping on non-Alt, non-Ctrl X
                if( info.Key == ConsoleKey.X ) break;                
            }

            Console.WriteLine("Stopping...");

            // Shut down the mic and kick the thread semaphore (without putting
            // anything in the queue).  This will (eventually) stop the thread
            // (which also signals the receiver thread to stop).
            wavein.StopRecording();
            try{ wavein.Dispose(); } catch(Exception){}
            SenderKick.Release();

            // Wait for both threads to exit.
            sender.Join();
            receiver.Join();

            // And close down the output.
            waveout.Stop();
            try{ waveout.Dispose(); } catch(Exception) {}

            // Sleep a little.  This seems to be accepted practice when shutting
            // down these audio components.
            Thread.Sleep(500);
        }


        /// <summary>
        /// Grabs the mic data and just queues it up for the Sender.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void  wavein_DataAvailable(object sender, WaveInEventArgs e)
        {
            // Create a local copy buffer.
            byte [] buffer = new byte [e.BytesRecorded];
            System.Buffer.BlockCopy(e.Buffer, 0, buffer, 0, e.BytesRecorded);

            // Drop it into the queue.  We'll need to lock for this.
            Lock.WaitOne();
            SenderQueue.AddLast(buffer);
            Lock.ReleaseMutex();

            // and kick the thread.
            SenderKick.Release();
        }


        static
        void
        Sender()
        {
            // Holds the data from the DataAvailable event.
            byte [] qbuffer = null;

            for( ; ; )
            {
                // Wait for a 'kick'...
                SenderKick.WaitOne();

                // Lock...
                Lock.WaitOne();
                bool dataavailable = ( SenderQueue.Count != 0 );
                if( dataavailable )
                {
                    qbuffer = SenderQueue.First.Value;
                    SenderQueue.RemoveFirst();
                }
                Lock.ReleaseMutex();

                // If the queue was empty on a kick, then that's our signal to
                // exit.
                if( !dataavailable ) break;

                // Convert each 16-bit PCM sample to its 1-byte u-law equivalent.
                int numsamples = qbuffer.Length / sizeof(short);
                byte [] g711buff = new byte [numsamples];

                // I like unsafe for this kind of stuff!
                unsafe
                {
                    fixed( byte * inbytes = &qbuffer[0] )
                    fixed( byte * outbytes = &g711buff[0] )
                    {
                        // Recast input buffer to short[]
                        short * buff = (short *)inbytes;

                        // And loop over the samples.  Since both input and
                        // output are 16-bit, we can use the same index.
                        for( int index = 0; index < numsamples; ++index )
                        {
                            outbytes[index] = Encoder(buff[index]);
                        }
                    }
                }

                // This gets passed off to the reciver.  We'll queue it for now.
                Lock.WaitOne();
                ReceiverQueue.AddLast(g711buff);
                Lock.ReleaseMutex();
                ReceiverKick.Release();
            }

            // Log it.  We'll also kick the receiver (with no queue addition)
            // to force it to exit.
            Console.WriteLine("Sender: Exiting.");
            ReceiverKick.Release();
        }

        static
        void
        Receiver()
        {
            byte [] qbuffer = null;
            for( ; ; )
            {
                // Wait for a 'kick'...
                ReceiverKick.WaitOne();

                // Lock...
                Lock.WaitOne();
                bool dataavailable = ( ReceiverQueue.Count != 0 );
                if( dataavailable )
                {
                    qbuffer = ReceiverQueue.First.Value;
                    ReceiverQueue.RemoveFirst();
                }
                Lock.ReleaseMutex();

                // Exit on kick with no data.
                if( !dataavailable ) break;

                // As above, but we convert in reverse, from 1-byte u-law
                // samples to 2-byte PCM samples.
                int numsamples = qbuffer.Length;
                byte [] outbuff = new byte [qbuffer.Length * 2];
                unsafe
                {
                    fixed( byte * inbytes = &qbuffer[0] )
                    fixed( byte * outbytes = &outbuff[0] )
                    {
                        // Recast the output to short[]
                        short * outpcm = (short *)outbytes;

                        // And loop over the u-las samples.
                        for( int index = 0; index < numsamples; ++index )
                        {
                            outpcm[index] = Decoder(inbytes[index]);
                        }
                    }
                }

                // And write the output buffer to the Provider buffer for the
                // WaveOut devices.
                OutProvider.AddSamples(outbuff, 0, outbuff.Length);
            }

            Console.Write("Receiver: Exiting.");
        }


        /// <summary>Lock for the sender queue.</summary>
        static Mutex Lock = new Mutex();

        static WaveFormat CommonFormat;

        /// <summary>"Kick" semaphore for the sender queue.</summary>
        static Semaphore SenderKick = new Semaphore(0, int.MaxValue);
        /// <summary>Queue of byte buffers from the DataAvailable event.</summary>
        static LinkedList<byte []> SenderQueue = new LinkedList<byte[]>();

        static Semaphore ReceiverKick = new Semaphore(0, int.MaxValue);
        static LinkedList<byte []> ReceiverQueue = new LinkedList<byte[]>();

        /// <summary>WaveProvider for the output.</summary>
        static BufferedWaveProvider OutProvider;
    }
}
like image 105
Bob C Avatar answered Dec 28 '22 06:12

Bob C