/* GStreamer * Copyright (C) 2005 Wim Taymans * * gstsegment.c: GstSegment subsystem * * 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 "gst_private.h" #include "gstutils.h" #include "gstsegment.h" /** * SECTION:gstsegment * @short_description: Structure describing the configured region of interest * in a media file. * @see_also: #GstEvent * * This helper structure holds the relevant values for tracking the region of * interest in a media file, called a segment. * * The structure can be used for two purposes: * * performing seeks (handling seek events) * tracking playback regions (handling newsegment events) * * * The segment is usually configured by the application with a seek event which * is propagated upstream and eventually handled by an element that performs the seek. * * The configured segment is then propagated back downstream with a newsegment event. * This information is then used to clip media to the segment boundaries. * * A segment structure is initialized with gst_segment_init(), which takes a #GstFormat * that will be used as the format of the segment values. The segment will be configured * with a start value of 0 and a stop/duration of -1, which is undefined. The default * rate is 1.0. * * If the segment is used for managing seeks, the segment duration should be set with * gst_segment_set_duration(). The public duration field contains the duration of the * segment. * * The current position in the segment should be set with the gst_segment_set_last_stop(). * The public last_stop field contains the last set stop position in the segment. * * For elements that perform seeks, the current segment should be updated with the * gst_segment_set_seek() and the values from the seek event. This method will update * all the segment fields. The last_pos field will contain the new playback position. * If the cur_type was different from GST_SEEK_TYPE_NONE, playback continues from * the last_pos position, possibly with updated flags or rate. * * For elements that want to use #GstSegment to track the playback region, use * gst_segment_set_newsegment() to update the segment fields with the information from * the newsegment event. The gst_segment_clip() method can be used to check and clip * the media data to the segment boundaries. * * For elements that want to synchronize to the pipeline clock, gst_segment_to_running_time() * can be used to convert a timestamp to a value that can be used to synchronize * to the clock. This function takes into account all accumulated segments. * * For elements that need to perform operations on media data in stream_time, * gst_segment_to_stream_time() can be used to convert a timestamp and the segment * info to stream time (which is always between 0 and the duration of the stream). * * Last reviewed on 2006-03-12 (0.10.5) */ static GstSegment * gst_segment_copy (GstSegment * segment) { GstSegment *result = NULL; if (segment) { result = gst_segment_new (); memcpy (result, segment, sizeof (GstSegment)); } return NULL; } GType gst_segment_get_type (void) { static GType gst_segment_type = 0; if (G_UNLIKELY (gst_segment_type == 0)) { gst_segment_type = g_boxed_type_register_static ("GstSegment", (GBoxedCopyFunc) gst_segment_copy, (GBoxedFreeFunc) gst_segment_free); } return gst_segment_type; } /** * gst_segment_new: * * Allocate a new #GstSegment structure and initialize it using * gst_segment_init(). * * Returns: a new #GstSegment, free with gst_segment_free(). */ GstSegment * gst_segment_new (void) { GstSegment *result; result = g_new0 (GstSegment, 1); gst_segment_init (result, GST_FORMAT_UNDEFINED); return result; } /** * gst_segment_free: * @segment: a #GstSegment * * Free the allocated segment @segment. */ void gst_segment_free (GstSegment * segment) { g_free (segment); } /** * gst_segment_init: * @segment: a #GstSegment structure. * @format: the format of the segment. * * The start/last_stop positions are set to 0 and the stop/duration * fields are set to -1 (unknown). The default rate of 1.0 and no * flags are set. * * Initialize @segment to its default values. */ void gst_segment_init (GstSegment * segment, GstFormat format) { g_return_if_fail (segment != NULL); segment->rate = 1.0; segment->abs_rate = 1.0; segment->format = format; segment->flags = 0; segment->start = 0; segment->stop = -1; segment->time = 0; segment->accum = 0; segment->last_stop = 0; segment->duration = -1; } /** * gst_segment_set_duration: * @segment: a #GstSegment structure. * @format: the format of the segment. * @duration: the duration of the segment info. * * Set the duration of the segment to @duration. This function is mainly * used by elements that perform seeking and know the total duration of the * segment. * * This field should be set to allow seeking requests relative to the * duration. */ void gst_segment_set_duration (GstSegment * segment, GstFormat format, gint64 duration) { g_return_if_fail (segment != NULL); if (segment->format == GST_FORMAT_UNDEFINED) segment->format = format; else g_return_if_fail (segment->format == format); segment->duration = duration; } /** * gst_segment_set_last_stop: * @segment: a #GstSegment structure. * @format: the format of the segment. * @position: the position * * Set the last observed stop position in the segment to @position. * * This field should be set to allow seeking requests relative to the * current playing position. */ void gst_segment_set_last_stop (GstSegment * segment, GstFormat format, gint64 position) { g_return_if_fail (segment != NULL); if (segment->format == GST_FORMAT_UNDEFINED) segment->format = format; else g_return_if_fail (segment->format == format); segment->last_stop = MAX (segment->start, position); } /** * gst_segment_set_seek: * @segment: a #GstSegment structure. * @rate: the rate of the segment. * @format: the format of the segment. * @flags: the seek flags for the segment * @cur_type: the seek method * @cur: the seek start value * @stop_type: the seek method * @stop: the seek stop value * @update: boolean holding whether start or stop were updated. * * Update the segment structure with the field values of a seek event. * * After calling this method, the segment field last_stop will contain * the requested new position in the segment. If the cur_type is different * from GST_SEEK_TYPE_NONE, the current position is not updated and * streaming should continue from the last position, possibly with * updated rate, flags or stop position. */ void gst_segment_set_seek (GstSegment * segment, gdouble rate, GstFormat format, GstSeekFlags flags, GstSeekType cur_type, gint64 cur, GstSeekType stop_type, gint64 stop, gboolean * update) { gboolean update_stop, update_start; g_return_if_fail (rate != 0.0); g_return_if_fail (segment != NULL); if (segment->format == GST_FORMAT_UNDEFINED) segment->format = format; else g_return_if_fail (segment->format == format); update_stop = update_start = TRUE; /* start is never invalid */ switch (cur_type) { case GST_SEEK_TYPE_NONE: /* no update to segment */ cur = segment->start; update_start = FALSE; break; case GST_SEEK_TYPE_SET: /* cur holds desired position */ break; case GST_SEEK_TYPE_CUR: /* add cur to currently configure segment */ cur = segment->start + cur; break; case GST_SEEK_TYPE_END: if (segment->duration != -1) { /* add cur to total length */ cur = segment->duration + cur; } else { /* no update if duration unknown */ cur = segment->start; update_start = FALSE; } break; } /* bring in sane range */ if (segment->duration != -1) cur = CLAMP (cur, 0, segment->duration); else cur = MAX (cur, 0); /* stop can be -1 if we have not configured a stop. */ switch (stop_type) { case GST_SEEK_TYPE_NONE: stop = segment->stop; update_stop = FALSE; break; case GST_SEEK_TYPE_SET: /* stop folds required value */ break; case GST_SEEK_TYPE_CUR: if (segment->stop != -1) stop = segment->stop + stop; else stop = -1; break; case GST_SEEK_TYPE_END: if (segment->duration != -1) stop = segment->duration + stop; else { stop = segment->stop; update_stop = FALSE; } break; } /* if we have a valid stop time, make sure it is clipped */ if (stop != -1) { if (segment->duration != -1) stop = CLAMP (stop, 0, segment->duration); else stop = MAX (stop, 0); } /* we can't have stop before start */ if (stop != -1) g_return_if_fail (cur <= stop); segment->rate = rate; segment->abs_rate = ABS (rate); segment->flags = flags; segment->start = cur; if (update_start) { segment->last_stop = cur; } segment->time = segment->last_stop; segment->stop = stop; if (update) *update = update_start || update_stop; } /** * gst_segment_set_newsegment: * @segment: a #GstSegment structure. * @update: flag indicating a new segment is started or updated * @rate: the rate of the segment. * @format: the format of the segment. * @start: the new start value * @stop: the new stop value * @time: the new stream time * * Update the segment structure with the field values of a new segment event. */ void gst_segment_set_newsegment (GstSegment * segment, gboolean update, gdouble rate, GstFormat format, gint64 start, gint64 stop, gint64 time) { gint64 duration; g_return_if_fail (rate != 0.0); g_return_if_fail (segment != NULL); if (segment->format == GST_FORMAT_UNDEFINED) segment->format = format; /* any other format with 0 also gives time 0, the other values are * invalid in the format though. */ if (format != segment->format && start == 0) { format = segment->format; if (stop != 0) stop = -1; if (time != 0) time = -1; } g_return_if_fail (segment->format == format); if (update) { /* an update to the current segment is done, elapsed time is * difference between the old start and new start. */ duration = start - segment->start; } else { /* the new segment has to be aligned with the old segment. * We first update the accumulated time of the previous * segment. the accumulated time is used when syncing to the * clock. */ if (GST_CLOCK_TIME_IS_VALID (segment->stop)) { duration = segment->stop - segment->start; } else if (GST_CLOCK_TIME_IS_VALID (segment->last_stop)) { /* else use last seen timestamp as segment stop */ duration = segment->last_stop - segment->start; } else { /* else we don't know */ duration = 0; } } /* use previous rate to calculate duration */ segment->accum += gst_gdouble_to_guint64 ( (gst_guint64_to_gdouble (duration) / segment->abs_rate)); /* then update the current segment */ segment->rate = rate; segment->abs_rate = ABS (rate); segment->start = start; segment->last_stop = start; segment->stop = stop; segment->time = time; } /** * gst_segment_to_stream_time: * @segment: a #GstSegment structure. * @format: the format of the segment. * @position: the position in the segment * * Translate @position to stream time using the currently configured * segment. * * This function is typically used by elements that need to operate on * the stream time of the buffers it receives, such as effect plugins. * The stream time is always between 0 and the total duration of the * media stream. * * Returns: the position in stream_time. */ gint64 gst_segment_to_stream_time (GstSegment * segment, GstFormat format, gint64 position) { gint64 result, time; g_return_val_if_fail (segment != NULL, -1); if (segment->format == GST_FORMAT_UNDEFINED) segment->format = format; else g_return_val_if_fail (segment->format == format, -1); if ((time = segment->time) == -1) time = 0; if (position != -1 && position >= segment->start) result = ((position - segment->start) / segment->abs_rate) + time; else result = -1; return result; } /** * gst_segment_to_running_time: * @segment: a #GstSegment structure. * @format: the format of the segment. * @position: the position in the segment * * Translate @position to the total running time using the currently configured * and previously accumulated segments. * * This function is typically used by elements that need to synchronize to the * global clock in a pipeline. The runnning time is a constantly increasing value * starting from 0. When gst_segment_init() is called, this value will reset to * 0. * * Returns: the position as the total running time. */ gint64 gst_segment_to_running_time (GstSegment * segment, GstFormat format, gint64 position) { gint64 result; g_return_val_if_fail (segment != NULL, -1); if (segment->format == GST_FORMAT_UNDEFINED) segment->format = format; else if (segment->accum) g_return_val_if_fail (segment->format == format, -1); if (position != -1 && position >= segment->start) result = ((position - segment->start) / segment->abs_rate) + segment->accum; else result = -1; return result; } /** * gst_segment_clip: * @segment: a #GstSegment structure. * @format: the format of the segment. * @start: the start position in the segment * @stop: the stop position in the segment * @clip_start: the clipped start position in the segment * @clip_stop: the clipped stop position in the segment * * Clip the given @start and @stop values to the segment boundaries given * in @segment. * * If the function returns FALSE, @start and @stop are known to fall * outside of @segment and @clip_start and @clip_stop are not updated. * * When the function returns TRUE, @clip_start and @clip_stop will be * updated. If @clip_start or @clip_stop are different from @start or @stop * respectively, the region fell partially in the segment. * * Returns: TRUE if the given @start and @stop times fall partially or * completely in @segment, FALSE if the values are completely outside * of the segment. */ gboolean gst_segment_clip (GstSegment * segment, GstFormat format, gint64 start, gint64 stop, gint64 * clip_start, gint64 * clip_stop) { g_return_val_if_fail (segment != NULL, FALSE); if (segment->format == GST_FORMAT_UNDEFINED) segment->format = format; else g_return_val_if_fail (segment->format == format, FALSE); /* if we have a stop position and a valid start and start is bigger, * we're outside of the segment */ if (segment->stop != -1 && start != -1 && start >= segment->stop) return FALSE; /* if a stop position is given and is before the segment start, * we're outside of the segment */ if (stop != -1 && stop <= segment->start) return FALSE; if (clip_start) { if (start == -1) *clip_start = -1; else *clip_start = MAX (start, segment->start); } if (clip_stop) { if (stop == -1) *clip_stop = segment->stop; else if (segment->stop == -1) *clip_stop = MAX (-1, stop); else *clip_stop = MIN (stop, segment->stop); if (segment->duration != -1) *clip_stop = MIN (*clip_stop, segment->duration); } return TRUE; }