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