blob: f0ec00e1a71db36dfaae4082f198ae703710c940 [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
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010013import android.graphics.SurfaceTexture;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000014import android.media.MediaCodec;
buildbot@webrtc.orga09a9992014-08-13 17:26:08 +000015import android.media.MediaCodecInfo;
glaznev@webrtc.org99678452014-09-15 17:52:42 +000016import android.media.MediaCodecInfo.CodecCapabilities;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000017import android.media.MediaCodecList;
18import android.media.MediaFormat;
19import android.os.Build;
Per488e75f2015-11-19 10:43:36 +010020import android.os.SystemClock;
glaznev@webrtc.org99678452014-09-15 17:52:42 +000021import android.view.Surface;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000022import java.nio.ByteBuffer;
Magnus Jedvert6062f372017-11-16 16:53:12 +010023import java.util.ArrayDeque;
Alex Leung5b6891a2018-01-18 10:01:14 -080024import java.util.ArrayList;
Magnus Jedvert7e319372015-10-02 15:49:38 +020025import java.util.Arrays;
Alex Glazneveee86a62016-01-29 14:17:07 -080026import java.util.HashSet;
Magnus Jedvert91b348c2015-10-07 22:57:06 +020027import java.util.List;
Sami Kalliomakid3235f02016-08-02 15:44:04 +020028import java.util.Queue;
Alex Glazneveee86a62016-01-29 14:17:07 -080029import java.util.Set;
Alex Glaznev5c3da4b2015-10-30 15:31:07 -070030import java.util.concurrent.CountDownLatch;
Per488e75f2015-11-19 10:43:36 +010031import java.util.concurrent.TimeUnit;
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010032import javax.annotation.Nullable;
Magnus Jedvertb9ac1212018-04-23 11:29:05 +020033import org.webrtc.EglBase;
34import org.webrtc.VideoFrame;
magjed8c425aa2015-10-22 16:52:39 -070035
Magnus Jedvert9060eb12017-12-12 12:52:54 +010036// Java-side of peerconnection.cc:MediaCodecVideoDecoder.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000037// This class is an implementation detail of the Java PeerConnection API.
Patrik Höglund68876f92015-11-12 17:36:48 +010038@SuppressWarnings("deprecation")
Alex Glaznev908e77b2015-04-22 09:25:34 -070039public class MediaCodecVideoDecoder {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +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::VideoDecoder API as closely as
43 // possibly to minimize the amount of translation work necessary.
44
45 private static final String TAG = "MediaCodecVideoDecoder";
magjedcedddbd2016-02-26 09:36:04 -080046 private static final long MAX_DECODE_TIME_MS = 200;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000047
magjed0c29de52017-03-02 00:55:32 -080048 // TODO(magjed): Use MediaFormat constants when part of the public API.
49 private static final String FORMAT_KEY_STRIDE = "stride";
50 private static final String FORMAT_KEY_SLICE_HEIGHT = "slice-height";
51 private static final String FORMAT_KEY_CROP_LEFT = "crop-left";
52 private static final String FORMAT_KEY_CROP_RIGHT = "crop-right";
53 private static final String FORMAT_KEY_CROP_TOP = "crop-top";
54 private static final String FORMAT_KEY_CROP_BOTTOM = "crop-bottom";
55
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000056 // Tracks webrtc::VideoCodecType.
Magnus Jedvert655e1962017-12-08 11:05:22 +010057 public enum VideoCodecType {
Niels Möller520ca4e2018-06-04 11:14:38 +020058 VIDEO_CODEC_UNKNOWN,
Magnus Jedvert655e1962017-12-08 11:05:22 +010059 VIDEO_CODEC_VP8,
60 VIDEO_CODEC_VP9,
61 VIDEO_CODEC_H264;
62
63 @CalledByNative("VideoCodecType")
64 static VideoCodecType fromNativeIndex(int nativeIndex) {
65 return values()[nativeIndex];
66 }
67 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000068
Alex Glazneveee86a62016-01-29 14:17:07 -080069 // Timeout for input buffer dequeue.
70 private static final int DEQUEUE_INPUT_TIMEOUT = 500000;
71 // Timeout for codec releasing.
72 private static final int MEDIA_CODEC_RELEASE_TIMEOUT_MS = 5000;
73 // Max number of output buffers queued before starting to drop decoded frames.
74 private static final int MAX_QUEUED_OUTPUTBUFFERS = 3;
Alex Glaznevc6aec4b2015-10-19 16:39:19 -070075 // Active running decoder instance. Set in initDecode() (called from native code)
76 // and reset to null in release() call.
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010077 @Nullable private static MediaCodecVideoDecoder runningInstance = null;
78 @Nullable private static MediaCodecVideoDecoderErrorCallback errorCallback = null;
Alex Glaznev5c3da4b2015-10-30 15:31:07 -070079 private static int codecErrors = 0;
Alex Glazneveee86a62016-01-29 14:17:07 -080080 // List of disabled codec types - can be set from application.
81 private static Set<String> hwDecoderDisabledTypes = new HashSet<String>();
Alex Glaznev5c3da4b2015-10-30 15:31:07 -070082
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010083 @Nullable private Thread mediaCodecThread;
84 @Nullable private MediaCodec mediaCodec;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000085 private ByteBuffer[] inputBuffers;
86 private ByteBuffer[] outputBuffers;
87 private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8";
Alex Glaznev69a7fd52015-11-10 10:25:40 -080088 private static final String VP9_MIME_TYPE = "video/x-vnd.on2.vp9";
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000089 private static final String H264_MIME_TYPE = "video/avc";
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000090 // List of supported HW VP8 decoders.
Alex Leung5b6891a2018-01-18 10:01:14 -080091 private static final String[] supportedVp8HwCodecPrefixes() {
92 ArrayList<String> supportedPrefixes = new ArrayList<String>();
93 supportedPrefixes.add("OMX.qcom.");
94 supportedPrefixes.add("OMX.Nvidia.");
95 supportedPrefixes.add("OMX.Exynos.");
96 supportedPrefixes.add("OMX.Intel.");
97 if (PeerConnectionFactory.fieldTrialsFindFullName("WebRTC-MediaTekVP8").equals("Enabled")
98 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
99 supportedPrefixes.add("OMX.MTK.");
100 }
101 return supportedPrefixes.toArray(new String[supportedPrefixes.size()]);
102 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800103 // List of supported HW VP9 decoders.
sakalb6760f92016-09-29 04:12:44 -0700104 private static final String[] supportedVp9HwCodecPrefixes = {"OMX.qcom.", "OMX.Exynos."};
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000105 // List of supported HW H.264 decoders.
Alex Leung5b6891a2018-01-18 10:01:14 -0800106 private static final String[] supportedH264HwCodecPrefixes() {
107 ArrayList<String> supportedPrefixes = new ArrayList<String>();
108 supportedPrefixes.add("OMX.qcom.");
109 supportedPrefixes.add("OMX.Intel.");
110 supportedPrefixes.add("OMX.Exynos.");
111 if (PeerConnectionFactory.fieldTrialsFindFullName("WebRTC-MediaTekH264").equals("Enabled")
Alex Leung28e71072018-02-05 13:42:48 -0800112 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
Alex Leung5b6891a2018-01-18 10:01:14 -0800113 supportedPrefixes.add("OMX.MTK.");
114 }
115 return supportedPrefixes.toArray(new String[supportedPrefixes.size()]);
116 }
117
glaznev0c1d0602017-01-27 12:24:24 -0800118 // List of supported HW H.264 high profile decoders.
glaznevcca0f6c2017-06-14 10:20:54 -0700119 private static final String supportedQcomH264HighProfileHwCodecPrefix = "OMX.qcom.";
120 private static final String supportedExynosH264HighProfileHwCodecPrefix = "OMX.Exynos.";
Alex Leung5b6891a2018-01-18 10:01:14 -0800121 private static final String supportedMediaTekH264HighProfileHwCodecPrefix = "OMX.MTK.";
glaznev893a7ee2016-09-22 10:44:30 -0700122
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000123 // NV12 color format supported by QCOM codec, but not declared in MediaCodec -
124 // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
glaznev893a7ee2016-09-22 10:44:30 -0700125 private static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar32m4ka = 0x7FA30C01;
126 private static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar16m4ka = 0x7FA30C02;
127 private static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar64x32Tile2m8ka = 0x7FA30C03;
128 private static final int COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000129 // Allowable color formats supported by codec - in order of preference.
Magnus Jedvert7e319372015-10-02 15:49:38 +0200130 private static final List<Integer> supportedColorList = Arrays.asList(
sakalb6760f92016-09-29 04:12:44 -0700131 CodecCapabilities.COLOR_FormatYUV420Planar, CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
132 CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
133 COLOR_QCOM_FORMATYVU420PackedSemiPlanar32m4ka, COLOR_QCOM_FORMATYVU420PackedSemiPlanar16m4ka,
134 COLOR_QCOM_FORMATYVU420PackedSemiPlanar64x32Tile2m8ka,
135 COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m);
glaznev893a7ee2016-09-22 10:44:30 -0700136
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000137 private int colorFormat;
138 private int width;
139 private int height;
140 private int stride;
141 private int sliceHeight;
Per488e75f2015-11-19 10:43:36 +0100142 private boolean hasDecodedFirstFrame;
Magnus Jedvert6062f372017-11-16 16:53:12 +0100143 private final Queue<TimeStamps> decodeStartTimeMs = new ArrayDeque<TimeStamps>();
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000144 private boolean useSurface;
Perc01c2542015-11-13 16:58:26 +0100145
Per488e75f2015-11-19 10:43:36 +0100146 // The below variables are only used when decoding to a Surface.
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100147 @Nullable private TextureListener textureListener;
Per488e75f2015-11-19 10:43:36 +0100148 private int droppedFrames;
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100149 @Nullable private Surface surface = null;
sakalb6760f92016-09-29 04:12:44 -0700150 private final Queue<DecodedOutputBuffer> dequeuedSurfaceOutputBuffers =
Magnus Jedvert6062f372017-11-16 16:53:12 +0100151 new ArrayDeque<DecodedOutputBuffer>();
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000152
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700153 // MediaCodec error handler - invoked when critical error happens which may prevent
154 // further use of media codec API. Now it means that one of media codec instances
155 // is hanging and can no longer be used in the next call.
156 public static interface MediaCodecVideoDecoderErrorCallback {
157 void onMediaCodecVideoDecoderCriticalError(int codecErrors);
158 }
159
160 public static void setErrorCallback(MediaCodecVideoDecoderErrorCallback errorCallback) {
161 Logging.d(TAG, "Set error callback");
162 MediaCodecVideoDecoder.errorCallback = errorCallback;
163 }
164
Alex Glazneveee86a62016-01-29 14:17:07 -0800165 // Functions to disable HW decoding - can be called from applications for platforms
166 // which have known HW decoding problems.
167 public static void disableVp8HwCodec() {
168 Logging.w(TAG, "VP8 decoding is disabled by application.");
169 hwDecoderDisabledTypes.add(VP8_MIME_TYPE);
170 }
171
172 public static void disableVp9HwCodec() {
173 Logging.w(TAG, "VP9 decoding is disabled by application.");
174 hwDecoderDisabledTypes.add(VP9_MIME_TYPE);
175 }
176
177 public static void disableH264HwCodec() {
178 Logging.w(TAG, "H.264 decoding is disabled by application.");
179 hwDecoderDisabledTypes.add(H264_MIME_TYPE);
180 }
181
182 // Functions to query if HW decoding is supported.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100183 @CalledByNativeUnchecked
Alex Glazneveee86a62016-01-29 14:17:07 -0800184 public static boolean isVp8HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700185 return !hwDecoderDisabledTypes.contains(VP8_MIME_TYPE)
Alex Leung5b6891a2018-01-18 10:01:14 -0800186 && (findDecoder(VP8_MIME_TYPE, supportedVp8HwCodecPrefixes()) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800187 }
188
Magnus Jedvert655e1962017-12-08 11:05:22 +0100189 @CalledByNativeUnchecked
Alex Glazneveee86a62016-01-29 14:17:07 -0800190 public static boolean isVp9HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700191 return !hwDecoderDisabledTypes.contains(VP9_MIME_TYPE)
192 && (findDecoder(VP9_MIME_TYPE, supportedVp9HwCodecPrefixes) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800193 }
194
Magnus Jedvert655e1962017-12-08 11:05:22 +0100195 @CalledByNativeUnchecked
Alex Glazneveee86a62016-01-29 14:17:07 -0800196 public static boolean isH264HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700197 return !hwDecoderDisabledTypes.contains(H264_MIME_TYPE)
Alex Leung5b6891a2018-01-18 10:01:14 -0800198 && (findDecoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes()) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800199 }
200
Magnus Jedvert655e1962017-12-08 11:05:22 +0100201 @CalledByNative
glaznev0c1d0602017-01-27 12:24:24 -0800202 public static boolean isH264HighProfileHwSupported() {
glaznevcca0f6c2017-06-14 10:20:54 -0700203 if (hwDecoderDisabledTypes.contains(H264_MIME_TYPE)) {
204 return false;
205 }
206 // Support H.264 HP decoding on QCOM chips for Android L and above.
207 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
208 && findDecoder(H264_MIME_TYPE, new String[] {supportedQcomH264HighProfileHwCodecPrefix})
209 != null) {
210 return true;
211 }
212 // Support H.264 HP decoding on Exynos chips for Android M and above.
213 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
214 && findDecoder(H264_MIME_TYPE, new String[] {supportedExynosH264HighProfileHwCodecPrefix})
215 != null) {
216 return true;
217 }
Alex Leung28e71072018-02-05 13:42:48 -0800218 // Support H.264 HP decoding on MediaTek chips for Android O_MR1 and above
Alex Leung5b6891a2018-01-18 10:01:14 -0800219 if (PeerConnectionFactory.fieldTrialsFindFullName("WebRTC-MediaTekH264").equals("Enabled")
Alex Leung28e71072018-02-05 13:42:48 -0800220 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1
Alex Leung5b6891a2018-01-18 10:01:14 -0800221 && findDecoder(H264_MIME_TYPE, new String[] {supportedMediaTekH264HighProfileHwCodecPrefix})
222 != null) {
223 return true;
224 }
glaznevcca0f6c2017-06-14 10:20:54 -0700225 return false;
glaznev0c1d0602017-01-27 12:24:24 -0800226 }
227
Alex Glazneveee86a62016-01-29 14:17:07 -0800228 public static void printStackTrace() {
229 if (runningInstance != null && runningInstance.mediaCodecThread != null) {
230 StackTraceElement[] mediaCodecStackTraces = runningInstance.mediaCodecThread.getStackTrace();
231 if (mediaCodecStackTraces.length > 0) {
232 Logging.d(TAG, "MediaCodecVideoDecoder stacks trace:");
233 for (StackTraceElement stackTrace : mediaCodecStackTraces) {
234 Logging.d(TAG, stackTrace.toString());
235 }
236 }
237 }
238 }
239
240 // Helper struct for findDecoder() below.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000241 private static class DecoderProperties {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000242 public DecoderProperties(String codecName, int colorFormat) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000243 this.codecName = codecName;
244 this.colorFormat = colorFormat;
245 }
246 public final String codecName; // OpenMax component name for VP8 codec.
sakalb6760f92016-09-29 04:12:44 -0700247 public final int colorFormat; // Color format supported by codec.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000248 }
249
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100250 private static @Nullable DecoderProperties findDecoder(
251 String mime, String[] supportedCodecPrefixes) {
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000252 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000253 return null; // MediaCodec.setParameters is missing.
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000254 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800255 Logging.d(TAG, "Trying to find HW decoder for mime " + mime);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000256 for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
Alex Glaznev0060c562016-08-08 12:27:24 -0700257 MediaCodecInfo info = null;
258 try {
259 info = MediaCodecList.getCodecInfoAt(i);
260 } catch (IllegalArgumentException e) {
sakalb6760f92016-09-29 04:12:44 -0700261 Logging.e(TAG, "Cannot retrieve decoder codec info", e);
Alex Glaznev0060c562016-08-08 12:27:24 -0700262 }
263 if (info == null || info.isEncoder()) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000264 continue;
265 }
266 String name = null;
267 for (String mimeType : info.getSupportedTypes()) {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000268 if (mimeType.equals(mime)) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000269 name = info.getName();
270 break;
271 }
272 }
273 if (name == null) {
sakalb6760f92016-09-29 04:12:44 -0700274 continue; // No HW support in this codec; try the next one.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000275 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800276 Logging.d(TAG, "Found candidate decoder " + name);
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000277
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000278 // Check if this is supported decoder.
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000279 boolean supportedCodec = false;
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000280 for (String codecPrefix : supportedCodecPrefixes) {
281 if (name.startsWith(codecPrefix)) {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000282 supportedCodec = true;
283 break;
284 }
285 }
286 if (!supportedCodec) {
287 continue;
288 }
289
290 // Check if codec supports either yuv420 or nv12.
Alex Glaznev0060c562016-08-08 12:27:24 -0700291 CodecCapabilities capabilities;
292 try {
293 capabilities = info.getCapabilitiesForType(mime);
294 } catch (IllegalArgumentException e) {
sakalb6760f92016-09-29 04:12:44 -0700295 Logging.e(TAG, "Cannot retrieve decoder capabilities", e);
Alex Glaznev0060c562016-08-08 12:27:24 -0700296 continue;
297 }
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000298 for (int colorFormat : capabilities.colorFormats) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700299 Logging.v(TAG, " Color: 0x" + Integer.toHexString(colorFormat));
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000300 }
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000301 for (int supportedColorFormat : supportedColorList) {
302 for (int codecColorFormat : capabilities.colorFormats) {
303 if (codecColorFormat == supportedColorFormat) {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000304 // Found supported HW decoder.
sakalb6760f92016-09-29 04:12:44 -0700305 Logging.d(TAG, "Found target decoder " + name + ". Color: 0x"
306 + Integer.toHexString(codecColorFormat));
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000307 return new DecoderProperties(name, codecColorFormat);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000308 }
309 }
310 }
311 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800312 Logging.d(TAG, "No HW decoder found for mime " + mime);
sakalb6760f92016-09-29 04:12:44 -0700313 return null; // No HW decoder.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000314 }
315
Magnus Jedvert655e1962017-12-08 11:05:22 +0100316 @CalledByNative
317 MediaCodecVideoDecoder() {}
318
Magnus Jedvert7e319372015-10-02 15:49:38 +0200319 private void checkOnMediaCodecThread() throws IllegalStateException {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000320 if (mediaCodecThread.getId() != Thread.currentThread().getId()) {
sakalb6760f92016-09-29 04:12:44 -0700321 throw new IllegalStateException("MediaCodecVideoDecoder previously operated on "
322 + mediaCodecThread + " but is now called on " + Thread.currentThread());
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000323 }
324 }
325
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200326 // Pass null in |eglContext| to configure the codec for ByteBuffer output.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100327 @CalledByNativeUnchecked
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200328 private boolean initDecode(
329 VideoCodecType type, int width, int height, @Nullable EglBase.Context eglContext) {
Alejandro Luebs69ddaef2015-10-09 15:46:09 -0700330 if (mediaCodecThread != null) {
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800331 throw new RuntimeException("initDecode: Forgot to release()?");
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000332 }
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800333
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000334 String mime = null;
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200335 useSurface = (eglContext != null);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000336 String[] supportedCodecPrefixes = null;
337 if (type == VideoCodecType.VIDEO_CODEC_VP8) {
338 mime = VP8_MIME_TYPE;
Alex Leung5b6891a2018-01-18 10:01:14 -0800339 supportedCodecPrefixes = supportedVp8HwCodecPrefixes();
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800340 } else if (type == VideoCodecType.VIDEO_CODEC_VP9) {
341 mime = VP9_MIME_TYPE;
342 supportedCodecPrefixes = supportedVp9HwCodecPrefixes;
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000343 } else if (type == VideoCodecType.VIDEO_CODEC_H264) {
344 mime = H264_MIME_TYPE;
Alex Leung5b6891a2018-01-18 10:01:14 -0800345 supportedCodecPrefixes = supportedH264HwCodecPrefixes();
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000346 } else {
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800347 throw new RuntimeException("initDecode: Non-supported codec " + type);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000348 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000349 DecoderProperties properties = findDecoder(mime, supportedCodecPrefixes);
350 if (properties == null) {
351 throw new RuntimeException("Cannot find HW decoder for " + type);
352 }
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800353
sakalb6760f92016-09-29 04:12:44 -0700354 Logging.d(TAG, "Java initDecode: " + type + " : " + width + " x " + height + ". Color: 0x"
355 + Integer.toHexString(properties.colorFormat) + ". Use Surface: " + useSurface);
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800356
Alex Glaznevc6aec4b2015-10-19 16:39:19 -0700357 runningInstance = this; // Decoder is now running and can be queried for stack traces.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000358 mediaCodecThread = Thread.currentThread();
359 try {
360 this.width = width;
361 this.height = height;
362 stride = width;
363 sliceHeight = height;
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000364
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200365 if (useSurface) {
366 @Nullable
367 final SurfaceTextureHelper surfaceTextureHelper =
368 SurfaceTextureHelper.create("Decoder SurfaceTextureHelper", eglContext);
369 if (surfaceTextureHelper != null) {
370 textureListener = new TextureListener(surfaceTextureHelper);
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200371 textureListener.setSize(width, height);
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200372 surface = new Surface(surfaceTextureHelper.getSurfaceTexture());
373 }
Magnus Jedvert80cf97c2015-06-11 10:08:59 +0200374 }
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000375
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000376 MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000377 if (!useSurface) {
378 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
379 }
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700380 Logging.d(TAG, " Format: " + format);
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800381 mediaCodec = MediaCodecVideoEncoder.createByCodecName(properties.codecName);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000382 if (mediaCodec == null) {
Alex Glaznev325d4142015-10-12 14:56:02 -0700383 Logging.e(TAG, "Can not create media decoder");
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000384 return false;
385 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200386 mediaCodec.configure(format, surface, null, 0);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000387 mediaCodec.start();
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800388
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000389 colorFormat = properties.colorFormat;
390 outputBuffers = mediaCodec.getOutputBuffers();
391 inputBuffers = mediaCodec.getInputBuffers();
Per488e75f2015-11-19 10:43:36 +0100392 decodeStartTimeMs.clear();
393 hasDecodedFirstFrame = false;
394 dequeuedSurfaceOutputBuffers.clear();
395 droppedFrames = 0;
sakalb6760f92016-09-29 04:12:44 -0700396 Logging.d(TAG,
397 "Input buffers: " + inputBuffers.length + ". Output buffers: " + outputBuffers.length);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000398 return true;
399 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700400 Logging.e(TAG, "initDecode failed", e);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000401 return false;
402 }
403 }
404
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800405 // Resets the decoder so it can start decoding frames with new resolution.
406 // Flushes MediaCodec and clears decoder output buffers.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100407 @CalledByNativeUnchecked
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800408 private void reset(int width, int height) {
409 if (mediaCodecThread == null || mediaCodec == null) {
410 throw new RuntimeException("Incorrect reset call for non-initialized decoder.");
411 }
412 Logging.d(TAG, "Java reset: " + width + " x " + height);
413
414 mediaCodec.flush();
415
416 this.width = width;
417 this.height = height;
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200418 if (textureListener != null) {
419 textureListener.setSize(width, height);
420 }
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800421 decodeStartTimeMs.clear();
422 dequeuedSurfaceOutputBuffers.clear();
423 hasDecodedFirstFrame = false;
424 droppedFrames = 0;
425 }
426
Magnus Jedvert655e1962017-12-08 11:05:22 +0100427 @CalledByNativeUnchecked
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000428 private void release() {
Per488e75f2015-11-19 10:43:36 +0100429 Logging.d(TAG, "Java releaseDecoder. Total number of dropped frames: " + droppedFrames);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000430 checkOnMediaCodecThread();
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700431
432 // Run Mediacodec stop() and release() on separate thread since sometime
433 // Mediacodec.stop() may hang.
434 final CountDownLatch releaseDone = new CountDownLatch(1);
435
436 Runnable runMediaCodecRelease = new Runnable() {
437 @Override
438 public void run() {
439 try {
440 Logging.d(TAG, "Java releaseDecoder on release thread");
441 mediaCodec.stop();
442 mediaCodec.release();
443 Logging.d(TAG, "Java releaseDecoder on release thread done");
444 } catch (Exception e) {
445 Logging.e(TAG, "Media decoder release failed", e);
446 }
447 releaseDone.countDown();
448 }
449 };
450 new Thread(runMediaCodecRelease).start();
451
452 if (!ThreadUtils.awaitUninterruptibly(releaseDone, MEDIA_CODEC_RELEASE_TIMEOUT_MS)) {
453 Logging.e(TAG, "Media decoder release timeout");
454 codecErrors++;
455 if (errorCallback != null) {
456 Logging.e(TAG, "Invoke codec error callback. Errors: " + codecErrors);
457 errorCallback.onMediaCodecVideoDecoderCriticalError(codecErrors);
458 }
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000459 }
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700460
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000461 mediaCodec = null;
462 mediaCodecThread = null;
Alex Glaznevc6aec4b2015-10-19 16:39:19 -0700463 runningInstance = null;
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000464 if (useSurface) {
465 surface.release();
Magnus Jedvert7e319372015-10-02 15:49:38 +0200466 surface = null;
Per488e75f2015-11-19 10:43:36 +0100467 textureListener.release();
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000468 }
Alex Glaznev325d4142015-10-12 14:56:02 -0700469 Logging.d(TAG, "Java releaseDecoder done");
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000470 }
471
472 // Dequeue an input buffer and return its index, -1 if no input buffer is
473 // available, or -2 if the codec is no longer operative.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100474 @CalledByNativeUnchecked
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000475 private int dequeueInputBuffer() {
476 checkOnMediaCodecThread();
477 try {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000478 return mediaCodec.dequeueInputBuffer(DEQUEUE_INPUT_TIMEOUT);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000479 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700480 Logging.e(TAG, "dequeueIntputBuffer failed", e);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000481 return -2;
482 }
483 }
484
Magnus Jedvert655e1962017-12-08 11:05:22 +0100485 @CalledByNativeUnchecked
Per488e75f2015-11-19 10:43:36 +0100486 private boolean queueInputBuffer(int inputBufferIndex, int size, long presentationTimeStamUs,
487 long timeStampMs, long ntpTimeStamp) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000488 checkOnMediaCodecThread();
489 try {
490 inputBuffers[inputBufferIndex].position(0);
491 inputBuffers[inputBufferIndex].limit(size);
sakalb6760f92016-09-29 04:12:44 -0700492 decodeStartTimeMs.add(
493 new TimeStamps(SystemClock.elapsedRealtime(), timeStampMs, ntpTimeStamp));
Per488e75f2015-11-19 10:43:36 +0100494 mediaCodec.queueInputBuffer(inputBufferIndex, 0, size, presentationTimeStamUs, 0);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000495 return true;
sakalb6760f92016-09-29 04:12:44 -0700496 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700497 Logging.e(TAG, "decode failed", e);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000498 return false;
499 }
500 }
501
Per488e75f2015-11-19 10:43:36 +0100502 private static class TimeStamps {
503 public TimeStamps(long decodeStartTimeMs, long timeStampMs, long ntpTimeStampMs) {
504 this.decodeStartTimeMs = decodeStartTimeMs;
505 this.timeStampMs = timeStampMs;
506 this.ntpTimeStampMs = ntpTimeStampMs;
507 }
Alex Glazneveee86a62016-01-29 14:17:07 -0800508 // Time when this frame was queued for decoding.
509 private final long decodeStartTimeMs;
510 // Only used for bookkeeping in Java. Stores C++ inputImage._timeStamp value for input frame.
511 private final long timeStampMs;
512 // Only used for bookkeeping in Java. Stores C++ inputImage.ntp_time_ms_ value for input frame.
513 private final long ntpTimeStampMs;
Per488e75f2015-11-19 10:43:36 +0100514 }
515
516 // Helper struct for dequeueOutputBuffer() below.
517 private static class DecodedOutputBuffer {
glaznev94291482016-02-01 13:17:18 -0800518 public DecodedOutputBuffer(int index, int offset, int size, long presentationTimeStampMs,
519 long timeStampMs, long ntpTimeStampMs, long decodeTime, long endDecodeTime) {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000520 this.index = index;
521 this.offset = offset;
522 this.size = size;
glaznev94291482016-02-01 13:17:18 -0800523 this.presentationTimeStampMs = presentationTimeStampMs;
Per488e75f2015-11-19 10:43:36 +0100524 this.timeStampMs = timeStampMs;
525 this.ntpTimeStampMs = ntpTimeStampMs;
526 this.decodeTimeMs = decodeTime;
527 this.endDecodeTimeMs = endDecodeTime;
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000528 }
529
530 private final int index;
531 private final int offset;
532 private final int size;
glaznev94291482016-02-01 13:17:18 -0800533 // Presentation timestamp returned in dequeueOutputBuffer call.
534 private final long presentationTimeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800535 // C++ inputImage._timeStamp value for output frame.
Per488e75f2015-11-19 10:43:36 +0100536 private final long timeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800537 // C++ inputImage.ntp_time_ms_ value for output frame.
Per488e75f2015-11-19 10:43:36 +0100538 private final long ntpTimeStampMs;
539 // Number of ms it took to decode this frame.
540 private final long decodeTimeMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800541 // System time when this frame decoding finished.
Per488e75f2015-11-19 10:43:36 +0100542 private final long endDecodeTimeMs;
Magnus Jedvert655e1962017-12-08 11:05:22 +0100543
544 @CalledByNative("DecodedOutputBuffer")
545 int getIndex() {
546 return index;
547 }
548
549 @CalledByNative("DecodedOutputBuffer")
550 int getOffset() {
551 return offset;
552 }
553
554 @CalledByNative("DecodedOutputBuffer")
555 int getSize() {
556 return size;
557 }
558
559 @CalledByNative("DecodedOutputBuffer")
560 long getPresentationTimestampMs() {
561 return presentationTimeStampMs;
562 }
563
564 @CalledByNative("DecodedOutputBuffer")
565 long getTimestampMs() {
566 return timeStampMs;
567 }
568
569 @CalledByNative("DecodedOutputBuffer")
570 long getNtpTimestampMs() {
571 return ntpTimeStampMs;
572 }
573
574 @CalledByNative("DecodedOutputBuffer")
575 long getDecodeTimeMs() {
576 return decodeTimeMs;
577 }
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000578 }
579
Per488e75f2015-11-19 10:43:36 +0100580 // Helper struct for dequeueTextureBuffer() below.
magjed44bf6f52015-10-03 02:08:00 -0700581 private static class DecodedTextureBuffer {
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200582 private final VideoFrame.Buffer videoFrameBuffer;
glaznev94291482016-02-01 13:17:18 -0800583 // Presentation timestamp returned in dequeueOutputBuffer call.
584 private final long presentationTimeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800585 // C++ inputImage._timeStamp value for output frame.
Per488e75f2015-11-19 10:43:36 +0100586 private final long timeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800587 // C++ inputImage.ntp_time_ms_ value for output frame.
Per488e75f2015-11-19 10:43:36 +0100588 private final long ntpTimeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800589 // Number of ms it took to decode this frame.
Per488e75f2015-11-19 10:43:36 +0100590 private final long decodeTimeMs;
591 // Interval from when the frame finished decoding until this buffer has been created.
592 // Since there is only one texture, this interval depend on the time from when
593 // a frame is decoded and provided to C++ and until that frame is returned to the MediaCodec
594 // so that the texture can be updated with the next decoded frame.
595 private final long frameDelayMs;
magjed44bf6f52015-10-03 02:08:00 -0700596
Per488e75f2015-11-19 10:43:36 +0100597 // A DecodedTextureBuffer with zero |textureID| has special meaning and represents a frame
598 // that was dropped.
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200599 public DecodedTextureBuffer(VideoFrame.Buffer videoFrameBuffer, long presentationTimeStampMs,
600 long timeStampMs, long ntpTimeStampMs, long decodeTimeMs, long frameDelay) {
601 this.videoFrameBuffer = videoFrameBuffer;
glaznev94291482016-02-01 13:17:18 -0800602 this.presentationTimeStampMs = presentationTimeStampMs;
Per488e75f2015-11-19 10:43:36 +0100603 this.timeStampMs = timeStampMs;
604 this.ntpTimeStampMs = ntpTimeStampMs;
605 this.decodeTimeMs = decodeTimeMs;
606 this.frameDelayMs = frameDelay;
magjed44bf6f52015-10-03 02:08:00 -0700607 }
Magnus Jedvert655e1962017-12-08 11:05:22 +0100608
609 @CalledByNative("DecodedTextureBuffer")
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200610 VideoFrame.Buffer getVideoFrameBuffer() {
611 return videoFrameBuffer;
Magnus Jedvert655e1962017-12-08 11:05:22 +0100612 }
613
614 @CalledByNative("DecodedTextureBuffer")
615 long getPresentationTimestampMs() {
616 return presentationTimeStampMs;
617 }
618
619 @CalledByNative("DecodedTextureBuffer")
620 long getTimeStampMs() {
621 return timeStampMs;
622 }
623
624 @CalledByNative("DecodedTextureBuffer")
625 long getNtpTimestampMs() {
626 return ntpTimeStampMs;
627 }
628
629 @CalledByNative("DecodedTextureBuffer")
630 long getDecodeTimeMs() {
631 return decodeTimeMs;
632 }
633
634 @CalledByNative("DecodedTextureBuffer")
635 long getFrameDelayMs() {
636 return frameDelayMs;
637 }
magjed44bf6f52015-10-03 02:08:00 -0700638 }
639
Per488e75f2015-11-19 10:43:36 +0100640 // Poll based texture listener.
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200641 private class TextureListener implements VideoSink {
Per488e75f2015-11-19 10:43:36 +0100642 private final SurfaceTextureHelper surfaceTextureHelper;
643 // |newFrameLock| is used to synchronize arrival of new frames with wait()/notifyAll().
644 private final Object newFrameLock = new Object();
645 // |bufferToRender| is non-null when waiting for transition between addBufferToRender() to
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200646 // onFrame().
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100647 @Nullable private DecodedOutputBuffer bufferToRender;
648 @Nullable private DecodedTextureBuffer renderedBuffer;
Per488e75f2015-11-19 10:43:36 +0100649
650 public TextureListener(SurfaceTextureHelper surfaceTextureHelper) {
651 this.surfaceTextureHelper = surfaceTextureHelper;
magjed81e8e372016-03-03 02:11:44 -0800652 surfaceTextureHelper.startListening(this);
Per488e75f2015-11-19 10:43:36 +0100653 }
654
655 public void addBufferToRender(DecodedOutputBuffer buffer) {
656 if (bufferToRender != null) {
sakalb6760f92016-09-29 04:12:44 -0700657 Logging.e(TAG, "Unexpected addBufferToRender() called while waiting for a texture.");
Per488e75f2015-11-19 10:43:36 +0100658 throw new IllegalStateException("Waiting for a texture.");
659 }
660 bufferToRender = buffer;
661 }
662
663 public boolean isWaitingForTexture() {
664 synchronized (newFrameLock) {
665 return bufferToRender != null;
666 }
667 }
668
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200669 public void setSize(int width, int height) {
670 surfaceTextureHelper.setTextureSize(width, height);
671 }
672
Per488e75f2015-11-19 10:43:36 +0100673 // Callback from |surfaceTextureHelper|. May be called on an arbitrary thread.
674 @Override
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200675 public void onFrame(VideoFrame frame) {
Per488e75f2015-11-19 10:43:36 +0100676 synchronized (newFrameLock) {
677 if (renderedBuffer != null) {
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200678 Logging.e(TAG, "Unexpected onFrame() called while already holding a texture.");
Per488e75f2015-11-19 10:43:36 +0100679 throw new IllegalStateException("Already holding a texture.");
680 }
681 // |timestampNs| is always zero on some Android versions.
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200682 final VideoFrame.Buffer buffer = frame.getBuffer();
683 buffer.retain();
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200684 renderedBuffer = new DecodedTextureBuffer(buffer, bufferToRender.presentationTimeStampMs,
685 bufferToRender.timeStampMs, bufferToRender.ntpTimeStampMs, bufferToRender.decodeTimeMs,
Per488e75f2015-11-19 10:43:36 +0100686 SystemClock.elapsedRealtime() - bufferToRender.endDecodeTimeMs);
687 bufferToRender = null;
688 newFrameLock.notifyAll();
689 }
690 }
691
692 // Dequeues and returns a DecodedTextureBuffer if available, or null otherwise.
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100693 @Nullable
Sami Kalliomäki9828beb2017-10-26 16:21:22 +0200694 @SuppressWarnings("WaitNotInLoop")
Per488e75f2015-11-19 10:43:36 +0100695 public DecodedTextureBuffer dequeueTextureBuffer(int timeoutMs) {
696 synchronized (newFrameLock) {
697 if (renderedBuffer == null && timeoutMs > 0 && isWaitingForTexture()) {
698 try {
699 newFrameLock.wait(timeoutMs);
sakalb6760f92016-09-29 04:12:44 -0700700 } catch (InterruptedException e) {
Per488e75f2015-11-19 10:43:36 +0100701 // Restore the interrupted status by reinterrupting the thread.
702 Thread.currentThread().interrupt();
703 }
704 }
705 DecodedTextureBuffer returnedBuffer = renderedBuffer;
706 renderedBuffer = null;
707 return returnedBuffer;
708 }
709 }
710
711 public void release() {
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200712 // SurfaceTextureHelper.stopListening() will block until any onFrame() in progress is done.
713 // Therefore, the call must be outside any synchronized statement that is also used in the
714 // onFrame() above to avoid deadlocks.
nissea44e72c2016-05-27 00:27:59 -0700715 surfaceTextureHelper.stopListening();
Per488e75f2015-11-19 10:43:36 +0100716 synchronized (newFrameLock) {
717 if (renderedBuffer != null) {
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200718 renderedBuffer.getVideoFrameBuffer().release();
Per488e75f2015-11-19 10:43:36 +0100719 renderedBuffer = null;
720 }
721 }
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200722 surfaceTextureHelper.dispose();
Per488e75f2015-11-19 10:43:36 +0100723 }
724 }
725
726 // Returns null if no decoded buffer is available, and otherwise a DecodedByteBuffer.
Magnus Jedvert7e319372015-10-02 15:49:38 +0200727 // Throws IllegalStateException if call is made on the wrong thread, if color format changes to an
728 // unsupported format, or if |mediaCodec| is not in the Executing state. Throws CodecException
729 // upon codec error.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100730 @CalledByNativeUnchecked
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100731 private @Nullable DecodedOutputBuffer dequeueOutputBuffer(int dequeueTimeoutMs) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000732 checkOnMediaCodecThread();
Per488e75f2015-11-19 10:43:36 +0100733 if (decodeStartTimeMs.isEmpty()) {
734 return null;
735 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200736 // Drain the decoder until receiving a decoded buffer or hitting
737 // MediaCodec.INFO_TRY_AGAIN_LATER.
738 final MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
739 while (true) {
sakalb6760f92016-09-29 04:12:44 -0700740 final int result =
741 mediaCodec.dequeueOutputBuffer(info, TimeUnit.MILLISECONDS.toMicros(dequeueTimeoutMs));
Magnus Jedvert7e319372015-10-02 15:49:38 +0200742 switch (result) {
Magnus Jedvert7e319372015-10-02 15:49:38 +0200743 case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000744 outputBuffers = mediaCodec.getOutputBuffers();
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700745 Logging.d(TAG, "Decoder output buffers changed: " + outputBuffers.length);
Per488e75f2015-11-19 10:43:36 +0100746 if (hasDecodedFirstFrame) {
747 throw new RuntimeException("Unexpected output buffer change event.");
748 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200749 break;
750 case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000751 MediaFormat format = mediaCodec.getOutputFormat();
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700752 Logging.d(TAG, "Decoder format changed: " + format.toString());
magjed0c29de52017-03-02 00:55:32 -0800753 final int newWidth;
754 final int newHeight;
755 if (format.containsKey(FORMAT_KEY_CROP_LEFT) && format.containsKey(FORMAT_KEY_CROP_RIGHT)
756 && format.containsKey(FORMAT_KEY_CROP_BOTTOM)
757 && format.containsKey(FORMAT_KEY_CROP_TOP)) {
758 newWidth = 1 + format.getInteger(FORMAT_KEY_CROP_RIGHT)
759 - format.getInteger(FORMAT_KEY_CROP_LEFT);
760 newHeight = 1 + format.getInteger(FORMAT_KEY_CROP_BOTTOM)
761 - format.getInteger(FORMAT_KEY_CROP_TOP);
762 } else {
763 newWidth = format.getInteger(MediaFormat.KEY_WIDTH);
764 newHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
Per488e75f2015-11-19 10:43:36 +0100765 }
magjed0c29de52017-03-02 00:55:32 -0800766 if (hasDecodedFirstFrame && (newWidth != width || newHeight != height)) {
767 throw new RuntimeException("Unexpected size change. Configured " + width + "*" + height
768 + ". New " + newWidth + "*" + newHeight);
769 }
770 width = newWidth;
771 height = newHeight;
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200772 if (textureListener != null) {
773 textureListener.setSize(width, height);
774 }
Per488e75f2015-11-19 10:43:36 +0100775
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000776 if (!useSurface && format.containsKey(MediaFormat.KEY_COLOR_FORMAT)) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000777 colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700778 Logging.d(TAG, "Color: 0x" + Integer.toHexString(colorFormat));
Magnus Jedvert7e319372015-10-02 15:49:38 +0200779 if (!supportedColorList.contains(colorFormat)) {
780 throw new IllegalStateException("Non supported color format: " + colorFormat);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000781 }
782 }
magjed0c29de52017-03-02 00:55:32 -0800783 if (format.containsKey(FORMAT_KEY_STRIDE)) {
784 stride = format.getInteger(FORMAT_KEY_STRIDE);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000785 }
magjed0c29de52017-03-02 00:55:32 -0800786 if (format.containsKey(FORMAT_KEY_SLICE_HEIGHT)) {
787 sliceHeight = format.getInteger(FORMAT_KEY_SLICE_HEIGHT);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000788 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200789 Logging.d(TAG, "Frame stride and slice height: " + stride + " x " + sliceHeight);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000790 stride = Math.max(width, stride);
791 sliceHeight = Math.max(height, sliceHeight);
Magnus Jedvert7e319372015-10-02 15:49:38 +0200792 break;
Per488e75f2015-11-19 10:43:36 +0100793 case MediaCodec.INFO_TRY_AGAIN_LATER:
794 return null;
Magnus Jedvert7e319372015-10-02 15:49:38 +0200795 default:
Per488e75f2015-11-19 10:43:36 +0100796 hasDecodedFirstFrame = true;
797 TimeStamps timeStamps = decodeStartTimeMs.remove();
magjedcedddbd2016-02-26 09:36:04 -0800798 long decodeTimeMs = SystemClock.elapsedRealtime() - timeStamps.decodeStartTimeMs;
799 if (decodeTimeMs > MAX_DECODE_TIME_MS) {
Alex Glaznevd5704842016-06-27 11:51:10 -0700800 Logging.e(TAG, "Very high decode time: " + decodeTimeMs + "ms"
sakalb6760f92016-09-29 04:12:44 -0700801 + ". Q size: " + decodeStartTimeMs.size()
802 + ". Might be caused by resuming H264 decoding after a pause.");
magjedcedddbd2016-02-26 09:36:04 -0800803 decodeTimeMs = MAX_DECODE_TIME_MS;
804 }
sakalb6760f92016-09-29 04:12:44 -0700805 return new DecodedOutputBuffer(result, info.offset, info.size,
806 TimeUnit.MICROSECONDS.toMillis(info.presentationTimeUs), timeStamps.timeStampMs,
807 timeStamps.ntpTimeStampMs, decodeTimeMs, SystemClock.elapsedRealtime());
808 }
perkj9cb89822015-11-11 03:27:01 -0800809 }
perkj9cb89822015-11-11 03:27:01 -0800810 }
811
Per488e75f2015-11-19 10:43:36 +0100812 // Returns null if no decoded buffer is available, and otherwise a DecodedTextureBuffer.
813 // Throws IllegalStateException if call is made on the wrong thread, if color format changes to an
814 // unsupported format, or if |mediaCodec| is not in the Executing state. Throws CodecException
815 // upon codec error. If |dequeueTimeoutMs| > 0, the oldest decoded frame will be dropped if
816 // a frame can't be returned.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100817 @CalledByNativeUnchecked
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100818 private @Nullable DecodedTextureBuffer dequeueTextureBuffer(int dequeueTimeoutMs) {
Per488e75f2015-11-19 10:43:36 +0100819 checkOnMediaCodecThread();
820 if (!useSurface) {
821 throw new IllegalStateException("dequeueTexture() called for byte buffer decoding.");
822 }
823 DecodedOutputBuffer outputBuffer = dequeueOutputBuffer(dequeueTimeoutMs);
824 if (outputBuffer != null) {
825 dequeuedSurfaceOutputBuffers.add(outputBuffer);
826 }
827
828 MaybeRenderDecodedTextureBuffer();
829 // Check if there is texture ready now by waiting max |dequeueTimeoutMs|.
830 DecodedTextureBuffer renderedBuffer = textureListener.dequeueTextureBuffer(dequeueTimeoutMs);
831 if (renderedBuffer != null) {
832 MaybeRenderDecodedTextureBuffer();
833 return renderedBuffer;
834 }
835
836 if ((dequeuedSurfaceOutputBuffers.size()
sakalb6760f92016-09-29 04:12:44 -0700837 >= Math.min(MAX_QUEUED_OUTPUTBUFFERS, outputBuffers.length)
838 || (dequeueTimeoutMs > 0 && !dequeuedSurfaceOutputBuffers.isEmpty()))) {
Per488e75f2015-11-19 10:43:36 +0100839 ++droppedFrames;
840 // Drop the oldest frame still in dequeuedSurfaceOutputBuffers.
841 // The oldest frame is owned by |textureListener| and can't be dropped since
842 // mediaCodec.releaseOutputBuffer has already been called.
843 final DecodedOutputBuffer droppedFrame = dequeuedSurfaceOutputBuffers.remove();
844 if (dequeueTimeoutMs > 0) {
perkj7baf79f2015-11-24 06:26:38 -0800845 // TODO(perkj): Re-add the below log when VideoRenderGUI has been removed or fixed to
846 // return the one and only texture even if it does not render.
glaznevae95ff32016-02-04 11:47:12 -0800847 Logging.w(TAG, "Draining decoder. Dropping frame with TS: "
sakalb6760f92016-09-29 04:12:44 -0700848 + droppedFrame.presentationTimeStampMs + ". Total number of dropped frames: "
849 + droppedFrames);
Per488e75f2015-11-19 10:43:36 +0100850 } else {
sakalb6760f92016-09-29 04:12:44 -0700851 Logging.w(TAG, "Too many output buffers " + dequeuedSurfaceOutputBuffers.size()
852 + ". Dropping frame with TS: " + droppedFrame.presentationTimeStampMs
853 + ". Total number of dropped frames: " + droppedFrames);
Per488e75f2015-11-19 10:43:36 +0100854 }
855
856 mediaCodec.releaseOutputBuffer(droppedFrame.index, false /* render */);
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200857 return new DecodedTextureBuffer(null /* videoFrameBuffer */,
858 droppedFrame.presentationTimeStampMs, droppedFrame.timeStampMs,
859 droppedFrame.ntpTimeStampMs, droppedFrame.decodeTimeMs,
Per488e75f2015-11-19 10:43:36 +0100860 SystemClock.elapsedRealtime() - droppedFrame.endDecodeTimeMs);
861 }
862 return null;
863 }
864
865 private void MaybeRenderDecodedTextureBuffer() {
866 if (dequeuedSurfaceOutputBuffers.isEmpty() || textureListener.isWaitingForTexture()) {
867 return;
868 }
869 // Get the first frame in the queue and render to the decoder output surface.
870 final DecodedOutputBuffer buffer = dequeuedSurfaceOutputBuffers.remove();
871 textureListener.addBufferToRender(buffer);
872 mediaCodec.releaseOutputBuffer(buffer.index, true /* render */);
873 }
874
magjed44bf6f52015-10-03 02:08:00 -0700875 // Release a dequeued output byte buffer back to the codec for re-use. Should only be called for
876 // non-surface decoding.
877 // Throws IllegalStateException if the call is made on the wrong thread, if codec is configured
878 // for surface decoding, or if |mediaCodec| is not in the Executing state. Throws
879 // MediaCodec.CodecException upon codec error.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100880 @CalledByNativeUnchecked
Per488e75f2015-11-19 10:43:36 +0100881 private void returnDecodedOutputBuffer(int index)
Magnus Jedvert7e319372015-10-02 15:49:38 +0200882 throws IllegalStateException, MediaCodec.CodecException {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000883 checkOnMediaCodecThread();
magjed44bf6f52015-10-03 02:08:00 -0700884 if (useSurface) {
Per488e75f2015-11-19 10:43:36 +0100885 throw new IllegalStateException("returnDecodedOutputBuffer() called for surface decoding.");
magjed44bf6f52015-10-03 02:08:00 -0700886 }
887 mediaCodec.releaseOutputBuffer(index, false /* render */);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000888 }
Magnus Jedvert655e1962017-12-08 11:05:22 +0100889
890 @CalledByNative
891 ByteBuffer[] getInputBuffers() {
892 return inputBuffers;
893 }
894
895 @CalledByNative
896 ByteBuffer[] getOutputBuffers() {
897 return outputBuffers;
898 }
899
900 @CalledByNative
901 int getColorFormat() {
902 return colorFormat;
903 }
904
905 @CalledByNative
906 int getWidth() {
907 return width;
908 }
909
910 @CalledByNative
911 int getHeight() {
912 return height;
913 }
914
915 @CalledByNative
916 int getStride() {
917 return stride;
918 }
919
920 @CalledByNative
921 int getSliceHeight() {
922 return sliceHeight;
923 }
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000924}