shithub: libvpx

Download patch

ref: 7ee697a5daea0dfd7470cc9d1b352de01af0836f
parent: 9f33d7530ab42e21c2ec2151a5db94fc623823f1
author: angiebird <angiebird@google.com>
date: Sun Nov 10 05:48:22 EST 2019

Add ComputeFirstPassStats()

Change-Id: Iaed87a4fa35f456aec5d88d07fade636280eb211

--- /dev/null
+++ b/test/simple_encode_test.cc
@@ -1,0 +1,36 @@
+#include <memory>
+#include <vector>
+#include "third_party/googletest/src/include/gtest/gtest.h"
+#include "vp9/simple_encode.h"
+
+namespace {
+
+TEST(SimpleEncode, ComputeFirstPassStats) {
+  int w = 352;
+  int h = 288;
+  int frame_rate_num = 30;
+  int frame_rate_den = 1;
+  int target_bitrate = 200;
+  int num_frames = 17;
+  // TODO(angiebird): Figure out how to upload test video to our codebase
+  FILE *file = fopen("bus_352x288_420_f20_b8.yuv", "r");
+  SimpleEncode simple_encode(w, h, frame_rate_num, frame_rate_den,
+                             target_bitrate, num_frames, file);
+  simple_encode.ComputeFirstPassStats();
+  std::vector<std::vector<double>> frame_stats =
+      simple_encode.ObserveFirstPassStats();
+  EXPECT_EQ(frame_stats.size(), static_cast<size_t>(num_frames));
+  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);
+  }
+}
+
+}  // namespace
--- a/test/test-data.mk
+++ b/test/test-data.mk
@@ -26,6 +26,7 @@
 LIBVPX_TEST_DATA-$(CONFIG_VP9_ENCODER) += rush_hour_444.y4m
 LIBVPX_TEST_DATA-$(CONFIG_VP9_ENCODER) += screendata.y4m
 LIBVPX_TEST_DATA-$(CONFIG_VP9_ENCODER) += niklas_640_480_30.yuv
+LIBVPX_TEST_DATA-$(CONFIG_RATE_CTRL) += bus_352x288_420_f20_b8.yuv
 
 # Test vectors
 LIBVPX_TEST_DATA-$(CONFIG_VP8_DECODER) += vp80-00-comprehensive-001.ivf
--- a/test/test-data.sha1
+++ b/test/test-data.sha1
@@ -1,3 +1,4 @@
+3eaf216d9fc8b4b9bb8c3956311f49a85974806c *bus_352x288_420_f20_b8.yuv
 d5dfb0151c9051f8c85999255645d7a23916d3c0 *hantro_collage_w352h288.yuv
 b87815bf86020c592ccc7a846ba2e28ec8043902 *hantro_odd.yuv
 76024eb753cdac6a5e5703aaea189d35c3c30ac7 *invalid-vp90-2-00-quantizer-00.webm.ivf.s5861_r01-05_b6-.v2.ivf
--- a/test/test.mk
+++ b/test/test.mk
@@ -58,6 +58,7 @@
 LIBVPX_TEST_SRCS-$(CONFIG_VP9_ENCODER) += svc_test.h
 LIBVPX_TEST_SRCS-$(CONFIG_VP9_ENCODER) += svc_end_to_end_test.cc
 LIBVPX_TEST_SRCS-$(CONFIG_VP9_ENCODER) += timestamp_test.cc
+LIBVPX_TEST_SRCS-$(CONFIG_RATE_CTRL)   += simple_encode_test.cc
 
 LIBVPX_TEST_SRCS-yes                   += decode_test_driver.cc
 LIBVPX_TEST_SRCS-yes                   += decode_test_driver.h
--- a/vp9/encoder/vp9_firstpass.c
+++ b/vp9/encoder/vp9_firstpass.c
@@ -3685,3 +3685,10 @@
   return coding_frame_num;
 }
 #endif
+
+FIRSTPASS_STATS vp9_get_frame_stats(const TWO_PASS *twopass) {
+  return twopass->this_frame_stats;
+}
+FIRSTPASS_STATS vp9_get_total_stats(const TWO_PASS *twopass) {
+  return twopass->total_stats;
+}
--- a/vp9/encoder/vp9_firstpass.h
+++ b/vp9/encoder/vp9_firstpass.h
@@ -256,6 +256,9 @@
                              int multi_layer_arf, int allow_alt_ref);
 #endif
 
+FIRSTPASS_STATS vp9_get_frame_stats(const TWO_PASS *two_pass);
+FIRSTPASS_STATS vp9_get_total_stats(const TWO_PASS *two_pass);
+
 #ifdef __cplusplus
 }  // extern "C"
 #endif
--- a/vp9/simple_encode.cc
+++ b/vp9/simple_encode.cc
@@ -1,27 +1,173 @@
+#include <vector>
 #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"
 
+// 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;
+}
+
 class SimpleEncode::impl {
  public:
   VP9_COMP *cpi;
-  BufferPool *buffer_pool;
+  std::vector<FIRSTPASS_STATS> frame_stats;
 };
 
+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));
+  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) {
+  vpx_free(cpi->common.buffer_pool);
+  vp9_remove_compressor(cpi);
+}
+
+static INLINE vpx_rational_t make_vpx_rational(int num, int den) {
+  vpx_rational_t v;
+  v.num = num;
+  v.den = den;
+  return v;
+}
+
 SimpleEncode::SimpleEncode(int frame_width, int frame_height,
-                           vpx_rational_t frame_rate, int target_bitrate)
+                           int frame_rate_num, int frame_rate_den,
+                           int target_bitrate, int num_frames, FILE *file)
     : pimpl{ std::unique_ptr<impl>(new impl()) } {
+  this->frame_width = frame_width;
+  this->frame_height = frame_height;
+  this->frame_rate_num = frame_rate_num;
+  this->frame_rate_den = frame_rate_den;
+  this->target_bitrate = target_bitrate;
+  this->num_frames = num_frames;
+  this->file = file;
+  pimpl->cpi = NULL;
+}
+
+void SimpleEncode::ComputeFirstPassStats() {
+  const vpx_img_fmt_t img_fmt = VPX_IMG_FMT_I420;
+  vpx_rational_t frame_rate = make_vpx_rational(frame_rate_num, frame_rate_den);
+  const VP9EncoderConfig oxcf = vp9_get_encoder_config(
+      frame_width, frame_height, frame_rate, target_bitrate, VPX_RC_FIRST_PASS);
+  VP9_COMP *cpi = init_encoder(&oxcf, img_fmt);
+  struct lookahead_ctx *lookahead = cpi->lookahead;
+  int i;
+  int use_highbitdepth = 0;
+#if CONFIG_VP9_HIGHBITDEPTH
+  use_highbitdepth = cpi->common.use_highbitdepth;
+#endif
+  vpx_image_t img;
+  vpx_img_alloc(&img, img_fmt, frame_width, frame_height, 1);
+  rewind(file);
+  pimpl->frame_stats.clear();
+  for (i = 0; i < num_frames; ++i) {
+    assert(!vp9_lookahead_full(lookahead));
+    if (img_read(&img, 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;  // Make vp9_get_compressed_data process a frame
+        size_t size;
+        unsigned int frame_flags = 0;
+        // TODO(angiebird): Call vp9_first_pass directly
+        vp9_get_compressed_data(cpi, &frame_flags, &size, NULL, &time_stamp,
+                                &time_end, flush);
+        // vp9_get_compressed_data only generates first pass stats not
+        // compresses data
+        assert(size == 0);
+      }
+      pimpl->frame_stats.push_back(vp9_get_frame_stats(&cpi->twopass));
+    }
+  }
+  vp9_end_first_pass(cpi);
+  // TODO(angiebird): Store the total_stats apart form frame_stats
+  pimpl->frame_stats.push_back(vp9_get_total_stats(&cpi->twopass));
+  free_encoder(cpi);
+  rewind(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 frame_stats is the total_stats.
+  // Change the code structure, so that we don't have to make these assumptions
+
+  // Note the last entry of frame_stats is the total_stats, we don't need it.
+  for (size_t i = 0; i < pimpl->frame_stats.size() - 1; ++i) {
+    double *buf_start = reinterpret_cast<double *>(&pimpl->frame_stats[i]);
+    // We use - 1 here because the last member in FIRSTPASS_STATS is not double
+    double *buf_end =
+        buf_start + sizeof(pimpl->frame_stats[i]) / sizeof(*buf_end) - 1;
+    std::vector<double> this_stats(buf_start, buf_end);
+    output_stats.push_back(this_stats);
+  }
+  return output_stats;
+}
+
+void SimpleEncode::StartEncode() {
+  vpx_rational_t frame_rate = make_vpx_rational(frame_rate_num, frame_rate_den);
   VP9EncoderConfig oxcf = vp9_get_encoder_config(
       frame_width, frame_height, frame_rate, target_bitrate, VPX_RC_LAST_PASS);
-  pimpl->buffer_pool = (BufferPool *)vpx_calloc(1, sizeof(*pimpl->buffer_pool));
-  vp9_initialize_enc();
-  pimpl->cpi = vp9_create_compressor(&oxcf, pimpl->buffer_pool);
-  vp9_update_compressor_with_img_fmt(pimpl->cpi, VPX_IMG_FMT_I420);
+  assert(pimpl->cpi == NULL);
+  pimpl->cpi = init_encoder(&oxcf, VPX_IMG_FMT_I420);
+  rewind(file);
 }
 
-SimpleEncode::~SimpleEncode() {
-  vpx_free(pimpl->buffer_pool);
-  vp9_remove_compressor(pimpl->cpi);
+void SimpleEncode::EndEncode() {
+  free_encoder(pimpl->cpi);
   pimpl->cpi = nullptr;
+  rewind(file);
 }
+
+SimpleEncode::~SimpleEncode() {}
--- a/vp9/simple_encode.h
+++ b/vp9/simple_encode.h
@@ -1,13 +1,35 @@
 #include <memory>
+#include <vector>
 class SimpleEncode {
  public:
-  SimpleEncode(int frame_width, int frame_height, vpx_rational_t frame_rate,
-               int target_bitrate);
+  SimpleEncode(int frame_width, int frame_height, int frame_rate_num,
+               int frame_rate_den, int target_bitrate, int num_frames,
+               FILE *file);
   ~SimpleEncode();
   SimpleEncode(SimpleEncode &&) = delete;
   SimpleEncode &operator=(SimpleEncode &&) = delete;
 
+  // Make encoder compute the first pass stats and store it internally for
+  // future encode
+  void ComputeFirstPassStats();
+
+  // Output the first pass stats
+  std::vector<std::vector<double>> ObserveFirstPassStats();
+
+  // Initialize the encoder for actual encoding
+  void StartEncode();
+
+  // Free the encoder
+  void EndEncode();
+
  private:
   class impl;
+  int frame_width;
+  int frame_height;
+  int frame_rate_num;
+  int frame_rate_den;
+  int target_bitrate;
+  int num_frames;
+  FILE *file;
   std::unique_ptr<impl> pimpl;
 };
--- a/vp9/vp9_cx_iface.c
+++ b/vp9/vp9_cx_iface.c
@@ -22,6 +22,7 @@
 #include "vp9/encoder/vp9_encoder.h"
 #include "vpx/vp8cx.h"
 #include "vp9/common/vp9_alloccommon.h"
+#include "vp9/vp9_cx_iface.h"
 #include "vp9/encoder/vp9_firstpass.h"
 #include "vp9/encoder/vp9_lookahead.h"
 #include "vp9/vp9_cx_iface.h"
@@ -1084,18 +1085,6 @@
 #endif
   }
   return index_sz;
-}
-
-static int64_t timebase_units_to_ticks(const vpx_rational64_t *timestamp_ratio,
-                                       int64_t n) {
-  return n * timestamp_ratio->num / timestamp_ratio->den;
-}
-
-static int64_t ticks_to_timebase_units(const vpx_rational64_t *timestamp_ratio,
-                                       int64_t n) {
-  int64_t round = timestamp_ratio->num / 2;
-  if (round > 0) --round;
-  return (n * timestamp_ratio->den + round) / timestamp_ratio->num;
 }
 
 static vpx_codec_frame_flags_t get_frame_pkt_flags(const VP9_COMP *cpi,
--- a/vp9/vp9_cx_iface.h
+++ b/vp9/vp9_cx_iface.h
@@ -23,6 +23,18 @@
                                         vpx_enc_pass enc_pass);
 FRAME_INFO vp9_get_frame_info(const VP9EncoderConfig *oxcf);
 
+static INLINE int64_t
+timebase_units_to_ticks(const vpx_rational64_t *timestamp_ratio, int64_t n) {
+  return n * timestamp_ratio->num / timestamp_ratio->den;
+}
+
+static INLINE int64_t
+ticks_to_timebase_units(const vpx_rational64_t *timestamp_ratio, int64_t n) {
+  int64_t round = timestamp_ratio->num / 2;
+  if (round > 0) --round;
+  return (n * timestamp_ratio->den + round) / timestamp_ratio->num;
+}
+
 #ifdef __cplusplus
 }  // extern "C"
 #endif