Improve Java video codec error handling.
FALLBACK_SOFTWARE is now treated as a critical error and results in immediate fallback to software coding if available. If ERROR is returned, codec reset is attempted. If that fails, software fallback is used. Bug: b/73498933 Change-Id: I7fe163efd09e6f27c72491e9595954ddc59b1448 Reviewed-on: https://webrtc-review.googlesource.com/54901 Reviewed-by: Alex Glaznev <glaznev@webrtc.org> Commit-Queue: Sami Kalliomäki <sakal@webrtc.org> Cr-Commit-Position: refs/heads/master@{#22169}
This commit is contained in:
parent
99f52f83e4
commit
d60d5c4479
@ -163,7 +163,7 @@ class HardwareVideoDecoder
|
|||||||
Logging.d(TAG, "initDecodeInternal");
|
Logging.d(TAG, "initDecodeInternal");
|
||||||
if (outputThread != null) {
|
if (outputThread != null) {
|
||||||
Logging.e(TAG, "initDecodeInternal called while the codec is already running");
|
Logging.e(TAG, "initDecodeInternal called while the codec is already running");
|
||||||
return VideoCodecStatus.ERROR;
|
return VideoCodecStatus.FALLBACK_SOFTWARE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: it is not necessary to initialize dimensions under the lock, since the output thread
|
// Note: it is not necessary to initialize dimensions under the lock, since the output thread
|
||||||
@ -180,7 +180,7 @@ class HardwareVideoDecoder
|
|||||||
codec = MediaCodec.createByCodecName(codecName);
|
codec = MediaCodec.createByCodecName(codecName);
|
||||||
} catch (IOException | IllegalArgumentException e) {
|
} catch (IOException | IllegalArgumentException e) {
|
||||||
Logging.e(TAG, "Cannot create media decoder " + codecName);
|
Logging.e(TAG, "Cannot create media decoder " + codecName);
|
||||||
return VideoCodecStatus.ERROR;
|
return VideoCodecStatus.FALLBACK_SOFTWARE;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
MediaFormat format = MediaFormat.createVideoFormat(codecType.mimeType(), width, height);
|
MediaFormat format = MediaFormat.createVideoFormat(codecType.mimeType(), width, height);
|
||||||
@ -192,7 +192,7 @@ class HardwareVideoDecoder
|
|||||||
} catch (IllegalStateException e) {
|
} catch (IllegalStateException e) {
|
||||||
Logging.e(TAG, "initDecode failed", e);
|
Logging.e(TAG, "initDecode failed", e);
|
||||||
release();
|
release();
|
||||||
return VideoCodecStatus.ERROR;
|
return VideoCodecStatus.FALLBACK_SOFTWARE;
|
||||||
}
|
}
|
||||||
running = true;
|
running = true;
|
||||||
outputThread = createOutputThread();
|
outputThread = createOutputThread();
|
||||||
@ -241,11 +241,11 @@ class HardwareVideoDecoder
|
|||||||
// Need to process a key frame first.
|
// Need to process a key frame first.
|
||||||
if (frame.frameType != EncodedImage.FrameType.VideoFrameKey) {
|
if (frame.frameType != EncodedImage.FrameType.VideoFrameKey) {
|
||||||
Logging.e(TAG, "decode() - key frame required first");
|
Logging.e(TAG, "decode() - key frame required first");
|
||||||
return VideoCodecStatus.ERROR;
|
return VideoCodecStatus.NO_OUTPUT;
|
||||||
}
|
}
|
||||||
if (!frame.completeFrame) {
|
if (!frame.completeFrame) {
|
||||||
Logging.e(TAG, "decode() - complete frame required first");
|
Logging.e(TAG, "decode() - complete frame required first");
|
||||||
return VideoCodecStatus.ERROR;
|
return VideoCodecStatus.NO_OUTPUT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -176,7 +176,7 @@ class HardwareVideoEncoder implements VideoEncoder {
|
|||||||
codec = MediaCodec.createByCodecName(codecName);
|
codec = MediaCodec.createByCodecName(codecName);
|
||||||
} catch (IOException | IllegalArgumentException e) {
|
} catch (IOException | IllegalArgumentException e) {
|
||||||
Logging.e(TAG, "Cannot create media encoder " + codecName);
|
Logging.e(TAG, "Cannot create media encoder " + codecName);
|
||||||
return VideoCodecStatus.ERROR;
|
return VideoCodecStatus.FALLBACK_SOFTWARE;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int colorFormat = useSurfaceMode ? surfaceColorFormat : yuvColorFormat;
|
final int colorFormat = useSurfaceMode ? surfaceColorFormat : yuvColorFormat;
|
||||||
@ -218,7 +218,7 @@ class HardwareVideoEncoder implements VideoEncoder {
|
|||||||
} catch (IllegalStateException e) {
|
} catch (IllegalStateException e) {
|
||||||
Logging.e(TAG, "initEncodeInternal failed", e);
|
Logging.e(TAG, "initEncodeInternal failed", e);
|
||||||
release();
|
release();
|
||||||
return VideoCodecStatus.ERROR;
|
return VideoCodecStatus.FALLBACK_SOFTWARE;
|
||||||
}
|
}
|
||||||
|
|
||||||
running = true;
|
running = true;
|
||||||
|
|||||||
@ -67,9 +67,10 @@ int32_t VideoDecoderWrapper::InitDecodeInternal(JNIEnv* jni) {
|
|||||||
Java_VideoDecoderWrapper_createDecoderCallback(jni,
|
Java_VideoDecoderWrapper_createDecoderCallback(jni,
|
||||||
jlongFromPointer(this));
|
jlongFromPointer(this));
|
||||||
|
|
||||||
ScopedJavaLocalRef<jobject> ret =
|
int32_t status = JavaToNativeVideoCodecStatus(
|
||||||
Java_VideoDecoder_initDecode(jni, decoder_, settings, callback);
|
jni, Java_VideoDecoder_initDecode(jni, decoder_, settings, callback));
|
||||||
if (JavaToNativeVideoCodecStatus(jni, ret) == WEBRTC_VIDEO_CODEC_OK) {
|
RTC_LOG(LS_INFO) << "initDecode: " << status;
|
||||||
|
if (status == WEBRTC_VIDEO_CODEC_OK) {
|
||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +78,7 @@ int32_t VideoDecoderWrapper::InitDecodeInternal(JNIEnv* jni) {
|
|||||||
// providing QP values.
|
// providing QP values.
|
||||||
qp_parsing_enabled_ = true;
|
qp_parsing_enabled_ = true;
|
||||||
|
|
||||||
return HandleReturnCode(jni, ret);
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t VideoDecoderWrapper::Decode(
|
int32_t VideoDecoderWrapper::Decode(
|
||||||
@ -116,7 +117,7 @@ int32_t VideoDecoderWrapper::Decode(
|
|||||||
ScopedJavaLocalRef<jobject> decode_info;
|
ScopedJavaLocalRef<jobject> decode_info;
|
||||||
ScopedJavaLocalRef<jobject> ret =
|
ScopedJavaLocalRef<jobject> ret =
|
||||||
Java_VideoDecoder_decode(env, decoder_, jinput_image, decode_info);
|
Java_VideoDecoder_decode(env, decoder_, jinput_image, decode_info);
|
||||||
return HandleReturnCode(env, ret);
|
return HandleReturnCode(env, ret, "decode");
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t VideoDecoderWrapper::RegisterDecodeCompleteCallback(
|
int32_t VideoDecoderWrapper::RegisterDecodeCompleteCallback(
|
||||||
@ -128,13 +129,14 @@ int32_t VideoDecoderWrapper::RegisterDecodeCompleteCallback(
|
|||||||
|
|
||||||
int32_t VideoDecoderWrapper::Release() {
|
int32_t VideoDecoderWrapper::Release() {
|
||||||
JNIEnv* jni = AttachCurrentThreadIfNeeded();
|
JNIEnv* jni = AttachCurrentThreadIfNeeded();
|
||||||
ScopedJavaLocalRef<jobject> ret = Java_VideoDecoder_release(jni, decoder_);
|
int32_t status = JavaToNativeVideoCodecStatus(
|
||||||
|
jni, Java_VideoDecoder_release(jni, decoder_));
|
||||||
|
RTC_LOG(LS_INFO) << "release: " << status;
|
||||||
{
|
{
|
||||||
rtc::CritScope cs(&frame_extra_infos_lock_);
|
rtc::CritScope cs(&frame_extra_infos_lock_);
|
||||||
frame_extra_infos_.clear();
|
frame_extra_infos_.clear();
|
||||||
}
|
}
|
||||||
initialized_ = false;
|
initialized_ = false;
|
||||||
int32_t status = HandleReturnCode(jni, ret);
|
|
||||||
// It is allowed to reinitialize the codec on a different thread.
|
// It is allowed to reinitialize the codec on a different thread.
|
||||||
decoder_thread_checker_.DetachFromThread();
|
decoder_thread_checker_.DetachFromThread();
|
||||||
return status;
|
return status;
|
||||||
@ -193,19 +195,29 @@ void VideoDecoderWrapper::OnDecodedFrame(
|
|||||||
}
|
}
|
||||||
|
|
||||||
int32_t VideoDecoderWrapper::HandleReturnCode(JNIEnv* jni,
|
int32_t VideoDecoderWrapper::HandleReturnCode(JNIEnv* jni,
|
||||||
const JavaRef<jobject>& code) {
|
const JavaRef<jobject>& j_value,
|
||||||
int32_t value = JavaToNativeVideoCodecStatus(jni, code);
|
const char* method_name) {
|
||||||
if (value < 0) { // Any errors are represented by negative values.
|
int32_t value = JavaToNativeVideoCodecStatus(jni, j_value);
|
||||||
// Reset the codec.
|
if (value >= 0) { // OK or NO_OUTPUT
|
||||||
if (Release() == WEBRTC_VIDEO_CODEC_OK) {
|
|
||||||
InitDecodeInternal(jni);
|
|
||||||
}
|
|
||||||
|
|
||||||
RTC_LOG(LS_WARNING) << "Falling back to software decoder.";
|
|
||||||
return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
|
|
||||||
} else {
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RTC_LOG(LS_WARNING) << method_name << ": " << value;
|
||||||
|
if (value == WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE ||
|
||||||
|
value == WEBRTC_VIDEO_CODEC_UNINITIALIZED) { // Critical error.
|
||||||
|
RTC_LOG(LS_WARNING) << "Java decoder requested software fallback.";
|
||||||
|
return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try resetting the codec.
|
||||||
|
if (Release() == WEBRTC_VIDEO_CODEC_OK &&
|
||||||
|
InitDecodeInternal(jni) == WEBRTC_VIDEO_CODEC_OK) {
|
||||||
|
RTC_LOG(LS_WARNING) << "Reset Java decoder.";
|
||||||
|
return WEBRTC_VIDEO_CODEC_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
RTC_LOG(LS_WARNING) << "Unable to reset Java decoder.";
|
||||||
|
return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
|
||||||
}
|
}
|
||||||
|
|
||||||
rtc::Optional<uint8_t> VideoDecoderWrapper::ParseQP(
|
rtc::Optional<uint8_t> VideoDecoderWrapper::ParseQP(
|
||||||
|
|||||||
@ -73,7 +73,9 @@ class VideoDecoderWrapper : public VideoDecoder {
|
|||||||
|
|
||||||
// Takes Java VideoCodecStatus, handles it and returns WEBRTC_VIDEO_CODEC_*
|
// Takes Java VideoCodecStatus, handles it and returns WEBRTC_VIDEO_CODEC_*
|
||||||
// status code.
|
// status code.
|
||||||
int32_t HandleReturnCode(JNIEnv* jni, const JavaRef<jobject>& code)
|
int32_t HandleReturnCode(JNIEnv* jni,
|
||||||
|
const JavaRef<jobject>& j_value,
|
||||||
|
const char* method_name)
|
||||||
RTC_RUN_ON(decoder_thread_checker_);
|
RTC_RUN_ON(decoder_thread_checker_);
|
||||||
|
|
||||||
rtc::Optional<uint8_t> ParseQP(const EncodedImage& input_image)
|
rtc::Optional<uint8_t> ParseQP(const EncodedImage& input_image)
|
||||||
|
|||||||
@ -31,8 +31,6 @@
|
|||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
namespace jni {
|
namespace jni {
|
||||||
|
|
||||||
static const int kMaxJavaEncoderResets = 3;
|
|
||||||
|
|
||||||
VideoEncoderWrapper::VideoEncoderWrapper(JNIEnv* jni,
|
VideoEncoderWrapper::VideoEncoderWrapper(JNIEnv* jni,
|
||||||
const JavaRef<jobject>& j_encoder)
|
const JavaRef<jobject>& j_encoder)
|
||||||
: encoder_(jni, j_encoder), int_array_class_(GetClass(jni, "[I")) {
|
: encoder_(jni, j_encoder), int_array_class_(GetClass(jni, "[I")) {
|
||||||
@ -81,14 +79,14 @@ int32_t VideoEncoderWrapper::InitEncodeInternal(JNIEnv* jni) {
|
|||||||
Java_VideoEncoderWrapper_createEncoderCallback(jni,
|
Java_VideoEncoderWrapper_createEncoderCallback(jni,
|
||||||
jlongFromPointer(this));
|
jlongFromPointer(this));
|
||||||
|
|
||||||
ScopedJavaLocalRef<jobject> ret =
|
int32_t status = JavaToNativeVideoCodecStatus(
|
||||||
Java_VideoEncoder_initEncode(jni, encoder_, settings, callback);
|
jni, Java_VideoEncoder_initEncode(jni, encoder_, settings, callback));
|
||||||
|
RTC_LOG(LS_INFO) << "initEncode: " << status;
|
||||||
|
|
||||||
if (JavaToNativeVideoCodecStatus(jni, ret) == WEBRTC_VIDEO_CODEC_OK) {
|
if (status == WEBRTC_VIDEO_CODEC_OK) {
|
||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
}
|
}
|
||||||
|
return status;
|
||||||
return HandleReturnCode(jni, ret);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t VideoEncoderWrapper::RegisterEncodeCompleteCallback(
|
int32_t VideoEncoderWrapper::RegisterEncodeCompleteCallback(
|
||||||
@ -99,11 +97,15 @@ int32_t VideoEncoderWrapper::RegisterEncodeCompleteCallback(
|
|||||||
|
|
||||||
int32_t VideoEncoderWrapper::Release() {
|
int32_t VideoEncoderWrapper::Release() {
|
||||||
JNIEnv* jni = AttachCurrentThreadIfNeeded();
|
JNIEnv* jni = AttachCurrentThreadIfNeeded();
|
||||||
ScopedJavaLocalRef<jobject> ret = Java_VideoEncoder_release(jni, encoder_);
|
|
||||||
|
int32_t status = JavaToNativeVideoCodecStatus(
|
||||||
|
jni, Java_VideoEncoder_release(jni, encoder_));
|
||||||
|
RTC_LOG(LS_INFO) << "release: " << status;
|
||||||
frame_extra_infos_.clear();
|
frame_extra_infos_.clear();
|
||||||
initialized_ = false;
|
initialized_ = false;
|
||||||
encoder_queue_ = nullptr;
|
encoder_queue_ = nullptr;
|
||||||
return HandleReturnCode(jni, ret);
|
|
||||||
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t VideoEncoderWrapper::Encode(
|
int32_t VideoEncoderWrapper::Encode(
|
||||||
@ -132,7 +134,7 @@ int32_t VideoEncoderWrapper::Encode(
|
|||||||
ScopedJavaLocalRef<jobject> ret =
|
ScopedJavaLocalRef<jobject> ret =
|
||||||
Java_VideoEncoder_encode(jni, encoder_, j_frame, encode_info);
|
Java_VideoEncoder_encode(jni, encoder_, j_frame, encode_info);
|
||||||
ReleaseJavaVideoFrame(jni, j_frame);
|
ReleaseJavaVideoFrame(jni, j_frame);
|
||||||
return HandleReturnCode(jni, ret);
|
return HandleReturnCode(jni, ret, "encode");
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t VideoEncoderWrapper::SetChannelParameters(uint32_t packet_loss,
|
int32_t VideoEncoderWrapper::SetChannelParameters(uint32_t packet_loss,
|
||||||
@ -140,7 +142,7 @@ int32_t VideoEncoderWrapper::SetChannelParameters(uint32_t packet_loss,
|
|||||||
JNIEnv* jni = AttachCurrentThreadIfNeeded();
|
JNIEnv* jni = AttachCurrentThreadIfNeeded();
|
||||||
ScopedJavaLocalRef<jobject> ret = Java_VideoEncoder_setChannelParameters(
|
ScopedJavaLocalRef<jobject> ret = Java_VideoEncoder_setChannelParameters(
|
||||||
jni, encoder_, (jshort)packet_loss, (jlong)rtt);
|
jni, encoder_, (jshort)packet_loss, (jlong)rtt);
|
||||||
return HandleReturnCode(jni, ret);
|
return HandleReturnCode(jni, ret, "setChannelParameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t VideoEncoderWrapper::SetRateAllocation(
|
int32_t VideoEncoderWrapper::SetRateAllocation(
|
||||||
@ -152,7 +154,7 @@ int32_t VideoEncoderWrapper::SetRateAllocation(
|
|||||||
ToJavaBitrateAllocation(jni, allocation);
|
ToJavaBitrateAllocation(jni, allocation);
|
||||||
ScopedJavaLocalRef<jobject> ret = Java_VideoEncoder_setRateAllocation(
|
ScopedJavaLocalRef<jobject> ret = Java_VideoEncoder_setRateAllocation(
|
||||||
jni, encoder_, j_bitrate_allocation, (jint)framerate);
|
jni, encoder_, j_bitrate_allocation, (jint)framerate);
|
||||||
return HandleReturnCode(jni, ret);
|
return HandleReturnCode(jni, ret, "setRateAllocation");
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoEncoderWrapper::ScalingSettings VideoEncoderWrapper::GetScalingSettings()
|
VideoEncoderWrapper::ScalingSettings VideoEncoderWrapper::GetScalingSettings()
|
||||||
@ -263,21 +265,29 @@ void VideoEncoderWrapper::OnEncodedFrame(JNIEnv* jni,
|
|||||||
}
|
}
|
||||||
|
|
||||||
int32_t VideoEncoderWrapper::HandleReturnCode(JNIEnv* jni,
|
int32_t VideoEncoderWrapper::HandleReturnCode(JNIEnv* jni,
|
||||||
const JavaRef<jobject>& code) {
|
const JavaRef<jobject>& j_value,
|
||||||
int32_t value = JavaToNativeVideoCodecStatus(jni, code);
|
const char* method_name) {
|
||||||
if (value < 0) { // Any errors are represented by negative values.
|
int32_t value = JavaToNativeVideoCodecStatus(jni, j_value);
|
||||||
// Try resetting the codec.
|
if (value >= 0) { // OK or NO_OUTPUT
|
||||||
if (++num_resets_ <= kMaxJavaEncoderResets &&
|
|
||||||
Release() == WEBRTC_VIDEO_CODEC_OK) {
|
|
||||||
RTC_LOG(LS_WARNING) << "Reset Java encoder: " << num_resets_;
|
|
||||||
return InitEncodeInternal(jni);
|
|
||||||
}
|
|
||||||
|
|
||||||
RTC_LOG(LS_WARNING) << "Falling back to software decoder.";
|
|
||||||
return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
|
|
||||||
} else {
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RTC_LOG(LS_WARNING) << method_name << ": " << value;
|
||||||
|
if (value == WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE ||
|
||||||
|
value == WEBRTC_VIDEO_CODEC_UNINITIALIZED) { // Critical error.
|
||||||
|
RTC_LOG(LS_WARNING) << "Java encoder requested software fallback.";
|
||||||
|
return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try resetting the codec.
|
||||||
|
if (Release() == WEBRTC_VIDEO_CODEC_OK &&
|
||||||
|
InitEncodeInternal(jni) == WEBRTC_VIDEO_CODEC_OK) {
|
||||||
|
RTC_LOG(LS_WARNING) << "Reset Java encoder.";
|
||||||
|
return WEBRTC_VIDEO_CODEC_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
RTC_LOG(LS_WARNING) << "Unable to reset Java encoder.";
|
||||||
|
return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
|
||||||
}
|
}
|
||||||
|
|
||||||
RTPFragmentationHeader VideoEncoderWrapper::ParseFragmentationHeader(
|
RTPFragmentationHeader VideoEncoderWrapper::ParseFragmentationHeader(
|
||||||
|
|||||||
@ -78,7 +78,9 @@ class VideoEncoderWrapper : public VideoEncoder {
|
|||||||
|
|
||||||
// Takes Java VideoCodecStatus, handles it and returns WEBRTC_VIDEO_CODEC_*
|
// Takes Java VideoCodecStatus, handles it and returns WEBRTC_VIDEO_CODEC_*
|
||||||
// status code.
|
// status code.
|
||||||
int32_t HandleReturnCode(JNIEnv* jni, const JavaRef<jobject>& code);
|
int32_t HandleReturnCode(JNIEnv* jni,
|
||||||
|
const JavaRef<jobject>& j_value,
|
||||||
|
const char* method_name);
|
||||||
|
|
||||||
RTPFragmentationHeader ParseFragmentationHeader(
|
RTPFragmentationHeader ParseFragmentationHeader(
|
||||||
const std::vector<uint8_t>& buffer);
|
const std::vector<uint8_t>& buffer);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user