blob: 6b98598705c0f777eae5d73a8c18de880bff2cb0 [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;
Magnus Jedvert655e1962017-12-08 11:05:22 +010032import org.webrtc.EglBase14;
33import org.webrtc.VideoFrame;
fischman@webrtc.org540acde2014-02-13 03:56:14 +000034
Magnus Jedvert9060eb12017-12-12 12:52:54 +010035// Java-side of peerconnection.cc:MediaCodecVideoEncoder.
fischman@webrtc.org540acde2014-02-13 03:56:14 +000036// This class is an implementation detail of the Java PeerConnection API.
Patrik Höglund68876f92015-11-12 17:36:48 +010037@TargetApi(19)
38@SuppressWarnings("deprecation")
perkj@webrtc.org47098872014-10-24 11:38:19 +000039public class MediaCodecVideoEncoder {
fischman@webrtc.org540acde2014-02-13 03:56:14 +000040 // This class is constructed, operated, and destroyed by its C++ incarnation,
41 // so the class and its methods have non-public visibility. The API this
42 // class exposes aims to mimic the webrtc::VideoEncoder API as closely as
43 // possibly to minimize the amount of translation work necessary.
44
45 private static final String TAG = "MediaCodecVideoEncoder";
46
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000047 // Tracks webrtc::VideoCodecType.
Magnus Jedvert655e1962017-12-08 11:05:22 +010048 public enum VideoCodecType {
49 VIDEO_CODEC_VP8,
50 VIDEO_CODEC_VP9,
51 VIDEO_CODEC_H264;
52
53 @CalledByNative("VideoCodecType")
54 static VideoCodecType fromNativeIndex(int nativeIndex) {
55 return values()[nativeIndex];
56 }
57 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000058
Alex Glaznev5c3da4b2015-10-30 15:31:07 -070059 private static final int MEDIA_CODEC_RELEASE_TIMEOUT_MS = 5000; // Timeout for codec releasing.
sakalb6760f92016-09-29 04:12:44 -070060 private static final int DEQUEUE_TIMEOUT = 0; // Non-blocking, no wait.
Alex Glaznev269fe752016-05-25 16:17:33 -070061 private static final int BITRATE_ADJUSTMENT_FPS = 30;
Alex Glaznevc55c39d2016-09-02 12:16:27 -070062 private static final int MAXIMUM_INITIAL_FPS = 30;
Alex Glaznevcfaca032016-09-06 14:08:19 -070063 private static final double BITRATE_CORRECTION_SEC = 3.0;
Alex Glaznev7fa4a722017-01-17 15:32:02 -080064 // Maximum bitrate correction scale - no more than 4 times.
65 private static final double BITRATE_CORRECTION_MAX_SCALE = 4;
Alex Glaznevcfaca032016-09-06 14:08:19 -070066 // Amount of correction steps to reach correction maximum scale.
Alex Glaznev7fa4a722017-01-17 15:32:02 -080067 private static final int BITRATE_CORRECTION_STEPS = 20;
Alex Glaznevc7483a72017-01-05 15:22:24 -080068 // Forced key frame interval - used to reduce color distortions on Qualcomm platform.
alexlau84ee5c62017-06-02 17:36:32 -070069 private static final long QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_L_MS = 15000;
70 private static final long QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_M_MS = 20000;
Alex Glaznevc7483a72017-01-05 15:22:24 -080071 private static final long QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_N_MS = 15000;
Alex Glaznevcfaca032016-09-06 14:08:19 -070072
perkj9576e542015-11-12 06:43:16 -080073 // Active running encoder instance. Set in initEncode() (called from native code)
Alex Glaznevc6aec4b2015-10-19 16:39:19 -070074 // and reset to null in release() call.
75 private static MediaCodecVideoEncoder runningInstance = null;
Alex Glaznev5c3da4b2015-10-30 15:31:07 -070076 private static MediaCodecVideoEncoderErrorCallback errorCallback = null;
77 private static int codecErrors = 0;
Alex Glazneveee86a62016-01-29 14:17:07 -080078 // List of disabled codec types - can be set from application.
79 private static Set<String> hwEncoderDisabledTypes = new HashSet<String>();
Alex Glaznev5c3da4b2015-10-30 15:31:07 -070080
Alejandro Luebs69ddaef2015-10-09 15:46:09 -070081 private Thread mediaCodecThread;
fischman@webrtc.org540acde2014-02-13 03:56:14 +000082 private MediaCodec mediaCodec;
83 private ByteBuffer[] outputBuffers;
perkj48477c12015-12-18 00:34:37 -080084 private EglBase14 eglBase;
glaznev3fc23502017-06-15 16:24:37 -070085 private int profile;
Magnus Jedvert51254332015-12-15 16:22:29 +010086 private int width;
87 private int height;
perkj30e91822015-11-20 01:31:25 -080088 private Surface inputSurface;
89 private GlRectDrawer drawer;
Alex Glaznev269fe752016-05-25 16:17:33 -070090
fischman@webrtc.org540acde2014-02-13 03:56:14 +000091 private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8";
Alex Glaznevad948c42015-11-18 13:06:42 -080092 private static final String VP9_MIME_TYPE = "video/x-vnd.on2.vp9";
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000093 private static final String H264_MIME_TYPE = "video/avc";
Alex Glaznev269fe752016-05-25 16:17:33 -070094
glaznev3fc23502017-06-15 16:24:37 -070095 private static final int VIDEO_AVCProfileHigh = 8;
96 private static final int VIDEO_AVCLevel3 = 0x100;
97
Alex Glaznevcfaca032016-09-06 14:08:19 -070098 // Type of bitrate adjustment for video encoder.
99 public enum BitrateAdjustmentType {
100 // No adjustment - video encoder has no known bitrate problem.
101 NO_ADJUSTMENT,
102 // Framerate based bitrate adjustment is required - HW encoder does not use frame
103 // timestamps to calculate frame bitrate budget and instead is relying on initial
104 // fps configuration assuming that all frames are coming at fixed initial frame rate.
105 FRAMERATE_ADJUSTMENT,
106 // Dynamic bitrate adjustment is required - HW encoder used frame timestamps, but actual
107 // bitrate deviates too much from the target value.
108 DYNAMIC_ADJUSTMENT
109 }
110
glaznev3fc23502017-06-15 16:24:37 -0700111 // Should be in sync with webrtc::H264::Profile.
112 public static enum H264Profile {
113 CONSTRAINED_BASELINE(0),
114 BASELINE(1),
115 MAIN(2),
116 CONSTRAINED_HIGH(3),
117 HIGH(4);
118
119 private final int value;
120
121 H264Profile(int value) {
122 this.value = value;
123 }
124
125 public int getValue() {
126 return value;
127 }
128 }
129
Alex Glaznev269fe752016-05-25 16:17:33 -0700130 // Class describing supported media codec properties.
131 private static class MediaCodecProperties {
132 public final String codecPrefix;
133 // Minimum Android SDK required for this codec to be used.
134 public final int minSdk;
135 // Flag if encoder implementation does not use frame timestamps to calculate frame bitrate
136 // budget and instead is relying on initial fps configuration assuming that all frames are
137 // coming at fixed initial frame rate. Bitrate adjustment is required for this case.
Alex Glaznevcfaca032016-09-06 14:08:19 -0700138 public final BitrateAdjustmentType bitrateAdjustmentType;
Alex Glaznev269fe752016-05-25 16:17:33 -0700139
140 MediaCodecProperties(
Alex Glaznevcfaca032016-09-06 14:08:19 -0700141 String codecPrefix, int minSdk, BitrateAdjustmentType bitrateAdjustmentType) {
Alex Glaznev269fe752016-05-25 16:17:33 -0700142 this.codecPrefix = codecPrefix;
143 this.minSdk = minSdk;
Alex Glaznevcfaca032016-09-06 14:08:19 -0700144 this.bitrateAdjustmentType = bitrateAdjustmentType;
Alex Glaznev269fe752016-05-25 16:17:33 -0700145 }
146 }
147
Alex Glaznevdcd730f2016-04-21 17:01:46 -0700148 // List of supported HW VP8 encoders.
Alex Glaznev269fe752016-05-25 16:17:33 -0700149 private static final MediaCodecProperties qcomVp8HwProperties = new MediaCodecProperties(
Alex Glaznevcfaca032016-09-06 14:08:19 -0700150 "OMX.qcom.", Build.VERSION_CODES.KITKAT, BitrateAdjustmentType.NO_ADJUSTMENT);
Alex Glaznev269fe752016-05-25 16:17:33 -0700151 private static final MediaCodecProperties exynosVp8HwProperties = new MediaCodecProperties(
Alex Glaznevcfaca032016-09-06 14:08:19 -0700152 "OMX.Exynos.", Build.VERSION_CODES.M, BitrateAdjustmentType.DYNAMIC_ADJUSTMENT);
magjed295760d2017-01-12 01:11:57 -0800153 private static final MediaCodecProperties intelVp8HwProperties = new MediaCodecProperties(
154 "OMX.Intel.", Build.VERSION_CODES.LOLLIPOP, BitrateAdjustmentType.NO_ADJUSTMENT);
155 private static MediaCodecProperties[] vp8HwList() {
156 final ArrayList<MediaCodecProperties> supported_codecs = new ArrayList<MediaCodecProperties>();
157 supported_codecs.add(qcomVp8HwProperties);
158 supported_codecs.add(exynosVp8HwProperties);
159 if (PeerConnectionFactory.fieldTrialsFindFullName("WebRTC-IntelVP8").equals("Enabled")) {
160 supported_codecs.add(intelVp8HwProperties);
161 }
162 return supported_codecs.toArray(new MediaCodecProperties[supported_codecs.size()]);
163 }
Alex Glaznev269fe752016-05-25 16:17:33 -0700164
Alex Glaznevdcd730f2016-04-21 17:01:46 -0700165 // List of supported HW VP9 encoders.
Alex Glaznev269fe752016-05-25 16:17:33 -0700166 private static final MediaCodecProperties qcomVp9HwProperties = new MediaCodecProperties(
glaznev6fac4292017-06-02 20:18:54 -0700167 "OMX.qcom.", Build.VERSION_CODES.N, BitrateAdjustmentType.NO_ADJUSTMENT);
Alex Glaznev269fe752016-05-25 16:17:33 -0700168 private static final MediaCodecProperties exynosVp9HwProperties = new MediaCodecProperties(
glaznev6fac4292017-06-02 20:18:54 -0700169 "OMX.Exynos.", Build.VERSION_CODES.N, BitrateAdjustmentType.FRAMERATE_ADJUSTMENT);
sakalb6760f92016-09-29 04:12:44 -0700170 private static final MediaCodecProperties[] vp9HwList =
171 new MediaCodecProperties[] {qcomVp9HwProperties, exynosVp9HwProperties};
Alex Glaznev269fe752016-05-25 16:17:33 -0700172
Alex Glaznevdcd730f2016-04-21 17:01:46 -0700173 // List of supported HW H.264 encoders.
Alex Glaznev269fe752016-05-25 16:17:33 -0700174 private static final MediaCodecProperties qcomH264HwProperties = new MediaCodecProperties(
Alex Glaznevcfaca032016-09-06 14:08:19 -0700175 "OMX.qcom.", Build.VERSION_CODES.KITKAT, BitrateAdjustmentType.NO_ADJUSTMENT);
Alex Glaznev269fe752016-05-25 16:17:33 -0700176 private static final MediaCodecProperties exynosH264HwProperties = new MediaCodecProperties(
Alex Glaznevcfaca032016-09-06 14:08:19 -0700177 "OMX.Exynos.", Build.VERSION_CODES.LOLLIPOP, BitrateAdjustmentType.FRAMERATE_ADJUSTMENT);
sakalb6760f92016-09-29 04:12:44 -0700178 private static final MediaCodecProperties[] h264HwList =
179 new MediaCodecProperties[] {qcomH264HwProperties, exynosH264HwProperties};
Alex Glaznev269fe752016-05-25 16:17:33 -0700180
glaznev3fc23502017-06-15 16:24:37 -0700181 // List of supported HW H.264 high profile encoders.
182 private static final MediaCodecProperties exynosH264HighProfileHwProperties =
183 new MediaCodecProperties(
184 "OMX.Exynos.", Build.VERSION_CODES.M, BitrateAdjustmentType.FRAMERATE_ADJUSTMENT);
185 private static final MediaCodecProperties[] h264HighProfileHwList =
186 new MediaCodecProperties[] {exynosH264HighProfileHwProperties};
187
Alex Glaznev0c850202015-08-04 10:26:59 -0700188 // List of devices with poor H.264 encoder quality.
sakalb6760f92016-09-29 04:12:44 -0700189 // HW H.264 encoder on below devices has poor bitrate control - actual
190 // bitrates deviates a lot from the target value.
191 private static final String[] H264_HW_EXCEPTION_MODELS =
192 new String[] {"SAMSUNG-SGH-I337", "Nexus 7", "Nexus 4"};
Alex Glaznev0c850202015-08-04 10:26:59 -0700193
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000194 // Bitrate modes - should be in sync with OMX_VIDEO_CONTROLRATETYPE defined
195 // in OMX_Video.h
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000196 private static final int VIDEO_ControlRateConstant = 2;
197 // NV12 color format supported by QCOM codec, but not declared in MediaCodec -
198 // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
sakalb6760f92016-09-29 04:12:44 -0700199 private static final int COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000200 // Allowable color formats supported by codec - in order of preference.
sakalb6760f92016-09-29 04:12:44 -0700201 private static final int[] supportedColorList = {CodecCapabilities.COLOR_FormatYUV420Planar,
202 CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
203 CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
204 COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m};
205 private static final int[] supportedSurfaceColorList = {CodecCapabilities.COLOR_FormatSurface};
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000206 private VideoCodecType type;
Magnus Jedvert655e1962017-12-08 11:05:22 +0100207 private int colorFormat;
Alex Glaznevcfaca032016-09-06 14:08:19 -0700208
209 // Variables used for dynamic bitrate adjustment.
210 private BitrateAdjustmentType bitrateAdjustmentType = BitrateAdjustmentType.NO_ADJUSTMENT;
211 private double bitrateAccumulator;
212 private double bitrateAccumulatorMax;
213 private double bitrateObservationTimeMs;
214 private int bitrateAdjustmentScaleExp;
215 private int targetBitrateBps;
216 private int targetFps;
perkj9576e542015-11-12 06:43:16 -0800217
Alex Glaznevc7483a72017-01-05 15:22:24 -0800218 // Interval in ms to force key frame generation. Used to reduce the time of color distortions
219 // happened sometime when using Qualcomm video encoder.
220 private long forcedKeyFrameMs;
221 private long lastKeyFrameMs;
222
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000223 // SPS and PPS NALs (Config frame) for H.264.
224 private ByteBuffer configData = null;
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000225
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700226 // MediaCodec error handler - invoked when critical error happens which may prevent
227 // further use of media codec API. Now it means that one of media codec instances
228 // is hanging and can no longer be used in the next call.
229 public static interface MediaCodecVideoEncoderErrorCallback {
230 void onMediaCodecVideoEncoderCriticalError(int codecErrors);
231 }
232
233 public static void setErrorCallback(MediaCodecVideoEncoderErrorCallback errorCallback) {
234 Logging.d(TAG, "Set error callback");
235 MediaCodecVideoEncoder.errorCallback = errorCallback;
236 }
237
Alex Glazneveee86a62016-01-29 14:17:07 -0800238 // Functions to disable HW encoding - can be called from applications for platforms
239 // which have known HW decoding problems.
240 public static void disableVp8HwCodec() {
241 Logging.w(TAG, "VP8 encoding is disabled by application.");
242 hwEncoderDisabledTypes.add(VP8_MIME_TYPE);
243 }
244
245 public static void disableVp9HwCodec() {
246 Logging.w(TAG, "VP9 encoding is disabled by application.");
247 hwEncoderDisabledTypes.add(VP9_MIME_TYPE);
248 }
249
250 public static void disableH264HwCodec() {
251 Logging.w(TAG, "H.264 encoding is disabled by application.");
252 hwEncoderDisabledTypes.add(H264_MIME_TYPE);
253 }
254
255 // Functions to query if HW encoding is supported.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100256 @CalledByNative
Alex Glazneveee86a62016-01-29 14:17:07 -0800257 public static boolean isVp8HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700258 return !hwEncoderDisabledTypes.contains(VP8_MIME_TYPE)
magjed295760d2017-01-12 01:11:57 -0800259 && (findHwEncoder(VP8_MIME_TYPE, vp8HwList(), supportedColorList) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800260 }
261
Alex Glaznevc7483a72017-01-05 15:22:24 -0800262 public static EncoderProperties vp8HwEncoderProperties() {
263 if (hwEncoderDisabledTypes.contains(VP8_MIME_TYPE)) {
264 return null;
265 } else {
magjed295760d2017-01-12 01:11:57 -0800266 return findHwEncoder(VP8_MIME_TYPE, vp8HwList(), supportedColorList);
Alex Glaznevc7483a72017-01-05 15:22:24 -0800267 }
268 }
269
Magnus Jedvert655e1962017-12-08 11:05:22 +0100270 @CalledByNative
Alex Glazneveee86a62016-01-29 14:17:07 -0800271 public static boolean isVp9HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700272 return !hwEncoderDisabledTypes.contains(VP9_MIME_TYPE)
273 && (findHwEncoder(VP9_MIME_TYPE, vp9HwList, supportedColorList) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800274 }
275
Magnus Jedvert655e1962017-12-08 11:05:22 +0100276 @CalledByNative
Alex Glazneveee86a62016-01-29 14:17:07 -0800277 public static boolean isH264HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700278 return !hwEncoderDisabledTypes.contains(H264_MIME_TYPE)
279 && (findHwEncoder(H264_MIME_TYPE, h264HwList, supportedColorList) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800280 }
281
glaznev3fc23502017-06-15 16:24:37 -0700282 public static boolean isH264HighProfileHwSupported() {
283 return !hwEncoderDisabledTypes.contains(H264_MIME_TYPE)
284 && (findHwEncoder(H264_MIME_TYPE, h264HighProfileHwList, supportedColorList) != null);
285 }
286
Alex Glazneveee86a62016-01-29 14:17:07 -0800287 public static boolean isVp8HwSupportedUsingTextures() {
sakalb6760f92016-09-29 04:12:44 -0700288 return !hwEncoderDisabledTypes.contains(VP8_MIME_TYPE)
magjed295760d2017-01-12 01:11:57 -0800289 && (findHwEncoder(VP8_MIME_TYPE, vp8HwList(), supportedSurfaceColorList) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800290 }
291
292 public static boolean isVp9HwSupportedUsingTextures() {
sakalb6760f92016-09-29 04:12:44 -0700293 return !hwEncoderDisabledTypes.contains(VP9_MIME_TYPE)
294 && (findHwEncoder(VP9_MIME_TYPE, vp9HwList, supportedSurfaceColorList) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800295 }
296
297 public static boolean isH264HwSupportedUsingTextures() {
sakalb6760f92016-09-29 04:12:44 -0700298 return !hwEncoderDisabledTypes.contains(H264_MIME_TYPE)
299 && (findHwEncoder(H264_MIME_TYPE, h264HwList, supportedSurfaceColorList) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800300 }
301
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000302 // Helper struct for findHwEncoder() below.
Alex Glaznevc7483a72017-01-05 15:22:24 -0800303 public static class EncoderProperties {
Alex Glaznevcfaca032016-09-06 14:08:19 -0700304 public EncoderProperties(
305 String codecName, int colorFormat, BitrateAdjustmentType bitrateAdjustmentType) {
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000306 this.codecName = codecName;
307 this.colorFormat = colorFormat;
Alex Glaznevcfaca032016-09-06 14:08:19 -0700308 this.bitrateAdjustmentType = bitrateAdjustmentType;
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000309 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000310 public final String codecName; // OpenMax component name for HW codec.
sakalb6760f92016-09-29 04:12:44 -0700311 public final int colorFormat; // Color format supported by codec.
Alex Glaznevcfaca032016-09-06 14:08:19 -0700312 public final BitrateAdjustmentType bitrateAdjustmentType; // Bitrate adjustment type
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000313 }
314
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000315 private static EncoderProperties findHwEncoder(
Alex Glaznev269fe752016-05-25 16:17:33 -0700316 String mime, MediaCodecProperties[] supportedHwCodecProperties, int[] colorList) {
Alex Glaznev0c850202015-08-04 10:26:59 -0700317 // MediaCodec.setParameters is missing for JB and below, so bitrate
318 // can not be adjusted dynamically.
319 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
320 return null;
321 }
322
323 // Check if device is in H.264 exception list.
324 if (mime.equals(H264_MIME_TYPE)) {
325 List<String> exceptionModels = Arrays.asList(H264_HW_EXCEPTION_MODELS);
326 if (exceptionModels.contains(Build.MODEL)) {
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700327 Logging.w(TAG, "Model: " + Build.MODEL + " has black listed H.264 encoder.");
Alex Glaznev0c850202015-08-04 10:26:59 -0700328 return null;
329 }
330 }
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000331
332 for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
Alex Glaznev0060c562016-08-08 12:27:24 -0700333 MediaCodecInfo info = null;
334 try {
335 info = MediaCodecList.getCodecInfoAt(i);
336 } catch (IllegalArgumentException e) {
sakalb6760f92016-09-29 04:12:44 -0700337 Logging.e(TAG, "Cannot retrieve encoder codec info", e);
Alex Glaznev0060c562016-08-08 12:27:24 -0700338 }
339 if (info == null || !info.isEncoder()) {
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000340 continue;
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000341 }
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000342 String name = null;
343 for (String mimeType : info.getSupportedTypes()) {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000344 if (mimeType.equals(mime)) {
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000345 name = info.getName();
346 break;
347 }
348 }
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000349 if (name == null) {
sakalb6760f92016-09-29 04:12:44 -0700350 continue; // No HW support in this codec; try the next one.
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000351 }
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700352 Logging.v(TAG, "Found candidate encoder " + name);
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000353
354 // Check if this is supported HW encoder.
355 boolean supportedCodec = false;
Alex Glaznevcfaca032016-09-06 14:08:19 -0700356 BitrateAdjustmentType bitrateAdjustmentType = BitrateAdjustmentType.NO_ADJUSTMENT;
Alex Glaznev269fe752016-05-25 16:17:33 -0700357 for (MediaCodecProperties codecProperties : supportedHwCodecProperties) {
358 if (name.startsWith(codecProperties.codecPrefix)) {
359 if (Build.VERSION.SDK_INT < codecProperties.minSdk) {
sakalb6760f92016-09-29 04:12:44 -0700360 Logging.w(
361 TAG, "Codec " + name + " is disabled due to SDK version " + Build.VERSION.SDK_INT);
Alex Glaznev269fe752016-05-25 16:17:33 -0700362 continue;
363 }
Alex Glaznevcfaca032016-09-06 14:08:19 -0700364 if (codecProperties.bitrateAdjustmentType != BitrateAdjustmentType.NO_ADJUSTMENT) {
365 bitrateAdjustmentType = codecProperties.bitrateAdjustmentType;
sakalb6760f92016-09-29 04:12:44 -0700366 Logging.w(
367 TAG, "Codec " + name + " requires bitrate adjustment: " + bitrateAdjustmentType);
Alex Glaznev269fe752016-05-25 16:17:33 -0700368 }
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000369 supportedCodec = true;
370 break;
371 }
372 }
373 if (!supportedCodec) {
374 continue;
375 }
376
Alex Glaznev269fe752016-05-25 16:17:33 -0700377 // Check if HW codec supports known color format.
Alex Glaznev0060c562016-08-08 12:27:24 -0700378 CodecCapabilities capabilities;
379 try {
380 capabilities = info.getCapabilitiesForType(mime);
381 } catch (IllegalArgumentException e) {
sakalb6760f92016-09-29 04:12:44 -0700382 Logging.e(TAG, "Cannot retrieve encoder capabilities", e);
Alex Glaznev0060c562016-08-08 12:27:24 -0700383 continue;
384 }
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000385 for (int colorFormat : capabilities.colorFormats) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700386 Logging.v(TAG, " Color: 0x" + Integer.toHexString(colorFormat));
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000387 }
388
perkj30e91822015-11-20 01:31:25 -0800389 for (int supportedColorFormat : colorList) {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000390 for (int codecColorFormat : capabilities.colorFormats) {
391 if (codecColorFormat == supportedColorFormat) {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000392 // Found supported HW encoder.
sakalb6760f92016-09-29 04:12:44 -0700393 Logging.d(TAG, "Found target encoder for mime " + mime + " : " + name + ". Color: 0x"
394 + Integer.toHexString(codecColorFormat) + ". Bitrate adjustment: "
395 + bitrateAdjustmentType);
Alex Glaznevcfaca032016-09-06 14:08:19 -0700396 return new EncoderProperties(name, codecColorFormat, bitrateAdjustmentType);
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000397 }
398 }
399 }
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000400 }
sakalb6760f92016-09-29 04:12:44 -0700401 return null; // No HW encoder.
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000402 }
403
Magnus Jedvert655e1962017-12-08 11:05:22 +0100404 @CalledByNative
405 MediaCodecVideoEncoder() {}
406
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000407 private void checkOnMediaCodecThread() {
408 if (mediaCodecThread.getId() != Thread.currentThread().getId()) {
sakalb6760f92016-09-29 04:12:44 -0700409 throw new RuntimeException("MediaCodecVideoEncoder previously operated on " + mediaCodecThread
410 + " but is now called on " + Thread.currentThread());
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000411 }
412 }
413
Alex Glaznev325d4142015-10-12 14:56:02 -0700414 public static void printStackTrace() {
Alex Glaznevc6aec4b2015-10-19 16:39:19 -0700415 if (runningInstance != null && runningInstance.mediaCodecThread != null) {
416 StackTraceElement[] mediaCodecStackTraces = runningInstance.mediaCodecThread.getStackTrace();
Alex Glaznev325d4142015-10-12 14:56:02 -0700417 if (mediaCodecStackTraces.length > 0) {
418 Logging.d(TAG, "MediaCodecVideoEncoder stacks trace:");
419 for (StackTraceElement stackTrace : mediaCodecStackTraces) {
420 Logging.d(TAG, stackTrace.toString());
421 }
422 }
423 }
424 }
425
henrike@webrtc.org528fc652014-10-06 17:56:43 +0000426 static MediaCodec createByCodecName(String codecName) {
427 try {
428 // In the L-SDK this call can throw IOException so in order to work in
429 // both cases catch an exception.
430 return MediaCodec.createByCodecName(codecName);
431 } catch (Exception e) {
432 return null;
433 }
434 }
435
Magnus Jedvert655e1962017-12-08 11:05:22 +0100436 @CalledByNativeUnchecked
glaznev3fc23502017-06-15 16:24:37 -0700437 boolean initEncode(VideoCodecType type, int profile, int width, int height, int kbps, int fps,
perkj48477c12015-12-18 00:34:37 -0800438 EglBase14.Context sharedContext) {
perkj30e91822015-11-20 01:31:25 -0800439 final boolean useSurface = sharedContext != null;
glaznev3fc23502017-06-15 16:24:37 -0700440 Logging.d(TAG,
441 "Java initEncode: " + type + ". Profile: " + profile + " : " + width + " x " + height
442 + ". @ " + kbps + " kbps. Fps: " + fps + ". Encode from texture : " + useSurface);
perkj9576e542015-11-12 06:43:16 -0800443
glaznev3fc23502017-06-15 16:24:37 -0700444 this.profile = profile;
Magnus Jedvert51254332015-12-15 16:22:29 +0100445 this.width = width;
446 this.height = height;
Alejandro Luebs69ddaef2015-10-09 15:46:09 -0700447 if (mediaCodecThread != null) {
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000448 throw new RuntimeException("Forgot to release()?");
449 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000450 EncoderProperties properties = null;
451 String mime = null;
452 int keyFrameIntervalSec = 0;
glaznev3fc23502017-06-15 16:24:37 -0700453 boolean configureH264HighProfile = false;
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000454 if (type == VideoCodecType.VIDEO_CODEC_VP8) {
455 mime = VP8_MIME_TYPE;
Alex Glaznev269fe752016-05-25 16:17:33 -0700456 properties = findHwEncoder(
magjed295760d2017-01-12 01:11:57 -0800457 VP8_MIME_TYPE, vp8HwList(), useSurface ? supportedSurfaceColorList : supportedColorList);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000458 keyFrameIntervalSec = 100;
Alex Glaznevad948c42015-11-18 13:06:42 -0800459 } else if (type == VideoCodecType.VIDEO_CODEC_VP9) {
460 mime = VP9_MIME_TYPE;
Alex Glaznev269fe752016-05-25 16:17:33 -0700461 properties = findHwEncoder(
462 VP9_MIME_TYPE, vp9HwList, useSurface ? supportedSurfaceColorList : supportedColorList);
Alex Glaznevad948c42015-11-18 13:06:42 -0800463 keyFrameIntervalSec = 100;
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000464 } else if (type == VideoCodecType.VIDEO_CODEC_H264) {
465 mime = H264_MIME_TYPE;
Alex Glaznev269fe752016-05-25 16:17:33 -0700466 properties = findHwEncoder(
467 H264_MIME_TYPE, h264HwList, useSurface ? supportedSurfaceColorList : supportedColorList);
glaznev3fc23502017-06-15 16:24:37 -0700468 if (profile == H264Profile.CONSTRAINED_HIGH.getValue()) {
469 EncoderProperties h264HighProfileProperties = findHwEncoder(H264_MIME_TYPE,
470 h264HighProfileHwList, useSurface ? supportedSurfaceColorList : supportedColorList);
471 if (h264HighProfileProperties != null) {
472 Logging.d(TAG, "High profile H.264 encoder supported.");
473 configureH264HighProfile = true;
474 } else {
475 Logging.d(TAG, "High profile H.264 encoder requested, but not supported. Use baseline.");
476 }
477 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000478 keyFrameIntervalSec = 20;
479 }
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000480 if (properties == null) {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000481 throw new RuntimeException("Can not find HW encoder for " + type);
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000482 }
Alex Glaznevc6aec4b2015-10-19 16:39:19 -0700483 runningInstance = this; // Encoder is now running and can be queried for stack traces.
perkj9576e542015-11-12 06:43:16 -0800484 colorFormat = properties.colorFormat;
Alex Glaznevcfaca032016-09-06 14:08:19 -0700485 bitrateAdjustmentType = properties.bitrateAdjustmentType;
486 if (bitrateAdjustmentType == BitrateAdjustmentType.FRAMERATE_ADJUSTMENT) {
Alex Glaznev269fe752016-05-25 16:17:33 -0700487 fps = BITRATE_ADJUSTMENT_FPS;
sakalb6760f92016-09-29 04:12:44 -0700488 } else {
Alex Glaznevc55c39d2016-09-02 12:16:27 -0700489 fps = Math.min(fps, MAXIMUM_INITIAL_FPS);
Alex Glaznev269fe752016-05-25 16:17:33 -0700490 }
Alex Glaznevc7483a72017-01-05 15:22:24 -0800491
492 forcedKeyFrameMs = 0;
493 lastKeyFrameMs = -1;
Alex Glaznev512f00b2017-01-05 16:40:46 -0800494 if (type == VideoCodecType.VIDEO_CODEC_VP8
495 && properties.codecName.startsWith(qcomVp8HwProperties.codecPrefix)) {
alexlau84ee5c62017-06-02 17:36:32 -0700496 if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP
497 || Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP_MR1) {
498 forcedKeyFrameMs = QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_L_MS;
499 } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.M) {
Alex Glaznevc7483a72017-01-05 15:22:24 -0800500 forcedKeyFrameMs = QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_M_MS;
501 } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
502 forcedKeyFrameMs = QCOM_VP8_KEY_FRAME_INTERVAL_ANDROID_N_MS;
503 }
504 }
505
sakalb6760f92016-09-29 04:12:44 -0700506 Logging.d(TAG, "Color format: " + colorFormat + ". Bitrate adjustment: " + bitrateAdjustmentType
Alex Glaznevc7483a72017-01-05 15:22:24 -0800507 + ". Key frame interval: " + forcedKeyFrameMs + " . Initial fps: " + fps);
Alex Glaznevcfaca032016-09-06 14:08:19 -0700508 targetBitrateBps = 1000 * kbps;
509 targetFps = fps;
510 bitrateAccumulatorMax = targetBitrateBps / 8.0;
511 bitrateAccumulator = 0;
512 bitrateObservationTimeMs = 0;
513 bitrateAdjustmentScaleExp = 0;
perkj9576e542015-11-12 06:43:16 -0800514
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000515 mediaCodecThread = Thread.currentThread();
516 try {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000517 MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
Alex Glaznevcfaca032016-09-06 14:08:19 -0700518 format.setInteger(MediaFormat.KEY_BIT_RATE, targetBitrateBps);
Alex Glaznev8a2cd3d2015-08-11 11:32:53 -0700519 format.setInteger("bitrate-mode", VIDEO_ControlRateConstant);
glaznev@webrtc.orga40210a2014-06-10 23:48:29 +0000520 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
Alex Glaznevcfaca032016-09-06 14:08:19 -0700521 format.setInteger(MediaFormat.KEY_FRAME_RATE, targetFps);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000522 format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, keyFrameIntervalSec);
glaznev3fc23502017-06-15 16:24:37 -0700523 if (configureH264HighProfile) {
524 format.setInteger("profile", VIDEO_AVCProfileHigh);
525 format.setInteger("level", VIDEO_AVCLevel3);
526 }
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700527 Logging.d(TAG, " Format: " + format);
henrike@webrtc.org528fc652014-10-06 17:56:43 +0000528 mediaCodec = createByCodecName(properties.codecName);
perkj9576e542015-11-12 06:43:16 -0800529 this.type = type;
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000530 if (mediaCodec == null) {
Alex Glaznev325d4142015-10-12 14:56:02 -0700531 Logging.e(TAG, "Can not create media encoder");
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 }
sakalb6760f92016-09-29 04:12:44 -0700535 mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
perkj9576e542015-11-12 06:43:16 -0800536
perkj30e91822015-11-20 01:31:25 -0800537 if (useSurface) {
perkj48477c12015-12-18 00:34:37 -0800538 eglBase = new EglBase14(sharedContext, EglBase.CONFIG_RECORDABLE);
perkj30e91822015-11-20 01:31:25 -0800539 // Create an input surface and keep a reference since we must release the surface when done.
540 inputSurface = mediaCodec.createInputSurface();
541 eglBase.createSurface(inputSurface);
542 drawer = new GlRectDrawer();
543 }
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000544 mediaCodec.start();
545 outputBuffers = mediaCodec.getOutputBuffers();
perkj9576e542015-11-12 06:43:16 -0800546 Logging.d(TAG, "Output buffers: " + outputBuffers.length);
547
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000548 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700549 Logging.e(TAG, "initEncode failed", e);
sakal996a83c2017-03-15 05:53:14 -0700550 release();
perkj9576e542015-11-12 06:43:16 -0800551 return false;
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000552 }
perkj9576e542015-11-12 06:43:16 -0800553 return true;
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000554 }
555
Magnus Jedvert655e1962017-12-08 11:05:22 +0100556 @CalledByNativeUnchecked
sakalb6760f92016-09-29 04:12:44 -0700557 ByteBuffer[] getInputBuffers() {
perkj9576e542015-11-12 06:43:16 -0800558 ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
559 Logging.d(TAG, "Input buffers: " + inputBuffers.length);
560 return inputBuffers;
561 }
562
Alex Glaznevc7483a72017-01-05 15:22:24 -0800563 void checkKeyFrameRequired(boolean requestedKeyFrame, long presentationTimestampUs) {
564 long presentationTimestampMs = (presentationTimestampUs + 500) / 1000;
565 if (lastKeyFrameMs < 0) {
566 lastKeyFrameMs = presentationTimestampMs;
567 }
568 boolean forcedKeyFrame = false;
569 if (!requestedKeyFrame && forcedKeyFrameMs > 0
570 && presentationTimestampMs > lastKeyFrameMs + forcedKeyFrameMs) {
571 forcedKeyFrame = true;
572 }
573 if (requestedKeyFrame || forcedKeyFrame) {
574 // Ideally MediaCodec would honor BUFFER_FLAG_SYNC_FRAME so we could
575 // indicate this in queueInputBuffer() below and guarantee _this_ frame
576 // be encoded as a key frame, but sadly that flag is ignored. Instead,
577 // we request a key frame "soon".
578 if (requestedKeyFrame) {
579 Logging.d(TAG, "Sync frame request");
580 } else {
581 Logging.d(TAG, "Sync frame forced");
582 }
583 Bundle b = new Bundle();
584 b.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
585 mediaCodec.setParameters(b);
586 lastKeyFrameMs = presentationTimestampMs;
587 }
588 }
589
Magnus Jedvert655e1962017-12-08 11:05:22 +0100590 @CalledByNativeUnchecked
perkj9576e542015-11-12 06:43:16 -0800591 boolean encodeBuffer(
sakalb6760f92016-09-29 04:12:44 -0700592 boolean isKeyframe, int inputBuffer, int size, long presentationTimestampUs) {
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000593 checkOnMediaCodecThread();
594 try {
Alex Glaznevc7483a72017-01-05 15:22:24 -0800595 checkKeyFrameRequired(isKeyframe, presentationTimestampUs);
sakalb6760f92016-09-29 04:12:44 -0700596 mediaCodec.queueInputBuffer(inputBuffer, 0, size, presentationTimestampUs, 0);
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000597 return true;
sakalb6760f92016-09-29 04:12:44 -0700598 } catch (IllegalStateException e) {
perkj9576e542015-11-12 06:43:16 -0800599 Logging.e(TAG, "encodeBuffer failed", e);
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000600 return false;
601 }
602 }
603
Magnus Jedvert655e1962017-12-08 11:05:22 +0100604 @CalledByNativeUnchecked
perkj30e91822015-11-20 01:31:25 -0800605 boolean encodeTexture(boolean isKeyframe, int oesTextureId, float[] transformationMatrix,
606 long presentationTimestampUs) {
607 checkOnMediaCodecThread();
608 try {
Alex Glaznevc7483a72017-01-05 15:22:24 -0800609 checkKeyFrameRequired(isKeyframe, presentationTimestampUs);
perkj30e91822015-11-20 01:31:25 -0800610 eglBase.makeCurrent();
perkj226a6022015-12-02 02:24:40 -0800611 // TODO(perkj): glClear() shouldn't be necessary since every pixel is covered anyway,
612 // but it's a workaround for bug webrtc:5147.
613 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
ivoc1aa435c2016-05-04 07:14:14 -0700614 drawer.drawOes(oesTextureId, transformationMatrix, width, height, 0, 0, width, height);
perkj48477c12015-12-18 00:34:37 -0800615 eglBase.swapBuffers(TimeUnit.MICROSECONDS.toNanos(presentationTimestampUs));
perkj30e91822015-11-20 01:31:25 -0800616 return true;
sakalb6760f92016-09-29 04:12:44 -0700617 } catch (RuntimeException e) {
perkj30e91822015-11-20 01:31:25 -0800618 Logging.e(TAG, "encodeTexture failed", e);
619 return false;
620 }
621 }
622
sakalb5f5bdc2017-08-10 04:15:42 -0700623 /**
Magnus Jedvert655e1962017-12-08 11:05:22 +0100624 * Encodes a new style VideoFrame. |bufferIndex| is -1 if we are not encoding in surface mode.
sakalb5f5bdc2017-08-10 04:15:42 -0700625 */
Magnus Jedvert655e1962017-12-08 11:05:22 +0100626 @CalledByNativeUnchecked
sakalb5f5bdc2017-08-10 04:15:42 -0700627 boolean encodeFrame(long nativeEncoder, boolean isKeyframe, VideoFrame frame, int bufferIndex) {
628 checkOnMediaCodecThread();
629 try {
630 long presentationTimestampUs = TimeUnit.NANOSECONDS.toMicros(frame.getTimestampNs());
631 checkKeyFrameRequired(isKeyframe, presentationTimestampUs);
632
633 VideoFrame.Buffer buffer = frame.getBuffer();
634 if (buffer instanceof VideoFrame.TextureBuffer) {
635 VideoFrame.TextureBuffer textureBuffer = (VideoFrame.TextureBuffer) buffer;
636 eglBase.makeCurrent();
637 // TODO(perkj): glClear() shouldn't be necessary since every pixel is covered anyway,
638 // but it's a workaround for bug webrtc:5147.
639 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
magjed7cede372017-09-11 06:12:07 -0700640 VideoFrameDrawer.drawTexture(drawer, textureBuffer, new Matrix() /* renderMatrix */, width,
sakal9bc599f2017-09-08 04:46:33 -0700641 height, 0 /* viewportX */, 0 /* viewportY */, width, height);
sakalb5f5bdc2017-08-10 04:15:42 -0700642 eglBase.swapBuffers(frame.getTimestampNs());
643 } else {
644 VideoFrame.I420Buffer i420Buffer = buffer.toI420();
Sami Kalliomäkie3044fe2017-10-02 09:41:55 +0200645 final int chromaHeight = (height + 1) / 2;
646 final ByteBuffer dataY = i420Buffer.getDataY();
647 final ByteBuffer dataU = i420Buffer.getDataU();
648 final ByteBuffer dataV = i420Buffer.getDataV();
649 final int strideY = i420Buffer.getStrideY();
650 final int strideU = i420Buffer.getStrideU();
651 final int strideV = i420Buffer.getStrideV();
652 if (dataY.capacity() < strideY * height) {
653 throw new RuntimeException("Y-plane buffer size too small.");
654 }
655 if (dataU.capacity() < strideU * chromaHeight) {
656 throw new RuntimeException("U-plane buffer size too small.");
657 }
658 if (dataV.capacity() < strideV * chromaHeight) {
659 throw new RuntimeException("V-plane buffer size too small.");
660 }
Magnus Jedvert9060eb12017-12-12 12:52:54 +0100661 fillInputBufferNative(
Sami Kalliomäkie3044fe2017-10-02 09:41:55 +0200662 nativeEncoder, bufferIndex, dataY, strideY, dataU, strideU, dataV, strideV);
sakalb5f5bdc2017-08-10 04:15:42 -0700663 i420Buffer.release();
664 // I420 consists of one full-resolution and two half-resolution planes.
665 // 1 + 1 / 4 + 1 / 4 = 3 / 2
666 int yuvSize = width * height * 3 / 2;
667 mediaCodec.queueInputBuffer(bufferIndex, 0, yuvSize, presentationTimestampUs, 0);
668 }
669 return true;
670 } catch (RuntimeException e) {
671 Logging.e(TAG, "encodeFrame failed", e);
672 return false;
673 }
674 }
675
Magnus Jedvert655e1962017-12-08 11:05:22 +0100676 @CalledByNativeUnchecked
perkj9576e542015-11-12 06:43:16 -0800677 void release() {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700678 Logging.d(TAG, "Java releaseEncoder");
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000679 checkOnMediaCodecThread();
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700680
sakal996a83c2017-03-15 05:53:14 -0700681 class CaughtException {
682 Exception e;
683 }
684 final CaughtException caughtException = new CaughtException();
685 boolean stopHung = false;
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700686
sakal996a83c2017-03-15 05:53:14 -0700687 if (mediaCodec != null) {
688 // Run Mediacodec stop() and release() on separate thread since sometime
689 // Mediacodec.stop() may hang.
690 final CountDownLatch releaseDone = new CountDownLatch(1);
691
692 Runnable runMediaCodecRelease = new Runnable() {
693 @Override
694 public void run() {
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700695 Logging.d(TAG, "Java releaseEncoder on release thread");
sakal996a83c2017-03-15 05:53:14 -0700696 try {
697 mediaCodec.stop();
698 } catch (Exception e) {
699 Logging.e(TAG, "Media encoder stop failed", e);
700 }
701 try {
702 mediaCodec.release();
703 } catch (Exception e) {
704 Logging.e(TAG, "Media encoder release failed", e);
705 caughtException.e = e;
706 }
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700707 Logging.d(TAG, "Java releaseEncoder on release thread done");
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700708
sakal996a83c2017-03-15 05:53:14 -0700709 releaseDone.countDown();
710 }
711 };
712 new Thread(runMediaCodecRelease).start();
713
714 if (!ThreadUtils.awaitUninterruptibly(releaseDone, MEDIA_CODEC_RELEASE_TIMEOUT_MS)) {
715 Logging.e(TAG, "Media encoder release timeout");
716 stopHung = true;
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700717 }
sakal996a83c2017-03-15 05:53:14 -0700718
719 mediaCodec = null;
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000720 }
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700721
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000722 mediaCodecThread = null;
perkj30e91822015-11-20 01:31:25 -0800723 if (drawer != null) {
724 drawer.release();
725 drawer = null;
726 }
727 if (eglBase != null) {
728 eglBase.release();
729 eglBase = null;
730 }
731 if (inputSurface != null) {
732 inputSurface.release();
733 inputSurface = null;
734 }
Alex Glaznevc6aec4b2015-10-19 16:39:19 -0700735 runningInstance = null;
sakal996a83c2017-03-15 05:53:14 -0700736
737 if (stopHung) {
738 codecErrors++;
739 if (errorCallback != null) {
740 Logging.e(TAG, "Invoke codec error callback. Errors: " + codecErrors);
741 errorCallback.onMediaCodecVideoEncoderCriticalError(codecErrors);
742 }
743 throw new RuntimeException("Media encoder release timeout.");
744 }
745
746 // Re-throw any runtime exception caught inside the other thread. Since this is an invoke, add
747 // stack trace for the waiting thread as well.
748 if (caughtException.e != null) {
749 final RuntimeException runtimeException = new RuntimeException(caughtException.e);
750 runtimeException.setStackTrace(ThreadUtils.concatStackTraces(
751 caughtException.e.getStackTrace(), runtimeException.getStackTrace()));
752 throw runtimeException;
753 }
754
Alex Glaznev325d4142015-10-12 14:56:02 -0700755 Logging.d(TAG, "Java releaseEncoder done");
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000756 }
757
Magnus Jedvert655e1962017-12-08 11:05:22 +0100758 @CalledByNativeUnchecked
Alex Glaznev269fe752016-05-25 16:17:33 -0700759 private boolean setRates(int kbps, int frameRate) {
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000760 checkOnMediaCodecThread();
Alex Glaznevcfaca032016-09-06 14:08:19 -0700761
762 int codecBitrateBps = 1000 * kbps;
763 if (bitrateAdjustmentType == BitrateAdjustmentType.DYNAMIC_ADJUSTMENT) {
764 bitrateAccumulatorMax = codecBitrateBps / 8.0;
765 if (targetBitrateBps > 0 && codecBitrateBps < targetBitrateBps) {
766 // Rescale the accumulator level if the accumulator max decreases
767 bitrateAccumulator = bitrateAccumulator * codecBitrateBps / targetBitrateBps;
768 }
Alex Glaznev269fe752016-05-25 16:17:33 -0700769 }
Alex Glaznevcfaca032016-09-06 14:08:19 -0700770 targetBitrateBps = codecBitrateBps;
771 targetFps = frameRate;
772
773 // Adjust actual encoder bitrate based on bitrate adjustment type.
774 if (bitrateAdjustmentType == BitrateAdjustmentType.FRAMERATE_ADJUSTMENT && targetFps > 0) {
775 codecBitrateBps = BITRATE_ADJUSTMENT_FPS * targetBitrateBps / targetFps;
sakalb6760f92016-09-29 04:12:44 -0700776 Logging.v(TAG,
777 "setRates: " + kbps + " -> " + (codecBitrateBps / 1000) + " kbps. Fps: " + targetFps);
Alex Glaznevcfaca032016-09-06 14:08:19 -0700778 } else if (bitrateAdjustmentType == BitrateAdjustmentType.DYNAMIC_ADJUSTMENT) {
sakalb6760f92016-09-29 04:12:44 -0700779 Logging.v(TAG, "setRates: " + kbps + " kbps. Fps: " + targetFps + ". ExpScale: "
780 + bitrateAdjustmentScaleExp);
Alex Glaznevcfaca032016-09-06 14:08:19 -0700781 if (bitrateAdjustmentScaleExp != 0) {
sakalb6760f92016-09-29 04:12:44 -0700782 codecBitrateBps = (int) (codecBitrateBps * getBitrateScale(bitrateAdjustmentScaleExp));
Alex Glaznevcfaca032016-09-06 14:08:19 -0700783 }
784 } else {
785 Logging.v(TAG, "setRates: " + kbps + " kbps. Fps: " + targetFps);
786 }
787
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000788 try {
789 Bundle params = new Bundle();
Alex Glaznevcfaca032016-09-06 14:08:19 -0700790 params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, codecBitrateBps);
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000791 mediaCodec.setParameters(params);
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000792 return true;
793 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700794 Logging.e(TAG, "setRates failed", e);
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000795 return false;
796 }
797 }
798
799 // Dequeue an input buffer and return its index, -1 if no input buffer is
800 // available, or -2 if the codec is no longer operative.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100801 @CalledByNativeUnchecked
perkj9576e542015-11-12 06:43:16 -0800802 int dequeueInputBuffer() {
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000803 checkOnMediaCodecThread();
804 try {
805 return mediaCodec.dequeueInputBuffer(DEQUEUE_TIMEOUT);
806 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700807 Logging.e(TAG, "dequeueIntputBuffer failed", e);
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000808 return -2;
809 }
810 }
811
812 // Helper struct for dequeueOutputBuffer() below.
perkj9576e542015-11-12 06:43:16 -0800813 static class OutputBufferInfo {
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000814 public OutputBufferInfo(
sakalb6760f92016-09-29 04:12:44 -0700815 int index, ByteBuffer buffer, boolean isKeyFrame, long presentationTimestampUs) {
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000816 this.index = index;
817 this.buffer = buffer;
818 this.isKeyFrame = isKeyFrame;
819 this.presentationTimestampUs = presentationTimestampUs;
820 }
821
perkj9576e542015-11-12 06:43:16 -0800822 public final int index;
823 public final ByteBuffer buffer;
824 public final boolean isKeyFrame;
825 public final long presentationTimestampUs;
Magnus Jedvert655e1962017-12-08 11:05:22 +0100826
827 @CalledByNative("OutputBufferInfo")
828 int getIndex() {
829 return index;
830 }
831
832 @CalledByNative("OutputBufferInfo")
833 ByteBuffer getBuffer() {
834 return buffer;
835 }
836
837 @CalledByNative("OutputBufferInfo")
838 boolean isKeyFrame() {
839 return isKeyFrame;
840 }
841
842 @CalledByNative("OutputBufferInfo")
843 long getPresentationTimestampUs() {
844 return presentationTimestampUs;
845 }
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000846 }
847
848 // Dequeue and return an output buffer, or null if no output is ready. Return
849 // a fake OutputBufferInfo with index -1 if the codec is no longer operable.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100850 @CalledByNativeUnchecked
perkj9576e542015-11-12 06:43:16 -0800851 OutputBufferInfo dequeueOutputBuffer() {
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000852 checkOnMediaCodecThread();
853 try {
854 MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
855 int result = mediaCodec.dequeueOutputBuffer(info, DEQUEUE_TIMEOUT);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000856 // Check if this is config frame and save configuration data.
857 if (result >= 0) {
sakalb6760f92016-09-29 04:12:44 -0700858 boolean isConfigFrame = (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0;
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000859 if (isConfigFrame) {
sakalb6760f92016-09-29 04:12:44 -0700860 Logging.d(TAG, "Config frame generated. Offset: " + info.offset + ". Size: " + info.size);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000861 configData = ByteBuffer.allocateDirect(info.size);
862 outputBuffers[result].position(info.offset);
863 outputBuffers[result].limit(info.offset + info.size);
864 configData.put(outputBuffers[result]);
glaznev3fc23502017-06-15 16:24:37 -0700865 // Log few SPS header bytes to check profile and level.
866 String spsData = "";
867 for (int i = 0; i < (info.size < 8 ? info.size : 8); i++) {
868 spsData += Integer.toHexString(configData.get(i) & 0xff) + " ";
869 }
870 Logging.d(TAG, spsData);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000871 // Release buffer back.
872 mediaCodec.releaseOutputBuffer(result, false);
873 // Query next output.
874 result = mediaCodec.dequeueOutputBuffer(info, DEQUEUE_TIMEOUT);
875 }
876 }
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000877 if (result >= 0) {
878 // MediaCodec doesn't care about Buffer position/remaining/etc so we can
879 // mess with them to get a slice and avoid having to pass extra
880 // (BufferInfo-related) parameters back to C++.
881 ByteBuffer outputBuffer = outputBuffers[result].duplicate();
882 outputBuffer.position(info.offset);
883 outputBuffer.limit(info.offset + info.size);
Alex Glaznevcfaca032016-09-06 14:08:19 -0700884 reportEncodedFrame(info.size);
885
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000886 // Check key frame flag.
sakalb6760f92016-09-29 04:12:44 -0700887 boolean isKeyFrame = (info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
glaznev@webrtc.orgc6c1dfd2014-06-13 22:59:08 +0000888 if (isKeyFrame) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700889 Logging.d(TAG, "Sync frame generated");
glaznev@webrtc.orgc6c1dfd2014-06-13 22:59:08 +0000890 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000891 if (isKeyFrame && type == VideoCodecType.VIDEO_CODEC_H264) {
sakalb6760f92016-09-29 04:12:44 -0700892 Logging.d(TAG, "Appending config frame of size " + configData.capacity()
893 + " to output buffer with offset " + info.offset + ", size " + info.size);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000894 // For H.264 key frame append SPS and PPS NALs at the start
sakalb6760f92016-09-29 04:12:44 -0700895 ByteBuffer keyFrameBuffer = ByteBuffer.allocateDirect(configData.capacity() + info.size);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000896 configData.rewind();
897 keyFrameBuffer.put(configData);
898 keyFrameBuffer.put(outputBuffer);
899 keyFrameBuffer.position(0);
sakalb6760f92016-09-29 04:12:44 -0700900 return new OutputBufferInfo(result, keyFrameBuffer, isKeyFrame, info.presentationTimeUs);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000901 } else {
sakalb6760f92016-09-29 04:12:44 -0700902 return new OutputBufferInfo(
903 result, outputBuffer.slice(), isKeyFrame, info.presentationTimeUs);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000904 }
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000905 } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
906 outputBuffers = mediaCodec.getOutputBuffers();
907 return dequeueOutputBuffer();
908 } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
909 return dequeueOutputBuffer();
910 } else if (result == MediaCodec.INFO_TRY_AGAIN_LATER) {
911 return null;
912 }
913 throw new RuntimeException("dequeueOutputBuffer: " + result);
914 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700915 Logging.e(TAG, "dequeueOutputBuffer failed", e);
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000916 return new OutputBufferInfo(-1, null, false, -1);
917 }
918 }
919
Alex Glaznevcfaca032016-09-06 14:08:19 -0700920 private double getBitrateScale(int bitrateAdjustmentScaleExp) {
921 return Math.pow(BITRATE_CORRECTION_MAX_SCALE,
sakalb6760f92016-09-29 04:12:44 -0700922 (double) bitrateAdjustmentScaleExp / BITRATE_CORRECTION_STEPS);
Alex Glaznevcfaca032016-09-06 14:08:19 -0700923 }
924
925 private void reportEncodedFrame(int size) {
926 if (targetFps == 0 || bitrateAdjustmentType != BitrateAdjustmentType.DYNAMIC_ADJUSTMENT) {
927 return;
928 }
929
930 // Accumulate the difference between actial and expected frame sizes.
931 double expectedBytesPerFrame = targetBitrateBps / (8.0 * targetFps);
932 bitrateAccumulator += (size - expectedBytesPerFrame);
933 bitrateObservationTimeMs += 1000.0 / targetFps;
934
935 // Put a cap on the accumulator, i.e., don't let it grow beyond some level to avoid
936 // using too old data for bitrate adjustment.
937 double bitrateAccumulatorCap = BITRATE_CORRECTION_SEC * bitrateAccumulatorMax;
938 bitrateAccumulator = Math.min(bitrateAccumulator, bitrateAccumulatorCap);
939 bitrateAccumulator = Math.max(bitrateAccumulator, -bitrateAccumulatorCap);
940
941 // Do bitrate adjustment every 3 seconds if actual encoder bitrate deviates too much
942 // form the target value.
943 if (bitrateObservationTimeMs > 1000 * BITRATE_CORRECTION_SEC) {
sakalb6760f92016-09-29 04:12:44 -0700944 Logging.d(TAG, "Acc: " + (int) bitrateAccumulator + ". Max: " + (int) bitrateAccumulatorMax
945 + ". ExpScale: " + bitrateAdjustmentScaleExp);
Alex Glaznevcfaca032016-09-06 14:08:19 -0700946 boolean bitrateAdjustmentScaleChanged = false;
947 if (bitrateAccumulator > bitrateAccumulatorMax) {
948 // Encoder generates too high bitrate - need to reduce the scale.
Alex Glaznev7fa4a722017-01-17 15:32:02 -0800949 int bitrateAdjustmentInc = (int) (bitrateAccumulator / bitrateAccumulatorMax + 0.5);
950 bitrateAdjustmentScaleExp -= bitrateAdjustmentInc;
Alex Glaznevcfaca032016-09-06 14:08:19 -0700951 bitrateAccumulator = bitrateAccumulatorMax;
Alex Glaznevcfaca032016-09-06 14:08:19 -0700952 bitrateAdjustmentScaleChanged = true;
953 } else if (bitrateAccumulator < -bitrateAccumulatorMax) {
954 // Encoder generates too low bitrate - need to increase the scale.
Alex Glaznev7fa4a722017-01-17 15:32:02 -0800955 int bitrateAdjustmentInc = (int) (-bitrateAccumulator / bitrateAccumulatorMax + 0.5);
956 bitrateAdjustmentScaleExp += bitrateAdjustmentInc;
Alex Glaznevcfaca032016-09-06 14:08:19 -0700957 bitrateAccumulator = -bitrateAccumulatorMax;
958 bitrateAdjustmentScaleChanged = true;
959 }
960 if (bitrateAdjustmentScaleChanged) {
961 bitrateAdjustmentScaleExp = Math.min(bitrateAdjustmentScaleExp, BITRATE_CORRECTION_STEPS);
962 bitrateAdjustmentScaleExp = Math.max(bitrateAdjustmentScaleExp, -BITRATE_CORRECTION_STEPS);
sakalb6760f92016-09-29 04:12:44 -0700963 Logging.d(TAG, "Adjusting bitrate scale to " + bitrateAdjustmentScaleExp + ". Value: "
964 + getBitrateScale(bitrateAdjustmentScaleExp));
Alex Glaznevcfaca032016-09-06 14:08:19 -0700965 setRates(targetBitrateBps / 1000, targetFps);
966 }
967 bitrateObservationTimeMs = 0;
968 }
969 }
970
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000971 // Release a dequeued output buffer back to the codec for re-use. Return
972 // false if the codec is no longer operable.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100973 @CalledByNativeUnchecked
perkj9576e542015-11-12 06:43:16 -0800974 boolean releaseOutputBuffer(int index) {
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000975 checkOnMediaCodecThread();
976 try {
977 mediaCodec.releaseOutputBuffer(index, false);
978 return true;
979 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700980 Logging.e(TAG, "releaseOutputBuffer failed", e);
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000981 return false;
982 }
983 }
sakalb5f5bdc2017-08-10 04:15:42 -0700984
Magnus Jedvert655e1962017-12-08 11:05:22 +0100985 @CalledByNative
986 int getColorFormat() {
987 return colorFormat;
988 }
989
990 @CalledByNative
991 static boolean isTextureBuffer(VideoFrame.Buffer buffer) {
992 return buffer instanceof VideoFrame.TextureBuffer;
993 }
994
sakalb5f5bdc2017-08-10 04:15:42 -0700995 /** Fills an inputBuffer with the given index with data from the byte buffers. */
Magnus Jedvert9060eb12017-12-12 12:52:54 +0100996 private static native void fillInputBufferNative(long nativeEncoder, int inputBuffer,
997 ByteBuffer dataY, int strideY, ByteBuffer dataU, int strideU, ByteBuffer dataV, int strideV);
fischman@webrtc.org540acde2014-02-13 03:56:14 +0000998}