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 15 matching lines...) Expand all Loading... | |
26 */ | 26 */ |
27 | 27 |
28 | 28 |
29 package org.webrtc; | 29 package org.webrtc; |
30 | 30 |
31 import android.media.MediaCodec; | 31 import android.media.MediaCodec; |
32 import android.media.MediaCodecInfo.CodecCapabilities; | 32 import android.media.MediaCodecInfo.CodecCapabilities; |
33 import android.media.MediaCodecInfo; | 33 import android.media.MediaCodecInfo; |
34 import android.media.MediaCodecList; | 34 import android.media.MediaCodecList; |
35 import android.media.MediaFormat; | 35 import android.media.MediaFormat; |
36 import android.opengl.EGLContext; | |
37 import android.opengl.GLES20; | |
36 import android.os.Build; | 38 import android.os.Build; |
37 import android.os.Bundle; | 39 import android.os.Bundle; |
40 import android.view.Surface; | |
38 | 41 |
39 import org.webrtc.Logging; | 42 import org.webrtc.Logging; |
40 | 43 |
41 import java.nio.ByteBuffer; | 44 import java.nio.ByteBuffer; |
42 import java.util.Arrays; | 45 import java.util.Arrays; |
43 import java.util.List; | 46 import java.util.List; |
44 | 47 |
45 // Java-side of peerconnection_jni.cc:MediaCodecVideoEncoder. | 48 // Java-side of peerconnection_jni.cc:MediaCodecVideoEncoder. |
46 // This class is an implementation detail of the Java PeerConnection API. | 49 // This class is an implementation detail of the Java PeerConnection API. |
47 // MediaCodec is thread-hostile so this class must be operated on a single | 50 // MediaCodec is thread-hostile so this class must be operated on a single |
(...skipping 10 matching lines...) Expand all Loading... | |
58 public enum VideoCodecType { | 61 public enum VideoCodecType { |
59 VIDEO_CODEC_VP8, | 62 VIDEO_CODEC_VP8, |
60 VIDEO_CODEC_VP9, | 63 VIDEO_CODEC_VP9, |
61 VIDEO_CODEC_H264 | 64 VIDEO_CODEC_H264 |
62 } | 65 } |
63 | 66 |
64 private static final int DEQUEUE_TIMEOUT = 0; // Non-blocking, no wait. | 67 private static final int DEQUEUE_TIMEOUT = 0; // Non-blocking, no wait. |
65 private Thread mediaCodecThread; | 68 private Thread mediaCodecThread; |
66 private MediaCodec mediaCodec; | 69 private MediaCodec mediaCodec; |
67 private ByteBuffer[] outputBuffers; | 70 private ByteBuffer[] outputBuffers; |
71 private EglBase eglBase; | |
72 private Surface inputSurface; | |
73 private GlRectDrawer drawer; | |
68 private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8"; | 74 private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8"; |
69 private static final String H264_MIME_TYPE = "video/avc"; | 75 private static final String H264_MIME_TYPE = "video/avc"; |
70 // List of supported HW VP8 codecs. | 76 // List of supported HW VP8 codecs. |
71 private static final String[] supportedVp8HwCodecPrefixes = | 77 private static final String[] supportedVp8HwCodecPrefixes = |
72 {"OMX.qcom.", "OMX.Intel." }; | 78 {"OMX.qcom.", "OMX.Intel." }; |
73 // List of supported HW H.264 codecs. | 79 // List of supported HW H.264 codecs. |
74 private static final String[] supportedH264HwCodecPrefixes = | 80 private static final String[] supportedH264HwCodecPrefixes = |
75 {"OMX.qcom." }; | 81 {"OMX.qcom." }; |
76 // List of devices with poor H.264 encoder quality. | 82 // List of devices with poor H.264 encoder quality. |
77 private static final String[] H264_HW_EXCEPTION_MODELS = new String[] { | 83 private static final String[] H264_HW_EXCEPTION_MODELS = new String[] { |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
110 private static class EncoderProperties { | 116 private static class EncoderProperties { |
111 public EncoderProperties(String codecName, int colorFormat) { | 117 public EncoderProperties(String codecName, int colorFormat) { |
112 this.codecName = codecName; | 118 this.codecName = codecName; |
113 this.colorFormat = colorFormat; | 119 this.colorFormat = colorFormat; |
114 } | 120 } |
115 public final String codecName; // OpenMax component name for HW codec. | 121 public final String codecName; // OpenMax component name for HW codec. |
116 public final int colorFormat; // Color format supported by codec. | 122 public final int colorFormat; // Color format supported by codec. |
117 } | 123 } |
118 | 124 |
119 private static EncoderProperties findHwEncoder( | 125 private static EncoderProperties findHwEncoder( |
120 String mime, String[] supportedHwCodecPrefixes) { | 126 String mime, String[] supportedHwCodecPrefixes, boolean useSurface) { |
121 // MediaCodec.setParameters is missing for JB and below, so bitrate | 127 // MediaCodec.setParameters is missing for JB and below, so bitrate |
122 // can not be adjusted dynamically. | 128 // can not be adjusted dynamically. |
123 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { | 129 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { |
124 return null; | 130 return null; |
125 } | 131 } |
126 | 132 |
127 // Check if device is in H.264 exception list. | 133 // Check if device is in H.264 exception list. |
128 if (mime.equals(H264_MIME_TYPE)) { | 134 if (mime.equals(H264_MIME_TYPE)) { |
129 List<String> exceptionModels = Arrays.asList(H264_HW_EXCEPTION_MODELS); | 135 List<String> exceptionModels = Arrays.asList(H264_HW_EXCEPTION_MODELS); |
130 if (exceptionModels.contains(Build.MODEL)) { | 136 if (exceptionModels.contains(Build.MODEL)) { |
(...skipping 30 matching lines...) Expand all Loading... | |
161 } | 167 } |
162 if (!supportedCodec) { | 168 if (!supportedCodec) { |
163 continue; | 169 continue; |
164 } | 170 } |
165 | 171 |
166 CodecCapabilities capabilities = info.getCapabilitiesForType(mime); | 172 CodecCapabilities capabilities = info.getCapabilitiesForType(mime); |
167 for (int colorFormat : capabilities.colorFormats) { | 173 for (int colorFormat : capabilities.colorFormats) { |
168 Logging.v(TAG, " Color: 0x" + Integer.toHexString(colorFormat)); | 174 Logging.v(TAG, " Color: 0x" + Integer.toHexString(colorFormat)); |
169 } | 175 } |
170 | 176 |
177 if (useSurface) { | |
178 for (int codecColorFormat : capabilities.colorFormats) { | |
179 if (codecColorFormat == CodecCapabilities.COLOR_FormatSurface) { | |
180 // Found supported HW encoder. | |
181 Logging.d(TAG, "Found target encoder for mime " + mime + " : " + nam e + | |
182 ". Using Surface as input"); | |
183 return new EncoderProperties(name, codecColorFormat); | |
184 } | |
185 } | |
186 return null; | |
187 } | |
188 | |
171 // Check if codec supports either yuv420 or nv12. | 189 // Check if codec supports either yuv420 or nv12. |
172 for (int supportedColorFormat : supportedColorList) { | 190 for (int supportedColorFormat : supportedColorList) { |
173 for (int codecColorFormat : capabilities.colorFormats) { | 191 for (int codecColorFormat : capabilities.colorFormats) { |
174 if (codecColorFormat == supportedColorFormat) { | 192 if (codecColorFormat == supportedColorFormat) { |
175 // Found supported HW encoder. | 193 // Found supported HW encoder. |
176 Logging.d(TAG, "Found target encoder for mime " + mime + " : " + nam e + | 194 Logging.d(TAG, "Found target encoder for mime " + mime + " : " + nam e + |
177 ". Color: 0x" + Integer.toHexString(codecColorFormat)); | 195 ". Color: 0x" + Integer.toHexString(codecColorFormat)); |
178 return new EncoderProperties(name, codecColorFormat); | 196 return new EncoderProperties(name, codecColorFormat); |
179 } | 197 } |
180 } | 198 } |
181 } | 199 } |
182 } | 200 } |
183 return null; // No HW VP8 encoder. | 201 return null; // No HW VP8 encoder. |
184 } | 202 } |
185 | 203 |
186 public static boolean isVp8HwSupported() { | 204 public static boolean isVp8HwSupported() { |
187 return findHwEncoder(VP8_MIME_TYPE, supportedVp8HwCodecPrefixes) != null; | 205 return findHwEncoder(VP8_MIME_TYPE, supportedVp8HwCodecPrefixes, false) != n ull; |
188 } | 206 } |
189 | 207 |
190 public static boolean isH264HwSupported() { | 208 public static boolean isH264HwSupported() { |
191 return findHwEncoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes) != null; | 209 return findHwEncoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes, false) != null; |
210 } | |
211 | |
212 public static boolean isVp8HwSupportedUsingTextures() { | |
AlexG
2015/10/14 23:48:27
Are these 2 functions called from native code or f
perkj_webrtc
2015/11/16 13:08:51
they are here so that an app can ask if the hw cod
| |
213 return findHwEncoder(VP8_MIME_TYPE, supportedVp8HwCodecPrefixes, true) != nu ll; | |
214 } | |
215 | |
216 public static boolean isH264HwSupportedUsingTextures() { | |
217 return findHwEncoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes, true) != null; | |
192 } | 218 } |
193 | 219 |
194 private void checkOnMediaCodecThread() { | 220 private void checkOnMediaCodecThread() { |
195 if (mediaCodecThread.getId() != Thread.currentThread().getId()) { | 221 if (mediaCodecThread.getId() != Thread.currentThread().getId()) { |
196 throw new RuntimeException( | 222 throw new RuntimeException( |
197 "MediaCodecVideoEncoder previously operated on " + mediaCodecThread + | 223 "MediaCodecVideoEncoder previously operated on " + mediaCodecThread + |
198 " but is now called on " + Thread.currentThread()); | 224 " but is now called on " + Thread.currentThread()); |
199 } | 225 } |
200 } | 226 } |
201 | 227 |
202 static MediaCodec createByCodecName(String codecName) { | 228 static MediaCodec createByCodecName(String codecName) { |
203 try { | 229 try { |
204 // In the L-SDK this call can throw IOException so in order to work in | 230 // In the L-SDK this call can throw IOException so in order to work in |
205 // both cases catch an exception. | 231 // both cases catch an exception. |
206 return MediaCodec.createByCodecName(codecName); | 232 return MediaCodec.createByCodecName(codecName); |
207 } catch (Exception e) { | 233 } catch (Exception e) { |
208 return null; | 234 return null; |
209 } | 235 } |
210 } | 236 } |
211 | 237 |
212 // Returns false if the hardware encoder currently can't be used. | 238 boolean initEncode(VideoCodecType type, int width, int height, int kbps, int f ps, |
213 boolean initEncode(VideoCodecType type, int width, int height, int kbps, int f ps) { | 239 EGLContext sharedContext) { |
240 final boolean useSurface = sharedContext != null; | |
214 Logging.d(TAG, "Java initEncode: " + type + " : " + width + " x " + height + | 241 Logging.d(TAG, "Java initEncode: " + type + " : " + width + " x " + height + |
215 ". @ " + kbps + " kbps. Fps: " + fps + "."); | 242 ". @ " + kbps + " kbps. Fps: " + fps + ". Encode from texture : " + |
243 (useSurface ? "True" : "False")); | |
216 | 244 |
217 if (mediaCodecThread != null) { | 245 if (mediaCodecThread != null) { |
218 throw new RuntimeException("Forgot to release()?"); | 246 throw new RuntimeException("Forgot to release()?"); |
219 } | 247 } |
220 EncoderProperties properties = null; | 248 EncoderProperties properties = null; |
221 String mime = null; | 249 String mime = null; |
222 int keyFrameIntervalSec = 0; | 250 int keyFrameIntervalSec = 0; |
223 if (type == VideoCodecType.VIDEO_CODEC_VP8) { | 251 if (type == VideoCodecType.VIDEO_CODEC_VP8) { |
224 mime = VP8_MIME_TYPE; | 252 mime = VP8_MIME_TYPE; |
225 properties = findHwEncoder(VP8_MIME_TYPE, supportedVp8HwCodecPrefixes); | 253 properties = findHwEncoder(VP8_MIME_TYPE, supportedVp8HwCodecPrefixes, use Surface); |
226 keyFrameIntervalSec = 100; | 254 keyFrameIntervalSec = 100; |
227 } else if (type == VideoCodecType.VIDEO_CODEC_H264) { | 255 } else if (type == VideoCodecType.VIDEO_CODEC_H264) { |
228 mime = H264_MIME_TYPE; | 256 mime = H264_MIME_TYPE; |
229 properties = findHwEncoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes); | 257 properties = findHwEncoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes, u seSurface); |
230 keyFrameIntervalSec = 20; | 258 keyFrameIntervalSec = 20; |
231 } | 259 } |
232 if (properties == null) { | 260 if (properties == null) { |
233 throw new RuntimeException("Can not find HW encoder for " + type); | 261 throw new RuntimeException("Can not find HW encoder for " + type); |
234 } | 262 } |
235 colorFormat = properties.colorFormat; | 263 colorFormat = properties.colorFormat; |
236 mediaCodecThread = Thread.currentThread(); | 264 mediaCodecThread = Thread.currentThread(); |
237 try { | 265 try { |
238 MediaFormat format = MediaFormat.createVideoFormat(mime, width, height); | 266 MediaFormat format = MediaFormat.createVideoFormat(mime, width, height); |
239 format.setInteger(MediaFormat.KEY_BIT_RATE, 1000 * kbps); | 267 format.setInteger(MediaFormat.KEY_BIT_RATE, 1000 * kbps); |
240 format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); | 268 format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); |
241 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat); | 269 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat); |
242 format.setInteger(MediaFormat.KEY_FRAME_RATE, fps); | 270 format.setInteger(MediaFormat.KEY_FRAME_RATE, fps); |
243 format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, keyFrameIntervalSec); | 271 format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, keyFrameIntervalSec); |
244 Logging.d(TAG, " Format: " + format); | 272 Logging.d(TAG, " Format: " + format); |
245 mediaCodec = createByCodecName(properties.codecName); | 273 mediaCodec = createByCodecName(properties.codecName); |
246 this.type = type; | 274 this.type = type; |
247 if (mediaCodec == null) { | 275 if (mediaCodec == null) { |
248 return false; | 276 return false; |
249 } | 277 } |
250 mediaCodec.configure( | 278 mediaCodec.configure( |
251 format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); | 279 format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); |
252 | 280 |
281 if (useSurface) { | |
282 eglBase = new EglBase(sharedContext, EglBase.ConfigType.RECORDABLE); | |
283 // Create an input surface and keep a reference since we must release th e surface when done. | |
284 inputSurface = mediaCodec.createInputSurface(); | |
285 eglBase.createSurface(inputSurface); | |
286 drawer = new GlRectDrawer(); | |
287 } | |
253 mediaCodec.start(); | 288 mediaCodec.start(); |
254 outputBuffers = mediaCodec.getOutputBuffers(); | 289 outputBuffers = mediaCodec.getOutputBuffers(); |
255 | 290 |
256 } catch (IllegalStateException e) { | 291 } catch (IllegalStateException e) { |
257 Logging.e(TAG, "initEncode failed", e); | 292 Logging.e(TAG, "initEncode failed", e); |
258 return false; | 293 return false; |
259 } | 294 } |
260 return true; | 295 return true; |
261 } | 296 } |
262 | 297 |
(...skipping 21 matching lines...) Expand all Loading... | |
284 mediaCodec.queueInputBuffer( | 319 mediaCodec.queueInputBuffer( |
285 inputBuffer, 0, size, presentationTimestampUs, 0); | 320 inputBuffer, 0, size, presentationTimestampUs, 0); |
286 return true; | 321 return true; |
287 } | 322 } |
288 catch (IllegalStateException e) { | 323 catch (IllegalStateException e) { |
289 Logging.e(TAG, "encodeBuffer failed", e); | 324 Logging.e(TAG, "encodeBuffer failed", e); |
290 return false; | 325 return false; |
291 } | 326 } |
292 } | 327 } |
293 | 328 |
329 boolean encodeTexture(boolean isKeyframe, int oesTextureId, float[] transforma tionMatrix, | |
330 long presentationTimestampUs) { | |
331 checkOnMediaCodecThread(); | |
332 try { | |
333 if (isKeyframe) { | |
334 Logging.d(TAG, "Sync frame request"); | |
335 Bundle b = new Bundle(); | |
336 b.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0); | |
337 mediaCodec.setParameters(b); | |
338 } | |
339 eglBase.makeCurrent(); | |
340 drawer.drawOes(oesTextureId, transformationMatrix); | |
341 eglBase.swapBuffers(presentationTimestampUs * 1000); | |
342 return true; | |
343 } | |
344 catch (RuntimeException e) { | |
345 Logging.e(TAG, "encodeTexture failed", e); | |
346 return false; | |
347 } | |
348 } | |
349 | |
294 void release() { | 350 void release() { |
295 Logging.d(TAG, "Java releaseEncoder"); | 351 Logging.d(TAG, "Java releaseEncoder"); |
296 checkOnMediaCodecThread(); | 352 checkOnMediaCodecThread(); |
297 try { | 353 try { |
298 mediaCodec.stop(); | 354 mediaCodec.stop(); |
299 mediaCodec.release(); | 355 mediaCodec.release(); |
300 } catch (IllegalStateException e) { | 356 } catch (IllegalStateException e) { |
301 Logging.e(TAG, "release failed", e); | 357 Logging.e(TAG, "release failed", e); |
302 } | 358 } |
303 mediaCodec = null; | 359 mediaCodec = null; |
304 mediaCodecThread = null; | 360 mediaCodecThread = null; |
361 if (drawer != null) { | |
362 drawer.release(); | |
AlexG
2015/10/14 23:48:27
nit: drawer = null and same for 2 checks below?
perkj_webrtc
2015/11/16 13:08:51
Done.
| |
363 } | |
364 if (eglBase != null) { | |
365 eglBase.release(); | |
366 } | |
367 if (inputSurface != null) { | |
368 inputSurface.release(); | |
369 } | |
305 } | 370 } |
306 | 371 |
307 private boolean setRates(int kbps, int frameRateIgnored) { | 372 private boolean setRates(int kbps, int frameRateIgnored) { |
308 // frameRate argument is ignored - HW encoder is supposed to use | 373 // frameRate argument is ignored - HW encoder is supposed to use |
309 // video frame timestamps for bit allocation. | 374 // video frame timestamps for bit allocation. |
310 checkOnMediaCodecThread(); | 375 checkOnMediaCodecThread(); |
311 Logging.v(TAG, "setRates: " + kbps + " kbps. Fps: " + frameRateIgnored); | 376 Logging.v(TAG, "setRates: " + kbps + " kbps. Fps: " + frameRateIgnored); |
312 try { | 377 try { |
313 Bundle params = new Bundle(); | 378 Bundle params = new Bundle(); |
314 params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, 1000 * kbps); | 379 params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, 1000 * kbps); |
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
424 checkOnMediaCodecThread(); | 489 checkOnMediaCodecThread(); |
425 try { | 490 try { |
426 mediaCodec.releaseOutputBuffer(index, false); | 491 mediaCodec.releaseOutputBuffer(index, false); |
427 return true; | 492 return true; |
428 } catch (IllegalStateException e) { | 493 } catch (IllegalStateException e) { |
429 Logging.e(TAG, "releaseOutputBuffer failed", e); | 494 Logging.e(TAG, "releaseOutputBuffer failed", e); |
430 return false; | 495 return false; |
431 } | 496 } |
432 } | 497 } |
433 } | 498 } |
OLD | NEW |