VBで音量マスターボリューム変更

はじめに

windows vistaからアプリケーションごとに別々に音声ボリュームが設定できるようになり
代わりに今までやり方ではマスターボリュームにはタッチできなくなってしまいました

そしてMSはcore audioという音声コントロールシステムをappleからパクり
COMというおそろしい枠組みに閉じ込めましたとさ

わけあってこれをVB.netから取得設定する事になったのですが
検索にもひっかからず非常に面倒だったので
メモだけでも残しておきます

参考にしたサンプル

EndpointVolume - Windows applications | Microsoft Docs
windows sdk内のサンプル

Vista Core Audio API Master Volume Control - CodeProject
C#4.0のみでcomのロードからライブラリ化までしてある
同じ.netなので.netでCOMのロードのやり方なんかは参考にしやすかった

D_DevLogWindows Vistaでマスターボリュームを変更する
delphiのみでのサンプル
規模が小さく概要がつかみやすかった

コード

殆どがCOMの定義のみで
ロードしたりプロパティ化したりするのは
MMDeviceApi.CAEndpointVolumeクラスでやってる
すごいダルかった!

Imports System.Runtime.InteropServices

Namespace MMDeviceApi


    Public Class CAEndpointVolume
        Implements IDisposable

        Private _realEnumerator As IMMDeviceEnumerator
        Private _realDevice As IMMDevice
        Private _realEndpoint As IAudioEndpointVolume

        Public Sub New()
            If (System.Environment.OSVersion.Version.Major < 6) Then
                'xp以下はアウト
                Throw New NotSupportedException("This functionality is only supported on Windows Vista or newer.")
            End If

            'immdevice enumerator生成
            _realEnumerator = CType(New _MMDeviceEnumerator(), IMMDeviceEnumerator)

            'device取得
            Marshal.ThrowExceptionForHR(_realEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, _realDevice))

            'endpoint取得
            Marshal.ThrowExceptionForHR(_realDevice.Activate(IID_IAudioEndpointVolume, CLSCTX.ALL, IntPtr.Zero, _realEndpoint))
        End Sub

        Sub Dispose() Implements IDisposable.Dispose
            Marshal.ReleaseComObject(_realEndpoint)
            Marshal.ReleaseComObject(_realDevice)
            Marshal.ReleaseComObject(_realEnumerator)
        End Sub

        Public Property Mute() As Boolean
            Get
                Dim ret As Boolean
                Marshal.ThrowExceptionForHR(_realEndpoint.GetMute(ret))
                Return ret
            End Get
            Set(ByVal value As Boolean)
                Marshal.ThrowExceptionForHR(_realEndpoint.SetMute(value, Guid.Empty))
            End Set
        End Property

        Public Property MasterVolume() As Single
            Get
                Dim ret As Single
                Marshal.ThrowExceptionForHR(_realEndpoint.GetMasterVolumeLevelScalar(ret))
                Return ret
            End Get
            Set(ByVal value As Single)
                Marshal.ThrowExceptionForHR(_realEndpoint.SetMasterVolumeLevelScalar(value, Guid.Empty))
            End Set
        End Property

    End Class

    <Flags()> _
    Public Enum CLSCTX : uint
        INPROC_SERVER = &H1
        INPROC_HANDLER = &H2
        LOCAL_SERVER = &H4
        INPROC_SERVER16 = &H8
        REMOTE_SERVER = &H10
        INPROC_HANDLER16 = &H20
        RESERVED1 = &H40
        RESERVED2 = &H80
        RESERVED3 = &H100
        RESERVED4 = &H200
        NO_CODE_DOWNLOAD = &H400
        RESERVED5 = &H800
        NO_CUSTOM_MARSHAL = &H1000
        ENABLE_CODE_DOWNLOAD = &H2000
        NO_FAILURE_LOG = &H4000
        DISABLE_AAA = &H8000
        ENABLE_AAA = &H10000
        FROM_DEFAULT_CONTEXT = &H20000
        INPROC = INPROC_SERVER Or INPROC_HANDLER
        SERVER = INPROC_SERVER Or LOCAL_SERVER Or REMOTE_SERVER
        ALL = SERVER Or INPROC_HANDLER
    End Enum


    Module MMDevGUID
        Public CLASS_IMMDeviceEnumerator As Guid = New Guid("{BCDE0395-E52F-467C-8E3D-C4579291692E}")
        Public IID_IMMDeviceEnumerator As Guid = New Guid("{A95664D2-9614-4F35-A746-DE8DB63617E6}")
        Public IID_IMMDevice As Guid = New Guid("{D666063F-1587-4E43-81F1-B948E807363F}")
        Public IID_IMMDeviceCollection As Guid = New Guid("{0BD7A1BE-7A1A-44DB-8397-CC5392387B5E}")
        Public IID_IAudioEndpointVolume As Guid = New Guid("{5CDF2C82-841E-4546-9722-0CF74078229A}")
        Public IID_IAudioMeterInformation As Guid = New Guid("{C02216F6-8C67-4B5B-9D00-D008E73E0064}")
        Public IID_IAudioEndpointVolumeCallback As Guid = New Guid("{657804FA-D6AD-4496-8A60-352752AF4F89}")
    End Module

    Public Enum EDataFlow
        eRender = 0
        eCapture
        eAll
        EDataFlow_enum_count
    End Enum

    Public Enum ERole
        eConsole = 0
        eMultimedia
        eCommunications
        ERole_enum_count
    End Enum

    <StructLayout(LayoutKind.Sequential)> _
    Public Structure AUDIO_VOLUME_NOTIFICATION_DATA
        Public guidEventContext As Guid
        Public bMuted As Boolean
        Public fMasterVolume As Single
        Public nChannels As UInteger
        Public afChannelVolumes As Single
    End Structure


    <ComImport(), Guid("657804FA-D6AD-4496-8A60-352752AF4F89"), _
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> _
    Public Interface IAudioEndpointVolumeCallback
        <PreserveSig()> Function OnNotify(ByVal pNotifyData As IntPtr) As Integer
    End Interface

    <ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> _
    Public Interface IMMNotificationClient
    End Interface

    <ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> _
    Public Interface IPropertyStore
    End Interface


    'IAudioEndpointVolume再定義
    <ComImport(), Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), _
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> _
    Public Interface IAudioEndpointVolume
        <PreserveSig()> Function RegisterControlChangeNotify(ByVal pNotify As IAudioEndpointVolumeCallback) As Integer
        <PreserveSig()> Function UnregisterControlChangeNotify(ByVal pNotify As IAudioEndpointVolumeCallback) As Integer
        <PreserveSig()> Function GetChannelCount(<Out()> ByRef pnChannelCount As Integer) As Integer
        <PreserveSig()> Function SetMasterVolumeLevel(ByVal fLevelDB As Single, ByVal pguidEventContext As Guid) As Integer
        <PreserveSig()> Function SetMasterVolumeLevelScalar(ByVal fLevel As Single, ByVal pguidEventContext As Guid) As Integer
        <PreserveSig()> Function GetMasterVolumeLevel(<Out()> ByRef pfLevelDB As Single) As Integer
        <PreserveSig()> Function GetMasterVolumeLevelScalar(<Out()> ByRef pfLevel As Single) As Integer
        <PreserveSig()> Function SetChannelVolumeLevel(ByVal nChannel As UInteger, ByVal fLevelDB As Single, ByVal pguidEventContext As Guid) As Integer
        <PreserveSig()> Function SetChannelVolumeLevelScalar(ByVal nChannel As UInteger, ByVal fLevel As Single, ByVal pguidEventContext As Guid) As Integer
        <PreserveSig()> Function GetChannelVolumeLevel(ByVal nChannel As UInteger, <Out()> ByRef pfLevelDB As Single) As Integer
        <PreserveSig()> Function GetChannelVolumeLevelScalar(ByVal nChannel As UInteger, <Out()> ByRef pfLevel As Single) As Integer
        <PreserveSig()> Function SetMute(<MarshalAs(UnmanagedType.Bool)> ByVal bMute As Boolean, ByVal pguidEventContext As Guid) As Integer
        <PreserveSig()> Function GetMute(<Out()> ByRef pbMute As Boolean) As Integer
        <PreserveSig()> Function GetVolumeStepInfo(<Out()> ByRef pnStep As UInteger, <Out()> ByRef pnStepCount As UInteger) As Integer
        <PreserveSig()> Function VolumeStepUp(ByVal pguidEventContext As Guid) As Integer
        <PreserveSig()> Function VolumeStepDown(ByVal pguidEventContext As Guid) As Integer
        <PreserveSig()> Function QueryHardwareSupport(<Out()> ByRef pdwHardwareSupportMask As UInteger) As Integer
        <PreserveSig()> Function GetVolumeRange(<Out()> ByRef pflVolumeMindB As Single, <Out()> ByRef pflVolumeMaxdB As Single, <Out()> ByRef pflVolumeIncrementdB As Single) As Integer
    End Interface


    <ComImport(), Guid("D666063F-1587-4E43-81F1-B948E807363F"), _
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> _
    Public Interface IMMDevice
        <PreserveSig()> _
        Function Activate(ByRef iid As Guid, ByVal dwClsCtx As CLSCTX, ByVal pActivationParams As IntPtr, <MarshalAs(UnmanagedType.IUnknown), Out()> ByRef ppInterface As Object) As Integer
        <PreserveSig()> _
        Function OpenPropertyStore(ByVal stgmAccess As Integer, <Out()> ByRef propertyStore As IPropertyStore) As Integer
        <PreserveSig()> _
        Function GetId(<MarshalAs(UnmanagedType.LPWStr), Out()> ByVal ppstrId As String) As Integer
        <PreserveSig()> _
        Function GetState(<Out()> ByRef pdwState As Integer)
    End Interface

    <ComImport(), Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E"), _
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> _
    Public Interface IMMDeviceCollection
        <PreserveSig()> _
        Function GetCount(<Out()> ByRef pcDevices As UInteger) As Integer
        <PreserveSig()> _
        Function Item(ByVal nDevice As UInteger, <Out()> ByRef Device As IMMDevice) As Integer
    End Interface

    <ComImport(), Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), _
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown)> _
    Public Interface IMMDeviceEnumerator
        <PreserveSig()> _
        Function EnumAudioEndpoints(ByVal dataFlow As EDataFlow, ByVal StateMask As UInteger, <Out()> ByRef device As IMMDeviceCollection) As Integer
        <PreserveSig()> _
        Function GetDefaultAudioEndpoint(ByVal dataFlow As EDataFlow, ByVal role As ERole, <Out()> ByRef ppEndpoint As IMMDevice) As Integer
        <PreserveSig()> _
        Function GetDevice(ByVal pwstrId As String, <Out()> ByRef ppDevice As IMMDevice) As Integer
        <PreserveSig()> _
        Function RegisterEndpointNotificationCallback(ByVal pClient As IntPtr) As Integer
        <PreserveSig()> _
        Function UnregisterEndpointNotificationCallback(ByVal pClient As IntPtr) As Integer
    End Interface

    <ComImport(), Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")> _
    Friend Class _MMDeviceEnumerator
    End Class

End Namespace

使い方

MMDeviceApi.CAEndpointVolumeに集約してあります

  • new
  • Dispose
  • Muteプロパティ
  • MasterVolumeプロパティ

以下のようにusingしながらプロパティを見れば大丈夫

例外処理は適当に付け足してね

'音声の取得
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    Using dev As MMDeviceApi.CAEndpointVolume = New MMDeviceApi.CAEndpointVolume

        CheckBox1.Checked = dev.Mute
        TextBox1.Text = dev.MasterVolume.ToString()

    End Using
End Sub
'音声の設定
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click

    Using dev As MMDeviceApi.CAEndpointVolume = New MMDeviceApi.CAEndpointVolume

        dev.Mute = CheckBox1.Checked
        dev.MasterVolume = Single.Parse(TextBox1.Text)

    End Using
End Sub