mirror of https://github.com/NekoX-Dev/NekoX.git
315 lines
9.5 KiB
Java
315 lines
9.5 KiB
Java
/*
|
|
* 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 com.google.android.exoplayer2.decoder;
|
|
|
|
import androidx.annotation.CallSuper;
|
|
import androidx.annotation.Nullable;
|
|
import com.google.android.exoplayer2.C;
|
|
import 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);
|
|
}
|