blob: cc014fa98814e7ffd1055ed0373b477e7509964d [file] [log] [blame]
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +00001/*
kjellanderb24317b2016-02-10 07:54:43 -08002 * Copyright 2014 The WebRTC project authors. All Rights Reserved.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +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.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +00009 */
10
11package org.webrtc;
12
13import android.media.MediaCodec;
buildbot@webrtc.orga09a9992014-08-13 17:26:08 +000014import android.media.MediaCodecInfo;
glaznev@webrtc.org99678452014-09-15 17:52:42 +000015import android.media.MediaCodecInfo.CodecCapabilities;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000016import android.media.MediaCodecList;
17import android.media.MediaFormat;
18import android.os.Build;
Per488e75f2015-11-19 10:43:36 +010019import android.os.SystemClock;
glaznev@webrtc.org99678452014-09-15 17:52:42 +000020import android.view.Surface;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000021import java.nio.ByteBuffer;
Magnus Jedvert6062f372017-11-16 16:53:12 +010022import java.util.ArrayDeque;
Alex Leung5b6891a2018-01-18 10:01:14 -080023import java.util.ArrayList;
Magnus Jedvert7e319372015-10-02 15:49:38 +020024import java.util.Arrays;
Alex Glazneveee86a62016-01-29 14:17:07 -080025import java.util.HashSet;
Magnus Jedvert91b348c2015-10-07 22:57:06 +020026import java.util.List;
Sami Kalliomakid3235f02016-08-02 15:44:04 +020027import java.util.Queue;
Alex Glazneveee86a62016-01-29 14:17:07 -080028import java.util.Set;
Alex Glaznev5c3da4b2015-10-30 15:31:07 -070029import java.util.concurrent.CountDownLatch;
Per488e75f2015-11-19 10:43:36 +010030import java.util.concurrent.TimeUnit;
magjed8c425aa2015-10-22 16:52:39 -070031
Magnus Jedvert9060eb12017-12-12 12:52:54 +010032// Java-side of peerconnection.cc:MediaCodecVideoDecoder.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000033// This class is an implementation detail of the Java PeerConnection API.
Patrik Höglund68876f92015-11-12 17:36:48 +010034@SuppressWarnings("deprecation")
Alex Glaznev908e77b2015-04-22 09:25:34 -070035public class MediaCodecVideoDecoder {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000036 // This class is constructed, operated, and destroyed by its C++ incarnation,
37 // so the class and its methods have non-public visibility. The API this
38 // class exposes aims to mimic the webrtc::VideoDecoder API as closely as
39 // possibly to minimize the amount of translation work necessary.
40
41 private static final String TAG = "MediaCodecVideoDecoder";
magjedcedddbd2016-02-26 09:36:04 -080042 private static final long MAX_DECODE_TIME_MS = 200;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000043
magjed0c29de52017-03-02 00:55:32 -080044 // TODO(magjed): Use MediaFormat constants when part of the public API.
45 private static final String FORMAT_KEY_STRIDE = "stride";
46 private static final String FORMAT_KEY_SLICE_HEIGHT = "slice-height";
47 private static final String FORMAT_KEY_CROP_LEFT = "crop-left";
48 private static final String FORMAT_KEY_CROP_RIGHT = "crop-right";
49 private static final String FORMAT_KEY_CROP_TOP = "crop-top";
50 private static final String FORMAT_KEY_CROP_BOTTOM = "crop-bottom";
51
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000052 // Tracks webrtc::VideoCodecType.
Magnus Jedvert655e1962017-12-08 11:05:22 +010053 public enum VideoCodecType {
54 VIDEO_CODEC_VP8,
55 VIDEO_CODEC_VP9,
56 VIDEO_CODEC_H264;
57
58 @CalledByNative("VideoCodecType")
59 static VideoCodecType fromNativeIndex(int nativeIndex) {
60 return values()[nativeIndex];
61 }
62 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000063
Alex Glazneveee86a62016-01-29 14:17:07 -080064 // Timeout for input buffer dequeue.
65 private static final int DEQUEUE_INPUT_TIMEOUT = 500000;
66 // Timeout for codec releasing.
67 private static final int MEDIA_CODEC_RELEASE_TIMEOUT_MS = 5000;
68 // Max number of output buffers queued before starting to drop decoded frames.
69 private static final int MAX_QUEUED_OUTPUTBUFFERS = 3;
Alex Glaznevc6aec4b2015-10-19 16:39:19 -070070 // Active running decoder instance. Set in initDecode() (called from native code)
71 // and reset to null in release() call.
72 private static MediaCodecVideoDecoder runningInstance = null;
Alex Glaznev5c3da4b2015-10-30 15:31:07 -070073 private static MediaCodecVideoDecoderErrorCallback errorCallback = null;
74 private static int codecErrors = 0;
Alex Glazneveee86a62016-01-29 14:17:07 -080075 // List of disabled codec types - can be set from application.
76 private static Set<String> hwDecoderDisabledTypes = new HashSet<String>();
Alex Glaznev5c3da4b2015-10-30 15:31:07 -070077
Alejandro Luebs69ddaef2015-10-09 15:46:09 -070078 private Thread mediaCodecThread;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000079 private MediaCodec mediaCodec;
80 private ByteBuffer[] inputBuffers;
81 private ByteBuffer[] outputBuffers;
82 private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8";
Alex Glaznev69a7fd52015-11-10 10:25:40 -080083 private static final String VP9_MIME_TYPE = "video/x-vnd.on2.vp9";
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000084 private static final String H264_MIME_TYPE = "video/avc";
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000085 // List of supported HW VP8 decoders.
Alex Leung5b6891a2018-01-18 10:01:14 -080086 private static final String[] supportedVp8HwCodecPrefixes() {
87 ArrayList<String> supportedPrefixes = new ArrayList<String>();
88 supportedPrefixes.add("OMX.qcom.");
89 supportedPrefixes.add("OMX.Nvidia.");
90 supportedPrefixes.add("OMX.Exynos.");
91 supportedPrefixes.add("OMX.Intel.");
92 if (PeerConnectionFactory.fieldTrialsFindFullName("WebRTC-MediaTekVP8").equals("Enabled")
93 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
94 supportedPrefixes.add("OMX.MTK.");
95 }
96 return supportedPrefixes.toArray(new String[supportedPrefixes.size()]);
97 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -080098 // List of supported HW VP9 decoders.
sakalb6760f92016-09-29 04:12:44 -070099 private static final String[] supportedVp9HwCodecPrefixes = {"OMX.qcom.", "OMX.Exynos."};
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000100 // List of supported HW H.264 decoders.
Alex Leung5b6891a2018-01-18 10:01:14 -0800101 private static final String[] supportedH264HwCodecPrefixes() {
102 ArrayList<String> supportedPrefixes = new ArrayList<String>();
103 supportedPrefixes.add("OMX.qcom.");
104 supportedPrefixes.add("OMX.Intel.");
105 supportedPrefixes.add("OMX.Exynos.");
106 if (PeerConnectionFactory.fieldTrialsFindFullName("WebRTC-MediaTekH264").equals("Enabled")
107 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
108 supportedPrefixes.add("OMX.MTK.");
109 }
110 return supportedPrefixes.toArray(new String[supportedPrefixes.size()]);
111 }
112
glaznev0c1d0602017-01-27 12:24:24 -0800113 // List of supported HW H.264 high profile decoders.
glaznevcca0f6c2017-06-14 10:20:54 -0700114 private static final String supportedQcomH264HighProfileHwCodecPrefix = "OMX.qcom.";
115 private static final String supportedExynosH264HighProfileHwCodecPrefix = "OMX.Exynos.";
Alex Leung5b6891a2018-01-18 10:01:14 -0800116 private static final String supportedMediaTekH264HighProfileHwCodecPrefix = "OMX.MTK.";
glaznev893a7ee2016-09-22 10:44:30 -0700117
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000118 // NV12 color format supported by QCOM codec, but not declared in MediaCodec -
119 // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
glaznev893a7ee2016-09-22 10:44:30 -0700120 private static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar32m4ka = 0x7FA30C01;
121 private static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar16m4ka = 0x7FA30C02;
122 private static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar64x32Tile2m8ka = 0x7FA30C03;
123 private static final int COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000124 // Allowable color formats supported by codec - in order of preference.
Magnus Jedvert7e319372015-10-02 15:49:38 +0200125 private static final List<Integer> supportedColorList = Arrays.asList(
sakalb6760f92016-09-29 04:12:44 -0700126 CodecCapabilities.COLOR_FormatYUV420Planar, CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
127 CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
128 COLOR_QCOM_FORMATYVU420PackedSemiPlanar32m4ka, COLOR_QCOM_FORMATYVU420PackedSemiPlanar16m4ka,
129 COLOR_QCOM_FORMATYVU420PackedSemiPlanar64x32Tile2m8ka,
130 COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m);
glaznev893a7ee2016-09-22 10:44:30 -0700131
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000132 private int colorFormat;
133 private int width;
134 private int height;
135 private int stride;
136 private int sliceHeight;
Per488e75f2015-11-19 10:43:36 +0100137 private boolean hasDecodedFirstFrame;
Magnus Jedvert6062f372017-11-16 16:53:12 +0100138 private final Queue<TimeStamps> decodeStartTimeMs = new ArrayDeque<TimeStamps>();
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000139 private boolean useSurface;
Perc01c2542015-11-13 16:58:26 +0100140
Per488e75f2015-11-19 10:43:36 +0100141 // The below variables are only used when decoding to a Surface.
142 private TextureListener textureListener;
Per488e75f2015-11-19 10:43:36 +0100143 private int droppedFrames;
144 private Surface surface = null;
sakalb6760f92016-09-29 04:12:44 -0700145 private final Queue<DecodedOutputBuffer> dequeuedSurfaceOutputBuffers =
Magnus Jedvert6062f372017-11-16 16:53:12 +0100146 new ArrayDeque<DecodedOutputBuffer>();
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000147
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700148 // MediaCodec error handler - invoked when critical error happens which may prevent
149 // further use of media codec API. Now it means that one of media codec instances
150 // is hanging and can no longer be used in the next call.
151 public static interface MediaCodecVideoDecoderErrorCallback {
152 void onMediaCodecVideoDecoderCriticalError(int codecErrors);
153 }
154
155 public static void setErrorCallback(MediaCodecVideoDecoderErrorCallback errorCallback) {
156 Logging.d(TAG, "Set error callback");
157 MediaCodecVideoDecoder.errorCallback = errorCallback;
158 }
159
Alex Glazneveee86a62016-01-29 14:17:07 -0800160 // Functions to disable HW decoding - can be called from applications for platforms
161 // which have known HW decoding problems.
162 public static void disableVp8HwCodec() {
163 Logging.w(TAG, "VP8 decoding is disabled by application.");
164 hwDecoderDisabledTypes.add(VP8_MIME_TYPE);
165 }
166
167 public static void disableVp9HwCodec() {
168 Logging.w(TAG, "VP9 decoding is disabled by application.");
169 hwDecoderDisabledTypes.add(VP9_MIME_TYPE);
170 }
171
172 public static void disableH264HwCodec() {
173 Logging.w(TAG, "H.264 decoding is disabled by application.");
174 hwDecoderDisabledTypes.add(H264_MIME_TYPE);
175 }
176
177 // Functions to query if HW decoding is supported.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100178 @CalledByNativeUnchecked
Alex Glazneveee86a62016-01-29 14:17:07 -0800179 public static boolean isVp8HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700180 return !hwDecoderDisabledTypes.contains(VP8_MIME_TYPE)
Alex Leung5b6891a2018-01-18 10:01:14 -0800181 && (findDecoder(VP8_MIME_TYPE, supportedVp8HwCodecPrefixes()) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800182 }
183
Magnus Jedvert655e1962017-12-08 11:05:22 +0100184 @CalledByNativeUnchecked
Alex Glazneveee86a62016-01-29 14:17:07 -0800185 public static boolean isVp9HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700186 return !hwDecoderDisabledTypes.contains(VP9_MIME_TYPE)
187 && (findDecoder(VP9_MIME_TYPE, supportedVp9HwCodecPrefixes) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800188 }
189
Magnus Jedvert655e1962017-12-08 11:05:22 +0100190 @CalledByNativeUnchecked
Alex Glazneveee86a62016-01-29 14:17:07 -0800191 public static boolean isH264HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700192 return !hwDecoderDisabledTypes.contains(H264_MIME_TYPE)
Alex Leung5b6891a2018-01-18 10:01:14 -0800193 && (findDecoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes()) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800194 }
195
Magnus Jedvert655e1962017-12-08 11:05:22 +0100196 @CalledByNative
glaznev0c1d0602017-01-27 12:24:24 -0800197 public static boolean isH264HighProfileHwSupported() {
glaznevcca0f6c2017-06-14 10:20:54 -0700198 if (hwDecoderDisabledTypes.contains(H264_MIME_TYPE)) {
199 return false;
200 }
201 // Support H.264 HP decoding on QCOM chips for Android L and above.
202 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
203 && findDecoder(H264_MIME_TYPE, new String[] {supportedQcomH264HighProfileHwCodecPrefix})
204 != null) {
205 return true;
206 }
207 // Support H.264 HP decoding on Exynos chips for Android M and above.
208 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
209 && findDecoder(H264_MIME_TYPE, new String[] {supportedExynosH264HighProfileHwCodecPrefix})
210 != null) {
211 return true;
212 }
Alex Leung5b6891a2018-01-18 10:01:14 -0800213 // Support H.264 HP decoding on MediaTek chips for Android O and above
214 if (PeerConnectionFactory.fieldTrialsFindFullName("WebRTC-MediaTekH264").equals("Enabled")
215 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
216 && findDecoder(H264_MIME_TYPE, new String[] {supportedMediaTekH264HighProfileHwCodecPrefix})
217 != null) {
218 return true;
219 }
glaznevcca0f6c2017-06-14 10:20:54 -0700220 return false;
glaznev0c1d0602017-01-27 12:24:24 -0800221 }
222
Alex Glazneveee86a62016-01-29 14:17:07 -0800223 public static void printStackTrace() {
224 if (runningInstance != null && runningInstance.mediaCodecThread != null) {
225 StackTraceElement[] mediaCodecStackTraces = runningInstance.mediaCodecThread.getStackTrace();
226 if (mediaCodecStackTraces.length > 0) {
227 Logging.d(TAG, "MediaCodecVideoDecoder stacks trace:");
228 for (StackTraceElement stackTrace : mediaCodecStackTraces) {
229 Logging.d(TAG, stackTrace.toString());
230 }
231 }
232 }
233 }
234
235 // Helper struct for findDecoder() below.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000236 private static class DecoderProperties {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000237 public DecoderProperties(String codecName, int colorFormat) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000238 this.codecName = codecName;
239 this.colorFormat = colorFormat;
240 }
241 public final String codecName; // OpenMax component name for VP8 codec.
sakalb6760f92016-09-29 04:12:44 -0700242 public final int colorFormat; // Color format supported by codec.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000243 }
244
sakalb6760f92016-09-29 04:12:44 -0700245 private static DecoderProperties findDecoder(String mime, String[] supportedCodecPrefixes) {
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000246 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000247 return null; // MediaCodec.setParameters is missing.
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000248 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800249 Logging.d(TAG, "Trying to find HW decoder for mime " + mime);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000250 for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
Alex Glaznev0060c562016-08-08 12:27:24 -0700251 MediaCodecInfo info = null;
252 try {
253 info = MediaCodecList.getCodecInfoAt(i);
254 } catch (IllegalArgumentException e) {
sakalb6760f92016-09-29 04:12:44 -0700255 Logging.e(TAG, "Cannot retrieve decoder codec info", e);
Alex Glaznev0060c562016-08-08 12:27:24 -0700256 }
257 if (info == null || info.isEncoder()) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000258 continue;
259 }
260 String name = null;
261 for (String mimeType : info.getSupportedTypes()) {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000262 if (mimeType.equals(mime)) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000263 name = info.getName();
264 break;
265 }
266 }
267 if (name == null) {
sakalb6760f92016-09-29 04:12:44 -0700268 continue; // No HW support in this codec; try the next one.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000269 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800270 Logging.d(TAG, "Found candidate decoder " + name);
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000271
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000272 // Check if this is supported decoder.
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000273 boolean supportedCodec = false;
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000274 for (String codecPrefix : supportedCodecPrefixes) {
275 if (name.startsWith(codecPrefix)) {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000276 supportedCodec = true;
277 break;
278 }
279 }
280 if (!supportedCodec) {
281 continue;
282 }
283
284 // Check if codec supports either yuv420 or nv12.
Alex Glaznev0060c562016-08-08 12:27:24 -0700285 CodecCapabilities capabilities;
286 try {
287 capabilities = info.getCapabilitiesForType(mime);
288 } catch (IllegalArgumentException e) {
sakalb6760f92016-09-29 04:12:44 -0700289 Logging.e(TAG, "Cannot retrieve decoder capabilities", e);
Alex Glaznev0060c562016-08-08 12:27:24 -0700290 continue;
291 }
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000292 for (int colorFormat : capabilities.colorFormats) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700293 Logging.v(TAG, " Color: 0x" + Integer.toHexString(colorFormat));
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000294 }
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000295 for (int supportedColorFormat : supportedColorList) {
296 for (int codecColorFormat : capabilities.colorFormats) {
297 if (codecColorFormat == supportedColorFormat) {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000298 // Found supported HW decoder.
sakalb6760f92016-09-29 04:12:44 -0700299 Logging.d(TAG, "Found target decoder " + name + ". Color: 0x"
300 + Integer.toHexString(codecColorFormat));
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000301 return new DecoderProperties(name, codecColorFormat);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000302 }
303 }
304 }
305 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800306 Logging.d(TAG, "No HW decoder found for mime " + mime);
sakalb6760f92016-09-29 04:12:44 -0700307 return null; // No HW decoder.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000308 }
309
Magnus Jedvert655e1962017-12-08 11:05:22 +0100310 @CalledByNative
311 MediaCodecVideoDecoder() {}
312
Magnus Jedvert7e319372015-10-02 15:49:38 +0200313 private void checkOnMediaCodecThread() throws IllegalStateException {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000314 if (mediaCodecThread.getId() != Thread.currentThread().getId()) {
sakalb6760f92016-09-29 04:12:44 -0700315 throw new IllegalStateException("MediaCodecVideoDecoder previously operated on "
316 + mediaCodecThread + " but is now called on " + Thread.currentThread());
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000317 }
318 }
319
Per488e75f2015-11-19 10:43:36 +0100320 // Pass null in |surfaceTextureHelper| to configure the codec for ByteBuffer output.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100321 @CalledByNativeUnchecked
Per488e75f2015-11-19 10:43:36 +0100322 private boolean initDecode(
sakalb6760f92016-09-29 04:12:44 -0700323 VideoCodecType type, int width, int height, SurfaceTextureHelper surfaceTextureHelper) {
Alejandro Luebs69ddaef2015-10-09 15:46:09 -0700324 if (mediaCodecThread != null) {
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800325 throw new RuntimeException("initDecode: Forgot to release()?");
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000326 }
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800327
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000328 String mime = null;
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800329 useSurface = (surfaceTextureHelper != null);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000330 String[] supportedCodecPrefixes = null;
331 if (type == VideoCodecType.VIDEO_CODEC_VP8) {
332 mime = VP8_MIME_TYPE;
Alex Leung5b6891a2018-01-18 10:01:14 -0800333 supportedCodecPrefixes = supportedVp8HwCodecPrefixes();
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800334 } else if (type == VideoCodecType.VIDEO_CODEC_VP9) {
335 mime = VP9_MIME_TYPE;
336 supportedCodecPrefixes = supportedVp9HwCodecPrefixes;
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000337 } else if (type == VideoCodecType.VIDEO_CODEC_H264) {
338 mime = H264_MIME_TYPE;
Alex Leung5b6891a2018-01-18 10:01:14 -0800339 supportedCodecPrefixes = supportedH264HwCodecPrefixes();
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000340 } else {
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800341 throw new RuntimeException("initDecode: Non-supported codec " + type);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000342 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000343 DecoderProperties properties = findDecoder(mime, supportedCodecPrefixes);
344 if (properties == null) {
345 throw new RuntimeException("Cannot find HW decoder for " + type);
346 }
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800347
sakalb6760f92016-09-29 04:12:44 -0700348 Logging.d(TAG, "Java initDecode: " + type + " : " + width + " x " + height + ". Color: 0x"
349 + Integer.toHexString(properties.colorFormat) + ". Use Surface: " + useSurface);
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800350
Alex Glaznevc6aec4b2015-10-19 16:39:19 -0700351 runningInstance = this; // Decoder is now running and can be queried for stack traces.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000352 mediaCodecThread = Thread.currentThread();
353 try {
354 this.width = width;
355 this.height = height;
356 stride = width;
357 sliceHeight = height;
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000358
359 if (useSurface) {
Per488e75f2015-11-19 10:43:36 +0100360 textureListener = new TextureListener(surfaceTextureHelper);
361 surface = new Surface(surfaceTextureHelper.getSurfaceTexture());
Magnus Jedvert80cf97c2015-06-11 10:08:59 +0200362 }
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000363
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000364 MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000365 if (!useSurface) {
366 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
367 }
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700368 Logging.d(TAG, " Format: " + format);
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800369 mediaCodec = MediaCodecVideoEncoder.createByCodecName(properties.codecName);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000370 if (mediaCodec == null) {
Alex Glaznev325d4142015-10-12 14:56:02 -0700371 Logging.e(TAG, "Can not create media decoder");
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000372 return false;
373 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200374 mediaCodec.configure(format, surface, null, 0);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000375 mediaCodec.start();
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800376
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000377 colorFormat = properties.colorFormat;
378 outputBuffers = mediaCodec.getOutputBuffers();
379 inputBuffers = mediaCodec.getInputBuffers();
Per488e75f2015-11-19 10:43:36 +0100380 decodeStartTimeMs.clear();
381 hasDecodedFirstFrame = false;
382 dequeuedSurfaceOutputBuffers.clear();
383 droppedFrames = 0;
sakalb6760f92016-09-29 04:12:44 -0700384 Logging.d(TAG,
385 "Input buffers: " + inputBuffers.length + ". Output buffers: " + outputBuffers.length);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000386 return true;
387 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700388 Logging.e(TAG, "initDecode failed", e);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000389 return false;
390 }
391 }
392
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800393 // Resets the decoder so it can start decoding frames with new resolution.
394 // Flushes MediaCodec and clears decoder output buffers.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100395 @CalledByNativeUnchecked
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800396 private void reset(int width, int height) {
397 if (mediaCodecThread == null || mediaCodec == null) {
398 throw new RuntimeException("Incorrect reset call for non-initialized decoder.");
399 }
400 Logging.d(TAG, "Java reset: " + width + " x " + height);
401
402 mediaCodec.flush();
403
404 this.width = width;
405 this.height = height;
406 decodeStartTimeMs.clear();
407 dequeuedSurfaceOutputBuffers.clear();
408 hasDecodedFirstFrame = false;
409 droppedFrames = 0;
410 }
411
Magnus Jedvert655e1962017-12-08 11:05:22 +0100412 @CalledByNativeUnchecked
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000413 private void release() {
Per488e75f2015-11-19 10:43:36 +0100414 Logging.d(TAG, "Java releaseDecoder. Total number of dropped frames: " + droppedFrames);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000415 checkOnMediaCodecThread();
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700416
417 // Run Mediacodec stop() and release() on separate thread since sometime
418 // Mediacodec.stop() may hang.
419 final CountDownLatch releaseDone = new CountDownLatch(1);
420
421 Runnable runMediaCodecRelease = new Runnable() {
422 @Override
423 public void run() {
424 try {
425 Logging.d(TAG, "Java releaseDecoder on release thread");
426 mediaCodec.stop();
427 mediaCodec.release();
428 Logging.d(TAG, "Java releaseDecoder on release thread done");
429 } catch (Exception e) {
430 Logging.e(TAG, "Media decoder release failed", e);
431 }
432 releaseDone.countDown();
433 }
434 };
435 new Thread(runMediaCodecRelease).start();
436
437 if (!ThreadUtils.awaitUninterruptibly(releaseDone, MEDIA_CODEC_RELEASE_TIMEOUT_MS)) {
438 Logging.e(TAG, "Media decoder release timeout");
439 codecErrors++;
440 if (errorCallback != null) {
441 Logging.e(TAG, "Invoke codec error callback. Errors: " + codecErrors);
442 errorCallback.onMediaCodecVideoDecoderCriticalError(codecErrors);
443 }
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000444 }
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700445
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000446 mediaCodec = null;
447 mediaCodecThread = null;
Alex Glaznevc6aec4b2015-10-19 16:39:19 -0700448 runningInstance = null;
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000449 if (useSurface) {
450 surface.release();
Magnus Jedvert7e319372015-10-02 15:49:38 +0200451 surface = null;
Per488e75f2015-11-19 10:43:36 +0100452 textureListener.release();
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000453 }
Alex Glaznev325d4142015-10-12 14:56:02 -0700454 Logging.d(TAG, "Java releaseDecoder done");
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000455 }
456
457 // Dequeue an input buffer and return its index, -1 if no input buffer is
458 // available, or -2 if the codec is no longer operative.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100459 @CalledByNativeUnchecked
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000460 private int dequeueInputBuffer() {
461 checkOnMediaCodecThread();
462 try {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000463 return mediaCodec.dequeueInputBuffer(DEQUEUE_INPUT_TIMEOUT);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000464 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700465 Logging.e(TAG, "dequeueIntputBuffer failed", e);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000466 return -2;
467 }
468 }
469
Magnus Jedvert655e1962017-12-08 11:05:22 +0100470 @CalledByNativeUnchecked
Per488e75f2015-11-19 10:43:36 +0100471 private boolean queueInputBuffer(int inputBufferIndex, int size, long presentationTimeStamUs,
472 long timeStampMs, long ntpTimeStamp) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000473 checkOnMediaCodecThread();
474 try {
475 inputBuffers[inputBufferIndex].position(0);
476 inputBuffers[inputBufferIndex].limit(size);
sakalb6760f92016-09-29 04:12:44 -0700477 decodeStartTimeMs.add(
478 new TimeStamps(SystemClock.elapsedRealtime(), timeStampMs, ntpTimeStamp));
Per488e75f2015-11-19 10:43:36 +0100479 mediaCodec.queueInputBuffer(inputBufferIndex, 0, size, presentationTimeStamUs, 0);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000480 return true;
sakalb6760f92016-09-29 04:12:44 -0700481 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700482 Logging.e(TAG, "decode failed", e);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000483 return false;
484 }
485 }
486
Per488e75f2015-11-19 10:43:36 +0100487 private static class TimeStamps {
488 public TimeStamps(long decodeStartTimeMs, long timeStampMs, long ntpTimeStampMs) {
489 this.decodeStartTimeMs = decodeStartTimeMs;
490 this.timeStampMs = timeStampMs;
491 this.ntpTimeStampMs = ntpTimeStampMs;
492 }
Alex Glazneveee86a62016-01-29 14:17:07 -0800493 // Time when this frame was queued for decoding.
494 private final long decodeStartTimeMs;
495 // Only used for bookkeeping in Java. Stores C++ inputImage._timeStamp value for input frame.
496 private final long timeStampMs;
497 // Only used for bookkeeping in Java. Stores C++ inputImage.ntp_time_ms_ value for input frame.
498 private final long ntpTimeStampMs;
Per488e75f2015-11-19 10:43:36 +0100499 }
500
501 // Helper struct for dequeueOutputBuffer() below.
502 private static class DecodedOutputBuffer {
glaznev94291482016-02-01 13:17:18 -0800503 public DecodedOutputBuffer(int index, int offset, int size, long presentationTimeStampMs,
504 long timeStampMs, long ntpTimeStampMs, long decodeTime, long endDecodeTime) {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000505 this.index = index;
506 this.offset = offset;
507 this.size = size;
glaznev94291482016-02-01 13:17:18 -0800508 this.presentationTimeStampMs = presentationTimeStampMs;
Per488e75f2015-11-19 10:43:36 +0100509 this.timeStampMs = timeStampMs;
510 this.ntpTimeStampMs = ntpTimeStampMs;
511 this.decodeTimeMs = decodeTime;
512 this.endDecodeTimeMs = endDecodeTime;
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000513 }
514
515 private final int index;
516 private final int offset;
517 private final int size;
glaznev94291482016-02-01 13:17:18 -0800518 // Presentation timestamp returned in dequeueOutputBuffer call.
519 private final long presentationTimeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800520 // C++ inputImage._timeStamp value for output frame.
Per488e75f2015-11-19 10:43:36 +0100521 private final long timeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800522 // C++ inputImage.ntp_time_ms_ value for output frame.
Per488e75f2015-11-19 10:43:36 +0100523 private final long ntpTimeStampMs;
524 // Number of ms it took to decode this frame.
525 private final long decodeTimeMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800526 // System time when this frame decoding finished.
Per488e75f2015-11-19 10:43:36 +0100527 private final long endDecodeTimeMs;
Magnus Jedvert655e1962017-12-08 11:05:22 +0100528
529 @CalledByNative("DecodedOutputBuffer")
530 int getIndex() {
531 return index;
532 }
533
534 @CalledByNative("DecodedOutputBuffer")
535 int getOffset() {
536 return offset;
537 }
538
539 @CalledByNative("DecodedOutputBuffer")
540 int getSize() {
541 return size;
542 }
543
544 @CalledByNative("DecodedOutputBuffer")
545 long getPresentationTimestampMs() {
546 return presentationTimeStampMs;
547 }
548
549 @CalledByNative("DecodedOutputBuffer")
550 long getTimestampMs() {
551 return timeStampMs;
552 }
553
554 @CalledByNative("DecodedOutputBuffer")
555 long getNtpTimestampMs() {
556 return ntpTimeStampMs;
557 }
558
559 @CalledByNative("DecodedOutputBuffer")
560 long getDecodeTimeMs() {
561 return decodeTimeMs;
562 }
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000563 }
564
Per488e75f2015-11-19 10:43:36 +0100565 // Helper struct for dequeueTextureBuffer() below.
magjed44bf6f52015-10-03 02:08:00 -0700566 private static class DecodedTextureBuffer {
567 private final int textureID;
Per488e75f2015-11-19 10:43:36 +0100568 private final float[] transformMatrix;
glaznev94291482016-02-01 13:17:18 -0800569 // Presentation timestamp returned in dequeueOutputBuffer call.
570 private final long presentationTimeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800571 // C++ inputImage._timeStamp value for output frame.
Per488e75f2015-11-19 10:43:36 +0100572 private final long timeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800573 // C++ inputImage.ntp_time_ms_ value for output frame.
Per488e75f2015-11-19 10:43:36 +0100574 private final long ntpTimeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800575 // Number of ms it took to decode this frame.
Per488e75f2015-11-19 10:43:36 +0100576 private final long decodeTimeMs;
577 // Interval from when the frame finished decoding until this buffer has been created.
578 // Since there is only one texture, this interval depend on the time from when
579 // a frame is decoded and provided to C++ and until that frame is returned to the MediaCodec
580 // so that the texture can be updated with the next decoded frame.
581 private final long frameDelayMs;
magjed44bf6f52015-10-03 02:08:00 -0700582
Per488e75f2015-11-19 10:43:36 +0100583 // A DecodedTextureBuffer with zero |textureID| has special meaning and represents a frame
584 // that was dropped.
glaznev94291482016-02-01 13:17:18 -0800585 public DecodedTextureBuffer(int textureID, float[] transformMatrix,
586 long presentationTimeStampMs, long timeStampMs, long ntpTimeStampMs, long decodeTimeMs,
587 long frameDelay) {
magjed44bf6f52015-10-03 02:08:00 -0700588 this.textureID = textureID;
Per488e75f2015-11-19 10:43:36 +0100589 this.transformMatrix = transformMatrix;
glaznev94291482016-02-01 13:17:18 -0800590 this.presentationTimeStampMs = presentationTimeStampMs;
Per488e75f2015-11-19 10:43:36 +0100591 this.timeStampMs = timeStampMs;
592 this.ntpTimeStampMs = ntpTimeStampMs;
593 this.decodeTimeMs = decodeTimeMs;
594 this.frameDelayMs = frameDelay;
magjed44bf6f52015-10-03 02:08:00 -0700595 }
Magnus Jedvert655e1962017-12-08 11:05:22 +0100596
597 @CalledByNative("DecodedTextureBuffer")
598 int getTextureId() {
599 return textureID;
600 }
601
602 @CalledByNative("DecodedTextureBuffer")
603 float[] getTransformMatrix() {
604 return transformMatrix;
605 }
606
607 @CalledByNative("DecodedTextureBuffer")
608 long getPresentationTimestampMs() {
609 return presentationTimeStampMs;
610 }
611
612 @CalledByNative("DecodedTextureBuffer")
613 long getTimeStampMs() {
614 return timeStampMs;
615 }
616
617 @CalledByNative("DecodedTextureBuffer")
618 long getNtpTimestampMs() {
619 return ntpTimeStampMs;
620 }
621
622 @CalledByNative("DecodedTextureBuffer")
623 long getDecodeTimeMs() {
624 return decodeTimeMs;
625 }
626
627 @CalledByNative("DecodedTextureBuffer")
628 long getFrameDelayMs() {
629 return frameDelayMs;
630 }
magjed44bf6f52015-10-03 02:08:00 -0700631 }
632
Per488e75f2015-11-19 10:43:36 +0100633 // Poll based texture listener.
634 private static class TextureListener
635 implements SurfaceTextureHelper.OnTextureFrameAvailableListener {
636 private final SurfaceTextureHelper surfaceTextureHelper;
637 // |newFrameLock| is used to synchronize arrival of new frames with wait()/notifyAll().
638 private final Object newFrameLock = new Object();
639 // |bufferToRender| is non-null when waiting for transition between addBufferToRender() to
640 // onTextureFrameAvailable().
641 private DecodedOutputBuffer bufferToRender;
642 private DecodedTextureBuffer renderedBuffer;
643
644 public TextureListener(SurfaceTextureHelper surfaceTextureHelper) {
645 this.surfaceTextureHelper = surfaceTextureHelper;
magjed81e8e372016-03-03 02:11:44 -0800646 surfaceTextureHelper.startListening(this);
Per488e75f2015-11-19 10:43:36 +0100647 }
648
649 public void addBufferToRender(DecodedOutputBuffer buffer) {
650 if (bufferToRender != null) {
sakalb6760f92016-09-29 04:12:44 -0700651 Logging.e(TAG, "Unexpected addBufferToRender() called while waiting for a texture.");
Per488e75f2015-11-19 10:43:36 +0100652 throw new IllegalStateException("Waiting for a texture.");
653 }
654 bufferToRender = buffer;
655 }
656
657 public boolean isWaitingForTexture() {
658 synchronized (newFrameLock) {
659 return bufferToRender != null;
660 }
661 }
662
663 // Callback from |surfaceTextureHelper|. May be called on an arbitrary thread.
664 @Override
665 public void onTextureFrameAvailable(
666 int oesTextureId, float[] transformMatrix, long timestampNs) {
667 synchronized (newFrameLock) {
668 if (renderedBuffer != null) {
sakalb6760f92016-09-29 04:12:44 -0700669 Logging.e(
670 TAG, "Unexpected onTextureFrameAvailable() called while already holding a texture.");
Per488e75f2015-11-19 10:43:36 +0100671 throw new IllegalStateException("Already holding a texture.");
672 }
673 // |timestampNs| is always zero on some Android versions.
674 renderedBuffer = new DecodedTextureBuffer(oesTextureId, transformMatrix,
glaznev94291482016-02-01 13:17:18 -0800675 bufferToRender.presentationTimeStampMs, bufferToRender.timeStampMs,
676 bufferToRender.ntpTimeStampMs, bufferToRender.decodeTimeMs,
Per488e75f2015-11-19 10:43:36 +0100677 SystemClock.elapsedRealtime() - bufferToRender.endDecodeTimeMs);
678 bufferToRender = null;
679 newFrameLock.notifyAll();
680 }
681 }
682
683 // Dequeues and returns a DecodedTextureBuffer if available, or null otherwise.
Sami Kalliomäki9828beb2017-10-26 16:21:22 +0200684 @SuppressWarnings("WaitNotInLoop")
Per488e75f2015-11-19 10:43:36 +0100685 public DecodedTextureBuffer dequeueTextureBuffer(int timeoutMs) {
686 synchronized (newFrameLock) {
687 if (renderedBuffer == null && timeoutMs > 0 && isWaitingForTexture()) {
688 try {
689 newFrameLock.wait(timeoutMs);
sakalb6760f92016-09-29 04:12:44 -0700690 } catch (InterruptedException e) {
Per488e75f2015-11-19 10:43:36 +0100691 // Restore the interrupted status by reinterrupting the thread.
692 Thread.currentThread().interrupt();
693 }
694 }
695 DecodedTextureBuffer returnedBuffer = renderedBuffer;
696 renderedBuffer = null;
697 return returnedBuffer;
698 }
699 }
700
701 public void release() {
nissea44e72c2016-05-27 00:27:59 -0700702 // SurfaceTextureHelper.stopListening() will block until any onTextureFrameAvailable() in
703 // progress is done. Therefore, the call must be outside any synchronized
Per488e75f2015-11-19 10:43:36 +0100704 // statement that is also used in the onTextureFrameAvailable() above to avoid deadlocks.
nissea44e72c2016-05-27 00:27:59 -0700705 surfaceTextureHelper.stopListening();
Per488e75f2015-11-19 10:43:36 +0100706 synchronized (newFrameLock) {
707 if (renderedBuffer != null) {
708 surfaceTextureHelper.returnTextureFrame();
709 renderedBuffer = null;
710 }
711 }
712 }
713 }
714
715 // Returns null if no decoded buffer is available, and otherwise a DecodedByteBuffer.
Magnus Jedvert7e319372015-10-02 15:49:38 +0200716 // Throws IllegalStateException if call is made on the wrong thread, if color format changes to an
717 // unsupported format, or if |mediaCodec| is not in the Executing state. Throws CodecException
718 // upon codec error.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100719 @CalledByNativeUnchecked
Per488e75f2015-11-19 10:43:36 +0100720 private DecodedOutputBuffer dequeueOutputBuffer(int dequeueTimeoutMs) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000721 checkOnMediaCodecThread();
Per488e75f2015-11-19 10:43:36 +0100722 if (decodeStartTimeMs.isEmpty()) {
723 return null;
724 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200725 // Drain the decoder until receiving a decoded buffer or hitting
726 // MediaCodec.INFO_TRY_AGAIN_LATER.
727 final MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
728 while (true) {
sakalb6760f92016-09-29 04:12:44 -0700729 final int result =
730 mediaCodec.dequeueOutputBuffer(info, TimeUnit.MILLISECONDS.toMicros(dequeueTimeoutMs));
Magnus Jedvert7e319372015-10-02 15:49:38 +0200731 switch (result) {
Magnus Jedvert7e319372015-10-02 15:49:38 +0200732 case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000733 outputBuffers = mediaCodec.getOutputBuffers();
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700734 Logging.d(TAG, "Decoder output buffers changed: " + outputBuffers.length);
Per488e75f2015-11-19 10:43:36 +0100735 if (hasDecodedFirstFrame) {
736 throw new RuntimeException("Unexpected output buffer change event.");
737 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200738 break;
739 case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000740 MediaFormat format = mediaCodec.getOutputFormat();
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700741 Logging.d(TAG, "Decoder format changed: " + format.toString());
magjed0c29de52017-03-02 00:55:32 -0800742 final int newWidth;
743 final int newHeight;
744 if (format.containsKey(FORMAT_KEY_CROP_LEFT) && format.containsKey(FORMAT_KEY_CROP_RIGHT)
745 && format.containsKey(FORMAT_KEY_CROP_BOTTOM)
746 && format.containsKey(FORMAT_KEY_CROP_TOP)) {
747 newWidth = 1 + format.getInteger(FORMAT_KEY_CROP_RIGHT)
748 - format.getInteger(FORMAT_KEY_CROP_LEFT);
749 newHeight = 1 + format.getInteger(FORMAT_KEY_CROP_BOTTOM)
750 - format.getInteger(FORMAT_KEY_CROP_TOP);
751 } else {
752 newWidth = format.getInteger(MediaFormat.KEY_WIDTH);
753 newHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
Per488e75f2015-11-19 10:43:36 +0100754 }
magjed0c29de52017-03-02 00:55:32 -0800755 if (hasDecodedFirstFrame && (newWidth != width || newHeight != height)) {
756 throw new RuntimeException("Unexpected size change. Configured " + width + "*" + height
757 + ". New " + newWidth + "*" + newHeight);
758 }
759 width = newWidth;
760 height = newHeight;
Per488e75f2015-11-19 10:43:36 +0100761
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000762 if (!useSurface && format.containsKey(MediaFormat.KEY_COLOR_FORMAT)) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000763 colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700764 Logging.d(TAG, "Color: 0x" + Integer.toHexString(colorFormat));
Magnus Jedvert7e319372015-10-02 15:49:38 +0200765 if (!supportedColorList.contains(colorFormat)) {
766 throw new IllegalStateException("Non supported color format: " + colorFormat);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000767 }
768 }
magjed0c29de52017-03-02 00:55:32 -0800769 if (format.containsKey(FORMAT_KEY_STRIDE)) {
770 stride = format.getInteger(FORMAT_KEY_STRIDE);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000771 }
magjed0c29de52017-03-02 00:55:32 -0800772 if (format.containsKey(FORMAT_KEY_SLICE_HEIGHT)) {
773 sliceHeight = format.getInteger(FORMAT_KEY_SLICE_HEIGHT);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000774 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200775 Logging.d(TAG, "Frame stride and slice height: " + stride + " x " + sliceHeight);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000776 stride = Math.max(width, stride);
777 sliceHeight = Math.max(height, sliceHeight);
Magnus Jedvert7e319372015-10-02 15:49:38 +0200778 break;
Per488e75f2015-11-19 10:43:36 +0100779 case MediaCodec.INFO_TRY_AGAIN_LATER:
780 return null;
Magnus Jedvert7e319372015-10-02 15:49:38 +0200781 default:
Per488e75f2015-11-19 10:43:36 +0100782 hasDecodedFirstFrame = true;
783 TimeStamps timeStamps = decodeStartTimeMs.remove();
magjedcedddbd2016-02-26 09:36:04 -0800784 long decodeTimeMs = SystemClock.elapsedRealtime() - timeStamps.decodeStartTimeMs;
785 if (decodeTimeMs > MAX_DECODE_TIME_MS) {
Alex Glaznevd5704842016-06-27 11:51:10 -0700786 Logging.e(TAG, "Very high decode time: " + decodeTimeMs + "ms"
sakalb6760f92016-09-29 04:12:44 -0700787 + ". Q size: " + decodeStartTimeMs.size()
788 + ". Might be caused by resuming H264 decoding after a pause.");
magjedcedddbd2016-02-26 09:36:04 -0800789 decodeTimeMs = MAX_DECODE_TIME_MS;
790 }
sakalb6760f92016-09-29 04:12:44 -0700791 return new DecodedOutputBuffer(result, info.offset, info.size,
792 TimeUnit.MICROSECONDS.toMillis(info.presentationTimeUs), timeStamps.timeStampMs,
793 timeStamps.ntpTimeStampMs, decodeTimeMs, SystemClock.elapsedRealtime());
794 }
perkj9cb89822015-11-11 03:27:01 -0800795 }
perkj9cb89822015-11-11 03:27:01 -0800796 }
797
Per488e75f2015-11-19 10:43:36 +0100798 // Returns null if no decoded buffer is available, and otherwise a DecodedTextureBuffer.
799 // Throws IllegalStateException if call is made on the wrong thread, if color format changes to an
800 // unsupported format, or if |mediaCodec| is not in the Executing state. Throws CodecException
801 // upon codec error. If |dequeueTimeoutMs| > 0, the oldest decoded frame will be dropped if
802 // a frame can't be returned.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100803 @CalledByNativeUnchecked
Per488e75f2015-11-19 10:43:36 +0100804 private DecodedTextureBuffer dequeueTextureBuffer(int dequeueTimeoutMs) {
805 checkOnMediaCodecThread();
806 if (!useSurface) {
807 throw new IllegalStateException("dequeueTexture() called for byte buffer decoding.");
808 }
809 DecodedOutputBuffer outputBuffer = dequeueOutputBuffer(dequeueTimeoutMs);
810 if (outputBuffer != null) {
811 dequeuedSurfaceOutputBuffers.add(outputBuffer);
812 }
813
814 MaybeRenderDecodedTextureBuffer();
815 // Check if there is texture ready now by waiting max |dequeueTimeoutMs|.
816 DecodedTextureBuffer renderedBuffer = textureListener.dequeueTextureBuffer(dequeueTimeoutMs);
817 if (renderedBuffer != null) {
818 MaybeRenderDecodedTextureBuffer();
819 return renderedBuffer;
820 }
821
822 if ((dequeuedSurfaceOutputBuffers.size()
sakalb6760f92016-09-29 04:12:44 -0700823 >= Math.min(MAX_QUEUED_OUTPUTBUFFERS, outputBuffers.length)
824 || (dequeueTimeoutMs > 0 && !dequeuedSurfaceOutputBuffers.isEmpty()))) {
Per488e75f2015-11-19 10:43:36 +0100825 ++droppedFrames;
826 // Drop the oldest frame still in dequeuedSurfaceOutputBuffers.
827 // The oldest frame is owned by |textureListener| and can't be dropped since
828 // mediaCodec.releaseOutputBuffer has already been called.
829 final DecodedOutputBuffer droppedFrame = dequeuedSurfaceOutputBuffers.remove();
830 if (dequeueTimeoutMs > 0) {
perkj7baf79f2015-11-24 06:26:38 -0800831 // TODO(perkj): Re-add the below log when VideoRenderGUI has been removed or fixed to
832 // return the one and only texture even if it does not render.
glaznevae95ff32016-02-04 11:47:12 -0800833 Logging.w(TAG, "Draining decoder. Dropping frame with TS: "
sakalb6760f92016-09-29 04:12:44 -0700834 + droppedFrame.presentationTimeStampMs + ". Total number of dropped frames: "
835 + droppedFrames);
Per488e75f2015-11-19 10:43:36 +0100836 } else {
sakalb6760f92016-09-29 04:12:44 -0700837 Logging.w(TAG, "Too many output buffers " + dequeuedSurfaceOutputBuffers.size()
838 + ". Dropping frame with TS: " + droppedFrame.presentationTimeStampMs
839 + ". Total number of dropped frames: " + droppedFrames);
Per488e75f2015-11-19 10:43:36 +0100840 }
841
842 mediaCodec.releaseOutputBuffer(droppedFrame.index, false /* render */);
sakalb6760f92016-09-29 04:12:44 -0700843 return new DecodedTextureBuffer(0, null, droppedFrame.presentationTimeStampMs,
844 droppedFrame.timeStampMs, droppedFrame.ntpTimeStampMs, droppedFrame.decodeTimeMs,
Per488e75f2015-11-19 10:43:36 +0100845 SystemClock.elapsedRealtime() - droppedFrame.endDecodeTimeMs);
846 }
847 return null;
848 }
849
850 private void MaybeRenderDecodedTextureBuffer() {
851 if (dequeuedSurfaceOutputBuffers.isEmpty() || textureListener.isWaitingForTexture()) {
852 return;
853 }
854 // Get the first frame in the queue and render to the decoder output surface.
855 final DecodedOutputBuffer buffer = dequeuedSurfaceOutputBuffers.remove();
856 textureListener.addBufferToRender(buffer);
857 mediaCodec.releaseOutputBuffer(buffer.index, true /* render */);
858 }
859
magjed44bf6f52015-10-03 02:08:00 -0700860 // Release a dequeued output byte buffer back to the codec for re-use. Should only be called for
861 // non-surface decoding.
862 // Throws IllegalStateException if the call is made on the wrong thread, if codec is configured
863 // for surface decoding, or if |mediaCodec| is not in the Executing state. Throws
864 // MediaCodec.CodecException upon codec error.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100865 @CalledByNativeUnchecked
Per488e75f2015-11-19 10:43:36 +0100866 private void returnDecodedOutputBuffer(int index)
Magnus Jedvert7e319372015-10-02 15:49:38 +0200867 throws IllegalStateException, MediaCodec.CodecException {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000868 checkOnMediaCodecThread();
magjed44bf6f52015-10-03 02:08:00 -0700869 if (useSurface) {
Per488e75f2015-11-19 10:43:36 +0100870 throw new IllegalStateException("returnDecodedOutputBuffer() called for surface decoding.");
magjed44bf6f52015-10-03 02:08:00 -0700871 }
872 mediaCodec.releaseOutputBuffer(index, false /* render */);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000873 }
Magnus Jedvert655e1962017-12-08 11:05:22 +0100874
875 @CalledByNative
876 ByteBuffer[] getInputBuffers() {
877 return inputBuffers;
878 }
879
880 @CalledByNative
881 ByteBuffer[] getOutputBuffers() {
882 return outputBuffers;
883 }
884
885 @CalledByNative
886 int getColorFormat() {
887 return colorFormat;
888 }
889
890 @CalledByNative
891 int getWidth() {
892 return width;
893 }
894
895 @CalledByNative
896 int getHeight() {
897 return height;
898 }
899
900 @CalledByNative
901 int getStride() {
902 return stride;
903 }
904
905 @CalledByNative
906 int getSliceHeight() {
907 return sliceHeight;
908 }
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000909}