Fix extension FLAC decoder to correctly handle non-16-bit depths

PiperOrigin-RevId: 288860159
This commit is contained in:
olly 2020-01-09 10:13:25 +00:00 committed by Oliver Woodman
parent 017a7cf38c
commit 1a9b301f52
6 changed files with 71 additions and 26 deletions

View File

@ -56,6 +56,10 @@
([#4078](https://github.com/google/ExoPlayer/issues/4078)). ([#4078](https://github.com/google/ExoPlayer/issues/4078)).
* Don't use notification chronometer if playback speed is != 1.0 * Don't use notification chronometer if playback speed is != 1.0
([#6816](https://github.com/google/ExoPlayer/issues/6816)). ([#6816](https://github.com/google/ExoPlayer/issues/6816)).
* FLAC extension: Fix handling of bit depths other than 16 in `FLACDecoder`.
This issue caused FLAC streams with other bit depths to sound like white noise
on earlier releases, but only when embedded in a non-FLAC container such as
Matroska or MP4.
### 2.11.1 (2019-12-20) ### ### 2.11.1 (2019-12-20) ###
@ -222,7 +226,7 @@
`C.MSG_SET_OUTPUT_BUFFER_RENDERER`. `C.MSG_SET_OUTPUT_BUFFER_RENDERER`.
* Use `VideoDecoderRenderer` as an implementation of * Use `VideoDecoderRenderer` as an implementation of
`VideoDecoderOutputBufferRenderer`, instead of `VideoDecoderSurfaceView`. `VideoDecoderOutputBufferRenderer`, instead of `VideoDecoderSurfaceView`.
* Flac extension: Update to use NDK r20. * FLAC extension: Update to use NDK r20.
* Opus extension: Update to use NDK r20. * Opus extension: Update to use NDK r20.
* FFmpeg extension: * FFmpeg extension:
* Update to use NDK r20. * Update to use NDK r20.
@ -359,7 +363,7 @@
([#6241](https://github.com/google/ExoPlayer/issues/6241)). ([#6241](https://github.com/google/ExoPlayer/issues/6241)).
* MP3: Use CBR header bitrate, not calculated bitrate. This reverts a change * MP3: Use CBR header bitrate, not calculated bitrate. This reverts a change
from 2.9.3 ([#6238](https://github.com/google/ExoPlayer/issues/6238)). from 2.9.3 ([#6238](https://github.com/google/ExoPlayer/issues/6238)).
* Flac extension: Parse `VORBIS_COMMENT` and `PICTURE` metadata * FLAC extension: Parse `VORBIS_COMMENT` and `PICTURE` metadata
([#5527](https://github.com/google/ExoPlayer/issues/5527)). ([#5527](https://github.com/google/ExoPlayer/issues/5527)).
* Fix issue where initial seek positions get ignored when playing a preroll ad * Fix issue where initial seek positions get ignored when playing a preroll ad
([#6201](https://github.com/google/ExoPlayer/issues/6201)). ([#6201](https://github.com/google/ExoPlayer/issues/6201)).
@ -368,7 +372,7 @@
([#6153](https://github.com/google/ExoPlayer/issues/6153)). ([#6153](https://github.com/google/ExoPlayer/issues/6153)).
* Fix `DataSchemeDataSource` re-opening and range requests * Fix `DataSchemeDataSource` re-opening and range requests
([#6192](https://github.com/google/ExoPlayer/issues/6192)). ([#6192](https://github.com/google/ExoPlayer/issues/6192)).
* Fix Flac and ALAC playback on some LG devices * Fix FLAC and ALAC playback on some LG devices
([#5938](https://github.com/google/ExoPlayer/issues/5938)). ([#5938](https://github.com/google/ExoPlayer/issues/5938)).
* Fix issue when calling `performClick` on `PlayerView` without * Fix issue when calling `performClick` on `PlayerView` without
`PlayerControlView` `PlayerControlView`

View File

@ -33,7 +33,7 @@ import java.util.List;
/* package */ final class FlacDecoder extends /* package */ final class FlacDecoder extends
SimpleDecoder<DecoderInputBuffer, SimpleOutputBuffer, FlacDecoderException> { SimpleDecoder<DecoderInputBuffer, SimpleOutputBuffer, FlacDecoderException> {
private final int maxOutputBufferSize; private final FlacStreamMetadata streamMetadata;
private final FlacDecoderJni decoderJni; private final FlacDecoderJni decoderJni;
/** /**
@ -59,7 +59,6 @@ import java.util.List;
} }
decoderJni = new FlacDecoderJni(); decoderJni = new FlacDecoderJni();
decoderJni.setData(ByteBuffer.wrap(initializationData.get(0))); decoderJni.setData(ByteBuffer.wrap(initializationData.get(0)));
FlacStreamMetadata streamMetadata;
try { try {
streamMetadata = decoderJni.decodeStreamMetadata(); streamMetadata = decoderJni.decodeStreamMetadata();
} catch (ParserException e) { } catch (ParserException e) {
@ -72,7 +71,6 @@ import java.util.List;
int initialInputBufferSize = int initialInputBufferSize =
maxInputBufferSize != Format.NO_VALUE ? maxInputBufferSize : streamMetadata.maxFrameSize; maxInputBufferSize != Format.NO_VALUE ? maxInputBufferSize : streamMetadata.maxFrameSize;
setInitialInputBufferSize(initialInputBufferSize); setInitialInputBufferSize(initialInputBufferSize);
maxOutputBufferSize = streamMetadata.getMaxDecodedFrameSize();
} }
@Override @Override
@ -103,7 +101,8 @@ import java.util.List;
decoderJni.flush(); decoderJni.flush();
} }
decoderJni.setData(Util.castNonNull(inputBuffer.data)); decoderJni.setData(Util.castNonNull(inputBuffer.data));
ByteBuffer outputData = outputBuffer.init(inputBuffer.timeUs, maxOutputBufferSize); ByteBuffer outputData =
outputBuffer.init(inputBuffer.timeUs, streamMetadata.getMaxDecodedFrameSize());
try { try {
decoderJni.decodeSample(outputData); decoderJni.decodeSample(outputData);
} catch (FlacDecoderJni.FlacFrameDecodeException e) { } catch (FlacDecoderJni.FlacFrameDecodeException e) {
@ -121,4 +120,8 @@ import java.util.List;
decoderJni.release(); decoderJni.release();
} }
/** Returns the {@link FlacStreamMetadata} decoded from the initialization data. */
public FlacStreamMetadata getStreamMetadata() {
return streamMetadata;
}
} }

View File

@ -24,15 +24,20 @@ import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer; import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.FlacConstants;
import com.google.android.exoplayer2.util.FlacStreamMetadata;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /** Decodes and renders audio using the native Flac decoder. */
* Decodes and renders audio using the native Flac decoder. public final class LibflacAudioRenderer extends SimpleDecoderAudioRenderer {
*/
public class LibflacAudioRenderer extends SimpleDecoderAudioRenderer {
private static final int NUM_BUFFERS = 16; private static final int NUM_BUFFERS = 16;
@MonotonicNonNull private FlacStreamMetadata streamMetadata;
public LibflacAudioRenderer() { public LibflacAudioRenderer() {
this(/* eventHandler= */ null, /* eventListener= */ null); this(/* eventHandler= */ null, /* eventListener= */ null);
} }
@ -57,7 +62,23 @@ public class LibflacAudioRenderer extends SimpleDecoderAudioRenderer {
if (!FlacLibrary.isAvailable() if (!FlacLibrary.isAvailable()
|| !MimeTypes.AUDIO_FLAC.equalsIgnoreCase(format.sampleMimeType)) { || !MimeTypes.AUDIO_FLAC.equalsIgnoreCase(format.sampleMimeType)) {
return FORMAT_UNSUPPORTED_TYPE; return FORMAT_UNSUPPORTED_TYPE;
} else if (!supportsOutput(format.channelCount, C.ENCODING_PCM_16BIT)) { }
// Compute the PCM encoding that the FLAC decoder will output.
@C.PcmEncoding int pcmEncoding;
if (format.initializationData.isEmpty()) {
// The initialization data might not be set if the format was obtained from a manifest (e.g.
// for DASH playbacks) rather than directly from the media. In this case we assume
// ENCODING_PCM_16BIT. If the actual encoding is different, playback will still succeed as
// long as the AudioSink supports it (which will always be true when using DefaultAudioSink).
pcmEncoding = C.ENCODING_PCM_16BIT;
} else {
int streamMetadataOffset =
FlacConstants.STREAM_MARKER_SIZE + FlacConstants.METADATA_BLOCK_HEADER_SIZE;
FlacStreamMetadata streamMetadata =
new FlacStreamMetadata(format.initializationData.get(0), streamMetadataOffset);
pcmEncoding = Util.getPcmEncoding(streamMetadata.bitsPerSample);
}
if (!supportsOutput(format.channelCount, pcmEncoding)) {
return FORMAT_UNSUPPORTED_SUBTYPE; return FORMAT_UNSUPPORTED_SUBTYPE;
} else if (!supportsFormatDrm(drmSessionManager, format.drmInitData)) { } else if (!supportsFormatDrm(drmSessionManager, format.drmInitData)) {
return FORMAT_UNSUPPORTED_DRM; return FORMAT_UNSUPPORTED_DRM;
@ -69,8 +90,27 @@ public class LibflacAudioRenderer extends SimpleDecoderAudioRenderer {
@Override @Override
protected FlacDecoder createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto) protected FlacDecoder createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto)
throws FlacDecoderException { throws FlacDecoderException {
return new FlacDecoder( FlacDecoder decoder =
NUM_BUFFERS, NUM_BUFFERS, format.maxInputSize, format.initializationData); new FlacDecoder(NUM_BUFFERS, NUM_BUFFERS, format.maxInputSize, format.initializationData);
streamMetadata = decoder.getStreamMetadata();
return decoder;
} }
@Override
protected Format getOutputFormat() {
Assertions.checkNotNull(streamMetadata);
return Format.createAudioSampleFormat(
/* id= */ null,
MimeTypes.AUDIO_RAW,
/* codecs= */ null,
/* bitrate= */ Format.NO_VALUE,
/* maxInputSize= */ Format.NO_VALUE,
streamMetadata.channels,
streamMetadata.sampleRate,
Util.getPcmEncoding(streamMetadata.bitsPerSample),
/* initializationData= */ null,
/* drmInitData= */ null,
/* selectionFlags= */ 0,
/* language= */ null);
}
} }

View File

@ -351,15 +351,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
/** /**
* Returns the format of audio buffers output by the decoder. Will not be called until the first * 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. * output buffer has been dequeued, so the decoder may use input data to determine the format.
* <p>
* The default implementation returns a 16-bit PCM format with the same channel count and sample
* rate as the input.
*/ */
protected Format getOutputFormat() { protected abstract Format getOutputFormat();
return Format.createAudioSampleFormat(null, MimeTypes.AUDIO_RAW, null, Format.NO_VALUE,
Format.NO_VALUE, inputFormat.channelCount, inputFormat.sampleRate, C.ENCODING_PCM_16BIT,
null, null, 0, null);
}
/** /**
* Returns whether the existing decoder can be kept for a new format. * Returns whether the existing decoder can be kept for a new format.

View File

@ -102,8 +102,9 @@ public final class FlacStreamMetadata {
/** /**
* Parses binary FLAC stream info metadata. * Parses binary FLAC stream info metadata.
* *
* @param data An array containing binary FLAC stream info block (with or without header). * @param data An array containing binary FLAC stream info block.
* @param offset The offset of the stream info block in {@code data} (header excluded). * @param offset The offset of the stream info block in {@code data}, excluding the header (i.e.
* the offset points to the first byte of the minimum block size).
*/ */
public FlacStreamMetadata(byte[] data, int offset) { public FlacStreamMetadata(byte[] data, int offset) {
ParsableBitArray scratch = new ParsableBitArray(data); ParsableBitArray scratch = new ParsableBitArray(data);

View File

@ -67,10 +67,14 @@ public class SimpleDecoderAudioRendererTest {
@Override @Override
protected SimpleDecoder< protected SimpleDecoder<
DecoderInputBuffer, ? extends SimpleOutputBuffer, ? extends AudioDecoderException> DecoderInputBuffer, ? extends SimpleOutputBuffer, ? extends AudioDecoderException>
createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto) createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto) {
throws AudioDecoderException {
return new FakeDecoder(); return new FakeDecoder();
} }
@Override
protected Format getOutputFormat() {
return FORMAT;
}
}; };
} }