Audio extension decoders: Pass decoder to getOutputFormat

It seems generally useful to have access to the decoder in
getOutputFormat. We're currently working around lack of access
by using member variables in the concrete audio extension
renderers. In the case of the Ffmpeg extension, holding a
reference to the decoder is preventing it from being garbage
collected when the decoder is released by the base class.

PiperOrigin-RevId: 324799670
This commit is contained in:
olly 2020-08-04 14:25:39 +01:00 committed by kim-vde
parent a6be8eeb6b
commit 4d03d30890
6 changed files with 32 additions and 54 deletions

View file

@ -34,10 +34,9 @@ import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.TraceUtil;
import com.google.android.exoplayer2.util.Util;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Decodes and renders audio using FFmpeg. */
public final class FfmpegAudioRenderer extends DecoderAudioRenderer {
public final class FfmpegAudioRenderer extends DecoderAudioRenderer<FfmpegAudioDecoder> {
private static final String TAG = "FfmpegAudioRenderer";
@ -46,8 +45,6 @@ public final class FfmpegAudioRenderer extends DecoderAudioRenderer {
/** The default input buffer size. */
private static final int DEFAULT_INPUT_BUFFER_SIZE = 960 * 6;
private @MonotonicNonNull FfmpegAudioDecoder decoder;
public FfmpegAudioRenderer() {
this(/* eventHandler= */ null, /* eventListener= */ null);
}
@ -122,7 +119,7 @@ public final class FfmpegAudioRenderer extends DecoderAudioRenderer {
TraceUtil.beginSection("createFfmpegAudioDecoder");
int initialInputBufferSize =
format.maxInputSize != Format.NO_VALUE ? format.maxInputSize : DEFAULT_INPUT_BUFFER_SIZE;
decoder =
FfmpegAudioDecoder decoder =
new FfmpegAudioDecoder(
format, NUM_BUFFERS, NUM_BUFFERS, initialInputBufferSize, shouldOutputFloat(format));
TraceUtil.endSection();
@ -130,7 +127,7 @@ public final class FfmpegAudioRenderer extends DecoderAudioRenderer {
}
@Override
public Format getOutputFormat() {
public Format getOutputFormat(FfmpegAudioDecoder decoder) {
Assertions.checkNotNull(decoder);
return new Format.Builder()
.setSampleMimeType(MimeTypes.AUDIO_RAW)

View file

@ -25,21 +25,17 @@ import com.google.android.exoplayer2.audio.AudioSink;
import com.google.android.exoplayer2.audio.DecoderAudioRenderer;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.extractor.FlacStreamMetadata;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.FlacConstants;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.TraceUtil;
import com.google.android.exoplayer2.util.Util;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Decodes and renders audio using the native Flac decoder. */
public final class LibflacAudioRenderer extends DecoderAudioRenderer {
public final class LibflacAudioRenderer extends DecoderAudioRenderer<FlacDecoder> {
private static final String TAG = "LibflacAudioRenderer";
private static final int NUM_BUFFERS = 16;
private @MonotonicNonNull FlacStreamMetadata streamMetadata;
public LibflacAudioRenderer() {
this(/* eventHandler= */ null, /* eventListener= */ null);
}
@ -120,14 +116,13 @@ public final class LibflacAudioRenderer extends DecoderAudioRenderer {
TraceUtil.beginSection("createFlacDecoder");
FlacDecoder decoder =
new FlacDecoder(NUM_BUFFERS, NUM_BUFFERS, format.maxInputSize, format.initializationData);
streamMetadata = decoder.getStreamMetadata();
TraceUtil.endSection();
return decoder;
}
@Override
protected Format getOutputFormat() {
return getOutputFormat(Assertions.checkNotNull(streamMetadata));
protected Format getOutputFormat(FlacDecoder decoder) {
return getOutputFormat(decoder.getStreamMetadata());
}
private static Format getOutputFormat(FlacStreamMetadata streamMetadata) {

View file

@ -30,7 +30,7 @@ import com.google.android.exoplayer2.util.TraceUtil;
import com.google.android.exoplayer2.util.Util;
/** Decodes and renders audio using the native Opus decoder. */
public class LibopusAudioRenderer extends DecoderAudioRenderer {
public class LibopusAudioRenderer extends DecoderAudioRenderer<OpusDecoder> {
private static final String TAG = "LibopusAudioRenderer";
/** The number of input and output buffers. */
@ -38,10 +38,6 @@ public class LibopusAudioRenderer extends DecoderAudioRenderer {
/** The default input buffer size. */
private static final int DEFAULT_INPUT_BUFFER_SIZE = 960 * 6;
private int channelCount;
private int sampleRate;
private boolean outputFloat;
public LibopusAudioRenderer() {
this(/* eventHandler= */ null, /* eventListener= */ null);
}
@ -108,7 +104,7 @@ public class LibopusAudioRenderer extends DecoderAudioRenderer {
int formatSupport =
getSinkFormatSupport(
Util.getPcmFormat(C.ENCODING_PCM_FLOAT, format.channelCount, format.sampleRate));
outputFloat = formatSupport == AudioSink.SINK_FORMAT_SUPPORTED_DIRECTLY;
boolean outputFloat = formatSupport == AudioSink.SINK_FORMAT_SUPPORTED_DIRECTLY;
int initialInputBufferSize =
format.maxInputSize != Format.NO_VALUE ? format.maxInputSize : DEFAULT_INPUT_BUFFER_SIZE;
@ -120,16 +116,15 @@ public class LibopusAudioRenderer extends DecoderAudioRenderer {
format.initializationData,
mediaCrypto,
outputFloat);
channelCount = decoder.getChannelCount();
sampleRate = decoder.getSampleRate();
TraceUtil.endSection();
return decoder;
}
@Override
protected Format getOutputFormat() {
@C.PcmEncoding int pcmEncoding = outputFloat ? C.ENCODING_PCM_FLOAT : C.ENCODING_PCM_16BIT;
return Util.getPcmFormat(pcmEncoding, channelCount, sampleRate);
protected Format getOutputFormat(OpusDecoder decoder) {
@C.PcmEncoding
int pcmEncoding = decoder.outputFloat ? C.ENCODING_PCM_FLOAT : C.ENCODING_PCM_16BIT;
return Util.getPcmFormat(pcmEncoding, decoder.channelCount, OpusDecoder.SAMPLE_RATE);
}
}

View file

@ -36,21 +36,22 @@ import java.util.List;
private static final int DEFAULT_SEEK_PRE_ROLL_SAMPLES = 3840;
/** Opus streams are always decoded at 48000 Hz. */
private static final int SAMPLE_RATE = 48_000;
public static final int SAMPLE_RATE = 48_000;
private static final int NO_ERROR = 0;
private static final int DECODE_ERROR = -1;
private static final int DRM_ERROR = -2;
public final boolean outputFloat;
public final int channelCount;
@Nullable private final ExoMediaCrypto exoMediaCrypto;
private final int channelCount;
private final int headerSkipSamples;
private final int headerSeekPreRollSamples;
private final long nativeDecoderContext;
private int skipSamples;
private final boolean outputFloat;
/**
* Creates an Opus decoder.
@ -230,16 +231,6 @@ import java.util.List;
opusClose(nativeDecoderContext);
}
/** Returns the channel count of output audio. */
public int getChannelCount() {
return channelCount;
}
/** Returns the sample rate of output audio. */
public int getSampleRate() {
return SAMPLE_RATE;
}
private static int nsToSamples(long ns) {
return (int) (ns * SAMPLE_RATE / 1000000000);
}

View file

@ -73,7 +73,10 @@ import java.lang.annotation.RetentionPolicy;
* underlying audio track.
* </ul>
*/
public abstract class DecoderAudioRenderer extends BaseRenderer implements MediaClock {
public abstract class DecoderAudioRenderer<
T extends
Decoder<DecoderInputBuffer, ? extends SimpleOutputBuffer, ? extends DecoderException>>
extends BaseRenderer implements MediaClock {
@Documented
@Retention(RetentionPolicy.SOURCE)
@ -109,9 +112,7 @@ public abstract class DecoderAudioRenderer extends BaseRenderer implements Media
private int encoderDelay;
private int encoderPadding;
@Nullable
private Decoder<DecoderInputBuffer, ? extends SimpleOutputBuffer, ? extends DecoderException>
decoder;
@Nullable private T decoder;
@Nullable private DecoderInputBuffer inputBuffer;
@Nullable private SimpleOutputBuffer outputBuffer;
@ -317,15 +318,16 @@ public abstract class DecoderAudioRenderer extends BaseRenderer implements Media
* @return The decoder.
* @throws DecoderException If an error occurred creating a suitable decoder.
*/
protected abstract Decoder<
DecoderInputBuffer, ? extends SimpleOutputBuffer, ? extends DecoderException>
createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto) throws DecoderException;
protected abstract T createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto)
throws DecoderException;
/**
* Returns the format of audio buffers output by the decoder. Will not be called until the first
* output buffer has been dequeued, so the decoder may use input data to determine the format.
*
* @param decoder The decoder.
*/
protected abstract Format getOutputFormat();
protected abstract Format getOutputFormat(T decoder);
/**
* Returns whether the existing decoder can be kept for a new format.
@ -365,7 +367,7 @@ public abstract class DecoderAudioRenderer extends BaseRenderer implements Media
try {
processEndOfStream();
} catch (AudioSink.WriteException e) {
throw createRendererException(e, getOutputFormat());
throw createRendererException(e, getOutputFormat(decoder));
}
}
return false;
@ -373,7 +375,7 @@ public abstract class DecoderAudioRenderer extends BaseRenderer implements Media
if (audioTrackNeedsConfigure) {
Format outputFormat =
getOutputFormat()
getOutputFormat(decoder)
.buildUpon()
.setEncoderDelay(encoderDelay)
.setEncoderPadding(encoderPadding)

View file

@ -55,13 +55,13 @@ public class DecoderAudioRendererTest {
new Format.Builder().setSampleMimeType(MimeTypes.AUDIO_RAW).build();
@Mock private AudioSink mockAudioSink;
private DecoderAudioRenderer audioRenderer;
private DecoderAudioRenderer<FakeDecoder> audioRenderer;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
audioRenderer =
new DecoderAudioRenderer(null, null, mockAudioSink) {
new DecoderAudioRenderer<FakeDecoder>(null, null, mockAudioSink) {
@Override
public String getName() {
return "TestAudioRenderer";
@ -74,14 +74,12 @@ public class DecoderAudioRendererTest {
}
@Override
protected SimpleDecoder<
DecoderInputBuffer, ? extends SimpleOutputBuffer, ? extends DecoderException>
createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto) {
protected FakeDecoder createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto) {
return new FakeDecoder();
}
@Override
protected Format getOutputFormat() {
protected Format getOutputFormat(FakeDecoder decoder) {
return FORMAT;
}
};