1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
|
/**
* Copyright 2013-2023 JogAmp Community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of JogAmp Community.
*/
package com.jogamp.common.av;
import java.nio.ByteBuffer;
import jogamp.common.Debug;
public interface AudioSink {
public static final boolean DEBUG = Debug.debug("AudioSink");
/** Default frame duration in millisecond, i.e. 1 frame per {@value} ms. */
public static final int DefaultFrameDuration = 32;
/** Initial audio queue size in milliseconds. {@value} ms, i.e. 16 frames per 32 ms. See {@link #init(AudioFormat, float, int, int, int)}.*/
public static final int DefaultInitialQueueSize = 16 * 32; // 512 ms
/** Audio queue grow size in milliseconds. {@value} ms, i.e. 16 frames per 32 ms. See {@link #init(AudioFormat, float, int, int, int)}.*/
public static final int DefaultQueueGrowAmount = 16 * 32; // 512 ms
/** Audio queue limit w/ video in milliseconds. {@value} ms, i.e. 96 frames per 32 ms. See {@link #init(AudioFormat, float, int, int, int)}.*/
public static final int DefaultQueueLimitWithVideo = 96 * 32; // 3072 ms
/** Audio queue limit w/o video in milliseconds. {@value} ms, i.e. 32 frames per 32 ms. See {@link #init(AudioFormat, float, int, int, int)}.*/
public static final int DefaultQueueLimitAudioOnly = 32 * 32; // 1024 ms
/** Default {@link AudioFormat}, [type PCM, sampleRate 44100, sampleSize 16, channelCount 2, signed, fixedP, !planar, littleEndian]. */
public static final AudioFormat DefaultFormat = new AudioFormat(44100, 16, 2, true /* signed */,
true /* fixed point */, false /* planar */, true /* littleEndian */);
/**
* Abstract audio frame tracking {@link TimeFrameI} pts and size in bytes.
* <p>
* Implementations may assign actual data to queue frames from streaming, see {@link AudioDataFrame}.
* </p>
*/
public static abstract class AudioFrame extends TimeFrameI {
protected int byteSize;
public AudioFrame() {
this.byteSize = 0;
}
public AudioFrame(final int pts, final int duration, final int byteCount) {
super(pts, duration);
this.byteSize=byteCount;
}
/** Get this frame's size in bytes. */
public final int getByteSize() { return byteSize; }
/** Set this frame's size in bytes. */
public final void setByteSize(final int size) { this.byteSize=size; }
@Override
public String toString() {
return "AudioFrame[pts " + pts + " ms, l " + duration + " ms, "+byteSize + " bytes]";
}
}
/**
* Audio data frame example of {@link AudioFrame} with actual audio data being attached.
*/
public static class AudioDataFrame extends AudioFrame {
protected final ByteBuffer data;
public AudioDataFrame(final int pts, final int duration, final ByteBuffer bytes, final int byteCount) {
super(pts, duration, byteCount);
if( byteCount > bytes.remaining() ) {
throw new IllegalArgumentException("Give size "+byteCount+" exceeds remaining bytes in ls "+bytes+". "+this);
}
this.data=bytes;
}
/** Get this frame's data. */
public final ByteBuffer getData() { return data; }
@Override
public String toString() {
return "AudioDataFrame[pts " + pts + " ms, l " + duration + " ms, "+byteSize + " bytes, " + data + "]";
}
}
/**
* Exclusively locks this instance for the calling thread, if implementation utilizes locking.
* @see #unlockExclusive()
*/
public void lockExclusive();
/**
* Releases the exclusive lock for the calling thread, if implementation utilizes locking.
* @see #lockExclusive()
*/
public void unlockExclusive();
/**
* Returns the <code>available state</code> of this instance.
* <p>
* The <code>available state</code> is affected by this instance
* overall availability, i.e. after instantiation,
* as well as by {@link #destroy()}.
* </p>
*/
public boolean isAvailable();
/** Returns the playback speed. */
public float getPlaySpeed();
/**
* Sets the playback speed.
* <p>
* To simplify test, play speed is <i>normalized</i>, i.e.
* <ul>
* <li><code>1.0f</code>: if <code> Math.abs(1.0f - rate) < 0.01f </code></li>
* </ul>
* </p>
* @return true if successful, otherwise false, i.e. due to unsupported value range of implementation.
*/
public boolean setPlaySpeed(float s);
/** Returns the volume. */
public float getVolume();
/**
* Sets the volume [0f..1f].
* <p>
* To simplify test, volume is <i>normalized</i>, i.e.
* <ul>
* <li><code>0.0f</code>: if <code> Math.abs(v) < 0.01f </code></li>
* <li><code>1.0f</code>: if <code> Math.abs(1.0f - v) < 0.01f </code></li>
* </ul>
* </p>
* @return true if successful, otherwise false, i.e. due to unsupported value range of implementation.
*/
public boolean setVolume(float v);
/**
* Returns the preferred sample-rate of this sink, i.e. mixer frequency in Hz, e.g. 41000 or 48000.
* <p>
* The preferred sample-rate is guaranteed to be supported
* and shall reflect this sinks most native format,
* i.e. best performance w/o data conversion.
* </p>
* <p>
* May return {@link AudioSink#DefaultFormat}'s 44100 default if undefined.
* </p>
* @see #init(AudioFormat, float, int, int, int)
* @see #isSupported(AudioFormat)
*/
public int getPreferredSampleRate();
/**
* Returns the number of sources the used device is capable to mix.
* <p>
* This device attribute is only formally exposed and not used,
* since an audio sink is only utilizing one source.
* </p>
* <p>
* May return <code>-1</code> if undefined.
* </p>
* @return
*/
public int getSourceCount();
/**
* Returns the default (minimum) latency in seconds
* <p>
* Latency might be the reciprocal mixer-refresh-interval [Hz], e.g. 50 Hz refresh-rate = 20ms minimum latency.
* </p>
* <p>
* May return 20ms for a 50 Hz refresh rate if undefined.
* </p>
*/
public float getDefaultLatency();
/**
* Returns the preferred {@link AudioFormat} by this sink.
* <p>
* The preferred format is guaranteed to be supported
* and shall reflect this sinks most native format,
* i.e. best performance w/o data conversion.
* </p>
* <p>
* Known {@link #AudioFormat} attributes considered by implementations:
* <ul>
* <li>ALAudioSink: {@link AudioFormat#sampleRate}.
* </ul>
* </p>
* <p>
* May return {@link AudioSink#DefaultFormat} if undefined.
* </p>
* @see #init(AudioFormat, float, int, int, int)
* @see #isSupported(AudioFormat)
* @see #getPreferredSampleRate()
*/
public AudioFormat getPreferredFormat();
/** Return the maximum number of supported channels, e.g. 1 for mono, 2 for stereo, etc. */
public int getMaxSupportedChannels();
/**
* Returns true if the given format is supported by the sink, otherwise false.
* @see #init(AudioFormat, float, int, int, int)
* @see #getPreferredFormat()
*/
public boolean isSupported(AudioFormat format);
/**
* Initializes the sink.
* <p>
* Implementation must match the given <code>requestedFormat</code> {@link AudioFormat}.
* </p>
* <p>
* Caller shall validate <code>requestedFormat</code> via {@link #isSupported(AudioFormat)}
* beforehand and try to find a suitable supported one.
* {@link #getPreferredFormat()} and {@link #getMaxSupportedChannels()} may help.
* </p>
* @param requestedFormat the requested {@link AudioFormat}.
* @param frameDuration average frame duration in milliseconds.
* May assist a caching {@link AudioFrame} based implementation to limit waiting when dequeuing frames, e.g. JOAL's ALAudioSink.
* Also may assist to adjust latency of the backend, as currently used for JOAL's ALAudioSink.
* A value below 30ms or {@link #DefaultFrameDuration} may increase the audio processing load.
* Set to {@link #DefaultFrameDuration}, if <code>frameDuration < 1 ms</code>.
* @param initialQueueSize initial time in milliseconds to queue in this sink, see {@link #DefaultInitialQueueSize}.
* @param queueGrowAmount time in milliseconds to grow queue if full, see {@link #DefaultQueueGrowAmount}.
* @param queueLimit maximum time in milliseconds the queue can hold (and grow), see {@link #DefaultQueueLimitWithVideo} and {@link #DefaultQueueLimitAudioOnly}.
* @return true if successful, otherwise false
*/
public boolean init(AudioFormat requestedFormat, int frameDuration,
int initialQueueSize, int queueGrowAmount, int queueLimit);
/**
* Returns the {@link AudioFormat} as chosen by {@link #init(AudioFormat, float, int, int, int)},
* i.e. it shall match the <i>requestedFormat</i>.
*/
public AudioFormat getChosenFormat();
/**
* Returns the (minimum) latency in seconds of this sink as set by {@link #init(AudioFormat, float, int, int, int)}, see {@link #getDefaultLatency()}.
* <p>
* Latency might be the reciprocal mixer-refresh-interval [Hz], e.g. 50 Hz refresh-rate = 20ms minimum latency.
* </p>
* @see #init(AudioFormat, float, int, int, int)
*/
public float getLatency();
/**
* Returns true, if {@link #play()} has been requested <i>and</i> the sink is still playing,
* otherwise false.
*/
public boolean isPlaying();
/**
* Play buffers queued via {@link #enqueueData(AudioFrame)} from current internal position.
* If no buffers are yet queued or the queue runs empty, playback is being continued when buffers are enqueued later on.
* @see #enqueueData(AudioFrame)
* @see #pause()
*/
public void play();
/**
* Pause playing buffers while keeping enqueued data incl. it's internal position.
* @see #play()
* @see #flush()
* @see #enqueueData(AudioFrame)
*/
public void pause();
/**
* Flush all queued buffers, implies {@link #pause()}.
* <p>
* {@link #init(AudioFormat, float, int, int, int)} must be called first.
* </p>
* @see #play()
* @see #pause()
* @see #enqueueData(AudioFrame)
* @see #init(AudioFormat, float, int, int, int)
*/
public void flush();
/** Destroys this instance, i.e. closes all streams and devices allocated. */
public void destroy();
/**
* Returns the number of allocated buffers as requested by
* {@link #init(AudioFormat, float, int, int, int)}.
* @see #init(AudioFormat, float, int, int, int)
*/
public int getFrameCount();
/**
* Returns the current enqueued frames count since {@link #init(AudioFormat, float, int, int, int)}.
* @see #init(AudioFormat, float, int, int, int)
*/
public int getEnqueuedFrameCount();
/**
* Returns the current number of frames queued for playing.
* <p>
* {@link #init(AudioFormat, float, int, int, int)} must be called first.
* </p>
* @see #init(AudioFormat, float, int, int, int)
*/
public int getQueuedFrameCount();
/**
* Returns the current number of bytes queued for playing.
* <p>
* {@link #init(AudioFormat, float, int, int, int)} must be called first.
* </p>
* @see #init(AudioFormat, float, int, int, int)
*/
public int getQueuedByteCount();
/**
* Returns the current queued frame time in milliseconds for playing.
* <p>
* {@link #init(AudioFormat, float, int, int, int)} must be called first.
* </p>
* @see #init(AudioFormat, float, int, int, int)
*/
public int getQueuedTime();
/**
* Return the current audio presentation timestamp (PTS) in milliseconds.
*/
public int getPTS();
/**
* Returns the current number of frames in the sink available for writing.
* <p>
* {@link #init(AudioFormat, float, int, int, int)} must be called first.
* </p>
* @see #init(AudioFormat, float, int, int, int)
*/
public int getFreeFrameCount();
/**
* Enqueue <code>byteCount</code> bytes of the remaining bytes of the given NIO {@link ByteBuffer} to this sink.
* <p>
* The data must comply with the chosen {@link AudioFormat} as set via {@link #init(AudioFormat, float, int, int, int)}.
* </p>
* <p>
* {@link #init(AudioFormat, float, int, int, int)} must be called first.
* </p>
* @returns the enqueued internal {@link AudioFrame}.
* @see #init(AudioFormat, float, int, int, int)
*/
public AudioFrame enqueueData(int pts, ByteBuffer bytes, int byteCount);
}
|