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