Skip to content

Microphone Selection

Sirius GG edited this page Apr 26, 2023 · 7 revisions

Is it necessary?

JARVIS uses the Java Sound API to record audio. By default, the Microphone Class (and, consequently, all subclasses) will use the Default System Microphone as determined by the JRE implementation. As such, in 99% of scenarios, selecting the microphone is unnecessary. If you ever do want to select a microphone (due to say a specific implementation on Linux or adding a front end feature for the end user), just use these easy steps to do so.

Basics

The a TargetDataLine is a stream of audioData from an audio input device. The AudioSystem provides the default TargetDataLine based off which microphone can support the specified audio format and the default system microphone. To generate a TargetDataLine from a specific audio source, one has to use a Line.Info to specify where the TargetDataLine should originate from. Here is some code to generate a TargetDataLine from that info.

public static TargetDataLine getTargetDataLine(Line.Info lineInfo){
	try {
		return (TargetDataLine) AudioSystem.getLine(lineInfo);
	} catch (LineUnavailableException ex) {//If the line is unavailable, it returns
		ex.printStackTrace();
		return null;
	}
}

To set the TargetDataLine for the Microphone class or any subclasses use. Microphone.setTargetDataLine(TargetDataLine); The following section will cover how to determine what the LineInfo of the specified data is.

Enumerating the Microphones

First, if you are selecting a specific microphone, you will need to know the properties of said microphone. Here is the basic code to list all the Microphones properties. Doing so well allow you to manually grab the data from the Microphone in a format the hardware supports. You cannot get a higher sampling rate than the Microphone allows for instance. Here is some code fragments that will print out the properties of all the lines available on your system.

	//Enumerates all available microphones
	Mixer.Info[] mixerInfos = AudioSystem.getMixerInfo();
	for (Mixer.Info info: mixerInfos){
		Mixer m = AudioSystem.getMixer(info);
		Line.Info[] lineInfos = m.getTargetLineInfo();
		if(lineInfos.length>=1 && lineInfos[0].getLineClass().equals(TargetDataLine.class)){//Only prints out info is it is a Microphone
			System.out.println("Line Name: " + info.getName());//The name of the AudioDevice
			System.out.println("Line Description: " + info.getDescription());//The type of audio device
			for (Line.Info lineInfo:lineInfos){
				System.out.println ("\t"+"---"+lineInfo);
				Line line;
				try {
					line = m.getLine(lineInfo);
				} catch (LineUnavailableException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					return;
				}
				System.out.println("\t-----"+line);
			}
		}
	}

The way most operating systems handle the Audio Device is by using it's name (specified by Mixer.getName()). So if you are looking for a specific microphone, that is the output you should pay attention to. In the event you want user interaction of this data, you will need to transform it in some way.


Front End Transformation

There are several manners to present this data to the user. I would recommend using a HashMap and then adding the KeySet() to a non-editable ComboBox. Then add a itemListener to change the TargetDataLine using Microphone.setTargetDataLine(TargetDataLine). Here is a method that generates the HashMap.

/**
 * Generates a hashmap to simplify the microphone selection process.
 * The keyset is the name of the audio device's Mixer
 * The value is the first lineInfo from that Mixer.
 * @author Aaron Gokaslan (Skylion)
 * @return The generated hashmap
 */
public static HashMap<String, Line.Info> enumerateMicrophones(){
	HashMap<String, Line.Info> out = new HashMap<String, Line.Info>();
	Mixer.Info[] mixerInfos = AudioSystem.getMixerInfo();
	for (Mixer.Info info: mixerInfos){
		Mixer m = AudioSystem.getMixer(info);
		Line.Info[] lineInfos = m.getTargetLineInfo();
		if(lineInfos.length>=1 && lineInfos[0].getLineClass().equals(TargetDataLine.class)//Only adds to hashmap if it is audio input device
			out.put(info.getName(), lineInfos[0]);//Please enjoy my pun
	}
	return out;
}

Such a hashmap simplifies the microphone selection process for the end user. Feel free to add any implementations of microphone selection processes to this wiki.

Alternative Front End Transformation Example

The following is an example of an extension of the JPanel which allows the user to choose a specific mixer. The TargetDataLine can then be obtained from the mixer. Instead of using a JCombobox, this JPanel uses radio buttons.

 /**
  * This is an input panel specifically designed to create a selection method for each microphone.
  * In this case, a microphone is defined as mixer and not a specific target data line for sake of
  * end user simplicity. This class extends JPanel and fires property change listeners when the
  * user selects a new mixer.
  * @author Aaron Gokaslan
  * This class was inspired by TarsosDSP.
  */

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Line;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.TargetDataLine;
import javax.swing.ButtonGroup;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.border.TitledBorder;

public class MicrophonePanel extends JPanel {

	/**
	 * Auto-generated Serial Long
	 */
	private static final long serialVersionUID = 1L;
	
	Mixer mixer = null;
	
	public MicrophonePanel(){
		super(new BorderLayout());
		this.setBorder(new TitledBorder("1. Choose a microphone input"));
		JPanel buttonPanel = new JPanel(new GridLayout(0,1));
		ButtonGroup group = new ButtonGroup();
		Mixer.Info[] mixerInfos = AudioSystem.getMixerInfo();
		for (Mixer.Info info: mixerInfos){
			Mixer m = AudioSystem.getMixer(info);
			Line.Info[] lineInfos = m.getTargetLineInfo();
			if(lineInfos.length > 0 && lineInfos[0].getLineClass().equals(TargetDataLine.class)){
				JRadioButton button = new JRadioButton();
				button.setText(info.getName());
				button.setActionCommand(info.toString());
				button.addActionListener(setInput);
				buttonPanel.add(button);
				group.add(button);
			}
		}
		this.add(new JScrollPane(buttonPanel,JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
				JScrollPane.HORIZONTAL_SCROLLBAR_NEVER),BorderLayout.CENTER);
		this.setMaximumSize(new Dimension(300,150));
		this.setPreferredSize(new Dimension(300,150));
	}
	
	private ActionListener setInput = new ActionListener(){
		@Override
		public void actionPerformed(ActionEvent arg0) {
			for(Mixer.Info info : AudioSystem.getMixerInfo()){
				if(arg0.getActionCommand().equals(info.toString())){
					Mixer newValue = AudioSystem.getMixer(info);
					MicrophonePanel.this.firePropertyChange("mixer", mixer, newValue);
					MicrophonePanel.this.mixer = newValue;
					break;
				}
			}
		}
	};
	
	//Example Method
	public static void main(String[] args){
		JFrame frame = new JFrame("Microphone Selection Test");
		MicrophonePanel panel = new MicrophonePanel();
		frame.getContentPane().add(panel);
		frame.setVisible(true);;
		frame.setLocationRelativeTo(null);
		frame.pack();
	}

}