CapturingAudioSink.java
/*
* Copyright (C) 2020 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
*
* http://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.test.utils;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.audio.AudioSink;
import androidx.media3.exoplayer.audio.ForwardingAudioSink;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/** A {@link ForwardingAudioSink} that captures configuration, discontinuity and buffer events. */
@UnstableApi
public final class CapturingAudioSink extends ForwardingAudioSink implements Dumper.Dumpable {
private final List<Dumper.Dumpable> interceptedData;
@Nullable private ByteBuffer currentBuffer;
public CapturingAudioSink(AudioSink sink) {
super(sink);
interceptedData = new ArrayList<>();
}
@Override
public void configure(Format inputFormat, int specifiedBufferSize, @Nullable int[] outputChannels)
throws ConfigurationException {
interceptedData.add(
new DumpableConfiguration(
inputFormat.pcmEncoding, inputFormat.channelCount, inputFormat.sampleRate));
super.configure(inputFormat, specifiedBufferSize, outputChannels);
}
@Override
public void handleDiscontinuity() {
interceptedData.add(new DumpableDiscontinuity());
super.handleDiscontinuity();
}
@Override
@SuppressWarnings("ReferenceEquality")
public boolean handleBuffer(
ByteBuffer buffer, long presentationTimeUs, int encodedAccessUnitCount)
throws InitializationException, WriteException {
// handleBuffer is called repeatedly with the same buffer until it's been fully consumed by the
// sink. We only want to dump each buffer once, and we need to do so before the sink being
// forwarded to has a chance to modify its position.
if (buffer != currentBuffer) {
interceptedData.add(new DumpableBuffer(buffer, presentationTimeUs));
currentBuffer = buffer;
}
boolean fullyConsumed = super.handleBuffer(buffer, presentationTimeUs, encodedAccessUnitCount);
if (fullyConsumed) {
currentBuffer = null;
}
return fullyConsumed;
}
@Override
public void flush() {
currentBuffer = null;
super.flush();
}
@Override
public void reset() {
currentBuffer = null;
super.reset();
}
@Override
public void dump(Dumper dumper) {
for (int i = 0; i < interceptedData.size(); i++) {
interceptedData.get(i).dump(dumper);
}
}
private static final class DumpableConfiguration implements Dumper.Dumpable {
private final @C.PcmEncoding int inputPcmEncoding;
private final int inputChannelCount;
private final int inputSampleRate;
public DumpableConfiguration(
@C.PcmEncoding int inputPcmEncoding, int inputChannelCount, int inputSampleRate) {
this.inputPcmEncoding = inputPcmEncoding;
this.inputChannelCount = inputChannelCount;
this.inputSampleRate = inputSampleRate;
}
@Override
public void dump(Dumper dumper) {
dumper
.startBlock("config")
.add("pcmEncoding", inputPcmEncoding)
.add("channelCount", inputChannelCount)
.add("sampleRate", inputSampleRate)
.endBlock();
}
}
private static final class DumpableBuffer implements Dumper.Dumpable {
private final long presentationTimeUs;
private final int dataHashcode;
public DumpableBuffer(ByteBuffer buffer, long presentationTimeUs) {
this.presentationTimeUs = presentationTimeUs;
// Compute a hash of the buffer data without changing its position.
int initialPosition = buffer.position();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
buffer.position(initialPosition);
this.dataHashcode = Arrays.hashCode(data);
}
@Override
public void dump(Dumper dumper) {
dumper
.startBlock("buffer")
.add("time", presentationTimeUs)
.add("data", dataHashcode)
.endBlock();
}
}
private static final class DumpableDiscontinuity implements Dumper.Dumpable {
@Override
public void dump(Dumper dumper) {
dumper.startBlock("discontinuity").endBlock();
}
}
}