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 22 matching lines...) Expand all Loading... |
33 import android.media.MediaCodecList; | 33 import android.media.MediaCodecList; |
34 import android.media.MediaFormat; | 34 import android.media.MediaFormat; |
35 import android.os.Build; | 35 import android.os.Build; |
36 import android.os.Bundle; | 36 import android.os.Bundle; |
37 | 37 |
38 import org.webrtc.Logging; | 38 import org.webrtc.Logging; |
39 | 39 |
40 import java.nio.ByteBuffer; | 40 import java.nio.ByteBuffer; |
41 import java.util.Arrays; | 41 import java.util.Arrays; |
42 import java.util.List; | 42 import java.util.List; |
| 43 import java.util.concurrent.CountDownLatch; |
43 | 44 |
44 // Java-side of peerconnection_jni.cc:MediaCodecVideoEncoder. | 45 // Java-side of peerconnection_jni.cc:MediaCodecVideoEncoder. |
45 // This class is an implementation detail of the Java PeerConnection API. | 46 // This class is an implementation detail of the Java PeerConnection API. |
46 // MediaCodec is thread-hostile so this class must be operated on a single | |
47 // thread. | |
48 public class MediaCodecVideoEncoder { | 47 public class MediaCodecVideoEncoder { |
49 // This class is constructed, operated, and destroyed by its C++ incarnation, | 48 // This class is constructed, operated, and destroyed by its C++ incarnation, |
50 // so the class and its methods have non-public visibility. The API this | 49 // so the class and its methods have non-public visibility. The API this |
51 // class exposes aims to mimic the webrtc::VideoEncoder API as closely as | 50 // class exposes aims to mimic the webrtc::VideoEncoder API as closely as |
52 // possibly to minimize the amount of translation work necessary. | 51 // possibly to minimize the amount of translation work necessary. |
53 | 52 |
54 private static final String TAG = "MediaCodecVideoEncoder"; | 53 private static final String TAG = "MediaCodecVideoEncoder"; |
55 | 54 |
56 // Tracks webrtc::VideoCodecType. | 55 // Tracks webrtc::VideoCodecType. |
57 public enum VideoCodecType { | 56 public enum VideoCodecType { |
58 VIDEO_CODEC_VP8, | 57 VIDEO_CODEC_VP8, |
59 VIDEO_CODEC_VP9, | 58 VIDEO_CODEC_VP9, |
60 VIDEO_CODEC_H264 | 59 VIDEO_CODEC_H264 |
61 } | 60 } |
62 | 61 |
| 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 initDecode() (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; |
| 68 private static int codecErrors = 0; |
| 69 |
67 private Thread mediaCodecThread; | 70 private Thread mediaCodecThread; |
68 private MediaCodec mediaCodec; | 71 private MediaCodec mediaCodec; |
69 private ByteBuffer[] outputBuffers; | 72 private ByteBuffer[] outputBuffers; |
70 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"; |
71 private static final String H264_MIME_TYPE = "video/avc"; | 74 private static final String H264_MIME_TYPE = "video/avc"; |
72 // List of supported HW VP8 codecs. | 75 // List of supported HW VP8 codecs. |
73 private static final String[] supportedVp8HwCodecPrefixes = | 76 private static final String[] supportedVp8HwCodecPrefixes = |
74 {"OMX.qcom.", "OMX.Intel." }; | 77 {"OMX.qcom.", "OMX.Intel." }; |
75 // List of supported HW H.264 codecs. | 78 // List of supported HW H.264 codecs. |
76 private static final String[] supportedH264HwCodecPrefixes = | 79 private static final String[] supportedH264HwCodecPrefixes = |
(...skipping 24 matching lines...) Expand all Loading... |
101 }; | 104 }; |
102 private int colorFormat; | 105 private int colorFormat; |
103 // Video encoder type. | 106 // Video encoder type. |
104 private VideoCodecType type; | 107 private VideoCodecType type; |
105 // SPS and PPS NALs (Config frame) for H.264. | 108 // SPS and PPS NALs (Config frame) for H.264. |
106 private ByteBuffer configData = null; | 109 private ByteBuffer configData = null; |
107 | 110 |
108 private MediaCodecVideoEncoder() { | 111 private MediaCodecVideoEncoder() { |
109 } | 112 } |
110 | 113 |
| 114 // 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 |
| 116 // is hanging and can no longer be used in the next call. |
| 117 public static interface MediaCodecVideoEncoderErrorCallback { |
| 118 void onMediaCodecVideoEncoderCriticalError(int codecErrors); |
| 119 } |
| 120 |
| 121 public static void setErrorCallback(MediaCodecVideoEncoderErrorCallback errorC
allback) { |
| 122 Logging.d(TAG, "Set error callback"); |
| 123 MediaCodecVideoEncoder.errorCallback = errorCallback; |
| 124 } |
| 125 |
111 // Helper struct for findHwEncoder() below. | 126 // Helper struct for findHwEncoder() below. |
112 private static class EncoderProperties { | 127 private static class EncoderProperties { |
113 public EncoderProperties(String codecName, int colorFormat) { | 128 public EncoderProperties(String codecName, int colorFormat) { |
114 this.codecName = codecName; | 129 this.codecName = codecName; |
115 this.colorFormat = colorFormat; | 130 this.colorFormat = colorFormat; |
116 } | 131 } |
117 public final String codecName; // OpenMax component name for HW codec. | 132 public final String codecName; // OpenMax component name for HW codec. |
118 public final int colorFormat; // Color format supported by codec. | 133 public final int colorFormat; // Color format supported by codec. |
119 } | 134 } |
120 | 135 |
121 private static EncoderProperties findHwEncoder( | 136 private static EncoderProperties findHwEncoder( |
122 String mime, String[] supportedHwCodecPrefixes) { | 137 String mime, String[] supportedHwCodecPrefixes) { |
123 // MediaCodec.setParameters is missing for JB and below, so bitrate | 138 // MediaCodec.setParameters is missing for JB and below, so bitrate |
124 // can not be adjusted dynamically. | 139 // can not be adjusted dynamically. |
125 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { | 140 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { |
126 return null; | 141 return null; |
127 } | 142 } |
128 | 143 |
129 // Check if device is in H.264 exception list. | 144 // Check if device is in H.264 exception list. |
130 if (mime.equals(H264_MIME_TYPE)) { | 145 if (mime.equals(H264_MIME_TYPE)) { |
131 List<String> exceptionModels = Arrays.asList(H264_HW_EXCEPTION_MODELS); | 146 List<String> exceptionModels = Arrays.asList(H264_HW_EXCEPTION_MODELS); |
132 if (exceptionModels.contains(Build.MODEL)) { | 147 if (exceptionModels.contains(Build.MODEL)) { |
133 Logging.w(TAG, "Model: " + Build.MODEL + | 148 Logging.w(TAG, "Model: " + Build.MODEL + " has black listed H.264 encode
r."); |
134 " has black listed H.264 encoder."); | |
135 return null; | 149 return null; |
136 } | 150 } |
137 } | 151 } |
138 | 152 |
139 for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) { | 153 for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) { |
140 MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i); | 154 MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i); |
141 if (!info.isEncoder()) { | 155 if (!info.isEncoder()) { |
142 continue; | 156 continue; |
143 } | 157 } |
144 String name = null; | 158 String name = null; |
(...skipping 154 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
299 } | 313 } |
300 catch (IllegalStateException e) { | 314 catch (IllegalStateException e) { |
301 Logging.e(TAG, "encode failed", e); | 315 Logging.e(TAG, "encode failed", e); |
302 return false; | 316 return false; |
303 } | 317 } |
304 } | 318 } |
305 | 319 |
306 private void release() { | 320 private void release() { |
307 Logging.d(TAG, "Java releaseEncoder"); | 321 Logging.d(TAG, "Java releaseEncoder"); |
308 checkOnMediaCodecThread(); | 322 checkOnMediaCodecThread(); |
309 try { | 323 |
310 mediaCodec.stop(); | 324 // Run Mediacodec stop() and release() on separate thread since sometime |
311 mediaCodec.release(); | 325 // Mediacodec.stop() may hang. |
312 } catch (IllegalStateException e) { | 326 final CountDownLatch releaseDone = new CountDownLatch(1); |
313 Logging.e(TAG, "release failed", e); | 327 |
| 328 Runnable runMediaCodecRelease = new Runnable() { |
| 329 @Override |
| 330 public void run() { |
| 331 try { |
| 332 Logging.d(TAG, "Java releaseEncoder on release thread"); |
| 333 mediaCodec.stop(); |
| 334 mediaCodec.release(); |
| 335 Logging.d(TAG, "Java releaseEncoder on release thread done"); |
| 336 } catch (Exception e) { |
| 337 Logging.e(TAG, "Media encoder release failed", e); |
| 338 } |
| 339 releaseDone.countDown(); |
| 340 } |
| 341 }; |
| 342 new Thread(runMediaCodecRelease).start(); |
| 343 |
| 344 if (!ThreadUtils.awaitUninterruptibly(releaseDone, MEDIA_CODEC_RELEASE_TIMEO
UT_MS)) { |
| 345 Logging.e(TAG, "Media encoder release timeout"); |
| 346 codecErrors++; |
| 347 if (errorCallback != null) { |
| 348 Logging.e(TAG, "Invoke codec error callback. Errors: " + codecErrors); |
| 349 errorCallback.onMediaCodecVideoEncoderCriticalError(codecErrors); |
| 350 } |
314 } | 351 } |
| 352 |
315 mediaCodec = null; | 353 mediaCodec = null; |
316 mediaCodecThread = null; | 354 mediaCodecThread = null; |
317 runningInstance = null; | 355 runningInstance = null; |
318 Logging.d(TAG, "Java releaseEncoder done"); | 356 Logging.d(TAG, "Java releaseEncoder done"); |
319 } | 357 } |
320 | 358 |
321 private boolean setRates(int kbps, int frameRateIgnored) { | 359 private boolean setRates(int kbps, int frameRateIgnored) { |
322 // frameRate argument is ignored - HW encoder is supposed to use | 360 // frameRate argument is ignored - HW encoder is supposed to use |
323 // video frame timestamps for bit allocation. | 361 // video frame timestamps for bit allocation. |
324 checkOnMediaCodecThread(); | 362 checkOnMediaCodecThread(); |
(...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
438 checkOnMediaCodecThread(); | 476 checkOnMediaCodecThread(); |
439 try { | 477 try { |
440 mediaCodec.releaseOutputBuffer(index, false); | 478 mediaCodec.releaseOutputBuffer(index, false); |
441 return true; | 479 return true; |
442 } catch (IllegalStateException e) { | 480 } catch (IllegalStateException e) { |
443 Logging.e(TAG, "releaseOutputBuffer failed", e); | 481 Logging.e(TAG, "releaseOutputBuffer failed", e); |
444 return false; | 482 return false; |
445 } | 483 } |
446 } | 484 } |
447 } | 485 } |
OLD | NEW |