blob: b31d3769b224237f507c86799db7269ac825ace3 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.extractor.ogg;
import static com.google.android.exoplayer2.extractor.ExtractorUtil.readFullyQuietly;
import static com.google.android.exoplayer2.extractor.ExtractorUtil.skipFullyQuietly;
import static java.lang.Math.max;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.io.IOException;
import java.util.Arrays;
/** OGG packet class. */
/* package */ final class OggPacket {
private final OggPageHeader pageHeader = new OggPageHeader();
private final ParsableByteArray packetArray =
new ParsableByteArray(new byte[OggPageHeader.MAX_PAGE_PAYLOAD], 0);
private int currentSegmentIndex = C.INDEX_UNSET;
private int segmentCount;
private boolean populated;
/** Resets this reader. */
public void reset() {
pageHeader.reset();
packetArray.reset(/* limit= */ 0);
currentSegmentIndex = C.INDEX_UNSET;
populated = false;
}
/**
* Reads the next packet of the ogg stream. In case of an {@code IOException} the caller must make
* sure to pass the same instance of {@code ParsableByteArray} to this method again so this reader
* can resume properly from an error while reading a continued packet spanned across multiple
* pages.
*
* @param input The {@link ExtractorInput} to read data from.
* @return {@code true} if the read was successful. The read fails if the end of the input is
* encountered without reading the whole packet.
* @throws IOException If reading from the input fails.
*/
public boolean populate(ExtractorInput input) throws IOException {
Assertions.checkState(input != null);
if (populated) {
populated = false;
packetArray.reset(/* limit= */ 0);
}
while (!populated) {
if (currentSegmentIndex < 0) {
// We're at the start of a page.
if (!pageHeader.skipToNextPage(input) || !pageHeader.populate(input, /* quiet= */ true)) {
return false;
}
int segmentIndex = 0;
int bytesToSkip = pageHeader.headerSize;
if ((pageHeader.type & 0x01) == 0x01 && packetArray.limit() == 0) {
// After seeking, the first packet may be the remainder
// part of a continued packet which has to be discarded.
bytesToSkip += calculatePacketSize(segmentIndex);
segmentIndex += segmentCount;
}
if (!skipFullyQuietly(input, bytesToSkip)) {
return false;
}
currentSegmentIndex = segmentIndex;
}
int size = calculatePacketSize(currentSegmentIndex);
int segmentIndex = currentSegmentIndex + segmentCount;
if (size > 0) {
packetArray.ensureCapacity(packetArray.limit() + size);
if (!readFullyQuietly(input, packetArray.getData(), packetArray.limit(), size)) {
return false;
}
packetArray.setLimit(packetArray.limit() + size);
populated = pageHeader.laces[segmentIndex - 1] != 255;
}
// Advance now since we are sure reading didn't throw an exception.
currentSegmentIndex =
segmentIndex == pageHeader.pageSegmentCount ? C.INDEX_UNSET : segmentIndex;
}
return true;
}
/**
* An OGG Packet may span multiple pages. Returns the {@link OggPageHeader} of the last page read,
* or an empty header if the packet has yet to be populated.
*
* <p>Note that the returned {@link OggPageHeader} is mutable and may be updated during subsequent
* calls to {@link #populate(ExtractorInput)}.
*
* @return the {@code PageHeader} of the last page read or an empty header if the packet has yet
* to be populated.
*/
public OggPageHeader getPageHeader() {
return pageHeader;
}
/** Returns a {@link ParsableByteArray} containing the packet's payload. */
public ParsableByteArray getPayload() {
return packetArray;
}
/** Trims the packet data array. */
public void trimPayload() {
if (packetArray.getData().length == OggPageHeader.MAX_PAGE_PAYLOAD) {
return;
}
packetArray.reset(
Arrays.copyOf(
packetArray.getData(), max(OggPageHeader.MAX_PAGE_PAYLOAD, packetArray.limit())),
/* limit= */ packetArray.limit());
}
/**
* Calculates the size of the packet starting from {@code startSegmentIndex}.
*
* @param startSegmentIndex the index of the first segment of the packet.
* @return Size of the packet.
*/
private int calculatePacketSize(int startSegmentIndex) {
segmentCount = 0;
int size = 0;
while (startSegmentIndex + segmentCount < pageHeader.pageSegmentCount) {
int segmentLength = pageHeader.laces[startSegmentIndex + segmentCount++];
size += segmentLength;
if (segmentLength != 255) {
// packets end at first lace < 255
break;
}
}
return size;
}
}