Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / libjingle / source / talk / app / webrtc / java / src / org / webrtc / MediaCodecVideoEncoder.java
1 /*
2  * libjingle
3  * Copyright 2013, 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
29 package org.webrtc;
30
31 import android.media.MediaCodec;
32 import android.media.MediaCodecInfo.CodecCapabilities;
33 import android.media.MediaCodecInfo;
34 import android.media.MediaCodecList;
35 import android.media.MediaFormat;
36 import android.os.Build;
37 import android.os.Bundle;
38 import android.util.Log;
39
40 import java.nio.ByteBuffer;
41
42 // Java-side of peerconnection_jni.cc:MediaCodecVideoEncoder.
43 // This class is an implementation detail of the Java PeerConnection API.
44 // MediaCodec is thread-hostile so this class must be operated on a single
45 // thread.
46 class MediaCodecVideoEncoder {
47   // This class is constructed, operated, and destroyed by its C++ incarnation,
48   // so the class and its methods have non-public visibility.  The API this
49   // class exposes aims to mimic the webrtc::VideoEncoder API as closely as
50   // possibly to minimize the amount of translation work necessary.
51
52   private static final String TAG = "MediaCodecVideoEncoder";
53
54   private static final int DEQUEUE_TIMEOUT = 0;  // Non-blocking, no wait.
55   private Thread mediaCodecThread;
56   private MediaCodec mediaCodec;
57   private ByteBuffer[] outputBuffers;
58   private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8";
59   // List of supported HW VP8 codecs.
60   private static final String[] supportedHwCodecPrefixes =
61     {"OMX.qcom.", "OMX.Nvidia." };
62   // Bitrate mode
63   private static final int VIDEO_ControlRateConstant = 2;
64   // NV12 color format supported by QCOM codec, but not declared in MediaCodec -
65   // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
66   private static final int
67     COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
68   // Allowable color formats supported by codec - in order of preference.
69   private static final int[] supportedColorList = {
70     CodecCapabilities.COLOR_FormatYUV420Planar,
71     CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
72     CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
73     COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m
74   };
75   private int colorFormat;
76
77   private MediaCodecVideoEncoder() {}
78
79   // Helper struct for findVp8HwEncoder() below.
80   private static class EncoderProperties {
81     public EncoderProperties(String codecName, int colorFormat) {
82       this.codecName = codecName;
83       this.colorFormat = colorFormat;
84     }
85     public final String codecName; // OpenMax component name for VP8 codec.
86     public final int colorFormat;  // Color format supported by codec.
87   }
88
89   private static EncoderProperties findVp8HwEncoder() {
90     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
91       return null; // MediaCodec.setParameters is missing.
92
93     for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
94       MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
95       if (!info.isEncoder()) {
96         continue;
97       }
98       String name = null;
99       for (String mimeType : info.getSupportedTypes()) {
100         if (mimeType.equals(VP8_MIME_TYPE)) {
101           name = info.getName();
102           break;
103         }
104       }
105       if (name == null) {
106         continue;  // No VP8 support in this codec; try the next one.
107       }
108       Log.d(TAG, "Found candidate encoder " + name);
109
110       // Check if this is supported HW encoder.
111       boolean supportedCodec = false;
112       for (String hwCodecPrefix : supportedHwCodecPrefixes) {
113         if (name.startsWith(hwCodecPrefix)) {
114           supportedCodec = true;
115           break;
116         }
117       }
118       if (!supportedCodec) {
119         continue;
120       }
121
122       CodecCapabilities capabilities =
123           info.getCapabilitiesForType(VP8_MIME_TYPE);
124       for (int colorFormat : capabilities.colorFormats) {
125         Log.d(TAG, "   Color: 0x" + Integer.toHexString(colorFormat));
126       }
127
128       // Check if codec supports either yuv420 or nv12.
129       for (int supportedColorFormat : supportedColorList) {
130         for (int codecColorFormat : capabilities.colorFormats) {
131           if (codecColorFormat == supportedColorFormat) {
132             // Found supported HW VP8 encoder.
133             Log.d(TAG, "Found target encoder " + name +
134                 ". Color: 0x" + Integer.toHexString(codecColorFormat));
135             return new EncoderProperties(name, codecColorFormat);
136           }
137         }
138       }
139     }
140     return null;  // No HW VP8 encoder.
141   }
142
143   private static boolean isPlatformSupported() {
144     return findVp8HwEncoder() != null;
145   }
146
147   private void checkOnMediaCodecThread() {
148     if (mediaCodecThread.getId() != Thread.currentThread().getId()) {
149       throw new RuntimeException(
150           "MediaCodecVideoEncoder previously operated on " + mediaCodecThread +
151           " but is now called on " + Thread.currentThread());
152     }
153   }
154
155   // Return the array of input buffers, or null on failure.
156   private ByteBuffer[] initEncode(int width, int height, int kbps, int fps) {
157     Log.d(TAG, "Java initEncode: " + width + " x " + height +
158         ". @ " + kbps + " kbps. Fps: " + fps +
159         ". Color: 0x" + Integer.toHexString(colorFormat));
160     if (mediaCodecThread != null) {
161       throw new RuntimeException("Forgot to release()?");
162     }
163     EncoderProperties properties = findVp8HwEncoder();
164     if (properties == null) {
165       throw new RuntimeException("Can not find HW VP8 encoder");
166     }
167     mediaCodecThread = Thread.currentThread();
168     try {
169       MediaFormat format =
170           MediaFormat.createVideoFormat(VP8_MIME_TYPE, width, height);
171       format.setInteger(MediaFormat.KEY_BIT_RATE, 1000 * kbps);
172       format.setInteger("bitrate-mode", VIDEO_ControlRateConstant);
173       format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
174       // Default WebRTC settings
175       format.setInteger(MediaFormat.KEY_FRAME_RATE, fps);
176       format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 100);
177       Log.d(TAG, "  Format: " + format);
178       mediaCodec = MediaCodec.createByCodecName(properties.codecName);
179       if (mediaCodec == null) {
180         return null;
181       }
182       mediaCodec.configure(
183           format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
184       mediaCodec.start();
185       colorFormat = properties.colorFormat;
186       outputBuffers = mediaCodec.getOutputBuffers();
187       ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
188       Log.d(TAG, "Input buffers: " + inputBuffers.length +
189           ". Output buffers: " + outputBuffers.length);
190       return inputBuffers;
191     } catch (IllegalStateException e) {
192       Log.e(TAG, "initEncode failed", e);
193       return null;
194     }
195   }
196
197   private boolean encode(
198       boolean isKeyframe, int inputBuffer, int size,
199       long presentationTimestampUs) {
200     checkOnMediaCodecThread();
201     try {
202       if (isKeyframe) {
203         // Ideally MediaCodec would honor BUFFER_FLAG_SYNC_FRAME so we could
204         // indicate this in queueInputBuffer() below and guarantee _this_ frame
205         // be encoded as a key frame, but sadly that flag is ignored.  Instead,
206         // we request a key frame "soon".
207         Log.d(TAG, "Sync frame request");
208         Bundle b = new Bundle();
209         b.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
210         mediaCodec.setParameters(b);
211       }
212       mediaCodec.queueInputBuffer(
213           inputBuffer, 0, size, presentationTimestampUs, 0);
214       return true;
215     }
216     catch (IllegalStateException e) {
217       Log.e(TAG, "encode failed", e);
218       return false;
219     }
220   }
221
222   private void release() {
223     Log.d(TAG, "Java releaseEncoder");
224     checkOnMediaCodecThread();
225     try {
226       mediaCodec.stop();
227       mediaCodec.release();
228     } catch (IllegalStateException e) {
229       Log.e(TAG, "release failed", e);
230     }
231     mediaCodec = null;
232     mediaCodecThread = null;
233   }
234
235   private boolean setRates(int kbps, int frameRateIgnored) {
236     // frameRate argument is ignored - HW encoder is supposed to use
237     // video frame timestamps for bit allocation.
238     checkOnMediaCodecThread();
239     Log.v(TAG, "setRates: " + kbps + " kbps. Fps: " + frameRateIgnored);
240     try {
241       Bundle params = new Bundle();
242       params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, 1000 * kbps);
243       mediaCodec.setParameters(params);
244       return true;
245     } catch (IllegalStateException e) {
246       Log.e(TAG, "setRates failed", e);
247       return false;
248     }
249   }
250
251   // Dequeue an input buffer and return its index, -1 if no input buffer is
252   // available, or -2 if the codec is no longer operative.
253   private int dequeueInputBuffer() {
254     checkOnMediaCodecThread();
255     try {
256       return mediaCodec.dequeueInputBuffer(DEQUEUE_TIMEOUT);
257     } catch (IllegalStateException e) {
258       Log.e(TAG, "dequeueIntputBuffer failed", e);
259       return -2;
260     }
261   }
262
263   // Helper struct for dequeueOutputBuffer() below.
264   private static class OutputBufferInfo {
265     public OutputBufferInfo(
266         int index, ByteBuffer buffer, boolean isKeyFrame,
267         long presentationTimestampUs) {
268       this.index = index;
269       this.buffer = buffer;
270       this.isKeyFrame = isKeyFrame;
271       this.presentationTimestampUs = presentationTimestampUs;
272     }
273
274     private final int index;
275     private final ByteBuffer buffer;
276     private final boolean isKeyFrame;
277     private final long presentationTimestampUs;
278   }
279
280   // Dequeue and return an output buffer, or null if no output is ready.  Return
281   // a fake OutputBufferInfo with index -1 if the codec is no longer operable.
282   private OutputBufferInfo dequeueOutputBuffer() {
283     checkOnMediaCodecThread();
284     try {
285       MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
286       int result = mediaCodec.dequeueOutputBuffer(info, DEQUEUE_TIMEOUT);
287       if (result >= 0) {
288         // MediaCodec doesn't care about Buffer position/remaining/etc so we can
289         // mess with them to get a slice and avoid having to pass extra
290         // (BufferInfo-related) parameters back to C++.
291         ByteBuffer outputBuffer = outputBuffers[result].duplicate();
292         outputBuffer.position(info.offset);
293         outputBuffer.limit(info.offset + info.size);
294         boolean isKeyFrame =
295             (info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
296         if (isKeyFrame) {
297           Log.d(TAG, "Sync frame generated");
298         }
299         return new OutputBufferInfo(
300             result, outputBuffer.slice(), isKeyFrame, info.presentationTimeUs);
301       } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
302         outputBuffers = mediaCodec.getOutputBuffers();
303         return dequeueOutputBuffer();
304       } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
305         return dequeueOutputBuffer();
306       } else if (result == MediaCodec.INFO_TRY_AGAIN_LATER) {
307         return null;
308       }
309       throw new RuntimeException("dequeueOutputBuffer: " + result);
310     } catch (IllegalStateException e) {
311       Log.e(TAG, "dequeueOutputBuffer failed", e);
312       return new OutputBufferInfo(-1, null, false, -1);
313     }
314   }
315
316   // Release a dequeued output buffer back to the codec for re-use.  Return
317   // false if the codec is no longer operable.
318   private boolean releaseOutputBuffer(int index) {
319     checkOnMediaCodecThread();
320     try {
321       mediaCodec.releaseOutputBuffer(index, false);
322       return true;
323     } catch (IllegalStateException e) {
324       Log.e(TAG, "releaseOutputBuffer failed", e);
325       return false;
326     }
327   }
328 }