Add a simple frame data inspection API.

This patch adds a decoder control that exposes frame data via a simple
 callback.

Change-Id: Icae73ae6b5da8a7783db9fadb1fff4a85d77174b
diff --git a/aom/aomdx.h b/aom/aomdx.h
index 2a00ecb..99f50c1 100644
--- a/aom/aomdx.h
+++ b/aom/aomdx.h
@@ -41,6 +41,23 @@
  */
 typedef struct Accounting Accounting;
 
+/** Callback that inspects decoder frame data.
+ */
+typedef void (*aom_inspect_cb)(void *decoder, void *ctx);
+
+/*!\brief Structure to hold inspection callback and context.
+ *
+ * Defines a structure to hold the inspection callback function and calling
+ * context.
+ */
+typedef struct aom_inspect_init {
+  /*! Inspection callback. */
+  aom_inspect_cb inspect_cb;
+
+  /*! Inspection context. */
+  void *inspect_ctx;
+} aom_inspect_init;
+
 /*!\enum aom_dec_control_id
  * \brief AOM decoder control functions
  *
@@ -129,6 +146,12 @@
   AV1_SET_DECODE_TILE_ROW,
   AV1_SET_DECODE_TILE_COL,
 
+  /** control function to set an aom_inspect_cb callback that is invoked each
+   * time a frame is decoded.  When compiled without --enable-inspection, this
+   * returns AOM_CODEC_INCAPABLE.
+   */
+  AV1_SET_INSPECTION_CALLBACK,
+
   AOM_DECODER_CTRL_ID_MAX,
 };
 
@@ -184,6 +207,8 @@
 #define AOM_CTRL_AV1_SET_DECODE_TILE_ROW
 AOM_CTRL_USE_TYPE(AV1_SET_DECODE_TILE_COL, int)
 #define AOM_CTRL_AV1_SET_DECODE_TILE_COL
+AOM_CTRL_USE_TYPE(AV1_SET_INSPECTION_CALLBACK, aom_inspect_init *)
+#define AOM_CTRL_AV1_SET_INSPECTION_CALLBACK
 /*!\endcond */
 /*! @} - end defgroup aom_decoder */
 
diff --git a/av1/av1.cmake b/av1/av1.cmake
index 2253c9b..eb25b8a 100644
--- a/av1/av1.cmake
+++ b/av1/av1.cmake
@@ -85,6 +85,13 @@
     "${AOM_ROOT}/av1/decoder/dthread.c"
     "${AOM_ROOT}/av1/decoder/dthread.h")
 
+if (CONFIG_INSPECTION)
+  set(AOM_AV1_DECODER_SOURCES
+      ${AOM_AV1_DECODER_SOURCES}
+      "${AOM_ROOT}/av1/decoder/inspection.c"
+      "${AOM_ROOT}/av1/decoder/inspection.h")
+endif ()
+
 set(AOM_AV1_ENCODER_SOURCES
     "${AOM_ROOT}/av1/av1_cx_iface.c"
     "${AOM_ROOT}/av1/encoder/aq_complexity.c"
diff --git a/av1/av1_dx.mk b/av1/av1_dx.mk
index 9cad308..4ab873c 100644
--- a/av1/av1_dx.mk
+++ b/av1/av1_dx.mk
@@ -33,6 +33,11 @@
 AV1_DX_SRCS-yes += decoder/dsubexp.c
 AV1_DX_SRCS-yes += decoder/dsubexp.h
 
+ifeq ($(CONFIG_INSPECTION),yes)
+AV1_DX_SRCS-yes += decoder/inspection.c
+AV1_DX_SRCS-yes += decoder/inspection.h
+endif
+
 ifeq ($(CONFIG_PVQ),yes)
 # PVQ from daala
 AV1_DX_SRCS-yes += decoder/pvq_decoder.c
diff --git a/av1/av1_dx_iface.c b/av1/av1_dx_iface.c
index b0499f5..8cd40a3 100644
--- a/av1/av1_dx_iface.c
+++ b/av1/av1_dx_iface.c
@@ -80,6 +80,11 @@
   void *ext_priv;  // Private data associated with the external frame buffers.
   aom_get_frame_buffer_cb_fn_t get_ext_fb_cb;
   aom_release_frame_buffer_cb_fn_t release_ext_fb_cb;
+
+#if CONFIG_INSPECTION
+  aom_inspect_cb inspect_cb;
+  void *inspect_ctx;
+#endif
 };
 
 static aom_codec_err_t decoder_init(aom_codec_ctx_t *ctx,
@@ -483,6 +488,10 @@
     // decrypt config between frames.
     frame_worker_data->pbi->decrypt_cb = ctx->decrypt_cb;
     frame_worker_data->pbi->decrypt_state = ctx->decrypt_state;
+#if CONFIG_INSPECTION
+    frame_worker_data->pbi->inspect_cb = ctx->inspect_cb;
+    frame_worker_data->pbi->inspect_ctx = ctx->inspect_ctx;
+#endif
 
 #if CONFIG_EXT_TILE
     frame_worker_data->pbi->dec_tile_row = ctx->decode_tile_row;
@@ -1133,6 +1142,20 @@
   return AOM_CODEC_OK;
 }
 
+static aom_codec_err_t ctrl_set_inspection_callback(aom_codec_alg_priv_t *ctx,
+                                                    va_list args) {
+#if !CONFIG_INSPECTION
+  (void)ctx;
+  (void)args;
+  return AOM_CODEC_INCAPABLE;
+#else
+  aom_inspect_init *init = va_arg(args, aom_inspect_init *);
+  ctx->inspect_cb = init->inspect_cb;
+  ctx->inspect_ctx = init->inspect_ctx;
+  return AOM_CODEC_OK;
+#endif
+}
+
 static aom_codec_ctrl_fn_map_t decoder_ctrl_maps[] = {
   { AOM_COPY_REFERENCE, ctrl_copy_reference },
 
@@ -1149,6 +1172,7 @@
   { AV1_SET_SKIP_LOOP_FILTER, ctrl_set_skip_loop_filter },
   { AV1_SET_DECODE_TILE_ROW, ctrl_set_decode_tile_row },
   { AV1_SET_DECODE_TILE_COL, ctrl_set_decode_tile_col },
+  { AV1_SET_INSPECTION_CALLBACK, ctrl_set_inspection_callback },
 
   // Getters
   { AOMD_GET_FRAME_CORRUPTED, ctrl_get_frame_corrupted },
diff --git a/av1/decoder/decodeframe.c b/av1/decoder/decodeframe.c
index a12555e..5c4a371 100644
--- a/av1/decoder/decodeframe.c
+++ b/av1/decoder/decodeframe.c
@@ -33,6 +33,9 @@
 #include "av1/common/clpf.h"
 #include "av1/common/dering.h"
 #endif
+#if CONFIG_INSPECTION
+#include "av1/decoder/inspection.h"
+#endif
 #include "av1/common/common.h"
 #include "av1/common/entropy.h"
 #include "av1/common/entropymode.h"
@@ -5034,6 +5037,12 @@
                        "Decode failed. Frame data is corrupted.");
   }
 
+#if CONFIG_INSPECTION
+  if (pbi->inspect_cb != NULL) {
+    (*pbi->inspect_cb)(pbi, pbi->inspect_ctx);
+  }
+#endif
+
   // Non frame parallel update frame context here.
   if (!cm->error_resilient_mode && !context_updated)
     cm->frame_contexts[cm->frame_context_idx] = *cm->fc;
diff --git a/av1/decoder/decoder.h b/av1/decoder/decoder.h
index 12add96..7f09e7f 100644
--- a/av1/decoder/decoder.h
+++ b/av1/decoder/decoder.h
@@ -25,6 +25,9 @@
 #if CONFIG_ACCOUNTING
 #include "av1/common/accounting.h"
 #endif
+#if CONFIG_INSPECTION
+#include "av1/decoder/inspection.h"
+#endif
 
 #if CONFIG_PVQ
 #include "aom_dsp/entdec.h"
@@ -137,6 +140,10 @@
 #if CONFIG_REFERENCE_BUFFER
   SequenceHeader seq_params;
 #endif
+#if CONFIG_INSPECTION
+  aom_inspect_cb inspect_cb;
+  void *inspect_ctx;
+#endif
 } AV1Decoder;
 
 int av1_receive_compressed_data(struct AV1Decoder *pbi, size_t size,
diff --git a/av1/decoder/inspection.c b/av1/decoder/inspection.c
new file mode 100644
index 0000000..3736739
--- /dev/null
+++ b/av1/decoder/inspection.c
@@ -0,0 +1,74 @@
+#include "av1/decoder/decoder.h"
+#include "av1/decoder/inspection.h"
+#include "av1/common/enums.h"
+
+void ifd_init(insp_frame_data *fd, int frame_width, int frame_height) {
+  fd->mi_cols = ALIGN_POWER_OF_TWO(frame_width, MI_SIZE_LOG2) >> MI_SIZE_LOG2;
+  fd->mi_rows = ALIGN_POWER_OF_TWO(frame_height, MI_SIZE_LOG2) >> MI_SIZE_LOG2;
+  fd->mi_grid = (insp_mi_data *)aom_malloc(sizeof(insp_mi_data) * fd->mi_rows *
+                                           fd->mi_cols);
+}
+
+void ifd_clear(insp_frame_data *fd) {
+  aom_free(fd->mi_grid);
+  fd->mi_grid = NULL;
+}
+
+/* TODO(negge) This function may be called by more than one thread when using
+               a multi-threaded decoder and this may cause a data race. */
+int ifd_inspect(insp_frame_data *fd, void *decoder) {
+  struct AV1Decoder *pbi = (struct AV1Decoder *)decoder;
+  AV1_COMMON *const cm = &pbi->common;
+  // TODO(negge): Should this function just call ifd_clear() and ifd_init()?
+  if (fd->mi_rows != cm->mi_rows || fd->mi_cols != cm->mi_cols) {
+    return 0;
+  }
+  fd->show_frame = cm->show_frame;
+  fd->frame_type = cm->frame_type;
+  fd->base_qindex = cm->base_qindex;
+#if CONFIG_ACCOUNTING
+  fd->accounting = &pbi->accounting;
+#endif
+#if CONFIG_CDEF
+// TODO(negge): copy per frame CDEF data
+#endif
+  int i, j;
+  for (i = 0; i < MAX_SEGMENTS; i++) {
+    for (j = 0; j < 2; j++) {
+      fd->y_dequant[i][j] = cm->y_dequant[i][j];
+      fd->uv_dequant[i][j] = cm->uv_dequant[i][j];
+    }
+  }
+  for (j = 0; j < cm->mi_rows; j++) {
+    for (i = 0; i < cm->mi_cols; i++) {
+      const MB_MODE_INFO *mbmi =
+          &cm->mi_grid_visible[j * cm->mi_stride + i]->mbmi;
+      insp_mi_data *mi = &fd->mi_grid[j * cm->mi_cols + j];
+      // Segment
+      mi->segment_id = mbmi->segment_id;
+      // Motion Vectors
+      mi->mv[0].row = mbmi->mv[0].as_mv.row;
+      mi->mv[0].col = mbmi->mv[0].as_mv.col;
+      mi->mv[1].row = mbmi->mv[1].as_mv.row;
+      mi->mv[1].col = mbmi->mv[1].as_mv.col;
+      // Reference Frames
+      mi->ref_frame[0] = mbmi->ref_frame[0];
+      mi->ref_frame[1] = mbmi->ref_frame[1];
+      // Prediction Mode
+      mi->mode = mbmi->mode;
+      // Block Size
+      mi->sb_type = mbmi->sb_type;
+      // Skip Flag
+      mi->skip = mbmi->skip;
+      // Filters
+      mi->filter = mbmi->interp_filter;
+      // Transform
+      mi->tx_type = mbmi->tx_type;
+      mi->tx_size = mbmi->tx_size;
+#if CONFIG_CDEF
+// TODO(negge): copy per block CDEF data
+#endif
+    }
+  }
+  return 1;
+}
diff --git a/av1/decoder/inspection.h b/av1/decoder/inspection.h
new file mode 100644
index 0000000..37e7b95
--- /dev/null
+++ b/av1/decoder/inspection.h
@@ -0,0 +1,57 @@
+#ifndef AOM_INSPECTION_H_
+#define AOM_INSPECTION_H_
+
+#if CONFIG_ACCOUNTING
+#include "av1/common/accounting.h"
+#endif
+
+typedef void (*aom_inspect_cb)(void *decoder, void *data);
+
+typedef struct insp_mv insp_mv;
+
+struct insp_mv {
+  int16_t row;
+  int16_t col;
+};
+
+typedef struct insp_mi_data insp_mi_data;
+
+struct insp_mi_data {
+  insp_mv mv[2];
+  int8_t ref_frame[2];
+  int8_t mode;
+  int8_t sb_type;
+  int8_t skip;
+  int8_t segment_id;
+  int8_t filter;
+  int8_t tx_type;
+  int8_t tx_size;
+#if CONFIG_CDEF
+// TODO(negge): add per block CDEF data
+#endif
+};
+
+typedef struct insp_frame_data insp_frame_data;
+
+struct insp_frame_data {
+#if CONFIG_ACCOUNTING
+  Accounting *accounting;
+#endif
+  insp_mi_data *mi_grid;
+  int show_frame;
+  int frame_type;
+  int base_qindex;
+  int mi_rows;
+  int mi_cols;
+  int16_t y_dequant[MAX_SEGMENTS][2];
+  int16_t uv_dequant[MAX_SEGMENTS][2];
+#if CONFIG_CDEF
+// TODO(negge): add per frame CDEF data
+#endif
+};
+
+void ifd_init(insp_frame_data *fd, int frame_width, int frame_height);
+void ifd_clear(insp_frame_data *fd);
+int ifd_inspect(insp_frame_data *fd, void *decoder);
+
+#endif
diff --git a/configure b/configure
index 2704dc8..873851c 100755
--- a/configure
+++ b/configure
@@ -60,6 +60,7 @@
   ${toggle_webm_io}               enable input from and output to WebM container
   ${toggle_libyuv}                enable libyuv
   ${toggle_accounting}            enable bit accounting
+  ${toggle_inspection}            enable bitstream inspection
 
 Codecs:
   Codecs can be selectively enabled or disabled individually, or by family:
@@ -354,6 +355,7 @@
     webm_io
     libyuv
     accounting
+    inspection
     decode_perf_tests
     encode_perf_tests
     multi_res_encoding
@@ -415,6 +417,7 @@
     webm_io
     libyuv
     accounting
+    inspection
     decode_perf_tests
     encode_perf_tests
     multi_res_encoding