#include "gstbaseparse.h"
+#define MIN_FRAMES_TO_POST_BITRATE 10
+
GST_DEBUG_CATEGORY_STATIC (gst_base_parse_debug);
#define GST_CAT_DEFAULT gst_base_parse_debug
guint64 framecount;
guint64 bytecount;
+ guint64 data_bytecount;
guint64 acc_duration;
+ gboolean post_min_bitrate;
+ gboolean post_avg_bitrate;
+ gboolean post_max_bitrate;
+ guint min_bitrate;
+ guint avg_bitrate;
+ guint max_bitrate;
+
GList *pending_events;
GstBuffer *cache;
};
-struct _GstBaseParseClassPrivate
-{
- gpointer _padding;
-};
-
static GstElementClass *parent_class = NULL;
-static void gst_base_parse_base_init (gpointer g_class);
-static void gst_base_parse_base_finalize (gpointer g_class);
static void gst_base_parse_class_init (GstBaseParseClass * klass);
static void gst_base_parse_init (GstBaseParse * parse,
GstBaseParseClass * klass);
if (!base_parse_type) {
static const GTypeInfo base_parse_info = {
sizeof (GstBaseParseClass),
- (GBaseInitFunc) gst_base_parse_base_init,
- (GBaseFinalizeFunc) gst_base_parse_base_finalize,
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
(GClassInitFunc) gst_base_parse_class_init,
NULL,
NULL,
};
base_parse_type = g_type_register_static (GST_TYPE_ELEMENT,
- "GstBaseParse", &base_parse_info, G_TYPE_FLAG_ABSTRACT);
+ "GstAudioBaseParseBad", &base_parse_info, G_TYPE_FLAG_ABSTRACT);
}
return base_parse_type;
}
gboolean active);
static gboolean gst_base_parse_handle_seek (GstBaseParse * parse,
GstEvent * event);
+static void gst_base_parse_handle_tag (GstBaseParse * parse, GstEvent * event);
static gboolean gst_base_parse_src_event (GstPad * pad, GstEvent * event);
static gboolean gst_base_parse_sink_event (GstPad * pad, GstEvent * event);
static void gst_base_parse_drain (GstBaseParse * parse);
-static void
-gst_base_parse_base_init (gpointer g_class)
-{
- GstBaseParseClass *klass = GST_BASE_PARSE_CLASS (g_class);
- GstBaseParseClassPrivate *priv;
-
- GST_DEBUG_CATEGORY_INIT (gst_base_parse_debug, "baseparse", 0,
- "baseparse element");
-
- /* TODO: Remove this once GObject supports class private data */
- priv = g_slice_new0 (GstBaseParseClassPrivate);
- if (klass->priv)
- memcpy (priv, klass->priv, sizeof (GstBaseParseClassPrivate));
- klass->priv = priv;
-}
-
-static void
-gst_base_parse_base_finalize (gpointer g_class)
-{
- GstBaseParseClass *klass = GST_BASE_PARSE_CLASS (g_class);
-
- g_slice_free (GstBaseParseClassPrivate, klass->priv);
- klass->priv = NULL;
-}
+static void gst_base_parse_post_bitrates (GstBaseParse * parse,
+ gboolean post_min, gboolean post_avg, gboolean post_max);
static void
gst_base_parse_finalize (GObject * object)
/* Default handlers */
klass->check_valid_frame = gst_base_parse_check_frame;
klass->parse_frame = gst_base_parse_parse_frame;
- klass->event = gst_base_parse_sink_eventfunc;
klass->src_event = gst_base_parse_src_eventfunc;
klass->is_seekable = gst_base_parse_is_seekable;
klass->convert = gst_base_parse_convert;
+
+ GST_DEBUG_CATEGORY_INIT (gst_base_parse_debug, "baseparse", 0,
+ "baseparse element");
}
static void
&& GST_EVENT_TYPE (event) != GST_EVENT_NEWSEGMENT
&& GST_EVENT_TYPE (event) != GST_EVENT_FLUSH_START
&& GST_EVENT_TYPE (event) != GST_EVENT_FLUSH_STOP) {
+
+ if (GST_EVENT_TYPE (event) == GST_EVENT_TAG)
+ /* See if any bitrate tags were posted */
+ gst_base_parse_handle_tag (parse, event);
+
parse->priv->pending_events =
g_list_append (parse->priv->pending_events, event);
ret = TRUE;
} else {
+ if (GST_EVENT_TYPE (event) == GST_EVENT_EOS &&
+ parse->priv->framecount < MIN_FRAMES_TO_POST_BITRATE)
+ /* We've not posted bitrate tags yet - do so now */
+ gst_base_parse_post_bitrates (parse, TRUE, TRUE, TRUE);
+
if (bclass->event)
handled = bclass->event (parse, event);
if (!handled)
+ handled = gst_base_parse_sink_eventfunc (parse, event);
+
+ if (!handled)
ret = gst_pad_event_default (pad, event);
}
if (!handled)
ret = gst_pad_event_default (pad, event);
+ else
+ gst_event_unref (event);
gst_object_unref (parse);
return ret;
{
if (bclass->is_seekable (parse)) {
handled = gst_base_parse_handle_seek (parse, event);
- gst_event_unref (event);
}
break;
}
}
}
+static void
+gst_base_parse_post_bitrates (GstBaseParse * parse, gboolean post_min,
+ gboolean post_avg, gboolean post_max)
+{
+ GstTagList *taglist = gst_tag_list_new ();
+
+ if (post_min && parse->priv->post_min_bitrate)
+ gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE,
+ GST_TAG_MINIMUM_BITRATE, parse->priv->min_bitrate, NULL);
+
+ if (post_avg && parse->priv->post_min_bitrate)
+ gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, GST_TAG_BITRATE,
+ parse->priv->avg_bitrate, NULL);
+
+ if (post_max && parse->priv->post_max_bitrate)
+ gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE,
+ GST_TAG_MAXIMUM_BITRATE, parse->priv->max_bitrate, NULL);
+
+ GST_DEBUG_OBJECT (parse, "Updated bitrates. Min: %u, Avg: %u, Max: %u",
+ parse->priv->min_bitrate, parse->priv->avg_bitrate,
+ parse->priv->max_bitrate);
+
+ gst_element_found_tags_for_pad (GST_ELEMENT (parse), parse->srcpad, taglist);
+}
+
+/**
+ * gst_base_parse_update_bitrates:
+ * @parse: #GstBaseParse.
+ * @buffer: Current frame as a #GstBuffer
+ *
+ * Keeps track of the minimum and maximum bitrates, and also maintains a
+ * running average bitrate of the stream so far.
+ */
+static void
+gst_base_parse_update_bitrates (GstBaseParse * parse, GstBuffer * buffer)
+{
+ /* Only update the tag on a 10 kbps delta */
+ static const gint update_threshold = 10000;
+
+ GstBaseParseClass *klass;
+ guint64 data_len, frame_dur;
+ gint overhead = 0, frame_bitrate, old_avg_bitrate = parse->priv->avg_bitrate;
+ gboolean update_min = FALSE, update_avg = FALSE, update_max = FALSE;
+
+ klass = GST_BASE_PARSE_GET_CLASS (parse);
+
+ if (klass->get_frame_overhead) {
+ overhead = klass->get_frame_overhead (parse, buffer);
+ if (overhead == -1)
+ return;
+ }
+
+ data_len = GST_BUFFER_SIZE (buffer) - overhead;
+ parse->priv->data_bytecount += data_len;
+
+ if (parse->priv->fps_num) {
+ /* Calculate duration of a frame from frame properties */
+ frame_dur = (GST_SECOND * parse->priv->fps_den) / parse->priv->fps_num;
+ parse->priv->avg_bitrate = (8 * parse->priv->data_bytecount * GST_SECOND) /
+ (parse->priv->framecount * frame_dur);
+
+ } else if (GST_BUFFER_DURATION_IS_VALID (buffer)) {
+ /* Calculate duration of a frame from buffer properties */
+ frame_dur = GST_BUFFER_DURATION (buffer);
+ parse->priv->avg_bitrate = (8 * parse->priv->data_bytecount * GST_SECOND) /
+ parse->priv->acc_duration;
+
+ } else {
+ /* No way to figure out frame duration (is this even possible?) */
+ return;
+ }
+
+ frame_bitrate = (8 * data_len * GST_SECOND) / frame_dur;
+
+ if (frame_bitrate < parse->priv->min_bitrate) {
+ parse->priv->min_bitrate = frame_bitrate;
+ update_min = TRUE;
+ }
+
+ if (frame_bitrate > parse->priv->max_bitrate) {
+ parse->priv->max_bitrate = frame_bitrate;
+ update_max = TRUE;
+ }
+
+ if (old_avg_bitrate / update_threshold !=
+ parse->priv->avg_bitrate / update_threshold)
+ update_avg = TRUE;
+
+ if (parse->priv->framecount >= MIN_FRAMES_TO_POST_BITRATE &&
+ (update_min || update_avg || update_max))
+ gst_base_parse_post_bitrates (parse, update_min, update_avg, update_max);
+}
+
/**
* gst_base_parse_handle_and_push_buffer:
* @parse: #GstBaseParse.
gst_base_parse_push_buffer (GstBaseParse * parse, GstBuffer * buffer)
{
GstFlowReturn ret = GST_FLOW_OK;
+ GstClockTime last_start = GST_CLOCK_TIME_NONE;
GstClockTime last_stop = GST_CLOCK_TIME_NONE;
GST_LOG_OBJECT (parse,
(parse->priv->framecount % parse->priv->update_interval) == 0)
gst_base_parse_update_duration (parse);
+ gst_base_parse_update_bitrates (parse, buffer);
+
if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
- last_stop = GST_BUFFER_TIMESTAMP (buffer);
- if (last_stop != GST_CLOCK_TIME_NONE && GST_BUFFER_DURATION_IS_VALID (buffer))
- last_stop += GST_BUFFER_DURATION (buffer);
+ last_start = last_stop = GST_BUFFER_TIMESTAMP (buffer);
+ if (last_start != GST_CLOCK_TIME_NONE
+ && GST_BUFFER_DURATION_IS_VALID (buffer))
+ last_stop = last_start + GST_BUFFER_DURATION (buffer);
/* should have caps by now */
g_return_val_if_fail (GST_PAD_CAPS (parse->srcpad), GST_FLOW_ERROR);
gst_buffer_set_caps (buffer, GST_PAD_CAPS (parse->srcpad));
+ /* segment times are typically estimates,
+ * actual frame data might lead subclass to different timestamps,
+ * so override segment start from what is supplied there */
+ if (G_UNLIKELY (parse->pending_segment && !parse->priv->passthrough &&
+ GST_CLOCK_TIME_IS_VALID (last_start))) {
+ gst_event_unref (parse->pending_segment);
+ /* stop time possibly lost this way,
+ * but unlikely and not really supported */
+ parse->pending_segment =
+ gst_event_new_new_segment (FALSE, parse->segment.rate,
+ parse->segment.format, last_start, -1, last_start);
+ }
+
/* and should then also be linked downstream, so safe to send some events */
if (parse->priv->pad_mode == GST_ACTIVATE_PULL) {
if (G_UNLIKELY (parse->close_segment)) {
/* Caching here actually makes much less difference than one would expect.
* We do it mainly to avoid pulling buffers of 1 byte all the time */
if (parse->priv->cache) {
- guint64 cache_offset = GST_BUFFER_OFFSET (parse->priv->cache);
- guint cache_size = GST_BUFFER_SIZE (parse->priv->cache);
+ gint64 cache_offset = GST_BUFFER_OFFSET (parse->priv->cache);
+ gint cache_size = GST_BUFFER_SIZE (parse->priv->cache);
if (cache_offset <= parse->priv->offset &&
(parse->priv->offset + size) <= (cache_offset + cache_size)) {
if (ret != GST_FLOW_OK) {
GST_DEBUG_OBJECT (parse, "flow: %s", gst_flow_get_name (ret));
- if (GST_FLOW_IS_FATAL (ret)) {
+ if (ret == GST_FLOW_UNEXPECTED) {
+ gst_pad_push_event (parse->srcpad, gst_event_new_eos ());
+ } else if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_UNEXPECTED) {
GST_ELEMENT_ERROR (parse, STREAM, FAILED, (NULL),
("streaming task paused, reason: %s", gst_flow_get_name (ret)));
gst_pad_push_event (parse->srcpad, gst_event_new_eos ());
parse->priv->estimated_duration = -1;
parse->priv->next_ts = 0;
parse->priv->passthrough = FALSE;
+ parse->priv->post_min_bitrate = TRUE;
+ parse->priv->post_avg_bitrate = TRUE;
+ parse->priv->post_max_bitrate = TRUE;
+ parse->priv->min_bitrate = G_MAXUINT;
+ parse->priv->max_bitrate = 0;
+ parse->priv->max_bitrate = 0;
if (parse->pending_segment)
gst_event_unref (parse->pending_segment);
* gst_base_transform_get_sync:
* @parse: the #GstBaseParse to query
*
- * Returns TRUE if parser is considered 'in sync'. That is, frames have been
+ * Returns: TRUE if parser is considered 'in sync'. That is, frames have been
* continuously successfully parsed and pushed.
*/
gboolean
* gst_base_transform_get_drain:
* @parse: the #GstBaseParse to query
*
- * Returns TRUE if parser is currently 'draining'. That is, leftover data
+ * Returns: TRUE if parser is currently 'draining'. That is, leftover data
* (e.g. in FLUSH or EOS situation) is being parsed.
*/
gboolean
gst_event_parse_seek (event, &rate, &format, &flags,
&cur_type, &cur, &stop_type, &stop);
+ GST_DEBUG_OBJECT (parse, "seek to format %s, "
+ "start type %d at %" GST_TIME_FORMAT ", end type %d at %"
+ GST_TIME_FORMAT, gst_format_get_name (format),
+ cur_type, GST_TIME_ARGS (cur), stop_type, GST_TIME_ARGS (stop));
+
/* no negative rates yet */
if (rate < 0.0)
goto negative_rate;
* it directly or fail. For TIME, try upstream, but do it ourselves if
* it fails upstream */
if (format != GST_FORMAT_TIME) {
- return gst_pad_push_event (parse->sinkpad, event);
+ /* default action delegates to upstream */
+ return FALSE;
} else {
gst_event_ref (event);
if (gst_pad_push_event (parse->sinkpad, event)) {
- gst_event_unref (event);
return TRUE;
}
}
+ /* too much estimating going on to support this sensibly,
+ * and no eos/end-of-segment loop handling either ... */
+ if ((stop_type == GST_SEEK_TYPE_SET && stop != GST_CLOCK_TIME_NONE) ||
+ (stop_type != GST_SEEK_TYPE_NONE && stop_type != GST_SEEK_TYPE_SET) ||
+ (flags & GST_SEEK_FLAG_SEGMENT))
+ goto wrong_type;
+ stop = -1;
+
/* get flush flag */
flush = flags & GST_SEEK_FLAG_FLUSH;
+ /* copy segment, we need this because we still need the old
+ * segment when we close the current segment. */
+ memcpy (&seeksegment, &parse->segment, sizeof (GstSegment));
+
+ GST_DEBUG_OBJECT (parse, "configuring seek");
+ gst_segment_set_seek (&seeksegment, rate, format, flags,
+ cur_type, cur, stop_type, stop, &update);
+
+ /* figure out the last position we need to play. If it's configured (stop !=
+ * -1), use that, else we play until the total duration of the file */
+ if ((stop = seeksegment.stop) == -1)
+ stop = seeksegment.duration;
+
dstformat = GST_FORMAT_BYTES;
- if (!gst_pad_query_convert (parse->srcpad, format, cur, &dstformat, &seekpos)) {
+ if (!gst_pad_query_convert (parse->srcpad, format, seeksegment.last_stop,
+ &dstformat, &seekpos)) {
GST_DEBUG_OBJECT (parse, "conversion failed");
return FALSE;
}
GST_DEBUG_OBJECT (parse, "stopped streaming at %" G_GINT64_FORMAT,
last_stop);
- /* copy segment, we need this because we still need the old
- * segment when we close the current segment. */
- memcpy (&seeksegment, &parse->segment, sizeof (GstSegment));
-
- GST_DEBUG_OBJECT (parse, "configuring seek");
- gst_segment_set_seek (&seeksegment, rate, format, flags,
- cur_type, cur, stop_type, stop, &update);
-
- /* figure out the last position we need to play. If it's configured (stop !=
- * -1), use that, else we play until the total duration of the file */
- if ((stop = seeksegment.stop) == -1)
- stop = seeksegment.duration;
-
+ /* now commit to new position */
parse->priv->offset = seekpos;
/* prepare for streaming again */
}
}
+/**
+ * gst_base_parse_handle_tag:
+ * @parse: #GstBaseParse.
+ * @event: #GstEvent.
+ *
+ * Checks if bitrates are available from upstream tags so that we don't
+ * override them later
+ */
+static void
+gst_base_parse_handle_tag (GstBaseParse * parse, GstEvent * event)
+{
+ GstTagList *taglist = NULL;
+ guint tmp;
+
+ gst_event_parse_tag (event, &taglist);
+
+ if (gst_tag_list_get_uint (taglist, GST_TAG_MINIMUM_BITRATE, &tmp))
+ parse->priv->post_min_bitrate = FALSE;
+ if (gst_tag_list_get_uint (taglist, GST_TAG_BITRATE, &tmp))
+ parse->priv->post_avg_bitrate = FALSE;
+ if (gst_tag_list_get_uint (taglist, GST_TAG_MAXIMUM_BITRATE, &tmp))
+ parse->priv->post_max_bitrate = FALSE;
+}
/**
* gst_base_parse_sink_setcaps: