blob: d380fa47c710c08cc3c4d35a0966174438aa83c5 [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.mkv;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.io.IOException;
/**
* Utility class that peeks from the input stream in order to determine whether it appears to be
* compatible input for this extractor.
*/
/* package */ final class Sniffer {
/**
* The number of bytes to search for a valid header in {@link #sniff(ExtractorInput)}.
*/
private static final int SEARCH_LENGTH = 1024;
private static final int ID_EBML = 0x1A45DFA3;
private final ParsableByteArray scratch;
private int peekLength;
public Sniffer() {
scratch = new ParsableByteArray(8);
}
/** @see com.google.android.exoplayer2.extractor.Extractor#sniff(ExtractorInput) */
public boolean sniff(ExtractorInput input) throws IOException {
long inputLength = input.getLength();
int bytesToSearch = (int) (inputLength == C.LENGTH_UNSET || inputLength > SEARCH_LENGTH
? SEARCH_LENGTH : inputLength);
// Find four bytes equal to ID_EBML near the start of the input.
input.peekFully(scratch.data, 0, 4);
long tag = scratch.readUnsignedInt();
peekLength = 4;
while (tag != ID_EBML) {
if (++peekLength == bytesToSearch) {
return false;
}
input.peekFully(scratch.data, 0, 1);
tag = (tag << 8) & 0xFFFFFF00;
tag |= scratch.data[0] & 0xFF;
}
// Read the size of the EBML header and make sure it is within the stream.
long headerSize = readUint(input);
long headerStart = peekLength;
if (headerSize == Long.MIN_VALUE
|| (inputLength != C.LENGTH_UNSET && headerStart + headerSize >= inputLength)) {
return false;
}
// Read the payload elements in the EBML header.
while (peekLength < headerStart + headerSize) {
long id = readUint(input);
if (id == Long.MIN_VALUE) {
return false;
}
long size = readUint(input);
if (size < 0 || size > Integer.MAX_VALUE) {
return false;
}
if (size != 0) {
int sizeInt = (int) size;
input.advancePeekPosition(sizeInt);
peekLength += sizeInt;
}
}
return peekLength == headerStart + headerSize;
}
/** Peeks a variable-length unsigned EBML integer from the input. */
private long readUint(ExtractorInput input) throws IOException {
input.peekFully(scratch.data, 0, 1);
int value = scratch.data[0] & 0xFF;
if (value == 0) {
return Long.MIN_VALUE;
}
int mask = 0x80;
int length = 0;
while ((value & mask) == 0) {
mask >>= 1;
length++;
}
value &= ~mask;
input.peekFully(scratch.data, 1, length);
for (int i = 0; i < length; i++) {
value <<= 8;
value += scratch.data[i + 1] & 0xFF;
}
peekLength += length + 1;
return value;
}
}