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