blob: 53308717ae077414d9b0a96720dc74b88ea1f989 [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;
Magnus Jedvert7e319372015-10-02 15:49:38 +020023import java.util.Arrays;
Alex Glazneveee86a62016-01-29 14:17:07 -080024import java.util.HashSet;
Magnus Jedvert91b348c2015-10-07 22:57:06 +020025import java.util.List;
Sami Kalliomakid3235f02016-08-02 15:44:04 +020026import java.util.Queue;
Alex Glazneveee86a62016-01-29 14:17:07 -080027import java.util.Set;
Alex Glaznev5c3da4b2015-10-30 15:31:07 -070028import java.util.concurrent.CountDownLatch;
Per488e75f2015-11-19 10:43:36 +010029import java.util.concurrent.TimeUnit;
magjed8c425aa2015-10-22 16:52:39 -070030
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000031// Java-side of peerconnection_jni.cc:MediaCodecVideoDecoder.
32// This class is an implementation detail of the Java PeerConnection API.
Patrik Höglund68876f92015-11-12 17:36:48 +010033@SuppressWarnings("deprecation")
Alex Glaznev908e77b2015-04-22 09:25:34 -070034public class MediaCodecVideoDecoder {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000035 // This class is constructed, operated, and destroyed by its C++ incarnation,
36 // so the class and its methods have non-public visibility. The API this
37 // class exposes aims to mimic the webrtc::VideoDecoder API as closely as
38 // possibly to minimize the amount of translation work necessary.
39
40 private static final String TAG = "MediaCodecVideoDecoder";
magjedcedddbd2016-02-26 09:36:04 -080041 private static final long MAX_DECODE_TIME_MS = 200;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000042
magjed0c29de52017-03-02 00:55:32 -080043 // TODO(magjed): Use MediaFormat constants when part of the public API.
44 private static final String FORMAT_KEY_STRIDE = "stride";
45 private static final String FORMAT_KEY_SLICE_HEIGHT = "slice-height";
46 private static final String FORMAT_KEY_CROP_LEFT = "crop-left";
47 private static final String FORMAT_KEY_CROP_RIGHT = "crop-right";
48 private static final String FORMAT_KEY_CROP_TOP = "crop-top";
49 private static final String FORMAT_KEY_CROP_BOTTOM = "crop-bottom";
50
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000051 // Tracks webrtc::VideoCodecType.
Magnus Jedvert655e1962017-12-08 11:05:22 +010052 public enum VideoCodecType {
53 VIDEO_CODEC_VP8,
54 VIDEO_CODEC_VP9,
55 VIDEO_CODEC_H264;
56
57 @CalledByNative("VideoCodecType")
58 static VideoCodecType fromNativeIndex(int nativeIndex) {
59 return values()[nativeIndex];
60 }
61 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000062
Alex Glazneveee86a62016-01-29 14:17:07 -080063 // Timeout for input buffer dequeue.
64 private static final int DEQUEUE_INPUT_TIMEOUT = 500000;
65 // Timeout for codec releasing.
66 private static final int MEDIA_CODEC_RELEASE_TIMEOUT_MS = 5000;
67 // Max number of output buffers queued before starting to drop decoded frames.
68 private static final int MAX_QUEUED_OUTPUTBUFFERS = 3;
Alex Glaznevc6aec4b2015-10-19 16:39:19 -070069 // Active running decoder instance. Set in initDecode() (called from native code)
70 // and reset to null in release() call.
71 private static MediaCodecVideoDecoder runningInstance = null;
Alex Glaznev5c3da4b2015-10-30 15:31:07 -070072 private static MediaCodecVideoDecoderErrorCallback errorCallback = null;
73 private static int codecErrors = 0;
Alex Glazneveee86a62016-01-29 14:17:07 -080074 // List of disabled codec types - can be set from application.
75 private static Set<String> hwDecoderDisabledTypes = new HashSet<String>();
Alex Glaznev5c3da4b2015-10-30 15:31:07 -070076
Alejandro Luebs69ddaef2015-10-09 15:46:09 -070077 private Thread mediaCodecThread;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000078 private MediaCodec mediaCodec;
79 private ByteBuffer[] inputBuffers;
80 private ByteBuffer[] outputBuffers;
81 private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8";
Alex Glaznev69a7fd52015-11-10 10:25:40 -080082 private static final String VP9_MIME_TYPE = "video/x-vnd.on2.vp9";
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000083 private static final String H264_MIME_TYPE = "video/avc";
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000084 // List of supported HW VP8 decoders.
sakalb6760f92016-09-29 04:12:44 -070085 private static final String[] supportedVp8HwCodecPrefixes = {
86 "OMX.qcom.", "OMX.Nvidia.", "OMX.Exynos.", "OMX.Intel."};
Alex Glaznev69a7fd52015-11-10 10:25:40 -080087 // List of supported HW VP9 decoders.
sakalb6760f92016-09-29 04:12:44 -070088 private static final String[] supportedVp9HwCodecPrefixes = {"OMX.qcom.", "OMX.Exynos."};
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000089 // List of supported HW H.264 decoders.
sakalb6760f92016-09-29 04:12:44 -070090 private static final String[] supportedH264HwCodecPrefixes = {
91 "OMX.qcom.", "OMX.Intel.", "OMX.Exynos."};
glaznev0c1d0602017-01-27 12:24:24 -080092 // List of supported HW H.264 high profile decoders.
glaznevcca0f6c2017-06-14 10:20:54 -070093 private static final String supportedQcomH264HighProfileHwCodecPrefix = "OMX.qcom.";
94 private static final String supportedExynosH264HighProfileHwCodecPrefix = "OMX.Exynos.";
glaznev893a7ee2016-09-22 10:44:30 -070095
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000096 // NV12 color format supported by QCOM codec, but not declared in MediaCodec -
97 // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
glaznev893a7ee2016-09-22 10:44:30 -070098 private static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar32m4ka = 0x7FA30C01;
99 private static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar16m4ka = 0x7FA30C02;
100 private static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar64x32Tile2m8ka = 0x7FA30C03;
101 private static final int COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000102 // Allowable color formats supported by codec - in order of preference.
Magnus Jedvert7e319372015-10-02 15:49:38 +0200103 private static final List<Integer> supportedColorList = Arrays.asList(
sakalb6760f92016-09-29 04:12:44 -0700104 CodecCapabilities.COLOR_FormatYUV420Planar, CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
105 CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
106 COLOR_QCOM_FORMATYVU420PackedSemiPlanar32m4ka, COLOR_QCOM_FORMATYVU420PackedSemiPlanar16m4ka,
107 COLOR_QCOM_FORMATYVU420PackedSemiPlanar64x32Tile2m8ka,
108 COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m);
glaznev893a7ee2016-09-22 10:44:30 -0700109
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000110 private int colorFormat;
111 private int width;
112 private int height;
113 private int stride;
114 private int sliceHeight;
Per488e75f2015-11-19 10:43:36 +0100115 private boolean hasDecodedFirstFrame;
Magnus Jedvert6062f372017-11-16 16:53:12 +0100116 private final Queue<TimeStamps> decodeStartTimeMs = new ArrayDeque<TimeStamps>();
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000117 private boolean useSurface;
Perc01c2542015-11-13 16:58:26 +0100118
Per488e75f2015-11-19 10:43:36 +0100119 // The below variables are only used when decoding to a Surface.
120 private TextureListener textureListener;
Per488e75f2015-11-19 10:43:36 +0100121 private int droppedFrames;
122 private Surface surface = null;
sakalb6760f92016-09-29 04:12:44 -0700123 private final Queue<DecodedOutputBuffer> dequeuedSurfaceOutputBuffers =
Magnus Jedvert6062f372017-11-16 16:53:12 +0100124 new ArrayDeque<DecodedOutputBuffer>();
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000125
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700126 // MediaCodec error handler - invoked when critical error happens which may prevent
127 // further use of media codec API. Now it means that one of media codec instances
128 // is hanging and can no longer be used in the next call.
129 public static interface MediaCodecVideoDecoderErrorCallback {
130 void onMediaCodecVideoDecoderCriticalError(int codecErrors);
131 }
132
133 public static void setErrorCallback(MediaCodecVideoDecoderErrorCallback errorCallback) {
134 Logging.d(TAG, "Set error callback");
135 MediaCodecVideoDecoder.errorCallback = errorCallback;
136 }
137
Alex Glazneveee86a62016-01-29 14:17:07 -0800138 // Functions to disable HW decoding - can be called from applications for platforms
139 // which have known HW decoding problems.
140 public static void disableVp8HwCodec() {
141 Logging.w(TAG, "VP8 decoding is disabled by application.");
142 hwDecoderDisabledTypes.add(VP8_MIME_TYPE);
143 }
144
145 public static void disableVp9HwCodec() {
146 Logging.w(TAG, "VP9 decoding is disabled by application.");
147 hwDecoderDisabledTypes.add(VP9_MIME_TYPE);
148 }
149
150 public static void disableH264HwCodec() {
151 Logging.w(TAG, "H.264 decoding is disabled by application.");
152 hwDecoderDisabledTypes.add(H264_MIME_TYPE);
153 }
154
155 // Functions to query if HW decoding is supported.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100156 @CalledByNativeUnchecked
Alex Glazneveee86a62016-01-29 14:17:07 -0800157 public static boolean isVp8HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700158 return !hwDecoderDisabledTypes.contains(VP8_MIME_TYPE)
159 && (findDecoder(VP8_MIME_TYPE, supportedVp8HwCodecPrefixes) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800160 }
161
Magnus Jedvert655e1962017-12-08 11:05:22 +0100162 @CalledByNativeUnchecked
Alex Glazneveee86a62016-01-29 14:17:07 -0800163 public static boolean isVp9HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700164 return !hwDecoderDisabledTypes.contains(VP9_MIME_TYPE)
165 && (findDecoder(VP9_MIME_TYPE, supportedVp9HwCodecPrefixes) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800166 }
167
Magnus Jedvert655e1962017-12-08 11:05:22 +0100168 @CalledByNativeUnchecked
Alex Glazneveee86a62016-01-29 14:17:07 -0800169 public static boolean isH264HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700170 return !hwDecoderDisabledTypes.contains(H264_MIME_TYPE)
171 && (findDecoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800172 }
173
Magnus Jedvert655e1962017-12-08 11:05:22 +0100174 @CalledByNative
glaznev0c1d0602017-01-27 12:24:24 -0800175 public static boolean isH264HighProfileHwSupported() {
glaznevcca0f6c2017-06-14 10:20:54 -0700176 if (hwDecoderDisabledTypes.contains(H264_MIME_TYPE)) {
177 return false;
178 }
179 // Support H.264 HP decoding on QCOM chips for Android L and above.
180 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
181 && findDecoder(H264_MIME_TYPE, new String[] {supportedQcomH264HighProfileHwCodecPrefix})
182 != null) {
183 return true;
184 }
185 // Support H.264 HP decoding on Exynos chips for Android M and above.
186 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
187 && findDecoder(H264_MIME_TYPE, new String[] {supportedExynosH264HighProfileHwCodecPrefix})
188 != null) {
189 return true;
190 }
191 return false;
glaznev0c1d0602017-01-27 12:24:24 -0800192 }
193
Alex Glazneveee86a62016-01-29 14:17:07 -0800194 public static void printStackTrace() {
195 if (runningInstance != null && runningInstance.mediaCodecThread != null) {
196 StackTraceElement[] mediaCodecStackTraces = runningInstance.mediaCodecThread.getStackTrace();
197 if (mediaCodecStackTraces.length > 0) {
198 Logging.d(TAG, "MediaCodecVideoDecoder stacks trace:");
199 for (StackTraceElement stackTrace : mediaCodecStackTraces) {
200 Logging.d(TAG, stackTrace.toString());
201 }
202 }
203 }
204 }
205
206 // Helper struct for findDecoder() below.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000207 private static class DecoderProperties {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000208 public DecoderProperties(String codecName, int colorFormat) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000209 this.codecName = codecName;
210 this.colorFormat = colorFormat;
211 }
212 public final String codecName; // OpenMax component name for VP8 codec.
sakalb6760f92016-09-29 04:12:44 -0700213 public final int colorFormat; // Color format supported by codec.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000214 }
215
sakalb6760f92016-09-29 04:12:44 -0700216 private static DecoderProperties findDecoder(String mime, String[] supportedCodecPrefixes) {
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000217 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000218 return null; // MediaCodec.setParameters is missing.
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000219 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800220 Logging.d(TAG, "Trying to find HW decoder for mime " + mime);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000221 for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
Alex Glaznev0060c562016-08-08 12:27:24 -0700222 MediaCodecInfo info = null;
223 try {
224 info = MediaCodecList.getCodecInfoAt(i);
225 } catch (IllegalArgumentException e) {
sakalb6760f92016-09-29 04:12:44 -0700226 Logging.e(TAG, "Cannot retrieve decoder codec info", e);
Alex Glaznev0060c562016-08-08 12:27:24 -0700227 }
228 if (info == null || info.isEncoder()) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000229 continue;
230 }
231 String name = null;
232 for (String mimeType : info.getSupportedTypes()) {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000233 if (mimeType.equals(mime)) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000234 name = info.getName();
235 break;
236 }
237 }
238 if (name == null) {
sakalb6760f92016-09-29 04:12:44 -0700239 continue; // No HW support in this codec; try the next one.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000240 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800241 Logging.d(TAG, "Found candidate decoder " + name);
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000242
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000243 // Check if this is supported decoder.
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000244 boolean supportedCodec = false;
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000245 for (String codecPrefix : supportedCodecPrefixes) {
246 if (name.startsWith(codecPrefix)) {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000247 supportedCodec = true;
248 break;
249 }
250 }
251 if (!supportedCodec) {
252 continue;
253 }
254
255 // Check if codec supports either yuv420 or nv12.
Alex Glaznev0060c562016-08-08 12:27:24 -0700256 CodecCapabilities capabilities;
257 try {
258 capabilities = info.getCapabilitiesForType(mime);
259 } catch (IllegalArgumentException e) {
sakalb6760f92016-09-29 04:12:44 -0700260 Logging.e(TAG, "Cannot retrieve decoder capabilities", e);
Alex Glaznev0060c562016-08-08 12:27:24 -0700261 continue;
262 }
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000263 for (int colorFormat : capabilities.colorFormats) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700264 Logging.v(TAG, " Color: 0x" + Integer.toHexString(colorFormat));
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000265 }
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000266 for (int supportedColorFormat : supportedColorList) {
267 for (int codecColorFormat : capabilities.colorFormats) {
268 if (codecColorFormat == supportedColorFormat) {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000269 // Found supported HW decoder.
sakalb6760f92016-09-29 04:12:44 -0700270 Logging.d(TAG, "Found target decoder " + name + ". Color: 0x"
271 + Integer.toHexString(codecColorFormat));
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000272 return new DecoderProperties(name, codecColorFormat);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000273 }
274 }
275 }
276 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800277 Logging.d(TAG, "No HW decoder found for mime " + mime);
sakalb6760f92016-09-29 04:12:44 -0700278 return null; // No HW decoder.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000279 }
280
Magnus Jedvert655e1962017-12-08 11:05:22 +0100281 @CalledByNative
282 MediaCodecVideoDecoder() {}
283
Magnus Jedvert7e319372015-10-02 15:49:38 +0200284 private void checkOnMediaCodecThread() throws IllegalStateException {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000285 if (mediaCodecThread.getId() != Thread.currentThread().getId()) {
sakalb6760f92016-09-29 04:12:44 -0700286 throw new IllegalStateException("MediaCodecVideoDecoder previously operated on "
287 + mediaCodecThread + " but is now called on " + Thread.currentThread());
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000288 }
289 }
290
Per488e75f2015-11-19 10:43:36 +0100291 // Pass null in |surfaceTextureHelper| to configure the codec for ByteBuffer output.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100292 @CalledByNativeUnchecked
Per488e75f2015-11-19 10:43:36 +0100293 private boolean initDecode(
sakalb6760f92016-09-29 04:12:44 -0700294 VideoCodecType type, int width, int height, SurfaceTextureHelper surfaceTextureHelper) {
Alejandro Luebs69ddaef2015-10-09 15:46:09 -0700295 if (mediaCodecThread != null) {
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800296 throw new RuntimeException("initDecode: Forgot to release()?");
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000297 }
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800298
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000299 String mime = null;
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800300 useSurface = (surfaceTextureHelper != null);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000301 String[] supportedCodecPrefixes = null;
302 if (type == VideoCodecType.VIDEO_CODEC_VP8) {
303 mime = VP8_MIME_TYPE;
304 supportedCodecPrefixes = supportedVp8HwCodecPrefixes;
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800305 } else if (type == VideoCodecType.VIDEO_CODEC_VP9) {
306 mime = VP9_MIME_TYPE;
307 supportedCodecPrefixes = supportedVp9HwCodecPrefixes;
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000308 } else if (type == VideoCodecType.VIDEO_CODEC_H264) {
309 mime = H264_MIME_TYPE;
310 supportedCodecPrefixes = supportedH264HwCodecPrefixes;
311 } else {
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800312 throw new RuntimeException("initDecode: Non-supported codec " + type);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000313 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000314 DecoderProperties properties = findDecoder(mime, supportedCodecPrefixes);
315 if (properties == null) {
316 throw new RuntimeException("Cannot find HW decoder for " + type);
317 }
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800318
sakalb6760f92016-09-29 04:12:44 -0700319 Logging.d(TAG, "Java initDecode: " + type + " : " + width + " x " + height + ". Color: 0x"
320 + Integer.toHexString(properties.colorFormat) + ". Use Surface: " + useSurface);
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800321
Alex Glaznevc6aec4b2015-10-19 16:39:19 -0700322 runningInstance = this; // Decoder is now running and can be queried for stack traces.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000323 mediaCodecThread = Thread.currentThread();
324 try {
325 this.width = width;
326 this.height = height;
327 stride = width;
328 sliceHeight = height;
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000329
330 if (useSurface) {
Per488e75f2015-11-19 10:43:36 +0100331 textureListener = new TextureListener(surfaceTextureHelper);
332 surface = new Surface(surfaceTextureHelper.getSurfaceTexture());
Magnus Jedvert80cf97c2015-06-11 10:08:59 +0200333 }
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000334
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000335 MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000336 if (!useSurface) {
337 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
338 }
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700339 Logging.d(TAG, " Format: " + format);
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800340 mediaCodec = MediaCodecVideoEncoder.createByCodecName(properties.codecName);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000341 if (mediaCodec == null) {
Alex Glaznev325d4142015-10-12 14:56:02 -0700342 Logging.e(TAG, "Can not create media decoder");
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000343 return false;
344 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200345 mediaCodec.configure(format, surface, null, 0);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000346 mediaCodec.start();
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800347
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000348 colorFormat = properties.colorFormat;
349 outputBuffers = mediaCodec.getOutputBuffers();
350 inputBuffers = mediaCodec.getInputBuffers();
Per488e75f2015-11-19 10:43:36 +0100351 decodeStartTimeMs.clear();
352 hasDecodedFirstFrame = false;
353 dequeuedSurfaceOutputBuffers.clear();
354 droppedFrames = 0;
sakalb6760f92016-09-29 04:12:44 -0700355 Logging.d(TAG,
356 "Input buffers: " + inputBuffers.length + ". Output buffers: " + outputBuffers.length);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000357 return true;
358 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700359 Logging.e(TAG, "initDecode failed", e);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000360 return false;
361 }
362 }
363
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800364 // Resets the decoder so it can start decoding frames with new resolution.
365 // Flushes MediaCodec and clears decoder output buffers.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100366 @CalledByNativeUnchecked
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800367 private void reset(int width, int height) {
368 if (mediaCodecThread == null || mediaCodec == null) {
369 throw new RuntimeException("Incorrect reset call for non-initialized decoder.");
370 }
371 Logging.d(TAG, "Java reset: " + width + " x " + height);
372
373 mediaCodec.flush();
374
375 this.width = width;
376 this.height = height;
377 decodeStartTimeMs.clear();
378 dequeuedSurfaceOutputBuffers.clear();
379 hasDecodedFirstFrame = false;
380 droppedFrames = 0;
381 }
382
Magnus Jedvert655e1962017-12-08 11:05:22 +0100383 @CalledByNativeUnchecked
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000384 private void release() {
Per488e75f2015-11-19 10:43:36 +0100385 Logging.d(TAG, "Java releaseDecoder. Total number of dropped frames: " + droppedFrames);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000386 checkOnMediaCodecThread();
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700387
388 // Run Mediacodec stop() and release() on separate thread since sometime
389 // Mediacodec.stop() may hang.
390 final CountDownLatch releaseDone = new CountDownLatch(1);
391
392 Runnable runMediaCodecRelease = new Runnable() {
393 @Override
394 public void run() {
395 try {
396 Logging.d(TAG, "Java releaseDecoder on release thread");
397 mediaCodec.stop();
398 mediaCodec.release();
399 Logging.d(TAG, "Java releaseDecoder on release thread done");
400 } catch (Exception e) {
401 Logging.e(TAG, "Media decoder release failed", e);
402 }
403 releaseDone.countDown();
404 }
405 };
406 new Thread(runMediaCodecRelease).start();
407
408 if (!ThreadUtils.awaitUninterruptibly(releaseDone, MEDIA_CODEC_RELEASE_TIMEOUT_MS)) {
409 Logging.e(TAG, "Media decoder release timeout");
410 codecErrors++;
411 if (errorCallback != null) {
412 Logging.e(TAG, "Invoke codec error callback. Errors: " + codecErrors);
413 errorCallback.onMediaCodecVideoDecoderCriticalError(codecErrors);
414 }
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000415 }
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700416
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000417 mediaCodec = null;
418 mediaCodecThread = null;
Alex Glaznevc6aec4b2015-10-19 16:39:19 -0700419 runningInstance = null;
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000420 if (useSurface) {
421 surface.release();
Magnus Jedvert7e319372015-10-02 15:49:38 +0200422 surface = null;
Per488e75f2015-11-19 10:43:36 +0100423 textureListener.release();
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000424 }
Alex Glaznev325d4142015-10-12 14:56:02 -0700425 Logging.d(TAG, "Java releaseDecoder done");
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000426 }
427
428 // Dequeue an input buffer and return its index, -1 if no input buffer is
429 // available, or -2 if the codec is no longer operative.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100430 @CalledByNativeUnchecked
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000431 private int dequeueInputBuffer() {
432 checkOnMediaCodecThread();
433 try {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000434 return mediaCodec.dequeueInputBuffer(DEQUEUE_INPUT_TIMEOUT);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000435 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700436 Logging.e(TAG, "dequeueIntputBuffer failed", e);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000437 return -2;
438 }
439 }
440
Magnus Jedvert655e1962017-12-08 11:05:22 +0100441 @CalledByNativeUnchecked
Per488e75f2015-11-19 10:43:36 +0100442 private boolean queueInputBuffer(int inputBufferIndex, int size, long presentationTimeStamUs,
443 long timeStampMs, long ntpTimeStamp) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000444 checkOnMediaCodecThread();
445 try {
446 inputBuffers[inputBufferIndex].position(0);
447 inputBuffers[inputBufferIndex].limit(size);
sakalb6760f92016-09-29 04:12:44 -0700448 decodeStartTimeMs.add(
449 new TimeStamps(SystemClock.elapsedRealtime(), timeStampMs, ntpTimeStamp));
Per488e75f2015-11-19 10:43:36 +0100450 mediaCodec.queueInputBuffer(inputBufferIndex, 0, size, presentationTimeStamUs, 0);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000451 return true;
sakalb6760f92016-09-29 04:12:44 -0700452 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700453 Logging.e(TAG, "decode failed", e);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000454 return false;
455 }
456 }
457
Per488e75f2015-11-19 10:43:36 +0100458 private static class TimeStamps {
459 public TimeStamps(long decodeStartTimeMs, long timeStampMs, long ntpTimeStampMs) {
460 this.decodeStartTimeMs = decodeStartTimeMs;
461 this.timeStampMs = timeStampMs;
462 this.ntpTimeStampMs = ntpTimeStampMs;
463 }
Alex Glazneveee86a62016-01-29 14:17:07 -0800464 // Time when this frame was queued for decoding.
465 private final long decodeStartTimeMs;
466 // Only used for bookkeeping in Java. Stores C++ inputImage._timeStamp value for input frame.
467 private final long timeStampMs;
468 // Only used for bookkeeping in Java. Stores C++ inputImage.ntp_time_ms_ value for input frame.
469 private final long ntpTimeStampMs;
Per488e75f2015-11-19 10:43:36 +0100470 }
471
472 // Helper struct for dequeueOutputBuffer() below.
473 private static class DecodedOutputBuffer {
glaznev94291482016-02-01 13:17:18 -0800474 public DecodedOutputBuffer(int index, int offset, int size, long presentationTimeStampMs,
475 long timeStampMs, long ntpTimeStampMs, long decodeTime, long endDecodeTime) {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000476 this.index = index;
477 this.offset = offset;
478 this.size = size;
glaznev94291482016-02-01 13:17:18 -0800479 this.presentationTimeStampMs = presentationTimeStampMs;
Per488e75f2015-11-19 10:43:36 +0100480 this.timeStampMs = timeStampMs;
481 this.ntpTimeStampMs = ntpTimeStampMs;
482 this.decodeTimeMs = decodeTime;
483 this.endDecodeTimeMs = endDecodeTime;
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000484 }
485
486 private final int index;
487 private final int offset;
488 private final int size;
glaznev94291482016-02-01 13:17:18 -0800489 // Presentation timestamp returned in dequeueOutputBuffer call.
490 private final long presentationTimeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800491 // C++ inputImage._timeStamp value for output frame.
Per488e75f2015-11-19 10:43:36 +0100492 private final long timeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800493 // C++ inputImage.ntp_time_ms_ value for output frame.
Per488e75f2015-11-19 10:43:36 +0100494 private final long ntpTimeStampMs;
495 // Number of ms it took to decode this frame.
496 private final long decodeTimeMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800497 // System time when this frame decoding finished.
Per488e75f2015-11-19 10:43:36 +0100498 private final long endDecodeTimeMs;
Magnus Jedvert655e1962017-12-08 11:05:22 +0100499
500 @CalledByNative("DecodedOutputBuffer")
501 int getIndex() {
502 return index;
503 }
504
505 @CalledByNative("DecodedOutputBuffer")
506 int getOffset() {
507 return offset;
508 }
509
510 @CalledByNative("DecodedOutputBuffer")
511 int getSize() {
512 return size;
513 }
514
515 @CalledByNative("DecodedOutputBuffer")
516 long getPresentationTimestampMs() {
517 return presentationTimeStampMs;
518 }
519
520 @CalledByNative("DecodedOutputBuffer")
521 long getTimestampMs() {
522 return timeStampMs;
523 }
524
525 @CalledByNative("DecodedOutputBuffer")
526 long getNtpTimestampMs() {
527 return ntpTimeStampMs;
528 }
529
530 @CalledByNative("DecodedOutputBuffer")
531 long getDecodeTimeMs() {
532 return decodeTimeMs;
533 }
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000534 }
535
Per488e75f2015-11-19 10:43:36 +0100536 // Helper struct for dequeueTextureBuffer() below.
magjed44bf6f52015-10-03 02:08:00 -0700537 private static class DecodedTextureBuffer {
538 private final int textureID;
Per488e75f2015-11-19 10:43:36 +0100539 private final float[] transformMatrix;
glaznev94291482016-02-01 13:17:18 -0800540 // Presentation timestamp returned in dequeueOutputBuffer call.
541 private final long presentationTimeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800542 // C++ inputImage._timeStamp value for output frame.
Per488e75f2015-11-19 10:43:36 +0100543 private final long timeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800544 // C++ inputImage.ntp_time_ms_ value for output frame.
Per488e75f2015-11-19 10:43:36 +0100545 private final long ntpTimeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800546 // Number of ms it took to decode this frame.
Per488e75f2015-11-19 10:43:36 +0100547 private final long decodeTimeMs;
548 // Interval from when the frame finished decoding until this buffer has been created.
549 // Since there is only one texture, this interval depend on the time from when
550 // a frame is decoded and provided to C++ and until that frame is returned to the MediaCodec
551 // so that the texture can be updated with the next decoded frame.
552 private final long frameDelayMs;
magjed44bf6f52015-10-03 02:08:00 -0700553
Per488e75f2015-11-19 10:43:36 +0100554 // A DecodedTextureBuffer with zero |textureID| has special meaning and represents a frame
555 // that was dropped.
glaznev94291482016-02-01 13:17:18 -0800556 public DecodedTextureBuffer(int textureID, float[] transformMatrix,
557 long presentationTimeStampMs, long timeStampMs, long ntpTimeStampMs, long decodeTimeMs,
558 long frameDelay) {
magjed44bf6f52015-10-03 02:08:00 -0700559 this.textureID = textureID;
Per488e75f2015-11-19 10:43:36 +0100560 this.transformMatrix = transformMatrix;
glaznev94291482016-02-01 13:17:18 -0800561 this.presentationTimeStampMs = presentationTimeStampMs;
Per488e75f2015-11-19 10:43:36 +0100562 this.timeStampMs = timeStampMs;
563 this.ntpTimeStampMs = ntpTimeStampMs;
564 this.decodeTimeMs = decodeTimeMs;
565 this.frameDelayMs = frameDelay;
magjed44bf6f52015-10-03 02:08:00 -0700566 }
Magnus Jedvert655e1962017-12-08 11:05:22 +0100567
568 @CalledByNative("DecodedTextureBuffer")
569 int getTextureId() {
570 return textureID;
571 }
572
573 @CalledByNative("DecodedTextureBuffer")
574 float[] getTransformMatrix() {
575 return transformMatrix;
576 }
577
578 @CalledByNative("DecodedTextureBuffer")
579 long getPresentationTimestampMs() {
580 return presentationTimeStampMs;
581 }
582
583 @CalledByNative("DecodedTextureBuffer")
584 long getTimeStampMs() {
585 return timeStampMs;
586 }
587
588 @CalledByNative("DecodedTextureBuffer")
589 long getNtpTimestampMs() {
590 return ntpTimeStampMs;
591 }
592
593 @CalledByNative("DecodedTextureBuffer")
594 long getDecodeTimeMs() {
595 return decodeTimeMs;
596 }
597
598 @CalledByNative("DecodedTextureBuffer")
599 long getFrameDelayMs() {
600 return frameDelayMs;
601 }
magjed44bf6f52015-10-03 02:08:00 -0700602 }
603
Per488e75f2015-11-19 10:43:36 +0100604 // Poll based texture listener.
605 private static class TextureListener
606 implements SurfaceTextureHelper.OnTextureFrameAvailableListener {
607 private final SurfaceTextureHelper surfaceTextureHelper;
608 // |newFrameLock| is used to synchronize arrival of new frames with wait()/notifyAll().
609 private final Object newFrameLock = new Object();
610 // |bufferToRender| is non-null when waiting for transition between addBufferToRender() to
611 // onTextureFrameAvailable().
612 private DecodedOutputBuffer bufferToRender;
613 private DecodedTextureBuffer renderedBuffer;
614
615 public TextureListener(SurfaceTextureHelper surfaceTextureHelper) {
616 this.surfaceTextureHelper = surfaceTextureHelper;
magjed81e8e372016-03-03 02:11:44 -0800617 surfaceTextureHelper.startListening(this);
Per488e75f2015-11-19 10:43:36 +0100618 }
619
620 public void addBufferToRender(DecodedOutputBuffer buffer) {
621 if (bufferToRender != null) {
sakalb6760f92016-09-29 04:12:44 -0700622 Logging.e(TAG, "Unexpected addBufferToRender() called while waiting for a texture.");
Per488e75f2015-11-19 10:43:36 +0100623 throw new IllegalStateException("Waiting for a texture.");
624 }
625 bufferToRender = buffer;
626 }
627
628 public boolean isWaitingForTexture() {
629 synchronized (newFrameLock) {
630 return bufferToRender != null;
631 }
632 }
633
634 // Callback from |surfaceTextureHelper|. May be called on an arbitrary thread.
635 @Override
636 public void onTextureFrameAvailable(
637 int oesTextureId, float[] transformMatrix, long timestampNs) {
638 synchronized (newFrameLock) {
639 if (renderedBuffer != null) {
sakalb6760f92016-09-29 04:12:44 -0700640 Logging.e(
641 TAG, "Unexpected onTextureFrameAvailable() called while already holding a texture.");
Per488e75f2015-11-19 10:43:36 +0100642 throw new IllegalStateException("Already holding a texture.");
643 }
644 // |timestampNs| is always zero on some Android versions.
645 renderedBuffer = new DecodedTextureBuffer(oesTextureId, transformMatrix,
glaznev94291482016-02-01 13:17:18 -0800646 bufferToRender.presentationTimeStampMs, bufferToRender.timeStampMs,
647 bufferToRender.ntpTimeStampMs, bufferToRender.decodeTimeMs,
Per488e75f2015-11-19 10:43:36 +0100648 SystemClock.elapsedRealtime() - bufferToRender.endDecodeTimeMs);
649 bufferToRender = null;
650 newFrameLock.notifyAll();
651 }
652 }
653
654 // Dequeues and returns a DecodedTextureBuffer if available, or null otherwise.
Sami Kalliomäki9828beb2017-10-26 16:21:22 +0200655 @SuppressWarnings("WaitNotInLoop")
Per488e75f2015-11-19 10:43:36 +0100656 public DecodedTextureBuffer dequeueTextureBuffer(int timeoutMs) {
657 synchronized (newFrameLock) {
658 if (renderedBuffer == null && timeoutMs > 0 && isWaitingForTexture()) {
659 try {
660 newFrameLock.wait(timeoutMs);
sakalb6760f92016-09-29 04:12:44 -0700661 } catch (InterruptedException e) {
Per488e75f2015-11-19 10:43:36 +0100662 // Restore the interrupted status by reinterrupting the thread.
663 Thread.currentThread().interrupt();
664 }
665 }
666 DecodedTextureBuffer returnedBuffer = renderedBuffer;
667 renderedBuffer = null;
668 return returnedBuffer;
669 }
670 }
671
672 public void release() {
nissea44e72c2016-05-27 00:27:59 -0700673 // SurfaceTextureHelper.stopListening() will block until any onTextureFrameAvailable() in
674 // progress is done. Therefore, the call must be outside any synchronized
Per488e75f2015-11-19 10:43:36 +0100675 // statement that is also used in the onTextureFrameAvailable() above to avoid deadlocks.
nissea44e72c2016-05-27 00:27:59 -0700676 surfaceTextureHelper.stopListening();
Per488e75f2015-11-19 10:43:36 +0100677 synchronized (newFrameLock) {
678 if (renderedBuffer != null) {
679 surfaceTextureHelper.returnTextureFrame();
680 renderedBuffer = null;
681 }
682 }
683 }
684 }
685
686 // Returns null if no decoded buffer is available, and otherwise a DecodedByteBuffer.
Magnus Jedvert7e319372015-10-02 15:49:38 +0200687 // Throws IllegalStateException if call is made on the wrong thread, if color format changes to an
688 // unsupported format, or if |mediaCodec| is not in the Executing state. Throws CodecException
689 // upon codec error.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100690 @CalledByNativeUnchecked
Per488e75f2015-11-19 10:43:36 +0100691 private DecodedOutputBuffer dequeueOutputBuffer(int dequeueTimeoutMs) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000692 checkOnMediaCodecThread();
Per488e75f2015-11-19 10:43:36 +0100693 if (decodeStartTimeMs.isEmpty()) {
694 return null;
695 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200696 // Drain the decoder until receiving a decoded buffer or hitting
697 // MediaCodec.INFO_TRY_AGAIN_LATER.
698 final MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
699 while (true) {
sakalb6760f92016-09-29 04:12:44 -0700700 final int result =
701 mediaCodec.dequeueOutputBuffer(info, TimeUnit.MILLISECONDS.toMicros(dequeueTimeoutMs));
Magnus Jedvert7e319372015-10-02 15:49:38 +0200702 switch (result) {
Magnus Jedvert7e319372015-10-02 15:49:38 +0200703 case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000704 outputBuffers = mediaCodec.getOutputBuffers();
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700705 Logging.d(TAG, "Decoder output buffers changed: " + outputBuffers.length);
Per488e75f2015-11-19 10:43:36 +0100706 if (hasDecodedFirstFrame) {
707 throw new RuntimeException("Unexpected output buffer change event.");
708 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200709 break;
710 case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000711 MediaFormat format = mediaCodec.getOutputFormat();
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700712 Logging.d(TAG, "Decoder format changed: " + format.toString());
magjed0c29de52017-03-02 00:55:32 -0800713 final int newWidth;
714 final int newHeight;
715 if (format.containsKey(FORMAT_KEY_CROP_LEFT) && format.containsKey(FORMAT_KEY_CROP_RIGHT)
716 && format.containsKey(FORMAT_KEY_CROP_BOTTOM)
717 && format.containsKey(FORMAT_KEY_CROP_TOP)) {
718 newWidth = 1 + format.getInteger(FORMAT_KEY_CROP_RIGHT)
719 - format.getInteger(FORMAT_KEY_CROP_LEFT);
720 newHeight = 1 + format.getInteger(FORMAT_KEY_CROP_BOTTOM)
721 - format.getInteger(FORMAT_KEY_CROP_TOP);
722 } else {
723 newWidth = format.getInteger(MediaFormat.KEY_WIDTH);
724 newHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
Per488e75f2015-11-19 10:43:36 +0100725 }
magjed0c29de52017-03-02 00:55:32 -0800726 if (hasDecodedFirstFrame && (newWidth != width || newHeight != height)) {
727 throw new RuntimeException("Unexpected size change. Configured " + width + "*" + height
728 + ". New " + newWidth + "*" + newHeight);
729 }
730 width = newWidth;
731 height = newHeight;
Per488e75f2015-11-19 10:43:36 +0100732
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000733 if (!useSurface && format.containsKey(MediaFormat.KEY_COLOR_FORMAT)) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000734 colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700735 Logging.d(TAG, "Color: 0x" + Integer.toHexString(colorFormat));
Magnus Jedvert7e319372015-10-02 15:49:38 +0200736 if (!supportedColorList.contains(colorFormat)) {
737 throw new IllegalStateException("Non supported color format: " + colorFormat);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000738 }
739 }
magjed0c29de52017-03-02 00:55:32 -0800740 if (format.containsKey(FORMAT_KEY_STRIDE)) {
741 stride = format.getInteger(FORMAT_KEY_STRIDE);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000742 }
magjed0c29de52017-03-02 00:55:32 -0800743 if (format.containsKey(FORMAT_KEY_SLICE_HEIGHT)) {
744 sliceHeight = format.getInteger(FORMAT_KEY_SLICE_HEIGHT);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000745 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200746 Logging.d(TAG, "Frame stride and slice height: " + stride + " x " + sliceHeight);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000747 stride = Math.max(width, stride);
748 sliceHeight = Math.max(height, sliceHeight);
Magnus Jedvert7e319372015-10-02 15:49:38 +0200749 break;
Per488e75f2015-11-19 10:43:36 +0100750 case MediaCodec.INFO_TRY_AGAIN_LATER:
751 return null;
Magnus Jedvert7e319372015-10-02 15:49:38 +0200752 default:
Per488e75f2015-11-19 10:43:36 +0100753 hasDecodedFirstFrame = true;
754 TimeStamps timeStamps = decodeStartTimeMs.remove();
magjedcedddbd2016-02-26 09:36:04 -0800755 long decodeTimeMs = SystemClock.elapsedRealtime() - timeStamps.decodeStartTimeMs;
756 if (decodeTimeMs > MAX_DECODE_TIME_MS) {
Alex Glaznevd5704842016-06-27 11:51:10 -0700757 Logging.e(TAG, "Very high decode time: " + decodeTimeMs + "ms"
sakalb6760f92016-09-29 04:12:44 -0700758 + ". Q size: " + decodeStartTimeMs.size()
759 + ". Might be caused by resuming H264 decoding after a pause.");
magjedcedddbd2016-02-26 09:36:04 -0800760 decodeTimeMs = MAX_DECODE_TIME_MS;
761 }
sakalb6760f92016-09-29 04:12:44 -0700762 return new DecodedOutputBuffer(result, info.offset, info.size,
763 TimeUnit.MICROSECONDS.toMillis(info.presentationTimeUs), timeStamps.timeStampMs,
764 timeStamps.ntpTimeStampMs, decodeTimeMs, SystemClock.elapsedRealtime());
765 }
perkj9cb89822015-11-11 03:27:01 -0800766 }
perkj9cb89822015-11-11 03:27:01 -0800767 }
768
Per488e75f2015-11-19 10:43:36 +0100769 // Returns null if no decoded buffer is available, and otherwise a DecodedTextureBuffer.
770 // Throws IllegalStateException if call is made on the wrong thread, if color format changes to an
771 // unsupported format, or if |mediaCodec| is not in the Executing state. Throws CodecException
772 // upon codec error. If |dequeueTimeoutMs| > 0, the oldest decoded frame will be dropped if
773 // a frame can't be returned.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100774 @CalledByNativeUnchecked
Per488e75f2015-11-19 10:43:36 +0100775 private DecodedTextureBuffer dequeueTextureBuffer(int dequeueTimeoutMs) {
776 checkOnMediaCodecThread();
777 if (!useSurface) {
778 throw new IllegalStateException("dequeueTexture() called for byte buffer decoding.");
779 }
780 DecodedOutputBuffer outputBuffer = dequeueOutputBuffer(dequeueTimeoutMs);
781 if (outputBuffer != null) {
782 dequeuedSurfaceOutputBuffers.add(outputBuffer);
783 }
784
785 MaybeRenderDecodedTextureBuffer();
786 // Check if there is texture ready now by waiting max |dequeueTimeoutMs|.
787 DecodedTextureBuffer renderedBuffer = textureListener.dequeueTextureBuffer(dequeueTimeoutMs);
788 if (renderedBuffer != null) {
789 MaybeRenderDecodedTextureBuffer();
790 return renderedBuffer;
791 }
792
793 if ((dequeuedSurfaceOutputBuffers.size()
sakalb6760f92016-09-29 04:12:44 -0700794 >= Math.min(MAX_QUEUED_OUTPUTBUFFERS, outputBuffers.length)
795 || (dequeueTimeoutMs > 0 && !dequeuedSurfaceOutputBuffers.isEmpty()))) {
Per488e75f2015-11-19 10:43:36 +0100796 ++droppedFrames;
797 // Drop the oldest frame still in dequeuedSurfaceOutputBuffers.
798 // The oldest frame is owned by |textureListener| and can't be dropped since
799 // mediaCodec.releaseOutputBuffer has already been called.
800 final DecodedOutputBuffer droppedFrame = dequeuedSurfaceOutputBuffers.remove();
801 if (dequeueTimeoutMs > 0) {
perkj7baf79f2015-11-24 06:26:38 -0800802 // TODO(perkj): Re-add the below log when VideoRenderGUI has been removed or fixed to
803 // return the one and only texture even if it does not render.
glaznevae95ff32016-02-04 11:47:12 -0800804 Logging.w(TAG, "Draining decoder. Dropping frame with TS: "
sakalb6760f92016-09-29 04:12:44 -0700805 + droppedFrame.presentationTimeStampMs + ". Total number of dropped frames: "
806 + droppedFrames);
Per488e75f2015-11-19 10:43:36 +0100807 } else {
sakalb6760f92016-09-29 04:12:44 -0700808 Logging.w(TAG, "Too many output buffers " + dequeuedSurfaceOutputBuffers.size()
809 + ". Dropping frame with TS: " + droppedFrame.presentationTimeStampMs
810 + ". Total number of dropped frames: " + droppedFrames);
Per488e75f2015-11-19 10:43:36 +0100811 }
812
813 mediaCodec.releaseOutputBuffer(droppedFrame.index, false /* render */);
sakalb6760f92016-09-29 04:12:44 -0700814 return new DecodedTextureBuffer(0, null, droppedFrame.presentationTimeStampMs,
815 droppedFrame.timeStampMs, droppedFrame.ntpTimeStampMs, droppedFrame.decodeTimeMs,
Per488e75f2015-11-19 10:43:36 +0100816 SystemClock.elapsedRealtime() - droppedFrame.endDecodeTimeMs);
817 }
818 return null;
819 }
820
821 private void MaybeRenderDecodedTextureBuffer() {
822 if (dequeuedSurfaceOutputBuffers.isEmpty() || textureListener.isWaitingForTexture()) {
823 return;
824 }
825 // Get the first frame in the queue and render to the decoder output surface.
826 final DecodedOutputBuffer buffer = dequeuedSurfaceOutputBuffers.remove();
827 textureListener.addBufferToRender(buffer);
828 mediaCodec.releaseOutputBuffer(buffer.index, true /* render */);
829 }
830
magjed44bf6f52015-10-03 02:08:00 -0700831 // Release a dequeued output byte buffer back to the codec for re-use. Should only be called for
832 // non-surface decoding.
833 // Throws IllegalStateException if the call is made on the wrong thread, if codec is configured
834 // for surface decoding, or if |mediaCodec| is not in the Executing state. Throws
835 // MediaCodec.CodecException upon codec error.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100836 @CalledByNativeUnchecked
Per488e75f2015-11-19 10:43:36 +0100837 private void returnDecodedOutputBuffer(int index)
Magnus Jedvert7e319372015-10-02 15:49:38 +0200838 throws IllegalStateException, MediaCodec.CodecException {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000839 checkOnMediaCodecThread();
magjed44bf6f52015-10-03 02:08:00 -0700840 if (useSurface) {
Per488e75f2015-11-19 10:43:36 +0100841 throw new IllegalStateException("returnDecodedOutputBuffer() called for surface decoding.");
magjed44bf6f52015-10-03 02:08:00 -0700842 }
843 mediaCodec.releaseOutputBuffer(index, false /* render */);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000844 }
Magnus Jedvert655e1962017-12-08 11:05:22 +0100845
846 @CalledByNative
847 ByteBuffer[] getInputBuffers() {
848 return inputBuffers;
849 }
850
851 @CalledByNative
852 ByteBuffer[] getOutputBuffers() {
853 return outputBuffers;
854 }
855
856 @CalledByNative
857 int getColorFormat() {
858 return colorFormat;
859 }
860
861 @CalledByNative
862 int getWidth() {
863 return width;
864 }
865
866 @CalledByNative
867 int getHeight() {
868 return height;
869 }
870
871 @CalledByNative
872 int getStride() {
873 return stride;
874 }
875
876 @CalledByNative
877 int getSliceHeight() {
878 return sliceHeight;
879 }
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000880}