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