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