1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.exoplayer2.extractor.mkv; 17 18 import static com.google.common.truth.Truth.assertThat; 19 20 import androidx.test.ext.junit.runners.AndroidJUnit4; 21 import com.google.android.exoplayer2.extractor.ExtractorInput; 22 import com.google.android.exoplayer2.testutil.FakeExtractorInput; 23 import com.google.android.exoplayer2.testutil.TestUtil; 24 import java.io.IOException; 25 import java.util.ArrayList; 26 import java.util.Arrays; 27 import java.util.List; 28 import org.junit.Test; 29 import org.junit.runner.RunWith; 30 31 /** Tests {@link DefaultEbmlReader}. */ 32 @RunWith(AndroidJUnit4.class) 33 public class DefaultEbmlReaderTest { 34 35 @Test masterElement()36 public void masterElement() throws IOException { 37 ExtractorInput input = createTestInput(0x1A, 0x45, 0xDF, 0xA3, 0x84, 0x42, 0x85, 0x81, 0x01); 38 TestProcessor expected = new TestProcessor(); 39 expected.startMasterElement(TestProcessor.ID_EBML, 5, 4); 40 expected.integerElement(TestProcessor.ID_DOC_TYPE_READ_VERSION, 1); 41 expected.endMasterElement(TestProcessor.ID_EBML); 42 assertEvents(input, expected.events); 43 } 44 45 @Test masterElementEmpty()46 public void masterElementEmpty() throws IOException { 47 ExtractorInput input = createTestInput(0x18, 0x53, 0x80, 0x67, 0x80); 48 TestProcessor expected = new TestProcessor(); 49 expected.startMasterElement(TestProcessor.ID_SEGMENT, 5, 0); 50 expected.endMasterElement(TestProcessor.ID_SEGMENT); 51 assertEvents(input, expected.events); 52 } 53 54 @Test unsignedIntegerElement()55 public void unsignedIntegerElement() throws IOException { 56 // 0xFE is chosen because for signed integers it should be interpreted as -2 57 ExtractorInput input = createTestInput(0x42, 0xF7, 0x81, 0xFE); 58 TestProcessor expected = new TestProcessor(); 59 expected.integerElement(TestProcessor.ID_EBML_READ_VERSION, 254); 60 assertEvents(input, expected.events); 61 } 62 63 @Test unsignedIntegerElementLarge()64 public void unsignedIntegerElementLarge() throws IOException { 65 ExtractorInput input = 66 createTestInput(0x42, 0xF7, 0x88, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); 67 TestProcessor expected = new TestProcessor(); 68 expected.integerElement(TestProcessor.ID_EBML_READ_VERSION, Long.MAX_VALUE); 69 assertEvents(input, expected.events); 70 } 71 72 @Test unsignedIntegerElementTooLargeBecomesNegative()73 public void unsignedIntegerElementTooLargeBecomesNegative() throws IOException { 74 ExtractorInput input = 75 createTestInput(0x42, 0xF7, 0x88, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF); 76 TestProcessor expected = new TestProcessor(); 77 expected.integerElement(TestProcessor.ID_EBML_READ_VERSION, -1); 78 assertEvents(input, expected.events); 79 } 80 81 @Test stringElement()82 public void stringElement() throws IOException { 83 ExtractorInput input = createTestInput(0x42, 0x82, 0x86, 0x41, 0x62, 0x63, 0x31, 0x32, 0x33); 84 TestProcessor expected = new TestProcessor(); 85 expected.stringElement(TestProcessor.ID_DOC_TYPE, "Abc123"); 86 assertEvents(input, expected.events); 87 } 88 89 @Test stringElementWithZeroPadding()90 public void stringElementWithZeroPadding() throws IOException, InterruptedException { 91 ExtractorInput input = createTestInput(0x42, 0x82, 0x86, 0x41, 0x62, 0x63, 0x00, 0x00, 0x00); 92 TestProcessor expected = new TestProcessor(); 93 expected.stringElement(TestProcessor.ID_DOC_TYPE, "Abc"); 94 assertEvents(input, expected.events); 95 } 96 97 @Test stringElementEmpty()98 public void stringElementEmpty() throws IOException { 99 ExtractorInput input = createTestInput(0x42, 0x82, 0x80); 100 TestProcessor expected = new TestProcessor(); 101 expected.stringElement(TestProcessor.ID_DOC_TYPE, ""); 102 assertEvents(input, expected.events); 103 } 104 105 @Test floatElementFourBytes()106 public void floatElementFourBytes() throws IOException { 107 ExtractorInput input = 108 createTestInput(0x44, 0x89, 0x84, 0x3F, 0x80, 0x00, 0x00); 109 TestProcessor expected = new TestProcessor(); 110 expected.floatElement(TestProcessor.ID_DURATION, 1.0); 111 assertEvents(input, expected.events); 112 } 113 114 @Test floatElementEightBytes()115 public void floatElementEightBytes() throws IOException { 116 ExtractorInput input = 117 createTestInput(0x44, 0x89, 0x88, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); 118 TestProcessor expected = new TestProcessor(); 119 expected.floatElement(TestProcessor.ID_DURATION, -2.0); 120 assertEvents(input, expected.events); 121 } 122 123 @Test binaryElement()124 public void binaryElement() throws IOException { 125 ExtractorInput input = 126 createTestInput(0xA3, 0x88, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08); 127 TestProcessor expected = new TestProcessor(); 128 expected.binaryElement( 129 TestProcessor.ID_SIMPLE_BLOCK, 130 8, 131 createTestInput(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08)); 132 assertEvents(input, expected.events); 133 } 134 assertEvents(ExtractorInput input, List<String> expectedEvents)135 private static void assertEvents(ExtractorInput input, List<String> expectedEvents) 136 throws IOException { 137 DefaultEbmlReader reader = new DefaultEbmlReader(); 138 TestProcessor output = new TestProcessor(); 139 reader.init(output); 140 141 // We expect the number of successful reads to equal the number of expected events. 142 for (int i = 0; i < expectedEvents.size(); i++) { 143 assertThat(reader.read(input)).isTrue(); 144 } 145 // The next read should be unsuccessful. 146 assertThat(reader.read(input)).isFalse(); 147 // Check that we really did get to the end of input. 148 assertThat(input.readFully(new byte[1], 0, 1, true)).isFalse(); 149 150 assertThat(output.events).containsExactlyElementsIn(expectedEvents).inOrder(); 151 } 152 153 /** 154 * Helper to build an {@link ExtractorInput} from byte data. 155 * 156 * @param data Zero or more integers with values between {@code 0x00} and {@code 0xFF}. 157 * @return An {@link ExtractorInput} from which the data can be read. 158 */ createTestInput(int... data)159 private static ExtractorInput createTestInput(int... data) { 160 return new FakeExtractorInput.Builder() 161 .setData(TestUtil.createByteArray(data)) 162 .setSimulateUnknownLength(true) 163 .build(); 164 } 165 166 /** An {@link EbmlProcessor} that records each event callback. */ 167 private static final class TestProcessor implements EbmlProcessor { 168 169 // Element IDs 170 private static final int ID_EBML = 0x1A45DFA3; 171 private static final int ID_EBML_READ_VERSION = 0x42F7; 172 private static final int ID_DOC_TYPE = 0x4282; 173 private static final int ID_DOC_TYPE_READ_VERSION = 0x4285; 174 175 private static final int ID_SEGMENT = 0x18538067; 176 private static final int ID_DURATION = 0x4489; 177 private static final int ID_SIMPLE_BLOCK = 0xA3; 178 179 private final List<String> events = new ArrayList<>(); 180 181 @Override 182 @EbmlProcessor.ElementType getElementType(int id)183 public int getElementType(int id) { 184 switch (id) { 185 case ID_EBML: 186 case ID_SEGMENT: 187 return EbmlProcessor.ELEMENT_TYPE_MASTER; 188 case ID_EBML_READ_VERSION: 189 case ID_DOC_TYPE_READ_VERSION: 190 return EbmlProcessor.ELEMENT_TYPE_UNSIGNED_INT; 191 case ID_DOC_TYPE: 192 return EbmlProcessor.ELEMENT_TYPE_STRING; 193 case ID_SIMPLE_BLOCK: 194 return EbmlProcessor.ELEMENT_TYPE_BINARY; 195 case ID_DURATION: 196 return EbmlProcessor.ELEMENT_TYPE_FLOAT; 197 default: 198 return EbmlProcessor.ELEMENT_TYPE_UNKNOWN; 199 } 200 } 201 202 @Override isLevel1Element(int id)203 public boolean isLevel1Element(int id) { 204 return false; 205 } 206 207 @Override startMasterElement(int id, long contentPosition, long contentSize)208 public void startMasterElement(int id, long contentPosition, long contentSize) { 209 events.add(formatEvent(id, "start contentPosition=" + contentPosition 210 + " contentSize=" + contentSize)); 211 } 212 213 @Override endMasterElement(int id)214 public void endMasterElement(int id) { 215 events.add(formatEvent(id, "end")); 216 } 217 218 @Override integerElement(int id, long value)219 public void integerElement(int id, long value) { 220 events.add(formatEvent(id, "integer=" + value)); 221 } 222 223 @Override floatElement(int id, double value)224 public void floatElement(int id, double value) { 225 events.add(formatEvent(id, "float=" + value)); 226 } 227 228 @Override stringElement(int id, String value)229 public void stringElement(int id, String value) { 230 events.add(formatEvent(id, "string=" + value)); 231 } 232 233 @Override binaryElement(int id, int contentSize, ExtractorInput input)234 public void binaryElement(int id, int contentSize, ExtractorInput input) throws IOException { 235 byte[] bytes = new byte[contentSize]; 236 input.readFully(bytes, 0, contentSize); 237 events.add(formatEvent(id, "bytes=" + Arrays.toString(bytes))); 238 } 239 formatEvent(int id, String event)240 private static String formatEvent(int id, String event) { 241 return "[" + Integer.toHexString(id) + "] " + event; 242 } 243 244 } 245 246 } 247