Index: webrtc/api/android/jni/androidmediaencoder_jni.cc |
diff --git a/webrtc/api/android/jni/androidmediaencoder_jni.cc b/webrtc/api/android/jni/androidmediaencoder_jni.cc |
index 8d0d3b54bd512557401dcf652b94d62d930388f4..e19095725d917d79dacf857314bd38ce873150c2 100644 |
--- a/webrtc/api/android/jni/androidmediaencoder_jni.cc |
+++ b/webrtc/api/android/jni/androidmediaencoder_jni.cc |
@@ -129,8 +129,20 @@ class MediaCodecVideoEncoder : public webrtc::VideoEncoder, |
// ResetCodecOnCodecThread() calls ReleaseOnCodecThread() and |
// InitEncodeOnCodecThread() in an attempt to restore the codec to an |
// operable state. Necessary after all manner of OMX-layer errors. |
+ // Returns true if the codec was reset successfully. |
bool ResetCodecOnCodecThread(); |
+ // Fallback to a software encoder if one is supported else try to reset the |
+ // encoder. Called with |reset_if_fallback_unavailable| equal to false from |
+ // init/release encoder so that we don't go into infinite recursion. |
+ // Returns true if the codec was reset successfully. |
+ bool ProcessHWErrorOnCodecThread(bool reset_if_fallback_unavailable); |
+ |
+ // Calls ProcessHWErrorOnCodecThread(true). Returns |
+ // WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE if sw_fallback_required_ was set or |
+ // WEBRTC_VIDEO_CODEC_ERROR otherwise. |
+ int32_t ProcessHWErrorOnEncodeOnCodecThread(); |
+ |
// Implementation of webrtc::VideoEncoder methods above, all running on the |
// codec thread exclusively. |
// |
@@ -282,6 +294,8 @@ class MediaCodecVideoEncoder : public webrtc::VideoEncoder, |
int64_t last_frame_received_ms_; |
int frames_received_since_last_key_; |
webrtc::VideoCodecMode codec_mode_; |
+ |
+ bool sw_fallback_required_; |
}; |
MediaCodecVideoEncoder::~MediaCodecVideoEncoder() { |
@@ -289,25 +303,27 @@ MediaCodecVideoEncoder::~MediaCodecVideoEncoder() { |
Release(); |
} |
-MediaCodecVideoEncoder::MediaCodecVideoEncoder( |
- JNIEnv* jni, VideoCodecType codecType, jobject egl_context) : |
- codecType_(codecType), |
- callback_(NULL), |
- codec_thread_(new Thread()), |
- j_media_codec_video_encoder_class_( |
- jni, |
- FindClass(jni, "org/webrtc/MediaCodecVideoEncoder")), |
- j_media_codec_video_encoder_( |
- jni, |
- jni->NewObject(*j_media_codec_video_encoder_class_, |
- GetMethodID(jni, |
- *j_media_codec_video_encoder_class_, |
- "<init>", |
- "()V"))), |
- inited_(false), |
- use_surface_(false), |
- picture_id_(0), |
- egl_context_(egl_context) { |
+MediaCodecVideoEncoder::MediaCodecVideoEncoder(JNIEnv* jni, |
+ VideoCodecType codecType, |
+ jobject egl_context) |
+ : codecType_(codecType), |
+ callback_(NULL), |
+ codec_thread_(new Thread()), |
+ j_media_codec_video_encoder_class_( |
+ jni, |
+ FindClass(jni, "org/webrtc/MediaCodecVideoEncoder")), |
+ j_media_codec_video_encoder_( |
+ jni, |
+ jni->NewObject(*j_media_codec_video_encoder_class_, |
+ GetMethodID(jni, |
+ *j_media_codec_video_encoder_class_, |
+ "<init>", |
+ "()V"))), |
+ inited_(false), |
+ use_surface_(false), |
+ picture_id_(0), |
+ egl_context_(egl_context), |
+ sw_fallback_required_(false) { |
ScopedLocalRefFrame local_ref_frame(jni); |
// It would be nice to avoid spinning up a new thread per MediaCodec, and |
// instead re-use e.g. the PeerConnectionFactory's |worker_thread_|, but bug |
@@ -361,7 +377,10 @@ MediaCodecVideoEncoder::MediaCodecVideoEncoder( |
GetFieldID(jni, j_output_buffer_info_class, "isKeyFrame", "Z"); |
j_info_presentation_timestamp_us_field_ = GetFieldID( |
jni, j_output_buffer_info_class, "presentationTimestampUs", "J"); |
- CHECK_EXCEPTION(jni) << "MediaCodecVideoEncoder ctor failed"; |
+ if (CheckException(jni)) { |
+ ALOGW << "MediaCodecVideoEncoder ctor failed."; |
+ ProcessHWErrorOnCodecThread(true /* reset_if_fallback_unavailable */); |
+ } |
srand(time(NULL)); |
AllowBlockingCalls(); |
} |
@@ -378,7 +397,9 @@ int32_t MediaCodecVideoEncoder::InitEncode( |
RTC_CHECK(codec_settings->codecType == codecType_) |
<< "Unsupported codec " << codec_settings->codecType << " for " |
<< codecType_; |
- |
+ if (sw_fallback_required_) { |
+ return WEBRTC_VIDEO_CODEC_OK; |
+ } |
codec_mode_ = codec_settings->mode; |
int init_width = codec_settings->width; |
int init_height = codec_settings->height; |
@@ -494,20 +515,45 @@ void MediaCodecVideoEncoder::OnMessage(rtc::Message* msg) { |
bool MediaCodecVideoEncoder::ResetCodecOnCodecThread() { |
RTC_DCHECK(codec_thread_checker_.CalledOnValidThread()); |
ALOGE << "ResetOnCodecThread"; |
- if (ReleaseOnCodecThread() != WEBRTC_VIDEO_CODEC_OK || |
- InitEncodeOnCodecThread(width_, height_, 0, 0, false) != |
- WEBRTC_VIDEO_CODEC_OK) { |
- // TODO(fischman): wouldn't it be nice if there was a way to gracefully |
- // degrade to a SW encoder at this point? There isn't one AFAICT :( |
- // https://code.google.com/p/webrtc/issues/detail?id=2920 |
+ if (ReleaseOnCodecThread() != WEBRTC_VIDEO_CODEC_OK) { |
+ ALOGE << "Releasing codec failed during reset."; |
+ return false; |
+ } |
+ if (InitEncodeOnCodecThread(width_, height_, 0, 0, false) != |
+ WEBRTC_VIDEO_CODEC_OK) { |
+ ALOGE << "Initializing encoder failed during reset."; |
return false; |
} |
return true; |
} |
+bool MediaCodecVideoEncoder::ProcessHWErrorOnCodecThread( |
+ bool reset_if_fallback_unavailable) { |
+ ALOGE << "ProcessHWErrorOnCodecThread"; |
+ if (VideoEncoder::IsSupportedSoftware( |
+ VideoEncoder::CodecToEncoderType(codecType_))) { |
+ ALOGE << "Fallback to SW encoder."; |
+ sw_fallback_required_ = true; |
+ return false; |
+ } else if (reset_if_fallback_unavailable) { |
+ ALOGE << "Reset encoder."; |
+ return ResetCodecOnCodecThread(); |
+ } |
+ return false; |
+} |
+ |
+int32_t MediaCodecVideoEncoder::ProcessHWErrorOnEncodeOnCodecThread() { |
+ ProcessHWErrorOnCodecThread(true /* reset_if_fallback_unavailable */); |
+ return sw_fallback_required_ ? WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE |
+ : WEBRTC_VIDEO_CODEC_ERROR; |
+} |
+ |
int32_t MediaCodecVideoEncoder::InitEncodeOnCodecThread( |
int width, int height, int kbps, int fps, bool use_surface) { |
RTC_DCHECK(codec_thread_checker_.CalledOnValidThread()); |
+ if (sw_fallback_required_) { |
+ return WEBRTC_VIDEO_CODEC_OK; |
+ } |
RTC_CHECK(!use_surface || egl_context_ != nullptr) << "EGL context not set."; |
JNIEnv* jni = AttachCurrentThreadIfNeeded(); |
ScopedLocalRefFrame local_ref_frame(jni); |
@@ -560,16 +606,27 @@ int32_t MediaCodecVideoEncoder::InitEncodeOnCodecThread( |
(use_surface ? egl_context_ : nullptr)); |
if (!encode_status) { |
ALOGE << "Failed to configure encoder."; |
+ ProcessHWErrorOnCodecThread(false /* reset_if_fallback_unavailable */); |
+ return WEBRTC_VIDEO_CODEC_ERROR; |
+ } |
+ if (CheckException(jni)) { |
+ ALOGE << "Exception in init encode."; |
+ ProcessHWErrorOnCodecThread(false /* reset_if_fallback_unavailable */); |
return WEBRTC_VIDEO_CODEC_ERROR; |
} |
- CHECK_EXCEPTION(jni); |
if (!use_surface) { |
jobjectArray input_buffers = reinterpret_cast<jobjectArray>( |
jni->CallObjectMethod(*j_media_codec_video_encoder_, |
j_get_input_buffers_method_)); |
- CHECK_EXCEPTION(jni); |
+ if (CheckException(jni)) { |
+ ALOGE << "Exception in get input buffers."; |
+ ProcessHWErrorOnCodecThread(false /* reset_if_fallback_unavailable */); |
+ return WEBRTC_VIDEO_CODEC_ERROR; |
+ } |
+ |
if (IsNull(jni, input_buffers)) { |
+ ProcessHWErrorOnCodecThread(false /* reset_if_fallback_unavailable */); |
return WEBRTC_VIDEO_CODEC_ERROR; |
} |
@@ -585,6 +642,7 @@ int32_t MediaCodecVideoEncoder::InitEncodeOnCodecThread( |
break; |
default: |
LOG(LS_ERROR) << "Wrong color format."; |
+ ProcessHWErrorOnCodecThread(false /* reset_if_fallback_unavailable */); |
return WEBRTC_VIDEO_CODEC_ERROR; |
} |
size_t num_input_buffers = jni->GetArrayLength(input_buffers); |
@@ -596,7 +654,11 @@ int32_t MediaCodecVideoEncoder::InitEncodeOnCodecThread( |
jni->NewGlobalRef(jni->GetObjectArrayElement(input_buffers, i)); |
int64_t yuv_buffer_capacity = |
jni->GetDirectBufferCapacity(input_buffers_[i]); |
- CHECK_EXCEPTION(jni); |
+ if (CheckException(jni)) { |
+ ALOGE << "Exception in get direct buffer capacity."; |
+ ProcessHWErrorOnCodecThread(false /* reset_if_fallback_unavailable */); |
+ return WEBRTC_VIDEO_CODEC_ERROR; |
+ } |
RTC_CHECK(yuv_buffer_capacity >= yuv_size_) << "Insufficient capacity"; |
} |
} |
@@ -610,6 +672,8 @@ int32_t MediaCodecVideoEncoder::EncodeOnCodecThread( |
const std::vector<webrtc::FrameType>* frame_types, |
const int64_t frame_input_time_ms) { |
RTC_DCHECK(codec_thread_checker_.CalledOnValidThread()); |
+ if (sw_fallback_required_) |
+ return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE; |
JNIEnv* jni = AttachCurrentThreadIfNeeded(); |
ScopedLocalRefFrame local_ref_frame(jni); |
@@ -636,8 +700,11 @@ int32_t MediaCodecVideoEncoder::EncodeOnCodecThread( |
frames_received_++; |
if (!DeliverPendingOutputs(jni)) { |
- if (!ResetCodecOnCodecThread()) |
- return WEBRTC_VIDEO_CODEC_ERROR; |
+ if (!ProcessHWErrorOnCodecThread( |
+ true /* reset_if_fallback_unavailable */)) { |
+ return sw_fallback_required_ ? WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE |
+ : WEBRTC_VIDEO_CODEC_ERROR; |
+ } |
} |
if (frames_encoded_ < kMaxEncodedLogFrames) { |
ALOGD << "Encoder frame in # " << (frames_received_ - 1) |
@@ -669,9 +736,8 @@ int32_t MediaCodecVideoEncoder::EncodeOnCodecThread( |
consecutive_full_queue_frame_drops_++; |
if (consecutive_full_queue_frame_drops_ >= |
ENCODER_STALL_FRAMEDROP_THRESHOLD) { |
- ALOGE << "Encoder got stuck. Reset."; |
- ResetCodecOnCodecThread(); |
- return WEBRTC_VIDEO_CODEC_ERROR; |
+ ALOGE << "Encoder got stuck."; |
+ return ProcessHWErrorOnEncodeOnCodecThread(); |
} |
frames_dropped_media_encoder_++; |
OnDroppedFrameOnCodecThread(); |
@@ -715,7 +781,10 @@ int32_t MediaCodecVideoEncoder::EncodeOnCodecThread( |
if (!input_frame.video_frame_buffer()->native_handle()) { |
int j_input_buffer_index = jni->CallIntMethod(*j_media_codec_video_encoder_, |
j_dequeue_input_buffer_method_); |
- CHECK_EXCEPTION(jni); |
+ if (CheckException(jni)) { |
+ ALOGE << "Exception in dequeu input buffer."; |
+ return ProcessHWErrorOnEncodeOnCodecThread(); |
+ } |
if (j_input_buffer_index == -1) { |
// Video codec falls behind - no input buffer available. |
ALOGW << "Encoder drop frame - no input buffers available"; |
@@ -731,8 +800,7 @@ int32_t MediaCodecVideoEncoder::EncodeOnCodecThread( |
} |
return WEBRTC_VIDEO_CODEC_OK; // TODO(fischman): see webrtc bug 2887. |
} else if (j_input_buffer_index == -2) { |
- ResetCodecOnCodecThread(); |
- return WEBRTC_VIDEO_CODEC_ERROR; |
+ return ProcessHWErrorOnEncodeOnCodecThread(); |
} |
encode_status = EncodeByteBufferOnCodecThread(jni, key_frame, input_frame, |
j_input_buffer_index); |
@@ -742,8 +810,7 @@ int32_t MediaCodecVideoEncoder::EncodeOnCodecThread( |
if (!encode_status) { |
ALOGE << "Failed encode frame with timestamp: " << input_frame.timestamp(); |
- ResetCodecOnCodecThread(); |
- return WEBRTC_VIDEO_CODEC_ERROR; |
+ return ProcessHWErrorOnEncodeOnCodecThread(); |
} |
// Save input image timestamps for later output. |
@@ -760,9 +827,7 @@ int32_t MediaCodecVideoEncoder::EncodeOnCodecThread( |
codec_thread_->PostDelayed(RTC_FROM_HERE, kMediaCodecPollMs, this); |
if (!DeliverPendingOutputs(jni)) { |
- ALOGE << "Failed deliver pending outputs."; |
- ResetCodecOnCodecThread(); |
- return WEBRTC_VIDEO_CODEC_ERROR; |
+ return ProcessHWErrorOnEncodeOnCodecThread(); |
} |
return WEBRTC_VIDEO_CODEC_OK; |
} |
@@ -810,7 +875,11 @@ bool MediaCodecVideoEncoder::EncodeByteBufferOnCodecThread(JNIEnv* jni, |
jobject j_input_buffer = input_buffers_[input_buffer_index]; |
uint8_t* yuv_buffer = |
reinterpret_cast<uint8_t*>(jni->GetDirectBufferAddress(j_input_buffer)); |
- CHECK_EXCEPTION(jni); |
+ if (CheckException(jni)) { |
+ ALOGE << "Exception in get direct buffer address."; |
+ ProcessHWErrorOnCodecThread(true /* reset_if_fallback_unavailable */); |
+ return false; |
+ } |
RTC_CHECK(yuv_buffer) << "Indirect buffer??"; |
RTC_CHECK(!libyuv::ConvertFromI420( |
frame.video_frame_buffer()->DataY(), |
@@ -828,7 +897,11 @@ bool MediaCodecVideoEncoder::EncodeByteBufferOnCodecThread(JNIEnv* jni, |
input_buffer_index, |
yuv_size_, |
current_timestamp_us_); |
- CHECK_EXCEPTION(jni); |
+ if (CheckException(jni)) { |
+ ALOGE << "Exception in encode buffer."; |
+ ProcessHWErrorOnCodecThread(true /* reset_if_fallback_unavailable */); |
+ return false; |
+ } |
return encode_status; |
} |
@@ -845,7 +918,11 @@ bool MediaCodecVideoEncoder::EncodeTextureOnCodecThread(JNIEnv* jni, |
handle->oes_texture_id, |
sampling_matrix, |
current_timestamp_us_); |
- CHECK_EXCEPTION(jni); |
+ if (CheckException(jni)) { |
+ ALOGE << "Exception in encode texture."; |
+ ProcessHWErrorOnCodecThread(true /* reset_if_fallback_unavailable */); |
+ return false; |
+ } |
return encode_status; |
} |
@@ -872,7 +949,11 @@ int32_t MediaCodecVideoEncoder::ReleaseOnCodecThread() { |
jni->DeleteGlobalRef(input_buffers_[i]); |
input_buffers_.clear(); |
jni->CallVoidMethod(*j_media_codec_video_encoder_, j_release_method_); |
- CHECK_EXCEPTION(jni); |
+ if (CheckException(jni)) { |
+ ALOGE << "Exception in release."; |
+ ProcessHWErrorOnCodecThread(false /* reset_if_fallback_unavailable */); |
+ return WEBRTC_VIDEO_CODEC_ERROR; |
+ } |
rtc::MessageQueueManager::Clear(this); |
inited_ = false; |
use_surface_ = false; |
@@ -883,6 +964,8 @@ int32_t MediaCodecVideoEncoder::ReleaseOnCodecThread() { |
int32_t MediaCodecVideoEncoder::SetRatesOnCodecThread(uint32_t new_bit_rate, |
uint32_t frame_rate) { |
RTC_DCHECK(codec_thread_checker_.CalledOnValidThread()); |
+ if (sw_fallback_required_) |
+ return WEBRTC_VIDEO_CODEC_OK; |
frame_rate = (frame_rate < MAX_ALLOWED_VIDEO_FPS) ? |
frame_rate : MAX_ALLOWED_VIDEO_FPS; |
if (last_set_bitrate_kbps_ == new_bit_rate && |
@@ -904,10 +987,10 @@ int32_t MediaCodecVideoEncoder::SetRatesOnCodecThread(uint32_t new_bit_rate, |
j_set_rates_method_, |
last_set_bitrate_kbps_, |
last_set_fps_); |
- CHECK_EXCEPTION(jni); |
- if (!ret) { |
- ResetCodecOnCodecThread(); |
- return WEBRTC_VIDEO_CODEC_ERROR; |
+ if (CheckException(jni) || !ret) { |
+ ProcessHWErrorOnCodecThread(true /* reset_if_fallback_unavailable */); |
+ return sw_fallback_required_ ? WEBRTC_VIDEO_CODEC_OK |
+ : WEBRTC_VIDEO_CODEC_ERROR; |
} |
return WEBRTC_VIDEO_CODEC_OK; |
} |
@@ -943,7 +1026,11 @@ bool MediaCodecVideoEncoder::DeliverPendingOutputs(JNIEnv* jni) { |
while (true) { |
jobject j_output_buffer_info = jni->CallObjectMethod( |
*j_media_codec_video_encoder_, j_dequeue_output_buffer_method_); |
- CHECK_EXCEPTION(jni); |
+ if (CheckException(jni)) { |
+ ALOGE << "Exception in set dequeue output buffer."; |
+ ProcessHWErrorOnCodecThread(true /* reset_if_fallback_unavailable */); |
+ return WEBRTC_VIDEO_CODEC_ERROR; |
+ } |
if (IsNull(jni, j_output_buffer_info)) { |
break; |
} |
@@ -951,7 +1038,7 @@ bool MediaCodecVideoEncoder::DeliverPendingOutputs(JNIEnv* jni) { |
int output_buffer_index = |
GetOutputBufferInfoIndex(jni, j_output_buffer_info); |
if (output_buffer_index == -1) { |
- ResetCodecOnCodecThread(); |
+ ProcessHWErrorOnCodecThread(true /* reset_if_fallback_unavailable */); |
return false; |
} |
@@ -979,7 +1066,11 @@ bool MediaCodecVideoEncoder::DeliverPendingOutputs(JNIEnv* jni) { |
size_t payload_size = jni->GetDirectBufferCapacity(j_output_buffer); |
uint8_t* payload = reinterpret_cast<uint8_t*>( |
jni->GetDirectBufferAddress(j_output_buffer)); |
- CHECK_EXCEPTION(jni); |
+ if (CheckException(jni)) { |
+ ALOGE << "Exception in get direct buffer address."; |
+ ProcessHWErrorOnCodecThread(true /* reset_if_fallback_unavailable */); |
+ return WEBRTC_VIDEO_CODEC_ERROR; |
+ } |
// Callback - return encoded frame. |
int32_t callback_status = 0; |
@@ -1079,7 +1170,7 @@ bool MediaCodecVideoEncoder::DeliverPendingOutputs(JNIEnv* jni) { |
ALOGE << "Data:" << image->_buffer[0] << " " << image->_buffer[1] |
<< " " << image->_buffer[2] << " " << image->_buffer[3] |
<< " " << image->_buffer[4] << " " << image->_buffer[5]; |
- ResetCodecOnCodecThread(); |
+ ProcessHWErrorOnCodecThread(true /* reset_if_fallback_unavailable */); |
return false; |
} |
scPositions[scPositionsLength] = payload_size; |
@@ -1100,9 +1191,8 @@ bool MediaCodecVideoEncoder::DeliverPendingOutputs(JNIEnv* jni) { |
bool success = jni->CallBooleanMethod(*j_media_codec_video_encoder_, |
j_release_output_buffer_method_, |
output_buffer_index); |
- CHECK_EXCEPTION(jni); |
- if (!success) { |
- ResetCodecOnCodecThread(); |
+ if (CheckException(jni) || !success) { |
+ ProcessHWErrorOnCodecThread(true /* reset_if_fallback_unavailable */); |
return false; |
} |