• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer
2  * Copyright (C) 2005 Wim Taymans <wim@fluendo.com>
3  *
4  * gstsegment.c: GstSegment subsystem
5  *
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.
10  *
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.
15  *
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., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 
22 #include "gst_private.h"
23 
24 #include <math.h>
25 
26 #include "gstutils.h"
27 #include "gstsegment.h"
28 
29 /**
30  * SECTION:gstsegment
31  * @title: GstSegment
32  * @short_description: Structure describing the configured region of interest
33  *                     in a media file.
34  * @see_also: #GstEvent
35  *
36  * This helper structure holds the relevant values for tracking the region of
37  * interest in a media file, called a segment.
38  *
39  * The structure can be used for two purposes:
40  *
41  *   * performing seeks (handling seek events)
42  *   * tracking playback regions (handling newsegment events)
43  *
44  * The segment is usually configured by the application with a seek event which
45  * is propagated upstream and eventually handled by an element that performs the seek.
46  *
47  * The configured segment is then propagated back downstream with a newsegment event.
48  * This information is then used to clip media to the segment boundaries.
49  *
50  * A segment structure is initialized with gst_segment_init(), which takes a #GstFormat
51  * that will be used as the format of the segment values. The segment will be configured
52  * with a start value of 0 and a stop/duration of -1, which is undefined. The default
53  * rate and applied_rate is 1.0.
54  *
55  * The public duration field contains the duration of the segment. When using
56  * the segment for seeking, the start and time members should normally be left
57  * to their default 0 value. The stop position is left to -1 unless explicitly
58  * configured to a different value after a seek event.
59  *
60  * The current position in the segment should be set by changing the position
61  * member in the structure.
62  *
63  * For elements that perform seeks, the current segment should be updated with the
64  * gst_segment_do_seek() and the values from the seek event. This method will update
65  * all the segment fields. The position field will contain the new playback position.
66  * If the start_type was different from GST_SEEK_TYPE_NONE, playback continues from
67  * the position position, possibly with updated flags or rate.
68  *
69  * For elements that want to use #GstSegment to track the playback region,
70  * update the segment fields with the information from the newsegment event.
71  * The gst_segment_clip() method can be used to check and clip
72  * the media data to the segment boundaries.
73  *
74  * For elements that want to synchronize to the pipeline clock, gst_segment_to_running_time()
75  * can be used to convert a timestamp to a value that can be used to synchronize
76  * to the clock. This function takes into account the base as well as
77  * any rate or applied_rate conversions.
78  *
79  * For elements that need to perform operations on media data in stream_time,
80  * gst_segment_to_stream_time() can be used to convert a timestamp and the segment
81  * info to stream time (which is always between 0 and the duration of the stream).
82  */
83 
84 /* FIXME 2.0: remove unused format parameter.
85  * Most of the methods in gstsegment.c take and extra GstFormat format, just to
86  * verify segment->format == format.
87  * See https://bugzilla.gnome.org/show_bug.cgi?id=788979
88  */
89 
90 /**
91  * gst_segment_copy:
92  * @segment: (transfer none): a #GstSegment
93  *
94  * Create a copy of given @segment.
95  *
96  * Free-function: gst_segment_free
97  *
98  * Returns: (transfer full): a new #GstSegment, free with gst_segment_free().
99  */
100 GstSegment *
gst_segment_copy(const GstSegment * segment)101 gst_segment_copy (const GstSegment * segment)
102 {
103   GstSegment *result = NULL;
104 
105   if (segment) {
106     result = (GstSegment *) g_slice_copy (sizeof (GstSegment), segment);
107   }
108   return result;
109 }
110 
111 /**
112  * gst_segment_copy_into:
113  * @src: (transfer none): a #GstSegment
114  * @dest: (transfer none): a #GstSegment
115  *
116  * Copy the contents of @src into @dest.
117  */
118 void
gst_segment_copy_into(const GstSegment * src,GstSegment * dest)119 gst_segment_copy_into (const GstSegment * src, GstSegment * dest)
120 {
121   memcpy (dest, src, sizeof (GstSegment));
122 }
123 
124 G_DEFINE_BOXED_TYPE (GstSegment, gst_segment,
125     (GBoxedCopyFunc) gst_segment_copy, (GBoxedFreeFunc) gst_segment_free);
126 
127 /**
128  * gst_segment_new:
129  *
130  * Allocate a new #GstSegment structure and initialize it using
131  * gst_segment_init().
132  *
133  * Free-function: gst_segment_free
134  *
135  * Returns: (transfer full): a new #GstSegment, free with gst_segment_free().
136  */
137 GstSegment *
gst_segment_new(void)138 gst_segment_new (void)
139 {
140   GstSegment *result;
141 
142   result = g_slice_new0 (GstSegment);
143   gst_segment_init (result, GST_FORMAT_UNDEFINED);
144 
145   return result;
146 }
147 
148 /**
149  * gst_segment_free:
150  * @segment: (in) (transfer full): a #GstSegment
151  *
152  * Free the allocated segment @segment.
153  */
154 void
gst_segment_free(GstSegment * segment)155 gst_segment_free (GstSegment * segment)
156 {
157   g_slice_free (GstSegment, segment);
158 }
159 
160 /**
161  * gst_segment_init:
162  * @segment: a #GstSegment structure.
163  * @format: the format of the segment.
164  *
165  * The start/position fields are set to 0 and the stop/duration
166  * fields are set to -1 (unknown). The default rate of 1.0 and no
167  * flags are set.
168  *
169  * Initialize @segment to its default values.
170  */
171 void
gst_segment_init(GstSegment * segment,GstFormat format)172 gst_segment_init (GstSegment * segment, GstFormat format)
173 {
174   g_return_if_fail (segment != NULL);
175 
176   segment->flags = GST_SEGMENT_FLAG_NONE;
177   segment->rate = 1.0;
178   segment->applied_rate = 1.0;
179   segment->format = format;
180   segment->base = 0;
181   segment->offset = 0;
182   segment->start = 0;
183   segment->stop = -1;
184   segment->time = 0;
185   segment->position = 0;
186   segment->duration = -1;
187 }
188 
189 /**
190  * gst_segment_do_seek:
191  * @segment: a #GstSegment structure.
192  * @rate: the rate of the segment.
193  * @format: the format of the segment.
194  * @flags: the segment flags for the segment
195  * @start_type: the seek method
196  * @start: the seek start value
197  * @stop_type: the seek method
198  * @stop: the seek stop value
199  * @update: (out) (allow-none): boolean holding whether position was updated.
200  *
201  * Update the segment structure with the field values of a seek event (see
202  * gst_event_new_seek()).
203  *
204  * After calling this method, the segment field position and time will
205  * contain the requested new position in the segment. The new requested
206  * position in the segment depends on @rate and @start_type and @stop_type.
207  *
208  * For positive @rate, the new position in the segment is the new @segment
209  * start field when it was updated with a @start_type different from
210  * #GST_SEEK_TYPE_NONE. If no update was performed on @segment start position
211  * (#GST_SEEK_TYPE_NONE), @start is ignored and @segment position is
212  * unmodified.
213  *
214  * For negative @rate, the new position in the segment is the new @segment
215  * stop field when it was updated with a @stop_type different from
216  * #GST_SEEK_TYPE_NONE. If no stop was previously configured in the segment, the
217  * duration of the segment will be used to update the stop position.
218  * If no update was performed on @segment stop position (#GST_SEEK_TYPE_NONE),
219  * @stop is ignored and @segment position is unmodified.
220  *
221  * The applied rate of the segment will be set to 1.0 by default.
222  * If the caller can apply a rate change, it should update @segment
223  * rate and applied_rate after calling this function.
224  *
225  * @update will be set to %TRUE if a seek should be performed to the segment
226  * position field. This field can be %FALSE if, for example, only the @rate
227  * has been changed but not the playback position.
228  *
229  * Returns: %TRUE if the seek could be performed.
230  */
231 gboolean
gst_segment_do_seek(GstSegment * segment,gdouble rate,GstFormat format,GstSeekFlags flags,GstSeekType start_type,guint64 start,GstSeekType stop_type,guint64 stop,gboolean * update)232 gst_segment_do_seek (GstSegment * segment, gdouble rate,
233     GstFormat format, GstSeekFlags flags,
234     GstSeekType start_type, guint64 start,
235     GstSeekType stop_type, guint64 stop, gboolean * update)
236 {
237   gboolean update_stop, update_start;
238   guint64 position, base;
239 
240   g_return_val_if_fail (rate != 0.0, FALSE);
241   g_return_val_if_fail (segment != NULL, FALSE);
242   g_return_val_if_fail (segment->format == format, FALSE);
243 
244   /* Elements should not pass instant-rate seeks to gst_segment_do_seek().
245    * This helps catch elements that have not been updated yet */
246   if (flags & GST_SEEK_FLAG_INSTANT_RATE_CHANGE)
247     return FALSE;
248 
249   update_start = update_stop = TRUE;
250 
251   position = segment->position;
252 
253   /* segment->start is never invalid */
254   switch (start_type) {
255     case GST_SEEK_TYPE_NONE:
256       /* no update to segment, take previous start */
257       start = segment->start;
258       update_start = FALSE;
259       break;
260     case GST_SEEK_TYPE_SET:
261       /* start holds desired position, map -1 to the start */
262       if (start == -1)
263         start = 0;
264       break;
265     case GST_SEEK_TYPE_END:
266       if (segment->duration != -1) {
267         /* add start to total length */
268         start = segment->duration + start;
269       } else {
270         /* no update if duration unknown */
271         start = segment->start;
272         update_start = FALSE;
273       }
274       break;
275   }
276   /* bring in sane range */
277   if (segment->duration != -1)
278     start = MIN (start, segment->duration);
279   else
280     start = MAX ((gint64) start, 0);
281 
282   /* stop can be -1 if we have not configured a stop. */
283   switch (stop_type) {
284     case GST_SEEK_TYPE_NONE:
285       stop = segment->stop;
286       update_stop = FALSE;
287       break;
288     case GST_SEEK_TYPE_SET:
289       /* stop holds required value */
290       break;
291     case GST_SEEK_TYPE_END:
292       if (segment->duration != -1) {
293         stop = segment->duration + stop;
294       } else {
295         stop = segment->stop;
296         update_stop = FALSE;
297       }
298       break;
299   }
300 
301   /* if we have a valid stop time, make sure it is clipped */
302   if (stop != -1) {
303     if (segment->duration != -1)
304       stop = CLAMP ((gint64) stop, 0, (gint64) segment->duration);
305     else
306       stop = MAX ((gint64) stop, 0);
307   }
308 
309   /* we can't have stop before start */
310   if (stop != -1) {
311     if (start > stop) {
312       GST_WARNING ("segment update failed: start(%" G_GUINT64_FORMAT
313           ") > stop(%" G_GUINT64_FORMAT ")", start, stop);
314       g_return_val_if_fail (start <= stop, FALSE);
315       return FALSE;
316     }
317   }
318 
319   if (flags & GST_SEEK_FLAG_FLUSH) {
320     /* flush resets the running_time */
321     base = 0;
322   } else {
323     /* make sure the position is inside the segment start/stop */
324     position = CLAMP (position, segment->start, segment->stop);
325 
326     /* remember the elapsed time */
327     base = gst_segment_to_running_time (segment, format, position);
328     GST_DEBUG ("updated segment.base: %" G_GUINT64_FORMAT, base);
329   }
330 
331   if (update_start && rate > 0.0) {
332     position = start;
333   }
334   if (update_stop && rate < 0.0) {
335     if (stop != -1)
336       position = stop;
337     else {
338       if (segment->duration != -1)
339         position = segment->duration;
340       else
341         position = 0;
342     }
343   }
344 
345   /* set update arg to reflect update of position */
346   if (update)
347     *update = position != segment->position;
348 
349   /* update new values */
350   /* be explicit about our GstSeekFlag -> GstSegmentFlag conversion */
351   segment->flags = GST_SEGMENT_FLAG_NONE;
352   if ((flags & GST_SEEK_FLAG_FLUSH) != 0)
353     segment->flags |= GST_SEGMENT_FLAG_RESET;
354   if ((flags & GST_SEEK_FLAG_TRICKMODE) != 0)
355     segment->flags |= GST_SEGMENT_FLAG_TRICKMODE;
356   if ((flags & GST_SEEK_FLAG_SEGMENT) != 0)
357     segment->flags |= GST_SEGMENT_FLAG_SEGMENT;
358   if ((flags & GST_SEEK_FLAG_TRICKMODE_KEY_UNITS) != 0)
359     segment->flags |= GST_SEGMENT_FLAG_TRICKMODE_KEY_UNITS;
360   if ((flags & GST_SEEK_FLAG_TRICKMODE_NO_AUDIO) != 0)
361     segment->flags |= GST_SEGMENT_FLAG_TRICKMODE_NO_AUDIO;
362   if ((flags & GST_SEEK_FLAG_TRICKMODE_FORWARD_PREDICTED) != 0)
363     segment->flags |= GST_SEGMENT_FLAG_TRICKMODE_FORWARD_PREDICTED;
364 
365   segment->rate = rate;
366   segment->applied_rate = 1.0;
367 
368   segment->base = base;
369   if (rate > 0.0)
370     segment->offset = position - start;
371   else {
372     if (stop != -1)
373       segment->offset = stop - position;
374     else if (segment->duration != -1)
375       segment->offset = segment->duration - position;
376     else
377       segment->offset = 0;
378   }
379 
380   segment->start = start;
381   segment->stop = stop;
382   segment->time = start;
383   segment->position = position;
384 
385   GST_INFO ("segment updated: %" GST_SEGMENT_FORMAT, segment);
386 
387   return TRUE;
388 }
389 
390 /**
391  * gst_segment_to_stream_time_full:
392  * @segment: a #GstSegment structure.
393  * @format: the format of the segment.
394  * @position: the position in the segment
395  * @stream_time: (out): result stream-time
396  *
397  * Translate @position to the total stream time using the currently configured
398  * segment. Compared to gst_segment_to_stream_time() this function can return
399  * negative stream-time.
400  *
401  * This function is typically used by elements that need to synchronize buffers
402  * against the clock or each other.
403  *
404  * @position can be any value and the result of this function for values outside
405  * of the segment is extrapolated.
406  *
407  * When 1 is returned, @position resulted in a positive stream-time returned
408  * in @stream_time.
409  *
410  * When this function returns -1, the returned @stream_time should be negated
411  * to get the real negative stream time.
412  *
413  * Returns: a 1 or -1 on success, 0 on failure.
414  *
415  * Since: 1.8
416  */
417 gint
gst_segment_to_stream_time_full(const GstSegment * segment,GstFormat format,guint64 position,guint64 * stream_time)418 gst_segment_to_stream_time_full (const GstSegment * segment, GstFormat format,
419     guint64 position, guint64 * stream_time)
420 {
421   guint64 start, stop, time;
422   gdouble abs_applied_rate;
423   gint res;
424 
425   /* format does not matter for -1 */
426   if (G_UNLIKELY (position == -1)) {
427     *stream_time = -1;
428     return 0;
429   }
430 
431   g_return_val_if_fail (segment != NULL, 0);
432   g_return_val_if_fail (segment->format == format, 0);
433 
434   stop = segment->stop;
435 
436   start = segment->start;
437   time = segment->time;
438 
439   /* time must be known */
440   if (G_UNLIKELY (time == -1))
441     return 0;
442 
443   abs_applied_rate = ABS (segment->applied_rate);
444 
445   /* add or subtract from segment time based on applied rate */
446   if (G_LIKELY (segment->applied_rate > 0.0)) {
447     if (G_LIKELY (position > start)) {
448       /* bring to uncorrected position in segment */
449       *stream_time = position - start;
450       /* correct for applied rate if needed */
451       if (G_UNLIKELY (abs_applied_rate != 1.0))
452         *stream_time *= abs_applied_rate;
453       /* correct for segment time */
454       *stream_time += time;
455       res = 1;
456     } else {
457       *stream_time = start - position;
458       if (G_UNLIKELY (abs_applied_rate != 1.0))
459         *stream_time *= abs_applied_rate;
460       if (*stream_time > time) {
461         *stream_time -= time;
462         res = -1;
463       } else {
464         *stream_time = time - *stream_time;
465         res = 1;
466       }
467     }
468   } else {
469     /* correct for segment time. Streams with a negative applied_rate
470      * have timestamps between start and stop, as usual, but have the
471      * time member starting high and going backwards.  */
472     /* cannot continue without a known segment stop */
473     if (G_UNLIKELY (stop == -1))
474       return 0;
475     if (G_UNLIKELY (position > stop)) {
476       *stream_time = position - stop;
477       if (G_UNLIKELY (abs_applied_rate != 1.0))
478         *stream_time *= abs_applied_rate;
479       if (*stream_time > time) {
480         *stream_time -= time;
481         res = -1;
482       } else {
483         *stream_time = time - *stream_time;
484         res = 1;
485       }
486     } else {
487       *stream_time = stop - position;
488       if (G_UNLIKELY (abs_applied_rate != 1.0))
489         *stream_time *= abs_applied_rate;
490       *stream_time += time;
491       res = 1;
492     }
493   }
494 
495   return res;
496 }
497 
498 /**
499  * gst_segment_to_stream_time:
500  * @segment: a #GstSegment structure.
501  * @format: the format of the segment.
502  * @position: the position in the segment
503  *
504  * Translate @position to stream time using the currently configured
505  * segment. The @position value must be between @segment start and
506  * stop value.
507  *
508  * This function is typically used by elements that need to operate on
509  * the stream time of the buffers it receives, such as effect plugins.
510  * In those use cases, @position is typically the buffer timestamp or
511  * clock time that one wants to convert to the stream time.
512  * The stream time is always between 0 and the total duration of the
513  * media stream.
514  *
515  * Returns: the position in stream_time or -1 when an invalid position
516  * was given.
517  *
518  * Since: 1.8
519  */
520 guint64
gst_segment_to_stream_time(const GstSegment * segment,GstFormat format,guint64 position)521 gst_segment_to_stream_time (const GstSegment * segment, GstFormat format,
522     guint64 position)
523 {
524   guint64 result;
525 
526   g_return_val_if_fail (segment != NULL, -1);
527   g_return_val_if_fail (segment->format == format, -1);
528 
529   /* before the segment boundary */
530   if (G_UNLIKELY (position < segment->start)) {
531     GST_DEBUG ("position(%" G_GUINT64_FORMAT ") < start(%" G_GUINT64_FORMAT
532         ")", position, segment->start);
533     return -1;
534   }
535   /* after the segment boundary */
536   if (G_UNLIKELY (segment->stop != -1 && position > segment->stop)) {
537     GST_DEBUG ("position(%" G_GUINT64_FORMAT ") > stop(%" G_GUINT64_FORMAT
538         ")", position, segment->stop);
539     return -1;
540   }
541 
542   if (gst_segment_to_stream_time_full (segment, format, position, &result) == 1)
543     return result;
544 
545   return -1;
546 }
547 
548 /**
549  * gst_segment_position_from_stream_time_full:
550  * @segment: a #GstSegment structure.
551  * @format: the format of the segment.
552  * @stream_time: the stream-time
553  * @position: (out): the resulting position in the segment
554  *
555  * Translate @stream_time to the segment position using the currently configured
556  * segment. Compared to gst_segment_position_from_stream_time() this function can
557  * return negative segment position.
558  *
559  * This function is typically used by elements that need to synchronize buffers
560  * against the clock or each other.
561  *
562  * @stream_time can be any value and the result of this function for values outside
563  * of the segment is extrapolated.
564  *
565  * When 1 is returned, @stream_time resulted in a positive position returned
566  * in @position.
567  *
568  * When this function returns -1, the returned @position should be negated
569  * to get the real negative segment position.
570  *
571  * Returns: a 1 or -1 on success, 0 on failure.
572  *
573  * Since: 1.8
574  */
575 gint
gst_segment_position_from_stream_time_full(const GstSegment * segment,GstFormat format,guint64 stream_time,guint64 * position)576 gst_segment_position_from_stream_time_full (const GstSegment * segment,
577     GstFormat format, guint64 stream_time, guint64 * position)
578 {
579   guint64 start, time;
580   gdouble abs_applied_rate;
581   gint res;
582 
583   /* format does not matter for -1 */
584   if (G_UNLIKELY (stream_time == -1)) {
585     *position = -1;
586     return 0;
587   }
588 
589   g_return_val_if_fail (segment != NULL, -1);
590   g_return_val_if_fail (segment->format == format, -1);
591 
592   start = segment->start;
593   time = segment->time;
594 
595   /* time must be known */
596   if (G_UNLIKELY (time == -1))
597     return 0;
598 
599   abs_applied_rate = ABS (segment->applied_rate);
600 
601   if (G_LIKELY (segment->applied_rate > 0.0)) {
602     if (G_LIKELY (stream_time > time)) {
603       res = 1;
604       *position = stream_time - time;
605     } else {
606       res = -1;
607       *position = time - stream_time;
608     }
609     /* correct for applied rate if needed */
610     if (G_UNLIKELY (abs_applied_rate != 1.0))
611       *position /= abs_applied_rate;
612 
613     if (G_UNLIKELY (res == -1)) {
614       if (*position > start) {
615         *position -= start;
616       } else {
617         *position = start - *position;
618         res = 1;
619       }
620     } else {
621       *position += start;
622     }
623   } else {
624     GstClockTime stop = segment->stop;
625     /* cannot continue without a known segment stop */
626     if (G_UNLIKELY (stop == -1))
627       return 0;
628     if (G_UNLIKELY (time > stream_time)) {
629       res = -1;
630       *position = time - stream_time;
631     } else {
632       res = 1;
633       *position = stream_time - time;
634     }
635     if (G_UNLIKELY (abs_applied_rate != 1.0))
636       *position /= abs_applied_rate;
637     if (G_UNLIKELY (stop < *position)) {
638       if (G_LIKELY (res == 1)) {
639         *position -= stop;
640         res = -1;
641       } else {
642         *position += stop;
643         res = 1;
644       }
645     } else {
646       if (G_LIKELY (res == 1)) {
647         *position = stop - *position;
648         res = 1;
649       } else {
650         *position += stop;
651         res = 1;
652       }
653     }
654   }
655 
656   return res;
657 }
658 
659 /**
660  * gst_segment_position_from_stream_time:
661  * @segment: a #GstSegment structure.
662  * @format: the format of the segment.
663  * @stream_time: the stream_time in the segment
664  *
665  * Convert @stream_time into a position in the segment so that
666  * gst_segment_to_stream_time() with that position returns @stream_time.
667  *
668  * Returns: the position in the segment for @stream_time. This function returns
669  * -1 when @stream_time is -1 or when it is not inside @segment.
670  *
671  * Since: 1.8
672  */
673 guint64
gst_segment_position_from_stream_time(const GstSegment * segment,GstFormat format,guint64 stream_time)674 gst_segment_position_from_stream_time (const GstSegment * segment,
675     GstFormat format, guint64 stream_time)
676 {
677   guint64 position;
678   gint res;
679 
680   g_return_val_if_fail (segment != NULL, -1);
681   g_return_val_if_fail (segment->format == format, -1);
682 
683   res =
684       gst_segment_position_from_stream_time_full (segment, format, stream_time,
685       &position);
686 
687   /* before the segment boundary */
688   if (G_UNLIKELY (position < segment->start)) {
689     GST_DEBUG ("position(%" G_GUINT64_FORMAT ") < start(%" G_GUINT64_FORMAT
690         ")", position, segment->start);
691     return -1;
692   }
693 
694   /* after the segment boundary */
695   if (G_UNLIKELY (segment->stop != -1 && position > segment->stop)) {
696     GST_DEBUG ("position(%" G_GUINT64_FORMAT ") > stop(%" G_GUINT64_FORMAT
697         ")", position, segment->stop);
698     return -1;
699   }
700 
701   if (res == 1)
702     return position;
703 
704   return -1;
705 }
706 
707 /**
708  * gst_segment_to_running_time_full:
709  * @segment: a #GstSegment structure.
710  * @format: the format of the segment.
711  * @position: the position in the segment
712  * @running_time: (out) (allow-none): result running-time
713  *
714  * Translate @position to the total running time using the currently configured
715  * segment. Compared to gst_segment_to_running_time() this function can return
716  * negative running-time.
717  *
718  * This function is typically used by elements that need to synchronize buffers
719  * against the clock or each other.
720  *
721  * @position can be any value and the result of this function for values outside
722  * of the segment is extrapolated.
723  *
724  * When 1 is returned, @position resulted in a positive running-time returned
725  * in @running_time.
726  *
727  * When this function returns -1, the returned @running_time should be negated
728  * to get the real negative running time.
729  *
730  * Returns: a 1 or -1 on success, 0 on failure.
731  *
732  * Since: 1.6
733  */
734 gint
gst_segment_to_running_time_full(const GstSegment * segment,GstFormat format,guint64 position,guint64 * running_time)735 gst_segment_to_running_time_full (const GstSegment * segment, GstFormat format,
736     guint64 position, guint64 * running_time)
737 {
738   gint res = 0;
739   guint64 result;
740   guint64 start, stop, offset;
741   gdouble abs_rate;
742 
743   if (G_UNLIKELY (position == -1)) {
744     GST_DEBUG ("invalid position (-1)");
745     goto done;
746   }
747 
748   g_return_val_if_fail (segment != NULL, 0);
749   g_return_val_if_fail (segment->format == format, 0);
750 
751   offset = segment->offset;
752 
753   if (G_LIKELY (segment->rate > 0.0)) {
754     start = segment->start + offset;
755 
756     /* bring to uncorrected position in segment */
757     if (position < start) {
758       /* negative value */
759       result = start - position;
760       res = -1;
761     } else {
762       result = position - start;
763       res = 1;
764     }
765   } else {
766     stop = segment->stop;
767 
768     if (stop == -1 && segment->duration != -1)
769       stop = segment->start + segment->duration;
770 
771     /* cannot continue if no stop position set or invalid offset */
772     g_return_val_if_fail (stop != -1, 0);
773     g_return_val_if_fail (stop >= offset, 0);
774 
775     stop -= offset;
776 
777     /* bring to uncorrected position in segment */
778     if (position > stop) {
779       /* negative value */
780       result = position - stop;
781       res = -1;
782     } else {
783       result = stop - position;
784       res = 1;
785     }
786   }
787 
788   if (running_time) {
789     /* scale based on the rate, avoid division by and conversion to
790      * float when not needed */
791     abs_rate = ABS (segment->rate);
792     if (G_UNLIKELY (abs_rate != 1.0))
793       result /= abs_rate;
794 
795     /* correct for base of the segment */
796     if (res == 1)
797       /* positive, add base */
798       *running_time = result + segment->base;
799     else if (segment->base >= result) {
800       /* negative and base is bigger, subtract from base and we have a
801        * positive value again */
802       *running_time = segment->base - result;
803       res = 1;
804     } else {
805       /* negative and base is smaller, subtract base and remainder is
806        * negative */
807       *running_time = result - segment->base;
808     }
809   }
810   return res;
811 
812 done:
813   {
814     if (running_time)
815       *running_time = -1;
816     return 0;
817   }
818 }
819 
820 /**
821  * gst_segment_to_running_time:
822  * @segment: a #GstSegment structure.
823  * @format: the format of the segment.
824  * @position: the position in the segment
825  *
826  * Translate @position to the total running time using the currently configured
827  * segment. Position is a value between @segment start and stop time.
828  *
829  * This function is typically used by elements that need to synchronize to the
830  * global clock in a pipeline. The running time is a constantly increasing value
831  * starting from 0. When gst_segment_init() is called, this value will reset to
832  * 0.
833  *
834  * This function returns -1 if the position is outside of @segment start and stop.
835  *
836  * Returns: the position as the total running time or -1 when an invalid position
837  * was given.
838  */
839 guint64
gst_segment_to_running_time(const GstSegment * segment,GstFormat format,guint64 position)840 gst_segment_to_running_time (const GstSegment * segment, GstFormat format,
841     guint64 position)
842 {
843   guint64 result;
844 
845   g_return_val_if_fail (segment != NULL, -1);
846   g_return_val_if_fail (segment->format == format, -1);
847 
848   /* before the segment boundary */
849   if (G_UNLIKELY (position < segment->start)) {
850     GST_DEBUG ("position(%" G_GUINT64_FORMAT ") < start(%" G_GUINT64_FORMAT
851         ")", position, segment->start);
852     return -1;
853   }
854   /* after the segment boundary */
855   if (G_UNLIKELY (segment->stop != -1 && position > segment->stop)) {
856     GST_DEBUG ("position(%" G_GUINT64_FORMAT ") > stop(%" G_GUINT64_FORMAT
857         ")", position, segment->stop);
858     return -1;
859   }
860 
861   if (gst_segment_to_running_time_full (segment, format, position,
862           &result) == 1)
863     return result;
864 
865   return -1;
866 }
867 
868 /**
869  * gst_segment_clip:
870  * @segment: a #GstSegment structure.
871  * @format: the format of the segment.
872  * @start: the start position in the segment
873  * @stop: the stop position in the segment
874  * @clip_start: (out) (allow-none): the clipped start position in the segment
875  * @clip_stop: (out) (allow-none): the clipped stop position in the segment
876  *
877  * Clip the given @start and @stop values to the segment boundaries given
878  * in @segment. @start and @stop are compared and clipped to @segment
879  * start and stop values.
880  *
881  * If the function returns %FALSE, @start and @stop are known to fall
882  * outside of @segment and @clip_start and @clip_stop are not updated.
883  *
884  * When the function returns %TRUE, @clip_start and @clip_stop will be
885  * updated. If @clip_start or @clip_stop are different from @start or @stop
886  * respectively, the region fell partially in the segment.
887  *
888  * Note that when @stop is -1, @clip_stop will be set to the end of the
889  * segment. Depending on the use case, this may or may not be what you want.
890  *
891  * Returns: %TRUE if the given @start and @stop times fall partially or
892  *     completely in @segment, %FALSE if the values are completely outside
893  *     of the segment.
894  */
895 gboolean
gst_segment_clip(const GstSegment * segment,GstFormat format,guint64 start,guint64 stop,guint64 * clip_start,guint64 * clip_stop)896 gst_segment_clip (const GstSegment * segment, GstFormat format, guint64 start,
897     guint64 stop, guint64 * clip_start, guint64 * clip_stop)
898 {
899   g_return_val_if_fail (segment != NULL, FALSE);
900   g_return_val_if_fail (segment->format == format, FALSE);
901 
902   /* if we have a stop position and a valid start and start is bigger,
903    * we're outside of the segment. (Special case) segment start and
904    * segment stop can be identical. In this case, if start is also identical,
905    * it's inside of segment */
906   if (G_UNLIKELY (segment->stop != -1 && start != -1 && (start > segment->stop
907               || (segment->start != segment->stop && start == segment->stop))))
908     return FALSE;
909 
910   /* if a stop position is given and is before the segment start,
911    * we're outside of the segment. Special case is were start
912    * and stop are equal to the segment start. In that case we
913    * are inside the segment. */
914   if (G_UNLIKELY (stop != -1 && (stop < segment->start || (start != stop
915                   && stop == segment->start))))
916     return FALSE;
917 
918   if (clip_start) {
919     if (start == -1)
920       *clip_start = -1;
921     else
922       *clip_start = MAX (start, segment->start);
923   }
924 
925   if (clip_stop) {
926     if (stop == -1)
927       *clip_stop = segment->stop;
928     else if (segment->stop == -1)
929       *clip_stop = stop;
930     else
931       *clip_stop = MIN (stop, segment->stop);
932   }
933 
934   return TRUE;
935 }
936 
937 /**
938  * gst_segment_position_from_running_time:
939  * @segment: a #GstSegment structure.
940  * @format: the format of the segment.
941  * @running_time: the running_time in the segment
942  *
943  * Convert @running_time into a position in the segment so that
944  * gst_segment_to_running_time() with that position returns @running_time.
945  *
946  * Returns: the position in the segment for @running_time. This function returns
947  * -1 when @running_time is -1 or when it is not inside @segment.
948  *
949  * Since: 1.8
950  */
951 guint64
gst_segment_position_from_running_time(const GstSegment * segment,GstFormat format,guint64 running_time)952 gst_segment_position_from_running_time (const GstSegment * segment,
953     GstFormat format, guint64 running_time)
954 {
955   guint64 position;
956   gint res;
957 
958   g_return_val_if_fail (segment != NULL, -1);
959   g_return_val_if_fail (segment->format == format, -1);
960 
961   res =
962       gst_segment_position_from_running_time_full (segment, format,
963       running_time, &position);
964 
965   if (res != 1)
966     return -1;
967 
968   /* before the segment boundary */
969   if (G_UNLIKELY (position < segment->start)) {
970     GST_DEBUG ("position(%" G_GUINT64_FORMAT ") < start(%" G_GUINT64_FORMAT
971         ")", position, segment->start);
972     return -1;
973   }
974 
975   /* after the segment boundary */
976   if (G_UNLIKELY (segment->stop != -1 && position > segment->stop)) {
977     GST_DEBUG ("position(%" G_GUINT64_FORMAT ") > stop(%" G_GUINT64_FORMAT
978         ")", position, segment->stop);
979     return -1;
980   }
981 
982   return position;
983 }
984 
985 /**
986  * gst_segment_position_from_running_time_full:
987  * @segment: a #GstSegment structure.
988  * @format: the format of the segment.
989  * @running_time: the running-time
990  * @position: (out): the resulting position in the segment
991  *
992  * Translate @running_time to the segment position using the currently configured
993  * segment. Compared to gst_segment_position_from_running_time() this function can
994  * return negative segment position.
995  *
996  * This function is typically used by elements that need to synchronize buffers
997  * against the clock or each other.
998  *
999  * @running_time can be any value and the result of this function for values
1000  * outside of the segment is extrapolated.
1001  *
1002  * When 1 is returned, @running_time resulted in a positive position returned
1003  * in @position.
1004  *
1005  * When this function returns -1, the returned @position was < 0, and the value
1006  * in the position variable should be negated to get the real negative segment
1007  * position.
1008  *
1009  * Returns: a 1 or -1 on success, 0 on failure.
1010  *
1011  * Since: 1.8
1012  */
1013 gint
gst_segment_position_from_running_time_full(const GstSegment * segment,GstFormat format,guint64 running_time,guint64 * position)1014 gst_segment_position_from_running_time_full (const GstSegment * segment,
1015     GstFormat format, guint64 running_time, guint64 * position)
1016 {
1017   gint res;
1018   guint64 start, stop, base;
1019   gdouble abs_rate;
1020 
1021   if (G_UNLIKELY (running_time == -1)) {
1022     *position = -1;
1023     return 0;
1024   }
1025 
1026   g_return_val_if_fail (segment != NULL, 0);
1027   g_return_val_if_fail (segment->format == format, 0);
1028 
1029   base = segment->base;
1030 
1031   abs_rate = ABS (segment->rate);
1032 
1033   start = segment->start;
1034   stop = segment->stop;
1035 
1036   if (G_LIKELY (segment->rate > 0.0)) {
1037     /* start by subtracting the base time */
1038     if (G_LIKELY (running_time >= base)) {
1039       *position = running_time - base;
1040       /* move into the segment at the right rate */
1041       if (G_UNLIKELY (abs_rate != 1.0))
1042         *position = ceil (*position * abs_rate);
1043       /* bring to corrected position in segment */
1044       *position += start + segment->offset;
1045       res = 1;
1046     } else {
1047       *position = base - running_time;
1048       if (G_UNLIKELY (abs_rate != 1.0))
1049         *position = ceil (*position * abs_rate);
1050       if (start + segment->offset >= *position) {
1051         /* The TS is before the segment, but the result is >= 0 */
1052         *position = start + segment->offset - *position;
1053         res = 1;
1054       } else {
1055         /* The TS is before the segment, and the result is < 0
1056          * so negate the return result */
1057         *position = *position - (start + segment->offset);
1058         res = -1;
1059       }
1060     }
1061   } else {
1062     if (G_LIKELY (running_time >= base)) {
1063       *position = running_time - base;
1064       if (G_UNLIKELY (abs_rate != 1.0))
1065         *position = ceil (*position * abs_rate);
1066       if (G_UNLIKELY (stop < *position + segment->offset)) {
1067         *position += segment->offset - stop;
1068         res = -1;
1069       } else {
1070         *position = stop - *position - segment->offset;
1071         res = 1;
1072       }
1073     } else {
1074       /* This case is tricky. Requested running time precedes the
1075        * segment base, so in a reversed segment where rate < 0, that
1076        * means it's before the alignment point of (stop - offset).
1077        * Before = always bigger than (stop-offset), which is usually +ve,
1078        * but could be -ve is offset is big enough. -ve position implies
1079        * that the offset has clipped away the entire segment anyway */
1080       *position = base - running_time;
1081       if (G_UNLIKELY (abs_rate != 1.0))
1082         *position = ceil (*position * abs_rate);
1083 
1084       if (G_LIKELY (stop + *position >= segment->offset)) {
1085         *position = stop + *position - segment->offset;
1086         res = 1;
1087       } else {
1088         /* Requested position is still negative because offset is big,
1089          * so negate the result */
1090         *position = segment->offset - *position - stop;
1091         res = -1;
1092       }
1093     }
1094   }
1095   return res;
1096 }
1097 
1098 /**
1099  * gst_segment_to_position:
1100  * @segment: a #GstSegment structure.
1101  * @format: the format of the segment.
1102  * @running_time: the running_time in the segment
1103  *
1104  * Convert @running_time into a position in the segment so that
1105  * gst_segment_to_running_time() with that position returns @running_time.
1106  *
1107  * Returns: the position in the segment for @running_time. This function returns
1108  * -1 when @running_time is -1 or when it is not inside @segment.
1109  *
1110  * Deprecated: Use gst_segment_position_from_running_time() instead.
1111  */
1112 #ifndef GST_REMOVE_DEPRECATED
1113 guint64
gst_segment_to_position(const GstSegment * segment,GstFormat format,guint64 running_time)1114 gst_segment_to_position (const GstSegment * segment, GstFormat format,
1115     guint64 running_time)
1116 {
1117   return gst_segment_position_from_running_time (segment, format, running_time);
1118 }
1119 #endif
1120 
1121 /**
1122  * gst_segment_set_running_time:
1123  * @segment: a #GstSegment structure.
1124  * @format: the format of the segment.
1125  * @running_time: the running_time in the segment
1126  *
1127  * Adjust the start/stop and base values of @segment such that the next valid
1128  * buffer will be one with @running_time.
1129  *
1130  * Returns: %TRUE if the segment could be updated successfully. If %FALSE is
1131  * returned, @running_time is -1 or not in @segment.
1132  */
1133 gboolean
gst_segment_set_running_time(GstSegment * segment,GstFormat format,guint64 running_time)1134 gst_segment_set_running_time (GstSegment * segment, GstFormat format,
1135     guint64 running_time)
1136 {
1137   guint64 position;
1138   guint64 start, stop;
1139 
1140   /* start by bringing the running_time into the segment position */
1141   position =
1142       gst_segment_position_from_running_time (segment, format, running_time);
1143 
1144   /* we must have a valid position now */
1145   if (G_UNLIKELY (position == -1))
1146     return FALSE;
1147 
1148   start = segment->start;
1149   stop = segment->stop;
1150 
1151   if (G_LIKELY (segment->rate > 0.0)) {
1152     /* update the start and time values */
1153     start = position;
1154   } else {
1155     /* reverse, update stop */
1156     stop = position;
1157   }
1158   /* and base time is exactly the running time */
1159   segment->time = gst_segment_to_stream_time (segment, format, start);
1160   segment->start = start;
1161   segment->stop = stop;
1162   segment->base = running_time;
1163 
1164   return TRUE;
1165 }
1166 
1167 /**
1168  * gst_segment_offset_running_time:
1169  * @segment: a #GstSegment structure.
1170  * @format: the format of the segment.
1171  * @offset: the offset to apply in the segment
1172  *
1173  * Adjust the values in @segment so that @offset is applied to all
1174  * future running-time calculations.
1175  *
1176  * Since: 1.2.3
1177  *
1178  * Returns: %TRUE if the segment could be updated successfully. If %FALSE is
1179  * returned, @offset is not in @segment.
1180  */
1181 gboolean
gst_segment_offset_running_time(GstSegment * segment,GstFormat format,gint64 offset)1182 gst_segment_offset_running_time (GstSegment * segment, GstFormat format,
1183     gint64 offset)
1184 {
1185   g_return_val_if_fail (segment != NULL, FALSE);
1186   g_return_val_if_fail (segment->format == format, FALSE);
1187 
1188   if (offset == 0)
1189     return TRUE;
1190 
1191   if (offset > 0) {
1192     /* positive offset, we can simply apply to the base time */
1193     segment->base += offset;
1194   } else {
1195     offset = -offset;
1196     /* negative offset, first try to subtract from base */
1197     if (segment->base > offset) {
1198       segment->base -= offset;
1199     } else {
1200       guint64 position;
1201 
1202       /* subtract all from segment.base, remainder in offset */
1203       offset -= segment->base;
1204       segment->base = 0;
1205       position =
1206           gst_segment_position_from_running_time (segment, format, offset);
1207       if (position == -1)
1208         return FALSE;
1209 
1210       segment->offset = position - segment->start;
1211     }
1212   }
1213   return TRUE;
1214 }
1215 
1216 /**
1217  * gst_segment_is_equal:
1218  * @s0: a #GstSegment structure.
1219  * @s1: a #GstSegment structure.
1220  *
1221  * Checks for two segments being equal. Equality here is defined
1222  * as perfect equality, including floating point values.
1223  *
1224  * Since: 1.6
1225  *
1226  * Returns: %TRUE if the segments are equal, %FALSE otherwise.
1227  */
1228 gboolean
gst_segment_is_equal(const GstSegment * s0,const GstSegment * s1)1229 gst_segment_is_equal (const GstSegment * s0, const GstSegment * s1)
1230 {
1231   if (s0->flags != s1->flags)
1232     return FALSE;
1233   if (s0->rate != s1->rate)
1234     return FALSE;
1235   if (s0->applied_rate != s1->applied_rate)
1236     return FALSE;
1237   if (s0->format != s1->format)
1238     return FALSE;
1239   if (s0->base != s1->base)
1240     return FALSE;
1241   if (s0->offset != s1->offset)
1242     return FALSE;
1243   if (s0->start != s1->start)
1244     return FALSE;
1245   if (s0->stop != s1->stop)
1246     return FALSE;
1247   if (s0->time != s1->time)
1248     return FALSE;
1249   if (s0->position != s1->position)
1250     return FALSE;
1251   if (s0->duration != s1->duration)
1252     return FALSE;
1253   return TRUE;
1254 }
1255