Add HW fallback option to software encoding.

Permits falling back to software encoding for unsupported resolutions.

BUG=chromium:475116, chromium:487934
R=mflodman@webrtc.org, stefan@webrtc.org

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

Cr-Commit-Position: refs/heads/master@{#9227}
diff --git a/talk/media/webrtc/webrtcvideoengine2.cc b/talk/media/webrtc/webrtcvideoengine2.cc
index a7fc6bd..7d8cc02 100644
--- a/talk/media/webrtc/webrtcvideoengine2.cc
+++ b/talk/media/webrtc/webrtcvideoengine2.cc
@@ -1634,6 +1634,21 @@
       codec_settings(codec_settings) {
 }
 
+WebRtcVideoChannel2::WebRtcVideoSendStream::AllocatedEncoder::AllocatedEncoder(
+    webrtc::VideoEncoder* encoder,
+    webrtc::VideoCodecType type,
+    bool external)
+    : encoder(encoder),
+      external_encoder(nullptr),
+      type(type),
+      external(external) {
+  if (external) {
+    external_encoder = encoder;
+    this->encoder =
+        new webrtc::VideoEncoderSoftwareFallbackWrapper(type, encoder);
+  }
+}
+
 WebRtcVideoChannel2::WebRtcVideoSendStream::WebRtcVideoSendStream(
     webrtc::Call* call,
     WebRtcVideoEncoderFactory* external_encoder_factory,
@@ -1885,10 +1900,9 @@
 void WebRtcVideoChannel2::WebRtcVideoSendStream::DestroyVideoEncoder(
     AllocatedEncoder* encoder) {
   if (encoder->external) {
-    external_encoder_factory_->DestroyVideoEncoder(encoder->encoder);
-  } else {
-    delete encoder->encoder;
+    external_encoder_factory_->DestroyVideoEncoder(encoder->external_encoder);
   }
+  delete encoder->encoder;
 }
 
 void WebRtcVideoChannel2::WebRtcVideoSendStream::SetCodecAndOptions(
diff --git a/talk/media/webrtc/webrtcvideoengine2.h b/talk/media/webrtc/webrtcvideoengine2.h
index 8d24f29..5897309 100644
--- a/talk/media/webrtc/webrtcvideoengine2.h
+++ b/talk/media/webrtc/webrtcvideoengine2.h
@@ -321,9 +321,9 @@
     struct AllocatedEncoder {
       AllocatedEncoder(webrtc::VideoEncoder* encoder,
                        webrtc::VideoCodecType type,
-                       bool external)
-          : encoder(encoder), type(type), external(external) {}
+                       bool external);
       webrtc::VideoEncoder* encoder;
+      webrtc::VideoEncoder* external_encoder;
       webrtc::VideoCodecType type;
       bool external;
     };
diff --git a/webrtc/video/BUILD.gn b/webrtc/video/BUILD.gn
index 02c56a2..0504dbe 100644
--- a/webrtc/video/BUILD.gn
+++ b/webrtc/video/BUILD.gn
@@ -22,6 +22,7 @@
     "transport_adapter.cc",
     "transport_adapter.h",
     "video_decoder.cc",
+    "video_encoder.cc",
     "video_receive_stream.cc",
     "video_receive_stream.h",
     "video_send_stream.cc",
diff --git a/webrtc/video/call.cc b/webrtc/video/call.cc
index f19c3b4..37f9f2d 100644
--- a/webrtc/video/call.cc
+++ b/webrtc/video/call.cc
@@ -36,16 +36,6 @@
 #include "webrtc/video/video_send_stream.h"
 
 namespace webrtc {
-VideoEncoder* VideoEncoder::Create(VideoEncoder::EncoderType codec_type) {
-  switch (codec_type) {
-    case kVp8:
-      return VP8Encoder::Create();
-    case kVp9:
-      return VP9Encoder::Create();
-  }
-  RTC_NOTREACHED();
-  return nullptr;
-}
 
 const int Call::Config::kDefaultStartBitrateBps = 300000;
 
diff --git a/webrtc/video/video_encoder.cc b/webrtc/video/video_encoder.cc
new file mode 100644
index 0000000..6931856
--- /dev/null
+++ b/webrtc/video/video_encoder.cc
@@ -0,0 +1,125 @@
+/*
+ *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/video_encoder.h"
+
+#include "webrtc/base/checks.h"
+#include "webrtc/modules/video_coding/codecs/vp8/include/vp8.h"
+#include "webrtc/modules/video_coding/codecs/vp9/include/vp9.h"
+#include "webrtc/system_wrappers/interface/logging.h"
+
+namespace webrtc {
+VideoEncoder* VideoEncoder::Create(VideoEncoder::EncoderType codec_type) {
+  switch (codec_type) {
+    case kVp8:
+      return VP8Encoder::Create();
+    case kVp9:
+      return VP9Encoder::Create();
+    case kUnsupportedCodec:
+      RTC_NOTREACHED();
+      return nullptr;
+  }
+  RTC_NOTREACHED();
+  return nullptr;
+}
+
+VideoEncoder::EncoderType CodecToEncoderType(VideoCodecType codec_type) {
+  switch (codec_type) {
+    case kVideoCodecVP8:
+      return VideoEncoder::kVp8;
+    case kVideoCodecVP9:
+      return VideoEncoder::kVp9;
+    default:
+      return VideoEncoder::kUnsupportedCodec;
+  }
+}
+
+VideoEncoderSoftwareFallbackWrapper::VideoEncoderSoftwareFallbackWrapper(
+    VideoCodecType codec_type,
+    webrtc::VideoEncoder* encoder)
+    : encoder_type_(CodecToEncoderType(codec_type)),
+      encoder_(encoder),
+      callback_(nullptr) {
+}
+
+int32_t VideoEncoderSoftwareFallbackWrapper::InitEncode(
+    const VideoCodec* codec_settings,
+    int32_t number_of_cores,
+    size_t max_payload_size) {
+  int32_t ret =
+      encoder_->InitEncode(codec_settings, number_of_cores, max_payload_size);
+  if (ret == WEBRTC_VIDEO_CODEC_OK || encoder_type_ == kUnsupportedCodec) {
+    fallback_encoder_.reset();
+    if (callback_)
+      encoder_->RegisterEncodeCompleteCallback(callback_);
+    return ret;
+  }
+  // Try to instantiate software codec.
+  fallback_encoder_.reset(VideoEncoder::Create(encoder_type_));
+  if (fallback_encoder_->InitEncode(codec_settings, number_of_cores,
+                                    max_payload_size) ==
+      WEBRTC_VIDEO_CODEC_OK) {
+    if (callback_)
+      fallback_encoder_->RegisterEncodeCompleteCallback(callback_);
+    return WEBRTC_VIDEO_CODEC_OK;
+  }
+  // Software encoder failed, reset and use original return code.
+  fallback_encoder_.reset();
+  return ret;
+}
+
+int32_t VideoEncoderSoftwareFallbackWrapper::RegisterEncodeCompleteCallback(
+    EncodedImageCallback* callback) {
+  callback_ = callback;
+  int32_t ret = encoder_->RegisterEncodeCompleteCallback(callback);
+  if (fallback_encoder_)
+    return fallback_encoder_->RegisterEncodeCompleteCallback(callback);
+  return ret;
+}
+
+int32_t VideoEncoderSoftwareFallbackWrapper::Release() {
+  if (fallback_encoder_)
+    return fallback_encoder_->Release();
+  return encoder_->Release();
+}
+
+int32_t VideoEncoderSoftwareFallbackWrapper::Encode(
+    const I420VideoFrame& frame,
+    const CodecSpecificInfo* codec_specific_info,
+    const std::vector<VideoFrameType>* frame_types) {
+  if (fallback_encoder_)
+    return fallback_encoder_->Encode(frame, codec_specific_info, frame_types);
+  return encoder_->Encode(frame, codec_specific_info, frame_types);
+}
+
+int32_t VideoEncoderSoftwareFallbackWrapper::SetChannelParameters(
+    uint32_t packet_loss,
+    int64_t rtt) {
+  int32_t ret = encoder_->SetChannelParameters(packet_loss, rtt);
+  if (fallback_encoder_)
+    return fallback_encoder_->SetChannelParameters(packet_loss, rtt);
+  return ret;
+}
+
+int32_t VideoEncoderSoftwareFallbackWrapper::SetRates(uint32_t bitrate,
+                                                      uint32_t framerate) {
+  int32_t ret = encoder_->SetRates(bitrate, framerate);
+  if (fallback_encoder_)
+    return fallback_encoder_->SetRates(bitrate, framerate);
+  return ret;
+}
+
+void VideoEncoderSoftwareFallbackWrapper::OnDroppedFrame() {
+  if (fallback_encoder_)
+    return fallback_encoder_->OnDroppedFrame();
+  return encoder_->OnDroppedFrame();
+}
+
+}  // namespace webrtc
diff --git a/webrtc/video/video_encoder_unittest.cc b/webrtc/video/video_encoder_unittest.cc
new file mode 100644
index 0000000..acfc4de
--- /dev/null
+++ b/webrtc/video/video_encoder_unittest.cc
@@ -0,0 +1,200 @@
+/*
+ *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/video_encoder.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/modules/video_coding/codecs/interface/video_error_codes.h"
+
+namespace webrtc {
+
+const size_t kMaxPayloadSize = 800;
+
+class VideoEncoderSoftwareFallbackWrapperTest : public ::testing::Test {
+ protected:
+  VideoEncoderSoftwareFallbackWrapperTest()
+      : fallback_wrapper_(kVideoCodecVP8, &fake_encoder_) {}
+
+  class CountingFakeEncoder : public VideoEncoder {
+   public:
+    int32_t InitEncode(const VideoCodec* codec_settings,
+                       int32_t number_of_cores,
+                       size_t max_payload_size) override {
+      ++init_encode_count_;
+      return init_encode_return_code_;
+    }
+    int32_t Encode(const I420VideoFrame& frame,
+                   const CodecSpecificInfo* codec_specific_info,
+                   const std::vector<VideoFrameType>* frame_types) override {
+      ++encode_count_;
+      return WEBRTC_VIDEO_CODEC_OK;
+    }
+
+    int32_t RegisterEncodeCompleteCallback(
+        EncodedImageCallback* callback) override {
+      encode_complete_callback_ = callback;
+      return WEBRTC_VIDEO_CODEC_OK;
+    }
+
+    int32_t Release() override {
+      ++release_count_;
+      return WEBRTC_VIDEO_CODEC_OK;
+    }
+
+    int32_t SetChannelParameters(uint32_t packet_loss, int64_t rtt) override {
+      ++set_channel_parameters_count_;
+      return WEBRTC_VIDEO_CODEC_OK;
+    }
+
+    int32_t SetRates(uint32_t bitrate, uint32_t framerate) override {
+      ++set_rates_count_;
+      return WEBRTC_VIDEO_CODEC_OK;
+    }
+
+    void OnDroppedFrame() override { ++on_dropped_frame_count_; }
+
+    int init_encode_count_ = 0;
+    int32_t init_encode_return_code_ = WEBRTC_VIDEO_CODEC_OK;
+    int encode_count_ = 0;
+    EncodedImageCallback* encode_complete_callback_ = nullptr;
+    int release_count_ = 0;
+    int set_channel_parameters_count_ = 0;
+    int set_rates_count_ = 0;
+    int on_dropped_frame_count_ = 0;
+  };
+
+  class FakeEncodedImageCallback : public EncodedImageCallback {
+   public:
+    int32_t Encoded(const EncodedImage& encoded_image,
+                    const CodecSpecificInfo* codec_specific_info,
+                    const RTPFragmentationHeader* fragmentation) override {
+      return ++callback_count_;
+    }
+    int callback_count_ = 0;
+  };
+
+  void UtilizeFallbackEncoder();
+
+  FakeEncodedImageCallback callback_;
+  CountingFakeEncoder fake_encoder_;
+  VideoEncoderSoftwareFallbackWrapper fallback_wrapper_;
+  VideoCodec codec_ = {};
+  I420VideoFrame frame_;
+};
+
+void VideoEncoderSoftwareFallbackWrapperTest::UtilizeFallbackEncoder() {
+  static const int kWidth = 320;
+  static const int kHeight = 240;
+  fallback_wrapper_.RegisterEncodeCompleteCallback(&callback_);
+  EXPECT_EQ(&callback_, fake_encoder_.encode_complete_callback_);
+
+  // Register with failing fake encoder. Should succeed with VP8 fallback.
+  codec_.codecType = kVideoCodecVP8;
+  codec_.maxFramerate = 30;
+  codec_.width = kWidth;
+  codec_.height = kHeight;
+  fake_encoder_.init_encode_return_code_ = WEBRTC_VIDEO_CODEC_ERROR;
+  EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
+            fallback_wrapper_.InitEncode(&codec_, 2, kMaxPayloadSize));
+  EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, fallback_wrapper_.SetRates(300, 30));
+
+  frame_.CreateEmptyFrame(kWidth, kHeight, kWidth, (kWidth + 1) / 2,
+                          (kWidth + 1) / 2);
+  memset(frame_.buffer(webrtc::kYPlane), 16,
+         frame_.allocated_size(webrtc::kYPlane));
+  memset(frame_.buffer(webrtc::kUPlane), 128,
+         frame_.allocated_size(webrtc::kUPlane));
+  memset(frame_.buffer(webrtc::kVPlane), 128,
+         frame_.allocated_size(webrtc::kVPlane));
+
+  std::vector<VideoFrameType> types(1, kKeyFrame);
+  EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
+            fallback_wrapper_.Encode(frame_, nullptr, &types));
+  EXPECT_EQ(0, fake_encoder_.encode_count_);
+  EXPECT_GT(callback_.callback_count_, 0);
+}
+
+TEST_F(VideoEncoderSoftwareFallbackWrapperTest, InitializesEncoder) {
+  VideoCodec codec = {};
+  fallback_wrapper_.InitEncode(&codec, 2, kMaxPayloadSize);
+  EXPECT_EQ(1, fake_encoder_.init_encode_count_);
+}
+
+TEST_F(VideoEncoderSoftwareFallbackWrapperTest, CanUtilizeFallbackEncoder) {
+  UtilizeFallbackEncoder();
+  EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, fallback_wrapper_.Release());
+}
+
+TEST_F(VideoEncoderSoftwareFallbackWrapperTest,
+       InternalEncoderNotReleasedDuringFallback) {
+  UtilizeFallbackEncoder();
+  EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, fallback_wrapper_.Release());
+  EXPECT_EQ(0, fake_encoder_.release_count_);
+}
+
+TEST_F(VideoEncoderSoftwareFallbackWrapperTest,
+       InternalEncoderNotEncodingDuringFallback) {
+  UtilizeFallbackEncoder();
+  EXPECT_EQ(0, fake_encoder_.encode_count_);
+
+  EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, fallback_wrapper_.Release());
+}
+
+TEST_F(VideoEncoderSoftwareFallbackWrapperTest,
+       CanRegisterCallbackWhileUsingFallbackEncoder) {
+  UtilizeFallbackEncoder();
+  // Registering an encode-complete callback should still work when fallback
+  // encoder is being used.
+  FakeEncodedImageCallback callback2;
+  fallback_wrapper_.RegisterEncodeCompleteCallback(&callback2);
+  EXPECT_EQ(&callback2, fake_encoder_.encode_complete_callback_);
+
+  // Encoding a frame using the fallback should arrive at the new callback.
+  std::vector<VideoFrameType> types(1, kKeyFrame);
+  frame_.set_timestamp(frame_.timestamp() + 1000);
+  EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
+            fallback_wrapper_.Encode(frame_, nullptr, &types));
+
+  EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, fallback_wrapper_.Release());
+}
+
+TEST_F(VideoEncoderSoftwareFallbackWrapperTest,
+       SetChannelParametersForwardedDuringFallback) {
+  UtilizeFallbackEncoder();
+  EXPECT_EQ(0, fake_encoder_.set_channel_parameters_count_);
+  fallback_wrapper_.SetChannelParameters(1, 1);
+  EXPECT_EQ(1, fake_encoder_.set_channel_parameters_count_);
+  EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, fallback_wrapper_.Release());
+}
+
+TEST_F(VideoEncoderSoftwareFallbackWrapperTest,
+       SetRatesForwardedDuringFallback) {
+  UtilizeFallbackEncoder();
+  EXPECT_EQ(1, fake_encoder_.set_rates_count_);
+  fallback_wrapper_.SetRates(1, 1);
+  EXPECT_EQ(2, fake_encoder_.set_rates_count_);
+  EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, fallback_wrapper_.Release());
+}
+
+TEST_F(VideoEncoderSoftwareFallbackWrapperTest,
+       OnDroppedFrameForwardedWithoutFallback) {
+  fallback_wrapper_.OnDroppedFrame();
+  EXPECT_EQ(1, fake_encoder_.on_dropped_frame_count_);
+}
+
+TEST_F(VideoEncoderSoftwareFallbackWrapperTest,
+       OnDroppedFrameNotForwardedDuringFallback) {
+  UtilizeFallbackEncoder();
+  fallback_wrapper_.OnDroppedFrame();
+  EXPECT_EQ(0, fake_encoder_.on_dropped_frame_count_);
+  EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, fallback_wrapper_.Release());
+}
+
+}  // namespace webrtc
diff --git a/webrtc/video/webrtc_video.gypi b/webrtc/video/webrtc_video.gypi
index 37047c6..545f284 100644
--- a/webrtc/video/webrtc_video.gypi
+++ b/webrtc/video/webrtc_video.gypi
@@ -23,6 +23,7 @@
       'video/transport_adapter.cc',
       'video/transport_adapter.h',
       'video/video_decoder.cc',
+      'video/video_encoder.cc',
       'video/video_receive_stream.cc',
       'video/video_receive_stream.h',
       'video/video_send_stream.cc',
diff --git a/webrtc/video_encoder.h b/webrtc/video_encoder.h
index ce2569b..832f40f 100644
--- a/webrtc/video_encoder.h
+++ b/webrtc/video_encoder.h
@@ -39,6 +39,7 @@
   enum EncoderType {
     kVp8,
     kVp9,
+    kUnsupportedCodec,
   };
 
   static VideoEncoder* Create(EncoderType codec_type);
@@ -125,5 +126,36 @@
   virtual void OnDroppedFrame() {};
 };
 
+// Class used to wrap external VideoEncoders to provide a fallback option on
+// software encoding when a hardware encoder fails to encode a stream due to
+// hardware restrictions, such as max resolution.
+class VideoEncoderSoftwareFallbackWrapper : public VideoEncoder {
+ public:
+  VideoEncoderSoftwareFallbackWrapper(VideoCodecType codec_type,
+                                      webrtc::VideoEncoder* encoder);
+
+  int32_t InitEncode(const VideoCodec* codec_settings,
+                     int32_t number_of_cores,
+                     size_t max_payload_size) override;
+
+  int32_t RegisterEncodeCompleteCallback(
+      EncodedImageCallback* callback) override;
+
+  int32_t Release() override;
+  int32_t Encode(const I420VideoFrame& frame,
+                 const CodecSpecificInfo* codec_specific_info,
+                 const std::vector<VideoFrameType>* frame_types) override;
+  int32_t SetChannelParameters(uint32_t packet_loss, int64_t rtt) override;
+
+  int32_t SetRates(uint32_t bitrate, uint32_t framerate) override;
+  void OnDroppedFrame() override;
+
+ private:
+  const EncoderType encoder_type_;
+  webrtc::VideoEncoder* const encoder_;
+
+  rtc::scoped_ptr<webrtc::VideoEncoder> fallback_encoder_;
+  EncodedImageCallback* callback_;
+};
 }  // namespace webrtc
 #endif  // WEBRTC_VIDEO_ENCODER_H_
diff --git a/webrtc/webrtc_tests.gypi b/webrtc/webrtc_tests.gypi
index a47129a..6a4dec9 100644
--- a/webrtc/webrtc_tests.gypi
+++ b/webrtc/webrtc_tests.gypi
@@ -151,6 +151,7 @@
         'video/end_to_end_tests.cc',
         'video/send_statistics_proxy_unittest.cc',
         'video/video_decoder_unittest.cc',
+        'video/video_encoder_unittest.cc',
         'video/video_send_stream_tests.cc',
       ],
       'dependencies': [