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>"GstMultiFileSink"</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>"filename"</classname>:
54 * the filename where the buffer was written.
55 * </para>
56 * </listitem>
57 * <listitem>
58 * <para>
59 * #gint
60 * <classname>"index"</classname>:
61 * the index of the buffer.
62 * </para>
63 * </listitem>
64 * <listitem>
65 * <para>
66 * #GstClockTime
67 * <classname>"timestamp"</classname>:
68 * the timestamp of the buffer.
69 * </para>
70 * </listitem>
71 * <listitem>
72 * <para>
73 * #GstClockTime
74 * <classname>"stream-time"</classname>:
75 * the stream time of the buffer.
76 * </para>
77 * </listitem>
78 * <listitem>
79 * <para>
80 * #GstClockTime
81 * <classname>"running-time"</classname>:
82 * the running_time of the buffer.
83 * </para>
84 * </listitem>
85 * <listitem>
86 * <para>
87 * #GstClockTime
88 * <classname>"duration"</classname>:
89 * the duration of the buffer.
90 * </para>
91 * </listitem>
92 * <listitem>
93 * <para>
94 * #guint64
95 * <classname>"offset"</classname>:
96 * the offset of the buffer that triggered the message.
97 * </para>
98 * </listitem>
99 * <listitem>
100 * <para>
101 * #guint64
102 * <classname>"offset-end"</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, ×tamp,
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