JOAL v2.6.0-rc-20250712
JOAL, OpenAL® API Binding for Java™ (public API).
ALAudioSink.java
Go to the documentation of this file.
1/**
2 * Copyright 2013-2023 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:
6 *
7 * 1. Redistributions of source code must retain the above copyright notice, this list of
8 * conditions and the following disclaimer.
9 *
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.
13 *
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.
23 *
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 */
28package com.jogamp.openal.util;
29
30import java.nio.ByteBuffer;
31
32import jogamp.openal.Debug;
33
34import com.jogamp.common.ExceptionUtils;
35import com.jogamp.common.av.AudioFormat;
36import com.jogamp.common.av.AudioSink;
37import com.jogamp.common.av.AudioSink.AudioFrame;
38import com.jogamp.common.av.PTS;
39import com.jogamp.common.av.TimeFrameI;
40import com.jogamp.common.os.Clock;
41import com.jogamp.common.util.LFRingbuffer;
42import com.jogamp.common.util.PropertyAccess;
43import com.jogamp.common.util.Ringbuffer;
44import com.jogamp.common.util.TSPrinter;
45import com.jogamp.openal.AL;
46import com.jogamp.openal.ALC;
47import com.jogamp.openal.ALCConstants;
48import com.jogamp.openal.ALCcontext;
49import com.jogamp.openal.ALCdevice;
50import com.jogamp.openal.ALConstants;
51import com.jogamp.openal.ALException;
52import com.jogamp.openal.ALExt;
53import com.jogamp.openal.ALExt.ALEVENTPROCSOFT;
54import com.jogamp.openal.ALExtConstants;
55import com.jogamp.openal.sound3d.AudioSystem3D;
56import com.jogamp.openal.sound3d.Context;
57import com.jogamp.openal.sound3d.Device;
58import com.jogamp.openal.sound3d.Source;
59
60/***
61 * OpenAL {@link AudioSink} implementation.
62 * <p>
63 * Besides given {@link AudioSink} functionality, implementation is fully functional regarding {@link AudioFormat} and all OpenAL parameter.<br/>
64 * <ul>
65 * <li>All OpenAL parameter can be queried</li>
66 * <li>Instance can be constructed with an OpenAL device and context, see {@link #ALAudioSink(ALCdevice, ALCcontext)}</li>
67 * <li>Initialization can be performed with OpenAL paramters, see {@link #init(int, int, int, int, int, float, int, int, int)}</li>
68 * </ul>
69 * </p>
70 */
71public final class ALAudioSink implements AudioSink {
72 private static final boolean DEBUG_TRACE;
73 private static final TSPrinter logout;
74 private static final ALC alc;
75 private static final AL al;
76 private static final ALExt alExt;
77 private static final boolean staticsInitialized;
78
79 private final Device device;
80 private boolean hasSOFTBufferSamples;
81 private boolean hasEXTMcFormats;
82 private boolean hasEXTFloat32;
83 private boolean hasEXTDouble;
84 private boolean hasALC_thread_local_context;
85 private boolean hasAL_SOFT_events;
86 private boolean useAL_SOFT_events;
87 private int sourceCount;
88 /** default latency in [s] */
89 private float defaultLatency;
90 /** latency in [s] */
91 private float latency;
92 private final AudioFormat nativeFormat;
93 private int userMaxChannels = 8;
94 private AudioFormat preferredFormat;
95 private final Context context;
96
97 /** Playback speed, range [0.5 - 2.0], default 1.0. */
98 private float playSpeed = 1.0f;
99 private float volume = 1.0f;
100
101 static class ALAudioFrame extends AudioFrame {
102 private final int alBuffer;
103
104 ALAudioFrame(final int alBuffer) {
105 this.alBuffer = alBuffer;
106 }
107 public ALAudioFrame(final int alBuffer, final int pts, final int duration, final int dataSize) {
108 super(pts, duration, dataSize);
109 this.alBuffer = alBuffer;
110 }
111
112 /** Get this frame's OpenAL buffer name */
113 public final int getALBuffer() { return alBuffer; }
114
115 @Override
116 public String toString() {
117 return "ALAudioFrame[pts " + pts + " ms, l " + duration + " ms, " + byteSize + " bytes, buffer "+alBuffer+"]";
118 }
119 }
120
121 private int[] alBufferNames = null;
122 /** queue limit in [ms] */
123 private int queueSize = 0;
124 /** average frame duration in [s], initialized with latency */
125 private float avgFrameDuration = 0f;
126
127 private Ringbuffer<ALAudioFrame> alFramesFree = null;
128 private Ringbuffer<ALAudioFrame> alFramesPlaying = null;
129 private volatile int alBufferBytesQueued = 0;
130 private volatile int last_buffered_pts = TimeFrameI.INVALID_PTS;
131 private volatile boolean playRequested = false;
132 private final PTS pts = new PTS( () -> { return playRequested ? playSpeed : 0f; } );
133 private volatile int enqueuedFrameCount;
134
135 private final Source alSource = new Source();
136 private AudioFormat chosenFormat;
137 private int alChannelLayout;
138 private int alSampleType;
139 private int alFormat;
140 private volatile boolean available;
141
142
143 static {
144 Debug.initSingleton();
145 DEBUG_TRACE = PropertyAccess.isPropertyDefined("joal.debug.AudioSink.trace", true);
146 if( DEBUG || DEBUG_TRACE ) {
147 logout = TSPrinter.stderr();
148 } else {
149 logout = null;
150 }
151 alc = AudioSystem3D.getALC();
152 al = AudioSystem3D.getAL();
153 alExt = AudioSystem3D.getALExt();
154 staticsInitialized = AudioSystem3D.isAvailable();
155 }
156
157 /** Returns true if OpenAL has been loaded and static fields {@link ALC}, {@link AL} and {@link ALExt} have been initialized successfully, otherwise false. */
158 public static boolean isInitialized() {
159 return staticsInitialized;
160 }
161
162 private static Device createDevice(final String name) {
163 final Device d = new Device(name);
164 if( !d.isValid() ) {
165 throw new ALException(getThreadName()+": ALAudioSink: Error opening OpenAL device '"+name+"'");
166 }
167 return d;
168 }
169
170 /**
171 * Create a new instance with a new default {@link ALCdevice}
172 * @throws ALException if the default {@link ALCdevice} couldn't be fully created including its context.
173 */
174 public ALAudioSink() throws ALException {
175 this((Device)null);
176 }
177
178 /**
179 * Create a new instance with a new named {@link ALCdevice}
180 * @param deviceName name of
181 * @throws ALException if the default {@link ALCdevice} couldn't be fully created including its context.
182 */
183 public ALAudioSink(final String deviceName) throws ALException {
184 this(createDevice(deviceName));
185 }
186
187 /**
188 * Create a new instance with an optional given {@link ALCdevice}
189 *
190 * @param alDevice optional OpenAL {@link Device}, a default device is opened if null.
191 * @throws ALException if the default {@link ALCdevice} couldn't be fully created including its context.
192 */
193 public ALAudioSink(final Device alDevice) throws ALException {
194 available = false;
195 chosenFormat = null;
196
197 if( !staticsInitialized ) {
198 device = null;
199 context = null;
200 nativeFormat = DefaultFormat;
201 return;
202 }
203 if( null == alDevice ) {
204 device = createDevice(null); // default device
205 if( !device.isValid() ) {
206 throw new ALException(getThreadName()+": ALAudioSink: Couldn't open default device: "+device);
207 }
208 } else {
209 device = alDevice;
210 if( !device.open() ) {
211 throw new ALException(getThreadName()+": ALAudioSink: Error device not open or couldn't be opened "+device);
212 }
213 }
214 // Create audio context.
215 context = new Context(device, null);
216 if ( !context.isValid() ) {
217 throw new ALException(getThreadName()+": ALAudioSink: Error creating OpenAL context "+context);
218 }
219
220 makeCurrent(true /* throw */);
221 try {
222 hasSOFTBufferSamples = al.alIsExtensionPresent(ALHelpers.AL_SOFT_buffer_samples);
223 hasEXTMcFormats = al.alIsExtensionPresent(ALHelpers.AL_EXT_MCFORMATS);
226 hasALC_thread_local_context = context.hasALC_thread_local_context;
227 hasAL_SOFT_events = al.alIsExtensionPresent(ALHelpers.AL_SOFT_events);
228 useAL_SOFT_events = hasAL_SOFT_events;
229
230 int checkErrIter = 1;
231 AudioSystem3D.checkError(device, "init."+checkErrIter++, DEBUG, false);
232 int defaultSampleRate = DefaultFormat.sampleRate;
233 {
234 final int[] value = { 0 };
235 alc.alcGetIntegerv(device.getALDevice(), ALCConstants.ALC_FREQUENCY, 1, value, 0);
236 if( AudioSystem3D.checkError(device, "read ALC_FREQUENCY", DEBUG, false) || 0 == value[0] ) {
237 if( DEBUG ) {
238 logout.println("ALAudioSink.queryDefaultSampleRate: failed, using default "+defaultSampleRate);
239 }
240 } else {
241 defaultSampleRate = value[0];
242 if( DEBUG ) {
243 logout.println("ALAudioSink.queryDefaultSampleRate: OK "+defaultSampleRate);
244 }
245 }
246 value[0] = 0;
247 alc.alcGetIntegerv(device.getALDevice(), ALCConstants.ALC_MONO_SOURCES, 1, value, 0);
248 if( AudioSystem3D.checkError(device, "read ALC_MONO_SOURCES", DEBUG, false) ) {
249 sourceCount = -1;
250 if( DEBUG ) {
251 logout.println("ALAudioSink.queryMonoSourceCount: failed");
252 }
253 } else {
254 sourceCount = value[0];
255 }
256 value[0] = 0;
257 alc.alcGetIntegerv(device.getALDevice(), ALCConstants.ALC_REFRESH, 1, value, 0);
258 if( AudioSystem3D.checkError(device, "read ALC_FREQUENCY", DEBUG, false) || 0 == value[0] ) {
259 defaultLatency = 20f/1000f; // OpenAL-Soft default seems to be 50 Hz -> 20ms min latency
260 if( DEBUG ) {
261 logout.println("ALAudioSink.queryDefaultRefreshRate: failed");
262 }
263 } else {
264 defaultLatency = 1f/value[0]; // Hz -> s
265 if( DEBUG ) {
266 logout.println("ALAudioSink.queryDefaultRefreshRate: OK "+value[0]+" Hz = "+(1000f*defaultLatency)+" ms");
267 }
268 }
269 }
270 nativeFormat = new AudioFormat(defaultSampleRate, DefaultFormat.sampleSize, getMaxSupportedChannels(false),
271 DefaultFormat.signed, DefaultFormat.fixedP, DefaultFormat.planar, DefaultFormat.littleEndian);
272 preferredFormat = nativeFormat;
273 if( DEBUG ) {
274 final int[] alcvers = { 0, 0 };
275 System.out.println("ALAudioSink: OpenAL Version: "+al.alGetString(ALConstants.AL_VERSION));
276 System.out.println("ALAudioSink: OpenAL Extensions: "+al.alGetString(ALConstants.AL_EXTENSIONS));
277 AudioSystem3D.checkError(device, "init."+checkErrIter++, DEBUG, false);
278 System.out.println("ALAudioSink: Null device OpenALC:");
279 alc.alcGetIntegerv(null, ALCConstants.ALC_MAJOR_VERSION, 1, alcvers, 0);
280 alc.alcGetIntegerv(null, ALCConstants.ALC_MINOR_VERSION, 1, alcvers, 1);
281 System.out.println(" Version: "+alcvers[0]+"."+alcvers[1]);
282 System.out.println(" Extensions: "+alc.alcGetString(null, ALCConstants.ALC_EXTENSIONS));
283 AudioSystem3D.checkError(device, "init."+checkErrIter++, DEBUG, false);
284 System.out.println("ALAudioSink: Device "+device+" OpenALC:");
285 alc.alcGetIntegerv(device.getALDevice(), ALCConstants.ALC_MAJOR_VERSION, 1, alcvers, 0);
286 alc.alcGetIntegerv(device.getALDevice(), ALCConstants.ALC_MINOR_VERSION, 1, alcvers, 1);
287 System.out.println(" Version: "+alcvers[0]+"."+alcvers[1]);
288 System.out.println(" Extensions: "+alc.alcGetString(device.getALDevice(), ALCConstants.ALC_EXTENSIONS));
289 System.out.println("ALAudioSink: hasSOFTBufferSamples "+hasSOFTBufferSamples);
290 System.out.println("ALAudioSink: hasEXTMcFormats "+hasEXTMcFormats);
291 System.out.println("ALAudioSink: hasEXTFloat32 "+hasEXTFloat32);
292 System.out.println("ALAudioSink: hasEXTDouble "+hasEXTDouble);
293 System.out.println("ALAudioSink: hasALC_thread_local_context "+hasALC_thread_local_context);
294 System.out.println("ALAudioSink: hasAL_SOFT_events "+hasAL_SOFT_events);
295 System.out.println("ALAudioSink: maxSupportedChannels "+getMaxSupportedChannels(false));
296 System.out.println("ALAudioSink: nativeAudioFormat "+nativeFormat);
297 System.out.println("ALAudioSink: defaultMixerRefreshRate "+(1000f*defaultLatency)+" ms, "+(1f/defaultLatency)+" Hz");
298 AudioSystem3D.checkError(device, "init."+checkErrIter++, DEBUG, false);
299 }
300
301 if( DEBUG ) {
302 logout.println("ALAudioSink: Using device: " + device);
303 }
304 available = true;
305 } finally {
306 release(true /* throw */);
307 }
308 }
309
310 // Expose AudioSink OpenAL implementation specifics
311
312 /** Return OpenAL global {@link AL}. */
313 public static final AL getAL() { return al; }
314 /** Return OpenAL global {@link ALC}. */
315 public static final ALC getALC() { return alc; }
316 /** Return OpenAL global {@link ALExt}. */
317 public static final ALExt getALExt() { return alExt; }
318
319 /** Return this instance's OpenAL {@link Device}. */
320 public final Device getDevice() { return device; }
321 /** Return this instance's OpenAL {@link Context}. */
322 public final Context getContext() { return context; }
323 /** Return this instance's OpenAL {@link Source}. */
324 public final Source getSource() { return alSource; }
325
326 /** Return whether OpenAL extension <code>AL_SOFT_buffer_samples</code> is available. */
327 public final boolean hasSOFTBufferSamples() { return hasSOFTBufferSamples; }
328 /** Return whether OpenAL extension <code>AL_EXT_MCFORMATS</code> is available. */
329 public final boolean hasEXTMcFormats() { return hasEXTMcFormats; }
330 /** Return whether OpenAL extension <code>AL_EXT_FLOAT32</code> is available. */
331 public final boolean hasEXTFloat32() { return hasEXTFloat32; }
332 /** Return whether OpenAL extension <code>AL_EXT_DOUBLE</code> is available. */
333 public final boolean hasEXTDouble() { return hasEXTDouble; }
334 /** Return whether OpenAL extension <code>ALC_EXT_thread_local_context</code> is available. */
335 public final boolean hasALCThreadLocalContext() { return hasALC_thread_local_context; }
336 /** Return whether OpenAL extension <code>AL_SOFT_events</code> is available. */
337 public final boolean hasSOFTEvents() { return hasAL_SOFT_events; }
338 /** Enable or disable <code>AL_SOFT_events</code>, default is enabled if {@link #hasSOFTEvents()}. */
339 public final void setUseSOFTEvents(final boolean v) { useAL_SOFT_events = v; }
340 /** Returns whether <code>AL_SOFT_events</code> is enabled, default if {@link #hasSOFTEvents()}. */
341 public final boolean getUseSOFTEvents(final boolean v) { return useAL_SOFT_events; }
342
343 /** Return this instance's OpenAL channel layout, set after {@link #init(AudioFormat, float, int)}. */
344 public final int getALChannelLayout() { return alChannelLayout; }
345 /** Return this instance's OpenAL sample type, set after {@link #init(AudioFormat, float, int)}. */
346 public final int getALSampleType() { return alSampleType; }
347 /** Return this instance's OpenAL format, set after {@link #init(AudioFormat, float, int)}. */
348 public final int getALFormat() { return alFormat; }
349
350 // AudioSink implementation ...
351
352 @Override
353 public final boolean makeCurrent(final boolean throwException) {
354 return context.makeCurrent(throwException);
355 }
356 @Override
357 public final boolean release(final boolean throwException) {
358 return context.release(throwException);
359 }
360 private final void destroyContext() {
361 context.destroy();
362 }
363
364 @Override
365 public final String toString() {
366 final int ctxHash = context != null ? context.hashCode() : 0;
367 final int alFramesEnqueued = alFramesPlaying != null ? alFramesPlaying.size() : 0;
368 final int alFramesFree_ = alFramesFree!= null ? alFramesFree.size() : 0;
369 return String.format("ALAudioSink[playReq %b, device '%s', ctx 0x%x, alSource %d"+
370 ", chosen %s, al[chan %s, type %s, fmt 0x%x, tlc %b, soft[buffer %b, events %b/%b]"+
371 ", latency %.2f/%.2f ms, sources %d], playSpeed %.2f, "+
372 "play[used %d, apts %d], queued[free %d, apts %d, %.1f ms, %d bytes, avg %.2f ms/frame, max %d ms]]",
373 playRequested, device.getName(), ctxHash, alSource.getID(), chosenFormat,
374 ALHelpers.alChannelLayoutName(alChannelLayout), ALHelpers.alSampleTypeName(alSampleType),
375 alFormat, hasALC_thread_local_context, hasSOFTBufferSamples, useAL_SOFT_events, hasAL_SOFT_events,
376 1000f*latency, 1000f*defaultLatency, sourceCount, playSpeed,
377 alFramesEnqueued, getPTS().getLast(),
378 alFramesFree_, getLastBufferedPTS(), 1000f*getQueuedDuration(), alBufferBytesQueued, 1000f*avgFrameDuration, queueSize
379 );
380 }
381
382 public final String getPerfString() {
383 final int alFramesEnqueued = alFramesPlaying != null ? alFramesPlaying.size() : 0;
384 final int alFramesFree_ = alFramesFree!= null ? alFramesFree.size() : 0;
385 return String.format("play[used %d, apts %d], queued[free %d, apts %d, %.1f ms, %d bytes, avg %.2f ms/frame, max %d ms]",
386 alFramesEnqueued, getPTS().getLast(),
387 alFramesFree_, getLastBufferedPTS(), 1000f*getQueuedDuration(), alBufferBytesQueued, 1000f*avgFrameDuration, queueSize
388 );
389 }
390
391 @Override
392 public int getSourceCount() { return sourceCount; }
393
394 @Override
395 public float getDefaultLatency() { return defaultLatency; }
396
397 @Override
398 public float getLatency() { return latency; }
399
400 @Override
401 public final AudioFormat getNativeFormat() {
402 if( !staticsInitialized ) {
403 return null;
404 }
405 return nativeFormat;
406 }
407
408 @Override
409 public final AudioFormat getPreferredFormat() {
410 if( !staticsInitialized ) {
411 return null;
412 }
413 return preferredFormat;
414 }
415
416 @Override
417 public final void setChannelLimit(final int cc) {
418 userMaxChannels = Math.min(8, Math.max(1, cc));
419
420 preferredFormat = new AudioFormat(nativeFormat.sampleRate,
421 nativeFormat.sampleSize, getMaxSupportedChannels(true),
422 nativeFormat.signed, nativeFormat.fixedP,
423 nativeFormat.planar, nativeFormat.littleEndian);
424 if( DEBUG ) {
425 System.out.println("ALAudioSink: channelLimit "+userMaxChannels+", preferredFormat "+preferredFormat);
426 }
427 }
428
429 private final int getMaxSupportedChannels(final boolean considerLimit) {
430 if( !staticsInitialized ) {
431 return 0;
432 }
433 final int cc;
434 if( hasEXTMcFormats || hasSOFTBufferSamples ) {
435 cc = 8;
436 } else {
437 cc = 2;
438 }
439 return considerLimit ? Math.min(userMaxChannels, cc) : cc;
440 }
441
442 @Override
443 public final boolean isSupported(final AudioFormat format) {
444 if( !staticsInitialized ) {
445 return false;
446 }
447 if( format.planar != preferredFormat.planar ||
448 format.littleEndian != preferredFormat.littleEndian ||
449 format.sampleRate > preferredFormat.sampleRate ||
450 format.channelCount > preferredFormat.channelCount )
451 {
452 if( DEBUG ) {
453 logout.println(getThreadName()+": ALAudioSink.isSupported: NO.0 "+format);
454 }
455 return false;
456 }
457 final int alFormat = ALHelpers.getALFormat(format, al, alExt,
458 hasSOFTBufferSamples, hasEXTMcFormats,
459 hasEXTFloat32, hasEXTDouble);
460 if( ALConstants.AL_NONE != alFormat ) {
461 if( DEBUG ) {
462 logout.println(getThreadName()+": ALAudioSink.isSupported: OK "+format+", alFormat "+toHexString(alFormat));
463 }
464 return true;
465 } else {
466 if( DEBUG ) {
467 logout.println(getThreadName()+": ALAudioSink.isSupported: NO.1 "+format);
468 }
469 return false;
470 }
471 }
472
473 @Override
474 public final boolean init(final AudioFormat requestedFormat, final int frameDurationHint, final int queueSize)
475 {
476 if( !staticsInitialized ) {
477 return false;
478 }
479 final int alChannelLayout = ALHelpers.getDefaultALChannelLayout(requestedFormat.channelCount);
480 final int alSampleType = ALHelpers.getALSampleType(requestedFormat.sampleSize, requestedFormat.signed, requestedFormat.fixedP);
481 final int alFormat;
482 if( ALConstants.AL_NONE != alChannelLayout && ALConstants.AL_NONE != alSampleType ) {
483 alFormat = ALHelpers.getALFormat(alChannelLayout, alSampleType, al, alExt,
484 hasSOFTBufferSamples, hasEXTMcFormats,
485 hasEXTFloat32, hasEXTDouble);
486 } else {
487 alFormat = ALConstants.AL_NONE;
488 }
489 if( ALConstants.AL_NONE == alFormat ) {
490 // not supported
491 if( DEBUG ) {
492 logout.println(getThreadName()+": ALAudioSink.init1: Not supported: "+requestedFormat+", "+toString());
493 }
494 return false;
495 }
496 return initImpl(requestedFormat, alChannelLayout, alSampleType, alFormat, frameDurationHint/1000f, queueSize);
497 }
498
499 /**
500 * Initializes the sink using the given OpenAL audio parameter and streaming details.
501 * @param alChannelLayout OpenAL channel layout
502 * @param alSampleType OpenAL sample type
503 * @param alFormat OpenAL format
504 * @param sampleRate sample rate, e.g. 44100
505 * @param sampleSize sample size in bits, e.g. 16
506 * @param frameDurationHint average {@link AudioFrame} duration hint in milliseconds.
507 * Assists to adjust latency of the backend, as currently used for JOAL's ALAudioSink.
508 * A value below 30ms or {@link #DefaultFrameDuration} may increase the audio processing load.
509 * Assumed as {@link #DefaultFrameDuration}, if <code>frameDuration < 1 ms</code>.
510 * @param queueSize queue size in milliseconds, see {@link #DefaultQueueSize}.
511 * Uses `frameDurationHint` to determine initial {@link AudioFrame} queue size.
512 * @return true if successful, otherwise false
513 * @see #enqueueData(int, ByteBuffer, int)
514 * @see #getAvgFrameDuration()
515 * @see ALHelpers#getAudioFormat(int, int, int, int, int)
516 * @see #init(AudioFormat, float, int)
517 */
518 public final boolean init(final int alChannelLayout, final int alSampleType, final int alFormat,
519 final int sampleRate, final int sampleSize, final int frameDurationHint, final int queueSize)
520 {
521 final AudioFormat requestedFormat = ALHelpers.getAudioFormat(alChannelLayout, alSampleType, alFormat, sampleRate, sampleSize);
522 if( null == requestedFormat ) {
523 if( DEBUG ) {
524 logout.println(getThreadName()+": ALAudioSink.init2: Invalid AL channelLayout "+toHexString(alChannelLayout)+
525 ", sampleType "+toHexString(alSampleType)+", format "+toHexString(alFormat)+" or sample[rate "+sampleRate+", size "+sampleSize+"]; "+toString());
526 }
527 return false;
528 }
529 return initImpl(requestedFormat, alChannelLayout, alSampleType, alFormat, frameDurationHint/1000f, queueSize);
530 }
531
532 private final synchronized boolean initImpl(final AudioFormat requestedFormat,
533 final int alChannelLayout, final int alSampleType, final int alFormat,
534 float frameDurationHintS, final int queueSize) {
535 this.alChannelLayout = alChannelLayout;
536 this.alSampleType = alSampleType;
537 this.alFormat = alFormat;
538
539 /**
540 * OpenAL is pretty relaxed on formats, so whall we.
541 * Deduced requested AudioFormat is already a validated OpenAL compatible one.
542 * Drop filtering ..
543 if( !isSupported(requestedFormat) ) {
544 if( DEBUG ) {
545 logout.println(getThreadName()+": ALAudioSink.init: Requested format "+requestedFormat+" not supported, preferred is "+preferredFormat+", "+this);
546 }
547 return false;
548 }
549 */
550
551 // Flush all old buffers
552 makeCurrent(true /* throw */);
553 if( context.getLockCount() != 1 ) {
554 release(false);
555 throw new ALException("init() must be called w/o makeCurrent: lockCount "+context+", "+this);
556 }
557 boolean releaseContext = true;
558 try {
559 stopImpl(true);
560 destroySource();
561 destroyBuffers();
562
563 frameDurationHintS = frameDurationHintS >= 1f/1000f ? frameDurationHintS : AudioSink.DefaultFrameDuration/1000f;
564 // Re-Create audio context if default latency is not sufficient
565 {
566 final int defRefreshRate = Math.round( 1f / defaultLatency ); // s -> Hz
567 final int expMixerRefreshRate = Math.round( 1f / frameDurationHintS ); // s -> Hz
568
569 if( frameDurationHintS < defaultLatency ) {
570 if( DEBUG ) {
571 logout.println(getThreadName()+": ALAudioSink.init: Re-create context as latency exp "+
572 (1000f*frameDurationHintS)+" ms ("+expMixerRefreshRate+" Hz) < default "+(1000f*defaultLatency)+" ms ("+defRefreshRate+" Hz)");
573 }
574 if( !context.recreate( new int[] { ALCConstants.ALC_REFRESH, expMixerRefreshRate } ) ) {
575 logout.println(getThreadName()+": ALAudioSink: Error creating OpenAL context "+context);
576 return false;
577 }
578 } else if( DEBUG ) {
579 logout.println(getThreadName()+": ALAudioSink.init: Keep context, latency exp "+
580 (1000f*frameDurationHintS)+" ms ("+expMixerRefreshRate+" Hz) >= default "+(1000f*defaultLatency)+" ms ("+defRefreshRate+" Hz)");
581 }
582 }
583 // Get actual refresh rate
584 {
585 final int[] value = { 0 };
586 alc.alcGetIntegerv(device.getALDevice(), ALCConstants.ALC_REFRESH, 1, value, 0);
587 if( AudioSystem3D.checkError(device, "read ALC_FREQUENCY", DEBUG, false) || 0 == value[0] ) {
588 latency = defaultLatency;
589 if( DEBUG ) {
590 logout.println("ALAudioSink.queryRefreshRate: failed, claiming default "+(1000f*latency)+" ms");
591 }
592 } else {
593 latency = 1f/value[0]; // Hz -> ms
594 if( DEBUG ) {
595 logout.println("ALAudioSink.queryRefreshRate: OK "+value[0]+" Hz = "+(1000f*latency)+" ms");
596 }
597 }
598 }
599 if( !createSource() ) {
600 destroyContext();
601 releaseContext = false;
602 return false;
603 }
604
605 // Allocate new buffers
606 {
607 final int frameCount = requestedFormat.getFrameCount(
608 queueSize > 0 ? queueSize/1000f : AudioSink.DefaultQueueSize/1000f, frameDurationHintS);
609 alBufferNames = new int[frameCount];
610 al.alGenBuffers(frameCount, alBufferNames, 0);
611 if( AudioSystem3D.checkALError("alGenBuffers", true, false) ) {
612 alBufferNames = null;
613 destroySource();
614 destroyContext();
615 releaseContext = false;
616 return false;
617 }
618 final ALAudioFrame[] alFrames = new ALAudioFrame[frameCount];
619 for(int i=0; i<frameCount; i++) {
620 alFrames[i] = new ALAudioFrame(alBufferNames[i]);
621 }
622 alFramesFree = new LFRingbuffer<ALAudioFrame>(alFrames);
623 alFramesPlaying = new LFRingbuffer<ALAudioFrame>(ALAudioFrame[].class, frameCount);
624 this.queueSize = queueSize > 0 ? queueSize : AudioSink.DefaultQueueSize;
625 if( DEBUG_TRACE ) {
626 alFramesFree.dump(System.err, "Avail-init");
627 alFramesPlaying.dump(System.err, "Playi-init");
628 }
629 }
630 if( hasAL_SOFT_events && useAL_SOFT_events ) {
631 alExt.alEventCallbackSOFT(alEventCallback, context.getALContext());
632 alExt.alEventControlSOFT(1, new int[] { ALExtConstants.AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT }, 0, true);
633 }
634 } finally {
635 if( releaseContext ) {
636 release(false /* throw */);
637 }
638 }
639 chosenFormat = requestedFormat;
640 avgFrameDuration = latency;
641 if( DEBUG ) {
642 logout.println(getThreadName()+": ALAudioSink.init: OK "+requestedFormat+", "+toString());
643 }
644 return true;
645 }
646
647 @Override
648 public final AudioFormat getChosenFormat() {
649 return chosenFormat;
650 }
651
652 private void destroyBuffers() {
653 if( !staticsInitialized ) {
654 return;
655 }
656 if( null != alBufferNames ) {
657 try {
658 al.alDeleteBuffers(alBufferNames.length, alBufferNames, 0);
659 } catch (final Throwable t) {
660 if( DEBUG ) {
661 logout.println("Caught "+t.getClass().getName()+": "+t.getMessage());
662 t.printStackTrace();
663 }
664 }
665 alFramesFree.clear();
666 alFramesFree = null;
667 alFramesPlaying.clear();
668 alFramesPlaying = null;
669 alBufferBytesQueued = 0;
670 // alFrames = null;
671 alBufferNames = null;
672 }
673 }
674
675 private void destroySource() {
676 if( !alSource.isValid() ) {
677 return;
678 }
679 alSource.delete();
680 }
681 private boolean createSource() {
682 if( alSource.isValid() ) {
683 return true;
684 }
685 return alSource.create();
686 }
687
688 @Override
689 public final void destroy() {
690 if( !available ) {
691 return;
692 }
693 available = false;
694 if( null != context ) {
695 makeCurrent(true /* throw */);
696 if( hasAL_SOFT_events ) {
699 ALExtConstants.AL_EVENT_TYPE_DISCONNECTED_SOFT
700 }, 0, false);
701 alExt.alEventCallbackSOFT(null, context.getALContext());
702 }
703 }
704 try {
705 stopImpl(true);
706 destroySource();
707 destroyBuffers();
708 } finally {
709 destroyContext();
710 }
711 device.close();
712 chosenFormat = null;
713 }
714
715 @Override
716 public final boolean isAvailable() {
717 return available;
718 }
719
720 final ALEVENTPROCSOFT alEventCallback = new ALEVENTPROCSOFT() {
721 @SuppressWarnings("unused")
722 @Override
723 public void callback(final int eventType, final int object, final int param,
724 final String message, final ALCcontext context) {
725 if( false ) {
726 final com.jogamp.openal.ALContextKey k = new com.jogamp.openal.ALContextKey(context);
727 logout.println("ALAudioSink.Event: type "+toHexString(eventType)+", obj "+toHexString(object)+", param "+param+
728 ", msg '"+message+"', userParam "+k);
729 }
731 alSource.getID() == object )
732 {
733 synchronized( eventReleasedBuffersLock ) {
734 if( false ) {
735 final com.jogamp.openal.ALContextKey k = new com.jogamp.openal.ALContextKey(context);
736 logout.println("ALAudioSink.Event: type "+toHexString(eventType)+", obj "+toHexString(object)+
737 ", eventReleasedBuffers +"+param+" -> "+(eventReleasedBuffers + param)+
738 ", msg '"+message+"', userParam "+k);
739 }
740 eventReleasedBuffers += param;
741 eventReleasedBuffersLock.notifyAll();
742 }
743 }
744 }
745 };
746 private final Object eventReleasedBuffersLock = new Object();
747 private volatile int eventReleasedBuffers = 0;
748
749 private final int waitForReleasedEvent(final long t0, final boolean wait, final int releaseBufferCountReq) {
750 if( alBufferBytesQueued == 0 ) {
751 return 0;
752 }
753 final int enqueuedBuffers = alFramesPlaying.size();
754 int wait_cycles=0;
755 int slept = 0;
756 int releasedBuffers = 0;
757 do {
758 synchronized( eventReleasedBuffersLock ) {
759 while( wait && alBufferBytesQueued > 0 && eventReleasedBuffers < releaseBufferCountReq ) {
760 wait_cycles++;
761 try {
762 eventReleasedBuffersLock.wait();
763 } catch (final InterruptedException e) { }
764 }
765 // AL_SOFT_events cumulated released buffers is 'sometimes wrong'
766 // Workaround: Query released buffers after receiving event and use minimum. (FIXME)
767 final int releasedBuffersByEvent = eventReleasedBuffers;
768 final int releasedBuffersByQuery = alSource.getBuffersProcessed();
769 releasedBuffers = Math.min(releasedBuffersByEvent, releasedBuffersByQuery);
770 eventReleasedBuffers = 0;
771 if( DEBUG ) {
772 slept += Clock.currentMillis() - t0;
773 if( wait || releasedBuffers > 0 ) {
774 final String warnInfo = releasedBuffers != releasedBuffersByEvent ? " ** Warning ** " : "";
775 logout.println("ALAudioSink.DeqEvent["+wait_cycles+"]: released "+releasedBuffers+warnInfo+
776 " [enqeueud "+enqueuedBuffers+", event "+
777 releasedBuffersByEvent+", query "+releasedBuffersByQuery+"], req "+releaseBufferCountReq+", slept "+
778 slept+" ms, free total "+alFramesFree.size());
779 }
780 }
781 }
782 } while ( wait && alBufferBytesQueued > 0 && releasedBuffers < releaseBufferCountReq );
783 return releasedBuffers;
784 }
785 private final int waitForReleasedPoll(final boolean wait, final int releaseBufferCountReq) {
786 if( alBufferBytesQueued == 0 ) {
787 return 0;
788 }
789 final long sleepLimes = Math.round( releaseBufferCountReq * 1000.0*avgFrameDuration );
790 int wait_cycles=0;
791 long slept = 0;
792 boolean onceBusyDebug = true;
793 int releasedBuffers = 0;
794 do {
795 releasedBuffers = alSource.getBuffersProcessed();
796 if( wait && releasedBuffers < releaseBufferCountReq ) {
797 wait_cycles++;
798 // clip wait at [avgFrameDuration .. 300] ms
799 final int sleep = Math.max(2, Math.min(300, Math.round( (releaseBufferCountReq-releasedBuffers) * 1000f*avgFrameDuration) ) ) - 1; // 1 ms off for busy-loop
800 if( slept + sleep + 1 <= sleepLimes ) {
801 if( DEBUG ) {
802 logout.println("ALAudioSink: DeqPoll["+wait_cycles+"].1:"+
803 "released "+releasedBuffers+"/"+releaseBufferCountReq+", sleep "+sleep+"/"+slept+"/"+sleepLimes+
804 " ms, "+getPerfString()+", state "+ALHelpers.alSourceStateString(getSourceState(false)));
805 }
806 release(true /* throw */);
807 try {
808 Thread.sleep( sleep );
809 slept += sleep;
810 } catch (final InterruptedException e) {
811 } finally {
812 makeCurrent(true /* throw */);
813 }
814 } else {
815 // Empirical best behavior w/ openal-soft (sort of needs min ~21ms to complete processing a buffer even if period < 20ms?)
816 if( DEBUG ) {
817 if( onceBusyDebug ) {
818 logout.println("ALAudioSink: DeqPoll["+wait_cycles+"].2:"+
819 "released "+releasedBuffers+"/"+releaseBufferCountReq+", sleep "+sleep+"->1/"+slept+"/"+sleepLimes+
820 " ms, "+getPerfString()+", state "+ALHelpers.alSourceStateString(getSourceState(false)));
821 onceBusyDebug = false;
822 }
823 }
824 release(true /* throw */);
825 try {
826 Thread.sleep( 1 );
827 slept += 1;
828 } catch (final InterruptedException e) {
829 } finally {
830 makeCurrent(true /* throw */);
831 }
832 }
833 }
834 } while ( wait && alBufferBytesQueued > 0 && releasedBuffers < releaseBufferCountReq );
835 return releasedBuffers;
836 }
837 /**
838 * Dequeuing playing audio frames.
839 * @param wait if true, waiting for completion of audio buffers
840 * @param releaseBufferCountReq number of buffers to be released
841 */
842 private final int dequeueBuffer(final boolean wait, final int releaseBufferCountReq) {
843 final long t0 = Clock.currentMillis();
844 final int releasedBufferCount;
845 if( hasAL_SOFT_events && useAL_SOFT_events ) {
846 releasedBufferCount = waitForReleasedEvent(t0, wait, releaseBufferCountReq);
847 } else {
848 releasedBufferCount = waitForReleasedPoll(wait, releaseBufferCountReq);
849 }
850 final long t1 = Clock.currentMillis();
851 if( releasedBufferCount > 0 ) {
852 final int[] buffers = new int[releasedBufferCount];
853 alSource.unqueueBuffers(buffers);
854
855 for ( int i=0; i<releasedBufferCount; i++ ) {
856 final ALAudioFrame releasedBuffer = alFramesPlaying.get();
857 if( null == releasedBuffer ) {
858 throw new InternalError("Internal Error: "+this);
859 } else {
860 if( releasedBuffer.alBuffer != buffers[i] ) {
861 alFramesFree.dump(System.err, "Avail-deq02-post");
862 alFramesPlaying.dump(System.err, "Playi-deq02-post");
863 throw new InternalError("Buffer name mismatch: dequeued: "+buffers[i]+", released "+releasedBuffer+", "+this);
864 }
865 alBufferBytesQueued -= releasedBuffer.getByteSize();
866 {
867 pts.set(t1, releasedBuffer.getPTS() /* + releasedBuffer.getDuration() */);
868 // playingPTS = releasedBuffer.getPTS();
869 // final float queuedDuration = chosenFormat.getBytesDuration(alBufferBytesQueued); // [s]
870 // playingPTS += (int)( queuedDuration * 1.0f * 1000f + 0.5f ); // released frame already gone, add (forward) 80% of queued buffer duration
871 }
872 if( !alFramesFree.put(releasedBuffer) ) {
873 throw new InternalError("Internal Error: "+this);
874 }
875 if(DEBUG_TRACE) {
876 logout.println("<< [al "+buffers[i]+", q "+releasedBuffer.alBuffer+"] <- "+getPerfString()+" @ "+getThreadName());
877 }
878 }
879 }
880 if( DEBUG ) {
881 logout.println("ALAudioSink.Dequeued: "+(t1-t0)+
882 "ms , released "+releasedBufferCount+"/"+releaseBufferCountReq+", "+getPerfString()+
883 ", state "+ALHelpers.alSourceStateString(getSourceState(false)));
884 }
885 }
886 return releasedBufferCount;
887 }
888
889 private final void dequeueForceAll() {
890 if(DEBUG_TRACE) {
891 logout.println("< _FLUSH_ <- "+getPerfString()+" @ "+getThreadName());
892 }
893 int processedBufferCount = 0;
894 al.alSourcei(alSource.getID(), ALConstants.AL_BUFFER, 0); // explicit force zero buffer!
895 if(DEBUG_TRACE) {
896 processedBufferCount = alSource.getBuffersProcessed();
897 }
898 final int alErr = al.alGetError();
899 while ( !alFramesPlaying.isEmpty() ) {
900 final ALAudioFrame releasedBuffer = alFramesPlaying.get();
901 if( null == releasedBuffer ) {
902 throw new InternalError("Internal Error: "+this);
903 }
904 alBufferBytesQueued -= releasedBuffer.getByteSize();
905 if( !alFramesFree.put(releasedBuffer) ) {
906 throw new InternalError("Internal Error: "+this);
907 }
908 }
909 alBufferBytesQueued = 0;
910 last_buffered_pts = TimeFrameI.INVALID_PTS;
911 pts.set(0, TimeFrameI.INVALID_PTS);
912 if(DEBUG_TRACE) {
913 logout.println("<< _FLUSH_ [al "+processedBufferCount+", err "+toHexString(alErr)+"] <- "+getPerfString()+" @ "+getThreadName());
914 ExceptionUtils.dumpStack(System.err);
915 }
916 }
917
918 @Override
919 public final PTS updateQueue() {
920 if( !available || null == chosenFormat ) {
921 return pts;
922 }
923
924 // OpenAL consumes buffers in the background
925 // we first need to initialize the OpenAL buffers then
926 // start continuous playback.
927 makeCurrent(true /* throw */);
928 try {
929 dequeueBuffer( false /* wait */, 1 );
930 } finally {
931 release(true /* throw */);
932 }
933 return pts;
934 }
935
936 @Override
937 public final AudioFrame enqueueData(final int pts, final ByteBuffer bytes, final int byteCount) {
938 if( !available || null == chosenFormat ) {
939 return null;
940 }
941 final ALAudioFrame alFrame;
942
943 // OpenAL consumes buffers in the background
944 // we first need to initialize the OpenAL buffers then
945 // start continuous playback.
946 makeCurrent(true /* throw */);
947 try {
948 final int sourceState0 = DEBUG ? getSourceState(false) : 0;
949 final float neededDuration = chosenFormat.getBytesDuration(byteCount); // [s]
950 int enqueuedBuffers = alFramesPlaying.size();
951 final char avgUpdateC;
952
953 // 1) Update avgFrameDuration ..
954 if( enqueuedBuffers > 2 ) {
955 final float queuedDuration = chosenFormat.getBytesDuration(alBufferBytesQueued); // [s]
956 avgFrameDuration = queuedDuration / enqueuedBuffers;
957 avgUpdateC = '*';
958 } else {
959 avgUpdateC = '_';
960 }
961
962 // 2) SOFT dequeue w/o wait
963 if( alFramesFree.isEmpty() || enqueuedBuffers > 2 ) {
964 if( DEBUG ) {
965 logout.printf("ALAudioSink.DequeuSoft"+avgUpdateC+": %.2f ms, queued %d, %s%n",
966 1000f*neededDuration, enqueuedBuffers, getPerfString());
967 }
968 dequeueBuffer( false /* wait */, 1 );
969 }
970 enqueuedBuffers = alFramesPlaying.size();
971
972 // 3) HARD dequeue with wait
973 if( alFramesFree.isEmpty() && isPlayingImpl() ) {
974 // possible if grow failed or already exceeds it's limit - only possible if playing ..
975 final int releaseBuffersHardReq = Math.max(1, enqueuedBuffers / 3 ); // [1 .. enqueuedBuffers / 3]
976 if( DEBUG ) {
977 logout.printf("ALAudioSink.DequeuHard"+avgUpdateC+": %.2f ms, req %d, queued %d, %s%n",
978 1000f*neededDuration, releaseBuffersHardReq, enqueuedBuffers, getPerfString());
979 }
980 dequeueBuffer( true /* wait */, releaseBuffersHardReq );
981 }
982
983 // 4) Add new frame
984 alFrame = alFramesFree.get();
985 if( null == alFrame ) {
986 alFramesFree.dump(System.err, "Avail");
987 throw new InternalError("Internal Error: avail.get null "+alFramesFree+", "+this);
988 }
989 alFrame.setPTS(pts);
990 alFrame.setDuration(Math.round(1000f*neededDuration));
991 alFrame.setByteSize(byteCount);
992 if( !alFramesPlaying.put( alFrame ) ) {
993 throw new InternalError("Internal Error: "+this);
994 }
995 last_buffered_pts = pts;
996 final int[] alBufferNames = new int[] { alFrame.alBuffer };
997 if( hasSOFTBufferSamples ) {
998 final int samplesPerChannel = chosenFormat.getBytesSampleCount(byteCount) / chosenFormat.channelCount;
999 // final int samplesPerChannel = ALHelpers.bytesToSampleCount(byteCount, alChannelLayout, alSampleType);
1000 alExt.alBufferSamplesSOFT(alFrame.alBuffer, chosenFormat.sampleRate, alFormat,
1001 samplesPerChannel, alChannelLayout, alSampleType, bytes);
1002 } else {
1003 al.alBufferData(alFrame.alBuffer, alFormat, bytes, byteCount, chosenFormat.sampleRate);
1004 }
1005
1006 alSource.queueBuffers(alBufferNames);
1007 alBufferBytesQueued += byteCount;
1008 enqueuedFrameCount++; // safe: only written-to while locked!
1009
1010 if(DEBUG_TRACE) {
1011 logout.println(">> "+alFrame.alBuffer+" -> "+getPerfString()+" @ "+getThreadName());
1012 }
1013 if( DEBUG ) {
1014 final int sourceState1 = getSourceState(false);
1015 playImpl(); // continue playing, fixes issue where we ran out of enqueued data!
1016 final int sourceState2 = getSourceState(false);
1017 if( sourceState0 != sourceState1 || sourceState0 != sourceState2 || sourceState1 != sourceState2 ) {
1018 logout.printf("ALAudioSink.Enqueued : %.2f ms, %s, state* %s -> %s -> %s; %s bytes%n",
1019 1000f*neededDuration, getPerfString(),
1020 ALHelpers.alSourceStateString(sourceState0), ALHelpers.alSourceStateString(sourceState1), ALHelpers.alSourceStateString(sourceState2), byteCount);
1021 } else {
1022 logout.printf("ALAudioSink.Enqueued : %.2f ms, %s, state %s; %d bytes%n",
1023 1000f*neededDuration, getPerfString(), ALHelpers.alSourceStateString(sourceState2), byteCount);
1024 }
1025 } else {
1026 playImpl(); // continue playing, fixes issue where we ran out of enqueued data!
1027 }
1028 } finally {
1029 release(true /* throw */);
1030 }
1031 return alFrame;
1032 }
1033
1034 @Override
1035 public final boolean isPlaying() {
1036 if( !available || null == chosenFormat ) {
1037 return false;
1038 }
1039 if( playRequested ) {
1040 makeCurrent(true /* throw */);
1041 try {
1042 return isPlayingImpl();
1043 } finally {
1044 release(true /* throw */);
1045 }
1046 } else {
1047 return false;
1048 }
1049 }
1050 private final boolean isPlayingImpl() {
1051 if( playRequested ) {
1052 return ALConstants.AL_PLAYING == getSourceState(false);
1053 } else {
1054 return false;
1055 }
1056 }
1057 private final int getSourceState(final boolean ignoreError) {
1058 if( !alSource.isValid() ) {
1059 final String msg = getThreadName()+": getSourceState: invalid "+alSource;
1060 if( ignoreError ) {
1061 if( DEBUG ) {
1062 logout.println(msg);
1063 }
1064 return ALConstants.AL_NONE;
1065 } else {
1066 throw new ALException(msg);
1067 }
1068 }
1069 final int[] val = { ALConstants.AL_NONE };
1070 al.alGetSourcei(alSource.getID(), ALConstants.AL_SOURCE_STATE, val, 0);
1071 if( AudioSystem3D.checkALError("alGetSourcei", true, false) ) {
1072 final String msg = getThreadName()+": Error while querying SOURCE_STATE. "+this;
1073 if( ignoreError ) {
1074 if( DEBUG ) {
1075 logout.println(msg);
1076 }
1077 return ALConstants.AL_NONE;
1078 } else {
1079 throw new ALException(msg);
1080 }
1081 }
1082 return val[0];
1083 }
1084
1085 @Override
1086 public final void play() {
1087 if( !available || null == chosenFormat ) {
1088 return;
1089 }
1090 playRequested = true;
1091 makeCurrent(true /* throw */);
1092 try {
1093 playImpl();
1094 if( DEBUG ) {
1095 logout.println(getThreadName()+": ALAudioSink: play, state "+ALHelpers.alSourceStateString(getSourceState(false))+", "+this);
1096 }
1097 } finally {
1098 release(true /* throw */);
1099 }
1100 }
1101 private final void playImpl() {
1102 if( playRequested && ALConstants.AL_PLAYING != getSourceState(false) ) {
1103 alSource.play();
1104 AudioSystem3D.checkALError("alSourcePlay", true, true);
1105 }
1106 }
1107
1108 @Override
1109 public final void pause() {
1110 if( !available || null == chosenFormat ) {
1111 return;
1112 }
1113 if( playRequested ) {
1114 makeCurrent(true /* throw */);
1115 try {
1116 pauseImpl();
1117 if( DEBUG ) {
1118 logout.println(getThreadName()+": ALAudioSink: pause, state "+ALHelpers.alSourceStateString(getSourceState(false))+", "+this);
1119 }
1120 } finally {
1121 release(true /* throw */);
1122 }
1123 }
1124 }
1125 private final void pauseImpl() {
1126 if( isPlayingImpl() ) {
1127 playRequested = false;
1128 alSource.pause();
1129 AudioSystem3D.checkALError("alSourcePause", true, true);
1130 }
1131 }
1132 private final void stopImpl(final boolean ignoreError) {
1133 if( !alSource.isValid() ) {
1134 return;
1135 }
1136 if( ALConstants.AL_STOPPED != getSourceState(ignoreError) ) {
1137 playRequested = false;
1138 alSource.stop();
1139 if( AudioSystem3D.checkALError("alSourcePause", true, false) ) {
1140 final String msg = "Error while stopping. "+this;
1141 if( ignoreError ) {
1142 if( DEBUG ) {
1143 logout.println(getThreadName()+": "+msg);
1144 }
1145 } else {
1146 throw new ALException(getThreadName()+": Error while stopping. "+this);
1147 }
1148 }
1149 }
1150 }
1151
1152 @Override
1153 public final float getPlaySpeed() { return playSpeed; }
1154
1155 @Override
1156 public final boolean setPlaySpeed(float rate) {
1157 if( !available || null == chosenFormat ) {
1158 return false;
1159 }
1160 makeCurrent(true /* throw */);
1161 try {
1162 if( Math.abs(1.0f - rate) < 0.01f ) {
1163 rate = 1.0f;
1164 }
1165 if( 0.5f <= rate && rate <= 2.0f ) { // OpenAL limits
1166 playSpeed = rate;
1167 alSource.setPitch(playSpeed);
1168 return true;
1169 }
1170 } finally {
1171 release(true /* throw */);
1172 }
1173 return false;
1174 }
1175
1176 @Override
1177 public final float getVolume() {
1178 return volume;
1179 }
1180
1181 private static final float clipAudioVolume(final float v) {
1182 if( v < 0.01f ) {
1183 return 0.0f;
1184 } else if( Math.abs(1.0f - v) < 0.01f ) {
1185 return 1.0f;
1186 }
1187 return v;
1188 }
1189 @Override
1190 public final boolean setVolume(float v) {
1191 if( !available || null == chosenFormat ) {
1192 return false;
1193 }
1194 makeCurrent(true /* throw */);
1195 try {
1196 v = clipAudioVolume(v);
1197 if( 0.0f <= v && v <= 1.0f ) { // OpenAL limits
1198 volume = v;
1199 alSource.setGain(v);
1200 return true;
1201 }
1202 } finally {
1203 release(true /* throw */);
1204 }
1205 return false;
1206 }
1207
1208 @Override
1209 public final void flush() {
1210 if( !available || null == chosenFormat ) {
1211 return;
1212 }
1213 makeCurrent(true /* throw */);
1214 try {
1215 // pauseImpl();
1216 stopImpl(false);
1217 // Redundant: dequeueBuffer( false /* wait */, true /* ignoreBufferInconsistency */);
1218 dequeueForceAll();
1219 if( alBufferNames.length != alFramesFree.size() || alFramesPlaying.size() != 0 ) {
1220 throw new InternalError("XXX: "+this);
1221 }
1222 if( DEBUG ) {
1223 logout.println(getThreadName()+": ALAudioSink: flush, state "+ALHelpers.alSourceStateString(getSourceState(false))+", "+this);
1224 }
1225 } finally {
1226 release(true /* throw */);
1227 }
1228 }
1229
1230 @Override
1231 public final int getEnqueuedFrameCount() {
1232 return enqueuedFrameCount;
1233 }
1234
1235 @Override
1236 public final int getFrameCount() {
1237 return null != alBufferNames ? alBufferNames.length : 0;
1238 }
1239
1240 @Override
1241 public final int getQueuedFrameCount() {
1242 if( !available || null == chosenFormat ) {
1243 return 0;
1244 }
1245 return alFramesPlaying.size();
1246 }
1247
1248 @Override
1249 public final int getFreeFrameCount() {
1250 if( !available || null == chosenFormat ) {
1251 return 0;
1252 }
1253 return alFramesFree.size();
1254 }
1255
1256 @Override
1257 public final int getQueuedByteCount() {
1258 if( !available || null == chosenFormat ) {
1259 return 0;
1260 }
1261 return alBufferBytesQueued;
1262 }
1263
1264 @Override
1265 public final float getQueuedDuration() {
1266 if( !available || null == chosenFormat ) {
1267 return 0;
1268 }
1269 return chosenFormat.getBytesDuration(alBufferBytesQueued);
1270 }
1271
1272 @Override
1273 public float getAvgFrameDuration() {
1274 return avgFrameDuration;
1275 }
1276
1277 @Override
1278 public final PTS getPTS() { return pts; }
1279
1280 @Override
1281 public final int getLastBufferedPTS() { return last_buffered_pts; }
1282
1283 private static final String toHexString(final int v) { return "0x"+Integer.toHexString(v); }
1284 private static final String getThreadName() { return Thread.currentThread().getName(); }
1285}
Implementing equals(Object) based on the native address and hashCode() on the HashUtil#getAddrHash32_...
ALContextKey(final ALCcontext context)
Creates an instance using the current context as key.
A generic exception for OpenAL errors used throughout the binding as a substitute for RuntimeExceptio...
The AudioSystem3D class provides a set of methods for creating and manipulating a 3D audio environmen...
static final ALC getALC()
Return OpenAL global ALC.
static boolean checkError(final Device device, final String prefix, final boolean verbose, final boolean throwException)
Returns true if an OpenAL ALC or AL error occurred, otherwise false.
static final AL getAL()
Return OpenAL global AL.
static boolean checkALError(final String prefix, final boolean verbose, final boolean throwException)
Returns true if an OpenAL AL error occurred, otherwise false.
static boolean isAvailable()
Returns the available state of this instance.
static final ALExt getALExt()
Return OpenAL global ALExt.
This class provides a Sound3D Context associated with a specified device.
Definition: Context.java:49
boolean release(final boolean throwException)
Releases control of this audio context from the current thread, if implementation utilizes context lo...
Definition: Context.java:302
boolean isValid()
Returns whether getALContext() is valid, i.e.
Definition: Context.java:161
boolean makeCurrent(final boolean throwException)
Makes the audio context current on the calling thread.
Definition: Context.java:231
int getLockCount()
Return the lock count of this context, i.e.
Definition: Context.java:211
boolean recreate(final int[] attributes)
Recreates the internal ALCcontext instance, i.e.
Definition: Context.java:137
void destroy()
destroys this context freeing its resources.
Definition: Context.java:171
ALCcontext getALContext()
Returns the OpenAL ALCcontext.
Definition: Context.java:158
final boolean hasALC_thread_local_context
Definition: Context.java:54
This class provides a handle to a specific audio device.
Definition: Device.java:46
String getName()
Returns the device name.
Definition: Device.java:62
void close()
closes the device, freeing its resources.
Definition: Device.java:105
ALCdevice getALDevice()
Returns the OpenAL ALCdevice.
Definition: Device.java:65
boolean open()
Opens the device if not yet opened.
Definition: Device.java:92
boolean isValid()
Returns whether getALDevice() is open and valid, i.e.
Definition: Device.java:73
This class is used to represent sound-producing objects in the Sound3D environment.
Definition: Source.java:48
boolean isValid()
Returns whether getID() is valid, i.e.
Definition: Source.java:88
int getBuffersProcessed()
Gets the number of buffers already processed on this source.
Definition: Source.java:512
void unqueueBuffers(final Buffer[] buffers)
Unqueues one or more buffers on a source.
Definition: Source.java:579
void setPitch(final float pitch)
Sets the pitch of the audio on this source.
Definition: Source.java:158
boolean create()
Creates a new OpenAL source ID if isValid() == false.
Definition: Source.java:69
void stop()
Stops the audio in this Source.
Definition: Source.java:130
void delete()
Delete this source, freeing its resources.
Definition: Source.java:95
void setGain(final float gain)
Sets the gain of the audio on this source.
Definition: Source.java:181
void pause()
pauses the audio in this Source.
Definition: Source.java:123
int getID()
Return the OpenAL source ID, -1 if invalid.
Definition: Source.java:85
void play()
Beginning playing the audio in this source.
Definition: Source.java:116
void queueBuffers(final Buffer[] buffers)
Queues one or more buffers on a source.
Definition: Source.java:551
final boolean release(final boolean throwException)
final AudioFormat getChosenFormat()
final AudioFrame enqueueData(final int pts, final ByteBuffer bytes, final int byteCount)
final int getALChannelLayout()
Return this instance's OpenAL channel layout, set after init(AudioFormat, float, int).
final boolean init(final AudioFormat requestedFormat, final int frameDurationHint, final int queueSize)
ALAudioSink(final Device alDevice)
Create a new instance with an optional given ALCdevice.
final boolean hasEXTDouble()
Return whether OpenAL extension AL_EXT_DOUBLE is available.
static final ALC getALC()
Return OpenAL global ALC.
final boolean setPlaySpeed(float rate)
final boolean hasALCThreadLocalContext()
Return whether OpenAL extension ALC_EXT_thread_local_context is available.
final boolean isSupported(final AudioFormat format)
final Context getContext()
Return this instance's OpenAL Context.
ALAudioSink(final String deviceName)
Create a new instance with a new named ALCdevice.
static final AL getAL()
Return OpenAL global AL.
final AudioFormat getNativeFormat()
final boolean makeCurrent(final boolean throwException)
final boolean init(final int alChannelLayout, final int alSampleType, final int alFormat, final int sampleRate, final int sampleSize, final int frameDurationHint, final int queueSize)
Initializes the sink using the given OpenAL audio parameter and streaming details.
final AudioFormat getPreferredFormat()
final int getALSampleType()
Return this instance's OpenAL sample type, set after init(AudioFormat, float, int).
final void setUseSOFTEvents(final boolean v)
Enable or disable AL_SOFT_events, default is enabled if hasSOFTEvents().
final boolean hasSOFTBufferSamples()
Return whether OpenAL extension AL_SOFT_buffer_samples is available.
final boolean hasSOFTEvents()
Return whether OpenAL extension AL_SOFT_events is available.
ALAudioSink()
Create a new instance with a new default ALCdevice.
final boolean getUseSOFTEvents(final boolean v)
Returns whether AL_SOFT_events is enabled, default if hasSOFTEvents().
static boolean isInitialized()
Returns true if OpenAL has been loaded and static fields ALC, AL and ALExt have been initialized succ...
final Device getDevice()
Return this instance's OpenAL Device.
static final ALExt getALExt()
Return OpenAL global ALExt.
final boolean hasEXTFloat32()
Return whether OpenAL extension AL_EXT_FLOAT32 is available.
final int getALFormat()
Return this instance's OpenAL format, set after init(AudioFormat, float, int).
final Source getSource()
Return this instance's OpenAL Source.
final boolean setVolume(float v)
final boolean hasEXTMcFormats()
Return whether OpenAL extension AL_EXT_MCFORMATS is available.
final void setChannelLimit(final int cc)
static int getALFormat(final AudioFormat audioFormat, final AL al, final ALExt alExt)
Returns a compatible AL buffer format given the AudioFormat, which determines the AL channel layout a...
Definition: ALHelpers.java:114
static final String alChannelLayoutName(final int alChannelLayout)
Returns the readable name of the given AL channel layout.
Definition: ALHelpers.java:411
static final String AL_SOFT_events
Definition: ALHelpers.java:47
static final String AL_EXT_DOUBLE
Definition: ALHelpers.java:51
static final String AL_SOFT_buffer_samples
openal-soft >= 1.18.0
Definition: ALHelpers.java:45
static final String alSampleTypeName(final int alSampleType)
Returns the readable name of the given AL sample type.
Definition: ALHelpers.java:475
static final String AL_EXT_MCFORMATS
Definition: ALHelpers.java:49
static final String alSourceStateString(final int sourceState)
Returns given ALConstants#AL_SOURCE_STATE AL#alGetSourcei(int, int, int[], int)} value as a string.
Definition: ALHelpers.java:600
static final String AL_EXT_FLOAT32
Definition: ALHelpers.java:50
static AudioFormat getAudioFormat(final int alChannelLayout, final int alSampleType, final int alFormat, final int sampleRate, final int sampleSize)
Returns a compatible AudioFormat based on given OpenAL channel-layout, sample-type and format,...
Definition: ALHelpers.java:75
static final int getDefaultALChannelLayout(final int channelCount)
Returns the default AL channel layout matching the given channel count, or ALConstants#AL_NONE.
Definition: ALHelpers.java:395
static final int getALSampleType(final int sampleSize, final boolean signed, final boolean fixedP)
Returns the AL sample type matching the given audio type attributes, or ALConstants#AL_NONE.
Definition: ALHelpers.java:446
static final int ALC_MAJOR_VERSION
Define "ALC_MAJOR_VERSION" with expression '0x1000', CType: int.
static final int ALC_MINOR_VERSION
Define "ALC_MINOR_VERSION" with expression '0x1001', CType: int.
static final int ALC_FREQUENCY
Define "ALC_FREQUENCY" with expression '0x1007', CType: int.
static final int ALC_EXTENSIONS
Define "ALC_EXTENSIONS" with expression '0x1006', CType: int.
static final int ALC_MONO_SOURCES
Define "ALC_MONO_SOURCES" with expression '0x1010', CType: int.
static final int ALC_REFRESH
Define "ALC_REFRESH" with expression '0x1008', CType: int.
String alcGetString(ALCdevice device, int param)
Entry point (through function pointer) to C language function: const ALCchar * alcGetString(ALCdevi...
void alcGetIntegerv(ALCdevice device, int param, int size, IntBuffer values)
Entry point (through function pointer) to C language function: void alcGetIntegerv(ALCdevice * dev...
static final int AL_PLAYING
Define "AL_PLAYING" with expression '0x1012', CType: int.
static final int AL_VERSION
Define "AL_VERSION" with expression '0xB002', CType: int.
static final int AL_EXTENSIONS
Define "AL_EXTENSIONS" with expression '0xB004', CType: int.
static final int AL_NONE
Define "AL_NONE" with expression '0', CType: int.
static final int AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT
Define "AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT" with expression '0x19A5', CType: int.
static final int AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT
Define "AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT" with expression '0x19A4', CType: int.
JavaCallback interface: ALEVENTPROCSOFT -> void (*ALEVENTPROCSOFT)(ALenum eventType,...
Definition: ALExt.java:28
void alEventControlSOFT(int count, IntBuffer types, boolean enable)
Entry point (through function pointer) to C language function: void alEventControlSOFT(ALsizei coun...
void alEventCallbackSOFT(ALEVENTPROCSOFT callback, ALCcontext userParam)
Entry point (through function pointer) to C language function: void alEventCallbackSOFT(ALEVENTPROC...
void alBufferSamplesSOFT(int buffer, int samplerate, int internalformat, int samples, int channels, int type, Buffer data)
Entry point (through function pointer) to C language function: void alBufferSamplesSOFT(ALuint buff...
void alBufferData(int buffer, int format, Buffer data, int size, int samplerate)
Entry point (through function pointer) to C language function: void alBufferData(ALuint buffer,...
int alGetError()
Entry point (through function pointer) to C language function: ALenum alGetError()
void alGenBuffers(int n, IntBuffer buffers)
Entry point (through function pointer) to C language function: void alGenBuffers(ALsizei n,...
boolean alIsExtensionPresent(String extname)
Entry point (through function pointer) to C language function: ALboolean alIsExtensionPresent(const...
void alGetSourcei(int source, int param, IntBuffer value)
Entry point (through function pointer) to C language function: void alGetSourcei(ALuint source,...
void alSourcei(int source, int param, int value)
Entry point (through function pointer) to C language function: void alSourcei(ALuint source,...
void alDeleteBuffers(int n, IntBuffer buffers)
Entry point (through function pointer) to C language function: void alDeleteBuffers(ALsizei n,...
String alGetString(int param)
Entry point (through function pointer) to C language function: const ALchar * alGetString(ALenum pa...