mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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
|
@UnstableApi
|
||||||
public final class DefaultAudioSink implements AudioSink {
|
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
|
* 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.
|
* second attempt is made using this buffer size.
|
||||||
@ -271,6 +282,7 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
|
|
||||||
private boolean buildCalled;
|
private boolean buildCalled;
|
||||||
private AudioTrackBufferSizeProvider audioTrackBufferSizeProvider;
|
private AudioTrackBufferSizeProvider audioTrackBufferSizeProvider;
|
||||||
|
private AudioTrackProvider audioTrackProvider;
|
||||||
private @MonotonicNonNull AudioOffloadSupportProvider audioOffloadSupportProvider;
|
private @MonotonicNonNull AudioOffloadSupportProvider audioOffloadSupportProvider;
|
||||||
@Nullable private AudioOffloadListener audioOffloadListener;
|
@Nullable private AudioOffloadListener audioOffloadListener;
|
||||||
|
|
||||||
@ -282,6 +294,7 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
this.context = null;
|
this.context = null;
|
||||||
audioCapabilities = DEFAULT_AUDIO_CAPABILITIES;
|
audioCapabilities = DEFAULT_AUDIO_CAPABILITIES;
|
||||||
audioTrackBufferSizeProvider = AudioTrackBufferSizeProvider.DEFAULT;
|
audioTrackBufferSizeProvider = AudioTrackBufferSizeProvider.DEFAULT;
|
||||||
|
audioTrackProvider = AudioTrackProvider.DEFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -293,6 +306,7 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
this.context = context;
|
this.context = context;
|
||||||
audioCapabilities = DEFAULT_AUDIO_CAPABILITIES;
|
audioCapabilities = DEFAULT_AUDIO_CAPABILITIES;
|
||||||
audioTrackBufferSizeProvider = AudioTrackBufferSizeProvider.DEFAULT;
|
audioTrackBufferSizeProvider = AudioTrackBufferSizeProvider.DEFAULT;
|
||||||
|
audioTrackProvider = AudioTrackProvider.DEFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -406,6 +420,18 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
return this;
|
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. */
|
/** Builds the {@link DefaultAudioSink}. Must only be called once per Builder instance. */
|
||||||
public DefaultAudioSink build() {
|
public DefaultAudioSink build() {
|
||||||
checkState(!buildCalled);
|
checkState(!buildCalled);
|
||||||
@ -505,6 +531,7 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
private final AudioTrackBufferSizeProvider audioTrackBufferSizeProvider;
|
private final AudioTrackBufferSizeProvider audioTrackBufferSizeProvider;
|
||||||
private final AudioOffloadSupportProvider audioOffloadSupportProvider;
|
private final AudioOffloadSupportProvider audioOffloadSupportProvider;
|
||||||
@Nullable private final AudioOffloadListener audioOffloadListener;
|
@Nullable private final AudioOffloadListener audioOffloadListener;
|
||||||
|
private final AudioTrackProvider audioTrackProvider;
|
||||||
|
|
||||||
@Nullable private PlayerId playerId;
|
@Nullable private PlayerId playerId;
|
||||||
@Nullable private Listener listener;
|
@Nullable private Listener listener;
|
||||||
@ -590,6 +617,7 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
initializationExceptionPendingExceptionHolder = new PendingExceptionHolder<>();
|
initializationExceptionPendingExceptionHolder = new PendingExceptionHolder<>();
|
||||||
writeExceptionPendingExceptionHolder = new PendingExceptionHolder<>();
|
writeExceptionPendingExceptionHolder = new PendingExceptionHolder<>();
|
||||||
audioOffloadListener = builder.audioOffloadListener;
|
audioOffloadListener = builder.audioOffloadListener;
|
||||||
|
audioTrackProvider = builder.audioTrackProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
// AudioSink implementation.
|
// AudioSink implementation.
|
||||||
@ -1045,7 +1073,12 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
|
|
||||||
private AudioTrack buildAudioTrack(Configuration configuration) throws InitializationException {
|
private AudioTrack buildAudioTrack(Configuration configuration) throws InitializationException {
|
||||||
try {
|
try {
|
||||||
AudioTrack audioTrack = configuration.buildAudioTrack(audioAttributes, audioSessionId);
|
AudioTrack audioTrack =
|
||||||
|
buildAudioTrack(
|
||||||
|
configuration.buildAudioTrackConfig(),
|
||||||
|
audioAttributes,
|
||||||
|
audioSessionId,
|
||||||
|
configuration.inputFormat);
|
||||||
if (audioOffloadListener != null) {
|
if (audioOffloadListener != null) {
|
||||||
audioOffloadListener.onOffloadedPlayback(isOffloadedPlayback(audioTrack));
|
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)
|
@RequiresApi(29)
|
||||||
private void registerStreamEventCallbackV29(AudioTrack audioTrack) {
|
private void registerStreamEventCallbackV29(AudioTrack audioTrack) {
|
||||||
if (offloadStreamEventCallbackV29 == null) {
|
if (offloadStreamEventCallbackV29 == null) {
|
||||||
@ -2199,88 +2273,6 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
bufferSize);
|
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() {
|
public boolean outputModeIsOffload() {
|
||||||
return outputMode == OUTPUT_MODE_OFFLOAD;
|
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