-
Notifications
You must be signed in to change notification settings - Fork 292
/
AudioSink.java
480 lines (427 loc) · 19.4 KB
/
AudioSink.java
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
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.exoplayer.audio;
import static java.lang.annotation.ElementType.TYPE_USE;
import android.media.AudioDeviceInfo;
import android.media.AudioTrack;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.AuxEffectInfo;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.PlaybackParameters;
import androidx.media3.common.Player;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.ExoPlaybackException;
import androidx.media3.exoplayer.analytics.PlayerId;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.nio.ByteBuffer;
/**
* A sink that consumes audio data.
*
* <p>Before starting playback, specify the input audio format by calling {@link #configure(Format,
* int, int[])}.
*
* <p>Call {@link #handleBuffer(ByteBuffer, long, int)} to write data, and {@link
* #handleDiscontinuity()} when the data being fed is discontinuous. Call {@link #play()} to start
* playing the written data.
*
* <p>Call {@link #configure(Format, int, int[])} whenever the input format changes. The sink will
* be reinitialized on the next call to {@link #handleBuffer(ByteBuffer, long, int)}.
*
* <p>Call {@link #flush()} to prepare the sink to receive audio data from a new playback position.
*
* <p>Call {@link #playToEndOfStream()} repeatedly to play out all data when no more input buffers
* will be provided via {@link #handleBuffer(ByteBuffer, long, int)} until the next {@link
* #flush()}. Call {@link #reset()} when the instance is no longer required.
*
* <p>The implementation may be backed by a platform {@link AudioTrack}. In this case, {@link
* #setAudioSessionId(int)}, {@link #setAudioAttributes(AudioAttributes)}, {@link
* #enableTunnelingV21()} and {@link #disableTunneling()} may be called before writing data to the
* sink. These methods may also be called after writing data to the sink, in which case it will be
* reinitialized as required. For implementations that are not based on platform {@link
* AudioTrack}s, calling methods relating to audio sessions, audio attributes, and tunneling may
* have no effect.
*/
@UnstableApi
public interface AudioSink {
/** Listener for audio sink events. */
interface Listener {
/**
* Called when the audio sink handles a buffer whose timestamp is discontinuous with the last
* buffer handled since it was reset.
*/
void onPositionDiscontinuity();
/**
* Called when the audio sink's position has increased for the first time since it was last
* paused or flushed.
*
* @param playoutStartSystemTimeMs The approximate derived {@link System#currentTimeMillis()} at
* which playout started. Only valid if the audio track has not underrun.
*/
default void onPositionAdvancing(long playoutStartSystemTimeMs) {}
/**
* Called when the audio sink runs out of data.
*
* <p>An audio sink implementation may never call this method (for example, if audio data is
* consumed in batches rather than based on the sink's own clock).
*
* @param bufferSize The size of the sink's buffer, in bytes.
* @param bufferSizeMs The size of the sink's buffer, in milliseconds, if it is configured for
* PCM output. {@link C#TIME_UNSET} if it is configured for encoded audio output, as the
* buffered media can have a variable bitrate so the duration may be unknown.
* @param elapsedSinceLastFeedMs The time since the sink was last fed data, in milliseconds.
*/
void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs);
/**
* Called when skipping silences is enabled or disabled.
*
* @param skipSilenceEnabled Whether skipping silences is enabled.
*/
void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled);
/** Called when the offload buffer has been partially emptied. */
default void onOffloadBufferEmptying() {}
/** Called when the offload buffer has been filled completely. */
default void onOffloadBufferFull() {}
/**
* Called when {@link AudioSink} has encountered an error.
*
* <p>If the sink writes to a platform {@link AudioTrack}, this will called for all {@link
* AudioTrack} errors.
*
* <p>This method being called does not indicate that playback has failed, or that it will fail.
* The player may be able to recover from the error (for example by recreating the AudioTrack,
* possibly with different settings) and continue. Hence applications should <em>not</em>
* implement this method to display a user visible error or initiate an application level retry
* ({@link Player.Listener#onPlayerError} is the appropriate place to implement such behavior).
* This method is called to provide the application with an opportunity to log the error if it
* wishes to do so.
*
* <p>Fatal errors that cannot be recovered will be reported wrapped in a {@link
* ExoPlaybackException} by {@link Player.Listener#onPlayerError(PlaybackException)}.
*
* @param audioSinkError The error that occurred. Typically an {@link InitializationException},
* a {@link WriteException}, or an {@link UnexpectedDiscontinuityException}.
*/
default void onAudioSinkError(Exception audioSinkError) {}
}
/** Thrown when a failure occurs configuring the sink. */
final class ConfigurationException extends Exception {
/** Input {@link Format} of the sink when the configuration failure occurs. */
public final Format format;
/** Creates a new configuration exception with the specified {@code cause} and no message. */
public ConfigurationException(Throwable cause, Format format) {
super(cause);
this.format = format;
}
/** Creates a new configuration exception with the specified {@code message} and no cause. */
public ConfigurationException(String message, Format format) {
super(message);
this.format = format;
}
}
/** Thrown when a failure occurs initializing the sink. */
final class InitializationException extends Exception {
/** The underlying {@link AudioTrack}'s state. */
public final int audioTrackState;
/** If the exception can be recovered by recreating the sink. */
public final boolean isRecoverable;
/** The input {@link Format} of the sink when the error occurs. */
public final Format format;
/**
* Creates a new instance.
*
* @param audioTrackState The underlying {@link AudioTrack}'s state.
* @param sampleRate The requested sample rate in Hz.
* @param channelConfig The requested channel configuration.
* @param bufferSize The requested buffer size in bytes.
* @param format The input format of the sink when the error occurs.
* @param isRecoverable Whether the exception can be recovered by recreating the sink.
* @param audioTrackException Exception thrown during the creation of the {@link AudioTrack}.
*/
public InitializationException(
int audioTrackState,
int sampleRate,
int channelConfig,
int bufferSize,
Format format,
boolean isRecoverable,
@Nullable Exception audioTrackException) {
super(
"AudioTrack init failed "
+ audioTrackState
+ " "
+ ("Config(" + sampleRate + ", " + channelConfig + ", " + bufferSize + ")")
+ (isRecoverable ? " (recoverable)" : ""),
audioTrackException);
this.audioTrackState = audioTrackState;
this.isRecoverable = isRecoverable;
this.format = format;
}
}
/** Thrown when a failure occurs writing to the sink. */
final class WriteException extends Exception {
/**
* The error value returned from the sink implementation. If the sink writes to a platform
* {@link AudioTrack}, this will be the error value returned from {@link
* AudioTrack#write(byte[], int, int)} or {@link AudioTrack#write(ByteBuffer, int, int)}.
* Otherwise, the meaning of the error code depends on the sink implementation.
*/
public final int errorCode;
/** If the exception can be recovered by recreating the sink. */
public final boolean isRecoverable;
/** The input {@link Format} of the sink when the error occurs. */
public final Format format;
/**
* Creates an instance.
*
* @param errorCode The error value returned from the sink implementation.
* @param format The input format of the sink when the error occurs.
* @param isRecoverable Whether the exception can be recovered by recreating the sink.
*/
public WriteException(int errorCode, Format format, boolean isRecoverable) {
super("AudioTrack write failed: " + errorCode);
this.isRecoverable = isRecoverable;
this.errorCode = errorCode;
this.format = format;
}
}
/** Thrown when the sink encounters an unexpected timestamp discontinuity. */
final class UnexpectedDiscontinuityException extends Exception {
/** The actual presentation time of a sample, in microseconds. */
public final long actualPresentationTimeUs;
/** The expected presentation time of a sample, in microseconds. */
public final long expectedPresentationTimeUs;
/**
* Creates an instance.
*
* @param actualPresentationTimeUs The actual presentation time of a sample, in microseconds.
* @param expectedPresentationTimeUs The expected presentation time of a sample, in
* microseconds.
*/
public UnexpectedDiscontinuityException(
long actualPresentationTimeUs, long expectedPresentationTimeUs) {
super(
"Unexpected audio track timestamp discontinuity: expected "
+ expectedPresentationTimeUs
+ ", got "
+ actualPresentationTimeUs);
this.actualPresentationTimeUs = actualPresentationTimeUs;
this.expectedPresentationTimeUs = expectedPresentationTimeUs;
}
}
/**
* The level of support the sink provides for a format. One of {@link
* #SINK_FORMAT_SUPPORTED_DIRECTLY}, {@link #SINK_FORMAT_SUPPORTED_WITH_TRANSCODING} or {@link
* #SINK_FORMAT_UNSUPPORTED}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef({
SINK_FORMAT_SUPPORTED_DIRECTLY,
SINK_FORMAT_SUPPORTED_WITH_TRANSCODING,
SINK_FORMAT_UNSUPPORTED
})
@interface SinkFormatSupport {}
/** The sink supports the format directly, without the need for internal transcoding. */
int SINK_FORMAT_SUPPORTED_DIRECTLY = 2;
/**
* The sink supports the format, but needs to transcode it internally to do so. Internal
* transcoding may result in lower quality and higher CPU load in some cases.
*/
int SINK_FORMAT_SUPPORTED_WITH_TRANSCODING = 1;
/** The sink does not support the format. */
int SINK_FORMAT_UNSUPPORTED = 0;
/** Returned by {@link #getCurrentPositionUs(boolean)} when the position is not set. */
long CURRENT_POSITION_NOT_SET = Long.MIN_VALUE;
/**
* Sets the listener for sink events, which should be the audio renderer.
*
* @param listener The listener for sink events, which should be the audio renderer.
*/
void setListener(Listener listener);
/**
* Sets the {@link PlayerId} of the player using this audio sink.
*
* @param playerId The {@link PlayerId}, or null to clear a previously set id.
*/
default void setPlayerId(@Nullable PlayerId playerId) {}
/**
* Returns whether the sink supports a given {@link Format}.
*
* @param format The format.
* @return Whether the sink supports the format.
*/
boolean supportsFormat(Format format);
/**
* Returns the level of support that the sink provides for a given {@link Format}.
*
* @param format The format.
* @return The level of support provided.
*/
@SinkFormatSupport
int getFormatSupport(Format format);
/**
* Returns the playback position in the stream starting at zero, in microseconds, or {@link
* #CURRENT_POSITION_NOT_SET} if it is not yet available.
*
* @param sourceEnded Specify {@code true} if no more input buffers will be provided.
* @return The playback position relative to the start of playback, in microseconds.
*/
long getCurrentPositionUs(boolean sourceEnded);
/**
* Configures (or reconfigures) the sink.
*
* @param inputFormat The format of audio data provided in the input buffers.
* @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to infer a
* suitable buffer size.
* @param outputChannels A mapping from input to output channels that is applied to this sink's
* input as a preprocessing step, if handling PCM input. Specify {@code null} to leave the
* input unchanged. Otherwise, the element at index {@code i} specifies index of the input
* channel to map to output channel {@code i} when preprocessing input buffers. After the map
* is applied the audio data will have {@code outputChannels.length} channels.
* @throws ConfigurationException If an error occurs configuring the sink.
*/
void configure(Format inputFormat, int specifiedBufferSize, @Nullable int[] outputChannels)
throws ConfigurationException;
/** Starts or resumes consuming audio if initialized. */
void play();
/** Signals to the sink that the next buffer may be discontinuous with the previous buffer. */
void handleDiscontinuity();
/**
* Attempts to process data from a {@link ByteBuffer}, starting from its current position and
* ending at its limit (exclusive). The position of the {@link ByteBuffer} is advanced by the
* number of bytes that were handled. {@link Listener#onPositionDiscontinuity()} will be called if
* {@code presentationTimeUs} is discontinuous with the last buffer handled since the last reset.
*
* <p>Returns whether the data was handled in full. If the data was not handled in full then the
* same {@link ByteBuffer} must be provided to subsequent calls until it has been fully consumed,
* except in the case of an intervening call to {@link #flush()} (or to {@link #configure(Format,
* int, int[])} that causes the sink to be flushed).
*
* @param buffer The buffer containing audio data.
* @param presentationTimeUs The presentation timestamp of the buffer in microseconds.
* @param encodedAccessUnitCount The number of encoded access units in the buffer, or 1 if the
* buffer contains PCM audio. This allows batching multiple encoded access units in one
* buffer.
* @return Whether the buffer was handled fully.
* @throws InitializationException If an error occurs initializing the sink.
* @throws WriteException If an error occurs writing the audio data.
*/
boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs, int encodedAccessUnitCount)
throws InitializationException, WriteException;
/**
* Processes any remaining data. {@link #isEnded()} will return {@code true} when no data remains.
*
* @throws WriteException If an error occurs draining data to the sink.
*/
void playToEndOfStream() throws WriteException;
/**
* Returns whether {@link #playToEndOfStream} has been called and all buffers have been processed.
*/
boolean isEnded();
/** Returns whether the sink has data pending that has not been consumed yet. */
boolean hasPendingData();
/**
* Attempts to set the playback parameters. The audio sink may override these parameters if they
* are not supported.
*
* @param playbackParameters The new playback parameters to attempt to set.
*/
void setPlaybackParameters(PlaybackParameters playbackParameters);
/** Returns the active {@link PlaybackParameters}. */
PlaybackParameters getPlaybackParameters();
/** Sets whether silences should be skipped in the audio stream. */
void setSkipSilenceEnabled(boolean skipSilenceEnabled);
/** Returns whether silences are skipped in the audio stream. */
boolean getSkipSilenceEnabled();
/**
* Sets attributes for audio playback. If the attributes have changed and if the sink is not
* configured for use with tunneling, then it is reset and the audio session id is cleared.
*
* <p>If the sink is configured for use with tunneling then the audio attributes are ignored. The
* sink is not reset and the audio session id is not cleared. The passed attributes will be used
* if the sink is later re-configured into non-tunneled mode.
*
* @param audioAttributes The attributes for audio playback.
*/
void setAudioAttributes(AudioAttributes audioAttributes);
/**
* Returns the audio attributes used for audio playback, or {@code null} if the sink does not use
* audio attributes.
*/
@Nullable
AudioAttributes getAudioAttributes();
/** Sets the audio session id. */
void setAudioSessionId(int audioSessionId);
/** Sets the auxiliary effect. */
void setAuxEffectInfo(AuxEffectInfo auxEffectInfo);
/**
* Sets the preferred audio device.
*
* @param audioDeviceInfo The preferred {@linkplain AudioDeviceInfo audio device}, or null to
* restore the default.
*/
@RequiresApi(23)
default void setPreferredDevice(@Nullable AudioDeviceInfo audioDeviceInfo) {}
/**
* Enables tunneling, if possible. The sink is reset if tunneling was previously disabled.
* Enabling tunneling is only possible if the sink is based on a platform {@link AudioTrack}, and
* requires platform API version 21 onwards.
*
* @throws IllegalStateException Thrown if enabling tunneling on platform API version < 21.
*/
void enableTunnelingV21();
/**
* Disables tunneling. If tunneling was previously enabled then the sink is reset and any audio
* session id is cleared.
*/
void disableTunneling();
/**
* Sets the playback volume.
*
* @param volume Linear output gain to apply to all channels. Should be in the range [0.0, 1.0].
*/
void setVolume(float volume);
/** Pauses playback. */
void pause();
/**
* Flushes the sink, after which it is ready to receive buffers from a new playback position.
*
* <p>The audio session may remain active until {@link #reset()} is called.
*/
void flush();
/**
* Flushes the sink, after which it is ready to receive buffers from a new playback position.
*
* <p>Does not release the {@link AudioTrack} held by the sink.
*
* <p>This method is experimental, and will be renamed or removed in a future release.
*
* <p>Only for experimental use as part of {@link
* MediaCodecAudioRenderer#experimentalSetEnableKeepAudioTrackOnSeek(boolean)}.
*/
void experimentalFlushWithoutAudioTrackRelease();
/** Resets the sink, releasing any resources that it currently holds. */
void reset();
}