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