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