Merge tag 'v1.15.2' into merge-wigeon-2

Release v1.15.2 Wigeon Duck

2025-05-28 v1.15.2 "Wigeon Duck"

  This release fixes CVE-2025-5283 (bug webm:413411335), and is ABI compatible
  with the previous release.

Change-Id: I4ac1e0e96602f26cb0e7883dcc87f3b232bc0554
diff --git a/README b/README
index 9f17a63..e197561 100644
--- a/README
+++ b/README
@@ -7,7 +7,7 @@
 
   1. Prerequisites
 
-    * All x86 targets require the Yasm[1] assembler be installed[2].
+    * All x86 targets require the NASM[0] or Yasm[1] assembler be installed[2].
     * All Windows builds require that Cygwin[3] or MSYS2[4] be installed.
     * Building the documentation requires Doxygen[5]. If you do not
       have this package, the install-docs option will be disabled.
@@ -18,11 +18,15 @@
       source[7]. These requirements are optional if not running the unit
       tests.
 
+    [0]: https://www.nasm.us/
     [1]: http://www.tortall.net/projects/yasm
     [2]: For Visual Studio the base yasm binary (not vsyasm) should be in the
          PATH for Visual Studio. For VS2017 it is sufficient to rename
          yasm-<version>-<arch>.exe to yasm.exe and place it in:
          Program Files (x86)/Microsoft Visual Studio/2017/<level>/Common7/Tools/
+         The MSYS2 version of the yasm binary can also be used and avoids an
+         issue caused by a missing Visual C++ Redistributable install (Visual
+         Studio 2010, MSVCR100.dll).
     [3]: http://www.cygwin.com
     [4]: http://www.msys2.org/
     [5]: http://www.doxygen.org
diff --git a/args.c b/args.c
index 0fb87d9..a1ab054 100644
--- a/args.c
+++ b/args.c
@@ -24,7 +24,7 @@
 extern void die(const char *fmt, ...);
 #endif
 
-struct arg arg_init(char **argv) {
+static struct arg arg_init(char **argv) {
   struct arg a;
 
   a.argv = argv;
diff --git a/args.h b/args.h
index aae8ec0..acab48b 100644
--- a/args.h
+++ b/args.h
@@ -45,12 +45,12 @@
 #define ARG_DEF_LIST_END \
   { 0 }
 
-struct arg arg_init(char **argv);
 int arg_match(struct arg *arg_, const struct arg_def *def, char **argv);
 const char *arg_next(struct arg *arg);
 void arg_show_usage(FILE *fp, const struct arg_def *const *defs);
 char **argv_dup(int argc, const char **argv);
 
+// Note: arg_match() must be called before invoking these functions.
 unsigned int arg_parse_uint(const struct arg *arg);
 int arg_parse_int(const struct arg *arg);
 struct vpx_rational arg_parse_rational(const struct arg *arg);
diff --git a/build/make/Makefile b/build/make/Makefile
index 658b376..39dff82 100644
--- a/build/make/Makefile
+++ b/build/make/Makefile
@@ -150,8 +150,8 @@
 $(BUILD_PFX)%_neon_i8mm.c.o: CFLAGS += -march=armv8.2-a+dotprod+i8mm
 $(BUILD_PFX)%_sve.c.d: CFLAGS += -march=armv8.2-a+dotprod+i8mm+sve
 $(BUILD_PFX)%_sve.c.o: CFLAGS += -march=armv8.2-a+dotprod+i8mm+sve
-$(BUILD_PFX)%_sve2.c.d: CFLAGS += -march=armv9-a+sve2
-$(BUILD_PFX)%_sve2.c.o: CFLAGS += -march=armv9-a+sve2
+$(BUILD_PFX)%_sve2.c.d: CFLAGS += -march=armv9-a+i8mm+sve2
+$(BUILD_PFX)%_sve2.c.o: CFLAGS += -march=armv9-a+i8mm+sve2
 
 # POWER
 $(BUILD_PFX)%_vsx.c.d: CFLAGS += -maltivec -mvsx
diff --git a/build/make/configure.sh b/build/make/configure.sh
index cc5bf6c..d1d89aa 100644
--- a/build/make/configure.sh
+++ b/build/make/configure.sh
@@ -1229,7 +1229,8 @@
                 ;;
             esac
 
-            if [ "$(show_darwin_sdk_major_version iphoneos)" -gt 8 ]; then
+            if [ "$(show_darwin_sdk_major_version iphoneos)" -gt 8 \
+               && [ "$(show_xcode_version)" -lt 16 ]; then
               check_add_cflags -fembed-bitcode
               check_add_asflags -fembed-bitcode
               check_add_ldflags -fembed-bitcode
diff --git a/build/make/rtcd.pl b/build/make/rtcd.pl
index 9f6aad8..156199a 100755
--- a/build/make/rtcd.pl
+++ b/build/make/rtcd.pl
@@ -455,19 +455,35 @@
   common_bottom;
 }
 
+# List of architectures in low-to-high preference order.
+my @PRIORITY_ARCH = qw/
+  c
+  mmx sse sse2 sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512
+  arm_crc32 neon neon_dotprod neon_i8mm sve sve2
+  rvv
+  vsx
+  dspr2 msa
+/;
+my %PRIORITY_INDEX;
+for (my $i = 0; $i < @PRIORITY_ARCH; $i++) {
+  $PRIORITY_INDEX{$PRIORITY_ARCH[$i]} = $i;
+}
+
 #
 # Main Driver
 #
 
 &require("c");
-&require(keys %required);
+&require(sort { $PRIORITY_INDEX{$a} <=> $PRIORITY_INDEX{$b} } keys %required);
 if ($opts{arch} eq 'x86') {
   @ALL_ARCHS = filter(qw/mmx sse sse2 sse3 ssse3 sse4_1 avx avx2 avx512/);
   x86;
 } elsif ($opts{arch} eq 'x86_64') {
   @ALL_ARCHS = filter(qw/mmx sse sse2 sse3 ssse3 sse4_1 avx avx2 avx512/);
-  @REQUIRES = filter(qw/mmx sse sse2/);
-  &require(@REQUIRES);
+  if (keys %required == 0) {
+    @REQUIRES = filter(qw/mmx sse sse2/);
+    &require(@REQUIRES);
+  }
   x86;
 } elsif ($opts{arch} eq 'mips32' || $opts{arch} eq 'mips64') {
   my $have_dspr2 = 0;
@@ -505,8 +521,10 @@
   arm;
 } elsif ($opts{arch} eq 'armv8' || $opts{arch} eq 'arm64' ) {
   @ALL_ARCHS = filter(qw/neon neon_dotprod neon_i8mm sve sve2/);
-  @REQUIRES = filter(qw/neon/);
-  &require(@REQUIRES);
+  if (keys %required == 0) {
+    @REQUIRES = filter(qw/neon/);
+    &require(@REQUIRES);
+  }
   arm;
 } elsif ($opts{arch} =~ /^ppc/ ) {
   @ALL_ARCHS = filter(qw/vsx/);
diff --git a/configure b/configure
index 457bd6b..777cb84 100755
--- a/configure
+++ b/configure
@@ -308,7 +308,6 @@
     fp_mb_stats
     emulate_hardware
     non_greedy_mv
-    rate_ctrl
     collect_component_timing
 "
 CONFIG_LIST="
diff --git a/examples.mk b/examples.mk
index 48fcc29..a586799 100644
--- a/examples.mk
+++ b/examples.mk
@@ -369,6 +369,13 @@
 $(foreach proj,$(call enabled,PROJECTS),\
     $(eval $(call vcproj_template,$(proj))))
 
+# Generate a list of all enabled sources, in particular for exporting to gyp
+# based build systems.
+vpxdec_srcs.txt:
+	@echo "    [CREATE] $@"
+	@echo $(vpxdec.SRCS) | xargs -n1 echo | LC_ALL=C sort -u > $@
+CLEAN-OBJS += vpxdec_srcs.txt
+
 #
 # Documentation Rules
 #
diff --git a/examples/vp8_multi_resolution_encoder.c b/examples/vp8_multi_resolution_encoder.c
index 62d96de..60161da 100644
--- a/examples/vp8_multi_resolution_encoder.c
+++ b/examples/vp8_multi_resolution_encoder.c
@@ -25,7 +25,6 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdarg.h>
-#include <string.h>
 #include <math.h>
 #include <assert.h>
 #include <sys/time.h>
@@ -438,7 +437,7 @@
 
   /* Other-resolution encoder settings */
   for (i = 1; i < NUM_ENCODERS; i++) {
-    memcpy(&cfg[i], &cfg[0], sizeof(vpx_codec_enc_cfg_t));
+    cfg[i] = cfg[0];
 
     cfg[i].rc_target_bitrate = target_bitrate[i];
 
diff --git a/examples/vpx_temporal_svc_encoder.c b/examples/vpx_temporal_svc_encoder.c
index a800278..01badbe 100644
--- a/examples/vpx_temporal_svc_encoder.c
+++ b/examples/vpx_temporal_svc_encoder.c
@@ -607,19 +607,24 @@
 }
 
 #if ROI_MAP
-static void read_mask(FILE *mask_file, int *seg_map) {
+static int read_mask(FILE *mask_file, int *seg_map, int allowed_mask_rows,
+                     int allowed_mask_cols) {
   int mask_rows, mask_cols, i, j;
   int *map_start = seg_map;
-  fscanf(mask_file, "%d %d\n", &mask_cols, &mask_rows);
+  if (fscanf(mask_file, "%d %d\n", &mask_cols, &mask_rows) != 2) return 0;
+  if (mask_rows != allowed_mask_rows || mask_cols != allowed_mask_cols) {
+    return 0;
+  }
   for (i = 0; i < mask_rows; i++) {
     for (j = 0; j < mask_cols; j++) {
-      fscanf(mask_file, "%d ", &seg_map[j]);
+      if (fscanf(mask_file, "%d ", &seg_map[j]) != 1) return 0;
       // reverse the bit
       seg_map[j] = 1 - seg_map[j];
     }
     seg_map += mask_cols;
   }
   seg_map = map_start;
+  return 1;
 }
 #endif
 
@@ -965,12 +970,16 @@
              argv[argc - 1], frame_cnt);
     mask_file = fopen(mask_file_name, "r");
     if (mask_file != NULL) {
-      read_mask(mask_file, mask_map);
+      int mask_is_valid = read_mask(mask_file, mask_map, mask_rows, mask_cols);
       fclose(mask_file);
-      // set_roi_map(encoder->name, &cfg, &roi);
-      set_roi_skip_map(&cfg, &roi, mask_map, prev_mask_map, frame_cnt);
-      if (vpx_codec_control(&codec, VP9E_SET_ROI_MAP, &roi))
-        die_codec(&codec, "Failed to set ROI map");
+      if (mask_is_valid) {
+        // set_roi_map(encoder->name, &cfg, &roi);
+        set_roi_skip_map(&cfg, &roi, mask_map, prev_mask_map, frame_cnt);
+        if (vpx_codec_control(&codec, VP9E_SET_ROI_MAP, &roi))
+          die_codec(&codec, "Failed to set ROI map");
+      } else {
+        die_codec(&codec, "Mask input is invalid for ROI map");
+      }
     }
 #endif
     frame_avail = read_frame(&input_ctx, &raw);
diff --git a/libs.mk b/libs.mk
index 4a28b11..c7db0c1 100644
--- a/libs.mk
+++ b/libs.mk
@@ -191,16 +191,6 @@
 INSTALL-LIBS-$(CONFIG_DEBUG_LIBS) += $(LIBSUBDIR)/libvpx_g.a
 endif
 
-ifeq ($(CONFIG_VP9_ENCODER)$(CONFIG_RATE_CTRL),yesyes)
-  SIMPLE_ENCODE_SRCS := $(call enabled,CODEC_SRCS)
-  SIMPLE_ENCODE_SRCS += $(VP9_PREFIX)simple_encode.cc
-  SIMPLE_ENCODE_SRCS += $(VP9_PREFIX)simple_encode.h
-  SIMPLE_ENCODE_SRCS += ivfenc.h
-  SIMPLE_ENCODE_SRCS += ivfenc.c
-  INSTALL-SRCS-$(CONFIG_CODEC_SRCS) += $(VP9_PREFIX)simple_encode.cc
-  INSTALL-SRCS-$(CONFIG_CODEC_SRCS) += $(VP9_PREFIX)simple_encode.h
-endif
-
 CODEC_SRCS=$(call enabled,CODEC_SRCS)
 
 INSTALL-SRCS-$(CONFIG_CODEC_SRCS) += $(CODEC_SRCS)
@@ -214,6 +204,11 @@
 	@echo $(CODEC_SRCS) | xargs -n1 echo | LC_ALL=C sort -u > $@
 CLEAN-OBJS += libvpx_srcs.txt
 
+libvpxrc_srcs.txt:
+	@echo "    [CREATE] $@"
+	@echo $(RC_RTC_SRCS) | xargs -n1 echo | LC_ALL=C sort -u > $@
+CLEAN-OBJS += libvpxrc_srcs.txt
+
 # Assembly files that are included, but don't define symbols themselves.
 # Filtered out to avoid Windows build warnings.
 ASM_INCLUDES := \
@@ -419,13 +414,6 @@
   $(BUILD_PFX)libvpxrc_g.a: $(RC_RTC_OBJS)
 endif
 
-ifeq ($(CONFIG_VP9_ENCODER)$(CONFIG_RATE_CTRL),yesyes)
-  SIMPLE_ENCODE_OBJS=$(call objs,$(SIMPLE_ENCODE_SRCS))
-  OBJS-yes += $(SIMPLE_ENCODE_OBJS)
-  LIBS-yes += $(BUILD_PFX)libsimple_encode.a $(BUILD_PFX)libsimple_encode_g.a
-  $(BUILD_PFX)libsimple_encode_g.a: $(SIMPLE_ENCODE_OBJS)
-endif
-
 endif # ifeq ($(CONFIG_EXTERNAL_BUILD),yes)
 
 libvpx.ver: $(call enabled,CODEC_EXPORTS)
@@ -513,11 +501,6 @@
 RC_INTERFACE_TEST_OBJS := $(sort $(call objs,$(RC_INTERFACE_TEST_SRCS)))
 endif
 
-SIMPLE_ENCODE_TEST_BIN=./test_simple_encode$(EXE_SFX)
-SIMPLE_ENCODE_TEST_SRCS=$(call addprefix_clean,test/,\
-                        $(call enabled,SIMPLE_ENCODE_TEST_SRCS))
-SIMPLE_ENCODE_TEST_OBJS := $(sort $(call objs,$(SIMPLE_ENCODE_TEST_SRCS)))
-
 libvpx_test_srcs.txt:
 	@echo "    [CREATE] $@"
 	@echo $(LIBVPX_TEST_SRCS) | xargs -n1 echo | LC_ALL=C sort -u > $@
@@ -688,18 +671,6 @@
 endif  # RC_INTERFACE_TEST
 endif  # CONFIG_ENCODERS
 
-ifneq ($(strip $(SIMPLE_ENCODE_TEST_OBJS)),)
-$(SIMPLE_ENCODE_TEST_OBJS) $(SIMPLE_ENCODE_TEST_OBJS:.o=.d): \
-  CXXFLAGS += $(GTEST_INCLUDES)
-OBJS-yes += $(SIMPLE_ENCODE_TEST_OBJS)
-BINS-yes += $(SIMPLE_ENCODE_TEST_BIN)
-
-$(SIMPLE_ENCODE_TEST_BIN): $(TEST_LIBS) libsimple_encode.a
-$(eval $(call linkerxx_template,$(SIMPLE_ENCODE_TEST_BIN), \
-              $(SIMPLE_ENCODE_TEST_OBJS) \
-              -L. -lsimple_encode -lvpx -lgtest $(extralibs) -lm))
-endif  # SIMPLE_ENCODE_TEST
-
 endif  # CONFIG_EXTERNAL_BUILD
 
 # Install test sources only if codec source is included
diff --git a/test/convolve_test.cc b/test/convolve_test.cc
index d947179..541d8c4 100644
--- a/test/convolve_test.cc
+++ b/test/convolve_test.cc
@@ -37,6 +37,13 @@
                              const InterpKernel *filter, int x0_q4,
                              int x_step_q4, int y0_q4, int y_step_q4, int w,
                              int h);
+#if !CONFIG_REALTIME_ONLY && CONFIG_VP9_ENCODER
+typedef void (*ConvolveFunc12Tap)(const uint8_t *src, ptrdiff_t src_stride,
+                                  uint8_t *dst, ptrdiff_t dst_stride,
+                                  const InterpKernel12 *filter, int x0_q4,
+                                  int x_step_q4, int y0_q4, int y_step_q4,
+                                  int w, int h);
+#endif
 
 typedef void (*WrapperFilterBlock2d8Func)(
     const uint8_t *src_ptr, const unsigned int src_stride,
@@ -80,6 +87,25 @@
 
 typedef std::tuple<int, int, const ConvolveFunctions *> ConvolveParam;
 
+#if !CONFIG_REALTIME_ONLY && CONFIG_VP9_ENCODER
+struct ConvolveFunctions12Tap {
+  ConvolveFunctions12Tap(ConvolveFunc12Tap h12, ConvolveFunc12Tap v12,
+                         ConvolveFunc12Tap hv12, int bd)
+      : use_highbd_(bd) {
+    h12_ = h12;
+    v12_ = v12;
+    hv12_ = hv12;
+  }
+
+  ConvolveFunc12Tap h12_;
+  ConvolveFunc12Tap v12_;
+  ConvolveFunc12Tap hv12_;
+  int use_highbd_;  // 0 if high bitdepth not used, else the actual bit depth.
+};
+
+typedef std::tuple<int, int, const ConvolveFunctions12Tap *> Convolve12TapParam;
+#endif
+
 #define ALL_SIZES(convolve_fn)                                            \
   make_tuple(4, 4, &convolve_fn), make_tuple(8, 4, &convolve_fn),         \
       make_tuple(4, 8, &convolve_fn), make_tuple(8, 8, &convolve_fn),     \
@@ -88,7 +114,13 @@
       make_tuple(16, 32, &convolve_fn), make_tuple(32, 32, &convolve_fn), \
       make_tuple(64, 32, &convolve_fn), make_tuple(32, 64, &convolve_fn), \
       make_tuple(64, 64, &convolve_fn)
-
+#if !CONFIG_REALTIME_ONLY && CONFIG_VP9_ENCODER
+#define ALL_SIZES_12TAP(convolve_fn)                                      \
+  make_tuple(8, 8, &convolve_fn), make_tuple(16, 8, &convolve_fn),        \
+      make_tuple(8, 16, &convolve_fn), make_tuple(16, 16, &convolve_fn),  \
+      make_tuple(32, 16, &convolve_fn), make_tuple(16, 32, &convolve_fn), \
+      make_tuple(32, 32, &convolve_fn)
+#endif
 // Reference 8-tap subpixel filter, slightly modified to fit into this test.
 #define VP9_FILTER_WEIGHT 128
 #define VP9_FILTER_SHIFT 7
@@ -549,6 +581,438 @@
 uint16_t *ConvolveTest::output16_ = nullptr;
 uint16_t *ConvolveTest::output16_ref_ = nullptr;
 #endif
+#if !CONFIG_REALTIME_ONLY && CONFIG_VP9_ENCODER
+class ConvolveTest12Tap : public ::testing::TestWithParam<Convolve12TapParam> {
+ public:
+  static void SetUpTestSuite() {
+    // Force input_ to be unaligned, output to be 16 byte aligned.
+    input_ = reinterpret_cast<uint8_t *>(
+                 vpx_memalign(kDataAlignment, kInputBufferSize + 1)) +
+             1;
+    output_ = reinterpret_cast<uint8_t *>(
+        vpx_memalign(kDataAlignment, kOutputBufferSize));
+#if CONFIG_VP9_HIGHBITDEPTH
+    input16_ = reinterpret_cast<uint16_t *>(vpx_memalign(
+                   kDataAlignment, (kInputBufferSize + 1) * sizeof(uint16_t))) +
+               1;
+    output16_ = reinterpret_cast<uint16_t *>(
+        vpx_memalign(kDataAlignment, (kOutputBufferSize) * sizeof(uint16_t)));
+#endif
+  }
+
+  void TearDown() override { libvpx_test::ClearSystemState(); }
+
+  static void TearDownTestSuite() {
+    vpx_free(input_ - 1);
+    input_ = nullptr;
+    vpx_free(output_);
+    output_ = nullptr;
+#if CONFIG_VP9_HIGHBITDEPTH
+    vpx_free(input16_ - 1);
+    input16_ = nullptr;
+    vpx_free(output16_);
+    output16_ = nullptr;
+#endif
+  }
+
+ protected:
+  static const int kDataAlignment = 16;
+  static const int kOuterBlockSize = 256;
+  static const int kInputStride = kOuterBlockSize;
+  static const int kOutputStride = kOuterBlockSize;
+  static const int kInputBufferSize = kOuterBlockSize * kOuterBlockSize;
+  static const int kOutputBufferSize = kOuterBlockSize * kOuterBlockSize;
+
+  int Width() const { return GET_PARAM(0); }
+  int Height() const { return GET_PARAM(1); }
+  int BorderLeft() const {
+    const int center = (kOuterBlockSize - Width()) / 2;
+    return (center + (kDataAlignment - 1)) & ~(kDataAlignment - 1);
+  }
+  int BorderTop() const { return (kOuterBlockSize - Height()) / 2; }
+
+  bool IsIndexInBorder(int i) {
+    return (i < BorderTop() * kOuterBlockSize ||
+            i >= (BorderTop() + Height()) * kOuterBlockSize ||
+            i % kOuterBlockSize < BorderLeft() ||
+            i % kOuterBlockSize >= (BorderLeft() + Width()));
+  }
+
+  void SetUp() override {
+    UUT_ = GET_PARAM(2);
+#if CONFIG_VP9_HIGHBITDEPTH
+    if (UUT_->use_highbd_ != 0) {
+      mask_ = (1 << UUT_->use_highbd_) - 1;
+    } else {
+      mask_ = 255;
+    }
+#endif
+    /* Set up guard blocks for an inner block centered in the outer block */
+    for (int i = 0; i < kOutputBufferSize; ++i) {
+      if (IsIndexInBorder(i)) {
+        output_[i] = 255;
+#if CONFIG_VP9_HIGHBITDEPTH
+        output16_[i] = mask_;
+#endif
+      } else {
+        output_[i] = 0;
+#if CONFIG_VP9_HIGHBITDEPTH
+        output16_[i] = 0;
+#endif
+      }
+    }
+
+    ::libvpx_test::ACMRandom prng;
+    for (int i = 0; i < kInputBufferSize; ++i) {
+      if (i & 1) {
+        input_[i] = 255;
+#if CONFIG_VP9_HIGHBITDEPTH
+        input16_[i] = mask_;
+#endif
+      } else {
+        input_[i] = prng.Rand8Extremes();
+#if CONFIG_VP9_HIGHBITDEPTH
+        input16_[i] = prng.Rand16() & mask_;
+#endif
+      }
+    }
+  }
+
+  void SetConstantInput(int value) {
+    memset(input_, value, kInputBufferSize);
+#if CONFIG_VP9_HIGHBITDEPTH
+    vpx_memset16(input16_, value, kInputBufferSize);
+#endif
+  }
+
+  void CheckGuardBlocks() {
+    for (int i = 0; i < kOutputBufferSize; ++i) {
+      if (IsIndexInBorder(i)) {
+        EXPECT_EQ(255, output_[i]);
+      }
+    }
+  }
+
+  uint8_t *input() const {
+    const int offset = BorderTop() * kOuterBlockSize + BorderLeft();
+#if CONFIG_VP9_HIGHBITDEPTH
+    if (UUT_->use_highbd_ == 0) {
+      return input_ + offset;
+    } else {
+      return CAST_TO_BYTEPTR(input16_ + offset);
+    }
+#else
+    return input_ + offset;
+#endif
+  }
+
+  uint8_t *output() const {
+    const int offset = BorderTop() * kOuterBlockSize + BorderLeft();
+#if CONFIG_VP9_HIGHBITDEPTH
+    if (UUT_->use_highbd_ == 0) {
+      return output_ + offset;
+    } else {
+      return CAST_TO_BYTEPTR(output16_ + offset);
+    }
+#else
+    return output_ + offset;
+#endif
+  }
+
+  uint16_t lookup(uint8_t *list, int index) const {
+#if CONFIG_VP9_HIGHBITDEPTH
+    if (UUT_->use_highbd_ == 0) {
+      return list[index];
+    } else {
+      return CAST_TO_SHORTPTR(list)[index];
+    }
+#else
+    return list[index];
+#endif
+  }
+
+  void assign_val(uint8_t *list, int index, uint16_t val) const {
+#if CONFIG_VP9_HIGHBITDEPTH
+    if (UUT_->use_highbd_ == 0) {
+      list[index] = (uint8_t)val;
+    } else {
+      CAST_TO_SHORTPTR(list)[index] = val;
+    }
+#else
+    list[index] = (uint8_t)val;
+#endif
+  }
+  const ConvolveFunctions12Tap *UUT_;
+  static uint8_t *input_;
+  static uint8_t *output_;
+#if CONFIG_VP9_HIGHBITDEPTH
+  static uint16_t *input16_;
+  static uint16_t *output16_;
+  int mask_;
+#endif
+};
+
+uint8_t *ConvolveTest12Tap::input_ = nullptr;
+uint8_t *ConvolveTest12Tap::output_ = nullptr;
+#if CONFIG_VP9_HIGHBITDEPTH
+uint16_t *ConvolveTest12Tap::input16_ = nullptr;
+uint16_t *ConvolveTest12Tap::output16_ = nullptr;
+#endif
+
+TEST_P(ConvolveTest12Tap, MatchesReferenceSubpixelFilter) {
+  uint8_t *const in = input();
+  uint8_t *const out = output();
+#if CONFIG_VP9_HIGHBITDEPTH
+  uint8_t ref8[kOutputStride * kMaxDimension];
+  uint16_t ref16[kOutputStride * kMaxDimension];
+  uint8_t *ref;
+  if (UUT_->use_highbd_ == 0) {
+    ref = ref8;
+  } else {
+    ref = CAST_TO_BYTEPTR(ref16);
+  }
+#else
+  uint8_t ref[kOutputStride * kMaxDimension];
+#endif
+
+  // Populate ref and out with some random data
+  ::libvpx_test::ACMRandom prng;
+  for (int y = 0; y < Height(); ++y) {
+    for (int x = 0; x < Width(); ++x) {
+      uint16_t r;
+#if CONFIG_VP9_HIGHBITDEPTH
+      if (UUT_->use_highbd_ == 0 || UUT_->use_highbd_ == 8) {
+        r = prng.Rand8Extremes();
+      } else {
+        r = prng.Rand16() & mask_;
+      }
+#else
+      r = prng.Rand8Extremes();
+#endif
+
+      assign_val(out, y * kOutputStride + x, r);
+      assign_val(ref, y * kOutputStride + x, r);
+    }
+  }
+
+  const InterpKernel12 *filters = sub_pel_filters_12;
+  for (int filter_x = 0; filter_x < 16; ++filter_x) {
+    for (int filter_y = 0; filter_y < 16; ++filter_y) {
+#if CONFIG_VP9_HIGHBITDEPTH
+      if (UUT_->use_highbd_ == 0) {
+        vpx_convolve12_c(in, kInputStride, ref, kOutputStride, filters,
+                         filter_x, 16, filter_y, 16, Width(), Height());
+      } else {
+        vpx_highbd_convolve12_c(CAST_TO_SHORTPTR(in), kInputStride,
+                                CAST_TO_SHORTPTR(ref), kOutputStride, filters,
+                                filter_x, 16, filter_y, 16, Width(), Height(),
+                                UUT_->use_highbd_);
+      }
+#else
+      vpx_convolve12_c(in, kInputStride, ref, kOutputStride, filters, filter_x,
+                       16, filter_y, 16, Width(), Height());
+#endif
+      if (filter_x && filter_y)
+        ASM_REGISTER_STATE_CHECK(
+            UUT_->hv12_(in, kInputStride, out, kOutputStride, filters, filter_x,
+                        16, filter_y, 16, Width(), Height()));
+      else if (filter_y)
+        ASM_REGISTER_STATE_CHECK(UUT_->v12_(in, kInputStride, out,
+                                            kOutputStride, filters, 0, 16,
+                                            filter_y, 16, Width(), Height()));
+      else if (filter_x)
+        ASM_REGISTER_STATE_CHECK(UUT_->h12_(in, kInputStride, out,
+                                            kOutputStride, filters, filter_x,
+                                            16, 0, 16, Width(), Height()));
+      else
+        continue;
+
+      CheckGuardBlocks();
+
+      for (int y = 0; y < Height(); ++y) {
+        for (int x = 0; x < Width(); ++x)
+          ASSERT_EQ(lookup(ref, y * kOutputStride + x),
+                    lookup(out, y * kOutputStride + x))
+              << "mismatch at (" << x << "," << y << "), "
+              << "filters ("
+              << "," << filter_x << "," << filter_y << ")";
+      }
+    }
+  }
+}
+
+TEST_P(ConvolveTest12Tap, FilterExtremes) {
+  uint8_t *const in = input();
+  uint8_t *const out = output();
+#if CONFIG_VP9_HIGHBITDEPTH
+  uint8_t ref8[kOutputStride * kMaxDimension];
+  uint16_t ref16[kOutputStride * kMaxDimension];
+  uint8_t *ref;
+  if (UUT_->use_highbd_ == 0) {
+    ref = ref8;
+  } else {
+    ref = CAST_TO_BYTEPTR(ref16);
+  }
+#else
+  uint8_t ref[kOutputStride * kMaxDimension];
+#endif
+
+  // Populate ref and out with some random data
+  ::libvpx_test::ACMRandom prng;
+  for (int y = 0; y < Height(); ++y) {
+    for (int x = 0; x < Width(); ++x) {
+      uint16_t r;
+#if CONFIG_VP9_HIGHBITDEPTH
+      if (UUT_->use_highbd_ == 0 || UUT_->use_highbd_ == 8) {
+        r = prng.Rand8Extremes();
+      } else {
+        r = prng.Rand16() & mask_;
+      }
+#else
+      r = prng.Rand8Extremes();
+#endif
+      assign_val(out, y * kOutputStride + x, r);
+      assign_val(ref, y * kOutputStride + x, r);
+    }
+  }
+
+  for (int axis = 0; axis < 2; axis++) {
+    int seed_val = 0;
+    while (seed_val < 256) {
+      for (int y = 0; y < 8; ++y) {
+        for (int x = 0; x < 8; ++x) {
+#if CONFIG_VP9_HIGHBITDEPTH
+          assign_val(in, y * kOutputStride + x - MAX_FILTER_TAP / 2 + 1,
+                     ((seed_val >> (axis ? y : x)) & 1) * mask_);
+#else
+          assign_val(in, y * kOutputStride + x - MAX_FILTER_TAP / 2 + 1,
+                     ((seed_val >> (axis ? y : x)) & 1) * 255);
+#endif
+          if (axis) seed_val++;
+        }
+        if (axis) {
+          seed_val -= 8;
+        } else {
+          seed_val++;
+        }
+      }
+      if (axis) seed_val += 8;
+
+      const InterpKernel12 *filters = sub_pel_filters_12;
+      for (int filter_x = 0; filter_x < 16; ++filter_x) {
+        for (int filter_y = 0; filter_y < 16; ++filter_y) {
+#if CONFIG_VP9_HIGHBITDEPTH
+          if (UUT_->use_highbd_ == 0) {
+            vpx_convolve12_c(in, kInputStride, ref, kOutputStride, filters,
+                             filter_x, 16, filter_y, 16, Width(), Height());
+          } else {
+            vpx_highbd_convolve12_c(CAST_TO_SHORTPTR(in), kInputStride,
+                                    CAST_TO_SHORTPTR(ref), kOutputStride,
+                                    filters, filter_x, 16, filter_y, 16,
+                                    Width(), Height(), UUT_->use_highbd_);
+          }
+#else
+          vpx_convolve12_c(in, kInputStride, ref, kOutputStride, filters,
+                           filter_x, 16, filter_y, 16, Width(), Height());
+#endif
+          if (filter_x && filter_y)
+            ASM_REGISTER_STATE_CHECK(
+                UUT_->hv12_(in, kInputStride, out, kOutputStride, filters,
+                            filter_x, 16, filter_y, 16, Width(), Height()));
+          else if (filter_y)
+            ASM_REGISTER_STATE_CHECK(
+                UUT_->v12_(in, kInputStride, out, kOutputStride, filters, 0, 16,
+                           filter_y, 16, Width(), Height()));
+          else if (filter_x)
+            ASM_REGISTER_STATE_CHECK(
+                UUT_->h12_(in, kInputStride, out, kOutputStride, filters,
+                           filter_x, 16, 0, 16, Width(), Height()));
+          else
+            continue;
+
+          for (int y = 0; y < Height(); ++y) {
+            for (int x = 0; x < Width(); ++x)
+              ASSERT_EQ(lookup(ref, y * kOutputStride + x),
+                        lookup(out, y * kOutputStride + x))
+                  << "mismatch at (" << x << "," << y << "), "
+                  << "filters ("
+                  << "," << filter_x << "," << filter_y << ")";
+          }
+        }
+      }
+    }
+  }
+}
+
+TEST_P(ConvolveTest12Tap, DISABLED_12Tap_Speed) {
+  const uint8_t *const in = input();
+  uint8_t *const out = output();
+  const InterpKernel12 *const twelvetap = sub_pel_filters_12;
+  const int kNumTests = 5000000;
+  const int width = Width();
+  const int height = Height();
+  vpx_usec_timer timer;
+
+  SetConstantInput(127);
+
+  vpx_usec_timer_start(&timer);
+  for (int n = 0; n < kNumTests; ++n) {
+    UUT_->hv12_(in, kInputStride, out, kOutputStride, twelvetap, 8, 16, 8, 16,
+                width, height);
+  }
+  vpx_usec_timer_mark(&timer);
+
+  const int elapsed_time = static_cast<int>(vpx_usec_timer_elapsed(&timer));
+  printf("convolve12_%dx%d_%d: %d us\n", width, height,
+         UUT_->use_highbd_ ? UUT_->use_highbd_ : 8, elapsed_time);
+}
+
+TEST_P(ConvolveTest12Tap, DISABLED_12Tap_Horiz_Speed) {
+  const uint8_t *const in = input();
+  uint8_t *const out = output();
+  const InterpKernel12 *const twelvetap = sub_pel_filters_12;
+  const int kNumTests = 5000000;
+  const int width = Width();
+  const int height = Height();
+  vpx_usec_timer timer;
+
+  SetConstantInput(127);
+
+  vpx_usec_timer_start(&timer);
+  for (int n = 0; n < kNumTests; ++n) {
+    UUT_->h12_(in, kInputStride, out, kOutputStride, twelvetap, 8, 16, 8, 16,
+               width, height);
+  }
+  vpx_usec_timer_mark(&timer);
+
+  const int elapsed_time = static_cast<int>(vpx_usec_timer_elapsed(&timer));
+  printf("convolve12_horiz_%dx%d_%d: %d us\n", width, height,
+         UUT_->use_highbd_ ? UUT_->use_highbd_ : 8, elapsed_time);
+}
+
+TEST_P(ConvolveTest12Tap, DISABLED_12Tap_Vert_Speed) {
+  const uint8_t *const in = input();
+  uint8_t *const out = output();
+  const InterpKernel12 *const twelvetap = sub_pel_filters_12;
+  const int kNumTests = 5000000;
+  const int width = Width();
+  const int height = Height();
+  vpx_usec_timer timer;
+
+  SetConstantInput(127);
+
+  vpx_usec_timer_start(&timer);
+  for (int n = 0; n < kNumTests; ++n) {
+    UUT_->v12_(in, kInputStride, out, kOutputStride, twelvetap, 8, 16, 8, 16,
+               width, height);
+  }
+  vpx_usec_timer_mark(&timer);
+
+  const int elapsed_time = static_cast<int>(vpx_usec_timer_elapsed(&timer));
+  printf("convolve12_vert_%dx%d_%d: %d us\n", width, height,
+         UUT_->use_highbd_ ? UUT_->use_highbd_ : 8, elapsed_time);
+}
+#endif
 
 TEST_P(ConvolveTest, GuardBlocks) { CheckGuardBlocks(); }
 
@@ -1304,6 +1768,79 @@
 #endif
 INSTANTIATE_TEST_SUITE_P(C, ConvolveTest,
                          ::testing::ValuesIn(kArrayConvolve_c));
+#if !CONFIG_REALTIME_ONLY && CONFIG_VP9_ENCODER
+#if CONFIG_VP9_HIGHBITDEPTH
+#define WRAP12TAP(func, bd)                                                  \
+  void wrap_##func##_##bd(                                                   \
+      const uint8_t *src, ptrdiff_t src_stride, uint8_t *dst,                \
+      ptrdiff_t dst_stride, const InterpKernel12 *filter, int x0_q4,         \
+      int x_step_q4, int y0_q4, int y_step_q4, int w, int h) {               \
+    vpx_highbd_##func(reinterpret_cast<const uint16_t *>(src), src_stride,   \
+                      reinterpret_cast<uint16_t *>(dst), dst_stride, filter, \
+                      x0_q4, x_step_q4, y0_q4, y_step_q4, w, h, bd);         \
+  }
+
+#if HAVE_AVX2
+WRAP12TAP(convolve12_horiz_avx2, 8)
+WRAP12TAP(convolve12_vert_avx2, 8)
+WRAP12TAP(convolve12_avx2, 8)
+WRAP12TAP(convolve12_horiz_avx2, 10)
+WRAP12TAP(convolve12_vert_avx2, 10)
+WRAP12TAP(convolve12_avx2, 10)
+WRAP12TAP(convolve12_horiz_avx2, 12)
+WRAP12TAP(convolve12_vert_avx2, 12)
+WRAP12TAP(convolve12_avx2, 12)
+#endif  // HAVE_AVX2
+
+#if HAVE_SSSE3
+WRAP12TAP(convolve12_horiz_ssse3, 8)
+WRAP12TAP(convolve12_vert_ssse3, 8)
+WRAP12TAP(convolve12_ssse3, 8)
+WRAP12TAP(convolve12_horiz_ssse3, 10)
+WRAP12TAP(convolve12_vert_ssse3, 10)
+WRAP12TAP(convolve12_ssse3, 10)
+WRAP12TAP(convolve12_horiz_ssse3, 12)
+WRAP12TAP(convolve12_vert_ssse3, 12)
+WRAP12TAP(convolve12_ssse3, 12)
+#endif  // HAVE_SSSE3
+
+WRAP12TAP(convolve12_horiz_c, 8)
+WRAP12TAP(convolve12_vert_c, 8)
+WRAP12TAP(convolve12_c, 8)
+WRAP12TAP(convolve12_horiz_c, 10)
+WRAP12TAP(convolve12_vert_c, 10)
+WRAP12TAP(convolve12_c, 10)
+WRAP12TAP(convolve12_horiz_c, 12)
+WRAP12TAP(convolve12_vert_c, 12)
+WRAP12TAP(convolve12_c, 12)
+#undef WRAP12TAP
+
+const ConvolveFunctions12Tap convolve12tap_8bit_c(wrap_convolve12_horiz_c_8,
+                                                  wrap_convolve12_vert_c_8,
+                                                  wrap_convolve12_c_8, 8);
+
+const ConvolveFunctions12Tap convolve12tap_10bit_c(wrap_convolve12_horiz_c_10,
+                                                   wrap_convolve12_vert_c_10,
+                                                   wrap_convolve12_c_10, 10);
+
+const ConvolveFunctions12Tap convolve12tap_12bit_c(wrap_convolve12_horiz_c_12,
+                                                   wrap_convolve12_vert_c_12,
+                                                   wrap_convolve12_c_12, 12);
+
+const Convolve12TapParam kArrayConvolve12Tap_c[] = {
+  ALL_SIZES_12TAP(convolve12tap_8bit_c), ALL_SIZES_12TAP(convolve12tap_10bit_c),
+  ALL_SIZES_12TAP(convolve12tap_12bit_c)
+};
+#else
+const ConvolveFunctions12Tap convolve12Tap_c(vpx_convolve12_horiz_c,
+                                             vpx_convolve12_vert_c,
+                                             vpx_convolve12_c, 0);
+const Convolve12TapParam kArrayConvolve12Tap_c[] = { ALL_SIZES_12TAP(
+    convolve12Tap_c) };
+#endif
+INSTANTIATE_TEST_SUITE_P(C, ConvolveTest12Tap,
+                         ::testing::ValuesIn(kArrayConvolve12Tap_c));
+#endif
 
 #if HAVE_SSE2 && VPX_ARCH_X86_64
 #if CONFIG_VP9_HIGHBITDEPTH
@@ -1359,6 +1896,37 @@
 const ConvolveParam kArrayConvolve8_ssse3[] = { ALL_SIZES(convolve8_ssse3) };
 INSTANTIATE_TEST_SUITE_P(SSSE3, ConvolveTest,
                          ::testing::ValuesIn(kArrayConvolve8_ssse3));
+
+#if !CONFIG_REALTIME_ONLY && CONFIG_VP9_ENCODER
+#if CONFIG_VP9_HIGHBITDEPTH
+const ConvolveFunctions12Tap convolve12tap_8bit_ssse3(
+    wrap_convolve12_horiz_ssse3_8, wrap_convolve12_vert_ssse3_8,
+    wrap_convolve12_ssse3_8, 8);
+
+const ConvolveFunctions12Tap convolve12tap_10bit_ssse3(
+    wrap_convolve12_horiz_ssse3_10, wrap_convolve12_vert_ssse3_10,
+    wrap_convolve12_ssse3_10, 10);
+
+const ConvolveFunctions12Tap convolve12tap_12bit_ssse3(
+    wrap_convolve12_horiz_ssse3_12, wrap_convolve12_vert_ssse3_12,
+    wrap_convolve12_ssse3_12, 12);
+
+const Convolve12TapParam kArrayConvolve12Tap_ssse3[] = {
+  ALL_SIZES_12TAP(convolve12tap_8bit_ssse3),
+  ALL_SIZES_12TAP(convolve12tap_10bit_ssse3),
+  ALL_SIZES_12TAP(convolve12tap_12bit_ssse3)
+};
+#else
+const ConvolveFunctions12Tap convolve12_ssse3(vpx_convolve12_horiz_ssse3,
+                                              vpx_convolve12_vert_ssse3,
+                                              vpx_convolve12_ssse3, 0);
+const Convolve12TapParam kArrayConvolve12Tap_ssse3[] = { ALL_SIZES_12TAP(
+    convolve12_ssse3) };
+#endif  // CONFIG_VP9_HIGHBITDEPTH
+
+INSTANTIATE_TEST_SUITE_P(SSSE3, ConvolveTest12Tap,
+                         ::testing::ValuesIn(kArrayConvolve12Tap_ssse3));
+#endif  // !CONFIG_REALTIME_ONLY && CONFIG_VP9_ENCODER
 #endif
 
 #if HAVE_AVX2
@@ -1402,6 +1970,36 @@
 INSTANTIATE_TEST_SUITE_P(AVX2, ConvolveTest,
                          ::testing::ValuesIn(kArrayConvolve8_avx2));
 #endif  // CONFIG_VP9_HIGHBITDEPTH
+
+#if !CONFIG_REALTIME_ONLY && CONFIG_VP9_ENCODER
+#if CONFIG_VP9_HIGHBITDEPTH
+const ConvolveFunctions12Tap convolve12Tap_8bit_avx2(
+    wrap_convolve12_horiz_avx2_8, wrap_convolve12_vert_avx2_8,
+    wrap_convolve12_avx2_8, 8);
+
+const ConvolveFunctions12Tap convolve12Tap_10bit_avx2(
+    wrap_convolve12_horiz_avx2_10, wrap_convolve12_vert_avx2_10,
+    wrap_convolve12_avx2_10, 10);
+
+const ConvolveFunctions12Tap convolve12Tap_12bit_avx2(
+    wrap_convolve12_horiz_avx2_12, wrap_convolve12_vert_avx2_12,
+    wrap_convolve12_avx2_12, 12);
+
+const Convolve12TapParam kArrayConvolve12Tap_avx2[] = {
+  ALL_SIZES_12TAP(convolve12Tap_8bit_avx2),
+  ALL_SIZES_12TAP(convolve12Tap_10bit_avx2),
+  ALL_SIZES_12TAP(convolve12Tap_12bit_avx2)
+};
+#else
+const ConvolveFunctions12Tap convolve12Tap_avx2(vpx_convolve12_horiz_avx2,
+                                                vpx_convolve12_vert_avx2,
+                                                vpx_convolve12_avx2, 0);
+const Convolve12TapParam kArrayConvolve12Tap_avx2[] = { ALL_SIZES_12TAP(
+    convolve12Tap_avx2) };
+#endif
+INSTANTIATE_TEST_SUITE_P(AVX2, ConvolveTest12Tap,
+                         ::testing::ValuesIn(kArrayConvolve12Tap_avx2));
+#endif
 #endif  // HAVE_AVX2
 
 #if HAVE_NEON
@@ -1445,6 +2043,16 @@
 #endif  // CONFIG_VP9_HIGHBITDEPTH
 INSTANTIATE_TEST_SUITE_P(NEON, ConvolveTest,
                          ::testing::ValuesIn(kArrayConvolve_neon));
+
+#if !CONFIG_REALTIME_ONLY && CONFIG_VP9_ENCODER
+const ConvolveFunctions12Tap convolve12Tap_neon(vpx_convolve12_horiz_neon,
+                                                vpx_convolve12_vert_neon,
+                                                vpx_convolve12_neon, 0);
+const Convolve12TapParam kArrayConvolve12Tap_neon[] = { ALL_SIZES_12TAP(
+    convolve12Tap_neon) };
+INSTANTIATE_TEST_SUITE_P(NEON, ConvolveTest12Tap,
+                         ::testing::ValuesIn(kArrayConvolve12Tap_neon));
+#endif
 #endif  // HAVE_NEON
 
 #if HAVE_NEON_DOTPROD
@@ -1460,6 +2068,16 @@
     convolve8_neon_dotprod) };
 INSTANTIATE_TEST_SUITE_P(NEON_DOTPROD, ConvolveTest,
                          ::testing::ValuesIn(kArrayConvolve_neon_dotprod));
+
+#if !CONFIG_REALTIME_ONLY && CONFIG_VP9_ENCODER
+const ConvolveFunctions12Tap convolve12Tap_neon_dotprod(
+    vpx_convolve12_horiz_neon_dotprod, vpx_convolve12_vert_neon_dotprod,
+    vpx_convolve12_neon_dotprod, 0);
+const Convolve12TapParam kArrayConvolve12Tap_neon_dotprod[] = { ALL_SIZES_12TAP(
+    convolve12Tap_neon_dotprod) };
+INSTANTIATE_TEST_SUITE_P(NEON_DOTPROD, ConvolveTest12Tap,
+                         ::testing::ValuesIn(kArrayConvolve12Tap_neon_dotprod));
+#endif
 #endif  // HAVE_NEON_DOTPROD
 
 #if HAVE_SVE
@@ -1543,6 +2161,16 @@
     convolve8_neon_i8mm) };
 INSTANTIATE_TEST_SUITE_P(NEON_I8MM, ConvolveTest,
                          ::testing::ValuesIn(kArrayConvolve_neon_i8mm));
+
+#if !CONFIG_REALTIME_ONLY && CONFIG_VP9_ENCODER
+const ConvolveFunctions12Tap convolve12Tap_neon_i8mm(
+    vpx_convolve12_horiz_neon_i8mm, vpx_convolve12_vert_neon_i8mm,
+    vpx_convolve12_neon_i8mm, 0);
+const Convolve12TapParam kArrayConvolve12Tap_neon_i8mm[] = { ALL_SIZES_12TAP(
+    convolve12Tap_neon_i8mm) };
+INSTANTIATE_TEST_SUITE_P(NEON_I8MM, ConvolveTest12Tap,
+                         ::testing::ValuesIn(kArrayConvolve12Tap_neon_i8mm));
+#endif
 #endif  // HAVE_NEON_I8MM
 
 #if HAVE_DSPR2
diff --git a/test/decode_corrupted.cc b/test/decode_corrupted.cc
index 8e51fb3..55919d0 100644
--- a/test/decode_corrupted.cc
+++ b/test/decode_corrupted.cc
@@ -59,7 +59,7 @@
     if (pkt->data.frame.flags & VPX_FRAME_IS_KEY) return pkt;
     if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) return pkt;
 
-    memcpy(&modified_pkt_, pkt, sizeof(*pkt));
+    modified_pkt_ = *pkt;
 
     // Halve the size so it's corrupted to decoder.
     modified_pkt_.data.frame.sz = modified_pkt_.data.frame.sz / 2;
diff --git a/test/decode_test_driver.cc b/test/decode_test_driver.cc
index 794e367..af87bc2 100644
--- a/test/decode_test_driver.cc
+++ b/test/decode_test_driver.cc
@@ -111,7 +111,7 @@
 }
 
 void DecoderTest::set_cfg(const vpx_codec_dec_cfg_t &dec_cfg) {
-  memcpy(&cfg_, &dec_cfg, sizeof(cfg_));
+  cfg_ = dec_cfg;
 }
 
 void DecoderTest::set_flags(const vpx_codec_flags_t flags) { flags_ = flags; }
diff --git a/test/encode_api_test.cc b/test/encode_api_test.cc
index 05d31e6..a58552a 100644
--- a/test/encode_api_test.cc
+++ b/test/encode_api_test.cc
@@ -700,7 +700,7 @@
     cfg[0].g_timebase.num = 1;                 /* Set fps */
     cfg[0].g_timebase.den = framerate;
 
-    memcpy(&cfg[1], &cfg[0], sizeof(cfg[0]));
+    cfg[1] = cfg[0];
     cfg[1].rc_target_bitrate = 500;
     cfg[1].g_w = width_down;
     cfg[1].g_h = height_down;
diff --git a/test/fdct8x8_test.cc b/test/fdct8x8_test.cc
index d8778f9..d02a47c 100644
--- a/test/fdct8x8_test.cc
+++ b/test/fdct8x8_test.cc
@@ -133,13 +133,16 @@
 #endif  // HAVE_SSE2
 #endif  // CONFIG_VP9_HIGHBITDEPTH
 
-// Visual Studio 2022 (cl.exe) targeting AArch64 with optimizations enabled
-// produces invalid code in RunExtremalCheck() and RunInvAccuracyCheck().
-// See:
+// Visual Studio 2022 (cl.exe) < 17.12.3 targeting AArch64 with optimizations
+// enabled produces invalid code in RunExtremalCheck() and
+// RunInvAccuracyCheck(). See:
 // https://developercommunity.visualstudio.com/t/1770-preview-1:-Misoptimization-for-AR/10369786
-// TODO(jzern): check the compiler version after a fix for the issue is
-// released.
-#if defined(_MSC_VER) && defined(_M_ARM64) && !defined(__clang__)
+#if defined(_MSC_FULL_VER) && _MSC_FULL_VER < 194234435 && \
+    defined(_M_ARM64) && !defined(__clang__)
+#define AOM_WORK_AROUND_MSVC_BUG_10369786
+#endif
+
+#ifdef AOM_WORK_AROUND_MSVC_BUG_10369786
 #pragma optimize("", off)
 #endif
 class FwdTrans8x8TestBase {
@@ -533,7 +536,7 @@
   vpx_bit_depth_t bit_depth_;
   int mask_;
 };
-#if defined(_MSC_VER) && defined(_M_ARM64) && !defined(__clang__)
+#ifdef AOM_WORK_AROUND_MSVC_BUG_10369786
 #pragma optimize("", on)
 #endif
 
diff --git a/test/quantize_test.cc b/test/quantize_test.cc
index 2ef0408..cd02e0f 100644
--- a/test/quantize_test.cc
+++ b/test/quantize_test.cc
@@ -71,7 +71,7 @@
     // Copy macroblockd from the reference to get pre-set-up dequant values.
     macroblockd_dst_ = reinterpret_cast<MACROBLOCKD *>(
         vpx_memalign(32, sizeof(*macroblockd_dst_)));
-    memcpy(macroblockd_dst_, &vp8_comp_->mb.e_mbd, sizeof(*macroblockd_dst_));
+    *macroblockd_dst_ = vp8_comp_->mb.e_mbd;
     // Fix block pointers - currently they point to the blocks in the reference
     // structure.
     vp8_setup_block_dptrs(macroblockd_dst_);
@@ -80,7 +80,7 @@
   void UpdateQuantizer(int q) {
     vp8_set_quantizer(vp8_comp_, q);
 
-    memcpy(macroblockd_dst_, &vp8_comp_->mb.e_mbd, sizeof(*macroblockd_dst_));
+    *macroblockd_dst_ = vp8_comp_->mb.e_mbd;
     vp8_setup_block_dptrs(macroblockd_dst_);
   }
 
diff --git a/test/resize_test.cc b/test/resize_test.cc
index 93e1bf6..1a83194 100644
--- a/test/resize_test.cc
+++ b/test/resize_test.cc
@@ -93,11 +93,20 @@
 void ScaleForFrameNumber(unsigned int frame, unsigned int initial_w,
                          unsigned int initial_h, unsigned int *w,
                          unsigned int *h, bool flag_codec,
-                         bool smaller_width_larger_size_) {
+                         bool smaller_width_larger_size,
+                         bool random_input_one_half_only) {
   *w = initial_w;
   *h = initial_h;
 
-  if (smaller_width_larger_size_) {
+  if (random_input_one_half_only == 1) {
+    if (frame < 100) {
+      return;
+    }
+    *w = initial_w / 2;
+    *h = initial_h / 2;
+    return;
+  }
+  if (smaller_width_larger_size) {
     if (frame < 30) {
       return;
     }
@@ -236,13 +245,19 @@
 
 class ResizingVideoSource : public ::libvpx_test::DummyVideoSource {
  public:
-  ResizingVideoSource() {
-    SetSize(kInitialWidth, kInitialHeight);
+  ResizingVideoSource(int width, int height)
+      : smaller_width_larger_size_(false), random_input_one_half_only_(false),
+        configured_width_(width), configured_height_(height) {
     limit_ = 350;
-    smaller_width_larger_size_ = false;
+    SetSize(configured_width_, configured_height_);
   }
   bool flag_codec_;
   bool smaller_width_larger_size_;
+  bool random_input_one_half_only_;
+  // configured_width_/height_ is the configured resolution when codec is
+  // created.
+  int configured_width_;
+  int configured_height_;
   ~ResizingVideoSource() override = default;
 
  protected:
@@ -250,11 +265,25 @@
     ++frame_;
     unsigned int width = 0;
     unsigned int height = 0;
-    ScaleForFrameNumber(frame_, kInitialWidth, kInitialHeight, &width, &height,
-                        flag_codec_, smaller_width_larger_size_);
+    ScaleForFrameNumber(frame_, configured_width_, configured_height_, &width,
+                        &height, flag_codec_, smaller_width_larger_size_,
+                        random_input_one_half_only_);
     SetSize(width, height);
     FillFrame();
   }
+
+  void FillFrame() override {
+    if (img_) {
+      memset(img_->img_data, 0, raw_sz_);
+      if (random_input_one_half_only_) {
+        libvpx_test::ACMRandom rnd(libvpx_test::ACMRandom::DeterministicSeed());
+        unsigned char *image = img_->planes[0];
+        for (size_t i = 0; i < raw_sz_; ++i) {
+          image[i] = rnd.Rand8();
+        }
+      }
+    }
+  }
 };
 
 class ResizeTest
@@ -296,26 +325,26 @@
 };
 
 TEST_P(ResizeTest, TestExternalResizeWorks) {
-  ResizingVideoSource video;
+  ResizingVideoSource video(kInitialWidth, kInitialHeight);
   video.flag_codec_ = false;
   video.smaller_width_larger_size_ = false;
   cfg_.g_lag_in_frames = 0;
   ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
 
-  for (std::vector<FrameInfo>::const_iterator info = frame_info_list_.begin();
-       info != frame_info_list_.end(); ++info) {
-    const unsigned int frame = static_cast<unsigned>(info->pts);
+  for (const auto &info : frame_info_list_) {
+    const unsigned int frame = static_cast<unsigned>(info.pts);
     unsigned int expected_w;
     unsigned int expected_h;
-    const size_t idx = info - frame_info_list_.begin();
-    ASSERT_EQ(info->w, GetFrameWidth(idx));
-    ASSERT_EQ(info->h, GetFrameHeight(idx));
+    const size_t idx = &info - &frame_info_list_[0];
+    ASSERT_EQ(info.w, GetFrameWidth(idx));
+    ASSERT_EQ(info.h, GetFrameHeight(idx));
     ScaleForFrameNumber(frame, kInitialWidth, kInitialHeight, &expected_w,
                         &expected_h, video.flag_codec_,
-                        video.smaller_width_larger_size_);
-    EXPECT_EQ(expected_w, info->w)
+                        video.smaller_width_larger_size_,
+                        /*random_input_one_half_only=*/false);
+    EXPECT_EQ(expected_w, info.w)
         << "Frame " << frame << " had unexpected width";
-    EXPECT_EQ(expected_h, info->h)
+    EXPECT_EQ(expected_h, info.h)
         << "Frame " << frame << " had unexpected height";
   }
 }
@@ -419,15 +448,14 @@
   cfg_.g_lag_in_frames = 0;
   ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
 
-  for (std::vector<FrameInfo>::const_iterator info = frame_info_list_.begin();
-       info != frame_info_list_.end(); ++info) {
-    const vpx_codec_pts_t pts = info->pts;
+  for (const auto &info : frame_info_list_) {
+    const vpx_codec_pts_t pts = info.pts;
     if (pts >= kStepDownFrame && pts < kStepUpFrame) {
-      ASSERT_EQ(282U, info->w) << "Frame " << pts << " had unexpected width";
-      ASSERT_EQ(173U, info->h) << "Frame " << pts << " had unexpected height";
+      ASSERT_EQ(282U, info.w) << "Frame " << pts << " had unexpected width";
+      ASSERT_EQ(173U, info.h) << "Frame " << pts << " had unexpected height";
     } else {
-      EXPECT_EQ(352U, info->w) << "Frame " << pts << " had unexpected width";
-      EXPECT_EQ(288U, info->h) << "Frame " << pts << " had unexpected height";
+      EXPECT_EQ(352U, info.w) << "Frame " << pts << " had unexpected width";
+      EXPECT_EQ(288U, info.h) << "Frame " << pts << " had unexpected height";
     }
   }
 }
@@ -453,6 +481,10 @@
     if (video->frame() == 0) {
       encoder->Control(VP9E_SET_AQ_MODE, 3);
       encoder->Control(VP8E_SET_CPUUSED, set_cpu_used_);
+      if (cfg_.g_threads > 0) {
+        encoder->Control(VP9E_SET_ROW_MT, 1);
+        encoder->Control(VP9E_SET_TILE_COLUMNS, cfg_.g_threads >> 1);
+      }
     }
 
     if (change_bitrate_ && video->frame() == 120) {
@@ -528,7 +560,7 @@
 };
 
 TEST_P(ResizeRealtimeTest, TestExternalResizeWorks) {
-  ResizingVideoSource video;
+  ResizingVideoSource video(kInitialWidth, kInitialHeight);
   video.flag_codec_ = true;
   video.smaller_width_larger_size_ = false;
   DefaultConfig();
@@ -539,24 +571,57 @@
   mismatch_nframes_ = 0;
   ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
 
-  for (std::vector<FrameInfo>::const_iterator info = frame_info_list_.begin();
-       info != frame_info_list_.end(); ++info) {
-    const unsigned int frame = static_cast<unsigned>(info->pts);
+  for (const auto &info : frame_info_list_) {
+    const unsigned int frame = static_cast<unsigned>(info.pts);
     unsigned int expected_w;
     unsigned int expected_h;
     ScaleForFrameNumber(frame, kInitialWidth, kInitialHeight, &expected_w,
                         &expected_h, video.flag_codec_,
-                        video.smaller_width_larger_size_);
-    EXPECT_EQ(expected_w, info->w)
+                        video.smaller_width_larger_size_,
+                        /*random_input_one_half_only=*/false);
+    EXPECT_EQ(expected_w, info.w)
         << "Frame " << frame << " had unexpected width";
-    EXPECT_EQ(expected_h, info->h)
+    EXPECT_EQ(expected_h, info.h)
         << "Frame " << frame << " had unexpected height";
-    EXPECT_EQ(static_cast<unsigned int>(0), GetMismatchFrames());
+    EXPECT_EQ(GetMismatchFrames(), static_cast<unsigned int>(0));
+  }
+}
+
+// This test uses 4 threads with small keyframe spacing, random input,
+// and uses 640x480 as initial resolution.
+TEST_P(ResizeRealtimeTest, TestExternalResizeWorks4Threads) {
+  ResizingVideoSource video(640, 480);
+  video.flag_codec_ = true;
+  video.smaller_width_larger_size_ = false;
+  video.random_input_one_half_only_ = true;
+  DefaultConfig();
+  // Disable internal resize for this test.
+  cfg_.rc_resize_allowed = 0;
+  cfg_.g_threads = 4;
+  cfg_.kf_max_dist = 50;
+  cfg_.kf_min_dist = 50;
+  change_bitrate_ = false;
+  mismatch_psnr_ = 0.0;
+  mismatch_nframes_ = 0;
+  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
+
+  for (const auto &info : frame_info_list_) {
+    const unsigned int frame = static_cast<unsigned>(info.pts);
+    unsigned int expected_w;
+    unsigned int expected_h;
+    ScaleForFrameNumber(frame, 640, 480, &expected_w, &expected_h,
+                        video.flag_codec_, video.smaller_width_larger_size_,
+                        video.random_input_one_half_only_);
+    EXPECT_EQ(expected_w, info.w)
+        << "Frame " << frame << " had unexpected width";
+    EXPECT_EQ(expected_h, info.h)
+        << "Frame " << frame << " had unexpected height";
+    EXPECT_EQ(GetMismatchFrames(), static_cast<unsigned int>(0));
   }
 }
 
 TEST_P(ResizeRealtimeTest, TestExternalResizeSmallerWidthBiggerSize) {
-  ResizingVideoSource video;
+  ResizingVideoSource video(kInitialWidth, kInitialHeight);
   video.flag_codec_ = true;
   video.smaller_width_larger_size_ = true;
   DefaultConfig();
@@ -567,19 +632,19 @@
   mismatch_nframes_ = 0;
   ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
 
-  for (std::vector<FrameInfo>::const_iterator info = frame_info_list_.begin();
-       info != frame_info_list_.end(); ++info) {
-    const unsigned int frame = static_cast<unsigned>(info->pts);
+  for (const auto &info : frame_info_list_) {
+    const unsigned int frame = static_cast<unsigned>(info.pts);
     unsigned int expected_w;
     unsigned int expected_h;
     ScaleForFrameNumber(frame, kInitialWidth, kInitialHeight, &expected_w,
                         &expected_h, video.flag_codec_,
-                        video.smaller_width_larger_size_);
-    EXPECT_EQ(expected_w, info->w)
+                        video.smaller_width_larger_size_,
+                        /*random_input_one_half_only=*/false);
+    EXPECT_EQ(expected_w, info.w)
         << "Frame " << frame << " had unexpected width";
-    EXPECT_EQ(expected_h, info->h)
+    EXPECT_EQ(expected_h, info.h)
         << "Frame " << frame << " had unexpected height";
-    EXPECT_EQ(static_cast<unsigned int>(0), GetMismatchFrames());
+    EXPECT_EQ(GetMismatchFrames(), static_cast<unsigned int>(0));
   }
 }
 
@@ -601,21 +666,20 @@
   unsigned int last_w = cfg_.g_w;
   unsigned int last_h = cfg_.g_h;
   int resize_count = 0;
-  for (std::vector<FrameInfo>::const_iterator info = frame_info_list_.begin();
-       info != frame_info_list_.end(); ++info) {
-    if (info->w != last_w || info->h != last_h) {
+  for (const auto &info : frame_info_list_) {
+    if (info.w != last_w || info.h != last_h) {
       // Verify that resize down occurs.
-      ASSERT_LT(info->w, last_w);
-      ASSERT_LT(info->h, last_h);
-      last_w = info->w;
-      last_h = info->h;
+      ASSERT_LT(info.w, last_w);
+      ASSERT_LT(info.h, last_h);
+      last_w = info.w;
+      last_h = info.h;
       resize_count++;
     }
   }
 
   // Verify that we get 1 resize down event in this test.
   ASSERT_EQ(1, resize_count) << "Resizing should occur.";
-  EXPECT_EQ(static_cast<unsigned int>(0), GetMismatchFrames());
+  EXPECT_EQ(GetMismatchFrames(), static_cast<unsigned int>(0));
 #else
   GTEST_SKIP()
       << "Warning: VP9 decoder unavailable, unable to check resize count!\n";
@@ -643,31 +707,30 @@
   unsigned int last_w = cfg_.g_w;
   unsigned int last_h = cfg_.g_h;
   int resize_count = 0;
-  for (std::vector<FrameInfo>::const_iterator info = frame_info_list_.begin();
-       info != frame_info_list_.end(); ++info) {
-    const size_t idx = info - frame_info_list_.begin();
-    ASSERT_EQ(info->w, GetFrameWidth(idx));
-    ASSERT_EQ(info->h, GetFrameHeight(idx));
-    if (info->w != last_w || info->h != last_h) {
+  for (const auto &info : frame_info_list_) {
+    const size_t idx = &info - &frame_info_list_[0];
+    ASSERT_EQ(info.w, GetFrameWidth(idx));
+    ASSERT_EQ(info.h, GetFrameHeight(idx));
+    if (info.w != last_w || info.h != last_h) {
       resize_count++;
       if (resize_count <= 2) {
         // Verify that resize down occurs.
-        ASSERT_LT(info->w, last_w);
-        ASSERT_LT(info->h, last_h);
+        ASSERT_LT(info.w, last_w);
+        ASSERT_LT(info.h, last_h);
       } else if (resize_count > 2) {
         // Verify that resize up occurs.
-        ASSERT_GT(info->w, last_w);
-        ASSERT_GT(info->h, last_h);
+        ASSERT_GT(info.w, last_w);
+        ASSERT_GT(info.h, last_h);
       }
-      last_w = info->w;
-      last_h = info->h;
+      last_w = info.w;
+      last_h = info.h;
     }
   }
 
 #if CONFIG_VP9_DECODER
   // Verify that we get 4 resize events in this test.
   ASSERT_EQ(resize_count, 4) << "Resizing should occur twice.";
-  EXPECT_EQ(static_cast<unsigned int>(0), GetMismatchFrames());
+  EXPECT_EQ(GetMismatchFrames(), static_cast<unsigned int>(0));
 #else
   GTEST_SKIP()
       << "Warning: VP9 decoder unavailable, unable to check resize count!\n";
diff --git a/test/sad_test.cc b/test/sad_test.cc
index 73beb19..19227cc 100644
--- a/test/sad_test.cc
+++ b/test/sad_test.cc
@@ -1888,11 +1888,38 @@
 #endif  // HAVE_AVX2
 
 #if HAVE_AVX512
+const SadMxNParam avx512_tests[] = {
+  SadMxNParam(64, 64, &vpx_sad64x64_avx512),
+  SadMxNParam(64, 32, &vpx_sad64x32_avx512),
+};
+INSTANTIATE_TEST_SUITE_P(AVX512, SADTest, ::testing::ValuesIn(avx512_tests));
+
+const SadSkipMxNParam skip_avx512_tests[] = {
+  SadSkipMxNParam(64, 64, &vpx_sad_skip_64x64_avx512),
+  SadSkipMxNParam(64, 32, &vpx_sad_skip_64x32_avx512),
+};
+INSTANTIATE_TEST_SUITE_P(AVX512, SADSkipTest,
+                         ::testing::ValuesIn(skip_avx512_tests));
+
+const SadMxNAvgParam avg_avx512_tests[] = {
+  SadMxNAvgParam(64, 64, &vpx_sad64x64_avg_avx512),
+  SadMxNAvgParam(64, 32, &vpx_sad64x32_avg_avx512),
+};
+INSTANTIATE_TEST_SUITE_P(AVX512, SADavgTest,
+                         ::testing::ValuesIn(avg_avx512_tests));
+
 const SadMxNx4Param x4d_avx512_tests[] = {
   SadMxNx4Param(64, 64, &vpx_sad64x64x4d_avx512),
 };
 INSTANTIATE_TEST_SUITE_P(AVX512, SADx4Test,
                          ::testing::ValuesIn(x4d_avx512_tests));
+
+const SadSkipMxNx4Param skip_x4d_avx512_tests[] = {
+  SadSkipMxNx4Param(64, 64, &vpx_sad_skip_64x64x4d_avx512),
+  SadSkipMxNx4Param(64, 32, &vpx_sad_skip_64x32x4d_avx512),
+};
+INSTANTIATE_TEST_SUITE_P(AVX512, SADSkipx4Test,
+                         ::testing::ValuesIn(skip_x4d_avx512_tests));
 #endif  // HAVE_AVX512
 
 //------------------------------------------------------------------------------
diff --git a/test/simple_encode_test.cc b/test/simple_encode_test.cc
deleted file mode 100644
index 0f8671c..0000000
--- a/test/simple_encode_test.cc
+++ /dev/null
@@ -1,574 +0,0 @@
-/*
- *  Copyright (c) 2019 The WebM 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 <math.h>
-#include <memory>
-#include <string>
-#include <vector>
-#include "gtest/gtest.h"
-#include "test/video_source.h"
-#include "vp9/simple_encode.h"
-
-namespace vp9 {
-namespace {
-
-double GetBitrateInKbps(size_t bit_size, int num_frames, int frame_rate_num,
-                        int frame_rate_den) {
-  return static_cast<double>(bit_size) / num_frames * frame_rate_num /
-         frame_rate_den / 1000.0;
-}
-
-// Returns the number of unit in size of 4.
-// For example, if size is 7, return 2.
-int GetNumUnit4x4(int size) { return (size + 3) >> 2; }
-
-class SimpleEncodeTest : public ::testing::Test {
- protected:
-  const int width_ = 352;
-  const int height_ = 288;
-  const int frame_rate_num_ = 30;
-  const int frame_rate_den_ = 1;
-  const int target_bitrate_ = 1000;
-  const int num_frames_ = 17;
-  const int target_level_ = LEVEL_UNKNOWN;
-  const std::string in_file_path_str_ =
-      libvpx_test::GetDataPath() + "/bus_352x288_420_f20_b8.yuv";
-};
-
-TEST_F(SimpleEncodeTest, ComputeFirstPassStats) {
-  SimpleEncode simple_encode(width_, height_, frame_rate_num_, frame_rate_den_,
-                             target_bitrate_, num_frames_, target_level_,
-                             in_file_path_str_.c_str());
-  simple_encode.ComputeFirstPassStats();
-  std::vector<std::vector<double>> frame_stats =
-      simple_encode.ObserveFirstPassStats();
-  EXPECT_EQ(frame_stats.size(), static_cast<size_t>(num_frames_));
-  const size_t data_num = frame_stats[0].size();
-  // Read ObserveFirstPassStats before changing FIRSTPASS_STATS.
-  EXPECT_EQ(data_num, static_cast<size_t>(25));
-  for (size_t i = 0; i < frame_stats.size(); ++i) {
-    EXPECT_EQ(frame_stats[i].size(), data_num);
-    // FIRSTPASS_STATS's first element is frame
-    EXPECT_EQ(frame_stats[i][0], i);
-    // FIRSTPASS_STATS's last element is count, and the count is 1 for single
-    // frame stats
-    EXPECT_EQ(frame_stats[i][data_num - 1], 1);
-  }
-}
-
-TEST_F(SimpleEncodeTest, ObserveFirstPassMotionVectors) {
-  SimpleEncode simple_encode(width_, height_, frame_rate_num_, frame_rate_den_,
-                             target_bitrate_, num_frames_, target_level_,
-                             in_file_path_str_.c_str());
-  simple_encode.ComputeFirstPassStats();
-  std::vector<std::vector<MotionVectorInfo>> fps_motion_vectors =
-      simple_encode.ObserveFirstPassMotionVectors();
-  EXPECT_EQ(fps_motion_vectors.size(), static_cast<size_t>(num_frames_));
-  const size_t num_blocks = ((width_ + 15) >> 4) * ((height_ + 15) >> 4);
-  EXPECT_EQ(num_blocks, fps_motion_vectors[0].size());
-  for (size_t i = 0; i < fps_motion_vectors.size(); ++i) {
-    EXPECT_EQ(num_blocks, fps_motion_vectors[i].size());
-    for (size_t j = 0; j < num_blocks; ++j) {
-      const int mv_count = fps_motion_vectors[i][j].mv_count;
-      const int ref_count =
-          (fps_motion_vectors[i][j].ref_frame[0] != kRefFrameTypeNone) +
-          (fps_motion_vectors[i][j].ref_frame[1] != kRefFrameTypeNone);
-      EXPECT_EQ(mv_count, ref_count);
-    }
-  }
-}
-
-TEST_F(SimpleEncodeTest, GetCodingFrameNum) {
-  SimpleEncode simple_encode(width_, height_, frame_rate_num_, frame_rate_den_,
-                             target_bitrate_, num_frames_, target_level_,
-                             in_file_path_str_.c_str());
-  simple_encode.ComputeFirstPassStats();
-  const int num_coding_frames = simple_encode.GetCodingFrameNum();
-  EXPECT_EQ(num_coding_frames, 19);
-}
-
-TEST_F(SimpleEncodeTest, EncodeFrame) {
-  SimpleEncode simple_encode(width_, height_, frame_rate_num_, frame_rate_den_,
-                             target_bitrate_, num_frames_, target_level_,
-                             in_file_path_str_.c_str());
-  simple_encode.ComputeFirstPassStats();
-  int num_coding_frames = simple_encode.GetCodingFrameNum();
-  EXPECT_GE(num_coding_frames, num_frames_);
-  simple_encode.StartEncode();
-  size_t total_data_bit_size = 0;
-  int coded_show_frame_count = 0;
-  int frame_coding_index = 0;
-  while (coded_show_frame_count < num_frames_) {
-    const GroupOfPicture group_of_picture =
-        simple_encode.ObserveGroupOfPicture();
-    const std::vector<EncodeFrameInfo> &encode_frame_list =
-        group_of_picture.encode_frame_list;
-    for (size_t group_index = 0; group_index < encode_frame_list.size();
-         ++group_index) {
-      EncodeFrameResult encode_frame_result;
-      simple_encode.EncodeFrame(&encode_frame_result);
-      EXPECT_EQ(encode_frame_result.show_idx,
-                encode_frame_list[group_index].show_idx);
-      EXPECT_EQ(encode_frame_result.frame_type,
-                encode_frame_list[group_index].frame_type);
-      EXPECT_EQ(encode_frame_list[group_index].coding_index,
-                frame_coding_index);
-      EXPECT_GE(encode_frame_result.psnr, 34)
-          << "The psnr is supposed to be greater than 34 given the "
-             "target_bitrate 1000 kbps";
-      EXPECT_EQ(encode_frame_result.ref_frame_info,
-                encode_frame_list[group_index].ref_frame_info);
-      total_data_bit_size += encode_frame_result.coding_data_bit_size;
-      ++frame_coding_index;
-    }
-    coded_show_frame_count += group_of_picture.show_frame_count;
-  }
-  const double bitrate = GetBitrateInKbps(total_data_bit_size, num_frames_,
-                                          frame_rate_num_, frame_rate_den_);
-  const double off_target_threshold = 150;
-  EXPECT_LE(fabs(target_bitrate_ - bitrate), off_target_threshold);
-  simple_encode.EndEncode();
-}
-
-TEST_F(SimpleEncodeTest, ObserveKeyFrameMap) {
-  SimpleEncode simple_encode(width_, height_, frame_rate_num_, frame_rate_den_,
-                             target_bitrate_, num_frames_, target_level_,
-                             in_file_path_str_.c_str());
-  simple_encode.ComputeFirstPassStats();
-  std::vector<int> key_frame_map = simple_encode.ObserveKeyFrameMap();
-  EXPECT_EQ(key_frame_map.size(), static_cast<size_t>(num_frames_));
-  simple_encode.StartEncode();
-  int coded_show_frame_count = 0;
-  while (coded_show_frame_count < num_frames_) {
-    const GroupOfPicture group_of_picture =
-        simple_encode.ObserveGroupOfPicture();
-    const std::vector<EncodeFrameInfo> &encode_frame_list =
-        group_of_picture.encode_frame_list;
-    for (size_t group_index = 0; group_index < encode_frame_list.size();
-         ++group_index) {
-      EncodeFrameResult encode_frame_result;
-      simple_encode.EncodeFrame(&encode_frame_result);
-      if (encode_frame_result.frame_type == kFrameTypeKey) {
-        EXPECT_EQ(key_frame_map[encode_frame_result.show_idx], 1);
-      } else {
-        EXPECT_EQ(key_frame_map[encode_frame_result.show_idx], 0);
-      }
-    }
-    coded_show_frame_count += group_of_picture.show_frame_count;
-  }
-  simple_encode.EndEncode();
-}
-
-TEST_F(SimpleEncodeTest, EncodeFrameWithTargetFrameBits) {
-  SimpleEncode simple_encode(width_, height_, frame_rate_num_, frame_rate_den_,
-                             target_bitrate_, num_frames_, target_level_,
-                             in_file_path_str_.c_str());
-  simple_encode.ComputeFirstPassStats();
-  const int num_coding_frames = simple_encode.GetCodingFrameNum();
-  simple_encode.StartEncode();
-  for (int i = 0; i < num_coding_frames; ++i) {
-    EncodeFrameInfo encode_frame_info = simple_encode.GetNextEncodeFrameInfo();
-    int target_frame_bits;
-    switch (encode_frame_info.frame_type) {
-      case kFrameTypeInter: target_frame_bits = 20000; break;
-      case kFrameTypeKey:
-      case kFrameTypeAltRef:
-      case kFrameTypeGolden: target_frame_bits = 100000; break;
-      case kFrameTypeOverlay: target_frame_bits = 2000; break;
-      default: target_frame_bits = 20000;
-    }
-
-    double percent_diff = 15;
-    if (encode_frame_info.frame_type == kFrameTypeOverlay) {
-      percent_diff = 100;
-    }
-    EncodeFrameResult encode_frame_result;
-    simple_encode.EncodeFrameWithTargetFrameBits(
-        &encode_frame_result, target_frame_bits, percent_diff);
-    const int recode_count = encode_frame_result.recode_count;
-    // TODO(angiebird): Replace 7 by RATE_CTRL_MAX_RECODE_NUM
-    EXPECT_LE(recode_count, 7);
-    EXPECT_GE(recode_count, 1);
-
-    const double diff = fabs((double)encode_frame_result.coding_data_bit_size -
-                             target_frame_bits);
-    EXPECT_LE(diff * 100 / target_frame_bits, percent_diff);
-  }
-  simple_encode.EndEncode();
-}
-
-TEST_F(SimpleEncodeTest, EncodeFrameWithQuantizeIndex) {
-  SimpleEncode simple_encode(width_, height_, frame_rate_num_, frame_rate_den_,
-                             target_bitrate_, num_frames_, target_level_,
-                             in_file_path_str_.c_str());
-  simple_encode.ComputeFirstPassStats();
-  const int num_coding_frames = simple_encode.GetCodingFrameNum();
-  simple_encode.StartEncode();
-  for (int i = 0; i < num_coding_frames; ++i) {
-    const int assigned_quantize_index = 100 + i;
-    EncodeFrameResult encode_frame_result;
-    simple_encode.EncodeFrameWithQuantizeIndex(&encode_frame_result,
-                                               assigned_quantize_index);
-    EXPECT_EQ(encode_frame_result.quantize_index, assigned_quantize_index);
-  }
-  simple_encode.EndEncode();
-}
-
-// This test encodes the video using EncodeFrame(), where quantize indexes
-// are selected by vp9 rate control.
-// Encode stats and the quantize_indexes are collected.
-// Then the test encodes the video again using EncodeFrameWithQuantizeIndex()
-// using the quantize indexes collected from the first run.
-// Then test whether the encode stats of the two encoding runs match.
-TEST_F(SimpleEncodeTest, EncodeConsistencyTest) {
-  std::vector<int> quantize_index_list;
-  std::vector<uint64_t> ref_sse_list;
-  std::vector<double> ref_psnr_list;
-  std::vector<size_t> ref_bit_size_list;
-  std::vector<FrameType> ref_frame_type_list;
-  std::vector<int> ref_show_idx_list;
-  {
-    // The first encode.
-    SimpleEncode simple_encode(width_, height_, frame_rate_num_,
-                               frame_rate_den_, target_bitrate_, num_frames_,
-                               target_level_, in_file_path_str_.c_str());
-    simple_encode.ComputeFirstPassStats();
-    const int num_coding_frames = simple_encode.GetCodingFrameNum();
-    simple_encode.StartEncode();
-    for (int i = 0; i < num_coding_frames; ++i) {
-      EncodeFrameResult encode_frame_result;
-      simple_encode.EncodeFrame(&encode_frame_result);
-      quantize_index_list.push_back(encode_frame_result.quantize_index);
-      ref_sse_list.push_back(encode_frame_result.sse);
-      ref_psnr_list.push_back(encode_frame_result.psnr);
-      ref_bit_size_list.push_back(encode_frame_result.coding_data_bit_size);
-      ref_frame_type_list.push_back(encode_frame_result.frame_type);
-      ref_show_idx_list.push_back(encode_frame_result.show_idx);
-    }
-    simple_encode.EndEncode();
-  }
-  {
-    // The second encode with quantize index got from the first encode.
-    SimpleEncode simple_encode(width_, height_, frame_rate_num_,
-                               frame_rate_den_, target_bitrate_, num_frames_,
-                               target_level_, in_file_path_str_.c_str());
-    simple_encode.ComputeFirstPassStats();
-    const int num_coding_frames = simple_encode.GetCodingFrameNum();
-    EXPECT_EQ(static_cast<size_t>(num_coding_frames),
-              quantize_index_list.size());
-    simple_encode.StartEncode();
-    for (int i = 0; i < num_coding_frames; ++i) {
-      EncodeFrameResult encode_frame_result;
-      simple_encode.EncodeFrameWithQuantizeIndex(&encode_frame_result,
-                                                 quantize_index_list[i]);
-      EXPECT_EQ(encode_frame_result.quantize_index, quantize_index_list[i]);
-      EXPECT_EQ(encode_frame_result.sse, ref_sse_list[i]);
-      EXPECT_DOUBLE_EQ(encode_frame_result.psnr, ref_psnr_list[i]);
-      EXPECT_EQ(encode_frame_result.coding_data_bit_size, ref_bit_size_list[i]);
-      EXPECT_EQ(encode_frame_result.frame_type, ref_frame_type_list[i]);
-      EXPECT_EQ(encode_frame_result.show_idx, ref_show_idx_list[i]);
-    }
-    simple_encode.EndEncode();
-  }
-}
-
-// Test the information (partition info and motion vector info) stored in
-// encoder is the same between two encode runs.
-TEST_F(SimpleEncodeTest, EncodeConsistencyTest2) {
-  const int num_rows_4x4 = GetNumUnit4x4(width_);
-  const int num_cols_4x4 = GetNumUnit4x4(height_);
-  const int num_units_4x4 = num_rows_4x4 * num_cols_4x4;
-  // The first encode.
-  SimpleEncode simple_encode(width_, height_, frame_rate_num_, frame_rate_den_,
-                             target_bitrate_, num_frames_, target_level_,
-                             in_file_path_str_.c_str());
-  simple_encode.ComputeFirstPassStats();
-  const int num_coding_frames = simple_encode.GetCodingFrameNum();
-  std::vector<PartitionInfo> partition_info_list(num_units_4x4 *
-                                                 num_coding_frames);
-  std::vector<MotionVectorInfo> motion_vector_info_list(num_units_4x4 *
-                                                        num_coding_frames);
-  simple_encode.StartEncode();
-  for (int i = 0; i < num_coding_frames; ++i) {
-    EncodeFrameResult encode_frame_result;
-    simple_encode.EncodeFrame(&encode_frame_result);
-    for (int j = 0; j < num_rows_4x4 * num_cols_4x4; ++j) {
-      partition_info_list[i * num_units_4x4 + j] =
-          encode_frame_result.partition_info[j];
-      motion_vector_info_list[i * num_units_4x4 + j] =
-          encode_frame_result.motion_vector_info[j];
-    }
-  }
-  simple_encode.EndEncode();
-  // The second encode.
-  SimpleEncode simple_encode_2(width_, height_, frame_rate_num_,
-                               frame_rate_den_, target_bitrate_, num_frames_,
-                               target_level_, in_file_path_str_.c_str());
-  simple_encode_2.ComputeFirstPassStats();
-  const int num_coding_frames_2 = simple_encode_2.GetCodingFrameNum();
-  simple_encode_2.StartEncode();
-  for (int i = 0; i < num_coding_frames_2; ++i) {
-    EncodeFrameResult encode_frame_result;
-    simple_encode_2.EncodeFrame(&encode_frame_result);
-    for (int j = 0; j < num_rows_4x4 * num_cols_4x4; ++j) {
-      EXPECT_EQ(encode_frame_result.partition_info[j].row,
-                partition_info_list[i * num_units_4x4 + j].row);
-      EXPECT_EQ(encode_frame_result.partition_info[j].column,
-                partition_info_list[i * num_units_4x4 + j].column);
-      EXPECT_EQ(encode_frame_result.partition_info[j].row_start,
-                partition_info_list[i * num_units_4x4 + j].row_start);
-      EXPECT_EQ(encode_frame_result.partition_info[j].column_start,
-                partition_info_list[i * num_units_4x4 + j].column_start);
-      EXPECT_EQ(encode_frame_result.partition_info[j].width,
-                partition_info_list[i * num_units_4x4 + j].width);
-      EXPECT_EQ(encode_frame_result.partition_info[j].height,
-                partition_info_list[i * num_units_4x4 + j].height);
-
-      EXPECT_EQ(encode_frame_result.motion_vector_info[j].mv_count,
-                motion_vector_info_list[i * num_units_4x4 + j].mv_count);
-      EXPECT_EQ(encode_frame_result.motion_vector_info[j].ref_frame[0],
-                motion_vector_info_list[i * num_units_4x4 + j].ref_frame[0]);
-      EXPECT_EQ(encode_frame_result.motion_vector_info[j].ref_frame[1],
-                motion_vector_info_list[i * num_units_4x4 + j].ref_frame[1]);
-      EXPECT_EQ(encode_frame_result.motion_vector_info[j].mv_row[0],
-                motion_vector_info_list[i * num_units_4x4 + j].mv_row[0]);
-      EXPECT_EQ(encode_frame_result.motion_vector_info[j].mv_column[0],
-                motion_vector_info_list[i * num_units_4x4 + j].mv_column[0]);
-      EXPECT_EQ(encode_frame_result.motion_vector_info[j].mv_row[1],
-                motion_vector_info_list[i * num_units_4x4 + j].mv_row[1]);
-      EXPECT_EQ(encode_frame_result.motion_vector_info[j].mv_column[1],
-                motion_vector_info_list[i * num_units_4x4 + j].mv_column[1]);
-    }
-  }
-  simple_encode_2.EndEncode();
-}
-
-// Test the information stored in encoder is the same between two encode runs.
-TEST_F(SimpleEncodeTest, EncodeConsistencyTest3) {
-  std::vector<int> quantize_index_list;
-  const int num_rows_4x4 = GetNumUnit4x4(width_);
-  const int num_cols_4x4 = GetNumUnit4x4(height_);
-  const int num_units_4x4 = num_rows_4x4 * num_cols_4x4;
-  // The first encode.
-  SimpleEncode simple_encode(width_, height_, frame_rate_num_, frame_rate_den_,
-                             target_bitrate_, num_frames_, target_level_,
-                             in_file_path_str_.c_str());
-  simple_encode.ComputeFirstPassStats();
-  const int num_coding_frames = simple_encode.GetCodingFrameNum();
-  std::vector<PartitionInfo> partition_info_list(num_units_4x4 *
-                                                 num_coding_frames);
-  simple_encode.StartEncode();
-  for (int i = 0; i < num_coding_frames; ++i) {
-    EncodeFrameResult encode_frame_result;
-    simple_encode.EncodeFrame(&encode_frame_result);
-    quantize_index_list.push_back(encode_frame_result.quantize_index);
-    for (int j = 0; j < num_rows_4x4 * num_cols_4x4; ++j) {
-      partition_info_list[i * num_units_4x4 + j] =
-          encode_frame_result.partition_info[j];
-    }
-  }
-  simple_encode.EndEncode();
-  // The second encode.
-  SimpleEncode simple_encode_2(width_, height_, frame_rate_num_,
-                               frame_rate_den_, target_bitrate_, num_frames_,
-                               target_level_, in_file_path_str_.c_str());
-  simple_encode_2.ComputeFirstPassStats();
-  const int num_coding_frames_2 = simple_encode_2.GetCodingFrameNum();
-  simple_encode_2.StartEncode();
-  for (int i = 0; i < num_coding_frames_2; ++i) {
-    EncodeFrameResult encode_frame_result;
-    simple_encode_2.EncodeFrameWithQuantizeIndex(&encode_frame_result,
-                                                 quantize_index_list[i]);
-    for (int j = 0; j < num_rows_4x4 * num_cols_4x4; ++j) {
-      EXPECT_EQ(encode_frame_result.partition_info[j].row,
-                partition_info_list[i * num_units_4x4 + j].row);
-      EXPECT_EQ(encode_frame_result.partition_info[j].column,
-                partition_info_list[i * num_units_4x4 + j].column);
-      EXPECT_EQ(encode_frame_result.partition_info[j].row_start,
-                partition_info_list[i * num_units_4x4 + j].row_start);
-      EXPECT_EQ(encode_frame_result.partition_info[j].column_start,
-                partition_info_list[i * num_units_4x4 + j].column_start);
-      EXPECT_EQ(encode_frame_result.partition_info[j].width,
-                partition_info_list[i * num_units_4x4 + j].width);
-      EXPECT_EQ(encode_frame_result.partition_info[j].height,
-                partition_info_list[i * num_units_4x4 + j].height);
-    }
-  }
-  simple_encode_2.EndEncode();
-}
-
-// Encode with default VP9 decision first.
-// Get QPs and arf locations from the first encode.
-// Set external arfs and QPs for the second encode.
-// Expect to get matched results.
-TEST_F(SimpleEncodeTest, EncodeConsistencySetExternalGroupOfPicturesMap) {
-  std::vector<int> quantize_index_list;
-  std::vector<uint64_t> ref_sse_list;
-  std::vector<double> ref_psnr_list;
-  std::vector<size_t> ref_bit_size_list;
-  std::vector<int> gop_map(num_frames_, 0);
-  {
-    // The first encode.
-    SimpleEncode simple_encode(width_, height_, frame_rate_num_,
-                               frame_rate_den_, target_bitrate_, num_frames_,
-                               target_level_, in_file_path_str_.c_str());
-    simple_encode.ComputeFirstPassStats();
-    simple_encode.StartEncode();
-
-    int coded_show_frame_count = 0;
-    while (coded_show_frame_count < num_frames_) {
-      const GroupOfPicture group_of_picture =
-          simple_encode.ObserveGroupOfPicture();
-      gop_map[coded_show_frame_count] |= kGopMapFlagStart;
-      if (group_of_picture.use_alt_ref) {
-        gop_map[coded_show_frame_count] |= kGopMapFlagUseAltRef;
-      }
-      const std::vector<EncodeFrameInfo> &encode_frame_list =
-          group_of_picture.encode_frame_list;
-      for (size_t group_index = 0; group_index < encode_frame_list.size();
-           ++group_index) {
-        EncodeFrameResult encode_frame_result;
-        simple_encode.EncodeFrame(&encode_frame_result);
-        quantize_index_list.push_back(encode_frame_result.quantize_index);
-        ref_sse_list.push_back(encode_frame_result.sse);
-        ref_psnr_list.push_back(encode_frame_result.psnr);
-        ref_bit_size_list.push_back(encode_frame_result.coding_data_bit_size);
-      }
-      coded_show_frame_count += group_of_picture.show_frame_count;
-    }
-    simple_encode.EndEncode();
-  }
-  {
-    // The second encode with quantize index got from the first encode.
-    // The external arfs are the same as the first encode.
-    SimpleEncode simple_encode(width_, height_, frame_rate_num_,
-                               frame_rate_den_, target_bitrate_, num_frames_,
-                               target_level_, in_file_path_str_.c_str());
-    simple_encode.ComputeFirstPassStats();
-    simple_encode.SetExternalGroupOfPicturesMap(gop_map.data(), gop_map.size());
-    const int num_coding_frames = simple_encode.GetCodingFrameNum();
-    EXPECT_EQ(static_cast<size_t>(num_coding_frames),
-              quantize_index_list.size());
-    simple_encode.StartEncode();
-    for (int i = 0; i < num_coding_frames; ++i) {
-      EncodeFrameResult encode_frame_result;
-      simple_encode.EncodeFrameWithQuantizeIndex(&encode_frame_result,
-                                                 quantize_index_list[i]);
-      EXPECT_EQ(encode_frame_result.quantize_index, quantize_index_list[i]);
-      EXPECT_EQ(encode_frame_result.sse, ref_sse_list[i]);
-      EXPECT_DOUBLE_EQ(encode_frame_result.psnr, ref_psnr_list[i]);
-      EXPECT_EQ(encode_frame_result.coding_data_bit_size, ref_bit_size_list[i]);
-    }
-    simple_encode.EndEncode();
-  }
-}
-
-TEST_F(SimpleEncodeTest, SetExternalGroupOfPicturesMap) {
-  SimpleEncode simple_encode(width_, height_, frame_rate_num_, frame_rate_den_,
-                             target_bitrate_, num_frames_, target_level_,
-                             in_file_path_str_.c_str());
-  simple_encode.ComputeFirstPassStats();
-
-  std::vector<int> gop_map(num_frames_, 0);
-
-  // Should be the first gop group.
-  gop_map[0] = 0;
-
-  // Second gop group with an alt ref.
-  gop_map[5] |= kGopMapFlagStart | kGopMapFlagUseAltRef;
-
-  // Third gop group without an alt ref.
-  gop_map[10] |= kGopMapFlagStart;
-
-  // Last gop group.
-  gop_map[14] |= kGopMapFlagStart | kGopMapFlagUseAltRef;
-
-  simple_encode.SetExternalGroupOfPicturesMap(gop_map.data(), gop_map.size());
-
-  std::vector<int> observed_gop_map =
-      simple_encode.ObserveExternalGroupOfPicturesMap();
-
-  // First gop group.
-  // There is always a key frame at show_idx 0 and key frame should always be
-  // the start of a gop. We expect ObserveExternalGroupOfPicturesMap() will
-  // insert an extra gop start here.
-  EXPECT_EQ(observed_gop_map[0], kGopMapFlagStart | kGopMapFlagUseAltRef);
-
-  // Second gop group with an alt ref.
-  EXPECT_EQ(observed_gop_map[5], kGopMapFlagStart | kGopMapFlagUseAltRef);
-
-  // Third gop group without an alt ref.
-  EXPECT_EQ(observed_gop_map[10], kGopMapFlagStart);
-
-  // Last gop group. The last gop is not supposed to use an alt ref. We expect
-  // ObserveExternalGroupOfPicturesMap() will remove the alt ref flag here.
-  EXPECT_EQ(observed_gop_map[14], kGopMapFlagStart);
-
-  int ref_gop_show_frame_count_list[4] = { 5, 5, 4, 3 };
-  size_t ref_gop_coded_frame_count_list[4] = { 6, 6, 4, 3 };
-  int gop_count = 0;
-
-  simple_encode.StartEncode();
-  int coded_show_frame_count = 0;
-  while (coded_show_frame_count < num_frames_) {
-    const GroupOfPicture group_of_picture =
-        simple_encode.ObserveGroupOfPicture();
-    const std::vector<EncodeFrameInfo> &encode_frame_list =
-        group_of_picture.encode_frame_list;
-    EXPECT_EQ(encode_frame_list.size(),
-              ref_gop_coded_frame_count_list[gop_count]);
-    EXPECT_EQ(group_of_picture.show_frame_count,
-              ref_gop_show_frame_count_list[gop_count]);
-    for (size_t group_index = 0; group_index < encode_frame_list.size();
-         ++group_index) {
-      EncodeFrameResult encode_frame_result;
-      simple_encode.EncodeFrame(&encode_frame_result);
-    }
-    coded_show_frame_count += group_of_picture.show_frame_count;
-    ++gop_count;
-  }
-  EXPECT_EQ(gop_count, 4);
-  simple_encode.EndEncode();
-}
-
-TEST_F(SimpleEncodeTest, GetEncodeFrameInfo) {
-  // Makes sure that the encode_frame_info obtained from GetEncodeFrameInfo()
-  // matches the counterpart in encode_frame_result obtained from EncodeFrame()
-  SimpleEncode simple_encode(width_, height_, frame_rate_num_, frame_rate_den_,
-                             target_bitrate_, num_frames_, target_level_,
-                             in_file_path_str_.c_str());
-  simple_encode.ComputeFirstPassStats();
-  const int num_coding_frames = simple_encode.GetCodingFrameNum();
-  simple_encode.StartEncode();
-  for (int i = 0; i < num_coding_frames; ++i) {
-    EncodeFrameInfo encode_frame_info = simple_encode.GetNextEncodeFrameInfo();
-    EncodeFrameResult encode_frame_result;
-    simple_encode.EncodeFrame(&encode_frame_result);
-    EXPECT_EQ(encode_frame_info.show_idx, encode_frame_result.show_idx);
-    EXPECT_EQ(encode_frame_info.frame_type, encode_frame_result.frame_type);
-  }
-  simple_encode.EndEncode();
-}
-
-TEST_F(SimpleEncodeTest, GetFramePixelCount) {
-  SimpleEncode simple_encode(width_, height_, frame_rate_num_, frame_rate_den_,
-                             target_bitrate_, num_frames_, target_level_,
-                             in_file_path_str_.c_str());
-  EXPECT_EQ(simple_encode.GetFramePixelCount(),
-            static_cast<uint64_t>(width_ * height_ * 3 / 2));
-}
-
-}  // namespace
-}  // namespace vp9
-
-int main(int argc, char **argv) {
-  ::testing::InitGoogleTest(&argc, argv);
-  return RUN_ALL_TESTS();
-}
diff --git a/test/svc_datarate_test.cc b/test/svc_datarate_test.cc
index 0ff0c8a..5e74d7e 100644
--- a/test/svc_datarate_test.cc
+++ b/test/svc_datarate_test.cc
@@ -81,10 +81,13 @@
     num_resize_down_ = 0;
     num_resize_up_ = 0;
     for (int i = 0; i < VPX_MAX_LAYERS; i++) {
-      prev_frame_width[i] = 320;
-      prev_frame_height[i] = 240;
+      prev_frame_width_[i] = 320;
+      prev_frame_height_[i] = 240;
     }
     ksvc_flex_noupd_tlenh_ = false;
+    external_resize_dynamic_drop_layer_ = false;
+    external_resize_pattern_ = 0;
+    superframe_cnt_ = 0;
   }
   void BeginPassHook(unsigned int /*pass*/) override {}
 
@@ -207,6 +210,8 @@
       if (use_post_encode_drop_) {
         encoder->Control(VP9E_SET_POSTENCODE_DROP, use_post_encode_drop_);
       }
+      // We want to force external resize on the very first frame.
+      if (external_resize_dynamic_drop_layer_) video->Next();
     }
 
     if (denoiser_off_on_) {
@@ -316,7 +321,98 @@
       encoder->Config(&cfg_);
     }
 
-    if (dynamic_drop_layer_ && !single_layer_resize_) {
+    if (external_resize_dynamic_drop_layer_) {
+      frame_flags_ = 0;
+      for (int i = 0; i < 9; ++i) {
+        svc_params_.min_quantizers[i] = 20;
+        svc_params_.max_quantizers[i] = 56;
+      }
+      if (video->frame() == 1 || video->frame() == 150) {
+        // Set the new top width/height for external resize.
+        top_sl_width_ = video->img()->d_w;
+        top_sl_height_ = video->img()->d_h;
+        for (int i = 0; i < 9; ++i) {
+          bitrate_layer_[i] = cfg_.layer_target_bitrate[i];
+        }
+        if (external_resize_pattern_ == 1) {
+          // Input size is 1/4. 2 top spatial layers are dropped.
+          // This will trigger skip encoding/dropping of two top spatial layers.
+          cfg_.rc_target_bitrate -=
+              cfg_.layer_target_bitrate[5] + cfg_.layer_target_bitrate[8];
+          for (int i = 3; i < 9; ++i) {
+            cfg_.layer_target_bitrate[i] = 0;
+          }
+          for (int sl = 0; sl < 3; sl++) {
+            svc_params_.scaling_factor_num[sl] = 1;
+            svc_params_.scaling_factor_den[sl] = 1;
+          }
+        } else if (external_resize_pattern_ == 2) {
+          // Input size is 1/2. Top spatial layer is dropped.
+          // This will trigger skip encoding/dropping of top spatial layer.
+          cfg_.rc_target_bitrate -= cfg_.layer_target_bitrate[8];
+          for (int i = 6; i < 9; ++i) {
+            cfg_.layer_target_bitrate[i] = 0;
+          }
+          svc_params_.scaling_factor_num[0] = 1;
+          svc_params_.scaling_factor_den[0] = 2;
+          svc_params_.scaling_factor_num[1] = 1;
+          svc_params_.scaling_factor_den[1] = 1;
+          svc_params_.scaling_factor_num[2] = 1;
+          svc_params_.scaling_factor_den[2] = 1;
+        }
+        encoder->Config(&cfg_);
+        encoder->Control(VP9E_SET_SVC_PARAMETERS, &svc_params_);
+      } else if (video->frame() == 50 || video->frame() == 200) {
+        top_sl_width_ = video->img()->d_w;
+        top_sl_height_ = video->img()->d_h;
+        if (external_resize_pattern_ == 1) {
+          // Input size is 1/2. Change layer bitrates to set top layer to 0.
+          // This will trigger skip encoding/dropping of top spatial layer.
+          cfg_.rc_target_bitrate += bitrate_layer_[5];
+          for (int i = 3; i < 6; ++i) {
+            cfg_.layer_target_bitrate[i] = bitrate_layer_[i];
+          }
+          svc_params_.scaling_factor_num[0] = 1;
+          svc_params_.scaling_factor_den[0] = 2;
+          svc_params_.scaling_factor_num[1] = 1;
+          svc_params_.scaling_factor_den[1] = 1;
+          svc_params_.scaling_factor_num[2] = 1;
+          svc_params_.scaling_factor_den[2] = 1;
+        } else if (external_resize_pattern_ == 2) {
+          // Input size is 1/4. Change layer bitrates to set two top layers to
+          // 0. This will trigger skip encoding/dropping of two top spatial
+          // layers.
+          cfg_.rc_target_bitrate -= bitrate_layer_[5];
+          for (int i = 3; i < 6; ++i) {
+            cfg_.layer_target_bitrate[i] = 0;
+          }
+          for (int sl = 0; sl < 3; sl++) {
+            svc_params_.scaling_factor_num[sl] = 1;
+            svc_params_.scaling_factor_den[sl] = 1;
+          }
+        }
+        encoder->Config(&cfg_);
+        encoder->Control(VP9E_SET_SVC_PARAMETERS, &svc_params_);
+      } else if (video->frame() == 100 || video->frame() == 250) {
+        top_sl_width_ = video->img()->d_w;
+        top_sl_height_ = video->img()->d_h;
+        // Input is original size. Change layer bitrates to nonzero for all
+        // layers.
+        cfg_.rc_target_bitrate =
+            bitrate_layer_[2] + bitrate_layer_[5] + bitrate_layer_[8];
+        for (int i = 0; i < 9; ++i) {
+          cfg_.layer_target_bitrate[i] = bitrate_layer_[i];
+        }
+        svc_params_.scaling_factor_num[0] = 1;
+        svc_params_.scaling_factor_den[0] = 4;
+        svc_params_.scaling_factor_num[1] = 1;
+        svc_params_.scaling_factor_den[1] = 2;
+        svc_params_.scaling_factor_num[2] = 1;
+        svc_params_.scaling_factor_den[2] = 1;
+        encoder->Config(&cfg_);
+        encoder->Control(VP9E_SET_SVC_PARAMETERS, &svc_params_);
+      }
+    } else if (dynamic_drop_layer_ && !single_layer_resize_) {
       if (video->frame() == 0) {
         // Change layer bitrates to set top layers to 0. This will trigger skip
         // encoding/dropping of top two spatial layers.
@@ -432,6 +528,7 @@
     const vpx_rational_t tb = video->timebase();
     timebase_ = static_cast<double>(tb.num) / tb.den;
     duration_ = 0;
+    superframe_cnt_++;
   }
 
   vpx_codec_err_t parse_superframe_index(const uint8_t *data, size_t data_sz,
@@ -476,6 +573,12 @@
     last_pts_ = pkt->data.frame.pts;
     const bool key_frame =
         (pkt->data.frame.flags & VPX_FRAME_IS_KEY) ? true : false;
+    if (external_resize_dynamic_drop_layer_) {
+      // No key frames expected in stream, except for first.
+      if (cfg_.kf_max_dist > 1000) {
+        ASSERT_FALSE(key_frame && superframe_cnt_ > 1);
+      }
+    }
     if (key_frame) {
       // For test that inserts layer sync frames: requesting a layer_sync on
       // the base layer must force key frame. So if any key frame occurs after
@@ -547,7 +650,7 @@
         }
       }
 
-      if (!single_layer_resize_) {
+      if (!single_layer_resize_ && sl < number_spatial_layers_ - 1) {
         unsigned int scaled_width = top_sl_width_ *
                                     svc_params_.scaling_factor_num[sl] /
                                     svc_params_.scaling_factor_den[sl];
@@ -559,15 +662,15 @@
         if (scaled_height % 2 != 0) scaled_height += 1;
         ASSERT_EQ(pkt->data.frame.height[sl], scaled_height);
       } else if (superframe_count_ > 0) {
-        if (pkt->data.frame.width[sl] < prev_frame_width[sl] &&
-            pkt->data.frame.height[sl] < prev_frame_height[sl])
+        if (pkt->data.frame.width[sl] < prev_frame_width_[sl] &&
+            pkt->data.frame.height[sl] < prev_frame_height_[sl])
           num_resize_down_ += 1;
-        if (pkt->data.frame.width[sl] > prev_frame_width[sl] &&
-            pkt->data.frame.height[sl] > prev_frame_height[sl])
+        if (pkt->data.frame.width[sl] > prev_frame_width_[sl] &&
+            pkt->data.frame.height[sl] > prev_frame_height_[sl])
           num_resize_up_ += 1;
       }
-      prev_frame_width[sl] = pkt->data.frame.width[sl];
-      prev_frame_height[sl] = pkt->data.frame.height[sl];
+      prev_frame_width_[sl] = pkt->data.frame.width[sl];
+      prev_frame_height_[sl] = pkt->data.frame.height[sl];
     }
   }
 
@@ -634,9 +737,13 @@
   bool denoiser_enable_layers_;
   int num_resize_up_;
   int num_resize_down_;
-  unsigned int prev_frame_width[VPX_MAX_LAYERS];
-  unsigned int prev_frame_height[VPX_MAX_LAYERS];
+  unsigned int prev_frame_width_[VPX_MAX_LAYERS];
+  unsigned int prev_frame_height_[VPX_MAX_LAYERS];
   bool ksvc_flex_noupd_tlenh_;
+  bool external_resize_dynamic_drop_layer_;
+  int bitrate_layer_[9];
+  int external_resize_pattern_;
+  int superframe_cnt_;
 
  private:
   void SetConfig(const int num_temporal_layer) override {
@@ -662,6 +769,80 @@
   unsigned int mismatch_nframes_;
 };
 
+void ScaleForFrameNumber(unsigned int frame, unsigned int initial_w,
+                         unsigned int initial_h, unsigned int *w,
+                         unsigned int *h, int resize_pattern) {
+  *w = initial_w;
+  *h = initial_h;
+  if (resize_pattern == 1) {
+    if (frame < 50) {
+      *w = initial_w / 4;
+      *h = initial_h / 4;
+    } else if (frame < 100) {
+      *w = initial_w / 2;
+      *h = initial_h / 2;
+    } else if (frame < 150) {
+      *w = initial_w;
+      *h = initial_h;
+    } else if (frame < 200) {
+      *w = initial_w / 4;
+      *h = initial_h / 4;
+    } else if (frame < 250) {
+      *w = initial_w / 2;
+      *h = initial_h / 2;
+    }
+  } else if (resize_pattern == 2) {
+    if (frame < 50) {
+      *w = initial_w / 2;
+      *h = initial_h / 2;
+    } else if (frame < 100) {
+      *w = initial_w / 4;
+      *h = initial_h / 4;
+    } else if (frame < 150) {
+      *w = initial_w;
+      *h = initial_h;
+    } else if (frame < 200) {
+      *w = initial_w / 2;
+      *h = initial_h / 2;
+    } else if (frame < 250) {
+      *w = initial_w / 4;
+      *h = initial_h / 4;
+    }
+  }
+}
+
+class ResizingVideoSource : public ::libvpx_test::DummyVideoSource {
+ public:
+  ResizingVideoSource(int width, int height) {
+    top_width_ = width;
+    top_height_ = height;
+    SetSize(top_width_, top_height_);
+    limit_ = 300;
+  }
+  int external_resize_pattern_ = 1;
+  int force_zero_source_ = 0;
+  int top_width_;
+  int top_height_;
+  ~ResizingVideoSource() override = default;
+
+ protected:
+  void Next() override {
+    ++frame_;
+    unsigned int width = 0;
+    unsigned int height = 0;
+    libvpx_test::ACMRandom rnd(libvpx_test::ACMRandom::DeterministicSeed());
+    ScaleForFrameNumber(frame_, top_width_, top_height_, &width, &height,
+                        external_resize_pattern_);
+    SetSize(width, height);
+    FillFrame();
+    unsigned char *image = img_->planes[0];
+    for (size_t i = 0; i < raw_sz_; ++i) {
+      image[i] = rnd.Rand8();
+      if (force_zero_source_ && frame_ % 20 == 0) image[i] = 0;
+    }
+  }
+};
+
 // Params: speed setting.
 class DatarateOnePassCbrSvcSingleBR
     : public DatarateOnePassCbrSvc,
@@ -1070,7 +1251,7 @@
 #endif
 }
 
-// For  pass CBR SVC with 1 spatial and 2 temporal layers with dynamic resize
+// For 1 pass CBR SVC with 1 spatial and 2 temporal layers with dynamic resize
 // and denoiser enabled. The resizer will resize the single layer down and back
 // up again, as the bitrate goes back up.
 TEST_P(DatarateOnePassCbrSvcSingleBR, OnePassCbrSvc1SL2TL_DenoiseResize) {
@@ -1152,6 +1333,172 @@
 #endif
 }
 
+// For 1 pass CBR SVC with 3 spatial and 3 temporal layers with external resize
+// and denoiser enabled. The external resizer will resize down and back up,
+// setting 0/nonzero bitrate on spatial enhancement layers to disable/enable
+// layers. Resizing starts on first frame and the pattern is:
+//  1/4 -> 1/2 -> 1 -> 1/4 -> 1/2.
+TEST_P(DatarateOnePassCbrSvcSingleBR,
+       OnePassCbrSvc3SL3TL_DenoiseExternalResizePattern1) {
+  SetSvcConfig(3, 3);
+  cfg_.rc_buf_initial_sz = 500;
+  cfg_.rc_buf_optimal_sz = 500;
+  cfg_.rc_buf_sz = 1000;
+  cfg_.rc_min_quantizer = 40;
+  cfg_.rc_max_quantizer = 63;
+  cfg_.g_threads = 1;
+  cfg_.temporal_layering_mode = 3;
+  cfg_.ts_rate_decimator[0] = 4;
+  cfg_.ts_rate_decimator[1] = 2;
+  cfg_.ts_rate_decimator[2] = 1;
+  cfg_.rc_dropframe_thresh = 1;
+  cfg_.kf_max_dist = 10000;
+  cfg_.kf_min_dist = 10000;
+  cfg_.rc_resize_allowed = 0;
+  cfg_.g_w = 1280;
+  cfg_.g_h = 720;
+  top_sl_width_ = 1280;
+  top_sl_height_ = 720;
+  ResizingVideoSource video(1280, 720);
+  video.external_resize_pattern_ = 1;
+  video.force_zero_source_ = 0;
+  cfg_.rc_target_bitrate = 1000;
+  ResetModel();
+  dynamic_drop_layer_ = false;
+  single_layer_resize_ = false;
+  denoiser_on_ = 1;
+  base_speed_setting_ = speed_setting_;
+  external_resize_dynamic_drop_layer_ = true;
+  external_resize_pattern_ = video.external_resize_pattern_;
+  AssignLayerBitrates();
+  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
+}
+
+// For 1 pass CBR SVC with 3 spatial and 3 temporal layers with external resize
+// and denoiser enabled. The external resizer will resize down and back up,
+// setting 0/nonzero bitrate on spatial enhancement layers to disable/enable
+// layers. Resizing starts on first frame and the pattern is:
+//  1/2 -> 1/4 -> 1 -> 1/2 -> 1/4.
+TEST_P(DatarateOnePassCbrSvcSingleBR,
+       OnePassCbrSvc3SL3TL_DenoiseExternalResizePattern2) {
+  SetSvcConfig(3, 3);
+  cfg_.rc_buf_initial_sz = 500;
+  cfg_.rc_buf_optimal_sz = 500;
+  cfg_.rc_buf_sz = 1000;
+  cfg_.rc_min_quantizer = 40;
+  cfg_.rc_max_quantizer = 63;
+  cfg_.g_threads = 1;
+  cfg_.temporal_layering_mode = 3;
+  cfg_.ts_rate_decimator[0] = 4;
+  cfg_.ts_rate_decimator[1] = 2;
+  cfg_.ts_rate_decimator[2] = 1;
+  cfg_.rc_dropframe_thresh = 1;
+  cfg_.kf_max_dist = 10000;
+  cfg_.kf_min_dist = 10000;
+  cfg_.rc_resize_allowed = 0;
+  cfg_.g_w = 1280;
+  cfg_.g_h = 720;
+  top_sl_width_ = 1280;
+  top_sl_height_ = 720;
+  ResizingVideoSource video(1280, 720);
+  video.external_resize_pattern_ = 2;
+  video.force_zero_source_ = 0;
+  cfg_.rc_target_bitrate = 1000;
+  ResetModel();
+  dynamic_drop_layer_ = false;
+  single_layer_resize_ = false;
+  denoiser_on_ = 1;
+  base_speed_setting_ = speed_setting_;
+  external_resize_dynamic_drop_layer_ = true;
+  external_resize_pattern_ = video.external_resize_pattern_;
+  AssignLayerBitrates();
+  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
+}
+
+// For 1 pass CBR SVC with 3 spatial and 3 temporal layers with external resize
+// and denoiser enabled. The external resizer will resize down and back up,
+// setting 0/nonzero bitrate on spatial enhancement layers to disable/enable
+// layers. Resizing starts on first frame and the pattern is:
+//  1/2 -> 1/4 -> 1 -> 1/2 -> 1/4. This test uses 4 threads with small keyframe
+// spacing, and top resolution is 1280x960.
+TEST_P(DatarateOnePassCbrSvcSingleBR,
+       OnePassCbrSvc3SL3TL_DenoiseExternalResizePattern2Key4Threads) {
+  SetSvcConfig(3, 3);
+  cfg_.rc_buf_initial_sz = 500;
+  cfg_.rc_buf_optimal_sz = 500;
+  cfg_.rc_buf_sz = 1000;
+  cfg_.rc_min_quantizer = 40;
+  cfg_.rc_max_quantizer = 63;
+  cfg_.g_threads = 4;
+  cfg_.temporal_layering_mode = 3;
+  cfg_.ts_rate_decimator[0] = 4;
+  cfg_.ts_rate_decimator[1] = 2;
+  cfg_.ts_rate_decimator[2] = 1;
+  cfg_.rc_dropframe_thresh = 1;
+  cfg_.kf_max_dist = 40;
+  cfg_.kf_min_dist = 40;
+  cfg_.rc_resize_allowed = 0;
+  cfg_.g_w = 1280;
+  cfg_.g_h = 960;
+  top_sl_width_ = cfg_.g_w;
+  top_sl_height_ = cfg_.g_h;
+  ResizingVideoSource video(1280, 960);
+  video.external_resize_pattern_ = 2;
+  video.force_zero_source_ = 0;
+  cfg_.rc_target_bitrate = 1000;
+  ResetModel();
+  dynamic_drop_layer_ = false;
+  single_layer_resize_ = false;
+  denoiser_on_ = 1;
+  base_speed_setting_ = speed_setting_;
+  external_resize_dynamic_drop_layer_ = true;
+  external_resize_pattern_ = video.external_resize_pattern_;
+  AssignLayerBitrates();
+  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
+}
+
+// For 1 pass CBR SVC with 3 spatial and 3 temporal layers with external resize
+// and denoiser enabled. The external resizer will resize down and back up,
+// setting 0/nonzero bitrate on spatial enhancement layers to disable/enable
+// layers. Resizing starts on first frame and the pattern is:
+//  1/4 -> 1/2 -> 1 -> 1/4 -> 1/2. The source will be set to 0 every x frames,
+// otherwise random values, to trigger scene detection in the encoder.
+TEST_P(DatarateOnePassCbrSvcSingleBR,
+       OnePassCbrSvc3SL3TL_DenoiseExternalResizePattern1SceneChange) {
+  SetSvcConfig(3, 3);
+  cfg_.rc_buf_initial_sz = 500;
+  cfg_.rc_buf_optimal_sz = 500;
+  cfg_.rc_buf_sz = 1000;
+  cfg_.rc_min_quantizer = 40;
+  cfg_.rc_max_quantizer = 63;
+  cfg_.g_threads = 1;
+  cfg_.temporal_layering_mode = 3;
+  cfg_.ts_rate_decimator[0] = 4;
+  cfg_.ts_rate_decimator[1] = 2;
+  cfg_.ts_rate_decimator[2] = 1;
+  cfg_.rc_dropframe_thresh = 1;
+  cfg_.kf_max_dist = 10000;
+  cfg_.kf_min_dist = 10000;
+  cfg_.rc_resize_allowed = 0;
+  cfg_.g_w = 1280;
+  cfg_.g_h = 720;
+  top_sl_width_ = 1280;
+  top_sl_height_ = 720;
+  ResizingVideoSource video(1280, 720);
+  video.external_resize_pattern_ = 1;
+  video.force_zero_source_ = 1;
+  cfg_.rc_target_bitrate = 1000;
+  ResetModel();
+  dynamic_drop_layer_ = false;
+  single_layer_resize_ = false;
+  denoiser_on_ = 1;
+  base_speed_setting_ = speed_setting_;
+  external_resize_dynamic_drop_layer_ = true;
+  external_resize_pattern_ = video.external_resize_pattern_;
+  AssignLayerBitrates();
+  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
+}
+
 // Params: speed setting and index for bitrate array.
 class DatarateOnePassCbrSvcMultiBR
     : public DatarateOnePassCbrSvc,
@@ -1317,6 +1664,71 @@
 }
 
 // Check basic rate targeting for 1 pass CBR SVC: 3 spatial layers and
+// 3 temporal layers. Run HD clip with 4 threads, for 1284x770, which
+// likely is the issue for Bug: 366146260.
+TEST_P(DatarateOnePassCbrSvcFrameDropMultiBR,
+       OnePassCbrSvc3SL3TL4Threads1284x770) {
+  SetSvcConfig(3, 3);
+  cfg_.rc_buf_initial_sz = 500;
+  cfg_.rc_buf_optimal_sz = 500;
+  cfg_.rc_buf_sz = 1000;
+  cfg_.rc_min_quantizer = 0;
+  cfg_.rc_max_quantizer = 63;
+  cfg_.g_threads = 4;
+  cfg_.rc_dropframe_thresh = 30;
+  cfg_.kf_max_dist = 9999;
+  ::libvpx_test::Y4mVideoSource video("niklas_1284_770_30.y4m", 0, 60);
+  top_sl_width_ = 1284;
+  top_sl_height_ = 770;
+  layer_framedrop_ = 0;
+  const int bitrates[3] = { 200, 400, 600 };
+  cfg_.rc_target_bitrate = bitrates[GET_PARAM(3)];
+  ResetModel();
+  layer_framedrop_ = GET_PARAM(2);
+  AssignLayerBitrates();
+  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
+  CheckLayerRateTargeting(number_spatial_layers_, number_temporal_layers_, 0.58,
+                          1.2);
+#if CONFIG_VP9_DECODER
+  // The non-reference frames are expected to be mismatched frames as the
+  // encoder will avoid loopfilter on these frames.
+  EXPECT_EQ(GetNonRefFrames(), GetMismatchFrames());
+#endif
+}
+
+// Check basic rate targeting for 1 pass CBR SVC: 3 spatial layers and
+// 3 temporal layers. Run HD clip with 4 threads, for 1857x167.
+TEST_P(DatarateOnePassCbrSvcFrameDropMultiBR,
+       OnePassCbrSvc3SL3TL4Threads1857x167) {
+  SetSvcConfig(3, 3);
+  cfg_.rc_buf_initial_sz = 500;
+  cfg_.rc_buf_optimal_sz = 500;
+  cfg_.rc_buf_sz = 1000;
+  cfg_.rc_min_quantizer = 0;
+  cfg_.rc_max_quantizer = 63;
+  cfg_.g_threads = 1;
+  cfg_.rc_dropframe_thresh = 30;
+  cfg_.kf_max_dist = 9999;
+  ::libvpx_test::Y4mVideoSource video("niklas_1857_167_30.y4m", 0, 60);
+  top_sl_width_ = 1857;
+  top_sl_height_ = 167;
+  layer_framedrop_ = 0;
+  const int bitrates[3] = { 200, 400, 600 };
+  cfg_.rc_target_bitrate = bitrates[GET_PARAM(3)];
+  ResetModel();
+  layer_framedrop_ = GET_PARAM(2);
+  AssignLayerBitrates();
+  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
+  CheckLayerRateTargeting(number_spatial_layers_, number_temporal_layers_, 0.58,
+                          1.2);
+#if CONFIG_VP9_DECODER
+  // The non-reference frames are expected to be mismatched frames as the
+  // encoder will avoid loopfilter on these frames.
+  EXPECT_EQ(GetNonRefFrames(), GetMismatchFrames());
+#endif
+}
+
+// Check basic rate targeting for 1 pass CBR SVC: 3 spatial layers and
 // 2 temporal layers, for KSVC in flexible mode with no update of reference
 // frames for all spatial layers on TL > 0 superframes.
 // Run HD clip with 4 threads.
diff --git a/test/test-data.mk b/test/test-data.mk
index 75110fc..118f03e 100644
--- a/test/test-data.mk
+++ b/test/test-data.mk
@@ -26,6 +26,8 @@
 
 LIBVPX_TEST_DATA-$(CONFIG_VP9_ENCODER) += desktop_credits.y4m
 LIBVPX_TEST_DATA-$(CONFIG_VP9_ENCODER) += niklas_1280_720_30.y4m
+LIBVPX_TEST_DATA-$(CONFIG_VP9_ENCODER) += niklas_1284_770_30.y4m
+LIBVPX_TEST_DATA-$(CONFIG_VP9_ENCODER) += niklas_1857_167_30.y4m
 LIBVPX_TEST_DATA-$(CONFIG_VP9_ENCODER) += noisy_clip_640_360.y4m
 LIBVPX_TEST_DATA-$(CONFIG_VP9_ENCODER) += rush_hour_444.y4m
 LIBVPX_TEST_DATA-$(CONFIG_VP9_ENCODER) += screendata.y4m
diff --git a/test/test-data.sha1 b/test/test-data.sha1
index 49f816f..5df2240 100644
--- a/test/test-data.sha1
+++ b/test/test-data.sha1
@@ -872,3 +872,5 @@
 8a0b2c350539859463d3546a67876c83ff6ff0ac *desktopqvga.320_240.yuv
 ad9942a073e245585c93f764ea299382a65939a7 *crowd_run_360p_10_150f.y4m
 f9a73e921552598a5804911e9f84fec2318e056a *repro-oss-fuzz-69906.y4m
+320874b648f54e9156339e9d7e322ec4c51cb5f7 *niklas_1284_770_30.y4m
+64e876e725f83b93e92a8240457c152dd6f5b77d *niklas_1857_167_30.y4m
diff --git a/test/test.mk b/test/test.mk
index 28fe9db..7c5cbf0 100644
--- a/test/test.mk
+++ b/test/test.mk
@@ -70,7 +70,6 @@
 ifneq ($(CONFIG_REALTIME_ONLY),yes)
 LIBVPX_TEST_SRCS-$(CONFIG_VP9_ENCODER) += vp9_ext_ratectrl_test.cc
 endif
-LIBVPX_TEST_SRCS-$(CONFIG_VP9_ENCODER) += ../vp9/simple_encode.h
 
 LIBVPX_TEST_SRCS-yes                   += decode_test_driver.cc
 LIBVPX_TEST_SRCS-yes                   += decode_test_driver.h
@@ -211,10 +210,6 @@
 endif
 LIBVPX_TEST_SRCS-$(CONFIG_VP9_ENCODER) += vp9_arf_freq_test.cc
 
-ifeq ($(CONFIG_VP9_ENCODER),yes)
-SIMPLE_ENCODE_TEST_SRCS-$(CONFIG_RATE_CTRL) := simple_encode_test.cc
-endif
-
 endif # VP9
 
 ## Multi-codec / unconditional whitebox tests.
diff --git a/test/vp9_ext_ratectrl_test.cc b/test/vp9_ext_ratectrl_test.cc
index 8f7372e..b85dee1 100644
--- a/test/vp9_ext_ratectrl_test.cc
+++ b/test/vp9_ext_ratectrl_test.cc
@@ -22,7 +22,6 @@
 #if CONFIG_VP9_DECODER
 #include "vpx/vp8dx.h"
 #endif
-#include "vp9/simple_encode.h"
 #include "vpx/vpx_codec.h"
 #include "vpx/vpx_encoder.h"
 #include "vpx/vpx_ext_ratectrl.h"
@@ -175,6 +174,9 @@
       static_cast<RateControllerForTest *>(rate_ctrl_model);
   frame_decision->q_index =
       test_controller->CalculateFrameDecision(frame_gop_index);
+  frame_decision->rdmult =
+      frame_decision->q_index * frame_decision->q_index / 2;
+  frame_decision->delta_q_uv = 0;
   return VPX_RC_OK;
 }
 
diff --git a/third_party/libwebm/mkvparser/mkvparser.cc b/third_party/libwebm/mkvparser/mkvparser.cc
index 042a0c5..4fa7b37 100644
--- a/third_party/libwebm/mkvparser/mkvparser.cc
+++ b/third_party/libwebm/mkvparser/mkvparser.cc
@@ -8,7 +8,6 @@
 #include "mkvparser/mkvparser.h"
 
 #if defined(_MSC_VER) && _MSC_VER < 1800
-#include <float.h>  // _isnan() / _finite()
 #define MSC_COMPAT
 #endif
 
@@ -7891,8 +7890,10 @@
     if (frame_size <= 0)
       return E_FILE_FORMAT_INVALID;
 
+#if LLONG_MAX > LONG_MAX
     if (frame_size > LONG_MAX)
       return E_FILE_FORMAT_INVALID;
+#endif
 
     if ((pos + len) > stop)
       return E_FILE_FORMAT_INVALID;
@@ -7958,8 +7959,10 @@
       if (frame_size <= 0)
         return E_FILE_FORMAT_INVALID;
 
+#if LLONG_MAX > LONG_MAX
       if (frame_size > LONG_MAX)
         return E_FILE_FORMAT_INVALID;
+#endif
 
       curr.len = static_cast<long>(frame_size);
       // Check if size + curr.len could overflow.
diff --git a/tools.mk b/tools.mk
index dd2ebeb..79bb0cb 100644
--- a/tools.mk
+++ b/tools.mk
@@ -91,6 +91,13 @@
 $(foreach proj,$(call enabled,PROJECTS),\
     $(eval $(call vcproj_template,$(proj))))
 
+# Generate a list of all enabled sources, in particular for exporting to gyp
+# based build systems.
+tiny_ssim_srcs.txt:
+	@echo "    [CREATE] $@"
+	@echo $(tiny_ssim.SRCS) | xargs -n1 echo | LC_ALL=C sort -u > $@
+CLEAN-OBJS += tiny_ssim_srcs.txt
+
 #
 # Documentation Rules
 #
diff --git a/tools/set_analyzer_env.sh b/tools/set_analyzer_env.sh
index 4d659db..a3e8a1a 100644
--- a/tools/set_analyzer_env.sh
+++ b/tools/set_analyzer_env.sh
@@ -73,12 +73,18 @@
 # Exact backtraces.
 cflags="${cflags} -fno-optimize-sibling-calls"
 
-if [ "${sanitizer}" = "cfi" ]; then
-  # https://clang.llvm.org/docs/ControlFlowIntegrity.html
-  cflags="${cflags} -fno-sanitize-trap=cfi -flto -fvisibility=hidden"
-  ldflags="${ldflags} -fno-sanitize-trap=cfi -flto -fuse-ld=gold"
-  export AR="llvm-ar"
-fi
+case "${sanitizer}" in
+  cfi)
+    # https://clang.llvm.org/docs/ControlFlowIntegrity.html
+    cflags="${cflags} -fno-sanitize-trap=cfi -flto -fvisibility=hidden"
+    ldflags="${ldflags} -fno-sanitize-trap=cfi -flto -fuse-ld=gold"
+    export AR="llvm-ar"
+    ;;
+  integer|undefined)
+    # https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html
+    cflags="${cflags} -fsanitize=float-cast-overflow"
+    ;;
+esac
 
 set -x
 export CC="clang"
diff --git a/tools/tiny_ssim.c b/tools/tiny_ssim.c
index 8cc4d98..c07a9d2 100644
--- a/tools/tiny_ssim.c
+++ b/tools/tiny_ssim.c
@@ -340,11 +340,24 @@
   }
 
   if (argc > 3) {
-    sscanf(argv[3], "%dx%d", &w, &h);
+    if (sscanf(argv[3], "%dx%d", &w, &h) != 2) {
+      fprintf(stderr, "arguments for w/h not assigned!\n");
+      goto clean_up;
+    }
+    // Limit width/height to 4K. The frame_size set in the function
+    // open_input_file() will still be within range of int.
+    if (w < 1 || w > 4096 || h < 1 || h > 4096) {
+      fprintf(stderr,
+              "width or height is too large (above 4096) or below 1!\n");
+      goto clean_up;
+    }
   }
 
   if (argc > 6) {
-    sscanf(argv[6], "%d", &bit_depth);
+    if (sscanf(argv[6], "%d", &bit_depth) != 1) {
+      fprintf(stderr, "argument for bit_depth not assigned!\n");
+      goto clean_up;
+    }
   }
 
   if (open_input_file(argv[1], &in[0], w, h, bit_depth) < 0) {
@@ -389,7 +402,10 @@
   // encoding in mode 10. 7 would be reasonable for comparing TL0 of a 4-layer
   // encoding.
   if (argc > 4) {
-    sscanf(argv[4], "%d", &tl_skip);
+    if (sscanf(argv[4], "%d", &tl_skip) != 1) {
+      fprintf(stderr, "argument for tl_skip not assigned!\n");
+      goto clean_up;
+    }
     if (argc > 5) {
       framestats = fopen(argv[5], "w");
       if (!framestats) {
diff --git a/vp8/decoder/decodeframe.c b/vp8/decoder/decodeframe.c
index af9a98c..a20f33d 100644
--- a/vp8/decoder/decodeframe.c
+++ b/vp8/decoder/decodeframe.c
@@ -965,8 +965,8 @@
         data = data_end;
       }
     } else {
-      memcpy(&xd->pre, yv12_fb_new, sizeof(YV12_BUFFER_CONFIG));
-      memcpy(&xd->dst, yv12_fb_new, sizeof(YV12_BUFFER_CONFIG));
+      xd->pre = *yv12_fb_new;
+      xd->dst = *yv12_fb_new;
     }
   }
   if ((!pbi->decoded_key_frame && pc->frame_type != KEY_FRAME)) {
@@ -1156,7 +1156,7 @@
   if (pbi->ec_active && xd->corrupted) pc->refresh_entropy_probs = 0;
 #endif
   if (pc->refresh_entropy_probs == 0) {
-    memcpy(&pc->lfc, &pc->fc, sizeof(pc->fc));
+    pc->lfc = pc->fc;
   }
 
   pc->refresh_last_frame = pc->frame_type == KEY_FRAME || vp8_read_bit(bc);
@@ -1245,7 +1245,7 @@
    * \n",bc->pos+pbi->bc2.pos); */
 
   if (pc->refresh_entropy_probs == 0) {
-    memcpy(&pc->fc, &pc->lfc, sizeof(pc->fc));
+    pc->fc = pc->lfc;
     pbi->independent_partitions = prev_independent_partitions;
   }
 
diff --git a/vp8/encoder/encodeframe.c b/vp8/encoder/encodeframe.c
index fa43340..97855ae 100644
--- a/vp8/encoder/encodeframe.c
+++ b/vp8/encoder/encodeframe.c
@@ -747,8 +747,10 @@
 #endif
 
   {
+#if CONFIG_INTERNAL_STATS
     struct vpx_usec_timer emr_timer;
     vpx_usec_timer_start(&emr_timer);
+#endif
 
 #if CONFIG_MULTITHREAD
     if (vpx_atomic_load_acquire(&cpi->b_multi_threaded)) {
@@ -894,8 +896,10 @@
     }
 #endif
 
+#if CONFIG_INTERNAL_STATS
     vpx_usec_timer_mark(&emr_timer);
     cpi->time_encode_mb_row += vpx_usec_timer_elapsed(&emr_timer);
+#endif
   }
 
   // Work out the segment probabilities if segmentation is enabled
diff --git a/vp8/encoder/encodemb.c b/vp8/encoder/encodemb.c
index 3fd8d5f..052d09b 100644
--- a/vp8/encoder/encodemb.c
+++ b/vp8/encoder/encodemb.c
@@ -396,8 +396,8 @@
   ENTROPY_CONTEXT *ta;
   ENTROPY_CONTEXT *tl;
 
-  memcpy(&t_above, x->e_mbd.above_context, sizeof(ENTROPY_CONTEXT_PLANES));
-  memcpy(&t_left, x->e_mbd.left_context, sizeof(ENTROPY_CONTEXT_PLANES));
+  t_above = *x->e_mbd.above_context;
+  t_left = *x->e_mbd.left_context;
 
   ta = (ENTROPY_CONTEXT *)&t_above;
   tl = (ENTROPY_CONTEXT *)&t_left;
@@ -437,8 +437,8 @@
 
   if (!x->e_mbd.left_context) return;
 
-  memcpy(&t_above, x->e_mbd.above_context, sizeof(ENTROPY_CONTEXT_PLANES));
-  memcpy(&t_left, x->e_mbd.left_context, sizeof(ENTROPY_CONTEXT_PLANES));
+  t_above = *x->e_mbd.above_context;
+  t_left = *x->e_mbd.left_context;
 
   ta = (ENTROPY_CONTEXT *)&t_above;
   tl = (ENTROPY_CONTEXT *)&t_left;
@@ -470,8 +470,8 @@
 
   if (!x->e_mbd.left_context) return;
 
-  memcpy(&t_above, x->e_mbd.above_context, sizeof(ENTROPY_CONTEXT_PLANES));
-  memcpy(&t_left, x->e_mbd.left_context, sizeof(ENTROPY_CONTEXT_PLANES));
+  t_above = *x->e_mbd.above_context;
+  t_left = *x->e_mbd.left_context;
 
   ta = (ENTROPY_CONTEXT *)&t_above;
   tl = (ENTROPY_CONTEXT *)&t_left;
diff --git a/vp8/encoder/firstpass.c b/vp8/encoder/firstpass.c
index 8a3861e..9871e56 100644
--- a/vp8/encoder/firstpass.c
+++ b/vp8/encoder/firstpass.c
@@ -795,7 +795,7 @@
     fps.duration = (double)(cpi->source->ts_end - cpi->source->ts_start);
 
     /* don't want to do output stats with a stack variable! */
-    memcpy(&cpi->twopass.this_frame_stats, &fps, sizeof(FIRSTPASS_STATS));
+    cpi->twopass.this_frame_stats = fps;
     output_stats(cpi->output_pkt_list, &cpi->twopass.this_frame_stats);
     accumulate_stats(&cpi->twopass.total_stats, &fps);
   }
@@ -1700,7 +1700,7 @@
       break;
     }
 
-    memcpy(this_frame, &next_frame, sizeof(*this_frame));
+    *this_frame = next_frame;
 
     old_boost_score = boost_score;
   }
@@ -2236,7 +2236,7 @@
   /* keyframe and section processing ! */
   if (cpi->twopass.frames_to_key == 0) {
     /* Define next KF group and assign bits to it */
-    memcpy(&this_frame_copy, &this_frame, sizeof(this_frame));
+    this_frame_copy = this_frame;
     find_next_key_frame(cpi, &this_frame_copy);
 
     /* Special case: Error error_resilient_mode mode does not make much
@@ -2258,7 +2258,7 @@
   /* Is this a GF / ARF (Note that a KF is always also a GF) */
   if (cpi->frames_till_gf_update_due == 0) {
     /* Define next gf group and assign bits to it */
-    memcpy(&this_frame_copy, &this_frame, sizeof(this_frame));
+    this_frame_copy = this_frame;
     define_gf_group(cpi, &this_frame_copy);
 
     /* If we are going to code an altref frame at the end of the group
@@ -2273,7 +2273,7 @@
        * to the GF group
        */
       int bak = cpi->per_frame_bandwidth;
-      memcpy(&this_frame_copy, &this_frame, sizeof(this_frame));
+      this_frame_copy = this_frame;
       assign_std_frame_bits(cpi, &this_frame_copy);
       cpi->per_frame_bandwidth = bak;
     }
@@ -2293,12 +2293,12 @@
 
       if (cpi->common.frame_type != KEY_FRAME) {
         /* Assign bits from those allocated to the GF group */
-        memcpy(&this_frame_copy, &this_frame, sizeof(this_frame));
+        this_frame_copy = this_frame;
         assign_std_frame_bits(cpi, &this_frame_copy);
       }
     } else {
       /* Assign bits from those allocated to the GF group */
-      memcpy(&this_frame_copy, &this_frame, sizeof(this_frame));
+      this_frame_copy = this_frame;
       assign_std_frame_bits(cpi, &this_frame_copy);
     }
   }
@@ -2430,7 +2430,7 @@
     double decay_accumulator = 1.0;
     double next_iiratio;
 
-    memcpy(&local_next_frame, next_frame, sizeof(*next_frame));
+    local_next_frame = *next_frame;
 
     /* Note the starting file position so we can reset to it */
     start_pos = cpi->twopass.stats_in;
@@ -2523,7 +2523,7 @@
   cpi->twopass.frames_to_key = 1;
 
   /* Take a copy of the initial frame details */
-  memcpy(&first_frame, this_frame, sizeof(*this_frame));
+  first_frame = *this_frame;
 
   cpi->twopass.kf_group_bits = 0;
   cpi->twopass.kf_group_error_left = 0;
@@ -2545,7 +2545,7 @@
     kf_group_coded_err += this_frame->coded_error;
 
     /* Load the next frame's stats. */
-    memcpy(&last_frame, this_frame, sizeof(*this_frame));
+    last_frame = *this_frame;
     input_stats(cpi, this_frame);
 
     /* Provided that we are not at the end of the file... */
@@ -2608,7 +2608,7 @@
     cpi->twopass.frames_to_key /= 2;
 
     /* Copy first frame details */
-    memcpy(&tmp_frame, &first_frame, sizeof(first_frame));
+    tmp_frame = first_frame;
 
     /* Reset to the start of the group */
     reset_fpf_position(cpi, start_position);
diff --git a/vp8/encoder/onyx_if.c b/vp8/encoder/onyx_if.c
index 17726fa..70104ab 100644
--- a/vp8/encoder/onyx_if.c
+++ b/vp8/encoder/onyx_if.c
@@ -23,6 +23,7 @@
 #include "mcomp.h"
 #include "firstpass.h"
 #include "vpx_dsp/psnr.h"
+#include "vpx_dsp/vpx_dsp_common.h"
 #include "vpx_scale/vpx_scale.h"
 #include "vp8/common/extend.h"
 #include "ratectrl.h"
@@ -1874,8 +1875,7 @@
             ? (2 * (cpi->common.mb_rows * cpi->common.mb_cols) /
                cpi->cyclic_refresh_mode_max_mbs_perframe)
             : 10;
-    cpi->gf_interval_onepass_cbr =
-        VPXMIN(40, VPXMAX(6, cpi->gf_interval_onepass_cbr));
+    cpi->gf_interval_onepass_cbr = clamp(cpi->gf_interval_onepass_cbr, 6, 40);
     cpi->baseline_gf_interval = cpi->gf_interval_onepass_cbr;
   }
 
@@ -3140,11 +3140,15 @@
   if (cm->no_lpf) {
     cm->filter_level = 0;
   } else {
+#if CONFIG_INTERNAL_STATS
     struct vpx_usec_timer timer;
+#endif
 
     vpx_clear_system_state();
 
+#if CONFIG_INTERNAL_STATS
     vpx_usec_timer_start(&timer);
+#endif
     if (cpi->sf.auto_filter == 0) {
 #if CONFIG_TEMPORAL_DENOISING
       if (cpi->oxcf.noise_sensitivity && cm->frame_type != KEY_FRAME) {
@@ -3179,8 +3183,10 @@
       vp8cx_set_alt_lf_level(cpi, cm->filter_level);
     }
 
+#if CONFIG_INTERNAL_STATS
     vpx_usec_timer_mark(&timer);
     cpi->time_pick_lpf += vpx_usec_timer_elapsed(&timer);
+#endif
   }
 
 #if CONFIG_MULTITHREAD
@@ -3936,7 +3942,7 @@
 
       if (cm->refresh_entropy_probs == 0) {
         /* save a copy for later refresh */
-        memcpy(&cm->lfc, &cm->fc, sizeof(cm->fc));
+        cm->lfc = cm->fc;
       }
 
       vp8_update_coef_context(cpi);
@@ -4784,10 +4790,14 @@
 int vp8_receive_raw_frame(VP8_COMP *cpi, unsigned int frame_flags,
                           YV12_BUFFER_CONFIG *sd, int64_t time_stamp,
                           int64_t end_time) {
+#if CONFIG_INTERNAL_STATS
   struct vpx_usec_timer timer;
+#endif
   int res = 0;
 
+#if CONFIG_INTERNAL_STATS
   vpx_usec_timer_start(&timer);
+#endif
 
   /* Reinit the lookahead buffer if the frame size changes */
   if (sd->y_width != cpi->oxcf.Width || sd->y_height != cpi->oxcf.Height) {
@@ -4800,8 +4810,10 @@
                          cpi->active_map_enabled ? cpi->active_map : NULL)) {
     res = -1;
   }
+#if CONFIG_INTERNAL_STATS
   vpx_usec_timer_mark(&timer);
   cpi->time_receive_data += vpx_usec_timer_elapsed(&timer);
+#endif
 
   return res;
 }
@@ -4822,16 +4834,19 @@
                             unsigned char *dest_end, int64_t *time_stamp,
                             int64_t *time_end, int flush) {
   VP8_COMMON *cm;
-  struct vpx_usec_timer tsctimer;
   struct vpx_usec_timer ticktimer;
+#if CONFIG_INTERNAL_STATS
   struct vpx_usec_timer cmptimer;
+#endif
   YV12_BUFFER_CONFIG *force_src_buffer = NULL;
 
   if (!cpi) return -1;
 
   cm = &cpi->common;
 
+#if CONFIG_INTERNAL_STATS
   vpx_usec_timer_start(&cmptimer);
+#endif
 
   cpi->source = NULL;
 
@@ -5005,7 +5020,6 @@
   }
 
   if (cpi->compressor_speed == 2) {
-    vpx_usec_timer_start(&tsctimer);
     vpx_usec_timer_start(&ticktimer);
   }
 
@@ -5080,7 +5094,6 @@
 
   if (cpi->compressor_speed == 2) {
     unsigned int duration, duration2;
-    vpx_usec_timer_mark(&tsctimer);
     vpx_usec_timer_mark(&ticktimer);
 
     duration = (int)(vpx_usec_timer_elapsed(&ticktimer));
@@ -5107,16 +5120,16 @@
   }
 
   if (cm->refresh_entropy_probs == 0) {
-    memcpy(&cm->fc, &cm->lfc, sizeof(cm->fc));
+    cm->fc = cm->lfc;
   }
 
   /* Save the contexts separately for alt ref, gold and last. */
   /* (TODO jbb -> Optimize this with pointers to avoid extra copies. ) */
-  if (cm->refresh_alt_ref_frame) memcpy(&cpi->lfc_a, &cm->fc, sizeof(cm->fc));
+  if (cm->refresh_alt_ref_frame) cpi->lfc_a = cm->fc;
 
-  if (cm->refresh_golden_frame) memcpy(&cpi->lfc_g, &cm->fc, sizeof(cm->fc));
+  if (cm->refresh_golden_frame) cpi->lfc_g = cm->fc;
 
-  if (cm->refresh_last_frame) memcpy(&cpi->lfc_n, &cm->fc, sizeof(cm->fc));
+  if (cm->refresh_last_frame) cpi->lfc_n = cm->fc;
 
   /* if it's a dropped frame honor the requests on subsequent frames */
   if (*size > 0) {
@@ -5133,8 +5146,10 @@
   /* Save layer specific state */
   if (cpi->oxcf.number_of_layers > 1) vp8_save_layer_context(cpi);
 
+#if CONFIG_INTERNAL_STATS
   vpx_usec_timer_mark(&cmptimer);
   cpi->time_compress_data += vpx_usec_timer_elapsed(&cmptimer);
+#endif
 
 #if CONFIG_MULTITHREAD
   /* wait for the lpf thread done */
diff --git a/vp8/encoder/onyx_int.h b/vp8/encoder/onyx_int.h
index 0f4550a..c2a1373 100644
--- a/vp8/encoder/onyx_int.h
+++ b/vp8/encoder/onyx_int.h
@@ -556,10 +556,12 @@
   vp8_refining_search_fn_t refining_search_sad;
   vp8_diamond_search_fn_t diamond_search_sad;
   vp8_variance_fn_ptr_t fn_ptr[BLOCK_MAX_SEGMENTS];
+#if CONFIG_INTERNAL_STATS
   uint64_t time_receive_data;
   uint64_t time_compress_data;
   uint64_t time_pick_lpf;
   uint64_t time_encode_mb_row;
+#endif
 
   int base_skip_false_prob[128];
 
diff --git a/vp8/encoder/ratectrl.c b/vp8/encoder/ratectrl.c
index 2f75ec9..215d9c7 100644
--- a/vp8/encoder/ratectrl.c
+++ b/vp8/encoder/ratectrl.c
@@ -259,9 +259,9 @@
   /* Make sure we initialize separate contexts for altref,gold, and normal.
    * TODO shouldn't need 3 different copies of structure to do this!
    */
-  memcpy(&cpi->lfc_a, &cpi->common.fc, sizeof(cpi->common.fc));
-  memcpy(&cpi->lfc_g, &cpi->common.fc, sizeof(cpi->common.fc));
-  memcpy(&cpi->lfc_n, &cpi->common.fc, sizeof(cpi->common.fc));
+  cpi->lfc_a = cpi->common.fc;
+  cpi->lfc_g = cpi->common.fc;
+  cpi->lfc_n = cpi->common.fc;
 
   cpi->common.filter_level = cpi->common.base_qindex * 3 / 8;
 
diff --git a/vp8/encoder/rdopt.c b/vp8/encoder/rdopt.c
index 671cccd..ad3866c 100644
--- a/vp8/encoder/rdopt.c
+++ b/vp8/encoder/rdopt.c
@@ -451,8 +451,8 @@
   ENTROPY_CONTEXT *ta;
   ENTROPY_CONTEXT *tl;
 
-  memcpy(&t_above, mb->e_mbd.above_context, sizeof(ENTROPY_CONTEXT_PLANES));
-  memcpy(&t_left, mb->e_mbd.left_context, sizeof(ENTROPY_CONTEXT_PLANES));
+  t_above = *mb->e_mbd.above_context;
+  t_left = *mb->e_mbd.left_context;
 
   ta = (ENTROPY_CONTEXT *)&t_above;
   tl = (ENTROPY_CONTEXT *)&t_left;
@@ -597,8 +597,8 @@
   ENTROPY_CONTEXT *tl;
   const int *bmode_costs;
 
-  memcpy(&t_above, mb->e_mbd.above_context, sizeof(ENTROPY_CONTEXT_PLANES));
-  memcpy(&t_left, mb->e_mbd.left_context, sizeof(ENTROPY_CONTEXT_PLANES));
+  t_above = *mb->e_mbd.above_context;
+  t_left = *mb->e_mbd.left_context;
 
   ta = (ENTROPY_CONTEXT *)&t_above;
   tl = (ENTROPY_CONTEXT *)&t_left;
@@ -689,8 +689,8 @@
   ENTROPY_CONTEXT *ta;
   ENTROPY_CONTEXT *tl;
 
-  memcpy(&t_above, mb->e_mbd.above_context, sizeof(ENTROPY_CONTEXT_PLANES));
-  memcpy(&t_left, mb->e_mbd.left_context, sizeof(ENTROPY_CONTEXT_PLANES));
+  t_above = *mb->e_mbd.above_context;
+  t_left = *mb->e_mbd.left_context;
 
   ta = (ENTROPY_CONTEXT *)&t_above;
   tl = (ENTROPY_CONTEXT *)&t_left;
@@ -962,8 +962,8 @@
   ENTROPY_CONTEXT_PLANES t_above, t_left;
   ENTROPY_CONTEXT_PLANES t_above_b, t_left_b;
 
-  memcpy(&t_above, x->e_mbd.above_context, sizeof(ENTROPY_CONTEXT_PLANES));
-  memcpy(&t_left, x->e_mbd.left_context, sizeof(ENTROPY_CONTEXT_PLANES));
+  t_above = *x->e_mbd.above_context;
+  t_left = *x->e_mbd.left_context;
 
   vp8_zero(t_above_b);
   vp8_zero(t_left_b);
@@ -1003,8 +1003,8 @@
       ENTROPY_CONTEXT *ta_s;
       ENTROPY_CONTEXT *tl_s;
 
-      memcpy(&t_above_s, &t_above, sizeof(ENTROPY_CONTEXT_PLANES));
-      memcpy(&t_left_s, &t_left, sizeof(ENTROPY_CONTEXT_PLANES));
+      t_above_s = t_above;
+      t_left_s = t_left;
 
       ta_s = (ENTROPY_CONTEXT *)&t_above_s;
       tl_s = (ENTROPY_CONTEXT *)&t_left_s;
@@ -1146,13 +1146,13 @@
         mode_selected = this_mode;
         best_label_rd = this_rd;
 
-        memcpy(&t_above_b, &t_above_s, sizeof(ENTROPY_CONTEXT_PLANES));
-        memcpy(&t_left_b, &t_left_s, sizeof(ENTROPY_CONTEXT_PLANES));
+        t_above_b = t_above_s;
+        t_left_b = t_left_s;
       }
     } /*for each 4x4 mode*/
 
-    memcpy(&t_above, &t_above_b, sizeof(ENTROPY_CONTEXT_PLANES));
-    memcpy(&t_left, &t_left_b, sizeof(ENTROPY_CONTEXT_PLANES));
+    t_above = t_above_b;
+    t_left = t_left_b;
 
     labels2mode(x, labels, i, mode_selected, &mode_mv[mode_selected],
                 bsi->ref_mv, x->mvcost);
@@ -1736,9 +1736,8 @@
              (rd->distortion2 - rd->distortion_uv));
 
   best_mode->rd = this_rd;
-  memcpy(&best_mode->mbmode, &x->e_mbd.mode_info_context->mbmi,
-         sizeof(MB_MODE_INFO));
-  memcpy(&best_mode->partition, x->partition_info, sizeof(PARTITION_INFO));
+  best_mode->mbmode = x->e_mbd.mode_info_context->mbmi;
+  best_mode->partition = *x->partition_info;
 
   if ((this_mode == B_PRED) || (this_mode == SPLITMV)) {
     int i;
@@ -2348,8 +2347,7 @@
   }
 
   /* macroblock modes */
-  memcpy(&x->e_mbd.mode_info_context->mbmi, &best_mode.mbmode,
-         sizeof(MB_MODE_INFO));
+  x->e_mbd.mode_info_context->mbmi = best_mode.mbmode;
 
   if (best_mode.mbmode.mode == B_PRED) {
     for (i = 0; i < 16; ++i) {
@@ -2362,7 +2360,7 @@
       xd->mode_info_context->bmi[i].mv.as_int = best_mode.bmodes[i].mv.as_int;
     }
 
-    memcpy(x->partition_info, &best_mode.partition, sizeof(PARTITION_INFO));
+    *x->partition_info = best_mode.partition;
 
     x->e_mbd.mode_info_context->mbmi.mv.as_int =
         x->partition_info->bmi[15].mv.as_int;
diff --git a/vp8/vp8_cx_iface.c b/vp8/vp8_cx_iface.c
index 35c94fb..96313ce 100644
--- a/vp8/vp8_cx_iface.c
+++ b/vp8/vp8_cx_iface.c
@@ -8,6 +8,7 @@
  *  be found in the AUTHORS file in the root of the source tree.
  */
 
+#include <assert.h>
 #include <limits.h>
 #include <stdint.h>
 #include <stdlib.h>
@@ -396,8 +397,7 @@
   if (mr_cfg) {
     oxcf->mr_total_resolutions = mr_cfg->mr_total_resolutions;
     oxcf->mr_encoder_id = mr_cfg->mr_encoder_id;
-    oxcf->mr_down_sampling_factor.num = mr_cfg->mr_down_sampling_factor.num;
-    oxcf->mr_down_sampling_factor.den = mr_cfg->mr_down_sampling_factor.den;
+    oxcf->mr_down_sampling_factor = mr_cfg->mr_down_sampling_factor;
     oxcf->mr_low_res_mode_info = mr_cfg->mr_low_res_mode_info;
   }
 #else
@@ -667,11 +667,22 @@
   }
 #else
   (void)cfg;
-  (void)mem_loc;
+  *mem_loc = NULL;
 #endif
   return res;
 }
 
+static void vp8e_mr_free_mem(void *mem_loc) {
+#if CONFIG_MULTI_RES_ENCODING
+  LOWER_RES_FRAME_INFO *shared_mem_loc = (LOWER_RES_FRAME_INFO *)mem_loc;
+  free(shared_mem_loc->mb_info);
+  free(mem_loc);
+#else
+  (void)mem_loc;
+  assert(!mem_loc);
+#endif
+}
+
 static vpx_codec_err_t vp8e_init(vpx_codec_ctx_t *ctx,
                                  vpx_codec_priv_enc_mr_cfg_t *mr_cfg) {
   vpx_codec_err_t res = VPX_CODEC_OK;
@@ -754,10 +765,7 @@
   /* Free multi-encoder shared memory */
   if (ctx->oxcf.mr_total_resolutions > 0 &&
       (ctx->oxcf.mr_encoder_id == ctx->oxcf.mr_total_resolutions - 1)) {
-    LOWER_RES_FRAME_INFO *shared_mem_loc =
-        (LOWER_RES_FRAME_INFO *)ctx->oxcf.mr_low_res_mode_info;
-    free(shared_mem_loc->mb_info);
-    free(ctx->oxcf.mr_low_res_mode_info);
+    vp8e_mr_free_mem(ctx->oxcf.mr_low_res_mode_info);
   }
 #endif
 
@@ -1431,5 +1439,6 @@
       NULL,
       vp8e_get_preview,
       vp8e_mr_alloc_mem,
+      vp8e_mr_free_mem,
   } /* encoder functions */
 };
diff --git a/vp8/vp8_dx_iface.c b/vp8/vp8_dx_iface.c
index fa7d7be..65a86e2 100644
--- a/vp8/vp8_dx_iface.c
+++ b/vp8/vp8_dx_iface.c
@@ -747,6 +747,7 @@
       NULL,    /* vpx_codec_enc_config_set_fn_t */
       NULL,    /* vpx_codec_get_global_headers_fn_t */
       NULL,    /* vpx_codec_get_preview_frame_fn_t */
-      NULL     /* vpx_codec_enc_mr_get_mem_loc_fn_t */
+      NULL,    /* vpx_codec_enc_mr_get_mem_loc_fn_t */
+      NULL     /* vpx_codec_enc_mr_free_mem_loc_fn_t */
   }
 };
diff --git a/vp9/common/vp9_rtcd_defs.pl b/vp9/common/vp9_rtcd_defs.pl
index af3ff0e..85fbde6 100644
--- a/vp9/common/vp9_rtcd_defs.pl
+++ b/vp9/common/vp9_rtcd_defs.pl
@@ -18,6 +18,9 @@
 #include "vp9/common/vp9_common.h"
 #include "vp9/common/vp9_enums.h"
 #include "vp9/common/vp9_filter.h"
+#if !CONFIG_REALTIME_ONLY && CONFIG_VP9_ENCODER
+#include "vp9/encoder/vp9_temporal_filter.h"
+#endif
 
 struct macroblockd;
 
@@ -115,7 +118,6 @@
 # Encoder functions below this point.
 #
 if (vpx_config("CONFIG_VP9_ENCODER") eq "yes") {
-
 # ENCODEMB INVOKE
 
 #
@@ -189,6 +191,30 @@
   }
 }
 
+#
+# 12-tap filter used in prediction data generation during temporal filtering
+#
+if (vpx_config("CONFIG_REALTIME_ONLY") ne "yes") {
+  add_proto qw/void vpx_convolve12_vert/, "const uint8_t *src, ptrdiff_t src_stride, uint8_t *dst, ptrdiff_t dst_stride, const InterpKernel12 *filter, int x0_q4, int x_step_q4, int y0_q4, int y_step_q4, int w, int h";
+  specialize qw/vpx_convolve12_vert ssse3 avx2 neon neon_dotprod neon_i8mm/;
+
+  add_proto qw/void vpx_convolve12_horiz/, "const uint8_t *src, ptrdiff_t src_stride, uint8_t *dst, ptrdiff_t dst_stride, const InterpKernel12 *filter, int x0_q4, int x_step_q4, int y0_q4, int y_step_q4, int w, int h";
+  specialize qw/vpx_convolve12_horiz ssse3 avx2 neon neon_dotprod neon_i8mm/;
+
+  add_proto qw/void vpx_convolve12/, "const uint8_t *src, ptrdiff_t src_stride, uint8_t *dst, ptrdiff_t dst_stride, const InterpKernel12 *filter, int x0_q4, int x_step_q4, int y0_q4, int y_step_q4, int w, int h";
+  specialize qw/vpx_convolve12 ssse3 avx2 neon neon_dotprod neon_i8mm/;
+
+  if (vpx_config("CONFIG_VP9_HIGHBITDEPTH") eq "yes") {
+    add_proto qw/void vpx_highbd_convolve12_vert/, "const uint16_t *src, ptrdiff_t src_stride, uint16_t *dst, ptrdiff_t dst_stride, const InterpKernel12 *filter, int x0_q4, int x_step_q4, int y0_q4, int y_step_q4, int w, int h, int bd";
+    specialize qw/vpx_highbd_convolve12_vert ssse3 avx2/;
+
+    add_proto qw/void vpx_highbd_convolve12_horiz/, "const uint16_t *src, ptrdiff_t src_stride, uint16_t *dst, ptrdiff_t dst_stride, const InterpKernel12 *filter, int x0_q4, int x_step_q4, int y0_q4, int y_step_q4, int w, int h, int bd";
+    specialize qw/vpx_highbd_convolve12_horiz ssse3 avx2/;
+
+    add_proto qw/void vpx_highbd_convolve12/, "const uint16_t *src, ptrdiff_t src_stride, uint16_t *dst, ptrdiff_t dst_stride, const InterpKernel12 *filter, int x0_q4, int x_step_q4, int y0_q4, int y_step_q4, int w, int h, int bd";
+    specialize qw/vpx_highbd_convolve12 ssse3 avx2/;
+  }
+}
 
 if (vpx_config("CONFIG_VP9_HIGHBITDEPTH") eq "yes") {
 
diff --git a/vp9/encoder/arm/neon/vp9_temporal_filter_neon.c b/vp9/encoder/arm/neon/vp9_temporal_filter_neon.c
index a651a15..b6cce39 100644
--- a/vp9/encoder/arm/neon/vp9_temporal_filter_neon.c
+++ b/vp9/encoder/arm/neon/vp9_temporal_filter_neon.c
@@ -14,6 +14,8 @@
 #include "./vp9_rtcd.h"
 #include "./vpx_config.h"
 #include "vpx/vpx_integer.h"
+#include "vpx_dsp/arm/mem_neon.h"
+#include "vpx_dsp/arm/transpose_neon.h"
 #include "vp9/encoder/vp9_encoder.h"
 #include "vp9/encoder/vp9_temporal_filter.h"
 #include "vp9/encoder/vp9_temporal_filter_constants.h"
@@ -847,3 +849,255 @@
                                use_whole_blk, u_accum, u_count, v_accum,
                                v_count, y_dist_ptr, u_dist_ptr, v_dist_ptr);
 }
+
+static INLINE uint8x8_t convolve12_8(const int16x8_t s0, const int16x8_t s1,
+                                     const int16x8_t s2, const int16x8_t s3,
+                                     const int16x8_t s4, const int16x8_t s5,
+                                     const int16x8_t s6, const int16x8_t s7,
+                                     const int16x8_t s8, const int16x8_t s9,
+                                     const int16x8_t sA, const int16x8_t sB,
+                                     const int16x8_t filter_0_7,
+                                     const int16x4_t filter_8_11) {
+  const int16x4_t filter_0_3 = vget_low_s16(filter_0_7);
+  const int16x4_t filter_4_7 = vget_high_s16(filter_0_7);
+
+  int16x8_t sum = vmulq_lane_s16(s0, filter_0_3, 0);
+  sum = vmlaq_lane_s16(sum, s1, filter_0_3, 1);
+  sum = vmlaq_lane_s16(sum, s2, filter_0_3, 2);
+  sum = vmlaq_lane_s16(sum, s3, filter_0_3, 3);
+  sum = vmlaq_lane_s16(sum, s4, filter_4_7, 0);
+
+  sum = vmlaq_lane_s16(sum, s7, filter_4_7, 3);
+  sum = vmlaq_lane_s16(sum, s8, filter_8_11, 0);
+  sum = vmlaq_lane_s16(sum, s9, filter_8_11, 1);
+  sum = vmlaq_lane_s16(sum, sA, filter_8_11, 2);
+  sum = vmlaq_lane_s16(sum, sB, filter_8_11, 3);
+
+  // Saturating addition is required for the largest filter taps to avoid
+  // overflow (while staying in 16-bit elements.)
+  sum = vqaddq_s16(sum, vmulq_lane_s16(s5, filter_4_7, 1));
+  sum = vqaddq_s16(sum, vmulq_lane_s16(s6, filter_4_7, 2));
+
+  return vqrshrun_n_s16(sum, FILTER_BITS);
+}
+
+void vpx_convolve12_horiz_neon(const uint8_t *src, ptrdiff_t src_stride,
+                               uint8_t *dst, ptrdiff_t dst_stride,
+                               const InterpKernel12 *filter, int x0_q4,
+                               int x_step_q4, int y0_q4, int y_step_q4, int w,
+                               int h) {
+  // Scaling not supported by Neon implementation.
+  if (x_step_q4 != 16) {
+    vpx_convolve12_horiz_c(src, src_stride, dst, dst_stride, filter, x0_q4,
+                           x_step_q4, y0_q4, y_step_q4, w, h);
+    return;
+  }
+
+  assert(w == 32 || w == 16 || w == 8);
+  assert(h % 4 == 0);
+
+  const int16x8_t filter_0_7 = vld1q_s16(filter[x0_q4]);
+  const int16x4_t filter_8_11 = vld1_s16(filter[x0_q4] + 8);
+
+  src -= MAX_FILTER_TAP / 2 - 1;
+
+  do {
+    const uint8_t *s = src;
+    uint8_t *d = dst;
+    int width = w;
+
+    uint8x8_t t0, t1, t2, t3;
+    load_u8_8x4(s, src_stride, &t0, &t1, &t2, &t3);
+    transpose_u8_8x4(&t0, &t1, &t2, &t3);
+
+    int16x4_t s0 = vget_low_s16(vreinterpretq_s16_u16(vmovl_u8(t0)));
+    int16x4_t s1 = vget_low_s16(vreinterpretq_s16_u16(vmovl_u8(t1)));
+    int16x4_t s2 = vget_low_s16(vreinterpretq_s16_u16(vmovl_u8(t2)));
+    int16x4_t s3 = vget_low_s16(vreinterpretq_s16_u16(vmovl_u8(t3)));
+    int16x4_t s4 = vget_high_s16(vreinterpretq_s16_u16(vmovl_u8(t0)));
+    int16x4_t s5 = vget_high_s16(vreinterpretq_s16_u16(vmovl_u8(t1)));
+    int16x4_t s6 = vget_high_s16(vreinterpretq_s16_u16(vmovl_u8(t2)));
+    int16x4_t s7 = vget_high_s16(vreinterpretq_s16_u16(vmovl_u8(t3)));
+
+    int16x8_t s0s1 = vcombine_s16(s0, s1);
+    int16x8_t s1s2 = vcombine_s16(s1, s2);
+    int16x8_t s2s3 = vcombine_s16(s2, s3);
+    int16x8_t s3s4 = vcombine_s16(s3, s4);
+    int16x8_t s4s5 = vcombine_s16(s4, s5);
+    int16x8_t s5s6 = vcombine_s16(s5, s6);
+    int16x8_t s6s7 = vcombine_s16(s6, s7);
+
+    load_u8_8x4(s + 8, src_stride, &t0, &t1, &t2, &t3);
+    transpose_u8_8x4(&t0, &t1, &t2, &t3);
+
+    int16x4_t s8 = vget_low_s16(vreinterpretq_s16_u16(vmovl_u8(t0)));
+    int16x4_t s9 = vget_low_s16(vreinterpretq_s16_u16(vmovl_u8(t1)));
+    int16x4_t sA = vget_low_s16(vreinterpretq_s16_u16(vmovl_u8(t2)));
+
+    int16x8_t s7s8 = vcombine_s16(s7, s8);
+    int16x8_t s8s9 = vcombine_s16(s8, s9);
+    int16x8_t s9sA = vcombine_s16(s9, sA);
+
+    s += 11;
+
+    do {
+      load_u8_8x4(s, src_stride, &t0, &t1, &t2, &t3);
+      transpose_u8_8x4(&t0, &t1, &t2, &t3);
+
+      int16x4_t sB = vget_low_s16(vreinterpretq_s16_u16(vmovl_u8(t0)));
+      int16x4_t sC = vget_low_s16(vreinterpretq_s16_u16(vmovl_u8(t1)));
+      int16x4_t sD = vget_low_s16(vreinterpretq_s16_u16(vmovl_u8(t2)));
+      int16x4_t sE = vget_low_s16(vreinterpretq_s16_u16(vmovl_u8(t3)));
+
+      int16x8_t sAsB = vcombine_s16(sA, sB);
+      int16x8_t sBsC = vcombine_s16(sB, sC);
+      int16x8_t sCsD = vcombine_s16(sC, sD);
+      int16x8_t sDsE = vcombine_s16(sD, sE);
+
+      uint8x8_t d01 =
+          convolve12_8(s0s1, s1s2, s2s3, s3s4, s4s5, s5s6, s6s7, s7s8, s8s9,
+                       s9sA, sAsB, sBsC, filter_0_7, filter_8_11);
+      uint8x8_t d23 =
+          convolve12_8(s2s3, s3s4, s4s5, s5s6, s6s7, s7s8, s8s9, s9sA, sAsB,
+                       sBsC, sCsD, sDsE, filter_0_7, filter_8_11);
+
+      transpose_u8_4x4(&d01, &d23);
+
+      store_u8(d + 0 * dst_stride, 2 * dst_stride, d01);
+      store_u8(d + 1 * dst_stride, 2 * dst_stride, d23);
+
+      s0s1 = s4s5;
+      s1s2 = s5s6;
+      s2s3 = s6s7;
+      s3s4 = s7s8;
+      s4s5 = s8s9;
+      s5s6 = s9sA;
+      s6s7 = sAsB;
+      s7s8 = sBsC;
+      s8s9 = sCsD;
+      s9sA = sDsE;
+      sA = sE;
+      s += 4;
+      d += 4;
+      width -= 4;
+    } while (width != 0);
+    src += 4 * src_stride;
+    dst += 4 * dst_stride;
+    h -= 4;
+  } while (h != 0);
+}
+
+void vpx_convolve12_vert_neon(const uint8_t *src, ptrdiff_t src_stride,
+                              uint8_t *dst, ptrdiff_t dst_stride,
+                              const InterpKernel12 *filter, int x0_q4,
+                              int x_step_q4, int y0_q4, int y_step_q4, int w,
+                              int h) {
+  // Scaling not supported by Neon implementation.
+  if (y_step_q4 != 16) {
+    vpx_convolve12_vert_c(src, src_stride, dst, dst_stride, filter, x0_q4,
+                          x_step_q4, y0_q4, y_step_q4, w, h);
+    return;
+  }
+
+  assert(w == 32 || w == 16 || w == 8);
+  assert(h == 32 || h == 16 || h == 8);
+
+  const int16x8_t filter_0_7 = vld1q_s16(filter[y0_q4]);
+  const int16x4_t filter_8_11 = vld1_s16(filter[y0_q4] + 8);
+
+  src -= src_stride * (MAX_FILTER_TAP / 2 - 1);
+
+  do {
+    const uint8_t *s = src;
+    uint8_t *d = dst;
+    int height = h;
+
+    uint8x8_t t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, tA;
+    load_u8_8x11(s, src_stride, &t0, &t1, &t2, &t3, &t4, &t5, &t6, &t7, &t8,
+                 &t9, &tA);
+    int16x8_t s0 = vreinterpretq_s16_u16(vmovl_u8(t0));
+    int16x8_t s1 = vreinterpretq_s16_u16(vmovl_u8(t1));
+    int16x8_t s2 = vreinterpretq_s16_u16(vmovl_u8(t2));
+    int16x8_t s3 = vreinterpretq_s16_u16(vmovl_u8(t3));
+    int16x8_t s4 = vreinterpretq_s16_u16(vmovl_u8(t4));
+    int16x8_t s5 = vreinterpretq_s16_u16(vmovl_u8(t5));
+    int16x8_t s6 = vreinterpretq_s16_u16(vmovl_u8(t6));
+    int16x8_t s7 = vreinterpretq_s16_u16(vmovl_u8(t7));
+    int16x8_t s8 = vreinterpretq_s16_u16(vmovl_u8(t8));
+    int16x8_t s9 = vreinterpretq_s16_u16(vmovl_u8(t9));
+    int16x8_t sA = vreinterpretq_s16_u16(vmovl_u8(tA));
+
+    s += 11 * src_stride;
+
+    do {
+      uint8x8_t tB, tC, tD, tE;
+      load_u8_8x4(s, src_stride, &tB, &tC, &tD, &tE);
+
+      int16x8_t sB = vreinterpretq_s16_u16(vmovl_u8(tB));
+      int16x8_t sC = vreinterpretq_s16_u16(vmovl_u8(tC));
+      int16x8_t sD = vreinterpretq_s16_u16(vmovl_u8(tD));
+      int16x8_t sE = vreinterpretq_s16_u16(vmovl_u8(tE));
+
+      uint8x8_t d0 = convolve12_8(s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA,
+                                  sB, filter_0_7, filter_8_11);
+      uint8x8_t d1 = convolve12_8(s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB,
+                                  sC, filter_0_7, filter_8_11);
+      uint8x8_t d2 = convolve12_8(s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC,
+                                  sD, filter_0_7, filter_8_11);
+      uint8x8_t d3 = convolve12_8(s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD,
+                                  sE, filter_0_7, filter_8_11);
+
+      store_u8_8x4(d, dst_stride, d0, d1, d2, d3);
+
+      s0 = s4;
+      s1 = s5;
+      s2 = s6;
+      s3 = s7;
+      s4 = s8;
+      s5 = s9;
+      s6 = sA;
+      s7 = sB;
+      s8 = sC;
+      s9 = sD;
+      sA = sE;
+      s += 4 * src_stride;
+      d += 4 * dst_stride;
+      height -= 4;
+    } while (height != 0);
+    src += 8;
+    dst += 8;
+    w -= 8;
+  } while (w != 0);
+}
+
+void vpx_convolve12_neon(const uint8_t *src, ptrdiff_t src_stride, uint8_t *dst,
+                         ptrdiff_t dst_stride, const InterpKernel12 *filter,
+                         int x0_q4, int x_step_q4, int y0_q4, int y_step_q4,
+                         int w, int h) {
+  // Scaling not supported by Neon implementation.
+  if (x_step_q4 != 16 || y_step_q4 != 16) {
+    vpx_convolve12_c(src, src_stride, dst, dst_stride, filter, x0_q4, x_step_q4,
+                     y0_q4, y_step_q4, w, h);
+    return;
+  }
+
+  assert(w == 32 || w == 16 || w == 8);
+  assert(h == 32 || h == 16 || h == 8);
+
+  DECLARE_ALIGNED(32, uint8_t, im_block[BW * (BH + MAX_FILTER_TAP)]);
+
+  const int im_stride = BW;
+  // Account for the vertical pass needing MAX_FILTER_TAP / 2 - 1 lines prior
+  // and MAX_FILTER_TAP / 2 lines post. (+1 to make total divisible by 4.)
+  const int im_height = h + MAX_FILTER_TAP;
+  const ptrdiff_t border_offset = MAX_FILTER_TAP / 2 - 1;
+
+  // Filter starting border_offset rows up.
+  vpx_convolve12_horiz_neon(src - src_stride * border_offset, src_stride,
+                            im_block, im_stride, filter, x0_q4, x_step_q4,
+                            y0_q4, y_step_q4, w, im_height);
+
+  vpx_convolve12_vert_neon(im_block + im_stride * border_offset, im_stride, dst,
+                           dst_stride, filter, x0_q4, x_step_q4, y0_q4,
+                           y_step_q4, w, h);
+}
diff --git a/vp9/encoder/arm/neon/vp9_temporal_filter_neon_dotprod.c b/vp9/encoder/arm/neon/vp9_temporal_filter_neon_dotprod.c
new file mode 100644
index 0000000..5fea75a
--- /dev/null
+++ b/vp9/encoder/arm/neon/vp9_temporal_filter_neon_dotprod.c
@@ -0,0 +1,392 @@
+/*
+ *  Copyright (c) 2025 The WebM 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 <assert.h>
+#include <arm_neon.h>
+
+#include "./vp9_rtcd.h"
+#include "./vpx_config.h"
+#include "vpx/vpx_integer.h"
+#include "vpx_dsp/arm/mem_neon.h"
+#include "vp9/encoder/vp9_temporal_filter.h"
+
+DECLARE_ALIGNED(16, static const uint8_t, kDotProdPermuteTbl[48]) = {
+  // clang-format off
+  0,  1,  2,  3,  1,  2,  3,  4,  2,  3,  4,  5,  3,  4,  5,  6,
+  4,  5,  6,  7,  5,  6,  7,  8,  6,  7,  8,  9,  7,  8,  9, 10,
+  8,  9, 10, 11,  9, 10, 11, 12, 10, 11, 12, 13, 11, 12, 13, 14
+  // clang-format on
+};
+
+DECLARE_ALIGNED(16, static const uint8_t, kDotProdMergeBlockTbl[48]) = {
+  // clang-format off
+  // Shift left and insert new last column in transposed 4x4 block.
+  1,  2,  3, 16,  5,  6,  7, 20,  9, 10, 11, 24, 13, 14, 15, 28,
+  // Shift left and insert two new columns in transposed 4x4 block.
+  2,  3, 16, 17,  6,  7, 20, 21, 10, 11, 24, 25, 14, 15, 28, 29,
+  // Shift left and insert three new columns in transposed 4x4 block.
+  3, 16, 17, 18,  7, 20, 21, 22, 11, 24, 25, 26, 15, 28, 29, 30
+  // clang-format on
+};
+
+static INLINE uint8x8_t convolve12_8_h(uint8x16_t samples[2],
+                                       const int8x16_t filter,
+                                       const uint8x16x3_t perm_tbl) {
+  // Transform sample range to [-128, 127] for 8-bit signed dot product.
+  int8x16_t samples_128[2] = {
+    vreinterpretq_s8_u8(vsubq_u8(samples[0], vdupq_n_u8(128))),
+    vreinterpretq_s8_u8(vsubq_u8(samples[1], vdupq_n_u8(128)))
+  };
+
+  // Permute samples ready for dot product.
+  // {  0,  1,  2,  3,  1,  2,  3,  4,  2,  3,  4,  5,  3,  4,  5,  6 }
+  // {  4,  5,  6,  7,  5,  6,  7,  8,  6,  7,  8,  9,  7,  8,  9, 10 }
+  // {  8,  9, 10, 11,  9, 10, 11, 12, 10, 11, 12, 13, 11, 12, 13, 14 }
+  // { 12, 13, 14, 15, 13, 14, 15, 16, 14, 15, 16, 17, 15, 16, 17, 18 }
+  int8x16_t perm_samples[4] = { vqtbl1q_s8(samples_128[0], perm_tbl.val[0]),
+                                vqtbl1q_s8(samples_128[0], perm_tbl.val[1]),
+                                vqtbl1q_s8(samples_128[0], perm_tbl.val[2]),
+                                vqtbl1q_s8(samples_128[1], perm_tbl.val[2]) };
+
+  // Accumulate into 128 << FILTER_BITS to account for range transform.
+  int32x4_t acc = vdupq_n_s32(128 << FILTER_BITS);
+
+  int32x4_t sum0123 = vdotq_laneq_s32(acc, perm_samples[0], filter, 0);
+  sum0123 = vdotq_laneq_s32(sum0123, perm_samples[1], filter, 1);
+  sum0123 = vdotq_laneq_s32(sum0123, perm_samples[2], filter, 2);
+
+  int32x4_t sum4567 = vdotq_laneq_s32(acc, perm_samples[1], filter, 0);
+  sum4567 = vdotq_laneq_s32(sum4567, perm_samples[2], filter, 1);
+  sum4567 = vdotq_laneq_s32(sum4567, perm_samples[3], filter, 2);
+
+  // Narrow and re-pack.
+  int16x8_t sum_s16 = vcombine_s16(vqrshrn_n_s32(sum0123, FILTER_BITS),
+                                   vqrshrn_n_s32(sum4567, FILTER_BITS));
+  return vqmovun_s16(sum_s16);
+}
+
+void vpx_convolve12_horiz_neon_dotprod(const uint8_t *src, ptrdiff_t src_stride,
+                                       uint8_t *dst, ptrdiff_t dst_stride,
+                                       const InterpKernel12 *filter, int x0_q4,
+                                       int x_step_q4, int y0_q4, int y_step_q4,
+                                       int w, int h) {
+  // Scaling not supported by Neon implementation.
+  if (x_step_q4 != 16) {
+    vpx_convolve12_horiz_c(src, src_stride, dst, dst_stride, filter, x0_q4,
+                           x_step_q4, y0_q4, y_step_q4, w, h);
+    return;
+  }
+
+  assert(w == 32 || w == 16 || w == 8);
+  assert(h == 32 || h == 16 || h == 8);
+
+  const int16x8_t x_filter_0_7 = vld1q_s16(filter[x0_q4]);
+  const int16x4_t x_filter_8_11 = vld1_s16(filter[x0_q4] + 8);
+  const int16x8_t x_filter_8_15 = vcombine_s16(x_filter_8_11, vdup_n_s16(0));
+  const int8x16_t x_filter =
+      vcombine_s8(vmovn_s16(x_filter_0_7), vmovn_s16(x_filter_8_15));
+
+  const uint8x16x3_t permute_tbl = vld1q_u8_x3(kDotProdPermuteTbl);
+
+  src -= MAX_FILTER_TAP / 2 - 1;
+
+  do {
+    const uint8_t *s = src;
+    uint8_t *d = dst;
+    int width = w;
+
+    do {
+      uint8x16_t s0[2], s1[2], s2[2], s3[2];
+      load_u8_16x4(s, src_stride, &s0[0], &s1[0], &s2[0], &s3[0]);
+      load_u8_16x4(s + 4, src_stride, &s0[1], &s1[1], &s2[1], &s3[1]);
+
+      uint8x8_t d0 = convolve12_8_h(s0, x_filter, permute_tbl);
+      uint8x8_t d1 = convolve12_8_h(s1, x_filter, permute_tbl);
+      uint8x8_t d2 = convolve12_8_h(s2, x_filter, permute_tbl);
+      uint8x8_t d3 = convolve12_8_h(s3, x_filter, permute_tbl);
+
+      store_u8_8x4(d, dst_stride, d0, d1, d2, d3);
+
+      s += 8;
+      d += 8;
+      width -= 8;
+    } while (width != 0);
+    src += 4 * src_stride;
+    dst += 4 * dst_stride;
+    h -= 4;
+  } while (h != 0);
+}
+
+static INLINE uint8x8_t convolve12_8_v(
+    const int8x16_t s0_lo, const int8x16_t s0_hi, const int8x16_t s1_lo,
+    const int8x16_t s1_hi, const int8x16_t s2_lo, const int8x16_t s2_hi,
+    const int8x8_t filters_0_7, const int8x8_t filters_4_11) {
+  // The sample range transform and permutation are performed by the caller.
+  // Accumulate into 128 << FILTER_BITS to account for range transform.
+  int32x4_t acc = vdupq_n_s32(128 << FILTER_BITS);
+
+  int32x4_t sum0123 = vdotq_lane_s32(acc, s0_lo, filters_0_7, 0);
+  sum0123 = vdotq_lane_s32(sum0123, s1_lo, filters_0_7, 1);
+  sum0123 = vdotq_lane_s32(sum0123, s2_lo, filters_4_11, 1);
+
+  int32x4_t sum4567 = vdotq_lane_s32(acc, s0_hi, filters_0_7, 0);
+  sum4567 = vdotq_lane_s32(sum4567, s1_hi, filters_0_7, 1);
+  sum4567 = vdotq_lane_s32(sum4567, s2_hi, filters_4_11, 1);
+
+  // Narrow and re-pack.
+  int16x8_t sum = vcombine_s16(vqmovn_s32(sum0123), vqmovn_s32(sum4567));
+  return vqrshrun_n_s16(sum, FILTER_BITS);
+}
+
+static INLINE void transpose_concat_8x4(int8x8_t a0, int8x8_t a1, int8x8_t a2,
+                                        int8x8_t a3, int8x16_t *b0,
+                                        int8x16_t *b1) {
+  // Transpose 8-bit elements and concatenate result rows as follows:
+  // a0: 00, 01, 02, 03, 04, 05, 06, 07
+  // a1: 10, 11, 12, 13, 14, 15, 16, 17
+  // a2: 20, 21, 22, 23, 24, 25, 26, 27
+  // a3: 30, 31, 32, 33, 34, 35, 36, 37
+  //
+  // b0: 00, 10, 20, 30, 01, 11, 21, 31, 02, 12, 22, 32, 03, 13, 23, 33
+  // b1: 04, 14, 24, 34, 05, 15, 25, 35, 06, 16, 26, 36, 07, 17, 27, 37
+
+  int8x16_t a0q = vcombine_s8(a0, vdup_n_s8(0));
+  int8x16_t a1q = vcombine_s8(a1, vdup_n_s8(0));
+  int8x16_t a2q = vcombine_s8(a2, vdup_n_s8(0));
+  int8x16_t a3q = vcombine_s8(a3, vdup_n_s8(0));
+
+  int8x16_t a02 = vzipq_s8(a0q, a2q).val[0];
+  int8x16_t a13 = vzipq_s8(a1q, a3q).val[0];
+
+  int8x16x2_t a0123 = vzipq_s8(a02, a13);
+
+  *b0 = a0123.val[0];
+  *b1 = a0123.val[1];
+}
+
+void vpx_convolve12_vert_neon_dotprod(const uint8_t *src, ptrdiff_t src_stride,
+                                      uint8_t *dst, ptrdiff_t dst_stride,
+                                      const InterpKernel12 *filter, int x0_q4,
+                                      int x_step_q4, int y0_q4, int y_step_q4,
+                                      int w, int h) {
+  // Scaling not supported by Neon implementation.
+  if (y_step_q4 != 16) {
+    vpx_convolve12_vert_c(src, src_stride, dst, dst_stride, filter, x0_q4,
+                          x_step_q4, y0_q4, y_step_q4, w, h);
+    return;
+  }
+
+  assert(w == 32 || w == 16 || w == 8);
+  assert(h == 32 || h == 16 || h == 8);
+
+  const int8x8_t filter_0_7 = vmovn_s16(vld1q_s16(filter[y0_q4]));
+  const int8x8_t filter_4_11 = vmovn_s16(vld1q_s16(filter[y0_q4] + 4));
+
+  const uint8x16x3_t merge_block_tbl = vld1q_u8_x3(kDotProdMergeBlockTbl);
+
+  src -= src_stride * (MAX_FILTER_TAP / 2 - 1);
+
+  do {
+    int height = h;
+    const uint8_t *s = src;
+    uint8_t *d = dst;
+
+    uint8x8_t t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, tA;
+    load_u8_8x11(s, src_stride, &t0, &t1, &t2, &t3, &t4, &t5, &t6, &t7, &t8,
+                 &t9, &tA);
+    s += 11 * src_stride;
+
+    // Transform sample range to [-128, 127] for 8-bit signed dot product.
+    int8x8_t s0 = vreinterpret_s8_u8(vsub_u8(t0, vdup_n_u8(128)));
+    int8x8_t s1 = vreinterpret_s8_u8(vsub_u8(t1, vdup_n_u8(128)));
+    int8x8_t s2 = vreinterpret_s8_u8(vsub_u8(t2, vdup_n_u8(128)));
+    int8x8_t s3 = vreinterpret_s8_u8(vsub_u8(t3, vdup_n_u8(128)));
+    int8x8_t s4 = vreinterpret_s8_u8(vsub_u8(t4, vdup_n_u8(128)));
+    int8x8_t s5 = vreinterpret_s8_u8(vsub_u8(t5, vdup_n_u8(128)));
+    int8x8_t s6 = vreinterpret_s8_u8(vsub_u8(t6, vdup_n_u8(128)));
+    int8x8_t s7 = vreinterpret_s8_u8(vsub_u8(t7, vdup_n_u8(128)));
+    int8x8_t s8 = vreinterpret_s8_u8(vsub_u8(t8, vdup_n_u8(128)));
+    int8x8_t s9 = vreinterpret_s8_u8(vsub_u8(t9, vdup_n_u8(128)));
+    int8x8_t sA = vreinterpret_s8_u8(vsub_u8(tA, vdup_n_u8(128)));
+
+    // This operation combines a conventional transpose and the sample permute
+    // (see horizontal case) required before computing the dot product.
+    int8x16_t s0123_lo, s0123_hi, s1234_lo, s1234_hi, s2345_lo, s2345_hi,
+        s3456_lo, s3456_hi, s4567_lo, s4567_hi, s5678_lo, s5678_hi, s6789_lo,
+        s6789_hi, s789A_lo, s789A_hi;
+    transpose_concat_8x4(s0, s1, s2, s3, &s0123_lo, &s0123_hi);
+    transpose_concat_8x4(s1, s2, s3, s4, &s1234_lo, &s1234_hi);
+    transpose_concat_8x4(s2, s3, s4, s5, &s2345_lo, &s2345_hi);
+    transpose_concat_8x4(s3, s4, s5, s6, &s3456_lo, &s3456_hi);
+    transpose_concat_8x4(s4, s5, s6, s7, &s4567_lo, &s4567_hi);
+    transpose_concat_8x4(s5, s6, s7, s8, &s5678_lo, &s5678_hi);
+    transpose_concat_8x4(s6, s7, s8, s9, &s6789_lo, &s6789_hi);
+    transpose_concat_8x4(s7, s8, s9, sA, &s789A_lo, &s789A_hi);
+
+    do {
+      uint8x8_t tB, tC, tD, tE;
+      load_u8_8x4(s, src_stride, &tB, &tC, &tD, &tE);
+
+      int8x8_t sB = vreinterpret_s8_u8(vsub_u8(tB, vdup_n_u8(128)));
+      int8x8_t sC = vreinterpret_s8_u8(vsub_u8(tC, vdup_n_u8(128)));
+      int8x8_t sD = vreinterpret_s8_u8(vsub_u8(tD, vdup_n_u8(128)));
+      int8x8_t sE = vreinterpret_s8_u8(vsub_u8(tE, vdup_n_u8(128)));
+
+      int8x16_t s89AB_lo, s89AB_hi, s9ABC_lo, s9ABC_hi, sABCD_lo, sABCD_hi,
+          sBCDE_lo, sBCDE_hi;
+      transpose_concat_8x4(sB, sC, sD, sE, &sBCDE_lo, &sBCDE_hi);
+
+      // Merge new data into block from previous iteration.
+      int8x16x2_t samples_LUT_lo = { { s789A_lo, sBCDE_lo } };
+      s89AB_lo = vqtbl2q_s8(samples_LUT_lo, merge_block_tbl.val[0]);
+      s9ABC_lo = vqtbl2q_s8(samples_LUT_lo, merge_block_tbl.val[1]);
+      sABCD_lo = vqtbl2q_s8(samples_LUT_lo, merge_block_tbl.val[2]);
+
+      int8x16x2_t samples_LUT_hi = { { s789A_hi, sBCDE_hi } };
+      s89AB_hi = vqtbl2q_s8(samples_LUT_hi, merge_block_tbl.val[0]);
+      s9ABC_hi = vqtbl2q_s8(samples_LUT_hi, merge_block_tbl.val[1]);
+      sABCD_hi = vqtbl2q_s8(samples_LUT_hi, merge_block_tbl.val[2]);
+
+      uint8x8_t d0 =
+          convolve12_8_v(s0123_lo, s0123_hi, s4567_lo, s4567_hi, s89AB_lo,
+                         s89AB_hi, filter_0_7, filter_4_11);
+      uint8x8_t d1 =
+          convolve12_8_v(s1234_lo, s1234_hi, s5678_lo, s5678_hi, s9ABC_lo,
+                         s9ABC_hi, filter_0_7, filter_4_11);
+      uint8x8_t d2 =
+          convolve12_8_v(s2345_lo, s2345_hi, s6789_lo, s6789_hi, sABCD_lo,
+                         sABCD_hi, filter_0_7, filter_4_11);
+      uint8x8_t d3 =
+          convolve12_8_v(s3456_lo, s3456_hi, s789A_lo, s789A_hi, sBCDE_lo,
+                         sBCDE_hi, filter_0_7, filter_4_11);
+
+      store_u8_8x4(d, dst_stride, d0, d1, d2, d3);
+
+      // Prepare block for next iteration - re-using as much as possible.
+      // Shuffle everything up four rows.
+      s0123_lo = s4567_lo;
+      s0123_hi = s4567_hi;
+      s1234_lo = s5678_lo;
+      s1234_hi = s5678_hi;
+      s2345_lo = s6789_lo;
+      s2345_hi = s6789_hi;
+      s3456_lo = s789A_lo;
+      s3456_hi = s789A_hi;
+      s4567_lo = s89AB_lo;
+      s4567_hi = s89AB_hi;
+      s5678_lo = s9ABC_lo;
+      s5678_hi = s9ABC_hi;
+      s6789_lo = sABCD_lo;
+      s6789_hi = sABCD_hi;
+      s789A_lo = sBCDE_lo;
+      s789A_hi = sBCDE_hi;
+
+      s += 4 * src_stride;
+      d += 4 * dst_stride;
+      height -= 4;
+    } while (height != 0);
+    src += 8;
+    dst += 8;
+    w -= 8;
+  } while (w != 0);
+}
+
+static INLINE void vpx_convolve12_2d_horiz_neon_dotprod(
+    const uint8_t *src, ptrdiff_t src_stride, uint8_t *dst,
+    ptrdiff_t dst_stride, const InterpKernel12 *filter, int x0_q4, int w,
+    int h) {
+  assert(w == 32 || w == 16 || w == 8);
+  assert(h % 4 == 3);
+
+  const int16x8_t x_filter_0_7 = vld1q_s16(filter[x0_q4]);
+  const int16x4_t x_filter_8_11 = vld1_s16(filter[x0_q4] + 8);
+  const int16x8_t x_filter_8_15 = vcombine_s16(x_filter_8_11, vdup_n_s16(0));
+  const int8x16_t x_filter =
+      vcombine_s8(vmovn_s16(x_filter_0_7), vmovn_s16(x_filter_8_15));
+
+  const uint8x16x3_t permute_tbl = vld1q_u8_x3(kDotProdPermuteTbl);
+
+  src -= MAX_FILTER_TAP / 2 - 1;
+
+  do {
+    const uint8_t *s = src;
+    uint8_t *d = dst;
+    int width = w;
+
+    do {
+      uint8x16_t s0[2], s1[2], s2[2], s3[2];
+      load_u8_16x4(s, src_stride, &s0[0], &s1[0], &s2[0], &s3[0]);
+      load_u8_16x4(s + 4, src_stride, &s0[1], &s1[1], &s2[1], &s3[1]);
+
+      uint8x8_t d0 = convolve12_8_h(s0, x_filter, permute_tbl);
+      uint8x8_t d1 = convolve12_8_h(s1, x_filter, permute_tbl);
+      uint8x8_t d2 = convolve12_8_h(s2, x_filter, permute_tbl);
+      uint8x8_t d3 = convolve12_8_h(s3, x_filter, permute_tbl);
+
+      store_u8_8x4(d, dst_stride, d0, d1, d2, d3);
+
+      s += 8;
+      d += 8;
+      width -= 8;
+    } while (width != 0);
+    src += 4 * src_stride;
+    dst += 4 * dst_stride;
+    h -= 4;
+  } while (h != 3);
+
+  do {
+    uint8x16_t s0[2], s1[2], s2[2];
+    load_u8_16x3(src, src_stride, &s0[0], &s1[0], &s2[0]);
+    load_u8_16x3(src + 4, src_stride, &s0[1], &s1[1], &s2[1]);
+
+    uint8x8_t d0 = convolve12_8_h(s0, x_filter, permute_tbl);
+    uint8x8_t d1 = convolve12_8_h(s1, x_filter, permute_tbl);
+    uint8x8_t d2 = convolve12_8_h(s2, x_filter, permute_tbl);
+
+    store_u8_8x3(dst, dst_stride, d0, d1, d2);
+
+    src += 8;
+    dst += 8;
+    w -= 8;
+  } while (w != 0);
+}
+
+void vpx_convolve12_neon_dotprod(const uint8_t *src, ptrdiff_t src_stride,
+                                 uint8_t *dst, ptrdiff_t dst_stride,
+                                 const InterpKernel12 *filter, int x0_q4,
+                                 int x_step_q4, int y0_q4, int y_step_q4, int w,
+                                 int h) {
+  // Scaling not supported by Neon implementation.
+  if (x_step_q4 != 16 || y_step_q4 != 16) {
+    vpx_convolve12_c(src, src_stride, dst, dst_stride, filter, x0_q4, x_step_q4,
+                     y0_q4, y_step_q4, w, h);
+    return;
+  }
+
+  assert(w == 32 || w == 16 || w == 8);
+  assert(h == 32 || h == 16 || h == 8);
+
+  DECLARE_ALIGNED(32, uint8_t, im_block[BW * (BH + MAX_FILTER_TAP)]);
+
+  const int im_stride = BW;
+  // Account for the vertical pass needing MAX_FILTER_TAP / 2 - 1 lines prior
+  // and MAX_FILTER_TAP / 2 lines post.
+  const int im_height = h + MAX_FILTER_TAP - 1;
+  const ptrdiff_t border_offset = MAX_FILTER_TAP / 2 - 1;
+
+  // Filter starting border_offset rows up.
+  vpx_convolve12_2d_horiz_neon_dotprod(src - src_stride * border_offset,
+                                       src_stride, im_block, im_stride, filter,
+                                       x0_q4, w, im_height);
+
+  vpx_convolve12_vert_neon_dotprod(im_block + im_stride * border_offset,
+                                   im_stride, dst, dst_stride, filter, x0_q4,
+                                   x_step_q4, y0_q4, y_step_q4, w, h);
+}
diff --git a/vp9/encoder/arm/neon/vp9_temporal_filter_neon_i8mm.c b/vp9/encoder/arm/neon/vp9_temporal_filter_neon_i8mm.c
new file mode 100644
index 0000000..9e1f39b
--- /dev/null
+++ b/vp9/encoder/arm/neon/vp9_temporal_filter_neon_i8mm.c
@@ -0,0 +1,376 @@
+/*
+ *  Copyright (c) 2025 The WebM 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 <assert.h>
+#include <arm_neon.h>
+
+#include "./vp9_rtcd.h"
+#include "./vpx_config.h"
+#include "vpx/vpx_integer.h"
+#include "vpx_dsp/arm/mem_neon.h"
+#include "vp9/encoder/vp9_temporal_filter.h"
+
+DECLARE_ALIGNED(16, static const uint8_t, kMatMulPermuteTbl[32]) = {
+  // clang-format off
+  0,  1,  2,  3,  4,  5,  6,  7,  2,  3,  4,  5,  6,  7,  8,  9,
+  4,  5,  6,  7,  8,  9, 10, 11,  6,  7,  8,  9, 10, 11, 12, 13
+  // clang-format on
+};
+
+DECLARE_ALIGNED(16, static const uint8_t, kDotProdMergeBlockTbl[48]) = {
+  // clang-format off
+  // Shift left and insert new last column in transposed 4x4 block.
+  1,  2,  3, 16,  5,  6,  7, 20,  9, 10, 11, 24, 13, 14, 15, 28,
+  // Shift left and insert two new columns in transposed 4x4 block.
+  2,  3, 16, 17,  6,  7, 20, 21, 10, 11, 24, 25, 14, 15, 28, 29,
+  // Shift left and insert three new columns in transposed 4x4 block.
+  3, 16, 17, 18,  7, 20, 21, 22, 11, 24, 25, 26, 15, 28, 29, 30
+  // clang-format on
+};
+
+static INLINE uint8x8_t convolve12_8_h(uint8x16_t samples[2],
+                                       const int8x16_t filter[2],
+                                       const uint8x16x2_t perm_tbl) {
+  // Permute samples ready for matrix multiply.
+  // {  0,  1,  2,  3,  4,  5,  6,  7,  2,  3,  4,  5,  6,  7,  8,  9 }
+  // {  4,  5,  6,  7,  8,  9, 10, 11,  6,  7,  8,  9, 10, 11, 12, 13 }
+  // {  6,  7,  8,  9, 10, 11, 12, 13,  8,  9, 10, 11, 12, 13, 14, 15 }
+  // { 10, 11, 12, 13, 14, 15, 16, 17, 12, 13, 14, 15, 16, 17, 18, 19 }
+  uint8x16_t perm_samples[4] = { vqtbl1q_u8(samples[0], perm_tbl.val[0]),
+                                 vqtbl1q_u8(samples[0], perm_tbl.val[1]),
+                                 vqtbl1q_u8(samples[1], perm_tbl.val[0]),
+                                 vqtbl1q_u8(samples[1], perm_tbl.val[1]) };
+
+  // These instructions multiply a 2x8 matrix (samples) by an 8x2 matrix
+  // (filter), destructively accumulating into the destination register.
+  int32x4_t sum0123 = vusmmlaq_s32(vdupq_n_s32(0), perm_samples[0], filter[0]);
+  int32x4_t sum4567 = vusmmlaq_s32(vdupq_n_s32(0), perm_samples[1], filter[0]);
+  sum0123 = vusmmlaq_s32(sum0123, perm_samples[2], filter[1]);
+  sum4567 = vusmmlaq_s32(sum4567, perm_samples[3], filter[1]);
+
+  // Narrow and re-pack.
+  int16x8_t sum_s16 = vcombine_s16(vqrshrn_n_s32(sum0123, FILTER_BITS),
+                                   vqrshrn_n_s32(sum4567, FILTER_BITS));
+  return vqmovun_s16(sum_s16);
+}
+
+void vpx_convolve12_horiz_neon_i8mm(const uint8_t *src, ptrdiff_t src_stride,
+                                    uint8_t *dst, ptrdiff_t dst_stride,
+                                    const InterpKernel12 *filter, int x0_q4,
+                                    int x_step_q4, int y0_q4, int y_step_q4,
+                                    int w, int h) {
+  // Scaling not supported by Neon implementation.
+  if (x_step_q4 != 16) {
+    vpx_convolve12_horiz_c(src, src_stride, dst, dst_stride, filter, x0_q4,
+                           x_step_q4, y0_q4, y_step_q4, w, h);
+    return;
+  }
+
+  assert(w == 32 || w == 16 || w == 8);
+  assert(h == 32 || h == 16 || h == 8);
+
+  // Split 12-tap filter into two 6-tap filters, masking the top two elements.
+  // { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0 }
+  const int8x8_t mask = vcreate_s8(0x0000ffffffffffff);
+  const int8x8_t filter_0 = vand_s8(vmovn_s16(vld1q_s16(filter[x0_q4])), mask);
+  const int8x8_t filter_1 =
+      vext_s8(vmovn_s16(vld1q_s16(filter[x0_q4] + 4)), vdup_n_s8(0), 2);
+
+  // Stagger each 6-tap filter to enable use of matrix multiply instructions.
+  // { f0, f1, f2, f3, f4, f5,  0,  0,  0, f0, f1, f2, f3, f4, f5,  0 }
+  const int8x16_t x_filter[2] = {
+    vcombine_s8(filter_0, vext_s8(filter_0, filter_0, 7)),
+    vcombine_s8(filter_1, vext_s8(filter_1, filter_1, 7))
+  };
+
+  const uint8x16x2_t permute_tbl = vld1q_u8_x2(kMatMulPermuteTbl);
+
+  src -= MAX_FILTER_TAP / 2 - 1;
+
+  do {
+    const uint8_t *s = src;
+    uint8_t *d = dst;
+    int width = w;
+
+    do {
+      uint8x16_t s0[2], s1[2], s2[2], s3[2];
+      load_u8_16x4(s, src_stride, &s0[0], &s1[0], &s2[0], &s3[0]);
+      load_u8_16x4(s + 6, src_stride, &s0[1], &s1[1], &s2[1], &s3[1]);
+
+      uint8x8_t d0 = convolve12_8_h(s0, x_filter, permute_tbl);
+      uint8x8_t d1 = convolve12_8_h(s1, x_filter, permute_tbl);
+      uint8x8_t d2 = convolve12_8_h(s2, x_filter, permute_tbl);
+      uint8x8_t d3 = convolve12_8_h(s3, x_filter, permute_tbl);
+
+      store_u8_8x4(d, dst_stride, d0, d1, d2, d3);
+
+      s += 8;
+      d += 8;
+      width -= 8;
+    } while (width != 0);
+    src += 4 * src_stride;
+    dst += 4 * dst_stride;
+    h -= 4;
+  } while (h != 0);
+}
+
+static INLINE uint8x8_t convolve12_8_v(
+    const uint8x16_t s0_lo, const uint8x16_t s0_hi, const uint8x16_t s1_lo,
+    const uint8x16_t s1_hi, const uint8x16_t s2_lo, const uint8x16_t s2_hi,
+    const int8x8_t filters_0_7, const int8x8_t filters_4_11) {
+  // The sample range transform and permutation are performed by the caller.
+  int32x4_t sum0123 = vusdotq_lane_s32(vdupq_n_s32(0), s0_lo, filters_0_7, 0);
+  sum0123 = vusdotq_lane_s32(sum0123, s1_lo, filters_0_7, 1);
+  sum0123 = vusdotq_lane_s32(sum0123, s2_lo, filters_4_11, 1);
+
+  int32x4_t sum4567 = vusdotq_lane_s32(vdupq_n_s32(0), s0_hi, filters_0_7, 0);
+  sum4567 = vusdotq_lane_s32(sum4567, s1_hi, filters_0_7, 1);
+  sum4567 = vusdotq_lane_s32(sum4567, s2_hi, filters_4_11, 1);
+
+  // Narrow and re-pack.
+  int16x8_t sum = vcombine_s16(vqmovn_s32(sum0123), vqmovn_s32(sum4567));
+  return vqrshrun_n_s16(sum, FILTER_BITS);
+}
+
+static INLINE void transpose_concat_8x4(uint8x8_t a0, uint8x8_t a1,
+                                        uint8x8_t a2, uint8x8_t a3,
+                                        uint8x16_t *b0, uint8x16_t *b1) {
+  // Transpose 8-bit elements and concatenate result rows as follows:
+  // a0: 00, 01, 02, 03, 04, 05, 06, 07
+  // a1: 10, 11, 12, 13, 14, 15, 16, 17
+  // a2: 20, 21, 22, 23, 24, 25, 26, 27
+  // a3: 30, 31, 32, 33, 34, 35, 36, 37
+  //
+  // b0: 00, 10, 20, 30, 01, 11, 21, 31, 02, 12, 22, 32, 03, 13, 23, 33
+  // b1: 04, 14, 24, 34, 05, 15, 25, 35, 06, 16, 26, 36, 07, 17, 27, 37
+
+  uint8x16_t a0q = vcombine_u8(a0, vdup_n_u8(0));
+  uint8x16_t a1q = vcombine_u8(a1, vdup_n_u8(0));
+  uint8x16_t a2q = vcombine_u8(a2, vdup_n_u8(0));
+  uint8x16_t a3q = vcombine_u8(a3, vdup_n_u8(0));
+
+  uint8x16_t a02 = vzipq_u8(a0q, a2q).val[0];
+  uint8x16_t a13 = vzipq_u8(a1q, a3q).val[0];
+
+  uint8x16x2_t a0123 = vzipq_u8(a02, a13);
+
+  *b0 = a0123.val[0];
+  *b1 = a0123.val[1];
+}
+
+void vpx_convolve12_vert_neon_i8mm(const uint8_t *src, ptrdiff_t src_stride,
+                                   uint8_t *dst, ptrdiff_t dst_stride,
+                                   const InterpKernel12 *filter, int x0_q4,
+                                   int x_step_q4, int y0_q4, int y_step_q4,
+                                   int w, int h) {
+  // Scaling not supported by Neon implementation.
+  if (y_step_q4 != 16) {
+    vpx_convolve12_vert_c(src, src_stride, dst, dst_stride, filter, x0_q4,
+                          x_step_q4, y0_q4, y_step_q4, w, h);
+    return;
+  }
+
+  assert(w == 32 || w == 16 || w == 8);
+  assert(h == 32 || h == 16 || h == 8);
+
+  const int8x8_t filter_0_7 = vmovn_s16(vld1q_s16(filter[y0_q4]));
+  const int8x8_t filter_4_11 = vmovn_s16(vld1q_s16(filter[y0_q4] + 4));
+
+  const uint8x16x3_t merge_block_tbl = vld1q_u8_x3(kDotProdMergeBlockTbl);
+
+  src -= src_stride * (MAX_FILTER_TAP / 2 - 1);
+
+  do {
+    int height = h;
+    const uint8_t *s = src;
+    uint8_t *d = dst;
+
+    uint8x8_t s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA;
+    load_u8_8x11(s, src_stride, &s0, &s1, &s2, &s3, &s4, &s5, &s6, &s7, &s8,
+                 &s9, &sA);
+    s += 11 * src_stride;
+
+    // This operation combines a conventional transpose and the sample permute
+    // (see horizontal case) required before computing the dot product.
+    uint8x16_t s0123_lo, s0123_hi, s1234_lo, s1234_hi, s2345_lo, s2345_hi,
+        s3456_lo, s3456_hi, s4567_lo, s4567_hi, s5678_lo, s5678_hi, s6789_lo,
+        s6789_hi, s789A_lo, s789A_hi;
+    transpose_concat_8x4(s0, s1, s2, s3, &s0123_lo, &s0123_hi);
+    transpose_concat_8x4(s1, s2, s3, s4, &s1234_lo, &s1234_hi);
+    transpose_concat_8x4(s2, s3, s4, s5, &s2345_lo, &s2345_hi);
+    transpose_concat_8x4(s3, s4, s5, s6, &s3456_lo, &s3456_hi);
+    transpose_concat_8x4(s4, s5, s6, s7, &s4567_lo, &s4567_hi);
+    transpose_concat_8x4(s5, s6, s7, s8, &s5678_lo, &s5678_hi);
+    transpose_concat_8x4(s6, s7, s8, s9, &s6789_lo, &s6789_hi);
+    transpose_concat_8x4(s7, s8, s9, sA, &s789A_lo, &s789A_hi);
+
+    do {
+      uint8x8_t sB, sC, sD, sE;
+      load_u8_8x4(s, src_stride, &sB, &sC, &sD, &sE);
+
+      uint8x16_t s89AB_lo, s89AB_hi, s9ABC_lo, s9ABC_hi, sABCD_lo, sABCD_hi,
+          sBCDE_lo, sBCDE_hi;
+      transpose_concat_8x4(sB, sC, sD, sE, &sBCDE_lo, &sBCDE_hi);
+
+      // Merge new data into block from previous iteration.
+      uint8x16x2_t samples_LUT_lo = { { s789A_lo, sBCDE_lo } };
+      s89AB_lo = vqtbl2q_u8(samples_LUT_lo, merge_block_tbl.val[0]);
+      s9ABC_lo = vqtbl2q_u8(samples_LUT_lo, merge_block_tbl.val[1]);
+      sABCD_lo = vqtbl2q_u8(samples_LUT_lo, merge_block_tbl.val[2]);
+
+      uint8x16x2_t samples_LUT_hi = { { s789A_hi, sBCDE_hi } };
+      s89AB_hi = vqtbl2q_u8(samples_LUT_hi, merge_block_tbl.val[0]);
+      s9ABC_hi = vqtbl2q_u8(samples_LUT_hi, merge_block_tbl.val[1]);
+      sABCD_hi = vqtbl2q_u8(samples_LUT_hi, merge_block_tbl.val[2]);
+
+      uint8x8_t d0 =
+          convolve12_8_v(s0123_lo, s0123_hi, s4567_lo, s4567_hi, s89AB_lo,
+                         s89AB_hi, filter_0_7, filter_4_11);
+      uint8x8_t d1 =
+          convolve12_8_v(s1234_lo, s1234_hi, s5678_lo, s5678_hi, s9ABC_lo,
+                         s9ABC_hi, filter_0_7, filter_4_11);
+      uint8x8_t d2 =
+          convolve12_8_v(s2345_lo, s2345_hi, s6789_lo, s6789_hi, sABCD_lo,
+                         sABCD_hi, filter_0_7, filter_4_11);
+      uint8x8_t d3 =
+          convolve12_8_v(s3456_lo, s3456_hi, s789A_lo, s789A_hi, sBCDE_lo,
+                         sBCDE_hi, filter_0_7, filter_4_11);
+
+      store_u8_8x4(d, dst_stride, d0, d1, d2, d3);
+
+      // Prepare block for next iteration - re-using as much as possible.
+      // Shuffle everything up four rows.
+      s0123_lo = s4567_lo;
+      s0123_hi = s4567_hi;
+      s1234_lo = s5678_lo;
+      s1234_hi = s5678_hi;
+      s2345_lo = s6789_lo;
+      s2345_hi = s6789_hi;
+      s3456_lo = s789A_lo;
+      s3456_hi = s789A_hi;
+      s4567_lo = s89AB_lo;
+      s4567_hi = s89AB_hi;
+      s5678_lo = s9ABC_lo;
+      s5678_hi = s9ABC_hi;
+      s6789_lo = sABCD_lo;
+      s6789_hi = sABCD_hi;
+      s789A_lo = sBCDE_lo;
+      s789A_hi = sBCDE_hi;
+
+      s += 4 * src_stride;
+      d += 4 * dst_stride;
+      height -= 4;
+    } while (height != 0);
+    src += 8;
+    dst += 8;
+    w -= 8;
+  } while (w != 0);
+}
+
+static INLINE void vpx_convolve12_2d_horiz_neon_i8mm(
+    const uint8_t *src, ptrdiff_t src_stride, uint8_t *dst,
+    ptrdiff_t dst_stride, const InterpKernel12 *filter, int x0_q4, int w,
+    int h) {
+  assert(w == 32 || w == 16 || w == 8);
+  assert(h % 4 == 3);
+
+  // Split 12-tap filter into two 6-tap filters, masking the top two elements.
+  // { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0 }
+  const int8x8_t mask = vcreate_s8(0x0000ffffffffffff);
+  const int8x8_t filter_0 = vand_s8(vmovn_s16(vld1q_s16(filter[x0_q4])), mask);
+  const int8x8_t filter_1 =
+      vext_s8(vmovn_s16(vld1q_s16(filter[x0_q4] + 4)), vdup_n_s8(0), 2);
+
+  // Stagger each 6-tap filter to enable use of matrix multiply instructions.
+  // { f0, f1, f2, f3, f4, f5,  0,  0,  0, f0, f1, f2, f3, f4, f5,  0 }
+  const int8x16_t x_filter[2] = {
+    vcombine_s8(filter_0, vext_s8(filter_0, filter_0, 7)),
+    vcombine_s8(filter_1, vext_s8(filter_1, filter_1, 7))
+  };
+
+  const uint8x16x2_t permute_tbl = vld1q_u8_x2(kMatMulPermuteTbl);
+
+  src -= MAX_FILTER_TAP / 2 - 1;
+
+  do {
+    const uint8_t *s = src;
+    uint8_t *d = dst;
+    int width = w;
+
+    do {
+      uint8x16_t s0[2], s1[2], s2[2], s3[2];
+      load_u8_16x4(s, src_stride, &s0[0], &s1[0], &s2[0], &s3[0]);
+      load_u8_16x4(s + 6, src_stride, &s0[1], &s1[1], &s2[1], &s3[1]);
+
+      uint8x8_t d0 = convolve12_8_h(s0, x_filter, permute_tbl);
+      uint8x8_t d1 = convolve12_8_h(s1, x_filter, permute_tbl);
+      uint8x8_t d2 = convolve12_8_h(s2, x_filter, permute_tbl);
+      uint8x8_t d3 = convolve12_8_h(s3, x_filter, permute_tbl);
+
+      store_u8_8x4(d, dst_stride, d0, d1, d2, d3);
+
+      s += 8;
+      d += 8;
+      width -= 8;
+    } while (width != 0);
+    src += 4 * src_stride;
+    dst += 4 * dst_stride;
+    h -= 4;
+  } while (h != 3);
+
+  do {
+    uint8x16_t s0[2], s1[2], s2[2];
+    load_u8_16x3(src, src_stride, &s0[0], &s1[0], &s2[0]);
+    load_u8_16x3(src + 6, src_stride, &s0[1], &s1[1], &s2[1]);
+
+    uint8x8_t d0 = convolve12_8_h(s0, x_filter, permute_tbl);
+    uint8x8_t d1 = convolve12_8_h(s1, x_filter, permute_tbl);
+    uint8x8_t d2 = convolve12_8_h(s2, x_filter, permute_tbl);
+
+    store_u8_8x3(dst, dst_stride, d0, d1, d2);
+
+    src += 8;
+    dst += 8;
+    w -= 8;
+  } while (w != 0);
+}
+
+void vpx_convolve12_neon_i8mm(const uint8_t *src, ptrdiff_t src_stride,
+                              uint8_t *dst, ptrdiff_t dst_stride,
+                              const InterpKernel12 *filter, int x0_q4,
+                              int x_step_q4, int y0_q4, int y_step_q4, int w,
+                              int h) {
+  // Scaling not supported by Neon implementation.
+  if (x_step_q4 != 16 || y_step_q4 != 16) {
+    vpx_convolve12_c(src, src_stride, dst, dst_stride, filter, x0_q4, x_step_q4,
+                     y0_q4, y_step_q4, w, h);
+    return;
+  }
+
+  assert(w == 32 || w == 16 || w == 8);
+  assert(h == 32 || h == 16 || h == 8);
+
+  DECLARE_ALIGNED(32, uint8_t, im_block[BW * (BH + MAX_FILTER_TAP)]);
+
+  const int im_stride = BW;
+  // Account for the vertical pass needing MAX_FILTER_TAP / 2 - 1 lines prior
+  // and MAX_FILTER_TAP / 2 lines post.
+  const int im_height = h + MAX_FILTER_TAP - 1;
+  const ptrdiff_t border_offset = MAX_FILTER_TAP / 2 - 1;
+
+  // Filter starting border_offset rows up.
+  vpx_convolve12_2d_horiz_neon_i8mm(src - src_stride * border_offset,
+                                    src_stride, im_block, im_stride, filter,
+                                    x0_q4, w, im_height);
+
+  vpx_convolve12_vert_neon_i8mm(im_block + im_stride * border_offset, im_stride,
+                                dst, dst_stride, filter, x0_q4, x_step_q4,
+                                y0_q4, y_step_q4, w, h);
+}
diff --git a/vp9/encoder/vp9_aq_cyclicrefresh.c b/vp9/encoder/vp9_aq_cyclicrefresh.c
index 109ba0c..92a31eb 100644
--- a/vp9/encoder/vp9_aq_cyclicrefresh.c
+++ b/vp9/encoder/vp9_aq_cyclicrefresh.c
@@ -603,14 +603,16 @@
       (cpi->use_svc && cpi->svc.high_source_sad_superframe);
   if (cm->current_video_frame == 0) cr->low_content_avg = 0.0;
   // Reset if resoluton change has occurred.
-  if (cpi->resize_pending != 0) vp9_cyclic_refresh_reset_resize(cpi);
+  if (cpi->resize_pending != 0 && cpi->svc.temporal_layer_id == 0)
+    vp9_cyclic_refresh_reset_resize(cpi);
   if (!cr->apply_cyclic_refresh || (cpi->force_update_segmentation) ||
       scene_change_detected) {
     // Set segmentation map to 0 and disable.
     unsigned char *const seg_map = cpi->segmentation_map;
     memset(seg_map, 0, cm->mi_rows * cm->mi_cols);
     vp9_disable_segmentation(&cm->seg);
-    if (cm->frame_type == KEY_FRAME || scene_change_detected) {
+    if ((cm->frame_type == KEY_FRAME || scene_change_detected) &&
+        cpi->svc.temporal_layer_id == 0) {
       memset(cr->last_coded_q_map, MAXQ,
              cm->mi_rows * cm->mi_cols * sizeof(*cr->last_coded_q_map));
       cr->sb_index = 0;
diff --git a/vp9/encoder/vp9_bitstream.c b/vp9/encoder/vp9_bitstream.c
index be34271..0be2b1f 100644
--- a/vp9/encoder/vp9_bitstream.c
+++ b/vp9/encoder/vp9_bitstream.c
@@ -1249,6 +1249,7 @@
     }
   } else {
     assert(cm->profile == PROFILE_1 || cm->profile == PROFILE_3);
+    assert(cm->subsampling_x == 0 && cm->subsampling_y == 0);
     vpx_wb_write_bit(wb, 0);  // unused
   }
 }
diff --git a/vp9/encoder/vp9_encodeframe.c b/vp9/encoder/vp9_encodeframe.c
index 937f22e..4c991e3 100644
--- a/vp9/encoder/vp9_encodeframe.c
+++ b/vp9/encoder/vp9_encodeframe.c
@@ -965,6 +965,10 @@
   return 0;
 }
 
+// Set the partition for mi_col/row_high (current resolution) based on
+// the previous spatial layer (mi_col/row). Returns 0 if partition is set,
+// returns 1 if no scale partitioning is done. Return 1 means the variance
+// partitioning will be used.
 static int scale_partitioning_svc(VP9_COMP *cpi, MACROBLOCK *x, MACROBLOCKD *xd,
                                   BLOCK_SIZE bsize, int mi_row, int mi_col,
                                   int mi_row_high, int mi_col_high) {
@@ -974,31 +978,29 @@
   // Variables with _high are for higher resolution.
   int bsize_high = 0;
   int subsize_high = 0;
-  const int bsl_high = b_width_log2_lookup[bsize];
-  const int bs_high = (1 << bsl_high) >> 2;
-  const int has_rows = (mi_row_high + bs_high) < cm->mi_rows;
-  const int has_cols = (mi_col_high + bs_high) < cm->mi_cols;
+  const int bsl = b_width_log2_lookup[bsize];
+  const int bs = (1 << bsl) >> 2;
+  const int has_rows = (mi_row_high + bs) < cm->mi_rows;
+  const int has_cols = (mi_col_high + bs) < cm->mi_cols;
 
-  const int row_boundary_block_scale_factor[BLOCK_SIZES] = { 13, 13, 13, 1, 0,
-                                                             1,  1,  0,  1, 1,
-                                                             0,  1,  0 };
-  const int col_boundary_block_scale_factor[BLOCK_SIZES] = { 13, 13, 13, 2, 2,
-                                                             0,  2,  2,  0, 2,
-                                                             2,  0,  0 };
   int start_pos;
   BLOCK_SIZE bsize_low;
   PARTITION_TYPE partition_high;
 
-  if (mi_row_high >= cm->mi_rows || mi_col_high >= cm->mi_cols) return 0;
+  // If the lower layer frame is outside the boundary (this can happen for
+  // odd size resolutions) then do not scale partitioning from the lower
+  // layer. Do variance based partitioning instead (return 1).
   if (mi_row >= svc->mi_rows[svc->spatial_layer_id - 1] ||
       mi_col >= svc->mi_cols[svc->spatial_layer_id - 1])
-    return 0;
+    return 1;
+
+  // Do not scale partitioning from lower layers on the boundary. Do
+  // variance based partitioning instead (return 1).
+  if (!has_rows || !has_cols) return 1;
 
   // Find corresponding (mi_col/mi_row) block down-scaled by 2x2.
   start_pos = mi_row * (svc->mi_stride[svc->spatial_layer_id - 1]) + mi_col;
   bsize_low = prev_part[start_pos];
-  // The block size is too big for boundaries. Do variance based partitioning.
-  if ((!has_rows || !has_cols) && bsize_low > BLOCK_16X16) return 1;
 
   // For reference frames: return 1 (do variance-based partitioning) if the
   // superblock is not low source sad and lower-resoln bsize is below 32x32.
@@ -1012,23 +1014,13 @@
   } else if (bsize_low >= BLOCK_32X32) {
     bsize_high = BLOCK_64X64;
   }
-  // Scale up blocks on boundary.
-  if (!has_cols && has_rows) {
-    bsize_high = bsize_low + row_boundary_block_scale_factor[bsize_low];
-  } else if (has_cols && !has_rows) {
-    bsize_high = bsize_low + col_boundary_block_scale_factor[bsize_low];
-  } else if (!has_cols && !has_rows) {
-    bsize_high = bsize_low;
-  }
 
-  partition_high = partition_lookup[bsl_high][bsize_high];
+  partition_high = partition_lookup[bsl][bsize_high];
   subsize_high = get_subsize(bsize, partition_high);
 
   if (subsize_high < BLOCK_8X8) {
     set_block_size(cpi, x, xd, mi_row_high, mi_col_high, bsize_high);
   } else {
-    const int bsl = b_width_log2_lookup[bsize];
-    const int bs = (1 << bsl) >> 2;
     switch (partition_high) {
       case PARTITION_NONE:
         set_block_size(cpi, x, xd, mi_row_high, mi_col_high, bsize_high);
@@ -1036,13 +1028,13 @@
       case PARTITION_HORZ:
         set_block_size(cpi, x, xd, mi_row_high, mi_col_high, subsize_high);
         if (subsize_high < BLOCK_64X64)
-          set_block_size(cpi, x, xd, mi_row_high + bs_high, mi_col_high,
+          set_block_size(cpi, x, xd, mi_row_high + bs, mi_col_high,
                          subsize_high);
         break;
       case PARTITION_VERT:
         set_block_size(cpi, x, xd, mi_row_high, mi_col_high, subsize_high);
         if (subsize_high < BLOCK_64X64)
-          set_block_size(cpi, x, xd, mi_row_high, mi_col_high + bs_high,
+          set_block_size(cpi, x, xd, mi_row_high, mi_col_high + bs,
                          subsize_high);
         break;
       default:
@@ -1051,15 +1043,15 @@
                                    mi_row_high, mi_col_high))
           return 1;
         if (scale_partitioning_svc(cpi, x, xd, subsize_high, mi_row + (bs >> 1),
-                                   mi_col, mi_row_high + bs_high, mi_col_high))
+                                   mi_col, mi_row_high + bs, mi_col_high))
           return 1;
         if (scale_partitioning_svc(cpi, x, xd, subsize_high, mi_row,
                                    mi_col + (bs >> 1), mi_row_high,
-                                   mi_col_high + bs_high))
+                                   mi_col_high + bs))
           return 1;
         if (scale_partitioning_svc(cpi, x, xd, subsize_high, mi_row + (bs >> 1),
-                                   mi_col + (bs >> 1), mi_row_high + bs_high,
-                                   mi_col_high + bs_high))
+                                   mi_col + (bs >> 1), mi_row_high + bs,
+                                   mi_col_high + bs))
           return 1;
         break;
     }
@@ -3741,6 +3733,17 @@
 
   if (cpi->twopass.gf_group.layer_depth[gf_group_index] > 1) return orig_rdmult;
 
+  if (cpi->ext_ratectrl.ready &&
+      (cpi->ext_ratectrl.funcs.rc_type & VPX_RC_QP) != 0 &&
+      cpi->ext_ratectrl.funcs.get_encodeframe_decision != NULL) {
+    int sb_size = num_8x8_blocks_wide_lookup[BLOCK_64X64] * MI_SIZE;
+    int sb_stride = (cpi->common.width + sb_size - 1) / sb_size;
+    int sby = mi_row / 8;
+    int sbx = mi_col / 8;
+    return (int)((cpi->sb_mul_scale[sby * sb_stride + sbx] * orig_rdmult) /
+                 256);
+  }
+
   for (row = mi_row; row < mi_row + mi_high; ++row) {
     for (col = mi_col; col < mi_col + mi_wide; ++col) {
       TplDepStats *this_stats = &tpl_stats[row * tpl_stride + col];
@@ -3759,225 +3762,13 @@
   beta = r0 / rk;
   dr = vp9_get_adaptive_rdmult(cpi, beta);
 
-  dr = VPXMIN(dr, orig_rdmult * 3 / 2);
-  dr = VPXMAX(dr, orig_rdmult * 1 / 2);
-
+  dr = clamp(dr, orig_rdmult * 1 / 2, orig_rdmult * 3 / 2);
   dr = VPXMAX(1, dr);
 
   return dr;
 }
 #endif  // !CONFIG_REALTIME_ONLY
 
-#if CONFIG_RATE_CTRL
-static void assign_partition_info(
-    const int row_start_4x4, const int col_start_4x4, const int block_width_4x4,
-    const int block_height_4x4, const int num_unit_rows,
-    const int num_unit_cols, PARTITION_INFO *partition_info) {
-  int i, j;
-  for (i = 0; i < block_height_4x4; ++i) {
-    for (j = 0; j < block_width_4x4; ++j) {
-      const int row_4x4 = row_start_4x4 + i;
-      const int col_4x4 = col_start_4x4 + j;
-      const int unit_index = row_4x4 * num_unit_cols + col_4x4;
-      if (row_4x4 >= num_unit_rows || col_4x4 >= num_unit_cols) continue;
-      partition_info[unit_index].row = row_4x4 << 2;
-      partition_info[unit_index].column = col_4x4 << 2;
-      partition_info[unit_index].row_start = row_start_4x4 << 2;
-      partition_info[unit_index].column_start = col_start_4x4 << 2;
-      partition_info[unit_index].width = block_width_4x4 << 2;
-      partition_info[unit_index].height = block_height_4x4 << 2;
-    }
-  }
-}
-
-static void assign_motion_vector_info(const int block_width_4x4,
-                                      const int block_height_4x4,
-                                      const int row_start_4x4,
-                                      const int col_start_4x4,
-                                      const int num_unit_rows,
-                                      const int num_unit_cols, MV *source_mv[2],
-                                      MV_REFERENCE_FRAME source_ref_frame[2],
-                                      MOTION_VECTOR_INFO *motion_vector_info) {
-  int i, j;
-  for (i = 0; i < block_height_4x4; ++i) {
-    for (j = 0; j < block_width_4x4; ++j) {
-      const int row_4x4 = row_start_4x4 + i;
-      const int col_4x4 = col_start_4x4 + j;
-      const int unit_index = row_4x4 * num_unit_cols + col_4x4;
-      if (row_4x4 >= num_unit_rows || col_4x4 >= num_unit_cols) continue;
-      if (source_ref_frame[1] == NO_REF_FRAME) {
-        assert(source_mv[1]->row == 0 && source_mv[1]->col == 0);
-      }
-      motion_vector_info[unit_index].ref_frame[0] = source_ref_frame[0];
-      motion_vector_info[unit_index].ref_frame[1] = source_ref_frame[1];
-      motion_vector_info[unit_index].mv[0].as_mv.row = source_mv[0]->row;
-      motion_vector_info[unit_index].mv[0].as_mv.col = source_mv[0]->col;
-      motion_vector_info[unit_index].mv[1].as_mv.row = source_mv[1]->row;
-      motion_vector_info[unit_index].mv[1].as_mv.col = source_mv[1]->col;
-    }
-  }
-}
-
-static void store_superblock_info(
-    const PC_TREE *const pc_tree, MODE_INFO **mi_grid_visible,
-    const int mi_stride, const int square_size_4x4, const int num_unit_rows,
-    const int num_unit_cols, const int row_start_4x4, const int col_start_4x4,
-    PARTITION_INFO *partition_info, MOTION_VECTOR_INFO *motion_vector_info) {
-  const int subblock_square_size_4x4 = square_size_4x4 >> 1;
-  if (row_start_4x4 >= num_unit_rows || col_start_4x4 >= num_unit_cols) return;
-  assert(pc_tree->partitioning != PARTITION_INVALID);
-  // End node, no split.
-  if (pc_tree->partitioning == PARTITION_NONE ||
-      pc_tree->partitioning == PARTITION_HORZ ||
-      pc_tree->partitioning == PARTITION_VERT || square_size_4x4 == 1) {
-    const int mi_row = row_start_4x4 >> 1;
-    const int mi_col = col_start_4x4 >> 1;
-    const int mi_idx = mi_stride * mi_row + mi_col;
-    MODE_INFO **mi = mi_grid_visible + mi_idx;
-    MV *source_mv[2];
-    MV_REFERENCE_FRAME source_ref_frame[2];
-
-    // partition info
-    const int block_width_4x4 = (pc_tree->partitioning == PARTITION_VERT)
-                                    ? square_size_4x4 >> 1
-                                    : square_size_4x4;
-    const int block_height_4x4 = (pc_tree->partitioning == PARTITION_HORZ)
-                                     ? square_size_4x4 >> 1
-                                     : square_size_4x4;
-    assign_partition_info(row_start_4x4, col_start_4x4, block_width_4x4,
-                          block_height_4x4, num_unit_rows, num_unit_cols,
-                          partition_info);
-    if (pc_tree->partitioning == PARTITION_VERT) {
-      assign_partition_info(row_start_4x4, col_start_4x4 + block_width_4x4,
-                            block_width_4x4, block_height_4x4, num_unit_rows,
-                            num_unit_cols, partition_info);
-    } else if (pc_tree->partitioning == PARTITION_HORZ) {
-      assign_partition_info(row_start_4x4 + block_height_4x4, col_start_4x4,
-                            block_width_4x4, block_height_4x4, num_unit_rows,
-                            num_unit_cols, partition_info);
-    }
-
-    // motion vector info
-    if (pc_tree->partitioning == PARTITION_HORZ) {
-      int is_valid_second_rectangle = 0;
-      assert(square_size_4x4 > 1);
-      // First rectangle.
-      source_ref_frame[0] = mi[0]->ref_frame[0];
-      source_ref_frame[1] = mi[0]->ref_frame[1];
-      source_mv[0] = &mi[0]->mv[0].as_mv;
-      source_mv[1] = &mi[0]->mv[1].as_mv;
-      assign_motion_vector_info(block_width_4x4, block_height_4x4,
-                                row_start_4x4, col_start_4x4, num_unit_rows,
-                                num_unit_cols, source_mv, source_ref_frame,
-                                motion_vector_info);
-      // Second rectangle.
-      if (square_size_4x4 == 2) {
-        is_valid_second_rectangle = 1;
-        source_ref_frame[0] = mi[0]->ref_frame[0];
-        source_ref_frame[1] = mi[0]->ref_frame[1];
-        source_mv[0] = &mi[0]->bmi[2].as_mv[0].as_mv;
-        source_mv[1] = &mi[0]->bmi[2].as_mv[1].as_mv;
-      } else {
-        const int mi_row_2 = mi_row + (block_height_4x4 >> 1);
-        const int mi_col_2 = mi_col;
-        if (mi_row_2 * 2 < num_unit_rows && mi_col_2 * 2 < num_unit_cols) {
-          const int mi_idx_2 = mi_stride * mi_row_2 + mi_col_2;
-          is_valid_second_rectangle = 1;
-          mi = mi_grid_visible + mi_idx_2;
-          source_ref_frame[0] = mi[0]->ref_frame[0];
-          source_ref_frame[1] = mi[0]->ref_frame[1];
-          source_mv[0] = &mi[0]->mv[0].as_mv;
-          source_mv[1] = &mi[0]->mv[1].as_mv;
-        }
-      }
-      if (is_valid_second_rectangle) {
-        assign_motion_vector_info(
-            block_width_4x4, block_height_4x4, row_start_4x4 + block_height_4x4,
-            col_start_4x4, num_unit_rows, num_unit_cols, source_mv,
-            source_ref_frame, motion_vector_info);
-      }
-    } else if (pc_tree->partitioning == PARTITION_VERT) {
-      int is_valid_second_rectangle = 0;
-      assert(square_size_4x4 > 1);
-      // First rectangle.
-      source_ref_frame[0] = mi[0]->ref_frame[0];
-      source_ref_frame[1] = mi[0]->ref_frame[1];
-      source_mv[0] = &mi[0]->mv[0].as_mv;
-      source_mv[1] = &mi[0]->mv[1].as_mv;
-      assign_motion_vector_info(block_width_4x4, block_height_4x4,
-                                row_start_4x4, col_start_4x4, num_unit_rows,
-                                num_unit_cols, source_mv, source_ref_frame,
-                                motion_vector_info);
-      // Second rectangle.
-      if (square_size_4x4 == 2) {
-        is_valid_second_rectangle = 1;
-        source_ref_frame[0] = mi[0]->ref_frame[0];
-        source_ref_frame[1] = mi[0]->ref_frame[1];
-        source_mv[0] = &mi[0]->bmi[1].as_mv[0].as_mv;
-        source_mv[1] = &mi[0]->bmi[1].as_mv[1].as_mv;
-      } else {
-        const int mi_row_2 = mi_row;
-        const int mi_col_2 = mi_col + (block_width_4x4 >> 1);
-        if (mi_row_2 * 2 < num_unit_rows && mi_col_2 * 2 < num_unit_cols) {
-          const int mi_idx_2 = mi_stride * mi_row_2 + mi_col_2;
-          is_valid_second_rectangle = 1;
-          mi = mi_grid_visible + mi_idx_2;
-          source_ref_frame[0] = mi[0]->ref_frame[0];
-          source_ref_frame[1] = mi[0]->ref_frame[1];
-          source_mv[0] = &mi[0]->mv[0].as_mv;
-          source_mv[1] = &mi[0]->mv[1].as_mv;
-        }
-      }
-      if (is_valid_second_rectangle) {
-        assign_motion_vector_info(
-            block_width_4x4, block_height_4x4, row_start_4x4,
-            col_start_4x4 + block_width_4x4, num_unit_rows, num_unit_cols,
-            source_mv, source_ref_frame, motion_vector_info);
-      }
-    } else {
-      assert(pc_tree->partitioning == PARTITION_NONE || square_size_4x4 == 1);
-      source_ref_frame[0] = mi[0]->ref_frame[0];
-      source_ref_frame[1] = mi[0]->ref_frame[1];
-      if (square_size_4x4 == 1) {
-        const int sub8x8_row = row_start_4x4 % 2;
-        const int sub8x8_col = col_start_4x4 % 2;
-        const int sub8x8_idx = sub8x8_row * 2 + sub8x8_col;
-        source_mv[0] = &mi[0]->bmi[sub8x8_idx].as_mv[0].as_mv;
-        source_mv[1] = &mi[0]->bmi[sub8x8_idx].as_mv[1].as_mv;
-      } else {
-        source_mv[0] = &mi[0]->mv[0].as_mv;
-        source_mv[1] = &mi[0]->mv[1].as_mv;
-      }
-      assign_motion_vector_info(block_width_4x4, block_height_4x4,
-                                row_start_4x4, col_start_4x4, num_unit_rows,
-                                num_unit_cols, source_mv, source_ref_frame,
-                                motion_vector_info);
-    }
-
-    return;
-  }
-  // recursively traverse partition tree when partition is split.
-  assert(pc_tree->partitioning == PARTITION_SPLIT);
-  store_superblock_info(pc_tree->u.split[0], mi_grid_visible, mi_stride,
-                        subblock_square_size_4x4, num_unit_rows, num_unit_cols,
-                        row_start_4x4, col_start_4x4, partition_info,
-                        motion_vector_info);
-  store_superblock_info(pc_tree->u.split[1], mi_grid_visible, mi_stride,
-                        subblock_square_size_4x4, num_unit_rows, num_unit_cols,
-                        row_start_4x4, col_start_4x4 + subblock_square_size_4x4,
-                        partition_info, motion_vector_info);
-  store_superblock_info(pc_tree->u.split[2], mi_grid_visible, mi_stride,
-                        subblock_square_size_4x4, num_unit_rows, num_unit_cols,
-                        row_start_4x4 + subblock_square_size_4x4, col_start_4x4,
-                        partition_info, motion_vector_info);
-  store_superblock_info(pc_tree->u.split[3], mi_grid_visible, mi_stride,
-                        subblock_square_size_4x4, num_unit_rows, num_unit_cols,
-                        row_start_4x4 + subblock_square_size_4x4,
-                        col_start_4x4 + subblock_square_size_4x4,
-                        partition_info, motion_vector_info);
-}
-#endif  // CONFIG_RATE_CTRL
-
 #if !CONFIG_REALTIME_ONLY
 // TODO(jingning,jimbankoski,rbultje): properly skip partition types that are
 // unlikely to be selected depending on previous rate-distortion optimization
@@ -4460,21 +4251,6 @@
 #if CONFIG_COLLECT_COMPONENT_TIMING
     end_timing(cpi, encode_sb_time);
 #endif
-#if CONFIG_RATE_CTRL
-    if (oxcf->use_simple_encode_api) {
-      // Store partition, motion vector of the superblock.
-      if (output_enabled) {
-        const int num_unit_rows =
-            get_num_unit_4x4(cpi->frame_info.frame_height);
-        const int num_unit_cols = get_num_unit_4x4(cpi->frame_info.frame_width);
-        store_superblock_info(pc_tree, cm->mi_grid_visible, cm->mi_stride,
-                              num_4x4_blocks_wide_lookup[BLOCK_64X64],
-                              num_unit_rows, num_unit_cols, mi_row << 1,
-                              mi_col << 1, cpi->partition_info,
-                              cpi->motion_vector_info);
-      }
-    }
-#endif  // CONFIG_RATE_CTRL
   }
 
   if (bsize == BLOCK_64X64) {
@@ -6176,8 +5952,10 @@
   if (cpi->oxcf.aq_mode == PERCEPTUAL_AQ) build_kmeans_segmentation(cpi);
 
   {
+#if CONFIG_INTERNAL_STATS
     struct vpx_usec_timer emr_timer;
     vpx_usec_timer_start(&emr_timer);
+#endif
 
     if (!cpi->row_mt) {
       cpi->row_mt_sync_read_ptr = vp9_row_mt_sync_read_dummy;
@@ -6194,8 +5972,10 @@
       vp9_encode_tiles_row_mt(cpi);
     }
 
+#if CONFIG_INTERNAL_STATS
     vpx_usec_timer_mark(&emr_timer);
     cpi->time_encode_sb_row += vpx_usec_timer_elapsed(&emr_timer);
+#endif
   }
 
   sf->skip_encode_frame =
diff --git a/vp9/encoder/vp9_encoder.c b/vp9/encoder/vp9_encoder.c
index 59179fa..9b22ecb 100644
--- a/vp9/encoder/vp9_encoder.c
+++ b/vp9/encoder/vp9_encoder.c
@@ -78,6 +78,7 @@
 #include "vp9/encoder/vp9_multi_thread.h"
 #include "vp9/encoder/vp9_noise_estimate.h"
 #include "vp9/encoder/vp9_picklpf.h"
+#include "vp9/encoder/vp9_quantize.h"
 #include "vp9/encoder/vp9_ratectrl.h"
 #include "vp9/encoder/vp9_rd.h"
 #include "vp9/encoder/vp9_resize.h"
@@ -1021,18 +1022,12 @@
   vpx_free(cpi->mb_wiener_variance);
   cpi->mb_wiener_variance = NULL;
 
+  vpx_free(cpi->sb_mul_scale);
+  cpi->sb_mul_scale = NULL;
+
   vpx_free(cpi->mi_ssim_rdmult_scaling_factors);
   cpi->mi_ssim_rdmult_scaling_factors = NULL;
 
-#if CONFIG_RATE_CTRL
-  if (cpi->oxcf.use_simple_encode_api) {
-    free_partition_info(cpi);
-    free_motion_vector_info(cpi);
-    free_fp_motion_vector_info(cpi);
-    free_tpl_stats_info(cpi);
-  }
-#endif
-
   vp9_free_ref_frame_buffers(cm->buffer_pool);
 #if CONFIG_VP9_POSTPROC
   vp9_free_postproc_buffers(cm);
@@ -2065,6 +2060,24 @@
   cpi->copied_frame_cnt = NULL;
 }
 
+#if CONFIG_VP9_TEMPORAL_DENOISING
+static void setup_denoiser_buffer(VP9_COMP *cpi) {
+  VP9_COMMON *const cm = &cpi->common;
+  if (cpi->oxcf.noise_sensitivity > 0 &&
+      !cpi->denoiser.frame_buffer_initialized) {
+    if (vp9_denoiser_alloc(cm, &cpi->svc, &cpi->denoiser, cpi->use_svc,
+                           cpi->oxcf.noise_sensitivity, cm->width, cm->height,
+                           cm->subsampling_x, cm->subsampling_y,
+#if CONFIG_VP9_HIGHBITDEPTH
+                           cm->use_highbitdepth,
+#endif
+                           VP9_ENC_BORDER_IN_PIXELS))
+      vpx_internal_error(&cm->error, VPX_CODEC_MEM_ERROR,
+                         "Failed to allocate denoiser");
+  }
+}
+#endif
+
 void vp9_change_config(struct VP9_COMP *cpi, const VP9EncoderConfig *oxcf) {
   VP9_COMMON *const cm = &cpi->common;
   RATE_CONTROL *const rc = &cpi->rc;
@@ -2172,9 +2185,46 @@
         &cm->error, cpi->skin_map,
         vpx_calloc(cm->mi_rows * cm->mi_cols, sizeof(*cpi->skin_map)));
 
+    if (cpi->svc.number_spatial_layers > 1) {
+#if CONFIG_VP9_TEMPORAL_DENOISING
+      // Reset the denoiser for svc on the resize change.
+      if (cpi->oxcf.noise_sensitivity > 0) {
+        vp9_denoiser_free(&cpi->denoiser);
+        setup_denoiser_buffer(cpi);
+      }
+#endif
+      if (cpi->oxcf.aq_mode == CYCLIC_REFRESH_AQ) {
+        for (int sl = 0; sl < cpi->svc.number_spatial_layers; ++sl) {
+          const int layer =
+              LAYER_IDS_TO_IDX(sl, 0, cpi->svc.number_temporal_layers);
+          LAYER_CONTEXT *const lc = &cpi->svc.layer_context[layer];
+          lc->sb_index = 0;
+          lc->actual_num_seg1_blocks = 0;
+          lc->actual_num_seg2_blocks = 0;
+          lc->counter_encode_maxq_scene_change = 0;
+          vpx_free(lc->map);
+          CHECK_MEM_ERROR(
+              &cm->error, lc->map,
+              vpx_calloc(cm->mi_rows * cm->mi_cols, sizeof(*lc->map)));
+          vpx_free(lc->last_coded_q_map);
+          CHECK_MEM_ERROR(&cm->error, lc->last_coded_q_map,
+                          vpx_malloc(cm->mi_rows * cm->mi_cols *
+                                     sizeof(*lc->last_coded_q_map)));
+          memset(lc->last_coded_q_map, MAXQ, cm->mi_rows * cm->mi_cols);
+          vpx_free(lc->consec_zero_mv);
+          CHECK_MEM_ERROR(&cm->error, lc->consec_zero_mv,
+                          vpx_calloc(cm->mi_rows * cm->mi_cols,
+                                     sizeof(*lc->consec_zero_mv)));
+        }
+        cpi->refresh_golden_frame = 1;
+        cpi->refresh_alt_ref_frame = 1;
+      }
+    }
+
     free_copy_partition_data(cpi);
     alloc_copy_partition_data(cpi);
-    if (cpi->oxcf.aq_mode == CYCLIC_REFRESH_AQ)
+    if (cpi->oxcf.aq_mode == CYCLIC_REFRESH_AQ &&
+        cpi->svc.number_spatial_layers == 1)
       vp9_cyclic_refresh_reset_resize(cpi);
     rc->rc_1_frame = 0;
     rc->rc_2_frame = 0;
@@ -2306,8 +2356,17 @@
     cm->use_highbitdepth = use_highbitdepth;
 #endif
     alloc_util_frame_buffers(cpi);
-    cpi->initial_width = cm->width;
-    cpi->initial_height = cm->height;
+    // The initial_width/height is used to clamp the encoding width/height in
+    // vp9_set_size_literal(). The check below is added to avoid setting the
+    // initial_width/height to a smaller resolution than the one configured.
+    // This can happen when the user passes in a lower resolution on the very
+    // first frame (after creating the encoder with a larger resolution). For
+    // spatial layers this will prevent user from going back up in resolution
+    // (i.e., the top layer will get stuck at the lower resolution).
+    if (cm->width > cpi->initial_width || cm->height > cpi->initial_height) {
+      cpi->initial_width = cm->width;
+      cpi->initial_height = cm->height;
+    }
     cpi->initial_mbs = cm->MBs;
   }
 }
@@ -2342,24 +2401,6 @@
   return fmt & VPX_IMG_FMT_HIGHBITDEPTH;
 }
 
-#if CONFIG_VP9_TEMPORAL_DENOISING
-static void setup_denoiser_buffer(VP9_COMP *cpi) {
-  VP9_COMMON *const cm = &cpi->common;
-  if (cpi->oxcf.noise_sensitivity > 0 &&
-      !cpi->denoiser.frame_buffer_initialized) {
-    if (vp9_denoiser_alloc(cm, &cpi->svc, &cpi->denoiser, cpi->use_svc,
-                           cpi->oxcf.noise_sensitivity, cm->width, cm->height,
-                           cm->subsampling_x, cm->subsampling_y,
-#if CONFIG_VP9_HIGHBITDEPTH
-                           cm->use_highbitdepth,
-#endif
-                           VP9_ENC_BORDER_IN_PIXELS))
-      vpx_internal_error(&cm->error, VPX_CODEC_MEM_ERROR,
-                         "Failed to allocate denoiser");
-  }
-}
-#endif
-
 void vp9_update_compressor_with_img_fmt(VP9_COMP *cpi, vpx_img_fmt_t img_fmt) {
   const VP9EncoderConfig *oxcf = &cpi->oxcf;
   unsigned int subsampling_x, subsampling_y;
@@ -2427,6 +2468,8 @@
   vp9_init_rd_parameters(cpi);
 
   init_frame_indexes(cm);
+  cpi->initial_width = cpi->oxcf.width;
+  cpi->initial_height = cpi->oxcf.height;
   cpi->tile_data = NULL;
 
   realloc_segmentation_maps(cpi);
@@ -2753,16 +2796,6 @@
 
   cm->error.setjmp = 0;
 
-#if CONFIG_RATE_CTRL
-  encode_command_init(&cpi->encode_command);
-  if (oxcf->use_simple_encode_api) {
-    partition_info_init(cpi);
-    motion_vector_info_init(cpi);
-    fp_motion_vector_info_init(cpi);
-    tpl_stats_info_init(cpi);
-  }
-#endif
-
   return cpi;
 }
 
@@ -3448,11 +3481,15 @@
     lf->filter_level = 0;
     lf->last_filt_level = 0;
   } else {
+#if CONFIG_INTERNAL_STATS
     struct vpx_usec_timer timer;
+#endif
 
     vpx_clear_system_state();
 
+#if CONFIG_INTERNAL_STATS
     vpx_usec_timer_start(&timer);
+#endif
 
     if (!cpi->rc.is_src_frame_alt_ref) {
       if ((cpi->common.frame_type == KEY_FRAME) &&
@@ -3465,8 +3502,10 @@
       lf->filter_level = 0;
     }
 
+#if CONFIG_INTERNAL_STATS
     vpx_usec_timer_mark(&timer);
     cpi->time_pick_lpf += vpx_usec_timer_elapsed(&timer);
+#endif
   }
 
   if (lf->filter_level > 0 && is_reference_frame) {
@@ -4213,7 +4252,7 @@
                                  cpi->oxcf.rc_mode == VPX_CBR &&
                                  cm->frame_type != KEY_FRAME;
 
-  vp9_set_quantizer(cpi, q);
+  vp9_set_quantizer(cpi, q, 0);
   vp9_set_variance_partition_thresholds(cpi, q, 0);
 
   setup_frame(cpi);
@@ -4242,7 +4281,7 @@
       (cpi->rc.high_source_sad ||
        (cpi->use_svc && svc->high_source_sad_superframe))) {
     if (vp9_encodedframe_overshoot(cpi, -1, &q)) {
-      vp9_set_quantizer(cpi, q);
+      vp9_set_quantizer(cpi, q, 0);
       vp9_set_variance_partition_thresholds(cpi, q, 0);
     }
   }
@@ -4303,7 +4342,7 @@
     // adjust some rate control parameters, and return to re-encode the frame.
     if (vp9_encodedframe_overshoot(cpi, frame_size, &q)) {
       vpx_clear_system_state();
-      vp9_set_quantizer(cpi, q);
+      vp9_set_quantizer(cpi, q, 0);
       vp9_set_variance_partition_thresholds(cpi, q, 0);
       suppress_active_map(cpi);
       // Turn-off cyclic refresh for re-encoded frame.
@@ -4362,150 +4401,8 @@
   return VPXMIN(qstep, MAX_QSTEP_ADJ);
 }
 
-#if CONFIG_RATE_CTRL
-static void init_rq_history(RATE_QINDEX_HISTORY *rq_history) {
-  rq_history->recode_count = 0;
-  rq_history->q_index_high = 255;
-  rq_history->q_index_low = 0;
-}
-
-static void update_rq_history(RATE_QINDEX_HISTORY *rq_history, int target_bits,
-                              int actual_bits, int q_index) {
-  rq_history->q_index_history[rq_history->recode_count] = q_index;
-  rq_history->rate_history[rq_history->recode_count] = actual_bits;
-  if (actual_bits <= target_bits) {
-    rq_history->q_index_high = q_index;
-  }
-  if (actual_bits >= target_bits) {
-    rq_history->q_index_low = q_index;
-  }
-  rq_history->recode_count += 1;
-}
-
-static int guess_q_index_from_model(const RATE_QSTEP_MODEL *rq_model,
-                                    int target_bits) {
-  // The model predicts bits as follows.
-  // target_bits = bias - ratio * log2(q_step)
-  // Given the target_bits, we compute the q_step as follows.
-  double q_step;
-  assert(rq_model->ratio > 0);
-  q_step = pow(2.0, (rq_model->bias - target_bits) / rq_model->ratio);
-  // TODO(angiebird): Make this function support highbitdepth.
-  return vp9_convert_q_to_qindex(q_step, VPX_BITS_8);
-}
-
-static int guess_q_index_linear(int prev_q_index, int target_bits,
-                                int actual_bits, int gap) {
-  int q_index = prev_q_index;
-  if (actual_bits < target_bits) {
-    q_index -= gap;
-    q_index = VPXMAX(q_index, 0);
-  } else {
-    q_index += gap;
-    q_index = VPXMIN(q_index, 255);
-  }
-  return q_index;
-}
-
-static double get_bits_percent_diff(int target_bits, int actual_bits) {
-  double diff;
-  target_bits = VPXMAX(target_bits, 1);
-  diff = abs(target_bits - actual_bits) * 1. / target_bits;
-  return diff * 100;
-}
-
-static int rq_model_predict_q_index(const RATE_QSTEP_MODEL *rq_model,
-                                    const RATE_QINDEX_HISTORY *rq_history,
-                                    int target_bits) {
-  int q_index = 128;
-  if (rq_history->recode_count > 0) {
-    const int actual_bits =
-        rq_history->rate_history[rq_history->recode_count - 1];
-    const int prev_q_index =
-        rq_history->q_index_history[rq_history->recode_count - 1];
-    const double percent_diff = get_bits_percent_diff(target_bits, actual_bits);
-    if (percent_diff > 50) {
-      // Binary search.
-      // When the actual_bits and target_bits are far apart, binary search
-      // q_index is faster.
-      q_index = (rq_history->q_index_low + rq_history->q_index_high) / 2;
-    } else {
-      if (rq_model->ready) {
-        q_index = guess_q_index_from_model(rq_model, target_bits);
-      } else {
-        // TODO(angiebird): Find a better way to set the gap.
-        q_index =
-            guess_q_index_linear(prev_q_index, target_bits, actual_bits, 20);
-      }
-    }
-  } else {
-    if (rq_model->ready) {
-      q_index = guess_q_index_from_model(rq_model, target_bits);
-    }
-  }
-
-  assert(rq_history->q_index_low <= rq_history->q_index_high);
-  if (q_index <= rq_history->q_index_low) {
-    q_index = rq_history->q_index_low + 1;
-  }
-  if (q_index >= rq_history->q_index_high) {
-    q_index = rq_history->q_index_high - 1;
-  }
-  return q_index;
-}
-
-static void rq_model_update(const RATE_QINDEX_HISTORY *rq_history,
-                            int target_bits, RATE_QSTEP_MODEL *rq_model) {
-  const int recode_count = rq_history->recode_count;
-  const double delta = 0.00001;
-  if (recode_count >= 2) {
-    const int q_index1 = rq_history->q_index_history[recode_count - 2];
-    const int q_index2 = rq_history->q_index_history[recode_count - 1];
-    const int r1 = rq_history->rate_history[recode_count - 2];
-    const int r2 = rq_history->rate_history[recode_count - 1];
-    int valid = 0;
-    // lower q_index should yield higher bit rate
-    if (q_index1 < q_index2) {
-      valid = r1 > r2;
-    } else if (q_index1 > q_index2) {
-      valid = r1 < r2;
-    }
-    // Only update the model when the q_index and rate behave normally.
-    if (valid) {
-      // Fit the ratio and bias of rq_model based on last two recode histories.
-      const double s1 = vp9_convert_qindex_to_q(q_index1, VPX_BITS_8);
-      const double s2 = vp9_convert_qindex_to_q(q_index2, VPX_BITS_8);
-      if (fabs(log2(s1) - log2(s2)) > delta) {
-        rq_model->ratio = (r2 - r1) / (log2(s1) - log2(s2));
-        rq_model->bias = r1 + (rq_model->ratio) * log2(s1);
-        if (rq_model->ratio > delta && rq_model->bias > delta) {
-          rq_model->ready = 1;
-        }
-      }
-    }
-  } else if (recode_count == 1) {
-    if (rq_model->ready) {
-      // Update the ratio only when the initial model exists and we only have
-      // one recode history.
-      const int prev_q = rq_history->q_index_history[recode_count - 1];
-      const double prev_q_step = vp9_convert_qindex_to_q(prev_q, VPX_BITS_8);
-      if (fabs(log2(prev_q_step)) > delta) {
-        const int actual_bits = rq_history->rate_history[recode_count - 1];
-        rq_model->ratio =
-            rq_model->ratio + (target_bits - actual_bits) / log2(prev_q_step);
-      }
-    }
-  }
-}
-#endif  // CONFIG_RATE_CTRL
-
 static void encode_with_recode_loop(VP9_COMP *cpi, size_t *size, uint8_t *dest,
-                                    size_t dest_size
-#if CONFIG_RATE_CTRL
-                                    ,
-                                    RATE_QINDEX_HISTORY *rq_history
-#endif  // CONFIG_RATE_CTRL
-) {
+                                    size_t dest_size) {
   const VP9EncoderConfig *const oxcf = &cpi->oxcf;
   VP9_COMMON *const cm = &cpi->common;
   RATE_CONTROL *const rc = &cpi->rc;
@@ -4525,17 +4422,6 @@
 
   const int orig_rc_max_frame_bandwidth = rc->max_frame_bandwidth;
 
-#if CONFIG_RATE_CTRL
-  RATE_QSTEP_MODEL *rq_model;
-  {
-    const FRAME_UPDATE_TYPE update_type =
-        cpi->twopass.gf_group.update_type[cpi->twopass.gf_group.index];
-    const ENCODE_FRAME_TYPE frame_type = get_encode_frame_type(update_type);
-    rq_model = &cpi->rq_model[frame_type];
-  }
-  init_rq_history(rq_history);
-#endif  // CONFIG_RATE_CTRL
-
   if (cm->show_existing_frame) {
     rc->this_frame_target = 0;
     if (is_psnr_calc_enabled(cpi)) set_raw_source_frame(cpi);
@@ -4624,36 +4510,38 @@
       vp9_scale_references(cpi);
     }
 
-#if CONFIG_RATE_CTRL
-    // TODO(angiebird): This is a hack for making sure the encoder use the
-    // external_quantize_index exactly. Avoid this kind of hack later.
-    if (cpi->oxcf.use_simple_encode_api) {
-      if (cpi->encode_command.use_external_target_frame_bits) {
-        q = rq_model_predict_q_index(rq_model, rq_history,
-                                     rc->this_frame_target);
-      }
-      if (cpi->encode_command.use_external_quantize_index) {
-        q = cpi->encode_command.external_quantize_index;
-      }
-    }
-#endif  // CONFIG_RATE_CTRL
     const GF_GROUP *gf_group = &cpi->twopass.gf_group;
+    int ext_rc_delta_q_uv = 0;
     if (cpi->ext_ratectrl.ready &&
         (cpi->ext_ratectrl.funcs.rc_type & VPX_RC_QP) != 0 &&
         cpi->ext_ratectrl.funcs.get_encodeframe_decision != NULL) {
       vpx_codec_err_t codec_status;
       vpx_rc_encodeframe_decision_t encode_frame_decision;
+      int sb_size = num_8x8_blocks_wide_lookup[BLOCK_64X64] * MI_SIZE;
+      int frame_height_sb = (cm->height + sb_size - 1) / sb_size;
+      int frame_width_sb = (cm->width + sb_size - 1) / sb_size;
+      CHECK_MEM_ERROR(&cm->error, encode_frame_decision.sb_params_list,
+                      (sb_params *)vpx_calloc(
+                          frame_height_sb * frame_width_sb,
+                          sizeof(*encode_frame_decision.sb_params_list)));
       codec_status = vp9_extrc_get_encodeframe_decision(
           &cpi->ext_ratectrl, gf_group->index, &encode_frame_decision);
       if (codec_status != VPX_CODEC_OK) {
         vpx_internal_error(&cm->error, codec_status,
                            "vp9_extrc_get_encodeframe_decision() failed");
       }
+      for (int idx = 0; idx < frame_height_sb * frame_width_sb; ++idx) {
+        cpi->sb_mul_scale[idx] =
+            (((int64_t)encode_frame_decision.sb_params_list[idx].rdmult * 256) /
+             (encode_frame_decision.rdmult + 1));
+      }
+      vpx_free(encode_frame_decision.sb_params_list);
       // If the external model recommends a reserved value, we use
       // libvpx's default q.
       if (encode_frame_decision.q_index != VPX_DEFAULT_Q) {
         q = encode_frame_decision.q_index;
       }
+      ext_rc_delta_q_uv = encode_frame_decision.delta_q_uv;
     }
 
     if (cpi->ext_ratectrl.ready && cpi->ext_ratectrl.log_file) {
@@ -4662,7 +4550,7 @@
               gf_group->index, gf_group->update_type[gf_group->index], q);
     }
 
-    vp9_set_quantizer(cpi, q);
+    vp9_set_quantizer(cpi, q, ext_rc_delta_q_uv);
 
     if (loop_count == 0) setup_frame(cpi);
 
@@ -4705,39 +4593,6 @@
         (cpi->ext_ratectrl.funcs.rc_type & VPX_RC_QP) != 0) {
       break;
     }
-#if CONFIG_RATE_CTRL
-    if (cpi->oxcf.use_simple_encode_api) {
-      // This part needs to be after save_coding_context() because
-      // restore_coding_context will be called in the end of this function.
-      // TODO(angiebird): This is a hack for making sure the encoder use the
-      // external_quantize_index exactly. Avoid this kind of hack later.
-      if (cpi->encode_command.use_external_quantize_index) {
-        break;
-      }
-
-      if (cpi->encode_command.use_external_target_frame_bits) {
-        const double percent_diff = get_bits_percent_diff(
-            rc->this_frame_target, rc->projected_frame_size);
-        update_rq_history(rq_history, rc->this_frame_target,
-                          rc->projected_frame_size, q);
-        loop_count += 1;
-
-        rq_model_update(rq_history, rc->this_frame_target, rq_model);
-
-        // Check if we hit the target bitrate.
-        if (percent_diff <=
-                cpi->encode_command.target_frame_bits_error_percent ||
-            rq_history->recode_count >= RATE_CTRL_MAX_RECODE_NUM ||
-            rq_history->q_index_low >= rq_history->q_index_high) {
-          break;
-        }
-
-        loop = 1;
-        restore_coding_context(cpi);
-        continue;
-      }
-    }
-#endif  // CONFIG_RATE_CTRL
 
     if (oxcf->rc_mode == VPX_Q) {
       loop = 0;
@@ -5184,8 +5039,7 @@
   // Base the filter strength on the current active max Q.
   const int q = (int)(vp9_convert_qindex_to_q(twopass->active_worst_quality,
                                               cm->bit_depth));
-  int strength =
-      VPXMAX(oxcf->arnr_strength >> 2, VPXMIN(oxcf->arnr_strength, (q >> 4)));
+  int strength = clamp(q >> 4, oxcf->arnr_strength >> 2, oxcf->arnr_strength);
 
   // Denoise each of Y,U and V buffers.
   spatial_denoise_buffer(cpi, src->y_buffer, src->y_stride, src->y_width,
@@ -5332,6 +5186,23 @@
   cpi->mb_wiener_var_cols = cm->mb_cols;
 }
 
+static void init_sb_mul_scale_buffer(VP9_COMP *cpi) {
+  VP9_COMMON *cm = &cpi->common;
+
+  if (cpi->mb_wiener_var_rows >= cm->mb_rows &&
+      cpi->mb_wiener_var_cols >= cm->mb_cols)
+    return;
+
+  vpx_free(cpi->sb_mul_scale);
+  cpi->sb_mul_scale = NULL;
+
+  CHECK_MEM_ERROR(
+      &cm->error, cpi->sb_mul_scale,
+      vpx_calloc(cm->mb_rows * cm->mb_cols, sizeof(*cpi->sb_mul_scale)));
+  cpi->mb_wiener_var_rows = cm->mb_rows;
+  cpi->mb_wiener_var_cols = cm->mb_cols;
+}
+
 static void set_mb_wiener_variance(VP9_COMP *cpi) {
   VP9_COMMON *cm = &cpi->common;
   uint8_t *buffer = cpi->Source->y_buffer;
@@ -5451,69 +5322,6 @@
   encode_frame_result->update_type = update_type;
   encode_frame_result->quantize_index = quantize_index;
 }
-
-#if CONFIG_RATE_CTRL
-static void yv12_buffer_to_image_buffer(const YV12_BUFFER_CONFIG *yv12_buffer,
-                                        IMAGE_BUFFER *image_buffer) {
-  const uint8_t *src_buf_ls[3] = { yv12_buffer->y_buffer, yv12_buffer->u_buffer,
-                                   yv12_buffer->v_buffer };
-  const int src_stride_ls[3] = { yv12_buffer->y_stride, yv12_buffer->uv_stride,
-                                 yv12_buffer->uv_stride };
-  const int w_ls[3] = { yv12_buffer->y_crop_width, yv12_buffer->uv_crop_width,
-                        yv12_buffer->uv_crop_width };
-  const int h_ls[3] = { yv12_buffer->y_crop_height, yv12_buffer->uv_crop_height,
-                        yv12_buffer->uv_crop_height };
-  int plane;
-  for (plane = 0; plane < 3; ++plane) {
-    const int src_stride = src_stride_ls[plane];
-    const int w = w_ls[plane];
-    const int h = h_ls[plane];
-    const uint8_t *src_buf = src_buf_ls[plane];
-    uint8_t *dst_buf = image_buffer->plane_buffer[plane];
-    int r;
-    assert(image_buffer->plane_width[plane] == w);
-    assert(image_buffer->plane_height[plane] == h);
-    for (r = 0; r < h; ++r) {
-      memcpy(dst_buf, src_buf, sizeof(*src_buf) * w);
-      src_buf += src_stride;
-      dst_buf += w;
-    }
-  }
-}
-
-// This function will update extra information specific for simple_encode APIs
-static void update_encode_frame_result_simple_encode(
-    int ref_frame_flags, FRAME_UPDATE_TYPE update_type,
-    const YV12_BUFFER_CONFIG *source_frame, const RefCntBuffer *coded_frame_buf,
-    RefCntBuffer *ref_frame_bufs[MAX_INTER_REF_FRAMES], int quantize_index,
-    uint32_t bit_depth, uint32_t input_bit_depth, const FRAME_COUNTS *counts,
-    const PARTITION_INFO *partition_info,
-    const MOTION_VECTOR_INFO *motion_vector_info,
-    const TplDepStats *tpl_stats_info,
-    ENCODE_FRAME_RESULT *encode_frame_result) {
-  PSNR_STATS psnr;
-  update_encode_frame_result_basic(update_type, coded_frame_buf->frame_index,
-                                   quantize_index, encode_frame_result);
-  compute_psnr_stats(source_frame, &coded_frame_buf->buf, bit_depth,
-                     input_bit_depth);
-  encode_frame_result->frame_coding_index = coded_frame_buf->frame_coding_index;
-
-  vp9_get_ref_frame_info(update_type, ref_frame_flags, ref_frame_bufs,
-                         encode_frame_result->ref_frame_coding_indexes,
-                         encode_frame_result->ref_frame_valid_list);
-
-  encode_frame_result->psnr = psnr.psnr[0];
-  encode_frame_result->sse = psnr.sse[0];
-  encode_frame_result->frame_counts = *counts;
-  encode_frame_result->partition_info = partition_info;
-  encode_frame_result->motion_vector_info = motion_vector_info;
-  encode_frame_result->tpl_stats_info = tpl_stats_info;
-  if (encode_frame_result->coded_frame.allocated) {
-    yv12_buffer_to_image_buffer(&coded_frame_buf->buf,
-                                &encode_frame_result->coded_frame);
-  }
-}
-#endif  // CONFIG_RATE_CTRL
 #endif  // !CONFIG_REALTIME_ONLY
 
 static void encode_frame_to_data_rate(
@@ -5588,6 +5396,8 @@
     set_mb_wiener_variance(cpi);
   }
 
+  init_sb_mul_scale_buffer(cpi);
+
   vpx_clear_system_state();
 
 #if CONFIG_INTERNAL_STATS
@@ -5628,10 +5438,6 @@
     if (!encode_without_recode_loop(cpi, size, dest, dest_size)) return;
   } else {
 #if !CONFIG_REALTIME_ONLY
-#if CONFIG_RATE_CTRL
-    encode_with_recode_loop(cpi, size, dest, dest_size,
-                            &encode_frame_result->rq_history);
-#else  // CONFIG_RATE_CTRL
 #if CONFIG_COLLECT_COMPONENT_TIMING
     start_timing(cpi, encode_with_recode_loop_time);
 #endif
@@ -5639,7 +5445,6 @@
 #if CONFIG_COLLECT_COMPONENT_TIMING
     end_timing(cpi, encode_with_recode_loop_time);
 #endif
-#endif  // CONFIG_RATE_CTRL
 #endif  // !CONFIG_REALTIME_ONLY
   }
 
@@ -5729,7 +5534,7 @@
 #if CONFIG_REALTIME_ONLY
   (void)encode_frame_result;
   assert(encode_frame_result == NULL);
-#else  // CONFIG_REALTIME_ONLY
+#else   // CONFIG_REALTIME_ONLY
   if (encode_frame_result != NULL) {
     const RefCntBuffer *coded_frame_buf =
         get_ref_cnt_buffer(cm, cm->new_fb_idx);
@@ -5765,19 +5570,6 @@
               "ENCODE_FRAME_RESULT gop_index %d psnr %f bits %zu\n",
               cpi->twopass.gf_group.index, psnr.psnr[0], (*size) << 3);
     }
-
-#if CONFIG_RATE_CTRL
-    if (cpi->oxcf.use_simple_encode_api) {
-      const int ref_frame_flags = get_ref_frame_flags(cpi);
-      update_encode_frame_result_simple_encode(
-          ref_frame_flags,
-          cpi->twopass.gf_group.update_type[cpi->twopass.gf_group.index],
-          cpi->Source, coded_frame_buf, ref_frame_bufs, quantize_index,
-          cm->bit_depth, cpi->oxcf.input_bit_depth, cpi->td.counts,
-          cpi->partition_info, cpi->motion_vector_info, cpi->tpl_stats_info,
-          encode_frame_result);
-    }
-#endif  // CONFIG_RATE_CTRL
   }
 #endif  // CONFIG_REALTIME_ONLY
 
@@ -5943,7 +5735,9 @@
                           YV12_BUFFER_CONFIG *sd, int64_t time_stamp,
                           int64_t end_time) {
   VP9_COMMON *const cm = &cpi->common;
+#if CONFIG_INTERNAL_STATS
   struct vpx_usec_timer timer;
+#endif
   int res = 0;
   const int subsampling_x = sd->subsampling_x;
   const int subsampling_y = sd->subsampling_y;
@@ -5960,13 +5754,17 @@
 
   alloc_raw_frame_buffers(cpi);
 
+#if CONFIG_INTERNAL_STATS
   vpx_usec_timer_start(&timer);
+#endif
 
   if (vp9_lookahead_push(cpi->lookahead, sd, time_stamp, end_time,
                          use_highbitdepth, frame_flags))
     res = -1;
+#if CONFIG_INTERNAL_STATS
   vpx_usec_timer_mark(&timer);
   cpi->time_receive_data += vpx_usec_timer_elapsed(&timer);
+#endif
 
   if ((cm->profile == PROFILE_0 || cm->profile == PROFILE_2) &&
       (subsampling_x != 1 || subsampling_y != 1)) {
@@ -5980,7 +5778,18 @@
                        "4:2:0 color format requires profile 0 or 2");
     res = -1;
   }
-
+  if (cm->color_space == VPX_CS_SRGB) {
+    if (cm->profile == PROFILE_0 || cm->profile == PROFILE_2) {
+      vpx_internal_error(&cm->error, VPX_CODEC_INVALID_PARAM,
+                         "SRGB color space requires profile 1 or 3");
+      res = -1;
+    }
+    if (subsampling_x != 0 || subsampling_y != 0) {
+      vpx_internal_error(&cm->error, VPX_CODEC_INVALID_PARAM,
+                         "SRGB color space requires 4:4:4");
+      res = -1;
+    }
+  }
   return res;
 }
 
@@ -6339,12 +6148,6 @@
 
 void vp9_init_encode_frame_result(ENCODE_FRAME_RESULT *encode_frame_result) {
   encode_frame_result->show_idx = -1;  // Actual encoding doesn't happen.
-#if CONFIG_RATE_CTRL
-  encode_frame_result->frame_coding_index = -1;
-  vp9_zero(encode_frame_result->coded_frame);
-  encode_frame_result->coded_frame.allocated = 0;
-  init_rq_history(&encode_frame_result->rq_history);
-#endif  // CONFIG_RATE_CTRL
 }
 
 // Returns if TPL stats need to be calculated.
@@ -6374,7 +6177,9 @@
   VP9_COMMON *const cm = &cpi->common;
   BufferPool *const pool = cm->buffer_pool;
   RATE_CONTROL *const rc = &cpi->rc;
+#if CONFIG_INTERNAL_STATS
   struct vpx_usec_timer cmptimer;
+#endif
   YV12_BUFFER_CONFIG *force_src_buffer = NULL;
   struct lookahead_entry *last_source = NULL;
   struct lookahead_entry *source = NULL;
@@ -6390,7 +6195,9 @@
     vp9_one_pass_svc_start_layer(cpi);
   }
 
+#if CONFIG_INTERNAL_STATS
   vpx_usec_timer_start(&cmptimer);
+#endif
 
   vp9_set_high_precision_mv(cpi, ALTREF_HIGH_PRECISION_MV);
 
@@ -6731,8 +6538,10 @@
   if (cpi->svc.spatial_layer_id == cpi->svc.number_spatial_layers - 1)
     cpi->fixed_qp_onepass = 0;
 
+#if CONFIG_INTERNAL_STATS
   vpx_usec_timer_mark(&cmptimer);
   cpi->time_compress_data += vpx_usec_timer_elapsed(&cmptimer);
+#endif
 
   if (cpi->keep_level_stats && oxcf->pass != 1)
     update_level_info(cpi, size, arf_src_index);
diff --git a/vp9/encoder/vp9_encoder.h b/vp9/encoder/vp9_encoder.h
index 8afea3a..f7ae7d7 100644
--- a/vp9/encoder/vp9_encoder.h
+++ b/vp9/encoder/vp9_encoder.h
@@ -285,7 +285,6 @@
   int row_mt;
   unsigned int motion_vector_unit_test;
   int delta_q_uv;
-  int use_simple_encode_api;  // Use SimpleEncode APIs or not
 } VP9EncoderConfig;
 
 static INLINE int is_lossless_requested(const VP9EncoderConfig *cfg) {
@@ -525,143 +524,6 @@
   int group_idx;
 } KMEANS_DATA;
 
-#if CONFIG_RATE_CTRL
-typedef struct PARTITION_INFO {
-  int row;           // row pixel offset of current 4x4 block
-  int column;        // column pixel offset of current 4x4 block
-  int row_start;     // row pixel offset of the start of the prediction block
-  int column_start;  // column pixel offset of the start of the prediction block
-  int width;         // prediction block width
-  int height;        // prediction block height
-} PARTITION_INFO;
-
-typedef struct MOTION_VECTOR_INFO {
-  MV_REFERENCE_FRAME ref_frame[2];
-  int_mv mv[2];
-} MOTION_VECTOR_INFO;
-
-typedef struct GOP_COMMAND {
-  int use;  // use this command to set gop or not. If not, use vp9's decision.
-  int show_frame_count;
-  int use_alt_ref;
-} GOP_COMMAND;
-
-static INLINE void gop_command_on(GOP_COMMAND *gop_command,
-                                  int show_frame_count, int use_alt_ref) {
-  gop_command->use = 1;
-  gop_command->show_frame_count = show_frame_count;
-  gop_command->use_alt_ref = use_alt_ref;
-}
-
-static INLINE void gop_command_off(GOP_COMMAND *gop_command) {
-  gop_command->use = 0;
-  gop_command->show_frame_count = 0;
-  gop_command->use_alt_ref = 0;
-}
-
-static INLINE int gop_command_coding_frame_count(
-    const GOP_COMMAND *gop_command) {
-  if (gop_command->use == 0) {
-    assert(0);
-    return -1;
-  }
-  return gop_command->show_frame_count + gop_command->use_alt_ref;
-}
-
-// TODO(angiebird): See if we can merge this one with FrameType in
-// simple_encode.h
-typedef enum ENCODE_FRAME_TYPE {
-  ENCODE_FRAME_TYPE_KEY,
-  ENCODE_FRAME_TYPE_INTER,
-  ENCODE_FRAME_TYPE_ALTREF,
-  ENCODE_FRAME_TYPE_OVERLAY,
-  ENCODE_FRAME_TYPE_GOLDEN,
-  ENCODE_FRAME_TYPES,
-} ENCODE_FRAME_TYPE;
-
-// TODO(angiebird): Merge this function with get_frame_type_from_update_type()
-static INLINE ENCODE_FRAME_TYPE
-get_encode_frame_type(FRAME_UPDATE_TYPE update_type) {
-  switch (update_type) {
-    case KF_UPDATE: return ENCODE_FRAME_TYPE_KEY;
-    case ARF_UPDATE: return ENCODE_FRAME_TYPE_ALTREF;
-    case GF_UPDATE: return ENCODE_FRAME_TYPE_GOLDEN;
-    case OVERLAY_UPDATE: return ENCODE_FRAME_TYPE_OVERLAY;
-    case LF_UPDATE: return ENCODE_FRAME_TYPE_INTER;
-    default:
-      fprintf(stderr, "Unsupported update_type %d\n", update_type);
-      abort();
-      return ENCODE_FRAME_TYPE_INTER;
-  }
-}
-
-typedef struct RATE_QSTEP_MODEL {
-  // The rq model predicts the bit usage as follows.
-  // rate = bias - ratio * log2(q_step)
-  int ready;
-  double bias;
-  double ratio;
-} RATE_QSTEP_MODEL;
-
-typedef struct ENCODE_COMMAND {
-  int use_external_quantize_index;
-  int external_quantize_index;
-
-  int use_external_target_frame_bits;
-  int target_frame_bits;
-  double target_frame_bits_error_percent;
-
-  GOP_COMMAND gop_command;
-} ENCODE_COMMAND;
-
-static INLINE void encode_command_set_gop_command(
-    ENCODE_COMMAND *encode_command, GOP_COMMAND gop_command) {
-  encode_command->gop_command = gop_command;
-}
-
-static INLINE void encode_command_set_external_quantize_index(
-    ENCODE_COMMAND *encode_command, int quantize_index) {
-  encode_command->use_external_quantize_index = 1;
-  encode_command->external_quantize_index = quantize_index;
-}
-
-static INLINE void encode_command_reset_external_quantize_index(
-    ENCODE_COMMAND *encode_command) {
-  encode_command->use_external_quantize_index = 0;
-  encode_command->external_quantize_index = -1;
-}
-
-static INLINE void encode_command_set_target_frame_bits(
-    ENCODE_COMMAND *encode_command, int target_frame_bits,
-    double target_frame_bits_error_percent) {
-  encode_command->use_external_target_frame_bits = 1;
-  encode_command->target_frame_bits = target_frame_bits;
-  encode_command->target_frame_bits_error_percent =
-      target_frame_bits_error_percent;
-}
-
-static INLINE void encode_command_reset_target_frame_bits(
-    ENCODE_COMMAND *encode_command) {
-  encode_command->use_external_target_frame_bits = 0;
-  encode_command->target_frame_bits = -1;
-  encode_command->target_frame_bits_error_percent = 0;
-}
-
-static INLINE void encode_command_init(ENCODE_COMMAND *encode_command) {
-  vp9_zero(*encode_command);
-  encode_command_reset_external_quantize_index(encode_command);
-  encode_command_reset_target_frame_bits(encode_command);
-  gop_command_off(&encode_command->gop_command);
-}
-
-// Returns number of units in size of 4, if not multiple not a multiple of 4,
-// round it up. For example, size is 7, return 2.
-static INLINE int get_num_unit_4x4(int size) { return (size + 3) >> 2; }
-// Returns number of units in size of 16, if not multiple not a multiple of 16,
-// round it up. For example, size is 17, return 2.
-static INLINE int get_num_unit_16x16(int size) { return (size + 15) >> 4; }
-#endif  // CONFIG_RATE_CTRL
-
 #if CONFIG_COLLECT_COMPONENT_TIMING
 #include "vpx_ports/vpx_timer.h"
 // Adjust the following to add new components.
@@ -803,6 +665,8 @@
   int mb_wiener_var_cols;
   double *mi_ssim_rdmult_scaling_factors;
 
+  int64_t *sb_mul_scale;
+
   YV12_BUFFER_CONFIG last_frame_uf;
 
   TOKENEXTRA *tile_tok[4][1 << 6];
@@ -865,10 +729,12 @@
   struct scale_factors me_sf;
   vp9_diamond_search_fn_t diamond_search_sad;
   vp9_variance_fn_ptr_t fn_ptr[BLOCK_SIZES];
+#if CONFIG_INTERNAL_STATS
   uint64_t time_receive_data;
   uint64_t time_compress_data;
   uint64_t time_pick_lpf;
   uint64_t time_encode_sb_row;
+#endif
 
   TWO_PASS twopass;
 
@@ -1035,15 +901,6 @@
   vpx_roi_map_t roi;
 
   LOOPFILTER_CONTROL loopfilter_ctrl;
-#if CONFIG_RATE_CTRL
-  ENCODE_COMMAND encode_command;
-  PARTITION_INFO *partition_info;
-  MOTION_VECTOR_INFO *motion_vector_info;
-  MOTION_VECTOR_INFO *fp_motion_vector_info;
-  TplDepStats *tpl_stats_info;
-
-  RATE_QSTEP_MODEL rq_model[ENCODE_FRAME_TYPES];
-#endif
   EXT_RATECTRL ext_ratectrl;
 
   int fixed_qp_onepass;
@@ -1072,139 +929,9 @@
 #endif
 } VP9_COMP;
 
-#if CONFIG_RATE_CTRL
-// Allocates memory for the partition information.
-// The unit size is each 4x4 block.
-// Only called once in vp9_create_compressor().
-static INLINE void partition_info_init(struct VP9_COMP *cpi) {
-  VP9_COMMON *const cm = &cpi->common;
-  const int unit_width = get_num_unit_4x4(cpi->frame_info.frame_width);
-  const int unit_height = get_num_unit_4x4(cpi->frame_info.frame_height);
-  CHECK_MEM_ERROR(&cm->error, cpi->partition_info,
-                  (PARTITION_INFO *)vpx_calloc(unit_width * unit_height,
-                                               sizeof(PARTITION_INFO)));
-  memset(cpi->partition_info, 0,
-         unit_width * unit_height * sizeof(PARTITION_INFO));
-}
-
-// Frees memory of the partition information.
-// Only called once in dealloc_compressor_data().
-static INLINE void free_partition_info(struct VP9_COMP *cpi) {
-  vpx_free(cpi->partition_info);
-  cpi->partition_info = NULL;
-}
-
-static INLINE void reset_mv_info(MOTION_VECTOR_INFO *mv_info) {
-  mv_info->ref_frame[0] = NO_REF_FRAME;
-  mv_info->ref_frame[1] = NO_REF_FRAME;
-  mv_info->mv[0].as_int = INVALID_MV;
-  mv_info->mv[1].as_int = INVALID_MV;
-}
-
-// Allocates memory for the motion vector information.
-// The unit size is each 4x4 block.
-// Only called once in vp9_create_compressor().
-static INLINE void motion_vector_info_init(struct VP9_COMP *cpi) {
-  VP9_COMMON *const cm = &cpi->common;
-  const int unit_width = get_num_unit_4x4(cpi->frame_info.frame_width);
-  const int unit_height = get_num_unit_4x4(cpi->frame_info.frame_height);
-  CHECK_MEM_ERROR(&cm->error, cpi->motion_vector_info,
-                  (MOTION_VECTOR_INFO *)vpx_calloc(unit_width * unit_height,
-                                                   sizeof(MOTION_VECTOR_INFO)));
-  memset(cpi->motion_vector_info, 0,
-         unit_width * unit_height * sizeof(MOTION_VECTOR_INFO));
-}
-
-// Frees memory of the motion vector information.
-// Only called once in dealloc_compressor_data().
-static INLINE void free_motion_vector_info(struct VP9_COMP *cpi) {
-  vpx_free(cpi->motion_vector_info);
-  cpi->motion_vector_info = NULL;
-}
-
-// Allocates memory for the tpl stats information.
-// Only called once in vp9_create_compressor().
-static INLINE void tpl_stats_info_init(struct VP9_COMP *cpi) {
-  VP9_COMMON *const cm = &cpi->common;
-  CHECK_MEM_ERROR(
-      &cm->error, cpi->tpl_stats_info,
-      (TplDepStats *)vpx_calloc(MAX_LAG_BUFFERS, sizeof(TplDepStats)));
-  memset(cpi->tpl_stats_info, 0, MAX_LAG_BUFFERS * sizeof(TplDepStats));
-}
-
-// Frees memory of the tpl stats information.
-// Only called once in dealloc_compressor_data().
-static INLINE void free_tpl_stats_info(struct VP9_COMP *cpi) {
-  vpx_free(cpi->tpl_stats_info);
-  cpi->tpl_stats_info = NULL;
-}
-
-// Allocates memory for the first pass motion vector information.
-// The unit size is each 16x16 block.
-// Only called once in vp9_create_compressor().
-static INLINE void fp_motion_vector_info_init(struct VP9_COMP *cpi) {
-  VP9_COMMON *const cm = &cpi->common;
-  const int unit_width = get_num_unit_16x16(cpi->frame_info.frame_width);
-  const int unit_height = get_num_unit_16x16(cpi->frame_info.frame_height);
-  CHECK_MEM_ERROR(&cm->error, cpi->fp_motion_vector_info,
-                  (MOTION_VECTOR_INFO *)vpx_calloc(unit_width * unit_height,
-                                                   sizeof(MOTION_VECTOR_INFO)));
-}
-
-static INLINE void fp_motion_vector_info_reset(
-    int frame_width, int frame_height,
-    MOTION_VECTOR_INFO *fp_motion_vector_info) {
-  const int unit_width = get_num_unit_16x16(frame_width);
-  const int unit_height = get_num_unit_16x16(frame_height);
-  int i;
-  for (i = 0; i < unit_width * unit_height; ++i) {
-    reset_mv_info(fp_motion_vector_info + i);
-  }
-}
-
-// Frees memory of the first pass motion vector information.
-// Only called once in dealloc_compressor_data().
-static INLINE void free_fp_motion_vector_info(struct VP9_COMP *cpi) {
-  vpx_free(cpi->fp_motion_vector_info);
-  cpi->fp_motion_vector_info = NULL;
-}
-
-// This is the c-version counter part of ImageBuffer
-typedef struct IMAGE_BUFFER {
-  int allocated;
-  int plane_width[3];
-  int plane_height[3];
-  uint8_t *plane_buffer[3];
-} IMAGE_BUFFER;
-
-#define RATE_CTRL_MAX_RECODE_NUM 7
-
-typedef struct RATE_QINDEX_HISTORY {
-  int recode_count;
-  int q_index_history[RATE_CTRL_MAX_RECODE_NUM];
-  int rate_history[RATE_CTRL_MAX_RECODE_NUM];
-  int q_index_high;
-  int q_index_low;
-} RATE_QINDEX_HISTORY;
-
-#endif  // CONFIG_RATE_CTRL
-
 typedef struct ENCODE_FRAME_RESULT {
   int show_idx;
   FRAME_UPDATE_TYPE update_type;
-#if CONFIG_RATE_CTRL
-  int frame_coding_index;
-  int ref_frame_coding_indexes[MAX_INTER_REF_FRAMES];
-  int ref_frame_valid_list[MAX_INTER_REF_FRAMES];
-  double psnr;
-  uint64_t sse;
-  FRAME_COUNTS frame_counts;
-  const PARTITION_INFO *partition_info;
-  const MOTION_VECTOR_INFO *motion_vector_info;
-  const TplDepStats *tpl_stats_info;
-  IMAGE_BUFFER coded_frame;
-  RATE_QINDEX_HISTORY rq_history;
-#endif  // CONFIG_RATE_CTRL
   int quantize_index;
 } ENCODE_FRAME_RESULT;
 
diff --git a/vp9/encoder/vp9_ethread.c b/vp9/encoder/vp9_ethread.c
index c3b7950..efe4725 100644
--- a/vp9/encoder/vp9_ethread.c
+++ b/vp9/encoder/vp9_ethread.c
@@ -278,13 +278,13 @@
   tile_data->fp_data.sum_in_vectors += tile_data_t->fp_data.sum_in_vectors;
   tile_data->fp_data.intra_smooth_count +=
       tile_data_t->fp_data.intra_smooth_count;
+  const int min_start_row = VPXMIN(tile_data->fp_data.image_data_start_row,
+                                   tile_data_t->fp_data.image_data_start_row);
   tile_data->fp_data.image_data_start_row =
-      VPXMIN(tile_data->fp_data.image_data_start_row,
-             tile_data_t->fp_data.image_data_start_row) == INVALID_ROW
+      (min_start_row == INVALID_ROW)
           ? VPXMAX(tile_data->fp_data.image_data_start_row,
                    tile_data_t->fp_data.image_data_start_row)
-          : VPXMIN(tile_data->fp_data.image_data_start_row,
-                   tile_data_t->fp_data.image_data_start_row);
+          : min_start_row;
 }
 #endif  // !CONFIG_REALTIME_ONLY
 
diff --git a/vp9/encoder/vp9_firstpass.c b/vp9/encoder/vp9_firstpass.c
index a41eca9..32ddacf 100644
--- a/vp9/encoder/vp9_firstpass.c
+++ b/vp9/encoder/vp9_firstpass.c
@@ -863,31 +863,15 @@
   this_tile->fp_data.sum_mvcs += fp_acc_data->sum_mvcs;
   this_tile->fp_data.sum_in_vectors += fp_acc_data->sum_in_vectors;
   this_tile->fp_data.intra_smooth_count += fp_acc_data->intra_smooth_count;
+  const int min_start_row = VPXMIN(this_tile->fp_data.image_data_start_row,
+                                   fp_acc_data->image_data_start_row);
   this_tile->fp_data.image_data_start_row =
-      VPXMIN(this_tile->fp_data.image_data_start_row,
-             fp_acc_data->image_data_start_row) == INVALID_ROW
+      (min_start_row == INVALID_ROW)
           ? VPXMAX(this_tile->fp_data.image_data_start_row,
                    fp_acc_data->image_data_start_row)
-          : VPXMIN(this_tile->fp_data.image_data_start_row,
-                   fp_acc_data->image_data_start_row);
+          : min_start_row;
 }
 
-#if CONFIG_RATE_CTRL
-static void store_fp_motion_vector(VP9_COMP *cpi, const MV *mv,
-                                   const int mb_row, const int mb_col,
-                                   MV_REFERENCE_FRAME frame_type,
-                                   const int mv_idx) {
-  VP9_COMMON *const cm = &cpi->common;
-  const int mb_index = mb_row * cm->mb_cols + mb_col;
-  MOTION_VECTOR_INFO *this_motion_vector_info =
-      &cpi->fp_motion_vector_info[mb_index];
-  this_motion_vector_info->ref_frame[mv_idx] = frame_type;
-  if (frame_type != INTRA_FRAME) {
-    this_motion_vector_info->mv[mv_idx].as_mv = *mv;
-  }
-}
-#endif  // CONFIG_RATE_CTRL
-
 #define NZ_MOTION_PENALTY 128
 #define INTRA_MODE_PENALTY 1024
 void vp9_first_pass_encode_tile_mb_row(VP9_COMP *cpi, ThreadData *td,
@@ -1118,13 +1102,6 @@
       struct buf_2d unscaled_last_source_buf_2d;
       vp9_variance_fn_ptr_t v_fn_ptr = cpi->fn_ptr[bsize];
 
-#if CONFIG_RATE_CTRL
-      if (cpi->oxcf.use_simple_encode_api) {
-        // Store zero mv as default
-        store_fp_motion_vector(cpi, &mv, mb_row, mb_col, LAST_FRAME, 0);
-      }
-#endif  // CONFIG_RAGE_CTRL
-
       xd->plane[0].pre[0].buf = first_ref_buf->y_buffer + recon_yoffset;
 #if CONFIG_VP9_HIGHBITDEPTH
       if (xd->cur_buf->flags & YV12_FLAG_HIGHBITDEPTH) {
@@ -1189,11 +1166,6 @@
                 vp9_get_mvpred_var(x, &tmp_mv, &zero_mv, &v_fn_ptr, 0);
           }
         }
-#if CONFIG_RATE_CTRL
-        if (cpi->oxcf.use_simple_encode_api) {
-          store_fp_motion_vector(cpi, &mv, mb_row, mb_col, LAST_FRAME, 0);
-        }
-#endif  // CONFIG_RAGE_CTRL
 
         // Search in an older reference frame.
         if ((cm->current_video_frame > 1) && gld_yv12 != NULL) {
@@ -1215,12 +1187,6 @@
 #endif  // CONFIG_VP9_HIGHBITDEPTH
 
           first_pass_motion_search(cpi, x, &zero_mv, &tmp_mv, &gf_motion_error);
-#if CONFIG_RATE_CTRL
-          if (cpi->oxcf.use_simple_encode_api) {
-            store_fp_motion_vector(cpi, &tmp_mv, mb_row, mb_col, GOLDEN_FRAME,
-                                   1);
-          }
-#endif  // CONFIG_RAGE_CTRL
 
           if (gf_motion_error < motion_error && gf_motion_error < this_error)
             ++(fp_acc_data->second_ref_count);
@@ -1346,11 +1312,6 @@
       }
     } else {
       fp_acc_data->sr_coded_error += (int64_t)this_error;
-#if CONFIG_RATE_CTRL
-      if (cpi->oxcf.use_simple_encode_api) {
-        store_fp_motion_vector(cpi, NULL, mb_row, mb_col, INTRA_FRAME, 0);
-      }
-#endif  // CONFIG_RAGE_CTRL
     }
     fp_acc_data->coded_error += (int64_t)this_error;
 
@@ -1380,13 +1341,6 @@
   // Tiling is ignored in the first pass.
   vp9_tile_init(tile, cm, 0, 0);
   tile_data.firstpass_top_mv = zero_mv;
-#if CONFIG_RATE_CTRL
-  if (cpi->oxcf.use_simple_encode_api) {
-    fp_motion_vector_info_reset(cpi->frame_info.frame_width,
-                                cpi->frame_info.frame_height,
-                                cpi->fp_motion_vector_info);
-  }
-#endif
 
   for (mb_row = 0; mb_row < cm->mb_rows; ++mb_row) {
     best_ref_mv = zero_mv;
@@ -1420,7 +1374,7 @@
   assert(frame_is_intra_only(cm) || (lst_yv12 != NULL));
 
   set_first_pass_params(cpi);
-  vp9_set_quantizer(cpi, find_fp_qindex(cm->bit_depth));
+  vp9_set_quantizer(cpi, find_fp_qindex(cm->bit_depth), 0);
 
   vp9_setup_block_planes(&x->e_mbd, cm->subsampling_x, cm->subsampling_y);
 
@@ -1582,9 +1536,9 @@
   last_group_rate_err =
       (double)twopass->rolling_arf_group_actual_bits /
       DOUBLE_DIVIDE_CHECK((double)twopass->rolling_arf_group_target_bits);
-  last_group_rate_err = VPXMAX(0.25, VPXMIN(4.0, last_group_rate_err));
+  last_group_rate_err = fclamp(last_group_rate_err, 0.25, 4.0);
   twopass->bpm_factor *= (3.0 + last_group_rate_err) / 4.0;
-  twopass->bpm_factor = VPXMAX(0.25, VPXMIN(4.0, twopass->bpm_factor));
+  twopass->bpm_factor = fclamp(twopass->bpm_factor, 0.25, 4.0);
 #endif
 
   if (target_rate <= 0) {
@@ -1608,9 +1562,9 @@
     last_group_rate_err =
         (double)twopass->rolling_arf_group_actual_bits /
         DOUBLE_DIVIDE_CHECK((double)twopass->rolling_arf_group_target_bits);
-    last_group_rate_err = VPXMAX(0.25, VPXMIN(4.0, last_group_rate_err));
+    last_group_rate_err = fclamp(last_group_rate_err, 0.25, 4.0);
     twopass->bpm_factor *= (3.0 + last_group_rate_err) / 4.0;
-    twopass->bpm_factor = VPXMAX(0.25, VPXMIN(4.0, twopass->bpm_factor));
+    twopass->bpm_factor = fclamp(twopass->bpm_factor, 0.25, 4.0);
 #endif
 
     // Try and pick a max Q that will be high enough to encode the
@@ -2626,9 +2580,6 @@
  *   rc->frames_since_key
  *   rc->source_alt_ref_active
  *
- * Special case: if CONFIG_RATE_CTRL is true, the external arf indexes will
- * determine the arf position.
- *
  * TODO(angiebird): Separate the dynamic fields and static fields into two
  * structs.
  */
@@ -2746,21 +2697,6 @@
   return gop_coding_frames;
 }
 
-static RANGE get_active_gf_inverval_range_simple(int min_gf_interval,
-                                                 int arf_active_or_kf,
-                                                 int frames_to_key) {
-  RANGE active_gf_interval;
-  active_gf_interval.min = min_gf_interval + arf_active_or_kf + 2;
-  active_gf_interval.max = 16 + arf_active_or_kf;
-
-  if ((active_gf_interval.max <= frames_to_key) &&
-      (active_gf_interval.max >= (frames_to_key - min_gf_interval))) {
-    active_gf_interval.min = frames_to_key / 2;
-    active_gf_interval.max = frames_to_key / 2;
-  }
-  return active_gf_interval;
-}
-
 static RANGE get_active_gf_inverval_range(
     const FRAME_INFO *frame_info, const RATE_CONTROL *rc, int arf_active_or_kf,
     int gf_start_show_idx, int active_worst_quality, int last_boosted_qindex) {
@@ -2883,14 +2819,9 @@
 
   vpx_clear_system_state();
 
-  if (oxcf->use_simple_encode_api) {
-    active_gf_interval = get_active_gf_inverval_range_simple(
-        rc->min_gf_interval, arf_active_or_kf, rc->frames_to_key);
-  } else {
-    active_gf_interval = get_active_gf_inverval_range(
-        frame_info, rc, arf_active_or_kf, gf_start_show_idx,
-        twopass->active_worst_quality, rc->last_boosted_qindex);
-  }
+  active_gf_interval = get_active_gf_inverval_range(
+      frame_info, rc, arf_active_or_kf, gf_start_show_idx,
+      twopass->active_worst_quality, rc->last_boosted_qindex);
 
   if (cpi->multi_layer_arf) {
     int arf_layers = get_arf_layers(cpi->multi_layer_arf, oxcf->enable_auto_arf,
@@ -2911,19 +2842,6 @@
     use_alt_ref = gop_decision.use_alt_ref;
   }
 
-#if CONFIG_RATE_CTRL
-  // If the external gop_command is on, we will override the decisions
-  // of gop_coding_frames and use_alt_ref.
-  if (cpi->oxcf.use_simple_encode_api) {
-    const GOP_COMMAND *gop_command = &cpi->encode_command.gop_command;
-    assert(allow_alt_ref == 1);
-    if (gop_command->use) {
-      gop_coding_frames = gop_command_coding_frame_count(gop_command);
-      use_alt_ref = gop_command->use_alt_ref;
-    }
-  }
-#endif
-
   // Was the group length constrained by the requirement for a new KF?
   rc->constrained_gf_group = (gop_coding_frames >= rc->frames_to_key) ? 1 : 0;
 
@@ -3015,11 +2933,6 @@
       int show_idx = gf_start_show_idx + j;
       const FIRSTPASS_STATS *frame_stats =
           fps_get_frame_stats(first_pass_info, show_idx);
-      // TODO(b/345831640): Why do we set gop_coding_frames as the upperbound of
-      // the for loop here? gop_coding_frames does not reflect the "show frame
-      // count" in a GOP. Therefore, it's possible to get a NULL pointer from
-      // fps_get_frame_stats(). Here we mitigate the issue using break whenever
-      // frame_stats == NULL. Show we set the upperbound to show frame count?
       if (frame_stats == NULL) {
         if (cpi->ext_ratectrl.ready &&
             (cpi->ext_ratectrl.funcs.rc_type & VPX_RC_GOP) != 0 &&
@@ -3031,9 +2944,7 @@
           // computing gf_group_err which will be used to compute gf_group_bits
           // for libvpx internal rate control. Since ext_ratectrl is using
           // external rate control module, this part becomes non-critical.
-          // Hence, we can safely turn off this error reporting. In the future,
-          // we should refactor the code so that this part is not used by
-          // ext_ratectrl.
+          // Hence, we can safely turn off this error reporting.
           break;
         }
         vpx_internal_error(&cm->error, VPX_CODEC_ERROR,
@@ -3507,8 +3418,8 @@
     kf_boost_scan_frames = (int)(VPXMAX(64 * zero_motion_avg - 16,
                                         160 * motion_compensable_avg - 112));
     kf_boost_scan_frames =
-        VPXMAX(VPXMIN(kf_boost_scan_frames, MAX_SCAN_FRAMES_FOR_KF_BOOST),
-               MIN_SCAN_FRAMES_FOR_KF_BOOST);
+        clamp(kf_boost_scan_frames, MIN_SCAN_FRAMES_FOR_KF_BOOST,
+              MAX_SCAN_FRAMES_FOR_KF_BOOST);
   }
   reset_fpf_position(twopass, start_position);
 
@@ -3936,146 +3847,6 @@
   }
 }
 
-#if CONFIG_RATE_CTRL
-void vp9_get_next_group_of_picture(const VP9_COMP *cpi, int *first_is_key_frame,
-                                   int *use_alt_ref, int *coding_frame_count,
-                                   int *first_show_idx,
-                                   int *last_gop_use_alt_ref) {
-  const GOP_COMMAND *gop_command = &cpi->encode_command.gop_command;
-  // We make a copy of rc here because we want to get information from the
-  // encoder without changing its state.
-  // TODO(angiebird): Avoid copying rc here.
-  RATE_CONTROL rc = cpi->rc;
-  const int multi_layer_arf = 0;
-  const int allow_alt_ref = 1;
-  // We assume that current_video_frame is updated to the show index of the
-  // frame we are about to called. Note that current_video_frame is updated at
-  // the end of encode_frame_to_data_rate().
-  // TODO(angiebird): Avoid this kind of fragile style.
-  *first_show_idx = cpi->common.current_video_frame;
-  *last_gop_use_alt_ref = rc.source_alt_ref_active;
-
-  *first_is_key_frame = 0;
-  if (rc.frames_to_key == 0) {
-    rc.frames_to_key = vp9_get_frames_to_next_key(
-        &cpi->oxcf, &cpi->twopass, *first_show_idx, rc.min_gf_interval);
-    rc.frames_since_key = 0;
-    *first_is_key_frame = 1;
-  }
-
-  if (gop_command->use) {
-    *coding_frame_count = gop_command_coding_frame_count(gop_command);
-    *use_alt_ref = gop_command->use_alt_ref;
-    assert(gop_command->show_frame_count <= rc.frames_to_key);
-  } else {
-    *coding_frame_count = vp9_get_gop_coding_frame_count(
-        &cpi->oxcf, &cpi->twopass, &cpi->frame_info, &rc, *first_show_idx,
-        multi_layer_arf, allow_alt_ref, *first_is_key_frame,
-        *last_gop_use_alt_ref, use_alt_ref);
-  }
-}
-
-int vp9_get_gop_coding_frame_count(const VP9EncoderConfig *oxcf,
-                                   const TWO_PASS *const twopass,
-                                   const FRAME_INFO *frame_info,
-                                   const RATE_CONTROL *rc, int show_idx,
-                                   int multi_layer_arf, int allow_alt_ref,
-                                   int first_is_key_frame,
-                                   int last_gop_use_alt_ref, int *use_alt_ref) {
-  int frame_count;
-  double gop_intra_factor;
-  const int arf_active_or_kf = last_gop_use_alt_ref || first_is_key_frame;
-  RANGE active_gf_interval;
-  int arf_layers;
-  int end_of_sequence = 0;
-  if (oxcf->use_simple_encode_api) {
-    active_gf_interval = get_active_gf_inverval_range_simple(
-        rc->min_gf_interval, arf_active_or_kf, rc->frames_to_key);
-  } else {
-    active_gf_interval = get_active_gf_inverval_range(
-        frame_info, rc, arf_active_or_kf, show_idx, /*active_worst_quality=*/0,
-        /*last_boosted_qindex=*/0);
-  }
-
-  arf_layers = get_arf_layers(multi_layer_arf, oxcf->enable_auto_arf,
-                              active_gf_interval.max);
-  if (multi_layer_arf) {
-    gop_intra_factor = 1.0 + 0.25 * arf_layers;
-  } else {
-    gop_intra_factor = 1.0;
-  }
-
-  frame_count = get_gop_coding_frame_num(
-      use_alt_ref, frame_info, twopass, rc, show_idx, &active_gf_interval,
-      gop_intra_factor, oxcf->lag_in_frames, &end_of_sequence);
-  *use_alt_ref &= allow_alt_ref;
-  return frame_count;
-}
-
-// Under CONFIG_RATE_CTRL, once the first_pass_info is ready, the number of
-// coding frames (including show frame and alt ref) can be determined.
-int vp9_get_coding_frame_num(const VP9EncoderConfig *oxcf,
-                             const TWO_PASS *const twopass,
-                             const FRAME_INFO *frame_info, int multi_layer_arf,
-                             int allow_alt_ref) {
-  const FIRST_PASS_INFO *first_pass_info = &twopass->first_pass_info;
-  int coding_frame_num = 0;
-  RATE_CONTROL rc;
-  int gop_coding_frame_count;
-  int gop_show_frames;
-  int show_idx = 0;
-  int last_gop_use_alt_ref = 0;
-  vp9_rc_init(oxcf, 1, &rc);
-
-  while (show_idx < first_pass_info->num_frames) {
-    int use_alt_ref;
-    int first_is_key_frame = 0;
-    if (rc.frames_to_key == 0) {
-      rc.frames_to_key = vp9_get_frames_to_next_key(oxcf, twopass, show_idx,
-                                                    rc.min_gf_interval);
-      rc.frames_since_key = 0;
-      first_is_key_frame = 1;
-    }
-
-    gop_coding_frame_count = vp9_get_gop_coding_frame_count(
-        oxcf, twopass, frame_info, &rc, show_idx, multi_layer_arf,
-        allow_alt_ref, first_is_key_frame, last_gop_use_alt_ref, &use_alt_ref);
-
-    rc.source_alt_ref_active = use_alt_ref;
-    last_gop_use_alt_ref = use_alt_ref;
-    gop_show_frames = gop_coding_frame_count - use_alt_ref;
-    rc.frames_to_key -= gop_show_frames;
-    rc.frames_since_key += gop_show_frames;
-    show_idx += gop_show_frames;
-    coding_frame_num += gop_show_frames + use_alt_ref;
-  }
-  return coding_frame_num;
-}
-
-void vp9_get_key_frame_map(const VP9EncoderConfig *oxcf,
-                           const TWO_PASS *const twopass, int *key_frame_map) {
-  const FIRST_PASS_INFO *first_pass_info = &twopass->first_pass_info;
-  int show_idx = 0;
-  RATE_CONTROL rc;
-  vp9_rc_init(oxcf, 1, &rc);
-
-  // key_frame_map points to an int array with size equal to
-  // first_pass_info->num_frames, which is also the number of show frames in the
-  // video.
-  memset(key_frame_map, 0,
-         sizeof(*key_frame_map) * first_pass_info->num_frames);
-  while (show_idx < first_pass_info->num_frames) {
-    int key_frame_group_size;
-    key_frame_map[show_idx] = 1;
-    key_frame_group_size =
-        vp9_get_frames_to_next_key(oxcf, twopass, show_idx, rc.min_gf_interval);
-    assert(key_frame_group_size > 0);
-    show_idx += key_frame_group_size;
-  }
-  assert(show_idx == first_pass_info->num_frames);
-}
-#endif  // CONFIG_RATE_CTRL
-
 FIRSTPASS_STATS vp9_get_frame_stats(const TWO_PASS *twopass) {
   return twopass->this_frame_stats;
 }
diff --git a/vp9/encoder/vp9_firstpass.h b/vp9/encoder/vp9_firstpass.h
index 94b0837..9fdd5fc 100644
--- a/vp9/encoder/vp9_firstpass.h
+++ b/vp9/encoder/vp9_firstpass.h
@@ -227,54 +227,6 @@
 int vp9_get_frames_to_next_key(const struct VP9EncoderConfig *oxcf,
                                const TWO_PASS *const twopass, int kf_show_idx,
                                int min_gf_interval);
-#if CONFIG_RATE_CTRL
-/* Call this function to get info about the next group of pictures.
- * This function should be called after vp9_create_compressor() when encoding
- * starts or after vp9_get_compressed_data() when the encoding process of
- * the last group of pictures is just finished.
- */
-void vp9_get_next_group_of_picture(const struct VP9_COMP *cpi,
-                                   int *first_is_key_frame, int *use_alt_ref,
-                                   int *coding_frame_count, int *first_show_idx,
-                                   int *last_gop_use_alt_ref);
-
-/*!\brief Call this function before coding a new group of pictures to get
- * information about it.
- * \param[in] oxcf                 Encoder config
- * \param[in] twopass              Twopass info
- * \param[in] frame_info           Frame info
- * \param[in] rc                   Rate control state
- * \param[in] show_idx             Show index of the first frame in the group
- * \param[in] multi_layer_arf      Is multi-layer alternate reference used
- * \param[in] allow_alt_ref        Is alternate reference allowed
- * \param[in] first_is_key_frame   Is the first frame in the group a key frame
- * \param[in] last_gop_use_alt_ref Does the last group use alternate reference
- *
- * \param[out] use_alt_ref         Does this group use alternate reference
- *
- * \return Returns coding frame count
- */
-int vp9_get_gop_coding_frame_count(const struct VP9EncoderConfig *oxcf,
-                                   const TWO_PASS *const twopass,
-                                   const FRAME_INFO *frame_info,
-                                   const RATE_CONTROL *rc, int show_idx,
-                                   int multi_layer_arf, int allow_alt_ref,
-                                   int first_is_key_frame,
-                                   int last_gop_use_alt_ref, int *use_alt_ref);
-
-int vp9_get_coding_frame_num(const struct VP9EncoderConfig *oxcf,
-                             const TWO_PASS *const twopass,
-                             const FRAME_INFO *frame_info, int multi_layer_arf,
-                             int allow_alt_ref);
-
-/*!\brief Compute a key frame binary map indicates whether key frames appear
- * in the corresponding positions. The passed in key_frame_map must point to an
- * integer array with length equal to twopass->first_pass_info.num_frames,
- * which is the number of show frames in the video.
- */
-void vp9_get_key_frame_map(const struct VP9EncoderConfig *oxcf,
-                           const TWO_PASS *const twopass, int *key_frame_map);
-#endif  // CONFIG_RATE_CTRL
 
 FIRSTPASS_STATS vp9_get_frame_stats(const TWO_PASS *twopass);
 FIRSTPASS_STATS vp9_get_total_stats(const TWO_PASS *twopass);
diff --git a/vp9/encoder/vp9_lookahead.c b/vp9/encoder/vp9_lookahead.c
index b6be4f8..ba4fe3d 100644
--- a/vp9/encoder/vp9_lookahead.c
+++ b/vp9/encoder/vp9_lookahead.c
@@ -132,6 +132,7 @@
     vpx_free_frame_buffer(&buf->img);
     buf->img = new_img;
   } else if (new_dimensions) {
+    int aligned_width = ALIGN_POWER_OF_TWO(width, 3);
     buf->img.y_width = src->y_width;
     buf->img.y_height = src->y_height;
     buf->img.uv_width = src->uv_width;
@@ -142,6 +143,13 @@
     buf->img.uv_crop_height = src->uv_crop_height;
     buf->img.subsampling_x = src->subsampling_x;
     buf->img.subsampling_y = src->subsampling_y;
+    // Here the new width (src->y_crop_width) is <= the previous width
+    // (since otherwise it would enter the "larger_dimensions" code), so
+    // it is safe here to update the stride.
+    // The stride setting is taken from vpx_alloc_frame_buffer().
+    buf->img.y_stride =
+        ALIGN_POWER_OF_TWO((aligned_width + 2 * buf->img.border), 5);
+    buf->img.uv_stride = buf->img.y_stride >> subsampling_x;
   }
   vp9_copy_and_extend_frame(src, &buf->img);
 
diff --git a/vp9/encoder/vp9_mcomp.c b/vp9/encoder/vp9_mcomp.c
index cbe1c40..1f7f174 100644
--- a/vp9/encoder/vp9_mcomp.c
+++ b/vp9/encoder/vp9_mcomp.c
@@ -1868,8 +1868,8 @@
 
   // Check size of proposed first range against magnitude of the centre
   // value used as a starting point.
-  range = VPXMAX(range, (5 * VPXMAX(abs(temp_mv.row), abs(temp_mv.col))) / 4);
-  range = VPXMIN(range, MAX_RANGE);
+  range = clamp(range, (5 * VPXMAX(abs(temp_mv.row), abs(temp_mv.col))) / 4,
+                MAX_RANGE);
   interval = VPXMAX(interval, range / baseline_interval_divisor);
 
   // initial search
@@ -2629,8 +2629,8 @@
 
   // Check size of proposed first range against magnitude of the centre
   // value used as a starting point.
-  range = VPXMAX(range, (5 * VPXMAX(abs(temp_mv.row), abs(temp_mv.col))) / 4);
-  range = VPXMIN(range, MAX_RANGE);
+  range = clamp(range, (5 * VPXMAX(abs(temp_mv.row), abs(temp_mv.col))) / 4,
+                MAX_RANGE);
   interval = VPXMAX(interval, range / baseline_interval_divisor);
 
   // initial search
diff --git a/vp9/encoder/vp9_pickmode.c b/vp9/encoder/vp9_pickmode.c
index d561b50..b47dc6c 100644
--- a/vp9/encoder/vp9_pickmode.c
+++ b/vp9/encoder/vp9_pickmode.c
@@ -12,6 +12,7 @@
 #include <limits.h>
 #include <math.h>
 #include <stdio.h>
+#include <stdlib.h>
 
 #include "./vp9_rtcd.h"
 #include "./vpx_dsp_rtcd.h"
@@ -128,15 +129,24 @@
       !cpi->svc.layer_context[cpi->svc.temporal_layer_id].is_key_frame &&
       ref_frame == LAST_FRAME) {
     // Get base layer mv.
-    MV_REF *candidate =
-        &cm->prev_frame
-             ->mvs[(mi_col >> 1) + (mi_row >> 1) * (cm->mi_cols >> 1)];
-    if (candidate->mv[0].as_int != INVALID_MV) {
-      base_mv->as_mv.row = (candidate->mv[0].as_mv.row * 2);
-      base_mv->as_mv.col = (candidate->mv[0].as_mv.col * 2);
-      clamp_mv_ref(&base_mv->as_mv, xd);
-    } else {
-      base_mv->as_int = INVALID_MV;
+    const int prev_layer = cpi->svc.spatial_layer_id - 1;
+    const int index =
+        (mi_col >> 1) + (mi_row >> 1) * cpi->svc.mi_cols[prev_layer];
+    // prev_frame->mvs[] is allocated to size mi_cols * mi_rows corresponding
+    // to the previous spatial layer, so the index check is against
+    // svc.mi_col/rows[prev_layer].
+    if (index < cpi->svc.mi_cols[prev_layer] * cpi->svc.mi_rows[prev_layer]) {
+      MV_REF *candidate = &cm->prev_frame->mvs[index];
+      // Avoid using base_mv if scaled mv is out of range, for either component.
+      if (candidate->mv[0].as_int != INVALID_MV &&
+          abs(candidate->mv[0].as_mv.row) <= INT16_MAX >> 1 &&
+          abs(candidate->mv[0].as_mv.col) <= INT16_MAX >> 1) {
+        base_mv->as_mv.row = candidate->mv[0].as_mv.row * 2;
+        base_mv->as_mv.col = candidate->mv[0].as_mv.col * 2;
+        clamp_mv_ref(&base_mv->as_mv, xd);
+      } else {
+        base_mv->as_int = INVALID_MV;
+      }
     }
   }
 
diff --git a/vp9/encoder/vp9_quantize.c b/vp9/encoder/vp9_quantize.c
index d37e020..a1e0b44 100644
--- a/vp9/encoder/vp9_quantize.c
+++ b/vp9/encoder/vp9_quantize.c
@@ -289,7 +289,7 @@
   vp9_init_plane_quantizers(cpi, &cpi->td.mb);
 }
 
-void vp9_set_quantizer(VP9_COMP *cpi, int q) {
+void vp9_set_quantizer(VP9_COMP *cpi, int q, int ext_rc_delta_q_uv) {
   VP9_COMMON *cm = &cpi->common;
   // quantizer has to be reinitialized with vp9_init_quantizer() if any
   // delta_q changes.
@@ -297,6 +297,13 @@
   cm->y_dc_delta_q = 0;
   cm->uv_dc_delta_q = 0;
   cm->uv_ac_delta_q = 0;
+
+  if (ext_rc_delta_q_uv != 0) {
+    cm->uv_dc_delta_q = cm->uv_ac_delta_q = ext_rc_delta_q_uv;
+    vp9_init_quantizer(cpi);
+    return;
+  }
+
   if (cpi->oxcf.delta_q_uv != 0) {
     cm->uv_dc_delta_q = cm->uv_ac_delta_q = cpi->oxcf.delta_q_uv;
     vp9_init_quantizer(cpi);
diff --git a/vp9/encoder/vp9_quantize.h b/vp9/encoder/vp9_quantize.h
index f626f06..bca2e05 100644
--- a/vp9/encoder/vp9_quantize.h
+++ b/vp9/encoder/vp9_quantize.h
@@ -46,7 +46,7 @@
 
 void vp9_init_quantizer(struct VP9_COMP *cpi);
 
-void vp9_set_quantizer(struct VP9_COMP *cm, int q);
+void vp9_set_quantizer(struct VP9_COMP *cpi, int q, int ext_rc_delta_q_uv);
 
 int vp9_quantizer_to_qindex(int quantizer);
 
diff --git a/vp9/encoder/vp9_ratectrl.c b/vp9/encoder/vp9_ratectrl.c
index e95bcc8..f6ebf0c 100644
--- a/vp9/encoder/vp9_ratectrl.c
+++ b/vp9/encoder/vp9_ratectrl.c
@@ -1299,6 +1299,30 @@
   } else {
     q = vp9_rc_regulate_q(cpi, rc->this_frame_target, active_best_quality,
                           active_worst_quality);
+
+    // For no lookahead: if buffer_level indicates overshoot, then avoid going
+    // to very low QP. This reduces overshoot observed in Issue: 376707227.
+    // Note the buffer_level is updated for every encoded frame as:
+    // buffer_level - starting_buffer_level += (avg_frame_bandwidth -
+    // encoded_frame_size). So normalizing this with framerate and #encoded
+    // frames (current_video_frame) gives the difference/error between target
+    // and encoding bitrate. The additional avg_frame_bandwidth term is to
+    // compensate for the pre-encoded buffer update (in
+    // vp9_rc_get_one_pass_vbr_params).
+    const int qp_thresh = 32;
+    const int64_t bitrate_err =
+        (int64_t)(cpi->framerate *
+                  (rc->buffer_level - rc->starting_buffer_level -
+                   rc->avg_frame_bandwidth) /
+                  (cm->current_video_frame + 1));
+    // Threshold may be tuned, but for now condition this on low QP.
+    if (cpi->oxcf.lag_in_frames == 0 && bitrate_err / 1000 < -10 &&
+        qp_thresh < rc->worst_quality &&
+        (q < qp_thresh || *top_index < qp_thresh)) {
+      q = qp_thresh;
+      *top_index = VPXMAX(*top_index, q);
+    }
+
     if (q > *top_index) {
       // Special case when we are targeting the max allowed rate
       if (rc->this_frame_target >= rc->max_frame_bandwidth)
@@ -1710,14 +1734,6 @@
                                   rate_thresh_mult[rc->frame_size_selector]);
   }
 
-#if CONFIG_RATE_CTRL
-  if (cpi->oxcf.use_simple_encode_api) {
-    if (cpi->encode_command.use_external_target_frame_bits) {
-      rc->this_frame_target = cpi->encode_command.target_frame_bits;
-    }
-  }
-#endif  // CONFIG_RATE_CTRL
-
   // Target rate per SB64 (including partial SB64s.
   const int64_t sb64_target_rate =
       ((int64_t)rc->this_frame_target * 64 * 64) / (cm->width * cm->height);
@@ -2126,6 +2142,7 @@
   else
     target = vp9_calc_pframe_target_size_one_pass_vbr(cpi);
   vp9_rc_set_frame_target(cpi, target);
+  if (cm->show_frame) vp9_update_buffer_level_preencode(cpi);
   if (cpi->oxcf.aq_mode == CYCLIC_REFRESH_AQ && cpi->oxcf.pass == 0)
     vp9_cyclic_refresh_update_parameters(cpi);
 }
@@ -2138,15 +2155,16 @@
   const int64_t one_pct_bits = 1 + rc->optimal_buffer_level / 100;
   int min_frame_target =
       VPXMAX(rc->avg_frame_bandwidth >> 4, FRAME_OVERHEAD_BITS);
-  int target;
+  int64_t target;
 
   if (oxcf->gf_cbr_boost_pct) {
     const int af_ratio_pct = oxcf->gf_cbr_boost_pct + 100;
     target = cpi->refresh_golden_frame
-                 ? (rc->avg_frame_bandwidth * rc->baseline_gf_interval *
-                    af_ratio_pct) /
+                 ? ((int64_t)rc->avg_frame_bandwidth *
+                    rc->baseline_gf_interval * af_ratio_pct) /
                        (rc->baseline_gf_interval * 100 + af_ratio_pct - 100)
-                 : (rc->avg_frame_bandwidth * rc->baseline_gf_interval * 100) /
+                 : ((int64_t)rc->avg_frame_bandwidth *
+                    rc->baseline_gf_interval * 100) /
                        (rc->baseline_gf_interval * 100 + af_ratio_pct - 100);
   } else {
     target = rc->avg_frame_bandwidth;
@@ -2164,19 +2182,20 @@
   if (diff > 0) {
     // Lower the target bandwidth for this frame.
     const int pct_low = (int)VPXMIN(diff / one_pct_bits, oxcf->under_shoot_pct);
-    target -= (int)(((int64_t)target * pct_low) / 200);
+    target -= (target * pct_low) / 200;
   } else if (diff < 0) {
     // Increase the target bandwidth for this frame.
     const int pct_high =
         (int)VPXMIN(-diff / one_pct_bits, oxcf->over_shoot_pct);
-    target += (int)(((int64_t)target * pct_high) / 200);
+    target += (target * pct_high) / 200;
   }
   if (oxcf->rc_max_inter_bitrate_pct) {
-    const int max_rate =
-        rc->avg_frame_bandwidth * oxcf->rc_max_inter_bitrate_pct / 100;
+    const int64_t max_rate =
+        (int64_t)rc->avg_frame_bandwidth * oxcf->rc_max_inter_bitrate_pct / 100;
     target = VPXMIN(target, max_rate);
   }
-  return VPXMAX(min_frame_target, target);
+  if (target > INT_MAX) target = INT_MAX;
+  return VPXMAX(min_frame_target, (int)target);
 }
 
 int vp9_calc_iframe_target_size_one_pass_cbr(const VP9_COMP *cpi) {
@@ -2585,13 +2604,6 @@
     // Set Maximum gf/arf interval
     rc->max_gf_interval = oxcf->max_gf_interval;
     rc->min_gf_interval = oxcf->min_gf_interval;
-#if CONFIG_RATE_CTRL
-    if (oxcf->use_simple_encode_api) {
-      // In this experiment, we avoid framerate being changed dynamically during
-      // encoding.
-      framerate = oxcf->init_framerate;
-    }
-#endif  // CONFIG_RATE_CTRL
     if (rc->min_gf_interval == 0) {
       rc->min_gf_interval = vp9_rc_get_default_min_gf_interval(
           oxcf->width, oxcf->height, framerate);
@@ -3254,12 +3266,22 @@
     double rate_correction_factor =
         cpi->rc.rate_correction_factors[INTER_NORMAL];
     const int target_size = cpi->rc.avg_frame_bandwidth;
+    const uint64_t sad_thr = 64 * 64 * 32;
+    int force_maxqp = 1;
     double new_correction_factor;
     int target_bits_per_mb;
     double q2;
     int enumerator;
-    // Force a re-encode, and for now use max-QP.
-    *q = cpi->rc.worst_quality;
+    // Set a larger QP.
+    if (cpi->oxcf.content != VP9E_CONTENT_SCREEN &&
+        cm->width * cm->height >= 1280 * 720 &&
+        (rc->buffer_level > (3 * rc->optimal_buffer_level) >> 2) &&
+        (cpi->rc.avg_source_sad[0] < sad_thr)) {
+      *q = (*q + cpi->rc.worst_quality) >> 1;
+      force_maxqp = 0;
+    } else {
+      *q = cpi->rc.worst_quality;
+    }
     cpi->cyclic_refresh->counter_encode_maxq_scene_change = 0;
     cpi->rc.re_encode_maxq_scene_change = 1;
     // If the frame_size is much larger than the threshold (big content change)
@@ -3333,7 +3355,7 @@
           lrc->rc_1_frame = 0;
           lrc->rc_2_frame = 0;
           lrc->rate_correction_factors[INTER_NORMAL] = rate_correction_factor;
-          lrc->force_max_q = 1;
+          lrc->force_max_q = force_maxqp;
         }
       }
     }
diff --git a/vp9/encoder/vp9_rdopt.c b/vp9/encoder/vp9_rdopt.c
index 260aa0a..93a4d96 100644
--- a/vp9/encoder/vp9_rdopt.c
+++ b/vp9/encoder/vp9_rdopt.c
@@ -2319,8 +2319,7 @@
 
           if (!subpelmv && have_ref &&
               ref_bsi->rdstat[block][mode_idx].brdcost < INT64_MAX) {
-            memcpy(&bsi->rdstat[block][mode_idx],
-                   &ref_bsi->rdstat[block][mode_idx], sizeof(SEG_RDSTAT));
+            bsi->rdstat[block][mode_idx] = ref_bsi->rdstat[block][mode_idx];
             if (num_4x4_blocks_wide > 1)
               bsi->rdstat[block + 1][mode_idx].eobs =
                   ref_bsi->rdstat[block + 1][mode_idx].eobs;
@@ -4893,8 +4892,7 @@
   if (!is_inter_block(&best_mbmode)) {
     for (i = 0; i < 4; i++) xd->mi[0]->bmi[i].as_mode = best_bmodes[i].as_mode;
   } else {
-    for (i = 0; i < 4; ++i)
-      memcpy(&xd->mi[0]->bmi[i], &best_bmodes[i], sizeof(b_mode_info));
+    for (i = 0; i < 4; ++i) xd->mi[0]->bmi[i] = best_bmodes[i];
 
     mi->mv[0].as_int = xd->mi[0]->bmi[3].as_mv[0].as_int;
     mi->mv[1].as_int = xd->mi[0]->bmi[3].as_mv[1].as_int;
diff --git a/vp9/encoder/vp9_svc_layercontext.c b/vp9/encoder/vp9_svc_layercontext.c
index 6bc329a..6e9405e 100644
--- a/vp9/encoder/vp9_svc_layercontext.c
+++ b/vp9/encoder/vp9_svc_layercontext.c
@@ -457,7 +457,13 @@
                           int *height_out) {
   int w, h;
 
-  if (width_out == NULL || height_out == NULL || den == 0) return;
+  if (width_out == NULL || height_out == NULL) return;
+
+  if (den == 0 || num == 0) {
+    *width_out = width_org;
+    *height_out = height_org;
+    return;
+  }
 
   w = width_org * num / den;
   h = height_org * num / den;
@@ -787,9 +793,6 @@
     svc->use_partition_reuse = 1;
   }
   svc->force_zero_mode_spatial_ref = 1;
-  svc->mi_stride[svc->spatial_layer_id] = cpi->common.mi_stride;
-  svc->mi_rows[svc->spatial_layer_id] = cpi->common.mi_rows;
-  svc->mi_cols[svc->spatial_layer_id] = cpi->common.mi_cols;
 
   // For constrained_from_above drop mode: before encoding superframe (i.e.,
   // at SL0 frame) check all spatial layers (starting from top) for possible
@@ -991,6 +994,9 @@
   if (vp9_set_size_literal(cpi, width, height) != 0)
     return VPX_CODEC_INVALID_PARAM;
 
+  svc->mi_stride[svc->spatial_layer_id] = cpi->common.mi_stride;
+  svc->mi_rows[svc->spatial_layer_id] = cpi->common.mi_rows;
+  svc->mi_cols[svc->spatial_layer_id] = cpi->common.mi_cols;
   return 0;
 }
 
diff --git a/vp9/encoder/vp9_temporal_filter.c b/vp9/encoder/vp9_temporal_filter.c
index 2d696f4..46f36c3 100644
--- a/vp9/encoder/vp9_temporal_filter.c
+++ b/vp9/encoder/vp9_temporal_filter.c
@@ -45,36 +45,17 @@
                                          0U,          991146300U };
 #endif  // CONFIG_VP9_HIGHBITDEPTH
 
-// Prediction function using 12-tap interpolation filter.
-// TODO(yunqingwang@google.com): add SIMD optimization.
-#define MAX_FILTER_TAP 12
+static const MV kZeroMv = { 0, 0 };
 #define TF_INTERP_EXTEND 6
-typedef int16_t InterpKernel12[MAX_FILTER_TAP];
-// 12-tap filter (used by the encoder only).
-DECLARE_ALIGNED(256, static const InterpKernel12,
-                sub_pel_filters_12[SUBPEL_SHIFTS]) = {
-  { 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0 },
-  { 0, 1, -2, 3, -7, 127, 8, -4, 2, -1, 1, 0 },
-  { -1, 2, -3, 6, -13, 124, 18, -8, 4, -2, 2, -1 },
-  { -1, 3, -4, 8, -18, 120, 28, -12, 7, -4, 2, -1 },
-  { -1, 3, -6, 10, -21, 115, 38, -15, 8, -5, 3, -1 },
-  { -2, 4, -6, 12, -24, 108, 49, -18, 10, -6, 3, -2 },
-  { -2, 4, -7, 13, -25, 100, 60, -21, 11, -7, 4, -2 },
-  { -2, 4, -7, 13, -26, 91, 71, -24, 13, -7, 4, -2 },
-  { -2, 4, -7, 13, -25, 81, 81, -25, 13, -7, 4, -2 },
-  { -2, 4, -7, 13, -24, 71, 91, -26, 13, -7, 4, -2 },
-  { -2, 4, -7, 11, -21, 60, 100, -25, 13, -7, 4, -2 },
-  { -2, 3, -6, 10, -18, 49, 108, -24, 12, -6, 4, -2 },
-  { -1, 3, -5, 8, -15, 38, 115, -21, 10, -6, 3, -1 },
-  { -1, 2, -4, 7, -12, 28, 120, -18, 8, -4, 3, -1 },
-  { -1, 2, -2, 4, -8, 18, 124, -13, 6, -3, 2, -1 },
-  { 0, 1, -1, 2, -4, 8, 127, -7, 3, -2, 1, 0 }
-};
 
-static void convolve_horiz_12(const uint8_t *src, ptrdiff_t src_stride,
-                              uint8_t *dst, ptrdiff_t dst_stride,
-                              const InterpKernel12 *x_filters, int x0_q4,
-                              int x_step_q4, int w, int h) {
+// Prediction function using 12-tap interpolation filter.
+void vpx_convolve12_horiz_c(const uint8_t *src, ptrdiff_t src_stride,
+                            uint8_t *dst, ptrdiff_t dst_stride,
+                            const InterpKernel12 *filter, int x0_q4,
+                            int x_step_q4, int y0_q4, int y_step_q4, int w,
+                            int h) {
+  (void)y0_q4;
+  (void)y_step_q4;
   int x, y;
   src -= MAX_FILTER_TAP / 2 - 1;
 
@@ -82,7 +63,7 @@
     int x_q4 = x0_q4;
     for (x = 0; x < w; ++x) {
       const uint8_t *const src_x = &src[x_q4 >> SUBPEL_BITS];
-      const int16_t *const x_filter = x_filters[x_q4 & SUBPEL_MASK];
+      const int16_t *const x_filter = filter[x_q4 & SUBPEL_MASK];
       int k, sum = 0;
       for (k = 0; k < MAX_FILTER_TAP; ++k) sum += src_x[k] * x_filter[k];
       dst[x] = clip_pixel(ROUND_POWER_OF_TWO(sum, FILTER_BITS));
@@ -93,10 +74,13 @@
   }
 }
 
-static void convolve_vert_12(const uint8_t *src, ptrdiff_t src_stride,
-                             uint8_t *dst, ptrdiff_t dst_stride,
-                             const InterpKernel12 *y_filters, int y0_q4,
-                             int y_step_q4, int w, int h) {
+void vpx_convolve12_vert_c(const uint8_t *src, ptrdiff_t src_stride,
+                           uint8_t *dst, ptrdiff_t dst_stride,
+                           const InterpKernel12 *filter, int x0_q4,
+                           int x_step_q4, int y0_q4, int y_step_q4, int w,
+                           int h) {
+  (void)x0_q4;
+  (void)x_step_q4;
   int x, y;
   src -= src_stride * (MAX_FILTER_TAP / 2 - 1);
 
@@ -104,7 +88,7 @@
     int y_q4 = y0_q4;
     for (y = 0; y < h; ++y) {
       const uint8_t *src_y = &src[(y_q4 >> SUBPEL_BITS) * src_stride];
-      const int16_t *const y_filter = y_filters[y_q4 & SUBPEL_MASK];
+      const int16_t *const y_filter = filter[y_q4 & SUBPEL_MASK];
       int k, sum = 0;
       for (k = 0; k < MAX_FILTER_TAP; ++k)
         sum += src_y[k * src_stride] * y_filter[k];
@@ -117,21 +101,21 @@
 }
 
 // Copied from vpx_convolve8_c(). Possible block sizes are 32x32, 16x16, 8x8.
-static void vpx_convolve8_12_c(const uint8_t *src, ptrdiff_t src_stride,
-                               uint8_t *dst, ptrdiff_t dst_stride,
-                               const InterpKernel12 *filter, int x0_q4,
-                               int x_step_q4, int y0_q4, int y_step_q4, int w,
-                               int h) {
+void vpx_convolve12_c(const uint8_t *src, ptrdiff_t src_stride, uint8_t *dst,
+                      ptrdiff_t dst_stride, const InterpKernel12 *filter,
+                      int x0_q4, int x_step_q4, int y0_q4, int y_step_q4, int w,
+                      int h) {
   uint8_t temp[BW * (BH + MAX_FILTER_TAP - 1)];
   const int temp_stride = BW;
   const int intermediate_height =
       (((h - 1) * y_step_q4 + y0_q4) >> SUBPEL_BITS) + MAX_FILTER_TAP;
 
-  convolve_horiz_12(src - src_stride * (MAX_FILTER_TAP / 2 - 1), src_stride,
-                    temp, temp_stride, filter, x0_q4, x_step_q4, w,
-                    intermediate_height);
-  convolve_vert_12(temp + temp_stride * (MAX_FILTER_TAP / 2 - 1), temp_stride,
-                   dst, dst_stride, filter, y0_q4, y_step_q4, w, h);
+  vpx_convolve12_horiz_c(src - src_stride * (MAX_FILTER_TAP / 2 - 1),
+                         src_stride, temp, temp_stride, filter, x0_q4,
+                         x_step_q4, y0_q4, y_step_q4, w, intermediate_height);
+  vpx_convolve12_vert_c(temp + temp_stride * (MAX_FILTER_TAP / 2 - 1),
+                        temp_stride, dst, dst_stride, filter, x0_q4, x_step_q4,
+                        y0_q4, y_step_q4, w, h);
 }
 
 static void vp9_build_inter_predictor_12(
@@ -148,15 +132,29 @@
 
   src += (mv.row >> SUBPEL_BITS) * src_stride + (mv.col >> SUBPEL_BITS);
 
-  vpx_convolve8_12_c(src, src_stride, dst, dst_stride, kernel, subpel_x,
-                     sf->x_step_q4, subpel_y, sf->y_step_q4, w, h);
+  if (subpel_x == 0 && subpel_y == 0) {
+    vpx_convolve_copy(src, src_stride, dst, dst_stride, NULL, subpel_x,
+                      sf->x_step_q4, subpel_y, sf->y_step_q4, w, h);
+  } else if (subpel_x == 0 && subpel_y != 0) {
+    vpx_convolve12_vert(src, src_stride, dst, dst_stride, kernel, subpel_x,
+                        sf->x_step_q4, subpel_y, sf->y_step_q4, w, h);
+  } else if (subpel_x != 0 && subpel_y == 0) {
+    vpx_convolve12_horiz(src, src_stride, dst, dst_stride, kernel, subpel_x,
+                         sf->x_step_q4, subpel_y, sf->y_step_q4, w, h);
+  } else {
+    vpx_convolve12(src, src_stride, dst, dst_stride, kernel, subpel_x,
+                   sf->x_step_q4, subpel_y, sf->y_step_q4, w, h);
+  }
 }
 
 #if CONFIG_VP9_HIGHBITDEPTH
-static void highbd_convolve_horiz_12(const uint16_t *src, ptrdiff_t src_stride,
-                                     uint16_t *dst, ptrdiff_t dst_stride,
-                                     const InterpKernel12 *x_filters, int x0_q4,
-                                     int x_step_q4, int w, int h, int bd) {
+void vpx_highbd_convolve12_horiz_c(const uint16_t *src, ptrdiff_t src_stride,
+                                   uint16_t *dst, ptrdiff_t dst_stride,
+                                   const InterpKernel12 *filter, int x0_q4,
+                                   int x_step_q4, int y0_q4, int y_step_q4,
+                                   int w, int h, int bd) {
+  (void)y0_q4;
+  (void)y_step_q4;
   int x, y;
   src -= MAX_FILTER_TAP / 2 - 1;
 
@@ -164,7 +162,7 @@
     int x_q4 = x0_q4;
     for (x = 0; x < w; ++x) {
       const uint16_t *const src_x = &src[x_q4 >> SUBPEL_BITS];
-      const int16_t *const x_filter = x_filters[x_q4 & SUBPEL_MASK];
+      const int16_t *const x_filter = filter[x_q4 & SUBPEL_MASK];
       int k, sum = 0;
       for (k = 0; k < MAX_FILTER_TAP; ++k) sum += src_x[k] * x_filter[k];
       dst[x] = clip_pixel_highbd(ROUND_POWER_OF_TWO(sum, FILTER_BITS), bd);
@@ -175,10 +173,13 @@
   }
 }
 
-static void highbd_convolve_vert_12(const uint16_t *src, ptrdiff_t src_stride,
-                                    uint16_t *dst, ptrdiff_t dst_stride,
-                                    const InterpKernel12 *y_filters, int y0_q4,
-                                    int y_step_q4, int w, int h, int bd) {
+void vpx_highbd_convolve12_vert_c(const uint16_t *src, ptrdiff_t src_stride,
+                                  uint16_t *dst, ptrdiff_t dst_stride,
+                                  const InterpKernel12 *filter, int x0_q4,
+                                  int x_step_q4, int y0_q4, int y_step_q4,
+                                  int w, int h, int bd) {
+  (void)x0_q4;
+  (void)x_step_q4;
   int x, y;
   src -= src_stride * (MAX_FILTER_TAP / 2 - 1);
 
@@ -186,7 +187,7 @@
     int y_q4 = y0_q4;
     for (y = 0; y < h; ++y) {
       const uint16_t *src_y = &src[(y_q4 >> SUBPEL_BITS) * src_stride];
-      const int16_t *const y_filter = y_filters[y_q4 & SUBPEL_MASK];
+      const int16_t *const y_filter = filter[y_q4 & SUBPEL_MASK];
       int k, sum = 0;
       for (k = 0; k < MAX_FILTER_TAP; ++k)
         sum += src_y[k * src_stride] * y_filter[k];
@@ -199,32 +200,33 @@
   }
 }
 
-static void highbd_convolve_12(const uint16_t *src, ptrdiff_t src_stride,
-                               uint16_t *dst, ptrdiff_t dst_stride,
-                               const InterpKernel12 *filter, int x0_q4,
-                               int x_step_q4, int y0_q4, int y_step_q4, int w,
-                               int h, int bd) {
+static void highbd_convolve12(const uint16_t *src, ptrdiff_t src_stride,
+                              uint16_t *dst, ptrdiff_t dst_stride,
+                              const InterpKernel12 *filter, int x0_q4,
+                              int x_step_q4, int y0_q4, int y_step_q4, int w,
+                              int h, int bd) {
   uint16_t temp[BW * (BH + MAX_FILTER_TAP - 1)];
   const int temp_stride = BW;
   const int intermediate_height =
       (((h - 1) * y_step_q4 + y0_q4) >> SUBPEL_BITS) + MAX_FILTER_TAP;
 
-  highbd_convolve_horiz_12(src - src_stride * (MAX_FILTER_TAP / 2 - 1),
-                           src_stride, temp, temp_stride, filter, x0_q4,
-                           x_step_q4, w, intermediate_height, bd);
-  highbd_convolve_vert_12(temp + temp_stride * (MAX_FILTER_TAP / 2 - 1),
-                          temp_stride, dst, dst_stride, filter, y0_q4,
-                          y_step_q4, w, h, bd);
+  vpx_highbd_convolve12_horiz_c(src - src_stride * (MAX_FILTER_TAP / 2 - 1),
+                                src_stride, temp, temp_stride, filter, x0_q4,
+                                x_step_q4, y0_q4, y_step_q4, w,
+                                intermediate_height, bd);
+  vpx_highbd_convolve12_vert_c(temp + temp_stride * (MAX_FILTER_TAP / 2 - 1),
+                               temp_stride, dst, dst_stride, filter, x0_q4,
+                               x_step_q4, y0_q4, y_step_q4, w, h, bd);
 }
 
 // Copied from vpx_highbd_convolve8_c()
-static void vpx_highbd_convolve8_12_c(const uint16_t *src, ptrdiff_t src_stride,
-                                      uint16_t *dst, ptrdiff_t dst_stride,
-                                      const InterpKernel12 *filter, int x0_q4,
-                                      int x_step_q4, int y0_q4, int y_step_q4,
-                                      int w, int h, int bd) {
-  highbd_convolve_12(src, src_stride, dst, dst_stride, filter, x0_q4, x_step_q4,
-                     y0_q4, y_step_q4, w, h, bd);
+void vpx_highbd_convolve12_c(const uint16_t *src, ptrdiff_t src_stride,
+                             uint16_t *dst, ptrdiff_t dst_stride,
+                             const InterpKernel12 *filter, int x0_q4,
+                             int x_step_q4, int y0_q4, int y_step_q4, int w,
+                             int h, int bd) {
+  highbd_convolve12(src, src_stride, dst, dst_stride, filter, x0_q4, x_step_q4,
+                    y0_q4, y_step_q4, w, h, bd);
 }
 
 static void vp9_highbd_build_inter_predictor_12(
@@ -242,8 +244,21 @@
 
   src += (mv.row >> SUBPEL_BITS) * src_stride + (mv.col >> SUBPEL_BITS);
 
-  vpx_highbd_convolve8_12_c(src, src_stride, dst, dst_stride, kernel, subpel_x,
-                            sf->x_step_q4, subpel_y, sf->y_step_q4, w, h, bd);
+  if (subpel_x == 0 && subpel_y == 0) {
+    vpx_highbd_convolve_copy(src, src_stride, dst, dst_stride, NULL, subpel_x,
+                             sf->x_step_q4, subpel_y, sf->y_step_q4, w, h, bd);
+  } else if (subpel_x == 0 && subpel_y != 0) {
+    vpx_highbd_convolve12_vert(src, src_stride, dst, dst_stride, kernel,
+                               subpel_x, sf->x_step_q4, subpel_y, sf->y_step_q4,
+                               w, h, bd);
+  } else if (subpel_x != 0 && subpel_y == 0) {
+    vpx_highbd_convolve12_horiz(src, src_stride, dst, dst_stride, kernel,
+                                subpel_x, sf->x_step_q4, subpel_y,
+                                sf->y_step_q4, w, h, bd);
+  } else {
+    vpx_highbd_convolve12(src, src_stride, dst, dst_stride, kernel, subpel_x,
+                          sf->x_step_q4, subpel_y, sf->y_step_q4, w, h, bd);
+  }
 }
 #endif  // CONFIG_VP9_HIGHBITDEPTH
 
@@ -754,7 +769,7 @@
 static uint32_t temporal_filter_find_matching_mb_c(
     VP9_COMP *cpi, ThreadData *td, uint8_t *arf_frame_buf,
     uint8_t *frame_ptr_buf, int stride, MV *ref_mv, MV *blk_mvs,
-    int *blk_bestsme) {
+    int *blk_bestsme, int *is_dc_diff_large) {
   MACROBLOCK *const x = &td->mb;
   MACROBLOCKD *const xd = &x->e_mbd;
   MV_SPEED_FEATURES *const mv_sf = &cpi->sf.mv;
@@ -784,6 +799,7 @@
   x->plane[0].src.stride = stride;
   xd->plane[0].pre[0].buf = frame_ptr_buf;
   xd->plane[0].pre[0].stride = stride;
+  *is_dc_diff_large = 0;
 
   step_param = mv_sf->reduce_first_step_size;
   step_param = VPXMIN(step_param, MAX_MVSEARCH_STEPS - 2);
@@ -805,6 +821,7 @@
       x->errorperbit, &cpi->fn_ptr[TF_BLOCK], 0, mv_sf->subpel_search_level,
       cond_cost_list(cpi, cost_list), NULL, NULL, &distortion, &sse, NULL, BW,
       BH, USE_8_TAPS_SHARP);
+  *is_dc_diff_large = 50 * bestsme < sse;
 
   // DO motion search on 4 16x16 sub_blocks.
   best_ref_mv1.row = ref_mv->row;
@@ -958,12 +975,17 @@
         const int thresh_low = 10000;
         const int thresh_high = 20000;
         int blk_bestsme[4] = { INT_MAX, INT_MAX, INT_MAX, INT_MAX };
+        int is_dc_diff_large = 0;
 
         // Find best match in this frame by MC
         int err = temporal_filter_find_matching_mb_c(
             cpi, td, frames[alt_ref_index]->y_buffer + mb_y_offset,
             frames[frame]->y_buffer + mb_y_offset, frames[frame]->y_stride,
-            &ref_mv, blk_mvs, blk_bestsme);
+            &ref_mv, blk_mvs, blk_bestsme, &is_dc_diff_large);
+
+        if (cpi->oxcf.enable_keyframe_filtering == 1 &&
+            cpi->common.frame_type == KEY_FRAME && is_dc_diff_large)
+          strength = VPXMIN(strength, 1);
 
         int err16 =
             blk_bestsme[0] + blk_bestsme[1] + blk_bestsme[2] + blk_bestsme[3];
@@ -1245,7 +1267,7 @@
   if (oxcf->pass == 2) {
     base_strength = oxcf->arnr_strength + cpi->twopass.arnr_strength_adjustment;
     // Clip to allowed range.
-    base_strength = VPXMIN(6, VPXMAX(0, base_strength));
+    base_strength = clamp(base_strength, 0, 6);
   } else {
     base_strength = oxcf->arnr_strength;
   }
diff --git a/vp9/encoder/vp9_temporal_filter.h b/vp9/encoder/vp9_temporal_filter.h
index 553a468..59fb71f 100644
--- a/vp9/encoder/vp9_temporal_filter.h
+++ b/vp9/encoder/vp9_temporal_filter.h
@@ -16,7 +16,8 @@
 #endif
 
 #define ARNR_FILT_QINDEX 128
-static const MV kZeroMv = { 0, 0 };
+struct VP9_COMP;
+struct ThreadData;
 
 // Block size used in temporal filtering
 #define TF_BLOCK BLOCK_32X32
@@ -31,13 +32,37 @@
 #define TF_SUB_BLOCK BLOCK_16X16
 #define SUB_BH 16
 #define SUB_BW 16
+#define MAX_FILTER_TAP 12
+
+typedef int16_t InterpKernel12[MAX_FILTER_TAP];
+
+// 12-tap filter (used by the encoder only).
+DECLARE_ALIGNED(256, static const InterpKernel12,
+                sub_pel_filters_12[SUBPEL_SHIFTS]) = {
+  { 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0 },
+  { 0, 1, -2, 3, -7, 127, 8, -4, 2, -1, 1, 0 },
+  { -1, 2, -3, 6, -13, 124, 18, -8, 4, -2, 2, -1 },
+  { -1, 3, -4, 8, -18, 120, 28, -12, 7, -4, 2, -1 },
+  { -1, 3, -6, 10, -21, 115, 38, -15, 8, -5, 3, -1 },
+  { -2, 4, -6, 12, -24, 108, 49, -18, 10, -6, 3, -2 },
+  { -2, 4, -7, 13, -25, 100, 60, -21, 11, -7, 4, -2 },
+  { -2, 4, -7, 13, -26, 91, 71, -24, 13, -7, 4, -2 },
+  { -2, 4, -7, 13, -25, 81, 81, -25, 13, -7, 4, -2 },
+  { -2, 4, -7, 13, -24, 71, 91, -26, 13, -7, 4, -2 },
+  { -2, 4, -7, 11, -21, 60, 100, -25, 13, -7, 4, -2 },
+  { -2, 3, -6, 10, -18, 49, 108, -24, 12, -6, 4, -2 },
+  { -1, 3, -5, 8, -15, 38, 115, -21, 10, -6, 3, -1 },
+  { -1, 2, -4, 7, -12, 28, 120, -18, 8, -4, 3, -1 },
+  { -1, 2, -2, 4, -8, 18, 124, -13, 6, -3, 2, -1 },
+  { 0, 1, -1, 2, -4, 8, 127, -7, 3, -2, 1, 0 }
+};
 
 void vp9_temporal_filter_init(void);
-void vp9_temporal_filter(VP9_COMP *cpi, int distance);
+void vp9_temporal_filter(struct VP9_COMP *cpi, int distance);
 
-void vp9_temporal_filter_iterate_row_c(VP9_COMP *cpi, ThreadData *td,
-                                       int mb_row, int mb_col_start,
-                                       int mb_col_end);
+void vp9_temporal_filter_iterate_row_c(struct VP9_COMP *cpi,
+                                       struct ThreadData *td, int mb_row,
+                                       int mb_col_start, int mb_col_end);
 
 #ifdef __cplusplus
 }  // extern "C"
diff --git a/vp9/encoder/vp9_tpl_model.c b/vp9/encoder/vp9_tpl_model.c
index c40d71e..f65c98f 100644
--- a/vp9/encoder/vp9_tpl_model.c
+++ b/vp9/encoder/vp9_tpl_model.c
@@ -78,7 +78,9 @@
       for (i = 0; i < 3; ++i) ref_table[i] = -REFS_PER_FRAME;
 
       gf_picture[0].frame =
-          &cm->buffer_pool->frame_bufs[gf_group->update_ref_idx[0]].buf;
+          &cm->buffer_pool
+               ->frame_bufs[cm->ref_frame_map[gf_group->update_ref_idx[0]]]
+               .buf;
       ref_table[gf_group->update_ref_idx[0]] = 0;
 
       for (i = 0; i < 3; ++i) gf_picture[0].ref_frame[i] = -REFS_PER_FRAME;
@@ -606,8 +608,9 @@
                                          TplDepStats *tpl_stats, int mi_row,
                                          int mi_col, BLOCK_SIZE bsize,
                                          int src_stride, int64_t recon_error,
-                                         int64_t rate_cost, int ref_frame_idx,
-                                         int mi_rows, int mi_cols) {
+                                         int64_t pred_error, int64_t rate_cost,
+                                         int ref_frame_idx, int mi_rows,
+                                         int mi_cols) {
   const int mi_height = num_8x8_blocks_high_lookup[bsize];
   const int mi_width = num_8x8_blocks_wide_lookup[bsize];
   const TplDepStats *src_stats = &tpl_stats[mi_row * src_stride + mi_col];
@@ -628,8 +631,11 @@
       tpl_block_stats_ptr->intra_pred_err = src_stats->intra_cost;
       tpl_block_stats_ptr->srcrf_dist = recon_error << TPL_DEP_COST_SCALE_LOG2;
       tpl_block_stats_ptr->srcrf_rate = rate_cost << TPL_DEP_COST_SCALE_LOG2;
-      tpl_block_stats_ptr->mv_r = src_stats->mv.as_mv.row;
-      tpl_block_stats_ptr->mv_c = src_stats->mv.as_mv.col;
+      tpl_block_stats_ptr->pred_error = pred_error << TPL_DEP_COST_SCALE_LOG2;
+      tpl_block_stats_ptr->mv_r = (src_stats->mv.as_mv.row >= 0 ? 1 : -1) *
+                                  (abs(src_stats->mv.as_mv.row) + 4) / 8;
+      tpl_block_stats_ptr->mv_c = (src_stats->mv.as_mv.col >= 0 ? 1 : -1) *
+                                  (abs(src_stats->mv.as_mv.col) + 4) / 8;
       tpl_block_stats_ptr->ref_frame_index = ref_frame_idx;
     }
   }
@@ -1469,7 +1475,7 @@
       tpl_store_before_propagation(
           tpl_frame_stats_before_propagation->block_stats_list,
           tpl_frame->tpl_stats_ptr, mi_row, mi_col, bsize, tpl_frame->stride,
-          recon_error, rate_cost, ref_frame_idx, tpl_frame->mi_rows,
+          recon_error, sse, rate_cost, ref_frame_idx, tpl_frame->mi_rows,
           tpl_frame->mi_cols);
 
       tpl_model_update(cpi->tpl_stats, tpl_frame->tpl_stats_ptr, mi_row, mi_col,
@@ -1671,49 +1677,8 @@
   free_tpl_frame_stats_list(&cpi->tpl_gop_stats);
 }
 
-#if CONFIG_RATE_CTRL
-static void accumulate_frame_tpl_stats(VP9_COMP *cpi) {
-  VP9_COMMON *const cm = &cpi->common;
-  const GF_GROUP *gf_group = &cpi->twopass.gf_group;
-  int show_frame_count = 0;
-  int frame_idx;
-  // Accumulate tpl stats for each frame in the current group of picture.
-  for (frame_idx = 1; frame_idx < gf_group->gf_group_size; ++frame_idx) {
-    TplDepFrame *tpl_frame = &cpi->tpl_stats[frame_idx];
-    TplDepStats *tpl_stats = tpl_frame->tpl_stats_ptr;
-    const int tpl_stride = tpl_frame->stride;
-    int64_t intra_cost_base = 0;
-    int64_t inter_cost_base = 0;
-    int64_t mc_dep_cost_base = 0;
-    int64_t mc_ref_cost_base = 0;
-    int64_t mc_flow_base = 0;
-    int row, col;
-
-    if (!tpl_frame->is_valid) continue;
-
-    for (row = 0; row < cm->mi_rows && tpl_frame->is_valid; ++row) {
-      for (col = 0; col < cm->mi_cols; ++col) {
-        TplDepStats *this_stats = &tpl_stats[row * tpl_stride + col];
-        intra_cost_base += this_stats->intra_cost;
-        inter_cost_base += this_stats->inter_cost;
-        mc_dep_cost_base += this_stats->mc_dep_cost;
-        mc_ref_cost_base += this_stats->mc_ref_cost;
-        mc_flow_base += this_stats->mc_flow;
-      }
-    }
-
-    cpi->tpl_stats_info[show_frame_count].intra_cost = intra_cost_base;
-    cpi->tpl_stats_info[show_frame_count].inter_cost = inter_cost_base;
-    cpi->tpl_stats_info[show_frame_count].mc_dep_cost = mc_dep_cost_base;
-    cpi->tpl_stats_info[show_frame_count].mc_ref_cost = mc_ref_cost_base;
-    cpi->tpl_stats_info[show_frame_count].mc_flow = mc_flow_base;
-
-    ++show_frame_count;
-  }
-}
-#endif  // CONFIG_RATE_CTRL
-
 void vp9_estimate_tpl_qp_gop(VP9_COMP *cpi) {
+  VP9_COMMON *cm = &cpi->common;
   int gop_length = cpi->twopass.gf_group.gf_group_size;
   int bottom_index, top_index;
   int idx;
@@ -1721,6 +1686,19 @@
   const int is_src_frame_alt_ref = cpi->rc.is_src_frame_alt_ref;
   const int refresh_frame_context = cpi->common.refresh_frame_context;
 
+  const int sb_size = num_8x8_blocks_wide_lookup[BLOCK_64X64] * MI_SIZE;
+  const int frame_height_sb = (cm->height + sb_size - 1) / sb_size;
+  const int frame_width_sb = (cm->width + sb_size - 1) / sb_size;
+
+  vpx_codec_err_t codec_status;
+  const GF_GROUP *gf_group = &cpi->twopass.gf_group;
+  vpx_rc_encodeframe_decision_t encode_frame_decision;
+
+  CHECK_MEM_ERROR(
+      &cm->error, encode_frame_decision.sb_params_list,
+      (sb_params *)vpx_malloc(frame_height_sb * frame_width_sb *
+                              sizeof(*encode_frame_decision.sb_params_list)));
+
   for (idx = gf_index; idx <= gop_length; ++idx) {
     TplDepFrame *tpl_frame = &cpi->tpl_stats[idx];
     int target_rate = cpi->twopass.gf_group.bit_allocation[idx];
@@ -1730,17 +1708,21 @@
     if (cpi->ext_ratectrl.ready &&
         (cpi->ext_ratectrl.funcs.rc_type & VPX_RC_QP) != 0 &&
         cpi->ext_ratectrl.funcs.get_encodeframe_decision != NULL) {
-      VP9_COMMON *cm = &cpi->common;
-      vpx_codec_err_t codec_status;
-      const GF_GROUP *gf_group = &cpi->twopass.gf_group;
-      vpx_rc_encodeframe_decision_t encode_frame_decision;
       if (idx == gop_length) break;
+      memset(encode_frame_decision.sb_params_list, 0,
+             sizeof(*encode_frame_decision.sb_params_list) * frame_height_sb *
+                 frame_width_sb);
       codec_status = vp9_extrc_get_encodeframe_decision(
           &cpi->ext_ratectrl, gf_group->index, &encode_frame_decision);
       if (codec_status != VPX_CODEC_OK) {
         vpx_internal_error(&cm->error, codec_status,
                            "vp9_extrc_get_encodeframe_decision() failed");
       }
+      for (int i = 0; i < frame_height_sb * frame_width_sb; ++i) {
+        cpi->sb_mul_scale[i] =
+            (((int64_t)encode_frame_decision.sb_params_list[i].rdmult * 256) /
+             (encode_frame_decision.rdmult + 1));
+      }
       tpl_frame->base_qindex = encode_frame_decision.q_index;
     } else {
       tpl_frame->base_qindex = vp9_rc_pick_q_and_bounds_two_pass(
@@ -1753,6 +1735,8 @@
   cpi->rc.is_src_frame_alt_ref = is_src_frame_alt_ref;
   cpi->common.refresh_frame_context = refresh_frame_context;
   vp9_configure_buffer_updates(cpi, gf_index);
+
+  vpx_free(encode_frame_decision.sb_params_list);
 }
 
 void vp9_setup_tpl_stats(VP9_COMP *cpi) {
@@ -1783,7 +1767,7 @@
   if (cpi->ext_ratectrl.ready &&
       cpi->ext_ratectrl.funcs.send_tpl_gop_stats != NULL) {
     // Intra search on key frame
-    if (gf_picture[0].update_type != OVERLAY_UPDATE) {
+    if (gf_group->update_type[0] != OVERLAY_UPDATE) {
       mc_flow_dispenser(cpi, gf_picture, 0, cpi->tpl_bsize);
     }
     // TPL stats has extra frames from next GOP. Trim those extra frames for
@@ -1804,10 +1788,4 @@
   dump_tpl_stats(cpi, tpl_group_frames, gf_group, gf_picture, cpi->tpl_bsize);
 #endif  // DUMP_TPL_STATS
 #endif  // CONFIG_NON_GREEDY_MV
-
-#if CONFIG_RATE_CTRL
-  if (cpi->oxcf.use_simple_encode_api) {
-    accumulate_frame_tpl_stats(cpi);
-  }
-#endif  // CONFIG_RATE_CTRL
 }
diff --git a/vp9/encoder/x86/highbd_temporal_filter_avx2.c b/vp9/encoder/x86/highbd_temporal_filter_avx2.c
new file mode 100644
index 0000000..418edd6
--- /dev/null
+++ b/vp9/encoder/x86/highbd_temporal_filter_avx2.c
@@ -0,0 +1,263 @@
+/*
+ *  Copyright (c) 2024 The WebM 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 <assert.h>
+#include <immintrin.h>
+
+#include "./vp9_rtcd.h"
+#include "./vpx_dsp_rtcd.h"
+#include "vp9/encoder/vp9_temporal_filter.h"
+
+static INLINE void highbd_shuffle_12tap_filter_avx2(const int16_t *filter,
+                                                    __m256i *f) {
+  const __m256i f_low =
+      _mm256_broadcastsi128_si256(_mm_loadu_si128((const __m128i *)filter));
+  const __m256i f_high = _mm256_broadcastsi128_si256(
+      _mm_loadl_epi64((const __m128i *)(filter + 8)));
+
+  f[0] = _mm256_shuffle_epi32(f_low, 0x00);
+  f[1] = _mm256_shuffle_epi32(f_low, 0x55);
+  f[2] = _mm256_shuffle_epi32(f_low, 0xaa);
+  f[3] = _mm256_shuffle_epi32(f_low, 0xff);
+  f[4] = _mm256_shuffle_epi32(f_high, 0x00);
+  f[5] = _mm256_shuffle_epi32(f_high, 0x55);
+}
+
+static INLINE __m256i highbd_convolve_12tap(const __m256i *s,
+                                            const __m256i *f) {
+  const __m256i res_0 = _mm256_madd_epi16(s[0], f[0]);
+  const __m256i res_1 = _mm256_madd_epi16(s[1], f[1]);
+  const __m256i res_2 = _mm256_madd_epi16(s[2], f[2]);
+  const __m256i res_3 = _mm256_madd_epi16(s[3], f[3]);
+  const __m256i res_4 = _mm256_madd_epi16(s[4], f[4]);
+  const __m256i res_5 = _mm256_madd_epi16(s[5], f[5]);
+
+  const __m256i res =
+      _mm256_add_epi32(_mm256_add_epi32(res_0, res_1),
+                       _mm256_add_epi32(_mm256_add_epi32(res_2, res_3),
+                                        _mm256_add_epi32(res_4, res_5)));
+  return res;
+}
+
+static INLINE void reuse_src_data_avx2(const __m256i *src, __m256i *des) {
+  des[0] = src[0];
+  des[1] = src[1];
+  des[2] = src[2];
+  des[3] = src[3];
+  des[4] = src[4];
+}
+
+void vpx_highbd_convolve12_horiz_avx2(const uint16_t *src, ptrdiff_t src_stride,
+                                      uint16_t *dst, ptrdiff_t dst_stride,
+                                      const InterpKernel12 *filter, int x0_q4,
+                                      int x_step_q4, int y0_q4, int y_step_q4,
+                                      int w, int h, int bd) {
+  assert(x_step_q4 == 16);
+  (void)y0_q4;
+  (void)x_step_q4;
+  (void)y_step_q4;
+  const uint16_t *src_ptr = src;
+  src_ptr -= MAX_FILTER_TAP / 2 - 1;
+  __m256i s[6], f[6];
+  const __m256i rounding = _mm256_set1_epi32(1 << (FILTER_BITS - 1));
+  const __m256i max = _mm256_set1_epi16((1 << bd) - 1);
+  highbd_shuffle_12tap_filter_avx2(filter[x0_q4], f);
+
+  for (int j = 0; j < w; j += 8) {
+    for (int i = 0; i < h; i += 2) {
+      // s00 s01 s02 s03 s04 s05 s06 s07 s08 s09 s010 s011 s012 s013 s014 s015
+      const __m256i row0 =
+          _mm256_loadu_si256((const __m256i *)&src_ptr[i * src_stride + j]);
+      // s10 s11 s12 s13 s14 s15 s16 s17 s18 s19 s110 s111 s112 s113 s114
+      // s115
+      const __m256i row1 = _mm256_loadu_si256(
+          (const __m256i *)&src_ptr[(i + 1) * src_stride + j]);
+      // s016 s017 s018 s019 s020 s021 s022 s023
+      const __m128i row0_16 =
+          _mm_loadu_si128((const __m128i *)&src_ptr[i * src_stride + j + 16]);
+      // s116 s117 s118 s119 s120 s121 s122 s123
+      const __m128i row1_16 = _mm_loadu_si128(
+          (const __m128i *)&src_ptr[(i + 1) * src_stride + j + 16]);
+
+      // s00 s01 s02 s03 s04 s05 s06 s07 | s10 s11 s12 s13 s14 s15 s16 s17
+      const __m256i r0 = _mm256_permute2x128_si256(row0, row1, 0x20);
+      // s08 s09 s010 s011 s012 s013 s014 s015 | s18 s19 s110 s111 s112 s113
+      // s114 s115
+      const __m256i r1 = _mm256_permute2x128_si256(row0, row1, 0x31);
+      // s016 s017 s018 s019 s020 s021 s022 s023 | s116 s117 s118 s119 s120 s121
+      // s122 s123
+      const __m256i r2 =
+          _mm256_inserti128_si256(_mm256_castsi128_si256(row0_16), row1_16, 1);
+
+      // even pixels
+      s[0] = r0;
+      s[1] = _mm256_alignr_epi8(r1, r0, 4);
+      s[2] = _mm256_alignr_epi8(r1, r0, 8);
+      s[3] = _mm256_alignr_epi8(r1, r0, 12);
+      s[4] = r1;
+      s[5] = _mm256_alignr_epi8(r2, r1, 4);
+
+      // 00 02 04 06 | 10 12 14 16
+      __m256i res_even = highbd_convolve_12tap(s, f);
+      res_even =
+          _mm256_srai_epi32(_mm256_add_epi32(res_even, rounding), FILTER_BITS);
+
+      // odd pixels
+      s[0] = _mm256_alignr_epi8(r1, r0, 2);
+      s[1] = _mm256_alignr_epi8(r1, r0, 6);
+      s[2] = _mm256_alignr_epi8(r1, r0, 10);
+      s[3] = _mm256_alignr_epi8(r1, r0, 14);
+      s[4] = _mm256_alignr_epi8(r2, r1, 2);
+      s[5] = _mm256_alignr_epi8(r2, r1, 6);
+
+      // 01 03 05 07 | 11 13 15 17
+      __m256i res_odd = highbd_convolve_12tap(s, f);
+      res_odd =
+          _mm256_srai_epi32(_mm256_add_epi32(res_odd, rounding), FILTER_BITS);
+
+      // 00 01 02 03 | 10 11 12 13
+      const __m256i res_0 = _mm256_unpacklo_epi32(res_even, res_odd);
+      // 04 05 06 07 | 14 15 16 17
+      const __m256i res_1 = _mm256_unpackhi_epi32(res_even, res_odd);
+      // 00 01 02 03 | 04 05 06 07 | 10 11 12 13 | 14 15 16 17
+      const __m256i res_2 = _mm256_packus_epi32(res_0, res_1);
+      const __m256i res = _mm256_min_epi16(res_2, max);
+      _mm_storeu_si128((__m128i *)&dst[i * dst_stride + j],
+                       _mm256_castsi256_si128(res));
+      if (i + 1 < h) {
+        _mm_storeu_si128((__m128i *)(&dst[(i + 1) * dst_stride + j]),
+                         _mm256_extractf128_si256(res, 1));
+      }
+    }
+  }
+}
+
+void vpx_highbd_convolve12_vert_avx2(const uint16_t *src, ptrdiff_t src_stride,
+                                     uint16_t *dst, ptrdiff_t dst_stride,
+                                     const InterpKernel12 *filter, int x0_q4,
+                                     int x_step_q4, int y0_q4, int y_step_q4,
+                                     int w, int h, int bd) {
+  assert(y_step_q4 == 16);
+  (void)x0_q4;
+  (void)x_step_q4;
+  (void)y_step_q4;
+  const uint16_t *src_ptr = src;
+  src_ptr -= src_stride * (MAX_FILTER_TAP / 2 - 1);
+  __m256i s[12], f[6];
+  const __m256i rounding = _mm256_set1_epi32(((1 << FILTER_BITS) >> 1));
+  const __m256i max = _mm256_set1_epi16((1 << bd) - 1);
+  highbd_shuffle_12tap_filter_avx2(filter[y0_q4], f);
+
+  for (int j = 0; j < w; j += 8) {
+    __m128i s0 =
+        _mm_loadu_si128((const __m128i *)(src_ptr + 0 * src_stride + j));
+    __m128i s1 =
+        _mm_loadu_si128((const __m128i *)(src_ptr + 1 * src_stride + j));
+    __m128i s2 =
+        _mm_loadu_si128((const __m128i *)(src_ptr + 2 * src_stride + j));
+    __m128i s3 =
+        _mm_loadu_si128((const __m128i *)(src_ptr + 3 * src_stride + j));
+    __m128i s4 =
+        _mm_loadu_si128((const __m128i *)(src_ptr + 4 * src_stride + j));
+    __m128i s5 =
+        _mm_loadu_si128((const __m128i *)(src_ptr + 5 * src_stride + j));
+    __m128i s6 =
+        _mm_loadu_si128((const __m128i *)(src_ptr + 6 * src_stride + j));
+    __m128i s7 =
+        _mm_loadu_si128((const __m128i *)(src_ptr + 7 * src_stride + j));
+    __m128i s8 =
+        _mm_loadu_si128((const __m128i *)(src_ptr + 8 * src_stride + j));
+    __m128i s9 =
+        _mm_loadu_si128((const __m128i *)(src_ptr + 9 * src_stride + j));
+    __m128i s10t =
+        _mm_loadu_si128((const __m128i *)(src_ptr + 10 * src_stride + j));
+
+    __m256i r01 = _mm256_inserti128_si256(_mm256_castsi128_si256(s0), s1, 1);
+    __m256i r12 = _mm256_inserti128_si256(_mm256_castsi128_si256(s1), s2, 1);
+    __m256i r23 = _mm256_inserti128_si256(_mm256_castsi128_si256(s2), s3, 1);
+    __m256i r34 = _mm256_inserti128_si256(_mm256_castsi128_si256(s3), s4, 1);
+    __m256i r45 = _mm256_inserti128_si256(_mm256_castsi128_si256(s4), s5, 1);
+    __m256i r56 = _mm256_inserti128_si256(_mm256_castsi128_si256(s5), s6, 1);
+    __m256i r67 = _mm256_inserti128_si256(_mm256_castsi128_si256(s6), s7, 1);
+    __m256i r78 = _mm256_inserti128_si256(_mm256_castsi128_si256(s7), s8, 1);
+    __m256i r89 = _mm256_inserti128_si256(_mm256_castsi128_si256(s8), s9, 1);
+    __m256i r910 = _mm256_inserti128_si256(_mm256_castsi128_si256(s9), s10t, 1);
+
+    s[0] = _mm256_unpacklo_epi16(r01, r12);
+    s[1] = _mm256_unpacklo_epi16(r23, r34);
+    s[2] = _mm256_unpacklo_epi16(r45, r56);
+    s[3] = _mm256_unpacklo_epi16(r67, r78);
+    s[4] = _mm256_unpacklo_epi16(r89, r910);
+
+    s[6] = _mm256_unpackhi_epi16(r01, r12);
+    s[7] = _mm256_unpackhi_epi16(r23, r34);
+    s[8] = _mm256_unpackhi_epi16(r45, r56);
+    s[9] = _mm256_unpackhi_epi16(r67, r78);
+    s[10] = _mm256_unpackhi_epi16(r89, r910);
+    for (int i = 0; i < h; i += 2) {
+      const __m128i s10 = _mm_loadu_si128(
+          (const __m128i *)(src_ptr + (i + 10) * src_stride + j));
+      const __m128i s11 = _mm_loadu_si128(
+          (const __m128i *)(src_ptr + (i + 11) * src_stride + j));
+      const __m128i s12 = _mm_loadu_si128(
+          (const __m128i *)(src_ptr + (i + 12) * src_stride + j));
+      __m256i r1011 =
+          _mm256_inserti128_si256(_mm256_castsi128_si256(s10), s11, 1);
+      __m256i r1112 =
+          _mm256_inserti128_si256(_mm256_castsi128_si256(s11), s12, 1);
+
+      s[5] = _mm256_unpacklo_epi16(r1011, r1112);
+      s[11] = _mm256_unpackhi_epi16(r1011, r1112);
+
+      // 00 01 02 03 | 10 11 12 13
+      const __m256i res_a = highbd_convolve_12tap(s, f);
+      __m256i res_a_round =
+          _mm256_srai_epi32(_mm256_add_epi32(res_a, rounding), FILTER_BITS);
+      // 04 05 06 07 | 14 15 16 17
+      const __m256i res_b = highbd_convolve_12tap(s + 6, f);
+      __m256i res_b_round =
+          _mm256_srai_epi32(_mm256_add_epi32(res_b, rounding), FILTER_BITS);
+
+      // 00 01 02 03 | 04 05 06 07 | 10 11 12 13 | 14 15 16 17
+      const __m256i res_0 = _mm256_packus_epi32(res_a_round, res_b_round);
+      const __m256i res = _mm256_min_epi16(res_0, max);
+      _mm_storeu_si128((__m128i *)&dst[i * dst_stride + j],
+                       _mm256_castsi256_si128(res));
+
+      _mm_storeu_si128((__m128i *)(&dst[(i + 1) * dst_stride + j]),
+                       _mm256_extractf128_si256(res, 1));
+
+      reuse_src_data_avx2(s + 1, s);
+      reuse_src_data_avx2(s + 7, s + 6);
+    }
+  }
+}
+
+void vpx_highbd_convolve12_avx2(const uint16_t *src, ptrdiff_t src_stride,
+                                uint16_t *dst, ptrdiff_t dst_stride,
+                                const InterpKernel12 *filter, int x0_q4,
+                                int x_step_q4, int y0_q4, int y_step_q4, int w,
+                                int h, int bd) {
+  assert(x_step_q4 == 16 && y_step_q4 == 16);
+  assert(h == 32 || h == 16 || h == 8);
+  assert(w == 32 || w == 16 || w == 8);
+  DECLARE_ALIGNED(32, uint16_t, temp[BW * (BH + MAX_FILTER_TAP - 1)]);
+  const int temp_stride = BW;
+  const int intermediate_height =
+      (((h - 1) * y_step_q4 + y0_q4) >> SUBPEL_BITS) + MAX_FILTER_TAP;
+
+  vpx_highbd_convolve12_horiz_avx2(src - src_stride * (MAX_FILTER_TAP / 2 - 1),
+                                   src_stride, temp, temp_stride, filter, x0_q4,
+                                   x_step_q4, y0_q4, y_step_q4, w,
+                                   intermediate_height, bd);
+  vpx_highbd_convolve12_vert_avx2(temp + temp_stride * (MAX_FILTER_TAP / 2 - 1),
+                                  temp_stride, dst, dst_stride, filter, x0_q4,
+                                  x_step_q4, y0_q4, y_step_q4, w, h, bd);
+}
diff --git a/vp9/encoder/x86/highbd_temporal_filter_ssse3.c b/vp9/encoder/x86/highbd_temporal_filter_ssse3.c
new file mode 100644
index 0000000..4540dca
--- /dev/null
+++ b/vp9/encoder/x86/highbd_temporal_filter_ssse3.c
@@ -0,0 +1,233 @@
+/*
+ *  Copyright (c) 2024 The WebM 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 <assert.h>
+#include <tmmintrin.h>  // SSSE3
+
+#include "./vp9_rtcd.h"
+#include "./vpx_dsp_rtcd.h"
+#include "vp9/encoder/vp9_temporal_filter.h"
+
+static INLINE void highbd_shuffle_12tap_filter_ssse3(const int16_t *filter,
+                                                     __m128i *f) {
+  const __m128i f_low = _mm_loadu_si128((const __m128i *)filter);
+  const __m128i f_high = _mm_loadl_epi64((const __m128i *)(filter + 8));
+
+  f[0] = _mm_shuffle_epi32(f_low, 0x00);
+  f[1] = _mm_shuffle_epi32(f_low, 0x55);
+  f[2] = _mm_shuffle_epi32(f_low, 0xaa);
+  f[3] = _mm_shuffle_epi32(f_low, 0xff);
+  f[4] = _mm_shuffle_epi32(f_high, 0x00);
+  f[5] = _mm_shuffle_epi32(f_high, 0x55);
+}
+
+static INLINE void unpacklo_src_ssse3(__m128i *a, __m128i *s) {
+  s[0] = _mm_unpacklo_epi16(a[0], a[1]);
+  s[1] = _mm_unpacklo_epi16(a[2], a[3]);
+  s[2] = _mm_unpacklo_epi16(a[4], a[5]);
+  s[3] = _mm_unpacklo_epi16(a[6], a[7]);
+  s[4] = _mm_unpacklo_epi16(a[8], a[9]);
+}
+
+static INLINE void unpackhi_src_ssse3(__m128i *a, __m128i *s) {
+  s[0] = _mm_unpackhi_epi16(a[0], a[1]);
+  s[1] = _mm_unpackhi_epi16(a[2], a[3]);
+  s[2] = _mm_unpackhi_epi16(a[4], a[5]);
+  s[3] = _mm_unpackhi_epi16(a[6], a[7]);
+  s[4] = _mm_unpackhi_epi16(a[8], a[9]);
+}
+
+static INLINE __m128i highbd_convolve_12tap(const __m128i *s,
+                                            const __m128i *f) {
+  const __m128i rounding = _mm_set1_epi32(1 << (FILTER_BITS - 1));
+  const __m128i res_0 = _mm_madd_epi16(s[0], f[0]);
+  const __m128i res_1 = _mm_madd_epi16(s[1], f[1]);
+  const __m128i res_2 = _mm_madd_epi16(s[2], f[2]);
+  const __m128i res_3 = _mm_madd_epi16(s[3], f[3]);
+  const __m128i res_4 = _mm_madd_epi16(s[4], f[4]);
+  const __m128i res_5 = _mm_madd_epi16(s[5], f[5]);
+
+  const __m128i res_6 = _mm_add_epi32(
+      _mm_add_epi32(res_0, res_1),
+      _mm_add_epi32(_mm_add_epi32(res_2, res_3), _mm_add_epi32(res_4, res_5)));
+  const __m128i res =
+      _mm_srai_epi32(_mm_add_epi32(res_6, rounding), FILTER_BITS);
+  return res;
+}
+
+static INLINE void reuse_src_data_ssse3(const __m128i *src, __m128i *des) {
+  des[0] = src[0];
+  des[1] = src[1];
+  des[2] = src[2];
+  des[3] = src[3];
+  des[4] = src[4];
+}
+
+void vpx_highbd_convolve12_horiz_ssse3(const uint16_t *src,
+                                       ptrdiff_t src_stride, uint16_t *dst,
+                                       ptrdiff_t dst_stride,
+                                       const InterpKernel12 *filter, int x0_q4,
+                                       int x_step_q4, int y0_q4, int y_step_q4,
+                                       int w, int h, int bd) {
+  assert(x_step_q4 == 16);
+  (void)y0_q4;
+  (void)x_step_q4;
+  (void)y_step_q4;
+  const uint16_t *src_ptr = src;
+  src_ptr -= MAX_FILTER_TAP / 2 - 1;
+  __m128i s[6], f[6];
+  const __m128i max = _mm_set1_epi16((1 << bd) - 1);
+  const __m128i min = _mm_setzero_si128();
+  highbd_shuffle_12tap_filter_ssse3(filter[x0_q4], f);
+
+  for (int j = 0; j < w; j += 8) {
+    for (int i = 0; i < h; i++) {
+      // s00 s01 s02 s03 s04 s05 s06 s07
+      const __m128i r0 =
+          _mm_loadu_si128((const __m128i *)&src_ptr[i * src_stride + j]);
+      // s08 s09 s010 s011 s012 s013 s014 s015
+      const __m128i r1 =
+          _mm_loadu_si128((const __m128i *)&src_ptr[i * src_stride + j + 8]);
+      // s016 s017 s018 s019 s020 s021 s022 s023
+      const __m128i r2 =
+          _mm_loadu_si128((const __m128i *)&src_ptr[i * src_stride + j + 16]);
+
+      // even pixels
+      s[0] = r0;
+      s[1] = _mm_alignr_epi8(r1, r0, 4);
+      s[2] = _mm_alignr_epi8(r1, r0, 8);
+      s[3] = _mm_alignr_epi8(r1, r0, 12);
+      s[4] = r1;
+      s[5] = _mm_alignr_epi8(r2, r1, 4);
+
+      // 00 02 04 06
+      __m128i res_even = highbd_convolve_12tap(s, f);
+
+      // odd pixels
+      s[0] = _mm_alignr_epi8(r1, r0, 2);
+      s[1] = _mm_alignr_epi8(r1, r0, 6);
+      s[2] = _mm_alignr_epi8(r1, r0, 10);
+      s[3] = _mm_alignr_epi8(r1, r0, 14);
+      s[4] = _mm_alignr_epi8(r2, r1, 2);
+      s[5] = _mm_alignr_epi8(r2, r1, 6);
+
+      // 01 03 05 07
+      __m128i res_odd = highbd_convolve_12tap(s, f);
+
+      // 00 01 02 03
+      const __m128i res_0 = _mm_unpacklo_epi32(res_even, res_odd);
+      // 04 05 06 07
+      const __m128i res_1 = _mm_unpackhi_epi32(res_even, res_odd);
+      // 00 01 02 03 | 04 05 06 07
+      const __m128i res_2 = _mm_packs_epi32(res_0, res_1);
+      const __m128i res = _mm_max_epi16(_mm_min_epi16(res_2, max), min);
+      _mm_storeu_si128((__m128i *)&dst[i * dst_stride + j], res);
+    }
+  }
+}
+
+void vpx_highbd_convolve12_vert_ssse3(const uint16_t *src, ptrdiff_t src_stride,
+                                      uint16_t *dst, ptrdiff_t dst_stride,
+                                      const InterpKernel12 *filter, int x0_q4,
+                                      int x_step_q4, int y0_q4, int y_step_q4,
+                                      int w, int h, int bd) {
+  assert(y_step_q4 == 16);
+  (void)x0_q4;
+  (void)x_step_q4;
+  (void)y_step_q4;
+  const uint16_t *src_ptr = src;
+  src_ptr -= src_stride * (MAX_FILTER_TAP / 2 - 1);
+  __m128i s[12], r[12], a[11], f[6];
+  const __m128i max = _mm_set1_epi16((1 << bd) - 1);
+  const __m128i min = _mm_setzero_si128();
+  highbd_shuffle_12tap_filter_ssse3(filter[y0_q4], f);
+
+  for (int j = 0; j < w; j += 8) {
+    a[0] = _mm_loadu_si128((const __m128i *)(src_ptr + 0 * src_stride + j));
+    a[1] = _mm_loadu_si128((const __m128i *)(src_ptr + 1 * src_stride + j));
+    a[2] = _mm_loadu_si128((const __m128i *)(src_ptr + 2 * src_stride + j));
+    a[3] = _mm_loadu_si128((const __m128i *)(src_ptr + 3 * src_stride + j));
+    a[4] = _mm_loadu_si128((const __m128i *)(src_ptr + 4 * src_stride + j));
+    a[5] = _mm_loadu_si128((const __m128i *)(src_ptr + 5 * src_stride + j));
+    a[6] = _mm_loadu_si128((const __m128i *)(src_ptr + 6 * src_stride + j));
+    a[7] = _mm_loadu_si128((const __m128i *)(src_ptr + 7 * src_stride + j));
+    a[8] = _mm_loadu_si128((const __m128i *)(src_ptr + 8 * src_stride + j));
+    a[9] = _mm_loadu_si128((const __m128i *)(src_ptr + 9 * src_stride + j));
+    a[10] = _mm_loadu_si128((const __m128i *)(src_ptr + 10 * src_stride + j));
+
+    // even row
+    unpacklo_src_ssse3(a, s);
+    unpackhi_src_ssse3(a, s + 6);
+    // odd row
+    unpacklo_src_ssse3(a + 1, r);
+    unpackhi_src_ssse3(a + 1, r + 6);
+
+    for (int i = 0; i < h; i += 2) {
+      const __m128i s0 = _mm_loadu_si128(
+          (const __m128i *)(src_ptr + (i + 10) * src_stride + j));
+      const __m128i s1 = _mm_loadu_si128(
+          (const __m128i *)(src_ptr + (i + 11) * src_stride + j));
+      const __m128i s2 = _mm_loadu_si128(
+          (const __m128i *)(src_ptr + (i + 12) * src_stride + j));
+
+      s[5] = _mm_unpacklo_epi16(s0, s1);
+      r[5] = _mm_unpacklo_epi16(s1, s2);
+
+      s[11] = _mm_unpackhi_epi16(s0, s1);
+      r[11] = _mm_unpackhi_epi16(s1, s2);
+
+      // 00 01 02 03
+      const __m128i res_a = highbd_convolve_12tap(s, f);
+      // 04 05 06 07
+      const __m128i res_b = highbd_convolve_12tap(s + 6, f);
+      // 10 11 12 13
+      const __m128i res_c = highbd_convolve_12tap(r, f);
+      // 14 15 16 17
+      const __m128i res_d = highbd_convolve_12tap(r + 6, f);
+
+      // 00 01 02 03 | 04 05 06 07
+      const __m128i res_0 = _mm_packs_epi32(res_a, res_b);
+      // 10 11 12 13 | 14 15 16 17
+      const __m128i res_1 = _mm_packs_epi32(res_c, res_d);
+      const __m128i res_r0 = _mm_max_epi16(_mm_min_epi16(res_0, max), min);
+      const __m128i res_r1 = _mm_max_epi16(_mm_min_epi16(res_1, max), min);
+
+      _mm_storeu_si128((__m128i *)&dst[i * dst_stride + j], res_r0);
+      _mm_storeu_si128((__m128i *)&dst[(i + 1) * dst_stride + j], res_r1);
+
+      reuse_src_data_ssse3(s + 1, s);
+      reuse_src_data_ssse3(s + 7, s + 6);
+      reuse_src_data_ssse3(r + 1, r);
+      reuse_src_data_ssse3(r + 7, r + 6);
+    }
+  }
+}
+
+void vpx_highbd_convolve12_ssse3(const uint16_t *src, ptrdiff_t src_stride,
+                                 uint16_t *dst, ptrdiff_t dst_stride,
+                                 const InterpKernel12 *filter, int x0_q4,
+                                 int x_step_q4, int y0_q4, int y_step_q4, int w,
+                                 int h, int bd) {
+  assert(x_step_q4 == 16 && y_step_q4 == 16);
+  assert(h == 32 || h == 16 || h == 8);
+  assert(w == 32 || w == 16 || w == 8);
+  DECLARE_ALIGNED(32, uint16_t, temp[BW * (BH + MAX_FILTER_TAP - 1)]);
+  const int temp_stride = BW;
+  const int intermediate_height =
+      (((h - 1) * y_step_q4 + y0_q4) >> SUBPEL_BITS) + MAX_FILTER_TAP;
+
+  vpx_highbd_convolve12_horiz_ssse3(src - src_stride * (MAX_FILTER_TAP / 2 - 1),
+                                    src_stride, temp, temp_stride, filter,
+                                    x0_q4, x_step_q4, y0_q4, y_step_q4, w,
+                                    intermediate_height, bd);
+  vpx_highbd_convolve12_vert_ssse3(
+      temp + temp_stride * (MAX_FILTER_TAP / 2 - 1), temp_stride, dst,
+      dst_stride, filter, x0_q4, x_step_q4, y0_q4, y_step_q4, w, h, bd);
+}
diff --git a/vp9/encoder/x86/temporal_filter_avx2.c b/vp9/encoder/x86/temporal_filter_avx2.c
new file mode 100644
index 0000000..9d94ff8
--- /dev/null
+++ b/vp9/encoder/x86/temporal_filter_avx2.c
@@ -0,0 +1,441 @@
+/*
+ *  Copyright (c) 2024 The WebM 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 <assert.h>
+#include <immintrin.h>
+
+#include "./vp9_rtcd.h"
+#include "vp9/encoder/vp9_temporal_filter.h"
+
+DECLARE_ALIGNED(32, static const uint8_t,
+                shuffle_src_mask1_avx2[32]) = { 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5,
+                                                6, 6, 7, 7, 8, 0, 1, 1, 2, 2, 3,
+                                                3, 4, 4, 5, 5, 6, 6, 7, 7, 8 };
+
+DECLARE_ALIGNED(32, static const uint8_t, shuffle_src_mask2_avx2[32]) = {
+  2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10,
+  2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10
+};
+
+DECLARE_ALIGNED(32, static const uint8_t, shuffle_src_mask3_avx2[32]) = {
+  4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12,
+  4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12
+};
+
+DECLARE_ALIGNED(32, static const uint8_t, shuffle_src_mask4_avx2[32]) = {
+  6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14,
+  6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14
+};
+
+static INLINE void shuffle_12tap_filter_avx2(const int16_t *filter,
+                                             __m256i *f) {
+  const __m256i f_low =
+      _mm256_broadcastsi128_si256(_mm_loadu_si128((const __m128i *)filter));
+  const __m256i f_high = _mm256_broadcastsi128_si256(
+      _mm_loadl_epi64((const __m128i *)(filter + 8)));
+
+  f[0] = _mm256_shuffle_epi8(f_low, _mm256_set1_epi16(0x0200u));
+  f[1] = _mm256_shuffle_epi8(f_low, _mm256_set1_epi16(0x0604u));
+  f[2] = _mm256_shuffle_epi8(f_low, _mm256_set1_epi16(0x0a08u));
+  f[3] = _mm256_shuffle_epi8(f_low, _mm256_set1_epi16(0x0e0cu));
+  f[4] = _mm256_shuffle_epi8(f_high, _mm256_set1_epi16(0x0200u));
+  f[5] = _mm256_shuffle_epi8(f_high, _mm256_set1_epi16(0x0604u));
+}
+
+static INLINE void shuffle_src_data_avx2(const __m256i *r1, const __m256i *r2,
+                                         const __m256i *f, __m256i *s) {
+  s[0] = _mm256_shuffle_epi8(*r1, f[0]);
+  s[1] = _mm256_shuffle_epi8(*r1, f[1]);
+  s[2] = _mm256_shuffle_epi8(*r1, f[2]);
+  s[3] = _mm256_shuffle_epi8(*r1, f[3]);
+  s[4] = _mm256_shuffle_epi8(*r2, f[0]);
+  s[5] = _mm256_shuffle_epi8(*r2, f[1]);
+}
+
+static INLINE void reuse_src_data_avx2(const __m256i *src, __m256i *des) {
+  des[0] = src[0];
+  des[1] = src[1];
+  des[2] = src[2];
+  des[3] = src[3];
+  des[4] = src[4];
+}
+
+static INLINE __m256i convolve12_16_avx2(const __m256i *s, const __m256i *f) {
+  // multiply 2 adjacent elements with the filter and add the result
+  const __m256i k_64 = _mm256_set1_epi16(1 << (FILTER_BITS - 1));
+  const __m256i x0 = _mm256_maddubs_epi16(s[0], f[0]);
+  const __m256i x1 = _mm256_maddubs_epi16(s[1], f[1]);
+  const __m256i x2 = _mm256_maddubs_epi16(s[2], f[2]);
+  const __m256i x3 = _mm256_maddubs_epi16(s[3], f[3]);
+  const __m256i x4 = _mm256_maddubs_epi16(s[4], f[4]);
+  const __m256i x5 = _mm256_maddubs_epi16(s[5], f[5]);
+  __m256i sum1, sum2, sum3;
+
+  sum1 = _mm256_add_epi16(x0, x2);
+  sum2 = _mm256_add_epi16(x3, x5);
+  sum3 = _mm256_add_epi16(x1, x4);
+  sum3 = _mm256_add_epi16(sum3, k_64);
+
+  const __m256i s0 = _mm256_cvtepi16_epi32(_mm256_castsi256_si128(sum1));
+  const __m256i s1 = _mm256_cvtepi16_epi32(_mm256_extracti128_si256(sum1, 1));
+  const __m256i s2 = _mm256_cvtepi16_epi32(_mm256_castsi256_si128(sum2));
+  const __m256i s3 = _mm256_cvtepi16_epi32(_mm256_extracti128_si256(sum2, 1));
+  const __m256i s4 = _mm256_cvtepi16_epi32(_mm256_castsi256_si128(sum3));
+  const __m256i s5 = _mm256_cvtepi16_epi32(_mm256_extracti128_si256(sum3, 1));
+
+  sum1 = _mm256_add_epi32(s0, s2);
+  sum2 = _mm256_add_epi32(s1, s3);
+  sum1 = _mm256_add_epi32(sum1, s4);
+  sum2 = _mm256_add_epi32(sum2, s5);
+
+  // round and shift by 7 bit each 32 bit
+  // 0 1 2 3 4 5 6 7
+  sum1 = _mm256_srai_epi32(sum1, FILTER_BITS);
+  // 8 9 10 11 12 13 14 15
+  sum2 = _mm256_srai_epi32(sum2, FILTER_BITS);
+
+  // 0 1 2 3 8 9 10 11 4 5 6 7 12 13 14 15
+  // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+  __m256i const res =
+      _mm256_permute4x64_epi64(_mm256_packus_epi32(sum1, sum2), 0xD8);
+  return res;
+}
+
+void vpx_convolve12_horiz_avx2(const uint8_t *src, ptrdiff_t src_stride,
+                               uint8_t *dst, ptrdiff_t dst_stride,
+                               const InterpKernel12 *filter, int x0_q4,
+                               int x_step_q4, int y0_q4, int y_step_q4, int w,
+                               int h) {
+  assert(x_step_q4 == 16);
+  assert(w == 32 || w == 16 || w == 8);
+  (void)y0_q4;
+  (void)x_step_q4;
+  (void)y_step_q4;
+  const uint8_t *src_ptr = src;
+  src_ptr -= MAX_FILTER_TAP / 2 - 1;
+  __m256i s[6], f[6], src_mask[4];
+
+  shuffle_12tap_filter_avx2(filter[x0_q4], f);
+  src_mask[0] = _mm256_load_si256((__m256i const *)shuffle_src_mask1_avx2);
+  src_mask[1] = _mm256_load_si256((__m256i const *)shuffle_src_mask2_avx2);
+  src_mask[2] = _mm256_load_si256((__m256i const *)shuffle_src_mask3_avx2);
+  src_mask[3] = _mm256_load_si256((__m256i const *)shuffle_src_mask4_avx2);
+  if (w == 8) {
+    for (int i = 0; i < h; i += 4) {
+      // s00 s01 s02 s03 s04 s05 s06 s07 s08 s09 s010 s011 s012 s013 s014 s015
+      const __m128i row0 =
+          _mm_loadu_si128((const __m128i *)&src_ptr[i * src_stride]);
+      // s08 s09 s010 s011 s012 s013 s014 s015 s016 s017 s018 s019 s020 s021
+      // s022 s023
+      const __m128i row0_8 =
+          _mm_loadu_si128((const __m128i *)&src_ptr[i * src_stride + 8]);
+      // s10 s11 s12 s13 s14 s15 s16 s17 s18 s19 s110 s111 s112 s113 s114 s115
+      const __m128i row1 =
+          _mm_loadu_si128((const __m128i *)&src_ptr[(i + 1) * src_stride]);
+      const __m128i row1_8 =
+          _mm_loadu_si128((const __m128i *)&src_ptr[(i + 1) * src_stride + 8]);
+      // s20 s21 s22 s23 s24 s25 s26 s27 s28 s29 s210 s211 s212 s213 s214 s215
+      const __m128i row2 =
+          _mm_loadu_si128((const __m128i *)&src_ptr[(i + 2) * src_stride]);
+      const __m128i row2_8 =
+          _mm_loadu_si128((const __m128i *)&src_ptr[(i + 2) * src_stride + 8]);
+      // s30 s31 s32 s33 s34 s35 s36 s37 s38 s39 s310 s311 s312 s313 s314 s115
+      const __m128i row3 =
+          _mm_loadu_si128((const __m128i *)&src_ptr[(i + 3) * src_stride]);
+      const __m128i row3_8 =
+          _mm_loadu_si128((const __m128i *)&src_ptr[(i + 3) * src_stride + 8]);
+      // s00 s01 s02 s03 s04 s05 s06 s07 s08 s09 s010 s011 s012 s013 s014 s015 |
+      // s10 s11 s12 s13 s14 s15 s16 s17 s18 s19 s110 s111 s112 s113 s114 s115
+      const __m256i row01 =
+          _mm256_inserti128_si256(_mm256_castsi128_si256(row0), row1, 1);
+      // s20 s21 s22 s23 s24 s25 s26 s27 s28 s29 s210 s211 s212 s213 s214 s215 |
+      // s30 s31 s32 s33 s34 s35 s36 s37 s38 s39 s310 s311 s312 s313 s314 s115
+      const __m256i row23 =
+          _mm256_inserti128_si256(_mm256_castsi128_si256(row2), row3, 1);
+      // s08 s09 s010 s011 s012 s013 s014 s015 s016 s017 s018 s019 s020 s021
+      // s022 s023 | s18 s19 s110 s111 s112 s113 s114 s115 s116 s117 s118 s119
+      // s120 s121 s122 s123
+      const __m256i row01_8 =
+          _mm256_inserti128_si256(_mm256_castsi128_si256(row0_8), row1_8, 1);
+      const __m256i row23_8 =
+          _mm256_inserti128_si256(_mm256_castsi128_si256(row2_8), row3_8, 1);
+
+      shuffle_src_data_avx2(&row01, &row01_8, src_mask, s);
+      const __m256i res_0 = convolve12_16_avx2(s, f);
+
+      shuffle_src_data_avx2(&row23, &row23_8, src_mask, s);
+      const __m256i res_1 = convolve12_16_avx2(s, f);
+
+      // 00 01 02 03 04 05 06 07 | 10 11 12 13 14 15 16 17 | 08 09 010 011 012
+      // 013 014 015 | 18 19 110 111 112 113 114 115
+      const __m256i res = _mm256_packus_epi16(res_0, res_1);
+      const __m128i res_lo = _mm256_castsi256_si128(res);
+      const __m128i res_hi = _mm256_extracti128_si256(res, 1);
+
+      _mm_storel_epi64((__m128i *)&dst[i * dst_stride], res_lo);
+      _mm_storel_epi64((__m128i *)&dst[(i + 1) * dst_stride], res_hi);
+      _mm_storel_epi64((__m128i *)&dst[(i + 2) * dst_stride],
+                       _mm_srli_si128(res_lo, 8));
+      _mm_storel_epi64((__m128i *)&dst[(i + 3) * dst_stride],
+                       _mm_srli_si128(res_hi, 8));
+    }
+  } else {
+    for (int j = 0; j < w; j += 16) {
+      for (int i = 0; i < h; i += 2) {
+        // s00 s01 s02 s03 s04 s05 s06 s07 s08 s09 s010 s011 s012 s013 s014 s015
+        const __m128i row0 =
+            _mm_loadu_si128((const __m128i *)&src_ptr[i * src_stride + j]);
+        // s016 s017 s018 s019 s020 s021 s022 s023 s024 s025 s026 s027 s028 s029
+        // s030 s031
+        const __m128i row0_16 =
+            _mm_loadu_si128((const __m128i *)&src_ptr[i * src_stride + j + 16]);
+        // s10 s11 s12 s13 s14 s15 s16 s17 s18 s19 s110 s111 s112 s113 s114
+        // s115
+        const __m128i row1 = _mm_loadu_si128(
+            (const __m128i *)&src_ptr[(i + 1) * src_stride + j]);
+        // s116 s117 s118 s119 s120 s121 s122 s123 s124 s125 s126 s127 s128
+        // s129 s130 s131
+        const __m128i row1_16 = _mm_loadu_si128(
+            (const __m128i *)&src_ptr[(i + 1) * src_stride + j + 16]);
+
+        // s00 s01 s02 s03 s04 s05 s06 s07 s08 s09 s010 s011 s012 s013 s014 s015
+        // | s10 s11 s12 s13 s14 s15 s16 s17 s18 s19 s110 s111 s112 s113 s114
+        // s115
+        const __m256i r0 =
+            _mm256_inserti128_si256(_mm256_castsi128_si256(row0), row1, 1);
+        // s016 s017 s018 s019 s020 s021 s022 s023 s024 s025 s026 s027 s028 s029
+        // s030 s031 | s116 s117 s118 s119 s120 s121 s122 s123 s124 s125 s126
+        // s127 s128 s129 s130 s131
+        const __m256i r2 = _mm256_inserti128_si256(
+            _mm256_castsi128_si256(row0_16), row1_16, 1);
+
+        // s08 s09 s010 s011 s012 s013 s014 s015 s016 s017 s018 s019 s020 s021
+        // s022 s023 | s18 s19 s110 s111 s112 s113 s114 s115 s116 s117 s118 s119
+        // s120 s121 s122 s123
+        const __m256i r1 = _mm256_alignr_epi8(r2, r0, 8);
+
+        shuffle_src_data_avx2(&r0, &r1, src_mask, s);
+        const __m256i res_0 = convolve12_16_avx2(s, f);
+
+        shuffle_src_data_avx2(&r1, &r2, src_mask, s);
+        const __m256i res_1 = convolve12_16_avx2(s, f);
+
+        const __m256i res = _mm256_packus_epi16(res_0, res_1);
+
+        _mm_storeu_si128((__m128i *)&dst[i * dst_stride + j],
+                         _mm256_castsi256_si128(res));
+        if (i + 1 < h) {
+          _mm_storeu_si128((__m128i *)&dst[(i + 1) * dst_stride + j],
+                           _mm256_extracti128_si256(res, 1));
+        }
+      }
+    }
+  }
+}
+
+void vpx_convolve12_vert_avx2(const uint8_t *src, ptrdiff_t src_stride,
+                              uint8_t *dst, ptrdiff_t dst_stride,
+                              const InterpKernel12 *filter, int x0_q4,
+                              int x_step_q4, int y0_q4, int y_step_q4, int w,
+                              int h) {
+  assert(y_step_q4 == 16);
+  assert(h == 32 || h == 16 || h == 8);
+  assert(w == 32 || w == 16 || w == 8);
+  (void)x0_q4;
+  (void)x_step_q4;
+  (void)y_step_q4;
+  const uint8_t *src_ptr = src;
+  src_ptr -= src_stride * (MAX_FILTER_TAP / 2 - 1);
+  __m256i s[12], f[6];
+
+  shuffle_12tap_filter_avx2(filter[y0_q4], f);
+  if (w == 8) {
+    const __m128i s0 =
+        _mm_loadl_epi64((const __m128i *)(src_ptr + 0 * src_stride));
+    const __m128i s1 =
+        _mm_loadl_epi64((const __m128i *)(src_ptr + 1 * src_stride));
+    const __m128i s2 =
+        _mm_loadl_epi64((const __m128i *)(src_ptr + 2 * src_stride));
+    const __m128i s3 =
+        _mm_loadl_epi64((const __m128i *)(src_ptr + 3 * src_stride));
+    const __m128i s4 =
+        _mm_loadl_epi64((const __m128i *)(src_ptr + 4 * src_stride));
+    const __m128i s5 =
+        _mm_loadl_epi64((const __m128i *)(src_ptr + 5 * src_stride));
+    const __m128i s6 =
+        _mm_loadl_epi64((const __m128i *)(src_ptr + 6 * src_stride));
+    const __m128i s7 =
+        _mm_loadl_epi64((const __m128i *)(src_ptr + 7 * src_stride));
+    const __m128i s8 =
+        _mm_loadl_epi64((const __m128i *)(src_ptr + 8 * src_stride));
+    const __m128i s9 =
+        _mm_loadl_epi64((const __m128i *)(src_ptr + 9 * src_stride));
+    const __m128i s10t =
+        _mm_loadl_epi64((const __m128i *)(src_ptr + 10 * src_stride));
+
+    const __m256i r01 =
+        _mm256_inserti128_si256(_mm256_castsi128_si256(s0), s1, 1);
+    const __m256i r12 =
+        _mm256_inserti128_si256(_mm256_castsi128_si256(s1), s2, 1);
+    const __m256i r23 =
+        _mm256_inserti128_si256(_mm256_castsi128_si256(s2), s3, 1);
+    const __m256i r34 =
+        _mm256_inserti128_si256(_mm256_castsi128_si256(s3), s4, 1);
+    const __m256i r45 =
+        _mm256_inserti128_si256(_mm256_castsi128_si256(s4), s5, 1);
+    const __m256i r56 =
+        _mm256_inserti128_si256(_mm256_castsi128_si256(s5), s6, 1);
+    const __m256i r67 =
+        _mm256_inserti128_si256(_mm256_castsi128_si256(s6), s7, 1);
+    const __m256i r78 =
+        _mm256_inserti128_si256(_mm256_castsi128_si256(s7), s8, 1);
+    const __m256i r89 =
+        _mm256_inserti128_si256(_mm256_castsi128_si256(s8), s9, 1);
+    const __m256i r910 =
+        _mm256_inserti128_si256(_mm256_castsi128_si256(s9), s10t, 1);
+
+    s[0] = _mm256_unpacklo_epi8(r01, r12);
+    s[1] = _mm256_unpacklo_epi8(r23, r34);
+    s[2] = _mm256_unpacklo_epi8(r45, r56);
+    s[3] = _mm256_unpacklo_epi8(r67, r78);
+    s[4] = _mm256_unpacklo_epi8(r89, r910);
+    for (int i = 0; i < h; i += 2) {
+      const __m128i s10 =
+          _mm_loadl_epi64((const __m128i *)(src_ptr + (i + 10) * src_stride));
+      const __m128i s11 =
+          _mm_loadl_epi64((const __m128i *)(src_ptr + (i + 11) * src_stride));
+      const __m128i s12 =
+          _mm_loadl_epi64((const __m128i *)(src_ptr + (i + 12) * src_stride));
+
+      const __m256i r1011 =
+          _mm256_inserti128_si256(_mm256_castsi128_si256(s10), s11, 1);
+      const __m256i r1112 =
+          _mm256_inserti128_si256(_mm256_castsi128_si256(s11), s12, 1);
+      s[5] = _mm256_unpacklo_epi8(r1011, r1112);
+      const __m256i res_0 = convolve12_16_avx2(s, f);
+
+      __m256i res = _mm256_packus_epi16(res_0, res_0);
+
+      _mm_storel_epi64((__m128i *)&dst[i * dst_stride],
+                       _mm256_castsi256_si128(res));
+      _mm_storel_epi64((__m128i *)&dst[(i + 1) * dst_stride],
+                       _mm256_extracti128_si256(res, 1));
+
+      reuse_src_data_avx2(s + 1, s);
+    }
+  } else {
+    for (int j = 0; j < w; j += 16) {
+      const __m128i s0 =
+          _mm_loadu_si128((const __m128i *)(src_ptr + 0 * src_stride + j));
+      const __m128i s1 =
+          _mm_loadu_si128((const __m128i *)(src_ptr + 1 * src_stride + j));
+      const __m128i s2 =
+          _mm_loadu_si128((const __m128i *)(src_ptr + 2 * src_stride + j));
+      const __m128i s3 =
+          _mm_loadu_si128((const __m128i *)(src_ptr + 3 * src_stride + j));
+      const __m128i s4 =
+          _mm_loadu_si128((const __m128i *)(src_ptr + 4 * src_stride + j));
+      const __m128i s5 =
+          _mm_loadu_si128((const __m128i *)(src_ptr + 5 * src_stride + j));
+      const __m128i s6 =
+          _mm_loadu_si128((const __m128i *)(src_ptr + 6 * src_stride + j));
+      const __m128i s7 =
+          _mm_loadu_si128((const __m128i *)(src_ptr + 7 * src_stride + j));
+      const __m128i s8 =
+          _mm_loadu_si128((const __m128i *)(src_ptr + 8 * src_stride + j));
+      const __m128i s9 =
+          _mm_loadu_si128((const __m128i *)(src_ptr + 9 * src_stride + j));
+      const __m128i s10t =
+          _mm_loadu_si128((const __m128i *)(src_ptr + 10 * src_stride + j));
+
+      const __m256i r01 =
+          _mm256_inserti128_si256(_mm256_castsi128_si256(s0), s1, 1);
+      const __m256i r12 =
+          _mm256_inserti128_si256(_mm256_castsi128_si256(s1), s2, 1);
+      const __m256i r23 =
+          _mm256_inserti128_si256(_mm256_castsi128_si256(s2), s3, 1);
+      const __m256i r34 =
+          _mm256_inserti128_si256(_mm256_castsi128_si256(s3), s4, 1);
+      const __m256i r45 =
+          _mm256_inserti128_si256(_mm256_castsi128_si256(s4), s5, 1);
+      const __m256i r56 =
+          _mm256_inserti128_si256(_mm256_castsi128_si256(s5), s6, 1);
+      const __m256i r67 =
+          _mm256_inserti128_si256(_mm256_castsi128_si256(s6), s7, 1);
+      const __m256i r78 =
+          _mm256_inserti128_si256(_mm256_castsi128_si256(s7), s8, 1);
+      const __m256i r89 =
+          _mm256_inserti128_si256(_mm256_castsi128_si256(s8), s9, 1);
+      const __m256i r910 =
+          _mm256_inserti128_si256(_mm256_castsi128_si256(s9), s10t, 1);
+
+      s[0] = _mm256_unpacklo_epi8(r01, r12);
+      s[1] = _mm256_unpacklo_epi8(r23, r34);
+      s[2] = _mm256_unpacklo_epi8(r45, r56);
+      s[3] = _mm256_unpacklo_epi8(r67, r78);
+      s[4] = _mm256_unpacklo_epi8(r89, r910);
+
+      s[6] = _mm256_unpackhi_epi8(r01, r12);
+      s[7] = _mm256_unpackhi_epi8(r23, r34);
+      s[8] = _mm256_unpackhi_epi8(r45, r56);
+      s[9] = _mm256_unpackhi_epi8(r67, r78);
+      s[10] = _mm256_unpackhi_epi8(r89, r910);
+      for (int i = 0; i < h; i += 2) {
+        const __m128i s10 = _mm_loadu_si128(
+            (const __m128i *)(src_ptr + (i + 10) * src_stride + j));
+        const __m128i s11 = _mm_loadu_si128(
+            (const __m128i *)(src_ptr + (i + 11) * src_stride + j));
+        const __m128i s12 = _mm_loadu_si128(
+            (const __m128i *)(src_ptr + (i + 12) * src_stride + j));
+
+        const __m256i r1011 =
+            _mm256_inserti128_si256(_mm256_castsi128_si256(s10), s11, 1);
+        const __m256i r1112 =
+            _mm256_inserti128_si256(_mm256_castsi128_si256(s11), s12, 1);
+
+        s[5] = _mm256_unpacklo_epi8(r1011, r1112);
+        s[11] = _mm256_unpackhi_epi8(r1011, r1112);
+
+        const __m256i res_0 = convolve12_16_avx2(s, f);
+        const __m256i res_1 = convolve12_16_avx2(s + 6, f);
+
+        __m256i res = _mm256_packus_epi16(res_0, res_1);
+
+        _mm_storeu_si128((__m128i *)&dst[i * dst_stride + j],
+                         _mm256_castsi256_si128(res));
+        _mm_storeu_si128((__m128i *)&dst[(i + 1) * dst_stride + j],
+                         _mm256_extracti128_si256(res, 1));
+
+        reuse_src_data_avx2(s + 1, s);
+        reuse_src_data_avx2(s + 7, s + 6);
+      }
+    }
+  }
+}
+
+void vpx_convolve12_avx2(const uint8_t *src, ptrdiff_t src_stride, uint8_t *dst,
+                         ptrdiff_t dst_stride, const InterpKernel12 *filter,
+                         int x0_q4, int x_step_q4, int y0_q4, int y_step_q4,
+                         int w, int h) {
+  assert(x_step_q4 == 16 && y_step_q4 == 16);
+  assert(h == 32 || h == 16 || h == 8);
+  assert(w == 32 || w == 16 || w == 8);
+  DECLARE_ALIGNED(32, uint8_t, temp[BW * (BH + MAX_FILTER_TAP - 1)]);
+  const int temp_stride = BW;
+  const int intermediate_height =
+      (((h - 1) * y_step_q4 + y0_q4) >> SUBPEL_BITS) + MAX_FILTER_TAP;
+  vpx_convolve12_horiz_avx2(src - src_stride * (MAX_FILTER_TAP / 2 - 1),
+                            src_stride, temp, temp_stride, filter, x0_q4,
+                            x_step_q4, y0_q4, y_step_q4, w,
+                            intermediate_height);
+  vpx_convolve12_vert_avx2(temp + temp_stride * (MAX_FILTER_TAP / 2 - 1),
+                           temp_stride, dst, dst_stride, filter, x0_q4,
+                           x_step_q4, y0_q4, y_step_q4, w, h);
+}
diff --git a/vp9/encoder/x86/temporal_filter_ssse3.c b/vp9/encoder/x86/temporal_filter_ssse3.c
new file mode 100644
index 0000000..abf0ae1
--- /dev/null
+++ b/vp9/encoder/x86/temporal_filter_ssse3.c
@@ -0,0 +1,279 @@
+/*
+ *  Copyright (c) 2024 The WebM 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 <assert.h>
+#include <tmmintrin.h>  // SSSE3
+
+#include "./vp9_rtcd.h"
+#include "./vpx_dsp_rtcd.h"
+#include "vp9/encoder/vp9_temporal_filter.h"
+
+DECLARE_ALIGNED(16, static const uint8_t,
+                shuffle_src_mask1_ssse3[32]) = { 0, 1, 1, 2, 2, 3, 3, 4,
+                                                 4, 5, 5, 6, 6, 7, 7, 8 };
+
+DECLARE_ALIGNED(16, static const uint8_t,
+                shuffle_src_mask2_ssse3[32]) = { 2, 3, 3, 4, 4, 5, 5, 6,
+                                                 6, 7, 7, 8, 8, 9, 9, 10 };
+
+DECLARE_ALIGNED(16, static const uint8_t,
+                shuffle_src_mask3_ssse3[32]) = { 4, 5, 5, 6,  6,  7,  7,  8,
+                                                 8, 9, 9, 10, 10, 11, 11, 12 };
+
+DECLARE_ALIGNED(16, static const uint8_t, shuffle_src_mask4_ssse3[32]) = {
+  6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14
+};
+
+static INLINE void sign_extend_16bit_to_32bit_ssse3(__m128i in, __m128i zero,
+                                                    __m128i *out_lo,
+                                                    __m128i *out_hi) {
+  const __m128i sign_bits = _mm_cmpgt_epi16(zero, in);
+  *out_lo = _mm_unpacklo_epi16(in, sign_bits);
+  *out_hi = _mm_unpackhi_epi16(in, sign_bits);
+}
+
+static INLINE void shuffle_12tap_filter_ssse3(const int16_t *filter,
+                                              __m128i *f) {
+  const __m128i f_low = _mm_loadu_si128((const __m128i *)filter);
+  const __m128i f_high = _mm_loadl_epi64((const __m128i *)(filter + 8));
+
+  f[0] = _mm_shuffle_epi8(f_low, _mm_set1_epi16(0x0200u));
+  f[1] = _mm_shuffle_epi8(f_low, _mm_set1_epi16(0x0604u));
+  f[2] = _mm_shuffle_epi8(f_low, _mm_set1_epi16(0x0a08u));
+  f[3] = _mm_shuffle_epi8(f_low, _mm_set1_epi16(0x0e0cu));
+  f[4] = _mm_shuffle_epi8(f_high, _mm_set1_epi16(0x0200u));
+  f[5] = _mm_shuffle_epi8(f_high, _mm_set1_epi16(0x0604u));
+}
+
+static INLINE void shuffle_src_data_ssse3(const __m128i *r1, const __m128i *r2,
+                                          const __m128i *f, __m128i *s) {
+  s[0] = _mm_shuffle_epi8(*r1, f[0]);
+  s[1] = _mm_shuffle_epi8(*r1, f[1]);
+  s[2] = _mm_shuffle_epi8(*r1, f[2]);
+  s[3] = _mm_shuffle_epi8(*r1, f[3]);
+  s[4] = _mm_shuffle_epi8(*r2, f[0]);
+  s[5] = _mm_shuffle_epi8(*r2, f[1]);
+}
+
+static INLINE void reuse_src_data_ssse3(const __m128i *src, __m128i *des) {
+  des[0] = src[0];
+  des[1] = src[1];
+  des[2] = src[2];
+  des[3] = src[3];
+  des[4] = src[4];
+}
+
+static INLINE __m128i convolve12_16_ssse3(const __m128i *const s,
+                                          const __m128i *const f) {
+  // multiply 2 adjacent elements with the filter and add the result
+  const __m128i k_64 = _mm_set1_epi16(1 << (FILTER_BITS - 1));
+  const __m128i x0 = _mm_maddubs_epi16(s[0], f[0]);
+  const __m128i x1 = _mm_maddubs_epi16(s[1], f[1]);
+  const __m128i x2 = _mm_maddubs_epi16(s[2], f[2]);
+  const __m128i x3 = _mm_maddubs_epi16(s[3], f[3]);
+  const __m128i x4 = _mm_maddubs_epi16(s[4], f[4]);
+  const __m128i x5 = _mm_maddubs_epi16(s[5], f[5]);
+  __m128i sum1, sum2, sum3, s0, s1, s2, s3, s4, s5;
+
+  sum1 = _mm_add_epi16(x0, x2);
+  sum2 = _mm_add_epi16(x3, x5);
+  sum3 = _mm_add_epi16(x1, x4);
+  sum3 = _mm_add_epi16(sum3, k_64);
+
+  sign_extend_16bit_to_32bit_ssse3(sum1, _mm_setzero_si128(), &s0, &s1);
+  sign_extend_16bit_to_32bit_ssse3(sum2, _mm_setzero_si128(), &s2, &s3);
+  sign_extend_16bit_to_32bit_ssse3(sum3, _mm_setzero_si128(), &s4, &s5);
+  sum1 = _mm_add_epi32(s0, s2);
+  sum2 = _mm_add_epi32(s1, s3);
+  sum1 = _mm_add_epi32(sum1, s4);
+  sum2 = _mm_add_epi32(sum2, s5);
+
+  // round and shift by 7 bit each 32 bit
+  // 0 1 2 3
+  sum1 = _mm_srai_epi32(sum1, FILTER_BITS);
+  // 4 5 6 7
+  sum2 = _mm_srai_epi32(sum2, FILTER_BITS);
+
+  // 0 1 2 3 4 5 6 7
+  __m128i const res = _mm_packs_epi32(sum1, sum2);
+  return res;
+}
+
+void vpx_convolve12_horiz_ssse3(const uint8_t *src, ptrdiff_t src_stride,
+                                uint8_t *dst, ptrdiff_t dst_stride,
+                                const InterpKernel12 *filter, int x0_q4,
+                                int x_step_q4, int y0_q4, int y_step_q4, int w,
+                                int h) {
+  assert(x_step_q4 == 16);
+  assert(w == 32 || w == 16 || w == 8);
+  (void)y0_q4;
+  (void)x_step_q4;
+  (void)y_step_q4;
+  const uint8_t *src_ptr = src;
+  src_ptr -= MAX_FILTER_TAP / 2 - 1;
+  __m128i s[6], f[6], src_mask[4];
+
+  shuffle_12tap_filter_ssse3(filter[x0_q4], f);
+  src_mask[0] = _mm_load_si128((__m128i const *)shuffle_src_mask1_ssse3);
+  src_mask[1] = _mm_load_si128((__m128i const *)shuffle_src_mask2_ssse3);
+  src_mask[2] = _mm_load_si128((__m128i const *)shuffle_src_mask3_ssse3);
+  src_mask[3] = _mm_load_si128((__m128i const *)shuffle_src_mask4_ssse3);
+  if (w == 8) {
+    for (int i = 0; i < h; i += 2) {
+      // s00 s01 s02 s03 s04 s05 s06 s07 s08 s09 s010 s011 s012 s013 s014 s015
+      const __m128i row0 =
+          _mm_loadu_si128((const __m128i *)&src_ptr[i * src_stride]);
+      // s08 s09 s010 s011 s012 s013 s014 s015 s016 s017 s018 s019 s020 s021
+      // s022 s023
+      const __m128i row0_8 =
+          _mm_loadu_si128((const __m128i *)&src_ptr[i * src_stride + 8]);
+      // s10 s11 s12 s13 s14 s15 s16 s17 s18 s19 s110 s111 s112 s113 s114 s115
+      const __m128i row1 =
+          _mm_loadu_si128((const __m128i *)&src_ptr[(i + 1) * src_stride]);
+      const __m128i row1_8 =
+          _mm_loadu_si128((const __m128i *)&src_ptr[(i + 1) * src_stride + 8]);
+
+      shuffle_src_data_ssse3(&row0, &row0_8, src_mask, s);
+      const __m128i res_0 = convolve12_16_ssse3(s, f);
+
+      shuffle_src_data_ssse3(&row1, &row1_8, src_mask, s);
+      const __m128i res_1 = convolve12_16_ssse3(s, f);
+
+      const __m128i res = _mm_packus_epi16(res_0, res_1);
+      _mm_storel_epi64((__m128i *)&dst[i * dst_stride], res);
+      _mm_storel_epi64((__m128i *)&dst[(i + 1) * dst_stride],
+                       _mm_srli_si128(res, 8));
+    }
+  } else {
+    for (int j = 0; j < w; j += 16) {
+      for (int i = 0; i < h; i++) {
+        // s00 s01 s02 s03 s04 s05 s06 s07 s08 s09 s010 s011 s012 s013 s014 s015
+        const __m128i r0 =
+            _mm_loadu_si128((const __m128i *)&src_ptr[i * src_stride + j]);
+        // s016 s017 s018 s019 s020 s021 s022 s023 s024 s025 s026 s027 s028 s029
+        // s030 s031
+        const __m128i r2 =
+            _mm_loadu_si128((const __m128i *)&src_ptr[i * src_stride + j + 16]);
+
+        // s08 s09 s010 s011 s012 s013 s014 s015 s016 s017 s018 s019 s020 s021
+        // s022 s023
+        const __m128i r1 = _mm_alignr_epi8(r2, r0, 8);
+
+        shuffle_src_data_ssse3(&r0, &r1, src_mask, s);
+        const __m128i res_0 = convolve12_16_ssse3(s, f);
+
+        shuffle_src_data_ssse3(&r1, &r2, src_mask, s);
+        const __m128i res_1 = convolve12_16_ssse3(s, f);
+
+        const __m128i res = _mm_packus_epi16(res_0, res_1);
+        _mm_storeu_si128((__m128i *)&dst[i * dst_stride + j], res);
+      }
+    }
+  }
+}
+
+void vpx_convolve12_vert_ssse3(const uint8_t *src, ptrdiff_t src_stride,
+                               uint8_t *dst, ptrdiff_t dst_stride,
+                               const InterpKernel12 *filter, int x0_q4,
+                               int x_step_q4, int y0_q4, int y_step_q4, int w,
+                               int h) {
+  assert(y_step_q4 == 16);
+  assert(h == 32 || h == 16 || h == 8);
+  assert(w == 32 || w == 16 || w == 8);
+  (void)x0_q4;
+  (void)x_step_q4;
+  (void)y_step_q4;
+  const uint8_t *src_ptr = src;
+  src_ptr -= src_stride * (MAX_FILTER_TAP / 2 - 1);
+  __m128i s[12], f[6];
+
+  shuffle_12tap_filter_ssse3(filter[y0_q4], f);
+  for (int j = 0; j < w; j += 8) {
+    const __m128i s0 =
+        _mm_loadl_epi64((const __m128i *)(src_ptr + 0 * src_stride + j));
+    const __m128i s1 =
+        _mm_loadl_epi64((const __m128i *)(src_ptr + 1 * src_stride + j));
+    const __m128i s2 =
+        _mm_loadl_epi64((const __m128i *)(src_ptr + 2 * src_stride + j));
+    const __m128i s3 =
+        _mm_loadl_epi64((const __m128i *)(src_ptr + 3 * src_stride + j));
+    const __m128i s4 =
+        _mm_loadl_epi64((const __m128i *)(src_ptr + 4 * src_stride + j));
+    const __m128i s5 =
+        _mm_loadl_epi64((const __m128i *)(src_ptr + 5 * src_stride + j));
+    const __m128i s6 =
+        _mm_loadl_epi64((const __m128i *)(src_ptr + 6 * src_stride + j));
+    const __m128i s7 =
+        _mm_loadl_epi64((const __m128i *)(src_ptr + 7 * src_stride + j));
+    const __m128i s8 =
+        _mm_loadl_epi64((const __m128i *)(src_ptr + 8 * src_stride + j));
+    const __m128i s9 =
+        _mm_loadl_epi64((const __m128i *)(src_ptr + 9 * src_stride + j));
+    const __m128i s10t =
+        _mm_loadl_epi64((const __m128i *)(src_ptr + 10 * src_stride + j));
+
+    // 00 10 01 11 02 12 03 13 04 14 05 15 06 16 07 17
+    s[0] = _mm_unpacklo_epi8(s0, s1);
+    s[1] = _mm_unpacklo_epi8(s2, s3);
+    s[2] = _mm_unpacklo_epi8(s4, s5);
+    s[3] = _mm_unpacklo_epi8(s6, s7);
+    s[4] = _mm_unpacklo_epi8(s8, s9);
+
+    s[6] = _mm_unpacklo_epi8(s1, s2);
+    s[7] = _mm_unpacklo_epi8(s3, s4);
+    s[8] = _mm_unpacklo_epi8(s5, s6);
+    s[9] = _mm_unpacklo_epi8(s7, s8);
+    s[10] = _mm_unpacklo_epi8(s9, s10t);
+    for (int i = 0; i < h; i += 2) {
+      const __m128i s10 = _mm_loadl_epi64(
+          (const __m128i *)(src_ptr + (i + 10) * src_stride + j));
+      const __m128i s11 = _mm_loadl_epi64(
+          (const __m128i *)(src_ptr + (i + 11) * src_stride + j));
+      const __m128i s12 = _mm_loadl_epi64(
+          (const __m128i *)(src_ptr + (i + 12) * src_stride + j));
+
+      s[5] = _mm_unpacklo_epi8(s10, s11);
+      s[11] = _mm_unpacklo_epi8(s11, s12);
+
+      const __m128i res_0 = convolve12_16_ssse3(s, f);
+      const __m128i res_1 = convolve12_16_ssse3(s + 6, f);
+
+      __m128i res = _mm_packus_epi16(res_0, res_1);
+
+      _mm_storel_epi64((__m128i *)&dst[i * dst_stride + j], res);
+      _mm_storel_epi64((__m128i *)&dst[(i + 1) * dst_stride + j],
+                       _mm_srli_si128(res, 8));
+
+      reuse_src_data_ssse3(s + 1, s);
+      reuse_src_data_ssse3(s + 7, s + 6);
+    }
+  }
+}
+
+void vpx_convolve12_ssse3(const uint8_t *src, ptrdiff_t src_stride,
+                          uint8_t *dst, ptrdiff_t dst_stride,
+                          const InterpKernel12 *filter, int x0_q4,
+                          int x_step_q4, int y0_q4, int y_step_q4, int w,
+                          int h) {
+  assert(x_step_q4 == 16 && y_step_q4 == 16);
+  assert(h == 32 || h == 16 || h == 8);
+  assert(w == 32 || w == 16 || w == 8);
+  DECLARE_ALIGNED(32, uint8_t, temp[BW * (BH + MAX_FILTER_TAP - 1)]);
+  const int temp_stride = BW;
+  const int intermediate_height =
+      (((h - 1) * y_step_q4 + y0_q4) >> SUBPEL_BITS) + MAX_FILTER_TAP;
+  vpx_convolve12_horiz_ssse3(src - src_stride * (MAX_FILTER_TAP / 2 - 1),
+                             src_stride, temp, temp_stride, filter, x0_q4,
+                             x_step_q4, y0_q4, y_step_q4, w,
+                             intermediate_height);
+  vpx_convolve12_vert_ssse3(temp + temp_stride * (MAX_FILTER_TAP / 2 - 1),
+                            temp_stride, dst, dst_stride, filter, x0_q4,
+                            x_step_q4, y0_q4, y_step_q4, w, h);
+}
diff --git a/vp9/simple_encode.cc b/vp9/simple_encode.cc
deleted file mode 100644
index 54b4f38..0000000
--- a/vp9/simple_encode.cc
+++ /dev/null
@@ -1,1345 +0,0 @@
-/*
- *  Copyright (c) 2019 The WebM 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 <stdio.h>
-#include <stdlib.h>
-
-#include <memory>
-#include <vector>
-
-#include "./ivfenc.h"
-#include "vp9/common/vp9_entropymode.h"
-#include "vp9/common/vp9_enums.h"
-#include "vp9/common/vp9_onyxc_int.h"
-#include "vp9/vp9_iface_common.h"
-#include "vp9/encoder/vp9_encoder.h"
-#include "vp9/encoder/vp9_firstpass.h"
-#include "vp9/simple_encode.h"
-#include "vp9/vp9_cx_iface.h"
-
-namespace vp9 {
-
-static int get_plane_height(vpx_img_fmt_t img_fmt, int frame_height,
-                            int plane) {
-  assert(plane < 3);
-  if (plane == 0) {
-    return frame_height;
-  }
-  switch (img_fmt) {
-    case VPX_IMG_FMT_I420:
-    case VPX_IMG_FMT_I440:
-    case VPX_IMG_FMT_YV12:
-    case VPX_IMG_FMT_I42016:
-    case VPX_IMG_FMT_I44016: return (frame_height + 1) >> 1;
-    default: return frame_height;
-  }
-}
-
-static int get_plane_width(vpx_img_fmt_t img_fmt, int frame_width, int plane) {
-  assert(plane < 3);
-  if (plane == 0) {
-    return frame_width;
-  }
-  switch (img_fmt) {
-    case VPX_IMG_FMT_I420:
-    case VPX_IMG_FMT_YV12:
-    case VPX_IMG_FMT_I422:
-    case VPX_IMG_FMT_I42016:
-    case VPX_IMG_FMT_I42216: return (frame_width + 1) >> 1;
-    default: return frame_width;
-  }
-}
-
-// TODO(angiebird): Merge this function with vpx_img_plane_width()
-static int img_plane_width(const vpx_image_t *img, int plane) {
-  if (plane > 0 && img->x_chroma_shift > 0)
-    return (img->d_w + 1) >> img->x_chroma_shift;
-  else
-    return img->d_w;
-}
-
-// TODO(angiebird): Merge this function with vpx_img_plane_height()
-static int img_plane_height(const vpx_image_t *img, int plane) {
-  if (plane > 0 && img->y_chroma_shift > 0)
-    return (img->d_h + 1) >> img->y_chroma_shift;
-  else
-    return img->d_h;
-}
-
-// TODO(angiebird): Merge this function with vpx_img_read()
-static int img_read(vpx_image_t *img, FILE *file) {
-  int plane;
-
-  for (plane = 0; plane < 3; ++plane) {
-    unsigned char *buf = img->planes[plane];
-    const int stride = img->stride[plane];
-    const int w = img_plane_width(img, plane) *
-                  ((img->fmt & VPX_IMG_FMT_HIGHBITDEPTH) ? 2 : 1);
-    const int h = img_plane_height(img, plane);
-    int y;
-
-    for (y = 0; y < h; ++y) {
-      if (fread(buf, 1, w, file) != (size_t)w) return 0;
-      buf += stride;
-    }
-  }
-
-  return 1;
-}
-
-// Assume every config in VP9EncoderConfig is less than 100 characters.
-#define ENCODE_CONFIG_BUF_SIZE 100
-struct EncodeConfig {
-  char name[ENCODE_CONFIG_BUF_SIZE];
-  char value[ENCODE_CONFIG_BUF_SIZE];
-};
-
-class SimpleEncode::EncodeImpl {
- public:
-  VP9_COMP *cpi;
-  vpx_img_fmt_t img_fmt;
-  vpx_image_t tmp_img;
-  std::vector<FIRSTPASS_STATS> first_pass_stats;
-  std::vector<EncodeConfig> encode_config_list;
-};
-
-static VP9_COMP *init_encoder(const VP9EncoderConfig *oxcf,
-                              vpx_img_fmt_t img_fmt) {
-  VP9_COMP *cpi;
-  BufferPool *buffer_pool = (BufferPool *)vpx_calloc(1, sizeof(*buffer_pool));
-  if (!buffer_pool) return NULL;
-  vp9_initialize_enc();
-  cpi = vp9_create_compressor(oxcf, buffer_pool);
-  vp9_update_compressor_with_img_fmt(cpi, img_fmt);
-  return cpi;
-}
-
-static void free_encoder(VP9_COMP *cpi) {
-  BufferPool *buffer_pool = cpi->common.buffer_pool;
-  vp9_remove_compressor(cpi);
-  // buffer_pool needs to be free after cpi because buffer_pool contains
-  // allocated buffers that will be free in vp9_remove_compressor()
-  vpx_free(buffer_pool);
-}
-
-static INLINE vpx_rational_t make_vpx_rational(int num, int den) {
-  vpx_rational_t v;
-  v.num = num;
-  v.den = den;
-  return v;
-}
-
-static INLINE FrameType
-get_frame_type_from_update_type(FRAME_UPDATE_TYPE update_type) {
-  switch (update_type) {
-    case KF_UPDATE: return kFrameTypeKey;
-    case ARF_UPDATE: return kFrameTypeAltRef;
-    case GF_UPDATE: return kFrameTypeGolden;
-    case OVERLAY_UPDATE: return kFrameTypeOverlay;
-    case LF_UPDATE: return kFrameTypeInter;
-    default:
-      fprintf(stderr, "Unsupported update_type %d\n", update_type);
-      abort();
-  }
-}
-
-static void update_partition_info(const PARTITION_INFO *input_partition_info,
-                                  const int num_rows_4x4,
-                                  const int num_cols_4x4,
-                                  PartitionInfo *output_partition_info) {
-  const int num_units_4x4 = num_rows_4x4 * num_cols_4x4;
-  for (int i = 0; i < num_units_4x4; ++i) {
-    output_partition_info[i].row = input_partition_info[i].row;
-    output_partition_info[i].column = input_partition_info[i].column;
-    output_partition_info[i].row_start = input_partition_info[i].row_start;
-    output_partition_info[i].column_start =
-        input_partition_info[i].column_start;
-    output_partition_info[i].width = input_partition_info[i].width;
-    output_partition_info[i].height = input_partition_info[i].height;
-  }
-}
-
-// translate MV_REFERENCE_FRAME to RefFrameType
-static RefFrameType mv_ref_frame_to_ref_frame_type(
-    MV_REFERENCE_FRAME mv_ref_frame) {
-  switch (mv_ref_frame) {
-    case LAST_FRAME: return kRefFrameTypeLast;
-    case GOLDEN_FRAME: return kRefFrameTypePast;
-    case ALTREF_FRAME: return kRefFrameTypeFuture;
-    default: return kRefFrameTypeNone;
-  }
-}
-
-static void update_motion_vector_info(
-    const MOTION_VECTOR_INFO *input_motion_vector_info, const int num_rows_4x4,
-    const int num_cols_4x4, MotionVectorInfo *output_motion_vector_info,
-    int motion_vector_scale) {
-  const int num_units_4x4 = num_rows_4x4 * num_cols_4x4;
-  for (int i = 0; i < num_units_4x4; ++i) {
-    const MV_REFERENCE_FRAME *in_ref_frame =
-        input_motion_vector_info[i].ref_frame;
-    output_motion_vector_info[i].mv_count =
-        (in_ref_frame[0] == INTRA_FRAME)
-            ? 0
-            : ((in_ref_frame[1] == NO_REF_FRAME) ? 1 : 2);
-    if (in_ref_frame[0] == NO_REF_FRAME) {
-      fprintf(stderr, "in_ref_frame[0] shouldn't be NO_REF_FRAME\n");
-      abort();
-    }
-    output_motion_vector_info[i].ref_frame[0] =
-        mv_ref_frame_to_ref_frame_type(in_ref_frame[0]);
-    output_motion_vector_info[i].ref_frame[1] =
-        mv_ref_frame_to_ref_frame_type(in_ref_frame[1]);
-    output_motion_vector_info[i].mv_row[0] =
-        (double)input_motion_vector_info[i].mv[0].as_mv.row /
-        motion_vector_scale;
-    output_motion_vector_info[i].mv_column[0] =
-        (double)input_motion_vector_info[i].mv[0].as_mv.col /
-        motion_vector_scale;
-    output_motion_vector_info[i].mv_row[1] =
-        (double)input_motion_vector_info[i].mv[1].as_mv.row /
-        motion_vector_scale;
-    output_motion_vector_info[i].mv_column[1] =
-        (double)input_motion_vector_info[i].mv[1].as_mv.col /
-        motion_vector_scale;
-  }
-}
-
-static void update_tpl_stats_info(const TplDepStats *input_tpl_stats_info,
-                                  const int show_frame_count,
-                                  TplStatsInfo *output_tpl_stats_info) {
-  int frame_idx;
-  for (frame_idx = 0; frame_idx < show_frame_count; ++frame_idx) {
-    output_tpl_stats_info[frame_idx].intra_cost =
-        input_tpl_stats_info[frame_idx].intra_cost;
-    output_tpl_stats_info[frame_idx].inter_cost =
-        input_tpl_stats_info[frame_idx].inter_cost;
-    output_tpl_stats_info[frame_idx].mc_flow =
-        input_tpl_stats_info[frame_idx].mc_flow;
-    output_tpl_stats_info[frame_idx].mc_dep_cost =
-        input_tpl_stats_info[frame_idx].mc_dep_cost;
-    output_tpl_stats_info[frame_idx].mc_ref_cost =
-        input_tpl_stats_info[frame_idx].mc_ref_cost;
-  }
-}
-
-static void update_frame_counts(const FRAME_COUNTS *input_counts,
-                                FrameCounts *output_counts) {
-  // Init array sizes.
-  output_counts->y_mode.resize(BLOCK_SIZE_GROUPS);
-  for (int i = 0; i < BLOCK_SIZE_GROUPS; ++i) {
-    output_counts->y_mode[i].resize(INTRA_MODES);
-  }
-
-  output_counts->uv_mode.resize(INTRA_MODES);
-  for (int i = 0; i < INTRA_MODES; ++i) {
-    output_counts->uv_mode[i].resize(INTRA_MODES);
-  }
-
-  output_counts->partition.resize(PARTITION_CONTEXTS);
-  for (int i = 0; i < PARTITION_CONTEXTS; ++i) {
-    output_counts->partition[i].resize(PARTITION_TYPES);
-  }
-
-  output_counts->coef.resize(TX_SIZES);
-  output_counts->eob_branch.resize(TX_SIZES);
-  for (int i = 0; i < TX_SIZES; ++i) {
-    output_counts->coef[i].resize(PLANE_TYPES);
-    output_counts->eob_branch[i].resize(PLANE_TYPES);
-    for (int j = 0; j < PLANE_TYPES; ++j) {
-      output_counts->coef[i][j].resize(REF_TYPES);
-      output_counts->eob_branch[i][j].resize(REF_TYPES);
-      for (int k = 0; k < REF_TYPES; ++k) {
-        output_counts->coef[i][j][k].resize(COEF_BANDS);
-        output_counts->eob_branch[i][j][k].resize(COEF_BANDS);
-        for (int l = 0; l < COEF_BANDS; ++l) {
-          output_counts->coef[i][j][k][l].resize(COEFF_CONTEXTS);
-          output_counts->eob_branch[i][j][k][l].resize(COEFF_CONTEXTS);
-          for (int m = 0; m < COEFF_CONTEXTS; ++m) {
-            output_counts->coef[i][j][k][l][m].resize(UNCONSTRAINED_NODES + 1);
-          }
-        }
-      }
-    }
-  }
-
-  output_counts->switchable_interp.resize(SWITCHABLE_FILTER_CONTEXTS);
-  for (int i = 0; i < SWITCHABLE_FILTER_CONTEXTS; ++i) {
-    output_counts->switchable_interp[i].resize(SWITCHABLE_FILTERS);
-  }
-
-  output_counts->inter_mode.resize(INTER_MODE_CONTEXTS);
-  for (int i = 0; i < INTER_MODE_CONTEXTS; ++i) {
-    output_counts->inter_mode[i].resize(INTER_MODES);
-  }
-
-  output_counts->intra_inter.resize(INTRA_INTER_CONTEXTS);
-  for (int i = 0; i < INTRA_INTER_CONTEXTS; ++i) {
-    output_counts->intra_inter[i].resize(2);
-  }
-
-  output_counts->comp_inter.resize(COMP_INTER_CONTEXTS);
-  for (int i = 0; i < COMP_INTER_CONTEXTS; ++i) {
-    output_counts->comp_inter[i].resize(2);
-  }
-
-  output_counts->single_ref.resize(REF_CONTEXTS);
-  for (int i = 0; i < REF_CONTEXTS; ++i) {
-    output_counts->single_ref[i].resize(2);
-    for (int j = 0; j < 2; ++j) {
-      output_counts->single_ref[i][j].resize(2);
-    }
-  }
-
-  output_counts->comp_ref.resize(REF_CONTEXTS);
-  for (int i = 0; i < REF_CONTEXTS; ++i) {
-    output_counts->comp_ref[i].resize(2);
-  }
-
-  output_counts->skip.resize(SKIP_CONTEXTS);
-  for (int i = 0; i < SKIP_CONTEXTS; ++i) {
-    output_counts->skip[i].resize(2);
-  }
-
-  output_counts->tx.p32x32.resize(TX_SIZE_CONTEXTS);
-  output_counts->tx.p16x16.resize(TX_SIZE_CONTEXTS);
-  output_counts->tx.p8x8.resize(TX_SIZE_CONTEXTS);
-  for (int i = 0; i < TX_SIZE_CONTEXTS; i++) {
-    output_counts->tx.p32x32[i].resize(TX_SIZES);
-    output_counts->tx.p16x16[i].resize(TX_SIZES - 1);
-    output_counts->tx.p8x8[i].resize(TX_SIZES - 2);
-  }
-  output_counts->tx.tx_totals.resize(TX_SIZES);
-
-  output_counts->mv.joints.resize(MV_JOINTS);
-  output_counts->mv.comps.resize(2);
-  for (int i = 0; i < 2; ++i) {
-    output_counts->mv.comps[i].sign.resize(2);
-    output_counts->mv.comps[i].classes.resize(MV_CLASSES);
-    output_counts->mv.comps[i].class0.resize(CLASS0_SIZE);
-    output_counts->mv.comps[i].bits.resize(MV_OFFSET_BITS);
-    for (int j = 0; j < MV_OFFSET_BITS; ++j) {
-      output_counts->mv.comps[i].bits[j].resize(2);
-    }
-    output_counts->mv.comps[i].class0_fp.resize(CLASS0_SIZE);
-    for (int j = 0; j < CLASS0_SIZE; ++j) {
-      output_counts->mv.comps[i].class0_fp[j].resize(MV_FP_SIZE);
-    }
-    output_counts->mv.comps[i].fp.resize(MV_FP_SIZE);
-    output_counts->mv.comps[i].class0_hp.resize(2);
-    output_counts->mv.comps[i].hp.resize(2);
-  }
-
-  // Populate counts.
-  for (int i = 0; i < BLOCK_SIZE_GROUPS; ++i) {
-    for (int j = 0; j < INTRA_MODES; ++j) {
-      output_counts->y_mode[i][j] = input_counts->y_mode[i][j];
-    }
-  }
-  for (int i = 0; i < INTRA_MODES; ++i) {
-    for (int j = 0; j < INTRA_MODES; ++j) {
-      output_counts->uv_mode[i][j] = input_counts->uv_mode[i][j];
-    }
-  }
-  for (int i = 0; i < PARTITION_CONTEXTS; ++i) {
-    for (int j = 0; j < PARTITION_TYPES; ++j) {
-      output_counts->partition[i][j] = input_counts->partition[i][j];
-    }
-  }
-  for (int i = 0; i < TX_SIZES; ++i) {
-    for (int j = 0; j < PLANE_TYPES; ++j) {
-      for (int k = 0; k < REF_TYPES; ++k) {
-        for (int l = 0; l < COEF_BANDS; ++l) {
-          for (int m = 0; m < COEFF_CONTEXTS; ++m) {
-            output_counts->eob_branch[i][j][k][l][m] =
-                input_counts->eob_branch[i][j][k][l][m];
-            for (int n = 0; n < UNCONSTRAINED_NODES + 1; n++) {
-              output_counts->coef[i][j][k][l][m][n] =
-                  input_counts->coef[i][j][k][l][m][n];
-            }
-          }
-        }
-      }
-    }
-  }
-  for (int i = 0; i < SWITCHABLE_FILTER_CONTEXTS; ++i) {
-    for (int j = 0; j < SWITCHABLE_FILTERS; ++j) {
-      output_counts->switchable_interp[i][j] =
-          input_counts->switchable_interp[i][j];
-    }
-  }
-  for (int i = 0; i < INTER_MODE_CONTEXTS; ++i) {
-    for (int j = 0; j < INTER_MODES; ++j) {
-      output_counts->inter_mode[i][j] = input_counts->inter_mode[i][j];
-    }
-  }
-  for (int i = 0; i < INTRA_INTER_CONTEXTS; ++i) {
-    for (int j = 0; j < 2; ++j) {
-      output_counts->intra_inter[i][j] = input_counts->intra_inter[i][j];
-    }
-  }
-  for (int i = 0; i < COMP_INTER_CONTEXTS; ++i) {
-    for (int j = 0; j < 2; ++j) {
-      output_counts->comp_inter[i][j] = input_counts->comp_inter[i][j];
-    }
-  }
-  for (int i = 0; i < REF_CONTEXTS; ++i) {
-    for (int j = 0; j < 2; ++j) {
-      for (int k = 0; k < 2; ++k) {
-        output_counts->single_ref[i][j][k] = input_counts->single_ref[i][j][k];
-      }
-    }
-  }
-  for (int i = 0; i < REF_CONTEXTS; ++i) {
-    for (int j = 0; j < 2; ++j) {
-      output_counts->comp_ref[i][j] = input_counts->comp_ref[i][j];
-    }
-  }
-  for (int i = 0; i < SKIP_CONTEXTS; ++i) {
-    for (int j = 0; j < 2; ++j) {
-      output_counts->skip[i][j] = input_counts->skip[i][j];
-    }
-  }
-  for (int i = 0; i < TX_SIZE_CONTEXTS; i++) {
-    for (int j = 0; j < TX_SIZES; j++) {
-      output_counts->tx.p32x32[i][j] = input_counts->tx.p32x32[i][j];
-    }
-    for (int j = 0; j < TX_SIZES - 1; j++) {
-      output_counts->tx.p16x16[i][j] = input_counts->tx.p16x16[i][j];
-    }
-    for (int j = 0; j < TX_SIZES - 2; j++) {
-      output_counts->tx.p8x8[i][j] = input_counts->tx.p8x8[i][j];
-    }
-  }
-  for (int i = 0; i < TX_SIZES; i++) {
-    output_counts->tx.tx_totals[i] = input_counts->tx.tx_totals[i];
-  }
-  for (int i = 0; i < MV_JOINTS; i++) {
-    output_counts->mv.joints[i] = input_counts->mv.joints[i];
-  }
-  for (int k = 0; k < 2; k++) {
-    const nmv_component_counts *const comps_t = &input_counts->mv.comps[k];
-    for (int i = 0; i < 2; i++) {
-      output_counts->mv.comps[k].sign[i] = comps_t->sign[i];
-      output_counts->mv.comps[k].class0_hp[i] = comps_t->class0_hp[i];
-      output_counts->mv.comps[k].hp[i] = comps_t->hp[i];
-    }
-    for (int i = 0; i < MV_CLASSES; i++) {
-      output_counts->mv.comps[k].classes[i] = comps_t->classes[i];
-    }
-    for (int i = 0; i < CLASS0_SIZE; i++) {
-      output_counts->mv.comps[k].class0[i] = comps_t->class0[i];
-      for (int j = 0; j < MV_FP_SIZE; j++) {
-        output_counts->mv.comps[k].class0_fp[i][j] = comps_t->class0_fp[i][j];
-      }
-    }
-    for (int i = 0; i < MV_OFFSET_BITS; i++) {
-      for (int j = 0; j < 2; j++) {
-        output_counts->mv.comps[k].bits[i][j] = comps_t->bits[i][j];
-      }
-    }
-    for (int i = 0; i < MV_FP_SIZE; i++) {
-      output_counts->mv.comps[k].fp[i] = comps_t->fp[i];
-    }
-  }
-}
-
-void output_image_buffer(const ImageBuffer &image_buffer, std::FILE *out_file) {
-  for (int plane = 0; plane < 3; ++plane) {
-    const int w = image_buffer.plane_width[plane];
-    const int h = image_buffer.plane_height[plane];
-    const uint8_t *buf = image_buffer.plane_buffer[plane].get();
-    fprintf(out_file, "%d %d\n", h, w);
-    for (int i = 0; i < w * h; ++i) {
-      fprintf(out_file, "%d ", (int)buf[i]);
-    }
-    fprintf(out_file, "\n");
-  }
-}
-
-static bool init_image_buffer(ImageBuffer *image_buffer, int frame_width,
-                              int frame_height, vpx_img_fmt_t img_fmt) {
-  for (int plane = 0; plane < 3; ++plane) {
-    const int w = get_plane_width(img_fmt, frame_width, plane);
-    const int h = get_plane_height(img_fmt, frame_height, plane);
-    image_buffer->plane_width[plane] = w;
-    image_buffer->plane_height[plane] = h;
-    image_buffer->plane_buffer[plane].reset(new (std::nothrow) uint8_t[w * h]);
-    if (image_buffer->plane_buffer[plane].get() == nullptr) {
-      return false;
-    }
-  }
-  return true;
-}
-
-static void ImageBuffer_to_IMAGE_BUFFER(const ImageBuffer &image_buffer,
-                                        IMAGE_BUFFER *image_buffer_c) {
-  image_buffer_c->allocated = 1;
-  for (int plane = 0; plane < 3; ++plane) {
-    image_buffer_c->plane_width[plane] = image_buffer.plane_width[plane];
-    image_buffer_c->plane_height[plane] = image_buffer.plane_height[plane];
-    image_buffer_c->plane_buffer[plane] =
-        image_buffer.plane_buffer[plane].get();
-  }
-}
-
-static size_t get_max_coding_data_byte_size(int frame_width, int frame_height) {
-  return frame_width * frame_height * 3;
-}
-
-static bool init_encode_frame_result(EncodeFrameResult *encode_frame_result,
-                                     int frame_width, int frame_height,
-                                     vpx_img_fmt_t img_fmt) {
-  const size_t max_coding_data_byte_size =
-      get_max_coding_data_byte_size(frame_width, frame_height);
-
-  encode_frame_result->coding_data.reset(
-      new (std::nothrow) uint8_t[max_coding_data_byte_size]);
-  encode_frame_result->max_coding_data_byte_size = max_coding_data_byte_size;
-
-  encode_frame_result->num_rows_4x4 = get_num_unit_4x4(frame_height);
-  encode_frame_result->num_cols_4x4 = get_num_unit_4x4(frame_width);
-  encode_frame_result->partition_info.resize(encode_frame_result->num_rows_4x4 *
-                                             encode_frame_result->num_cols_4x4);
-  encode_frame_result->motion_vector_info.resize(
-      encode_frame_result->num_rows_4x4 * encode_frame_result->num_cols_4x4);
-  encode_frame_result->tpl_stats_info.resize(MAX_LAG_BUFFERS);
-
-  if (encode_frame_result->coding_data.get() == nullptr) {
-    encode_frame_result->max_coding_data_byte_size = 0;
-    return false;
-  }
-  return init_image_buffer(&encode_frame_result->coded_frame, frame_width,
-                           frame_height, img_fmt);
-}
-
-static void encode_frame_result_update_rq_history(
-    const RATE_QINDEX_HISTORY *rq_history,
-    EncodeFrameResult *encode_frame_result) {
-  encode_frame_result->recode_count = rq_history->recode_count;
-  for (int i = 0; i < encode_frame_result->recode_count; ++i) {
-    const int q_index = rq_history->q_index_history[i];
-    const int rate = rq_history->rate_history[i];
-    encode_frame_result->q_index_history.push_back(q_index);
-    encode_frame_result->rate_history.push_back(rate);
-  }
-}
-
-static void update_encode_frame_result(
-    EncodeFrameResult *encode_frame_result, const int show_frame_count,
-    const ENCODE_FRAME_RESULT *encode_frame_info) {
-  encode_frame_result->coding_data_bit_size =
-      encode_frame_result->coding_data_byte_size * 8;
-  encode_frame_result->show_idx = encode_frame_info->show_idx;
-  encode_frame_result->coding_idx = encode_frame_info->frame_coding_index;
-  assert(kRefFrameTypeMax == MAX_INTER_REF_FRAMES);
-  for (int i = 0; i < kRefFrameTypeMax; ++i) {
-    encode_frame_result->ref_frame_info.coding_indexes[i] =
-        encode_frame_info->ref_frame_coding_indexes[i];
-    encode_frame_result->ref_frame_info.valid_list[i] =
-        encode_frame_info->ref_frame_valid_list[i];
-  }
-  encode_frame_result->frame_type =
-      get_frame_type_from_update_type(encode_frame_info->update_type);
-  encode_frame_result->psnr = encode_frame_info->psnr;
-  encode_frame_result->sse = encode_frame_info->sse;
-  encode_frame_result->quantize_index = encode_frame_info->quantize_index;
-  update_partition_info(encode_frame_info->partition_info,
-                        encode_frame_result->num_rows_4x4,
-                        encode_frame_result->num_cols_4x4,
-                        &encode_frame_result->partition_info[0]);
-  update_motion_vector_info(encode_frame_info->motion_vector_info,
-                            encode_frame_result->num_rows_4x4,
-                            encode_frame_result->num_cols_4x4,
-                            &encode_frame_result->motion_vector_info[0],
-                            kMotionVectorSubPixelPrecision);
-  update_frame_counts(&encode_frame_info->frame_counts,
-                      &encode_frame_result->frame_counts);
-  if (encode_frame_result->frame_type == kFrameTypeAltRef) {
-    update_tpl_stats_info(encode_frame_info->tpl_stats_info, show_frame_count,
-                          &encode_frame_result->tpl_stats_info[0]);
-  }
-  encode_frame_result_update_rq_history(&encode_frame_info->rq_history,
-                                        encode_frame_result);
-}
-
-static void IncreaseGroupOfPictureIndex(GroupOfPicture *group_of_picture) {
-  ++group_of_picture->next_encode_frame_index;
-}
-
-static int IsGroupOfPictureFinished(const GroupOfPicture &group_of_picture) {
-  return static_cast<size_t>(group_of_picture.next_encode_frame_index) ==
-         group_of_picture.encode_frame_list.size();
-}
-
-bool operator==(const RefFrameInfo &a, const RefFrameInfo &b) {
-  bool match = true;
-  for (int i = 0; i < kRefFrameTypeMax; ++i) {
-    match &= a.coding_indexes[i] == b.coding_indexes[i];
-    match &= a.valid_list[i] == b.valid_list[i];
-  }
-  return match;
-}
-
-static void InitRefFrameInfo(RefFrameInfo *ref_frame_info) {
-  for (int i = 0; i < kRefFrameTypeMax; ++i) {
-    ref_frame_info->coding_indexes[i] = -1;
-    ref_frame_info->valid_list[i] = 0;
-  }
-}
-
-// After finishing coding a frame, this function will update the coded frame
-// into the ref_frame_info based on the frame_type and the coding_index.
-static void PostUpdateRefFrameInfo(FrameType frame_type, int frame_coding_index,
-                                   RefFrameInfo *ref_frame_info) {
-  // This part is written based on the logics in vp9_configure_buffer_updates()
-  // and update_ref_frames()
-  int *ref_frame_coding_indexes = ref_frame_info->coding_indexes;
-  switch (frame_type) {
-    case kFrameTypeKey:
-      ref_frame_coding_indexes[kRefFrameTypeLast] = frame_coding_index;
-      ref_frame_coding_indexes[kRefFrameTypePast] = frame_coding_index;
-      ref_frame_coding_indexes[kRefFrameTypeFuture] = frame_coding_index;
-      break;
-    case kFrameTypeInter:
-      ref_frame_coding_indexes[kRefFrameTypeLast] = frame_coding_index;
-      break;
-    case kFrameTypeAltRef:
-      ref_frame_coding_indexes[kRefFrameTypeFuture] = frame_coding_index;
-      break;
-    case kFrameTypeOverlay:
-      // Reserve the past coding_index in the future slot. This logic is from
-      // update_ref_frames() with condition vp9_preserve_existing_gf() == 1
-      // TODO(angiebird): Invetegate why we need this.
-      ref_frame_coding_indexes[kRefFrameTypeFuture] =
-          ref_frame_coding_indexes[kRefFrameTypePast];
-      ref_frame_coding_indexes[kRefFrameTypePast] = frame_coding_index;
-      break;
-    case kFrameTypeGolden:
-      ref_frame_coding_indexes[kRefFrameTypePast] = frame_coding_index;
-      ref_frame_coding_indexes[kRefFrameTypeLast] = frame_coding_index;
-      break;
-  }
-
-  //  This part is written based on the logics in get_ref_frame_flags() but we
-  //  rename the flags alt, golden to future, past respectively. Mark
-  //  non-duplicated reference frames as valid. The priorities are
-  //  kRefFrameTypeLast > kRefFrameTypePast > kRefFrameTypeFuture.
-  const int last_index = ref_frame_coding_indexes[kRefFrameTypeLast];
-  const int past_index = ref_frame_coding_indexes[kRefFrameTypePast];
-  const int future_index = ref_frame_coding_indexes[kRefFrameTypeFuture];
-
-  int *ref_frame_valid_list = ref_frame_info->valid_list;
-  for (int ref_frame_idx = 0; ref_frame_idx < kRefFrameTypeMax;
-       ++ref_frame_idx) {
-    ref_frame_valid_list[ref_frame_idx] = 1;
-  }
-
-  if (past_index == last_index) {
-    ref_frame_valid_list[kRefFrameTypePast] = 0;
-  }
-
-  if (future_index == last_index) {
-    ref_frame_valid_list[kRefFrameTypeFuture] = 0;
-  }
-
-  if (future_index == past_index) {
-    ref_frame_valid_list[kRefFrameTypeFuture] = 0;
-  }
-}
-
-static void SetGroupOfPicture(int first_is_key_frame, int use_alt_ref,
-                              int coding_frame_count, int first_show_idx,
-                              int last_gop_use_alt_ref, int start_coding_index,
-                              const RefFrameInfo &start_ref_frame_info,
-                              GroupOfPicture *group_of_picture) {
-  // Clean up the state of previous group of picture.
-  group_of_picture->encode_frame_list.clear();
-  group_of_picture->next_encode_frame_index = 0;
-  group_of_picture->show_frame_count = coding_frame_count - use_alt_ref;
-  group_of_picture->start_show_index = first_show_idx;
-  group_of_picture->start_coding_index = start_coding_index;
-  group_of_picture->first_is_key_frame = first_is_key_frame;
-  group_of_picture->use_alt_ref = use_alt_ref;
-  group_of_picture->last_gop_use_alt_ref = last_gop_use_alt_ref;
-
-  // We need to make a copy of start reference frame info because we
-  // use it to simulate the ref frame update.
-  RefFrameInfo ref_frame_info = start_ref_frame_info;
-
-  {
-    // First frame in the group of pictures. It's either key frame or show inter
-    // frame.
-    EncodeFrameInfo encode_frame_info;
-    // Set frame_type
-    if (first_is_key_frame) {
-      encode_frame_info.frame_type = kFrameTypeKey;
-    } else {
-      if (last_gop_use_alt_ref) {
-        encode_frame_info.frame_type = kFrameTypeOverlay;
-      } else {
-        encode_frame_info.frame_type = kFrameTypeGolden;
-      }
-    }
-
-    encode_frame_info.show_idx = first_show_idx;
-    encode_frame_info.coding_index = start_coding_index;
-
-    encode_frame_info.ref_frame_info = ref_frame_info;
-    PostUpdateRefFrameInfo(encode_frame_info.frame_type,
-                           encode_frame_info.coding_index, &ref_frame_info);
-
-    group_of_picture->encode_frame_list.push_back(encode_frame_info);
-  }
-
-  const int show_frame_count = coding_frame_count - use_alt_ref;
-  if (use_alt_ref) {
-    // If there is alternate reference, it is always coded at the second place.
-    // Its show index (or timestamp) is at the last of this group
-    EncodeFrameInfo encode_frame_info;
-    encode_frame_info.frame_type = kFrameTypeAltRef;
-    encode_frame_info.show_idx = first_show_idx + show_frame_count;
-    encode_frame_info.coding_index = start_coding_index + 1;
-
-    encode_frame_info.ref_frame_info = ref_frame_info;
-    PostUpdateRefFrameInfo(encode_frame_info.frame_type,
-                           encode_frame_info.coding_index, &ref_frame_info);
-
-    group_of_picture->encode_frame_list.push_back(encode_frame_info);
-  }
-
-  // Encode the rest show inter frames.
-  for (int i = 1; i < show_frame_count; ++i) {
-    EncodeFrameInfo encode_frame_info;
-    encode_frame_info.frame_type = kFrameTypeInter;
-    encode_frame_info.show_idx = first_show_idx + i;
-    encode_frame_info.coding_index = start_coding_index + use_alt_ref + i;
-
-    encode_frame_info.ref_frame_info = ref_frame_info;
-    PostUpdateRefFrameInfo(encode_frame_info.frame_type,
-                           encode_frame_info.coding_index, &ref_frame_info);
-
-    group_of_picture->encode_frame_list.push_back(encode_frame_info);
-  }
-}
-
-// Gets group of picture information from VP9's decision, and update
-// |group_of_picture| accordingly.
-// This is called at the starting of encoding of each group of picture.
-static void UpdateGroupOfPicture(const VP9_COMP *cpi, int start_coding_index,
-                                 const RefFrameInfo &start_ref_frame_info,
-                                 GroupOfPicture *group_of_picture) {
-  int first_is_key_frame;
-  int use_alt_ref;
-  int coding_frame_count;
-  int first_show_idx;
-  int last_gop_use_alt_ref;
-  vp9_get_next_group_of_picture(cpi, &first_is_key_frame, &use_alt_ref,
-                                &coding_frame_count, &first_show_idx,
-                                &last_gop_use_alt_ref);
-  SetGroupOfPicture(first_is_key_frame, use_alt_ref, coding_frame_count,
-                    first_show_idx, last_gop_use_alt_ref, start_coding_index,
-                    start_ref_frame_info, group_of_picture);
-}
-
-#define SET_STRUCT_VALUE(config, structure, ret, field) \
-  do {                                                  \
-    if (strcmp(config.name, #field) == 0) {             \
-      structure->field = atoi(config.value);            \
-      ret = 1;                                          \
-    }                                                   \
-  } while (false)
-
-static void UpdateEncodeConfig(const EncodeConfig &config,
-                               VP9EncoderConfig *oxcf) {
-  int ret = 0;
-  SET_STRUCT_VALUE(config, oxcf, ret, key_freq);
-  SET_STRUCT_VALUE(config, oxcf, ret, two_pass_vbrmin_section);
-  SET_STRUCT_VALUE(config, oxcf, ret, two_pass_vbrmax_section);
-  SET_STRUCT_VALUE(config, oxcf, ret, under_shoot_pct);
-  SET_STRUCT_VALUE(config, oxcf, ret, over_shoot_pct);
-  SET_STRUCT_VALUE(config, oxcf, ret, max_threads);
-  SET_STRUCT_VALUE(config, oxcf, ret, frame_parallel_decoding_mode);
-  SET_STRUCT_VALUE(config, oxcf, ret, tile_columns);
-  SET_STRUCT_VALUE(config, oxcf, ret, arnr_max_frames);
-  SET_STRUCT_VALUE(config, oxcf, ret, arnr_strength);
-  SET_STRUCT_VALUE(config, oxcf, ret, lag_in_frames);
-  SET_STRUCT_VALUE(config, oxcf, ret, encode_breakout);
-  SET_STRUCT_VALUE(config, oxcf, ret, enable_tpl_model);
-  SET_STRUCT_VALUE(config, oxcf, ret, enable_auto_arf);
-  if (strcmp(config.name, "rc_mode") == 0) {
-    int rc_mode = atoi(config.value);
-    if (rc_mode >= VPX_VBR && rc_mode <= VPX_Q) {
-      oxcf->rc_mode = (enum vpx_rc_mode)rc_mode;
-      ret = 1;
-    } else {
-      fprintf(stderr, "Invalid rc_mode value: %d\n", rc_mode);
-    }
-  }
-  SET_STRUCT_VALUE(config, oxcf, ret, cq_level);
-  if (ret == 0) {
-    fprintf(stderr, "Ignored unsupported encode_config %s\n", config.name);
-  }
-}
-
-static VP9EncoderConfig GetEncodeConfig(
-    int frame_width, int frame_height, vpx_rational_t frame_rate,
-    int target_bitrate, int encode_speed, int target_level,
-    vpx_enc_pass enc_pass,
-    const std::vector<EncodeConfig> &encode_config_list) {
-  VP9EncoderConfig oxcf = vp9_get_encoder_config(
-      frame_width, frame_height, frame_rate, target_bitrate, encode_speed,
-      target_level, enc_pass);
-  for (const auto &config : encode_config_list) {
-    UpdateEncodeConfig(config, &oxcf);
-  }
-  if (enc_pass == VPX_RC_FIRST_PASS) {
-    oxcf.lag_in_frames = 0;
-  }
-  oxcf.use_simple_encode_api = 1;
-  return oxcf;
-}
-
-SimpleEncode::SimpleEncode(int frame_width, int frame_height,
-                           int frame_rate_num, int frame_rate_den,
-                           int target_bitrate, int num_frames, int target_level,
-                           const char *infile_path, const char *outfile_path) {
-  impl_ptr_ = std::unique_ptr<EncodeImpl>(new EncodeImpl());
-  frame_width_ = frame_width;
-  frame_height_ = frame_height;
-  frame_rate_num_ = frame_rate_num;
-  frame_rate_den_ = frame_rate_den;
-  target_bitrate_ = target_bitrate;
-  num_frames_ = num_frames;
-  encode_speed_ = 0;
-  target_level_ = target_level;
-
-  frame_coding_index_ = 0;
-  show_frame_count_ = 0;
-
-  key_frame_group_index_ = 0;
-  key_frame_group_size_ = 0;
-
-  // TODO(angirbid): Should we keep a file pointer here or keep the file_path?
-  assert(infile_path != nullptr);
-  in_file_ = fopen(infile_path, "r");
-  if (outfile_path != nullptr) {
-    out_file_ = fopen(outfile_path, "w");
-  } else {
-    out_file_ = nullptr;
-  }
-  impl_ptr_->cpi = nullptr;
-  impl_ptr_->img_fmt = VPX_IMG_FMT_I420;
-
-  InitRefFrameInfo(&ref_frame_info_);
-}
-
-void SimpleEncode::SetEncodeSpeed(int encode_speed) {
-  encode_speed_ = encode_speed;
-}
-
-StatusCode SimpleEncode::SetEncodeConfig(const char *name, const char *value) {
-  if (name == nullptr || value == nullptr) {
-    fprintf(stderr, "SetEncodeConfig: null pointer, name %p value %p\n", name,
-            value);
-    return StatusError;
-  }
-  EncodeConfig config;
-  snprintf(config.name, ENCODE_CONFIG_BUF_SIZE, "%s", name);
-  snprintf(config.value, ENCODE_CONFIG_BUF_SIZE, "%s", value);
-  impl_ptr_->encode_config_list.push_back(config);
-  return StatusOk;
-}
-
-StatusCode SimpleEncode::DumpEncodeConfigs(int pass, FILE *fp) {
-  if (fp == nullptr) {
-    fprintf(stderr, "DumpEncodeConfigs: null pointer, fp %p\n", fp);
-    return StatusError;
-  }
-  vpx_enc_pass enc_pass;
-  if (pass == 1) {
-    enc_pass = VPX_RC_FIRST_PASS;
-  } else {
-    enc_pass = VPX_RC_LAST_PASS;
-  }
-  const vpx_rational_t frame_rate =
-      make_vpx_rational(frame_rate_num_, frame_rate_den_);
-  const VP9EncoderConfig oxcf = GetEncodeConfig(
-      frame_width_, frame_height_, frame_rate, target_bitrate_, encode_speed_,
-      target_level_, enc_pass, impl_ptr_->encode_config_list);
-  vp9_dump_encoder_config(&oxcf, fp);
-  return StatusOk;
-}
-
-void SimpleEncode::ComputeFirstPassStats() {
-  vpx_rational_t frame_rate =
-      make_vpx_rational(frame_rate_num_, frame_rate_den_);
-  const VP9EncoderConfig oxcf = GetEncodeConfig(
-      frame_width_, frame_height_, frame_rate, target_bitrate_, encode_speed_,
-      target_level_, VPX_RC_FIRST_PASS, impl_ptr_->encode_config_list);
-  impl_ptr_->cpi = init_encoder(&oxcf, impl_ptr_->img_fmt);
-  struct lookahead_ctx *lookahead = impl_ptr_->cpi->lookahead;
-  int i;
-  int use_highbitdepth = 0;
-  const int num_rows_16x16 = get_num_unit_16x16(frame_height_);
-  const int num_cols_16x16 = get_num_unit_16x16(frame_width_);
-#if CONFIG_VP9_HIGHBITDEPTH
-  use_highbitdepth = impl_ptr_->cpi->common.use_highbitdepth;
-#endif
-  vpx_image_t img;
-  if (impl_ptr_->img_fmt == VPX_IMG_FMT_NV12) {
-    fprintf(stderr, "VPX_IMG_FMT_NV12 is not supported\n");
-    abort();
-  }
-  vpx_img_alloc(&img, impl_ptr_->img_fmt, frame_width_, frame_height_, 1);
-  rewind(in_file_);
-  impl_ptr_->first_pass_stats.clear();
-  for (i = 0; i < num_frames_; ++i) {
-    assert(!vp9_lookahead_full(lookahead));
-    if (img_read(&img, in_file_)) {
-      int next_show_idx = vp9_lookahead_next_show_idx(lookahead);
-      int64_t ts_start =
-          timebase_units_to_ticks(&oxcf.g_timebase_in_ts, next_show_idx);
-      int64_t ts_end =
-          timebase_units_to_ticks(&oxcf.g_timebase_in_ts, next_show_idx + 1);
-      YV12_BUFFER_CONFIG sd;
-      image2yuvconfig(&img, &sd);
-      vp9_lookahead_push(lookahead, &sd, ts_start, ts_end, use_highbitdepth, 0);
-      {
-        int64_t time_stamp;
-        int64_t time_end;
-        int flush = 1;  // Makes vp9_get_compressed_data process a frame
-        size_t size;
-        unsigned int frame_flags = 0;
-        ENCODE_FRAME_RESULT encode_frame_info;
-        vp9_init_encode_frame_result(&encode_frame_info);
-        // TODO(angiebird): Call vp9_first_pass directly
-        vp9_get_compressed_data(impl_ptr_->cpi, &frame_flags, &size, nullptr, 0,
-                                &time_stamp, &time_end, flush,
-                                &encode_frame_info);
-        // vp9_get_compressed_data only generates first pass stats not
-        // compresses data
-        assert(size == 0);
-        // Get vp9 first pass motion vector info.
-        std::vector<MotionVectorInfo> mv_info(num_rows_16x16 * num_cols_16x16);
-        update_motion_vector_info(
-            impl_ptr_->cpi->fp_motion_vector_info, num_rows_16x16,
-            num_cols_16x16, mv_info.data(), kMotionVectorFullPixelPrecision);
-        fp_motion_vector_info_.push_back(mv_info);
-      }
-      impl_ptr_->first_pass_stats.push_back(
-          vp9_get_frame_stats(&impl_ptr_->cpi->twopass));
-    }
-  }
-  // TODO(angiebird): Store the total_stats apart form first_pass_stats
-  impl_ptr_->first_pass_stats.push_back(
-      vp9_get_total_stats(&impl_ptr_->cpi->twopass));
-  vp9_end_first_pass(impl_ptr_->cpi);
-
-  // Generate key_frame_map based on impl_ptr_->first_pass_stats.
-  key_frame_map_ = ComputeKeyFrameMap();
-
-  free_encoder(impl_ptr_->cpi);
-  impl_ptr_->cpi = nullptr;
-  rewind(in_file_);
-  vpx_img_free(&img);
-}
-
-std::vector<std::vector<double>> SimpleEncode::ObserveFirstPassStats() {
-  std::vector<std::vector<double>> output_stats;
-  // TODO(angiebird): This function make several assumptions of
-  // FIRSTPASS_STATS. 1) All elements in FIRSTPASS_STATS are double except the
-  // last one. 2) The last entry of first_pass_stats is the total_stats.
-  // Change the code structure, so that we don't have to make these assumptions
-
-  // Note the last entry of first_pass_stats is the total_stats, we don't need
-  // it.
-  for (size_t i = 0; i < impl_ptr_->first_pass_stats.size() - 1; ++i) {
-    double *buf_start =
-        reinterpret_cast<double *>(&impl_ptr_->first_pass_stats[i]);
-    // We use - 1 here because the last member in FIRSTPASS_STATS is not double
-    double *buf_end =
-        buf_start + sizeof(impl_ptr_->first_pass_stats[i]) / sizeof(*buf_end) -
-        1;
-    std::vector<double> this_stats(buf_start, buf_end);
-    output_stats.push_back(this_stats);
-  }
-  return output_stats;
-}
-
-std::vector<std::vector<MotionVectorInfo>>
-SimpleEncode::ObserveFirstPassMotionVectors() {
-  return fp_motion_vector_info_;
-}
-
-void SimpleEncode::SetExternalGroupOfPicturesMap(int *gop_map,
-                                                 int gop_map_size) {
-  for (int i = 0; i < gop_map_size; ++i) {
-    gop_map_.push_back(gop_map[i]);
-  }
-  // The following will check and modify gop_map_ to make sure the
-  // gop_map_ satisfies the constraints.
-  // 1) Each key frame position should be at the start of a gop.
-  // 2) The last gop should not use an alt ref.
-  assert(gop_map_.size() == key_frame_map_.size());
-  int last_gop_start = 0;
-  for (int i = 0; static_cast<size_t>(i) < gop_map_.size(); ++i) {
-    if (key_frame_map_[i] == 1 && gop_map_[i] == 0) {
-      fprintf(stderr, "Add an extra gop start at show_idx %d\n", i);
-      // Insert a gop start at key frame location.
-      gop_map_[i] |= kGopMapFlagStart;
-      gop_map_[i] |= kGopMapFlagUseAltRef;
-    }
-    if (gop_map_[i] & kGopMapFlagStart) {
-      last_gop_start = i;
-    }
-  }
-  if (gop_map_[last_gop_start] & kGopMapFlagUseAltRef) {
-    fprintf(stderr,
-            "Last group of pictures starting at show_idx %d shouldn't use alt "
-            "ref\n",
-            last_gop_start);
-    gop_map_[last_gop_start] &= ~kGopMapFlagUseAltRef;
-  }
-}
-
-std::vector<int> SimpleEncode::ObserveExternalGroupOfPicturesMap() {
-  return gop_map_;
-}
-
-template <typename T>
-T *GetVectorData(const std::vector<T> &v) {
-  if (v.empty()) {
-    return nullptr;
-  }
-  return const_cast<T *>(v.data());
-}
-
-static GOP_COMMAND GetGopCommand(const std::vector<int> &gop_map,
-                                 int start_show_index) {
-  GOP_COMMAND gop_command;
-  if (static_cast<size_t>(start_show_index) < gop_map.size()) {
-    assert((gop_map[start_show_index] & kGopMapFlagStart) != 0);
-    int end_show_index = start_show_index + 1;
-    // gop_map[end_show_index] & kGopMapFlagStart == 0 means this is
-    // the start of a gop.
-    while (static_cast<size_t>(end_show_index) < gop_map.size() &&
-           (gop_map[end_show_index] & kGopMapFlagStart) == 0) {
-      ++end_show_index;
-    }
-    const int show_frame_count = end_show_index - start_show_index;
-    int use_alt_ref = (gop_map[start_show_index] & kGopMapFlagUseAltRef) != 0;
-    if (static_cast<size_t>(end_show_index) == gop_map.size()) {
-      // This is the last gop group, there must be no altref.
-      use_alt_ref = 0;
-    }
-    gop_command_on(&gop_command, show_frame_count, use_alt_ref);
-  } else {
-    gop_command_off(&gop_command);
-  }
-  return gop_command;
-}
-
-void SimpleEncode::StartEncode() {
-  assert(impl_ptr_->first_pass_stats.size() > 0);
-  vpx_rational_t frame_rate =
-      make_vpx_rational(frame_rate_num_, frame_rate_den_);
-  VP9EncoderConfig oxcf = GetEncodeConfig(
-      frame_width_, frame_height_, frame_rate, target_bitrate_, encode_speed_,
-      target_level_, VPX_RC_LAST_PASS, impl_ptr_->encode_config_list);
-
-  vpx_fixed_buf_t stats;
-  stats.buf = GetVectorData(impl_ptr_->first_pass_stats);
-  stats.sz = sizeof(impl_ptr_->first_pass_stats[0]) *
-             impl_ptr_->first_pass_stats.size();
-
-  vp9_set_first_pass_stats(&oxcf, &stats);
-  assert(impl_ptr_->cpi == nullptr);
-  impl_ptr_->cpi = init_encoder(&oxcf, impl_ptr_->img_fmt);
-  if (impl_ptr_->img_fmt == VPX_IMG_FMT_NV12) {
-    fprintf(stderr, "VPX_IMG_FMT_NV12 is not supported\n");
-    abort();
-  }
-  vpx_img_alloc(&impl_ptr_->tmp_img, impl_ptr_->img_fmt, frame_width_,
-                frame_height_, 1);
-
-  frame_coding_index_ = 0;
-  show_frame_count_ = 0;
-
-  assert(impl_ptr_->cpi != nullptr);
-  FRAME_INFO frame_info = vp9_get_frame_info(&oxcf);
-  unsigned int screen_area = frame_info.frame_width * frame_info.frame_height;
-  vp9_init_vizier_params(&impl_ptr_->cpi->twopass, screen_area);
-
-  UpdateKeyFrameGroup(show_frame_count_);
-
-  const GOP_COMMAND gop_command = GetGopCommand(gop_map_, show_frame_count_);
-  encode_command_set_gop_command(&impl_ptr_->cpi->encode_command, gop_command);
-  UpdateGroupOfPicture(impl_ptr_->cpi, frame_coding_index_, ref_frame_info_,
-                       &group_of_picture_);
-  rewind(in_file_);
-
-  if (out_file_ != nullptr) {
-    const char *fourcc = "VP90";
-    // In SimpleEncode, we use time_base = 1 / TICKS_PER_SEC.
-    // Based on that, the ivf_timestamp for each image is set to
-    // show_idx * TICKS_PER_SEC / frame_rate
-    // such that each image's actual timestamp in seconds can be computed as
-    // ivf_timestamp * time_base == show_idx / frame_rate
-    // TODO(angiebird): 1) Add unit test for ivf timestamp.
-    // 2) Simplify the frame_rate setting process.
-    vpx_rational_t time_base = make_vpx_rational(1, TICKS_PER_SEC);
-    ivf_write_file_header_with_video_info(out_file_, *(const uint32_t *)fourcc,
-                                          num_frames_, frame_width_,
-                                          frame_height_, time_base);
-  }
-}
-
-void SimpleEncode::EndEncode() {
-  free_encoder(impl_ptr_->cpi);
-  impl_ptr_->cpi = nullptr;
-  vpx_img_free(&impl_ptr_->tmp_img);
-  rewind(in_file_);
-}
-
-void SimpleEncode::UpdateKeyFrameGroup(int key_frame_show_index) {
-  const VP9_COMP *cpi = impl_ptr_->cpi;
-  key_frame_group_index_ = 0;
-  key_frame_group_size_ = vp9_get_frames_to_next_key(
-      &cpi->oxcf, &cpi->twopass, key_frame_show_index, cpi->rc.min_gf_interval);
-  assert(key_frame_group_size_ > 0);
-  // Init the reference frame info when a new key frame group appears.
-  InitRefFrameInfo(&ref_frame_info_);
-}
-
-void SimpleEncode::PostUpdateKeyFrameGroupIndex(FrameType frame_type) {
-  if (frame_type != kFrameTypeAltRef) {
-    // key_frame_group_index_ only counts show frames
-    ++key_frame_group_index_;
-  }
-}
-
-int SimpleEncode::GetKeyFrameGroupSize() const { return key_frame_group_size_; }
-
-GroupOfPicture SimpleEncode::ObserveGroupOfPicture() const {
-  return group_of_picture_;
-}
-
-EncodeFrameInfo SimpleEncode::GetNextEncodeFrameInfo() const {
-  return group_of_picture_
-      .encode_frame_list[group_of_picture_.next_encode_frame_index];
-}
-
-void SimpleEncode::PostUpdateState(
-    const EncodeFrameResult &encode_frame_result) {
-  // This function needs to be called before the increament of
-  // frame_coding_index_
-  PostUpdateRefFrameInfo(encode_frame_result.frame_type, frame_coding_index_,
-                         &ref_frame_info_);
-  ++frame_coding_index_;
-  if (encode_frame_result.frame_type != kFrameTypeAltRef) {
-    // Only kFrameTypeAltRef is not a show frame
-    ++show_frame_count_;
-  }
-
-  PostUpdateKeyFrameGroupIndex(encode_frame_result.frame_type);
-  if (key_frame_group_index_ == key_frame_group_size_) {
-    UpdateKeyFrameGroup(show_frame_count_);
-  }
-
-  IncreaseGroupOfPictureIndex(&group_of_picture_);
-  if (IsGroupOfPictureFinished(group_of_picture_)) {
-    const GOP_COMMAND gop_command = GetGopCommand(gop_map_, show_frame_count_);
-    encode_command_set_gop_command(&impl_ptr_->cpi->encode_command,
-                                   gop_command);
-    // This function needs to be called after ref_frame_info_ is updated
-    // properly in PostUpdateRefFrameInfo() and UpdateKeyFrameGroup().
-    UpdateGroupOfPicture(impl_ptr_->cpi, frame_coding_index_, ref_frame_info_,
-                         &group_of_picture_);
-  }
-}
-
-void SimpleEncode::EncodeFrame(EncodeFrameResult *encode_frame_result) {
-  VP9_COMP *cpi = impl_ptr_->cpi;
-  struct lookahead_ctx *lookahead = cpi->lookahead;
-  int use_highbitdepth = 0;
-#if CONFIG_VP9_HIGHBITDEPTH
-  use_highbitdepth = cpi->common.use_highbitdepth;
-#endif
-  // The lookahead's size is set to oxcf->lag_in_frames.
-  // We want to fill lookahead to it's max capacity if possible so that the
-  // encoder can construct alt ref frame in time.
-  // In the other words, we hope vp9_get_compressed_data to encode a frame
-  // every time in the function
-  while (!vp9_lookahead_full(lookahead)) {
-    // TODO(angiebird): Check whether we can move this file read logics to
-    // lookahead
-    if (img_read(&impl_ptr_->tmp_img, in_file_)) {
-      int next_show_idx = vp9_lookahead_next_show_idx(lookahead);
-      int64_t ts_start =
-          timebase_units_to_ticks(&cpi->oxcf.g_timebase_in_ts, next_show_idx);
-      int64_t ts_end = timebase_units_to_ticks(&cpi->oxcf.g_timebase_in_ts,
-                                               next_show_idx + 1);
-      YV12_BUFFER_CONFIG sd;
-      image2yuvconfig(&impl_ptr_->tmp_img, &sd);
-      vp9_lookahead_push(lookahead, &sd, ts_start, ts_end, use_highbitdepth, 0);
-    } else {
-      break;
-    }
-  }
-
-  if (init_encode_frame_result(encode_frame_result, frame_width_, frame_height_,
-                               impl_ptr_->img_fmt)) {
-    int64_t time_stamp;
-    int64_t time_end;
-    int flush = 1;  // Make vp9_get_compressed_data encode a frame
-    unsigned int frame_flags = 0;
-    ENCODE_FRAME_RESULT encode_frame_info;
-    vp9_init_encode_frame_result(&encode_frame_info);
-    ImageBuffer_to_IMAGE_BUFFER(encode_frame_result->coded_frame,
-                                &encode_frame_info.coded_frame);
-    vp9_get_compressed_data(cpi, &frame_flags,
-                            &encode_frame_result->coding_data_byte_size,
-                            encode_frame_result->coding_data.get(),
-                            encode_frame_result->max_coding_data_byte_size,
-                            &time_stamp, &time_end, flush, &encode_frame_info);
-    if (out_file_ != nullptr) {
-      ivf_write_frame_header(out_file_, time_stamp,
-                             encode_frame_result->coding_data_byte_size);
-      fwrite(encode_frame_result->coding_data.get(), 1,
-             encode_frame_result->coding_data_byte_size, out_file_);
-    }
-
-    // vp9_get_compressed_data is expected to encode a frame every time, so the
-    // data size should be greater than zero.
-    if (encode_frame_result->coding_data_byte_size <= 0) {
-      fprintf(stderr, "Coding data size <= 0\n");
-      abort();
-    }
-    if (encode_frame_result->coding_data_byte_size >
-        encode_frame_result->max_coding_data_byte_size) {
-      fprintf(stderr, "Coding data size exceeds the maximum.\n");
-      abort();
-    }
-
-    const GroupOfPicture group_of_picture = this->ObserveGroupOfPicture();
-    const int show_frame_count = group_of_picture.show_frame_count;
-    update_encode_frame_result(encode_frame_result, show_frame_count,
-                               &encode_frame_info);
-    PostUpdateState(*encode_frame_result);
-  } else {
-    // TODO(angiebird): Clean up encode_frame_result.
-    fprintf(stderr, "init_encode_frame_result() failed.\n");
-    this->EndEncode();
-  }
-}
-
-void SimpleEncode::EncodeFrameWithQuantizeIndex(
-    EncodeFrameResult *encode_frame_result, int quantize_index) {
-  encode_command_set_external_quantize_index(&impl_ptr_->cpi->encode_command,
-                                             quantize_index);
-  EncodeFrame(encode_frame_result);
-  encode_command_reset_external_quantize_index(&impl_ptr_->cpi->encode_command);
-}
-
-void SimpleEncode::EncodeFrameWithTargetFrameBits(
-    EncodeFrameResult *encode_frame_result, int target_frame_bits,
-    double percent_diff) {
-  encode_command_set_target_frame_bits(&impl_ptr_->cpi->encode_command,
-                                       target_frame_bits, percent_diff);
-  EncodeFrame(encode_frame_result);
-  encode_command_reset_target_frame_bits(&impl_ptr_->cpi->encode_command);
-}
-
-static int GetCodingFrameNumFromGopMap(const std::vector<int> &gop_map) {
-  int start_show_index = 0;
-  int coding_frame_count = 0;
-  while (static_cast<size_t>(start_show_index) < gop_map.size()) {
-    const GOP_COMMAND gop_command = GetGopCommand(gop_map, start_show_index);
-    start_show_index += gop_command.show_frame_count;
-    coding_frame_count += gop_command_coding_frame_count(&gop_command);
-  }
-  assert(static_cast<size_t>(start_show_index) == gop_map.size());
-  return coding_frame_count;
-}
-
-int SimpleEncode::GetCodingFrameNum() const {
-  assert(impl_ptr_->first_pass_stats.size() > 0);
-  if (gop_map_.size() > 0) {
-    return GetCodingFrameNumFromGopMap(gop_map_);
-  }
-
-  // These are the default settings for now.
-  TWO_PASS twopass;
-  const int multi_layer_arf = 0;
-  const int allow_alt_ref = 1;
-  vpx_rational_t frame_rate =
-      make_vpx_rational(frame_rate_num_, frame_rate_den_);
-  const VP9EncoderConfig oxcf = GetEncodeConfig(
-      frame_width_, frame_height_, frame_rate, target_bitrate_, encode_speed_,
-      target_level_, VPX_RC_LAST_PASS, impl_ptr_->encode_config_list);
-  FRAME_INFO frame_info = vp9_get_frame_info(&oxcf);
-  fps_init_first_pass_info(&twopass.first_pass_info,
-                           GetVectorData(impl_ptr_->first_pass_stats),
-                           num_frames_);
-  unsigned int screen_area = frame_info.frame_width * frame_info.frame_height;
-  vp9_init_vizier_params(&twopass, screen_area);
-  return vp9_get_coding_frame_num(&oxcf, &twopass, &frame_info, multi_layer_arf,
-                                  allow_alt_ref);
-}
-
-std::vector<int> SimpleEncode::ComputeKeyFrameMap() const {
-  // The last entry of first_pass_stats is the overall stats.
-  assert(impl_ptr_->first_pass_stats.size() ==
-         static_cast<size_t>(num_frames_) + 1);
-  vpx_rational_t frame_rate =
-      make_vpx_rational(frame_rate_num_, frame_rate_den_);
-  const VP9EncoderConfig oxcf = GetEncodeConfig(
-      frame_width_, frame_height_, frame_rate, target_bitrate_, encode_speed_,
-      target_level_, VPX_RC_LAST_PASS, impl_ptr_->encode_config_list);
-  TWO_PASS twopass;
-  fps_init_first_pass_info(&twopass.first_pass_info,
-                           GetVectorData(impl_ptr_->first_pass_stats),
-                           num_frames_);
-  std::vector<int> key_frame_map(num_frames_, 0);
-  vp9_get_key_frame_map(&oxcf, &twopass, GetVectorData(key_frame_map));
-  return key_frame_map;
-}
-
-std::vector<int> SimpleEncode::ObserveKeyFrameMap() const {
-  return key_frame_map_;
-}
-
-uint64_t SimpleEncode::GetFramePixelCount() const {
-  assert(frame_width_ % 2 == 0);
-  assert(frame_height_ % 2 == 0);
-  switch (impl_ptr_->img_fmt) {
-    case VPX_IMG_FMT_I420: return frame_width_ * frame_height_ * 3 / 2;
-    case VPX_IMG_FMT_I422: return frame_width_ * frame_height_ * 2;
-    case VPX_IMG_FMT_I444: return frame_width_ * frame_height_ * 3;
-    case VPX_IMG_FMT_I440: return frame_width_ * frame_height_ * 2;
-    case VPX_IMG_FMT_I42016: return frame_width_ * frame_height_ * 3 / 2;
-    case VPX_IMG_FMT_I42216: return frame_width_ * frame_height_ * 2;
-    case VPX_IMG_FMT_I44416: return frame_width_ * frame_height_ * 3;
-    case VPX_IMG_FMT_I44016: return frame_width_ * frame_height_ * 2;
-    default: return 0;
-  }
-}
-
-SimpleEncode::~SimpleEncode() {
-  if (in_file_ != nullptr) {
-    fclose(in_file_);
-  }
-  if (out_file_ != nullptr) {
-    fclose(out_file_);
-  }
-}
-
-}  // namespace vp9
diff --git a/vp9/simple_encode.h b/vp9/simple_encode.h
deleted file mode 100644
index 94ecbf2..0000000
--- a/vp9/simple_encode.h
+++ /dev/null
@@ -1,584 +0,0 @@
-/*
- *  Copyright (c) 2019 The WebM 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.
- */
-
-#ifndef VPX_VP9_SIMPLE_ENCODE_H_
-#define VPX_VP9_SIMPLE_ENCODE_H_
-
-#include <cstddef>
-#include <cstdint>
-#include <cstdio>
-#include <memory>
-#include <vector>
-
-namespace vp9 {
-
-enum StatusCode {
-  StatusOk = 0,
-  StatusError,
-};
-
-// TODO(angiebird): Add description for each frame type.
-enum FrameType {
-  kFrameTypeKey = 0,
-  kFrameTypeInter = 1,
-  kFrameTypeAltRef = 2,
-  kFrameTypeOverlay = 3,
-  kFrameTypeGolden = 4,
-};
-
-// TODO(angiebird): Add description for each reference frame type.
-// This enum numbers have to be contiguous and start from zero except
-// kNoneRefFrame.
-enum RefFrameType {
-  kRefFrameTypeLast = 0,
-  kRefFrameTypePast = 1,
-  kRefFrameTypeFuture = 2,
-  kRefFrameTypeMax = 3,
-  kRefFrameTypeNone = -1,
-};
-
-enum VP9_LEVEL {
-  LEVEL_UNKNOWN = 0,
-  LEVEL_AUTO = 1,
-  LEVEL_1 = 10,
-  LEVEL_1_1 = 11,
-  LEVEL_2 = 20,
-  LEVEL_2_1 = 21,
-  LEVEL_3 = 30,
-  LEVEL_3_1 = 31,
-  LEVEL_4 = 40,
-  LEVEL_4_1 = 41,
-  LEVEL_5 = 50,
-  LEVEL_5_1 = 51,
-  LEVEL_5_2 = 52,
-  LEVEL_6 = 60,
-  LEVEL_6_1 = 61,
-  LEVEL_6_2 = 62,
-  LEVEL_MAX = 255
-};
-
-enum GopMapFlag {
-  kGopMapFlagStart =
-      1 << 0,  // Indicate this location is the start of a group of pictures.
-  kGopMapFlagUseAltRef =
-      1 << 1,  // Indicate this group of pictures will use an alt ref. Only set
-               // this flag when kGopMapFlagStart is set.
-};
-
-// The frame is split to 4x4 blocks.
-// This structure contains the information of each 4x4 block.
-struct PartitionInfo {
-  int row;           // row pixel offset of current 4x4 block
-  int column;        // column pixel offset of current 4x4 block
-  int row_start;     // row pixel offset of the start of the prediction block
-  int column_start;  // column pixel offset of the start of the prediction block
-  int width;         // prediction block width
-  int height;        // prediction block height
-};
-
-constexpr int kMotionVectorSubPixelPrecision = 8;
-constexpr int kMotionVectorFullPixelPrecision = 1;
-
-// In the first pass. The frame is split to 16x16 blocks.
-// This structure contains the information of each 16x16 block.
-// In the second pass. The frame is split to 4x4 blocks.
-// This structure contains the information of each 4x4 block.
-struct MotionVectorInfo {
-  // Number of valid motion vectors, always 0 if this block is in the key frame.
-  // For inter frames, it could be 1 or 2.
-  int mv_count;
-  // The reference frame for motion vectors. If the second motion vector does
-  // not exist (mv_count = 1), the reference frame is kNoneRefFrame.
-  // Otherwise, the reference frame is either kRefFrameTypeLast, or
-  // kRefFrameTypePast, or kRefFrameTypeFuture.
-  RefFrameType ref_frame[2];
-  // The row offset of motion vectors in the unit of pixel.
-  // If the second motion vector does not exist, the value is 0.
-  double mv_row[2];
-  // The column offset of motion vectors in the unit of pixel.
-  // If the second motion vector does not exist, the value is 0.
-  double mv_column[2];
-};
-
-// Accumulated tpl stats of all blocks in one frame.
-// For each frame, the tpl stats are computed per 32x32 block.
-struct TplStatsInfo {
-  // Intra complexity: the sum of absolute transform difference (SATD) of
-  // intra predicted residuals.
-  int64_t intra_cost;
-  // Inter complexity: the SATD of inter predicted residuals.
-  int64_t inter_cost;
-  // Motion compensated information flow. It measures how much information
-  // is propagated from the current frame to other frames.
-  int64_t mc_flow;
-  // Motion compensated dependency cost. It equals to its own intra_cost
-  // plus the mc_flow.
-  int64_t mc_dep_cost;
-  // Motion compensated reference cost.
-  int64_t mc_ref_cost;
-};
-
-struct RefFrameInfo {
-  int coding_indexes[kRefFrameTypeMax];
-
-  // Indicate whether the reference frames are available or not.
-  // When the reference frame type is not valid, it means either the to-be-coded
-  // frame is a key frame or the reference frame already appears in other
-  // reference frame type. vp9 always keeps three types of reference frame
-  // available.  However, the duplicated reference frames will not be
-  // chosen by the encoder. The priorities of choosing reference frames are
-  // kRefFrameTypeLast > kRefFrameTypePast > kRefFrameTypeFuture.
-  // For example, if kRefFrameTypeLast and kRefFrameTypePast both point to the
-  // same frame, kRefFrameTypePast will be set to invalid.
-  // 1: the ref frame type is available 0: the ref frame type is not available
-  int valid_list[kRefFrameTypeMax];
-};
-
-bool operator==(const RefFrameInfo &a, const RefFrameInfo &b);
-
-struct EncodeFrameInfo {
-  int show_idx;
-
-  // Each show or no show frame is assigned with a coding index based on its
-  // coding order (starting from zero) in the coding process of the entire
-  // video. The coding index for each frame is unique.
-  int coding_index;
-  RefFrameInfo ref_frame_info;
-  FrameType frame_type;
-};
-
-// This structure is a copy of vp9 |nmv_component_counts|.
-struct NewMotionvectorComponentCounts {
-  std::vector<unsigned int> sign;
-  std::vector<unsigned int> classes;
-  std::vector<unsigned int> class0;
-  std::vector<std::vector<unsigned int>> bits;
-  std::vector<std::vector<unsigned int>> class0_fp;
-  std::vector<unsigned int> fp;
-  std::vector<unsigned int> class0_hp;
-  std::vector<unsigned int> hp;
-};
-
-// This structure is a copy of vp9 |nmv_context_counts|.
-struct NewMotionVectorContextCounts {
-  std::vector<unsigned int> joints;
-  std::vector<NewMotionvectorComponentCounts> comps;
-};
-
-using UintArray2D = std::vector<std::vector<unsigned int>>;
-using UintArray3D = std::vector<std::vector<std::vector<unsigned int>>>;
-using UintArray5D = std::vector<
-    std::vector<std::vector<std::vector<std::vector<unsigned int>>>>>;
-using UintArray6D = std::vector<std::vector<
-    std::vector<std::vector<std::vector<std::vector<unsigned int>>>>>>;
-
-// This structure is a copy of vp9 |tx_counts|.
-struct TransformSizeCounts {
-  // Transform size found in blocks of partition size 32x32.
-  // First dimension: transform size contexts (2).
-  // Second dimension: transform size type (3: 32x32, 16x16, 8x8)
-  UintArray2D p32x32;
-  // Transform size found in blocks of partition size 16x16.
-  // First dimension: transform size contexts (2).
-  // Second dimension: transform size type (2: 16x16, 8x8)
-  UintArray2D p16x16;
-  // Transform size found in blocks of partition size 8x8.
-  // First dimension: transform size contexts (2).
-  // Second dimension: transform size type (1: 8x8)
-  UintArray2D p8x8;
-  // Overall transform size count.
-  std::vector<unsigned int> tx_totals;
-};
-
-// This structure is a copy of vp9 |FRAME_COUNTS|.
-struct FrameCounts {
-  // Intra prediction mode for luma plane. First dimension: block size (4).
-  // Second dimension: intra prediction mode (10).
-  UintArray2D y_mode;
-  // Intra prediction mode for chroma plane. First and second dimension:
-  // intra prediction mode (10).
-  UintArray2D uv_mode;
-  // Partition type. First dimension: partition contexts (16).
-  // Second dimension: partition type (4).
-  UintArray2D partition;
-  // Transform coefficient.
-  UintArray6D coef;
-  // End of block (the position of the last non-zero transform coefficient)
-  UintArray5D eob_branch;
-  // Interpolation filter type. First dimension: switchable filter contexts (4).
-  // Second dimension: filter types (3).
-  UintArray2D switchable_interp;
-  // Inter prediction mode (the motion vector type).
-  // First dimension: inter mode contexts (7).
-  // Second dimension: mode type (4).
-  UintArray2D inter_mode;
-  // Block is intra or inter predicted. First dimension: contexts (4).
-  // Second dimension: type (0 for intra, 1 for inter).
-  UintArray2D intra_inter;
-  // Block is compound predicted (predicted from average of two blocks).
-  // First dimension: contexts (5).
-  // Second dimension: type (0 for single, 1 for compound prediction).
-  UintArray2D comp_inter;
-  // Type of the reference frame. Only one reference frame.
-  // First dimension: context (5). Second dimension: context (2).
-  // Third dimension: count (2).
-  UintArray3D single_ref;
-  // Type of the two reference frames.
-  // First dimension: context (5). Second dimension: count (2).
-  UintArray2D comp_ref;
-  // Block skips transform and quantization, uses prediction as reconstruction.
-  // First dimension: contexts (3). Second dimension: type (0 not skip, 1 skip).
-  UintArray2D skip;
-  // Transform size.
-  TransformSizeCounts tx;
-  // New motion vector.
-  NewMotionVectorContextCounts mv;
-};
-
-struct ImageBuffer {
-  // The image data is stored in raster order,
-  // i.e. image[plane][r][c] =
-  // plane_buffer[plane][r * plane_width[plane] + plane_height[plane]].
-  std::unique_ptr<unsigned char[]> plane_buffer[3];
-  int plane_width[3];
-  int plane_height[3];
-};
-
-void output_image_buffer(const ImageBuffer &image_buffer, std::FILE *out_file);
-
-struct EncodeFrameResult {
-  int show_idx;
-  FrameType frame_type;
-  int coding_idx;
-  RefFrameInfo ref_frame_info;
-  size_t coding_data_bit_size;
-  size_t coding_data_byte_size;
-  // The EncodeFrame will allocate a buffer, write the coding data into the
-  // buffer and give the ownership of the buffer to coding_data.
-  std::unique_ptr<unsigned char[]> coding_data;
-  size_t max_coding_data_byte_size;
-  double psnr;
-  uint64_t sse;
-  int quantize_index;
-  FrameCounts frame_counts;
-  int num_rows_4x4;  // number of row units, in size of 4.
-  int num_cols_4x4;  // number of column units, in size of 4.
-  // A vector of the partition information of the frame.
-  // The number of elements is |num_rows_4x4| * |num_cols_4x4|.
-  // The frame is divided 4x4 blocks of |num_rows_4x4| rows and
-  // |num_cols_4x4| columns.
-  // Each 4x4 block contains the current pixel position (|row|, |column|),
-  // the start pixel position of the partition (|row_start|, |column_start|),
-  // and the |width|, |height| of the partition.
-  // The current pixel position can be the same as the start pixel position
-  // if the 4x4 block is the top-left block in the partition. Otherwise, they
-  // are different.
-  // Within the same partition, all 4x4 blocks have the same |row_start|,
-  // |column_start|, |width| and |height|.
-  // For example, if the frame is partitioned to a 32x32 block,
-  // starting at (0, 0). Then, there're 64 4x4 blocks within this partition.
-  // They all have the same |row_start|, |column_start|, |width|, |height|,
-  // which can be used to figure out the start of the current partition and
-  // the start of the next partition block.
-  // Horizontal next: |column_start| + |width|,
-  // Vertical next: |row_start| + |height|.
-  std::vector<PartitionInfo> partition_info;
-  // A vector of the motion vector information of the frame.
-  // The number of elements is |num_rows_4x4| * |num_cols_4x4|.
-  // The frame is divided into 4x4 blocks of |num_rows_4x4| rows and
-  // |num_cols_4x4| columns.
-  // Each 4x4 block contains 0 motion vector if this is an intra predicted
-  // frame (for example, the key frame). If the frame is inter predicted,
-  // each 4x4 block contains either 1 or 2 motion vectors.
-  // Similar to partition info, all 4x4 blocks inside the same partition block
-  // share the same motion vector information.
-  std::vector<MotionVectorInfo> motion_vector_info;
-  // A vector of the tpl stats information.
-  // The tpl stats measure the complexity of a frame, as well as the
-  // information propagated along the motion trajectory between frames, in
-  // the reference frame structure.
-  // The tpl stats could be used as a more accurate spatial and temporal
-  // complexity measure in addition to the first pass stats.
-  // The vector contains tpl stats for all show frames in a GOP.
-  // The tpl stats stored in the vector is according to the encoding order.
-  // For example, suppose there are N show frames for the current GOP.
-  // Then tpl_stats_info[0] stores the information of the first frame to be
-  // encoded for this GOP, i.e., the AltRef frame.
-  std::vector<TplStatsInfo> tpl_stats_info;
-  ImageBuffer coded_frame;
-
-  // recode_count, q_index_history and rate_history are only available when
-  // EncodeFrameWithTargetFrameBits() is used.
-  int recode_count;
-  std::vector<int> q_index_history;
-  std::vector<int> rate_history;
-};
-
-struct GroupOfPicture {
-  // This list will be updated internally in StartEncode() and
-  // EncodeFrame()/EncodeFrameWithQuantizeIndex().
-  // In EncodeFrame()/EncodeFrameWithQuantizeIndex(), the update will only be
-  // triggered when the coded frame is the last one in the previous group of
-  // pictures.
-  std::vector<EncodeFrameInfo> encode_frame_list;
-
-  // Indicates the index of the next coding frame in encode_frame_list.
-  // In other words, EncodeFrameInfo of the next coding frame can be
-  // obtained with encode_frame_list[next_encode_frame_index].
-  // Internally, next_encode_frame_index will be set to zero after the last
-  // frame of the group of pictures is coded. Otherwise, next_encode_frame_index
-  // will be increased after each EncodeFrame()/EncodeFrameWithQuantizeIndex()
-  // call.
-  int next_encode_frame_index;
-
-  // Number of show frames in this group of pictures.
-  int show_frame_count;
-
-  // The show index/timestamp of the earliest show frame in the group of
-  // pictures.
-  int start_show_index;
-
-  // The coding index of the first coding frame in the group of pictures.
-  int start_coding_index;
-
-  // Indicates whether this group of pictures starts with a key frame.
-  int first_is_key_frame;
-
-  // Indicates whether this group of pictures uses an alt ref.
-  int use_alt_ref;
-
-  // Indicates whether previous group of pictures used an alt ref.
-  int last_gop_use_alt_ref;
-};
-
-class SimpleEncode {
- public:
-  // When outfile_path is set, the encoder will output the bitstream in ivf
-  // format.
-  SimpleEncode(int frame_width, int frame_height, int frame_rate_num,
-               int frame_rate_den, int target_bitrate, int num_frames,
-               int target_level, const char *infile_path,
-               const char *outfile_path = nullptr);
-  ~SimpleEncode();
-  SimpleEncode(SimpleEncode &) = delete;
-  SimpleEncode &operator=(const SimpleEncode &) = delete;
-
-  // Adjusts the encoder's coding speed.
-  // If this function is not called, the encoder will use default encode_speed
-  // 0. Call this function before ComputeFirstPassStats() if needed.
-  // The encode_speed is equivalent to --cpu-used of the vpxenc command.
-  // The encode_speed's range should be [0, 9].
-  // Setting the encode_speed to a higher level will yield faster coding
-  // at the cost of lower compression efficiency.
-  void SetEncodeSpeed(int encode_speed);
-
-  // Set encoder config
-  // The following configs in VP9EncoderConfig are allowed to change in this
-  // function. See https://ffmpeg.org/ffmpeg-codecs.html#libvpx for each
-  // config's meaning.
-  // Configs in VP9EncoderConfig:          Equivalent configs in ffmpeg:
-  // 1  key_freq                           -g
-  // 2  two_pass_vbrmin_section            -minrate * 100LL / bit_rate
-  // 3  two_pass_vbrmax_section            -maxrate * 100LL / bit_rate
-  // 4  under_shoot_pct                    -undershoot-pct
-  // 5  over_shoot_pct                     -overshoot-pct
-  // 6  max_threads                        -threads
-  // 7  frame_parallel_decoding_mode       -frame-parallel
-  // 8  tile_column                        -tile-columns
-  // 9  arnr_max_frames                    -arnr-maxframes
-  // 10 arnr_strength                      -arnr-strength
-  // 11 lag_in_frames                      -rc_lookahead
-  // 12 encode_breakout                    -static-thresh
-  // 13 enable_tpl_model                   -enable-tpl
-  // 14 enable_auto_arf                    -auto-alt-ref
-  // 15 rc_mode
-  //    Possible Settings:
-  //      0 - Variable Bit Rate (VPX_VBR)  -b:v <bit_rate>
-  //      1 - Constant Bit Rate (VPX_CBR)  -b:v <bit_rate> -minrate <bit_rate>
-  //                                        -maxrate <bit_rate>
-  //        two_pass_vbrmin_section == 100   i.e. bit_rate == minrate == maxrate
-  //        two_pass_vbrmax_section == 100
-  //      2 - Constrained Quality (VPX_CQ) -crf <cq_level> -b:v bit_rate
-  //      3 - Constant Quality (VPX_Q)     -crf <cq_level> -b:v 0
-  //    See https://trac.ffmpeg.org/wiki/Encode/VP9 for more details.
-  // 16 cq_level                          see rc_mode for details.
-  StatusCode SetEncodeConfig(const char *name, const char *value);
-
-  // A debug function that dumps configs from VP9EncoderConfig
-  // pass = 1: first pass, pass = 2: second pass
-  // fp: file pointer for dumping config
-  StatusCode DumpEncodeConfigs(int pass, FILE *fp);
-
-  // Makes encoder compute the first pass stats and store it at
-  // impl_ptr_->first_pass_stats. key_frame_map_ is also computed based on the
-  // first pass stats.
-  void ComputeFirstPassStats();
-
-  // Outputs the first pass stats represented by a 2-D vector.
-  // One can use the frame index at first dimension to retrieve the stats for
-  // each video frame. The stats of each video frame is a vector of 25 double
-  // values. For details, please check FIRSTPASS_STATS in vp9_firstpass.h
-  std::vector<std::vector<double>> ObserveFirstPassStats();
-
-  // Outputs the first pass motion vectors represented by a 2-D vector.
-  // One can use the frame index at first dimension to retrieve the mvs for
-  // each video frame. The frame is divided into 16x16 blocks. The number of
-  // elements is round_up(|num_rows_4x4| / 4) * round_up(|num_cols_4x4| / 4).
-  std::vector<std::vector<MotionVectorInfo>> ObserveFirstPassMotionVectors();
-
-  // Ouputs a copy of key_frame_map_, a binary vector with size equal to the
-  // number of show frames in the video. For each entry in the vector, 1
-  // indicates the position is a key frame and 0 indicates it's not a key frame.
-  // This function should be called after ComputeFirstPassStats()
-  std::vector<int> ObserveKeyFrameMap() const;
-
-  // Sets group of pictures map for coding the entire video.
-  // Each entry in the gop_map corresponds to a show frame in the video.
-  // Therefore, the size of gop_map should equal to the number of show frames in
-  // the entire video.
-  // If a given entry's kGopMapFlagStart is set, it means this is the start of a
-  // gop. Once kGopMapFlagStart is set, one can set kGopMapFlagUseAltRef to
-  // indicate whether this gop use altref.
-  // If a given entry is zero, it means it's in the middle of a gop.
-  // This function should be called only once after ComputeFirstPassStats(),
-  // before StartEncode().
-  // This API will check and modify the gop_map to satisfy the following
-  // constraints.
-  // 1) Each key frame position should be at the start of a gop.
-  // 2) The last gop should not use an alt ref.
-  void SetExternalGroupOfPicturesMap(int *gop_map, int gop_map_size);
-
-  // Observe the group of pictures map set through
-  // SetExternalGroupOfPicturesMap(). This function should be called after
-  // SetExternalGroupOfPicturesMap().
-  std::vector<int> ObserveExternalGroupOfPicturesMap();
-
-  // Initializes the encoder for actual encoding.
-  // This function should be called after ComputeFirstPassStats().
-  void StartEncode();
-
-  // Frees the encoder.
-  // This function should be called after StartEncode() or EncodeFrame().
-  void EndEncode();
-
-  // The key frame group size includes one key frame plus the number of
-  // following inter frames. Note that the key frame group size only counts the
-  // show frames. The number of no show frames like alternate refereces are not
-  // counted.
-  int GetKeyFrameGroupSize() const;
-
-  // Provides the group of pictures that the next coding frame is in.
-  // Only call this function between StartEncode() and EndEncode()
-  GroupOfPicture ObserveGroupOfPicture() const;
-
-  // Gets encode_frame_info for the next coding frame.
-  // Only call this function between StartEncode() and EndEncode()
-  EncodeFrameInfo GetNextEncodeFrameInfo() const;
-
-  // Encodes a frame
-  // This function should be called after StartEncode() and before EndEncode().
-  void EncodeFrame(EncodeFrameResult *encode_frame_result);
-
-  // Encodes a frame with a specific quantize index.
-  // This function should be called after StartEncode() and before EndEncode().
-  void EncodeFrameWithQuantizeIndex(EncodeFrameResult *encode_frame_result,
-                                    int quantize_index);
-
-  // Encode a frame with target frame bits usage.
-  // The encoder will find a quantize index to make the actual frame bits usage
-  // match the target. EncodeFrameWithTargetFrameBits() will recode the frame
-  // up to 7 times to find a q_index to make the actual_frame_bits satisfy the
-  // following inequality. |actual_frame_bits - target_frame_bits| * 100 /
-  // target_frame_bits
-  // <= percent_diff.
-  void EncodeFrameWithTargetFrameBits(EncodeFrameResult *encode_frame_result,
-                                      int target_frame_bits,
-                                      double percent_diff);
-
-  // Gets the number of coding frames for the video. The coding frames include
-  // show frame and no show frame.
-  // This function should be called after ComputeFirstPassStats().
-  int GetCodingFrameNum() const;
-
-  // Gets the total number of pixels of YUV planes per frame.
-  uint64_t GetFramePixelCount() const;
-
- private:
-  // Compute the key frame locations of the video based on first pass stats.
-  // The results are returned as a binary vector with 1s indicating keyframes
-  // and 0s indicating non keyframes.
-  // It has to be called after impl_ptr_->first_pass_stats is computed.
-  std::vector<int> ComputeKeyFrameMap() const;
-
-  // Updates key_frame_group_size_, reset key_frame_group_index_ and init
-  // ref_frame_info_.
-  void UpdateKeyFrameGroup(int key_frame_show_index);
-
-  // Update key_frame_group_index_.
-  void PostUpdateKeyFrameGroupIndex(FrameType frame_type);
-
-  void PostUpdateState(const EncodeFrameResult &encode_frame_result);
-
-  class EncodeImpl;
-
-  int frame_width_;   // frame width in pixels.
-  int frame_height_;  // frame height in pixels.
-  int frame_rate_num_;
-  int frame_rate_den_;
-  int target_bitrate_;
-  int num_frames_;
-  int encode_speed_;
-  int target_level_;
-
-  std::FILE *in_file_;
-  std::FILE *out_file_;
-  std::unique_ptr<EncodeImpl> impl_ptr_;
-
-  std::vector<int> key_frame_map_;
-  std::vector<int> gop_map_;
-  GroupOfPicture group_of_picture_;
-
-  // The key frame group size includes one key frame plus the number of
-  // following inter frames. Note that the key frame group size only counts the
-  // show frames. The number of no show frames like alternate references are not
-  // counted.
-  int key_frame_group_size_;
-
-  // The index for the to-be-coded show frame in the key frame group.
-  int key_frame_group_index_;
-
-  // Each show or no show frame is assigned with a coding index based on its
-  // coding order (starting from zero) in the coding process of the entire
-  // video. The coding index of the to-be-coded frame.
-  int frame_coding_index_;
-
-  // Number of show frames we have coded so far.
-  int show_frame_count_;
-
-  // TODO(angiebird): Do we need to reset ref_frames_info_ when the next key
-  // frame appears?
-  // Reference frames info of the to-be-coded frame.
-  RefFrameInfo ref_frame_info_;
-
-  // A 2-D vector of motion vector information of the frame collected
-  // from the first pass. The first dimension is the frame index.
-  // Each frame is divided into 16x16 blocks. The number of elements is
-  // round_up(|num_rows_4x4| / 4) * round_up(|num_cols_4x4| / 4).
-  // Each 16x16 block contains 0 motion vector if this is an intra predicted
-  // frame (for example, the key frame). If the frame is inter predicted,
-  // each 16x16 block contains either 1 or 2 motion vectors.
-  // The first motion vector is always from the LAST_FRAME.
-  // The second motion vector is always from the GOLDEN_FRAME.
-  std::vector<std::vector<MotionVectorInfo>> fp_motion_vector_info_;
-};
-
-}  // namespace vp9
-
-#endif  // VPX_VP9_SIMPLE_ENCODE_H_
diff --git a/vp9/vp9_cx_iface.c b/vp9/vp9_cx_iface.c
index 55a2d0b..072ab27 100644
--- a/vp9/vp9_cx_iface.c
+++ b/vp9/vp9_cx_iface.c
@@ -17,6 +17,7 @@
 #include "vpx/vpx_encoder.h"
 #include "vpx/vpx_ext_ratectrl.h"
 #include "vpx_dsp/psnr.h"
+#include "vpx_dsp/vpx_dsp_common.h"
 #include "vpx_ports/static_assert.h"
 #include "vpx_ports/system_state.h"
 #include "vpx_util/vpx_timestamp.h"
@@ -507,7 +508,6 @@
 static vpx_codec_err_t set_encoder_config(
     VP9EncoderConfig *oxcf, vpx_codec_enc_cfg_t *cfg,
     const struct vp9_extracfg *extra_cfg) {
-  const int is_vbr = cfg->rc_end_usage == VPX_VBR;
   int sl, tl;
   unsigned int raw_target_rate;
   oxcf->profile = cfg->g_profile;
@@ -570,9 +570,9 @@
     oxcf->resize_mode = RESIZE_NONE;
   }
 
-  oxcf->maximum_buffer_size_ms = is_vbr ? 240000 : cfg->rc_buf_sz;
-  oxcf->starting_buffer_level_ms = is_vbr ? 60000 : cfg->rc_buf_initial_sz;
-  oxcf->optimal_buffer_level_ms = is_vbr ? 60000 : cfg->rc_buf_optimal_sz;
+  oxcf->maximum_buffer_size_ms = cfg->rc_buf_sz;
+  oxcf->starting_buffer_level_ms = cfg->rc_buf_initial_sz;
+  oxcf->optimal_buffer_level_ms = cfg->rc_buf_optimal_sz;
 
   oxcf->drop_frames_water_mark = cfg->rc_dropframe_thresh;
 
@@ -671,7 +671,6 @@
   }
 
   if (get_level_index(oxcf->target_level) >= 0) config_target_level(oxcf);
-  oxcf->use_simple_encode_api = 0;
   // vp9_dump_encoder_config(oxcf, stderr);
   return VPX_CODEC_OK;
 }
@@ -811,14 +810,17 @@
     // Note: function encoder_set_config() is allowed to be called multiple
     // times. However, when the original frame width or height is less than two
     // times of the new frame width or height, a forced key frame should be
-    // used. To make sure the correct detection of a forced key frame, we need
+    // used (for the case of single spatial layer, since otherwise a previous
+    //  encoded frame at a lower layer may be the desired reference). To make
+    //  sure the correct detection of a forced key frame, we need
     // to update the frame width and height only when the actual encoding is
     // performed. cpi->last_coded_width and cpi->last_coded_height are used to
     // track the actual coded frame size.
     if ((ctx->cpi->last_coded_width && ctx->cpi->last_coded_height &&
-         !valid_ref_frame_size(ctx->cpi->last_coded_width,
-                               ctx->cpi->last_coded_height, cfg->g_w,
-                               cfg->g_h)) ||
+         (!valid_ref_frame_size(ctx->cpi->last_coded_width,
+                                ctx->cpi->last_coded_height, cfg->g_w,
+                                cfg->g_h) &&
+          ctx->cpi->svc.number_spatial_layers == 1)) ||
         (ctx->cpi->initial_width && (int)cfg->g_w > ctx->cpi->initial_width) ||
         (ctx->cpi->initial_height &&
          (int)cfg->g_h > ctx->cpi->initial_height)) {
@@ -911,8 +913,7 @@
   struct vp9_extracfg extra_cfg = ctx->extra_cfg;
   // Use fastest speed setting (speed 9 or -9) if it's set beyond the range.
   extra_cfg.cpu_used = CAST(VP8E_SET_CPUUSED, args);
-  extra_cfg.cpu_used = VPXMIN(9, extra_cfg.cpu_used);
-  extra_cfg.cpu_used = VPXMAX(-9, extra_cfg.cpu_used);
+  extra_cfg.cpu_used = clamp(extra_cfg.cpu_used, -9, 9);
 #if CONFIG_REALTIME_ONLY
   if (extra_cfg.cpu_used > -5 && extra_cfg.cpu_used < 5)
     extra_cfg.cpu_used = (extra_cfg.cpu_used > 0) ? 5 : -5;
@@ -1878,6 +1879,12 @@
       LAYER_CONTEXT *lc = &cpi->svc.layer_context[layer];
       lc->max_q = params->max_quantizers[layer];
       lc->min_q = params->min_quantizers[layer];
+      // Checks on valid scale factors.
+      if (params->scaling_factor_num[sl] < 1 ||
+          params->scaling_factor_den[sl] < 1 ||
+          (params->scaling_factor_num[sl] > params->scaling_factor_den[sl])) {
+        return VPX_CODEC_INVALID_PARAM;
+      }
       lc->scaling_factor_num = params->scaling_factor_num[sl];
       lc->scaling_factor_den = params->scaling_factor_den[sl];
       lc->speed = params->speed_per_layer[sl];
@@ -1973,7 +1980,7 @@
                                            va_list args) {
   struct vp9_extracfg extra_cfg = ctx->extra_cfg;
   int data = va_arg(args, int);
-  data = VPXMIN(VPXMAX(data, -15), 15);
+  data = clamp(data, -15, 15);
   extra_cfg.delta_q_uv = data;
   return update_extra_cfg(ctx, &extra_cfg);
 }
@@ -2285,7 +2292,8 @@
       encoder_set_config,          // vpx_codec_enc_config_set_fn_t
       encoder_get_global_headers,  // vpx_codec_get_global_headers_fn_t
       encoder_get_preview,         // vpx_codec_get_preview_frame_fn_t
-      NULL                         // vpx_codec_enc_mr_get_mem_loc_fn_t
+      NULL,                        // vpx_codec_enc_mr_get_mem_loc_fn_t
+      NULL                         // vpx_codec_enc_mr_free_mem_loc_fn_t
   }
 };
 
@@ -2490,7 +2498,6 @@
   DUMP_STRUCT_VALUE(fp, oxcf, row_mt);
   DUMP_STRUCT_VALUE(fp, oxcf, motion_vector_unit_test);
   DUMP_STRUCT_VALUE(fp, oxcf, delta_q_uv);
-  DUMP_STRUCT_VALUE(fp, oxcf, use_simple_encode_api);
 }
 
 FRAME_INFO vp9_get_frame_info(const VP9EncoderConfig *oxcf) {
diff --git a/vp9/vp9_dx_iface.c b/vp9/vp9_dx_iface.c
index 7567910..b6eeee0 100644
--- a/vp9/vp9_dx_iface.c
+++ b/vp9/vp9_dx_iface.c
@@ -737,6 +737,7 @@
       NULL,  // vpx_codec_enc_config_set_fn_t
       NULL,  // vpx_codec_get_global_headers_fn_t
       NULL,  // vpx_codec_get_preview_frame_fn_t
-      NULL   // vpx_codec_enc_mr_get_mem_loc_fn_t
+      NULL,  // vpx_codec_enc_mr_get_mem_loc_fn_t
+      NULL   // vpx_codec_enc_mr_free_mem_loc_fn_t
   }
 };
diff --git a/vp9/vp9cx.mk b/vp9/vp9cx.mk
index 7a0e2d8..2248e6b 100644
--- a/vp9/vp9cx.mk
+++ b/vp9/vp9cx.mk
@@ -110,10 +110,14 @@
 VP9_CX_SRCS-yes += encoder/vp9_mbgraph.c
 VP9_CX_SRCS-yes += encoder/vp9_mbgraph.h
 
+VP9_CX_SRCS-$(HAVE_SSSE3) += encoder/x86/temporal_filter_ssse3.c
 VP9_CX_SRCS-$(HAVE_SSE4_1) += encoder/x86/temporal_filter_sse4.c
+VP9_CX_SRCS-$(HAVE_AVX2) += encoder/x86/temporal_filter_avx2.c
 VP9_CX_SRCS-$(HAVE_SSE4_1) += encoder/vp9_temporal_filter_constants.h
 VP9_CX_SRCS-$(HAVE_NEON) += encoder/arm/neon/vp9_temporal_filter_neon.c
 VP9_CX_SRCS-$(HAVE_NEON) += encoder/vp9_temporal_filter_constants.h
+VP9_CX_SRCS-$(HAVE_NEON_DOTPROD) += encoder/arm/neon/vp9_temporal_filter_neon_dotprod.c
+VP9_CX_SRCS-$(HAVE_NEON_I8MM) += encoder/arm/neon/vp9_temporal_filter_neon_i8mm.c
 
 VP9_CX_SRCS-$(HAVE_SSE2) += encoder/x86/vp9_quantize_sse2.c
 VP9_CX_SRCS-$(HAVE_SSSE3) += encoder/x86/vp9_quantize_ssse3.c
@@ -121,7 +125,9 @@
 VP9_CX_SRCS-$(HAVE_NEON) += encoder/arm/neon/vp9_diamond_search_sad_neon.c
 ifeq ($(CONFIG_VP9_HIGHBITDEPTH),yes)
 VP9_CX_SRCS-$(HAVE_SSE2) += encoder/x86/vp9_highbd_block_error_intrin_sse2.c
+VP9_CX_SRCS-$(HAVE_SSSE3) += encoder/x86/highbd_temporal_filter_ssse3.c
 VP9_CX_SRCS-$(HAVE_SSE4_1) += encoder/x86/highbd_temporal_filter_sse4.c
+VP9_CX_SRCS-$(HAVE_AVX2) += encoder/x86/highbd_temporal_filter_avx2.c
 VP9_CX_SRCS-$(HAVE_NEON) += encoder/arm/neon/vp9_highbd_temporal_filter_neon.c
 endif
 
@@ -162,10 +168,16 @@
 VP9_CX_SRCS_REMOVE-$(CONFIG_REALTIME_ONLY) += encoder/vp9_firstpass.c
 VP9_CX_SRCS_REMOVE-$(CONFIG_REALTIME_ONLY) += encoder/vp9_mbgraph.c
 VP9_CX_SRCS_REMOVE-$(CONFIG_REALTIME_ONLY) += encoder/vp9_temporal_filter.c
+VP9_CX_SRCS_REMOVE-$(CONFIG_REALTIME_ONLY) += encoder/x86/temporal_filter_ssse3.c
 VP9_CX_SRCS_REMOVE-$(CONFIG_REALTIME_ONLY) += encoder/x86/temporal_filter_sse4.c
+VP9_CX_SRCS_REMOVE-$(CONFIG_REALTIME_ONLY) += encoder/x86/temporal_filter_avx2.c
 VP9_CX_SRCS_REMOVE-$(CONFIG_REALTIME_ONLY) += encoder/vp9_temporal_filter_constants.h
+VP9_CX_SRCS_REMOVE-$(CONFIG_REALTIME_ONLY) += encoder/x86/highbd_temporal_filter_ssse3.c
 VP9_CX_SRCS_REMOVE-$(CONFIG_REALTIME_ONLY) += encoder/x86/highbd_temporal_filter_sse4.c
+VP9_CX_SRCS_REMOVE-$(CONFIG_REALTIME_ONLY) += encoder/x86/highbd_temporal_filter_avx2.c
 VP9_CX_SRCS_REMOVE-$(CONFIG_REALTIME_ONLY) += encoder/arm/neon/vp9_temporal_filter_neon.c
+VP9_CX_SRCS_REMOVE-$(CONFIG_REALTIME_ONLY) += encoder/arm/neon/vp9_temporal_filter_neon_dotprod.c
+VP9_CX_SRCS_REMOVE-$(CONFIG_REALTIME_ONLY) += encoder/arm/neon/vp9_temporal_filter_neon_i8mm.c
 VP9_CX_SRCS_REMOVE-$(CONFIG_REALTIME_ONLY) += encoder/arm/neon/vp9_highbd_temporal_filter_neon.c
 VP9_CX_SRCS_REMOVE-$(CONFIG_REALTIME_ONLY) += encoder/vp9_alt_ref_aq.h
 VP9_CX_SRCS_REMOVE-$(CONFIG_REALTIME_ONLY) += encoder/vp9_alt_ref_aq.c
diff --git a/vpx/internal/vpx_codec_internal.h b/vpx/internal/vpx_codec_internal.h
index 275b6a4..ff51881 100644
--- a/vpx/internal/vpx_codec_internal.h
+++ b/vpx/internal/vpx_codec_internal.h
@@ -269,6 +269,8 @@
 typedef vpx_codec_err_t (*vpx_codec_enc_mr_get_mem_loc_fn_t)(
     const vpx_codec_enc_cfg_t *cfg, void **mem_loc);
 
+typedef void (*vpx_codec_enc_mr_free_mem_loc_fn_t)(void *mem_loc);
+
 /*!\brief usage configuration mapping
  *
  * This structure stores the mapping between usage identifiers and
@@ -318,6 +320,8 @@
         get_preview; /**< \copydoc ::vpx_codec_get_preview_frame_fn_t */
     vpx_codec_enc_mr_get_mem_loc_fn_t
         mr_get_mem_loc; /**< \copydoc ::vpx_codec_enc_mr_get_mem_loc_fn_t */
+    vpx_codec_enc_mr_free_mem_loc_fn_t
+        mr_free_mem_loc; /**< \copydoc ::vpx_codec_enc_mr_free_mem_loc_fn_t */
   } enc;
 };
 
diff --git a/vpx/src/vpx_encoder.c b/vpx/src/vpx_encoder.c
index 3af4cea..36dfa51 100644
--- a/vpx/src/vpx_encoder.c
+++ b/vpx/src/vpx_encoder.c
@@ -15,10 +15,9 @@
 #include <assert.h>
 #include <limits.h>
 #include <stdint.h>
-#include <stdlib.h>
 #include <string.h>
-#include "vp8/common/blockd.h"
 #include "vpx_config.h"
+#include "vpx/vpx_encoder.h"
 #include "vpx/internal/vpx_codec_internal.h"
 
 #define SAVE_STATUS(ctx, var) ((ctx) ? ((ctx)->err = (var)) : (var))
@@ -68,13 +67,14 @@
 }
 
 vpx_codec_err_t vpx_codec_enc_init_multi_ver(
-    vpx_codec_ctx_t *ctx, vpx_codec_iface_t *iface, vpx_codec_enc_cfg_t *cfg,
-    int num_enc, vpx_codec_flags_t flags, vpx_rational_t *dsf, int ver) {
+    vpx_codec_ctx_t *ctx, vpx_codec_iface_t *iface,
+    const vpx_codec_enc_cfg_t *cfg, int num_enc, vpx_codec_flags_t flags,
+    const vpx_rational_t *dsf, int ver) {
   vpx_codec_err_t res = VPX_CODEC_OK;
 
   if (ver != VPX_ENCODER_ABI_VERSION)
     res = VPX_CODEC_ABI_MISMATCH;
-  else if (!ctx || !iface || !cfg || (num_enc > 16 || num_enc < 1))
+  else if (!ctx || !iface || !cfg || (num_enc > 16 || num_enc < 1) || !dsf)
     res = VPX_CODEC_INVALID_PARAM;
   else if (iface->abi_version != VPX_CODEC_INTERNAL_ABI_VERSION)
     res = VPX_CODEC_ABI_MISMATCH;
@@ -90,7 +90,7 @@
 #if CONFIG_MULTI_RES_ENCODING
     int mem_loc_owned = 0;
 #endif
-    void *mem_loc = NULL;
+    void *mem_loc;
 
     if (iface->enc.mr_get_mem_loc == NULL) return VPX_CODEC_INCAPABLE;
 
@@ -106,8 +106,7 @@
           mr_cfg.mr_low_res_mode_info = mem_loc;
           mr_cfg.mr_total_resolutions = num_enc;
           mr_cfg.mr_encoder_id = num_enc - 1 - i;
-          mr_cfg.mr_down_sampling_factor.num = dsf->num;
-          mr_cfg.mr_down_sampling_factor.den = dsf->den;
+          mr_cfg.mr_down_sampling_factor = *dsf;
 
           ctx->iface = iface;
           ctx->name = iface->name;
@@ -136,8 +135,7 @@
 #if CONFIG_MULTI_RES_ENCODING
           if (!mem_loc_owned) {
             assert(mem_loc);
-            free(((LOWER_RES_FRAME_INFO *)mem_loc)->mb_info);
-            free(mem_loc);
+            iface->enc.mr_free_mem_loc(mem_loc);
           }
 #endif
           return SAVE_STATUS(ctx, res);
diff --git a/vpx/vp8cx.h b/vpx/vp8cx.h
index 4e29725..2665630 100644
--- a/vpx/vp8cx.h
+++ b/vpx/vp8cx.h
@@ -877,7 +877,7 @@
   VP8_EIGHT_TOKENPARTITION = 3
 } vp8e_token_partitions;
 
-/*!brief VP9 encoder content type */
+/*!\brief VP9 encoder content type */
 typedef enum {
   VP9E_CONTENT_DEFAULT,
   VP9E_CONTENT_SCREEN,
diff --git a/vpx/vpx_decoder.h b/vpx/vpx_decoder.h
index e2c31ea..0536d5d 100644
--- a/vpx/vpx_decoder.h
+++ b/vpx/vpx_decoder.h
@@ -60,7 +60,7 @@
 #define VPX_CODEC_CAP_INPUT_FRAGMENTS 0x100000
 /*!\brief Can support frame-based multi-threading */
 #define VPX_CODEC_CAP_FRAME_THREADING 0x200000
-/*!brief Can support external frame buffers */
+/*!\brief Can support external frame buffers */
 #define VPX_CODEC_CAP_EXTERNAL_FRAME_BUFFER 0x400000
 
 /*! \brief Initialization-time Feature Enabling
diff --git a/vpx/vpx_encoder.h b/vpx/vpx_encoder.h
index 501723b..a26c8b2 100644
--- a/vpx/vpx_encoder.h
+++ b/vpx/vpx_encoder.h
@@ -879,7 +879,7 @@
  *
  * \param[in]    ctx     Pointer to this instance's context.
  * \param[in]    iface   Pointer to the algorithm interface to use.
- * \param[in]    cfg     Configuration to use, if known. May be NULL.
+ * \param[in]    cfg     Configuration to use.
  * \param[in]    flags   Bitfield of VPX_CODEC_USE_* flags
  * \param[in]    ver     ABI version number. Must be set to
  *                       VPX_ENCODER_ABI_VERSION
@@ -902,27 +902,32 @@
 
 /*!\brief Initialize multi-encoder instance
  *
- * Initializes multi-encoder context using the given interface.
+ * Initializes multiple encoder contexts using the given interface.
  * Applications should call the vpx_codec_enc_init_multi convenience macro
  * instead of this function directly, to ensure that the ABI version number
  * parameter is properly initialized.
  *
- * \param[in]    ctx     Pointer to this instance's context.
+ * \param[in]    ctx     Pointer to an array of num_enc instances' contexts.
  * \param[in]    iface   Pointer to the algorithm interface to use.
- * \param[in]    cfg     Configuration to use, if known. May be NULL.
+ * \param[in]    cfg     An array of num_enc configurations to use.
  * \param[in]    num_enc Total number of encoders.
  * \param[in]    flags   Bitfield of VPX_CODEC_USE_* flags
- * \param[in]    dsf     Pointer to down-sampling factors.
+ * \param[in]    dsf     Pointer to an array of num_enc down-sampling factors.
  * \param[in]    ver     ABI version number. Must be set to
  *                       VPX_ENCODER_ABI_VERSION
  * \retval #VPX_CODEC_OK
  *     The encoder algorithm has been initialized.
  * \retval #VPX_CODEC_MEM_ERROR
  *     Memory allocation failed.
+ *
+ * \note
+ * This is only supported by VP8. iface must point to the interface to the VP8
+ * encoder.
  */
 vpx_codec_err_t vpx_codec_enc_init_multi_ver(
-    vpx_codec_ctx_t *ctx, vpx_codec_iface_t *iface, vpx_codec_enc_cfg_t *cfg,
-    int num_enc, vpx_codec_flags_t flags, vpx_rational_t *dsf, int ver);
+    vpx_codec_ctx_t *ctx, vpx_codec_iface_t *iface,
+    const vpx_codec_enc_cfg_t *cfg, int num_enc, vpx_codec_flags_t flags,
+    const vpx_rational_t *dsf, int ver);
 
 /*!\brief Convenience macro for vpx_codec_enc_init_multi_ver()
  *
diff --git a/vpx/vpx_ext_ratectrl.h b/vpx/vpx_ext_ratectrl.h
index 4820d36..db4f04d 100644
--- a/vpx/vpx_ext_ratectrl.h
+++ b/vpx/vpx_ext_ratectrl.h
@@ -132,14 +132,29 @@
  */
 #define VPX_DEFAULT_RDMULT -1
 
+/*!\brief Superblock quantization parameters
+ * Store the superblock quantiztaion parameters
+ */
+typedef struct sb_parameters {
+  int q_index; /**< Quantizer step index [0..255]*/
+  int rdmult;  /**< Superblock level Lagrangian multiplier*/
+} sb_params;
+
 /*!\brief Encode frame decision made by the external rate control model
  *
  * The encoder will receive the decision from the external rate control model
  * through vpx_rc_funcs_t::get_encodeframe_decision().
  */
 typedef struct vpx_rc_encodeframe_decision {
-  int q_index; /**< Quantizer step index [0..255]*/
-  int rdmult;  /**< Frame level Lagrangian multiplier*/
+  int q_index;    /**< Required: Quantizer step index [0..255]*/
+  int rdmult;     /**< Required: Frame level Lagrangian multiplier*/
+  int delta_q_uv; /**< Required: Delta QP for UV */
+  /*!
+   * Optional: Superblock quantization parameters
+   * It is zero initialized by default. It will be set for key and ARF frames
+   * but not leaf frames.
+   */
+  sb_params *sb_params_list;
 } vpx_rc_encodeframe_decision_t;
 
 /*!\brief Information for the frame to be encoded.
diff --git a/vpx/vpx_tpl.h b/vpx/vpx_tpl.h
index e14eefc..064c566 100644
--- a/vpx/vpx_tpl.h
+++ b/vpx/vpx_tpl.h
@@ -38,10 +38,11 @@
   int16_t col;            /**< Pixel col of the top left corner */
   int64_t intra_cost;     /**< Intra cost */
   int64_t inter_cost;     /**< Inter cost */
-  int16_t mv_r;           /**< Motion vector row */
-  int16_t mv_c;           /**< Motion vector col */
+  int16_t mv_r;           /**< Motion vector row in pixel */
+  int16_t mv_c;           /**< Motion vector col in pixel */
   int64_t srcrf_rate;     /**< Rate from source ref frame */
   int64_t srcrf_dist;     /**< Distortion from source ref frame */
+  int64_t pred_error;     /**< Prediction error */
   int64_t inter_pred_err; /**< Inter prediction error */
   int64_t intra_pred_err; /**< Intra prediction error */
   int ref_frame_index;    /**< Ref frame index in the ref frame buffer */
diff --git a/vpx_dsp/arm/fdct_partial_neon.c b/vpx_dsp/arm/fdct_partial_neon.c
index df0da54..ee9e599 100644
--- a/vpx_dsp/arm/fdct_partial_neon.c
+++ b/vpx_dsp/arm/fdct_partial_neon.c
@@ -37,13 +37,15 @@
   output[1] = 0;
 }
 
-// Visual Studio 2022 (cl.exe) targeting AArch64 with optimizations enabled
-// will fail with an internal compiler error.
-// See:
+// Visual Studio 2022 (cl.exe) < 17.7 targeting AArch64 with optimizations
+// enabled will fail with an internal compiler error. See:
 // https://developercommunity.visualstudio.com/t/Compiler-crash-C1001-when-building-a-for/10346110
-// TODO(jzern): check the compiler version after a fix for the issue is
-// released.
-#if defined(_MSC_VER) && defined(_M_ARM64) && !defined(__clang__)
+#if defined(_MSC_VER) && _MSC_VER < 1937 && defined(_M_ARM64) && \
+    !defined(__clang__)
+#define AOM_WORK_AROUND_MSVC_BUG_10346110
+#endif
+
+#ifdef AOM_WORK_AROUND_MSVC_BUG_10346110
 #pragma optimize("", off)
 #endif
 void vpx_fdct8x8_1_neon(const int16_t *input, tran_low_t *output, int stride) {
@@ -58,9 +60,10 @@
   output[0] = (tran_low_t)horizontal_add_int16x8(sum);
   output[1] = 0;
 }
-#if defined(_MSC_VER) && defined(_M_ARM64) && !defined(__clang__)
+#ifdef AOM_WORK_AROUND_MSVC_BUG_10346110
 #pragma optimize("", on)
 #endif
+#undef AOM_WORK_AROUND_MSVC_BUG_10346110
 
 void vpx_fdct16x16_1_neon(const int16_t *input, tran_low_t *output,
                           int stride) {
diff --git a/vpx_dsp/arm/highbd_vpx_convolve8_sve2.c b/vpx_dsp/arm/highbd_vpx_convolve8_sve2.c
index bf0ee96..06be652 100644
--- a/vpx_dsp/arm/highbd_vpx_convolve8_sve2.c
+++ b/vpx_dsp/arm/highbd_vpx_convolve8_sve2.c
@@ -53,13 +53,13 @@
   int16x8_t s2q = vcombine_s16(s2, vdup_n_s16(0));
   int16x8_t s3q = vcombine_s16(s3, vdup_n_s16(0));
 
-  int32x4_t s01 = vreinterpretq_s32_s16(vzip1q_s16(s0q, s1q));
-  int32x4_t s23 = vreinterpretq_s32_s16(vzip1q_s16(s2q, s3q));
+  int16x8_t s02 = vzip1q_s16(s0q, s2q);
+  int16x8_t s13 = vzip1q_s16(s1q, s3q);
 
-  int32x4x2_t t0123 = vzipq_s32(s01, s23);
+  int16x8x2_t s0123 = vzipq_s16(s02, s13);
 
-  res[0] = vreinterpretq_s16_s32(t0123.val[0]);
-  res[1] = vreinterpretq_s16_s32(t0123.val[1]);
+  res[0] = s0123.val[0];
+  res[1] = s0123.val[1];
 }
 
 static INLINE void transpose_concat_8x4(const int16x8_t s0, const int16x8_t s1,
@@ -76,18 +76,16 @@
   // res[2]: 04 14 24 34 05 15 25 35
   // res[3]: 06 16 26 36 07 17 27 37
 
-  int16x8x2_t s01 = vzipq_s16(s0, s1);
-  int16x8x2_t s23 = vzipq_s16(s2, s3);
+  int16x8x2_t s02 = vzipq_s16(s0, s2);
+  int16x8x2_t s13 = vzipq_s16(s1, s3);
 
-  int32x4x2_t t0123_lo = vzipq_s32(vreinterpretq_s32_s16(s01.val[0]),
-                                   vreinterpretq_s32_s16(s23.val[0]));
-  int32x4x2_t t0123_hi = vzipq_s32(vreinterpretq_s32_s16(s01.val[1]),
-                                   vreinterpretq_s32_s16(s23.val[1]));
+  int16x8x2_t s0123_lo = vzipq_s16(s02.val[0], s13.val[0]);
+  int16x8x2_t s0123_hi = vzipq_s16(s02.val[1], s13.val[1]);
 
-  res[0] = vreinterpretq_s16_s32(t0123_lo.val[0]);
-  res[1] = vreinterpretq_s16_s32(t0123_lo.val[1]);
-  res[2] = vreinterpretq_s16_s32(t0123_hi.val[0]);
-  res[3] = vreinterpretq_s16_s32(t0123_hi.val[1]);
+  res[0] = s0123_lo.val[0];
+  res[1] = s0123_lo.val[1];
+  res[2] = s0123_hi.val[0];
+  res[3] = s0123_hi.val[1];
 }
 
 static INLINE void vpx_tbl2x4_s16(int16x8_t s0[4], int16x8_t s1[4],
diff --git a/vpx_dsp/arm/mem_neon.h b/vpx_dsp/arm/mem_neon.h
index 268c4bd..de78a6e 100644
--- a/vpx_dsp/arm/mem_neon.h
+++ b/vpx_dsp/arm/mem_neon.h
@@ -372,6 +372,36 @@
   *s7 = vld1_u8(s);
 }
 
+static INLINE void load_u8_8x11(const uint8_t *s, ptrdiff_t p,
+                                uint8x8_t *const s0, uint8x8_t *const s1,
+                                uint8x8_t *const s2, uint8x8_t *const s3,
+                                uint8x8_t *const s4, uint8x8_t *const s5,
+                                uint8x8_t *const s6, uint8x8_t *const s7,
+                                uint8x8_t *const s8, uint8x8_t *const s9,
+                                uint8x8_t *const s10) {
+  *s0 = vld1_u8(s);
+  s += p;
+  *s1 = vld1_u8(s);
+  s += p;
+  *s2 = vld1_u8(s);
+  s += p;
+  *s3 = vld1_u8(s);
+  s += p;
+  *s4 = vld1_u8(s);
+  s += p;
+  *s5 = vld1_u8(s);
+  s += p;
+  *s6 = vld1_u8(s);
+  s += p;
+  *s7 = vld1_u8(s);
+  s += p;
+  *s8 = vld1_u8(s);
+  s += p;
+  *s9 = vld1_u8(s);
+  s += p;
+  *s10 = vld1_u8(s);
+}
+
 static INLINE void store_u8_8x8(uint8_t *s, const ptrdiff_t p,
                                 const uint8x8_t s0, const uint8x8_t s1,
                                 const uint8x8_t s2, const uint8x8_t s3,
diff --git a/vpx_dsp/arm/vpx_convolve8_neon_dotprod.c b/vpx_dsp/arm/vpx_convolve8_neon_dotprod.c
index b05a49d..ad5ed37 100644
--- a/vpx_dsp/arm/vpx_convolve8_neon_dotprod.c
+++ b/vpx_dsp/arm/vpx_convolve8_neon_dotprod.c
@@ -375,13 +375,10 @@
   int8x16_t a2q = vcombine_s8(a2, vdup_n_s8(0));
   int8x16_t a3q = vcombine_s8(a3, vdup_n_s8(0));
 
-  int8x16_t a01 = vzipq_s8(a0q, a1q).val[0];
-  int8x16_t a23 = vzipq_s8(a2q, a3q).val[0];
+  int8x16_t a02 = vzipq_s8(a0q, a2q).val[0];
+  int8x16_t a13 = vzipq_s8(a1q, a3q).val[0];
 
-  int16x8_t a0123 =
-      vzipq_s16(vreinterpretq_s16_s8(a01), vreinterpretq_s16_s8(a23)).val[0];
-
-  *b = vreinterpretq_s8_s16(a0123);
+  *b = vzipq_s8(a02, a13).val[0];
 }
 
 static INLINE void transpose_concat_8x4(int8x8_t a0, int8x8_t a1, int8x8_t a2,
@@ -401,14 +398,13 @@
   int8x16_t a2q = vcombine_s8(a2, vdup_n_s8(0));
   int8x16_t a3q = vcombine_s8(a3, vdup_n_s8(0));
 
-  int8x16_t a01 = vzipq_s8(a0q, a1q).val[0];
-  int8x16_t a23 = vzipq_s8(a2q, a3q).val[0];
+  int8x16_t a02 = vzipq_s8(a0q, a2q).val[0];
+  int8x16_t a13 = vzipq_s8(a1q, a3q).val[0];
 
-  int16x8x2_t a0123 =
-      vzipq_s16(vreinterpretq_s16_s8(a01), vreinterpretq_s16_s8(a23));
+  int8x16x2_t a0123 = vzipq_s8(a02, a13);
 
-  *b0 = vreinterpretq_s8_s16(a0123.val[0]);
-  *b1 = vreinterpretq_s8_s16(a0123.val[1]);
+  *b0 = a0123.val[0];
+  *b1 = a0123.val[1];
 }
 
 static INLINE int16x4_t convolve8_4_v(const int8x16_t samples_lo,
diff --git a/vpx_dsp/arm/vpx_convolve8_neon_i8mm.c b/vpx_dsp/arm/vpx_convolve8_neon_i8mm.c
index e582004..b9abe80 100644
--- a/vpx_dsp/arm/vpx_convolve8_neon_i8mm.c
+++ b/vpx_dsp/arm/vpx_convolve8_neon_i8mm.c
@@ -356,13 +356,10 @@
   uint8x16_t a2q = vcombine_u8(a2, vdup_n_u8(0));
   uint8x16_t a3q = vcombine_u8(a3, vdup_n_u8(0));
 
-  uint8x16_t a01 = vzipq_u8(a0q, a1q).val[0];
-  uint8x16_t a23 = vzipq_u8(a2q, a3q).val[0];
+  uint8x16_t a02 = vzipq_u8(a0q, a2q).val[0];
+  uint8x16_t a13 = vzipq_u8(a1q, a3q).val[0];
 
-  uint16x8_t a0123 =
-      vzipq_u16(vreinterpretq_u16_u8(a01), vreinterpretq_u16_u8(a23)).val[0];
-
-  *b = vreinterpretq_u8_u16(a0123);
+  *b = vzipq_u8(a02, a13).val[0];
 }
 
 static INLINE void transpose_concat_8x4(uint8x8_t a0, uint8x8_t a1,
@@ -382,14 +379,13 @@
   uint8x16_t a2q = vcombine_u8(a2, vdup_n_u8(0));
   uint8x16_t a3q = vcombine_u8(a3, vdup_n_u8(0));
 
-  uint8x16_t a01 = vzipq_u8(a0q, a1q).val[0];
-  uint8x16_t a23 = vzipq_u8(a2q, a3q).val[0];
+  uint8x16_t a02 = vzipq_u8(a0q, a2q).val[0];
+  uint8x16_t a13 = vzipq_u8(a1q, a3q).val[0];
 
-  uint16x8x2_t a0123 =
-      vzipq_u16(vreinterpretq_u16_u8(a01), vreinterpretq_u16_u8(a23));
+  uint8x16x2_t a0123 = vzipq_u8(a02, a13);
 
-  *b0 = vreinterpretq_u8_u16(a0123.val[0]);
-  *b1 = vreinterpretq_u8_u16(a0123.val[1]);
+  *b0 = a0123.val[0];
+  *b1 = a0123.val[1];
 }
 
 static INLINE int16x4_t convolve8_4_v(const uint8x16_t samples_lo,
diff --git a/vpx_dsp/loongarch/quantize_lsx.c b/vpx_dsp/loongarch/quantize_lsx.c
index 9bb1691..1299e75 100644
--- a/vpx_dsp/loongarch/quantize_lsx.c
+++ b/vpx_dsp/loongarch/quantize_lsx.c
@@ -91,7 +91,7 @@
 
 #if !CONFIG_VP9_HIGHBITDEPTH
 
-void vpx_quantize_b_lsx(const int16_t *coeff_ptr, intptr_t n_coeffs,
+void vpx_quantize_b_lsx(const tran_low_t *coeff_ptr, intptr_t n_coeffs,
                         const struct macroblock_plane *const mb_plane,
                         tran_low_t *qcoeff_ptr, tran_low_t *dqcoeff_ptr,
                         const int16_t *dequant_ptr, uint16_t *eob_ptr,
diff --git a/vpx_dsp/vpx_dsp.mk b/vpx_dsp/vpx_dsp.mk
index e3de5bc..357ad08 100644
--- a/vpx_dsp/vpx_dsp.mk
+++ b/vpx_dsp/vpx_dsp.mk
@@ -395,6 +395,7 @@
 DSP_SRCS-$(HAVE_AVX2)   += x86/sad_avx2.c
 DSP_SRCS-$(HAVE_AVX2)   += x86/subtract_avx2.c
 DSP_SRCS-$(HAVE_AVX512) += x86/sad4d_avx512.c
+DSP_SRCS-$(HAVE_AVX512) += x86/sad_avx512.c
 
 DSP_SRCS-$(HAVE_SSE2)   += x86/sad4d_sse2.asm
 DSP_SRCS-$(HAVE_SSE2)   += x86/sad_sse2.asm
diff --git a/vpx_dsp/vpx_dsp_common.h b/vpx_dsp/vpx_dsp_common.h
index 528f33f..9d26682 100644
--- a/vpx_dsp/vpx_dsp_common.h
+++ b/vpx_dsp/vpx_dsp_common.h
@@ -47,13 +47,12 @@
 
 typedef int16_t tran_coef_t;
 
-// Visual Studio 2022 (cl.exe) targeting AArch64 with optimizations enabled
-// produces invalid code for clip_pixel() when the return type is uint8_t.
-// See:
+// Visual Studio 2022 (cl.exe) < 17.7 targeting AArch64 with optimizations
+// enabled produces invalid code for clip_pixel() when the return type is
+// uint8_t. See:
 // https://developercommunity.visualstudio.com/t/Misoptimization-for-ARM64-in-VS-2022-17/10363361
-// TODO(jzern): check the compiler version after a fix for the issue is
-// released.
-#if defined(_MSC_VER) && defined(_M_ARM64) && !defined(__clang__)
+#if defined(_MSC_VER) && _MSC_VER < 1937 && defined(_M_ARM64) && \
+    !defined(__clang__)
 static INLINE int clip_pixel(int val) {
   return (val > 255) ? 255 : (val < 0) ? 0 : val;
 }
diff --git a/vpx_dsp/vpx_dsp_rtcd_defs.pl b/vpx_dsp/vpx_dsp_rtcd_defs.pl
index d845830..db508e9 100644
--- a/vpx_dsp/vpx_dsp_rtcd_defs.pl
+++ b/vpx_dsp/vpx_dsp_rtcd_defs.pl
@@ -723,10 +723,10 @@
 #
 if (vpx_config("CONFIG_VP9_ENCODER") eq "yes") {
   add_proto qw/void vpx_quantize_b/, "const tran_low_t *coeff_ptr, intptr_t n_coeffs, const struct macroblock_plane *const mb_plane, tran_low_t *qcoeff_ptr, tran_low_t *dqcoeff_ptr, const int16_t *dequant_ptr, uint16_t *eob_ptr, const struct ScanOrder *const scan_order";
-  specialize qw/vpx_quantize_b neon sse2 ssse3 avx avx2 vsx lsx/;
+  specialize qw/vpx_quantize_b neon sse2 ssse3 avx avx2 vsx/;
 
   add_proto qw/void vpx_quantize_b_32x32/, "const tran_low_t *coeff_ptr, const struct macroblock_plane *const mb_plane, tran_low_t *qcoeff_ptr, tran_low_t *dqcoeff_ptr, const int16_t *dequant_ptr, uint16_t *eob_ptr, const struct ScanOrder *const scan_order";
-  specialize qw/vpx_quantize_b_32x32 neon ssse3 avx avx2 vsx lsx/;
+  specialize qw/vpx_quantize_b_32x32 neon ssse3 avx avx2 vsx/;
 
   if (vpx_config("CONFIG_VP9_HIGHBITDEPTH") eq "yes") {
     add_proto qw/void vpx_highbd_quantize_b/, "const tran_low_t *coeff_ptr, intptr_t n_coeffs, const struct macroblock_plane *const mb_plane, tran_low_t *qcoeff_ptr, tran_low_t *dqcoeff_ptr, const int16_t *dequant_ptr, uint16_t *eob_ptr, const struct ScanOrder *const scan_order";
@@ -734,7 +734,11 @@
 
     add_proto qw/void vpx_highbd_quantize_b_32x32/, "const tran_low_t *coeff_ptr, const struct macroblock_plane *const mb_plane, tran_low_t *qcoeff_ptr, tran_low_t *dqcoeff_ptr, const int16_t *dequant_ptr, uint16_t *eob_ptr, const struct ScanOrder *const scan_order";
     specialize qw/vpx_highbd_quantize_b_32x32 neon sse2 avx2/;
-  }  # CONFIG_VP9_HIGHBITDEPTH
+  } else {
+    specialize qw/vpx_quantize_b lsx/;
+
+    specialize qw/vpx_quantize_b_32x32 lsx/;
+  } # CONFIG_VP9_HIGHBITDEPTH
 }  # CONFIG_VP9_ENCODER
 
 if (vpx_config("CONFIG_ENCODERS") eq "yes") {
@@ -751,10 +755,10 @@
 # Single block SAD
 #
 add_proto qw/unsigned int vpx_sad64x64/, "const uint8_t *src_ptr, int src_stride, const uint8_t *ref_ptr, int ref_stride";
-specialize qw/vpx_sad64x64 neon neon_dotprod avx2 msa sse2 vsx mmi lsx/;
+specialize qw/vpx_sad64x64 neon neon_dotprod avx512 avx2 msa sse2 vsx mmi lsx/;
 
 add_proto qw/unsigned int vpx_sad64x32/, "const uint8_t *src_ptr, int src_stride, const uint8_t *ref_ptr, int ref_stride";
-specialize qw/vpx_sad64x32 neon neon_dotprod avx2 msa sse2 vsx mmi/;
+specialize qw/vpx_sad64x32 neon neon_dotprod avx512 avx2 msa sse2 vsx mmi/;
 
 add_proto qw/unsigned int vpx_sad32x64/, "const uint8_t *src_ptr, int src_stride, const uint8_t *ref_ptr, int ref_stride";
 specialize qw/vpx_sad32x64 neon neon_dotprod avx2 msa sse2 vsx mmi/;
@@ -790,10 +794,10 @@
 specialize qw/vpx_sad4x4 neon msa sse2 mmi/;
 
 add_proto qw/unsigned int vpx_sad_skip_64x64/, "const uint8_t *src_ptr, int src_stride, const uint8_t *ref_ptr, int ref_stride";
-specialize qw/vpx_sad_skip_64x64 neon neon_dotprod avx2 sse2/;
+specialize qw/vpx_sad_skip_64x64 neon neon_dotprod avx512 avx2 sse2/;
 
 add_proto qw/unsigned int vpx_sad_skip_64x32/, "const uint8_t *src_ptr, int src_stride, const uint8_t *ref_ptr, int ref_stride";
-specialize qw/vpx_sad_skip_64x32 neon neon_dotprod avx2 sse2/;
+specialize qw/vpx_sad_skip_64x32 neon neon_dotprod avx512 avx2 sse2/;
 
 add_proto qw/unsigned int vpx_sad_skip_32x64/, "const uint8_t *src_ptr, int src_stride, const uint8_t *ref_ptr, int ref_stride";
 specialize qw/vpx_sad_skip_32x64 neon neon_dotprod avx2 sse2/;
@@ -889,10 +893,10 @@
 }  # CONFIG_VP9_ENCODER
 
 add_proto qw/unsigned int vpx_sad64x64_avg/, "const uint8_t *src_ptr, int src_stride, const uint8_t *ref_ptr, int ref_stride, const uint8_t *second_pred";
-specialize qw/vpx_sad64x64_avg neon neon_dotprod avx2 msa sse2 vsx mmi lsx/;
+specialize qw/vpx_sad64x64_avg neon neon_dotprod avx512 avx2 msa sse2 vsx mmi lsx/;
 
 add_proto qw/unsigned int vpx_sad64x32_avg/, "const uint8_t *src_ptr, int src_stride, const uint8_t *ref_ptr, int ref_stride, const uint8_t *second_pred";
-specialize qw/vpx_sad64x32_avg neon neon_dotprod avx2 msa sse2 vsx mmi/;
+specialize qw/vpx_sad64x32_avg neon neon_dotprod avx512 avx2 msa sse2 vsx mmi/;
 
 add_proto qw/unsigned int vpx_sad32x64_avg/, "const uint8_t *src_ptr, int src_stride, const uint8_t *ref_ptr, int ref_stride, const uint8_t *second_pred";
 specialize qw/vpx_sad32x64_avg neon neon_dotprod avx2 msa sse2 vsx mmi/;
@@ -970,10 +974,10 @@
 specialize qw/vpx_sad4x4x4d neon msa sse2 mmi/;
 
 add_proto qw/void vpx_sad_skip_64x64x4d/, "const uint8_t *src_ptr, int src_stride, const uint8_t *const ref_array[4], int ref_stride, uint32_t sad_array[4]";
-specialize qw/vpx_sad_skip_64x64x4d neon neon_dotprod avx2 sse2/;
+specialize qw/vpx_sad_skip_64x64x4d neon neon_dotprod avx512 avx2 sse2/;
 
 add_proto qw/void vpx_sad_skip_64x32x4d/, "const uint8_t *src_ptr, int src_stride, const uint8_t *const ref_array[4], int ref_stride, uint32_t sad_array[4]";
-specialize qw/vpx_sad_skip_64x32x4d neon neon_dotprod avx2 sse2/;
+specialize qw/vpx_sad_skip_64x32x4d neon neon_dotprod avx512 avx2 sse2/;
 
 add_proto qw/void vpx_sad_skip_32x64x4d/, "const uint8_t *src_ptr, int src_stride, const uint8_t *const ref_array[4], int ref_stride, uint32_t sad_array[4]";
 specialize qw/vpx_sad_skip_32x64x4d neon neon_dotprod avx2 sse2/;
diff --git a/vpx_dsp/x86/sad4d_avx512.c b/vpx_dsp/x86/sad4d_avx512.c
index cfd23fe..cc36cae 100644
--- a/vpx_dsp/x86/sad4d_avx512.c
+++ b/vpx_dsp/x86/sad4d_avx512.c
@@ -11,9 +11,10 @@
 #include "./vpx_dsp_rtcd.h"
 #include "vpx/vpx_integer.h"
 
-void vpx_sad64x64x4d_avx512(const uint8_t *src_ptr, int src_stride,
-                            const uint8_t *const ref_array[4], int ref_stride,
-                            uint32_t sad_array[4]) {
+static INLINE void sad64xhx4d_avx512(const uint8_t *src_ptr, int src_stride,
+                                     const uint8_t *const ref_array[4],
+                                     int ref_stride, int h,
+                                     uint32_t sad_array[4]) {
   __m512i src_reg, ref0_reg, ref1_reg, ref2_reg, ref3_reg;
   __m512i sum_ref0, sum_ref1, sum_ref2, sum_ref3;
   __m512i sum_mlow, sum_mhigh;
@@ -28,7 +29,7 @@
   sum_ref1 = _mm512_set1_epi16(0);
   sum_ref2 = _mm512_set1_epi16(0);
   sum_ref3 = _mm512_set1_epi16(0);
-  for (i = 0; i < 64; i++) {
+  for (i = 0; i < h; i++) {
     // load src and all ref[]
     src_reg = _mm512_loadu_si512((const __m512i *)src_ptr);
     ref0_reg = _mm512_loadu_si512((const __m512i *)ref0);
@@ -81,3 +82,24 @@
     _mm_storeu_si128((__m128i *)(sad_array), sum128);
   }
 }
+
+void vpx_sad64x64x4d_avx512(const uint8_t *src, int src_stride,
+                            const uint8_t *const ref_array[4], int ref_stride,
+                            uint32_t sad_array[4]) {
+  sad64xhx4d_avx512(src, src_stride, ref_array, ref_stride, 64, sad_array);
+}
+
+#define SADS64_H(h)                                                          \
+  void vpx_sad_skip_64x##h##x4d_avx512(                                      \
+      const uint8_t *src, int src_stride, const uint8_t *const ref_array[4], \
+      int ref_stride, uint32_t sad_array[4]) {                               \
+    sad64xhx4d_avx512(src, 2 * src_stride, ref_array, 2 * ref_stride,        \
+                      ((h) >> 1), sad_array);                                \
+    sad_array[0] <<= 1;                                                      \
+    sad_array[1] <<= 1;                                                      \
+    sad_array[2] <<= 1;                                                      \
+    sad_array[3] <<= 1;                                                      \
+  }
+
+SADS64_H(64)
+SADS64_H(32)
diff --git a/vpx_dsp/x86/sad_avx512.c b/vpx_dsp/x86/sad_avx512.c
new file mode 100644
index 0000000..38bd3be
--- /dev/null
+++ b/vpx_dsp/x86/sad_avx512.c
@@ -0,0 +1,88 @@
+/*
+ *  Copyright (c) 2025 The WebM 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 <immintrin.h>
+#include "./vpx_dsp_rtcd.h"
+#include "vpx_ports/mem.h"
+
+static INLINE unsigned int sad64xh_avx512(const uint8_t *src_ptr,
+                                          int src_stride,
+                                          const uint8_t *ref_ptr,
+                                          int ref_stride, int h) {
+  int i, res;
+  __m512i sad_reg, ref_reg;
+  __m512i sum_sad = _mm512_setzero_si512();
+  for (i = 0; i < h; i++) {
+    ref_reg = _mm512_loadu_si512((const __m512i *)ref_ptr);
+    sad_reg =
+        _mm512_sad_epu8(ref_reg, _mm512_loadu_si512((__m512 const *)src_ptr));
+    sum_sad = _mm512_add_epi32(sum_sad, sad_reg);
+    ref_ptr += ref_stride;
+    src_ptr += src_stride;
+  }
+  res = _mm512_reduce_add_epi32(sum_sad);
+  return res;
+}
+
+#define FSAD64_H(h)                                                           \
+  unsigned int vpx_sad64x##h##_avx512(const uint8_t *src_ptr, int src_stride, \
+                                      const uint8_t *ref_ptr,                 \
+                                      int ref_stride) {                       \
+    return sad64xh_avx512(src_ptr, src_stride, ref_ptr, ref_stride, h);       \
+  }
+
+#define FSADS64_H(h)                                                  \
+  unsigned int vpx_sad_skip_64x##h##_avx512(                          \
+      const uint8_t *src_ptr, int src_stride, const uint8_t *ref_ptr, \
+      int ref_stride) {                                               \
+    return 2 * sad64xh_avx512(src_ptr, src_stride * 2, ref_ptr,       \
+                              ref_stride * 2, h / 2);                 \
+  }
+
+#define FSAD64  \
+  FSAD64_H(64)  \
+  FSAD64_H(32)  \
+  FSADS64_H(64) \
+  FSADS64_H(32)
+
+FSAD64
+
+#undef FSAD64
+#undef FSAD64_H
+#undef FSADS64_H
+
+#define FSADAVG64_H(h)                                                         \
+  unsigned int vpx_sad64x##h##_avg_avx512(                                     \
+      const uint8_t *src_ptr, int src_stride, const uint8_t *ref_ptr,          \
+      int ref_stride, const uint8_t *second_pred) {                            \
+    int i;                                                                     \
+    __m512i sad_reg, ref_reg;                                                  \
+    __m512i sum_sad = _mm512_setzero_si512();                                  \
+    for (i = 0; i < h; i++) {                                                  \
+      ref_reg = _mm512_loadu_si512((const __m512i *)ref_ptr);                  \
+      ref_reg = _mm512_avg_epu8(                                               \
+          ref_reg, _mm512_loadu_si512((const __m512i *)second_pred));          \
+      sad_reg = _mm512_sad_epu8(ref_reg,                                       \
+                                _mm512_loadu_si512((const __m512i *)src_ptr)); \
+      sum_sad = _mm512_add_epi32(sum_sad, sad_reg);                            \
+      ref_ptr += ref_stride;                                                   \
+      src_ptr += src_stride;                                                   \
+      second_pred += 64;                                                       \
+    }                                                                          \
+    return (unsigned int)_mm512_reduce_add_epi32(sum_sad);                     \
+  }
+
+#define FSADAVG64 \
+  FSADAVG64_H(64) \
+  FSADAVG64_H(32)
+
+FSADAVG64
+
+#undef FSADAVG64
+#undef FSADAVG64_H
diff --git a/vpx_ports/vpx_timer.h b/vpx_ports/vpx_timer.h
index 4934d52..55e7891 100644
--- a/vpx_ports/vpx_timer.h
+++ b/vpx_ports/vpx_timer.h
@@ -31,17 +31,17 @@
 /*
  * POSIX specific includes
  */
-#include <sys/time.h>
+#include <time.h>
 
 /* timersub is not provided by msys at this time. */
-#ifndef timersub
-#define timersub(a, b, result)                       \
+#ifndef timersub_ns
+#define timersub_ns(a, b, result)                    \
   do {                                               \
     (result)->tv_sec = (a)->tv_sec - (b)->tv_sec;    \
-    (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
-    if ((result)->tv_usec < 0) {                     \
+    (result)->tv_nsec = (a)->tv_nsec - (b)->tv_nsec; \
+    if ((result)->tv_nsec < 0) {                     \
       --(result)->tv_sec;                            \
-      (result)->tv_usec += 1000000;                  \
+      (result)->tv_nsec += 1000000000;               \
     }                                                \
   } while (0)
 #endif
@@ -51,23 +51,27 @@
 #if defined(_WIN32)
   LARGE_INTEGER begin, end;
 #else
-  struct timeval begin, end;
+  struct timespec begin, end;
 #endif
 };
 
 static INLINE void vpx_usec_timer_start(struct vpx_usec_timer *t) {
 #if defined(_WIN32)
   QueryPerformanceCounter(&t->begin);
+#elif defined(CLOCK_MONOTONIC_RAW)
+  clock_gettime(CLOCK_MONOTONIC_RAW, &t->begin);
 #else
-  gettimeofday(&t->begin, NULL);
+  clock_gettime(CLOCK_MONOTONIC, &t->begin);
 #endif
 }
 
 static INLINE void vpx_usec_timer_mark(struct vpx_usec_timer *t) {
 #if defined(_WIN32)
   QueryPerformanceCounter(&t->end);
+#elif defined(CLOCK_MONOTONIC_RAW)
+  clock_gettime(CLOCK_MONOTONIC_RAW, &t->end);
 #else
-  gettimeofday(&t->end, NULL);
+  clock_gettime(CLOCK_MONOTONIC, &t->end);
 #endif
 }
 
@@ -80,18 +84,18 @@
   QueryPerformanceFrequency(&freq);
   return diff.QuadPart * 1000000 / freq.QuadPart;
 #else
-  struct timeval diff;
+  struct timespec diff;
 
-  timersub(&t->end, &t->begin, &diff);
-  return (int64_t)diff.tv_sec * 1000000 + diff.tv_usec;
+  timersub_ns(&t->end, &t->begin, &diff);
+  return (int64_t)diff.tv_sec * 1000000 + diff.tv_nsec / 1000;
 #endif
 }
 
 #else /* CONFIG_OS_SUPPORT = 0*/
 
 /* Empty timer functions if CONFIG_OS_SUPPORT = 0 */
-#ifndef timersub
-#define timersub(a, b, result)
+#ifndef timersub_ns
+#define timersub_ns(a, b, result)
 #endif
 
 struct vpx_usec_timer {
diff --git a/vpx_ports/x86.h b/vpx_ports/x86.h
index 3fa50b2..4e450f8 100644
--- a/vpx_ports/x86.h
+++ b/vpx_ports/x86.h
@@ -164,6 +164,22 @@
 #define BIT(n) (1u << (n))
 #endif
 
+#define MMX_BITS BIT(23)
+#define SSE_BITS BIT(25)
+#define SSE2_BITS BIT(26)
+#define SSE3_BITS BIT(0)
+#define SSSE3_BITS BIT(9)
+#define SSE4_1_BITS BIT(19)
+// Bits 27 (OSXSAVE) & 28 (256-bit AVX)
+#define AVX_BITS (BIT(27) | BIT(28))
+#define AVX2_BITS BIT(5)
+// Bits 16 (AVX-512F) & 17 (AVX-512DQ) & 28 (AVX-512CD) & 30 (AVX-512BW)
+// & 31 (AVX-512VL)
+#define AVX512_BITS (BIT(16) | BIT(17) | BIT(28) | BIT(30) | BIT(31))
+
+#define FEATURE_SET(reg, feature) \
+  (((reg) & (feature##_BITS)) == (feature##_BITS))
+
 static INLINE int x86_simd_caps(void) {
   unsigned int flags = 0;
   unsigned int mask = ~0u;
@@ -173,58 +189,41 @@
 
   /* See if the CPU capabilities are being overridden by the environment */
   env = getenv("VPX_SIMD_CAPS");
-
   if (env && *env) return (int)strtol(env, NULL, 0);
 
   env = getenv("VPX_SIMD_CAPS_MASK");
-
   if (env && *env) mask = (unsigned int)strtoul(env, NULL, 0);
 
   /* Ensure that the CPUID instruction supports extended features */
   cpuid(0, 0, max_cpuid_val, reg_ebx, reg_ecx, reg_edx);
-
   if (max_cpuid_val < 1) return 0;
 
   /* Get the standard feature flags */
   cpuid(1, 0, reg_eax, reg_ebx, reg_ecx, reg_edx);
 
-  if (reg_edx & BIT(23)) flags |= HAS_MMX;
+  flags |= FEATURE_SET(reg_edx, MMX) ? HAS_MMX : 0;
+  flags |= FEATURE_SET(reg_edx, SSE) ? HAS_SSE : 0;
+  flags |= FEATURE_SET(reg_edx, SSE2) ? HAS_SSE2 : 0;
+  flags |= FEATURE_SET(reg_ecx, SSE3) ? HAS_SSE3 : 0;
+  flags |= FEATURE_SET(reg_ecx, SSSE3) ? HAS_SSSE3 : 0;
+  flags |= FEATURE_SET(reg_ecx, SSE4_1) ? HAS_SSE4_1 : 0;
 
-  if (reg_edx & BIT(25)) flags |= HAS_SSE; /* aka xmm */
-
-  if (reg_edx & BIT(26)) flags |= HAS_SSE2; /* aka wmt */
-
-  if (reg_ecx & BIT(0)) flags |= HAS_SSE3;
-
-  if (reg_ecx & BIT(9)) flags |= HAS_SSSE3;
-
-  if (reg_ecx & BIT(19)) flags |= HAS_SSE4_1;
-
-  // bits 27 (OSXSAVE) & 28 (256-bit AVX)
-  if ((reg_ecx & (BIT(27) | BIT(28))) == (BIT(27) | BIT(28))) {
+  if (FEATURE_SET(reg_ecx, AVX)) {
     // Check for OS-support of YMM state. Necessary for AVX and AVX2.
     if ((xgetbv() & 0x6) == 0x6) {
       flags |= HAS_AVX;
-
       if (max_cpuid_val >= 7) {
         /* Get the leaf 7 feature flags. Needed to check for AVX2 support */
         cpuid(7, 0, reg_eax, reg_ebx, reg_ecx, reg_edx);
-
-        if (reg_ebx & BIT(5)) flags |= HAS_AVX2;
-
-        // bits 16 (AVX-512F) & 17 (AVX-512DQ) & 28 (AVX-512CD) &
-        // 30 (AVX-512BW) & 32 (AVX-512VL)
-        if ((reg_ebx & (BIT(16) | BIT(17) | BIT(28) | BIT(30) | BIT(31))) ==
-            (BIT(16) | BIT(17) | BIT(28) | BIT(30) | BIT(31))) {
+        flags |= FEATURE_SET(reg_ebx, AVX2) ? HAS_AVX2 : 0;
+        if (FEATURE_SET(reg_ebx, AVX512)) {
           // Check for OS-support of ZMM and YMM state. Necessary for AVX-512.
           if ((xgetbv() & 0xe6) == 0xe6) flags |= HAS_AVX512;
         }
       }
     }
   }
-
   (void)reg_eax;  // Avoid compiler warning on unused-but-set variable.
-
   return flags & mask;
 }
 
diff --git a/vpx_scale/generic/yv12config.c b/vpx_scale/generic/yv12config.c
index c52dab0..c04315a 100644
--- a/vpx_scale/generic/yv12config.c
+++ b/vpx_scale/generic/yv12config.c
@@ -10,6 +10,7 @@
 
 #include <assert.h>
 #include <limits.h>
+#include <stdint.h>
 
 #include "vpx_scale/yv12config.h"
 #include "vpx_mem/vpx_mem.h"
@@ -199,11 +200,13 @@
     if (frame_size > VPX_MAX_ALLOCABLE_MEMORY / REF_FRAMES) return -1;
 #endif  // VPX_MAX_ALLOCABLE_MEMORY
 
+#if UINT64_MAX > SIZE_MAX
     // frame_size is stored in buffer_alloc_sz, which is a size_t. If it won't
     // fit, fail early.
     if (frame_size > SIZE_MAX) {
       return -1;
     }
+#endif
 
     if (cb != NULL) {
       const int align_addr_extra_size = 31;
diff --git a/vpxenc.c b/vpxenc.c
index 5e9fdde..cab7d34 100644
--- a/vpxenc.c
+++ b/vpxenc.c
@@ -852,7 +852,7 @@
   }
 
   if (prev) {
-    memcpy(stream, prev, sizeof(*stream));
+    *stream = *prev;
     stream->index++;
     prev->next = stream;
   } else {