1 /*
2  * libjingle
3  * Copyright 2014 Google Inc.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  *  1. Redistributions of source code must retain the above copyright notice,
9  *     this list of conditions and the following disclaimer.
10  *  2. Redistributions in binary form must reproduce the above copyright notice,
11  *     this list of conditions and the following disclaimer in the documentation
12  *     and/or other materials provided with the distribution.
13  *  3. The name of the author may not be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 package org.webrtc;
29 
30 import android.graphics.SurfaceTexture;
31 import android.media.MediaCodec;
32 import android.media.MediaCodecInfo;
33 import android.media.MediaCodecInfo.CodecCapabilities;
34 import android.media.MediaCodecList;
35 import android.media.MediaFormat;
36 import android.os.Build;
37 import android.os.SystemClock;
38 import android.view.Surface;
39 
40 import org.webrtc.Logging;
41 
42 import java.nio.ByteBuffer;
43 import java.util.Arrays;
44 import java.util.LinkedList;
45 import java.util.List;
46 import java.util.concurrent.CountDownLatch;
47 import java.util.Queue;
48 import java.util.concurrent.TimeUnit;
49 
50 // Java-side of peerconnection_jni.cc:MediaCodecVideoDecoder.
51 // This class is an implementation detail of the Java PeerConnection API.
52 @SuppressWarnings("deprecation")
53 public class MediaCodecVideoDecoder {
54   // This class is constructed, operated, and destroyed by its C++ incarnation,
55   // so the class and its methods have non-public visibility.  The API this
56   // class exposes aims to mimic the webrtc::VideoDecoder API as closely as
57   // possibly to minimize the amount of translation work necessary.
58 
59   private static final String TAG = "MediaCodecVideoDecoder";
60 
61   // Tracks webrtc::VideoCodecType.
62   public enum VideoCodecType {
63     VIDEO_CODEC_VP8,
64     VIDEO_CODEC_VP9,
65     VIDEO_CODEC_H264
66   }
67 
68   private static final int DEQUEUE_INPUT_TIMEOUT = 500000;  // 500 ms timeout.
69   private static final int MEDIA_CODEC_RELEASE_TIMEOUT_MS = 5000; // Timeout for codec releasing.
70   // Active running decoder instance. Set in initDecode() (called from native code)
71   // and reset to null in release() call.
72   private static MediaCodecVideoDecoder runningInstance = null;
73   private static MediaCodecVideoDecoderErrorCallback errorCallback = null;
74   private static int codecErrors = 0;
75 
76   private Thread mediaCodecThread;
77   private MediaCodec mediaCodec;
78   private ByteBuffer[] inputBuffers;
79   private ByteBuffer[] outputBuffers;
80   private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8";
81   private static final String VP9_MIME_TYPE = "video/x-vnd.on2.vp9";
82   private static final String H264_MIME_TYPE = "video/avc";
83   // List of supported HW VP8 decoders.
84   private static final String[] supportedVp8HwCodecPrefixes =
85     {"OMX.qcom.", "OMX.Nvidia.", "OMX.Exynos.", "OMX.Intel." };
86   // List of supported HW VP9 decoders.
87   private static final String[] supportedVp9HwCodecPrefixes =
88     {"OMX.qcom.", "OMX.Exynos." };
89   // List of supported HW H.264 decoders.
90   private static final String[] supportedH264HwCodecPrefixes =
91     {"OMX.qcom.", "OMX.Intel." };
92   // NV12 color format supported by QCOM codec, but not declared in MediaCodec -
93   // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
94   private static final int
95     COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
96   // Allowable color formats supported by codec - in order of preference.
97   private static final List<Integer> supportedColorList = Arrays.asList(
98     CodecCapabilities.COLOR_FormatYUV420Planar,
99     CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
100     CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
101     COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m);
102   private int colorFormat;
103   private int width;
104   private int height;
105   private int stride;
106   private int sliceHeight;
107   private boolean hasDecodedFirstFrame;
108   private final Queue<TimeStamps> decodeStartTimeMs = new LinkedList<TimeStamps>();
109   private boolean useSurface;
110 
111   // The below variables are only used when decoding to a Surface.
112   private TextureListener textureListener;
113   // Max number of output buffers queued before starting to drop decoded frames.
114   private static final int MAX_QUEUED_OUTPUTBUFFERS = 3;
115   private int droppedFrames;
116   private Surface surface = null;
117   private final Queue<DecodedOutputBuffer>
118       dequeuedSurfaceOutputBuffers = new LinkedList<DecodedOutputBuffer>();
119 
120   // MediaCodec error handler - invoked when critical error happens which may prevent
121   // further use of media codec API. Now it means that one of media codec instances
122   // is hanging and can no longer be used in the next call.
123   public static interface MediaCodecVideoDecoderErrorCallback {
onMediaCodecVideoDecoderCriticalError(int codecErrors)124     void onMediaCodecVideoDecoderCriticalError(int codecErrors);
125   }
126 
setErrorCallback(MediaCodecVideoDecoderErrorCallback errorCallback)127   public static void setErrorCallback(MediaCodecVideoDecoderErrorCallback errorCallback) {
128     Logging.d(TAG, "Set error callback");
129     MediaCodecVideoDecoder.errorCallback = errorCallback;
130   }
131 
132   // Helper struct for findVp8Decoder() below.
133   private static class DecoderProperties {
DecoderProperties(String codecName, int colorFormat)134     public DecoderProperties(String codecName, int colorFormat) {
135       this.codecName = codecName;
136       this.colorFormat = colorFormat;
137     }
138     public final String codecName; // OpenMax component name for VP8 codec.
139     public final int colorFormat;  // Color format supported by codec.
140   }
141 
findDecoder( String mime, String[] supportedCodecPrefixes)142   private static DecoderProperties findDecoder(
143       String mime, String[] supportedCodecPrefixes) {
144     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
145       return null; // MediaCodec.setParameters is missing.
146     }
147     Logging.d(TAG, "Trying to find HW decoder for mime " + mime);
148     for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
149       MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
150       if (info.isEncoder()) {
151         continue;
152       }
153       String name = null;
154       for (String mimeType : info.getSupportedTypes()) {
155         if (mimeType.equals(mime)) {
156           name = info.getName();
157           break;
158         }
159       }
160       if (name == null) {
161         continue;  // No HW support in this codec; try the next one.
162       }
163       Logging.d(TAG, "Found candidate decoder " + name);
164 
165       // Check if this is supported decoder.
166       boolean supportedCodec = false;
167       for (String codecPrefix : supportedCodecPrefixes) {
168         if (name.startsWith(codecPrefix)) {
169           supportedCodec = true;
170           break;
171         }
172       }
173       if (!supportedCodec) {
174         continue;
175       }
176 
177       // Check if codec supports either yuv420 or nv12.
178       CodecCapabilities capabilities =
179           info.getCapabilitiesForType(mime);
180       for (int colorFormat : capabilities.colorFormats) {
181         Logging.v(TAG, "   Color: 0x" + Integer.toHexString(colorFormat));
182       }
183       for (int supportedColorFormat : supportedColorList) {
184         for (int codecColorFormat : capabilities.colorFormats) {
185           if (codecColorFormat == supportedColorFormat) {
186             // Found supported HW decoder.
187             Logging.d(TAG, "Found target decoder " + name +
188                 ". Color: 0x" + Integer.toHexString(codecColorFormat));
189             return new DecoderProperties(name, codecColorFormat);
190           }
191         }
192       }
193     }
194     Logging.d(TAG, "No HW decoder found for mime " + mime);
195     return null;  // No HW decoder.
196   }
197 
isVp8HwSupported()198   public static boolean isVp8HwSupported() {
199     return findDecoder(VP8_MIME_TYPE, supportedVp8HwCodecPrefixes) != null;
200   }
201 
isVp9HwSupported()202   public static boolean isVp9HwSupported() {
203     return findDecoder(VP9_MIME_TYPE, supportedVp9HwCodecPrefixes) != null;
204   }
205 
isH264HwSupported()206   public static boolean isH264HwSupported() {
207     return findDecoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes) != null;
208   }
209 
printStackTrace()210   public static void printStackTrace() {
211     if (runningInstance != null && runningInstance.mediaCodecThread != null) {
212       StackTraceElement[] mediaCodecStackTraces = runningInstance.mediaCodecThread.getStackTrace();
213       if (mediaCodecStackTraces.length > 0) {
214         Logging.d(TAG, "MediaCodecVideoDecoder stacks trace:");
215         for (StackTraceElement stackTrace : mediaCodecStackTraces) {
216           Logging.d(TAG, stackTrace.toString());
217         }
218       }
219     }
220   }
221 
checkOnMediaCodecThread()222   private void checkOnMediaCodecThread() throws IllegalStateException {
223     if (mediaCodecThread.getId() != Thread.currentThread().getId()) {
224       throw new IllegalStateException(
225           "MediaCodecVideoDecoder previously operated on " + mediaCodecThread +
226           " but is now called on " + Thread.currentThread());
227     }
228   }
229 
230   // Pass null in |surfaceTextureHelper| to configure the codec for ByteBuffer output.
initDecode( VideoCodecType type, int width, int height, SurfaceTextureHelper surfaceTextureHelper)231   private boolean initDecode(
232       VideoCodecType type, int width, int height, SurfaceTextureHelper surfaceTextureHelper) {
233     if (mediaCodecThread != null) {
234       throw new RuntimeException("Forgot to release()?");
235     }
236     useSurface = (surfaceTextureHelper != null);
237     String mime = null;
238     String[] supportedCodecPrefixes = null;
239     if (type == VideoCodecType.VIDEO_CODEC_VP8) {
240       mime = VP8_MIME_TYPE;
241       supportedCodecPrefixes = supportedVp8HwCodecPrefixes;
242     } else if (type == VideoCodecType.VIDEO_CODEC_VP9) {
243       mime = VP9_MIME_TYPE;
244       supportedCodecPrefixes = supportedVp9HwCodecPrefixes;
245     } else if (type == VideoCodecType.VIDEO_CODEC_H264) {
246       mime = H264_MIME_TYPE;
247       supportedCodecPrefixes = supportedH264HwCodecPrefixes;
248     } else {
249       throw new RuntimeException("Non supported codec " + type);
250     }
251     DecoderProperties properties = findDecoder(mime, supportedCodecPrefixes);
252     if (properties == null) {
253       throw new RuntimeException("Cannot find HW decoder for " + type);
254     }
255     Logging.d(TAG, "Java initDecode: " + type + " : "+ width + " x " + height +
256         ". Color: 0x" + Integer.toHexString(properties.colorFormat) +
257         ". Use Surface: " + useSurface);
258     runningInstance = this; // Decoder is now running and can be queried for stack traces.
259     mediaCodecThread = Thread.currentThread();
260     try {
261       this.width = width;
262       this.height = height;
263       stride = width;
264       sliceHeight = height;
265 
266       if (useSurface) {
267         textureListener = new TextureListener(surfaceTextureHelper);
268         surface = new Surface(surfaceTextureHelper.getSurfaceTexture());
269       }
270 
271       MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
272       if (!useSurface) {
273         format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
274       }
275       Logging.d(TAG, "  Format: " + format);
276       mediaCodec =
277           MediaCodecVideoEncoder.createByCodecName(properties.codecName);
278       if (mediaCodec == null) {
279         Logging.e(TAG, "Can not create media decoder");
280         return false;
281       }
282       mediaCodec.configure(format, surface, null, 0);
283       mediaCodec.start();
284       colorFormat = properties.colorFormat;
285       outputBuffers = mediaCodec.getOutputBuffers();
286       inputBuffers = mediaCodec.getInputBuffers();
287       decodeStartTimeMs.clear();
288       hasDecodedFirstFrame = false;
289       dequeuedSurfaceOutputBuffers.clear();
290       droppedFrames = 0;
291       Logging.d(TAG, "Input buffers: " + inputBuffers.length +
292           ". Output buffers: " + outputBuffers.length);
293       return true;
294     } catch (IllegalStateException e) {
295       Logging.e(TAG, "initDecode failed", e);
296       return false;
297     }
298   }
299 
release()300   private void release() {
301     Logging.d(TAG, "Java releaseDecoder. Total number of dropped frames: " + droppedFrames);
302     checkOnMediaCodecThread();
303 
304     // Run Mediacodec stop() and release() on separate thread since sometime
305     // Mediacodec.stop() may hang.
306     final CountDownLatch releaseDone = new CountDownLatch(1);
307 
308     Runnable runMediaCodecRelease = new Runnable() {
309       @Override
310       public void run() {
311         try {
312           Logging.d(TAG, "Java releaseDecoder on release thread");
313           mediaCodec.stop();
314           mediaCodec.release();
315           Logging.d(TAG, "Java releaseDecoder on release thread done");
316         } catch (Exception e) {
317           Logging.e(TAG, "Media decoder release failed", e);
318         }
319         releaseDone.countDown();
320       }
321     };
322     new Thread(runMediaCodecRelease).start();
323 
324     if (!ThreadUtils.awaitUninterruptibly(releaseDone, MEDIA_CODEC_RELEASE_TIMEOUT_MS)) {
325       Logging.e(TAG, "Media decoder release timeout");
326       codecErrors++;
327       if (errorCallback != null) {
328         Logging.e(TAG, "Invoke codec error callback. Errors: " + codecErrors);
329         errorCallback.onMediaCodecVideoDecoderCriticalError(codecErrors);
330       }
331     }
332 
333     mediaCodec = null;
334     mediaCodecThread = null;
335     runningInstance = null;
336     if (useSurface) {
337       surface.release();
338       surface = null;
339       textureListener.release();
340     }
341     Logging.d(TAG, "Java releaseDecoder done");
342   }
343 
344   // Dequeue an input buffer and return its index, -1 if no input buffer is
345   // available, or -2 if the codec is no longer operative.
dequeueInputBuffer()346   private int dequeueInputBuffer() {
347     checkOnMediaCodecThread();
348     try {
349       return mediaCodec.dequeueInputBuffer(DEQUEUE_INPUT_TIMEOUT);
350     } catch (IllegalStateException e) {
351       Logging.e(TAG, "dequeueIntputBuffer failed", e);
352       return -2;
353     }
354   }
355 
queueInputBuffer(int inputBufferIndex, int size, long presentationTimeStamUs, long timeStampMs, long ntpTimeStamp)356   private boolean queueInputBuffer(int inputBufferIndex, int size, long presentationTimeStamUs,
357       long timeStampMs, long ntpTimeStamp) {
358     checkOnMediaCodecThread();
359     try {
360       inputBuffers[inputBufferIndex].position(0);
361       inputBuffers[inputBufferIndex].limit(size);
362       decodeStartTimeMs.add(new TimeStamps(SystemClock.elapsedRealtime(), timeStampMs,
363           ntpTimeStamp));
364       mediaCodec.queueInputBuffer(inputBufferIndex, 0, size, presentationTimeStamUs, 0);
365       return true;
366     }
367     catch (IllegalStateException e) {
368       Logging.e(TAG, "decode failed", e);
369       return false;
370     }
371   }
372 
373   private static class TimeStamps {
TimeStamps(long decodeStartTimeMs, long timeStampMs, long ntpTimeStampMs)374     public TimeStamps(long decodeStartTimeMs, long timeStampMs, long ntpTimeStampMs) {
375       this.decodeStartTimeMs = decodeStartTimeMs;
376       this.timeStampMs = timeStampMs;
377       this.ntpTimeStampMs = ntpTimeStampMs;
378     }
379     private final long decodeStartTimeMs; // Time when this frame was queued for decoding.
380     private final long timeStampMs; // Only used for bookkeeping in Java. Used in C++;
381     private final long ntpTimeStampMs; // Only used for bookkeeping in Java. Used in C++;
382   }
383 
384   // Helper struct for dequeueOutputBuffer() below.
385   private static class DecodedOutputBuffer {
DecodedOutputBuffer(int index, int offset, int size, long timeStampMs, long ntpTimeStampMs, long decodeTime, long endDecodeTime)386     public DecodedOutputBuffer(int index, int offset, int size, long timeStampMs,
387         long ntpTimeStampMs, long decodeTime, long endDecodeTime) {
388       this.index = index;
389       this.offset = offset;
390       this.size = size;
391       this.timeStampMs = timeStampMs;
392       this.ntpTimeStampMs = ntpTimeStampMs;
393       this.decodeTimeMs = decodeTime;
394       this.endDecodeTimeMs = endDecodeTime;
395     }
396 
397     private final int index;
398     private final int offset;
399     private final int size;
400     private final long timeStampMs;
401     private final long ntpTimeStampMs;
402     // Number of ms it took to decode this frame.
403     private final long decodeTimeMs;
404     // System time when this frame finished decoding.
405     private final long endDecodeTimeMs;
406   }
407 
408   // Helper struct for dequeueTextureBuffer() below.
409   private static class DecodedTextureBuffer {
410     private final int textureID;
411     private final float[] transformMatrix;
412     private final long timeStampMs;
413     private final long ntpTimeStampMs;
414     private final long decodeTimeMs;
415     // Interval from when the frame finished decoding until this buffer has been created.
416     // Since there is only one texture, this interval depend on the time from when
417     // a frame is decoded and provided to C++ and until that frame is returned to the MediaCodec
418     // so that the texture can be updated with the next decoded frame.
419     private final long frameDelayMs;
420 
421     // A DecodedTextureBuffer with zero |textureID| has special meaning and represents a frame
422     // that was dropped.
DecodedTextureBuffer(int textureID, float[] transformMatrix, long timeStampMs, long ntpTimeStampMs, long decodeTimeMs, long frameDelay)423     public DecodedTextureBuffer(int textureID, float[] transformMatrix, long timeStampMs,
424         long ntpTimeStampMs, long decodeTimeMs, long frameDelay) {
425       this.textureID = textureID;
426       this.transformMatrix = transformMatrix;
427       this.timeStampMs = timeStampMs;
428       this.ntpTimeStampMs = ntpTimeStampMs;
429       this.decodeTimeMs = decodeTimeMs;
430       this.frameDelayMs = frameDelay;
431     }
432   }
433 
434   // Poll based texture listener.
435   private static class TextureListener
436       implements SurfaceTextureHelper.OnTextureFrameAvailableListener {
437     private final SurfaceTextureHelper surfaceTextureHelper;
438     // |newFrameLock| is used to synchronize arrival of new frames with wait()/notifyAll().
439     private final Object newFrameLock = new Object();
440     // |bufferToRender| is non-null when waiting for transition between addBufferToRender() to
441     // onTextureFrameAvailable().
442     private DecodedOutputBuffer bufferToRender;
443     private DecodedTextureBuffer renderedBuffer;
444 
TextureListener(SurfaceTextureHelper surfaceTextureHelper)445     public TextureListener(SurfaceTextureHelper surfaceTextureHelper) {
446       this.surfaceTextureHelper = surfaceTextureHelper;
447       surfaceTextureHelper.setListener(this);
448     }
449 
addBufferToRender(DecodedOutputBuffer buffer)450     public void addBufferToRender(DecodedOutputBuffer buffer) {
451       if (bufferToRender != null) {
452         Logging.e(TAG,
453             "Unexpected addBufferToRender() called while waiting for a texture.");
454         throw new IllegalStateException("Waiting for a texture.");
455       }
456       bufferToRender = buffer;
457     }
458 
isWaitingForTexture()459     public boolean isWaitingForTexture() {
460       synchronized (newFrameLock) {
461         return bufferToRender != null;
462       }
463     }
464 
465     // Callback from |surfaceTextureHelper|. May be called on an arbitrary thread.
466     @Override
onTextureFrameAvailable( int oesTextureId, float[] transformMatrix, long timestampNs)467     public void onTextureFrameAvailable(
468         int oesTextureId, float[] transformMatrix, long timestampNs) {
469       synchronized (newFrameLock) {
470         if (renderedBuffer != null) {
471           Logging.e(TAG,
472               "Unexpected onTextureFrameAvailable() called while already holding a texture.");
473           throw new IllegalStateException("Already holding a texture.");
474         }
475         // |timestampNs| is always zero on some Android versions.
476         renderedBuffer = new DecodedTextureBuffer(oesTextureId, transformMatrix,
477             bufferToRender.timeStampMs, bufferToRender.ntpTimeStampMs, bufferToRender.decodeTimeMs,
478             SystemClock.elapsedRealtime() - bufferToRender.endDecodeTimeMs);
479         bufferToRender = null;
480         newFrameLock.notifyAll();
481       }
482     }
483 
484     // Dequeues and returns a DecodedTextureBuffer if available, or null otherwise.
dequeueTextureBuffer(int timeoutMs)485     public DecodedTextureBuffer dequeueTextureBuffer(int timeoutMs) {
486       synchronized (newFrameLock) {
487         if (renderedBuffer == null && timeoutMs > 0 && isWaitingForTexture()) {
488           try {
489             newFrameLock.wait(timeoutMs);
490           } catch(InterruptedException e) {
491             // Restore the interrupted status by reinterrupting the thread.
492             Thread.currentThread().interrupt();
493           }
494         }
495         DecodedTextureBuffer returnedBuffer = renderedBuffer;
496         renderedBuffer = null;
497         return returnedBuffer;
498       }
499     }
500 
release()501     public void release() {
502       // SurfaceTextureHelper.disconnect() will block until any onTextureFrameAvailable() in
503       // progress is done. Therefore, the call to disconnect() must be outside any synchronized
504       // statement that is also used in the onTextureFrameAvailable() above to avoid deadlocks.
505       surfaceTextureHelper.disconnect();
506       synchronized (newFrameLock) {
507         if (renderedBuffer != null) {
508           surfaceTextureHelper.returnTextureFrame();
509           renderedBuffer = null;
510         }
511       }
512     }
513   }
514 
515   // Returns null if no decoded buffer is available, and otherwise a DecodedByteBuffer.
516   // Throws IllegalStateException if call is made on the wrong thread, if color format changes to an
517   // unsupported format, or if |mediaCodec| is not in the Executing state. Throws CodecException
518   // upon codec error.
dequeueOutputBuffer(int dequeueTimeoutMs)519   private DecodedOutputBuffer dequeueOutputBuffer(int dequeueTimeoutMs) {
520     checkOnMediaCodecThread();
521     if (decodeStartTimeMs.isEmpty()) {
522       return null;
523     }
524     // Drain the decoder until receiving a decoded buffer or hitting
525     // MediaCodec.INFO_TRY_AGAIN_LATER.
526     final MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
527     while (true) {
528       final int result = mediaCodec.dequeueOutputBuffer(
529           info, TimeUnit.MILLISECONDS.toMicros(dequeueTimeoutMs));
530       switch (result) {
531         case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
532           outputBuffers = mediaCodec.getOutputBuffers();
533           Logging.d(TAG, "Decoder output buffers changed: " + outputBuffers.length);
534           if (hasDecodedFirstFrame) {
535             throw new RuntimeException("Unexpected output buffer change event.");
536           }
537           break;
538         case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
539           MediaFormat format = mediaCodec.getOutputFormat();
540           Logging.d(TAG, "Decoder format changed: " + format.toString());
541           int new_width = format.getInteger(MediaFormat.KEY_WIDTH);
542           int new_height = format.getInteger(MediaFormat.KEY_HEIGHT);
543           if (hasDecodedFirstFrame && (new_width != width || new_height != height)) {
544             throw new RuntimeException("Unexpected size change. Configured " + width + "*" +
545                 height + ". New " + new_width + "*" + new_height);
546           }
547           width = format.getInteger(MediaFormat.KEY_WIDTH);
548           height = format.getInteger(MediaFormat.KEY_HEIGHT);
549 
550           if (!useSurface && format.containsKey(MediaFormat.KEY_COLOR_FORMAT)) {
551             colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
552             Logging.d(TAG, "Color: 0x" + Integer.toHexString(colorFormat));
553             if (!supportedColorList.contains(colorFormat)) {
554               throw new IllegalStateException("Non supported color format: " + colorFormat);
555             }
556           }
557           if (format.containsKey("stride")) {
558             stride = format.getInteger("stride");
559           }
560           if (format.containsKey("slice-height")) {
561             sliceHeight = format.getInteger("slice-height");
562           }
563           Logging.d(TAG, "Frame stride and slice height: " + stride + " x " + sliceHeight);
564           stride = Math.max(width, stride);
565           sliceHeight = Math.max(height, sliceHeight);
566           break;
567         case MediaCodec.INFO_TRY_AGAIN_LATER:
568           return null;
569         default:
570           hasDecodedFirstFrame = true;
571           TimeStamps timeStamps = decodeStartTimeMs.remove();
572           return new DecodedOutputBuffer(result, info.offset, info.size, timeStamps.timeStampMs,
573               timeStamps.ntpTimeStampMs,
574               SystemClock.elapsedRealtime() - timeStamps.decodeStartTimeMs,
575               SystemClock.elapsedRealtime());
576         }
577     }
578   }
579 
580   // Returns null if no decoded buffer is available, and otherwise a DecodedTextureBuffer.
581   // Throws IllegalStateException if call is made on the wrong thread, if color format changes to an
582   // unsupported format, or if |mediaCodec| is not in the Executing state. Throws CodecException
583   // upon codec error. If |dequeueTimeoutMs| > 0, the oldest decoded frame will be dropped if
584   // a frame can't be returned.
dequeueTextureBuffer(int dequeueTimeoutMs)585   private DecodedTextureBuffer dequeueTextureBuffer(int dequeueTimeoutMs) {
586     checkOnMediaCodecThread();
587     if (!useSurface) {
588       throw new IllegalStateException("dequeueTexture() called for byte buffer decoding.");
589     }
590     DecodedOutputBuffer outputBuffer = dequeueOutputBuffer(dequeueTimeoutMs);
591     if (outputBuffer != null) {
592       dequeuedSurfaceOutputBuffers.add(outputBuffer);
593     }
594 
595     MaybeRenderDecodedTextureBuffer();
596     // Check if there is texture ready now by waiting max |dequeueTimeoutMs|.
597     DecodedTextureBuffer renderedBuffer = textureListener.dequeueTextureBuffer(dequeueTimeoutMs);
598     if (renderedBuffer != null) {
599       MaybeRenderDecodedTextureBuffer();
600       return renderedBuffer;
601     }
602 
603     if ((dequeuedSurfaceOutputBuffers.size()
604          >= Math.min(MAX_QUEUED_OUTPUTBUFFERS, outputBuffers.length)
605          || (dequeueTimeoutMs > 0 && !dequeuedSurfaceOutputBuffers.isEmpty()))) {
606       ++droppedFrames;
607       // Drop the oldest frame still in dequeuedSurfaceOutputBuffers.
608       // The oldest frame is owned by |textureListener| and can't be dropped since
609       // mediaCodec.releaseOutputBuffer has already been called.
610       final DecodedOutputBuffer droppedFrame = dequeuedSurfaceOutputBuffers.remove();
611       if (dequeueTimeoutMs > 0) {
612         // TODO(perkj): Re-add the below log when VideoRenderGUI has been removed or fixed to
613         // return the one and only texture even if it does not render.
614         // Logging.w(TAG, "Draining decoder. Dropping frame with TS: "
615         //    + droppedFrame.timeStampMs + ". Total number of dropped frames: " + droppedFrames);
616       } else {
617         Logging.w(TAG, "Too many output buffers. Dropping frame with TS: "
618             + droppedFrame.timeStampMs + ". Total number of dropped frames: " + droppedFrames);
619       }
620 
621       mediaCodec.releaseOutputBuffer(droppedFrame.index, false /* render */);
622       return new DecodedTextureBuffer(0, null, droppedFrame.timeStampMs,
623           droppedFrame.ntpTimeStampMs, droppedFrame.decodeTimeMs,
624           SystemClock.elapsedRealtime() - droppedFrame.endDecodeTimeMs);
625     }
626     return null;
627   }
628 
MaybeRenderDecodedTextureBuffer()629   private void MaybeRenderDecodedTextureBuffer() {
630     if (dequeuedSurfaceOutputBuffers.isEmpty() || textureListener.isWaitingForTexture()) {
631       return;
632     }
633     // Get the first frame in the queue and render to the decoder output surface.
634     final DecodedOutputBuffer buffer = dequeuedSurfaceOutputBuffers.remove();
635     textureListener.addBufferToRender(buffer);
636     mediaCodec.releaseOutputBuffer(buffer.index, true /* render */);
637   }
638 
639   // Release a dequeued output byte buffer back to the codec for re-use. Should only be called for
640   // non-surface decoding.
641   // Throws IllegalStateException if the call is made on the wrong thread, if codec is configured
642   // for surface decoding, or if |mediaCodec| is not in the Executing state. Throws
643   // MediaCodec.CodecException upon codec error.
returnDecodedOutputBuffer(int index)644   private void returnDecodedOutputBuffer(int index)
645       throws IllegalStateException, MediaCodec.CodecException {
646     checkOnMediaCodecThread();
647     if (useSurface) {
648       throw new IllegalStateException("returnDecodedOutputBuffer() called for surface decoding.");
649     }
650     mediaCodec.releaseOutputBuffer(index, false /* render */);
651   }
652 }
653