Selecting non-default Mixer

I created the TinySound library recently for playing sounds and music. I’m quite happy with the library so far and have been trying to address any issues that people have with it quickly.


Recently, I tried using it on my old laptop running Fedora 14 and I noticed substantial latency when playing sounds. TinySound only opens one line and does all the mixing in software before writing to the line, so I thought it was a problem with my mixer, but some debugging showed that the latency was coming after writing to the line. When I look at what mixers are available, there are two Intel mixers, a Pulse Audio mixer and the default mixer. When I explicitly select one of the Intel mixers or the Pulse Audio mixer to open the line, the latency is gone. So, the default mixer is introducing latency (sounds on the order of 100-200ms). I don’t know how common of a problem this is on Linux machines (this is the first Linux machine with this problem that I’ve seen/heard), but is there a proper way to pick the “best” mixer? Should I just hope that the default mixer is the “best” mixer and accept the fact that some machines will experience latency?

One possibility is to make a menu with mixer options.
Example: the “Options” menu here
http://www.hexara.com/VSL/JTheremin.htm

I don’t know of any way to determine the latency of the different output lines. But maybe there is a way. I’ll be watching here to find out if anyone knows of a way.

That’s a reasonable solution for an application, but TinySound is a library so it may not be appropriate in this case. It occurs to me that I could allow the user to pass a Mixer.Info into the init() function, giving them the ability to select the Mixer if they want. Still, I’m hopeful that someone has a solid solution.

By the way, your theremin is awesome.

Thanks, re Theremin. I learned a LOT working on it, but it needs more attention still. There are many issues…

I hope someone comes up with something for you. If not, it might be something you can consider “outside” of your library. Do other Java Sound libraries take responsibility for the output line and its latency? I honestly don’t know the answer to this. Do graphics libraries take responsibility for the hosts’ graphic cards? (Not sure if that is analogous or not.)

Having the user pass in the Mixer as a parameter makes a lot of sense. That is basically what happens when the Mixer menu option is selected.

This is a common issue. The first mixer returned when you get a list of mixers from AudioSystem is often the best performing. However, this isn’t always the mixer that AudioSystem will use when requesting a line. It’s almost always better to get the line from the mixer directly.

In terms of choosing a mixer, the JavasoundAudioServer in JAudioLibs just takes a String for the device name. This allows the developer to present the choice to the user however seems best. I chose this method, rather than allowing the developer to specify the Mixer directly, because the library can then deal with searching for a Mixer that provides the requisite output lines (not all do!). An empty device name returns the first suitable found mixer. The code is here.

Also, consider passing in a buffersize when you open the line - (ie. here). You can never be sure what you’ll get otherwise.

Hi

Take care of some other bugs under Linux. For example, when I use a webcam with an integrated microphone, JavaSound tries to use it as an headspeaker :clue: According to someone else, this bug doesn’t come from JavaSound.

The example provided by nsigma might be helpful.

I have very little to add, except that I have similar troubles with Java sound in Linux. Sound will only work if nothing else on my system is using sound currently. If Java grabs the sound, then nothing else can play sounds. I’ve tried manually selecting different mixers/lines via the APIs, but nothing was useful.

The best I can do is catch any exceptions and ignore. If this were 1993, I’d even consider showing the user a panel for trying each mixer/line to find one that works (and ask them what IRQ their wave table is on).

Thanks for any further Java+Linux+Sound advice!

No, it won’t be, at least on Oracle Java 6. The JavaSound ALSA backend there works only with exclusive access to the soundcard (unless you have one of the very rare soundcards that can hardware mix lines). This approach bypasses whatever software mixing is available on the system (eg. PulseAudio). It should be ‘fixed’ in both OpenJDK and Java 7 though, so the future looks better.

The one way you might be able to get non-exclusive sound to work is to use the old Java Sound Audio Engine, and start Java using padsp to route /dev/dsp through PulseAudio. It may or may not work, and the performance will probably be lousy!

EDIT: If you don’t want to show your own panel for device choice, you could also try and interpret the contents of the folder ‘.pulse’ in the user’s home directory, which should contain details of the system soundcard settings on most current Linux distros. Not sure how easy it is to map PulseAusio device ID’s to JavaSound Mixers though.

Yes, according to me! It’s a problem with ALSA putting your webcam at /dev/dsp and I’ve pointed you at bug reports about it. It should only affect the old Java Sound Audio Engine, which I believe is removed from Java 7, so should no longer be an issue.

There is also the possibility that code using the ALSA mixers could encounter this if they don’t check for the availability of output lines from the mixer (as I mentioned above).

You do realise you bring this up in every JavaSound thread?!?! :stuck_out_tongue:

Thanks so much for your insights! That’s exactly the trouble I’ve encountered. I also found that OpenJDK won’t even open my sound files (Oracle Java 6 does). It’s probably just something I did with the encoding, so I don’t blame Java (yet): RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 44100 Hz

Thanks for the input everyone! I would still be happy to get more responses, but here is what I’m thinking so far:

First, I’ll add a buffer size when opening a line as nsigma suggested and I’ll probably also add another init() function that takes a Mixer.Info so the user of the library can select a Mixer or pass that responsibility onto the user of the software via a panel or something.

When a Mixer.Info is not provided, however, I’ll have to decide where to get a SourceDataLine. Right now, I just leave that up to the AudioSystem. That seems to work for most everyone, so I don’t want to make the library less robust for rare cases. If, however, I can add a little complexity to the internals to account for some rare cases and still be robust I’d like to do that. Based on nsigma’s input about the index of the best mixer, and Nyhm and nsigma’s input on Java Sound needing exclusive access to the soundcard, I figure that I want to pick the first Mixer that can support my Line and which has fewer Lines open than it can handle. Looking at the API docs, I’ve come up with this:


public static Mixer findMixer(AudioFormat format) {
	DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class,
			format);
	Mixer.Info[] mixerInfos = AudioSystem.getMixerInfo();
	//check each available mixer to see if it is acceptable
	for (int i = 0; i < mixerInfos.length; i++) {
		Mixer mixer = AudioSystem.getMixer(mixerInfos[i]);
		//first check if it supports our line
		if (!mixer.isLineSupported(lineInfo)) {
			continue; //nope
		}
		//now check if we've used up our lines
		int maxLines = mixer.getMaxLines(lineInfo);
		//if it's not specified, it's supposedly unlimited
		if (maxLines == AudioSystem.NOT_SPECIFIED) {
			return mixer;
		}
		//otherwise we should count them
		int linesOpen = 0;
		Line[] sourceLines = mixer.getSourceLines();
		for (int s = 0; s < sourceLines.length; s++) {
			//check if it matches our line
			if (sourceLines[s].getLineInfo().matches(lineInfo)) {
				linesOpen++; //one line used up
			}
		}
		//now we can see if any are available
		if (maxLines > linesOpen) {
			return mixer;
		}
	}
	//couldn't find one
	return null;
}

Alternatively, I could just iterate through trying to open Lines until I get one, but I don’t like using Exceptions as normal behavior. I’ll play around with this, but if anyone knows of any undocumented reasons that this code is broken (there seems to be a lot of pitfalls in the Linux implementations) I would be glad to know.

Is this using Clips? Try using a stereo sound and see if the works OK? IMO, the fact that Clips are DataLines from a Mixer is probably the worst part of the JavaSound API, at least with the way that the backends work. I’m pretty sure some of the Mixers in both OpenJDK and Oracle’s Java can only open stereo lines, and that might vary from system to system too based on the underlying soundcard drivers.

If you’re not already, switch to a software mixer such as the OP’s.

Mageia Linux 1 still ships OpenJDK 1.6.

Yes and it is a good idea. If it can avoid some headaches to someone else, I will be very happy.

As you’ve always said this only affects Oracle Java 6, how is that relevant??? Or are you now getting this issue on OpenJDK too?

As I’ve said before, start a separate thread about this, and maybe we can get to the bottom of the actual issue. Spamming every thread about JavaSound isn’t helpful when you have no idea what the underlying problem is, or how to work around it. Your usual advice to ignore JavaSound and use OpenAL is only useful for some, and only useful if people are happy / able to use additional native code. It’s throwing the baby out with the bathwater. You’re not helping people avoid headaches - you’re giving them one! ::slight_smile:

OK no one appointed me referee, but this is getting a little tiresome.

If gouessej doesn’t mention an issue on this thread and starts his own thread, then chances are the people here who might find the ideas and POV worth considering won’t even hear about them.

I very much appreciate reading nsigma’s reply and differing opinion. But I think telling gouessej he shouldn’t even post his POV is over some sort of etiquette line.

I hope both continue their generous contributions of their time and knowledge, and take the rhetorical position of presenting their ideas in a way that presumes the OP and the rest of us are capable of coming to our own conclusions based upon the merits.

You’re missing the point of my post! I would never try and stop people giving their POV. What annoys me is that gouessej keeps making the same blanket statement. I’ve pointed him at relevant bug reports on numerous occasions, and have offered to help track down the exact cause on numerous occasions. The reason that needs to be elsewhere is it involves posting the output of a variety of commands, which would really hijack a thread. I’m sorry, but just saying “it doesn’t work” is like the world’s worst bug report, and not particularly useful to anyone. I don’t consider “don’t use JavaSound” a workaround either! :wink:

Getting back on topic! :wink: There’s possibly a documented reason that code won’t work as you expect. I think

sourceLines[s].getLineInfo().matches(lineInfo)

is only going to match lines that have the same audio format you’ve specified, so you’d actually miss counting lines that are open but have a different format, yet still count towards the total. That’s an unlikely scenario though.

More likely is that you’re not accounting for lines opened by another process. See in the JavaDoc

[quote]Since certain lines are a shared resource, a mixer may not be able to open the maximum number of lines if another process has opened lines of this mixer.
[/quote]
I think you’ve got an interesting approach there, but I would personally say go for just the first mixer that supports output, and throw an Exception if you can’t open it. The reason - someone starts playing a game and the sound comes from their standard speakers, then they use something else that grabs the soundcard, then they go back to your game while the soundcard is still tied up and suddenly the game sound comes out of another (unexpected) output.

If you use the first mixer that supports output, you will get the system default. If that’s changed by PulseAudio, then you might want to consider also parsing the .pulse folder as suggested earlier. Just picking the next available mixer is actually not very user-friendly at all, and could lead to fried speakers! :slight_smile:

Yeah, I realized this shortly after posting it and instead used the alternative of trying to open a line as I go. I haven’t pushed any changes yet though since I may be the only person who has experienced this latency issue so far.

Wait, how could this lead to fried speakers?

LOL. Just worst case scenario, though something I’ve heard of happening in the pro-audio world. If someone’s been listening to some really quiet music through their Hi-Fi, and you unexpectedly send a really hot signal down there, then you could theoretically damage the speakers.

Mostly, though, it’s just not a nice thing to do to your users. On my laptop, if the first mixer is busy, you’ll start sending the sound through the HDMI port if you pick the next mixer, which isn’t connected to anything, so no audio but no exception either. I’d throw the Exception and let the users of your library decide how best to handle, which would probably be a manual selection dialog.

It sounds like the most appropriate solution is to just add a second init() function which takes a Mixer.Info, so the user can specify which Mixer to use. I’ll, of course, leave the current init() function in as well, which uses the AudioSystem.getLine() function.

I’d say yes to the two init functions, but use the first mixer that supports outputs by default. Using a line from AudioSystem.getLine will get you lousy performance in some cases, particularly on Linux. This is because AudioSystem looks for mixer capable of playing multiple lines, so ignores the direct mixers because they can only use a single line. As you’re doing software mixing, you want to benefit from the better performing mixer by default.