OLD | NEW |
1 /* | 1 /* |
2 * libjingle | 2 * libjingle |
3 * Copyright 2014 Google Inc. | 3 * Copyright 2014 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 package org.webrtc; | 28 package org.webrtc; |
29 | 29 |
30 import android.graphics.SurfaceTexture; | 30 import android.graphics.SurfaceTexture; |
31 import android.media.MediaCodec; | 31 import android.media.MediaCodec; |
32 import android.media.MediaCodecInfo; | 32 import android.media.MediaCodecInfo; |
33 import android.media.MediaCodecInfo.CodecCapabilities; | 33 import android.media.MediaCodecInfo.CodecCapabilities; |
34 import android.media.MediaCodecList; | 34 import android.media.MediaCodecList; |
35 import android.media.MediaFormat; | 35 import android.media.MediaFormat; |
36 import android.opengl.EGL14; | |
37 import android.opengl.EGLContext; | 36 import android.opengl.EGLContext; |
38 import android.opengl.GLES11Ext; | 37 import android.opengl.GLES11Ext; |
39 import android.opengl.GLES20; | 38 import android.opengl.GLES20; |
40 import android.os.Build; | 39 import android.os.Build; |
| 40 import android.util.Log; |
41 import android.view.Surface; | 41 import android.view.Surface; |
42 | 42 |
43 import org.webrtc.Logging; | |
44 | |
45 import java.nio.ByteBuffer; | 43 import java.nio.ByteBuffer; |
46 | 44 |
47 // Java-side of peerconnection_jni.cc:MediaCodecVideoDecoder. | 45 // Java-side of peerconnection_jni.cc:MediaCodecVideoDecoder. |
48 // This class is an implementation detail of the Java PeerConnection API. | 46 // This class is an implementation detail of the Java PeerConnection API. |
49 // 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 |
50 // thread. | 48 // thread. |
51 public class MediaCodecVideoDecoder { | 49 public class MediaCodecVideoDecoder { |
52 // This class is constructed, operated, and destroyed by its C++ incarnation, | 50 // This class is constructed, operated, and destroyed by its C++ incarnation, |
53 // 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 |
54 // class exposes aims to mimic the webrtc::VideoDecoder API as closely as | 52 // class exposes aims to mimic the webrtc::VideoDecoder API as closely as |
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
123 String name = null; | 121 String name = null; |
124 for (String mimeType : info.getSupportedTypes()) { | 122 for (String mimeType : info.getSupportedTypes()) { |
125 if (mimeType.equals(mime)) { | 123 if (mimeType.equals(mime)) { |
126 name = info.getName(); | 124 name = info.getName(); |
127 break; | 125 break; |
128 } | 126 } |
129 } | 127 } |
130 if (name == null) { | 128 if (name == null) { |
131 continue; // No HW support in this codec; try the next one. | 129 continue; // No HW support in this codec; try the next one. |
132 } | 130 } |
133 Logging.v(TAG, "Found candidate decoder " + name); | 131 Log.v(TAG, "Found candidate decoder " + name); |
134 | 132 |
135 // Check if this is supported decoder. | 133 // Check if this is supported decoder. |
136 boolean supportedCodec = false; | 134 boolean supportedCodec = false; |
137 for (String codecPrefix : supportedCodecPrefixes) { | 135 for (String codecPrefix : supportedCodecPrefixes) { |
138 if (name.startsWith(codecPrefix)) { | 136 if (name.startsWith(codecPrefix)) { |
139 supportedCodec = true; | 137 supportedCodec = true; |
140 break; | 138 break; |
141 } | 139 } |
142 } | 140 } |
143 if (!supportedCodec) { | 141 if (!supportedCodec) { |
144 continue; | 142 continue; |
145 } | 143 } |
146 | 144 |
147 // Check if codec supports either yuv420 or nv12. | 145 // Check if codec supports either yuv420 or nv12. |
148 CodecCapabilities capabilities = | 146 CodecCapabilities capabilities = |
149 info.getCapabilitiesForType(mime); | 147 info.getCapabilitiesForType(mime); |
150 for (int colorFormat : capabilities.colorFormats) { | 148 for (int colorFormat : capabilities.colorFormats) { |
151 Logging.v(TAG, " Color: 0x" + Integer.toHexString(colorFormat)); | 149 Log.v(TAG, " Color: 0x" + Integer.toHexString(colorFormat)); |
152 } | 150 } |
153 for (int supportedColorFormat : supportedColorList) { | 151 for (int supportedColorFormat : supportedColorList) { |
154 for (int codecColorFormat : capabilities.colorFormats) { | 152 for (int codecColorFormat : capabilities.colorFormats) { |
155 if (codecColorFormat == supportedColorFormat) { | 153 if (codecColorFormat == supportedColorFormat) { |
156 // Found supported HW decoder. | 154 // Found supported HW decoder. |
157 Logging.d(TAG, "Found target decoder " + name + | 155 Log.d(TAG, "Found target decoder " + name + |
158 ". Color: 0x" + Integer.toHexString(codecColorFormat)); | 156 ". Color: 0x" + Integer.toHexString(codecColorFormat)); |
159 return new DecoderProperties(name, codecColorFormat); | 157 return new DecoderProperties(name, codecColorFormat); |
160 } | 158 } |
161 } | 159 } |
162 } | 160 } |
163 } | 161 } |
164 return null; // No HW decoder. | 162 return null; // No HW decoder. |
165 } | 163 } |
166 | 164 |
167 public static boolean isVp8HwSupported() { | 165 public static boolean isVp8HwSupported() { |
(...skipping 29 matching lines...) Expand all Loading... |
197 } else if (type == VideoCodecType.VIDEO_CODEC_H264) { | 195 } else if (type == VideoCodecType.VIDEO_CODEC_H264) { |
198 mime = H264_MIME_TYPE; | 196 mime = H264_MIME_TYPE; |
199 supportedCodecPrefixes = supportedH264HwCodecPrefixes; | 197 supportedCodecPrefixes = supportedH264HwCodecPrefixes; |
200 } else { | 198 } else { |
201 throw new RuntimeException("Non supported codec " + type); | 199 throw new RuntimeException("Non supported codec " + type); |
202 } | 200 } |
203 DecoderProperties properties = findDecoder(mime, supportedCodecPrefixes); | 201 DecoderProperties properties = findDecoder(mime, supportedCodecPrefixes); |
204 if (properties == null) { | 202 if (properties == null) { |
205 throw new RuntimeException("Cannot find HW decoder for " + type); | 203 throw new RuntimeException("Cannot find HW decoder for " + type); |
206 } | 204 } |
207 Logging.d(TAG, "Java initDecode: " + type + " : "+ width + " x " + height + | 205 Log.d(TAG, "Java initDecode: " + type + " : "+ width + " x " + height + |
208 ". Color: 0x" + Integer.toHexString(properties.colorFormat) + | 206 ". Color: 0x" + Integer.toHexString(properties.colorFormat) + |
209 ". Use Surface: " + useSurface); | 207 ". Use Surface: " + useSurface); |
210 if (sharedContext != null) { | 208 if (sharedContext != null) { |
211 Logging.d(TAG, "Decoder shared EGL Context: " + sharedContext); | 209 Log.d(TAG, "Decoder shared EGL Context: " + sharedContext); |
212 } | 210 } |
213 mediaCodecThread = Thread.currentThread(); | 211 mediaCodecThread = Thread.currentThread(); |
214 try { | 212 try { |
215 Surface decodeSurface = null; | 213 Surface decodeSurface = null; |
216 this.width = width; | 214 this.width = width; |
217 this.height = height; | 215 this.height = height; |
218 this.useSurface = useSurface; | 216 this.useSurface = useSurface; |
219 stride = width; | 217 stride = width; |
220 sliceHeight = height; | 218 sliceHeight = height; |
221 | 219 |
222 if (useSurface) { | 220 if (useSurface) { |
223 // Create shared EGL context. | 221 // Create shared EGL context. |
224 eglBase = new EglBase(sharedContext, EglBase.ConfigType.PIXEL_BUFFER); | 222 eglBase = new EglBase(sharedContext, EglBase.ConfigType.PIXEL_BUFFER); |
225 eglBase.createDummyPbufferSurface(); | 223 eglBase.createDummyPbufferSurface(); |
226 eglBase.makeCurrent(); | 224 eglBase.makeCurrent(); |
227 | 225 |
228 // Create output surface | 226 // Create output surface |
229 textureID = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES); | 227 textureID = GlUtil.generateTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES); |
230 Logging.d(TAG, "Video decoder TextureID = " + textureID); | 228 Log.d(TAG, "Video decoder TextureID = " + textureID); |
231 surfaceTexture = new SurfaceTexture(textureID); | 229 surfaceTexture = new SurfaceTexture(textureID); |
232 surface = new Surface(surfaceTexture); | 230 surface = new Surface(surfaceTexture); |
233 decodeSurface = surface; | 231 decodeSurface = surface; |
234 } | 232 } |
235 | 233 |
236 MediaFormat format = MediaFormat.createVideoFormat(mime, width, height); | 234 MediaFormat format = MediaFormat.createVideoFormat(mime, width, height); |
237 if (!useSurface) { | 235 if (!useSurface) { |
238 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat); | 236 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat); |
239 } | 237 } |
240 Logging.d(TAG, " Format: " + format); | 238 Log.d(TAG, " Format: " + format); |
241 mediaCodec = | 239 mediaCodec = |
242 MediaCodecVideoEncoder.createByCodecName(properties.codecName); | 240 MediaCodecVideoEncoder.createByCodecName(properties.codecName); |
243 if (mediaCodec == null) { | 241 if (mediaCodec == null) { |
244 return false; | 242 return false; |
245 } | 243 } |
246 mediaCodec.configure(format, decodeSurface, null, 0); | 244 mediaCodec.configure(format, decodeSurface, null, 0); |
247 mediaCodec.start(); | 245 mediaCodec.start(); |
248 colorFormat = properties.colorFormat; | 246 colorFormat = properties.colorFormat; |
249 outputBuffers = mediaCodec.getOutputBuffers(); | 247 outputBuffers = mediaCodec.getOutputBuffers(); |
250 inputBuffers = mediaCodec.getInputBuffers(); | 248 inputBuffers = mediaCodec.getInputBuffers(); |
251 Logging.d(TAG, "Input buffers: " + inputBuffers.length + | 249 Log.d(TAG, "Input buffers: " + inputBuffers.length + |
252 ". Output buffers: " + outputBuffers.length); | 250 ". Output buffers: " + outputBuffers.length); |
253 return true; | 251 return true; |
254 } catch (IllegalStateException e) { | 252 } catch (IllegalStateException e) { |
255 Logging.e(TAG, "initDecode failed", e); | 253 Log.e(TAG, "initDecode failed", e); |
256 return false; | 254 return false; |
257 } | 255 } |
258 } | 256 } |
259 | 257 |
260 private void release() { | 258 private void release() { |
261 Logging.d(TAG, "Java releaseDecoder"); | 259 Log.d(TAG, "Java releaseDecoder"); |
262 checkOnMediaCodecThread(); | 260 checkOnMediaCodecThread(); |
263 try { | 261 try { |
264 mediaCodec.stop(); | 262 mediaCodec.stop(); |
265 mediaCodec.release(); | 263 mediaCodec.release(); |
266 } catch (IllegalStateException e) { | 264 } catch (IllegalStateException e) { |
267 Logging.e(TAG, "release failed", e); | 265 Log.e(TAG, "release failed", e); |
268 } | 266 } |
269 mediaCodec = null; | 267 mediaCodec = null; |
270 mediaCodecThread = null; | 268 mediaCodecThread = null; |
271 if (useSurface) { | 269 if (useSurface) { |
272 surface.release(); | 270 surface.release(); |
273 if (textureID != 0) { | 271 if (textureID != 0) { |
274 Logging.d(TAG, "Delete video decoder TextureID " + textureID); | 272 Log.d(TAG, "Delete video decoder TextureID " + textureID); |
275 GLES20.glDeleteTextures(1, new int[] {textureID}, 0); | 273 GLES20.glDeleteTextures(1, new int[] {textureID}, 0); |
276 textureID = 0; | 274 textureID = 0; |
277 } | 275 } |
278 eglBase.release(); | 276 eglBase.release(); |
279 eglBase = null; | 277 eglBase = null; |
280 } | 278 } |
281 } | 279 } |
282 | 280 |
283 // Dequeue an input buffer and return its index, -1 if no input buffer is | 281 // Dequeue an input buffer and return its index, -1 if no input buffer is |
284 // available, or -2 if the codec is no longer operative. | 282 // available, or -2 if the codec is no longer operative. |
285 private int dequeueInputBuffer() { | 283 private int dequeueInputBuffer() { |
286 checkOnMediaCodecThread(); | 284 checkOnMediaCodecThread(); |
287 try { | 285 try { |
288 return mediaCodec.dequeueInputBuffer(DEQUEUE_INPUT_TIMEOUT); | 286 return mediaCodec.dequeueInputBuffer(DEQUEUE_INPUT_TIMEOUT); |
289 } catch (IllegalStateException e) { | 287 } catch (IllegalStateException e) { |
290 Logging.e(TAG, "dequeueIntputBuffer failed", e); | 288 Log.e(TAG, "dequeueIntputBuffer failed", e); |
291 return -2; | 289 return -2; |
292 } | 290 } |
293 } | 291 } |
294 | 292 |
295 private boolean queueInputBuffer( | 293 private boolean queueInputBuffer( |
296 int inputBufferIndex, int size, long timestampUs) { | 294 int inputBufferIndex, int size, long timestampUs) { |
297 checkOnMediaCodecThread(); | 295 checkOnMediaCodecThread(); |
298 try { | 296 try { |
299 inputBuffers[inputBufferIndex].position(0); | 297 inputBuffers[inputBufferIndex].position(0); |
300 inputBuffers[inputBufferIndex].limit(size); | 298 inputBuffers[inputBufferIndex].limit(size); |
301 mediaCodec.queueInputBuffer(inputBufferIndex, 0, size, timestampUs, 0); | 299 mediaCodec.queueInputBuffer(inputBufferIndex, 0, size, timestampUs, 0); |
302 return true; | 300 return true; |
303 } | 301 } |
304 catch (IllegalStateException e) { | 302 catch (IllegalStateException e) { |
305 Logging.e(TAG, "decode failed", e); | 303 Log.e(TAG, "decode failed", e); |
306 return false; | 304 return false; |
307 } | 305 } |
308 } | 306 } |
309 | 307 |
310 // Helper struct for dequeueOutputBuffer() below. | 308 // Helper struct for dequeueOutputBuffer() below. |
311 private static class DecoderOutputBufferInfo { | 309 private static class DecoderOutputBufferInfo { |
312 public DecoderOutputBufferInfo( | 310 public DecoderOutputBufferInfo( |
313 int index, int offset, int size, long presentationTimestampUs) { | 311 int index, int offset, int size, long presentationTimestampUs) { |
314 this.index = index; | 312 this.index = index; |
315 this.offset = offset; | 313 this.offset = offset; |
(...skipping 11 matching lines...) Expand all Loading... |
327 // buffer available or -2 if error happened. | 325 // buffer available or -2 if error happened. |
328 private DecoderOutputBufferInfo dequeueOutputBuffer(int dequeueTimeoutUs) { | 326 private DecoderOutputBufferInfo dequeueOutputBuffer(int dequeueTimeoutUs) { |
329 checkOnMediaCodecThread(); | 327 checkOnMediaCodecThread(); |
330 try { | 328 try { |
331 MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); | 329 MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); |
332 int result = mediaCodec.dequeueOutputBuffer(info, dequeueTimeoutUs); | 330 int result = mediaCodec.dequeueOutputBuffer(info, dequeueTimeoutUs); |
333 while (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED || | 331 while (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED || |
334 result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { | 332 result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { |
335 if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { | 333 if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { |
336 outputBuffers = mediaCodec.getOutputBuffers(); | 334 outputBuffers = mediaCodec.getOutputBuffers(); |
337 Logging.d(TAG, "Decoder output buffers changed: " + outputBuffers.leng
th); | 335 Log.d(TAG, "Decoder output buffers changed: " + outputBuffers.length); |
338 } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { | 336 } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { |
339 MediaFormat format = mediaCodec.getOutputFormat(); | 337 MediaFormat format = mediaCodec.getOutputFormat(); |
340 Logging.d(TAG, "Decoder format changed: " + format.toString()); | 338 Log.d(TAG, "Decoder format changed: " + format.toString()); |
341 width = format.getInteger(MediaFormat.KEY_WIDTH); | 339 width = format.getInteger(MediaFormat.KEY_WIDTH); |
342 height = format.getInteger(MediaFormat.KEY_HEIGHT); | 340 height = format.getInteger(MediaFormat.KEY_HEIGHT); |
343 if (!useSurface && format.containsKey(MediaFormat.KEY_COLOR_FORMAT)) { | 341 if (!useSurface && format.containsKey(MediaFormat.KEY_COLOR_FORMAT)) { |
344 colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT); | 342 colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT); |
345 Logging.d(TAG, "Color: 0x" + Integer.toHexString(colorFormat)); | 343 Log.d(TAG, "Color: 0x" + Integer.toHexString(colorFormat)); |
346 // Check if new color space is supported. | 344 // Check if new color space is supported. |
347 boolean validColorFormat = false; | 345 boolean validColorFormat = false; |
348 for (int supportedColorFormat : supportedColorList) { | 346 for (int supportedColorFormat : supportedColorList) { |
349 if (colorFormat == supportedColorFormat) { | 347 if (colorFormat == supportedColorFormat) { |
350 validColorFormat = true; | 348 validColorFormat = true; |
351 break; | 349 break; |
352 } | 350 } |
353 } | 351 } |
354 if (!validColorFormat) { | 352 if (!validColorFormat) { |
355 Logging.e(TAG, "Non supported color format"); | 353 Log.e(TAG, "Non supported color format"); |
356 return new DecoderOutputBufferInfo(-1, 0, 0, -1); | 354 return new DecoderOutputBufferInfo(-1, 0, 0, -1); |
357 } | 355 } |
358 } | 356 } |
359 if (format.containsKey("stride")) { | 357 if (format.containsKey("stride")) { |
360 stride = format.getInteger("stride"); | 358 stride = format.getInteger("stride"); |
361 } | 359 } |
362 if (format.containsKey("slice-height")) { | 360 if (format.containsKey("slice-height")) { |
363 sliceHeight = format.getInteger("slice-height"); | 361 sliceHeight = format.getInteger("slice-height"); |
364 } | 362 } |
365 Logging.d(TAG, "Frame stride and slice height: " | 363 Log.d(TAG, "Frame stride and slice height: " |
366 + stride + " x " + sliceHeight); | 364 + stride + " x " + sliceHeight); |
367 stride = Math.max(width, stride); | 365 stride = Math.max(width, stride); |
368 sliceHeight = Math.max(height, sliceHeight); | 366 sliceHeight = Math.max(height, sliceHeight); |
369 } | 367 } |
370 result = mediaCodec.dequeueOutputBuffer(info, dequeueTimeoutUs); | 368 result = mediaCodec.dequeueOutputBuffer(info, dequeueTimeoutUs); |
371 } | 369 } |
372 if (result >= 0) { | 370 if (result >= 0) { |
373 return new DecoderOutputBufferInfo(result, info.offset, info.size, | 371 return new DecoderOutputBufferInfo(result, info.offset, info.size, |
374 info.presentationTimeUs); | 372 info.presentationTimeUs); |
375 } | 373 } |
376 return null; | 374 return null; |
377 } catch (IllegalStateException e) { | 375 } catch (IllegalStateException e) { |
378 Logging.e(TAG, "dequeueOutputBuffer failed", e); | 376 Log.e(TAG, "dequeueOutputBuffer failed", e); |
379 return new DecoderOutputBufferInfo(-1, 0, 0, -1); | 377 return new DecoderOutputBufferInfo(-1, 0, 0, -1); |
380 } | 378 } |
381 } | 379 } |
382 | 380 |
383 // Release a dequeued output buffer back to the codec for re-use. Return | 381 // Release a dequeued output buffer back to the codec for re-use. Return |
384 // false if the codec is no longer operable. | 382 // false if the codec is no longer operable. |
385 private boolean releaseOutputBuffer(int index, boolean render) { | 383 private boolean releaseOutputBuffer(int index, boolean render) { |
386 checkOnMediaCodecThread(); | 384 checkOnMediaCodecThread(); |
387 try { | 385 try { |
388 if (!useSurface) { | 386 if (!useSurface) { |
389 render = false; | 387 render = false; |
390 } | 388 } |
391 mediaCodec.releaseOutputBuffer(index, render); | 389 mediaCodec.releaseOutputBuffer(index, render); |
392 return true; | 390 return true; |
393 } catch (IllegalStateException e) { | 391 } catch (IllegalStateException e) { |
394 Logging.e(TAG, "releaseOutputBuffer failed", e); | 392 Log.e(TAG, "releaseOutputBuffer failed", e); |
395 return false; | 393 return false; |
396 } | 394 } |
397 } | 395 } |
398 } | 396 } |
OLD | NEW |