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