diff options
-rw-r--r-- | ext/vpx/gstvp9enc.c | 3 | ||||
-rw-r--r-- | ext/vpx/gstvpxenc.c | 87 | ||||
-rw-r--r-- | ext/vpx/gstvpxenc.h | 5 | ||||
-rw-r--r-- | tests/check/elements/vp8enc.c | 106 | ||||
-rw-r--r-- | tests/check/elements/vp9enc.c | 46 |
5 files changed, 231 insertions, 16 deletions
diff --git a/ext/vpx/gstvp9enc.c b/ext/vpx/gstvp9enc.c index 362856476..f3058081e 100644 --- a/ext/vpx/gstvp9enc.c +++ b/ext/vpx/gstvp9enc.c @@ -101,6 +101,8 @@ static GstFlowReturn gst_vp9_enc_handle_invisible_frame_buffer (GstVPXEnc * enc, static void gst_vp9_enc_set_frame_user_data (GstVPXEnc * enc, GstVideoCodecFrame * frame, vpx_image_t * image); +#define DEFAULT_BITS_PER_PIXEL 0.0289 + static void gst_vp9_enc_class_init (GstVP9EncClass * klass) { @@ -152,6 +154,7 @@ gst_vp9_enc_init (GstVP9Enc * gst_vp9_enc) } else { gst_vpx_enc->have_default_config = TRUE; } + gst_vpx_enc->bits_per_pixel = DEFAULT_BITS_PER_PIXEL; } static vpx_codec_iface_t * diff --git a/ext/vpx/gstvpxenc.c b/ext/vpx/gstvpxenc.c index 12a781df2..cfb69c833 100644 --- a/ext/vpx/gstvpxenc.c +++ b/ext/vpx/gstvpxenc.c @@ -47,7 +47,7 @@ GST_DEBUG_CATEGORY_STATIC (gst_vpxenc_debug); #define DEFAULT_PROFILE 0 #define DEFAULT_RC_END_USAGE VPX_VBR -#define DEFAULT_RC_TARGET_BITRATE 256000 +#define DEFAULT_RC_TARGET_BITRATE 0 #define DEFAULT_RC_MIN_QUANTIZER 4 #define DEFAULT_RC_MAX_QUANTIZER 63 @@ -102,6 +102,8 @@ GST_DEBUG_CATEGORY_STATIC (gst_vpxenc_debug); #define DEFAULT_TIMEBASE_N 0 #define DEFAULT_TIMEBASE_D 1 +#define DEFAULT_BITS_PER_PIXEL 0.0434 + enum { PROP_0, @@ -148,7 +150,8 @@ enum PROP_TUNING, PROP_CQ_LEVEL, PROP_MAX_INTRA_BITRATE_PCT, - PROP_TIMEBASE + PROP_TIMEBASE, + PROP_BITS_PER_PIXEL }; @@ -368,7 +371,8 @@ gst_vpx_enc_class_init (GstVPXEncClass * klass) g_object_class_install_property (gobject_class, PROP_RC_TARGET_BITRATE, g_param_spec_int ("target-bitrate", "Target bitrate", - "Target bitrate (in bits/sec)", + "Target bitrate (in bits/sec) (0: auto - bitrate depends on " + "resolution, see \"bits-per-pixel\" property for more info)", 0, G_MAXINT, DEFAULT_RC_TARGET_BITRATE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); @@ -638,6 +642,13 @@ gst_vpx_enc_class_init (GstVPXEncClass * klass) 0, 1, G_MAXINT, 1, DEFAULT_TIMEBASE_N, DEFAULT_TIMEBASE_D, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_BITS_PER_PIXEL, + g_param_spec_float ("bits-per-pixel", "Bits per pixel", + "Factor to convert number of pixels to bitrate value " + "(only has an effect if target-bitrate=0)", + 0.0, G_MAXFLOAT, DEFAULT_BITS_PER_PIXEL, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + GST_DEBUG_CATEGORY_INIT (gst_vpxenc_debug, "vpxenc", 0, "VPX Encoder"); gst_type_mark_as_plugin_api (GST_VPX_ENC_END_USAGE_TYPE); @@ -657,7 +668,7 @@ gst_vpx_enc_init (GstVPXEnc * gst_vpx_enc) gst_vpx_enc->cfg.rc_end_usage = DEFAULT_RC_END_USAGE; gst_vpx_enc->cfg.rc_target_bitrate = DEFAULT_RC_TARGET_BITRATE / 1000; - gst_vpx_enc->rc_target_bitrate_set = FALSE; + gst_vpx_enc->rc_target_bitrate_auto = DEFAULT_RC_TARGET_BITRATE == 0; gst_vpx_enc->cfg.rc_min_quantizer = DEFAULT_RC_MIN_QUANTIZER; gst_vpx_enc->cfg.rc_max_quantizer = DEFAULT_RC_MAX_QUANTIZER; gst_vpx_enc->cfg.rc_dropframe_thresh = DEFAULT_RC_DROPFRAME_THRESH; @@ -705,6 +716,7 @@ gst_vpx_enc_init (GstVPXEnc * gst_vpx_enc) gst_vpx_enc->max_intra_bitrate_pct = DEFAULT_MAX_INTRA_BITRATE_PCT; gst_vpx_enc->timebase_n = DEFAULT_TIMEBASE_N; gst_vpx_enc->timebase_d = DEFAULT_TIMEBASE_D; + gst_vpx_enc->bits_per_pixel = DEFAULT_BITS_PER_PIXEL; gst_vpx_enc->cfg.g_profile = DEFAULT_PROFILE; @@ -735,6 +747,42 @@ gst_vpx_enc_finalize (GObject * object) } static void +gst_vpx_enc_set_auto_bitrate (GstVPXEnc * encoder) +{ + if (encoder->input_state != NULL) { + guint size; + guint pixels_per_sec; + guint target_bitrate; + guint fps_n, fps_d; + + if (GST_VIDEO_INFO_FPS_D (&encoder->input_state->info) != 0) { + fps_n = GST_VIDEO_INFO_FPS_N (&encoder->input_state->info); + fps_d = GST_VIDEO_INFO_FPS_D (&encoder->input_state->info); + } else { + /* otherwise assume 30 frames per second as a fallback */ + fps_n = 30; + fps_d = 1; + } + + size = + GST_VIDEO_INFO_WIDTH (&encoder->input_state->info) * + GST_VIDEO_INFO_HEIGHT (&encoder->input_state->info); + pixels_per_sec = size * fps_n / fps_d; + target_bitrate = pixels_per_sec * encoder->bits_per_pixel; + + GST_DEBUG_OBJECT (encoder, + "Setting autobitrate for %ux%ux @ %u/%ufps %.4f = %ubps", + GST_VIDEO_INFO_WIDTH (&encoder->input_state->info), + GST_VIDEO_INFO_HEIGHT (&encoder->input_state->info), + GST_VIDEO_INFO_FPS_N (&encoder->input_state->info), + GST_VIDEO_INFO_FPS_D (&encoder->input_state->info), + encoder->bits_per_pixel, target_bitrate); + + encoder->cfg.rc_target_bitrate = target_bitrate / 1000; + } +} + +static void gst_vpx_enc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { @@ -753,8 +801,13 @@ gst_vpx_enc_set_property (GObject * object, guint prop_id, global = TRUE; break; case PROP_RC_TARGET_BITRATE: - gst_vpx_enc->cfg.rc_target_bitrate = g_value_get_int (value) / 1000; - gst_vpx_enc->rc_target_bitrate_set = TRUE; + if (g_value_get_int (value) == 0) { + gst_vpx_enc_set_auto_bitrate (gst_vpx_enc); + gst_vpx_enc->rc_target_bitrate_auto = TRUE; + } else { + gst_vpx_enc->cfg.rc_target_bitrate = g_value_get_int (value) / 1000; + gst_vpx_enc->rc_target_bitrate_auto = FALSE; + } global = TRUE; break; case PROP_RC_MIN_QUANTIZER: @@ -1097,6 +1150,13 @@ gst_vpx_enc_set_property (GObject * object, guint prop_id, gst_vpx_enc->timebase_n = gst_value_get_fraction_numerator (value); gst_vpx_enc->timebase_d = gst_value_get_fraction_denominator (value); break; + case PROP_BITS_PER_PIXEL: + gst_vpx_enc->bits_per_pixel = g_value_get_float (value); + if (gst_vpx_enc->rc_target_bitrate_auto) { + gst_vpx_enc_set_auto_bitrate (gst_vpx_enc); + global = TRUE; + } + break; default: break; } @@ -1318,6 +1378,9 @@ gst_vpx_enc_get_property (GObject * object, guint prop_id, GValue * value, gst_value_set_fraction (value, gst_vpx_enc->timebase_n, gst_vpx_enc->timebase_d); break; + case PROP_BITS_PER_PIXEL: + g_value_set_float (value, gst_vpx_enc->bits_per_pixel); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1452,14 +1515,6 @@ gst_vpx_enc_set_format (GstVideoEncoder * video_encoder, } encoder->cfg.g_profile = gst_vpx_enc_get_downstream_profile (encoder); - - /* Scale default bitrate to our size */ - if (!encoder->rc_target_bitrate_set) - encoder->cfg.rc_target_bitrate = - gst_util_uint64_scale (DEFAULT_RC_TARGET_BITRATE, - GST_VIDEO_INFO_WIDTH (info) * GST_VIDEO_INFO_HEIGHT (info), - 320 * 240 * 1000); - encoder->cfg.g_w = GST_VIDEO_INFO_WIDTH (info); encoder->cfg.g_h = GST_VIDEO_INFO_HEIGHT (info); @@ -1641,6 +1696,10 @@ gst_vpx_enc_set_format (GstVideoEncoder * video_encoder, gst_video_codec_state_unref (encoder->input_state); encoder->input_state = gst_video_codec_state_ref (state); + /* Scale default bitrate to our size */ + if (encoder->rc_target_bitrate_auto) + gst_vpx_enc_set_auto_bitrate (encoder); + /* prepare cached image buffer setup */ image = &encoder->image; memset (image, 0, sizeof (*image)); diff --git a/ext/vpx/gstvpxenc.h b/ext/vpx/gstvpxenc.h index 5a0d5ba53..fbf5476ba 100644 --- a/ext/vpx/gstvpxenc.h +++ b/ext/vpx/gstvpxenc.h @@ -68,7 +68,7 @@ struct _GstVPXEnc /* properties */ vpx_codec_enc_cfg_t cfg; gboolean have_default_config; - gboolean rc_target_bitrate_set; + gboolean rc_target_bitrate_auto; gint n_ts_target_bitrate; gint n_ts_rate_decimator; gint n_ts_layer_id; @@ -100,6 +100,9 @@ struct _GstVPXEnc unsigned int timebase_n; unsigned int timebase_d; + /* Bits per Pixel */ + gfloat bits_per_pixel; + /* state */ gboolean inited; diff --git a/tests/check/elements/vp8enc.c b/tests/check/elements/vp8enc.c index 86b1d929d..e9d8c071f 100644 --- a/tests/check/elements/vp8enc.c +++ b/tests/check/elements/vp8enc.c @@ -17,8 +17,9 @@ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ - +#include <gst/check/gstharness.h> #include <gst/check/gstcheck.h> +#include <gst/video/video.h> static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, @@ -90,6 +91,7 @@ cleanup_vp8enc (GstElement * vp8enc) gst_check_teardown_element (vp8enc); } + GST_START_TEST (test_encode_simple) { GstElement *vp8enc; @@ -152,6 +154,106 @@ GST_START_TEST (test_encode_simple) GST_END_TEST; +#define gst_caps_new_i420(w, h) gst_caps_new_i420_full (w, h, 30, 1, 1, 1) +static GstCaps * +gst_caps_new_i420_full (gint width, gint height, gint fps_n, gint fps_d, + gint par_n, gint par_d) +{ + GstVideoInfo info; + gst_video_info_init (&info); + gst_video_info_set_format (&info, GST_VIDEO_FORMAT_I420, width, height); + GST_VIDEO_INFO_FPS_N (&info) = fps_n; + GST_VIDEO_INFO_FPS_D (&info) = fps_d; + GST_VIDEO_INFO_PAR_N (&info) = par_n; + GST_VIDEO_INFO_PAR_D (&info) = par_d; + return gst_video_info_to_caps (&info); +} + +static GstBuffer * +gst_harness_create_video_buffer_from_info (GstHarness * h, gint value, + GstVideoInfo * info, GstClockTime timestamp, GstClockTime duration) +{ + GstBuffer *buf; + gsize size; + + size = GST_VIDEO_INFO_SIZE (info); + + buf = gst_harness_create_buffer (h, size); + gst_buffer_memset (buf, 0, value, size); + g_assert (buf != NULL); + + gst_buffer_add_video_meta_full (buf, + GST_VIDEO_FRAME_FLAG_NONE, + GST_VIDEO_INFO_FORMAT (info), + GST_VIDEO_INFO_WIDTH (info), + GST_VIDEO_INFO_HEIGHT (info), + GST_VIDEO_INFO_N_PLANES (info), info->offset, info->stride); + + GST_BUFFER_PTS (buf) = timestamp; + GST_BUFFER_DURATION (buf) = duration; + + return buf; +} + +static GstBuffer * +gst_harness_create_video_buffer_full (GstHarness * h, gint value, + guint width, guint height, GstClockTime timestamp, GstClockTime duration) +{ + GstVideoInfo info; + + gst_video_info_init (&info); + gst_video_info_set_format (&info, GST_VIDEO_FORMAT_I420, width, height); + + return gst_harness_create_video_buffer_from_info (h, value, &info, + timestamp, duration); +} + +GST_START_TEST (test_encode_simple_when_bitrate_set_to_zero) +{ + GstHarness *h = gst_harness_new_parse ("vp8enc target-bitrate=0"); + GstBuffer *buf; + + gst_harness_set_src_caps (h, gst_caps_new_i420 (320, 240)); + + buf = gst_harness_create_video_buffer_full (h, 0x42, + 320, 240, 0, gst_util_uint64_scale (GST_SECOND, 1, 30)); + gst_harness_push (h, buf); + gst_buffer_unref (gst_harness_pull (h)); + gst_harness_teardown (h); +} + +GST_END_TEST; + +GST_START_TEST (test_autobitrate_changes_with_caps) +{ + gint bitrate = 0; + GstHarness *h = gst_harness_new ("vp8enc"); + gst_harness_set_src_caps (h, gst_caps_new_i420_full (1280, 720, 30, 1, 1, 1)); + + /* Default settings for 720p @ 30fps ~1.2Mbps */ + g_object_get (h->element, "target-bitrate", &bitrate, NULL); + fail_unless_equals_int (bitrate, 1199000); + + /* Change bits-per-pixel 0.036 to give us ~1Mbps */ + g_object_set (h->element, "bits-per-pixel", 0.037, NULL); + g_object_get (h->element, "target-bitrate", &bitrate, NULL); + fail_unless_equals_int (bitrate, 1022000); + + /* Halving the framerate should halve the auto bitrate */ + gst_harness_set_src_caps (h, gst_caps_new_i420_full (1280, 720, 15, 1, 1, 1)); + g_object_get (h->element, "target-bitrate", &bitrate, NULL); + fail_unless_equals_int (bitrate, 511000); + + /* Halving the resolution should quarter the auto bitrate */ + gst_harness_set_src_caps (h, gst_caps_new_i420_full (640, 360, 15, 1, 1, 1)); + g_object_get (h->element, "target-bitrate", &bitrate, NULL); + fail_unless_equals_int (bitrate, 127000); + + gst_harness_teardown (h); +} + +GST_END_TEST; + static Suite * vp8enc_suite (void) { @@ -161,6 +263,8 @@ vp8enc_suite (void) suite_add_tcase (s, tc_chain); tcase_add_test (tc_chain, test_encode_simple); + tcase_add_test (tc_chain, test_encode_simple_when_bitrate_set_to_zero); + tcase_add_test (tc_chain, test_autobitrate_changes_with_caps); return s; } diff --git a/tests/check/elements/vp9enc.c b/tests/check/elements/vp9enc.c index f7be0e0fb..b7ae12fd9 100644 --- a/tests/check/elements/vp9enc.c +++ b/tests/check/elements/vp9enc.c @@ -21,6 +21,21 @@ #include <gst/check/gstcheck.h> #include <gst/video/video.h> +#define gst_caps_new_i420(w, h) gst_caps_new_i420_full (w, h, 30, 1, 1, 1) +static GstCaps * +gst_caps_new_i420_full (gint width, gint height, gint fps_n, gint fps_d, + gint par_n, gint par_d) +{ + GstVideoInfo info; + gst_video_info_init (&info); + gst_video_info_set_format (&info, GST_VIDEO_FORMAT_I420, width, height); + GST_VIDEO_INFO_FPS_N (&info) = fps_n; + GST_VIDEO_INFO_FPS_D (&info) = fps_d; + GST_VIDEO_INFO_PAR_N (&info) = par_n; + GST_VIDEO_INFO_PAR_D (&info) = par_d; + return gst_video_info_to_caps (&info); +} + GST_START_TEST (test_encode_lag_in_frames) { GstHarness *h = gst_harness_new_parse ("vp9enc lag-in-frames=5 cpu-used=8 " @@ -62,6 +77,36 @@ GST_START_TEST (test_encode_lag_in_frames) GST_END_TEST; +GST_START_TEST (test_autobitrate_changes_with_caps) +{ + gint bitrate = 0; + GstHarness *h = gst_harness_new ("vp9enc"); + gst_harness_set_src_caps (h, gst_caps_new_i420_full (1280, 720, 30, 1, 1, 1)); + + /* Default settings for 720p @ 30fps ~0.8Mbps */ + g_object_get (h->element, "target-bitrate", &bitrate, NULL); + fail_unless_equals_int (bitrate, 799000); + + /* Change bits-per-pixel 0.036 to give us ~1Mbps */ + g_object_set (h->element, "bits-per-pixel", 0.037, NULL); + g_object_get (h->element, "target-bitrate", &bitrate, NULL); + fail_unless_equals_int (bitrate, 1022000); + + /* Halving the framerate should halve the auto bitrate */ + gst_harness_set_src_caps (h, gst_caps_new_i420_full (1280, 720, 15, 1, 1, 1)); + g_object_get (h->element, "target-bitrate", &bitrate, NULL); + fail_unless_equals_int (bitrate, 511000); + + /* Halving the resolution should quarter the auto bitrate */ + gst_harness_set_src_caps (h, gst_caps_new_i420_full (640, 360, 15, 1, 1, 1)); + g_object_get (h->element, "target-bitrate", &bitrate, NULL); + fail_unless_equals_int (bitrate, 127000); + + gst_harness_teardown (h); +} + +GST_END_TEST; + static Suite * vp9enc_suite (void) { @@ -71,6 +116,7 @@ vp9enc_suite (void) suite_add_tcase (s, tc_chain); tcase_add_test (tc_chain, test_encode_lag_in_frames); + tcase_add_test (tc_chain, test_autobitrate_changes_with_caps); return s; } |