1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.android.exoplayer2.extractor;
17 
18 import androidx.annotation.Nullable;
19 import com.google.android.exoplayer2.C;
20 import com.google.android.exoplayer2.Format;
21 import com.google.android.exoplayer2.metadata.Metadata;
22 import com.google.android.exoplayer2.metadata.flac.PictureFrame;
23 import com.google.android.exoplayer2.metadata.flac.VorbisComment;
24 import com.google.android.exoplayer2.util.Log;
25 import com.google.android.exoplayer2.util.MimeTypes;
26 import com.google.android.exoplayer2.util.ParsableBitArray;
27 import com.google.android.exoplayer2.util.Util;
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.List;
31 
32 /**
33  * Holder for FLAC metadata.
34  *
35  * @see <a href="https://xiph.org/flac/format.html#metadata_block_streaminfo">FLAC format
36  *     METADATA_BLOCK_STREAMINFO</a>
37  * @see <a href="https://xiph.org/flac/format.html#metadata_block_seektable">FLAC format
38  *     METADATA_BLOCK_SEEKTABLE</a>
39  * @see <a href="https://xiph.org/flac/format.html#metadata_block_vorbis_comment">FLAC format
40  *     METADATA_BLOCK_VORBIS_COMMENT</a>
41  * @see <a href="https://xiph.org/flac/format.html#metadata_block_picture">FLAC format
42  *     METADATA_BLOCK_PICTURE</a>
43  */
44 public final class FlacStreamMetadata {
45 
46   /** A FLAC seek table. */
47   public static class SeekTable {
48     /** Seek points sample numbers. */
49     public final long[] pointSampleNumbers;
50     /** Seek points byte offsets from the first frame. */
51     public final long[] pointOffsets;
52 
SeekTable(long[] pointSampleNumbers, long[] pointOffsets)53     public SeekTable(long[] pointSampleNumbers, long[] pointOffsets) {
54       this.pointSampleNumbers = pointSampleNumbers;
55       this.pointOffsets = pointOffsets;
56     }
57   }
58 
59   private static final String TAG = "FlacStreamMetadata";
60 
61   /** Indicates that a value is not in the corresponding lookup table. */
62   public static final int NOT_IN_LOOKUP_TABLE = -1;
63   /** Separator between the field name of a Vorbis comment and the corresponding value. */
64   private static final String SEPARATOR = "=";
65 
66   /** Minimum number of samples per block. */
67   public final int minBlockSizeSamples;
68   /** Maximum number of samples per block. */
69   public final int maxBlockSizeSamples;
70   /** Minimum frame size in bytes, or 0 if the value is unknown. */
71   public final int minFrameSize;
72   /** Maximum frame size in bytes, or 0 if the value is unknown. */
73   public final int maxFrameSize;
74   /** Sample rate in Hertz. */
75   public final int sampleRate;
76   /**
77    * Lookup key corresponding to the stream sample rate, or {@link #NOT_IN_LOOKUP_TABLE} if it is
78    * not in the lookup table.
79    *
80    * <p>This key is used to indicate the sample rate in the frame header for the most common values.
81    *
82    * <p>The sample rate lookup table is described in https://xiph.org/flac/format.html#frame_header.
83    */
84   public final int sampleRateLookupKey;
85   /** Number of audio channels. */
86   public final int channels;
87   /** Number of bits per sample. */
88   public final int bitsPerSample;
89   /**
90    * Lookup key corresponding to the number of bits per sample of the stream, or {@link
91    * #NOT_IN_LOOKUP_TABLE} if it is not in the lookup table.
92    *
93    * <p>This key is used to indicate the number of bits per sample in the frame header for the most
94    * common values.
95    *
96    * <p>The sample size lookup table is described in https://xiph.org/flac/format.html#frame_header.
97    */
98   public final int bitsPerSampleLookupKey;
99   /** Total number of samples, or 0 if the value is unknown. */
100   public final long totalSamples;
101   /** Seek table, or {@code null} if it is not provided. */
102   @Nullable public final SeekTable seekTable;
103   /** Content metadata, or {@code null} if it is not provided. */
104   @Nullable private final Metadata metadata;
105 
106   /**
107    * Parses binary FLAC stream info metadata.
108    *
109    * @param data An array containing binary FLAC stream info block.
110    * @param offset The offset of the stream info block in {@code data}, excluding the header (i.e.
111    *     the offset points to the first byte of the minimum block size).
112    */
FlacStreamMetadata(byte[] data, int offset)113   public FlacStreamMetadata(byte[] data, int offset) {
114     ParsableBitArray scratch = new ParsableBitArray(data);
115     scratch.setPosition(offset * 8);
116     minBlockSizeSamples = scratch.readBits(16);
117     maxBlockSizeSamples = scratch.readBits(16);
118     minFrameSize = scratch.readBits(24);
119     maxFrameSize = scratch.readBits(24);
120     sampleRate = scratch.readBits(20);
121     sampleRateLookupKey = getSampleRateLookupKey(sampleRate);
122     channels = scratch.readBits(3) + 1;
123     bitsPerSample = scratch.readBits(5) + 1;
124     bitsPerSampleLookupKey = getBitsPerSampleLookupKey(bitsPerSample);
125     totalSamples = scratch.readBitsToLong(36);
126     seekTable = null;
127     metadata = null;
128   }
129 
130   // Used in native code.
FlacStreamMetadata( int minBlockSizeSamples, int maxBlockSizeSamples, int minFrameSize, int maxFrameSize, int sampleRate, int channels, int bitsPerSample, long totalSamples, ArrayList<String> vorbisComments, ArrayList<PictureFrame> pictureFrames)131   public FlacStreamMetadata(
132       int minBlockSizeSamples,
133       int maxBlockSizeSamples,
134       int minFrameSize,
135       int maxFrameSize,
136       int sampleRate,
137       int channels,
138       int bitsPerSample,
139       long totalSamples,
140       ArrayList<String> vorbisComments,
141       ArrayList<PictureFrame> pictureFrames) {
142     this(
143         minBlockSizeSamples,
144         maxBlockSizeSamples,
145         minFrameSize,
146         maxFrameSize,
147         sampleRate,
148         channels,
149         bitsPerSample,
150         totalSamples,
151         /* seekTable= */ null,
152         buildMetadata(vorbisComments, pictureFrames));
153   }
154 
FlacStreamMetadata( int minBlockSizeSamples, int maxBlockSizeSamples, int minFrameSize, int maxFrameSize, int sampleRate, int channels, int bitsPerSample, long totalSamples, @Nullable SeekTable seekTable, @Nullable Metadata metadata)155   private FlacStreamMetadata(
156       int minBlockSizeSamples,
157       int maxBlockSizeSamples,
158       int minFrameSize,
159       int maxFrameSize,
160       int sampleRate,
161       int channels,
162       int bitsPerSample,
163       long totalSamples,
164       @Nullable SeekTable seekTable,
165       @Nullable Metadata metadata) {
166     this.minBlockSizeSamples = minBlockSizeSamples;
167     this.maxBlockSizeSamples = maxBlockSizeSamples;
168     this.minFrameSize = minFrameSize;
169     this.maxFrameSize = maxFrameSize;
170     this.sampleRate = sampleRate;
171     this.sampleRateLookupKey = getSampleRateLookupKey(sampleRate);
172     this.channels = channels;
173     this.bitsPerSample = bitsPerSample;
174     this.bitsPerSampleLookupKey = getBitsPerSampleLookupKey(bitsPerSample);
175     this.totalSamples = totalSamples;
176     this.seekTable = seekTable;
177     this.metadata = metadata;
178   }
179 
180   /** Returns the maximum size for a decoded frame from the FLAC stream. */
getMaxDecodedFrameSize()181   public int getMaxDecodedFrameSize() {
182     return maxBlockSizeSamples * channels * (bitsPerSample / 8);
183   }
184 
185   /** Returns the bitrate of the stream after it's decoded into PCM. */
getDecodedBitrate()186   public int getDecodedBitrate() {
187     return bitsPerSample * sampleRate * channels;
188   }
189 
190   /**
191    * Returns the duration of the FLAC stream in microseconds, or {@link C#TIME_UNSET} if the total
192    * number of samples if unknown.
193    */
getDurationUs()194   public long getDurationUs() {
195     return totalSamples == 0 ? C.TIME_UNSET : totalSamples * C.MICROS_PER_SECOND / sampleRate;
196   }
197 
198   /**
199    * Returns the sample number of the sample at a given time.
200    *
201    * @param timeUs Time position in microseconds in the FLAC stream.
202    * @return The sample number corresponding to the time position.
203    */
getSampleNumber(long timeUs)204   public long getSampleNumber(long timeUs) {
205     long sampleNumber = (timeUs * sampleRate) / C.MICROS_PER_SECOND;
206     return Util.constrainValue(sampleNumber, /* min= */ 0, totalSamples - 1);
207   }
208 
209   /** Returns the approximate number of bytes per frame for the current FLAC stream. */
getApproxBytesPerFrame()210   public long getApproxBytesPerFrame() {
211     long approxBytesPerFrame;
212     if (maxFrameSize > 0) {
213       approxBytesPerFrame = ((long) maxFrameSize + minFrameSize) / 2 + 1;
214     } else {
215       // Uses the stream's block-size if it's a known fixed block-size stream, otherwise uses the
216       // default value for FLAC block-size, which is 4096.
217       long blockSizeSamples =
218           (minBlockSizeSamples == maxBlockSizeSamples && minBlockSizeSamples > 0)
219               ? minBlockSizeSamples
220               : 4096;
221       approxBytesPerFrame = (blockSizeSamples * channels * bitsPerSample) / 8 + 64;
222     }
223     return approxBytesPerFrame;
224   }
225 
226   /**
227    * Returns a {@link Format} extracted from the FLAC stream metadata.
228    *
229    * <p>{@code streamMarkerAndInfoBlock} is updated to set the bit corresponding to the stream info
230    * last metadata block flag to true.
231    *
232    * @param streamMarkerAndInfoBlock An array containing the FLAC stream marker followed by the
233    *     stream info block.
234    * @param id3Metadata The ID3 metadata of the stream, or {@code null} if there is no such data.
235    * @return The extracted {@link Format}.
236    */
getFormat(byte[] streamMarkerAndInfoBlock, @Nullable Metadata id3Metadata)237   public Format getFormat(byte[] streamMarkerAndInfoBlock, @Nullable Metadata id3Metadata) {
238     // Set the last metadata block flag, ignore the other blocks.
239     streamMarkerAndInfoBlock[4] = (byte) 0x80;
240     int maxInputSize = maxFrameSize > 0 ? maxFrameSize : Format.NO_VALUE;
241     @Nullable Metadata metadataWithId3 = getMetadataCopyWithAppendedEntriesFrom(id3Metadata);
242     return new Format.Builder()
243         .setSampleMimeType(MimeTypes.AUDIO_FLAC)
244         .setMaxInputSize(maxInputSize)
245         .setChannelCount(channels)
246         .setSampleRate(sampleRate)
247         .setInitializationData(Collections.singletonList(streamMarkerAndInfoBlock))
248         .setMetadata(metadataWithId3)
249         .build();
250   }
251 
252   /** Returns a copy of the content metadata with entries from {@code other} appended. */
253   @Nullable
getMetadataCopyWithAppendedEntriesFrom(@ullable Metadata other)254   public Metadata getMetadataCopyWithAppendedEntriesFrom(@Nullable Metadata other) {
255     return metadata == null ? other : metadata.copyWithAppendedEntriesFrom(other);
256   }
257 
258   /** Returns a copy of {@code this} with the seek table replaced by the one given. */
copyWithSeekTable(@ullable SeekTable seekTable)259   public FlacStreamMetadata copyWithSeekTable(@Nullable SeekTable seekTable) {
260     return new FlacStreamMetadata(
261         minBlockSizeSamples,
262         maxBlockSizeSamples,
263         minFrameSize,
264         maxFrameSize,
265         sampleRate,
266         channels,
267         bitsPerSample,
268         totalSamples,
269         seekTable,
270         metadata);
271   }
272 
273   /** Returns a copy of {@code this} with the given Vorbis comments added to the metadata. */
copyWithVorbisComments(List<String> vorbisComments)274   public FlacStreamMetadata copyWithVorbisComments(List<String> vorbisComments) {
275     @Nullable
276     Metadata appendedMetadata =
277         getMetadataCopyWithAppendedEntriesFrom(
278             buildMetadata(vorbisComments, Collections.emptyList()));
279     return new FlacStreamMetadata(
280         minBlockSizeSamples,
281         maxBlockSizeSamples,
282         minFrameSize,
283         maxFrameSize,
284         sampleRate,
285         channels,
286         bitsPerSample,
287         totalSamples,
288         seekTable,
289         appendedMetadata);
290   }
291 
292   /** Returns a copy of {@code this} with the given picture frames added to the metadata. */
copyWithPictureFrames(List<PictureFrame> pictureFrames)293   public FlacStreamMetadata copyWithPictureFrames(List<PictureFrame> pictureFrames) {
294     @Nullable
295     Metadata appendedMetadata =
296         getMetadataCopyWithAppendedEntriesFrom(
297             buildMetadata(Collections.emptyList(), pictureFrames));
298     return new FlacStreamMetadata(
299         minBlockSizeSamples,
300         maxBlockSizeSamples,
301         minFrameSize,
302         maxFrameSize,
303         sampleRate,
304         channels,
305         bitsPerSample,
306         totalSamples,
307         seekTable,
308         appendedMetadata);
309   }
310 
getSampleRateLookupKey(int sampleRate)311   private static int getSampleRateLookupKey(int sampleRate) {
312     switch (sampleRate) {
313       case 88200:
314         return 1;
315       case 176400:
316         return 2;
317       case 192000:
318         return 3;
319       case 8000:
320         return 4;
321       case 16000:
322         return 5;
323       case 22050:
324         return 6;
325       case 24000:
326         return 7;
327       case 32000:
328         return 8;
329       case 44100:
330         return 9;
331       case 48000:
332         return 10;
333       case 96000:
334         return 11;
335       default:
336         return NOT_IN_LOOKUP_TABLE;
337     }
338   }
339 
getBitsPerSampleLookupKey(int bitsPerSample)340   private static int getBitsPerSampleLookupKey(int bitsPerSample) {
341     switch (bitsPerSample) {
342       case 8:
343         return 1;
344       case 12:
345         return 2;
346       case 16:
347         return 4;
348       case 20:
349         return 5;
350       case 24:
351         return 6;
352       default:
353         return NOT_IN_LOOKUP_TABLE;
354     }
355   }
356 
357   @Nullable
buildMetadata( List<String> vorbisComments, List<PictureFrame> pictureFrames)358   private static Metadata buildMetadata(
359       List<String> vorbisComments, List<PictureFrame> pictureFrames) {
360     if (vorbisComments.isEmpty() && pictureFrames.isEmpty()) {
361       return null;
362     }
363 
364     ArrayList<Metadata.Entry> metadataEntries = new ArrayList<>();
365     for (int i = 0; i < vorbisComments.size(); i++) {
366       String vorbisComment = vorbisComments.get(i);
367       String[] keyAndValue = Util.splitAtFirst(vorbisComment, SEPARATOR);
368       if (keyAndValue.length != 2) {
369         Log.w(TAG, "Failed to parse Vorbis comment: " + vorbisComment);
370       } else {
371         VorbisComment entry = new VorbisComment(keyAndValue[0], keyAndValue[1]);
372         metadataEntries.add(entry);
373       }
374     }
375     metadataEntries.addAll(pictureFrames);
376 
377     return metadataEntries.isEmpty() ? null : new Metadata(metadataEntries);
378   }
379 }
380