From 7e319372abd4403565aa3d27a2d1bfa069969b19 Mon Sep 17 00:00:00 2001 From: Magnus Jedvert Date: Fri, 2 Oct 2015 15:49:38 +0200 Subject: [PATCH] Android MediaCodecVideoDecoder: Cleanup to prepare for texture liftime management This CL should not change the behaviour of the decoder. The purpose is to prepare for lifetime management of textures received from the SurfaceTexture. The main change is to only use exceptions for error signaling in MediaCodecVideoDecoder.dequeueOutputBuffer() and MediaCodecVideoDecoder.releaseOutputBuffer(), not both exceptions and error return values. BUG=webrtc:4993 R=perkj@webrtc.org Review URL: https://codereview.webrtc.org/1383983003 . Cr-Commit-Position: refs/heads/master@{#10148} --- .../java/jni/androidmediadecoder_jni.cc | 14 ++- .../org/webrtc/MediaCodecVideoDecoder.java | 100 ++++++++---------- 2 files changed, 49 insertions(+), 65 deletions(-) diff --git a/talk/app/webrtc/java/jni/androidmediadecoder_jni.cc b/talk/app/webrtc/java/jni/androidmediadecoder_jni.cc index 4f7445ef43..a5a25f00cf 100644 --- a/talk/app/webrtc/java/jni/androidmediadecoder_jni.cc +++ b/talk/app/webrtc/java/jni/androidmediadecoder_jni.cc @@ -37,6 +37,7 @@ #include "webrtc/base/logging.h" #include "webrtc/base/scoped_ref_ptr.h" #include "webrtc/base/thread.h" +#include "webrtc/base/timeutils.h" #include "webrtc/common_video/interface/i420_buffer_pool.h" #include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h" #include "webrtc/system_wrappers/interface/logcat_trace_context.h" @@ -198,7 +199,7 @@ MediaCodecVideoDecoder::MediaCodecVideoDecoder( jni, *j_media_codec_video_decoder_class_, "dequeueOutputBuffer", "(I)Lorg/webrtc/MediaCodecVideoDecoder$DecoderOutputBufferInfo;"); j_release_output_buffer_method_ = GetMethodID( - jni, *j_media_codec_video_decoder_class_, "releaseOutputBuffer", "(I)Z"); + jni, *j_media_codec_video_decoder_class_, "releaseOutputBuffer", "(I)V"); j_input_buffers_field_ = GetFieldID( jni, *j_media_codec_video_decoder_class_, @@ -572,16 +573,13 @@ bool MediaCodecVideoDecoder::DeliverPendingOutputs( // Extract output buffer info from Java DecoderOutputBufferInfo. int output_buffer_index = GetIntField(jni, j_decoder_output_buffer_info, j_info_index_field_); - if (output_buffer_index < 0) { - ALOGE("dequeueOutputBuffer error : %d", output_buffer_index); - return false; - } + RTC_CHECK_GE(output_buffer_index, 0); int output_buffer_offset = GetIntField(jni, j_decoder_output_buffer_info, j_info_offset_field_); int output_buffer_size = GetIntField(jni, j_decoder_output_buffer_info, j_info_size_field_); long output_timestamps_ms = GetLongField(jni, j_decoder_output_buffer_info, - j_info_presentation_timestamp_us_field_) / 1000; + j_info_presentation_timestamp_us_field_) / rtc::kNumMicrosecsPerMillisec; if (CheckException(jni)) { return false; } @@ -677,11 +675,11 @@ bool MediaCodecVideoDecoder::DeliverPendingOutputs( color_format, output_timestamps_ms, frame_decoding_time_ms); // Return output buffer back to codec. - bool success = jni->CallBooleanMethod( + jni->CallVoidMethod( *j_media_codec_video_decoder_, j_release_output_buffer_method_, output_buffer_index); - if (CheckException(jni) || !success) { + if (CheckException(jni)) { ALOGE("releaseOutputBuffer error"); return false; } diff --git a/talk/app/webrtc/java/src/org/webrtc/MediaCodecVideoDecoder.java b/talk/app/webrtc/java/src/org/webrtc/MediaCodecVideoDecoder.java index 4809cf65b2..7221a36190 100644 --- a/talk/app/webrtc/java/src/org/webrtc/MediaCodecVideoDecoder.java +++ b/talk/app/webrtc/java/src/org/webrtc/MediaCodecVideoDecoder.java @@ -42,6 +42,8 @@ import android.view.Surface; import org.webrtc.Logging; import java.nio.ByteBuffer; +import java.util.List; +import java.util.Arrays; // Java-side of peerconnection_jni.cc:MediaCodecVideoDecoder. // This class is an implementation detail of the Java PeerConnection API. @@ -80,19 +82,18 @@ public class MediaCodecVideoDecoder { private static final int COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04; // Allowable color formats supported by codec - in order of preference. - private static final int[] supportedColorList = { + private static final List supportedColorList = Arrays.asList( CodecCapabilities.COLOR_FormatYUV420Planar, CodecCapabilities.COLOR_FormatYUV420SemiPlanar, CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar, - COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m - }; + COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m); private int colorFormat; private int width; private int height; private int stride; private int sliceHeight; private boolean useSurface; - private int textureID = -1; + private int textureID = 0; private SurfaceTexture surfaceTexture = null; private Surface surface = null; private EglBase eglBase; @@ -171,9 +172,9 @@ public class MediaCodecVideoDecoder { return findDecoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes) != null; } - private void checkOnMediaCodecThread() { + private void checkOnMediaCodecThread() throws IllegalStateException { if (mediaCodecThread.getId() != Thread.currentThread().getId()) { - throw new RuntimeException( + throw new IllegalStateException( "MediaCodecVideoDecoder previously operated on " + mediaCodecThread + " but is now called on " + Thread.currentThread()); } @@ -208,7 +209,6 @@ public class MediaCodecVideoDecoder { } mediaCodecThread = Thread.currentThread(); try { - Surface decodeSurface = null; this.width = width; this.height = height; stride = width; @@ -225,7 +225,6 @@ public class MediaCodecVideoDecoder { Logging.d(TAG, "Video decoder TextureID = " + textureID); surfaceTexture = new SurfaceTexture(textureID); surface = new Surface(surfaceTexture); - decodeSurface = surface; } MediaFormat format = MediaFormat.createVideoFormat(mime, width, height); @@ -238,7 +237,7 @@ public class MediaCodecVideoDecoder { if (mediaCodec == null) { return false; } - mediaCodec.configure(format, decodeSurface, null, 0); + mediaCodec.configure(format, surface, null, 0); mediaCodec.start(); colorFormat = properties.colorFormat; outputBuffers = mediaCodec.getOutputBuffers(); @@ -265,11 +264,10 @@ public class MediaCodecVideoDecoder { mediaCodecThread = null; if (useSurface) { surface.release(); - if (textureID != 0) { - Logging.d(TAG, "Delete video decoder TextureID " + textureID); - GLES20.glDeleteTextures(1, new int[] {textureID}, 0); - textureID = 0; - } + surface = null; + Logging.d(TAG, "Delete video decoder TextureID " + textureID); + GLES20.glDeleteTextures(1, new int[] {textureID}, 0); + textureID = 0; eglBase.release(); eglBase = null; } @@ -318,19 +316,26 @@ public class MediaCodecVideoDecoder { private final long presentationTimestampUs; } - // Dequeue and return an output buffer index, -1 if no output - // buffer available or -2 if error happened. - private DecoderOutputBufferInfo dequeueOutputBuffer(int dequeueTimeoutUs) { + // Dequeue and return a DecoderOutputBufferInfo, or null if no decoded buffer is ready. + // Throws IllegalStateException if call is made on the wrong thread, if color format changes to an + // unsupported format, or if |mediaCodec| is not in the Executing state. Throws CodecException + // upon codec error. + private DecoderOutputBufferInfo dequeueOutputBuffer(int dequeueTimeoutUs) + throws IllegalStateException, MediaCodec.CodecException { checkOnMediaCodecThread(); - try { - MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); - int result = mediaCodec.dequeueOutputBuffer(info, dequeueTimeoutUs); - while (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED || - result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { - if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { + // Drain the decoder until receiving a decoded buffer or hitting + // MediaCodec.INFO_TRY_AGAIN_LATER. + final MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); + while (true) { + final int result = mediaCodec.dequeueOutputBuffer(info, dequeueTimeoutUs); + switch (result) { + case MediaCodec.INFO_TRY_AGAIN_LATER: + return null; + case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: outputBuffers = mediaCodec.getOutputBuffers(); Logging.d(TAG, "Decoder output buffers changed: " + outputBuffers.length); - } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + break; + case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: MediaFormat format = mediaCodec.getOutputFormat(); Logging.d(TAG, "Decoder format changed: " + format.toString()); width = format.getInteger(MediaFormat.KEY_WIDTH); @@ -338,17 +343,8 @@ public class MediaCodecVideoDecoder { if (!useSurface && format.containsKey(MediaFormat.KEY_COLOR_FORMAT)) { colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT); Logging.d(TAG, "Color: 0x" + Integer.toHexString(colorFormat)); - // Check if new color space is supported. - boolean validColorFormat = false; - for (int supportedColorFormat : supportedColorList) { - if (colorFormat == supportedColorFormat) { - validColorFormat = true; - break; - } - } - if (!validColorFormat) { - Logging.e(TAG, "Non supported color format"); - return new DecoderOutputBufferInfo(-1, 0, 0, -1); + if (!supportedColorList.contains(colorFormat)) { + throw new IllegalStateException("Non supported color format: " + colorFormat); } } if (format.containsKey("stride")) { @@ -357,34 +353,24 @@ public class MediaCodecVideoDecoder { if (format.containsKey("slice-height")) { sliceHeight = format.getInteger("slice-height"); } - Logging.d(TAG, "Frame stride and slice height: " - + stride + " x " + sliceHeight); + Logging.d(TAG, "Frame stride and slice height: " + stride + " x " + sliceHeight); stride = Math.max(width, stride); sliceHeight = Math.max(height, sliceHeight); - } - result = mediaCodec.dequeueOutputBuffer(info, dequeueTimeoutUs); + break; + default: + // Output buffer decoded. + return new DecoderOutputBufferInfo( + result, info.offset, info.size, info.presentationTimeUs); } - if (result >= 0) { - return new DecoderOutputBufferInfo(result, info.offset, info.size, - info.presentationTimeUs); - } - return null; - } catch (IllegalStateException e) { - Logging.e(TAG, "dequeueOutputBuffer failed", e); - return new DecoderOutputBufferInfo(-1, 0, 0, -1); } } - // Release a dequeued output buffer back to the codec for re-use. Return - // false if the codec is no longer operable. - private boolean releaseOutputBuffer(int index) { + // Release a dequeued output buffer back to the codec for re-use. + // Throws IllegalStateException if the call is made on the wrong thread or if |mediaCodec| is not + // in the Executing state. Throws MediaCodec.CodecException upon codec error. + private void releaseOutputBuffer(int index) + throws IllegalStateException, MediaCodec.CodecException { checkOnMediaCodecThread(); - try { - mediaCodec.releaseOutputBuffer(index, useSurface); - return true; - } catch (IllegalStateException e) { - Logging.e(TAG, "releaseOutputBuffer failed", e); - return false; - } + mediaCodec.releaseOutputBuffer(index, useSurface); } }