78da72204caa0dcd4994dbf829438615fbd2ca62
[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 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.
57  *
58  * The current position in the segment should be set with the gst_segment_set_last_stop().
59  * The public last_stop field contains the last set stop position in the segment.
60  *
61  * For elements that perform seeks, the current segment should be updated with the
62  * gst_segment_set_seek() and the values from the seek event. This method will update
63  * all the segment fields. The last_pos field will contain the new playback position.
64  * If the cur_type was different from GST_SEEK_TYPE_NONE, playback continues from
65  * the last_pos position, possibly with updated flags or rate.
66  *
67  * For elements that want to use #GstSegment to track the playback region, use
68  * gst_segment_set_newsegment() to update the segment fields with the information from
69  * the newsegment event. The gst_segment_clip() method can be used to check and clip
70  * the media data to the segment boundaries.
71  *
72  * For elements that want to synchronize to the pipeline clock, gst_segment_to_running_time()
73  * can be used to convert a timestamp to a value that can be used to synchronize
74  * to the clock. This function takes into account all accumulated segments.
75  *
76  * For elements that need to perform operations on media data in stream_time, 
77  * gst_segment_to_stream_time() can be used to convert a timestamp and the segment
78  * info to stream time (which is always between 0 and the duration of the stream).
79  *
80  * Last reviewed on 2006-03-12 (0.10.5)
81  */
82
83 static GstSegment *
84 gst_segment_copy (GstSegment * segment)
85 {
86   GstSegment *result = NULL;
87
88   if (segment) {
89     result = gst_segment_new ();
90     memcpy (result, segment, sizeof (GstSegment));
91   }
92   return NULL;
93 }
94
95 GType
96 gst_segment_get_type (void)
97 {
98   static GType gst_segment_type = 0;
99
100   if (G_UNLIKELY (gst_segment_type == 0)) {
101     gst_segment_type = g_boxed_type_register_static ("GstSegment",
102         (GBoxedCopyFunc) gst_segment_copy, (GBoxedFreeFunc) gst_segment_free);
103   }
104
105   return gst_segment_type;
106 }
107
108 /**
109  * gst_segment_new:
110  *
111  * Allocate a new #GstSegment structure and initialize it using 
112  * gst_segment_init().
113  *
114  * Returns: a new #GstSegment, free with gst_segment_free().
115  */
116 GstSegment *
117 gst_segment_new (void)
118 {
119   GstSegment *result;
120
121   result = g_new0 (GstSegment, 1);
122   gst_segment_init (result, GST_FORMAT_UNDEFINED);
123
124   return result;
125 }
126
127 /**
128  * gst_segment_free:
129  * @segment: a #GstSegment
130  *
131  * Free the allocated segment @segment.
132  */
133 void
134 gst_segment_free (GstSegment * segment)
135 {
136   g_free (segment);
137 }
138
139 /**
140  * gst_segment_init:
141  * @segment: a #GstSegment structure.
142  * @format: the format of the segment.
143  *
144  * The start/last_stop positions are set to 0 and the stop/duration
145  * fields are set to -1 (unknown). The default rate of 1.0 and no
146  * flags are set.
147  *
148  * Initialize @segment to its default values.
149  */
150 void
151 gst_segment_init (GstSegment * segment, GstFormat format)
152 {
153   g_return_if_fail (segment != NULL);
154
155   segment->rate = 1.0;
156   segment->abs_rate = 1.0;
157   segment->format = format;
158   segment->flags = 0;
159   segment->start = 0;
160   segment->stop = -1;
161   segment->time = 0;
162   segment->accum = 0;
163   segment->last_stop = 0;
164   segment->duration = -1;
165 }
166
167 /**
168  * gst_segment_set_duration:
169  * @segment: a #GstSegment structure.
170  * @format: the format of the segment.
171  * @duration: the duration of the segment info.
172  *
173  * Set the duration of the segment to @duration. This function is mainly
174  * used by elements that perform seeking and know the total duration of the
175  * segment.
176  * 
177  * This field should be set to allow seeking requests relative to the
178  * duration.
179  */
180 void
181 gst_segment_set_duration (GstSegment * segment, GstFormat format,
182     gint64 duration)
183 {
184   g_return_if_fail (segment != NULL);
185
186   if (segment->format == GST_FORMAT_UNDEFINED)
187     segment->format = format;
188   else
189     g_return_if_fail (segment->format == format);
190
191   segment->duration = duration;
192 }
193
194 /**
195  * gst_segment_set_last_stop:
196  * @segment: a #GstSegment structure.
197  * @format: the format of the segment.
198  * @position: the position 
199  *
200  * Set the last observed stop position in the segment to @position.
201  *
202  * This field should be set to allow seeking requests relative to the
203  * current playing position.
204  */
205 void
206 gst_segment_set_last_stop (GstSegment * segment, GstFormat format,
207     gint64 position)
208 {
209   g_return_if_fail (segment != NULL);
210
211   if (segment->format == GST_FORMAT_UNDEFINED)
212     segment->format = format;
213   else
214     g_return_if_fail (segment->format == format);
215
216   segment->last_stop = MAX (segment->start, position);
217 }
218
219 /**
220  * gst_segment_set_seek:
221  * @segment: a #GstSegment structure.
222  * @rate: the rate of the segment.
223  * @format: the format of the segment.
224  * @flags: the seek flags for the segment
225  * @cur_type: the seek method
226  * @cur: the seek start value
227  * @stop_type: the seek method
228  * @stop: the seek stop value
229  * @update: boolean holding whether start or stop were updated.
230  *
231  * Update the segment structure with the field values of a seek event.
232  *
233  * After calling this method, the segment field last_stop will contain
234  * the requested new position in the segment. If the cur_type is different
235  * from GST_SEEK_TYPE_NONE, the current position is not updated and 
236  * streaming should continue from the last position, possibly with
237  * updated rate, flags or stop position.
238  */
239 void
240 gst_segment_set_seek (GstSegment * segment, gdouble rate,
241     GstFormat format, GstSeekFlags flags,
242     GstSeekType cur_type, gint64 cur,
243     GstSeekType stop_type, gint64 stop, gboolean * update)
244 {
245   gboolean update_stop, update_start;
246
247   g_return_if_fail (rate != 0.0);
248   g_return_if_fail (segment != NULL);
249
250   if (segment->format == GST_FORMAT_UNDEFINED)
251     segment->format = format;
252   else
253     g_return_if_fail (segment->format == format);
254
255   update_stop = update_start = TRUE;
256
257   /* start is never invalid */
258   switch (cur_type) {
259     case GST_SEEK_TYPE_NONE:
260       /* no update to segment */
261       cur = segment->start;
262       update_start = FALSE;
263       break;
264     case GST_SEEK_TYPE_SET:
265       /* cur holds desired position */
266       break;
267     case GST_SEEK_TYPE_CUR:
268       /* add cur to currently configure segment */
269       cur = segment->start + cur;
270       break;
271     case GST_SEEK_TYPE_END:
272       if (segment->duration != -1) {
273         /* add cur to total length */
274         cur = segment->duration + cur;
275       } else {
276         /* no update if duration unknown */
277         cur = segment->start;
278         update_start = FALSE;
279       }
280       break;
281   }
282   /* bring in sane range */
283   if (segment->duration != -1)
284     cur = CLAMP (cur, 0, segment->duration);
285   else
286     cur = MAX (cur, 0);
287
288   /* stop can be -1 if we have not configured a stop. */
289   switch (stop_type) {
290     case GST_SEEK_TYPE_NONE:
291       stop = segment->stop;
292       update_stop = FALSE;
293       break;
294     case GST_SEEK_TYPE_SET:
295       /* stop folds required value */
296       break;
297     case GST_SEEK_TYPE_CUR:
298       if (segment->stop != -1)
299         stop = segment->stop + stop;
300       else
301         stop = -1;
302       break;
303     case GST_SEEK_TYPE_END:
304       if (segment->duration != -1)
305         stop = segment->duration + stop;
306       else {
307         stop = segment->stop;
308         update_stop = FALSE;
309       }
310       break;
311   }
312
313   /* if we have a valid stop time, make sure it is clipped */
314   if (stop != -1) {
315     if (segment->duration != -1)
316       stop = CLAMP (stop, 0, segment->duration);
317     else
318       stop = MAX (stop, 0);
319   }
320
321   /* we can't have stop before start */
322   if (stop != -1)
323     g_return_if_fail (cur <= stop);
324
325   segment->rate = rate;
326   segment->abs_rate = ABS (rate);
327   segment->flags = flags;
328   segment->start = cur;
329   if (update_start) {
330     segment->last_stop = cur;
331   }
332   segment->time = segment->last_stop;
333   segment->stop = stop;
334
335   if (update)
336     *update = update_start || update_stop;
337 }
338
339 /**
340  * gst_segment_set_newsegment:
341  * @segment: a #GstSegment structure.
342  * @update: flag indicating a new segment is started or updated
343  * @rate: the rate of the segment.
344  * @format: the format of the segment.
345  * @start: the new start value
346  * @stop: the new stop value
347  * @time: the new stream time
348  *
349  * Update the segment structure with the field values of a new segment event.
350  */
351 void
352 gst_segment_set_newsegment (GstSegment * segment, gboolean update, gdouble rate,
353     GstFormat format, gint64 start, gint64 stop, gint64 time)
354 {
355   gint64 duration;
356
357   g_return_if_fail (rate != 0.0);
358   g_return_if_fail (segment != NULL);
359
360   if (segment->format == GST_FORMAT_UNDEFINED)
361     segment->format = format;
362
363   /* any other format with 0 also gives time 0, the other values are
364    * invalid in the format though. */
365   if (format != segment->format && start == 0) {
366     format = segment->format;
367     if (stop != 0)
368       stop = -1;
369     if (time != 0)
370       time = -1;
371   }
372
373   g_return_if_fail (segment->format == format);
374
375   if (update) {
376     /* an update to the current segment is done, elapsed time is
377      * difference between the old start and new start. */
378     duration = start - segment->start;
379   } else {
380     /* the new segment has to be aligned with the old segment.
381      * We first update the accumulated time of the previous
382      * segment. the accumulated time is used when syncing to the
383      * clock. 
384      */
385     if (GST_CLOCK_TIME_IS_VALID (segment->stop)) {
386       duration = segment->stop - segment->start;
387     } else if (GST_CLOCK_TIME_IS_VALID (segment->last_stop)) {
388       /* else use last seen timestamp as segment stop */
389       duration = segment->last_stop - segment->start;
390     } else {
391       /* else we don't know */
392       duration = 0;
393     }
394   }
395   /* use previous rate to calculate duration */
396   segment->accum += gst_gdouble_to_guint64 (
397       (gst_guint64_to_gdouble (duration) / segment->abs_rate));
398
399   /* then update the current segment */
400   segment->rate = rate;
401   segment->abs_rate = ABS (rate);
402   segment->start = start;
403   segment->last_stop = start;
404   segment->stop = stop;
405   segment->time = time;
406 }
407
408 /**
409  * gst_segment_to_stream_time:
410  * @segment: a #GstSegment structure.
411  * @format: the format of the segment.
412  * @position: the position in the segment
413  *
414  * Translate @position to stream time using the currently configured 
415  * segment. The @position value must be between @segment start and
416  * stop value. 
417  *
418  * This function is typically used by elements that need to operate on
419  * the stream time of the buffers it receives, such as effect plugins.
420  * In those use cases, @position is typically the buffer timestamp that
421  * one wants to convert to the stream time.
422  * The stream time is always between 0 and the total duration of the 
423  * media stream. 
424  *
425  * Returns: the position in stream_time or -1 when an invalid position
426  * was given.
427  */
428 gint64
429 gst_segment_to_stream_time (GstSegment * segment, GstFormat format,
430     gint64 position)
431 {
432   gint64 result, time;
433
434   g_return_val_if_fail (segment != NULL, -1);
435
436   if (segment->format == GST_FORMAT_UNDEFINED)
437     segment->format = format;
438   else
439     g_return_val_if_fail (segment->format == format, -1);
440
441   if ((time = segment->time) == -1)
442     time = 0;
443
444   if (position != -1 && position >= segment->start)
445     result = ((position - segment->start) / segment->abs_rate) + time;
446   else
447     result = -1;
448
449   return result;
450 }
451
452 /**
453  * gst_segment_to_running_time:
454  * @segment: a #GstSegment structure.
455  * @format: the format of the segment.
456  * @position: the position in the segment
457  *
458  * Translate @position to the total running time using the currently configured 
459  * and previously accumulated segments.
460  *
461  * This function is typically used by elements that need to synchronize to the
462  * global clock in a pipeline. The runnning time is a constantly increasing value
463  * starting from 0. When gst_segment_init() is called, this value will reset to
464  * 0.
465  *
466  * Returns: the position as the total running time.
467  */
468 gint64
469 gst_segment_to_running_time (GstSegment * segment, GstFormat format,
470     gint64 position)
471 {
472   gint64 result;
473
474   g_return_val_if_fail (segment != NULL, -1);
475
476   if (segment->format == GST_FORMAT_UNDEFINED)
477     segment->format = format;
478   else if (segment->accum)
479     g_return_val_if_fail (segment->format == format, -1);
480
481   if (position != -1 && position >= segment->start)
482     result = ((position - segment->start) / segment->abs_rate) + segment->accum;
483   else
484     result = -1;
485
486   return result;
487 }
488
489 /**
490  * gst_segment_clip:
491  * @segment: a #GstSegment structure.
492  * @format: the format of the segment.
493  * @start: the start position in the segment
494  * @stop: the stop position in the segment
495  * @clip_start: the clipped start position in the segment
496  * @clip_stop: the clipped stop position in the segment
497  *
498  * Clip the given @start and @stop values to the segment boundaries given
499  * in @segment.
500  *
501  * If the function returns FALSE, @start and @stop are known to fall
502  * outside of @segment and @clip_start and @clip_stop are not updated.
503  *
504  * When the function returns TRUE, @clip_start and @clip_stop will be
505  * updated. If @clip_start or @clip_stop are different from @start or @stop
506  * respectively, the region fell partially in the segment.
507  *
508  * Returns: TRUE if the given @start and @stop times fall partially or 
509  *     completely in @segment, FALSE if the values are completely outside 
510  *     of the segment.
511  */
512 gboolean
513 gst_segment_clip (GstSegment * segment, GstFormat format, gint64 start,
514     gint64 stop, gint64 * clip_start, gint64 * clip_stop)
515 {
516   g_return_val_if_fail (segment != NULL, FALSE);
517
518   if (segment->format == GST_FORMAT_UNDEFINED)
519     segment->format = format;
520   else
521     g_return_val_if_fail (segment->format == format, FALSE);
522
523   /* if we have a stop position and a valid start and start is bigger, 
524    * we're outside of the segment */
525   if (segment->stop != -1 && start != -1 && start >= segment->stop)
526     return FALSE;
527
528   /* if a stop position is given and is before the segment start,
529    * we're outside of the segment */
530   if (stop != -1 && stop <= segment->start)
531     return FALSE;
532
533   if (clip_start) {
534     if (start == -1)
535       *clip_start = -1;
536     else
537       *clip_start = MAX (start, segment->start);
538   }
539
540   if (clip_stop) {
541     if (stop == -1)
542       *clip_stop = segment->stop;
543     else if (segment->stop == -1)
544       *clip_stop = MAX (-1, stop);
545     else
546       *clip_stop = MIN (stop, segment->stop);
547
548     if (segment->duration != -1)
549       *clip_stop = MIN (*clip_stop, segment->duration);
550   }
551
552   return TRUE;
553 }