1 /*
2  *  Copyright 2018 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 package org.webrtc;
12 
13 import android.graphics.SurfaceTexture;
14 import android.media.MediaCodec;
15 import android.media.MediaCodecInfo.CodecCapabilities;
16 import android.media.MediaCrypto;
17 import android.media.MediaFormat;
18 import android.os.Bundle;
19 import android.support.annotation.Nullable;
20 import android.view.Surface;
21 import java.nio.ByteBuffer;
22 import java.util.ArrayList;
23 import java.util.List;
24 
25 /**
26  * Fake MediaCodec that implements the basic state machine.
27  *
28  * @note This class is only intended for single-threaded tests and is not thread-safe.
29  */
30 public class FakeMediaCodecWrapper implements MediaCodecWrapper {
31   private static final int NUM_INPUT_BUFFERS = 10;
32   private static final int NUM_OUTPUT_BUFFERS = 10;
33   private static final int MAX_ENCODED_DATA_SIZE_BYTES = 1_000;
34 
35   /**
36    * MediaCodec state as defined by:
37    * https://developer.android.com/reference/android/media/MediaCodec.html
38    */
39   public enum State {
40     STOPPED_CONFIGURED(Primary.STOPPED),
41     STOPPED_UNINITIALIZED(Primary.STOPPED),
42     STOPPED_ERROR(Primary.STOPPED),
43     EXECUTING_FLUSHED(Primary.EXECUTING),
44     EXECUTING_RUNNING(Primary.EXECUTING),
45     EXECUTING_END_OF_STREAM(Primary.EXECUTING),
46     RELEASED(Primary.RELEASED);
47 
48     public enum Primary { STOPPED, EXECUTING, RELEASED }
49 
50     private final Primary primary;
51 
State(Primary primary)52     State(Primary primary) {
53       this.primary = primary;
54     }
55 
getPrimary()56     public Primary getPrimary() {
57       return primary;
58     }
59   }
60 
61   /** Represents an output buffer that will be returned by dequeueOutputBuffer. */
62   public static class QueuedOutputBufferInfo {
63     private int index;
64     private int offset;
65     private int size;
66     private long presentationTimeUs;
67     private int flags;
68 
QueuedOutputBufferInfo( int index, int offset, int size, long presentationTimeUs, int flags)69     private QueuedOutputBufferInfo(
70         int index, int offset, int size, long presentationTimeUs, int flags) {
71       this.index = index;
72       this.offset = offset;
73       this.size = size;
74       this.presentationTimeUs = presentationTimeUs;
75       this.flags = flags;
76     }
77 
create( int index, int offset, int size, long presentationTimeUs, int flags)78     public static QueuedOutputBufferInfo create(
79         int index, int offset, int size, long presentationTimeUs, int flags) {
80       return new QueuedOutputBufferInfo(index, offset, size, presentationTimeUs, flags);
81     }
82 
getIndex()83     public int getIndex() {
84       return index;
85     }
86 
getOffset()87     public int getOffset() {
88       return offset;
89     }
90 
getSize()91     public int getSize() {
92       return size;
93     }
94 
getPresentationTimeUs()95     public long getPresentationTimeUs() {
96       return presentationTimeUs;
97     }
98 
getFlags()99     public int getFlags() {
100       return flags;
101     }
102   }
103 
104   private State state = State.STOPPED_UNINITIALIZED;
105   private @Nullable MediaFormat configuredFormat;
106   private int configuredFlags;
107   private final MediaFormat outputFormat;
108   private final ByteBuffer[] inputBuffers = new ByteBuffer[NUM_INPUT_BUFFERS];
109   private final ByteBuffer[] outputBuffers = new ByteBuffer[NUM_OUTPUT_BUFFERS];
110   private final boolean[] inputBufferReserved = new boolean[NUM_INPUT_BUFFERS];
111   private final boolean[] outputBufferReserved = new boolean[NUM_OUTPUT_BUFFERS];
112   private final List<QueuedOutputBufferInfo> queuedOutputBuffers = new ArrayList<>();
113 
FakeMediaCodecWrapper(MediaFormat outputFormat)114   public FakeMediaCodecWrapper(MediaFormat outputFormat) {
115     this.outputFormat = outputFormat;
116   }
117 
118   /** Returns the current simulated state of MediaCodec. */
getState()119   public State getState() {
120     return state;
121   }
122 
123   /** Gets the last configured media format passed to configure. */
getConfiguredFormat()124   public @Nullable MediaFormat getConfiguredFormat() {
125     return configuredFormat;
126   }
127 
128   /** Returns the last flags passed to configure. */
getConfiguredFlags()129   public int getConfiguredFlags() {
130     return configuredFlags;
131   }
132 
133   /**
134    * Adds a texture buffer that will be returned by dequeueOutputBuffer. Returns index of the
135    * buffer.
136    */
addOutputTexture(long presentationTimestampUs, int flags)137   public int addOutputTexture(long presentationTimestampUs, int flags) {
138     int index = getFreeOutputBuffer();
139     queuedOutputBuffers.add(QueuedOutputBufferInfo.create(
140         index, /* offset= */ 0, /* size= */ 0, presentationTimestampUs, flags));
141     return index;
142   }
143 
144   /**
145    * Adds a byte buffer buffer that will be returned by dequeueOutputBuffer. Returns index of the
146    * buffer.
147    */
addOutputData(byte[] data, long presentationTimestampUs, int flags)148   public int addOutputData(byte[] data, long presentationTimestampUs, int flags) {
149     int index = getFreeOutputBuffer();
150     ByteBuffer outputBuffer = outputBuffers[index];
151 
152     outputBuffer.clear();
153     outputBuffer.put(data);
154     outputBuffer.rewind();
155 
156     queuedOutputBuffers.add(QueuedOutputBufferInfo.create(
157         index, /* offset= */ 0, data.length, presentationTimestampUs, flags));
158     return index;
159   }
160 
161   /**
162    * Returns the first output buffer that is not reserved and reserves it. It will be stay reserved
163    * until released with releaseOutputBuffer.
164    */
getFreeOutputBuffer()165   private int getFreeOutputBuffer() {
166     for (int i = 0; i < NUM_OUTPUT_BUFFERS; i++) {
167       if (!outputBufferReserved[i]) {
168         outputBufferReserved[i] = true;
169         return i;
170       }
171     }
172     throw new RuntimeException("All output buffers reserved!");
173   }
174 
175   @Override
configure(MediaFormat format, Surface surface, MediaCrypto crypto, int flags)176   public void configure(MediaFormat format, Surface surface, MediaCrypto crypto, int flags) {
177     if (state != State.STOPPED_UNINITIALIZED) {
178       throw new IllegalStateException("Expected state STOPPED_UNINITIALIZED but was " + state);
179     }
180     state = State.STOPPED_CONFIGURED;
181     configuredFormat = format;
182     configuredFlags = flags;
183 
184     final int width = configuredFormat.getInteger(MediaFormat.KEY_WIDTH);
185     final int height = configuredFormat.getInteger(MediaFormat.KEY_HEIGHT);
186     final int yuvSize = width * height * 3 / 2;
187     final int inputBufferSize;
188     final int outputBufferSize;
189 
190     if ((flags & MediaCodec.CONFIGURE_FLAG_ENCODE) != 0) {
191       final int colorFormat = configuredFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT);
192 
193       inputBufferSize = colorFormat == CodecCapabilities.COLOR_FormatSurface ? 0 : yuvSize;
194       outputBufferSize = MAX_ENCODED_DATA_SIZE_BYTES;
195     } else {
196       inputBufferSize = MAX_ENCODED_DATA_SIZE_BYTES;
197       outputBufferSize = surface != null ? 0 : yuvSize;
198     }
199 
200     for (int i = 0; i < inputBuffers.length; i++) {
201       inputBuffers[i] = ByteBuffer.allocateDirect(inputBufferSize);
202     }
203     for (int i = 0; i < outputBuffers.length; i++) {
204       outputBuffers[i] = ByteBuffer.allocateDirect(outputBufferSize);
205     }
206   }
207 
208   @Override
start()209   public void start() {
210     if (state != State.STOPPED_CONFIGURED) {
211       throw new IllegalStateException("Expected state STOPPED_CONFIGURED but was " + state);
212     }
213     state = State.EXECUTING_RUNNING;
214   }
215 
216   @Override
flush()217   public void flush() {
218     if (state.getPrimary() != State.Primary.EXECUTING) {
219       throw new IllegalStateException("Expected state EXECUTING but was " + state);
220     }
221     state = State.EXECUTING_FLUSHED;
222   }
223 
224   @Override
stop()225   public void stop() {
226     if (state.getPrimary() != State.Primary.EXECUTING) {
227       throw new IllegalStateException("Expected state EXECUTING but was " + state);
228     }
229     state = State.STOPPED_UNINITIALIZED;
230   }
231 
232   @Override
release()233   public void release() {
234     state = State.RELEASED;
235   }
236 
237   @Override
dequeueInputBuffer(long timeoutUs)238   public int dequeueInputBuffer(long timeoutUs) {
239     if (state != State.EXECUTING_FLUSHED && state != State.EXECUTING_RUNNING) {
240       throw new IllegalStateException(
241           "Expected state EXECUTING_FLUSHED or EXECUTING_RUNNING but was " + state);
242     }
243     state = State.EXECUTING_RUNNING;
244 
245     for (int i = 0; i < NUM_INPUT_BUFFERS; i++) {
246       if (!inputBufferReserved[i]) {
247         inputBufferReserved[i] = true;
248         return i;
249       }
250     }
251     return MediaCodec.INFO_TRY_AGAIN_LATER;
252   }
253 
254   @Override
queueInputBuffer( int index, int offset, int size, long presentationTimeUs, int flags)255   public void queueInputBuffer(
256       int index, int offset, int size, long presentationTimeUs, int flags) {
257     if (state.getPrimary() != State.Primary.EXECUTING) {
258       throw new IllegalStateException("Expected state EXECUTING but was " + state);
259     }
260     if (flags != 0) {
261       throw new UnsupportedOperationException(
262           "Flags are not implemented in FakeMediaCodecWrapper.");
263     }
264   }
265 
266   @Override
dequeueOutputBuffer(MediaCodec.BufferInfo info, long timeoutUs)267   public int dequeueOutputBuffer(MediaCodec.BufferInfo info, long timeoutUs) {
268     if (state.getPrimary() != State.Primary.EXECUTING) {
269       throw new IllegalStateException("Expected state EXECUTING but was " + state);
270     }
271 
272     if (queuedOutputBuffers.isEmpty()) {
273       return MediaCodec.INFO_TRY_AGAIN_LATER;
274     }
275     QueuedOutputBufferInfo outputBufferInfo = queuedOutputBuffers.remove(/* index= */ 0);
276     info.set(outputBufferInfo.getOffset(), outputBufferInfo.getSize(),
277         outputBufferInfo.getPresentationTimeUs(), outputBufferInfo.getFlags());
278     return outputBufferInfo.getIndex();
279   }
280 
281   @Override
releaseOutputBuffer(int index, boolean render)282   public void releaseOutputBuffer(int index, boolean render) {
283     if (state.getPrimary() != State.Primary.EXECUTING) {
284       throw new IllegalStateException("Expected state EXECUTING but was " + state);
285     }
286     if (!outputBufferReserved[index]) {
287       throw new RuntimeException("Released output buffer was not in use.");
288     }
289     outputBufferReserved[index] = false;
290   }
291 
292   @Override
getInputBuffers()293   public ByteBuffer[] getInputBuffers() {
294     return inputBuffers;
295   }
296 
297   @Override
getOutputBuffers()298   public ByteBuffer[] getOutputBuffers() {
299     return outputBuffers;
300   }
301 
302   @Override
getOutputFormat()303   public MediaFormat getOutputFormat() {
304     return outputFormat;
305   }
306 
307   @Override
createInputSurface()308   public Surface createInputSurface() {
309     return new Surface(new SurfaceTexture(/* texName= */ 0));
310   }
311 
312   @Override
setParameters(Bundle params)313   public void setParameters(Bundle params) {}
314 }
315