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