列出 Select 个声卡来播放声音

List and Select Sound Card to Play Sound

我有一个用 Visual Basic 2010 Express 编码的校铃项目。运行我的程序的计算机有两个或多个声卡。首先,我将向用户列出声卡。用户将 select 声卡工作。最后,我的程序将在该声卡上响铃。我的代码一切正常,但我无法列出声卡的名称并在指定的声卡上响铃。

我使用 WMPLib 来播放音乐。我有这些代码,但出现错误 "the value is not in the expected range"。我发现我的代码中的错误所在:

    Public Declare Function waveOutGetNumDevs Lib "winmm" () As Integer
    Public Declare Function mciSendCommand Lib "winmm.dll" Alias "mciSendCommandA" (ByVal wDeviceID As Integer, ByVal uMessage As String, ByVal dwParam1 As Integer, ByVal dwParam2 As Object) As Integer
    Public Declare Function mciGetErrorString Lib "winmm.dll" Alias "mciGetErrorStringA" (ByVal dwError As Integer, ByVal lpstrBuffer As String, ByVal uLength As Integer) As Integer
    Public Declare Function mciSendString Lib "winmm.dll" Alias "mciSendStringA" (ByVal lpstrCommand As String, ByVal lpstrReturnString As String, ByVal uReturnLength As Integer, ByVal hwndCallback As Integer) As Integer
    Public Declare Function mciGetDeviceID Lib "winmm.dll" Alias "mciGetDeviceIDA" (ByVal lpstrName As String) As Integer

Public Const MMSYSERR_NOERROR = 0
    Public Const MCI_SET = &H80D
    Public Const MCI_WAVE_OUTPUT = &H800000
    Public Structure MCI_WAVE_SET_PARMS
        Dim dwCallback As Integer
        Dim dwTimeFormat As Integer
        Dim dwAudio As Integer
        Dim wInput As Integer
        Dim wOutput As Integer
        Dim wFormatTag As Short
        Dim wReserved2 As Short
        Dim nChannels As Short
        Dim wReserved3 As Short
        Dim nSamplesPerSec As Integer
        Dim nAvgBytesPerSec As Integer
        Dim nBlockAlign As Short
        Dim wReserved4 As Short
        Dim wBitsPerSample As Short
        Dim wReserved5 As Short
    End Structure

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        Dim parms As MCI_WAVE_SET_PARMS
        Dim wDeviceID As Integer
        Dim ret As Integer

        parms.wOutput = 0

        wDeviceID = mciGetDeviceID("waveaudio")

        ' the value is not in the expected range error is here and it spots parms
        ret = mciSendCommand(wDeviceID, MCI_SET, MCI_WAVE_OUTPUT, parms)

        If (ret <> MMSYSERR_NOERROR) Then
            Stop
        End If

        If ofd.ShowDialog Then
            ret = mciSendString("Open " & Chr(34) & ofd.FileName & Chr(34) & " alias audio", CStr(0), 0, 0)
            ret = mciSendString("Open audio", CStr(0), 0, 0)
        End If
    End Sub

WindowsMedia.Net

您可以使用 WindowsMedia.Net library 执行此操作。

以下示例取自下面的link,它是一个Windows形式的代码,包含列出所有可用音频设备和选择默认设备所需的功能(一个将作为声音输出的)。

首先我会尝试将代码分成两部分:

  1. 列出可用的音频设备
  2. 更改默认音频设备

列出可用设备

Private Sub RefreshInformation()
    PopulateDeviceComboBox()
    DisplayDefaultWaveOutDevice()
End Sub

Private Sub PopulateDeviceComboBox()
    DevicesComboBox.Items.Clear()
    ' How many wave out devices are there? WaveOutGetNumDevs API call.
    Dim waveOutDeviceCount As Integer = waveOut.GetNumDevs()
    For i As Integer = 0 To waveOutDeviceCount - 1
        Dim caps As New WaveOutCaps
        ' Get a name - its in a WAVEOUTCAPS structure. 
        ' The name is truncated to 31 chars by the api call. You probably have to 
        ' dig around in the registry to get the full name.
        Dim result As Integer = waveOut.GetDevCaps(i, caps, Marshal.SizeOf(caps))
        If result <> MMSYSERR.NoError Then
            Dim err As MMSYSERR = DirectCast(result, MMSYSERR)
            Throw New Win32Exception("GetDevCaps() error, Result: " & result.ToString("x8") & ", " & err.ToString)
        End If
        DevicesComboBox.Items.Add(New WaveOutDevice(i, caps))
    Next
    DevicesComboBox.SelectedIndex = 0
End Sub

Private Sub DisplayDefaultWaveOutDevice()
    Dim currentDefault As Integer = GetIdOfDefaultWaveOutDevice()
    Dim device As WaveOutDevice = DirectCast(DevicesComboBox.Items(currentDefault), WaveOutDevice)
    DefaultDeviceLabel.Text = "Defualt: " & device.WaveOutCaps.szPname
End Sub

Private Function GetIdOfDefaultWaveOutDevice() As Integer        
    Dim id As Integer = 0
    Dim hId As IntPtr
    Dim flags As Integer = 0
    Dim hFlags As IntPtr
    Dim result As Integer
    Try
        ' It would be easier to declare a nice overload with ByRef Integers.
        hId = Marshal.AllocHGlobal(4)
        hFlags = Marshal.AllocHGlobal(4)
        ' http://msdn.microsoft.com/en-us/library/bb981557.aspx
        result = waveOut.Message(WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, hId, hFlags)
        If result <> MMSYSERR.NoError Then
            Dim err As MMSYSERR = DirectCast(result, MMSYSERR)
            Throw New Win32Exception("waveOutMessage() error, Result: " & result.ToString("x8") & ", " & err.ToString)
        End If
        id = Marshal.ReadInt32(hId)
        flags = Marshal.ReadInt32(hFlags)
    Finally
        Marshal.FreeHGlobal(hId)
        Marshal.FreeHGlobal(hFlags)
    End Try
    ' There is only one flag, DRVM_MAPPER_PREFERRED_FLAGS_PREFERREDONLY, defined as 1
    ' "When the DRVM_MAPPER_PREFERRED_FLAGS_PREFERREDONLY flag bit is set, ... blah ...,  
    ' the waveIn and waveOut APIs use only the current preferred device and do not search 
    ' for other available devices if the preferred device is unavailable. 
    Return id
End Function

更改默认设备

Private Sub SetDefaultButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles SetDefaultButton.Click
    If DevicesComboBox.Items.Count = 0 Then Return
    Dim selectedDevice As WaveOutDevice = DirectCast(DevicesComboBox.SelectedItem, WaveOutDevice)
    SetDefault(selectedDevice.Id)
    RefreshInformation()
End Sub

    Private Sub SetDefault(ByVal id As Integer)
        Dim defaultId As Integer = GetIdOfDefaultWaveOutDevice()
        If defaultId = id Then Return ' no change.
        Dim result As Integer
        ' So here we say "change the Id of the device that has id id to 0", which makes it the default.
        result = waveOut.Message(WAVE_MAPPER, DRVM_MAPPER_PREFERRED_SET, New IntPtr(id), IntPtr.Zero)
        If result <> MMSYSERR.NoError Then
            Dim err As MMSYSERR = DirectCast(result, MMSYSERR)
            Throw New Win32Exception("waveOutMessage() error, Result: " & result.ToString("x8") & ", " & err.ToString)
        End If
    End Sub

完整代码

Imports MultiMedia 
Imports System.Runtime.InteropServices
Imports System.ComponentModel

Public Class Form1

    Private DevicesComboBox As New ComboBox
    Private DefaultDeviceLabel As New Label
    Private WithEvents SetDefaultButton As New Button
    Private Const DRVM_MAPPER_PREFERRED_GET As Integer = &H2015
    Private Const DRVM_MAPPER_PREFERRED_SET As Integer = &H2016
    Private WAVE_MAPPER As New IntPtr(-1)

    ' This just brings together a device ID and a WaveOutCaps so 
    ' that we can store them in a combobox.
    Private Structure WaveOutDevice

        Private m_id As Integer
        Public Property Id() As Integer
            Get
                Return m_id
            End Get
            Set(ByVal value As Integer)
                m_id = value
            End Set
        End Property

        Private m_caps As WaveOutCaps
        Public Property WaveOutCaps() As WaveOutCaps
            Get
                Return m_caps
            End Get
            Set(ByVal value As WaveOutCaps)
                m_caps = value
            End Set
        End Property

        Sub New(ByVal id As Integer, ByVal caps As WaveOutCaps)
            m_id = id
            m_caps = caps
        End Sub

        Public Overrides Function ToString() As String
            Return WaveOutCaps.szPname
        End Function

    End Structure

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' I do use the IDE for this stuff normally... (in case anyone is wondering)
        Me.Controls.AddRange(New Control() {DevicesComboBox, DefaultDeviceLabel, SetDefaultButton})
        DevicesComboBox.Location = New Point(5, 5)
        DevicesComboBox.DropDownStyle = ComboBoxStyle.DropDownList
        DevicesComboBox.Width = Me.ClientSize.Width - 10
        DevicesComboBox.Anchor = AnchorStyles.Left Or AnchorStyles.Right
        DefaultDeviceLabel.Location = New Point(DevicesComboBox.Left, DevicesComboBox.Bottom + 5)
        DefaultDeviceLabel.AutoSize = True
        SetDefaultButton.Location = New Point(DefaultDeviceLabel.Left, DefaultDeviceLabel.Bottom + 5)
        SetDefaultButton.Text = "Set Default"
        SetDefaultButton.AutoSize = True
        RefreshInformation()
    End Sub

    Private Sub RefreshInformation()
        PopulateDeviceComboBox()
        DisplayDefaultWaveOutDevice()
    End Sub

    Private Sub PopulateDeviceComboBox()
        DevicesComboBox.Items.Clear()
        ' How many wave out devices are there? WaveOutGetNumDevs API call.
        Dim waveOutDeviceCount As Integer = waveOut.GetNumDevs()
        For i As Integer = 0 To waveOutDeviceCount - 1
            Dim caps As New WaveOutCaps
            ' Get a name - its in a WAVEOUTCAPS structure. 
            ' The name is truncated to 31 chars by the api call. You probably have to 
            ' dig around in the registry to get the full name.
            Dim result As Integer = waveOut.GetDevCaps(i, caps, Marshal.SizeOf(caps))
            If result <> MMSYSERR.NoError Then
                Dim err As MMSYSERR = DirectCast(result, MMSYSERR)
                Throw New Win32Exception("GetDevCaps() error, Result: " & result.ToString("x8") & ", " & err.ToString)
            End If
            DevicesComboBox.Items.Add(New WaveOutDevice(i, caps))
        Next
        DevicesComboBox.SelectedIndex = 0
    End Sub

    Private Sub DisplayDefaultWaveOutDevice()
        Dim currentDefault As Integer = GetIdOfDefaultWaveOutDevice()
        Dim device As WaveOutDevice = DirectCast(DevicesComboBox.Items(currentDefault), WaveOutDevice)
        DefaultDeviceLabel.Text = "Defualt: " & device.WaveOutCaps.szPname
    End Sub

    Private Function GetIdOfDefaultWaveOutDevice() As Integer        
        Dim id As Integer = 0
        Dim hId As IntPtr
        Dim flags As Integer = 0
        Dim hFlags As IntPtr
        Dim result As Integer
        Try
            ' It would be easier to declare a nice overload with ByRef Integers.
            hId = Marshal.AllocHGlobal(4)
            hFlags = Marshal.AllocHGlobal(4)
            ' http://msdn.microsoft.com/en-us/library/bb981557.aspx
            result = waveOut.Message(WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, hId, hFlags)
            If result <> MMSYSERR.NoError Then
                Dim err As MMSYSERR = DirectCast(result, MMSYSERR)
                Throw New Win32Exception("waveOutMessage() error, Result: " & result.ToString("x8") & ", " & err.ToString)
            End If
            id = Marshal.ReadInt32(hId)
            flags = Marshal.ReadInt32(hFlags)
        Finally
            Marshal.FreeHGlobal(hId)
            Marshal.FreeHGlobal(hFlags)
        End Try
        ' There is only one flag, DRVM_MAPPER_PREFERRED_FLAGS_PREFERREDONLY, defined as 1
        ' "When the DRVM_MAPPER_PREFERRED_FLAGS_PREFERREDONLY flag bit is set, ... blah ...,  
        ' the waveIn and waveOut APIs use only the current preferred device and do not search 
        ' for other available devices if the preferred device is unavailable. 
        Return id
    End Function

    Private Sub SetDefaultButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles SetDefaultButton.Click
        If DevicesComboBox.Items.Count = 0 Then Return
        Dim selectedDevice As WaveOutDevice = DirectCast(DevicesComboBox.SelectedItem, WaveOutDevice)
        SetDefault(selectedDevice.Id)
        RefreshInformation()
    End Sub

    Private Sub SetDefault(ByVal id As Integer)
        Dim defaultId As Integer = GetIdOfDefaultWaveOutDevice()
        If defaultId = id Then Return ' no change.
        Dim result As Integer
        ' So here we say "change the Id of the device that has id id to 0", which makes it the default.
        result = waveOut.Message(WAVE_MAPPER, DRVM_MAPPER_PREFERRED_SET, New IntPtr(id), IntPtr.Zero)
        If result <> MMSYSERR.NoError Then
            Dim err As MMSYSERR = DirectCast(result, MMSYSERR)
            Throw New Win32Exception("waveOutMessage() error, Result: " & result.ToString("x8") & ", " & err.ToString)
        End If
    End Sub

End Class

System.Management

您可以使用 System.Management 程序集检索可用的音频设备,它是 .Net 框架的一部分:

ManagementObjectSearcher mo = 
  new ManagementObjectSearcher("select * from Win32_SoundDevice");

foreach (ManagementObject soundDevice in mo.Get())
{
     String deviceId = soundDevice.GetPropertyValue("DeviceId").ToString();
     String name  = soundDevice.GetPropertyValue("Name").ToString();

  //saving the name  and device id in array
} 

参考文献

  • Get list of audio devices and select one using c#(本link(使用Lync 2013 SDK)提供的另一种解决方案
  • Win32_SoundDevice class