Search code examples
vb.netwmplib

List and Select Sound Card to Play Sound


I have a school bell project coded with Visual Basic 2010 Express. The computer which runs my program has two or more sound cards. First I will list the sound cards to user. User will select the sound card to work. Finally my program will ring the bells on that sound card. Everything is okey for my codes but i can't list the names of sound cards and ring the bell on specified sound card.

I use WMPLib to play music. I have these codes but there becomes an error "the value is not in the expected range". I spotted where the error is in my codes:

    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

Solution

  • WindowsMedia.Net

    You can do this using WindowsMedia.Net library.

    The following example is taken from the link below, it is a code blonging to a Windows form and contains the functionality needed to list all available audio devices and choose the default device (the one that will act as sound output).

    First i will try to split the code into 2 parts:

    1. List available audio devices
    2. Change default audio device

    List available devices

    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
    

    Change default device

    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
    

    Full code

    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

    You can retrieve the available audio devices using System.Management assembly which is a part of .Net framework:

    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
    } 
    

    References