/** * Creates a new default chain of audio processors, with the user-defined {@code * audioProcessors} applied before silence skipping and playback parameters. */ public DefaultAudioProcessorChain(AudioProcessor... audioProcessors) { this.audioProcessors = Arrays.copyOf(audioProcessors, audioProcessors.length + 2); silenceSkippingAudioProcessor = new SilenceSkippingAudioProcessor(); sonicAudioProcessor = new SonicAudioProcessor(); this.audioProcessors[audioProcessors.length] = silenceSkippingAudioProcessor; this.audioProcessors[audioProcessors.length + 1] = sonicAudioProcessor; }
@Override public void reset() { enabled = false; flush(); buffer = EMPTY_BUFFER; channelCount = Format.NO_VALUE; sampleRateHz = Format.NO_VALUE; paddingSize = 0; maybeSilenceBuffer = Util.EMPTY_BYTE_ARRAY; paddingBuffer = Util.EMPTY_BYTE_ARRAY; }
@Override public long getSkippedOutputFrameCount() { return silenceSkippingAudioProcessor.getSkippedFrames(); } }
@Override public void flush() { if (isActive()) { int maybeSilenceBufferSize = durationUsToFrames(MINIMUM_SILENCE_DURATION_US) * bytesPerFrame; if (maybeSilenceBuffer.length != maybeSilenceBufferSize) { maybeSilenceBuffer = new byte[maybeSilenceBufferSize]; } paddingSize = durationUsToFrames(PADDING_SILENCE_US) * bytesPerFrame; if (paddingBuffer.length != paddingSize) { paddingBuffer = new byte[paddingSize]; } } state = STATE_NOISY; outputBuffer = EMPTY_BUFFER; inputEnded = false; skippedFrames = 0; maybeSilenceBufferSize = 0; hasOutputNoise = false; }
/** * Incrementally processes new input from {@code inputBuffer} while in {@link #STATE_SILENT}, * updating the state if needed. */ private void processSilence(ByteBuffer inputBuffer) { int limit = inputBuffer.limit(); int noisyPosition = findNoisePosition(inputBuffer); inputBuffer.limit(noisyPosition); skippedFrames += inputBuffer.remaining() / bytesPerFrame; updatePaddingBuffer(inputBuffer, paddingBuffer, paddingSize); if (noisyPosition < limit) { // Output the padding, which may include previous input as well as new input, then transition // back to the noisy state. output(paddingBuffer, paddingSize); state = STATE_NOISY; // Restore the limit. inputBuffer.limit(limit); } }
/** * Incrementally processes new input from {@code inputBuffer} while in {@link #STATE_NOISY}, * updating the state if needed. */ private void processNoisy(ByteBuffer inputBuffer) { int limit = inputBuffer.limit(); // Check if there's any noise within the maybe silence buffer duration. inputBuffer.limit(Math.min(limit, inputBuffer.position() + maybeSilenceBuffer.length)); int noiseLimit = findNoiseLimit(inputBuffer); if (noiseLimit == inputBuffer.position()) { // The buffer contains the start of possible silence. state = STATE_MAYBE_SILENT; } else { inputBuffer.limit(noiseLimit); output(inputBuffer); } // Restore the limit. inputBuffer.limit(limit); }
@Test public void testEnabledProcessor_isActive() throws Exception { // Given an enabled processor. silenceSkippingAudioProcessor.setEnabled(true); // When configuring it. boolean reconfigured = silenceSkippingAudioProcessor.configure( TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT); silenceSkippingAudioProcessor.flush(); // It's active. assertThat(reconfigured).isTrue(); assertThat(silenceSkippingAudioProcessor.isActive()).isTrue(); }
/** * Processes the entire stream provided by {@code inputBufferProvider} in chunks of {@code * inputBufferSize} and returns the total number of output frames. */ private static long process( SilenceSkippingAudioProcessor processor, InputBufferProvider inputBufferProvider, int inputBufferSize) throws UnhandledFormatException { processor.flush(); long totalOutputFrames = 0; while (inputBufferProvider.hasRemaining()) { ByteBuffer inputBuffer = inputBufferProvider.getNextInputBuffer(inputBufferSize); while (inputBuffer.hasRemaining()) { processor.queueInput(inputBuffer); ByteBuffer outputBuffer = processor.getOutput(); totalOutputFrames += outputBuffer.remaining() / (2 * processor.getOutputChannelCount()); outputBuffer.clear(); } } processor.queueEndOfStream(); while (!processor.isEnded()) { ByteBuffer outputBuffer = processor.getOutput(); totalOutputFrames += outputBuffer.remaining() / (2 * processor.getOutputChannelCount()); outputBuffer.clear(); } return totalOutputFrames; }
@Test public void testDisabledProcessor_isNotActive() throws Exception { // Given a disabled processor. silenceSkippingAudioProcessor.setEnabled(false); // When configuring it. silenceSkippingAudioProcessor.configure( TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT); // It's not active. assertThat(silenceSkippingAudioProcessor.isActive()).isFalse(); }
@Test public void testDefaultProcessor_isNotEnabled() throws Exception { // Given a processor in its default state. // When reconfigured. silenceSkippingAudioProcessor.configure( TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT); // It's not active. assertThat(silenceSkippingAudioProcessor.isActive()).isFalse(); }
@Override public PlaybackParameters applyPlaybackParameters(PlaybackParameters playbackParameters) { silenceSkippingAudioProcessor.setEnabled(playbackParameters.skipSilence); return new PlaybackParameters( sonicAudioProcessor.setSpeed(playbackParameters.speed), sonicAudioProcessor.setPitch(playbackParameters.pitch), playbackParameters.skipSilence); }
@Test public void testChangingSampleRate_requiresReconfiguration() throws Exception { // Given an enabled processor and configured processor. silenceSkippingAudioProcessor.setEnabled(true); boolean reconfigured = silenceSkippingAudioProcessor.configure( TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT); if (reconfigured) { silenceSkippingAudioProcessor.flush(); } // When reconfiguring it with a different sample rate. reconfigured = silenceSkippingAudioProcessor.configure( TEST_SIGNAL_SAMPLE_RATE_HZ * 2, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT); // It's reconfigured. assertThat(reconfigured).isTrue(); assertThat(silenceSkippingAudioProcessor.isActive()).isTrue(); }
int noisePosition = findNoisePosition(inputBuffer); int maybeSilenceInputSize = noisePosition - inputBuffer.position(); int maybeSilenceBufferRemaining = maybeSilenceBuffer.length - maybeSilenceBufferSize; if (noisePosition < limit && maybeSilenceInputSize < maybeSilenceBufferRemaining) { output(maybeSilenceBuffer, maybeSilenceBufferSize); maybeSilenceBufferSize = 0; state = STATE_NOISY; output(maybeSilenceBuffer, paddingSize); skippedFrames += (maybeSilenceBufferSize - paddingSize * 2) / bytesPerFrame; } else { skippedFrames += (maybeSilenceBufferSize - paddingSize) / bytesPerFrame; updatePaddingBuffer(inputBuffer, maybeSilenceBuffer, maybeSilenceBufferSize); maybeSilenceBufferSize = 0; state = STATE_SILENT;
@Test public void testReconfiguringWithSameSampleRate_doesNotRequireReconfiguration() throws Exception { // Given an enabled processor and configured processor. silenceSkippingAudioProcessor.setEnabled(true); boolean reconfigured = silenceSkippingAudioProcessor.configure( TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT); assertThat(reconfigured).isTrue(); silenceSkippingAudioProcessor.flush(); // When reconfiguring it with the same sample rate. reconfigured = silenceSkippingAudioProcessor.configure( TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT); // It's not reconfigured but it is active. assertThat(reconfigured).isFalse(); assertThat(silenceSkippingAudioProcessor.isActive()).isTrue(); }
@Before public void setUp() { silenceSkippingAudioProcessor = new SilenceSkippingAudioProcessor(); }
/** * 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) { this.enabled = enabled; flush(); }
@Test public void testSkipThenFlush_resetsSkippedFrameCount() throws Exception { // Given a signal that alternates between silence and noise. InputBufferProvider inputBufferProvider = getInputBufferProviderForAlternatingSilenceAndNoise( TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, TEST_SIGNAL_SILENCE_DURATION_MS, TEST_SIGNAL_NOISE_DURATION_MS, TEST_SIGNAL_FRAME_COUNT); // When processing the entire signal then flushing. SilenceSkippingAudioProcessor silenceSkippingAudioProcessor = new SilenceSkippingAudioProcessor(); silenceSkippingAudioProcessor.setEnabled(true); boolean reconfigured = silenceSkippingAudioProcessor.configure( TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT); silenceSkippingAudioProcessor.flush(); assertThat(reconfigured).isTrue(); assertThat(silenceSkippingAudioProcessor.isActive()).isTrue(); process(silenceSkippingAudioProcessor, inputBufferProvider, INPUT_BUFFER_SIZE); silenceSkippingAudioProcessor.flush(); // The skipped frame count is zero. assertThat(silenceSkippingAudioProcessor.getSkippedFrames()).isEqualTo(0); }
@Test public void testSkipWithSmallerInputBufferSize_hasCorrectOutputAndSkippedFrameCounts() throws Exception { // Given a signal that alternates between silence and noise. InputBufferProvider inputBufferProvider = getInputBufferProviderForAlternatingSilenceAndNoise( TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, TEST_SIGNAL_SILENCE_DURATION_MS, TEST_SIGNAL_NOISE_DURATION_MS, TEST_SIGNAL_FRAME_COUNT); // When processing the entire signal with a smaller input buffer size. SilenceSkippingAudioProcessor silenceSkippingAudioProcessor = new SilenceSkippingAudioProcessor(); silenceSkippingAudioProcessor.setEnabled(true); boolean reconfigured = silenceSkippingAudioProcessor.configure( TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT); silenceSkippingAudioProcessor.flush(); assertThat(reconfigured).isTrue(); assertThat(silenceSkippingAudioProcessor.isActive()).isTrue(); long totalOutputFrames = process(silenceSkippingAudioProcessor, inputBufferProvider, /* inputBufferSize= */ 80); // The right number of frames are skipped/output. assertThat(totalOutputFrames).isEqualTo(57980); assertThat(silenceSkippingAudioProcessor.getSkippedFrames()).isEqualTo(42020); }
@Test public void testSkipInAlternatingTestSignal_hasCorrectOutputAndSkippedFrameCounts() throws Exception { // Given a signal that alternates between silence and noise. InputBufferProvider inputBufferProvider = getInputBufferProviderForAlternatingSilenceAndNoise( TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, TEST_SIGNAL_SILENCE_DURATION_MS, TEST_SIGNAL_NOISE_DURATION_MS, TEST_SIGNAL_FRAME_COUNT); // When processing the entire signal. SilenceSkippingAudioProcessor silenceSkippingAudioProcessor = new SilenceSkippingAudioProcessor(); silenceSkippingAudioProcessor.setEnabled(true); boolean reconfigured = silenceSkippingAudioProcessor.configure( TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT); silenceSkippingAudioProcessor.flush(); assertThat(reconfigured).isTrue(); assertThat(silenceSkippingAudioProcessor.isActive()).isTrue(); long totalOutputFrames = process(silenceSkippingAudioProcessor, inputBufferProvider, INPUT_BUFFER_SIZE); // The right number of frames are skipped/output. assertThat(totalOutputFrames).isEqualTo(57980); assertThat(silenceSkippingAudioProcessor.getSkippedFrames()).isEqualTo(42020); }
@Test public void testSkipInNoisySignal_skipsNothing() throws Exception { // Given a signal with only silence. InputBufferProvider inputBufferProvider = getInputBufferProviderForAlternatingSilenceAndNoise( TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, /* silenceDurationMs= */ 0, TEST_SIGNAL_NOISE_DURATION_MS, TEST_SIGNAL_FRAME_COUNT); // When processing the entire signal. SilenceSkippingAudioProcessor silenceSkippingAudioProcessor = new SilenceSkippingAudioProcessor(); silenceSkippingAudioProcessor.setEnabled(true); boolean reconfigured = silenceSkippingAudioProcessor.configure( TEST_SIGNAL_SAMPLE_RATE_HZ, TEST_SIGNAL_CHANNEL_COUNT, C.ENCODING_PCM_16BIT); silenceSkippingAudioProcessor.flush(); assertThat(reconfigured).isTrue(); assertThat(silenceSkippingAudioProcessor.isActive()).isTrue(); long totalOutputFrames = process(silenceSkippingAudioProcessor, inputBufferProvider, INPUT_BUFFER_SIZE); // None of the signal is skipped. assertThat(totalOutputFrames).isEqualTo(TEST_SIGNAL_FRAME_COUNT); assertThat(silenceSkippingAudioProcessor.getSkippedFrames()).isEqualTo(0); }