This project has moved and is read-only. For the latest updates, please go here.

Wasapi Mic + Audio loopback recording and mixing

Sep 30, 2011 at 10:15 AM

Hi guys, i'm trying to develop an hardware-independent sound system which can record (using wasapi maybe) audio from soundcard using wasapi loopback (successful) and microphone always from wasapi driver, and mixing them to produce a single wave file. I'm in trouble with microphone recording using wasapi (using WaveIn it works perfectly).

 

        private AudioClient             wasapiSpeakersClient;
        private AudioClient             wasapiMicrophoneClient;

        private NAudio.Wave.WaveFormat  micWaveFormat = new NAudio.Wave.WaveFormat(44100, 32, 1);

 

        public MMDevice GetSpeakers()
        {
            MMDeviceEnumerator devices = new MMDeviceEnumerator();
            MMDeviceCollection coll = devices.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active);
            if (coll.Count > 0)
            {
                return coll[0];
            }
            else
            {
                return null;
            }
        }
        
        public MMDevice GetMicrophone()
        {
            MMDeviceEnumerator devices = new MMDeviceEnumerator();
            MMDeviceCollection coll = devices.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active);
            if (coll.Count > 0)
            {                
                return coll[0];
            }
            else
            {
                return null;
            }            
        }
        private void SetupAudioWasapiLoopback()
        {
            long requestedDuration = REFTIMES_PER_MILLISEC * 100;

            this.wasapiSpeakersClient = GetSpeakers().AudioClient;
            this.wasapiMicrophoneClient = GetMicrophone().AudioClient;

            try
            {
                this.wasapiSpeakersClient.Initialize(AudioClientShareMode.Shared,
                                AudioClientStreamFlags.Loopback,
                                requestedDuration,
                                0,
                                this.wasapiSpeakersClient.MixFormat,
                                Guid.Empty);
                this.IsSpeakersReady = true;
            }
            catch(Exception e)
            {
                this.IsSpeakersReady = false;
                Utils.LogException("wasapiSpeakersClient.Initialize()", e);
            }

            try
            {
                this.wasapiMicrophoneClient.Initialize(AudioClientShareMode.Shared,
                                AudioClientStreamFlags.None,
                                requestedDuration,
                                0,
                                micWaveFormat,//wasapiMicrophoneClient this.wasapiSpeakersClient.MixFormat,
                                Guid.Empty);
                this.IsMicReady = true;
            }
            catch(Exception e)
            {
 this.IsMicReady = false; Utils.LogException("wasapiMicrophoneClient.Initialize()", e); }

 

This code throws the exception on wasapiMicrophoneClient.Initialize()... everytime on any soundcard. Why ?

The microphone is successfully retrieved as "Volume Mic" by GetMicrophone(), but it fails when initializing. I'm missing some piece of code ? Please help me!

Oct 18, 2011 at 3:59 PM

I imagine it is your recording wave format. It must match exactly what WASAPI wants to give you, which is probably not 32 bit integer recording.

Oct 19, 2011 at 9:41 AM

Hi Mark, i've resolved some weeks ago.

Under Win7/Vista i get audio speakers with WASAPI and mic with WaveIn.

      private void WaveIn_DataAvailable(object sender, WaveInEventArgs e)
        {
            byte[] SpeakersBuffer = null;
            int SpeakersOffset = 0;

            if (!this.isSpeaking)
            {
                this.isSpeaking = VoiceActivityDetection(e.Buffer);
            }
            else
            {
                if (this.isWasapiDriver)
                {
                    SpeakersBuffer = ReadNextSpeakersPacket(this.wasapiSpeakersClient.AudioCaptureClient, ref SpeakersOffset);
                    Mix(SpeakersBuffer, SpeakersOffset, e.Buffer, e.BytesRecorded);
                    Thread.Sleep(sleepMilliseconds);
                }
                else
                {
                    EncodeMicrophoneOnly(e.Buffer);
                }
            }
        }

All streams (mic and speakers) are 44.1 Khz, 16 bit stereo. So i mix them byte by byte with following code (may be useful for other people):

int BufferSize = 35280;
            
            //BufferSize = (SpeakersBufferOffset > MicrophoneBufferOffset) ? SpeakersBufferOffset : MicrophoneBufferOffset;
            byte[] spkBufferMono = new byte[BufferSize / 2];
            byte[] micBufferMono = new byte[BufferSize / 2];
            byte[] sample32bit = new byte[4];

            // Convert to mono
            for (int i = 0, j = 0; i < BufferSize; i += 8, j += 4)
            {
                Array.Copy(SpeakersBuffer, i, spkBufferMono, j, 4);
                Array.Copy(MicrophoneBuffer, i, micBufferMono, j, 4);
            }

            //Downgrade to 16 bit            
            for (int i = 0, j = 0; i < BufferSize / 2; i += 4, j+=2)
            {                
                Array.Copy(spkBufferMono, i, sample32bit, 0, 4);
                float f_Speakers = BitConverter.ToSingle(sample32bit, 0);

                Array.Copy(micBufferMono, i, sample32bit, 0, 4);
                float f_Mic = BitConverter.ToSingle(sample32bit, 0);

                Equalize(ref f_Speakers, ref f_Mic);

                short short_Speakers;
                short short_Mic;
                SampleConverter.Convert(f_Speakers, out short_Speakers);
                SampleConverter.Convert(f_Mic, out short_Mic);

                // Mixing
                short snd1 = Convert.ToInt16((~short_Speakers | 1));
                short snd2 = Convert.ToInt16((~short_Mic | 1));
                short o = 0;

                // ensure the value is within range of signed short
                if ((snd1 + snd2) >= -32768 && (snd1 + snd2) <= 32767)
                {
                    o = Convert.ToInt16(snd1 + snd2);
                }

                byte[] b = SignedToComplement(o);

                try
                {
                    this.encoderMP3.Write(b);
                }
                catch
                {
                }
            }

But under Windows XP i don't understand how to get audio speakers. Can you tell me how to get speakers audio without WASAPI ?

Oct 19, 2011 at 9:42 AM

Unfortunately, it's not possible on Windows XP unless the soundcard driver explicitly supports a "What you hear" mode

Mark

Oct 19, 2011 at 2:13 PM

Ok, I figured this, but I was expecting a confirmation of your.

Thank you again.