1 /* 2 * libjingle 3 * Copyright 2013 Google Inc. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 3. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 package org.webrtc; 29 30 import android.annotation.TargetApi; 31 import android.media.MediaCodec; 32 import android.media.MediaCodecInfo.CodecCapabilities; 33 import android.media.MediaCodecInfo; 34 import android.media.MediaCodecList; 35 import android.media.MediaFormat; 36 import android.opengl.GLES20; 37 import android.os.Build; 38 import android.os.Bundle; 39 import android.view.Surface; 40 41 import org.webrtc.Logging; 42 43 import java.nio.ByteBuffer; 44 import java.util.Arrays; 45 import java.util.List; 46 import java.util.concurrent.CountDownLatch; 47 import java.util.concurrent.TimeUnit; 48 49 // Java-side of peerconnection_jni.cc:MediaCodecVideoEncoder. 50 // This class is an implementation detail of the Java PeerConnection API. 51 @TargetApi(19) 52 @SuppressWarnings("deprecation") 53 public class MediaCodecVideoEncoder { 54 // This class is constructed, operated, and destroyed by its C++ incarnation, 55 // so the class and its methods have non-public visibility. The API this 56 // class exposes aims to mimic the webrtc::VideoEncoder API as closely as 57 // possibly to minimize the amount of translation work necessary. 58 59 private static final String TAG = "MediaCodecVideoEncoder"; 60 61 // Tracks webrtc::VideoCodecType. 62 public enum VideoCodecType { 63 VIDEO_CODEC_VP8, 64 VIDEO_CODEC_VP9, 65 VIDEO_CODEC_H264 66 } 67 68 private static final int MEDIA_CODEC_RELEASE_TIMEOUT_MS = 5000; // Timeout for codec releasing. 69 private static final int DEQUEUE_TIMEOUT = 0; // Non-blocking, no wait. 70 // Active running encoder instance. Set in initEncode() (called from native code) 71 // and reset to null in release() call. 72 private static MediaCodecVideoEncoder runningInstance = null; 73 private static MediaCodecVideoEncoderErrorCallback errorCallback = null; 74 private static int codecErrors = 0; 75 76 private Thread mediaCodecThread; 77 private MediaCodec mediaCodec; 78 private ByteBuffer[] outputBuffers; 79 private EglBase14 eglBase; 80 private int width; 81 private int height; 82 private Surface inputSurface; 83 private GlRectDrawer drawer; 84 private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8"; 85 private static final String VP9_MIME_TYPE = "video/x-vnd.on2.vp9"; 86 private static final String H264_MIME_TYPE = "video/avc"; 87 // List of supported HW VP8 codecs. 88 private static final String[] supportedVp8HwCodecPrefixes = 89 {"OMX.qcom.", "OMX.Intel." }; 90 // List of supported HW VP9 decoders. 91 private static final String[] supportedVp9HwCodecPrefixes = 92 {"OMX.qcom."}; 93 // List of supported HW H.264 codecs. 94 private static final String[] supportedH264HwCodecPrefixes = 95 {"OMX.qcom." }; 96 // List of devices with poor H.264 encoder quality. 97 private static final String[] H264_HW_EXCEPTION_MODELS = new String[] { 98 // HW H.264 encoder on below devices has poor bitrate control - actual 99 // bitrates deviates a lot from the target value. 100 "SAMSUNG-SGH-I337", 101 "Nexus 7", 102 "Nexus 4" 103 }; 104 105 // Bitrate modes - should be in sync with OMX_VIDEO_CONTROLRATETYPE defined 106 // in OMX_Video.h 107 private static final int VIDEO_ControlRateVariable = 1; 108 private static final int VIDEO_ControlRateConstant = 2; 109 // NV12 color format supported by QCOM codec, but not declared in MediaCodec - 110 // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h 111 private static final int 112 COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04; 113 // Allowable color formats supported by codec - in order of preference. 114 private static final int[] supportedColorList = { 115 CodecCapabilities.COLOR_FormatYUV420Planar, 116 CodecCapabilities.COLOR_FormatYUV420SemiPlanar, 117 CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar, 118 COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m 119 }; 120 private static final int[] supportedSurfaceColorList = { 121 CodecCapabilities.COLOR_FormatSurface 122 }; 123 private VideoCodecType type; 124 private int colorFormat; // Used by native code. 125 126 // SPS and PPS NALs (Config frame) for H.264. 127 private ByteBuffer configData = null; 128 129 // MediaCodec error handler - invoked when critical error happens which may prevent 130 // further use of media codec API. Now it means that one of media codec instances 131 // is hanging and can no longer be used in the next call. 132 public static interface MediaCodecVideoEncoderErrorCallback { onMediaCodecVideoEncoderCriticalError(int codecErrors)133 void onMediaCodecVideoEncoderCriticalError(int codecErrors); 134 } 135 setErrorCallback(MediaCodecVideoEncoderErrorCallback errorCallback)136 public static void setErrorCallback(MediaCodecVideoEncoderErrorCallback errorCallback) { 137 Logging.d(TAG, "Set error callback"); 138 MediaCodecVideoEncoder.errorCallback = errorCallback; 139 } 140 141 // Helper struct for findHwEncoder() below. 142 private static class EncoderProperties { EncoderProperties(String codecName, int colorFormat)143 public EncoderProperties(String codecName, int colorFormat) { 144 this.codecName = codecName; 145 this.colorFormat = colorFormat; 146 } 147 public final String codecName; // OpenMax component name for HW codec. 148 public final int colorFormat; // Color format supported by codec. 149 } 150 findHwEncoder( String mime, String[] supportedHwCodecPrefixes, int[] colorList)151 private static EncoderProperties findHwEncoder( 152 String mime, String[] supportedHwCodecPrefixes, int[] colorList) { 153 // MediaCodec.setParameters is missing for JB and below, so bitrate 154 // can not be adjusted dynamically. 155 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 156 return null; 157 } 158 159 // Check if device is in H.264 exception list. 160 if (mime.equals(H264_MIME_TYPE)) { 161 List<String> exceptionModels = Arrays.asList(H264_HW_EXCEPTION_MODELS); 162 if (exceptionModels.contains(Build.MODEL)) { 163 Logging.w(TAG, "Model: " + Build.MODEL + " has black listed H.264 encoder."); 164 return null; 165 } 166 } 167 168 for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) { 169 MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i); 170 if (!info.isEncoder()) { 171 continue; 172 } 173 String name = null; 174 for (String mimeType : info.getSupportedTypes()) { 175 if (mimeType.equals(mime)) { 176 name = info.getName(); 177 break; 178 } 179 } 180 if (name == null) { 181 continue; // No HW support in this codec; try the next one. 182 } 183 Logging.v(TAG, "Found candidate encoder " + name); 184 185 // Check if this is supported HW encoder. 186 boolean supportedCodec = false; 187 for (String hwCodecPrefix : supportedHwCodecPrefixes) { 188 if (name.startsWith(hwCodecPrefix)) { 189 supportedCodec = true; 190 break; 191 } 192 } 193 if (!supportedCodec) { 194 continue; 195 } 196 197 CodecCapabilities capabilities = info.getCapabilitiesForType(mime); 198 for (int colorFormat : capabilities.colorFormats) { 199 Logging.v(TAG, " Color: 0x" + Integer.toHexString(colorFormat)); 200 } 201 202 for (int supportedColorFormat : colorList) { 203 for (int codecColorFormat : capabilities.colorFormats) { 204 if (codecColorFormat == supportedColorFormat) { 205 // Found supported HW encoder. 206 Logging.d(TAG, "Found target encoder for mime " + mime + " : " + name + 207 ". Color: 0x" + Integer.toHexString(codecColorFormat)); 208 return new EncoderProperties(name, codecColorFormat); 209 } 210 } 211 } 212 } 213 return null; // No HW encoder. 214 } 215 isVp8HwSupported()216 public static boolean isVp8HwSupported() { 217 return findHwEncoder(VP8_MIME_TYPE, supportedVp8HwCodecPrefixes, supportedColorList) != null; 218 } 219 isVp9HwSupported()220 public static boolean isVp9HwSupported() { 221 return findHwEncoder(VP9_MIME_TYPE, supportedVp9HwCodecPrefixes, supportedColorList) != null; 222 } 223 isH264HwSupported()224 public static boolean isH264HwSupported() { 225 return findHwEncoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes, supportedColorList) != null; 226 } 227 isVp8HwSupportedUsingTextures()228 public static boolean isVp8HwSupportedUsingTextures() { 229 return findHwEncoder( 230 VP8_MIME_TYPE, supportedVp8HwCodecPrefixes, supportedSurfaceColorList) != null; 231 } 232 isVp9HwSupportedUsingTextures()233 public static boolean isVp9HwSupportedUsingTextures() { 234 return findHwEncoder( 235 VP9_MIME_TYPE, supportedVp9HwCodecPrefixes, supportedSurfaceColorList) != null; 236 } 237 isH264HwSupportedUsingTextures()238 public static boolean isH264HwSupportedUsingTextures() { 239 return findHwEncoder( 240 H264_MIME_TYPE, supportedH264HwCodecPrefixes, supportedSurfaceColorList) != null; 241 } 242 checkOnMediaCodecThread()243 private void checkOnMediaCodecThread() { 244 if (mediaCodecThread.getId() != Thread.currentThread().getId()) { 245 throw new RuntimeException( 246 "MediaCodecVideoEncoder previously operated on " + mediaCodecThread + 247 " but is now called on " + Thread.currentThread()); 248 } 249 } 250 printStackTrace()251 public static void printStackTrace() { 252 if (runningInstance != null && runningInstance.mediaCodecThread != null) { 253 StackTraceElement[] mediaCodecStackTraces = runningInstance.mediaCodecThread.getStackTrace(); 254 if (mediaCodecStackTraces.length > 0) { 255 Logging.d(TAG, "MediaCodecVideoEncoder stacks trace:"); 256 for (StackTraceElement stackTrace : mediaCodecStackTraces) { 257 Logging.d(TAG, stackTrace.toString()); 258 } 259 } 260 } 261 } 262 createByCodecName(String codecName)263 static MediaCodec createByCodecName(String codecName) { 264 try { 265 // In the L-SDK this call can throw IOException so in order to work in 266 // both cases catch an exception. 267 return MediaCodec.createByCodecName(codecName); 268 } catch (Exception e) { 269 return null; 270 } 271 } 272 initEncode(VideoCodecType type, int width, int height, int kbps, int fps, EglBase14.Context sharedContext)273 boolean initEncode(VideoCodecType type, int width, int height, int kbps, int fps, 274 EglBase14.Context sharedContext) { 275 final boolean useSurface = sharedContext != null; 276 Logging.d(TAG, "Java initEncode: " + type + " : " + width + " x " + height + 277 ". @ " + kbps + " kbps. Fps: " + fps + ". Encode from texture : " + useSurface); 278 279 this.width = width; 280 this.height = height; 281 if (mediaCodecThread != null) { 282 throw new RuntimeException("Forgot to release()?"); 283 } 284 EncoderProperties properties = null; 285 String mime = null; 286 int keyFrameIntervalSec = 0; 287 if (type == VideoCodecType.VIDEO_CODEC_VP8) { 288 mime = VP8_MIME_TYPE; 289 properties = findHwEncoder(VP8_MIME_TYPE, supportedVp8HwCodecPrefixes, 290 useSurface ? supportedSurfaceColorList : supportedColorList); 291 keyFrameIntervalSec = 100; 292 } else if (type == VideoCodecType.VIDEO_CODEC_VP9) { 293 mime = VP9_MIME_TYPE; 294 properties = findHwEncoder(VP9_MIME_TYPE, supportedH264HwCodecPrefixes, 295 useSurface ? supportedSurfaceColorList : supportedColorList); 296 keyFrameIntervalSec = 100; 297 } else if (type == VideoCodecType.VIDEO_CODEC_H264) { 298 mime = H264_MIME_TYPE; 299 properties = findHwEncoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes, 300 useSurface ? supportedSurfaceColorList : supportedColorList); 301 keyFrameIntervalSec = 20; 302 } 303 if (properties == null) { 304 throw new RuntimeException("Can not find HW encoder for " + type); 305 } 306 runningInstance = this; // Encoder is now running and can be queried for stack traces. 307 colorFormat = properties.colorFormat; 308 Logging.d(TAG, "Color format: " + colorFormat); 309 310 mediaCodecThread = Thread.currentThread(); 311 try { 312 MediaFormat format = MediaFormat.createVideoFormat(mime, width, height); 313 format.setInteger(MediaFormat.KEY_BIT_RATE, 1000 * kbps); 314 format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); 315 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat); 316 format.setInteger(MediaFormat.KEY_FRAME_RATE, fps); 317 format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, keyFrameIntervalSec); 318 Logging.d(TAG, " Format: " + format); 319 mediaCodec = createByCodecName(properties.codecName); 320 this.type = type; 321 if (mediaCodec == null) { 322 Logging.e(TAG, "Can not create media encoder"); 323 return false; 324 } 325 mediaCodec.configure( 326 format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 327 328 if (useSurface) { 329 eglBase = new EglBase14(sharedContext, EglBase.CONFIG_RECORDABLE); 330 // Create an input surface and keep a reference since we must release the surface when done. 331 inputSurface = mediaCodec.createInputSurface(); 332 eglBase.createSurface(inputSurface); 333 drawer = new GlRectDrawer(); 334 } 335 mediaCodec.start(); 336 outputBuffers = mediaCodec.getOutputBuffers(); 337 Logging.d(TAG, "Output buffers: " + outputBuffers.length); 338 339 } catch (IllegalStateException e) { 340 Logging.e(TAG, "initEncode failed", e); 341 return false; 342 } 343 return true; 344 } 345 getInputBuffers()346 ByteBuffer[] getInputBuffers() { 347 ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers(); 348 Logging.d(TAG, "Input buffers: " + inputBuffers.length); 349 return inputBuffers; 350 } 351 encodeBuffer( boolean isKeyframe, int inputBuffer, int size, long presentationTimestampUs)352 boolean encodeBuffer( 353 boolean isKeyframe, int inputBuffer, int size, 354 long presentationTimestampUs) { 355 checkOnMediaCodecThread(); 356 try { 357 if (isKeyframe) { 358 // Ideally MediaCodec would honor BUFFER_FLAG_SYNC_FRAME so we could 359 // indicate this in queueInputBuffer() below and guarantee _this_ frame 360 // be encoded as a key frame, but sadly that flag is ignored. Instead, 361 // we request a key frame "soon". 362 Logging.d(TAG, "Sync frame request"); 363 Bundle b = new Bundle(); 364 b.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0); 365 mediaCodec.setParameters(b); 366 } 367 mediaCodec.queueInputBuffer( 368 inputBuffer, 0, size, presentationTimestampUs, 0); 369 return true; 370 } 371 catch (IllegalStateException e) { 372 Logging.e(TAG, "encodeBuffer failed", e); 373 return false; 374 } 375 } 376 encodeTexture(boolean isKeyframe, int oesTextureId, float[] transformationMatrix, long presentationTimestampUs)377 boolean encodeTexture(boolean isKeyframe, int oesTextureId, float[] transformationMatrix, 378 long presentationTimestampUs) { 379 checkOnMediaCodecThread(); 380 try { 381 if (isKeyframe) { 382 Logging.d(TAG, "Sync frame request"); 383 Bundle b = new Bundle(); 384 b.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0); 385 mediaCodec.setParameters(b); 386 } 387 eglBase.makeCurrent(); 388 // TODO(perkj): glClear() shouldn't be necessary since every pixel is covered anyway, 389 // but it's a workaround for bug webrtc:5147. 390 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); 391 drawer.drawOes(oesTextureId, transformationMatrix, 0, 0, width, height); 392 eglBase.swapBuffers(TimeUnit.MICROSECONDS.toNanos(presentationTimestampUs)); 393 return true; 394 } 395 catch (RuntimeException e) { 396 Logging.e(TAG, "encodeTexture failed", e); 397 return false; 398 } 399 } 400 release()401 void release() { 402 Logging.d(TAG, "Java releaseEncoder"); 403 checkOnMediaCodecThread(); 404 405 // Run Mediacodec stop() and release() on separate thread since sometime 406 // Mediacodec.stop() may hang. 407 final CountDownLatch releaseDone = new CountDownLatch(1); 408 409 Runnable runMediaCodecRelease = new Runnable() { 410 @Override 411 public void run() { 412 try { 413 Logging.d(TAG, "Java releaseEncoder on release thread"); 414 mediaCodec.stop(); 415 mediaCodec.release(); 416 Logging.d(TAG, "Java releaseEncoder on release thread done"); 417 } catch (Exception e) { 418 Logging.e(TAG, "Media encoder release failed", e); 419 } 420 releaseDone.countDown(); 421 } 422 }; 423 new Thread(runMediaCodecRelease).start(); 424 425 if (!ThreadUtils.awaitUninterruptibly(releaseDone, MEDIA_CODEC_RELEASE_TIMEOUT_MS)) { 426 Logging.e(TAG, "Media encoder release timeout"); 427 codecErrors++; 428 if (errorCallback != null) { 429 Logging.e(TAG, "Invoke codec error callback. Errors: " + codecErrors); 430 errorCallback.onMediaCodecVideoEncoderCriticalError(codecErrors); 431 } 432 } 433 434 mediaCodec = null; 435 mediaCodecThread = null; 436 if (drawer != null) { 437 drawer.release(); 438 drawer = null; 439 } 440 if (eglBase != null) { 441 eglBase.release(); 442 eglBase = null; 443 } 444 if (inputSurface != null) { 445 inputSurface.release(); 446 inputSurface = null; 447 } 448 runningInstance = null; 449 Logging.d(TAG, "Java releaseEncoder done"); 450 } 451 setRates(int kbps, int frameRateIgnored)452 private boolean setRates(int kbps, int frameRateIgnored) { 453 // frameRate argument is ignored - HW encoder is supposed to use 454 // video frame timestamps for bit allocation. 455 checkOnMediaCodecThread(); 456 Logging.v(TAG, "setRates: " + kbps + " kbps. Fps: " + frameRateIgnored); 457 try { 458 Bundle params = new Bundle(); 459 params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, 1000 * kbps); 460 mediaCodec.setParameters(params); 461 return true; 462 } catch (IllegalStateException e) { 463 Logging.e(TAG, "setRates failed", e); 464 return false; 465 } 466 } 467 468 // Dequeue an input buffer and return its index, -1 if no input buffer is 469 // available, or -2 if the codec is no longer operative. dequeueInputBuffer()470 int dequeueInputBuffer() { 471 checkOnMediaCodecThread(); 472 try { 473 return mediaCodec.dequeueInputBuffer(DEQUEUE_TIMEOUT); 474 } catch (IllegalStateException e) { 475 Logging.e(TAG, "dequeueIntputBuffer failed", e); 476 return -2; 477 } 478 } 479 480 // Helper struct for dequeueOutputBuffer() below. 481 static class OutputBufferInfo { OutputBufferInfo( int index, ByteBuffer buffer, boolean isKeyFrame, long presentationTimestampUs)482 public OutputBufferInfo( 483 int index, ByteBuffer buffer, 484 boolean isKeyFrame, long presentationTimestampUs) { 485 this.index = index; 486 this.buffer = buffer; 487 this.isKeyFrame = isKeyFrame; 488 this.presentationTimestampUs = presentationTimestampUs; 489 } 490 491 public final int index; 492 public final ByteBuffer buffer; 493 public final boolean isKeyFrame; 494 public final long presentationTimestampUs; 495 } 496 497 // Dequeue and return an output buffer, or null if no output is ready. Return 498 // a fake OutputBufferInfo with index -1 if the codec is no longer operable. dequeueOutputBuffer()499 OutputBufferInfo dequeueOutputBuffer() { 500 checkOnMediaCodecThread(); 501 try { 502 MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); 503 int result = mediaCodec.dequeueOutputBuffer(info, DEQUEUE_TIMEOUT); 504 // Check if this is config frame and save configuration data. 505 if (result >= 0) { 506 boolean isConfigFrame = 507 (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0; 508 if (isConfigFrame) { 509 Logging.d(TAG, "Config frame generated. Offset: " + info.offset + 510 ". Size: " + info.size); 511 configData = ByteBuffer.allocateDirect(info.size); 512 outputBuffers[result].position(info.offset); 513 outputBuffers[result].limit(info.offset + info.size); 514 configData.put(outputBuffers[result]); 515 // Release buffer back. 516 mediaCodec.releaseOutputBuffer(result, false); 517 // Query next output. 518 result = mediaCodec.dequeueOutputBuffer(info, DEQUEUE_TIMEOUT); 519 } 520 } 521 if (result >= 0) { 522 // MediaCodec doesn't care about Buffer position/remaining/etc so we can 523 // mess with them to get a slice and avoid having to pass extra 524 // (BufferInfo-related) parameters back to C++. 525 ByteBuffer outputBuffer = outputBuffers[result].duplicate(); 526 outputBuffer.position(info.offset); 527 outputBuffer.limit(info.offset + info.size); 528 // Check key frame flag. 529 boolean isKeyFrame = 530 (info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0; 531 if (isKeyFrame) { 532 Logging.d(TAG, "Sync frame generated"); 533 } 534 if (isKeyFrame && type == VideoCodecType.VIDEO_CODEC_H264) { 535 Logging.d(TAG, "Appending config frame of size " + configData.capacity() + 536 " to output buffer with offset " + info.offset + ", size " + 537 info.size); 538 // For H.264 key frame append SPS and PPS NALs at the start 539 ByteBuffer keyFrameBuffer = ByteBuffer.allocateDirect( 540 configData.capacity() + info.size); 541 configData.rewind(); 542 keyFrameBuffer.put(configData); 543 keyFrameBuffer.put(outputBuffer); 544 keyFrameBuffer.position(0); 545 return new OutputBufferInfo(result, keyFrameBuffer, 546 isKeyFrame, info.presentationTimeUs); 547 } else { 548 return new OutputBufferInfo(result, outputBuffer.slice(), 549 isKeyFrame, info.presentationTimeUs); 550 } 551 } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 552 outputBuffers = mediaCodec.getOutputBuffers(); 553 return dequeueOutputBuffer(); 554 } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 555 return dequeueOutputBuffer(); 556 } else if (result == MediaCodec.INFO_TRY_AGAIN_LATER) { 557 return null; 558 } 559 throw new RuntimeException("dequeueOutputBuffer: " + result); 560 } catch (IllegalStateException e) { 561 Logging.e(TAG, "dequeueOutputBuffer failed", e); 562 return new OutputBufferInfo(-1, null, false, -1); 563 } 564 } 565 566 // Release a dequeued output buffer back to the codec for re-use. Return 567 // false if the codec is no longer operable. releaseOutputBuffer(int index)568 boolean releaseOutputBuffer(int index) { 569 checkOnMediaCodecThread(); 570 try { 571 mediaCodec.releaseOutputBuffer(index, false); 572 return true; 573 } catch (IllegalStateException e) { 574 Logging.e(TAG, "releaseOutputBuffer failed", e); 575 return false; 576 } 577 } 578 } 579