1

I've been trying to get audio information using powershell 4.0, specifically the peak audio as sound plays. So I found a .Net wrapper for the core audio api (can be found here and the .dll is located in CoreAudio\bin) and have been trying to use that. However, for the life of me, I cannot figure out how to do so. The main problem is I have not been able to figure out the syntax for accessing and using the methods for the classes contained within the assembly. Specifically, I am trying to work with CoreAudio.AudioPeakMeter and CoreAudio.AudioMute.

CoreAudio.AudioPeakMeter, for example, should have two members, GetChannelCount and GetLevel.

I have tried using

[CoreAudio.AudioPeakMeter] | Get-Member -Static

but that only returns Equals and ReferenceEquals which is confirmed when I use

[CoreAudio.AudioPeakMeter]::

I even found a function someone had posted online for retrieving constructors. Again, this turned up nothing. As in literally nothing. No output is returned. I confirmed this works for others such as System.Windows.Thickness which was used an example on the post.

Here's the Get-Contstructor function I found if you are interested.

function get-Constructor ([type]$type, [Switch]$FullName)
{
    foreach ($c in $type.GetConstructors())
    {
        $type.Name + "("
        foreach ($p in $c.GetParameters())
        {
             if ($fullName)
             {
                  "`t{0} {1}," -f $p.ParameterType.FullName, $p.Name 
             }else
             {
                  "`t{0} {1}," -f $p.ParameterType.Name, $p.Name 
             }
        }
        ")"
    }
}

HOWEVER, I know GetChannelCount and GetLevel exist and can be found in ISE via

(New-Object CoreAudio.AudioPeakMeter).GetChannelCount

as well as using the command

[CoreAudio.AudioPeakMeter].GetMethods()

For ISE, after entering the period both GetChannelCount and GetLevel populate in the ISE list. But of course, since the initial issue is New-Object re an error on CoreAudio.AudioPeakMeter, powershell doesn't even attempt to access the two members.

The latter command returns this which I've posted to pastebin.

The method property IsSecurityCritical made me think I need to run powershell with elevated privileges. However, that does not seem to be the case as it made no difference.

I've confirmed that these results are the same for all of the classes in the assembly. I can access the enumerators though. Here's an example:

[enum]::GetNames([CoreAudio.DEVICE_STATE])  

This will properly return all possible values of CoreAudio.DEVICE_STATE.

Is there something I'm missing? As far as I can tell the assembly is properly loaded (I used Add-Type) but powershell says the class methods do not exist even though they do.

Mechajesus
  • 11
  • 2
  • The `AudioPeakMeter` class does not have a public constructor. It looks like one of the few classes with a public constructor is `MMDeviceEnumerator`. I would start there. You could probably get more info from reading the included samples. – Mike Zboray Sep 30 '14 at 00:04
  • My knowledge of C# (or any version of C really) is pretty limited so I'm trying to understand the source code and examples as best I can but am still coming up short. From what I can tell and from my research, it looks like AudioPeakMeter has public constructors but Powershell seems to be unable to see them. I've also noticed `[CoreAudio.AudioPeakMeter]::GetChannelCount` runs with no errors however no values are returned. From the source code, it should be returning an int. – Mechajesus Sep 30 '14 at 20:47

1 Answers1

0

Found the solution! It's not exactly the solution I was looking for but it works. I did some more research and found this post. It seems .net wrappers for the core audio api simply don't play nice with powershell. I ended up taking a look at one of the answers for that question, figuring out how it worked, and modifying it to do what I needed. Poking around the api more, I figured out I was trying to use the wrong interface anyway. I needed to be using IAudioMeterInformation instead of IAudioPeakMeter.

Here's what I came up with that works:

Add-Type -TypeDefinition @'
using System.Runtime.InteropServices;

[Guid("C02216F6-8C67-4B5B-9D00-D008E73E0064"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IAudioMeterInformation {
  int GetPeakValue(out float pfPeak);
}
[Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IMMDevice {
  int Activate(ref System.Guid id, int clsCtx, int activationParams, out IAudioMeterInformation aev);
}
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IMMDeviceEnumerator {
  int f(); // Unused
  int GetDefaultAudioEndpoint(int dataFlow, int role, out IMMDevice endpoint);
}
[ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] class MMDeviceEnumeratorComObject { }

public class AudioMeter {
  static IAudioMeterInformation Meter() {
    var enumerator = new MMDeviceEnumeratorComObject() as IMMDeviceEnumerator;
    IMMDevice dev = null;
    Marshal.ThrowExceptionForHR(enumerator.GetDefaultAudioEndpoint(/*eRender*/ 0, /*eMultimedia*/ 1, out dev));
    IAudioMeterInformation epv = null;
    var epvid = typeof(IAudioMeterInformation).GUID;
    Marshal.ThrowExceptionForHR(dev.Activate(ref epvid, /*CLSCTX_ALL*/ 23, 0, out epv));
    return epv;
  }
  public static float PeakValue {
    get {float mv = -1; Marshal.ThrowExceptionForHR(Meter().GetPeakValue(out mv)); return mv;}
  }
}    
'@

Now I can retrieve the peak value for the audio device and stream the value via a loop. The peak value is a floating variable ranging from 0 to 1 so I multiplied the value by 100 and converted it to an integer to round the value to the nearest whole number. The loop ends when a key press is detected.

do
{
    Start-Sleep -Milliseconds 100
    $var = [AudioMeter]::PeakValue * 100
    $var
}
until($Host.UI.RawUI.KeyAvailable)
$Host.UI.RawUI.FlushInputBuffer()

I still haven't figured out how to be able to use the IMMDevice::Activate() method on multiple interfaces simultaneously so I can use IAudioEndpointVolume as well to access the Mute method but that's probably a different topic for a different day.

Community
  • 1
  • 1
Mechajesus
  • 11
  • 2