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}
This commit is contained in:
Magnus Jedvert
2015-10-02 15:49:38 +02:00
parent 6781ea49a8
commit 7e319372ab
2 changed files with 49 additions and 65 deletions

View File

@ -37,6 +37,7 @@
#include "webrtc/base/logging.h" #include "webrtc/base/logging.h"
#include "webrtc/base/scoped_ref_ptr.h" #include "webrtc/base/scoped_ref_ptr.h"
#include "webrtc/base/thread.h" #include "webrtc/base/thread.h"
#include "webrtc/base/timeutils.h"
#include "webrtc/common_video/interface/i420_buffer_pool.h" #include "webrtc/common_video/interface/i420_buffer_pool.h"
#include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h" #include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h"
#include "webrtc/system_wrappers/interface/logcat_trace_context.h" #include "webrtc/system_wrappers/interface/logcat_trace_context.h"
@ -198,7 +199,7 @@ MediaCodecVideoDecoder::MediaCodecVideoDecoder(
jni, *j_media_codec_video_decoder_class_, "dequeueOutputBuffer", jni, *j_media_codec_video_decoder_class_, "dequeueOutputBuffer",
"(I)Lorg/webrtc/MediaCodecVideoDecoder$DecoderOutputBufferInfo;"); "(I)Lorg/webrtc/MediaCodecVideoDecoder$DecoderOutputBufferInfo;");
j_release_output_buffer_method_ = GetMethodID( 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( j_input_buffers_field_ = GetFieldID(
jni, *j_media_codec_video_decoder_class_, jni, *j_media_codec_video_decoder_class_,
@ -572,16 +573,13 @@ bool MediaCodecVideoDecoder::DeliverPendingOutputs(
// Extract output buffer info from Java DecoderOutputBufferInfo. // Extract output buffer info from Java DecoderOutputBufferInfo.
int output_buffer_index = int output_buffer_index =
GetIntField(jni, j_decoder_output_buffer_info, j_info_index_field_); GetIntField(jni, j_decoder_output_buffer_info, j_info_index_field_);
if (output_buffer_index < 0) { RTC_CHECK_GE(output_buffer_index, 0);
ALOGE("dequeueOutputBuffer error : %d", output_buffer_index);
return false;
}
int output_buffer_offset = int output_buffer_offset =
GetIntField(jni, j_decoder_output_buffer_info, j_info_offset_field_); GetIntField(jni, j_decoder_output_buffer_info, j_info_offset_field_);
int output_buffer_size = int output_buffer_size =
GetIntField(jni, j_decoder_output_buffer_info, j_info_size_field_); GetIntField(jni, j_decoder_output_buffer_info, j_info_size_field_);
long output_timestamps_ms = GetLongField(jni, j_decoder_output_buffer_info, 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)) { if (CheckException(jni)) {
return false; return false;
} }
@ -677,11 +675,11 @@ bool MediaCodecVideoDecoder::DeliverPendingOutputs(
color_format, output_timestamps_ms, frame_decoding_time_ms); color_format, output_timestamps_ms, frame_decoding_time_ms);
// Return output buffer back to codec. // Return output buffer back to codec.
bool success = jni->CallBooleanMethod( jni->CallVoidMethod(
*j_media_codec_video_decoder_, *j_media_codec_video_decoder_,
j_release_output_buffer_method_, j_release_output_buffer_method_,
output_buffer_index); output_buffer_index);
if (CheckException(jni) || !success) { if (CheckException(jni)) {
ALOGE("releaseOutputBuffer error"); ALOGE("releaseOutputBuffer error");
return false; return false;
} }

View File

@ -42,6 +42,8 @@ import android.view.Surface;
import org.webrtc.Logging; import org.webrtc.Logging;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.List;
import java.util.Arrays;
// Java-side of peerconnection_jni.cc:MediaCodecVideoDecoder. // Java-side of peerconnection_jni.cc:MediaCodecVideoDecoder.
// This class is an implementation detail of the Java PeerConnection API. // This class is an implementation detail of the Java PeerConnection API.
@ -80,19 +82,18 @@ public class MediaCodecVideoDecoder {
private static final int private static final int
COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04; COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
// Allowable color formats supported by codec - in order of preference. // Allowable color formats supported by codec - in order of preference.
private static final int[] supportedColorList = { private static final List<Integer> supportedColorList = Arrays.asList(
CodecCapabilities.COLOR_FormatYUV420Planar, CodecCapabilities.COLOR_FormatYUV420Planar,
CodecCapabilities.COLOR_FormatYUV420SemiPlanar, CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar, CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m);
};
private int colorFormat; private int colorFormat;
private int width; private int width;
private int height; private int height;
private int stride; private int stride;
private int sliceHeight; private int sliceHeight;
private boolean useSurface; private boolean useSurface;
private int textureID = -1; private int textureID = 0;
private SurfaceTexture surfaceTexture = null; private SurfaceTexture surfaceTexture = null;
private Surface surface = null; private Surface surface = null;
private EglBase eglBase; private EglBase eglBase;
@ -171,9 +172,9 @@ public class MediaCodecVideoDecoder {
return findDecoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes) != null; return findDecoder(H264_MIME_TYPE, supportedH264HwCodecPrefixes) != null;
} }
private void checkOnMediaCodecThread() { private void checkOnMediaCodecThread() throws IllegalStateException {
if (mediaCodecThread.getId() != Thread.currentThread().getId()) { if (mediaCodecThread.getId() != Thread.currentThread().getId()) {
throw new RuntimeException( throw new IllegalStateException(
"MediaCodecVideoDecoder previously operated on " + mediaCodecThread + "MediaCodecVideoDecoder previously operated on " + mediaCodecThread +
" but is now called on " + Thread.currentThread()); " but is now called on " + Thread.currentThread());
} }
@ -208,7 +209,6 @@ public class MediaCodecVideoDecoder {
} }
mediaCodecThread = Thread.currentThread(); mediaCodecThread = Thread.currentThread();
try { try {
Surface decodeSurface = null;
this.width = width; this.width = width;
this.height = height; this.height = height;
stride = width; stride = width;
@ -225,7 +225,6 @@ public class MediaCodecVideoDecoder {
Logging.d(TAG, "Video decoder TextureID = " + textureID); Logging.d(TAG, "Video decoder TextureID = " + textureID);
surfaceTexture = new SurfaceTexture(textureID); surfaceTexture = new SurfaceTexture(textureID);
surface = new Surface(surfaceTexture); surface = new Surface(surfaceTexture);
decodeSurface = surface;
} }
MediaFormat format = MediaFormat.createVideoFormat(mime, width, height); MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
@ -238,7 +237,7 @@ public class MediaCodecVideoDecoder {
if (mediaCodec == null) { if (mediaCodec == null) {
return false; return false;
} }
mediaCodec.configure(format, decodeSurface, null, 0); mediaCodec.configure(format, surface, null, 0);
mediaCodec.start(); mediaCodec.start();
colorFormat = properties.colorFormat; colorFormat = properties.colorFormat;
outputBuffers = mediaCodec.getOutputBuffers(); outputBuffers = mediaCodec.getOutputBuffers();
@ -265,11 +264,10 @@ public class MediaCodecVideoDecoder {
mediaCodecThread = null; mediaCodecThread = null;
if (useSurface) { if (useSurface) {
surface.release(); surface.release();
if (textureID != 0) { surface = null;
Logging.d(TAG, "Delete video decoder TextureID " + textureID); Logging.d(TAG, "Delete video decoder TextureID " + textureID);
GLES20.glDeleteTextures(1, new int[] {textureID}, 0); GLES20.glDeleteTextures(1, new int[] {textureID}, 0);
textureID = 0; textureID = 0;
}
eglBase.release(); eglBase.release();
eglBase = null; eglBase = null;
} }
@ -318,19 +316,26 @@ public class MediaCodecVideoDecoder {
private final long presentationTimestampUs; private final long presentationTimestampUs;
} }
// Dequeue and return an output buffer index, -1 if no output // Dequeue and return a DecoderOutputBufferInfo, or null if no decoded buffer is ready.
// buffer available or -2 if error happened. // Throws IllegalStateException if call is made on the wrong thread, if color format changes to an
private DecoderOutputBufferInfo dequeueOutputBuffer(int dequeueTimeoutUs) { // 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(); checkOnMediaCodecThread();
try { // Drain the decoder until receiving a decoded buffer or hitting
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); // MediaCodec.INFO_TRY_AGAIN_LATER.
int result = mediaCodec.dequeueOutputBuffer(info, dequeueTimeoutUs); final MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
while (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED || while (true) {
result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { final int result = mediaCodec.dequeueOutputBuffer(info, dequeueTimeoutUs);
if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { switch (result) {
case MediaCodec.INFO_TRY_AGAIN_LATER:
return null;
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
outputBuffers = mediaCodec.getOutputBuffers(); outputBuffers = mediaCodec.getOutputBuffers();
Logging.d(TAG, "Decoder output buffers changed: " + outputBuffers.length); 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(); MediaFormat format = mediaCodec.getOutputFormat();
Logging.d(TAG, "Decoder format changed: " + format.toString()); Logging.d(TAG, "Decoder format changed: " + format.toString());
width = format.getInteger(MediaFormat.KEY_WIDTH); width = format.getInteger(MediaFormat.KEY_WIDTH);
@ -338,17 +343,8 @@ public class MediaCodecVideoDecoder {
if (!useSurface && format.containsKey(MediaFormat.KEY_COLOR_FORMAT)) { if (!useSurface && format.containsKey(MediaFormat.KEY_COLOR_FORMAT)) {
colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT); colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
Logging.d(TAG, "Color: 0x" + Integer.toHexString(colorFormat)); Logging.d(TAG, "Color: 0x" + Integer.toHexString(colorFormat));
// Check if new color space is supported. if (!supportedColorList.contains(colorFormat)) {
boolean validColorFormat = false; throw new IllegalStateException("Non supported color format: " + colorFormat);
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 (format.containsKey("stride")) { if (format.containsKey("stride")) {
@ -357,34 +353,24 @@ public class MediaCodecVideoDecoder {
if (format.containsKey("slice-height")) { if (format.containsKey("slice-height")) {
sliceHeight = format.getInteger("slice-height"); sliceHeight = format.getInteger("slice-height");
} }
Logging.d(TAG, "Frame stride and slice height: " Logging.d(TAG, "Frame stride and slice height: " + stride + " x " + sliceHeight);
+ stride + " x " + sliceHeight);
stride = Math.max(width, stride); stride = Math.max(width, stride);
sliceHeight = Math.max(height, sliceHeight); sliceHeight = Math.max(height, sliceHeight);
break;
default:
// Output buffer decoded.
return new DecoderOutputBufferInfo(
result, info.offset, info.size, info.presentationTimeUs);
} }
result = mediaCodec.dequeueOutputBuffer(info, dequeueTimeoutUs);
}
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 // Release a dequeued output buffer back to the codec for re-use.
// false if the codec is no longer operable. // Throws IllegalStateException if the call is made on the wrong thread or if |mediaCodec| is not
private boolean releaseOutputBuffer(int index) { // in the Executing state. Throws MediaCodec.CodecException upon codec error.
private void releaseOutputBuffer(int index)
throws IllegalStateException, MediaCodec.CodecException {
checkOnMediaCodecThread(); checkOnMediaCodecThread();
try {
mediaCodec.releaseOutputBuffer(index, useSurface); mediaCodec.releaseOutputBuffer(index, useSurface);
return true;
} catch (IllegalStateException e) {
Logging.e(TAG, "releaseOutputBuffer failed", e);
return false;
}
} }
} }