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.C;
19 import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.ExtractorInput;
20 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Assertions;
21 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.ParsableByteArray;
22 import java.io.IOException;
23 import java.util.Arrays;
24 
25 /**
26  * OGG packet class.
27  */
28 /* package */ final class OggPacket {
29 
30   private final OggPageHeader pageHeader = new OggPageHeader();
31   private final ParsableByteArray packetArray = new ParsableByteArray(
32       new byte[OggPageHeader.MAX_PAGE_PAYLOAD], 0);
33 
34   private int currentSegmentIndex = C.INDEX_UNSET;
35   private int segmentCount;
36   private boolean populated;
37 
38   /**
39    * Resets this reader.
40    */
reset()41   public void reset() {
42     pageHeader.reset();
43     packetArray.reset();
44     currentSegmentIndex = C.INDEX_UNSET;
45     populated = false;
46   }
47 
48   /**
49    * Reads the next packet of the ogg stream. In case of an {@code IOException} the caller must make
50    * sure to pass the same instance of {@code ParsableByteArray} to this method again so this reader
51    * can resume properly from an error while reading a continued packet spanned across multiple
52    * pages.
53    *
54    * @param input The {@link ExtractorInput} to read data from.
55    * @return {@code true} if the read was successful. The read fails if the end of the input is
56    *     encountered without reading data.
57    * @throws IOException If reading from the input fails.
58    * @throws InterruptedException If the thread is interrupted.
59    */
populate(ExtractorInput input)60   public boolean populate(ExtractorInput input) throws IOException, InterruptedException {
61     Assertions.checkState(input != null);
62 
63     if (populated) {
64       populated = false;
65       packetArray.reset();
66     }
67 
68     while (!populated) {
69       if (currentSegmentIndex < 0) {
70         // We're at the start of a page.
71         if (!pageHeader.populate(input, true)) {
72           return false;
73         }
74         int segmentIndex = 0;
75         int bytesToSkip = pageHeader.headerSize;
76         if ((pageHeader.type & 0x01) == 0x01 && packetArray.limit() == 0) {
77           // After seeking, the first packet may be the remainder
78           // part of a continued packet which has to be discarded.
79           bytesToSkip += calculatePacketSize(segmentIndex);
80           segmentIndex += segmentCount;
81         }
82         input.skipFully(bytesToSkip);
83         currentSegmentIndex = segmentIndex;
84       }
85 
86       int size = calculatePacketSize(currentSegmentIndex);
87       int segmentIndex = currentSegmentIndex + segmentCount;
88       if (size > 0) {
89         if (packetArray.capacity() < packetArray.limit() + size) {
90           packetArray.data = Arrays.copyOf(packetArray.data, packetArray.limit() + size);
91         }
92         input.readFully(packetArray.data, packetArray.limit(), size);
93         packetArray.setLimit(packetArray.limit() + size);
94         populated = pageHeader.laces[segmentIndex - 1] != 255;
95       }
96       // Advance now since we are sure reading didn't throw an exception.
97       currentSegmentIndex = segmentIndex == pageHeader.pageSegmentCount ? C.INDEX_UNSET
98           : segmentIndex;
99     }
100     return true;
101   }
102 
103   /**
104    * An OGG Packet may span multiple pages. Returns the {@link OggPageHeader} of the last page read,
105    * or an empty header if the packet has yet to be populated.
106    *
107    * <p>Note that the returned {@link OggPageHeader} is mutable and may be updated during subsequent
108    * calls to {@link #populate(ExtractorInput)}.
109    *
110    * @return the {@code PageHeader} of the last page read or an empty header if the packet has yet
111    *     to be populated.
112    */
getPageHeader()113   public OggPageHeader getPageHeader() {
114     return pageHeader;
115   }
116 
117   /**
118    * Returns a {@link ParsableByteArray} containing the packet's payload.
119    */
getPayload()120   public ParsableByteArray getPayload() {
121     return packetArray;
122   }
123 
124   /**
125    * Trims the packet data array.
126    */
trimPayload()127   public void trimPayload() {
128     if (packetArray.data.length == OggPageHeader.MAX_PAGE_PAYLOAD) {
129       return;
130     }
131     packetArray.data = Arrays.copyOf(packetArray.data, Math.max(OggPageHeader.MAX_PAGE_PAYLOAD,
132         packetArray.limit()));
133   }
134 
135   /**
136    * Calculates the size of the packet starting from {@code startSegmentIndex}.
137    *
138    * @param startSegmentIndex the index of the first segment of the packet.
139    * @return Size of the packet.
140    */
calculatePacketSize(int startSegmentIndex)141   private int calculatePacketSize(int startSegmentIndex) {
142     segmentCount = 0;
143     int size = 0;
144     while (startSegmentIndex + segmentCount < pageHeader.pageSegmentCount) {
145       int segmentLength = pageHeader.laces[startSegmentIndex + segmentCount++];
146       size += segmentLength;
147       if (segmentLength != 255) {
148         // packets end at first lace < 255
149         break;
150       }
151     }
152     return size;
153   }
154 
155 }
156