Jogamp
Bug 1270 - Fix OSX El Capitan 10.11 stuttering: ALAudioSink: DEBUG: Show OpenAL Version
[jogl.git] / src / jogl / classes / jogamp / opengl / openal / av / ALAudioSink.java
CommitLineData
da7210c6
SG
1/**
2 * Copyright 2013 JogAmp Community. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without modification, are
5 * permitted provided that the following conditions are met:
d42b236e 6 *
da7210c6
SG
7 * 1. Redistributions of source code must retain the above copyright notice, this list of
8 * conditions and the following disclaimer.
d42b236e 9 *
da7210c6
SG
10 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
11 * of conditions and the following disclaimer in the documentation and/or other materials
12 * provided with the distribution.
d42b236e 13 *
da7210c6
SG
14 * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
15 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
d42b236e 23 *
da7210c6
SG
24 * The views and conclusions contained in the software and documentation are those of the
25 * authors and should not be interpreted as representing official policies, either expressed
26 * or implied, of JogAmp Community.
27 */
3bf56421
XR
28package jogamp.opengl.openal.av;
29
3bf56421 30
deae6def
SG
31import java.nio.ByteBuffer;
32import java.util.Arrays;
33
938fbc9e
SG
34import jogamp.opengl.Debug;
35
74fe4c34 36import com.jogamp.common.ExceptionUtils;
f18a94b3 37import com.jogamp.common.util.LFRingbuffer;
556d92b6 38import com.jogamp.common.util.PropertyAccess;
f18a94b3 39import com.jogamp.common.util.Ringbuffer;
bc377663
SG
40import com.jogamp.common.util.locks.LockFactory;
41import com.jogamp.common.util.locks.RecursiveLock;
da7210c6
SG
42import com.jogamp.openal.AL;
43import com.jogamp.openal.ALC;
556d92b6 44import com.jogamp.openal.ALCConstants;
da7210c6
SG
45import com.jogamp.openal.ALCcontext;
46import com.jogamp.openal.ALCdevice;
556d92b6 47import com.jogamp.openal.ALConstants;
e28a3b39 48import com.jogamp.openal.ALExt;
da7210c6 49import com.jogamp.openal.ALFactory;
e28a3b39 50import com.jogamp.openal.util.ALHelpers;
da7210c6 51import com.jogamp.opengl.util.av.AudioSink;
3bf56421 52
da7210c6
SG
53/***
54 * OpenAL Audio Sink
55 */
3bf56421
XR
56public class ALAudioSink implements AudioSink {
57
e28a3b39 58 private static final String AL_SOFT_buffer_samples = "AL_SOFT_buffer_samples";
938fbc9e
SG
59 private static final String ALC_EXT_thread_local_context = "ALC_EXT_thread_local_context";
60 private static final boolean DEBUG_TRACE;
da7210c6
SG
61 private static final ALC alc;
62 private static final AL al;
e28a3b39 63 private static final ALExt alExt;
d42b236e
SG
64 private static final boolean staticAvailable;
65
da7210c6
SG
66 private String deviceSpecifier;
67 private ALCdevice device;
e28a3b39 68 private boolean hasSOFTBufferSamples;
938fbc9e 69 private boolean hasALC_thread_local_context;
d42b236e 70 private AudioFormat preferredAudioFormat;
da7210c6 71 private ALCcontext context;
f18a94b3 72 private final RecursiveLock lock = LockFactory.createRecursiveLock();
da7210c6 73
bc377663 74 /** Playback speed, range [0.5 - 2.0], default 1.0. */
f18a94b3 75 private float playSpeed;
8cdbfb67 76 private float volume = 1.0f;
d42b236e 77
deae6def
SG
78 static class ALAudioFrame extends AudioFrame {
79 private final int alBuffer;
d42b236e 80
556d92b6 81 ALAudioFrame(final int alBuffer) {
deae6def
SG
82 this.alBuffer = alBuffer;
83 }
556d92b6 84 public ALAudioFrame(final int alBuffer, final int pts, final int duration, final int dataSize) {
deae6def
SG
85 super(pts, duration, dataSize);
86 this.alBuffer = alBuffer;
87 }
d42b236e 88
deae6def
SG
89 /** Get this frame's OpenAL buffer name */
90 public final int getALBuffer() { return alBuffer; }
d42b236e
SG
91
92 public String toString() {
deae6def 93 return "ALAudioFrame[pts " + pts + " ms, l " + duration + " ms, " + byteSize + " bytes, buffer "+alBuffer+"]";
da7210c6 94 }
da7210c6 95 }
d42b236e 96
deae6def
SG
97 // private ALAudioFrame[] alFrames = null;
98 private int[] alBufferNames = null;
f18a94b3
SG
99 private int frameGrowAmount = 0;
100 private int frameLimit = 0;
d42b236e 101
deae6def
SG
102 private Ringbuffer<ALAudioFrame> alFramesAvail = null;
103 private Ringbuffer<ALAudioFrame> alFramesPlaying = null;
bc377663 104 private volatile int alBufferBytesQueued = 0;
c200045a 105 private volatile int playingPTS = AudioFrame.INVALID_PTS;
bc377663 106 private volatile int enqueuedFrameCount;
da7210c6
SG
107
108 private int[] alSource = null;
e28a3b39
SG
109 private AudioFormat chosenFormat;
110 private int alChannelLayout;
111 private int alSampleType;
112 private int alFormat;
da7210c6 113 private boolean initialized;
d42b236e 114
bc377663 115 private volatile boolean playRequested = false;
da7210c6 116
3bf56421 117 static {
938fbc9e 118 Debug.initSingleton();
556d92b6 119 DEBUG_TRACE = PropertyAccess.isPropertyDefined("jogl.debug.AudioSink.trace", true);
938fbc9e 120
da7210c6
SG
121 ALC _alc = null;
122 AL _al = null;
e28a3b39 123 ALExt _alExt = null;
3bf56421 124 try {
d42b236e 125 _alc = ALFactory.getALC();
da7210c6 126 _al = ALFactory.getAL();
e28a3b39 127 _alExt = ALFactory.getALExt();
556d92b6 128 } catch(final Throwable t) {
da7210c6 129 if( DEBUG ) {
43c85971 130 System.err.println("ALAudioSink: Caught "+t.getClass().getName()+": "+t.getMessage());
da7210c6
SG
131 t.printStackTrace();
132 }
133 }
134 alc = _alc;
135 al = _al;
e28a3b39
SG
136 alExt = _alExt;
137 staticAvailable = null != alc && null != al && null != alExt;
da7210c6 138 }
d42b236e 139
bc905a10
SG
140 private void clearPreALError(final String prefix) {
141 checkALError(prefix);
142 }
143 private boolean checkALError(final String prefix) {
144 final int alcErr = alc.alcGetError(device);
145 final int alErr = al.alGetError();
556d92b6 146 final boolean ok = ALCConstants.ALC_NO_ERROR == alcErr && ALConstants.AL_NO_ERROR == alErr;
bc905a10
SG
147 if( DEBUG ) {
148 System.err.println("ALAudioSink."+prefix+": ok "+ok+", err [alc "+toHexString(alcErr)+", al "+toHexString(alErr)+"]");
149 }
150 return ok;
151 }
152
da7210c6
SG
153 public ALAudioSink() {
154 initialized = false;
155 chosenFormat = null;
d42b236e 156
da7210c6
SG
157 if( !staticAvailable ) {
158 return;
159 }
13d850c7
SG
160 synchronized(ALAudioSink.class) {
161 try {
162 // Get handle to default device.
163 device = alc.alcOpenDevice(null);
164 if (device == null) {
165 throw new RuntimeException(getThreadName()+": ALAudioSink: Error opening default OpenAL device");
166 }
bc905a10
SG
167 int checkErrIter = 1;
168
169 clearPreALError("init."+checkErrIter++);
d42b236e 170
13d850c7 171 // Get the device specifier.
556d92b6 172 deviceSpecifier = alc.alcGetString(device, ALCConstants.ALC_DEVICE_SPECIFIER);
13d850c7
SG
173 if (deviceSpecifier == null) {
174 throw new RuntimeException(getThreadName()+": ALAudioSink: Error getting specifier for default OpenAL device");
175 }
d42b236e 176
13d850c7 177 // Create audio context.
bc905a10
SG
178 // final int[] attrs = new int[] { ALC.ALC_FREQUENCY, DefaultFormat.sampleRate, 0 };
179 // context = alc.alcCreateContext(device, attrs, 0);
13d850c7
SG
180 context = alc.alcCreateContext(device, null);
181 if (context == null) {
182 throw new RuntimeException(getThreadName()+": ALAudioSink: Error creating OpenAL context for "+deviceSpecifier);
183 }
d42b236e 184
13d850c7
SG
185 lockContext();
186 try {
187 // Check for an error.
556d92b6 188 if ( alc.alcGetError(device) != ALCConstants.ALC_NO_ERROR ) {
13d850c7
SG
189 throw new RuntimeException(getThreadName()+": ALAudioSink: Error making OpenAL context current");
190 }
d42b236e 191
13d850c7
SG
192 hasSOFTBufferSamples = al.alIsExtensionPresent(AL_SOFT_buffer_samples);
193 hasALC_thread_local_context = alc.alcIsExtensionPresent(null, ALC_EXT_thread_local_context) ||
194 alc.alcIsExtensionPresent(device, ALC_EXT_thread_local_context) ;
bc905a10
SG
195 clearPreALError("init."+checkErrIter++);
196 preferredAudioFormat = new AudioFormat(querySampleRate(), DefaultFormat.sampleSize, DefaultFormat.channelCount, DefaultFormat.signed, DefaultFormat.fixedP, DefaultFormat.planar, DefaultFormat.littleEndian);
13d850c7 197 if( DEBUG ) {
5b6112bf
SG
198 final int[] alcvers = { 0, 0 };
199 System.out.println("ALAudioSink: OpenAL Version: "+al.alGetString(ALConstants.AL_VERSION));
200 System.out.println("ALAudioSink: OpenAL Extensions: "+al.alGetString(ALConstants.AL_EXTENSIONS));
bc905a10 201 clearPreALError("init."+checkErrIter++);
5b6112bf
SG
202 System.out.println("ALAudioSink: Null device OpenALC:");
203 alc.alcGetIntegerv(null, ALCConstants.ALC_MAJOR_VERSION, 1, alcvers, 0);
204 alc.alcGetIntegerv(null, ALCConstants.ALC_MINOR_VERSION, 1, alcvers, 1);
205 System.out.println(" Version: "+alcvers[0]+"."+alcvers[1]);
206 System.out.println(" Extensions: "+alc.alcGetString(null, ALCConstants.ALC_EXTENSIONS));
bc905a10 207 clearPreALError("init."+checkErrIter++);
5b6112bf
SG
208 System.out.println("ALAudioSink: Device "+deviceSpecifier+" OpenALC:");
209 alc.alcGetIntegerv(device, ALCConstants.ALC_MAJOR_VERSION, 1, alcvers, 0);
210 alc.alcGetIntegerv(device, ALCConstants.ALC_MINOR_VERSION, 1, alcvers, 1);
211 System.out.println(" Version: "+alcvers[0]+"."+alcvers[1]);
212 System.out.println(" Extensions: "+alc.alcGetString(device, ALCConstants.ALC_EXTENSIONS));
13d850c7
SG
213 System.out.println("ALAudioSink: hasSOFTBufferSamples "+hasSOFTBufferSamples);
214 System.out.println("ALAudioSink: hasALC_thread_local_context "+hasALC_thread_local_context);
215 System.out.println("ALAudioSink: preferredAudioFormat "+preferredAudioFormat);
bc905a10 216 clearPreALError("init."+checkErrIter++);
13d850c7 217 }
d42b236e 218
13d850c7
SG
219 // Create source
220 {
221 alSource = new int[1];
222 al.alGenSources(1, alSource, 0);
223 final int err = al.alGetError();
556d92b6 224 if( ALConstants.AL_NO_ERROR != err ) {
13d850c7
SG
225 alSource = null;
226 throw new RuntimeException(getThreadName()+": ALAudioSink: Error generating Source: 0x"+Integer.toHexString(err));
227 }
228 }
d42b236e 229
13d850c7
SG
230 if( DEBUG ) {
231 System.err.println("ALAudioSink: Using device: " + deviceSpecifier);
d42b236e 232 }
13d850c7
SG
233 initialized = true;
234 } finally {
235 unlockContext();
bc377663 236 }
13d850c7 237 return;
556d92b6 238 } catch ( final Exception e ) {
d42b236e 239 if( DEBUG ) {
13d850c7 240 System.err.println(e.getMessage());
bc905a10 241 e.printStackTrace();
bc377663 242 }
13d850c7 243 destroy();
da7210c6 244 }
3bf56421
XR
245 }
246 }
d42b236e 247
bc905a10
SG
248 private final int querySampleRate() {
249 final int sampleRate;
e28a3b39 250 final int[] value = new int[1];
556d92b6 251 alc.alcGetIntegerv(device, ALCConstants.ALC_FREQUENCY, 1, value, 0);
bc905a10
SG
252 final int alcErr = alc.alcGetError(device);
253 final int alErr = al.alGetError();
556d92b6 254 if ( ALCConstants.ALC_NO_ERROR == alcErr && ALConstants.AL_NO_ERROR == alErr && 0 != value[0] ) {
e28a3b39 255 sampleRate = value[0];
bc905a10
SG
256 } else {
257 sampleRate = DefaultFormat.sampleRate;
258 }
259 if( DEBUG ) {
260 System.err.println("ALAudioSink.querySampleRate: err [alc "+toHexString(alcErr)+", al "+toHexString(alErr)+"], freq: "+value[0]+" -> "+sampleRate);
e28a3b39 261 }
bc905a10 262 return sampleRate;
e28a3b39 263 }
d42b236e 264
bc377663
SG
265 private final void lockContext() {
266 lock.lock();
938fbc9e
SG
267 if( hasALC_thread_local_context ) {
268 alExt.alcSetThreadContext(context);
269 } else {
270 alc.alcMakeContextCurrent(context);
271 }
272 final int alcErr = alc.alcGetError(null);
556d92b6 273 if( ALCConstants.ALC_NO_ERROR != alcErr ) {
c4368e89 274 final String err = getThreadName()+": ALCError "+toHexString(alcErr)+" while makeCurrent. "+this;
938fbc9e 275 System.err.println(err);
74fe4c34 276 ExceptionUtils.dumpStack(System.err);
938fbc9e
SG
277 lock.unlock();
278 throw new RuntimeException(err);
279 }
280 final int alErr = al.alGetError();
556d92b6 281 if( ALCConstants.ALC_NO_ERROR != alErr ) {
c4368e89
SG
282 if( DEBUG ) {
283 System.err.println(getThreadName()+": Prev - ALError "+toHexString(alErr)+" @ makeCurrent. "+this);
74fe4c34 284 ExceptionUtils.dumpStack(System.err);
c4368e89 285 }
938fbc9e 286 }
bc377663
SG
287 }
288 private final void unlockContext() {
938fbc9e
SG
289 if( hasALC_thread_local_context ) {
290 alExt.alcSetThreadContext(null);
291 } else {
292 alc.alcMakeContextCurrent(null);
293 }
bc377663
SG
294 lock.unlock();
295 }
296 private final void destroyContext() {
297 lock.lock();
298 try {
299 if( null != context ) {
300 try {
301 alc.alcDestroyContext(context);
556d92b6 302 } catch (final Throwable t) {
bc377663 303 if( DEBUG ) {
74fe4c34 304 ExceptionUtils.dumpThrowable("", t);
bc377663
SG
305 }
306 }
307 context = null;
308 }
309 // unroll lock !
310 while(lock.getHoldCount() > 1) {
311 lock.unlock();
312 }
313 } finally {
314 lock.unlock();
315 }
316 }
d42b236e 317
3bf56421 318 @Override
bc377663 319 public final String toString() {
da7210c6 320 final int alSrcName = null != alSource ? alSource[0] : 0;
deae6def 321 final int alBuffersLen = null != alBufferNames ? alBufferNames.length : 0;
d42b236e 322 final int ctxHash = context != null ? context.hashCode() : 0;
5b6112bf
SG
323 final int alFramesAvailSize = alFramesAvail != null ? alFramesAvail.size() : 0;
324 final int alFramesPlayingSize = alFramesPlaying != null ? alFramesPlaying.size() : 0;
bc377663 325 return "ALAudioSink[init "+initialized+", playRequested "+playRequested+", device "+deviceSpecifier+", ctx "+toHexString(ctxHash)+", alSource "+alSrcName+
e28a3b39
SG
326 ", chosen "+chosenFormat+
327 ", al[chan "+ALHelpers.alChannelLayoutName(alChannelLayout)+", type "+ALHelpers.alSampleTypeName(alSampleType)+
328 ", fmt "+toHexString(alFormat)+", soft "+hasSOFTBufferSamples+
5b6112bf
SG
329 "], playSpeed "+playSpeed+", buffers[total "+alBuffersLen+", avail "+alFramesAvailSize+", "+
330 "queued["+alFramesPlayingSize+", apts "+getPTS()+", "+getQueuedTime() + " ms, " + alBufferBytesQueued+" bytes], "+
517371b2 331 "queue[g "+frameGrowAmount+", l "+frameLimit+"]";
bc377663 332 }
d42b236e 333
938fbc9e
SG
334 private final String shortString() {
335 final int alSrcName = null != alSource ? alSource[0] : 0;
336 final int ctxHash = context != null ? context.hashCode() : 0;
337 return "[ctx "+toHexString(ctxHash)+", playReq "+playRequested+", alSrc "+alSrcName+
338 ", queued["+alFramesPlaying.size()+", " + alBufferBytesQueued+" bytes], "+
339 "queue[g "+frameGrowAmount+", l "+frameLimit+"]";
340 }
341
bc377663 342 public final String getPerfString() {
deae6def
SG
343 final int alBuffersLen = null != alBufferNames ? alBufferNames.length : 0;
344 return "Play [buffer "+alFramesPlaying.size()+"/"+alBuffersLen+", apts "+getPTS()+", "+getQueuedTime() + " ms, " + alBufferBytesQueued+" bytes]";
3bf56421 345 }
d42b236e 346
3bf56421 347 @Override
e28a3b39
SG
348 public final AudioFormat getPreferredFormat() {
349 if( !staticAvailable ) {
350 return null;
351 }
352 return preferredAudioFormat;
da7210c6 353 }
d42b236e 354
da7210c6 355 @Override
e28a3b39 356 public final int getMaxSupportedChannels() {
da7210c6 357 if( !staticAvailable ) {
e28a3b39 358 return 0;
da7210c6 359 }
e28a3b39
SG
360 return hasSOFTBufferSamples ? 8 : 2;
361 }
d42b236e 362
e28a3b39 363 @Override
556d92b6 364 public final boolean isSupported(final AudioFormat format) {
e28a3b39
SG
365 if( !staticAvailable ) {
366 return false;
367 }
368 if( format.planar || !format.littleEndian ) {
d42b236e 369 // FIXME big-endian supported w/ SOFT where it's native format!
e28a3b39
SG
370 return false;
371 }
372 final int alChannelLayout = ALHelpers.getDefaultALChannelLayout(format.channelCount);
556d92b6 373 if( ALConstants.AL_NONE != alChannelLayout ) {
e28a3b39 374 final int alSampleType = ALHelpers.getALSampleType(format.sampleSize, format.signed, format.fixedP);
556d92b6 375 if( ALConstants.AL_NONE != alSampleType ) {
e28a3b39 376 lockContext();
d42b236e 377 try {
e28a3b39 378 final int alFormat = ALHelpers.getALFormat(alChannelLayout, alSampleType, hasSOFTBufferSamples, al, alExt);
556d92b6 379 return ALConstants.AL_NONE != alFormat;
e28a3b39
SG
380 } finally {
381 unlockContext();
da7210c6 382 }
e28a3b39 383 }
da7210c6 384 }
e28a3b39
SG
385 return false;
386 }
d42b236e 387
e28a3b39 388 @Override
556d92b6 389 public final boolean init(final AudioFormat requestedFormat, final float frameDuration, final int initialQueueSize, final int queueGrowAmount, final int queueLimit) {
e28a3b39
SG
390 if( !staticAvailable ) {
391 return false;
392 }
393 alChannelLayout = ALHelpers.getDefaultALChannelLayout(requestedFormat.channelCount);
394 alSampleType = ALHelpers.getALSampleType(requestedFormat.sampleSize, requestedFormat.signed, requestedFormat.fixedP);
bc377663
SG
395 lockContext();
396 try {
556d92b6 397 if( ALConstants.AL_NONE != alChannelLayout && ALConstants.AL_NONE != alSampleType ) {
e28a3b39
SG
398 alFormat = ALHelpers.getALFormat(alChannelLayout, alSampleType, hasSOFTBufferSamples, al, alExt);
399 } else {
556d92b6 400 alFormat = ALConstants.AL_NONE;
e28a3b39 401 }
556d92b6 402 if( ALConstants.AL_NONE == alFormat ) {
e28a3b39
SG
403 // not supported
404 return false;
405 }
bc377663
SG
406 // Allocate buffers
407 destroyBuffers();
408 {
d42b236e 409 final float useFrameDuration = frameDuration > 1f ? frameDuration : AudioSink.DefaultFrameDuration;
517371b2
SG
410 final int initialFrameCount = requestedFormat.getFrameCount(
411 initialQueueSize > 0 ? initialQueueSize : AudioSink.DefaultInitialQueueSize, useFrameDuration);
d42b236e 412 // frameDuration, int initialQueueSize, int queueGrowAmount, int queueLimit) {
deae6def
SG
413 alBufferNames = new int[initialFrameCount];
414 al.alGenBuffers(initialFrameCount, alBufferNames, 0);
bc377663 415 final int err = al.alGetError();
556d92b6 416 if( ALConstants.AL_NO_ERROR != err ) {
deae6def 417 alBufferNames = null;
c4368e89 418 throw new RuntimeException(getThreadName()+": ALAudioSink: Error generating Buffers: 0x"+Integer.toHexString(err));
bc377663 419 }
deae6def 420 final ALAudioFrame[] alFrames = new ALAudioFrame[initialFrameCount];
f18a94b3 421 for(int i=0; i<initialFrameCount; i++) {
deae6def 422 alFrames[i] = new ALAudioFrame(alBufferNames[i]);
bc377663 423 }
d42b236e 424
deae6def
SG
425 alFramesAvail = new LFRingbuffer<ALAudioFrame>(alFrames);
426 alFramesPlaying = new LFRingbuffer<ALAudioFrame>(ALAudioFrame[].class, initialFrameCount);
517371b2
SG
427 this.frameGrowAmount = requestedFormat.getFrameCount(
428 queueGrowAmount > 0 ? queueGrowAmount : AudioSink.DefaultQueueGrowAmount, useFrameDuration);
429 this.frameLimit = requestedFormat.getFrameCount(
430 queueLimit > 0 ? queueLimit : AudioSink.DefaultQueueLimitWithVideo, useFrameDuration);
938fbc9e
SG
431 if( DEBUG_TRACE ) {
432 alFramesAvail.dump(System.err, "Avail-init");
433 alFramesPlaying.dump(System.err, "Playi-init");
434 }
da7210c6 435 }
bc377663
SG
436 } finally {
437 unlockContext();
da7210c6 438 }
d42b236e 439
da7210c6 440 chosenFormat = requestedFormat;
e28a3b39 441 return true;
da7210c6 442 }
d42b236e 443
2d50663d
SG
444 @Override
445 public final AudioFormat getChosenFormat() {
446 return chosenFormat;
447 }
448
556d92b6 449 private static int[] concat(final int[] first, final int[] second) {
deae6def
SG
450 final int[] result = Arrays.copyOf(first, first.length + second.length);
451 System.arraycopy(second, 0, result, first.length, second.length);
452 return result;
453 }
454 /**
455 private static <T> T[] concat(T[] first, T[] second) {
456 final T[] result = Arrays.copyOf(first, first.length + second.length);
457 System.arraycopy(second, 0, result, first.length, second.length);
458 return result;
459 } */
d42b236e 460
f18a94b3 461 private boolean growBuffers() {
deae6def
SG
462 if( !alFramesAvail.isEmpty() || !alFramesPlaying.isFull() ) {
463 throw new InternalError("Buffers: Avail is !empty "+alFramesAvail+" or Playing is !full "+alFramesPlaying);
f18a94b3 464 }
deae6def 465 if( alFramesAvail.capacity() >= frameLimit || alFramesPlaying.capacity() >= frameLimit ) {
f18a94b3 466 if( DEBUG ) {
deae6def 467 System.err.println(getThreadName()+": ALAudioSink.growBuffers: Frame limit "+frameLimit+" reached: Avail "+alFramesAvail+", Playing "+alFramesPlaying);
f18a94b3
SG
468 }
469 return false;
470 }
d42b236e 471
deae6def
SG
472 final int[] newALBufferNames = new int[frameGrowAmount];
473 al.alGenBuffers(frameGrowAmount, newALBufferNames, 0);
f18a94b3 474 final int err = al.alGetError();
556d92b6 475 if( ALConstants.AL_NO_ERROR != err ) {
f18a94b3
SG
476 if( DEBUG ) {
477 System.err.println(getThreadName()+": ALAudioSink.growBuffers: Error generating "+frameGrowAmount+" new Buffers: 0x"+Integer.toHexString(err));
478 }
479 return false;
480 }
deae6def 481 alBufferNames = concat(alBufferNames, newALBufferNames);
d42b236e 482
deae6def 483 final ALAudioFrame[] newALBuffers = new ALAudioFrame[frameGrowAmount];
f18a94b3 484 for(int i=0; i<frameGrowAmount; i++) {
deae6def 485 newALBuffers[i] = new ALAudioFrame(newALBufferNames[i]);
f18a94b3 486 }
deae6def 487 // alFrames = concat(alFrames , newALBuffers);
f18a94b3 488
deae6def
SG
489 alFramesAvail.growEmptyBuffer(newALBuffers);
490 alFramesPlaying.growFullBuffer(frameGrowAmount);
491 if( alFramesAvail.isEmpty() || alFramesPlaying.isFull() ) {
492 throw new InternalError("Buffers: Avail is empty "+alFramesAvail+" or Playing is full "+alFramesPlaying);
f18a94b3
SG
493 }
494 if( DEBUG ) {
deae6def 495 System.err.println(getThreadName()+": ALAudioSink: Buffer grown "+frameGrowAmount+": Avail "+alFramesAvail+", playing "+alFramesPlaying);
f18a94b3 496 }
938fbc9e
SG
497 if( DEBUG_TRACE ) {
498 alFramesAvail.dump(System.err, "Avail-grow");
499 alFramesPlaying.dump(System.err, "Playi-grow");
500 }
f18a94b3
SG
501 return true;
502 }
d42b236e 503
da7210c6
SG
504 private void destroyBuffers() {
505 if( !staticAvailable ) {
506 return;
507 }
deae6def 508 if( null != alBufferNames ) {
da7210c6 509 try {
deae6def 510 al.alDeleteBuffers(alBufferNames.length, alBufferNames, 0);
556d92b6 511 } catch (final Throwable t) {
da7210c6 512 if( DEBUG ) {
43c85971 513 System.err.println("Caught "+t.getClass().getName()+": "+t.getMessage());
da7210c6 514 t.printStackTrace();
16d446b7 515 }
16d446b7 516 }
deae6def
SG
517 alFramesAvail.clear();
518 alFramesAvail = null;
519 alFramesPlaying.clear();
520 alFramesPlaying = null;
da7210c6 521 alBufferBytesQueued = 0;
deae6def
SG
522 // alFrames = null;
523 alBufferNames = null;
da7210c6
SG
524 }
525 }
d42b236e 526
da7210c6 527 @Override
bc377663 528 public final void destroy() {
da7210c6
SG
529 initialized = false;
530 if( !staticAvailable ) {
531 return;
532 }
da7210c6 533 if( null != context ) {
bc377663
SG
534 lockContext();
535 }
536 try {
c4368e89 537 stopImpl(true);
bc377663
SG
538 if( null != alSource ) {
539 try {
540 al.alDeleteSources(1, alSource, 0);
556d92b6 541 } catch (final Throwable t) {
bc377663 542 if( DEBUG ) {
43c85971 543 System.err.println("Caught "+t.getClass().getName()+": "+t.getMessage());
bc377663
SG
544 t.printStackTrace();
545 }
da7210c6 546 }
bc377663 547 alSource = null;
da7210c6 548 }
d42b236e 549
bc377663
SG
550 destroyBuffers();
551 } finally {
552 destroyContext();
da7210c6
SG
553 }
554 if( null != device ) {
555 try {
556 alc.alcCloseDevice(device);
556d92b6 557 } catch (final Throwable t) {
da7210c6 558 if( DEBUG ) {
43c85971 559 System.err.println("Caught "+t.getClass().getName()+": "+t.getMessage());
da7210c6
SG
560 t.printStackTrace();
561 }
16d446b7 562 }
d42b236e 563 device = null;
16d446b7 564 }
da7210c6
SG
565 chosenFormat = null;
566 }
d42b236e 567
da7210c6 568 @Override
bc377663 569 public final boolean isInitialized() {
da7210c6 570 return initialized;
3bf56421 571 }
d42b236e 572
938fbc9e 573 private final int dequeueBuffer(final boolean wait, final boolean ignoreBufferInconsistency) {
556d92b6 574 int alErr = ALConstants.AL_NO_ERROR;
bc377663 575 final int releaseBufferCount;
d42b236e 576 if( alBufferBytesQueued > 0 ) {
deae6def 577 final int releaseBufferLimes = Math.max(1, alFramesPlaying.size() / 4 );
bc377663
SG
578 final int[] val=new int[1];
579 int i=0;
580 do {
556d92b6 581 al.alGetSourcei(alSource[0], ALConstants.AL_BUFFERS_PROCESSED, val, 0);
bc377663 582 alErr = al.alGetError();
556d92b6 583 if( ALConstants.AL_NO_ERROR != alErr ) {
c4368e89 584 throw new RuntimeException(getThreadName()+": ALError "+toHexString(alErr)+" while quering processed buffers at source. "+this);
da7210c6 585 }
f18a94b3 586 if( wait && val[0] < releaseBufferLimes ) {
bc377663 587 i++;
f18a94b3 588 // clip wait at [2 .. 100] ms
517371b2 589 final int avgBufferDura = chosenFormat.getBytesDuration( alBufferBytesQueued / alFramesPlaying.size() );
f18a94b3 590 final int sleep = Math.max(2, Math.min(100, releaseBufferLimes * avgBufferDura));
8a032a2c 591 if( DEBUG ) {
556d92b6 592 System.err.println(getThreadName()+": ALAudioSink: Dequeue.wait["+i+"]: avgBufferDura "+avgBufferDura+", releaseBufferLimes "+releaseBufferLimes+", sleep "+sleep+" ms, playImpl "+(ALConstants.AL_PLAYING == getSourceState(false))+", processed "+val[0]+", "+this);
d42b236e 593 }
bc377663
SG
594 unlockContext();
595 try {
f18a94b3 596 Thread.sleep( sleep - 1 );
556d92b6 597 } catch (final InterruptedException e) {
bc377663
SG
598 } finally {
599 lockContext();
600 }
601 }
f18a94b3 602 } while ( wait && val[0] < releaseBufferLimes && alBufferBytesQueued > 0 );
bc377663
SG
603 releaseBufferCount = val[0];
604 } else {
605 releaseBufferCount = 0;
606 }
3bf56421 607
bc377663 608 if( releaseBufferCount > 0 ) {
938fbc9e 609 final int[] buffers = new int[releaseBufferCount];
bc377663 610 al.alSourceUnqueueBuffers(alSource[0], releaseBufferCount, buffers, 0);
da7210c6 611 alErr = al.alGetError();
556d92b6 612 if( ALConstants.AL_NO_ERROR != alErr ) {
c4368e89 613 throw new RuntimeException(getThreadName()+": ALError "+toHexString(alErr)+" while dequeueing "+releaseBufferCount+" buffers. "+this);
da7210c6 614 }
bc377663 615 for ( int i=0; i<releaseBufferCount; i++ ) {
deae6def 616 final ALAudioFrame releasedBuffer = alFramesPlaying.get();
da7210c6 617 if( null == releasedBuffer ) {
938fbc9e
SG
618 if( !ignoreBufferInconsistency ) {
619 throw new InternalError("Internal Error: "+this);
620 }
621 } else {
622 if(DEBUG_TRACE) {
623 System.err.println("< [al "+buffers[i]+", q "+releasedBuffer.alBuffer+"] <- "+shortString()+" @ "+getThreadName());
624 }
625 if( releasedBuffer.alBuffer != buffers[i] ) {
626 if( !ignoreBufferInconsistency ) {
627 alFramesAvail.dump(System.err, "Avail-deq02-post");
628 alFramesPlaying.dump(System.err, "Playi-deq02-post");
629 throw new InternalError("Buffer name mismatch: dequeued: "+buffers[i]+", released "+releasedBuffer+", "+this);
630 }
631 }
96d530e7
SG
632 alBufferBytesQueued -= releasedBuffer.getByteSize();
633 if( !alFramesAvail.put(releasedBuffer) ) {
634 throw new InternalError("Internal Error: "+this);
635 }
636 if(DEBUG_TRACE) {
637 System.err.println("<< [al "+buffers[i]+", q "+releasedBuffer.alBuffer+"] <- "+shortString()+" @ "+getThreadName());
638 }
938fbc9e 639 }
da7210c6
SG
640 }
641 }
bc377663 642 return releaseBufferCount;
da7210c6 643 }
d42b236e 644 private final void dequeueForceAll() {
938fbc9e
SG
645 if(DEBUG_TRACE) {
646 System.err.println("< _FLUSH_ <- "+shortString()+" @ "+getThreadName());
647 }
648 final int[] val=new int[1];
556d92b6 649 al.alSourcei(alSource[0], ALConstants.AL_BUFFER, 0); // explicit force zero buffer!
938fbc9e 650 if(DEBUG_TRACE) {
556d92b6 651 al.alGetSourcei(alSource[0], ALConstants.AL_BUFFERS_PROCESSED, val, 0);
938fbc9e
SG
652 }
653 final int alErr = al.alGetError();
d42b236e
SG
654 while ( !alFramesPlaying.isEmpty() ) {
655 final ALAudioFrame releasedBuffer = alFramesPlaying.get();
656 if( null == releasedBuffer ) {
657 throw new InternalError("Internal Error: "+this);
658 }
659 alBufferBytesQueued -= releasedBuffer.getByteSize();
660 if( !alFramesAvail.put(releasedBuffer) ) {
661 throw new InternalError("Internal Error: "+this);
662 }
663 }
938fbc9e
SG
664 alBufferBytesQueued = 0;
665 if(DEBUG_TRACE) {
666 System.err.println("<< _FLUSH_ [al "+val[0]+", err "+toHexString(alErr)+"] <- "+shortString()+" @ "+getThreadName());
74fe4c34 667 ExceptionUtils.dumpStack(System.err);
d42b236e
SG
668 }
669 }
670
556d92b6 671 private final int dequeueBuffer(final boolean wait, final int inPTS, final int inDuration) {
938fbc9e 672 final int dequeuedBufferCount = dequeueBuffer( wait, false /* ignoreBufferInconsistency */ );
deae6def 673 final ALAudioFrame currentBuffer = alFramesPlaying.peek();
f18a94b3 674 if( null != currentBuffer ) {
deae6def 675 playingPTS = currentBuffer.getPTS();
f18a94b3 676 } else {
deae6def 677 playingPTS = inPTS;
f18a94b3
SG
678 }
679 if( DEBUG ) {
680 if( dequeuedBufferCount > 0 ) {
deae6def 681 System.err.println(getThreadName()+": ALAudioSink: Write "+inPTS+", "+inDuration+" ms, dequeued "+dequeuedBufferCount+", wait "+wait+", "+getPerfString());
f18a94b3
SG
682 }
683 }
684 return dequeuedBufferCount;
685 }
d42b236e 686
3bf56421 687 @Override
556d92b6 688 public final AudioFrame enqueueData(final int pts, final ByteBuffer bytes, final int byteCount) {
da7210c6 689 if( !initialized || null == chosenFormat ) {
deae6def 690 return null;
da7210c6 691 }
deae6def 692 final ALAudioFrame alFrame;
d42b236e 693
da7210c6
SG
694 // OpenAL consumes buffers in the background
695 // we first need to initialize the OpenAL buffers then
696 // start continuous playback.
bc377663
SG
697 lockContext();
698 try {
517371b2 699 final int duration = chosenFormat.getBytesDuration(byteCount);
f18a94b3 700 final boolean dequeueDone;
deae6def 701 if( alFramesAvail.isEmpty() ) {
f18a94b3 702 // try to dequeue first
deae6def
SG
703 dequeueDone = dequeueBuffer(false, pts, duration) > 0;
704 if( alFramesAvail.isEmpty() ) {
f18a94b3
SG
705 // try to grow
706 growBuffers();
bc377663 707 }
f18a94b3
SG
708 } else {
709 dequeueDone = false;
710 }
deae6def
SG
711 if( !dequeueDone && alFramesPlaying.size() > 0 ) { // dequeue only possible if playing ..
712 final boolean wait = isPlayingImpl0() && alFramesAvail.isEmpty(); // possible if grow failed or already exceeds it's limit!
713 dequeueBuffer(wait, pts, duration);
bc377663 714 }
d42b236e 715
deae6def
SG
716 alFrame = alFramesAvail.get();
717 if( null == alFrame ) {
718 alFramesAvail.dump(System.err, "Avail");
719 throw new InternalError("Internal Error: avail.get null "+alFramesAvail+", "+this);
bc377663 720 }
deae6def
SG
721 alFrame.setPTS(pts);
722 alFrame.setDuration(duration);
723 alFrame.setByteSize(byteCount);
724 if( !alFramesPlaying.put( alFrame ) ) {
bc377663
SG
725 throw new InternalError("Internal Error: "+this);
726 }
938fbc9e 727 final int[] alBufferNames = new int[] { alFrame.alBuffer };
e28a3b39
SG
728 if( hasSOFTBufferSamples ) {
729 final int samplesPerChannel = chosenFormat.getBytesSampleCount(byteCount) / chosenFormat.channelCount;
730 // final int samplesPerChannel = ALHelpers.bytesToSampleCount(byteCount, alChannelLayout, alSampleType);
731 alExt.alBufferSamplesSOFT(alFrame.alBuffer, chosenFormat.sampleRate, alFormat,
732 samplesPerChannel, alChannelLayout, alSampleType, bytes);
733 } else {
734 al.alBufferData(alFrame.alBuffer, alFormat, bytes, byteCount, chosenFormat.sampleRate);
735 }
d42b236e 736
938fbc9e
SG
737 if(DEBUG_TRACE) {
738 System.err.println("> "+alFrame.alBuffer+" -> "+shortString()+" @ "+getThreadName());
739 }
740
bc377663 741 al.alSourceQueueBuffers(alSource[0], 1, alBufferNames, 0);
938fbc9e 742 final int alErr = al.alGetError();
556d92b6 743 if( ALConstants.AL_NO_ERROR != alErr ) {
c4368e89 744 throw new RuntimeException(getThreadName()+": ALError "+toHexString(alErr)+" while queueing buffer "+toHexString(alBufferNames[0])+". "+this);
bc377663 745 }
deae6def 746 alBufferBytesQueued += byteCount;
7d0c81f2 747 enqueuedFrameCount++; // safe: only written-to while locked!
d42b236e 748
938fbc9e
SG
749 if(DEBUG_TRACE) {
750 System.err.println(">> "+alFrame.alBuffer+" -> "+shortString()+" @ "+getThreadName());
751 }
752
bc377663
SG
753 playImpl(); // continue playing, fixes issue where we ran out of enqueued data!
754 } finally {
755 unlockContext();
da7210c6 756 }
deae6def 757 return alFrame;
bc377663
SG
758 }
759
760 @Override
761 public final boolean isPlaying() {
762 if( !initialized || null == chosenFormat ) {
763 return false;
da7210c6 764 }
bc377663
SG
765 if( playRequested ) {
766 lockContext();
767 try {
768 return isPlayingImpl0();
769 } finally {
770 unlockContext();
d42b236e 771 }
bc377663
SG
772 } else {
773 return false;
da7210c6 774 }
bc377663
SG
775 }
776 private final boolean isPlayingImpl0() {
777 if( playRequested ) {
556d92b6 778 return ALConstants.AL_PLAYING == getSourceState(false);
bc377663
SG
779 } else {
780 return false;
da7210c6 781 }
bc377663 782 }
556d92b6 783 private final int getSourceState(final boolean ignoreError) {
bc377663 784 final int[] val = new int[1];
556d92b6 785 al.alGetSourcei(alSource[0], ALConstants.AL_SOURCE_STATE, val, 0);
bc377663 786 final int alErr = al.alGetError();
556d92b6 787 if( ALConstants.AL_NO_ERROR != alErr ) {
c4368e89
SG
788 final String msg = getThreadName()+": ALError "+toHexString(alErr)+" while querying SOURCE_STATE. "+this;
789 if( ignoreError ) {
790 if( DEBUG ) {
791 System.err.println(msg);
792 }
793 } else {
794 throw new RuntimeException(msg);
795 }
da7210c6 796 }
d42b236e 797 return val[0];
bc377663 798 }
d42b236e 799
bc377663
SG
800 @Override
801 public final void play() {
802 if( !initialized || null == chosenFormat ) {
803 return;
804 }
805 playRequested = true;
806 lockContext();
807 try {
808 playImpl();
809 if( DEBUG ) {
556d92b6 810 System.err.println(getThreadName()+": ALAudioSink: PLAY playImpl "+(ALConstants.AL_PLAYING == getSourceState(false))+", "+this);
d42b236e 811 }
bc377663
SG
812 } finally {
813 unlockContext();
d42b236e 814 }
bc377663
SG
815 }
816 private final void playImpl() {
556d92b6 817 if( playRequested && ALConstants.AL_PLAYING != getSourceState(false) ) {
bc377663
SG
818 al.alSourcePlay(alSource[0]);
819 final int alErr = al.alGetError();
556d92b6 820 if( ALConstants.AL_NO_ERROR != alErr ) {
c4368e89 821 throw new RuntimeException(getThreadName()+": ALError "+toHexString(alErr)+" while start playing. "+this);
bc377663 822 }
d42b236e 823 }
bc377663 824 }
d42b236e 825
bc377663
SG
826 @Override
827 public final void pause() {
828 if( !initialized || null == chosenFormat ) {
829 return;
830 }
831 if( playRequested ) {
832 lockContext();
833 try {
834 pauseImpl();
da7210c6 835 if( DEBUG ) {
556d92b6 836 System.err.println(getThreadName()+": ALAudioSink: PAUSE playImpl "+(ALConstants.AL_PLAYING == getSourceState(false))+", "+this);
d42b236e 837 }
bc377663
SG
838 } finally {
839 unlockContext();
da7210c6
SG
840 }
841 }
3bf56421 842 }
bc377663
SG
843 private final void pauseImpl() {
844 if( isPlayingImpl0() ) {
845 playRequested = false;
846 al.alSourcePause(alSource[0]);
847 final int alErr = al.alGetError();
556d92b6 848 if( ALConstants.AL_NO_ERROR != alErr ) {
c4368e89 849 throw new RuntimeException(getThreadName()+": ALError "+toHexString(alErr)+" while pausing. "+this);
bc377663
SG
850 }
851 }
852 }
556d92b6
SG
853 private final void stopImpl(final boolean ignoreError) {
854 if( ALConstants.AL_STOPPED != getSourceState(ignoreError) ) {
bc377663
SG
855 playRequested = false;
856 al.alSourceStop(alSource[0]);
857 final int alErr = al.alGetError();
556d92b6 858 if( ALConstants.AL_NO_ERROR != alErr ) {
c4368e89
SG
859 final String msg = "ALError "+toHexString(alErr)+" while stopping. "+this;
860 if( ignoreError ) {
861 if( DEBUG ) {
862 System.err.println(getThreadName()+": "+msg);
863 }
864 } else {
865 throw new RuntimeException(getThreadName()+": ALError "+toHexString(alErr)+" while stopping. "+this);
866 }
bc377663
SG
867 }
868 }
869 }
d42b236e 870
bc377663
SG
871 @Override
872 public final float getPlaySpeed() { return playSpeed; }
d42b236e 873
bc377663 874 @Override
d42b236e 875 public final boolean setPlaySpeed(float rate) {
bc377663
SG
876 if( !initialized || null == chosenFormat ) {
877 return false;
878 }
879 lockContext();
880 try {
881 if( Math.abs(1.0f - rate) < 0.01f ) {
882 rate = 1.0f;
883 }
d42b236e 884 if( 0.5f <= rate && rate <= 2.0f ) { // OpenAL limits
bc377663 885 playSpeed = rate;
556d92b6 886 al.alSourcef(alSource[0], ALConstants.AL_PITCH, playSpeed);
bc377663 887 return true;
d42b236e 888 }
bc377663
SG
889 } finally {
890 unlockContext();
8cdbfb67 891 }
d42b236e 892 return false;
8cdbfb67 893 }
d42b236e 894
8cdbfb67
SG
895 @Override
896 public final float getVolume() {
d42b236e 897 return volume;
8cdbfb67 898 }
d42b236e 899
8cdbfb67
SG
900 @Override
901 public final boolean setVolume(float v) {
902 if( !initialized || null == chosenFormat ) {
903 return false;
904 }
905 lockContext();
906 try {
907 if( Math.abs(v) < 0.01f ) {
908 v = 0.0f;
909 } else if( Math.abs(1.0f - v) < 0.01f ) {
910 v = 1.0f;
911 }
d42b236e 912 if( 0.0f <= v && v <= 1.0f ) { // OpenAL limits
8cdbfb67 913 volume = v;
556d92b6 914 al.alSourcef(alSource[0], ALConstants.AL_GAIN, v);
8cdbfb67 915 return true;
d42b236e 916 }
8cdbfb67
SG
917 } finally {
918 unlockContext();
bc377663 919 }
d42b236e 920 return false;
bc377663 921 }
d42b236e 922
bc377663
SG
923 @Override
924 public final void flush() {
925 if( !initialized || null == chosenFormat ) {
926 return;
927 }
928 lockContext();
929 try {
930 // pauseImpl();
c4368e89 931 stopImpl(false);
938fbc9e 932 // Redundant: dequeueBuffer( false /* wait */, true /* ignoreBufferInconsistency */);
d42b236e 933 dequeueForceAll();
deae6def 934 if( alBufferNames.length != alFramesAvail.size() || alFramesPlaying.size() != 0 ) {
bc377663
SG
935 throw new InternalError("XXX: "+this);
936 }
937 if( DEBUG ) {
556d92b6 938 System.err.println(getThreadName()+": ALAudioSink: FLUSH playImpl "+(ALConstants.AL_PLAYING == getSourceState(false))+", "+this);
d42b236e 939 }
bc377663
SG
940 } finally {
941 unlockContext();
d42b236e 942 }
bc377663 943 }
d42b236e 944
6509c313 945 @Override
bc377663
SG
946 public final int getEnqueuedFrameCount() {
947 return enqueuedFrameCount;
948 }
d42b236e 949
bc377663
SG
950 @Override
951 public final int getFrameCount() {
deae6def 952 return null != alBufferNames ? alBufferNames.length : 0;
bc377663 953 }
d42b236e 954
bc377663
SG
955 @Override
956 public final int getQueuedFrameCount() {
da7210c6
SG
957 if( !initialized || null == chosenFormat ) {
958 return 0;
959 }
deae6def 960 return alFramesPlaying.size();
da7210c6 961 }
d42b236e 962
da7210c6 963 @Override
bc377663 964 public final int getFreeFrameCount() {
da7210c6
SG
965 if( !initialized || null == chosenFormat ) {
966 return 0;
967 }
deae6def 968 return alFramesAvail.size();
3bf56421 969 }
d42b236e 970
da7210c6 971 @Override
bc377663 972 public final int getQueuedByteCount() {
da7210c6
SG
973 if( !initialized || null == chosenFormat ) {
974 return 0;
975 }
bc377663 976 return alBufferBytesQueued;
da7210c6 977 }
d42b236e 978
da7210c6 979 @Override
bc377663
SG
980 public final int getQueuedTime() {
981 if( !initialized || null == chosenFormat ) {
982 return 0;
983 }
517371b2 984 return chosenFormat.getBytesDuration(alBufferBytesQueued);
da7210c6 985 }
d42b236e 986
bc377663 987 @Override
c200045a 988 public final int getPTS() { return playingPTS; }
d42b236e 989
556d92b6 990 private static final String toHexString(final int v) { return "0x"+Integer.toHexString(v); }
d42b236e 991 private static final String getThreadName() { return Thread.currentThread().getName(); }
3bf56421 992}
http://JogAmp.org git info: FAQ, tutorial and man pages.