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 org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.ogg; 17 18 import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.ExtractorInput; 19 import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.FlacFrameReader; 20 import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.FlacMetadataReader; 21 import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.FlacSeekTableSeekMap; 22 import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.SeekMap; 23 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Assertions; 24 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.FlacConstants; 25 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.FlacStreamMetadata; 26 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.ParsableByteArray; 27 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util; 28 import java.io.IOException; 29 import java.util.Arrays; 30 31 /** 32 * {@link StreamReader} to extract Flac data out of Ogg byte stream. 33 */ 34 /* package */ final class FlacReader extends StreamReader { 35 36 private static final byte AUDIO_PACKET_TYPE = (byte) 0xFF; 37 38 private static final int FRAME_HEADER_SAMPLE_NUMBER_OFFSET = 4; 39 40 private FlacStreamMetadata streamMetadata; 41 private FlacOggSeeker flacOggSeeker; 42 verifyBitstreamType(ParsableByteArray data)43 public static boolean verifyBitstreamType(ParsableByteArray data) { 44 return data.bytesLeft() >= 5 && data.readUnsignedByte() == 0x7F && // packet type 45 data.readUnsignedInt() == 0x464C4143; // ASCII signature "FLAC" 46 } 47 48 @Override reset(boolean headerData)49 protected void reset(boolean headerData) { 50 super.reset(headerData); 51 if (headerData) { 52 streamMetadata = null; 53 flacOggSeeker = null; 54 } 55 } 56 isAudioPacket(byte[] data)57 private static boolean isAudioPacket(byte[] data) { 58 return data[0] == AUDIO_PACKET_TYPE; 59 } 60 61 @Override preparePayload(ParsableByteArray packet)62 protected long preparePayload(ParsableByteArray packet) { 63 if (!isAudioPacket(packet.data)) { 64 return -1; 65 } 66 return getFlacFrameBlockSize(packet); 67 } 68 69 @Override readHeaders(ParsableByteArray packet, long position, SetupData setupData)70 protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) { 71 byte[] data = packet.data; 72 if (streamMetadata == null) { 73 streamMetadata = new FlacStreamMetadata(data, 17); 74 byte[] metadata = Arrays.copyOfRange(data, 9, packet.limit()); 75 setupData.format = streamMetadata.getFormat(metadata, /* id3Metadata= */ null); 76 } else if ((data[0] & 0x7F) == FlacConstants.METADATA_TYPE_SEEK_TABLE) { 77 flacOggSeeker = new FlacOggSeeker(); 78 FlacStreamMetadata.SeekTable seekTable = 79 FlacMetadataReader.readSeekTableMetadataBlock(packet); 80 streamMetadata = streamMetadata.copyWithSeekTable(seekTable); 81 } else if (isAudioPacket(data)) { 82 if (flacOggSeeker != null) { 83 flacOggSeeker.setFirstFrameOffset(position); 84 setupData.oggSeeker = flacOggSeeker; 85 } 86 return false; 87 } 88 return true; 89 } 90 getFlacFrameBlockSize(ParsableByteArray packet)91 private int getFlacFrameBlockSize(ParsableByteArray packet) { 92 int blockSizeKey = (packet.data[2] & 0xFF) >> 4; 93 if (blockSizeKey == 6 || blockSizeKey == 7) { 94 // Skip the sample number. 95 packet.skipBytes(FRAME_HEADER_SAMPLE_NUMBER_OFFSET); 96 packet.readUtf8EncodedLong(); 97 } 98 int result = FlacFrameReader.readFrameBlockSizeSamplesFromKey(packet, blockSizeKey); 99 packet.setPosition(0); 100 return result; 101 } 102 103 private class FlacOggSeeker implements OggSeeker { 104 105 private long firstFrameOffset; 106 private long pendingSeekGranule; 107 FlacOggSeeker()108 public FlacOggSeeker() { 109 firstFrameOffset = -1; 110 pendingSeekGranule = -1; 111 } 112 setFirstFrameOffset(long firstFrameOffset)113 public void setFirstFrameOffset(long firstFrameOffset) { 114 this.firstFrameOffset = firstFrameOffset; 115 } 116 117 @Override read(ExtractorInput input)118 public long read(ExtractorInput input) throws IOException, InterruptedException { 119 if (pendingSeekGranule >= 0) { 120 long result = -(pendingSeekGranule + 2); 121 pendingSeekGranule = -1; 122 return result; 123 } 124 return -1; 125 } 126 127 @Override startSeek(long targetGranule)128 public void startSeek(long targetGranule) { 129 Assertions.checkNotNull(streamMetadata.seekTable); 130 long[] seekPointGranules = streamMetadata.seekTable.pointSampleNumbers; 131 int index = Util.binarySearchFloor(seekPointGranules, targetGranule, true, true); 132 pendingSeekGranule = seekPointGranules[index]; 133 } 134 135 @Override createSeekMap()136 public SeekMap createSeekMap() { 137 Assertions.checkState(firstFrameOffset != -1); 138 return new FlacSeekTableSeekMap(streamMetadata, firstFrameOffset); 139 } 140 141 } 142 143 } 144