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)).
* Don't use notification chronometer if playback speed is != 1.0
([#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) ###
@ -222,7 +226,7 @@
`C.MSG_SET_OUTPUT_BUFFER_RENDERER`.
* Use `VideoDecoderRenderer` as an implementation of
`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.
* FFmpeg extension:
* Update to use NDK r20.
@ -359,7 +363,7 @@
([#6241](https://github.com/google/ExoPlayer/issues/6241)).
* MP3: Use CBR header bitrate, not calculated bitrate. This reverts a change
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)).
* Fix issue where initial seek positions get ignored when playing a preroll ad
([#6201](https://github.com/google/ExoPlayer/issues/6201)).
@ -368,7 +372,7 @@
([#6153](https://github.com/google/ExoPlayer/issues/6153)).
* Fix `DataSchemeDataSource` re-opening and range requests
([#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)).
* Fix issue when calling `performClick` on `PlayerView` without
`PlayerControlView`

View File

@ -33,7 +33,7 @@ import java.util.List;
/* package */ final class FlacDecoder extends
SimpleDecoder<DecoderInputBuffer, SimpleOutputBuffer, FlacDecoderException> {
private final int maxOutputBufferSize;
private final FlacStreamMetadata streamMetadata;
private final FlacDecoderJni decoderJni;
/**
@ -59,7 +59,6 @@ import java.util.List;
}
decoderJni = new FlacDecoderJni();
decoderJni.setData(ByteBuffer.wrap(initializationData.get(0)));
FlacStreamMetadata streamMetadata;
try {
streamMetadata = decoderJni.decodeStreamMetadata();
} catch (ParserException e) {
@ -72,7 +71,6 @@ import java.util.List;
int initialInputBufferSize =
maxInputBufferSize != Format.NO_VALUE ? maxInputBufferSize : streamMetadata.maxFrameSize;
setInitialInputBufferSize(initialInputBufferSize);
maxOutputBufferSize = streamMetadata.getMaxDecodedFrameSize();
}
@Override
@ -103,7 +101,8 @@ import java.util.List;
decoderJni.flush();
}
decoderJni.setData(Util.castNonNull(inputBuffer.data));
ByteBuffer outputData = outputBuffer.init(inputBuffer.timeUs, maxOutputBufferSize);
ByteBuffer outputData =
outputBuffer.init(inputBuffer.timeUs, streamMetadata.getMaxDecodedFrameSize());
try {
decoderJni.decodeSample(outputData);
} catch (FlacDecoderJni.FlacFrameDecodeException e) {
@ -121,4 +120,8 @@ import java.util.List;
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.drm.DrmSessionManager;
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.Util;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Decodes and renders audio using the native Flac decoder.
*/
public class LibflacAudioRenderer extends SimpleDecoderAudioRenderer {
/** Decodes and renders audio using the native Flac decoder. */
public final class LibflacAudioRenderer extends SimpleDecoderAudioRenderer {
private static final int NUM_BUFFERS = 16;
@MonotonicNonNull private FlacStreamMetadata streamMetadata;
public LibflacAudioRenderer() {
this(/* eventHandler= */ null, /* eventListener= */ null);
}
@ -57,7 +62,23 @@ public class LibflacAudioRenderer extends SimpleDecoderAudioRenderer {
if (!FlacLibrary.isAvailable()
|| !MimeTypes.AUDIO_FLAC.equalsIgnoreCase(format.sampleMimeType)) {
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;
} else if (!supportsFormatDrm(drmSessionManager, format.drmInitData)) {
return FORMAT_UNSUPPORTED_DRM;
@ -69,8 +90,27 @@ public class LibflacAudioRenderer extends SimpleDecoderAudioRenderer {
@Override
protected FlacDecoder createDecoder(Format format, @Nullable ExoMediaCrypto mediaCrypto)
throws FlacDecoderException {
return new FlacDecoder(
NUM_BUFFERS, NUM_BUFFERS, format.maxInputSize, format.initializationData);
FlacDecoder decoder =
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
* 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() {
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);
}
protected abstract Format getOutputFormat();
/**
* 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.
*
* @param data An array containing binary FLAC stream info block (with or without header).
* @param offset The offset of the stream info block in {@code data} (header excluded).
* @param data An array containing binary FLAC stream info block.
* @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) {
ParsableBitArray scratch = new ParsableBitArray(data);

View File

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