diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 8593feba5ca..1c3f6307e31 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -57,6 +57,10 @@ * Smooth Streaming extension: * RTSP extension: * Decoder extensions (FFmpeg, VP9, AV1, etc.): + * FFmpeg extension: Fix an issue that prevented some FLAC files from + playing by ensuring the `STREAMINFO` block is correctly parsed and + passed to the decoder + ([#2887](https://github.com/androidx/media/issues/2887)). * MIDI extension: * Leanback extension: * Cast extension: diff --git a/libraries/decoder_ffmpeg/src/main/java/androidx/media3/decoder/ffmpeg/FfmpegAudioDecoder.java b/libraries/decoder_ffmpeg/src/main/java/androidx/media3/decoder/ffmpeg/FfmpegAudioDecoder.java index d558c639009..5b692180c06 100644 --- a/libraries/decoder_ffmpeg/src/main/java/androidx/media3/decoder/ffmpeg/FfmpegAudioDecoder.java +++ b/libraries/decoder_ffmpeg/src/main/java/androidx/media3/decoder/ffmpeg/FfmpegAudioDecoder.java @@ -39,6 +39,12 @@ private static final int AUDIO_DECODER_ERROR_INVALID_DATA = -1; private static final int AUDIO_DECODER_ERROR_OTHER = -2; + // FLAC parsing constants + private static final byte[] flacStreamMarker = {'f', 'L', 'a', 'C'}; + private static final int FLAC_METADATA_TYPE_STREAM_INFO = 0; + private static final int FLAC_METADATA_BLOCK_HEADER_SIZE = 4; + private static final int FLAC_STREAM_INFO_DATA_SIZE = 34; + private final String codecName; @Nullable private final byte[] extraData; private final @C.PcmEncoding int encoding; @@ -190,6 +196,8 @@ private static byte[] getExtraData(String mimeType, List initializationD return getAlacExtraData(initializationData); case MimeTypes.AUDIO_VORBIS: return getVorbisExtraData(initializationData); + case MimeTypes.AUDIO_FLAC: + return getFlacExtraData(initializationData); default: // Other codecs do not require extra data. return null; @@ -227,6 +235,71 @@ private static byte[] getVorbisExtraData(List initializationData) { return extraData; } + @Nullable + private static byte[] getFlacExtraData(List initializationData) { + for (int i = 0; i < initializationData.size(); i++) { + byte[] out = extractFlacStreamInfo(initializationData.get(i)); + if (out != null) { + return out; + } + } + return null; + } + + @Nullable + private static byte[] extractFlacStreamInfo(byte[] data) { + int offset = 0; + if (arrayStartsWith(data, flacStreamMarker)) { + offset = flacStreamMarker.length; + } + + + if (data.length - offset == FLAC_STREAM_INFO_DATA_SIZE) { + byte[] streamInfo = new byte[FLAC_STREAM_INFO_DATA_SIZE]; + System.arraycopy(data, offset, streamInfo, 0, FLAC_STREAM_INFO_DATA_SIZE); + return streamInfo; + } + + if (data.length >= offset + FLAC_METADATA_BLOCK_HEADER_SIZE) { + int type = data[offset] & 0x7F; + int length = + ((data[offset + 1] & 0xFF) << 16) + | ((data[offset + 2] & 0xFF) << 8) + | (data[offset + 3] & 0xFF); + + if (type == FLAC_METADATA_TYPE_STREAM_INFO + && length == FLAC_STREAM_INFO_DATA_SIZE + && data.length + >= offset + + FLAC_METADATA_BLOCK_HEADER_SIZE + + FLAC_STREAM_INFO_DATA_SIZE) { + byte[] streamInfo = new byte[FLAC_STREAM_INFO_DATA_SIZE]; + System.arraycopy( + data, + offset + FLAC_METADATA_BLOCK_HEADER_SIZE, + streamInfo, + 0, + FLAC_STREAM_INFO_DATA_SIZE); + return streamInfo; + } + } + + return null; + } + + private static boolean arrayStartsWith(byte[] data, byte[] prefix) { + if (data.length < prefix.length) { + return false; + } + for (int i = 0; i < prefix.length; i++) { + if (data[i] != prefix[i]) { + return false; + } + } + return true; + } + + private native long ffmpegInitialize( String codecName, @Nullable byte[] extraData,