update plugin initialization restructuring (see email for details
[platform/upstream/gstreamer.git] / gst / elements / gstfilesrc.c
1 /* GStreamer
2  * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
3  *                    2000 Wim Taymans <wtay@chello.be>
4  *
5  * gstfilesrc.c:
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #  include "config.h"
25 #endif
26
27 #include <gst/gst.h>
28 #include "gstfilesrc.h"
29
30 #include <stdio.h>
31 #include <sys/stat.h>
32 #include <fcntl.h>
33 #include <unistd.h>
34 #include <sys/mman.h>
35 #include <errno.h>
36 #include <string.h>
37
38
39 /**********************************************************************
40  * GStreamer Default File Source
41  * Theory of Operation
42  *
43  * This source uses mmap(2) to efficiently load data from a file.
44  * To do this without seriously polluting the applications' memory
45  * space, it must do so in smaller chunks, say 1-4MB at a time.
46  * Buffers are then subdivided from these mmap'd chunks, to directly
47  * make use of the mmap.
48  *
49  * To handle refcounting so that the mmap can be freed at the appropriate
50  * time, a buffer will be created for each mmap'd region, and all new
51  * buffers will be sub-buffers of this top-level buffer.  As they are 
52  * freed, the refcount goes down on the mmap'd buffer and its free()
53  * function is called, which will call munmap(2) on itself.
54  *
55  * If a buffer happens to cross the boundaries of an mmap'd region, we
56  * have to decide whether it's more efficient to copy the data into a
57  * new buffer, or mmap() just that buffer.  There will have to be a
58  * breakpoint size to determine which will be done.  The mmap() size
59  * has a lot to do with this as well, because you end up in double-
60  * jeopardy: the larger the outgoing buffer, the more data to copy when
61  * it overlaps, *and* the more frequently you'll have buffers that *do*
62  * overlap.
63  *
64  * Seeking is another tricky aspect to do efficiently.  The initial
65  * implementation of this source won't make use of these features, however.
66  * The issue is that if an application seeks backwards in a file, *and*
67  * that region of the file is covered by an mmap that hasn't been fully
68  * deallocated, we really should re-use it.  But keeping track of these
69  * regions is tricky because we have to lock the structure that holds
70  * them.  We need to settle on a locking primitive (GMutex seems to be
71  * a really good option...), then we can do that.
72  */
73
74
75 GST_DEBUG_CATEGORY_STATIC (gst_filesrc_debug);
76 #define GST_CAT_DEFAULT gst_filesrc_debug
77
78 GstElementDetails gst_filesrc_details = GST_ELEMENT_DETAILS (
79   "File Source",
80   "Source/File",
81   "Read from arbitrary point in a file",
82   "Erik Walthinsen <omega@cse.ogi.edu>"
83 );
84
85 #define DEFAULT_BLOCKSIZE       4*1024
86 #define DEFAULT_MMAPSIZE        4*1024*1024
87
88 /* FileSrc signals and args */
89 enum {
90   /* FILL ME */
91   LAST_SIGNAL
92 };
93
94 enum {
95   ARG_0,
96   ARG_LOCATION,
97   ARG_FD,
98   ARG_BLOCKSIZE,
99   ARG_MMAPSIZE,
100   ARG_TOUCH,
101 };
102
103 GST_PAD_EVENT_MASK_FUNCTION (gst_filesrc_get_event_mask,
104   { GST_EVENT_SEEK, GST_SEEK_METHOD_CUR | 
105                     GST_SEEK_METHOD_SET | 
106                     GST_SEEK_METHOD_END | 
107                     GST_SEEK_FLAG_FLUSH },
108   { GST_EVENT_FLUSH, 0 },
109   { GST_EVENT_SIZE, 0 }
110 )
111
112 GST_PAD_QUERY_TYPE_FUNCTION (gst_filesrc_get_query_types,
113   GST_QUERY_TOTAL,
114   GST_QUERY_POSITION
115 )
116
117 GST_PAD_FORMATS_FUNCTION (gst_filesrc_get_formats,
118   GST_FORMAT_BYTES
119 )
120
121 static void             gst_filesrc_base_init           (gpointer g_class);
122 static void             gst_filesrc_class_init          (GstFileSrcClass *klass);
123 static void             gst_filesrc_init                (GstFileSrc *filesrc);
124 static void             gst_filesrc_dispose             (GObject *object);
125
126 static void             gst_filesrc_set_property        (GObject *object, guint prop_id, 
127                                                          const GValue *value, GParamSpec *pspec);
128 static void             gst_filesrc_get_property        (GObject *object, guint prop_id, 
129                                                          GValue *value, GParamSpec *pspec);
130
131 static GstData *        gst_filesrc_get                 (GstPad *pad);
132 static gboolean         gst_filesrc_srcpad_event        (GstPad *pad, GstEvent *event);
133 static gboolean         gst_filesrc_srcpad_query        (GstPad *pad, GstQueryType type,
134                                                          GstFormat *format, gint64 *value);
135
136 static GstElementStateReturn    gst_filesrc_change_state        (GstElement *element);
137
138
139 static GstElementClass *parent_class = NULL;
140 /*static guint gst_filesrc_signals[LAST_SIGNAL] = { 0 };*/
141
142 GType
143 gst_filesrc_get_type(void)
144 {
145   static GType filesrc_type = 0;
146
147   if (!filesrc_type) {
148     static const GTypeInfo filesrc_info = {
149       sizeof(GstFileSrcClass), 
150       gst_filesrc_base_init,
151       NULL,
152       (GClassInitFunc)gst_filesrc_class_init,
153       NULL,
154       NULL,
155       sizeof(GstFileSrc),
156       0,
157       (GInstanceInitFunc)gst_filesrc_init,
158     };
159     filesrc_type = g_type_register_static (GST_TYPE_ELEMENT, "GstFileSrc", &filesrc_info, 0);
160
161     GST_DEBUG_CATEGORY_INIT (gst_filesrc_debug, "filesrc", 0, "filesrc element");
162   }
163   return filesrc_type;
164 }
165
166 static void
167 gst_filesrc_base_init (gpointer g_class)
168 {
169   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class);
170
171   gst_element_class_set_details (gstelement_class, &gst_filesrc_details);
172 }
173 static void
174 gst_filesrc_class_init (GstFileSrcClass *klass)
175 {
176   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
177   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
178
179   gobject_class = (GObjectClass*)klass;
180
181   parent_class = g_type_class_peek_parent (klass);
182
183   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_FD,
184     g_param_spec_int ("fd", "File-descriptor", "File-descriptor for the file being mmap()d",
185                       0, G_MAXINT, 0, G_PARAM_READABLE));
186   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_LOCATION,
187     g_param_spec_string ("location", "File Location", "Location of the file to read",
188                          NULL, G_PARAM_READWRITE));
189   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_BLOCKSIZE,
190     g_param_spec_ulong ("blocksize", "Block size", "Size in bytes to read per buffer",
191                         1, G_MAXULONG, DEFAULT_BLOCKSIZE, G_PARAM_READWRITE));
192   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_MMAPSIZE,
193     g_param_spec_ulong ("mmapsize", "mmap() Block Size",
194                         "Size in bytes of mmap()d regions",
195                         0, G_MAXULONG, DEFAULT_MMAPSIZE, G_PARAM_READWRITE));
196   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_TOUCH,
197     g_param_spec_boolean ("touch", "Touch read data",
198                           "Touch data to force disk read", 
199                           FALSE, G_PARAM_READWRITE));
200
201   gobject_class->dispose        = gst_filesrc_dispose;
202   gobject_class->set_property   = gst_filesrc_set_property;
203   gobject_class->get_property   = gst_filesrc_get_property;
204
205   gstelement_class->change_state = gst_filesrc_change_state;
206 }
207
208 static gint
209 gst_filesrc_bufcmp (gconstpointer a, gconstpointer b)
210 {
211 /*  GstBuffer *bufa = (GstBuffer *)a, *bufb = (GstBuffer *)b;*/
212
213   /* sort first by offset, then in reverse by size */
214   if (GST_BUFFER_OFFSET(a) < GST_BUFFER_OFFSET(b)) return -1;
215   else if (GST_BUFFER_OFFSET(a) > GST_BUFFER_OFFSET(b)) return 1;
216   else if (GST_BUFFER_SIZE(a) > GST_BUFFER_SIZE(b)) return -1;
217   else if (GST_BUFFER_SIZE(a) < GST_BUFFER_SIZE(b)) return 1;
218   else return 0;
219 }
220
221 static void
222 gst_filesrc_init (GstFileSrc *src)
223 {
224   src->srcpad = gst_pad_new ("src", GST_PAD_SRC);
225   gst_pad_set_get_function (src->srcpad, gst_filesrc_get);
226   gst_pad_set_event_function (src->srcpad, gst_filesrc_srcpad_event);
227   gst_pad_set_event_mask_function (src->srcpad, gst_filesrc_get_event_mask);
228   gst_pad_set_query_function (src->srcpad, gst_filesrc_srcpad_query);
229   gst_pad_set_query_type_function (src->srcpad, gst_filesrc_get_query_types);
230   gst_pad_set_formats_function (src->srcpad, gst_filesrc_get_formats);
231   gst_element_add_pad (GST_ELEMENT (src), src->srcpad);
232
233   src->pagesize = getpagesize();
234
235   src->filename = NULL;
236   src->fd = 0;
237   src->filelen = 0;
238
239   src->curoffset = 0;
240   src->block_size = DEFAULT_BLOCKSIZE;
241   src->touch = FALSE;
242
243   src->mapbuf = NULL;
244   src->mapsize = DEFAULT_MMAPSIZE;              /* default is 4MB */
245
246   src->map_regions = g_tree_new (gst_filesrc_bufcmp);
247   src->map_regions_lock = g_mutex_new();
248
249   src->seek_happened = FALSE;
250 }
251
252 static void
253 gst_filesrc_dispose (GObject *object)
254 {
255   GstFileSrc *src;
256
257   src = GST_FILESRC (object);
258
259   G_OBJECT_CLASS (parent_class)->dispose (object);
260
261   g_tree_destroy (src->map_regions);
262   g_mutex_free (src->map_regions_lock);
263   if (src->filename)
264     g_free (src->filename);
265 }
266
267
268 static void
269 gst_filesrc_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
270 {
271   GstFileSrc *src;
272
273   /* it's not null if we got it, but it might not be ours */
274   g_return_if_fail (GST_IS_FILESRC (object));
275
276   src = GST_FILESRC (object);
277
278   switch (prop_id) {
279     case ARG_LOCATION:
280       /* the element must be stopped in order to do this */
281       g_return_if_fail (GST_STATE (src) < GST_STATE_PLAYING);
282
283       if (src->filename) g_free (src->filename);
284       /* clear the filename if we get a NULL (is that possible?) */
285       if (g_value_get_string (value) == NULL) {
286         gst_element_set_state (GST_ELEMENT (object), GST_STATE_NULL);
287         src->filename = NULL;
288       /* otherwise set the new filename */
289       } else {
290         src->filename = g_strdup (g_value_get_string (value));
291       }
292       g_object_notify (G_OBJECT (src), "location");
293       break;
294     case ARG_BLOCKSIZE:
295       src->block_size = g_value_get_ulong (value);
296       g_object_notify (G_OBJECT (src), "blocksize");
297       break;
298     case ARG_MMAPSIZE:
299       if ((src->mapsize % src->pagesize) == 0) {
300         src->mapsize = g_value_get_ulong (value);
301         g_object_notify (G_OBJECT (src), "mmapsize");
302       } else {
303         GST_INFO_OBJECT (src, "invalid mapsize, must a multiple of pagesize, which is %d", 
304                   src->pagesize);
305       }
306       break;
307     case ARG_TOUCH:
308       src->touch = g_value_get_boolean (value);
309       g_object_notify (G_OBJECT (src), "touch");
310       break;
311     default:
312       break;
313   }
314 }
315
316 static void
317 gst_filesrc_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
318 {
319   GstFileSrc *src;
320
321   /* it's not null if we got it, but it might not be ours */
322   g_return_if_fail (GST_IS_FILESRC (object));
323
324   src = GST_FILESRC (object);
325
326   switch (prop_id) {
327     case ARG_LOCATION:
328       g_value_set_string (value, src->filename);
329       break;
330     case ARG_FD:
331       g_value_set_int (value, src->fd);
332       break;
333     case ARG_BLOCKSIZE:
334       g_value_set_ulong (value, src->block_size);
335       break;
336     case ARG_MMAPSIZE:
337       g_value_set_ulong (value, src->mapsize);
338       break;
339     case ARG_TOUCH:
340       g_value_set_boolean (value, src->touch);
341       break;
342     default:
343       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
344       break;
345   }
346 }
347
348 static void
349 gst_filesrc_free_parent_mmap (GstBuffer *buf)
350 {
351   GstFileSrc *src = GST_FILESRC (GST_BUFFER_POOL_PRIVATE (buf));
352
353   GST_LOG_OBJECT (src, "freeing mmap()d buffer at %"G_GUINT64_FORMAT"+%u", 
354                   GST_BUFFER_OFFSET (buf), GST_BUFFER_SIZE (buf));
355
356   /* remove the buffer from the list of available mmap'd regions */
357   g_mutex_lock (src->map_regions_lock);
358   g_tree_remove (src->map_regions, buf);
359   /* check to see if the tree is empty */
360   if (g_tree_nnodes (src->map_regions) == 0) {
361     /* we have to free the bufferpool we don't have yet */
362   }
363   g_mutex_unlock (src->map_regions_lock);
364
365 #ifdef MADV_DONTNEED
366   /* madvise to tell the kernel what to do with it */
367   madvise (GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf), MADV_DONTNEED);
368 #endif
369   /* now unmap the memory */
370   munmap (GST_BUFFER_DATA (buf), GST_BUFFER_MAXSIZE (buf));
371   /* cast to unsigned long, since there's no gportable way to print
372    * guint64 as hex */
373   GST_LOG_OBJECT (src, "unmapped region %08lx+%08lx at %p", 
374                   (unsigned long) GST_BUFFER_OFFSET (buf),
375                   (unsigned long) GST_BUFFER_MAXSIZE (buf), 
376                   GST_BUFFER_DATA (buf));
377
378   GST_BUFFER_DATA (buf) = NULL;
379
380   g_object_unref (src);
381   gst_buffer_default_free (buf);
382 }
383
384 static GstBuffer *
385 gst_filesrc_map_region (GstFileSrc *src, off_t offset, size_t size)
386 {
387   GstBuffer *buf;
388   gint retval;
389   void *mmapregion;
390
391   g_return_val_if_fail (offset >= 0, NULL);
392
393   GST_LOG_OBJECT (src, "mapping region %08llx+%08lx from file into memory",offset,(unsigned long)size);
394   mmapregion = mmap (NULL, size, PROT_READ, MAP_SHARED, src->fd, offset);
395
396   if (mmapregion == NULL) {
397     gst_element_error (GST_ELEMENT (src), "couldn't map file");
398     return NULL;
399   }
400   else if (mmapregion == MAP_FAILED) {
401     GST_WARNING_OBJECT (src, "mmap (0x%08lx, %d, 0x%llx) failed: %s",
402              (unsigned long)size, src->fd, offset, strerror (errno));
403     return NULL;
404   }
405   GST_LOG_OBJECT (src, "mapped region %08lx+%08lx from file into memory at %p", 
406                   (unsigned long)offset, (unsigned long)size, mmapregion);
407
408   /* time to allocate a new mapbuf */
409   buf = gst_buffer_new ();
410   /* mmap() the data into this new buffer */
411   GST_BUFFER_DATA (buf) = mmapregion;
412
413 #ifdef MADV_SEQUENTIAL
414   /* madvise to tell the kernel what to do with it */
415   retval = madvise (GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf), MADV_SEQUENTIAL);
416 #endif
417   /* fill in the rest of the fields */
418   GST_BUFFER_FLAG_SET (buf, GST_BUFFER_READONLY);
419   GST_BUFFER_FLAG_SET (buf, GST_BUFFER_ORIGINAL);
420   GST_BUFFER_SIZE (buf) = size;
421   GST_BUFFER_MAXSIZE (buf) = size;
422   GST_BUFFER_OFFSET (buf) = offset;
423   GST_BUFFER_TIMESTAMP (buf) = GST_CLOCK_TIME_NONE;
424   GST_BUFFER_POOL_PRIVATE (buf) = src;
425   g_object_ref (src);
426   GST_BUFFER_FREE_FUNC (buf) = (GstDataFreeFunction) gst_filesrc_free_parent_mmap;
427
428   g_mutex_lock (src->map_regions_lock);
429   g_tree_insert (src->map_regions,buf,buf);
430   g_mutex_unlock (src->map_regions_lock);
431
432   return buf;
433 }
434
435 static GstBuffer *
436 gst_filesrc_map_small_region (GstFileSrc *src, off_t offset, size_t size)
437 {
438   size_t mapsize;
439   off_t mod, mapbase;
440   GstBuffer *map;
441
442 /*  printf("attempting to map a small buffer at %d+%d\n",offset,size); */
443
444   /* if the offset starts at a non-page boundary, we have to special case */
445   if ((mod = offset % src->pagesize)) {
446     GstBuffer *ret;
447
448     mapbase = offset - mod;
449     mapsize = ((size + mod + src->pagesize - 1) / src->pagesize) * src->pagesize;
450 /*    printf("not on page boundaries, resizing map to %d+%d\n",mapbase,mapsize);*/
451     map = gst_filesrc_map_region(src, mapbase, mapsize);
452     if (map == NULL)
453       return NULL;
454
455     ret = gst_buffer_create_sub (map, offset - mapbase, size);
456     GST_BUFFER_OFFSET (ret) = GST_BUFFER_OFFSET (map) + offset - mapbase;
457
458     gst_buffer_unref (map);
459
460     return ret;
461   }
462
463   return gst_filesrc_map_region(src,offset,size);
464 }
465
466 typedef struct {
467   off_t offset;
468   off_t size;
469 } GstFileSrcRegion;
470
471 /* This allows us to search for a potential mmap region. */
472 static gint
473 gst_filesrc_search_region_match (gpointer a, gpointer b)
474 {
475   GstFileSrcRegion *r = (GstFileSrcRegion *)b;
476
477   /* trying to walk b down the tree, current node is a */
478   if (r->offset < GST_BUFFER_OFFSET(a)) return -1;
479   else if (r->offset >= (GST_BUFFER_OFFSET(a) + GST_BUFFER_SIZE(a))) return 1;
480   else if ((r->offset + r->size) <= (GST_BUFFER_OFFSET(a) + GST_BUFFER_SIZE(a))) return 0;
481
482   return -2;
483 }
484
485 /**
486  * gst_filesrc_get_mmap:
487  * @pad: #GstPad to push a buffer from
488  *
489  * Push a new buffer from the filesrc at the current offset.
490  */
491 static GstBuffer *
492 gst_filesrc_get_mmap (GstFileSrc *src)
493 {
494   GstBuffer *buf = NULL, *map;
495   size_t readsize, mapsize;
496   off_t readend,mapstart,mapend;
497   GstFileSrcRegion region;
498   int i;
499
500   /* calculate end pointers so we don't have to do so repeatedly later */
501   readsize = src->block_size;
502   readend = src->curoffset + src->block_size;           /* note this is the byte *after* the read */
503   mapstart = GST_BUFFER_OFFSET (src->mapbuf);
504   mapsize = GST_BUFFER_SIZE (src->mapbuf);
505   mapend = mapstart + mapsize;                  /* note this is the byte *after* the map */
506
507   /* check to see if we're going to overflow the end of the file */
508   if (readend > src->filelen) {
509     readsize = src->filelen - src->curoffset;
510     readend = src->curoffset + readsize;
511   }
512
513   GST_LOG ("attempting to read %08lx, %08lx, %08lx, %08lx", 
514            (unsigned long)readsize, (unsigned long)readend,
515            (unsigned long)mapstart, (unsigned long)mapend);
516
517   /* if the start is past the mapstart */
518   if (src->curoffset >= mapstart) {
519     /* if the end is before the mapend, the buffer is in current mmap region... */
520     /* ('cause by definition if readend is in the buffer, so's readstart) */
521     if (readend <= mapend) {
522       GST_LOG_OBJECT (src, "read buf %llu+%d lives in current mapbuf %lld+%d, creating subbuffer of mapbuf",
523              src->curoffset, readsize, mapstart, mapsize);
524       buf = gst_buffer_create_sub (src->mapbuf, src->curoffset - mapstart,
525                                    readsize);
526       GST_BUFFER_OFFSET (buf) = src->curoffset;
527
528     /* if the start actually is within the current mmap region, map an overlap buffer */
529     } else if (src->curoffset < mapend) {
530       GST_LOG_OBJECT (src, "read buf %llu+%d starts in mapbuf %d+%d but ends outside, creating new mmap",
531              (unsigned long long) src->curoffset, (gint) readsize, (gint) mapstart, (gint) mapsize);
532       buf = gst_filesrc_map_small_region (src, src->curoffset, readsize);
533       if (buf == NULL)
534         return NULL;
535     }
536
537     /* the only other option is that buffer is totally outside, which means we search for it */
538
539   /* now we can assume that the start is *before* the current mmap region */
540   /* if the readend is past mapstart, we have two options */
541   } else if (readend >= mapstart) {
542     /* either the read buffer overlaps the start of the mmap region */
543     /* or the read buffer fully contains the current mmap region    */
544     /* either way, it's really not relevant, we just create a new region anyway*/
545     GST_LOG_OBJECT (src, "read buf %llu+%d starts before mapbuf %d+%d, but overlaps it",
546              (unsigned long long) src->curoffset, (gint) readsize, (gint) mapstart, (gint) mapsize);
547     buf = gst_filesrc_map_small_region (src, src->curoffset, readsize);
548     if (buf == NULL)
549       return NULL;
550   }
551
552   /* then deal with the case where the read buffer is totally outside */
553   if (buf == NULL) {
554     /* first check to see if there's a map that covers the right region already */
555     GST_LOG_OBJECT (src, "searching for mapbuf to cover %llu+%d",src->curoffset,readsize);
556     region.offset = src->curoffset;
557     region.size = readsize;
558     map = g_tree_search (src->map_regions,
559                          (GCompareFunc) gst_filesrc_search_region_match,
560                          (gpointer)&region);
561
562     /* if we found an exact match, subbuffer it */
563     if (map != NULL) {
564       GST_LOG_OBJECT (src, "found mapbuf at %"G_GUINT64_FORMAT"+%u, creating subbuffer",
565                       GST_BUFFER_OFFSET (map), GST_BUFFER_SIZE (map));
566       buf = gst_buffer_create_sub (map, src->curoffset - GST_BUFFER_OFFSET(map), readsize);
567       GST_BUFFER_OFFSET (buf) = src->curoffset;
568
569     /* otherwise we need to create something out of thin air */
570     } else {
571       /* if the read buffer crosses a mmap region boundary, create a one-off region */
572       if ((src->curoffset / src->mapsize) != (readend / src->mapsize)) {
573         GST_LOG_OBJECT (src, "read buf %llu+%d crosses a %d-byte boundary, creating a one-off",
574                src->curoffset,readsize,src->mapsize);
575         buf = gst_filesrc_map_small_region (src, src->curoffset, readsize);
576         if (buf == NULL)
577           return NULL;
578
579       /* otherwise we will create a new mmap region and set it to the default */
580       } else {
581         size_t mapsize;
582
583         off_t nextmap = src->curoffset - (src->curoffset % src->mapsize);
584         GST_LOG_OBJECT (src, "read buf %llu+%d in new mapbuf at %llu+%d, mapping and subbuffering",
585                src->curoffset, readsize, nextmap, src->mapsize);
586         /* first, we're done with the old mapbuf */
587         gst_buffer_unref(src->mapbuf);
588         mapsize = src->mapsize;
589
590         /* double the mapsize as long as the readsize is smaller */
591         while (readsize - (src->curoffset - nextmap) > mapsize) {
592           GST_LOG_OBJECT (src, "readsize smaller then mapsize %08x %d", readsize, mapsize);
593           mapsize <<=1;
594         }
595         /* create a new one */
596         src->mapbuf = gst_filesrc_map_region (src, nextmap, mapsize);
597         if (src->mapbuf == NULL)
598           return NULL;
599
600         /* subbuffer it */
601         buf = gst_buffer_create_sub (src->mapbuf, src->curoffset - nextmap, readsize);
602         GST_BUFFER_OFFSET (buf) = mapstart + src->curoffset - nextmap;
603       }
604     }
605   }
606
607   /* if we need to touch the buffer (to bring it into memory), do so */
608   if (src->touch) {
609     volatile guchar *p = GST_BUFFER_DATA (buf), c;
610
611     for (i=0; i < GST_BUFFER_SIZE (buf); i += src->pagesize)
612       c = p[i];
613   }
614
615   /* we're done, return the buffer */
616   src->curoffset += GST_BUFFER_SIZE(buf);
617   return buf;
618 }
619
620 static GstBuffer *
621 gst_filesrc_get_read (GstFileSrc *src)
622 {
623   GstBuffer *buf = NULL;
624   size_t readsize;
625   int ret;
626
627   readsize = src->block_size;
628   if (src->curoffset + readsize > src->filelen) {
629     readsize = src->filelen - src->curoffset;
630   }
631
632   buf = gst_buffer_new_and_alloc (readsize);
633   g_return_val_if_fail (buf != NULL, NULL);
634
635   ret = read (src->fd, GST_BUFFER_DATA (buf), readsize);
636   if (ret < 0){
637     gst_element_error (GST_ELEMENT (src), "reading file (%s)",
638         strerror (errno), NULL);
639     return NULL;
640   }
641   if (ret < readsize) {
642     gst_element_error (GST_ELEMENT (src), "unexpected end of file", NULL);
643     return NULL;
644   }
645
646   src->curoffset += readsize;
647
648   return buf;
649 }
650
651 static GstData *
652 gst_filesrc_get (GstPad *pad)
653 {
654   GstFileSrc *src;
655
656   g_return_val_if_fail (pad != NULL, NULL);
657   src = GST_FILESRC (gst_pad_get_parent (pad));
658   g_return_val_if_fail (GST_FLAG_IS_SET (src, GST_FILESRC_OPEN), NULL);
659
660   /* check for seek */
661   if (src->seek_happened) {
662     GstEvent *event;
663
664     src->seek_happened = FALSE;
665     GST_DEBUG_OBJECT (src, "sending discont");
666     event = gst_event_new_discontinuous (FALSE, GST_FORMAT_BYTES, src->curoffset, NULL);
667     src->need_flush = FALSE;
668     return GST_DATA (event);
669   }
670   /* check for flush */
671   if (src->need_flush) {
672     src->need_flush = FALSE;
673     GST_DEBUG_OBJECT (src, "sending flush");
674     return GST_DATA (gst_event_new_flush ());
675   }
676
677   /* check for EOF */
678   if (src->curoffset == src->filelen) {
679     GST_DEBUG_OBJECT (src, "eos %" G_GINT64_FORMAT" %" G_GINT64_FORMAT,
680                src->curoffset, src->filelen);
681     gst_element_set_eos (GST_ELEMENT (src));
682     return GST_DATA (gst_event_new (GST_EVENT_EOS));
683   }
684
685   if (src->using_mmap){
686     return GST_DATA (gst_filesrc_get_mmap (src));
687   }else{
688     return GST_DATA (gst_filesrc_get_read (src));
689   }
690 }
691
692 /* open the file and mmap it, necessary to go to READY state */
693 static gboolean 
694 gst_filesrc_open_file (GstFileSrc *src)
695 {
696   g_return_val_if_fail (!GST_FLAG_IS_SET (src ,GST_FILESRC_OPEN), FALSE);
697
698   GST_INFO_OBJECT (src, "opening file %s",src->filename);
699
700   /* open the file */
701   src->fd = open (src->filename, O_RDONLY);
702   if (src->fd < 0) {
703     gst_element_error (GST_ELEMENT (src), "opening file \"%s\" (%s)", 
704                        src->filename, strerror (errno), NULL);
705     return FALSE;
706   } else {
707     /* check if it is a regular file, otherwise bail out */
708     struct stat stat_results;
709
710     fstat(src->fd, &stat_results);
711
712     if (!S_ISREG(stat_results.st_mode)) {
713       gst_element_error (GST_ELEMENT (src), "opening file \"%s\" failed. it isn't a regular file", 
714                                         src->filename, NULL);
715       close(src->fd);
716       return FALSE;
717     }
718                 
719     /* find the file length */
720     src->filelen = stat_results.st_size;
721
722     /* allocate the first mmap'd region */
723     src->mapbuf = gst_filesrc_map_region (src, 0, src->mapsize);
724     if (src->mapbuf == NULL) {
725       src->using_mmap = FALSE;
726     }else{
727       src->using_mmap = TRUE;
728     }
729
730     src->curoffset = 0;
731
732     GST_FLAG_SET (src, GST_FILESRC_OPEN);
733   }
734   return TRUE;
735 }
736
737 /* unmap and close the file */
738 static void
739 gst_filesrc_close_file (GstFileSrc *src)
740 {
741   g_return_if_fail (GST_FLAG_IS_SET (src, GST_FILESRC_OPEN));
742
743   /* close the file */
744   close (src->fd);
745
746   /* zero out a lot of our state */
747   src->fd = 0;
748   src->filelen = 0;
749   src->curoffset = 0;
750
751   if (src->mapbuf) {
752     gst_buffer_unref (src->mapbuf);
753     src->mapbuf = NULL;
754   }
755
756   GST_FLAG_UNSET (src, GST_FILESRC_OPEN);
757 }
758
759
760 static GstElementStateReturn
761 gst_filesrc_change_state (GstElement *element)
762 {
763   GstFileSrc *src = GST_FILESRC(element);
764
765   switch (GST_STATE_TRANSITION (element)) {
766     case GST_STATE_NULL_TO_READY:
767       break;
768     case GST_STATE_READY_TO_NULL:
769       break;
770     case GST_STATE_READY_TO_PAUSED:
771       if (!GST_FLAG_IS_SET (element, GST_FILESRC_OPEN)) {
772         if (!gst_filesrc_open_file (GST_FILESRC (element)))
773           return GST_STATE_FAILURE;
774       }
775       break;
776     case GST_STATE_PAUSED_TO_READY:
777       if (GST_FLAG_IS_SET (element, GST_FILESRC_OPEN))
778         gst_filesrc_close_file (GST_FILESRC (element));
779       src->seek_happened = TRUE;
780       break;
781     default:
782       break;
783   }
784
785   if (GST_ELEMENT_CLASS (parent_class)->change_state)
786     return GST_ELEMENT_CLASS (parent_class)->change_state (element);
787
788   return GST_STATE_SUCCESS;
789 }
790
791 static gboolean
792 gst_filesrc_srcpad_query (GstPad *pad, GstQueryType type,
793                           GstFormat *format, gint64 *value)
794 {
795   GstFileSrc *src = GST_FILESRC (GST_PAD_PARENT (pad));
796
797   switch (type) {
798     case GST_QUERY_TOTAL:
799       if (*format != GST_FORMAT_BYTES) {
800         return FALSE;
801       }
802       *value = src->filelen;
803       break;
804     case GST_QUERY_POSITION:
805       switch (*format) {
806         case GST_FORMAT_BYTES:
807           *value = src->curoffset;
808           break;
809         case GST_FORMAT_PERCENT:
810           if (src->filelen == 0)
811             return FALSE;
812           *value = src->curoffset * GST_FORMAT_PERCENT_MAX / src->filelen;
813           break;
814         default:
815           return FALSE;
816       }
817       break;
818     default:
819       return FALSE;
820       break;
821   }
822   return TRUE;
823 }
824
825 static gboolean
826 gst_filesrc_srcpad_event (GstPad *pad, GstEvent *event)
827 {
828   GstFileSrc *src = GST_FILESRC (GST_PAD_PARENT (pad));
829
830   GST_DEBUG_OBJECT (src, "event %d", GST_EVENT_TYPE (event));
831
832   switch (GST_EVENT_TYPE (event)) {
833     case GST_EVENT_SEEK:
834     {
835       gint64 offset;
836
837       if (GST_EVENT_SEEK_FORMAT (event) != GST_FORMAT_BYTES) {
838         goto error;
839       }
840
841       offset = GST_EVENT_SEEK_OFFSET (event);
842
843       switch (GST_EVENT_SEEK_METHOD (event)) {
844         case GST_SEEK_METHOD_SET:
845           if (offset > src->filelen) 
846             goto error;
847           src->curoffset = offset;
848           GST_DEBUG_OBJECT (src, "seek set pending to %" G_GINT64_FORMAT, src->curoffset);
849           break;
850         case GST_SEEK_METHOD_CUR:
851           if (offset + src->curoffset > src->filelen) 
852             goto error;
853           src->curoffset += offset;
854           GST_DEBUG_OBJECT (src, "seek cur pending to %" G_GINT64_FORMAT, src->curoffset);
855           break;
856         case GST_SEEK_METHOD_END:
857           if (ABS (offset) > src->filelen) 
858             goto error;
859           src->curoffset = src->filelen - ABS (offset);
860           GST_DEBUG_OBJECT (src, "seek end pending to %" G_GINT64_FORMAT, src->curoffset);
861           break;
862         default:
863           goto error;
864           break;
865       }
866       src->seek_happened = TRUE;
867       src->need_flush = GST_EVENT_SEEK_FLAGS(event) & GST_SEEK_FLAG_FLUSH;
868       break;
869     }
870     case GST_EVENT_SIZE:
871       if (GST_EVENT_SIZE_FORMAT (event) != GST_FORMAT_BYTES) {
872         goto error;
873       }
874       src->block_size = GST_EVENT_SIZE_VALUE (event);
875       g_object_notify (G_OBJECT (src), "blocksize");  
876       break;
877     case GST_EVENT_FLUSH:
878       src->need_flush = TRUE;
879       break;
880     default:
881       goto error;
882       break;
883   }
884   gst_event_unref (event);
885   return TRUE;
886
887 error:
888   gst_event_unref (event);
889   return FALSE;
890 }