GST_DEBUG reorganization containing loads of stuff:
[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_element_error (GST_ELEMENT (src), "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:
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 (GstPad *pad)
496 {
497   GstFileSrc *src;
498   GstBuffer *buf = NULL, *map;
499   size_t readsize, mapsize;
500   off_t readend,mapstart,mapend;
501   GstFileSrcRegion region;
502   int i;
503
504   g_return_val_if_fail (pad != NULL, NULL);
505   src = GST_FILESRC (gst_pad_get_parent (pad));
506   g_return_val_if_fail (GST_FLAG_IS_SET (src, GST_FILESRC_OPEN), NULL);
507
508   /* check for seek */
509   if (src->seek_happened) {
510     GstEvent *event;
511
512     src->seek_happened = FALSE;
513     GST_DEBUG ("filesrc sending discont");
514     event = gst_event_new_discontinuous (FALSE, GST_FORMAT_BYTES, src->curoffset, NULL);
515     src->need_flush = FALSE;
516     return GST_BUFFER (event);
517   }
518   /* check for flush */
519   if (src->need_flush) {
520     src->need_flush = FALSE;
521     GST_DEBUG ("filesrc sending flush");
522     return GST_BUFFER (gst_event_new_flush ());
523   }
524
525   /* check for EOF */
526   if (src->curoffset == src->filelen) {
527     GST_DEBUG ("filesrc eos %" G_GINT64_FORMAT" %" G_GINT64_FORMAT,
528                src->curoffset, src->filelen);
529     gst_element_set_eos (GST_ELEMENT (src));
530     return GST_BUFFER (gst_event_new (GST_EVENT_EOS));
531   }
532
533   /* calculate end pointers so we don't have to do so repeatedly later */
534   readsize = src->block_size;
535   readend = src->curoffset + src->block_size;           /* note this is the byte *after* the read */
536   mapstart = GST_BUFFER_OFFSET (src->mapbuf);
537   mapsize = GST_BUFFER_SIZE (src->mapbuf);
538   mapend = mapstart + mapsize;                  /* note this is the byte *after* the map */
539
540   /* check to see if we're going to overflow the end of the file */
541   if (readend > src->filelen) {
542     readsize = src->filelen - src->curoffset;
543     readend = src->curoffset + readsize;
544   }
545
546   GST_LOG ("attempting to read %08lx, %08lx, %08lx, %08lx", 
547            (unsigned long)readsize, (unsigned long)readend,
548            (unsigned long)mapstart, (unsigned long)mapend);
549
550   /* if the start is past the mapstart */
551   if (src->curoffset >= mapstart) {
552     /* if the end is before the mapend, the buffer is in current mmap region... */
553     /* ('cause by definition if readend is in the buffer, so's readstart) */
554     if (readend <= mapend) {
555       fs_print ("read buf %llu+%d lives in current mapbuf %lld+%d, creating subbuffer of mapbuf\n",
556              src->curoffset, readsize, mapstart, mapsize);
557       buf = gst_buffer_create_sub (src->mapbuf, src->curoffset - mapstart,
558                                    readsize);
559       GST_BUFFER_OFFSET (buf) = src->curoffset;
560
561     /* if the start actually is within the current mmap region, map an overlap buffer */
562     } else if (src->curoffset < mapend) {
563       fs_print ("read buf %llu+%d starts in mapbuf %d+%d but ends outside, creating new mmap\n",
564              src->curoffset, readsize, mapstart, mapsize);
565       buf = gst_filesrc_map_small_region (src, src->curoffset, readsize);
566       if (buf == NULL)
567         return NULL;
568     }
569
570     /* the only other option is that buffer is totally outside, which means we search for it */
571
572   /* now we can assume that the start is *before* the current mmap region */
573   /* if the readend is past mapstart, we have two options */
574   } else if (readend >= mapstart) {
575     /* either the read buffer overlaps the start of the mmap region */
576     /* or the read buffer fully contains the current mmap region    */
577     /* either way, it's really not relevant, we just create a new region anyway*/
578     fs_print ("read buf %llu+%d starts before mapbuf %d+%d, but overlaps it\n",
579              src->curoffset,readsize, mapstart, mapsize);
580     buf = gst_filesrc_map_small_region (src, src->curoffset, readsize);
581     if (buf == NULL)
582       return NULL;
583   }
584
585   /* then deal with the case where the read buffer is totally outside */
586   if (buf == NULL) {
587     /* first check to see if there's a map that covers the right region already */
588     fs_print ("searching for mapbuf to cover %llu+%d\n",src->curoffset,readsize);
589     region.offset = src->curoffset;
590     region.size = readsize;
591     map = g_tree_search (src->map_regions,
592                          (GCompareFunc) gst_filesrc_search_region_match,
593                          (gpointer)&region);
594
595     /* if we found an exact match, subbuffer it */
596     if (map != NULL) {
597       fs_print ("found mapbuf at %d+%d, creating subbuffer\n",GST_BUFFER_OFFSET(map),GST_BUFFER_SIZE(map));
598       buf = gst_buffer_create_sub (map, src->curoffset - GST_BUFFER_OFFSET(map), readsize);
599       GST_BUFFER_OFFSET (buf) = src->curoffset;
600
601     /* otherwise we need to create something out of thin air */
602     } else {
603       /* if the read buffer crosses a mmap region boundary, create a one-off region */
604       if ((src->curoffset / src->mapsize) != (readend / src->mapsize)) {
605         fs_print ("read buf %llu+%d crosses a %d-byte boundary, creating a one-off\n",
606                src->curoffset,readsize,src->mapsize);
607         buf = gst_filesrc_map_small_region (src, src->curoffset, readsize);
608         if (buf == NULL)
609           return NULL;
610
611       /* otherwise we will create a new mmap region and set it to the default */
612       } else {
613         size_t mapsize;
614
615         off_t nextmap = src->curoffset - (src->curoffset % src->mapsize);
616         fs_print ("read buf %llu+%d in new mapbuf at %llu+%d, mapping and subbuffering\n",
617                src->curoffset, readsize, nextmap, src->mapsize);
618         /* first, we're done with the old mapbuf */
619         gst_buffer_unref(src->mapbuf);
620         mapsize = src->mapsize;
621
622         /* double the mapsize as long as the readsize is smaller */
623         while (readsize - (src->curoffset - nextmap) > mapsize) {
624           fs_print ("readsize smaller then mapsize %08x %d\n", readsize, mapsize);
625           mapsize <<=1;
626         }
627         /* create a new one */
628         src->mapbuf = gst_filesrc_map_region (src, nextmap, mapsize);
629         if (src->mapbuf == NULL)
630           return NULL;
631
632         /* subbuffer it */
633         buf = gst_buffer_create_sub (src->mapbuf, src->curoffset - nextmap, readsize);
634         GST_BUFFER_OFFSET (buf) = mapstart + src->curoffset - nextmap;
635       }
636     }
637   }
638
639   /* if we need to touch the buffer (to bring it into memory), do so */
640   if (src->touch) {
641     volatile guchar *p = GST_BUFFER_DATA (buf), c;
642
643     for (i=0; i < GST_BUFFER_SIZE (buf); i += src->pagesize)
644       c = p[i];
645   }
646
647   /* we're done, return the buffer */
648   src->curoffset += GST_BUFFER_SIZE(buf);
649   return buf;
650 }
651
652 /* open the file and mmap it, necessary to go to READY state */
653 static gboolean 
654 gst_filesrc_open_file (GstFileSrc *src)
655 {
656   g_return_val_if_fail (!GST_FLAG_IS_SET (src ,GST_FILESRC_OPEN), FALSE);
657
658   GST_DEBUG ( "opening file %s",src->filename);
659
660   /* open the file */
661   src->fd = open (src->filename, O_RDONLY);
662   if (src->fd < 0) {
663     gst_element_error (GST_ELEMENT (src), "opening file \"%s\" (%s)", 
664                        src->filename, strerror (errno), NULL);
665     return FALSE;
666   } else {
667     /* check if it is a regular file, otherwise bail out */
668     struct stat stat_results;
669
670     fstat(src->fd, &stat_results);
671
672     if (!S_ISREG(stat_results.st_mode)) {
673       gst_element_error (GST_ELEMENT (src), "opening file \"%s\" failed. it isn't a regular file", 
674                                         src->filename, NULL);
675       close(src->fd);
676       return FALSE;
677     }
678                 
679     /* find the file length */
680     src->filelen = lseek (src->fd, 0, SEEK_END);
681     lseek (src->fd, 0, SEEK_SET);
682
683     /* allocate the first mmap'd region */
684     src->mapbuf = gst_filesrc_map_region (src, 0, src->mapsize);
685     if (src->mapbuf == NULL)
686       return FALSE;
687
688     src->curoffset = 0;
689
690     GST_FLAG_SET (src, GST_FILESRC_OPEN);
691   }
692   return TRUE;
693 }
694
695 /* unmap and close the file */
696 static void
697 gst_filesrc_close_file (GstFileSrc *src)
698 {
699   g_return_if_fail (GST_FLAG_IS_SET (src, GST_FILESRC_OPEN));
700
701   /* close the file */
702   close (src->fd);
703
704   /* zero out a lot of our state */
705   src->fd = 0;
706   src->filelen = 0;
707   src->curoffset = 0;
708
709   if (src->mapbuf) {
710     gst_buffer_unref (src->mapbuf);
711     src->mapbuf = NULL;
712   }
713
714   GST_FLAG_UNSET (src, GST_FILESRC_OPEN);
715 }
716
717
718 static GstElementStateReturn
719 gst_filesrc_change_state (GstElement *element)
720 {
721   GstFileSrc *src = GST_FILESRC(element);
722
723   switch (GST_STATE_TRANSITION (element)) {
724     case GST_STATE_NULL_TO_READY:
725       break;
726     case GST_STATE_READY_TO_NULL:
727       break;
728     case GST_STATE_READY_TO_PAUSED:
729       if (!GST_FLAG_IS_SET (element, GST_FILESRC_OPEN)) {
730         if (!gst_filesrc_open_file (GST_FILESRC (element)))
731           return GST_STATE_FAILURE;
732       }
733       break;
734     case GST_STATE_PAUSED_TO_READY:
735       if (GST_FLAG_IS_SET (element, GST_FILESRC_OPEN))
736         gst_filesrc_close_file (GST_FILESRC (element));
737       src->seek_happened = TRUE;
738       break;
739     default:
740       break;
741   }
742
743   if (GST_ELEMENT_CLASS (parent_class)->change_state)
744     return GST_ELEMENT_CLASS (parent_class)->change_state (element);
745
746   return GST_STATE_SUCCESS;
747 }
748
749 static gboolean
750 gst_filesrc_srcpad_query (GstPad *pad, GstQueryType type,
751                           GstFormat *format, gint64 *value)
752 {
753   GstFileSrc *src = GST_FILESRC (GST_PAD_PARENT (pad));
754
755   switch (type) {
756     case GST_QUERY_TOTAL:
757       if (*format != GST_FORMAT_BYTES) {
758         return FALSE;
759       }
760       *value = src->filelen;
761       break;
762     case GST_QUERY_POSITION:
763       switch (*format) {
764         case GST_FORMAT_BYTES:
765           *value = src->curoffset;
766           break;
767         case GST_FORMAT_PERCENT:
768           if (src->filelen == 0)
769             return FALSE;
770           *value = src->curoffset * GST_FORMAT_PERCENT_MAX / src->filelen;
771           break;
772         default:
773           return FALSE;
774       }
775       break;
776     default:
777       return FALSE;
778       break;
779   }
780   return TRUE;
781 }
782
783 static gboolean
784 gst_filesrc_srcpad_event (GstPad *pad, GstEvent *event)
785 {
786   GstFileSrc *src = GST_FILESRC (GST_PAD_PARENT (pad));
787
788   GST_DEBUG ( "event %d", GST_EVENT_TYPE (event));
789
790   switch (GST_EVENT_TYPE (event)) {
791     case GST_EVENT_SEEK:
792     {
793       gint64 offset;
794
795       if (GST_EVENT_SEEK_FORMAT (event) != GST_FORMAT_BYTES) {
796         goto error;
797       }
798
799       offset = GST_EVENT_SEEK_OFFSET (event);
800
801       switch (GST_EVENT_SEEK_METHOD (event)) {
802         case GST_SEEK_METHOD_SET:
803           if (offset > src->filelen) 
804             goto error;
805           src->curoffset = offset;
806           GST_DEBUG ( "seek set pending to %" G_GINT64_FORMAT, src->curoffset);
807           break;
808         case GST_SEEK_METHOD_CUR:
809           if (offset + src->curoffset > src->filelen) 
810             goto error;
811           src->curoffset += offset;
812           GST_DEBUG ( "seek cur pending to %" G_GINT64_FORMAT, src->curoffset);
813           break;
814         case GST_SEEK_METHOD_END:
815           if (ABS (offset) > src->filelen) 
816             goto error;
817           src->curoffset = src->filelen - ABS (offset);
818           GST_DEBUG ( "seek end pending to %" G_GINT64_FORMAT, src->curoffset);
819           break;
820         default:
821           goto error;
822           break;
823       }
824       src->seek_happened = TRUE;
825       src->need_flush = GST_EVENT_SEEK_FLAGS(event) & GST_SEEK_FLAG_FLUSH;
826       break;
827     }
828     case GST_EVENT_SIZE:
829       if (GST_EVENT_SIZE_FORMAT (event) != GST_FORMAT_BYTES) {
830         goto error;
831       }
832       src->block_size = GST_EVENT_SIZE_VALUE (event);
833       g_object_notify (G_OBJECT (src), "blocksize");  
834       break;
835     case GST_EVENT_FLUSH:
836       src->need_flush = TRUE;
837       break;
838     default:
839       goto error;
840       break;
841   }
842   gst_event_unref (event);
843   return TRUE;
844
845 error:
846   gst_event_unref (event);
847   return FALSE;
848 }