diff --git a/RELEASENOTES.md b/RELEASENOTES.md index bdef903be1..b6381e271f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -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` diff --git a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoder.java b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoder.java index e1f6112319..013b23ef21 100644 --- a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoder.java +++ b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacDecoder.java @@ -33,7 +33,7 @@ import java.util.List; /* package */ final class FlacDecoder extends SimpleDecoder { - 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; + } } diff --git a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/LibflacAudioRenderer.java b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/LibflacAudioRenderer.java index 3e8d727476..7d9f6253e3 100644 --- a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/LibflacAudioRenderer.java +++ b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/LibflacAudioRenderer.java @@ -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); + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java index 60870204cc..3977650146 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java @@ -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. - *

- * 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. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/FlacStreamMetadata.java b/library/core/src/main/java/com/google/android/exoplayer2/util/FlacStreamMetadata.java index deced8ebe6..470e82c13f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/FlacStreamMetadata.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/FlacStreamMetadata.java @@ -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); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java index f8fd2fc9ca..0d8d2dabb1 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java @@ -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; + } }; }