summaryrefslogtreecommitdiffstats
path: root/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/util/FlacStreamMetadata.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/util/FlacStreamMetadata.java')
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/util/FlacStreamMetadata.java384
1 files changed, 384 insertions, 0 deletions
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/util/FlacStreamMetadata.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/util/FlacStreamMetadata.java
new file mode 100644
index 0000000000..893481d8da
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/util/FlacStreamMetadata.java
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mozilla.thirdparty.com.google.android.exoplayer2.util;
+
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.Format;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.Metadata;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.flac.PictureFrame;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.metadata.flac.VorbisComment;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Holder for FLAC metadata.
+ *
+ * @see <a href="https://xiph.org/flac/format.html#metadata_block_streaminfo">FLAC format
+ * METADATA_BLOCK_STREAMINFO</a>
+ * @see <a href="https://xiph.org/flac/format.html#metadata_block_seektable">FLAC format
+ * METADATA_BLOCK_SEEKTABLE</a>
+ * @see <a href="https://xiph.org/flac/format.html#metadata_block_vorbis_comment">FLAC format
+ * METADATA_BLOCK_VORBIS_COMMENT</a>
+ * @see <a href="https://xiph.org/flac/format.html#metadata_block_picture">FLAC format
+ * METADATA_BLOCK_PICTURE</a>
+ */
+public final class FlacStreamMetadata {
+
+ /** A FLAC seek table. */
+ public static class SeekTable {
+ /** Seek points sample numbers. */
+ public final long[] pointSampleNumbers;
+ /** Seek points byte offsets from the first frame. */
+ public final long[] pointOffsets;
+
+ public SeekTable(long[] pointSampleNumbers, long[] pointOffsets) {
+ this.pointSampleNumbers = pointSampleNumbers;
+ this.pointOffsets = pointOffsets;
+ }
+ }
+
+ private static final String TAG = "FlacStreamMetadata";
+
+ /** Indicates that a value is not in the corresponding lookup table. */
+ public static final int NOT_IN_LOOKUP_TABLE = -1;
+ /** Separator between the field name of a Vorbis comment and the corresponding value. */
+ private static final String SEPARATOR = "=";
+
+ /** Minimum number of samples per block. */
+ public final int minBlockSizeSamples;
+ /** Maximum number of samples per block. */
+ public final int maxBlockSizeSamples;
+ /** Minimum frame size in bytes, or 0 if the value is unknown. */
+ public final int minFrameSize;
+ /** Maximum frame size in bytes, or 0 if the value is unknown. */
+ public final int maxFrameSize;
+ /** Sample rate in Hertz. */
+ public final int sampleRate;
+ /**
+ * Lookup key corresponding to the stream sample rate, or {@link #NOT_IN_LOOKUP_TABLE} if it is
+ * not in the lookup table.
+ *
+ * <p>This key is used to indicate the sample rate in the frame header for the most common values.
+ *
+ * <p>The sample rate lookup table is described in https://xiph.org/flac/format.html#frame_header.
+ */
+ public final int sampleRateLookupKey;
+ /** Number of audio channels. */
+ public final int channels;
+ /** Number of bits per sample. */
+ public final int bitsPerSample;
+ /**
+ * Lookup key corresponding to the number of bits per sample of the stream, or {@link
+ * #NOT_IN_LOOKUP_TABLE} if it is not in the lookup table.
+ *
+ * <p>This key is used to indicate the number of bits per sample in the frame header for the most
+ * common values.
+ *
+ * <p>The sample size lookup table is described in https://xiph.org/flac/format.html#frame_header.
+ */
+ public final int bitsPerSampleLookupKey;
+ /** Total number of samples, or 0 if the value is unknown. */
+ public final long totalSamples;
+ /** Seek table, or {@code null} if it is not provided. */
+ @Nullable public final SeekTable seekTable;
+ /** Content metadata, or {@code null} if it is not provided. */
+ @Nullable private final Metadata metadata;
+
+ /**
+ * Parses binary FLAC stream info metadata.
+ *
+ * @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);
+ scratch.setPosition(offset * 8);
+ minBlockSizeSamples = scratch.readBits(16);
+ maxBlockSizeSamples = scratch.readBits(16);
+ minFrameSize = scratch.readBits(24);
+ maxFrameSize = scratch.readBits(24);
+ sampleRate = scratch.readBits(20);
+ sampleRateLookupKey = getSampleRateLookupKey(sampleRate);
+ channels = scratch.readBits(3) + 1;
+ bitsPerSample = scratch.readBits(5) + 1;
+ bitsPerSampleLookupKey = getBitsPerSampleLookupKey(bitsPerSample);
+ totalSamples = scratch.readBitsToLong(36);
+ seekTable = null;
+ metadata = null;
+ }
+
+ // Used in native code.
+ public FlacStreamMetadata(
+ int minBlockSizeSamples,
+ int maxBlockSizeSamples,
+ int minFrameSize,
+ int maxFrameSize,
+ int sampleRate,
+ int channels,
+ int bitsPerSample,
+ long totalSamples,
+ ArrayList<String> vorbisComments,
+ ArrayList<PictureFrame> pictureFrames) {
+ this(
+ minBlockSizeSamples,
+ maxBlockSizeSamples,
+ minFrameSize,
+ maxFrameSize,
+ sampleRate,
+ channels,
+ bitsPerSample,
+ totalSamples,
+ /* seekTable= */ null,
+ buildMetadata(vorbisComments, pictureFrames));
+ }
+
+ private FlacStreamMetadata(
+ int minBlockSizeSamples,
+ int maxBlockSizeSamples,
+ int minFrameSize,
+ int maxFrameSize,
+ int sampleRate,
+ int channels,
+ int bitsPerSample,
+ long totalSamples,
+ @Nullable SeekTable seekTable,
+ @Nullable Metadata metadata) {
+ this.minBlockSizeSamples = minBlockSizeSamples;
+ this.maxBlockSizeSamples = maxBlockSizeSamples;
+ this.minFrameSize = minFrameSize;
+ this.maxFrameSize = maxFrameSize;
+ this.sampleRate = sampleRate;
+ this.sampleRateLookupKey = getSampleRateLookupKey(sampleRate);
+ this.channels = channels;
+ this.bitsPerSample = bitsPerSample;
+ this.bitsPerSampleLookupKey = getBitsPerSampleLookupKey(bitsPerSample);
+ this.totalSamples = totalSamples;
+ this.seekTable = seekTable;
+ this.metadata = metadata;
+ }
+
+ /** Returns the maximum size for a decoded frame from the FLAC stream. */
+ public int getMaxDecodedFrameSize() {
+ return maxBlockSizeSamples * channels * (bitsPerSample / 8);
+ }
+
+ /** Returns the bit-rate of the FLAC stream. */
+ public int getBitRate() {
+ return bitsPerSample * sampleRate * channels;
+ }
+
+ /**
+ * Returns the duration of the FLAC stream in microseconds, or {@link C#TIME_UNSET} if the total
+ * number of samples if unknown.
+ */
+ public long getDurationUs() {
+ return totalSamples == 0 ? C.TIME_UNSET : totalSamples * C.MICROS_PER_SECOND / sampleRate;
+ }
+
+ /**
+ * Returns the sample number of the sample at a given time.
+ *
+ * @param timeUs Time position in microseconds in the FLAC stream.
+ * @return The sample number corresponding to the time position.
+ */
+ public long getSampleNumber(long timeUs) {
+ long sampleNumber = (timeUs * sampleRate) / C.MICROS_PER_SECOND;
+ return Util.constrainValue(sampleNumber, /* min= */ 0, totalSamples - 1);
+ }
+
+ /** Returns the approximate number of bytes per frame for the current FLAC stream. */
+ public long getApproxBytesPerFrame() {
+ long approxBytesPerFrame;
+ if (maxFrameSize > 0) {
+ approxBytesPerFrame = ((long) maxFrameSize + minFrameSize) / 2 + 1;
+ } else {
+ // Uses the stream's block-size if it's a known fixed block-size stream, otherwise uses the
+ // default value for FLAC block-size, which is 4096.
+ long blockSizeSamples =
+ (minBlockSizeSamples == maxBlockSizeSamples && minBlockSizeSamples > 0)
+ ? minBlockSizeSamples
+ : 4096;
+ approxBytesPerFrame = (blockSizeSamples * channels * bitsPerSample) / 8 + 64;
+ }
+ return approxBytesPerFrame;
+ }
+
+ /**
+ * Returns a {@link Format} extracted from the FLAC stream metadata.
+ *
+ * <p>{@code streamMarkerAndInfoBlock} is updated to set the bit corresponding to the stream info
+ * last metadata block flag to true.
+ *
+ * @param streamMarkerAndInfoBlock An array containing the FLAC stream marker followed by the
+ * stream info block.
+ * @param id3Metadata The ID3 metadata of the stream, or {@code null} if there is no such data.
+ * @return The extracted {@link Format}.
+ */
+ public Format getFormat(byte[] streamMarkerAndInfoBlock, @Nullable Metadata id3Metadata) {
+ // Set the last metadata block flag, ignore the other blocks.
+ streamMarkerAndInfoBlock[4] = (byte) 0x80;
+ int maxInputSize = maxFrameSize > 0 ? maxFrameSize : Format.NO_VALUE;
+ @Nullable Metadata metadataWithId3 = getMetadataCopyWithAppendedEntriesFrom(id3Metadata);
+
+ return Format.createAudioSampleFormat(
+ /* id= */ null,
+ MimeTypes.AUDIO_FLAC,
+ /* codecs= */ null,
+ getBitRate(),
+ maxInputSize,
+ channels,
+ sampleRate,
+ /* pcmEncoding= */ Format.NO_VALUE,
+ /* encoderDelay= */ 0,
+ /* encoderPadding= */ 0,
+ /* initializationData= */ Collections.singletonList(streamMarkerAndInfoBlock),
+ /* drmInitData= */ null,
+ /* selectionFlags= */ 0,
+ /* language= */ null,
+ metadataWithId3);
+ }
+
+ /** Returns a copy of the content metadata with entries from {@code other} appended. */
+ @Nullable
+ public Metadata getMetadataCopyWithAppendedEntriesFrom(@Nullable Metadata other) {
+ return metadata == null ? other : metadata.copyWithAppendedEntriesFrom(other);
+ }
+
+ /** Returns a copy of {@code this} with the seek table replaced by the one given. */
+ public FlacStreamMetadata copyWithSeekTable(@Nullable SeekTable seekTable) {
+ return new FlacStreamMetadata(
+ minBlockSizeSamples,
+ maxBlockSizeSamples,
+ minFrameSize,
+ maxFrameSize,
+ sampleRate,
+ channels,
+ bitsPerSample,
+ totalSamples,
+ seekTable,
+ metadata);
+ }
+
+ /** Returns a copy of {@code this} with the given Vorbis comments added to the metadata. */
+ public FlacStreamMetadata copyWithVorbisComments(List<String> vorbisComments) {
+ @Nullable
+ Metadata appendedMetadata =
+ getMetadataCopyWithAppendedEntriesFrom(
+ buildMetadata(vorbisComments, Collections.emptyList()));
+ return new FlacStreamMetadata(
+ minBlockSizeSamples,
+ maxBlockSizeSamples,
+ minFrameSize,
+ maxFrameSize,
+ sampleRate,
+ channels,
+ bitsPerSample,
+ totalSamples,
+ seekTable,
+ appendedMetadata);
+ }
+
+ /** Returns a copy of {@code this} with the given picture frames added to the metadata. */
+ public FlacStreamMetadata copyWithPictureFrames(List<PictureFrame> pictureFrames) {
+ @Nullable
+ Metadata appendedMetadata =
+ getMetadataCopyWithAppendedEntriesFrom(
+ buildMetadata(Collections.emptyList(), pictureFrames));
+ return new FlacStreamMetadata(
+ minBlockSizeSamples,
+ maxBlockSizeSamples,
+ minFrameSize,
+ maxFrameSize,
+ sampleRate,
+ channels,
+ bitsPerSample,
+ totalSamples,
+ seekTable,
+ appendedMetadata);
+ }
+
+ private static int getSampleRateLookupKey(int sampleRate) {
+ switch (sampleRate) {
+ case 88200:
+ return 1;
+ case 176400:
+ return 2;
+ case 192000:
+ return 3;
+ case 8000:
+ return 4;
+ case 16000:
+ return 5;
+ case 22050:
+ return 6;
+ case 24000:
+ return 7;
+ case 32000:
+ return 8;
+ case 44100:
+ return 9;
+ case 48000:
+ return 10;
+ case 96000:
+ return 11;
+ default:
+ return NOT_IN_LOOKUP_TABLE;
+ }
+ }
+
+ private static int getBitsPerSampleLookupKey(int bitsPerSample) {
+ switch (bitsPerSample) {
+ case 8:
+ return 1;
+ case 12:
+ return 2;
+ case 16:
+ return 4;
+ case 20:
+ return 5;
+ case 24:
+ return 6;
+ default:
+ return NOT_IN_LOOKUP_TABLE;
+ }
+ }
+
+ @Nullable
+ private static Metadata buildMetadata(
+ List<String> vorbisComments, List<PictureFrame> pictureFrames) {
+ if (vorbisComments.isEmpty() && pictureFrames.isEmpty()) {
+ return null;
+ }
+
+ ArrayList<Metadata.Entry> metadataEntries = new ArrayList<>();
+ for (int i = 0; i < vorbisComments.size(); i++) {
+ String vorbisComment = vorbisComments.get(i);
+ String[] keyAndValue = Util.splitAtFirst(vorbisComment, SEPARATOR);
+ if (keyAndValue.length != 2) {
+ Log.w(TAG, "Failed to parse Vorbis comment: " + vorbisComment);
+ } else {
+ VorbisComment entry = new VorbisComment(keyAndValue[0], keyAndValue[1]);
+ metadataEntries.add(entry);
+ }
+ }
+ metadataEntries.addAll(pictureFrames);
+
+ return metadataEntries.isEmpty() ? null : new Metadata(metadataEntries);
+ }
+}