Port gtk-doc comments to their equivalent markdown syntax
[platform/upstream/gstreamer.git] / plugins / elements / gstfilesink.c
index 02fee92..4ce527e 100644 (file)
  *
  * You should have received a copy of the GNU Library General Public
  * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
  */
 /**
  * SECTION:element-filesink
+ * @title: filesink
  * @see_also: #GstFileSrc
  *
  * Write incoming data to a file in the local file system.
  *
- * <refsect2>
- * <title>Example launch line</title>
+ * ## Example launch line
  * |[
- * gst-launch v4l2src num-buffers=1 ! jpegenc ! filesink location=capture1.jpeg
+ * gst-launch-1.0 v4l2src num-buffers=1 ! jpegenc ! filesink location=capture1.jpeg
  * ]| Capture one frame from a v4l2 camera and save as jpeg image.
- * </refsect2>
+ *
  */
 
 #ifdef HAVE_CONFIG_H
 #define lseek _lseeki64
 #undef off_t
 #define off_t guint64
+#undef ftruncate
+#define ftruncate _chsize
+#undef fsync
+#define fsync _commit
 #ifdef _MSC_VER                 /* Check if we are using MSVC, fileno is deprecated in favour */
 #define fileno _fileno          /* of _fileno */
 #endif
 #include <unistd.h>
 #endif
 
+#ifdef __BIONIC__               /* Android */
+#undef lseek
+#define lseek lseek64
+#undef off_t
+#define off_t guint64
+#endif
+
+#include "gstelements_private.h"
+#include "gstfilesink.h"
+
 static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
     GST_PAD_SINK,
     GST_PAD_ALWAYS,
     GST_STATIC_CAPS_ANY);
 
-#define GST_TYPE_BUFFER_MODE (buffer_mode_get_type ())
+#define GST_TYPE_FILE_SINK_BUFFER_MODE (gst_file_sink_buffer_mode_get_type ())
 static GType
-buffer_mode_get_type (void)
+gst_file_sink_buffer_mode_get_type (void)
 {
   static GType buffer_mode_type = 0;
   static const GEnumValue buffer_mode[] = {
-    {-1, "Default buffering", "default"},
-    {_IOFBF, "Fully buffered", "full"},
-    {_IOLBF, "Line buffered", "line"},
-    {_IONBF, "Unbuffered", "unbuffered"},
+    {GST_FILE_SINK_BUFFER_MODE_DEFAULT, "Default buffering", "default"},
+    {GST_FILE_SINK_BUFFER_MODE_FULL, "Fully buffered", "full"},
+    {GST_FILE_SINK_BUFFER_MODE_LINE, "Line buffered", "line"},
+    {GST_FILE_SINK_BUFFER_MODE_UNBUFFERED, "Unbuffered", "unbuffered"},
     {0, NULL, NULL},
   };
 
@@ -95,7 +109,7 @@ GST_DEBUG_CATEGORY_STATIC (gst_file_sink_debug);
 #define GST_CAT_DEFAULT gst_file_sink_debug
 
 #define DEFAULT_LOCATION       NULL
-#define DEFAULT_BUFFER_MODE    -1
+#define DEFAULT_BUFFER_MODE    GST_FILE_SINK_BUFFER_MODE_DEFAULT
 #define DEFAULT_BUFFER_SIZE    64 * 1024
 #define DEFAULT_APPEND         FALSE
 
@@ -162,6 +176,8 @@ static gboolean gst_file_sink_stop (GstBaseSink * sink);
 static gboolean gst_file_sink_event (GstBaseSink * sink, GstEvent * event);
 static GstFlowReturn gst_file_sink_render (GstBaseSink * sink,
     GstBuffer * buffer);
+static GstFlowReturn gst_file_sink_render_list (GstBaseSink * sink,
+    GstBufferList * list);
 
 static gboolean gst_file_sink_do_seek (GstFileSink * filesink,
     guint64 new_offset);
@@ -199,7 +215,7 @@ gst_file_sink_class_init (GstFileSinkClass * klass)
 
   g_object_class_install_property (gobject_class, PROP_BUFFER_MODE,
       g_param_spec_enum ("buffer-mode", "Buffering mode",
-          "The buffering mode to use", GST_TYPE_BUFFER_MODE,
+          "The buffering mode to use", GST_TYPE_FILE_SINK_BUFFER_MODE,
           DEFAULT_BUFFER_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
   g_object_class_install_property (gobject_class, PROP_BUFFER_SIZE,
@@ -210,27 +226,26 @@ gst_file_sink_class_init (GstFileSinkClass * klass)
 
   /**
    * GstFileSink:append
-   * 
-   * Append to an already existing file.
    *
-   * Since: 0.10.25
+   * Append to an already existing file.
    */
   g_object_class_install_property (gobject_class, PROP_APPEND,
       g_param_spec_boolean ("append", "Append",
           "Append to an already existing file", DEFAULT_APPEND,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
-  gst_element_class_set_details_simple (gstelement_class,
+  gst_element_class_set_static_metadata (gstelement_class,
       "File Sink",
       "Sink/File", "Write stream to a file",
       "Thomas Vander Stichele <thomas at apestaart dot org>");
-  gst_element_class_add_pad_template (gstelement_class,
-      gst_static_pad_template_get (&sinktemplate));
+  gst_element_class_add_static_pad_template (gstelement_class, &sinktemplate);
 
   gstbasesink_class->start = GST_DEBUG_FUNCPTR (gst_file_sink_start);
   gstbasesink_class->stop = GST_DEBUG_FUNCPTR (gst_file_sink_stop);
   gstbasesink_class->query = GST_DEBUG_FUNCPTR (gst_file_sink_query);
   gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_file_sink_render);
+  gstbasesink_class->render_list =
+      GST_DEBUG_FUNCPTR (gst_file_sink_render_list);
   gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_file_sink_event);
 
   if (sizeof (off_t) < 8) {
@@ -244,6 +259,7 @@ gst_file_sink_init (GstFileSink * filesink)
 {
   filesink->filename = NULL;
   filesink->file = NULL;
+  filesink->current_pos = 0;
   filesink->buffer_mode = DEFAULT_BUFFER_MODE;
   filesink->buffer_size = DEFAULT_BUFFER_SIZE;
   filesink->buffer = NULL;
@@ -269,7 +285,8 @@ gst_file_sink_dispose (GObject * object)
 }
 
 static gboolean
-gst_file_sink_set_location (GstFileSink * sink, const gchar * location)
+gst_file_sink_set_location (GstFileSink * sink, const gchar * location,
+    GError ** error)
 {
   if (sink->file)
     goto was_open;
@@ -281,8 +298,8 @@ gst_file_sink_set_location (GstFileSink * sink, const gchar * location)
      * this should be in UTF8 */
     sink->filename = g_strdup (location);
     sink->uri = gst_filename_to_uri (location, NULL);
-    GST_INFO ("filename : %s", sink->filename);
-    GST_INFO ("uri      : %s", sink->uri);
+    GST_INFO_OBJECT (sink, "filename : %s", sink->filename);
+    GST_INFO_OBJECT (sink, "uri      : %s", sink->uri);
   } else {
     sink->filename = NULL;
     sink->uri = NULL;
@@ -295,6 +312,9 @@ was_open:
   {
     g_warning ("Changing the `location' property on filesink when a file is "
         "open is not supported.");
+    g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_STATE,
+        "Changing the 'location' property on filesink when a file is "
+        "open is not supported");
     return FALSE;
   }
 }
@@ -307,7 +327,7 @@ gst_file_sink_set_property (GObject * object, guint prop_id,
 
   switch (prop_id) {
     case PROP_LOCATION:
-      gst_file_sink_set_location (sink, g_value_get_string (value));
+      gst_file_sink_set_location (sink, g_value_get_string (value), NULL);
       break;
     case PROP_BUFFER_MODE:
       sink->buffer_mode = g_value_get_enum (value);
@@ -381,7 +401,8 @@ gst_file_sink_open_file (GstFileSink * sink)
       sink->buffer = g_malloc (sink->buffer_size);
       buffer_size = sink->buffer_size;
     }
-#ifdef HAVE_STDIO_EXT_H
+    /* Cygwin does not have __fbufsize */
+#if defined(HAVE_STDIO_EXT_H) && !defined(__CYGWIN__)
     GST_DEBUG_OBJECT (sink, "change buffer size %u to %u, mode %d",
         (guint) __fbufsize (sink->file), buffer_size, mode);
 #else
@@ -424,7 +445,8 @@ gst_file_sink_close_file (GstFileSink * sink)
 {
   if (sink->file) {
     if (fclose (sink->file) != 0)
-      goto close_failed;
+      GST_ELEMENT_ERROR (sink, RESOURCE, CLOSE,
+          (_("Error closing file \"%s\"."), sink->filename), GST_ERROR_SYSTEM);
 
     GST_DEBUG_OBJECT (sink, "closed file");
     sink->file = NULL;
@@ -432,15 +454,6 @@ gst_file_sink_close_file (GstFileSink * sink)
     g_free (sink->buffer);
     sink->buffer = NULL;
   }
-  return;
-
-  /* ERRORS */
-close_failed:
-  {
-    GST_ELEMENT_ERROR (sink, RESOURCE, CLOSE,
-        (_("Error closing file \"%s\"."), sink->filename), GST_ERROR_SYSTEM);
-    return;
-  }
 }
 
 static gboolean
@@ -478,6 +491,16 @@ gst_file_sink_query (GstBaseSink * bsink, GstQuery * query)
       res = TRUE;
       break;
 
+    case GST_QUERY_SEEKING:
+      gst_query_parse_seeking (query, &format, NULL, NULL, NULL);
+      if (format == GST_FORMAT_BYTES || format == GST_FORMAT_DEFAULT) {
+        gst_query_set_seeking (query, GST_FORMAT_BYTES, self->seekable, 0, -1);
+      } else {
+        gst_query_set_seeking (query, format, FALSE, 0, -1);
+      }
+      res = TRUE;
+      break;
+
     default:
       res = GST_BASE_SINK_CLASS (parent_class)->query (bsink, query);
       break;
@@ -571,6 +594,13 @@ gst_file_sink_event (GstBaseSink * sink, GstEvent * event)
       }
       break;
     }
+    case GST_EVENT_FLUSH_STOP:
+      if (filesink->current_pos != 0 && filesink->seekable) {
+        gst_file_sink_do_seek (filesink, 0);
+        if (ftruncate (fileno (filesink->file), 0))
+          goto flush_failed;
+      }
+      break;
     case GST_EVENT_EOS:
       if (fflush (filesink->file))
         goto flush_failed;
@@ -579,7 +609,7 @@ gst_file_sink_event (GstBaseSink * sink, GstEvent * event)
       break;
   }
 
-  return TRUE;
+  return GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
 
   /* ERRORS */
 seek_failed:
@@ -587,6 +617,7 @@ seek_failed:
     GST_ELEMENT_ERROR (filesink, RESOURCE, SEEK,
         (_("Error while seeking in file \"%s\"."), filesink->filename),
         GST_ERROR_SYSTEM);
+    gst_event_unref (event);
     return FALSE;
   }
 flush_failed:
@@ -594,6 +625,7 @@ flush_failed:
     GST_ELEMENT_ERROR (filesink, RESOURCE, WRITE,
         (_("Error while writing to file \"%s\"."), filesink->filename),
         GST_ERROR_SYSTEM);
+    gst_event_unref (event);
     return FALSE;
   }
 }
@@ -622,46 +654,94 @@ gst_file_sink_get_current_offset (GstFileSink * filesink, guint64 * p_pos)
 }
 
 static GstFlowReturn
-gst_file_sink_render (GstBaseSink * sink, GstBuffer * buffer)
+gst_file_sink_render_buffers (GstFileSink * sink, GstBuffer ** buffers,
+    guint num_buffers, guint8 * mem_nums, guint total_mems)
 {
-  GstFileSink *filesink;
-  gsize size;
-  guint8 *data;
+  GST_DEBUG_OBJECT (sink,
+      "writing %u buffers (%u memories) at position %" G_GUINT64_FORMAT,
+      num_buffers, total_mems, sink->current_pos);
 
-  filesink = GST_FILE_SINK (sink);
-
-  data = gst_buffer_map (buffer, &size, NULL, GST_MAP_READ);
+  return gst_writev_buffers (GST_OBJECT_CAST (sink), fileno (sink->file), NULL,
+      buffers, num_buffers, mem_nums, total_mems, &sink->current_pos, 0);
+}
 
-  GST_DEBUG_OBJECT (filesink,
-      "writing %" G_GSIZE_FORMAT " bytes at %" G_GUINT64_FORMAT,
-      size, filesink->current_pos);
+static GstFlowReturn
+gst_file_sink_render_list (GstBaseSink * bsink, GstBufferList * buffer_list)
+{
+  GstFlowReturn flow;
+  GstBuffer **buffers;
+  GstFileSink *sink;
+  guint8 *mem_nums;
+  guint total_mems;
+  guint i, num_buffers;
+  gboolean sync_after = FALSE;
+
+  sink = GST_FILE_SINK_CAST (bsink);
+
+  num_buffers = gst_buffer_list_length (buffer_list);
+  if (num_buffers == 0)
+    goto no_data;
+
+  /* extract buffers from list and count memories */
+  buffers = g_newa (GstBuffer *, num_buffers);
+  mem_nums = g_newa (guint8, num_buffers);
+  for (i = 0, total_mems = 0; i < num_buffers; ++i) {
+    buffers[i] = gst_buffer_list_get (buffer_list, i);
+    mem_nums[i] = gst_buffer_n_memory (buffers[i]);
+    total_mems += mem_nums[i];
+    if (GST_BUFFER_FLAG_IS_SET (buffers[i], GST_BUFFER_FLAG_SYNC_AFTER))
+      sync_after = TRUE;
+  }
 
-  if (size > 0 && data != NULL) {
-    if (fwrite (data, size, 1, filesink->file) != 1)
-      goto handle_error;
+  flow =
+      gst_file_sink_render_buffers (sink, buffers, num_buffers, mem_nums,
+      total_mems);
 
-    filesink->current_pos += size;
+  if (flow == GST_FLOW_OK && sync_after) {
+    if (fflush (sink->file) || fsync (fileno (sink->file))) {
+      GST_ELEMENT_ERROR (sink, RESOURCE, WRITE,
+          (_("Error while writing to file \"%s\"."), sink->filename),
+          ("%s", g_strerror (errno)));
+      flow = GST_FLOW_ERROR;
+    }
   }
-  gst_buffer_unmap (buffer, data, size);
 
-  return GST_FLOW_OK;
+  return flow;
 
-handle_error:
+no_data:
   {
-    switch (errno) {
-      case ENOSPC:{
-        GST_ELEMENT_ERROR (filesink, RESOURCE, NO_SPACE_LEFT, (NULL), (NULL));
-        break;
-      }
-      default:{
-        GST_ELEMENT_ERROR (filesink, RESOURCE, WRITE,
-            (_("Error while writing to file \"%s\"."), filesink->filename),
-            ("%s", g_strerror (errno)));
-      }
+    GST_LOG_OBJECT (sink, "empty buffer list");
+    return GST_FLOW_OK;
+  }
+}
+
+static GstFlowReturn
+gst_file_sink_render (GstBaseSink * sink, GstBuffer * buffer)
+{
+  GstFileSink *filesink;
+  GstFlowReturn flow;
+  guint8 n_mem;
+
+  filesink = GST_FILE_SINK_CAST (sink);
+
+  n_mem = gst_buffer_n_memory (buffer);
+
+  if (n_mem > 0)
+    flow = gst_file_sink_render_buffers (filesink, &buffer, 1, &n_mem, n_mem);
+  else
+    flow = GST_FLOW_OK;
+
+  if (flow == GST_FLOW_OK &&
+      GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_SYNC_AFTER)) {
+    if (fflush (filesink->file) || fsync (fileno (filesink->file))) {
+      GST_ELEMENT_ERROR (filesink, RESOURCE, WRITE,
+          (_("Error while writing to file \"%s\"."), filesink->filename),
+          ("%s", g_strerror (errno)));
+      flow = GST_FLOW_ERROR;
     }
-    gst_buffer_unmap (buffer, data, size);
-    return GST_FLOW_ERROR;
   }
+
+  return flow;
 }
 
 static gboolean
@@ -685,36 +765,31 @@ gst_file_sink_uri_get_type (GType type)
   return GST_URI_SINK;
 }
 
-static gchar **
+static const gchar *const *
 gst_file_sink_uri_get_protocols (GType type)
 {
-  static gchar *protocols[] = { (char *) "file", NULL };
+  static const gchar *protocols[] = { "file", NULL };
 
   return protocols;
 }
 
-static const gchar *
+static gchar *
 gst_file_sink_uri_get_uri (GstURIHandler * handler)
 {
   GstFileSink *sink = GST_FILE_SINK (handler);
 
-  return sink->uri;
+  /* FIXME: make thread-safe */
+  return g_strdup (sink->uri);
 }
 
 static gboolean
-gst_file_sink_uri_set_uri (GstURIHandler * handler, const gchar * uri)
+gst_file_sink_uri_set_uri (GstURIHandler * handler, const gchar * uri,
+    GError ** error)
 {
-  gchar *protocol, *location;
+  gchar *location;
   gboolean ret;
   GstFileSink *sink = GST_FILE_SINK (handler);
 
-  protocol = gst_uri_get_protocol (uri);
-  if (strcmp (protocol, "file") != 0) {
-    g_free (protocol);
-    return FALSE;
-  }
-  g_free (protocol);
-
   /* allow file://localhost/foo/bar by stripping localhost but fail
    * for every other hostname */
   if (g_str_has_prefix (uri, "file://localhost/")) {
@@ -730,20 +805,26 @@ gst_file_sink_uri_set_uri (GstURIHandler * handler, const gchar * uri)
     /* Special case for "file://" as this is used by some applications
      *  to test with gst_element_make_from_uri if there's an element
      *  that supports the URI protocol. */
-    gst_file_sink_set_location (sink, NULL);
+    gst_file_sink_set_location (sink, NULL, NULL);
     return TRUE;
   } else {
     location = gst_uri_get_location (uri);
   }
 
-  if (!location)
+  if (!location) {
+    g_set_error_literal (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
+        "File URI without location");
     return FALSE;
+  }
+
   if (!g_path_is_absolute (location)) {
+    g_set_error_literal (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
+        "File URI location must be an absolute path");
     g_free (location);
     return FALSE;
   }
 
-  ret = gst_file_sink_set_location (sink, location);
+  ret = gst_file_sink_set_location (sink, location, error);
   g_free (location);
 
   return ret;