mirror of
https://github.com/samsonjs/media.git
synced 2026-04-06 11:25:46 +00:00
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:
parent
12ed18c74c
commit
3e6cf42f83
1 changed files with 228 additions and 173 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue