diff options
Diffstat (limited to 'mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder')
9 files changed, 1069 insertions, 0 deletions
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/Buffer.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/Buffer.java new file mode 100644 index 0000000000..ac254fae96 --- /dev/null +++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/Buffer.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2016 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 org.mozilla.thirdparty.com.google.android.exoplayer2.decoder; + +import org.mozilla.thirdparty.com.google.android.exoplayer2.C; + +/** + * Base class for buffers with flags. + */ +public abstract class Buffer { + + @C.BufferFlags + private int flags; + + /** + * Clears the buffer. + */ + public void clear() { + flags = 0; + } + + /** + * Returns whether the {@link C#BUFFER_FLAG_DECODE_ONLY} flag is set. + */ + public final boolean isDecodeOnly() { + return getFlag(C.BUFFER_FLAG_DECODE_ONLY); + } + + /** + * Returns whether the {@link C#BUFFER_FLAG_END_OF_STREAM} flag is set. + */ + public final boolean isEndOfStream() { + return getFlag(C.BUFFER_FLAG_END_OF_STREAM); + } + + /** + * Returns whether the {@link C#BUFFER_FLAG_KEY_FRAME} flag is set. + */ + public final boolean isKeyFrame() { + return getFlag(C.BUFFER_FLAG_KEY_FRAME); + } + + /** Returns whether the {@link C#BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA} flag is set. */ + public final boolean hasSupplementalData() { + return getFlag(C.BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA); + } + + /** + * Replaces this buffer's flags with {@code flags}. + * + * @param flags The flags to set, which should be a combination of the {@code C.BUFFER_FLAG_*} + * constants. + */ + public final void setFlags(@C.BufferFlags int flags) { + this.flags = flags; + } + + /** + * Adds the {@code flag} to this buffer's flags. + * + * @param flag The flag to add to this buffer's flags, which should be one of the + * {@code C.BUFFER_FLAG_*} constants. + */ + public final void addFlag(@C.BufferFlags int flag) { + flags |= flag; + } + + /** + * Removes the {@code flag} from this buffer's flags, if it is set. + * + * @param flag The flag to remove. + */ + public final void clearFlag(@C.BufferFlags int flag) { + flags &= ~flag; + } + + /** + * Returns whether the specified flag has been set on this buffer. + * + * @param flag The flag to check. + * @return Whether the flag is set. + */ + protected final boolean getFlag(@C.BufferFlags int flag) { + return (flags & flag) == flag; + } + +} diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/CryptoInfo.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/CryptoInfo.java new file mode 100644 index 0000000000..1bfb0fb06e --- /dev/null +++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/CryptoInfo.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2016 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 org.mozilla.thirdparty.com.google.android.exoplayer2.decoder; + +import android.annotation.TargetApi; +import org.mozilla.thirdparty.com.google.android.exoplayer2.C; +import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util; + +/** + * Compatibility wrapper for {@link android.media.MediaCodec.CryptoInfo}. + */ +public final class CryptoInfo { + + /** + * The 16 byte initialization vector. If the initialization vector of the content is shorter than + * 16 bytes, 0 byte padding is appended to extend the vector to the required 16 byte length. + * + * @see android.media.MediaCodec.CryptoInfo#iv + */ + public byte[] iv; + /** + * The 16 byte key id. + * + * @see android.media.MediaCodec.CryptoInfo#key + */ + public byte[] key; + /** + * The type of encryption that has been applied. Must be one of the {@link C.CryptoMode} values. + * + * @see android.media.MediaCodec.CryptoInfo#mode + */ + @C.CryptoMode public int mode; + /** + * The number of leading unencrypted bytes in each sub-sample. If null, all bytes are treated as + * encrypted and {@link #numBytesOfEncryptedData} must be specified. + * + * @see android.media.MediaCodec.CryptoInfo#numBytesOfClearData + */ + public int[] numBytesOfClearData; + /** + * The number of trailing encrypted bytes in each sub-sample. If null, all bytes are treated as + * clear and {@link #numBytesOfClearData} must be specified. + * + * @see android.media.MediaCodec.CryptoInfo#numBytesOfEncryptedData + */ + public int[] numBytesOfEncryptedData; + /** + * The number of subSamples that make up the buffer's contents. + * + * @see android.media.MediaCodec.CryptoInfo#numSubSamples + */ + public int numSubSamples; + /** + * @see android.media.MediaCodec.CryptoInfo.Pattern + */ + public int encryptedBlocks; + /** + * @see android.media.MediaCodec.CryptoInfo.Pattern + */ + public int clearBlocks; + + private final android.media.MediaCodec.CryptoInfo frameworkCryptoInfo; + private final PatternHolderV24 patternHolder; + + public CryptoInfo() { + frameworkCryptoInfo = new android.media.MediaCodec.CryptoInfo(); + patternHolder = Util.SDK_INT >= 24 ? new PatternHolderV24(frameworkCryptoInfo) : null; + } + + /** + * @see android.media.MediaCodec.CryptoInfo#set(int, int[], int[], byte[], byte[], int) + */ + public void set(int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData, + byte[] key, byte[] iv, @C.CryptoMode int mode, int encryptedBlocks, int clearBlocks) { + this.numSubSamples = numSubSamples; + this.numBytesOfClearData = numBytesOfClearData; + this.numBytesOfEncryptedData = numBytesOfEncryptedData; + this.key = key; + this.iv = iv; + this.mode = mode; + this.encryptedBlocks = encryptedBlocks; + this.clearBlocks = clearBlocks; + // Update frameworkCryptoInfo fields directly because CryptoInfo.set performs an unnecessary + // object allocation on Android N. + frameworkCryptoInfo.numSubSamples = numSubSamples; + frameworkCryptoInfo.numBytesOfClearData = numBytesOfClearData; + frameworkCryptoInfo.numBytesOfEncryptedData = numBytesOfEncryptedData; + frameworkCryptoInfo.key = key; + frameworkCryptoInfo.iv = iv; + frameworkCryptoInfo.mode = mode; + if (Util.SDK_INT >= 24) { + patternHolder.set(encryptedBlocks, clearBlocks); + } + } + + /** + * Returns an equivalent {@link android.media.MediaCodec.CryptoInfo} instance. + * + * <p>Successive calls to this method on a single {@link CryptoInfo} will return the same + * instance. Changes to the {@link CryptoInfo} will be reflected in the returned object. The + * return object should not be modified directly. + * + * @return The equivalent {@link android.media.MediaCodec.CryptoInfo} instance. + */ + public android.media.MediaCodec.CryptoInfo getFrameworkCryptoInfo() { + return frameworkCryptoInfo; + } + + /** @deprecated Use {@link #getFrameworkCryptoInfo()}. */ + @Deprecated + public android.media.MediaCodec.CryptoInfo getFrameworkCryptoInfoV16() { + return getFrameworkCryptoInfo(); + } + + @TargetApi(24) + private static final class PatternHolderV24 { + + private final android.media.MediaCodec.CryptoInfo frameworkCryptoInfo; + private final android.media.MediaCodec.CryptoInfo.Pattern pattern; + + private PatternHolderV24(android.media.MediaCodec.CryptoInfo frameworkCryptoInfo) { + this.frameworkCryptoInfo = frameworkCryptoInfo; + pattern = new android.media.MediaCodec.CryptoInfo.Pattern(0, 0); + } + + private void set(int encryptedBlocks, int clearBlocks) { + pattern.set(encryptedBlocks, clearBlocks); + frameworkCryptoInfo.setPattern(pattern); + } + + } + +} diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/Decoder.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/Decoder.java new file mode 100644 index 0000000000..8040c04ebe --- /dev/null +++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/Decoder.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2016 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 org.mozilla.thirdparty.com.google.android.exoplayer2.decoder; + +import androidx.annotation.Nullable; + +/** + * A media decoder. + * + * @param <I> The type of buffer input to the decoder. + * @param <O> The type of buffer output from the decoder. + * @param <E> The type of exception thrown from the decoder. + */ +public interface Decoder<I, O, E extends Exception> { + + /** + * Returns the name of the decoder. + * + * @return The name of the decoder. + */ + String getName(); + + /** + * Dequeues the next input buffer to be filled and queued to the decoder. + * + * @return The input buffer, which will have been cleared, or null if a buffer isn't available. + * @throws E If a decoder error has occurred. + */ + @Nullable + I dequeueInputBuffer() throws E; + + /** + * Queues an input buffer to the decoder. + * + * @param inputBuffer The input buffer. + * @throws E If a decoder error has occurred. + */ + void queueInputBuffer(I inputBuffer) throws E; + + /** + * Dequeues the next output buffer from the decoder. + * + * @return The output buffer, or null if an output buffer isn't available. + * @throws E If a decoder error has occurred. + */ + @Nullable + O dequeueOutputBuffer() throws E; + + /** + * Flushes the decoder. Ownership of dequeued input buffers is returned to the decoder. The caller + * is still responsible for releasing any dequeued output buffers. + */ + void flush(); + + /** + * Releases the decoder. Must be called when the decoder is no longer needed. + */ + void release(); + +} diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/DecoderCounters.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/DecoderCounters.java new file mode 100644 index 0000000000..f8bdb9b29a --- /dev/null +++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/DecoderCounters.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2016 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 org.mozilla.thirdparty.com.google.android.exoplayer2.decoder; + +/** + * Maintains decoder event counts, for debugging purposes only. + * <p> + * Counters should be written from the playback thread only. Counters may be read from any thread. + * To ensure that the counter values are made visible across threads, users of this class should + * invoke {@link #ensureUpdated()} prior to reading and after writing. + */ +public final class DecoderCounters { + + /** + * The number of times a decoder has been initialized. + */ + public int decoderInitCount; + /** + * The number of times a decoder has been released. + */ + public int decoderReleaseCount; + /** + * The number of queued input buffers. + */ + public int inputBufferCount; + /** + * The number of skipped input buffers. + * <p> + * A skipped input buffer is an input buffer that was deliberately not sent to the decoder. + */ + public int skippedInputBufferCount; + /** + * The number of rendered output buffers. + */ + public int renderedOutputBufferCount; + /** + * The number of skipped output buffers. + * <p> + * A skipped output buffer is an output buffer that was deliberately not rendered. + */ + public int skippedOutputBufferCount; + /** + * The number of dropped buffers. + * <p> + * A dropped buffer is an buffer that was supposed to be decoded/rendered, but was instead + * dropped because it could not be rendered in time. + */ + public int droppedBufferCount; + /** + * The maximum number of dropped buffers without an interleaving rendered output buffer. + * <p> + * Skipped output buffers are ignored for the purposes of calculating this value. + */ + public int maxConsecutiveDroppedBufferCount; + /** + * The number of times all buffers to a keyframe were dropped. + * <p> + * Each time buffers to a keyframe are dropped, this counter is increased by one, and the dropped + * buffer counters are increased by one (for the current output buffer) plus the number of buffers + * dropped from the source to advance to the keyframe. + */ + public int droppedToKeyframeCount; + + /** + * Should be called to ensure counter values are made visible across threads. The playback thread + * should call this method after updating the counter values. Any other thread should call this + * method before reading the counters. + */ + public synchronized void ensureUpdated() { + // Do nothing. The use of synchronized ensures a memory barrier should another thread also + // call this method. + } + + /** + * Merges the counts from {@code other} into this instance. + * + * @param other The {@link DecoderCounters} to merge into this instance. + */ + public void merge(DecoderCounters other) { + decoderInitCount += other.decoderInitCount; + decoderReleaseCount += other.decoderReleaseCount; + inputBufferCount += other.inputBufferCount; + skippedInputBufferCount += other.skippedInputBufferCount; + renderedOutputBufferCount += other.renderedOutputBufferCount; + skippedOutputBufferCount += other.skippedOutputBufferCount; + droppedBufferCount += other.droppedBufferCount; + maxConsecutiveDroppedBufferCount = Math.max(maxConsecutiveDroppedBufferCount, + other.maxConsecutiveDroppedBufferCount); + droppedToKeyframeCount += other.droppedToKeyframeCount; + } + +} diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java new file mode 100644 index 0000000000..254ecfdec8 --- /dev/null +++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/DecoderInputBuffer.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2016 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 org.mozilla.thirdparty.com.google.android.exoplayer2.decoder; + +import androidx.annotation.IntDef; +import androidx.annotation.Nullable; +import org.mozilla.thirdparty.com.google.android.exoplayer2.C; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.nio.ByteBuffer; +import org.checkerframework.checker.nullness.qual.EnsuresNonNull; + +/** + * Holds input for a decoder. + */ +public class DecoderInputBuffer extends Buffer { + + /** + * The buffer replacement mode, which may disable replacement. One of {@link + * #BUFFER_REPLACEMENT_MODE_DISABLED}, {@link #BUFFER_REPLACEMENT_MODE_NORMAL} or {@link + * #BUFFER_REPLACEMENT_MODE_DIRECT}. + */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + BUFFER_REPLACEMENT_MODE_DISABLED, + BUFFER_REPLACEMENT_MODE_NORMAL, + BUFFER_REPLACEMENT_MODE_DIRECT + }) + public @interface BufferReplacementMode {} + /** + * Disallows buffer replacement. + */ + public static final int BUFFER_REPLACEMENT_MODE_DISABLED = 0; + /** + * Allows buffer replacement using {@link ByteBuffer#allocate(int)}. + */ + public static final int BUFFER_REPLACEMENT_MODE_NORMAL = 1; + /** + * Allows buffer replacement using {@link ByteBuffer#allocateDirect(int)}. + */ + public static final int BUFFER_REPLACEMENT_MODE_DIRECT = 2; + + /** + * {@link CryptoInfo} for encrypted data. + */ + public final CryptoInfo cryptoInfo; + + /** The buffer's data, or {@code null} if no data has been set. */ + @Nullable public ByteBuffer data; + + // TODO: Remove this temporary signaling once end-of-stream propagation for clips using content + // protection is fixed. See [Internal: b/153326944] for details. + /** + * Whether the last attempt to read a sample into this buffer failed due to not yet having the DRM + * keys associated with the next sample. + */ + public boolean waitingForKeys; + + /** + * The time at which the sample should be presented. + */ + public long timeUs; + + /** + * Supplemental data related to the buffer, if {@link #hasSupplementalData()} returns true. If + * present, the buffer is populated with supplemental data from position 0 to its limit. + */ + @Nullable public ByteBuffer supplementalData; + + @BufferReplacementMode private final int bufferReplacementMode; + + /** + * Creates a new instance for which {@link #isFlagsOnly()} will return true. + * + * @return A new flags only input buffer. + */ + public static DecoderInputBuffer newFlagsOnlyInstance() { + return new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED); + } + + /** + * @param bufferReplacementMode Determines the behavior of {@link #ensureSpaceForWrite(int)}. One + * of {@link #BUFFER_REPLACEMENT_MODE_DISABLED}, {@link #BUFFER_REPLACEMENT_MODE_NORMAL} and + * {@link #BUFFER_REPLACEMENT_MODE_DIRECT}. + */ + public DecoderInputBuffer(@BufferReplacementMode int bufferReplacementMode) { + this.cryptoInfo = new CryptoInfo(); + this.bufferReplacementMode = bufferReplacementMode; + } + + /** + * Clears {@link #supplementalData} and ensures that it's large enough to accommodate {@code + * length} bytes. + * + * @param length The length of the supplemental data that must be accommodated, in bytes. + */ + @EnsuresNonNull("supplementalData") + public void resetSupplementalData(int length) { + if (supplementalData == null || supplementalData.capacity() < length) { + supplementalData = ByteBuffer.allocate(length); + } else { + supplementalData.clear(); + } + } + + /** + * Ensures that {@link #data} is large enough to accommodate a write of a given length at its + * current position. + * + * <p>If the capacity of {@link #data} is sufficient this method does nothing. If the capacity is + * insufficient then an attempt is made to replace {@link #data} with a new {@link ByteBuffer} + * whose capacity is sufficient. Data up to the current position is copied to the new buffer. + * + * @param length The length of the write that must be accommodated, in bytes. + * @throws IllegalStateException If there is insufficient capacity to accommodate the write and + * the buffer replacement mode of the holder is {@link #BUFFER_REPLACEMENT_MODE_DISABLED}. + */ + @EnsuresNonNull("data") + public void ensureSpaceForWrite(int length) { + if (data == null) { + data = createReplacementByteBuffer(length); + return; + } + // Check whether the current buffer is sufficient. + int capacity = data.capacity(); + int position = data.position(); + int requiredCapacity = position + length; + if (capacity >= requiredCapacity) { + return; + } + // Instantiate a new buffer if possible. + ByteBuffer newData = createReplacementByteBuffer(requiredCapacity); + newData.order(data.order()); + // Copy data up to the current position from the old buffer to the new one. + if (position > 0) { + data.flip(); + newData.put(data); + } + // Set the new buffer. + data = newData; + } + + /** + * Returns whether the buffer is only able to hold flags, meaning {@link #data} is null and + * its replacement mode is {@link #BUFFER_REPLACEMENT_MODE_DISABLED}. + */ + public final boolean isFlagsOnly() { + return data == null && bufferReplacementMode == BUFFER_REPLACEMENT_MODE_DISABLED; + } + + /** + * Returns whether the {@link C#BUFFER_FLAG_ENCRYPTED} flag is set. + */ + public final boolean isEncrypted() { + return getFlag(C.BUFFER_FLAG_ENCRYPTED); + } + + /** + * Flips {@link #data} and {@link #supplementalData} in preparation for being queued to a decoder. + * + * @see java.nio.Buffer#flip() + */ + public final void flip() { + data.flip(); + if (supplementalData != null) { + supplementalData.flip(); + } + } + + @Override + public void clear() { + super.clear(); + if (data != null) { + data.clear(); + } + if (supplementalData != null) { + supplementalData.clear(); + } + waitingForKeys = false; + } + + private ByteBuffer createReplacementByteBuffer(int requiredCapacity) { + if (bufferReplacementMode == BUFFER_REPLACEMENT_MODE_NORMAL) { + return ByteBuffer.allocate(requiredCapacity); + } else if (bufferReplacementMode == BUFFER_REPLACEMENT_MODE_DIRECT) { + return ByteBuffer.allocateDirect(requiredCapacity); + } else { + int currentCapacity = data == null ? 0 : data.capacity(); + throw new IllegalStateException("Buffer too small (" + currentCapacity + " < " + + requiredCapacity + ")"); + } + } + +} diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/OutputBuffer.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/OutputBuffer.java new file mode 100644 index 0000000000..73a8a7d2fd --- /dev/null +++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/OutputBuffer.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016 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 org.mozilla.thirdparty.com.google.android.exoplayer2.decoder; + +/** + * Output buffer decoded by a {@link Decoder}. + */ +public abstract class OutputBuffer extends Buffer { + + /** + * The presentation timestamp for the buffer, in microseconds. + */ + public long timeUs; + + /** + * The number of buffers immediately prior to this one that were skipped in the {@link Decoder}. + */ + public int skippedOutputBufferCount; + + /** + * Releases the output buffer for reuse. Must be called when the buffer is no longer needed. + */ + public abstract void release(); + +} diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/SimpleDecoder.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/SimpleDecoder.java new file mode 100644 index 0000000000..a193ad3c8e --- /dev/null +++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/SimpleDecoder.java @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2016 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 org.mozilla.thirdparty.com.google.android.exoplayer2.decoder; + +import androidx.annotation.CallSuper; +import androidx.annotation.Nullable; +import org.mozilla.thirdparty.com.google.android.exoplayer2.C; +import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Assertions; +import java.util.ArrayDeque; + +/** Base class for {@link Decoder}s that use their own decode thread. */ +@SuppressWarnings("UngroupedOverloads") +public abstract class SimpleDecoder< + I extends DecoderInputBuffer, O extends OutputBuffer, E extends Exception> + implements Decoder<I, O, E> { + + private final Thread decodeThread; + + private final Object lock; + private final ArrayDeque<I> queuedInputBuffers; + private final ArrayDeque<O> queuedOutputBuffers; + private final I[] availableInputBuffers; + private final O[] availableOutputBuffers; + + private int availableInputBufferCount; + private int availableOutputBufferCount; + private I dequeuedInputBuffer; + + private E exception; + private boolean flushed; + private boolean released; + private int skippedOutputBufferCount; + + /** + * @param inputBuffers An array of nulls that will be used to store references to input buffers. + * @param outputBuffers An array of nulls that will be used to store references to output buffers. + */ + protected SimpleDecoder(I[] inputBuffers, O[] outputBuffers) { + lock = new Object(); + queuedInputBuffers = new ArrayDeque<>(); + queuedOutputBuffers = new ArrayDeque<>(); + availableInputBuffers = inputBuffers; + availableInputBufferCount = inputBuffers.length; + for (int i = 0; i < availableInputBufferCount; i++) { + availableInputBuffers[i] = createInputBuffer(); + } + availableOutputBuffers = outputBuffers; + availableOutputBufferCount = outputBuffers.length; + for (int i = 0; i < availableOutputBufferCount; i++) { + availableOutputBuffers[i] = createOutputBuffer(); + } + decodeThread = new Thread() { + @Override + public void run() { + SimpleDecoder.this.run(); + } + }; + decodeThread.start(); + } + + /** + * Sets the initial size of each input buffer. + * <p> + * This method should only be called before the decoder is used (i.e. before the first call to + * {@link #dequeueInputBuffer()}. + * + * @param size The required input buffer size. + */ + protected final void setInitialInputBufferSize(int size) { + Assertions.checkState(availableInputBufferCount == availableInputBuffers.length); + for (I inputBuffer : availableInputBuffers) { + inputBuffer.ensureSpaceForWrite(size); + } + } + + @Override + @Nullable + public final I dequeueInputBuffer() throws E { + synchronized (lock) { + maybeThrowException(); + Assertions.checkState(dequeuedInputBuffer == null); + dequeuedInputBuffer = availableInputBufferCount == 0 ? null + : availableInputBuffers[--availableInputBufferCount]; + return dequeuedInputBuffer; + } + } + + @Override + public final void queueInputBuffer(I inputBuffer) throws E { + synchronized (lock) { + maybeThrowException(); + Assertions.checkArgument(inputBuffer == dequeuedInputBuffer); + queuedInputBuffers.addLast(inputBuffer); + maybeNotifyDecodeLoop(); + dequeuedInputBuffer = null; + } + } + + @Override + @Nullable + public final O dequeueOutputBuffer() throws E { + synchronized (lock) { + maybeThrowException(); + if (queuedOutputBuffers.isEmpty()) { + return null; + } + return queuedOutputBuffers.removeFirst(); + } + } + + /** + * Releases an output buffer back to the decoder. + * + * @param outputBuffer The output buffer being released. + */ + @CallSuper + protected void releaseOutputBuffer(O outputBuffer) { + synchronized (lock) { + releaseOutputBufferInternal(outputBuffer); + maybeNotifyDecodeLoop(); + } + } + + @Override + public final void flush() { + synchronized (lock) { + flushed = true; + skippedOutputBufferCount = 0; + if (dequeuedInputBuffer != null) { + releaseInputBufferInternal(dequeuedInputBuffer); + dequeuedInputBuffer = null; + } + while (!queuedInputBuffers.isEmpty()) { + releaseInputBufferInternal(queuedInputBuffers.removeFirst()); + } + while (!queuedOutputBuffers.isEmpty()) { + queuedOutputBuffers.removeFirst().release(); + } + exception = null; + } + } + + @CallSuper + @Override + public void release() { + synchronized (lock) { + released = true; + lock.notify(); + } + try { + decodeThread.join(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + /** + * Throws a decode exception, if there is one. + * + * @throws E The decode exception. + */ + private void maybeThrowException() throws E { + if (exception != null) { + throw exception; + } + } + + /** + * Notifies the decode loop if there exists a queued input buffer and an available output buffer + * to decode into. + * <p> + * Should only be called whilst synchronized on the lock object. + */ + private void maybeNotifyDecodeLoop() { + if (canDecodeBuffer()) { + lock.notify(); + } + } + + private void run() { + try { + while (decode()) { + // Do nothing. + } + } catch (InterruptedException e) { + // Not expected. + throw new IllegalStateException(e); + } + } + + private boolean decode() throws InterruptedException { + I inputBuffer; + O outputBuffer; + boolean resetDecoder; + + // Wait until we have an input buffer to decode, and an output buffer to decode into. + synchronized (lock) { + while (!released && !canDecodeBuffer()) { + lock.wait(); + } + if (released) { + return false; + } + inputBuffer = queuedInputBuffers.removeFirst(); + outputBuffer = availableOutputBuffers[--availableOutputBufferCount]; + resetDecoder = flushed; + flushed = false; + } + + if (inputBuffer.isEndOfStream()) { + outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); + } else { + if (inputBuffer.isDecodeOnly()) { + outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); + } + @Nullable E exception; + try { + exception = decode(inputBuffer, outputBuffer, resetDecoder); + } catch (RuntimeException e) { + // This can occur if a sample is malformed in a way that the decoder is not robust against. + // We don't want the process to die in this case, but we do want to propagate the error. + exception = createUnexpectedDecodeException(e); + } catch (OutOfMemoryError e) { + // This can occur if a sample is malformed in a way that causes the decoder to think it + // needs to allocate a large amount of memory. We don't want the process to die in this + // case, but we do want to propagate the error. + exception = createUnexpectedDecodeException(e); + } + if (exception != null) { + synchronized (lock) { + this.exception = exception; + } + return false; + } + } + + synchronized (lock) { + if (flushed) { + outputBuffer.release(); + } else if (outputBuffer.isDecodeOnly()) { + skippedOutputBufferCount++; + outputBuffer.release(); + } else { + outputBuffer.skippedOutputBufferCount = skippedOutputBufferCount; + skippedOutputBufferCount = 0; + queuedOutputBuffers.addLast(outputBuffer); + } + // Make the input buffer available again. + releaseInputBufferInternal(inputBuffer); + } + + return true; + } + + private boolean canDecodeBuffer() { + return !queuedInputBuffers.isEmpty() && availableOutputBufferCount > 0; + } + + private void releaseInputBufferInternal(I inputBuffer) { + inputBuffer.clear(); + availableInputBuffers[availableInputBufferCount++] = inputBuffer; + } + + private void releaseOutputBufferInternal(O outputBuffer) { + outputBuffer.clear(); + availableOutputBuffers[availableOutputBufferCount++] = outputBuffer; + } + + /** + * Creates a new input buffer. + */ + protected abstract I createInputBuffer(); + + /** + * Creates a new output buffer. + */ + protected abstract O createOutputBuffer(); + + /** + * Creates an exception to propagate for an unexpected decode error. + * + * @param error The unexpected decode error. + * @return The exception to propagate. + */ + protected abstract E createUnexpectedDecodeException(Throwable error); + + /** + * Decodes the {@code inputBuffer} and stores any decoded output in {@code outputBuffer}. + * + * @param inputBuffer The buffer to decode. + * @param outputBuffer The output buffer to store decoded data. The flag {@link + * C#BUFFER_FLAG_DECODE_ONLY} will be set if the same flag is set on {@code inputBuffer}, but + * may be set/unset as required. If the flag is set when the call returns then the output + * buffer will not be made available to dequeue. The output buffer may not have been populated + * in this case. + * @param reset Whether the decoder must be reset before decoding. + * @return A decoder exception if an error occurred, or null if decoding was successful. + */ + @Nullable + protected abstract E decode(I inputBuffer, O outputBuffer, boolean reset); +} diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/SimpleOutputBuffer.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/SimpleOutputBuffer.java new file mode 100644 index 0000000000..4b80d38e54 --- /dev/null +++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/SimpleOutputBuffer.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 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 org.mozilla.thirdparty.com.google.android.exoplayer2.decoder; + +import androidx.annotation.Nullable; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Buffer for {@link SimpleDecoder} output. + */ +public class SimpleOutputBuffer extends OutputBuffer { + + private final SimpleDecoder<?, SimpleOutputBuffer, ?> owner; + + @Nullable public ByteBuffer data; + + public SimpleOutputBuffer(SimpleDecoder<?, SimpleOutputBuffer, ?> owner) { + this.owner = owner; + } + + /** + * Initializes the buffer. + * + * @param timeUs The presentation timestamp for the buffer, in microseconds. + * @param size An upper bound on the size of the data that will be written to the buffer. + * @return The {@link #data} buffer, for convenience. + */ + public ByteBuffer init(long timeUs, int size) { + this.timeUs = timeUs; + if (data == null || data.capacity() < size) { + data = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()); + } + data.position(0); + data.limit(size); + return data; + } + + @Override + public void clear() { + super.clear(); + if (data != null) { + data.clear(); + } + } + + @Override + public void release() { + owner.releaseOutputBuffer(this); + } + +} diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/package-info.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/package-info.java new file mode 100644 index 0000000000..78a2c9f2e2 --- /dev/null +++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/decoder/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019 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. + */ +@NonNullApi +package org.mozilla.thirdparty.com.google.android.exoplayer2.decoder; + +import org.mozilla.thirdparty.com.google.android.exoplayer2.util.NonNullApi; |