OLD | NEW |
1 /* | 1 /* |
2 * libjingle | 2 * libjingle |
3 * Copyright 2013 Google Inc. | 3 * Copyright 2013 Google Inc. |
4 * | 4 * |
5 * Redistribution and use in source and binary forms, with or without | 5 * Redistribution and use in source and binary forms, with or without |
6 * modification, are permitted provided that the following conditions are met: | 6 * modification, are permitted provided that the following conditions are met: |
7 * | 7 * |
8 * 1. Redistributions of source code must retain the above copyright notice, | 8 * 1. Redistributions of source code must retain the above copyright notice, |
9 * this list of conditions and the following disclaimer. | 9 * this list of conditions and the following disclaimer. |
10 * 2. Redistributions in binary form must reproduce the above copyright notice, | 10 * 2. Redistributions in binary form must reproduce the above copyright notice, |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
54 | 54 |
55 // Tracks webrtc::VideoCodecType. | 55 // Tracks webrtc::VideoCodecType. |
56 public enum VideoCodecType { | 56 public enum VideoCodecType { |
57 VIDEO_CODEC_VP8, | 57 VIDEO_CODEC_VP8, |
58 VIDEO_CODEC_VP9, | 58 VIDEO_CODEC_VP9, |
59 VIDEO_CODEC_H264 | 59 VIDEO_CODEC_H264 |
60 } | 60 } |
61 | 61 |
62 private static final int MEDIA_CODEC_RELEASE_TIMEOUT_MS = 5000; // Timeout for
codec releasing. | 62 private static final int MEDIA_CODEC_RELEASE_TIMEOUT_MS = 5000; // Timeout for
codec releasing. |
63 private static final int DEQUEUE_TIMEOUT = 0; // Non-blocking, no wait. | 63 private static final int DEQUEUE_TIMEOUT = 0; // Non-blocking, no wait. |
64 // Active running encoder instance. Set in initDecode() (called from native co
de) | 64 // Active running encoder instance. Set in initEncode() (called from native co
de) |
65 // and reset to null in release() call. | 65 // and reset to null in release() call. |
66 private static MediaCodecVideoEncoder runningInstance = null; | 66 private static MediaCodecVideoEncoder runningInstance = null; |
67 private static MediaCodecVideoEncoderErrorCallback errorCallback = null; | 67 private static MediaCodecVideoEncoderErrorCallback errorCallback = null; |
68 private static int codecErrors = 0; | 68 private static int codecErrors = 0; |
69 | 69 |
70 private Thread mediaCodecThread; | 70 private Thread mediaCodecThread; |
71 private MediaCodec mediaCodec; | 71 private MediaCodec mediaCodec; |
72 private ByteBuffer[] outputBuffers; | 72 private ByteBuffer[] outputBuffers; |
73 private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8"; | 73 private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8"; |
74 private static final String H264_MIME_TYPE = "video/avc"; | 74 private static final String H264_MIME_TYPE = "video/avc"; |
(...skipping 20 matching lines...) Expand all Loading... |
95 // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h | 95 // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h |
96 private static final int | 96 private static final int |
97 COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04; | 97 COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04; |
98 // Allowable color formats supported by codec - in order of preference. | 98 // Allowable color formats supported by codec - in order of preference. |
99 private static final int[] supportedColorList = { | 99 private static final int[] supportedColorList = { |
100 CodecCapabilities.COLOR_FormatYUV420Planar, | 100 CodecCapabilities.COLOR_FormatYUV420Planar, |
101 CodecCapabilities.COLOR_FormatYUV420SemiPlanar, | 101 CodecCapabilities.COLOR_FormatYUV420SemiPlanar, |
102 CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar, | 102 CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar, |
103 COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m | 103 COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m |
104 }; | 104 }; |
105 private int colorFormat; | |
106 // Video encoder type. | |
107 private VideoCodecType type; | 105 private VideoCodecType type; |
| 106 private int colorFormat; // Used by native code. |
| 107 |
108 // SPS and PPS NALs (Config frame) for H.264. | 108 // SPS and PPS NALs (Config frame) for H.264. |
109 private ByteBuffer configData = null; | 109 private ByteBuffer configData = null; |
110 | 110 |
111 private MediaCodecVideoEncoder() { | |
112 } | |
113 | |
114 // MediaCodec error handler - invoked when critical error happens which may pr
event | 111 // MediaCodec error handler - invoked when critical error happens which may pr
event |
115 // further use of media codec API. Now it means that one of media codec instan
ces | 112 // further use of media codec API. Now it means that one of media codec instan
ces |
116 // is hanging and can no longer be used in the next call. | 113 // is hanging and can no longer be used in the next call. |
117 public static interface MediaCodecVideoEncoderErrorCallback { | 114 public static interface MediaCodecVideoEncoderErrorCallback { |
118 void onMediaCodecVideoEncoderCriticalError(int codecErrors); | 115 void onMediaCodecVideoEncoderCriticalError(int codecErrors); |
119 } | 116 } |
120 | 117 |
121 public static void setErrorCallback(MediaCodecVideoEncoderErrorCallback errorC
allback) { | 118 public static void setErrorCallback(MediaCodecVideoEncoderErrorCallback errorC
allback) { |
122 Logging.d(TAG, "Set error callback"); | 119 Logging.d(TAG, "Set error callback"); |
123 MediaCodecVideoEncoder.errorCallback = errorCallback; | 120 MediaCodecVideoEncoder.errorCallback = errorCallback; |
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
230 static MediaCodec createByCodecName(String codecName) { | 227 static MediaCodec createByCodecName(String codecName) { |
231 try { | 228 try { |
232 // In the L-SDK this call can throw IOException so in order to work in | 229 // In the L-SDK this call can throw IOException so in order to work in |
233 // both cases catch an exception. | 230 // both cases catch an exception. |
234 return MediaCodec.createByCodecName(codecName); | 231 return MediaCodec.createByCodecName(codecName); |
235 } catch (Exception e) { | 232 } catch (Exception e) { |
236 return null; | 233 return null; |
237 } | 234 } |
238 } | 235 } |
239 | 236 |
240 // Return the array of input buffers, or null on failure. | 237 // Returns false if the hardware encoder currently can't be used. |
241 private ByteBuffer[] initEncode( | 238 boolean initEncode(VideoCodecType type, int width, int height, int kbps, int f
ps) { |
242 VideoCodecType type, int width, int height, int kbps, int fps) { | |
243 Logging.d(TAG, "Java initEncode: " + type + " : " + width + " x " + height + | 239 Logging.d(TAG, "Java initEncode: " + type + " : " + width + " x " + height + |
244 ". @ " + kbps + " kbps. Fps: " + fps + | 240 ". @ " + kbps + " kbps. Fps: " + fps + "."); |
245 ". Color: 0x" + Integer.toHexString(colorFormat)); | 241 |
246 if (mediaCodecThread != null) { | 242 if (mediaCodecThread != null) { |
247 throw new RuntimeException("Forgot to release()?"); | 243 throw new RuntimeException("Forgot to release()?"); |
248 } | 244 } |
249 this.type = type; | |
250 EncoderProperties properties = null; | 245 EncoderProperties properties = null; |
251 String mime = null; | 246 String mime = null; |
252 int keyFrameIntervalSec = 0; | 247 int keyFrameIntervalSec = 0; |
253 if (type == VideoCodecType.VIDEO_CODEC_VP8) { | 248 if (type == VideoCodecType.VIDEO_CODEC_VP8) { |
254 mime = VP8_MIME_TYPE; | 249 mime = VP8_MIME_TYPE; |
255 properties = findHwEncoder(VP8_MIME_TYPE, supportedVp8HwCodecPrefixes); | 250 properties = findHwEncoder(VP8_MIME_TYPE, supportedVp8HwCodecPrefixes); |
256 keyFrameIntervalSec = 100; | 251 keyFrameIntervalSec = 100; |
257 } else if (type == VideoCodecType.VIDEO_CODEC_H264) { | 252 } else if (type == VideoCodecType.VIDEO_CODEC_H264) { |
258 mime = H264_MIME_TYPE; | 253 mime = H264_MIME_TYPE; |
259 properties = findHwEncoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes); | 254 properties = findHwEncoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes); |
260 keyFrameIntervalSec = 20; | 255 keyFrameIntervalSec = 20; |
261 } | 256 } |
262 if (properties == null) { | 257 if (properties == null) { |
263 throw new RuntimeException("Can not find HW encoder for " + type); | 258 throw new RuntimeException("Can not find HW encoder for " + type); |
264 } | 259 } |
265 runningInstance = this; // Encoder is now running and can be queried for sta
ck traces. | 260 runningInstance = this; // Encoder is now running and can be queried for sta
ck traces. |
| 261 colorFormat = properties.colorFormat; |
| 262 Logging.d(TAG, "Color format: " + colorFormat); |
| 263 |
266 mediaCodecThread = Thread.currentThread(); | 264 mediaCodecThread = Thread.currentThread(); |
267 try { | 265 try { |
268 MediaFormat format = MediaFormat.createVideoFormat(mime, width, height); | 266 MediaFormat format = MediaFormat.createVideoFormat(mime, width, height); |
269 format.setInteger(MediaFormat.KEY_BIT_RATE, 1000 * kbps); | 267 format.setInteger(MediaFormat.KEY_BIT_RATE, 1000 * kbps); |
270 format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); | 268 format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); |
271 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat); | 269 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat); |
272 format.setInteger(MediaFormat.KEY_FRAME_RATE, fps); | 270 format.setInteger(MediaFormat.KEY_FRAME_RATE, fps); |
273 format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, keyFrameIntervalSec); | 271 format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, keyFrameIntervalSec); |
274 Logging.d(TAG, " Format: " + format); | 272 Logging.d(TAG, " Format: " + format); |
275 mediaCodec = createByCodecName(properties.codecName); | 273 mediaCodec = createByCodecName(properties.codecName); |
| 274 this.type = type; |
276 if (mediaCodec == null) { | 275 if (mediaCodec == null) { |
277 Logging.e(TAG, "Can not create media encoder"); | 276 Logging.e(TAG, "Can not create media encoder"); |
278 return null; | 277 return false; |
279 } | 278 } |
280 mediaCodec.configure( | 279 mediaCodec.configure( |
281 format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); | 280 format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); |
| 281 |
282 mediaCodec.start(); | 282 mediaCodec.start(); |
283 colorFormat = properties.colorFormat; | |
284 outputBuffers = mediaCodec.getOutputBuffers(); | 283 outputBuffers = mediaCodec.getOutputBuffers(); |
285 ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers(); | 284 Logging.d(TAG, "Output buffers: " + outputBuffers.length); |
286 Logging.d(TAG, "Input buffers: " + inputBuffers.length + | 285 |
287 ". Output buffers: " + outputBuffers.length); | |
288 return inputBuffers; | |
289 } catch (IllegalStateException e) { | 286 } catch (IllegalStateException e) { |
290 Logging.e(TAG, "initEncode failed", e); | 287 Logging.e(TAG, "initEncode failed", e); |
291 return null; | 288 return false; |
292 } | 289 } |
| 290 return true; |
293 } | 291 } |
294 | 292 |
295 private boolean encode( | 293 ByteBuffer[] getInputBuffers() { |
| 294 ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers(); |
| 295 Logging.d(TAG, "Input buffers: " + inputBuffers.length); |
| 296 return inputBuffers; |
| 297 } |
| 298 |
| 299 boolean encodeBuffer( |
296 boolean isKeyframe, int inputBuffer, int size, | 300 boolean isKeyframe, int inputBuffer, int size, |
297 long presentationTimestampUs) { | 301 long presentationTimestampUs) { |
298 checkOnMediaCodecThread(); | 302 checkOnMediaCodecThread(); |
299 try { | 303 try { |
300 if (isKeyframe) { | 304 if (isKeyframe) { |
301 // Ideally MediaCodec would honor BUFFER_FLAG_SYNC_FRAME so we could | 305 // Ideally MediaCodec would honor BUFFER_FLAG_SYNC_FRAME so we could |
302 // indicate this in queueInputBuffer() below and guarantee _this_ frame | 306 // indicate this in queueInputBuffer() below and guarantee _this_ frame |
303 // be encoded as a key frame, but sadly that flag is ignored. Instead, | 307 // be encoded as a key frame, but sadly that flag is ignored. Instead, |
304 // we request a key frame "soon". | 308 // we request a key frame "soon". |
305 Logging.d(TAG, "Sync frame request"); | 309 Logging.d(TAG, "Sync frame request"); |
306 Bundle b = new Bundle(); | 310 Bundle b = new Bundle(); |
307 b.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0); | 311 b.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0); |
308 mediaCodec.setParameters(b); | 312 mediaCodec.setParameters(b); |
309 } | 313 } |
310 mediaCodec.queueInputBuffer( | 314 mediaCodec.queueInputBuffer( |
311 inputBuffer, 0, size, presentationTimestampUs, 0); | 315 inputBuffer, 0, size, presentationTimestampUs, 0); |
312 return true; | 316 return true; |
313 } | 317 } |
314 catch (IllegalStateException e) { | 318 catch (IllegalStateException e) { |
315 Logging.e(TAG, "encode failed", e); | 319 Logging.e(TAG, "encodeBuffer failed", e); |
316 return false; | 320 return false; |
317 } | 321 } |
318 } | 322 } |
319 | 323 |
320 private void release() { | 324 void release() { |
321 Logging.d(TAG, "Java releaseEncoder"); | 325 Logging.d(TAG, "Java releaseEncoder"); |
322 checkOnMediaCodecThread(); | 326 checkOnMediaCodecThread(); |
323 | 327 |
324 // Run Mediacodec stop() and release() on separate thread since sometime | 328 // Run Mediacodec stop() and release() on separate thread since sometime |
325 // Mediacodec.stop() may hang. | 329 // Mediacodec.stop() may hang. |
326 final CountDownLatch releaseDone = new CountDownLatch(1); | 330 final CountDownLatch releaseDone = new CountDownLatch(1); |
327 | 331 |
328 Runnable runMediaCodecRelease = new Runnable() { | 332 Runnable runMediaCodecRelease = new Runnable() { |
329 @Override | 333 @Override |
330 public void run() { | 334 public void run() { |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
367 mediaCodec.setParameters(params); | 371 mediaCodec.setParameters(params); |
368 return true; | 372 return true; |
369 } catch (IllegalStateException e) { | 373 } catch (IllegalStateException e) { |
370 Logging.e(TAG, "setRates failed", e); | 374 Logging.e(TAG, "setRates failed", e); |
371 return false; | 375 return false; |
372 } | 376 } |
373 } | 377 } |
374 | 378 |
375 // Dequeue an input buffer and return its index, -1 if no input buffer is | 379 // Dequeue an input buffer and return its index, -1 if no input buffer is |
376 // available, or -2 if the codec is no longer operative. | 380 // available, or -2 if the codec is no longer operative. |
377 private int dequeueInputBuffer() { | 381 int dequeueInputBuffer() { |
378 checkOnMediaCodecThread(); | 382 checkOnMediaCodecThread(); |
379 try { | 383 try { |
380 return mediaCodec.dequeueInputBuffer(DEQUEUE_TIMEOUT); | 384 return mediaCodec.dequeueInputBuffer(DEQUEUE_TIMEOUT); |
381 } catch (IllegalStateException e) { | 385 } catch (IllegalStateException e) { |
382 Logging.e(TAG, "dequeueIntputBuffer failed", e); | 386 Logging.e(TAG, "dequeueIntputBuffer failed", e); |
383 return -2; | 387 return -2; |
384 } | 388 } |
385 } | 389 } |
386 | 390 |
387 // Helper struct for dequeueOutputBuffer() below. | 391 // Helper struct for dequeueOutputBuffer() below. |
388 private static class OutputBufferInfo { | 392 static class OutputBufferInfo { |
389 public OutputBufferInfo( | 393 public OutputBufferInfo( |
390 int index, ByteBuffer buffer, | 394 int index, ByteBuffer buffer, |
391 boolean isKeyFrame, long presentationTimestampUs) { | 395 boolean isKeyFrame, long presentationTimestampUs) { |
392 this.index = index; | 396 this.index = index; |
393 this.buffer = buffer; | 397 this.buffer = buffer; |
394 this.isKeyFrame = isKeyFrame; | 398 this.isKeyFrame = isKeyFrame; |
395 this.presentationTimestampUs = presentationTimestampUs; | 399 this.presentationTimestampUs = presentationTimestampUs; |
396 } | 400 } |
397 | 401 |
398 private final int index; | 402 public final int index; |
399 private final ByteBuffer buffer; | 403 public final ByteBuffer buffer; |
400 private final boolean isKeyFrame; | 404 public final boolean isKeyFrame; |
401 private final long presentationTimestampUs; | 405 public final long presentationTimestampUs; |
402 } | 406 } |
403 | 407 |
404 // Dequeue and return an output buffer, or null if no output is ready. Return | 408 // Dequeue and return an output buffer, or null if no output is ready. Return |
405 // a fake OutputBufferInfo with index -1 if the codec is no longer operable. | 409 // a fake OutputBufferInfo with index -1 if the codec is no longer operable. |
406 private OutputBufferInfo dequeueOutputBuffer() { | 410 OutputBufferInfo dequeueOutputBuffer() { |
407 checkOnMediaCodecThread(); | 411 checkOnMediaCodecThread(); |
408 try { | 412 try { |
409 MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); | 413 MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); |
410 int result = mediaCodec.dequeueOutputBuffer(info, DEQUEUE_TIMEOUT); | 414 int result = mediaCodec.dequeueOutputBuffer(info, DEQUEUE_TIMEOUT); |
411 // Check if this is config frame and save configuration data. | 415 // Check if this is config frame and save configuration data. |
412 if (result >= 0) { | 416 if (result >= 0) { |
413 boolean isConfigFrame = | 417 boolean isConfigFrame = |
414 (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0; | 418 (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0; |
415 if (isConfigFrame) { | 419 if (isConfigFrame) { |
416 Logging.d(TAG, "Config frame generated. Offset: " + info.offset + | 420 Logging.d(TAG, "Config frame generated. Offset: " + info.offset + |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
465 } | 469 } |
466 throw new RuntimeException("dequeueOutputBuffer: " + result); | 470 throw new RuntimeException("dequeueOutputBuffer: " + result); |
467 } catch (IllegalStateException e) { | 471 } catch (IllegalStateException e) { |
468 Logging.e(TAG, "dequeueOutputBuffer failed", e); | 472 Logging.e(TAG, "dequeueOutputBuffer failed", e); |
469 return new OutputBufferInfo(-1, null, false, -1); | 473 return new OutputBufferInfo(-1, null, false, -1); |
470 } | 474 } |
471 } | 475 } |
472 | 476 |
473 // Release a dequeued output buffer back to the codec for re-use. Return | 477 // Release a dequeued output buffer back to the codec for re-use. Return |
474 // false if the codec is no longer operable. | 478 // false if the codec is no longer operable. |
475 private boolean releaseOutputBuffer(int index) { | 479 boolean releaseOutputBuffer(int index) { |
476 checkOnMediaCodecThread(); | 480 checkOnMediaCodecThread(); |
477 try { | 481 try { |
478 mediaCodec.releaseOutputBuffer(index, false); | 482 mediaCodec.releaseOutputBuffer(index, false); |
479 return true; | 483 return true; |
480 } catch (IllegalStateException e) { | 484 } catch (IllegalStateException e) { |
481 Logging.e(TAG, "releaseOutputBuffer failed", e); | 485 Logging.e(TAG, "releaseOutputBuffer failed", e); |
482 return false; | 486 return false; |
483 } | 487 } |
484 } | 488 } |
485 } | 489 } |
OLD | NEW |