splitmuxsink: Allow time and bytes to reach their respective thresholds
[platform/upstream/gst-plugins-good.git] / gst / multifile / gstmultifilesink.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  *                    2006 David A. Schleef <ds@schleef.org>
6  *                    2011 Collabora Ltd. <tim.muller@collabora.co.uk>
7  *                    2015 Tim-Philipp Müller <tim@centricular.com>
8  *
9  * gstmultifilesink.c:
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Library General Public
13  * License as published by the Free Software Foundation; either
14  * version 2 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Library General Public License for more details.
20  *
21  * You should have received a copy of the GNU Library General Public
22  * License along with this library; if not, write to the
23  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
24  * Boston, MA 02110-1301, USA.
25  */
26 /**
27  * SECTION:element-multifilesink
28  * @see_also: #GstFileSrc
29  *
30  * Write incoming data to a series of sequentially-named files.
31  *
32  * This element is usually used with data where each buffer is an
33  * independent unit of data in its own right (e.g. raw video buffers or
34  * encoded JPEG or PNG images) or with streamable container formats such
35  * as MPEG-TS or MPEG-PS.
36  *
37  * It is not possible to use this element to create independently playable
38  * mp4 files, use the splitmuxsink element for that instead.
39  *
40  * The filename property should contain a string with a \%d placeholder that will
41  * be substituted with the index for each filename.
42  *
43  * If the #GstMultiFileSink:post-messages property is #TRUE, it sends an application
44  * message named
45  * <classname>&quot;GstMultiFileSink&quot;</classname> after writing each
46  * buffer.
47  *
48  * The message's structure contains these fields:
49  * <itemizedlist>
50  * <listitem>
51  *   <para>
52  *   #gchar *
53  *   <classname>&quot;filename&quot;</classname>:
54  *   the filename where the buffer was written.
55  *   </para>
56  * </listitem>
57  * <listitem>
58  *   <para>
59  *   #gint
60  *   <classname>&quot;index&quot;</classname>:
61  *   the index of the buffer.
62  *   </para>
63  * </listitem>
64  * <listitem>
65  *   <para>
66  *   #GstClockTime
67  *   <classname>&quot;timestamp&quot;</classname>:
68  *   the timestamp of the buffer.
69  *   </para>
70  * </listitem>
71  * <listitem>
72  *   <para>
73  *   #GstClockTime
74  *   <classname>&quot;stream-time&quot;</classname>:
75  *   the stream time of the buffer.
76  *   </para>
77  * </listitem>
78  * <listitem>
79  *   <para>
80  *   #GstClockTime
81  *   <classname>&quot;running-time&quot;</classname>:
82  *   the running_time of the buffer.
83  *   </para>
84  * </listitem>
85  * <listitem>
86  *   <para>
87  *   #GstClockTime
88  *   <classname>&quot;duration&quot;</classname>:
89  *   the duration of the buffer.
90  *   </para>
91  * </listitem>
92  * <listitem>
93  *   <para>
94  *   #guint64
95  *   <classname>&quot;offset&quot;</classname>:
96  *   the offset of the buffer that triggered the message.
97  *   </para>
98  * </listitem>
99  * <listitem>
100  *   <para>
101  *   #guint64
102  *   <classname>&quot;offset-end&quot;</classname>:
103  *   the offset-end of the buffer that triggered the message.
104  *   </para>
105  * </listitem>
106  * </itemizedlist>
107  *
108  * <refsect2>
109  * <title>Example launch line</title>
110  * |[
111  * gst-launch-1.0 audiotestsrc ! multifilesink
112  * gst-launch-1.0 videotestsrc ! multifilesink post-messages=true filename="frame%d"
113  * ]|
114  * </refsect2>
115  */
116
117 #ifdef HAVE_CONFIG_H
118 #  include "config.h"
119 #endif
120 #include <gst/base/gstbasetransform.h>
121 #include <gst/video/video.h>
122 #include <glib/gstdio.h>
123 #include "gstmultifilesink.h"
124
125 static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
126     GST_PAD_SINK,
127     GST_PAD_ALWAYS,
128     GST_STATIC_CAPS_ANY);
129
130 GST_DEBUG_CATEGORY_STATIC (gst_multi_file_sink_debug);
131 #define GST_CAT_DEFAULT gst_multi_file_sink_debug
132
133 #define DEFAULT_LOCATION "%05d"
134 #define DEFAULT_INDEX 0
135 #define DEFAULT_POST_MESSAGES FALSE
136 #define DEFAULT_NEXT_FILE GST_MULTI_FILE_SINK_NEXT_BUFFER
137 #define DEFAULT_MAX_FILES 0
138 #define DEFAULT_MAX_FILE_SIZE G_GUINT64_CONSTANT(2*1024*1024*1024)
139 #define DEFAULT_MAX_FILE_DURATION GST_CLOCK_TIME_NONE
140 #define DEFAULT_AGGREGATE_GOPS FALSE
141
142 enum
143 {
144   PROP_0,
145   PROP_LOCATION,
146   PROP_INDEX,
147   PROP_POST_MESSAGES,
148   PROP_NEXT_FILE,
149   PROP_MAX_FILES,
150   PROP_MAX_FILE_SIZE,
151   PROP_MAX_FILE_DURATION,
152   PROP_AGGREGATE_GOPS
153 };
154
155 static void gst_multi_file_sink_finalize (GObject * object);
156
157 static void gst_multi_file_sink_set_property (GObject * object, guint prop_id,
158     const GValue * value, GParamSpec * pspec);
159 static void gst_multi_file_sink_get_property (GObject * object, guint prop_id,
160     GValue * value, GParamSpec * pspec);
161
162 static gboolean gst_multi_file_sink_start (GstBaseSink * bsink);
163 static gboolean gst_multi_file_sink_stop (GstBaseSink * sink);
164 static GstFlowReturn gst_multi_file_sink_render (GstBaseSink * sink,
165     GstBuffer * buffer);
166 static GstFlowReturn gst_multi_file_sink_render_list (GstBaseSink * sink,
167     GstBufferList * buffer_list);
168 static gboolean gst_multi_file_sink_set_caps (GstBaseSink * sink,
169     GstCaps * caps);
170 static gboolean gst_multi_file_sink_open_next_file (GstMultiFileSink *
171     multifilesink);
172 static void gst_multi_file_sink_close_file (GstMultiFileSink * multifilesink,
173     GstBuffer * buffer);
174 static void gst_multi_file_sink_ensure_max_files (GstMultiFileSink *
175     multifilesink);
176 static gboolean gst_multi_file_sink_event (GstBaseSink * sink,
177     GstEvent * event);
178
179 #define GST_TYPE_MULTI_FILE_SINK_NEXT (gst_multi_file_sink_next_get_type ())
180 static GType
181 gst_multi_file_sink_next_get_type (void)
182 {
183   static GType multi_file_sink_next_type = 0;
184   static const GEnumValue next_types[] = {
185     {GST_MULTI_FILE_SINK_NEXT_BUFFER, "New file for each buffer", "buffer"},
186     {GST_MULTI_FILE_SINK_NEXT_DISCONT, "New file after each discontinuity",
187         "discont"},
188     {GST_MULTI_FILE_SINK_NEXT_KEY_FRAME, "New file at each key frame "
189           "(Useful for MPEG-TS segmenting)", "key-frame"},
190     {GST_MULTI_FILE_SINK_NEXT_KEY_UNIT_EVENT,
191         "New file after a force key unit event", "key-unit-event"},
192     {GST_MULTI_FILE_SINK_NEXT_MAX_SIZE, "New file when the configured maximum "
193           "file size would be exceeded with the next buffer or buffer list",
194         "max-size"},
195     {GST_MULTI_FILE_SINK_NEXT_MAX_DURATION,
196           "New file when the configured maximum "
197           "file duration would be exceeded with the next buffer or buffer list",
198         "max-duration"},
199     {0, NULL, NULL}
200   };
201
202   if (!multi_file_sink_next_type) {
203     multi_file_sink_next_type =
204         g_enum_register_static ("GstMultiFileSinkNext", next_types);
205   }
206
207   return multi_file_sink_next_type;
208 }
209
210 #define gst_multi_file_sink_parent_class parent_class
211 G_DEFINE_TYPE (GstMultiFileSink, gst_multi_file_sink, GST_TYPE_BASE_SINK);
212
213 static void
214 gst_multi_file_sink_class_init (GstMultiFileSinkClass * klass)
215 {
216   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
217   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
218   GstBaseSinkClass *gstbasesink_class = GST_BASE_SINK_CLASS (klass);
219
220   gobject_class->set_property = gst_multi_file_sink_set_property;
221   gobject_class->get_property = gst_multi_file_sink_get_property;
222
223   g_object_class_install_property (gobject_class, PROP_LOCATION,
224       g_param_spec_string ("location", "File Location",
225           "Location of the file to write", NULL,
226           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
227
228   g_object_class_install_property (gobject_class, PROP_INDEX,
229       g_param_spec_int ("index", "Index",
230           "Index to use with location property to create file names.  The "
231           "index is incremented by one for each buffer written.",
232           0, G_MAXINT, DEFAULT_INDEX,
233           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
234   /**
235    * GstMultiFileSink:post-messages:
236    *
237    * Post a message on the GstBus for each file.
238    */
239   g_object_class_install_property (gobject_class, PROP_POST_MESSAGES,
240       g_param_spec_boolean ("post-messages", "Post Messages",
241           "Post a message for each file with information of the buffer",
242           DEFAULT_POST_MESSAGES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
243   /**
244    * GstMultiFileSink:next-file:
245    *
246    * When to start a new file.
247    */
248   g_object_class_install_property (gobject_class, PROP_NEXT_FILE,
249       g_param_spec_enum ("next-file", "Next File",
250           "When to start a new file",
251           GST_TYPE_MULTI_FILE_SINK_NEXT, DEFAULT_NEXT_FILE,
252           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
253
254
255   /**
256    * GstMultiFileSink:max-files:
257    *
258    * Maximum number of files to keep on disk. Once the maximum is reached, old
259    * files start to be deleted to make room for new ones.
260    */
261   g_object_class_install_property (gobject_class, PROP_MAX_FILES,
262       g_param_spec_uint ("max-files", "Max files",
263           "Maximum number of files to keep on disk. Once the maximum is reached,"
264           "old files start to be deleted to make room for new ones.",
265           0, G_MAXUINT, DEFAULT_MAX_FILES,
266           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
267
268   /**
269    * GstMultiFileSink:max-file-size:
270    *
271    * Maximum file size before starting a new file in max-size mode.
272    */
273   g_object_class_install_property (gobject_class, PROP_MAX_FILE_SIZE,
274       g_param_spec_uint64 ("max-file-size", "Maximum File Size",
275           "Maximum file size before starting a new file in max-size mode",
276           0, G_MAXUINT64, DEFAULT_MAX_FILE_SIZE,
277           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
278
279   /**
280    * GstMultiFileSink:max-file-duration:
281    *
282    * Maximum file size before starting a new file in max-size mode.
283    */
284   g_object_class_install_property (gobject_class, PROP_MAX_FILE_DURATION,
285       g_param_spec_uint64 ("max-file-duration", "Maximum File Duration",
286           "Maximum file duration before starting a new file in max-size mode",
287           0, G_MAXUINT64, DEFAULT_MAX_FILE_DURATION,
288           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
289
290   /**
291    * GstMultiFileSink:aggregate-gops:
292    *
293    * Whether to aggregate complete GOPs before doing any processing. Set this
294    * to TRUE to make sure each new file starts with a keyframe. This requires
295    * the upstream element to flag buffers containing key units and delta
296    * units correctly. At least the MPEG-PS and MPEG-TS muxers should be doing
297    * this.
298    *
299    * Since: 1.6
300    */
301   g_object_class_install_property (gobject_class, PROP_AGGREGATE_GOPS,
302       g_param_spec_boolean ("aggregate-gops", "Aggregate GOPs",
303           "Whether to aggregate GOPs and process them as a whole without "
304           "splitting", DEFAULT_AGGREGATE_GOPS,
305           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
306
307   gobject_class->finalize = gst_multi_file_sink_finalize;
308
309   gstbasesink_class->start = GST_DEBUG_FUNCPTR (gst_multi_file_sink_start);
310   gstbasesink_class->stop = GST_DEBUG_FUNCPTR (gst_multi_file_sink_stop);
311   gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_multi_file_sink_render);
312   gstbasesink_class->render_list =
313       GST_DEBUG_FUNCPTR (gst_multi_file_sink_render_list);
314   gstbasesink_class->set_caps =
315       GST_DEBUG_FUNCPTR (gst_multi_file_sink_set_caps);
316   gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_multi_file_sink_event);
317
318   GST_DEBUG_CATEGORY_INIT (gst_multi_file_sink_debug, "multifilesink", 0,
319       "multifilesink element");
320
321   gst_element_class_add_static_pad_template (gstelement_class, &sinktemplate);
322   gst_element_class_set_static_metadata (gstelement_class, "Multi-File Sink",
323       "Sink/File",
324       "Write buffers to a sequentially named set of files",
325       "David Schleef <ds@schleef.org>");
326 }
327
328 static void
329 gst_multi_file_sink_init (GstMultiFileSink * multifilesink)
330 {
331   multifilesink->filename = g_strdup (DEFAULT_LOCATION);
332   multifilesink->index = DEFAULT_INDEX;
333   multifilesink->post_messages = DEFAULT_POST_MESSAGES;
334   multifilesink->max_files = DEFAULT_MAX_FILES;
335   multifilesink->max_file_size = DEFAULT_MAX_FILE_SIZE;
336   multifilesink->max_file_duration = DEFAULT_MAX_FILE_DURATION;
337   multifilesink->files = NULL;
338   multifilesink->n_files = 0;
339
340   multifilesink->aggregate_gops = DEFAULT_AGGREGATE_GOPS;
341   multifilesink->gop_adapter = NULL;
342
343   gst_base_sink_set_sync (GST_BASE_SINK (multifilesink), FALSE);
344
345   multifilesink->next_segment = GST_CLOCK_TIME_NONE;
346   multifilesink->force_key_unit_count = -1;
347 }
348
349 static void
350 gst_multi_file_sink_finalize (GObject * object)
351 {
352   GstMultiFileSink *sink = GST_MULTI_FILE_SINK (object);
353
354   g_free (sink->filename);
355   g_slist_foreach (sink->files, (GFunc) g_free, NULL);
356   g_slist_free (sink->files);
357
358   G_OBJECT_CLASS (parent_class)->finalize (object);
359 }
360
361 static gboolean
362 gst_multi_file_sink_set_location (GstMultiFileSink * sink,
363     const gchar * location)
364 {
365   g_free (sink->filename);
366   /* FIXME: validate location to have just one %d */
367   sink->filename = g_strdup (location);
368
369   return TRUE;
370 }
371
372 static void
373 gst_multi_file_sink_set_property (GObject * object, guint prop_id,
374     const GValue * value, GParamSpec * pspec)
375 {
376   GstMultiFileSink *sink = GST_MULTI_FILE_SINK (object);
377
378   switch (prop_id) {
379     case PROP_LOCATION:
380       gst_multi_file_sink_set_location (sink, g_value_get_string (value));
381       break;
382     case PROP_INDEX:
383       sink->index = g_value_get_int (value);
384       break;
385     case PROP_POST_MESSAGES:
386       sink->post_messages = g_value_get_boolean (value);
387       break;
388     case PROP_NEXT_FILE:
389       sink->next_file = g_value_get_enum (value);
390       break;
391     case PROP_MAX_FILES:
392       sink->max_files = g_value_get_uint (value);
393       break;
394     case PROP_MAX_FILE_SIZE:
395       sink->max_file_size = g_value_get_uint64 (value);
396       break;
397     case PROP_MAX_FILE_DURATION:
398       sink->max_file_duration = g_value_get_uint64 (value);
399       break;
400     case PROP_AGGREGATE_GOPS:
401       sink->aggregate_gops = g_value_get_boolean (value);
402       break;
403     default:
404       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
405       break;
406   }
407 }
408
409 static void
410 gst_multi_file_sink_get_property (GObject * object, guint prop_id,
411     GValue * value, GParamSpec * pspec)
412 {
413   GstMultiFileSink *sink = GST_MULTI_FILE_SINK (object);
414
415   switch (prop_id) {
416     case PROP_LOCATION:
417       g_value_set_string (value, sink->filename);
418       break;
419     case PROP_INDEX:
420       g_value_set_int (value, sink->index);
421       break;
422     case PROP_POST_MESSAGES:
423       g_value_set_boolean (value, sink->post_messages);
424       break;
425     case PROP_NEXT_FILE:
426       g_value_set_enum (value, sink->next_file);
427       break;
428     case PROP_MAX_FILES:
429       g_value_set_uint (value, sink->max_files);
430       break;
431     case PROP_MAX_FILE_SIZE:
432       g_value_set_uint64 (value, sink->max_file_size);
433       break;
434     case PROP_MAX_FILE_DURATION:
435       g_value_set_uint64 (value, sink->max_file_duration);
436       break;
437     case PROP_AGGREGATE_GOPS:
438       g_value_set_boolean (value, sink->aggregate_gops);
439       break;
440     default:
441       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
442       break;
443   }
444 }
445
446 static gboolean
447 gst_multi_file_sink_start (GstBaseSink * bsink)
448 {
449   GstMultiFileSink *sink = GST_MULTI_FILE_SINK (bsink);
450
451   if (sink->aggregate_gops)
452     sink->gop_adapter = gst_adapter_new ();
453   sink->potential_next_gop = NULL;
454   sink->file_pts = GST_CLOCK_TIME_NONE;
455
456   return TRUE;
457 }
458
459 static gboolean
460 gst_multi_file_sink_stop (GstBaseSink * sink)
461 {
462   GstMultiFileSink *multifilesink;
463   int i;
464
465   multifilesink = GST_MULTI_FILE_SINK (sink);
466
467   if (multifilesink->file != NULL) {
468     fclose (multifilesink->file);
469     multifilesink->file = NULL;
470   }
471
472   if (multifilesink->streamheaders) {
473     for (i = 0; i < multifilesink->n_streamheaders; i++) {
474       gst_buffer_unref (multifilesink->streamheaders[i]);
475     }
476     g_free (multifilesink->streamheaders);
477     multifilesink->streamheaders = NULL;
478   }
479
480   if (multifilesink->gop_adapter != NULL) {
481     g_object_unref (multifilesink->gop_adapter);
482     multifilesink->gop_adapter = NULL;
483   }
484
485   if (multifilesink->potential_next_gop != NULL) {
486     g_list_free_full (multifilesink->potential_next_gop,
487         (GDestroyNotify) gst_buffer_unref);
488     multifilesink->potential_next_gop = NULL;
489   }
490
491   multifilesink->force_key_unit_count = -1;
492
493   return TRUE;
494 }
495
496
497 static void
498 gst_multi_file_sink_post_message_full (GstMultiFileSink * multifilesink,
499     GstClockTime timestamp, GstClockTime duration, GstClockTime offset,
500     GstClockTime offset_end, GstClockTime running_time,
501     GstClockTime stream_time, const char *filename)
502 {
503   GstStructure *s;
504
505   if (!multifilesink->post_messages)
506     return;
507
508   s = gst_structure_new ("GstMultiFileSink",
509       "filename", G_TYPE_STRING, filename,
510       "index", G_TYPE_INT, multifilesink->index,
511       "timestamp", G_TYPE_UINT64, timestamp,
512       "stream-time", G_TYPE_UINT64, stream_time,
513       "running-time", G_TYPE_UINT64, running_time,
514       "duration", G_TYPE_UINT64, duration,
515       "offset", G_TYPE_UINT64, offset,
516       "offset-end", G_TYPE_UINT64, offset_end, NULL);
517
518   gst_element_post_message (GST_ELEMENT_CAST (multifilesink),
519       gst_message_new_element (GST_OBJECT_CAST (multifilesink), s));
520 }
521
522 static void
523 gst_multi_file_sink_post_message_from_time (GstMultiFileSink * multifilesink,
524     GstClockTime timestamp, GstClockTime duration, const char *filename)
525 {
526   GstClockTime running_time, stream_time;
527   guint64 offset, offset_end;
528   GstSegment *segment;
529   GstFormat format;
530
531   if (!multifilesink->post_messages)
532     return;
533
534   segment = &GST_BASE_SINK (multifilesink)->segment;
535   format = segment->format;
536
537   offset = -1;
538   offset_end = -1;
539
540   running_time = gst_segment_to_running_time (segment, format, timestamp);
541   stream_time = gst_segment_to_stream_time (segment, format, timestamp);
542
543   gst_multi_file_sink_post_message_full (multifilesink, timestamp, duration,
544       offset, offset_end, running_time, stream_time, filename);
545 }
546
547 static void
548 gst_multi_file_sink_post_message (GstMultiFileSink * multifilesink,
549     GstBuffer * buffer, const char *filename)
550 {
551   GstClockTime duration, timestamp;
552   GstClockTime running_time, stream_time;
553   guint64 offset, offset_end;
554   GstSegment *segment;
555   GstFormat format;
556
557   if (!multifilesink->post_messages)
558     return;
559
560   segment = &GST_BASE_SINK (multifilesink)->segment;
561   format = segment->format;
562
563   timestamp = GST_BUFFER_TIMESTAMP (buffer);
564   duration = GST_BUFFER_DURATION (buffer);
565   offset = GST_BUFFER_OFFSET (buffer);
566   offset_end = GST_BUFFER_OFFSET_END (buffer);
567
568   running_time = gst_segment_to_running_time (segment, format, timestamp);
569   stream_time = gst_segment_to_stream_time (segment, format, timestamp);
570
571   gst_multi_file_sink_post_message_full (multifilesink, timestamp, duration,
572       offset, offset_end, running_time, stream_time, filename);
573 }
574
575 static gboolean
576 gst_multi_file_sink_write_stream_headers (GstMultiFileSink * sink)
577 {
578   int i;
579
580   if (sink->streamheaders == NULL)
581     return TRUE;
582
583   GST_DEBUG_OBJECT (sink, "Writing stream headers");
584
585   /* we want to write these at the beginning */
586   g_assert (sink->cur_file_size == 0);
587
588   for (i = 0; i < sink->n_streamheaders; i++) {
589     GstBuffer *hdr;
590     GstMapInfo map;
591     int ret;
592
593     hdr = sink->streamheaders[i];
594     gst_buffer_map (hdr, &map, GST_MAP_READ);
595     ret = fwrite (map.data, map.size, 1, sink->file);
596     gst_buffer_unmap (hdr, &map);
597
598     if (ret != 1)
599       return FALSE;
600
601     sink->cur_file_size += map.size;
602   }
603
604   return TRUE;
605 }
606
607 static GstFlowReturn
608 gst_multi_file_sink_write_buffer (GstMultiFileSink * multifilesink,
609     GstBuffer * buffer)
610 {
611   GstMapInfo map;
612   gboolean ret;
613   gboolean first_file = TRUE;
614
615   gst_buffer_map (buffer, &map, GST_MAP_READ);
616
617   switch (multifilesink->next_file) {
618     case GST_MULTI_FILE_SINK_NEXT_BUFFER:
619       if (multifilesink->files != NULL)
620         first_file = FALSE;
621       if (!gst_multi_file_sink_open_next_file (multifilesink))
622         goto stdio_write_error;
623       if (first_file == FALSE)
624         gst_multi_file_sink_write_stream_headers (multifilesink);
625       GST_DEBUG_OBJECT (multifilesink,
626           "Writing buffer data (%" G_GSIZE_FORMAT " bytes) to new file",
627           map.size);
628       ret = fwrite (map.data, map.size, 1, multifilesink->file);
629       if (ret != 1) {
630         gst_multi_file_sink_close_file (multifilesink, NULL);
631         goto stdio_write_error;
632       }
633
634       gst_multi_file_sink_close_file (multifilesink, buffer);
635       break;
636     case GST_MULTI_FILE_SINK_NEXT_DISCONT:
637       if (GST_BUFFER_IS_DISCONT (buffer)) {
638         if (multifilesink->file)
639           gst_multi_file_sink_close_file (multifilesink, buffer);
640       }
641
642       if (multifilesink->file == NULL) {
643         if (!gst_multi_file_sink_open_next_file (multifilesink))
644           goto stdio_write_error;
645       }
646
647       ret = fwrite (map.data, map.size, 1, multifilesink->file);
648       if (ret != 1)
649         goto stdio_write_error;
650
651       break;
652     case GST_MULTI_FILE_SINK_NEXT_KEY_FRAME:
653       if (multifilesink->next_segment == GST_CLOCK_TIME_NONE) {
654         if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) {
655           multifilesink->next_segment = GST_BUFFER_TIMESTAMP (buffer) +
656               10 * GST_SECOND;
657         }
658       }
659
660       if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer) &&
661           GST_BUFFER_TIMESTAMP (buffer) >= multifilesink->next_segment &&
662           !GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT)) {
663         if (multifilesink->file) {
664           first_file = FALSE;
665           gst_multi_file_sink_close_file (multifilesink, buffer);
666         }
667         multifilesink->next_segment += 10 * GST_SECOND;
668       }
669
670       if (multifilesink->file == NULL) {
671         if (!gst_multi_file_sink_open_next_file (multifilesink))
672           goto stdio_write_error;
673
674         if (!first_file)
675           gst_multi_file_sink_write_stream_headers (multifilesink);
676       }
677
678       ret = fwrite (map.data, map.size, 1, multifilesink->file);
679       if (ret != 1)
680         goto stdio_write_error;
681
682       break;
683     case GST_MULTI_FILE_SINK_NEXT_KEY_UNIT_EVENT:
684       if (multifilesink->file == NULL) {
685         if (!gst_multi_file_sink_open_next_file (multifilesink))
686           goto stdio_write_error;
687
688         /* we don't need to write stream headers here, they will be inserted in
689          * the stream by upstream elements if key unit events have
690          * all_headers=true set
691          */
692       }
693
694       ret = fwrite (map.data, map.size, 1, multifilesink->file);
695
696       if (ret != 1)
697         goto stdio_write_error;
698
699       break;
700     case GST_MULTI_FILE_SINK_NEXT_MAX_SIZE:{
701       guint64 new_size;
702
703       new_size = multifilesink->cur_file_size + map.size;
704       if (new_size > multifilesink->max_file_size) {
705
706         GST_INFO_OBJECT (multifilesink, "current size: %" G_GUINT64_FORMAT
707             ", new_size: %" G_GUINT64_FORMAT ", max. size %" G_GUINT64_FORMAT,
708             multifilesink->cur_file_size, new_size,
709             multifilesink->max_file_size);
710
711         if (multifilesink->file != NULL) {
712           first_file = FALSE;
713           gst_multi_file_sink_close_file (multifilesink, buffer);
714         }
715       }
716
717       if (multifilesink->file == NULL) {
718         if (!gst_multi_file_sink_open_next_file (multifilesink))
719           goto stdio_write_error;
720
721         if (!first_file)
722           gst_multi_file_sink_write_stream_headers (multifilesink);
723       }
724
725       ret = fwrite (map.data, map.size, 1, multifilesink->file);
726
727       if (ret != 1)
728         goto stdio_write_error;
729
730       multifilesink->cur_file_size += map.size;
731       break;
732     }
733     case GST_MULTI_FILE_SINK_NEXT_MAX_DURATION:{
734       GstClockTime new_duration = 0;
735
736       if (GST_BUFFER_PTS_IS_VALID (buffer)
737           && GST_CLOCK_TIME_IS_VALID (multifilesink->file_pts)) {
738         /* The new duration will extend to this new buffer pts ... */
739         new_duration = GST_BUFFER_PTS (buffer) - multifilesink->file_pts;
740         /* ... and duration (if it has one) */
741         if (GST_BUFFER_DURATION_IS_VALID (buffer))
742           new_duration += GST_BUFFER_DURATION (buffer);
743       }
744
745       if (new_duration > multifilesink->max_file_duration) {
746
747         GST_INFO_OBJECT (multifilesink,
748             "new_duration: %" G_GUINT64_FORMAT ", max. duration %"
749             G_GUINT64_FORMAT, new_duration, multifilesink->max_file_duration);
750
751         if (multifilesink->file != NULL) {
752           first_file = FALSE;
753           gst_multi_file_sink_close_file (multifilesink, buffer);
754         }
755       }
756
757       if (multifilesink->file == NULL) {
758         if (!gst_multi_file_sink_open_next_file (multifilesink))
759           goto stdio_write_error;
760
761         multifilesink->file_pts = GST_BUFFER_PTS (buffer);
762         if (!first_file)
763           gst_multi_file_sink_write_stream_headers (multifilesink);
764       }
765
766       ret = fwrite (map.data, map.size, 1, multifilesink->file);
767
768       if (ret != 1)
769         goto stdio_write_error;
770
771       break;
772     }
773     default:
774       g_assert_not_reached ();
775   }
776
777   gst_buffer_unmap (buffer, &map);
778   return GST_FLOW_OK;
779
780   /* ERRORS */
781 stdio_write_error:
782   switch (errno) {
783     case ENOSPC:
784       GST_ELEMENT_ERROR (multifilesink, RESOURCE, NO_SPACE_LEFT,
785           ("Error while writing to file."), ("%s", g_strerror (errno)));
786       break;
787     default:
788       GST_ELEMENT_ERROR (multifilesink, RESOURCE, WRITE,
789           ("Error while writing to file."), ("%s", g_strerror (errno)));
790   }
791   gst_buffer_unmap (buffer, &map);
792   return GST_FLOW_ERROR;
793 }
794
795 static GstFlowReturn
796 gst_multi_file_sink_render (GstBaseSink * bsink, GstBuffer * buffer)
797 {
798   GstMultiFileSink *sink = GST_MULTI_FILE_SINK (bsink);
799   GstFlowReturn flow = GST_FLOW_OK;
800   gboolean key_unit, header;
801
802   header = GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_HEADER);
803   key_unit = !GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT);
804
805   if (sink->aggregate_gops) {
806     GstBuffer *gop_buffer = NULL;
807     guint avail;
808
809     avail = gst_adapter_available (sink->gop_adapter);
810
811     GST_LOG_OBJECT (sink, "aggregate GOP: received %s%s unit buffer: "
812         "%" GST_PTR_FORMAT,
813         (key_unit) ? "key" : "delta", (header) ? " header" : "", buffer);
814
815     /* If it's a header buffer, it might potentially be for the next GOP */
816     if (header) {
817       GST_LOG_OBJECT (sink, "Accumulating buffer to potential next GOP");
818       sink->potential_next_gop =
819           g_list_append (sink->potential_next_gop, gst_buffer_ref (buffer));
820     } else {
821       if (key_unit && avail > 0) {
822         GstClockTime pts, dts;
823         GST_LOG_OBJECT (sink, "Grabbing pending completed GOP");
824         pts = gst_adapter_prev_pts_at_offset (sink->gop_adapter, 0, NULL);
825         dts = gst_adapter_prev_dts_at_offset (sink->gop_adapter, 0, NULL);
826         gop_buffer = gst_adapter_take_buffer (sink->gop_adapter, avail);
827         GST_BUFFER_PTS (gop_buffer) = pts;
828         GST_BUFFER_DTS (gop_buffer) = dts;
829       }
830
831       /* just accumulate the buffer */
832       if (sink->potential_next_gop) {
833         GList *tmp;
834         GST_LOG_OBJECT (sink,
835             "Carrying over pending next GOP data into adapter");
836         /* If we have pending data, put that first in the adapter */
837         for (tmp = sink->potential_next_gop; tmp; tmp = tmp->next) {
838           GstBuffer *tmpb = (GstBuffer *) tmp->data;
839           gst_adapter_push (sink->gop_adapter, tmpb);
840         }
841         g_list_free (sink->potential_next_gop);
842         sink->potential_next_gop = NULL;
843       }
844       GST_LOG_OBJECT (sink, "storing buffer in adapter");
845       gst_adapter_push (sink->gop_adapter, gst_buffer_ref (buffer));
846
847       if (gop_buffer != NULL) {
848         GST_DEBUG_OBJECT (sink, "writing out pending GOP, %u bytes", avail);
849         GST_DEBUG_OBJECT (sink,
850             "gop buffer pts:%" GST_TIME_FORMAT " dts:%" GST_TIME_FORMAT
851             " duration:%" GST_TIME_FORMAT,
852             GST_TIME_ARGS (GST_BUFFER_PTS (gop_buffer)),
853             GST_TIME_ARGS (GST_BUFFER_DTS (gop_buffer)),
854             GST_TIME_ARGS (GST_BUFFER_DURATION (gop_buffer)));
855         flow = gst_multi_file_sink_write_buffer (sink, gop_buffer);
856         gst_buffer_unref (gop_buffer);
857       }
858     }
859   } else {
860     flow = gst_multi_file_sink_write_buffer (sink, buffer);
861   }
862   return flow;
863 }
864
865 static gboolean
866 buffer_list_calc_size (GstBuffer ** buf, guint idx, gpointer data)
867 {
868   guint *p_size = data;
869   gsize buf_size;
870
871   buf_size = gst_buffer_get_size (*buf);
872   GST_TRACE ("buffer %u has size %" G_GSIZE_FORMAT, idx, buf_size);
873   *p_size += buf_size;
874
875   return TRUE;
876 }
877
878 static gboolean
879 buffer_list_copy_data (GstBuffer ** buf, guint idx, gpointer data)
880 {
881   GstBuffer *dest = data;
882   guint num, i;
883
884   if (idx == 0)
885     gst_buffer_copy_into (dest, *buf, GST_BUFFER_COPY_METADATA, 0, -1);
886
887   num = gst_buffer_n_memory (*buf);
888   for (i = 0; i < num; ++i) {
889     GstMemory *mem;
890
891     mem = gst_buffer_get_memory (*buf, i);
892     gst_buffer_append_memory (dest, mem);
893   }
894
895   return TRUE;
896 }
897
898 /* Our assumption for now is that the buffers in a buffer list should always
899  * end up in the same file. If someone wants different behaviour, they'll just
900  * have to add a property for that. */
901 static GstFlowReturn
902 gst_multi_file_sink_render_list (GstBaseSink * sink, GstBufferList * list)
903 {
904   GstBuffer *buf;
905   guint size = 0;
906
907   gst_buffer_list_foreach (list, buffer_list_calc_size, &size);
908   GST_LOG_OBJECT (sink, "total size of buffer list %p: %u", list, size);
909
910   /* copy all buffers in the list into one single buffer, so we can use
911    * the normal render function (FIXME: optimise to avoid the memcpy) */
912   buf = gst_buffer_new ();
913   gst_buffer_list_foreach (list, buffer_list_copy_data, buf);
914   g_assert (gst_buffer_get_size (buf) == size);
915
916   gst_multi_file_sink_render (sink, buf);
917   gst_buffer_unref (buf);
918
919   return GST_FLOW_OK;
920 }
921
922 static gboolean
923 gst_multi_file_sink_set_caps (GstBaseSink * sink, GstCaps * caps)
924 {
925   GstMultiFileSink *multifilesink;
926   GstStructure *structure;
927
928   multifilesink = GST_MULTI_FILE_SINK (sink);
929
930   structure = gst_caps_get_structure (caps, 0);
931   if (structure) {
932     const GValue *value;
933
934     value = gst_structure_get_value (structure, "streamheader");
935
936     if (GST_VALUE_HOLDS_ARRAY (value)) {
937       int i;
938
939       if (multifilesink->streamheaders) {
940         for (i = 0; i < multifilesink->n_streamheaders; i++) {
941           gst_buffer_unref (multifilesink->streamheaders[i]);
942         }
943         g_free (multifilesink->streamheaders);
944       }
945
946       multifilesink->n_streamheaders = gst_value_array_get_size (value);
947       multifilesink->streamheaders =
948           g_malloc (sizeof (GstBuffer *) * multifilesink->n_streamheaders);
949
950       for (i = 0; i < multifilesink->n_streamheaders; i++) {
951         multifilesink->streamheaders[i] =
952             gst_buffer_ref (gst_value_get_buffer (gst_value_array_get_value
953                 (value, i)));
954       }
955     }
956   }
957
958   return TRUE;
959 }
960
961 static void
962 gst_multi_file_sink_ensure_max_files (GstMultiFileSink * multifilesink)
963 {
964   char *filename;
965
966   while (multifilesink->max_files &&
967       multifilesink->n_files >= multifilesink->max_files) {
968     filename = multifilesink->files->data;
969     g_remove (filename);
970     g_free (filename);
971     multifilesink->files = g_slist_delete_link (multifilesink->files,
972         multifilesink->files);
973     multifilesink->n_files -= 1;
974   }
975 }
976
977 static gboolean
978 gst_multi_file_sink_event (GstBaseSink * sink, GstEvent * event)
979 {
980   GstMultiFileSink *multifilesink;
981   gchar *filename;
982
983   multifilesink = GST_MULTI_FILE_SINK (sink);
984
985   switch (GST_EVENT_TYPE (event)) {
986     case GST_EVENT_CUSTOM_DOWNSTREAM:
987     {
988       GstClockTime timestamp, duration;
989       GstClockTime running_time, stream_time;
990       guint64 offset, offset_end;
991       gboolean all_headers;
992       guint count;
993
994       if (multifilesink->next_file != GST_MULTI_FILE_SINK_NEXT_KEY_UNIT_EVENT ||
995           !gst_video_event_is_force_key_unit (event))
996         goto out;
997
998       gst_video_event_parse_downstream_force_key_unit (event, &timestamp,
999           &stream_time, &running_time, &all_headers, &count);
1000
1001       if (multifilesink->force_key_unit_count != -1 &&
1002           multifilesink->force_key_unit_count == count)
1003         goto out;
1004
1005       multifilesink->force_key_unit_count = count;
1006
1007       if (multifilesink->file) {
1008         duration = GST_CLOCK_TIME_NONE;
1009         offset = offset_end = -1;
1010         filename = g_strdup_printf (multifilesink->filename,
1011             multifilesink->index);
1012
1013         gst_multi_file_sink_close_file (multifilesink, NULL);
1014
1015         gst_multi_file_sink_post_message_full (multifilesink, timestamp,
1016             duration, offset, offset_end, running_time, stream_time, filename);
1017         g_free (filename);
1018       }
1019
1020       if (multifilesink->file == NULL) {
1021         if (!gst_multi_file_sink_open_next_file (multifilesink))
1022           goto stdio_write_error;
1023       }
1024
1025       break;
1026     }
1027     case GST_EVENT_EOS:
1028       if (multifilesink->aggregate_gops) {
1029         GstBuffer *buf = gst_buffer_new ();
1030
1031         /* push key unit buffer to force writing out the pending GOP data */
1032         GST_INFO_OBJECT (sink, "EOS, write pending GOP data");
1033         GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
1034         gst_multi_file_sink_render (sink, buf);
1035         gst_buffer_unref (buf);
1036       }
1037       if (multifilesink->file) {
1038         gchar *filename;
1039
1040         filename = g_strdup_printf (multifilesink->filename,
1041             multifilesink->index);
1042
1043         gst_multi_file_sink_close_file (multifilesink, NULL);
1044
1045         gst_multi_file_sink_post_message_from_time (multifilesink,
1046             GST_BASE_SINK (multifilesink)->segment.position, -1, filename);
1047         g_free (filename);
1048       }
1049       break;
1050     default:
1051       break;
1052   }
1053
1054 out:
1055   return GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
1056
1057   /* ERRORS */
1058 stdio_write_error:
1059   {
1060     GST_ELEMENT_ERROR (multifilesink, RESOURCE, WRITE,
1061         ("Error while writing to file."), (NULL));
1062     gst_event_unref (event);
1063     return FALSE;
1064   }
1065 }
1066
1067 static gboolean
1068 gst_multi_file_sink_open_next_file (GstMultiFileSink * multifilesink)
1069 {
1070   char *filename;
1071
1072   g_return_val_if_fail (multifilesink->file == NULL, FALSE);
1073
1074   gst_multi_file_sink_ensure_max_files (multifilesink);
1075   filename = g_strdup_printf (multifilesink->filename, multifilesink->index);
1076   multifilesink->file = g_fopen (filename, "wb");
1077   if (multifilesink->file == NULL) {
1078     g_free (filename);
1079     return FALSE;
1080   }
1081
1082   GST_INFO_OBJECT (multifilesink, "opening file %s", filename);
1083   multifilesink->files = g_slist_append (multifilesink->files, filename);
1084   multifilesink->n_files += 1;
1085
1086   multifilesink->cur_file_size = 0;
1087   return TRUE;
1088 }
1089
1090 static void
1091 gst_multi_file_sink_close_file (GstMultiFileSink * multifilesink,
1092     GstBuffer * buffer)
1093 {
1094   char *filename;
1095
1096   fclose (multifilesink->file);
1097   multifilesink->file = NULL;
1098
1099   if (buffer) {
1100     filename = g_strdup_printf (multifilesink->filename, multifilesink->index);
1101     gst_multi_file_sink_post_message (multifilesink, buffer, filename);
1102     g_free (filename);
1103   }
1104
1105   multifilesink->index++;
1106 }