Upstream version 9.38.198.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.media.MediaCodec;
31 import android.media.MediaCodecInfo.CodecCapabilities;
32 import android.media.MediaCodecInfo;
33 import android.media.MediaCodecList;
34 import android.media.MediaFormat;
35 import android.os.Build;
36 import android.os.Bundle;
37 import android.util.Log;
38 import java.nio.ByteBuffer;
39
40 // Java-side of peerconnection_jni.cc:MediaCodecVideoDecoder.
41 // This class is an implementation detail of the Java PeerConnection API.
42 // MediaCodec is thread-hostile so this class must be operated on a single
43 // thread.
44 class MediaCodecVideoDecoder {
45   // This class is constructed, operated, and destroyed by its C++ incarnation,
46   // so the class and its methods have non-public visibility.  The API this
47   // class exposes aims to mimic the webrtc::VideoDecoder API as closely as
48   // possibly to minimize the amount of translation work necessary.
49
50   private static final String TAG = "MediaCodecVideoDecoder";
51
52   private static final int DEQUEUE_TIMEOUT = 1000000;  // 1 sec timeout.
53   private Thread mediaCodecThread;
54   private MediaCodec mediaCodec;
55   private ByteBuffer[] inputBuffers;
56   private ByteBuffer[] outputBuffers;
57   private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8";
58   // List of supported HW VP8 decoders.
59   private static final String[] supportedHwCodecPrefixes =
60     {"OMX.Nvidia."};
61   // NV12 color format supported by QCOM codec, but not declared in MediaCodec -
62   // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
63   private static final int
64     COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
65   // Allowable color formats supported by codec - in order of preference.
66   private static final int[] supportedColorList = {
67     CodecCapabilities.COLOR_FormatYUV420Planar,
68     CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
69     CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
70     COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m
71   };
72   private int colorFormat;
73   private int width;
74   private int height;
75   private int stride;
76   private int sliceHeight;
77
78   private MediaCodecVideoDecoder() { }
79
80   // Helper struct for findVp8HwDecoder() below.
81   private static class DecoderProperties {
82     DecoderProperties(String codecName, int colorFormat) {
83       this.codecName = codecName;
84       this.colorFormat = colorFormat;
85     }
86     public final String codecName; // OpenMax component name for VP8 codec.
87     public final int colorFormat;  // Color format supported by codec.
88   }
89
90   private static DecoderProperties findVp8HwDecoder() {
91     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
92       return null; // MediaCodec.setParameters is missing.
93
94     for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
95       MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
96       if (info.isEncoder()) {
97         continue;
98       }
99       String name = null;
100       for (String mimeType : info.getSupportedTypes()) {
101         if (mimeType.equals(VP8_MIME_TYPE)) {
102           name = info.getName();
103           break;
104         }
105       }
106       if (name == null) {
107         continue;  // No VP8 support in this codec; try the next one.
108       }
109       Log.d(TAG, "Found candidate decoder " + name);
110       CodecCapabilities capabilities =
111           info.getCapabilitiesForType(VP8_MIME_TYPE);
112       for (int colorFormat : capabilities.colorFormats) {
113         Log.d(TAG, "   Color: 0x" + Integer.toHexString(colorFormat));
114       }
115
116       // Check if this is supported HW decoder
117       for (String hwCodecPrefix : supportedHwCodecPrefixes) {
118         if (!name.startsWith(hwCodecPrefix)) {
119           continue;
120         }
121         // Check if codec supports either yuv420 or nv12
122         for (int supportedColorFormat : supportedColorList) {
123           for (int codecColorFormat : capabilities.colorFormats) {
124             if (codecColorFormat == supportedColorFormat) {
125               // Found supported HW VP8 decoder
126               Log.d(TAG, "Found target decoder " + name +
127                   ". Color: 0x" + Integer.toHexString(codecColorFormat));
128               return new DecoderProperties(name, codecColorFormat);
129             }
130           }
131         }
132       }
133     }
134     return null;  // No HW VP8 decoder.
135   }
136
137   private static boolean isPlatformSupported() {
138     return findVp8HwDecoder() != null;
139   }
140
141   private void checkOnMediaCodecThread() {
142     if (mediaCodecThread.getId() != Thread.currentThread().getId()) {
143       throw new RuntimeException(
144           "MediaCodecVideoDecoder previously operated on " + mediaCodecThread +
145           " but is now called on " + Thread.currentThread());
146     }
147   }
148
149   private boolean initDecode(int width, int height) {
150     if (mediaCodecThread != null) {
151       throw new RuntimeException("Forgot to release()?");
152     }
153     DecoderProperties properties = findVp8HwDecoder();
154     if (properties == null) {
155       throw new RuntimeException("Cannot find HW VP8 decoder");
156     }
157     Log.d(TAG, "Java initDecode: " + width + " x " + height +
158         ". Color: 0x" + Integer.toHexString(properties.colorFormat));
159     mediaCodecThread = Thread.currentThread();
160     try {
161       this.width = width;
162       this.height = height;
163       stride = width;
164       sliceHeight = height;
165       MediaFormat format =
166           MediaFormat.createVideoFormat(VP8_MIME_TYPE, width, height);
167       format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
168       Log.d(TAG, "  Format: " + format);
169       mediaCodec = MediaCodec.createByCodecName(properties.codecName);
170       if (mediaCodec == null) {
171         return false;
172       }
173       mediaCodec.configure(format, null, null, 0);
174       mediaCodec.start();
175       colorFormat = properties.colorFormat;
176       outputBuffers = mediaCodec.getOutputBuffers();
177       inputBuffers = mediaCodec.getInputBuffers();
178       Log.d(TAG, "Input buffers: " + inputBuffers.length +
179           ". Output buffers: " + outputBuffers.length);
180       return true;
181     } catch (IllegalStateException e) {
182       Log.e(TAG, "initDecode failed", e);
183       return false;
184     }
185   }
186
187   private void release() {
188     Log.d(TAG, "Java releaseDecoder");
189     checkOnMediaCodecThread();
190     try {
191       mediaCodec.stop();
192       mediaCodec.release();
193     } catch (IllegalStateException e) {
194       Log.e(TAG, "release failed", e);
195     }
196     mediaCodec = null;
197     mediaCodecThread = null;
198   }
199
200   // Dequeue an input buffer and return its index, -1 if no input buffer is
201   // available, or -2 if the codec is no longer operative.
202   private int dequeueInputBuffer() {
203     checkOnMediaCodecThread();
204     try {
205       return mediaCodec.dequeueInputBuffer(DEQUEUE_TIMEOUT);
206     } catch (IllegalStateException e) {
207       Log.e(TAG, "dequeueIntputBuffer failed", e);
208       return -2;
209     }
210   }
211
212   private boolean queueInputBuffer(
213       int inputBufferIndex, int size, long timestampUs) {
214     checkOnMediaCodecThread();
215     try {
216       inputBuffers[inputBufferIndex].position(0);
217       inputBuffers[inputBufferIndex].limit(size);
218       mediaCodec.queueInputBuffer(inputBufferIndex, 0, size, timestampUs, 0);
219       return true;
220     }
221     catch (IllegalStateException e) {
222       Log.e(TAG, "decode failed", e);
223       return false;
224     }
225   }
226
227   // Dequeue and return an output buffer index, -1 if no output
228   // buffer available or -2 if error happened.
229   private int dequeueOutputBuffer() {
230     checkOnMediaCodecThread();
231     try {
232       MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
233       int result = mediaCodec.dequeueOutputBuffer(info, DEQUEUE_TIMEOUT);
234       while (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED ||
235           result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
236         if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
237           outputBuffers = mediaCodec.getOutputBuffers();
238         } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
239           MediaFormat format = mediaCodec.getOutputFormat();
240           Log.d(TAG, "Format changed: " + format.toString());
241           width = format.getInteger(MediaFormat.KEY_WIDTH);
242           height = format.getInteger(MediaFormat.KEY_HEIGHT);
243           if (format.containsKey(MediaFormat.KEY_COLOR_FORMAT)) {
244             colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
245             Log.d(TAG, "Color: 0x" + Integer.toHexString(colorFormat));
246             // Check if new color space is supported.
247             boolean validColorFormat = false;
248             for (int supportedColorFormat : supportedColorList) {
249               if (colorFormat == supportedColorFormat) {
250                 validColorFormat = true;
251                 break;
252               }
253             }
254             if (!validColorFormat) {
255               Log.e(TAG, "Non supported color format");
256               return -2;
257             }
258           }
259           if (format.containsKey("stride")) {
260             stride = format.getInteger("stride");
261           }
262           if (format.containsKey("slice-height")) {
263             sliceHeight = format.getInteger("slice-height");
264           }
265           Log.d(TAG, "Frame stride and slice height: "
266               + stride + " x " + sliceHeight);
267           stride = Math.max(width, stride);
268           sliceHeight = Math.max(height, sliceHeight);
269         }
270         result = mediaCodec.dequeueOutputBuffer(info, DEQUEUE_TIMEOUT);
271       }
272       return result;
273     } catch (IllegalStateException e) {
274       Log.e(TAG, "dequeueOutputBuffer failed", e);
275       return -2;
276     }
277   }
278
279   // Release a dequeued output buffer back to the codec for re-use.  Return
280   // false if the codec is no longer operable.
281   private boolean releaseOutputBuffer(int index) {
282     checkOnMediaCodecThread();
283     try {
284       mediaCodec.releaseOutputBuffer(index, false);
285       return true;
286     } catch (IllegalStateException e) {
287       Log.e(TAG, "releaseOutputBuffer failed", e);
288       return false;
289     }
290   }
291 }