videorate: Add fixed rate property

https://bugzilla.gnome.org/show_bug.cgi?id=699077
This commit is contained in:
Joris Valette 2013-09-17 17:42:05 +02:00 committed by Thibault Saunier
parent 9121131f31
commit 658ee6f0db
3 changed files with 388 additions and 22 deletions

View file

@ -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.
*
* <refsect2>
* <title>Example pipelines</title>
* |[
@ -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);
@ -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:{
@ -871,6 +907,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
@ -1384,6 +1505,15 @@ gst_video_rate_stop (GstBaseTransform * trans)
return TRUE;
}
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;

View file

@ -24,7 +24,6 @@
#include <gst/base/gstbasetransform.h>
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__ */

View file

@ -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;
}