Reading the microphone’s audio stream in WinRT

I wanted to use the microphone on a Surface tablet to measure the audio level in the room. The technique I used was to implement my own stream, and then have the Microphone write to it.

Set up the MediaCapture class as usual:

 

var media = new MediaCapture();
var captureInitSettings = new MediaCaptureInitializationSettings();
captureInitSettings.StreamingCaptureMode = StreamingCaptureMode.Audio;
await media.InitializeAsync(captureInitSettings);
media.Failed += (_, ex) => new MessageDialog(ex.Message).ShowAsync();

var stream = new AudioAmplitudeStream(); // custom stream implementation
media.StartRecordToStreamAsync(MediaEncodingProfile.CreateWav(AudioEncodingQuality.Low), stream);
stream.AmplitudeReading += AmplitudeReading; // get an amplitude event

 

But you’ll notice I’m passing in an ‘AudioAmplitudeStream’. This is my custom stream implementation which just takes the average of the amplitude reported by the microphone. Here’s the code:

 

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Storage.Streams;

namespace IoT.WinRT
{
    class AudioAmplitudeStream : IRandomAccessStream
    {
        public bool CanRead
        {
            get { return false; }
        }

        public bool CanWrite
        {
            get { return true; }
        }

        public IRandomAccessStream CloneStream()
        {
            throw new NotImplementedException();
        }

        public IInputStream GetInputStreamAt(ulong position)
        {
            throw new NotImplementedException();
        }

        public IOutputStream GetOutputStreamAt(ulong position)
        {
            throw new NotImplementedException();
        }

        public ulong Position
        {
            get { return 0; }
        }

        public void Seek(ulong position)
        {

        }

        public ulong Size
        {
            get
            {
                return 0;
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public void Dispose()
        {

        }

        public Windows.Foundation.IAsyncOperationWithProgress<IBuffer, uint> ReadAsync(IBuffer buffer, uint count, InputStreamOptions options)
        {
            throw new NotImplementedException();
        }

        public Windows.Foundation.IAsyncOperation FlushAsync()
        {
            return AsyncInfo.Run(_ => Task.Run(() => true));
        }

        public Windows.Foundation.IAsyncOperationWithProgress<uint, uint> WriteAsync(IBuffer buffer)
        {

            return AsyncInfo.Run<uint, uint>((token, progress) =>
            {
                return Task.Run(() =>
                {
                    using (var memoryStream = new MemoryStream())
                    using (var outputStream = memoryStream.AsOutputStream())
                    {
                        outputStream.WriteAsync(buffer).AsTask().Wait();

                        var byteArray = memoryStream.ToArray();
                        var amplitude = Decode(byteArray).Select(Math.Abs).Average(x => x);

                        if (AmplitudeReading != null) this.AmplitudeReading(this, amplitude);

                        progress.Report((uint)memoryStream.Length);
                        return (uint)memoryStream.Length;
                    }
                });
            });
        }

        private IEnumerable Decode(byte[] byteArray)
        {
            for (var i = 0; i < byteArray.Length - 1; i += 2)
            {
                yield return (BitConverter.ToInt16(byteArray, i));
            }
        }

        public delegate void AmplitudeReadingEventHandler(object sender, double reading);

        public event AmplitudeReadingEventHandler AmplitudeReading;

    }
}

 

The stream is only partially implemented, not all the calls are required.

You can then subscribe to the event, and show the background noise level in your app!

 

void AmplitudeReading(object sender, double reading)
{
    Debug.WriteLine("Noise level: {0:0} dB", ToDb(reading));           
}

static double ToDb(double value)
{
    return 20 * Math.Log10(Math.Sqrt(value * 2));
}

Here is a sample application: https://github.com/richorama/WinRTdB

Have fun!

Advertisements