Search code examples
debianalsajavasoundpulseaudiominim

Inconsistent Access to JavaSound SourceDataLine Through Minim


I'm a not-so-knowledgeable person in the Linux world trying to understand an audio problem I have in a project. In short, I'm trying to get an audio output through a Java library called Minim (ddf.minim). I can get an output by listening to an input and enabling "monitoring." Monitoring echoes the input to an output. I cannot however get a working output line independent of this feature.

Some context:

This application is running on a BeagleBone Black with Debian Buster. This is an ARM microcontroller. My Java application starts up with the system as a systemd service as the root user. It runs in headless mode but with a virtual frame buffer (xvfb) because it's a Processing application. I have a USB hub connected to the BeagleBone, which hosts a USB-to-AUX adapter. The AUX side splits into the pink/green input/output cables. I have a mic connected to the pink end and earbuds into the green end.

The problem:

When Minim's getLineIn() is used to get an input, no errors are seen. Enabling monitoring doesn't generate errors either. Using getLineOut() will show errors that look like this:

==== JavaSound Minim Error ====
==== Couldn't open the line: line with format PCM_SIGNED 44100.0 Hz, 16 bit, stereo, 4 bytes/frame, little-endian not supported.
==== JavaSound Minim Error ====
==== Unable to return a SourceDataLine: unsupported format - PCM_SIGNED 44100.0 Hz, 16 bit, stereo, 4 bytes/frame, little-endian

Here's my code that asks for an output:

Mixer.Info mixers[] = AudioSystem.getMixerInfo();
for(Mixer.Info info : mixers)
{
    try
    {
        mixer = AudioSystem.getMixer(info);
        if(!mixer.isOpen())
        {
            mixer.open();
        }
        M.setOutputMixer(mixer);
        LineOut = M.getLineOut();
    }
    catch(Exception e)
    {
        mixer = null; //This mixer can't be used.
        continue;
    }
    if(mixer != null && LineOut != null)
    {
        break;
    }
}

Errors don't always throw an exception, so that's why I have a null check. Interestingly, even when error messages show up, LineOut will not be null. I can pass it to other methods but I can never hear anything. Only two mixers ever show up, one for the USB adapter and one that throws an exception once opened.

Solutions I've tried:

Playing example files with aplay used to fail, but once I apt install-ed PulseAudio, they worked.

I don't have a soundcard 0, but the USB adapter shows as soundcard 1. I've changed the asound.conf to have these extra lines at the bottom:

defaults.pcm.card 1
defaults.ctl.card 1

I've also added these lines in the sound.properties config file under /usr/lib/jdk1.8.0_251/jre/lib after reading about Java's weak support for PulseAudio:

javax.sound.sampled.Clip=com.sun.media.sound.DirectAudioDeviceProvider
javax.sound.sampled.Port=com.sun.media.sound.PortMixerProvider
javax.sound.sampled.SourceDataLine=com.sun.media.sound.DirectAudioDeviceProvider
javax.sound.sampled.TargetDataLine=com.sun.media.sound.DirectAudioDeviceProvider

I've used the AudioSystem class to query for the supported formats of the mixer. They're the same formats I'm requesting, so I'm not confident the error message accurately describes the issue. I've tried mono, stereo, different buffer sizes... and probably other things.

I've changed my application to only seek an output, and not look for an input. The issue persists.

This code works fine on Windows, which is where I test things before porting it to the Debian machine.

I've read a lot about the general fragility of sound on Linux, and so my suspicion is that I'm doing something wrong there or Java is just not fit for outputting sound with my unique setup. Help?


Solution

  • I figured it out!

    Calling mixer.open() was causing problems. I guess Minim does this itself and expects users to leave it unopened when requesting a line.

    However, I did also have other discoveries.

    • Java and/or Minim are very sensitive to re-requesting audio resources, at least on Debian. Since requesting a line-in implicitly requested both an input and output for the monitoring feature, a subsequent call to getLineOut() would fail. You can, however, call Minim.stop() to start over and get resources again.
    • The AudioPlayer just seems broken. I can't get it to request a working output, but I found that FilePlayer works fine for some reason. You can even ask for an output with getLineOut() before making a FilePlayer. Maybe you have to call setOutputMixer() (without also calling getLineOut()) before getting an AudioPlayer in order for it to work.