Fix extension FLAC decoder to correctly handle non-16-bit depths
PiperOrigin-RevId: 288860159
This commit is contained in:
parent
017a7cf38c
commit
1a9b301f52
@ -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`
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user