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