plugins/elements/gstfilesink.*: Fix position reporting; rename data_written member...
[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  * @short_description: write stream to a file
26  * @see_also: #GstFileSrc
27  *
28  * Write incoming data to a file in the local file system.
29  */
30
31 #ifdef HAVE_CONFIG_H
32 #  include "config.h"
33 #endif
34
35 #include "../../gst/gst-i18n-lib.h"
36
37 #include <gst/gst.h>
38 #include <stdio.h>              /* for fseeko() */
39 #include <errno.h>
40 #include "gstfilesink.h"
41 #include <string.h>
42 #include <sys/stat.h>
43 #include <sys/types.h>
44 #ifdef HAVE_UNISTD_H
45 #include <unistd.h>
46 #endif
47
48 static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
49     GST_PAD_SINK,
50     GST_PAD_ALWAYS,
51     GST_STATIC_CAPS_ANY);
52
53 GST_DEBUG_CATEGORY_STATIC (gst_file_sink_debug);
54 #define GST_CAT_DEFAULT gst_file_sink_debug
55
56 static const GstElementDetails gst_file_sink_details =
57 GST_ELEMENT_DETAILS ("File Sink",
58     "Sink/File",
59     "Write stream to a file",
60     "Thomas <thomas@apestaart.org>");
61
62 enum
63 {
64   ARG_0,
65   ARG_LOCATION
66 };
67
68 static void gst_file_sink_dispose (GObject * object);
69
70 static void gst_file_sink_set_property (GObject * object, guint prop_id,
71     const GValue * value, GParamSpec * pspec);
72 static void gst_file_sink_get_property (GObject * object, guint prop_id,
73     GValue * value, GParamSpec * pspec);
74
75 static gboolean gst_file_sink_open_file (GstFileSink * sink);
76 static void gst_file_sink_close_file (GstFileSink * sink);
77
78 static gboolean gst_file_sink_start (GstBaseSink * sink);
79 static gboolean gst_file_sink_stop (GstBaseSink * sink);
80 static gboolean gst_file_sink_event (GstBaseSink * sink, GstEvent * event);
81 static GstFlowReturn gst_file_sink_render (GstBaseSink * sink,
82     GstBuffer * buffer);
83
84 static gboolean gst_file_sink_do_seek (GstFileSink * filesink,
85     guint64 new_offset);
86 static gboolean gst_file_sink_get_current_offset (GstFileSink * filesink,
87     guint64 * p_pos);
88
89 static gboolean gst_file_sink_query (GstPad * pad, GstQuery * query);
90
91 static void gst_file_sink_uri_handler_init (gpointer g_iface,
92     gpointer iface_data);
93
94
95 static void
96 _do_init (GType filesink_type)
97 {
98   static const GInterfaceInfo urihandler_info = {
99     gst_file_sink_uri_handler_init,
100     NULL,
101     NULL
102   };
103
104   g_type_add_interface_static (filesink_type, GST_TYPE_URI_HANDLER,
105       &urihandler_info);
106   GST_DEBUG_CATEGORY_INIT (gst_file_sink_debug, "filesink", 0,
107       "filesink element");
108 }
109
110 GST_BOILERPLATE_FULL (GstFileSink, gst_file_sink, GstBaseSink,
111     GST_TYPE_BASE_SINK, _do_init);
112
113 static void
114 gst_file_sink_base_init (gpointer g_class)
115 {
116   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class);
117
118   gst_element_class_add_pad_template (gstelement_class,
119       gst_static_pad_template_get (&sinktemplate));
120   gst_element_class_set_details (gstelement_class, &gst_file_sink_details);
121 }
122
123 static void
124 gst_file_sink_class_init (GstFileSinkClass * klass)
125 {
126   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
127   GstBaseSinkClass *gstbasesink_class = GST_BASE_SINK_CLASS (klass);
128
129   gobject_class->set_property = gst_file_sink_set_property;
130   gobject_class->get_property = gst_file_sink_get_property;
131
132   g_object_class_install_property (gobject_class, ARG_LOCATION,
133       g_param_spec_string ("location", "File Location",
134           "Location of the file to write", NULL, G_PARAM_READWRITE));
135
136   gobject_class->dispose = gst_file_sink_dispose;
137
138   gstbasesink_class->get_times = NULL;
139   gstbasesink_class->start = GST_DEBUG_FUNCPTR (gst_file_sink_start);
140   gstbasesink_class->stop = GST_DEBUG_FUNCPTR (gst_file_sink_stop);
141   gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_file_sink_render);
142   gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_file_sink_event);
143
144   if (sizeof (off_t) < 8) {
145     GST_LOG ("No large file support, sizeof (off_t) = %" G_GSIZE_FORMAT "!",
146         sizeof (off_t));
147   }
148 }
149
150 static void
151 gst_file_sink_init (GstFileSink * filesink, GstFileSinkClass * g_class)
152 {
153   GstPad *pad;
154
155   pad = GST_BASE_SINK_PAD (filesink);
156
157   gst_pad_set_query_function (pad, GST_DEBUG_FUNCPTR (gst_file_sink_query));
158
159   filesink->filename = NULL;
160   filesink->file = NULL;
161
162   gst_base_sink_set_sync (GST_BASE_SINK (filesink), FALSE);
163 }
164
165 static void
166 gst_file_sink_dispose (GObject * object)
167 {
168   GstFileSink *sink = GST_FILE_SINK (object);
169
170   G_OBJECT_CLASS (parent_class)->dispose (object);
171
172   g_free (sink->uri);
173   sink->uri = NULL;
174   g_free (sink->filename);
175   sink->filename = NULL;
176 }
177
178 static gboolean
179 gst_file_sink_set_location (GstFileSink * sink, const gchar * location)
180 {
181   if (sink->file)
182     goto was_open;
183
184   g_free (sink->filename);
185   g_free (sink->uri);
186   if (location != NULL) {
187     sink->filename = g_strdup (location);
188     sink->uri = gst_uri_construct ("file", location);
189   } else {
190     sink->filename = NULL;
191     sink->uri = NULL;
192   }
193
194   return TRUE;
195
196   /* ERRORS */
197 was_open:
198   {
199     g_warning ("Changing the `location' property on filesink when "
200         "a file is open not supported.");
201     return FALSE;
202   }
203 }
204 static void
205 gst_file_sink_set_property (GObject * object, guint prop_id,
206     const GValue * value, GParamSpec * pspec)
207 {
208   GstFileSink *sink = GST_FILE_SINK (object);
209
210   switch (prop_id) {
211     case ARG_LOCATION:
212       gst_file_sink_set_location (sink, g_value_get_string (value));
213       break;
214     default:
215       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
216       break;
217   }
218 }
219
220 static void
221 gst_file_sink_get_property (GObject * object, guint prop_id, GValue * value,
222     GParamSpec * pspec)
223 {
224   GstFileSink *sink = GST_FILE_SINK (object);
225
226   switch (prop_id) {
227     case ARG_LOCATION:
228       g_value_set_string (value, sink->filename);
229       break;
230     default:
231       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
232       break;
233   }
234 }
235
236 static gboolean
237 gst_file_sink_open_file (GstFileSink * sink)
238 {
239   /* open the file */
240   if (sink->filename == NULL || sink->filename[0] == '\0')
241     goto no_filename;
242
243   sink->file = fopen (sink->filename, "wb");
244   if (sink->file == NULL)
245     goto open_failed;
246
247   sink->current_pos = 0;
248   /* try to seek in the file to figure out if it is seekable */
249   sink->seekable = gst_file_sink_do_seek (sink, 0);
250
251   GST_DEBUG_OBJECT (sink, "opened file %s, seekable %d",
252       sink->filename, sink->seekable);
253
254   return TRUE;
255
256   /* ERRORS */
257 no_filename:
258   {
259     GST_ELEMENT_ERROR (sink, RESOURCE, NOT_FOUND,
260         (_("No file name specified for writing.")), (NULL));
261     return FALSE;
262   }
263 open_failed:
264   {
265     GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
266         (_("Could not open file \"%s\" for writing."), sink->filename),
267         GST_ERROR_SYSTEM);
268     return FALSE;
269   }
270 }
271
272 static void
273 gst_file_sink_close_file (GstFileSink * sink)
274 {
275   if (sink->file) {
276     if (fclose (sink->file) != 0)
277       goto close_failed;
278
279     GST_DEBUG_OBJECT (sink, "closed file");
280     sink->file = NULL;
281   }
282   return;
283
284   /* ERRORS */
285 close_failed:
286   {
287     GST_ELEMENT_ERROR (sink, RESOURCE, CLOSE,
288         (_("Error closing file \"%s\"."), sink->filename), GST_ERROR_SYSTEM);
289     return;
290   }
291 }
292
293 static gboolean
294 gst_file_sink_query (GstPad * pad, GstQuery * query)
295 {
296   GstFileSink *self;
297   GstFormat format;
298
299   self = GST_FILE_SINK (GST_PAD_PARENT (pad));
300
301   switch (GST_QUERY_TYPE (query)) {
302     case GST_QUERY_POSITION:
303       gst_query_parse_position (query, &format, NULL);
304       switch (format) {
305         case GST_FORMAT_DEFAULT:
306         case GST_FORMAT_BYTES:
307           gst_query_set_position (query, GST_FORMAT_BYTES, self->current_pos);
308           return TRUE;
309         default:
310           return FALSE;
311       }
312
313     case GST_QUERY_FORMATS:
314       gst_query_set_formats (query, 2, GST_FORMAT_DEFAULT, GST_FORMAT_BYTES);
315       return TRUE;
316
317     default:
318       return gst_pad_query_default (pad, query);
319   }
320 }
321
322 #if HAVE_FSEEKO
323 # define __GST_STDIO_SEEK_FUNCTION "fseeko"
324 #elif G_OS_UNIX
325 # define __GST_STDIO_SEEK_FUNCTION "lseek"
326 #else
327 # define __GST_STDIO_SEEK_FUNCTION "fseek"
328 #endif
329
330 static gboolean
331 gst_file_sink_do_seek (GstFileSink * filesink, guint64 new_offset)
332 {
333   GST_DEBUG_OBJECT (filesink, "Seeking to offset %" G_GUINT64_FORMAT
334       " using " __GST_STDIO_SEEK_FUNCTION, new_offset);
335
336   if (fflush (filesink->file))
337     goto flush_failed;
338
339 #if HAVE_FSEEKO
340   if (fseeko (filesink->file, (off_t) new_offset, SEEK_SET) != 0)
341     goto seek_failed;
342 #elif G_OS_UNIX
343   if (lseek (fileno (filesink->file), (off_t) new_offset,
344           SEEK_SET) == (off_t) - 1)
345     goto seek_failed;
346 #else
347   if (fseek (filesink->file, (long) new_offset, SEEK_SET) != 0)
348     goto seek_failed;
349 #endif
350
351   /* adjust position reporting after seek;
352    * presumably this should basically yield new_offset */
353   gst_file_sink_get_current_offset (filesink, &filesink->current_pos);
354
355   return TRUE;
356
357   /* ERRORS */
358 flush_failed:
359   {
360     GST_DEBUG_OBJECT (filesink, "Flush failed: %s", g_strerror (errno));
361     return FALSE;
362   }
363 seek_failed:
364   {
365     GST_DEBUG_OBJECT (filesink, "Seeking failed: %s", g_strerror (errno));
366     return FALSE;
367   }
368 }
369
370 /* handle events (search) */
371 static gboolean
372 gst_file_sink_event (GstBaseSink * sink, GstEvent * event)
373 {
374   GstEventType type;
375   GstFileSink *filesink;
376
377   filesink = GST_FILE_SINK (sink);
378
379   type = GST_EVENT_TYPE (event);
380
381   switch (type) {
382     case GST_EVENT_NEWSEGMENT:
383     {
384       gint64 start, stop, pos;
385       GstFormat format;
386
387       gst_event_parse_new_segment (event, NULL, NULL, &format, &start,
388           &stop, &pos);
389
390       if (format == GST_FORMAT_BYTES) {
391         /* FIXME, the seek should be performed on the pos field, start/stop are
392          * just boundaries for valid bytes offsets. We should also fill the file
393          * with zeroes if the new position extends the current EOF (sparse streams
394          * and segment accumulation). */
395         if (!gst_file_sink_do_seek (filesink, (guint64) start))
396           goto seek_failed;
397       } else {
398         GST_DEBUG ("Ignored NEWSEGMENT event of format %u (%s)",
399             (guint) format, gst_format_get_name (format));
400       }
401       break;
402     }
403     case GST_EVENT_EOS:
404       if (fflush (filesink->file))
405         goto flush_failed;
406       break;
407     default:
408       break;
409   }
410
411   return TRUE;
412
413   /* ERRORS */
414 seek_failed:
415   {
416     GST_ELEMENT_ERROR (filesink, RESOURCE, SEEK,
417         (_("Error while seeking in file \"%s\"."), filesink->filename),
418         GST_ERROR_SYSTEM);
419     return FALSE;
420   }
421 flush_failed:
422   {
423     GST_ELEMENT_ERROR (filesink, RESOURCE, WRITE,
424         (_("Error while writing to file \"%s\"."), filesink->filename),
425         GST_ERROR_SYSTEM);
426     return FALSE;
427   }
428 }
429
430 static gboolean
431 gst_file_sink_get_current_offset (GstFileSink * filesink, guint64 * p_pos)
432 {
433   off_t ret;
434
435 #if HAVE_FTELLO
436   ret = ftello (filesink->file);
437 #elif G_OS_UNIX
438   if (fflush (filesink->file)) {
439     GST_DEBUG_OBJECT (filesink, "Flush failed: %s", g_strerror (errno));
440     /* ignore and continue */
441   }
442   ret = lseek (fileno (filesink->file), 0, SEEK_CUR);
443 #else
444   ret = (off_t) ftell (filesink->file);
445 #endif
446
447   if (ret != (off_t) - 1)
448     *p_pos = (guint64) ret;
449
450   return (ret != (off_t) - 1);
451 }
452
453 static GstFlowReturn
454 gst_file_sink_render (GstBaseSink * sink, GstBuffer * buffer)
455 {
456   GstFileSink *filesink;
457   guint size;
458
459   size = GST_BUFFER_SIZE (buffer);
460
461   filesink = GST_FILE_SINK (sink);
462
463   GST_DEBUG_OBJECT (filesink, "writing %u bytes at %" G_GUINT64_FORMAT,
464       size, filesink->current_pos);
465
466   if (size > 0 && GST_BUFFER_DATA (buffer) != NULL) {
467     if (fwrite (GST_BUFFER_DATA (buffer), size, 1, filesink->file) != 1)
468       goto handle_error;
469
470     filesink->current_pos += size;
471   }
472
473   return GST_FLOW_OK;
474
475 handle_error:
476   {
477     switch (errno) {
478       case ENOSPC:{
479         GST_ELEMENT_ERROR (filesink, RESOURCE, NO_SPACE_LEFT, (NULL), (NULL));
480         break;
481       }
482       default:{
483         GST_ELEMENT_ERROR (filesink, RESOURCE, WRITE,
484             (_("Error while writing to file \"%s\"."), filesink->filename),
485             ("%s", g_strerror (errno)));
486       }
487     }
488     return GST_FLOW_ERROR;
489   }
490 }
491
492 static gboolean
493 gst_file_sink_start (GstBaseSink * basesink)
494 {
495   return gst_file_sink_open_file (GST_FILE_SINK (basesink));
496 }
497
498 static gboolean
499 gst_file_sink_stop (GstBaseSink * basesink)
500 {
501   gst_file_sink_close_file (GST_FILE_SINK (basesink));
502   return TRUE;
503 }
504
505 /*** GSTURIHANDLER INTERFACE *************************************************/
506
507 static GstURIType
508 gst_file_sink_uri_get_type (void)
509 {
510   return GST_URI_SINK;
511 }
512 static gchar **
513 gst_file_sink_uri_get_protocols (void)
514 {
515   static gchar *protocols[] = { "file", NULL };
516
517   return protocols;
518 }
519 static const gchar *
520 gst_file_sink_uri_get_uri (GstURIHandler * handler)
521 {
522   GstFileSink *sink = GST_FILE_SINK (handler);
523
524   return sink->uri;
525 }
526
527 static gboolean
528 gst_file_sink_uri_set_uri (GstURIHandler * handler, const gchar * uri)
529 {
530   gchar *protocol, *location;
531   gboolean ret;
532   GstFileSink *sink = GST_FILE_SINK (handler);
533
534   protocol = gst_uri_get_protocol (uri);
535   if (strcmp (protocol, "file") != 0) {
536     g_free (protocol);
537     return FALSE;
538   }
539   g_free (protocol);
540
541   /* allow file://localhost/foo/bar by stripping localhost but fail
542    * for every other hostname */
543   if (g_str_has_prefix (uri, "file://localhost/")) {
544     char *tmp;
545
546     /* 16 == strlen ("file://localhost") */
547     tmp = g_strconcat ("file://", uri + 16, NULL);
548     /* we use gst_uri_get_location() although we already have the
549      * "location" with uri + 16 because it provides unescaping */
550     location = gst_uri_get_location (tmp);
551     g_free (tmp);
552   } else if (strcmp (uri, "file://") == 0) {
553     /* Special case for "file://" as this is used by some applications
554      *  to test with gst_element_make_from_uri if there's an element
555      *  that supports the URI protocol. */
556     gst_file_sink_set_location (sink, NULL);
557     return TRUE;
558   } else {
559     location = gst_uri_get_location (uri);
560   }
561
562   if (!location)
563     return FALSE;
564   if (!g_path_is_absolute (location)) {
565     g_free (location);
566     return FALSE;
567   }
568
569   ret = gst_file_sink_set_location (sink, location);
570   g_free (location);
571
572   return ret;
573 }
574
575 static void
576 gst_file_sink_uri_handler_init (gpointer g_iface, gpointer iface_data)
577 {
578   GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
579
580   iface->get_type = gst_file_sink_uri_get_type;
581   iface->get_protocols = gst_file_sink_uri_get_protocols;
582   iface->get_uri = gst_file_sink_uri_get_uri;
583   iface->set_uri = gst_file_sink_uri_set_uri;
584 }