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 location="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_add_old_file (GstMultiFileSink * multifilesink,
175     gchar * fn);
176 static void gst_multi_file_sink_ensure_max_files (GstMultiFileSink *
177     multifilesink);
178 static gboolean gst_multi_file_sink_event (GstBaseSink * sink,
179     GstEvent * event);
180 
181 #define GST_TYPE_MULTI_FILE_SINK_NEXT (gst_multi_file_sink_next_get_type ())
182 static GType
gst_multi_file_sink_next_get_type(void)183 gst_multi_file_sink_next_get_type (void)
184 {
185   static GType multi_file_sink_next_type = 0;
186   static const GEnumValue next_types[] = {
187     {GST_MULTI_FILE_SINK_NEXT_BUFFER, "New file for each buffer", "buffer"},
188     {GST_MULTI_FILE_SINK_NEXT_DISCONT, "New file after each discontinuity",
189         "discont"},
190     {GST_MULTI_FILE_SINK_NEXT_KEY_FRAME, "New file at each key frame "
191           "(Useful for MPEG-TS segmenting)", "key-frame"},
192     {GST_MULTI_FILE_SINK_NEXT_KEY_UNIT_EVENT,
193         "New file after a force key unit event", "key-unit-event"},
194     {GST_MULTI_FILE_SINK_NEXT_MAX_SIZE, "New file when the configured maximum "
195           "file size would be exceeded with the next buffer or buffer list",
196         "max-size"},
197     {GST_MULTI_FILE_SINK_NEXT_MAX_DURATION,
198           "New file when the configured maximum "
199           "file duration would be exceeded with the next buffer or buffer list",
200         "max-duration"},
201     {0, NULL, NULL}
202   };
203 
204   if (!multi_file_sink_next_type) {
205     multi_file_sink_next_type =
206         g_enum_register_static ("GstMultiFileSinkNext", next_types);
207   }
208 
209   return multi_file_sink_next_type;
210 }
211 
212 #define gst_multi_file_sink_parent_class parent_class
213 G_DEFINE_TYPE (GstMultiFileSink, gst_multi_file_sink, GST_TYPE_BASE_SINK);
214 
215 static void
gst_multi_file_sink_class_init(GstMultiFileSinkClass * klass)216 gst_multi_file_sink_class_init (GstMultiFileSinkClass * klass)
217 {
218   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
219   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
220   GstBaseSinkClass *gstbasesink_class = GST_BASE_SINK_CLASS (klass);
221 
222   gobject_class->set_property = gst_multi_file_sink_set_property;
223   gobject_class->get_property = gst_multi_file_sink_get_property;
224 
225   g_object_class_install_property (gobject_class, PROP_LOCATION,
226       g_param_spec_string ("location", "File Location",
227           "Location of the file to write", NULL,
228           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
229 
230   g_object_class_install_property (gobject_class, PROP_INDEX,
231       g_param_spec_int ("index", "Index",
232           "Index to use with location property to create file names.  The "
233           "index is incremented by one for each buffer written.",
234           0, G_MAXINT, DEFAULT_INDEX,
235           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
236   /**
237    * GstMultiFileSink:post-messages:
238    *
239    * Post a message on the GstBus for each file.
240    */
241   g_object_class_install_property (gobject_class, PROP_POST_MESSAGES,
242       g_param_spec_boolean ("post-messages", "Post Messages",
243           "Post a message for each file with information of the buffer",
244           DEFAULT_POST_MESSAGES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
245   /**
246    * GstMultiFileSink:next-file:
247    *
248    * When to start a new file.
249    */
250   g_object_class_install_property (gobject_class, PROP_NEXT_FILE,
251       g_param_spec_enum ("next-file", "Next File",
252           "When to start a new file",
253           GST_TYPE_MULTI_FILE_SINK_NEXT, DEFAULT_NEXT_FILE,
254           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
255 
256 
257   /**
258    * GstMultiFileSink:max-files:
259    *
260    * Maximum number of files to keep on disk. Once the maximum is reached, old
261    * files start to be deleted to make room for new ones.
262    */
263   g_object_class_install_property (gobject_class, PROP_MAX_FILES,
264       g_param_spec_uint ("max-files", "Max files",
265           "Maximum number of files to keep on disk. Once the maximum is reached,"
266           "old files start to be deleted to make room for new ones.",
267           0, G_MAXUINT, DEFAULT_MAX_FILES,
268           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
269 
270   /**
271    * GstMultiFileSink:max-file-size:
272    *
273    * Maximum file size before starting a new file in max-size mode.
274    */
275   g_object_class_install_property (gobject_class, PROP_MAX_FILE_SIZE,
276       g_param_spec_uint64 ("max-file-size", "Maximum File Size",
277           "Maximum file size before starting a new file in max-size mode",
278           0, G_MAXUINT64, DEFAULT_MAX_FILE_SIZE,
279           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
280 
281   /**
282    * GstMultiFileSink:max-file-duration:
283    *
284    * Maximum file size before starting a new file in max-size mode.
285    */
286   g_object_class_install_property (gobject_class, PROP_MAX_FILE_DURATION,
287       g_param_spec_uint64 ("max-file-duration", "Maximum File Duration",
288           "Maximum file duration before starting a new file in max-size mode "
289           "(in nanoseconds)", 0, G_MAXUINT64, DEFAULT_MAX_FILE_DURATION,
290           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
291 
292   /**
293    * GstMultiFileSink:aggregate-gops:
294    *
295    * Whether to aggregate complete GOPs before doing any processing. Set this
296    * to TRUE to make sure each new file starts with a keyframe. This requires
297    * the upstream element to flag buffers containing key units and delta
298    * units correctly. At least the MPEG-PS and MPEG-TS muxers should be doing
299    * this.
300    *
301    * Since: 1.6
302    */
303   g_object_class_install_property (gobject_class, PROP_AGGREGATE_GOPS,
304       g_param_spec_boolean ("aggregate-gops", "Aggregate GOPs",
305           "Whether to aggregate GOPs and process them as a whole without "
306           "splitting", DEFAULT_AGGREGATE_GOPS,
307           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
308 
309   gobject_class->finalize = gst_multi_file_sink_finalize;
310 
311   gstbasesink_class->start = GST_DEBUG_FUNCPTR (gst_multi_file_sink_start);
312   gstbasesink_class->stop = GST_DEBUG_FUNCPTR (gst_multi_file_sink_stop);
313   gstbasesink_class->render = GST_DEBUG_FUNCPTR (gst_multi_file_sink_render);
314   gstbasesink_class->render_list =
315       GST_DEBUG_FUNCPTR (gst_multi_file_sink_render_list);
316   gstbasesink_class->set_caps =
317       GST_DEBUG_FUNCPTR (gst_multi_file_sink_set_caps);
318   gstbasesink_class->event = GST_DEBUG_FUNCPTR (gst_multi_file_sink_event);
319 
320   GST_DEBUG_CATEGORY_INIT (gst_multi_file_sink_debug, "multifilesink", 0,
321       "multifilesink element");
322 
323   gst_element_class_add_static_pad_template (gstelement_class, &sinktemplate);
324   gst_element_class_set_static_metadata (gstelement_class, "Multi-File Sink",
325       "Sink/File",
326       "Write buffers to a sequentially named set of files",
327       "David Schleef <ds@schleef.org>");
328 }
329 
330 static void
gst_multi_file_sink_init(GstMultiFileSink * multifilesink)331 gst_multi_file_sink_init (GstMultiFileSink * multifilesink)
332 {
333   multifilesink->filename = g_strdup (DEFAULT_LOCATION);
334   multifilesink->index = DEFAULT_INDEX;
335   multifilesink->post_messages = DEFAULT_POST_MESSAGES;
336   multifilesink->max_files = DEFAULT_MAX_FILES;
337   multifilesink->max_file_size = DEFAULT_MAX_FILE_SIZE;
338   multifilesink->max_file_duration = DEFAULT_MAX_FILE_DURATION;
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
gst_multi_file_sink_finalize(GObject * object)350 gst_multi_file_sink_finalize (GObject * object)
351 {
352   GstMultiFileSink *sink = GST_MULTI_FILE_SINK (object);
353 
354   g_free (sink->filename);
355 
356   G_OBJECT_CLASS (parent_class)->finalize (object);
357 }
358 
359 static gboolean
gst_multi_file_sink_set_location(GstMultiFileSink * sink,const gchar * location)360 gst_multi_file_sink_set_location (GstMultiFileSink * sink,
361     const gchar * location)
362 {
363   g_free (sink->filename);
364   /* FIXME: validate location to have just one %d */
365   sink->filename = g_strdup (location);
366 
367   return TRUE;
368 }
369 
370 static void
gst_multi_file_sink_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)371 gst_multi_file_sink_set_property (GObject * object, guint prop_id,
372     const GValue * value, GParamSpec * pspec)
373 {
374   GstMultiFileSink *sink = GST_MULTI_FILE_SINK (object);
375 
376   switch (prop_id) {
377     case PROP_LOCATION:
378       gst_multi_file_sink_set_location (sink, g_value_get_string (value));
379       break;
380     case PROP_INDEX:
381       sink->index = g_value_get_int (value);
382       break;
383     case PROP_POST_MESSAGES:
384       sink->post_messages = g_value_get_boolean (value);
385       break;
386     case PROP_NEXT_FILE:
387       sink->next_file = g_value_get_enum (value);
388       break;
389     case PROP_MAX_FILES:
390       sink->max_files = g_value_get_uint (value);
391       break;
392     case PROP_MAX_FILE_SIZE:
393       sink->max_file_size = g_value_get_uint64 (value);
394       break;
395     case PROP_MAX_FILE_DURATION:
396       sink->max_file_duration = g_value_get_uint64 (value);
397       break;
398     case PROP_AGGREGATE_GOPS:
399       sink->aggregate_gops = g_value_get_boolean (value);
400       break;
401     default:
402       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
403       break;
404   }
405 }
406 
407 static void
gst_multi_file_sink_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)408 gst_multi_file_sink_get_property (GObject * object, guint prop_id,
409     GValue * value, GParamSpec * pspec)
410 {
411   GstMultiFileSink *sink = GST_MULTI_FILE_SINK (object);
412 
413   switch (prop_id) {
414     case PROP_LOCATION:
415       g_value_set_string (value, sink->filename);
416       break;
417     case PROP_INDEX:
418       g_value_set_int (value, sink->index);
419       break;
420     case PROP_POST_MESSAGES:
421       g_value_set_boolean (value, sink->post_messages);
422       break;
423     case PROP_NEXT_FILE:
424       g_value_set_enum (value, sink->next_file);
425       break;
426     case PROP_MAX_FILES:
427       g_value_set_uint (value, sink->max_files);
428       break;
429     case PROP_MAX_FILE_SIZE:
430       g_value_set_uint64 (value, sink->max_file_size);
431       break;
432     case PROP_MAX_FILE_DURATION:
433       g_value_set_uint64 (value, sink->max_file_duration);
434       break;
435     case PROP_AGGREGATE_GOPS:
436       g_value_set_boolean (value, sink->aggregate_gops);
437       break;
438     default:
439       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
440       break;
441   }
442 }
443 
444 static gboolean
gst_multi_file_sink_start(GstBaseSink * bsink)445 gst_multi_file_sink_start (GstBaseSink * bsink)
446 {
447   GstMultiFileSink *sink = GST_MULTI_FILE_SINK (bsink);
448 
449   if (sink->aggregate_gops)
450     sink->gop_adapter = gst_adapter_new ();
451   sink->potential_next_gop = NULL;
452   sink->file_pts = GST_CLOCK_TIME_NONE;
453 
454   g_queue_init (&sink->old_files);
455 
456   return TRUE;
457 }
458 
459 static gboolean
gst_multi_file_sink_stop(GstBaseSink * sink)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   g_queue_foreach (&multifilesink->old_files, (GFunc) g_free, NULL);
494   g_queue_clear (&multifilesink->old_files);
495 
496   return TRUE;
497 }
498 
499 
500 static void
gst_multi_file_sink_post_message_full(GstMultiFileSink * multifilesink,GstClockTime timestamp,GstClockTime duration,GstClockTime offset,GstClockTime offset_end,GstClockTime running_time,GstClockTime stream_time,const char * filename)501 gst_multi_file_sink_post_message_full (GstMultiFileSink * multifilesink,
502     GstClockTime timestamp, GstClockTime duration, GstClockTime offset,
503     GstClockTime offset_end, GstClockTime running_time,
504     GstClockTime stream_time, const char *filename)
505 {
506   GstStructure *s;
507 
508   if (!multifilesink->post_messages)
509     return;
510 
511   s = gst_structure_new ("GstMultiFileSink",
512       "filename", G_TYPE_STRING, filename,
513       "index", G_TYPE_INT, multifilesink->index,
514       "timestamp", G_TYPE_UINT64, timestamp,
515       "stream-time", G_TYPE_UINT64, stream_time,
516       "running-time", G_TYPE_UINT64, running_time,
517       "duration", G_TYPE_UINT64, duration,
518       "offset", G_TYPE_UINT64, offset,
519       "offset-end", G_TYPE_UINT64, offset_end, NULL);
520 
521   gst_element_post_message (GST_ELEMENT_CAST (multifilesink),
522       gst_message_new_element (GST_OBJECT_CAST (multifilesink), s));
523 }
524 
525 static void
gst_multi_file_sink_post_message_from_time(GstMultiFileSink * multifilesink,GstClockTime timestamp,GstClockTime duration,const char * filename)526 gst_multi_file_sink_post_message_from_time (GstMultiFileSink * multifilesink,
527     GstClockTime timestamp, GstClockTime duration, const char *filename)
528 {
529   GstClockTime running_time, stream_time;
530   guint64 offset, offset_end;
531   GstSegment *segment;
532   GstFormat format;
533 
534   if (!multifilesink->post_messages)
535     return;
536 
537   segment = &GST_BASE_SINK (multifilesink)->segment;
538   format = segment->format;
539 
540   offset = -1;
541   offset_end = -1;
542 
543   running_time = gst_segment_to_running_time (segment, format, timestamp);
544   stream_time = gst_segment_to_stream_time (segment, format, timestamp);
545 
546   gst_multi_file_sink_post_message_full (multifilesink, timestamp, duration,
547       offset, offset_end, running_time, stream_time, filename);
548 }
549 
550 static void
gst_multi_file_sink_post_message(GstMultiFileSink * multifilesink,GstBuffer * buffer,const char * filename)551 gst_multi_file_sink_post_message (GstMultiFileSink * multifilesink,
552     GstBuffer * buffer, const char *filename)
553 {
554   GstClockTime duration, timestamp;
555   GstClockTime running_time, stream_time;
556   guint64 offset, offset_end;
557   GstSegment *segment;
558   GstFormat format;
559 
560   if (!multifilesink->post_messages)
561     return;
562 
563   segment = &GST_BASE_SINK (multifilesink)->segment;
564   format = segment->format;
565 
566   timestamp = GST_BUFFER_TIMESTAMP (buffer);
567   duration = GST_BUFFER_DURATION (buffer);
568   offset = GST_BUFFER_OFFSET (buffer);
569   offset_end = GST_BUFFER_OFFSET_END (buffer);
570 
571   running_time = gst_segment_to_running_time (segment, format, timestamp);
572   stream_time = gst_segment_to_stream_time (segment, format, timestamp);
573 
574   gst_multi_file_sink_post_message_full (multifilesink, timestamp, duration,
575       offset, offset_end, running_time, stream_time, filename);
576 }
577 
578 static gboolean
gst_multi_file_sink_write_stream_headers(GstMultiFileSink * sink)579 gst_multi_file_sink_write_stream_headers (GstMultiFileSink * sink)
580 {
581   int i;
582 
583   if (sink->streamheaders == NULL)
584     return TRUE;
585 
586   /* we want to write these at the beginning */
587   g_assert (sink->cur_file_size == 0);
588 
589   for (i = 0; i < sink->n_streamheaders; i++) {
590     GstBuffer *hdr;
591     GstMapInfo map;
592     int ret;
593 
594     hdr = sink->streamheaders[i];
595     gst_buffer_map (hdr, &map, GST_MAP_READ);
596     ret = fwrite (map.data, map.size, 1, sink->file);
597     gst_buffer_unmap (hdr, &map);
598 
599     if (ret != 1)
600       return FALSE;
601 
602     sink->cur_file_size += map.size;
603   }
604 
605   return TRUE;
606 }
607 
608 static GstFlowReturn
gst_multi_file_sink_write_buffer(GstMultiFileSink * multifilesink,GstBuffer * buffer)609 gst_multi_file_sink_write_buffer (GstMultiFileSink * multifilesink,
610     GstBuffer * buffer)
611 {
612   GstMapInfo map;
613   gchar *filename;
614   gboolean ret;
615   GError *error = NULL;
616   gboolean first_file = TRUE;
617 
618   gst_buffer_map (buffer, &map, GST_MAP_READ);
619 
620   switch (multifilesink->next_file) {
621     case GST_MULTI_FILE_SINK_NEXT_BUFFER:
622       gst_multi_file_sink_ensure_max_files (multifilesink);
623 
624       filename = g_strdup_printf (multifilesink->filename,
625           multifilesink->index);
626       ret = g_file_set_contents (filename, (char *) map.data, map.size, &error);
627       if (!ret)
628         goto write_error;
629 
630       gst_multi_file_sink_post_message (multifilesink, buffer, filename);
631 
632       gst_multi_file_sink_add_old_file (multifilesink, filename);
633 
634       multifilesink->index++;
635 
636       break;
637     case GST_MULTI_FILE_SINK_NEXT_DISCONT:
638       if (GST_BUFFER_IS_DISCONT (buffer)) {
639         if (multifilesink->file)
640           gst_multi_file_sink_close_file (multifilesink, buffer);
641       }
642 
643       if (multifilesink->file == NULL) {
644         if (!gst_multi_file_sink_open_next_file (multifilesink))
645           goto stdio_write_error;
646       }
647 
648       ret = fwrite (map.data, map.size, 1, multifilesink->file);
649       if (ret != 1)
650         goto stdio_write_error;
651 
652       break;
653     case GST_MULTI_FILE_SINK_NEXT_KEY_FRAME:
654       if (multifilesink->next_segment == GST_CLOCK_TIME_NONE) {
655         if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) {
656           multifilesink->next_segment = GST_BUFFER_TIMESTAMP (buffer) +
657               10 * GST_SECOND;
658         }
659       }
660 
661       if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer) &&
662           GST_BUFFER_TIMESTAMP (buffer) >= multifilesink->next_segment &&
663           !GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT)) {
664         if (multifilesink->file) {
665           first_file = FALSE;
666           gst_multi_file_sink_close_file (multifilesink, buffer);
667         }
668         multifilesink->next_segment += 10 * GST_SECOND;
669       }
670 
671       if (multifilesink->file == NULL) {
672         if (!gst_multi_file_sink_open_next_file (multifilesink))
673           goto stdio_write_error;
674 
675         if (!first_file)
676           gst_multi_file_sink_write_stream_headers (multifilesink);
677       }
678 
679       ret = fwrite (map.data, map.size, 1, multifilesink->file);
680       if (ret != 1)
681         goto stdio_write_error;
682 
683       break;
684     case GST_MULTI_FILE_SINK_NEXT_KEY_UNIT_EVENT:
685       if (multifilesink->file == NULL) {
686         if (!gst_multi_file_sink_open_next_file (multifilesink))
687           goto stdio_write_error;
688 
689         /* we don't need to write stream headers here, they will be inserted in
690          * the stream by upstream elements if key unit events have
691          * all_headers=true set
692          */
693       }
694 
695       ret = fwrite (map.data, map.size, 1, multifilesink->file);
696 
697       if (ret != 1)
698         goto stdio_write_error;
699 
700       break;
701     case GST_MULTI_FILE_SINK_NEXT_MAX_SIZE:{
702       guint64 new_size;
703 
704       new_size = multifilesink->cur_file_size + map.size;
705       if (new_size > multifilesink->max_file_size) {
706 
707         GST_INFO_OBJECT (multifilesink, "current size: %" G_GUINT64_FORMAT
708             ", new_size: %" G_GUINT64_FORMAT ", max. size %" G_GUINT64_FORMAT,
709             multifilesink->cur_file_size, new_size,
710             multifilesink->max_file_size);
711 
712         if (multifilesink->file != NULL) {
713           first_file = FALSE;
714           gst_multi_file_sink_close_file (multifilesink, buffer);
715         }
716       }
717 
718       if (multifilesink->file == NULL) {
719         if (!gst_multi_file_sink_open_next_file (multifilesink))
720           goto stdio_write_error;
721 
722         if (!first_file)
723           gst_multi_file_sink_write_stream_headers (multifilesink);
724       }
725 
726       ret = fwrite (map.data, map.size, 1, multifilesink->file);
727 
728       if (ret != 1)
729         goto stdio_write_error;
730 
731       multifilesink->cur_file_size += map.size;
732       break;
733     }
734     case GST_MULTI_FILE_SINK_NEXT_MAX_DURATION:{
735       GstClockTime new_duration = 0;
736 
737       if (GST_BUFFER_PTS_IS_VALID (buffer)
738           && GST_CLOCK_TIME_IS_VALID (multifilesink->file_pts)) {
739         /* The new duration will extend to this new buffer pts ... */
740         new_duration = GST_BUFFER_PTS (buffer) - multifilesink->file_pts;
741         /* ... and duration (if it has one) */
742         if (GST_BUFFER_DURATION_IS_VALID (buffer))
743           new_duration += GST_BUFFER_DURATION (buffer);
744       }
745 
746       if (new_duration > multifilesink->max_file_duration) {
747 
748         GST_INFO_OBJECT (multifilesink,
749             "new_duration: %" G_GUINT64_FORMAT ", max. duration %"
750             G_GUINT64_FORMAT, new_duration, multifilesink->max_file_duration);
751 
752         if (multifilesink->file != NULL) {
753           first_file = FALSE;
754           gst_multi_file_sink_close_file (multifilesink, buffer);
755         }
756       }
757 
758       if (multifilesink->file == NULL) {
759         if (!gst_multi_file_sink_open_next_file (multifilesink))
760           goto stdio_write_error;
761 
762         multifilesink->file_pts = GST_BUFFER_PTS (buffer);
763         if (!first_file)
764           gst_multi_file_sink_write_stream_headers (multifilesink);
765       }
766 
767       ret = fwrite (map.data, map.size, 1, multifilesink->file);
768 
769       if (ret != 1)
770         goto stdio_write_error;
771 
772       break;
773     }
774     default:
775       g_assert_not_reached ();
776   }
777 
778   gst_buffer_unmap (buffer, &map);
779   return GST_FLOW_OK;
780 
781   /* ERRORS */
782 write_error:
783   {
784     switch (error->code) {
785       case G_FILE_ERROR_NOSPC:{
786         GST_ELEMENT_ERROR (multifilesink, RESOURCE, NO_SPACE_LEFT, (NULL),
787             (NULL));
788         break;
789       }
790       default:{
791         GST_ELEMENT_ERROR (multifilesink, RESOURCE, WRITE,
792             ("Error while writing to file \"%s\".", filename),
793             ("%s", g_strerror (errno)));
794       }
795     }
796     g_error_free (error);
797     g_free (filename);
798 
799     gst_buffer_unmap (buffer, &map);
800     return GST_FLOW_ERROR;
801   }
802 stdio_write_error:
803   switch (errno) {
804     case ENOSPC:
805       GST_ELEMENT_ERROR (multifilesink, RESOURCE, NO_SPACE_LEFT,
806           ("Error while writing to file."), ("%s", g_strerror (errno)));
807       break;
808     default:
809       GST_ELEMENT_ERROR (multifilesink, RESOURCE, WRITE,
810           ("Error while writing to file."), ("%s", g_strerror (errno)));
811   }
812   gst_buffer_unmap (buffer, &map);
813   return GST_FLOW_ERROR;
814 }
815 
816 static GstFlowReturn
gst_multi_file_sink_render(GstBaseSink * bsink,GstBuffer * buffer)817 gst_multi_file_sink_render (GstBaseSink * bsink, GstBuffer * buffer)
818 {
819   GstMultiFileSink *sink = GST_MULTI_FILE_SINK (bsink);
820   GstFlowReturn flow = GST_FLOW_OK;
821   gboolean key_unit, header;
822 
823   header = GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_HEADER);
824   key_unit = !GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT);
825 
826   if (sink->aggregate_gops) {
827     GstBuffer *gop_buffer = NULL;
828     guint avail;
829 
830     avail = gst_adapter_available (sink->gop_adapter);
831 
832     GST_LOG_OBJECT (sink, "aggregate GOP: received %s%s unit buffer: "
833         "%" GST_PTR_FORMAT,
834         (key_unit) ? "key" : "delta", (header) ? " header" : "", buffer);
835 
836     /* If it's a header buffer, it might potentially be for the next GOP */
837     if (header) {
838       GST_LOG_OBJECT (sink, "Accumulating buffer to potential next GOP");
839       sink->potential_next_gop =
840           g_list_append (sink->potential_next_gop, gst_buffer_ref (buffer));
841     } else {
842       if (key_unit && avail > 0) {
843         GstClockTime pts, dts;
844         GST_LOG_OBJECT (sink, "Grabbing pending completed GOP");
845         pts = gst_adapter_prev_pts_at_offset (sink->gop_adapter, 0, NULL);
846         dts = gst_adapter_prev_dts_at_offset (sink->gop_adapter, 0, NULL);
847         gop_buffer = gst_adapter_take_buffer (sink->gop_adapter, avail);
848         GST_BUFFER_PTS (gop_buffer) = pts;
849         GST_BUFFER_DTS (gop_buffer) = dts;
850       }
851 
852       /* just accumulate the buffer */
853       if (sink->potential_next_gop) {
854         GList *tmp;
855         GST_LOG_OBJECT (sink,
856             "Carrying over pending next GOP data into adapter");
857         /* If we have pending data, put that first in the adapter */
858         for (tmp = sink->potential_next_gop; tmp; tmp = tmp->next) {
859           GstBuffer *tmpb = (GstBuffer *) tmp->data;
860           gst_adapter_push (sink->gop_adapter, tmpb);
861         }
862         g_list_free (sink->potential_next_gop);
863         sink->potential_next_gop = NULL;
864       }
865       GST_LOG_OBJECT (sink, "storing buffer in adapter");
866       gst_adapter_push (sink->gop_adapter, gst_buffer_ref (buffer));
867 
868       if (gop_buffer != NULL) {
869         GST_DEBUG_OBJECT (sink, "writing out pending GOP, %u bytes", avail);
870         GST_DEBUG_OBJECT (sink,
871             "gop buffer pts:%" GST_TIME_FORMAT " dts:%" GST_TIME_FORMAT
872             " duration:%" GST_TIME_FORMAT,
873             GST_TIME_ARGS (GST_BUFFER_PTS (gop_buffer)),
874             GST_TIME_ARGS (GST_BUFFER_DTS (gop_buffer)),
875             GST_TIME_ARGS (GST_BUFFER_DURATION (gop_buffer)));
876         flow = gst_multi_file_sink_write_buffer (sink, gop_buffer);
877         gst_buffer_unref (gop_buffer);
878       }
879     }
880   } else {
881     flow = gst_multi_file_sink_write_buffer (sink, buffer);
882   }
883   return flow;
884 }
885 
886 static gboolean
buffer_list_copy_data(GstBuffer ** buf,guint idx,gpointer data)887 buffer_list_copy_data (GstBuffer ** buf, guint idx, gpointer data)
888 {
889   GstBuffer *dest = data;
890   guint num, i;
891 
892   if (idx == 0)
893     gst_buffer_copy_into (dest, *buf, GST_BUFFER_COPY_METADATA, 0, -1);
894 
895   num = gst_buffer_n_memory (*buf);
896   for (i = 0; i < num; ++i) {
897     GstMemory *mem;
898 
899     mem = gst_buffer_get_memory (*buf, i);
900     gst_buffer_append_memory (dest, mem);
901   }
902 
903   return TRUE;
904 }
905 
906 /* Our assumption for now is that the buffers in a buffer list should always
907  * end up in the same file. If someone wants different behaviour, they'll just
908  * have to add a property for that. */
909 static GstFlowReturn
gst_multi_file_sink_render_list(GstBaseSink * sink,GstBufferList * list)910 gst_multi_file_sink_render_list (GstBaseSink * sink, GstBufferList * list)
911 {
912   GstBuffer *buf;
913   guint size;
914 
915   size = gst_buffer_list_calculate_size (list);
916   GST_LOG_OBJECT (sink, "total size of buffer list %p: %u", list, size);
917 
918   /* copy all buffers in the list into one single buffer, so we can use
919    * the normal render function (FIXME: optimise to avoid the memcpy) */
920   buf = gst_buffer_new ();
921   gst_buffer_list_foreach (list, buffer_list_copy_data, buf);
922   g_assert (gst_buffer_get_size (buf) == size);
923 
924   gst_multi_file_sink_render (sink, buf);
925   gst_buffer_unref (buf);
926 
927   return GST_FLOW_OK;
928 }
929 
930 static gboolean
gst_multi_file_sink_set_caps(GstBaseSink * sink,GstCaps * caps)931 gst_multi_file_sink_set_caps (GstBaseSink * sink, GstCaps * caps)
932 {
933   GstMultiFileSink *multifilesink;
934   GstStructure *structure;
935 
936   multifilesink = GST_MULTI_FILE_SINK (sink);
937 
938   structure = gst_caps_get_structure (caps, 0);
939   if (structure) {
940     const GValue *value;
941 
942     value = gst_structure_get_value (structure, "streamheader");
943 
944     if (GST_VALUE_HOLDS_ARRAY (value)) {
945       int i;
946 
947       if (multifilesink->streamheaders) {
948         for (i = 0; i < multifilesink->n_streamheaders; i++) {
949           gst_buffer_unref (multifilesink->streamheaders[i]);
950         }
951         g_free (multifilesink->streamheaders);
952       }
953 
954       multifilesink->n_streamheaders = gst_value_array_get_size (value);
955       multifilesink->streamheaders =
956           g_malloc (sizeof (GstBuffer *) * multifilesink->n_streamheaders);
957 
958       for (i = 0; i < multifilesink->n_streamheaders; i++) {
959         multifilesink->streamheaders[i] =
960             gst_buffer_ref (gst_value_get_buffer (gst_value_array_get_value
961                 (value, i)));
962       }
963     }
964   }
965 
966   return TRUE;
967 }
968 
969 /* Takes ownership of the filename string */
970 static void
gst_multi_file_sink_add_old_file(GstMultiFileSink * multifilesink,gchar * fn)971 gst_multi_file_sink_add_old_file (GstMultiFileSink * multifilesink, gchar * fn)
972 {
973   /* Only add file to the list if a max_files limit is set, otherwise we never
974    * prune the list and memory just builds up until the pipeline is stopped. */
975   if (multifilesink->max_files > 0) {
976     g_queue_push_tail (&multifilesink->old_files, fn);
977   } else {
978     g_free (fn);
979   }
980 }
981 
982 static void
gst_multi_file_sink_ensure_max_files(GstMultiFileSink * multifilesink)983 gst_multi_file_sink_ensure_max_files (GstMultiFileSink * multifilesink)
984 {
985   guint max_files = multifilesink->max_files;
986 
987   if (max_files == 0)
988     return;
989 
990   while (g_queue_get_length (&multifilesink->old_files) >= max_files) {
991     gchar *filename;
992 
993     filename = g_queue_pop_head (&multifilesink->old_files);
994     g_remove (filename);
995     g_free (filename);
996   }
997 }
998 
999 static gboolean
gst_multi_file_sink_event(GstBaseSink * sink,GstEvent * event)1000 gst_multi_file_sink_event (GstBaseSink * sink, GstEvent * event)
1001 {
1002   GstMultiFileSink *multifilesink;
1003   gchar *filename;
1004 
1005   multifilesink = GST_MULTI_FILE_SINK (sink);
1006 
1007   switch (GST_EVENT_TYPE (event)) {
1008     case GST_EVENT_CUSTOM_DOWNSTREAM:
1009     {
1010       GstClockTime timestamp, duration;
1011       GstClockTime running_time, stream_time;
1012       guint64 offset, offset_end;
1013       gboolean all_headers;
1014       guint count;
1015 
1016       if (multifilesink->next_file != GST_MULTI_FILE_SINK_NEXT_KEY_UNIT_EVENT ||
1017           !gst_video_event_is_force_key_unit (event))
1018         goto out;
1019 
1020       gst_video_event_parse_downstream_force_key_unit (event, &timestamp,
1021           &stream_time, &running_time, &all_headers, &count);
1022 
1023       if (multifilesink->force_key_unit_count != -1 &&
1024           multifilesink->force_key_unit_count == count)
1025         goto out;
1026 
1027       multifilesink->force_key_unit_count = count;
1028 
1029       if (multifilesink->file) {
1030         duration = GST_CLOCK_TIME_NONE;
1031         offset = offset_end = -1;
1032         filename = g_strdup_printf (multifilesink->filename,
1033             multifilesink->index);
1034 
1035         gst_multi_file_sink_close_file (multifilesink, NULL);
1036 
1037         gst_multi_file_sink_post_message_full (multifilesink, timestamp,
1038             duration, offset, offset_end, running_time, stream_time, filename);
1039         g_free (filename);
1040       }
1041 
1042       if (multifilesink->file == NULL) {
1043         if (!gst_multi_file_sink_open_next_file (multifilesink))
1044           goto stdio_write_error;
1045       }
1046 
1047       break;
1048     }
1049     case GST_EVENT_EOS:
1050       if (multifilesink->aggregate_gops) {
1051         GstBuffer *buf = gst_buffer_new ();
1052 
1053         /* push key unit buffer to force writing out the pending GOP data */
1054         GST_INFO_OBJECT (sink, "EOS, write pending GOP data");
1055         GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
1056         gst_multi_file_sink_render (sink, buf);
1057         gst_buffer_unref (buf);
1058       }
1059       if (multifilesink->file) {
1060         gchar *filename;
1061 
1062         filename = g_strdup_printf (multifilesink->filename,
1063             multifilesink->index);
1064 
1065         gst_multi_file_sink_close_file (multifilesink, NULL);
1066 
1067         gst_multi_file_sink_post_message_from_time (multifilesink,
1068             GST_BASE_SINK (multifilesink)->segment.position, -1, filename);
1069         g_free (filename);
1070       }
1071       break;
1072     default:
1073       break;
1074   }
1075 
1076 out:
1077   return GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
1078 
1079   /* ERRORS */
1080 stdio_write_error:
1081   {
1082     GST_ELEMENT_ERROR (multifilesink, RESOURCE, WRITE,
1083         ("Error while writing to file."), (NULL));
1084     gst_event_unref (event);
1085     return FALSE;
1086   }
1087 }
1088 
1089 static gboolean
gst_multi_file_sink_open_next_file(GstMultiFileSink * multifilesink)1090 gst_multi_file_sink_open_next_file (GstMultiFileSink * multifilesink)
1091 {
1092   char *filename;
1093 
1094   g_return_val_if_fail (multifilesink->file == NULL, FALSE);
1095 
1096   gst_multi_file_sink_ensure_max_files (multifilesink);
1097 
1098   filename = g_strdup_printf (multifilesink->filename, multifilesink->index);
1099   multifilesink->file = g_fopen (filename, "wb");
1100   if (multifilesink->file == NULL) {
1101     g_free (filename);
1102     return FALSE;
1103   }
1104 
1105   GST_INFO_OBJECT (multifilesink, "opening file %s", filename);
1106 
1107   gst_multi_file_sink_add_old_file (multifilesink, filename);
1108 
1109   multifilesink->cur_file_size = 0;
1110   return TRUE;
1111 }
1112 
1113 static void
gst_multi_file_sink_close_file(GstMultiFileSink * multifilesink,GstBuffer * buffer)1114 gst_multi_file_sink_close_file (GstMultiFileSink * multifilesink,
1115     GstBuffer * buffer)
1116 {
1117   char *filename;
1118 
1119   fclose (multifilesink->file);
1120   multifilesink->file = NULL;
1121 
1122   if (buffer) {
1123     filename = g_strdup_printf (multifilesink->filename, multifilesink->index);
1124     gst_multi_file_sink_post_message (multifilesink, buffer, filename);
1125     g_free (filename);
1126   }
1127 
1128   multifilesink->index++;
1129 }
1130