* 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.
+ *
* <refsect2>
* <title>Example pipelines</title>
* |[
#define DEFAULT_DROP_ONLY FALSE
#define DEFAULT_AVERAGE_PERIOD 0
#define DEFAULT_MAX_RATE G_MAXINT
+#define DEFAULT_RATE 1.0
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 =
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);
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);
/**
* 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,
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",
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;
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");
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:{
}
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)
{
/* 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,
/* 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
}
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)
{
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:
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;
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)
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);
}
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)
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;
}