blob: 8ab033d44377e064c88be2d6e05916242ed98f2a [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;
magjed8c425aa2015-10-22 16:52:39 -070033
Magnus Jedvert9060eb12017-12-12 12:52:54 +010034// Java-side of peerconnection.cc:MediaCodecVideoDecoder.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000035// This class is an implementation detail of the Java PeerConnection API.
Patrik Höglund68876f92015-11-12 17:36:48 +010036@SuppressWarnings("deprecation")
Alex Glaznev908e77b2015-04-22 09:25:34 -070037public class MediaCodecVideoDecoder {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000038 // This class is constructed, operated, and destroyed by its C++ incarnation,
39 // so the class and its methods have non-public visibility. The API this
40 // class exposes aims to mimic the webrtc::VideoDecoder API as closely as
41 // possibly to minimize the amount of translation work necessary.
42
43 private static final String TAG = "MediaCodecVideoDecoder";
magjedcedddbd2016-02-26 09:36:04 -080044 private static final long MAX_DECODE_TIME_MS = 200;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000045
magjed0c29de52017-03-02 00:55:32 -080046 // TODO(magjed): Use MediaFormat constants when part of the public API.
47 private static final String FORMAT_KEY_STRIDE = "stride";
48 private static final String FORMAT_KEY_SLICE_HEIGHT = "slice-height";
49 private static final String FORMAT_KEY_CROP_LEFT = "crop-left";
50 private static final String FORMAT_KEY_CROP_RIGHT = "crop-right";
51 private static final String FORMAT_KEY_CROP_TOP = "crop-top";
52 private static final String FORMAT_KEY_CROP_BOTTOM = "crop-bottom";
53
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000054 // Tracks webrtc::VideoCodecType.
Magnus Jedvert655e1962017-12-08 11:05:22 +010055 public enum VideoCodecType {
56 VIDEO_CODEC_VP8,
57 VIDEO_CODEC_VP9,
58 VIDEO_CODEC_H264;
59
60 @CalledByNative("VideoCodecType")
61 static VideoCodecType fromNativeIndex(int nativeIndex) {
62 return values()[nativeIndex];
63 }
64 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000065
Alex Glazneveee86a62016-01-29 14:17:07 -080066 // Timeout for input buffer dequeue.
67 private static final int DEQUEUE_INPUT_TIMEOUT = 500000;
68 // Timeout for codec releasing.
69 private static final int MEDIA_CODEC_RELEASE_TIMEOUT_MS = 5000;
70 // Max number of output buffers queued before starting to drop decoded frames.
71 private static final int MAX_QUEUED_OUTPUTBUFFERS = 3;
Alex Glaznevc6aec4b2015-10-19 16:39:19 -070072 // Active running decoder instance. Set in initDecode() (called from native code)
73 // and reset to null in release() call.
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010074 @Nullable private static MediaCodecVideoDecoder runningInstance = null;
75 @Nullable private static MediaCodecVideoDecoderErrorCallback errorCallback = null;
Alex Glaznev5c3da4b2015-10-30 15:31:07 -070076 private static int codecErrors = 0;
Alex Glazneveee86a62016-01-29 14:17:07 -080077 // List of disabled codec types - can be set from application.
78 private static Set<String> hwDecoderDisabledTypes = new HashSet<String>();
Alex Glaznev5c3da4b2015-10-30 15:31:07 -070079
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010080 @Nullable private Thread mediaCodecThread;
81 @Nullable private MediaCodec mediaCodec;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000082 private ByteBuffer[] inputBuffers;
83 private ByteBuffer[] outputBuffers;
84 private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8";
Alex Glaznev69a7fd52015-11-10 10:25:40 -080085 private static final String VP9_MIME_TYPE = "video/x-vnd.on2.vp9";
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000086 private static final String H264_MIME_TYPE = "video/avc";
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000087 // List of supported HW VP8 decoders.
Alex Leung5b6891a2018-01-18 10:01:14 -080088 private static final String[] supportedVp8HwCodecPrefixes() {
89 ArrayList<String> supportedPrefixes = new ArrayList<String>();
90 supportedPrefixes.add("OMX.qcom.");
91 supportedPrefixes.add("OMX.Nvidia.");
92 supportedPrefixes.add("OMX.Exynos.");
93 supportedPrefixes.add("OMX.Intel.");
94 if (PeerConnectionFactory.fieldTrialsFindFullName("WebRTC-MediaTekVP8").equals("Enabled")
95 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
96 supportedPrefixes.add("OMX.MTK.");
97 }
98 return supportedPrefixes.toArray(new String[supportedPrefixes.size()]);
99 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800100 // List of supported HW VP9 decoders.
sakalb6760f92016-09-29 04:12:44 -0700101 private static final String[] supportedVp9HwCodecPrefixes = {"OMX.qcom.", "OMX.Exynos."};
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000102 // List of supported HW H.264 decoders.
Alex Leung5b6891a2018-01-18 10:01:14 -0800103 private static final String[] supportedH264HwCodecPrefixes() {
104 ArrayList<String> supportedPrefixes = new ArrayList<String>();
105 supportedPrefixes.add("OMX.qcom.");
106 supportedPrefixes.add("OMX.Intel.");
107 supportedPrefixes.add("OMX.Exynos.");
108 if (PeerConnectionFactory.fieldTrialsFindFullName("WebRTC-MediaTekH264").equals("Enabled")
Alex Leung28e71072018-02-05 13:42:48 -0800109 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
Alex Leung5b6891a2018-01-18 10:01:14 -0800110 supportedPrefixes.add("OMX.MTK.");
111 }
112 return supportedPrefixes.toArray(new String[supportedPrefixes.size()]);
113 }
114
glaznev0c1d0602017-01-27 12:24:24 -0800115 // List of supported HW H.264 high profile decoders.
glaznevcca0f6c2017-06-14 10:20:54 -0700116 private static final String supportedQcomH264HighProfileHwCodecPrefix = "OMX.qcom.";
117 private static final String supportedExynosH264HighProfileHwCodecPrefix = "OMX.Exynos.";
Alex Leung5b6891a2018-01-18 10:01:14 -0800118 private static final String supportedMediaTekH264HighProfileHwCodecPrefix = "OMX.MTK.";
glaznev893a7ee2016-09-22 10:44:30 -0700119
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000120 // NV12 color format supported by QCOM codec, but not declared in MediaCodec -
121 // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
glaznev893a7ee2016-09-22 10:44:30 -0700122 private static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar32m4ka = 0x7FA30C01;
123 private static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar16m4ka = 0x7FA30C02;
124 private static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar64x32Tile2m8ka = 0x7FA30C03;
125 private static final int COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000126 // Allowable color formats supported by codec - in order of preference.
Magnus Jedvert7e319372015-10-02 15:49:38 +0200127 private static final List<Integer> supportedColorList = Arrays.asList(
sakalb6760f92016-09-29 04:12:44 -0700128 CodecCapabilities.COLOR_FormatYUV420Planar, CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
129 CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
130 COLOR_QCOM_FORMATYVU420PackedSemiPlanar32m4ka, COLOR_QCOM_FORMATYVU420PackedSemiPlanar16m4ka,
131 COLOR_QCOM_FORMATYVU420PackedSemiPlanar64x32Tile2m8ka,
132 COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m);
glaznev893a7ee2016-09-22 10:44:30 -0700133
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000134 private int colorFormat;
135 private int width;
136 private int height;
137 private int stride;
138 private int sliceHeight;
Per488e75f2015-11-19 10:43:36 +0100139 private boolean hasDecodedFirstFrame;
Magnus Jedvert6062f372017-11-16 16:53:12 +0100140 private final Queue<TimeStamps> decodeStartTimeMs = new ArrayDeque<TimeStamps>();
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000141 private boolean useSurface;
Perc01c2542015-11-13 16:58:26 +0100142
Per488e75f2015-11-19 10:43:36 +0100143 // The below variables are only used when decoding to a Surface.
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100144 @Nullable private TextureListener textureListener;
Per488e75f2015-11-19 10:43:36 +0100145 private int droppedFrames;
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100146 @Nullable private Surface surface = null;
sakalb6760f92016-09-29 04:12:44 -0700147 private final Queue<DecodedOutputBuffer> dequeuedSurfaceOutputBuffers =
Magnus Jedvert6062f372017-11-16 16:53:12 +0100148 new ArrayDeque<DecodedOutputBuffer>();
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000149
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700150 // MediaCodec error handler - invoked when critical error happens which may prevent
151 // further use of media codec API. Now it means that one of media codec instances
152 // is hanging and can no longer be used in the next call.
153 public static interface MediaCodecVideoDecoderErrorCallback {
154 void onMediaCodecVideoDecoderCriticalError(int codecErrors);
155 }
156
157 public static void setErrorCallback(MediaCodecVideoDecoderErrorCallback errorCallback) {
158 Logging.d(TAG, "Set error callback");
159 MediaCodecVideoDecoder.errorCallback = errorCallback;
160 }
161
Alex Glazneveee86a62016-01-29 14:17:07 -0800162 // Functions to disable HW decoding - can be called from applications for platforms
163 // which have known HW decoding problems.
164 public static void disableVp8HwCodec() {
165 Logging.w(TAG, "VP8 decoding is disabled by application.");
166 hwDecoderDisabledTypes.add(VP8_MIME_TYPE);
167 }
168
169 public static void disableVp9HwCodec() {
170 Logging.w(TAG, "VP9 decoding is disabled by application.");
171 hwDecoderDisabledTypes.add(VP9_MIME_TYPE);
172 }
173
174 public static void disableH264HwCodec() {
175 Logging.w(TAG, "H.264 decoding is disabled by application.");
176 hwDecoderDisabledTypes.add(H264_MIME_TYPE);
177 }
178
179 // Functions to query if HW decoding is supported.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100180 @CalledByNativeUnchecked
Alex Glazneveee86a62016-01-29 14:17:07 -0800181 public static boolean isVp8HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700182 return !hwDecoderDisabledTypes.contains(VP8_MIME_TYPE)
Alex Leung5b6891a2018-01-18 10:01:14 -0800183 && (findDecoder(VP8_MIME_TYPE, supportedVp8HwCodecPrefixes()) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800184 }
185
Magnus Jedvert655e1962017-12-08 11:05:22 +0100186 @CalledByNativeUnchecked
Alex Glazneveee86a62016-01-29 14:17:07 -0800187 public static boolean isVp9HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700188 return !hwDecoderDisabledTypes.contains(VP9_MIME_TYPE)
189 && (findDecoder(VP9_MIME_TYPE, supportedVp9HwCodecPrefixes) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800190 }
191
Magnus Jedvert655e1962017-12-08 11:05:22 +0100192 @CalledByNativeUnchecked
Alex Glazneveee86a62016-01-29 14:17:07 -0800193 public static boolean isH264HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700194 return !hwDecoderDisabledTypes.contains(H264_MIME_TYPE)
Alex Leung5b6891a2018-01-18 10:01:14 -0800195 && (findDecoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes()) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800196 }
197
Magnus Jedvert655e1962017-12-08 11:05:22 +0100198 @CalledByNative
glaznev0c1d0602017-01-27 12:24:24 -0800199 public static boolean isH264HighProfileHwSupported() {
glaznevcca0f6c2017-06-14 10:20:54 -0700200 if (hwDecoderDisabledTypes.contains(H264_MIME_TYPE)) {
201 return false;
202 }
203 // Support H.264 HP decoding on QCOM chips for Android L and above.
204 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
205 && findDecoder(H264_MIME_TYPE, new String[] {supportedQcomH264HighProfileHwCodecPrefix})
206 != null) {
207 return true;
208 }
209 // Support H.264 HP decoding on Exynos chips for Android M and above.
210 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
211 && findDecoder(H264_MIME_TYPE, new String[] {supportedExynosH264HighProfileHwCodecPrefix})
212 != null) {
213 return true;
214 }
Alex Leung28e71072018-02-05 13:42:48 -0800215 // Support H.264 HP decoding on MediaTek chips for Android O_MR1 and above
Alex Leung5b6891a2018-01-18 10:01:14 -0800216 if (PeerConnectionFactory.fieldTrialsFindFullName("WebRTC-MediaTekH264").equals("Enabled")
Alex Leung28e71072018-02-05 13:42:48 -0800217 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1
Alex Leung5b6891a2018-01-18 10:01:14 -0800218 && findDecoder(H264_MIME_TYPE, new String[] {supportedMediaTekH264HighProfileHwCodecPrefix})
219 != null) {
220 return true;
221 }
glaznevcca0f6c2017-06-14 10:20:54 -0700222 return false;
glaznev0c1d0602017-01-27 12:24:24 -0800223 }
224
Alex Glazneveee86a62016-01-29 14:17:07 -0800225 public static void printStackTrace() {
226 if (runningInstance != null && runningInstance.mediaCodecThread != null) {
227 StackTraceElement[] mediaCodecStackTraces = runningInstance.mediaCodecThread.getStackTrace();
228 if (mediaCodecStackTraces.length > 0) {
229 Logging.d(TAG, "MediaCodecVideoDecoder stacks trace:");
230 for (StackTraceElement stackTrace : mediaCodecStackTraces) {
231 Logging.d(TAG, stackTrace.toString());
232 }
233 }
234 }
235 }
236
237 // Helper struct for findDecoder() below.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000238 private static class DecoderProperties {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000239 public DecoderProperties(String codecName, int colorFormat) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000240 this.codecName = codecName;
241 this.colorFormat = colorFormat;
242 }
243 public final String codecName; // OpenMax component name for VP8 codec.
sakalb6760f92016-09-29 04:12:44 -0700244 public final int colorFormat; // Color format supported by codec.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000245 }
246
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100247 private static @Nullable DecoderProperties findDecoder(
248 String mime, String[] supportedCodecPrefixes) {
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000249 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000250 return null; // MediaCodec.setParameters is missing.
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000251 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800252 Logging.d(TAG, "Trying to find HW decoder for mime " + mime);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000253 for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
Alex Glaznev0060c562016-08-08 12:27:24 -0700254 MediaCodecInfo info = null;
255 try {
256 info = MediaCodecList.getCodecInfoAt(i);
257 } catch (IllegalArgumentException e) {
sakalb6760f92016-09-29 04:12:44 -0700258 Logging.e(TAG, "Cannot retrieve decoder codec info", e);
Alex Glaznev0060c562016-08-08 12:27:24 -0700259 }
260 if (info == null || info.isEncoder()) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000261 continue;
262 }
263 String name = null;
264 for (String mimeType : info.getSupportedTypes()) {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000265 if (mimeType.equals(mime)) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000266 name = info.getName();
267 break;
268 }
269 }
270 if (name == null) {
sakalb6760f92016-09-29 04:12:44 -0700271 continue; // No HW support in this codec; try the next one.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000272 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800273 Logging.d(TAG, "Found candidate decoder " + name);
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000274
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000275 // Check if this is supported decoder.
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000276 boolean supportedCodec = false;
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000277 for (String codecPrefix : supportedCodecPrefixes) {
278 if (name.startsWith(codecPrefix)) {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000279 supportedCodec = true;
280 break;
281 }
282 }
283 if (!supportedCodec) {
284 continue;
285 }
286
287 // Check if codec supports either yuv420 or nv12.
Alex Glaznev0060c562016-08-08 12:27:24 -0700288 CodecCapabilities capabilities;
289 try {
290 capabilities = info.getCapabilitiesForType(mime);
291 } catch (IllegalArgumentException e) {
sakalb6760f92016-09-29 04:12:44 -0700292 Logging.e(TAG, "Cannot retrieve decoder capabilities", e);
Alex Glaznev0060c562016-08-08 12:27:24 -0700293 continue;
294 }
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000295 for (int colorFormat : capabilities.colorFormats) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700296 Logging.v(TAG, " Color: 0x" + Integer.toHexString(colorFormat));
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000297 }
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000298 for (int supportedColorFormat : supportedColorList) {
299 for (int codecColorFormat : capabilities.colorFormats) {
300 if (codecColorFormat == supportedColorFormat) {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000301 // Found supported HW decoder.
sakalb6760f92016-09-29 04:12:44 -0700302 Logging.d(TAG, "Found target decoder " + name + ". Color: 0x"
303 + Integer.toHexString(codecColorFormat));
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000304 return new DecoderProperties(name, codecColorFormat);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000305 }
306 }
307 }
308 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800309 Logging.d(TAG, "No HW decoder found for mime " + mime);
sakalb6760f92016-09-29 04:12:44 -0700310 return null; // No HW decoder.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000311 }
312
Magnus Jedvert655e1962017-12-08 11:05:22 +0100313 @CalledByNative
314 MediaCodecVideoDecoder() {}
315
Magnus Jedvert7e319372015-10-02 15:49:38 +0200316 private void checkOnMediaCodecThread() throws IllegalStateException {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000317 if (mediaCodecThread.getId() != Thread.currentThread().getId()) {
sakalb6760f92016-09-29 04:12:44 -0700318 throw new IllegalStateException("MediaCodecVideoDecoder previously operated on "
319 + mediaCodecThread + " but is now called on " + Thread.currentThread());
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000320 }
321 }
322
Per488e75f2015-11-19 10:43:36 +0100323 // Pass null in |surfaceTextureHelper| to configure the codec for ByteBuffer output.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100324 @CalledByNativeUnchecked
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100325 private boolean initDecode(VideoCodecType type, int width, int height,
326 @Nullable SurfaceTextureHelper surfaceTextureHelper) {
Alejandro Luebs69ddaef2015-10-09 15:46:09 -0700327 if (mediaCodecThread != null) {
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800328 throw new RuntimeException("initDecode: Forgot to release()?");
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000329 }
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800330
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000331 String mime = null;
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800332 useSurface = (surfaceTextureHelper != null);
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000333 String[] supportedCodecPrefixes = null;
334 if (type == VideoCodecType.VIDEO_CODEC_VP8) {
335 mime = VP8_MIME_TYPE;
Alex Leung5b6891a2018-01-18 10:01:14 -0800336 supportedCodecPrefixes = supportedVp8HwCodecPrefixes();
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800337 } else if (type == VideoCodecType.VIDEO_CODEC_VP9) {
338 mime = VP9_MIME_TYPE;
339 supportedCodecPrefixes = supportedVp9HwCodecPrefixes;
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000340 } else if (type == VideoCodecType.VIDEO_CODEC_H264) {
341 mime = H264_MIME_TYPE;
Alex Leung5b6891a2018-01-18 10:01:14 -0800342 supportedCodecPrefixes = supportedH264HwCodecPrefixes();
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000343 } else {
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800344 throw new RuntimeException("initDecode: Non-supported codec " + type);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000345 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000346 DecoderProperties properties = findDecoder(mime, supportedCodecPrefixes);
347 if (properties == null) {
348 throw new RuntimeException("Cannot find HW decoder for " + type);
349 }
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800350
sakalb6760f92016-09-29 04:12:44 -0700351 Logging.d(TAG, "Java initDecode: " + type + " : " + width + " x " + height + ". Color: 0x"
352 + Integer.toHexString(properties.colorFormat) + ". Use Surface: " + useSurface);
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800353
Alex Glaznevc6aec4b2015-10-19 16:39:19 -0700354 runningInstance = this; // Decoder is now running and can be queried for stack traces.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000355 mediaCodecThread = Thread.currentThread();
356 try {
357 this.width = width;
358 this.height = height;
359 stride = width;
360 sliceHeight = height;
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000361
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100362 if (useSurface && surfaceTextureHelper != null) {
Per488e75f2015-11-19 10:43:36 +0100363 textureListener = new TextureListener(surfaceTextureHelper);
364 surface = new Surface(surfaceTextureHelper.getSurfaceTexture());
Magnus Jedvert80cf97c2015-06-11 10:08:59 +0200365 }
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000366
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000367 MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000368 if (!useSurface) {
369 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
370 }
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700371 Logging.d(TAG, " Format: " + format);
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800372 mediaCodec = MediaCodecVideoEncoder.createByCodecName(properties.codecName);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000373 if (mediaCodec == null) {
Alex Glaznev325d4142015-10-12 14:56:02 -0700374 Logging.e(TAG, "Can not create media decoder");
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000375 return false;
376 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200377 mediaCodec.configure(format, surface, null, 0);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000378 mediaCodec.start();
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800379
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000380 colorFormat = properties.colorFormat;
381 outputBuffers = mediaCodec.getOutputBuffers();
382 inputBuffers = mediaCodec.getInputBuffers();
Per488e75f2015-11-19 10:43:36 +0100383 decodeStartTimeMs.clear();
384 hasDecodedFirstFrame = false;
385 dequeuedSurfaceOutputBuffers.clear();
386 droppedFrames = 0;
sakalb6760f92016-09-29 04:12:44 -0700387 Logging.d(TAG,
388 "Input buffers: " + inputBuffers.length + ". Output buffers: " + outputBuffers.length);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000389 return true;
390 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700391 Logging.e(TAG, "initDecode failed", e);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000392 return false;
393 }
394 }
395
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800396 // Resets the decoder so it can start decoding frames with new resolution.
397 // Flushes MediaCodec and clears decoder output buffers.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100398 @CalledByNativeUnchecked
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800399 private void reset(int width, int height) {
400 if (mediaCodecThread == null || mediaCodec == null) {
401 throw new RuntimeException("Incorrect reset call for non-initialized decoder.");
402 }
403 Logging.d(TAG, "Java reset: " + width + " x " + height);
404
405 mediaCodec.flush();
406
407 this.width = width;
408 this.height = height;
409 decodeStartTimeMs.clear();
410 dequeuedSurfaceOutputBuffers.clear();
411 hasDecodedFirstFrame = false;
412 droppedFrames = 0;
413 }
414
Magnus Jedvert655e1962017-12-08 11:05:22 +0100415 @CalledByNativeUnchecked
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000416 private void release() {
Per488e75f2015-11-19 10:43:36 +0100417 Logging.d(TAG, "Java releaseDecoder. Total number of dropped frames: " + droppedFrames);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000418 checkOnMediaCodecThread();
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700419
420 // Run Mediacodec stop() and release() on separate thread since sometime
421 // Mediacodec.stop() may hang.
422 final CountDownLatch releaseDone = new CountDownLatch(1);
423
424 Runnable runMediaCodecRelease = new Runnable() {
425 @Override
426 public void run() {
427 try {
428 Logging.d(TAG, "Java releaseDecoder on release thread");
429 mediaCodec.stop();
430 mediaCodec.release();
431 Logging.d(TAG, "Java releaseDecoder on release thread done");
432 } catch (Exception e) {
433 Logging.e(TAG, "Media decoder release failed", e);
434 }
435 releaseDone.countDown();
436 }
437 };
438 new Thread(runMediaCodecRelease).start();
439
440 if (!ThreadUtils.awaitUninterruptibly(releaseDone, MEDIA_CODEC_RELEASE_TIMEOUT_MS)) {
441 Logging.e(TAG, "Media decoder release timeout");
442 codecErrors++;
443 if (errorCallback != null) {
444 Logging.e(TAG, "Invoke codec error callback. Errors: " + codecErrors);
445 errorCallback.onMediaCodecVideoDecoderCriticalError(codecErrors);
446 }
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000447 }
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700448
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000449 mediaCodec = null;
450 mediaCodecThread = null;
Alex Glaznevc6aec4b2015-10-19 16:39:19 -0700451 runningInstance = null;
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000452 if (useSurface) {
453 surface.release();
Magnus Jedvert7e319372015-10-02 15:49:38 +0200454 surface = null;
Per488e75f2015-11-19 10:43:36 +0100455 textureListener.release();
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000456 }
Alex Glaznev325d4142015-10-12 14:56:02 -0700457 Logging.d(TAG, "Java releaseDecoder done");
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000458 }
459
460 // Dequeue an input buffer and return its index, -1 if no input buffer is
461 // available, or -2 if the codec is no longer operative.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100462 @CalledByNativeUnchecked
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000463 private int dequeueInputBuffer() {
464 checkOnMediaCodecThread();
465 try {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000466 return mediaCodec.dequeueInputBuffer(DEQUEUE_INPUT_TIMEOUT);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000467 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700468 Logging.e(TAG, "dequeueIntputBuffer failed", e);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000469 return -2;
470 }
471 }
472
Magnus Jedvert655e1962017-12-08 11:05:22 +0100473 @CalledByNativeUnchecked
Per488e75f2015-11-19 10:43:36 +0100474 private boolean queueInputBuffer(int inputBufferIndex, int size, long presentationTimeStamUs,
475 long timeStampMs, long ntpTimeStamp) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000476 checkOnMediaCodecThread();
477 try {
478 inputBuffers[inputBufferIndex].position(0);
479 inputBuffers[inputBufferIndex].limit(size);
sakalb6760f92016-09-29 04:12:44 -0700480 decodeStartTimeMs.add(
481 new TimeStamps(SystemClock.elapsedRealtime(), timeStampMs, ntpTimeStamp));
Per488e75f2015-11-19 10:43:36 +0100482 mediaCodec.queueInputBuffer(inputBufferIndex, 0, size, presentationTimeStamUs, 0);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000483 return true;
sakalb6760f92016-09-29 04:12:44 -0700484 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700485 Logging.e(TAG, "decode failed", e);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000486 return false;
487 }
488 }
489
Per488e75f2015-11-19 10:43:36 +0100490 private static class TimeStamps {
491 public TimeStamps(long decodeStartTimeMs, long timeStampMs, long ntpTimeStampMs) {
492 this.decodeStartTimeMs = decodeStartTimeMs;
493 this.timeStampMs = timeStampMs;
494 this.ntpTimeStampMs = ntpTimeStampMs;
495 }
Alex Glazneveee86a62016-01-29 14:17:07 -0800496 // Time when this frame was queued for decoding.
497 private final long decodeStartTimeMs;
498 // Only used for bookkeeping in Java. Stores C++ inputImage._timeStamp value for input frame.
499 private final long timeStampMs;
500 // Only used for bookkeeping in Java. Stores C++ inputImage.ntp_time_ms_ value for input frame.
501 private final long ntpTimeStampMs;
Per488e75f2015-11-19 10:43:36 +0100502 }
503
504 // Helper struct for dequeueOutputBuffer() below.
505 private static class DecodedOutputBuffer {
glaznev94291482016-02-01 13:17:18 -0800506 public DecodedOutputBuffer(int index, int offset, int size, long presentationTimeStampMs,
507 long timeStampMs, long ntpTimeStampMs, long decodeTime, long endDecodeTime) {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000508 this.index = index;
509 this.offset = offset;
510 this.size = size;
glaznev94291482016-02-01 13:17:18 -0800511 this.presentationTimeStampMs = presentationTimeStampMs;
Per488e75f2015-11-19 10:43:36 +0100512 this.timeStampMs = timeStampMs;
513 this.ntpTimeStampMs = ntpTimeStampMs;
514 this.decodeTimeMs = decodeTime;
515 this.endDecodeTimeMs = endDecodeTime;
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000516 }
517
518 private final int index;
519 private final int offset;
520 private final int size;
glaznev94291482016-02-01 13:17:18 -0800521 // Presentation timestamp returned in dequeueOutputBuffer call.
522 private final long presentationTimeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800523 // C++ inputImage._timeStamp value for output frame.
Per488e75f2015-11-19 10:43:36 +0100524 private final long timeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800525 // C++ inputImage.ntp_time_ms_ value for output frame.
Per488e75f2015-11-19 10:43:36 +0100526 private final long ntpTimeStampMs;
527 // Number of ms it took to decode this frame.
528 private final long decodeTimeMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800529 // System time when this frame decoding finished.
Per488e75f2015-11-19 10:43:36 +0100530 private final long endDecodeTimeMs;
Magnus Jedvert655e1962017-12-08 11:05:22 +0100531
532 @CalledByNative("DecodedOutputBuffer")
533 int getIndex() {
534 return index;
535 }
536
537 @CalledByNative("DecodedOutputBuffer")
538 int getOffset() {
539 return offset;
540 }
541
542 @CalledByNative("DecodedOutputBuffer")
543 int getSize() {
544 return size;
545 }
546
547 @CalledByNative("DecodedOutputBuffer")
548 long getPresentationTimestampMs() {
549 return presentationTimeStampMs;
550 }
551
552 @CalledByNative("DecodedOutputBuffer")
553 long getTimestampMs() {
554 return timeStampMs;
555 }
556
557 @CalledByNative("DecodedOutputBuffer")
558 long getNtpTimestampMs() {
559 return ntpTimeStampMs;
560 }
561
562 @CalledByNative("DecodedOutputBuffer")
563 long getDecodeTimeMs() {
564 return decodeTimeMs;
565 }
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000566 }
567
Per488e75f2015-11-19 10:43:36 +0100568 // Helper struct for dequeueTextureBuffer() below.
magjed44bf6f52015-10-03 02:08:00 -0700569 private static class DecodedTextureBuffer {
570 private final int textureID;
Per488e75f2015-11-19 10:43:36 +0100571 private final float[] transformMatrix;
glaznev94291482016-02-01 13:17:18 -0800572 // Presentation timestamp returned in dequeueOutputBuffer call.
573 private final long presentationTimeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800574 // C++ inputImage._timeStamp value for output frame.
Per488e75f2015-11-19 10:43:36 +0100575 private final long timeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800576 // C++ inputImage.ntp_time_ms_ value for output frame.
Per488e75f2015-11-19 10:43:36 +0100577 private final long ntpTimeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800578 // Number of ms it took to decode this frame.
Per488e75f2015-11-19 10:43:36 +0100579 private final long decodeTimeMs;
580 // Interval from when the frame finished decoding until this buffer has been created.
581 // Since there is only one texture, this interval depend on the time from when
582 // a frame is decoded and provided to C++ and until that frame is returned to the MediaCodec
583 // so that the texture can be updated with the next decoded frame.
584 private final long frameDelayMs;
magjed44bf6f52015-10-03 02:08:00 -0700585
Per488e75f2015-11-19 10:43:36 +0100586 // A DecodedTextureBuffer with zero |textureID| has special meaning and represents a frame
587 // that was dropped.
glaznev94291482016-02-01 13:17:18 -0800588 public DecodedTextureBuffer(int textureID, float[] transformMatrix,
589 long presentationTimeStampMs, long timeStampMs, long ntpTimeStampMs, long decodeTimeMs,
590 long frameDelay) {
magjed44bf6f52015-10-03 02:08:00 -0700591 this.textureID = textureID;
Per488e75f2015-11-19 10:43:36 +0100592 this.transformMatrix = transformMatrix;
glaznev94291482016-02-01 13:17:18 -0800593 this.presentationTimeStampMs = presentationTimeStampMs;
Per488e75f2015-11-19 10:43:36 +0100594 this.timeStampMs = timeStampMs;
595 this.ntpTimeStampMs = ntpTimeStampMs;
596 this.decodeTimeMs = decodeTimeMs;
597 this.frameDelayMs = frameDelay;
magjed44bf6f52015-10-03 02:08:00 -0700598 }
Magnus Jedvert655e1962017-12-08 11:05:22 +0100599
600 @CalledByNative("DecodedTextureBuffer")
601 int getTextureId() {
602 return textureID;
603 }
604
605 @CalledByNative("DecodedTextureBuffer")
606 float[] getTransformMatrix() {
607 return transformMatrix;
608 }
609
610 @CalledByNative("DecodedTextureBuffer")
611 long getPresentationTimestampMs() {
612 return presentationTimeStampMs;
613 }
614
615 @CalledByNative("DecodedTextureBuffer")
616 long getTimeStampMs() {
617 return timeStampMs;
618 }
619
620 @CalledByNative("DecodedTextureBuffer")
621 long getNtpTimestampMs() {
622 return ntpTimeStampMs;
623 }
624
625 @CalledByNative("DecodedTextureBuffer")
626 long getDecodeTimeMs() {
627 return decodeTimeMs;
628 }
629
630 @CalledByNative("DecodedTextureBuffer")
631 long getFrameDelayMs() {
632 return frameDelayMs;
633 }
magjed44bf6f52015-10-03 02:08:00 -0700634 }
635
Per488e75f2015-11-19 10:43:36 +0100636 // Poll based texture listener.
637 private static class TextureListener
638 implements SurfaceTextureHelper.OnTextureFrameAvailableListener {
639 private final SurfaceTextureHelper surfaceTextureHelper;
640 // |newFrameLock| is used to synchronize arrival of new frames with wait()/notifyAll().
641 private final Object newFrameLock = new Object();
642 // |bufferToRender| is non-null when waiting for transition between addBufferToRender() to
643 // onTextureFrameAvailable().
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100644 @Nullable private DecodedOutputBuffer bufferToRender;
645 @Nullable private DecodedTextureBuffer renderedBuffer;
Per488e75f2015-11-19 10:43:36 +0100646
647 public TextureListener(SurfaceTextureHelper surfaceTextureHelper) {
648 this.surfaceTextureHelper = surfaceTextureHelper;
magjed81e8e372016-03-03 02:11:44 -0800649 surfaceTextureHelper.startListening(this);
Per488e75f2015-11-19 10:43:36 +0100650 }
651
652 public void addBufferToRender(DecodedOutputBuffer buffer) {
653 if (bufferToRender != null) {
sakalb6760f92016-09-29 04:12:44 -0700654 Logging.e(TAG, "Unexpected addBufferToRender() called while waiting for a texture.");
Per488e75f2015-11-19 10:43:36 +0100655 throw new IllegalStateException("Waiting for a texture.");
656 }
657 bufferToRender = buffer;
658 }
659
660 public boolean isWaitingForTexture() {
661 synchronized (newFrameLock) {
662 return bufferToRender != null;
663 }
664 }
665
666 // Callback from |surfaceTextureHelper|. May be called on an arbitrary thread.
667 @Override
668 public void onTextureFrameAvailable(
669 int oesTextureId, float[] transformMatrix, long timestampNs) {
670 synchronized (newFrameLock) {
671 if (renderedBuffer != null) {
sakalb6760f92016-09-29 04:12:44 -0700672 Logging.e(
673 TAG, "Unexpected onTextureFrameAvailable() called while already holding a texture.");
Per488e75f2015-11-19 10:43:36 +0100674 throw new IllegalStateException("Already holding a texture.");
675 }
676 // |timestampNs| is always zero on some Android versions.
677 renderedBuffer = new DecodedTextureBuffer(oesTextureId, transformMatrix,
glaznev94291482016-02-01 13:17:18 -0800678 bufferToRender.presentationTimeStampMs, bufferToRender.timeStampMs,
679 bufferToRender.ntpTimeStampMs, bufferToRender.decodeTimeMs,
Per488e75f2015-11-19 10:43:36 +0100680 SystemClock.elapsedRealtime() - bufferToRender.endDecodeTimeMs);
681 bufferToRender = null;
682 newFrameLock.notifyAll();
683 }
684 }
685
686 // Dequeues and returns a DecodedTextureBuffer if available, or null otherwise.
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100687 @Nullable
Sami Kalliomäki9828beb2017-10-26 16:21:22 +0200688 @SuppressWarnings("WaitNotInLoop")
Per488e75f2015-11-19 10:43:36 +0100689 public DecodedTextureBuffer dequeueTextureBuffer(int timeoutMs) {
690 synchronized (newFrameLock) {
691 if (renderedBuffer == null && timeoutMs > 0 && isWaitingForTexture()) {
692 try {
693 newFrameLock.wait(timeoutMs);
sakalb6760f92016-09-29 04:12:44 -0700694 } catch (InterruptedException e) {
Per488e75f2015-11-19 10:43:36 +0100695 // Restore the interrupted status by reinterrupting the thread.
696 Thread.currentThread().interrupt();
697 }
698 }
699 DecodedTextureBuffer returnedBuffer = renderedBuffer;
700 renderedBuffer = null;
701 return returnedBuffer;
702 }
703 }
704
705 public void release() {
nissea44e72c2016-05-27 00:27:59 -0700706 // SurfaceTextureHelper.stopListening() will block until any onTextureFrameAvailable() in
707 // progress is done. Therefore, the call must be outside any synchronized
Per488e75f2015-11-19 10:43:36 +0100708 // statement that is also used in the onTextureFrameAvailable() above to avoid deadlocks.
nissea44e72c2016-05-27 00:27:59 -0700709 surfaceTextureHelper.stopListening();
Per488e75f2015-11-19 10:43:36 +0100710 synchronized (newFrameLock) {
711 if (renderedBuffer != null) {
712 surfaceTextureHelper.returnTextureFrame();
713 renderedBuffer = null;
714 }
715 }
716 }
717 }
718
719 // Returns null if no decoded buffer is available, and otherwise a DecodedByteBuffer.
Magnus Jedvert7e319372015-10-02 15:49:38 +0200720 // Throws IllegalStateException if call is made on the wrong thread, if color format changes to an
721 // unsupported format, or if |mediaCodec| is not in the Executing state. Throws CodecException
722 // upon codec error.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100723 @CalledByNativeUnchecked
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100724 private @Nullable DecodedOutputBuffer dequeueOutputBuffer(int dequeueTimeoutMs) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000725 checkOnMediaCodecThread();
Per488e75f2015-11-19 10:43:36 +0100726 if (decodeStartTimeMs.isEmpty()) {
727 return null;
728 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200729 // Drain the decoder until receiving a decoded buffer or hitting
730 // MediaCodec.INFO_TRY_AGAIN_LATER.
731 final MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
732 while (true) {
sakalb6760f92016-09-29 04:12:44 -0700733 final int result =
734 mediaCodec.dequeueOutputBuffer(info, TimeUnit.MILLISECONDS.toMicros(dequeueTimeoutMs));
Magnus Jedvert7e319372015-10-02 15:49:38 +0200735 switch (result) {
Magnus Jedvert7e319372015-10-02 15:49:38 +0200736 case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000737 outputBuffers = mediaCodec.getOutputBuffers();
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700738 Logging.d(TAG, "Decoder output buffers changed: " + outputBuffers.length);
Per488e75f2015-11-19 10:43:36 +0100739 if (hasDecodedFirstFrame) {
740 throw new RuntimeException("Unexpected output buffer change event.");
741 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200742 break;
743 case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000744 MediaFormat format = mediaCodec.getOutputFormat();
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700745 Logging.d(TAG, "Decoder format changed: " + format.toString());
magjed0c29de52017-03-02 00:55:32 -0800746 final int newWidth;
747 final int newHeight;
748 if (format.containsKey(FORMAT_KEY_CROP_LEFT) && format.containsKey(FORMAT_KEY_CROP_RIGHT)
749 && format.containsKey(FORMAT_KEY_CROP_BOTTOM)
750 && format.containsKey(FORMAT_KEY_CROP_TOP)) {
751 newWidth = 1 + format.getInteger(FORMAT_KEY_CROP_RIGHT)
752 - format.getInteger(FORMAT_KEY_CROP_LEFT);
753 newHeight = 1 + format.getInteger(FORMAT_KEY_CROP_BOTTOM)
754 - format.getInteger(FORMAT_KEY_CROP_TOP);
755 } else {
756 newWidth = format.getInteger(MediaFormat.KEY_WIDTH);
757 newHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
Per488e75f2015-11-19 10:43:36 +0100758 }
magjed0c29de52017-03-02 00:55:32 -0800759 if (hasDecodedFirstFrame && (newWidth != width || newHeight != height)) {
760 throw new RuntimeException("Unexpected size change. Configured " + width + "*" + height
761 + ". New " + newWidth + "*" + newHeight);
762 }
763 width = newWidth;
764 height = newHeight;
Per488e75f2015-11-19 10:43:36 +0100765
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000766 if (!useSurface && format.containsKey(MediaFormat.KEY_COLOR_FORMAT)) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000767 colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700768 Logging.d(TAG, "Color: 0x" + Integer.toHexString(colorFormat));
Magnus Jedvert7e319372015-10-02 15:49:38 +0200769 if (!supportedColorList.contains(colorFormat)) {
770 throw new IllegalStateException("Non supported color format: " + colorFormat);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000771 }
772 }
magjed0c29de52017-03-02 00:55:32 -0800773 if (format.containsKey(FORMAT_KEY_STRIDE)) {
774 stride = format.getInteger(FORMAT_KEY_STRIDE);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000775 }
magjed0c29de52017-03-02 00:55:32 -0800776 if (format.containsKey(FORMAT_KEY_SLICE_HEIGHT)) {
777 sliceHeight = format.getInteger(FORMAT_KEY_SLICE_HEIGHT);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000778 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200779 Logging.d(TAG, "Frame stride and slice height: " + stride + " x " + sliceHeight);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000780 stride = Math.max(width, stride);
781 sliceHeight = Math.max(height, sliceHeight);
Magnus Jedvert7e319372015-10-02 15:49:38 +0200782 break;
Per488e75f2015-11-19 10:43:36 +0100783 case MediaCodec.INFO_TRY_AGAIN_LATER:
784 return null;
Magnus Jedvert7e319372015-10-02 15:49:38 +0200785 default:
Per488e75f2015-11-19 10:43:36 +0100786 hasDecodedFirstFrame = true;
787 TimeStamps timeStamps = decodeStartTimeMs.remove();
magjedcedddbd2016-02-26 09:36:04 -0800788 long decodeTimeMs = SystemClock.elapsedRealtime() - timeStamps.decodeStartTimeMs;
789 if (decodeTimeMs > MAX_DECODE_TIME_MS) {
Alex Glaznevd5704842016-06-27 11:51:10 -0700790 Logging.e(TAG, "Very high decode time: " + decodeTimeMs + "ms"
sakalb6760f92016-09-29 04:12:44 -0700791 + ". Q size: " + decodeStartTimeMs.size()
792 + ". Might be caused by resuming H264 decoding after a pause.");
magjedcedddbd2016-02-26 09:36:04 -0800793 decodeTimeMs = MAX_DECODE_TIME_MS;
794 }
sakalb6760f92016-09-29 04:12:44 -0700795 return new DecodedOutputBuffer(result, info.offset, info.size,
796 TimeUnit.MICROSECONDS.toMillis(info.presentationTimeUs), timeStamps.timeStampMs,
797 timeStamps.ntpTimeStampMs, decodeTimeMs, SystemClock.elapsedRealtime());
798 }
perkj9cb89822015-11-11 03:27:01 -0800799 }
perkj9cb89822015-11-11 03:27:01 -0800800 }
801
Per488e75f2015-11-19 10:43:36 +0100802 // Returns null if no decoded buffer is available, and otherwise a DecodedTextureBuffer.
803 // Throws IllegalStateException if call is made on the wrong thread, if color format changes to an
804 // unsupported format, or if |mediaCodec| is not in the Executing state. Throws CodecException
805 // upon codec error. If |dequeueTimeoutMs| > 0, the oldest decoded frame will be dropped if
806 // a frame can't be returned.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100807 @CalledByNativeUnchecked
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100808 private @Nullable DecodedTextureBuffer dequeueTextureBuffer(int dequeueTimeoutMs) {
Per488e75f2015-11-19 10:43:36 +0100809 checkOnMediaCodecThread();
810 if (!useSurface) {
811 throw new IllegalStateException("dequeueTexture() called for byte buffer decoding.");
812 }
813 DecodedOutputBuffer outputBuffer = dequeueOutputBuffer(dequeueTimeoutMs);
814 if (outputBuffer != null) {
815 dequeuedSurfaceOutputBuffers.add(outputBuffer);
816 }
817
818 MaybeRenderDecodedTextureBuffer();
819 // Check if there is texture ready now by waiting max |dequeueTimeoutMs|.
820 DecodedTextureBuffer renderedBuffer = textureListener.dequeueTextureBuffer(dequeueTimeoutMs);
821 if (renderedBuffer != null) {
822 MaybeRenderDecodedTextureBuffer();
823 return renderedBuffer;
824 }
825
826 if ((dequeuedSurfaceOutputBuffers.size()
sakalb6760f92016-09-29 04:12:44 -0700827 >= Math.min(MAX_QUEUED_OUTPUTBUFFERS, outputBuffers.length)
828 || (dequeueTimeoutMs > 0 && !dequeuedSurfaceOutputBuffers.isEmpty()))) {
Per488e75f2015-11-19 10:43:36 +0100829 ++droppedFrames;
830 // Drop the oldest frame still in dequeuedSurfaceOutputBuffers.
831 // The oldest frame is owned by |textureListener| and can't be dropped since
832 // mediaCodec.releaseOutputBuffer has already been called.
833 final DecodedOutputBuffer droppedFrame = dequeuedSurfaceOutputBuffers.remove();
834 if (dequeueTimeoutMs > 0) {
perkj7baf79f2015-11-24 06:26:38 -0800835 // TODO(perkj): Re-add the below log when VideoRenderGUI has been removed or fixed to
836 // return the one and only texture even if it does not render.
glaznevae95ff32016-02-04 11:47:12 -0800837 Logging.w(TAG, "Draining decoder. Dropping frame with TS: "
sakalb6760f92016-09-29 04:12:44 -0700838 + droppedFrame.presentationTimeStampMs + ". Total number of dropped frames: "
839 + droppedFrames);
Per488e75f2015-11-19 10:43:36 +0100840 } else {
sakalb6760f92016-09-29 04:12:44 -0700841 Logging.w(TAG, "Too many output buffers " + dequeuedSurfaceOutputBuffers.size()
842 + ". Dropping frame with TS: " + droppedFrame.presentationTimeStampMs
843 + ". Total number of dropped frames: " + droppedFrames);
Per488e75f2015-11-19 10:43:36 +0100844 }
845
846 mediaCodec.releaseOutputBuffer(droppedFrame.index, false /* render */);
sakalb6760f92016-09-29 04:12:44 -0700847 return new DecodedTextureBuffer(0, null, droppedFrame.presentationTimeStampMs,
848 droppedFrame.timeStampMs, droppedFrame.ntpTimeStampMs, droppedFrame.decodeTimeMs,
Per488e75f2015-11-19 10:43:36 +0100849 SystemClock.elapsedRealtime() - droppedFrame.endDecodeTimeMs);
850 }
851 return null;
852 }
853
854 private void MaybeRenderDecodedTextureBuffer() {
855 if (dequeuedSurfaceOutputBuffers.isEmpty() || textureListener.isWaitingForTexture()) {
856 return;
857 }
858 // Get the first frame in the queue and render to the decoder output surface.
859 final DecodedOutputBuffer buffer = dequeuedSurfaceOutputBuffers.remove();
860 textureListener.addBufferToRender(buffer);
861 mediaCodec.releaseOutputBuffer(buffer.index, true /* render */);
862 }
863
magjed44bf6f52015-10-03 02:08:00 -0700864 // Release a dequeued output byte buffer back to the codec for re-use. Should only be called for
865 // non-surface decoding.
866 // Throws IllegalStateException if the call is made on the wrong thread, if codec is configured
867 // for surface decoding, or if |mediaCodec| is not in the Executing state. Throws
868 // MediaCodec.CodecException upon codec error.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100869 @CalledByNativeUnchecked
Per488e75f2015-11-19 10:43:36 +0100870 private void returnDecodedOutputBuffer(int index)
Magnus Jedvert7e319372015-10-02 15:49:38 +0200871 throws IllegalStateException, MediaCodec.CodecException {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000872 checkOnMediaCodecThread();
magjed44bf6f52015-10-03 02:08:00 -0700873 if (useSurface) {
Per488e75f2015-11-19 10:43:36 +0100874 throw new IllegalStateException("returnDecodedOutputBuffer() called for surface decoding.");
magjed44bf6f52015-10-03 02:08:00 -0700875 }
876 mediaCodec.releaseOutputBuffer(index, false /* render */);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000877 }
Magnus Jedvert655e1962017-12-08 11:05:22 +0100878
879 @CalledByNative
880 ByteBuffer[] getInputBuffers() {
881 return inputBuffers;
882 }
883
884 @CalledByNative
885 ByteBuffer[] getOutputBuffers() {
886 return outputBuffers;
887 }
888
889 @CalledByNative
890 int getColorFormat() {
891 return colorFormat;
892 }
893
894 @CalledByNative
895 int getWidth() {
896 return width;
897 }
898
899 @CalledByNative
900 int getHeight() {
901 return height;
902 }
903
904 @CalledByNative
905 int getStride() {
906 return stride;
907 }
908
909 @CalledByNative
910 int getSliceHeight() {
911 return sliceHeight;
912 }
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000913}