• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.source;
17 
18 import androidx.annotation.Nullable;
19 import com.google.android.exoplayer2.C;
20 import com.google.android.exoplayer2.decoder.CryptoInfo;
21 import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
22 import com.google.android.exoplayer2.extractor.TrackOutput.CryptoData;
23 import com.google.android.exoplayer2.source.SampleQueue.SampleExtrasHolder;
24 import com.google.android.exoplayer2.upstream.Allocation;
25 import com.google.android.exoplayer2.upstream.Allocator;
26 import com.google.android.exoplayer2.upstream.DataReader;
27 import com.google.android.exoplayer2.util.ParsableByteArray;
28 import com.google.android.exoplayer2.util.Util;
29 import java.io.EOFException;
30 import java.io.IOException;
31 import java.nio.ByteBuffer;
32 import java.util.Arrays;
33 
34 /** A queue of media sample data. */
35 /* package */ class SampleDataQueue {
36 
37   private static final int INITIAL_SCRATCH_SIZE = 32;
38 
39   private final Allocator allocator;
40   private final int allocationLength;
41   private final ParsableByteArray scratch;
42 
43   // References into the linked list of allocations.
44   private AllocationNode firstAllocationNode;
45   private AllocationNode readAllocationNode;
46   private AllocationNode writeAllocationNode;
47 
48   // Accessed only by the loading thread (or the consuming thread when there is no loading thread).
49   private long totalBytesWritten;
50 
SampleDataQueue(Allocator allocator)51   public SampleDataQueue(Allocator allocator) {
52     this.allocator = allocator;
53     allocationLength = allocator.getIndividualAllocationLength();
54     scratch = new ParsableByteArray(INITIAL_SCRATCH_SIZE);
55     firstAllocationNode = new AllocationNode(/* startPosition= */ 0, allocationLength);
56     readAllocationNode = firstAllocationNode;
57     writeAllocationNode = firstAllocationNode;
58   }
59 
60   // Called by the consuming thread, but only when there is no loading thread.
61 
62   /** Clears all sample data. */
reset()63   public void reset() {
64     clearAllocationNodes(firstAllocationNode);
65     firstAllocationNode = new AllocationNode(0, allocationLength);
66     readAllocationNode = firstAllocationNode;
67     writeAllocationNode = firstAllocationNode;
68     totalBytesWritten = 0;
69     allocator.trim();
70   }
71 
72   /**
73    * Discards sample data bytes from the write side of the queue.
74    *
75    * @param totalBytesWritten The reduced total number of bytes written after the samples have been
76    *     discarded, or 0 if the queue is now empty.
77    */
discardUpstreamSampleBytes(long totalBytesWritten)78   public void discardUpstreamSampleBytes(long totalBytesWritten) {
79     this.totalBytesWritten = totalBytesWritten;
80     if (this.totalBytesWritten == 0
81         || this.totalBytesWritten == firstAllocationNode.startPosition) {
82       clearAllocationNodes(firstAllocationNode);
83       firstAllocationNode = new AllocationNode(this.totalBytesWritten, allocationLength);
84       readAllocationNode = firstAllocationNode;
85       writeAllocationNode = firstAllocationNode;
86     } else {
87       // Find the last node containing at least 1 byte of data that we need to keep.
88       AllocationNode lastNodeToKeep = firstAllocationNode;
89       while (this.totalBytesWritten > lastNodeToKeep.endPosition) {
90         lastNodeToKeep = lastNodeToKeep.next;
91       }
92       // Discard all subsequent nodes.
93       AllocationNode firstNodeToDiscard = lastNodeToKeep.next;
94       clearAllocationNodes(firstNodeToDiscard);
95       // Reset the successor of the last node to be an uninitialized node.
96       lastNodeToKeep.next = new AllocationNode(lastNodeToKeep.endPosition, allocationLength);
97       // Update writeAllocationNode and readAllocationNode as necessary.
98       writeAllocationNode =
99           this.totalBytesWritten == lastNodeToKeep.endPosition
100               ? lastNodeToKeep.next
101               : lastNodeToKeep;
102       if (readAllocationNode == firstNodeToDiscard) {
103         readAllocationNode = lastNodeToKeep.next;
104       }
105     }
106   }
107 
108   // Called by the consuming thread.
109 
110   /** Rewinds the read position to the first sample in the queue. */
rewind()111   public void rewind() {
112     readAllocationNode = firstAllocationNode;
113   }
114 
115   /**
116    * Reads data from the rolling buffer to populate a decoder input buffer.
117    *
118    * @param buffer The buffer to populate.
119    * @param extrasHolder The extras holder whose offset should be read and subsequently adjusted.
120    */
readToBuffer(DecoderInputBuffer buffer, SampleExtrasHolder extrasHolder)121   public void readToBuffer(DecoderInputBuffer buffer, SampleExtrasHolder extrasHolder) {
122     // Read encryption data if the sample is encrypted.
123     if (buffer.isEncrypted()) {
124       readEncryptionData(buffer, extrasHolder);
125     }
126     // Read sample data, extracting supplemental data into a separate buffer if needed.
127     if (buffer.hasSupplementalData()) {
128       // If there is supplemental data, the sample data is prefixed by its size.
129       scratch.reset(4);
130       readData(extrasHolder.offset, scratch.data, 4);
131       int sampleSize = scratch.readUnsignedIntToInt();
132       extrasHolder.offset += 4;
133       extrasHolder.size -= 4;
134 
135       // Write the sample data.
136       buffer.ensureSpaceForWrite(sampleSize);
137       readData(extrasHolder.offset, buffer.data, sampleSize);
138       extrasHolder.offset += sampleSize;
139       extrasHolder.size -= sampleSize;
140 
141       // Write the remaining data as supplemental data.
142       buffer.resetSupplementalData(extrasHolder.size);
143       readData(extrasHolder.offset, buffer.supplementalData, extrasHolder.size);
144     } else {
145       // Write the sample data.
146       buffer.ensureSpaceForWrite(extrasHolder.size);
147       readData(extrasHolder.offset, buffer.data, extrasHolder.size);
148     }
149   }
150 
151   /**
152    * Advances the read position to the specified absolute position.
153    *
154    * @param absolutePosition The new absolute read position. May be {@link C#POSITION_UNSET}, in
155    *     which case calling this method is a no-op.
156    */
discardDownstreamTo(long absolutePosition)157   public void discardDownstreamTo(long absolutePosition) {
158     if (absolutePosition == C.POSITION_UNSET) {
159       return;
160     }
161     while (absolutePosition >= firstAllocationNode.endPosition) {
162       // Advance firstAllocationNode to the specified absolute position. Also clear nodes that are
163       // advanced past, and return their underlying allocations to the allocator.
164       allocator.release(firstAllocationNode.allocation);
165       firstAllocationNode = firstAllocationNode.clear();
166     }
167     if (readAllocationNode.startPosition < firstAllocationNode.startPosition) {
168       // We discarded the node referenced by readAllocationNode. We need to advance it to the first
169       // remaining node.
170       readAllocationNode = firstAllocationNode;
171     }
172   }
173 
174   // Called by the loading thread.
175 
getTotalBytesWritten()176   public long getTotalBytesWritten() {
177     return totalBytesWritten;
178   }
179 
sampleData(DataReader input, int length, boolean allowEndOfInput)180   public int sampleData(DataReader input, int length, boolean allowEndOfInput) throws IOException {
181     length = preAppend(length);
182     int bytesAppended =
183         input.read(
184             writeAllocationNode.allocation.data,
185             writeAllocationNode.translateOffset(totalBytesWritten),
186             length);
187     if (bytesAppended == C.RESULT_END_OF_INPUT) {
188       if (allowEndOfInput) {
189         return C.RESULT_END_OF_INPUT;
190       }
191       throw new EOFException();
192     }
193     postAppend(bytesAppended);
194     return bytesAppended;
195   }
196 
sampleData(ParsableByteArray buffer, int length)197   public void sampleData(ParsableByteArray buffer, int length) {
198     while (length > 0) {
199       int bytesAppended = preAppend(length);
200       buffer.readBytes(
201           writeAllocationNode.allocation.data,
202           writeAllocationNode.translateOffset(totalBytesWritten),
203           bytesAppended);
204       length -= bytesAppended;
205       postAppend(bytesAppended);
206     }
207   }
208 
209   // Private methods.
210 
211   /**
212    * Reads encryption data for the current sample.
213    *
214    * <p>The encryption data is written into {@link DecoderInputBuffer#cryptoInfo}, and {@link
215    * SampleExtrasHolder#size} is adjusted to subtract the number of bytes that were read. The same
216    * value is added to {@link SampleExtrasHolder#offset}.
217    *
218    * @param buffer The buffer into which the encryption data should be written.
219    * @param extrasHolder The extras holder whose offset should be read and subsequently adjusted.
220    */
readEncryptionData(DecoderInputBuffer buffer, SampleExtrasHolder extrasHolder)221   private void readEncryptionData(DecoderInputBuffer buffer, SampleExtrasHolder extrasHolder) {
222     long offset = extrasHolder.offset;
223 
224     // Read the signal byte.
225     scratch.reset(1);
226     readData(offset, scratch.data, 1);
227     offset++;
228     byte signalByte = scratch.data[0];
229     boolean subsampleEncryption = (signalByte & 0x80) != 0;
230     int ivSize = signalByte & 0x7F;
231 
232     // Read the initialization vector.
233     CryptoInfo cryptoInfo = buffer.cryptoInfo;
234     if (cryptoInfo.iv == null) {
235       cryptoInfo.iv = new byte[16];
236     } else {
237       // Zero out cryptoInfo.iv so that if ivSize < 16, the remaining bytes are correctly set to 0.
238       Arrays.fill(cryptoInfo.iv, (byte) 0);
239     }
240     readData(offset, cryptoInfo.iv, ivSize);
241     offset += ivSize;
242 
243     // Read the subsample count, if present.
244     int subsampleCount;
245     if (subsampleEncryption) {
246       scratch.reset(2);
247       readData(offset, scratch.data, 2);
248       offset += 2;
249       subsampleCount = scratch.readUnsignedShort();
250     } else {
251       subsampleCount = 1;
252     }
253 
254     // Write the clear and encrypted subsample sizes.
255     @Nullable int[] clearDataSizes = cryptoInfo.numBytesOfClearData;
256     if (clearDataSizes == null || clearDataSizes.length < subsampleCount) {
257       clearDataSizes = new int[subsampleCount];
258     }
259     @Nullable int[] encryptedDataSizes = cryptoInfo.numBytesOfEncryptedData;
260     if (encryptedDataSizes == null || encryptedDataSizes.length < subsampleCount) {
261       encryptedDataSizes = new int[subsampleCount];
262     }
263     if (subsampleEncryption) {
264       int subsampleDataLength = 6 * subsampleCount;
265       scratch.reset(subsampleDataLength);
266       readData(offset, scratch.data, subsampleDataLength);
267       offset += subsampleDataLength;
268       scratch.setPosition(0);
269       for (int i = 0; i < subsampleCount; i++) {
270         clearDataSizes[i] = scratch.readUnsignedShort();
271         encryptedDataSizes[i] = scratch.readUnsignedIntToInt();
272       }
273     } else {
274       clearDataSizes[0] = 0;
275       encryptedDataSizes[0] = extrasHolder.size - (int) (offset - extrasHolder.offset);
276     }
277 
278     // Populate the cryptoInfo.
279     CryptoData cryptoData = Util.castNonNull(extrasHolder.cryptoData);
280     cryptoInfo.set(
281         subsampleCount,
282         clearDataSizes,
283         encryptedDataSizes,
284         cryptoData.encryptionKey,
285         cryptoInfo.iv,
286         cryptoData.cryptoMode,
287         cryptoData.encryptedBlocks,
288         cryptoData.clearBlocks);
289 
290     // Adjust the offset and size to take into account the bytes read.
291     int bytesRead = (int) (offset - extrasHolder.offset);
292     extrasHolder.offset += bytesRead;
293     extrasHolder.size -= bytesRead;
294   }
295 
296   /**
297    * Reads data from the front of the rolling buffer.
298    *
299    * @param absolutePosition The absolute position from which data should be read.
300    * @param target The buffer into which data should be written.
301    * @param length The number of bytes to read.
302    */
readData(long absolutePosition, ByteBuffer target, int length)303   private void readData(long absolutePosition, ByteBuffer target, int length) {
304     advanceReadTo(absolutePosition);
305     int remaining = length;
306     while (remaining > 0) {
307       int toCopy = Math.min(remaining, (int) (readAllocationNode.endPosition - absolutePosition));
308       Allocation allocation = readAllocationNode.allocation;
309       target.put(allocation.data, readAllocationNode.translateOffset(absolutePosition), toCopy);
310       remaining -= toCopy;
311       absolutePosition += toCopy;
312       if (absolutePosition == readAllocationNode.endPosition) {
313         readAllocationNode = readAllocationNode.next;
314       }
315     }
316   }
317 
318   /**
319    * Reads data from the front of the rolling buffer.
320    *
321    * @param absolutePosition The absolute position from which data should be read.
322    * @param target The array into which data should be written.
323    * @param length The number of bytes to read.
324    */
readData(long absolutePosition, byte[] target, int length)325   private void readData(long absolutePosition, byte[] target, int length) {
326     advanceReadTo(absolutePosition);
327     int remaining = length;
328     while (remaining > 0) {
329       int toCopy = Math.min(remaining, (int) (readAllocationNode.endPosition - absolutePosition));
330       Allocation allocation = readAllocationNode.allocation;
331       System.arraycopy(
332           allocation.data,
333           readAllocationNode.translateOffset(absolutePosition),
334           target,
335           length - remaining,
336           toCopy);
337       remaining -= toCopy;
338       absolutePosition += toCopy;
339       if (absolutePosition == readAllocationNode.endPosition) {
340         readAllocationNode = readAllocationNode.next;
341       }
342     }
343   }
344 
345   /**
346    * Advances the read position to the specified absolute position.
347    *
348    * @param absolutePosition The position to which {@link #readAllocationNode} should be advanced.
349    */
advanceReadTo(long absolutePosition)350   private void advanceReadTo(long absolutePosition) {
351     while (absolutePosition >= readAllocationNode.endPosition) {
352       readAllocationNode = readAllocationNode.next;
353     }
354   }
355 
356   /**
357    * Clears allocation nodes starting from {@code fromNode}.
358    *
359    * @param fromNode The node from which to clear.
360    */
clearAllocationNodes(AllocationNode fromNode)361   private void clearAllocationNodes(AllocationNode fromNode) {
362     if (!fromNode.wasInitialized) {
363       return;
364     }
365     // Bulk release allocations for performance (it's significantly faster when using
366     // DefaultAllocator because the allocator's lock only needs to be acquired and released once)
367     // [Internal: See b/29542039].
368     int allocationCount =
369         (writeAllocationNode.wasInitialized ? 1 : 0)
370             + ((int) (writeAllocationNode.startPosition - fromNode.startPosition)
371                 / allocationLength);
372     Allocation[] allocationsToRelease = new Allocation[allocationCount];
373     AllocationNode currentNode = fromNode;
374     for (int i = 0; i < allocationsToRelease.length; i++) {
375       allocationsToRelease[i] = currentNode.allocation;
376       currentNode = currentNode.clear();
377     }
378     allocator.release(allocationsToRelease);
379   }
380 
381   /**
382    * Called before writing sample data to {@link #writeAllocationNode}. May cause {@link
383    * #writeAllocationNode} to be initialized.
384    *
385    * @param length The number of bytes that the caller wishes to write.
386    * @return The number of bytes that the caller is permitted to write, which may be less than
387    *     {@code length}.
388    */
preAppend(int length)389   private int preAppend(int length) {
390     if (!writeAllocationNode.wasInitialized) {
391       writeAllocationNode.initialize(
392           allocator.allocate(),
393           new AllocationNode(writeAllocationNode.endPosition, allocationLength));
394     }
395     return Math.min(length, (int) (writeAllocationNode.endPosition - totalBytesWritten));
396   }
397 
398   /**
399    * Called after writing sample data. May cause {@link #writeAllocationNode} to be advanced.
400    *
401    * @param length The number of bytes that were written.
402    */
postAppend(int length)403   private void postAppend(int length) {
404     totalBytesWritten += length;
405     if (totalBytesWritten == writeAllocationNode.endPosition) {
406       writeAllocationNode = writeAllocationNode.next;
407     }
408   }
409 
410   /** A node in a linked list of {@link Allocation}s held by the output. */
411   private static final class AllocationNode {
412 
413     /** The absolute position of the start of the data (inclusive). */
414     public final long startPosition;
415     /** The absolute position of the end of the data (exclusive). */
416     public final long endPosition;
417     /** Whether the node has been initialized. Remains true after {@link #clear()}. */
418     public boolean wasInitialized;
419     /** The {@link Allocation}, or {@code null} if the node is not initialized. */
420     @Nullable public Allocation allocation;
421     /**
422      * The next {@link AllocationNode} in the list, or {@code null} if the node has not been
423      * initialized. Remains set after {@link #clear()}.
424      */
425     @Nullable public AllocationNode next;
426 
427     /**
428      * @param startPosition See {@link #startPosition}.
429      * @param allocationLength The length of the {@link Allocation} with which this node will be
430      *     initialized.
431      */
AllocationNode(long startPosition, int allocationLength)432     public AllocationNode(long startPosition, int allocationLength) {
433       this.startPosition = startPosition;
434       this.endPosition = startPosition + allocationLength;
435     }
436 
437     /**
438      * Initializes the node.
439      *
440      * @param allocation The node's {@link Allocation}.
441      * @param next The next {@link AllocationNode}.
442      */
initialize(Allocation allocation, AllocationNode next)443     public void initialize(Allocation allocation, AllocationNode next) {
444       this.allocation = allocation;
445       this.next = next;
446       wasInitialized = true;
447     }
448 
449     /**
450      * Gets the offset into the {@link #allocation}'s {@link Allocation#data} that corresponds to
451      * the specified absolute position.
452      *
453      * @param absolutePosition The absolute position.
454      * @return The corresponding offset into the allocation's data.
455      */
translateOffset(long absolutePosition)456     public int translateOffset(long absolutePosition) {
457       return (int) (absolutePosition - startPosition) + allocation.offset;
458     }
459 
460     /**
461      * Clears {@link #allocation} and {@link #next}.
462      *
463      * @return The cleared next {@link AllocationNode}.
464      */
clear()465     public AllocationNode clear() {
466       allocation = null;
467       AllocationNode temp = next;
468       next = null;
469       return temp;
470     }
471   }
472 }
473