Switch to SW video decoder on Android after getting 2 or more
critical errors from HW decoder.

BUG=410730
R=tkchin@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/23819004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@7368 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/talk/app/webrtc/java/jni/peerconnection_jni.cc b/talk/app/webrtc/java/jni/peerconnection_jni.cc
index 92acd7b..f482848 100644
--- a/talk/app/webrtc/java/jni/peerconnection_jni.cc
+++ b/talk/app/webrtc/java/jni/peerconnection_jni.cc
@@ -1993,6 +1993,7 @@
   bool key_frame_required_;
   bool inited_;
   bool use_surface_;
+  int error_count_;
   VideoCodec codec_;
   I420VideoFrame decoded_image_;
   NativeHandleImpl native_handle_;
@@ -2072,6 +2073,7 @@
 MediaCodecVideoDecoder::MediaCodecVideoDecoder(JNIEnv* jni)
   : key_frame_required_(true),
     inited_(false),
+    error_count_(0),
     codec_thread_(new Thread()),
     j_media_codec_video_decoder_class_(
         jni,
@@ -2089,7 +2091,7 @@
 
   j_init_decode_method_ = GetMethodID(
       jni, *j_media_codec_video_decoder_class_, "initDecode",
-      "(IIZLandroid/opengl/EGLContext;)Z");
+      "(IIZZLandroid/opengl/EGLContext;)Z");
   j_release_method_ =
       GetMethodID(jni, *j_media_codec_video_decoder_class_, "release", "()V");
   j_dequeue_input_buffer_method_ = GetMethodID(
@@ -2176,13 +2178,19 @@
   CheckOnCodecThread();
   JNIEnv* jni = AttachCurrentThreadIfNeeded();
   ScopedLocalRefFrame local_ref_frame(jni);
-  ALOGD("InitDecodeOnCodecThread: %d x %d. fps: %d",
-      codec_.width, codec_.height, codec_.maxFramerate);
+  ALOGD("InitDecodeOnCodecThread: %d x %d. Fps: %d. Errors: %d",
+      codec_.width, codec_.height, codec_.maxFramerate, error_count_);
+  bool use_sw_codec = false;
+  if (error_count_ > 1) {
+    // If more than one critical errors happen for HW codec, switch to SW codec.
+    use_sw_codec = true;
+  }
 
   bool success = jni->CallBooleanMethod(*j_media_codec_video_decoder_,
                                        j_init_decode_method_,
                                        codec_.width,
                                        codec_.height,
+                                       use_sw_codec,
                                        use_surface_,
                                        render_egl_context_);
   CHECK_EXCEPTION(jni);
@@ -2320,11 +2328,13 @@
   if (frames_received_ > frames_decoded_ + max_pending_frames_) {
     ALOGV("Wait for output...");
     if (!DeliverPendingOutputs(jni, kMediaCodecTimeoutMs * 1000)) {
+      error_count_++;
       Reset();
       return WEBRTC_VIDEO_CODEC_ERROR;
     }
     if (frames_received_ > frames_decoded_ + max_pending_frames_) {
       ALOGE("Output buffer dequeue timeout");
+      error_count_++;
       Reset();
       return WEBRTC_VIDEO_CODEC_ERROR;
     }
@@ -2336,6 +2346,7 @@
   CHECK_EXCEPTION(jni);
   if (j_input_buffer_index < 0) {
     ALOGE("dequeueInputBuffer error");
+    error_count_++;
     Reset();
     return WEBRTC_VIDEO_CODEC_ERROR;
   }
@@ -2350,6 +2361,7 @@
   if (buffer_capacity < inputImage._length) {
     ALOGE("Input frame size %d is bigger than buffer size %d.",
         inputImage._length, buffer_capacity);
+    error_count_++;
     Reset();
     return WEBRTC_VIDEO_CODEC_ERROR;
   }
@@ -2374,6 +2386,7 @@
   CHECK_EXCEPTION(jni);
   if (!success) {
     ALOGE("queueInputBuffer error");
+    error_count_++;
     Reset();
     return WEBRTC_VIDEO_CODEC_ERROR;
   }
@@ -2381,6 +2394,7 @@
   // Try to drain the decoder
   if (!DeliverPendingOutputs(jni, 0)) {
     ALOGE("DeliverPendingOutputs error");
+    error_count_++;
     Reset();
     return WEBRTC_VIDEO_CODEC_ERROR;
   }
@@ -2410,7 +2424,6 @@
       GetIntField(jni, j_decoder_output_buffer_info, j_info_index_field_);
   if (output_buffer_index < 0) {
     ALOGE("dequeueOutputBuffer error : %d", output_buffer_index);
-    Reset();
     return false;
   }
   int output_buffer_offset =
@@ -2435,7 +2448,6 @@
   if (!use_surface_) {
     if (output_buffer_size < width * height * 3 / 2) {
       ALOGE("Insufficient output buffer size: %d", output_buffer_size);
-      Reset();
       return false;
     }
     jobjectArray output_buffers = reinterpret_cast<jobjectArray>(GetObjectField(
@@ -2494,7 +2506,6 @@
   CHECK_EXCEPTION(jni);
   if (!success) {
     ALOGE("releaseOutputBuffer error");
-    Reset();
     return false;
   }
 
@@ -2561,7 +2572,10 @@
   CHECK(!msg->pdata) << "Unexpected message!";
   CheckOnCodecThread();
 
-  DeliverPendingOutputs(jni, 0);
+  if (!DeliverPendingOutputs(jni, 0)) {
+    error_count_++;
+    Reset();
+  }
   codec_thread_->PostDelayed(kMediaCodecPollMs, this);
 }
 
diff --git a/talk/app/webrtc/java/src/org/webrtc/MediaCodecVideoDecoder.java b/talk/app/webrtc/java/src/org/webrtc/MediaCodecVideoDecoder.java
index 9280743..fcb3d3f 100644
--- a/talk/app/webrtc/java/src/org/webrtc/MediaCodecVideoDecoder.java
+++ b/talk/app/webrtc/java/src/org/webrtc/MediaCodecVideoDecoder.java
@@ -68,6 +68,9 @@
   // List of supported HW VP8 decoders.
   private static final String[] supportedHwCodecPrefixes =
     {"OMX.qcom.", "OMX.Nvidia." };
+  // List of supported SW VP8 decoders.
+  private static final String[] supportedSwCodecPrefixes =
+    {"OMX.google."};
   // NV12 color format supported by QCOM codec, but not declared in MediaCodec -
   // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
   private static final int
@@ -96,7 +99,7 @@
 
   private MediaCodecVideoDecoder() { }
 
-  // Helper struct for findVp8HwDecoder() below.
+  // Helper struct for findVp8Decoder() below.
   private static class DecoderProperties {
     public DecoderProperties(String codecName, int colorFormat) {
       this.codecName = codecName;
@@ -106,10 +109,14 @@
     public final int colorFormat;  // Color format supported by codec.
   }
 
-  private static DecoderProperties findVp8HwDecoder() {
-    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
+  private static DecoderProperties findVp8Decoder(boolean useSwCodec) {
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
       return null; // MediaCodec.setParameters is missing.
-
+    }
+    String[] supportedCodecPrefixes = supportedHwCodecPrefixes;
+    if (useSwCodec) {
+      supportedCodecPrefixes = supportedSwCodecPrefixes;
+    }
     for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
       MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
       if (info.isEncoder()) {
@@ -127,10 +134,10 @@
       }
       Log.d(TAG, "Found candidate decoder " + name);
 
-      // Check if this is supported HW decoder.
+      // Check if this is supported decoder.
       boolean supportedCodec = false;
-      for (String hwCodecPrefix : supportedHwCodecPrefixes) {
-        if (name.startsWith(hwCodecPrefix)) {
+      for (String codecPrefix : supportedCodecPrefixes) {
+        if (name.startsWith(codecPrefix)) {
           supportedCodec = true;
           break;
         }
@@ -160,7 +167,7 @@
   }
 
   private static boolean isPlatformSupported() {
-    return findVp8HwDecoder() != null;
+    return findVp8Decoder(false) != null;
   }
 
   private void checkOnMediaCodecThread() {
@@ -265,21 +272,21 @@
     }
   }
 
-  private boolean initDecode(int width, int height, boolean useSurface,
-      EGLContext sharedContext) {
+  private boolean initDecode(int width, int height, boolean useSwCodec,
+      boolean useSurface, EGLContext sharedContext) {
     if (mediaCodecThread != null) {
       throw new RuntimeException("Forgot to release()?");
     }
     if (useSurface && sharedContext == null) {
       throw new RuntimeException("No shared EGL context.");
     }
-    DecoderProperties properties = findVp8HwDecoder();
+    DecoderProperties properties = findVp8Decoder(useSwCodec);
     if (properties == null) {
       throw new RuntimeException("Cannot find HW VP8 decoder");
     }
     Log.d(TAG, "Java initDecode: " + width + " x " + height +
         ". Color: 0x" + Integer.toHexString(properties.colorFormat) +
-        ". Use Surface: " + useSurface );
+        ". Use Surface: " + useSurface + ". Use SW codec: " + useSwCodec);
     if (sharedContext != null) {
       Log.d(TAG, "Decoder shared EGL Context: " + sharedContext);
     }