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