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