Allow AudioTrack to be provided by a customizable provider

PiperOrigin-RevId: 684800579
This commit is contained in:
tonihei 2024-10-11 05:21:07 -07:00 committed by Copybara-Service
parent ad0493b90f
commit 73f97c0371
2 changed files with 193 additions and 83 deletions

View File

@ -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;
}

View File

@ -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();
}
}