• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
19 import androidx.annotation.IntDef;
20 import com.google.android.exoplayer2.C;
21 import com.google.android.exoplayer2.ParserException;
22 import com.google.android.exoplayer2.extractor.ExtractorInput;
23 import com.google.android.exoplayer2.util.Assertions;
24 import java.io.EOFException;
25 import java.io.IOException;
26 import java.lang.annotation.Documented;
27 import java.lang.annotation.Retention;
28 import java.lang.annotation.RetentionPolicy;
29 import java.util.ArrayDeque;
30 import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
31 import org.checkerframework.checker.nullness.qual.RequiresNonNull;
32 
33 /**
34  * Default implementation of {@link EbmlReader}.
35  */
36 /* package */ final class DefaultEbmlReader implements EbmlReader {
37 
38   @Documented
39   @Retention(RetentionPolicy.SOURCE)
40   @IntDef({ELEMENT_STATE_READ_ID, ELEMENT_STATE_READ_CONTENT_SIZE, ELEMENT_STATE_READ_CONTENT})
41   private @interface ElementState {}
42 
43   private static final int ELEMENT_STATE_READ_ID = 0;
44   private static final int ELEMENT_STATE_READ_CONTENT_SIZE = 1;
45   private static final int ELEMENT_STATE_READ_CONTENT = 2;
46 
47   private static final int MAX_ID_BYTES = 4;
48   private static final int MAX_LENGTH_BYTES = 8;
49 
50   private static final int MAX_INTEGER_ELEMENT_SIZE_BYTES = 8;
51   private static final int VALID_FLOAT32_ELEMENT_SIZE_BYTES = 4;
52   private static final int VALID_FLOAT64_ELEMENT_SIZE_BYTES = 8;
53 
54   private final byte[] scratch;
55   private final ArrayDeque<MasterElement> masterElementsStack;
56   private final VarintReader varintReader;
57 
58   private @MonotonicNonNull EbmlProcessor processor;
59   private @ElementState int elementState;
60   private int elementId;
61   private long elementContentSize;
62 
DefaultEbmlReader()63   public DefaultEbmlReader() {
64     scratch = new byte[8];
65     masterElementsStack = new ArrayDeque<>();
66     varintReader = new VarintReader();
67   }
68 
69   @Override
init(EbmlProcessor processor)70   public void init(EbmlProcessor processor) {
71     this.processor = processor;
72   }
73 
74   @Override
reset()75   public void reset() {
76     elementState = ELEMENT_STATE_READ_ID;
77     masterElementsStack.clear();
78     varintReader.reset();
79   }
80 
81   @Override
read(ExtractorInput input)82   public boolean read(ExtractorInput input) throws IOException {
83     Assertions.checkStateNotNull(processor);
84     while (true) {
85       MasterElement head = masterElementsStack.peek();
86       if (head != null && input.getPosition() >= head.elementEndPosition) {
87         processor.endMasterElement(masterElementsStack.pop().elementId);
88         return true;
89       }
90 
91       if (elementState == ELEMENT_STATE_READ_ID) {
92         long result = varintReader.readUnsignedVarint(input, true, false, MAX_ID_BYTES);
93         if (result == C.RESULT_MAX_LENGTH_EXCEEDED) {
94           result = maybeResyncToNextLevel1Element(input);
95         }
96         if (result == C.RESULT_END_OF_INPUT) {
97           return false;
98         }
99         // Element IDs are at most 4 bytes, so we can cast to integers.
100         elementId = (int) result;
101         elementState = ELEMENT_STATE_READ_CONTENT_SIZE;
102       }
103 
104       if (elementState == ELEMENT_STATE_READ_CONTENT_SIZE) {
105         elementContentSize = varintReader.readUnsignedVarint(input, false, true, MAX_LENGTH_BYTES);
106         elementState = ELEMENT_STATE_READ_CONTENT;
107       }
108 
109       @EbmlProcessor.ElementType int type = processor.getElementType(elementId);
110       switch (type) {
111         case EbmlProcessor.ELEMENT_TYPE_MASTER:
112           long elementContentPosition = input.getPosition();
113           long elementEndPosition = elementContentPosition + elementContentSize;
114           masterElementsStack.push(new MasterElement(elementId, elementEndPosition));
115           processor.startMasterElement(elementId, elementContentPosition, elementContentSize);
116           elementState = ELEMENT_STATE_READ_ID;
117           return true;
118         case EbmlProcessor.ELEMENT_TYPE_UNSIGNED_INT:
119           if (elementContentSize > MAX_INTEGER_ELEMENT_SIZE_BYTES) {
120             throw new ParserException("Invalid integer size: " + elementContentSize);
121           }
122           processor.integerElement(elementId, readInteger(input, (int) elementContentSize));
123           elementState = ELEMENT_STATE_READ_ID;
124           return true;
125         case EbmlProcessor.ELEMENT_TYPE_FLOAT:
126           if (elementContentSize != VALID_FLOAT32_ELEMENT_SIZE_BYTES
127               && elementContentSize != VALID_FLOAT64_ELEMENT_SIZE_BYTES) {
128             throw new ParserException("Invalid float size: " + elementContentSize);
129           }
130           processor.floatElement(elementId, readFloat(input, (int) elementContentSize));
131           elementState = ELEMENT_STATE_READ_ID;
132           return true;
133         case EbmlProcessor.ELEMENT_TYPE_STRING:
134           if (elementContentSize > Integer.MAX_VALUE) {
135             throw new ParserException("String element size: " + elementContentSize);
136           }
137           processor.stringElement(elementId, readString(input, (int) elementContentSize));
138           elementState = ELEMENT_STATE_READ_ID;
139           return true;
140         case EbmlProcessor.ELEMENT_TYPE_BINARY:
141           processor.binaryElement(elementId, (int) elementContentSize, input);
142           elementState = ELEMENT_STATE_READ_ID;
143           return true;
144         case EbmlProcessor.ELEMENT_TYPE_UNKNOWN:
145           input.skipFully((int) elementContentSize);
146           elementState = ELEMENT_STATE_READ_ID;
147           break;
148         default:
149           throw new ParserException("Invalid element type " + type);
150       }
151     }
152   }
153 
154   /**
155    * Does a byte by byte search to try and find the next level 1 element. This method is called if
156    * some invalid data is encountered in the parser.
157    *
158    * @param input The {@link ExtractorInput} from which data has to be read.
159    * @return id of the next level 1 element that has been found.
160    * @throws EOFException If the end of input was encountered when searching for the next level 1
161    *     element.
162    * @throws IOException If an error occurs reading from the input.
163    */
164   @RequiresNonNull("processor")
maybeResyncToNextLevel1Element(ExtractorInput input)165   private long maybeResyncToNextLevel1Element(ExtractorInput input) throws IOException {
166     input.resetPeekPosition();
167     while (true) {
168       input.peekFully(scratch, 0, MAX_ID_BYTES);
169       int varintLength = VarintReader.parseUnsignedVarintLength(scratch[0]);
170       if (varintLength != C.LENGTH_UNSET && varintLength <= MAX_ID_BYTES) {
171         int potentialId = (int) VarintReader.assembleVarint(scratch, varintLength, false);
172         if (processor.isLevel1Element(potentialId)) {
173           input.skipFully(varintLength);
174           return potentialId;
175         }
176       }
177       input.skipFully(1);
178     }
179   }
180 
181   /**
182    * Reads and returns an integer of length {@code byteLength} from the {@link ExtractorInput}.
183    *
184    * @param input The {@link ExtractorInput} from which to read.
185    * @param byteLength The length of the integer being read.
186    * @return The read integer value.
187    * @throws IOException If an error occurs reading from the input.
188    */
readInteger(ExtractorInput input, int byteLength)189   private long readInteger(ExtractorInput input, int byteLength) throws IOException {
190     input.readFully(scratch, 0, byteLength);
191     long value = 0;
192     for (int i = 0; i < byteLength; i++) {
193       value = (value << 8) | (scratch[i] & 0xFF);
194     }
195     return value;
196   }
197 
198   /**
199    * Reads and returns a float of length {@code byteLength} from the {@link ExtractorInput}.
200    *
201    * @param input The {@link ExtractorInput} from which to read.
202    * @param byteLength The length of the float being read.
203    * @return The read float value.
204    * @throws IOException If an error occurs reading from the input.
205    */
readFloat(ExtractorInput input, int byteLength)206   private double readFloat(ExtractorInput input, int byteLength) throws IOException {
207     long integerValue = readInteger(input, byteLength);
208     double floatValue;
209     if (byteLength == VALID_FLOAT32_ELEMENT_SIZE_BYTES) {
210       floatValue = Float.intBitsToFloat((int) integerValue);
211     } else {
212       floatValue = Double.longBitsToDouble(integerValue);
213     }
214     return floatValue;
215   }
216 
217   /**
218    * Reads a string of length {@code byteLength} from the {@link ExtractorInput}. Zero padding is
219    * removed, so the returned string may be shorter than {@code byteLength}.
220    *
221    * @param input The {@link ExtractorInput} from which to read.
222    * @param byteLength The length of the string being read, including zero padding.
223    * @return The read string value.
224    * @throws IOException If an error occurs reading from the input.
225    */
readString(ExtractorInput input, int byteLength)226   private static String readString(ExtractorInput input, int byteLength) throws IOException {
227     if (byteLength == 0) {
228       return "";
229     }
230     byte[] stringBytes = new byte[byteLength];
231     input.readFully(stringBytes, 0, byteLength);
232     // Remove zero padding.
233     int trimmedLength = byteLength;
234     while (trimmedLength > 0 && stringBytes[trimmedLength - 1] == 0) {
235       trimmedLength--;
236     }
237     return new String(stringBytes, 0, trimmedLength);
238   }
239 
240   /**
241    * Used in {@link #masterElementsStack} to track when the current master element ends, so that
242    * {@link EbmlProcessor#endMasterElement(int)} can be called.
243    */
244   private static final class MasterElement {
245 
246     private final int elementId;
247     private final long elementEndPosition;
248 
MasterElement(int elementId, long elementEndPosition)249     private MasterElement(int elementId, long elementEndPosition) {
250       this.elementId = elementId;
251       this.elementEndPosition = elementEndPosition;
252     }
253 
254   }
255 
256 }
257