Factor out default audio sink configuration

This cleanup is in preparation for draining audio processors on reconfiguration
to a format that doesn't require a new AudioTrack to be created.

PiperOrigin-RevId: 233990983
This commit is contained in:
andrewlewis 2019-02-14 19:06:19 +00:00 committed by Andrew Lewis
parent 12ed18c74c
commit 3e6cf42f83

View file

@ -242,18 +242,10 @@ public final class DefaultAudioSink implements AudioSink {
/** Used to keep the audio session active on pre-V21 builds (see {@link #initialize()}). */
@Nullable private AudioTrack keepSessionIdAudioTrack;
private Configuration configuration;
private AudioTrack audioTrack;
private boolean isInputPcm;
private boolean shouldConvertHighResIntPcmToFloat;
private int inputSampleRate;
private int outputSampleRate;
private int outputChannelConfig;
private @C.Encoding int outputEncoding;
private AudioAttributes audioAttributes;
private boolean processingEnabled;
private boolean canApplyPlaybackParameters;
private int bufferSize;
private AudioAttributes audioAttributes;
@Nullable private PlaybackParameters afterDrainPlaybackParameters;
private PlaybackParameters playbackParameters;
private long playbackParametersOffsetUs;
@ -262,10 +254,8 @@ public final class DefaultAudioSink implements AudioSink {
@Nullable private ByteBuffer avSyncHeader;
private int bytesUntilNextAvSync;
private int pcmFrameSize;
private long submittedPcmBytes;
private long submittedEncodedFrames;
private int outputPcmFrameSize;
private long writtenPcmBytes;
private long writtenEncodedFrames;
private int framesPerEncodedSample;
@ -397,7 +387,7 @@ public final class DefaultAudioSink implements AudioSink {
return CURRENT_POSITION_NOT_SET;
}
long positionUs = audioTrackPositionTracker.getCurrentPositionUs(sourceEnded);
positionUs = Math.min(positionUs, framesToDurationUs(getWrittenFrames()));
positionUs = Math.min(positionUs, configuration.framesToDurationUs(getWrittenFrames()));
return startMediaTimeUs + applySkipping(applySpeedup(positionUs));
}
@ -411,23 +401,7 @@ public final class DefaultAudioSink implements AudioSink {
int trimStartFrames,
int trimEndFrames)
throws ConfigurationException {
boolean flush = false;
this.inputSampleRate = inputSampleRate;
int channelCount = inputChannelCount;
int sampleRate = inputSampleRate;
isInputPcm = Util.isEncodingLinearPcm(inputEncoding);
shouldConvertHighResIntPcmToFloat =
enableConvertHighResIntPcmToFloat
&& supportsOutput(channelCount, C.ENCODING_PCM_FLOAT)
&& Util.isEncodingHighResolutionIntegerPcm(inputEncoding);
if (isInputPcm) {
pcmFrameSize = Util.getPcmFrameSize(inputEncoding, channelCount);
}
@C.Encoding int encoding = inputEncoding;
boolean processingEnabled = isInputPcm && inputEncoding != C.ENCODING_PCM_FLOAT;
canApplyPlaybackParameters = processingEnabled && !shouldConvertHighResIntPcmToFloat;
if (Util.SDK_INT < 21 && channelCount == 8 && outputChannels == null) {
if (Util.SDK_INT < 21 && inputChannelCount == 8 && outputChannels == null) {
// AudioTrack doesn't support 8 channel output before Android L. Discard the last two (side)
// channels to give a 6 channel stream that is supported.
outputChannels = new int[6];
@ -436,10 +410,24 @@ public final class DefaultAudioSink implements AudioSink {
}
}
boolean isInputPcm = Util.isEncodingLinearPcm(inputEncoding);
boolean processingEnabled = isInputPcm && inputEncoding != C.ENCODING_PCM_FLOAT;
int sampleRate = inputSampleRate;
int channelCount = inputChannelCount;
@C.Encoding int encoding = inputEncoding;
boolean shouldConvertHighResIntPcmToFloat =
enableConvertHighResIntPcmToFloat
&& supportsOutput(inputChannelCount, C.ENCODING_PCM_FLOAT)
&& Util.isEncodingHighResolutionIntegerPcm(inputEncoding);
AudioProcessor[] availableAudioProcessors =
shouldConvertHighResIntPcmToFloat
? toFloatPcmAvailableAudioProcessors
: toIntPcmAvailableAudioProcessors;
boolean flush = false;
if (processingEnabled) {
trimmingAudioProcessor.setTrimFrameCount(trimStartFrames, trimEndFrames);
channelMappingAudioProcessor.setChannelMap(outputChannels);
for (AudioProcessor audioProcessor : getAvailableAudioProcessors()) {
for (AudioProcessor audioProcessor : availableAudioProcessors) {
try {
flush |= audioProcessor.configure(sampleRate, channelCount, encoding);
} catch (AudioProcessor.UnhandledFormatException e) {
@ -453,53 +441,39 @@ public final class DefaultAudioSink implements AudioSink {
}
}
int channelConfig = getChannelConfig(channelCount, isInputPcm);
if (channelConfig == AudioFormat.CHANNEL_INVALID) {
int outputChannelConfig = getChannelConfig(channelCount, isInputPcm);
if (outputChannelConfig == AudioFormat.CHANNEL_INVALID) {
throw new ConfigurationException("Unsupported channel count: " + channelCount);
}
if (!flush
&& isInitialized()
&& outputEncoding == encoding
&& outputSampleRate == sampleRate
&& outputChannelConfig == channelConfig) {
// We already have an audio track with the correct sample rate, channel config and encoding.
return;
}
flush();
this.processingEnabled = processingEnabled;
outputSampleRate = sampleRate;
outputChannelConfig = channelConfig;
outputEncoding = encoding;
outputPcmFrameSize =
isInputPcm ? Util.getPcmFrameSize(outputEncoding, channelCount) : C.LENGTH_UNSET;
bufferSize = specifiedBufferSize != 0 ? specifiedBufferSize : getDefaultBufferSize();
}
private int getDefaultBufferSize() {
if (isInputPcm) {
int minBufferSize =
AudioTrack.getMinBufferSize(outputSampleRate, outputChannelConfig, outputEncoding);
Assertions.checkState(minBufferSize != ERROR_BAD_VALUE);
int multipliedBufferSize = minBufferSize * BUFFER_MULTIPLICATION_FACTOR;
int minAppBufferSize = (int) durationUsToFrames(MIN_BUFFER_DURATION_US) * outputPcmFrameSize;
int maxAppBufferSize = (int) Math.max(minBufferSize,
durationUsToFrames(MAX_BUFFER_DURATION_US) * outputPcmFrameSize);
return Util.constrainValue(multipliedBufferSize, minAppBufferSize, maxAppBufferSize);
} else {
int rate = getMaximumEncodedRateBytesPerSecond(outputEncoding);
if (outputEncoding == C.ENCODING_AC3) {
rate *= AC3_BUFFER_MULTIPLICATION_FACTOR;
}
return (int) (PASSTHROUGH_BUFFER_DURATION_US * rate / C.MICROS_PER_SECOND);
int inputPcmFrameSize =
isInputPcm ? Util.getPcmFrameSize(inputEncoding, inputChannelCount) : C.LENGTH_UNSET;
int outputPcmFrameSize =
isInputPcm ? Util.getPcmFrameSize(encoding, channelCount) : C.LENGTH_UNSET;
boolean canApplyPlaybackParameters = processingEnabled && !shouldConvertHighResIntPcmToFloat;
Configuration pendingConfiguration =
new Configuration(
isInputPcm,
inputPcmFrameSize,
inputSampleRate,
outputPcmFrameSize,
sampleRate,
outputChannelConfig,
encoding,
specifiedBufferSize,
processingEnabled,
canApplyPlaybackParameters,
availableAudioProcessors);
if (flush || configuration == null || !pendingConfiguration.canReuseAudioTrack(configuration)) {
flush();
}
configuration = pendingConfiguration;
}
private void setupAudioProcessors() {
AudioProcessor[] audioProcessors = configuration.availableAudioProcessors;
ArrayList<AudioProcessor> newAudioProcessors = new ArrayList<>();
for (AudioProcessor audioProcessor : getAvailableAudioProcessors()) {
for (AudioProcessor audioProcessor : audioProcessors) {
if (audioProcessor.isActive()) {
newAudioProcessors.add(audioProcessor);
} else {
@ -528,7 +502,9 @@ public final class DefaultAudioSink implements AudioSink {
// initialization of the audio track to fail.
releasingConditionVariable.block();
audioTrack = initializeAudioTrack();
audioTrack =
Assertions.checkNotNull(configuration)
.buildAudioTrack(tunneling, audioAttributes, audioSessionId);
int audioSessionId = audioTrack.getAudioSessionId();
if (enablePreV21AudioSessionWorkaround) {
if (Util.SDK_INT < 21) {
@ -551,13 +527,16 @@ public final class DefaultAudioSink implements AudioSink {
}
playbackParameters =
canApplyPlaybackParameters
configuration.canApplyPlaybackParameters
? audioProcessorChain.applyPlaybackParameters(playbackParameters)
: PlaybackParameters.DEFAULT;
setupAudioProcessors();
audioTrackPositionTracker.setAudioTrack(
audioTrack, outputEncoding, outputPcmFrameSize, bufferSize);
audioTrack,
configuration.outputEncoding,
configuration.outputPcmFrameSize,
configuration.bufferSize);
setVolumeInternal();
if (auxEffectInfo.effectId != AuxEffectInfo.NO_AUX_EFFECT_ID) {
@ -606,9 +585,9 @@ public final class DefaultAudioSink implements AudioSink {
return true;
}
if (!isInputPcm && framesPerEncodedSample == 0) {
if (!configuration.isInputPcm && framesPerEncodedSample == 0) {
// If this is the first encoded sample, calculate the sample size in frames.
framesPerEncodedSample = getFramesPerEncodedSample(outputEncoding, buffer);
framesPerEncodedSample = getFramesPerEncodedSample(configuration.outputEncoding, buffer);
if (framesPerEncodedSample == 0) {
// We still don't know the number of frames per sample, so drop the buffer.
// For TrueHD this can occur after some seek operations, as not every sample starts with
@ -631,7 +610,7 @@ public final class DefaultAudioSink implements AudioSink {
new PlaybackParametersCheckpoint(
newPlaybackParameters,
Math.max(0, presentationTimeUs),
framesToDurationUs(getWrittenFrames())));
configuration.framesToDurationUs(getWrittenFrames())));
// Update the set of active audio processors to take into account the new parameters.
setupAudioProcessors();
}
@ -643,7 +622,7 @@ public final class DefaultAudioSink implements AudioSink {
// Sanity check that presentationTimeUs is consistent with the expected value.
long expectedPresentationTimeUs =
startMediaTimeUs
+ inputFramesToDurationUs(
+ configuration.inputFramesToDurationUs(
getSubmittedFrames() - trimmingAudioProcessor.getTrimmedFrameCount());
if (startMediaTimeState == START_IN_SYNC
&& Math.abs(expectedPresentationTimeUs - presentationTimeUs) > 200000) {
@ -663,7 +642,7 @@ public final class DefaultAudioSink implements AudioSink {
}
}
if (isInputPcm) {
if (configuration.isInputPcm) {
submittedPcmBytes += buffer.remaining();
} else {
submittedEncodedFrames += framesPerEncodedSample;
@ -672,7 +651,7 @@ public final class DefaultAudioSink implements AudioSink {
inputBuffer = buffer;
}
if (processingEnabled) {
if (configuration.processingEnabled) {
processBuffers(presentationTimeUs);
} else {
writeBuffer(inputBuffer, presentationTimeUs);
@ -769,11 +748,11 @@ public final class DefaultAudioSink implements AudioSink {
throw new WriteException(bytesWritten);
}
if (isInputPcm) {
if (configuration.isInputPcm) {
writtenPcmBytes += bytesWritten;
}
if (bytesWritten == bytesRemaining) {
if (!isInputPcm) {
if (!configuration.isInputPcm) {
writtenEncodedFrames += framesPerEncodedSample;
}
outputBuffer = null;
@ -798,7 +777,8 @@ public final class DefaultAudioSink implements AudioSink {
private boolean drainAudioProcessorsToEndOfStream() throws WriteException {
boolean audioProcessorNeedsEndOfStream = false;
if (drainingAudioProcessorIndex == C.INDEX_UNSET) {
drainingAudioProcessorIndex = processingEnabled ? 0 : activeAudioProcessors.length;
drainingAudioProcessorIndex =
configuration.processingEnabled ? 0 : activeAudioProcessors.length;
audioProcessorNeedsEndOfStream = true;
}
while (drainingAudioProcessorIndex < activeAudioProcessors.length) {
@ -837,7 +817,7 @@ public final class DefaultAudioSink implements AudioSink {
@Override
public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) {
if (isInitialized() && !canApplyPlaybackParameters) {
if (configuration != null && !configuration.canApplyPlaybackParameters) {
this.playbackParameters = PlaybackParameters.DEFAULT;
return this.playbackParameters;
}
@ -1060,99 +1040,27 @@ public final class DefaultAudioSink implements AudioSink {
}
private long applySkipping(long positionUs) {
return positionUs + framesToDurationUs(audioProcessorChain.getSkippedOutputFrameCount());
return positionUs
+ configuration.framesToDurationUs(audioProcessorChain.getSkippedOutputFrameCount());
}
private boolean isInitialized() {
return audioTrack != null;
}
private long inputFramesToDurationUs(long frameCount) {
return (frameCount * C.MICROS_PER_SECOND) / inputSampleRate;
}
private long framesToDurationUs(long frameCount) {
return (frameCount * C.MICROS_PER_SECOND) / outputSampleRate;
}
private long durationUsToFrames(long durationUs) {
return (durationUs * outputSampleRate) / C.MICROS_PER_SECOND;
}
private long getSubmittedFrames() {
return isInputPcm ? (submittedPcmBytes / pcmFrameSize) : submittedEncodedFrames;
return configuration.isInputPcm
? (submittedPcmBytes / configuration.inputPcmFrameSize)
: submittedEncodedFrames;
}
private long getWrittenFrames() {
return isInputPcm ? (writtenPcmBytes / outputPcmFrameSize) : writtenEncodedFrames;
return configuration.isInputPcm
? (writtenPcmBytes / configuration.outputPcmFrameSize)
: writtenEncodedFrames;
}
private AudioTrack initializeAudioTrack() throws InitializationException {
AudioTrack audioTrack;
if (Util.SDK_INT >= 21) {
audioTrack = createAudioTrackV21();
} else {
int streamType = Util.getStreamTypeForAudioUsage(audioAttributes.usage);
if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) {
audioTrack =
new AudioTrack(
streamType,
outputSampleRate,
outputChannelConfig,
outputEncoding,
bufferSize,
MODE_STREAM);
} else {
// Re-attach to the same audio session.
audioTrack =
new AudioTrack(
streamType,
outputSampleRate,
outputChannelConfig,
outputEncoding,
bufferSize,
MODE_STREAM,
audioSessionId);
}
}
int state = audioTrack.getState();
if (state != 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);
}
return audioTrack;
}
@TargetApi(21)
private AudioTrack createAudioTrackV21() {
android.media.AudioAttributes attributes;
if (tunneling) {
attributes = 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 {
attributes = audioAttributes.getAudioAttributesV21();
}
AudioFormat format =
new AudioFormat.Builder()
.setChannelMask(outputChannelConfig)
.setEncoding(outputEncoding)
.setSampleRate(outputSampleRate)
.build();
int audioSessionId = this.audioSessionId != C.AUDIO_SESSION_ID_UNSET ? this.audioSessionId
: AudioManager.AUDIO_SESSION_ID_GENERATE;
return new AudioTrack(attributes, format, bufferSize, MODE_STREAM, audioSessionId);
}
private AudioTrack initializeKeepSessionIdAudioTrack(int audioSessionId) {
private static AudioTrack initializeKeepSessionIdAudioTrack(int audioSessionId) {
int sampleRate = 4000; // Equal to private AudioTrack.MIN_SAMPLE_RATE.
int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
@C.PcmEncoding int encoding = C.ENCODING_PCM_16BIT;
@ -1161,12 +1069,6 @@ public final class DefaultAudioSink implements AudioSink {
MODE_STATIC, audioSessionId);
}
private AudioProcessor[] getAvailableAudioProcessors() {
return shouldConvertHighResIntPcmToFloat
? toFloatPcmAvailableAudioProcessors
: toIntPcmAvailableAudioProcessors;
}
private static int getChannelConfig(int channelCount, boolean isInputPcm) {
if (Util.SDK_INT <= 28 && !isInputPcm) {
// In passthrough mode the channel count used to configure the audio track doesn't affect how
@ -1288,9 +1190,7 @@ public final class DefaultAudioSink implements AudioSink {
audioTrack.setStereoVolume(volume, volume);
}
/**
* Stores playback parameters with the position and media time at which they apply.
*/
/** Stores playback parameters with the position and media time at which they apply. */
private static final class PlaybackParametersCheckpoint {
private final PlaybackParameters playbackParameters;
@ -1371,4 +1271,159 @@ public final class DefaultAudioSink implements AudioSink {
}
}
}
/** Stores configuration relating to the audio format. */
private static final class Configuration {
public final boolean isInputPcm;
public final int inputPcmFrameSize;
public final int inputSampleRate;
public final int outputPcmFrameSize;
public final int outputSampleRate;
public final int outputChannelConfig;
@C.Encoding public final int outputEncoding;
public final int bufferSize;
public final boolean processingEnabled;
public final boolean canApplyPlaybackParameters;
public final AudioProcessor[] availableAudioProcessors;
public Configuration(
boolean isInputPcm,
int inputPcmFrameSize,
int inputSampleRate,
int outputPcmFrameSize,
int outputSampleRate,
int outputChannelConfig,
int outputEncoding,
int specifiedBufferSize,
boolean processingEnabled,
boolean canApplyPlaybackParameters,
AudioProcessor[] availableAudioProcessors) {
this.isInputPcm = isInputPcm;
this.inputPcmFrameSize = inputPcmFrameSize;
this.inputSampleRate = inputSampleRate;
this.outputPcmFrameSize = outputPcmFrameSize;
this.outputSampleRate = outputSampleRate;
this.outputChannelConfig = outputChannelConfig;
this.outputEncoding = outputEncoding;
this.bufferSize = specifiedBufferSize != 0 ? specifiedBufferSize : getDefaultBufferSize();
this.processingEnabled = processingEnabled;
this.canApplyPlaybackParameters = canApplyPlaybackParameters;
this.availableAudioProcessors = availableAudioProcessors;
}
public boolean canReuseAudioTrack(Configuration audioTrackConfiguration) {
return audioTrackConfiguration.outputEncoding == outputEncoding
&& audioTrackConfiguration.outputSampleRate == outputSampleRate
&& audioTrackConfiguration.outputChannelConfig == outputChannelConfig;
}
public long inputFramesToDurationUs(long frameCount) {
return (frameCount * C.MICROS_PER_SECOND) / inputSampleRate;
}
public long framesToDurationUs(long frameCount) {
return (frameCount * C.MICROS_PER_SECOND) / outputSampleRate;
}
public long durationUsToFrames(long durationUs) {
return (durationUs * outputSampleRate) / C.MICROS_PER_SECOND;
}
public AudioTrack buildAudioTrack(
boolean tunneling, AudioAttributes audioAttributes, int audioSessionId)
throws InitializationException {
AudioTrack audioTrack;
if (Util.SDK_INT >= 21) {
audioTrack = createAudioTrackV21(tunneling, audioAttributes, audioSessionId);
} else {
int streamType = Util.getStreamTypeForAudioUsage(audioAttributes.usage);
if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) {
audioTrack =
new AudioTrack(
streamType,
outputSampleRate,
outputChannelConfig,
outputEncoding,
bufferSize,
MODE_STREAM);
} else {
// Re-attach to the same audio session.
audioTrack =
new AudioTrack(
streamType,
outputSampleRate,
outputChannelConfig,
outputEncoding,
bufferSize,
MODE_STREAM,
audioSessionId);
}
}
int state = audioTrack.getState();
if (state != 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);
}
return audioTrack;
}
@TargetApi(21)
private AudioTrack createAudioTrackV21(
boolean tunneling, AudioAttributes audioAttributes, int audioSessionId) {
android.media.AudioAttributes attributes;
if (tunneling) {
attributes =
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 {
attributes = audioAttributes.getAudioAttributesV21();
}
AudioFormat format =
new AudioFormat.Builder()
.setChannelMask(outputChannelConfig)
.setEncoding(outputEncoding)
.setSampleRate(outputSampleRate)
.build();
return new AudioTrack(
attributes,
format,
bufferSize,
MODE_STREAM,
audioSessionId != C.AUDIO_SESSION_ID_UNSET
? audioSessionId
: AudioManager.AUDIO_SESSION_ID_GENERATE);
}
private int getDefaultBufferSize() {
if (isInputPcm) {
int minBufferSize =
AudioTrack.getMinBufferSize(outputSampleRate, outputChannelConfig, outputEncoding);
Assertions.checkState(minBufferSize != ERROR_BAD_VALUE);
int multipliedBufferSize = minBufferSize * BUFFER_MULTIPLICATION_FACTOR;
int minAppBufferSize =
(int) durationUsToFrames(MIN_BUFFER_DURATION_US) * outputPcmFrameSize;
int maxAppBufferSize =
(int)
Math.max(
minBufferSize, durationUsToFrames(MAX_BUFFER_DURATION_US) * outputPcmFrameSize);
return Util.constrainValue(multipliedBufferSize, minAppBufferSize, maxAppBufferSize);
} else {
int rate = getMaximumEncodedRateBytesPerSecond(outputEncoding);
if (outputEncoding == C.ENCODING_AC3) {
rate *= AC3_BUFFER_MULTIPLICATION_FACTOR;
}
return (int) (PASSTHROUGH_BUFFER_DURATION_US * rate / C.MICROS_PER_SECOND);
}
}
}
}