X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=plugins%2Felements%2Fgstfilesink.c;h=90e4fa898a58219d42a60b72a1717bf071b7dfde;hb=refs%2Fchanges%2F76%2F222676%2F2;hp=6888b39278f9df9fd4de5bc0575f4c1713b60021;hpb=1074a4e99a473efd5ee690da9ecd797c55cec23a;p=platform%2Fupstream%2Fgstreamer.git diff --git a/plugins/elements/gstfilesink.c b/plugins/elements/gstfilesink.c index 6888b39..90e4fa8 100644 --- a/plugins/elements/gstfilesink.c +++ b/plugins/elements/gstfilesink.c @@ -17,21 +17,21 @@ * * 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. * - * - * Example launch line + * ## 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. - * + * */ #ifdef HAVE_CONFIG_H @@ -56,6 +56,10 @@ #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 @@ -66,21 +70,25 @@ #include #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 (deprecated, like full)", + "line"}, + {GST_FILE_SINK_BUFFER_MODE_UNBUFFERED, "Unbuffered", "unbuffered"}, {0, NULL, NULL}, }; @@ -95,7 +103,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 @@ -106,6 +114,9 @@ enum PROP_BUFFER_MODE, PROP_BUFFER_SIZE, PROP_APPEND, +#ifdef TIZEN_FEATURE_FILESINK_MODIFICATION + PROP_CURRENT_BYTES, +#endif PROP_LAST }; @@ -162,6 +173,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); @@ -173,6 +186,8 @@ static gboolean gst_file_sink_query (GstBaseSink * bsink, GstQuery * query); static void gst_file_sink_uri_handler_init (gpointer g_iface, gpointer iface_data); +static GstFlowReturn gst_file_sink_flush_buffer (GstFileSink * filesink); + #define _do_init \ G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, gst_file_sink_uri_handler_init); \ GST_DEBUG_CATEGORY_INIT (gst_file_sink_debug, "filesink", 0, "filesink element"); @@ -199,7 +214,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, @@ -207,30 +222,36 @@ gst_file_sink_class_init (GstFileSinkClass * klass) "Size of buffer in number of bytes for line or full buffer-mode", 0, G_MAXUINT, DEFAULT_BUFFER_SIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +#ifdef TIZEN_FEATURE_FILESINK_MODIFICATION + g_object_class_install_property (gobject_class, PROP_CURRENT_BYTES, + g_param_spec_uint64 ("current-bytes", "Current bytes", + "downloaded bytes so far", 0, + G_MAXUINT64, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +#endif /** * 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 "); - 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,9 +265,9 @@ 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; filesink->append = FALSE; gst_base_sink_set_sync (GST_BASE_SINK (filesink), FALSE); @@ -263,9 +284,6 @@ gst_file_sink_dispose (GObject * object) sink->uri = NULL; g_free (sink->filename); sink->filename = NULL; - g_free (sink->buffer); - sink->buffer = NULL; - sink->buffer_size = 0; } static gboolean @@ -282,8 +300,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; @@ -347,6 +365,11 @@ gst_file_sink_get_property (GObject * object, guint prop_id, GValue * value, case PROP_APPEND: g_value_set_boolean (value, sink->append); break; +#ifdef TIZEN_FEATURE_FILESINK_MODIFICATION + case PROP_CURRENT_BYTES: + g_value_set_uint64(value, sink->current_pos); + break; +#endif default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -356,8 +379,6 @@ gst_file_sink_get_property (GObject * object, guint prop_id, GValue * value, static gboolean gst_file_sink_open_file (GstFileSink * sink) { - gint mode; - /* open the file */ if (sink->filename == NULL || sink->filename[0] == '\0') goto no_filename; @@ -369,39 +390,23 @@ gst_file_sink_open_file (GstFileSink * sink) if (sink->file == NULL) goto open_failed; - /* see if we are asked to perform a specific kind of buffering */ - if ((mode = sink->buffer_mode) != -1) { - guint buffer_size; - - /* free previous buffer if any */ - g_free (sink->buffer); - - if (mode == _IONBF) { - /* no buffering */ - sink->buffer = NULL; - buffer_size = 0; - } else { - /* allocate buffer */ - sink->buffer = g_malloc (sink->buffer_size); - buffer_size = sink->buffer_size; - } -#ifdef HAVE_STDIO_EXT_H - GST_DEBUG_OBJECT (sink, "change buffer size %u to %u, mode %d", - (guint) __fbufsize (sink->file), buffer_size, mode); -#else - GST_DEBUG_OBJECT (sink, "change buffer size to %u, mode %d", - sink->buffer_size, mode); -#endif - if (setvbuf (sink->file, sink->buffer, mode, buffer_size) != 0) { - GST_WARNING_OBJECT (sink, "warning: setvbuf failed: %s", - g_strerror (errno)); - } - } - sink->current_pos = 0; /* try to seek in the file to figure out if it is seekable */ sink->seekable = gst_file_sink_do_seek (sink, 0); + if (sink->buffer) + gst_buffer_list_unref (sink->buffer); + sink->buffer = NULL; + if (sink->buffer_mode != GST_FILE_SINK_BUFFER_MODE_UNBUFFERED) { + if (sink->buffer_size == 0) { + sink->buffer_size = DEFAULT_BUFFER_SIZE; + g_object_notify (G_OBJECT (sink), "buffer-size"); + } + + sink->buffer = gst_buffer_list_new (); + sink->current_buffer_size = 0; + } + GST_DEBUG_OBJECT (sink, "opened file %s, seekable %d", sink->filename, sink->seekable); @@ -427,24 +432,23 @@ static void gst_file_sink_close_file (GstFileSink * sink) { if (sink->file) { + if (gst_file_sink_flush_buffer (sink) != GST_FLOW_OK) + GST_ELEMENT_ERROR (sink, RESOURCE, CLOSE, + (_("Error closing file \"%s\"."), sink->filename), NULL); + 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; - - 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; + if (sink->buffer) { + gst_buffer_list_unref (sink->buffer); + sink->buffer = NULL; } + sink->current_buffer_size = 0; } static gboolean @@ -463,7 +467,8 @@ gst_file_sink_query (GstBaseSink * bsink, GstQuery * query) switch (format) { case GST_FORMAT_DEFAULT: case GST_FORMAT_BYTES: - gst_query_set_position (query, GST_FORMAT_BYTES, self->current_pos); + gst_query_set_position (query, GST_FORMAT_BYTES, + self->current_pos + self->current_buffer_size); res = TRUE; break; default: @@ -513,8 +518,8 @@ gst_file_sink_do_seek (GstFileSink * filesink, guint64 new_offset) GST_DEBUG_OBJECT (filesink, "Seeking to offset %" G_GUINT64_FORMAT " using " __GST_STDIO_SEEK_FUNCTION, new_offset); - if (fflush (filesink->file)) - goto flush_failed; + if (gst_file_sink_flush_buffer (filesink) != GST_FLOW_OK) + goto flush_buffer_failed; #ifdef HAVE_FSEEKO if (fseeko (filesink->file, (off_t) new_offset, SEEK_SET) != 0) @@ -535,9 +540,9 @@ gst_file_sink_do_seek (GstFileSink * filesink, guint64 new_offset) return TRUE; /* ERRORS */ -flush_failed: +flush_buffer_failed: { - GST_DEBUG_OBJECT (filesink, "Flush failed: %s", g_strerror (errno)); + GST_DEBUG_OBJECT (filesink, "Flushing buffer failed"); return FALSE; } seek_failed: @@ -568,7 +573,8 @@ gst_file_sink_event (GstBaseSink * sink, GstEvent * event) if (segment->format == GST_FORMAT_BYTES) { /* only try to seek and fail when we are going to a different * position */ - if (filesink->current_pos != segment->start) { + if (filesink->current_pos + filesink->current_buffer_size != + segment->start) { /* FIXME, the seek should be performed on the pos field, start/stop are * just boundaries for valid bytes offsets. We should also fill the file * with zeroes if the new position extends the current EOF (sparse streams @@ -585,9 +591,21 @@ 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 truncate_failed; + } + if (filesink->buffer) { + gst_buffer_list_unref (filesink->buffer); + filesink->buffer = gst_buffer_list_new (); + filesink->current_buffer_size = 0; + } + break; case GST_EVENT_EOS: - if (fflush (filesink->file)) - goto flush_failed; + if (gst_file_sink_flush_buffer (filesink) != GST_FLOW_OK) + goto flush_buffer_failed; break; default: break; @@ -604,7 +622,14 @@ seek_failed: gst_event_unref (event); return FALSE; } -flush_failed: +flush_buffer_failed: + { + GST_ELEMENT_ERROR (filesink, RESOURCE, WRITE, + (_("Error while writing to file \"%s\"."), filesink->filename), NULL); + gst_event_unref (event); + return FALSE; + } +truncate_failed: { GST_ELEMENT_ERROR (filesink, RESOURCE, WRITE, (_("Error while writing to file \"%s\"."), filesink->filename), @@ -619,13 +644,14 @@ gst_file_sink_get_current_offset (GstFileSink * filesink, guint64 * p_pos) { off_t ret = -1; + /* no need to flush internal buffer here as this is only called right + * after a seek. If this changes then the buffer should be flushed here + * too + */ + #ifdef HAVE_FTELLO ret = ftello (filesink->file); #elif defined (G_OS_UNIX) || defined (G_OS_WIN32) - if (fflush (filesink->file)) { - GST_DEBUG_OBJECT (filesink, "Flush failed: %s", g_strerror (errno)); - /* ignore and continue */ - } ret = lseek (fileno (filesink->file), 0, SEEK_CUR); #else ret = (off_t) ftell (filesink->file); @@ -638,45 +664,209 @@ 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, gsize size) { - GstFileSink *filesink; - GstMapInfo info; + GST_DEBUG_OBJECT (sink, + "writing %u buffers (%u memories, %" G_GSIZE_FORMAT + " bytes) at position %" G_GUINT64_FORMAT, num_buffers, total_mems, size, + sink->current_pos); - filesink = GST_FILE_SINK (sink); + return gst_writev_buffers (GST_OBJECT_CAST (sink), fileno (sink->file), NULL, + buffers, num_buffers, mem_nums, total_mems, &sink->current_pos, 0); +} - gst_buffer_map (buffer, &info, GST_MAP_READ); +static GstFlowReturn +gst_file_sink_render_list_internal (GstFileSink * sink, + GstBufferList * buffer_list) +{ + GstFlowReturn flow; + GstBuffer **buffers; + guint8 *mem_nums; + guint total_mems; + gsize total_size = 0; + guint i, num_buffers; + + 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]; + total_size += gst_buffer_get_size (buffers[i]); + } - GST_DEBUG_OBJECT (filesink, - "writing %" G_GSIZE_FORMAT " bytes at %" G_GUINT64_FORMAT, - info.size, filesink->current_pos); + flow = + gst_file_sink_render_buffers (sink, buffers, num_buffers, mem_nums, + total_mems, total_size); - if (info.size > 0 && info.data != NULL) { - if (fwrite (info.data, info.size, 1, filesink->file) != 1) - goto handle_error; + return flow; - filesink->current_pos += info.size; +no_data: + { + GST_LOG_OBJECT (sink, "empty buffer list"); + return GST_FLOW_OK; } - gst_buffer_unmap (buffer, &info); +} - return GST_FLOW_OK; +static GstFlowReturn +gst_file_sink_flush_buffer (GstFileSink * filesink) +{ + GstFlowReturn flow_ret = GST_FLOW_OK; + + if (filesink->buffer) { + guint length; + + length = gst_buffer_list_length (filesink->buffer); + + if (length > 0) { + GST_DEBUG_OBJECT (filesink, "Flushing out buffer of size %u", + filesink->current_buffer_size); + flow_ret = + gst_file_sink_render_list_internal (filesink, filesink->buffer); + /* Remove all buffers from the list but keep the list. This ensures that + * we don't re-allocate the array storing the buffers all the time */ + gst_buffer_list_remove (filesink->buffer, 0, length); + filesink->current_buffer_size = 0; + } + } + + return flow_ret; +} + +static gboolean +has_sync_after_buffer (GstBuffer ** buffer, guint idx, gpointer user_data) +{ + if (GST_BUFFER_FLAG_IS_SET (*buffer, GST_BUFFER_FLAG_SYNC_AFTER)) { + gboolean *sync_after = user_data; + + *sync_after = TRUE; + return FALSE; + } -handle_error: + return TRUE; +} + +static gboolean +accumulate_size (GstBuffer ** buffer, guint idx, gpointer user_data) +{ + guint *size = user_data; + + *size += gst_buffer_get_size (*buffer); + + return TRUE; +} + +static GstFlowReturn +gst_file_sink_render_list (GstBaseSink * bsink, GstBufferList * buffer_list) +{ + GstFlowReturn flow; + GstFileSink *sink; + 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; + + gst_buffer_list_foreach (buffer_list, has_sync_after_buffer, &sync_after); + + if (sync_after || !sink->buffer) { + flow = gst_file_sink_flush_buffer (sink); + if (flow == GST_FLOW_OK) + flow = gst_file_sink_render_list_internal (sink, buffer_list); + } else { + guint size = 0; + gst_buffer_list_foreach (buffer_list, accumulate_size, &size); + + GST_DEBUG_OBJECT (sink, + "Queueing buffer list of %u bytes (%u buffers) at offset %" + G_GUINT64_FORMAT, size, num_buffers, + sink->current_pos + sink->current_buffer_size); + + for (i = 0; i < num_buffers; ++i) + gst_buffer_list_add (sink->buffer, + gst_buffer_ref (gst_buffer_list_get (buffer_list, i))); + sink->current_buffer_size += size; + + if (sink->current_buffer_size > sink->buffer_size) + flow = gst_file_sink_flush_buffer (sink); + else + flow = GST_FLOW_OK; + } + + if (flow == GST_FLOW_OK && sync_after) { + if (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; + } + } + + return flow; + +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; + gboolean sync_after; + + filesink = GST_FILE_SINK_CAST (sink); + + sync_after = GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_SYNC_AFTER); + + n_mem = gst_buffer_n_memory (buffer); + + if (n_mem > 0 && (sync_after || !filesink->buffer)) { + flow = gst_file_sink_flush_buffer (filesink); + if (flow == GST_FLOW_OK) + flow = + gst_file_sink_render_buffers (filesink, &buffer, 1, &n_mem, n_mem, + gst_buffer_get_size (buffer)); + } else if (n_mem > 0) { + GST_DEBUG_OBJECT (filesink, + "Queueing buffer of %" G_GSIZE_FORMAT " bytes at offset %" + G_GUINT64_FORMAT, gst_buffer_get_size (buffer), + filesink->current_pos + filesink->current_buffer_size); + + filesink->current_buffer_size += gst_buffer_get_size (buffer); + gst_buffer_list_add (filesink->buffer, gst_buffer_ref (buffer)); + + if (filesink->current_buffer_size > filesink->buffer_size) + flow = gst_file_sink_flush_buffer (filesink); + else + flow = GST_FLOW_OK; + } else { + flow = GST_FLOW_OK; + } + + if (flow == GST_FLOW_OK && sync_after) { + if (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, &info); - return GST_FLOW_ERROR; } + + return flow; } static gboolean