• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.view.Surface;
20 import androidx.annotation.Nullable;
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 inputFormat;
108   private final MediaFormat outputFormat;
109   private final ByteBuffer[] inputBuffers = new ByteBuffer[NUM_INPUT_BUFFERS];
110   private final ByteBuffer[] outputBuffers = new ByteBuffer[NUM_OUTPUT_BUFFERS];
111   private final boolean[] inputBufferReserved = new boolean[NUM_INPUT_BUFFERS];
112   private final boolean[] outputBufferReserved = new boolean[NUM_OUTPUT_BUFFERS];
113   private final List<QueuedOutputBufferInfo> queuedOutputBuffers = new ArrayList<>();
114 
FakeMediaCodecWrapper(MediaFormat inputFormat, MediaFormat outputFormat)115   public FakeMediaCodecWrapper(MediaFormat inputFormat, MediaFormat outputFormat) {
116     this.inputFormat = inputFormat;
117     this.outputFormat = outputFormat;
118   }
119 
120   /** Returns the current simulated state of MediaCodec. */
getState()121   public State getState() {
122     return state;
123   }
124 
125   /** Gets the last configured media format passed to configure. */
getConfiguredFormat()126   public @Nullable MediaFormat getConfiguredFormat() {
127     return configuredFormat;
128   }
129 
130   /** Returns the last flags passed to configure. */
getConfiguredFlags()131   public int getConfiguredFlags() {
132     return configuredFlags;
133   }
134 
135   /**
136    * Adds a texture buffer that will be returned by dequeueOutputBuffer. Returns index of the
137    * buffer.
138    */
addOutputTexture(long presentationTimestampUs, int flags)139   public int addOutputTexture(long presentationTimestampUs, int flags) {
140     int index = getFreeOutputBuffer();
141     queuedOutputBuffers.add(QueuedOutputBufferInfo.create(
142         index, /* offset= */ 0, /* size= */ 0, presentationTimestampUs, flags));
143     return index;
144   }
145 
146   /**
147    * Adds a byte buffer buffer that will be returned by dequeueOutputBuffer. Returns index of the
148    * buffer.
149    */
addOutputData(byte[] data, long presentationTimestampUs, int flags)150   public int addOutputData(byte[] data, long presentationTimestampUs, int flags) {
151     int index = getFreeOutputBuffer();
152     ByteBuffer outputBuffer = outputBuffers[index];
153 
154     outputBuffer.clear();
155     outputBuffer.put(data);
156     outputBuffer.rewind();
157 
158     queuedOutputBuffers.add(QueuedOutputBufferInfo.create(
159         index, /* offset= */ 0, data.length, presentationTimestampUs, flags));
160     return index;
161   }
162 
163   /**
164    * Returns the first output buffer that is not reserved and reserves it. It will be stay reserved
165    * until released with releaseOutputBuffer.
166    */
getFreeOutputBuffer()167   private int getFreeOutputBuffer() {
168     for (int i = 0; i < NUM_OUTPUT_BUFFERS; i++) {
169       if (!outputBufferReserved[i]) {
170         outputBufferReserved[i] = true;
171         return i;
172       }
173     }
174     throw new RuntimeException("All output buffers reserved!");
175   }
176 
177   @Override
configure(MediaFormat format, Surface surface, MediaCrypto crypto, int flags)178   public void configure(MediaFormat format, Surface surface, MediaCrypto crypto, int flags) {
179     if (state != State.STOPPED_UNINITIALIZED) {
180       throw new IllegalStateException("Expected state STOPPED_UNINITIALIZED but was " + state);
181     }
182     state = State.STOPPED_CONFIGURED;
183     configuredFormat = format;
184     configuredFlags = flags;
185 
186     final int width = configuredFormat.getInteger(MediaFormat.KEY_WIDTH);
187     final int height = configuredFormat.getInteger(MediaFormat.KEY_HEIGHT);
188     final int yuvSize = width * height * 3 / 2;
189     final int inputBufferSize;
190     final int outputBufferSize;
191 
192     if ((flags & MediaCodec.CONFIGURE_FLAG_ENCODE) != 0) {
193       final int colorFormat = configuredFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT);
194 
195       inputBufferSize = colorFormat == CodecCapabilities.COLOR_FormatSurface ? 0 : yuvSize;
196       outputBufferSize = MAX_ENCODED_DATA_SIZE_BYTES;
197     } else {
198       inputBufferSize = MAX_ENCODED_DATA_SIZE_BYTES;
199       outputBufferSize = surface != null ? 0 : yuvSize;
200     }
201 
202     for (int i = 0; i < inputBuffers.length; i++) {
203       inputBuffers[i] = ByteBuffer.allocateDirect(inputBufferSize);
204     }
205     for (int i = 0; i < outputBuffers.length; i++) {
206       outputBuffers[i] = ByteBuffer.allocateDirect(outputBufferSize);
207     }
208   }
209 
210   @Override
start()211   public void start() {
212     if (state != State.STOPPED_CONFIGURED) {
213       throw new IllegalStateException("Expected state STOPPED_CONFIGURED but was " + state);
214     }
215     state = State.EXECUTING_RUNNING;
216   }
217 
218   @Override
flush()219   public void flush() {
220     if (state.getPrimary() != State.Primary.EXECUTING) {
221       throw new IllegalStateException("Expected state EXECUTING but was " + state);
222     }
223     state = State.EXECUTING_FLUSHED;
224   }
225 
226   @Override
stop()227   public void stop() {
228     if (state.getPrimary() != State.Primary.EXECUTING) {
229       throw new IllegalStateException("Expected state EXECUTING but was " + state);
230     }
231     state = State.STOPPED_UNINITIALIZED;
232   }
233 
234   @Override
release()235   public void release() {
236     state = State.RELEASED;
237   }
238 
239   @Override
dequeueInputBuffer(long timeoutUs)240   public int dequeueInputBuffer(long timeoutUs) {
241     if (state != State.EXECUTING_FLUSHED && state != State.EXECUTING_RUNNING) {
242       throw new IllegalStateException(
243           "Expected state EXECUTING_FLUSHED or EXECUTING_RUNNING but was " + state);
244     }
245     state = State.EXECUTING_RUNNING;
246 
247     for (int i = 0; i < NUM_INPUT_BUFFERS; i++) {
248       if (!inputBufferReserved[i]) {
249         inputBufferReserved[i] = true;
250         return i;
251       }
252     }
253     return MediaCodec.INFO_TRY_AGAIN_LATER;
254   }
255 
256   @Override
queueInputBuffer( int index, int offset, int size, long presentationTimeUs, int flags)257   public void queueInputBuffer(
258       int index, int offset, int size, long presentationTimeUs, int flags) {
259     if (state.getPrimary() != State.Primary.EXECUTING) {
260       throw new IllegalStateException("Expected state EXECUTING but was " + state);
261     }
262     if (flags != 0) {
263       throw new UnsupportedOperationException(
264           "Flags are not implemented in FakeMediaCodecWrapper.");
265     }
266   }
267 
268   @Override
dequeueOutputBuffer(MediaCodec.BufferInfo info, long timeoutUs)269   public int dequeueOutputBuffer(MediaCodec.BufferInfo info, long timeoutUs) {
270     if (state.getPrimary() != State.Primary.EXECUTING) {
271       throw new IllegalStateException("Expected state EXECUTING but was " + state);
272     }
273 
274     if (queuedOutputBuffers.isEmpty()) {
275       return MediaCodec.INFO_TRY_AGAIN_LATER;
276     }
277     QueuedOutputBufferInfo outputBufferInfo = queuedOutputBuffers.remove(/* index= */ 0);
278     info.set(outputBufferInfo.getOffset(), outputBufferInfo.getSize(),
279         outputBufferInfo.getPresentationTimeUs(), outputBufferInfo.getFlags());
280     return outputBufferInfo.getIndex();
281   }
282 
283   @Override
releaseOutputBuffer(int index, boolean render)284   public void releaseOutputBuffer(int index, boolean render) {
285     if (state.getPrimary() != State.Primary.EXECUTING) {
286       throw new IllegalStateException("Expected state EXECUTING but was " + state);
287     }
288     if (!outputBufferReserved[index]) {
289       throw new RuntimeException("Released output buffer was not in use.");
290     }
291     outputBufferReserved[index] = false;
292   }
293 
294   @Override
getInputBuffer(int index)295   public ByteBuffer getInputBuffer(int index) {
296     return inputBuffers[index];
297   }
298 
299   @Override
getOutputBuffer(int index)300   public ByteBuffer getOutputBuffer(int index) {
301     return outputBuffers[index];
302   }
303 
304   @Override
getInputFormat()305   public MediaFormat getInputFormat() {
306     return inputFormat;
307   }
308 
309   @Override
getOutputFormat()310   public MediaFormat getOutputFormat() {
311     return outputFormat;
312   }
313 
314   @Override
createInputSurface()315   public Surface createInputSurface() {
316     return new Surface(new SurfaceTexture(/* texName= */ 0));
317   }
318 
319   @Override
setParameters(Bundle params)320   public void setParameters(Bundle params) {}
321 }
322