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