Upstream version 5.34.104.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;
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
39 import java.nio.ByteBuffer;
40
41 // Java-side of peerconnection_jni.cc:MediaCodecVideoEncoder.
42 // This class is an implementation detail of the Java PeerConnection API.
43 // MediaCodec is thread-hostile so this class must be operated on a single
44 // thread.
45 class MediaCodecVideoEncoder {
46   // This class is constructed, operated, and destroyed by its C++ incarnation,
47   // so the class and its methods have non-public visibility.  The API this
48   // class exposes aims to mimic the webrtc::VideoEncoder API as closely as
49   // possibly to minimize the amount of translation work necessary.
50
51   private static final String TAG = "MediaCodecVideoEncoder";
52
53   private static final int DEQUEUE_TIMEOUT = 0;  // Non-blocking, no wait.
54   private MediaCodec mediaCodec;
55   private ByteBuffer[] outputBuffers;
56   private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8";
57   private Thread mediaCodecThread;
58
59   private MediaCodecVideoEncoder() {}
60
61   private static boolean isPlatformSupported() {
62     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
63       return false; // MediaCodec.setParameters is missing.
64
65     if (!Build.MODEL.equals("Nexus 5")) {
66       // TODO(fischman): Currently the N5 is the only >=KK device containing a
67       // HW VP8 encoder, so don't bother with any others.  When this list grows,
68       // update the KEY_COLOR_FORMAT logic below.
69       return false;
70     }
71
72     for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
73       MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
74       if (!info.isEncoder())
75         continue;
76       String name = null;
77       for (String mimeType : info.getSupportedTypes()) {
78         if (mimeType.equals(VP8_MIME_TYPE)) {
79           name = info.getName();
80           break;
81         }
82       }
83       if (name == null)
84         continue;  // No VP8 support in this codec; try the next one.
85       if (name.startsWith("OMX.google.") || name.startsWith("OMX.SEC.")) {
86         // SW encoder is highest priority VP8 codec; unlikely we can get HW.
87         // "OMX.google." is known-software, while "OMX.SEC." is sometimes SW &
88         // sometimes HW, although not VP8 HW in any known device, so treat as SW
89         // here (b/9735008 #20).
90         return false;
91       }
92       return true;  // Yay, passed the gauntlet of pre-requisites!
93     }
94     return false;  // No VP8 encoder.
95   }
96
97   private static int bitRate(int kbps) {
98     // webrtc "kilo" means 1000, not 1024.  Apparently.
99     // (and the price for overshooting is frame-dropping as webrtc enforces its
100     // bandwidth estimation, which is unpleasant).
101     return kbps * 1000;
102   }
103
104   private void checkOnMediaCodecThread() {
105     if (mediaCodecThread.getId() != Thread.currentThread().getId()) {
106       throw new RuntimeException(
107           "MediaCodecVideoEncoder previously operated on " + mediaCodecThread +
108           " but is now called on " + Thread.currentThread());
109     }
110   }
111
112   // Return the array of input buffers, or null on failure.
113   private ByteBuffer[] initEncode(int width, int height, int kbps) {
114     if (mediaCodecThread != null) {
115       throw new RuntimeException("Forgot to release()?");
116     }
117     mediaCodecThread = Thread.currentThread();
118     try {
119       MediaFormat format =
120           MediaFormat.createVideoFormat(VP8_MIME_TYPE, width, height);
121       format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate(kbps));
122       // Arbitrary choices.
123       format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
124       format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 450);
125       // TODO(fischman): when there is more than just the N5 with a VP8 HW
126       // encoder, negotiate input colorformats with the codec.  For now
127       // hard-code qcom's supported value.  See isPlatformSupported above.
128       format.setInteger(
129           MediaFormat.KEY_COLOR_FORMAT,
130           MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
131       mediaCodec = MediaCodec.createEncoderByType(VP8_MIME_TYPE);
132       if (mediaCodec == null) {
133         return null;
134       }
135       mediaCodec.configure(
136           format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
137       mediaCodec.start();
138       outputBuffers = mediaCodec.getOutputBuffers();
139       return mediaCodec.getInputBuffers();
140     } catch (IllegalStateException e) {
141       Log.e(TAG, "initEncode failed", e);
142       return null;
143     }
144   }
145
146   private boolean encode(
147       boolean isKeyframe, int inputBuffer, int size,
148       long presentationTimestampUs) {
149     checkOnMediaCodecThread();
150     try {
151       if (isKeyframe) {
152         // Ideally MediaCodec would honor BUFFER_FLAG_SYNC_FRAME so we could
153         // indicate this in queueInputBuffer() below and guarantee _this_ frame
154         // be encoded as a key frame, but sadly that flag is ignored.  Instead,
155         // we request a key frame "soon".
156         Bundle b = new Bundle();
157         b.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
158         mediaCodec.setParameters(b);
159       }
160       mediaCodec.queueInputBuffer(
161           inputBuffer, 0, size, presentationTimestampUs, 0);
162       return true;
163     }
164     catch (IllegalStateException e) {
165       Log.e(TAG, "encode failed", e);
166       return false;
167     }
168   }
169
170   private void release() {
171     checkOnMediaCodecThread();
172     try {
173       mediaCodec.stop();
174       mediaCodec.release();
175     } catch (IllegalStateException e) {
176       Log.e(TAG, "release failed", e);
177     }
178     mediaCodec = null;
179     mediaCodecThread = null;
180   }
181
182   private boolean setRates(int kbps, int frameRateIgnored) {
183     checkOnMediaCodecThread();
184     try {
185       Bundle params = new Bundle();
186       params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bitRate(kbps));
187       mediaCodec.setParameters(params);
188       // Sure would be nice to honor the frameRate argument to this function,
189       // but MediaCodec doesn't expose that particular knob.  b/12977358
190       return true;
191     } catch (IllegalStateException e) {
192       Log.e(TAG, "setRates failed", e);
193       return false;
194     }
195   }
196
197   // Dequeue an input buffer and return its index, -1 if no input buffer is
198   // available, or -2 if the codec is no longer operative.
199   private int dequeueInputBuffer() {
200     checkOnMediaCodecThread();
201     try {
202       return mediaCodec.dequeueInputBuffer(DEQUEUE_TIMEOUT);
203     } catch (IllegalStateException e) {
204       Log.e(TAG, "dequeueIntputBuffer failed", e);
205       return -2;
206     }
207   }
208
209   // Helper struct for dequeueOutputBuffer() below.
210   private static class OutputBufferInfo {
211     public OutputBufferInfo(
212         int index, ByteBuffer buffer, boolean isKeyFrame,
213         long presentationTimestampUs) {
214       this.index = index;
215       this.buffer = buffer;
216       this.isKeyFrame = isKeyFrame;
217       this.presentationTimestampUs = presentationTimestampUs;
218     }
219
220     private final int index;
221     private final ByteBuffer buffer;
222     private final boolean isKeyFrame;
223     private final long presentationTimestampUs;
224   }
225
226   // Dequeue and return an output buffer, or null if no output is ready.  Return
227   // a fake OutputBufferInfo with index -1 if the codec is no longer operable.
228   private OutputBufferInfo dequeueOutputBuffer() {
229     checkOnMediaCodecThread();
230     try {
231       MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
232       int result = mediaCodec.dequeueOutputBuffer(info, DEQUEUE_TIMEOUT);
233       if (result >= 0) {
234         // MediaCodec doesn't care about Buffer position/remaining/etc so we can
235         // mess with them to get a slice and avoid having to pass extra
236         // (BufferInfo-related) parameters back to C++.
237         ByteBuffer outputBuffer = outputBuffers[result].duplicate();
238         outputBuffer.position(info.offset);
239         outputBuffer.limit(info.offset + info.size);
240         boolean isKeyFrame =
241             (info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
242         return new OutputBufferInfo(
243             result, outputBuffer.slice(), isKeyFrame, info.presentationTimeUs);
244       } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
245         outputBuffers = mediaCodec.getOutputBuffers();
246         return dequeueOutputBuffer();
247       } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
248         return dequeueOutputBuffer();
249       } else if (result == MediaCodec.INFO_TRY_AGAIN_LATER) {
250         return null;
251       }
252       throw new RuntimeException("dequeueOutputBuffer: " + result);
253     } catch (IllegalStateException e) {
254       Log.e(TAG, "dequeueOutputBuffer failed", e);
255       return new OutputBufferInfo(-1, null, false, -1);
256     }
257   }
258
259   // Release a dequeued output buffer back to the codec for re-use.  Return
260   // false if the codec is no longer operable.
261   private boolean releaseOutputBuffer(int index) {
262     checkOnMediaCodecThread();
263     try {
264       mediaCodec.releaseOutputBuffer(index, false);
265       return true;
266     } catch (IllegalStateException e) {
267       Log.e(TAG, "releaseOutputBuffer failed", e);
268       return false;
269     }
270   }
271 }