3 * Copyright 2014, Google Inc.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
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.
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.
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;
48 import java.nio.ByteBuffer;
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
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.
60 private static final String TAG = "MediaCodecVideoDecoder";
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 =
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
85 private int colorFormat;
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;
104 private MediaCodecVideoDecoder() { }
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;
112 public final String codecName; // OpenMax component name for VP8 codec.
113 public final int colorFormat; // Color format supported by codec.
116 private static DecoderProperties findVp8Decoder(boolean useSwCodec) {
117 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
118 return null; // MediaCodec.setParameters is missing.
120 String[] supportedCodecPrefixes = supportedHwCodecPrefixes;
122 supportedCodecPrefixes = supportedSwCodecPrefixes;
124 for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
125 MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
126 if (info.isEncoder()) {
130 for (String mimeType : info.getSupportedTypes()) {
131 if (mimeType.equals(VP8_MIME_TYPE)) {
132 name = info.getName();
137 continue; // No VP8 support in this codec; try the next one.
139 Log.d(TAG, "Found candidate decoder " + name);
141 // Check if this is supported decoder.
142 boolean supportedCodec = false;
143 for (String codecPrefix : supportedCodecPrefixes) {
144 if (name.startsWith(codecPrefix)) {
145 supportedCodec = true;
149 if (!supportedCodec) {
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));
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);
170 return null; // No HW VP8 decoder.
173 private static boolean isEGL14Supported() {
174 Log.d(TAG, "SDK version: " + CURRENT_SDK_VERSION);
175 return (CURRENT_SDK_VERSION >= EGL14_SDK_VERSION);
178 private static boolean isPlatformSupported() {
179 return findVp8Decoder(false) != null;
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());
190 private void checkEglError(String msg) {
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));
199 private void checkGlError(String msg) {
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));
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;
213 eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
214 if (eglDisplay == EGL14.EGL_NO_DISPLAY) {
215 throw new RuntimeException("Unable to get EGL14 display");
217 int[] version = new int[2];
218 if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) {
219 throw new RuntimeException("Unable to initialize EGL14");
222 // Configure EGL for pbuffer and OpenGL ES 2.0.
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,
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");
238 // Configure context for OpenGL ES 2.0.
239 int[] attrib_list = {
240 EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
243 eglContext = EGL14.eglCreateContext(eglDisplay, configs[0], sharedContext,
245 checkEglError("eglCreateContext");
246 if (eglContext == null) {
247 throw new RuntimeException("Null EGL context");
250 // Create a pbuffer surface.
251 int[] surfaceAttribs = {
252 EGL14.EGL_WIDTH, width,
253 EGL14.EGL_HEIGHT, height,
256 eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, configs[0],
258 checkEglError("eglCreatePbufferSurface");
259 if (eglSurface == null) {
260 throw new RuntimeException("EGL surface was null");
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);
272 eglDisplay = EGL14.EGL_NO_DISPLAY;
273 eglContext = EGL14.EGL_NO_CONTEXT;
274 eglSurface = EGL14.EGL_NO_SURFACE;
278 private void makeCurrent() {
279 if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
280 throw new RuntimeException("eglMakeCurrent failed");
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()?");
289 if (useSurface && sharedContext == null) {
290 throw new RuntimeException("No shared EGL context.");
292 DecoderProperties properties = findVp8Decoder(useSwCodec);
293 if (properties == null) {
294 throw new RuntimeException("Cannot find HW VP8 decoder");
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);
302 mediaCodecThread = Thread.currentThread();
304 Surface decodeSurface = null;
306 this.height = height;
307 this.useSurface = useSurface;
309 sliceHeight = height;
312 // Create shared EGL context.
313 eglSetup(sharedContext, width, height);
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");
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;
340 MediaFormat.createVideoFormat(VP8_MIME_TYPE, width, height);
342 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
344 Log.d(TAG, " Format: " + format);
346 MediaCodecVideoEncoder.createByCodecName(properties.codecName);
347 if (mediaCodec == null) {
350 mediaCodec.configure(format, decodeSurface, null, 0);
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);
358 } catch (IllegalStateException e) {
359 Log.e(TAG, "initDecode failed", e);
364 private void release() {
365 Log.d(TAG, "Java releaseDecoder");
366 checkOnMediaCodecThread();
369 mediaCodec.release();
370 } catch (IllegalStateException e) {
371 Log.e(TAG, "release failed", e);
374 mediaCodecThread = null;
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");
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();
393 return mediaCodec.dequeueInputBuffer(DEQUEUE_INPUT_TIMEOUT);
394 } catch (IllegalStateException e) {
395 Log.e(TAG, "dequeueIntputBuffer failed", e);
400 private boolean queueInputBuffer(
401 int inputBufferIndex, int size, long timestampUs) {
402 checkOnMediaCodecThread();
404 inputBuffers[inputBufferIndex].position(0);
405 inputBuffers[inputBufferIndex].limit(size);
406 mediaCodec.queueInputBuffer(inputBufferIndex, 0, size, timestampUs, 0);
409 catch (IllegalStateException e) {
410 Log.e(TAG, "decode failed", e);
415 // Helper struct for dequeueOutputBuffer() below.
416 private static class DecoderOutputBufferInfo {
417 public DecoderOutputBufferInfo(
418 int index, int offset, int size, long presentationTimestampUs) {
420 this.offset = offset;
422 this.presentationTimestampUs = presentationTimestampUs;
425 private final int index;
426 private final int offset;
427 private final int size;
428 private final long presentationTimestampUs;
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();
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;
459 if (!validColorFormat) {
460 Log.e(TAG, "Non supported color format");
461 return new DecoderOutputBufferInfo(-1, 0, 0, -1);
464 if (format.containsKey("stride")) {
465 stride = format.getInteger("stride");
467 if (format.containsKey("slice-height")) {
468 sliceHeight = format.getInteger("slice-height");
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);
475 result = mediaCodec.dequeueOutputBuffer(info, dequeueTimeoutUs);
478 return new DecoderOutputBufferInfo(result, info.offset, info.size,
479 info.presentationTimeUs);
482 } catch (IllegalStateException e) {
483 Log.e(TAG, "dequeueOutputBuffer failed", e);
484 return new DecoderOutputBufferInfo(-1, 0, 0, -1);
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();
496 mediaCodec.releaseOutputBuffer(index, render);
498 } catch (IllegalStateException e) {
499 Log.e(TAG, "releaseOutputBuffer failed", e);