#include "config.h"
#endif
- #include "gstbaseaudiodecoder.h"
-#define GST_USE_UNSTABLE_API
+ #include "gstaudiodecoder.h"
#include <gst/pbutils/descriptions.h>
#include <string.h>
};
- static void gst_base_audio_decoder_finalize (GObject * object);
- static void gst_base_audio_decoder_set_property (GObject * object,
+ static void gst_audio_decoder_finalize (GObject * object);
+ static void gst_audio_decoder_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
- static void gst_base_audio_decoder_get_property (GObject * object,
+ static void gst_audio_decoder_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec);
- static void gst_base_audio_decoder_clear_queues (GstBaseAudioDecoder * dec);
- static GstFlowReturn gst_base_audio_decoder_chain_reverse (GstBaseAudioDecoder *
+ static void gst_audio_decoder_clear_queues (GstAudioDecoder * dec);
+ static GstFlowReturn gst_audio_decoder_chain_reverse (GstAudioDecoder *
dec, GstBuffer * buf);
- static GstStateChangeReturn gst_base_audio_decoder_change_state (GstElement *
+ static GstStateChangeReturn gst_audio_decoder_change_state (GstElement *
element, GstStateChange transition);
- static gboolean gst_base_audio_decoder_sink_event (GstPad * pad,
- GstEvent * event);
- static gboolean gst_base_audio_decoder_src_event (GstPad * pad,
- GstEvent * event);
- static GstFlowReturn gst_base_audio_decoder_chain (GstPad * pad,
- GstBuffer * buf);
- static gboolean gst_base_audio_decoder_src_query (GstPad * pad,
- GstQuery * query);
- static gboolean gst_base_audio_decoder_sink_query (GstPad * pad,
- GstQuery * query);
- static const GstQueryType *gst_base_audio_decoder_get_query_types (GstPad *
- pad);
- static void gst_base_audio_decoder_reset (GstBaseAudioDecoder * dec,
- gboolean full);
-
- #define gst_base_audio_decoder_parent_class parent_class
- G_DEFINE_TYPE (GstBaseAudioDecoder, gst_base_audio_decoder, GST_TYPE_ELEMENT);
+ static gboolean gst_audio_decoder_sink_event (GstPad * pad, GstEvent * event);
+ static gboolean gst_audio_decoder_src_event (GstPad * pad, GstEvent * event);
-static gboolean gst_audio_decoder_sink_setcaps (GstPad * pad, GstCaps * caps);
-static gboolean gst_audio_decoder_src_setcaps (GstPad * pad, GstCaps * caps);
++static gboolean gst_audio_decoder_sink_setcaps (GstAudioDecoder * dec,
++ GstCaps * caps);
++gboolean gst_audio_decoder_src_setcaps (GstAudioDecoder * dec, GstCaps * caps);
+ static GstFlowReturn gst_audio_decoder_chain (GstPad * pad, GstBuffer * buf);
+ static gboolean gst_audio_decoder_src_query (GstPad * pad, GstQuery * query);
+ static gboolean gst_audio_decoder_sink_query (GstPad * pad, GstQuery * query);
+ static const GstQueryType *gst_audio_decoder_get_query_types (GstPad * pad);
+ static void gst_audio_decoder_reset (GstAudioDecoder * dec, gboolean full);
+
+
-GST_BOILERPLATE (GstAudioDecoder, gst_audio_decoder, GstElement,
- GST_TYPE_ELEMENT);
-
-static void
-gst_audio_decoder_base_init (gpointer g_class)
-{
-}
++#define gst_audio_decoder_parent_class parent_class
++G_DEFINE_TYPE (GstAudioDecoder, gst_audio_decoder, GST_TYPE_ELEMENT);
static void
- gst_base_audio_decoder_class_init (GstBaseAudioDecoderClass * klass)
+ gst_audio_decoder_class_init (GstAudioDecoderClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *element_class;
}
static void
- gst_base_audio_decoder_init (GstBaseAudioDecoder * dec)
-gst_audio_decoder_init (GstAudioDecoder * dec, GstAudioDecoderClass * klass)
++gst_audio_decoder_init (GstAudioDecoder * dec)
{
- GstBaseAudioDecoderClass *klass = GST_BASE_AUDIO_DECODER_GET_CLASS (dec);
++ GstAudioDecoderClass *klass = GST_AUDIO_DECODER_GET_CLASS (dec);
GstPadTemplate *pad_template;
- GST_DEBUG_OBJECT (dec, "gst_base_audio_decoder_init");
+ GST_DEBUG_OBJECT (dec, "gst_audio_decoder_init");
- dec->priv = GST_BASE_AUDIO_DECODER_GET_PRIVATE (dec);
+ dec->priv = GST_AUDIO_DECODER_GET_PRIVATE (dec);
/* Setup sink pad */
pad_template =
dec->sinkpad = gst_pad_new_from_template (pad_template, "sink");
gst_pad_set_event_function (dec->sinkpad,
- GST_DEBUG_FUNCPTR (gst_base_audio_decoder_sink_event));
+ GST_DEBUG_FUNCPTR (gst_audio_decoder_sink_event));
- gst_pad_set_setcaps_function (dec->sinkpad,
- GST_DEBUG_FUNCPTR (gst_audio_decoder_sink_setcaps));
gst_pad_set_chain_function (dec->sinkpad,
- GST_DEBUG_FUNCPTR (gst_base_audio_decoder_chain));
+ GST_DEBUG_FUNCPTR (gst_audio_decoder_chain));
gst_pad_set_query_function (dec->sinkpad,
- GST_DEBUG_FUNCPTR (gst_base_audio_decoder_sink_query));
+ GST_DEBUG_FUNCPTR (gst_audio_decoder_sink_query));
gst_element_add_pad (GST_ELEMENT (dec), dec->sinkpad);
GST_DEBUG_OBJECT (dec, "sinkpad created");
g_return_if_fail (pad_template != NULL);
dec->srcpad = gst_pad_new_from_template (pad_template, "src");
- gst_pad_set_setcaps_function (dec->srcpad,
- GST_DEBUG_FUNCPTR (gst_audio_decoder_src_setcaps));
gst_pad_set_event_function (dec->srcpad,
- GST_DEBUG_FUNCPTR (gst_base_audio_decoder_src_event));
+ GST_DEBUG_FUNCPTR (gst_audio_decoder_src_event));
gst_pad_set_query_function (dec->srcpad,
- GST_DEBUG_FUNCPTR (gst_base_audio_decoder_src_query));
+ GST_DEBUG_FUNCPTR (gst_audio_decoder_src_query));
gst_pad_set_query_type_function (dec->srcpad,
- GST_DEBUG_FUNCPTR (gst_base_audio_decoder_get_query_types));
+ GST_DEBUG_FUNCPTR (gst_audio_decoder_get_query_types));
gst_pad_use_fixed_caps (dec->srcpad);
gst_element_add_pad (GST_ELEMENT (dec), dec->srcpad);
GST_DEBUG_OBJECT (dec, "srcpad created");
dec->priv->samples_out = 0;
dec->priv->agg = -1;
dec->priv->error_count = 0;
- gst_base_audio_decoder_clear_queues (dec);
+ gst_audio_decoder_clear_queues (dec);
- gst_audio_info_clear (&dec->priv->ctx.info);
+ gst_audio_info_init (&dec->priv->ctx.info);
memset (&dec->priv->ctx, 0, sizeof (dec->priv->ctx));
if (dec->priv->taglist) {
/* automagically perform sanity checking of src caps;
* also extracts output data format */
-static gboolean
-gst_audio_decoder_src_setcaps (GstPad * pad, GstCaps * caps)
+gboolean
- gst_base_audio_decoder_src_setcaps (GstBaseAudioDecoder * dec, GstCaps * caps)
++gst_audio_decoder_src_setcaps (GstAudioDecoder * dec, GstCaps * caps)
{
- GstAudioDecoder *dec;
gboolean res = TRUE;
guint old_rate;
}
static gboolean
- gst_base_audio_decoder_sink_setcaps (GstBaseAudioDecoder * dec, GstCaps * caps)
-gst_audio_decoder_sink_setcaps (GstPad * pad, GstCaps * caps)
++gst_audio_decoder_sink_setcaps (GstAudioDecoder * dec, GstCaps * caps)
{
- GstBaseAudioDecoderClass *klass;
- GstAudioDecoder *dec;
+ GstAudioDecoderClass *klass;
gboolean res = TRUE;
- klass = GST_BASE_AUDIO_DECODER_GET_CLASS (dec);
- dec = GST_AUDIO_DECODER (gst_pad_get_parent (pad));
+ klass = GST_AUDIO_DECODER_GET_CLASS (dec);
GST_DEBUG_OBJECT (dec, "caps: %" GST_PTR_FORMAT, caps);
}
GstFlowReturn
- gst_base_audio_decoder_finish_frame (GstBaseAudioDecoder * dec, GstBuffer * buf,
+ gst_audio_decoder_finish_frame (GstAudioDecoder * dec, GstBuffer * buf,
gint frames)
{
- GstBaseAudioDecoderPrivate *priv;
- GstBaseAudioDecoderContext *ctx;
+ GstAudioDecoderPrivate *priv;
+ GstAudioDecoderContext *ctx;
gint samples = 0;
GstClockTime ts, next_ts;
+ gsize size;
/* subclass should know what it is producing by now */
- g_return_val_if_fail (buf == NULL || GST_PAD_CAPS (dec->srcpad) != NULL,
+ g_return_val_if_fail (buf == NULL || gst_pad_has_current_caps (dec->srcpad),
GST_FLOW_ERROR);
/* subclass should not hand us no data */
- g_return_val_if_fail (buf == NULL || GST_BUFFER_SIZE (buf) > 0,
+ g_return_val_if_fail (buf == NULL || gst_buffer_get_size (buf) > 0,
GST_FLOW_ERROR);
/* no dummy calls please */
g_return_val_if_fail (frames != 0, GST_FLOW_ERROR);
}
static GstFlowReturn
- gst_base_audio_decoder_handle_frame (GstBaseAudioDecoder * dec,
- GstBaseAudioDecoderClass * klass, GstBuffer * buffer)
+ gst_audio_decoder_handle_frame (GstAudioDecoder * dec,
+ GstAudioDecoderClass * klass, GstBuffer * buffer)
{
if (G_LIKELY (buffer)) {
+ gsize size = gst_buffer_get_size (buffer);
/* keep around for admin */
GST_LOG_OBJECT (dec, "tracking frame size %d, ts %" GST_TIME_FORMAT,
- GST_BUFFER_SIZE (buffer),
- GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)));
+ size, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)));
g_queue_push_tail (&dec->priv->frames, buffer);
dec->priv->ctx.delay = dec->priv->frames.length;
- dec->priv->bytes_in += GST_BUFFER_SIZE (buffer);
+ dec->priv->bytes_in += size;
} else {
GST_LOG_OBJECT (dec, "providing subclass with NULL frame");
}
}
/* finish current segment */
- gst_base_audio_decoder_drain (dec);
+ gst_audio_decoder_drain (dec);
+#if 0
if (update) {
/* time progressed without data, see if we can fill the gap with
* some concealment data */
GST_DEBUG_OBJECT (dec,
- "segment update: plc %d, do_plc %d, last_stop %" GST_TIME_FORMAT,
+ "segment update: plc %d, do_plc %d, position %" GST_TIME_FORMAT,
dec->priv->plc, dec->priv->ctx.do_plc,
- GST_TIME_ARGS (dec->segment.last_stop));
+ GST_TIME_ARGS (dec->segment.position));
if (dec->priv->plc && dec->priv->ctx.do_plc &&
- dec->segment.rate > 0.0 && dec->segment.last_stop < start) {
+ dec->segment.rate > 0.0 && dec->segment.position < start) {
- GstBaseAudioDecoderClass *klass;
+ GstAudioDecoderClass *klass;
GstBuffer *buf;
- klass = GST_BASE_AUDIO_DECODER_GET_CLASS (dec);
+ klass = GST_AUDIO_DECODER_GET_CLASS (dec);
/* hand subclass empty frame with duration that needs covering */
buf = gst_buffer_new ();
- GST_BUFFER_DURATION (buf) = start - dec->segment.last_stop;
+ GST_BUFFER_DURATION (buf) = start - dec->segment.position;
/* best effort, not much error handling */
- gst_base_audio_decoder_handle_frame (dec, klass, buf);
+ gst_audio_decoder_handle_frame (dec, klass, buf);
}
- } else {
+ } else
+#endif
+ {
/* prepare for next one */
- gst_base_audio_decoder_flush (dec, FALSE);
+ gst_audio_decoder_flush (dec, FALSE);
/* and that's where we time from,
* in case upstream does not come up with anything better
* (e.g. upstream BYTE) */
break;
case GST_EVENT_EOS:
- gst_base_audio_decoder_drain (dec);
+ gst_audio_decoder_drain (dec);
break;
- gst_base_audio_decoder_sink_setcaps (dec, caps);
+ case GST_EVENT_CAPS:
+ {
+ GstCaps *caps;
+
+ gst_event_parse_caps (event, &caps);
++ gst_audio_decoder_sink_setcaps (dec, caps);
+ gst_event_unref (event);
+ handled = TRUE;
+ break;
+ }
default:
break;
}
gst_query_parse_duration (query, &format, NULL);
/* try answering TIME by converting from BYTE if subclass allows */
- if (format == GST_FORMAT_TIME && gst_base_audio_decoder_do_byte (dec)) {
+ if (format == GST_FORMAT_TIME && gst_audio_decoder_do_byte (dec)) {
gint64 value;
- format = GST_FORMAT_BYTES;
- if (gst_pad_query_peer_duration (dec->sinkpad, &format, &value)) {
+ if (gst_pad_query_peer_duration (dec->sinkpad, GST_FORMAT_BYTES,
+ &value)) {
GST_LOG_OBJECT (dec, "upstream size %" G_GINT64_FORMAT, value);
- format = GST_FORMAT_TIME;
if (gst_pad_query_convert (dec->sinkpad, GST_FORMAT_BYTES, value,
- &format, &value)) {
+ GST_FORMAT_TIME, &value)) {
gst_query_set_duration (query, GST_FORMAT_TIME, value);
res = TRUE;
}
gpointer _gst_reserved[GST_PADDING_LARGE];
};
- gboolean gst_base_audio_decoder_src_setcaps (GstBaseAudioDecoder * dec,
- GstCaps * caps);
- GstFlowReturn gst_base_audio_decoder_finish_frame (GstBaseAudioDecoder * dec,
- GstBuffer * buf, gint frames);
+ GType gst_audio_decoder_get_type (void);
+
+ GstFlowReturn gst_audio_decoder_finish_frame (GstAudioDecoder * dec,
- GstBuffer * buf, gint frames);
++ GstBuffer * buf, gint frames);
/* context parameters */
- GstAudioInfo * gst_base_audio_decoder_get_audio_info (GstBaseAudioDecoder * dec);
+ GstAudioInfo * gst_audio_decoder_get_audio_info (GstAudioDecoder * dec);
+
+ void gst_audio_decoder_set_plc_aware (GstAudioDecoder * dec,
+ gboolean plc);
+
+ gint gst_audio_decoder_get_plc_aware (GstAudioDecoder * dec);
+
+ void gst_audio_decoder_set_byte_time (GstAudioDecoder * dec,
+ gboolean enabled);
- void gst_base_audio_decoder_set_plc_aware (GstBaseAudioDecoder * dec,
- gboolean plc);
- gint gst_base_audio_decoder_get_plc_aware (GstBaseAudioDecoder * dec);
+ gint gst_audio_decoder_get_byte_time (GstAudioDecoder * dec);
- void gst_base_audio_decoder_set_byte_time (GstBaseAudioDecoder * dec,
- gboolean enabled);
- gint gst_base_audio_decoder_get_byte_time (GstBaseAudioDecoder * dec);
+ gint gst_audio_decoder_get_delay (GstAudioDecoder * dec);
- gint gst_base_audio_decoder_get_delay (GstBaseAudioDecoder * dec);
+ void gst_audio_decoder_set_max_errors (GstAudioDecoder * dec,
+ gint num);
- void gst_base_audio_decoder_set_max_errors (GstBaseAudioDecoder * enc,
- gint num);
- gint gst_base_audio_decoder_get_max_errors (GstBaseAudioDecoder * dec);
+ gint gst_audio_decoder_get_max_errors (GstAudioDecoder * dec);
- void gst_base_audio_decoder_set_latency (GstBaseAudioDecoder * dec,
- GstClockTime min, GstClockTime max);
- void gst_base_audio_decoder_get_latency (GstBaseAudioDecoder * dec,
- GstClockTime * min, GstClockTime * max);
+ void gst_audio_decoder_set_latency (GstAudioDecoder * dec,
+ GstClockTime min,
+ GstClockTime max);
- void gst_base_audio_decoder_get_parse_state (GstBaseAudioDecoder * dec,
- gboolean * sync, gboolean * eos);
+ void gst_audio_decoder_get_latency (GstAudioDecoder * dec,
+ GstClockTime * min,
+ GstClockTime * max);
+
+ void gst_audio_decoder_get_parse_state (GstAudioDecoder * dec,
+ gboolean * sync,
+ gboolean * eos);
/* object properties */
# include "config.h"
#endif
- #include "gstbaseaudioencoder.h"
-#define GST_USE_UNSTABLE_API
+ #include "gstaudioencoder.h"
#include <gst/base/gstadapter.h>
#include <gst/audio/audio.h>
NULL /* interface_data */
};
- base_audio_encoder_type = g_type_register_static (GST_TYPE_ELEMENT,
- "GstBaseAudioEncoder", &base_audio_encoder_info, G_TYPE_FLAG_ABSTRACT);
+ audio_encoder_type = g_type_register_static (GST_TYPE_ELEMENT,
+ "GstAudioEncoder", &audio_encoder_info, G_TYPE_FLAG_ABSTRACT);
- g_type_add_interface_static (base_audio_encoder_type, GST_TYPE_PRESET,
+ g_type_add_interface_static (audio_encoder_type, GST_TYPE_PRESET,
&preset_interface_info);
}
- return base_audio_encoder_type;
+ return audio_encoder_type;
}
- static void gst_base_audio_encoder_finalize (GObject * object);
- static void gst_base_audio_encoder_reset (GstBaseAudioEncoder * enc,
- gboolean full);
+ static void gst_audio_encoder_finalize (GObject * object);
+ static void gst_audio_encoder_reset (GstAudioEncoder * enc, gboolean full);
- static void gst_base_audio_encoder_set_property (GObject * object,
+ static void gst_audio_encoder_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
- static void gst_base_audio_encoder_get_property (GObject * object,
+ static void gst_audio_encoder_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec);
- static gboolean gst_base_audio_encoder_sink_activate_push (GstPad * pad,
+ static gboolean gst_audio_encoder_sink_activate_push (GstPad * pad,
gboolean active);
- static gboolean gst_base_audio_encoder_sink_event (GstPad * pad,
- GstEvent * event);
- static GstFlowReturn gst_base_audio_encoder_chain (GstPad * pad,
- GstBuffer * buffer);
- static gboolean gst_base_audio_encoder_src_query (GstPad * pad,
- GstQuery * query);
- static gboolean gst_base_audio_encoder_sink_query (GstPad * pad,
- GstQuery * query);
- static const GstQueryType *gst_base_audio_encoder_get_query_types (GstPad *
- pad);
- static GstCaps *gst_base_audio_encoder_sink_getcaps (GstPad * pad,
- GstCaps * filter);
-
+ static gboolean gst_audio_encoder_sink_event (GstPad * pad, GstEvent * event);
-static gboolean gst_audio_encoder_sink_setcaps (GstPad * pad, GstCaps * caps);
++static gboolean gst_audio_encoder_sink_setcaps (GstAudioEncoder * enc,
++ GstCaps * caps);
+ static GstFlowReturn gst_audio_encoder_chain (GstPad * pad, GstBuffer * buffer);
+ static gboolean gst_audio_encoder_src_query (GstPad * pad, GstQuery * query);
+ static gboolean gst_audio_encoder_sink_query (GstPad * pad, GstQuery * query);
+ static const GstQueryType *gst_audio_encoder_get_query_types (GstPad * pad);
-static GstCaps *gst_audio_encoder_sink_getcaps (GstPad * pad);
-
++static GstCaps *gst_audio_encoder_sink_getcaps (GstPad * pad, GstCaps * filter);
static void
- gst_base_audio_encoder_class_init (GstBaseAudioEncoderClass * klass)
+ gst_audio_encoder_class_init (GstAudioEncoderClass * klass)
{
GObjectClass *gobject_class;
g_return_if_fail (pad_template != NULL);
enc->sinkpad = gst_pad_new_from_template (pad_template, "sink");
gst_pad_set_event_function (enc->sinkpad,
- GST_DEBUG_FUNCPTR (gst_base_audio_encoder_sink_event));
+ GST_DEBUG_FUNCPTR (gst_audio_encoder_sink_event));
- gst_pad_set_setcaps_function (enc->sinkpad,
- GST_DEBUG_FUNCPTR (gst_audio_encoder_sink_setcaps));
gst_pad_set_getcaps_function (enc->sinkpad,
- GST_DEBUG_FUNCPTR (gst_base_audio_encoder_sink_getcaps));
+ GST_DEBUG_FUNCPTR (gst_audio_encoder_sink_getcaps));
gst_pad_set_query_function (enc->sinkpad,
- GST_DEBUG_FUNCPTR (gst_base_audio_encoder_sink_query));
+ GST_DEBUG_FUNCPTR (gst_audio_encoder_sink_query));
gst_pad_set_chain_function (enc->sinkpad,
- GST_DEBUG_FUNCPTR (gst_base_audio_encoder_chain));
+ GST_DEBUG_FUNCPTR (gst_audio_encoder_chain));
gst_pad_set_activatepush_function (enc->sinkpad,
- GST_DEBUG_FUNCPTR (gst_base_audio_encoder_sink_activate_push));
+ GST_DEBUG_FUNCPTR (gst_audio_encoder_sink_activate_push));
gst_element_add_pad (GST_ELEMENT (enc), enc->sinkpad);
GST_DEBUG_OBJECT (enc, "sinkpad created");
}
static GstFlowReturn
- gst_base_audio_encoder_chain (GstPad * pad, GstBuffer * buffer)
+ gst_audio_encoder_chain (GstPad * pad, GstBuffer * buffer)
{
- GstBaseAudioEncoder *enc;
- GstBaseAudioEncoderPrivate *priv;
- GstBaseAudioEncoderContext *ctx;
+ GstAudioEncoder *enc;
+ GstAudioEncoderPrivate *priv;
+ GstAudioEncoderContext *ctx;
GstFlowReturn ret = GST_FLOW_OK;
gboolean discont;
+ gsize size;
- enc = GST_BASE_AUDIO_ENCODER (GST_OBJECT_PARENT (pad));
+ enc = GST_AUDIO_ENCODER (GST_OBJECT_PARENT (pad));
priv = enc->priv;
ctx = &enc->priv->ctx;
}
static gboolean
- gst_base_audio_encoder_sink_setcaps (GstBaseAudioEncoder * enc, GstCaps * caps)
-gst_audio_encoder_sink_setcaps (GstPad * pad, GstCaps * caps)
++gst_audio_encoder_sink_setcaps (GstAudioEncoder * enc, GstCaps * caps)
{
- GstBaseAudioEncoderClass *klass;
- GstBaseAudioEncoderContext *ctx;
- GstAudioEncoder *enc;
+ GstAudioEncoderClass *klass;
+ GstAudioEncoderContext *ctx;
- GstAudioInfo *state, *old_state;
+ GstAudioInfo state;
gboolean res = TRUE, changed = FALSE;
guint old_rate;
- klass = GST_BASE_AUDIO_ENCODER_GET_CLASS (enc);
- enc = GST_AUDIO_ENCODER (GST_PAD_PARENT (pad));
+ klass = GST_AUDIO_ENCODER_GET_CLASS (enc);
/* subclass must do something here ... */
g_return_val_if_fail (klass->set_format != NULL, FALSE);
}
static GstCaps *
- gst_base_audio_encoder_sink_getcaps (GstPad * pad, GstCaps * filter)
-gst_audio_encoder_sink_getcaps (GstPad * pad)
++gst_audio_encoder_sink_getcaps (GstPad * pad, GstCaps * filter)
{
- GstBaseAudioEncoder *enc;
- GstBaseAudioEncoderClass *klass;
+ GstAudioEncoder *enc;
+ GstAudioEncoderClass *klass;
GstCaps *caps;
- enc = GST_BASE_AUDIO_ENCODER (gst_pad_get_parent (pad));
- klass = GST_BASE_AUDIO_ENCODER_GET_CLASS (enc);
+ enc = GST_AUDIO_ENCODER (gst_pad_get_parent (pad));
+ klass = GST_AUDIO_ENCODER_GET_CLASS (enc);
g_assert (pad == enc->sinkpad);
if (klass->getcaps)
- caps = klass->getcaps (enc);
+ caps = klass->getcaps (enc, filter);
else
- caps = gst_base_audio_encoder_proxy_getcaps (enc, NULL);
+ caps = gst_audio_encoder_proxy_getcaps (enc, NULL);
gst_object_unref (enc);
GST_LOG_OBJECT (enc, "returning caps %" GST_PTR_FORMAT, caps);
}
static gboolean
- gst_base_audio_encoder_sink_eventfunc (GstBaseAudioEncoder * enc,
- GstEvent * event)
+ gst_audio_encoder_sink_eventfunc (GstAudioEncoder * enc, GstEvent * event)
{
- GstBaseAudioEncoderClass *klass;
+ GstAudioEncoderClass *klass;
gboolean handled = FALSE;
- klass = GST_BASE_AUDIO_ENCODER_GET_CLASS (enc);
+ klass = GST_AUDIO_ENCODER_GET_CLASS (enc);
switch (GST_EVENT_TYPE (event)) {
- case GST_EVENT_NEWSEGMENT:
+ case GST_EVENT_SEGMENT:
{
- GstFormat format;
- gdouble rate, arate;
- gint64 start, stop, time;
- gboolean update;
-
- gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format,
- &start, &stop, &time);
-
- if (format == GST_FORMAT_TIME) {
- GST_DEBUG_OBJECT (enc, "received TIME NEW_SEGMENT %" GST_TIME_FORMAT
- " -- %" GST_TIME_FORMAT ", time %" GST_TIME_FORMAT
- ", rate %g, applied_rate %g",
- GST_TIME_ARGS (start), GST_TIME_ARGS (stop), GST_TIME_ARGS (time),
- rate, arate);
+ GstSegment seg;
+
+ gst_event_copy_segment (event, &seg);
+
+ if (seg.format == GST_FORMAT_TIME) {
+ GST_DEBUG_OBJECT (enc, "received TIME SEGMENT %" GST_PTR_FORMAT, &seg);
} else {
- GST_DEBUG_OBJECT (enc, "received NEW_SEGMENT %" G_GINT64_FORMAT
- " -- %" G_GINT64_FORMAT ", time %" G_GINT64_FORMAT
- ", rate %g, applied_rate %g", start, stop, time, rate, arate);
+ GST_DEBUG_OBJECT (enc, "received SEGMENT %" GST_PTR_FORMAT, &seg);
GST_DEBUG_OBJECT (enc, "unsupported format; ignoring");
break;
}
/* finish current segment */
- gst_base_audio_encoder_drain (enc);
+ gst_audio_encoder_drain (enc);
/* reset partially for new segment */
- gst_base_audio_encoder_reset (enc, FALSE);
+ gst_audio_encoder_reset (enc, FALSE);
/* and follow along with segment */
- gst_segment_set_newsegment_full (&enc->segment, update, rate, arate,
- format, start, stop, time);
+ enc->segment = seg;
break;
}
break;
case GST_EVENT_EOS:
- gst_base_audio_encoder_drain (enc);
+ gst_audio_encoder_drain (enc);
break;
- gst_base_audio_encoder_sink_setcaps (enc, caps);
+ case GST_EVENT_CAPS:
+ {
+ GstCaps *caps;
+
+ gst_event_parse_caps (event, &caps);
++ gst_audio_encoder_sink_setcaps (enc, caps);
+ gst_event_unref (event);
+ handled = TRUE;
+ break;
+ }
+
default:
break;
}
--- /dev/null
- GstCaps * (*getcaps) (GstAudioEncoder *enc);
+ /* GStreamer
+ * Copyright (C) 2011 Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>.
+ * Copyright (C) 2011 Nokia Corporation. All rights reserved.
+ * Contact: Stefan Kost <stefan.kost@nokia.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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+ #ifndef __GST_AUDIO_ENCODER_H__
+ #define __GST_AUDIO_ENCODER_H__
+
+ #ifndef GST_USE_UNSTABLE_API
+ #warning "GstAudioEncoder is unstable API and may change in future."
+ #warning "You can define GST_USE_UNSTABLE_API to avoid this warning."
+ #endif
+
+ #include <gst/gst.h>
+ #include <gst/audio/audio.h>
+
+ G_BEGIN_DECLS
+
+ #define GST_TYPE_AUDIO_ENCODER (gst_audio_encoder_get_type())
+ #define GST_AUDIO_ENCODER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AUDIO_ENCODER,GstAudioEncoder))
+ #define GST_AUDIO_ENCODER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AUDIO_ENCODER,GstAudioEncoderClass))
+ #define GST_AUDIO_ENCODER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_AUDIO_ENCODER,GstAudioEncoderClass))
+ #define GST_IS_AUDIO_ENCODER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AUDIO_ENCODER))
+ #define GST_IS_AUDIO_ENCODER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AUDIO_ENCODER))
+ #define GST_AUDIO_ENCODER_CAST(obj) ((GstAudioEncoder *)(obj))
+
+ /**
+ * GST_AUDIO_ENCODER_SINK_NAME:
+ *
+ * the name of the templates for the sink pad
+ *
+ * Since: 0.10.36
+ */
+ #define GST_AUDIO_ENCODER_SINK_NAME "sink"
+ /**
+ * GST_AUDIO_ENCODER_SRC_NAME:
+ *
+ * the name of the templates for the source pad
+ *
+ * Since: 0.10.36
+ */
+ #define GST_AUDIO_ENCODER_SRC_NAME "src"
+
+ /**
+ * GST_AUDIO_ENCODER_SRC_PAD:
+ * @obj: base parse instance
+ *
+ * Gives the pointer to the source #GstPad object of the element.
+ *
+ * Since: 0.10.36
+ */
+ #define GST_AUDIO_ENCODER_SRC_PAD(obj) (GST_AUDIO_ENCODER_CAST (obj)->srcpad)
+
+ /**
+ * GST_AUDIO_ENCODER_SINK_PAD:
+ * @obj: base parse instance
+ *
+ * Gives the pointer to the sink #GstPad object of the element.
+ *
+ * Since: 0.10.36
+ */
+ #define GST_AUDIO_ENCODER_SINK_PAD(obj) (GST_AUDIO_ENCODER_CAST (obj)->sinkpad)
+
+ /**
+ * GST_AUDIO_ENCODER_SEGMENT:
+ * @obj: base parse instance
+ *
+ * Gives the segment of the element.
+ *
+ * Since: 0.10.36
+ */
+ #define GST_AUDIO_ENCODER_SEGMENT(obj) (GST_AUDIO_ENCODER_CAST (obj)->segment)
+
+
+ typedef struct _GstAudioEncoder GstAudioEncoder;
+ typedef struct _GstAudioEncoderClass GstAudioEncoderClass;
+
+ typedef struct _GstAudioEncoderPrivate GstAudioEncoderPrivate;
+
+ /**
+ * GstAudioEncoder:
+ *
+ * The opaque #GstAudioEncoder data structure.
+ *
+ * Since: 0.10.36
+ */
+ struct _GstAudioEncoder {
+ GstElement element;
+
+ /*< protected >*/
+ /* source and sink pads */
+ GstPad *sinkpad;
+ GstPad *srcpad;
+
+ /* MT-protected (with STREAM_LOCK) */
+ GstSegment segment;
+
+ /*< private >*/
+ GstAudioEncoderPrivate *priv;
+ gpointer _gst_reserved[GST_PADDING_LARGE];
+ };
+
+ /**
+ * GstAudioEncoderClass:
+ * @element_class: The parent class structure
+ * @start: Optional.
+ * Called when the element starts processing.
+ * Allows opening external resources.
+ * @stop: Optional.
+ * Called when the element stops processing.
+ * Allows closing external resources.
+ * @set_format: Notifies subclass of incoming data format.
+ * GstAudioInfo contains the format according to provided caps.
+ * @handle_frame: Provides input samples (or NULL to clear any remaining data)
+ * according to directions as configured by the subclass
+ * using the API. Input data ref management is performed
+ * by base class, subclass should not care or intervene.
+ * @flush: Optional.
+ * Instructs subclass to clear any codec caches and discard
+ * any pending samples and not yet returned encoded data.
+ * @event: Optional.
+ * Event handler on the sink pad. This function should return
+ * TRUE if the event was handled and should be discarded
+ * (i.e. not unref'ed).
+ * @pre_push: Optional.
+ * Called just prior to pushing (encoded data) buffer downstream.
+ * Subclass has full discretionary access to buffer,
+ * and a not OK flow return will abort downstream pushing.
+ * @getcaps: Optional.
+ * Allows for a custom sink getcaps implementation (e.g.
+ * for multichannel input specification). If not implemented,
+ * default returns gst_audio_encoder_proxy_getcaps
+ * applied to sink template caps.
+ *
+ * Subclasses can override any of the available virtual methods or not, as
+ * needed. At minimum @set_format and @handle_frame needs to be overridden.
+ *
+ * Since: 0.10.36
+ */
+ struct _GstAudioEncoderClass {
+ GstElementClass element_class;
+
+ /*< public >*/
+ /* virtual methods for subclasses */
+
+ gboolean (*start) (GstAudioEncoder *enc);
+
+ gboolean (*stop) (GstAudioEncoder *enc);
+
+ gboolean (*set_format) (GstAudioEncoder *enc,
+ GstAudioInfo *info);
+
+ GstFlowReturn (*handle_frame) (GstAudioEncoder *enc,
+ GstBuffer *buffer);
+
+ void (*flush) (GstAudioEncoder *enc);
+
+ GstFlowReturn (*pre_push) (GstAudioEncoder *enc,
+ GstBuffer **buffer);
+
+ gboolean (*event) (GstAudioEncoder *enc,
+ GstEvent *event);
+
++ GstCaps * (*getcaps) (GstAudioEncoder *enc, GstCaps *filter);
+
+ /*< private >*/
+ gpointer _gst_reserved[GST_PADDING_LARGE];
+ };
+
+ GType gst_audio_encoder_get_type (void);
+
+ GstFlowReturn gst_audio_encoder_finish_frame (GstAudioEncoder * enc,
+ GstBuffer * buffer,
+ gint samples);
+
+ GstCaps * gst_audio_encoder_proxy_getcaps (GstAudioEncoder * enc,
+ GstCaps * caps);
+
+
+ /* context parameters */
+ GstAudioInfo * gst_audio_encoder_get_audio_info (GstAudioEncoder * enc);
+
+ gint gst_audio_encoder_get_frame_samples (GstAudioEncoder * enc);
+
+ void gst_audio_encoder_set_frame_samples (GstAudioEncoder * enc, gint num);
+
+ gint gst_audio_encoder_get_frame_max (GstAudioEncoder * enc);
+
+ void gst_audio_encoder_set_frame_max (GstAudioEncoder * enc, gint num);
+
+ gint gst_audio_encoder_get_lookahead (GstAudioEncoder * enc);
+
+ void gst_audio_encoder_set_lookahead (GstAudioEncoder * enc, gint num);
+
+ void gst_audio_encoder_get_latency (GstAudioEncoder * enc,
+ GstClockTime * min,
+ GstClockTime * max);
+
+ void gst_audio_encoder_set_latency (GstAudioEncoder * enc,
+ GstClockTime min,
+ GstClockTime max);
+
+ /* object properties */
+
+ void gst_audio_encoder_set_mark_granule (GstAudioEncoder * enc,
+ gboolean enabled);
+
+ gboolean gst_audio_encoder_get_mark_granule (GstAudioEncoder * enc);
+
+ void gst_audio_encoder_set_perfect_timestamp (GstAudioEncoder * enc,
+ gboolean enabled);
+
+ gboolean gst_audio_encoder_get_perfect_timestamp (GstAudioEncoder * enc);
+
+ void gst_audio_encoder_set_hard_resync (GstAudioEncoder * enc,
+ gboolean enabled);
+
+ gboolean gst_audio_encoder_get_hard_resync (GstAudioEncoder * enc);
+
+ void gst_audio_encoder_set_tolerance (GstAudioEncoder * enc,
+ gint64 tolerance);
+
+ gint64 gst_audio_encoder_get_tolerance (GstAudioEncoder * enc);
+
+ G_END_DECLS
+
+ #endif /* __GST_AUDIO_ENCODER_H__ */
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
++ * Copyright (C) <2003> David Schleef <ds@schleef.org>
++ *
++ * 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., 59 Temple Place - Suite 330,
++ * Boston, MA 02111-1307, USA.
++ */
++
++/*
++ * This file was (probably) generated from
++ * $Id$
++ * and
++ * $Id$
++ */
++
++#ifdef HAVE_CONFIG_H
++#include "config.h"
++#endif
++
++#include <gst/gst.h>
++#include <gst/audio/audio.h>
++#include <gst/audio/gstaudiofilter.h>
++#include <string.h>
++
++GST_DEBUG_CATEGORY_STATIC (audio_filter_template_debug);
++#define GST_CAT_DEFAULT audio_filter_template_debug
++
++static const GstElementDetails audio_filter_template_details =
++GST_ELEMENT_DETAILS ("Audio filter template",
++ "Filter/Effect/Audio",
++ "Filters audio",
++ "David Schleef <ds@schleef.org>");
++
++typedef struct _GstAudioFilterTemplate GstAudioFilterTemplate;
++typedef struct _GstAudioFilterTemplateClass GstAudioFilterTemplateClass;
++
++#define GST_TYPE_AUDIO_FILTER_TEMPLATE \
++ (gst_audio_filter_template_get_type())
++#define GST_AUDIO_FILTER_TEMPLATE(obj) \
++ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AUDIO_FILTER_TEMPLATE,GstAudioFilterTemplate))
++#define GST_AUDIO_FILTER_TEMPLATE_CLASS(klass) \
++ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AUDIO_FILTER_TEMPLATE,GstAudioFilterTemplateClass))
++#define GST_IS_AUDIO_FILTER_TEMPLATE(obj) \
++ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AUDIO_FILTER_TEMPLATE))
++#define GST_IS_AUDIO_FILTER_TEMPLATE_CLASS(klass) \
++ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AUDIO_FILTER_TEMPLATE))
++
++struct _GstAudioFilterTemplate
++{
++ GstAudioFilter audiofilter;
++};
++
++struct _GstAudioFilterTemplateClass
++{
++ GstAudioFilterClass parent_class;
++};
++
++
++enum
++{
++ /* FILL ME */
++ LAST_SIGNAL
++};
++
++enum
++{
++ ARG_0
++ /* FILL ME */
++};
++
++GST_BOILERPLATE (GstAudioFilterTemplate, gst_audio_filter_template,
++ GstAudioFilter, GST_TYPE_AUDIO_FILTER);
++
++static void gst_audio_filter_template_set_property (GObject * object,
++ guint prop_id, const GValue * value, GParamSpec * pspec);
++static void gst_audio_filter_template_get_property (GObject * object,
++ guint prop_id, GValue * value, GParamSpec * pspec);
++
++static gboolean gst_audio_filter_template_setup (GstAudioFilter * filter,
++ GstRingBufferSpec * spec);
++static GstFlowReturn gst_audio_filter_template_filter (GstBaseTransform * bt,
++ GstBuffer * outbuf, GstBuffer * inbuf);
++static GstFlowReturn
++gst_audio_filter_template_filter_inplace (GstBaseTransform * base_transform,
++ GstBuffer * buf);
++
++#define ALLOWED_CAPS_STRING \
++ GST_AUDIO_INT_STANDARD_PAD_TEMPLATE_CAPS
++
++static void
++gst_audio_filter_template_base_init (gpointer g_class)
++{
++ GstAudioFilterTemplateClass *klass = (GstAudioFilterTemplateClass *) g_class;
++ GstAudioFilterClass *audiofilter_class = GST_AUDIO_FILTER_CLASS (g_class);
++ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
++ GstCaps *caps;
++
++ gst_element_class_set_details (element_class, &audio_filter_template_details);
++
++ caps = gst_caps_from_string (ALLOWED_CAPS_STRING);
++ gst_audio_filter_class_add_pad_templates (audiofilter_class, caps);
++ gst_caps_unref (caps);
++}
++
++static void
++gst_audio_filter_template_class_init (GstAudioFilterTemplateClass * klass)
++{
++ GObjectClass *gobject_class;
++ GstBaseTransformClass *btrans_class;
++ GstAudioFilterClass *audio_filter_class;
++
++ gobject_class = (GObjectClass *) klass;
++ btrans_class = (GstBaseTransformClass *) klass;
++ audio_filter_class = (GstAudioFilterClass *) klass;
++
++#if 0
++ g_object_class_install_property (gobject_class, ARG_METHOD,
++ g_param_spec_enum ("method", "method", "method",
++ GST_TYPE_AUDIOTEMPLATE_METHOD, GST_AUDIOTEMPLATE_METHOD_1,
++ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
++#endif
++
++ gobject_class->set_property = gst_audio_filter_template_set_property;
++ gobject_class->get_property = gst_audio_filter_template_get_property;
++
++ /* this function will be called whenever the format changes */
++ audio_filter_class->setup = gst_audio_filter_template_setup;
++
++ /* here you set up functions to process data (either in place, or from
++ * one input buffer to another output buffer); only one is required */
++ btrans_class->transform = gst_audio_filter_template_filter;
++ btrans_class->transform_ip = gst_audio_filter_template_filter_inplace;
++}
++
++static void
++gst_audio_filter_template_init (GstAudioFilterTemplate * audio_filter_template,
++ GstAudioFilterTemplateClass * g_class)
++{
++ GST_DEBUG ("init");
++
++ /* do stuff if you need to */
++}
++
++static void
++gst_audio_filter_template_set_property (GObject * object, guint prop_id,
++ const GValue * value, GParamSpec * pspec)
++{
++ GstAudioFilterTemplate *filter;
++
++ filter = GST_AUDIO_FILTER_TEMPLATE (object);
++
++ GST_DEBUG ("set property %u", prop_id);
++
++ GST_OBJECT_LOCK (filter);
++ switch (prop_id) {
++ default:
++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++ break;
++ }
++ GST_OBJECT_UNLOCK (filter);
++}
++
++static void
++gst_audio_filter_template_get_property (GObject * object, guint prop_id,
++ GValue * value, GParamSpec * pspec)
++{
++ GstAudioFilterTemplate *filter;
++
++ filter = GST_AUDIO_FILTER_TEMPLATE (object);
++
++ GST_DEBUG ("get property %u", prop_id);
++
++ GST_OBJECT_LOCK (filter);
++ switch (prop_id) {
++ default:
++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++ break;
++ }
++ GST_OBJECT_UNLOCK (filter);
++}
++
++static gboolean
++gst_audio_filter_template_setup (GstAudioFilter * filter,
++ GstRingBufferSpec * spec)
++{
++ GstAudioFilterTemplate *audio_filter_template;
++
++ audio_filter_template = GST_AUDIO_FILTER_TEMPLATE (filter);
++
++ /* if any setup needs to be done, do it here */
++
++ return TRUE; /* it's all good */
++}
++
++/* You may choose to implement either a copying filter or an
++ * in-place filter (or both). Implementing only one will give
++ * full functionality, however, implementing both will cause
++ * audiofilter to use the optimal function in every situation,
++ * with a minimum of memory copies. */
++
++static GstFlowReturn
++gst_audio_filter_template_filter (GstBaseTransform * base_transform,
++ GstBuffer * inbuf, GstBuffer * outbuf)
++{
++ GstAudioFilterTemplate *audio_filter_template;
++ GstAudioFilter *audiofilter;
++
++ audiofilter = GST_AUDIO_FILTER (base_transform);
++ audio_filter_template = GST_AUDIO_FILTER_TEMPLATE (base_transform);
++
++ /* do something interesting here. This simply copies the source
++ * to the destination. */
++
++ memcpy (GST_BUFFER_DATA (outbuf), GST_BUFFER_DATA (inbuf),
++ GST_BUFFER_SIZE (inbuf));
++
++ return GST_FLOW_OK;
++}
++
++static GstFlowReturn
++gst_audio_filter_template_filter_inplace (GstBaseTransform * base_transform,
++ GstBuffer * buf)
++{
++ GstAudioFilterTemplate *audio_filter_template;
++ GstAudioFilter *audiofilter;
++
++ audiofilter = GST_AUDIO_FILTER (base_transform);
++ audio_filter_template = GST_AUDIO_FILTER_TEMPLATE (base_transform);
++
++ /* do something interesting here. This simply copies the source
++ * to the destination. */
++
++ return GST_FLOW_OK;
++}
++
++static gboolean
++plugin_init (GstPlugin * plugin)
++{
++ GST_DEBUG_CATEGORY_INIT (audio_filter_template_debug, "audiofilterexample",
++ 0, "audiofilterexample");
++
++ return gst_element_register (plugin, "audiofilterexample", GST_RANK_NONE,
++ GST_TYPE_AUDIO_FILTER_TEMPLATE);
++}
++
++GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
++ GST_VERSION_MINOR,
++ "gstaudio_filter_template",
++ "Audio filter template",
++ plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);
--- /dev/null
--- /dev/null
++/* GStreamer RTSP extension
++ * Copyright (C) 2007 Wim Taymans <wim.taymans@gmail.com>
++ *
++ * gstrtspextension.c: RTSP extension mechanism
++ *
++ * 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., 59 Temple Place - Suite 330,
++ * Boston, MA 02111-1307, USA.
++ */
++
++/**
++ * SECTION:gstrtspextension
++ * @short_description: Interface for extending RTSP protocols
++ *
++ * <refsect2>
++ * <para>
++ * This interface is implemented e.g. by the Windows Media Streaming RTSP
++ * exentension (rtspwms) and the RealMedia RTSP extension (rtspreal).
++ * </para>
++ * </refsect2>
++ *
++ * Last reviewed on 2007-07-25 (0.10.14)
++ */
++
++#ifdef HAVE_CONFIG_H
++#include "config.h"
++#endif
++
++#include "gstrtsp-marshal.h"
++#include "gstrtsp-enumtypes.h"
++#include "gstrtspextension.h"
++
++static void gst_rtsp_extension_iface_init (GstRTSPExtension * iface);
++
++enum
++{
++ SIGNAL_SEND,
++ LAST_SIGNAL
++};
++
++static guint gst_rtsp_extension_signals[LAST_SIGNAL] = { 0 };
++
++GType
++gst_rtsp_extension_get_type (void)
++{
++ static GType gst_rtsp_extension_type = 0;
++
++ if (!gst_rtsp_extension_type) {
++ static const GTypeInfo gst_rtsp_extension_info = {
++ sizeof (GstRTSPExtensionInterface),
++ (GBaseInitFunc) gst_rtsp_extension_iface_init,
++ NULL,
++ NULL,
++ NULL,
++ NULL,
++ 0,
++ 0,
++ NULL,
++ };
++
++ gst_rtsp_extension_type = g_type_register_static (G_TYPE_INTERFACE,
++ "GstRTSPExtension", &gst_rtsp_extension_info, 0);
++ }
++ return gst_rtsp_extension_type;
++}
++
++static void
++gst_rtsp_extension_iface_init (GstRTSPExtension * iface)
++{
++ static gboolean initialized = FALSE;
++
++ if (!initialized) {
++ gst_rtsp_extension_signals[SIGNAL_SEND] =
++ g_signal_new ("send", G_TYPE_FROM_CLASS (iface),
++ G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstRTSPExtensionInterface,
++ send), NULL, NULL, gst_rtsp_marshal_ENUM__POINTER_POINTER,
++ GST_TYPE_RTSP_RESULT, 2, G_TYPE_POINTER, G_TYPE_POINTER);
++ initialized = TRUE;
++ }
++}
++
++gboolean
++gst_rtsp_extension_detect_server (GstRTSPExtension * ext, GstRTSPMessage * resp)
++{
++ GstRTSPExtensionInterface *iface;
++ gboolean res = TRUE;
++
++ iface = GST_RTSP_EXTENSION_GET_IFACE (ext);
++ if (iface->detect_server)
++ res = iface->detect_server (ext, resp);
++
++ return res;
++}
++
++GstRTSPResult
++gst_rtsp_extension_before_send (GstRTSPExtension * ext, GstRTSPMessage * req)
++{
++ GstRTSPExtensionInterface *iface;
++ GstRTSPResult res = GST_RTSP_OK;
++
++ iface = GST_RTSP_EXTENSION_GET_IFACE (ext);
++ if (iface->before_send)
++ res = iface->before_send (ext, req);
++
++ return res;
++}
++
++GstRTSPResult
++gst_rtsp_extension_after_send (GstRTSPExtension * ext, GstRTSPMessage * req,
++ GstRTSPMessage * resp)
++{
++ GstRTSPExtensionInterface *iface;
++ GstRTSPResult res = GST_RTSP_OK;
++
++ iface = GST_RTSP_EXTENSION_GET_IFACE (ext);
++ if (iface->after_send)
++ res = iface->after_send (ext, req, resp);
++
++ return res;
++}
++
++GstRTSPResult
++gst_rtsp_extension_parse_sdp (GstRTSPExtension * ext, GstSDPMessage * sdp,
++ GstStructure * s)
++{
++ GstRTSPExtensionInterface *iface;
++ GstRTSPResult res = GST_RTSP_OK;
++
++ iface = GST_RTSP_EXTENSION_GET_IFACE (ext);
++ if (iface->parse_sdp)
++ res = iface->parse_sdp (ext, sdp, s);
++
++ return res;
++}
++
++GstRTSPResult
++gst_rtsp_extension_setup_media (GstRTSPExtension * ext, GstSDPMedia * media)
++{
++ GstRTSPExtensionInterface *iface;
++ GstRTSPResult res = GST_RTSP_OK;
++
++ iface = GST_RTSP_EXTENSION_GET_IFACE (ext);
++ if (iface->setup_media)
++ res = iface->setup_media (ext, media);
++
++ return res;
++}
++
++gboolean
++gst_rtsp_extension_configure_stream (GstRTSPExtension * ext, GstCaps * caps)
++{
++ GstRTSPExtensionInterface *iface;
++ gboolean res = TRUE;
++
++ iface = GST_RTSP_EXTENSION_GET_IFACE (ext);
++ if (iface->configure_stream)
++ res = iface->configure_stream (ext, caps);
++
++ return res;
++}
++
++GstRTSPResult
++gst_rtsp_extension_get_transports (GstRTSPExtension * ext,
++ GstRTSPLowerTrans protocols, gchar ** transport)
++{
++ GstRTSPExtensionInterface *iface;
++ GstRTSPResult res = GST_RTSP_OK;
++
++ iface = GST_RTSP_EXTENSION_GET_IFACE (ext);
++ if (iface->get_transports)
++ res = iface->get_transports (ext, protocols, transport);
++
++ return res;
++}
++
++GstRTSPResult
++gst_rtsp_extension_stream_select (GstRTSPExtension * ext, GstRTSPUrl * url)
++{
++ GstRTSPExtensionInterface *iface;
++ GstRTSPResult res = GST_RTSP_OK;
++
++ iface = GST_RTSP_EXTENSION_GET_IFACE (ext);
++ if (iface->stream_select)
++ res = iface->stream_select (ext, url);
++
++ return res;
++}
++
++GstRTSPResult
++gst_rtsp_extension_receive_request (GstRTSPExtension * ext,
++ GstRTSPMessage * msg)
++{
++ GstRTSPExtensionInterface *iface;
++ GstRTSPResult res = GST_RTSP_ENOTIMPL;
++
++ iface = GST_RTSP_EXTENSION_GET_IFACE (ext);
++ if (iface->receive_request)
++ res = iface->receive_request (ext, msg);
++
++ return res;
++}
++
++GstRTSPResult
++gst_rtsp_extension_send (GstRTSPExtension * ext, GstRTSPMessage * req,
++ GstRTSPMessage * resp)
++{
++ GstRTSPResult res = GST_RTSP_OK;
++
++ g_signal_emit (ext, gst_rtsp_extension_signals[SIGNAL_SEND], 0,
++ req, resp, &res);
++
++ return res;
++}
--- /dev/null
--- /dev/null
++/* GStreamer Audio Process
++ * Copyright (C) 2010 Wim Taymans <wim.taymans@gmail.com>
++ *
++ * gstaudioprocess.h: Audio processing extension
++ *
++ * 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., 59 Temple Place - Suite 330,
++ * Boston, MA 02111-1307, USA.
++ */
++
++#ifndef __GST_AUDIO_PROCESS_H__
++#define __GST_AUDIO_PROCESS_H__
++
++#include <gst/gst.h>
++
++G_BEGIN_DECLS
++
++#define GST_TYPE_AUDIO_PROCESS \
++ (gst_audio_process_get_type ())
++#define GST_AUDIO_PROCESS(obj) \
++ (GST_IMPLEMENTS_INTERFACE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_AUDIO_PROCESS, GstAudioProcess))
++#define GST_IS_AUDIO_PROCESS(obj) \
++ (GST_IMPLEMENTS_INTERFACE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_AUDIO_PROCESS))
++#define GST_AUDIO_PROCESS_GET_IFACE(inst) \
++ (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GST_TYPE_AUDIO_PROCESS, GstAudioProcessInterface))
++
++typedef struct _GstAudioProcess GstAudioProcess;
++typedef struct _GstAudioProcessInterface GstAudioProcessInterface;
++
++struct _GstAudioProcessInterface {
++ GTypeInterface parent;
++
++ /* vfunctions */
++ gint (*activate) (GstAudioProcess *process, gboolean active);
++ gint (*process) (GstAudioProcess *process, gpointer src_in, gpointer sink_in,
++ gpointer src_out, guint length);
++
++ /*< private >*/
++ gpointer _gst_reserved[GST_PADDING];
++};
++
++GType gst_audio_process_get_type (void);
++
++/* invoke vfunction on interface */
++gint gst_audio_process_process (GstAudioProcess *ext, gpointer src_in, gpointer sink_in,
++ gpointer src_out, guint length);
++
++G_END_DECLS
++
++#endif /* __GST_AUDIO_PROCESS_H__ */
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
++ * 2005 Wim Taymans <wim@fluendo.com>
++ *
++ * gstaudioringbuffer.c: simple audio ringbuffer base class
++ *
++ * 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., 59 Temple Place - Suite 330,
++ * Boston, MA 02111-1307, USA.
++ */
++
++
++#include <string.h>
++
++#include "gstaudioringbuffer.h"
++
++GST_DEBUG_CATEGORY_STATIC (gst_audio_ring_buffer_debug);
++#define GST_CAT_DEFAULT gst_audio_ring_buffer_debug
++
++static void gst_audio_ring_buffer_class_init (GstAudioRingBufferClass * klass);
++static void gst_audio_ring_buffer_init (GstAudioRingBuffer * ringbuffer,
++ GstAudioRingBufferClass * klass);
++static void gst_audio_ring_buffer_dispose (GObject * object);
++static void gst_audio_ring_buffer_finalize (GObject * object);
++
++static GstRingBufferClass *ring_parent_class = NULL;
++
++static gboolean gst_audio_ring_buffer_start (GstRingBuffer * buf);
++static gboolean gst_audio_ring_buffer_pause (GstRingBuffer * buf);
++static gboolean gst_audio_ring_buffer_stop (GstRingBuffer * buf);
++static gboolean gst_audio_ring_buffer_activate (GstRingBuffer * buf,
++ gboolean active);
++
++/* ringbuffer abstract base class */
++GType
++gst_audio_ring_buffer_get_type (void)
++{
++ static GType ringbuffer_type = 0;
++
++ if (!ringbuffer_type) {
++ static const GTypeInfo ringbuffer_info = {
++ sizeof (GstAudioRingBufferClass),
++ NULL,
++ NULL,
++ (GClassInitFunc) gst_audio_ring_buffer_class_init,
++ NULL,
++ NULL,
++ sizeof (GstAudioRingBuffer),
++ 0,
++ (GInstanceInitFunc) gst_audio_ring_buffer_init,
++ NULL
++ };
++
++ ringbuffer_type =
++ g_type_register_static (GST_TYPE_RING_BUFFER, "GstAudioSinkRingBuffer",
++ &ringbuffer_info, G_TYPE_FLAG_ABSTRACT);
++
++ GST_DEBUG_CATEGORY_INIT (gst_audio_ring_buffer_debug, "audioringbuffer", 0,
++ "audio ringbuffer");
++ }
++ return ringbuffer_type;
++}
++
++static void
++gst_audio_ring_buffer_class_init (GstAudioRingBufferClass * klass)
++{
++ GObjectClass *gobject_class;
++ GstRingBufferClass *gstringbuffer_class;
++
++ gobject_class = (GObjectClass *) klass;
++ gstringbuffer_class = (GstRingBufferClass *) klass;
++
++ ring_parent_class = g_type_class_peek_parent (klass);
++
++ gobject_class->dispose = gst_audio_ring_buffer_dispose;
++ gobject_class->finalize = gst_audio_ring_buffer_finalize;
++
++ gstringbuffer_class->start = GST_DEBUG_FUNCPTR (gst_audio_ring_buffer_start);
++ gstringbuffer_class->pause = GST_DEBUG_FUNCPTR (gst_audio_ring_buffer_pause);
++ gstringbuffer_class->resume = GST_DEBUG_FUNCPTR (gst_audio_ring_buffer_start);
++ gstringbuffer_class->stop = GST_DEBUG_FUNCPTR (gst_audio_ring_buffer_stop);
++
++ gstringbuffer_class->activate =
++ GST_DEBUG_FUNCPTR (gst_audio_ring_buffer_activate);
++}
++
++static void
++gst_audio_ring_buffer_init (GstAudioRingBuffer * ringbuffer,
++ GstAudioRingBufferClass * g_class)
++{
++}
++
++static void
++gst_audio_ring_buffer_dispose (GObject * object)
++{
++ G_OBJECT_CLASS (ring_parent_class)->dispose (object);
++}
++
++static void
++gst_audio_ring_buffer_finalize (GObject * object)
++{
++ G_OBJECT_CLASS (ring_parent_class)->finalize (object);
++}
++
++static gboolean
++gst_audio_ring_buffer_activate (GstRingBuffer * buf, gboolean active)
++{
++ GstAudioRingBuffer *abuf;
++ gboolean res;
++
++ abuf = GST_AUDIO_RING_BUFFER_CAST (buf);
++
++ GST_OBJECT_UNLOCK (buf);
++ res = gst_ring_buffer_thread_activate (abuf->thread, active);
++ GST_OBJECT_LOCK (buf);
++
++ return res;
++}
++
++gboolean
++gst_audio_ring_buffer_set_thread (GstAudioRingBuffer * buf,
++ GstRingBufferThread * thread)
++{
++ GstRingBufferThread *old;
++
++ g_return_val_if_fail (GST_IS_AUDIO_RING_BUFFER (buf), FALSE);
++
++ old = buf->thread;
++ if (thread)
++ gst_object_ref (thread);
++ buf->thread = thread;
++ if (old)
++ gst_object_unref (old);
++
++ if (thread)
++ gst_ring_buffer_thread_set_ringbuffer (thread, buf);
++
++ return TRUE;
++}
++
++gboolean
++gst_audio_ring_buffer_link (GstAudioRingBuffer * buf1,
++ GstAudioRingBuffer * buf2)
++{
++ buf1->link = buf2;
++ buf2->link = buf1;
++
++ return TRUE;
++}
++
++static gboolean
++gst_audio_ring_buffer_start (GstRingBuffer * buf)
++{
++ GstAudioRingBuffer *abuf;
++
++ abuf = GST_AUDIO_RING_BUFFER_CAST (buf);
++
++ GST_DEBUG_OBJECT (buf, "start, sending signal");
++
++ return gst_ring_buffer_thread_start (abuf->thread);
++}
++
++static gboolean
++gst_audio_ring_buffer_pause (GstRingBuffer * buf)
++{
++ GstAudioRingBuffer *abuf;
++ GstAudioRingBufferClass *cbuf;
++
++ abuf = GST_AUDIO_RING_BUFFER_CAST (buf);
++ cbuf = GST_AUDIO_RING_BUFFER_GET_CLASS (abuf);
++
++ /* unblock any pending writes to the audio device */
++ if (cbuf->reset) {
++ GST_DEBUG_OBJECT (abuf, "reset...");
++ cbuf->reset (abuf);
++ GST_DEBUG_OBJECT (abuf, "reset done");
++ }
++ return TRUE;
++}
++
++static gboolean
++gst_audio_ring_buffer_stop (GstRingBuffer * buf)
++{
++ GstAudioRingBuffer *abuf;
++ GstAudioRingBufferClass *cbuf;
++
++ abuf = GST_AUDIO_RING_BUFFER_CAST (buf);
++ cbuf = GST_AUDIO_RING_BUFFER_GET_CLASS (abuf);
++
++ /* unblock any pending writes to the audio device */
++ if (cbuf->reset) {
++ GST_DEBUG_OBJECT (abuf, "reset...");
++ cbuf->reset (abuf);
++ GST_DEBUG_OBJECT (abuf, "reset done");
++ }
++#if 0
++ if (abuf->running) {
++ GST_DEBUG_OBJECT (sink, "stop, waiting...");
++ GST_AUDIO_RING_BUFFER_WAIT (buf);
++ GST_DEBUG_OBJECT (sink, "stopped");
++ }
++#endif
++
++ return TRUE;
++}
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
++ * 2005 Wim Taymans <wim@fluendo.com>
++ *
++ * gstaudioringbuffer.h:
++ *
++ * 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., 59 Temple Place - Suite 330,
++ * Boston, MA 02111-1307, USA.
++ */
++
++#ifndef __GST_AUDIO_RING_BUFFER_H__
++#define __GST_AUDIO_RING_BUFFER_H__
++
++#include <gst/gst.h>
++#include <gst/audio/gstringbuffer.h>
++
++G_BEGIN_DECLS
++
++#define GST_TYPE_AUDIO_RING_BUFFER (gst_audio_ring_buffer_get_type())
++#define GST_AUDIO_RING_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AUDIO_RING_BUFFER,GstAudioRingBuffer))
++#define GST_AUDIO_RING_BUFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AUDIO_RING_BUFFER,GstAudioRingBufferClass))
++#define GST_AUDIO_RING_BUFFER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),GST_TYPE_AUDIO_RING_BUFFER,GstAudioRingBufferClass))
++#define GST_IS_AUDIO_RING_BUFFER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AUDIO_RING_BUFFER))
++#define GST_IS_AUDIO_RING_BUFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AUDIO_RING_BUFFER))
++#define GST_AUDIO_RING_BUFFER_CAST(obj) ((GstAudioRingBuffer *)obj)
++
++typedef struct _GstAudioRingBuffer GstAudioRingBuffer;
++typedef struct _GstAudioRingBufferClass GstAudioRingBufferClass;
++
++#include <gst/audio/gstringbufferthread.h>
++
++typedef enum {
++ GST_AUDIO_RING_BUFFER_MODE_UNKNOWN,
++ GST_AUDIO_RING_BUFFER_MODE_PLAYBACK,
++ GST_AUDIO_RING_BUFFER_MODE_CAPTURE
++} GstAudioRingBufferMode;
++/**
++ * GstAudioRingBuffer:
++ *
++ * Opaque #GstAudioRingBuffer.
++ */
++struct _GstAudioRingBuffer {
++ GstRingBuffer element;
++
++ /*< protected >*/
++ GstAudioRingBufferMode mode;
++ GstRingBufferThread *thread;
++
++ GstAudioRingBuffer *link;
++
++ /*< private >*/
++ gpointer _gst_reserved[GST_PADDING];
++};
++
++/**
++ * GstAudioRingBufferClass:
++ * @parent_class: the parent class structure.
++ * @process: Write/Read data to/from the device.
++ * @reset: Returns as quickly as possible from a write/read and flush any pending
++ * samples from the device.
++ *
++ * #GstAudioRingBuffer class. Override the vmethods to implement functionality.
++ */
++struct _GstAudioRingBufferClass {
++ GstRingBufferClass parent_class;
++
++ /* vtable */
++
++ /* write/read samples to the device */
++ gint (*process) (GstAudioRingBuffer *buf, gpointer data, guint length);
++ /* reset the audio device, unblock from a read/write */
++ void (*reset) (GstAudioRingBuffer *buf);
++
++ /*< private >*/
++ gpointer _gst_reserved[GST_PADDING];
++};
++
++GType gst_audio_ring_buffer_get_type(void);
++
++gboolean gst_audio_ring_buffer_link (GstAudioRingBuffer *buf1, GstAudioRingBuffer *buf2);
++
++gboolean gst_audio_ring_buffer_set_thread (GstAudioRingBuffer *buf, GstRingBufferThread *thread);
++
++G_END_DECLS
++
++#endif /* __GST_AUDIO_RING_BUFFER_H__ */
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
++ * 2005 Wim Taymans <wim@fluendo.com>
++ *
++ * gstbaseaudiosrc.c:
++ *
++ * 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., 59 Temple Place - Suite 330,
++ * Boston, MA 02111-1307, USA.
++ */
++
++/**
++ * SECTION:gstbaseaudiosrc
++ * @short_description: Base class for audio sources
++ * @see_also: #GstAudioSrc, #GstRingBuffer.
++ *
++ * This is the base class for audio sources. Subclasses need to implement the
++ * ::create_ringbuffer vmethod. This base class will then take care of
++ * reading samples from the ringbuffer, synchronisation and flushing.
++ *
++ * Last reviewed on 2006-09-27 (0.10.12)
++ */
++
++#ifdef HAVE_CONFIG_H
++# include "config.h"
++#endif
++
++#include <string.h>
++
++#include "gstbaseaudiosrc.h"
++
++#include "gst/gst-i18n-plugin.h"
++
++GST_DEBUG_CATEGORY_STATIC (gst_base_audio_src_debug);
++#define GST_CAT_DEFAULT gst_base_audio_src_debug
++
++GType
++gst_base_audio_src_slave_method_get_type (void)
++{
++ static GType slave_method_type = 0;
++ static const GEnumValue slave_method[] = {
++ {GST_BASE_AUDIO_SRC_SLAVE_RESAMPLE, "Resampling slaving", "resample"},
++ {GST_BASE_AUDIO_SRC_SLAVE_RETIMESTAMP, "Re-timestamp", "re-timestamp"},
++ {GST_BASE_AUDIO_SRC_SLAVE_SKEW, "Skew", "skew"},
++ {GST_BASE_AUDIO_SRC_SLAVE_NONE, "No slaving", "none"},
++ {0, NULL, NULL},
++ };
++
++ if (!slave_method_type) {
++ slave_method_type =
++ g_enum_register_static ("GstBaseAudioSrcSlaveMethod", slave_method);
++ }
++ return slave_method_type;
++}
++
++#define GST_BASE_AUDIO_SRC_GET_PRIVATE(obj) \
++ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_BASE_AUDIO_SRC, GstBaseAudioSrcPrivate))
++
++struct _GstBaseAudioSrcPrivate
++{
++ gboolean provide_clock;
++
++ /* the clock slaving algorithm in use */
++ GstBaseAudioSrcSlaveMethod slave_method;
++};
++
++/* BaseAudioSrc signals and args */
++enum
++{
++ /* FILL ME */
++ LAST_SIGNAL
++};
++
++#define DEFAULT_BUFFER_TIME ((200 * GST_MSECOND) / GST_USECOND)
++#define DEFAULT_LATENCY_TIME ((10 * GST_MSECOND) / GST_USECOND)
++#define DEFAULT_ACTUAL_BUFFER_TIME -1
++#define DEFAULT_ACTUAL_LATENCY_TIME -1
++#define DEFAULT_PROVIDE_CLOCK TRUE
++#define DEFAULT_SLAVE_METHOD GST_BASE_AUDIO_SRC_SLAVE_SKEW
++
++enum
++{
++ PROP_0,
++ PROP_BUFFER_TIME,
++ PROP_LATENCY_TIME,
++ PROP_ACTUAL_BUFFER_TIME,
++ PROP_ACTUAL_LATENCY_TIME,
++ PROP_PROVIDE_CLOCK,
++ PROP_SLAVE_METHOD,
++ PROP_LAST
++};
++
++static void
++_do_init (GType type)
++{
++ GST_DEBUG_CATEGORY_INIT (gst_base_audio_src_debug, "baseaudiosrc", 0,
++ "baseaudiosrc element");
++
++#ifdef ENABLE_NLS
++ GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE,
++ LOCALEDIR);
++ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
++ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
++#endif /* ENABLE_NLS */
++}
++
++GST_BOILERPLATE_FULL (GstBaseAudioSrc, gst_base_audio_src, GstPushSrc,
++ GST_TYPE_PUSH_SRC, _do_init);
++
++static void gst_base_audio_src_set_property (GObject * object, guint prop_id,
++ const GValue * value, GParamSpec * pspec);
++static void gst_base_audio_src_get_property (GObject * object, guint prop_id,
++ GValue * value, GParamSpec * pspec);
++static void gst_base_audio_src_dispose (GObject * object);
++
++static GstStateChangeReturn gst_base_audio_src_change_state (GstElement *
++ element, GstStateChange transition);
++
++static GstClock *gst_base_audio_src_provide_clock (GstElement * elem);
++static GstClockTime gst_base_audio_src_get_time (GstClock * clock,
++ GstBaseAudioSrc * src);
++
++static GstFlowReturn gst_base_audio_src_create (GstBaseSrc * bsrc,
++ guint64 offset, guint length, GstBuffer ** buf);
++static gboolean gst_base_audio_src_check_get_range (GstBaseSrc * bsrc);
++
++static gboolean gst_base_audio_src_event (GstBaseSrc * bsrc, GstEvent * event);
++static void gst_base_audio_src_get_times (GstBaseSrc * bsrc,
++ GstBuffer * buffer, GstClockTime * start, GstClockTime * end);
++static gboolean gst_base_audio_src_setcaps (GstBaseSrc * bsrc, GstCaps * caps);
++static gboolean gst_base_audio_src_query (GstBaseSrc * bsrc, GstQuery * query);
++static void gst_base_audio_src_fixate (GstBaseSrc * bsrc, GstCaps * caps);
++
++/* static guint gst_base_audio_src_signals[LAST_SIGNAL] = { 0 }; */
++
++static void
++gst_base_audio_src_base_init (gpointer g_class)
++{
++}
++
++static void
++gst_base_audio_src_class_init (GstBaseAudioSrcClass * klass)
++{
++ GObjectClass *gobject_class;
++ GstElementClass *gstelement_class;
++ GstBaseSrcClass *gstbasesrc_class;
++
++ gobject_class = (GObjectClass *) klass;
++ gstelement_class = (GstElementClass *) klass;
++ gstbasesrc_class = (GstBaseSrcClass *) klass;
++
++ g_type_class_add_private (klass, sizeof (GstBaseAudioSrcPrivate));
++
++ gobject_class->set_property =
++ GST_DEBUG_FUNCPTR (gst_base_audio_src_set_property);
++ gobject_class->get_property =
++ GST_DEBUG_FUNCPTR (gst_base_audio_src_get_property);
++ gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_base_audio_src_dispose);
++
++ g_object_class_install_property (gobject_class, PROP_BUFFER_TIME,
++ g_param_spec_int64 ("buffer-time", "Buffer Time",
++ "Size of audio buffer in microseconds", 1,
++ G_MAXINT64, DEFAULT_BUFFER_TIME,
++ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
++
++ g_object_class_install_property (gobject_class, PROP_LATENCY_TIME,
++ g_param_spec_int64 ("latency-time", "Latency Time",
++ "Audio latency in microseconds", 1,
++ G_MAXINT64, DEFAULT_LATENCY_TIME,
++ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
++
++ /**
++ * GstBaseAudioSrc:actual-buffer-time:
++ *
++ * Actual configured size of audio buffer in microseconds.
++ *
++ * Since: 0.10.20
++ **/
++ g_object_class_install_property (gobject_class, PROP_ACTUAL_BUFFER_TIME,
++ g_param_spec_int64 ("actual-buffer-time", "Actual Buffer Time",
++ "Actual configured size of audio buffer in microseconds",
++ DEFAULT_ACTUAL_BUFFER_TIME, G_MAXINT64, DEFAULT_ACTUAL_BUFFER_TIME,
++ G_PARAM_READABLE));
++
++ /**
++ * GstBaseAudioSrc:actual-latency-time:
++ *
++ * Actual configured audio latency in microseconds.
++ *
++ * Since: 0.10.20
++ **/
++ g_object_class_install_property (gobject_class, PROP_ACTUAL_LATENCY_TIME,
++ g_param_spec_int64 ("actual-latency-time", "Actual Latency Time",
++ "Actual configured audio latency in microseconds",
++ DEFAULT_ACTUAL_LATENCY_TIME, G_MAXINT64, DEFAULT_ACTUAL_LATENCY_TIME,
++ G_PARAM_READABLE));
++
++ g_object_class_install_property (gobject_class, PROP_PROVIDE_CLOCK,
++ g_param_spec_boolean ("provide-clock", "Provide Clock",
++ "Provide a clock to be used as the global pipeline clock",
++ DEFAULT_PROVIDE_CLOCK, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
++
++ g_object_class_install_property (gobject_class, PROP_SLAVE_METHOD,
++ g_param_spec_enum ("slave-method", "Slave Method",
++ "Algorithm to use to match the rate of the masterclock",
++ GST_TYPE_BASE_AUDIO_SRC_SLAVE_METHOD, DEFAULT_SLAVE_METHOD,
++ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
++
++ gstelement_class->change_state =
++ GST_DEBUG_FUNCPTR (gst_base_audio_src_change_state);
++ gstelement_class->provide_clock =
++ GST_DEBUG_FUNCPTR (gst_base_audio_src_provide_clock);
++
++ gstbasesrc_class->set_caps = GST_DEBUG_FUNCPTR (gst_base_audio_src_setcaps);
++ gstbasesrc_class->event = GST_DEBUG_FUNCPTR (gst_base_audio_src_event);
++ gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_base_audio_src_query);
++ gstbasesrc_class->get_times =
++ GST_DEBUG_FUNCPTR (gst_base_audio_src_get_times);
++ gstbasesrc_class->create = GST_DEBUG_FUNCPTR (gst_base_audio_src_create);
++ gstbasesrc_class->check_get_range =
++ GST_DEBUG_FUNCPTR (gst_base_audio_src_check_get_range);
++ gstbasesrc_class->fixate = GST_DEBUG_FUNCPTR (gst_base_audio_src_fixate);
++
++ /* ref class from a thread-safe context to work around missing bit of
++ * thread-safety in GObject */
++ g_type_class_ref (GST_TYPE_AUDIO_CLOCK);
++ g_type_class_ref (GST_TYPE_RING_BUFFER);
++}
++
++static void
++gst_base_audio_src_init (GstBaseAudioSrc * baseaudiosrc,
++ GstBaseAudioSrcClass * g_class)
++{
++ baseaudiosrc->priv = GST_BASE_AUDIO_SRC_GET_PRIVATE (baseaudiosrc);
++
++ baseaudiosrc->buffer_time = DEFAULT_BUFFER_TIME;
++ baseaudiosrc->latency_time = DEFAULT_LATENCY_TIME;
++ baseaudiosrc->priv->provide_clock = DEFAULT_PROVIDE_CLOCK;
++ baseaudiosrc->priv->slave_method = DEFAULT_SLAVE_METHOD;
++ /* reset blocksize we use latency time to calculate a more useful
++ * value based on negotiated format. */
++ GST_BASE_SRC (baseaudiosrc)->blocksize = 0;
++
++ baseaudiosrc->clock = gst_audio_clock_new ("GstAudioSrcClock",
++ (GstAudioClockGetTimeFunc) gst_base_audio_src_get_time, baseaudiosrc);
++
++ /* we are always a live source */
++ gst_base_src_set_live (GST_BASE_SRC (baseaudiosrc), TRUE);
++ /* we operate in time */
++ gst_base_src_set_format (GST_BASE_SRC (baseaudiosrc), GST_FORMAT_TIME);
++}
++
++static void
++gst_base_audio_src_dispose (GObject * object)
++{
++ GstBaseAudioSrc *src;
++
++ src = GST_BASE_AUDIO_SRC (object);
++
++ GST_OBJECT_LOCK (src);
++ if (src->clock)
++ gst_object_unref (src->clock);
++ src->clock = NULL;
++
++ if (src->ringbuffer) {
++ gst_object_unparent (GST_OBJECT_CAST (src->ringbuffer));
++ src->ringbuffer = NULL;
++ }
++ GST_OBJECT_UNLOCK (src);
++
++ G_OBJECT_CLASS (parent_class)->dispose (object);
++}
++
++static GstClock *
++gst_base_audio_src_provide_clock (GstElement * elem)
++{
++ GstBaseAudioSrc *src;
++ GstClock *clock;
++
++ src = GST_BASE_AUDIO_SRC (elem);
++
++ /* we have no ringbuffer (must be NULL state) */
++ if (src->ringbuffer == NULL)
++ goto wrong_state;
++
++ if (!gst_ring_buffer_is_acquired (src->ringbuffer))
++ goto wrong_state;
++
++ GST_OBJECT_LOCK (src);
++ if (!src->priv->provide_clock)
++ goto clock_disabled;
++
++ clock = GST_CLOCK_CAST (gst_object_ref (src->clock));
++ GST_OBJECT_UNLOCK (src);
++
++ return clock;
++
++ /* ERRORS */
++wrong_state:
++ {
++ GST_DEBUG_OBJECT (src, "ringbuffer not acquired");
++ return NULL;
++ }
++clock_disabled:
++ {
++ GST_DEBUG_OBJECT (src, "clock provide disabled");
++ GST_OBJECT_UNLOCK (src);
++ return NULL;
++ }
++}
++
++static GstClockTime
++gst_base_audio_src_get_time (GstClock * clock, GstBaseAudioSrc * src)
++{
++ guint64 raw, samples;
++ guint delay;
++ GstClockTime result;
++
++ if (G_UNLIKELY (src->ringbuffer == NULL || src->ringbuffer->spec.rate == 0))
++ return GST_CLOCK_TIME_NONE;
++
++ raw = samples = gst_ring_buffer_samples_done (src->ringbuffer);
++
++ /* the number of samples not yet processed, this is still queued in the
++ * device (not yet read for capture). */
++ delay = gst_ring_buffer_delay (src->ringbuffer);
++
++ samples += delay;
++
++ result = gst_util_uint64_scale_int (samples, GST_SECOND,
++ src->ringbuffer->spec.rate);
++
++ GST_DEBUG_OBJECT (src,
++ "processed samples: raw %llu, delay %u, real %llu, time %"
++ GST_TIME_FORMAT, raw, delay, samples, GST_TIME_ARGS (result));
++
++ return result;
++}
++
++static gboolean
++gst_base_audio_src_check_get_range (GstBaseSrc * bsrc)
++{
++ /* we allow limited pull base operation of which the details
++ * will eventually exposed in an as of yet non-existing query.
++ * Basically pulling can be done on any number of bytes as long
++ * as the offset is -1 or sequentially increasing. */
++ return TRUE;
++}
++
++/**
++ * gst_base_audio_src_set_provide_clock:
++ * @src: a #GstBaseAudioSrc
++ * @provide: new state
++ *
++ * Controls whether @src will provide a clock or not. If @provide is %TRUE,
++ * gst_element_provide_clock() will return a clock that reflects the datarate
++ * of @src. If @provide is %FALSE, gst_element_provide_clock() will return NULL.
++ *
++ * Since: 0.10.16
++ */
++void
++gst_base_audio_src_set_provide_clock (GstBaseAudioSrc * src, gboolean provide)
++{
++ g_return_if_fail (GST_IS_BASE_AUDIO_SRC (src));
++
++ GST_OBJECT_LOCK (src);
++ src->priv->provide_clock = provide;
++ GST_OBJECT_UNLOCK (src);
++}
++
++/**
++ * gst_base_audio_src_get_provide_clock:
++ * @src: a #GstBaseAudioSrc
++ *
++ * Queries whether @src will provide a clock or not. See also
++ * gst_base_audio_src_set_provide_clock.
++ *
++ * Returns: %TRUE if @src will provide a clock.
++ *
++ * Since: 0.10.16
++ */
++gboolean
++gst_base_audio_src_get_provide_clock (GstBaseAudioSrc * src)
++{
++ gboolean result;
++
++ g_return_val_if_fail (GST_IS_BASE_AUDIO_SRC (src), FALSE);
++
++ GST_OBJECT_LOCK (src);
++ result = src->priv->provide_clock;
++ GST_OBJECT_UNLOCK (src);
++
++ return result;
++}
++
++/**
++ * gst_base_audio_src_set_slave_method:
++ * @src: a #GstBaseAudioSrc
++ * @method: the new slave method
++ *
++ * Controls how clock slaving will be performed in @src.
++ *
++ * Since: 0.10.20
++ */
++void
++gst_base_audio_src_set_slave_method (GstBaseAudioSrc * src,
++ GstBaseAudioSrcSlaveMethod method)
++{
++ g_return_if_fail (GST_IS_BASE_AUDIO_SRC (src));
++
++ GST_OBJECT_LOCK (src);
++ src->priv->slave_method = method;
++ GST_OBJECT_UNLOCK (src);
++}
++
++/**
++ * gst_base_audio_src_get_slave_method:
++ * @src: a #GstBaseAudioSrc
++ *
++ * Get the current slave method used by @src.
++ *
++ * Returns: The current slave method used by @src.
++ *
++ * Since: 0.10.20
++ */
++GstBaseAudioSrcSlaveMethod
++gst_base_audio_src_get_slave_method (GstBaseAudioSrc * src)
++{
++ GstBaseAudioSrcSlaveMethod result;
++
++ g_return_val_if_fail (GST_IS_BASE_AUDIO_SRC (src), -1);
++
++ GST_OBJECT_LOCK (src);
++ result = src->priv->slave_method;
++ GST_OBJECT_UNLOCK (src);
++
++ return result;
++}
++
++static void
++gst_base_audio_src_set_property (GObject * object, guint prop_id,
++ const GValue * value, GParamSpec * pspec)
++{
++ GstBaseAudioSrc *src;
++
++ src = GST_BASE_AUDIO_SRC (object);
++
++ switch (prop_id) {
++ case PROP_BUFFER_TIME:
++ src->buffer_time = g_value_get_int64 (value);
++ break;
++ case PROP_LATENCY_TIME:
++ src->latency_time = g_value_get_int64 (value);
++ break;
++ case PROP_PROVIDE_CLOCK:
++ gst_base_audio_src_set_provide_clock (src, g_value_get_boolean (value));
++ break;
++ case PROP_SLAVE_METHOD:
++ gst_base_audio_src_set_slave_method (src, g_value_get_enum (value));
++ break;
++ default:
++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++ break;
++ }
++}
++
++static void
++gst_base_audio_src_get_property (GObject * object, guint prop_id,
++ GValue * value, GParamSpec * pspec)
++{
++ GstBaseAudioSrc *src;
++
++ src = GST_BASE_AUDIO_SRC (object);
++
++ switch (prop_id) {
++ case PROP_BUFFER_TIME:
++ g_value_set_int64 (value, src->buffer_time);
++ break;
++ case PROP_LATENCY_TIME:
++ g_value_set_int64 (value, src->latency_time);
++ break;
++ case PROP_ACTUAL_BUFFER_TIME:
++ GST_OBJECT_LOCK (src);
++ if (src->ringbuffer && src->ringbuffer->acquired)
++ g_value_set_int64 (value, src->ringbuffer->spec.buffer_time);
++ else
++ g_value_set_int64 (value, DEFAULT_ACTUAL_BUFFER_TIME);
++ GST_OBJECT_UNLOCK (src);
++ break;
++ case PROP_ACTUAL_LATENCY_TIME:
++ GST_OBJECT_LOCK (src);
++ if (src->ringbuffer && src->ringbuffer->acquired)
++ g_value_set_int64 (value, src->ringbuffer->spec.latency_time);
++ else
++ g_value_set_int64 (value, DEFAULT_ACTUAL_LATENCY_TIME);
++ GST_OBJECT_UNLOCK (src);
++ break;
++ case PROP_PROVIDE_CLOCK:
++ g_value_set_boolean (value, gst_base_audio_src_get_provide_clock (src));
++ break;
++ case PROP_SLAVE_METHOD:
++ g_value_set_enum (value, gst_base_audio_src_get_slave_method (src));
++ break;
++ default:
++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++ break;
++ }
++}
++
++static void
++gst_base_audio_src_fixate (GstBaseSrc * bsrc, GstCaps * caps)
++{
++ GstStructure *s;
++ gint width, depth;
++
++ s = gst_caps_get_structure (caps, 0);
++
++ /* fields for all formats */
++ gst_structure_fixate_field_nearest_int (s, "rate", 44100);
++ gst_structure_fixate_field_nearest_int (s, "channels", 2);
++ gst_structure_fixate_field_nearest_int (s, "width", 16);
++
++ /* fields for int */
++ if (gst_structure_has_field (s, "depth")) {
++ gst_structure_get_int (s, "width", &width);
++ /* round width to nearest multiple of 8 for the depth */
++ depth = GST_ROUND_UP_8 (width);
++ gst_structure_fixate_field_nearest_int (s, "depth", depth);
++ }
++ if (gst_structure_has_field (s, "signed"))
++ gst_structure_fixate_field_boolean (s, "signed", TRUE);
++ if (gst_structure_has_field (s, "endianness"))
++ gst_structure_fixate_field_nearest_int (s, "endianness", G_BYTE_ORDER);
++}
++
++static gboolean
++gst_base_audio_src_setcaps (GstBaseSrc * bsrc, GstCaps * caps)
++{
++ GstBaseAudioSrc *src = GST_BASE_AUDIO_SRC (bsrc);
++ GstRingBufferSpec *spec;
++
++ spec = &src->ringbuffer->spec;
++
++ spec->buffer_time = src->buffer_time;
++ spec->latency_time = src->latency_time;
++
++ if (!gst_ring_buffer_parse_caps (spec, caps))
++ goto parse_error;
++
++ /* calculate suggested segsize and segtotal */
++ spec->segsize =
++ spec->rate * spec->bytes_per_sample * spec->latency_time / GST_MSECOND;
++ spec->segtotal = spec->buffer_time / spec->latency_time;
++
++ GST_DEBUG ("release old ringbuffer");
++
++ gst_ring_buffer_release (src->ringbuffer);
++
++ gst_ring_buffer_debug_spec_buff (spec);
++
++ GST_DEBUG ("acquire new ringbuffer");
++
++ if (!gst_ring_buffer_acquire (src->ringbuffer, spec))
++ goto acquire_error;
++
++ /* calculate actual latency and buffer times */
++ spec->latency_time =
++ spec->segsize * GST_MSECOND / (spec->rate * spec->bytes_per_sample);
++ spec->buffer_time =
++ spec->segtotal * spec->segsize * GST_MSECOND / (spec->rate *
++ spec->bytes_per_sample);
++
++ gst_ring_buffer_debug_spec_buff (spec);
++
++ g_object_notify (G_OBJECT (src), "actual-buffer-time");
++ g_object_notify (G_OBJECT (src), "actual-latency-time");
++
++ return TRUE;
++
++ /* ERRORS */
++parse_error:
++ {
++ GST_DEBUG ("could not parse caps");
++ return FALSE;
++ }
++acquire_error:
++ {
++ GST_DEBUG ("could not acquire ringbuffer");
++ return FALSE;
++ }
++}
++
++static void
++gst_base_audio_src_get_times (GstBaseSrc * bsrc, GstBuffer * buffer,
++ GstClockTime * start, GstClockTime * end)
++{
++ /* no need to sync to a clock here, we schedule the samples based
++ * on our own clock for the moment. */
++ *start = GST_CLOCK_TIME_NONE;
++ *end = GST_CLOCK_TIME_NONE;
++}
++
++static gboolean
++gst_base_audio_src_query (GstBaseSrc * bsrc, GstQuery * query)
++{
++ GstBaseAudioSrc *src = GST_BASE_AUDIO_SRC (bsrc);
++ gboolean res = FALSE;
++
++ switch (GST_QUERY_TYPE (query)) {
++ case GST_QUERY_LATENCY:
++ {
++ GstClockTime min_latency, max_latency;
++ GstRingBufferSpec *spec;
++
++ if (G_UNLIKELY (src->ringbuffer == NULL
++ || src->ringbuffer->spec.rate == 0))
++ goto done;
++
++ spec = &src->ringbuffer->spec;
++
++ /* we have at least 1 segment of latency */
++ min_latency =
++ gst_util_uint64_scale_int (spec->segsize, GST_SECOND,
++ spec->rate * spec->bytes_per_sample);
++ /* we cannot delay more than the buffersize else we lose data */
++ max_latency =
++ gst_util_uint64_scale_int (spec->segtotal * spec->segsize, GST_SECOND,
++ spec->rate * spec->bytes_per_sample);
++
++ GST_DEBUG_OBJECT (src,
++ "report latency min %" GST_TIME_FORMAT " max %" GST_TIME_FORMAT,
++ GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency));
++
++ /* we are always live, the min latency is 1 segment and the max latency is
++ * the complete buffer of segments. */
++ gst_query_set_latency (query, TRUE, min_latency, max_latency);
++
++ res = TRUE;
++ break;
++ }
++ default:
++ res = GST_BASE_SRC_CLASS (parent_class)->query (bsrc, query);
++ break;
++ }
++done:
++ return res;
++}
++
++static gboolean
++gst_base_audio_src_event (GstBaseSrc * bsrc, GstEvent * event)
++{
++ GstBaseAudioSrc *src = GST_BASE_AUDIO_SRC (bsrc);
++ gboolean res;
++
++ res = TRUE;
++
++ switch (GST_EVENT_TYPE (event)) {
++ case GST_EVENT_FLUSH_START:
++ GST_DEBUG_OBJECT (bsrc, "flush-start");
++ gst_ring_buffer_pause (src->ringbuffer);
++ gst_ring_buffer_clear_all (src->ringbuffer);
++ break;
++ case GST_EVENT_FLUSH_STOP:
++ GST_DEBUG_OBJECT (bsrc, "flush-stop");
++ /* always resync on sample after a flush */
++ src->next_sample = -1;
++ gst_ring_buffer_clear_all (src->ringbuffer);
++ break;
++ case GST_EVENT_SEEK:
++ GST_DEBUG_OBJECT (bsrc, "refuse to seek");
++ res = FALSE;
++ break;
++ default:
++ GST_DEBUG_OBJECT (bsrc, "dropping event %p", event);
++ break;
++ }
++ return res;
++}
++
++/* get the next offset in the ringbuffer for reading samples.
++ * If the next sample is too far away, this function will position itself to the
++ * next most recent sample, creating discontinuity */
++static guint64
++gst_base_audio_src_get_offset (GstBaseAudioSrc * src)
++{
++ guint64 sample;
++ gint readseg, segdone, segtotal, sps;
++ gint diff;
++
++ /* assume we can append to the previous sample */
++ sample = src->next_sample;
++ /* no previous sample, try to read from position 0 */
++ if (sample == -1)
++ sample = 0;
++
++ sps = src->ringbuffer->samples_per_seg;
++ segtotal = src->ringbuffer->spec.segtotal;
++
++ /* figure out the segment and the offset inside the segment where
++ * the sample should be read from. */
++ readseg = sample / sps;
++
++ /* get the currently processed segment */
++ segdone = g_atomic_int_get (&src->ringbuffer->segdone)
++ - src->ringbuffer->segbase;
++
++ GST_DEBUG_OBJECT (src, "reading from %d, we are at %d", readseg, segdone);
++
++ /* see how far away it is from the read segment, normally segdone (where new
++ * data is written in the ringbuffer) is bigger than readseg (where we are
++ * reading). */
++ diff = segdone - readseg;
++ if (diff >= segtotal) {
++ GST_DEBUG_OBJECT (src, "dropped, align to segment %d", segdone);
++ /* sample would be dropped, position to next playable position */
++ sample = ((guint64) (segdone)) * sps;
++ }
++
++ return sample;
++}
++
++static GstFlowReturn
++gst_base_audio_src_create (GstBaseSrc * bsrc, guint64 offset, guint length,
++ GstBuffer ** outbuf)
++{
++ GstBaseAudioSrc *src = GST_BASE_AUDIO_SRC (bsrc);
++ GstBuffer *buf;
++ guchar *data;
++ guint samples, total_samples;
++ guint64 sample;
++ gint bps;
++ GstRingBuffer *ringbuffer;
++ GstRingBufferSpec *spec;
++ guint read;
++ GstClockTime timestamp, duration;
++ GstClock *clock;
++
++ ringbuffer = src->ringbuffer;
++ spec = &ringbuffer->spec;
++
++ if (G_UNLIKELY (!gst_ring_buffer_is_acquired (ringbuffer)))
++ goto wrong_state;
++
++ bps = spec->bytes_per_sample;
++
++ if ((length == 0 && bsrc->blocksize == 0) || length == -1)
++ /* no length given, use the default segment size */
++ length = spec->segsize;
++ else
++ /* make sure we round down to an integral number of samples */
++ length -= length % bps;
++
++ /* figure out the offset in the ringbuffer */
++ if (G_UNLIKELY (offset != -1)) {
++ sample = offset / bps;
++ /* if a specific offset was given it must be the next sequential
++ * offset we expect or we fail for now. */
++ if (src->next_sample != -1 && sample != src->next_sample)
++ goto wrong_offset;
++ } else {
++ /* calculate the sequentially next sample we need to read. This can jump and
++ * create a DISCONT. */
++ sample = gst_base_audio_src_get_offset (src);
++ }
++
++ GST_DEBUG_OBJECT (src, "reading from sample %" G_GUINT64_FORMAT, sample);
++
++ /* get the number of samples to read */
++ total_samples = samples = length / bps;
++
++ /* FIXME, using a bufferpool would be nice here */
++ buf = gst_buffer_new_and_alloc (length);
++ data = GST_BUFFER_DATA (buf);
++
++ do {
++ read = gst_ring_buffer_read (ringbuffer, sample, data, samples);
++ GST_DEBUG_OBJECT (src, "read %u of %u", read, samples);
++ /* if we read all, we're done */
++ if (read == samples)
++ break;
++
++ /* else something interrupted us and we wait for playing again. */
++ GST_DEBUG_OBJECT (src, "wait playing");
++ if (gst_base_src_wait_playing (bsrc) != GST_FLOW_OK)
++ goto stopped;
++
++ GST_DEBUG_OBJECT (src, "continue playing");
++
++ /* read next samples */
++ sample += read;
++ samples -= read;
++ data += read * bps;
++ } while (TRUE);
++
++ /* mark discontinuity if needed */
++ if (G_UNLIKELY (sample != src->next_sample) && src->next_sample != -1) {
++ GST_WARNING_OBJECT (src,
++ "create DISCONT of %" G_GUINT64_FORMAT " samples at sample %"
++ G_GUINT64_FORMAT, sample - src->next_sample, sample);
++ GST_ELEMENT_WARNING (src, CORE, CLOCK,
++ (_("Can't record audio fast enough")),
++ ("Dropped %" G_GUINT64_FORMAT " samples. This is most likely because "
++ "downstream can't keep up and is consuming samples too slowly.",
++ sample - src->next_sample));
++ GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
++ }
++
++ src->next_sample = sample + samples;
++
++ /* get the normal timestamp to get the duration. */
++ timestamp = gst_util_uint64_scale_int (sample, GST_SECOND, spec->rate);
++ duration = gst_util_uint64_scale_int (src->next_sample, GST_SECOND,
++ spec->rate) - timestamp;
++
++ GST_OBJECT_LOCK (src);
++ if (!(clock = GST_ELEMENT_CLOCK (src)))
++ goto no_sync;
++
++ if (clock != src->clock) {
++ /* we are slaved, check how to handle this */
++ switch (src->priv->slave_method) {
++ case GST_BASE_AUDIO_SRC_SLAVE_RESAMPLE:
++ /* not implemented, use skew algorithm. This algorithm should
++ * work on the readout pointer and produces more or less samples based
++ * on the clock drift */
++ case GST_BASE_AUDIO_SRC_SLAVE_SKEW:
++ {
++ GstClockTime running_time;
++ GstClockTime base_time;
++ GstClockTime current_time;
++ guint64 running_time_sample;
++ gint running_time_segment;
++ gint current_segment;
++ gint segment_skew;
++ gint sps;
++
++ /* samples per segment */
++ sps = ringbuffer->samples_per_seg;
++
++ /* get the current time */
++ current_time = gst_clock_get_time (clock);
++
++ /* get the basetime */
++ base_time = GST_ELEMENT_CAST (src)->base_time;
++
++ /* get the running_time */
++ running_time = current_time - base_time;
++
++ /* the running_time converted to a sample (relative to the ringbuffer) */
++ running_time_sample =
++ gst_util_uint64_scale_int (running_time, spec->rate, GST_SECOND);
++
++ /* the segmentnr corrensponding to running_time, round down */
++ running_time_segment = running_time_sample / sps;
++
++ /* the segment currently read from the ringbuffer */
++ current_segment = sample / sps;
++
++ /* the skew we have between running_time and the ringbuffertime */
++ segment_skew = running_time_segment - current_segment;
++
++ GST_DEBUG_OBJECT (bsrc, "\n running_time = %" GST_TIME_FORMAT
++ "\n timestamp = %" GST_TIME_FORMAT
++ "\n running_time_segment = %d"
++ "\n current_segment = %d"
++ "\n segment_skew = %d",
++ GST_TIME_ARGS (running_time),
++ GST_TIME_ARGS (timestamp),
++ running_time_segment, current_segment, segment_skew);
++
++ /* Resync the ringbuffer if:
++ * 1. We get one segment into the future.
++ * This is clearly a lie, because we can't
++ * possibly have a buffer with timestamp 1 at
++ * time 0. (unless it has time-travelled...)
++ *
++ * 2. We are more than the length of the ringbuffer behind.
++ * The length of the ringbuffer then gets to dictate
++ * the threshold for what is concidered "too late"
++ *
++ * 3. If this is our first buffer.
++ * We know that we should catch up to running_time
++ * the first time we are ran.
++ */
++ if ((segment_skew < 0) ||
++ (segment_skew >= ringbuffer->spec.segtotal) ||
++ (current_segment == 0)) {
++ gint segments_written;
++ gint first_segment;
++ gint last_segment;
++ gint new_last_segment;
++ gint segment_diff;
++ gint new_first_segment;
++ guint64 new_sample;
++
++ /* we are going to say that the last segment was captured at the current time
++ (running_time), minus one segment of creation-latency in the ringbuffer.
++ This can be thought of as: The segment arrived in the ringbuffer at time X, and
++ that means it was created at time X - (one segment). */
++ new_last_segment = running_time_segment - 1;
++
++ /* for better readablity */
++ first_segment = current_segment;
++
++ /* get the amount of segments written from the device by now */
++ segments_written = g_atomic_int_get (&ringbuffer->segdone);
++
++ /* subtract the base to segments_written to get the number of the
++ last written segment in the ringbuffer (one segment written = segment 0) */
++ last_segment = segments_written - ringbuffer->segbase - 1;
++
++ /* we see how many segments the ringbuffer was timeshifted */
++ segment_diff = new_last_segment - last_segment;
++
++ /* we move the first segment an equal amount */
++ new_first_segment = first_segment + segment_diff;
++
++ /* and we also move the segmentbase the same amount */
++ ringbuffer->segbase -= segment_diff;
++
++ /* we calculate the new sample value */
++ new_sample = ((guint64) new_first_segment) * sps;
++
++ /* and get the relative time to this -> our new timestamp */
++ timestamp =
++ gst_util_uint64_scale_int (new_sample, GST_SECOND, spec->rate);
++
++ /* we update the next sample accordingly */
++ src->next_sample = new_sample + samples;
++
++ GST_DEBUG_OBJECT (bsrc,
++ "Timeshifted the ringbuffer with %d segments: "
++ "Updating the timestamp to %" GST_TIME_FORMAT ", "
++ "and src->next_sample to %" G_GUINT64_FORMAT, segment_diff,
++ GST_TIME_ARGS (timestamp), src->next_sample);
++ }
++ break;
++ }
++ case GST_BASE_AUDIO_SRC_SLAVE_RETIMESTAMP:
++ {
++ GstClockTime base_time, latency;
++
++ /* We are slaved to another clock, take running time of the pipeline clock and
++ * timestamp against it. Somebody else in the pipeline should figure out the
++ * clock drift. We keep the duration we calculated above. */
++ timestamp = gst_clock_get_time (clock);
++ base_time = GST_ELEMENT_CAST (src)->base_time;
++
++ if (timestamp > base_time)
++ timestamp -= base_time;
++ else
++ timestamp = 0;
++
++ /* subtract latency */
++ latency =
++ gst_util_uint64_scale_int (total_samples, GST_SECOND, spec->rate);
++ if (timestamp > latency)
++ timestamp -= latency;
++ else
++ timestamp = 0;
++ }
++ case GST_BASE_AUDIO_SRC_SLAVE_NONE:
++ break;
++ }
++ } else {
++ GstClockTime base_time;
++
++ /* to get the timestamp against the clock we also need to add our offset */
++ timestamp = gst_audio_clock_adjust (clock, timestamp);
++
++ /* we are not slaved, subtract base_time */
++ base_time = GST_ELEMENT_CAST (src)->base_time;
++
++ if (timestamp > base_time) {
++ timestamp -= base_time;
++ GST_LOG_OBJECT (src,
++ "buffer timestamp %" GST_TIME_FORMAT " (base_time %" GST_TIME_FORMAT
++ ")", GST_TIME_ARGS (timestamp), GST_TIME_ARGS (base_time));
++ } else {
++ GST_LOG_OBJECT (src,
++ "buffer timestamp 0, ts %" GST_TIME_FORMAT " <= base_time %"
++ GST_TIME_FORMAT, GST_TIME_ARGS (timestamp),
++ GST_TIME_ARGS (base_time));
++ timestamp = 0;
++ }
++ }
++
++no_sync:
++ GST_OBJECT_UNLOCK (src);
++
++ GST_BUFFER_TIMESTAMP (buf) = timestamp;
++ GST_BUFFER_DURATION (buf) = duration;
++ GST_BUFFER_OFFSET (buf) = sample;
++ GST_BUFFER_OFFSET_END (buf) = sample + samples;
++
++ *outbuf = buf;
++
++ return GST_FLOW_OK;
++
++ /* ERRORS */
++wrong_state:
++ {
++ GST_DEBUG_OBJECT (src, "ringbuffer in wrong state");
++ return GST_FLOW_WRONG_STATE;
++ }
++wrong_offset:
++ {
++ GST_ELEMENT_ERROR (src, RESOURCE, SEEK,
++ (NULL), ("resource can only be operated on sequentially but offset %"
++ G_GUINT64_FORMAT " was given", offset));
++ return GST_FLOW_ERROR;
++ }
++stopped:
++ {
++ gst_buffer_unref (buf);
++ GST_DEBUG_OBJECT (src, "ringbuffer stopped");
++ return GST_FLOW_WRONG_STATE;
++ }
++}
++
++/**
++ * gst_base_audio_src_create_ringbuffer:
++ * @src: a #GstBaseAudioSrc.
++ *
++ * Create and return the #GstRingBuffer for @src. This function will call the
++ * ::create_ringbuffer vmethod and will set @src as the parent of the returned
++ * buffer (see gst_object_set_parent()).
++ *
++ * Returns: The new ringbuffer of @src.
++ */
++GstRingBuffer *
++gst_base_audio_src_create_ringbuffer (GstBaseAudioSrc * src)
++{
++ GstBaseAudioSrcClass *bclass;
++ GstRingBuffer *buffer = NULL;
++
++ bclass = GST_BASE_AUDIO_SRC_GET_CLASS (src);
++ if (bclass->create_ringbuffer)
++ buffer = bclass->create_ringbuffer (src);
++
++ if (G_LIKELY (buffer))
++ gst_object_set_parent (GST_OBJECT_CAST (buffer), GST_OBJECT_CAST (src));
++
++ return buffer;
++}
++
++static GstStateChangeReturn
++gst_base_audio_src_change_state (GstElement * element,
++ GstStateChange transition)
++{
++ GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
++ GstBaseAudioSrc *src = GST_BASE_AUDIO_SRC (element);
++
++ switch (transition) {
++ case GST_STATE_CHANGE_NULL_TO_READY:
++ GST_DEBUG_OBJECT (src, "NULL->READY");
++ GST_OBJECT_LOCK (src);
++ if (src->ringbuffer == NULL) {
++ gst_audio_clock_reset (GST_AUDIO_CLOCK (src->clock), 0);
++ src->ringbuffer = gst_base_audio_src_create_ringbuffer (src);
++ }
++ GST_OBJECT_UNLOCK (src);
++ if (!gst_ring_buffer_open_device (src->ringbuffer))
++ goto open_failed;
++ break;
++ case GST_STATE_CHANGE_READY_TO_PAUSED:
++ GST_DEBUG_OBJECT (src, "READY->PAUSED");
++ src->next_sample = -1;
++ gst_ring_buffer_set_flushing (src->ringbuffer, FALSE);
++ gst_ring_buffer_may_start (src->ringbuffer, FALSE);
++ break;
++ case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
++ GST_DEBUG_OBJECT (src, "PAUSED->PLAYING");
++ gst_ring_buffer_may_start (src->ringbuffer, TRUE);
++ break;
++ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
++ GST_DEBUG_OBJECT (src, "PLAYING->PAUSED");
++ gst_ring_buffer_may_start (src->ringbuffer, FALSE);
++ gst_ring_buffer_pause (src->ringbuffer);
++ break;
++ case GST_STATE_CHANGE_PAUSED_TO_READY:
++ GST_DEBUG_OBJECT (src, "PAUSED->READY");
++ gst_ring_buffer_set_flushing (src->ringbuffer, TRUE);
++ break;
++ default:
++ break;
++ }
++
++ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
++
++ switch (transition) {
++ case GST_STATE_CHANGE_PAUSED_TO_READY:
++ GST_DEBUG_OBJECT (src, "PAUSED->READY");
++ gst_ring_buffer_release (src->ringbuffer);
++ break;
++ case GST_STATE_CHANGE_READY_TO_NULL:
++ GST_DEBUG_OBJECT (src, "READY->NULL");
++ gst_ring_buffer_close_device (src->ringbuffer);
++ GST_OBJECT_LOCK (src);
++ gst_object_unparent (GST_OBJECT_CAST (src->ringbuffer));
++ src->ringbuffer = NULL;
++ GST_OBJECT_UNLOCK (src);
++ break;
++ default:
++ break;
++ }
++
++ return ret;
++
++ /* ERRORS */
++open_failed:
++ {
++ /* subclass must post a meaningfull error message */
++ GST_DEBUG_OBJECT (src, "open failed");
++ return GST_STATE_CHANGE_FAILURE;
++ }
++
++}
--- /dev/null
--- /dev/null
++*************** gst_base_audio_src_create (GstBaseSrc * bsrc, guint64 offset, guint length,
++*** 865,935 ****
++ running_time_segment = running_time_sample / sps;
++
++ /* the segment currently read from the ringbuffer */
++- current_segment = sample / sps;
++-
++- /* the skew we have between running_time and the ringbuffertime */
++- segment_skew = running_time_segment - current_segment;
++-
++- GST_DEBUG_OBJECT (bsrc, "\n running_time = %" GST_TIME_FORMAT
++- "\n timestamp = %" GST_TIME_FORMAT
++- "\n running_time_segment = %d"
++- "\n current_segment = %d"
++- "\n segment_skew = %d",
++ GST_TIME_ARGS (running_time),
++ GST_TIME_ARGS (timestamp),
++- running_time_segment, current_segment, segment_skew);
++
++ /* Resync the ringbuffer if:
++- * 1. We are more than the length of the ringbuffer in front.
++- * The length of the ringbuffer then gets to dictate
++- * the threshold for what is concidered "too far ahead"
++- *
++- * 2. We are more than the length of the ringbuffer behind.
++ * The length of the ringbuffer then gets to dictate
++ * the threshold for what is concidered "too late"
++ *
++- * 3. If this is our first buffer.
++ * We know that we should catch up to running_time
++ * the first time we are ran.
++ */
++- if ((segment_skew <= -ringbuffer->spec.segtotal) ||
++- (segment_skew >= ringbuffer->spec.segtotal) ||
++- (current_segment == 0)) {
++- gint segments_written;
++- gint first_segment;
++- gint last_segment;
++- gint new_last_segment;
++ gint segment_diff;
++- gint new_first_segment;
++ guint64 new_sample;
++
++- /* we are going to say that the last segment was captured at the current time
++- (running_time), minus one segment of creation-latency in the ringbuffer.
++- This can be thought of as: The segment arrived in the ringbuffer at time X, and
++- that means it was created at time X - (one segment). */
++- new_last_segment = running_time_segment - 1;
++-
++- /* for better readablity */
++- first_segment = current_segment;
++-
++- /* get the amount of segments written from the device by now */
++- segments_written = g_atomic_int_get (&ringbuffer->segdone);
++-
++- /* subtract the base to segments_written to get the number of the
++- last written segment in the ringbuffer (one segment written = segment 0) */
++- last_segment = segments_written - ringbuffer->segbase - 1;
++-
++- /* we see how many segments the ringbuffer was timeshifted */
++- segment_diff = new_last_segment - last_segment;
++
++- /* we move the first segment an equal amount */
++- new_first_segment = first_segment + segment_diff;
++
++- /* and we also move the segmentbase the same amount */
++- ringbuffer->segbase -= segment_diff;
++
++ /* we calculate the new sample value */
++- new_sample = ((guint64) new_first_segment) * sps;
++
++ /* and get the relative time to this -> our new timestamp */
++ timestamp =
++--- 874,926 ----
++ running_time_segment = running_time_sample / sps;
++
++ /* the segment currently read from the ringbuffer */
+++ last_read_segment = sample / sps;
+++
+++ /* the skew we have between running_time and the ringbuffertime (last written to) */
+++ segment_skew = running_time_segment - last_written_segment;
+++
+++ GST_DEBUG_OBJECT (bsrc,
+++ "\n running_time = %" GST_TIME_FORMAT
+++ "\n timestamp = %" GST_TIME_FORMAT
+++ "\n running_time_segment = %d"
+++ "\n last_written_segment = %d"
+++ "\n segment_skew (running time segment - last_written_segment) = %d"
+++ "\n last_read_segment = %d",
++ GST_TIME_ARGS (running_time),
++ GST_TIME_ARGS (timestamp),
+++ running_time_segment,
+++ last_written_segment,
+++ segment_skew,
+++ last_read_segment);
++
++ /* Resync the ringbuffer if:
+++
+++ * 1. We are more than the length of the ringbuffer behind.
++ * The length of the ringbuffer then gets to dictate
++ * the threshold for what is concidered "too late"
++ *
+++ * 2. If this is our first buffer.
++ * We know that we should catch up to running_time
++ * the first time we are ran.
++ */
+++ if ((segment_skew >= ringbuffer->spec.segtotal) ||
+++ (last_read_segment == 0))
+++ {
+++ gint new_read_segment;
++ gint segment_diff;
++ guint64 new_sample;
++
+++ /* the difference between running_time and the last written segment */
+++ segment_diff = running_time_segment - last_written_segment;
++
+++ /* advance the ringbuffer */
+++ gst_ring_buffer_advance(ringbuffer, segment_diff);
++
+++ /* we move the new read segment to the last known written segment */
+++ new_read_segment = g_atomic_int_get (&ringbuffer->segdone) - ringbuffer->segbase;
++
++ /* we calculate the new sample value */
+++ new_sample = ((guint64) new_read_segment) * sps;
++
++ /* and get the relative time to this -> our new timestamp */
++ timestamp =
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
++ * 2005 Wim Taymans <wim@fluendo.com>
++ *
++ * gstaudioringbuffer.c: simple audio ringbuffer base class
++ *
++ * 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., 59 Temple Place - Suite 330,
++ * Boston, MA 02111-1307, USA.
++ */
++
++
++#include <string.h>
++
++#include "gstringbufferthread.h"
++
++GST_DEBUG_CATEGORY_STATIC (gst_ring_buffer_thread_debug);
++#define GST_CAT_DEFAULT gst_ring_buffer_thread_debug
++
++static void gst_ring_buffer_thread_class_init (GstRingBufferThreadClass *
++ klass);
++static void gst_ring_buffer_thread_init (GstRingBufferThread * ringbuffer,
++ GstRingBufferThreadClass * klass);
++static void gst_ring_buffer_thread_dispose (GObject * object);
++static void gst_ring_buffer_thread_finalize (GObject * object);
++
++static GstRingBufferClass *ring_parent_class = NULL;
++
++GType
++gst_ring_buffer_thread_get_type (void)
++{
++ static GType ringbuffer_type = 0;
++
++ if (!ringbuffer_type) {
++ static const GTypeInfo ringbuffer_info = {
++ sizeof (GstRingBufferThreadClass),
++ NULL,
++ NULL,
++ (GClassInitFunc) gst_ring_buffer_thread_class_init,
++ NULL,
++ NULL,
++ sizeof (GstRingBufferThread),
++ 0,
++ (GInstanceInitFunc) gst_ring_buffer_thread_init,
++ NULL
++ };
++
++ ringbuffer_type =
++ g_type_register_static (GST_TYPE_OBJECT, "GstRingBufferThread",
++ &ringbuffer_info, 0);
++
++ GST_DEBUG_CATEGORY_INIT (gst_ring_buffer_thread_debug, "ringbufferthread",
++ 0, "ringbuffer thread");
++ }
++ return ringbuffer_type;
++}
++
++static void
++gst_ring_buffer_thread_class_init (GstRingBufferThreadClass * klass)
++{
++ GObjectClass *gobject_class;
++
++ gobject_class = (GObjectClass *) klass;
++
++ ring_parent_class = g_type_class_peek_parent (klass);
++
++ gobject_class->dispose = gst_ring_buffer_thread_dispose;
++ gobject_class->finalize = gst_ring_buffer_thread_finalize;
++}
++
++typedef gint (*ProcessFunc) (GstAudioRingBuffer * buf, gpointer data,
++ guint length);
++
++/* this internal thread does nothing else but write samples to the audio device.
++ * It will write each segment in the ringbuffer and will update the play
++ * pointer.
++ * The start/stop methods control the thread.
++ */
++static void
++ring_buffer_thread_thread_func (GstRingBufferThread * thread)
++{
++ GstElement *parent = NULL;
++ GstMessage *message;
++ GValue val = { 0 };
++ GstAudioRingBuffer *capture, *playback;
++ ProcessFunc writefunc = NULL, readfunc = NULL;
++ gint preroll = 1;
++
++ GST_DEBUG_OBJECT (thread, "enter thread");
++
++ GST_OBJECT_LOCK (thread);
++ GST_DEBUG_OBJECT (thread, "signal wait");
++ GST_RING_BUFFER_THREAD_SIGNAL (thread);
++ if ((capture = thread->capture))
++ gst_object_ref (capture);
++ if ((playback = thread->playback))
++ gst_object_ref (playback);
++ GST_OBJECT_UNLOCK (thread);
++
++ if (capture)
++ readfunc = GST_AUDIO_RING_BUFFER_GET_CLASS (capture)->process;
++ if (playback)
++ writefunc = GST_AUDIO_RING_BUFFER_GET_CLASS (playback)->process;
++
++ if (parent) {
++ g_value_init (&val, G_TYPE_POINTER);
++ g_value_set_pointer (&val, thread->thread);
++ message = gst_message_new_stream_status (GST_OBJECT_CAST (thread),
++ GST_STREAM_STATUS_TYPE_ENTER, NULL);
++ gst_message_set_stream_status_object (message, &val);
++ GST_DEBUG_OBJECT (thread, "posting ENTER stream status");
++ gst_element_post_message (parent, message);
++ }
++
++ while (TRUE) {
++ gint left, processed;
++ guint8 *read_ptr, *write_ptr;
++ gint read_seg, write_seg;
++ gint read_len, write_len;
++ gboolean read_active, write_active;
++
++ if (playback)
++ write_active =
++ gst_ring_buffer_prepare_read (GST_RING_BUFFER_CAST (playback),
++ &write_seg, &write_ptr, &write_len);
++ else
++ write_active = FALSE;
++
++ if (playback) {
++ if (!write_active) {
++ write_ptr = GST_RING_BUFFER_CAST (playback)->empty_seg;
++ write_len = GST_RING_BUFFER_CAST (playback)->spec.segsize;
++ }
++
++ left = write_len;
++ do {
++ processed = writefunc (playback, write_ptr, left);
++ GST_LOG_OBJECT (thread, "written %d bytes of %d from segment %d",
++ processed, left, write_seg);
++ if (processed < 0 || processed > left) {
++ /* might not be critical, it e.g. happens when aborting playback */
++ GST_WARNING_OBJECT (thread,
++ "error writing data in %s (reason: %s), skipping segment (left: %d, processed: %d)",
++ GST_DEBUG_FUNCPTR_NAME (writefunc),
++ (errno > 1 ? g_strerror (errno) : "unknown"), left, processed);
++ break;
++ }
++ left -= processed;
++ write_ptr += processed;
++ } while (left > 0);
++
++ /* we wrote one segment */
++ gst_ring_buffer_advance (GST_RING_BUFFER_CAST (playback), 1);
++
++ if (preroll > 0) {
++ /* do not start reading until we have read enough data */
++ preroll--;
++ GST_DEBUG_OBJECT (thread, "need more preroll");
++ continue;
++ }
++ }
++
++
++ if (capture)
++ read_active =
++ gst_ring_buffer_prepare_read (GST_RING_BUFFER_CAST (capture),
++ &read_seg, &read_ptr, &read_len);
++ else
++ read_active = FALSE;
++
++ if (capture) {
++ left = read_len;
++ do {
++ processed = readfunc (capture, read_ptr, left);
++ GST_LOG_OBJECT (thread, "read %d bytes of %d from segment %d",
++ processed, left, read_seg);
++ if (processed < 0 || processed > left) {
++ /* might not be critical, it e.g. happens when aborting playback */
++ GST_WARNING_OBJECT (thread,
++ "error reading data in %s (reason: %s), skipping segment (left: %d, processed: %d)",
++ GST_DEBUG_FUNCPTR_NAME (readfunc),
++ (errno > 1 ? g_strerror (errno) : "unknown"), left, processed);
++ break;
++ }
++ left -= processed;
++ read_ptr += processed;
++ } while (left > 0);
++
++ if (read_active)
++ /* we read one segment */
++ gst_ring_buffer_advance (GST_RING_BUFFER_CAST (capture), 1);
++ }
++
++ if (!read_active && !write_active) {
++ GST_OBJECT_LOCK (thread);
++ if (!thread->running)
++ goto stop_running;
++ GST_DEBUG_OBJECT (thread, "signal wait");
++ GST_RING_BUFFER_THREAD_SIGNAL (thread);
++ GST_DEBUG_OBJECT (thread, "wait for action");
++ GST_RING_BUFFER_THREAD_WAIT (thread);
++ GST_DEBUG_OBJECT (thread, "got signal");
++ if (!thread->running)
++ goto stop_running;
++ GST_DEBUG_OBJECT (thread, "continue running");
++ GST_OBJECT_UNLOCK (thread);
++ }
++ }
++
++ /* Will never be reached */
++ g_assert_not_reached ();
++ return;
++
++ /* ERROR */
++stop_running:
++ {
++ GST_OBJECT_UNLOCK (thread);
++ GST_DEBUG_OBJECT (thread, "stop running, exit thread");
++ if (parent) {
++ message = gst_message_new_stream_status (GST_OBJECT_CAST (thread),
++ GST_STREAM_STATUS_TYPE_LEAVE, GST_ELEMENT_CAST (thread));
++ gst_message_set_stream_status_object (message, &val);
++ GST_DEBUG_OBJECT (thread, "posting LEAVE stream status");
++ gst_element_post_message (parent, message);
++ }
++ return;
++ }
++}
++
++static void
++gst_ring_buffer_thread_init (GstRingBufferThread * thread,
++ GstRingBufferThreadClass * g_class)
++{
++ thread->running = FALSE;
++ thread->cond = g_cond_new ();
++}
++
++static void
++gst_ring_buffer_thread_dispose (GObject * object)
++{
++ GstRingBufferThread *thread = GST_RING_BUFFER_THREAD_CAST (object);
++
++ GST_OBJECT_LOCK (thread);
++ if (thread->playback) {
++ gst_object_unref (thread->playback);
++ thread->playback = NULL;
++ }
++ if (thread->capture) {
++ gst_object_unref (thread->capture);
++ thread->capture = NULL;
++ }
++ GST_OBJECT_UNLOCK (thread);
++
++ G_OBJECT_CLASS (ring_parent_class)->dispose (object);
++}
++
++static void
++gst_ring_buffer_thread_finalize (GObject * object)
++{
++ GstRingBufferThread *thread = GST_RING_BUFFER_THREAD_CAST (object);
++
++ g_cond_free (thread->cond);
++
++ G_OBJECT_CLASS (ring_parent_class)->finalize (object);
++}
++
++gboolean
++gst_ring_buffer_thread_activate (GstRingBufferThread * thread, gboolean active)
++{
++ GError *error = NULL;
++
++ GST_OBJECT_LOCK (thread);
++ if (active) {
++ if (thread->active_count == 0) {
++ thread->running = TRUE;
++ GST_DEBUG_OBJECT (thread, "starting thread");
++ thread->thread =
++ g_thread_create ((GThreadFunc) ring_buffer_thread_thread_func, thread,
++ TRUE, &error);
++ if (!thread->thread || error != NULL)
++ goto thread_failed;
++
++ GST_DEBUG_OBJECT (thread, "waiting for thread");
++ /* the object lock is taken */
++ GST_RING_BUFFER_THREAD_WAIT (thread);
++ GST_DEBUG_OBJECT (thread, "thread is started");
++ }
++ thread->active_count++;
++ } else {
++ if (thread->active_count == 1) {
++ thread->running = FALSE;
++ GST_DEBUG_OBJECT (thread, "signal wait");
++ GST_RING_BUFFER_THREAD_SIGNAL (thread);
++ GST_OBJECT_UNLOCK (thread);
++
++ /* join the thread */
++ g_thread_join (thread->thread);
++
++ GST_OBJECT_LOCK (thread);
++ }
++ thread->active_count--;
++ }
++ GST_OBJECT_UNLOCK (thread);
++
++ return TRUE;
++
++ /* ERRORS */
++thread_failed:
++ {
++ if (error)
++ GST_ERROR_OBJECT (thread, "could not create thread %s", error->message);
++ else
++ GST_ERROR_OBJECT (thread, "could not create thread for unknown reason");
++ thread->running = FALSE;
++ GST_OBJECT_UNLOCK (thread);
++ return FALSE;
++ }
++}
++
++gboolean
++gst_ring_buffer_thread_set_ringbuffer (GstRingBufferThread * thread,
++ GstAudioRingBuffer * buf)
++{
++ GstAudioRingBuffer *old, **new;
++
++ g_return_val_if_fail (GST_IS_RING_BUFFER_THREAD (thread), FALSE);
++
++ if (buf->mode == GST_AUDIO_RING_BUFFER_MODE_PLAYBACK)
++ new = &thread->playback;
++ else
++ new = &thread->capture;
++
++ old = *new;
++ if (buf)
++ gst_object_ref (buf);
++ *new = buf;
++ if (old)
++ gst_object_unref (old);
++
++ return TRUE;
++}
++
++gboolean
++gst_ring_buffer_thread_start (GstRingBufferThread * thread)
++{
++ g_return_val_if_fail (GST_IS_RING_BUFFER_THREAD (thread), FALSE);
++
++ GST_RING_BUFFER_THREAD_SIGNAL (thread);
++
++ return TRUE;
++}
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
++ * 2005 Wim Taymans <wim@fluendo.com>
++ *
++ * gstaudioringbuffer.h:
++ *
++ * 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., 59 Temple Place - Suite 330,
++ * Boston, MA 02111-1307, USA.
++ */
++
++#ifndef __GST_RING_BUFFER_THREAD_H__
++#define __GST_RING_BUFFER_THREAD_H__
++
++#include <gst/gst.h>
++
++G_BEGIN_DECLS
++
++#define GST_TYPE_RING_BUFFER_THREAD (gst_ring_buffer_thread_get_type())
++#define GST_RING_BUFFER_THREAD(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RING_BUFFER_THREAD,GstRingBufferThread))
++#define GST_RING_BUFFER_THREAD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RING_BUFFER_THREAD,GstRingBufferThreadClass))
++#define GST_RING_BUFFER_THREAD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),GST_TYPE_RING_BUFFER_THREAD,GstRingBufferThreadClass))
++#define GST_IS_RING_BUFFER_THREAD(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RING_BUFFER_THREAD))
++#define GST_IS_RING_BUFFER_THREAD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RING_BUFFER_THREAD))
++#define GST_RING_BUFFER_THREAD_CAST(obj) ((GstRingBufferThread *)obj)
++
++typedef struct _GstRingBufferThread GstRingBufferThread;
++typedef struct _GstRingBufferThreadClass GstRingBufferThreadClass;
++
++#include <gst/audio/gstaudioringbuffer.h>
++
++#define GST_RING_BUFFER_THREAD_GET_COND(buf) (((GstRingBufferThread *)buf)->cond)
++#define GST_RING_BUFFER_THREAD_WAIT(buf) (g_cond_wait (GST_RING_BUFFER_THREAD_GET_COND (buf), GST_OBJECT_GET_LOCK (buf)))
++#define GST_RING_BUFFER_THREAD_SIGNAL(buf) (g_cond_signal (GST_RING_BUFFER_THREAD_GET_COND (buf)))
++#define GST_RING_BUFFER_THREAD_BROADCAST(buf)(g_cond_broadcast (GST_RING_BUFFER_THREAD_GET_COND (buf)))
++
++/**
++ * GstRingBufferThread:
++ *
++ * Opaque #GstRingBufferThread.
++ */
++struct _GstRingBufferThread {
++ GstObject parent;
++
++ gint active_count;
++
++ /*< private >*/ /* with LOCK */
++ GThread *thread;
++ gboolean running;
++ GCond *cond;
++
++ GstAudioRingBuffer *playback;
++ GstAudioRingBuffer *capture;
++
++ /*< private >*/
++ gpointer _gst_reserved[GST_PADDING];
++};
++
++/**
++ * GstRingBufferThreadClass:
++ * @parent_class: the parent class structure.
++ *
++ * #GstRingBufferThread class. Override the vmethods to implement functionality.
++ */
++struct _GstRingBufferThreadClass {
++ GstObjectClass parent_class;
++
++ /*< private >*/
++ gpointer _gst_reserved[GST_PADDING];
++};
++
++GType gst_ring_buffer_thread_get_type(void);
++
++gboolean gst_ring_buffer_thread_set_ringbuffer (GstRingBufferThread *thread, GstAudioRingBuffer *buf);
++
++gboolean gst_ring_buffer_thread_activate (GstRingBufferThread *thread, gboolean active);
++
++gboolean gst_ring_buffer_thread_start (GstRingBufferThread *thread);
++
++G_END_DECLS
++
++#endif /* __GST_RING_BUFFER_THREAD_H__ */
--- /dev/null
--- /dev/null
++? foo
++Index: Makefile.am
++===================================================================
++RCS file: /cvs/gstreamer/gst-plugins-base/gst-libs/gst/cdda/Makefile.am,v
++retrieving revision 1.4
++diff -u -p -u -p -r1.4 Makefile.am
++--- Makefile.am 3 Apr 2008 06:39:27 -0000 1.4
+++++ Makefile.am 21 Aug 2008 14:17:21 -0000
++@@ -1,9 +1,7 @@
++ lib_LTLIBRARIES = libgstcdda-@GST_MAJORMINOR@.la
++
++ libgstcdda_@GST_MAJORMINOR@_la_SOURCES = \
++- gstcddabasesrc.c \
++- sha1.c \
++- sha1.h
+++ gstcddabasesrc.c
++
++ libgstcdda_@GST_MAJORMINOR@includedir = $(includedir)/gstreamer-@GST_MAJORMINOR@/gst/cdda
++ libgstcdda_@GST_MAJORMINOR@include_HEADERS = \
++Index: gstcddabasesrc.c
++===================================================================
++RCS file: /cvs/gstreamer/gst-plugins-base/gst-libs/gst/cdda/gstcddabasesrc.c,v
++retrieving revision 1.19
++diff -u -p -u -p -r1.19 gstcddabasesrc.c
++--- gstcddabasesrc.c 28 May 2008 15:48:33 -0000 1.19
+++++ gstcddabasesrc.c 21 Aug 2008 14:17:21 -0000
++@@ -1084,36 +1084,35 @@ cddb_sum (gint n)
++ return ret;
++ }
++
++-#include "sha1.h"
++-
++ static void
++ gst_cddabasesrc_calculate_musicbrainz_discid (GstCddaBaseSrc * src)
++ {
++ GString *s;
++- SHA_INFO sha;
+++ GChecksum *sha;
++ guchar digest[20];
++ gchar *ptr;
++ gchar tmp[9];
++ gulong i;
++ guint leadout_sector;
+++ gsize digest_len;
++
++ s = g_string_new (NULL);
++
++ leadout_sector = src->tracks[src->num_tracks - 1].end + 1 + CD_MSF_OFFSET;
++
++ /* generate SHA digest */
++- sha_init (&sha);
+++ sha = g_checksum_new (G_CHECKSUM_SHA1);
++ g_snprintf (tmp, sizeof (tmp), "%02X", src->tracks[0].num);
++ g_string_append_printf (s, "%02X", src->tracks[0].num);
++- sha_update (&sha, (SHA_BYTE *) tmp, 2);
+++ g_checksum_update (sha, (guchar *) tmp, 2);
++
++ g_snprintf (tmp, sizeof (tmp), "%02X", src->tracks[src->num_tracks - 1].num);
++ g_string_append_printf (s, " %02X", src->tracks[src->num_tracks - 1].num);
++- sha_update (&sha, (SHA_BYTE *) tmp, 2);
+++ g_checksum_update (sha, (guchar *) tmp, 2);
++
++ g_snprintf (tmp, sizeof (tmp), "%08X", leadout_sector);
++ g_string_append_printf (s, " %08X", leadout_sector);
++- sha_update (&sha, (SHA_BYTE *) tmp, 8);
+++ g_checksum_update (sha, (guchar *) tmp, 8);
++
++ for (i = 0; i < 99; i++) {
++ if (i < src->num_tracks) {
++@@ -1121,15 +1120,17 @@ gst_cddabasesrc_calculate_musicbrainz_di
++
++ g_snprintf (tmp, sizeof (tmp), "%08X", frame_offset);
++ g_string_append_printf (s, " %08X", frame_offset);
++- sha_update (&sha, (SHA_BYTE *) tmp, 8);
+++ g_checksum_update (sha, (guchar *) tmp, 8);
++ } else {
++- sha_update (&sha, (SHA_BYTE *) "00000000", 8);
+++ g_checksum_update (sha, (guchar *) "00000000", 8);
++ }
++ }
++- sha_final (digest, &sha);
+++ digest_len = 20;
+++ g_checksum_get_digest (sha, (guint8 *) &digest, &digest_len);
++
++ /* re-encode to base64 */
++- ptr = g_base64_encode (digest, 20);
+++ ptr = g_base64_encode (digest, digest_len);
+++ g_checksum_free (sha);
++ i = strlen (ptr);
++
++ g_assert (i < sizeof (src->mb_discid) + 1);
++Index: sha1.c
++===================================================================
++RCS file: sha1.c
++diff -N sha1.c
++--- sha1.c 27 Feb 2008 10:42:08 -0000 1.2
+++++ /dev/null 1 Jan 1970 00:00:00 -0000
++@@ -1,450 +0,0 @@
++-/* (PD) 2001 The Bitzi Corporation
++- * Please see file COPYING or http://bitzi.com/publicdomain
++- * for more info.
++- *
++- * NIST Secure Hash Algorithm
++- * heavily modified by Uwe Hollerbach <uh@alumni.caltech edu>
++- * from Peter C. Gutmann's implementation as found in
++- * Applied Cryptography by Bruce Schneier
++- * Further modifications to include the "UNRAVEL" stuff, below
++- *
++- * This code is in the public domain
++- *
++- * $Id: sha1.c,v 1.2 2008-02-27 10:42:08 slomo Exp $
++- */
++-
++-#ifdef HAVE_CONFIG_H
++-#include "config.h"
++-#endif
++-#include <glib.h>
++-#define SHA_BYTE_ORDER G_BYTE_ORDER
++-
++-#include <string.h>
++-#include "sha1.h"
++-
++-/* UNRAVEL should be fastest & biggest */
++-/* UNROLL_LOOPS should be just as big, but slightly slower */
++-/* both undefined should be smallest and slowest */
++-
++-#define UNRAVEL
++-/* #define UNROLL_LOOPS */
++-
++-/* SHA f()-functions */
++-
++-#define f1(x,y,z) ((x & y) | (~x & z))
++-#define f2(x,y,z) (x ^ y ^ z)
++-#define f3(x,y,z) ((x & y) | (x & z) | (y & z))
++-#define f4(x,y,z) (x ^ y ^ z)
++-
++-/* SHA constants */
++-
++-#define CONST1 0x5a827999L
++-#define CONST2 0x6ed9eba1L
++-#define CONST3 0x8f1bbcdcL
++-#define CONST4 0xca62c1d6L
++-
++-/* truncate to 32 bits -- should be a null op on 32-bit machines */
++-
++-#define T32(x) ((x) & 0xffffffffL)
++-
++-/* 32-bit rotate */
++-
++-#define R32(x,n) T32(((x << n) | (x >> (32 - n))))
++-
++-/* the generic case, for when the overall rotation is not unraveled */
++-
++-#define FG(n) \
++- T = T32(R32(A,5) + f##n(B,C,D) + E + *WP++ + CONST##n); \
++- E = D; D = C; C = R32(B,30); B = A; A = T
++-
++-/* specific cases, for when the overall rotation is unraveled */
++-
++-#define FA(n) \
++- T = T32(R32(A,5) + f##n(B,C,D) + E + *WP++ + CONST##n); B = R32(B,30)
++-
++-#define FB(n) \
++- E = T32(R32(T,5) + f##n(A,B,C) + D + *WP++ + CONST##n); A = R32(A,30)
++-
++-#define FC(n) \
++- D = T32(R32(E,5) + f##n(T,A,B) + C + *WP++ + CONST##n); T = R32(T,30)
++-
++-#define FD(n) \
++- C = T32(R32(D,5) + f##n(E,T,A) + B + *WP++ + CONST##n); E = R32(E,30)
++-
++-#define FE(n) \
++- B = T32(R32(C,5) + f##n(D,E,T) + A + *WP++ + CONST##n); D = R32(D,30)
++-
++-#define FT(n) \
++- A = T32(R32(B,5) + f##n(C,D,E) + T + *WP++ + CONST##n); C = R32(C,30)
++-
++-/* do SHA transformation */
++-
++-static void
++-sha_transform (SHA_INFO * sha_info)
++-{
++- int i;
++- SHA_BYTE *dp;
++- SHA_LONG T, A, B, C, D, E, W[80], *WP;
++-
++- dp = sha_info->data;
++-
++-/*
++-the following makes sure that at least one code block below is
++-traversed or an error is reported, without the necessity for nested
++-preprocessor if/else/endif blocks, which are a great pain in the
++-nether regions of the anatomy...
++-*/
++-#undef SWAP_DONE
++-
++-#if (SHA_BYTE_ORDER == 1234)
++-#define SWAP_DONE
++- for (i = 0; i < 16; ++i) {
++- memcpy (&T, dp, sizeof (SHA_LONG));
++- dp += 4;
++- W[i] = ((T << 24) & 0xff000000) | ((T << 8) & 0x00ff0000) |
++- ((T >> 8) & 0x0000ff00) | ((T >> 24) & 0x000000ff);
++- }
++-#endif /* SHA_BYTE_ORDER == 1234 */
++-
++-#if (SHA_BYTE_ORDER == 4321)
++-#define SWAP_DONE
++- for (i = 0; i < 16; ++i) {
++- memcpy (&T, dp, sizeof (SHA_LONG));
++- dp += 4;
++- W[i] = T32 (T);
++- }
++-#endif /* SHA_BYTE_ORDER == 4321 */
++-
++-#if (SHA_BYTE_ORDER == 12345678)
++-#define SWAP_DONE
++- for (i = 0; i < 16; i += 2) {
++- memcpy (&T, dp, sizeof (SHA_LONG));
++- dp += 8;
++- W[i] = ((T << 24) & 0xff000000) | ((T << 8) & 0x00ff0000) |
++- ((T >> 8) & 0x0000ff00) | ((T >> 24) & 0x000000ff);
++- T >>= 32;
++- W[i + 1] = ((T << 24) & 0xff000000) | ((T << 8) & 0x00ff0000) |
++- ((T >> 8) & 0x0000ff00) | ((T >> 24) & 0x000000ff);
++- }
++-#endif /* SHA_BYTE_ORDER == 12345678 */
++-
++-#if (SHA_BYTE_ORDER == 87654321)
++-#define SWAP_DONE
++- for (i = 0; i < 16; i += 2) {
++- memcpy (&T, dp, sizeof (SHA_LONG));
++- dp += 8;
++- W[i] = T32 (T >> 32);
++- W[i + 1] = T32 (T);
++- }
++-#endif /* SHA_BYTE_ORDER == 87654321 */
++-
++-#ifndef SWAP_DONE
++-#error Unknown byte order -- you need to add code here
++-#endif /* SWAP_DONE */
++-
++- for (i = 16; i < 80; ++i) {
++- W[i] = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16];
++-#if (SHA_VERSION == 1)
++- W[i] = R32 (W[i], 1);
++-#endif /* SHA_VERSION */
++- }
++- A = sha_info->digest[0];
++- B = sha_info->digest[1];
++- C = sha_info->digest[2];
++- D = sha_info->digest[3];
++- E = sha_info->digest[4];
++- WP = W;
++-#ifdef UNRAVEL
++- FA (1);
++- FB (1);
++- FC (1);
++- FD (1);
++- FE (1);
++- FT (1);
++- FA (1);
++- FB (1);
++- FC (1);
++- FD (1);
++- FE (1);
++- FT (1);
++- FA (1);
++- FB (1);
++- FC (1);
++- FD (1);
++- FE (1);
++- FT (1);
++- FA (1);
++- FB (1);
++- FC (2);
++- FD (2);
++- FE (2);
++- FT (2);
++- FA (2);
++- FB (2);
++- FC (2);
++- FD (2);
++- FE (2);
++- FT (2);
++- FA (2);
++- FB (2);
++- FC (2);
++- FD (2);
++- FE (2);
++- FT (2);
++- FA (2);
++- FB (2);
++- FC (2);
++- FD (2);
++- FE (3);
++- FT (3);
++- FA (3);
++- FB (3);
++- FC (3);
++- FD (3);
++- FE (3);
++- FT (3);
++- FA (3);
++- FB (3);
++- FC (3);
++- FD (3);
++- FE (3);
++- FT (3);
++- FA (3);
++- FB (3);
++- FC (3);
++- FD (3);
++- FE (3);
++- FT (3);
++- FA (4);
++- FB (4);
++- FC (4);
++- FD (4);
++- FE (4);
++- FT (4);
++- FA (4);
++- FB (4);
++- FC (4);
++- FD (4);
++- FE (4);
++- FT (4);
++- FA (4);
++- FB (4);
++- FC (4);
++- FD (4);
++- FE (4);
++- FT (4);
++- FA (4);
++- FB (4);
++- sha_info->digest[0] = T32 (sha_info->digest[0] + E);
++- sha_info->digest[1] = T32 (sha_info->digest[1] + T);
++- sha_info->digest[2] = T32 (sha_info->digest[2] + A);
++- sha_info->digest[3] = T32 (sha_info->digest[3] + B);
++- sha_info->digest[4] = T32 (sha_info->digest[4] + C);
++-#else /* !UNRAVEL */
++-#ifdef UNROLL_LOOPS
++- FG (1);
++- FG (1);
++- FG (1);
++- FG (1);
++- FG (1);
++- FG (1);
++- FG (1);
++- FG (1);
++- FG (1);
++- FG (1);
++- FG (1);
++- FG (1);
++- FG (1);
++- FG (1);
++- FG (1);
++- FG (1);
++- FG (1);
++- FG (1);
++- FG (1);
++- FG (1);
++- FG (2);
++- FG (2);
++- FG (2);
++- FG (2);
++- FG (2);
++- FG (2);
++- FG (2);
++- FG (2);
++- FG (2);
++- FG (2);
++- FG (2);
++- FG (2);
++- FG (2);
++- FG (2);
++- FG (2);
++- FG (2);
++- FG (2);
++- FG (2);
++- FG (2);
++- FG (2);
++- FG (3);
++- FG (3);
++- FG (3);
++- FG (3);
++- FG (3);
++- FG (3);
++- FG (3);
++- FG (3);
++- FG (3);
++- FG (3);
++- FG (3);
++- FG (3);
++- FG (3);
++- FG (3);
++- FG (3);
++- FG (3);
++- FG (3);
++- FG (3);
++- FG (3);
++- FG (3);
++- FG (4);
++- FG (4);
++- FG (4);
++- FG (4);
++- FG (4);
++- FG (4);
++- FG (4);
++- FG (4);
++- FG (4);
++- FG (4);
++- FG (4);
++- FG (4);
++- FG (4);
++- FG (4);
++- FG (4);
++- FG (4);
++- FG (4);
++- FG (4);
++- FG (4);
++- FG (4);
++-#else /* !UNROLL_LOOPS */
++- for (i = 0; i < 20; ++i) {
++- FG (1);
++- }
++- for (i = 20; i < 40; ++i) {
++- FG (2);
++- }
++- for (i = 40; i < 60; ++i) {
++- FG (3);
++- }
++- for (i = 60; i < 80; ++i) {
++- FG (4);
++- }
++-#endif /* !UNROLL_LOOPS */
++- sha_info->digest[0] = T32 (sha_info->digest[0] + A);
++- sha_info->digest[1] = T32 (sha_info->digest[1] + B);
++- sha_info->digest[2] = T32 (sha_info->digest[2] + C);
++- sha_info->digest[3] = T32 (sha_info->digest[3] + D);
++- sha_info->digest[4] = T32 (sha_info->digest[4] + E);
++-#endif /* !UNRAVEL */
++-}
++-
++-/* initialize the SHA digest */
++-
++-void
++-sha_init (SHA_INFO * sha_info)
++-{
++- sha_info->digest[0] = 0x67452301L;
++- sha_info->digest[1] = 0xefcdab89L;
++- sha_info->digest[2] = 0x98badcfeL;
++- sha_info->digest[3] = 0x10325476L;
++- sha_info->digest[4] = 0xc3d2e1f0L;
++- sha_info->count_lo = 0L;
++- sha_info->count_hi = 0L;
++- sha_info->local = 0;
++-}
++-
++-/* update the SHA digest */
++-
++-void
++-sha_update (SHA_INFO * sha_info, SHA_BYTE * buffer, int count)
++-{
++- int i;
++- SHA_LONG clo;
++-
++- clo = T32 (sha_info->count_lo + ((SHA_LONG) count << 3));
++- if (clo < sha_info->count_lo) {
++- ++sha_info->count_hi;
++- }
++- sha_info->count_lo = clo;
++- sha_info->count_hi += (SHA_LONG) count >> 29;
++- if (sha_info->local) {
++- i = SHA_BLOCKSIZE - sha_info->local;
++- if (i > count) {
++- i = count;
++- }
++- memcpy (((SHA_BYTE *) sha_info->data) + sha_info->local, buffer, i);
++- count -= i;
++- buffer += i;
++- sha_info->local += i;
++- if (sha_info->local == SHA_BLOCKSIZE) {
++- sha_transform (sha_info);
++- } else {
++- return;
++- }
++- }
++- while (count >= SHA_BLOCKSIZE) {
++- memcpy (sha_info->data, buffer, SHA_BLOCKSIZE);
++- buffer += SHA_BLOCKSIZE;
++- count -= SHA_BLOCKSIZE;
++- sha_transform (sha_info);
++- }
++- memcpy (sha_info->data, buffer, count);
++- sha_info->local = count;
++-}
++-
++-/* finish computing the SHA digest */
++-
++-void
++-sha_final (unsigned char digest[20], SHA_INFO * sha_info)
++-{
++- int count;
++- SHA_LONG lo_bit_count, hi_bit_count;
++-
++- lo_bit_count = sha_info->count_lo;
++- hi_bit_count = sha_info->count_hi;
++- count = (int) ((lo_bit_count >> 3) & 0x3f);
++- ((SHA_BYTE *) sha_info->data)[count++] = 0x80;
++- if (count > SHA_BLOCKSIZE - 8) {
++- memset (((SHA_BYTE *) sha_info->data) + count, 0, SHA_BLOCKSIZE - count);
++- sha_transform (sha_info);
++- memset ((SHA_BYTE *) sha_info->data, 0, SHA_BLOCKSIZE - 8);
++- } else {
++- memset (((SHA_BYTE *) sha_info->data) + count, 0,
++- SHA_BLOCKSIZE - 8 - count);
++- }
++- sha_info->data[56] = (unsigned char) ((hi_bit_count >> 24) & 0xff);
++- sha_info->data[57] = (unsigned char) ((hi_bit_count >> 16) & 0xff);
++- sha_info->data[58] = (unsigned char) ((hi_bit_count >> 8) & 0xff);
++- sha_info->data[59] = (unsigned char) ((hi_bit_count >> 0) & 0xff);
++- sha_info->data[60] = (unsigned char) ((lo_bit_count >> 24) & 0xff);
++- sha_info->data[61] = (unsigned char) ((lo_bit_count >> 16) & 0xff);
++- sha_info->data[62] = (unsigned char) ((lo_bit_count >> 8) & 0xff);
++- sha_info->data[63] = (unsigned char) ((lo_bit_count >> 0) & 0xff);
++- sha_transform (sha_info);
++- digest[0] = (unsigned char) ((sha_info->digest[0] >> 24) & 0xff);
++- digest[1] = (unsigned char) ((sha_info->digest[0] >> 16) & 0xff);
++- digest[2] = (unsigned char) ((sha_info->digest[0] >> 8) & 0xff);
++- digest[3] = (unsigned char) ((sha_info->digest[0]) & 0xff);
++- digest[4] = (unsigned char) ((sha_info->digest[1] >> 24) & 0xff);
++- digest[5] = (unsigned char) ((sha_info->digest[1] >> 16) & 0xff);
++- digest[6] = (unsigned char) ((sha_info->digest[1] >> 8) & 0xff);
++- digest[7] = (unsigned char) ((sha_info->digest[1]) & 0xff);
++- digest[8] = (unsigned char) ((sha_info->digest[2] >> 24) & 0xff);
++- digest[9] = (unsigned char) ((sha_info->digest[2] >> 16) & 0xff);
++- digest[10] = (unsigned char) ((sha_info->digest[2] >> 8) & 0xff);
++- digest[11] = (unsigned char) ((sha_info->digest[2]) & 0xff);
++- digest[12] = (unsigned char) ((sha_info->digest[3] >> 24) & 0xff);
++- digest[13] = (unsigned char) ((sha_info->digest[3] >> 16) & 0xff);
++- digest[14] = (unsigned char) ((sha_info->digest[3] >> 8) & 0xff);
++- digest[15] = (unsigned char) ((sha_info->digest[3]) & 0xff);
++- digest[16] = (unsigned char) ((sha_info->digest[4] >> 24) & 0xff);
++- digest[17] = (unsigned char) ((sha_info->digest[4] >> 16) & 0xff);
++- digest[18] = (unsigned char) ((sha_info->digest[4] >> 8) & 0xff);
++- digest[19] = (unsigned char) ((sha_info->digest[4]) & 0xff);
++-}
++Index: sha1.h
++===================================================================
++RCS file: sha1.h
++diff -N sha1.h
++--- sha1.h 13 Dec 2007 10:10:35 -0000 1.2
+++++ /dev/null 1 Jan 1970 00:00:00 -0000
++@@ -1,62 +0,0 @@
++-/* NIST Secure Hash Algorithm */
++-/* heavily modified by Uwe Hollerbach <uh@alumni.caltech edu> */
++-/* from Peter C. Gutmann's implementation as found in */
++-/* Applied Cryptography by Bruce Schneier */
++-/* This code is in the public domain */
++-/* $Id: sha1.h,v 1.2 2007-12-13 10:10:35 tpm Exp $ */
++-
++-#ifndef __GST_CDDA_SHA_H__
++-#define __GST_CDDA_SHA_H__
++-
++-#include <stdlib.h>
++-#include <stdio.h>
++-
++-/* Useful defines & typedefs */
++-typedef unsigned char SHA_BYTE; /* 8-bit quantity */
++-typedef unsigned long SHA_LONG; /* 32-or-more-bit quantity */
++-
++-#define SHA_BLOCKSIZE 64
++-#define SHA_DIGESTSIZE 20
++-
++-typedef struct {
++- SHA_LONG digest[5]; /* message digest */
++- SHA_LONG count_lo, count_hi; /* 64-bit bit count */
++- SHA_BYTE data[SHA_BLOCKSIZE]; /* SHA data buffer */
++- int local; /* unprocessed amount in data */
++-} SHA_INFO;
++-
++-#define sha_init __gst_cdda_sha_init
++-#define sha_update __gst_cdda_sha_update
++-#define sha_final __gst_cdda_sha_final
++-
++-void sha_init(SHA_INFO *);
++-void sha_update(SHA_INFO *, SHA_BYTE *, int);
++-void sha_final(unsigned char [20], SHA_INFO *);
++-
++-#define SHA_VERSION 1
++-
++-#ifdef HAVE_CONFIG_H
++-#include "config.h"
++-
++-
++-#ifdef WORDS_BIGENDIAN
++-# if SIZEOF_LONG == 4
++-# define SHA_BYTE_ORDER 4321
++-# elif SIZEOF_LONG == 8
++-# define SHA_BYTE_ORDER 87654321
++-# endif
++-#else
++-# if SIZEOF_LONG == 4
++-# define SHA_BYTE_ORDER 1234
++-# elif SIZEOF_LONG == 8
++-# define SHA_BYTE_ORDER 12345678
++-# endif
++-#endif
++-
++-#else
++-
++-#define SHA_BYTE_ORDER 1234
++-
++-#endif
++-
++-#endif /* __GST_CDDA_SHA_H__ */
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) 1999 Erik Walthinsen <omega@cse.ogi.edu>
++ * Copyright (C) 2005 Tim-Philipp Müller <tim centricular net>
++ *
++ * 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., 59 Temple Place - Suite 330,
++ * Boston, MA 02111-1307, USA.
++ */
++
++/* TODO:
++ *
++ * - in ::start(), we want to post a tags message with an array or a list
++ * of tagslists of all tracks, so that applications know at least the
++ * number of tracks and all track durations immediately without having
++ * to do any querying. We have to decide what type and name to use for
++ * this array of track taglists.
++ *
++ * - FIX cddb discid calculation algorithm for mixed mode CDs - do we use
++ * offsets and duration of ALL tracks (data + audio) for the CDDB ID
++ * calculation, or only audio tracks?
++ *
++ * - Do we really need properties for the TOC bias/offset stuff? Wouldn't
++ * environment variables make much more sense? Do we need this at all
++ * (does it only affect ancient hardware?)
++ */
++
++/**
++ * SECTION:gstcddabasesrc
++ * @short_description: Base class for CD digital audio (CDDA) sources
++ *
++ * <refsect2>
++ * <para>
++ * Provides a base class for CDDA sources, which handles things like seeking,
++ * querying, discid calculation, tags, and buffer timestamping.
++ * </para>
++ * <title>Using GstCddaBaseSrc-based elements in applications</title>
++ * <para>
++ * GstCddaBaseSrc registers two #GstFormat<!-- -->s of its own, namely
++ * the "track" format and the "sector" format. Applications will usually
++ * only find the "track" format interesting. You can retrieve that #GstFormat
++ * for use in seek events or queries with gst_format_get_by_nick("track").
++ * </para>
++ * <para>
++ * In order to query the number of tracks, for example, an application would
++ * set the CDDA source element to READY or PAUSED state and then query the
++ * the number of tracks via gst_element_query_duration() using the track
++ * format acquired above. Applications can query the currently playing track
++ * in the same way.
++ * </para>
++ * <para>
++ * Alternatively, applications may retrieve the currently playing track and
++ * the total number of tracks from the taglist that will posted on the bus
++ * whenever the CD is opened or the currently playing track changes. The
++ * taglist will contain GST_TAG_TRACK_NUMBER and GST_TAG_TRACK_COUNT tags.
++ * </para>
++ * <para>
++ * Applications playing back CD audio using playbin and cdda://n URIs should
++ * issue a seek command in track format to change between tracks, rather than
++ * setting a new cdda://n+1 URI on playbin (as setting a new URI on playbin
++ * involves closing and re-opening the CD device, which is much much slower).
++ * </para>
++ * <title>Tags and meta-information</title>
++ * <para>
++ * CDDA sources will automatically emit a number of tags, details about which
++ * can be found in the libgsttag documentation. Those tags are:
++ * #GST_TAG_CDDA_CDDB_DISCID, #GST_TAG_CDDA_CDDB_DISCID_FULL,
++ * #GST_TAG_CDDA_MUSICBRAINZ_DISCID, #GST_TAG_CDDA_MUSICBRAINZ_DISCID_FULL,
++ * among others.
++ * </para>
++ * </refsect2>
++ */
++
++#ifdef HAVE_CONFIG_H
++#include "config.h"
++#endif
++
++#include <string.h>
++#include <stdlib.h> /* for strtol */
++
++#include "gstcddabasesrc.h"
++#include "gst/gst-i18n-plugin.h"
++
++GST_DEBUG_CATEGORY_STATIC (gst_cdda_base_src_debug);
++#define GST_CAT_DEFAULT gst_cdda_base_src_debug
++
++#define DEFAULT_DEVICE "/dev/cdrom"
++
++#define CD_FRAMESIZE_RAW (2352)
++
++#define SECTORS_PER_SECOND (75)
++#define SECTORS_PER_MINUTE (75*60)
++#define SAMPLES_PER_SECTOR (CD_FRAMESIZE_RAW >> 2)
++#define TIME_INTERVAL_FROM_SECTORS(sectors) ((SAMPLES_PER_SECTOR * sectors * GST_SECOND) / 44100)
++#define SECTORS_FROM_TIME_INTERVAL(dtime) (dtime * 44100 / (SAMPLES_PER_SECTOR * GST_SECOND))
++
++enum
++{
++ ARG_0,
++ ARG_MODE,
++ ARG_DEVICE,
++ ARG_TRACK,
++ ARG_TOC_OFFSET,
++ ARG_TOC_BIAS
++};
++
++static void gst_cdda_base_src_get_property (GObject * object, guint prop_id,
++ GValue * value, GParamSpec * pspec);
++static void gst_cdda_base_src_set_property (GObject * object, guint prop_id,
++ const GValue * value, GParamSpec * pspec);
++static void gst_cdda_base_src_finalize (GObject * obj);
++static const GstQueryType *gst_cdda_base_src_get_query_types (GstPad * pad);
++static gboolean gst_cdda_base_src_query (GstBaseSrc * src, GstQuery * query);
++static gboolean gst_cdda_base_src_handle_event (GstBaseSrc * basesrc,
++ GstEvent * event);
++static gboolean gst_cdda_base_src_do_seek (GstBaseSrc * basesrc,
++ GstSegment * segment);
++static void gst_cdda_base_src_setup_interfaces (GType type);
++static gboolean gst_cdda_base_src_start (GstBaseSrc * basesrc);
++static gboolean gst_cdda_base_src_stop (GstBaseSrc * basesrc);
++static GstFlowReturn gst_cdda_base_src_create (GstPushSrc * pushsrc,
++ GstBuffer ** buf);
++static gboolean gst_cdda_base_src_is_seekable (GstBaseSrc * basesrc);
++static void gst_cdda_base_src_update_duration (GstCddaBaseSrc * src);
++static void gst_cdda_base_src_set_index (GstElement * src, GstIndex * index);
++static GstIndex *gst_cdda_base_src_get_index (GstElement * src);
++
++GST_BOILERPLATE_FULL (GstCddaBaseSrc, gst_cdda_base_src, GstPushSrc,
++ GST_TYPE_PUSH_SRC, gst_cdda_base_src_setup_interfaces);
++
++#define SRC_CAPS \
++ "audio/x-raw-int, " \
++ "endianness = (int) BYTE_ORDER, " \
++ "signed = (boolean) true, " \
++ "width = (int) 16, " \
++ "depth = (int) 16, " \
++ "rate = (int) 44100, " \
++ "channels = (int) 2" \
++
++static GstStaticPadTemplate gst_cdda_base_src_src_template =
++GST_STATIC_PAD_TEMPLATE ("src",
++ GST_PAD_SRC,
++ GST_PAD_ALWAYS,
++ GST_STATIC_CAPS (SRC_CAPS)
++ );
++
++/* our two formats */
++static GstFormat track_format;
++static GstFormat sector_format;
++
++GType
++gst_cdda_base_src_mode_get_type (void)
++{
++ static GType mode_type; /* 0 */
++ static const GEnumValue modes[] = {
++ {GST_CDDA_BASE_SRC_MODE_NORMAL, "Stream consists of a single track",
++ "normal"},
++ {GST_CDDA_BASE_SRC_MODE_CONTINUOUS, "Stream consists of the whole disc",
++ "continuous"},
++ {0, NULL, NULL}
++ };
++
++ if (mode_type == 0)
++ mode_type = g_enum_register_static ("GstCddaBaseSrcMode", modes);
++
++ return mode_type;
++}
++
++static void
++gst_cdda_base_src_base_init (gpointer g_class)
++{
++ GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
++
++ gst_element_class_add_pad_template (element_class,
++ gst_static_pad_template_get (&gst_cdda_base_src_src_template));
++
++ /* our very own formats */
++ track_format = gst_format_register ("track", "CD track");
++ sector_format = gst_format_register ("sector", "CD sector");
++
++ /* register CDDA tags */
++ gst_tag_register_musicbrainz_tags ();
++
++#if 0
++ ///// FIXME: what type to use here? ///////
++ gst_tag_register (GST_TAG_CDDA_TRACK_TAGS, GST_TAG_FLAG_META, GST_TYPE_TAG_LIST, "track-tags", "CDDA taglist for one track", gst_tag_merge_use_first); ///////////// FIXME: right function??? ///////
++#endif
++
++ GST_DEBUG_CATEGORY_INIT (gst_cdda_base_src_debug, "cddabasesrc", 0,
++ "CDDA Base Source");
++}
++
++static void
++gst_cdda_base_src_class_init (GstCddaBaseSrcClass * klass)
++{
++ GstElementClass *element_class;
++ GstPushSrcClass *pushsrc_class;
++ GstBaseSrcClass *basesrc_class;
++ GObjectClass *gobject_class;
++
++ gobject_class = (GObjectClass *) klass;
++ element_class = (GstElementClass *) klass;
++ basesrc_class = (GstBaseSrcClass *) klass;
++ pushsrc_class = (GstPushSrcClass *) klass;
++
++ gobject_class->set_property = gst_cdda_base_src_set_property;
++ gobject_class->get_property = gst_cdda_base_src_get_property;
++ gobject_class->finalize = gst_cdda_base_src_finalize;
++
++ g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_DEVICE,
++ g_param_spec_string ("device", "Device", "CD device location",
++ NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
++ g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_MODE,
++ g_param_spec_enum ("mode", "Mode", "Mode", GST_TYPE_CDDA_BASE_SRC_MODE,
++ GST_CDDA_BASE_SRC_MODE_NORMAL,
++ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
++
++ g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TRACK,
++ g_param_spec_uint ("track", "Track", "Track", 1, 99, 1,
++ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
++
++#if 0
++ /* Do we really need this toc adjustment stuff as properties? does the user
++ * have a chance to set it in practice, e.g. when using sound-juicer, rb,
++ * totem, whatever? Shouldn't we rather use environment variables
++ * for this? (tpm) */
++
++ g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TOC_OFFSET,
++ g_param_spec_int ("toc-offset", "Table of contents offset",
++ "Add <n> sectors to the values reported", G_MININT, G_MAXINT, 0,
++ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
++ g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TOC_BIAS,
++ g_param_spec_boolean ("toc-bias", "Table of contents bias",
++ "Assume that the beginning offset of track 1 as reported in the TOC "
++ "will be addressed as LBA 0. Necessary for some Toshiba drives to "
++ "get track boundaries", FALSE,
++ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
++#endif
++
++ element_class->set_index = GST_DEBUG_FUNCPTR (gst_cdda_base_src_set_index);
++ element_class->get_index = GST_DEBUG_FUNCPTR (gst_cdda_base_src_get_index);
++
++ basesrc_class->start = GST_DEBUG_FUNCPTR (gst_cdda_base_src_start);
++ basesrc_class->stop = GST_DEBUG_FUNCPTR (gst_cdda_base_src_stop);
++ basesrc_class->query = GST_DEBUG_FUNCPTR (gst_cdda_base_src_query);
++ basesrc_class->event = GST_DEBUG_FUNCPTR (gst_cdda_base_src_handle_event);
++ basesrc_class->do_seek = GST_DEBUG_FUNCPTR (gst_cdda_base_src_do_seek);
++ basesrc_class->is_seekable =
++ GST_DEBUG_FUNCPTR (gst_cdda_base_src_is_seekable);
++
++ pushsrc_class->create = GST_DEBUG_FUNCPTR (gst_cdda_base_src_create);
++}
++
++static void
++gst_cdda_base_src_init (GstCddaBaseSrc * src, GstCddaBaseSrcClass * klass)
++{
++ gst_pad_set_query_type_function (GST_BASE_SRC_PAD (src),
++ GST_DEBUG_FUNCPTR (gst_cdda_base_src_get_query_types));
++
++ /* we're not live and we operate in time */
++ gst_base_src_set_format (GST_BASE_SRC (src), GST_FORMAT_TIME);
++ gst_base_src_set_live (GST_BASE_SRC (src), FALSE);
++
++ src->device = NULL;
++ src->mode = GST_CDDA_BASE_SRC_MODE_NORMAL;
++ src->uri_track = -1;
++}
++
++static void
++gst_cdda_base_src_finalize (GObject * obj)
++{
++ GstCddaBaseSrc *cddasrc = GST_CDDA_BASE_SRC (obj);
++
++ g_free (cddasrc->uri);
++ g_free (cddasrc->device);
++
++ G_OBJECT_CLASS (parent_class)->finalize (obj);
++}
++
++static void
++gst_cdda_base_src_set_property (GObject * object, guint prop_id,
++ const GValue * value, GParamSpec * pspec)
++{
++ GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (object);
++
++ GST_OBJECT_LOCK (src);
++
++ switch (prop_id) {
++ case ARG_MODE:{
++ src->mode = g_value_get_enum (value);
++ break;
++ }
++ case ARG_DEVICE:{
++ const gchar *dev = g_value_get_string (value);
++
++ g_free (src->device);
++ if (dev && *dev) {
++ src->device = g_strdup (dev);
++ } else {
++ src->device = NULL;
++ }
++ break;
++ }
++ case ARG_TRACK:{
++ guint track = g_value_get_uint (value);
++
++ if (src->num_tracks > 0 && track > src->num_tracks) {
++ g_warning ("Invalid track %u", track);
++ } else if (track > 0 && src->tracks != NULL) {
++ src->cur_sector = src->tracks[track - 1].start;
++ src->uri_track = track;
++ } else {
++ src->uri_track = track; /* seek will be done in start() */
++ }
++ break;
++ }
++ case ARG_TOC_OFFSET:{
++ src->toc_offset = g_value_get_int (value);
++ break;
++ }
++ case ARG_TOC_BIAS:{
++ src->toc_bias = g_value_get_boolean (value);
++ break;
++ }
++ default:{
++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++ break;
++ }
++ }
++
++ GST_OBJECT_UNLOCK (src);
++}
++
++static void
++gst_cdda_base_src_get_property (GObject * object, guint prop_id,
++ GValue * value, GParamSpec * pspec)
++{
++ GstCddaBaseSrcClass *klass = GST_CDDA_BASE_SRC_GET_CLASS (object);
++ GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (object);
++
++ GST_OBJECT_LOCK (src);
++
++ switch (prop_id) {
++ case ARG_MODE:
++ g_value_set_enum (value, src->mode);
++ break;
++ case ARG_DEVICE:{
++ if (src->device == NULL && klass->get_default_device != NULL) {
++ gchar *d = klass->get_default_device (src);
++
++ if (d != NULL) {
++ g_value_set_string (value, DEFAULT_DEVICE);
++ g_free (d);
++ break;
++ }
++ }
++ if (src->device == NULL)
++ g_value_set_string (value, DEFAULT_DEVICE);
++ else
++ g_value_set_string (value, src->device);
++ break;
++ }
++ case ARG_TRACK:{
++ if (src->num_tracks <= 0 && src->uri_track > 0) {
++ g_value_set_uint (value, src->uri_track);
++ } else {
++ g_value_set_uint (value, src->cur_track + 1);
++ }
++ break;
++ }
++ case ARG_TOC_OFFSET:
++ g_value_set_int (value, src->toc_offset);
++ break;
++ case ARG_TOC_BIAS:
++ g_value_set_boolean (value, src->toc_bias);
++ break;
++ default:{
++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++ break;
++ }
++ }
++
++ GST_OBJECT_UNLOCK (src);
++}
++
++static gint
++gst_cdda_base_src_get_track_from_sector (GstCddaBaseSrc * src, gint sector)
++{
++ gint i;
++
++ for (i = 0; i < src->num_tracks; ++i) {
++ if (sector >= src->tracks[i].start && sector <= src->tracks[i].end)
++ return i;
++ }
++ return -1;
++}
++
++static const GstQueryType *
++gst_cdda_base_src_get_query_types (GstPad * pad)
++{
++ static const GstQueryType src_query_types[] = {
++ GST_QUERY_DURATION,
++ GST_QUERY_POSITION,
++ GST_QUERY_CONVERT,
++ 0
++ };
++
++ return src_query_types;
++}
++
++static gboolean
++gst_cdda_base_src_convert (GstCddaBaseSrc * src, GstFormat src_format,
++ gint64 src_val, GstFormat dest_format, gint64 * dest_val)
++{
++ gboolean started;
++
++ GST_LOG_OBJECT (src, "converting value %" G_GINT64_FORMAT " from %s into %s",
++ src_val, gst_format_get_name (src_format),
++ gst_format_get_name (dest_format));
++
++ if (src_format == dest_format) {
++ *dest_val = src_val;
++ return TRUE;
++ }
++
++ started = GST_OBJECT_FLAG_IS_SET (GST_BASE_SRC (src), GST_BASE_SRC_STARTED);
++
++ if (src_format == track_format) {
++ if (!started)
++ goto not_started;
++ if (src_val < 0 || src_val >= src->num_tracks) {
++ GST_DEBUG_OBJECT (src, "track number %d out of bounds", (gint) src_val);
++ goto wrong_value;
++ }
++ src_format = GST_FORMAT_DEFAULT;
++ src_val = src->tracks[src_val].start * SAMPLES_PER_SECTOR;
++ } else if (src_format == sector_format) {
++ src_format = GST_FORMAT_DEFAULT;
++ src_val = src_val * SAMPLES_PER_SECTOR;
++ }
++
++ if (src_format == dest_format) {
++ *dest_val = src_val;
++ goto done;
++ }
++
++ switch (src_format) {
++ case GST_FORMAT_BYTES:
++ /* convert to samples (4 bytes per sample) */
++ src_val = src_val >> 2;
++ /* fallthrough */
++ case GST_FORMAT_DEFAULT:{
++ switch (dest_format) {
++ case GST_FORMAT_BYTES:{
++ if (src_val < 0) {
++ GST_DEBUG_OBJECT (src, "sample source value negative");
++ goto wrong_value;
++ }
++ *dest_val = src_val << 2; /* 4 bytes per sample */
++ break;
++ }
++ case GST_FORMAT_TIME:{
++ *dest_val = gst_util_uint64_scale_int (src_val, GST_SECOND, 44100);
++ break;
++ }
++ default:{
++ gint64 sector = src_val / SAMPLES_PER_SECTOR;
++
++ if (dest_format == sector_format) {
++ *dest_val = sector;
++ } else if (dest_format == track_format) {
++ if (!started)
++ goto not_started;
++ *dest_val = gst_cdda_base_src_get_track_from_sector (src, sector);
++ } else {
++ goto unknown_format;
++ }
++ break;
++ }
++ }
++ break;
++ }
++ case GST_FORMAT_TIME:{
++ gint64 sample_offset;
++
++ if (src_val == GST_CLOCK_TIME_NONE) {
++ GST_DEBUG_OBJECT (src, "source time value invalid");
++ goto wrong_value;
++ }
++
++ sample_offset = gst_util_uint64_scale_int (src_val, 44100, GST_SECOND);
++ switch (dest_format) {
++ case GST_FORMAT_BYTES:{
++ *dest_val = sample_offset << 2; /* 4 bytes per sample */
++ break;
++ }
++ case GST_FORMAT_DEFAULT:{
++ *dest_val = sample_offset;
++ break;
++ }
++ default:{
++ gint64 sector = sample_offset / SAMPLES_PER_SECTOR;
++
++ if (dest_format == sector_format) {
++ *dest_val = sector;
++ } else if (dest_format == track_format) {
++ if (!started)
++ goto not_started;
++ *dest_val = gst_cdda_base_src_get_track_from_sector (src, sector);
++ } else {
++ goto unknown_format;
++ }
++ break;
++ }
++ }
++ break;
++ }
++ default:{
++ goto unknown_format;
++ }
++ }
++
++done:
++ {
++ GST_LOG_OBJECT (src, "returning %" G_GINT64_FORMAT, *dest_val);
++ return TRUE;
++ }
++
++unknown_format:
++ {
++ GST_DEBUG_OBJECT (src, "conversion failed: %s", "unsupported format");
++ return FALSE;
++ }
++
++wrong_value:
++ {
++ GST_DEBUG_OBJECT (src, "conversion failed: %s",
++ "source value not within allowed range");
++ return FALSE;
++ }
++
++not_started:
++ {
++ GST_DEBUG_OBJECT (src, "conversion failed: %s",
++ "cannot do this conversion, device not open");
++ return FALSE;
++ }
++}
++
++static gboolean
++gst_cdda_base_src_query (GstBaseSrc * basesrc, GstQuery * query)
++{
++ GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (basesrc);
++ gboolean started;
++
++ started = GST_OBJECT_FLAG_IS_SET (basesrc, GST_BASE_SRC_STARTED);
++
++ GST_LOG_OBJECT (src, "handling %s query",
++ gst_query_type_get_name (GST_QUERY_TYPE (query)));
++
++ switch (GST_QUERY_TYPE (query)) {
++ case GST_QUERY_DURATION:{
++ GstFormat dest_format;
++ gint64 dest_val;
++ guint sectors;
++
++ gst_query_parse_duration (query, &dest_format, NULL);
++
++ if (!started)
++ return FALSE;
++
++ g_assert (src->tracks != NULL);
++
++ if (dest_format == track_format) {
++ GST_LOG_OBJECT (src, "duration: %d tracks", src->num_tracks);
++ gst_query_set_duration (query, track_format, src->num_tracks);
++ return TRUE;
++ }
++
++ if (src->cur_track < 0 || src->cur_track >= src->num_tracks)
++ return FALSE;
++
++ if (src->mode == GST_CDDA_BASE_SRC_MODE_NORMAL) {
++ sectors = src->tracks[src->cur_track].end -
++ src->tracks[src->cur_track].start + 1;
++ } else {
++ sectors = src->tracks[src->num_tracks - 1].end -
++ src->tracks[0].start + 1;
++ }
++
++ /* ... and convert into final format */
++ if (!gst_cdda_base_src_convert (src, sector_format, sectors,
++ dest_format, &dest_val)) {
++ return FALSE;
++ }
++
++ gst_query_set_duration (query, dest_format, dest_val);
++
++ GST_LOG ("duration: %u sectors, %" G_GINT64_FORMAT " in format %s",
++ sectors, dest_val, gst_format_get_name (dest_format));
++ break;
++ }
++ case GST_QUERY_POSITION:{
++ GstFormat dest_format;
++ gint64 pos_sector;
++ gint64 dest_val;
++
++ gst_query_parse_position (query, &dest_format, NULL);
++
++ if (!started)
++ return FALSE;
++
++ g_assert (src->tracks != NULL);
++
++ if (dest_format == track_format) {
++ GST_LOG_OBJECT (src, "position: track %d", src->cur_track);
++ gst_query_set_position (query, track_format, src->cur_track);
++ return TRUE;
++ }
++
++ if (src->cur_track < 0 || src->cur_track >= src->num_tracks)
++ return FALSE;
++
++ if (src->mode == GST_CDDA_BASE_SRC_MODE_NORMAL) {
++ pos_sector = src->cur_sector - src->tracks[src->cur_track].start;
++ } else {
++ pos_sector = src->cur_sector - src->tracks[0].start;
++ }
++
++ if (!gst_cdda_base_src_convert (src, sector_format, pos_sector,
++ dest_format, &dest_val)) {
++ return FALSE;
++ }
++
++ gst_query_set_position (query, dest_format, dest_val);
++
++ GST_LOG ("position: sector %u, %" G_GINT64_FORMAT " in format %s",
++ (guint) pos_sector, dest_val, gst_format_get_name (dest_format));
++ break;
++ }
++ case GST_QUERY_CONVERT:{
++ GstFormat src_format, dest_format;
++ gint64 src_val, dest_val;
++
++ gst_query_parse_convert (query, &src_format, &src_val, &dest_format,
++ NULL);
++
++ if (!gst_cdda_base_src_convert (src, src_format, src_val, dest_format,
++ &dest_val)) {
++ return FALSE;
++ }
++
++ gst_query_set_convert (query, src_format, src_val, dest_format, dest_val);
++ break;
++ }
++ default:{
++ GST_DEBUG_OBJECT (src, "unhandled query, chaining up to parent class");
++ return GST_BASE_SRC_CLASS (parent_class)->query (basesrc, query);
++ }
++ }
++
++ return TRUE;
++}
++
++static gboolean
++gst_cdda_base_src_is_seekable (GstBaseSrc * basesrc)
++{
++ return TRUE;
++}
++
++static gboolean
++gst_cdda_base_src_do_seek (GstBaseSrc * basesrc, GstSegment * segment)
++{
++ GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (basesrc);
++ gint64 seek_sector;
++
++ GST_DEBUG_OBJECT (src, "segment %" GST_TIME_FORMAT "-%" GST_TIME_FORMAT,
++ GST_TIME_ARGS (segment->start), GST_TIME_ARGS (segment->stop));
++
++ if (!gst_cdda_base_src_convert (src, GST_FORMAT_TIME, segment->start,
++ sector_format, &seek_sector)) {
++ GST_WARNING_OBJECT (src, "conversion failed");
++ return FALSE;
++ }
++
++ /* we should only really be called when open */
++ g_assert (src->cur_track >= 0 && src->cur_track < src->num_tracks);
++
++ switch (src->mode) {
++ case GST_CDDA_BASE_SRC_MODE_NORMAL:
++ seek_sector += src->tracks[src->cur_track].start;
++ break;
++ case GST_CDDA_BASE_SRC_MODE_CONTINUOUS:
++ seek_sector += src->tracks[0].start;
++ break;
++ default:
++ g_return_val_if_reached (FALSE);
++ }
++
++ src->cur_sector = (gint) seek_sector;
++
++ GST_DEBUG_OBJECT (src, "seek'd to sector %d", src->cur_sector);
++
++ return TRUE;
++}
++
++static gboolean
++gst_cdda_base_src_handle_track_seek (GstCddaBaseSrc * src, gdouble rate,
++ GstSeekFlags flags, GstSeekType start_type, gint64 start,
++ GstSeekType stop_type, gint64 stop)
++{
++ GstBaseSrc *basesrc = GST_BASE_SRC (src);
++ GstEvent *event;
++
++ if ((flags & GST_SEEK_FLAG_SEGMENT) == GST_SEEK_FLAG_SEGMENT) {
++ gint64 start_time = -1;
++ gint64 stop_time = -1;
++
++ if (src->mode != GST_CDDA_BASE_SRC_MODE_CONTINUOUS) {
++ GST_DEBUG_OBJECT (src, "segment seek in track format is only "
++ "supported in CONTINUOUS mode, not in mode %d", src->mode);
++ return FALSE;
++ }
++
++ switch (start_type) {
++ case GST_SEEK_TYPE_SET:
++ if (!gst_cdda_base_src_convert (src, track_format, start,
++ GST_FORMAT_TIME, &start_time)) {
++ GST_DEBUG_OBJECT (src, "cannot convert track %d to time",
++ (gint) start);
++ return FALSE;
++ }
++ break;
++ case GST_SEEK_TYPE_END:
++ if (!gst_cdda_base_src_convert (src, track_format,
++ src->num_tracks - start - 1, GST_FORMAT_TIME, &start_time)) {
++ GST_DEBUG_OBJECT (src, "cannot convert track %d to time",
++ (gint) start);
++ return FALSE;
++ }
++ start_type = GST_SEEK_TYPE_SET;
++ break;
++ case GST_SEEK_TYPE_NONE:
++ start_time = -1;
++ break;
++ default:
++ g_return_val_if_reached (FALSE);
++ }
++
++ switch (stop_type) {
++ case GST_SEEK_TYPE_SET:
++ if (!gst_cdda_base_src_convert (src, track_format, stop,
++ GST_FORMAT_TIME, &stop_time)) {
++ GST_DEBUG_OBJECT (src, "cannot convert track %d to time",
++ (gint) stop);
++ return FALSE;
++ }
++ break;
++ case GST_SEEK_TYPE_END:
++ if (!gst_cdda_base_src_convert (src, track_format,
++ src->num_tracks - stop - 1, GST_FORMAT_TIME, &stop_time)) {
++ GST_DEBUG_OBJECT (src, "cannot convert track %d to time",
++ (gint) stop);
++ return FALSE;
++ }
++ stop_type = GST_SEEK_TYPE_SET;
++ break;
++ case GST_SEEK_TYPE_NONE:
++ stop_time = -1;
++ break;
++ default:
++ g_return_val_if_reached (FALSE);
++ }
++
++ GST_LOG_OBJECT (src, "seek segment %" GST_TIME_FORMAT "-%" GST_TIME_FORMAT,
++ GST_TIME_ARGS (start_time), GST_TIME_ARGS (stop_time));
++
++ /* send fake segment seek event in TIME format to
++ * base class, which will hopefully handle the rest */
++
++ event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, start_type,
++ start_time, stop_type, stop_time);
++
++ return GST_BASE_SRC_CLASS (parent_class)->event (basesrc, event);
++ }
++
++ /* not a segment seek */
++
++ if (start_type == GST_SEEK_TYPE_NONE) {
++ GST_LOG_OBJECT (src, "start seek type is NONE, nothing to do");
++ return TRUE;
++ }
++
++ if (stop_type != GST_SEEK_TYPE_NONE) {
++ GST_WARNING_OBJECT (src, "ignoring stop seek type (expected NONE)");
++ }
++
++ if (start < 0 || start >= src->num_tracks) {
++ GST_DEBUG_OBJECT (src, "invalid track %" G_GINT64_FORMAT, start);
++ return FALSE;
++ }
++
++ GST_DEBUG_OBJECT (src, "seeking to track %" G_GINT64_FORMAT, start + 1);
++
++ src->cur_sector = src->tracks[start].start;
++ GST_DEBUG_OBJECT (src, "starting at sector %d", src->cur_sector);
++
++ if (src->cur_track != start) {
++ src->cur_track = (gint) start;
++ src->uri_track = -1;
++ src->prev_track = -1;
++
++ gst_cdda_base_src_update_duration (src);
++ } else {
++ GST_DEBUG_OBJECT (src, "is current track, just seeking back to start");
++ }
++
++ /* send fake segment seek event in TIME format to
++ * base class (so we get a newsegment etc.) */
++ event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags,
++ GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, -1);
++
++ return GST_BASE_SRC_CLASS (parent_class)->event (basesrc, event);
++}
++
++static gboolean
++gst_cdda_base_src_handle_event (GstBaseSrc * basesrc, GstEvent * event)
++{
++ GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (basesrc);
++ gboolean ret = FALSE;
++
++ GST_LOG_OBJECT (src, "handling %s event", GST_EVENT_TYPE_NAME (event));
++
++ switch (GST_EVENT_TYPE (event)) {
++ case GST_EVENT_SEEK:{
++ GstSeekType start_type, stop_type;
++ GstSeekFlags flags;
++ GstFormat format;
++ gdouble rate;
++ gint64 start, stop;
++
++ if (!GST_OBJECT_FLAG_IS_SET (basesrc, GST_BASE_SRC_STARTED)) {
++ GST_DEBUG_OBJECT (src, "seek failed: device not open");
++ break;
++ }
++
++ gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start,
++ &stop_type, &stop);
++
++ if (format == sector_format) {
++ GST_DEBUG_OBJECT (src, "seek in sector format not supported");
++ break;
++ }
++
++ if (format == track_format) {
++ ret = gst_cdda_base_src_handle_track_seek (src, rate, flags,
++ start_type, start, stop_type, stop);
++ } else {
++ GST_LOG_OBJECT (src, "let base class handle seek in %s format",
++ gst_format_get_name (format));
++ event = gst_event_ref (event);
++ ret = GST_BASE_SRC_CLASS (parent_class)->event (basesrc, event);
++ }
++ break;
++ }
++ default:{
++ GST_LOG_OBJECT (src, "let base class handle event");
++ ret = GST_BASE_SRC_CLASS (parent_class)->event (basesrc, event);
++ break;
++ }
++ }
++
++ return ret;
++}
++
++static GstURIType
++gst_cdda_base_src_uri_get_type (void)
++{
++ return GST_URI_SRC;
++}
++
++static gchar **
++gst_cdda_base_src_uri_get_protocols (void)
++{
++ static gchar *protocols[] = { "cdda", NULL };
++
++ return protocols;
++}
++
++static const gchar *
++gst_cdda_base_src_uri_get_uri (GstURIHandler * handler)
++{
++ GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (handler);
++
++ GST_OBJECT_LOCK (src);
++
++ g_free (src->uri);
++
++ if (GST_OBJECT_FLAG_IS_SET (GST_BASE_SRC (src), GST_BASE_SRC_STARTED)) {
++ src->uri = g_strdup_printf ("cdda://%d", src->uri_track);
++ } else {
++ src->uri = g_strdup ("cdda://1");
++ }
++
++ GST_OBJECT_UNLOCK (src);
++
++ return src->uri;
++}
++
++/* Note: gst_element_make_from_uri() might call us with just 'cdda://' as
++ * URI and expects us to return TRUE then (and this might be in any state) */
++
++static gboolean
++gst_cdda_base_src_uri_set_uri (GstURIHandler * handler, const gchar * uri)
++{
++ GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (handler);
++ gchar *protocol, *location;
++
++ GST_OBJECT_LOCK (src);
++
++ protocol = gst_uri_get_protocol (uri);
++ if (!protocol || strcmp (protocol, "cdda") != 0) {
++ g_free (protocol);
++ goto failed;
++ }
++ g_free (protocol);
++
++ location = gst_uri_get_location (uri);
++ if (location == NULL || *location == '\0') {
++ g_free (location);
++ location = g_strdup ("1");
++ }
++
++ src->uri_track = strtol (location, NULL, 10);
++ g_free (location);
++
++ if (src->uri_track == 0)
++ goto failed;
++
++ if (src->num_tracks > 0
++ && src->tracks != NULL && src->uri_track > src->num_tracks)
++ goto failed;
++
++ if (src->uri_track > 0 && src->tracks != NULL) {
++ GST_OBJECT_UNLOCK (src);
++
++ gst_pad_send_event (GST_BASE_SRC_PAD (src),
++ gst_event_new_seek (1.0, track_format, GST_SEEK_FLAG_FLUSH,
++ GST_SEEK_TYPE_SET, src->uri_track - 1, GST_SEEK_TYPE_NONE, -1));
++ } else {
++ /* seek will be done in start() */
++ GST_OBJECT_UNLOCK (src);
++ }
++
++ GST_LOG_OBJECT (handler, "successfully handled uri '%s'", uri);
++
++ return TRUE;
++
++failed:
++ {
++ GST_OBJECT_UNLOCK (src);
++ GST_DEBUG_OBJECT (src, "cannot handle URI '%s'", uri);
++ return FALSE;
++ }
++}
++
++static void
++gst_cdda_base_src_uri_handler_init (gpointer g_iface, gpointer iface_data)
++{
++ GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
++
++ iface->get_type = gst_cdda_base_src_uri_get_type;
++ iface->get_uri = gst_cdda_base_src_uri_get_uri;
++ iface->set_uri = gst_cdda_base_src_uri_set_uri;
++ iface->get_protocols = gst_cdda_base_src_uri_get_protocols;
++}
++
++static void
++gst_cdda_base_src_setup_interfaces (GType type)
++{
++ static const GInterfaceInfo urihandler_info = {
++ gst_cdda_base_src_uri_handler_init,
++ NULL,
++ NULL,
++ };
++
++ g_type_add_interface_static (type, GST_TYPE_URI_HANDLER, &urihandler_info);
++}
++
++/**
++ * gst_cdda_base_src_add_track:
++ * @src: a #GstCddaBaseSrc
++ * @track: address of #GstCddaBaseSrcTrack to add
++ *
++ * CDDA sources use this function from their start vfunc to announce the
++ * available data and audio tracks to the base source class. The caller
++ * should allocate @track on the stack, the base source will do a shallow
++ * copy of the structure (and take ownership of the taglist if there is one).
++ *
++ * Returns: FALSE on error, otherwise TRUE.
++ */
++
++gboolean
++gst_cdda_base_src_add_track (GstCddaBaseSrc * src, GstCddaBaseSrcTrack * track)
++{
++ g_return_val_if_fail (GST_IS_CDDA_BASE_SRC (src), FALSE);
++ g_return_val_if_fail (track != NULL, FALSE);
++ g_return_val_if_fail (track->num > 0, FALSE);
++
++ GST_DEBUG_OBJECT (src, "adding track %2u (%2u) [%6u-%6u] [%5s], tags: %"
++ GST_PTR_FORMAT, src->num_tracks + 1, track->num, track->start,
++ track->end, (track->is_audio) ? "AUDIO" : "DATA ", track->tags);
++
++ if (src->num_tracks > 0) {
++ guint end_of_previous_track = src->tracks[src->num_tracks - 1].end;
++
++ if (track->start <= end_of_previous_track) {
++ GST_WARNING ("track %2u overlaps with previous tracks", track->num);
++ return FALSE;
++ }
++ }
++
++ GST_OBJECT_LOCK (src);
++
++ ++src->num_tracks;
++ src->tracks = g_renew (GstCddaBaseSrcTrack, src->tracks, src->num_tracks);
++ src->tracks[src->num_tracks - 1] = *track;
++
++ GST_OBJECT_UNLOCK (src);
++
++ return TRUE;
++}
++
++static void
++gst_cdda_base_src_update_duration (GstCddaBaseSrc * src)
++{
++ GstBaseSrc *basesrc;
++ GstFormat format;
++ gint64 duration;
++
++ basesrc = GST_BASE_SRC (src);
++
++ format = GST_FORMAT_TIME;
++ if (gst_pad_query_duration (GST_BASE_SRC_PAD (src), &format, &duration)) {
++ gst_segment_set_duration (&basesrc->segment, GST_FORMAT_TIME, duration);
++ } else {
++ gst_segment_set_duration (&basesrc->segment, GST_FORMAT_TIME, -1);
++ duration = GST_CLOCK_TIME_NONE;
++ }
++
++ gst_element_post_message (GST_ELEMENT (src),
++ gst_message_new_duration (GST_OBJECT (src), GST_FORMAT_TIME, -1));
++
++ GST_LOG_OBJECT (src, "duration updated to %" GST_TIME_FORMAT,
++ GST_TIME_ARGS (duration));
++}
++
++#define CD_MSF_OFFSET 150
++
++/* the cddb hash function */
++static guint
++cddb_sum (gint n)
++{
++ guint ret;
++
++ ret = 0;
++ while (n > 0) {
++ ret += (n % 10);
++ n /= 10;
++ }
++ return ret;
++}
++
++#include "sha1.h"
++
++static void
++gst_cddabasesrc_calculate_musicbrainz_discid (GstCddaBaseSrc * src)
++{
++ GString *s;
++ SHA_INFO sha;
++ guchar digest[20];
++ gchar *ptr;
++ gchar tmp[9];
++ gulong i;
++ guint leadout_sector;
++
++ s = g_string_new (NULL);
++
++ leadout_sector = src->tracks[src->num_tracks - 1].end + 1 + CD_MSF_OFFSET;
++
++ /* generate SHA digest */
++ sha_init (&sha);
++ g_snprintf (tmp, sizeof (tmp), "%02X", src->tracks[0].num);
++ g_string_append_printf (s, "%02X", src->tracks[0].num);
++ sha_update (&sha, (SHA_BYTE *) tmp, 2);
++
++ g_snprintf (tmp, sizeof (tmp), "%02X", src->tracks[src->num_tracks - 1].num);
++ g_string_append_printf (s, " %02X", src->tracks[src->num_tracks - 1].num);
++ sha_update (&sha, (SHA_BYTE *) tmp, 2);
++
++ g_snprintf (tmp, sizeof (tmp), "%08X", leadout_sector);
++ g_string_append_printf (s, " %08X", leadout_sector);
++ sha_update (&sha, (SHA_BYTE *) tmp, 8);
++
++ for (i = 0; i < 99; i++) {
++ if (i < src->num_tracks) {
++ guint frame_offset = src->tracks[i].start + CD_MSF_OFFSET;
++
++ g_snprintf (tmp, sizeof (tmp), "%08X", frame_offset);
++ g_string_append_printf (s, " %08X", frame_offset);
++ sha_update (&sha, (SHA_BYTE *) tmp, 8);
++ } else {
++ sha_update (&sha, (SHA_BYTE *) "00000000", 8);
++ }
++ }
++ sha_final (digest, &sha);
++
++ /* re-encode to base64 */
++ ptr = g_base64_encode (digest, 20);
++ i = strlen (ptr);
++
++ g_assert (i < sizeof (src->mb_discid) + 1);
++ memcpy (src->mb_discid, ptr, i);
++ src->mb_discid[i] = '\0';
++ free (ptr);
++
++ /* Replace '/', '+' and '=' by '_', '.' and '-' as specified on
++ * http://musicbrainz.org/doc/DiscIDCalculation
++ */
++ for (ptr = src->mb_discid; *ptr != '\0'; ptr++) {
++ if (*ptr == '/')
++ *ptr = '_';
++ else if (*ptr == '+')
++ *ptr = '.';
++ else if (*ptr == '=')
++ *ptr = '-';
++ }
++
++ GST_DEBUG_OBJECT (src, "musicbrainz-discid = %s", src->mb_discid);
++ GST_DEBUG_OBJECT (src, "musicbrainz-discid-full = %s", s->str);
++
++ gst_tag_list_add (src->tags, GST_TAG_MERGE_REPLACE,
++ GST_TAG_CDDA_MUSICBRAINZ_DISCID, src->mb_discid,
++ GST_TAG_CDDA_MUSICBRAINZ_DISCID_FULL, s->str, NULL);
++
++ g_string_free (s, TRUE);
++}
++
++static void
++lba_to_msf (guint sector, guint * p_m, guint * p_s, guint * p_f, guint * p_secs)
++{
++ guint m, s, f;
++
++ m = sector / SECTORS_PER_MINUTE;
++ sector = sector % SECTORS_PER_MINUTE;
++ s = sector / SECTORS_PER_SECOND;
++ f = sector % SECTORS_PER_SECOND;
++
++ if (p_m)
++ *p_m = m;
++ if (p_s)
++ *p_s = s;
++ if (p_f)
++ *p_f = f;
++ if (p_secs)
++ *p_secs = s + (m * 60);
++}
++
++static void
++gst_cdda_base_src_calculate_cddb_id (GstCddaBaseSrc * src)
++{
++ GString *s;
++ guint first_sector = 0, last_sector = 0;
++ guint start_secs, end_secs, secs, len_secs;
++ guint total_secs, num_audio_tracks;
++ guint id, t, i;
++
++ id = 0;
++ total_secs = 0;
++ num_audio_tracks = 0;
++
++ /* FIXME: do we use offsets and duration of ALL tracks (data + audio)
++ * for the CDDB ID calculation, or only audio tracks? */
++ for (i = 0; i < src->num_tracks; ++i) {
++ if (1) { /* src->tracks[i].is_audio) { */
++ if (num_audio_tracks == 0) {
++ first_sector = src->tracks[i].start + CD_MSF_OFFSET;
++ }
++ last_sector = src->tracks[i].end + CD_MSF_OFFSET + 1;
++ ++num_audio_tracks;
++
++ lba_to_msf (src->tracks[i].start + CD_MSF_OFFSET, NULL, NULL, NULL,
++ &secs);
++
++ len_secs = (src->tracks[i].end - src->tracks[i].start + 1) / 75;
++
++ GST_DEBUG_OBJECT (src, "track %02u: lsn %6u (%02u:%02u), "
++ "length: %u seconds (%02u:%02u)",
++ num_audio_tracks, src->tracks[i].start + CD_MSF_OFFSET,
++ secs / 60, secs % 60, len_secs, len_secs / 60, len_secs % 60);
++
++ id += cddb_sum (secs);
++ total_secs += len_secs;
++ }
++ }
++
++ /* first_sector = src->tracks[0].start + CD_MSF_OFFSET; */
++ lba_to_msf (first_sector, NULL, NULL, NULL, &start_secs);
++
++ /* last_sector = src->tracks[src->num_tracks-1].end + CD_MSF_OFFSET; */
++ lba_to_msf (last_sector, NULL, NULL, NULL, &end_secs);
++
++ GST_DEBUG_OBJECT (src, "first_sector = %u = %u secs (%02u:%02u)",
++ first_sector, start_secs, start_secs / 60, start_secs % 60);
++ GST_DEBUG_OBJECT (src, "last_sector = %u = %u secs (%02u:%02u)",
++ last_sector, end_secs, end_secs / 60, end_secs % 60);
++
++ t = end_secs - start_secs;
++
++ GST_DEBUG_OBJECT (src, "total length = %u secs (%02u:%02u), added title "
++ "lengths = %u seconds (%02u:%02u)", t, t / 60, t % 60, total_secs,
++ total_secs / 60, total_secs % 60);
++
++ src->discid = ((id % 0xff) << 24 | t << 8 | num_audio_tracks);
++
++ s = g_string_new (NULL);
++ g_string_append_printf (s, "%08x", src->discid);
++
++ gst_tag_list_add (src->tags, GST_TAG_MERGE_REPLACE,
++ GST_TAG_CDDA_CDDB_DISCID, s->str, NULL);
++
++ g_string_append_printf (s, " %u", src->num_tracks);
++ for (i = 0; i < src->num_tracks; ++i) {
++ g_string_append_printf (s, " %u", src->tracks[i].start + CD_MSF_OFFSET);
++ }
++ g_string_append_printf (s, " %u", t);
++
++ gst_tag_list_add (src->tags, GST_TAG_MERGE_REPLACE,
++ GST_TAG_CDDA_CDDB_DISCID_FULL, s->str, NULL);
++
++ GST_DEBUG_OBJECT (src, "cddb discid = %s", s->str);
++
++ g_string_free (s, TRUE);
++}
++
++static void
++gst_cdda_base_src_add_tags (GstCddaBaseSrc * src)
++{
++ gint i;
++
++ /* fill in details for each track */
++ for (i = 0; i < src->num_tracks; ++i) {
++ gint64 duration;
++ guint num_sectors;
++
++ if (src->tracks[i].tags == NULL)
++ src->tracks[i].tags = gst_tag_list_new ();
++
++ num_sectors = src->tracks[i].end - src->tracks[i].start + 1;
++ gst_cdda_base_src_convert (src, sector_format, num_sectors,
++ GST_FORMAT_TIME, &duration);
++
++ gst_tag_list_add (src->tracks[i].tags,
++ GST_TAG_MERGE_REPLACE,
++ GST_TAG_TRACK_NUMBER, i + 1,
++ GST_TAG_TRACK_COUNT, src->num_tracks, GST_TAG_DURATION, duration, NULL);
++ }
++
++ /* now fill in per-album tags and include each track's tags
++ * in the album tags, so that interested parties can retrieve
++ * the relevant details for each track in one go */
++
++ /* /////////////////////////////// FIXME should we rather insert num_tracks
++ * tags by the name of 'track-tags' and have the caller use
++ * gst_tag_list_get_value_index() rather than use tag names incl.
++ * the track number ?? *////////////////////////////////////////
++
++ gst_tag_list_add (src->tags, GST_TAG_MERGE_REPLACE,
++ GST_TAG_TRACK_COUNT, src->num_tracks, NULL);
++#if 0
++ for (i = 0; i < src->num_tracks; ++i) {
++ gst_tag_list_add (src->tags, GST_TAG_MERGE_APPEND,
++ GST_TAG_CDDA_TRACK_TAGS, src->tracks[i].tags, NULL);
++ }
++#endif
++
++ GST_DEBUG ("src->tags = %" GST_PTR_FORMAT, src->tags);
++}
++
++static void
++gst_cdda_base_src_add_index_associations (GstCddaBaseSrc * src)
++{
++ gint i;
++
++ for (i = 0; i < src->num_tracks; i++) {
++ gint64 sector;
++
++ sector = src->tracks[i].start;
++ gst_index_add_association (src->index, src->index_id, GST_ASSOCIATION_FLAG_KEY_UNIT, track_format, i, /* here we count from 0 */
++ sector_format, sector,
++ GST_FORMAT_TIME,
++ (gint64) (((CD_FRAMESIZE_RAW >> 2) * sector * GST_SECOND) / 44100),
++ GST_FORMAT_BYTES, (gint64) (sector << 2), GST_FORMAT_DEFAULT,
++ (gint64) ((CD_FRAMESIZE_RAW >> 2) * sector), NULL);
++ }
++}
++
++static void
++gst_cdda_base_src_set_index (GstElement * element, GstIndex * index)
++{
++ GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (element);
++
++ src->index = index;
++
++ gst_index_get_writer_id (index, GST_OBJECT (src), &src->index_id);
++ gst_index_add_format (index, src->index_id, track_format);
++ gst_index_add_format (index, src->index_id, sector_format);
++}
++
++
++static GstIndex *
++gst_cdda_base_src_get_index (GstElement * element)
++{
++ GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (element);
++
++ return src->index;
++}
++
++static gint
++gst_cdda_base_src_track_sort_func (gconstpointer a, gconstpointer b,
++ gpointer foo)
++{
++ GstCddaBaseSrcTrack *track_a = ((GstCddaBaseSrcTrack *) a);
++ GstCddaBaseSrcTrack *track_b = ((GstCddaBaseSrcTrack *) b);
++
++ /* sort data tracks to the end, and audio tracks by track number */
++ if (track_a->is_audio == track_b->is_audio)
++ return (gint) track_a->num - (gint) track_b->num;
++
++ if (track_a->is_audio) {
++ return -1;
++ } else {
++ return 1;
++ }
++}
++
++static gboolean
++gst_cdda_base_src_start (GstBaseSrc * basesrc)
++{
++ GstCddaBaseSrcClass *klass = GST_CDDA_BASE_SRC_GET_CLASS (basesrc);
++ GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (basesrc);
++ gboolean ret;
++ gchar *device = NULL;
++
++ src->discid = 0;
++ src->mb_discid[0] = '\0';
++
++ g_assert (klass->open != NULL);
++
++ if (src->device != NULL) {
++ device = g_strdup (src->device);
++ } else if (klass->get_default_device != NULL) {
++ device = klass->get_default_device (src);
++ }
++
++ if (device == NULL)
++ device = g_strdup (DEFAULT_DEVICE);
++
++ GST_LOG_OBJECT (basesrc, "opening device %s", device);
++
++ src->tags = gst_tag_list_new ();
++
++ ret = klass->open (src, device);
++ g_free (device);
++ device = NULL;
++
++ if (!ret) {
++ GST_DEBUG_OBJECT (basesrc, "failed to open device");
++ /* subclass (should have) posted an error message with the details */
++ gst_cdda_base_src_stop (basesrc);
++ return FALSE;
++ }
++
++ if (src->num_tracks == 0 || src->tracks == NULL) {
++ GST_DEBUG_OBJECT (src, "no tracks");
++ GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
++ (_("This CD has no audio tracks")), (NULL));
++ gst_cdda_base_src_stop (basesrc);
++ return FALSE;
++ }
++
++ /* need to calculate disc IDs before we ditch the data tracks */
++ gst_cdda_base_src_calculate_cddb_id (src);
++ gst_cddabasesrc_calculate_musicbrainz_discid (src);
++
++#if 0
++ /* adjust sector offsets if necessary */
++ if (src->toc_bias) {
++ src->toc_offset -= src->tracks[0].start;
++ }
++ for (i = 0; i < src->num_tracks; ++i) {
++ src->tracks[i].start += src->toc_offset;
++ src->tracks[i].end += src->toc_offset;
++ }
++#endif
++
++ /* now that we calculated the various disc IDs,
++ * sort the data tracks to end and ignore them */
++ src->num_all_tracks = src->num_tracks;
++
++ g_qsort_with_data (src->tracks, src->num_tracks,
++ sizeof (GstCddaBaseSrcTrack), gst_cdda_base_src_track_sort_func, NULL);
++
++ while (src->num_tracks > 0 && !src->tracks[src->num_tracks - 1].is_audio)
++ --src->num_tracks;
++
++ if (src->num_tracks == 0) {
++ GST_DEBUG_OBJECT (src, "no audio tracks");
++ GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
++ (_("This CD has no audio tracks")), (NULL));
++ gst_cdda_base_src_stop (basesrc);
++ return FALSE;
++ }
++
++ gst_cdda_base_src_add_tags (src);
++
++ if (src->index && GST_INDEX_IS_WRITABLE (src->index))
++ gst_cdda_base_src_add_index_associations (src);
++
++ src->cur_track = 0;
++ src->prev_track = -1;
++
++ if (src->uri_track > 0 && src->uri_track <= src->num_tracks) {
++ GST_LOG_OBJECT (src, "seek to track %d", src->uri_track);
++ src->cur_track = src->uri_track - 1;
++ src->uri_track = -1;
++ src->mode = GST_CDDA_BASE_SRC_MODE_NORMAL;
++ }
++
++ src->cur_sector = src->tracks[src->cur_track].start;
++ GST_LOG_OBJECT (src, "starting at sector %d", src->cur_sector);
++
++ gst_cdda_base_src_update_duration (src);
++
++ return TRUE;
++}
++
++static void
++gst_cdda_base_src_clear_tracks (GstCddaBaseSrc * src)
++{
++ if (src->tracks != NULL) {
++ gint i;
++
++ for (i = 0; i < src->num_all_tracks; ++i) {
++ if (src->tracks[i].tags)
++ gst_tag_list_free (src->tracks[i].tags);
++ }
++
++ g_free (src->tracks);
++ src->tracks = NULL;
++ }
++ src->num_tracks = 0;
++ src->num_all_tracks = 0;
++}
++
++static gboolean
++gst_cdda_base_src_stop (GstBaseSrc * basesrc)
++{
++ GstCddaBaseSrcClass *klass = GST_CDDA_BASE_SRC_GET_CLASS (basesrc);
++ GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (basesrc);
++
++ g_assert (klass->close != NULL);
++
++ klass->close (src);
++
++ gst_cdda_base_src_clear_tracks (src);
++
++ if (src->tags) {
++ gst_tag_list_free (src->tags);
++ src->tags = NULL;
++ }
++
++ src->prev_track = -1;
++ src->cur_track = -1;
++
++ return TRUE;
++}
++
++
++static GstFlowReturn
++gst_cdda_base_src_create (GstPushSrc * pushsrc, GstBuffer ** buffer)
++{
++ GstCddaBaseSrcClass *klass = GST_CDDA_BASE_SRC_GET_CLASS (pushsrc);
++ GstCddaBaseSrc *src = GST_CDDA_BASE_SRC (pushsrc);
++ GstBuffer *buf;
++ GstFormat format;
++ gboolean eos;
++
++ GstClockTime position = GST_CLOCK_TIME_NONE;
++ GstClockTime duration = GST_CLOCK_TIME_NONE;
++ gint64 qry_position;
++
++ g_assert (klass->read_sector != NULL);
++
++ switch (src->mode) {
++ case GST_CDDA_BASE_SRC_MODE_NORMAL:
++ eos = (src->cur_sector > src->tracks[src->cur_track].end);
++ break;
++ case GST_CDDA_BASE_SRC_MODE_CONTINUOUS:
++ eos = (src->cur_sector > src->tracks[src->num_tracks - 1].end);
++ src->cur_track = gst_cdda_base_src_get_track_from_sector (src,
++ src->cur_sector);
++ break;
++ default:
++ g_return_val_if_reached (GST_FLOW_ERROR);
++ }
++
++ if (eos) {
++ src->prev_track = -1;
++ GST_DEBUG_OBJECT (src, "EOS at sector %d, cur_track=%d, mode=%d",
++ src->cur_sector, src->cur_track, src->mode);
++ /* base class will send EOS for us */
++ return GST_FLOW_UNEXPECTED;
++ }
++
++ if (src->prev_track != src->cur_track) {
++ GstTagList *tags;
++
++ tags = gst_tag_list_merge (src->tags, src->tracks[src->cur_track].tags,
++ GST_TAG_MERGE_REPLACE);
++ GST_LOG_OBJECT (src, "announcing tags: %" GST_PTR_FORMAT, tags);
++ gst_element_found_tags_for_pad (GST_ELEMENT (src),
++ GST_BASE_SRC_PAD (src), tags);
++ src->prev_track = src->cur_track;
++
++ gst_cdda_base_src_update_duration (src);
++
++ g_object_notify (G_OBJECT (src), "track");
++ }
++
++ GST_LOG_OBJECT (src, "asking for sector %u", src->cur_sector);
++
++ buf = klass->read_sector (src, src->cur_sector);
++
++ if (buf == NULL) {
++ GST_WARNING_OBJECT (src, "failed to read sector %u", src->cur_sector);
++ return GST_FLOW_ERROR;
++ }
++
++ if (GST_BUFFER_CAPS (buf) == NULL) {
++ gst_buffer_set_caps (buf, GST_PAD_CAPS (GST_BASE_SRC_PAD (src)));
++ }
++
++ format = GST_FORMAT_TIME;
++ if (gst_pad_query_position (GST_BASE_SRC_PAD (src), &format, &qry_position)) {
++ gint64 next_ts = 0;
++
++ position = (GstClockTime) qry_position;
++
++ ++src->cur_sector;
++ if (gst_pad_query_position (GST_BASE_SRC_PAD (src), &format, &next_ts)) {
++ duration = (GstClockTime) (next_ts - qry_position);
++ }
++ --src->cur_sector;
++ }
++
++ /* fallback duration: 4 bytes per sample, 44100 samples per second */
++ if (duration == GST_CLOCK_TIME_NONE) {
++ duration = gst_util_uint64_scale_int (GST_BUFFER_SIZE (buf) >> 2,
++ GST_SECOND, 44100);
++ }
++
++ GST_BUFFER_TIMESTAMP (buf) = position;
++ GST_BUFFER_DURATION (buf) = duration;
++
++ GST_LOG_OBJECT (src, "pushing sector %d with timestamp %" GST_TIME_FORMAT,
++ src->cur_sector, GST_TIME_ARGS (position));
++
++ ++src->cur_sector;
++
++ *buffer = buf;
++
++ return GST_FLOW_OK;
++}
--- /dev/null
--- /dev/null
++diff --git a/gst-libs/gst/rtp/gstrtcpbuffer.c b/gst-libs/gst/rtp/gstrtcpbuffer.c
++index ab77c8a..fb35a92 100644
++--- a/gst-libs/gst/rtp/gstrtcpbuffer.c
+++++ b/gst-libs/gst/rtp/gstrtcpbuffer.c
++@@ -449,6 +449,11 @@ gst_rtcp_buffer_add_packet (GstBuffer * buffer, GstRTCPType type,
++ case GST_RTCP_TYPE_APP:
++ len = 12;
++ break;
+++ case GST_RTCP_TYPE_RTPFB:
+++ len = 12;
+++ break;
+++ case GST_RTCP_TYPE_PSFB:
+++ len = 12;
++ default:
++ goto unknown_type;
++ }
++@@ -1637,6 +1642,147 @@ no_space:
++ }
++
++ /**
+++ * gst_rtcp_packet_fb_get_sender_ssrc:
+++ * @packet: a valid RTPFB or PSFB #GstRTCPPacket
+++ *
+++ * Get the sender SSRC field of the RTPFB or PSFB @packet.
+++ *
+++ * Returns: the sender SSRC.
+++ */
+++guint32
+++gst_rtcp_packet_fb_get_sender_ssrc (GstRTCPPacket * packet)
+++{
+++ guint8 *data;
+++ guint32 ssrc;
+++
+++ g_return_val_if_fail (packet != NULL, 0);
+++ g_return_val_if_fail ((packet->type == GST_RTCP_TYPE_RTPFB ||
+++ packet->type == GST_RTCP_TYPE_PSFB), 0);
+++ g_return_val_if_fail (GST_IS_BUFFER (packet->buffer), 0);
+++
+++ data = GST_BUFFER_DATA (packet->buffer);
+++
+++ /* skip header */
+++ data += packet->offset + 4;
+++ ssrc = GST_READ_UINT32_BE (data);
+++
+++ return ssrc;
+++}
+++
+++/**
+++ * gst_rtcp_packet_fb_set_sender_ssrc:
+++ * @packet: a valid RTPFB or PSFB #GstRTCPPacket
+++ *
+++ * Set the sender SSRC field of the RTPFB or PSFB @packet.
+++ */
+++void
+++gst_rtcp_packet_fb_set_sender_ssrc (GstRTCPPacket *packet, guint32 ssrc)
+++{
+++ guint8 *data;
+++
+++ g_return_if_fail (packet != NULL);
+++ g_return_if_fail (packet->type == GST_RTCP_TYPE_RTPFB ||
+++ packet->type == GST_RTCP_TYPE_PSFB);
+++ g_return_if_fail (GST_IS_BUFFER (packet->buffer));
+++
+++ data = GST_BUFFER_DATA (packet->buffer);
+++
+++ /* skip header */
+++ data += packet->offset + 4;
+++ GST_WRITE_UINT32_BE (data, ssrc);
+++}
+++
+++/**
+++ * gst_rtcp_packet_fb_get_media_ssrc:
+++ * @packet: a valid RTPFB or PSFB #GstRTCPPacket
+++ *
+++ * Get the media SSRC field of the RTPFB or PSFB @packet.
+++ *
+++ * Returns: the media SSRC.
+++ */
+++guint32
+++gst_rtcp_packet_fb_get_media_ssrc (GstRTCPPacket * packet)
+++{
+++ guint8 *data;
+++ guint32 ssrc;
+++
+++ g_return_val_if_fail (packet != NULL, 0);
+++ g_return_val_if_fail ((packet->type == GST_RTCP_TYPE_RTPFB ||
+++ packet->type == GST_RTCP_TYPE_PSFB), 0);
+++ g_return_val_if_fail (GST_IS_BUFFER (packet->buffer), 0);
+++
+++ data = GST_BUFFER_DATA (packet->buffer);
+++
+++ /* skip header and sender ssrc */
+++ data += packet->offset + 8;
+++ ssrc = GST_READ_UINT32_BE (data);
+++
+++ return ssrc;
+++}
+++
+++/**
+++ * gst_rtcp_packet_fb_set_media_ssrc:
+++ * @packet: a valid RTPFB or PSFB #GstRTCPPacket
+++ *
+++ * Set the media SSRC field of the RTPFB or PSFB @packet.
+++ */
+++void
+++gst_rtcp_packet_fb_set_media_ssrc (GstRTCPPacket *packet, guint32 ssrc)
+++{
+++ guint8 *data;
+++
+++ g_return_if_fail (packet != NULL);
+++ g_return_if_fail (packet->type == GST_RTCP_TYPE_RTPFB ||
+++ packet->type == GST_RTCP_TYPE_PSFB);
+++ g_return_if_fail (GST_IS_BUFFER (packet->buffer));
+++
+++ data = GST_BUFFER_DATA (packet->buffer);
+++
+++ /* skip header and sender ssrc */
+++ data += packet->offset + 8;
+++ GST_WRITE_UINT32_BE (data, ssrc);
+++}
+++
+++/**
+++ * gst_rtcp_packet_psfb_get_type:
+++ * @packet: a valid PSFB #GstRTCPPacket
+++ *
+++ * Get the feedback message type of the PSFB @packet.
+++ *
+++ * Returns: The feedback message type.
+++ */
+++GstRTCPPSFBType
+++gst_rtcp_packet_psfb_get_type (GstRTCPPacket *packet)
+++{
+++ g_return_val_if_fail (packet != NULL, GST_RTCP_PSFB_TYPE_INVALID);
+++ g_return_val_if_fail (packet->type == GST_RTCP_TYPE_PSFB,
+++ GST_RTCP_PSFB_TYPE_INVALID);
+++
+++ return packet->count;
+++}
+++
+++/**
+++ * gst_rtcp_packet_psfb_set_type:
+++ * @packet: a valid PSFB #GstRTCPPacket
+++ * @type: the #GstRTCPPSFBType to set
+++ *
+++ * Set the feedback message type of the PSFB @packet.
+++ */
+++void
+++gst_rtcp_packet_psfb_set_type (GstRTCPPacket *packet, GstRTCPPSFBType type)
+++{
+++ guint8 *data;
+++
+++ g_return_if_fail (packet != NULL);
+++ g_return_if_fail (packet->type == GST_RTCP_TYPE_PSFB);
+++ g_return_if_fail (GST_IS_BUFFER (packet->buffer));
+++
+++ data = GST_BUFFER_DATA (packet->buffer);
+++
+++ data[packet->offset] = (data[packet->offset] & 0xE0) | type;
+++}
+++
+++/**
++ * gst_rtcp_ntp_to_unix:
++ * @ntptime: an NTP timestamp
++ *
++diff --git a/gst-libs/gst/rtp/gstrtcpbuffer.h b/gst-libs/gst/rtp/gstrtcpbuffer.h
++index 9c908a8..bb247c9 100644
++--- a/gst-libs/gst/rtp/gstrtcpbuffer.h
+++++ b/gst-libs/gst/rtp/gstrtcpbuffer.h
++@@ -42,6 +42,8 @@ G_BEGIN_DECLS
++ * @GST_RTCP_TYPE_SDES: Source description
++ * @GST_RTCP_TYPE_BYE: Goodbye
++ * @GST_RTCP_TYPE_APP: Application defined
+++ * @GST_RTCP_TYPE_RTPFB: Transport layer feedback
+++ * @GST_RTCP_TYPE_PSFB: Payload-specific feedback
++ *
++ * Different RTCP packet types.
++ */
++@@ -52,9 +54,28 @@ typedef enum
++ GST_RTCP_TYPE_RR = 201,
++ GST_RTCP_TYPE_SDES = 202,
++ GST_RTCP_TYPE_BYE = 203,
++- GST_RTCP_TYPE_APP = 204
+++ GST_RTCP_TYPE_APP = 204,
+++ GST_RTCP_TYPE_RTPFB = 205,
+++ GST_RTCP_TYPE_PSFB = 206
++ } GstRTCPType;
++
+++/**
+++ * GstRTCPPSFBType:
+++ * @GST_RTCP_PSFB_TYPE_INVALID: Invalid type
+++ * @GST_RTCP_PSFB_TYPE_PLI: Picture Loss Indication
+++ * @GST_RTCP_PSFB_TYPE_SLI: Slice Loss Indication
+++ * @GST_RTCP_PSFB_TYPE_RPSI: Reference Picture Selection Indication
+++ * @GST_RTCP_PSFB_TYPE_AFB: Application layer Feedback
+++ */
+++typedef enum
+++{
+++ GST_RTCP_PSFB_TYPE_INVALID = 0,
+++ GST_RTCP_PSFB_TYPE_PLI = 1,
+++ GST_RTCP_PSFB_TYPE_SLI = 2,
+++ GST_RTCP_PSFB_TYPE_RPSI = 3,
+++ GST_RTCP_PSFB_TYPE_AFB = 15
+++} GstRTCPPSFBType;
+++
++ /**
++ * GstRTCPSDESType:
++ * @GST_RTCP_SDES_INVALID: Invalid SDES entry
++@@ -232,6 +253,16 @@ guint8 gst_rtcp_packet_bye_get_reason_len (GstRTCPPacket *packet);
++ gchar* gst_rtcp_packet_bye_get_reason (GstRTCPPacket *packet);
++ gboolean gst_rtcp_packet_bye_set_reason (GstRTCPPacket *packet, const gchar *reason);
++
+++/* feedback packets */
+++guint32 gst_rtcp_packet_fb_get_sender_ssrc (GstRTCPPacket *packet);
+++void gst_rtcp_packet_fb_set_sender_ssrc (GstRTCPPacket *packet, guint32 ssrc);
+++guint32 gst_rtcp_packet_fb_get_media_ssrc (GstRTCPPacket *packet);
+++void gst_rtcp_packet_fb_set_media_ssrc (GstRTCPPacket *packet, guint32 ssrc);
+++
+++/* psfb packets */
+++GstRTCPPSFBType gst_rtcp_packet_psfb_get_type (GstRTCPPacket *packet);
+++void gst_rtcp_packet_psfb_set_type (GstRTCPPacket *packet, GstRTCPPSFBType type);
+++
++ /* helper functions */
++ guint64 gst_rtcp_ntp_to_unix (guint64 ntptime);
++ guint64 gst_rtcp_unix_to_ntp (guint64 unixtime);
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) <2005> Wim Taymans <wim@fluendo.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
++ */
++
++/**
++ * SECTION:gstbasertppayload
++ * @short_description: Base class for RTP payloader
++ *
++ * <refsect2>
++ * <para>
++ * Provides a base class for RTP payloaders
++ * </para>
++ * </refsect2>
++ */
++
++#ifdef HAVE_CONFIG_H
++# include "config.h"
++#endif
++
++#include <string.h>
++
++#include <gst/rtp/gstrtpbuffer.h>
++
++#include "gstbasertppayload.h"
++
++GST_DEBUG_CATEGORY_STATIC (basertppayload_debug);
++#define GST_CAT_DEFAULT (basertppayload_debug)
++
++#define GST_BASE_RTP_PAYLOAD_GET_PRIVATE(obj) \
++ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_BASE_RTP_PAYLOAD, GstBaseRTPPayloadPrivate))
++
++struct _GstBaseRTPPayloadPrivate
++{
++ gboolean ts_offset_random;
++ gboolean seqnum_offset_random;
++ gboolean ssrc_random;
++ guint16 next_seqnum;
++ gboolean perfect_rtptime;
++
++ gint64 prop_max_ptime;
++ gint64 caps_max_ptime;
++};
++
++/* BaseRTPPayload signals and args */
++enum
++{
++ /* FILL ME */
++ LAST_SIGNAL
++};
++
++/* FIXME 0.11, a better default is the Ethernet MTU of
++ * 1500 - sizeof(headers) as pointed out by marcelm in IRC:
++ * So an Ethernet MTU of 1500, minus 60 for the max IP, minus 8 for UDP, gives
++ * 1432 bytes or so. And that should be adjusted downward further for other
++ * encapsulations like PPPoE, so 1400 at most.
++ */
++#define DEFAULT_MTU 1400
++#define DEFAULT_PT 96
++#define DEFAULT_SSRC -1
++#define DEFAULT_TIMESTAMP_OFFSET -1
++#define DEFAULT_SEQNUM_OFFSET -1
++#define DEFAULT_MAX_PTIME -1
++#define DEFAULT_MIN_PTIME 0
++#define DEFAULT_PERFECT_RTPTIME TRUE
++#define DEFAULT_PTIME_MULTIPLE 0
++
++enum
++{
++ PROP_0,
++ PROP_MTU,
++ PROP_PT,
++ PROP_SSRC,
++ PROP_TIMESTAMP_OFFSET,
++ PROP_SEQNUM_OFFSET,
++ PROP_MAX_PTIME,
++ PROP_MIN_PTIME,
++ PROP_TIMESTAMP,
++ PROP_SEQNUM,
++ PROP_PERFECT_RTPTIME,
++ PROP_PTIME_MULTIPLE,
++ PROP_LAST
++};
++
++static void gst_basertppayload_class_init (GstBaseRTPPayloadClass * klass);
++static void gst_basertppayload_base_init (GstBaseRTPPayloadClass * klass);
++static void gst_basertppayload_init (GstBaseRTPPayload * basertppayload,
++ gpointer g_class);
++static void gst_basertppayload_finalize (GObject * object);
++
++static gboolean gst_basertppayload_sink_setcaps (GstPad * pad, GstCaps * caps);
++static GstCaps *gst_basertppayload_sink_getcaps (GstPad * pad);
++static gboolean gst_basertppayload_event (GstPad * pad, GstEvent * event);
++static GstFlowReturn gst_basertppayload_chain (GstPad * pad,
++ GstBuffer * buffer);
++
++static void gst_basertppayload_set_property (GObject * object, guint prop_id,
++ const GValue * value, GParamSpec * pspec);
++static void gst_basertppayload_get_property (GObject * object, guint prop_id,
++ GValue * value, GParamSpec * pspec);
++
++static GstStateChangeReturn gst_basertppayload_change_state (GstElement *
++ element, GstStateChange transition);
++
++static GstElementClass *parent_class = NULL;
++
++/* FIXME 0.11: API should be changed to gst_base_typ_payload_xyz */
++
++GType
++gst_basertppayload_get_type (void)
++{
++ static GType basertppayload_type = 0;
++
++ if (!basertppayload_type) {
++ static const GTypeInfo basertppayload_info = {
++ sizeof (GstBaseRTPPayloadClass),
++ (GBaseInitFunc) gst_basertppayload_base_init,
++ NULL,
++ (GClassInitFunc) gst_basertppayload_class_init,
++ NULL,
++ NULL,
++ sizeof (GstBaseRTPPayload),
++ 0,
++ (GInstanceInitFunc) gst_basertppayload_init,
++ };
++
++ basertppayload_type =
++ g_type_register_static (GST_TYPE_ELEMENT, "GstBaseRTPPayload",
++ &basertppayload_info, G_TYPE_FLAG_ABSTRACT);
++ }
++ return basertppayload_type;
++}
++
++static void
++gst_basertppayload_base_init (GstBaseRTPPayloadClass * klass)
++{
++}
++
++static void
++gst_basertppayload_class_init (GstBaseRTPPayloadClass * klass)
++{
++ GObjectClass *gobject_class;
++ GstElementClass *gstelement_class;
++
++ gobject_class = (GObjectClass *) klass;
++ gstelement_class = (GstElementClass *) klass;
++
++ g_type_class_add_private (klass, sizeof (GstBaseRTPPayloadPrivate));
++
++ parent_class = g_type_class_peek_parent (klass);
++
++ gobject_class->finalize = gst_basertppayload_finalize;
++
++ gobject_class->set_property = gst_basertppayload_set_property;
++ gobject_class->get_property = gst_basertppayload_get_property;
++
++ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_MTU,
++ g_param_spec_uint ("mtu", "MTU",
++ "Maximum size of one packet",
++ 28, G_MAXUINT, DEFAULT_MTU,
++ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
++ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PT,
++ g_param_spec_uint ("pt", "payload type",
++ "The payload type of the packets", 0, 0x80, DEFAULT_PT,
++ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
++ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SSRC,
++ g_param_spec_uint ("ssrc", "SSRC",
++ "The SSRC of the packets (default == random)", 0, G_MAXUINT32,
++ DEFAULT_SSRC, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
++ g_object_class_install_property (G_OBJECT_CLASS (klass),
++ PROP_TIMESTAMP_OFFSET, g_param_spec_uint ("timestamp-offset",
++ "Timestamp Offset",
++ "Offset to add to all outgoing timestamps (default = random)", 0,
++ G_MAXUINT32, DEFAULT_TIMESTAMP_OFFSET,
++ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
++ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SEQNUM_OFFSET,
++ g_param_spec_int ("seqnum-offset", "Sequence number Offset",
++ "Offset to add to all outgoing seqnum (-1 = random)", -1, G_MAXUINT16,
++ DEFAULT_SEQNUM_OFFSET, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
++ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_MAX_PTIME,
++ g_param_spec_int64 ("max-ptime", "Max packet time",
++ "Maximum duration of the packet data in ns (-1 = unlimited up to MTU)",
++ -1, G_MAXINT64, DEFAULT_MAX_PTIME,
++ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
++ /**
++ * GstBaseRTPAudioPayload:min-ptime:
++ *
++ * Minimum duration of the packet data in ns (can't go above MTU)
++ *
++ * Since: 0.10.13
++ **/
++ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_MIN_PTIME,
++ g_param_spec_int64 ("min-ptime", "Min packet time",
++ "Minimum duration of the packet data in ns (can't go above MTU)",
++ 0, G_MAXINT64, DEFAULT_MIN_PTIME,
++ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
++
++ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TIMESTAMP,
++ g_param_spec_uint ("timestamp", "Timestamp",
++ "The RTP timestamp of the last processed packet",
++ 0, G_MAXUINT32, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
++ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SEQNUM,
++ g_param_spec_uint ("seqnum", "Sequence number",
++ "The RTP sequence number of the last processed packet",
++ 0, G_MAXUINT16, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
++
++ /**
++ * GstBaseRTPAudioPayload:perfect-rtptime:
++ *
++ * Try to use the offset fields to generate perfect RTP timestamps. when this
++ * option is disabled, RTP timestamps are generated from the GStreamer
++ * timestamps, which could result in RTP timestamps that don't increment with
++ * the amount of data in the packet.
++ *
++ * Since: 0.10.25
++ */
++ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PERFECT_RTPTIME,
++ g_param_spec_boolean ("perfect-rtptime", "Perfect RTP Time",
++ "Generate perfect RTP timestamps when possible",
++ DEFAULT_PERFECT_RTPTIME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
++ /**
++ * GstBaseRTPAudioPayload:ptime-multiple:
++ *
++ * Force buffers to be multiples of this duration in ns (0 disables)
++ *
++ * Since: 0.10.29
++ **/
++ g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PTIME_MULTIPLE,
++ g_param_spec_int64 ("ptime-multiple", "Packet time multiple",
++ "Force buffers to be multiples of this duration in ns (0 disables)",
++ 0, G_MAXINT64, DEFAULT_PTIME_MULTIPLE,
++ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
++
++ gstelement_class->change_state = gst_basertppayload_change_state;
++
++ GST_DEBUG_CATEGORY_INIT (basertppayload_debug, "basertppayload", 0,
++ "Base class for RTP Payloaders");
++}
++
++static void
++gst_basertppayload_init (GstBaseRTPPayload * basertppayload, gpointer g_class)
++{
++ GstPadTemplate *templ;
++ GstBaseRTPPayloadPrivate *priv;
++
++ basertppayload->priv = priv =
++ GST_BASE_RTP_PAYLOAD_GET_PRIVATE (basertppayload);
++
++ templ =
++ gst_element_class_get_pad_template (GST_ELEMENT_CLASS (g_class), "src");
++ g_return_if_fail (templ != NULL);
++
++ basertppayload->srcpad = gst_pad_new_from_template (templ, "src");
++ gst_element_add_pad (GST_ELEMENT (basertppayload), basertppayload->srcpad);
++
++ templ =
++ gst_element_class_get_pad_template (GST_ELEMENT_CLASS (g_class), "sink");
++ g_return_if_fail (templ != NULL);
++
++ basertppayload->sinkpad = gst_pad_new_from_template (templ, "sink");
++ gst_pad_set_setcaps_function (basertppayload->sinkpad,
++ gst_basertppayload_sink_setcaps);
++ gst_pad_set_getcaps_function (basertppayload->sinkpad,
++ gst_basertppayload_sink_getcaps);
++ gst_pad_set_event_function (basertppayload->sinkpad,
++ gst_basertppayload_event);
++ gst_pad_set_chain_function (basertppayload->sinkpad,
++ gst_basertppayload_chain);
++ gst_element_add_pad (GST_ELEMENT (basertppayload), basertppayload->sinkpad);
++
++ basertppayload->seq_rand = g_rand_new_with_seed (g_random_int ());
++ basertppayload->ssrc_rand = g_rand_new_with_seed (g_random_int ());
++ basertppayload->ts_rand = g_rand_new_with_seed (g_random_int ());
++
++ basertppayload->mtu = DEFAULT_MTU;
++ basertppayload->pt = DEFAULT_PT;
++ basertppayload->seqnum_offset = DEFAULT_SEQNUM_OFFSET;
++ basertppayload->ssrc = DEFAULT_SSRC;
++ basertppayload->ts_offset = DEFAULT_TIMESTAMP_OFFSET;
++ priv->seqnum_offset_random = (basertppayload->seqnum_offset == -1);
++ priv->ts_offset_random = (basertppayload->ts_offset == -1);
++ priv->ssrc_random = (basertppayload->ssrc == -1);
++
++ basertppayload->max_ptime = DEFAULT_MAX_PTIME;
++ basertppayload->min_ptime = DEFAULT_MIN_PTIME;
++ basertppayload->priv->perfect_rtptime = DEFAULT_PERFECT_RTPTIME;
++ basertppayload->abidata.ABI.ptime_multiple = DEFAULT_PTIME_MULTIPLE;
++
++ basertppayload->media = NULL;
++ basertppayload->encoding_name = NULL;
++
++ basertppayload->clock_rate = 0;
++
++ basertppayload->priv->caps_max_ptime = DEFAULT_MAX_PTIME;
++ basertppayload->priv->prop_max_ptime = DEFAULT_MAX_PTIME;
++}
++
++static void
++gst_basertppayload_finalize (GObject * object)
++{
++ GstBaseRTPPayload *basertppayload;
++
++ basertppayload = GST_BASE_RTP_PAYLOAD (object);
++
++ g_rand_free (basertppayload->seq_rand);
++ basertppayload->seq_rand = NULL;
++ g_rand_free (basertppayload->ssrc_rand);
++ basertppayload->ssrc_rand = NULL;
++ g_rand_free (basertppayload->ts_rand);
++ basertppayload->ts_rand = NULL;
++
++ g_free (basertppayload->media);
++ basertppayload->media = NULL;
++ g_free (basertppayload->encoding_name);
++ basertppayload->encoding_name = NULL;
++
++ G_OBJECT_CLASS (parent_class)->finalize (object);
++}
++
++static gboolean
++gst_basertppayload_sink_setcaps (GstPad * pad, GstCaps * caps)
++{
++ GstBaseRTPPayload *basertppayload;
++ GstBaseRTPPayloadClass *basertppayload_class;
++ gboolean ret = TRUE;
++
++ GST_DEBUG_OBJECT (pad, "setting caps %" GST_PTR_FORMAT, caps);
++ basertppayload = GST_BASE_RTP_PAYLOAD (gst_pad_get_parent (pad));
++ basertppayload_class = GST_BASE_RTP_PAYLOAD_GET_CLASS (basertppayload);
++
++ if (basertppayload_class->set_caps)
++ ret = basertppayload_class->set_caps (basertppayload, caps);
++
++ gst_object_unref (basertppayload);
++
++ return ret;
++}
++
++static GstCaps *
++gst_basertppayload_sink_getcaps (GstPad * pad)
++{
++ GstBaseRTPPayload *basertppayload;
++ GstBaseRTPPayloadClass *basertppayload_class;
++ GstCaps *caps = NULL;
++
++ GST_DEBUG_OBJECT (pad, "getting caps");
++
++ basertppayload = GST_BASE_RTP_PAYLOAD (gst_pad_get_parent (pad));
++ basertppayload_class = GST_BASE_RTP_PAYLOAD_GET_CLASS (basertppayload);
++
++ if (basertppayload_class->get_caps)
++ caps = basertppayload_class->get_caps (basertppayload, pad);
++
++ if (!caps) {
++ caps = GST_PAD_TEMPLATE_CAPS (GST_PAD_PAD_TEMPLATE (pad));
++ GST_DEBUG_OBJECT (pad,
++ "using pad template %p with caps %p %" GST_PTR_FORMAT,
++ GST_PAD_PAD_TEMPLATE (pad), caps, caps);
++
++ caps = gst_caps_ref (caps);
++ }
++
++ gst_object_unref (basertppayload);
++
++ return caps;
++}
++
++static gboolean
++gst_basertppayload_event (GstPad * pad, GstEvent * event)
++{
++ GstBaseRTPPayload *basertppayload;
++ GstBaseRTPPayloadClass *basertppayload_class;
++ gboolean res;
++
++ basertppayload = GST_BASE_RTP_PAYLOAD (gst_pad_get_parent (pad));
++ basertppayload_class = GST_BASE_RTP_PAYLOAD_GET_CLASS (basertppayload);
++
++ if (basertppayload_class->handle_event) {
++ res = basertppayload_class->handle_event (pad, event);
++ if (res)
++ goto done;
++ }
++
++ switch (GST_EVENT_TYPE (event)) {
++ case GST_EVENT_FLUSH_START:
++ res = gst_pad_event_default (pad, event);
++ break;
++ case GST_EVENT_FLUSH_STOP:
++ res = gst_pad_event_default (pad, event);
++ gst_segment_init (&basertppayload->segment, GST_FORMAT_UNDEFINED);
++ break;
++ case GST_EVENT_NEWSEGMENT:
++ {
++ gboolean update;
++ gdouble rate;
++ GstFormat fmt;
++ gint64 start, stop, position;
++
++ gst_event_parse_new_segment (event, &update, &rate, &fmt, &start, &stop,
++ &position);
++ gst_segment_set_newsegment (&basertppayload->segment, update, rate, fmt,
++ start, stop, position);
++
++ /* fallthrough */
++ }
++ default:
++ res = gst_pad_event_default (pad, event);
++ break;
++ }
++
++done:
++ gst_object_unref (basertppayload);
++
++ return res;
++}
++
++
++static GstFlowReturn
++gst_basertppayload_chain (GstPad * pad, GstBuffer * buffer)
++{
++ GstBaseRTPPayload *basertppayload;
++ GstBaseRTPPayloadClass *basertppayload_class;
++ GstFlowReturn ret;
++
++ basertppayload = GST_BASE_RTP_PAYLOAD (gst_pad_get_parent (pad));
++ basertppayload_class = GST_BASE_RTP_PAYLOAD_GET_CLASS (basertppayload);
++
++ if (!basertppayload_class->handle_buffer)
++ goto no_function;
++
++ ret = basertppayload_class->handle_buffer (basertppayload, buffer);
++
++ gst_object_unref (basertppayload);
++
++ return ret;
++
++ /* ERRORS */
++no_function:
++ {
++ GST_ELEMENT_ERROR (basertppayload, STREAM, NOT_IMPLEMENTED, (NULL),
++ ("subclass did not implement handle_buffer function"));
++ gst_object_unref (basertppayload);
++ gst_buffer_unref (buffer);
++ return GST_FLOW_ERROR;
++ }
++}
++
++/**
++ * gst_basertppayload_set_options:
++ * @payload: a #GstBaseRTPPayload
++ * @media: the media type (typically "audio" or "video")
++ * @dynamic: if the payload type is dynamic
++ * @encoding_name: the encoding name
++ * @clock_rate: the clock rate of the media
++ *
++ * Set the rtp options of the payloader. These options will be set in the caps
++ * of the payloader. Subclasses must call this method before calling
++ * gst_basertppayload_push() or gst_basertppayload_set_outcaps().
++ */
++void
++gst_basertppayload_set_options (GstBaseRTPPayload * payload,
++ const gchar * media, gboolean dynamic, const gchar * encoding_name,
++ guint32 clock_rate)
++{
++ g_return_if_fail (payload != NULL);
++ g_return_if_fail (clock_rate != 0);
++
++ g_free (payload->media);
++ payload->media = g_strdup (media);
++ payload->dynamic = dynamic;
++ g_free (payload->encoding_name);
++ payload->encoding_name = g_strdup (encoding_name);
++ payload->clock_rate = clock_rate;
++}
++
++static gboolean
++copy_fixed (GQuark field_id, const GValue * value, GstStructure * dest)
++{
++ if (gst_value_is_fixed (value)) {
++ gst_structure_id_set_value (dest, field_id, value);
++ }
++ return TRUE;
++}
++
++static void
++update_max_ptime (GstBaseRTPPayload * basertppayload)
++{
++ if (basertppayload->priv->caps_max_ptime != -1 &&
++ basertppayload->priv->prop_max_ptime != -1)
++ basertppayload->max_ptime = MIN (basertppayload->priv->caps_max_ptime,
++ basertppayload->priv->prop_max_ptime);
++ else if (basertppayload->priv->caps_max_ptime != -1)
++ basertppayload->max_ptime = basertppayload->priv->caps_max_ptime;
++ else if (basertppayload->priv->prop_max_ptime != -1)
++ basertppayload->max_ptime = basertppayload->priv->prop_max_ptime;
++ else
++ basertppayload->max_ptime = DEFAULT_MAX_PTIME;
++}
++
++/**
++ * gst_basertppayload_set_outcaps:
++ * @payload: a #GstBaseRTPPayload
++ * @fieldname: the first field name or %NULL
++ * @...: field values
++ *
++ * Configure the output caps with the optional parameters.
++ *
++ * Variable arguments should be in the form field name, field type
++ * (as a GType), value(s). The last variable argument should be NULL.
++ *
++ * Returns: %TRUE if the caps could be set.
++ */
++gboolean
++gst_basertppayload_set_outcaps (GstBaseRTPPayload * payload,
++ const gchar * fieldname, ...)
++{
++ GstCaps *srccaps, *peercaps;
++ gboolean res;
++
++ /* fill in the defaults, their properties cannot be negotiated. */
++ srccaps = gst_caps_new_simple ("application/x-rtp",
++ "media", G_TYPE_STRING, payload->media,
++ "clock-rate", G_TYPE_INT, payload->clock_rate,
++ "encoding-name", G_TYPE_STRING, payload->encoding_name, NULL);
++
++ GST_DEBUG_OBJECT (payload, "defaults: %" GST_PTR_FORMAT, srccaps);
++
++ if (fieldname) {
++ va_list varargs;
++
++ /* override with custom properties */
++ va_start (varargs, fieldname);
++ gst_caps_set_simple_valist (srccaps, fieldname, varargs);
++ va_end (varargs);
++
++ GST_DEBUG_OBJECT (payload, "custom added: %" GST_PTR_FORMAT, srccaps);
++ }
++
++ payload->priv->caps_max_ptime = DEFAULT_MAX_PTIME;
++ payload->abidata.ABI.ptime = 0;
++
++ /* the peer caps can override some of the defaults */
++ peercaps = gst_pad_peer_get_caps (payload->srcpad);
++ if (peercaps == NULL) {
++ /* no peer caps, just add the other properties */
++ gst_caps_set_simple (srccaps,
++ "payload", G_TYPE_INT, GST_BASE_RTP_PAYLOAD_PT (payload),
++ "ssrc", G_TYPE_UINT, payload->current_ssrc,
++ "clock-base", G_TYPE_UINT, payload->ts_base,
++ "seqnum-base", G_TYPE_UINT, payload->seqnum_base, NULL);
++
++ GST_DEBUG_OBJECT (payload, "no peer caps: %" GST_PTR_FORMAT, srccaps);
++ } else {
++ GstCaps *temp;
++ GstStructure *s, *d;
++ const GValue *value;
++ gint pt;
++ guint max_ptime, ptime;
++
++ /* peer provides caps we can use to fixate, intersect. This always returns a
++ * writable caps. */
++ temp = gst_caps_intersect (srccaps, peercaps);
++ gst_caps_unref (srccaps);
++ gst_caps_unref (peercaps);
++
++ if (gst_caps_is_empty (temp)) {
++ gst_caps_unref (temp);
++ return FALSE;
++ }
++
++ /* now fixate, start by taking the first caps */
++ gst_caps_truncate (temp);
++
++ /* get first structure */
++ s = gst_caps_get_structure (temp, 0);
++
++ if (gst_structure_get_uint (s, "maxptime", &max_ptime))
++ payload->priv->caps_max_ptime = max_ptime * GST_MSECOND;
++
++ if (gst_structure_get_uint (s, "ptime", &ptime))
++ payload->abidata.ABI.ptime = ptime * GST_MSECOND;
++
++ if (gst_structure_get_int (s, "payload", &pt)) {
++ /* use peer pt */
++ GST_BASE_RTP_PAYLOAD_PT (payload) = pt;
++ GST_LOG_OBJECT (payload, "using peer pt %d", pt);
++ } else {
++ if (gst_structure_has_field (s, "payload")) {
++ /* can only fixate if there is a field */
++ gst_structure_fixate_field_nearest_int (s, "payload",
++ GST_BASE_RTP_PAYLOAD_PT (payload));
++ gst_structure_get_int (s, "payload", &pt);
++ GST_LOG_OBJECT (payload, "using peer pt %d", pt);
++ } else {
++ /* no pt field, use the internal pt */
++ pt = GST_BASE_RTP_PAYLOAD_PT (payload);
++ gst_structure_set (s, "payload", G_TYPE_INT, pt, NULL);
++ GST_LOG_OBJECT (payload, "using internal pt %d", pt);
++ }
++ }
++
++ if (gst_structure_has_field_typed (s, "ssrc", G_TYPE_UINT)) {
++ value = gst_structure_get_value (s, "ssrc");
++ payload->current_ssrc = g_value_get_uint (value);
++ GST_LOG_OBJECT (payload, "using peer ssrc %08x", payload->current_ssrc);
++ } else {
++ /* FIXME, fixate_nearest_uint would be even better */
++ gst_structure_set (s, "ssrc", G_TYPE_UINT, payload->current_ssrc, NULL);
++ GST_LOG_OBJECT (payload, "using internal ssrc %08x",
++ payload->current_ssrc);
++ }
++
++ if (gst_structure_has_field_typed (s, "clock-base", G_TYPE_UINT)) {
++ value = gst_structure_get_value (s, "clock-base");
++ payload->ts_base = g_value_get_uint (value);
++ GST_LOG_OBJECT (payload, "using peer clock-base %u", payload->ts_base);
++ } else {
++ /* FIXME, fixate_nearest_uint would be even better */
++ gst_structure_set (s, "clock-base", G_TYPE_UINT, payload->ts_base, NULL);
++ GST_LOG_OBJECT (payload, "using internal clock-base %u",
++ payload->ts_base);
++ }
++ if (gst_structure_has_field_typed (s, "seqnum-base", G_TYPE_UINT)) {
++ value = gst_structure_get_value (s, "seqnum-base");
++ payload->seqnum_base = g_value_get_uint (value);
++ GST_LOG_OBJECT (payload, "using peer seqnum-base %u",
++ payload->seqnum_base);
++ } else {
++ /* FIXME, fixate_nearest_uint would be even better */
++ gst_structure_set (s, "seqnum-base", G_TYPE_UINT, payload->seqnum_base,
++ NULL);
++ GST_LOG_OBJECT (payload, "using internal seqnum-base %u",
++ payload->seqnum_base);
++ }
++
++ /* make the target caps by copying over all the fixed caps, removing the
++ * unfixed caps. */
++ srccaps = gst_caps_new_simple (gst_structure_get_name (s), NULL);
++ d = gst_caps_get_structure (srccaps, 0);
++
++ gst_structure_foreach (s, (GstStructureForeachFunc) copy_fixed, d);
++
++ gst_caps_unref (temp);
++
++ GST_DEBUG_OBJECT (payload, "with peer caps: %" GST_PTR_FORMAT, srccaps);
++ }
++
++ update_max_ptime (payload);
++
++ res = gst_pad_set_caps (GST_BASE_RTP_PAYLOAD_SRCPAD (payload), srccaps);
++ gst_caps_unref (srccaps);
++
++ return res;
++}
++
++/**
++ * gst_basertppayload_is_filled:
++ * @payload: a #GstBaseRTPPayload
++ * @size: the size of the packet
++ * @duration: the duration of the packet
++ *
++ * Check if the packet with @size and @duration would exceed the configured
++ * maximum size.
++ *
++ * Returns: %TRUE if the packet of @size and @duration would exceed the
++ * configured MTU or max_ptime.
++ */
++gboolean
++gst_basertppayload_is_filled (GstBaseRTPPayload * payload,
++ guint size, GstClockTime duration)
++{
++ if (size > payload->mtu)
++ return TRUE;
++
++ if (payload->max_ptime != -1 && duration >= payload->max_ptime)
++ return TRUE;
++
++ return FALSE;
++}
++
++typedef struct
++{
++ GstBaseRTPPayload *payload;
++ guint32 ssrc;
++ guint16 seqnum;
++ guint8 pt;
++ GstCaps *caps;
++ GstClockTime timestamp;
++ guint64 offset;
++ guint32 rtptime;
++} HeaderData;
++
++static GstBufferListItem
++find_timestamp (GstBuffer ** buffer, guint group, guint idx, HeaderData * data)
++{
++ data->timestamp = GST_BUFFER_TIMESTAMP (*buffer);
++ data->offset = GST_BUFFER_OFFSET (*buffer);
++
++ /* stop when we find a timestamp. We take whatever offset is associated with
++ * the timestamp (if any) to do perfect timestamps when we need to. */
++ if (data->timestamp != -1)
++ return GST_BUFFER_LIST_END;
++ else
++ return GST_BUFFER_LIST_CONTINUE;
++}
++
++static GstBufferListItem
++set_headers (GstBuffer ** buffer, guint group, guint idx, HeaderData * data)
++{
++ gst_rtp_buffer_set_ssrc (*buffer, data->ssrc);
++ gst_rtp_buffer_set_payload_type (*buffer, data->pt);
++ gst_rtp_buffer_set_seq (*buffer, data->seqnum);
++ gst_rtp_buffer_set_timestamp (*buffer, data->rtptime);
++ gst_buffer_set_caps (*buffer, data->caps);
++ /* increment the seqnum for each buffer */
++ data->seqnum++;
++
++ return GST_BUFFER_LIST_SKIP_GROUP;
++}
++
++/* Updates the SSRC, payload type, seqnum and timestamp of the RTP buffer
++ * before the buffer is pushed. */
++static GstFlowReturn
++gst_basertppayload_prepare_push (GstBaseRTPPayload * payload,
++ gpointer obj, gboolean is_list)
++{
++ GstBaseRTPPayloadPrivate *priv;
++ HeaderData data;
++
++ if (payload->clock_rate == 0)
++ goto no_rate;
++
++ priv = payload->priv;
++
++ /* update first, so that the property is set to the last
++ * seqnum pushed */
++ payload->seqnum = priv->next_seqnum;
++
++ /* fill in the fields we want to set on all headers */
++ data.payload = payload;
++ data.seqnum = payload->seqnum;
++ data.ssrc = payload->current_ssrc;
++ data.pt = payload->pt;
++ data.caps = GST_PAD_CAPS (payload->srcpad);
++
++ /* find the first buffer with a timestamp */
++ if (is_list) {
++ data.timestamp = -1;
++ data.offset = GST_BUFFER_OFFSET_NONE;
++ gst_buffer_list_foreach (GST_BUFFER_LIST_CAST (obj),
++ (GstBufferListFunc) find_timestamp, &data);
++ } else {
++ data.timestamp = GST_BUFFER_TIMESTAMP (GST_BUFFER_CAST (obj));
++ data.offset = GST_BUFFER_OFFSET (GST_BUFFER_CAST (obj));
++ }
++
++ /* convert to RTP time */
++ if (priv->perfect_rtptime && data.offset != GST_BUFFER_OFFSET_NONE) {
++ /* if we have an offset, use that for making an RTP timestamp */
++ data.rtptime = payload->ts_base + data.offset;
++ GST_LOG_OBJECT (payload,
++ "Using offset %" G_GUINT64_FORMAT " for RTP timestamp", data.offset);
++ } else if (GST_CLOCK_TIME_IS_VALID (data.timestamp)) {
++ gint64 rtime;
++
++ /* no offset, use the gstreamer timestamp */
++ rtime = gst_segment_to_running_time (&payload->segment, GST_FORMAT_TIME,
++ data.timestamp);
++
++ GST_LOG_OBJECT (payload,
++ "Using running_time %" GST_TIME_FORMAT " for RTP timestamp",
++ GST_TIME_ARGS (rtime));
++
++ rtime = gst_util_uint64_scale_int (rtime, payload->clock_rate, GST_SECOND);
++
++ /* add running_time in clock-rate units to the base timestamp */
++ data.rtptime = payload->ts_base + rtime;
++ } else {
++ GST_LOG_OBJECT (payload,
++ "Using previous RTP timestamp %" G_GUINT32_FORMAT, payload->timestamp);
++ /* no timestamp to convert, take previous timestamp */
++ data.rtptime = payload->timestamp;
++ }
++
++ /* set ssrc, payload type, seq number, caps and rtptime */
++ if (is_list) {
++ gst_buffer_list_foreach (GST_BUFFER_LIST_CAST (obj),
++ (GstBufferListFunc) set_headers, &data);
++ } else {
++ GstBuffer *buf = GST_BUFFER_CAST (obj);
++ set_headers (&buf, 0, 0, &data);
++ }
++
++ priv->next_seqnum = data.seqnum;
++ payload->timestamp = data.rtptime;
++
++ GST_LOG_OBJECT (payload,
++ "Preparing to push packet with size %d, seq=%d, rtptime=%u, timestamp %"
++ GST_TIME_FORMAT, (is_list) ? -1 :
++ GST_BUFFER_SIZE (GST_BUFFER (obj)), payload->seqnum, data.rtptime,
++ GST_TIME_ARGS (data.timestamp));
++
++ return GST_FLOW_OK;
++
++ /* ERRORS */
++no_rate:
++ {
++ GST_ELEMENT_ERROR (payload, STREAM, NOT_IMPLEMENTED, (NULL),
++ ("subclass did not specify clock-rate"));
++ return GST_FLOW_ERROR;
++ }
++}
++
++/**
++ * gst_basertppayload_push_list:
++ * @payload: a #GstBaseRTPPayload
++ * @list: a #GstBufferList
++ *
++ * Push @list to the peer element of the payloader. The SSRC, payload type,
++ * seqnum and timestamp of the RTP buffer will be updated first.
++ *
++ * This function takes ownership of @list.
++ *
++ * Returns: a #GstFlowReturn.
++ *
++ * Since: 0.10.24
++ */
++GstFlowReturn
++gst_basertppayload_push_list (GstBaseRTPPayload * payload, GstBufferList * list)
++{
++ GstFlowReturn res;
++
++ res = gst_basertppayload_prepare_push (payload, list, TRUE);
++
++ if (G_LIKELY (res == GST_FLOW_OK))
++ res = gst_pad_push_list (payload->srcpad, list);
++ else
++ gst_buffer_list_unref (list);
++
++ return res;
++}
++
++/**
++ * gst_basertppayload_push:
++ * @payload: a #GstBaseRTPPayload
++ * @buffer: a #GstBuffer
++ *
++ * Push @buffer to the peer element of the payloader. The SSRC, payload type,
++ * seqnum and timestamp of the RTP buffer will be updated first.
++ *
++ * This function takes ownership of @buffer.
++ *
++ * Returns: a #GstFlowReturn.
++ */
++GstFlowReturn
++gst_basertppayload_push (GstBaseRTPPayload * payload, GstBuffer * buffer)
++{
++ GstFlowReturn res;
++
++ res = gst_basertppayload_prepare_push (payload, buffer, FALSE);
++
++ if (G_LIKELY (res == GST_FLOW_OK))
++ res = gst_pad_push (payload->srcpad, buffer);
++ else
++ gst_buffer_unref (buffer);
++
++ return res;
++}
++
++static void
++gst_basertppayload_set_property (GObject * object, guint prop_id,
++ const GValue * value, GParamSpec * pspec)
++{
++ GstBaseRTPPayload *basertppayload;
++ GstBaseRTPPayloadPrivate *priv;
++ gint64 val;
++
++ basertppayload = GST_BASE_RTP_PAYLOAD (object);
++ priv = basertppayload->priv;
++
++ switch (prop_id) {
++ case PROP_MTU:
++ basertppayload->mtu = g_value_get_uint (value);
++ break;
++ case PROP_PT:
++ basertppayload->pt = g_value_get_uint (value);
++ break;
++ case PROP_SSRC:
++ val = g_value_get_uint (value);
++ basertppayload->ssrc = val;
++ priv->ssrc_random = FALSE;
++ break;
++ case PROP_TIMESTAMP_OFFSET:
++ val = g_value_get_uint (value);
++ basertppayload->ts_offset = val;
++ priv->ts_offset_random = FALSE;
++ break;
++ case PROP_SEQNUM_OFFSET:
++ val = g_value_get_int (value);
++ basertppayload->seqnum_offset = val;
++ priv->seqnum_offset_random = (val == -1);
++ GST_DEBUG_OBJECT (basertppayload, "seqnum offset 0x%04x, random %d",
++ basertppayload->seqnum_offset, priv->seqnum_offset_random);
++ break;
++ case PROP_MAX_PTIME:
++ basertppayload->priv->prop_max_ptime = g_value_get_int64 (value);
++ update_max_ptime (basertppayload);
++ break;
++ case PROP_MIN_PTIME:
++ basertppayload->min_ptime = g_value_get_int64 (value);
++ break;
++ case PROP_PERFECT_RTPTIME:
++ priv->perfect_rtptime = g_value_get_boolean (value);
++ break;
++ case PROP_PTIME_MULTIPLE:
++ basertppayload->abidata.ABI.ptime_multiple = g_value_get_int64 (value);
++ break;
++ default:
++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++ break;
++ }
++}
++
++static void
++gst_basertppayload_get_property (GObject * object, guint prop_id,
++ GValue * value, GParamSpec * pspec)
++{
++ GstBaseRTPPayload *basertppayload;
++ GstBaseRTPPayloadPrivate *priv;
++
++ basertppayload = GST_BASE_RTP_PAYLOAD (object);
++ priv = basertppayload->priv;
++
++ switch (prop_id) {
++ case PROP_MTU:
++ g_value_set_uint (value, basertppayload->mtu);
++ break;
++ case PROP_PT:
++ g_value_set_uint (value, basertppayload->pt);
++ break;
++ case PROP_SSRC:
++ if (priv->ssrc_random)
++ g_value_set_uint (value, -1);
++ else
++ g_value_set_uint (value, basertppayload->ssrc);
++ break;
++ case PROP_TIMESTAMP_OFFSET:
++ if (priv->ts_offset_random)
++ g_value_set_uint (value, -1);
++ else
++ g_value_set_uint (value, (guint32) basertppayload->ts_offset);
++ break;
++ case PROP_SEQNUM_OFFSET:
++ if (priv->seqnum_offset_random)
++ g_value_set_int (value, -1);
++ else
++ g_value_set_int (value, (guint16) basertppayload->seqnum_offset);
++ break;
++ case PROP_MAX_PTIME:
++ g_value_set_int64 (value, basertppayload->max_ptime);
++ break;
++ case PROP_MIN_PTIME:
++ g_value_set_int64 (value, basertppayload->min_ptime);
++ break;
++ case PROP_TIMESTAMP:
++ g_value_set_uint (value, basertppayload->timestamp);
++ break;
++ case PROP_SEQNUM:
++ g_value_set_uint (value, basertppayload->seqnum);
++ break;
++ case PROP_PERFECT_RTPTIME:
++ g_value_set_boolean (value, priv->perfect_rtptime);
++ break;
++ case PROP_PTIME_MULTIPLE:
++ g_value_set_int64 (value, basertppayload->abidata.ABI.ptime_multiple);
++ break;
++ default:
++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++ break;
++ }
++}
++
++static GstStateChangeReturn
++gst_basertppayload_change_state (GstElement * element,
++ GstStateChange transition)
++{
++ GstBaseRTPPayload *basertppayload;
++ GstBaseRTPPayloadPrivate *priv;
++ GstStateChangeReturn ret;
++
++ basertppayload = GST_BASE_RTP_PAYLOAD (element);
++ priv = basertppayload->priv;
++
++ switch (transition) {
++ case GST_STATE_CHANGE_NULL_TO_READY:
++ break;
++ case GST_STATE_CHANGE_READY_TO_PAUSED:
++ gst_segment_init (&basertppayload->segment, GST_FORMAT_UNDEFINED);
++
++ if (priv->seqnum_offset_random)
++ basertppayload->seqnum_base = g_random_int_range (0, G_MAXUINT16);
++ else
++ basertppayload->seqnum_base = basertppayload->seqnum_offset;
++ priv->next_seqnum = basertppayload->seqnum_base;
++ basertppayload->seqnum = basertppayload->seqnum_base;
++
++ if (priv->ssrc_random)
++ basertppayload->current_ssrc = g_random_int ();
++ else
++ basertppayload->current_ssrc = basertppayload->ssrc;
++
++ if (priv->ts_offset_random)
++ basertppayload->ts_base = g_random_int ();
++ else
++ basertppayload->ts_base = basertppayload->ts_offset;
++ basertppayload->timestamp = basertppayload->ts_base;
++ break;
++ default:
++ break;
++ }
++
++ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
++
++ switch (transition) {
++ case GST_STATE_CHANGE_READY_TO_NULL:
++ break;
++ default:
++ break;
++ }
++ return ret;
++}
--- /dev/null
--- /dev/null
++*************** struct _GstBaseRTPPayloadPrivate
++*** 48,53 ****
++ guint16 next_seqnum;
++ gboolean perfect_rtptime;
++ gboolean timestamp_valid;
++
++ gint64 prop_max_ptime;
++ gint64 caps_max_ptime;
++--- 48,54 ----
++ guint16 next_seqnum;
++ gboolean perfect_rtptime;
++ gboolean timestamp_valid;
+++ gboolean notified_first_timestamp;
++
++ gint64 prop_max_ptime;
++ gint64 caps_max_ptime;
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) <2005> Philippe Khalaf <burger@speedy.org>
++ * Copyright (C) <2006> Wim Taymans <wim@fluendo.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., 59 Temple Place - Suite 330,
++ * Boston, MA 02111-1307, USA.
++ */
++
++/**
++ * SECTION:gstrtpbuffer
++ * @short_description: Helper methods for dealing with RTP buffers
++ * @see_also: #GstBaseRTPPayload, #GstBaseRTPDepayload, gstrtcpbuffer
++ *
++ * <refsect2>
++ * <para>
++ * The GstRTPBuffer helper functions makes it easy to parse and create regular
++ * #GstBuffer objects that contain RTP payloads. These buffers are typically of
++ * 'application/x-rtp' #GstCaps.
++ * </para>
++ * </refsect2>
++ *
++ * Last reviewed on 2006-07-17 (0.10.10)
++ */
++
++#include "gstrtpbuffer.h"
++
++#include <stdlib.h>
++#include <string.h>
++
++#define GST_RTP_HEADER_LEN 12
++
++/* Note: we use bitfields here to make sure the compiler doesn't add padding
++ * between fields on certain architectures; can't assume aligned access either
++ */
++typedef struct _GstRTPHeader
++{
++#if G_BYTE_ORDER == G_LITTLE_ENDIAN
++ unsigned int csrc_count:4; /* CSRC count */
++ unsigned int extension:1; /* header extension flag */
++ unsigned int padding:1; /* padding flag */
++ unsigned int version:2; /* protocol version */
++ unsigned int payload_type:7; /* payload type */
++ unsigned int marker:1; /* marker bit */
++#elif G_BYTE_ORDER == G_BIG_ENDIAN
++ unsigned int version:2; /* protocol version */
++ unsigned int padding:1; /* padding flag */
++ unsigned int extension:1; /* header extension flag */
++ unsigned int csrc_count:4; /* CSRC count */
++ unsigned int marker:1; /* marker bit */
++ unsigned int payload_type:7; /* payload type */
++#else
++#error "G_BYTE_ORDER should be big or little endian."
++#endif
++ unsigned int seq:16; /* sequence number */
++ unsigned int timestamp:32; /* timestamp */
++ unsigned int ssrc:32; /* synchronization source */
++ guint8 csrclist[4]; /* optional CSRC list, 32 bits each */
++} GstRTPHeader;
++
++#define GST_RTP_HEADER_VERSION(data) (((GstRTPHeader *)(data))->version)
++#define GST_RTP_HEADER_PADDING(data) (((GstRTPHeader *)(data))->padding)
++#define GST_RTP_HEADER_EXTENSION(data) (((GstRTPHeader *)(data))->extension)
++#define GST_RTP_HEADER_CSRC_COUNT(data) (((GstRTPHeader *)(data))->csrc_count)
++#define GST_RTP_HEADER_MARKER(data) (((GstRTPHeader *)(data))->marker)
++#define GST_RTP_HEADER_PAYLOAD_TYPE(data) (((GstRTPHeader *)(data))->payload_type)
++#define GST_RTP_HEADER_SEQ(data) (((GstRTPHeader *)(data))->seq)
++#define GST_RTP_HEADER_TIMESTAMP(data) (((GstRTPHeader *)(data))->timestamp)
++#define GST_RTP_HEADER_SSRC(data) (((GstRTPHeader *)(data))->ssrc)
++#define GST_RTP_HEADER_CSRC_LIST_OFFSET(data,i) \
++ data + G_STRUCT_OFFSET(GstRTPHeader, csrclist) + \
++ ((i) * sizeof(guint32))
++#define GST_RTP_HEADER_CSRC_SIZE(data) (GST_RTP_HEADER_CSRC_COUNT(data) * sizeof (guint32))
++
++typedef enum
++{
++ PAYLOAD_TYPE,
++ SEQ,
++ TIMESTAMP,
++ SSRC,
++ NO_MORE
++} rtp_header_data_type;
++
++static gboolean validate_data (guint8 * data, guint len, guint8 * payload,
++ guint payload_len);
++static guint8 *gst_rtp_buffer_list_get_data (GstBufferList * list);
++static void gst_rtp_buffer_list_set_rtp_headers (GstBufferList * list,
++ gpointer data, rtp_header_data_type type);
++static void gst_rtp_buffer_list_set_data (guint8 * rtp_header, gpointer data,
++ rtp_header_data_type type);
++
++/**
++ * gst_rtp_buffer_allocate_data:
++ * @buffer: a #GstBuffer
++ * @payload_len: the length of the payload
++ * @pad_len: the amount of padding
++ * @csrc_count: the number of CSRC entries
++ *
++ * Allocate enough data in @buffer to hold an RTP packet with @csrc_count CSRCs,
++ * a payload length of @payload_len and padding of @pad_len.
++ * MALLOCDATA of @buffer will be overwritten and will not be freed.
++ * All other RTP header fields will be set to 0/FALSE.
++ */
++void
++gst_rtp_buffer_allocate_data (GstBuffer * buffer, guint payload_len,
++ guint8 pad_len, guint8 csrc_count)
++{
++ guint len;
++ guint8 *data;
++
++ g_return_if_fail (csrc_count <= 15);
++ g_return_if_fail (GST_IS_BUFFER (buffer));
++
++ len = GST_RTP_HEADER_LEN + csrc_count * sizeof (guint32)
++ + payload_len + pad_len;
++
++ data = g_malloc (len);
++ GST_BUFFER_MALLOCDATA (buffer) = data;
++ GST_BUFFER_DATA (buffer) = data;
++ GST_BUFFER_SIZE (buffer) = len;
++
++ /* fill in defaults */
++ GST_RTP_HEADER_VERSION (data) = GST_RTP_VERSION;
++ GST_RTP_HEADER_PADDING (data) = FALSE;
++ GST_RTP_HEADER_EXTENSION (data) = FALSE;
++ GST_RTP_HEADER_CSRC_COUNT (data) = csrc_count;
++ memset (GST_RTP_HEADER_CSRC_LIST_OFFSET (data, 0), 0,
++ csrc_count * sizeof (guint32));
++ GST_RTP_HEADER_MARKER (data) = FALSE;
++ GST_RTP_HEADER_PAYLOAD_TYPE (data) = 0;
++ GST_RTP_HEADER_SEQ (data) = 0;
++ GST_RTP_HEADER_TIMESTAMP (data) = 0;
++ GST_RTP_HEADER_SSRC (data) = 0;
++}
++
++/**
++ * gst_rtp_buffer_new_take_data:
++ * @data: data for the new buffer
++ * @len: the length of data
++ *
++ * Create a new buffer and set the data and size of the buffer to @data and @len
++ * respectively. @data will be freed when the buffer is unreffed, so this
++ * function transfers ownership of @data to the new buffer.
++ *
++ * Returns: A newly allocated buffer with @data and of size @len.
++ */
++GstBuffer *
++gst_rtp_buffer_new_take_data (gpointer data, guint len)
++{
++ GstBuffer *result;
++
++ g_return_val_if_fail (data != NULL, NULL);
++ g_return_val_if_fail (len > 0, NULL);
++
++ result = gst_buffer_new ();
++
++ GST_BUFFER_MALLOCDATA (result) = data;
++ GST_BUFFER_DATA (result) = data;
++ GST_BUFFER_SIZE (result) = len;
++
++ return result;
++}
++
++/**
++ * gst_rtp_buffer_new_copy_data:
++ * @data: data for the new buffer
++ * @len: the length of data
++ *
++ * Create a new buffer and set the data to a copy of @len
++ * bytes of @data and the size to @len. The data will be freed when the buffer
++ * is freed.
++ *
++ * Returns: A newly allocated buffer with a copy of @data and of size @len.
++ */
++GstBuffer *
++gst_rtp_buffer_new_copy_data (gpointer data, guint len)
++{
++ return gst_rtp_buffer_new_take_data (g_memdup (data, len), len);
++}
++
++/**
++ * gst_rtp_buffer_new_allocate:
++ * @payload_len: the length of the payload
++ * @pad_len: the amount of padding
++ * @csrc_count: the number of CSRC entries
++ *
++ * Allocate a new #Gstbuffer with enough data to hold an RTP packet with @csrc_count CSRCs,
++ * a payload length of @payload_len and padding of @pad_len.
++ * All other RTP header fields will be set to 0/FALSE.
++ *
++ * Returns: A newly allocated buffer that can hold an RTP packet with given
++ * parameters.
++ */
++GstBuffer *
++gst_rtp_buffer_new_allocate (guint payload_len, guint8 pad_len,
++ guint8 csrc_count)
++{
++ GstBuffer *result;
++
++ g_return_val_if_fail (csrc_count <= 15, NULL);
++
++ result = gst_buffer_new ();
++ gst_rtp_buffer_allocate_data (result, payload_len, pad_len, csrc_count);
++
++ return result;
++}
++
++/**
++ * gst_rtp_buffer_new_allocate_len:
++ * @packet_len: the total length of the packet
++ * @pad_len: the amount of padding
++ * @csrc_count: the number of CSRC entries
++ *
++ * Create a new #GstBuffer that can hold an RTP packet that is exactly
++ * @packet_len long. The length of the payload depends on @pad_len and
++ * @csrc_count and can be calculated with gst_rtp_buffer_calc_payload_len().
++ * All RTP header fields will be set to 0/FALSE.
++ *
++ * Returns: A newly allocated buffer that can hold an RTP packet of @packet_len.
++ */
++GstBuffer *
++gst_rtp_buffer_new_allocate_len (guint packet_len, guint8 pad_len,
++ guint8 csrc_count)
++{
++ guint len;
++
++ g_return_val_if_fail (csrc_count <= 15, NULL);
++
++ len = gst_rtp_buffer_calc_payload_len (packet_len, pad_len, csrc_count);
++
++ return gst_rtp_buffer_new_allocate (len, pad_len, csrc_count);
++}
++
++/**
++ * gst_rtp_buffer_calc_header_len:
++ * @csrc_count: the number of CSRC entries
++ *
++ * Calculate the header length of an RTP packet with @csrc_count CSRC entries.
++ * An RTP packet can have at most 15 CSRC entries.
++ *
++ * Returns: The length of an RTP header with @csrc_count CSRC entries.
++ */
++guint
++gst_rtp_buffer_calc_header_len (guint8 csrc_count)
++{
++ g_return_val_if_fail (csrc_count <= 15, 0);
++
++ return GST_RTP_HEADER_LEN + (csrc_count * sizeof (guint32));
++}
++
++/**
++ * gst_rtp_buffer_calc_packet_len:
++ * @payload_len: the length of the payload
++ * @pad_len: the amount of padding
++ * @csrc_count: the number of CSRC entries
++ *
++ * Calculate the total length of an RTP packet with a payload size of @payload_len,
++ * a padding of @pad_len and a @csrc_count CSRC entries.
++ *
++ * Returns: The total length of an RTP header with given parameters.
++ */
++guint
++gst_rtp_buffer_calc_packet_len (guint payload_len, guint8 pad_len,
++ guint8 csrc_count)
++{
++ g_return_val_if_fail (csrc_count <= 15, 0);
++
++ return payload_len + GST_RTP_HEADER_LEN + (csrc_count * sizeof (guint32))
++ + pad_len;
++}
++
++/**
++ * gst_rtp_buffer_calc_payload_len:
++ * @packet_len: the length of the total RTP packet
++ * @pad_len: the amount of padding
++ * @csrc_count: the number of CSRC entries
++ *
++ * Calculate the length of the payload of an RTP packet with size @packet_len,
++ * a padding of @pad_len and a @csrc_count CSRC entries.
++ *
++ * Returns: The length of the payload of an RTP packet with given parameters.
++ */
++guint
++gst_rtp_buffer_calc_payload_len (guint packet_len, guint8 pad_len,
++ guint8 csrc_count)
++{
++ g_return_val_if_fail (csrc_count <= 15, 0);
++
++ return packet_len - GST_RTP_HEADER_LEN - (csrc_count * sizeof (guint32))
++ - pad_len;
++}
++
++/**
++ * gst_rtp_buffer_validate_data:
++ * @data: the data to validate
++ * @len: the length of @data to validate
++ *
++ * Check if the @data and @size point to the data of a valid RTP packet.
++ * This function checks the length, version and padding of the packet data.
++ * Use this function to validate a packet before using the other functions in
++ * this module.
++ *
++ * Returns: TRUE if the data points to a valid RTP packet.
++ */
++gboolean
++gst_rtp_buffer_validate_data (guint8 * data, guint len)
++{
++ return validate_data (data, len, NULL, 0);
++}
++
++/**
++ * gst_rtp_buffer_validate:
++ * @buffer: the buffer to validate
++ *
++ * Check if the data pointed to by @buffer is a valid RTP packet using
++ * validate_data().
++ * Use this function to validate a packet before using the other functions in
++ * this module.
++ *
++ * Returns: TRUE if @buffer is a valid RTP packet.
++ */
++gboolean
++gst_rtp_buffer_validate (GstBuffer * buffer)
++{
++ guint8 *data;
++ guint len;
++
++ g_return_val_if_fail (GST_IS_BUFFER (buffer), FALSE);
++
++ data = GST_BUFFER_DATA (buffer);
++ len = GST_BUFFER_SIZE (buffer);
++
++ return validate_data (data, len, NULL, 0);
++}
++
++/**
++ * gst_rtp_buffer_list_validate:
++ * @list: the buffer list to validate
++ *
++ * Check if all RTP packets in the @list are valid using validate_data().
++ * Use this function to validate an list before using the other functions in
++ * this module.
++ *
++ * Returns: TRUE if @list consists only of valid RTP packets.
++ */
++gboolean
++gst_rtp_buffer_list_validate (GstBufferList * list)
++{
++ guint16 prev_seqnum = 0;
++ GstBufferListIterator *it;
++ guint i = 0;
++
++ g_return_val_if_fail (GST_IS_BUFFER_LIST (list), FALSE);
++
++ it = gst_buffer_list_iterate (list);
++ g_return_val_if_fail (it != NULL, FALSE);
++
++ /* iterate through all the RTP packets in the list */
++ while (gst_buffer_list_iterator_next_group (it)) {
++ GstBuffer *rtpbuf;
++ GstBuffer *paybuf;
++ guint8 *packet_header;
++ guint8 *packet_payload;
++ guint payload_size;
++ guint packet_size;
++
++ /* each group should consists of 2 buffers: one containing the RTP header
++ * and the other one the payload */
++ if (gst_buffer_list_iterator_n_buffers (it) != 2)
++ goto invalid_list;
++
++ /* get the RTP header */
++ rtpbuf = gst_buffer_list_iterator_next (it);
++ packet_header = GST_BUFFER_DATA (rtpbuf);
++ if (packet_header == NULL)
++ goto invalid_list;
++
++ /* get the payload */
++ paybuf = gst_buffer_list_iterator_next (it);
++ packet_payload = GST_BUFFER_DATA (paybuf);
++ if (packet_payload == NULL) {
++ goto invalid_list;
++ }
++ payload_size = GST_BUFFER_SIZE (paybuf);
++ if (payload_size == 0) {
++ goto invalid_list;
++ }
++
++ /* the size of the RTP packet within the current group */
++ packet_size = GST_BUFFER_SIZE (rtpbuf) + payload_size;
++
++ /* check the sequence number */
++ if (G_UNLIKELY (i == 0)) {
++ prev_seqnum = g_ntohs (GST_RTP_HEADER_SEQ (packet_header));
++ i++;
++ } else {
++ if (++prev_seqnum != g_ntohs (GST_RTP_HEADER_SEQ (packet_header)))
++ goto invalid_list;
++ }
++
++ /* validate packet */
++ if (!validate_data (packet_header, packet_size, packet_payload,
++ payload_size)) {
++ goto invalid_list;
++ }
++ }
++
++ gst_buffer_list_iterator_free (it);
++ return TRUE;
++
++invalid_list:
++ gst_buffer_list_iterator_free (it);
++ g_return_val_if_reached (FALSE);
++}
++
++/**
++ * validate_data:
++ * @data: the data to validate
++ * @len: the length of @data to validate
++ * @payload: the payload if @data represents the header only
++ * @payload_len: the len of the payload
++ *
++ * Checks if @data is a valid RTP packet.
++ *
++ * Returns: TRUE if @data is a valid RTP packet
++ */
++static gboolean
++validate_data (guint8 * data, guint len, guint8 * payload, guint payload_len)
++{
++ guint8 padding;
++ guint8 csrc_count;
++ guint header_len;
++ guint8 version;
++
++ g_return_val_if_fail (data != NULL, FALSE);
++
++ header_len = GST_RTP_HEADER_LEN;
++ if (G_UNLIKELY (len < header_len))
++ goto wrong_length;
++
++ /* check version */
++ version = (data[0] & 0xc0);
++ if (G_UNLIKELY (version != (GST_RTP_VERSION << 6)))
++ goto wrong_version;
++
++ /* calc header length with csrc */
++ csrc_count = (data[0] & 0x0f);
++ header_len += csrc_count * sizeof (guint32);
++
++ /* calc extension length when present. */
++ if (data[0] & 0x10) {
++ guint8 *extpos;
++ guint16 extlen;
++
++ /* this points to the extenstion bits and header length */
++ extpos = &data[header_len];
++
++ /* skip the header and check that we have enough space */
++ header_len += 4;
++ if (G_UNLIKELY (len < header_len))
++ goto wrong_length;
++
++ /* skip id */
++ extpos += 2;
++ /* read length as the number of 32 bits words */
++ extlen = GST_READ_UINT16_BE (extpos);
++
++ header_len += extlen * sizeof (guint32);
++ }
++
++ /* check for padding */
++ if (data[0] & 0x20) {
++ if (payload)
++ padding = payload[payload_len - 1];
++ else
++ padding = data[len - 1];
++ } else {
++ padding = 0;
++ }
++
++ /* check if padding and header not bigger than packet length */
++ if (G_UNLIKELY (len < padding + header_len))
++ goto wrong_padding;
++
++ return TRUE;
++
++ /* ERRORS */
++wrong_length:
++ {
++ GST_DEBUG ("len < header_len check failed (%d < %d)", len, header_len);
++ return FALSE;
++ }
++wrong_version:
++ {
++ GST_DEBUG ("version check failed (%d != %d)", version, GST_RTP_VERSION);
++ return FALSE;
++ }
++wrong_padding:
++ {
++ GST_DEBUG ("padding check failed (%d - %d < %d)", len, header_len, padding);
++ return FALSE;
++ }
++}
++
++/**
++ * gst_rtp_buffer_set_packet_len:
++ * @buffer: the buffer
++ * @len: the new packet length
++ *
++ * Set the total @buffer size to @len. The data in the buffer will be made
++ * larger if needed. Any padding will be removed from the packet.
++ */
++void
++gst_rtp_buffer_set_packet_len (GstBuffer * buffer, guint len)
++{
++ guint oldlen;
++ guint8 *data;
++
++ oldlen = GST_BUFFER_SIZE (buffer);
++ data = GST_BUFFER_DATA (buffer);
++
++ if (oldlen < len) {
++ data = g_realloc (GST_BUFFER_MALLOCDATA (buffer), len);
++ GST_BUFFER_MALLOCDATA (buffer) = data;
++ GST_BUFFER_DATA (buffer) = data;
++ }
++ GST_BUFFER_SIZE (buffer) = len;
++
++ /* remove any padding */
++ GST_RTP_HEADER_PADDING (data) = FALSE;
++}
++
++/**
++ * gst_rtp_buffer_get_packet_len:
++ * @buffer: the buffer
++ *
++ * Return the total length of the packet in @buffer.
++ *
++ * Returns: The total length of the packet in @buffer.
++ */
++guint
++gst_rtp_buffer_get_packet_len (GstBuffer * buffer)
++{
++ return GST_BUFFER_SIZE (buffer);
++}
++
++/**
++ * gst_rtp_buffer_get_header_len:
++ * @buffer: the buffer
++ *
++ * Return the total length of the header in @buffer. This include the length of
++ * the fixed header, the CSRC list and the extension header.
++ *
++ * Returns: The total length of the header in @buffer.
++ */
++guint
++gst_rtp_buffer_get_header_len (GstBuffer * buffer)
++{
++ guint len;
++ guint8 *data;
++
++ data = GST_BUFFER_DATA (buffer);
++
++ len = GST_RTP_HEADER_LEN + GST_RTP_HEADER_CSRC_SIZE (data);
++ if (GST_RTP_HEADER_EXTENSION (data))
++ len += GST_READ_UINT16_BE (data + len + 2) * 4 + 4;
++
++ return len;
++}
++
++/**
++ * gst_rtp_buffer_get_version:
++ * @buffer: the buffer
++ *
++ * Get the version number of the RTP packet in @buffer.
++ *
++ * Returns: The version of @buffer.
++ */
++guint8
++gst_rtp_buffer_get_version (GstBuffer * buffer)
++{
++ return GST_RTP_HEADER_VERSION (GST_BUFFER_DATA (buffer));
++}
++
++/**
++ * gst_rtp_buffer_set_version:
++ * @buffer: the buffer
++ * @version: the new version
++ *
++ * Set the version of the RTP packet in @buffer to @version.
++ */
++void
++gst_rtp_buffer_set_version (GstBuffer * buffer, guint8 version)
++{
++ g_return_if_fail (version < 0x04);
++
++ GST_RTP_HEADER_VERSION (GST_BUFFER_DATA (buffer)) = version;
++}
++
++/**
++ * gst_rtp_buffer_get_padding:
++ * @buffer: the buffer
++ *
++ * Check if the padding bit is set on the RTP packet in @buffer.
++ *
++ * Returns: TRUE if @buffer has the padding bit set.
++ */
++gboolean
++gst_rtp_buffer_get_padding (GstBuffer * buffer)
++{
++ return GST_RTP_HEADER_PADDING (GST_BUFFER_DATA (buffer));
++}
++
++/**
++ * gst_rtp_buffer_set_padding:
++ * @buffer: the buffer
++ * @padding: the new padding
++ *
++ * Set the padding bit on the RTP packet in @buffer to @padding.
++ */
++void
++gst_rtp_buffer_set_padding (GstBuffer * buffer, gboolean padding)
++{
++ GST_RTP_HEADER_PADDING (GST_BUFFER_DATA (buffer)) = padding;
++}
++
++/**
++ * gst_rtp_buffer_pad_to:
++ * @buffer: the buffer
++ * @len: the new amount of padding
++ *
++ * Set the amount of padding in the RTP packet in @buffer to
++ * @len. If @len is 0, the padding is removed.
++ *
++ * NOTE: This function does not work correctly.
++ */
++void
++gst_rtp_buffer_pad_to (GstBuffer * buffer, guint len)
++{
++ guint8 *data;
++
++ data = GST_BUFFER_DATA (buffer);
++
++ if (len > 0)
++ GST_RTP_HEADER_PADDING (data) = TRUE;
++ else
++ GST_RTP_HEADER_PADDING (data) = FALSE;
++
++ /* FIXME, set the padding byte at the end of the payload data */
++}
++
++/**
++ * gst_rtp_buffer_get_extension:
++ * @buffer: the buffer
++ *
++ * Check if the extension bit is set on the RTP packet in @buffer.
++ *
++ * Returns: TRUE if @buffer has the extension bit set.
++ */
++gboolean
++gst_rtp_buffer_get_extension (GstBuffer * buffer)
++{
++ return GST_RTP_HEADER_EXTENSION (GST_BUFFER_DATA (buffer));
++}
++
++/**
++ * gst_rtp_buffer_set_extension:
++ * @buffer: the buffer
++ * @extension: the new extension
++ *
++ * Set the extension bit on the RTP packet in @buffer to @extension.
++ */
++void
++gst_rtp_buffer_set_extension (GstBuffer * buffer, gboolean extension)
++{
++ GST_RTP_HEADER_EXTENSION (GST_BUFFER_DATA (buffer)) = extension;
++}
++
++/**
++ * gst_rtp_buffer_get_extension_data:
++ * @buffer: the buffer
++ * @bits: location for result bits
++ * @data: location for data
++ * @wordlen: location for length of @data in 32 bits words
++ *
++ * Get the extension data. @bits will contain the extension 16 bits of custom
++ * data. @data will point to the data in the extension and @wordlen will contain
++ * the length of @data in 32 bits words.
++ *
++ * If @buffer did not contain an extension, this function will return %FALSE
++ * with @bits, @data and @wordlen unchanged.
++ *
++ * Returns: TRUE if @buffer had the extension bit set.
++ *
++ * Since: 0.10.15
++ */
++gboolean
++gst_rtp_buffer_get_extension_data (GstBuffer * buffer, guint16 * bits,
++ gpointer * data, guint * wordlen)
++{
++ guint len;
++ guint8 *pdata;
++
++ pdata = GST_BUFFER_DATA (buffer);
++
++ if (!GST_RTP_HEADER_EXTENSION (pdata))
++ return FALSE;
++
++ /* move to the extension */
++ len = GST_RTP_HEADER_LEN + GST_RTP_HEADER_CSRC_SIZE (pdata);
++ pdata += len;
++
++ if (bits)
++ *bits = GST_READ_UINT16_BE (pdata);
++ if (wordlen)
++ *wordlen = GST_READ_UINT16_BE (pdata + 2);
++ if (data)
++ *data = pdata + 4;
++
++ return TRUE;
++}
++
++/**
++ * gst_rtp_buffer_set_extension_data:
++ * @buffer: the buffer
++ * @bits: the bits specific for the extension
++ * @length: the length that counts the number of 32-bit words in
++ * the extension, excluding the extension header ( therefore zero is a valid length)
++ *
++ * Set the extension bit of the rtp buffer and fill in the @bits and @length of the
++ * extension header. It will refuse to set the extension data if the buffer is not
++ * large enough.
++ *
++ * Returns: True if done.
++ *
++ * Since : 0.10.18
++ */
++gboolean
++gst_rtp_buffer_set_extension_data (GstBuffer * buffer, guint16 bits,
++ guint16 length)
++{
++ guint32 min_size = 0;
++ guint8 *data;
++
++ data = GST_BUFFER_DATA (buffer);
++
++ /* check if the buffer is big enough to hold the extension */
++ min_size =
++ GST_RTP_HEADER_LEN + GST_RTP_HEADER_CSRC_SIZE (data) + 4 +
++ length * sizeof (guint32);
++ if (G_UNLIKELY (min_size > GST_BUFFER_SIZE (buffer)))
++ goto too_small;
++
++ /* now we can set the extension bit */
++ gst_rtp_buffer_set_extension (buffer, TRUE);
++
++ data += GST_RTP_HEADER_LEN + GST_RTP_HEADER_CSRC_SIZE (data);
++ GST_WRITE_UINT16_BE (data, bits);
++ GST_WRITE_UINT16_BE (data + 2, length);
++
++ return TRUE;
++
++ /* ERRORS */
++too_small:
++ {
++ g_warning
++ ("rtp buffer too small: need more than %d bytes but only have %d bytes",
++ min_size, GST_BUFFER_SIZE (buffer));
++ return FALSE;
++ }
++}
++
++/**
++ * gst_rtp_buffer_get_ssrc:
++ * @buffer: the buffer
++ *
++ * Get the SSRC of the RTP packet in @buffer.
++ *
++ * Returns: the SSRC of @buffer in host order.
++ */
++guint32
++gst_rtp_buffer_get_ssrc (GstBuffer * buffer)
++{
++ return g_ntohl (GST_RTP_HEADER_SSRC (GST_BUFFER_DATA (buffer)));
++}
++
++/**
++ * gst_rtp_buffer_list_get_ssrc:
++ * @list: the list
++ *
++ * Get the SSRC of the first RTP packet in @list.
++ * All RTP packets within @list have the same SSRC.
++ *
++ * Returns: the SSRC of @list in host order.
++ */
++guint32
++gst_rtp_buffer_list_get_ssrc (GstBufferList * list)
++{
++ guint8 *data;
++ data = gst_rtp_buffer_list_get_data (list);
++ g_return_val_if_fail (data != NULL, 0);
++ return g_ntohl (GST_RTP_HEADER_SSRC (data));
++}
++
++/**
++ * gst_rtp_buffer_set_ssrc:
++ * @buffer: the buffer
++ * @ssrc: the new SSRC
++ *
++ * Set the SSRC on the RTP packet in @buffer to @ssrc.
++ */
++void
++gst_rtp_buffer_set_ssrc (GstBuffer * buffer, guint32 ssrc)
++{
++ GST_RTP_HEADER_SSRC (GST_BUFFER_DATA (buffer)) = g_htonl (ssrc);
++}
++
++/**
++ * gst_rtp_buffer_list_set_ssrc:
++ * @list: the buffer list
++ * @ssrc: the new SSRC
++ *
++ * Set the SSRC on each RTP packet in @list to @ssrc.
++ */
++void
++gst_rtp_buffer_list_set_ssrc (GstBufferList * list, guint32 ssrc)
++{
++ gst_rtp_buffer_list_set_rtp_headers (list, &ssrc, SSRC);
++}
++
++/**
++ * gst_rtp_buffer_get_csrc_count:
++ * @buffer: the buffer
++ *
++ * Get the CSRC count of the RTP packet in @buffer.
++ *
++ * Returns: the CSRC count of @buffer.
++ */
++guint8
++gst_rtp_buffer_get_csrc_count (GstBuffer * buffer)
++{
++ return GST_RTP_HEADER_CSRC_COUNT (GST_BUFFER_DATA (buffer));
++}
++
++/**
++ * gst_rtp_buffer_get_csrc:
++ * @buffer: the buffer
++ * @idx: the index of the CSRC to get
++ *
++ * Get the CSRC at index @idx in @buffer.
++ *
++ * Returns: the CSRC at index @idx in host order.
++ */
++guint32
++gst_rtp_buffer_get_csrc (GstBuffer * buffer, guint8 idx)
++{
++ guint8 *data;
++
++ data = GST_BUFFER_DATA (buffer);
++
++ g_return_val_if_fail (idx < GST_RTP_HEADER_CSRC_COUNT (data), 0);
++
++ return GST_READ_UINT32_BE (GST_RTP_HEADER_CSRC_LIST_OFFSET (data, idx));
++}
++
++/**
++ * gst_rtp_buffer_set_csrc:
++ * @buffer: the buffer
++ * @idx: the CSRC index to set
++ * @csrc: the CSRC in host order to set at @idx
++ *
++ * Modify the CSRC at index @idx in @buffer to @csrc.
++ */
++void
++gst_rtp_buffer_set_csrc (GstBuffer * buffer, guint8 idx, guint32 csrc)
++{
++ guint8 *data;
++
++ data = GST_BUFFER_DATA (buffer);
++
++ g_return_if_fail (idx < GST_RTP_HEADER_CSRC_COUNT (data));
++
++ GST_WRITE_UINT32_BE (GST_RTP_HEADER_CSRC_LIST_OFFSET (data, idx), csrc);
++}
++
++/**
++ * gst_rtp_buffer_get_marker:
++ * @buffer: the buffer
++ *
++ * Check if the marker bit is set on the RTP packet in @buffer.
++ *
++ * Returns: TRUE if @buffer has the marker bit set.
++ */
++gboolean
++gst_rtp_buffer_get_marker (GstBuffer * buffer)
++{
++ return GST_RTP_HEADER_MARKER (GST_BUFFER_DATA (buffer));
++}
++
++/**
++ * gst_rtp_buffer_set_marker:
++ * @buffer: the buffer
++ * @marker: the new marker
++ *
++ * Set the marker bit on the RTP packet in @buffer to @marker.
++ */
++void
++gst_rtp_buffer_set_marker (GstBuffer * buffer, gboolean marker)
++{
++ GST_RTP_HEADER_MARKER (GST_BUFFER_DATA (buffer)) = marker;
++}
++
++/**
++ * gst_rtp_buffer_get_payload_type:
++ * @buffer: the buffer
++ *
++ * Get the payload type of the RTP packet in @buffer.
++ *
++ * Returns: The payload type.
++ */
++guint8
++gst_rtp_buffer_get_payload_type (GstBuffer * buffer)
++{
++ return GST_RTP_HEADER_PAYLOAD_TYPE (GST_BUFFER_DATA (buffer));
++}
++
++/**
++ * gst_rtp_buffer_list_get_payload_type:
++ * @list: the list
++ *
++ * Get the payload type of the first RTP packet in @list.
++ * All packets in @list should have the same payload type.
++ *
++ * Returns: The payload type.
++ */
++guint8
++gst_rtp_buffer_list_get_payload_type (GstBufferList * list)
++{
++ guint8 *data;
++ data = gst_rtp_buffer_list_get_data (list);
++ g_return_val_if_fail (data != NULL, 0);
++ return GST_RTP_HEADER_PAYLOAD_TYPE (data);
++}
++
++/**
++ * gst_rtp_buffer_set_payload_type:
++ * @buffer: the buffer
++ * @payload_type: the new type
++ *
++ * Set the payload type of the RTP packet in @buffer to @payload_type.
++ */
++void
++gst_rtp_buffer_set_payload_type (GstBuffer * buffer, guint8 payload_type)
++{
++ g_return_if_fail (payload_type < 0x80);
++
++ GST_RTP_HEADER_PAYLOAD_TYPE (GST_BUFFER_DATA (buffer)) = payload_type;
++}
++
++/**
++ * gst_rtp_buffer_list_set_payload_type:
++ * @list: the buffer list
++ * @payload_type: the new type
++ *
++ * Set the payload type of each RTP packet in @list to @payload_type.
++ */
++void
++gst_rtp_buffer_list_set_payload_type (GstBufferList * list, guint8 payload_type)
++{
++ g_return_if_fail (payload_type < 0x80);
++
++ gst_rtp_buffer_list_set_rtp_headers (list, &payload_type, PAYLOAD_TYPE);
++}
++
++/**
++ * gst_rtp_buffer_get_seq:
++ * @buffer: the buffer
++ *
++ * Get the sequence number of the RTP packet in @buffer.
++ *
++ * Returns: The sequence number in host order.
++ */
++guint16
++gst_rtp_buffer_get_seq (GstBuffer * buffer)
++{
++ return g_ntohs (GST_RTP_HEADER_SEQ (GST_BUFFER_DATA (buffer)));
++}
++
++/**
++ * gst_rtp_buffer_set_seq:
++ * @buffer: the buffer
++ * @seq: the new sequence number
++ *
++ * Set the sequence number of the RTP packet in @buffer to @seq.
++ */
++void
++gst_rtp_buffer_set_seq (GstBuffer * buffer, guint16 seq)
++{
++ GST_RTP_HEADER_SEQ (GST_BUFFER_DATA (buffer)) = g_htons (seq);
++}
++
++/**
++ * gst_rtp_buffer_list_set_seq:
++ * @list: the buffer list
++ * @seq: the new sequence number
++ *
++ * Set the sequence number of each RTP packet in @list to @seq.
++ *
++ * Returns: The seq number of the last packet in the list + 1.
++ */
++guint16
++gst_rtp_buffer_list_set_seq (GstBufferList * list, guint16 seq)
++{
++ gst_rtp_buffer_list_set_rtp_headers (list, &seq, SEQ);
++ return seq;
++}
++
++/**
++ * gst_rtp_buffer_get_timestamp:
++ * @buffer: the buffer
++ *
++ * Get the timestamp of the RTP packet in @buffer.
++ *
++ * Returns: The timestamp in host order.
++ */
++guint32
++gst_rtp_buffer_get_timestamp (GstBuffer * buffer)
++{
++ return g_ntohl (GST_RTP_HEADER_TIMESTAMP (GST_BUFFER_DATA (buffer)));
++}
++
++/**
++ * gst_rtp_buffer_list_get_timestamp:
++ * @list: the list
++ *
++ * Get the timestamp of the first RTP packet in @list.
++ * All packets within @list have the same timestamp.
++ *
++ * Returns: The timestamp in host order.
++ */
++guint32
++gst_rtp_buffer_list_get_timestamp (GstBufferList * list)
++{
++ guint8 *data;
++ data = gst_rtp_buffer_list_get_data (list);
++ g_return_val_if_fail (data != NULL, 0);
++ return g_ntohl (GST_RTP_HEADER_TIMESTAMP (data));
++}
++
++/**
++ * gst_rtp_buffer_set_timestamp:
++ * @buffer: the buffer
++ * @timestamp: the new timestamp
++ *
++ * Set the timestamp of the RTP packet in @buffer to @timestamp.
++ */
++void
++gst_rtp_buffer_set_timestamp (GstBuffer * buffer, guint32 timestamp)
++{
++ GST_RTP_HEADER_TIMESTAMP (GST_BUFFER_DATA (buffer)) = g_htonl (timestamp);
++}
++
++/**
++ * gst_rtp_buffer_list_set_timestamp:
++ * @list: the buffer list
++ * @timestamp: the new timestamp
++ *
++ * Set the timestamp of each RTP packet in @list to @timestamp.
++ */
++void
++gst_rtp_buffer_list_set_timestamp (GstBufferList * list, guint32 timestamp)
++{
++ gst_rtp_buffer_list_set_rtp_headers (list, ×tamp, TIMESTAMP);
++}
++
++/**
++ * gst_rtp_buffer_get_payload_subbuffer:
++ * @buffer: the buffer
++ * @offset: the offset in the payload
++ * @len: the length in the payload
++ *
++ * Create a subbuffer of the payload of the RTP packet in @buffer. @offset bytes
++ * are skipped in the payload and the subbuffer will be of size @len.
++ * If @len is -1 the total payload starting from @offset if subbuffered.
++ *
++ * Returns: A new buffer with the specified data of the payload.
++ *
++ * Since: 0.10.10
++ */
++GstBuffer *
++gst_rtp_buffer_get_payload_subbuffer (GstBuffer * buffer, guint offset,
++ guint len)
++{
++ guint poffset, plen;
++
++ plen = gst_rtp_buffer_get_payload_len (buffer);
++ /* we can't go past the length */
++ if (G_UNLIKELY (offset >= plen))
++ goto wrong_offset;
++
++ /* apply offset */
++ poffset = gst_rtp_buffer_get_header_len (buffer) + offset;
++ plen -= offset;
++
++ /* see if we need to shrink the buffer based on @len */
++ if (len != -1 && len < plen)
++ plen = len;
++
++ return gst_buffer_create_sub (buffer, poffset, plen);
++
++ /* ERRORS */
++wrong_offset:
++ {
++ g_warning ("offset=%u should be less then plen=%u", offset, plen);
++ return NULL;
++ }
++}
++
++/**
++ * gst_rtp_buffer_get_payload_buffer:
++ * @buffer: the buffer
++ *
++ * Create a buffer of the payload of the RTP packet in @buffer. This function
++ * will internally create a subbuffer of @buffer so that a memcpy can be
++ * avoided.
++ *
++ * Returns: A new buffer with the data of the payload.
++ */
++GstBuffer *
++gst_rtp_buffer_get_payload_buffer (GstBuffer * buffer)
++{
++ return gst_rtp_buffer_get_payload_subbuffer (buffer, 0, -1);
++}
++
++/**
++ * gst_rtp_buffer_get_payload_len:
++ * @buffer: the buffer
++ *
++ * Get the length of the payload of the RTP packet in @buffer.
++ *
++ * Returns: The length of the payload in @buffer.
++ */
++guint
++gst_rtp_buffer_get_payload_len (GstBuffer * buffer)
++{
++ guint len, size;
++ guint8 *data;
++
++ size = GST_BUFFER_SIZE (buffer);
++ data = GST_BUFFER_DATA (buffer);
++
++ len = size - gst_rtp_buffer_get_header_len (buffer);
++
++ if (GST_RTP_HEADER_PADDING (data))
++ len -= data[size - 1];
++
++ return len;
++}
++
++/**
++ * gst_rtp_buffer_list_get_payload_len:
++ * @buffer: the buffer
++ *
++ * Get the length of the payload of the RTP packet in @list.
++ *
++ * Returns: The length of the payload in @list.
++ */
++guint
++gst_rtp_buffer_list_get_payload_len (GstBufferList * list)
++{
++ guint len = 0;
++ GstBufferListIterator *it;
++ it = gst_buffer_list_iterate (list);
++
++ while (gst_buffer_list_iterator_next_group (it)) {
++ guint i;
++ GstBuffer *buf;
++
++ i = 0;
++ while ((buf = gst_buffer_list_iterator_next (it))) {
++ /* skip the RTP header */
++ if (!i++)
++ continue;
++ /* take the size of the current buffer */
++ len += GST_BUFFER_SIZE (buf);
++ }
++ }
++
++ gst_buffer_list_iterator_free (it);
++
++ return len;
++}
++
++/**
++ * gst_rtp_buffer_get_payload:
++ * @buffer: the buffer
++ *
++ * Get a pointer to the payload data in @buffer. This pointer is valid as long
++ * as a reference to @buffer is held.
++ *
++ * Returns: A pointer to the payload data in @buffer.
++ */
++gpointer
++gst_rtp_buffer_get_payload (GstBuffer * buffer)
++{
++ return GST_BUFFER_DATA (buffer) + gst_rtp_buffer_get_header_len (buffer);
++}
++
++/**
++ * gst_rtp_buffer_default_clock_rate:
++ * @payload_type: the static payload type
++ *
++ * Get the default clock-rate for the static payload type @payload_type.
++ *
++ * Returns: the default clock rate or -1 if the payload type is not static or
++ * the clock-rate is undefined.
++ *
++ * Since: 0.10.13
++ */
++guint32
++gst_rtp_buffer_default_clock_rate (guint8 payload_type)
++{
++ const GstRTPPayloadInfo *info;
++ guint32 res;
++
++ info = gst_rtp_payload_info_for_pt (payload_type);
++ if (!info)
++ return -1;
++
++ res = info->clock_rate;
++ /* 0 means unknown so we have to return -1 from this function */
++ if (res == 0)
++ res = -1;
++
++ return res;
++}
++
++/**
++ * gst_rtp_buffer_compare_seqnum:
++ * @seqnum1: a sequence number
++ * @seqnum2: a sequence number
++ *
++ * Compare two sequence numbers, taking care of wraparounds. This function
++ * returns the difference between @seqnum1 and @seqnum2.
++ *
++ * Returns: a negative value if @seqnum1 is bigger than @seqnum2, 0 if they
++ * are equal or a positive value if @seqnum1 is smaller than @segnum2.
++ *
++ * Since: 0.10.15
++ */
++gint
++gst_rtp_buffer_compare_seqnum (guint16 seqnum1, guint16 seqnum2)
++{
++ return (gint16) (seqnum2 - seqnum1);
++}
++
++/**
++ * gst_rtp_buffer_ext_timestamp:
++ * @exttimestamp: a previous extended timestamp
++ * @timestamp: a new timestamp
++ *
++ * Update the @exttimestamp field with @timestamp. For the first call of the
++ * method, @exttimestamp should point to a location with a value of -1.
++ *
++ * This function makes sure that the returned value is a constantly increasing
++ * value even in the case where there is a timestamp wraparound.
++ *
++ * Returns: The extended timestamp of @timestamp.
++ *
++ * Since: 0.10.15
++ */
++guint64
++gst_rtp_buffer_ext_timestamp (guint64 * exttimestamp, guint32 timestamp)
++{
++ guint64 result, diff, ext;
++
++ g_return_val_if_fail (exttimestamp != NULL, -1);
++
++ ext = *exttimestamp;
++
++ if (ext == -1) {
++ result = timestamp;
++ } else {
++ /* pick wraparound counter from previous timestamp and add to new timestamp */
++ result = timestamp + (ext & ~(G_GINT64_CONSTANT (0xffffffff)));
++
++ /* check for timestamp wraparound */
++ if (result < ext)
++ diff = ext - result;
++ else
++ diff = result - ext;
++
++ if (diff > G_MAXINT32) {
++ /* timestamp went backwards more than allowed, we wrap around and get
++ * updated extended timestamp. */
++ result += (G_GINT64_CONSTANT (1) << 32);
++ }
++ }
++ *exttimestamp = result;
++
++ return result;
++}
++
++/**
++ * gst_rtp_buffer_list_get_data:
++ * @list: a buffer list
++ *
++ * Returns ponter to the RTP header of the first packet within the list
++ *
++ * Returns: pointer to the first RTP header
++ */
++static guint8 *
++gst_rtp_buffer_list_get_data (GstBufferList * list)
++{
++ GstBufferListIterator *it;
++ GstBuffer *rtpbuf;
++
++ it = gst_buffer_list_iterate (list);
++ if (!gst_buffer_list_iterator_next_group (it))
++ goto invalid_list;
++ rtpbuf = gst_buffer_list_iterator_next (it);
++ if (!rtpbuf)
++ goto invalid_list;
++
++ gst_buffer_list_iterator_free (it);
++ return GST_BUFFER_DATA (rtpbuf);
++
++invalid_list:
++ gst_buffer_list_iterator_free (it);
++ g_return_val_if_reached (FALSE);
++}
++
++/**
++ * gst_rtp_buffer_list_set_rtp_headers:
++ * @list: a buffer list
++ * @data: data to be set
++ * @type: which field in the header to be set
++ *
++ * Sets the field specified by @type to @data.
++ * This function updates all RTP headers within @list.
++ */
++static void
++gst_rtp_buffer_list_set_rtp_headers (GstBufferList * list,
++ gpointer data, rtp_header_data_type type)
++{
++ GstBufferListIterator *it;
++ it = gst_buffer_list_iterate (list);
++
++ while (gst_buffer_list_iterator_next_group (it)) {
++ GstBuffer *rtpbuf;
++ guint8 *rtp_header;
++ rtpbuf = gst_buffer_list_iterator_next (it);
++ rtp_header = GST_BUFFER_DATA (rtpbuf);
++ gst_rtp_buffer_list_set_data (rtp_header, data, type);
++ }
++
++ gst_buffer_list_iterator_free (it);
++}
++
++/**
++ * gst_rtp_buffer_list_set_data:
++ * @rtp_header: rtp header to be updated
++ * @data: data to be set
++ * @type: which field in the header to be set
++ *
++ * Sets the field specified by @type to @data.
++ * When setting SEQ number, this function will also increase
++ * @data by one.
++ */
++static void
++gst_rtp_buffer_list_set_data (guint8 * rtp_header,
++ gpointer data, rtp_header_data_type type)
++{
++ switch (type) {
++ case PAYLOAD_TYPE:
++ GST_RTP_HEADER_PAYLOAD_TYPE (rtp_header) = *(guint8 *) data;
++ break;
++ case SEQ:
++ GST_RTP_HEADER_SEQ (rtp_header) = g_htons (*(guint16 *) data);
++ (*(guint16 *) data)++;
++ break;
++ case SSRC:
++ GST_RTP_HEADER_SSRC (rtp_header) = g_htonl (*(guint32 *) data);
++ break;
++ case TIMESTAMP:
++ GST_RTP_HEADER_TIMESTAMP (rtp_header) = g_htonl (*(guint32 *) data);
++ break;
++ default:
++ g_warning ("Unknown data type");
++ break;
++ }
++}
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) <2005-2009> Wim Taymans <wim.taymans@gmail.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., 59 Temple Place - Suite 330,
++ * Boston, MA 02111-1307, USA.
++ */
++/*
++ * Unless otherwise indicated, Source Code is licensed under MIT license.
++ * See further explanation attached in License Statement (distributed in the file
++ * LICENSE).
++ *
++ * Permission is hereby granted, free of charge, to any person obtaining a copy of
++ * this software and associated documentation files (the "Software"), to deal in
++ * the Software without restriction, including without limitation the rights to
++ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
++ * of the Software, and to permit persons to whom the Software is furnished to do
++ * so, subject to the following conditions:
++ *
++ * The above copyright notice and this permission notice shall be included in all
++ * copies or substantial portions of the Software.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
++ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
++ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
++ * SOFTWARE.
++ */
++
++/**
++ * SECTION:gstrtspconnection
++ * @short_description: manage RTSP connections
++ * @see_also: gstrtspurl
++ *
++ * <refsect2>
++ * <para>
++ * This object manages the RTSP connection to the server. It provides function
++ * to receive and send bytes and messages.
++ * </para>
++ * </refsect2>
++ *
++ * Last reviewed on 2007-07-24 (0.10.14)
++ */
++
++#ifdef HAVE_CONFIG_H
++# include <config.h>
++#endif
++
++#include <stdio.h>
++#include <errno.h>
++#include <stdlib.h>
++#include <string.h>
++#include <time.h>
++
++#ifdef HAVE_UNISTD_H
++#include <unistd.h>
++#endif
++
++/* we include this here to get the G_OS_* defines */
++#include <glib.h>
++#include <gst/gst.h>
++
++#ifdef G_OS_WIN32
++/* ws2_32.dll has getaddrinfo and freeaddrinfo on Windows XP and later.
++ * minwg32 headers check WINVER before allowing the use of these */
++#ifndef WINVER
++#define WINVER 0x0501
++#endif
++#include <winsock2.h>
++#include <ws2tcpip.h>
++#define EINPROGRESS WSAEINPROGRESS
++#else
++#include <sys/ioctl.h>
++#include <netdb.h>
++#include <sys/socket.h>
++#include <fcntl.h>
++#include <netinet/in.h>
++#endif
++
++#ifdef HAVE_FIONREAD_IN_SYS_FILIO
++#include <sys/filio.h>
++#endif
++
++#include "gstrtspconnection.h"
++#include "gstrtspbase64.h"
++
++union gst_sockaddr
++{
++ struct sockaddr sa;
++ struct sockaddr_in sa_in;
++ struct sockaddr_in6 sa_in6;
++ struct sockaddr_storage sa_stor;
++};
++
++typedef struct
++{
++ gint state;
++ guint save;
++ guchar out[3]; /* the size must be evenly divisible by 3 */
++ guint cout;
++ guint coutl;
++} DecodeCtx;
++
++#ifdef G_OS_WIN32
++#define READ_SOCKET(fd, buf, len) recv (fd, (char *)buf, len, 0)
++#define WRITE_SOCKET(fd, buf, len) send (fd, (const char *)buf, len, 0)
++#define SETSOCKOPT(sock, level, name, val, len) setsockopt (sock, level, name, (const char *)val, len)
++#define CLOSE_SOCKET(sock) closesocket (sock)
++#define ERRNO_IS_EAGAIN (WSAGetLastError () == WSAEWOULDBLOCK)
++#define ERRNO_IS_EINTR (WSAGetLastError () == WSAEINTR)
++/* According to Microsoft's connect() documentation this one returns
++ * WSAEWOULDBLOCK and not WSAEINPROGRESS. */
++#define ERRNO_IS_EINPROGRESS (WSAGetLastError () == WSAEWOULDBLOCK)
++#else
++#define READ_SOCKET(fd, buf, len) read (fd, buf, len)
++#define WRITE_SOCKET(fd, buf, len) write (fd, buf, len)
++#define SETSOCKOPT(sock, level, name, val, len) setsockopt (sock, level, name, val, len)
++#define CLOSE_SOCKET(sock) close (sock)
++#define ERRNO_IS_EAGAIN (errno == EAGAIN)
++#define ERRNO_IS_EINTR (errno == EINTR)
++#define ERRNO_IS_EINPROGRESS (errno == EINPROGRESS)
++#endif
++
++#define ADD_POLLFD(fdset, pfd, fd) \
++G_STMT_START { \
++ (pfd)->fd = fd; \
++ gst_poll_add_fd (fdset, pfd); \
++} G_STMT_END
++
++#define REMOVE_POLLFD(fdset, pfd) \
++G_STMT_START { \
++ if ((pfd)->fd != -1) { \
++ GST_DEBUG ("remove fd %d", (pfd)->fd); \
++ gst_poll_remove_fd (fdset, pfd); \
++ CLOSE_SOCKET ((pfd)->fd); \
++ (pfd)->fd = -1; \
++ } \
++} G_STMT_END
++
++typedef enum
++{
++ TUNNEL_STATE_NONE,
++ TUNNEL_STATE_GET,
++ TUNNEL_STATE_POST,
++ TUNNEL_STATE_COMPLETE
++} GstRTSPTunnelState;
++
++#define TUNNELID_LEN 24
++
++struct _GstRTSPConnection
++{
++ /*< private > */
++ /* URL for the connection */
++ GstRTSPUrl *url;
++
++ /* connection state */
++ GstPollFD fd0;
++ GstPollFD fd1;
++
++ GstPollFD *readfd;
++ GstPollFD *writefd;
++
++ gboolean manual_http;
++
++ gchar tunnelid[TUNNELID_LEN];
++ gboolean tunneled;
++ GstRTSPTunnelState tstate;
++
++ GstPoll *fdset;
++ gchar *ip;
++
++ gint read_ahead;
++
++ gchar *initial_buffer;
++ gsize initial_buffer_offset;
++
++ /* Session state */
++ gint cseq; /* sequence number */
++ gchar session_id[512]; /* session id */
++ gint timeout; /* session timeout in seconds */
++ GTimer *timer; /* timeout timer */
++
++ /* Authentication */
++ GstRTSPAuthMethod auth_method;
++ gchar *username;
++ gchar *passwd;
++ GHashTable *auth_params;
++
++ DecodeCtx ctx;
++ DecodeCtx *ctxp;
++
++ gchar *proxy_host;
++ guint proxy_port;
++};
++
++enum
++{
++ STATE_START = 0,
++ STATE_DATA_HEADER,
++ STATE_DATA_BODY,
++ STATE_READ_LINES,
++ STATE_END,
++ STATE_LAST
++};
++
++enum
++{
++ READ_AHEAD_EOH = -1, /* end of headers */
++ READ_AHEAD_CRLF = -2,
++ READ_AHEAD_CRLFCR = -3
++};
++
++/* a structure for constructing RTSPMessages */
++typedef struct
++{
++ gint state;
++ GstRTSPResult status;
++ guint8 buffer[4096];
++ guint offset;
++
++ guint line;
++ guint8 *body_data;
++ glong body_len;
++} GstRTSPBuilder;
++
++static void
++build_reset (GstRTSPBuilder * builder)
++{
++ g_free (builder->body_data);
++ memset (builder, 0, sizeof (GstRTSPBuilder));
++}
++
++/**
++ * gst_rtsp_connection_create:
++ * @url: a #GstRTSPUrl
++ * @conn: storage for a #GstRTSPConnection
++ *
++ * Create a newly allocated #GstRTSPConnection from @url and store it in @conn.
++ * The connection will not yet attempt to connect to @url, use
++ * gst_rtsp_connection_connect().
++ *
++ * A copy of @url will be made.
++ *
++ * Returns: #GST_RTSP_OK when @conn contains a valid connection.
++ */
++GstRTSPResult
++gst_rtsp_connection_create (const GstRTSPUrl * url, GstRTSPConnection ** conn)
++{
++ GstRTSPConnection *newconn;
++#ifdef G_OS_WIN32
++ WSADATA w;
++ int error;
++#endif
++
++ g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
++
++#ifdef G_OS_WIN32
++ error = WSAStartup (0x0202, &w);
++
++ if (error)
++ goto startup_error;
++
++ if (w.wVersion != 0x0202)
++ goto version_error;
++#endif
++
++ newconn = g_new0 (GstRTSPConnection, 1);
++
++ if ((newconn->fdset = gst_poll_new (TRUE)) == NULL)
++ goto no_fdset;
++
++ newconn->url = gst_rtsp_url_copy (url);
++ newconn->fd0.fd = -1;
++ newconn->fd1.fd = -1;
++ newconn->timer = g_timer_new ();
++ newconn->timeout = 60;
++ newconn->cseq = 1;
++
++ newconn->auth_method = GST_RTSP_AUTH_NONE;
++ newconn->username = NULL;
++ newconn->passwd = NULL;
++ newconn->auth_params = NULL;
++
++ *conn = newconn;
++
++ return GST_RTSP_OK;
++
++ /* ERRORS */
++#ifdef G_OS_WIN32
++startup_error:
++ {
++ g_warning ("Error %d on WSAStartup", error);
++ return GST_RTSP_EWSASTART;
++ }
++version_error:
++ {
++ g_warning ("Windows sockets are not version 0x202 (current 0x%x)",
++ w.wVersion);
++ WSACleanup ();
++ return GST_RTSP_EWSAVERSION;
++ }
++#endif
++no_fdset:
++ {
++ g_free (newconn);
++#ifdef G_OS_WIN32
++ WSACleanup ();
++#endif
++ return GST_RTSP_ESYS;
++ }
++}
++
++/**
++ * gst_rtsp_connection_create_from_fd:
++ * @fd: a file descriptor
++ * @ip: the IP address of the other end
++ * @port: the port used by the other end
++ * @initial_buffer: data already read from @fd
++ * @conn: storage for a #GstRTSPConnection
++ *
++ * Create a new #GstRTSPConnection for handling communication on the existing
++ * file descriptor @fd. The @initial_buffer contains any data already read from
++ * @fd which should be used before starting to read new data.
++ *
++ * Returns: #GST_RTSP_OK when @conn contains a valid connection.
++ *
++ * Since: 0.10.25
++ */
++GstRTSPResult
++gst_rtsp_connection_create_from_fd (gint fd, const gchar * ip, guint16 port,
++ const gchar * initial_buffer, GstRTSPConnection ** conn)
++{
++ GstRTSPConnection *newconn = NULL;
++ GstRTSPUrl *url;
++#ifdef G_OS_WIN32
++ gulong flags = 1;
++#endif
++ GstRTSPResult res;
++
++ g_return_val_if_fail (fd >= 0, GST_RTSP_EINVAL);
++ g_return_val_if_fail (ip != NULL, GST_RTSP_EINVAL);
++ g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
++
++ /* set to non-blocking mode so that we can cancel the communication */
++#ifndef G_OS_WIN32
++ fcntl (fd, F_SETFL, O_NONBLOCK);
++#else
++ ioctlsocket (fd, FIONBIO, &flags);
++#endif /* G_OS_WIN32 */
++
++ /* create a url for the client address */
++ url = g_new0 (GstRTSPUrl, 1);
++ url->host = g_strdup (ip);
++ url->port = port;
++
++ /* now create the connection object */
++ GST_RTSP_CHECK (gst_rtsp_connection_create (url, &newconn), newconn_failed);
++ gst_rtsp_url_free (url);
++
++ ADD_POLLFD (newconn->fdset, &newconn->fd0, fd);
++
++ /* both read and write initially */
++ newconn->readfd = &newconn->fd0;
++ newconn->writefd = &newconn->fd0;
++
++ newconn->ip = g_strdup (ip);
++
++ newconn->initial_buffer = g_strdup (initial_buffer);
++
++ *conn = newconn;
++
++ return GST_RTSP_OK;
++
++ /* ERRORS */
++newconn_failed:
++ {
++ gst_rtsp_url_free (url);
++ return res;
++ }
++}
++
++/**
++ * gst_rtsp_connection_accept:
++ * @sock: a socket
++ * @conn: storage for a #GstRTSPConnection
++ *
++ * Accept a new connection on @sock and create a new #GstRTSPConnection for
++ * handling communication on new socket.
++ *
++ * Returns: #GST_RTSP_OK when @conn contains a valid connection.
++ *
++ * Since: 0.10.23
++ */
++GstRTSPResult
++gst_rtsp_connection_accept (gint sock, GstRTSPConnection ** conn)
++{
++ int fd;
++ union gst_sockaddr sa;
++ socklen_t slen = sizeof (sa);
++ gchar ip[INET6_ADDRSTRLEN];
++ guint16 port;
++
++ g_return_val_if_fail (sock >= 0, GST_RTSP_EINVAL);
++ g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
++
++ memset (&sa, 0, slen);
++
++#ifndef G_OS_WIN32
++ fd = accept (sock, &sa.sa, &slen);
++#else
++ fd = accept (sock, &sa.sa, (gint *) & slen);
++#endif /* G_OS_WIN32 */
++ if (fd == -1)
++ goto accept_failed;
++
++ if (getnameinfo (&sa.sa, slen, ip, sizeof (ip), NULL, 0, NI_NUMERICHOST) != 0)
++ goto getnameinfo_failed;
++
++ if (sa.sa.sa_family == AF_INET)
++ port = sa.sa_in.sin_port;
++ else if (sa.sa.sa_family == AF_INET6)
++ port = sa.sa_in6.sin6_port;
++ else
++ goto wrong_family;
++
++ return gst_rtsp_connection_create_from_fd (fd, ip, port, NULL, conn);
++
++ /* ERRORS */
++accept_failed:
++ {
++ return GST_RTSP_ESYS;
++ }
++getnameinfo_failed:
++wrong_family:
++ {
++ CLOSE_SOCKET (fd);
++ return GST_RTSP_ERROR;
++ }
++}
++
++static gchar *
++do_resolve (const gchar * host)
++{
++ static gchar ip[INET6_ADDRSTRLEN];
++ struct addrinfo *aires;
++ struct addrinfo *ai;
++ gint aierr;
++
++ aierr = getaddrinfo (host, NULL, NULL, &aires);
++ if (aierr != 0)
++ goto no_addrinfo;
++
++ for (ai = aires; ai; ai = ai->ai_next) {
++ if (ai->ai_family == AF_INET || ai->ai_family == AF_INET6) {
++ break;
++ }
++ }
++ if (ai == NULL)
++ goto no_family;
++
++ aierr = getnameinfo (ai->ai_addr, ai->ai_addrlen, ip, sizeof (ip), NULL, 0,
++ NI_NUMERICHOST | NI_NUMERICSERV);
++ if (aierr != 0)
++ goto no_address;
++
++ freeaddrinfo (aires);
++
++ return g_strdup (ip);
++
++ /* ERRORS */
++no_addrinfo:
++ {
++ GST_ERROR ("no addrinfo found for %s: %s", host, gai_strerror (aierr));
++ return NULL;
++ }
++no_family:
++ {
++ GST_ERROR ("no family found for %s", host);
++ freeaddrinfo (aires);
++ return NULL;
++ }
++no_address:
++ {
++ GST_ERROR ("no address found for %s: %s", host, gai_strerror (aierr));
++ freeaddrinfo (aires);
++ return NULL;
++ }
++}
++
++static GstRTSPResult
++do_connect (const gchar * ip, guint16 port, GstPollFD * fdout,
++ GstPoll * fdset, GTimeVal * timeout)
++{
++ gint fd;
++ struct addrinfo hints;
++ struct addrinfo *aires;
++ struct addrinfo *ai;
++ gint aierr;
++ gchar service[NI_MAXSERV];
++ gint ret;
++#ifdef G_OS_WIN32
++ unsigned long flags = 1;
++#endif /* G_OS_WIN32 */
++ GstClockTime to;
++ gint retval;
++
++ memset (&hints, 0, sizeof hints);
++ hints.ai_flags = AI_NUMERICHOST;
++ hints.ai_family = AF_UNSPEC;
++ hints.ai_socktype = SOCK_STREAM;
++ g_snprintf (service, sizeof (service) - 1, "%hu", port);
++ service[sizeof (service) - 1] = '\0';
++
++ aierr = getaddrinfo (ip, service, &hints, &aires);
++ if (aierr != 0)
++ goto no_addrinfo;
++
++ for (ai = aires; ai; ai = ai->ai_next) {
++ if (ai->ai_family == AF_INET || ai->ai_family == AF_INET6) {
++ break;
++ }
++ }
++ if (ai == NULL)
++ goto no_family;
++
++ fd = socket (ai->ai_family, SOCK_STREAM, 0);
++ if (fd == -1)
++ goto no_socket;
++
++ /* set to non-blocking mode so that we can cancel the connect */
++#ifndef G_OS_WIN32
++ fcntl (fd, F_SETFL, O_NONBLOCK);
++#else
++ ioctlsocket (fd, FIONBIO, &flags);
++#endif /* G_OS_WIN32 */
++
++ /* add the socket to our fdset */
++ ADD_POLLFD (fdset, fdout, fd);
++
++ /* we are going to connect ASYNC now */
++ ret = connect (fd, ai->ai_addr, ai->ai_addrlen);
++ if (ret == 0)
++ goto done;
++ if (!ERRNO_IS_EINPROGRESS)
++ goto sys_error;
++
++ /* wait for connect to complete up to the specified timeout or until we got
++ * interrupted. */
++ gst_poll_fd_ctl_write (fdset, fdout, TRUE);
++
++ to = timeout ? GST_TIMEVAL_TO_TIME (*timeout) : GST_CLOCK_TIME_NONE;
++
++ do {
++ retval = gst_poll_wait (fdset, to);
++ } while (retval == -1 && (errno == EINTR || errno == EAGAIN));
++
++ if (retval == 0)
++ goto timeout;
++ else if (retval == -1)
++ goto sys_error;
++
++ /* we can still have an error connecting on windows */
++ if (gst_poll_fd_has_error (fdset, fdout)) {
++ socklen_t len = sizeof (errno);
++#ifndef G_OS_WIN32
++ getsockopt (fd, SOL_SOCKET, SO_ERROR, &errno, &len);
++#else
++ getsockopt (fd, SOL_SOCKET, SO_ERROR, (char *) &errno, &len);
++#endif
++ goto sys_error;
++ }
++
++ gst_poll_fd_ignored (fdset, fdout);
++
++done:
++ freeaddrinfo (aires);
++
++ return GST_RTSP_OK;
++
++ /* ERRORS */
++no_addrinfo:
++ {
++ GST_ERROR ("no addrinfo found for %s: %s", ip, gai_strerror (aierr));
++ return GST_RTSP_ERROR;
++ }
++no_family:
++ {
++ GST_ERROR ("no family found for %s", ip);
++ freeaddrinfo (aires);
++ return GST_RTSP_ERROR;
++ }
++no_socket:
++ {
++ GST_ERROR ("no socket %d (%s)", errno, g_strerror (errno));
++ freeaddrinfo (aires);
++ return GST_RTSP_ESYS;
++ }
++sys_error:
++ {
++ GST_ERROR ("system error %d (%s)", errno, g_strerror (errno));
++ REMOVE_POLLFD (fdset, fdout);
++ freeaddrinfo (aires);
++ return GST_RTSP_ESYS;
++ }
++timeout:
++ {
++ GST_ERROR ("timeout");
++ REMOVE_POLLFD (fdset, fdout);
++ freeaddrinfo (aires);
++ return GST_RTSP_ETIMEOUT;
++ }
++}
++
++static GstRTSPResult
++setup_tunneling (GstRTSPConnection * conn, GTimeVal * timeout)
++{
++ gint i;
++ GstRTSPResult res;
++ gchar *ip;
++ gchar *uri;
++ gchar *value;
++ guint16 port, url_port;
++ GstRTSPUrl *url;
++ gchar *hostparam;
++ GstRTSPMessage *msg;
++ GstRTSPMessage response;
++ gboolean old_http;
++
++ memset (&response, 0, sizeof (response));
++ gst_rtsp_message_init (&response);
++
++ /* create a random sessionid */
++ for (i = 0; i < TUNNELID_LEN; i++)
++ conn->tunnelid[i] = g_random_int_range ('a', 'z');
++ conn->tunnelid[TUNNELID_LEN - 1] = '\0';
++
++ url = conn->url;
++ /* get the port from the url */
++ gst_rtsp_url_get_port (url, &url_port);
++
++ if (conn->proxy_host) {
++ uri = g_strdup_printf ("http://%s:%d%s%s%s", url->host, url_port,
++ url->abspath, url->query ? "?" : "", url->query ? url->query : "");
++ hostparam = g_strdup_printf ("%s:%d", url->host, url_port);
++ ip = conn->proxy_host;
++ port = conn->proxy_port;
++ } else {
++ uri = g_strdup_printf ("%s%s%s", url->abspath, url->query ? "?" : "",
++ url->query ? url->query : "");
++ hostparam = NULL;
++ ip = conn->ip;
++ port = url_port;
++ }
++
++ /* create the GET request for the read connection */
++ GST_RTSP_CHECK (gst_rtsp_message_new_request (&msg, GST_RTSP_GET, uri),
++ no_message);
++ msg->type = GST_RTSP_MESSAGE_HTTP_REQUEST;
++
++ if (hostparam != NULL)
++ gst_rtsp_message_add_header (msg, GST_RTSP_HDR_HOST, hostparam);
++ gst_rtsp_message_add_header (msg, GST_RTSP_HDR_X_SESSIONCOOKIE,
++ conn->tunnelid);
++ gst_rtsp_message_add_header (msg, GST_RTSP_HDR_ACCEPT,
++ "application/x-rtsp-tunnelled");
++ gst_rtsp_message_add_header (msg, GST_RTSP_HDR_CACHE_CONTROL, "no-cache");
++ gst_rtsp_message_add_header (msg, GST_RTSP_HDR_PRAGMA, "no-cache");
++
++ /* we start by writing to this fd */
++ conn->writefd = &conn->fd0;
++
++ /* we need to temporarily set conn->tunneled to FALSE to prevent the HTTP
++ * request from being base64 encoded */
++ conn->tunneled = FALSE;
++ GST_RTSP_CHECK (gst_rtsp_connection_send (conn, msg, timeout), write_failed);
++ gst_rtsp_message_free (msg);
++ conn->tunneled = TRUE;
++
++ /* receive the response to the GET request */
++ /* we need to temporarily set manual_http to TRUE since
++ * gst_rtsp_connection_receive() will treat the HTTP response as a parsing
++ * failure otherwise */
++ old_http = conn->manual_http;
++ conn->manual_http = TRUE;
++ GST_RTSP_CHECK (gst_rtsp_connection_receive (conn, &response, timeout),
++ read_failed);
++ conn->manual_http = old_http;
++
++ if (response.type != GST_RTSP_MESSAGE_HTTP_RESPONSE ||
++ response.type_data.response.code != GST_RTSP_STS_OK)
++ goto wrong_result;
++
++ if (gst_rtsp_message_get_header (&response, GST_RTSP_HDR_X_SERVER_IP_ADDRESS,
++ &value, 0) != GST_RTSP_OK) {
++ if (conn->proxy_host) {
++ /* if we use a proxy we need to change the destination url */
++ g_free (url->host);
++ url->host = g_strdup (value);
++ g_free (hostparam);
++ hostparam = g_strdup_printf ("%s:%d", url->host, url_port);
++ } else {
++ /* and resolve the new ip address */
++ if (!(ip = do_resolve (conn->ip)))
++ goto not_resolved;
++ g_free (conn->ip);
++ conn->ip = ip;
++ }
++ }
++
++ /* connect to the host/port */
++ res = do_connect (ip, port, &conn->fd1, conn->fdset, timeout);
++ if (res != GST_RTSP_OK)
++ goto connect_failed;
++
++ /* this is now our writing socket */
++ conn->writefd = &conn->fd1;
++
++ /* create the POST request for the write connection */
++ GST_RTSP_CHECK (gst_rtsp_message_new_request (&msg, GST_RTSP_POST, uri),
++ no_message);
++ msg->type = GST_RTSP_MESSAGE_HTTP_REQUEST;
++
++ if (hostparam != NULL)
++ gst_rtsp_message_add_header (msg, GST_RTSP_HDR_HOST, hostparam);
++ gst_rtsp_message_add_header (msg, GST_RTSP_HDR_X_SESSIONCOOKIE,
++ conn->tunnelid);
++ gst_rtsp_message_add_header (msg, GST_RTSP_HDR_ACCEPT,
++ "application/x-rtsp-tunnelled");
++ gst_rtsp_message_add_header (msg, GST_RTSP_HDR_CACHE_CONTROL, "no-cache");
++ gst_rtsp_message_add_header (msg, GST_RTSP_HDR_PRAGMA, "no-cache");
++ gst_rtsp_message_add_header (msg, GST_RTSP_HDR_EXPIRES,
++ "Sun, 9 Jan 1972 00:00:00 GMT");
++ gst_rtsp_message_add_header (msg, GST_RTSP_HDR_CONTENT_LENGTH, "32767");
++
++ /* we need to temporarily set conn->tunneled to FALSE to prevent the HTTP
++ * request from being base64 encoded */
++ conn->tunneled = FALSE;
++ GST_RTSP_CHECK (gst_rtsp_connection_send (conn, msg, timeout), write_failed);
++ gst_rtsp_message_free (msg);
++ conn->tunneled = TRUE;
++
++exit:
++ gst_rtsp_message_unset (&response);
++ g_free (hostparam);
++ g_free (uri);
++
++ return res;
++
++ /* ERRORS */
++no_message:
++ {
++ GST_ERROR ("failed to create request (%d)", res);
++ goto exit;
++ }
++write_failed:
++ {
++ GST_ERROR ("write failed (%d)", res);
++ gst_rtsp_message_free (msg);
++ conn->tunneled = TRUE;
++ goto exit;
++ }
++read_failed:
++ {
++ GST_ERROR ("read failed (%d)", res);
++ conn->manual_http = FALSE;
++ goto exit;
++ }
++wrong_result:
++ {
++ GST_ERROR ("got failure response %d %s", response.type_data.response.code,
++ response.type_data.response.reason);
++ res = GST_RTSP_ERROR;
++ goto exit;
++ }
++not_resolved:
++ {
++ GST_ERROR ("could not resolve %s", conn->ip);
++ res = GST_RTSP_ENET;
++ goto exit;
++ }
++connect_failed:
++ {
++ GST_ERROR ("failed to connect");
++ goto exit;
++ }
++}
++
++/**
++ * gst_rtsp_connection_connect:
++ * @conn: a #GstRTSPConnection
++ * @timeout: a #GTimeVal timeout
++ *
++ * Attempt to connect to the url of @conn made with
++ * gst_rtsp_connection_create(). If @timeout is #NULL this function can block
++ * forever. If @timeout contains a valid timeout, this function will return
++ * #GST_RTSP_ETIMEOUT after the timeout expired.
++ *
++ * This function can be cancelled with gst_rtsp_connection_flush().
++ *
++ * Returns: #GST_RTSP_OK when a connection could be made.
++ */
++GstRTSPResult
++gst_rtsp_connection_connect (GstRTSPConnection * conn, GTimeVal * timeout)
++{
++ GstRTSPResult res;
++ gchar *ip;
++ guint16 port;
++ GstRTSPUrl *url;
++
++ g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
++ g_return_val_if_fail (conn->url != NULL, GST_RTSP_EINVAL);
++ g_return_val_if_fail (conn->fd0.fd < 0, GST_RTSP_EINVAL);
++
++ url = conn->url;
++
++ if (conn->proxy_host && conn->tunneled) {
++ if (!(ip = do_resolve (conn->proxy_host))) {
++ GST_ERROR ("could not resolve %s", conn->proxy_host);
++ goto not_resolved;
++ }
++ port = conn->proxy_port;
++ g_free (conn->proxy_host);
++ conn->proxy_host = ip;
++ } else {
++ if (!(ip = do_resolve (url->host))) {
++ GST_ERROR ("could not resolve %s", url->host);
++ goto not_resolved;
++ }
++ /* get the port from the url */
++ gst_rtsp_url_get_port (url, &port);
++
++ g_free (conn->ip);
++ conn->ip = ip;
++ }
++
++ /* connect to the host/port */
++ res = do_connect (ip, port, &conn->fd0, conn->fdset, timeout);
++ if (res != GST_RTSP_OK)
++ goto connect_failed;
++
++ /* this is our read URL */
++ conn->readfd = &conn->fd0;
++
++ if (conn->tunneled) {
++ res = setup_tunneling (conn, timeout);
++ if (res != GST_RTSP_OK)
++ goto tunneling_failed;
++ } else {
++ conn->writefd = &conn->fd0;
++ }
++
++ return GST_RTSP_OK;
++
++not_resolved:
++ {
++ return GST_RTSP_ENET;
++ }
++connect_failed:
++ {
++ GST_ERROR ("failed to connect");
++ return res;
++ }
++tunneling_failed:
++ {
++ GST_ERROR ("failed to setup tunneling");
++ return res;
++ }
++}
++
++static void
++auth_digest_compute_hex_urp (const gchar * username,
++ const gchar * realm, const gchar * password, gchar hex_urp[33])
++{
++ GChecksum *md5_context = g_checksum_new (G_CHECKSUM_MD5);
++ const gchar *digest_string;
++
++ g_checksum_update (md5_context, (const guchar *) username, strlen (username));
++ g_checksum_update (md5_context, (const guchar *) ":", 1);
++ g_checksum_update (md5_context, (const guchar *) realm, strlen (realm));
++ g_checksum_update (md5_context, (const guchar *) ":", 1);
++ g_checksum_update (md5_context, (const guchar *) password, strlen (password));
++ digest_string = g_checksum_get_string (md5_context);
++
++ memset (hex_urp, 0, 33);
++ memcpy (hex_urp, digest_string, strlen (digest_string));
++
++ g_checksum_free (md5_context);
++}
++
++static void
++auth_digest_compute_response (const gchar * method,
++ const gchar * uri, const gchar * hex_a1, const gchar * nonce,
++ gchar response[33])
++{
++ char hex_a2[33] = { 0, };
++ GChecksum *md5_context = g_checksum_new (G_CHECKSUM_MD5);
++ const gchar *digest_string;
++
++ /* compute A2 */
++ g_checksum_update (md5_context, (const guchar *) method, strlen (method));
++ g_checksum_update (md5_context, (const guchar *) ":", 1);
++ g_checksum_update (md5_context, (const guchar *) uri, strlen (uri));
++ digest_string = g_checksum_get_string (md5_context);
++ memcpy (hex_a2, digest_string, strlen (digest_string));
++
++ /* compute KD */
++ g_checksum_reset (md5_context);
++ g_checksum_update (md5_context, (const guchar *) hex_a1, strlen (hex_a1));
++ g_checksum_update (md5_context, (const guchar *) ":", 1);
++ g_checksum_update (md5_context, (const guchar *) nonce, strlen (nonce));
++ g_checksum_update (md5_context, (const guchar *) ":", 1);
++
++ g_checksum_update (md5_context, (const guchar *) hex_a2, 32);
++ digest_string = g_checksum_get_string (md5_context);
++ memset (response, 0, 33);
++ memcpy (response, digest_string, strlen (digest_string));
++
++ g_checksum_free (md5_context);
++}
++
++static void
++add_auth_header (GstRTSPConnection * conn, GstRTSPMessage * message)
++{
++ switch (conn->auth_method) {
++ case GST_RTSP_AUTH_BASIC:{
++ gchar *user_pass;
++ gchar *user_pass64;
++ gchar *auth_string;
++
++ user_pass = g_strdup_printf ("%s:%s", conn->username, conn->passwd);
++ user_pass64 = g_base64_encode ((guchar *) user_pass, strlen (user_pass));
++ auth_string = g_strdup_printf ("Basic %s", user_pass64);
++
++ gst_rtsp_message_take_header (message, GST_RTSP_HDR_AUTHORIZATION,
++ auth_string);
++
++ g_free (user_pass);
++ g_free (user_pass64);
++ break;
++ }
++ case GST_RTSP_AUTH_DIGEST:{
++ gchar response[33], hex_urp[33];
++ gchar *auth_string, *auth_string2;
++ gchar *realm;
++ gchar *nonce;
++ gchar *opaque;
++ const gchar *uri;
++ const gchar *method;
++
++ /* we need to have some params set */
++ if (conn->auth_params == NULL)
++ break;
++
++ /* we need the realm and nonce */
++ realm = (gchar *) g_hash_table_lookup (conn->auth_params, "realm");
++ nonce = (gchar *) g_hash_table_lookup (conn->auth_params, "nonce");
++ if (realm == NULL || nonce == NULL)
++ break;
++
++ auth_digest_compute_hex_urp (conn->username, realm, conn->passwd,
++ hex_urp);
++
++ method = gst_rtsp_method_as_text (message->type_data.request.method);
++ uri = message->type_data.request.uri;
++
++ /* Assume no qop, algorithm=md5, stale=false */
++ /* For algorithm MD5, a1 = urp. */
++ auth_digest_compute_response (method, uri, hex_urp, nonce, response);
++ auth_string = g_strdup_printf ("Digest username=\"%s\", "
++ "realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"",
++ conn->username, realm, nonce, uri, response);
++
++ opaque = (gchar *) g_hash_table_lookup (conn->auth_params, "opaque");
++ if (opaque) {
++ auth_string2 = g_strdup_printf ("%s, opaque=\"%s\"", auth_string,
++ opaque);
++ g_free (auth_string);
++ auth_string = auth_string2;
++ }
++ gst_rtsp_message_take_header (message, GST_RTSP_HDR_AUTHORIZATION,
++ auth_string);
++ break;
++ }
++ default:
++ /* Nothing to do */
++ break;
++ }
++}
++
++static void
++gen_date_string (gchar * date_string, guint len)
++{
++ GTimeVal tv;
++ time_t t;
++#ifdef HAVE_GMTIME_R
++ struct tm tm_;
++#endif
++
++ g_get_current_time (&tv);
++ t = (time_t) tv.tv_sec;
++
++#ifdef HAVE_GMTIME_R
++ strftime (date_string, len, "%a, %d %b %Y %H:%M:%S GMT", gmtime_r (&t, &tm_));
++#else
++ strftime (date_string, len, "%a, %d %b %Y %H:%M:%S GMT", gmtime (&t));
++#endif
++}
++
++static GstRTSPResult
++write_bytes (gint fd, const guint8 * buffer, guint * idx, guint size)
++{
++ guint left;
++
++ if (G_UNLIKELY (*idx > size))
++ return GST_RTSP_ERROR;
++
++ left = size - *idx;
++
++ while (left) {
++ gint r;
++
++ r = WRITE_SOCKET (fd, &buffer[*idx], left);
++ if (G_UNLIKELY (r == 0)) {
++ return GST_RTSP_EINTR;
++ } else if (G_UNLIKELY (r < 0)) {
++ if (ERRNO_IS_EAGAIN)
++ return GST_RTSP_EINTR;
++ if (!ERRNO_IS_EINTR)
++ return GST_RTSP_ESYS;
++ } else {
++ left -= r;
++ *idx += r;
++ }
++ }
++ return GST_RTSP_OK;
++}
++
++static gint
++fill_raw_bytes (GstRTSPConnection * conn, guint8 * buffer, guint size)
++{
++ gint out = 0;
++
++ if (G_UNLIKELY (conn->initial_buffer != NULL)) {
++ gsize left = strlen (&conn->initial_buffer[conn->initial_buffer_offset]);
++
++ out = MIN (left, size);
++ memcpy (buffer, &conn->initial_buffer[conn->initial_buffer_offset], out);
++
++ if (left == (gsize) out) {
++ g_free (conn->initial_buffer);
++ conn->initial_buffer = NULL;
++ conn->initial_buffer_offset = 0;
++ } else
++ conn->initial_buffer_offset += out;
++ }
++
++ if (G_LIKELY (size > (guint) out)) {
++ gint r;
++
++ r = READ_SOCKET (conn->readfd->fd, &buffer[out], size - out);
++ if (r <= 0) {
++ if (out == 0)
++ out = r;
++ } else
++ out += r;
++ }
++
++ return out;
++}
++
++static gint
++fill_bytes (GstRTSPConnection * conn, guint8 * buffer, guint size)
++{
++ DecodeCtx *ctx = conn->ctxp;
++ gint out = 0;
++
++ if (ctx) {
++ while (size > 0) {
++ guint8 in[sizeof (ctx->out) * 4 / 3];
++ gint r;
++
++ while (size > 0 && ctx->cout < ctx->coutl) {
++ /* we have some leftover bytes */
++ *buffer++ = ctx->out[ctx->cout++];
++ size--;
++ out++;
++ }
++
++ /* got what we needed? */
++ if (size == 0)
++ break;
++
++ /* try to read more bytes */
++ r = fill_raw_bytes (conn, in, sizeof (in));
++ if (r <= 0) {
++ if (out == 0)
++ out = r;
++ break;
++ }
++
++ ctx->cout = 0;
++ ctx->coutl =
++ g_base64_decode_step ((gchar *) in, r, ctx->out, &ctx->state,
++ &ctx->save);
++ }
++ } else {
++ out = fill_raw_bytes (conn, buffer, size);
++ }
++
++ return out;
++}
++
++static GstRTSPResult
++read_bytes (GstRTSPConnection * conn, guint8 * buffer, guint * idx, guint size)
++{
++ guint left;
++
++ if (G_UNLIKELY (*idx > size))
++ return GST_RTSP_ERROR;
++
++ left = size - *idx;
++
++ while (left) {
++ gint r;
++
++ r = fill_bytes (conn, &buffer[*idx], left);
++ if (G_UNLIKELY (r == 0)) {
++ return GST_RTSP_EEOF;
++ } else if (G_UNLIKELY (r < 0)) {
++ if (ERRNO_IS_EAGAIN)
++ return GST_RTSP_EINTR;
++ if (!ERRNO_IS_EINTR)
++ return GST_RTSP_ESYS;
++ } else {
++ left -= r;
++ *idx += r;
++ }
++ }
++ return GST_RTSP_OK;
++}
++
++/* The code below tries to handle clients using \r, \n or \r\n to indicate the
++ * end of a line. It even does its best to handle clients which mix them (even
++ * though this is a really stupid idea (tm).) It also handles Line White Space
++ * (LWS), where a line end followed by whitespace is considered LWS. This is
++ * the method used in RTSP (and HTTP) to break long lines.
++ */
++static GstRTSPResult
++read_line (GstRTSPConnection * conn, guint8 * buffer, guint * idx, guint size)
++{
++ while (TRUE) {
++ guint8 c;
++ gint r;
++
++ if (conn->read_ahead == READ_AHEAD_EOH) {
++ /* the last call to read_line() already determined that we have reached
++ * the end of the headers, so convey that information now */
++ conn->read_ahead = 0;
++ break;
++ } else if (conn->read_ahead == READ_AHEAD_CRLF) {
++ /* the last call to read_line() left off after having read \r\n */
++ c = '\n';
++ } else if (conn->read_ahead == READ_AHEAD_CRLFCR) {
++ /* the last call to read_line() left off after having read \r\n\r */
++ c = '\r';
++ } else if (conn->read_ahead != 0) {
++ /* the last call to read_line() left us with a character to start with */
++ c = (guint8) conn->read_ahead;
++ conn->read_ahead = 0;
++ } else {
++ /* read the next character */
++ r = fill_bytes (conn, &c, 1);
++ if (G_UNLIKELY (r == 0)) {
++ return GST_RTSP_EEOF;
++ } else if (G_UNLIKELY (r < 0)) {
++ if (ERRNO_IS_EAGAIN)
++ return GST_RTSP_EINTR;
++ if (!ERRNO_IS_EINTR)
++ return GST_RTSP_ESYS;
++ continue;
++ }
++ }
++
++ /* special treatment of line endings */
++ if (c == '\r' || c == '\n') {
++ guint8 read_ahead;
++
++ retry:
++ /* need to read ahead one more character to know what to do... */
++ r = fill_bytes (conn, &read_ahead, 1);
++ if (G_UNLIKELY (r == 0)) {
++ return GST_RTSP_EEOF;
++ } else if (G_UNLIKELY (r < 0)) {
++ if (ERRNO_IS_EAGAIN) {
++ /* remember the original character we read and try again next time */
++ if (conn->read_ahead == 0)
++ conn->read_ahead = c;
++ return GST_RTSP_EINTR;
++ }
++ if (!ERRNO_IS_EINTR)
++ return GST_RTSP_ESYS;
++ goto retry;
++ }
++
++ if (read_ahead == ' ' || read_ahead == '\t') {
++ if (conn->read_ahead == READ_AHEAD_CRLFCR) {
++ /* got \r\n\r followed by whitespace, treat it as a normal line
++ * followed by one starting with LWS */
++ conn->read_ahead = read_ahead;
++ break;
++ } else {
++ /* got LWS, change the line ending to a space and continue */
++ c = ' ';
++ conn->read_ahead = read_ahead;
++ }
++ } else if (conn->read_ahead == READ_AHEAD_CRLFCR) {
++ if (read_ahead == '\r' || read_ahead == '\n') {
++ /* got \r\n\r\r or \r\n\r\n, treat it as the end of the headers */
++ conn->read_ahead = READ_AHEAD_EOH;
++ break;
++ } else {
++ /* got \r\n\r followed by something else, this is not really
++ * supported since we have probably just eaten the first character
++ * of the body or the next message, so just ignore the second \r
++ * and live with it... */
++ conn->read_ahead = read_ahead;
++ break;
++ }
++ } else if (conn->read_ahead == READ_AHEAD_CRLF) {
++ if (read_ahead == '\r') {
++ /* got \r\n\r so far, need one more character... */
++ conn->read_ahead = READ_AHEAD_CRLFCR;
++ goto retry;
++ } else if (read_ahead == '\n') {
++ /* got \r\n\n, treat it as the end of the headers */
++ conn->read_ahead = READ_AHEAD_EOH;
++ break;
++ } else {
++ /* found the end of a line, keep read_ahead for the next line */
++ conn->read_ahead = read_ahead;
++ break;
++ }
++ } else if (c == read_ahead) {
++ /* got double \r or \n, treat it as the end of the headers */
++ conn->read_ahead = READ_AHEAD_EOH;
++ break;
++ } else if (c == '\r' && read_ahead == '\n') {
++ /* got \r\n so far, still need more to know what to do... */
++ conn->read_ahead = READ_AHEAD_CRLF;
++ goto retry;
++ } else {
++ /* found the end of a line, keep read_ahead for the next line */
++ conn->read_ahead = read_ahead;
++ break;
++ }
++ }
++
++ if (G_LIKELY (*idx < size - 1))
++ buffer[(*idx)++] = c;
++ }
++ buffer[*idx] = '\0';
++
++ return GST_RTSP_OK;
++}
++
++/**
++ * gst_rtsp_connection_write:
++ * @conn: a #GstRTSPConnection
++ * @data: the data to write
++ * @size: the size of @data
++ * @timeout: a timeout value or #NULL
++ *
++ * Attempt to write @size bytes of @data to the connected @conn, blocking up to
++ * the specified @timeout. @timeout can be #NULL, in which case this function
++ * might block forever.
++ *
++ * This function can be cancelled with gst_rtsp_connection_flush().
++ *
++ * Returns: #GST_RTSP_OK on success.
++ */
++GstRTSPResult
++gst_rtsp_connection_write (GstRTSPConnection * conn, const guint8 * data,
++ guint size, GTimeVal * timeout)
++{
++ guint offset;
++ gint retval;
++ GstClockTime to;
++ GstRTSPResult res;
++
++ g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
++ g_return_val_if_fail (data != NULL || size == 0, GST_RTSP_EINVAL);
++ g_return_val_if_fail (conn->writefd != NULL, GST_RTSP_EINVAL);
++
++ gst_poll_set_controllable (conn->fdset, TRUE);
++ gst_poll_fd_ctl_write (conn->fdset, conn->writefd, TRUE);
++ gst_poll_fd_ctl_read (conn->fdset, conn->readfd, FALSE);
++ /* clear all previous poll results */
++ gst_poll_fd_ignored (conn->fdset, conn->writefd);
++ gst_poll_fd_ignored (conn->fdset, conn->readfd);
++
++ to = timeout ? GST_TIMEVAL_TO_TIME (*timeout) : GST_CLOCK_TIME_NONE;
++
++ offset = 0;
++
++ while (TRUE) {
++ /* try to write */
++ res = write_bytes (conn->writefd->fd, data, &offset, size);
++ if (G_LIKELY (res == GST_RTSP_OK))
++ break;
++ if (G_UNLIKELY (res != GST_RTSP_EINTR))
++ goto write_error;
++
++ /* not all is written, wait until we can write more */
++ do {
++ retval = gst_poll_wait (conn->fdset, to);
++ } while (retval == -1 && (errno == EINTR || errno == EAGAIN));
++
++ if (G_UNLIKELY (retval == 0))
++ goto timeout;
++
++ if (G_UNLIKELY (retval == -1)) {
++ if (errno == EBUSY)
++ goto stopped;
++ else
++ goto select_error;
++ }
++ }
++ return GST_RTSP_OK;
++
++ /* ERRORS */
++timeout:
++ {
++ return GST_RTSP_ETIMEOUT;
++ }
++select_error:
++ {
++ return GST_RTSP_ESYS;
++ }
++stopped:
++ {
++ return GST_RTSP_EINTR;
++ }
++write_error:
++ {
++ return res;
++ }
++}
++
++static GString *
++message_to_string (GstRTSPConnection * conn, GstRTSPMessage * message)
++{
++ GString *str = NULL;
++
++ str = g_string_new ("");
++
++ switch (message->type) {
++ case GST_RTSP_MESSAGE_REQUEST:
++ /* create request string, add CSeq */
++ g_string_append_printf (str, "%s %s RTSP/1.0\r\n"
++ "CSeq: %d\r\n",
++ gst_rtsp_method_as_text (message->type_data.request.method),
++ message->type_data.request.uri, conn->cseq++);
++ /* add session id if we have one */
++ if (conn->session_id[0] != '\0') {
++ gst_rtsp_message_remove_header (message, GST_RTSP_HDR_SESSION, -1);
++ gst_rtsp_message_add_header (message, GST_RTSP_HDR_SESSION,
++ conn->session_id);
++ }
++ /* add any authentication headers */
++ add_auth_header (conn, message);
++ break;
++ case GST_RTSP_MESSAGE_RESPONSE:
++ /* create response string */
++ g_string_append_printf (str, "RTSP/1.0 %d %s\r\n",
++ message->type_data.response.code, message->type_data.response.reason);
++ break;
++ case GST_RTSP_MESSAGE_HTTP_REQUEST:
++ /* create request string */
++ g_string_append_printf (str, "%s %s HTTP/%s\r\n",
++ gst_rtsp_method_as_text (message->type_data.request.method),
++ message->type_data.request.uri,
++ gst_rtsp_version_as_text (message->type_data.request.version));
++ /* add any authentication headers */
++ add_auth_header (conn, message);
++ break;
++ case GST_RTSP_MESSAGE_HTTP_RESPONSE:
++ /* create response string */
++ g_string_append_printf (str, "HTTP/%s %d %s\r\n",
++ gst_rtsp_version_as_text (message->type_data.request.version),
++ message->type_data.response.code, message->type_data.response.reason);
++ break;
++ case GST_RTSP_MESSAGE_DATA:
++ {
++ guint8 data_header[4];
++
++ /* prepare data header */
++ data_header[0] = '$';
++ data_header[1] = message->type_data.data.channel;
++ data_header[2] = (message->body_size >> 8) & 0xff;
++ data_header[3] = message->body_size & 0xff;
++
++ /* create string with header and data */
++ str = g_string_append_len (str, (gchar *) data_header, 4);
++ str =
++ g_string_append_len (str, (gchar *) message->body,
++ message->body_size);
++ break;
++ }
++ default:
++ g_string_free (str, TRUE);
++ g_return_val_if_reached (NULL);
++ break;
++ }
++
++ /* append headers and body */
++ if (message->type != GST_RTSP_MESSAGE_DATA) {
++ gchar date_string[100];
++
++ gen_date_string (date_string, sizeof (date_string));
++
++ /* add date header */
++ gst_rtsp_message_remove_header (message, GST_RTSP_HDR_DATE, -1);
++ gst_rtsp_message_add_header (message, GST_RTSP_HDR_DATE, date_string);
++
++ /* append headers */
++ gst_rtsp_message_append_headers (message, str);
++
++ /* append Content-Length and body if needed */
++ if (message->body != NULL && message->body_size > 0) {
++ gchar *len;
++
++ len = g_strdup_printf ("%d", message->body_size);
++ g_string_append_printf (str, "%s: %s\r\n",
++ gst_rtsp_header_as_text (GST_RTSP_HDR_CONTENT_LENGTH), len);
++ g_free (len);
++ /* header ends here */
++ g_string_append (str, "\r\n");
++ str =
++ g_string_append_len (str, (gchar *) message->body,
++ message->body_size);
++ } else {
++ /* just end headers */
++ g_string_append (str, "\r\n");
++ }
++ }
++
++ return str;
++}
++
++/**
++ * gst_rtsp_connection_send:
++ * @conn: a #GstRTSPConnection
++ * @message: the message to send
++ * @timeout: a timeout value or #NULL
++ *
++ * Attempt to send @message to the connected @conn, blocking up to
++ * the specified @timeout. @timeout can be #NULL, in which case this function
++ * might block forever.
++ *
++ * This function can be cancelled with gst_rtsp_connection_flush().
++ *
++ * Returns: #GST_RTSP_OK on success.
++ */
++GstRTSPResult
++gst_rtsp_connection_send (GstRTSPConnection * conn, GstRTSPMessage * message,
++ GTimeVal * timeout)
++{
++ GString *string = NULL;
++ GstRTSPResult res;
++ gchar *str;
++ gsize len;
++
++ g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
++ g_return_val_if_fail (message != NULL, GST_RTSP_EINVAL);
++
++ if (G_UNLIKELY (!(string = message_to_string (conn, message))))
++ goto no_message;
++
++ if (conn->tunneled) {
++ str = g_base64_encode ((const guchar *) string->str, string->len);
++ g_string_free (string, TRUE);
++ len = strlen (str);
++ } else {
++ str = string->str;
++ len = string->len;
++ g_string_free (string, FALSE);
++ }
++
++ /* write request */
++ res = gst_rtsp_connection_write (conn, (guint8 *) str, len, timeout);
++
++ g_free (str);
++
++ return res;
++
++no_message:
++ {
++ g_warning ("Wrong message");
++ return GST_RTSP_EINVAL;
++ }
++}
++
++static GstRTSPResult
++parse_string (gchar * dest, gint size, gchar ** src)
++{
++ GstRTSPResult res = GST_RTSP_OK;
++ gint idx;
++
++ idx = 0;
++ /* skip spaces */
++ while (g_ascii_isspace (**src))
++ (*src)++;
++
++ while (!g_ascii_isspace (**src) && **src != '\0') {
++ if (idx < size - 1)
++ dest[idx++] = **src;
++ else
++ res = GST_RTSP_EPARSE;
++ (*src)++;
++ }
++ if (size > 0)
++ dest[idx] = '\0';
++
++ return res;
++}
++
++static GstRTSPResult
++parse_protocol_version (gchar * protocol, GstRTSPMsgType * type,
++ GstRTSPVersion * version)
++{
++ GstRTSPResult res = GST_RTSP_OK;
++ gchar *ver;
++
++ if (G_LIKELY ((ver = strchr (protocol, '/')) != NULL)) {
++ guint major;
++ guint minor;
++ gchar dummychar;
++
++ *ver++ = '\0';
++
++ /* the version number must be formatted as X.Y with nothing following */
++ if (sscanf (ver, "%u.%u%c", &major, &minor, &dummychar) != 2)
++ res = GST_RTSP_EPARSE;
++
++ if (g_ascii_strcasecmp (protocol, "RTSP") == 0) {
++ if (major != 1 || minor != 0) {
++ *version = GST_RTSP_VERSION_INVALID;
++ res = GST_RTSP_ERROR;
++ }
++ } else if (g_ascii_strcasecmp (protocol, "HTTP") == 0) {
++ if (*type == GST_RTSP_MESSAGE_REQUEST)
++ *type = GST_RTSP_MESSAGE_HTTP_REQUEST;
++ else if (*type == GST_RTSP_MESSAGE_RESPONSE)
++ *type = GST_RTSP_MESSAGE_HTTP_RESPONSE;
++
++ if (major == 1 && minor == 1) {
++ *version = GST_RTSP_VERSION_1_1;
++ } else if (major != 1 || minor != 0) {
++ *version = GST_RTSP_VERSION_INVALID;
++ res = GST_RTSP_ERROR;
++ }
++ } else
++ res = GST_RTSP_EPARSE;
++ } else
++ res = GST_RTSP_EPARSE;
++
++ return res;
++}
++
++static GstRTSPResult
++parse_response_status (guint8 * buffer, GstRTSPMessage * msg)
++{
++ GstRTSPResult res = GST_RTSP_OK;
++ GstRTSPResult res2;
++ gchar versionstr[20];
++ gchar codestr[4];
++ gint code;
++ gchar *bptr;
++
++ bptr = (gchar *) buffer;
++
++ if (parse_string (versionstr, sizeof (versionstr), &bptr) != GST_RTSP_OK)
++ res = GST_RTSP_EPARSE;
++
++ if (parse_string (codestr, sizeof (codestr), &bptr) != GST_RTSP_OK)
++ res = GST_RTSP_EPARSE;
++ code = atoi (codestr);
++ if (G_UNLIKELY (*codestr == '\0' || code < 0 || code >= 600))
++ res = GST_RTSP_EPARSE;
++
++ while (g_ascii_isspace (*bptr))
++ bptr++;
++
++ if (G_UNLIKELY (gst_rtsp_message_init_response (msg, code, bptr,
++ NULL) != GST_RTSP_OK))
++ res = GST_RTSP_EPARSE;
++
++ res2 = parse_protocol_version (versionstr, &msg->type,
++ &msg->type_data.response.version);
++ if (G_LIKELY (res == GST_RTSP_OK))
++ res = res2;
++
++ return res;
++}
++
++static GstRTSPResult
++parse_request_line (guint8 * buffer, GstRTSPMessage * msg)
++{
++ GstRTSPResult res = GST_RTSP_OK;
++ GstRTSPResult res2;
++ gchar versionstr[20];
++ gchar methodstr[20];
++ gchar urlstr[4096];
++ gchar *bptr;
++ GstRTSPMethod method;
++
++ bptr = (gchar *) buffer;
++
++ if (parse_string (methodstr, sizeof (methodstr), &bptr) != GST_RTSP_OK)
++ res = GST_RTSP_EPARSE;
++ method = gst_rtsp_find_method (methodstr);
++
++ if (parse_string (urlstr, sizeof (urlstr), &bptr) != GST_RTSP_OK)
++ res = GST_RTSP_EPARSE;
++ if (G_UNLIKELY (*urlstr == '\0'))
++ res = GST_RTSP_EPARSE;
++
++ if (parse_string (versionstr, sizeof (versionstr), &bptr) != GST_RTSP_OK)
++ res = GST_RTSP_EPARSE;
++
++ if (G_UNLIKELY (*bptr != '\0'))
++ res = GST_RTSP_EPARSE;
++
++ if (G_UNLIKELY (gst_rtsp_message_init_request (msg, method,
++ urlstr) != GST_RTSP_OK))
++ res = GST_RTSP_EPARSE;
++
++ res2 = parse_protocol_version (versionstr, &msg->type,
++ &msg->type_data.request.version);
++ if (G_LIKELY (res == GST_RTSP_OK))
++ res = res2;
++
++ if (G_LIKELY (msg->type == GST_RTSP_MESSAGE_REQUEST)) {
++ /* GET and POST are not allowed as RTSP methods */
++ if (msg->type_data.request.method == GST_RTSP_GET ||
++ msg->type_data.request.method == GST_RTSP_POST) {
++ msg->type_data.request.method = GST_RTSP_INVALID;
++ if (res == GST_RTSP_OK)
++ res = GST_RTSP_ERROR;
++ }
++ } else if (msg->type == GST_RTSP_MESSAGE_HTTP_REQUEST) {
++ /* only GET and POST are allowed as HTTP methods */
++ if (msg->type_data.request.method != GST_RTSP_GET &&
++ msg->type_data.request.method != GST_RTSP_POST) {
++ msg->type_data.request.method = GST_RTSP_INVALID;
++ if (res == GST_RTSP_OK)
++ res = GST_RTSP_ERROR;
++ }
++ }
++
++ return res;
++}
++
++/* parsing lines means reading a Key: Value pair */
++static GstRTSPResult
++parse_line (guint8 * buffer, GstRTSPMessage * msg)
++{
++ GstRTSPHeaderField field;
++ gchar *line = (gchar *) buffer;
++ gchar *value;
++
++ if ((value = strchr (line, ':')) == NULL || value == line)
++ goto parse_error;
++
++ /* trim space before the colon */
++ if (value[-1] == ' ')
++ value[-1] = '\0';
++
++ /* replace the colon with a NUL */
++ *value++ = '\0';
++
++ /* find the header */
++ field = gst_rtsp_find_header_field (line);
++ if (field == GST_RTSP_HDR_INVALID)
++ goto done;
++
++ /* split up the value in multiple key:value pairs if it contains comma(s) */
++ while (*value != '\0') {
++ gchar *next_value;
++ gchar *comma = NULL;
++ gboolean quoted = FALSE;
++ guint comment = 0;
++
++ /* trim leading space */
++ if (*value == ' ')
++ value++;
++
++ /* for headers which may not appear multiple times, and thus may not
++ * contain multiple values on the same line, we can short-circuit the loop
++ * below and the entire value results in just one key:value pair*/
++ if (!gst_rtsp_header_allow_multiple (field))
++ next_value = value + strlen (value);
++ else
++ next_value = value;
++
++ /* find the next value, taking special care of quotes and comments */
++ while (*next_value != '\0') {
++ if ((quoted || comment != 0) && *next_value == '\\' &&
++ next_value[1] != '\0')
++ next_value++;
++ else if (comment == 0 && *next_value == '"')
++ quoted = !quoted;
++ else if (!quoted && *next_value == '(')
++ comment++;
++ else if (comment != 0 && *next_value == ')')
++ comment--;
++ else if (!quoted && comment == 0) {
++ /* To quote RFC 2068: "User agents MUST take special care in parsing
++ * the WWW-Authenticate field value if it contains more than one
++ * challenge, or if more than one WWW-Authenticate header field is
++ * provided, since the contents of a challenge may itself contain a
++ * comma-separated list of authentication parameters."
++ *
++ * What this means is that we cannot just look for an unquoted comma
++ * when looking for multiple values in Proxy-Authenticate and
++ * WWW-Authenticate headers. Instead we need to look for the sequence
++ * "comma [space] token space token" before we can split after the
++ * comma...
++ */
++ if (field == GST_RTSP_HDR_PROXY_AUTHENTICATE ||
++ field == GST_RTSP_HDR_WWW_AUTHENTICATE) {
++ if (*next_value == ',') {
++ if (next_value[1] == ' ') {
++ /* skip any space following the comma so we do not mistake it for
++ * separating between two tokens */
++ next_value++;
++ }
++ comma = next_value;
++ } else if (*next_value == ' ' && next_value[1] != ',' &&
++ next_value[1] != '=' && comma != NULL) {
++ next_value = comma;
++ comma = NULL;
++ break;
++ }
++ } else if (*next_value == ',')
++ break;
++ }
++
++ next_value++;
++ }
++
++ /* trim space */
++ if (value != next_value && next_value[-1] == ' ')
++ next_value[-1] = '\0';
++
++ if (*next_value != '\0')
++ *next_value++ = '\0';
++
++ /* add the key:value pair */
++ if (*value != '\0')
++ gst_rtsp_message_add_header (msg, field, value);
++
++ value = next_value;
++ }
++
++done:
++ return GST_RTSP_OK;
++
++ /* ERRORS */
++parse_error:
++ {
++ return GST_RTSP_EPARSE;
++ }
++}
++
++/* convert all consecutive whitespace to a single space */
++static void
++normalize_line (guint8 * buffer)
++{
++ while (*buffer) {
++ if (g_ascii_isspace (*buffer)) {
++ guint8 *tmp;
++
++ *buffer++ = ' ';
++ for (tmp = buffer; g_ascii_isspace (*tmp); tmp++) {
++ }
++ if (buffer != tmp)
++ memmove (buffer, tmp, strlen ((gchar *) tmp) + 1);
++ } else {
++ buffer++;
++ }
++ }
++}
++
++/* returns:
++ * GST_RTSP_OK when a complete message was read.
++ * GST_RTSP_EEOF: when the socket is closed
++ * GST_RTSP_EINTR: when more data is needed.
++ * GST_RTSP_..: some other error occured.
++ */
++static GstRTSPResult
++build_next (GstRTSPBuilder * builder, GstRTSPMessage * message,
++ GstRTSPConnection * conn)
++{
++ GstRTSPResult res;
++
++ while (TRUE) {
++ switch (builder->state) {
++ case STATE_START:
++ builder->offset = 0;
++ res =
++ read_bytes (conn, (guint8 *) builder->buffer, &builder->offset, 1);
++ if (res != GST_RTSP_OK)
++ goto done;
++
++ /* we have 1 bytes now and we can see if this is a data message or
++ * not */
++ if (builder->buffer[0] == '$') {
++ /* data message, prepare for the header */
++ builder->state = STATE_DATA_HEADER;
++ } else {
++ builder->line = 0;
++ builder->state = STATE_READ_LINES;
++ }
++ break;
++ case STATE_DATA_HEADER:
++ {
++ res =
++ read_bytes (conn, (guint8 *) builder->buffer, &builder->offset, 4);
++ if (res != GST_RTSP_OK)
++ goto done;
++
++ gst_rtsp_message_init_data (message, builder->buffer[1]);
++
++ builder->body_len = (builder->buffer[2] << 8) | builder->buffer[3];
++ builder->body_data = g_malloc (builder->body_len + 1);
++ builder->body_data[builder->body_len] = '\0';
++ builder->offset = 0;
++ builder->state = STATE_DATA_BODY;
++ break;
++ }
++ case STATE_DATA_BODY:
++ {
++ res =
++ read_bytes (conn, builder->body_data, &builder->offset,
++ builder->body_len);
++ if (res != GST_RTSP_OK)
++ goto done;
++
++ /* we have the complete body now, store in the message adjusting the
++ * length to include the traling '\0' */
++ gst_rtsp_message_take_body (message,
++ (guint8 *) builder->body_data, builder->body_len + 1);
++ builder->body_data = NULL;
++ builder->body_len = 0;
++
++ builder->state = STATE_END;
++ break;
++ }
++ case STATE_READ_LINES:
++ {
++ res = read_line (conn, builder->buffer, &builder->offset,
++ sizeof (builder->buffer));
++ if (res != GST_RTSP_OK)
++ goto done;
++
++ /* we have a regular response */
++ if (builder->buffer[0] == '\0') {
++ gchar *hdrval;
++
++ /* empty line, end of message header */
++ /* see if there is a Content-Length header, but ignore it if this
++ * is a POST request with an x-sessioncookie header */
++ if (gst_rtsp_message_get_header (message,
++ GST_RTSP_HDR_CONTENT_LENGTH, &hdrval, 0) == GST_RTSP_OK &&
++ (message->type != GST_RTSP_MESSAGE_HTTP_REQUEST ||
++ message->type_data.request.method != GST_RTSP_POST ||
++ gst_rtsp_message_get_header (message,
++ GST_RTSP_HDR_X_SESSIONCOOKIE, NULL, 0) != GST_RTSP_OK)) {
++ /* there is, prepare to read the body */
++ builder->body_len = atol (hdrval);
++ builder->body_data = g_malloc (builder->body_len + 1);
++ builder->body_data[builder->body_len] = '\0';
++ builder->offset = 0;
++ builder->state = STATE_DATA_BODY;
++ } else {
++ builder->state = STATE_END;
++ }
++ break;
++ }
++
++ /* we have a line */
++ normalize_line (builder->buffer);
++ if (builder->line == 0) {
++ /* first line, check for response status */
++ if (memcmp (builder->buffer, "RTSP", 4) == 0 ||
++ memcmp (builder->buffer, "HTTP", 4) == 0) {
++ builder->status = parse_response_status (builder->buffer, message);
++ } else {
++ builder->status = parse_request_line (builder->buffer, message);
++ }
++ } else {
++ /* else just parse the line */
++ res = parse_line (builder->buffer, message);
++ if (res != GST_RTSP_OK)
++ builder->status = res;
++ }
++ builder->line++;
++ builder->offset = 0;
++ break;
++ }
++ case STATE_END:
++ {
++ gchar *session_cookie;
++ gchar *session_id;
++
++ if (message->type == GST_RTSP_MESSAGE_DATA) {
++ /* data messages don't have headers */
++ res = GST_RTSP_OK;
++ goto done;
++ }
++
++ /* save the tunnel session in the connection */
++ if (message->type == GST_RTSP_MESSAGE_HTTP_REQUEST &&
++ !conn->manual_http &&
++ conn->tstate == TUNNEL_STATE_NONE &&
++ gst_rtsp_message_get_header (message, GST_RTSP_HDR_X_SESSIONCOOKIE,
++ &session_cookie, 0) == GST_RTSP_OK) {
++ strncpy (conn->tunnelid, session_cookie, TUNNELID_LEN);
++ conn->tunnelid[TUNNELID_LEN - 1] = '\0';
++ conn->tunneled = TRUE;
++ }
++
++ /* save session id in the connection for further use */
++ if (message->type == GST_RTSP_MESSAGE_RESPONSE &&
++ gst_rtsp_message_get_header (message, GST_RTSP_HDR_SESSION,
++ &session_id, 0) == GST_RTSP_OK) {
++ gint maxlen, i;
++
++ maxlen = sizeof (conn->session_id) - 1;
++ /* the sessionid can have attributes marked with ;
++ * Make sure we strip them */
++ for (i = 0; session_id[i] != '\0'; i++) {
++ if (session_id[i] == ';') {
++ maxlen = i;
++ /* parse timeout */
++ do {
++ i++;
++ } while (g_ascii_isspace (session_id[i]));
++ if (g_str_has_prefix (&session_id[i], "timeout=")) {
++ gint to;
++
++ /* if we parsed something valid, configure */
++ if ((to = atoi (&session_id[i + 8])) > 0)
++ conn->timeout = to;
++ }
++ break;
++ }
++ }
++
++ /* make sure to not overflow */
++ strncpy (conn->session_id, session_id, maxlen);
++ conn->session_id[maxlen] = '\0';
++ }
++ res = builder->status;
++ goto done;
++ }
++ default:
++ res = GST_RTSP_ERROR;
++ break;
++ }
++ }
++done:
++ return res;
++}
++
++/**
++ * gst_rtsp_connection_read:
++ * @conn: a #GstRTSPConnection
++ * @data: the data to read
++ * @size: the size of @data
++ * @timeout: a timeout value or #NULL
++ *
++ * Attempt to read @size bytes into @data from the connected @conn, blocking up to
++ * the specified @timeout. @timeout can be #NULL, in which case this function
++ * might block forever.
++ *
++ * This function can be cancelled with gst_rtsp_connection_flush().
++ *
++ * Returns: #GST_RTSP_OK on success.
++ */
++GstRTSPResult
++gst_rtsp_connection_read (GstRTSPConnection * conn, guint8 * data, guint size,
++ GTimeVal * timeout)
++{
++ guint offset;
++ gint retval;
++ GstClockTime to;
++ GstRTSPResult res;
++
++ g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
++ g_return_val_if_fail (data != NULL, GST_RTSP_EINVAL);
++ g_return_val_if_fail (conn->readfd != NULL, GST_RTSP_EINVAL);
++
++ if (G_UNLIKELY (size == 0))
++ return GST_RTSP_OK;
++
++ offset = 0;
++
++ /* configure timeout if any */
++ to = timeout ? GST_TIMEVAL_TO_TIME (*timeout) : GST_CLOCK_TIME_NONE;
++
++ gst_poll_set_controllable (conn->fdset, TRUE);
++ gst_poll_fd_ctl_write (conn->fdset, conn->writefd, FALSE);
++ gst_poll_fd_ctl_read (conn->fdset, conn->readfd, TRUE);
++
++ while (TRUE) {
++ res = read_bytes (conn, data, &offset, size);
++ if (G_UNLIKELY (res == GST_RTSP_EEOF))
++ goto eof;
++ if (G_LIKELY (res == GST_RTSP_OK))
++ break;
++ if (G_UNLIKELY (res != GST_RTSP_EINTR))
++ goto read_error;
++
++ do {
++ retval = gst_poll_wait (conn->fdset, to);
++ } while (retval == -1 && (errno == EINTR || errno == EAGAIN));
++
++ /* check for timeout */
++ if (G_UNLIKELY (retval == 0))
++ goto select_timeout;
++
++ if (G_UNLIKELY (retval == -1)) {
++ if (errno == EBUSY)
++ goto stopped;
++ else
++ goto select_error;
++ }
++ gst_poll_set_controllable (conn->fdset, FALSE);
++ }
++ return GST_RTSP_OK;
++
++ /* ERRORS */
++select_error:
++ {
++ return GST_RTSP_ESYS;
++ }
++select_timeout:
++ {
++ return GST_RTSP_ETIMEOUT;
++ }
++stopped:
++ {
++ return GST_RTSP_EINTR;
++ }
++eof:
++ {
++ return GST_RTSP_EEOF;
++ }
++read_error:
++ {
++ return res;
++ }
++}
++
++static GstRTSPMessage *
++gen_tunnel_reply (GstRTSPConnection * conn, GstRTSPStatusCode code,
++ const GstRTSPMessage * request)
++{
++ GstRTSPMessage *msg;
++ GstRTSPResult res;
++
++ if (gst_rtsp_status_as_text (code) == NULL)
++ code = GST_RTSP_STS_INTERNAL_SERVER_ERROR;
++
++ GST_RTSP_CHECK (gst_rtsp_message_new_response (&msg, code, NULL, request),
++ no_message);
++
++ gst_rtsp_message_add_header (msg, GST_RTSP_HDR_SERVER,
++ "GStreamer RTSP Server");
++ gst_rtsp_message_add_header (msg, GST_RTSP_HDR_CONNECTION, "close");
++ gst_rtsp_message_add_header (msg, GST_RTSP_HDR_CACHE_CONTROL, "no-store");
++ gst_rtsp_message_add_header (msg, GST_RTSP_HDR_PRAGMA, "no-cache");
++
++ if (code == GST_RTSP_STS_OK) {
++ if (conn->ip)
++ gst_rtsp_message_add_header (msg, GST_RTSP_HDR_X_SERVER_IP_ADDRESS,
++ conn->ip);
++ gst_rtsp_message_add_header (msg, GST_RTSP_HDR_CONTENT_TYPE,
++ "application/x-rtsp-tunnelled");
++ }
++
++ return msg;
++
++ /* ERRORS */
++no_message:
++ {
++ return NULL;
++ }
++}
++
++/**
++ * gst_rtsp_connection_receive:
++ * @conn: a #GstRTSPConnection
++ * @message: the message to read
++ * @timeout: a timeout value or #NULL
++ *
++ * Attempt to read into @message from the connected @conn, blocking up to
++ * the specified @timeout. @timeout can be #NULL, in which case this function
++ * might block forever.
++ *
++ * This function can be cancelled with gst_rtsp_connection_flush().
++ *
++ * Returns: #GST_RTSP_OK on success.
++ */
++GstRTSPResult
++gst_rtsp_connection_receive (GstRTSPConnection * conn, GstRTSPMessage * message,
++ GTimeVal * timeout)
++{
++ GstRTSPResult res;
++ GstRTSPBuilder builder;
++ gint retval;
++ GstClockTime to;
++
++ g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
++ g_return_val_if_fail (message != NULL, GST_RTSP_EINVAL);
++ g_return_val_if_fail (conn->readfd != NULL, GST_RTSP_EINVAL);
++
++ /* configure timeout if any */
++ to = timeout ? GST_TIMEVAL_TO_TIME (*timeout) : GST_CLOCK_TIME_NONE;
++
++ gst_poll_set_controllable (conn->fdset, TRUE);
++ gst_poll_fd_ctl_write (conn->fdset, conn->writefd, FALSE);
++ gst_poll_fd_ctl_read (conn->fdset, conn->readfd, TRUE);
++
++ memset (&builder, 0, sizeof (GstRTSPBuilder));
++ while (TRUE) {
++ res = build_next (&builder, message, conn);
++ if (G_UNLIKELY (res == GST_RTSP_EEOF))
++ goto eof;
++ else if (G_LIKELY (res == GST_RTSP_OK)) {
++ if (!conn->manual_http) {
++ if (message->type == GST_RTSP_MESSAGE_HTTP_REQUEST) {
++ if (conn->tstate == TUNNEL_STATE_NONE &&
++ message->type_data.request.method == GST_RTSP_GET) {
++ GstRTSPMessage *response;
++
++ conn->tstate = TUNNEL_STATE_GET;
++
++ /* tunnel GET request, we can reply now */
++ response = gen_tunnel_reply (conn, GST_RTSP_STS_OK, message);
++ res = gst_rtsp_connection_send (conn, response, timeout);
++ gst_rtsp_message_free (response);
++ if (res == GST_RTSP_OK)
++ res = GST_RTSP_ETGET;
++ goto cleanup;
++ } else if (conn->tstate == TUNNEL_STATE_NONE &&
++ message->type_data.request.method == GST_RTSP_POST) {
++ conn->tstate = TUNNEL_STATE_POST;
++
++ /* tunnel POST request, the caller now has to link the two
++ * connections. */
++ res = GST_RTSP_ETPOST;
++ goto cleanup;
++ } else {
++ res = GST_RTSP_EPARSE;
++ goto cleanup;
++ }
++ } else if (message->type == GST_RTSP_MESSAGE_HTTP_RESPONSE) {
++ res = GST_RTSP_EPARSE;
++ goto cleanup;
++ }
++ }
++
++ break;
++ } else if (G_UNLIKELY (res != GST_RTSP_EINTR))
++ goto read_error;
++
++ do {
++ retval = gst_poll_wait (conn->fdset, to);
++ } while (retval == -1 && (errno == EINTR || errno == EAGAIN));
++
++ /* check for timeout */
++ if (G_UNLIKELY (retval == 0))
++ goto select_timeout;
++
++ if (G_UNLIKELY (retval == -1)) {
++ if (errno == EBUSY)
++ goto stopped;
++ else
++ goto select_error;
++ }
++ gst_poll_set_controllable (conn->fdset, FALSE);
++ }
++
++ /* we have a message here */
++ build_reset (&builder);
++
++ return GST_RTSP_OK;
++
++ /* ERRORS */
++select_error:
++ {
++ res = GST_RTSP_ESYS;
++ goto cleanup;
++ }
++select_timeout:
++ {
++ res = GST_RTSP_ETIMEOUT;
++ goto cleanup;
++ }
++stopped:
++ {
++ res = GST_RTSP_EINTR;
++ goto cleanup;
++ }
++eof:
++ {
++ res = GST_RTSP_EEOF;
++ goto cleanup;
++ }
++read_error:
++cleanup:
++ {
++ build_reset (&builder);
++ gst_rtsp_message_unset (message);
++ return res;
++ }
++}
++
++/**
++ * gst_rtsp_connection_close:
++ * @conn: a #GstRTSPConnection
++ *
++ * Close the connected @conn. After this call, the connection is in the same
++ * state as when it was first created.
++ *
++ * Returns: #GST_RTSP_OK on success.
++ */
++GstRTSPResult
++gst_rtsp_connection_close (GstRTSPConnection * conn)
++{
++ g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
++
++ g_free (conn->ip);
++ conn->ip = NULL;
++
++ conn->read_ahead = 0;
++
++ g_free (conn->initial_buffer);
++ conn->initial_buffer = NULL;
++ conn->initial_buffer_offset = 0;
++
++ REMOVE_POLLFD (conn->fdset, &conn->fd0);
++ REMOVE_POLLFD (conn->fdset, &conn->fd1);
++ conn->writefd = NULL;
++ conn->readfd = NULL;
++ conn->tunneled = FALSE;
++ conn->tstate = TUNNEL_STATE_NONE;
++ conn->ctxp = NULL;
++ g_free (conn->username);
++ conn->username = NULL;
++ g_free (conn->passwd);
++ conn->passwd = NULL;
++ gst_rtsp_connection_clear_auth_params (conn);
++ conn->timeout = 60;
++ conn->cseq = 0;
++ conn->session_id[0] = '\0';
++
++ return GST_RTSP_OK;
++}
++
++/**
++ * gst_rtsp_connection_free:
++ * @conn: a #GstRTSPConnection
++ *
++ * Close and free @conn.
++ *
++ * Returns: #GST_RTSP_OK on success.
++ */
++GstRTSPResult
++gst_rtsp_connection_free (GstRTSPConnection * conn)
++{
++ GstRTSPResult res;
++
++ g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
++
++ res = gst_rtsp_connection_close (conn);
++ gst_poll_free (conn->fdset);
++ g_timer_destroy (conn->timer);
++ gst_rtsp_url_free (conn->url);
++ g_free (conn->proxy_host);
++ g_free (conn);
++#ifdef G_OS_WIN32
++ WSACleanup ();
++#endif
++
++ return res;
++}
++
++/**
++ * gst_rtsp_connection_poll:
++ * @conn: a #GstRTSPConnection
++ * @events: a bitmask of #GstRTSPEvent flags to check
++ * @revents: location for result flags
++ * @timeout: a timeout
++ *
++ * Wait up to the specified @timeout for the connection to become available for
++ * at least one of the operations specified in @events. When the function returns
++ * with #GST_RTSP_OK, @revents will contain a bitmask of available operations on
++ * @conn.
++ *
++ * @timeout can be #NULL, in which case this function might block forever.
++ *
++ * This function can be cancelled with gst_rtsp_connection_flush().
++ *
++ * Returns: #GST_RTSP_OK on success.
++ *
++ * Since: 0.10.15
++ */
++GstRTSPResult
++gst_rtsp_connection_poll (GstRTSPConnection * conn, GstRTSPEvent events,
++ GstRTSPEvent * revents, GTimeVal * timeout)
++{
++ GstClockTime to;
++ gint retval;
++
++ g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
++ g_return_val_if_fail (events != 0, GST_RTSP_EINVAL);
++ g_return_val_if_fail (revents != NULL, GST_RTSP_EINVAL);
++ g_return_val_if_fail (conn->readfd != NULL, GST_RTSP_EINVAL);
++ g_return_val_if_fail (conn->writefd != NULL, GST_RTSP_EINVAL);
++
++ gst_poll_set_controllable (conn->fdset, TRUE);
++
++ /* add fd to writer set when asked to */
++ gst_poll_fd_ctl_write (conn->fdset, conn->writefd,
++ events & GST_RTSP_EV_WRITE);
++
++ /* add fd to reader set when asked to */
++ gst_poll_fd_ctl_read (conn->fdset, conn->readfd, events & GST_RTSP_EV_READ);
++
++ /* configure timeout if any */
++ to = timeout ? GST_TIMEVAL_TO_TIME (*timeout) : GST_CLOCK_TIME_NONE;
++
++ do {
++ retval = gst_poll_wait (conn->fdset, to);
++ } while (retval == -1 && (errno == EINTR || errno == EAGAIN));
++
++ if (G_UNLIKELY (retval == 0))
++ goto select_timeout;
++
++ if (G_UNLIKELY (retval == -1)) {
++ if (errno == EBUSY)
++ goto stopped;
++ else
++ goto select_error;
++ }
++
++ *revents = 0;
++ if (events & GST_RTSP_EV_READ) {
++ if (gst_poll_fd_can_read (conn->fdset, conn->readfd))
++ *revents |= GST_RTSP_EV_READ;
++ }
++ if (events & GST_RTSP_EV_WRITE) {
++ if (gst_poll_fd_can_write (conn->fdset, conn->writefd))
++ *revents |= GST_RTSP_EV_WRITE;
++ }
++ return GST_RTSP_OK;
++
++ /* ERRORS */
++select_timeout:
++ {
++ return GST_RTSP_ETIMEOUT;
++ }
++select_error:
++ {
++ return GST_RTSP_ESYS;
++ }
++stopped:
++ {
++ return GST_RTSP_EINTR;
++ }
++}
++
++/**
++ * gst_rtsp_connection_next_timeout:
++ * @conn: a #GstRTSPConnection
++ * @timeout: a timeout
++ *
++ * Calculate the next timeout for @conn, storing the result in @timeout.
++ *
++ * Returns: #GST_RTSP_OK.
++ */
++GstRTSPResult
++gst_rtsp_connection_next_timeout (GstRTSPConnection * conn, GTimeVal * timeout)
++{
++ gdouble elapsed;
++ glong sec;
++ gulong usec;
++
++ g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
++ g_return_val_if_fail (timeout != NULL, GST_RTSP_EINVAL);
++
++ elapsed = g_timer_elapsed (conn->timer, &usec);
++ if (elapsed >= conn->timeout) {
++ sec = 0;
++ usec = 0;
++ } else {
++ sec = conn->timeout - elapsed;
++ }
++
++ timeout->tv_sec = sec;
++ timeout->tv_usec = usec;
++
++ return GST_RTSP_OK;
++}
++
++/**
++ * gst_rtsp_connection_reset_timeout:
++ * @conn: a #GstRTSPConnection
++ *
++ * Reset the timeout of @conn.
++ *
++ * Returns: #GST_RTSP_OK.
++ */
++GstRTSPResult
++gst_rtsp_connection_reset_timeout (GstRTSPConnection * conn)
++{
++ g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
++
++ g_timer_start (conn->timer);
++
++ return GST_RTSP_OK;
++}
++
++/**
++ * gst_rtsp_connection_flush:
++ * @conn: a #GstRTSPConnection
++ * @flush: start or stop the flush
++ *
++ * Start or stop the flushing action on @conn. When flushing, all current
++ * and future actions on @conn will return #GST_RTSP_EINTR until the connection
++ * is set to non-flushing mode again.
++ *
++ * Returns: #GST_RTSP_OK.
++ */
++GstRTSPResult
++gst_rtsp_connection_flush (GstRTSPConnection * conn, gboolean flush)
++{
++ g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
++
++ gst_poll_set_flushing (conn->fdset, flush);
++
++ return GST_RTSP_OK;
++}
++
++/**
++ * gst_rtsp_connection_set_proxy:
++ * @conn: a #GstRTSPConnection
++ * @host: the proxy host
++ * @port: the proxy port
++ *
++ * Set the proxy host and port.
++ *
++ * Returns: #GST_RTSP_OK.
++ *
++ * Since: 0.10.23
++ */
++GstRTSPResult
++gst_rtsp_connection_set_proxy (GstRTSPConnection * conn,
++ const gchar * host, guint port)
++{
++ g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
++
++ g_free (conn->proxy_host);
++ conn->proxy_host = g_strdup (host);
++ conn->proxy_port = port;
++
++ return GST_RTSP_OK;
++}
++
++/**
++ * gst_rtsp_connection_set_auth:
++ * @conn: a #GstRTSPConnection
++ * @method: authentication method
++ * @user: the user
++ * @pass: the password
++ *
++ * Configure @conn for authentication mode @method with @user and @pass as the
++ * user and password respectively.
++ *
++ * Returns: #GST_RTSP_OK.
++ */
++GstRTSPResult
++gst_rtsp_connection_set_auth (GstRTSPConnection * conn,
++ GstRTSPAuthMethod method, const gchar * user, const gchar * pass)
++{
++ g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
++
++ if (method == GST_RTSP_AUTH_DIGEST && ((user == NULL || pass == NULL)
++ || g_strrstr (user, ":") != NULL))
++ return GST_RTSP_EINVAL;
++
++ /* Make sure the username and passwd are being set for authentication */
++ if (method == GST_RTSP_AUTH_NONE && (user == NULL || pass == NULL))
++ return GST_RTSP_EINVAL;
++
++ /* ":" chars are not allowed in usernames for basic auth */
++ if (method == GST_RTSP_AUTH_BASIC && g_strrstr (user, ":") != NULL)
++ return GST_RTSP_EINVAL;
++
++ g_free (conn->username);
++ g_free (conn->passwd);
++
++ conn->auth_method = method;
++ conn->username = g_strdup (user);
++ conn->passwd = g_strdup (pass);
++
++ return GST_RTSP_OK;
++}
++
++/**
++ * str_case_hash:
++ * @key: ASCII string to hash
++ *
++ * Hashes @key in a case-insensitive manner.
++ *
++ * Returns: the hash code.
++ **/
++static guint
++str_case_hash (gconstpointer key)
++{
++ const char *p = key;
++ guint h = g_ascii_toupper (*p);
++
++ if (h)
++ for (p += 1; *p != '\0'; p++)
++ h = (h << 5) - h + g_ascii_toupper (*p);
++
++ return h;
++}
++
++/**
++ * str_case_equal:
++ * @v1: an ASCII string
++ * @v2: another ASCII string
++ *
++ * Compares @v1 and @v2 in a case-insensitive manner
++ *
++ * Returns: %TRUE if they are equal (modulo case)
++ **/
++static gboolean
++str_case_equal (gconstpointer v1, gconstpointer v2)
++{
++ const char *string1 = v1;
++ const char *string2 = v2;
++
++ return g_ascii_strcasecmp (string1, string2) == 0;
++}
++
++/**
++ * gst_rtsp_connection_set_auth_param:
++ * @conn: a #GstRTSPConnection
++ * @param: authentication directive
++ * @value: value
++ *
++ * Setup @conn with authentication directives. This is not necesary for
++ * methods #GST_RTSP_AUTH_NONE and #GST_RTSP_AUTH_BASIC. For
++ * #GST_RTSP_AUTH_DIGEST, directives should be taken from the digest challenge
++ * in the WWW-Authenticate response header and can include realm, domain,
++ * nonce, opaque, stale, algorithm, qop as per RFC2617.
++ *
++ * Since: 0.10.20
++ */
++void
++gst_rtsp_connection_set_auth_param (GstRTSPConnection * conn,
++ const gchar * param, const gchar * value)
++{
++ g_return_if_fail (conn != NULL);
++ g_return_if_fail (param != NULL);
++
++ if (conn->auth_params == NULL) {
++ conn->auth_params =
++ g_hash_table_new_full (str_case_hash, str_case_equal, g_free, g_free);
++ }
++ g_hash_table_insert (conn->auth_params, g_strdup (param), g_strdup (value));
++}
++
++/**
++ * gst_rtsp_connection_clear_auth_params:
++ * @conn: a #GstRTSPConnection
++ *
++ * Clear the list of authentication directives stored in @conn.
++ *
++ * Since: 0.10.20
++ */
++void
++gst_rtsp_connection_clear_auth_params (GstRTSPConnection * conn)
++{
++ g_return_if_fail (conn != NULL);
++
++ if (conn->auth_params != NULL) {
++ g_hash_table_destroy (conn->auth_params);
++ conn->auth_params = NULL;
++ }
++}
++
++static GstRTSPResult
++set_qos_dscp (gint fd, guint qos_dscp)
++{
++ union gst_sockaddr sa;
++ socklen_t slen = sizeof (sa);
++ gint af;
++ gint tos;
++
++ if (fd == -1)
++ return GST_RTSP_OK;
++
++ if (getsockname (fd, &sa.sa, &slen) < 0)
++ goto no_getsockname;
++
++ af = sa.sa.sa_family;
++
++ /* if this is an IPv4-mapped address then do IPv4 QoS */
++ if (af == AF_INET6) {
++ if (IN6_IS_ADDR_V4MAPPED (&sa.sa_in6.sin6_addr))
++ af = AF_INET;
++ }
++
++ /* extract and shift 6 bits of the DSCP */
++ tos = (qos_dscp & 0x3f) << 2;
++
++ switch (af) {
++ case AF_INET:
++ if (SETSOCKOPT (fd, IPPROTO_IP, IP_TOS, &tos, sizeof (tos)) < 0)
++ goto no_setsockopt;
++ break;
++ case AF_INET6:
++#ifdef IPV6_TCLASS
++ if (SETSOCKOPT (fd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof (tos)) < 0)
++ goto no_setsockopt;
++ break;
++#endif
++ default:
++ goto wrong_family;
++ }
++
++ return GST_RTSP_OK;
++
++ /* ERRORS */
++no_getsockname:
++no_setsockopt:
++ {
++ return GST_RTSP_ESYS;
++ }
++
++wrong_family:
++ {
++ return GST_RTSP_ERROR;
++ }
++}
++
++/**
++ * gst_rtsp_connection_set_qos_dscp:
++ * @conn: a #GstRTSPConnection
++ * @qos_dscp: DSCP value
++ *
++ * Configure @conn to use the specified DSCP value.
++ *
++ * Returns: #GST_RTSP_OK on success.
++ *
++ * Since: 0.10.20
++ */
++GstRTSPResult
++gst_rtsp_connection_set_qos_dscp (GstRTSPConnection * conn, guint qos_dscp)
++{
++ GstRTSPResult res;
++
++ g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
++ g_return_val_if_fail (conn->readfd != NULL, GST_RTSP_EINVAL);
++ g_return_val_if_fail (conn->writefd != NULL, GST_RTSP_EINVAL);
++
++ res = set_qos_dscp (conn->fd0.fd, qos_dscp);
++ if (res == GST_RTSP_OK)
++ res = set_qos_dscp (conn->fd1.fd, qos_dscp);
++
++ return res;
++}
++
++
++/**
++ * gst_rtsp_connection_get_url:
++ * @conn: a #GstRTSPConnection
++ *
++ * Retrieve the URL of the other end of @conn.
++ *
++ * Returns: The URL. This value remains valid until the
++ * connection is freed.
++ *
++ * Since: 0.10.23
++ */
++GstRTSPUrl *
++gst_rtsp_connection_get_url (const GstRTSPConnection * conn)
++{
++ g_return_val_if_fail (conn != NULL, NULL);
++
++ return conn->url;
++}
++
++/**
++ * gst_rtsp_connection_get_ip:
++ * @conn: a #GstRTSPConnection
++ *
++ * Retrieve the IP address of the other end of @conn.
++ *
++ * Returns: The IP address as a string. this value remains valid until the
++ * connection is closed.
++ *
++ * Since: 0.10.20
++ */
++const gchar *
++gst_rtsp_connection_get_ip (const GstRTSPConnection * conn)
++{
++ g_return_val_if_fail (conn != NULL, NULL);
++
++ return conn->ip;
++}
++
++/**
++ * gst_rtsp_connection_set_ip:
++ * @conn: a #GstRTSPConnection
++ * @ip: an ip address
++ *
++ * Set the IP address of the server.
++ *
++ * Since: 0.10.23
++ */
++void
++gst_rtsp_connection_set_ip (GstRTSPConnection * conn, const gchar * ip)
++{
++ g_return_if_fail (conn != NULL);
++
++ g_free (conn->ip);
++ conn->ip = g_strdup (ip);
++}
++
++/**
++ * gst_rtsp_connection_get_readfd:
++ * @conn: a #GstRTSPConnection
++ *
++ * Get the file descriptor for reading.
++ *
++ * Returns: the file descriptor used for reading or -1 on error. The file
++ * descriptor remains valid until the connection is closed.
++ *
++ * Since: 0.10.23
++ */
++gint
++gst_rtsp_connection_get_readfd (const GstRTSPConnection * conn)
++{
++ g_return_val_if_fail (conn != NULL, -1);
++ g_return_val_if_fail (conn->readfd != NULL, -1);
++
++ return conn->readfd->fd;
++}
++
++/**
++ * gst_rtsp_connection_get_writefd:
++ * @conn: a #GstRTSPConnection
++ *
++ * Get the file descriptor for writing.
++ *
++ * Returns: the file descriptor used for writing or -1 on error. The file
++ * descriptor remains valid until the connection is closed.
++ *
++ * Since: 0.10.23
++ */
++gint
++gst_rtsp_connection_get_writefd (const GstRTSPConnection * conn)
++{
++ g_return_val_if_fail (conn != NULL, -1);
++ g_return_val_if_fail (conn->writefd != NULL, -1);
++
++ return conn->writefd->fd;
++}
++
++/**
++ * gst_rtsp_connection_set_http_mode:
++ * @conn: a #GstRTSPConnection
++ * @enable: %TRUE to enable manual HTTP mode
++ *
++ * By setting the HTTP mode to %TRUE the message parsing will support HTTP
++ * messages in addition to the RTSP messages. It will also disable the
++ * automatic handling of setting up an HTTP tunnel.
++ *
++ * Since: 0.10.25
++ */
++void
++gst_rtsp_connection_set_http_mode (GstRTSPConnection * conn, gboolean enable)
++{
++ g_return_if_fail (conn != NULL);
++
++ conn->manual_http = enable;
++}
++
++/**
++ * gst_rtsp_connection_set_tunneled:
++ * @conn: a #GstRTSPConnection
++ * @tunneled: the new state
++ *
++ * Set the HTTP tunneling state of the connection. This must be configured before
++ * the @conn is connected.
++ *
++ * Since: 0.10.23
++ */
++void
++gst_rtsp_connection_set_tunneled (GstRTSPConnection * conn, gboolean tunneled)
++{
++ g_return_if_fail (conn != NULL);
++ g_return_if_fail (conn->readfd == NULL);
++ g_return_if_fail (conn->writefd == NULL);
++
++ conn->tunneled = tunneled;
++}
++
++/**
++ * gst_rtsp_connection_is_tunneled:
++ * @conn: a #GstRTSPConnection
++ *
++ * Get the tunneling state of the connection.
++ *
++ * Returns: if @conn is using HTTP tunneling.
++ *
++ * Since: 0.10.23
++ */
++gboolean
++gst_rtsp_connection_is_tunneled (const GstRTSPConnection * conn)
++{
++ g_return_val_if_fail (conn != NULL, FALSE);
++
++ return conn->tunneled;
++}
++
++/**
++ * gst_rtsp_connection_get_tunnelid:
++ * @conn: a #GstRTSPConnection
++ *
++ * Get the tunnel session id the connection.
++ *
++ * Returns: returns a non-empty string if @conn is being tunneled over HTTP.
++ *
++ * Since: 0.10.23
++ */
++const gchar *
++gst_rtsp_connection_get_tunnelid (const GstRTSPConnection * conn)
++{
++ g_return_val_if_fail (conn != NULL, NULL);
++
++ if (!conn->tunneled)
++ return NULL;
++
++ return conn->tunnelid;
++}
++
++/**
++ * gst_rtsp_connection_do_tunnel:
++ * @conn: a #GstRTSPConnection
++ * @conn2: a #GstRTSPConnection or %NULL
++ *
++ * If @conn received the first tunnel connection and @conn2 received
++ * the second tunnel connection, link the two connections together so that
++ * @conn manages the tunneled connection.
++ *
++ * After this call, @conn2 cannot be used anymore and must be freed with
++ * gst_rtsp_connection_free().
++ *
++ * If @conn2 is %NULL then only the base64 decoding context will be setup for
++ * @conn.
++ *
++ * Returns: return GST_RTSP_OK on success.
++ *
++ * Since: 0.10.23
++ */
++GstRTSPResult
++gst_rtsp_connection_do_tunnel (GstRTSPConnection * conn,
++ GstRTSPConnection * conn2)
++{
++ g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
++
++ if (conn2 != NULL) {
++ g_return_val_if_fail (conn->tstate == TUNNEL_STATE_GET, GST_RTSP_EINVAL);
++ g_return_val_if_fail (conn2->tstate == TUNNEL_STATE_POST, GST_RTSP_EINVAL);
++ g_return_val_if_fail (!memcmp (conn2->tunnelid, conn->tunnelid,
++ TUNNELID_LEN), GST_RTSP_EINVAL);
++
++ /* both connections have fd0 as the read/write socket. start by taking the
++ * socket from conn2 and set it as the socket in conn */
++ conn->fd1 = conn2->fd0;
++
++ /* clean up some of the state of conn2 */
++ gst_poll_remove_fd (conn2->fdset, &conn2->fd0);
++ conn2->fd0.fd = -1;
++ conn2->readfd = conn2->writefd = NULL;
++
++ /* We make fd0 the write socket and fd1 the read socket. */
++ conn->writefd = &conn->fd0;
++ conn->readfd = &conn->fd1;
++
++ conn->tstate = TUNNEL_STATE_COMPLETE;
++ }
++
++ /* we need base64 decoding for the readfd */
++ conn->ctx.state = 0;
++ conn->ctx.save = 0;
++ conn->ctx.cout = 0;
++ conn->ctx.coutl = 0;
++ conn->ctxp = &conn->ctx;
++
++ return GST_RTSP_OK;
++}
++
++#define READ_COND (G_IO_IN | G_IO_HUP | G_IO_ERR)
++#define WRITE_COND (G_IO_OUT | G_IO_ERR)
++
++typedef struct
++{
++ guint8 *data;
++ guint size;
++ guint id;
++} GstRTSPRec;
++
++/* async functions */
++struct _GstRTSPWatch
++{
++ GSource source;
++
++ GstRTSPConnection *conn;
++
++ GstRTSPBuilder builder;
++ GstRTSPMessage message;
++
++ GPollFD readfd;
++ GPollFD writefd;
++ gboolean write_added;
++
++ /* queued message for transmission */
++ guint id;
++ GMutex *mutex;
++ GQueue *messages;
++ guint8 *write_data;
++ guint write_off;
++ guint write_size;
++ guint write_id;
++
++ GstRTSPWatchFuncs funcs;
++
++ gpointer user_data;
++ GDestroyNotify notify;
++};
++
++static gboolean
++gst_rtsp_source_prepare (GSource * source, gint * timeout)
++{
++ GstRTSPWatch *watch = (GstRTSPWatch *) source;
++
++ if (watch->conn->initial_buffer != NULL)
++ return TRUE;
++
++ *timeout = (watch->conn->timeout * 1000);
++
++ return FALSE;
++}
++
++static gboolean
++gst_rtsp_source_check (GSource * source)
++{
++ GstRTSPWatch *watch = (GstRTSPWatch *) source;
++
++ if (watch->readfd.revents & READ_COND)
++ return TRUE;
++
++ if (watch->writefd.revents & WRITE_COND)
++ return TRUE;
++
++ return FALSE;
++}
++
++static gboolean
++gst_rtsp_source_dispatch (GSource * source, GSourceFunc callback G_GNUC_UNUSED,
++ gpointer user_data G_GNUC_UNUSED)
++{
++ GstRTSPWatch *watch = (GstRTSPWatch *) source;
++ GstRTSPResult res;
++
++ /* first read as much as we can */
++ if (watch->readfd.revents & READ_COND || watch->conn->initial_buffer != NULL) {
++ do {
++ res = build_next (&watch->builder, &watch->message, watch->conn);
++ if (res == GST_RTSP_EINTR)
++ break;
++ else if (G_UNLIKELY (res == GST_RTSP_EEOF))
++ goto eof;
++ else if (G_LIKELY (res == GST_RTSP_OK)) {
++ if (!watch->conn->manual_http &&
++ watch->message.type == GST_RTSP_MESSAGE_HTTP_REQUEST) {
++ if (watch->conn->tstate == TUNNEL_STATE_NONE &&
++ watch->message.type_data.request.method == GST_RTSP_GET) {
++ GstRTSPMessage *response;
++ GstRTSPStatusCode code;
++
++ watch->conn->tstate = TUNNEL_STATE_GET;
++
++ if (watch->funcs.tunnel_start)
++ code = watch->funcs.tunnel_start (watch, watch->user_data);
++ else
++ code = GST_RTSP_STS_OK;
++
++ /* queue the response */
++ response = gen_tunnel_reply (watch->conn, code, &watch->message);
++ gst_rtsp_watch_send_message (watch, response, NULL);
++ gst_rtsp_message_free (response);
++ goto read_done;
++ } else if (watch->conn->tstate == TUNNEL_STATE_NONE &&
++ watch->message.type_data.request.method == GST_RTSP_POST) {
++ watch->conn->tstate = TUNNEL_STATE_POST;
++
++ /* in the callback the connection should be tunneled with the
++ * GET connection */
++ if (watch->funcs.tunnel_complete)
++ watch->funcs.tunnel_complete (watch, watch->user_data);
++ goto read_done;
++ }
++ }
++ }
++
++ if (!watch->conn->manual_http) {
++ /* if manual HTTP support is not enabled, then restore the message to
++ * what it would have looked like without the support for parsing HTTP
++ * messages being present */
++ if (watch->message.type == GST_RTSP_MESSAGE_HTTP_REQUEST) {
++ watch->message.type = GST_RTSP_MESSAGE_REQUEST;
++ watch->message.type_data.request.method = GST_RTSP_INVALID;
++ if (watch->message.type_data.request.version != GST_RTSP_VERSION_1_0)
++ watch->message.type_data.request.version = GST_RTSP_VERSION_INVALID;
++ res = GST_RTSP_EPARSE;
++ } else if (watch->message.type == GST_RTSP_MESSAGE_HTTP_RESPONSE) {
++ watch->message.type = GST_RTSP_MESSAGE_RESPONSE;
++ if (watch->message.type_data.response.version != GST_RTSP_VERSION_1_0)
++ watch->message.type_data.response.version =
++ GST_RTSP_VERSION_INVALID;
++ res = GST_RTSP_EPARSE;
++ }
++ }
++
++ if (G_LIKELY (res == GST_RTSP_OK)) {
++ if (watch->funcs.message_received)
++ watch->funcs.message_received (watch, &watch->message,
++ watch->user_data);
++ } else {
++ if (watch->funcs.error_full)
++ GST_RTSP_CHECK (watch->funcs.error_full (watch, res, &watch->message,
++ 0, watch->user_data), error);
++ else
++ goto error;
++ }
++
++ read_done:
++ gst_rtsp_message_unset (&watch->message);
++ build_reset (&watch->builder);
++ } while (FALSE);
++ }
++
++ if (watch->writefd.revents & WRITE_COND) {
++ g_mutex_lock (watch->mutex);
++ do {
++ if (watch->write_data == NULL) {
++ GstRTSPRec *rec;
++
++ /* get a new message from the queue */
++ rec = g_queue_pop_tail (watch->messages);
++ if (rec == NULL)
++ goto done;
++
++ watch->write_off = 0;
++ watch->write_data = rec->data;
++ watch->write_size = rec->size;
++ watch->write_id = rec->id;
++
++ g_slice_free (GstRTSPRec, rec);
++ }
++
++ res = write_bytes (watch->writefd.fd, watch->write_data,
++ &watch->write_off, watch->write_size);
++ g_mutex_unlock (watch->mutex);
++ if (res == GST_RTSP_EINTR)
++ goto write_blocked;
++ else if (G_LIKELY (res == GST_RTSP_OK)) {
++ if (watch->funcs.message_sent)
++ watch->funcs.message_sent (watch, watch->write_id, watch->user_data);
++ } else {
++ if (watch->funcs.error_full)
++ GST_RTSP_CHECK (watch->funcs.error_full (watch, res, NULL,
++ watch->write_id, watch->user_data), error);
++ else
++ goto error;
++ }
++ g_mutex_lock (watch->mutex);
++
++ g_free (watch->write_data);
++ watch->write_data = NULL;
++ } while (TRUE);
++
++ done:
++ if (watch->write_added) {
++ g_source_remove_poll ((GSource *) watch, &watch->writefd);
++ watch->write_added = FALSE;
++ watch->writefd.revents = 0;
++ }
++
++ g_mutex_unlock (watch->mutex);
++ }
++
++write_blocked:
++ return TRUE;
++
++ /* ERRORS */
++eof:
++ {
++ if (watch->funcs.closed)
++ watch->funcs.closed (watch, watch->user_data);
++ return FALSE;
++ }
++error:
++ {
++ if (watch->funcs.error)
++ watch->funcs.error (watch, res, watch->user_data);
++ return FALSE;
++ }
++}
++
++static void
++gst_rtsp_rec_free (gpointer data)
++{
++ GstRTSPRec *rec = data;
++
++ g_free (rec->data);
++ g_slice_free (GstRTSPRec, rec);
++}
++
++static void
++gst_rtsp_source_finalize (GSource * source)
++{
++ GstRTSPWatch *watch = (GstRTSPWatch *) source;
++
++ build_reset (&watch->builder);
++ gst_rtsp_message_unset (&watch->message);
++
++ g_queue_foreach (watch->messages, (GFunc) gst_rtsp_rec_free, NULL);
++ g_queue_free (watch->messages);
++ watch->messages = NULL;
++
++ g_mutex_free (watch->mutex);
++
++ g_free (watch->write_data);
++
++ if (watch->notify)
++ watch->notify (watch->user_data);
++}
++
++static GSourceFuncs gst_rtsp_source_funcs = {
++ gst_rtsp_source_prepare,
++ gst_rtsp_source_check,
++ gst_rtsp_source_dispatch,
++ gst_rtsp_source_finalize,
++ NULL,
++ NULL
++};
++
++/**
++ * gst_rtsp_watch_new:
++ * @conn: a #GstRTSPConnection
++ * @funcs: watch functions
++ * @user_data: user data to pass to @funcs
++ * @notify: notify when @user_data is not referenced anymore
++ *
++ * Create a watch object for @conn. The functions provided in @funcs will be
++ * called with @user_data when activity happened on the watch.
++ *
++ * The new watch is usually created so that it can be attached to a
++ * maincontext with gst_rtsp_watch_attach().
++ *
++ * @conn must exist for the entire lifetime of the watch.
++ *
++ * Returns: a #GstRTSPWatch that can be used for asynchronous RTSP
++ * communication. Free with gst_rtsp_watch_unref () after usage.
++ *
++ * Since: 0.10.23
++ */
++GstRTSPWatch *
++gst_rtsp_watch_new (GstRTSPConnection * conn,
++ GstRTSPWatchFuncs * funcs, gpointer user_data, GDestroyNotify notify)
++{
++ GstRTSPWatch *result;
++
++ g_return_val_if_fail (conn != NULL, NULL);
++ g_return_val_if_fail (funcs != NULL, NULL);
++ g_return_val_if_fail (conn->readfd != NULL, NULL);
++ g_return_val_if_fail (conn->writefd != NULL, NULL);
++
++ result = (GstRTSPWatch *) g_source_new (&gst_rtsp_source_funcs,
++ sizeof (GstRTSPWatch));
++
++ result->conn = conn;
++ result->builder.state = STATE_START;
++
++ result->mutex = g_mutex_new ();
++ result->messages = g_queue_new ();
++
++ result->readfd.fd = -1;
++ result->writefd.fd = -1;
++
++ gst_rtsp_watch_reset (result);
++
++ result->funcs = *funcs;
++ result->user_data = user_data;
++ result->notify = notify;
++
++ /* only add the read fd, the write fd is only added when we have data
++ * to send. */
++ g_source_add_poll ((GSource *) result, &result->readfd);
++
++ return result;
++}
++
++/**
++ * gst_rtsp_watch_reset:
++ * @watch: a #GstRTSPWatch
++ *
++ * Reset @watch, this is usually called after gst_rtsp_connection_do_tunnel()
++ * when the file descriptors of the connection might have changed.
++ *
++ * Since: 0.10.23
++ */
++void
++gst_rtsp_watch_reset (GstRTSPWatch * watch)
++{
++ if (watch->readfd.fd != -1)
++ g_source_remove_poll ((GSource *) watch, &watch->readfd);
++ if (watch->writefd.fd != -1)
++ g_source_remove_poll ((GSource *) watch, &watch->writefd);
++
++ watch->readfd.fd = watch->conn->readfd->fd;
++ watch->readfd.events = READ_COND;
++ watch->readfd.revents = 0;
++
++ watch->writefd.fd = watch->conn->writefd->fd;
++ watch->writefd.events = WRITE_COND;
++ watch->writefd.revents = 0;
++ watch->write_added = FALSE;
++
++ g_source_add_poll ((GSource *) watch, &watch->readfd);
++}
++
++/**
++ * gst_rtsp_watch_attach:
++ * @watch: a #GstRTSPWatch
++ * @context: a GMainContext (if NULL, the default context will be used)
++ *
++ * Adds a #GstRTSPWatch to a context so that it will be executed within that context.
++ *
++ * Returns: the ID (greater than 0) for the watch within the GMainContext.
++ *
++ * Since: 0.10.23
++ */
++guint
++gst_rtsp_watch_attach (GstRTSPWatch * watch, GMainContext * context)
++{
++ g_return_val_if_fail (watch != NULL, 0);
++
++ return g_source_attach ((GSource *) watch, context);
++}
++
++/**
++ * gst_rtsp_watch_unref:
++ * @watch: a #GstRTSPWatch
++ *
++ * Decreases the reference count of @watch by one. If the resulting reference
++ * count is zero the watch and associated memory will be destroyed.
++ *
++ * Since: 0.10.23
++ */
++void
++gst_rtsp_watch_unref (GstRTSPWatch * watch)
++{
++ g_return_if_fail (watch != NULL);
++
++ g_source_unref ((GSource *) watch);
++}
++
++/**
++ * gst_rtsp_watch_write_data:
++ * @watch: a #GstRTSPWatch
++ * @data: the data to queue
++ * @size: the size of @data
++ * @id: location for a message ID or %NULL
++ *
++ * Write @data using the connection of the @watch. If it cannot be sent
++ * immediately, it will be queued for transmission in @watch. The contents of
++ * @message will then be serialized and transmitted when the connection of the
++ * @watch becomes writable. In case the @message is queued, the ID returned in
++ * @id will be non-zero and used as the ID argument in the message_sent
++ * callback.
++ *
++ * This function will take ownership of @data and g_free() it after use.
++ *
++ * Returns: #GST_RTSP_OK on success.
++ *
++ * Since: 0.10.25
++ */
++GstRTSPResult
++gst_rtsp_watch_write_data (GstRTSPWatch * watch, const guint8 * data,
++ guint size, guint * id)
++{
++ GstRTSPResult res;
++ GstRTSPRec *rec;
++ guint off = 0;
++
++ g_return_val_if_fail (watch != NULL, GST_RTSP_EINVAL);
++ g_return_val_if_fail (data != NULL, GST_RTSP_EINVAL);
++ g_return_val_if_fail (size != 0, GST_RTSP_EINVAL);
++
++ g_mutex_lock (watch->mutex);
++
++ if (watch->messages->length == 0) {
++ res = write_bytes (watch->writefd.fd, data, &off, size);
++ if (res != GST_RTSP_EINTR) {
++ if (id != NULL)
++ *id = 0;
++ g_free ((gpointer) data);
++ goto done;
++ }
++ }
++
++ /* make a record with the data and id */
++ rec = g_slice_new (GstRTSPRec);
++ if (off == 0) {
++ rec->data = (guint8 *) data;
++ rec->size = size;
++ } else {
++ rec->data = g_memdup (data + off, size - off);
++ rec->size = size - off;
++ g_free ((gpointer) data);
++ }
++
++ do {
++ /* make sure rec->id is never 0 */
++ rec->id = ++watch->id;
++ } while (G_UNLIKELY (rec->id == 0));
++
++ /* add the record to a queue. FIXME we would like to have an upper limit here */
++ g_queue_push_head (watch->messages, rec);
++
++ /* make sure the main context will now also check for writability on the
++ * socket */
++ if (!watch->write_added) {
++ g_source_add_poll ((GSource *) watch, &watch->writefd);
++ watch->write_added = TRUE;
++ }
++
++ if (id != NULL)
++ *id = rec->id;
++ res = GST_RTSP_OK;
++
++done:
++ g_mutex_unlock (watch->mutex);
++ return res;
++}
++
++/**
++ * gst_rtsp_watch_send_message:
++ * @watch: a #GstRTSPWatch
++ * @message: a #GstRTSPMessage
++ * @id: location for a message ID or %NULL
++ *
++ * Send a @message using the connection of the @watch. If it cannot be sent
++ * immediately, it will be queued for transmission in @watch. The contents of
++ * @message will then be serialized and transmitted when the connection of the
++ * @watch becomes writable. In case the @message is queued, the ID returned in
++ * @id will be non-zero and used as the ID argument in the message_sent
++ * callback.
++ *
++ * Returns: #GST_RTSP_OK on success.
++ *
++ * Since: 0.10.25
++ */
++GstRTSPResult
++gst_rtsp_watch_send_message (GstRTSPWatch * watch, GstRTSPMessage * message,
++ guint * id)
++{
++ GString *str;
++ guint size;
++
++ g_return_val_if_fail (watch != NULL, GST_RTSP_EINVAL);
++ g_return_val_if_fail (message != NULL, GST_RTSP_EINVAL);
++
++ /* make a record with the message as a string and id */
++ str = message_to_string (watch->conn, message);
++ size = str->len;
++ return gst_rtsp_watch_write_data (watch,
++ (guint8 *) g_string_free (str, FALSE), size, id);
++}
++
++/**
++ * gst_rtsp_watch_queue_data:
++ * @watch: a #GstRTSPWatch
++ * @data: the data to queue
++ * @size: the size of @data
++ *
++ * Queue @data for transmission in @watch. It will be transmitted when the
++ * connection of the @watch becomes writable.
++ *
++ * This function will take ownership of @data and g_free() it after use.
++ *
++ * The return value of this function will be used as the id argument in the
++ * message_sent callback.
++ *
++ * Deprecated: Use gst_rtsp_watch_write_data()
++ *
++ * Returns: an id.
++ *
++ * Since: 0.10.24
++ */
++#ifndef GST_REMOVE_DEPRECATED
++guint
++gst_rtsp_watch_queue_data (GstRTSPWatch * watch, const guint8 * data,
++ guint size)
++{
++ GstRTSPRec *rec;
++
++ g_return_val_if_fail (watch != NULL, GST_RTSP_EINVAL);
++ g_return_val_if_fail (data != NULL, GST_RTSP_EINVAL);
++ g_return_val_if_fail (size != 0, GST_RTSP_EINVAL);
++
++ g_mutex_lock (watch->mutex);
++
++ /* make a record with the data and id */
++ rec = g_slice_new (GstRTSPRec);
++ rec->data = (guint8 *) data;
++ rec->size = size;
++ do {
++ /* make sure rec->id is never 0 */
++ rec->id = ++watch->id;
++ } while (G_UNLIKELY (rec->id == 0));
++
++ /* add the record to a queue. FIXME we would like to have an upper limit here */
++ g_queue_push_head (watch->messages, rec);
++
++ /* make sure the main context will now also check for writability on the
++ * socket */
++ if (!watch->write_added) {
++ g_source_add_poll ((GSource *) watch, &watch->writefd);
++ watch->write_added = TRUE;
++ }
++
++ g_mutex_unlock (watch->mutex);
++ return rec->id;
++}
++#endif /* GST_REMOVE_DEPRECATED */
++
++/**
++ * gst_rtsp_watch_queue_message:
++ * @watch: a #GstRTSPWatch
++ * @message: a #GstRTSPMessage
++ *
++ * Queue a @message for transmission in @watch. The contents of this
++ * message will be serialized and transmitted when the connection of the
++ * @watch becomes writable.
++ *
++ * The return value of this function will be used as the id argument in the
++ * message_sent callback.
++ *
++ * Deprecated: Use gst_rtsp_watch_send_message()
++ *
++ * Returns: an id.
++ *
++ * Since: 0.10.23
++ */
++#ifndef GST_REMOVE_DEPRECATED
++guint
++gst_rtsp_watch_queue_message (GstRTSPWatch * watch, GstRTSPMessage * message)
++{
++ GString *str;
++ guint size;
++
++ g_return_val_if_fail (watch != NULL, GST_RTSP_EINVAL);
++ g_return_val_if_fail (message != NULL, GST_RTSP_EINVAL);
++
++ /* make a record with the message as a string and id */
++ str = message_to_string (watch->conn, message);
++ size = str->len;
++ return gst_rtsp_watch_queue_data (watch,
++ (guint8 *) g_string_free (str, FALSE), size);
++}
++#endif /* GST_REMOVE_DEPRECATED */
--- /dev/null
--- /dev/null
++#include "rtsp-marshal.h"
++
++#include <glib-object.h>
++
++
++#ifdef G_ENABLE_DEBUG
++#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v)
++#define g_marshal_value_peek_char(v) g_value_get_char (v)
++#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v)
++#define g_marshal_value_peek_int(v) g_value_get_int (v)
++#define g_marshal_value_peek_uint(v) g_value_get_uint (v)
++#define g_marshal_value_peek_long(v) g_value_get_long (v)
++#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v)
++#define g_marshal_value_peek_int64(v) g_value_get_int64 (v)
++#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v)
++#define g_marshal_value_peek_enum(v) g_value_get_enum (v)
++#define g_marshal_value_peek_flags(v) g_value_get_flags (v)
++#define g_marshal_value_peek_float(v) g_value_get_float (v)
++#define g_marshal_value_peek_double(v) g_value_get_double (v)
++#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v)
++#define g_marshal_value_peek_param(v) g_value_get_param (v)
++#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v)
++#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v)
++#define g_marshal_value_peek_object(v) g_value_get_object (v)
++#else /* !G_ENABLE_DEBUG */
++/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API.
++ * Do not access GValues directly in your code. Instead, use the
++ * g_value_get_*() functions
++ */
++#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int
++#define g_marshal_value_peek_char(v) (v)->data[0].v_int
++#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint
++#define g_marshal_value_peek_int(v) (v)->data[0].v_int
++#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint
++#define g_marshal_value_peek_long(v) (v)->data[0].v_long
++#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong
++#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64
++#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64
++#define g_marshal_value_peek_enum(v) (v)->data[0].v_long
++#define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong
++#define g_marshal_value_peek_float(v) (v)->data[0].v_float
++#define g_marshal_value_peek_double(v) (v)->data[0].v_double
++#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer
++#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer
++#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer
++#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer
++#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer
++#endif /* !G_ENABLE_DEBUG */
++
++
++/* ENUM:POINTER,POINTER (rtsp-marshal.list:1) */
++void
++gst_rtsp_marshal_ENUM__POINTER_POINTER (GClosure * closure,
++ GValue * return_value G_GNUC_UNUSED,
++ guint n_param_values,
++ const GValue * param_values,
++ gpointer invocation_hint G_GNUC_UNUSED, gpointer marshal_data)
++{
++ typedef gint (*GMarshalFunc_ENUM__POINTER_POINTER) (gpointer data1,
++ gpointer arg_1, gpointer arg_2, gpointer data2);
++ register GMarshalFunc_ENUM__POINTER_POINTER callback;
++ register GCClosure *cc = (GCClosure *) closure;
++ register gpointer data1, data2;
++ gint v_return;
++
++ g_return_if_fail (return_value != NULL);
++ g_return_if_fail (n_param_values == 3);
++
++ if (G_CCLOSURE_SWAP_DATA (closure)) {
++ data1 = closure->data;
++ data2 = g_value_peek_pointer (param_values + 0);
++ } else {
++ data1 = g_value_peek_pointer (param_values + 0);
++ data2 = closure->data;
++ }
++ callback =
++ (GMarshalFunc_ENUM__POINTER_POINTER) (marshal_data ? marshal_data :
++ cc->callback);
++
++ v_return = callback (data1,
++ g_marshal_value_peek_pointer (param_values + 1),
++ g_marshal_value_peek_pointer (param_values + 2), data2);
++
++ g_value_set_enum (return_value, v_return);
++}
--- /dev/null
--- /dev/null
++
++#ifndef __gst_rtsp_marshal_MARSHAL_H__
++#define __gst_rtsp_marshal_MARSHAL_H__
++
++#include <glib-object.h>
++
++G_BEGIN_DECLS
++
++/* ENUM:POINTER,POINTER (rtsp-marshal.list:1) */
++extern void gst_rtsp_marshal_ENUM__POINTER_POINTER (GClosure *closure,
++ GValue *return_value,
++ guint n_param_values,
++ const GValue *param_values,
++ gpointer invocation_hint,
++ gpointer marshal_data);
++
++G_END_DECLS
++
++#endif /* __gst_rtsp_marshal_MARSHAL_H__ */
++
--- /dev/null
--- /dev/null
++commit bf5ef87699b8ef602548eec131312d7a733e278e
++Author: Josep Torra <n770galaxy@gmail.com>
++Date: Tue Apr 14 18:03:09 2009 +0200
++
++ Added RTSP headers related to Windows Media extension.
++
++diff --git a/gst-libs/gst/rtsp/gstrtspdefs.c b/gst-libs/gst/rtsp/gstrtspdefs.c
++index 0ab2b95..8b086e5 100644
++--- a/gst-libs/gst/rtsp/gstrtspdefs.c
+++++ b/gst-libs/gst/rtsp/gstrtspdefs.c
++@@ -164,6 +164,27 @@ static const gchar *rtsp_headers[] = {
++ "ETag", /* ETag */
++ "If-Match", /* If-Match */
++
+++ /* WM extensions [MS-RTSP] */
+++ "Accept-Charset", /* Accept-Charset */
+++ "Supported", /* Supported */
+++ "Vary", /* Vary */
+++ "X-Accelerate-Streaming", /* X-Accelerate-Streaming */
+++ "X-Accept-Authentication", /* X-Accept-Authentication */
+++ "X-Accept-Proxy-Authentication", /* X-Accept-Proxy-Authentication */
+++ "X-Broadcast-Id", /* X-Broadcast-Id */
+++ "X-Burst-Streaming", /* X-Burst-Streaming */
+++ "X-Notice", /* X-Notice */
+++ "X-Player-Lag-Time", /* X-Player-Lag-Time */
+++ "X-Playlist", /* X-Playlist */
+++ "X-Playlist-Change-Notice", /* X-Playlist-Change-Notice */
+++ "X-Playlist-Gen-Id", /* X-Playlist-Gen-Id */
+++ "X-Playlist-Seek-Id", /* X-Playlist-Seek-Id */
+++ "X-Proxy-Client-Agent", /* X-Proxy-Client-Agent */
+++ "X-Proxy-Client-Verb", /* X-Proxy-Client-Verb */
+++ "X-Receding-PlaylistChange", /* X-Receding-PlaylistChange */
+++ "X-RTP-Info", /* X-RTP-Info */
+++ "X-StartupProfile", /* X-StartupProfile */
+++
++ NULL
++ };
++
++diff --git a/gst-libs/gst/rtsp/gstrtspdefs.h b/gst-libs/gst/rtsp/gstrtspdefs.h
++index dd4b531..ae3b105 100644
++--- a/gst-libs/gst/rtsp/gstrtspdefs.h
+++++ b/gst-libs/gst/rtsp/gstrtspdefs.h
++@@ -287,7 +287,29 @@ typedef enum {
++
++ /* Since 0.10.23 */
++ GST_RTSP_HDR_ETAG, /* ETag */
++- GST_RTSP_HDR_IF_MATCH /* If-Match */
+++ GST_RTSP_HDR_IF_MATCH, /* If-Match */
+++
+++ /* WM extensions [MS-RTSP] */
+++ GST_RTSP_HDR_ACCEPT_CHARSET, /* Accept-Charset */
+++ GST_RTSP_HDR_SUPPORTED, /* Supported */
+++ GST_RTSP_HDR_VARY, /* Vary */
+++ GST_RTSP_HDR_X_ACCELERATE_STREAMING, /* X-Accelerate-Streaming */
+++ GST_RTSP_HDR_X_ACCEPT_AUTHENT, /* X-Accept-Authentication */
+++ GST_RTSP_HDR_X_ACCEPT_PROXY_AUTHENT, /* X-Accept-Proxy-Authentication */
+++ GST_RTSP_HDR_X_BROADCAST_ID, /* X-Broadcast-Id */
+++ GST_RTSP_HDR_X_BURST_STREAMING, /* X-Burst-Streaming */
+++ GST_RTSP_HDR_X_NOTICE, /* X-Notice */
+++ GST_RTSP_HDR_X_PLAYER_LAG_TIME, /* X-Player-Lag-Time */
+++ GST_RTSP_HDR_X_PLAYLIST, /* X-Playlist */
+++ GST_RTSP_HDR_X_PLAYLIST_CHANGE_NOTICE, /* X-Playlist-Change-Notice */
+++ GST_RTSP_HDR_X_PLAYLIST_GEN_ID, /* X-Playlist-Gen-Id */
+++ GST_RTSP_HDR_X_PLAYLIST_SEEK_ID, /* X-Playlist-Seek-Id */
+++ GST_RTSP_HDR_X_PROXY_CLIENT_AGENT, /* X-Proxy-Client-Agent */
+++ GST_RTSP_HDR_X_PROXY_CLIENT_VERB, /* X-Proxy-Client-Verb */
+++ GST_RTSP_HDR_X_RECEDING_PLAYLISTCHANGE, /* X-Receding-PlaylistChange */
+++ GST_RTSP_HDR_X_RTP_INFO, /* X-RTP-Info */
+++ GST_RTSP_HDR_X_STARTUPPROFILE /* X-StartupProfile */
+++
++ } GstRTSPHeaderField;
++
++ typedef enum {
--- /dev/null
--- /dev/null
++/* GStreamer
++ * Copyright (C) <2011> Wim Taymans <wim.taymans@gmail.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., 59 Temple Place - Suite 330,
++ * Boston, MA 02111-1307, USA.
++ */
++
++#ifndef __GST_META_VIDEO_H__
++#define __GST_META_VIDEO_H__
++
++#include <gst/gst.h>
++
++#include <gst/video/video.h>
++
++G_BEGIN_DECLS
++
++#define GST_META_API_VIDEO "GstMetaVideo"
++#define GST_META_INFO_VIDEO (gst_meta_video_get_info())
++
++typedef struct _GstMetaVideo GstMetaVideo;
++
++/**
++ * GstMetaVideo:
++ * @meta: parent #GstMeta
++ * @flags: additional video flags
++ * @n_planes: the number of planes in the image
++ * @offset: array of offsets for the planes
++ * @stride: array of strides for the planes
++ * @map: map the memory of a plane
++ * @unmap: unmap the memory of a plane
++ *
++ * Extra buffer metadata describing image properties
++ */
++struct _GstMetaVideo {
++ GstMeta meta;
++
++ GstBuffer *buffer;
++
++ GstVideoFlags flags;
++ GstVideoFormat format;
++ guint width;
++ guint height;
++
++ guint n_planes;
++ gsize offset[GST_VIDEO_MAX_PLANES];
++ gint stride[GST_VIDEO_MAX_PLANES];
++
++ gpointer (*map) (GstMetaVideo *meta, guint plane, gint *stride,
++ GstMapFlags flags);
++ gboolean (*unmap) (GstMetaVideo *meta, guint plane, gpointer data);
++};
++
++const GstMetaInfo * gst_meta_video_get_info (void);
++
++#define gst_buffer_get_meta_video(b) ((GstMetaVideo*)gst_buffer_get_meta((b),GST_META_INFO_VIDEO))
++GstMetaVideo * gst_buffer_add_meta_video (GstBuffer *buffer, GstVideoFlags flags,
++ GstVideoFormat format, guint width, guint height);
++GstMetaVideo * gst_buffer_add_meta_video_full (GstBuffer *buffer, GstVideoFlags flags,
++ GstVideoFormat format, guint width, guint height,
++ guint n_planes, gsize offset[GST_VIDEO_MAX_PLANES],
++ gint stride[GST_VIDEO_MAX_PLANES]);
++
++gpointer gst_meta_video_map (GstMetaVideo *meta, guint plane, gint *stride,
++ GstMapFlags flags);
++gboolean gst_meta_video_unmap (GstMetaVideo *meta, guint plane, gpointer data);
++
++
++G_END_DECLS
++
++#endif /* __GST_META_VIDEO_H__ */
/* FIXME : Once we have properties for specific converters, use those */
if (GST_IS_ENCODING_VIDEO_PROFILE (sprof)) {
const gboolean native_video =
-- ! !(ebin->flags & GST_ENC_FLAG_NO_VIDEO_CONVERSION);
++ !!(ebin->flags & GST_ENC_FLAG_NO_VIDEO_CONVERSION);
GstElement *cspace = NULL, *scale, *vrate, *cspace2 = NULL;
GST_LOG ("Adding conversion elements for video stream");
built_sources = gstplay-marshal.c
built_headers = gstplay-marshal.h
-plugin_LTLIBRARIES = libgstplaybin.la libgstdecodebin.la libgstdecodebin2.la
+plugin_LTLIBRARIES = libgstplayback.la
-csp_cflags = -DCOLORSPACE=\"ffmpegcolorspace\"
++csp_cflags = -DCOLORSPACE=\"videoconvert\"
+
-libgstplaybin_la_SOURCES = \
+libgstplayback_la_SOURCES = \
+ gstdecodebin2.c \
+ gsturidecodebin.c \
gstplayback.c \
- gstplaybin.c \
gstplaybin2.c \
gstplaysink.c \
- gstplaybasebin.c \
gstplay-enum.c \
- gststreaminfo.c \
gststreamselector.c \
gstsubtitleoverlay.c \
gstplaysinkvideoconvert.c \
gstplaysinkaudioconvert.c \
gststreamsynchronizer.c
-nodist_libgstplaybin_la_SOURCES = $(built_sources)
-libgstplaybin_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) $(csp_cflags)
-libgstplaybin_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
-libgstplaybin_la_LIBADD = \
+nodist_libgstplayback_la_SOURCES = $(built_sources)
- libgstplayback_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS)
++libgstplayback_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) $(csp_cflags)
+libgstplayback_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
+libgstplayback_la_LIBADD = \
$(top_builddir)/gst-libs/gst/pbutils/libgstpbutils-@GST_MAJORMINOR@.la \
$(top_builddir)/gst-libs/gst/interfaces/libgstinterfaces-@GST_MAJORMINOR@.la \
$(top_builddir)/gst-libs/gst/video/libgstvideo-@GST_MAJORMINOR@.la \
Android.mk: Makefile.am $(BUILT_SOURCES)
androgenizer \
- -:PROJECT libgstdecodebin -:SHARED libgstdecodebin \
- -:TAGS eng debug \
- -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \
- -:SOURCES $(libgstdecodebin_la_SOURCES) \
- $(nodist_libgstdecodebin_la_SOURCES) \
- -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstdecodebin_la_CFLAGS) $(csp_cflags) \
- -:LDFLAGS $(libgstdecodebin_la_LDFLAGS) \
- $(libgstdecodebin_la_LIBADD) \
- -ldl \
- -:PASSTHROUGH LOCAL_ARM_MODE:=arm \
- LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \
- \
- -:PROJECT libgstdecodebin2 -:SHARED libgstdecodebin2 \
- -:TAGS eng debug \
- -:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \
- -:SOURCES $(libgstdecodebin2_la_SOURCES) \
- $(nodist_libgstdecodebin2_la_SOURCES) \
- -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstdecodebin2_la_CFLAGS) $(csp_cflags) \
- -:LDFLAGS $(libgstdecodebin2_la_LDFLAGS) \
- $(libgstdecodebin2_la_LIBADD) \
- -ldl \
- -:PASSTHROUGH LOCAL_ARM_MODE:=arm \
- LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \
- \
- -:PROJECT libgstplaybin -:SHARED libgstplaybin \
+ -:PROJECT libgstplayback -:SHARED libgstplayback \
-:TAGS eng debug \
-:REL_TOP $(top_srcdir) -:ABS_TOP $(abs_top_srcdir) \
- -:SOURCES $(libgstplaybin_la_SOURCES) \
- $(nodist_libgstplaybin_la_SOURCES) \
- -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstplaybin_la_CFLAGS) $(csp_cflags) \
- -:LDFLAGS $(libgstplaybin_la_LDFLAGS) \
- $(libgstplaybin_la_LIBADD) \
+ -:SOURCES $(libgstplayback_la_SOURCES) \
+ $(nodist_libgstplayback_la_SOURCES) \
- -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstplayback_la_CFLAGS) \
++ -:CFLAGS $(DEFS) $(DEFAULT_INCLUDES) $(libgstplayback_la_CFLAGS) $(csp_cflags) \
+ -:LDFLAGS $(libgstplayback_la_LDFLAGS) \
+ $(libgstplayback_la_LIBADD) \
-ldl \
-:PASSTHROUGH LOCAL_ARM_MODE:=arm \
LOCAL_MODULE_PATH:='$$(TARGET_OUT)/lib/gstreamer-0.10' \
ppad->pad = gst_object_ref (pad);
ppad->chain = chain;
ppad->event_probe_id =
- gst_pad_add_event_probe (pad, (GCallback) pad_event_cb, ppad);
+ gst_pad_add_probe (pad, GST_PROBE_TYPE_EVENT, pad_event_cb, ppad, NULL);
chain->pending_pads = g_list_prepend (chain->pending_pads, ppad);
- CHAIN_MUTEX_UNLOCK (chain);
g_signal_connect (G_OBJECT (pad), "notify::caps",
G_CALLBACK (caps_notify_cb), chain);
+ CHAIN_MUTEX_UNLOCK (chain);
return;
}
}
static void gst_video_rate_swap_prev (GstVideoRate * videorate,
GstBuffer * buffer, gint64 time);
- static gboolean gst_video_rate_event (GstPad * pad, GstEvent * event);
- static gboolean gst_video_rate_query (GstPad * pad, GstQuery * query);
- static GstFlowReturn gst_video_rate_chain (GstPad * pad, GstBuffer * buffer);
-static gboolean gst_video_rate_event (GstBaseTransform * trans,
++static gboolean gst_video_rate_sink_event (GstBaseTransform * trans,
+ GstEvent * event);
+ static gboolean gst_video_rate_query (GstBaseTransform * trans,
+ GstPadDirection direction, GstQuery * query);
+
+ static gboolean gst_video_rate_setcaps (GstBaseTransform * trans,
+ GstCaps * in_caps, GstCaps * out_caps);
+
+ static GstCaps *gst_video_rate_transform_caps (GstBaseTransform * trans,
- GstPadDirection direction, GstCaps * caps);
++ GstPadDirection direction, GstCaps * caps, GstCaps * filter);
+
+ static void gst_video_rate_fixate_caps (GstBaseTransform * trans,
+ GstPadDirection direction, GstCaps * caps, GstCaps * othercaps);
+
-static GstFlowReturn gst_video_rate_prepare_output_buffer (GstBaseTransform *
- trans, GstBuffer * input, gint size, GstCaps * caps, GstBuffer ** buf);
+ static GstFlowReturn gst_video_rate_transform_ip (GstBaseTransform * trans,
+ GstBuffer * buf);
+
+ static gboolean gst_video_rate_start (GstBaseTransform * trans);
+
static void gst_video_rate_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
static GParamSpec *pspec_drop = NULL;
static GParamSpec *pspec_duplicate = NULL;
-GST_BOILERPLATE (GstVideoRate, gst_video_rate,
- GstBaseTransform, GST_TYPE_BASE_TRANSFORM);
-
-static void
-gst_video_rate_base_init (gpointer g_class)
-{
- GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
-
- gst_element_class_set_details_simple (element_class,
- "Video rate adjuster", "Filter/Effect/Video",
- "Drops/duplicates/adjusts timestamps on video frames to make a perfect stream",
- "Wim Taymans <wim@fluendo.com>");
-
- gst_element_class_add_pad_template (element_class,
- gst_static_pad_template_get (&gst_video_rate_sink_template));
- gst_element_class_add_pad_template (element_class,
- gst_static_pad_template_get (&gst_video_rate_src_template));
-}
+#define gst_video_rate_parent_class parent_class
- G_DEFINE_TYPE (GstVideoRate, gst_video_rate, GST_TYPE_ELEMENT);
++G_DEFINE_TYPE (GstVideoRate, gst_video_rate, GST_TYPE_BASE_TRANSFORM);
static void
gst_video_rate_class_init (GstVideoRateClass * klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
- parent_class = g_type_class_peek_parent (klass);
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+ GstBaseTransformClass *base_class = GST_BASE_TRANSFORM_CLASS (klass);
- parent_class = g_type_class_peek_parent (klass);
-
object_class->set_property = gst_video_rate_set_property;
object_class->get_property = gst_video_rate_get_property;
- base_class->prepare_output_buffer =
- GST_DEBUG_FUNCPTR (gst_video_rate_prepare_output_buffer);
- base_class->event = GST_DEBUG_FUNCPTR (gst_video_rate_event);
+ base_class->set_caps = GST_DEBUG_FUNCPTR (gst_video_rate_setcaps);
+ base_class->transform_caps =
+ 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->start = GST_DEBUG_FUNCPTR (gst_video_rate_start);
+ base_class->fixate_caps = GST_DEBUG_FUNCPTR (gst_video_rate_fixate_caps);
+ base_class->query = GST_DEBUG_FUNCPTR (gst_video_rate_query);
+
g_object_class_install_property (object_class, ARG_IN,
g_param_spec_uint64 ("in", "In",
"Number of input frames", 0, G_MAXUINT64, 0,
"Period over which to average the framerate (in ns) (0 = disabled)",
0, G_MAXINT64, DEFAULT_AVERAGE_PERIOD,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
- element_class->change_state = GST_DEBUG_FUNCPTR (gst_video_rate_change_state);
- }
-
- /* return the caps that can be used on out_pad given in_caps on in_pad */
- static gboolean
- gst_video_rate_transformcaps (GstPad * in_pad, GstCaps * in_caps,
- GstPad * out_pad, GstCaps ** out_caps, GstCaps * filter)
- {
- GstCaps *intersect, *in_templ;
- gint i;
- GSList *extra_structures = NULL;
- GSList *iter;
-
- in_templ = gst_pad_get_pad_template_caps (in_pad);
- intersect =
- gst_caps_intersect_full (in_caps, in_templ, GST_CAPS_INTERSECT_FIRST);
- gst_caps_unref (in_templ);
+
- /* all possible framerates are allowed */
- for (i = 0; i < gst_caps_get_size (intersect); i++) {
- GstStructure *structure;
++ gst_element_class_set_details_simple (element_class,
++ "Video rate adjuster", "Filter/Effect/Video",
++ "Drops/duplicates/adjusts timestamps on video frames to make a perfect stream",
++ "Wim Taymans <wim@fluendo.com>");
+
- structure = gst_caps_get_structure (intersect, i);
++ gst_element_class_add_pad_template (element_class,
++ gst_static_pad_template_get (&gst_video_rate_sink_template));
++ gst_element_class_add_pad_template (element_class,
++ gst_static_pad_template_get (&gst_video_rate_src_template));
+
+ }
- if (gst_structure_has_field (structure, "framerate")) {
- GstStructure *copy_structure;
+ static GstCaps *
+ gst_video_rate_transform_caps (GstBaseTransform * trans,
- GstPadDirection direction, GstCaps * caps)
++ GstPadDirection direction, GstCaps * caps, GstCaps * filter)
+ {
+ GstCaps *ret;
+ GstStructure *s;
- copy_structure = gst_structure_copy (structure);
- gst_structure_set (copy_structure,
- "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, G_MAXINT, 1, NULL);
- extra_structures = g_slist_append (extra_structures, copy_structure);
- }
- }
+ /* Should always be called with simple caps */
+ g_return_val_if_fail (GST_CAPS_IS_SIMPLE (caps), NULL);
- /* append the extra structures */
- for (iter = extra_structures; iter != NULL; iter = g_slist_next (iter)) {
- gst_caps_append_structure (intersect, (GstStructure *) iter->data);
- }
- g_slist_free (extra_structures);
+ ret = gst_caps_copy (caps);
- if (filter) {
- GstCaps *tmp;
+ s = gst_structure_copy (gst_caps_get_structure (caps, 0));
- tmp = gst_caps_intersect_full (filter, intersect, GST_CAPS_INTERSECT_FIRST);
- gst_caps_unref (intersect);
- intersect = tmp;
- }
+ /* set the framerate as a range */
+ gst_structure_set (s, "framerate", GST_TYPE_FRACTION_RANGE, 0, 1,
+ G_MAXINT, 1, NULL);
- *out_caps = intersect;
+ gst_caps_append_structure (ret, s);
- return TRUE;
+ return ret;
}
- static GstCaps *
- gst_video_rate_getcaps (GstPad * pad, GstCaps * filter)
+ static void
+ gst_video_rate_fixate_caps (GstBaseTransform * trans,
+ GstPadDirection direction, GstCaps * caps, GstCaps * othercaps)
{
- GstVideoRate *videorate;
- GstPad *otherpad;
- GstCaps *caps;
-
- videorate = GST_VIDEO_RATE (GST_PAD_PARENT (pad));
-
- otherpad = (pad == videorate->srcpad) ? videorate->sinkpad :
- videorate->srcpad;
-
- /* we can do what the peer can */
- caps = gst_pad_peer_get_caps (otherpad, filter);
- if (caps) {
- GstCaps *transform, *intersect;
-
- gst_video_rate_transformcaps (otherpad, caps, pad, &transform, filter);
+ GstStructure *s;
+ gint num, denom;
- /* Now prefer the downstream caps if possible */
- intersect =
- gst_caps_intersect_full (caps, transform, GST_CAPS_INTERSECT_FIRST);
- if (!gst_caps_is_empty (intersect)) {
- gst_caps_append (intersect, transform);
- gst_caps_unref (caps);
- caps = intersect;
- } else {
- gst_caps_unref (intersect);
- caps = transform;
- }
- } else {
- /* no peer, our padtemplate is enough then */
- caps = gst_pad_get_pad_template_caps (pad);
- if (filter) {
- GstCaps *intersection;
- intersection =
- gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
- gst_caps_unref (caps);
- caps = intersection;
- }
- }
+ s = gst_caps_get_structure (caps, 0);
+ if (G_UNLIKELY (!gst_structure_get_fraction (s, "framerate", &num, &denom)))
+ return;
- return caps;
+ s = gst_caps_get_structure (othercaps, 0);
+ gst_structure_fixate_field_nearest_fraction (s, "framerate", num, denom);
}
static gboolean
- gst_video_rate_set_src_caps (GstVideoRate * videorate, GstCaps * caps)
+ gst_video_rate_setcaps (GstBaseTransform * trans, GstCaps * in_caps,
+ GstCaps * out_caps)
{
- GstVideoRate *videorate;
++ GstVideoRate *videorate = GST_VIDEO_RATE (trans);
GstStructure *structure;
+ gboolean ret = TRUE;
gint rate_numerator, rate_denominator;
- GST_DEBUG_OBJECT (videorate, "src caps %" GST_PTR_FORMAT, caps);
+ videorate = GST_VIDEO_RATE (trans);
+
+ GST_DEBUG_OBJECT (trans, "setcaps called in: %" GST_PTR_FORMAT
+ " out: %" GST_PTR_FORMAT, in_caps, out_caps);
+
+ structure = gst_caps_get_structure (in_caps, 0);
+ if (!gst_structure_get_fraction (structure, "framerate",
+ &rate_numerator, &rate_denominator))
+ goto no_framerate;
+
+ videorate->from_rate_numerator = rate_numerator;
+ videorate->from_rate_denominator = rate_denominator;
- structure = gst_caps_get_structure (caps, 0);
+ structure = gst_caps_get_structure (out_caps, 0);
if (!gst_structure_get_fraction (structure, "framerate",
&rate_numerator, &rate_denominator))
goto no_framerate;
}
static void
-gst_video_rate_init (GstVideoRate * videorate, GstVideoRateClass * klass)
+gst_video_rate_init (GstVideoRate * videorate)
{
- videorate->sinkpad =
- gst_pad_new_from_static_template (&gst_video_rate_sink_template, "sink");
- gst_pad_set_event_function (videorate->sinkpad,
- GST_DEBUG_FUNCPTR (gst_video_rate_event));
- gst_pad_set_chain_function (videorate->sinkpad,
- GST_DEBUG_FUNCPTR (gst_video_rate_chain));
- gst_pad_set_getcaps_function (videorate->sinkpad,
- GST_DEBUG_FUNCPTR (gst_video_rate_getcaps));
- gst_element_add_pad (GST_ELEMENT (videorate), videorate->sinkpad);
-
- videorate->srcpad =
- gst_pad_new_from_static_template (&gst_video_rate_src_template, "src");
- gst_pad_set_query_function (videorate->srcpad,
- GST_DEBUG_FUNCPTR (gst_video_rate_query));
- gst_pad_set_getcaps_function (videorate->srcpad,
- GST_DEBUG_FUNCPTR (gst_video_rate_getcaps));
- gst_element_add_pad (GST_ELEMENT (videorate), videorate->srcpad);
-
gst_video_rate_reset (videorate);
videorate->silent = DEFAULT_SILENT;
videorate->new_pref = DEFAULT_NEW_PREF;
#define MAGIC_LIMIT 25
static gboolean
- gst_video_rate_event (GstPad * pad, GstEvent * event)
-gst_video_rate_event (GstBaseTransform * trans, GstEvent * event)
++gst_video_rate_sink_event (GstBaseTransform * trans, GstEvent * event)
{
GstVideoRate *videorate;
- gboolean ret;
- videorate = GST_VIDEO_RATE (gst_pad_get_parent (pad));
+ videorate = GST_VIDEO_RATE (trans);
switch (GST_EVENT_TYPE (event)) {
- case GST_EVENT_CAPS:
- {
- GstCaps *caps;
-
- gst_event_parse_caps (event, &caps);
- ret = gst_video_rate_set_sink_caps (videorate, caps);
- gst_event_unref (event);
-
- /* don't forward */
- goto done;
- }
- case GST_EVENT_NEWSEGMENT:
+ case GST_EVENT_SEGMENT:
{
- gint64 start, stop, time;
- gdouble rate, arate;
- gboolean update;
- GstFormat format;
+ const GstSegment *segment;
- gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format,
- &start, &stop, &time);
+ gst_event_parse_segment (event, &segment);
- if (format != GST_FORMAT_TIME)
+ if (segment->format != GST_FORMAT_TIME)
goto format_error;
GST_DEBUG_OBJECT (videorate, "handle NEWSEGMENT");
gst_query_set_latency (query, live, min, max);
}
gst_object_unref (peer);
+ break;
}
- break;
+ /* Simple fallthrough if we don't have a latency or not a peer that we
+ * can't ask about its latency yet.. */
}
default:
- res = gst_pad_query_default (pad, query);
- res = parent_class->query (trans, direction, query);
++ res =
++ GST_BASE_TRANSFORM_CLASS (parent_class)->query (trans, direction,
++ query);
break;
}
- gst_object_unref (videorate);
return res;
}
}
static GstFlowReturn
- gst_video_rate_chain (GstPad * pad, GstBuffer * buffer)
-gst_video_rate_prepare_output_buffer (GstBaseTransform * trans,
- GstBuffer * input, gint size, GstCaps * caps, GstBuffer ** buf)
-{
- if (gst_buffer_is_metadata_writable (input)) {
- gst_buffer_set_caps (input, caps);
- *buf = gst_buffer_ref (input);
- } else {
- *buf = gst_buffer_create_sub (input, 0, GST_BUFFER_SIZE (input));
- gst_buffer_set_caps (*buf, caps);
- }
-
- return GST_FLOW_OK;
-}
-
-static GstFlowReturn
+ gst_video_rate_transform_ip (GstBaseTransform * trans, GstBuffer * buffer)
{
GstVideoRate *videorate;
- GstFlowReturn res = GST_FLOW_OK;
+ GstFlowReturn res = GST_BASE_TRANSFORM_FLOW_DROPPED;
GstClockTime intime, in_ts, in_dur;
GstClockTime avg_period;
gboolean skip = FALSE;
--- /dev/null
--- /dev/null
++diff --git a/gst/videorate/gstvideorate.c b/gst/videorate/gstvideorate.c
++index 8d22186..7afcfdd 100644
++--- a/gst/videorate/gstvideorate.c
+++++ b/gst/videorate/gstvideorate.c
++@@ -357,6 +357,7 @@ gst_video_rate_reset (GstVideoRate * videorate)
++ videorate->drop = 0;
++ videorate->dup = 0;
++ videorate->next_ts = GST_CLOCK_TIME_NONE;
+++ videorate->discont = TRUE;
++ gst_video_rate_swap_prev (videorate, NULL, 0);
++
++ gst_segment_init (&videorate->segment, GST_FORMAT_TIME);
++@@ -409,6 +410,13 @@ gst_video_rate_flush_prev (GstVideoRate * videorate)
++ GST_BUFFER_OFFSET (outbuf) = videorate->out;
++ GST_BUFFER_OFFSET_END (outbuf) = videorate->out + 1;
++
+++ if (videorate->discont) {
+++ GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT);
+++ videorate->discont = FALSE;
+++ }
+++ else
+++ GST_BUFFER_FLAG_UNSET (outbuf, GST_BUFFER_FLAG_DISCONT);
+++
++ /* this is the timestamp we put on the buffer */
++ push_ts = videorate->next_ts;
++
++@@ -810,6 +818,9 @@ gst_video_rate_change_state (GstElement * element, GstStateChange transition)
++ videorate = GST_VIDEO_RATE (element);
++
++ switch (transition) {
+++ case GST_STATE_CHANGE_READY_TO_PAUSED:
+++ videorate->discont = TRUE;
+++ break;
++ default:
++ break;
++ }
++diff --git a/gst/videorate/gstvideorate.h b/gst/videorate/gstvideorate.h
++index ea6063b..fe7feb6 100644
++--- a/gst/videorate/gstvideorate.h
+++++ b/gst/videorate/gstvideorate.h
++@@ -56,6 +56,7 @@ struct _GstVideoRate
++ GstBuffer *prevbuf;
++ guint64 prev_ts; /* Previous buffer timestamp */
++ guint64 segment_out; /* in-segment counting */
+++ gboolean discont;
++
++ /* segment handling */
++ GstSegment segment;
DEFAULT_PROP_ADD_BORDERS,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_SHARPNESS,
+ g_param_spec_double ("sharpness", "Sharpness",
+ "Sharpness of filter", 0.0, 2.0, DEFAULT_PROP_SHARPNESS,
+ G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_SHARPEN,
+ g_param_spec_double ("sharpen", "Sharpen",
+ "Sharpening", 0.0, 1.0, DEFAULT_PROP_SHARPEN,
+ G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_DITHER,
+ g_param_spec_boolean ("dither", "Dither",
+ "Add dither (only used for Lanczos method)",
+ DEFAULT_PROP_DITHER,
+ G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ #if 0
+ /* I am hiding submethod for now, since it's poorly named, poorly
+ * documented, and will probably just get people into trouble. */
+ g_object_class_install_property (gobject_class, PROP_SUBMETHOD,
+ g_param_spec_int ("submethod", "submethod",
+ "submethod", 0, 3, DEFAULT_PROP_SUBMETHOD,
+ G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ #endif
+
+ g_object_class_install_property (gobject_class, PROP_ENVELOPE,
+ g_param_spec_double ("envelope", "Envelope",
+ "Size of filter envelope", 0.0, 5.0, DEFAULT_PROP_ENVELOPE,
+ G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gst_element_class_set_details_simple (element_class,
+ "Video scaler", "Filter/Converter/Video/Scaler",
+ "Resizes video", "Wim Taymans <wim.taymans@chello.be>");
+
+ gst_element_class_add_pad_template (element_class,
+ gst_video_scale_sink_template_factory ());
+ gst_element_class_add_pad_template (element_class,
+ gst_video_scale_src_template_factory ());
+
trans_class->transform_caps =
GST_DEBUG_FUNCPTR (gst_video_scale_transform_caps);
trans_class->set_caps = GST_DEBUG_FUNCPTR (gst_video_scale_set_caps);
case GST_VIDEO_FORMAT_ABGR:
case GST_VIDEO_FORMAT_AYUV:
if (add_borders)
- vs_fill_borders_RGBA (&dest, black);
+ vs_fill_borders_RGBA (&dest[0], black);
switch (method) {
case GST_VIDEO_SCALE_NEAREST:
- vs_image_scale_nearest_RGBA (&dest, &src, videoscale->tmp_buf);
+ vs_image_scale_nearest_RGBA (&dest[0], &src[0], videoscale->tmp_buf);
break;
case GST_VIDEO_SCALE_BILINEAR:
- vs_image_scale_linear_RGBA (&dest, &src, videoscale->tmp_buf);
+ vs_image_scale_linear_RGBA (&dest[0], &src[0], videoscale->tmp_buf);
break;
case GST_VIDEO_SCALE_4TAP:
- vs_image_scale_4tap_RGBA (&dest, &src, videoscale->tmp_buf);
+ vs_image_scale_4tap_RGBA (&dest[0], &src[0], videoscale->tmp_buf);
break;
- vs_image_scale_lanczos_AYUV (&dest, &src, videoscale->tmp_buf,
+ case GST_VIDEO_SCALE_LANCZOS:
++ vs_image_scale_lanczos_AYUV (&dest[0], &src[0], videoscale->tmp_buf,
+ videoscale->sharpness, videoscale->dither, videoscale->submethod,
+ videoscale->envelope, videoscale->sharpen);
+ break;
default:
goto unknown_mode;
}
}
switch (method) {
case GST_VIDEO_SCALE_NEAREST:
- vs_image_scale_nearest_Y (&dest, &src, videoscale->tmp_buf);
- vs_image_scale_nearest_Y (&dest_u, &src_u, videoscale->tmp_buf);
- vs_image_scale_nearest_Y (&dest_v, &src_v, videoscale->tmp_buf);
+ vs_image_scale_nearest_Y (&dest[0], &src[0], videoscale->tmp_buf);
+ vs_image_scale_nearest_Y (&dest[1], &src[1], videoscale->tmp_buf);
+ vs_image_scale_nearest_Y (&dest[2], &src[2], videoscale->tmp_buf);
break;
case GST_VIDEO_SCALE_BILINEAR:
- vs_image_scale_linear_Y (&dest, &src, videoscale->tmp_buf);
- vs_image_scale_linear_Y (&dest_u, &src_u, videoscale->tmp_buf);
- vs_image_scale_linear_Y (&dest_v, &src_v, videoscale->tmp_buf);
+ vs_image_scale_linear_Y (&dest[0], &src[0], videoscale->tmp_buf);
+ vs_image_scale_linear_Y (&dest[1], &src[1], videoscale->tmp_buf);
+ vs_image_scale_linear_Y (&dest[2], &src[2], videoscale->tmp_buf);
break;
case GST_VIDEO_SCALE_4TAP:
- vs_image_scale_4tap_Y (&dest, &src, videoscale->tmp_buf);
- vs_image_scale_4tap_Y (&dest_u, &src_u, videoscale->tmp_buf);
- vs_image_scale_4tap_Y (&dest_v, &src_v, videoscale->tmp_buf);
+ vs_image_scale_4tap_Y (&dest[0], &src[0], videoscale->tmp_buf);
+ vs_image_scale_4tap_Y (&dest[1], &src[1], videoscale->tmp_buf);
+ vs_image_scale_4tap_Y (&dest[2], &src[2], videoscale->tmp_buf);
break;
- vs_image_scale_lanczos_Y (&dest, &src, videoscale->tmp_buf,
+ case GST_VIDEO_SCALE_LANCZOS:
- vs_image_scale_lanczos_Y (&dest_u, &src_u, videoscale->tmp_buf,
++ vs_image_scale_lanczos_Y (&dest[0], &src[0], videoscale->tmp_buf,
+ videoscale->sharpness, videoscale->dither, videoscale->submethod,
+ videoscale->envelope, videoscale->sharpen);
- vs_image_scale_lanczos_Y (&dest_v, &src_v, videoscale->tmp_buf,
++ vs_image_scale_lanczos_Y (&dest[1], &src[1], videoscale->tmp_buf,
+ videoscale->sharpness, videoscale->dither, videoscale->submethod,
+ videoscale->envelope, videoscale->sharpen);
++ vs_image_scale_lanczos_Y (&dest[2], &src[2], videoscale->tmp_buf,
+ videoscale->sharpness, videoscale->dither, videoscale->submethod,
+ videoscale->envelope, videoscale->sharpen);
+ break;
default:
goto unknown_mode;
}
struct _GstVideoScale {
GstVideoFilter element;
+ /* properties */
GstVideoScaleMethod method;
gboolean add_borders;
+ double sharpness;
+ double sharpen;
+ gboolean dither;
+ int submethod;
+ double envelope;
/* negotiated stuff */
- GstVideoFormat format;
- gint to_width;
- gint to_height;
- gint from_width;
- gint from_height;
- guint src_size;
- guint dest_size;
+ GstVideoInfo from_info;
+ GstVideoInfo to_info;
gint borders_h;
gint borders_w;
GstEncodingProfile *profile;
GstCaps *tmpcaps;
GValue strvalue = { 0, };
- GValue miniobjectvalue = { 0, };
+ GValue objectvalue = { 0, };
+ gst_debug_set_threshold_for_name ("default", GST_LEVEL_NONE);
+
/* Test loading using short method and all arguments */
target = gst_encoding_target_load ("myponytarget", "herding", NULL);
fail_unless (target != NULL);
gint i;
guint8 *data;
+ gst_debug_set_threshold_for_name ("default", GST_LEVEL_NONE);
+
from_buffer = gst_buffer_new_and_alloc (640 * 480 * 4);
- data = GST_BUFFER_DATA (from_buffer);
+ data = gst_buffer_map (from_buffer, NULL, NULL, GST_MAP_WRITE);
for (i = 0; i < 640 * 480; i++) {
data[4 * i + 0] = 0; /* x */
data[4 * i + 1] = 255; /* R */
GMainLoop *loop;
ConvertFrameContext cf_data = { NULL, NULL, NULL };
+ gst_debug_set_threshold_for_name ("default", GST_LEVEL_NONE);
+
from_buffer = gst_buffer_new_and_alloc (640 * 480 * 4);
- data = GST_BUFFER_DATA (from_buffer);
+ data = gst_buffer_map (from_buffer, NULL, NULL, GST_MAP_WRITE);
for (i = 0; i < 640 * 480; i++) {
data[4 * i + 0] = 0; /* x */
data[4 * i + 1] = 255; /* R */