From ae86dec9cafabe2e607e053860d3b4e1c64e876c Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Tue, 9 Dec 2014 13:18:42 +0100 Subject: [PATCH] videorate: Detect framerate if not forced to variable downstream In case upstream does not provide videorate with framerate information, it will detect the current framerate from the buffer it received, but if downstream forces the use of variable framerate (most probably through the use of a caps filter with framerate = 0 / 1), videorate will respect that. And add some unit tests https://bugzilla.gnome.org/show_bug.cgi?id=734424 --- gst/videorate/gstvideorate.c | 66 +++++++++++++++++++- gst/videorate/gstvideorate.h | 2 + tests/check/elements/videorate.c | 103 +++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 1 deletion(-) diff --git a/gst/videorate/gstvideorate.c b/gst/videorate/gstvideorate.c index a31055cecb..f432ecedb3 100644 --- a/gst/videorate/gstvideorate.c +++ b/gst/videorate/gstvideorate.c @@ -374,7 +374,17 @@ gst_video_rate_transform_caps (GstBaseTransform * trans, s2 = gst_structure_copy (s); s3 = NULL; - if (videorate->drop_only) { + if (videorate->updating_caps) { + GST_INFO_OBJECT (trans, + "Only updating caps %" GST_PTR_FORMAT " with framerate" " %d/%d", + caps, videorate->to_rate_numerator, videorate->to_rate_denominator); + + gst_structure_set (s1, "framerate", GST_TYPE_FRACTION, + videorate->to_rate_numerator, videorate->to_rate_denominator, NULL); + ret = gst_caps_merge_structure (ret, s1); + + continue; + } else if (videorate->drop_only) { gint min_num = 0, min_denom = 1; gint max_num = G_MAXINT, max_denom = 1; @@ -550,6 +560,7 @@ gst_video_rate_reset (GstVideoRate * videorate) videorate->last_ts = GST_CLOCK_TIME_NONE; videorate->discont = TRUE; videorate->average = 0; + videorate->force_variable_rate = FALSE; gst_video_rate_swap_prev (videorate, NULL, 0); gst_segment_init (&videorate->segment, GST_FORMAT_TIME); @@ -981,6 +992,54 @@ drop: return GST_BASE_TRANSFORM_FLOW_DROPPED; } +/* Check if downstream forces variable framerate (0/1) and if + * it is the case, use variable framerate ourself + * Otherwise compute the framerate from the 2 buffers that we + * have already received and make use of it as wanted framerate + */ +static void +gst_video_rate_check_variable_rate (GstVideoRate * videorate, + GstBuffer * buffer) +{ + GstStructure *st; + gint fps_d, fps_n; + GstCaps *srcpadcaps, *tmpcaps; + + GstPad *pad = NULL; + + srcpadcaps = + gst_pad_get_current_caps (GST_BASE_TRANSFORM_SRC_PAD (videorate)); + + gst_video_guess_framerate (GST_BUFFER_PTS (buffer) - + GST_BUFFER_PTS (videorate->prevbuf), &fps_n, &fps_d); + tmpcaps = gst_caps_copy (srcpadcaps); + st = gst_caps_get_structure (tmpcaps, 0); + gst_structure_set (st, "framerate", GST_TYPE_FRACTION, fps_n, fps_d, NULL); + gst_caps_unref (srcpadcaps); + + pad = gst_pad_get_peer (GST_BASE_TRANSFORM_SRC_PAD (videorate)); + if (pad && !gst_pad_query_accept_caps (pad, tmpcaps)) { + videorate->force_variable_rate = TRUE; + GST_DEBUG_OBJECT (videorate, "Downstream forces variable framerate" + " respecting it"); + + goto done; + } + + videorate->to_rate_numerator = fps_n; + videorate->to_rate_denominator = fps_d; + + GST_ERROR_OBJECT (videorate, "Computed framerate to %d/%d", + videorate->to_rate_numerator, videorate->to_rate_denominator); + + videorate->updating_caps = TRUE; + gst_base_transform_update_src_caps (GST_BASE_TRANSFORM (videorate), tmpcaps); + +done: + if (pad) + gst_object_unref (pad); +} + static GstFlowReturn gst_video_rate_transform_ip (GstBaseTransform * trans, GstBuffer * buffer) { @@ -997,6 +1056,11 @@ gst_video_rate_transform_ip (GstBaseTransform * trans, GstBuffer * buffer) videorate->to_rate_denominator == 0) goto not_negotiated; + if (videorate->to_rate_numerator == 0 && videorate->prevbuf && + !videorate->force_variable_rate) { + gst_video_rate_check_variable_rate (videorate, buffer); + } + GST_OBJECT_LOCK (videorate); avg_period = videorate->average_period_set; GST_OBJECT_UNLOCK (videorate); diff --git a/gst/videorate/gstvideorate.h b/gst/videorate/gstvideorate.h index 92c841cc42..d5e7c19ef0 100644 --- a/gst/videorate/gstvideorate.h +++ b/gst/videorate/gstvideorate.h @@ -65,6 +65,8 @@ struct _GstVideoRate guint64 average_period; GstClockTimeDiff wanted_diff; /* target average diff */ GstClockTimeDiff average; /* moving average period */ + gboolean force_variable_rate; + gboolean updating_caps; /* segment handling */ GstSegment segment; diff --git a/tests/check/elements/videorate.c b/tests/check/elements/videorate.c index f8a9e08490..ec57b70383 100644 --- a/tests/check/elements/videorate.c +++ b/tests/check/elements/videorate.c @@ -40,6 +40,10 @@ static GstPad *mysrcpad, *mysinkpad; "framerate = (fraction) 25/1 , " \ "format = (string) I420" +#define VIDEO_CAPS_FORCE_VARIABLE_FRAMERATE_STRING \ + "video/x-raw, " \ + "framerate = (fraction) 0/1" + #define VIDEO_CAPS_NO_FRAMERATE_STRING \ "video/x-raw, " \ "width = (int) 320, " \ @@ -77,6 +81,13 @@ static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", GST_STATIC_CAPS (VIDEO_CAPS_TEMPLATE_STRING) ); +static GstStaticPadTemplate force_variable_rate_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (VIDEO_CAPS_FORCE_VARIABLE_FRAMERATE_STRING) + ); + static void assert_videorate_stats (GstElement * videorate, const gchar * reason, guint64 xin, guint64 xout, guint64 xdropped, guint64 xduplicated) @@ -1015,8 +1026,10 @@ GST_START_TEST (test_caps_negotiation) videorate = setup_videorate_full (&srctemplate, &sinktemplate); caps = gst_caps_from_string (test->caps); + g_object_set_data_full (G_OBJECT (mysrcpad), "caps", gst_caps_ref (caps), (GDestroyNotify) gst_caps_unref); + g_object_set_data_full (G_OBJECT (mysinkpad), "caps", gst_caps_ref (caps), (GDestroyNotify) gst_caps_unref); gst_caps_unref (caps); @@ -1036,6 +1049,95 @@ GST_START_TEST (test_caps_negotiation) GST_END_TEST; +static void +videorate_send_buffers (GstElement * videorate, + const gchar * pre_push_caps, const gchar * post_push_caps) +{ + GstCaps *caps, *expected_caps; + GstBuffer *first; + GstBuffer *second; + GstBuffer *third; + + caps = gst_pad_get_current_caps (mysinkpad); + expected_caps = gst_caps_from_string (pre_push_caps); + gst_check_caps_equal (caps, expected_caps); + gst_caps_unref (caps); + gst_caps_unref (expected_caps); + + GST_DEBUG ("pushing first buffer"); + first = gst_buffer_new_and_alloc (4); + gst_buffer_memset (first, 0, 0, 4); + GST_BUFFER_TIMESTAMP (first) = 0; + fail_unless (gst_pad_push (mysrcpad, first) == GST_FLOW_OK); + + /* second buffer */ + second = gst_buffer_new_and_alloc (4); + GST_BUFFER_TIMESTAMP (second) = GST_SECOND / 25; + gst_buffer_memset (second, 0, 0, 4); + + fail_unless (gst_pad_push (mysrcpad, second) == GST_FLOW_OK); + + /* third buffer with new size */ + third = gst_buffer_new_and_alloc (4); + GST_BUFFER_TIMESTAMP (third) = 2 * GST_SECOND / 25; + gst_buffer_memset (third, 0, 0, 4); + + fail_unless (gst_pad_push (mysrcpad, third) == GST_FLOW_OK); + + caps = gst_pad_get_current_caps (mysinkpad); + expected_caps = gst_caps_from_string (post_push_caps); + gst_check_caps_equal (caps, expected_caps); + gst_caps_unref (caps); + gst_caps_unref (expected_caps); + +} + +GST_START_TEST (test_fixed_framerate) +{ + GstElement *videorate; + GstCaps *caps = gst_caps_from_string ("video/x-raw,framerate=0/1"); + + /* 1) if upstream caps contain a non-0/1 framerate, we should use that and pass + * it on downstream (if possible; otherwise fixate_to_nearest) + */ + videorate = setup_videorate_full (&srctemplate, &sinktemplate); + + caps = gst_caps_from_string ("video/x-raw,framerate=25/1"); + ASSERT_SET_STATE (videorate, GST_STATE_PLAYING, GST_STATE_CHANGE_SUCCESS); + gst_check_setup_events (mysrcpad, videorate, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + videorate_send_buffers (videorate, "video/x-raw,framerate=25/1", + "video/x-raw,framerate=25/1"); + cleanup_videorate (videorate); + + /* 2) if upstream framerate is 0/1 and downstream doesn't force a particular + * framerate, we try to guess based on buffer intervals and use that as output + * framerate */ + videorate = setup_videorate_full (&srctemplate, &sinktemplate); + ASSERT_SET_STATE (videorate, GST_STATE_PLAYING, GST_STATE_CHANGE_SUCCESS); + caps = gst_caps_from_string ("video/x-raw,framerate=0/1"); + gst_check_setup_events (mysrcpad, videorate, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + videorate_send_buffers (videorate, "video/x-raw,framerate=0/1", + "video/x-raw,framerate=25/1"); + cleanup_videorate (videorate); + + /* 3) if downstream force variable framerate, do that */ + videorate = + setup_videorate_full (&srctemplate, &force_variable_rate_template); + ASSERT_SET_STATE (videorate, GST_STATE_PLAYING, GST_STATE_CHANGE_SUCCESS); + caps = gst_caps_from_string ("video/x-raw,framerate=0/1"); + gst_check_setup_events (mysrcpad, videorate, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + videorate_send_buffers (videorate, "video/x-raw,framerate=0/1", + "video/x-raw,framerate=0/1"); + cleanup_videorate (videorate); + +} + +GST_END_TEST; + + static Suite * videorate_suite (void) { @@ -1054,6 +1156,7 @@ videorate_suite (void) tcase_add_test (tc_chain, test_selected_caps); tcase_add_loop_test (tc_chain, test_caps_negotiation, 0, G_N_ELEMENTS (caps_negotiation_tests)); + tcase_add_test (tc_chain, test_fixed_framerate); return s; }