Using Wasapi instead of WaveIn and WaveOut: show the device properties?

Nov 4, 2014 at 6:20 PM
In the Recorder example you show how to get devices and the characteristics. As these classes are deprecated, I was writing a new program with Wasapi. I can get hold of the output devices, but I cannot get to the channels and device driver (as you can in your example).
Could you help me?
I found the Guid's for the properties. I can insert them in Win32 PropertyStore.cs but not in PropertyStore [from Metadata] (where one arrives when Goto Definition is used), so they remain invalid. Inserting them in my source results in NULL values returned.

My source:
    WasapiOut wasapiCapture = new WasapiOut();
    public Form1()
    {
        InitializeComponent();
        MMDeviceEnumerator deviceEnumerator = new  MMDeviceEnumerator();
        MMDeviceCollection deviceCollection = deviceEnumerator.EnumAudioEndpoints(DataFlow.Render, DeviceState.Active);
        foreach (var device in deviceCollection)
        {
            var item = new ListViewItem(device.FriendlyName);
            Console.WriteLine("Name: {0}", device.PropertyStore[PropertyStore.FriendlyName]);
            Console.WriteLine("Chan: {0}", device.PropertyStore[PKEY_Audio_ChannelCount]);
            Console.WriteLine("Vers: {0}", device.PropertyStore[PKEY_Hardware_DriverVersion]);

            //item.SubItems.Add(device.Channels.ToString());
            //item.SubItems.Add(device.DriverVersion.ToString());
            item.Tag = device;
            deviceList.Items.Add(item);
        }
    }
the Guid's:
    public static readonly PropertyKey PKEY_Audio_ChannelCount = new PropertyKey(
        new Guid(0x64440490, 0x4C8B, 0x11D1, 0x8B, 0x70, 0x08, 0x00, 0x36, 0xB1, 0x1A, 0x03),
        7);

    public static readonly PropertyKey PKEY_Hardware_DriverVersion = new PropertyKey(
        new Guid(0x5EAF3EF2, 0xE0CA, 0x4598, 0xBF, 0x06, 0x71, 0xED, 0x1D, 0x9D, 0xD9, 0x53),
        9);
Coordinator
Nov 11, 2014 at 11:53 AM
I've just had a look at the PKEY_Audio_ChannelCount key. I've found this header file: https://github.com/eddieringle/portaudio/blob/master/src/hostapi/wasapi/mingw-include/propkey.h and the documentation of the key tells us
Indicates the channel count for the audio file. Values: 1 (mono), 2 (stereo).
But I've found a way to determine the WaveFormat:
MMDevice device = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Console);
var blob = device.PropertyStore[new PropertyKey(new Guid(0xf19f064d, 0x82c, 0x4e27, 0xbc, 0x73, 0x68,
    0x82, 0xa1, 0xbb, 0x8e, 0x4c), 0)].BlobValue;
Console.WriteLine(WaveFormatFromBlob(blob));

...

private static WaveFormat WaveFormatFromBlob(Blob blob)
{
    if (blob.Length == 0)
        return null;
    if(blob.Length == 40)
        return (WaveFormatExtensible)Marshal.PtrToStructure(blob.Data, typeof(WaveFormatExtensible));                
    
    return (WaveFormat)Marshal.PtrToStructure(blob.Data, typeof (WaveFormat));
}
But please note that not all devices offer this property. So you may have to handle some exceptions.
Nov 17, 2014 at 1:06 PM
Edited Nov 23, 2014 at 7:45 PM
I deleted this entry, as it is of little use, now I posted the answer below
Nov 23, 2014 at 7:44 PM
I found the answer in a reply you posted on StackOverflow. I now have a working version of your WMA recorder catching the sound that is playing on the speakers of the pc. I'd like to give you the source, that you might add it as an example. Seems usefull to me!
The principal parts are these Form elements:
        private System.Windows.Forms.TextBox outFileName;
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.Button browseButton;
        private System.Windows.Forms.Label label2;
        private System.Windows.Forms.Button recordButton;
        private System.Windows.Forms.Button stopButton;
        private System.Windows.Forms.ListView deviceList;
        private System.Windows.Forms.ColumnHeader columnName;
        private System.Windows.Forms.ColumnHeader columnChannels;
        private System.Windows.Forms.Label label3;
        private System.Windows.Forms.TextBox DeviceName;
and this as the source for processing the form:
    using System;
    using System.Windows.Forms;
    using CSCore;
    using CSCore.SoundIn;
    using CSCore.SoundOut;
    using CSCore.CoreAudioAPI;
    using CSCore.MediaFoundation;
    using CSCore.Win32;
    using System.Runtime.InteropServices;
    namespace RecordWhatYouHear
    {
    public partial class RecordHear : Form
    {
        bool Programmed = false; // don't act on programmed changes 
        MMDevice deviceSelected;
        WasapiCapture capture;
        MediaFoundationEncoder writer;
        bool gotSound = false; // don't record if nothing is playing yet.
        private static WaveFormat WaveFormatFromBlob(Blob blob)
        {
            if (blob.Length == 40)
                return (WaveFormat)Marshal.PtrToStructure(blob.Data, typeof(WaveFormatExtensible));
            return (WaveFormat)Marshal.PtrToStructure(blob.Data, typeof(WaveFormat));
        }
        public RecordHear()
        {
            InitializeComponent();

            MMDeviceEnumerator deviceEnumerator = new MMDeviceEnumerator();
            MMDeviceCollection deviceCollection = deviceEnumerator.EnumAudioEndpoints(DataFlow.Render, DeviceState.Active);
            foreach (MMDevice device in deviceCollection)
            {
                var item = new ListViewItem(device.FriendlyName);
                item.Tag = device;
                if (deviceList.Items.Count == 0)
                {
                    DeviceName.Text = item.Text;
                    deviceSelected = device;
                }
                WaveFormat waveFormat = WaveFormatFromBlob(device.PropertyStore[new PropertyKey(new Guid(0xf19f064d, 0x82c, 0x4e27, 0xbc, 0x73, 0x68,
                                                                              0x82, 0xa1, 0xbb, 0x8e, 0x4c), 0)].BlobValue);

                item.SubItems.Add(waveFormat.Channels.ToString());
                deviceList.Items.Add(item);
            }
            recordButton.Visible = (DeviceName.Text != "" && outFileName.Text != "");
            if (deviceList.Items.Count == 1)
           {
                  deviceList.Visible = false;
                  label2.Visible = false;
           }

        }

        private void browseButton_Click(object sender, EventArgs e)
        {
            SaveFileDialog sfd = new SaveFileDialog();
            sfd.Filter = "WMA (*.wma)|*.wma";
            sfd.Title = "Soundfilename";
            sfd.FileName = String.Empty;
            if (sfd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                outFileName.Text = sfd.FileName;
                recordButton.Visible = (DeviceName.Text != "" && outFileName.Text != "");
            }
        }

        private void deviceList_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (!Programmed) // react only on USER input
            {
                Programmed = true;
                foreach (ListViewItem item in deviceList.SelectedItems)
                {
                    DeviceName.Text = item.Text;
                    deviceSelected = (MMDevice)item.Tag;
                    item.Selected = false;
                }
                Programmed = false;
            }
        }

        private void recordButton_Click(object sender, EventArgs e)
        {
            capture = new WasapiLoopbackCapture();            
            // you can choose a device here
            capture.Device = deviceSelected;
            //initialize the selected device for recording
            capture.Initialize();
            //create a wavewriter to write the data to
            try
            {
                writer = MediaFoundationEncoder.CreateWMAEncoder(capture.WaveFormat, outFileName.Text);
            }
            catch (Exception em)
            {
                MessageBox.Show(em.Message, "WMA file Error",  MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                capture.Dispose();
                return;
            }
            //setup an eventhandler to receive the recorded data
            capture.DataAvailable += (s, ec) =>
                {
                    if (!gotSound)
                    {
                        for (int c = 0; c < ec.ByteCount && !gotSound; c++) if (ec.Data[c] != 0) gotSound = true;
                    }
                    //save the recorded audio
                    if (gotSound) writer.Write(ec.Data, ec.Offset, ec.ByteCount);
               };
             //start recording
             capture.Start();
             stopButton.Visible = true;
             recordButton.Visible = false;
        }
        private void outFileName_TextChanged(object sender, EventArgs e)
        {
            recordButton.Visible = (DeviceName.Text != "" && outFileName.Text != "");
        }
        private void stopButton_Click(object sender, EventArgs e)
        {
            capture.Stop();
            stopButton.Visible = false;
            recordButton.Visible = true;
            writer.Dispose();
            writer = null;
            capture.Dispose();
            capture = null;
            gotSound = false;
        }
     }
     }
Coordinator
Nov 23, 2014 at 7:56 PM
Thanks for sharing. I am gonna try it out and if it works fine, I am gonna add it as an example.
Nov 24, 2014 at 6:44 AM
Download the project without .exe's from here so you waste no time on implementing the Form!