<Sound Dev> Opening multiple output lines

Mark Wielaard mark at klomp.org
Sun May 4 10:19:47 PDT 2008


Hi Tom,

On Wed, Apr 30, 2008 at 09:17:11PM -0400, Thomas Fitzsimmons wrote:
>>> I'm testing a sound-using applet on Fedora 8.  Sun JDK 1.6 runs the 
>>> appletcorrectly but OpenJDK does not.  The applet attempts to open two 
>>> audio playback lines in succession, without closing the first before
>>> attempting to open the second.
>>> The first open attempt succeeds but the second attempt fails with:
>>>
>>> javax.sound.sampled.LineUnavailableException: line with format 
>>> PCM_SIGNED
>>> 44100.0 Hz, 16 bit, stereo, 4 bytes/frame, little-endian not supported.
>>> [...]
>>>
>>> The exception message is misleading since the line format is 
>>> supported.  The actual cause of the failure is in:
>>>
>>> PLATFORM_API_LinuxOS_ALSA_PCMUtils.c:openPCMfromDeviceID
>>>
>>> This call:
>>>
>>>      ret = snd_pcm_open(handle, buffer,
>>> isSource?SND_PCM_STREAM_PLAYBACK:SND_PCM_STREAM_CAPTURE,
>>>                         SND_PCM_NONBLOCK);
>>>
>>> returns the error corresponding to "Device or resource busy".
>
> Juraj Svec wrote:
>> could you please send some more information? Output of the 
>> TRACE1("Opening ALSA device %s\n", buffer); above the snd_pcm_open would 
>> be great and also your sound card type and version will definitely help.
>
> I've attached the output of running AudioSystemGetLineTest against 
> openjdk-6-src-b09-11_apr_2008 with USE_TRACE defined,
> and the description of my sound card reported by lspci -vv.

I had the same problem with a similar setup and with applications that
forget to close a line they don't use anymore (unfortunately this seems
very common). With the current directaudio backend I don't see how multiple
lines for the same hardware device could work though. So I am using a
trick to look for "sloppy" applications. If the last line opened in the
directaudio device was for the same hardware format then we silence that
one first so we can hand out a new one. This seems to work surprisingly
well. And it doesn't seem to interfere with applications that handle the
hardware formats they need explicitly.

With gcjwebplugin and this patch we can happily play the vNES games :)

Of course a real solution would be to import or write a better mixer
that does share lines properly.

Cheers,

Mark
-------------- next part --------------
--- /home/mark/src/openjdk/jdk/src/share/classes/com/sun/media/sound/DirectAudioDevice.java	2008-04-13 01:05:30.000000000 +0200
+++ openjdk/jdk/src/share/classes/com/sun/media/sound/DirectAudioDevice.java	2008-05-04 18:42:39.000000000 +0200
@@ -394,7 +394,10 @@
         private float leftGain, rightGain;
         protected volatile boolean noService = false; // do not run the nService method
 
-        protected Object lockNative = new Object();
+        // Guards all native calls and the lastOpened static variable.
+        protected static Object lockNative = new Object();
+        // Keeps track of last opened line, see implOpen "trick".
+        protected static DirectDL lastOpened;
 
         // CONSTRUCTOR
         protected DirectDL(DataLine.Info info,
@@ -496,20 +499,47 @@
             // align buffer to full frames
             bufferSize = ((int) bufferSize / format.getFrameSize()) * format.getFrameSize();
 
-            id = nOpen(mixerIndex, deviceID, isSource,
-                       encoding,
-                       hardwareFormat.getSampleRate(),
-                       hardwareFormat.getSampleSizeInBits(),
-                       hardwareFormat.getFrameSize(),
-                       hardwareFormat.getChannels(),
-                       hardwareFormat.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED),
-                       hardwareFormat.isBigEndian(),
-                       bufferSize);
+	    synchronized(lockNative) {
+	      id = nOpen(mixerIndex, deviceID, isSource,
+			 encoding,
+			 hardwareFormat.getSampleRate(),
+			 hardwareFormat.getSampleSizeInBits(),
+			 hardwareFormat.getFrameSize(),
+			 hardwareFormat.getChannels(),
+			 hardwareFormat.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED),
+			 hardwareFormat.isBigEndian(),
+			 bufferSize);
+	      
+	      if (id == 0) {
+		// Bah... Dirty trick. The most likely cause is an application
+		// already having a line open for this particular hardware
+		// format and forgetting about it. If so, silently close that
+		// implementation and try again. Unfortuantely we can only
+		// open one line per hardware format currently.
+		if (lastOpened != null
+		    && hardwareFormat.matches(lastOpened.hardwareFormat)) {
+		  lastOpened.implClose();
+		  lastOpened = null;
+		  
+		  id = nOpen(mixerIndex, deviceID, isSource,
+			     encoding,
+			     hardwareFormat.getSampleRate(),
+			     hardwareFormat.getSampleSizeInBits(),
+			     hardwareFormat.getFrameSize(),
+			     hardwareFormat.getChannels(),
+			     hardwareFormat.getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED),
+			     hardwareFormat.isBigEndian(),
+			     bufferSize);
+		}
+		
+		if (id == 0) {
+		    // TODO: nicer error messages...
+		    throw new LineUnavailableException("line with format "+format+" not supported.");
+                }
+	      }
+	      lastOpened = this;
+	    }
 
-            if (id == 0) {
-                // TODO: nicer error messages...
-                throw new LineUnavailableException("line with format "+format+" not supported.");
-            }
             this.bufferSize = nGetBufferSize(id, isSource);
             if (this.bufferSize < 1) {
                 // this is an error!
@@ -616,6 +646,8 @@
             id = 0;
             synchronized (lockNative) {
                 nClose(oldID, isSource);
+                if (lastOpened == this)
+                  lastOpened = null;
             }
             bytePosition = 0;
             softwareConversionSize = 0;


More information about the sound-dev mailing list