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