diff options
Diffstat (limited to 'media/libvpx/libvpx/vp9/simple_encode.cc')
-rw-r--r-- | media/libvpx/libvpx/vp9/simple_encode.cc | 1332 |
1 files changed, 1332 insertions, 0 deletions
diff --git a/media/libvpx/libvpx/vp9/simple_encode.cc b/media/libvpx/libvpx/vp9/simple_encode.cc new file mode 100644 index 0000000000..f42912d35b --- /dev/null +++ b/media/libvpx/libvpx/vp9/simple_encode.cc @@ -0,0 +1,1332 @@ +/* + * 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 <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(); + return kFrameTypeInter; + } +} + +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] == NONE) ? 1 : 2); + if (in_ref_frame[0] == NONE) { + fprintf(stderr, "in_ref_frame[0] shouldn't be NONE\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->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) { + 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; + 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, + &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); + 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(), &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(); + } + const size_t max_coding_data_byte_size = + get_max_coding_data_byte_size(frame_width_, frame_height_); + if (encode_frame_result->coding_data_byte_size > + 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 |