Allow AudioTrack to be provided by a customizable provider
PiperOrigin-RevId: 684800579
This commit is contained in:
parent
ad0493b90f
commit
73f97c0371
@ -92,6 +92,17 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
@UnstableApi
|
||||
public final class DefaultAudioSink implements AudioSink {
|
||||
|
||||
/** Provider for {@link AudioTrack} instances. */
|
||||
public interface AudioTrackProvider {
|
||||
|
||||
/** The default provider for {@link AudioTrack} instances. */
|
||||
AudioTrackProvider DEFAULT = new DefaultAudioTrackProvider();
|
||||
|
||||
/** Returns a new {@link AudioTrack} for the given parameters. */
|
||||
AudioTrack getAudioTrack(
|
||||
AudioTrackConfig audioTrackConfig, AudioAttributes audioAttributes, int audioSessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* If an attempt to instantiate an AudioTrack with a buffer size larger than this value fails, a
|
||||
* second attempt is made using this buffer size.
|
||||
@ -271,6 +282,7 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
|
||||
private boolean buildCalled;
|
||||
private AudioTrackBufferSizeProvider audioTrackBufferSizeProvider;
|
||||
private AudioTrackProvider audioTrackProvider;
|
||||
private @MonotonicNonNull AudioOffloadSupportProvider audioOffloadSupportProvider;
|
||||
@Nullable private AudioOffloadListener audioOffloadListener;
|
||||
|
||||
@ -282,6 +294,7 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
this.context = null;
|
||||
audioCapabilities = DEFAULT_AUDIO_CAPABILITIES;
|
||||
audioTrackBufferSizeProvider = AudioTrackBufferSizeProvider.DEFAULT;
|
||||
audioTrackProvider = AudioTrackProvider.DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -293,6 +306,7 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
this.context = context;
|
||||
audioCapabilities = DEFAULT_AUDIO_CAPABILITIES;
|
||||
audioTrackBufferSizeProvider = AudioTrackBufferSizeProvider.DEFAULT;
|
||||
audioTrackProvider = AudioTrackProvider.DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -406,6 +420,18 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link AudioTrackProvider} used to create {@link AudioTrack} instances.
|
||||
*
|
||||
* @param audioTrackProvider The {@link AudioTrackProvider}.
|
||||
* @return This builder.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setAudioTrackProvider(AudioTrackProvider audioTrackProvider) {
|
||||
this.audioTrackProvider = audioTrackProvider;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Builds the {@link DefaultAudioSink}. Must only be called once per Builder instance. */
|
||||
public DefaultAudioSink build() {
|
||||
checkState(!buildCalled);
|
||||
@ -505,6 +531,7 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
private final AudioTrackBufferSizeProvider audioTrackBufferSizeProvider;
|
||||
private final AudioOffloadSupportProvider audioOffloadSupportProvider;
|
||||
@Nullable private final AudioOffloadListener audioOffloadListener;
|
||||
private final AudioTrackProvider audioTrackProvider;
|
||||
|
||||
@Nullable private PlayerId playerId;
|
||||
@Nullable private Listener listener;
|
||||
@ -590,6 +617,7 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
initializationExceptionPendingExceptionHolder = new PendingExceptionHolder<>();
|
||||
writeExceptionPendingExceptionHolder = new PendingExceptionHolder<>();
|
||||
audioOffloadListener = builder.audioOffloadListener;
|
||||
audioTrackProvider = builder.audioTrackProvider;
|
||||
}
|
||||
|
||||
// AudioSink implementation.
|
||||
@ -1045,7 +1073,12 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
|
||||
private AudioTrack buildAudioTrack(Configuration configuration) throws InitializationException {
|
||||
try {
|
||||
AudioTrack audioTrack = configuration.buildAudioTrack(audioAttributes, audioSessionId);
|
||||
AudioTrack audioTrack =
|
||||
buildAudioTrack(
|
||||
configuration.buildAudioTrackConfig(),
|
||||
audioAttributes,
|
||||
audioSessionId,
|
||||
configuration.inputFormat);
|
||||
if (audioOffloadListener != null) {
|
||||
audioOffloadListener.onOffloadedPlayback(isOffloadedPlayback(audioTrack));
|
||||
}
|
||||
@ -1058,6 +1091,47 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
}
|
||||
}
|
||||
|
||||
private AudioTrack buildAudioTrack(
|
||||
AudioTrackConfig audioTrackConfig,
|
||||
AudioAttributes audioAttributes,
|
||||
int audioSessionId,
|
||||
Format inputFormat)
|
||||
throws InitializationException {
|
||||
AudioTrack audioTrack;
|
||||
try {
|
||||
audioTrack =
|
||||
audioTrackProvider.getAudioTrack(audioTrackConfig, audioAttributes, audioSessionId);
|
||||
} catch (UnsupportedOperationException | IllegalArgumentException e) {
|
||||
throw new InitializationException(
|
||||
AudioTrack.STATE_UNINITIALIZED,
|
||||
audioTrackConfig.sampleRate,
|
||||
audioTrackConfig.channelConfig,
|
||||
audioTrackConfig.encoding,
|
||||
inputFormat,
|
||||
/* isRecoverable= */ audioTrackConfig.offload,
|
||||
e);
|
||||
}
|
||||
|
||||
int state = audioTrack.getState();
|
||||
if (state != AudioTrack.STATE_INITIALIZED) {
|
||||
try {
|
||||
audioTrack.release();
|
||||
} catch (Exception e) {
|
||||
// The track has already failed to initialize, so it wouldn't be that surprising if
|
||||
// release were to fail too. Swallow the exception.
|
||||
}
|
||||
throw new InitializationException(
|
||||
state,
|
||||
audioTrackConfig.sampleRate,
|
||||
audioTrackConfig.channelConfig,
|
||||
audioTrackConfig.encoding,
|
||||
inputFormat,
|
||||
/* isRecoverable= */ audioTrackConfig.offload,
|
||||
/* audioTrackException= */ null);
|
||||
}
|
||||
return audioTrack;
|
||||
}
|
||||
|
||||
@RequiresApi(29)
|
||||
private void registerStreamEventCallbackV29(AudioTrack audioTrack) {
|
||||
if (offloadStreamEventCallbackV29 == null) {
|
||||
@ -2199,88 +2273,6 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
bufferSize);
|
||||
}
|
||||
|
||||
public AudioTrack buildAudioTrack(AudioAttributes audioAttributes, int audioSessionId)
|
||||
throws InitializationException {
|
||||
AudioTrack audioTrack;
|
||||
try {
|
||||
audioTrack = createAudioTrack(audioAttributes, audioSessionId);
|
||||
} catch (UnsupportedOperationException | IllegalArgumentException e) {
|
||||
throw new InitializationException(
|
||||
AudioTrack.STATE_UNINITIALIZED,
|
||||
outputSampleRate,
|
||||
outputChannelConfig,
|
||||
bufferSize,
|
||||
inputFormat,
|
||||
/* isRecoverable= */ outputModeIsOffload(),
|
||||
e);
|
||||
}
|
||||
|
||||
int state = audioTrack.getState();
|
||||
if (state != AudioTrack.STATE_INITIALIZED) {
|
||||
try {
|
||||
audioTrack.release();
|
||||
} catch (Exception e) {
|
||||
// The track has already failed to initialize, so it wouldn't be that surprising if
|
||||
// release were to fail too. Swallow the exception.
|
||||
}
|
||||
throw new InitializationException(
|
||||
state,
|
||||
outputSampleRate,
|
||||
outputChannelConfig,
|
||||
bufferSize,
|
||||
inputFormat,
|
||||
/* isRecoverable= */ outputModeIsOffload(),
|
||||
/* audioTrackException= */ null);
|
||||
}
|
||||
return audioTrack;
|
||||
}
|
||||
|
||||
private AudioTrack createAudioTrack(AudioAttributes audioAttributes, int audioSessionId) {
|
||||
if (Util.SDK_INT >= 29) {
|
||||
return createAudioTrackV29(audioAttributes, audioSessionId);
|
||||
} else {
|
||||
return createAudioTrackV21(audioAttributes, audioSessionId);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(29)
|
||||
private AudioTrack createAudioTrackV29(AudioAttributes audioAttributes, int audioSessionId) {
|
||||
AudioFormat audioFormat =
|
||||
Util.getAudioFormat(outputSampleRate, outputChannelConfig, outputEncoding);
|
||||
android.media.AudioAttributes audioTrackAttributes =
|
||||
getAudioTrackAttributes(audioAttributes, tunneling);
|
||||
return new AudioTrack.Builder()
|
||||
.setAudioAttributes(audioTrackAttributes)
|
||||
.setAudioFormat(audioFormat)
|
||||
.setTransferMode(AudioTrack.MODE_STREAM)
|
||||
.setBufferSizeInBytes(bufferSize)
|
||||
.setSessionId(audioSessionId)
|
||||
.setOffloadedPlayback(outputMode == OUTPUT_MODE_OFFLOAD)
|
||||
.build();
|
||||
}
|
||||
|
||||
private AudioTrack createAudioTrackV21(AudioAttributes audioAttributes, int audioSessionId) {
|
||||
return new AudioTrack(
|
||||
getAudioTrackAttributes(audioAttributes, tunneling),
|
||||
Util.getAudioFormat(outputSampleRate, outputChannelConfig, outputEncoding),
|
||||
bufferSize,
|
||||
AudioTrack.MODE_STREAM,
|
||||
audioSessionId);
|
||||
}
|
||||
|
||||
private static android.media.AudioAttributes getAudioTrackAttributes(
|
||||
AudioAttributes audioAttributes, boolean tunneling) {
|
||||
if (tunneling) {
|
||||
return new android.media.AudioAttributes.Builder()
|
||||
.setContentType(android.media.AudioAttributes.CONTENT_TYPE_MOVIE)
|
||||
.setFlags(android.media.AudioAttributes.FLAG_HW_AV_SYNC)
|
||||
.setUsage(android.media.AudioAttributes.USAGE_MEDIA)
|
||||
.build();
|
||||
} else {
|
||||
return audioAttributes.getAudioAttributesV21().audioAttributes;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean outputModeIsOffload() {
|
||||
return outputMode == OUTPUT_MODE_OFFLOAD;
|
||||
}
|
||||
|
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2024 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
|
||||
*
|
||||
* https://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 android.media.AudioFormat;
|
||||
import android.media.AudioTrack;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.media3.common.AudioAttributes;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
|
||||
/**
|
||||
* The default provider for {@link AudioTrack} instances.
|
||||
*
|
||||
* <p>Subclasses of this provider can customize the {@link AudioTrack.Builder} in {@link
|
||||
* #customizeAudioTrackBuilder} if required.
|
||||
*/
|
||||
@UnstableApi
|
||||
public class DefaultAudioTrackProvider implements DefaultAudioSink.AudioTrackProvider {
|
||||
|
||||
@Override
|
||||
public final AudioTrack getAudioTrack(
|
||||
AudioSink.AudioTrackConfig audioTrackConfig,
|
||||
AudioAttributes audioAttributes,
|
||||
int audioSessionId) {
|
||||
if (Util.SDK_INT >= 23) {
|
||||
return createAudioTrackV23(audioTrackConfig, audioAttributes, audioSessionId);
|
||||
} else {
|
||||
return createAudioTrackV21(audioTrackConfig, audioAttributes, audioSessionId);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(23)
|
||||
private AudioTrack createAudioTrackV23(
|
||||
AudioSink.AudioTrackConfig audioTrackConfig,
|
||||
AudioAttributes audioAttributes,
|
||||
int audioSessionId) {
|
||||
AudioFormat audioFormat =
|
||||
Util.getAudioFormat(
|
||||
audioTrackConfig.sampleRate, audioTrackConfig.channelConfig, audioTrackConfig.encoding);
|
||||
android.media.AudioAttributes audioTrackAttributes =
|
||||
getAudioTrackAttributesV21(audioAttributes, audioTrackConfig.tunneling);
|
||||
AudioTrack.Builder audioTrackBuilder =
|
||||
new AudioTrack.Builder()
|
||||
.setAudioAttributes(audioTrackAttributes)
|
||||
.setAudioFormat(audioFormat)
|
||||
.setTransferMode(AudioTrack.MODE_STREAM)
|
||||
.setBufferSizeInBytes(audioTrackConfig.bufferSize)
|
||||
.setSessionId(audioSessionId);
|
||||
if (Util.SDK_INT >= 29) {
|
||||
setOffloadedPlaybackV29(audioTrackBuilder, audioTrackConfig.offload);
|
||||
}
|
||||
return customizeAudioTrackBuilder(audioTrackBuilder).build();
|
||||
}
|
||||
|
||||
@RequiresApi(29)
|
||||
private void setOffloadedPlaybackV29(AudioTrack.Builder audioTrackBuilder, boolean isOffloaded) {
|
||||
audioTrackBuilder.setOffloadedPlayback(isOffloaded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optionally customize {@link AudioTrack.Builder} with other parameters.
|
||||
*
|
||||
* <p>Note that this method is only called on API 23 and above.
|
||||
*
|
||||
* @param audioTrackBuilder The {@link AudioTrack.Builder} on which to set the attributes.
|
||||
* @return The same {@link AudioTrack.Builder} instance provided.
|
||||
*/
|
||||
@RequiresApi(23) // AudioTrack.Builder is available starting from API 23.
|
||||
@CanIgnoreReturnValue
|
||||
protected AudioTrack.Builder customizeAudioTrackBuilder(AudioTrack.Builder audioTrackBuilder) {
|
||||
return audioTrackBuilder;
|
||||
}
|
||||
|
||||
private AudioTrack createAudioTrackV21(
|
||||
AudioSink.AudioTrackConfig audioTrackConfig,
|
||||
AudioAttributes audioAttributes,
|
||||
int audioSessionId) {
|
||||
return new AudioTrack(
|
||||
getAudioTrackAttributesV21(audioAttributes, audioTrackConfig.tunneling),
|
||||
Util.getAudioFormat(
|
||||
audioTrackConfig.sampleRate, audioTrackConfig.channelConfig, audioTrackConfig.encoding),
|
||||
audioTrackConfig.bufferSize,
|
||||
AudioTrack.MODE_STREAM,
|
||||
audioSessionId);
|
||||
}
|
||||
|
||||
private android.media.AudioAttributes getAudioTrackAttributesV21(
|
||||
AudioAttributes audioAttributes, boolean tunneling) {
|
||||
if (tunneling) {
|
||||
return getAudioTrackTunnelingAttributesV21();
|
||||
} else {
|
||||
return audioAttributes.getAudioAttributesV21().audioAttributes;
|
||||
}
|
||||
}
|
||||
|
||||
private android.media.AudioAttributes getAudioTrackTunnelingAttributesV21() {
|
||||
return new android.media.AudioAttributes.Builder()
|
||||
.setContentType(android.media.AudioAttributes.CONTENT_TYPE_MOVIE)
|
||||
.setFlags(android.media.AudioAttributes.FLAG_HW_AV_SYNC)
|
||||
.setUsage(android.media.AudioAttributes.USAGE_MEDIA)
|
||||
.build();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user