1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.JELLY_BEAN; 4 import static android.os.Build.VERSION_CODES.LOLLIPOP; 5 import static android.os.Build.VERSION_CODES.N_MR1; 6 import static android.os.Build.VERSION_CODES.O; 7 import static android.os.Build.VERSION_CODES.TIRAMISU; 8 import static com.google.common.base.Preconditions.checkState; 9 import static java.util.concurrent.TimeUnit.MICROSECONDS; 10 import static org.robolectric.shadow.api.Shadow.invokeConstructor; 11 import static org.robolectric.util.ReflectionHelpers.callConstructor; 12 13 import android.annotation.NonNull; 14 import android.annotation.Nullable; 15 import android.media.MediaCodec; 16 import android.media.MediaCodec.BufferInfo; 17 import android.media.MediaCodec.CodecException; 18 import android.media.MediaCodecInfo; 19 import android.media.MediaCodecList; 20 import android.media.MediaCrypto; 21 import android.media.MediaFormat; 22 import android.view.Surface; 23 import com.google.common.annotations.VisibleForTesting; 24 import java.nio.Buffer; 25 import java.nio.ByteBuffer; 26 import java.nio.ByteOrder; 27 import java.util.ArrayList; 28 import java.util.Collections; 29 import java.util.HashMap; 30 import java.util.List; 31 import java.util.Map; 32 import java.util.concurrent.BlockingQueue; 33 import java.util.concurrent.LinkedBlockingDeque; 34 import org.robolectric.annotation.Implementation; 35 import org.robolectric.annotation.Implements; 36 import org.robolectric.annotation.RealObject; 37 import org.robolectric.annotation.Resetter; 38 import org.robolectric.util.ReflectionHelpers; 39 import org.robolectric.util.ReflectionHelpers.ClassParameter; 40 import org.robolectric.versioning.AndroidVersions.U; 41 42 /** 43 * Implementation of {@link android.media.MediaCodec} which supports both asynchronous and 44 * synchronous modes. 45 * 46 * <p>By default for any encoded required, a 1 to 1 mapping will be used between the input and 47 * output buffers. Data from a queued input buffer will be copied to the output buffer. In the case 48 * that is it necessary so simulate some form of data compression, a custom encoder or decoder can 49 * be added via {@link #addEncoder(String, CodecConfig)} and {@link #addDecoder(String, 50 * CodecConfig)} respectively. 51 * 52 * <p>Asynchronous mode: Once the codec is started, a format change will be reported, switching to 53 * an empty {@link android.media.MediaFormat} with fake codec-specific info. Following this, the 54 * implementation will present an input buffer, which will be copied to an output buffer once 55 * queued, which will be subsequently presented to the callback handler. 56 */ 57 @Implements(value = MediaCodec.class, minSdk = JELLY_BEAN, looseSignatures = true) 58 public class ShadowMediaCodec { 59 private static final int DEFAULT_BUFFER_SIZE = 512; 60 @VisibleForTesting static final int BUFFER_COUNT = 10; 61 62 // Must keep in sync with MediaCodec.java 63 private static final int EVENT_CALLBACK = 1; 64 private static final int CB_INPUT_AVAILABLE = 1; 65 private static final int CB_OUTPUT_AVAILABLE = 2; 66 private static final int CB_OUTPUT_FORMAT_CHANGE = 4; 67 68 private static final Map<String, CodecConfig> encoders = new HashMap<>(); 69 private static final Map<String, CodecConfig> decoders = new HashMap<>(); 70 71 /** 72 * Default codec that simply moves bytes from the input to the output buffers where the buffers 73 * are of equal size. 74 */ 75 private static final CodecConfig DEFAULT_CODEC = 76 new CodecConfig(DEFAULT_BUFFER_SIZE, DEFAULT_BUFFER_SIZE, (in, out) -> out.put(in)); 77 78 /** Add a fake encoding codec to the Shadow. */ addEncoder(String type, CodecConfig config)79 public static void addEncoder(String type, CodecConfig config) { 80 encoders.put(type, config); 81 } 82 83 /** Add a fake decoding codec to the Shadow. */ addDecoder(String type, CodecConfig config)84 public static void addDecoder(String type, CodecConfig config) { 85 decoders.put(type, config); 86 } 87 88 /** Clears any previously added encoders and decoders. */ 89 @Resetter clearCodecs()90 public static void clearCodecs() { 91 encoders.clear(); 92 decoders.clear(); 93 } 94 95 @RealObject private MediaCodec realCodec; 96 @Nullable private CodecConfig.Codec fakeCodec; 97 98 @Nullable private MediaCodec.Callback callback; 99 100 @Nullable private MediaFormat pendingOutputFormat; 101 @Nullable private MediaFormat outputFormat; 102 103 private final BlockingQueue<Integer> inputBuffersPendingDequeue = new LinkedBlockingDeque<>(); 104 private final BlockingQueue<Integer> outputBuffersPendingDequeue = new LinkedBlockingDeque<>(); 105 /* 106 * Ensures that a dequeued input buffer cannot be queued again until its corresponding output 107 * buffer is dequeued and released. 108 */ 109 private final List<Integer> inputBuffersPendingQueuing = 110 Collections.synchronizedList(new ArrayList<>()); 111 112 private final ByteBuffer[] inputBuffers = new ByteBuffer[BUFFER_COUNT]; 113 private final ByteBuffer[] outputBuffers = new ByteBuffer[BUFFER_COUNT]; 114 private final BufferInfo[] outputBufferInfos = new BufferInfo[BUFFER_COUNT]; 115 116 private boolean isAsync = false; 117 118 // Member methods. 119 120 @Implementation __constructor__(String name, boolean nameIsType, boolean encoder)121 protected void __constructor__(String name, boolean nameIsType, boolean encoder) { 122 invokeConstructor( 123 MediaCodec.class, 124 realCodec, 125 ClassParameter.from(String.class, name), 126 ClassParameter.from(boolean.class, nameIsType), 127 ClassParameter.from(boolean.class, encoder)); 128 129 if (!nameIsType) { 130 for (MediaCodecInfo codecInfo : 131 new MediaCodecList(MediaCodecList.ALL_CODECS).getCodecInfos()) { 132 if (codecInfo.getName().equals(name)) { 133 encoder = codecInfo.isEncoder(); 134 break; 135 } 136 } 137 } 138 139 CodecConfig codecConfig = 140 encoder 141 ? encoders.getOrDefault(name, DEFAULT_CODEC) 142 : decoders.getOrDefault(name, DEFAULT_CODEC); 143 fakeCodec = codecConfig.codec; 144 145 for (int i = 0; i < BUFFER_COUNT; i++) { 146 inputBuffers[i] = 147 ByteBuffer.allocateDirect(codecConfig.inputBufferSize).order(ByteOrder.LITTLE_ENDIAN); 148 outputBuffers[i] = 149 ByteBuffer.allocateDirect(codecConfig.outputBufferSize).order(ByteOrder.LITTLE_ENDIAN); 150 outputBufferInfos[i] = new BufferInfo(); 151 } 152 } 153 154 /** Saves the callback to allow use inside the shadow. */ 155 @Implementation(minSdk = LOLLIPOP) native_setCallback(MediaCodec.Callback callback)156 protected void native_setCallback(MediaCodec.Callback callback) { 157 this.callback = callback; 158 } 159 160 @Implementation(minSdk = LOLLIPOP, maxSdk = N_MR1) native_configure( String[] keys, Object[] values, Surface surface, MediaCrypto crypto, int flags)161 protected void native_configure( 162 String[] keys, Object[] values, Surface surface, MediaCrypto crypto, int flags) { 163 innerConfigure(keys, values, surface, crypto, flags); 164 } 165 166 @Implementation(minSdk = O) native_configure( Object keys, Object values, Object surface, Object crypto, Object descramblerBinder, Object flags)167 protected void native_configure( 168 Object keys, 169 Object values, 170 Object surface, 171 Object crypto, 172 Object descramblerBinder, 173 Object flags) { 174 innerConfigure( 175 (String[]) keys, (Object[]) values, (Surface) surface, (MediaCrypto) crypto, (int) flags); 176 } 177 innerConfigure( String[] keys, Object[] values, @Nullable Surface surface, @Nullable MediaCrypto mediaCrypto, int flags)178 private void innerConfigure( 179 String[] keys, 180 Object[] values, 181 @Nullable Surface surface, 182 @Nullable MediaCrypto mediaCrypto, 183 int flags) { 184 isAsync = callback != null; 185 pendingOutputFormat = recreateMediaFormatFromKeysValues(keys, values); 186 fakeCodec.onConfigured(pendingOutputFormat, surface, mediaCrypto, flags); 187 } 188 189 /** 190 * Starts the async encoding process, by first reporting a format change event, and then 191 * presenting an input buffer to the callback. 192 */ 193 @Implementation(minSdk = LOLLIPOP) native_start()194 protected void native_start() { 195 // Reset state 196 inputBuffersPendingDequeue.clear(); 197 outputBuffersPendingDequeue.clear(); 198 for (int i = 0; i < BUFFER_COUNT; i++) { 199 inputBuffersPendingDequeue.add(i); 200 } 201 202 if (isAsync) { 203 // Report the format as changed, to simulate adding codec specific info before making input 204 // buffers available. 205 HashMap<String, Object> format = new HashMap<>(); 206 format.put("csd-0", ByteBuffer.wrap(new byte[] {0x13, 0x10})); 207 format.put("csd-1", ByteBuffer.wrap(new byte[0])); 208 postFakeNativeEvent(EVENT_CALLBACK, CB_OUTPUT_FORMAT_CHANGE, 0, format); 209 210 try { 211 makeInputBufferAvailable(inputBuffersPendingDequeue.take()); 212 } catch (InterruptedException e) { 213 Thread.currentThread().interrupt(); 214 } 215 } 216 } 217 218 /** Flushes the available output buffers. */ 219 @Implementation(minSdk = LOLLIPOP) native_flush()220 protected void native_flush() { 221 // Reset input buffers only if the MediaCodec is in synchronous mode. If it is in asynchronous 222 // mode, the client needs to call start(). 223 if (!isAsync) { 224 inputBuffersPendingDequeue.clear(); 225 outputBuffersPendingDequeue.clear(); 226 inputBuffersPendingQueuing.clear(); 227 for (int i = 0; i < BUFFER_COUNT; i++) { 228 inputBuffersPendingDequeue.add(i); 229 ((Buffer) inputBuffers[i]).clear(); 230 } 231 } 232 } 233 234 /** Returns the shadow buffers used for input or output. */ 235 @Implementation getBuffers(boolean input)236 protected ByteBuffer[] getBuffers(boolean input) { 237 return input ? inputBuffers : outputBuffers; 238 } 239 240 /** Returns the input or output buffer corresponding to the given index, or null if invalid. */ 241 @Implementation(minSdk = LOLLIPOP) getBuffer(boolean input, int index)242 protected ByteBuffer getBuffer(boolean input, int index) { 243 ByteBuffer[] buffers = input ? inputBuffers : outputBuffers; 244 return index >= 0 && index < buffers.length && !(input && codecOwnsInputBuffer(index)) 245 ? buffers[index] 246 : null; 247 } 248 249 @Implementation(minSdk = LOLLIPOP) native_dequeueInputBuffer(long timeoutUs)250 protected int native_dequeueInputBuffer(long timeoutUs) { 251 checkState(!isAsync, "Attempting to deque buffer in Async mode."); 252 try { 253 Integer index; 254 255 if (timeoutUs < 0) { 256 index = inputBuffersPendingDequeue.take(); 257 } else { 258 index = inputBuffersPendingDequeue.poll(timeoutUs, MICROSECONDS); 259 } 260 261 if (index == null) { 262 return MediaCodec.INFO_TRY_AGAIN_LATER; 263 } 264 265 inputBuffersPendingQueuing.add(index); 266 return index; 267 } catch (InterruptedException e) { 268 Thread.currentThread().interrupt(); 269 return MediaCodec.INFO_TRY_AGAIN_LATER; 270 } 271 } 272 273 /** 274 * Triggers presentation of the corresponding output buffer for the given input buffer, and passes 275 * the given metadata as buffer info. 276 */ 277 @Implementation(minSdk = LOLLIPOP) native_queueInputBuffer( int index, int offset, int size, long presentationTimeUs, int flags)278 protected void native_queueInputBuffer( 279 int index, int offset, int size, long presentationTimeUs, int flags) { 280 if (index < 0 281 || index >= inputBuffers.length 282 || codecOwnsInputBuffer(index) 283 || !canQueueInputBuffer(index)) { 284 throwCodecException( 285 /* errorCode= */ 0, /* actionCode= */ 0, "Input buffer not owned by client: " + index); 286 } 287 288 BufferInfo info = new BufferInfo(); 289 info.set(offset, size, presentationTimeUs, flags); 290 291 makeOutputBufferAvailable(index, info); 292 inputBuffersPendingQueuing.remove(Integer.valueOf(index)); 293 } 294 295 @Implementation(minSdk = LOLLIPOP) native_dequeueOutputBuffer(BufferInfo info, long timeoutUs)296 protected int native_dequeueOutputBuffer(BufferInfo info, long timeoutUs) { 297 checkState(!isAsync, "Attempting to deque buffer in Async mode."); 298 try { 299 if (pendingOutputFormat != null) { 300 outputFormat = pendingOutputFormat; 301 pendingOutputFormat = null; 302 return MediaCodec.INFO_OUTPUT_FORMAT_CHANGED; 303 } 304 305 Integer index; 306 if (timeoutUs < 0) { 307 index = outputBuffersPendingDequeue.take(); 308 } else { 309 index = outputBuffersPendingDequeue.poll(timeoutUs, MICROSECONDS); 310 } 311 312 if (index == null) { 313 return MediaCodec.INFO_TRY_AGAIN_LATER; 314 } 315 316 copyBufferInfo(outputBufferInfos[index], info); 317 318 return index; 319 } catch (InterruptedException e) { 320 Thread.currentThread().interrupt(); 321 return MediaCodec.INFO_TRY_AGAIN_LATER; 322 } 323 } 324 325 @Implementation releaseOutputBuffer(int index, boolean renderer)326 protected void releaseOutputBuffer(int index, boolean renderer) { 327 releaseOutputBuffer(index); 328 } 329 330 @Implementation(minSdk = LOLLIPOP) releaseOutputBuffer(int index, long renderTimestampNs)331 protected void releaseOutputBuffer(int index, long renderTimestampNs) { 332 releaseOutputBuffer(index); 333 } 334 releaseOutputBuffer(int index)335 private void releaseOutputBuffer(int index) { 336 if (outputBuffersPendingDequeue.contains(index)) { 337 throw new IllegalStateException("Cannot release a buffer when it's still owned by the codec"); 338 } 339 makeInputBufferAvailable(index); 340 } 341 makeInputBufferAvailable(int index)342 private void makeInputBufferAvailable(int index) { 343 if (index < 0 || index >= inputBuffers.length) { 344 throw new IndexOutOfBoundsException("Cannot make non-existent input available."); 345 } 346 347 // Reset the input buffer. 348 ((Buffer) inputBuffers[index]).clear(); 349 350 if (isAsync) { 351 inputBuffersPendingQueuing.add(index); 352 // Signal input buffer availability. 353 postFakeNativeEvent(EVENT_CALLBACK, CB_INPUT_AVAILABLE, index, null); 354 } else { 355 inputBuffersPendingDequeue.add(index); 356 } 357 } 358 makeOutputBufferAvailable(int index, BufferInfo info)359 private void makeOutputBufferAvailable(int index, BufferInfo info) { 360 if (index < 0 || index >= outputBuffers.length) { 361 throw new IndexOutOfBoundsException("Cannot make non-existent output buffer available."); 362 } 363 Buffer inputBuffer = inputBuffers[index]; 364 Buffer outputBuffer = outputBuffers[index]; 365 BufferInfo outputBufferInfo = outputBufferInfos[index]; 366 367 // Clears the output buffer, as it's already fully consumed. 368 outputBuffer.clear(); 369 370 inputBuffer.position(info.offset).limit(info.offset + info.size); 371 fakeCodec.process(inputBuffers[index], outputBuffers[index]); 372 373 outputBufferInfo.flags = info.flags; 374 outputBufferInfo.size = outputBuffer.position(); 375 outputBufferInfo.offset = info.offset; 376 outputBufferInfo.presentationTimeUs = info.presentationTimeUs; 377 outputBuffer.flip(); 378 379 outputBuffersPendingDequeue.add(index); 380 381 if (isAsync) { 382 // Dequeue the buffer to signal its availablility to the client. 383 outputBuffersPendingDequeue.remove(Integer.valueOf(index)); 384 // Signal output buffer availability. 385 postFakeNativeEvent(EVENT_CALLBACK, CB_OUTPUT_AVAILABLE, index, outputBufferInfos[index]); 386 } 387 } 388 postFakeNativeEvent(int what, int arg1, int arg2, @Nullable Object obj)389 private void postFakeNativeEvent(int what, int arg1, int arg2, @Nullable Object obj) { 390 ReflectionHelpers.callInstanceMethod( 391 MediaCodec.class, 392 realCodec, 393 "postEventFromNative", 394 ClassParameter.from(int.class, what), 395 ClassParameter.from(int.class, arg1), 396 ClassParameter.from(int.class, arg2), 397 ClassParameter.from(Object.class, obj)); 398 } 399 codecOwnsInputBuffer(int index)400 private boolean codecOwnsInputBuffer(int index) { 401 return inputBuffersPendingDequeue.contains(index); 402 } 403 canQueueInputBuffer(int index)404 private boolean canQueueInputBuffer(int index) { 405 return inputBuffersPendingQueuing.contains(index); 406 } 407 408 /** Prevents calling Android-only methods on basic ByteBuffer objects. */ 409 @Implementation(minSdk = LOLLIPOP, maxSdk = TIRAMISU) invalidateByteBuffer(@ullable ByteBuffer[] buffers, int index)410 protected void invalidateByteBuffer(@Nullable ByteBuffer[] buffers, int index) {} 411 412 @Implementation(minSdk = U.SDK_INT) invalidateByteBufferLocked( @ullable ByteBuffer[] buffers, int index, boolean input)413 protected void invalidateByteBufferLocked( 414 @Nullable ByteBuffer[] buffers, int index, boolean input) {} 415 416 /** Prevents calling Android-only methods on basic ByteBuffer objects. */ 417 @Implementation(minSdk = LOLLIPOP, maxSdk = TIRAMISU) validateInputByteBuffer(@ullable ByteBuffer[] buffers, int index)418 protected void validateInputByteBuffer(@Nullable ByteBuffer[] buffers, int index) {} 419 420 @Implementation(minSdk = U.SDK_INT) validateInputByteBufferLocked(@ullable ByteBuffer[] buffers, int index)421 protected void validateInputByteBufferLocked(@Nullable ByteBuffer[] buffers, int index) {} 422 423 /** Prevents calling Android-only methods on basic ByteBuffer objects. */ 424 @Implementation(minSdk = LOLLIPOP, maxSdk = TIRAMISU) revalidateByteBuffer(@ullable ByteBuffer[] buffers, int index)425 protected void revalidateByteBuffer(@Nullable ByteBuffer[] buffers, int index) {} 426 427 @Implementation(minSdk = U.SDK_INT) revalidateByteBuffer(@ullable ByteBuffer[] buffers, int index, boolean input)428 protected void revalidateByteBuffer(@Nullable ByteBuffer[] buffers, int index, boolean input) {} 429 430 /** 431 * Prevents calling Android-only methods on basic ByteBuffer objects. Replicates existing behavior 432 * adjusting buffer positions and limits. 433 */ 434 @Implementation(minSdk = LOLLIPOP, maxSdk = TIRAMISU) validateOutputByteBuffer( @ullable ByteBuffer[] buffers, int index, @NonNull BufferInfo info)435 protected void validateOutputByteBuffer( 436 @Nullable ByteBuffer[] buffers, int index, @NonNull BufferInfo info) { 437 if (buffers != null && index >= 0 && index < buffers.length) { 438 Buffer buffer = (Buffer) buffers[index]; 439 if (buffer != null) { 440 buffer.limit(info.offset + info.size).position(info.offset); 441 } 442 } 443 } 444 445 @Implementation(minSdk = U.SDK_INT) validateOutputByteBufferLocked( @ullable ByteBuffer[] buffers, int index, @NonNull BufferInfo info)446 protected void validateOutputByteBufferLocked( 447 @Nullable ByteBuffer[] buffers, int index, @NonNull BufferInfo info) { 448 validateOutputByteBuffer(buffers, index, info); 449 } 450 451 /** Prevents calling Android-only methods on basic ByteBuffer objects. */ 452 @Implementation(minSdk = LOLLIPOP, maxSdk = TIRAMISU) invalidateByteBuffers(@ullable ByteBuffer[] buffers)453 protected void invalidateByteBuffers(@Nullable ByteBuffer[] buffers) {} 454 455 @Implementation(minSdk = U.SDK_INT) invalidateByteBuffersLocked(@ullable ByteBuffer[] buffers)456 protected void invalidateByteBuffersLocked(@Nullable ByteBuffer[] buffers) {} 457 458 /** Prevents attempting to free non-direct ByteBuffer objects. */ 459 @Implementation(minSdk = LOLLIPOP, maxSdk = TIRAMISU) freeByteBuffer(@ullable ByteBuffer buffer)460 protected void freeByteBuffer(@Nullable ByteBuffer buffer) {} 461 462 @Implementation(minSdk = U.SDK_INT) freeByteBufferLocked(@ullable ByteBuffer buffer)463 protected void freeByteBufferLocked(@Nullable ByteBuffer buffer) {} 464 465 /** Shadows CodecBuffer to prevent attempting to free non-direct ByteBuffer objects. */ 466 @Implements(className = "android.media.MediaCodec$BufferMap$CodecBuffer", minSdk = LOLLIPOP) 467 protected static class ShadowCodecBuffer { 468 469 // Seems to be required to work. ShadowCodecBuffer()470 public ShadowCodecBuffer() {} 471 472 // Seems to be required to work. 473 @Implementation __constructor__()474 protected void __constructor__() {} 475 476 /** Prevents attempting to free non-direct ByteBuffer objects. */ 477 @Implementation free()478 protected void free() {} 479 } 480 481 /** Returns a default {@link MediaFormat} if not set via {@link #getOutputFormat()}. */ 482 @Implementation getOutputFormat()483 protected MediaFormat getOutputFormat() { 484 if (outputFormat == null) { 485 return new MediaFormat(); 486 } 487 return outputFormat; 488 } 489 copyBufferInfo(BufferInfo from, BufferInfo to)490 private static void copyBufferInfo(BufferInfo from, BufferInfo to) { 491 to.set(from.offset, from.size, from.presentationTimeUs, from.flags); 492 } 493 recreateMediaFormatFromKeysValues(String[] keys, Object[] values)494 private static MediaFormat recreateMediaFormatFromKeysValues(String[] keys, Object[] values) { 495 MediaFormat mediaFormat = new MediaFormat(); 496 497 // This usage of `instanceof` is how API 29 `MediaFormat#getValueTypeForKey` works. 498 for (int i = 0; i < keys.length; i++) { 499 if (values[i] == null || values[i] instanceof ByteBuffer) { 500 mediaFormat.setByteBuffer(keys[i], (ByteBuffer) values[i]); 501 } else if (values[i] instanceof Integer) { 502 mediaFormat.setInteger(keys[i], (Integer) values[i]); 503 } else if (values[i] instanceof Long) { 504 mediaFormat.setLong(keys[i], (Long) values[i]); 505 } else if (values[i] instanceof Float) { 506 mediaFormat.setFloat(keys[i], (Float) values[i]); 507 } else if (values[i] instanceof String) { 508 mediaFormat.setString(keys[i], (String) values[i]); 509 } else { 510 throw new IllegalArgumentException("Invalid value for key."); 511 } 512 } 513 514 return mediaFormat; 515 } 516 517 /** 518 * Configuration that can be supplied to {@link ShadowMediaCodec} to simulate actual 519 * encoding/decoding. 520 */ 521 public static final class CodecConfig { 522 523 private final int inputBufferSize; 524 private final int outputBufferSize; 525 private final Codec codec; 526 527 /** 528 * @param inputBufferSize the size of the buffers offered as input to the codec. 529 * @param outputBufferSize the size of the buffers offered as output from the codec. 530 * @param codec should be able to map from input size -> output size 531 */ CodecConfig(int inputBufferSize, int outputBufferSize, Codec codec)532 public CodecConfig(int inputBufferSize, int outputBufferSize, Codec codec) { 533 this.inputBufferSize = inputBufferSize; 534 this.outputBufferSize = outputBufferSize; 535 536 this.codec = codec; 537 } 538 539 /** 540 * A codec is implemented as part of the configuration to allow the {@link ShadowMediaCodec} to 541 * simulate actual encoding/decoding. It's not expected for implementations to perform real 542 * encoding/decoding, but to produce a output similar in size ratio to the expected codec.. 543 */ 544 public interface Codec { 545 546 /** Move the bytes on the in buffer to the out buffer */ process(ByteBuffer in, ByteBuffer out)547 void process(ByteBuffer in, ByteBuffer out); 548 /** Called when the codec is configured. @see MediaCodec#configure */ onConfigured( MediaFormat format, @Nullable Surface surface, @Nullable MediaCrypto crypto, int flags)549 default void onConfigured( 550 MediaFormat format, @Nullable Surface surface, @Nullable MediaCrypto crypto, int flags) {} 551 } 552 } 553 554 /** Reflectively throws a {@link CodecException}. */ throwCodecException(int errorCode, int actionCode, String message)555 private static void throwCodecException(int errorCode, int actionCode, String message) { 556 throw callConstructor( 557 MediaCodec.CodecException.class, 558 ClassParameter.from(Integer.TYPE, errorCode), 559 ClassParameter.from(Integer.TYPE, actionCode), 560 ClassParameter.from(String.class, message)); 561 } 562 } 563