blob: ec65475b71928a82afb63ca4bd2a9f1aa990244a [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;
Sami Kalliomäki3d50a312018-09-11 11:11:47 +020026import java.util.HashMap;
Alex Glazneveee86a62016-01-29 14:17:07 -080027import java.util.HashSet;
Magnus Jedvert91b348c2015-10-07 22:57:06 +020028import java.util.List;
Sami Kalliomakid3235f02016-08-02 15:44:04 +020029import java.util.Queue;
Alex Glazneveee86a62016-01-29 14:17:07 -080030import java.util.Set;
Alex Glaznev5c3da4b2015-10-30 15:31:07 -070031import java.util.concurrent.CountDownLatch;
Per488e75f2015-11-19 10:43:36 +010032import java.util.concurrent.TimeUnit;
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010033import javax.annotation.Nullable;
Magnus Jedvertb9ac1212018-04-23 11:29:05 +020034import org.webrtc.EglBase;
35import org.webrtc.VideoFrame;
magjed8c425aa2015-10-22 16:52:39 -070036
Magnus Jedvert9060eb12017-12-12 12:52:54 +010037// Java-side of peerconnection.cc:MediaCodecVideoDecoder.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000038// This class is an implementation detail of the Java PeerConnection API.
Patrik Höglund68876f92015-11-12 17:36:48 +010039@SuppressWarnings("deprecation")
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +020040@Deprecated
Alex Glaznev908e77b2015-04-22 09:25:34 -070041public class MediaCodecVideoDecoder {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +000042 // This class is constructed, operated, and destroyed by its C++ incarnation,
43 // so the class and its methods have non-public visibility. The API this
44 // class exposes aims to mimic the webrtc::VideoDecoder API as closely as
45 // possibly to minimize the amount of translation work necessary.
46
47 private static final String TAG = "MediaCodecVideoDecoder";
Magnus Jedverte26ff4b2018-07-13 16:09:20 +020048
49 /**
50 * Create a VideoDecoderFactory that can be injected in the PeerConnectionFactory and replicate
51 * the old behavior.
52 */
53 public static VideoDecoderFactory createFactory() {
54 return new DefaultVideoDecoderFactory(new HwDecoderFactory());
55 }
56
57 // Factory for creating HW MediaCodecVideoDecoder instances.
58 static class HwDecoderFactory implements VideoDecoderFactory {
59 private static boolean isSameCodec(VideoCodecInfo codecA, VideoCodecInfo codecB) {
60 if (!codecA.name.equalsIgnoreCase(codecB.name)) {
61 return false;
62 }
63 return codecA.name.equalsIgnoreCase("H264")
64 ? H264Utils.isSameH264Profile(codecA.params, codecB.params)
65 : true;
66 }
67
68 private static boolean isCodecSupported(
69 VideoCodecInfo[] supportedCodecs, VideoCodecInfo codec) {
70 for (VideoCodecInfo supportedCodec : supportedCodecs) {
71 if (isSameCodec(supportedCodec, codec)) {
72 return true;
73 }
74 }
75 return false;
76 }
77
78 private static VideoCodecInfo[] getSupportedHardwareCodecs() {
79 final List<VideoCodecInfo> codecs = new ArrayList<VideoCodecInfo>();
80
81 if (isVp8HwSupported()) {
82 Logging.d(TAG, "VP8 HW Decoder supported.");
83 codecs.add(new VideoCodecInfo("VP8", new HashMap<>()));
84 }
85
86 if (isVp9HwSupported()) {
87 Logging.d(TAG, "VP9 HW Decoder supported.");
88 codecs.add(new VideoCodecInfo("VP9", new HashMap<>()));
89 }
90
91 if (isH264HighProfileHwSupported()) {
92 Logging.d(TAG, "H.264 High Profile HW Decoder supported.");
93 codecs.add(H264Utils.DEFAULT_H264_HIGH_PROFILE_CODEC);
94 }
95
96 if (isH264HwSupported()) {
97 Logging.d(TAG, "H.264 HW Decoder supported.");
98 codecs.add(H264Utils.DEFAULT_H264_BASELINE_PROFILE_CODEC);
99 }
100
101 return codecs.toArray(new VideoCodecInfo[codecs.size()]);
102 }
103
104 private final VideoCodecInfo[] supportedHardwareCodecs = getSupportedHardwareCodecs();
105
106 @Override
107 public VideoCodecInfo[] getSupportedCodecs() {
108 return supportedHardwareCodecs;
109 }
110
111 @Nullable
112 @Override
113 public VideoDecoder createDecoder(VideoCodecInfo codec) {
114 if (!isCodecSupported(supportedHardwareCodecs, codec)) {
115 Logging.d(TAG, "No HW video decoder for codec " + codec.name);
116 return null;
117 }
118 Logging.d(TAG, "Create HW video decoder for " + codec.name);
119 return new WrappedNativeVideoDecoder() {
120 @Override
121 public long createNativeVideoDecoder() {
122 return nativeCreateDecoder(codec.name, useSurface());
123 }
124 };
125 }
126 }
127
magjedcedddbd2016-02-26 09:36:04 -0800128 private static final long MAX_DECODE_TIME_MS = 200;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000129
magjed0c29de52017-03-02 00:55:32 -0800130 // TODO(magjed): Use MediaFormat constants when part of the public API.
131 private static final String FORMAT_KEY_STRIDE = "stride";
132 private static final String FORMAT_KEY_SLICE_HEIGHT = "slice-height";
133 private static final String FORMAT_KEY_CROP_LEFT = "crop-left";
134 private static final String FORMAT_KEY_CROP_RIGHT = "crop-right";
135 private static final String FORMAT_KEY_CROP_TOP = "crop-top";
136 private static final String FORMAT_KEY_CROP_BOTTOM = "crop-bottom";
137
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000138 // Tracks webrtc::VideoCodecType.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100139 public enum VideoCodecType {
Niels Möller520ca4e2018-06-04 11:14:38 +0200140 VIDEO_CODEC_UNKNOWN,
Magnus Jedvert655e1962017-12-08 11:05:22 +0100141 VIDEO_CODEC_VP8,
142 VIDEO_CODEC_VP9,
143 VIDEO_CODEC_H264;
144
145 @CalledByNative("VideoCodecType")
146 static VideoCodecType fromNativeIndex(int nativeIndex) {
147 return values()[nativeIndex];
148 }
149 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000150
Alex Glazneveee86a62016-01-29 14:17:07 -0800151 // Timeout for input buffer dequeue.
152 private static final int DEQUEUE_INPUT_TIMEOUT = 500000;
153 // Timeout for codec releasing.
154 private static final int MEDIA_CODEC_RELEASE_TIMEOUT_MS = 5000;
155 // Max number of output buffers queued before starting to drop decoded frames.
156 private static final int MAX_QUEUED_OUTPUTBUFFERS = 3;
Alex Glaznevc6aec4b2015-10-19 16:39:19 -0700157 // Active running decoder instance. Set in initDecode() (called from native code)
158 // and reset to null in release() call.
Sami Kalliomäki3d50a312018-09-11 11:11:47 +0200159 @Nullable private static MediaCodecVideoDecoder runningInstance;
160 @Nullable private static MediaCodecVideoDecoderErrorCallback errorCallback;
161 private static int codecErrors;
Alex Glazneveee86a62016-01-29 14:17:07 -0800162 // List of disabled codec types - can be set from application.
163 private static Set<String> hwDecoderDisabledTypes = new HashSet<String>();
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +0200164 @Nullable private static EglBase eglBase;
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700165
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100166 @Nullable private Thread mediaCodecThread;
167 @Nullable private MediaCodec mediaCodec;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000168 private ByteBuffer[] inputBuffers;
169 private ByteBuffer[] outputBuffers;
170 private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8";
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800171 private static final String VP9_MIME_TYPE = "video/x-vnd.on2.vp9";
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000172 private static final String H264_MIME_TYPE = "video/avc";
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000173 // List of supported HW VP8 decoders.
Alex Leung5b6891a2018-01-18 10:01:14 -0800174 private static final String[] supportedVp8HwCodecPrefixes() {
175 ArrayList<String> supportedPrefixes = new ArrayList<String>();
176 supportedPrefixes.add("OMX.qcom.");
177 supportedPrefixes.add("OMX.Nvidia.");
178 supportedPrefixes.add("OMX.Exynos.");
179 supportedPrefixes.add("OMX.Intel.");
180 if (PeerConnectionFactory.fieldTrialsFindFullName("WebRTC-MediaTekVP8").equals("Enabled")
181 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
182 supportedPrefixes.add("OMX.MTK.");
183 }
184 return supportedPrefixes.toArray(new String[supportedPrefixes.size()]);
185 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800186 // List of supported HW VP9 decoders.
sakalb6760f92016-09-29 04:12:44 -0700187 private static final String[] supportedVp9HwCodecPrefixes = {"OMX.qcom.", "OMX.Exynos."};
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000188 // List of supported HW H.264 decoders.
Alex Leung5b6891a2018-01-18 10:01:14 -0800189 private static final String[] supportedH264HwCodecPrefixes() {
190 ArrayList<String> supportedPrefixes = new ArrayList<String>();
191 supportedPrefixes.add("OMX.qcom.");
192 supportedPrefixes.add("OMX.Intel.");
193 supportedPrefixes.add("OMX.Exynos.");
194 if (PeerConnectionFactory.fieldTrialsFindFullName("WebRTC-MediaTekH264").equals("Enabled")
Alex Leung28e71072018-02-05 13:42:48 -0800195 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
Alex Leung5b6891a2018-01-18 10:01:14 -0800196 supportedPrefixes.add("OMX.MTK.");
197 }
198 return supportedPrefixes.toArray(new String[supportedPrefixes.size()]);
199 }
200
glaznev0c1d0602017-01-27 12:24:24 -0800201 // List of supported HW H.264 high profile decoders.
glaznevcca0f6c2017-06-14 10:20:54 -0700202 private static final String supportedQcomH264HighProfileHwCodecPrefix = "OMX.qcom.";
203 private static final String supportedExynosH264HighProfileHwCodecPrefix = "OMX.Exynos.";
Alex Leung5b6891a2018-01-18 10:01:14 -0800204 private static final String supportedMediaTekH264HighProfileHwCodecPrefix = "OMX.MTK.";
glaznev893a7ee2016-09-22 10:44:30 -0700205
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000206 // NV12 color format supported by QCOM codec, but not declared in MediaCodec -
207 // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
glaznev893a7ee2016-09-22 10:44:30 -0700208 private static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar32m4ka = 0x7FA30C01;
209 private static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar16m4ka = 0x7FA30C02;
210 private static final int COLOR_QCOM_FORMATYVU420PackedSemiPlanar64x32Tile2m8ka = 0x7FA30C03;
211 private static final int COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000212 // Allowable color formats supported by codec - in order of preference.
Magnus Jedvert7e319372015-10-02 15:49:38 +0200213 private static final List<Integer> supportedColorList = Arrays.asList(
sakalb6760f92016-09-29 04:12:44 -0700214 CodecCapabilities.COLOR_FormatYUV420Planar, CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
215 CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
216 COLOR_QCOM_FORMATYVU420PackedSemiPlanar32m4ka, COLOR_QCOM_FORMATYVU420PackedSemiPlanar16m4ka,
217 COLOR_QCOM_FORMATYVU420PackedSemiPlanar64x32Tile2m8ka,
218 COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m);
glaznev893a7ee2016-09-22 10:44:30 -0700219
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000220 private int colorFormat;
221 private int width;
222 private int height;
223 private int stride;
224 private int sliceHeight;
Per488e75f2015-11-19 10:43:36 +0100225 private boolean hasDecodedFirstFrame;
Magnus Jedvert6062f372017-11-16 16:53:12 +0100226 private final Queue<TimeStamps> decodeStartTimeMs = new ArrayDeque<TimeStamps>();
Perc01c2542015-11-13 16:58:26 +0100227
Per488e75f2015-11-19 10:43:36 +0100228 // The below variables are only used when decoding to a Surface.
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100229 @Nullable private TextureListener textureListener;
Per488e75f2015-11-19 10:43:36 +0100230 private int droppedFrames;
Sami Kalliomäki3d50a312018-09-11 11:11:47 +0200231 @Nullable private Surface surface;
sakalb6760f92016-09-29 04:12:44 -0700232 private final Queue<DecodedOutputBuffer> dequeuedSurfaceOutputBuffers =
Magnus Jedvert6062f372017-11-16 16:53:12 +0100233 new ArrayDeque<DecodedOutputBuffer>();
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000234
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700235 // MediaCodec error handler - invoked when critical error happens which may prevent
236 // further use of media codec API. Now it means that one of media codec instances
237 // is hanging and can no longer be used in the next call.
238 public static interface MediaCodecVideoDecoderErrorCallback {
239 void onMediaCodecVideoDecoderCriticalError(int codecErrors);
240 }
241
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +0200242 /** Set EGL context used by HW decoding. The EGL context must be shared with the remote render. */
243 public static void setEglContext(EglBase.Context eglContext) {
244 if (eglBase != null) {
245 Logging.w(TAG, "Egl context already set.");
246 eglBase.release();
247 }
248 eglBase = EglBase.create(eglContext);
249 }
250
251 /** Dispose the EGL context used by HW decoding. */
252 public static void disposeEglContext() {
253 if (eglBase != null) {
254 eglBase.release();
255 eglBase = null;
256 }
257 }
258
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +0200259 static boolean useSurface() {
260 return eglBase != null;
261 }
262
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700263 public static void setErrorCallback(MediaCodecVideoDecoderErrorCallback errorCallback) {
264 Logging.d(TAG, "Set error callback");
265 MediaCodecVideoDecoder.errorCallback = errorCallback;
266 }
267
Alex Glazneveee86a62016-01-29 14:17:07 -0800268 // Functions to disable HW decoding - can be called from applications for platforms
269 // which have known HW decoding problems.
270 public static void disableVp8HwCodec() {
271 Logging.w(TAG, "VP8 decoding is disabled by application.");
272 hwDecoderDisabledTypes.add(VP8_MIME_TYPE);
273 }
274
275 public static void disableVp9HwCodec() {
276 Logging.w(TAG, "VP9 decoding is disabled by application.");
277 hwDecoderDisabledTypes.add(VP9_MIME_TYPE);
278 }
279
280 public static void disableH264HwCodec() {
281 Logging.w(TAG, "H.264 decoding is disabled by application.");
282 hwDecoderDisabledTypes.add(H264_MIME_TYPE);
283 }
284
285 // Functions to query if HW decoding is supported.
286 public static boolean isVp8HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700287 return !hwDecoderDisabledTypes.contains(VP8_MIME_TYPE)
Alex Leung5b6891a2018-01-18 10:01:14 -0800288 && (findDecoder(VP8_MIME_TYPE, supportedVp8HwCodecPrefixes()) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800289 }
290
291 public static boolean isVp9HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700292 return !hwDecoderDisabledTypes.contains(VP9_MIME_TYPE)
293 && (findDecoder(VP9_MIME_TYPE, supportedVp9HwCodecPrefixes) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800294 }
295
296 public static boolean isH264HwSupported() {
sakalb6760f92016-09-29 04:12:44 -0700297 return !hwDecoderDisabledTypes.contains(H264_MIME_TYPE)
Alex Leung5b6891a2018-01-18 10:01:14 -0800298 && (findDecoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes()) != null);
Alex Glazneveee86a62016-01-29 14:17:07 -0800299 }
300
glaznev0c1d0602017-01-27 12:24:24 -0800301 public static boolean isH264HighProfileHwSupported() {
glaznevcca0f6c2017-06-14 10:20:54 -0700302 if (hwDecoderDisabledTypes.contains(H264_MIME_TYPE)) {
303 return false;
304 }
305 // Support H.264 HP decoding on QCOM chips for Android L and above.
306 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
307 && findDecoder(H264_MIME_TYPE, new String[] {supportedQcomH264HighProfileHwCodecPrefix})
308 != null) {
309 return true;
310 }
311 // Support H.264 HP decoding on Exynos chips for Android M and above.
312 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
313 && findDecoder(H264_MIME_TYPE, new String[] {supportedExynosH264HighProfileHwCodecPrefix})
314 != null) {
315 return true;
316 }
Alex Leung28e71072018-02-05 13:42:48 -0800317 // Support H.264 HP decoding on MediaTek chips for Android O_MR1 and above
Alex Leung5b6891a2018-01-18 10:01:14 -0800318 if (PeerConnectionFactory.fieldTrialsFindFullName("WebRTC-MediaTekH264").equals("Enabled")
Alex Leung28e71072018-02-05 13:42:48 -0800319 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1
Alex Leung5b6891a2018-01-18 10:01:14 -0800320 && findDecoder(H264_MIME_TYPE, new String[] {supportedMediaTekH264HighProfileHwCodecPrefix})
321 != null) {
322 return true;
323 }
glaznevcca0f6c2017-06-14 10:20:54 -0700324 return false;
glaznev0c1d0602017-01-27 12:24:24 -0800325 }
326
Alex Glazneveee86a62016-01-29 14:17:07 -0800327 public static void printStackTrace() {
328 if (runningInstance != null && runningInstance.mediaCodecThread != null) {
329 StackTraceElement[] mediaCodecStackTraces = runningInstance.mediaCodecThread.getStackTrace();
330 if (mediaCodecStackTraces.length > 0) {
331 Logging.d(TAG, "MediaCodecVideoDecoder stacks trace:");
332 for (StackTraceElement stackTrace : mediaCodecStackTraces) {
333 Logging.d(TAG, stackTrace.toString());
334 }
335 }
336 }
337 }
338
339 // Helper struct for findDecoder() below.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000340 private static class DecoderProperties {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000341 public DecoderProperties(String codecName, int colorFormat) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000342 this.codecName = codecName;
343 this.colorFormat = colorFormat;
344 }
345 public final String codecName; // OpenMax component name for VP8 codec.
sakalb6760f92016-09-29 04:12:44 -0700346 public final int colorFormat; // Color format supported by codec.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000347 }
348
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100349 private static @Nullable DecoderProperties findDecoder(
350 String mime, String[] supportedCodecPrefixes) {
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000351 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000352 return null; // MediaCodec.setParameters is missing.
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000353 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800354 Logging.d(TAG, "Trying to find HW decoder for mime " + mime);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000355 for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
Alex Glaznev0060c562016-08-08 12:27:24 -0700356 MediaCodecInfo info = null;
357 try {
358 info = MediaCodecList.getCodecInfoAt(i);
359 } catch (IllegalArgumentException e) {
sakalb6760f92016-09-29 04:12:44 -0700360 Logging.e(TAG, "Cannot retrieve decoder codec info", e);
Alex Glaznev0060c562016-08-08 12:27:24 -0700361 }
362 if (info == null || info.isEncoder()) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000363 continue;
364 }
365 String name = null;
366 for (String mimeType : info.getSupportedTypes()) {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000367 if (mimeType.equals(mime)) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000368 name = info.getName();
369 break;
370 }
371 }
372 if (name == null) {
sakalb6760f92016-09-29 04:12:44 -0700373 continue; // No HW support in this codec; try the next one.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000374 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800375 Logging.d(TAG, "Found candidate decoder " + name);
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000376
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000377 // Check if this is supported decoder.
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000378 boolean supportedCodec = false;
glaznev@webrtc.org25cc7452014-10-02 16:58:05 +0000379 for (String codecPrefix : supportedCodecPrefixes) {
380 if (name.startsWith(codecPrefix)) {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000381 supportedCodec = true;
382 break;
383 }
384 }
385 if (!supportedCodec) {
386 continue;
387 }
388
389 // Check if codec supports either yuv420 or nv12.
Alex Glaznev0060c562016-08-08 12:27:24 -0700390 CodecCapabilities capabilities;
391 try {
392 capabilities = info.getCapabilitiesForType(mime);
393 } catch (IllegalArgumentException e) {
sakalb6760f92016-09-29 04:12:44 -0700394 Logging.e(TAG, "Cannot retrieve decoder capabilities", e);
Alex Glaznev0060c562016-08-08 12:27:24 -0700395 continue;
396 }
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000397 for (int colorFormat : capabilities.colorFormats) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700398 Logging.v(TAG, " Color: 0x" + Integer.toHexString(colorFormat));
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000399 }
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000400 for (int supportedColorFormat : supportedColorList) {
401 for (int codecColorFormat : capabilities.colorFormats) {
402 if (codecColorFormat == supportedColorFormat) {
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000403 // Found supported HW decoder.
sakalb6760f92016-09-29 04:12:44 -0700404 Logging.d(TAG, "Found target decoder " + name + ". Color: 0x"
405 + Integer.toHexString(codecColorFormat));
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000406 return new DecoderProperties(name, codecColorFormat);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000407 }
408 }
409 }
410 }
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800411 Logging.d(TAG, "No HW decoder found for mime " + mime);
sakalb6760f92016-09-29 04:12:44 -0700412 return null; // No HW decoder.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000413 }
414
Magnus Jedvert655e1962017-12-08 11:05:22 +0100415 @CalledByNative
416 MediaCodecVideoDecoder() {}
417
Magnus Jedvert7e319372015-10-02 15:49:38 +0200418 private void checkOnMediaCodecThread() throws IllegalStateException {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000419 if (mediaCodecThread.getId() != Thread.currentThread().getId()) {
sakalb6760f92016-09-29 04:12:44 -0700420 throw new IllegalStateException("MediaCodecVideoDecoder previously operated on "
421 + mediaCodecThread + " but is now called on " + Thread.currentThread());
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000422 }
423 }
424
Magnus Jedvert655e1962017-12-08 11:05:22 +0100425 @CalledByNativeUnchecked
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +0200426 private boolean initDecode(VideoCodecType type, int width, int height) {
Alejandro Luebs69ddaef2015-10-09 15:46:09 -0700427 if (mediaCodecThread != null) {
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800428 throw new RuntimeException("initDecode: Forgot to release()?");
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000429 }
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800430
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000431 String mime = null;
432 String[] supportedCodecPrefixes = null;
433 if (type == VideoCodecType.VIDEO_CODEC_VP8) {
434 mime = VP8_MIME_TYPE;
Alex Leung5b6891a2018-01-18 10:01:14 -0800435 supportedCodecPrefixes = supportedVp8HwCodecPrefixes();
Alex Glaznev69a7fd52015-11-10 10:25:40 -0800436 } else if (type == VideoCodecType.VIDEO_CODEC_VP9) {
437 mime = VP9_MIME_TYPE;
438 supportedCodecPrefixes = supportedVp9HwCodecPrefixes;
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000439 } else if (type == VideoCodecType.VIDEO_CODEC_H264) {
440 mime = H264_MIME_TYPE;
Alex Leung5b6891a2018-01-18 10:01:14 -0800441 supportedCodecPrefixes = supportedH264HwCodecPrefixes();
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000442 } else {
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800443 throw new RuntimeException("initDecode: Non-supported codec " + type);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000444 }
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000445 DecoderProperties properties = findDecoder(mime, supportedCodecPrefixes);
446 if (properties == null) {
447 throw new RuntimeException("Cannot find HW decoder for " + type);
448 }
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800449
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +0200450 Logging.d(TAG,
451 "Java initDecode: " + type + " : " + width + " x " + height + ". Color: 0x"
452 + Integer.toHexString(properties.colorFormat) + ". Use Surface: " + useSurface());
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800453
Alex Glaznevc6aec4b2015-10-19 16:39:19 -0700454 runningInstance = this; // Decoder is now running and can be queried for stack traces.
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000455 mediaCodecThread = Thread.currentThread();
456 try {
457 this.width = width;
458 this.height = height;
459 stride = width;
460 sliceHeight = height;
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000461
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +0200462 if (useSurface()) {
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200463 @Nullable
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +0200464 final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create(
465 "Decoder SurfaceTextureHelper", eglBase.getEglBaseContext());
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200466 if (surfaceTextureHelper != null) {
467 textureListener = new TextureListener(surfaceTextureHelper);
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200468 textureListener.setSize(width, height);
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200469 surface = new Surface(surfaceTextureHelper.getSurfaceTexture());
470 }
Magnus Jedvert80cf97c2015-06-11 10:08:59 +0200471 }
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000472
glaznev@webrtc.orgb28474c2015-02-23 17:44:27 +0000473 MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +0200474 if (!useSurface()) {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000475 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
476 }
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700477 Logging.d(TAG, " Format: " + format);
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800478 mediaCodec = MediaCodecVideoEncoder.createByCodecName(properties.codecName);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000479 if (mediaCodec == null) {
Alex Glaznev325d4142015-10-12 14:56:02 -0700480 Logging.e(TAG, "Can not create media decoder");
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000481 return false;
482 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200483 mediaCodec.configure(format, surface, null, 0);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000484 mediaCodec.start();
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800485
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000486 colorFormat = properties.colorFormat;
487 outputBuffers = mediaCodec.getOutputBuffers();
488 inputBuffers = mediaCodec.getInputBuffers();
Per488e75f2015-11-19 10:43:36 +0100489 decodeStartTimeMs.clear();
490 hasDecodedFirstFrame = false;
491 dequeuedSurfaceOutputBuffers.clear();
492 droppedFrames = 0;
sakalb6760f92016-09-29 04:12:44 -0700493 Logging.d(TAG,
494 "Input buffers: " + inputBuffers.length + ". Output buffers: " + outputBuffers.length);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000495 return true;
496 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700497 Logging.e(TAG, "initDecode failed", e);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000498 return false;
499 }
500 }
501
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800502 // Resets the decoder so it can start decoding frames with new resolution.
503 // Flushes MediaCodec and clears decoder output buffers.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100504 @CalledByNativeUnchecked
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800505 private void reset(int width, int height) {
506 if (mediaCodecThread == null || mediaCodec == null) {
507 throw new RuntimeException("Incorrect reset call for non-initialized decoder.");
508 }
509 Logging.d(TAG, "Java reset: " + width + " x " + height);
510
511 mediaCodec.flush();
512
513 this.width = width;
514 this.height = height;
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200515 if (textureListener != null) {
516 textureListener.setSize(width, height);
517 }
Alex Glaznev6a4a03c2016-03-04 14:10:50 -0800518 decodeStartTimeMs.clear();
519 dequeuedSurfaceOutputBuffers.clear();
520 hasDecodedFirstFrame = false;
521 droppedFrames = 0;
522 }
523
Magnus Jedvert655e1962017-12-08 11:05:22 +0100524 @CalledByNativeUnchecked
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000525 private void release() {
Per488e75f2015-11-19 10:43:36 +0100526 Logging.d(TAG, "Java releaseDecoder. Total number of dropped frames: " + droppedFrames);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000527 checkOnMediaCodecThread();
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700528
529 // Run Mediacodec stop() and release() on separate thread since sometime
530 // Mediacodec.stop() may hang.
531 final CountDownLatch releaseDone = new CountDownLatch(1);
532
533 Runnable runMediaCodecRelease = new Runnable() {
534 @Override
535 public void run() {
536 try {
537 Logging.d(TAG, "Java releaseDecoder on release thread");
538 mediaCodec.stop();
539 mediaCodec.release();
540 Logging.d(TAG, "Java releaseDecoder on release thread done");
541 } catch (Exception e) {
542 Logging.e(TAG, "Media decoder release failed", e);
543 }
544 releaseDone.countDown();
545 }
546 };
547 new Thread(runMediaCodecRelease).start();
548
549 if (!ThreadUtils.awaitUninterruptibly(releaseDone, MEDIA_CODEC_RELEASE_TIMEOUT_MS)) {
550 Logging.e(TAG, "Media decoder release timeout");
551 codecErrors++;
552 if (errorCallback != null) {
553 Logging.e(TAG, "Invoke codec error callback. Errors: " + codecErrors);
554 errorCallback.onMediaCodecVideoDecoderCriticalError(codecErrors);
555 }
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000556 }
Alex Glaznev5c3da4b2015-10-30 15:31:07 -0700557
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000558 mediaCodec = null;
559 mediaCodecThread = null;
Alex Glaznevc6aec4b2015-10-19 16:39:19 -0700560 runningInstance = null;
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +0200561 if (useSurface()) {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000562 surface.release();
Magnus Jedvert7e319372015-10-02 15:49:38 +0200563 surface = null;
Per488e75f2015-11-19 10:43:36 +0100564 textureListener.release();
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000565 }
Alex Glaznev325d4142015-10-12 14:56:02 -0700566 Logging.d(TAG, "Java releaseDecoder done");
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000567 }
568
569 // Dequeue an input buffer and return its index, -1 if no input buffer is
570 // available, or -2 if the codec is no longer operative.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100571 @CalledByNativeUnchecked
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000572 private int dequeueInputBuffer() {
573 checkOnMediaCodecThread();
574 try {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000575 return mediaCodec.dequeueInputBuffer(DEQUEUE_INPUT_TIMEOUT);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000576 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700577 Logging.e(TAG, "dequeueIntputBuffer failed", e);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000578 return -2;
579 }
580 }
581
Magnus Jedvert655e1962017-12-08 11:05:22 +0100582 @CalledByNativeUnchecked
Per488e75f2015-11-19 10:43:36 +0100583 private boolean queueInputBuffer(int inputBufferIndex, int size, long presentationTimeStamUs,
584 long timeStampMs, long ntpTimeStamp) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000585 checkOnMediaCodecThread();
586 try {
587 inputBuffers[inputBufferIndex].position(0);
588 inputBuffers[inputBufferIndex].limit(size);
sakalb6760f92016-09-29 04:12:44 -0700589 decodeStartTimeMs.add(
590 new TimeStamps(SystemClock.elapsedRealtime(), timeStampMs, ntpTimeStamp));
Per488e75f2015-11-19 10:43:36 +0100591 mediaCodec.queueInputBuffer(inputBufferIndex, 0, size, presentationTimeStamUs, 0);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000592 return true;
sakalb6760f92016-09-29 04:12:44 -0700593 } catch (IllegalStateException e) {
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700594 Logging.e(TAG, "decode failed", e);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000595 return false;
596 }
597 }
598
Per488e75f2015-11-19 10:43:36 +0100599 private static class TimeStamps {
600 public TimeStamps(long decodeStartTimeMs, long timeStampMs, long ntpTimeStampMs) {
601 this.decodeStartTimeMs = decodeStartTimeMs;
602 this.timeStampMs = timeStampMs;
603 this.ntpTimeStampMs = ntpTimeStampMs;
604 }
Alex Glazneveee86a62016-01-29 14:17:07 -0800605 // Time when this frame was queued for decoding.
606 private final long decodeStartTimeMs;
607 // Only used for bookkeeping in Java. Stores C++ inputImage._timeStamp value for input frame.
608 private final long timeStampMs;
609 // Only used for bookkeeping in Java. Stores C++ inputImage.ntp_time_ms_ value for input frame.
610 private final long ntpTimeStampMs;
Per488e75f2015-11-19 10:43:36 +0100611 }
612
613 // Helper struct for dequeueOutputBuffer() below.
614 private static class DecodedOutputBuffer {
glaznev94291482016-02-01 13:17:18 -0800615 public DecodedOutputBuffer(int index, int offset, int size, long presentationTimeStampMs,
616 long timeStampMs, long ntpTimeStampMs, long decodeTime, long endDecodeTime) {
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000617 this.index = index;
618 this.offset = offset;
619 this.size = size;
glaznev94291482016-02-01 13:17:18 -0800620 this.presentationTimeStampMs = presentationTimeStampMs;
Per488e75f2015-11-19 10:43:36 +0100621 this.timeStampMs = timeStampMs;
622 this.ntpTimeStampMs = ntpTimeStampMs;
623 this.decodeTimeMs = decodeTime;
624 this.endDecodeTimeMs = endDecodeTime;
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000625 }
626
627 private final int index;
628 private final int offset;
629 private final int size;
glaznev94291482016-02-01 13:17:18 -0800630 // Presentation timestamp returned in dequeueOutputBuffer call.
631 private final long presentationTimeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800632 // C++ inputImage._timeStamp value for output frame.
Per488e75f2015-11-19 10:43:36 +0100633 private final long timeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800634 // C++ inputImage.ntp_time_ms_ value for output frame.
Per488e75f2015-11-19 10:43:36 +0100635 private final long ntpTimeStampMs;
636 // Number of ms it took to decode this frame.
637 private final long decodeTimeMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800638 // System time when this frame decoding finished.
Per488e75f2015-11-19 10:43:36 +0100639 private final long endDecodeTimeMs;
Magnus Jedvert655e1962017-12-08 11:05:22 +0100640
641 @CalledByNative("DecodedOutputBuffer")
642 int getIndex() {
643 return index;
644 }
645
646 @CalledByNative("DecodedOutputBuffer")
647 int getOffset() {
648 return offset;
649 }
650
651 @CalledByNative("DecodedOutputBuffer")
652 int getSize() {
653 return size;
654 }
655
656 @CalledByNative("DecodedOutputBuffer")
657 long getPresentationTimestampMs() {
658 return presentationTimeStampMs;
659 }
660
661 @CalledByNative("DecodedOutputBuffer")
662 long getTimestampMs() {
663 return timeStampMs;
664 }
665
666 @CalledByNative("DecodedOutputBuffer")
667 long getNtpTimestampMs() {
668 return ntpTimeStampMs;
669 }
670
671 @CalledByNative("DecodedOutputBuffer")
672 long getDecodeTimeMs() {
673 return decodeTimeMs;
674 }
glaznev@webrtc.org99678452014-09-15 17:52:42 +0000675 }
676
Per488e75f2015-11-19 10:43:36 +0100677 // Helper struct for dequeueTextureBuffer() below.
magjed44bf6f52015-10-03 02:08:00 -0700678 private static class DecodedTextureBuffer {
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200679 private final VideoFrame.Buffer videoFrameBuffer;
glaznev94291482016-02-01 13:17:18 -0800680 // Presentation timestamp returned in dequeueOutputBuffer call.
681 private final long presentationTimeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800682 // C++ inputImage._timeStamp value for output frame.
Per488e75f2015-11-19 10:43:36 +0100683 private final long timeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800684 // C++ inputImage.ntp_time_ms_ value for output frame.
Per488e75f2015-11-19 10:43:36 +0100685 private final long ntpTimeStampMs;
Alex Glazneveee86a62016-01-29 14:17:07 -0800686 // Number of ms it took to decode this frame.
Per488e75f2015-11-19 10:43:36 +0100687 private final long decodeTimeMs;
688 // Interval from when the frame finished decoding until this buffer has been created.
689 // Since there is only one texture, this interval depend on the time from when
690 // a frame is decoded and provided to C++ and until that frame is returned to the MediaCodec
691 // so that the texture can be updated with the next decoded frame.
692 private final long frameDelayMs;
magjed44bf6f52015-10-03 02:08:00 -0700693
Per488e75f2015-11-19 10:43:36 +0100694 // A DecodedTextureBuffer with zero |textureID| has special meaning and represents a frame
695 // that was dropped.
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200696 public DecodedTextureBuffer(VideoFrame.Buffer videoFrameBuffer, long presentationTimeStampMs,
697 long timeStampMs, long ntpTimeStampMs, long decodeTimeMs, long frameDelay) {
698 this.videoFrameBuffer = videoFrameBuffer;
glaznev94291482016-02-01 13:17:18 -0800699 this.presentationTimeStampMs = presentationTimeStampMs;
Per488e75f2015-11-19 10:43:36 +0100700 this.timeStampMs = timeStampMs;
701 this.ntpTimeStampMs = ntpTimeStampMs;
702 this.decodeTimeMs = decodeTimeMs;
703 this.frameDelayMs = frameDelay;
magjed44bf6f52015-10-03 02:08:00 -0700704 }
Magnus Jedvert655e1962017-12-08 11:05:22 +0100705
706 @CalledByNative("DecodedTextureBuffer")
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200707 VideoFrame.Buffer getVideoFrameBuffer() {
708 return videoFrameBuffer;
Magnus Jedvert655e1962017-12-08 11:05:22 +0100709 }
710
711 @CalledByNative("DecodedTextureBuffer")
712 long getPresentationTimestampMs() {
713 return presentationTimeStampMs;
714 }
715
716 @CalledByNative("DecodedTextureBuffer")
717 long getTimeStampMs() {
718 return timeStampMs;
719 }
720
721 @CalledByNative("DecodedTextureBuffer")
722 long getNtpTimestampMs() {
723 return ntpTimeStampMs;
724 }
725
726 @CalledByNative("DecodedTextureBuffer")
727 long getDecodeTimeMs() {
728 return decodeTimeMs;
729 }
730
731 @CalledByNative("DecodedTextureBuffer")
732 long getFrameDelayMs() {
733 return frameDelayMs;
734 }
magjed44bf6f52015-10-03 02:08:00 -0700735 }
736
Per488e75f2015-11-19 10:43:36 +0100737 // Poll based texture listener.
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200738 private class TextureListener implements VideoSink {
Per488e75f2015-11-19 10:43:36 +0100739 private final SurfaceTextureHelper surfaceTextureHelper;
740 // |newFrameLock| is used to synchronize arrival of new frames with wait()/notifyAll().
741 private final Object newFrameLock = new Object();
742 // |bufferToRender| is non-null when waiting for transition between addBufferToRender() to
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200743 // onFrame().
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100744 @Nullable private DecodedOutputBuffer bufferToRender;
745 @Nullable private DecodedTextureBuffer renderedBuffer;
Per488e75f2015-11-19 10:43:36 +0100746
747 public TextureListener(SurfaceTextureHelper surfaceTextureHelper) {
748 this.surfaceTextureHelper = surfaceTextureHelper;
magjed81e8e372016-03-03 02:11:44 -0800749 surfaceTextureHelper.startListening(this);
Per488e75f2015-11-19 10:43:36 +0100750 }
751
752 public void addBufferToRender(DecodedOutputBuffer buffer) {
753 if (bufferToRender != null) {
sakalb6760f92016-09-29 04:12:44 -0700754 Logging.e(TAG, "Unexpected addBufferToRender() called while waiting for a texture.");
Per488e75f2015-11-19 10:43:36 +0100755 throw new IllegalStateException("Waiting for a texture.");
756 }
757 bufferToRender = buffer;
758 }
759
760 public boolean isWaitingForTexture() {
761 synchronized (newFrameLock) {
762 return bufferToRender != null;
763 }
764 }
765
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200766 public void setSize(int width, int height) {
767 surfaceTextureHelper.setTextureSize(width, height);
768 }
769
Per488e75f2015-11-19 10:43:36 +0100770 // Callback from |surfaceTextureHelper|. May be called on an arbitrary thread.
771 @Override
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200772 public void onFrame(VideoFrame frame) {
Per488e75f2015-11-19 10:43:36 +0100773 synchronized (newFrameLock) {
774 if (renderedBuffer != null) {
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200775 Logging.e(TAG, "Unexpected onFrame() called while already holding a texture.");
Per488e75f2015-11-19 10:43:36 +0100776 throw new IllegalStateException("Already holding a texture.");
777 }
778 // |timestampNs| is always zero on some Android versions.
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200779 final VideoFrame.Buffer buffer = frame.getBuffer();
780 buffer.retain();
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200781 renderedBuffer = new DecodedTextureBuffer(buffer, bufferToRender.presentationTimeStampMs,
782 bufferToRender.timeStampMs, bufferToRender.ntpTimeStampMs, bufferToRender.decodeTimeMs,
Per488e75f2015-11-19 10:43:36 +0100783 SystemClock.elapsedRealtime() - bufferToRender.endDecodeTimeMs);
784 bufferToRender = null;
785 newFrameLock.notifyAll();
786 }
787 }
788
789 // Dequeues and returns a DecodedTextureBuffer if available, or null otherwise.
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100790 @Nullable
Sami Kalliomäki9828beb2017-10-26 16:21:22 +0200791 @SuppressWarnings("WaitNotInLoop")
Per488e75f2015-11-19 10:43:36 +0100792 public DecodedTextureBuffer dequeueTextureBuffer(int timeoutMs) {
793 synchronized (newFrameLock) {
794 if (renderedBuffer == null && timeoutMs > 0 && isWaitingForTexture()) {
795 try {
796 newFrameLock.wait(timeoutMs);
sakalb6760f92016-09-29 04:12:44 -0700797 } catch (InterruptedException e) {
Per488e75f2015-11-19 10:43:36 +0100798 // Restore the interrupted status by reinterrupting the thread.
799 Thread.currentThread().interrupt();
800 }
801 }
802 DecodedTextureBuffer returnedBuffer = renderedBuffer;
803 renderedBuffer = null;
804 return returnedBuffer;
805 }
806 }
807
808 public void release() {
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200809 // SurfaceTextureHelper.stopListening() will block until any onFrame() in progress is done.
810 // Therefore, the call must be outside any synchronized statement that is also used in the
811 // onFrame() above to avoid deadlocks.
nissea44e72c2016-05-27 00:27:59 -0700812 surfaceTextureHelper.stopListening();
Per488e75f2015-11-19 10:43:36 +0100813 synchronized (newFrameLock) {
814 if (renderedBuffer != null) {
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200815 renderedBuffer.getVideoFrameBuffer().release();
Per488e75f2015-11-19 10:43:36 +0100816 renderedBuffer = null;
817 }
818 }
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200819 surfaceTextureHelper.dispose();
Per488e75f2015-11-19 10:43:36 +0100820 }
821 }
822
823 // Returns null if no decoded buffer is available, and otherwise a DecodedByteBuffer.
Magnus Jedvert7e319372015-10-02 15:49:38 +0200824 // Throws IllegalStateException if call is made on the wrong thread, if color format changes to an
825 // unsupported format, or if |mediaCodec| is not in the Executing state. Throws CodecException
826 // upon codec error.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100827 @CalledByNativeUnchecked
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100828 private @Nullable DecodedOutputBuffer dequeueOutputBuffer(int dequeueTimeoutMs) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000829 checkOnMediaCodecThread();
Per488e75f2015-11-19 10:43:36 +0100830 if (decodeStartTimeMs.isEmpty()) {
831 return null;
832 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200833 // Drain the decoder until receiving a decoded buffer or hitting
834 // MediaCodec.INFO_TRY_AGAIN_LATER.
835 final MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
836 while (true) {
sakalb6760f92016-09-29 04:12:44 -0700837 final int result =
838 mediaCodec.dequeueOutputBuffer(info, TimeUnit.MILLISECONDS.toMicros(dequeueTimeoutMs));
Magnus Jedvert7e319372015-10-02 15:49:38 +0200839 switch (result) {
Magnus Jedvert7e319372015-10-02 15:49:38 +0200840 case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000841 outputBuffers = mediaCodec.getOutputBuffers();
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700842 Logging.d(TAG, "Decoder output buffers changed: " + outputBuffers.length);
Per488e75f2015-11-19 10:43:36 +0100843 if (hasDecodedFirstFrame) {
844 throw new RuntimeException("Unexpected output buffer change event.");
845 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200846 break;
847 case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000848 MediaFormat format = mediaCodec.getOutputFormat();
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700849 Logging.d(TAG, "Decoder format changed: " + format.toString());
magjed0c29de52017-03-02 00:55:32 -0800850 final int newWidth;
851 final int newHeight;
852 if (format.containsKey(FORMAT_KEY_CROP_LEFT) && format.containsKey(FORMAT_KEY_CROP_RIGHT)
853 && format.containsKey(FORMAT_KEY_CROP_BOTTOM)
854 && format.containsKey(FORMAT_KEY_CROP_TOP)) {
855 newWidth = 1 + format.getInteger(FORMAT_KEY_CROP_RIGHT)
856 - format.getInteger(FORMAT_KEY_CROP_LEFT);
857 newHeight = 1 + format.getInteger(FORMAT_KEY_CROP_BOTTOM)
858 - format.getInteger(FORMAT_KEY_CROP_TOP);
859 } else {
860 newWidth = format.getInteger(MediaFormat.KEY_WIDTH);
861 newHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
Per488e75f2015-11-19 10:43:36 +0100862 }
magjed0c29de52017-03-02 00:55:32 -0800863 if (hasDecodedFirstFrame && (newWidth != width || newHeight != height)) {
864 throw new RuntimeException("Unexpected size change. Configured " + width + "*" + height
865 + ". New " + newWidth + "*" + newHeight);
866 }
867 width = newWidth;
868 height = newHeight;
Magnus Jedvert80e7a7f2018-07-06 11:15:13 +0200869 if (textureListener != null) {
870 textureListener.setSize(width, height);
871 }
Per488e75f2015-11-19 10:43:36 +0100872
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +0200873 if (!useSurface() && format.containsKey(MediaFormat.KEY_COLOR_FORMAT)) {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000874 colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
Jiayang Liu5975b3c2015-09-16 13:40:53 -0700875 Logging.d(TAG, "Color: 0x" + Integer.toHexString(colorFormat));
Magnus Jedvert7e319372015-10-02 15:49:38 +0200876 if (!supportedColorList.contains(colorFormat)) {
877 throw new IllegalStateException("Non supported color format: " + colorFormat);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000878 }
879 }
magjed0c29de52017-03-02 00:55:32 -0800880 if (format.containsKey(FORMAT_KEY_STRIDE)) {
881 stride = format.getInteger(FORMAT_KEY_STRIDE);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000882 }
magjed0c29de52017-03-02 00:55:32 -0800883 if (format.containsKey(FORMAT_KEY_SLICE_HEIGHT)) {
884 sliceHeight = format.getInteger(FORMAT_KEY_SLICE_HEIGHT);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000885 }
Magnus Jedvert7e319372015-10-02 15:49:38 +0200886 Logging.d(TAG, "Frame stride and slice height: " + stride + " x " + sliceHeight);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000887 stride = Math.max(width, stride);
888 sliceHeight = Math.max(height, sliceHeight);
Magnus Jedvert7e319372015-10-02 15:49:38 +0200889 break;
Per488e75f2015-11-19 10:43:36 +0100890 case MediaCodec.INFO_TRY_AGAIN_LATER:
891 return null;
Magnus Jedvert7e319372015-10-02 15:49:38 +0200892 default:
Per488e75f2015-11-19 10:43:36 +0100893 hasDecodedFirstFrame = true;
894 TimeStamps timeStamps = decodeStartTimeMs.remove();
magjedcedddbd2016-02-26 09:36:04 -0800895 long decodeTimeMs = SystemClock.elapsedRealtime() - timeStamps.decodeStartTimeMs;
896 if (decodeTimeMs > MAX_DECODE_TIME_MS) {
Alex Glaznevd5704842016-06-27 11:51:10 -0700897 Logging.e(TAG, "Very high decode time: " + decodeTimeMs + "ms"
sakalb6760f92016-09-29 04:12:44 -0700898 + ". Q size: " + decodeStartTimeMs.size()
899 + ". Might be caused by resuming H264 decoding after a pause.");
magjedcedddbd2016-02-26 09:36:04 -0800900 decodeTimeMs = MAX_DECODE_TIME_MS;
901 }
sakalb6760f92016-09-29 04:12:44 -0700902 return new DecodedOutputBuffer(result, info.offset, info.size,
903 TimeUnit.MICROSECONDS.toMillis(info.presentationTimeUs), timeStamps.timeStampMs,
904 timeStamps.ntpTimeStampMs, decodeTimeMs, SystemClock.elapsedRealtime());
905 }
perkj9cb89822015-11-11 03:27:01 -0800906 }
perkj9cb89822015-11-11 03:27:01 -0800907 }
908
Per488e75f2015-11-19 10:43:36 +0100909 // Returns null if no decoded buffer is available, and otherwise a DecodedTextureBuffer.
910 // Throws IllegalStateException if call is made on the wrong thread, if color format changes to an
911 // unsupported format, or if |mediaCodec| is not in the Executing state. Throws CodecException
912 // upon codec error. If |dequeueTimeoutMs| > 0, the oldest decoded frame will be dropped if
913 // a frame can't be returned.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100914 @CalledByNativeUnchecked
Sami Kalliomäkie7592d82018-03-22 13:32:44 +0100915 private @Nullable DecodedTextureBuffer dequeueTextureBuffer(int dequeueTimeoutMs) {
Per488e75f2015-11-19 10:43:36 +0100916 checkOnMediaCodecThread();
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +0200917 if (!useSurface()) {
Per488e75f2015-11-19 10:43:36 +0100918 throw new IllegalStateException("dequeueTexture() called for byte buffer decoding.");
919 }
920 DecodedOutputBuffer outputBuffer = dequeueOutputBuffer(dequeueTimeoutMs);
921 if (outputBuffer != null) {
922 dequeuedSurfaceOutputBuffers.add(outputBuffer);
923 }
924
925 MaybeRenderDecodedTextureBuffer();
926 // Check if there is texture ready now by waiting max |dequeueTimeoutMs|.
927 DecodedTextureBuffer renderedBuffer = textureListener.dequeueTextureBuffer(dequeueTimeoutMs);
928 if (renderedBuffer != null) {
929 MaybeRenderDecodedTextureBuffer();
930 return renderedBuffer;
931 }
932
933 if ((dequeuedSurfaceOutputBuffers.size()
sakalb6760f92016-09-29 04:12:44 -0700934 >= Math.min(MAX_QUEUED_OUTPUTBUFFERS, outputBuffers.length)
935 || (dequeueTimeoutMs > 0 && !dequeuedSurfaceOutputBuffers.isEmpty()))) {
Per488e75f2015-11-19 10:43:36 +0100936 ++droppedFrames;
937 // Drop the oldest frame still in dequeuedSurfaceOutputBuffers.
938 // The oldest frame is owned by |textureListener| and can't be dropped since
939 // mediaCodec.releaseOutputBuffer has already been called.
940 final DecodedOutputBuffer droppedFrame = dequeuedSurfaceOutputBuffers.remove();
941 if (dequeueTimeoutMs > 0) {
perkj7baf79f2015-11-24 06:26:38 -0800942 // TODO(perkj): Re-add the below log when VideoRenderGUI has been removed or fixed to
943 // return the one and only texture even if it does not render.
glaznevae95ff32016-02-04 11:47:12 -0800944 Logging.w(TAG, "Draining decoder. Dropping frame with TS: "
sakalb6760f92016-09-29 04:12:44 -0700945 + droppedFrame.presentationTimeStampMs + ". Total number of dropped frames: "
946 + droppedFrames);
Per488e75f2015-11-19 10:43:36 +0100947 } else {
sakalb6760f92016-09-29 04:12:44 -0700948 Logging.w(TAG, "Too many output buffers " + dequeuedSurfaceOutputBuffers.size()
949 + ". Dropping frame with TS: " + droppedFrame.presentationTimeStampMs
950 + ". Total number of dropped frames: " + droppedFrames);
Per488e75f2015-11-19 10:43:36 +0100951 }
952
953 mediaCodec.releaseOutputBuffer(droppedFrame.index, false /* render */);
Magnus Jedvertb9ac1212018-04-23 11:29:05 +0200954 return new DecodedTextureBuffer(null /* videoFrameBuffer */,
955 droppedFrame.presentationTimeStampMs, droppedFrame.timeStampMs,
956 droppedFrame.ntpTimeStampMs, droppedFrame.decodeTimeMs,
Per488e75f2015-11-19 10:43:36 +0100957 SystemClock.elapsedRealtime() - droppedFrame.endDecodeTimeMs);
958 }
959 return null;
960 }
961
962 private void MaybeRenderDecodedTextureBuffer() {
963 if (dequeuedSurfaceOutputBuffers.isEmpty() || textureListener.isWaitingForTexture()) {
964 return;
965 }
966 // Get the first frame in the queue and render to the decoder output surface.
967 final DecodedOutputBuffer buffer = dequeuedSurfaceOutputBuffers.remove();
968 textureListener.addBufferToRender(buffer);
969 mediaCodec.releaseOutputBuffer(buffer.index, true /* render */);
970 }
971
magjed44bf6f52015-10-03 02:08:00 -0700972 // Release a dequeued output byte buffer back to the codec for re-use. Should only be called for
973 // non-surface decoding.
974 // Throws IllegalStateException if the call is made on the wrong thread, if codec is configured
975 // for surface decoding, or if |mediaCodec| is not in the Executing state. Throws
976 // MediaCodec.CodecException upon codec error.
Magnus Jedvert655e1962017-12-08 11:05:22 +0100977 @CalledByNativeUnchecked
Per488e75f2015-11-19 10:43:36 +0100978 private void returnDecodedOutputBuffer(int index)
Magnus Jedvert7e319372015-10-02 15:49:38 +0200979 throws IllegalStateException, MediaCodec.CodecException {
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000980 checkOnMediaCodecThread();
Magnus Jedvert0f0e7a62018-07-11 14:53:21 +0200981 if (useSurface()) {
Per488e75f2015-11-19 10:43:36 +0100982 throw new IllegalStateException("returnDecodedOutputBuffer() called for surface decoding.");
magjed44bf6f52015-10-03 02:08:00 -0700983 }
984 mediaCodec.releaseOutputBuffer(index, false /* render */);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +0000985 }
Magnus Jedvert655e1962017-12-08 11:05:22 +0100986
987 @CalledByNative
988 ByteBuffer[] getInputBuffers() {
989 return inputBuffers;
990 }
991
992 @CalledByNative
993 ByteBuffer[] getOutputBuffers() {
994 return outputBuffers;
995 }
996
997 @CalledByNative
998 int getColorFormat() {
999 return colorFormat;
1000 }
1001
1002 @CalledByNative
1003 int getWidth() {
1004 return width;
1005 }
1006
1007 @CalledByNative
1008 int getHeight() {
1009 return height;
1010 }
1011
1012 @CalledByNative
1013 int getStride() {
1014 return stride;
1015 }
1016
1017 @CalledByNative
1018 int getSliceHeight() {
1019 return sliceHeight;
1020 }
Magnus Jedverte26ff4b2018-07-13 16:09:20 +02001021
1022 private static native long nativeCreateDecoder(String codec, boolean useSurface);
glaznev@webrtc.orgefe4b9a2014-07-22 17:44:53 +00001023}