win32: fix seeking in files >4GB
[platform/upstream/gstreamer.git] / plugins / elements / gstfilesink.c
1 /* GStreamer
2  * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
3  *                    2000 Wim Taymans <wtay@chello.be>
4  *                    2006 Wim Taymans <wim@fluendo.com>
5  *
6  * gstfilesink.c:
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23 /**
24  * SECTION:element-filesink
25  * @see_also: #GstFileSrc
26  *
27  * Write incoming data to a file in the local file system.
28  */
29
30 #ifdef HAVE_CONFIG_H
31 #  include "config.h"
32 #endif
33
34 #include "../../gst/gst-i18n-lib.h"
35
36 #include <gst/gst.h>
37 #include <stdio.h>              /* for fseeko() */
38 #ifdef HAVE_STDIO_EXT_H
39 #include <stdio_ext.h>          /* for __fbufsize, for debugging */
40 #endif
41 #include <errno.h>
42 #include "gstfilesink.h"
43 #include <string.h>
44 #include <sys/types.h>
45
46 #ifdef G_OS_WIN32
47 #include <io.h>                 /* lseek, open, close, read */
48 #undef lseek
49 #define lseek _lseeki64
50 #undef off_t
51 #define off_t guint64
52 #endif
53
54 #include <sys/stat.h>
55 #ifdef HAVE_UNISTD_H
56 #include <unistd.h>
57 #endif
58
59 static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
60     GST_PAD_SINK,
61     GST_PAD_ALWAYS,
62     GST_STATIC_CAPS_ANY);
63
64 #define GST_TYPE_BUFFER_MODE (buffer_mode_get_type ())
65 static GType
66 buffer_mode_get_type (void)
67 {
68   static GType buffer_mode_type = 0;
69   static const GEnumValue buffer_mode[] = {
70     {-1, "Default buffering", "default"},
71     {_IOFBF, "Fully buffered", "full"},
72     {_IOLBF, "Line buffered", "line"},
73     {_IONBF, "Unbuffered", "unbuffered"},
74     {0, NULL, NULL},
75   };
76
77   if (!buffer_mode_type) {
78     buffer_mode_type =
79         g_enum_register_static ("GstFileSinkBufferMode", buffer_mode);
80   }
81   return buffer_mode_type;
82 }
83
84 GST_DEBUG_CATEGORY_STATIC (gst_file_sink_debug);
85 #define GST_CAT_DEFAULT gst_file_sink_debug
86
87 #define DEFAULT_LOCATION        NULL
88 #define DEFAULT_BUFFER_MODE     -1
89 #define DEFAULT_BUFFER_SIZE     64 * 1024
90
91 enum
92 {
93   PROP_0,
94   PROP_LOCATION,
95   PROP_BUFFER_MODE,
96   PROP_BUFFER_SIZE,
97   PROP_LAST
98 };
99
100 static void gst_file_sink_dispose (GObject * object);
101
102 static void gst_file_sink_set_property (GObject * object, guint prop_id,
103     const GValue * value, GParamSpec * pspec);
104 static void gst_file_sink_get_property (GObject * object, guint prop_id,
105     GValue * value, GParamSpec * pspec);
106
107 static gboolean gst_file_sink_open_file (GstFileSink * sink);
108 static void gst_file_sink_close_file (GstFileSink * sink);
109
110 static gboolean gst_file_sink_start (GstBaseSink * sink);
111 static gboolean gst_file_sink_stop (GstBaseSink * sink);
112 static gboolean gst_file_sink_event (GstBaseSink * sink, GstEvent * event);
113 static GstFlowReturn gst_file_sink_render (GstBaseSink * sink,
114     GstBuffer * buffer);
115
116 static gboolean gst_file_sink_do_seek (GstFileSink * filesink,
117     guint64 new_offset);
118 static gboolean gst_file_sink_get_current_offset (GstFileSink * filesink,
119     guint64 * p_pos);
120
121 static gboolean gst_file_sink_query (GstPad * pad, GstQuery * query);
122
123 static void gst_file_sink_uri_handler_init (gpointer g_iface,
124     gpointer iface_data);
125
126
127 static void
128 _do_init (GType filesink_type)
129 {
130   static const GInterfaceInfo urihandler_info = {
131     gst_file_sink_uri_handler_init,
132     NULL,
133     NULL
134   };
135
136   g_type_add_interface_static (filesink_type, GST_TYPE_URI_HANDLER,
137       &urihandler_info);
138   GST_DEBUG_CATEGORY_INIT (gst_file_sink_debug, "filesink", 0,
139       "filesink element");
140 }
141
142 GST_BOILERPLATE_FULL (GstFileSink, gst_file_sink, GstBaseSink,
143     GST_TYPE_BASE_SINK, _do_init);
144
145 static void
146 gst_file_sink_base_init (gpointer g_class)
147 {
148   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class);
149
150   gst_element_class_set_details_simple (gstelement_class,
151       "File Sink",
152       "Sink/File", "Write stream to a file",
153       "Thomas Vander Stichele <thomas at apestaart dot org>");
154   gst_element_class_add_pad_template (gstelement_class,
155       gst_static_pad_template_get (&sinktemplate));
156 }
157
158 static void
159 gst_file_sink_class_init (GstFileSinkClass * klass)
160 {
161   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
162   GstBaseSinkClass *gstbasesink_class = GST_BASE_SINK_CLASS (klass);
163
164   gobject_class->dispose = gst_file_sink_dispose;
165
166   gobject_class->set_property = gst_file_sink_set_property;
167   gobject_class->get_property = gst_file_sink_get_property;
168
169   g_object_class_install_property (gobject_class, PROP_LOCATION,
170       g_param_spec_string ("location", "File Location",
171           "Location of the file to write", NULL,
172           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
173
174   g_object_class_install_property (gobject_class, PROP_BUFFER_MODE,
175       g_param_spec_enum ("buffer-mode", "Buffering mode",
176           "The buffering mode to use", GST_TYPE_BUFFER_MODE,
177           DEFAULT_BUFFER_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
178
179   g_object_class_install_property (gobject_class, PROP_BUFFER_SIZE,
180       g_param_spec_uint ("buffer-size", "Buffering size",
181           "Size of buffer in number of bytes for line or full buffer-mode", 0,
182           G_MAXUINT, DEFAULT_BUFFER_SIZE,
183           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
184
185   gstbasesink_class->get_times = NULL;
186   gstbasesink_class->start = GST_DEBUG_FUNCPTR (gst_file_sink_start);
187   gstbasesink_class->stop = GST_DEBUG_FUNCPTR (gst_file_sink_stop);
188   gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_file_sink_render);
189   gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_file_sink_event);
190
191   if (sizeof (off_t) < 8) {
192     GST_LOG ("No large file support, sizeof (off_t) = %" G_GSIZE_FORMAT "!",
193         sizeof (off_t));
194   }
195 }
196
197 static void
198 gst_file_sink_init (GstFileSink * filesink, GstFileSinkClass * g_class)
199 {
200   GstPad *pad;
201
202   pad = GST_BASE_SINK_PAD (filesink);
203
204   gst_pad_set_query_function (pad, GST_DEBUG_FUNCPTR (gst_file_sink_query));
205
206   filesink->filename = NULL;
207   filesink->file = NULL;
208   filesink->buffer_mode = DEFAULT_BUFFER_MODE;
209   filesink->buffer_size = DEFAULT_BUFFER_SIZE;
210   filesink->buffer = NULL;
211
212   gst_base_sink_set_sync (GST_BASE_SINK (filesink), FALSE);
213 }
214
215 static void
216 gst_file_sink_dispose (GObject * object)
217 {
218   GstFileSink *sink = GST_FILE_SINK (object);
219
220   G_OBJECT_CLASS (parent_class)->dispose (object);
221
222   g_free (sink->uri);
223   sink->uri = NULL;
224   g_free (sink->filename);
225   sink->filename = NULL;
226   g_free (sink->buffer);
227   sink->buffer = NULL;
228   sink->buffer_size = 0;
229 }
230
231 static gboolean
232 gst_file_sink_set_location (GstFileSink * sink, const gchar * location)
233 {
234   if (sink->file)
235     goto was_open;
236
237   g_free (sink->filename);
238   g_free (sink->uri);
239   if (location != NULL) {
240     /* we store the filename as we received it from the application. On Windows
241      * this should be in UTF8 */
242     sink->filename = g_strdup (location);
243     sink->uri = gst_uri_construct ("file", sink->filename);
244   } else {
245     sink->filename = NULL;
246     sink->uri = NULL;
247   }
248
249   return TRUE;
250
251   /* ERRORS */
252 was_open:
253   {
254     g_warning ("Changing the `location' property on filesink when "
255         "a file is open not supported.");
256     return FALSE;
257   }
258 }
259
260 static void
261 gst_file_sink_set_property (GObject * object, guint prop_id,
262     const GValue * value, GParamSpec * pspec)
263 {
264   GstFileSink *sink = GST_FILE_SINK (object);
265
266   switch (prop_id) {
267     case PROP_LOCATION:
268       gst_file_sink_set_location (sink, g_value_get_string (value));
269       break;
270     case PROP_BUFFER_MODE:
271       sink->buffer_mode = g_value_get_enum (value);
272       break;
273     case PROP_BUFFER_SIZE:
274       sink->buffer_size = g_value_get_uint (value);
275       break;
276     default:
277       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
278       break;
279   }
280 }
281
282 static void
283 gst_file_sink_get_property (GObject * object, guint prop_id, GValue * value,
284     GParamSpec * pspec)
285 {
286   GstFileSink *sink = GST_FILE_SINK (object);
287
288   switch (prop_id) {
289     case PROP_LOCATION:
290       g_value_set_string (value, sink->filename);
291       break;
292     case PROP_BUFFER_MODE:
293       g_value_set_enum (value, sink->buffer_mode);
294       break;
295     case PROP_BUFFER_SIZE:
296       g_value_set_uint (value, sink->buffer_size);
297       break;
298     default:
299       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
300       break;
301   }
302 }
303
304 static gboolean
305 gst_file_sink_open_file (GstFileSink * sink)
306 {
307   gint mode;
308
309   /* open the file */
310   if (sink->filename == NULL || sink->filename[0] == '\0')
311     goto no_filename;
312
313   /* FIXME, can we use g_fopen here? some people say that the FILE object is
314    * local to the .so that performed the fopen call, which would not be us when
315    * we use g_fopen. */
316   sink->file = fopen (sink->filename, "wb");
317   if (sink->file == NULL)
318     goto open_failed;
319
320   /* see if we are asked to perform a specific kind of buffering */
321   if ((mode = sink->buffer_mode) != -1) {
322     gsize buffer_size;
323
324     /* free previous buffer if any */
325     g_free (sink->buffer);
326
327     if (mode == _IONBF) {
328       /* no buffering */
329       sink->buffer = NULL;
330       buffer_size = 0;
331     } else {
332       /* allocate buffer */
333       sink->buffer = g_malloc (sink->buffer_size);
334       buffer_size = sink->buffer_size;
335     }
336 #ifdef HAVE_STDIO_EXT_H
337     GST_DEBUG_OBJECT (sink, "change buffer size %d to %d, mode %d",
338         __fbufsize (sink->file), buffer_size, mode);
339 #else
340     GST_DEBUG_OBJECT (sink, "change  buffer size to %d, mode %d",
341         sink->buffer_size, mode);
342 #endif
343     if (setvbuf (sink->file, sink->buffer, mode, buffer_size) != 0) {
344       GST_WARNING_OBJECT (sink, "warning: setvbuf failed: %s",
345           g_strerror (errno));
346     }
347   }
348
349   sink->current_pos = 0;
350   /* try to seek in the file to figure out if it is seekable */
351   sink->seekable = gst_file_sink_do_seek (sink, 0);
352
353   GST_DEBUG_OBJECT (sink, "opened file %s, seekable %d",
354       sink->filename, sink->seekable);
355
356   return TRUE;
357
358   /* ERRORS */
359 no_filename:
360   {
361     GST_ELEMENT_ERROR (sink, RESOURCE, NOT_FOUND,
362         (_("No file name specified for writing.")), (NULL));
363     return FALSE;
364   }
365 open_failed:
366   {
367     GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
368         (_("Could not open file \"%s\" for writing."), sink->filename),
369         GST_ERROR_SYSTEM);
370     return FALSE;
371   }
372 }
373
374 static void
375 gst_file_sink_close_file (GstFileSink * sink)
376 {
377   if (sink->file) {
378     if (fclose (sink->file) != 0)
379       goto close_failed;
380
381     GST_DEBUG_OBJECT (sink, "closed file");
382     sink->file = NULL;
383
384     g_free (sink->buffer);
385     sink->buffer = NULL;
386   }
387   return;
388
389   /* ERRORS */
390 close_failed:
391   {
392     GST_ELEMENT_ERROR (sink, RESOURCE, CLOSE,
393         (_("Error closing file \"%s\"."), sink->filename), GST_ERROR_SYSTEM);
394     return;
395   }
396 }
397
398 static gboolean
399 gst_file_sink_query (GstPad * pad, GstQuery * query)
400 {
401   GstFileSink *self;
402   GstFormat format;
403
404   self = GST_FILE_SINK (GST_PAD_PARENT (pad));
405
406   switch (GST_QUERY_TYPE (query)) {
407     case GST_QUERY_POSITION:
408       gst_query_parse_position (query, &format, NULL);
409       switch (format) {
410         case GST_FORMAT_DEFAULT:
411         case GST_FORMAT_BYTES:
412           gst_query_set_position (query, GST_FORMAT_BYTES, self->current_pos);
413           return TRUE;
414         default:
415           return FALSE;
416       }
417
418     case GST_QUERY_FORMATS:
419       gst_query_set_formats (query, 2, GST_FORMAT_DEFAULT, GST_FORMAT_BYTES);
420       return TRUE;
421
422     case GST_QUERY_URI:
423       gst_query_set_uri (query, self->uri);
424       return TRUE;
425
426     default:
427       return gst_pad_query_default (pad, query);
428   }
429 }
430
431 #ifdef HAVE_FSEEKO
432 # define __GST_STDIO_SEEK_FUNCTION "fseeko"
433 #elif defined (G_OS_UNIX) || defined (G_OS_WIN32)
434 # define __GST_STDIO_SEEK_FUNCTION "lseek"
435 #else
436 # define __GST_STDIO_SEEK_FUNCTION "fseek"
437 #endif
438
439 static gboolean
440 gst_file_sink_do_seek (GstFileSink * filesink, guint64 new_offset)
441 {
442   GST_DEBUG_OBJECT (filesink, "Seeking to offset %" G_GUINT64_FORMAT
443       " using " __GST_STDIO_SEEK_FUNCTION, new_offset);
444
445   if (fflush (filesink->file))
446     goto flush_failed;
447
448 #ifdef HAVE_FSEEKO
449   if (fseeko (filesink->file, (off_t) new_offset, SEEK_SET) != 0)
450     goto seek_failed;
451 #elif defined (G_OS_UNIX) || defined (G_OS_WIN32)
452   if (lseek (fileno (filesink->file), (off_t) new_offset,
453           SEEK_SET) == (off_t) - 1)
454     goto seek_failed;
455 #else
456   if (fseek (filesink->file, (long) new_offset, SEEK_SET) != 0)
457     goto seek_failed;
458 #endif
459
460   /* adjust position reporting after seek;
461    * presumably this should basically yield new_offset */
462   gst_file_sink_get_current_offset (filesink, &filesink->current_pos);
463
464   return TRUE;
465
466   /* ERRORS */
467 flush_failed:
468   {
469     GST_DEBUG_OBJECT (filesink, "Flush failed: %s", g_strerror (errno));
470     return FALSE;
471   }
472 seek_failed:
473   {
474     GST_DEBUG_OBJECT (filesink, "Seeking failed: %s", g_strerror (errno));
475     return FALSE;
476   }
477 }
478
479 /* handle events (search) */
480 static gboolean
481 gst_file_sink_event (GstBaseSink * sink, GstEvent * event)
482 {
483   GstEventType type;
484   GstFileSink *filesink;
485
486   filesink = GST_FILE_SINK (sink);
487
488   type = GST_EVENT_TYPE (event);
489
490   switch (type) {
491     case GST_EVENT_NEWSEGMENT:
492     {
493       gint64 start, stop, pos;
494       GstFormat format;
495
496       gst_event_parse_new_segment (event, NULL, NULL, &format, &start,
497           &stop, &pos);
498
499       if (format == GST_FORMAT_BYTES) {
500         /* only try to seek and fail when we are going to a different
501          * position */
502         if (filesink->current_pos != start) {
503           /* FIXME, the seek should be performed on the pos field, start/stop are
504            * just boundaries for valid bytes offsets. We should also fill the file
505            * with zeroes if the new position extends the current EOF (sparse streams
506            * and segment accumulation). */
507           if (!gst_file_sink_do_seek (filesink, (guint64) start))
508             goto seek_failed;
509         } else {
510           GST_DEBUG_OBJECT (filesink, "Ignored NEWSEGMENT, no seek needed");
511         }
512       } else {
513         GST_DEBUG_OBJECT (filesink,
514             "Ignored NEWSEGMENT event of format %u (%s)", (guint) format,
515             gst_format_get_name (format));
516       }
517       break;
518     }
519     case GST_EVENT_EOS:
520       if (fflush (filesink->file))
521         goto flush_failed;
522       break;
523     default:
524       break;
525   }
526
527   return TRUE;
528
529   /* ERRORS */
530 seek_failed:
531   {
532     GST_ELEMENT_ERROR (filesink, RESOURCE, SEEK,
533         (_("Error while seeking in file \"%s\"."), filesink->filename),
534         GST_ERROR_SYSTEM);
535     return FALSE;
536   }
537 flush_failed:
538   {
539     GST_ELEMENT_ERROR (filesink, RESOURCE, WRITE,
540         (_("Error while writing to file \"%s\"."), filesink->filename),
541         GST_ERROR_SYSTEM);
542     return FALSE;
543   }
544 }
545
546 static gboolean
547 gst_file_sink_get_current_offset (GstFileSink * filesink, guint64 * p_pos)
548 {
549   off_t ret = -1;
550
551 #ifdef HAVE_FTELLO
552   ret = ftello (filesink->file);
553 #elif defined (G_OS_UNIX) || defined (G_OS_WIN32)
554   if (fflush (filesink->file)) {
555     GST_DEBUG_OBJECT (filesink, "Flush failed: %s", g_strerror (errno));
556     /* ignore and continue */
557   }
558   ret = lseek (fileno (filesink->file), 0, SEEK_CUR);
559 #else
560   ret = (off_t) ftell (filesink->file);
561 #endif
562
563   if (ret != (off_t) - 1)
564     *p_pos = (guint64) ret;
565
566   return (ret != (off_t) - 1);
567 }
568
569 static GstFlowReturn
570 gst_file_sink_render (GstBaseSink * sink, GstBuffer * buffer)
571 {
572   GstFileSink *filesink;
573   guint size;
574   guint8 *data;
575
576   filesink = GST_FILE_SINK (sink);
577
578   size = GST_BUFFER_SIZE (buffer);
579   data = GST_BUFFER_DATA (buffer);
580
581   GST_DEBUG_OBJECT (filesink, "writing %u bytes at %" G_GUINT64_FORMAT,
582       size, filesink->current_pos);
583
584   if (size > 0 && data != NULL) {
585     if (fwrite (data, size, 1, filesink->file) != 1)
586       goto handle_error;
587
588     filesink->current_pos += size;
589   }
590
591   return GST_FLOW_OK;
592
593 handle_error:
594   {
595     switch (errno) {
596       case ENOSPC:{
597         GST_ELEMENT_ERROR (filesink, RESOURCE, NO_SPACE_LEFT, (NULL), (NULL));
598         break;
599       }
600       default:{
601         GST_ELEMENT_ERROR (filesink, RESOURCE, WRITE,
602             (_("Error while writing to file \"%s\"."), filesink->filename),
603             ("%s", g_strerror (errno)));
604       }
605     }
606     return GST_FLOW_ERROR;
607   }
608 }
609
610 static gboolean
611 gst_file_sink_start (GstBaseSink * basesink)
612 {
613   return gst_file_sink_open_file (GST_FILE_SINK (basesink));
614 }
615
616 static gboolean
617 gst_file_sink_stop (GstBaseSink * basesink)
618 {
619   gst_file_sink_close_file (GST_FILE_SINK (basesink));
620   return TRUE;
621 }
622
623 /*** GSTURIHANDLER INTERFACE *************************************************/
624
625 static GstURIType
626 gst_file_sink_uri_get_type (void)
627 {
628   return GST_URI_SINK;
629 }
630
631 static gchar **
632 gst_file_sink_uri_get_protocols (void)
633 {
634   static gchar *protocols[] = { "file", NULL };
635
636   return protocols;
637 }
638
639 static const gchar *
640 gst_file_sink_uri_get_uri (GstURIHandler * handler)
641 {
642   GstFileSink *sink = GST_FILE_SINK (handler);
643
644   return sink->uri;
645 }
646
647 static gboolean
648 gst_file_sink_uri_set_uri (GstURIHandler * handler, const gchar * uri)
649 {
650   gchar *protocol, *location;
651   gboolean ret;
652   GstFileSink *sink = GST_FILE_SINK (handler);
653
654   protocol = gst_uri_get_protocol (uri);
655   if (strcmp (protocol, "file") != 0) {
656     g_free (protocol);
657     return FALSE;
658   }
659   g_free (protocol);
660
661   /* allow file://localhost/foo/bar by stripping localhost but fail
662    * for every other hostname */
663   if (g_str_has_prefix (uri, "file://localhost/")) {
664     char *tmp;
665
666     /* 16 == strlen ("file://localhost") */
667     tmp = g_strconcat ("file://", uri + 16, NULL);
668     /* we use gst_uri_get_location() although we already have the
669      * "location" with uri + 16 because it provides unescaping */
670     location = gst_uri_get_location (tmp);
671     g_free (tmp);
672   } else if (strcmp (uri, "file://") == 0) {
673     /* Special case for "file://" as this is used by some applications
674      *  to test with gst_element_make_from_uri if there's an element
675      *  that supports the URI protocol. */
676     gst_file_sink_set_location (sink, NULL);
677     return TRUE;
678   } else {
679     location = gst_uri_get_location (uri);
680   }
681
682   if (!location)
683     return FALSE;
684   if (!g_path_is_absolute (location)) {
685     g_free (location);
686     return FALSE;
687   }
688
689   ret = gst_file_sink_set_location (sink, location);
690   g_free (location);
691
692   return ret;
693 }
694
695 static void
696 gst_file_sink_uri_handler_init (gpointer g_iface, gpointer iface_data)
697 {
698   GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
699
700   iface->get_type = gst_file_sink_uri_get_type;
701   iface->get_protocols = gst_file_sink_uri_get_protocols;
702   iface->get_uri = gst_file_sink_uri_get_uri;
703   iface->set_uri = gst_file_sink_uri_set_uri;
704 }