diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java b/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java index 9f58528895..9be0c43b76 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java @@ -242,6 +242,8 @@ import java.util.Locale; + "&key=ik0", Util.TYPE_OTHER), new Sample("Google Play (MP3 Audio)", "http://storage.googleapis.com/exoplayer-test-media-0/play.mp3", Util.TYPE_OTHER), + new Sample("Google Play (Ogg/Vorbis Audio)", + "https://storage.googleapis.com/exoplayer-test-media-1/ogg/play.ogg", Util.TYPE_OTHER), new Sample("Google Glass (WebM Video with Vorbis Audio)", "http://demos.webmproject.org/exoplayer/glass_vp9_vorbis.webm", Util.TYPE_OTHER), new Sample("Big Buck Bunny (FLV Video)", diff --git a/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/OggReaderTest.java b/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/OggReaderTest.java new file mode 100644 index 0000000000..12e604d99b --- /dev/null +++ b/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/OggReaderTest.java @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2015 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.exoplayer.extractor.ogg; + +import com.google.android.exoplayer.util.ParsableByteArray; + +import android.util.Log; + +import junit.framework.TestCase; + +import java.io.IOException; + +/** + * Unit test for {@link OggReader} + */ +public final class OggReaderTest extends TestCase { + + private static final String TAG = "OggReaderTest"; + + private OggReader oggReader; + private RecordableOggExtractorInput extractorInput; + + @Override + public void setUp() throws Exception { + super.setUp(); + extractorInput = new RecordableOggExtractorInput(1024 * 64); + // we want the mocked ExtractorInput to throw errors often + extractorInput.doThrowExceptionsAtPeek(true); + extractorInput.doThrowExceptionsAtRead(true); + // create reader + oggReader = new OggReader(); + oggReader.reset(); + } + + public void testReadPacketUntilEOFIncludingAnEmptyPage() throws Exception { + // record first page with a single packet + extractorInput.recordOggHeader((byte) 0x02, 0, (byte) 0x01); + extractorInput.recordOggLaces(new byte[]{0x08}); + extractorInput.recordOggPacket(RecordableExtractorInput.getBytesGrowingValues(8)); + // record intermediate page with two packets + extractorInput.recordOggHeader((byte) 0x00, 16, (byte) 0x02); + extractorInput.recordOggLaces(new byte[]{(byte) 0xFF, 0x11}); + extractorInput.recordOggPacket(RecordableExtractorInput.getBytesGrowingValues(255 + 17)); + // empty page + extractorInput.recordOggHeader((byte) 0x00, 16, (byte) 0x00); + // record last page with two packets (256 and 271 bytes) + extractorInput.recordOggHeader((byte) 0x04, 128, (byte) 0x04); + extractorInput.recordOggLaces(new byte[]{(byte) 0xFF, 0x01, (byte) 0xff, 0x10}); + extractorInput.recordOggPacket(RecordableExtractorInput + .getBytesGrowingValues(255 + 1 + 255 + 16)); + + + // read first packet + final ParsableByteArray packetArray = new ParsableByteArray(new byte[255 * 255], 0); + readPacketUntilSuccess(packetArray); + // verify + assertEquals(8, packetArray.limit()); + assertTrue((oggReader.getPageHeader().type & 0x02) == 0x02); + assertFalse((oggReader.getPageHeader().type & 0x04) == 0x04); + assertEquals(0x02, oggReader.getPageHeader().type); + assertEquals(27 + 1, oggReader.getPageHeader().headerSize); + assertEquals(8, oggReader.getPageHeader().bodySize); + assertEquals(RecordableExtractorInput.STREAM_REVISION, oggReader.getPageHeader().revision); + assertEquals(1, oggReader.getPageHeader().pageSegmentCount); + assertEquals(1000, oggReader.getPageHeader().pageSequenceNumber); + assertEquals(4096, oggReader.getPageHeader().streamSerialNumber); + assertEquals(0, oggReader.getPageHeader().granulePosition); + for (int i = 0; i < 8; i++) { + assertEquals(i, packetArray.readUnsignedByte()); + } + + packetArray.reset(); + // read second packet + readPacketUntilSuccess(packetArray); + // verify + assertEquals(255 + 17, packetArray.limit()); + assertFalse((oggReader.getPageHeader().type & 0x02) == 0x02); + assertFalse((oggReader.getPageHeader().type & 0x04) == 0x04); + assertEquals(0, oggReader.getPageHeader().type); + assertEquals(27 + 2, oggReader.getPageHeader().headerSize); + assertEquals(255 + 17, oggReader.getPageHeader().bodySize); + assertEquals(2, oggReader.getPageHeader().pageSegmentCount); + assertEquals(1001, oggReader.getPageHeader().pageSequenceNumber); + assertEquals(16, oggReader.getPageHeader().granulePosition); + + packetArray.reset(); + // read next packet and skip empty page + readPacketUntilSuccess(packetArray); + // verify + assertEquals(255 + 1, packetArray.limit()); + assertFalse((oggReader.getPageHeader().type & 0x02) == 0x02); + assertTrue((oggReader.getPageHeader().type & 0x04) == 0x04); + assertEquals(4, oggReader.getPageHeader().type); + assertEquals(27 + 4, oggReader.getPageHeader().headerSize); + assertEquals(255 + 1 + 255 + 16, oggReader.getPageHeader().bodySize); + assertEquals(4, oggReader.getPageHeader().pageSegmentCount); + // page 1002 is empty, so current is 1003 + assertEquals(1003, oggReader.getPageHeader().pageSequenceNumber); + assertEquals(128, oggReader.getPageHeader().granulePosition); + + packetArray.reset(); + // read last packet + readPacketUntilSuccess(packetArray); + assertEquals(255 + 16, packetArray.limit()); + // EOF! + readEOFUntilSuccess(packetArray, 10); + } + + public void testReadPacketWithZeroSizeTerminator() throws Exception { + // record first page with a single packet + extractorInput.recordOggHeader((byte) 0x06, 0, (byte) 0x04); + extractorInput.recordOggLaces(new byte[]{(byte) 0xff, 0x00, 0x00, 0x08}); + extractorInput.recordOggPacket(RecordableExtractorInput.getBytesGrowingValues(255 + 8)); + + ParsableByteArray packetArray = new ParsableByteArray(new byte[255 * 255], 0); + readPacketUntilSuccess(packetArray); + assertEquals(255, packetArray.limit()); + + packetArray.reset(); + readPacketUntilSuccess(packetArray); + assertEquals(8, packetArray.limit()); + + readEOFUntilSuccess(packetArray, 10); + } + + public void testReadContinuedPacket() throws Exception { + // record first page with a packet continuing on the second page + extractorInput.recordOggHeader((byte) 0x02, 0, (byte) 0x02); + extractorInput.recordOggLaces(new byte[]{(byte) 0xFF, (byte) 0xFF}); + extractorInput.recordOggPacket(RecordableExtractorInput.getBytesGrowingValues(510)); + // record the continuing page + extractorInput.recordOggHeader((byte) 0x05, 10, (byte) 0x01); + extractorInput.recordOggLaces(new byte[]{0x08}); + extractorInput.recordOggPacket(RecordableExtractorInput.getBytesGrowingValues(8, (byte) 0x22)); + + // there is only one single packet across two pages + ParsableByteArray packetArray = new ParsableByteArray(new byte[255 * 255], 0); + readPacketUntilSuccess(packetArray); + + assertEquals(255 + 255 + 8, packetArray.limit()); + assertTrue((oggReader.getPageHeader().type & 0x04) == 0x04); + assertFalse((oggReader.getPageHeader().type & 0x02) == 0x02); + // we must be on the second page already + assertEquals(1001, oggReader.getPageHeader().pageSequenceNumber); + + // verify packet data + for (int i = 0; i < 255; i++) { + assertEquals(i, packetArray.readUnsignedByte()); + } + assertEquals(255, packetArray.getPosition()); + for (int i = 0; i < 255; i++) { + assertEquals(i, packetArray.readUnsignedByte()); + } + assertEquals(510, packetArray.getPosition()); + for (int i = 0; i < 8; i++) { + assertEquals(i + 0x22, packetArray.readUnsignedByte()); + } + assertEquals(0, packetArray.bytesLeft()); + // EOF! + readEOFUntilSuccess(packetArray, 10); + } + + // no one does this with vorbis buts it's supported + public void testReadContinuedPacketOverMoreThan2Pages() throws Exception { + // record first page with a packet continuing on the second page + extractorInput.recordOggHeader((byte) 0x02, 0, (byte) 0x02); + extractorInput.recordOggLaces(new byte[]{(byte) 0xFF, (byte) 0xFF}); + extractorInput.recordOggPacket(RecordableExtractorInput.getBytesGrowingValues(510)); + // record the first continuing page + extractorInput.recordOggHeader((byte) 0x01, 10, (byte) 0x01); + extractorInput.recordOggLaces(new byte[]{(byte) 0xFF}); + extractorInput.recordOggPacket(RecordableExtractorInput.getBytesGrowingValues(255)); + // record the second continuing page + extractorInput.recordOggHeader((byte) 0x01, 10, (byte) 0x01); + extractorInput.recordOggLaces(new byte[]{(byte) 0xFF}); + extractorInput.recordOggPacket(RecordableExtractorInput.getBytesGrowingValues(255)); + // record the third continuing page + extractorInput.recordOggHeader((byte) 0x05, 10, (byte) 0x01); + extractorInput.recordOggLaces(new byte[]{(byte) 0x08}); + extractorInput.recordOggPacket(RecordableExtractorInput.getBytesGrowingValues(8, (byte) 0x22)); + + // there is only one single packet across two pages + ParsableByteArray packetArray = new ParsableByteArray(new byte[255 * 255], 0); + readPacketUntilSuccess(packetArray); + + assertEquals(255 + 255 + 255 + 255 + 8, packetArray.limit()); + assertTrue((oggReader.getPageHeader().type & 0x04) == 0x04); + assertFalse((oggReader.getPageHeader().type & 0x02) == 0x02); + // we must be on the fourth page already + assertEquals(1003, oggReader.getPageHeader().pageSequenceNumber); + + // verify packet data + for (int i = 0; i < 255; i++) { + assertEquals(i, packetArray.readUnsignedByte()); + } + assertEquals(255, packetArray.getPosition()); + for (int i = 0; i < 255; i++) { + assertEquals(i, packetArray.readUnsignedByte()); + } + assertEquals(510, packetArray.getPosition()); + for (int i = 0; i < 255; i++) { + assertEquals(i, packetArray.readUnsignedByte()); + } + assertEquals(765, packetArray.getPosition()); + for (int i = 0; i < 255; i++) { + assertEquals(i, packetArray.readUnsignedByte()); + } + assertEquals(1020, packetArray.getPosition()); + for (int i = 0; i < 8; i++) { + assertEquals(i + 0x22, packetArray.readUnsignedByte()); + } + assertEquals(0, packetArray.bytesLeft()); + // EOF! + readEOFUntilSuccess(packetArray, 10); + } + + public void testReadExceptionThrownWhilePeekingHeader() throws Exception { + // record first page with two packets packet + extractorInput.recordOggHeader((byte) 0x02, 0, (byte) 0x02); + extractorInput.recordOggLaces(new byte[]{(byte) 0x01, (byte) 0x08}); + extractorInput.recordOggPacket(new byte[]{0x10}); + extractorInput.recordOggPacket(RecordableExtractorInput.getBytesGrowingValues(8)); + + // record next page + extractorInput.recordOggHeader((byte) 0x05, 10, (byte) 0x01); + extractorInput.recordOggLaces(new byte[]{0x08}); + extractorInput.recordOggPacket(RecordableExtractorInput.getBytesGrowingValues(8, (byte) 0x22)); + + ParsableByteArray packetArray = new ParsableByteArray(new byte[255 * 255], 0); + readPacketUntilSuccess(packetArray); + // verify packet data + assertEquals(1, packetArray.limit()); + assertEquals(0x10, packetArray.data[0]); + // verify header + assertTrue((oggReader.getPageHeader().type & 0x02) == 0x02); + assertFalse((oggReader.getPageHeader().type & 0x04) == 0x04); + assertEquals(27 + 2, oggReader.getPageHeader().headerSize); + assertEquals(9, oggReader.getPageHeader().bodySize); + assertEquals(2, oggReader.getPageHeader().pageSegmentCount); + assertEquals(1000, oggReader.getPageHeader().pageSequenceNumber); + assertEquals(0, oggReader.getPageHeader().granulePosition); + + packetArray.reset(); + readPacketUntilSuccess(packetArray); + } + + public void testReadNoZeroSizedPacketsAreReturned() throws Exception { + extractorInput.recordOggHeader((byte) 0x02, 0, (byte) 0x04); + extractorInput.recordOggLaces(new byte[]{(byte) 0x08, (byte) 0x00, (byte) 0x00, (byte) 0x08}); + extractorInput.recordOggPacket(new byte[]{0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10}); + extractorInput.recordOggPacket(new byte[]{0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20}); + + ParsableByteArray packetArray = new ParsableByteArray(new byte[1024], 0); + + readPacketUntilSuccess(packetArray); + assertEquals(8, packetArray.limit()); + assertEquals(0x10, packetArray.data[0]); + assertEquals(0x10, packetArray.data[7]); + + packetArray.reset(); + readPacketUntilSuccess(packetArray); + assertEquals(8, packetArray.limit()); + assertEquals(0x20, packetArray.data[0]); + assertEquals(0x20, packetArray.data[7]); + + readEOFUntilSuccess(packetArray, 10); + } + + public void testReadZeroSizedPacketsAtEndOfStream() throws Exception { + extractorInput.recordOggHeader((byte) 0x02, 0, (byte) 0x01); + extractorInput.recordOggLaces(new byte[]{(byte) 0x08}); + extractorInput.recordOggPacket(new byte[]{0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10}); + + extractorInput.recordOggHeader((byte) 0x04, 0, (byte) 0x03); + extractorInput.recordOggLaces(new byte[]{(byte) 0x08, (byte) 0x00, (byte) 0x00}); + extractorInput.recordOggPacket(new byte[]{0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10}); + + extractorInput.recordOggHeader((byte) 0x04, 0, (byte) 0x03); + extractorInput.recordOggLaces(new byte[]{(byte) 0x08, 0x00, 0x00}); + extractorInput.recordOggPacket(new byte[]{0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10}); + + ParsableByteArray packetArray = new ParsableByteArray(new byte[1024], 0); + + readPacketUntilSuccess(packetArray); + assertEquals(8, packetArray.limit()); + + packetArray.reset(); + readPacketUntilSuccess(packetArray); + assertEquals(8, packetArray.limit()); + + packetArray.reset(); + readPacketUntilSuccess(packetArray); + assertEquals(8, packetArray.limit()); + + packetArray.reset(); + readEOFUntilSuccess(packetArray, 10); + assertEquals(0, packetArray.limit()); + } + + private void readPacketUntilSuccess(ParsableByteArray packetArray) { + int exceptionCount = 0; + while (exceptionCount < 10) { + try { + assertTrue(oggReader.readPacket(extractorInput, packetArray)); + break; + } catch (IOException | InterruptedException e) { + exceptionCount++; + extractorInput.resetPeekPosition(); + } + } + + if (exceptionCount == 10) { + fail("maxException threshold reached"); + } + } + + private void readEOFUntilSuccess(ParsableByteArray packetArray, int maxExceptions) { + int exceptionCount = 0; + while (exceptionCount < maxExceptions) { + try { + assertFalse(oggReader.readPacket(extractorInput, packetArray)); + break; + } catch (IOException | InterruptedException e) { + exceptionCount++; + Log.e(TAG, e.getMessage(), e); + } + } + if (exceptionCount == maxExceptions) { + fail("maxException threshold reached"); + } + } + +} diff --git a/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/OggVorbisExtractorTest.java b/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/OggVorbisExtractorTest.java new file mode 100644 index 0000000000..cb07736aa0 --- /dev/null +++ b/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/OggVorbisExtractorTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2015 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.exoplayer.extractor.ogg; + +import com.google.android.exoplayer.util.ParsableByteArray; + +import android.util.Log; + +import junit.framework.TestCase; + +import java.io.IOException; + +/** + * Unit test for {@link OggVorbisExtractor}. + */ +public final class OggVorbisExtractorTest extends TestCase { + + private static final String TAG = "OggVorbisExtractorTest"; + + private OggVorbisExtractor extractor; + private RecordableOggExtractorInput extractorInput; + + @Override + public void setUp() throws Exception { + super.setUp(); + extractorInput = new RecordableOggExtractorInput(1024 * 64); + extractor = new OggVorbisExtractor(); + } + + public void testSniff() throws Exception { + extractorInput.recordOggHeader((byte) 0x02, 0, (byte) 0x02); + extractorInput.recordOggLaces(new byte[]{120, 120}); + assertTrue(extractor.sniff(extractorInput)); + } + + public void testSniffFails() throws Exception { + extractorInput.recordOggHeader((byte) 0x00, 0, (byte) 0); + assertFalse(extractor.sniff(extractorInput)); + } + + public void testAppendNumberOfSamples() throws Exception { + ParsableByteArray buffer = new ParsableByteArray(4); + buffer.setLimit(0); + OggVorbisExtractor.appendNumberOfSamples(buffer, 0x01234567); + assertEquals(4, buffer.limit()); + assertEquals(0x67, buffer.data[0]); + assertEquals(0x45, buffer.data[1]); + assertEquals(0x23, buffer.data[2]); + assertEquals(0x01, buffer.data[3]); + } + + public void testReadSetupHeadersWithIOExceptions() throws IOException, InterruptedException { + extractorInput.doThrowExceptionsAtRead(true); + extractorInput.doThrowExceptionsAtPeek(true); + + byte[] data = TestData.getVorbisHeaderPages(); + extractorInput.record(data); + + int exceptionCount = 0; + int maxExceptions = 20; + OggVorbisExtractor.VorbisSetup vorbisSetup; + while (exceptionCount < maxExceptions) { + try { + vorbisSetup = extractor.readSetupHeaders(extractorInput, + new ParsableByteArray(new byte[255 * 255], 0)); + + assertNotNull(vorbisSetup.idHeader); + assertNotNull(vorbisSetup.commentHeader); + assertNotNull(vorbisSetup.setupHeaderData); + assertNotNull(vorbisSetup.modes); + + assertEquals(45, vorbisSetup.commentHeader.length); + assertEquals(30, vorbisSetup.idHeader.data.length); + assertEquals(3597, vorbisSetup.setupHeaderData.length); + + assertEquals(-1, vorbisSetup.idHeader.bitrateMax); + assertEquals(-1, vorbisSetup.idHeader.bitrateMin); + assertEquals(66666, vorbisSetup.idHeader.bitrateNominal); + assertEquals(512, vorbisSetup.idHeader.blockSize0); + assertEquals(1024, vorbisSetup.idHeader.blockSize1); + assertEquals(2, vorbisSetup.idHeader.channels); + assertTrue(vorbisSetup.idHeader.framingFlag); + assertEquals(22050, vorbisSetup.idHeader.sampleRate); + assertEquals(0, vorbisSetup.idHeader.version); + + assertEquals("Xiph.Org libVorbis I 20030909", vorbisSetup.commentHeader.vendor); + assertEquals(1, vorbisSetup.iLogModes); + + assertEquals(data[data.length - 1], + vorbisSetup.setupHeaderData[vorbisSetup.setupHeaderData.length - 1]); + + assertFalse(vorbisSetup.modes[0].blockFlag); + assertTrue(vorbisSetup.modes[1].blockFlag); + break; + } catch (Throwable e) { + Log.e(TAG, e.getMessage(), e); + extractorInput.resetPeekPosition(); + exceptionCount++; + } + } + if (exceptionCount >= maxExceptions) { + fail("more than " + maxExceptions + " exceptions thrown"); + } + } + +} diff --git a/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/RecordableExtractorInput.java b/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/RecordableExtractorInput.java new file mode 100644 index 0000000000..8cb9db208e --- /dev/null +++ b/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/RecordableExtractorInput.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2015 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.exoplayer.extractor.ogg; + +import com.google.android.exoplayer.C; +import com.google.android.exoplayer.extractor.ExtractorInput; + +import java.io.EOFException; +import java.io.IOException; + +/** + * Implementation of {@link ExtractorInput} for testing purpose. + */ +/* package */ class RecordableExtractorInput implements ExtractorInput { + protected static final byte STREAM_REVISION = 0x00; + + private byte[] data; + private int readOffset; + private int writeOffset; + private int peekOffset; + + private boolean throwExceptionsAtRead = false; + private boolean throwExceptionsAtPeek = false; + private int numberOfReadsUntilException = 1; + private int numberOfPeeksUntilException = 1; + private int readCounter; + private int peekCounter; + private int maxReadExceptions = Integer.MAX_VALUE; + private int maxPeekExceptions = Integer.MAX_VALUE; + private int readExceptionCounter; + private int peekExceptionCounter; + + + /** + * Constructs an instance with a initial array of bytes. + * + * @param data the initial data. + * @param writeOffset the {@code writeOffset} from where to start recording. + */ + public RecordableExtractorInput(byte[] data, int writeOffset) { + this.data = data; + this.writeOffset = writeOffset; + } + + /** + * Constructs an instance with an empty data array with length {@code maxBytes}. + * + * @param maxBytes the maximal number of bytes this {@code ExtractorInput} can store. + */ + public RecordableExtractorInput(int maxBytes) { + this(new byte[maxBytes], 0); + } + + @Override + public int read(byte[] target, int offset, int length) throws IOException, InterruptedException { + readFully(target, offset, length); + return isEOF() ? C.RESULT_END_OF_INPUT : length; + } + + @Override + public boolean readFully(byte[] target, int offset, int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + readCounter++; + if (throwExceptionsAtRead + && readExceptionCounter < maxReadExceptions + && readCounter % numberOfReadsUntilException == 0) { + readCounter = 0; + numberOfReadsUntilException++; + readExceptionCounter++; + throw new IOException("deliberately thrown an exception for testing"); + } + if (readOffset + length > writeOffset) { + if (!allowEndOfInput) { + throw new EOFException(); + } + return false; + } + System.arraycopy(data, readOffset, target, offset, length); + readOffset += length; + peekOffset = readOffset; + return true; + } + + @Override + public void readFully(byte[] target, int offset, int length) + throws IOException, InterruptedException { + readFully(target, offset, length, false); + } + + @Override + public int skip(int length) throws IOException, InterruptedException { + skipFully(length); + return isEOF() ? C.RESULT_END_OF_INPUT : length; + } + + private boolean isEOF() { + return readOffset == writeOffset; + } + + @Override + public boolean skipFully(int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + if (readOffset + length >= writeOffset) { + if (!allowEndOfInput) { + throw new EOFException(); + } + return false; + } + readOffset += length; + peekOffset = readOffset; + return true; + } + + @Override + public void skipFully(int length) throws IOException, InterruptedException { + skipFully(length, false); + } + + @Override + public boolean peekFully(byte[] target, int offset, int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + peekCounter++; + if (throwExceptionsAtPeek + && peekExceptionCounter < maxPeekExceptions + && peekCounter % numberOfPeeksUntilException == 0) { + peekCounter = 0; + numberOfPeeksUntilException++; + peekExceptionCounter++; + throw new IOException("deliberately thrown an exception for testing"); + } + if (peekOffset + length > writeOffset) { + if (!allowEndOfInput) { + throw new EOFException(); + } + return false; + } + System.arraycopy(data, peekOffset, target, offset, length); + peekOffset += length; + return true; + } + + @Override + public void peekFully(byte[] target, int offset, int length) + throws IOException, InterruptedException { + peekFully(target, offset, length, false); + } + + @Override + public boolean advancePeekPosition(int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + if (peekOffset + length >= writeOffset) { + if (!allowEndOfInput) { + throw new EOFException(); + } + return false; + } + peekOffset += length; + return true; + } + + @Override + public void advancePeekPosition(int length) throws IOException, InterruptedException { + advancePeekPosition(length, false); + } + + @Override + public void resetPeekPosition() { + peekOffset = readOffset; + } + + @Override + public long getPosition() { + return readOffset; + } + + @Override + public long getLength() { + return writeOffset; + } + + /** + * Records the {@code bytes}. + * + * @param bytes the bytes to record. + */ + public void record(final byte[] bytes) { + System.arraycopy(bytes, 0, data, writeOffset, bytes.length); + writeOffset += bytes.length; + } + + /** Records a single byte. **/ + public void record(byte b) { + record(new byte[]{b}); + } + + /** + * Gets a byte array with length {@code length} with ascending values starting from 0 (zero). + * + * @param length the length of the array. + * @return an array of bytes with ascending values. + */ + public static byte[] getBytesGrowingValues(int length) { + return fillBytesGrowingValues(new byte[length], length, (byte) 0); + } + + /** + * Gets a byte array with length {@code length} with ascending values starting + * from {@code startValue}. + * + * @param length the length of the array. + * @param startValue the value from which to start. + * @return an array of bytes with ascending values starting from {@code startValue}. + */ + public static byte[] getBytesGrowingValues(int length, byte startValue) { + return fillBytesGrowingValues(new byte[length], length, startValue); + } + + /** + * Fills the byte array passed as argument with ascending values. + * + * @param bytes the byte array to fill with values. + * @param limit the number of bytes to set in the array. + * @param startValue the startValue from which the values in the array have to start. + */ + public static byte[] fillBytesGrowingValues(byte[] bytes, int limit, byte startValue) { + for (int i = 0; i < bytes.length; i++) { + if (i < limit) { + bytes[i] = (byte) ((i + startValue) % 255); + } else { + bytes[i] = 0; + } + } + return bytes; + } + + public void setMaxReadExceptions(int maxReadExceptions) { + this.maxReadExceptions = maxReadExceptions; + } + + public void setMaxPeekExceptions(int maxPeekExceptions) { + this.maxPeekExceptions = maxPeekExceptions; + } + + public void setNumberOfReadsUntilException(int numberOfReadsUntilException) { + this.numberOfReadsUntilException = numberOfReadsUntilException; + } + + public void setNumberOfPeeksUntilException(int numberOfPeeksUntilException) { + this.numberOfPeeksUntilException = numberOfPeeksUntilException; + } + + public void doThrowExceptionsAtRead(boolean throwExceptionsAtRead) { + this.throwExceptionsAtRead = throwExceptionsAtRead; + } + + public void doThrowExceptionsAtPeek(boolean throwExceptionsAtPeek) { + this.throwExceptionsAtPeek = throwExceptionsAtPeek; + } + +} diff --git a/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/RecordableOggExtractorInput.java b/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/RecordableOggExtractorInput.java new file mode 100644 index 0000000000..c293178ffc --- /dev/null +++ b/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/RecordableOggExtractorInput.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2015 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.exoplayer.extractor.ogg; + +/** + * A {@link RecordableOggExtractorInput} with convenient methods to record an OGG byte stream. + */ +/* package */ final class RecordableOggExtractorInput extends RecordableExtractorInput { + + private long pageSequenceCounter; + + public RecordableOggExtractorInput(byte[] data, int writeOffset) { + super(data, writeOffset); + pageSequenceCounter = 1000; + } + + public RecordableOggExtractorInput(int maxBytes) { + this(new byte[maxBytes], 0); + } + + /** + * Syntax sugar to make tests more readable. + * + * @param laces the laces to record to the data. + */ + protected void recordOggLaces(final byte[] laces) { + record(laces); + } + + /** + * Syntax sugar to make tests more readable. + * + * @param packet the packet bytes to record to the data. + */ + protected void recordOggPacket(final byte[] packet) { + record(packet); + } + + protected void recordOggHeader(final byte headerType, final long granule, + final byte pageSegmentCount) { + record((byte) 0x4F); // O + record((byte) 0x67); // g + record((byte) 0x67); // g + record((byte) 0x53); // S + record(STREAM_REVISION); + record(headerType); + recordGranulePosition(granule); + record((byte) 0x00); // LSB of data serial number + record((byte) 0x10); + record((byte) 0x00); + record((byte) 0x00); // MSB of data serial number + recordPageSequenceCounter(); + record((byte) 0x00); // LSB of page checksum + record((byte) 0x00); + record((byte) 0x00); + record((byte) 0x00); // MSB of page checksum + record(pageSegmentCount); // 0 - 255 + } + + protected void recordGranulePosition(long granule) { + record((byte) (granule & 0xFF)); + record((byte) ((granule >> 8) & 0xFF)); + record((byte) ((granule >> 16) & 0xFF)); + record((byte) ((granule >> 24) & 0xFF)); + record((byte) ((granule >> 32) & 0xFF)); + record((byte) ((granule >> 40) & 0xFF)); + record((byte) ((granule >> 48) & 0xFF)); + record((byte) ((granule >> 56) & 0xFF)); + } + + protected void recordPageSequenceCounter() { + record((byte) (pageSequenceCounter & 0xFF)); + record((byte) ((pageSequenceCounter >> 8) & 0xFF)); + record((byte) ((pageSequenceCounter >> 16) & 0xFF)); + record((byte) ((pageSequenceCounter++ >> 24) & 0xFF)); + } + +} diff --git a/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/TestData.java b/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/TestData.java new file mode 100644 index 0000000000..e76086249e --- /dev/null +++ b/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/TestData.java @@ -0,0 +1,1039 @@ +/* + * Copyright (C) 2015 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.exoplayer.extractor.ogg; + +/** + * Provides ogg/vorbis test data in bytes for unit tests. + */ +/* package */ final class TestData { + + /** + * Returns the initial two pages of bytes which by spec contain the three vorbis header packets: + * identification, comment and setup header. + */ + public static byte[] getVorbisHeaderPages() { + byte[] data = new byte[VORBIS_HEADER_PAGES.length]; + System.arraycopy(VORBIS_HEADER_PAGES, 0, data, 0, + VORBIS_HEADER_PAGES.length); + return data; + } + + /** + * Returns a valid vorbis identification header in bytes. + */ + public static byte[] getIdentificationHeaderData() { + int idHeaderStart = 28; + int idHeaderLength = 30; + byte[] idHeaderData = new byte[idHeaderLength]; + System.arraycopy(VORBIS_HEADER_PAGES, idHeaderStart, idHeaderData, 0, idHeaderLength); + return idHeaderData; + } + + /** + * Returns a valid vorbis comment header with 3 comments including utf8 chars in bytes. + */ + public static byte[] getCommentHeaderDataUTF8() { + byte[] commentHeaderData = new byte[COMMENT_HEADER_WITH_UTF8.length]; + System.arraycopy(COMMENT_HEADER_WITH_UTF8, 0, commentHeaderData, 0, + COMMENT_HEADER_WITH_UTF8.length); + return commentHeaderData; + } + + /** + * Returns a valid vorbis setup header in bytes. + */ + public static byte[] getSetupHeaderData() { + int setupHeaderStart = 146; + int setupHeaderLength = VORBIS_HEADER_PAGES.length - setupHeaderStart; + byte[] setupHeaderData = new byte[setupHeaderLength]; + System.arraycopy(VORBIS_HEADER_PAGES, setupHeaderStart, setupHeaderData, 0, setupHeaderLength); + return setupHeaderData; + } + + private static final byte[] COMMENT_HEADER_WITH_UTF8 = { + (byte) 0x03, (byte) 0x76, (byte) 0x6f, (byte) 0x72, // 3, v, o, r, + (byte) 0x62, (byte) 0x69, (byte) 0x73, (byte) 0x2b, // b, i, s, . + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x58, + (byte) 0x69, (byte) 0x70, (byte) 0x68, (byte) 0x2e, + (byte) 0x4f, (byte) 0x72, (byte) 0x67, (byte) 0x20, + (byte) 0x6c, (byte) 0x69, (byte) 0x62, (byte) 0x56, + (byte) 0x6f, (byte) 0x72, (byte) 0x62, (byte) 0x69, + (byte) 0x73, (byte) 0x20, (byte) 0x49, (byte) 0x20, + (byte) 0x32, (byte) 0x30, (byte) 0x31, (byte) 0x32, + (byte) 0x30, (byte) 0x32, (byte) 0x30, (byte) 0x33, + (byte) 0x20, (byte) 0x28, (byte) 0x4f, (byte) 0x6d, + (byte) 0x6e, (byte) 0x69, (byte) 0x70, (byte) 0x72, + (byte) 0x65, (byte) 0x73, (byte) 0x65, (byte) 0x6e, + (byte) 0x74, (byte) 0x29, (byte) 0x03, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x0a, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x41, (byte) 0x4c, + (byte) 0x42, (byte) 0x55, (byte) 0x4d, (byte) 0x3d, + (byte) 0xc3, (byte) 0xa4, (byte) 0xc3, (byte) 0xb6, + (byte) 0x13, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x54, (byte) 0x49, (byte) 0x54, (byte) 0x4c, + (byte) 0x45, (byte) 0x3d, (byte) 0x41, (byte) 0x20, + (byte) 0x73, (byte) 0x61, (byte) 0x6d, (byte) 0x70, + (byte) 0x6c, (byte) 0x65, (byte) 0x20, (byte) 0x73, + (byte) 0x6f, (byte) 0x6e, (byte) 0x67, (byte) 0x0d, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x41, + (byte) 0x52, (byte) 0x54, (byte) 0x49, (byte) 0x53, + (byte) 0x54, (byte) 0x3d, (byte) 0x47, (byte) 0x6f, + (byte) 0x6f, (byte) 0x67, (byte) 0x6c, (byte) 0x65, + (byte) 0x01 + }; + + // two OGG pages with 3 packets (id, comment and setup header) + // length: 3743 bytes + private static final byte[] VORBIS_HEADER_PAGES = { /* capture pattern ogg header 1 */ + (byte) 0x4f, (byte) 0x67, (byte) 0x67, (byte) 0x53, // O,g,g,S : start pos 0 + (byte) 0x00, (byte) 0x02, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x5e, (byte) 0x5f, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x83, (byte) 0x36, + (byte) 0xe3, (byte) 0x49, (byte) 0x01, (byte) 0x1e, /* capture pattern vorbis id header */ + (byte) 0x01, (byte) 0x76, (byte) 0x6f, (byte) 0x72, // 1,v,o,r : start pos 28 + (byte) 0x62, (byte) 0x69, (byte) 0x73, (byte) 0x00, // b,i,s,. + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02, + (byte) 0x22, (byte) 0x56, (byte) 0x00, (byte) 0x00, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0x6a, (byte) 0x04, (byte) 0x01, (byte) 0x00, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, /* capture pattern ogg header 2 */ + (byte) 0xa9, (byte) 0x01, (byte) 0x4f, (byte) 0x67, // .,.,O,g : start pos 86 + (byte) 0x67, (byte) 0x53, (byte) 0x00, (byte) 0x00, // g,S,.,. + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x5e, (byte) 0x5f, (byte) 0x00, (byte) 0x00, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x69, (byte) 0xf8, (byte) 0xeb, (byte) 0xe1, + (byte) 0x10, (byte) 0x2d, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, /* capture pattern vorbis comment header*/ + (byte) 0x1b, (byte) 0x03, (byte) 0x76, (byte) 0x6f, // .,3,v,o : start pos 101 + (byte) 0x72, (byte) 0x62, (byte) 0x69, (byte) 0x73, // r,b,i,s + (byte) 0x1d, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x58, (byte) 0x69, (byte) 0x70, (byte) 0x68, + (byte) 0x2e, (byte) 0x4f, (byte) 0x72, (byte) 0x67, + (byte) 0x20, (byte) 0x6c, (byte) 0x69, (byte) 0x62, + (byte) 0x56, (byte) 0x6f, (byte) 0x72, (byte) 0x62, + (byte) 0x69, (byte) 0x73, (byte) 0x20, (byte) 0x49, + (byte) 0x20, (byte) 0x32, (byte) 0x30, (byte) 0x30, + (byte) 0x33, (byte) 0x30, (byte) 0x39, (byte) 0x30, + (byte) 0x39, (byte) 0x00, (byte) 0x00, (byte) 0x00, /* capture pattern vorbis setup header */ + (byte) 0x00, (byte) 0x01, (byte) 0x05, (byte) 0x76, // .,.,5,v : start pos 146 + (byte) 0x6f, (byte) 0x72, (byte) 0x62, (byte) 0x69, // o,r,b,i + (byte) 0x73, (byte) 0x22, (byte) 0x42, (byte) 0x43, // s,. + (byte) 0x56, (byte) 0x01, (byte) 0x00, (byte) 0x40, + (byte) 0x00, (byte) 0x00, (byte) 0x18, (byte) 0x42, + (byte) 0x10, (byte) 0x2a, (byte) 0x05, (byte) 0xad, + (byte) 0x63, (byte) 0x8e, (byte) 0x3a, (byte) 0xc8, + (byte) 0x15, (byte) 0x21, (byte) 0x8c, (byte) 0x19, + (byte) 0xa2, (byte) 0xa0, (byte) 0x42, (byte) 0xca, + (byte) 0x29, (byte) 0xc7, (byte) 0x1d, (byte) 0x42, + (byte) 0xd0, (byte) 0x21, (byte) 0xa3, (byte) 0x24, + (byte) 0x43, (byte) 0x88, (byte) 0x3a, (byte) 0xc6, + (byte) 0x35, (byte) 0xc7, (byte) 0x18, (byte) 0x63, + (byte) 0x47, (byte) 0xb9, (byte) 0x64, (byte) 0x8a, + (byte) 0x42, (byte) 0xc9, (byte) 0x81, (byte) 0xd0, + (byte) 0x90, (byte) 0x55, (byte) 0x00, (byte) 0x00, + (byte) 0x40, (byte) 0x00, (byte) 0x00, (byte) 0xa4, + (byte) 0x1c, (byte) 0x57, (byte) 0x50, (byte) 0x72, + (byte) 0x49, (byte) 0x2d, (byte) 0xe7, (byte) 0x9c, + (byte) 0x73, (byte) 0xa3, (byte) 0x18, (byte) 0x57, + (byte) 0xcc, (byte) 0x71, (byte) 0xe8, (byte) 0x20, + (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xe5, + (byte) 0x20, (byte) 0x67, (byte) 0xcc, (byte) 0x71, + (byte) 0x09, (byte) 0x25, (byte) 0xe7, (byte) 0x9c, + (byte) 0x73, (byte) 0x8e, (byte) 0x39, (byte) 0xe7, + (byte) 0x92, (byte) 0x72, (byte) 0x8e, (byte) 0x31, + (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xa3, + (byte) 0x18, (byte) 0x57, (byte) 0x0e, (byte) 0x72, + (byte) 0x29, (byte) 0x2d, (byte) 0xe7, (byte) 0x9c, + (byte) 0x73, (byte) 0x81, (byte) 0x14, (byte) 0x47, + (byte) 0x8a, (byte) 0x71, (byte) 0xa7, (byte) 0x18, + (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xa4, + (byte) 0x1c, (byte) 0x47, (byte) 0x8a, (byte) 0x71, + (byte) 0xa8, (byte) 0x18, (byte) 0xe7, (byte) 0x9c, + (byte) 0x73, (byte) 0x6d, (byte) 0x31, (byte) 0xb7, + (byte) 0x92, (byte) 0x72, (byte) 0xce, (byte) 0x39, + (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xe6, + (byte) 0x20, (byte) 0x87, (byte) 0x52, (byte) 0x72, + (byte) 0xae, (byte) 0x35, (byte) 0xe7, (byte) 0x9c, + (byte) 0x73, (byte) 0xa4, (byte) 0x18, (byte) 0x67, + (byte) 0x0e, (byte) 0x72, (byte) 0x0b, (byte) 0x25, + (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xc6, + (byte) 0x20, (byte) 0x67, (byte) 0xcc, (byte) 0x71, + (byte) 0xeb, (byte) 0x20, (byte) 0xe7, (byte) 0x9c, + (byte) 0x73, (byte) 0x8c, (byte) 0x35, (byte) 0xb7, + (byte) 0xd4, (byte) 0x72, (byte) 0xce, (byte) 0x39, + (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xce, + (byte) 0x39, (byte) 0xe7, (byte) 0x9c, (byte) 0x73, + (byte) 0xce, (byte) 0x39, (byte) 0xe7, (byte) 0x9c, + (byte) 0x73, (byte) 0x8c, (byte) 0x31, (byte) 0xe7, + (byte) 0x9c, (byte) 0x73, (byte) 0xce, (byte) 0x39, + (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0x6e, + (byte) 0x31, (byte) 0xe7, (byte) 0x16, (byte) 0x73, + (byte) 0xae, (byte) 0x39, (byte) 0xe7, (byte) 0x9c, + (byte) 0x73, (byte) 0xce, (byte) 0x39, (byte) 0xe7, + (byte) 0x1c, (byte) 0x73, (byte) 0xce, (byte) 0x39, + (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0x20, + (byte) 0x34, (byte) 0x64, (byte) 0x15, (byte) 0x00, + (byte) 0x90, (byte) 0x00, (byte) 0x00, (byte) 0xa0, + (byte) 0xa1, (byte) 0x28, (byte) 0x8a, (byte) 0xe2, + (byte) 0x28, (byte) 0x0e, (byte) 0x10, (byte) 0x1a, + (byte) 0xb2, (byte) 0x0a, (byte) 0x00, (byte) 0xc8, + (byte) 0x00, (byte) 0x00, (byte) 0x10, (byte) 0x40, + (byte) 0x71, (byte) 0x14, (byte) 0x47, (byte) 0x91, + (byte) 0x14, (byte) 0x4b, (byte) 0xb1, (byte) 0x1c, + (byte) 0xcb, (byte) 0xd1, (byte) 0x24, (byte) 0x0d, + (byte) 0x08, (byte) 0x0d, (byte) 0x59, (byte) 0x05, + (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, + (byte) 0x08, (byte) 0x00, (byte) 0x00, (byte) 0xa0, + (byte) 0x48, (byte) 0x86, (byte) 0xa4, (byte) 0x48, + (byte) 0x8a, (byte) 0xa5, (byte) 0x58, (byte) 0x8e, + (byte) 0x66, (byte) 0x69, (byte) 0x9e, (byte) 0x26, + (byte) 0x7a, (byte) 0xa2, (byte) 0x28, (byte) 0x9a, + (byte) 0xa2, (byte) 0x2a, (byte) 0xab, (byte) 0xb2, + (byte) 0x69, (byte) 0xca, (byte) 0xb2, (byte) 0x2c, + (byte) 0xcb, (byte) 0xb2, (byte) 0xeb, (byte) 0xba, + (byte) 0x2e, (byte) 0x10, (byte) 0x1a, (byte) 0xb2, + (byte) 0x0a, (byte) 0x00, (byte) 0x48, (byte) 0x00, + (byte) 0x00, (byte) 0x50, (byte) 0x51, (byte) 0x14, + (byte) 0xc5, (byte) 0x70, (byte) 0x14, (byte) 0x07, + (byte) 0x08, (byte) 0x0d, (byte) 0x59, (byte) 0x05, + (byte) 0x00, (byte) 0x64, (byte) 0x00, (byte) 0x00, + (byte) 0x08, (byte) 0x60, (byte) 0x28, (byte) 0x8a, + (byte) 0xa3, (byte) 0x38, (byte) 0x8e, (byte) 0xe4, + (byte) 0x58, (byte) 0x92, (byte) 0xa5, (byte) 0x59, + (byte) 0x9e, (byte) 0x07, (byte) 0x84, (byte) 0x86, + (byte) 0xac, (byte) 0x02, (byte) 0x00, (byte) 0x80, + (byte) 0x00, (byte) 0x00, (byte) 0x04, (byte) 0x00, + (byte) 0x00, (byte) 0x50, (byte) 0x0c, (byte) 0x47, + (byte) 0xb1, (byte) 0x14, (byte) 0x4d, (byte) 0xf1, + (byte) 0x24, (byte) 0xcf, (byte) 0xf2, (byte) 0x3c, + (byte) 0xcf, (byte) 0xf3, (byte) 0x3c, (byte) 0xcf, + (byte) 0xf3, (byte) 0x3c, (byte) 0xcf, (byte) 0xf3, + (byte) 0x3c, (byte) 0xcf, (byte) 0xf3, (byte) 0x3c, + (byte) 0xcf, (byte) 0xf3, (byte) 0x3c, (byte) 0xcf, + (byte) 0xf3, (byte) 0x3c, (byte) 0x0d, (byte) 0x08, + (byte) 0x0d, (byte) 0x59, (byte) 0x05, (byte) 0x00, + (byte) 0x20, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x82, (byte) 0x28, (byte) 0x64, (byte) 0x18, + (byte) 0x03, (byte) 0x42, (byte) 0x43, (byte) 0x56, + (byte) 0x01, (byte) 0x00, (byte) 0x40, (byte) 0x00, + (byte) 0x00, (byte) 0x08, (byte) 0x21, (byte) 0x1a, + (byte) 0x19, (byte) 0x43, (byte) 0x9d, (byte) 0x52, + (byte) 0x12, (byte) 0x5c, (byte) 0x0a, (byte) 0x16, + (byte) 0x42, (byte) 0x1c, (byte) 0x11, (byte) 0x43, + (byte) 0x1d, (byte) 0x42, (byte) 0xce, (byte) 0x43, + (byte) 0xa9, (byte) 0xa5, (byte) 0x83, (byte) 0xe0, + (byte) 0x29, (byte) 0x85, (byte) 0x25, (byte) 0x63, + (byte) 0xd2, (byte) 0x53, (byte) 0xac, (byte) 0x41, + (byte) 0x08, (byte) 0x21, (byte) 0x7c, (byte) 0xef, + (byte) 0x3d, (byte) 0xf7, (byte) 0xde, (byte) 0x7b, + (byte) 0xef, (byte) 0x81, (byte) 0xd0, (byte) 0x90, + (byte) 0x55, (byte) 0x00, (byte) 0x00, (byte) 0x10, + (byte) 0x00, (byte) 0x00, (byte) 0x61, (byte) 0x14, + (byte) 0x38, (byte) 0x88, (byte) 0x81, (byte) 0xc7, + (byte) 0x24, (byte) 0x08, (byte) 0x21, (byte) 0x84, + (byte) 0x62, (byte) 0x14, (byte) 0x27, (byte) 0x44, + (byte) 0x71, (byte) 0xa6, (byte) 0x20, (byte) 0x08, + (byte) 0x21, (byte) 0x84, (byte) 0xe5, (byte) 0x24, + (byte) 0x58, (byte) 0xca, (byte) 0x79, (byte) 0xe8, + (byte) 0x24, (byte) 0x08, (byte) 0xdd, (byte) 0x83, + (byte) 0x10, (byte) 0x42, (byte) 0xb8, (byte) 0x9c, + (byte) 0x7b, (byte) 0xcb, (byte) 0xb9, (byte) 0xf7, + (byte) 0xde, (byte) 0x7b, (byte) 0x20, (byte) 0x34, + (byte) 0x64, (byte) 0x15, (byte) 0x00, (byte) 0x00, + (byte) 0x08, (byte) 0x00, (byte) 0xc0, (byte) 0x20, + (byte) 0x84, (byte) 0x10, (byte) 0x42, (byte) 0x08, + (byte) 0x21, (byte) 0x84, (byte) 0x10, (byte) 0x42, + (byte) 0x08, (byte) 0x29, (byte) 0xa4, (byte) 0x94, + (byte) 0x52, (byte) 0x48, (byte) 0x29, (byte) 0xa6, + (byte) 0x98, (byte) 0x62, (byte) 0x8a, (byte) 0x29, + (byte) 0xc7, (byte) 0x1c, (byte) 0x73, (byte) 0xcc, + (byte) 0x31, (byte) 0xc7, (byte) 0x20, (byte) 0x83, + (byte) 0x0c, (byte) 0x32, (byte) 0xe8, (byte) 0xa0, + (byte) 0x93, (byte) 0x4e, (byte) 0x3a, (byte) 0xc9, + (byte) 0xa4, (byte) 0x92, (byte) 0x4e, (byte) 0x3a, + (byte) 0xca, (byte) 0x24, (byte) 0xa3, (byte) 0x8e, + (byte) 0x52, (byte) 0x6b, (byte) 0x29, (byte) 0xb5, + (byte) 0x14, (byte) 0x53, (byte) 0x4c, (byte) 0xb1, + (byte) 0xe5, (byte) 0x16, (byte) 0x63, (byte) 0xad, + (byte) 0xb5, (byte) 0xd6, (byte) 0x9c, (byte) 0x73, + (byte) 0xaf, (byte) 0x41, (byte) 0x29, (byte) 0x63, + (byte) 0x8c, (byte) 0x31, (byte) 0xc6, (byte) 0x18, + (byte) 0x63, (byte) 0x8c, (byte) 0x31, (byte) 0xc6, + (byte) 0x18, (byte) 0x63, (byte) 0x8c, (byte) 0x31, + (byte) 0xc6, (byte) 0x18, (byte) 0x23, (byte) 0x08, + (byte) 0x0d, (byte) 0x59, (byte) 0x05, (byte) 0x00, + (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x10, + (byte) 0x06, (byte) 0x19, (byte) 0x64, (byte) 0x90, + (byte) 0x41, (byte) 0x08, (byte) 0x21, (byte) 0x84, + (byte) 0x14, (byte) 0x52, (byte) 0x48, (byte) 0x29, + (byte) 0xa6, (byte) 0x98, (byte) 0x72, (byte) 0xcc, + (byte) 0x31, (byte) 0xc7, (byte) 0x1c, (byte) 0x03, + (byte) 0x42, (byte) 0x43, (byte) 0x56, (byte) 0x01, + (byte) 0x00, (byte) 0x80, (byte) 0x00, (byte) 0x00, + (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x1c, (byte) 0x45, (byte) 0x52, (byte) 0x24, + (byte) 0x47, (byte) 0x72, (byte) 0x24, (byte) 0x47, + (byte) 0x92, (byte) 0x24, (byte) 0xc9, (byte) 0x92, + (byte) 0x2c, (byte) 0x49, (byte) 0x93, (byte) 0x3c, + (byte) 0xcb, (byte) 0xb3, (byte) 0x3c, (byte) 0xcb, + (byte) 0xb3, (byte) 0x3c, (byte) 0x4d, (byte) 0xd4, + (byte) 0x44, (byte) 0x4d, (byte) 0x15, (byte) 0x55, + (byte) 0xd5, (byte) 0x55, (byte) 0x6d, (byte) 0xd7, + (byte) 0xf6, (byte) 0x6d, (byte) 0x5f, (byte) 0xf6, + (byte) 0x6d, (byte) 0xdf, (byte) 0xd5, (byte) 0x65, + (byte) 0xdf, (byte) 0xf6, (byte) 0x65, (byte) 0xdb, + (byte) 0xd5, (byte) 0x65, (byte) 0x5d, (byte) 0x96, + (byte) 0x65, (byte) 0xdd, (byte) 0xb5, (byte) 0x6d, + (byte) 0x5d, (byte) 0xd6, (byte) 0x5d, (byte) 0x5d, + (byte) 0xd7, (byte) 0x75, (byte) 0x5d, (byte) 0xd7, + (byte) 0x75, (byte) 0x5d, (byte) 0xd7, (byte) 0x75, + (byte) 0x5d, (byte) 0xd7, (byte) 0x75, (byte) 0x5d, + (byte) 0xd7, (byte) 0x75, (byte) 0x5d, (byte) 0xd7, + (byte) 0x81, (byte) 0xd0, (byte) 0x90, (byte) 0x55, + (byte) 0x00, (byte) 0x80, (byte) 0x04, (byte) 0x00, + (byte) 0x80, (byte) 0x8e, (byte) 0xe4, (byte) 0x38, + (byte) 0x8e, (byte) 0xe4, (byte) 0x38, (byte) 0x8e, + (byte) 0xe4, (byte) 0x48, (byte) 0x8e, (byte) 0xa4, + (byte) 0x48, (byte) 0x0a, (byte) 0x10, (byte) 0x1a, + (byte) 0xb2, (byte) 0x0a, (byte) 0x00, (byte) 0x90, + (byte) 0x01, (byte) 0x00, (byte) 0x10, (byte) 0x00, + (byte) 0x80, (byte) 0xa3, (byte) 0x38, (byte) 0x8a, + (byte) 0xe3, (byte) 0x48, (byte) 0x8e, (byte) 0xe4, + (byte) 0x58, (byte) 0x8e, (byte) 0x25, (byte) 0x59, + (byte) 0x92, (byte) 0x26, (byte) 0x69, (byte) 0x96, + (byte) 0x67, (byte) 0x79, (byte) 0x96, (byte) 0xa7, + (byte) 0x79, (byte) 0x9a, (byte) 0xa8, (byte) 0x89, + (byte) 0x1e, (byte) 0x10, (byte) 0x1a, (byte) 0xb2, + (byte) 0x0a, (byte) 0x00, (byte) 0x00, (byte) 0x04, + (byte) 0x00, (byte) 0x10, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80, + (byte) 0xa2, (byte) 0x28, (byte) 0x8a, (byte) 0xa3, + (byte) 0x38, (byte) 0x8e, (byte) 0x24, (byte) 0x59, + (byte) 0x96, (byte) 0xa6, (byte) 0x69, (byte) 0x9e, + (byte) 0xa7, (byte) 0x7a, (byte) 0xa2, (byte) 0x28, + (byte) 0x9a, (byte) 0xaa, (byte) 0xaa, (byte) 0x8a, + (byte) 0xa6, (byte) 0xa9, (byte) 0xaa, (byte) 0xaa, + (byte) 0x6a, (byte) 0x9a, (byte) 0xa6, (byte) 0x69, + (byte) 0x9a, (byte) 0xa6, (byte) 0x69, (byte) 0x9a, + (byte) 0xa6, (byte) 0x69, (byte) 0x9a, (byte) 0xa6, + (byte) 0x69, (byte) 0x9a, (byte) 0xa6, (byte) 0x69, + (byte) 0x9a, (byte) 0xa6, (byte) 0x69, (byte) 0x9a, + (byte) 0xa6, (byte) 0x69, (byte) 0x9a, (byte) 0xa6, + (byte) 0x69, (byte) 0x9a, (byte) 0xa6, (byte) 0x69, + (byte) 0x9a, (byte) 0xa6, (byte) 0x69, (byte) 0x9a, + (byte) 0xa6, (byte) 0x69, (byte) 0x02, (byte) 0xa1, + (byte) 0x21, (byte) 0xab, (byte) 0x00, (byte) 0x00, + (byte) 0x09, (byte) 0x00, (byte) 0x00, (byte) 0x1d, + (byte) 0xc7, (byte) 0x71, (byte) 0x1c, (byte) 0x47, + (byte) 0x71, (byte) 0x1c, (byte) 0xc7, (byte) 0x71, + (byte) 0x24, (byte) 0x47, (byte) 0x92, (byte) 0x24, + (byte) 0x20, (byte) 0x34, (byte) 0x64, (byte) 0x15, + (byte) 0x00, (byte) 0x20, (byte) 0x03, (byte) 0x00, + (byte) 0x20, (byte) 0x00, (byte) 0x00, (byte) 0x43, + (byte) 0x51, (byte) 0x1c, (byte) 0x45, (byte) 0x72, + (byte) 0x2c, (byte) 0xc7, (byte) 0x92, (byte) 0x34, + (byte) 0x4b, (byte) 0xb3, (byte) 0x3c, (byte) 0xcb, + (byte) 0xd3, (byte) 0x44, (byte) 0xcf, (byte) 0xf4, + (byte) 0x5c, (byte) 0x51, (byte) 0x36, (byte) 0x75, + (byte) 0x53, (byte) 0x57, (byte) 0x6d, (byte) 0x20, + (byte) 0x34, (byte) 0x64, (byte) 0x15, (byte) 0x00, + (byte) 0x00, (byte) 0x08, (byte) 0x00, (byte) 0x20, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xc7, (byte) 0x73, + (byte) 0x3c, (byte) 0xc7, (byte) 0x73, (byte) 0x3c, + (byte) 0xc9, (byte) 0x93, (byte) 0x3c, (byte) 0xcb, + (byte) 0x73, (byte) 0x3c, (byte) 0xc7, (byte) 0x93, + (byte) 0x3c, (byte) 0x49, (byte) 0xd3, (byte) 0x34, + (byte) 0x4d, (byte) 0xd3, (byte) 0x34, (byte) 0x4d, + (byte) 0xd3, (byte) 0x34, (byte) 0x4d, (byte) 0xd3, + (byte) 0x34, (byte) 0x4d, (byte) 0xd3, (byte) 0x34, + (byte) 0x4d, (byte) 0xd3, (byte) 0x34, (byte) 0x4d, + (byte) 0xd3, (byte) 0x34, (byte) 0x4d, (byte) 0xd3, + (byte) 0x34, (byte) 0x4d, (byte) 0xd3, (byte) 0x34, + (byte) 0x4d, (byte) 0xd3, (byte) 0x34, (byte) 0x4d, + (byte) 0xd3, (byte) 0x34, (byte) 0x4d, (byte) 0xd3, + (byte) 0x34, (byte) 0x4d, (byte) 0xd3, (byte) 0x34, + (byte) 0x4d, (byte) 0xd3, (byte) 0x34, (byte) 0x4d, + (byte) 0x03, (byte) 0x42, (byte) 0x43, (byte) 0x56, + (byte) 0x02, (byte) 0x00, (byte) 0x64, (byte) 0x00, + (byte) 0x00, (byte) 0x90, (byte) 0x02, (byte) 0xcf, + (byte) 0x42, (byte) 0x29, (byte) 0x2d, (byte) 0x46, + (byte) 0x02, (byte) 0x1c, (byte) 0x88, (byte) 0x98, + (byte) 0xa3, (byte) 0xd8, (byte) 0x7b, (byte) 0xef, + (byte) 0xbd, (byte) 0xf7, (byte) 0xde, (byte) 0x7b, + (byte) 0x65, (byte) 0x3c, (byte) 0x92, (byte) 0x88, + (byte) 0x49, (byte) 0xed, (byte) 0x31, (byte) 0xf4, + (byte) 0xd4, (byte) 0x31, (byte) 0x07, (byte) 0xb1, + (byte) 0x67, (byte) 0xc6, (byte) 0x23, (byte) 0x66, + (byte) 0x94, (byte) 0xa3, (byte) 0xd8, (byte) 0x29, + (byte) 0xcf, (byte) 0x1c, (byte) 0x42, (byte) 0x0c, + (byte) 0x62, (byte) 0xe8, (byte) 0x3c, (byte) 0x74, + (byte) 0x4a, (byte) 0x31, (byte) 0x88, (byte) 0x29, + (byte) 0xf5, (byte) 0x52, (byte) 0x32, (byte) 0xc6, + (byte) 0x20, (byte) 0xc6, (byte) 0xd8, (byte) 0x63, + (byte) 0x0c, (byte) 0x21, (byte) 0x94, (byte) 0x18, + (byte) 0x08, (byte) 0x0d, (byte) 0x59, (byte) 0x21, + (byte) 0x00, (byte) 0x84, (byte) 0x66, (byte) 0x00, + (byte) 0x18, (byte) 0x24, (byte) 0x09, (byte) 0x90, + (byte) 0x34, (byte) 0x0d, (byte) 0x90, (byte) 0x34, + (byte) 0x0d, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x24, (byte) 0x4f, (byte) 0x03, (byte) 0x34, + (byte) 0x51, (byte) 0x04, (byte) 0x34, (byte) 0x4f, + (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x49, (byte) 0xf3, (byte) 0x00, (byte) 0x4d, + (byte) 0xf4, (byte) 0x00, (byte) 0x4d, (byte) 0x14, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x90, (byte) 0x3c, (byte) 0x0d, (byte) 0xf0, + (byte) 0x44, (byte) 0x11, (byte) 0xd0, (byte) 0x44, + (byte) 0x11, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x34, (byte) 0x51, (byte) 0x04, (byte) 0x44, + (byte) 0x51, (byte) 0x05, (byte) 0x44, (byte) 0xd5, + (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x4d, (byte) 0x14, (byte) 0x01, (byte) 0x4f, + (byte) 0x15, (byte) 0x01, (byte) 0xd1, (byte) 0x54, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x90, (byte) 0x34, (byte) 0x0f, (byte) 0xd0, + (byte) 0x44, (byte) 0x11, (byte) 0xf0, (byte) 0x44, + (byte) 0x11, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x34, (byte) 0x51, (byte) 0x04, (byte) 0x44, + (byte) 0xd5, (byte) 0x04, (byte) 0x3c, (byte) 0x51, + (byte) 0x05, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x4d, (byte) 0x14, (byte) 0x01, (byte) 0xd1, + (byte) 0x54, (byte) 0x01, (byte) 0x51, (byte) 0x15, + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x04, + (byte) 0x00, (byte) 0x00, (byte) 0x04, (byte) 0x38, + (byte) 0x00, (byte) 0x00, (byte) 0x04, (byte) 0x58, + (byte) 0x08, (byte) 0x85, (byte) 0x86, (byte) 0xac, + (byte) 0x08, (byte) 0x00, (byte) 0xe2, (byte) 0x04, + (byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x10, + (byte) 0x00, (byte) 0x00, (byte) 0x30, (byte) 0xe0, + (byte) 0x00, (byte) 0x00, (byte) 0x10, (byte) 0x60, + (byte) 0x42, (byte) 0x19, (byte) 0x28, (byte) 0x34, + (byte) 0x64, (byte) 0x45, (byte) 0x00, (byte) 0x10, + (byte) 0x27, (byte) 0x00, (byte) 0x60, (byte) 0x70, + (byte) 0x1c, (byte) 0xcb, (byte) 0x02, (byte) 0x00, + (byte) 0x00, (byte) 0x47, (byte) 0x92, (byte) 0x34, + (byte) 0x0d, (byte) 0x00, (byte) 0x00, (byte) 0x1c, + (byte) 0x49, (byte) 0xd2, (byte) 0x34, (byte) 0x00, + (byte) 0x00, (byte) 0xd0, (byte) 0x34, (byte) 0x4d, + (byte) 0x14, (byte) 0x01, (byte) 0x00, (byte) 0xc0, + (byte) 0xd2, (byte) 0x34, (byte) 0x51, (byte) 0x04, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x30, + (byte) 0xe0, (byte) 0x00, (byte) 0x00, (byte) 0x10, + (byte) 0x60, (byte) 0x42, (byte) 0x19, (byte) 0x28, + (byte) 0x34, (byte) 0x64, (byte) 0x25, (byte) 0x00, + (byte) 0x10, (byte) 0x05, (byte) 0x00, (byte) 0x60, + (byte) 0x30, (byte) 0x14, (byte) 0x4d, (byte) 0x03, + (byte) 0x58, (byte) 0x16, (byte) 0xc0, (byte) 0xb2, + (byte) 0x00, (byte) 0x9a, (byte) 0x06, (byte) 0xd0, + (byte) 0x34, (byte) 0x80, (byte) 0xe7, (byte) 0x01, + (byte) 0x3c, (byte) 0x11, (byte) 0x60, (byte) 0x9a, + (byte) 0x00, (byte) 0x40, (byte) 0x00, (byte) 0x00, + (byte) 0x40, (byte) 0x81, (byte) 0x03, (byte) 0x00, + (byte) 0x40, (byte) 0x80, (byte) 0x0d, (byte) 0x9a, + (byte) 0x12, (byte) 0x8b, (byte) 0x03, (byte) 0x14, + (byte) 0x1a, (byte) 0xb2, (byte) 0x12, (byte) 0x00, + (byte) 0x88, (byte) 0x02, (byte) 0x00, (byte) 0x30, + (byte) 0x28, (byte) 0x8a, (byte) 0x24, (byte) 0x59, + (byte) 0x96, (byte) 0xe7, (byte) 0x41, (byte) 0xd3, + (byte) 0x34, (byte) 0x4d, (byte) 0x14, (byte) 0xa1, + (byte) 0x69, (byte) 0x9a, (byte) 0x26, (byte) 0x8a, + (byte) 0xf0, (byte) 0x3c, (byte) 0xcf, (byte) 0x13, + (byte) 0x45, (byte) 0x78, (byte) 0x9e, (byte) 0xe7, + (byte) 0x99, (byte) 0x26, (byte) 0x44, (byte) 0xd1, + (byte) 0xf3, (byte) 0x4c, (byte) 0x13, (byte) 0xa2, + (byte) 0xe8, (byte) 0x79, (byte) 0xa6, (byte) 0x09, + (byte) 0xd3, (byte) 0x14, (byte) 0x45, (byte) 0xd3, + (byte) 0x04, (byte) 0xa2, (byte) 0x68, (byte) 0x9a, + (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x0a, + (byte) 0x1c, (byte) 0x00, (byte) 0x00, (byte) 0x02, + (byte) 0x6c, (byte) 0xd0, (byte) 0x94, (byte) 0x58, + (byte) 0x1c, (byte) 0xa0, (byte) 0xd0, (byte) 0x90, + (byte) 0x95, (byte) 0x00, (byte) 0x40, (byte) 0x48, + (byte) 0x00, (byte) 0x80, (byte) 0x41, (byte) 0x51, + (byte) 0x2c, (byte) 0xcb, (byte) 0xf3, (byte) 0x44, + (byte) 0x51, (byte) 0x14, (byte) 0x4d, (byte) 0x53, + (byte) 0x55, (byte) 0x5d, (byte) 0x17, (byte) 0x9a, + (byte) 0xe6, (byte) 0x79, (byte) 0xa2, (byte) 0x28, + (byte) 0x8a, (byte) 0xa6, (byte) 0xa9, (byte) 0xaa, + (byte) 0xae, (byte) 0x0b, (byte) 0x4d, (byte) 0xf3, + (byte) 0x3c, (byte) 0x51, (byte) 0x14, (byte) 0x45, + (byte) 0xd3, (byte) 0x54, (byte) 0x55, (byte) 0xd7, + (byte) 0x85, (byte) 0xe7, (byte) 0x79, (byte) 0xa2, + (byte) 0x29, (byte) 0x9a, (byte) 0xa6, (byte) 0x69, + (byte) 0xaa, (byte) 0xaa, (byte) 0xeb, (byte) 0xc2, + (byte) 0xf3, (byte) 0x44, (byte) 0xd1, (byte) 0x34, + (byte) 0x4d, (byte) 0x53, (byte) 0x55, (byte) 0x55, + (byte) 0xd7, (byte) 0x75, (byte) 0xe1, (byte) 0x79, + (byte) 0xa2, (byte) 0x68, (byte) 0x9a, (byte) 0xa6, + (byte) 0xa9, (byte) 0xaa, (byte) 0xae, (byte) 0xeb, + (byte) 0xba, (byte) 0xf0, (byte) 0x3c, (byte) 0x51, + (byte) 0x34, (byte) 0x4d, (byte) 0xd3, (byte) 0x54, + (byte) 0x55, (byte) 0xd7, (byte) 0x95, (byte) 0x65, + (byte) 0x88, (byte) 0xa2, (byte) 0x28, (byte) 0x9a, + (byte) 0xa6, (byte) 0x69, (byte) 0xaa, (byte) 0xaa, + (byte) 0xeb, (byte) 0xca, (byte) 0x32, (byte) 0x10, + (byte) 0x45, (byte) 0xd3, (byte) 0x34, (byte) 0x4d, + (byte) 0x55, (byte) 0x75, (byte) 0x5d, (byte) 0x59, + (byte) 0x06, (byte) 0xa2, (byte) 0x68, (byte) 0x9a, + (byte) 0xaa, (byte) 0xea, (byte) 0xba, (byte) 0xae, + (byte) 0x2b, (byte) 0xcb, (byte) 0x40, (byte) 0x14, + (byte) 0x4d, (byte) 0x53, (byte) 0x55, (byte) 0x5d, + (byte) 0xd7, (byte) 0x75, (byte) 0x65, (byte) 0x19, + (byte) 0x98, (byte) 0xa6, (byte) 0x6a, (byte) 0xaa, + (byte) 0xaa, (byte) 0xeb, (byte) 0xca, (byte) 0xb2, + (byte) 0x2c, (byte) 0x03, (byte) 0x4c, (byte) 0x53, + (byte) 0x55, (byte) 0x5d, (byte) 0x57, (byte) 0x96, + (byte) 0x65, (byte) 0x19, (byte) 0xa0, (byte) 0xaa, + (byte) 0xae, (byte) 0xeb, (byte) 0xba, (byte) 0xb2, + (byte) 0x6c, (byte) 0xdb, (byte) 0x00, (byte) 0x55, + (byte) 0x75, (byte) 0x5d, (byte) 0xd7, (byte) 0x95, + (byte) 0x65, (byte) 0xdb, (byte) 0x06, (byte) 0xb8, + (byte) 0xae, (byte) 0xeb, (byte) 0xca, (byte) 0xb2, + (byte) 0x2c, (byte) 0xdb, (byte) 0x36, (byte) 0x00, + (byte) 0xd7, (byte) 0x95, (byte) 0x65, (byte) 0x59, + (byte) 0xb6, (byte) 0x6d, (byte) 0x01, (byte) 0x00, + (byte) 0x00, (byte) 0x07, (byte) 0x0e, (byte) 0x00, + (byte) 0x00, (byte) 0x01, (byte) 0x46, (byte) 0xd0, + (byte) 0x49, (byte) 0x46, (byte) 0x95, (byte) 0x45, + (byte) 0xd8, (byte) 0x68, (byte) 0xc2, (byte) 0x85, + (byte) 0x07, (byte) 0xa0, (byte) 0xd0, (byte) 0x90, + (byte) 0x15, (byte) 0x01, (byte) 0x40, (byte) 0x14, + (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x8c, + (byte) 0x52, (byte) 0x8a, (byte) 0x29, (byte) 0x65, + (byte) 0x18, (byte) 0x93, (byte) 0x50, (byte) 0x4a, + (byte) 0x09, (byte) 0x0d, (byte) 0x63, (byte) 0x52, + (byte) 0x4a, (byte) 0x2a, (byte) 0xa5, (byte) 0x92, + (byte) 0x92, (byte) 0x52, (byte) 0x4a, (byte) 0xa5, + (byte) 0x54, (byte) 0x12, (byte) 0x52, (byte) 0x4a, + (byte) 0xa9, (byte) 0x94, (byte) 0x4a, (byte) 0x4a, + (byte) 0x4a, (byte) 0x29, (byte) 0x95, (byte) 0x92, + (byte) 0x51, (byte) 0x4a, (byte) 0x29, (byte) 0xb5, + (byte) 0x96, (byte) 0x2a, (byte) 0x29, (byte) 0xa9, + (byte) 0x94, (byte) 0x94, (byte) 0x52, (byte) 0x25, + (byte) 0xa5, (byte) 0xa4, (byte) 0x92, (byte) 0x52, + (byte) 0x2a, (byte) 0x00, (byte) 0x00, (byte) 0xec, + (byte) 0xc0, (byte) 0x01, (byte) 0x00, (byte) 0xec, + (byte) 0xc0, (byte) 0x42, (byte) 0x28, (byte) 0x34, + (byte) 0x64, (byte) 0x25, (byte) 0x00, (byte) 0x90, + (byte) 0x07, (byte) 0x00, (byte) 0x40, (byte) 0x10, + (byte) 0x82, (byte) 0x14, (byte) 0x63, (byte) 0x8c, + (byte) 0x39, (byte) 0x27, (byte) 0xa5, (byte) 0x54, + (byte) 0x8a, (byte) 0x31, (byte) 0xe7, (byte) 0x9c, + (byte) 0x93, (byte) 0x52, (byte) 0x2a, (byte) 0xc5, + (byte) 0x98, (byte) 0x73, (byte) 0xce, (byte) 0x49, + (byte) 0x29, (byte) 0x19, (byte) 0x63, (byte) 0xcc, + (byte) 0x39, (byte) 0xe7, (byte) 0xa4, (byte) 0x94, + (byte) 0x8c, (byte) 0x31, (byte) 0xe6, (byte) 0x9c, + (byte) 0x73, (byte) 0x52, (byte) 0x4a, (byte) 0xc6, + (byte) 0x9c, (byte) 0x73, (byte) 0xce, (byte) 0x39, + (byte) 0x29, (byte) 0x25, (byte) 0x63, (byte) 0xce, + (byte) 0x39, (byte) 0xe7, (byte) 0x9c, (byte) 0x94, + (byte) 0xd2, (byte) 0x39, (byte) 0xe7, (byte) 0x9c, + (byte) 0x83, (byte) 0x50, (byte) 0x4a, (byte) 0x29, + (byte) 0xa5, (byte) 0x73, (byte) 0xce, (byte) 0x41, + (byte) 0x28, (byte) 0xa5, (byte) 0x94, (byte) 0x12, + (byte) 0x42, (byte) 0xe7, (byte) 0x20, (byte) 0x94, + (byte) 0x52, (byte) 0x4a, (byte) 0xe9, (byte) 0x9c, + (byte) 0x73, (byte) 0x10, (byte) 0x0a, (byte) 0x00, + (byte) 0x00, (byte) 0x2a, (byte) 0x70, (byte) 0x00, + (byte) 0x00, (byte) 0x08, (byte) 0xb0, (byte) 0x51, + (byte) 0x64, (byte) 0x73, (byte) 0x82, (byte) 0x91, + (byte) 0xa0, (byte) 0x42, (byte) 0x43, (byte) 0x56, + (byte) 0x02, (byte) 0x00, (byte) 0xa9, (byte) 0x00, + (byte) 0x00, (byte) 0x06, (byte) 0xc7, (byte) 0xb1, + (byte) 0x2c, (byte) 0x4d, (byte) 0xd3, (byte) 0x34, + (byte) 0xcf, (byte) 0x13, (byte) 0x45, (byte) 0x4b, + (byte) 0x92, (byte) 0x34, (byte) 0xcf, (byte) 0x13, + (byte) 0x3d, (byte) 0x4f, (byte) 0x14, (byte) 0x4d, + (byte) 0xd5, (byte) 0x92, (byte) 0x24, (byte) 0xcf, + (byte) 0x13, (byte) 0x45, (byte) 0xcf, (byte) 0x13, + (byte) 0x4d, (byte) 0x53, (byte) 0xe5, (byte) 0x79, + (byte) 0x9e, (byte) 0x28, (byte) 0x8a, (byte) 0xa2, + (byte) 0x68, (byte) 0x9a, (byte) 0xaa, (byte) 0x4a, + (byte) 0x14, (byte) 0x45, (byte) 0x4f, (byte) 0x14, + (byte) 0x45, (byte) 0xd1, (byte) 0x34, (byte) 0x55, + (byte) 0x95, (byte) 0x2c, (byte) 0x8b, (byte) 0xa2, + (byte) 0x69, (byte) 0x9a, (byte) 0xa6, (byte) 0xaa, + (byte) 0xba, (byte) 0x2e, (byte) 0x5b, (byte) 0x16, + (byte) 0x45, (byte) 0xd3, (byte) 0x34, (byte) 0x4d, + (byte) 0x55, (byte) 0x75, (byte) 0x5d, (byte) 0x98, + (byte) 0xa6, (byte) 0x28, (byte) 0xaa, (byte) 0xaa, + (byte) 0xeb, (byte) 0xca, (byte) 0x2e, (byte) 0x4c, + (byte) 0x53, (byte) 0x14, (byte) 0x4d, (byte) 0xd3, + (byte) 0x75, (byte) 0x65, (byte) 0x19, (byte) 0xb2, + (byte) 0xad, (byte) 0x9a, (byte) 0xaa, (byte) 0xea, + (byte) 0xba, (byte) 0xb2, (byte) 0x0d, (byte) 0xdb, + (byte) 0x36, (byte) 0x4d, (byte) 0x55, (byte) 0x75, + (byte) 0x5d, (byte) 0x59, (byte) 0x06, (byte) 0xae, + (byte) 0xeb, (byte) 0xba, (byte) 0xb2, (byte) 0x6c, + (byte) 0xeb, (byte) 0xc0, (byte) 0x75, (byte) 0x5d, + (byte) 0x57, (byte) 0x96, (byte) 0x6d, (byte) 0x5d, + (byte) 0x00, (byte) 0x00, (byte) 0x78, (byte) 0x82, + (byte) 0x03, (byte) 0x00, (byte) 0x50, (byte) 0x81, + (byte) 0x0d, (byte) 0xab, (byte) 0x23, (byte) 0x9c, + (byte) 0x14, (byte) 0x8d, (byte) 0x05, (byte) 0x16, + (byte) 0x1a, (byte) 0xb2, (byte) 0x12, (byte) 0x00, + (byte) 0xc8, (byte) 0x00, (byte) 0x00, (byte) 0x20, + (byte) 0x08, (byte) 0x41, (byte) 0x48, (byte) 0x29, + (byte) 0x85, (byte) 0x90, (byte) 0x52, (byte) 0x0a, + (byte) 0x21, (byte) 0xa5, (byte) 0x14, (byte) 0x42, + (byte) 0x4a, (byte) 0x29, (byte) 0x84, (byte) 0x04, + (byte) 0x00, (byte) 0x00, (byte) 0x0c, (byte) 0x38, + (byte) 0x00, (byte) 0x00, (byte) 0x04, (byte) 0x98, + (byte) 0x50, (byte) 0x06, (byte) 0x0a, (byte) 0x0d, + (byte) 0x59, (byte) 0x09, (byte) 0x00, (byte) 0xa4, + (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x10, + (byte) 0x42, (byte) 0x08, (byte) 0x21, (byte) 0x84, + (byte) 0x10, (byte) 0x42, (byte) 0x08, (byte) 0x21, + (byte) 0x84, (byte) 0x10, (byte) 0x42, (byte) 0x08, + (byte) 0x21, (byte) 0x84, (byte) 0x10, (byte) 0x42, + (byte) 0x08, (byte) 0x21, (byte) 0x84, (byte) 0x10, + (byte) 0x42, (byte) 0x08, (byte) 0x21, (byte) 0x84, + (byte) 0x10, (byte) 0x42, (byte) 0x08, (byte) 0x21, + (byte) 0x84, (byte) 0x10, (byte) 0x42, (byte) 0x08, + (byte) 0x21, (byte) 0x84, (byte) 0x10, (byte) 0x42, + (byte) 0x08, (byte) 0x21, (byte) 0x84, (byte) 0x10, + (byte) 0x42, (byte) 0x08, (byte) 0x21, (byte) 0x84, + (byte) 0x10, (byte) 0x42, (byte) 0x08, (byte) 0x21, + (byte) 0x84, (byte) 0xce, (byte) 0x39, (byte) 0xe7, + (byte) 0x9c, (byte) 0x73, (byte) 0xce, (byte) 0x39, + (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xce, + (byte) 0x39, (byte) 0xe7, (byte) 0x9c, (byte) 0x73, + (byte) 0xce, (byte) 0x39, (byte) 0xe7, (byte) 0x9c, + (byte) 0x73, (byte) 0xce, (byte) 0x39, (byte) 0xe7, + (byte) 0x9c, (byte) 0x73, (byte) 0xce, (byte) 0x39, + (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xce, + (byte) 0x39, (byte) 0xe7, (byte) 0x9c, (byte) 0x73, + (byte) 0xce, (byte) 0x39, (byte) 0xe7, (byte) 0x9c, + (byte) 0x73, (byte) 0xce, (byte) 0x39, (byte) 0xe7, + (byte) 0x9c, (byte) 0x73, (byte) 0xce, (byte) 0x39, + (byte) 0xe7, (byte) 0x9c, (byte) 0x73, (byte) 0xce, + (byte) 0x39, (byte) 0xe7, (byte) 0x9c, (byte) 0x73, + (byte) 0x02, (byte) 0x00, (byte) 0xb1, (byte) 0x2b, + (byte) 0x1c, (byte) 0x00, (byte) 0x76, (byte) 0x22, + (byte) 0x6c, (byte) 0x58, (byte) 0x1d, (byte) 0xe1, + (byte) 0xa4, (byte) 0x68, (byte) 0x2c, (byte) 0xb0, + (byte) 0xd0, (byte) 0x90, (byte) 0x95, (byte) 0x00, + (byte) 0x40, (byte) 0x38, (byte) 0x00, (byte) 0x00, + (byte) 0x60, (byte) 0x8c, (byte) 0x31, (byte) 0xce, + (byte) 0x59, (byte) 0xac, (byte) 0xb5, (byte) 0xd6, + (byte) 0x5a, (byte) 0x2b, (byte) 0xa5, (byte) 0x94, + (byte) 0x92, (byte) 0x50, (byte) 0x6b, (byte) 0xad, + (byte) 0xb5, (byte) 0xd6, (byte) 0x9a, (byte) 0x29, + (byte) 0xa4, (byte) 0x94, (byte) 0x84, (byte) 0x16, + (byte) 0x63, (byte) 0x8c, (byte) 0x31, (byte) 0xc6, + (byte) 0x98, (byte) 0x31, (byte) 0x08, (byte) 0x29, + (byte) 0xb5, (byte) 0x18, (byte) 0x63, (byte) 0x8c, + (byte) 0x31, (byte) 0xc6, (byte) 0x8c, (byte) 0x39, + (byte) 0x47, (byte) 0x2d, (byte) 0xc6, (byte) 0x18, + (byte) 0x63, (byte) 0x8c, (byte) 0x31, (byte) 0xb6, + (byte) 0x56, (byte) 0x4a, (byte) 0x6c, (byte) 0x31, + (byte) 0xc6, (byte) 0x18, (byte) 0x63, (byte) 0x8c, + (byte) 0xb1, (byte) 0xb5, (byte) 0x52, (byte) 0x62, + (byte) 0x8c, (byte) 0x31, (byte) 0xc6, (byte) 0x18, + (byte) 0x63, (byte) 0x8c, (byte) 0x31, (byte) 0xc6, + (byte) 0x16, (byte) 0x5b, (byte) 0x8c, (byte) 0x31, + (byte) 0xc6, (byte) 0x18, (byte) 0x63, (byte) 0x8c, + (byte) 0x31, (byte) 0xb6, (byte) 0x18, (byte) 0x63, + (byte) 0x8c, (byte) 0x31, (byte) 0xc6, (byte) 0x18, + (byte) 0x63, (byte) 0x8c, (byte) 0x31, (byte) 0xc6, + (byte) 0x18, (byte) 0x63, (byte) 0x8c, (byte) 0x31, + (byte) 0xc6, (byte) 0x18, (byte) 0x63, (byte) 0x8c, + (byte) 0x31, (byte) 0xb6, (byte) 0x18, (byte) 0x63, + (byte) 0x8c, (byte) 0x31, (byte) 0xc6, (byte) 0x18, + (byte) 0x63, (byte) 0x8c, (byte) 0x31, (byte) 0xc6, + (byte) 0x18, (byte) 0x63, (byte) 0x8c, (byte) 0x31, + (byte) 0xc6, (byte) 0x18, (byte) 0x63, (byte) 0x8c, + (byte) 0x31, (byte) 0xc6, (byte) 0x18, (byte) 0x63, + (byte) 0x8c, (byte) 0x31, (byte) 0xc6, (byte) 0x18, + (byte) 0x63, (byte) 0x6c, (byte) 0x31, (byte) 0xc6, + (byte) 0x18, (byte) 0x63, (byte) 0x8c, (byte) 0x31, + (byte) 0xc6, (byte) 0x18, (byte) 0x63, (byte) 0x8c, + (byte) 0x31, (byte) 0xc6, (byte) 0x18, (byte) 0x63, + (byte) 0x2c, (byte) 0x00, (byte) 0xc0, (byte) 0xe4, + (byte) 0xc1, (byte) 0x01, (byte) 0x00, (byte) 0x2a, + (byte) 0xc1, (byte) 0xc6, (byte) 0x19, (byte) 0x56, + (byte) 0x92, (byte) 0xce, (byte) 0x0a, (byte) 0x47, + (byte) 0x83, (byte) 0x0b, (byte) 0x0d, (byte) 0x59, + (byte) 0x09, (byte) 0x00, (byte) 0xe4, (byte) 0x06, + (byte) 0x00, (byte) 0x00, (byte) 0xc6, (byte) 0x28, + (byte) 0xc5, (byte) 0x98, (byte) 0x63, (byte) 0xce, + (byte) 0x41, (byte) 0x08, (byte) 0xa1, (byte) 0x94, + (byte) 0x12, (byte) 0x4a, (byte) 0x49, (byte) 0xad, + (byte) 0x75, (byte) 0xce, (byte) 0x39, (byte) 0x08, + (byte) 0x21, (byte) 0x94, (byte) 0x52, (byte) 0x4a, + (byte) 0x49, (byte) 0xa9, (byte) 0xb4, (byte) 0x94, + (byte) 0x62, (byte) 0xca, (byte) 0x98, (byte) 0x73, + (byte) 0xce, (byte) 0x41, (byte) 0x08, (byte) 0xa5, + (byte) 0x94, (byte) 0x12, (byte) 0x4a, (byte) 0x49, + (byte) 0xa9, (byte) 0xa5, (byte) 0xd4, (byte) 0x39, + (byte) 0xe7, (byte) 0x20, (byte) 0x94, (byte) 0x52, + (byte) 0x4a, (byte) 0x4a, (byte) 0x29, (byte) 0xa5, + (byte) 0x94, (byte) 0x5a, (byte) 0x6a, (byte) 0xad, + (byte) 0x73, (byte) 0x10, (byte) 0x42, (byte) 0x08, + (byte) 0xa5, (byte) 0x94, (byte) 0x52, (byte) 0x4a, + (byte) 0x4a, (byte) 0x29, (byte) 0xa5, (byte) 0xd4, + (byte) 0x52, (byte) 0x08, (byte) 0x21, (byte) 0x94, + (byte) 0x52, (byte) 0x4a, (byte) 0x2a, (byte) 0x29, + (byte) 0xa5, (byte) 0x94, (byte) 0x52, (byte) 0x6b, + (byte) 0xad, (byte) 0xa5, (byte) 0x10, (byte) 0x42, + (byte) 0x28, (byte) 0xa5, (byte) 0x94, (byte) 0x94, + (byte) 0x52, (byte) 0x4a, (byte) 0x29, (byte) 0xa5, + (byte) 0xd4, (byte) 0x5a, (byte) 0x8b, (byte) 0xa1, + (byte) 0x94, (byte) 0x90, (byte) 0x4a, (byte) 0x29, + (byte) 0x25, (byte) 0xa5, (byte) 0x94, (byte) 0x52, + (byte) 0x49, (byte) 0x2d, (byte) 0xb5, (byte) 0x96, + (byte) 0x5a, (byte) 0x2a, (byte) 0xa1, (byte) 0x94, + (byte) 0x54, (byte) 0x52, (byte) 0x4a, (byte) 0x29, + (byte) 0xa5, (byte) 0x94, (byte) 0x52, (byte) 0x6b, + (byte) 0xa9, (byte) 0xb5, (byte) 0x56, (byte) 0x4a, + (byte) 0x49, (byte) 0x25, (byte) 0xa5, (byte) 0x94, + (byte) 0x52, (byte) 0x4a, (byte) 0x29, (byte) 0xa5, + (byte) 0xd4, (byte) 0x62, (byte) 0x6b, (byte) 0x29, + (byte) 0x94, (byte) 0x92, (byte) 0x52, (byte) 0x49, + (byte) 0x29, (byte) 0xb5, (byte) 0x94, (byte) 0x52, + (byte) 0x4a, (byte) 0xad, (byte) 0xc5, (byte) 0xd8, + (byte) 0x62, (byte) 0x29, (byte) 0xad, (byte) 0xa4, + (byte) 0x94, (byte) 0x52, (byte) 0x4a, (byte) 0x29, + (byte) 0xa5, (byte) 0xd6, (byte) 0x52, (byte) 0x6c, + (byte) 0xad, (byte) 0xb5, (byte) 0xd8, (byte) 0x52, + (byte) 0x4a, (byte) 0x29, (byte) 0xa5, (byte) 0x96, + (byte) 0x5a, (byte) 0x4a, (byte) 0x29, (byte) 0xb5, + (byte) 0x16, (byte) 0x5b, (byte) 0x6a, (byte) 0x2d, + (byte) 0xa5, (byte) 0x94, (byte) 0x52, (byte) 0x4b, + (byte) 0x29, (byte) 0xa5, (byte) 0x96, (byte) 0x52, + (byte) 0x4b, (byte) 0x2d, (byte) 0xc6, (byte) 0xd6, + (byte) 0x5a, (byte) 0x4b, (byte) 0x29, (byte) 0xa5, + (byte) 0xd4, (byte) 0x52, (byte) 0x6a, (byte) 0xa9, + (byte) 0xa5, (byte) 0x94, (byte) 0x52, (byte) 0x6c, + (byte) 0xad, (byte) 0xb5, (byte) 0x98, (byte) 0x52, + (byte) 0x6a, (byte) 0x2d, (byte) 0xa5, (byte) 0xd4, + (byte) 0x52, (byte) 0x6b, (byte) 0x2d, (byte) 0xb5, + (byte) 0xd8, (byte) 0x52, (byte) 0x6a, (byte) 0x2d, + (byte) 0xb5, (byte) 0x94, (byte) 0x52, (byte) 0x6b, + (byte) 0xa9, (byte) 0xa5, (byte) 0x94, (byte) 0x5a, + (byte) 0x6b, (byte) 0x2d, (byte) 0xb6, (byte) 0xd8, + (byte) 0x5a, (byte) 0x6b, (byte) 0x29, (byte) 0xb5, + (byte) 0x94, (byte) 0x52, (byte) 0x4a, (byte) 0xa9, + (byte) 0xb5, (byte) 0x16, (byte) 0x5b, (byte) 0x8a, + (byte) 0xb1, (byte) 0xb5, (byte) 0xd4, (byte) 0x4a, + (byte) 0x4a, (byte) 0x29, (byte) 0xb5, (byte) 0xd4, + (byte) 0x5a, (byte) 0x6a, (byte) 0x2d, (byte) 0xb6, + (byte) 0x16, (byte) 0x5b, (byte) 0x6b, (byte) 0xad, + (byte) 0xa5, (byte) 0xd6, (byte) 0x5a, (byte) 0x6a, + (byte) 0x29, (byte) 0xa5, (byte) 0x16, (byte) 0x5b, + (byte) 0x8c, (byte) 0x31, (byte) 0xc6, (byte) 0x16, + (byte) 0x63, (byte) 0x6b, (byte) 0x31, (byte) 0xa5, + (byte) 0x94, (byte) 0x52, (byte) 0x4b, (byte) 0xa9, + (byte) 0xa5, (byte) 0x02, (byte) 0x00, (byte) 0x80, + (byte) 0x0e, (byte) 0x1c, (byte) 0x00, (byte) 0x00, + (byte) 0x02, (byte) 0x8c, (byte) 0xa8, (byte) 0xb4, + (byte) 0x10, (byte) 0x3b, (byte) 0xcd, (byte) 0xb8, + (byte) 0xf2, (byte) 0x08, (byte) 0x1c, (byte) 0x51, + (byte) 0xc8, (byte) 0x30, (byte) 0x01, (byte) 0x15, + (byte) 0x1a, (byte) 0xb2, (byte) 0x12, (byte) 0x00, + (byte) 0x20, (byte) 0x03, (byte) 0x00, (byte) 0x20, + (byte) 0x90, (byte) 0x69, (byte) 0x92, (byte) 0x39, + (byte) 0x49, (byte) 0xa9, (byte) 0x11, (byte) 0x26, + (byte) 0x39, (byte) 0xc5, (byte) 0xa0, (byte) 0x94, + (byte) 0xe6, (byte) 0x9c, (byte) 0x53, (byte) 0x4a, + (byte) 0x29, (byte) 0xa5, (byte) 0x34, (byte) 0x44, + (byte) 0x96, (byte) 0x64, (byte) 0x90, (byte) 0x62, + (byte) 0x50, (byte) 0x1d, (byte) 0x99, (byte) 0x8c, + (byte) 0x39, (byte) 0x49, (byte) 0x39, (byte) 0x43, + (byte) 0xa4, (byte) 0x31, (byte) 0xa4, (byte) 0x20, + (byte) 0xf5, (byte) 0x4c, (byte) 0x91, (byte) 0xc7, + (byte) 0x94, (byte) 0x62, (byte) 0x10, (byte) 0x43, + (byte) 0x48, (byte) 0x2a, (byte) 0x74, (byte) 0x8a, + (byte) 0x39, (byte) 0x6c, (byte) 0x35, (byte) 0xf9, + (byte) 0x58, (byte) 0x42, (byte) 0x07, (byte) 0xb1, + (byte) 0x06, (byte) 0x65, (byte) 0x8c, (byte) 0x70, + (byte) 0x29, (byte) 0xc5, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x08, (byte) 0x02, (byte) 0x00, + (byte) 0x04, (byte) 0x84, (byte) 0x04, (byte) 0x00, + (byte) 0x18, (byte) 0x20, (byte) 0x28, (byte) 0x98, + (byte) 0x01, (byte) 0x00, (byte) 0x06, (byte) 0x07, + (byte) 0x08, (byte) 0x23, (byte) 0x07, (byte) 0x02, + (byte) 0x1d, (byte) 0x01, (byte) 0x04, (byte) 0x0e, + (byte) 0x6d, (byte) 0x00, (byte) 0x80, (byte) 0x81, + (byte) 0x08, (byte) 0x99, (byte) 0x09, (byte) 0x0c, + (byte) 0x0a, (byte) 0xa1, (byte) 0xc1, (byte) 0x41, + (byte) 0x26, (byte) 0x00, (byte) 0x3c, (byte) 0x40, + (byte) 0x44, (byte) 0x48, (byte) 0x05, (byte) 0x00, + (byte) 0x89, (byte) 0x09, (byte) 0x8a, (byte) 0xd2, + (byte) 0x85, (byte) 0x2e, (byte) 0x08, (byte) 0x21, + (byte) 0x82, (byte) 0x74, (byte) 0x11, (byte) 0x64, + (byte) 0xf1, (byte) 0xc0, (byte) 0x85, (byte) 0x13, + (byte) 0x37, (byte) 0x9e, (byte) 0xb8, (byte) 0xe1, + (byte) 0x84, (byte) 0x0e, (byte) 0x6d, (byte) 0x20, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x20, (byte) 0x00, (byte) 0xf0, + (byte) 0x01, (byte) 0x00, (byte) 0x90, (byte) 0x50, + (byte) 0x00, (byte) 0x11, (byte) 0x11, (byte) 0xd1, + (byte) 0xcc, (byte) 0x55, (byte) 0x58, (byte) 0x5c, + (byte) 0x60, (byte) 0x64, (byte) 0x68, (byte) 0x6c, + (byte) 0x70, (byte) 0x74, (byte) 0x78, (byte) 0x7c, + (byte) 0x80, (byte) 0x84, (byte) 0x08, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x10, (byte) 0x00, (byte) 0x7c, (byte) 0x00, + (byte) 0x00, (byte) 0x24, (byte) 0x22, (byte) 0x40, + (byte) 0x44, (byte) 0x44, (byte) 0x34, (byte) 0x73, + (byte) 0x15, (byte) 0x16, (byte) 0x17, (byte) 0x18, + (byte) 0x19, (byte) 0x1a, (byte) 0x1b, (byte) 0x1c, + (byte) 0x1d, (byte) 0x1e, (byte) 0x1f, (byte) 0x20, + (byte) 0x21, (byte) 0x01, (byte) 0x00, (byte) 0x80, + (byte) 0x00, (byte) 0x02, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x20, (byte) 0x80, + (byte) 0x00, (byte) 0x04, (byte) 0x04, (byte) 0x04, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x02, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x04, (byte) 0x04 + }; + +} diff --git a/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/VorbisBitArrayTest.java b/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/VorbisBitArrayTest.java new file mode 100644 index 0000000000..954b7f39fc --- /dev/null +++ b/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/VorbisBitArrayTest.java @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2015 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.exoplayer.extractor.ogg; + +import com.google.android.exoplayer.ParserException; +import com.google.android.exoplayer.util.ParsableBitArray; + +import junit.framework.TestCase; + +/** + * Unit test for {@link VorbisBitArray}. + */ +public final class VorbisBitArrayTest extends TestCase { + + public void testReadBit() throws ParserException { + VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ + (byte) 0x5c, 0x50 + }); + + assertFalse(bitArray.readBit()); + assertFalse(bitArray.readBit()); + assertTrue(bitArray.readBit()); + assertTrue(bitArray.readBit()); + + assertTrue(bitArray.readBit()); + assertFalse(bitArray.readBit()); + assertTrue(bitArray.readBit()); + assertFalse(bitArray.readBit()); + + assertFalse(bitArray.readBit()); + assertFalse(bitArray.readBit()); + assertFalse(bitArray.readBit()); + assertFalse(bitArray.readBit()); + + assertTrue(bitArray.readBit()); + assertFalse(bitArray.readBit()); + assertTrue(bitArray.readBit()); + assertFalse(bitArray.readBit()); + + try { + assertFalse(bitArray.readBit()); + fail(); + } catch (IllegalStateException e) {/* ignored */} + } + + public void testSkipBits() throws ParserException { + VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ + (byte) 0xF0, 0x0F + }); + + bitArray.skipBits(10); + assertEquals(10, bitArray.getPosition()); + assertTrue(bitArray.readBit()); + assertTrue(bitArray.readBit()); + assertFalse(bitArray.readBit()); + bitArray.skipBits(1); + assertEquals(14, bitArray.getPosition()); + assertFalse(bitArray.readBit()); + assertFalse(bitArray.readBit()); + try { + bitArray.readBit(); + fail(); + } catch (IllegalStateException e) { + // ignored + } + } + + + public void testSkipBitsThrowsErrorIfEOB() throws ParserException { + VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ + (byte) 0xF0, 0x0F + }); + + try { + bitArray.skipBits(17); + fail(); + } catch (IllegalStateException e) {/* ignored */} + } + + public void testGetPosition() throws Exception { + VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ + (byte) 0xF0, 0x0F + }); + + assertEquals(0, bitArray.getPosition()); + bitArray.readBit(); + assertEquals(1, bitArray.getPosition()); + bitArray.readBit(); + bitArray.readBit(); + bitArray.skipBits(4); + assertEquals(7, bitArray.getPosition()); + } + + public void testSetPosition() throws Exception { + VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ + (byte) 0xF0, 0x0F + }); + + assertEquals(0, bitArray.getPosition()); + bitArray.setPosition(4); + assertEquals(4, bitArray.getPosition()); + + bitArray.setPosition(15); + assertFalse(bitArray.readBit()); + try { + bitArray.readBit(); + fail(); + } catch (IllegalStateException e) {/* ignored */} + + } + public void testSetPositionIllegalPositions() throws Exception { + VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ + (byte) 0xF0, 0x0F + }); + + try { + bitArray.setPosition(16); + fail(); + } catch (IllegalArgumentException e) { + assertEquals(0, bitArray.getPosition()); + } + + try { + bitArray.setPosition(-1); + fail(); + } catch (IllegalArgumentException e) { + assertEquals(0, bitArray.getPosition()); + } + } + + public void testReadInt32() throws ParserException { + byte[] data = {(byte) 0xF0, 0x0F, (byte) 0xF0, 0x0F}; + VorbisBitArray lsb = new VorbisBitArray(data); + assertEquals(0x0FF00FF0, lsb.readBits(32)); + + data = new byte[]{0x0F, (byte) 0xF0, 0x0F, (byte) 0xF0}; + lsb = new VorbisBitArray(data); + assertEquals(0xF00FF00F, lsb.readBits(32)); + } + + public void testReadBits() throws Exception { + VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ + (byte) 0x03, 0x22 + }); + + assertEquals(3, bitArray.readBits(2)); + bitArray.skipBits(6); + assertEquals(2, bitArray.readBits(2)); + bitArray.skipBits(2); + assertEquals(2, bitArray.readBits(2)); + + bitArray.reset(); + assertEquals(0x2203, bitArray.readBits(16)); + } + + public void testRead4BitsBeyondBoundary() throws Exception { + VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ + 0x2e, 0x10 + }); + assertEquals(0x2e, bitArray.readBits(7)); + assertEquals(7, bitArray.getPosition()); + assertEquals(0x0, bitArray.readBits(4)); + } + + public void testReadBitsBeyondByteBoundaries() throws Exception { + VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ + (byte) 0xFF, (byte) 0x0F, (byte) 0xFF, (byte) 0x0F + }); + + assertEquals(0x0FFF0FFF, bitArray.readBits(32)); + + bitArray.reset(); + bitArray.skipBits(4); + assertEquals(0xF0FF, bitArray.readBits(16)); + + bitArray.reset(); + bitArray.skipBits(6); + assertEquals(0xc3F, bitArray.readBits(12)); + + bitArray.reset(); + bitArray.skipBits(6); + assertTrue(bitArray.readBit()); + assertTrue(bitArray.readBit()); + assertEquals(24, bitArray.bitsLeft()); + + bitArray.reset(); + bitArray.skipBits(10); + assertEquals(3, bitArray.readBits(5)); + assertEquals(15, bitArray.getPosition()); + } + + public void testReadBitsIllegalLengths() throws Exception { + VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ + (byte) 0x03, 0x22, 0x30 + }); + + // reading zero bits gets 0 without advancing position + // (like a zero-bit read is defined to yield zer0) + assertEquals(0, bitArray.readBits(0)); + assertEquals(0, bitArray.getPosition()); + bitArray.readBit(); + assertEquals(1, bitArray.getPosition()); + + try { + bitArray.readBits(24); + fail(); + } catch (IllegalStateException e) { + assertEquals(1, bitArray.getPosition()); + } + } + + public void testLimit() throws ParserException { + VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ + (byte) 0xc0, 0x02 + }, 1); + + try { + bitArray.skipBits(9); + fail(); + } catch (IllegalStateException e) { + assertEquals(0, bitArray.getPosition()); + } + + try { + bitArray.readBits(9); + fail(); + } catch (IllegalStateException e) { + assertEquals(0, bitArray.getPosition()); + } + + bitArray.readBits(8); + assertEquals(8, bitArray.getPosition()); + try { + bitArray.readBit(); + fail(); + } catch (IllegalStateException e) { + assertEquals(8, bitArray.getPosition()); + } + } + + public void testBitsLeft() throws ParserException { + VorbisBitArray bitArray = new VorbisBitArray(new byte[]{ + (byte) 0xc0, 0x02 + }); + assertEquals(16, bitArray.bitsLeft()); + assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft()); + + bitArray.skipBits(1); + assertEquals(15, bitArray.bitsLeft()); + assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft()); + + bitArray.skipBits(3); + assertEquals(12, bitArray.bitsLeft()); + assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft()); + + bitArray.setPosition(6); + assertEquals(10, bitArray.bitsLeft()); + assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft()); + + bitArray.readBit(); + assertEquals(9, bitArray.bitsLeft()); + assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft()); + + bitArray.readBits(1); + assertEquals(8, bitArray.bitsLeft()); + assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft()); + + bitArray.readBits(4); + assertEquals(4, bitArray.bitsLeft()); + assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft()); + + bitArray.readBits(4); + assertEquals(0, bitArray.bitsLeft()); + assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft()); + + try { + bitArray.readBit(); + fail(); + } catch (IllegalStateException e) { + assertEquals(0, bitArray.bitsLeft()); + } + } + + public void testReadBitCompareWithMSb() throws ParserException { + byte[] data = {0x0F}; + VorbisBitArray lsb = new VorbisBitArray(data); + ParsableBitArray msb = new ParsableBitArray(data); + + assertEquals(lsb.readBit(), !msb.readBit()); + assertEquals(lsb.readBit(), !msb.readBit()); + assertEquals(lsb.readBit(), !msb.readBit()); + assertEquals(lsb.readBit(), !msb.readBit()); + assertEquals(lsb.readBit(), !msb.readBit()); + assertEquals(lsb.readBit(), !msb.readBit()); + assertEquals(lsb.readBit(), !msb.readBit()); + assertEquals(lsb.readBit(), !msb.readBit()); + } + + public void testReadBitsCompareWithMSb() throws ParserException { + byte[] data = {0x0F}; + VorbisBitArray lsb = new VorbisBitArray(data); + ParsableBitArray msb = new ParsableBitArray(data); + + assertEquals(15, lsb.readBits(4)); + assertEquals(lsb.readBits(4), msb.readBits(4)); + assertEquals(15, msb.readBits(4)); + } + + public void testReadBitsCompareWithMSbBeyondByteBoundary() throws ParserException { + byte[] data = {(byte) 0xF0, 0x0F}; + VorbisBitArray lsb = new VorbisBitArray(data); + ParsableBitArray msb = new ParsableBitArray(data); + + assertEquals(0x00, lsb.readBits(4)); + assertEquals(0x0F, msb.readBits(4)); + + assertEquals(0xFF, lsb.readBits(8)); + assertEquals(0x00, msb.readBits(8)); + + assertEquals(0x00, lsb.readBits(4)); + assertEquals(0x0F, msb.readBits(4)); + } + +} diff --git a/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/VorbisUtilTest.java b/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/VorbisUtilTest.java new file mode 100644 index 0000000000..7bd17fb65a --- /dev/null +++ b/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/VorbisUtilTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2015 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.exoplayer.extractor.ogg; + +import com.google.android.exoplayer.ParserException; +import com.google.android.exoplayer.util.ParsableByteArray; + +import junit.framework.TestCase; + +/** + * Unit test for {@link VorbisUtil}. + */ +public final class VorbisUtilTest extends TestCase { + + public void testILog() throws Exception { + assertEquals(0, VorbisUtil.iLog(0)); + assertEquals(1, VorbisUtil.iLog(1)); + assertEquals(2, VorbisUtil.iLog(2)); + assertEquals(2, VorbisUtil.iLog(3)); + assertEquals(3, VorbisUtil.iLog(4)); + assertEquals(3, VorbisUtil.iLog(5)); + assertEquals(4, VorbisUtil.iLog(8)); + + assertEquals(0, VorbisUtil.iLog(-1)); + assertEquals(0, VorbisUtil.iLog(-122)); + } + + public void testReadBits() throws Exception { + assertEquals(0, VorbisUtil.readBits((byte) 0x00, 2, 2)); + assertEquals(1, VorbisUtil.readBits((byte) 0x02, 1, 1)); + assertEquals(15, VorbisUtil.readBits((byte) 0xF0, 4, 4)); + assertEquals(1, VorbisUtil.readBits((byte) 0x80, 1, 7)); + } + + public void testReadIdHeader() throws Exception { + byte[] data = TestData.getIdentificationHeaderData(); + ParsableByteArray headerData = new ParsableByteArray(data, data.length); + + VorbisUtil.VorbisIdHeader vorbisIdHeader = + VorbisUtil.readVorbisIdentificationHeader(headerData); + + assertEquals(22050, vorbisIdHeader.sampleRate); + assertEquals(0, vorbisIdHeader.version); + assertTrue(vorbisIdHeader.framingFlag); + assertEquals(2, vorbisIdHeader.channels); + assertEquals(512, vorbisIdHeader.blockSize0); + assertEquals(1024, vorbisIdHeader.blockSize1); + assertEquals(-1, vorbisIdHeader.bitrateMax); + assertEquals(-1, vorbisIdHeader.bitrateMin); + assertEquals(66666, vorbisIdHeader.bitrateNominal); + assertEquals(66666, vorbisIdHeader.getApproximateBitrate()); + } + + public void testReadCommentHeader() throws ParserException { + byte[] data = TestData.getCommentHeaderDataUTF8(); + ParsableByteArray headerData = new ParsableByteArray(data, data.length); + + VorbisUtil.CommentHeader commentHeader = VorbisUtil.readVorbisCommentHeader(headerData); + assertEquals("Xiph.Org libVorbis I 20120203 (Omnipresent)", commentHeader.vendor); + assertEquals(3, commentHeader.comments.length); + assertEquals("ALBUM=รครถ", commentHeader.comments[0]); + assertEquals("TITLE=A sample song", commentHeader.comments[1]); + assertEquals("ARTIST=Google", commentHeader.comments[2]); + } + + public void testReadVorbisModes() throws ParserException { + byte[] data = TestData.getSetupHeaderData(); + ParsableByteArray headerData = new ParsableByteArray(data, data.length); + + VorbisUtil.Mode[] modes = VorbisUtil.readVorbisModes(headerData, 2); + + assertEquals(2, modes.length); + + assertEquals(false, modes[0].blockFlag); + assertEquals(0, modes[0].mapping); + assertEquals(0, modes[0].transformType); + assertEquals(0, modes[0].windowType); + + assertEquals(true, modes[1].blockFlag); + assertEquals(1, modes[1].mapping); + assertEquals(0, modes[1].transformType); + assertEquals(0, modes[1].windowType); + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java index e13bb16422..2033b7b4bc 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java @@ -154,6 +154,13 @@ public final class ExtractorSampleSource implements SampleSource, SampleSourceRe } catch (ClassNotFoundException e) { // Extractor not found. } + try { + DEFAULT_EXTRACTOR_CLASSES.add( + Class.forName("com.google.android.exoplayer.extractor.ogg.OggVorbisExtractor") + .asSubclass(Extractor.class)); + } catch (ClassNotFoundException e) { + // Extractor not found. + } } private final ExtractorHolder extractorHolder; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ogg/OggReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ogg/OggReader.java new file mode 100644 index 0000000000..b913aed4c9 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ogg/OggReader.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2015 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.exoplayer.extractor.ogg; + +import com.google.android.exoplayer.ParserException; +import com.google.android.exoplayer.extractor.ExtractorInput; +import com.google.android.exoplayer.util.Assertions; +import com.google.android.exoplayer.util.ParsableByteArray; +import com.google.android.exoplayer.util.Util; + +import java.io.IOException; + +/** + * Reads OGG packets from an {@link ExtractorInput}. + */ +/* package */ final class OggReader { + + private static final String CAPTURE_PATTERN_PAGE = "OggS"; + + private final PageHeader pageHeader = new PageHeader(); + private final ParsableByteArray headerArray = new ParsableByteArray(27 + 255); + + private int currentSegmentIndex = -1; + + /** + * Resets this reader. + */ + public void reset() { + pageHeader.reset(); + headerArray.reset(); + currentSegmentIndex = -1; + } + + /** + * 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. + * @param packetArray the {@link ParsableByteArray} to write the packet data into. + * @return {@code true} if the read was successful. {@code false} if the end of the input was + * encountered having read no data. + * @throws IOException thrown if reading from the input fails. + * @throws InterruptedException thrown if interrupted while reading from input. + */ + public boolean readPacket(ExtractorInput input, ParsableByteArray packetArray) + throws IOException, InterruptedException { + Assertions.checkState(input != null && packetArray != null); + + boolean packetComplete = false; + while (!packetComplete) { + if (currentSegmentIndex < 0) { + // We're at the start of a page. + if (!populatePageHeader(input, pageHeader, headerArray, false)) { + return false; + } + currentSegmentIndex = 0; + } + + int packetSize = 0; + int segmentIndex = currentSegmentIndex; + // add up packetSize from laces + while (segmentIndex < pageHeader.pageSegmentCount) { + int segmentLength = pageHeader.laces[segmentIndex++]; + packetSize += segmentLength; + if (segmentLength != 255) { + // packets end at first lace < 255 + break; + } + } + + if (packetSize > 0) { + input.readFully(packetArray.data, packetArray.limit(), packetSize); + packetArray.setLimit(packetArray.limit() + packetSize); + packetComplete = pageHeader.laces[segmentIndex - 1] != 255; + } + // advance now since we are sure reading didn't throw an exception + currentSegmentIndex = segmentIndex == pageHeader.pageSegmentCount ? -1 : segmentIndex; + } + return true; + } + + /** + * Returns the {@link OggReader.PageHeader} of the current page. The header might not have been + * populated if the first packet has yet to be read. + *

+ * Note that there is only a single instance of {@code OggReader.PageHeader} which is mutable. + * The value of the fields might be changed by the reader when reading the stream advances and + * the next page is read (which implies reading and populating the next header). + * + * @return the {@code PageHeader} of the current page or {@code null}. + */ + public PageHeader getPageHeader() { + return pageHeader; + } + + /** + * Reads/peeks an Ogg page header and stores the data in the {@code header} object passed + * as argument. + * + * @param input the {@link ExtractorInput} to read from. + * @param header the {@link PageHeader} to read from. + * @param scratch a scratch array temporary use. + * @param peek pass {@code true} if data should only be peeked from current peek position. + * @return {@code true} if the read was successful. {@code false} if the end of the + * input was encountered having read no data. + * @throws IOException thrown if reading data fails or the stream is invalid. + * @throws InterruptedException thrown if thread is interrupted when reading/peeking. + */ + public static boolean populatePageHeader(ExtractorInput input, PageHeader header, + ParsableByteArray scratch, boolean peek) throws IOException, InterruptedException { + + scratch.reset(); + header.reset(); + if (!input.peekFully(scratch.data, 0, 27, true)) { + return false; + } + if (scratch.readUnsignedInt() != Util.getIntegerCodeForString(CAPTURE_PATTERN_PAGE)) { + throw new ParserException("expected OggS capture pattern at begin of page"); + } + + header.revision = scratch.readUnsignedByte(); + if (header.revision != 0x00) { + throw new ParserException("unsupported bit stream revision"); + } + header.type = scratch.readUnsignedByte(); + + header.granulePosition = scratch.readLittleEndianLong(); + header.streamSerialNumber = scratch.readLittleEndianUnsignedInt(); + header.pageSequenceNumber = scratch.readLittleEndianUnsignedInt(); + header.pageChecksum = scratch.readLittleEndianUnsignedInt(); + header.pageSegmentCount = scratch.readUnsignedByte(); + + scratch.reset(); + // calculate total size of header including laces + header.headerSize = 27 + header.pageSegmentCount; + input.peekFully(scratch.data, 0, header.pageSegmentCount); + for (int i = 0; i < header.pageSegmentCount; i++) { + header.laces[i] = scratch.readUnsignedByte(); + header.bodySize += header.laces[i]; + } + if (!peek) { + input.skipFully(header.headerSize); + } + return true; + } + + /** + * Data object to store header information. Be aware that {@code laces.length} is always 255. + * Instead use {@code pageSegmentCount} to iterate. + */ + public static final class PageHeader { + + public int revision; + public int type; + public long granulePosition; + public long streamSerialNumber; + public long pageSequenceNumber; + public long pageChecksum; + public int pageSegmentCount; + public int headerSize; + public int bodySize; + public int[] laces = new int[255]; + + /** + * Resets all primitive member fields to zero. + */ + public void reset() { + revision = 0; + type = 0; + granulePosition = 0; + streamSerialNumber = 0; + pageSequenceNumber = 0; + pageChecksum = 0; + pageSegmentCount = 0; + headerSize = 0; + bodySize = 0; + } + + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ogg/OggVorbisExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/ogg/OggVorbisExtractor.java new file mode 100644 index 0000000000..b96b7d1146 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ogg/OggVorbisExtractor.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2015 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.exoplayer.extractor.ogg; + +import com.google.android.exoplayer.C; +import com.google.android.exoplayer.MediaFormat; +import com.google.android.exoplayer.ParserException; +import com.google.android.exoplayer.extractor.Extractor; +import com.google.android.exoplayer.extractor.ExtractorInput; +import com.google.android.exoplayer.extractor.ExtractorOutput; +import com.google.android.exoplayer.extractor.PositionHolder; +import com.google.android.exoplayer.extractor.SeekMap; +import com.google.android.exoplayer.extractor.TrackOutput; +import com.google.android.exoplayer.extractor.ogg.VorbisUtil.Mode; +import com.google.android.exoplayer.util.MimeTypes; +import com.google.android.exoplayer.util.ParsableByteArray; + +import android.util.Log; + +import java.io.IOException; +import java.util.ArrayList; + +/** + * {@link Extractor} to extract Vorbis data out of Ogg byte stream. + */ +public final class OggVorbisExtractor implements Extractor { + + private static final String TAG = "OggVorbisExtractor"; + + private static final int OGG_MAX_SEGMENT_SIZE = 255; + + private final ParsableByteArray scratch = new ParsableByteArray( + new byte[OGG_MAX_SEGMENT_SIZE * 255], 0); + private final OggReader oggReader = new OggReader(); + + private TrackOutput trackOutput; + private VorbisSetup vorbisSetup; + private int previousPacketBlockSize; + private long elapsedSamples; + private boolean seenFirstAudioPacket; + + private VorbisUtil.VorbisIdHeader vorbisIdHeader; + private VorbisUtil.CommentHeader commentHeader; + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + try { + OggReader.PageHeader header = new OggReader.PageHeader(); + OggReader.populatePageHeader(input, header, scratch, true); + if ((header.type & 0x02) != 0x02) { + throw new ParserException("expected page to be first page of a logical stream"); + } + input.resetPeekPosition(); + } catch (ParserException e) { + Log.e(TAG, e.getMessage()); + return false; + } + return true; + } + + @Override + public void init(ExtractorOutput output) { + trackOutput = output.track(0); + output.endTracks(); + output.seekMap(SeekMap.UNSEEKABLE); + } + + @Override + public void seek() { + oggReader.reset(); + previousPacketBlockSize = -1; + elapsedSamples = 0; + seenFirstAudioPacket = false; + scratch.reset(); + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + + if (vorbisSetup == null) { + vorbisSetup = readSetupHeaders(input, scratch); + ArrayList codecInitialisationData = new ArrayList<>(); + codecInitialisationData.clear(); + codecInitialisationData.add(vorbisSetup.idHeader.data); + codecInitialisationData.add(vorbisSetup.setupHeaderData); + + long duration = input.getLength() == C.LENGTH_UNBOUNDED ? C.UNKNOWN_TIME_US + : input.getLength() * 8000000 / vorbisSetup.idHeader.getApproximateBitrate(); + trackOutput.format(MediaFormat.createAudioFormat(null, MimeTypes.AUDIO_VORBIS, + this.vorbisSetup.idHeader.bitrateNominal, OGG_MAX_SEGMENT_SIZE * 255, duration, + this.vorbisSetup.idHeader.channels, (int) this.vorbisSetup.idHeader.sampleRate, + codecInitialisationData, null)); + } + if (oggReader.readPacket(input, scratch)) { + // if this is an audio packet... + if ((scratch.data[0] & 0x01) != 1) { + // ... we need to decode the block size + int packetBlockSize = decodeBlockSize(scratch.data[0], vorbisSetup); + // a packet contains samples produced from overlapping the previous and current frame data + // (https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-350001.3.2) + int samplesInPacket = seenFirstAudioPacket ? (packetBlockSize + previousPacketBlockSize) / 4 + : 0; + // codec expects the number of samples appended to audio data + appendNumberOfSamples(scratch, samplesInPacket); + + // calculate time and send audio data to codec + long timeUs = elapsedSamples * C.MICROS_PER_SECOND / vorbisSetup.idHeader.sampleRate; + trackOutput.sampleData(scratch, scratch.limit()); + trackOutput.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, scratch.limit(), 0, null); + + // update state in members for next iteration + seenFirstAudioPacket = true; + elapsedSamples += samplesInPacket; + previousPacketBlockSize = packetBlockSize; + } + scratch.reset(); + return RESULT_CONTINUE; + } + return RESULT_END_OF_INPUT; + } + + //@VisibleForTesting + /* package */ VorbisSetup readSetupHeaders(ExtractorInput input, ParsableByteArray scratch) + throws IOException, InterruptedException { + + if (vorbisIdHeader == null) { + oggReader.readPacket(input, scratch); + vorbisIdHeader = VorbisUtil.readVorbisIdentificationHeader(scratch); + scratch.reset(); + } + + if (commentHeader == null) { + oggReader.readPacket(input, scratch); + commentHeader = VorbisUtil.readVorbisCommentHeader(scratch); + scratch.reset(); + } + + oggReader.readPacket(input, scratch); + // the third packet contains the setup header + byte[] setupHeaderData = new byte[scratch.limit()]; + // raw data of vorbis setup header has to be passed to decoder as CSD buffer #2 + System.arraycopy(scratch.data, 0, setupHeaderData, 0, scratch.limit()); + // partially decode setup header to get the modes + Mode[] modes = VorbisUtil.readVorbisModes(scratch, vorbisIdHeader.channels); + // we need the ilog of modes all the time when extracting, so we compute it once + int iLogModes = VorbisUtil.iLog(modes.length - 1); + scratch.reset(); + + return new VorbisSetup(vorbisIdHeader, commentHeader, setupHeaderData, modes, iLogModes); + } + + //@VisibleForTesting + /* package */ static void appendNumberOfSamples(ParsableByteArray buffer, + long packetSampleCount) { + + buffer.setLimit(buffer.limit() + 4); + // The vorbis decoder expects the number of samples in the packet + // to be appended to the audio data as an int32 + buffer.data[buffer.limit() - 4] = (byte) ((packetSampleCount) & 0xFF); + buffer.data[buffer.limit() - 3] = (byte) ((packetSampleCount >>> 8) & 0xFF); + buffer.data[buffer.limit() - 2] = (byte) ((packetSampleCount >>> 16) & 0xFF); + buffer.data[buffer.limit() - 1] = (byte) ((packetSampleCount >>> 24) & 0xFF); + } + + private static int decodeBlockSize(byte firstByteOfAudioPacket, VorbisSetup vorbisSetup) { + // read modeNumber (https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-730004.3.1) + int modeNumber = VorbisUtil.readBits(firstByteOfAudioPacket, vorbisSetup.iLogModes, 1); + int currentBlockSize; + if (!vorbisSetup.modes[modeNumber].blockFlag) { + currentBlockSize = vorbisSetup.idHeader.blockSize0; + } else { + currentBlockSize = vorbisSetup.idHeader.blockSize1; + } + return currentBlockSize; + } + + /** + * Class to hold all data read from Vorbis setup headers. + */ + /* package */ static final class VorbisSetup { + + public final VorbisUtil.VorbisIdHeader idHeader; + public final VorbisUtil.CommentHeader commentHeader; + public final byte[] setupHeaderData; + public final Mode[] modes; + public final int iLogModes; + + public VorbisSetup(VorbisUtil.VorbisIdHeader idHeader, VorbisUtil.CommentHeader + commentHeader, byte[] setupHeaderData, Mode[] modes, int iLogModes) { + this.idHeader = idHeader; + this.commentHeader = commentHeader; + this.setupHeaderData = setupHeaderData; + this.modes = modes; + this.iLogModes = iLogModes; + } + + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ogg/VorbisBitArray.java b/library/src/main/java/com/google/android/exoplayer/extractor/ogg/VorbisBitArray.java new file mode 100644 index 0000000000..95ae0eb789 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ogg/VorbisBitArray.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2015 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.exoplayer.extractor.ogg; + +import com.google.android.exoplayer.util.Assertions; + +/** + * Wraps a byte array, providing methods that allow it to be read as a vorbis bitstream. + * + * @see Vorbis bitpacking + * specification + */ +/* package */ final class VorbisBitArray { + + public final byte[] data; + private int limit; + private int byteOffset; + private int bitOffset; + + /** + * Creates a new instance that wraps an existing array. + * + * @param data the array to wrap. + */ + public VorbisBitArray(byte[] data) { + this(data, data.length); + } + + /** + * Creates a new instance that wraps an existing array. + * + * @param data the array to wrap. + * @param limit the limit in bytes. + */ + public VorbisBitArray(byte[] data, int limit) { + this.data = data; + this.limit = limit * 8; + } + + /** Resets the reading position to zero. */ + public void reset() { + byteOffset = 0; + bitOffset = 0; + } + + /** + * Reads a single bit. + * + * @return {@code true} if the bit is set, {@code false} otherwise. + */ + public boolean readBit() { + return readBits(1) == 1; + } + + /** + * Reads up to 32 bits. + * + * @param numBits The number of bits to read. + * @return An int whose bottom {@code numBits} bits hold the read data. + */ + public int readBits(int numBits) { + Assertions.checkState(getPosition() + numBits <= limit); + if (numBits == 0) { + return 0; + } + int result = 0; + int bitCount = 0; + if (bitOffset != 0) { + bitCount = Math.min(numBits, 8 - bitOffset); + int mask = 0xFF >>> (8 - bitCount); + result = (data[byteOffset] >>> bitOffset) & mask; + bitOffset += bitCount; + if (bitOffset == 8) { + byteOffset++; + bitOffset = 0; + } + } + + if (numBits - bitCount > 7) { + int numBytes = (numBits - bitCount) / 8; + for (int i = 0; i < numBytes; i++) { + result |= (data[byteOffset++] & 0xFFL) << bitCount; + bitCount += 8; + } + } + + if (numBits > bitCount) { + int bitsOnNextByte = numBits - bitCount; + int mask = 0xFF >>> (8 - bitsOnNextByte); + result |= (data[byteOffset] & mask) << bitCount; + bitOffset += bitsOnNextByte; + } + return result; + } + + /** + * Skips {@code numberOfBits} bits. + * + * @param numberOfBits the number of bits to skip. + */ + public void skipBits(int numberOfBits) { + Assertions.checkState(getPosition() + numberOfBits <= limit); + byteOffset += numberOfBits / 8; + bitOffset += numberOfBits % 8; + if (bitOffset > 7) { + byteOffset++; + bitOffset -= 8; + } + } + + /** + * Gets the current reading position in bits. + * + * @return the current reading position in bits. + */ + public int getPosition() { + return byteOffset * 8 + bitOffset; + } + + /** + * Sets the index of the current reading position in bits. + * + * @param position the new reading position in bits. + */ + public void setPosition(int position) { + Assertions.checkArgument(position < limit && position >= 0); + byteOffset = position / 8; + bitOffset = position - (byteOffset * 8); + } + + /** + * Gets the number of remaining bits. + * + * @return number of remaining bits. + */ + public int bitsLeft() { + return limit - getPosition(); + } + + /** + * Returns the limit in bits. + * + * @return the limit in bits. + **/ + public int limit() { + return limit; + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ogg/VorbisUtil.java b/library/src/main/java/com/google/android/exoplayer/extractor/ogg/VorbisUtil.java new file mode 100644 index 0000000000..8d0e7892a8 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ogg/VorbisUtil.java @@ -0,0 +1,483 @@ +/* + * Copyright (C) 2015 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.exoplayer.extractor.ogg; + +import com.google.android.exoplayer.ParserException; +import com.google.android.exoplayer.util.ParsableByteArray; + +import android.util.Log; + +import java.util.Arrays; + +/** + * Utility methods for parsing vorbis streams. + */ +/* package */ final class VorbisUtil { + + private static final String TAG = "VorbisUtil"; + + /** + * Returns ilog(x), which is the index of the highest set bit in {@code x}. + * + * @see + * Vorbis spec + * @param x the value of which the ilog should be calculated. + * @return ilog(x) + */ + public static int iLog(int x) { + int val = 0; + while (x > 0) { + val++; + x >>>= 1; + } + return val; + } + + /** + * Reads an int of {@code length} bits from {@code src} starting at + * {@code leastSignificantBitIndex}. + * + * @param src the {@code byte} to read from. + * @param length the length in bits of the int to read. + * @param leastSignificantBitIndex the index of the least significant bit of the int to read. + * @return the int value read. + */ + public static int readBits(byte src, int length, int leastSignificantBitIndex) { + return (src >> leastSignificantBitIndex) & (255 >>> (8 - length)); + } + + /** + * Reads a vorbis identification header from {@code headerData}. + * + * @see Vorbis + * spec/Identification header + * @param headerData a {@link ParsableByteArray} wrapping the header data. + * @return a {@link VorbisUtil.VorbisIdHeader} with meta data. + * @throws ParserException thrown if invalid capture pattern is detected. + */ + public static VorbisIdHeader readVorbisIdentificationHeader(ParsableByteArray headerData) + throws ParserException { + + captureVorbisHeader(0x01, headerData); + + long version = headerData.readLittleEndianUnsignedInt(); + int channels = headerData.readUnsignedByte(); + long sampleRate = headerData.readLittleEndianUnsignedInt(); + int bitrateMax = headerData.readLittleEndianInt(); + int bitrateNominal = headerData.readLittleEndianInt(); + int bitrateMin = headerData.readLittleEndianInt(); + + int blockSize = headerData.readUnsignedByte(); + int blockSize0 = (int) Math.pow(2, blockSize & 0x0F); + int blockSize1 = (int) Math.pow(2, (blockSize & 0xF0) >> 4); + + boolean framingFlag = (headerData.readUnsignedByte() & 0x01) > 0; + // raw data of vorbis setup header has to be passed to decoder as CSD buffer #1 + byte[] data = Arrays.copyOf(headerData.data, headerData.limit()); + + return new VorbisIdHeader(version, channels, sampleRate, bitrateMax, bitrateNominal, bitrateMin, + blockSize0, blockSize1, framingFlag, data); + } + + /** + * Reads a vorbis comment header. + * + * @see + * Vorbis spec/Comment header + * @param headerData a {@link ParsableByteArray} wrapping the header data. + * @return a {@link VorbisUtil.CommentHeader} with all the comments. + * @throws ParserException thrown if invalid capture pattern is detected. + */ + public static CommentHeader readVorbisCommentHeader(ParsableByteArray headerData) + throws ParserException { + + int length = captureVorbisHeader(0x03, headerData); + + int len = (int) headerData.readLittleEndianUnsignedInt(); + length += 4; + String vendor = headerData.readString(len); + length += vendor.length(); + + long commentListLen = headerData.readLittleEndianUnsignedInt(); + String[] comments = new String[(int) commentListLen]; + length += 4; + for (int i = 0; i < commentListLen; i++) { + len = (int) headerData.readLittleEndianUnsignedInt(); + length += 4; + comments[i] = headerData.readString(len); + length += comments[i].length(); + } + if ((headerData.readUnsignedByte() & 0x01) == 0) { + throw new ParserException("framing bit expected to be set"); + } + length += 1; + return new CommentHeader(vendor, comments, length); + } + + private static int captureVorbisHeader(int headerType, ParsableByteArray idHeader) + throws ParserException { + if (idHeader.readUnsignedByte() != headerType) { + throw new ParserException("expected header type " + Integer.toHexString(headerType)); + } + + if (!(idHeader.readUnsignedByte() == 'v' + && idHeader.readUnsignedByte() == 'o' + && idHeader.readUnsignedByte() == 'r' + && idHeader.readUnsignedByte() == 'b' + && idHeader.readUnsignedByte() == 'i' + && idHeader.readUnsignedByte() == 's')) { + throw new ParserException("expected characters 'vorbis'"); + } + return 7; // bytes read + } + + /** + * This method reads the modes which are located at the very end of the vorbis setup header. + * That's why we need to partially decode or at least read the entire setup header to know + * where to start reading the modes. + * + * @see + * Vorbis spec/Setup header + * @param headerData a {@link ParsableByteArray} containing setup header data. + * @param channels the number of channels. + * @return an array of {@link Mode}s. + * @throws ParserException thrown if bit stream is invalid. + */ + public static Mode[] readVorbisModes(ParsableByteArray headerData, int channels) + throws ParserException { + + captureVorbisHeader(0x05, headerData); + + int numberOfBooks = headerData.readUnsignedByte() + 1; + + VorbisBitArray bitArray = new VorbisBitArray(headerData.data); + bitArray.skipBits(headerData.getPosition() * 8); + + for (int i = 0; i < numberOfBooks; i++) { + readBook(bitArray); + } + + int timeCount = bitArray.readBits(6) + 1; + for (int i = 0; i < timeCount; i++) { + if (bitArray.readBits(16) != 0x00) { + throw new ParserException("placeholder of time domain transforms not zeroed out"); + } + } + readFloors(bitArray); + readResidues(bitArray); + readMappings(channels, bitArray); + + Mode[] modes = readModes(bitArray); + if (!bitArray.readBit()) { + throw new ParserException("framing bit after modes not set as expected"); + } + return modes; + } + + private static Mode[] readModes(VorbisBitArray bitArray) throws ParserException { + int modeCount = bitArray.readBits(6) + 1; + Mode[] modes = new Mode[modeCount]; + for (int i = 0; i < modeCount; i++) { + boolean blockFlag = bitArray.readBit(); + int windowType = bitArray.readBits(16); + int transformType = bitArray.readBits(16); + int mapping = bitArray.readBits(8); + modes[i] = new Mode(blockFlag, windowType, transformType, mapping); + } + return modes; + } + + private static void readMappings(int channels, VorbisBitArray bitArray) + throws ParserException { + int mappingsCount = bitArray.readBits(6) + 1; + for (int i = 0; i < mappingsCount; i++) { + int mappingType = bitArray.readBits(16); + switch (mappingType) { + case 0: + int submaps; + if (bitArray.readBit()) { + submaps = bitArray.readBits(4) + 1; + } else { + submaps = 1; + } + int couplingSteps; + if (bitArray.readBit()) { + couplingSteps = bitArray.readBits(8) + 1; + for (int j = 0; j < couplingSteps; j++) { + bitArray.skipBits(iLog(channels - 1)); // magnitude + bitArray.skipBits(iLog(channels - 1)); // angle + } + } /*else { + couplingSteps = 0; + }*/ + if (bitArray.readBits(2) != 0x00) { + throw new ParserException("to reserved bits must be zero after mapping coupling steps"); + } + if (submaps > 1) { + for (int j = 0; j < channels; j++) { + bitArray.skipBits(4); // mappingMux + } + } + for (int j = 0; j < submaps; j++) { + bitArray.skipBits(8); // discard + bitArray.skipBits(8); // submapFloor + bitArray.skipBits(8); // submapResidue + } + break; + default: + Log.e(TAG, "mapping type other than 0 not supported: " + mappingType); + } + } + } + + private static void readResidues(VorbisBitArray bitArray) throws ParserException { + int residueCount = bitArray.readBits(6) + 1; + for (int i = 0; i < residueCount; i++) { + int residueType = bitArray.readBits(16); + if (residueType > 2) { + throw new ParserException("residueType greater than 2 is not decodable"); + } else { + bitArray.skipBits(24); // begin + bitArray.skipBits(24); // end + bitArray.skipBits(24); // partitionSize (add one) + int classifications = bitArray.readBits(6) + 1; + bitArray.skipBits(8); // classbook + int[] cascade = new int[classifications]; + for (int j = 0; j < classifications; j++) { + int highBits = 0; + int lowBits = bitArray.readBits(3); + if (bitArray.readBit()) { + highBits = bitArray.readBits(5); + } + cascade[j] = highBits * 8 + lowBits; + } + for (int j = 0; j < classifications; j++) { + for (int k = 0; k < 8; k++) { + if ((cascade[j] & (0x01 << k)) != 0) { + bitArray.skipBits(8); // discard + } + } + } + } + } + } + + private static void readFloors(VorbisBitArray bitArray) throws ParserException { + int floorCount = bitArray.readBits(6) + 1; + for (int i = 0; i < floorCount; i++) { + int floorType = bitArray.readBits(16); + switch (floorType) { + case 0: + bitArray.skipBits(8); //order + bitArray.skipBits(16); // rate + bitArray.skipBits(16); // barkMapSize + bitArray.skipBits(6); // amplitudeBits + bitArray.skipBits(8); // amplitudeOffset + int floorNumberOfBooks = bitArray.readBits(4) + 1; + for (int j = 0; j < floorNumberOfBooks; j++) { + bitArray.skipBits(8); + } + break; + case 1: + int partitions = bitArray.readBits(5); + int maximumClass = -1; + int[] partitionClassList = new int[partitions]; + for (int j = 0; j < partitions; j++) { + partitionClassList[j] = bitArray.readBits(4); + if (partitionClassList[j] > maximumClass) { + maximumClass = partitionClassList[j]; + } + } + int[] classDimensions = new int[maximumClass + 1]; + for (int j = 0; j < classDimensions.length; j++) { + classDimensions[j] = bitArray.readBits(3) + 1; + int classSubclasses = bitArray.readBits(2); + if (classSubclasses > 0) { + bitArray.skipBits(8); // classMasterbooks + } + for (int k = 0; k < (1 << classSubclasses); k++) { + bitArray.skipBits(8); // subclassBook (subtract 1) + } + } + bitArray.skipBits(2); // multiplier (add one) + int rangeBits = bitArray.readBits(4); + int count = 0; + for (int j = 0, k = 0; j < partitions; j++) { + int idx = partitionClassList[j]; + count += classDimensions[idx]; + for (; k < count; k++) { + bitArray.skipBits(rangeBits); // floorValue + } + } + break; + default: + throw new ParserException("floor type greater than 1 not decodable: " + floorType); + } + } + } + + private static CodeBook readBook(VorbisBitArray bitArray) throws ParserException { + if (bitArray.readBits(24) != 0x564342) { + throw new ParserException("expected code book to start with [0x56, 0x43, 0x42] at " + + bitArray.getPosition()); + } + int dimensions = bitArray.readBits(16); + int entries = bitArray.readBits(24); + long[] lengthMap = new long[entries]; + + boolean isOrdered = bitArray.readBit(); + if (!isOrdered) { + boolean isSparse = bitArray.readBit(); + for (int i = 0; i < lengthMap.length; i++) { + if (isSparse) { + if (bitArray.readBit()) { + lengthMap[i] = bitArray.readBits(5) + 1; + } else { // entry unused + lengthMap[i] = 0; + } + } else { // not sparse + lengthMap[i] = bitArray.readBits(5) + 1; + } + } + } else { + int length = bitArray.readBits(5) + 1; + for (int i = 0; i < lengthMap.length;) { + int num = bitArray.readBits(iLog(entries - i)); + for (int j = 0; j < num && i < lengthMap.length; i++, j++) { + lengthMap[i] = length; + } + length++; + } + } + + int lookupType = bitArray.readBits(4); + if (lookupType > 2) { + throw new ParserException("lookup type greater than 2 not decodable: " + lookupType); + } else if (lookupType == 1 || lookupType == 2) { + bitArray.skipBits(32); // minimumValue + bitArray.skipBits(32); // deltaValue + int valueBits = bitArray.readBits(4) + 1; + bitArray.skipBits(1); // sequenceP + long lookupValuesCount; + if (lookupType == 1) { + if (dimensions != 0) { + lookupValuesCount = mapType1QuantValues(entries, dimensions); + } else { + // TODO no sample file found yet + lookupValuesCount = 0; + } + } else { + // TODO no sample file found yet + lookupValuesCount = entries * dimensions; + } + // discard (no decoding required yet) + bitArray.skipBits((int) (lookupValuesCount * valueBits)); + } + return new CodeBook(dimensions, entries, lengthMap, lookupType, isOrdered); + } + + /** + * @see function + * _book_maptype1_quantvals of libvorbis. + */ + private static long mapType1QuantValues(long entries, long dimension) { + return (long) Math.floor(Math.pow((double) entries, 1.d / dimension)); + } + + public static final class CodeBook { + + public final int dimensions; + public final int entries; + public final long[] lengthMap; + public final int lookupType; + public final boolean isOrdered; + + public CodeBook(int dimensions, int entries, long[] lengthMap, int lookupType, + boolean isOrdered) { + this.dimensions = dimensions; + this.entries = entries; + this.lengthMap = lengthMap; + this.lookupType = lookupType; + this.isOrdered = isOrdered; + } + + } + + public static final class CommentHeader { + + public final String vendor; + public final String[] comments; + public final int length; + + public CommentHeader(String vendor, String[] comments, int length) { + this.vendor = vendor; + this.comments = comments; + this.length = length; + } + + } + + public static final class VorbisIdHeader { + + public final long version; + public final int channels; + public final long sampleRate; + public final int bitrateMax; + public final int bitrateNominal; + public final int bitrateMin; + public final int blockSize0; + public final int blockSize1; + public final boolean framingFlag; + public final byte[] data; + + public VorbisIdHeader(long version, int channels, long sampleRate, int bitrateMax, + int bitrateNominal, int bitrateMin, int blockSize0, int blockSize1, boolean framingFlag, + byte[] data) { + this.version = version; + this.channels = channels; + this.sampleRate = sampleRate; + this.bitrateMax = bitrateMax; + this.bitrateNominal = bitrateNominal; + this.bitrateMin = bitrateMin; + this.blockSize0 = blockSize0; + this.blockSize1 = blockSize1; + this.framingFlag = framingFlag; + this.data = data; + } + + public int getApproximateBitrate() { + return bitrateNominal == 0 ? (bitrateMin + bitrateMax) / 2 : bitrateNominal; + } + + } + + public static final class Mode { + + public final boolean blockFlag; + public final int windowType; + public final int transformType; + public final int mapping; + + public Mode(boolean blockFlag, int windowType, int transformType, int mapping) { + this.blockFlag = blockFlag; + this.windowType = windowType; + this.transformType = transformType; + this.mapping = mapping; + } + + } + +}