blob: b009893ba7fd3f7a147a40e65adb798262c2dfa7 [file] [log] [blame]
fischman@webrtc.org540acde2014-02-13 03:56:14 +00001/*
kjellanderb24317b2016-02-10 07:54:43 -08002 * Copyright 2013 The WebRTC project authors. All Rights Reserved.
fischman@webrtc.org540acde2014-02-13 03:56:14 +00003 *
kjellanderb24317b2016-02-10 07:54:43 -08004 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
fischman@webrtc.org540acde2014-02-13 03:56:14 +00009 */
10
fischman@webrtc.org540acde2014-02-13 03:56:14 +000011package org.webrtc;
12
Patrik Höglund68876f92015-11-12 17:36:48 +010013import android.annotation.TargetApi;
sakalb5f5bdc2017-08-10 04:15:42 -070014import android.graphics.Matrix;
fischman@webrtc.org540acde2014-02-13 03:56:14 +000015import android.media.MediaCodec;
16import android.media.MediaCodecInfo;
Sami Kalliomakid3235f02016-08-02 15:44:04 +020017import android.media.MediaCodecInfo.CodecCapabilities;
fischman@webrtc.org540acde2014-02-13 03:56:14 +000018import android.media.MediaCodecList;
19import android.media.MediaFormat;
perkj30e91822015-11-20 01:31:25 -080020import android.opengl.GLES20;
fischman@webrtc.org540acde2014-02-13 03:56:14 +000021import android.os.Build;
22import android.os.Bundle;
perkj30e91822015-11-20 01:31:25 -080023import android.view.Surface;
fischman@webrtc.org540acde2014-02-13 03:56:14 +000024import java.nio.ByteBuffer;
magjed295760d2017-01-12 01:11:57 -080025import java.util.ArrayList;
Alex Glaznev0c850202015-08-04 10:26:59 -070026import java.util.Arrays;
Alex Glazneveee86a62016-01-29 14:17:07 -080027import java.util.HashSet;
Alex Glaznev0c850202015-08-04 10:26:59 -070028import java.util.List;
Alex Glazneveee86a62016-01-29 14:17:07 -080029import java.util.Set;
Alex Glaznev5c3da4b2015-10-30 15:31:07 -070030import java.util.concurrent.CountDownLatch;
perkj48477c12015-12-18 00:34:37 -080031import java.util.concurrent.TimeUnit;
fischman@webrtc.org540acde2014-02-13 03:56:14 +000032
33// Java-side of peerconnection_jni.cc:MediaCodecVideoEncoder.
34// This class is an implementation detail of the Java PeerConnection API.
Patrik Höglund68876f92015-11-12 17:36:48 +010035@TargetApi(19)
36@SuppressWarnings("deprecation")
perkj@webrtc.org47098872014-10-24 11:38:19 +000037public class MediaCodecVideoEncoder {
fischman@webrtc.org540acde2014-02-13 03:56:14 +000038 // This class is constructed, operated, and destroyed by its C++ incarnation,
39 // so the class and its methods have non-public visibility. The API this
40 // class exposes aims to mimic the webrtc::VideoEncoder API as closely as
41 // possibly to minimize the amount of translation work necessary.
42
43 private static final String TAG = "MediaCodecVideoEncoder";
44
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000045 // Tracks webrtc::VideoCodecType.
sakalb6760f92016-09-29 04:12:44 -070046 public enum VideoCodecType { VIDEO_CODEC_VP8, VIDEO_CODEC_VP9, VIDEO_CODEC_H264 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000047
Alex Glaznev5c3da4b2015-10-30 15:31:07 -070048 private static final int MEDIA_CODEC_RELEASE_TIMEOUT_MS = 5000; // Timeout for codec releasing.
sakalb6760f92016-09-29 04:12:44 -070049 private static final int DEQUEUE_TIMEOUT = 0; // Non-blocking, no wait.
Alex Glaznev269fe752016-05-25 16:17:33 -070050 private static final int BITRATE_ADJUSTMENT_FPS = 30;
Alex Glaznevc55c39d2016-09-02 12:16:27 -070051 private static final int MAXIMUM_INITIAL_FPS = 30;
Alex Glaznevcfaca032016-09-06 14:08:19 -070052 private static final double BITRATE_CORRECTION_SEC = 3.0;
Alex Glaznev7fa4a722017-01-17 15:32:02 -080053 // Maximum bitrate correction scale - no more than 4 times.
54 private static final double BITRATE_CORRECTION_MAX_SCALE = 4;
Alex Glaznevcfaca032016-09-06 14:08:19 -070055 // Amount of correction steps to reach correction maximum scale.
Alex Glaznev7fa4a722017-01-17 15:32:02 -080056 private static final int BITRATE_CORRECTION_STEPS = 20;
Alex Glaznevc7483a72017-01-05 15:22:24 -080057 // Forced key frame interval - used to reduce color distortions on Qualcomm platform.
alexlau84ee5c62017-06-02 17:36:32 -070058 private static final long QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_L_MS = 15000;
59 private static final long QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_M_MS = 20000;
Alex Glaznevc7483a72017-01-05 15:22:24 -080060 private static final long QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_N_MS = 15000;
Alex Glaznevcfaca032016-09-06 14:08:19 -070061
perkj9576e542015-11-12 06:43:16 -080062 // Active running encoder instance. Set in initEncode() (called from native code)
Alex Glaznevc6aec4b2015-10-19 16:39:19 -070063 // and reset to null in release() call.
64 private static MediaCodecVideoEncoder runningInstance = null;
Alex Glaznev5c3da4b2015-10-30 15:31:07 -070065 private static MediaCodecVideoEncoderErrorCallback errorCallback = null;
66 private static int codecErrors = 0;
Alex Glazneveee86a62016-01-29 14:17:07 -080067 // List of disabled codec types - can be set from application.
68 private static Set<String> hwEncoderDisabledTypes = new HashSet<String>();
Alex Glaznev5c3da4b2015-10-30 15:31:07 -070069
Alejandro Luebs69ddaef2015-10-09 15:46:09 -070070 private Thread mediaCodecThread;
fischman@webrtc.org540acde2014-02-13 03:56:14 +000071 private MediaCodec mediaCodec;
72 private ByteBuffer[] outputBuffers;
perkj48477c12015-12-18 00:34:37 -080073 private EglBase14 eglBase;
glaznev3fc23502017-06-15 16:24:37 -070074 private int profile;
Magnus Jedvert51254332015-12-15 16:22:29 +010075 private int width;
76 private int height;
perkj30e91822015-11-20 01:31:25 -080077 private Surface inputSurface;
78 private GlRectDrawer drawer;
Alex Glaznev269fe752016-05-25 16:17:33 -070079
fischman@webrtc.org540acde2014-02-13 03:56:14 +000080 private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8";
Alex Glaznevad948c42015-11-18 13:06:42 -080081 private static final String VP9_MIME_TYPE = "video/x-vnd.on2.vp9";
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000082 private static final String H264_MIME_TYPE = "video/avc";
Alex Glaznev269fe752016-05-25 16:17:33 -070083
glaznev3fc23502017-06-15 16:24:37 -070084 private static final int VIDEO_AVCProfileHigh = 8;
85 private static final int VIDEO_AVCLevel3 = 0x100;
86
Alex Glaznevcfaca032016-09-06 14:08:19 -070087 // Type of bitrate adjustment for video encoder.
88 public enum BitrateAdjustmentType {
89 // No adjustment - video encoder has no known bitrate problem.
90 NO_ADJUSTMENT,
91 // Framerate based bitrate adjustment is required - HW encoder does not use frame
92 // timestamps to calculate frame bitrate budget and instead is relying on initial
93 // fps configuration assuming that all frames are coming at fixed initial frame rate.
94 FRAMERATE_ADJUSTMENT,
95 // Dynamic bitrate adjustment is required - HW encoder used frame timestamps, but actual
96 // bitrate deviates too much from the target value.
97 DYNAMIC_ADJUSTMENT
98 }
99
glaznev3fc23502017-06-15 16:24:37 -0700100 // Should be in sync with webrtc::H264::Profile.
101 public static enum H264Profile {
102 CONSTRAINED_BASELINE(0),
103 BASELINE(1),
104 MAIN(2),
105 CONSTRAINED_HIGH(3),
106 HIGH(4);
107
108 private final int value;
109
110 H264Profile(int value) {
111 this.value = value;
112 }
113
114 public int getValue() {
115 return value;
116 }
117 }
118
Alex Glaznev269fe752016-05-25 16:17:33 -0700119 // Class describing supported media codec properties.
120 private static class MediaCodecProperties {
121 public final String codecPrefix;
122 // Minimum Android SDK required for this codec to be used.
123 public final int minSdk;
124 // Flag if encoder implementation does not use frame timestamps to calculate frame bitrate
125 // budget and instead is relying on initial fps configuration assuming that all frames are
126 // coming at fixed initial frame rate. Bitrate adjustment is required for this case.
Alex Glaznevcfaca032016-09-06 14:08:19 -0700127 public final BitrateAdjustmentType bitrateAdjustmentType;
Alex Glaznev269fe752016-05-25 16:17:33 -0700128
129 MediaCodecProperties(
Alex Glaznevcfaca032016-09-06 14:08:19 -0700130 String codecPrefix, int minSdk, BitrateAdjustmentType bitrateAdjustmentType) {
Alex Glaznev269fe752016-05-25 16:17:33 -0700131 this.codecPrefix = codecPrefix;
132 this.minSdk = minSdk;
Alex Glaznevcfaca032016-09-06 14:08:19 -0700133 this.bitrateAdjustmentType = bitrateAdjustmentType;
Alex Glaznev269fe752016-05-25 16:17:33 -0700134 }
135 }
136
Alex Glaznevdcd730f2016-04-21 17:01:46 -0700137 // List of supported HW VP8 encoders.
Alex Glaznev269fe752016-05-25 16:17:33 -0700138 private static final MediaCodecProperties qcomVp8HwProperties = new MediaCodecProperties(
Alex Glaznevcfaca032016-09-06 14:08:19 -0700139 "OMX.qcom.", Build.VERSION_CODES.KITKAT, BitrateAdjustmentType.NO_ADJUSTMENT);
Alex Glaznev269fe752016-05-25 16:17:33 -0700140 private static final MediaCodecProperties exynosVp8HwProperties = new MediaCodecProperties(
Alex Glaznevcfaca032016-09-06 14:08:19 -0700141 "OMX.Exynos.", Build.VERSION_CODES.M, BitrateAdjustmentType.DYNAMIC_ADJUSTMENT);
magjed295760d2017-01-12 01:11:57 -0800142 private static final MediaCodecProperties intelVp8HwProperties = new MediaCodecProperties(
143 "OMX.Intel.", Build.VERSION_CODES.LOLLIPOP, BitrateAdjustmentType.NO_ADJUSTMENT);
144 private static MediaCodecProperties[] vp8HwList() {
145 final ArrayList<MediaCodecProperties> supported_codecs = new ArrayList<MediaCodecProperties>();
146 supported_codecs.add(qcomVp8HwProperties);
147 supported_codecs.add(exynosVp8HwProperties);
148 if (PeerConnectionFactory.fieldTrialsFindFullName("WebRTC-IntelVP8").equals("Enabled")) {
149 supported_codecs.add(intelVp8HwProperties);
150 }
151 return supported_codecs.toArray(new MediaCodecProperties[supported_codecs.size()]);
152 }
Alex Glaznev269fe752016-05-25 16:17:33 -0700153
Alex Glaznevdcd730f2016-04-21 17:01:46 -0700154 // List of supported HW VP9 encoders.
Alex Glaznev269fe752016-05-25 16:17:33 -0700155 private static final MediaCodecProperties qcomVp9HwProperties = new MediaCodecProperties(
glaznev6fac4292017-06-02 20:18:54 -0700156 "OMX.qcom.", Build.VERSION_CODES.N, BitrateAdjustmentType.NO_ADJUSTMENT);
Alex Glaznev269fe752016-05-25 16:17:33 -0700157 private static final MediaCodecProperties exynosVp9HwProperties = new MediaCodecProperties(
glaznev6fac4292017-06-02 20:18:54 -0700158 "OMX.Exynos.", Build.VERSION_CODES.N, BitrateAdjustmentType.FRAMERATE_ADJUSTMENT);
sakalb6760f92016-09-29 04:12:44 -0700159 private static final MediaCodecProperties[] vp9HwList =
160 new MediaCodecProperties[] {qcomVp9HwProperties, exynosVp9HwProperties};
Alex Glaznev269fe752016-05-25 16:17:33 -0700161
Alex Glaznevdcd730f2016-04-21 17:01:46 -0700162 // List of supported HW H.264 encoders.
Alex Glaznev269fe752016-05-25 16:17:33 -0700163 private static final MediaCodecProperties qcomH264HwProperties = new MediaCodecProperties(
Alex Glaznevcfaca032016-09-06 14:08:19 -0700164 "OMX.qcom.", Build.VERSION_CODES.KITKAT, BitrateAdjustmentType.NO_ADJUSTMENT);
Alex Glaznev269fe752016-05-25 16:17:33 -0700165 private static final MediaCodecProperties exynosH264HwProperties = new MediaCodecProperties(
Alex Glaznevcfaca032016-09-06 14:08:19 -0700166 "OMX.Exynos.", Build.VERSION_CODES.LOLLIPOP, BitrateAdjustmentType.FRAMERATE_ADJUSTMENT);
sakalb6760f92016-09-29 04:12:44 -0700167 private static final MediaCodecProperties[] h264HwList =
168 new MediaCodecProperties[] {qcomH264HwProperties, exynosH264HwProperties};
Alex Glaznev269fe752016-05-25 16:17:33 -0700169
glaznev3fc23502017-06-15 16:24:37 -0700170 // List of supported HW H.264 high profile encoders.
171 private static final MediaCodecProperties exynosH264HighProfileHwProperties =
172 new MediaCodecProperties(
173 "OMX.Exynos.", Build.VERSION_CODES.M, BitrateAdjustmentType.FRAMERATE_ADJUSTMENT);
174 private static final MediaCodecProperties[] h264HighProfileHwList =
175 new MediaCodecProperties[] {exynosH264HighProfileHwProperties};
176
Alex Glaznev0c850202015-08-04 10:26:59 -0700177 // List of devices with poor H.264 encoder quality.
sakalb6760f92016-09-29 04:12:44 -0700178 // HW H.264 encoder on below devices has poor bitrate control - actual
179 // bitrates deviates a lot from the target value.
180 private static final String[] H264_HW_EXCEPTION_MODELS =
181 new String[] {"SAMSUNG-SGH-I337", "Nexus 7", "Nexus 4"};
Alex Glaznev0c850202015-08-04 10:26:59 -0700182
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000183 // Bitrate modes - should be in sync with OMX_VIDEO_CONTROLRATETYPE defined
184 // in OMX_Video.h
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000185 private static final int VIDEO_ControlRateConstant = 2;
186 // NV12 color format supported by QCOM codec, but not declared in MediaCodec -
187 // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
sakalb6760f92016-09-29 04:12:44 -0700188 private static final int COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000189 // Allowable color formats supported by codec - in order of preference.
sakalb6760f92016-09-29 04:12:44 -0700190 private static final int[] supportedColorList = {CodecCapabilities.COLOR_FormatYUV420Planar,
191 CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
192 CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
193 COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m};
194 private static final int[] supportedSurfaceColorList = {CodecCapabilities.COLOR_FormatSurface};
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000195 private VideoCodecType type;
sakalb6760f92016-09-29 04:12:44 -0700196 private int colorFormat; // Used by native code.
Alex Glaznevcfaca032016-09-06 14:08:19 -0700197
198 // Variables used for dynamic bitrate adjustment.
199 private BitrateAdjustmentType bitrateAdjustmentType = BitrateAdjustmentType.NO_ADJUSTMENT;
200 private double bitrateAccumulator;
201 private double bitrateAccumulatorMax;
202 private double bitrateObservationTimeMs;
203 private int bitrateAdjustmentScaleExp;
204 private int targetBitrateBps;
205 private int targetFps;
perkj9576e542015-11-12 06:43:16 -0800206
Alex Glaznevc7483a72017-01-05 15:22:24 -0800207 // Interval in ms to force key frame generation. Used to reduce the time of color distortions
208 // happened sometime when using Qualcomm video encoder.
209 private long forcedKeyFrameMs;
210 private long lastKeyFrameMs;
211
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000212 // SPS and PPS NALs (Config frame) for H.264.
213 private ByteBuffer configData = null;
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000214
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700215 // MediaCodec error handler - invoked when critical error happens which may prevent
216 // further use of media codec API. Now it means that one of media codec instances
217 // is hanging and can no longer be used in the next call.
218 public static interface MediaCodecVideoEncoderErrorCallback {
219 void onMediaCodecVideoEncoderCriticalError(int codecErrors);
220 }
221
222 public static void setErrorCallback(MediaCodecVideoEncoderErrorCallback errorCallback) {
223 Logging.d(TAG, "Set error callback");
224 MediaCodecVideoEncoder.errorCallback = errorCallback;
225 }
226
Alex Glazneveee86a62016-01-29 14:17:07 -0800227 // Functions to disable HW encoding - can be called from applications for platforms
228 // which have known HW decoding problems.
229 public static void disableVp8HwCodec() {
230 Logging.w(TAG, "VP8 encoding is disabled by application.");
231 hwEncoderDisabledTypes.add(VP8_MIME_TYPE);
232 }
233
234 public static void disableVp9HwCodec() {
235 Logging.w(TAG, "VP9 encoding is disabled by application.");
236 hwEncoderDisabledTypes.add(VP9_MIME_TYPE);
237 }
238
239 public static void disableH264HwCodec() {
240 Logging.w(TAG, "H.264 encoding is disabled by application.");
241 hwEncoderDisabledTypes.add(H264_MIME_TYPE);
242 }
243
244 // Functions to query if HW encoding is supported.
245 public static boolean isVp8HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700246 return !hwEncoderDisabledTypes.contains(VP8_MIME_TYPE)
magjed295760d2017-01-12 01:11:57 -0800247 && (findHwEncoder(VP8_MIME_TYPE, vp8HwList(), supportedColorList) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800248 }
249
Alex Glaznevc7483a72017-01-05 15:22:24 -0800250 public static EncoderProperties vp8HwEncoderProperties() {
251 if (hwEncoderDisabledTypes.contains(VP8_MIME_TYPE)) {
252 return null;
253 } else {
magjed295760d2017-01-12 01:11:57 -0800254 return findHwEncoder(VP8_MIME_TYPE, vp8HwList(), supportedColorList);
Alex Glaznevc7483a72017-01-05 15:22:24 -0800255 }
256 }
257
Alex Glazneveee86a62016-01-29 14:17:07 -0800258 public static boolean isVp9HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700259 return !hwEncoderDisabledTypes.contains(VP9_MIME_TYPE)
260 && (findHwEncoder(VP9_MIME_TYPE, vp9HwList, supportedColorList) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800261 }
262
263 public static boolean isH264HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700264 return !hwEncoderDisabledTypes.contains(H264_MIME_TYPE)
265 && (findHwEncoder(H264_MIME_TYPE, h264HwList, supportedColorList) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800266 }
267
glaznev3fc23502017-06-15 16:24:37 -0700268 public static boolean isH264HighProfileHwSupported() {
269 return !hwEncoderDisabledTypes.contains(H264_MIME_TYPE)
270 && (findHwEncoder(H264_MIME_TYPE, h264HighProfileHwList, supportedColorList) != null);
271 }
272
Alex Glazneveee86a62016-01-29 14:17:07 -0800273 public static boolean isVp8HwSupportedUsingTextures() {
sakalb6760f92016-09-29 04:12:44 -0700274 return !hwEncoderDisabledTypes.contains(VP8_MIME_TYPE)
magjed295760d2017-01-12 01:11:57 -0800275 && (findHwEncoder(VP8_MIME_TYPE, vp8HwList(), supportedSurfaceColorList) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800276 }
277
278 public static boolean isVp9HwSupportedUsingTextures() {
sakalb6760f92016-09-29 04:12:44 -0700279 return !hwEncoderDisabledTypes.contains(VP9_MIME_TYPE)
280 && (findHwEncoder(VP9_MIME_TYPE, vp9HwList, supportedSurfaceColorList) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800281 }
282
283 public static boolean isH264HwSupportedUsingTextures() {
sakalb6760f92016-09-29 04:12:44 -0700284 return !hwEncoderDisabledTypes.contains(H264_MIME_TYPE)
285 && (findHwEncoder(H264_MIME_TYPE, h264HwList, supportedSurfaceColorList) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800286 }
287
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000288 // Helper struct for findHwEncoder() below.
Alex Glaznevc7483a72017-01-05 15:22:24 -0800289 public static class EncoderProperties {
Alex Glaznevcfaca032016-09-06 14:08:19 -0700290 public EncoderProperties(
291 String codecName, int colorFormat, BitrateAdjustmentType bitrateAdjustmentType) {
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000292 this.codecName = codecName;
293 this.colorFormat = colorFormat;
Alex Glaznevcfaca032016-09-06 14:08:19 -0700294 this.bitrateAdjustmentType = bitrateAdjustmentType;
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000295 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000296 public final String codecName; // OpenMax component name for HW codec.
sakalb6760f92016-09-29 04:12:44 -0700297 public final int colorFormat; // Color format supported by codec.
Alex Glaznevcfaca032016-09-06 14:08:19 -0700298 public final BitrateAdjustmentType bitrateAdjustmentType; // Bitrate adjustment type
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000299 }
300
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000301 private static EncoderProperties findHwEncoder(
Alex Glaznev269fe752016-05-25 16:17:33 -0700302 String mime, MediaCodecProperties[] supportedHwCodecProperties, int[] colorList) {
Alex Glaznev0c850202015-08-04 10:26:59 -0700303 // MediaCodec.setParameters is missing for JB and below, so bitrate
304 // can not be adjusted dynamically.
305 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
306 return null;
307 }
308
309 // Check if device is in H.264 exception list.
310 if (mime.equals(H264_MIME_TYPE)) {
311 List<String> exceptionModels = Arrays.asList(H264_HW_EXCEPTION_MODELS);
312 if (exceptionModels.contains(Build.MODEL)) {
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700313 Logging.w(TAG, "Model: " + Build.MODEL + " has black listed H.264 encoder.");
Alex Glaznev0c850202015-08-04 10:26:59 -0700314 return null;
315 }
316 }
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000317
318 for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
Alex Glaznev0060c562016-08-08 12:27:24 -0700319 MediaCodecInfo info = null;
320 try {
321 info = MediaCodecList.getCodecInfoAt(i);
322 } catch (IllegalArgumentException e) {
sakalb6760f92016-09-29 04:12:44 -0700323 Logging.e(TAG, "Cannot retrieve encoder codec info", e);
Alex Glaznev0060c562016-08-08 12:27:24 -0700324 }
325 if (info == null || !info.isEncoder()) {
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000326 continue;
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000327 }
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000328 String name = null;
329 for (String mimeType : info.getSupportedTypes()) {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000330 if (mimeType.equals(mime)) {
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000331 name = info.getName();
332 break;
333 }
334 }
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000335 if (name == null) {
sakalb6760f92016-09-29 04:12:44 -0700336 continue; // No HW support in this codec; try the next one.
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000337 }
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700338 Logging.v(TAG, "Found candidate encoder " + name);
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000339
340 // Check if this is supported HW encoder.
341 boolean supportedCodec = false;
Alex Glaznevcfaca032016-09-06 14:08:19 -0700342 BitrateAdjustmentType bitrateAdjustmentType = BitrateAdjustmentType.NO_ADJUSTMENT;
Alex Glaznev269fe752016-05-25 16:17:33 -0700343 for (MediaCodecProperties codecProperties : supportedHwCodecProperties) {
344 if (name.startsWith(codecProperties.codecPrefix)) {
345 if (Build.VERSION.SDK_INT < codecProperties.minSdk) {
sakalb6760f92016-09-29 04:12:44 -0700346 Logging.w(
347 TAG, "Codec " + name + " is disabled due to SDK version " + Build.VERSION.SDK_INT);
Alex Glaznev269fe752016-05-25 16:17:33 -0700348 continue;
349 }
Alex Glaznevcfaca032016-09-06 14:08:19 -0700350 if (codecProperties.bitrateAdjustmentType != BitrateAdjustmentType.NO_ADJUSTMENT) {
351 bitrateAdjustmentType = codecProperties.bitrateAdjustmentType;
sakalb6760f92016-09-29 04:12:44 -0700352 Logging.w(
353 TAG, "Codec " + name + " requires bitrate adjustment: " + bitrateAdjustmentType);
Alex Glaznev269fe752016-05-25 16:17:33 -0700354 }
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000355 supportedCodec = true;
356 break;
357 }
358 }
359 if (!supportedCodec) {
360 continue;
361 }
362
Alex Glaznev269fe752016-05-25 16:17:33 -0700363 // Check if HW codec supports known color format.
Alex Glaznev0060c562016-08-08 12:27:24 -0700364 CodecCapabilities capabilities;
365 try {
366 capabilities = info.getCapabilitiesForType(mime);
367 } catch (IllegalArgumentException e) {
sakalb6760f92016-09-29 04:12:44 -0700368 Logging.e(TAG, "Cannot retrieve encoder capabilities", e);
Alex Glaznev0060c562016-08-08 12:27:24 -0700369 continue;
370 }
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000371 for (int colorFormat : capabilities.colorFormats) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700372 Logging.v(TAG, " Color: 0x" + Integer.toHexString(colorFormat));
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000373 }
374
perkj30e91822015-11-20 01:31:25 -0800375 for (int supportedColorFormat : colorList) {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000376 for (int codecColorFormat : capabilities.colorFormats) {
377 if (codecColorFormat == supportedColorFormat) {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000378 // Found supported HW encoder.
sakalb6760f92016-09-29 04:12:44 -0700379 Logging.d(TAG, "Found target encoder for mime " + mime + " : " + name + ". Color: 0x"
380 + Integer.toHexString(codecColorFormat) + ". Bitrate adjustment: "
381 + bitrateAdjustmentType);
Alex Glaznevcfaca032016-09-06 14:08:19 -0700382 return new EncoderProperties(name, codecColorFormat, bitrateAdjustmentType);
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000383 }
384 }
385 }
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000386 }
sakalb6760f92016-09-29 04:12:44 -0700387 return null; // No HW encoder.
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000388 }
389
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000390 private void checkOnMediaCodecThread() {
391 if (mediaCodecThread.getId() != Thread.currentThread().getId()) {
sakalb6760f92016-09-29 04:12:44 -0700392 throw new RuntimeException("MediaCodecVideoEncoder previously operated on " + mediaCodecThread
393 + " but is now called on " + Thread.currentThread());
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000394 }
395 }
396
Alex Glaznev325d4142015-10-12 14:56:02 -0700397 public static void printStackTrace() {
Alex Glaznevc6aec4b2015-10-19 16:39:19 -0700398 if (runningInstance != null && runningInstance.mediaCodecThread != null) {
399 StackTraceElement[] mediaCodecStackTraces = runningInstance.mediaCodecThread.getStackTrace();
Alex Glaznev325d4142015-10-12 14:56:02 -0700400 if (mediaCodecStackTraces.length > 0) {
401 Logging.d(TAG, "MediaCodecVideoEncoder stacks trace:");
402 for (StackTraceElement stackTrace : mediaCodecStackTraces) {
403 Logging.d(TAG, stackTrace.toString());
404 }
405 }
406 }
407 }
408
henrike@webrtc.org528fc652014-10-06 17:56:43 +0000409 static MediaCodec createByCodecName(String codecName) {
410 try {
411 // In the L-SDK this call can throw IOException so in order to work in
412 // both cases catch an exception.
413 return MediaCodec.createByCodecName(codecName);
414 } catch (Exception e) {
415 return null;
416 }
417 }
418
glaznev3fc23502017-06-15 16:24:37 -0700419 boolean initEncode(VideoCodecType type, int profile, int width, int height, int kbps, int fps,
perkj48477c12015-12-18 00:34:37 -0800420 EglBase14.Context sharedContext) {
perkj30e91822015-11-20 01:31:25 -0800421 final boolean useSurface = sharedContext != null;
glaznev3fc23502017-06-15 16:24:37 -0700422 Logging.d(TAG,
423 "Java initEncode: " + type + ". Profile: " + profile + " : " + width + " x " + height
424 + ". @ " + kbps + " kbps. Fps: " + fps + ". Encode from texture : " + useSurface);
perkj9576e542015-11-12 06:43:16 -0800425
glaznev3fc23502017-06-15 16:24:37 -0700426 this.profile = profile;
Magnus Jedvert51254332015-12-15 16:22:29 +0100427 this.width = width;
428 this.height = height;
Alejandro Luebs69ddaef2015-10-09 15:46:09 -0700429 if (mediaCodecThread != null) {
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000430 throw new RuntimeException("Forgot to release()?");
431 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000432 EncoderProperties properties = null;
433 String mime = null;
434 int keyFrameIntervalSec = 0;
glaznev3fc23502017-06-15 16:24:37 -0700435 boolean configureH264HighProfile = false;
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000436 if (type == VideoCodecType.VIDEO_CODEC_VP8) {
437 mime = VP8_MIME_TYPE;
Alex Glaznev269fe752016-05-25 16:17:33 -0700438 properties = findHwEncoder(
magjed295760d2017-01-12 01:11:57 -0800439 VP8_MIME_TYPE, vp8HwList(), useSurface ? supportedSurfaceColorList : supportedColorList);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000440 keyFrameIntervalSec = 100;
Alex Glaznevad948c42015-11-18 13:06:42 -0800441 } else if (type == VideoCodecType.VIDEO_CODEC_VP9) {
442 mime = VP9_MIME_TYPE;
Alex Glaznev269fe752016-05-25 16:17:33 -0700443 properties = findHwEncoder(
444 VP9_MIME_TYPE, vp9HwList, useSurface ? supportedSurfaceColorList : supportedColorList);
Alex Glaznevad948c42015-11-18 13:06:42 -0800445 keyFrameIntervalSec = 100;
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000446 } else if (type == VideoCodecType.VIDEO_CODEC_H264) {
447 mime = H264_MIME_TYPE;
Alex Glaznev269fe752016-05-25 16:17:33 -0700448 properties = findHwEncoder(
449 H264_MIME_TYPE, h264HwList, useSurface ? supportedSurfaceColorList : supportedColorList);
glaznev3fc23502017-06-15 16:24:37 -0700450 if (profile == H264Profile.CONSTRAINED_HIGH.getValue()) {
451 EncoderProperties h264HighProfileProperties = findHwEncoder(H264_MIME_TYPE,
452 h264HighProfileHwList, useSurface ? supportedSurfaceColorList : supportedColorList);
453 if (h264HighProfileProperties != null) {
454 Logging.d(TAG, "High profile H.264 encoder supported.");
455 configureH264HighProfile = true;
456 } else {
457 Logging.d(TAG, "High profile H.264 encoder requested, but not supported. Use baseline.");
458 }
459 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000460 keyFrameIntervalSec = 20;
461 }
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000462 if (properties == null) {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000463 throw new RuntimeException("Can not find HW encoder for " + type);
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000464 }
Alex Glaznevc6aec4b2015-10-19 16:39:19 -0700465 runningInstance = this; // Encoder is now running and can be queried for stack traces.
perkj9576e542015-11-12 06:43:16 -0800466 colorFormat = properties.colorFormat;
Alex Glaznevcfaca032016-09-06 14:08:19 -0700467 bitrateAdjustmentType = properties.bitrateAdjustmentType;
468 if (bitrateAdjustmentType == BitrateAdjustmentType.FRAMERATE_ADJUSTMENT) {
Alex Glaznev269fe752016-05-25 16:17:33 -0700469 fps = BITRATE_ADJUSTMENT_FPS;
sakalb6760f92016-09-29 04:12:44 -0700470 } else {
Alex Glaznevc55c39d2016-09-02 12:16:27 -0700471 fps = Math.min(fps, MAXIMUM_INITIAL_FPS);
Alex Glaznev269fe752016-05-25 16:17:33 -0700472 }
Alex Glaznevc7483a72017-01-05 15:22:24 -0800473
474 forcedKeyFrameMs = 0;
475 lastKeyFrameMs = -1;
Alex Glaznev512f00b2017-01-05 16:40:46 -0800476 if (type == VideoCodecType.VIDEO_CODEC_VP8
477 && properties.codecName.startsWith(qcomVp8HwProperties.codecPrefix)) {
alexlau84ee5c62017-06-02 17:36:32 -0700478 if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP
479 || Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP_MR1) {
480 forcedKeyFrameMs = QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_L_MS;
481 } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.M) {
Alex Glaznevc7483a72017-01-05 15:22:24 -0800482 forcedKeyFrameMs = QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_M_MS;
483 } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
484 forcedKeyFrameMs = QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_N_MS;
485 }
486 }
487
sakalb6760f92016-09-29 04:12:44 -0700488 Logging.d(TAG, "Color format: " + colorFormat + ". Bitrate adjustment: " + bitrateAdjustmentType
Alex Glaznevc7483a72017-01-05 15:22:24 -0800489 + ". Key frame interval: " + forcedKeyFrameMs + " . Initial fps: " + fps);
Alex Glaznevcfaca032016-09-06 14:08:19 -0700490 targetBitrateBps = 1000 * kbps;
491 targetFps = fps;
492 bitrateAccumulatorMax = targetBitrateBps / 8.0;
493 bitrateAccumulator = 0;
494 bitrateObservationTimeMs = 0;
495 bitrateAdjustmentScaleExp = 0;
perkj9576e542015-11-12 06:43:16 -0800496
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000497 mediaCodecThread = Thread.currentThread();
498 try {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000499 MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
Alex Glaznevcfaca032016-09-06 14:08:19 -0700500 format.setInteger(MediaFormat.KEY_BIT_RATE, targetBitrateBps);
Alex Glaznev8a2cd3d2015-08-11 11:32:53 -0700501 format.setInteger("bitrate-mode", VIDEO_ControlRateConstant);
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000502 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
Alex Glaznevcfaca032016-09-06 14:08:19 -0700503 format.setInteger(MediaFormat.KEY_FRAME_RATE, targetFps);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000504 format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, keyFrameIntervalSec);
glaznev3fc23502017-06-15 16:24:37 -0700505 if (configureH264HighProfile) {
506 format.setInteger("profile", VIDEO_AVCProfileHigh);
507 format.setInteger("level", VIDEO_AVCLevel3);
508 }
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700509 Logging.d(TAG, " Format: " + format);
henrike@webrtc.org528fc652014-10-06 17:56:43 +0000510 mediaCodec = createByCodecName(properties.codecName);
perkj9576e542015-11-12 06:43:16 -0800511 this.type = type;
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000512 if (mediaCodec == null) {
Alex Glaznev325d4142015-10-12 14:56:02 -0700513 Logging.e(TAG, "Can not create media encoder");
sakal996a83c2017-03-15 05:53:14 -0700514 release();
perkj9576e542015-11-12 06:43:16 -0800515 return false;
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000516 }
sakalb6760f92016-09-29 04:12:44 -0700517 mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
perkj9576e542015-11-12 06:43:16 -0800518
perkj30e91822015-11-20 01:31:25 -0800519 if (useSurface) {
perkj48477c12015-12-18 00:34:37 -0800520 eglBase = new EglBase14(sharedContext, EglBase.CONFIG_RECORDABLE);
perkj30e91822015-11-20 01:31:25 -0800521 // Create an input surface and keep a reference since we must release the surface when done.
522 inputSurface = mediaCodec.createInputSurface();
523 eglBase.createSurface(inputSurface);
524 drawer = new GlRectDrawer();
525 }
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000526 mediaCodec.start();
527 outputBuffers = mediaCodec.getOutputBuffers();
perkj9576e542015-11-12 06:43:16 -0800528 Logging.d(TAG, "Output buffers: " + outputBuffers.length);
529
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000530 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700531 Logging.e(TAG, "initEncode failed", e);
sakal996a83c2017-03-15 05:53:14 -0700532 release();
perkj9576e542015-11-12 06:43:16 -0800533 return false;
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000534 }
perkj9576e542015-11-12 06:43:16 -0800535 return true;
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000536 }
537
sakalb6760f92016-09-29 04:12:44 -0700538 ByteBuffer[] getInputBuffers() {
perkj9576e542015-11-12 06:43:16 -0800539 ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
540 Logging.d(TAG, "Input buffers: " + inputBuffers.length);
541 return inputBuffers;
542 }
543
Alex Glaznevc7483a72017-01-05 15:22:24 -0800544 void checkKeyFrameRequired(boolean requestedKeyFrame, long presentationTimestampUs) {
545 long presentationTimestampMs = (presentationTimestampUs + 500) / 1000;
546 if (lastKeyFrameMs < 0) {
547 lastKeyFrameMs = presentationTimestampMs;
548 }
549 boolean forcedKeyFrame = false;
550 if (!requestedKeyFrame && forcedKeyFrameMs > 0
551 && presentationTimestampMs > lastKeyFrameMs + forcedKeyFrameMs) {
552 forcedKeyFrame = true;
553 }
554 if (requestedKeyFrame || forcedKeyFrame) {
555 // Ideally MediaCodec would honor BUFFER_FLAG_SYNC_FRAME so we could
556 // indicate this in queueInputBuffer() below and guarantee _this_ frame
557 // be encoded as a key frame, but sadly that flag is ignored. Instead,
558 // we request a key frame "soon".
559 if (requestedKeyFrame) {
560 Logging.d(TAG, "Sync frame request");
561 } else {
562 Logging.d(TAG, "Sync frame forced");
563 }
564 Bundle b = new Bundle();
565 b.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
566 mediaCodec.setParameters(b);
567 lastKeyFrameMs = presentationTimestampMs;
568 }
569 }
570
perkj9576e542015-11-12 06:43:16 -0800571 boolean encodeBuffer(
sakalb6760f92016-09-29 04:12:44 -0700572 boolean isKeyframe, int inputBuffer, int size, long presentationTimestampUs) {
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000573 checkOnMediaCodecThread();
574 try {
Alex Glaznevc7483a72017-01-05 15:22:24 -0800575 checkKeyFrameRequired(isKeyframe, presentationTimestampUs);
sakalb6760f92016-09-29 04:12:44 -0700576 mediaCodec.queueInputBuffer(inputBuffer, 0, size, presentationTimestampUs, 0);
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000577 return true;
sakalb6760f92016-09-29 04:12:44 -0700578 } catch (IllegalStateException e) {
perkj9576e542015-11-12 06:43:16 -0800579 Logging.e(TAG, "encodeBuffer failed", e);
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000580 return false;
581 }
582 }
583
perkj30e91822015-11-20 01:31:25 -0800584 boolean encodeTexture(boolean isKeyframe, int oesTextureId, float[] transformationMatrix,
585 long presentationTimestampUs) {
586 checkOnMediaCodecThread();
587 try {
Alex Glaznevc7483a72017-01-05 15:22:24 -0800588 checkKeyFrameRequired(isKeyframe, presentationTimestampUs);
perkj30e91822015-11-20 01:31:25 -0800589 eglBase.makeCurrent();
perkj226a6022015-12-02 02:24:40 -0800590 // TODO(perkj): glClear() shouldn't be necessary since every pixel is covered anyway,
591 // but it's a workaround for bug webrtc:5147.
592 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
ivoc1aa435c2016-05-04 07:14:14 -0700593 drawer.drawOes(oesTextureId, transformationMatrix, width, height, 0, 0, width, height);
perkj48477c12015-12-18 00:34:37 -0800594 eglBase.swapBuffers(TimeUnit.MICROSECONDS.toNanos(presentationTimestampUs));
perkj30e91822015-11-20 01:31:25 -0800595 return true;
sakalb6760f92016-09-29 04:12:44 -0700596 } catch (RuntimeException e) {
perkj30e91822015-11-20 01:31:25 -0800597 Logging.e(TAG, "encodeTexture failed", e);
598 return false;
599 }
600 }
601
sakalb5f5bdc2017-08-10 04:15:42 -0700602 /**
603 * Encodes a new style VideoFrame. Called by JNI. |bufferIndex| is -1 if we are not encoding in
604 * surface mode.
605 */
606 boolean encodeFrame(long nativeEncoder, boolean isKeyframe, VideoFrame frame, int bufferIndex) {
607 checkOnMediaCodecThread();
608 try {
609 long presentationTimestampUs = TimeUnit.NANOSECONDS.toMicros(frame.getTimestampNs());
610 checkKeyFrameRequired(isKeyframe, presentationTimestampUs);
611
612 VideoFrame.Buffer buffer = frame.getBuffer();
613 if (buffer instanceof VideoFrame.TextureBuffer) {
614 VideoFrame.TextureBuffer textureBuffer = (VideoFrame.TextureBuffer) buffer;
615 eglBase.makeCurrent();
616 // TODO(perkj): glClear() shouldn't be necessary since every pixel is covered anyway,
617 // but it's a workaround for bug webrtc:5147.
618 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
magjed7cede372017-09-11 06:12:07 -0700619 VideoFrameDrawer.drawTexture(drawer, textureBuffer, new Matrix() /* renderMatrix */, width,
sakal9bc599f2017-09-08 04:46:33 -0700620 height, 0 /* viewportX */, 0 /* viewportY */, width, height);
sakalb5f5bdc2017-08-10 04:15:42 -0700621 eglBase.swapBuffers(frame.getTimestampNs());
622 } else {
623 VideoFrame.I420Buffer i420Buffer = buffer.toI420();
Sami Kalliomäkie3044fe2017-10-02 09:41:55 +0200624 final int chromaHeight = (height + 1) / 2;
625 final ByteBuffer dataY = i420Buffer.getDataY();
626 final ByteBuffer dataU = i420Buffer.getDataU();
627 final ByteBuffer dataV = i420Buffer.getDataV();
628 final int strideY = i420Buffer.getStrideY();
629 final int strideU = i420Buffer.getStrideU();
630 final int strideV = i420Buffer.getStrideV();
631 if (dataY.capacity() < strideY * height) {
632 throw new RuntimeException("Y-plane buffer size too small.");
633 }
634 if (dataU.capacity() < strideU * chromaHeight) {
635 throw new RuntimeException("U-plane buffer size too small.");
636 }
637 if (dataV.capacity() < strideV * chromaHeight) {
638 throw new RuntimeException("V-plane buffer size too small.");
639 }
640 nativeFillBuffer(
641 nativeEncoder, bufferIndex, dataY, strideY, dataU, strideU, dataV, strideV);
sakalb5f5bdc2017-08-10 04:15:42 -0700642 i420Buffer.release();
643 // I420 consists of one full-resolution and two half-resolution planes.
644 // 1 + 1 / 4 + 1 / 4 = 3 / 2
645 int yuvSize = width * height * 3 / 2;
646 mediaCodec.queueInputBuffer(bufferIndex, 0, yuvSize, presentationTimestampUs, 0);
647 }
648 return true;
649 } catch (RuntimeException e) {
650 Logging.e(TAG, "encodeFrame failed", e);
651 return false;
652 }
653 }
654
perkj9576e542015-11-12 06:43:16 -0800655 void release() {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700656 Logging.d(TAG, "Java releaseEncoder");
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000657 checkOnMediaCodecThread();
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700658
sakal996a83c2017-03-15 05:53:14 -0700659 class CaughtException {
660 Exception e;
661 }
662 final CaughtException caughtException = new CaughtException();
663 boolean stopHung = false;
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700664
sakal996a83c2017-03-15 05:53:14 -0700665 if (mediaCodec != null) {
666 // Run Mediacodec stop() and release() on separate thread since sometime
667 // Mediacodec.stop() may hang.
668 final CountDownLatch releaseDone = new CountDownLatch(1);
669
670 Runnable runMediaCodecRelease = new Runnable() {
671 @Override
672 public void run() {
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700673 Logging.d(TAG, "Java releaseEncoder on release thread");
sakal996a83c2017-03-15 05:53:14 -0700674 try {
675 mediaCodec.stop();
676 } catch (Exception e) {
677 Logging.e(TAG, "Media encoder stop failed", e);
678 }
679 try {
680 mediaCodec.release();
681 } catch (Exception e) {
682 Logging.e(TAG, "Media encoder release failed", e);
683 caughtException.e = e;
684 }
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700685 Logging.d(TAG, "Java releaseEncoder on release thread done");
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700686
sakal996a83c2017-03-15 05:53:14 -0700687 releaseDone.countDown();
688 }
689 };
690 new Thread(runMediaCodecRelease).start();
691
692 if (!ThreadUtils.awaitUninterruptibly(releaseDone, MEDIA_CODEC_RELEASE_TIMEOUT_MS)) {
693 Logging.e(TAG, "Media encoder release timeout");
694 stopHung = true;
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700695 }
sakal996a83c2017-03-15 05:53:14 -0700696
697 mediaCodec = null;
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000698 }
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700699
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000700 mediaCodecThread = null;
perkj30e91822015-11-20 01:31:25 -0800701 if (drawer != null) {
702 drawer.release();
703 drawer = null;
704 }
705 if (eglBase != null) {
706 eglBase.release();
707 eglBase = null;
708 }
709 if (inputSurface != null) {
710 inputSurface.release();
711 inputSurface = null;
712 }
Alex Glaznevc6aec4b2015-10-19 16:39:19 -0700713 runningInstance = null;
sakal996a83c2017-03-15 05:53:14 -0700714
715 if (stopHung) {
716 codecErrors++;
717 if (errorCallback != null) {
718 Logging.e(TAG, "Invoke codec error callback. Errors: " + codecErrors);
719 errorCallback.onMediaCodecVideoEncoderCriticalError(codecErrors);
720 }
721 throw new RuntimeException("Media encoder release timeout.");
722 }
723
724 // Re-throw any runtime exception caught inside the other thread. Since this is an invoke, add
725 // stack trace for the waiting thread as well.
726 if (caughtException.e != null) {
727 final RuntimeException runtimeException = new RuntimeException(caughtException.e);
728 runtimeException.setStackTrace(ThreadUtils.concatStackTraces(
729 caughtException.e.getStackTrace(), runtimeException.getStackTrace()));
730 throw runtimeException;
731 }
732
Alex Glaznev325d4142015-10-12 14:56:02 -0700733 Logging.d(TAG, "Java releaseEncoder done");
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000734 }
735
Alex Glaznev269fe752016-05-25 16:17:33 -0700736 private boolean setRates(int kbps, int frameRate) {
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000737 checkOnMediaCodecThread();
Alex Glaznevcfaca032016-09-06 14:08:19 -0700738
739 int codecBitrateBps = 1000 * kbps;
740 if (bitrateAdjustmentType == BitrateAdjustmentType.DYNAMIC_ADJUSTMENT) {
741 bitrateAccumulatorMax = codecBitrateBps / 8.0;
742 if (targetBitrateBps > 0 && codecBitrateBps < targetBitrateBps) {
743 // Rescale the accumulator level if the accumulator max decreases
744 bitrateAccumulator = bitrateAccumulator * codecBitrateBps / targetBitrateBps;
745 }
Alex Glaznev269fe752016-05-25 16:17:33 -0700746 }
Alex Glaznevcfaca032016-09-06 14:08:19 -0700747 targetBitrateBps = codecBitrateBps;
748 targetFps = frameRate;
749
750 // Adjust actual encoder bitrate based on bitrate adjustment type.
751 if (bitrateAdjustmentType == BitrateAdjustmentType.FRAMERATE_ADJUSTMENT && targetFps > 0) {
752 codecBitrateBps = BITRATE_ADJUSTMENT_FPS * targetBitrateBps / targetFps;
sakalb6760f92016-09-29 04:12:44 -0700753 Logging.v(TAG,
754 "setRates: " + kbps + " -> " + (codecBitrateBps / 1000) + " kbps. Fps: " + targetFps);
Alex Glaznevcfaca032016-09-06 14:08:19 -0700755 } else if (bitrateAdjustmentType == BitrateAdjustmentType.DYNAMIC_ADJUSTMENT) {
sakalb6760f92016-09-29 04:12:44 -0700756 Logging.v(TAG, "setRates: " + kbps + " kbps. Fps: " + targetFps + ". ExpScale: "
757 + bitrateAdjustmentScaleExp);
Alex Glaznevcfaca032016-09-06 14:08:19 -0700758 if (bitrateAdjustmentScaleExp != 0) {
sakalb6760f92016-09-29 04:12:44 -0700759 codecBitrateBps = (int) (codecBitrateBps * getBitrateScale(bitrateAdjustmentScaleExp));
Alex Glaznevcfaca032016-09-06 14:08:19 -0700760 }
761 } else {
762 Logging.v(TAG, "setRates: " + kbps + " kbps. Fps: " + targetFps);
763 }
764
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000765 try {
766 Bundle params = new Bundle();
Alex Glaznevcfaca032016-09-06 14:08:19 -0700767 params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, codecBitrateBps);
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000768 mediaCodec.setParameters(params);
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000769 return true;
770 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700771 Logging.e(TAG, "setRates failed", e);
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000772 return false;
773 }
774 }
775
776 // Dequeue an input buffer and return its index, -1 if no input buffer is
777 // available, or -2 if the codec is no longer operative.
perkj9576e542015-11-12 06:43:16 -0800778 int dequeueInputBuffer() {
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000779 checkOnMediaCodecThread();
780 try {
781 return mediaCodec.dequeueInputBuffer(DEQUEUE_TIMEOUT);
782 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700783 Logging.e(TAG, "dequeueIntputBuffer failed", e);
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000784 return -2;
785 }
786 }
787
788 // Helper struct for dequeueOutputBuffer() below.
perkj9576e542015-11-12 06:43:16 -0800789 static class OutputBufferInfo {
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000790 public OutputBufferInfo(
sakalb6760f92016-09-29 04:12:44 -0700791 int index, ByteBuffer buffer, boolean isKeyFrame, long presentationTimestampUs) {
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000792 this.index = index;
793 this.buffer = buffer;
794 this.isKeyFrame = isKeyFrame;
795 this.presentationTimestampUs = presentationTimestampUs;
796 }
797
perkj9576e542015-11-12 06:43:16 -0800798 public final int index;
799 public final ByteBuffer buffer;
800 public final boolean isKeyFrame;
801 public final long presentationTimestampUs;
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000802 }
803
804 // Dequeue and return an output buffer, or null if no output is ready. Return
805 // a fake OutputBufferInfo with index -1 if the codec is no longer operable.
perkj9576e542015-11-12 06:43:16 -0800806 OutputBufferInfo dequeueOutputBuffer() {
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000807 checkOnMediaCodecThread();
808 try {
809 MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
810 int result = mediaCodec.dequeueOutputBuffer(info, DEQUEUE_TIMEOUT);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000811 // Check if this is config frame and save configuration data.
812 if (result >= 0) {
sakalb6760f92016-09-29 04:12:44 -0700813 boolean isConfigFrame = (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0;
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000814 if (isConfigFrame) {
sakalb6760f92016-09-29 04:12:44 -0700815 Logging.d(TAG, "Config frame generated. Offset: " + info.offset + ". Size: " + info.size);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000816 configData = ByteBuffer.allocateDirect(info.size);
817 outputBuffers[result].position(info.offset);
818 outputBuffers[result].limit(info.offset + info.size);
819 configData.put(outputBuffers[result]);
glaznev3fc23502017-06-15 16:24:37 -0700820 // Log few SPS header bytes to check profile and level.
821 String spsData = "";
822 for (int i = 0; i < (info.size < 8 ? info.size : 8); i++) {
823 spsData += Integer.toHexString(configData.get(i) & 0xff) + " ";
824 }
825 Logging.d(TAG, spsData);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000826 // Release buffer back.
827 mediaCodec.releaseOutputBuffer(result, false);
828 // Query next output.
829 result = mediaCodec.dequeueOutputBuffer(info, DEQUEUE_TIMEOUT);
830 }
831 }
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000832 if (result >= 0) {
833 // MediaCodec doesn't care about Buffer position/remaining/etc so we can
834 // mess with them to get a slice and avoid having to pass extra
835 // (BufferInfo-related) parameters back to C++.
836 ByteBuffer outputBuffer = outputBuffers[result].duplicate();
837 outputBuffer.position(info.offset);
838 outputBuffer.limit(info.offset + info.size);
Alex Glaznevcfaca032016-09-06 14:08:19 -0700839 reportEncodedFrame(info.size);
840
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000841 // Check key frame flag.
sakalb6760f92016-09-29 04:12:44 -0700842 boolean isKeyFrame = (info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
glaznev@webrtc.orgc6c1dfd2014-06-13 22:59:08 +0000843 if (isKeyFrame) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700844 Logging.d(TAG, "Sync frame generated");
glaznev@webrtc.orgc6c1dfd2014-06-13 22:59:08 +0000845 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000846 if (isKeyFrame && type == VideoCodecType.VIDEO_CODEC_H264) {
sakalb6760f92016-09-29 04:12:44 -0700847 Logging.d(TAG, "Appending config frame of size " + configData.capacity()
848 + " to output buffer with offset " + info.offset + ", size " + info.size);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000849 // For H.264 key frame append SPS and PPS NALs at the start
sakalb6760f92016-09-29 04:12:44 -0700850 ByteBuffer keyFrameBuffer = ByteBuffer.allocateDirect(configData.capacity() + info.size);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000851 configData.rewind();
852 keyFrameBuffer.put(configData);
853 keyFrameBuffer.put(outputBuffer);
854 keyFrameBuffer.position(0);
sakalb6760f92016-09-29 04:12:44 -0700855 return new OutputBufferInfo(result, keyFrameBuffer, isKeyFrame, info.presentationTimeUs);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000856 } else {
sakalb6760f92016-09-29 04:12:44 -0700857 return new OutputBufferInfo(
858 result, outputBuffer.slice(), isKeyFrame, info.presentationTimeUs);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000859 }
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000860 } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
861 outputBuffers = mediaCodec.getOutputBuffers();
862 return dequeueOutputBuffer();
863 } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
864 return dequeueOutputBuffer();
865 } else if (result == MediaCodec.INFO_TRY_AGAIN_LATER) {
866 return null;
867 }
868 throw new RuntimeException("dequeueOutputBuffer: " + result);
869 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700870 Logging.e(TAG, "dequeueOutputBuffer failed", e);
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000871 return new OutputBufferInfo(-1, null, false, -1);
872 }
873 }
874
Alex Glaznevcfaca032016-09-06 14:08:19 -0700875 private double getBitrateScale(int bitrateAdjustmentScaleExp) {
876 return Math.pow(BITRATE_CORRECTION_MAX_SCALE,
sakalb6760f92016-09-29 04:12:44 -0700877 (double) bitrateAdjustmentScaleExp / BITRATE_CORRECTION_STEPS);
Alex Glaznevcfaca032016-09-06 14:08:19 -0700878 }
879
880 private void reportEncodedFrame(int size) {
881 if (targetFps == 0 || bitrateAdjustmentType != BitrateAdjustmentType.DYNAMIC_ADJUSTMENT) {
882 return;
883 }
884
885 // Accumulate the difference between actial and expected frame sizes.
886 double expectedBytesPerFrame = targetBitrateBps / (8.0 * targetFps);
887 bitrateAccumulator += (size - expectedBytesPerFrame);
888 bitrateObservationTimeMs += 1000.0 / targetFps;
889
890 // Put a cap on the accumulator, i.e., don't let it grow beyond some level to avoid
891 // using too old data for bitrate adjustment.
892 double bitrateAccumulatorCap = BITRATE_CORRECTION_SEC * bitrateAccumulatorMax;
893 bitrateAccumulator = Math.min(bitrateAccumulator, bitrateAccumulatorCap);
894 bitrateAccumulator = Math.max(bitrateAccumulator, -bitrateAccumulatorCap);
895
896 // Do bitrate adjustment every 3 seconds if actual encoder bitrate deviates too much
897 // form the target value.
898 if (bitrateObservationTimeMs > 1000 * BITRATE_CORRECTION_SEC) {
sakalb6760f92016-09-29 04:12:44 -0700899 Logging.d(TAG, "Acc: " + (int) bitrateAccumulator + ". Max: " + (int) bitrateAccumulatorMax
900 + ". ExpScale: " + bitrateAdjustmentScaleExp);
Alex Glaznevcfaca032016-09-06 14:08:19 -0700901 boolean bitrateAdjustmentScaleChanged = false;
902 if (bitrateAccumulator > bitrateAccumulatorMax) {
903 // Encoder generates too high bitrate - need to reduce the scale.
Alex Glaznev7fa4a722017-01-17 15:32:02 -0800904 int bitrateAdjustmentInc = (int) (bitrateAccumulator / bitrateAccumulatorMax + 0.5);
905 bitrateAdjustmentScaleExp -= bitrateAdjustmentInc;
Alex Glaznevcfaca032016-09-06 14:08:19 -0700906 bitrateAccumulator = bitrateAccumulatorMax;
Alex Glaznevcfaca032016-09-06 14:08:19 -0700907 bitrateAdjustmentScaleChanged = true;
908 } else if (bitrateAccumulator < -bitrateAccumulatorMax) {
909 // Encoder generates too low bitrate - need to increase the scale.
Alex Glaznev7fa4a722017-01-17 15:32:02 -0800910 int bitrateAdjustmentInc = (int) (-bitrateAccumulator / bitrateAccumulatorMax + 0.5);
911 bitrateAdjustmentScaleExp += bitrateAdjustmentInc;
Alex Glaznevcfaca032016-09-06 14:08:19 -0700912 bitrateAccumulator = -bitrateAccumulatorMax;
913 bitrateAdjustmentScaleChanged = true;
914 }
915 if (bitrateAdjustmentScaleChanged) {
916 bitrateAdjustmentScaleExp = Math.min(bitrateAdjustmentScaleExp, BITRATE_CORRECTION_STEPS);
917 bitrateAdjustmentScaleExp = Math.max(bitrateAdjustmentScaleExp, -BITRATE_CORRECTION_STEPS);
sakalb6760f92016-09-29 04:12:44 -0700918 Logging.d(TAG, "Adjusting bitrate scale to " + bitrateAdjustmentScaleExp + ". Value: "
919 + getBitrateScale(bitrateAdjustmentScaleExp));
Alex Glaznevcfaca032016-09-06 14:08:19 -0700920 setRates(targetBitrateBps / 1000, targetFps);
921 }
922 bitrateObservationTimeMs = 0;
923 }
924 }
925
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000926 // Release a dequeued output buffer back to the codec for re-use. Return
927 // false if the codec is no longer operable.
perkj9576e542015-11-12 06:43:16 -0800928 boolean releaseOutputBuffer(int index) {
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000929 checkOnMediaCodecThread();
930 try {
931 mediaCodec.releaseOutputBuffer(index, false);
932 return true;
933 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700934 Logging.e(TAG, "releaseOutputBuffer failed", e);
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000935 return false;
936 }
937 }
sakalb5f5bdc2017-08-10 04:15:42 -0700938
939 /** Fills an inputBuffer with the given index with data from the byte buffers. */
940 private static native void nativeFillBuffer(long nativeEncoder, int inputBuffer, ByteBuffer dataY,
941 int strideY, ByteBuffer dataU, int strideU, ByteBuffer dataV, int strideV);
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000942}