blob: fc7218953e987959e638d82d2121841ffa0b5c06 [file] [log] [blame]
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +00001/*
kjellanderb24317b2016-02-10 07:54:43 -08002 * Copyright 2014 The WebRTC project authors. All Rights Reserved.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +00003 *
kjellanderb24317b2016-02-10 07:54:43 -08004 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +00009 */
10
11package org.webrtc;
12
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010013import android.graphics.SurfaceTexture;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000014import android.media.MediaCodec;
buildbot@webrtc.orga09a9992014-08-13 17:26:08 +000015import android.media.MediaCodecInfo;
glaznev@webrtc.org99678452014-09-15 17:52:42 +000016import android.media.MediaCodecInfo.CodecCapabilities;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000017import android.media.MediaCodecList;
18import android.media.MediaFormat;
19import android.os.Build;
Per488e75f2015-11-19 10:43:36 +010020import android.os.SystemClock;
glaznev@webrtc.org99678452014-09-15 17:52:42 +000021import android.view.Surface;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000022import java.nio.ByteBuffer;
Magnus Jedvert6062f372017-11-16 16:53:12 +010023import java.util.ArrayDeque;
Alex Leung5b6891a2018-01-18 10:01:14 -080024import java.util.ArrayList;
Magnus Jedvert7e319372015-10-02 15:49:38 +020025import java.util.Arrays;
Alex Glazneveee86a62016-01-29 14:17:07 -080026import java.util.HashSet;
Magnus Jedvert91b348c2015-10-07 22:57:06 +020027import java.util.List;
Sami Kalliomakid3235f02016-08-02 15:44:04 +020028import java.util.Queue;
Alex Glazneveee86a62016-01-29 14:17:07 -080029import java.util.Set;
Alex Glaznev5c3da4b2015-10-30 15:31:07 -070030import java.util.concurrent.CountDownLatch;
Per488e75f2015-11-19 10:43:36 +010031import java.util.concurrent.TimeUnit;
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010032import javax.annotation.Nullable;
Magnus Jedvertb9ac1212018-04-23 11:29:05 +020033import org.webrtc.EglBase;
34import org.webrtc.VideoFrame;
magjed8c425aa2015-10-22 16:52:39 -070035
Magnus Jedvert9060eb12017-12-12 12:52:54 +010036// Java-side of peerconnection.cc:MediaCodecVideoDecoder.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000037// This class is an implementation detail of the Java PeerConnection API.
Patrik Höglund68876f92015-11-12 17:36:48 +010038@SuppressWarnings("deprecation")
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +020039@Deprecated
Alex Glaznev908e77b2015-04-22 09:25:34 -070040public class MediaCodecVideoDecoder {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000041 // This class is constructed, operated, and destroyed by its C++ incarnation,
42 // so the class and its methods have non-public visibility. The API this
43 // class exposes aims to mimic the webrtc::VideoDecoder API as closely as
44 // possibly to minimize the amount of translation work necessary.
45
46 private static final String TAG = "MediaCodecVideoDecoder";
magjedcedddbd2016-02-26 09:36:04 -080047 private static final long MAX_DECODE_TIME_MS = 200;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000048
magjed0c29de52017-03-02 00:55:32 -080049 // TODO(magjed): Use MediaFormat constants when part of the public API.
50 private static final String FORMAT_KEY_STRIDE = "stride";
51 private static final String FORMAT_KEY_SLICE_HEIGHT = "slice-height";
52 private static final String FORMAT_KEY_CROP_LEFT = "crop-left";
53 private static final String FORMAT_KEY_CROP_RIGHT = "crop-right";
54 private static final String FORMAT_KEY_CROP_TOP = "crop-top";
55 private static final String FORMAT_KEY_CROP_BOTTOM = "crop-bottom";
56
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000057 // Tracks webrtc::VideoCodecType.
Magnus Jedvert655e1962017-12-08 11:05:22 +010058 public enum VideoCodecType {
Niels Möller520ca4e2018-06-04 11:14:38 +020059 VIDEO_CODEC_UNKNOWN,
Magnus Jedvert655e1962017-12-08 11:05:22 +010060 VIDEO_CODEC_VP8,
61 VIDEO_CODEC_VP9,
62 VIDEO_CODEC_H264;
63
64 @CalledByNative("VideoCodecType")
65 static VideoCodecType fromNativeIndex(int nativeIndex) {
66 return values()[nativeIndex];
67 }
68 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000069
Alex Glazneveee86a62016-01-29 14:17:07 -080070 // Timeout for input buffer dequeue.
71 private static final int DEQUEUE_INPUT_TIMEOUT = 500000;
72 // Timeout for codec releasing.
73 private static final int MEDIA_CODEC_RELEASE_TIMEOUT_MS = 5000;
74 // Max number of output buffers queued before starting to drop decoded frames.
75 private static final int MAX_QUEUED_OUTPUTBUFFERS = 3;
Alex Glaznevc6aec4b2015-10-19 16:39:19 -070076 // Active running decoder instance. Set in initDecode() (called from native code)
77 // and reset to null in release() call.
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010078 @Nullable private static MediaCodecVideoDecoder runningInstance = null;
79 @Nullable private static MediaCodecVideoDecoderErrorCallback errorCallback = null;
Alex Glaznev5c3da4b2015-10-30 15:31:07 -070080 private static int codecErrors = 0;
Alex Glazneveee86a62016-01-29 14:17:07 -080081 // List of disabled codec types - can be set from application.
82 private static Set<String> hwDecoderDisabledTypes = new HashSet<String>();
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +020083 @Nullable private static EglBase eglBase;
Alex Glaznev5c3da4b2015-10-30 15:31:07 -070084
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010085 @Nullable private Thread mediaCodecThread;
86 @Nullable private MediaCodec mediaCodec;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000087 private ByteBuffer[] inputBuffers;
88 private ByteBuffer[] outputBuffers;
89 private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8";
Alex Glaznev69a7fd52015-11-10 10:25:40 -080090 private static final String VP9_MIME_TYPE = "video/x-vnd.on2.vp9";
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +000091 private static final String H264_MIME_TYPE = "video/avc";
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000092 // List of supported HW VP8 decoders.
Alex Leung5b6891a2018-01-18 10:01:14 -080093 private static final String[] supportedVp8HwCodecPrefixes() {
94 ArrayList<String> supportedPrefixes = new ArrayList<String>();
95 supportedPrefixes.add("OMX.qcom.");
96 supportedPrefixes.add("OMX.Nvidia.");
97 supportedPrefixes.add("OMX.Exynos.");
98 supportedPrefixes.add("OMX.Intel.");
99 if (PeerConnectionFactory.fieldTrialsFindFullName("WebRTC-MediaTekVP8").equals("Enabled")
100 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
101 supportedPrefixes.add("OMX.MTK.");
102 }
103 return supportedPrefixes.toArray(new String[supportedPrefixes.size()]);
104 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800105 // List of supported HW VP9 decoders.
sakalb6760f92016-09-29 04:12:44 -0700106 private static final String[] supportedVp9HwCodecPrefixes = {"OMX.qcom.", "OMX.Exynos."};
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000107 // List of supported HW H.264 decoders.
Alex Leung5b6891a2018-01-18 10:01:14 -0800108 private static final String[] supportedH264HwCodecPrefixes() {
109 ArrayList<String> supportedPrefixes = new ArrayList<String>();
110 supportedPrefixes.add("OMX.qcom.");
111 supportedPrefixes.add("OMX.Intel.");
112 supportedPrefixes.add("OMX.Exynos.");
113 if (PeerConnectionFactory.fieldTrialsFindFullName("WebRTC-MediaTekH264").equals("Enabled")
Alex Leung28e71072018-02-05 13:42:48 -0800114 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
Alex Leung5b6891a2018-01-18 10:01:14 -0800115 supportedPrefixes.add("OMX.MTK.");
116 }
117 return supportedPrefixes.toArray(new String[supportedPrefixes.size()]);
118 }
119
glaznev0c1d0602017-01-27 12:24:24 -0800120 // List of supported HW H.264 high profile decoders.
glaznevcca0f6c2017-06-14 10:20:54 -0700121 private static final String supportedQcomH264HighProfileHwCodecPrefix = "OMX.qcom.";
122 private static final String supportedExynosH264HighProfileHwCodecPrefix = "OMX.Exynos.";
Alex Leung5b6891a2018-01-18 10:01:14 -0800123 private static final String supportedMediaTekH264HighProfileHwCodecPrefix = "OMX.MTK.";
glaznev893a7ee2016-09-22 10:44:30 -0700124
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000125 // NV12 color format supported by QCOM codec, but not declared in MediaCodec -
126 // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
glaznev893a7ee2016-09-22 10:44:30 -0700127 private static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar32m4ka = 0x7FA30C01;
128 private static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar16m4ka = 0x7FA30C02;
129 private static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar64x32Tile2m8ka = 0x7FA30C03;
130 private static final int COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000131 // Allowable color formats supported by codec - in order of preference.
Magnus Jedvert7e319372015-10-02 15:49:38 +0200132 private static final List<Integer> supportedColorList = Arrays.asList(
sakalb6760f92016-09-29 04:12:44 -0700133 CodecCapabilities.COLOR_FormatYUV420Planar, CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
134 CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
135 COLOR_QCOM_FORMATYVU420PackedSemiPlanar32m4ka, COLOR_QCOM_FORMATYVU420PackedSemiPlanar16m4ka,
136 COLOR_QCOM_FORMATYVU420PackedSemiPlanar64x32Tile2m8ka,
137 COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m);
glaznev893a7ee2016-09-22 10:44:30 -0700138
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000139 private int colorFormat;
140 private int width;
141 private int height;
142 private int stride;
143 private int sliceHeight;
Per488e75f2015-11-19 10:43:36 +0100144 private boolean hasDecodedFirstFrame;
Magnus Jedvert6062f372017-11-16 16:53:12 +0100145 private final Queue<TimeStamps> decodeStartTimeMs = new ArrayDeque<TimeStamps>();
Perc01c2542015-11-13 16:58:26 +0100146
Per488e75f2015-11-19 10:43:36 +0100147 // The below variables are only used when decoding to a Surface.
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100148 @Nullable private TextureListener textureListener;
Per488e75f2015-11-19 10:43:36 +0100149 private int droppedFrames;
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100150 @Nullable private Surface surface = null;
sakalb6760f92016-09-29 04:12:44 -0700151 private final Queue<DecodedOutputBuffer> dequeuedSurfaceOutputBuffers =
Magnus Jedvert6062f372017-11-16 16:53:12 +0100152 new ArrayDeque<DecodedOutputBuffer>();
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000153
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700154 // MediaCodec error handler - invoked when critical error happens which may prevent
155 // further use of media codec API. Now it means that one of media codec instances
156 // is hanging and can no longer be used in the next call.
157 public static interface MediaCodecVideoDecoderErrorCallback {
158 void onMediaCodecVideoDecoderCriticalError(int codecErrors);
159 }
160
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +0200161 /** Set EGL context used by HW decoding. The EGL context must be shared with the remote render. */
162 public static void setEglContext(EglBase.Context eglContext) {
163 if (eglBase != null) {
164 Logging.w(TAG, "Egl context already set.");
165 eglBase.release();
166 }
167 eglBase = EglBase.create(eglContext);
168 }
169
170 /** Dispose the EGL context used by HW decoding. */
171 public static void disposeEglContext() {
172 if (eglBase != null) {
173 eglBase.release();
174 eglBase = null;
175 }
176 }
177
178 @CalledByNative
179 static boolean useSurface() {
180 return eglBase != null;
181 }
182
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700183 public static void setErrorCallback(MediaCodecVideoDecoderErrorCallback errorCallback) {
184 Logging.d(TAG, "Set error callback");
185 MediaCodecVideoDecoder.errorCallback = errorCallback;
186 }
187
Alex Glazneveee86a62016-01-29 14:17:07 -0800188 // Functions to disable HW decoding - can be called from applications for platforms
189 // which have known HW decoding problems.
190 public static void disableVp8HwCodec() {
191 Logging.w(TAG, "VP8 decoding is disabled by application.");
192 hwDecoderDisabledTypes.add(VP8_MIME_TYPE);
193 }
194
195 public static void disableVp9HwCodec() {
196 Logging.w(TAG, "VP9 decoding is disabled by application.");
197 hwDecoderDisabledTypes.add(VP9_MIME_TYPE);
198 }
199
200 public static void disableH264HwCodec() {
201 Logging.w(TAG, "H.264 decoding is disabled by application.");
202 hwDecoderDisabledTypes.add(H264_MIME_TYPE);
203 }
204
205 // Functions to query if HW decoding is supported.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100206 @CalledByNativeUnchecked
Alex Glazneveee86a62016-01-29 14:17:07 -0800207 public static boolean isVp8HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700208 return !hwDecoderDisabledTypes.contains(VP8_MIME_TYPE)
Alex Leung5b6891a2018-01-18 10:01:14 -0800209 && (findDecoder(VP8_MIME_TYPE, supportedVp8HwCodecPrefixes()) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800210 }
211
Magnus Jedvert655e1962017-12-08 11:05:22 +0100212 @CalledByNativeUnchecked
Alex Glazneveee86a62016-01-29 14:17:07 -0800213 public static boolean isVp9HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700214 return !hwDecoderDisabledTypes.contains(VP9_MIME_TYPE)
215 && (findDecoder(VP9_MIME_TYPE, supportedVp9HwCodecPrefixes) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800216 }
217
Magnus Jedvert655e1962017-12-08 11:05:22 +0100218 @CalledByNativeUnchecked
Alex Glazneveee86a62016-01-29 14:17:07 -0800219 public static boolean isH264HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700220 return !hwDecoderDisabledTypes.contains(H264_MIME_TYPE)
Alex Leung5b6891a2018-01-18 10:01:14 -0800221 && (findDecoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes()) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800222 }
223
Magnus Jedvert655e1962017-12-08 11:05:22 +0100224 @CalledByNative
glaznev0c1d0602017-01-27 12:24:24 -0800225 public static boolean isH264HighProfileHwSupported() {
glaznevcca0f6c2017-06-14 10:20:54 -0700226 if (hwDecoderDisabledTypes.contains(H264_MIME_TYPE)) {
227 return false;
228 }
229 // Support H.264 HP decoding on QCOM chips for Android L and above.
230 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
231 && findDecoder(H264_MIME_TYPE, new String[] {supportedQcomH264HighProfileHwCodecPrefix})
232 != null) {
233 return true;
234 }
235 // Support H.264 HP decoding on Exynos chips for Android M and above.
236 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
237 && findDecoder(H264_MIME_TYPE, new String[] {supportedExynosH264HighProfileHwCodecPrefix})
238 != null) {
239 return true;
240 }
Alex Leung28e71072018-02-05 13:42:48 -0800241 // Support H.264 HP decoding on MediaTek chips for Android O_MR1 and above
Alex Leung5b6891a2018-01-18 10:01:14 -0800242 if (PeerConnectionFactory.fieldTrialsFindFullName("WebRTC-MediaTekH264").equals("Enabled")
Alex Leung28e71072018-02-05 13:42:48 -0800243 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1
Alex Leung5b6891a2018-01-18 10:01:14 -0800244 && findDecoder(H264_MIME_TYPE, new String[] {supportedMediaTekH264HighProfileHwCodecPrefix})
245 != null) {
246 return true;
247 }
glaznevcca0f6c2017-06-14 10:20:54 -0700248 return false;
glaznev0c1d0602017-01-27 12:24:24 -0800249 }
250
Alex Glazneveee86a62016-01-29 14:17:07 -0800251 public static void printStackTrace() {
252 if (runningInstance != null && runningInstance.mediaCodecThread != null) {
253 StackTraceElement[] mediaCodecStackTraces = runningInstance.mediaCodecThread.getStackTrace();
254 if (mediaCodecStackTraces.length > 0) {
255 Logging.d(TAG, "MediaCodecVideoDecoder stacks trace:");
256 for (StackTraceElement stackTrace : mediaCodecStackTraces) {
257 Logging.d(TAG, stackTrace.toString());
258 }
259 }
260 }
261 }
262
263 // Helper struct for findDecoder() below.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000264 private static class DecoderProperties {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000265 public DecoderProperties(String codecName, int colorFormat) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000266 this.codecName = codecName;
267 this.colorFormat = colorFormat;
268 }
269 public final String codecName; // OpenMax component name for VP8 codec.
sakalb6760f92016-09-29 04:12:44 -0700270 public final int colorFormat; // Color format supported by codec.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000271 }
272
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100273 private static @Nullable DecoderProperties findDecoder(
274 String mime, String[] supportedCodecPrefixes) {
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000275 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000276 return null; // MediaCodec.setParameters is missing.
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000277 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800278 Logging.d(TAG, "Trying to find HW decoder for mime " + mime);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000279 for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
Alex Glaznev0060c562016-08-08 12:27:24 -0700280 MediaCodecInfo info = null;
281 try {
282 info = MediaCodecList.getCodecInfoAt(i);
283 } catch (IllegalArgumentException e) {
sakalb6760f92016-09-29 04:12:44 -0700284 Logging.e(TAG, "Cannot retrieve decoder codec info", e);
Alex Glaznev0060c562016-08-08 12:27:24 -0700285 }
286 if (info == null || info.isEncoder()) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000287 continue;
288 }
289 String name = null;
290 for (String mimeType : info.getSupportedTypes()) {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000291 if (mimeType.equals(mime)) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000292 name = info.getName();
293 break;
294 }
295 }
296 if (name == null) {
sakalb6760f92016-09-29 04:12:44 -0700297 continue; // No HW support in this codec; try the next one.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000298 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800299 Logging.d(TAG, "Found candidate decoder " + name);
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000300
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000301 // Check if this is supported decoder.
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000302 boolean supportedCodec = false;
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000303 for (String codecPrefix : supportedCodecPrefixes) {
304 if (name.startsWith(codecPrefix)) {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000305 supportedCodec = true;
306 break;
307 }
308 }
309 if (!supportedCodec) {
310 continue;
311 }
312
313 // Check if codec supports either yuv420 or nv12.
Alex Glaznev0060c562016-08-08 12:27:24 -0700314 CodecCapabilities capabilities;
315 try {
316 capabilities = info.getCapabilitiesForType(mime);
317 } catch (IllegalArgumentException e) {
sakalb6760f92016-09-29 04:12:44 -0700318 Logging.e(TAG, "Cannot retrieve decoder capabilities", e);
Alex Glaznev0060c562016-08-08 12:27:24 -0700319 continue;
320 }
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000321 for (int colorFormat : capabilities.colorFormats) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700322 Logging.v(TAG, " Color: 0x" + Integer.toHexString(colorFormat));
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000323 }
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000324 for (int supportedColorFormat : supportedColorList) {
325 for (int codecColorFormat : capabilities.colorFormats) {
326 if (codecColorFormat == supportedColorFormat) {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000327 // Found supported HW decoder.
sakalb6760f92016-09-29 04:12:44 -0700328 Logging.d(TAG, "Found target decoder " + name + ". Color: 0x"
329 + Integer.toHexString(codecColorFormat));
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000330 return new DecoderProperties(name, codecColorFormat);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000331 }
332 }
333 }
334 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800335 Logging.d(TAG, "No HW decoder found for mime " + mime);
sakalb6760f92016-09-29 04:12:44 -0700336 return null; // No HW decoder.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000337 }
338
Magnus Jedvert655e1962017-12-08 11:05:22 +0100339 @CalledByNative
340 MediaCodecVideoDecoder() {}
341
Magnus Jedvert7e319372015-10-02 15:49:38 +0200342 private void checkOnMediaCodecThread() throws IllegalStateException {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000343 if (mediaCodecThread.getId() != Thread.currentThread().getId()) {
sakalb6760f92016-09-29 04:12:44 -0700344 throw new IllegalStateException("MediaCodecVideoDecoder previously operated on "
345 + mediaCodecThread + " but is now called on " + Thread.currentThread());
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000346 }
347 }
348
Magnus Jedvert655e1962017-12-08 11:05:22 +0100349 @CalledByNativeUnchecked
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +0200350 private boolean initDecode(VideoCodecType type, int width, int height) {
Alejandro Luebs69ddaef2015-10-09 15:46:09 -0700351 if (mediaCodecThread != null) {
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800352 throw new RuntimeException("initDecode: Forgot to release()?");
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000353 }
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800354
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000355 String mime = null;
356 String[] supportedCodecPrefixes = null;
357 if (type == VideoCodecType.VIDEO_CODEC_VP8) {
358 mime = VP8_MIME_TYPE;
Alex Leung5b6891a2018-01-18 10:01:14 -0800359 supportedCodecPrefixes = supportedVp8HwCodecPrefixes();
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800360 } else if (type == VideoCodecType.VIDEO_CODEC_VP9) {
361 mime = VP9_MIME_TYPE;
362 supportedCodecPrefixes = supportedVp9HwCodecPrefixes;
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000363 } else if (type == VideoCodecType.VIDEO_CODEC_H264) {
364 mime = H264_MIME_TYPE;
Alex Leung5b6891a2018-01-18 10:01:14 -0800365 supportedCodecPrefixes = supportedH264HwCodecPrefixes();
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000366 } else {
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800367 throw new RuntimeException("initDecode: Non-supported codec " + type);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000368 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000369 DecoderProperties properties = findDecoder(mime, supportedCodecPrefixes);
370 if (properties == null) {
371 throw new RuntimeException("Cannot find HW decoder for " + type);
372 }
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800373
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +0200374 Logging.d(TAG,
375 "Java initDecode: " + type + " : " + width + " x " + height + ". Color: 0x"
376 + Integer.toHexString(properties.colorFormat) + ". Use Surface: " + useSurface());
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800377
Alex Glaznevc6aec4b2015-10-19 16:39:19 -0700378 runningInstance = this; // Decoder is now running and can be queried for stack traces.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000379 mediaCodecThread = Thread.currentThread();
380 try {
381 this.width = width;
382 this.height = height;
383 stride = width;
384 sliceHeight = height;
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000385
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +0200386 if (useSurface()) {
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200387 @Nullable
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +0200388 final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create(
389 "Decoder SurfaceTextureHelper", eglBase.getEglBaseContext());
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200390 if (surfaceTextureHelper != null) {
391 textureListener = new TextureListener(surfaceTextureHelper);
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200392 textureListener.setSize(width, height);
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200393 surface = new Surface(surfaceTextureHelper.getSurfaceTexture());
394 }
Magnus Jedvert80cf97c2015-06-11 10:08:59 +0200395 }
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000396
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000397 MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +0200398 if (!useSurface()) {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000399 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
400 }
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700401 Logging.d(TAG, " Format: " + format);
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800402 mediaCodec = MediaCodecVideoEncoder.createByCodecName(properties.codecName);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000403 if (mediaCodec == null) {
Alex Glaznev325d4142015-10-12 14:56:02 -0700404 Logging.e(TAG, "Can not create media decoder");
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000405 return false;
406 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200407 mediaCodec.configure(format, surface, null, 0);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000408 mediaCodec.start();
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800409
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000410 colorFormat = properties.colorFormat;
411 outputBuffers = mediaCodec.getOutputBuffers();
412 inputBuffers = mediaCodec.getInputBuffers();
Per488e75f2015-11-19 10:43:36 +0100413 decodeStartTimeMs.clear();
414 hasDecodedFirstFrame = false;
415 dequeuedSurfaceOutputBuffers.clear();
416 droppedFrames = 0;
sakalb6760f92016-09-29 04:12:44 -0700417 Logging.d(TAG,
418 "Input buffers: " + inputBuffers.length + ". Output buffers: " + outputBuffers.length);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000419 return true;
420 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700421 Logging.e(TAG, "initDecode failed", e);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000422 return false;
423 }
424 }
425
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800426 // Resets the decoder so it can start decoding frames with new resolution.
427 // Flushes MediaCodec and clears decoder output buffers.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100428 @CalledByNativeUnchecked
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800429 private void reset(int width, int height) {
430 if (mediaCodecThread == null || mediaCodec == null) {
431 throw new RuntimeException("Incorrect reset call for non-initialized decoder.");
432 }
433 Logging.d(TAG, "Java reset: " + width + " x " + height);
434
435 mediaCodec.flush();
436
437 this.width = width;
438 this.height = height;
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200439 if (textureListener != null) {
440 textureListener.setSize(width, height);
441 }
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800442 decodeStartTimeMs.clear();
443 dequeuedSurfaceOutputBuffers.clear();
444 hasDecodedFirstFrame = false;
445 droppedFrames = 0;
446 }
447
Magnus Jedvert655e1962017-12-08 11:05:22 +0100448 @CalledByNativeUnchecked
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000449 private void release() {
Per488e75f2015-11-19 10:43:36 +0100450 Logging.d(TAG, "Java releaseDecoder. Total number of dropped frames: " + droppedFrames);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000451 checkOnMediaCodecThread();
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700452
453 // Run Mediacodec stop() and release() on separate thread since sometime
454 // Mediacodec.stop() may hang.
455 final CountDownLatch releaseDone = new CountDownLatch(1);
456
457 Runnable runMediaCodecRelease = new Runnable() {
458 @Override
459 public void run() {
460 try {
461 Logging.d(TAG, "Java releaseDecoder on release thread");
462 mediaCodec.stop();
463 mediaCodec.release();
464 Logging.d(TAG, "Java releaseDecoder on release thread done");
465 } catch (Exception e) {
466 Logging.e(TAG, "Media decoder release failed", e);
467 }
468 releaseDone.countDown();
469 }
470 };
471 new Thread(runMediaCodecRelease).start();
472
473 if (!ThreadUtils.awaitUninterruptibly(releaseDone, MEDIA_CODEC_RELEASE_TIMEOUT_MS)) {
474 Logging.e(TAG, "Media decoder release timeout");
475 codecErrors++;
476 if (errorCallback != null) {
477 Logging.e(TAG, "Invoke codec error callback. Errors: " + codecErrors);
478 errorCallback.onMediaCodecVideoDecoderCriticalError(codecErrors);
479 }
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000480 }
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700481
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000482 mediaCodec = null;
483 mediaCodecThread = null;
Alex Glaznevc6aec4b2015-10-19 16:39:19 -0700484 runningInstance = null;
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +0200485 if (useSurface()) {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000486 surface.release();
Magnus Jedvert7e319372015-10-02 15:49:38 +0200487 surface = null;
Per488e75f2015-11-19 10:43:36 +0100488 textureListener.release();
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000489 }
Alex Glaznev325d4142015-10-12 14:56:02 -0700490 Logging.d(TAG, "Java releaseDecoder done");
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000491 }
492
493 // Dequeue an input buffer and return its index, -1 if no input buffer is
494 // available, or -2 if the codec is no longer operative.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100495 @CalledByNativeUnchecked
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000496 private int dequeueInputBuffer() {
497 checkOnMediaCodecThread();
498 try {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000499 return mediaCodec.dequeueInputBuffer(DEQUEUE_INPUT_TIMEOUT);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000500 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700501 Logging.e(TAG, "dequeueIntputBuffer failed", e);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000502 return -2;
503 }
504 }
505
Magnus Jedvert655e1962017-12-08 11:05:22 +0100506 @CalledByNativeUnchecked
Per488e75f2015-11-19 10:43:36 +0100507 private boolean queueInputBuffer(int inputBufferIndex, int size, long presentationTimeStamUs,
508 long timeStampMs, long ntpTimeStamp) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000509 checkOnMediaCodecThread();
510 try {
511 inputBuffers[inputBufferIndex].position(0);
512 inputBuffers[inputBufferIndex].limit(size);
sakalb6760f92016-09-29 04:12:44 -0700513 decodeStartTimeMs.add(
514 new TimeStamps(SystemClock.elapsedRealtime(), timeStampMs, ntpTimeStamp));
Per488e75f2015-11-19 10:43:36 +0100515 mediaCodec.queueInputBuffer(inputBufferIndex, 0, size, presentationTimeStamUs, 0);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000516 return true;
sakalb6760f92016-09-29 04:12:44 -0700517 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700518 Logging.e(TAG, "decode failed", e);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000519 return false;
520 }
521 }
522
Per488e75f2015-11-19 10:43:36 +0100523 private static class TimeStamps {
524 public TimeStamps(long decodeStartTimeMs, long timeStampMs, long ntpTimeStampMs) {
525 this.decodeStartTimeMs = decodeStartTimeMs;
526 this.timeStampMs = timeStampMs;
527 this.ntpTimeStampMs = ntpTimeStampMs;
528 }
Alex Glazneveee86a62016-01-29 14:17:07 -0800529 // Time when this frame was queued for decoding.
530 private final long decodeStartTimeMs;
531 // Only used for bookkeeping in Java. Stores C++ inputImage._timeStamp value for input frame.
532 private final long timeStampMs;
533 // Only used for bookkeeping in Java. Stores C++ inputImage.ntp_time_ms_ value for input frame.
534 private final long ntpTimeStampMs;
Per488e75f2015-11-19 10:43:36 +0100535 }
536
537 // Helper struct for dequeueOutputBuffer() below.
538 private static class DecodedOutputBuffer {
glaznev94291482016-02-01 13:17:18 -0800539 public DecodedOutputBuffer(int index, int offset, int size, long presentationTimeStampMs,
540 long timeStampMs, long ntpTimeStampMs, long decodeTime, long endDecodeTime) {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000541 this.index = index;
542 this.offset = offset;
543 this.size = size;
glaznev94291482016-02-01 13:17:18 -0800544 this.presentationTimeStampMs = presentationTimeStampMs;
Per488e75f2015-11-19 10:43:36 +0100545 this.timeStampMs = timeStampMs;
546 this.ntpTimeStampMs = ntpTimeStampMs;
547 this.decodeTimeMs = decodeTime;
548 this.endDecodeTimeMs = endDecodeTime;
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000549 }
550
551 private final int index;
552 private final int offset;
553 private final int size;
glaznev94291482016-02-01 13:17:18 -0800554 // Presentation timestamp returned in dequeueOutputBuffer call.
555 private final long presentationTimeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800556 // C++ inputImage._timeStamp value for output frame.
Per488e75f2015-11-19 10:43:36 +0100557 private final long timeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800558 // C++ inputImage.ntp_time_ms_ value for output frame.
Per488e75f2015-11-19 10:43:36 +0100559 private final long ntpTimeStampMs;
560 // Number of ms it took to decode this frame.
561 private final long decodeTimeMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800562 // System time when this frame decoding finished.
Per488e75f2015-11-19 10:43:36 +0100563 private final long endDecodeTimeMs;
Magnus Jedvert655e1962017-12-08 11:05:22 +0100564
565 @CalledByNative("DecodedOutputBuffer")
566 int getIndex() {
567 return index;
568 }
569
570 @CalledByNative("DecodedOutputBuffer")
571 int getOffset() {
572 return offset;
573 }
574
575 @CalledByNative("DecodedOutputBuffer")
576 int getSize() {
577 return size;
578 }
579
580 @CalledByNative("DecodedOutputBuffer")
581 long getPresentationTimestampMs() {
582 return presentationTimeStampMs;
583 }
584
585 @CalledByNative("DecodedOutputBuffer")
586 long getTimestampMs() {
587 return timeStampMs;
588 }
589
590 @CalledByNative("DecodedOutputBuffer")
591 long getNtpTimestampMs() {
592 return ntpTimeStampMs;
593 }
594
595 @CalledByNative("DecodedOutputBuffer")
596 long getDecodeTimeMs() {
597 return decodeTimeMs;
598 }
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000599 }
600
Per488e75f2015-11-19 10:43:36 +0100601 // Helper struct for dequeueTextureBuffer() below.
magjed44bf6f52015-10-03 02:08:00 -0700602 private static class DecodedTextureBuffer {
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200603 private final VideoFrame.Buffer videoFrameBuffer;
glaznev94291482016-02-01 13:17:18 -0800604 // Presentation timestamp returned in dequeueOutputBuffer call.
605 private final long presentationTimeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800606 // C++ inputImage._timeStamp value for output frame.
Per488e75f2015-11-19 10:43:36 +0100607 private final long timeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800608 // C++ inputImage.ntp_time_ms_ value for output frame.
Per488e75f2015-11-19 10:43:36 +0100609 private final long ntpTimeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800610 // Number of ms it took to decode this frame.
Per488e75f2015-11-19 10:43:36 +0100611 private final long decodeTimeMs;
612 // Interval from when the frame finished decoding until this buffer has been created.
613 // Since there is only one texture, this interval depend on the time from when
614 // a frame is decoded and provided to C++ and until that frame is returned to the MediaCodec
615 // so that the texture can be updated with the next decoded frame.
616 private final long frameDelayMs;
magjed44bf6f52015-10-03 02:08:00 -0700617
Per488e75f2015-11-19 10:43:36 +0100618 // A DecodedTextureBuffer with zero |textureID| has special meaning and represents a frame
619 // that was dropped.
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200620 public DecodedTextureBuffer(VideoFrame.Buffer videoFrameBuffer, long presentationTimeStampMs,
621 long timeStampMs, long ntpTimeStampMs, long decodeTimeMs, long frameDelay) {
622 this.videoFrameBuffer = videoFrameBuffer;
glaznev94291482016-02-01 13:17:18 -0800623 this.presentationTimeStampMs = presentationTimeStampMs;
Per488e75f2015-11-19 10:43:36 +0100624 this.timeStampMs = timeStampMs;
625 this.ntpTimeStampMs = ntpTimeStampMs;
626 this.decodeTimeMs = decodeTimeMs;
627 this.frameDelayMs = frameDelay;
magjed44bf6f52015-10-03 02:08:00 -0700628 }
Magnus Jedvert655e1962017-12-08 11:05:22 +0100629
630 @CalledByNative("DecodedTextureBuffer")
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200631 VideoFrame.Buffer getVideoFrameBuffer() {
632 return videoFrameBuffer;
Magnus Jedvert655e1962017-12-08 11:05:22 +0100633 }
634
635 @CalledByNative("DecodedTextureBuffer")
636 long getPresentationTimestampMs() {
637 return presentationTimeStampMs;
638 }
639
640 @CalledByNative("DecodedTextureBuffer")
641 long getTimeStampMs() {
642 return timeStampMs;
643 }
644
645 @CalledByNative("DecodedTextureBuffer")
646 long getNtpTimestampMs() {
647 return ntpTimeStampMs;
648 }
649
650 @CalledByNative("DecodedTextureBuffer")
651 long getDecodeTimeMs() {
652 return decodeTimeMs;
653 }
654
655 @CalledByNative("DecodedTextureBuffer")
656 long getFrameDelayMs() {
657 return frameDelayMs;
658 }
magjed44bf6f52015-10-03 02:08:00 -0700659 }
660
Per488e75f2015-11-19 10:43:36 +0100661 // Poll based texture listener.
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200662 private class TextureListener implements VideoSink {
Per488e75f2015-11-19 10:43:36 +0100663 private final SurfaceTextureHelper surfaceTextureHelper;
664 // |newFrameLock| is used to synchronize arrival of new frames with wait()/notifyAll().
665 private final Object newFrameLock = new Object();
666 // |bufferToRender| is non-null when waiting for transition between addBufferToRender() to
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200667 // onFrame().
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100668 @Nullable private DecodedOutputBuffer bufferToRender;
669 @Nullable private DecodedTextureBuffer renderedBuffer;
Per488e75f2015-11-19 10:43:36 +0100670
671 public TextureListener(SurfaceTextureHelper surfaceTextureHelper) {
672 this.surfaceTextureHelper = surfaceTextureHelper;
magjed81e8e372016-03-03 02:11:44 -0800673 surfaceTextureHelper.startListening(this);
Per488e75f2015-11-19 10:43:36 +0100674 }
675
676 public void addBufferToRender(DecodedOutputBuffer buffer) {
677 if (bufferToRender != null) {
sakalb6760f92016-09-29 04:12:44 -0700678 Logging.e(TAG, "Unexpected addBufferToRender() called while waiting for a texture.");
Per488e75f2015-11-19 10:43:36 +0100679 throw new IllegalStateException("Waiting for a texture.");
680 }
681 bufferToRender = buffer;
682 }
683
684 public boolean isWaitingForTexture() {
685 synchronized (newFrameLock) {
686 return bufferToRender != null;
687 }
688 }
689
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200690 public void setSize(int width, int height) {
691 surfaceTextureHelper.setTextureSize(width, height);
692 }
693
Per488e75f2015-11-19 10:43:36 +0100694 // Callback from |surfaceTextureHelper|. May be called on an arbitrary thread.
695 @Override
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200696 public void onFrame(VideoFrame frame) {
Per488e75f2015-11-19 10:43:36 +0100697 synchronized (newFrameLock) {
698 if (renderedBuffer != null) {
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200699 Logging.e(TAG, "Unexpected onFrame() called while already holding a texture.");
Per488e75f2015-11-19 10:43:36 +0100700 throw new IllegalStateException("Already holding a texture.");
701 }
702 // |timestampNs| is always zero on some Android versions.
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200703 final VideoFrame.Buffer buffer = frame.getBuffer();
704 buffer.retain();
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200705 renderedBuffer = new DecodedTextureBuffer(buffer, bufferToRender.presentationTimeStampMs,
706 bufferToRender.timeStampMs, bufferToRender.ntpTimeStampMs, bufferToRender.decodeTimeMs,
Per488e75f2015-11-19 10:43:36 +0100707 SystemClock.elapsedRealtime() - bufferToRender.endDecodeTimeMs);
708 bufferToRender = null;
709 newFrameLock.notifyAll();
710 }
711 }
712
713 // Dequeues and returns a DecodedTextureBuffer if available, or null otherwise.
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100714 @Nullable
Sami Kalliomäki9828beb2017-10-26 16:21:22 +0200715 @SuppressWarnings("WaitNotInLoop")
Per488e75f2015-11-19 10:43:36 +0100716 public DecodedTextureBuffer dequeueTextureBuffer(int timeoutMs) {
717 synchronized (newFrameLock) {
718 if (renderedBuffer == null && timeoutMs > 0 && isWaitingForTexture()) {
719 try {
720 newFrameLock.wait(timeoutMs);
sakalb6760f92016-09-29 04:12:44 -0700721 } catch (InterruptedException e) {
Per488e75f2015-11-19 10:43:36 +0100722 // Restore the interrupted status by reinterrupting the thread.
723 Thread.currentThread().interrupt();
724 }
725 }
726 DecodedTextureBuffer returnedBuffer = renderedBuffer;
727 renderedBuffer = null;
728 return returnedBuffer;
729 }
730 }
731
732 public void release() {
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200733 // SurfaceTextureHelper.stopListening() will block until any onFrame() in progress is done.
734 // Therefore, the call must be outside any synchronized statement that is also used in the
735 // onFrame() above to avoid deadlocks.
nissea44e72c2016-05-27 00:27:59 -0700736 surfaceTextureHelper.stopListening();
Per488e75f2015-11-19 10:43:36 +0100737 synchronized (newFrameLock) {
738 if (renderedBuffer != null) {
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200739 renderedBuffer.getVideoFrameBuffer().release();
Per488e75f2015-11-19 10:43:36 +0100740 renderedBuffer = null;
741 }
742 }
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200743 surfaceTextureHelper.dispose();
Per488e75f2015-11-19 10:43:36 +0100744 }
745 }
746
747 // Returns null if no decoded buffer is available, and otherwise a DecodedByteBuffer.
Magnus Jedvert7e319372015-10-02 15:49:38 +0200748 // Throws IllegalStateException if call is made on the wrong thread, if color format changes to an
749 // unsupported format, or if |mediaCodec| is not in the Executing state. Throws CodecException
750 // upon codec error.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100751 @CalledByNativeUnchecked
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100752 private @Nullable DecodedOutputBuffer dequeueOutputBuffer(int dequeueTimeoutMs) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000753 checkOnMediaCodecThread();
Per488e75f2015-11-19 10:43:36 +0100754 if (decodeStartTimeMs.isEmpty()) {
755 return null;
756 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200757 // Drain the decoder until receiving a decoded buffer or hitting
758 // MediaCodec.INFO_TRY_AGAIN_LATER.
759 final MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
760 while (true) {
sakalb6760f92016-09-29 04:12:44 -0700761 final int result =
762 mediaCodec.dequeueOutputBuffer(info, TimeUnit.MILLISECONDS.toMicros(dequeueTimeoutMs));
Magnus Jedvert7e319372015-10-02 15:49:38 +0200763 switch (result) {
Magnus Jedvert7e319372015-10-02 15:49:38 +0200764 case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000765 outputBuffers = mediaCodec.getOutputBuffers();
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700766 Logging.d(TAG, "Decoder output buffers changed: " + outputBuffers.length);
Per488e75f2015-11-19 10:43:36 +0100767 if (hasDecodedFirstFrame) {
768 throw new RuntimeException("Unexpected output buffer change event.");
769 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200770 break;
771 case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000772 MediaFormat format = mediaCodec.getOutputFormat();
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700773 Logging.d(TAG, "Decoder format changed: " + format.toString());
magjed0c29de52017-03-02 00:55:32 -0800774 final int newWidth;
775 final int newHeight;
776 if (format.containsKey(FORMAT_KEY_CROP_LEFT) && format.containsKey(FORMAT_KEY_CROP_RIGHT)
777 && format.containsKey(FORMAT_KEY_CROP_BOTTOM)
778 && format.containsKey(FORMAT_KEY_CROP_TOP)) {
779 newWidth = 1 + format.getInteger(FORMAT_KEY_CROP_RIGHT)
780 - format.getInteger(FORMAT_KEY_CROP_LEFT);
781 newHeight = 1 + format.getInteger(FORMAT_KEY_CROP_BOTTOM)
782 - format.getInteger(FORMAT_KEY_CROP_TOP);
783 } else {
784 newWidth = format.getInteger(MediaFormat.KEY_WIDTH);
785 newHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
Per488e75f2015-11-19 10:43:36 +0100786 }
magjed0c29de52017-03-02 00:55:32 -0800787 if (hasDecodedFirstFrame && (newWidth != width || newHeight != height)) {
788 throw new RuntimeException("Unexpected size change. Configured " + width + "*" + height
789 + ". New " + newWidth + "*" + newHeight);
790 }
791 width = newWidth;
792 height = newHeight;
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200793 if (textureListener != null) {
794 textureListener.setSize(width, height);
795 }
Per488e75f2015-11-19 10:43:36 +0100796
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +0200797 if (!useSurface() && format.containsKey(MediaFormat.KEY_COLOR_FORMAT)) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000798 colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700799 Logging.d(TAG, "Color: 0x" + Integer.toHexString(colorFormat));
Magnus Jedvert7e319372015-10-02 15:49:38 +0200800 if (!supportedColorList.contains(colorFormat)) {
801 throw new IllegalStateException("Non supported color format: " + colorFormat);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000802 }
803 }
magjed0c29de52017-03-02 00:55:32 -0800804 if (format.containsKey(FORMAT_KEY_STRIDE)) {
805 stride = format.getInteger(FORMAT_KEY_STRIDE);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000806 }
magjed0c29de52017-03-02 00:55:32 -0800807 if (format.containsKey(FORMAT_KEY_SLICE_HEIGHT)) {
808 sliceHeight = format.getInteger(FORMAT_KEY_SLICE_HEIGHT);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000809 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200810 Logging.d(TAG, "Frame stride and slice height: " + stride + " x " + sliceHeight);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000811 stride = Math.max(width, stride);
812 sliceHeight = Math.max(height, sliceHeight);
Magnus Jedvert7e319372015-10-02 15:49:38 +0200813 break;
Per488e75f2015-11-19 10:43:36 +0100814 case MediaCodec.INFO_TRY_AGAIN_LATER:
815 return null;
Magnus Jedvert7e319372015-10-02 15:49:38 +0200816 default:
Per488e75f2015-11-19 10:43:36 +0100817 hasDecodedFirstFrame = true;
818 TimeStamps timeStamps = decodeStartTimeMs.remove();
magjedcedddbd2016-02-26 09:36:04 -0800819 long decodeTimeMs = SystemClock.elapsedRealtime() - timeStamps.decodeStartTimeMs;
820 if (decodeTimeMs > MAX_DECODE_TIME_MS) {
Alex Glaznevd5704842016-06-27 11:51:10 -0700821 Logging.e(TAG, "Very high decode time: " + decodeTimeMs + "ms"
sakalb6760f92016-09-29 04:12:44 -0700822 + ". Q size: " + decodeStartTimeMs.size()
823 + ". Might be caused by resuming H264 decoding after a pause.");
magjedcedddbd2016-02-26 09:36:04 -0800824 decodeTimeMs = MAX_DECODE_TIME_MS;
825 }
sakalb6760f92016-09-29 04:12:44 -0700826 return new DecodedOutputBuffer(result, info.offset, info.size,
827 TimeUnit.MICROSECONDS.toMillis(info.presentationTimeUs), timeStamps.timeStampMs,
828 timeStamps.ntpTimeStampMs, decodeTimeMs, SystemClock.elapsedRealtime());
829 }
perkj9cb89822015-11-11 03:27:01 -0800830 }
perkj9cb89822015-11-11 03:27:01 -0800831 }
832
Per488e75f2015-11-19 10:43:36 +0100833 // Returns null if no decoded buffer is available, and otherwise a DecodedTextureBuffer.
834 // Throws IllegalStateException if call is made on the wrong thread, if color format changes to an
835 // unsupported format, or if |mediaCodec| is not in the Executing state. Throws CodecException
836 // upon codec error. If |dequeueTimeoutMs| > 0, the oldest decoded frame will be dropped if
837 // a frame can't be returned.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100838 @CalledByNativeUnchecked
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100839 private @Nullable DecodedTextureBuffer dequeueTextureBuffer(int dequeueTimeoutMs) {
Per488e75f2015-11-19 10:43:36 +0100840 checkOnMediaCodecThread();
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +0200841 if (!useSurface()) {
Per488e75f2015-11-19 10:43:36 +0100842 throw new IllegalStateException("dequeueTexture() called for byte buffer decoding.");
843 }
844 DecodedOutputBuffer outputBuffer = dequeueOutputBuffer(dequeueTimeoutMs);
845 if (outputBuffer != null) {
846 dequeuedSurfaceOutputBuffers.add(outputBuffer);
847 }
848
849 MaybeRenderDecodedTextureBuffer();
850 // Check if there is texture ready now by waiting max |dequeueTimeoutMs|.
851 DecodedTextureBuffer renderedBuffer = textureListener.dequeueTextureBuffer(dequeueTimeoutMs);
852 if (renderedBuffer != null) {
853 MaybeRenderDecodedTextureBuffer();
854 return renderedBuffer;
855 }
856
857 if ((dequeuedSurfaceOutputBuffers.size()
sakalb6760f92016-09-29 04:12:44 -0700858 >= Math.min(MAX_QUEUED_OUTPUTBUFFERS, outputBuffers.length)
859 || (dequeueTimeoutMs > 0 && !dequeuedSurfaceOutputBuffers.isEmpty()))) {
Per488e75f2015-11-19 10:43:36 +0100860 ++droppedFrames;
861 // Drop the oldest frame still in dequeuedSurfaceOutputBuffers.
862 // The oldest frame is owned by |textureListener| and can't be dropped since
863 // mediaCodec.releaseOutputBuffer has already been called.
864 final DecodedOutputBuffer droppedFrame = dequeuedSurfaceOutputBuffers.remove();
865 if (dequeueTimeoutMs > 0) {
perkj7baf79f2015-11-24 06:26:38 -0800866 // TODO(perkj): Re-add the below log when VideoRenderGUI has been removed or fixed to
867 // return the one and only texture even if it does not render.
glaznevae95ff32016-02-04 11:47:12 -0800868 Logging.w(TAG, "Draining decoder. Dropping frame with TS: "
sakalb6760f92016-09-29 04:12:44 -0700869 + droppedFrame.presentationTimeStampMs + ". Total number of dropped frames: "
870 + droppedFrames);
Per488e75f2015-11-19 10:43:36 +0100871 } else {
sakalb6760f92016-09-29 04:12:44 -0700872 Logging.w(TAG, "Too many output buffers " + dequeuedSurfaceOutputBuffers.size()
873 + ". Dropping frame with TS: " + droppedFrame.presentationTimeStampMs
874 + ". Total number of dropped frames: " + droppedFrames);
Per488e75f2015-11-19 10:43:36 +0100875 }
876
877 mediaCodec.releaseOutputBuffer(droppedFrame.index, false /* render */);
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200878 return new DecodedTextureBuffer(null /* videoFrameBuffer */,
879 droppedFrame.presentationTimeStampMs, droppedFrame.timeStampMs,
880 droppedFrame.ntpTimeStampMs, droppedFrame.decodeTimeMs,
Per488e75f2015-11-19 10:43:36 +0100881 SystemClock.elapsedRealtime() - droppedFrame.endDecodeTimeMs);
882 }
883 return null;
884 }
885
886 private void MaybeRenderDecodedTextureBuffer() {
887 if (dequeuedSurfaceOutputBuffers.isEmpty() || textureListener.isWaitingForTexture()) {
888 return;
889 }
890 // Get the first frame in the queue and render to the decoder output surface.
891 final DecodedOutputBuffer buffer = dequeuedSurfaceOutputBuffers.remove();
892 textureListener.addBufferToRender(buffer);
893 mediaCodec.releaseOutputBuffer(buffer.index, true /* render */);
894 }
895
magjed44bf6f52015-10-03 02:08:00 -0700896 // Release a dequeued output byte buffer back to the codec for re-use. Should only be called for
897 // non-surface decoding.
898 // Throws IllegalStateException if the call is made on the wrong thread, if codec is configured
899 // for surface decoding, or if |mediaCodec| is not in the Executing state. Throws
900 // MediaCodec.CodecException upon codec error.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100901 @CalledByNativeUnchecked
Per488e75f2015-11-19 10:43:36 +0100902 private void returnDecodedOutputBuffer(int index)
Magnus Jedvert7e319372015-10-02 15:49:38 +0200903 throws IllegalStateException, MediaCodec.CodecException {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000904 checkOnMediaCodecThread();
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +0200905 if (useSurface()) {
Per488e75f2015-11-19 10:43:36 +0100906 throw new IllegalStateException("returnDecodedOutputBuffer() called for surface decoding.");
magjed44bf6f52015-10-03 02:08:00 -0700907 }
908 mediaCodec.releaseOutputBuffer(index, false /* render */);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000909 }
Magnus Jedvert655e1962017-12-08 11:05:22 +0100910
911 @CalledByNative
912 ByteBuffer[] getInputBuffers() {
913 return inputBuffers;
914 }
915
916 @CalledByNative
917 ByteBuffer[] getOutputBuffers() {
918 return outputBuffers;
919 }
920
921 @CalledByNative
922 int getColorFormat() {
923 return colorFormat;
924 }
925
926 @CalledByNative
927 int getWidth() {
928 return width;
929 }
930
931 @CalledByNative
932 int getHeight() {
933 return height;
934 }
935
936 @CalledByNative
937 int getStride() {
938 return stride;
939 }
940
941 @CalledByNative
942 int getSliceHeight() {
943 return sliceHeight;
944 }
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000945}