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.atomic.AtomicBoolean; |
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 // MediaCodec is thread-hostile so this class must be operated on a single |
47 // thread. | 48 // thread. |
48 public class MediaCodecVideoEncoder { | 49 public class MediaCodecVideoEncoder { |
49 // This class is constructed, operated, and destroyed by its C++ incarnation, | 50 // 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 | 51 // 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 | 52 // class exposes aims to mimic the webrtc::VideoEncoder API as closely as |
52 // possibly to minimize the amount of translation work necessary. | 53 // possibly to minimize the amount of translation work necessary. |
53 | 54 |
54 private static final String TAG = "MediaCodecVideoEncoder"; | 55 private static final String TAG = "MediaCodecVideoEncoder"; |
55 | 56 |
56 // Tracks webrtc::VideoCodecType. | 57 // Tracks webrtc::VideoCodecType. |
57 public enum VideoCodecType { | 58 public enum VideoCodecType { |
58 VIDEO_CODEC_VP8, | 59 VIDEO_CODEC_VP8, |
59 VIDEO_CODEC_VP9, | 60 VIDEO_CODEC_VP9, |
60 VIDEO_CODEC_H264 | 61 VIDEO_CODEC_H264 |
61 } | 62 } |
62 | 63 |
| 64 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. | 65 private static final int DEQUEUE_TIMEOUT = 0; // Non-blocking, no wait. |
64 // Active running encoder instance. Set in initDecode() (called from native co
de) | 66 // Active running encoder instance. Set in initDecode() (called from native co
de) |
65 // and reset to null in release() call. | 67 // and reset to null in release() call. |
66 private static MediaCodecVideoEncoder runningInstance = null; | 68 private static MediaCodecVideoEncoder runningInstance = null; |
| 69 private static MediaCodecVideoEncoderErrorCallback errorCallback = null; |
| 70 private static int codecErrors = 0; |
| 71 |
67 private Thread mediaCodecThread; | 72 private Thread mediaCodecThread; |
68 private MediaCodec mediaCodec; | 73 private MediaCodec mediaCodec; |
69 private ByteBuffer[] outputBuffers; | 74 private ByteBuffer[] outputBuffers; |
70 private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8"; | 75 private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8"; |
71 private static final String H264_MIME_TYPE = "video/avc"; | 76 private static final String H264_MIME_TYPE = "video/avc"; |
72 // List of supported HW VP8 codecs. | 77 // List of supported HW VP8 codecs. |
73 private static final String[] supportedVp8HwCodecPrefixes = | 78 private static final String[] supportedVp8HwCodecPrefixes = |
74 {"OMX.qcom.", "OMX.Intel." }; | 79 {"OMX.qcom.", "OMX.Intel." }; |
75 // List of supported HW H.264 codecs. | 80 // List of supported HW H.264 codecs. |
76 private static final String[] supportedH264HwCodecPrefixes = | 81 private static final String[] supportedH264HwCodecPrefixes = |
(...skipping 24 matching lines...) Expand all Loading... |
101 }; | 106 }; |
102 private int colorFormat; | 107 private int colorFormat; |
103 // Video encoder type. | 108 // Video encoder type. |
104 private VideoCodecType type; | 109 private VideoCodecType type; |
105 // SPS and PPS NALs (Config frame) for H.264. | 110 // SPS and PPS NALs (Config frame) for H.264. |
106 private ByteBuffer configData = null; | 111 private ByteBuffer configData = null; |
107 | 112 |
108 private MediaCodecVideoEncoder() { | 113 private MediaCodecVideoEncoder() { |
109 } | 114 } |
110 | 115 |
| 116 // MediaCodec error handler - invoked when critical error happens |
| 117 // which prevents further use of media codec API. |
| 118 public static interface MediaCodecVideoEncoderErrorCallback { |
| 119 void onMediaCodecVideoEncoderError(int codecErrors); |
| 120 } |
| 121 |
| 122 public static void setErrorCallback(MediaCodecVideoEncoderErrorCallback errorC
allback) { |
| 123 Logging.d(TAG, "Set error callback"); |
| 124 MediaCodecVideoEncoder.errorCallback = errorCallback; |
| 125 } |
| 126 |
111 // Helper struct for findHwEncoder() below. | 127 // Helper struct for findHwEncoder() below. |
112 private static class EncoderProperties { | 128 private static class EncoderProperties { |
113 public EncoderProperties(String codecName, int colorFormat) { | 129 public EncoderProperties(String codecName, int colorFormat) { |
114 this.codecName = codecName; | 130 this.codecName = codecName; |
115 this.colorFormat = colorFormat; | 131 this.colorFormat = colorFormat; |
116 } | 132 } |
117 public final String codecName; // OpenMax component name for HW codec. | 133 public final String codecName; // OpenMax component name for HW codec. |
118 public final int colorFormat; // Color format supported by codec. | 134 public final int colorFormat; // Color format supported by codec. |
119 } | 135 } |
120 | 136 |
121 private static EncoderProperties findHwEncoder( | 137 private static EncoderProperties findHwEncoder( |
122 String mime, String[] supportedHwCodecPrefixes) { | 138 String mime, String[] supportedHwCodecPrefixes) { |
123 // MediaCodec.setParameters is missing for JB and below, so bitrate | 139 // MediaCodec.setParameters is missing for JB and below, so bitrate |
124 // can not be adjusted dynamically. | 140 // can not be adjusted dynamically. |
125 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { | 141 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { |
126 return null; | 142 return null; |
127 } | 143 } |
128 | 144 |
129 // Check if device is in H.264 exception list. | 145 // Check if device is in H.264 exception list. |
130 if (mime.equals(H264_MIME_TYPE)) { | 146 if (mime.equals(H264_MIME_TYPE)) { |
131 List<String> exceptionModels = Arrays.asList(H264_HW_EXCEPTION_MODELS); | 147 List<String> exceptionModels = Arrays.asList(H264_HW_EXCEPTION_MODELS); |
132 if (exceptionModels.contains(Build.MODEL)) { | 148 if (exceptionModels.contains(Build.MODEL)) { |
133 Logging.w(TAG, "Model: " + Build.MODEL + | 149 Logging.w(TAG, "Model: " + Build.MODEL + " has black listed H.264 encode
r."); |
134 " has black listed H.264 encoder."); | |
135 return null; | 150 return null; |
136 } | 151 } |
137 } | 152 } |
138 | 153 |
139 for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) { | 154 for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) { |
140 MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i); | 155 MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i); |
141 if (!info.isEncoder()) { | 156 if (!info.isEncoder()) { |
142 continue; | 157 continue; |
143 } | 158 } |
144 String name = null; | 159 String name = null; |
(...skipping 154 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
299 } | 314 } |
300 catch (IllegalStateException e) { | 315 catch (IllegalStateException e) { |
301 Logging.e(TAG, "encode failed", e); | 316 Logging.e(TAG, "encode failed", e); |
302 return false; | 317 return false; |
303 } | 318 } |
304 } | 319 } |
305 | 320 |
306 private void release() { | 321 private void release() { |
307 Logging.d(TAG, "Java releaseEncoder"); | 322 Logging.d(TAG, "Java releaseEncoder"); |
308 checkOnMediaCodecThread(); | 323 checkOnMediaCodecThread(); |
309 try { | 324 |
310 mediaCodec.stop(); | 325 // Run Mediacodec stop() and release() on separate thread since sometime |
311 mediaCodec.release(); | 326 // Mediacodec.stop() may hang. |
312 } catch (IllegalStateException e) { | 327 final AtomicBoolean releaseDone = new AtomicBoolean(false); |
313 Logging.e(TAG, "release failed", e); | 328 |
| 329 Runnable runMediaCodecRelease = new Runnable() { |
| 330 @Override |
| 331 public void run() { |
| 332 try { |
| 333 Logging.d(TAG, "Java releaseEncoder on release thread"); |
| 334 mediaCodec.stop(); |
| 335 mediaCodec.release(); |
| 336 Logging.d(TAG, "Java releaseEncoder on release thread done"); |
| 337 } catch (Exception e) { |
| 338 Logging.e(TAG, "Media encoder release failed", e); |
| 339 } |
| 340 synchronized (releaseDone) { |
| 341 releaseDone.set(true); |
| 342 releaseDone.notifyAll(); |
| 343 } |
| 344 } |
| 345 }; |
| 346 new Thread(runMediaCodecRelease).start(); |
| 347 |
| 348 if (!releaseDone.get()) { |
| 349 synchronized(releaseDone) { |
| 350 try { |
| 351 releaseDone.wait(MEDIA_CODEC_RELEASE_TIMEOUT_MS); |
| 352 } catch (InterruptedException e) { |
| 353 Logging.e(TAG, "Wait exception.", e); |
| 354 } |
| 355 } |
314 } | 356 } |
| 357 if (!releaseDone.get()) { |
| 358 Logging.e(TAG, "Media encoder release timeout"); |
| 359 codecErrors++; |
| 360 if (errorCallback != null) { |
| 361 Logging.e(TAG, "Invoke codec error callback. Errors: " + codecErrors); |
| 362 errorCallback.onMediaCodecVideoEncoderError(codecErrors); |
| 363 } |
| 364 } |
| 365 |
315 mediaCodec = null; | 366 mediaCodec = null; |
316 mediaCodecThread = null; | 367 mediaCodecThread = null; |
317 runningInstance = null; | 368 runningInstance = null; |
318 Logging.d(TAG, "Java releaseEncoder done"); | 369 Logging.d(TAG, "Java releaseEncoder done"); |
319 } | 370 } |
320 | 371 |
321 private boolean setRates(int kbps, int frameRateIgnored) { | 372 private boolean setRates(int kbps, int frameRateIgnored) { |
322 // frameRate argument is ignored - HW encoder is supposed to use | 373 // frameRate argument is ignored - HW encoder is supposed to use |
323 // video frame timestamps for bit allocation. | 374 // video frame timestamps for bit allocation. |
324 checkOnMediaCodecThread(); | 375 checkOnMediaCodecThread(); |
(...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
438 checkOnMediaCodecThread(); | 489 checkOnMediaCodecThread(); |
439 try { | 490 try { |
440 mediaCodec.releaseOutputBuffer(index, false); | 491 mediaCodec.releaseOutputBuffer(index, false); |
441 return true; | 492 return true; |
442 } catch (IllegalStateException e) { | 493 } catch (IllegalStateException e) { |
443 Logging.e(TAG, "releaseOutputBuffer failed", e); | 494 Logging.e(TAG, "releaseOutputBuffer failed", e); |
444 return false; | 495 return false; |
445 } | 496 } |
446 } | 497 } |
447 } | 498 } |
OLD | NEW |