diff options
author | Sven Gothel <[email protected]> | 2014-02-15 09:12:30 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2014-02-15 09:12:30 +0100 |
commit | 938fbc9e4cb58c1ab6a6f66f128189ed7b634955 (patch) | |
tree | e1d4cf729e91ec2353a0735a937d9a2e1c0b46d4 /src/jogl | |
parent | 82f679b064784213591b460fc5eaa1f5f196fbd1 (diff) |
Bug 927 - Multithreading (MT) issues with ALAudioSink (openal-soft)
- Using update openal-soft (commit 7297c3214a4c648aaee81a9877da15b88f798197)
- Analyzed openal-soft threading issues:
- a global-lock would have removed the issue
- turns out that using ALC_EXT_thread_local_context's alcSetThreadContext(..)
instead of alcMakeContextCurrent(..) solves the issue
- Cleaned up al*GetError() queries and handling
- Simplified flush/dequeue buffers
Diffstat (limited to 'src/jogl')
-rw-r--r-- | src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java | 133 |
1 files changed, 102 insertions, 31 deletions
diff --git a/src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java b/src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java index 8b12f8a28..287b3081c 100644 --- a/src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java +++ b/src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java @@ -31,6 +31,8 @@ package jogamp.opengl.openal.av; import java.nio.ByteBuffer; import java.util.Arrays; +import jogamp.opengl.Debug; + import com.jogamp.common.util.LFRingbuffer; import com.jogamp.common.util.Ringbuffer; import com.jogamp.common.util.locks.LockFactory; @@ -50,6 +52,8 @@ import com.jogamp.opengl.util.av.AudioSink; public class ALAudioSink implements AudioSink { private static final String AL_SOFT_buffer_samples = "AL_SOFT_buffer_samples"; + private static final String ALC_EXT_thread_local_context = "ALC_EXT_thread_local_context"; + private static final boolean DEBUG_TRACE; private static final ALC alc; private static final AL al; private static final ALExt alExt; @@ -58,6 +62,7 @@ public class ALAudioSink implements AudioSink { private String deviceSpecifier; private ALCdevice device; private boolean hasSOFTBufferSamples; + private boolean hasALC_thread_local_context; private AudioFormat preferredAudioFormat; private ALCcontext context; private final RecursiveLock lock = LockFactory.createRecursiveLock(); @@ -106,6 +111,9 @@ public class ALAudioSink implements AudioSink { private volatile boolean playRequested = false; static { + Debug.initSingleton(); + DEBUG_TRACE = Debug.isPropertyDefined("jogl.debug.AudioSink.trace", true); + ALC _alc = null; AL _al = null; ALExt _alExt = null; @@ -160,12 +168,15 @@ public class ALAudioSink implements AudioSink { } hasSOFTBufferSamples = al.alIsExtensionPresent(AL_SOFT_buffer_samples); + hasALC_thread_local_context = alc.alcIsExtensionPresent(null, ALC_EXT_thread_local_context) || + alc.alcIsExtensionPresent(device, ALC_EXT_thread_local_context) ; preferredAudioFormat = queryPreferredAudioFormat(); if( DEBUG ) { System.out.println("ALAudioSink: OpenAL Extensions:"+al.alGetString(AL.AL_EXTENSIONS)); System.out.println("ALAudioSink: Null device OpenAL Extensions:"+alc.alcGetString(null, ALC.ALC_EXTENSIONS)); System.out.println("ALAudioSink: Device "+deviceSpecifier+" OpenAL Extensions:"+alc.alcGetString(device, ALC.ALC_EXTENSIONS)); System.out.println("ALAudioSink: hasSOFTBufferSamples "+hasSOFTBufferSamples); + System.out.println("ALAudioSink: hasALC_thread_local_context "+hasALC_thread_local_context); System.out.println("ALAudioSink: preferredAudioFormat "+preferredAudioFormat); } @@ -174,7 +185,7 @@ public class ALAudioSink implements AudioSink { alSource = new int[1]; al.alGenSources(1, alSource, 0); final int err = al.alGetError(); - if( err != AL.AL_NO_ERROR ) { + if( AL.AL_NO_ERROR != err ) { alSource = null; throw new RuntimeException("ALAudioSink: Error generating Source: 0x"+Integer.toHexString(err)); } @@ -208,10 +219,31 @@ public class ALAudioSink implements AudioSink { private final void lockContext() { lock.lock(); - alc.alcMakeContextCurrent(context); + if( hasALC_thread_local_context ) { + alExt.alcSetThreadContext(context); + } else { + alc.alcMakeContextCurrent(context); + } + final int alcErr = alc.alcGetError(null); + if( ALC.ALC_NO_ERROR != alcErr ) { + final String err = "ALCError "+toHexString(alcErr)+" while makeCurrent. "+this; + System.err.println(err); + Thread.dumpStack(); + lock.unlock(); + throw new RuntimeException(err); + } + final int alErr = al.alGetError(); + if( ALC.ALC_NO_ERROR != alErr ) { + System.err.println("Prev - ALError "+toHexString(alErr)+" @ makeCurrent. "+this); + Thread.dumpStack(); + } } private final void unlockContext() { - alc.alcMakeContextCurrent(null); + if( hasALC_thread_local_context ) { + alExt.alcSetThreadContext(null); + } else { + alc.alcMakeContextCurrent(null); + } lock.unlock(); } private final void destroyContext() { @@ -251,6 +283,14 @@ public class ALAudioSink implements AudioSink { "queue[g "+frameGrowAmount+", l "+frameLimit+"]"; } + private final String shortString() { + final int alSrcName = null != alSource ? alSource[0] : 0; + final int ctxHash = context != null ? context.hashCode() : 0; + return "[ctx "+toHexString(ctxHash)+", playReq "+playRequested+", alSrc "+alSrcName+ + ", queued["+alFramesPlaying.size()+", " + alBufferBytesQueued+" bytes], "+ + "queue[g "+frameGrowAmount+", l "+frameLimit+"]"; + } + public final String getPerfString() { final int alBuffersLen = null != alBufferNames ? alBufferNames.length : 0; return "Play [buffer "+alFramesPlaying.size()+"/"+alBuffersLen+", apts "+getPTS()+", "+getQueuedTime() + " ms, " + alBufferBytesQueued+" bytes]"; @@ -325,7 +365,7 @@ public class ALAudioSink implements AudioSink { alBufferNames = new int[initialFrameCount]; al.alGenBuffers(initialFrameCount, alBufferNames, 0); final int err = al.alGetError(); - if( err != AL.AL_NO_ERROR ) { + if( AL.AL_NO_ERROR != err ) { alBufferNames = null; throw new RuntimeException("ALAudioSink: Error generating Buffers: 0x"+Integer.toHexString(err)); } @@ -340,6 +380,10 @@ public class ALAudioSink implements AudioSink { queueGrowAmount > 0 ? queueGrowAmount : AudioSink.DefaultQueueGrowAmount, useFrameDuration); this.frameLimit = requestedFormat.getFrameCount( queueLimit > 0 ? queueLimit : AudioSink.DefaultQueueLimitWithVideo, useFrameDuration); + if( DEBUG_TRACE ) { + alFramesAvail.dump(System.err, "Avail-init"); + alFramesPlaying.dump(System.err, "Playi-init"); + } } } finally { unlockContext(); @@ -375,7 +419,7 @@ public class ALAudioSink implements AudioSink { final int[] newALBufferNames = new int[frameGrowAmount]; al.alGenBuffers(frameGrowAmount, newALBufferNames, 0); final int err = al.alGetError(); - if( err != AL.AL_NO_ERROR ) { + if( AL.AL_NO_ERROR != err ) { if( DEBUG ) { System.err.println(getThreadName()+": ALAudioSink.growBuffers: Error generating "+frameGrowAmount+" new Buffers: 0x"+Integer.toHexString(err)); } @@ -397,6 +441,10 @@ public class ALAudioSink implements AudioSink { if( DEBUG ) { System.err.println(getThreadName()+": ALAudioSink: Buffer grown "+frameGrowAmount+": Avail "+alFramesAvail+", playing "+alFramesPlaying); } + if( DEBUG_TRACE ) { + alFramesAvail.dump(System.err, "Avail-grow"); + alFramesPlaying.dump(System.err, "Playi-grow"); + } return true; } @@ -469,7 +517,7 @@ public class ALAudioSink implements AudioSink { return initialized; } - private final int dequeueBuffer(boolean wait) { + private final int dequeueBuffer(final boolean wait, final boolean ignoreBufferInconsistency) { int alErr = AL.AL_NO_ERROR; final int releaseBufferCount; if( alBufferBytesQueued > 0 ) { @@ -505,7 +553,7 @@ public class ALAudioSink implements AudioSink { } if( releaseBufferCount > 0 ) { - int[] buffers=new int[releaseBufferCount]; + final int[] buffers = new int[releaseBufferCount]; al.alSourceUnqueueBuffers(alSource[0], releaseBufferCount, buffers, 0); alErr = al.alGetError(); if( AL.AL_NO_ERROR != alErr ) { @@ -514,22 +562,42 @@ public class ALAudioSink implements AudioSink { for ( int i=0; i<releaseBufferCount; i++ ) { final ALAudioFrame releasedBuffer = alFramesPlaying.get(); if( null == releasedBuffer ) { - throw new InternalError("Internal Error: "+this); - } - if( releasedBuffer.alBuffer != buffers[i] ) { - alFramesAvail.dump(System.err, "Avail-deq02-post"); - alFramesPlaying.dump(System.err, "Playi-deq02-post"); - throw new InternalError("Buffer name mismatch: dequeued: "+buffers[i]+", released "+releasedBuffer+", "+this); + if( !ignoreBufferInconsistency ) { + throw new InternalError("Internal Error: "+this); + } + } else { + if(DEBUG_TRACE) { + System.err.println("< [al "+buffers[i]+", q "+releasedBuffer.alBuffer+"] <- "+shortString()+" @ "+getThreadName()); + } + if( releasedBuffer.alBuffer != buffers[i] ) { + if( !ignoreBufferInconsistency ) { + alFramesAvail.dump(System.err, "Avail-deq02-post"); + alFramesPlaying.dump(System.err, "Playi-deq02-post"); + throw new InternalError("Buffer name mismatch: dequeued: "+buffers[i]+", released "+releasedBuffer+", "+this); + } + } } alBufferBytesQueued -= releasedBuffer.getByteSize(); if( !alFramesAvail.put(releasedBuffer) ) { throw new InternalError("Internal Error: "+this); } + if(DEBUG_TRACE) { + System.err.println("<< [al "+buffers[i]+", q "+releasedBuffer.alBuffer+"] <- "+shortString()+" @ "+getThreadName()); + } } } return releaseBufferCount; } private final void dequeueForceAll() { + if(DEBUG_TRACE) { + System.err.println("< _FLUSH_ <- "+shortString()+" @ "+getThreadName()); + } + final int[] val=new int[1]; + al.alSourcei(alSource[0], AL.AL_BUFFER, 0); // explicit force zero buffer! + if(DEBUG_TRACE) { + al.alGetSourcei(alSource[0], AL.AL_BUFFERS_PROCESSED, val, 0); + } + final int alErr = al.alGetError(); while ( !alFramesPlaying.isEmpty() ) { final ALAudioFrame releasedBuffer = alFramesPlaying.get(); if( null == releasedBuffer ) { @@ -540,13 +608,15 @@ public class ALAudioSink implements AudioSink { throw new InternalError("Internal Error: "+this); } } - if( 0 != alBufferBytesQueued ) { - throw new InternalError("Internal Error: "+this); + alBufferBytesQueued = 0; + if(DEBUG_TRACE) { + System.err.println("<< _FLUSH_ [al "+val[0]+", err "+toHexString(alErr)+"] <- "+shortString()+" @ "+getThreadName()); + Thread.dumpStack(); } } private final int dequeueBuffer(boolean wait, int inPTS, int inDuration) { - final int dequeuedBufferCount = dequeueBuffer( wait ); + final int dequeuedBufferCount = dequeueBuffer( wait, false /* ignoreBufferInconsistency */ ); final ALAudioFrame currentBuffer = alFramesPlaying.peek(); if( null != currentBuffer ) { playingPTS = currentBuffer.getPTS(); @@ -572,18 +642,12 @@ public class ALAudioSink implements AudioSink { return null; } final ALAudioFrame alFrame; - int alErr = AL.AL_NO_ERROR; // OpenAL consumes buffers in the background // we first need to initialize the OpenAL buffers then // start continuous playback. lockContext(); try { - alErr = al.alGetError(); - if(al.alGetError() != AL.AL_NO_ERROR) { - throw new RuntimeException("ALError "+toHexString(alErr)+" while makeCurrent. "+this); - } - final int duration = chosenFormat.getBytesDuration(byteCount); final boolean dequeueDone; if( alFramesAvail.isEmpty() ) { @@ -612,6 +676,7 @@ public class ALAudioSink implements AudioSink { if( !alFramesPlaying.put( alFrame ) ) { throw new InternalError("Internal Error: "+this); } + final int[] alBufferNames = new int[] { alFrame.alBuffer }; if( hasSOFTBufferSamples ) { final int samplesPerChannel = chosenFormat.getBytesSampleCount(byteCount) / chosenFormat.channelCount; // final int samplesPerChannel = ALHelpers.bytesToSampleCount(byteCount, alChannelLayout, alSampleType); @@ -621,15 +686,22 @@ public class ALAudioSink implements AudioSink { al.alBufferData(alFrame.alBuffer, alFormat, bytes, byteCount, chosenFormat.sampleRate); } - final int[] alBufferNames = new int[] { alFrame.alBuffer }; + if(DEBUG_TRACE) { + System.err.println("> "+alFrame.alBuffer+" -> "+shortString()+" @ "+getThreadName()); + } + al.alSourceQueueBuffers(alSource[0], 1, alBufferNames, 0); - alErr = al.alGetError(); - if(al.alGetError() != AL.AL_NO_ERROR) { + final int alErr = al.alGetError(); + if( AL.AL_NO_ERROR != alErr ) { throw new RuntimeException("ALError "+toHexString(alErr)+" while queueing buffer "+toHexString(alBufferNames[0])+". "+this); } alBufferBytesQueued += byteCount; enqueuedFrameCount++; + if(DEBUG_TRACE) { + System.err.println(">> "+alFrame.alBuffer+" -> "+shortString()+" @ "+getThreadName()); + } + playImpl(); // continue playing, fixes issue where we ran out of enqueued data! } finally { unlockContext(); @@ -664,7 +736,7 @@ public class ALAudioSink implements AudioSink { final int[] val = new int[1]; al.alGetSourcei(alSource[0], AL.AL_SOURCE_STATE, val, 0); final int alErr = al.alGetError(); - if(al.alGetError() != AL.AL_NO_ERROR) { + if( AL.AL_NO_ERROR != alErr ) { throw new RuntimeException("ALError "+toHexString(alErr)+" while querying SOURCE_STATE. "+this); } return val[0]; @@ -690,7 +762,7 @@ public class ALAudioSink implements AudioSink { if( playRequested && AL.AL_PLAYING != getSourceState() ) { al.alSourcePlay(alSource[0]); final int alErr = al.alGetError(); - if(al.alGetError() != AL.AL_NO_ERROR) { + if( AL.AL_NO_ERROR != alErr ) { throw new RuntimeException("ALError "+toHexString(alErr)+" while start playing. "+this); } } @@ -718,7 +790,7 @@ public class ALAudioSink implements AudioSink { playRequested = false; al.alSourcePause(alSource[0]); final int alErr = al.alGetError(); - if(al.alGetError() != AL.AL_NO_ERROR) { + if( AL.AL_NO_ERROR != alErr ) { throw new RuntimeException("ALError "+toHexString(alErr)+" while pausing. "+this); } } @@ -728,7 +800,7 @@ public class ALAudioSink implements AudioSink { playRequested = false; al.alSourceStop(alSource[0]); final int alErr = al.alGetError(); - if(al.alGetError() != AL.AL_NO_ERROR) { + if( AL.AL_NO_ERROR != alErr ) { throw new RuntimeException("ALError "+toHexString(alErr)+" while pausing. "+this); } } @@ -795,8 +867,7 @@ public class ALAudioSink implements AudioSink { try { // pauseImpl(); stopImpl(); - al.alSourcei(alSource[0], AL.AL_BUFFER, 0); // explicit force zero buffer! - dequeueBuffer( false /* wait */ ); + // Redundant: dequeueBuffer( false /* wait */, true /* ignoreBufferInconsistency */); dequeueForceAll(); if( alBufferNames.length != alFramesAvail.size() || alFramesPlaying.size() != 0 ) { throw new InternalError("XXX: "+this); |