2 * Copyright (C) 2005 Wim Taymans <wim@fluendo.com>
4 * gstsegment.c: GstSegment subsystem
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
25 #include "gst_private.h"
28 #include "gstsegment.h"
32 * @short_description: Structure describing the configured region of interest
34 * @see_also: #GstEvent
36 * This helper structure holds the relevant values for tracking the region of
37 * interest in a media file, called a segment.
39 * The structure can be used for two purposes:
41 * <listitem><para>performing seeks (handling seek events)</para></listitem>
42 * <listitem><para>tracking playback regions (handling newsegment events)</para></listitem>
45 * The segment is usually configured by the application with a seek event which
46 * is propagated upstream and eventually handled by an element that performs the seek.
48 * The configured segment is then propagated back downstream with a newsegment event.
49 * This information is then used to clip media to the segment boundaries.
51 * A segment structure is initialized with gst_segment_init(), which takes a #GstFormat
52 * that will be used as the format of the segment values. The segment will be configured
53 * with a start value of 0 and a stop/duration of -1, which is undefined. The default
54 * rate and applied_rate is 1.0.
56 * If the segment is used for managing seeks, the segment duration should be set with
57 * gst_segment_set_duration(). The public duration field contains the duration of the
58 * segment. When using the segment for seeking, the start and time members should
59 * normally be left to their default 0 value. The stop position is left to -1 unless
60 * explicitly configured to a different value after a seek event.
62 * The current position in the segment should be set with the gst_segment_set_last_stop().
63 * The public last_stop field contains the last set stop position in the segment.
65 * For elements that perform seeks, the current segment should be updated with the
66 * gst_segment_set_seek() and the values from the seek event. This method will update
67 * all the segment fields. The last_stop field will contain the new playback position.
68 * If the cur_type was different from GST_SEEK_TYPE_NONE, playback continues from
69 * the last_stop position, possibly with updated flags or rate.
71 * For elements that want to use #GstSegment to track the playback region, use
72 * gst_segment_set_newsegment() to update the segment fields with the information from
73 * the newsegment event. The gst_segment_clip() method can be used to check and clip
74 * the media data to the segment boundaries.
76 * For elements that want to synchronize to the pipeline clock, gst_segment_to_running_time()
77 * can be used to convert a timestamp to a value that can be used to synchronize
78 * to the clock. This function takes into account all accumulated segments as well as
79 * any rate or applied_rate conversions.
81 * For elements that need to perform operations on media data in stream_time,
82 * gst_segment_to_stream_time() can be used to convert a timestamp and the segment
83 * info to stream time (which is always between 0 and the duration of the stream).
85 * Last reviewed on 2007-05-17 (0.10.13)
90 * @segment: a #GstSegment
92 * Create a copy of given @segment.
94 * Returns: a new #GstSegment, free with gst_segment_free().
99 gst_segment_copy (GstSegment * segment)
101 GstSegment *result = NULL;
104 result = (GstSegment *) g_slice_copy (sizeof (GstSegment), segment);
110 gst_segment_get_type (void)
112 static GType gst_segment_type = 0;
114 if (G_UNLIKELY (gst_segment_type == 0)) {
115 gst_segment_type = g_boxed_type_register_static ("GstSegment",
116 (GBoxedCopyFunc) gst_segment_copy, (GBoxedFreeFunc) gst_segment_free);
119 return gst_segment_type;
125 * Allocate a new #GstSegment structure and initialize it using
126 * gst_segment_init().
128 * Returns: a new #GstSegment, free with gst_segment_free().
131 gst_segment_new (void)
135 result = g_slice_new0 (GstSegment);
136 gst_segment_init (result, GST_FORMAT_UNDEFINED);
143 * @segment: a #GstSegment
145 * Free the allocated segment @segment.
148 gst_segment_free (GstSegment * segment)
150 g_slice_free (GstSegment, segment);
155 * @segment: a #GstSegment structure.
156 * @format: the format of the segment.
158 * The start/last_stop positions are set to 0 and the stop/duration
159 * fields are set to -1 (unknown). The default rate of 1.0 and no
162 * Initialize @segment to its default values.
165 gst_segment_init (GstSegment * segment, GstFormat format)
167 g_return_if_fail (segment != NULL);
170 segment->abs_rate = 1.0;
171 segment->applied_rate = 1.0;
172 segment->format = format;
178 segment->last_stop = 0;
179 segment->duration = -1;
183 * gst_segment_set_duration:
184 * @segment: a #GstSegment structure.
185 * @format: the format of the segment.
186 * @duration: the duration of the segment info or -1 if unknown.
188 * Set the duration of the segment to @duration. This function is mainly
189 * used by elements that perform seeking and know the total duration of the
192 * This field should be set to allow seeking requests relative to the
196 gst_segment_set_duration (GstSegment * segment, GstFormat format,
199 g_return_if_fail (segment != NULL);
201 if (G_UNLIKELY (segment->format == GST_FORMAT_UNDEFINED))
202 segment->format = format;
204 g_return_if_fail (segment->format == format);
206 segment->duration = duration;
210 * gst_segment_set_last_stop:
211 * @segment: a #GstSegment structure.
212 * @format: the format of the segment.
213 * @position: the position
215 * Set the last observed stop position in the segment to @position.
217 * This field should be set to allow seeking requests relative to the
218 * current playing position.
221 gst_segment_set_last_stop (GstSegment * segment, GstFormat format,
224 g_return_if_fail (segment != NULL);
226 if (G_UNLIKELY (segment->format == GST_FORMAT_UNDEFINED))
227 segment->format = format;
229 g_return_if_fail (segment->format == format);
231 segment->last_stop = MAX (segment->start, position);
235 * gst_segment_set_seek:
236 * @segment: a #GstSegment structure.
237 * @rate: the rate of the segment.
238 * @format: the format of the segment.
239 * @flags: the seek flags for the segment
240 * @start_type: the seek method
241 * @start: the seek start value
242 * @stop_type: the seek method
243 * @stop: the seek stop value
244 * @update: boolean holding whether last_stop was updated.
246 * Update the segment structure with the field values of a seek event (see
247 * gst_event_new_seek()).
249 * After calling this method, the segment field last_stop and time will
250 * contain the requested new position in the segment. The new requested
251 * position in the segment depends on @rate and @start_type and @stop_type.
253 * For positive @rate, the new position in the segment is the new @segment
254 * start field when it was updated with a @start_type different from
255 * #GST_SEEK_TYPE_NONE. If no update was performed on @segment start position
256 * (#GST_SEEK_TYPE_NONE), @start is ignored and @segment last_stop is
259 * For negative @rate, the new position in the segment is the new @segment
260 * stop field when it was updated with a @stop_type different from
261 * #GST_SEEK_TYPE_NONE. If no stop was previously configured in the segment, the
262 * duration of the segment will be used to update the stop position.
263 * If no update was performed on @segment stop position (#GST_SEEK_TYPE_NONE),
264 * @stop is ignored and @segment last_stop is unmodified.
266 * The applied rate of the segment will be set to 1.0 by default.
267 * If the caller can apply a rate change, it should update @segment
268 * rate and applied_rate after calling this function.
270 * @update will be set to TRUE if a seek should be performed to the segment
271 * last_stop field. This field can be FALSE if, for example, only the @rate
272 * has been changed but not the playback position.
275 gst_segment_set_seek (GstSegment * segment, gdouble rate,
276 GstFormat format, GstSeekFlags flags,
277 GstSeekType start_type, gint64 start,
278 GstSeekType stop_type, gint64 stop, gboolean * update)
280 gboolean update_stop, update_start;
283 g_return_if_fail (rate != 0.0);
284 g_return_if_fail (segment != NULL);
286 if (G_UNLIKELY (segment->format == GST_FORMAT_UNDEFINED))
287 segment->format = format;
289 update_start = update_stop = TRUE;
291 /* segment->start is never invalid */
292 switch (start_type) {
293 case GST_SEEK_TYPE_NONE:
294 /* no update to segment, take previous start */
295 start = segment->start;
296 update_start = FALSE;
298 case GST_SEEK_TYPE_SET:
299 /* start holds desired position, map -1 to the start */
302 /* start must be 0 or the formats must match */
303 g_return_if_fail (start == 0 || segment->format == format);
305 case GST_SEEK_TYPE_CUR:
306 g_return_if_fail (start == 0 || segment->format == format);
307 /* add start to currently configured segment */
308 start = segment->start + start;
310 case GST_SEEK_TYPE_END:
311 if (segment->duration != -1) {
312 g_return_if_fail (start == 0 || segment->format == format);
313 /* add start to total length */
314 start = segment->duration + start;
316 /* no update if duration unknown */
317 start = segment->start;
318 update_start = FALSE;
322 /* bring in sane range */
323 if (segment->duration != -1)
324 start = CLAMP (start, 0, segment->duration);
326 start = MAX (start, 0);
328 /* stop can be -1 if we have not configured a stop. */
330 case GST_SEEK_TYPE_NONE:
331 stop = segment->stop;
334 case GST_SEEK_TYPE_SET:
335 /* stop holds required value, if it's not -1, it must be of the same
336 * format as the segment. */
337 g_return_if_fail (stop == -1 || segment->format == format);
339 case GST_SEEK_TYPE_CUR:
340 if (segment->stop != -1) {
341 /* only add compatible formats or 0 */
342 g_return_if_fail (stop == 0 || segment->format == format);
343 stop = segment->stop + stop;
347 case GST_SEEK_TYPE_END:
348 if (segment->duration != -1) {
349 /* only add compatible formats or 0 */
350 g_return_if_fail (stop == 0 || segment->format == format);
351 stop = segment->duration + stop;
353 stop = segment->stop;
359 /* if we have a valid stop time, make sure it is clipped */
361 if (segment->duration != -1)
362 stop = CLAMP (stop, 0, segment->duration);
364 stop = MAX (stop, 0);
367 /* we can't have stop before start */
369 g_return_if_fail (start <= stop);
371 segment->rate = rate;
372 segment->abs_rate = ABS (rate);
373 segment->applied_rate = 1.0;
374 segment->flags = flags;
375 segment->start = start;
376 segment->stop = stop;
377 segment->time = start;
379 last_stop = segment->last_stop;
380 if (update_start && rate > 0.0) {
383 if (update_stop && rate < 0.0) {
387 if (segment->duration != -1)
388 last_stop = segment->duration;
393 /* set update arg to reflect update of last_stop */
395 *update = last_stop != segment->last_stop;
397 /* update new position */
398 segment->last_stop = last_stop;
402 * gst_segment_set_newsegment:
403 * @segment: a #GstSegment structure.
404 * @update: flag indicating a new segment is started or updated
405 * @rate: the rate of the segment.
406 * @format: the format of the segment.
407 * @start: the new start value
408 * @stop: the new stop value
409 * @time: the new stream time
411 * Update the segment structure with the field values of a new segment event and
412 * with a default applied_rate of 1.0.
417 gst_segment_set_newsegment (GstSegment * segment, gboolean update, gdouble rate,
418 GstFormat format, gint64 start, gint64 stop, gint64 time)
420 gst_segment_set_newsegment_full (segment, update, rate, 1.0, format, start,
425 * gst_segment_set_newsegment_full:
426 * @segment: a #GstSegment structure.
427 * @update: flag indicating a new segment is started or updated
428 * @rate: the rate of the segment.
429 * @applied_rate: the applied rate of the segment.
430 * @format: the format of the segment.
431 * @start: the new start value
432 * @stop: the new stop value
433 * @time: the new stream time
435 * Update the segment structure with the field values of a new segment event.
438 gst_segment_set_newsegment_full (GstSegment * segment, gboolean update,
439 gdouble rate, gdouble applied_rate, GstFormat format, gint64 start,
440 gint64 stop, gint64 time)
442 gint64 duration, last_stop;
444 g_return_if_fail (rate != 0.0);
445 g_return_if_fail (applied_rate != 0.0);
446 g_return_if_fail (segment != NULL);
448 GST_DEBUG ("configuring segment update %d, rate %lf, format %s, "
449 "start %" G_GINT64_FORMAT ", stop %" G_GINT64_FORMAT ", position %"
450 G_GINT64_FORMAT, update, rate, gst_format_get_name (format), start,
452 GST_DEBUG ("old segment was: %" GST_SEGMENT_FORMAT, segment);
454 if (G_UNLIKELY (segment->format == GST_FORMAT_UNDEFINED))
455 segment->format = format;
457 /* any other format with 0 also gives time 0, the other values are
458 * invalid in the format though. */
459 if (format != segment->format && start == 0) {
460 format = segment->format;
467 g_return_if_fail (segment->format == format);
470 if (G_LIKELY (segment->rate > 0.0)) {
471 /* an update to the current segment is done, elapsed time is
472 * difference between the old start and new start. */
473 if (start > segment->start)
474 duration = start - segment->start;
478 /* for negative rates, the elapsed duration is the diff between the stop
480 if (stop != -1 && stop < segment->stop)
481 duration = segment->stop - stop;
485 /* update last_stop to be a valid value in the updated segment */
486 if (start > segment->last_stop)
488 else if (stop != -1 && stop < segment->last_stop)
491 last_stop = segment->last_stop;
493 /* the new segment has to be aligned with the old segment.
494 * We first update the accumulated time of the previous
495 * segment. the accumulated time is used when syncing to the
497 if (segment->stop != -1) {
498 duration = segment->stop - segment->start;
499 } else if (segment->last_stop != -1) {
500 /* else use last seen timestamp as segment stop */
501 duration = segment->last_stop - segment->start;
503 /* else we don't know and throw a warning.. really, this should
504 * be fixed in the element. */
505 g_warning ("closing segment of unknown duration, assuming duration of 0");
508 /* position the last_stop to the next expected position in the new segment,
509 * which is the start or the stop of the segment */
515 /* use previous rate to calculate duration */
516 if (G_LIKELY (segment->abs_rate != 1.0))
517 duration /= segment->abs_rate;
519 /* accumulate duration */
520 segment->accum += duration;
522 /* then update the current segment */
523 segment->rate = rate;
524 segment->abs_rate = ABS (rate);
525 segment->applied_rate = applied_rate;
526 segment->start = start;
527 segment->last_stop = last_stop;
528 segment->stop = stop;
529 segment->time = time;
533 * gst_segment_to_stream_time:
534 * @segment: a #GstSegment structure.
535 * @format: the format of the segment.
536 * @position: the position in the segment
538 * Translate @position to stream time using the currently configured
539 * segment. The @position value must be between @segment start and
542 * This function is typically used by elements that need to operate on
543 * the stream time of the buffers it receives, such as effect plugins.
544 * In those use cases, @position is typically the buffer timestamp or
545 * clock time that one wants to convert to the stream time.
546 * The stream time is always between 0 and the total duration of the
549 * Returns: the position in stream_time or -1 when an invalid position
553 gst_segment_to_stream_time (GstSegment * segment, GstFormat format,
556 gint64 result, start, stop, time;
557 gdouble abs_applied_rate;
559 g_return_val_if_fail (segment != NULL, -1);
561 /* format does not matter for -1 */
562 if (G_UNLIKELY (position == -1))
565 if (G_UNLIKELY (segment->format == GST_FORMAT_UNDEFINED))
566 segment->format = format;
568 /* if we have the position for the same format as the segment, we can compare
569 * the start and stop values, otherwise we assume 0 and -1 */
570 if (G_LIKELY (segment->format == format)) {
571 start = segment->start;
572 stop = segment->stop;
573 time = segment->time;
580 /* outside of the segment boundary stop */
581 if (G_UNLIKELY (stop != -1 && position > stop))
584 /* before the segment boundary */
585 if (G_UNLIKELY (position < start))
588 /* time must be known */
589 if (G_UNLIKELY (time == -1))
592 /* bring to uncorrected position in segment */
593 result = position - start;
595 abs_applied_rate = ABS (segment->applied_rate);
597 /* correct for applied rate if needed */
598 if (G_UNLIKELY (abs_applied_rate != 1.0))
599 result *= abs_applied_rate;
601 /* add or subtract from segment time based on applied rate */
602 if (G_LIKELY (segment->applied_rate > 0.0)) {
603 /* correct for segment time */
606 /* correct for segment time, clamp at 0. Streams with a negative
607 * applied_rate have timestamps between start and stop, as usual, but have
608 * the time member starting high and going backwards. */
609 if (G_LIKELY (time > result))
610 result = time - result;
619 * gst_segment_to_running_time:
620 * @segment: a #GstSegment structure.
621 * @format: the format of the segment.
622 * @position: the position in the segment
624 * Translate @position to the total running time using the currently configured
625 * and previously accumulated segments. Position is a value between @segment
626 * start and stop time.
628 * This function is typically used by elements that need to synchronize to the
629 * global clock in a pipeline. The runnning time is a constantly increasing value
630 * starting from 0. When gst_segment_init() is called, this value will reset to
633 * This function returns -1 if the position is outside of @segment start and stop.
635 * Returns: the position as the total running time or -1 when an invalid position
639 gst_segment_to_running_time (GstSegment * segment, GstFormat format,
643 gint64 start, stop, accum;
645 g_return_val_if_fail (segment != NULL, -1);
647 if (G_UNLIKELY (position == -1))
650 if (G_UNLIKELY (segment->format == GST_FORMAT_UNDEFINED))
651 segment->format = format;
653 /* if we have the position for the same format as the segment, we can compare
654 * the start and stop values, otherwise we assume 0 and -1 */
655 if (G_LIKELY (segment->format == format)) {
656 start = segment->start;
657 stop = segment->stop;
658 accum = segment->accum;
665 /* before the segment boundary */
666 if (G_UNLIKELY (position < start))
669 if (G_LIKELY (segment->rate > 0.0)) {
670 /* outside of the segment boundary stop */
671 if (G_UNLIKELY (stop != -1 && position > stop))
674 /* bring to uncorrected position in segment */
675 result = position - start;
677 /* cannot continue if no stop position set or outside of
679 if (G_UNLIKELY (stop == -1 || position > stop))
682 /* bring to uncorrected position in segment */
683 result = stop - position;
686 /* scale based on the rate, avoid division by and conversion to
687 * float when not needed */
688 if (G_UNLIKELY (segment->abs_rate != 1.0))
689 result /= segment->abs_rate;
691 /* correct for accumulated segments */
699 * @segment: a #GstSegment structure.
700 * @format: the format of the segment.
701 * @start: the start position in the segment
702 * @stop: the stop position in the segment
703 * @clip_start: the clipped start position in the segment
704 * @clip_stop: the clipped stop position in the segment
706 * Clip the given @start and @stop values to the segment boundaries given
707 * in @segment. @start and @stop are compared and clipped to @segment
708 * start and stop values.
710 * If the function returns FALSE, @start and @stop are known to fall
711 * outside of @segment and @clip_start and @clip_stop are not updated.
713 * When the function returns TRUE, @clip_start and @clip_stop will be
714 * updated. If @clip_start or @clip_stop are different from @start or @stop
715 * respectively, the region fell partially in the segment.
717 * Note that when @stop is -1, @clip_stop will be set to the end of the
718 * segment. Depending on the use case, this may or may not be what you want.
720 * Returns: TRUE if the given @start and @stop times fall partially or
721 * completely in @segment, FALSE if the values are completely outside
725 gst_segment_clip (GstSegment * segment, GstFormat format, gint64 start,
726 gint64 stop, gint64 * clip_start, gint64 * clip_stop)
728 g_return_val_if_fail (segment != NULL, FALSE);
730 if (G_UNLIKELY (segment->format == GST_FORMAT_UNDEFINED))
731 segment->format = format;
733 g_return_val_if_fail (segment->format == format, FALSE);
735 /* if we have a stop position and a valid start and start is bigger,
736 * we're outside of the segment */
737 if (G_UNLIKELY (segment->stop != -1 && start != -1 && start >= segment->stop))
740 /* if a stop position is given and is before the segment start,
741 * we're outside of the segment */
742 if (G_UNLIKELY (stop != -1 && stop != start && stop <= segment->start))
749 *clip_start = MAX (start, segment->start);
754 *clip_stop = segment->stop;
755 else if (segment->stop == -1)
756 *clip_stop = MAX (-1, stop);
758 *clip_stop = MIN (stop, segment->stop);
760 if (segment->duration != -1)
761 *clip_stop = MIN (*clip_stop, segment->duration);
768 * gst_segment_to_position:
769 * @segment: a #GstSegment structure.
770 * @format: the format of the segment.
771 * @running_time: the running_time in the segment
773 * Convert @running_time into a position in the segment so that
774 * gst_segment_to_running_time() with that position returns @running_time.
776 * Returns: the position in the segment for @running_time. This function returns
777 * -1 when @running_time is -1 or when it is not inside @segment.
782 gst_segment_to_position (GstSegment * segment, GstFormat format,
786 gint64 start, stop, accum;
788 g_return_val_if_fail (segment != NULL, -1);
790 if (G_UNLIKELY (running_time == -1))
793 if (G_UNLIKELY (segment->format == GST_FORMAT_UNDEFINED))
794 segment->format = format;
796 /* if we have the position for the same format as the segment, we can compare
797 * the start and stop values, otherwise we assume 0 and -1 */
798 if (G_LIKELY (segment->format == format)) {
799 start = segment->start;
800 stop = segment->stop;
801 accum = segment->accum;
808 /* this running_time was for a previous segment */
809 if (running_time < accum)
812 /* start by subtracting the accumulated time */
813 result = running_time - accum;
815 /* move into the segment at the right rate */
816 if (G_UNLIKELY (segment->abs_rate != 1.0))
817 result = ceil (result * segment->abs_rate);
819 if (G_LIKELY (segment->rate > 0.0)) {
820 /* bring to corrected position in segment */
823 /* outside of the segment boundary stop */
824 if (G_UNLIKELY (stop != -1 && result > stop))
827 /* cannot continue if no stop position set or outside of
829 if (G_UNLIKELY (stop == -1 || result + start > stop))
832 /* bring to corrected position in segment */
833 result = stop - result;
840 * gst_segment_set_running_time:
841 * @segment: a #GstSegment structure.
842 * @format: the format of the segment.
843 * @running_time: the running_time in the segment
845 * Adjust the start/stop and accum values of @segment such that the next valid
846 * buffer will be one with @running_time.
848 * Returns: %TRUE if the segment could be updated successfully. If %FALSE is
849 * returned, @running_time is -1 or not in @segment.
854 gst_segment_set_running_time (GstSegment * segment, GstFormat format,
858 gint64 start, stop, last_stop;
860 /* start by bringing the running_time into the segment position */
861 position = gst_segment_to_position (segment, format, running_time);
863 /* we must have a valid position now */
864 if (G_UNLIKELY (position == -1))
867 start = segment->start;
868 stop = segment->stop;
869 last_stop = segment->last_stop;
871 if (G_LIKELY (segment->rate > 0.0)) {
872 /* update the start/last_stop and time values */
874 if (last_stop < start)
877 /* reverse, update stop */
879 /* if we were past the position, go back */
880 if (last_stop > stop)
883 /* and accumulated time is exactly the running time */
884 segment->time = gst_segment_to_stream_time (segment, format, start);
885 segment->start = start;
886 segment->stop = stop;
887 segment->last_stop = last_stop;
888 segment->accum = running_time;