gstreamer/tests/check/libs/rtpbasepayload.c
Stian Selnes f766b85b96 rtpbasepayload: rtpbasedepayload: Add source-info property
Add a source-info property that will read/write meta to the buffers
about RTP source information. The GstRTPSourceMeta can be used to
transport information about the origin of a buffer, e.g. the sources
that is included in a mixed audio buffer.

A new function gst_rtp_base_payload_allocate_output_buffer() is added
for payloaders to use to allocate the output RTP buffer with the correct
number of CSRCs according to the meta and fill it.

RTPSourceMeta does not make sense on RTP buffers since the information
is in the RTP header. So the payloader will strip the meta from the
output buffer.

https://bugzilla.gnome.org/show_bug.cgi?id=761947
2018-10-10 14:38:01 -04:00

1970 lines
65 KiB
C

/* GStreamer RTP base payloader unit tests
* Copyright (C) 2014 Sebastian Rasmussen <sebras@hotmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General
* Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/gst.h>
#include <gst/check/gstcheck.h>
#include <gst/check/gstharness.h>
#include <gst/rtp/rtp.h>
#define DEFAULT_CLOCK_RATE (42)
#define BUFFER_BEFORE_LIST (10)
/* GstRtpDummyPay */
#define GST_TYPE_RTP_DUMMY_PAY \
(gst_rtp_dummy_pay_get_type())
#define GST_RTP_DUMMY_PAY(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_DUMMY_PAY,GstRtpDummyPay))
#define GST_RTP_DUMMY_PAY_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_DUMMY_PAY,GstRtpDummyPayClass))
#define GST_IS_RTP_DUMMY_PAY(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_DUMMY_PAY))
#define GST_IS_RTP_DUMMY_PAY_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_DUMMY_PAY))
typedef struct _GstRtpDummyPay GstRtpDummyPay;
typedef struct _GstRtpDummyPayClass GstRtpDummyPayClass;
struct _GstRtpDummyPay
{
GstRTPBasePayload payload;
};
struct _GstRtpDummyPayClass
{
GstRTPBasePayloadClass parent_class;
};
GType gst_rtp_dummy_pay_get_type (void);
G_DEFINE_TYPE (GstRtpDummyPay, gst_rtp_dummy_pay, GST_TYPE_RTP_BASE_PAYLOAD);
static GstFlowReturn gst_rtp_dummy_pay_handle_buffer (GstRTPBasePayload * pay,
GstBuffer * buffer);
static GstStaticPadTemplate gst_rtp_dummy_pay_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS_ANY);
static GstStaticPadTemplate gst_rtp_dummy_pay_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("application/x-rtp"));
static void
gst_rtp_dummy_pay_class_init (GstRtpDummyPayClass * klass)
{
GstElementClass *gstelement_class;
GstRTPBasePayloadClass *gstrtpbasepayload_class;
gstelement_class = GST_ELEMENT_CLASS (klass);
gstrtpbasepayload_class = GST_RTP_BASE_PAYLOAD_CLASS (klass);
gst_element_class_add_static_pad_template (gstelement_class,
&gst_rtp_dummy_pay_sink_template);
gst_element_class_add_static_pad_template (gstelement_class,
&gst_rtp_dummy_pay_src_template);
gstrtpbasepayload_class->handle_buffer = gst_rtp_dummy_pay_handle_buffer;
}
static void
gst_rtp_dummy_pay_init (GstRtpDummyPay * pay)
{
gst_rtp_base_payload_set_options (GST_RTP_BASE_PAYLOAD (pay), "application",
TRUE, "dummy", DEFAULT_CLOCK_RATE);
}
static GstRtpDummyPay *
rtp_dummy_pay_new (void)
{
return g_object_new (GST_TYPE_RTP_DUMMY_PAY, NULL);
}
static GstFlowReturn
gst_rtp_dummy_pay_handle_buffer (GstRTPBasePayload * pay, GstBuffer * buffer)
{
GstBuffer *paybuffer;
GST_LOG ("payloading buffer pts=%" GST_TIME_FORMAT " offset=%"
G_GUINT64_FORMAT, GST_TIME_ARGS (GST_BUFFER_PTS (buffer)),
GST_BUFFER_OFFSET (buffer));
if (!gst_pad_has_current_caps (GST_RTP_BASE_PAYLOAD_SRCPAD (pay))) {
if (!gst_rtp_base_payload_set_outcaps (GST_RTP_BASE_PAYLOAD (pay),
"custom-caps", G_TYPE_UINT, DEFAULT_CLOCK_RATE, NULL)) {
gst_buffer_unref (buffer);
return GST_FLOW_NOT_NEGOTIATED;
}
}
paybuffer =
gst_rtp_base_payload_allocate_output_buffer (GST_RTP_BASE_PAYLOAD (pay),
0, 0, 0);
GST_BUFFER_PTS (paybuffer) = GST_BUFFER_PTS (buffer);
GST_BUFFER_OFFSET (paybuffer) = GST_BUFFER_OFFSET (buffer);
gst_buffer_append (paybuffer, buffer);
GST_LOG ("payloaded buffer pts=%" GST_TIME_FORMAT " offset=%"
G_GUINT64_FORMAT, GST_TIME_ARGS (GST_BUFFER_PTS (paybuffer)),
GST_BUFFER_OFFSET (paybuffer));
if (GST_BUFFER_PTS (paybuffer) < BUFFER_BEFORE_LIST) {
return gst_rtp_base_payload_push (pay, paybuffer);
} else {
GstBufferList *list = gst_buffer_list_new ();
gst_buffer_list_add (list, paybuffer);
return gst_rtp_base_payload_push_list (pay, list);
}
}
/* Helper functions and global state */
static GstStaticPadTemplate srctmpl = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS_ANY);
static GstStaticPadTemplate sinktmpl = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS_ANY);
static GstStaticPadTemplate special_sinktmpl = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("application/x-rtp, payload=(int)98, ssrc=(uint)24, "
"timestamp-offset=(uint)212, seqnum-offset=(uint)2424"));
typedef struct State State;
struct State
{
GstElement *element;
GstPad *sinkpad;
GstPad *srcpad;
};
static GList *events;
static gboolean
event_func (GstPad * pad, GstObject * noparent, GstEvent * event)
{
events = g_list_append (events, gst_event_ref (event));
return gst_pad_event_default (pad, noparent, event);
}
static void
drop_events (void)
{
while (events != NULL) {
gst_event_unref (GST_EVENT (events->data));
events = g_list_delete_link (events, events);
}
}
static void
validate_events_received (guint received)
{
fail_unless_equals_int (g_list_length (events), received);
}
static void
validate_event (guint index, const gchar * name, const gchar * field, ...)
{
GstEvent *event;
va_list var_args;
fail_if (index >= g_list_length (events));
event = GST_EVENT (g_list_nth_data (events, index));
fail_if (event == NULL);
GST_TRACE ("%" GST_PTR_FORMAT, event);
fail_unless_equals_string (GST_EVENT_TYPE_NAME (event), name);
va_start (var_args, field);
while (field) {
if (!g_strcmp0 (field, "timestamp")) {
GstClockTime expected = va_arg (var_args, GstClockTime);
GstClockTime timestamp, duration;
gst_event_parse_gap (event, &timestamp, &duration);
fail_unless_equals_uint64 (timestamp, expected);
} else if (!g_strcmp0 (field, "duration")) {
GstClockTime expected = va_arg (var_args, GstClockTime);
GstClockTime timestamp, duration;
gst_event_parse_gap (event, &timestamp, &duration);
fail_unless_equals_uint64 (duration, expected);
} else if (!g_strcmp0 (field, "time")) {
GstClockTime expected = va_arg (var_args, GstClockTime);
const GstSegment *segment;
gst_event_parse_segment (event, &segment);
fail_unless_equals_uint64 (segment->time, expected);
} else if (!g_strcmp0 (field, "start")) {
GstClockTime expected = va_arg (var_args, GstClockTime);
const GstSegment *segment;
gst_event_parse_segment (event, &segment);
fail_unless_equals_uint64 (segment->start, expected);
} else if (!g_strcmp0 (field, "stop")) {
GstClockTime expected = va_arg (var_args, GstClockTime);
const GstSegment *segment;
gst_event_parse_segment (event, &segment);
fail_unless_equals_uint64 (segment->stop, expected);
} else if (!g_strcmp0 (field, "applied-rate")) {
gdouble expected = va_arg (var_args, gdouble);
const GstSegment *segment;
gst_event_parse_segment (event, &segment);
fail_unless_equals_uint64 (segment->applied_rate, expected);
} else if (!g_strcmp0 (field, "rate")) {
gdouble expected = va_arg (var_args, gdouble);
const GstSegment *segment;
gst_event_parse_segment (event, &segment);
fail_unless_equals_uint64 (segment->rate, expected);
} else if (!g_strcmp0 (field, "media-type")) {
const gchar *expected = va_arg (var_args, const gchar *);
GstCaps *caps;
const gchar *media_type;
gst_event_parse_caps (event, &caps);
media_type = gst_structure_get_name (gst_caps_get_structure (caps, 0));
fail_unless_equals_string (media_type, expected);
} else if (!g_strcmp0 (field, "npt-start")) {
GstClockTime expected = va_arg (var_args, GstClockTime);
GstCaps *caps;
GstClockTime start;
gst_event_parse_caps (event, &caps);
fail_unless (gst_structure_get_clock_time (gst_caps_get_structure (caps,
0), "npt-start", &start));
fail_unless_equals_uint64 (start, expected);
} else if (!g_strcmp0 (field, "npt-stop")) {
GstClockTime expected = va_arg (var_args, GstClockTime);
GstCaps *caps;
GstClockTime stop;
gst_event_parse_caps (event, &caps);
fail_unless (gst_structure_get_clock_time (gst_caps_get_structure (caps,
0), "npt-stop", &stop));
fail_unless_equals_uint64 (stop, expected);
} else if (!g_strcmp0 (field, "play-speed")) {
gdouble expected = va_arg (var_args, gdouble);
GstCaps *caps;
gdouble speed;
gst_event_parse_caps (event, &caps);
fail_unless (gst_structure_get_double (gst_caps_get_structure (caps, 0),
"play-speed", &speed));
fail_unless (speed == expected);
} else if (!g_strcmp0 (field, "play-scale")) {
gdouble expected = va_arg (var_args, gdouble);
GstCaps *caps;
gdouble scale;
gst_event_parse_caps (event, &caps);
fail_unless (gst_structure_get_double (gst_caps_get_structure (caps, 0),
"play-scale", &scale));
fail_unless (scale == expected);
} else if (!g_strcmp0 (field, "ssrc")) {
guint expected = va_arg (var_args, guint);
GstCaps *caps;
guint ssrc;
gst_event_parse_caps (event, &caps);
fail_unless (gst_structure_get_uint (gst_caps_get_structure (caps, 0),
"ssrc", &ssrc));
fail_unless_equals_int (ssrc, expected);
} else if (!g_strcmp0 (field, "a-framerate")) {
const gchar *expected = va_arg (var_args, const gchar *);
GstCaps *caps;
const gchar *framerate;
gst_event_parse_caps (event, &caps);
framerate = gst_structure_get_string (gst_caps_get_structure (caps, 0),
"a-framerate");
fail_unless_equals_string (framerate, expected);
} else {
fail ("test cannot validate unknown event field '%s'", field);
}
field = va_arg (var_args, const gchar *);
}
va_end (var_args);
}
static void
validate_normal_start_events (guint index)
{
validate_event (index, "stream-start", NULL);
validate_event (index + 1, "caps", "media-type", "application/x-rtp", NULL);
validate_event (index + 2, "segment",
"time", G_GUINT64_CONSTANT (0),
"start", G_GUINT64_CONSTANT (0), "stop", G_MAXUINT64, NULL);
}
#define push_buffer(state, field, ...) \
push_buffer_full ((state), GST_FLOW_OK, (field), __VA_ARGS__)
#define push_buffer_fails(state, field, ...) \
push_buffer_full ((state), GST_FLOW_FLUSHING, (field), __VA_ARGS__)
static void
push_buffer_full (State * state, GstFlowReturn expected,
const gchar * field, ...)
{
GstBuffer *buf = gst_rtp_buffer_new_allocate (0, 0, 0);
GstRTPBuffer rtp = { NULL };
gboolean mapped = FALSE;
va_list var_args;
va_start (var_args, field);
while (field) {
if (!g_strcmp0 (field, "pts")) {
GstClockTime pts = va_arg (var_args, GstClockTime);
GST_BUFFER_PTS (buf) = pts;
} else if (!g_strcmp0 (field, "offset")) {
guint64 offset = va_arg (var_args, guint64);
GST_BUFFER_OFFSET (buf) = offset;
} else if (!g_strcmp0 (field, "discont")) {
gboolean discont = va_arg (var_args, gboolean);
if (discont) {
GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
} else {
GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT);
}
} else {
if (!mapped) {
gst_rtp_buffer_map (buf, GST_MAP_WRITE, &rtp);
mapped = TRUE;
}
if (!g_strcmp0 (field, "rtptime")) {
guint32 rtptime = va_arg (var_args, guint);
gst_rtp_buffer_set_timestamp (&rtp, rtptime);
} else if (!g_strcmp0 (field, "payload-type")) {
guint payload_type = va_arg (var_args, guint);
gst_rtp_buffer_set_payload_type (&rtp, payload_type);
} else if (!g_strcmp0 (field, "seq")) {
guint seq = va_arg (var_args, guint);
gst_rtp_buffer_set_seq (&rtp, seq);
} else if (!g_strcmp0 (field, "ssrc")) {
guint32 ssrc = va_arg (var_args, guint);
gst_rtp_buffer_set_ssrc (&rtp, ssrc);
} else {
fail ("test cannot set unknown buffer field '%s'", field);
}
}
field = va_arg (var_args, const gchar *);
}
va_end (var_args);
if (mapped) {
gst_rtp_buffer_unmap (&rtp);
}
fail_unless_equals_int (gst_pad_push (state->srcpad, buf), expected);
}
static void
push_buffer_list (State * state, const gchar * field, ...)
{
GstBuffer *buf = gst_rtp_buffer_new_allocate (0, 0, 0);
GstRTPBuffer rtp = { NULL };
gboolean mapped = FALSE;
GstBufferList *list;
va_list var_args;
va_start (var_args, field);
while (field) {
if (!g_strcmp0 (field, "pts")) {
GstClockTime pts = va_arg (var_args, GstClockTime);
GST_BUFFER_PTS (buf) = pts;
} else if (!g_strcmp0 (field, "offset")) {
guint64 offset = va_arg (var_args, guint64);
GST_BUFFER_OFFSET (buf) = offset;
} else if (!g_strcmp0 (field, "discont")) {
gboolean discont = va_arg (var_args, gboolean);
if (discont) {
GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
} else {
GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT);
}
} else {
if (!mapped) {
gst_rtp_buffer_map (buf, GST_MAP_WRITE, &rtp);
mapped = TRUE;
}
if (!g_strcmp0 (field, "rtptime")) {
guint32 rtptime = va_arg (var_args, guint);
gst_rtp_buffer_set_timestamp (&rtp, rtptime);
} else if (!g_strcmp0 (field, "payload-type")) {
guint payload_type = va_arg (var_args, guint);
gst_rtp_buffer_set_payload_type (&rtp, payload_type);
} else if (!g_strcmp0 (field, "seq")) {
guint seq = va_arg (var_args, guint);
gst_rtp_buffer_set_seq (&rtp, seq);
} else if (!g_strcmp0 (field, "ssrc")) {
guint32 ssrc = va_arg (var_args, guint);
gst_rtp_buffer_set_ssrc (&rtp, ssrc);
} else {
fail ("test cannot set unknown buffer field '%s'", field);
}
}
field = va_arg (var_args, const gchar *);
}
va_end (var_args);
if (mapped) {
gst_rtp_buffer_unmap (&rtp);
}
list = gst_buffer_list_new ();
gst_buffer_list_add (list, buf);
fail_unless_equals_int (gst_pad_push_list (state->srcpad, list), GST_FLOW_OK);
}
static void
validate_buffers_received (guint received_buffers)
{
fail_unless_equals_int (g_list_length (buffers), received_buffers);
}
static void
validate_buffer_valist (GstBuffer * buf, const gchar * field, va_list var_args)
{
GstRTPBuffer rtp = { NULL };
gboolean mapped = FALSE;
while (field) {
if (!g_strcmp0 (field, "pts")) {
GstClockTime pts = va_arg (var_args, GstClockTime);
fail_unless_equals_uint64 (GST_BUFFER_PTS (buf), pts);
} else if (!g_strcmp0 (field, "offset")) {
guint64 offset = va_arg (var_args, guint64);
fail_unless_equals_uint64 (GST_BUFFER_OFFSET (buf), offset);
} else if (!g_strcmp0 (field, "discont")) {
gboolean discont = va_arg (var_args, gboolean);
if (discont) {
fail_unless (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT));
} else {
fail_if (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT));
}
} else {
if (!mapped) {
gst_rtp_buffer_map (buf, GST_MAP_READ, &rtp);
mapped = TRUE;
}
if (!g_strcmp0 (field, "rtptime")) {
guint32 rtptime = va_arg (var_args, guint);
fail_unless_equals_int (gst_rtp_buffer_get_timestamp (&rtp), rtptime);
} else if (!g_strcmp0 (field, "payload-type")) {
guint pt = va_arg (var_args, guint);
fail_unless_equals_int (gst_rtp_buffer_get_payload_type (&rtp), pt);
} else if (!g_strcmp0 (field, "seq")) {
guint seq = va_arg (var_args, guint);
fail_unless_equals_int (gst_rtp_buffer_get_seq (&rtp), seq);
} else if (!g_strcmp0 (field, "ssrc")) {
guint32 ssrc = va_arg (var_args, guint);
fail_unless_equals_int (gst_rtp_buffer_get_ssrc (&rtp), ssrc);
} else if (!g_strcmp0 (field, "csrc")) {
guint idx = va_arg (var_args, guint);
guint csrc = va_arg (var_args, guint);
fail_unless_equals_int (gst_rtp_buffer_get_csrc (&rtp, idx), csrc);
} else if (!g_strcmp0 (field, "csrc-count")) {
guint csrc_count = va_arg (var_args, guint);
fail_unless_equals_int (gst_rtp_buffer_get_csrc_count (&rtp),
csrc_count);
} else {
fail ("test cannot validate unknown buffer field '%s'", field);
}
}
field = va_arg (var_args, const gchar *);
}
if (mapped) {
gst_rtp_buffer_unmap (&rtp);
}
}
static void
validate_buffer1 (GstBuffer * buf, const gchar * field, ...)
{
va_list var_args;
va_start (var_args, field);
validate_buffer_valist (buf, field, var_args);
va_end (var_args);
}
static void
validate_buffer (guint index, const gchar * field, ...)
{
GstBuffer *buf;
va_list var_args;
fail_if (index >= g_list_length (buffers));
buf = GST_BUFFER (g_list_nth_data (buffers, index));
fail_if (buf == NULL);
GST_TRACE ("%" GST_PTR_FORMAT, buf);
va_start (var_args, field);
validate_buffer_valist (buf, field, var_args);
va_end (var_args);
}
static void
get_buffer_field (guint index, const gchar * field, ...)
{
GstBuffer *buf;
GstRTPBuffer rtp = { NULL };
gboolean mapped = FALSE;
va_list var_args;
fail_if (index >= g_list_length (buffers));
buf = GST_BUFFER (g_list_nth_data (buffers, (index)));
fail_if (buf == NULL);
va_start (var_args, field);
while (field) {
if (!g_strcmp0 (field, "pts")) {
GstClockTime *pts = va_arg (var_args, GstClockTime *);
*pts = GST_BUFFER_PTS (buf);
} else if (!g_strcmp0 (field, "offset")) {
guint64 *offset = va_arg (var_args, guint64 *);
*offset = GST_BUFFER_OFFSET (buf);
} else if (!g_strcmp0 (field, "discont")) {
gboolean *discont = va_arg (var_args, gboolean *);
*discont = GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DISCONT);
} else {
if (!mapped) {
gst_rtp_buffer_map (buf, GST_MAP_READ, &rtp);
mapped = TRUE;
}
if (!g_strcmp0 (field, "rtptime")) {
guint32 *rtptime = va_arg (var_args, guint32 *);
*rtptime = gst_rtp_buffer_get_timestamp (&rtp);
} else if (!g_strcmp0 (field, "payload-type")) {
guint *pt = va_arg (var_args, guint *);
*pt = gst_rtp_buffer_get_payload_type (&rtp);
} else if (!g_strcmp0 (field, "seq")) {
guint16 *seq = va_arg (var_args, guint16 *);
*seq = gst_rtp_buffer_get_seq (&rtp);
} else if (!g_strcmp0 (field, "ssrc")) {
guint32 *ssrc = va_arg (var_args, guint32 *);
*ssrc = gst_rtp_buffer_get_ssrc (&rtp);
} else {
fail ("test retrieve validate unknown buffer field '%s'", field);
}
}
field = va_arg (var_args, const gchar *);
}
va_end (var_args);
if (mapped)
gst_rtp_buffer_unmap (&rtp);
}
static State *
create_payloader (const gchar * caps_str,
GstStaticPadTemplate * sinktmpl, const gchar * property, ...)
{
va_list var_args;
GstCaps *caps;
State *state;
state = g_new0 (State, 1);
state->element = GST_ELEMENT (rtp_dummy_pay_new ());
fail_unless (GST_IS_RTP_DUMMY_PAY (state->element));
va_start (var_args, property);
g_object_set_valist (G_OBJECT (state->element), property, var_args);
va_end (var_args);
state->srcpad = gst_check_setup_src_pad (state->element, &srctmpl);
state->sinkpad = gst_check_setup_sink_pad (state->element, sinktmpl);
fail_unless (gst_pad_set_active (state->srcpad, TRUE));
fail_unless (gst_pad_set_active (state->sinkpad, TRUE));
caps = gst_caps_from_string (caps_str);
gst_check_setup_events (state->srcpad, state->element, caps, GST_FORMAT_TIME);
gst_caps_unref (caps);
gst_pad_set_chain_function (state->sinkpad, gst_check_chain_func);
gst_pad_set_event_function (state->sinkpad, event_func);
return state;
}
static void
set_state (State * state, GstState new_state)
{
fail_unless_equals_int (gst_element_set_state (state->element, new_state),
GST_STATE_CHANGE_SUCCESS);
}
static void
validate_would_not_be_filled (State * state, guint size, GstClockTime duration)
{
GstRTPBasePayload *basepay;
basepay = GST_RTP_BASE_PAYLOAD (state->element);
fail_if (gst_rtp_base_payload_is_filled (basepay, size, duration));
}
static void
validate_would_be_filled (State * state, guint size, GstClockTime duration)
{
GstRTPBasePayload *basepay;
basepay = GST_RTP_BASE_PAYLOAD (state->element);
fail_unless (gst_rtp_base_payload_is_filled (basepay, size, duration));
}
static void
ssrc_collision (State * state, guint ssrc,
gboolean have_new_ssrc, guint new_ssrc)
{
GstStructure *s;
GstEvent *event;
if (have_new_ssrc) {
s = gst_structure_new ("GstRTPCollision",
"ssrc", G_TYPE_UINT, ssrc,
"suggested-ssrc", G_TYPE_UINT, new_ssrc, NULL);
} else {
s = gst_structure_new ("GstRTPCollision", "ssrc", G_TYPE_UINT, ssrc, NULL);
}
event = gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM, s);
fail_unless (gst_pad_push_event (state->sinkpad, event));
}
static void
reconfigure (State * state)
{
GstEvent *event;
event = gst_event_new_reconfigure ();
fail_unless (gst_pad_push_event (state->sinkpad, event));
}
static void
validate_stats (State * state, guint clock_rate,
GstClockTime running_time, guint16 seq, guint32 rtptime)
{
GstStructure *stats;
g_object_get (state->element, "stats", &stats, NULL);
fail_unless_equals_int (g_value_get_uint (gst_structure_get_value (stats,
"clock-rate")), clock_rate);
fail_unless_equals_uint64 (g_value_get_uint64 (gst_structure_get_value (stats,
"running-time")), running_time);
fail_unless_equals_int (g_value_get_uint (gst_structure_get_value (stats,
"seqnum")), seq);
fail_unless_equals_int (g_value_get_uint (gst_structure_get_value (stats,
"timestamp")), rtptime);
gst_structure_free (stats);
}
static void
destroy_payloader (State * state)
{
gst_check_teardown_sink_pad (state->element);
gst_check_teardown_src_pad (state->element);
gst_check_drop_buffers ();
drop_events ();
g_object_unref (state->element);
g_free (state);
}
/* Tests */
/* push two buffers to the payloader which should successfully payload them
* into RTP packets. the first packet will have a random rtptime and sequence
* number, but the last packet should have an rtptime incremented by
* DEFAULT_CLOCK_RATE and a sequence number incremented by one becuase the
* packets are sequential. besides the two payloaded RTP packets there should
* be the three events initial events: stream-start, caps and segment.
*/
GST_START_TEST (rtp_base_payload_buffer_test)
{
State *state;
guint32 rtptime;
guint16 seq;
state = create_payloader ("application/x-rtp", &sinktmpl,
"perfect-rtptime", FALSE, NULL);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 0 * GST_SECOND, NULL);
push_buffer (state, "pts", 1 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
validate_buffers_received (2);
validate_buffer (0, "pts", 0 * GST_SECOND, NULL);
get_buffer_field (0, "rtptime", &rtptime, "seq", &seq, NULL);
validate_buffer (1,
"pts", 1 * GST_SECOND,
"rtptime", rtptime + 1 * DEFAULT_CLOCK_RATE, "seq", seq + 1, NULL);
validate_events_received (3);
validate_normal_start_events (0);
destroy_payloader (state);
}
GST_END_TEST;
/* push single buffers in buffer lists to the payloader to be payloaded into
* RTP packets. the dummy payloader will start pushing buffer lists itself
* after BUFFER_BEFORE_LIST payloaded RTP packets. any RTP packets included in
* buffer lists should have rtptime and sequence numbers incrementting in the
* same way as for separate RTP packets.
*/
GST_START_TEST (rtp_base_payload_buffer_list_test)
{
State *state;
guint32 rtptime;
guint16 seq;
guint i;
state = create_payloader ("application/x-rtp", &sinktmpl, NULL);
set_state (state, GST_STATE_PLAYING);
for (i = 0; i < BUFFER_BEFORE_LIST + 1; i++) {
push_buffer_list (state, "pts", i * GST_SECOND, NULL);
}
set_state (state, GST_STATE_NULL);
validate_buffers_received (11);
validate_buffer (0, "pts", 0 * GST_SECOND, NULL);
get_buffer_field (0, "rtptime", &rtptime, "seq", &seq, NULL);
for (i = 1; i < BUFFER_BEFORE_LIST + 1; i++) {
validate_buffer (i,
"pts", i * GST_SECOND,
"rtptime", rtptime + i * DEFAULT_CLOCK_RATE, "seq", seq + i, NULL);
}
validate_events_received (3);
validate_normal_start_events (0);
destroy_payloader (state);
}
GST_END_TEST;
/* push two buffers. because the payloader is using non-perfect rtptime the
* second buffer will be timestamped with the default clock and ignore any
* offset set on the buffers being payloaded.
*/
GST_START_TEST (rtp_base_payload_normal_rtptime_test)
{
guint32 rtptime;
State *state;
state = create_payloader ("application/x-rtp", &sinktmpl,
"perfect-rtptime", FALSE, NULL);
set_state (state, GST_STATE_PLAYING);
push_buffer (state,
"pts", 0 * GST_SECOND, "offset", GST_BUFFER_OFFSET_NONE, NULL);
push_buffer (state,
"pts", 1 * GST_SECOND, "offset", GST_BUFFER_OFFSET_NONE, NULL);
set_state (state, GST_STATE_NULL);
validate_buffers_received (2);
validate_buffer (0,
"pts", 0 * GST_SECOND, "offset", GST_BUFFER_OFFSET_NONE, NULL);
get_buffer_field (0, "rtptime", &rtptime, NULL);
validate_buffer (1,
"pts", 1 * GST_SECOND,
"offset", GST_BUFFER_OFFSET_NONE,
"rtptime", rtptime + DEFAULT_CLOCK_RATE, NULL);
validate_events_received (3);
validate_normal_start_events (0);
destroy_payloader (state);
}
GST_END_TEST;
/* push two buffers. because the payloader is using perfect rtptime the
* second buffer will be timestamped with a timestamp incremented with the
* difference in offset between the first and second buffer. the pts will be
* ignored for any buffer after the first buffer.
*/
GST_START_TEST (rtp_base_payload_perfect_rtptime_test)
{
guint32 rtptime;
State *state;
state = create_payloader ("application/x-rtp", &sinktmpl,
"perfect-rtptime", TRUE, NULL);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 0 * GST_SECOND, "offset", G_GINT64_CONSTANT (0),
NULL);
push_buffer (state, "pts", GST_CLOCK_TIME_NONE, "offset",
G_GINT64_CONSTANT (21), NULL);
set_state (state, GST_STATE_NULL);
validate_buffers_received (2);
validate_buffer (0, "pts", 0 * GST_SECOND, "offset", G_GINT64_CONSTANT (0),
NULL);
get_buffer_field (0, "rtptime", &rtptime, NULL);
validate_buffer (1,
"pts", GST_CLOCK_TIME_NONE, "offset", G_GINT64_CONSTANT (21), "rtptime",
rtptime + 21, NULL);
validate_events_received (3);
validate_normal_start_events (0);
destroy_payloader (state);
}
GST_END_TEST;
/* validate that a payloader will re-use the last used timestamp when a buffer
* is using perfect rtptime and both the pushed buffers timestamp and the offset
* is NONE. the payloader is configuered to start with a specific timestamp.
* then a buffer is sent with a valid timestamp but without any offset. the
* payloded RTP packet is expected to use the specific timestamp. next another
* buffer is pushed with a normal timestamp set to illustrate that the payloaded
* RTP packet will have an increased timestamp. finally a buffer without any
* timestamp or offset is pushed. in this case the payloaded RTP packet is
* expected to have the same timestamp as the previously payloaded RTP packet.
*/
GST_START_TEST (rtp_base_payload_no_pts_no_offset_test)
{
State *state;
state = create_payloader ("application/x-rtp", &sinktmpl,
"timestamp-offset", 0x42, NULL);
set_state (state, GST_STATE_PLAYING);
push_buffer (state,
"pts", 0 * GST_SECOND, "offset", GST_BUFFER_OFFSET_NONE, NULL);
push_buffer (state,
"pts", 1 * GST_SECOND, "offset", GST_BUFFER_OFFSET_NONE, NULL);
push_buffer (state,
"pts", GST_CLOCK_TIME_NONE, "offset", GST_BUFFER_OFFSET_NONE, NULL);
set_state (state, GST_STATE_NULL);
validate_buffers_received (3);
validate_buffer (0,
"pts", 0 * GST_SECOND,
"offset", GST_BUFFER_OFFSET_NONE, "rtptime", 0x42, NULL);
validate_buffer (1,
"pts", 1 * GST_SECOND,
"offset", GST_BUFFER_OFFSET_NONE,
"rtptime", 0x42 + 1 * DEFAULT_CLOCK_RATE, NULL);
validate_buffer (2,
"pts", GST_CLOCK_TIME_NONE,
"offset", GST_BUFFER_OFFSET_NONE,
"rtptime", 0x42 + 1 * DEFAULT_CLOCK_RATE, NULL);
validate_events_received (3);
validate_normal_start_events (0);
destroy_payloader (state);
}
GST_END_TEST;
/* validate that a downstream element with caps on its sink pad can effectively
* configure the payloader's payload-type, ssrc, timestamp-offset and
* seqnum-offset properties and therefore also affect the payloaded RTP packets.
* this is done by connecting to a sink pad with template caps setting the
* relevant fields and then pushing a buffer and making sure that the payloaded
* RTP packet has the expected properties.
*/
GST_START_TEST (rtp_base_payload_downstream_caps_test)
{
State *state;
state = create_payloader ("application/x-rtp", &special_sinktmpl, NULL);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 0 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
validate_buffers_received (1);
validate_buffer (0,
"pts", 0 * GST_SECOND,
"seq", 2424, "payload-type", 98, "ssrc", 24, "rtptime", 212, NULL);
validate_events_received (3);
validate_normal_start_events (0);
destroy_payloader (state);
}
GST_END_TEST;
/* when a payloader receives a GstRTPCollision upstream event it should try to
* switch to a new ssrc for the next payloaded RTP packets. GstRTPCollision can
* supply a suggested new ssrc. if a suggested new ssrc is supplied then the
* payloaded is supposed to use this new ssrc, otherwise it should generate a
* new random ssrc which is not identical to the one that collided.
*
* this is tested by first setting the ssrc to a specific value and pushing a
* buffer. the payloaded RTP packet is validate to have the set ssrc. then a
* GstRTPCollision event is generated to instruct the payloader that the
* previously set ssrc collided. this event suggests a new ssrc and it is
* verified that a pushed buffer results in a payloaded RTP packet that actually
* uses this new ssrc. finally a new GstRTPCollision event is generated to
* indicate another ssrc collision. this time the event does not suggest a new
* ssrc. the payloaded RTP packet is then expected to have a new random ssrc
* different from the collided one.
*/
GST_START_TEST (rtp_base_payload_ssrc_collision_test)
{
State *state;
guint32 ssrc;
state = create_payloader ("application/x-rtp", &sinktmpl, NULL);
g_object_set (state->element, "ssrc", 0x4242, NULL);
g_object_get (state->element, "ssrc", &ssrc, NULL);
fail_unless_equals_int (ssrc, 0x4242);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 0 * GST_SECOND, NULL);
ssrc_collision (state, 0x4242, TRUE, 0x4343);
push_buffer (state, "pts", 1 * GST_SECOND, NULL);
ssrc_collision (state, 0x4343, FALSE, 0);
push_buffer (state, "pts", 2 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
validate_buffers_received (3);
validate_buffer (0, "pts", 0 * GST_SECOND, "ssrc", 0x4242, NULL);
validate_buffer (1, "pts", 1 * GST_SECOND, "ssrc", 0x4343, NULL);
validate_buffer (2, "pts", 2 * GST_SECOND, NULL);
get_buffer_field (2, "ssrc", &ssrc, NULL);
fail_if (ssrc == 0x4343);
validate_events_received (5);
validate_normal_start_events (0);
validate_event (3, "caps",
"media-type", "application/x-rtp", "ssrc", 0x4343, NULL);
validate_event (4, "caps",
"media-type", "application/x-rtp", "ssrc", ssrc, NULL);
destroy_payloader (state);
}
GST_END_TEST;
/* validate that an upstream event different from GstRTPCollision is succesfully
* forwarded to upstream elements. in this test a caps reconfiguration event is
* pushed upstream to validate the behaviour.
*/
GST_START_TEST (rtp_base_payload_reconfigure_test)
{
State *state;
state = create_payloader ("application/x-rtp", &sinktmpl, NULL);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 0 * GST_SECOND, NULL);
reconfigure (state);
push_buffer (state, "pts", 1 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
validate_buffers_received (2);
validate_buffer (0, "pts", 0 * GST_SECOND, NULL);
validate_buffer (1, "pts", 1 * GST_SECOND, NULL);
validate_events_received (4);
validate_normal_start_events (0);
destroy_payloader (state);
}
GST_END_TEST;
/* validate that changing the mtu actually affects whether buffers are
* considered to be filled. first detect the default mtu and check that having
* buffers slightly less or equal to the size will not be considered to be
* filled, and that going over this size will be filling the buffers. then
* change the mtu slightly and validate that the boundary actually changed.
* lastly try the boundary values and make sure that they work as expected.
*/
GST_START_TEST (rtp_base_payload_property_mtu_test)
{
State *state;
guint mtu, check;
state = create_payloader ("application/x-rtp", &sinktmpl, NULL);
g_object_get (state->element, "mtu", &mtu, NULL);
validate_would_not_be_filled (state, mtu - 1, GST_CLOCK_TIME_NONE);
validate_would_not_be_filled (state, mtu, GST_CLOCK_TIME_NONE);
validate_would_be_filled (state, mtu + 1, GST_CLOCK_TIME_NONE);
g_object_set (state->element, "mtu", mtu - 1, NULL);
g_object_get (state->element, "mtu", &check, NULL);
fail_unless_equals_int (check, mtu - 1);
validate_would_not_be_filled (state, mtu - 1, GST_CLOCK_TIME_NONE);
validate_would_be_filled (state, mtu, GST_CLOCK_TIME_NONE);
validate_would_be_filled (state, mtu + 1, GST_CLOCK_TIME_NONE);
g_object_set (state->element, "mtu", 28, NULL);
g_object_get (state->element, "mtu", &check, NULL);
fail_unless_equals_int (check, 28);
validate_would_not_be_filled (state, 28, GST_CLOCK_TIME_NONE);
validate_would_be_filled (state, 29, GST_CLOCK_TIME_NONE);
g_object_set (state->element, "mtu", G_MAXUINT, NULL);
g_object_get (state->element, "mtu", &check, NULL);
fail_unless_equals_int (check, G_MAXUINT);
validate_would_not_be_filled (state, G_MAXUINT - 1, GST_CLOCK_TIME_NONE);
validate_would_not_be_filled (state, G_MAXUINT, GST_CLOCK_TIME_NONE);
destroy_payloader (state);
}
GST_END_TEST;
/* validate that changing the payload-type will actually affect the
* payload-type of the payloaded RTP packets. first get the default, then send
* a buffer with this payload-type. increment the payload-type and send another
* buffer. then test the boundary values for the payload-type and make sure
* that these are all carried over to the payloaded RTP packets.
*/
GST_START_TEST (rtp_base_payload_property_pt_test)
{
State *state;
guint payload_type, check;
state = create_payloader ("application/x-rtp", &sinktmpl, NULL);
set_state (state, GST_STATE_PLAYING);
g_object_get (state->element, "pt", &payload_type, NULL);
push_buffer (state, "pts", 0 * GST_SECOND, NULL);
g_object_set (state->element, "pt", payload_type + 1, NULL);
g_object_get (state->element, "pt", &check, NULL);
fail_unless_equals_int (check, payload_type + 1);
push_buffer (state, "pts", 1 * GST_SECOND, NULL);
g_object_set (state->element, "pt", 0, NULL);
g_object_get (state->element, "pt", &check, NULL);
fail_unless_equals_int (check, 0);
push_buffer (state, "pts", 2 * GST_SECOND, NULL);
g_object_set (state->element, "pt", 0x7f, NULL);
g_object_get (state->element, "pt", &check, NULL);
fail_unless_equals_int (check, 0x7f);
push_buffer (state, "pts", 3 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
validate_buffers_received (4);
validate_buffer (0,
"pts", 0 * GST_SECOND, "payload-type", payload_type, NULL);
validate_buffer (1,
"pts", 1 * GST_SECOND, "payload-type", payload_type + 1, NULL);
validate_buffer (2, "pts", 2 * GST_SECOND, "payload-type", 0, NULL);
validate_buffer (3, "pts", 3 * GST_SECOND, "payload-type", 0x7f, NULL);
validate_events_received (3);
validate_normal_start_events (0);
destroy_payloader (state);
}
GST_END_TEST;
/* validate that changing the ssrc will actually affect the ssrc of the
* payloaded RTP packets. first get the current ssrc which should indicate
* random ssrcs. send two buffers and expect their ssrcs to be random but
* identical. since setting the ssrc will only take effect when the pipeline
* goes READY->PAUSED, bring the pipeline to NULL state, set the ssrc to a given
* value and make sure that this is carried over to the payloaded RTP packets.
* the last step is to test the boundary values.
*/
GST_START_TEST (rtp_base_payload_property_ssrc_test)
{
State *state;
guint32 ssrc;
state = create_payloader ("application/x-rtp", &sinktmpl, NULL);
set_state (state, GST_STATE_PLAYING);
g_object_get (state->element, "ssrc", &ssrc, NULL);
fail_unless_equals_int (ssrc, -1);
push_buffer (state, "pts", 0 * GST_SECOND, NULL);
push_buffer (state, "pts", 1 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
g_object_set (state->element, "ssrc", 0x4242, NULL);
g_object_get (state->element, "ssrc", &ssrc, NULL);
fail_unless_equals_int (ssrc, 0x4242);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 2 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
g_object_set (state->element, "ssrc", 0, NULL);
g_object_get (state->element, "ssrc", &ssrc, NULL);
fail_unless_equals_int (ssrc, 0);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 3 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
g_object_set (state->element, "ssrc", G_MAXUINT32, NULL);
g_object_get (state->element, "ssrc", &ssrc, NULL);
fail_unless_equals_int (ssrc, G_MAXUINT32);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 4 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
validate_buffers_received (5);
validate_buffer (0, "pts", 0 * GST_SECOND, NULL);
get_buffer_field (0, "ssrc", &ssrc, NULL);
validate_buffer (1, "pts", 1 * GST_SECOND, "ssrc", ssrc, NULL);
validate_buffer (2, "pts", 2 * GST_SECOND, "ssrc", 0x4242, NULL);
validate_buffer (3, "pts", 3 * GST_SECOND, "ssrc", 0, NULL);
validate_buffer (4, "pts", 4 * GST_SECOND, "ssrc", G_MAXUINT32, NULL);
validate_events_received (12);
validate_normal_start_events (0);
validate_normal_start_events (3);
validate_normal_start_events (6);
validate_normal_start_events (9);
destroy_payloader (state);
}
GST_END_TEST;
/* validate that changing the timestamp-offset will actually effect the rtptime
* of the payloaded RTP packets. unfortunately setting the timestamp-offset
* property will only take effect when the payloader goes from READY to PAUSED.
* so the test starts by making sure that the default timestamp-offset indicates
* random timestamps. then a buffer is pushed which is expected to be payloaded
* as an RTP packet with a random timestamp. then the timestamp-offset is
* modified without changing the state of the pipeline. therefore the next
* buffer pushed is expected to result in an RTP packet with a timestamp equal
* to the previous RTP packet incremented by DEFAULT_CLOCK_RATE. next the
* pipeline is brought to NULL state and the timestamp-offset is set to a
* specific value, the pipeline is then brought back to PLAYING state and the
* two buffers pushed are expected to result in payloaded RTP packets that have
* timestamps based on the set timestamp-offset incremented by multiples of
* DEFAULT_CLOCK_RATE. next the boundary values of the timestamp-offset are
* tested. again the pipeline state needs to be modified and buffers are pushed
* and the resulting payloaded RTP packets' timestamps are validated. note that
* the maximum timestamp-offset value will wrap around for the very last
* payloaded RTP packet.
*/
GST_START_TEST (rtp_base_payload_property_timestamp_offset_test)
{
guint32 rtptime;
guint32 offset;
State *state;
state = create_payloader ("application/x-rtp", &sinktmpl, NULL);
set_state (state, GST_STATE_PLAYING);
g_object_get (state->element, "timestamp-offset", &offset, NULL);
fail_unless_equals_int (offset, -1);
push_buffer (state, "pts", 0 * GST_SECOND, NULL);
g_object_set (state->element, "timestamp-offset", 0x42, NULL);
g_object_get (state->element, "timestamp-offset", &offset, NULL);
fail_unless_equals_int (offset, 0x42);
push_buffer (state, "pts", 1 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
g_object_set (state->element, "timestamp-offset", 0x4242, NULL);
g_object_get (state->element, "timestamp-offset", &offset, NULL);
fail_unless_equals_int (offset, 0x4242);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 2 * GST_SECOND, NULL);
push_buffer (state, "pts", 3 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
g_object_set (state->element, "timestamp-offset", 0, NULL);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 4 * GST_SECOND, NULL);
push_buffer (state, "pts", 5 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
g_object_set (state->element, "timestamp-offset", G_MAXUINT32, NULL);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 6 * GST_SECOND, NULL);
push_buffer (state, "pts", 7 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
validate_buffers_received (8);
validate_buffer (0, "pts", 0 * GST_SECOND, NULL);
get_buffer_field (0, "rtptime", &rtptime, NULL);
validate_buffer (1,
"pts", 1 * GST_SECOND, "rtptime", rtptime + 1 * DEFAULT_CLOCK_RATE, NULL);
validate_buffer (2,
"pts", 2 * GST_SECOND, "rtptime", 0x4242 + 2 * DEFAULT_CLOCK_RATE, NULL);
validate_buffer (3,
"pts", 3 * GST_SECOND, "rtptime", 0x4242 + 3 * DEFAULT_CLOCK_RATE, NULL);
validate_buffer (4,
"pts", 4 * GST_SECOND, "rtptime", 0 + 4 * DEFAULT_CLOCK_RATE, NULL);
validate_buffer (5,
"pts", 5 * GST_SECOND, "rtptime", 0 + 5 * DEFAULT_CLOCK_RATE, NULL);
validate_buffer (6,
"pts", 6 * GST_SECOND,
"rtptime", G_MAXUINT32 + 6 * DEFAULT_CLOCK_RATE, NULL);
validate_buffer (7,
"pts", 7 * GST_SECOND, "rtptime", 7 * DEFAULT_CLOCK_RATE - 1, NULL);
validate_events_received (12);
validate_normal_start_events (0);
validate_normal_start_events (3);
validate_normal_start_events (6);
validate_normal_start_events (9);
destroy_payloader (state);
}
GST_END_TEST;
/* as for timestamp-offset above setting the seqnum-offset property of a
* payloader will only take effect when the payloader goes from READY to PAUSED
* state. this test starts by validating that seqnum-offset indicates random
* sequence numbers and that the random sequence numbers increment by one for
* each payloaded RTP packet. also it is verified that setting seqnum-offset
* without bringing the pipeline to READY will not affect the payloaded RTP
* packets' sequence numbers. next the pipeline is brought to NULL state,
* seqnum-offset is set to a specific value before bringing the pipeline back to
* PLAYING state. the next two buffers pushed are expected to resulting in
* payloaded RTP packets that start with sequence numbers relating to the set
* seqnum-offset value, and that again increment by one for each packet. finally
* the boundary values of seqnum-offset are tested. this means bringing the
* pipeline to NULL state, setting the seqnum-offset and bringing the pipeline
* back to PLAYING state. note that for the very last payloded RTP packet the
* sequence number will have wrapped around because the previous packet is
* expected to have the maximum sequence number value.
*/
GST_START_TEST (rtp_base_payload_property_seqnum_offset_test)
{
State *state;
guint16 seq;
gint offset;
state = create_payloader ("application/x-rtp", &sinktmpl, NULL);
set_state (state, GST_STATE_PLAYING);
g_object_get (state->element, "seqnum-offset", &offset, NULL);
fail_unless_equals_int (offset, -1);
push_buffer (state, "pts", 0 * GST_SECOND, NULL);
g_object_set (state->element, "seqnum-offset", 0x42, NULL);
g_object_get (state->element, "seqnum-offset", &offset, NULL);
fail_unless_equals_int (offset, 0x42);
push_buffer (state, "pts", 1 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
g_object_set (state->element, "seqnum-offset", 0x4242, NULL);
g_object_get (state->element, "seqnum-offset", &offset, NULL);
fail_unless_equals_int (offset, 0x4242);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 2 * GST_SECOND, NULL);
push_buffer (state, "pts", 3 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
g_object_set (state->element, "seqnum-offset", -1, NULL);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 4 * GST_SECOND, NULL);
push_buffer (state, "pts", 5 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
g_object_set (state->element, "seqnum-offset", G_MAXUINT16, NULL);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 6 * GST_SECOND, NULL);
push_buffer (state, "pts", 7 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
validate_buffers_received (8);
validate_buffer (0, "pts", 0 * GST_SECOND, NULL);
get_buffer_field (0, "seq", &seq, NULL);
validate_buffer (1, "pts", 1 * GST_SECOND, "seq", seq + 1, NULL);
validate_buffer (2, "pts", 2 * GST_SECOND, "seq", 0x4242, NULL);
validate_buffer (3, "pts", 3 * GST_SECOND, "seq", 0x4242 + 1, NULL);
validate_buffer (4, "pts", 4 * GST_SECOND, NULL);
get_buffer_field (4, "seq", &seq, NULL);
validate_buffer (5, "pts", 5 * GST_SECOND, "seq", seq + 1, NULL);
validate_buffer (6, "pts", 6 * GST_SECOND, "seq", G_MAXUINT16, NULL);
validate_buffer (7, "pts", 7 * GST_SECOND, "seq", 0, NULL);
validate_events_received (12);
validate_normal_start_events (0);
validate_normal_start_events (3);
validate_normal_start_events (6);
validate_normal_start_events (9);
destroy_payloader (state);
}
GST_END_TEST;
/* a payloader's max-ptime property is linked to its MTU property. whenever a
* packet is larger than MTU or has a duration longer than max-ptime it will be
* considered to be full. so this test first validates that the default value of
* max-ptime is unspecified. then it retrieves the MTU and validates that a
* packet of size MTU will not be considered full even if the duration is at its
* maximum value. however incrementing the size to exceed the MTU will result in
* the packet being full. next max-ptime is set to a value and it is verified
* that only if both the size and duration are below the allowed values then the
* packet will be considered not to be full, otherwise it will be reported as
* being full. finally the boundary values of the property are tested in a
* similar fashion.
*/
GST_START_TEST (rtp_base_payload_property_max_ptime_test)
{
gint64 max_ptime;
State *state;
guint mtu;
state = create_payloader ("application/x-rtp", &sinktmpl, NULL);
g_object_get (state->element, "max-ptime", &max_ptime, NULL);
fail_unless_equals_int64 (max_ptime, -1);
g_object_get (state->element, "mtu", &mtu, NULL);
validate_would_not_be_filled (state, mtu, G_MAXINT64 - 1);
validate_would_be_filled (state, mtu + 1, G_MAXINT64 - 1);
g_object_set (state->element, "max-ptime", GST_SECOND, NULL);
g_object_get (state->element, "max-ptime", &max_ptime, NULL);
fail_unless_equals_int64 (max_ptime, GST_SECOND);
validate_would_not_be_filled (state, mtu, GST_SECOND - 1);
validate_would_be_filled (state, mtu, GST_SECOND);
validate_would_be_filled (state, mtu + 1, GST_SECOND - 1);
validate_would_be_filled (state, mtu + 1, GST_SECOND);
g_object_set (state->element, "max-ptime", G_GUINT64_CONSTANT (-1), NULL);
g_object_get (state->element, "max-ptime", &max_ptime, NULL);
fail_unless_equals_int64 (max_ptime, G_GUINT64_CONSTANT (-1));
validate_would_not_be_filled (state, mtu, G_MAXINT64 - 1);
validate_would_be_filled (state, mtu + 1, G_MAXINT64 - 1);
g_object_set (state->element, "max-ptime", G_MAXINT64, NULL);
g_object_get (state->element, "max-ptime", &max_ptime, NULL);
fail_unless_equals_int64 (max_ptime, G_MAXINT64);
validate_would_be_filled (state, mtu, G_MAXINT64);
destroy_payloader (state);
}
GST_END_TEST;
/* a basepayloader has a min-ptime property with an allowed range, the property
* itself is never checked by the payloader but is meant to be used by
* inheriting classes. therefore this test only validates that setting the
* property will mean that retrieveing the property results in the value
* previously being set. first the default value is validated, then a new
* specific value, before finally testing the boundary values.
*/
GST_START_TEST (rtp_base_payload_property_min_ptime_test)
{
State *state;
guint64 reference, min_ptime;
state = create_payloader ("application/x-rtp", &sinktmpl, NULL);
g_object_get (state->element, "min-ptime", &reference, NULL);
fail_unless_equals_int (reference, 0);
g_object_set (state->element, "min-ptime", reference + 1, NULL);
g_object_get (state->element, "min-ptime", &min_ptime, NULL);
fail_unless_equals_int (min_ptime, reference + 1);
g_object_set (state->element, "min-ptime", G_GUINT64_CONSTANT (0), NULL);
g_object_get (state->element, "min-ptime", &min_ptime, NULL);
fail_unless_equals_int (min_ptime, 0);
g_object_set (state->element, "min-ptime", G_MAXINT64, NULL);
g_object_get (state->element, "min-ptime", &min_ptime, NULL);
fail_unless_equals_int64 (min_ptime, G_MAXINT64);
destroy_payloader (state);
}
GST_END_TEST;
/* paylaoders have a timestamp property that reflects the timestamp of the last
* payloaded RTP packet. in this test the timestamp-offset is set to a specific
* value so that when the first buffer is pushed its timestamp can be predicted
* and thus that the timestamp property also has this value. (if
* timestamp-offset was not set the timestamp would be random). another buffer
* is then pushed and its timestamp is expected to increment by
* DEFAULT_CLOCK_RATE.
*/
GST_START_TEST (rtp_base_payload_property_timestamp_test)
{
State *state;
guint32 timestamp;
state = create_payloader ("application/x-rtp", &sinktmpl,
"timestamp-offset", 0, NULL);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 0 * GST_SECOND, NULL);
g_object_get (state->element, "timestamp", &timestamp, NULL);
fail_unless_equals_int (timestamp, 0);
push_buffer (state, "pts", 1 * GST_SECOND, NULL);
g_object_get (state->element, "timestamp", &timestamp, NULL);
fail_unless_equals_int (timestamp, DEFAULT_CLOCK_RATE);
set_state (state, GST_STATE_NULL);
validate_buffers_received (2);
validate_buffer (0, "pts", 0 * GST_SECOND, "rtptime", 0, NULL);
validate_buffer (1,
"pts", 1 * GST_SECOND, "rtptime", DEFAULT_CLOCK_RATE, NULL);
validate_events_received (3);
validate_normal_start_events (0);
destroy_payloader (state);
}
GST_END_TEST;
/* basepayloaders have a seqnum property that is supposed to contain the
* sequence number of the last payloaded RTP packet. so therefore this test
* initializes the seqnum-offset property to a know value and pushes a buffer.
* the payloaded RTP packet is expected to have a sequence number equal to the
* set seqnum-offset, as is the seqnum property. next another buffer is pushed
* and then both the payloaded RTP packet and the seqnum property value are
* expected to increment by one compared to the previous packet.
*/
GST_START_TEST (rtp_base_payload_property_seqnum_test)
{
State *state;
guint seq;
state = create_payloader ("application/x-rtp", &sinktmpl,
"seqnum-offset", 0, NULL);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 0 * GST_SECOND, NULL);
g_object_get (state->element, "seqnum", &seq, NULL);
fail_unless_equals_int (seq, 0);
push_buffer (state, "pts", 1 * GST_SECOND, NULL);
g_object_get (state->element, "seqnum", &seq, NULL);
fail_unless_equals_int (seq, 1);
set_state (state, GST_STATE_NULL);
validate_buffers_received (2);
validate_buffer (0, "pts", 0 * GST_SECOND, "seq", 0, NULL);
validate_buffer (1, "pts", 1 * GST_SECOND, "seq", 1, NULL);
validate_events_received (3);
validate_normal_start_events (0);
destroy_payloader (state);
}
GST_END_TEST;
/* basepayloader has a perfect-rtptime property when it is set to FALSE
* the timestamps of payloaded RTP packets will determined by initial
* timestamp-offset (usually random) as well as the clock-rate. when
* perfect-rtptime is set to TRUE the timestamps of payloaded RTP packets are
* instead determined by the timestamp of the first packet and then the
* difference in offset of the input buffers.
*
* to verify that this test starts by setting the timestamp-offset to a specific
* value to prevent random timestamps of the RTP packets. next perfect-rtptime
* is set to FALSE. the two buffers pushed will result in two payloaded RTP
* packets whose timestamps differ based on the current clock-rate
* DEFAULT_CLOCK_RATE. the next step is to set perfect-rtptime to TRUE. the two
* buffers that are pushed will result in two payloaded RTP packets. the first
* of these RTP packets has a timestamp that relates to the previous packet and
* the difference in offset between the middle two input buffers. the latter of
* the two RTP packets has a timestamp that instead relates to the offset of the
* last two input buffers.
*/
GST_START_TEST (rtp_base_payload_property_perfect_rtptime_test)
{
State *state;
guint32 timestamp_base = 0;
gboolean perfect;
state = create_payloader ("application/x-rtp", &sinktmpl,
"timestamp-offset", timestamp_base, NULL);
set_state (state, GST_STATE_PLAYING);
g_object_set (state->element, "perfect-rtptime", FALSE, NULL);
g_object_get (state->element, "perfect-rtptime", &perfect, NULL);
fail_unless (!perfect);
push_buffer (state, "pts", 0 * GST_SECOND, "offset", G_GINT64_CONSTANT (0),
NULL);
push_buffer (state, "pts", 1 * GST_SECOND, "offset", G_GINT64_CONSTANT (17),
NULL);
g_object_set (state->element, "perfect-rtptime", TRUE, NULL);
g_object_get (state->element, "perfect-rtptime", &perfect, NULL);
fail_unless (perfect);
push_buffer (state, "pts", 2 * GST_SECOND, "offset", G_GINT64_CONSTANT (31),
NULL);
push_buffer (state, "pts", 3 * GST_SECOND, "offset", G_GINT64_CONSTANT (67),
NULL);
set_state (state, GST_STATE_NULL);
validate_buffers_received (4);
validate_buffer (0,
"pts", 0 * GST_SECOND, "offset", G_GINT64_CONSTANT (0), "rtptime",
timestamp_base, NULL);
validate_buffer (1,
"pts", 1 * GST_SECOND,
"offset", G_GINT64_CONSTANT (17), "rtptime",
timestamp_base + 1 * DEFAULT_CLOCK_RATE, NULL);
validate_buffer (2,
"pts", 2 * GST_SECOND,
"offset", G_GINT64_CONSTANT (31),
"rtptime", timestamp_base + 1 * DEFAULT_CLOCK_RATE + (31 - 17), NULL);
validate_buffer (3,
"pts", 3 * GST_SECOND,
"offset", G_GINT64_CONSTANT (67),
"rtptime", timestamp_base + 1 * DEFAULT_CLOCK_RATE + (67 - 17), NULL);
validate_events_received (3);
validate_normal_start_events (0);
destroy_payloader (state);
}
GST_END_TEST;
/* basepayloaders have a ptime-multiple property but its value does not affect
* any payloaded RTP packets as this is supposed to be done by inherited
* classes. therefore this test only validates the default value of the
* property, makes sure that a set value actually sticks and that the boundary
* values are indeed allowed to be set.
*/
GST_START_TEST (rtp_base_payload_property_ptime_multiple_test)
{
State *state;
gint64 multiple;
state = create_payloader ("application/x-rtp", &sinktmpl, NULL);
g_object_get (state->element, "ptime-multiple", &multiple, NULL);
fail_unless_equals_int64 (multiple, 0);
g_object_set (state->element, "ptime-multiple", G_GINT64_CONSTANT (42), NULL);
g_object_get (state->element, "ptime-multiple", &multiple, NULL);
fail_unless_equals_int64 (multiple, 42);
g_object_set (state->element, "ptime-multiple", G_GINT64_CONSTANT (0), NULL);
g_object_get (state->element, "ptime-multiple", &multiple, NULL);
fail_unless_equals_int64 (multiple, 0);
g_object_set (state->element, "ptime-multiple", G_MAXINT64, NULL);
g_object_get (state->element, "ptime-multiple", &multiple, NULL);
fail_unless_equals_int64 (multiple, G_MAXINT64);
destroy_payloader (state);
}
GST_END_TEST;
/* basepayloaders have a property called stats that is used to atomically
* retrieve several values (clock-rate, running-time, seqnum and timestamp) that
* relate to the stream and its current progress. this test is meant to test
* retrieval of these values.
*
* first of all perfect-rtptime is set to TRUE, next the the test starts out by
* setting seqnum-offset and timestamp-offset to known values to prevent that
* sequence numbers and timestamps of payloaded RTP packets are random. next the
* stats property is retrieved. the clock-rate must be at the default
* DEFAULT_CLOCK_RATE, while running-time must be equal to the first buffers
* PTS. the sequence number should be equal to the initialized value of
* seqnum-offset and the timestamp should be equal to the initialized value of
* timestamp-offset. after pushing a second buffer the stats property is
* validate again. this time running-time, seqnum and timestamp should have
* advanced as expected. next the pipeline is brought to NULL state to be able
* to change the perfect-rtptime property to FALSE before going back to PLAYING
* state. this is done to validate that the stats values reflect normal
* timestamp updates that are not based on input buffer offsets as expected.
* lastly two buffers are pushed and the stats property retrieved after each
* time. here it is expected that the sequence numbers values are restarted at
* the inital value while the timestamps and running-time reflect the input
* buffers.
*/
GST_START_TEST (rtp_base_payload_property_stats_test)
{
State *state;
state = create_payloader ("application/x-rtp", &sinktmpl,
"perfect-rtptime", TRUE, "seqnum-offset", 0, "timestamp-offset", 0, NULL);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 0 * GST_SECOND, NULL);
validate_stats (state,
DEFAULT_CLOCK_RATE, 0 * GST_SECOND, 0, 0 * DEFAULT_CLOCK_RATE);
push_buffer (state, "pts", 1 * GST_SECOND, NULL);
validate_stats (state,
DEFAULT_CLOCK_RATE, 1 * DEFAULT_CLOCK_RATE, 1, 1 * DEFAULT_CLOCK_RATE);
set_state (state, GST_STATE_NULL);
g_object_set (state->element, "perfect-rtptime", FALSE, NULL);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 2 * GST_SECOND, NULL);
validate_stats (state,
DEFAULT_CLOCK_RATE, 2 * GST_SECOND, 0, 2 * DEFAULT_CLOCK_RATE);
push_buffer (state, "pts", 3 * GST_SECOND, NULL);
validate_stats (state,
DEFAULT_CLOCK_RATE, 3 * GST_SECOND, 1, 3 * DEFAULT_CLOCK_RATE);
set_state (state, GST_STATE_NULL);
validate_buffers_received (4);
validate_buffer (0, "pts", 0 * GST_SECOND, NULL);
validate_buffer (1, "pts", 1 * GST_SECOND, NULL);
validate_buffer (2, "pts", 2 * GST_SECOND, NULL);
validate_buffer (3, "pts", 3 * GST_SECOND, NULL);
validate_events_received (6);
validate_normal_start_events (0);
validate_normal_start_events (3);
destroy_payloader (state);
}
GST_END_TEST;
/* basepayloader has a property source-info that makes it aware of RTP
* source information passed as GstRTPSourceMeta on the input buffers. All
* sources found in the meta will be added to the list of CSRCs in the RTP
* header. A useful scenario for this is, for instance, to signal which
* sources contributed to a mixed audio stream. */
GST_START_TEST (rtp_base_payload_property_source_info_test)
{
GstHarness *h;
GstRtpDummyPay *pay;
GstBuffer *buffer;
guint csrc_count = 2;
const guint32 csrc[] = { 0x11, 0x22 };
const guint32 ssrc = 0x33;
pay = rtp_dummy_pay_new ();
h = gst_harness_new_with_element (GST_ELEMENT_CAST (pay), "sink", "src");
gst_harness_set_src_caps_str (h, "application/x-rtp");
/* Input buffer has no meta, payloader should not add CSRC */
g_object_set (pay, "source-info", TRUE, NULL);
buffer = gst_rtp_buffer_new_allocate (0, 0, 0);
buffer = gst_harness_push_and_pull (h, buffer);
validate_buffer1 (buffer, "csrc-count", 0, NULL);
fail_if (gst_buffer_get_rtp_source_meta (buffer));
gst_buffer_unref (buffer);
/* Input buffer has meta, payloader should add CSRC */
buffer = gst_rtp_buffer_new_allocate (0, 0, 0);
fail_unless (gst_buffer_add_rtp_source_meta (buffer, &ssrc, csrc,
csrc_count));
buffer = gst_harness_push_and_pull (h, buffer);
/* The meta SSRC should be added as the last contributing source */
validate_buffer1 (buffer, "csrc-count", 3, "csrc", 0, csrc[0],
"csrc", 1, csrc[1], "csrc", 2, ssrc, NULL);
fail_if (gst_buffer_get_rtp_source_meta (buffer));
gst_buffer_unref (buffer);
/* When property is disabled, the meta should be ignored and no CSRC
* added. */
g_object_set (pay, "source-info", FALSE, NULL);
buffer = gst_rtp_buffer_new_allocate (0, 0, 0);
fail_unless (gst_buffer_add_rtp_source_meta (buffer, NULL, csrc, csrc_count));
buffer = gst_harness_push_and_pull (h, buffer);
validate_buffer1 (buffer, "csrc-count", 0, NULL);
fail_if (gst_buffer_get_rtp_source_meta (buffer));
gst_buffer_unref (buffer);
g_object_unref (pay);
gst_harness_teardown (h);
}
GST_END_TEST;
/* push a single buffer to the payloader which should successfully payload it
* into an RTP packet. besides the payloaded RTP packet there should be the
* three events initial events: stream-start, caps and segment. because of that
* the input caps has framerate this will be propagated to an a-framerate field
* on the output caps.
*/
GST_START_TEST (rtp_base_payload_framerate_attribute)
{
State *state;
state = create_payloader ("video/x-raw,framerate=(fraction)1/4", &sinktmpl,
"perfect-rtptime", FALSE, NULL);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 0 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
validate_buffers_received (1);
validate_buffer (0, "pts", 0 * GST_SECOND, NULL);
validate_events_received (3);
validate_normal_start_events (0);
validate_event (1, "caps", "a-framerate", "0.25", NULL);
destroy_payloader (state);
}
GST_END_TEST;
/* push a single buffer to the payloader which should successfully payload it
* into an RTP packet. besides the payloaded RTP packet there should be the
* three events initial events: stream-start, caps and segment. because of that
* the input caps has both framerate and max-framerate set the a-framerate field
* on the output caps will correspond to the value of the max-framerate field.
*/
GST_START_TEST (rtp_base_payload_max_framerate_attribute)
{
State *state;
state =
create_payloader
("video/x-raw,framerate=(fraction)0/1,max-framerate=(fraction)1/8",
&sinktmpl, "perfect-rtptime", FALSE, NULL);
set_state (state, GST_STATE_PLAYING);
push_buffer (state, "pts", 0 * GST_SECOND, NULL);
set_state (state, GST_STATE_NULL);
validate_buffers_received (1);
validate_buffer (0, "pts", 0 * GST_SECOND, NULL);
validate_events_received (3);
validate_normal_start_events (0);
validate_event (1, "caps", "a-framerate", "0.125", NULL);
destroy_payloader (state);
}
GST_END_TEST;
static Suite *
rtp_basepayloading_suite (void)
{
Suite *s = suite_create ("rtp_base_payloading_test");
TCase *tc_chain = tcase_create ("payloading tests");
tcase_set_timeout (tc_chain, 60);
suite_add_tcase (s, tc_chain);
tcase_add_test (tc_chain, rtp_base_payload_buffer_test);
tcase_add_test (tc_chain, rtp_base_payload_buffer_list_test);
tcase_add_test (tc_chain, rtp_base_payload_normal_rtptime_test);
tcase_add_test (tc_chain, rtp_base_payload_perfect_rtptime_test);
tcase_add_test (tc_chain, rtp_base_payload_no_pts_no_offset_test);
tcase_add_test (tc_chain, rtp_base_payload_downstream_caps_test);
tcase_add_test (tc_chain, rtp_base_payload_ssrc_collision_test);
tcase_add_test (tc_chain, rtp_base_payload_reconfigure_test);
tcase_add_test (tc_chain, rtp_base_payload_property_mtu_test);
tcase_add_test (tc_chain, rtp_base_payload_property_pt_test);
tcase_add_test (tc_chain, rtp_base_payload_property_ssrc_test);
tcase_add_test (tc_chain, rtp_base_payload_property_timestamp_offset_test);
tcase_add_test (tc_chain, rtp_base_payload_property_seqnum_offset_test);
tcase_add_test (tc_chain, rtp_base_payload_property_max_ptime_test);
tcase_add_test (tc_chain, rtp_base_payload_property_min_ptime_test);
tcase_add_test (tc_chain, rtp_base_payload_property_timestamp_test);
tcase_add_test (tc_chain, rtp_base_payload_property_seqnum_test);
tcase_add_test (tc_chain, rtp_base_payload_property_perfect_rtptime_test);
tcase_add_test (tc_chain, rtp_base_payload_property_ptime_multiple_test);
tcase_add_test (tc_chain, rtp_base_payload_property_stats_test);
tcase_add_test (tc_chain, rtp_base_payload_property_source_info_test);
tcase_add_test (tc_chain, rtp_base_payload_framerate_attribute);
tcase_add_test (tc_chain, rtp_base_payload_max_framerate_attribute);
return s;
}
GST_CHECK_MAIN (rtp_basepayloading)