Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(10)

Side by Side Diff: talk/app/webrtc/java/src/org/webrtc/MediaCodecVideoEncoder.java

Issue 1403713002: MediaCodecVideoEncoder add support to encode from textures (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: Fixed tests Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 14 matching lines...) Expand all
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */ 26 */
27 27
28 package org.webrtc; 28 package org.webrtc;
29 29
30 import android.media.MediaCodec; 30 import android.media.MediaCodec;
31 import android.media.MediaCodecInfo.CodecCapabilities; 31 import android.media.MediaCodecInfo.CodecCapabilities;
32 import android.media.MediaCodecInfo; 32 import android.media.MediaCodecInfo;
33 import android.media.MediaCodecList; 33 import android.media.MediaCodecList;
34 import android.media.MediaFormat; 34 import android.media.MediaFormat;
35 import android.opengl.GLES20;
35 import android.os.Build; 36 import android.os.Build;
36 import android.os.Bundle; 37 import android.os.Bundle;
38 import android.view.Surface;
37 39
38 import org.webrtc.Logging; 40 import org.webrtc.Logging;
39 41
40 import java.nio.ByteBuffer; 42 import java.nio.ByteBuffer;
41 import java.util.Arrays; 43 import java.util.Arrays;
42 import java.util.List; 44 import java.util.List;
43 import java.util.concurrent.CountDownLatch; 45 import java.util.concurrent.CountDownLatch;
44 46
47 import javax.microedition.khronos.egl.EGLContext;
48
45 // Java-side of peerconnection_jni.cc:MediaCodecVideoEncoder. 49 // Java-side of peerconnection_jni.cc:MediaCodecVideoEncoder.
46 // This class is an implementation detail of the Java PeerConnection API. 50 // This class is an implementation detail of the Java PeerConnection API.
47 public class MediaCodecVideoEncoder { 51 public class MediaCodecVideoEncoder {
48 // This class is constructed, operated, and destroyed by its C++ incarnation, 52 // This class is constructed, operated, and destroyed by its C++ incarnation,
49 // so the class and its methods have non-public visibility. The API this 53 // so the class and its methods have non-public visibility. The API this
50 // class exposes aims to mimic the webrtc::VideoEncoder API as closely as 54 // class exposes aims to mimic the webrtc::VideoEncoder API as closely as
51 // possibly to minimize the amount of translation work necessary. 55 // possibly to minimize the amount of translation work necessary.
52 56
53 private static final String TAG = "MediaCodecVideoEncoder"; 57 private static final String TAG = "MediaCodecVideoEncoder";
54 58
55 // Tracks webrtc::VideoCodecType. 59 // Tracks webrtc::VideoCodecType.
56 public enum VideoCodecType { 60 public enum VideoCodecType {
57 VIDEO_CODEC_VP8, 61 VIDEO_CODEC_VP8,
58 VIDEO_CODEC_VP9, 62 VIDEO_CODEC_VP9,
59 VIDEO_CODEC_H264 63 VIDEO_CODEC_H264
60 } 64 }
61 65
62 private static final int MEDIA_CODEC_RELEASE_TIMEOUT_MS = 5000; // Timeout for codec releasing. 66 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. 67 private static final int DEQUEUE_TIMEOUT = 0; // Non-blocking, no wait.
64 // Active running encoder instance. Set in initEncode() (called from native co de) 68 // Active running encoder instance. Set in initEncode() (called from native co de)
65 // and reset to null in release() call. 69 // and reset to null in release() call.
66 private static MediaCodecVideoEncoder runningInstance = null; 70 private static MediaCodecVideoEncoder runningInstance = null;
67 private static MediaCodecVideoEncoderErrorCallback errorCallback = null; 71 private static MediaCodecVideoEncoderErrorCallback errorCallback = null;
68 private static int codecErrors = 0; 72 private static int codecErrors = 0;
69 73
70 private Thread mediaCodecThread; 74 private Thread mediaCodecThread;
71 private MediaCodec mediaCodec; 75 private MediaCodec mediaCodec;
72 private ByteBuffer[] outputBuffers; 76 private ByteBuffer[] outputBuffers;
77 private EglBase eglBase;
78 private Surface inputSurface;
79 private GlRectDrawer drawer;
73 private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8"; 80 private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8";
74 private static final String H264_MIME_TYPE = "video/avc"; 81 private static final String H264_MIME_TYPE = "video/avc";
75 // List of supported HW VP8 codecs. 82 // List of supported HW VP8 codecs.
76 private static final String[] supportedVp8HwCodecPrefixes = 83 private static final String[] supportedVp8HwCodecPrefixes =
77 {"OMX.qcom.", "OMX.Intel." }; 84 {"OMX.qcom.", "OMX.Intel." };
78 // List of supported HW H.264 codecs. 85 // List of supported HW H.264 codecs.
79 private static final String[] supportedH264HwCodecPrefixes = 86 private static final String[] supportedH264HwCodecPrefixes =
80 {"OMX.qcom." }; 87 {"OMX.qcom." };
81 // List of devices with poor H.264 encoder quality. 88 // List of devices with poor H.264 encoder quality.
82 private static final String[] H264_HW_EXCEPTION_MODELS = new String[] { 89 private static final String[] H264_HW_EXCEPTION_MODELS = new String[] {
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
124 private static class EncoderProperties { 131 private static class EncoderProperties {
125 public EncoderProperties(String codecName, int colorFormat) { 132 public EncoderProperties(String codecName, int colorFormat) {
126 this.codecName = codecName; 133 this.codecName = codecName;
127 this.colorFormat = colorFormat; 134 this.colorFormat = colorFormat;
128 } 135 }
129 public final String codecName; // OpenMax component name for HW codec. 136 public final String codecName; // OpenMax component name for HW codec.
130 public final int colorFormat; // Color format supported by codec. 137 public final int colorFormat; // Color format supported by codec.
131 } 138 }
132 139
133 private static EncoderProperties findHwEncoder( 140 private static EncoderProperties findHwEncoder(
134 String mime, String[] supportedHwCodecPrefixes) { 141 String mime, String[] supportedHwCodecPrefixes, boolean useSurface) {
135 // MediaCodec.setParameters is missing for JB and below, so bitrate 142 // MediaCodec.setParameters is missing for JB and below, so bitrate
136 // can not be adjusted dynamically. 143 // can not be adjusted dynamically.
137 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 144 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
138 return null; 145 return null;
139 } 146 }
140 147
141 // Check if device is in H.264 exception list. 148 // Check if device is in H.264 exception list.
142 if (mime.equals(H264_MIME_TYPE)) { 149 if (mime.equals(H264_MIME_TYPE)) {
143 List<String> exceptionModels = Arrays.asList(H264_HW_EXCEPTION_MODELS); 150 List<String> exceptionModels = Arrays.asList(H264_HW_EXCEPTION_MODELS);
144 if (exceptionModels.contains(Build.MODEL)) { 151 if (exceptionModels.contains(Build.MODEL)) {
(...skipping 29 matching lines...) Expand all
174 } 181 }
175 if (!supportedCodec) { 182 if (!supportedCodec) {
176 continue; 183 continue;
177 } 184 }
178 185
179 CodecCapabilities capabilities = info.getCapabilitiesForType(mime); 186 CodecCapabilities capabilities = info.getCapabilitiesForType(mime);
180 for (int colorFormat : capabilities.colorFormats) { 187 for (int colorFormat : capabilities.colorFormats) {
181 Logging.v(TAG, " Color: 0x" + Integer.toHexString(colorFormat)); 188 Logging.v(TAG, " Color: 0x" + Integer.toHexString(colorFormat));
182 } 189 }
183 190
191 if (useSurface) {
192 for (int codecColorFormat : capabilities.colorFormats) {
magjed_webrtc 2015/11/18 13:12:52 This looks like duplicated code. I suggest replaci
perkj_webrtc 2015/11/18 14:51:21 nice. yes
193 if (codecColorFormat == CodecCapabilities.COLOR_FormatSurface) {
194 // Found supported HW encoder.
195 Logging.d(TAG, "Found target encoder for mime " + mime + " : " + nam e +
196 ". Using Surface as input");
197 return new EncoderProperties(name, codecColorFormat);
198 }
199 }
200 return null;
201 }
202
184 // Check if codec supports either yuv420 or nv12. 203 // Check if codec supports either yuv420 or nv12.
185 for (int supportedColorFormat : supportedColorList) { 204 for (int supportedColorFormat : supportedColorList) {
186 for (int codecColorFormat : capabilities.colorFormats) { 205 for (int codecColorFormat : capabilities.colorFormats) {
187 if (codecColorFormat == supportedColorFormat) { 206 if (codecColorFormat == supportedColorFormat) {
188 // Found supported HW encoder. 207 // Found supported HW encoder.
189 Logging.d(TAG, "Found target encoder for mime " + mime + " : " + nam e + 208 Logging.d(TAG, "Found target encoder for mime " + mime + " : " + nam e +
190 ". Color: 0x" + Integer.toHexString(codecColorFormat)); 209 ". Color: 0x" + Integer.toHexString(codecColorFormat));
191 return new EncoderProperties(name, codecColorFormat); 210 return new EncoderProperties(name, codecColorFormat);
192 } 211 }
193 } 212 }
194 } 213 }
195 } 214 }
196 return null; // No HW VP8 encoder. 215 return null; // No HW VP8 encoder.
197 } 216 }
198 217
199 public static boolean isVp8HwSupported() { 218 public static boolean isVp8HwSupported() {
200 return findHwEncoder(VP8_MIME_TYPE, supportedVp8HwCodecPrefixes) != null; 219 return findHwEncoder(VP8_MIME_TYPE, supportedVp8HwCodecPrefixes, false) != n ull;
201 } 220 }
202 221
203 public static boolean isH264HwSupported() { 222 public static boolean isH264HwSupported() {
204 return findHwEncoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes) != null; 223 return findHwEncoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes, false) != null;
224 }
225
226 public static boolean isVp8HwSupportedUsingTextures() {
227 return findHwEncoder(VP8_MIME_TYPE, supportedVp8HwCodecPrefixes, true) != nu ll;
228 }
229
230 public static boolean isH264HwSupportedUsingTextures() {
231 return findHwEncoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes, true) != null;
205 } 232 }
206 233
207 private void checkOnMediaCodecThread() { 234 private void checkOnMediaCodecThread() {
208 if (mediaCodecThread.getId() != Thread.currentThread().getId()) { 235 if (mediaCodecThread.getId() != Thread.currentThread().getId()) {
209 throw new RuntimeException( 236 throw new RuntimeException(
210 "MediaCodecVideoEncoder previously operated on " + mediaCodecThread + 237 "MediaCodecVideoEncoder previously operated on " + mediaCodecThread +
211 " but is now called on " + Thread.currentThread()); 238 " but is now called on " + Thread.currentThread());
212 } 239 }
213 } 240 }
214 241
(...skipping 12 matching lines...) Expand all
227 static MediaCodec createByCodecName(String codecName) { 254 static MediaCodec createByCodecName(String codecName) {
228 try { 255 try {
229 // In the L-SDK this call can throw IOException so in order to work in 256 // In the L-SDK this call can throw IOException so in order to work in
230 // both cases catch an exception. 257 // both cases catch an exception.
231 return MediaCodec.createByCodecName(codecName); 258 return MediaCodec.createByCodecName(codecName);
232 } catch (Exception e) { 259 } catch (Exception e) {
233 return null; 260 return null;
234 } 261 }
235 } 262 }
236 263
237 // Returns false if the hardware encoder currently can't be used. 264 boolean initEncode(VideoCodecType type, int width, int height, int kbps, int f ps,
238 boolean initEncode(VideoCodecType type, int width, int height, int kbps, int f ps) { 265 EGLContext sharedContext) {
266 final boolean useSurface = sharedContext != null;
239 Logging.d(TAG, "Java initEncode: " + type + " : " + width + " x " + height + 267 Logging.d(TAG, "Java initEncode: " + type + " : " + width + " x " + height +
240 ". @ " + kbps + " kbps. Fps: " + fps + "."); 268 ". @ " + kbps + " kbps. Fps: " + fps + ". Encode from texture : " +
269 (useSurface ? "True" : "False"));
magjed_webrtc 2015/11/18 13:12:52 Just print useSurface, you don't need to convert i
perkj_webrtc 2015/11/18 14:51:21 Done.
241 270
242 if (mediaCodecThread != null) { 271 if (mediaCodecThread != null) {
243 throw new RuntimeException("Forgot to release()?"); 272 throw new RuntimeException("Forgot to release()?");
244 } 273 }
245 EncoderProperties properties = null; 274 EncoderProperties properties = null;
246 String mime = null; 275 String mime = null;
247 int keyFrameIntervalSec = 0; 276 int keyFrameIntervalSec = 0;
248 if (type == VideoCodecType.VIDEO_CODEC_VP8) { 277 if (type == VideoCodecType.VIDEO_CODEC_VP8) {
249 mime = VP8_MIME_TYPE; 278 mime = VP8_MIME_TYPE;
250 properties = findHwEncoder(VP8_MIME_TYPE, supportedVp8HwCodecPrefixes); 279 properties = findHwEncoder(VP8_MIME_TYPE, supportedVp8HwCodecPrefixes, use Surface);
251 keyFrameIntervalSec = 100; 280 keyFrameIntervalSec = 100;
252 } else if (type == VideoCodecType.VIDEO_CODEC_H264) { 281 } else if (type == VideoCodecType.VIDEO_CODEC_H264) {
253 mime = H264_MIME_TYPE; 282 mime = H264_MIME_TYPE;
254 properties = findHwEncoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes); 283 properties = findHwEncoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes, u seSurface);
255 keyFrameIntervalSec = 20; 284 keyFrameIntervalSec = 20;
256 } 285 }
257 if (properties == null) { 286 if (properties == null) {
258 throw new RuntimeException("Can not find HW encoder for " + type); 287 throw new RuntimeException("Can not find HW encoder for " + type);
259 } 288 }
260 runningInstance = this; // Encoder is now running and can be queried for sta ck traces. 289 runningInstance = this; // Encoder is now running and can be queried for sta ck traces.
261 colorFormat = properties.colorFormat; 290 colorFormat = properties.colorFormat;
262 Logging.d(TAG, "Color format: " + colorFormat); 291 Logging.d(TAG, "Color format: " + colorFormat);
263 292
264 mediaCodecThread = Thread.currentThread(); 293 mediaCodecThread = Thread.currentThread();
265 try { 294 try {
266 MediaFormat format = MediaFormat.createVideoFormat(mime, width, height); 295 MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
267 format.setInteger(MediaFormat.KEY_BIT_RATE, 1000 * kbps); 296 format.setInteger(MediaFormat.KEY_BIT_RATE, 1000 * kbps);
268 format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); 297 format.setInteger("bitrate-mode", VIDEO_ControlRateConstant);
269 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat); 298 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
270 format.setInteger(MediaFormat.KEY_FRAME_RATE, fps); 299 format.setInteger(MediaFormat.KEY_FRAME_RATE, fps);
271 format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, keyFrameIntervalSec); 300 format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, keyFrameIntervalSec);
272 Logging.d(TAG, " Format: " + format); 301 Logging.d(TAG, " Format: " + format);
273 mediaCodec = createByCodecName(properties.codecName); 302 mediaCodec = createByCodecName(properties.codecName);
274 this.type = type; 303 this.type = type;
275 if (mediaCodec == null) { 304 if (mediaCodec == null) {
276 Logging.e(TAG, "Can not create media encoder"); 305 Logging.e(TAG, "Can not create media encoder");
277 return false; 306 return false;
278 } 307 }
279 mediaCodec.configure( 308 mediaCodec.configure(
280 format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 309 format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
281 310
311 if (useSurface) {
312 eglBase = new EglBase(sharedContext, EglBase.ConfigType.RECORDABLE);
313 // Create an input surface and keep a reference since we must release th e surface when done.
314 inputSurface = mediaCodec.createInputSurface();
315 eglBase.createSurface(inputSurface);
316 drawer = new GlRectDrawer();
317 }
282 mediaCodec.start(); 318 mediaCodec.start();
283 outputBuffers = mediaCodec.getOutputBuffers(); 319 outputBuffers = mediaCodec.getOutputBuffers();
284 Logging.d(TAG, "Output buffers: " + outputBuffers.length); 320 Logging.d(TAG, "Output buffers: " + outputBuffers.length);
285 321
286 } catch (IllegalStateException e) { 322 } catch (IllegalStateException e) {
287 Logging.e(TAG, "initEncode failed", e); 323 Logging.e(TAG, "initEncode failed", e);
288 return false; 324 return false;
289 } 325 }
290 return true; 326 return true;
291 } 327 }
(...skipping 22 matching lines...) Expand all
314 mediaCodec.queueInputBuffer( 350 mediaCodec.queueInputBuffer(
315 inputBuffer, 0, size, presentationTimestampUs, 0); 351 inputBuffer, 0, size, presentationTimestampUs, 0);
316 return true; 352 return true;
317 } 353 }
318 catch (IllegalStateException e) { 354 catch (IllegalStateException e) {
319 Logging.e(TAG, "encodeBuffer failed", e); 355 Logging.e(TAG, "encodeBuffer failed", e);
320 return false; 356 return false;
321 } 357 }
322 } 358 }
323 359
360 boolean encodeTexture(boolean isKeyframe, int oesTextureId, float[] transforma tionMatrix,
361 long presentationTimestampUs) {
362 checkOnMediaCodecThread();
363 try {
364 if (isKeyframe) {
365 Logging.d(TAG, "Sync frame request");
366 Bundle b = new Bundle();
367 b.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
368 mediaCodec.setParameters(b);
369 }
370 eglBase.makeCurrent();
371 drawer.drawOes(oesTextureId, transformationMatrix);
372 // TODO(perkj): Do we have to call EGLExt.eglPresentationTimeANDROID ?
373 // If not, remove |presentationTimestampUs|.
374 eglBase.swapBuffers();
375 return true;
376 }
377 catch (RuntimeException e) {
378 Logging.e(TAG, "encodeTexture failed", e);
379 return false;
380 }
381 }
382
324 void release() { 383 void release() {
325 Logging.d(TAG, "Java releaseEncoder"); 384 Logging.d(TAG, "Java releaseEncoder");
326 checkOnMediaCodecThread(); 385 checkOnMediaCodecThread();
327 386
328 // Run Mediacodec stop() and release() on separate thread since sometime 387 // Run Mediacodec stop() and release() on separate thread since sometime
329 // Mediacodec.stop() may hang. 388 // Mediacodec.stop() may hang.
330 final CountDownLatch releaseDone = new CountDownLatch(1); 389 final CountDownLatch releaseDone = new CountDownLatch(1);
331 390
332 Runnable runMediaCodecRelease = new Runnable() { 391 Runnable runMediaCodecRelease = new Runnable() {
333 @Override 392 @Override
(...skipping 15 matching lines...) Expand all
349 Logging.e(TAG, "Media encoder release timeout"); 408 Logging.e(TAG, "Media encoder release timeout");
350 codecErrors++; 409 codecErrors++;
351 if (errorCallback != null) { 410 if (errorCallback != null) {
352 Logging.e(TAG, "Invoke codec error callback. Errors: " + codecErrors); 411 Logging.e(TAG, "Invoke codec error callback. Errors: " + codecErrors);
353 errorCallback.onMediaCodecVideoEncoderCriticalError(codecErrors); 412 errorCallback.onMediaCodecVideoEncoderCriticalError(codecErrors);
354 } 413 }
355 } 414 }
356 415
357 mediaCodec = null; 416 mediaCodec = null;
358 mediaCodecThread = null; 417 mediaCodecThread = null;
418 if (drawer != null) {
419 drawer.release();
420 drawer = null;
421 }
422 if (eglBase != null) {
423 eglBase.release();
424 eglBase = null;
425 }
426 if (inputSurface != null) {
427 inputSurface.release();
428 inputSurface = null;
429 }
359 runningInstance = null; 430 runningInstance = null;
360 Logging.d(TAG, "Java releaseEncoder done"); 431 Logging.d(TAG, "Java releaseEncoder done");
361 } 432 }
362 433
363 private boolean setRates(int kbps, int frameRateIgnored) { 434 private boolean setRates(int kbps, int frameRateIgnored) {
364 // frameRate argument is ignored - HW encoder is supposed to use 435 // frameRate argument is ignored - HW encoder is supposed to use
365 // video frame timestamps for bit allocation. 436 // video frame timestamps for bit allocation.
366 checkOnMediaCodecThread(); 437 checkOnMediaCodecThread();
367 Logging.v(TAG, "setRates: " + kbps + " kbps. Fps: " + frameRateIgnored); 438 Logging.v(TAG, "setRates: " + kbps + " kbps. Fps: " + frameRateIgnored);
368 try { 439 try {
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after
480 checkOnMediaCodecThread(); 551 checkOnMediaCodecThread();
481 try { 552 try {
482 mediaCodec.releaseOutputBuffer(index, false); 553 mediaCodec.releaseOutputBuffer(index, false);
483 return true; 554 return true;
484 } catch (IllegalStateException e) { 555 } catch (IllegalStateException e) {
485 Logging.e(TAG, "releaseOutputBuffer failed", e); 556 Logging.e(TAG, "releaseOutputBuffer failed", e);
486 return false; 557 return false;
487 } 558 }
488 } 559 }
489 } 560 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698