From: Joris Valette Date: Tue, 17 Sep 2013 15:42:05 +0000 (+0200) Subject: videorate: Add fixed rate property X-Git-Tag: 1.12.2~333 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=658ee6f0db73988e421c7884f3bb6cd3f54c2a70;p=platform%2Fupstream%2Fgst-plugins-base.git videorate: Add fixed rate property https://bugzilla.gnome.org/show_bug.cgi?id=699077 --- diff --git a/gst/videorate/gstvideorate.c b/gst/videorate/gstvideorate.c index eb3cc8c..86b4d89 100644 --- a/gst/videorate/gstvideorate.c +++ b/gst/videorate/gstvideorate.c @@ -52,6 +52,10 @@ * Note that property notification will happen from the streaming thread, so * applications should be prepared for this. * + * The property #GstVideoRate:rate allows the modification of video speed by a + * certain factor. It must not be confused with framerate. Think of rate as + * speed and framerate as flow. + * * * Example pipelines * |[ @@ -91,6 +95,7 @@ enum #define DEFAULT_DROP_ONLY FALSE #define DEFAULT_AVERAGE_PERIOD 0 #define DEFAULT_MAX_RATE G_MAXINT +#define DEFAULT_RATE 1.0 enum { @@ -104,7 +109,8 @@ enum PROP_SKIP_TO_FIRST, PROP_DROP_ONLY, PROP_AVERAGE_PERIOD, - PROP_MAX_RATE + PROP_MAX_RATE, + PROP_RATE }; static GstStaticPadTemplate gst_video_rate_src_template = @@ -127,6 +133,8 @@ static void gst_video_rate_swap_prev (GstVideoRate * videorate, GstBuffer * buffer, gint64 time); static gboolean gst_video_rate_sink_event (GstBaseTransform * trans, GstEvent * event); +static gboolean gst_video_rate_src_event (GstBaseTransform * trans, + GstEvent * event); static gboolean gst_video_rate_query (GstBaseTransform * trans, GstPadDirection direction, GstQuery * query); @@ -175,6 +183,7 @@ gst_video_rate_class_init (GstVideoRateClass * klass) GST_DEBUG_FUNCPTR (gst_video_rate_transform_caps); base_class->transform_ip = GST_DEBUG_FUNCPTR (gst_video_rate_transform_ip); base_class->sink_event = GST_DEBUG_FUNCPTR (gst_video_rate_sink_event); + base_class->src_event = GST_DEBUG_FUNCPTR (gst_video_rate_src_event); base_class->start = GST_DEBUG_FUNCPTR (gst_video_rate_start); base_class->stop = GST_DEBUG_FUNCPTR (gst_video_rate_stop); base_class->fixate_caps = GST_DEBUG_FUNCPTR (gst_video_rate_fixate_caps); @@ -207,7 +216,7 @@ gst_video_rate_class_init (GstVideoRateClass * klass) /** * GstVideoRate:skip-to-first: - * + * * Don't produce buffers before the first one we receive. */ g_object_class_install_property (object_class, PROP_SKIP_TO_FIRST, @@ -250,6 +259,19 @@ gst_video_rate_class_init (GstVideoRateClass * klass) 1, G_MAXINT, DEFAULT_MAX_RATE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + /** + * GstVideoRate:rate: + * + * Factor of speed for frame displaying + * + * Since: 1.12 + */ + g_object_class_install_property (object_class, PROP_RATE, + g_param_spec_double ("rate", "Rate", + "Factor of speed for frame displaying", 0.0, G_MAXDOUBLE, + DEFAULT_RATE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | + GST_PARAM_MUTABLE_READY)); + gst_element_class_set_static_metadata (element_class, "Video rate adjuster", "Filter/Effect/Video", "Drops/duplicates/adjusts timestamps on video frames to make a perfect stream", @@ -588,6 +610,7 @@ gst_video_rate_init (GstVideoRate * videorate) videorate->average_period = DEFAULT_AVERAGE_PERIOD; videorate->average_period_set = DEFAULT_AVERAGE_PERIOD; videorate->max_rate = DEFAULT_MAX_RATE; + videorate->rate = DEFAULT_RATE; videorate->from_rate_numerator = 0; videorate->from_rate_denominator = 0; @@ -727,11 +750,11 @@ gst_video_rate_sink_event (GstBaseTransform * trans, GstEvent * event) switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEGMENT: { - const GstSegment *segment; + GstSegment segment; + gint seqnum; - gst_event_parse_segment (event, &segment); - - if (segment->format != GST_FORMAT_TIME) + gst_event_copy_segment (event, &segment); + if (segment.format != GST_FORMAT_TIME) goto format_error; GST_DEBUG_OBJECT (videorate, "handle NEWSEGMENT"); @@ -776,10 +799,23 @@ gst_video_rate_sink_event (GstBaseTransform * trans, GstEvent * event) videorate->next_ts = GST_CLOCK_TIME_NONE; /* We just want to update the accumulated stream_time */ - gst_segment_copy_into (segment, &videorate->segment); + segment.start = (gint64) (segment.start / videorate->rate); + segment.position = (gint64) (segment.position / videorate->rate); + if (GST_CLOCK_TIME_IS_VALID (segment.stop)) + segment.stop = (gint64) (segment.stop / videorate->rate); + segment.time = (gint64) (segment.time / videorate->rate); + + gst_segment_copy_into (&segment, &videorate->segment); GST_DEBUG_OBJECT (videorate, "updated segment: %" GST_SEGMENT_FORMAT, &videorate->segment); + + + seqnum = gst_event_get_seqnum (event); + gst_event_unref (event); + event = gst_event_new_segment (&segment); + gst_event_set_seqnum (event, seqnum); + break; } case GST_EVENT_EOS:{ @@ -872,6 +908,47 @@ format_error: } static gboolean +gst_video_rate_src_event (GstBaseTransform * trans, GstEvent * event) +{ + GstVideoRate *videorate; + GstPad *sinkpad; + gboolean res = FALSE; + + videorate = GST_VIDEO_RATE (trans); + sinkpad = GST_BASE_TRANSFORM_SINK_PAD (trans); + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_SEEK: + { + gdouble srate; + GstSeekFlags flags; + GstSeekType start_type, stop_type; + gint64 start, stop; + gint seqnum = gst_event_get_seqnum (event); + + gst_event_parse_seek (event, &srate, NULL, &flags, &start_type, &start, + &stop_type, &stop); + + start = (gint64) (start * videorate->rate); + if (GST_CLOCK_TIME_IS_VALID (stop)) { + stop = (gint64) (stop * videorate->rate); + } + + gst_event_unref (event); + event = gst_event_new_seek (srate, GST_FORMAT_TIME, + flags, start_type, start, stop_type, stop); + gst_event_set_seqnum (event, seqnum); + + res = gst_pad_push_event (sinkpad, event); + break; + } + default: + res = gst_pad_push_event (sinkpad, event); + break; + } + return res; +} + +static gboolean gst_video_rate_query (GstBaseTransform * trans, GstPadDirection direction, GstQuery * query) { @@ -937,6 +1014,49 @@ gst_video_rate_query (GstBaseTransform * trans, GstPadDirection direction, /* Simple fallthrough if we don't have a latency or not a peer that we * can't ask about its latency yet.. */ } + case GST_QUERY_DURATION: + { + GstFormat format; + gint64 duration; + + + + gst_query_parse_duration (query, &format, &duration); + + if (format != GST_FORMAT_TIME) { + GST_DEBUG_OBJECT (videorate, "not TIME format"); + break; + } + GST_LOG_OBJECT (videorate, "upstream duration: %" G_GINT64_FORMAT, + duration); + if (GST_CLOCK_TIME_IS_VALID (duration)) { + duration = (gint64) (duration / videorate->rate); + } + GST_LOG_OBJECT (videorate, "our duration: %" G_GINT64_FORMAT, duration); + gst_query_set_duration (query, format, duration); + res = TRUE; + break; + } + case GST_QUERY_POSITION: + { + GstFormat dst_format; + gint64 dst_value; + + gst_query_parse_position (query, &dst_format, &dst_value); + + if (dst_format != GST_FORMAT_TIME) { + GST_DEBUG_OBJECT (videorate, "not TIME format"); + break; + } + dst_value = + (gint64) (gst_segment_to_stream_time (&videorate->segment, + GST_FORMAT_TIME, videorate->last_ts / videorate->rate)); + GST_LOG_OBJECT (videorate, "our position: %" GST_TIME_FORMAT, + GST_TIME_ARGS (dst_value)); + gst_query_set_position (query, dst_format, dst_value); + res = TRUE; + break; + } default: res = GST_BASE_TRANSFORM_CLASS (parent_class)->query (trans, direction, @@ -1272,23 +1392,24 @@ gst_video_rate_transform_ip (GstBaseTransform * trans, GstBuffer * buffer) /* got 2 buffers, see which one is the best */ do { + GstClockTime next_ts = videorate->next_ts * videorate->rate; /* take absolute values, beware: abs and ABS don't work for gint64 */ - if (prevtime > videorate->next_ts) - diff1 = prevtime - videorate->next_ts; + if (prevtime > next_ts) + diff1 = prevtime - next_ts; else - diff1 = videorate->next_ts - prevtime; + diff1 = next_ts - prevtime; - if (intime > videorate->next_ts) - diff2 = intime - videorate->next_ts; + if (intime > next_ts) + diff2 = intime - next_ts; else - diff2 = videorate->next_ts - intime; + diff2 = next_ts - intime; GST_LOG_OBJECT (videorate, "diff with prev %" GST_TIME_FORMAT " diff with new %" GST_TIME_FORMAT " outgoing ts %" GST_TIME_FORMAT, GST_TIME_ARGS (diff1), GST_TIME_ARGS (diff2), - GST_TIME_ARGS (videorate->next_ts)); + GST_TIME_ARGS (next_ts)); if (videorate->segment.rate < 0.0) { /* Make sure that we have a duration for this buffer. The previous @@ -1385,6 +1506,15 @@ gst_video_rate_stop (GstBaseTransform * trans) } static void +gst_videorate_update_duration (GstVideoRate * videorate) +{ + GstMessage *m; + + m = gst_message_new_duration_changed (GST_OBJECT (videorate)); + gst_element_post_message (GST_ELEMENT (videorate), m); +} + +static void gst_video_rate_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { @@ -1416,11 +1546,18 @@ gst_video_rate_set_property (GObject * object, case PROP_MAX_RATE: g_atomic_int_set (&videorate->max_rate, g_value_get_int (value)); goto reconfigure; + case PROP_RATE: + videorate->rate = g_value_get_double (value); + GST_OBJECT_UNLOCK (videorate); + + gst_videorate_update_duration (videorate); + return; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } GST_OBJECT_UNLOCK (videorate); + return; reconfigure: @@ -1471,6 +1608,9 @@ gst_video_rate_get_property (GObject * object, case PROP_MAX_RATE: g_value_set_int (value, g_atomic_int_get (&videorate->max_rate)); break; + case PROP_RATE: + g_value_set_double (value, videorate->rate); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; diff --git a/gst/videorate/gstvideorate.h b/gst/videorate/gstvideorate.h index d5e7c19..9a64413 100644 --- a/gst/videorate/gstvideorate.h +++ b/gst/videorate/gstvideorate.h @@ -24,7 +24,6 @@ #include G_BEGIN_DECLS - #define GST_TYPE_VIDEO_RATE \ (gst_video_rate_get_type()) #define GST_VIDEO_RATE(obj) \ @@ -35,7 +34,6 @@ G_BEGIN_DECLS (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_VIDEO_RATE)) #define GST_IS_VIDEO_RATE_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_VIDEO_RATE)) - typedef struct _GstVideoRate GstVideoRate; typedef struct _GstVideoRateClass GstVideoRateClass; @@ -80,6 +78,7 @@ struct _GstVideoRate guint64 average_period_set; volatile int max_rate; + gdouble rate; }; struct _GstVideoRateClass @@ -90,5 +89,4 @@ struct _GstVideoRateClass GType gst_video_rate_get_type (void); G_END_DECLS - #endif /* __GST_VIDEO_RATE_H__ */ diff --git a/tests/check/elements/videorate.c b/tests/check/elements/videorate.c index f0ce428..a788196 100644 --- a/tests/check/elements/videorate.c +++ b/tests/check/elements/videorate.c @@ -536,9 +536,9 @@ GST_START_TEST (test_no_framerate) GST_END_TEST; -/* This test outputs 2 buffers of same dimensions (320x240), then 1 buffer of - * differing dimensions (240x120), and then another buffer of previous - * dimensions (320x240) and checks that the 3 buffers output as a result have +/* This test outputs 2 buffers of same dimensions (320x240), then 1 buffer of + * differing dimensions (240x120), and then another buffer of previous + * dimensions (320x240) and checks that the 3 buffers output as a result have * correct caps (first 2 with 320x240 and 3rd with 240x120). */ GST_START_TEST (test_changing_size) @@ -999,8 +999,7 @@ check_caps_identical (GstCaps * a, GstCaps * b, const char *name) fail: caps_str_a = gst_caps_to_string (a); caps_str_b = gst_caps_to_string (b); - fail ("%s caps (%s) is not equal to caps (%s)", - name, caps_str_a, caps_str_b); + fail ("%s caps (%s) is not equal to caps (%s)", name, caps_str_a, caps_str_b); g_free (caps_str_a); g_free (caps_str_b); } @@ -1168,6 +1167,231 @@ GST_START_TEST (test_variable_framerate_renegotiation) GST_END_TEST; +/* Rate tests info */ +typedef struct +{ + gdouble rate; + guint64 expected_in, expected_out, expected_drop, expected_dup; + gint current_buf; + GstClockTime expected_ts; +} RateInfo; + +static RateInfo rate_tests[] = { + { + .rate = 1.0, + .expected_in = 34, + .expected_out = 25, + .expected_drop = 8, + .expected_dup = 0, + .current_buf = 0, + .expected_ts = 0}, + { + .rate = 0.5, + .expected_in = 34, + .expected_out = 50, + .expected_drop = 0, + .expected_dup = 17, + .current_buf = 0, + .expected_ts = 0}, + { + .rate = 2.0, + .expected_in = 34, + .expected_out = 13, + .expected_drop = 20, + .expected_dup = 0, + .current_buf = 0, + .expected_ts = 0}, +}; + +static GstPadProbeReturn +listen_outbuffer_ts (GstPad * pad, GstPadProbeInfo * info, gpointer user_data) +{ + GstBuffer *buffer; + guint64 buf_ts; + RateInfo *test = (RateInfo *) user_data; + + buffer = GST_PAD_PROBE_INFO_BUFFER (info); + buf_ts = GST_BUFFER_TIMESTAMP (buffer); + + GST_DEBUG ("Probed %d outbuf. ts : %" GST_TIME_FORMAT + ", expected : %" GST_TIME_FORMAT, test->current_buf, + GST_TIME_ARGS (buf_ts), GST_TIME_ARGS (test->expected_ts)); + fail_unless_equals_uint64 (buf_ts, test->expected_ts); + + /* Next expected timestamp with fps 25/1 */ + test->expected_ts += 40000000; + test->current_buf += 1; + + fail_if (test->current_buf > test->expected_out); + + return GST_PAD_PROBE_OK; +} + +GST_START_TEST (test_rate) +{ + GstElement *videorate; + RateInfo *test = &rate_tests[__i__]; + GstClockTime ts; + GstBuffer *buf; + GstCaps *caps; + gulong probe; + + videorate = setup_videorate (); + fail_unless (gst_element_set_state (videorate, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + probe = gst_pad_add_probe (mysinkpad, GST_PAD_PROBE_TYPE_BUFFER, + (GstPadProbeCallback) listen_outbuffer_ts, test, NULL); + + buf = gst_buffer_new_and_alloc (4); + gst_buffer_memset (buf, 0, 0, 4); + caps = gst_caps_from_string (VIDEO_CAPS_STRING); + gst_check_setup_events (mysrcpad, videorate, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (buf, "inbuffer", 1); + + /* Setting rate */ + g_object_set (videorate, "rate", test->rate, NULL); + + /* Push 1 second of buffers */ + for (ts = 0; ts < 1 * GST_SECOND; ts += GST_SECOND / 33) { + GstBuffer *inbuf; + + inbuf = gst_buffer_copy (buf); + GST_BUFFER_TIMESTAMP (inbuf) = ts; + + fail_unless_equals_int (gst_pad_push (mysrcpad, inbuf), GST_FLOW_OK); + } + + fail_unless_equals_int (g_list_length (buffers), test->expected_out); + assert_videorate_stats (videorate, "last buffer", test->expected_in, + test->expected_out, test->expected_drop, test->expected_dup); + + /* cleanup */ + gst_pad_remove_probe (mysinkpad, probe); + cleanup_videorate (videorate); +} + +GST_END_TEST; + +/* Probing the pad to force a fake upstream duration */ +static GstPadProbeReturn +listen_sink_query_duration (GstPad * pad, GstPadProbeInfo * info, + gpointer user_data) +{ + GstQuery *query; + gint64 *duration = (gint64 *) user_data; + + query = gst_pad_probe_info_get_query (info); + + if (GST_QUERY_TYPE (query) == GST_QUERY_DURATION) { + gst_query_set_duration (query, GST_FORMAT_TIME, *duration); + } + return GST_PAD_PROBE_OK; +} + +GST_START_TEST (test_query_duration) +{ + GstElement *videorate; + gulong probe_sink; + gint64 duration; + GstQuery *query; + + videorate = setup_videorate (); + fail_unless (gst_element_set_state (videorate, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + probe_sink = + gst_pad_add_probe (mysrcpad, + GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM | GST_PAD_PROBE_TYPE_PUSH, + (GstPadProbeCallback) listen_sink_query_duration, &duration, NULL); + + query = gst_query_new_duration (GST_FORMAT_TIME); + duration = GST_CLOCK_TIME_NONE; + gst_pad_peer_query (mysrcpad, query); + gst_query_parse_duration (query, NULL, &duration); + fail_unless_equals_uint64 (duration, GST_CLOCK_TIME_NONE); + + /* Setting fake upstream duration to 1 second */ + duration = GST_SECOND; + + /* Setting rate to 2.0 */ + g_object_set (videorate, "rate", 2.0, NULL); + + gst_pad_peer_query (mysrcpad, query); + gst_query_parse_duration (query, NULL, &duration); + fail_unless_equals_uint64 (duration, 0.5 * GST_SECOND); + + /* cleanup */ + gst_query_unref (query); + gst_pad_remove_probe (mysrcpad, probe_sink); + cleanup_videorate (videorate); +} + +GST_END_TEST; + +/* Position tests info */ +typedef struct +{ + gdouble rate; +} PositionInfo; + +static PositionInfo position_tests[] = { + { + .rate = 1.0}, + { + .rate = 0.5}, + { + .rate = 2.0}, + { + .rate = 1.7}, +}; + +GST_START_TEST (test_query_position) +{ + GstElement *videorate; + PositionInfo *test = &position_tests[__i__]; + GstClockTime ts; + GstBuffer *buf; + GstCaps *caps; + gint64 position, expected_position = 0; + + videorate = setup_videorate (); + fail_unless (gst_element_set_state (videorate, + GST_STATE_PLAYING) == GST_STATE_CHANGE_SUCCESS, + "could not set to playing"); + + buf = gst_buffer_new_and_alloc (4); + gst_buffer_memset (buf, 0, 0, 4); + caps = gst_caps_from_string (VIDEO_CAPS_STRING); + gst_check_setup_events (mysrcpad, videorate, caps, GST_FORMAT_TIME); + gst_caps_unref (caps); + ASSERT_BUFFER_REFCOUNT (buf, "inbuffer", 1); + + /* Push a few buffers */ + g_object_set (videorate, "rate", test->rate, NULL); + for (ts = 0; ts < GST_SECOND; ts += GST_SECOND / 20) { + GstBuffer *inbuf; + + inbuf = gst_buffer_copy (buf); + GST_BUFFER_TIMESTAMP (inbuf) = ts; + + fail_unless_equals_int (gst_pad_push (mysrcpad, inbuf), GST_FLOW_OK); + + expected_position = ts / test->rate; + gst_element_query_position (videorate, GST_FORMAT_TIME, &position); + GST_DEBUG_OBJECT (NULL, + "pushed buffer %" GST_TIME_FORMAT ", queried pos: %" GST_TIME_FORMAT + ", expected pos: %" GST_TIME_FORMAT, GST_TIME_ARGS (ts), + GST_TIME_ARGS (position), GST_TIME_ARGS (expected_position)); + fail_unless_equals_uint64 (position, expected_position); + } + + /* cleanup */ + cleanup_videorate (videorate); +} + +GST_END_TEST; static Suite * videorate_suite (void) @@ -1189,6 +1413,10 @@ videorate_suite (void) 0, G_N_ELEMENTS (caps_negotiation_tests)); tcase_add_test (tc_chain, test_fixed_framerate); tcase_add_test (tc_chain, test_variable_framerate_renegotiation); + tcase_add_loop_test (tc_chain, test_rate, 0, G_N_ELEMENTS (rate_tests)); + tcase_add_test (tc_chain, test_query_duration); + tcase_add_loop_test (tc_chain, test_query_position, 0, + G_N_ELEMENTS (position_tests)); return s; }