aboutsummaryrefslogtreecommitdiffstats
path: root/src/jogl/classes/com/jogamp/opengl/util/av/AudioSink.java
blob: 1d835dd335da2cb5a0c7218fe7a26968dc90d341 (plain)
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
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
/**
 * Copyright 2013 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.opengl.util.av;

import java.nio.ByteBuffer;

import com.jogamp.opengl.util.TimeFrameI;

import jogamp.opengl.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

    /**
     * Specifies the linear audio PCM format.
     */
    public static class AudioFormat {
        /**
         * @param sampleRate sample rate in Hz (1/s)
         * @param sampleSize sample size in bits
         * @param channelCount number of channels
         * @param signed true if signed number, false for unsigned
         * @param fixedP true for fixed point value, false for unsigned floating point value with a sampleSize of 32 (float) or 64 (double)
         * @param planar true for planar data package (each channel in own data buffer), false for packed data channels interleaved in one buffer.
         * @param littleEndian true for little-endian, false for big endian
         */
        public AudioFormat(int sampleRate, int sampleSize, int channelCount, boolean signed, boolean fixedP, boolean planar, boolean littleEndian) {
            this.sampleRate = sampleRate;
            this.sampleSize = sampleSize;
            this.channelCount = channelCount;
            this.signed = signed;
            this.fixedP = fixedP;
            this.planar = planar;
            this.littleEndian = littleEndian;
            if( !fixedP ) {
                if( sampleSize != 32 && sampleSize != 64 ) {
                    throw new IllegalArgumentException("Floating point: sampleSize "+sampleSize+" bits");
                }
                if( !signed ) {
                    throw new IllegalArgumentException("Floating point: unsigned");
                }
            }
        }

        /** Sample rate in Hz (1/s). */
        public final int sampleRate;
        /** Sample size in bits. */
        public final int sampleSize;
        /** Number of channels. */
        public final int channelCount;
        public final boolean signed;
        /** Fixed or floating point values. Floating point 'float' has {@link #sampleSize} 32, 'double' has {@link #sampleSize} 64. */
        public final boolean fixedP;
        /** Planar or packed samples. If planar, each channel has their own data buffer. If packed, channel data is interleaved in one buffer. */
        public final boolean planar;
        public final boolean littleEndian;


        //
        // Time <-> Bytes
        //

        /**
         * Returns the byte size of the given milliseconds
         * according to {@link #sampleSize}, {@link #channelCount} and {@link #sampleRate}.
         * <p>
         * Time -> Byte Count
         * </p>
         */
        public final int getDurationsByteSize(int millisecs) {
            final int bytesPerSample = sampleSize >>> 3; // /8
            return millisecs * ( channelCount * bytesPerSample * ( sampleRate / 1000 ) );
        }

        /**
         * Returns the duration in milliseconds of the given byte count
         * according to {@link #sampleSize}, {@link #channelCount} and {@link #sampleRate}.
         * <p>
         * Byte Count -> Time
         * </p>
         */
        public final int getBytesDuration(int byteCount) {
            final int bytesPerSample = sampleSize >>> 3; // /8
            return byteCount / ( channelCount * bytesPerSample * ( sampleRate / 1000 ) );
        }

        /**
         * Returns the duration in milliseconds of the given sample count per frame and channel
         * according to the {@link #sampleRate}, i.e.
         * <pre>
         *    ( 1000f * sampleCount ) / sampleRate
         * </pre>
         * <p>
         * Sample Count -> Time
         * </p>
         * @param sampleCount sample count per frame and channel
         */
        public final float getSamplesDuration(int sampleCount) {
            return ( 1000f * sampleCount ) / sampleRate;
        }

        /**
         * Returns the rounded frame count of the given milliseconds and frame duration.
         * <pre>
         *     Math.max( 1, millisecs / frameDuration + 0.5f )
         * </pre>
         * <p>
         * Note: <code>frameDuration</code> can be derived by <i>sample count per frame and channel</i>
         * via {@link #getSamplesDuration(int)}.
         * </p>
         * <p>
         * Frame Time -> Frame Count
         * </p>
         * @param millisecs time in milliseconds
         * @param frameDuration duration per frame in milliseconds.
         */
        public final int getFrameCount(int millisecs, float frameDuration) {
            return Math.max(1, (int) ( millisecs / frameDuration + 0.5f ));
        }

        /**
         * Returns the byte size of given sample count
         * according to the {@link #sampleSize}, i.e.:
         * <pre>
         *  sampleCount * ( sampleSize / 8 )
         * </pre>
         * <p>
         * Note: To retrieve the byte size for all channels,
         * you need to pre-multiply <code>sampleCount</code> with {@link #channelCount}.
         * </p>
         * <p>
         * Sample Count -> Byte Count
         * </p>
         * @param sampleCount sample count
         */
        public final int getSamplesByteCount(int sampleCount) {
            return sampleCount * ( sampleSize >>> 3 );
        }

        /**
         * Returns the sample count of given byte count
         * according to the {@link #sampleSize}, i.e.:
         * <pre>
         *  ( byteCount * 8 ) / sampleSize
         * </pre>
         * <p>
         * Note: If <code>byteCount</code> covers all channels and you request the sample size per channel,
         * you need to divide the result by <code>sampleCount</code> by {@link #channelCount}.
         * </p>
         * <p>
         * Byte Count -> Sample Count
         * </p>
         * @param byteCount number of bytes
         */
        public final int getBytesSampleCount(int byteCount) {
            return ( byteCount << 3 ) / sampleSize;
        }

        @Override
        public String toString() {
            return "AudioDataFormat[sampleRate "+sampleRate+", sampleSize "+sampleSize+", channelCount "+channelCount+
                   ", signed "+signed+", fixedP "+fixedP+", "+(planar?"planar":"packed")+", "+(littleEndian?"little":"big")+"-endian]"; }
    }
    /** 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 */);

    public static abstract class AudioFrame extends TimeFrameI {
        protected int byteSize;

        public AudioFrame() {
            this.byteSize = 0;
        }
        public AudioFrame(int pts, int duration, 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(int size) { this.byteSize=size; }

        @Override
        public String toString() {
            return "AudioFrame[pts " + pts + " ms, l " + duration + " ms, "+byteSize + " bytes]";
        }
    }
    public static class AudioDataFrame extends AudioFrame {
        protected final ByteBuffer data;

        public AudioDataFrame(int pts, int duration, ByteBuffer bytes, 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 + "]";
        }
    }

    /**
     * Returns the <code>initialized state</code> of this instance.
     * <p>
     * The <code>initialized state</code> is affected by this instance
     * overall availability, i.e. after instantiation,
     * as well as by {@link #destroy()}.
     * </p>
     */
    public boolean isInitialized();

    /** 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 {@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>
     * @see #initSink(AudioFormat)
     * @see #isSupported(AudioFormat)
     */
    public AudioFormat getPreferredFormat();

    /** Return the maximum number of supported channels. */
    public int getMaxSupportedChannels();

    /**
     * Returns true if the given format is supported by the sink, otherwise false.
     * @see #initSink(AudioFormat)
     * @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 or fixed frame duration in milliseconds
     *                      helping a caching {@link AudioFrame} based implementation to determine the frame count in the queue.
     *                      See {@link #DefaultFrameDuration}.
     * @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, float 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 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)
     */
    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)}.
     */
    public int getFrameCount();

    /** @return the current enqueued frames count since {@link #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>
     */
    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>
     */
    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>
     */
    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>
     */
    public int getFreeFrameCount();

    /**
     * Enqueue the remaining bytes of the given {@link AudioDataFrame}'s direct ByteBuffer to this sink.
     * <p>
     * The data must comply with the chosen {@link AudioFormat} as returned by {@link #initSink(AudioFormat)}.
     * </p>
     * <p>
     * {@link #init(AudioFormat, float, int, int, int)} must be called first.
     * </p>
     * @returns the enqueued internal {@link AudioFrame}, which may differ from the input <code>audioDataFrame</code>.
     * @deprecated User shall use {@link #enqueueData(int, ByteBuffer, int)}, which allows implementation
     *             to reuse specialized {@link AudioFrame} instances.
     */
    public AudioFrame enqueueData(AudioDataFrame audioDataFrame);

    /**
     * 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 returned by {@link #initSink(AudioFormat)}.
     * </p>
     * <p>
     * {@link #init(AudioFormat, float, int, int, int)} must be called first.
     * </p>
     * @returns the enqueued internal {@link AudioFrame}.
     */
    public AudioFrame enqueueData(int pts, ByteBuffer bytes, int byteCount);
}