I'm trying to figure out if sound of any kind is playing in Windows (by any application). If something is making a noise somewhere, I want to know about it!
After following the docs, I've found how to get a list of mixers on the machine, as well as lines for those mixers -- which, if I understand correctly, are what is used for input/output of the mixer.
However, the problem I'm having is that I don't know how to get the data I need from the line.
The only interface I see that has a notion of volume level is DataLine
. The problem with that is that I can't figure out what returns an object that implements the dataline interface.
Enumerating all of the mixers and lines:
public static void printMixers() {
Mixer.Info[] mixers = AudioSystem.getMixerInfo();
for (Mixer.Info mixerInfo : mixers) {
Mixer mixer = AudioSystem.getMixer(mixerInfo);
try {
mixer.open();
Line.Info[] lines = mixer.getSourceLineInfo();
for (Line.Info linfo : lines) {
System.out.println(linfo);
}
}
catch (LineUnavailableException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
That code enumerates and displays all of the audio devices on my machine. From that, shouldn't one of those Lines
contain some kind of playback level data?
The parametric equalizers on some mixers allow the frequencies of each band to be adjusted. A three-band equalizer system, which divides the frequency bands into LOW (low range), MID (middle range), and HIGH (high range).
On DL mixers, as well as many other digital consoles, the gain control is used to bring the input signal from the source up to a nominal level in the same way as any analog device. Specifically, the gain determines how much the analog audio signal is amplified by the preamp.
An audio mixer is a device with the primary function to accept, combine, process and monitor audio. Mixers are primarily used in four types of environments: live (at a concert), in a recording studio, for broadcast audio, and for film/television. An audio mixer can come in either analog or digital form.
Oh you wish to find the volume? Well, not all hardware supports it, but here is how you get the dataline.
public static SourceDataLine getSourceDataLine(Line.Info lineInfo){
try{
return (SourceDataLine) AudioSystem.getLine(lineInfo);
}
catch(Exception ex){
ex.printStackTrace();
return null;
}
}
Then just call SourceDataLine.getLevel() to get the volume. I hope this helps.
NB: If the sound is originating from outside the JVM or not via the JavaSound API, this method will not detect the sound as the JVM does not have access to the OS equivalent of the SourceDataLine.
UPDATE: Upon further research, getLevel() is not implemented on most Systems. So I have manually implemented the method based off this forum discussion: https://community.oracle.com/message/5391003
Here are the classes:
public class Main {
public static void main(String[] args){
MicrophoneAnalyzer mic = new MicrophoneAnalyzer(FLACFileWriter.FLAC);
System.out.println("HELLO");
mic.open();
while(true){
byte[] buffer = new byte[mic.getTargetDataLine().getFormat().getFrameSize()];
mic.getTargetDataLine().read(buffer, 0, buffer.length);
try{
System.out.println(getLevel(mic.getAudioFormat(), buffer));
}
catch(Exception e){
System.out.println("ERROR");
e.printStackTrace();
}
}
}
public static double getLevel(AudioFormat af, byte[] chunk) throws IOException{
PCMSigned8Bit converter = new PCMSigned8Bit(af);
if(chunk.length != converter.getRequiredChunkByteSize())
return -1;
AudioInputStream ais = converter.convert(chunk);
ais.read(chunk, 0, chunk.length);
long lSum = 0;
for(int i=0; i<chunk.length; i++)
lSum = lSum + chunk[i];
double dAvg = lSum / chunk.length;
double sumMeanSquare = 0d;
for(int j=0; j<chunk.length; j++)
sumMeanSquare = sumMeanSquare + Math.pow(chunk[j] - dAvg, 2d);
double averageMeanSquare = sumMeanSquare / chunk.length;
return (Math.pow(averageMeanSquare,0.5d));
}
}
The method I used only works on 8bitPCM so we have to convert the encoding to that using these two classes. Here is the general abstract converter class.
import java.io.ByteArrayInputStream;
import java.io.IOException;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
abstract class AbstractSignedLevelConverter
{
private AudioFormat srcf;
public AbstractSignedLevelConverter(AudioFormat sourceFormat)
{
srcf = sourceFormat;
}
protected AudioInputStream convert(byte[] chunk)
{
AudioInputStream ais = null;
if(AudioSystem.isConversionSupported( AudioFormat.Encoding.PCM_SIGNED,
srcf))
{
if(srcf.getEncoding() != AudioFormat.Encoding.PCM_SIGNED)
ais = AudioSystem.getAudioInputStream(
AudioFormat.Encoding.PCM_SIGNED,
new AudioInputStream(new ByteArrayInputStream(chunk),
srcf,
chunk.length * srcf.getFrameSize()));
else
ais = new AudioInputStream(new ByteArrayInputStream(chunk),
srcf,
chunk.length * srcf.getFrameSize());
}
return ais;
}
abstract public double convertToLevel(byte[] chunk) throws IOException;
public int getRequiredChunkByteSize()
{
return srcf.getFrameSize();
}
}
And here is the one for 8BitPCM
import java.io.IOException;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
public class PCMSigned8Bit extends AbstractSignedLevelConverter
{
PCMSigned8Bit(AudioFormat sourceFormat)
{
super(sourceFormat);
}
public double convertToLevel(byte[] chunk) throws IOException
{
if(chunk.length != getRequiredChunkByteSize())
return -1;
AudioInputStream ais = convert(chunk);
ais.read(chunk, 0, chunk.length);
return (double)chunk[0];
}
}
This is for TargetDataLine which may not work in your use case, but you could build a wrapper around SourceDataLine and use this to properly implement these methods. Hopes this helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With