Expose gst_segment_copy() to make things easier for the c++ bindings.
[platform/upstream/gstreamer.git] / gst / gstsegment.c
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., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22
23 #include "gst_private.h"
24
25 #include "gstutils.h"
26 #include "gstsegment.h"
27
28 /**
29  * SECTION:gstsegment
30  * @short_description: Structure describing the configured region of interest
31  *                     in a media file.
32  * @see_also: #GstEvent
33  *
34  * This helper structure holds the relevant values for tracking the region of
35  * interest in a media file, called a segment. 
36  *
37  * The structure can be used for two purposes:
38  * <itemizedlist>
39  *   <listitem><para>performing seeks (handling seek events)</para></listitem>
40  *   <listitem><para>tracking playback regions (handling newsegment events)</para></listitem>
41  * </itemizedlist>
42  *
43  * The segment is usually configured by the application with a seek event which 
44  * is propagated upstream and eventually handled by an element that performs the seek.
45  *
46  * The configured segment is then propagated back downstream with a newsegment event.
47  * This information is then used to clip media to the segment boundaries.
48  *
49  * A segment structure is initialized with gst_segment_init(), which takes a #GstFormat
50  * that will be used as the format of the segment values. The segment will be configured
51  * with a start value of 0 and a stop/duration of -1, which is undefined. The default
52  * rate and applied_rate is 1.0.
53  *
54  * If the segment is used for managing seeks, the segment duration should be set with
55  * gst_segment_set_duration(). The public duration field contains the duration of the
56  * segment. When using the segment for seeking, the start and time members should 
57  * normally be left to their default 0 value. The stop position is left to -1 unless
58  * explicitly configured to a different value after a seek event.
59  *
60  * The current position in the segment should be set with the gst_segment_set_last_stop().
61  * The public last_stop field contains the last set stop position in the segment.
62  *
63  * For elements that perform seeks, the current segment should be updated with the
64  * gst_segment_set_seek() and the values from the seek event. This method will update
65  * all the segment fields. The last_stop field will contain the new playback position.
66  * If the cur_type was different from GST_SEEK_TYPE_NONE, playback continues from
67  * the last_stop position, possibly with updated flags or rate.
68  *
69  * For elements that want to use #GstSegment to track the playback region, use
70  * gst_segment_set_newsegment() to update the segment fields with the information from
71  * the newsegment event. 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 all accumulated segments 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  * Last reviewed on 2007-05-17 (0.10.13)
84  */
85
86 /**
87  * gst_segment_copy:
88  * @segment: a #GstSegment
89  *
90  * Returns: a copy of @segment, free with gst_segment_free().
91  *
92  * Since: 0.10.20
93  */
94 GstSegment *
95 gst_segment_copy (GstSegment * segment)
96 {
97   GstSegment *result = NULL;
98
99   if (segment) {
100     result = gst_segment_new ();
101     memcpy (result, segment, sizeof (GstSegment));
102   }
103   return result;
104 }
105
106 GType
107 gst_segment_get_type (void)
108 {
109   static GType gst_segment_type = 0;
110
111   if (G_UNLIKELY (gst_segment_type == 0)) {
112     gst_segment_type = g_boxed_type_register_static ("GstSegment",
113         (GBoxedCopyFunc) gst_segment_copy, (GBoxedFreeFunc) gst_segment_free);
114   }
115
116   return gst_segment_type;
117 }
118
119 /**
120  * gst_segment_new:
121  *
122  * Allocate a new #GstSegment structure and initialize it using 
123  * gst_segment_init().
124  *
125  * Returns: a new #GstSegment, free with gst_segment_free().
126  */
127 GstSegment *
128 gst_segment_new (void)
129 {
130   GstSegment *result;
131
132   result = g_slice_new0 (GstSegment);
133   gst_segment_init (result, GST_FORMAT_UNDEFINED);
134
135   return result;
136 }
137
138 /**
139  * gst_segment_free:
140  * @segment: a #GstSegment
141  *
142  * Free the allocated segment @segment.
143  */
144 void
145 gst_segment_free (GstSegment * segment)
146 {
147   g_slice_free (GstSegment, segment);
148 }
149
150 /**
151  * gst_segment_init:
152  * @segment: a #GstSegment structure.
153  * @format: the format of the segment.
154  *
155  * The start/last_stop positions are set to 0 and the stop/duration
156  * fields are set to -1 (unknown). The default rate of 1.0 and no
157  * flags are set.
158  *
159  * Initialize @segment to its default values.
160  */
161 void
162 gst_segment_init (GstSegment * segment, GstFormat format)
163 {
164   g_return_if_fail (segment != NULL);
165
166   segment->rate = 1.0;
167   segment->abs_rate = 1.0;
168   segment->applied_rate = 1.0;
169   segment->format = format;
170   segment->flags = 0;
171   segment->start = 0;
172   segment->stop = -1;
173   segment->time = 0;
174   segment->accum = 0;
175   segment->last_stop = 0;
176   segment->duration = -1;
177 }
178
179 /**
180  * gst_segment_set_duration:
181  * @segment: a #GstSegment structure.
182  * @format: the format of the segment.
183  * @duration: the duration of the segment info or -1 if unknown.
184  *
185  * Set the duration of the segment to @duration. This function is mainly
186  * used by elements that perform seeking and know the total duration of the
187  * segment. 
188  * 
189  * This field should be set to allow seeking requests relative to the
190  * duration.
191  */
192 void
193 gst_segment_set_duration (GstSegment * segment, GstFormat format,
194     gint64 duration)
195 {
196   g_return_if_fail (segment != NULL);
197
198   if (G_UNLIKELY (segment->format == GST_FORMAT_UNDEFINED))
199     segment->format = format;
200   else
201     g_return_if_fail (segment->format == format);
202
203   segment->duration = duration;
204 }
205
206 /**
207  * gst_segment_set_last_stop:
208  * @segment: a #GstSegment structure.
209  * @format: the format of the segment.
210  * @position: the position 
211  *
212  * Set the last observed stop position in the segment to @position.
213  *
214  * This field should be set to allow seeking requests relative to the
215  * current playing position.
216  */
217 void
218 gst_segment_set_last_stop (GstSegment * segment, GstFormat format,
219     gint64 position)
220 {
221   g_return_if_fail (segment != NULL);
222
223   if (G_UNLIKELY (segment->format == GST_FORMAT_UNDEFINED))
224     segment->format = format;
225   else
226     g_return_if_fail (segment->format == format);
227
228   segment->last_stop = MAX (segment->start, position);
229 }
230
231 /**
232  * gst_segment_set_seek:
233  * @segment: a #GstSegment structure.
234  * @rate: the rate of the segment.
235  * @format: the format of the segment.
236  * @flags: the seek flags for the segment
237  * @start_type: the seek method
238  * @start: the seek start value
239  * @stop_type: the seek method
240  * @stop: the seek stop value
241  * @update: boolean holding whether last_stop was updated.
242  *
243  * Update the segment structure with the field values of a seek event (see
244  * gst_event_new_seek()).
245  *
246  * After calling this method, the segment field last_stop and time will
247  * contain the requested new position in the segment. The new requested
248  * position in the segment depends on @rate and @start_type and @stop_type. 
249  *
250  * For positive @rate, the new position in the segment is the new @segment
251  * start field when it was updated with a @start_type different from
252  * #GST_SEEK_TYPE_NONE. If no update was performed on @segment start position
253  * (#GST_SEEK_TYPE_NONE), @start is ignored and @segment last_stop is
254  * unmodified.
255  *
256  * For negative @rate, the new position in the segment is the new @segment
257  * stop field when it was updated with a @stop_type different from
258  * #GST_SEEK_TYPE_NONE. If no stop was previously configured in the segment, the
259  * duration of the segment will be used to update the stop position.
260  * If no update was performed on @segment stop position (#GST_SEEK_TYPE_NONE),
261  * @stop is ignored and @segment last_stop is unmodified.
262  *
263  * The applied rate of the segment will be set to 1.0 by default.
264  * If the caller can apply a rate change, it should update @segment
265  * rate and applied_rate after calling this function.
266  *
267  * @update will be set to TRUE if a seek should be performed to the segment 
268  * last_stop field. This field can be FALSE if, for example, only the @rate
269  * has been changed but not the playback position.
270  */
271 void
272 gst_segment_set_seek (GstSegment * segment, gdouble rate,
273     GstFormat format, GstSeekFlags flags,
274     GstSeekType start_type, gint64 start,
275     GstSeekType stop_type, gint64 stop, gboolean * update)
276 {
277   gboolean update_stop, update_start;
278   gint64 last_stop;
279
280   g_return_if_fail (rate != 0.0);
281   g_return_if_fail (segment != NULL);
282
283   if (G_UNLIKELY (segment->format == GST_FORMAT_UNDEFINED))
284     segment->format = format;
285
286   update_start = update_stop = TRUE;
287
288   /* segment->start is never invalid */
289   switch (start_type) {
290     case GST_SEEK_TYPE_NONE:
291       /* no update to segment, take previous start */
292       start = segment->start;
293       update_start = FALSE;
294       break;
295     case GST_SEEK_TYPE_SET:
296       /* start holds desired position, map -1 to the start */
297       if (start == -1)
298         start = 0;
299       /* start must be 0 or the formats must match */
300       g_return_if_fail (start == 0 || segment->format == format);
301       break;
302     case GST_SEEK_TYPE_CUR:
303       g_return_if_fail (start == 0 || segment->format == format);
304       /* add start to currently configured segment */
305       start = segment->start + start;
306       break;
307     case GST_SEEK_TYPE_END:
308       if (segment->duration != -1) {
309         g_return_if_fail (start == 0 || segment->format == format);
310         /* add start to total length */
311         start = segment->duration + start;
312       } else {
313         /* no update if duration unknown */
314         start = segment->start;
315         update_start = FALSE;
316       }
317       break;
318   }
319   /* bring in sane range */
320   if (segment->duration != -1)
321     start = CLAMP (start, 0, segment->duration);
322   else
323     start = MAX (start, 0);
324
325   /* stop can be -1 if we have not configured a stop. */
326   switch (stop_type) {
327     case GST_SEEK_TYPE_NONE:
328       stop = segment->stop;
329       update_stop = FALSE;
330       break;
331     case GST_SEEK_TYPE_SET:
332       /* stop holds required value, if it's not -1, it must be of the same
333        * format as the segment. */
334       g_return_if_fail (stop == -1 || segment->format == format);
335       break;
336     case GST_SEEK_TYPE_CUR:
337       if (segment->stop != -1) {
338         /* only add compatible formats or 0 */
339         g_return_if_fail (stop == 0 || segment->format == format);
340         stop = segment->stop + stop;
341       } else
342         stop = -1;
343       break;
344     case GST_SEEK_TYPE_END:
345       if (segment->duration != -1) {
346         /* only add compatible formats or 0 */
347         g_return_if_fail (stop == 0 || segment->format == format);
348         stop = segment->duration + stop;
349       } else {
350         stop = segment->stop;
351         update_stop = FALSE;
352       }
353       break;
354   }
355
356   /* if we have a valid stop time, make sure it is clipped */
357   if (stop != -1) {
358     if (segment->duration != -1)
359       stop = CLAMP (stop, 0, segment->duration);
360     else
361       stop = MAX (stop, 0);
362   }
363
364   /* we can't have stop before start */
365   if (stop != -1)
366     g_return_if_fail (start <= stop);
367
368   segment->rate = rate;
369   segment->abs_rate = ABS (rate);
370   segment->applied_rate = 1.0;
371   segment->flags = flags;
372   segment->start = start;
373   segment->stop = stop;
374   segment->time = start;
375
376   last_stop = segment->last_stop;
377   if (update_start && rate > 0.0) {
378     last_stop = start;
379   }
380   if (update_stop && rate < 0.0) {
381     if (stop != -1)
382       last_stop = stop;
383     else {
384       if (segment->duration != -1)
385         last_stop = segment->duration;
386       else
387         last_stop = 0;
388     }
389   }
390   /* set update arg to reflect update of last_stop */
391   if (update)
392     *update = last_stop != segment->last_stop;
393
394   /* update new position */
395   segment->last_stop = last_stop;
396 }
397
398 /**
399  * gst_segment_set_newsegment:
400  * @segment: a #GstSegment structure.
401  * @update: flag indicating a new segment is started or updated
402  * @rate: the rate of the segment.
403  * @format: the format of the segment.
404  * @start: the new start value
405  * @stop: the new stop value
406  * @time: the new stream time
407  *
408  * Update the segment structure with the field values of a new segment event and
409  * with a default applied_rate of 1.0.
410  *
411  * Since: 0.10.6
412  */
413 void
414 gst_segment_set_newsegment (GstSegment * segment, gboolean update, gdouble rate,
415     GstFormat format, gint64 start, gint64 stop, gint64 time)
416 {
417   gst_segment_set_newsegment_full (segment, update, rate, 1.0, format, start,
418       stop, time);
419 }
420
421 /**
422  * gst_segment_set_newsegment_full:
423  * @segment: a #GstSegment structure.
424  * @update: flag indicating a new segment is started or updated
425  * @rate: the rate of the segment.
426  * @applied_rate: the applied rate of the segment.
427  * @format: the format of the segment.
428  * @start: the new start value
429  * @stop: the new stop value
430  * @time: the new stream time
431  *
432  * Update the segment structure with the field values of a new segment event.
433  */
434 void
435 gst_segment_set_newsegment_full (GstSegment * segment, gboolean update,
436     gdouble rate, gdouble applied_rate, GstFormat format, gint64 start,
437     gint64 stop, gint64 time)
438 {
439   gint64 duration;
440
441   g_return_if_fail (rate != 0.0);
442   g_return_if_fail (applied_rate != 0.0);
443   g_return_if_fail (segment != NULL);
444
445   if (G_UNLIKELY (segment->format == GST_FORMAT_UNDEFINED))
446     segment->format = format;
447
448   /* any other format with 0 also gives time 0, the other values are
449    * invalid in the format though. */
450   if (format != segment->format && start == 0) {
451     format = segment->format;
452     if (stop != 0)
453       stop = -1;
454     if (time != 0)
455       time = -1;
456   }
457
458   g_return_if_fail (segment->format == format);
459
460   if (update) {
461     if (segment->rate > 0.0) {
462       /* an update to the current segment is done, elapsed time is
463        * difference between the old start and new start. */
464       if (start > segment->start)
465         duration = start - segment->start;
466       else
467         duration = 0;
468     } else {
469       /* for negative rates, the elapsed duration is the diff between the stop
470        * positions */
471       if (stop != -1 && stop < segment->stop)
472         duration = segment->stop - stop;
473       else
474         duration = 0;
475     }
476   } else {
477     /* the new segment has to be aligned with the old segment.
478      * We first update the accumulated time of the previous
479      * segment. the accumulated time is used when syncing to the
480      * clock. 
481      */
482     if (segment->stop != -1) {
483       duration = segment->stop - segment->start;
484     } else if (segment->last_stop != -1) {
485       /* else use last seen timestamp as segment stop */
486       duration = segment->last_stop - segment->start;
487     } else {
488       /* else we don't know and throw a warning.. really, this should
489        * be fixed in the element. */
490       g_warning ("closing segment of unknown duration, assuming duration of 0");
491       duration = 0;
492     }
493   }
494   /* use previous rate to calculate duration */
495   if (segment->abs_rate != 1.0)
496     duration /= segment->abs_rate;
497
498   /* accumulate duration */
499   segment->accum += duration;
500
501   /* then update the current segment */
502   segment->rate = rate;
503   segment->abs_rate = ABS (rate);
504   segment->applied_rate = applied_rate;
505   segment->start = start;
506   segment->last_stop = start;
507   segment->stop = stop;
508   segment->time = time;
509 }
510
511 /**
512  * gst_segment_to_stream_time:
513  * @segment: a #GstSegment structure.
514  * @format: the format of the segment.
515  * @position: the position in the segment
516  *
517  * Translate @position to stream time using the currently configured 
518  * segment. The @position value must be between @segment start and
519  * stop value. 
520  *
521  * This function is typically used by elements that need to operate on
522  * the stream time of the buffers it receives, such as effect plugins.
523  * In those use cases, @position is typically the buffer timestamp or 
524  * clock time that one wants to convert to the stream time.
525  * The stream time is always between 0 and the total duration of the 
526  * media stream. 
527  *
528  * Returns: the position in stream_time or -1 when an invalid position
529  * was given.
530  */
531 gint64
532 gst_segment_to_stream_time (GstSegment * segment, GstFormat format,
533     gint64 position)
534 {
535   gint64 result, start, stop, time;
536   gdouble abs_applied_rate;
537
538   g_return_val_if_fail (segment != NULL, -1);
539
540   /* format does not matter for -1 */
541   if (G_UNLIKELY (position == -1))
542     return -1;
543
544   if (G_UNLIKELY (segment->format == GST_FORMAT_UNDEFINED))
545     segment->format = format;
546
547   /* if we have the position for the same format as the segment, we can compare
548    * the start and stop values, otherwise we assume 0 and -1 */
549   if (segment->format == format) {
550     start = segment->start;
551     stop = segment->stop;
552     time = segment->time;
553   } else {
554     start = 0;
555     stop = -1;
556     time = 0;
557   }
558
559   /* outside of the segment boundary stop */
560   if (G_UNLIKELY (stop != -1 && position > stop))
561     return -1;
562
563   /* before the segment boundary */
564   if (G_UNLIKELY (position < start))
565     return -1;
566
567   /* time must be known */
568   if (G_UNLIKELY (time == -1))
569     return -1;
570
571   /* bring to uncorrected position in segment */
572   result = position - start;
573
574   abs_applied_rate = ABS (segment->applied_rate);
575
576   /* correct for applied rate if needed */
577   if (abs_applied_rate != 1.0)
578     result *= abs_applied_rate;
579
580   /* add or subtract from segment time based on applied rate */
581   if (segment->applied_rate > 0.0) {
582     /* correct for segment time */
583     result += time;
584   } else {
585     /* correct for segment time, clamp at 0. Streams with a negative
586      * applied_rate have timestamps between start and stop, as usual, but have
587      * the time member starting high and going backwards.  */
588     if (time > result)
589       result = time - result;
590     else
591       result = 0;
592   }
593
594   return result;
595 }
596
597 /**
598  * gst_segment_to_running_time:
599  * @segment: a #GstSegment structure.
600  * @format: the format of the segment.
601  * @position: the position in the segment
602  *
603  * Translate @position to the total running time using the currently configured 
604  * and previously accumulated segments. Position is a value between @segment
605  * start and stop time.
606  *
607  * This function is typically used by elements that need to synchronize to the
608  * global clock in a pipeline. The runnning time is a constantly increasing value
609  * starting from 0. When gst_segment_init() is called, this value will reset to
610  * 0.
611  *
612  * This function returns -1 if the position is outside of @segment start and stop.
613  *
614  * Returns: the position as the total running time or -1 when an invalid position
615  * was given.
616  */
617 gint64
618 gst_segment_to_running_time (GstSegment * segment, GstFormat format,
619     gint64 position)
620 {
621   gint64 result;
622   gint64 start, stop, accum;
623
624   g_return_val_if_fail (segment != NULL, -1);
625
626   if (G_UNLIKELY (position == -1))
627     return -1;
628
629   if (G_UNLIKELY (segment->format == GST_FORMAT_UNDEFINED))
630     segment->format = format;
631
632   /* if we have the position for the same format as the segment, we can compare
633    * the start and stop values, otherwise we assume 0 and -1 */
634   if (segment->format == format) {
635     start = segment->start;
636     stop = segment->stop;
637     accum = segment->accum;
638   } else {
639     start = 0;
640     stop = -1;
641     accum = 0;
642   }
643
644   /* before the segment boundary */
645   if (G_UNLIKELY (position < start))
646     return -1;
647
648   if (segment->rate > 0.0) {
649     /* outside of the segment boundary stop */
650     if (G_UNLIKELY (stop != -1 && position > stop))
651       return -1;
652
653     /* bring to uncorrected position in segment */
654     result = position - start;
655   } else {
656     /* cannot continue if no stop position set or outside of
657      * the segment. */
658     if (G_UNLIKELY (stop == -1 || position > stop))
659       return -1;
660
661     /* bring to uncorrected position in segment */
662     result = stop - position;
663   }
664
665   /* scale based on the rate, avoid division by and conversion to 
666    * float when not needed */
667   if (segment->abs_rate != 1.0)
668     result /= segment->abs_rate;
669
670   /* correct for accumulated segments */
671   result += accum;
672
673   return result;
674 }
675
676 /**
677  * gst_segment_clip:
678  * @segment: a #GstSegment structure.
679  * @format: the format of the segment.
680  * @start: the start position in the segment
681  * @stop: the stop position in the segment
682  * @clip_start: the clipped start position in the segment
683  * @clip_stop: the clipped stop position in the segment
684  *
685  * Clip the given @start and @stop values to the segment boundaries given
686  * in @segment. @start and @stop are compared and clipped to @segment 
687  * start and stop values.
688  *
689  * If the function returns FALSE, @start and @stop are known to fall
690  * outside of @segment and @clip_start and @clip_stop are not updated.
691  *
692  * When the function returns TRUE, @clip_start and @clip_stop will be
693  * updated. If @clip_start or @clip_stop are different from @start or @stop
694  * respectively, the region fell partially in the segment.
695  *
696  * Note that when @stop is -1, @clip_stop will be set to the end of the
697  * segment. Depending on the use case, this may or may not be what you want.
698  *
699  * Returns: TRUE if the given @start and @stop times fall partially or 
700  *     completely in @segment, FALSE if the values are completely outside 
701  *     of the segment.
702  */
703 gboolean
704 gst_segment_clip (GstSegment * segment, GstFormat format, gint64 start,
705     gint64 stop, gint64 * clip_start, gint64 * clip_stop)
706 {
707   g_return_val_if_fail (segment != NULL, FALSE);
708
709   if (G_UNLIKELY (segment->format == GST_FORMAT_UNDEFINED))
710     segment->format = format;
711   else
712     g_return_val_if_fail (segment->format == format, FALSE);
713
714   /* if we have a stop position and a valid start and start is bigger, 
715    * we're outside of the segment */
716   if (G_UNLIKELY (segment->stop != -1 && start != -1 && start >= segment->stop))
717     return FALSE;
718
719   /* if a stop position is given and is before the segment start,
720    * we're outside of the segment */
721   if (G_UNLIKELY (stop != -1 && stop != start && stop <= segment->start))
722     return FALSE;
723
724   if (clip_start) {
725     if (start == -1)
726       *clip_start = -1;
727     else
728       *clip_start = MAX (start, segment->start);
729   }
730
731   if (clip_stop) {
732     if (stop == -1)
733       *clip_stop = segment->stop;
734     else if (segment->stop == -1)
735       *clip_stop = MAX (-1, stop);
736     else
737       *clip_stop = MIN (stop, segment->stop);
738
739     if (segment->duration != -1)
740       *clip_stop = MIN (*clip_stop, segment->duration);
741   }
742
743   return TRUE;
744 }