Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / libjingle / source / talk / app / webrtc / java / src / org / webrtc / MediaCodecVideoDecoder.java
1 /*
2  * libjingle
3  * Copyright 2014, Google Inc.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  *  1. Redistributions of source code must retain the above copyright notice,
9  *     this list of conditions and the following disclaimer.
10  *  2. Redistributions in binary form must reproduce the above copyright notice,
11  *     this list of conditions and the following disclaimer in the documentation
12  *     and/or other materials provided with the distribution.
13  *  3. The name of the author may not be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 package org.webrtc;
29
30 import android.graphics.SurfaceTexture;
31 import android.media.MediaCodec;
32 import android.media.MediaCodecInfo;
33 import android.media.MediaCodecInfo.CodecCapabilities;
34 import android.media.MediaCodecList;
35 import android.media.MediaFormat;
36 import android.opengl.EGL14;
37 import android.opengl.EGLConfig;
38 import android.opengl.EGLContext;
39 import android.opengl.EGLDisplay;
40 import android.opengl.EGLSurface;
41 import android.opengl.GLES11Ext;
42 import android.opengl.GLES20;
43 import android.os.Build;
44 import android.os.Bundle;
45 import android.util.Log;
46 import android.view.Surface;
47
48 import java.nio.ByteBuffer;
49
50 // Java-side of peerconnection_jni.cc:MediaCodecVideoDecoder.
51 // This class is an implementation detail of the Java PeerConnection API.
52 // MediaCodec is thread-hostile so this class must be operated on a single
53 // thread.
54 class MediaCodecVideoDecoder {
55   // This class is constructed, operated, and destroyed by its C++ incarnation,
56   // so the class and its methods have non-public visibility.  The API this
57   // class exposes aims to mimic the webrtc::VideoDecoder API as closely as
58   // possibly to minimize the amount of translation work necessary.
59
60   private static final String TAG = "MediaCodecVideoDecoder";
61
62   private static final int DEQUEUE_INPUT_TIMEOUT = 500000;  // 500 ms timeout.
63   private Thread mediaCodecThread;
64   private MediaCodec mediaCodec;
65   private ByteBuffer[] inputBuffers;
66   private ByteBuffer[] outputBuffers;
67   private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8";
68   // List of supported HW VP8 decoders.
69   private static final String[] supportedHwCodecPrefixes =
70     {"OMX.qcom.", "OMX.Nvidia." };
71   // List of supported SW VP8 decoders.
72   private static final String[] supportedSwCodecPrefixes =
73     {"OMX.google."};
74   // NV12 color format supported by QCOM codec, but not declared in MediaCodec -
75   // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
76   private static final int
77     COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
78   // Allowable color formats supported by codec - in order of preference.
79   private static final int[] supportedColorList = {
80     CodecCapabilities.COLOR_FormatYUV420Planar,
81     CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
82     CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
83     COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m
84   };
85   private int colorFormat;
86   private int width;
87   private int height;
88   private int stride;
89   private int sliceHeight;
90   private boolean useSurface;
91   private int textureID = -1;
92   private SurfaceTexture surfaceTexture = null;
93   private Surface surface = null;
94   private float[] stMatrix = new float[16];
95   private EGLDisplay eglDisplay = EGL14.EGL_NO_DISPLAY;
96   private EGLContext eglContext = EGL14.EGL_NO_CONTEXT;
97   private EGLSurface eglSurface = EGL14.EGL_NO_SURFACE;
98   private static final int EGL14_SDK_VERSION =
99       android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
100   private static final int CURRENT_SDK_VERSION =
101       android.os.Build.VERSION.SDK_INT;
102
103
104   private MediaCodecVideoDecoder() { }
105
106   // Helper struct for findVp8Decoder() below.
107   private static class DecoderProperties {
108     public DecoderProperties(String codecName, int colorFormat) {
109       this.codecName = codecName;
110       this.colorFormat = colorFormat;
111     }
112     public final String codecName; // OpenMax component name for VP8 codec.
113     public final int colorFormat;  // Color format supported by codec.
114   }
115
116   private static DecoderProperties findVp8Decoder(boolean useSwCodec) {
117     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
118       return null; // MediaCodec.setParameters is missing.
119     }
120     String[] supportedCodecPrefixes = supportedHwCodecPrefixes;
121     if (useSwCodec) {
122       supportedCodecPrefixes = supportedSwCodecPrefixes;
123     }
124     for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
125       MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
126       if (info.isEncoder()) {
127         continue;
128       }
129       String name = null;
130       for (String mimeType : info.getSupportedTypes()) {
131         if (mimeType.equals(VP8_MIME_TYPE)) {
132           name = info.getName();
133           break;
134         }
135       }
136       if (name == null) {
137         continue;  // No VP8 support in this codec; try the next one.
138       }
139       Log.d(TAG, "Found candidate decoder " + name);
140
141       // Check if this is supported decoder.
142       boolean supportedCodec = false;
143       for (String codecPrefix : supportedCodecPrefixes) {
144         if (name.startsWith(codecPrefix)) {
145           supportedCodec = true;
146           break;
147         }
148       }
149       if (!supportedCodec) {
150         continue;
151       }
152
153       // Check if codec supports either yuv420 or nv12.
154       CodecCapabilities capabilities =
155           info.getCapabilitiesForType(VP8_MIME_TYPE);
156       for (int colorFormat : capabilities.colorFormats) {
157         Log.d(TAG, "   Color: 0x" + Integer.toHexString(colorFormat));
158       }
159       for (int supportedColorFormat : supportedColorList) {
160         for (int codecColorFormat : capabilities.colorFormats) {
161           if (codecColorFormat == supportedColorFormat) {
162             // Found supported HW VP8 decoder.
163             Log.d(TAG, "Found target decoder " + name +
164                 ". Color: 0x" + Integer.toHexString(codecColorFormat));
165             return new DecoderProperties(name, codecColorFormat);
166           }
167         }
168       }
169     }
170     return null;  // No HW VP8 decoder.
171   }
172
173   private static boolean isEGL14Supported() {
174     Log.d(TAG, "SDK version: " + CURRENT_SDK_VERSION);
175     return (CURRENT_SDK_VERSION >= EGL14_SDK_VERSION);
176   }
177
178   private static boolean isPlatformSupported() {
179     return findVp8Decoder(false) != null;
180   }
181
182   private void checkOnMediaCodecThread() {
183     if (mediaCodecThread.getId() != Thread.currentThread().getId()) {
184       throw new RuntimeException(
185           "MediaCodecVideoDecoder previously operated on " + mediaCodecThread +
186           " but is now called on " + Thread.currentThread());
187     }
188   }
189
190   private void checkEglError(String msg) {
191     int error;
192     if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
193       Log.e(TAG, msg + ": EGL Error: 0x" + Integer.toHexString(error));
194       throw new RuntimeException(
195           msg + ": EGL error: 0x" + Integer.toHexString(error));
196     }
197   }
198
199   private void checkGlError(String msg) {
200     int error;
201     if ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
202       Log.e(TAG, msg + ": GL Error: 0x" + Integer.toHexString(error));
203       throw new RuntimeException(
204           msg + ": GL Error: 0x " + Integer.toHexString(error));
205     }
206   }
207
208   private void eglSetup(EGLContext sharedContext, int width, int height) {
209     Log.d(TAG, "EGL setup");
210     if (sharedContext == null) {
211       sharedContext = EGL14.EGL_NO_CONTEXT;
212     }
213     eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
214     if (eglDisplay == EGL14.EGL_NO_DISPLAY) {
215       throw new RuntimeException("Unable to get EGL14 display");
216     }
217     int[] version = new int[2];
218     if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) {
219       throw new RuntimeException("Unable to initialize EGL14");
220     }
221
222     // Configure EGL for pbuffer and OpenGL ES 2.0.
223     int[] attribList = {
224       EGL14.EGL_RED_SIZE, 8,
225       EGL14.EGL_GREEN_SIZE, 8,
226       EGL14.EGL_BLUE_SIZE, 8,
227       EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
228       EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
229       EGL14.EGL_NONE
230     };
231     EGLConfig[] configs = new EGLConfig[1];
232     int[] numConfigs = new int[1];
233     if (!EGL14.eglChooseConfig(eglDisplay, attribList, 0, configs, 0,
234         configs.length, numConfigs, 0)) {
235       throw new RuntimeException("Unable to find RGB888 EGL config");
236     }
237
238     // Configure context for OpenGL ES 2.0.
239     int[] attrib_list = {
240       EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
241       EGL14.EGL_NONE
242     };
243     eglContext = EGL14.eglCreateContext(eglDisplay, configs[0], sharedContext,
244         attrib_list, 0);
245     checkEglError("eglCreateContext");
246     if (eglContext == null) {
247       throw new RuntimeException("Null EGL context");
248     }
249
250     // Create a pbuffer surface.
251     int[] surfaceAttribs = {
252       EGL14.EGL_WIDTH, width,
253       EGL14.EGL_HEIGHT, height,
254       EGL14.EGL_NONE
255     };
256     eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, configs[0],
257         surfaceAttribs, 0);
258     checkEglError("eglCreatePbufferSurface");
259     if (eglSurface == null) {
260       throw new RuntimeException("EGL surface was null");
261     }
262   }
263
264   private void eglRelease() {
265     Log.d(TAG, "EGL release");
266     if (eglDisplay != EGL14.EGL_NO_DISPLAY) {
267       EGL14.eglDestroySurface(eglDisplay, eglSurface);
268       EGL14.eglDestroyContext(eglDisplay, eglContext);
269       EGL14.eglReleaseThread();
270       EGL14.eglTerminate(eglDisplay);
271     }
272     eglDisplay = EGL14.EGL_NO_DISPLAY;
273     eglContext = EGL14.EGL_NO_CONTEXT;
274     eglSurface = EGL14.EGL_NO_SURFACE;
275   }
276
277
278   private void makeCurrent() {
279     if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
280       throw new RuntimeException("eglMakeCurrent failed");
281     }
282   }
283
284   private boolean initDecode(int width, int height, boolean useSwCodec,
285       boolean useSurface, EGLContext sharedContext) {
286     if (mediaCodecThread != null) {
287       throw new RuntimeException("Forgot to release()?");
288     }
289     if (useSurface && sharedContext == null) {
290       throw new RuntimeException("No shared EGL context.");
291     }
292     DecoderProperties properties = findVp8Decoder(useSwCodec);
293     if (properties == null) {
294       throw new RuntimeException("Cannot find HW VP8 decoder");
295     }
296     Log.d(TAG, "Java initDecode: " + width + " x " + height +
297         ". Color: 0x" + Integer.toHexString(properties.colorFormat) +
298         ". Use Surface: " + useSurface + ". Use SW codec: " + useSwCodec);
299     if (sharedContext != null) {
300       Log.d(TAG, "Decoder shared EGL Context: " + sharedContext);
301     }
302     mediaCodecThread = Thread.currentThread();
303     try {
304       Surface decodeSurface = null;
305       this.width = width;
306       this.height = height;
307       this.useSurface = useSurface;
308       stride = width;
309       sliceHeight = height;
310
311       if (useSurface) {
312         // Create shared EGL context.
313         eglSetup(sharedContext, width, height);
314         makeCurrent();
315
316         // Create output surface
317         int[] textures = new int[1];
318         GLES20.glGenTextures(1, textures, 0);
319         checkGlError("glGenTextures");
320         textureID = textures[0];
321         GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureID);
322         checkGlError("glBindTexture mTextureID");
323
324         GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
325             GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
326         GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
327             GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
328         GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
329             GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
330         GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
331             GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
332         checkGlError("glTexParameter");
333         Log.d(TAG, "Video decoder TextureID = " + textureID);
334         surfaceTexture = new SurfaceTexture(textureID);
335         surface = new Surface(surfaceTexture);
336         decodeSurface = surface;
337      }
338
339       MediaFormat format =
340           MediaFormat.createVideoFormat(VP8_MIME_TYPE, width, height);
341       if (!useSurface) {
342         format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
343       }
344       Log.d(TAG, "  Format: " + format);
345       mediaCodec =
346           MediaCodecVideoEncoder.createByCodecName(properties.codecName);
347       if (mediaCodec == null) {
348         return false;
349       }
350       mediaCodec.configure(format, decodeSurface, null, 0);
351       mediaCodec.start();
352       colorFormat = properties.colorFormat;
353       outputBuffers = mediaCodec.getOutputBuffers();
354       inputBuffers = mediaCodec.getInputBuffers();
355       Log.d(TAG, "Input buffers: " + inputBuffers.length +
356           ". Output buffers: " + outputBuffers.length);
357       return true;
358     } catch (IllegalStateException e) {
359       Log.e(TAG, "initDecode failed", e);
360       return false;
361     }
362   }
363
364   private void release() {
365     Log.d(TAG, "Java releaseDecoder");
366     checkOnMediaCodecThread();
367     try {
368       mediaCodec.stop();
369       mediaCodec.release();
370     } catch (IllegalStateException e) {
371       Log.e(TAG, "release failed", e);
372     }
373     mediaCodec = null;
374     mediaCodecThread = null;
375     if (useSurface) {
376       surface.release();
377       if (textureID >= 0) {
378         int[] textures = new int[1];
379         textures[0] = textureID;
380         Log.d(TAG, "Delete video decoder TextureID " + textureID);
381         GLES20.glDeleteTextures(1, textures, 0);
382         checkGlError("glDeleteTextures");
383       }
384       eglRelease();
385     }
386   }
387
388   // Dequeue an input buffer and return its index, -1 if no input buffer is
389   // available, or -2 if the codec is no longer operative.
390   private int dequeueInputBuffer() {
391     checkOnMediaCodecThread();
392     try {
393       return mediaCodec.dequeueInputBuffer(DEQUEUE_INPUT_TIMEOUT);
394     } catch (IllegalStateException e) {
395       Log.e(TAG, "dequeueIntputBuffer failed", e);
396       return -2;
397     }
398   }
399
400   private boolean queueInputBuffer(
401       int inputBufferIndex, int size, long timestampUs) {
402     checkOnMediaCodecThread();
403     try {
404       inputBuffers[inputBufferIndex].position(0);
405       inputBuffers[inputBufferIndex].limit(size);
406       mediaCodec.queueInputBuffer(inputBufferIndex, 0, size, timestampUs, 0);
407       return true;
408     }
409     catch (IllegalStateException e) {
410       Log.e(TAG, "decode failed", e);
411       return false;
412     }
413   }
414
415   // Helper struct for dequeueOutputBuffer() below.
416   private static class DecoderOutputBufferInfo {
417     public DecoderOutputBufferInfo(
418         int index, int offset, int size, long presentationTimestampUs) {
419       this.index = index;
420       this.offset = offset;
421       this.size = size;
422       this.presentationTimestampUs = presentationTimestampUs;
423     }
424
425     private final int index;
426     private final int offset;
427     private final int size;
428     private final long presentationTimestampUs;
429   }
430
431   // Dequeue and return an output buffer index, -1 if no output
432   // buffer available or -2 if error happened.
433   private DecoderOutputBufferInfo dequeueOutputBuffer(int dequeueTimeoutUs) {
434     checkOnMediaCodecThread();
435     try {
436       MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
437       int result = mediaCodec.dequeueOutputBuffer(info, dequeueTimeoutUs);
438       while (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED ||
439           result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
440         if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
441           outputBuffers = mediaCodec.getOutputBuffers();
442           Log.d(TAG, "Output buffers changed: " + outputBuffers.length);
443         } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
444           MediaFormat format = mediaCodec.getOutputFormat();
445           Log.d(TAG, "Format changed: " + format.toString());
446           width = format.getInteger(MediaFormat.KEY_WIDTH);
447           height = format.getInteger(MediaFormat.KEY_HEIGHT);
448           if (!useSurface && format.containsKey(MediaFormat.KEY_COLOR_FORMAT)) {
449             colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
450             Log.d(TAG, "Color: 0x" + Integer.toHexString(colorFormat));
451             // Check if new color space is supported.
452             boolean validColorFormat = false;
453             for (int supportedColorFormat : supportedColorList) {
454               if (colorFormat == supportedColorFormat) {
455                 validColorFormat = true;
456                 break;
457               }
458             }
459             if (!validColorFormat) {
460               Log.e(TAG, "Non supported color format");
461               return new DecoderOutputBufferInfo(-1, 0, 0, -1);
462             }
463           }
464           if (format.containsKey("stride")) {
465             stride = format.getInteger("stride");
466           }
467           if (format.containsKey("slice-height")) {
468             sliceHeight = format.getInteger("slice-height");
469           }
470           Log.d(TAG, "Frame stride and slice height: "
471               + stride + " x " + sliceHeight);
472           stride = Math.max(width, stride);
473           sliceHeight = Math.max(height, sliceHeight);
474         }
475         result = mediaCodec.dequeueOutputBuffer(info, dequeueTimeoutUs);
476       }
477       if (result >= 0) {
478         return new DecoderOutputBufferInfo(result, info.offset, info.size,
479             info.presentationTimeUs);
480       }
481       return null;
482     } catch (IllegalStateException e) {
483       Log.e(TAG, "dequeueOutputBuffer failed", e);
484       return new DecoderOutputBufferInfo(-1, 0, 0, -1);
485     }
486   }
487
488   // Release a dequeued output buffer back to the codec for re-use.  Return
489   // false if the codec is no longer operable.
490   private boolean releaseOutputBuffer(int index, boolean render) {
491     checkOnMediaCodecThread();
492     try {
493       if (!useSurface) {
494         render = false;
495       }
496       mediaCodec.releaseOutputBuffer(index, render);
497       return true;
498     } catch (IllegalStateException e) {
499       Log.e(TAG, "releaseOutputBuffer failed", e);
500       return false;
501     }
502   }
503 }