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