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.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;
40 import java.nio.ByteBuffer;
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
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.
52 private static final String TAG = "MediaCodecVideoEncoder";
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." };
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
75 private int colorFormat;
77 private MediaCodecVideoEncoder() {}
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;
85 public final String codecName; // OpenMax component name for VP8 codec.
86 public final int colorFormat; // Color format supported by codec.
89 private static EncoderProperties findVp8HwEncoder() {
90 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
91 return null; // MediaCodec.setParameters is missing.
93 for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
94 MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
95 if (!info.isEncoder()) {
99 for (String mimeType : info.getSupportedTypes()) {
100 if (mimeType.equals(VP8_MIME_TYPE)) {
101 name = info.getName();
106 continue; // No VP8 support in this codec; try the next one.
108 Log.d(TAG, "Found candidate encoder " + name);
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;
118 if (!supportedCodec) {
122 CodecCapabilities capabilities =
123 info.getCapabilitiesForType(VP8_MIME_TYPE);
124 for (int colorFormat : capabilities.colorFormats) {
125 Log.d(TAG, " Color: 0x" + Integer.toHexString(colorFormat));
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);
140 return null; // No HW VP8 encoder.
143 private static boolean isPlatformSupported() {
144 return findVp8HwEncoder() != null;
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());
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()?");
163 EncoderProperties properties = findVp8HwEncoder();
164 if (properties == null) {
165 throw new RuntimeException("Can not find HW VP8 encoder");
167 mediaCodecThread = Thread.currentThread();
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) {
182 mediaCodec.configure(
183 format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
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);
191 } catch (IllegalStateException e) {
192 Log.e(TAG, "initEncode failed", e);
197 private boolean encode(
198 boolean isKeyframe, int inputBuffer, int size,
199 long presentationTimestampUs) {
200 checkOnMediaCodecThread();
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);
212 mediaCodec.queueInputBuffer(
213 inputBuffer, 0, size, presentationTimestampUs, 0);
216 catch (IllegalStateException e) {
217 Log.e(TAG, "encode failed", e);
222 private void release() {
223 Log.d(TAG, "Java releaseEncoder");
224 checkOnMediaCodecThread();
227 mediaCodec.release();
228 } catch (IllegalStateException e) {
229 Log.e(TAG, "release failed", e);
232 mediaCodecThread = null;
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);
241 Bundle params = new Bundle();
242 params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, 1000 * kbps);
243 mediaCodec.setParameters(params);
245 } catch (IllegalStateException e) {
246 Log.e(TAG, "setRates failed", e);
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();
256 return mediaCodec.dequeueInputBuffer(DEQUEUE_TIMEOUT);
257 } catch (IllegalStateException e) {
258 Log.e(TAG, "dequeueIntputBuffer failed", e);
263 // Helper struct for dequeueOutputBuffer() below.
264 private static class OutputBufferInfo {
265 public OutputBufferInfo(
266 int index, ByteBuffer buffer, boolean isKeyFrame,
267 long presentationTimestampUs) {
269 this.buffer = buffer;
270 this.isKeyFrame = isKeyFrame;
271 this.presentationTimestampUs = presentationTimestampUs;
274 private final int index;
275 private final ByteBuffer buffer;
276 private final boolean isKeyFrame;
277 private final long presentationTimestampUs;
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();
285 MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
286 int result = mediaCodec.dequeueOutputBuffer(info, DEQUEUE_TIMEOUT);
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);
295 (info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
297 Log.d(TAG, "Sync frame generated");
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) {
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);
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();
321 mediaCodec.releaseOutputBuffer(index, false);
323 } catch (IllegalStateException e) {
324 Log.e(TAG, "releaseOutputBuffer failed", e);