Apply new playback parameters only once drained

Previously the SonicAudioProcessor and SilenceSkippingAudioProcessor would track
their pending playback parameters and only apply them in flush(). Having the
values only take effect once flushed made the processors a bit more difficult to
use, especially because the value returned by isActive wouldn't update
immediately.

Make DefaultAudioSink only set the new speed/pitch/skip silence setting after
the audio processors have drained. This means it's no longer necessary to keep
track of pending parameter values and also fixes a bug where initial playback
parameters weren't applied because the audio processors weren't flushed while
uninitialized before DefaultAudioSink called isActive() on them.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=191586727
This commit is contained in:
andrewlewis 2018-04-04 07:01:58 -07:00 committed by Oliver Woodman
parent 7c65b94578
commit e3eddc4d20
3 changed files with 62 additions and 49 deletions

View File

@ -162,7 +162,7 @@ public final class DefaultAudioSink implements AudioSink {
private boolean canApplyPlaybackParameters;
private int bufferSize;
@Nullable private PlaybackParameters drainingPlaybackParameters;
@Nullable private PlaybackParameters afterDrainPlaybackParameters;
private PlaybackParameters playbackParameters;
private long playbackParametersOffsetUs;
private long playbackParametersPositionUs;
@ -521,22 +521,21 @@ public final class DefaultAudioSink implements AudioSink {
}
}
if (drainingPlaybackParameters != null) {
if (afterDrainPlaybackParameters != null) {
if (!drainAudioProcessorsToEndOfStream()) {
// Don't process any more input until draining completes.
return false;
}
PlaybackParameters newPlaybackParameters = afterDrainPlaybackParameters;
afterDrainPlaybackParameters = null;
newPlaybackParameters = applyPlaybackParameters(newPlaybackParameters);
// Store the position and corresponding media time from which the parameters will apply.
playbackParametersCheckpoints.add(new PlaybackParametersCheckpoint(
drainingPlaybackParameters, Math.max(0, presentationTimeUs),
framesToDurationUs(getWrittenFrames())));
drainingPlaybackParameters = null;
// Flush the audio processors so that any new parameters take effect.
// TODO: Move parameter setting from setPlaybackParameters to here, so that it's not
// necessary to flush the processors twice.
sonicAudioProcessor.flush();
silenceSkippingAudioProcessor.flush();
playbackParametersCheckpoints.add(
new PlaybackParametersCheckpoint(
newPlaybackParameters,
Math.max(0, presentationTimeUs),
framesToDurationUs(getWrittenFrames())));
// Update the set of active audio processors to take into account the new parameters.
setupAudioProcessors();
}
@ -742,14 +741,9 @@ public final class DefaultAudioSink implements AudioSink {
this.playbackParameters = PlaybackParameters.DEFAULT;
return this.playbackParameters;
}
playbackParameters =
new PlaybackParameters(
sonicAudioProcessor.setSpeed(playbackParameters.speed),
sonicAudioProcessor.setPitch(playbackParameters.pitch),
playbackParameters.skipSilence);
silenceSkippingAudioProcessor.setEnabled(playbackParameters.skipSilence);
PlaybackParameters lastSetPlaybackParameters =
drainingPlaybackParameters != null ? drainingPlaybackParameters
afterDrainPlaybackParameters != null
? afterDrainPlaybackParameters
: !playbackParametersCheckpoints.isEmpty()
? playbackParametersCheckpoints.getLast().playbackParameters
: this.playbackParameters;
@ -757,9 +751,10 @@ public final class DefaultAudioSink implements AudioSink {
if (isInitialized()) {
// Drain the audio processors so we can determine the frame position at which the new
// parameters apply.
drainingPlaybackParameters = playbackParameters;
afterDrainPlaybackParameters = playbackParameters;
} else {
this.playbackParameters = playbackParameters;
// Update the playback parameters now.
this.playbackParameters = applyPlaybackParameters(playbackParameters);
}
}
return this.playbackParameters;
@ -845,9 +840,9 @@ public final class DefaultAudioSink implements AudioSink {
writtenPcmBytes = 0;
writtenEncodedFrames = 0;
framesPerEncodedSample = 0;
if (drainingPlaybackParameters != null) {
playbackParameters = drainingPlaybackParameters;
drainingPlaybackParameters = null;
if (afterDrainPlaybackParameters != null) {
playbackParameters = afterDrainPlaybackParameters;
afterDrainPlaybackParameters = null;
} else if (!playbackParametersCheckpoints.isEmpty()) {
playbackParameters = playbackParametersCheckpoints.getLast().playbackParameters;
}
@ -917,6 +912,21 @@ public final class DefaultAudioSink implements AudioSink {
}.start();
}
/**
* Configures audio processors to apply the specified playback parameters, returning the new
* parameters, which may differ from those passed in.
*
* @param playbackParameters The playback parameters to try to apply.
* @return The playback parameters that were actually applied.
*/
private PlaybackParameters applyPlaybackParameters(PlaybackParameters playbackParameters) {
silenceSkippingAudioProcessor.setEnabled(playbackParameters.skipSilence);
return new PlaybackParameters(
sonicAudioProcessor.setSpeed(playbackParameters.speed),
sonicAudioProcessor.setPitch(playbackParameters.pitch),
playbackParameters.skipSilence);
}
/**
* Returns the underlying audio track {@code positionUs} with any applicable speedup applied.
*/

View File

@ -72,7 +72,6 @@ import java.nio.ByteOrder;
private int bytesPerFrame;
private boolean enabled;
private boolean pendingEnabled;
private ByteBuffer buffer;
private ByteBuffer outputBuffer;
@ -108,13 +107,14 @@ import java.nio.ByteOrder;
}
/**
* Sets whether to skip silence in the input. The new setting will take effect after calling
* {@link #flush()}.
* Sets whether to skip silence in the input. Calling this method will discard any data buffered
* within the processor, and may update the value returned by {@link #isActive()}.
*
* @param enabled Whether to skip silence in the input.
*/
public void setEnabled(boolean enabled) {
pendingEnabled = enabled;
this.enabled = enabled;
flush();
}
/**
@ -144,7 +144,7 @@ import java.nio.ByteOrder;
@Override
public boolean isActive() {
return enabled;
return sampleRateHz != Format.NO_VALUE && enabled;
}
@Override
@ -208,7 +208,6 @@ import java.nio.ByteOrder;
@Override
public void flush() {
enabled = pendingEnabled;
if (isActive()) {
int maybeSilenceBufferSize = durationUsToFrames(MINIMUM_SILENCE_DURATION_US) * bytesPerFrame;
if (maybeSilenceBuffer.length != maybeSilenceBufferSize) {
@ -230,7 +229,6 @@ import java.nio.ByteOrder;
@Override
public void reset() {
enabled = false;
pendingEnabled = false;
flush();
buffer = EMPTY_BUFFER;
channelCount = Format.NO_VALUE;

View File

@ -67,8 +67,6 @@ public final class SonicAudioProcessor implements AudioProcessor {
private float speed;
private float pitch;
private int outputSampleRateHz;
private float pendingSpeed;
private float pendingPitch;
private int pendingOutputSampleRateHz;
private @Nullable Sonic sonic;
@ -85,8 +83,6 @@ public final class SonicAudioProcessor implements AudioProcessor {
public SonicAudioProcessor() {
speed = 1f;
pitch = 1f;
pendingSpeed = 1f;
pendingPitch = 1f;
channelCount = Format.NO_VALUE;
sampleRateHz = Format.NO_VALUE;
outputSampleRateHz = Format.NO_VALUE;
@ -97,25 +93,37 @@ public final class SonicAudioProcessor implements AudioProcessor {
}
/**
* Sets the playback speed. The new speed will take effect after a call to {@link #flush()}.
* Sets the playback speed. Calling this method will discard any data buffered within the
* processor, and may update the value returned by {@link #isActive()}.
*
* @param speed The requested new playback speed.
* @return The actual new playback speed.
*/
public float setSpeed(float speed) {
pendingSpeed = Util.constrainValue(speed, MINIMUM_SPEED, MAXIMUM_SPEED);
return pendingSpeed;
speed = Util.constrainValue(speed, MINIMUM_SPEED, MAXIMUM_SPEED);
if (this.speed != speed) {
this.speed = speed;
sonic = null;
}
flush();
return speed;
}
/**
* Sets the playback pitch. The new pitch will take effect after a call to {@link #flush()}.
* Sets the playback pitch. Calling this method will discard any data buffered within the
* processor, and may update the value returned by {@link #isActive()}.
*
* @param pitch The requested new pitch.
* @return The actual new pitch.
*/
public float setPitch(float pitch) {
pendingPitch = Util.constrainValue(pitch, MINIMUM_PITCH, MAXIMUM_PITCH);
return pendingPitch;
pitch = Util.constrainValue(pitch, MINIMUM_PITCH, MAXIMUM_PITCH);
if (this.pitch != pitch) {
this.pitch = pitch;
sonic = null;
}
flush();
return pitch;
}
/**
@ -170,8 +178,10 @@ public final class SonicAudioProcessor implements AudioProcessor {
@Override
public boolean isActive() {
return Math.abs(speed - 1f) >= CLOSE_THRESHOLD || Math.abs(pitch - 1f) >= CLOSE_THRESHOLD
|| outputSampleRateHz != sampleRateHz;
return sampleRateHz != Format.NO_VALUE
&& (Math.abs(speed - 1f) >= CLOSE_THRESHOLD
|| Math.abs(pitch - 1f) >= CLOSE_THRESHOLD
|| outputSampleRateHz != sampleRateHz);
}
@Override
@ -236,11 +246,8 @@ public final class SonicAudioProcessor implements AudioProcessor {
@Override
public void flush() {
boolean parametersChanged = pendingSpeed != speed || pendingPitch != pitch;
speed = pendingSpeed;
pitch = pendingPitch;
if (isActive()) {
if (sonic == null || parametersChanged) {
if (sonic == null) {
sonic = new Sonic(sampleRateHz, channelCount, speed, pitch, outputSampleRateHz);
} else {
sonic.flush();
@ -256,8 +263,6 @@ public final class SonicAudioProcessor implements AudioProcessor {
public void reset() {
speed = 1f;
pitch = 1f;
pendingSpeed = 1f;
pendingPitch = 1f;
channelCount = Format.NO_VALUE;
sampleRateHz = Format.NO_VALUE;
outputSampleRateHz = Format.NO_VALUE;