ExoPlayer module that provides the core ExoPlayer implementation for local media playback on Android, supporting various media formats and streaming protocols
—
Audio rendering in ExoPlayer handles decoding and outputting audio data to the Android audio system. The audio pipeline includes decoders, processors, and sinks that work together to provide high-quality audio playback with support for various codecs and audio effects.
The AudioSink interface defines how audio data is consumed and output to audio hardware.
public interface AudioSink {
/**
* Exception thrown when audio sink configuration fails.
*/
final class ConfigurationException extends Exception {
public final Format format;
public ConfigurationException(String message, Format format);
public ConfigurationException(Throwable cause, Format format);
public ConfigurationException(String message, Throwable cause, Format format);
}
/**
* Exception thrown when audio sink write operations fail.
*/
final class WriteException extends Exception {
public final int errorCode;
public final Format format;
public WriteException(int errorCode, Format format, Throwable cause);
}
/**
* Configures the sink for the specified audio format.
*
* @param format The audio format
* @param specifiedBufferSize The buffer size in bytes, or 0 for default
* @param outputChannels The output channel configuration, or null for default
* @throws ConfigurationException If configuration fails
*/
void configure(Format format, int specifiedBufferSize, @Nullable int[] outputChannels) throws ConfigurationException;
/**
* Starts or resumes audio output.
*/
void play();
/**
* Pauses audio output.
*/
void pause();
/**
* Plays to the end of the stream.
*
* @throws WriteException If writing fails
*/
void playToEndOfStream() throws WriteException;
/**
* Resets the sink.
*/
void reset();
/**
* Releases the sink.
*/
void release();
/**
* Sets the audio volume.
*
* @param volume The volume (0.0 to 1.0)
*/
void setVolume(float volume);
/**
* Sets the audio attributes.
*
* @param audioAttributes The audio attributes
*/
void setAudioAttributes(AudioAttributes audioAttributes);
/**
* Sets auxiliary effect information.
*
* @param auxEffectInfo The auxiliary effect info
*/
void setAuxEffectInfo(AuxEffectInfo auxEffectInfo);
/**
* Returns the current playback parameters.
*
* @return The current playback parameters
*/
PlaybackParameters getPlaybackParameters();
/**
* Sets playback parameters for speed and pitch adjustment.
*
* @param playbackParameters The playback parameters
*/
void setPlaybackParameters(PlaybackParameters playbackParameters);
/**
* Attempts to write audio data to the sink.
*
* @param buffer The audio data buffer
* @param presentationTimeUs The presentation time in microseconds
* @param encodedAccessUnitCount The number of encoded access units
* @return Whether the buffer was consumed
* @throws WriteException If writing fails
*/
boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs, int encodedAccessUnitCount) throws WriteException;
/**
* Returns the buffer size for the AudioTrack in microseconds.
*
* @return The buffer size in microseconds
*/
long getAudioTrackBufferSizeUs();
}The default implementation of AudioSink using Android's AudioTrack.
public final class DefaultAudioSink implements AudioSink {
/**
* Builder for DefaultAudioSink.
*/
public static final class Builder {
/**
* Creates a builder.
*/
public Builder();
/**
* Sets the audio capabilities.
*
* @param audioCapabilities The audio capabilities
* @return This builder
*/
public Builder setAudioCapabilities(AudioCapabilities audioCapabilities);
/**
* Sets the audio processors.
*
* @param audioProcessors The audio processors
* @return This builder
*/
public Builder setAudioProcessors(AudioProcessor[] audioProcessors);
/**
* Sets whether to enable float output.
*
* @param enableFloatOutput Whether to enable float output
* @return This builder
*/
public Builder setEnableFloatOutput(boolean enableFloatOutput);
/**
* Sets whether to enable AudioTrack playback parameters.
*
* @param enableAudioTrackPlaybackParams Whether to enable AudioTrack playback parameters
* @return This builder
*/
public Builder setEnableAudioTrackPlaybackParams(boolean enableAudioTrackPlaybackParams);
/**
* Sets the offload mode.
*
* @param offloadMode The offload mode
* @return This builder
*/
public Builder setOffloadMode(@OffloadMode int offloadMode);
/**
* Builds the DefaultAudioSink.
*
* @return The built DefaultAudioSink
*/
public DefaultAudioSink build();
}
// Offload modes
public static final int OFFLOAD_MODE_DISABLED = 0;
public static final int OFFLOAD_MODE_ENABLED_GAPLESS_REQUIRED = 1;
public static final int OFFLOAD_MODE_ENABLED_GAPLESS_NOT_REQUIRED = 2;
public static final int OFFLOAD_MODE_ENABLED_GAPLESS_DISABLED = 3;
}Audio renderer that uses MediaCodec for decoding.
public class MediaCodecAudioRenderer extends MediaCodecRenderer implements MediaClock {
/**
* Creates a MediaCodecAudioRenderer.
*
* @param context The context
* @param mediaCodecSelector The MediaCodec selector
* @param enableDecoderFallback Whether to enable decoder fallback
* @param eventHandler The event handler
* @param eventListener The event listener
* @param audioSink The audio sink
*/
public MediaCodecAudioRenderer(Context context, MediaCodecSelector mediaCodecSelector,
boolean enableDecoderFallback, @Nullable Handler eventHandler,
@Nullable AudioRendererEventListener eventListener, AudioSink audioSink);
/**
* Sets the volume.
*
* @param volume The volume (0.0 to 1.0)
*/
public final void setVolume(float volume);
/**
* Returns the current playback parameters.
*
* @return The current playback parameters
*/
public PlaybackParameters getPlaybackParameters();
/**
* Sets playback parameters.
*
* @param playbackParameters The playback parameters
*/
public void setPlaybackParameters(PlaybackParameters playbackParameters);
/**
* Returns the position according to the media clock.
*
* @return The position in microseconds
*/
@Override
public long getPositionUs();
/**
* Sets the audio session ID.
*
* @param audioSessionId The audio session ID
*/
public void setAudioSessionId(int audioSessionId);
/**
* Sets auxiliary effect information.
*
* @param auxEffectInfo The auxiliary effect info
*/
public void setAuxEffectInfo(AuxEffectInfo auxEffectInfo);
}Listener for audio renderer events.
public interface AudioRendererEventListener {
/**
* Called when the audio renderer is enabled.
*
* @param counters The decoder counters
*/
default void onAudioEnabled(DecoderCounters counters) {}
/**
* Called when the audio session ID changes.
*
* @param audioSessionId The new audio session ID
*/
default void onAudioSessionIdChanged(int audioSessionId) {}
/**
* Called when the audio decoder is initialized.
*
* @param decoderName The decoder name
* @param initializedTimestampMs The initialization timestamp
* @param initializationDurationMs The initialization duration
*/
default void onAudioDecoderInitialized(String decoderName, long initializedTimestampMs, long initializationDurationMs) {}
/**
* Called when the audio input format changes.
*
* @param format The new format
* @param decoderReuseEvaluation The decoder reuse evaluation
*/
default void onAudioInputFormatChanged(Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {}
/**
* Called when the audio position is advancing.
*
* @param playoutStartSystemTimeMs The playout start system time
*/
default void onAudioPositionAdvancing(long playoutStartSystemTimeMs) {}
/**
* Called when audio underrun occurs.
*
* @param bufferSize The buffer size
* @param bufferSizeMs The buffer size in milliseconds
* @param elapsedSinceLastFeedMs Time elapsed since last feed
*/
default void onAudioUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {}
/**
* Called when the audio decoder is released.
*
* @param decoderName The decoder name
*/
default void onAudioDecoderReleased(String decoderName) {}
/**
* Called when the audio renderer is disabled.
*
* @param counters The decoder counters
*/
default void onAudioDisabled(DecoderCounters counters) {}
/**
* Called when the audio sink underruns.
*
* @param bufferSize The buffer size
* @param bufferSizeMs The buffer size in milliseconds
* @param elapsedSinceLastFeedMs Time elapsed since last feed
*/
default void onAudioSinkUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {}
/**
* Called when audio codec error occurs.
*
* @param audioCodecError The audio codec error
*/
default void onAudioCodecError(Exception audioCodecError) {}
}Interface for audio processing components that can modify audio data.
public interface AudioProcessor {
/**
* Data structure for audio format.
*/
final class AudioFormat {
public static final AudioFormat NOT_SET = new AudioFormat(Format.NO_VALUE, Format.NO_VALUE, Format.NO_VALUE);
public final int sampleRate;
public final int channelCount;
@C.PcmEncoding public final int encoding;
public AudioFormat(int sampleRate, int channelCount, @C.PcmEncoding int encoding);
}
/**
* Exception thrown when audio processing fails.
*/
final class UnhandledAudioFormatException extends Exception {
public final AudioFormat inputAudioFormat;
public final AudioFormat outputAudioFormat;
public UnhandledAudioFormatException(AudioFormat inputAudioFormat, AudioFormat outputAudioFormat);
}
/**
* Configures the processor for the specified input format.
*
* @param inputAudioFormat The input audio format
* @return The output audio format
* @throws UnhandledAudioFormatException If the format is not supported
*/
AudioFormat configure(AudioFormat inputAudioFormat) throws UnhandledAudioFormatException;
/**
* Returns whether the processor is active.
*
* @return Whether the processor is active
*/
boolean isActive();
/**
* Queues input data for processing.
*
* @param inputBuffer The input buffer
*/
void queueInput(ByteBuffer inputBuffer);
/**
* Queues end of stream.
*/
void queueEndOfStream();
/**
* Returns a buffer containing processed output data.
*
* @return The output buffer, or EMPTY_BUFFER if no output is available
*/
ByteBuffer getOutput();
/**
* Returns whether the processor has ended.
*
* @return Whether the processor has ended
*/
boolean isEnded();
/**
* Flushes the processor.
*/
void flush();
/**
* Resets the processor.
*/
void reset();
}// Create default audio sink
DefaultAudioSink audioSink = new DefaultAudioSink.Builder().build();
// Create audio renderer
MediaCodecAudioRenderer audioRenderer = new MediaCodecAudioRenderer(
context,
MediaCodecSelector.DEFAULT,
/* enableDecoderFallback= */ false,
/* eventHandler= */ null,
/* eventListener= */ null,
audioSink
);
// Use with custom renderers factory
RenderersFactory renderersFactory = new DefaultRenderersFactory(context) {
@Override
protected void buildAudioRenderers(Context context, int extensionRendererMode,
MediaCodecSelector mediaCodecSelector, boolean enableDecoderFallback,
AudioSink audioSink, Handler eventHandler,
AudioRendererEventListener eventListener,
ArrayList<Renderer> out) {
out.add(new MediaCodecAudioRenderer(context, mediaCodecSelector, enableDecoderFallback,
eventHandler, eventListener, audioSink));
}
};// Configure audio sink with custom settings
DefaultAudioSink audioSink = new DefaultAudioSink.Builder()
.setEnableFloatOutput(true) // Enable high-resolution audio
.setEnableAudioTrackPlaybackParams(true) // Enable speed/pitch adjustment
.setOffloadMode(DefaultAudioSink.OFFLOAD_MODE_ENABLED_GAPLESS_REQUIRED) // Enable offload
.build();AudioRendererEventListener audioListener = new AudioRendererEventListener() {
@Override
public void onAudioEnabled(DecoderCounters counters) {
Log.d(TAG, "Audio enabled");
}
@Override
public void onAudioSessionIdChanged(int audioSessionId) {
Log.d(TAG, "Audio session ID changed: " + audioSessionId);
// Apply audio effects to this session
applyAudioEffects(audioSessionId);
}
@Override
public void onAudioDecoderInitialized(String decoderName, long initializedTimestampMs, long initializationDurationMs) {
Log.d(TAG, "Audio decoder initialized: " + decoderName);
}
@Override
public void onAudioUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
Log.w(TAG, "Audio underrun detected");
}
@Override
public void onAudioCodecError(Exception audioCodecError) {
Log.e(TAG, "Audio codec error", audioCodecError);
}
};
// Use with renderer
MediaCodecAudioRenderer audioRenderer = new MediaCodecAudioRenderer(
context, MediaCodecSelector.DEFAULT, false,
handler, audioListener, audioSink
);// Set volume on audio renderer
audioRenderer.setVolume(0.5f); // 50% volume
// Set playback parameters for speed/pitch adjustment
PlaybackParameters params = new PlaybackParameters(1.5f, 1.0f); // 1.5x speed, normal pitch
audioRenderer.setPlaybackParameters(params);
// Get current playback parameters
PlaybackParameters currentParams = audioRenderer.getPlaybackParameters();// Apply audio effects using session ID
private void applyAudioEffects(int audioSessionId) {
// Create equalizer
Equalizer equalizer = new Equalizer(0, audioSessionId);
equalizer.setEnabled(true);
// Create bass boost
BassBoost bassBoost = new BassBoost(0, audioSessionId);
bassBoost.setStrength((short) 1000); // Maximum bass boost
bassBoost.setEnabled(true);
// Create virtualizer
Virtualizer virtualizer = new Virtualizer(0, audioSessionId);
virtualizer.setStrength((short) 1000); // Maximum virtualization
virtualizer.setEnabled(true);
}public class VolumeAudioProcessor implements AudioProcessor {
private float volume = 1.0f;
private AudioFormat outputFormat;
private ByteBuffer buffer = EMPTY_BUFFER;
private boolean ended;
public void setVolume(float volume) {
this.volume = volume;
}
@Override
public AudioFormat configure(AudioFormat inputAudioFormat) throws UnhandledAudioFormatException {
if (inputAudioFormat.encoding != C.ENCODING_PCM_16BIT) {
throw new UnhandledAudioFormatException(inputAudioFormat, AudioFormat.NOT_SET);
}
this.outputFormat = inputAudioFormat;
return outputFormat;
}
@Override
public boolean isActive() {
return volume != 1.0f;
}
@Override
public void queueInput(ByteBuffer inputBuffer) {
if (!isActive()) {
buffer = inputBuffer;
return;
}
// Apply volume adjustment
ByteBuffer processedBuffer = ByteBuffer.allocate(inputBuffer.remaining());
while (inputBuffer.hasRemaining()) {
short sample = inputBuffer.getShort();
short adjustedSample = (short) (sample * volume);
processedBuffer.putShort(adjustedSample);
}
processedBuffer.flip();
buffer = processedBuffer;
}
@Override
public void queueEndOfStream() {
ended = true;
}
@Override
public ByteBuffer getOutput() {
ByteBuffer outputBuffer = buffer;
buffer = EMPTY_BUFFER;
return outputBuffer;
}
@Override
public boolean isEnded() {
return ended && buffer == EMPTY_BUFFER;
}
@Override
public void flush() {
buffer = EMPTY_BUFFER;
ended = false;
}
@Override
public void reset() {
flush();
outputFormat = null;
}
}Install with Tessl CLI
npx tessl i tessl/maven-androidx-media3--media3-exoplayer