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