3 * Copyright 2013, 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.
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;
39 import java.nio.ByteBuffer;
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
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.
51 private static final String TAG = "MediaCodecVideoEncoder";
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;
59 private MediaCodecVideoEncoder() {}
61 private static boolean isPlatformSupported() {
62 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
63 return false; // MediaCodec.setParameters is missing.
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.
72 for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
73 MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
74 if (!info.isEncoder())
77 for (String mimeType : info.getSupportedTypes()) {
78 if (mimeType.equals(VP8_MIME_TYPE)) {
79 name = info.getName();
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).
92 return true; // Yay, passed the gauntlet of pre-requisites!
94 return false; // No VP8 encoder.
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).
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());
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()?");
117 mediaCodecThread = Thread.currentThread();
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.
129 MediaFormat.KEY_COLOR_FORMAT,
130 MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
131 mediaCodec = MediaCodec.createEncoderByType(VP8_MIME_TYPE);
132 if (mediaCodec == null) {
135 mediaCodec.configure(
136 format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
138 outputBuffers = mediaCodec.getOutputBuffers();
139 return mediaCodec.getInputBuffers();
140 } catch (IllegalStateException e) {
141 Log.e(TAG, "initEncode failed", e);
146 private boolean encode(
147 boolean isKeyframe, int inputBuffer, int size,
148 long presentationTimestampUs) {
149 checkOnMediaCodecThread();
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);
160 mediaCodec.queueInputBuffer(
161 inputBuffer, 0, size, presentationTimestampUs, 0);
164 catch (IllegalStateException e) {
165 Log.e(TAG, "encode failed", e);
170 private void release() {
171 checkOnMediaCodecThread();
174 mediaCodec.release();
175 } catch (IllegalStateException e) {
176 Log.e(TAG, "release failed", e);
179 mediaCodecThread = null;
182 private boolean setRates(int kbps, int frameRateIgnored) {
183 checkOnMediaCodecThread();
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
191 } catch (IllegalStateException e) {
192 Log.e(TAG, "setRates failed", e);
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();
202 return mediaCodec.dequeueInputBuffer(DEQUEUE_TIMEOUT);
203 } catch (IllegalStateException e) {
204 Log.e(TAG, "dequeueIntputBuffer failed", e);
209 // Helper struct for dequeueOutputBuffer() below.
210 private static class OutputBufferInfo {
211 public OutputBufferInfo(
212 int index, ByteBuffer buffer, boolean isKeyFrame,
213 long presentationTimestampUs) {
215 this.buffer = buffer;
216 this.isKeyFrame = isKeyFrame;
217 this.presentationTimestampUs = presentationTimestampUs;
220 private final int index;
221 private final ByteBuffer buffer;
222 private final boolean isKeyFrame;
223 private final long presentationTimestampUs;
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();
231 MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
232 int result = mediaCodec.dequeueOutputBuffer(info, DEQUEUE_TIMEOUT);
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);
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) {
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);
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();
264 mediaCodec.releaseOutputBuffer(index, false);
266 } catch (IllegalStateException e) {
267 Log.e(TAG, "releaseOutputBuffer failed", e);