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.decoder; 17 18 import androidx.annotation.CallSuper; 19 import androidx.annotation.Nullable; 20 import com.google.android.exoplayer2.C; 21 import com.google.android.exoplayer2.util.Assertions; 22 import java.util.ArrayDeque; 23 24 /** 25 * Base class for {@link Decoder}s that use their own decode thread and decode each input buffer 26 * immediately into a corresponding output buffer. 27 */ 28 @SuppressWarnings("UngroupedOverloads") 29 public abstract class SimpleDecoder< 30 I extends DecoderInputBuffer, O extends OutputBuffer, E extends Exception> 31 implements Decoder<I, O, E> { 32 33 private final Thread decodeThread; 34 35 private final Object lock; 36 private final ArrayDeque<I> queuedInputBuffers; 37 private final ArrayDeque<O> queuedOutputBuffers; 38 private final I[] availableInputBuffers; 39 private final O[] availableOutputBuffers; 40 41 private int availableInputBufferCount; 42 private int availableOutputBufferCount; 43 private I dequeuedInputBuffer; 44 45 private E exception; 46 private boolean flushed; 47 private boolean released; 48 private int skippedOutputBufferCount; 49 50 /** 51 * @param inputBuffers An array of nulls that will be used to store references to input buffers. 52 * @param outputBuffers An array of nulls that will be used to store references to output buffers. 53 */ SimpleDecoder(I[] inputBuffers, O[] outputBuffers)54 protected SimpleDecoder(I[] inputBuffers, O[] outputBuffers) { 55 lock = new Object(); 56 queuedInputBuffers = new ArrayDeque<>(); 57 queuedOutputBuffers = new ArrayDeque<>(); 58 availableInputBuffers = inputBuffers; 59 availableInputBufferCount = inputBuffers.length; 60 for (int i = 0; i < availableInputBufferCount; i++) { 61 availableInputBuffers[i] = createInputBuffer(); 62 } 63 availableOutputBuffers = outputBuffers; 64 availableOutputBufferCount = outputBuffers.length; 65 for (int i = 0; i < availableOutputBufferCount; i++) { 66 availableOutputBuffers[i] = createOutputBuffer(); 67 } 68 decodeThread = 69 new Thread("ExoPlayer:SimpleDecoder") { 70 @Override 71 public void run() { 72 SimpleDecoder.this.run(); 73 } 74 }; 75 decodeThread.start(); 76 } 77 78 /** 79 * Sets the initial size of each input buffer. 80 * <p> 81 * This method should only be called before the decoder is used (i.e. before the first call to 82 * {@link #dequeueInputBuffer()}. 83 * 84 * @param size The required input buffer size. 85 */ setInitialInputBufferSize(int size)86 protected final void setInitialInputBufferSize(int size) { 87 Assertions.checkState(availableInputBufferCount == availableInputBuffers.length); 88 for (I inputBuffer : availableInputBuffers) { 89 inputBuffer.ensureSpaceForWrite(size); 90 } 91 } 92 93 @Override 94 @Nullable dequeueInputBuffer()95 public final I dequeueInputBuffer() throws E { 96 synchronized (lock) { 97 maybeThrowException(); 98 Assertions.checkState(dequeuedInputBuffer == null); 99 dequeuedInputBuffer = availableInputBufferCount == 0 ? null 100 : availableInputBuffers[--availableInputBufferCount]; 101 return dequeuedInputBuffer; 102 } 103 } 104 105 @Override queueInputBuffer(I inputBuffer)106 public final void queueInputBuffer(I inputBuffer) throws E { 107 synchronized (lock) { 108 maybeThrowException(); 109 Assertions.checkArgument(inputBuffer == dequeuedInputBuffer); 110 queuedInputBuffers.addLast(inputBuffer); 111 maybeNotifyDecodeLoop(); 112 dequeuedInputBuffer = null; 113 } 114 } 115 116 @Override 117 @Nullable dequeueOutputBuffer()118 public final O dequeueOutputBuffer() throws E { 119 synchronized (lock) { 120 maybeThrowException(); 121 if (queuedOutputBuffers.isEmpty()) { 122 return null; 123 } 124 return queuedOutputBuffers.removeFirst(); 125 } 126 } 127 128 /** 129 * Releases an output buffer back to the decoder. 130 * 131 * @param outputBuffer The output buffer being released. 132 */ 133 @CallSuper releaseOutputBuffer(O outputBuffer)134 protected void releaseOutputBuffer(O outputBuffer) { 135 synchronized (lock) { 136 releaseOutputBufferInternal(outputBuffer); 137 maybeNotifyDecodeLoop(); 138 } 139 } 140 141 @Override flush()142 public final void flush() { 143 synchronized (lock) { 144 flushed = true; 145 skippedOutputBufferCount = 0; 146 if (dequeuedInputBuffer != null) { 147 releaseInputBufferInternal(dequeuedInputBuffer); 148 dequeuedInputBuffer = null; 149 } 150 while (!queuedInputBuffers.isEmpty()) { 151 releaseInputBufferInternal(queuedInputBuffers.removeFirst()); 152 } 153 while (!queuedOutputBuffers.isEmpty()) { 154 queuedOutputBuffers.removeFirst().release(); 155 } 156 exception = null; 157 } 158 } 159 160 @CallSuper 161 @Override release()162 public void release() { 163 synchronized (lock) { 164 released = true; 165 lock.notify(); 166 } 167 try { 168 decodeThread.join(); 169 } catch (InterruptedException e) { 170 Thread.currentThread().interrupt(); 171 } 172 } 173 174 /** 175 * Throws a decode exception, if there is one. 176 * 177 * @throws E The decode exception. 178 */ maybeThrowException()179 private void maybeThrowException() throws E { 180 if (exception != null) { 181 throw exception; 182 } 183 } 184 185 /** 186 * Notifies the decode loop if there exists a queued input buffer and an available output buffer 187 * to decode into. 188 * <p> 189 * Should only be called whilst synchronized on the lock object. 190 */ maybeNotifyDecodeLoop()191 private void maybeNotifyDecodeLoop() { 192 if (canDecodeBuffer()) { 193 lock.notify(); 194 } 195 } 196 run()197 private void run() { 198 try { 199 while (decode()) { 200 // Do nothing. 201 } 202 } catch (InterruptedException e) { 203 // Not expected. 204 throw new IllegalStateException(e); 205 } 206 } 207 decode()208 private boolean decode() throws InterruptedException { 209 I inputBuffer; 210 O outputBuffer; 211 boolean resetDecoder; 212 213 // Wait until we have an input buffer to decode, and an output buffer to decode into. 214 synchronized (lock) { 215 while (!released && !canDecodeBuffer()) { 216 lock.wait(); 217 } 218 if (released) { 219 return false; 220 } 221 inputBuffer = queuedInputBuffers.removeFirst(); 222 outputBuffer = availableOutputBuffers[--availableOutputBufferCount]; 223 resetDecoder = flushed; 224 flushed = false; 225 } 226 227 if (inputBuffer.isEndOfStream()) { 228 outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); 229 } else { 230 if (inputBuffer.isDecodeOnly()) { 231 outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); 232 } 233 @Nullable E exception; 234 try { 235 exception = decode(inputBuffer, outputBuffer, resetDecoder); 236 } catch (RuntimeException e) { 237 // This can occur if a sample is malformed in a way that the decoder is not robust against. 238 // We don't want the process to die in this case, but we do want to propagate the error. 239 exception = createUnexpectedDecodeException(e); 240 } catch (OutOfMemoryError e) { 241 // This can occur if a sample is malformed in a way that causes the decoder to think it 242 // needs to allocate a large amount of memory. We don't want the process to die in this 243 // case, but we do want to propagate the error. 244 exception = createUnexpectedDecodeException(e); 245 } 246 if (exception != null) { 247 synchronized (lock) { 248 this.exception = exception; 249 } 250 return false; 251 } 252 } 253 254 synchronized (lock) { 255 if (flushed) { 256 outputBuffer.release(); 257 } else if (outputBuffer.isDecodeOnly()) { 258 skippedOutputBufferCount++; 259 outputBuffer.release(); 260 } else { 261 outputBuffer.skippedOutputBufferCount = skippedOutputBufferCount; 262 skippedOutputBufferCount = 0; 263 queuedOutputBuffers.addLast(outputBuffer); 264 } 265 // Make the input buffer available again. 266 releaseInputBufferInternal(inputBuffer); 267 } 268 269 return true; 270 } 271 canDecodeBuffer()272 private boolean canDecodeBuffer() { 273 return !queuedInputBuffers.isEmpty() && availableOutputBufferCount > 0; 274 } 275 releaseInputBufferInternal(I inputBuffer)276 private void releaseInputBufferInternal(I inputBuffer) { 277 inputBuffer.clear(); 278 availableInputBuffers[availableInputBufferCount++] = inputBuffer; 279 } 280 releaseOutputBufferInternal(O outputBuffer)281 private void releaseOutputBufferInternal(O outputBuffer) { 282 outputBuffer.clear(); 283 availableOutputBuffers[availableOutputBufferCount++] = outputBuffer; 284 } 285 286 /** 287 * Creates a new input buffer. 288 */ createInputBuffer()289 protected abstract I createInputBuffer(); 290 291 /** 292 * Creates a new output buffer. 293 */ createOutputBuffer()294 protected abstract O createOutputBuffer(); 295 296 /** 297 * Creates an exception to propagate for an unexpected decode error. 298 * 299 * @param error The unexpected decode error. 300 * @return The exception to propagate. 301 */ createUnexpectedDecodeException(Throwable error)302 protected abstract E createUnexpectedDecodeException(Throwable error); 303 304 /** 305 * Decodes the {@code inputBuffer} and stores any decoded output in {@code outputBuffer}. 306 * 307 * @param inputBuffer The buffer to decode. 308 * @param outputBuffer The output buffer to store decoded data. The flag {@link 309 * C#BUFFER_FLAG_DECODE_ONLY} will be set if the same flag is set on {@code inputBuffer}, but 310 * may be set/unset as required. If the flag is set when the call returns then the output 311 * buffer will not be made available to dequeue. The output buffer may not have been populated 312 * in this case. 313 * @param reset Whether the decoder must be reset before decoding. 314 * @return A decoder exception if an error occurred, or null if decoding was successful. 315 */ 316 @Nullable decode(I inputBuffer, O outputBuffer, boolean reset)317 protected abstract E decode(I inputBuffer, O outputBuffer, boolean reset); 318 } 319