Let firstpass_info support past stats
We need past stats for keyframe detection.
In this CL cur_index is added to indicate current frame's index.
start_index is indicating the first available stats.
When cur_index != start_index, it means there are past stats
available.
We use FIRSTPASS_INFO_STATS_PAST_MIN to control the past stats size.
BUG=aomedia:3069
BUG=aomedia:3070
Change-Id: I5f359c5081ffeced21c50662f716ee958186eb79
diff --git a/av1/encoder/encoder.c b/av1/encoder/encoder.c
index 3d9afeb..18863d7 100644
--- a/av1/encoder/encoder.c
+++ b/av1/encoder/encoder.c
@@ -3938,8 +3938,14 @@
static AOM_INLINE void update_keyframe_counters(AV1_COMP *cpi) {
if (cpi->common.show_frame && cpi->rc.frames_to_key) {
#if !CONFIG_REALTIME_ONLY
- FIRSTPASS_STATS dummy_stats;
- av1_firstpass_info_pop(&cpi->ppi->twopass.firstpass_info, &dummy_stats);
+ FIRSTPASS_INFO *firstpass_info = &cpi->ppi->twopass.firstpass_info;
+ if (firstpass_info->past_stats_count > FIRSTPASS_INFO_STATS_PAST_MIN) {
+ av1_firstpass_info_move_cur_index_and_pop(firstpass_info);
+ } else {
+ // When there is not enough past stats, we move the current
+ // index without poping the past stats
+ av1_firstpass_info_move_cur_index(firstpass_info);
+ }
#endif
cpi->rc.frames_since_key++;
cpi->rc.frames_to_key--;
diff --git a/av1/encoder/firstpass.c b/av1/encoder/firstpass.c
index 4051b0c..28d527a 100644
--- a/av1/encoder/firstpass.c
+++ b/av1/encoder/firstpass.c
@@ -1390,7 +1390,10 @@
sizeof(firstpass_info->static_stats_buf) /
sizeof(firstpass_info->static_stats_buf[0]);
firstpass_info->start_index = 0;
+ firstpass_info->cur_index = 0;
firstpass_info->stats_count = 0;
+ firstpass_info->future_stats_count = 0;
+ firstpass_info->past_stats_count = 0;
av1_zero(firstpass_info->total_stats);
if (ext_stats_buf_size == 0) {
return AOM_CODEC_OK;
@@ -1401,7 +1404,10 @@
firstpass_info->stats_buf = ext_stats_buf;
firstpass_info->stats_buf_size = ext_stats_buf_size;
firstpass_info->start_index = 0;
+ firstpass_info->cur_index = 0;
firstpass_info->stats_count = firstpass_info->stats_buf_size;
+ firstpass_info->future_stats_count = firstpass_info->stats_count;
+ firstpass_info->past_stats_count = 0;
av1_zero(firstpass_info->total_stats);
for (int i = 0; i < firstpass_info->stats_count; ++i) {
av1_accumulate_stats(&firstpass_info->total_stats,
@@ -1411,20 +1417,43 @@
return AOM_CODEC_OK;
}
-aom_codec_err_t av1_firstpass_info_pop(FIRSTPASS_INFO *firstpass_info,
- FIRSTPASS_STATS *output_stats) {
- if (firstpass_info->stats_count > 0) {
- const int next_start =
- (firstpass_info->start_index + 1) % firstpass_info->stats_buf_size;
- *output_stats = firstpass_info->stats_buf[firstpass_info->start_index];
- firstpass_info->start_index = next_start;
- --firstpass_info->stats_count;
+aom_codec_err_t av1_firstpass_info_move_cur_index(
+ FIRSTPASS_INFO *firstpass_info) {
+ assert(firstpass_info->future_stats_count +
+ firstpass_info->past_stats_count ==
+ firstpass_info->stats_count);
+ if (firstpass_info->future_stats_count > 1) {
+ firstpass_info->cur_index =
+ (firstpass_info->cur_index + 1) % firstpass_info->stats_buf_size;
+ --firstpass_info->future_stats_count;
+ ++firstpass_info->past_stats_count;
return AOM_CODEC_OK;
} else {
return AOM_CODEC_ERROR;
}
}
+aom_codec_err_t av1_firstpass_info_pop(FIRSTPASS_INFO *firstpass_info) {
+ if (firstpass_info->stats_count > 0 && firstpass_info->past_stats_count > 0) {
+ const int next_start =
+ (firstpass_info->start_index + 1) % firstpass_info->stats_buf_size;
+ firstpass_info->start_index = next_start;
+ --firstpass_info->stats_count;
+ --firstpass_info->past_stats_count;
+ return AOM_CODEC_OK;
+ } else {
+ return AOM_CODEC_ERROR;
+ }
+}
+
+aom_codec_err_t av1_firstpass_info_move_cur_index_and_pop(
+ FIRSTPASS_INFO *firstpass_info) {
+ aom_codec_err_t ret = av1_firstpass_info_move_cur_index(firstpass_info);
+ if (ret != AOM_CODEC_OK) return ret;
+ ret = av1_firstpass_info_pop(firstpass_info);
+ return ret;
+}
+
aom_codec_err_t av1_firstpass_info_push(FIRSTPASS_INFO *firstpass_info,
const FIRSTPASS_STATS *input_stats) {
if (firstpass_info->stats_count < firstpass_info->stats_buf_size) {
@@ -1433,6 +1462,7 @@
firstpass_info->stats_buf_size;
firstpass_info->stats_buf[next_index] = *input_stats;
++firstpass_info->stats_count;
+ ++firstpass_info->future_stats_count;
av1_accumulate_stats(&firstpass_info->total_stats, input_stats);
return AOM_CODEC_OK;
} else {
@@ -1441,12 +1471,29 @@
}
const FIRSTPASS_STATS *av1_firstpass_info_peek(
- const FIRSTPASS_INFO *firstpass_info, int index_offset) {
- if (index_offset >= 0 && index_offset < firstpass_info->stats_count) {
- const int index = (firstpass_info->start_index + index_offset) %
+ const FIRSTPASS_INFO *firstpass_info, int offset_from_cur) {
+ if (offset_from_cur >= -firstpass_info->past_stats_count &&
+ offset_from_cur < firstpass_info->future_stats_count) {
+ const int index = (firstpass_info->cur_index + offset_from_cur) %
firstpass_info->stats_buf_size;
return &firstpass_info->stats_buf[index];
} else {
return NULL;
}
}
+
+int av1_firstpass_info_future_count(const FIRSTPASS_INFO *firstpass_info,
+ int offset_from_cur) {
+ if (offset_from_cur < firstpass_info->future_stats_count) {
+ return firstpass_info->future_stats_count - offset_from_cur;
+ }
+ return 0;
+}
+
+int av1_firstpass_info_past_count(const FIRSTPASS_INFO *firstpass_info,
+ int offset_from_cur) {
+ if (offset_from_cur >= -firstpass_info->past_stats_count) {
+ return offset_from_cur + firstpass_info->past_stats_count;
+ }
+ return 0;
+}
diff --git a/av1/encoder/firstpass.h b/av1/encoder/firstpass.h
index 1367cdb..8e3518e 100644
--- a/av1/encoder/firstpass.h
+++ b/av1/encoder/firstpass.h
@@ -162,7 +162,17 @@
double cor_coeff;
} FIRSTPASS_STATS;
-/*!\cond */
+// We want to keep one past stats for key frame detection
+// in test_candidate_kf()
+#define FIRSTPASS_INFO_STATS_PAST_MIN 1
+
+// The size of static buffer used in FIRSTPASS_INFO.
+#define FIRSTPASS_INFO_STATIC_BUF_SIZE \
+ (MAX_LAP_BUFFERS + FIRSTPASS_INFO_STATS_PAST_MIN)
+
+/*!
+ * \brief Data structure used for managing first pass stats
+ */
typedef struct {
/*!
* An static buffer that will be used when no ext_stats_buf is assigned.
@@ -173,7 +183,7 @@
* encode the video in the same pass. In this scenario, the stats will
* be pushed and popped from static_stats_buf.
*/
- FIRSTPASS_STATS static_stats_buf[MAX_LAP_BUFFERS];
+ FIRSTPASS_STATS static_stats_buf[FIRSTPASS_INFO_STATIC_BUF_SIZE];
/*!
* A pointer point to first pass stats.
* Note that this buffer will be used as ring buffer.
@@ -184,15 +194,37 @@
*/
int stats_buf_size;
/*!
- * start_index of the current frame's stats
+ * start index of the available frame stats
+ * Note that start_index doesn't always point to
+ * current frame's stats because we need to
+ * keep past stats as well. To access current
+ * frame's stats, please use cur_index.
*/
int start_index;
+
/*!
* count available stats stored in stats_buf
+ * the following condition should stay true
+ * stats_count = future_stats_count + past_stats_count
*/
int stats_count;
/*!
+ * index of the current frame's stats
+ */
+ int cur_index;
+
+ /*!
+ * count available future stats including current stats
+ */
+ int future_stats_count;
+
+ /*!
+ * count available past stats EXCLUDING current stats
+ */
+ int past_stats_count;
+
+ /*!
* Accumulation of the stats being pushed into firstpass_info
*/
FIRSTPASS_STATS total_stats;
@@ -214,15 +246,31 @@
FIRSTPASS_STATS *ext_stats_buf,
int ext_stats_buf_size);
+/*!\brief Move cur_index by 1
+ *
+ * \ingroup rate_control
+ * \param[out] firstpass_info struct of firstpass_info.
+ * \return status
+ */
+aom_codec_err_t av1_firstpass_info_move_cur_index(
+ FIRSTPASS_INFO *firstpass_info);
+
/*!\brief Pop a stats from firstpass_info
*
* \ingroup rate_control
* \param[out] firstpass_info struct of firstpass_info.
- * \param[out] output_stats output stats
* \return status
*/
-aom_codec_err_t av1_firstpass_info_pop(FIRSTPASS_INFO *firstpass_info,
- FIRSTPASS_STATS *output_stats);
+aom_codec_err_t av1_firstpass_info_pop(FIRSTPASS_INFO *firstpass_info);
+
+/*!\brief Move cur_index by 1 and pop a stats from firstpass_info
+ *
+ * \ingroup rate_control
+ * \param[out] firstpass_info struct of firstpass_info.
+ * \return status
+ */
+aom_codec_err_t av1_firstpass_info_move_cur_index_and_pop(
+ FIRSTPASS_INFO *firstpass_info);
/*!\brief Push a stats into firstpass_info
*
@@ -237,23 +285,57 @@
/*!\brief Peek at a stats from firstpass_info
*
+ * The target index is as follows.
+ * (cur_index + offset_from_cur) % firstpass_info->stats_buf_size
+ *
* \ingroup rate_control
* \param[in] firstpass_info struct of firstpass_info.
- * \param[in] stats_index_offset index offset.
+ * \param[in] offset_from_cur index offset from cur_index.
* \return pointer to the stats. The pointer will be NULL if
* stats_index_offset is invalid.
*/
const FIRSTPASS_STATS *av1_firstpass_info_peek(
- const FIRSTPASS_INFO *firstpass_info, int stats_index_offset);
+ const FIRSTPASS_INFO *firstpass_info, int offset_from_cur);
+/*!\brief Count the future stats from the target in firstpass_info
+ * Note that the target stats will be counted as well.
+ * The target index is as follows.
+ * (cur_index + offset_from_curr) % firstpass_info->stats_buf_size
+ *
+ * \ingroup rate_control
+ * \param[in] firstpass_info struct of firstpass_info.
+ * \param[in] offset_from_cur target stats's inffset
+ * from cur_index.
+ * \return Number of stats in the future after the target stats
+ * including itself.
+ */
+int av1_firstpass_info_future_count(const FIRSTPASS_INFO *firstpass_info,
+ int offset_from_cur);
+
+/*!\brief Count the past stats before the target in firstpass_info
+ * Note that the target stats will NOT be counted.
+ * The target index is as follows.
+ * (cur_index + offset_from_cur) % firstpass_info->stats_buf_size
+ *
+ * \ingroup rate_control
+ * \param[in] firstpass_info struct of firstpass_info.
+ * \param[in] offset_from_cur target stats's index offset
+ * from cur_index.
+ * \return Number of stats in the past before the target stats
+ * excluding itself.
+ */
+int av1_firstpass_info_past_count(const FIRSTPASS_INFO *firstpass_info,
+ int offset_from_cur);
+
+/*!\cond */
#define FC_ANIMATION_THRESH 0.15
enum {
FC_NORMAL = 0,
FC_GRAPHICS_ANIMATION = 1,
FRAME_CONTENT_TYPES = 2
} UENUM1BYTE(FRAME_CONTENT_TYPE);
-
/*!\endcond */
+
/*!
* \brief Data related to the current GF/ARF group and the
* individual frames within the group
diff --git a/av1/encoder/pass2_strategy.c b/av1/encoder/pass2_strategy.c
index 2b258dd..4e2f1a7 100644
--- a/av1/encoder/pass2_strategy.c
+++ b/av1/encoder/pass2_strategy.c
@@ -417,7 +417,8 @@
// instead of a clean scene cut.
if (frame_interval > min_gf_interval && loop_decay_rate >= 0.999 &&
last_decay_rate < 0.9) {
- int stats_left = firstpass_info->stats_count - next_stats_index;
+ int stats_left =
+ av1_firstpass_info_future_count(firstpass_info, next_stats_index);
if (stats_left >= still_interval) {
int j;
// Look ahead a few frames to see if static condition persists...
@@ -2649,16 +2650,15 @@
int this_stats_index, int frame_count_so_far,
enum aom_rc_mode rc_mode, int scenecut_mode,
int num_mbs) {
- if (this_stats_index < 1 ||
- this_stats_index + 1 >= firstpass_info->stats_count) {
- return 0;
- }
const FIRSTPASS_STATS *last_stats =
av1_firstpass_info_peek(firstpass_info, this_stats_index - 1);
const FIRSTPASS_STATS *this_stats =
av1_firstpass_info_peek(firstpass_info, this_stats_index);
const FIRSTPASS_STATS *next_stats =
av1_firstpass_info_peek(firstpass_info, this_stats_index + 1);
+ if (last_stats == NULL || this_stats == NULL || next_stats == NULL) {
+ return 0;
+ }
int is_viable_kf = 0;
double pcnt_intra = 1.0 - this_stats->pcnt_inter;
@@ -2669,8 +2669,12 @@
int frames_to_test_after_candidate_key = SCENE_CUT_KEY_TEST_INTERVAL;
int count_for_tolerable_prediction = 3;
+ // We do "-1" because the candidate key is not counted.
+ int stats_after_this_stats =
+ av1_firstpass_info_future_count(firstpass_info, this_stats_index) - 1;
+
if (scenecut_mode == ENABLE_SCENECUT_MODE_1) {
- if (this_stats_index + 3 >= firstpass_info->stats_count) {
+ if (stats_after_this_stats < 3) {
return 0;
} else {
frames_to_test_after_candidate_key = 3;
@@ -2678,10 +2682,8 @@
}
}
// Make sure we have enough stats after the candidate key.
- // We do "-1" because the candidate key is not counted.
frames_to_test_after_candidate_key =
- AOMMIN(frames_to_test_after_candidate_key,
- firstpass_info->stats_count - this_stats_index - 1);
+ AOMMIN(frames_to_test_after_candidate_key, stats_after_this_stats);
// Does the frame satisfy the primary criteria of a key frame?
// See above for an explanation of the test criteria.
@@ -2842,7 +2844,9 @@
: cpi->common.mi_params.MBs;
const FIRSTPASS_STATS *this_stats =
av1_firstpass_info_peek(firstpass_info, 0);
- while (frames_to_key < firstpass_info->stats_count &&
+ const int future_stats_count =
+ av1_firstpass_info_future_count(firstpass_info, 0);
+ while (frames_to_key < future_stats_count &&
frames_to_key < num_frames_to_detect_scenecut) {
// Accumulate total number of stats available till next key frame
num_stats_used_for_kf_boost++;
@@ -2860,7 +2864,7 @@
// Provided that we are not at the end of the file...
if ((cpi->ppi->p_rc.enable_scenecut_detection > 0) && kf_cfg->auto_key &&
- frames_to_key + 1 < firstpass_info->stats_count) {
+ frames_to_key + 1 < future_stats_count) {
const FIRSTPASS_STATS *next_stats =
av1_firstpass_info_peek(firstpass_info, frames_to_key + 1);
double loop_decay_rate;
@@ -2925,7 +2929,7 @@
frames_to_key = num_frames_to_next_key;
if (!kf_cfg->fwd_kf_enabled || scenecut_detected ||
- frames_to_key == firstpass_info->stats_count) {
+ frames_to_key == future_stats_count) {
p_rc->next_is_fwd_key = 0;
}
@@ -3197,8 +3201,10 @@
if (kf_cfg->fwd_kf_enabled)
p_rc->next_is_fwd_key |= p_rc->next_key_frame_forced;
+ const int future_stats_count =
+ av1_firstpass_info_future_count(firstpass_info, 0);
// Special case for the last key frame of the file.
- if (frames_to_key == firstpass_info->stats_count) {
+ if (frames_to_key == future_stats_count) {
// Accumulate kf group error.
// TODO(angiebird): Why do we need to add last frame's stats
// here? Move it to a proper place.
diff --git a/test/firstpass_test.cc b/test/firstpass_test.cc
index 11cc0ec..ae75f2a 100644
--- a/test/firstpass_test.cc
+++ b/test/firstpass_test.cc
@@ -28,6 +28,9 @@
aom_codec_err_t ret =
av1_firstpass_info_init(&firstpass_info, ext_stats_buf, 10);
EXPECT_EQ(firstpass_info.stats_count, ref_stats_size);
+ EXPECT_EQ(firstpass_info.future_stats_count + firstpass_info.past_stats_count,
+ firstpass_info.stats_count);
+ EXPECT_EQ(firstpass_info.cur_index, 0);
EXPECT_EQ(ret, AOM_CODEC_OK);
}
@@ -35,37 +38,42 @@
FIRSTPASS_INFO firstpass_info;
aom_codec_err_t ret = av1_firstpass_info_init(&firstpass_info, NULL, 0);
EXPECT_EQ(firstpass_info.stats_count, 0);
+ EXPECT_EQ(firstpass_info.cur_index, 0);
EXPECT_EQ(ret, AOM_CODEC_OK);
}
TEST(FirstpassTest, FirstpassInfoPushPop) {
FIRSTPASS_INFO firstpass_info;
av1_firstpass_info_init(&firstpass_info, NULL, 0);
- EXPECT_EQ(firstpass_info.stats_buf_size, 48);
- for (int i = 0; i < 48; ++i) {
+ EXPECT_EQ(firstpass_info.stats_buf_size, FIRSTPASS_INFO_STATIC_BUF_SIZE);
+ for (int i = 0; i < FIRSTPASS_INFO_STATIC_BUF_SIZE; ++i) {
FIRSTPASS_STATS stats;
av1_zero(stats);
stats.frame = i;
aom_codec_err_t ret = av1_firstpass_info_push(&firstpass_info, &stats);
EXPECT_EQ(ret, AOM_CODEC_OK);
}
- EXPECT_EQ(firstpass_info.stats_count, 48);
- for (int i = 0; i < 20; ++i) {
- FIRSTPASS_STATS stats;
- av1_zero(stats);
- aom_codec_err_t ret = av1_firstpass_info_pop(&firstpass_info, &stats);
- EXPECT_EQ(stats.frame, i);
+ EXPECT_EQ(firstpass_info.stats_count, FIRSTPASS_INFO_STATIC_BUF_SIZE);
+ const int pop_count = FIRSTPASS_INFO_STATIC_BUF_SIZE / 2;
+ for (int i = 0; i < pop_count; ++i) {
+ const FIRSTPASS_STATS *stats = av1_firstpass_info_peek(&firstpass_info, 0);
+ aom_codec_err_t ret =
+ av1_firstpass_info_move_cur_index_and_pop(&firstpass_info);
+ EXPECT_NE(stats, nullptr);
+ EXPECT_EQ(stats->frame, i);
EXPECT_EQ(ret, AOM_CODEC_OK);
}
- EXPECT_EQ(firstpass_info.stats_count, 28);
+ EXPECT_EQ(firstpass_info.stats_count,
+ FIRSTPASS_INFO_STATIC_BUF_SIZE - pop_count);
- for (int i = 0; i < 20; ++i) {
+ const int push_count = FIRSTPASS_INFO_STATIC_BUF_SIZE / 2;
+ for (int i = 0; i < push_count; ++i) {
FIRSTPASS_STATS stats;
av1_zero(stats);
aom_codec_err_t ret = av1_firstpass_info_push(&firstpass_info, &stats);
EXPECT_EQ(ret, AOM_CODEC_OK);
}
- EXPECT_EQ(firstpass_info.stats_count, 48);
+ EXPECT_EQ(firstpass_info.stats_count, FIRSTPASS_INFO_STATIC_BUF_SIZE);
EXPECT_EQ(firstpass_info.stats_count, firstpass_info.stats_buf_size);
// Pusht a stats when the queue is full.
@@ -88,4 +96,67 @@
EXPECT_EQ(firstpass_info.total_stats.count, 10);
}
+TEST(FirstpassTest, FirstpassInfoMoveCurr) {
+ FIRSTPASS_INFO firstpass_info;
+ av1_firstpass_info_init(&firstpass_info, NULL, 0);
+ int frame_cnt = 0;
+ EXPECT_EQ(firstpass_info.stats_buf_size, FIRSTPASS_INFO_STATIC_BUF_SIZE);
+ for (int i = 0; i < FIRSTPASS_INFO_STATIC_BUF_SIZE; ++i) {
+ FIRSTPASS_STATS stats;
+ av1_zero(stats);
+ stats.frame = frame_cnt;
+ ++frame_cnt;
+ aom_codec_err_t ret = av1_firstpass_info_push(&firstpass_info, &stats);
+ EXPECT_EQ(ret, AOM_CODEC_OK);
+ }
+ EXPECT_EQ(firstpass_info.cur_index, firstpass_info.start_index);
+ aom_codec_err_t ret = av1_firstpass_info_pop(&firstpass_info);
+ // We cannot pop when cur_index == start_index
+ EXPECT_EQ(ret, AOM_CODEC_ERROR);
+ int ref_frame_cnt = 0;
+ const int move_count = FIRSTPASS_INFO_STATIC_BUF_SIZE * 2 / 3;
+ for (int i = 0; i < move_count; ++i) {
+ const FIRSTPASS_STATS *this_stats =
+ av1_firstpass_info_peek(&firstpass_info, 0);
+ EXPECT_EQ(this_stats->frame, ref_frame_cnt);
+ ++ref_frame_cnt;
+ av1_firstpass_info_move_cur_index(&firstpass_info);
+ }
+ EXPECT_EQ(firstpass_info.future_stats_count,
+ FIRSTPASS_INFO_STATIC_BUF_SIZE - move_count);
+ EXPECT_EQ(firstpass_info.past_stats_count, move_count);
+ EXPECT_EQ(firstpass_info.stats_count, FIRSTPASS_INFO_STATIC_BUF_SIZE);
+
+ const int test_count = FIRSTPASS_INFO_STATIC_BUF_SIZE / 2;
+ for (int i = 0; i < test_count; ++i) {
+ aom_codec_err_t ret = av1_firstpass_info_pop(&firstpass_info);
+ EXPECT_EQ(ret, AOM_CODEC_OK);
+ }
+
+ // Pop #test_count stats
+ for (int i = 0; i < test_count; ++i) {
+ FIRSTPASS_STATS stats;
+ av1_zero(stats);
+ stats.frame = frame_cnt;
+ ++frame_cnt;
+ aom_codec_err_t ret = av1_firstpass_info_push(&firstpass_info, &stats);
+ EXPECT_EQ(ret, AOM_CODEC_OK);
+ }
+
+ // peek and move #test_count stats
+ for (int i = 0; i < test_count; ++i) {
+ const FIRSTPASS_STATS *this_stats =
+ av1_firstpass_info_peek(&firstpass_info, 0);
+ EXPECT_EQ(this_stats->frame, ref_frame_cnt);
+ ++ref_frame_cnt;
+ av1_firstpass_info_move_cur_index(&firstpass_info);
+ }
+
+ // pop #test_count stats
+ for (int i = 0; i < test_count; ++i) {
+ aom_codec_err_t ret = av1_firstpass_info_pop(&firstpass_info);
+ EXPECT_EQ(ret, AOM_CODEC_OK);
+ }
+}
+
} // namespace