/* set to 0.5 seconds by default */
#define DEFAULT_MAX_DELAY G_GINT64_CONSTANT(500000000)
#define DEFAULT_MAX_PAGE_DELAY G_GINT64_CONSTANT(500000000)
+#define DEFAULT_MAX_TOLERANCE G_GINT64_CONSTANT(40000000)
enum
{
ARG_0,
ARG_MAX_DELAY,
ARG_MAX_PAGE_DELAY,
+ ARG_MAX_TOLERANCE
};
static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
"Maximum delay for sending out a page", 0, G_MAXUINT64,
DEFAULT_MAX_PAGE_DELAY,
(GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, ARG_MAX_TOLERANCE,
+ g_param_spec_uint64 ("max-tolerance", "Max time tolerance",
+ "Maximum timestamp difference for maintaining perfect granules",
+ 0, G_MAXUINT64, DEFAULT_MAX_TOLERANCE,
+ (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
gstelement_class->change_state = gst_ogg_mux_change_state;
ogg_mux->max_delay = DEFAULT_MAX_DELAY;
ogg_mux->max_page_delay = DEFAULT_MAX_PAGE_DELAY;
+ ogg_mux->max_tolerance = DEFAULT_MAX_TOLERANCE;
gst_ogg_mux_clear (ogg_mux);
}
oggpad->pagebuffers = g_queue_new ();
oggpad->map.headers = NULL;
oggpad->map.queued = NULL;
+ oggpad->next_granule = 0;
+ oggpad->keyframe_granule = -1;
gst_segment_init (&oggpad->segment, GST_FORMAT_TIME);
GstBuffer * buf)
{
GstClockTime time;
+ gint64 duration, granule, limit;
+ GstClockTime next_time;
+ GstClockTimeDiff diff;
+ ogg_packet packet;
+
+ /* ensure messing with metadata is ok */
+ buf = gst_buffer_make_metadata_writable (buf);
/* convert time to running time, so we need no longer bother about that */
time = GST_BUFFER_TIMESTAMP (buf);
gst_buffer_unref (buf);
return NULL;
} else {
- buf = gst_buffer_make_metadata_writable (buf);
GST_BUFFER_TIMESTAMP (buf) = time;
}
}
+ /* now come up with granulepos stuff corresponding to time */
+ if (!pad->have_type ||
+ pad->map.granulerate_n <= 0 || pad->map.granulerate_d <= 0)
+ goto no_granule;
+
+ packet.packet = GST_BUFFER_DATA (buf);
+ packet.bytes = GST_BUFFER_SIZE (buf);
+ duration = gst_ogg_stream_get_packet_duration (&pad->map, &packet);
+
+ /* give up if no duration can be determined, relying on upstream */
+ if (G_UNLIKELY (duration < 0)) {
+ /* well, if some day we really could handle sparse input ... */
+ if (pad->map.is_sparse) {
+ limit = 1;
+ diff = 2;
+ goto resync;
+ }
+ GST_WARNING_OBJECT (pad->collect.pad,
+ "failed to determine packet duration");
+ goto no_granule;
+ }
+
+ GST_LOG_OBJECT (pad->collect.pad, "buffer ts %" GST_TIME_FORMAT
+ ", duration %" GST_TIME_FORMAT ", granule duration %" G_GINT64_FORMAT,
+ GST_TIME_ARGS (time), GST_TIME_ARGS (GST_BUFFER_DURATION (buf)),
+ duration);
+
+ /* determine granule corresponding to time,
+ * using the inverse of oggdemux' granule -> time */
+
+ /* see if interpolated granule matches good enough */
+ granule = pad->next_granule;
+ next_time = gst_ogg_stream_granule_to_time (&pad->map, pad->next_granule);
+ diff = GST_CLOCK_DIFF (next_time, time);
+
+ /* we tolerate deviation up to configured or within granule granularity */
+ limit = gst_ogg_stream_granule_to_time (&pad->map, 1) / 2;
+ limit = MAX (limit, ogg_mux->max_tolerance);
+
+ GST_LOG_OBJECT (pad->collect.pad, "expected granule %" G_GINT64_FORMAT " == "
+ "time %" GST_TIME_FORMAT " --> ts diff %" GST_TIME_FORMAT
+ " < tolerance %" GST_TIME_FORMAT " (?)",
+ granule, GST_TIME_ARGS (next_time), GST_TIME_ARGS (ABS (diff)),
+ GST_TIME_ARGS (limit));
+
+resync:
+ /* if not good enough, determine granule based on time */
+ if (diff > limit || diff < -limit) {
+ granule = gst_util_uint64_scale_round (time, pad->map.granulerate_n,
+ GST_SECOND * pad->map.granulerate_d);
+ GST_DEBUG_OBJECT (pad->collect.pad,
+ "resyncing to determined granule %" G_GINT64_FORMAT, granule);
+ }
+
+ if (pad->map.is_ogm || pad->map.is_sparse) {
+ pad->next_granule = granule;
+ } else {
+ granule += duration;
+ pad->next_granule = granule;
+ }
+
+ /* track previous keyframe */
+ if (!GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT))
+ pad->keyframe_granule = granule;
+
+ /* determine corresponding time and granulepos */
+ GST_BUFFER_OFFSET (buf) = gst_ogg_stream_granule_to_time (&pad->map, granule);
+ GST_BUFFER_OFFSET_END (buf) =
+ gst_ogg_stream_granule_to_granulepos (&pad->map, granule,
+ pad->keyframe_granule);
+
return buf;
+
+ /* ERRORS */
+no_granule:
+ {
+ GST_DEBUG_OBJECT (pad->collect.pad, "could not determine granulepos, "
+ "falling back to upstream provided metadata");
+ return buf;
+ }
}
"got data buffer in control state, switching to data mode");
/* this is a data buffer so switch to data state */
pad->state = GST_OGG_PAD_STATE_DATA;
+
+ /* check if this type of stream allows generating granulepos
+ * metadata here, if not, upstream will have to provide */
+ if (gst_ogg_stream_granule_to_granulepos (&pad->map, 1, 1) < 0) {
+ GST_WARNING_OBJECT (data->pad, "can not generate metadata; "
+ "relying on upstream");
+ /* disable metadata code path, otherwise not used anyway */
+ pad->map.granulerate_n = 0;
+ }
}
}
case ARG_MAX_PAGE_DELAY:
g_value_set_uint64 (value, ogg_mux->max_page_delay);
break;
+ case ARG_MAX_TOLERANCE:
+ g_value_set_uint64 (value, ogg_mux->max_tolerance);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
case ARG_MAX_PAGE_DELAY:
ogg_mux->max_page_delay = g_value_get_uint64 (value);
break;
+ case ARG_MAX_TOLERANCE:
+ ogg_mux->max_tolerance = g_value_get_uint64 (value);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;