using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.IO; using System.Windows.Forms; using NAudio.CoreAudioApi; using NAudio.Wave; namespace RobvanderWoude { internal class AudioEndPoints { static readonly string progver = "2.00"; #region Global Variables static bool bgcolors = true; static bool fgcolors = false; static bool helprequested = false; static bool stereo = true; static DataGridView datagrid; static readonly Dictionary foregroundcolors = new Dictionary { [DeviceState.Active] = Color.Green, [DeviceState.Disabled] = Color.Orange, [DeviceState.NotPresent] = Color.SlateGray, [DeviceState.Unplugged] = Color.Red }; static readonly Dictionary backgroundcolors = new Dictionary { [DeviceState.Active] = Color.LightGreen, [DeviceState.Disabled] = Color.Yellow, [DeviceState.NotPresent] = Color.LightGray, [DeviceState.Unplugged] = Color.Red }; static Dictionary audioendpoints; static Form form; static int interval = 0; static Timer timer = new Timer( ); static WaveIn recorder; #endregion Global Variables [STAThread] static int Main( string[] args ) { #region Parse Command Line foreach ( string arg in args ) { string[] kvp = arg.Split( ":=".ToCharArray( ), 2 ); string key = kvp[0].ToUpper( ); string val = ( kvp.Length == 2 ? kvp[1] : string.Empty ); switch ( key ) { case "/?": return ShowHelp( true ); case "/B": bgcolors = false; fgcolors = false; break; case "/F": bgcolors = false; fgcolors = true; break; case "/M": stereo = false; break; case "/R": interval = 1; if ( string.IsNullOrWhiteSpace( val ) ) { if ( !int.TryParse( val, out interval ) ) { interval = 1; } } if ( interval < 0 ) { interval = 1; } timer.Interval = interval * 1000; timer.Tick += Timer_Tick; timer.Start( ); break; default: return ShowHelp( true ); } } #endregion Parse Command Line Application.EnableVisualStyles( ); form = new Form { BackColor = Color.White, Font = new Font( FontFamily.GenericSansSerif, 12 ), Size = new Size( 1380, 840 ), StartPosition = FormStartPosition.CenterScreen, Text = "AudioEndPoints.exe, \u00A0 Version " + progver + " \u00A0?\u00A0 \u00A0 Copyright \u00A9 2025 Rob van der Woude", Visible = true }; form.FormClosing += Form_FormClosing; audioendpoints = new Dictionary( ); recorder = new WaveIn( ); recorder.StartRecording( ); SetupDataGrid( ); PopulateDataGrid( ); while ( form.Enabled ) { Application.DoEvents( ); } return 0; } private static void Datagrid_KeyUp( object sender, KeyEventArgs e ) { if ( e.KeyCode == Keys.F1 ) { ShowHelp( false ); } if ( e.KeyCode == Keys.F5 ) { RefreshDataGrid( ); } if ( e.KeyCode == Keys.Escape ) { form.Close( ); } if ( e.Control && e.KeyCode == Keys.P ) { PrintDataGrid( ); } if ( e.Control && e.KeyCode == Keys.S ) { string filename = string.Format( "AudioEndPoints.{0}.{1}.txt", Environment.GetEnvironmentVariable( "COMPUTERNAME" ), DateTime.Now.ToString( "yyyy-MM-dd_HHmmss" ) ); string outfile = Path.Combine( Environment.CurrentDirectory, filename ); SaveDataGrid( outfile ); MessageBox.Show( "Saved as \"" + filename + "\" in folder \"" + Path.GetDirectoryName( outfile ) + "\"", "File Saved", MessageBoxButtons.OK, MessageBoxIcon.Information ); } } private static void Datagrid_SortCompare( object sender, DataGridViewSortCompareEventArgs e ) { if ( e.Column.Index == 0 ) { e.SortResult = int.Parse( e.CellValue1.ToString( ) ).CompareTo( int.Parse( e.CellValue2.ToString( ) ) ); e.Handled = true; // skip the default sorting } } private static void Form_FormClosing( object sender, FormClosingEventArgs e ) { try { timer.Stop( ); } catch { } try { recorder.StopRecording( ); } catch { } if ( helprequested ) { ShowHelp( true ); } form.Enabled = false; } private static void Timer_Tick( object sender, EventArgs e ) { RefreshDataGrid( ); } private static void InventoryAudioEndPoints( ) { MMDeviceEnumerator enumerator = new MMDeviceEnumerator( ); int index = 0; foreach ( MMDevice endpoint in enumerator.EnumerateAudioEndPoints( DataFlow.All, DeviceState.All ) ) { AudioEndPoint audioendpoint = new AudioEndPoint { Index = index, InOut = endpoint.DataFlow, Name = endpoint.FriendlyName, State = endpoint.State, ID = endpoint.ID, }; if ( audioendpoint.State == DeviceState.Active ) { audioendpoint.MasterVolumeLevelScalar = endpoint.AudioEndpointVolume.MasterVolumeLevelScalar; audioendpoint.MasterPeakValue = endpoint.AudioMeterInformation.MasterPeakValue; audioendpoint.PeakValues = endpoint.AudioMeterInformation.PeakValues; } else { audioendpoint.MasterVolumeLevelScalar = 0f; audioendpoint.MasterPeakValue = 0f; audioendpoint.PeakValues = null; } audioendpoints[index] = audioendpoint; index++; } } private static void PopulateDataGrid( ) { InventoryAudioEndPoints( ); foreach ( AudioEndPoint endpoint in audioendpoints.Values ) { PopulateDataGridRow( endpoint ); } for ( int i = 0; i < datagrid.ColumnCount; i++ ) { if ( i == datagrid.ColumnCount - 1 ) { datagrid.Columns[i].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill; } else { datagrid.Columns[i].AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells; } } } private static void PopulateDataGridRow( AudioEndPoint endpoint ) { string[] line; if ( stereo ) { line = new string[] { endpoint.Index.ToString( ), ( endpoint.InOut == DataFlow.Capture ? "IN" : "OUT" ), endpoint.Name, endpoint.State.ToString( ), endpoint.ID, endpoint.GetVolume( ), endpoint.GetPeakLevel( 0 ), endpoint.GetPeakLevel( 1 ) }; } else { line = new string[] { endpoint.Index.ToString( ), ( endpoint.InOut == DataFlow.Capture ? "IN" : "OUT" ), endpoint.Name, endpoint.State.ToString( ), endpoint.ID, endpoint.GetVolume( ), endpoint.GetPeakLevel( -1 ) }; } int row; if ( endpoint.Index < datagrid.Rows.Count - 1 ) { row = endpoint.Index; datagrid.Rows[row].SetValues( line ); } else { row = datagrid.Rows.Count - 1; datagrid.Rows.Add( line ); } if ( bgcolors ) { datagrid.Rows[row].DefaultCellStyle.BackColor = backgroundcolors[endpoint.State]; } if ( fgcolors ) { datagrid.Rows[row].DefaultCellStyle.ForeColor = foregroundcolors[endpoint.State]; } } private static void PrintDataGrid( ) { string printout = Path.GetTempFileName( ); SaveDataGrid( printout ); ProcessStartInfo psi = new ProcessStartInfo { UseShellExecute = false, Arguments = string.Format( "/p \"{0}\"", printout ), FileName = "notepad.exe" }; Process proc = Process.Start( psi ); proc.WaitForExit( ); File.Delete( printout ); } private static void RefreshDataGrid( ) { PopulateDataGrid( ); } private static void SaveDataGrid( string outfile ) { // line out colums int[] cw; if ( stereo ) { cw = new int[] { 5, 6, 0, 0, 0, 6, 6, 6 }; } else { cw = new int[] { 5, 6, 0, 0, 0, 6, 4 }; } for ( int i = 0; i < datagrid.Columns.Count; i++ ) { for ( int j = 0; j < datagrid.Rows.Count; j++ ) { if ( datagrid.Rows[j].Cells[i].Value != null && datagrid.Rows[j].Cells[i].Value.ToString( ).Length > cw[i] ) { cw[i] = datagrid.Rows[j].Cells[i].Value.ToString( ).Length; } } } // format printout List lines = new List( ); string linetemplate; if ( stereo ) { linetemplate = "{0,-" + cw[0] + "} {1,-" + cw[1] + "} {2,-" + cw[2] + "} {3,-" + cw[3] + "} {4,-" + cw[4] + "} {5," + cw[5] + "} {6," + cw[6] + "} {7," + cw[7] + "}"; } else { linetemplate = "{0,-" + cw[0] + "} {1,-" + cw[1] + "} {2,-" + cw[2] + "} {3,-" + cw[3] + "} {4,-" + cw[4] + "} {5," + cw[5] + "} {6," + cw[6] + "}"; } // header var cols = datagrid.Columns; string headerline; if ( stereo ) { headerline = string.Format( linetemplate, cols[0].HeaderText, cols[1].HeaderText.Replace( "\u00A0", "" ), cols[2].HeaderText, cols[3].HeaderText, cols[4].HeaderText, cols[5].HeaderText, cols[6].HeaderText, cols[7].HeaderText ); } else { headerline = string.Format( linetemplate, cols[0].HeaderText, cols[1].HeaderText.Replace( "\u00A0", "" ), cols[2].HeaderText, cols[3].HeaderText, cols[4].HeaderText, cols[5].HeaderText, cols[6].HeaderText ); } lines.Add( headerline ); // rows for ( int i = 0; i < datagrid.Rows.Count; i++ ) { var cells = datagrid.Rows[i].Cells; string line; if ( stereo ) { line = string.Format( linetemplate, cells[0].Value, cells[1].Value, cells[2].Value, cells[3].Value, cells[4].Value, cells[5].Value, cells[6].Value, cells[7].Value ); } else { line = string.Format( linetemplate, cells[0].Value, cells[1].Value, cells[2].Value, cells[3].Value, cells[4].Value, cells[5].Value, cells[6].Value ); } lines.Add( line ); } using ( StreamWriter print = new StreamWriter( outfile ) ) { for ( int i = 0; i < lines.Count; i++ ) { print.WriteLine( lines[i] ); } } } private static void SetupDataGrid( ) { datagrid = new DataGridView { Location = new Point( 0, 0 ), Size = new Size( 1200, 250 ), AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCellsExceptHeaders, ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.Single, CellBorderStyle = DataGridViewCellBorderStyle.Single, GridColor = Color.Black, RowHeadersVisible = false, SelectionMode = DataGridViewSelectionMode.FullRowSelect, MultiSelect = false, Dock = DockStyle.Fill }; if ( stereo ) { datagrid.ColumnCount = 8; } else { datagrid.ColumnCount = 7; form.Width -= 85; } datagrid.ColumnHeadersDefaultCellStyle.BackColor = Color.Navy; datagrid.ColumnHeadersDefaultCellStyle.ForeColor = Color.White; datagrid.ColumnHeadersDefaultCellStyle.Font = new Font( FontFamily.GenericSansSerif, 12, FontStyle.Bold ); datagrid.Columns[0].Name = "Index"; datagrid.Columns[1].Name = "In\u00A0/\u00A0Out"; datagrid.Columns[2].Name = "Name"; datagrid.Columns[3].Name = "Status"; datagrid.Columns[4].Name = "ID"; datagrid.Columns[5].Name = "Volume"; datagrid.Columns[5].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight; if ( stereo ) { datagrid.Columns[6].Name = "Peak\u00A0L"; datagrid.Columns[6].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight; datagrid.Columns[7].Name = "Peak\u00A0R"; datagrid.Columns[7].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight; } else { datagrid.Columns[6].Name = "Peak"; datagrid.Columns[6].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight; } datagrid.KeyUp += Datagrid_KeyUp; datagrid.SortCompare += Datagrid_SortCompare; form.Controls.Add( datagrid ); datagrid.Focus( ); } private static int ShowHelp( bool cli = false ) { string message = string.Format( "\nAudioEndPoints.exe, Version {0}\n", progver ); message += "List the local computer's audio endpoints\n\n"; message += "Usage:\tAudioEndPoints.exe\t[ /? ] [ /F | /B ] [ /M ] [ /R[:nn] ]\n\n"; message += "Where:\t \t/? \tdisplay this help text and exit\n"; message += " \t \t/F \tuse status dependent\n"; message += " \t \t \tFOREGROUND colors,\n"; message += " \t \t/B \tuse BLACK and WHITE,\n"; message += " \t \t \tdefault is status dependent\n"; message += " \t \t \tBACKGROUND colors\n"; message += " \t \t/M \tpeak levels for L and R combined,\n"; message += " \t \t \tdefault is separate peak levels\n"; message += " \t \t/R[:nn]\trefresh values every nn seconds,\n"; message += " \t \t \tdefault interval is 1 second\n\n"; message += "Click on ANY column header to sort the table by that column.\n"; message += "Note that the table's sort order will be reset to its default at\n"; message += "every refresh, be it by pressing F5 or by specifying a refresh\n"; message += "interval with the /R command line switch.\n"; message += "You can edit all cells, if you like.\n\n"; message += "Keys: \tCtrl+C will copy the SELECTED LINE to the clipboard,\n"; message += " \tCtrl+P will print the list,\n"; message += " \tCtrl+S will save it in a text file,\n"; message += " \tF1 will show this help text,\n"; message += " \tF5 will refresh volume and peak level values,\n"; message += " \tEscape will abort the program.\n\n"; message += "Credits:\tTo access audio endpoints, this program uses\n"; message += " \tNAudio created by Mark Heath:\n"; message += " \thttps://github.com/naudio/NAudio\n"; message += " \tVolume reading based on code by Mike de Klerk:\n"; message += " \thttps://stackoverflow.com/a/12534584\n\n"; message += "Written by Rob van der Woude\n"; message += "https://www.robvanderwoude.com\n"; if ( cli ) { Console.Error.WriteLine( message ); Environment.Exit( -1 ); Application.Exit( ); } else { helprequested = true; MessageBox.Show( message, "AudioEndPoints.exe, \u00A0 Version " + progver ); } return -1; } } public class AudioEndPoint { public int Index { get; set; } public DataFlow InOut { get; set; } public string Name { get; set; } public DeviceState State { get; set; } public string ID { get; set; } public float MasterVolumeLevelScalar { get; set; } public float MasterPeakValue { get; set; } public AudioMeterInformationChannels PeakValues { get; set; } public string GetVolume( ) { if ( State == DeviceState.Active ) { return string.Format( "{0} %", (int)( MasterVolumeLevelScalar * 100 ) ); } else { return string.Empty; } } public string GetPeakLevel( int channel = -1 ) { if ( State == DeviceState.Active ) { switch ( channel ) { case 0: return string.Format( "{0} %", (int)( 100 * float.Parse( PeakValues[0].ToString( ) ) ) ); case 1: return string.Format( "{0} %", (int)( 100 * float.Parse( PeakValues[1].ToString( ) ) ) ); default: return string.Format( "{0} %", (int)( 100 * MasterPeakValue ) ); } } else { return string.Empty; } } } }