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/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;
}

View File

@ -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<Integer> 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);
}
}