6

I have a simple capture/playback Swing app that has to detect if there is no appropriate microphone attached to the computer and warn the user. After a lot of fiddling around I have found the only solution that allowed me to detect the newly attached or removed microphone:

     com.sun.media.sound.JDK13Services.setCachingPeriod(0);

     private static boolean isMicrophoneAvailable() {
        try {
            if (!AudioSystem.isLineSupported(Port.Info.MICROPHONE)) {
                log.debug("NO MICROPHONE FOUND");
                return false;
            } else {
                log.debug("MICROPHONE FOUND");
                return true;
            }
        } catch (IllegalArgumentException e) {
            log.debug("INCONSISTENT");
        }
        return false;
    }

called in the background thread like this:

   new Thread() {
       public void run() {
            while(!thisFrame.isClosed()){
                if(isMicrophoneAvailable() == true){
                     //OK
                }else{
                     //WARN
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
      }
    }).start();

The problem is that although the device is being detected correctly using the described method, the list of underlying Lines is not refreshed. That is, when the program is started, and the device is attached later, the following exception is thrown when trying to record sound:

 java.lang.IllegalArgumentException: No line matching interface TargetDataLine supporting format PCM_SIGNED 44100.0 Hz, 16 bit, stereo, 4 bytes/frame, big-endian is supported.

Is there any way to get the lines list of AudioSystem refreshed? Maybe something similar to the JDK13Services workaround used at the very beginning to avoid caching?

UPDATE: Code that throws an exception:

        AudioFormat format = formatControls.getDefaultFormat();
        DataLine.Info info = new DataLine.Info(TargetDataLine.class,format);
        try {
            line = (TargetDataLine) AudioSystem.getLine(info);
            line.open(format, line.getBufferSize());
        } catch (LineUnavailableException ex) {
            shutDown("No audio input device available. Please make sure that a microphone is attached to your computer");
            return;
        } catch (Exception ex) {
            log.error(ex.toString());
            shutDown(ex.toString());
            return;
        }

and the Exception itself:

 java.lang.IllegalArgumentException: No line matching interface TargetDataLine supporting format PCM_SIGNED 44100.0 Hz, 16 bit, stereo, 4 bytes/frame, big-endian is supported.
Alex Fedulov
  • 1,442
  • 17
  • 26
  • Interesting question, +1. Just to check though, you do realize the fragility of using a class in the `com.sun` packages, right? Even in the JREs where it exists, it might be removed/moved/renamed in the next version. – Andrew Thompson Mar 26 '12 at 15:21
  • Indeed, I do realize that this is at least a bad practice, but it was really my last resort. I suppose it can be consider to be a Java Sound implementation flaw. – Alex Fedulov Mar 26 '12 at 15:25
  • One of many. JavaSound is good for the limited range of things it aims to support, but Sun never really developed it further. – Andrew Thompson Mar 26 '12 at 15:29

2 Answers2

1

Using the original post as inspiration, I came up with this as a means to detect when a mic is lost or gained. It detects (on my system) when a USB mic is plugged in or unplugged. I'm calling it from a background thread loop. The original method did not work for me as there is a built-in mic on the laptop, so I needed to detect the addition of second mic.

...
//call this once somewhere to turn the caching period down for faster detection
    try
    {
        //try to set the caching period, defaults to something like 55 seconds
        com.sun.media.sound.JDK13Services.setCachingPeriod(5);
    }
    catch( Exception e)
    {
        System.err.println("exception attempting to call com.sun.media.sound.JDK13Services.setCachingPeriod->" + e);
    }
...

private int lastNumMics = -1;
private synchronized void micCheck()
{
    Line.Info[] lineInfoArray = AudioSystem.getSourceLineInfo(Port.Info.MICROPHONE);
    int numMics = lineInfoArray == null ? 0 : lineInfoArray.length;
    if( lastNumMics > -1 )
    {
        if( numMics < lastNumMics )
        {
            //MICROPHONE_LOST
        }
        else if( numMics > lastNumMics )
        {
            //MICROPHONE_GAINED
        }
    }
    lastNumMics = numMics;
}
crig
  • 859
  • 6
  • 19
0

Posting the code that throws the exception might help.

I'm assuming you are only using the Port.Info to detect the presence of a microphone then getting a Dataline to record:

TargetDataLine dataLine = (TargetDataLine) AudioSystem.getLine(new DataLine.Info(TargetDataLine.class, audioFormat));                       
dataLine.open();
dataLine.start();               
dataLine.read(b, offset, len);

Note that you might still get a similar exception when disconnecting the microphone if the microphone is physically disconnected between the time you check for presence and the time you try to get the Dataline to record but connecting the microhpone should not be a problem.

msam
  • 4,259
  • 3
  • 19
  • 32
  • It seems that I am doing pretty much the same thing. Anyhow, I have updated the question with the code and the exception. – Alex Fedulov Mar 29 '12 at 14:28
  • Make sure the line supports that audio format. To list all audio formats supported by the line: AudioFormat adfs[] = ((DataLine.Info)dataLine.getLineInfo()).getFormats(); for (AudioFormat adf : adfs){ System.out.println(adf.toString()); } – msam Mar 30 '12 at 07:07
  • I do not quite get your idea. line = (TargetDataLine) AudioSystem.getLine(info); is where the exception is thrown, so I cannot get the line in the first place. Maybe I was not clear enough: the microphone works fine, if it is attached to the computer before the start of the program. The disconnection is correctly detected, the "Record" button is disabled. When plugged back it works again. The problem arises if the microphone was not attached before the jvm start. In this case, even if the Port.Info.MICROPHONE is detected, its lines are still non-functional. – Alex Fedulov Mar 30 '12 at 12:17
  • Misunderstood your previous post...I can give you a reason why it's failing: reducing the caching period in the JDK13Services works with the discovery of PortMixer but not with the DirectAudioDevice Mixer (the TargetDataLine). Can't give you a solution though – msam Mar 30 '12 at 14:52