1 /* OGG muxer plugin for GStreamer
2 * Copyright (C) 2004 Wim Taymans <wim@fluendo.com>
3 * Copyright (C) 2006 Thomas Vander Stichele <thomas at apestaart dot org>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21 /**
22 * SECTION:element-oggmux
23 * @title: oggmux
24 * @see_also: <link linkend="gst-plugins-base-plugins-oggdemux">oggdemux</link>
25 *
26 * This element merges streams (audio and video) into ogg files.
27 *
28 * ## Example pipelines
29 * |[
30 * gst-launch-1.0 v4l2src num-buffers=500 ! video/x-raw,width=320,height=240 ! videoconvert ! videorate ! theoraenc ! oggmux ! filesink location=video.ogg
31 * ]|
32 * Encodes a video stream captured from a v4l2-compatible camera to Ogg/Theora
33 * (the encoding will stop automatically after 500 frames)
34 *
35 */
36
37 #ifdef HAVE_CONFIG_H
38 #include "config.h"
39 #endif
40
41 #include <gst/gst.h>
42 #include <gst/base/gstbytewriter.h>
43 #include <gst/audio/audio.h>
44 #include <gst/tag/tag.h>
45
46 #include "gstoggelements.h"
47 #include "gstoggmux.h"
48
49 /* memcpy - if someone knows a way to get rid of it, please speak up
50 * note: the ogg docs even say you need this... */
51 #include <string.h>
52 #include <time.h>
53 #include <stdlib.h> /* rand, srand, atoi */
54
55 GST_DEBUG_CATEGORY_STATIC (gst_ogg_mux_debug);
56 #define GST_CAT_DEFAULT gst_ogg_mux_debug
57
58 /* This isn't generally what you'd want with an end-time macro, because
59 technically the end time of a buffer with invalid duration is invalid. But
60 for sorting ogg pages this is what we want. */
61 #define GST_BUFFER_END_TIME(buf) \
62 (GST_BUFFER_DURATION_IS_VALID (buf) \
63 ? GST_BUFFER_TIMESTAMP (buf) + GST_BUFFER_DURATION (buf) \
64 : GST_BUFFER_TIMESTAMP (buf))
65
66 #define GST_GP_FORMAT "[gp %8" G_GINT64_FORMAT "]"
67 #define GST_GP_CAST(_gp) ((gint64) _gp)
68
69 /* set to 0.5 seconds by default */
70 #define DEFAULT_MAX_DELAY G_GINT64_CONSTANT(500000000)
71 #define DEFAULT_MAX_PAGE_DELAY G_GINT64_CONSTANT(500000000)
72 #define DEFAULT_MAX_TOLERANCE G_GINT64_CONSTANT(40000000)
73 #define DEFAULT_SKELETON FALSE
74
75 enum
76 {
77 ARG_0,
78 ARG_MAX_DELAY,
79 ARG_MAX_PAGE_DELAY,
80 ARG_MAX_TOLERANCE,
81 ARG_SKELETON
82 };
83
84 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
85 GST_PAD_SRC,
86 GST_PAD_ALWAYS,
87 GST_STATIC_CAPS ("application/ogg; audio/ogg; video/ogg")
88 );
89
90 static GstStaticPadTemplate video_sink_factory =
91 GST_STATIC_PAD_TEMPLATE ("video_%u",
92 GST_PAD_SINK,
93 GST_PAD_REQUEST,
94 GST_STATIC_CAPS ("video/x-theora; "
95 "application/x-ogm-video; video/x-dirac; "
96 "video/x-smoke; video/x-vp8; video/x-daala")
97 );
98
99 static GstStaticPadTemplate audio_sink_factory =
100 GST_STATIC_PAD_TEMPLATE ("audio_%u",
101 GST_PAD_SINK,
102 GST_PAD_REQUEST,
103 GST_STATIC_CAPS
104 ("audio/x-vorbis; audio/x-flac; audio/x-speex; audio/x-celt; "
105 "application/x-ogm-audio; audio/x-opus")
106 );
107
108 static GstStaticPadTemplate subtitle_sink_factory =
109 GST_STATIC_PAD_TEMPLATE ("subtitle_%u",
110 GST_PAD_SINK,
111 GST_PAD_REQUEST,
112 GST_STATIC_CAPS ("text/x-cmml, encoded = (boolean) TRUE; "
113 "subtitle/x-kate; application/x-kate")
114 );
115
116 static void gst_ogg_mux_finalize (GObject * object);
117
118 static GstFlowReturn gst_ogg_mux_collected (GstCollectPads * pads,
119 GstOggMux * ogg_mux);
120 static gboolean gst_ogg_mux_sink_event (GstCollectPads * pads,
121 GstCollectData * pad, GstEvent * event, gpointer user_data);
122 static gboolean gst_ogg_mux_handle_src_event (GstPad * pad, GstObject * parent,
123 GstEvent * event);
124 static GstPad *gst_ogg_mux_request_new_pad (GstElement * element,
125 GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
126 static void gst_ogg_mux_release_pad (GstElement * element, GstPad * pad);
127 static void gst_ogg_pad_data_reset (GstOggMux * ogg_mux,
128 GstOggPadData * pad_data);
129
130 static void gst_ogg_mux_set_property (GObject * object,
131 guint prop_id, const GValue * value, GParamSpec * pspec);
132 static void gst_ogg_mux_get_property (GObject * object,
133 guint prop_id, GValue * value, GParamSpec * pspec);
134 static GstStateChangeReturn gst_ogg_mux_change_state (GstElement * element,
135 GstStateChange transition);
136
137 /*static guint gst_ogg_mux_signals[LAST_SIGNAL] = { 0 }; */
138
139
140 #define gst_ogg_mux_parent_class parent_class
141 G_DEFINE_TYPE_WITH_CODE (GstOggMux, gst_ogg_mux, GST_TYPE_ELEMENT,
142 G_IMPLEMENT_INTERFACE (GST_TYPE_PRESET, NULL));
143 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (oggmux, "oggmux", GST_RANK_PRIMARY,
144 GST_TYPE_OGG_MUX, GST_DEBUG_CATEGORY_INIT (gst_ogg_mux_debug, "oggmux", 0,
145 "ogg muxer"));
146
147 static void
gst_ogg_mux_class_init(GstOggMuxClass * klass)148 gst_ogg_mux_class_init (GstOggMuxClass * klass)
149 {
150 GObjectClass *gobject_class;
151 GstElementClass *gstelement_class;
152
153 gobject_class = (GObjectClass *) klass;
154 gstelement_class = (GstElementClass *) klass;
155
156 gobject_class->finalize = gst_ogg_mux_finalize;
157 gobject_class->get_property = gst_ogg_mux_get_property;
158 gobject_class->set_property = gst_ogg_mux_set_property;
159
160 gst_element_class_add_static_pad_template (gstelement_class, &src_factory);
161 gst_element_class_add_static_pad_template (gstelement_class,
162 &video_sink_factory);
163 gst_element_class_add_static_pad_template (gstelement_class,
164 &audio_sink_factory);
165 gst_element_class_add_static_pad_template (gstelement_class,
166 &subtitle_sink_factory);
167
168 gst_element_class_set_static_metadata (gstelement_class,
169 "Ogg muxer", "Codec/Muxer",
170 "mux ogg streams (info about ogg: http://xiph.org)",
171 "Wim Taymans <wim@fluendo.com>");
172
173 gstelement_class->request_new_pad = gst_ogg_mux_request_new_pad;
174 gstelement_class->release_pad = gst_ogg_mux_release_pad;
175
176 g_object_class_install_property (gobject_class, ARG_MAX_DELAY,
177 g_param_spec_uint64 ("max-delay", "Max delay",
178 "Maximum delay in multiplexing streams", 0, G_MAXUINT64,
179 DEFAULT_MAX_DELAY,
180 (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
181 g_object_class_install_property (gobject_class, ARG_MAX_PAGE_DELAY,
182 g_param_spec_uint64 ("max-page-delay", "Max page delay",
183 "Maximum delay for sending out a page", 0, G_MAXUINT64,
184 DEFAULT_MAX_PAGE_DELAY,
185 (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
186 g_object_class_install_property (gobject_class, ARG_MAX_TOLERANCE,
187 g_param_spec_uint64 ("max-tolerance", "Max time tolerance",
188 "Maximum timestamp difference for maintaining perfect granules",
189 0, G_MAXUINT64, DEFAULT_MAX_TOLERANCE,
190 (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
191 g_object_class_install_property (gobject_class, ARG_SKELETON,
192 g_param_spec_boolean ("skeleton", "Skeleton",
193 "Whether to include a Skeleton track",
194 DEFAULT_SKELETON,
195 (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
196
197 gstelement_class->change_state = gst_ogg_mux_change_state;
198
199 }
200
201 static void
gst_ogg_mux_clear(GstOggMux * ogg_mux)202 gst_ogg_mux_clear (GstOggMux * ogg_mux)
203 {
204 ogg_mux->pulling = NULL;
205 ogg_mux->need_headers = TRUE;
206 ogg_mux->need_start_events = TRUE;
207 ogg_mux->delta_pad = NULL;
208 ogg_mux->offset = 0;
209 ogg_mux->next_ts = 0;
210 ogg_mux->last_ts = GST_CLOCK_TIME_NONE;
211 }
212
213 static void
gst_ogg_mux_init(GstOggMux * ogg_mux)214 gst_ogg_mux_init (GstOggMux * ogg_mux)
215 {
216 GstElementClass *klass = GST_ELEMENT_GET_CLASS (ogg_mux);
217
218 ogg_mux->srcpad =
219 gst_pad_new_from_template (gst_element_class_get_pad_template (klass,
220 "src"), "src");
221 gst_pad_set_event_function (ogg_mux->srcpad, gst_ogg_mux_handle_src_event);
222 gst_element_add_pad (GST_ELEMENT (ogg_mux), ogg_mux->srcpad);
223
224 /* seed random number generator for creation of serial numbers */
225 srand (time (NULL));
226
227 ogg_mux->collect = gst_collect_pads_new ();
228 gst_collect_pads_set_function (ogg_mux->collect,
229 (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_ogg_mux_collected),
230 ogg_mux);
231 gst_collect_pads_set_event_function (ogg_mux->collect,
232 (GstCollectPadsEventFunction) GST_DEBUG_FUNCPTR (gst_ogg_mux_sink_event),
233 ogg_mux);
234
235 ogg_mux->max_delay = DEFAULT_MAX_DELAY;
236 ogg_mux->max_page_delay = DEFAULT_MAX_PAGE_DELAY;
237 ogg_mux->max_tolerance = DEFAULT_MAX_TOLERANCE;
238
239 gst_ogg_mux_clear (ogg_mux);
240 }
241
242 static void
gst_ogg_mux_finalize(GObject * object)243 gst_ogg_mux_finalize (GObject * object)
244 {
245 GstOggMux *ogg_mux;
246
247 ogg_mux = GST_OGG_MUX (object);
248
249 if (ogg_mux->collect) {
250 gst_object_unref (ogg_mux->collect);
251 ogg_mux->collect = NULL;
252 }
253
254 G_OBJECT_CLASS (parent_class)->finalize (object);
255 }
256
257 static void
gst_ogg_mux_ogg_pad_destroy_notify(GstCollectData * data)258 gst_ogg_mux_ogg_pad_destroy_notify (GstCollectData * data)
259 {
260 GstOggPadData *oggpad = (GstOggPadData *) data;
261 GstBuffer *buf;
262
263 ogg_stream_clear (&oggpad->map.stream);
264 gst_caps_replace (&oggpad->map.caps, NULL);
265
266 if (oggpad->pagebuffers) {
267 while ((buf = g_queue_pop_head (oggpad->pagebuffers)) != NULL) {
268 gst_buffer_unref (buf);
269 }
270 g_queue_free (oggpad->pagebuffers);
271 oggpad->pagebuffers = NULL;
272 }
273 }
274
275 static GstPadLinkReturn
gst_ogg_mux_sinkconnect(GstPad * pad,GstObject * parent,GstPad * peer)276 gst_ogg_mux_sinkconnect (GstPad * pad, GstObject * parent, GstPad * peer)
277 {
278 GstOggMux *ogg_mux;
279
280 ogg_mux = GST_OGG_MUX (parent);
281
282 GST_DEBUG_OBJECT (ogg_mux, "sinkconnect triggered on %s", GST_PAD_NAME (pad));
283
284 return GST_PAD_LINK_OK;
285 }
286
287 static void
gst_ogg_mux_flush(GstOggMux * ogg_mux)288 gst_ogg_mux_flush (GstOggMux * ogg_mux)
289 {
290 GSList *walk;
291
292 for (walk = ogg_mux->collect->data; walk; walk = g_slist_next (walk)) {
293 GstOggPadData *pad;
294
295 pad = (GstOggPadData *) walk->data;
296
297 gst_ogg_pad_data_reset (ogg_mux, pad);
298 }
299
300 gst_ogg_mux_clear (ogg_mux);
301 }
302
303 static gboolean
gst_ogg_mux_sink_event(GstCollectPads * pads,GstCollectData * pad,GstEvent * event,gpointer user_data)304 gst_ogg_mux_sink_event (GstCollectPads * pads, GstCollectData * pad,
305 GstEvent * event, gpointer user_data)
306 {
307 GstOggMux *ogg_mux = GST_OGG_MUX (user_data);
308 GstOggPadData *ogg_pad = (GstOggPadData *) pad;
309
310 GST_DEBUG_OBJECT (pad->pad, "Got %s event", GST_EVENT_TYPE_NAME (event));
311
312 switch (GST_EVENT_TYPE (event)) {
313 case GST_EVENT_SEGMENT:
314 {
315 const GstSegment *segment;
316
317 gst_event_parse_segment (event, &segment);
318
319 /* We don't support non time NEWSEGMENT events */
320 if (segment->format != GST_FORMAT_TIME) {
321 gst_event_unref (event);
322 event = NULL;
323 break;
324 }
325
326 gst_segment_copy_into (segment, &ogg_pad->segment);
327 break;
328 }
329 case GST_EVENT_FLUSH_STOP:{
330 /* only a single flush-stop is forwarded from collect pads */
331 gst_ogg_mux_flush (ogg_mux);
332 break;
333 }
334 case GST_EVENT_TAG:{
335 GstTagList *tags;
336
337 gst_event_parse_tag (event, &tags);
338 tags = gst_tag_list_merge (ogg_pad->tags, tags, GST_TAG_MERGE_APPEND);
339 if (ogg_pad->tags)
340 gst_tag_list_unref (ogg_pad->tags);
341 ogg_pad->tags = tags;
342
343 GST_DEBUG_OBJECT (ogg_mux, "Got tags %" GST_PTR_FORMAT, ogg_pad->tags);
344 break;
345 }
346 default:
347 break;
348 }
349
350 /* now GstCollectPads can take care of the rest, e.g. EOS */
351 if (event != NULL)
352 return gst_collect_pads_event_default (pads, pad, event, FALSE);
353
354 return TRUE;
355 }
356
357 static gboolean
gst_ogg_mux_is_serialno_present(GstOggMux * ogg_mux,guint32 serialno)358 gst_ogg_mux_is_serialno_present (GstOggMux * ogg_mux, guint32 serialno)
359 {
360 GSList *walk;
361
362 walk = ogg_mux->collect->data;
363 while (walk) {
364 GstOggPadData *pad = (GstOggPadData *) walk->data;
365 if (pad->map.serialno == serialno)
366 return TRUE;
367 walk = walk->next;
368 }
369
370 return FALSE;
371 }
372
373 static void
gst_ogg_pad_data_reset(GstOggMux * ogg_mux,GstOggPadData * oggpad)374 gst_ogg_pad_data_reset (GstOggMux * ogg_mux, GstOggPadData * oggpad)
375 {
376 oggpad->packetno = 0;
377 oggpad->pageno = 0;
378 oggpad->eos = FALSE;
379
380 /* we assume there will be some control data first for this pad */
381 oggpad->state = GST_OGG_PAD_STATE_CONTROL;
382 oggpad->new_page = TRUE;
383 oggpad->first_delta = FALSE;
384 oggpad->prev_delta = FALSE;
385 oggpad->data_pushed = FALSE;
386 oggpad->map.headers = NULL;
387 oggpad->map.queued = NULL;
388 oggpad->next_granule = 0;
389 oggpad->keyframe_granule = -1;
390 ogg_stream_clear (&oggpad->map.stream);
391 ogg_stream_init (&oggpad->map.stream, oggpad->map.serialno);
392
393 if (oggpad->pagebuffers) {
394 GstBuffer *buf;
395
396 while ((buf = g_queue_pop_head (oggpad->pagebuffers)) != NULL) {
397 gst_buffer_unref (buf);
398 }
399 } else if (GST_STATE (ogg_mux) > GST_STATE_READY) {
400 /* This will be initialized in init_collectpads when going from ready
401 * paused state */
402 oggpad->pagebuffers = g_queue_new ();
403 }
404
405 gst_segment_init (&oggpad->segment, GST_FORMAT_TIME);
406 }
407
408 static guint32
gst_ogg_mux_generate_serialno(GstOggMux * ogg_mux)409 gst_ogg_mux_generate_serialno (GstOggMux * ogg_mux)
410 {
411 guint32 serialno;
412
413 do {
414 serialno = g_random_int_range (0, G_MAXINT32);
415 } while (gst_ogg_mux_is_serialno_present (ogg_mux, serialno));
416
417 return serialno;
418 }
419
420 static GstPad *
gst_ogg_mux_request_new_pad(GstElement * element,GstPadTemplate * templ,const gchar * req_name,const GstCaps * caps)421 gst_ogg_mux_request_new_pad (GstElement * element,
422 GstPadTemplate * templ, const gchar * req_name, const GstCaps * caps)
423 {
424 GstOggMux *ogg_mux;
425 GstPad *newpad;
426 GstElementClass *klass;
427
428 g_return_val_if_fail (templ != NULL, NULL);
429
430 if (templ->direction != GST_PAD_SINK)
431 goto wrong_direction;
432
433 g_return_val_if_fail (GST_IS_OGG_MUX (element), NULL);
434 ogg_mux = GST_OGG_MUX (element);
435
436 klass = GST_ELEMENT_GET_CLASS (element);
437
438 if (templ != gst_element_class_get_pad_template (klass, "video_%u") &&
439 templ != gst_element_class_get_pad_template (klass, "audio_%u") &&
440 templ != gst_element_class_get_pad_template (klass, "subtitle_%u")) {
441 goto wrong_template;
442 }
443
444 {
445 guint32 serial;
446 gchar *name = NULL;
447
448 if (req_name == NULL || strlen (req_name) < 6) {
449 /* no name given when requesting the pad, use random serial number */
450 serial = gst_ogg_mux_generate_serialno (ogg_mux);
451 } else {
452 /* parse serial number from requested padname */
453 unsigned long long_serial;
454 char *endptr = NULL;
455 long_serial = strtoul (&req_name[5], &endptr, 10);
456 if ((endptr && *endptr) || (long_serial & ~0xffffffff)) {
457 GST_WARNING_OBJECT (ogg_mux, "Invalid serial number specification: %s",
458 req_name + 5);
459 return NULL;
460 }
461 serial = (guint32) long_serial;
462 }
463 /* create new pad with the name */
464 GST_DEBUG_OBJECT (ogg_mux, "Creating new pad for serial %d", serial);
465
466 if (templ == gst_element_class_get_pad_template (klass, "video_%u")) {
467 name = g_strdup_printf ("video_%u", serial);
468 } else if (templ == gst_element_class_get_pad_template (klass, "audio_%u")) {
469 name = g_strdup_printf ("audio_%u", serial);
470 } else if (templ == gst_element_class_get_pad_template (klass,
471 "subtitle_%u")) {
472 name = g_strdup_printf ("subtitle_%u", serial);
473 }
474 newpad = gst_pad_new_from_template (templ, name);
475 g_free (name);
476
477 /* construct our own wrapper data structure for the pad to
478 * keep track of its status */
479 {
480 GstOggPadData *oggpad;
481
482 oggpad = (GstOggPadData *)
483 gst_collect_pads_add_pad (ogg_mux->collect, newpad,
484 sizeof (GstOggPadData), gst_ogg_mux_ogg_pad_destroy_notify, FALSE);
485 ogg_mux->active_pads++;
486
487 oggpad->map.serialno = serial;
488 gst_ogg_pad_data_reset (ogg_mux, oggpad);
489 }
490 }
491
492 /* setup some pad functions */
493 gst_pad_set_link_function (newpad, gst_ogg_mux_sinkconnect);
494
495 /* dd the pad to the element */
496 gst_element_add_pad (element, newpad);
497
498 return newpad;
499
500 /* ERRORS */
501 wrong_direction:
502 {
503 g_warning ("ogg_mux: request pad that is not a SINK pad\n");
504 return NULL;
505 }
506 wrong_template:
507 {
508 g_warning ("ogg_mux: this is not our template!\n");
509 return NULL;
510 }
511 }
512
513 static void
gst_ogg_mux_release_pad(GstElement * element,GstPad * pad)514 gst_ogg_mux_release_pad (GstElement * element, GstPad * pad)
515 {
516 GstOggMux *ogg_mux;
517
518 ogg_mux = GST_OGG_MUX (gst_pad_get_parent (pad));
519
520 gst_collect_pads_remove_pad (ogg_mux->collect, pad);
521 gst_element_remove_pad (element, pad);
522
523 gst_object_unref (ogg_mux);
524 }
525
526 /* handle events */
527 static gboolean
gst_ogg_mux_handle_src_event(GstPad * pad,GstObject * parent,GstEvent * event)528 gst_ogg_mux_handle_src_event (GstPad * pad, GstObject * parent,
529 GstEvent * event)
530 {
531 gboolean res = FALSE;
532 GstOggMux *ogg_mux = GST_OGG_MUX (parent);
533
534 switch (GST_EVENT_TYPE (event)) {
535 case GST_EVENT_SEEK:{
536 GstSeekFlags flags;
537
538 gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL);
539 if (!ogg_mux->need_headers && (flags & GST_SEEK_FLAG_FLUSH) != 0) {
540 /* don't allow flushing seeks once we started */
541 gst_event_unref (event);
542 event = NULL;
543 }
544 break;
545 }
546 default:
547 break;
548 }
549
550 if (event != NULL)
551 res = gst_pad_event_default (pad, parent, event);
552
553 return res;
554 }
555
556 static GstBuffer *
gst_ogg_mux_buffer_from_page(GstOggMux * mux,ogg_page * page,gboolean delta)557 gst_ogg_mux_buffer_from_page (GstOggMux * mux, ogg_page * page, gboolean delta)
558 {
559 GstBuffer *buffer;
560
561 /* allocate space for header and body */
562 buffer = gst_buffer_new_and_alloc (page->header_len + page->body_len);
563 gst_buffer_fill (buffer, 0, page->header, page->header_len);
564 gst_buffer_fill (buffer, page->header_len, page->body, page->body_len);
565
566 /* Here we set granulepos as our OFFSET_END to give easy direct access to
567 * this value later. Before we push it, we reset this to OFFSET + SIZE
568 * (see gst_ogg_mux_push_buffer). */
569 GST_BUFFER_OFFSET_END (buffer) = ogg_page_granulepos (page);
570 if (delta)
571 GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT);
572
573 GST_LOG_OBJECT (mux, GST_GP_FORMAT
574 " created buffer %p from ogg page",
575 GST_GP_CAST (ogg_page_granulepos (page)), buffer);
576
577 return buffer;
578 }
579
580 static GstFlowReturn
gst_ogg_mux_push_buffer(GstOggMux * mux,GstBuffer * buffer,GstOggPadData * oggpad)581 gst_ogg_mux_push_buffer (GstOggMux * mux, GstBuffer * buffer,
582 GstOggPadData * oggpad)
583 {
584 /* fix up OFFSET and OFFSET_END again */
585 GST_BUFFER_OFFSET (buffer) = mux->offset;
586 mux->offset += gst_buffer_get_size (buffer);
587 GST_BUFFER_OFFSET_END (buffer) = mux->offset;
588
589 /* Ensure we have monotonically increasing timestamps in the output. */
590 if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) {
591 gint64 run_time = GST_BUFFER_TIMESTAMP (buffer);
592 if (mux->last_ts != GST_CLOCK_TIME_NONE && run_time < mux->last_ts)
593 GST_BUFFER_TIMESTAMP (buffer) = mux->last_ts;
594 else
595 mux->last_ts = run_time;
596 }
597
598 GST_LOG_OBJECT (mux->srcpad, "pushing %p, last_ts=%" GST_TIME_FORMAT,
599 buffer, GST_TIME_ARGS (mux->last_ts));
600
601 return gst_pad_push (mux->srcpad, buffer);
602 }
603
604 /* if all queues have at least one page, dequeue the page with the lowest
605 * timestamp */
606 static gboolean
gst_ogg_mux_dequeue_page(GstOggMux * mux,GstFlowReturn * flowret)607 gst_ogg_mux_dequeue_page (GstOggMux * mux, GstFlowReturn * flowret)
608 {
609 GSList *walk;
610 GstOggPadData *opad = NULL; /* "oldest" pad */
611 GstClockTime oldest = GST_CLOCK_TIME_NONE;
612 GstBuffer *buf = NULL;
613 gboolean ret = FALSE;
614
615 *flowret = GST_FLOW_OK;
616
617 walk = mux->collect->data;
618 while (walk) {
619 GstOggPadData *pad = (GstOggPadData *) walk->data;
620
621 /* We need each queue to either be at EOS, or have one or more pages
622 * available with a set granulepos (i.e. not -1), otherwise we don't have
623 * enough data yet to determine which stream needs to go next for correct
624 * time ordering. */
625 if (pad->pagebuffers->length == 0) {
626 if (pad->eos) {
627 GST_LOG_OBJECT (pad->collect.pad,
628 "pad is EOS, skipping for dequeue decision");
629 } else {
630 GST_LOG_OBJECT (pad->collect.pad,
631 "no pages in this queue, can't dequeue");
632 return FALSE;
633 }
634 } else {
635 /* We then need to check for a non-negative granulepos */
636 gboolean valid = FALSE;
637 GList *l;
638
639 for (l = pad->pagebuffers->head; l != NULL; l = l->next) {
640 buf = l->data;
641 /* Here we check the OFFSET_END, which is actually temporarily the
642 * granulepos value for this buffer */
643 if (GST_BUFFER_OFFSET_END_IS_VALID (buf)) {
644 valid = TRUE;
645 break;
646 }
647 }
648 if (!valid) {
649 GST_LOG_OBJECT (pad->collect.pad,
650 "No page timestamps in queue, can't dequeue");
651 return FALSE;
652 }
653 }
654
655 walk = g_slist_next (walk);
656 }
657
658 walk = mux->collect->data;
659 while (walk) {
660 GstOggPadData *pad = (GstOggPadData *) walk->data;
661
662 /* any page with a granulepos of -1 can be pushed immediately.
663 * TODO: it CAN be, but it seems silly to do so? */
664 buf = g_queue_peek_head (pad->pagebuffers);
665 while (buf && GST_BUFFER_OFFSET_END (buf) == -1) {
666 GST_LOG_OBJECT (pad->collect.pad, "[gp -1] pushing page");
667 g_queue_pop_head (pad->pagebuffers);
668 *flowret = gst_ogg_mux_push_buffer (mux, buf, pad);
669 buf = g_queue_peek_head (pad->pagebuffers);
670 ret = TRUE;
671 }
672
673 if (buf) {
674 /* if no oldest buffer yet, take this one */
675 if (oldest == GST_CLOCK_TIME_NONE) {
676 GST_LOG_OBJECT (mux, "no oldest yet, taking buffer %p from pad %"
677 GST_PTR_FORMAT " with gp time %" GST_TIME_FORMAT,
678 buf, pad->collect.pad, GST_TIME_ARGS (GST_BUFFER_OFFSET (buf)));
679 oldest = GST_BUFFER_OFFSET (buf);
680 opad = pad;
681 } else {
682 /* if we have an oldest, compare with this one */
683 if (GST_BUFFER_OFFSET (buf) < oldest) {
684 GST_LOG_OBJECT (mux, "older buffer %p, taking from pad %"
685 GST_PTR_FORMAT " with gp time %" GST_TIME_FORMAT,
686 buf, pad->collect.pad, GST_TIME_ARGS (GST_BUFFER_OFFSET (buf)));
687 oldest = GST_BUFFER_OFFSET (buf);
688 opad = pad;
689 }
690 }
691 }
692 walk = g_slist_next (walk);
693 }
694
695 if (oldest != GST_CLOCK_TIME_NONE) {
696 g_assert (opad);
697 buf = g_queue_pop_head (opad->pagebuffers);
698 GST_LOG_OBJECT (opad->collect.pad,
699 GST_GP_FORMAT " pushing oldest page buffer %p (granulepos time %"
700 GST_TIME_FORMAT ")", GST_BUFFER_OFFSET_END (buf), buf,
701 GST_TIME_ARGS (GST_BUFFER_OFFSET (buf)));
702 *flowret = gst_ogg_mux_push_buffer (mux, buf, opad);
703 ret = TRUE;
704 }
705
706 return ret;
707 }
708
709 /* put the given ogg page on a per-pad queue, timestamping it correctly.
710 * after that, dequeue and push as many pages as possible.
711 * Caller should make sure:
712 * pad->timestamp was set with the timestamp of the first packet put
713 * on the page
714 * pad->timestamp_end was set with the timestamp + duration of the last packet
715 * put on the page
716 * pad->gp_time was set with the time matching the gp of the last
717 * packet put on the page
718 *
719 * will also reset timestamp and timestamp_end, so caller func can restart
720 * counting.
721 */
722 static GstFlowReturn
gst_ogg_mux_pad_queue_page(GstOggMux * mux,GstOggPadData * pad,ogg_page * page,gboolean delta)723 gst_ogg_mux_pad_queue_page (GstOggMux * mux, GstOggPadData * pad,
724 ogg_page * page, gboolean delta)
725 {
726 GstFlowReturn ret;
727 GstBuffer *buffer = gst_ogg_mux_buffer_from_page (mux, page, delta);
728
729 /* take the timestamp of the first packet on this page */
730 GST_BUFFER_TIMESTAMP (buffer) = pad->timestamp;
731 GST_BUFFER_DURATION (buffer) = pad->timestamp_end - pad->timestamp;
732 /* take the gp time of the last completed packet on this page */
733 GST_BUFFER_OFFSET (buffer) = pad->gp_time;
734
735 /* the next page will start where the current page's end time leaves off */
736 pad->timestamp = pad->timestamp_end;
737
738 g_queue_push_tail (pad->pagebuffers, buffer);
739 GST_LOG_OBJECT (pad->collect.pad, GST_GP_FORMAT
740 " queued buffer page %p (gp time %"
741 GST_TIME_FORMAT ", timestamp %" GST_TIME_FORMAT
742 "), %d page buffers queued", GST_GP_CAST (ogg_page_granulepos (page)),
743 buffer, GST_TIME_ARGS (GST_BUFFER_OFFSET (buffer)),
744 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
745 g_queue_get_length (pad->pagebuffers));
746
747 while (gst_ogg_mux_dequeue_page (mux, &ret)) {
748 if (ret != GST_FLOW_OK)
749 break;
750 }
751
752 return ret;
753 }
754
755 /*
756 * Given two pads, compare the buffers queued on it.
757 * Returns:
758 * 0 if they have an equal priority
759 * -1 if the first is better
760 * 1 if the second is better
761 * Priority decided by: a) validity, b) older timestamp, c) smaller number
762 * of muxed pages
763 */
764 static gint
gst_ogg_mux_compare_pads(GstOggMux * ogg_mux,GstOggPadData * first,GstOggPadData * second)765 gst_ogg_mux_compare_pads (GstOggMux * ogg_mux, GstOggPadData * first,
766 GstOggPadData * second)
767 {
768 guint64 firsttime, secondtime;
769
770 /* if the first pad doesn't contain anything or is even NULL, return
771 * the second pad as best candidate and vice versa */
772 if (first == NULL)
773 return 1;
774 if (second == NULL)
775 return -1;
776
777 /* no timestamp on first buffer, it must go first */
778 firsttime = GST_BUFFER_TIMESTAMP (first->buffer);
779 if (firsttime == GST_CLOCK_TIME_NONE)
780 return -1;
781
782 /* no timestamp on second buffer, it must go first */
783 secondtime = GST_BUFFER_TIMESTAMP (second->buffer);
784 if (secondtime == GST_CLOCK_TIME_NONE)
785 return 1;
786
787 /* first buffer has higher timestamp, second one should go first */
788 if (secondtime < firsttime)
789 return 1;
790 /* second buffer has higher timestamp, first one should go first */
791 else if (secondtime > firsttime)
792 return -1;
793 else {
794 /* buffers with equal timestamps, prefer the pad that has the
795 * least number of pages muxed */
796 if (second->pageno < first->pageno)
797 return 1;
798 else if (second->pageno > first->pageno)
799 return -1;
800 }
801
802 /* same priority if all of the above failed */
803 return 0;
804 }
805
806 static GstBuffer *
gst_ogg_mux_decorate_buffer(GstOggMux * ogg_mux,GstOggPadData * pad,GstBuffer * buf)807 gst_ogg_mux_decorate_buffer (GstOggMux * ogg_mux, GstOggPadData * pad,
808 GstBuffer * buf)
809 {
810 GstClockTime time, end_time;
811 gint64 duration, granule, limit;
812 GstClockTime next_time;
813 GstClockTimeDiff diff;
814 GstMapInfo map;
815 ogg_packet packet;
816 gboolean end_clip = TRUE;
817 GstAudioClippingMeta *meta;
818
819 /* ensure messing with metadata is ok */
820 buf = gst_buffer_make_writable (buf);
821
822 /* convert time to running time, so we need no longer bother about that */
823 time = GST_BUFFER_TIMESTAMP (buf);
824 if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (time))) {
825 time = gst_segment_to_running_time (&pad->segment, GST_FORMAT_TIME, time);
826 if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (time))) {
827 gst_buffer_unref (buf);
828 return NULL;
829 } else {
830 GST_BUFFER_TIMESTAMP (buf) = time;
831 }
832 }
833
834 /* now come up with granulepos stuff corresponding to time */
835 if (!pad->have_type ||
836 pad->map.granulerate_n <= 0 || pad->map.granulerate_d <= 0)
837 goto no_granule;
838
839 gst_buffer_map (buf, &map, GST_MAP_READ);
840 packet.packet = map.data;
841 packet.bytes = map.size;
842
843 gst_ogg_stream_update_stats (&pad->map, &packet);
844
845 duration = gst_ogg_stream_get_packet_duration (&pad->map, &packet);
846
847 gst_buffer_unmap (buf, &map);
848
849 /* give up if no duration can be determined, relying on upstream */
850 if (G_UNLIKELY (duration < 0)) {
851 /* well, if some day we really could handle sparse input ... */
852 if (pad->map.is_sparse) {
853 granule = 0;
854 limit = 1;
855 diff = 2;
856 goto resync;
857 }
858 GST_WARNING_OBJECT (pad->collect.pad,
859 "failed to determine packet duration");
860 goto no_granule;
861 }
862
863 /* The last packet may have clipped samples. We need to test against
864 * the segment to ensure we do not use a granpos that encompasses those.
865 */
866 if (pad->map.audio_clipping) {
867 GstAudioClippingMeta *cmeta = gst_buffer_get_audio_clipping_meta (buf);
868
869 g_assert (!cmeta || cmeta->format == GST_FORMAT_DEFAULT);
870 if (cmeta && cmeta->end && cmeta->end < duration) {
871 GST_DEBUG_OBJECT (pad->collect.pad,
872 "Clipping %" G_GUINT64_FORMAT " samples at the end", cmeta->end);
873 duration -= cmeta->end;
874 end_clip = FALSE;
875 }
876 }
877
878 if (end_clip) {
879 end_time =
880 gst_ogg_stream_granule_to_time (&pad->map,
881 pad->next_granule + duration);
882 meta = gst_buffer_get_audio_clipping_meta (buf);
883 if (meta && meta->end) {
884 if (meta->format == GST_FORMAT_DEFAULT) {
885 if (meta->end > duration) {
886 GST_WARNING_OBJECT (pad->collect.pad,
887 "Clip meta tries to clip more sample than exist in the buffer, clipping all");
888 duration = 0;
889 } else {
890 duration -= meta->end;
891 }
892 } else {
893 GST_WARNING_OBJECT (pad->collect.pad,
894 "Unsupported format in clip meta");
895 }
896 }
897 if (end_time > pad->segment.stop
898 && !GST_CLOCK_TIME_IS_VALID (gst_segment_to_running_time (&pad->segment,
899 GST_FORMAT_TIME, pad->segment.start + end_time))) {
900 gint64 actual_duration =
901 gst_util_uint64_scale_round (pad->segment.stop - time,
902 pad->map.granulerate_n,
903 GST_SECOND * pad->map.granulerate_d);
904 GST_INFO_OBJECT (ogg_mux,
905 "Got clipped last packet of duration %" G_GINT64_FORMAT " (%"
906 G_GINT64_FORMAT " clipped)", actual_duration,
907 duration - actual_duration);
908 duration = actual_duration;
909 }
910 }
911
912 GST_LOG_OBJECT (pad->collect.pad, "buffer ts %" GST_TIME_FORMAT
913 ", duration %" GST_TIME_FORMAT ", granule duration %" G_GINT64_FORMAT,
914 GST_TIME_ARGS (time), GST_TIME_ARGS (GST_BUFFER_DURATION (buf)),
915 duration);
916
917 /* determine granule corresponding to time,
918 * using the inverse of oggdemux' granule -> time */
919
920 /* see if interpolated granule matches good enough */
921 granule = pad->next_granule;
922 next_time = gst_ogg_stream_granule_to_time (&pad->map, pad->next_granule);
923 diff = GST_CLOCK_DIFF (next_time, time);
924
925 /* we tolerate deviation up to configured or within granule granularity */
926 limit = gst_ogg_stream_granule_to_time (&pad->map, 1) / 2;
927 limit = MAX (limit, ogg_mux->max_tolerance);
928
929 GST_LOG_OBJECT (pad->collect.pad, "expected granule %" G_GINT64_FORMAT " == "
930 "time %" GST_TIME_FORMAT " --> ts diff %" GST_STIME_FORMAT
931 " < tolerance %" GST_TIME_FORMAT " (?)",
932 granule, GST_TIME_ARGS (next_time), GST_STIME_ARGS (diff),
933 GST_TIME_ARGS (limit));
934
935 resync:
936 /* if not good enough, determine granule based on time */
937 if (diff > limit || diff < -limit) {
938 granule = gst_util_uint64_scale_round (time, pad->map.granulerate_n,
939 GST_SECOND * pad->map.granulerate_d);
940 GST_DEBUG_OBJECT (pad->collect.pad,
941 "resyncing to determined granule %" G_GINT64_FORMAT, granule);
942 }
943
944 if (pad->map.is_ogm || pad->map.is_sparse) {
945 pad->next_granule = granule;
946 } else {
947 granule += duration;
948 pad->next_granule = granule;
949 }
950
951 /* track previous keyframe */
952 if (!GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT))
953 pad->keyframe_granule = granule;
954
955 /* determine corresponding time and granulepos */
956 GST_BUFFER_OFFSET (buf) = gst_ogg_stream_granule_to_time (&pad->map, granule);
957 GST_BUFFER_OFFSET_END (buf) =
958 gst_ogg_stream_granule_to_granulepos (&pad->map, granule,
959 pad->keyframe_granule);
960
961 GST_LOG_OBJECT (pad->collect.pad,
962 GST_GP_FORMAT " decorated buffer %p (granulepos time %" GST_TIME_FORMAT
963 ")", GST_BUFFER_OFFSET_END (buf), buf,
964 GST_TIME_ARGS (GST_BUFFER_OFFSET (buf)));
965
966 return buf;
967
968 /* ERRORS */
969 no_granule:
970 {
971 GST_DEBUG_OBJECT (pad->collect.pad, "could not determine granulepos, "
972 "falling back to upstream provided metadata");
973 return buf;
974 }
975 }
976
977
978 /* make sure at least one buffer is queued on all pads, two if possible
979 *
980 * if pad->buffer == NULL, pad->next_buffer != NULL, then
981 * we do not know if the buffer is the last or not
982 * if pad->buffer != NULL, pad->next_buffer != NULL, then
983 * pad->buffer is not the last buffer for the pad
984 * if pad->buffer != NULL, pad->next_buffer == NULL, then
985 * pad->buffer if the last buffer for the pad
986 *
987 * returns a pointer to an oggpad that holds the best buffer, or
988 * NULL when no pad was usable. "best" means the buffer marked
989 * with the lowest timestamp. If best->buffer == NULL then either
990 * we're at EOS (popped = FALSE), or a buffer got dropped, so retry. */
991 static GstOggPadData *
gst_ogg_mux_queue_pads(GstOggMux * ogg_mux,gboolean * popped)992 gst_ogg_mux_queue_pads (GstOggMux * ogg_mux, gboolean * popped)
993 {
994 GstOggPadData *bestpad = NULL;
995 GSList *walk;
996
997 *popped = FALSE;
998
999 /* try to make sure we have a buffer from each usable pad first */
1000 walk = ogg_mux->collect->data;
1001 while (walk) {
1002 GstOggPadData *pad;
1003 GstCollectData *data;
1004
1005 data = (GstCollectData *) walk->data;
1006 pad = (GstOggPadData *) data;
1007
1008 walk = g_slist_next (walk);
1009
1010 GST_LOG_OBJECT (data->pad, "looking at pad for buffer");
1011
1012 /* try to get a new buffer for this pad if needed and possible */
1013 if (pad->buffer == NULL) {
1014 GstBuffer *buf;
1015
1016 buf = gst_collect_pads_pop (ogg_mux->collect, data);
1017 GST_LOG_OBJECT (data->pad, "popped buffer %" GST_PTR_FORMAT, buf);
1018
1019 /* On EOS we get a NULL buffer */
1020 if (buf != NULL) {
1021 *popped = TRUE;
1022
1023 if (ogg_mux->delta_pad == NULL &&
1024 GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT))
1025 ogg_mux->delta_pad = pad;
1026
1027 /* if we need headers */
1028 if (pad->state == GST_OGG_PAD_STATE_CONTROL) {
1029 /* and we have one */
1030 ogg_packet packet;
1031 gboolean is_header;
1032 GstMapInfo map;
1033
1034 gst_buffer_map (buf, &map, GST_MAP_READ);
1035 packet.packet = map.data;
1036 packet.bytes = map.size;
1037
1038 /* if we're not yet in data mode, ensure we're setup on the first packet */
1039 if (!pad->have_type) {
1040 GstCaps *caps;
1041
1042 /* Use headers in caps, if any; this will allow us to be resilient
1043 * to starting streams on the fly, and some streams (like VP8
1044 * at least) do not send headers packets, as other muxers don't
1045 * expect/need them. */
1046 caps = gst_pad_get_current_caps (GST_PAD_CAST (data->pad));
1047 GST_DEBUG_OBJECT (data->pad, "checking caps: %" GST_PTR_FORMAT,
1048 caps);
1049
1050 pad->have_type =
1051 gst_ogg_stream_setup_map_from_caps_headers (&pad->map, caps);
1052
1053 if (!pad->have_type) {
1054 /* fallback on the packet */
1055 pad->have_type = gst_ogg_stream_setup_map (&pad->map, &packet);
1056 }
1057 if (!pad->have_type) {
1058 /* fallback 2 to try to get the mapping from the caps */
1059 pad->have_type =
1060 gst_ogg_stream_setup_map_from_caps (&pad->map, caps);
1061 }
1062 if (!pad->have_type) {
1063 GST_ERROR_OBJECT (data->pad,
1064 "mapper didn't recognise input stream " "(pad caps: %"
1065 GST_PTR_FORMAT ")", caps);
1066 } else {
1067 GST_DEBUG_OBJECT (data->pad, "caps detected: %" GST_PTR_FORMAT,
1068 pad->map.caps);
1069
1070 if (pad->map.is_sparse) {
1071 GST_DEBUG_OBJECT (data->pad, "Pad is sparse, marking as such");
1072 gst_collect_pads_set_waiting (ogg_mux->collect,
1073 (GstCollectData *) pad, FALSE);
1074 }
1075
1076 if (pad->map.is_video && ogg_mux->delta_pad == NULL) {
1077 ogg_mux->delta_pad = pad;
1078 GST_INFO_OBJECT (data->pad, "selected delta pad");
1079 }
1080 }
1081 if (caps)
1082 gst_caps_unref (caps);
1083 }
1084
1085 if (pad->have_type)
1086 is_header = gst_ogg_stream_packet_is_header (&pad->map, &packet);
1087 else /* fallback (FIXME 0.11: remove IN_CAPS hack) */
1088 is_header = GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_HEADER);
1089
1090 gst_buffer_unmap (buf, &map);
1091
1092 if (is_header) {
1093 GST_DEBUG_OBJECT (ogg_mux,
1094 "got header buffer in control state, ignoring");
1095 /* just ignore */
1096 pad->map.n_header_packets_seen++;
1097 gst_buffer_unref (buf);
1098 buf = NULL;
1099 } else {
1100 GST_DEBUG_OBJECT (ogg_mux,
1101 "got data buffer in control state, switching to data mode");
1102 /* this is a data buffer so switch to data state */
1103 pad->state = GST_OGG_PAD_STATE_DATA;
1104
1105 /* check if this type of stream allows generating granulepos
1106 * metadata here, if not, upstream will have to provide */
1107 if (gst_ogg_stream_granule_to_granulepos (&pad->map, 1, 1) < 0) {
1108 GST_WARNING_OBJECT (data->pad, "can not generate metadata; "
1109 "relying on upstream");
1110 /* disable metadata code path, otherwise not used anyway */
1111 pad->map.granulerate_n = 0;
1112 }
1113 }
1114 }
1115
1116 /* so now we should have a real data packet;
1117 * see that it is properly decorated */
1118 if (G_LIKELY (buf)) {
1119 buf = gst_ogg_mux_decorate_buffer (ogg_mux, pad, buf);
1120 if (G_UNLIKELY (!buf))
1121 GST_DEBUG_OBJECT (data->pad, "buffer clipped");
1122 }
1123 }
1124
1125 pad->buffer = buf;
1126 }
1127
1128 /* we should have a buffer now, see if it is the best pad to
1129 * pull on. Our best pad can't be eos */
1130 if (pad->buffer && !pad->eos) {
1131 if (gst_ogg_mux_compare_pads (ogg_mux, bestpad, pad) > 0) {
1132 GST_LOG_OBJECT (data->pad,
1133 "new best pad, with buffer %" GST_PTR_FORMAT, pad->buffer);
1134
1135 bestpad = pad;
1136 }
1137 }
1138 }
1139
1140 return bestpad;
1141 }
1142
1143 static GList *
gst_ogg_mux_get_headers(GstOggPadData * pad)1144 gst_ogg_mux_get_headers (GstOggPadData * pad)
1145 {
1146 GList *res = NULL;
1147 GstStructure *structure;
1148 GstCaps *caps;
1149 const GValue *streamheader;
1150 GstPad *thepad;
1151 GstBuffer *header;
1152
1153 thepad = pad->collect.pad;
1154
1155 GST_LOG_OBJECT (thepad, "getting headers");
1156
1157 caps = gst_pad_get_current_caps (thepad);
1158 if (caps == NULL) {
1159 GST_INFO_OBJECT (thepad, "got empty caps as negotiated format");
1160 return NULL;
1161 }
1162
1163 structure = gst_caps_get_structure (caps, 0);
1164 streamheader = gst_structure_get_value (structure, "streamheader");
1165 if (streamheader != NULL) {
1166 GST_LOG_OBJECT (thepad, "got header");
1167 if (G_VALUE_TYPE (streamheader) == GST_TYPE_ARRAY) {
1168 GArray *bufarr = g_value_peek_pointer (streamheader);
1169 gint i;
1170
1171 GST_LOG_OBJECT (thepad, "got fixed list");
1172
1173 for (i = 0; i < bufarr->len; i++) {
1174 GValue *bufval = &g_array_index (bufarr, GValue, i);
1175
1176 GST_LOG_OBJECT (thepad, "item %d", i);
1177 if (G_VALUE_TYPE (bufval) == GST_TYPE_BUFFER) {
1178 GstBuffer *buf = g_value_peek_pointer (bufval);
1179
1180 GST_LOG_OBJECT (thepad, "adding item %d to header list", i);
1181
1182 gst_buffer_ref (buf);
1183 res = g_list_append (res, buf);
1184 }
1185 }
1186 } else {
1187 GST_LOG_OBJECT (thepad, "streamheader is not fixed list");
1188 }
1189
1190 } else if (gst_structure_has_name (structure, "video/x-dirac")) {
1191 res = g_list_append (res, pad->buffer);
1192 pad->buffer = NULL;
1193 } else if (pad->have_type
1194 && (header = gst_ogg_stream_get_headers (&pad->map))) {
1195 res = g_list_append (res, header);
1196 } else {
1197 GST_LOG_OBJECT (thepad, "caps don't have streamheader");
1198 }
1199 gst_caps_unref (caps);
1200
1201 return res;
1202 }
1203
1204 static GstCaps *
gst_ogg_mux_set_header_on_caps(GstCaps * caps,GList * buffers)1205 gst_ogg_mux_set_header_on_caps (GstCaps * caps, GList * buffers)
1206 {
1207 GstStructure *structure;
1208 GValue array = { 0 };
1209 GList *walk = buffers;
1210
1211 caps = gst_caps_make_writable (caps);
1212
1213 structure = gst_caps_get_structure (caps, 0);
1214
1215 /* put buffers in a fixed list */
1216 g_value_init (&array, GST_TYPE_ARRAY);
1217
1218 while (walk) {
1219 GstBuffer *buf = GST_BUFFER (walk->data);
1220 GValue value = { 0 };
1221
1222 walk = walk->next;
1223
1224 /* mark buffer */
1225 GST_LOG ("Setting HEADER on buffer of length %" G_GSIZE_FORMAT,
1226 gst_buffer_get_size (buf));
1227 GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER);
1228
1229 g_value_init (&value, GST_TYPE_BUFFER);
1230 gst_value_set_buffer (&value, buf);
1231 gst_value_array_append_value (&array, &value);
1232 g_value_unset (&value);
1233 }
1234 gst_structure_take_value (structure, "streamheader", &array);
1235
1236 return caps;
1237 }
1238
1239 static void
gst_ogg_mux_create_header_packet_with_flags(ogg_packet * packet,gboolean bos,gboolean eos)1240 gst_ogg_mux_create_header_packet_with_flags (ogg_packet * packet,
1241 gboolean bos, gboolean eos)
1242 {
1243 packet->granulepos = 0;
1244 /* mark BOS and packet number */
1245 packet->b_o_s = bos;
1246 /* mark EOS */
1247 packet->e_o_s = eos;
1248 }
1249
1250 static void
gst_ogg_mux_create_header_packet(ogg_packet * packet,GstOggPadData * pad)1251 gst_ogg_mux_create_header_packet (ogg_packet * packet, GstOggPadData * pad)
1252 {
1253 gst_ogg_mux_create_header_packet_with_flags (packet, pad->packetno == 0, 0);
1254 packet->packetno = pad->packetno++;
1255 }
1256
1257 static void
gst_ogg_mux_submit_skeleton_header_packet(GstOggMux * mux,ogg_stream_state * os,GstBuffer * buf,gboolean bos,gboolean eos)1258 gst_ogg_mux_submit_skeleton_header_packet (GstOggMux * mux,
1259 ogg_stream_state * os, GstBuffer * buf, gboolean bos, gboolean eos)
1260 {
1261 ogg_packet packet;
1262 GstMapInfo map;
1263
1264 gst_buffer_map (buf, &map, GST_MAP_READ);
1265 packet.packet = map.data;
1266 packet.bytes = map.size;
1267 gst_ogg_mux_create_header_packet_with_flags (&packet, bos, eos);
1268 ogg_stream_packetin (os, &packet);
1269 gst_buffer_unmap (buf, &map);
1270 gst_buffer_unref (buf);
1271 }
1272
1273 static void
gst_ogg_mux_make_fishead(GstOggMux * mux,ogg_stream_state * os)1274 gst_ogg_mux_make_fishead (GstOggMux * mux, ogg_stream_state * os)
1275 {
1276 GstByteWriter bw;
1277 GstBuffer *fishead;
1278 gboolean handled = TRUE;
1279
1280 GST_DEBUG_OBJECT (mux, "Creating fishead");
1281
1282 gst_byte_writer_init_with_size (&bw, 64, TRUE);
1283 handled &= gst_byte_writer_put_string_utf8 (&bw, "fishead");
1284 handled &= gst_byte_writer_put_int16_le (&bw, 3); /* version major */
1285 handled &= gst_byte_writer_put_int16_le (&bw, 0); /* version minor */
1286 handled &= gst_byte_writer_put_int64_le (&bw, 0); /* presentation time numerator */
1287 handled &= gst_byte_writer_put_int64_le (&bw, 1000); /* ...and denominator */
1288 handled &= gst_byte_writer_put_int64_le (&bw, 0); /* base time numerator */
1289 handled &= gst_byte_writer_put_int64_le (&bw, 1000); /* ...and denominator */
1290 handled &= gst_byte_writer_fill (&bw, ' ', 20); /* UTC time */
1291 g_assert (handled && gst_byte_writer_get_pos (&bw) == 64);
1292 fishead = gst_byte_writer_reset_and_get_buffer (&bw);
1293 gst_ogg_mux_submit_skeleton_header_packet (mux, os, fishead, 1, 0);
1294 }
1295
1296 static void
gst_ogg_mux_byte_writer_put_string_utf8(GstByteWriter * bw,const char * s)1297 gst_ogg_mux_byte_writer_put_string_utf8 (GstByteWriter * bw, const char *s)
1298 {
1299 if (!gst_byte_writer_put_data (bw, (const guint8 *) s, strlen (s)))
1300 GST_ERROR ("put_data failed");
1301 }
1302
1303 static void
gst_ogg_mux_add_fisbone_message_header(GstOggMux * mux,GstByteWriter * bw,const char * tag,const char * value)1304 gst_ogg_mux_add_fisbone_message_header (GstOggMux * mux, GstByteWriter * bw,
1305 const char *tag, const char *value)
1306 {
1307 /* It is valid to pass NULL as the value to omit the tag */
1308 if (!value)
1309 return;
1310 GST_DEBUG_OBJECT (mux, "Adding fisbone message header %s: %s", tag, value);
1311 gst_ogg_mux_byte_writer_put_string_utf8 (bw, tag);
1312 gst_ogg_mux_byte_writer_put_string_utf8 (bw, ": ");
1313 gst_ogg_mux_byte_writer_put_string_utf8 (bw, value);
1314 gst_ogg_mux_byte_writer_put_string_utf8 (bw, "\r\n");
1315 }
1316
1317 static void
gst_ogg_mux_add_fisbone_message_header_from_tags(GstOggMux * mux,GstByteWriter * bw,const char * header,const char * tag,const GstTagList * tags)1318 gst_ogg_mux_add_fisbone_message_header_from_tags (GstOggMux * mux,
1319 GstByteWriter * bw, const char *header, const char *tag,
1320 const GstTagList * tags)
1321 {
1322 GString *s;
1323 guint size = gst_tag_list_get_tag_size (tags, tag), n;
1324 GST_DEBUG_OBJECT (mux, "Found %u tags for name %s", size, tag);
1325 if (size == 0)
1326 return;
1327 s = g_string_new ("");
1328 for (n = 0; n < size; ++n) {
1329 gchar *tmp;
1330 if (n)
1331 g_string_append (s, ", ");
1332 if (gst_tag_list_get_string_index (tags, tag, n, &tmp)) {
1333 g_string_append (s, tmp);
1334 g_free (tmp);
1335 } else {
1336 GST_WARNING_OBJECT (mux, "Tag %s index %u was not found (%u total)", tag,
1337 n, size);
1338 }
1339 }
1340 gst_ogg_mux_add_fisbone_message_header (mux, bw, header, s->str);
1341 g_string_free (s, TRUE);
1342 }
1343
1344 /* This is a basic placeholder to generate roles for the tracks.
1345 For tracks with more than one video, both video tracks will get
1346 tagged with a "video/main" role, but we have no way of knowing
1347 which one is the main one, if any. We could just pick one. For
1348 audio, it's more complicated as we don't know which is music,
1349 which is dubbing, etc. For kate, we could take a pretty good
1350 guess based on the category, as role essentially is category.
1351 For now, leave this as is. */
1352 static const char *
gst_ogg_mux_get_default_role(GstOggPadData * pad)1353 gst_ogg_mux_get_default_role (GstOggPadData * pad)
1354 {
1355 const char *type = gst_ogg_stream_get_media_type (&pad->map);
1356 if (type) {
1357 if (!strncmp (type, "video/", strlen ("video/")))
1358 return "video/main";
1359 if (!strncmp (type, "audio/", strlen ("audio/")))
1360 return "audio/main";
1361 if (!strcmp (type + strlen (type) - strlen ("kate"), "kate"))
1362 return "text/caption";
1363 }
1364 return NULL;
1365 }
1366
1367 static void
gst_ogg_mux_make_fisbone(GstOggMux * mux,ogg_stream_state * os,GstOggPadData * pad)1368 gst_ogg_mux_make_fisbone (GstOggMux * mux, ogg_stream_state * os,
1369 GstOggPadData * pad)
1370 {
1371 GstByteWriter bw;
1372 gboolean handled = TRUE;
1373
1374 GST_DEBUG_OBJECT (mux,
1375 "Creating %s fisbone for serial %08x",
1376 gst_ogg_stream_get_media_type (&pad->map), pad->map.serialno);
1377
1378 gst_byte_writer_init (&bw);
1379 handled &= gst_byte_writer_put_string_utf8 (&bw, "fisbone");
1380 handled &= gst_byte_writer_put_int32_le (&bw, 44); /* offset to message headers */
1381 handled &= gst_byte_writer_put_uint32_le (&bw, pad->map.serialno);
1382 handled &= gst_byte_writer_put_uint32_le (&bw, pad->map.n_header_packets);
1383 handled &= gst_byte_writer_put_uint64_le (&bw, pad->map.granulerate_n);
1384 handled &= gst_byte_writer_put_uint64_le (&bw, pad->map.granulerate_d);
1385 handled &= gst_byte_writer_put_uint64_le (&bw, 0); /* base granule */
1386 handled &= gst_byte_writer_put_uint32_le (&bw, pad->map.preroll);
1387 handled &= gst_byte_writer_put_uint8 (&bw, pad->map.granuleshift);
1388 handled &= gst_byte_writer_fill (&bw, 0, 3); /* padding */
1389 /* message header fields - MIME type for now */
1390 gst_ogg_mux_add_fisbone_message_header (mux, &bw, "Content-Type",
1391 gst_ogg_stream_get_media_type (&pad->map));
1392 gst_ogg_mux_add_fisbone_message_header (mux, &bw, "Role",
1393 gst_ogg_mux_get_default_role (pad));
1394 gst_ogg_mux_add_fisbone_message_header_from_tags (mux, &bw, "Language",
1395 GST_TAG_LANGUAGE_CODE, pad->tags);
1396 gst_ogg_mux_add_fisbone_message_header_from_tags (mux, &bw, "Title",
1397 GST_TAG_TITLE, pad->tags);
1398
1399 if (G_UNLIKELY (!handled))
1400 GST_WARNING_OBJECT (mux, "Error writing fishbon");
1401
1402 gst_ogg_mux_submit_skeleton_header_packet (mux, os,
1403 gst_byte_writer_reset_and_get_buffer (&bw), 0, 0);
1404 }
1405
1406 static void
gst_ogg_mux_make_fistail(GstOggMux * mux,ogg_stream_state * os)1407 gst_ogg_mux_make_fistail (GstOggMux * mux, ogg_stream_state * os)
1408 {
1409 GST_DEBUG_OBJECT (mux, "Creating fistail");
1410
1411 gst_ogg_mux_submit_skeleton_header_packet (mux, os,
1412 gst_buffer_new_and_alloc (0), 0, 1);
1413 }
1414
1415 /*
1416 * For each pad we need to write out one (small) header in one
1417 * page that allows decoders to identify the type of the stream.
1418 * After that we need to write out all extra info for the decoders.
1419 * In the case of a codec that also needs data as configuration, we can
1420 * find that info in the streamcaps.
1421 * After writing the headers we must start a new page for the data.
1422 */
1423 static GstFlowReturn
gst_ogg_mux_send_headers(GstOggMux * mux)1424 gst_ogg_mux_send_headers (GstOggMux * mux)
1425 {
1426 GSList *walk;
1427 GList *hbufs, *hwalk;
1428 GstCaps *caps;
1429 GstFlowReturn ret;
1430 ogg_page page;
1431 ogg_stream_state skeleton_stream;
1432
1433 hbufs = NULL;
1434 ret = GST_FLOW_OK;
1435
1436 GST_LOG_OBJECT (mux, "collecting headers");
1437
1438 walk = mux->collect->data;
1439 while (walk) {
1440 GstOggPadData *pad;
1441 GstPad *thepad;
1442
1443 pad = (GstOggPadData *) walk->data;
1444 thepad = pad->collect.pad;
1445
1446 walk = g_slist_next (walk);
1447
1448 GST_LOG_OBJECT (mux, "looking at pad %s:%s", GST_DEBUG_PAD_NAME (thepad));
1449
1450 /* if the pad has no buffer and is not sparse, we don't care */
1451 if (pad->buffer == NULL && !pad->map.is_sparse)
1452 continue;
1453
1454 /* now figure out the headers */
1455 pad->map.headers = gst_ogg_mux_get_headers (pad);
1456 }
1457
1458 GST_LOG_OBJECT (mux, "creating BOS pages");
1459 walk = mux->collect->data;
1460 while (walk) {
1461 GstOggPadData *pad;
1462 GstBuffer *buf;
1463 ogg_packet packet;
1464 GstPad *thepad;
1465 GstBuffer *hbuf;
1466 GstMapInfo map;
1467 GstCaps *caps;
1468 const gchar *mime_type = "";
1469
1470 pad = (GstOggPadData *) walk->data;
1471 thepad = pad->collect.pad;
1472 walk = walk->next;
1473
1474 pad->packetno = 0;
1475
1476 GST_LOG_OBJECT (thepad, "looping over headers");
1477
1478 if (pad->map.headers) {
1479 buf = GST_BUFFER (pad->map.headers->data);
1480 pad->map.headers = g_list_remove (pad->map.headers, buf);
1481 } else if (pad->buffer) {
1482 buf = pad->buffer;
1483 gst_buffer_ref (buf);
1484 } else {
1485 /* fixme -- should be caught in the previous list traversal. */
1486 GST_OBJECT_LOCK (thepad);
1487 g_critical ("No headers or buffers on pad %s:%s",
1488 GST_DEBUG_PAD_NAME (thepad));
1489 GST_OBJECT_UNLOCK (thepad);
1490 continue;
1491 }
1492
1493 if ((caps = gst_pad_get_current_caps (thepad))) {
1494 GstStructure *structure = gst_caps_get_structure (caps, 0);
1495 mime_type = gst_structure_get_name (structure);
1496 } else {
1497 GST_INFO_OBJECT (thepad, "got empty caps as negotiated format");
1498 }
1499
1500 /* create a packet from the buffer */
1501 gst_buffer_map (buf, &map, GST_MAP_READ);
1502 packet.packet = map.data;
1503 packet.bytes = map.size;
1504
1505 gst_ogg_mux_create_header_packet (&packet, pad);
1506
1507 /* swap the packet in */
1508 ogg_stream_packetin (&pad->map.stream, &packet);
1509
1510 gst_buffer_unmap (buf, &map);
1511 gst_buffer_unref (buf);
1512
1513 GST_LOG_OBJECT (thepad, "flushing out BOS page");
1514 if (!ogg_stream_flush (&pad->map.stream, &page))
1515 g_critical ("Could not flush BOS page");
1516
1517 hbuf = gst_ogg_mux_buffer_from_page (mux, &page, FALSE);
1518
1519 GST_LOG_OBJECT (mux, "swapped out page with mime type '%s'", mime_type);
1520
1521 /* quick hack: put video pages at the front.
1522 * Ideally, we would have a settable enum for which Ogg
1523 * profile we work with, and order based on that.
1524 * (FIXME: if there is more than one video stream, shouldn't we only put
1525 * one's BOS into the first page, followed by an audio stream's BOS, and
1526 * only then followed by the remaining video and audio streams?) */
1527 if (pad->map.is_video) {
1528 GST_DEBUG_OBJECT (thepad, "putting %s page at the front", mime_type);
1529 hbufs = g_list_prepend (hbufs, hbuf);
1530 } else {
1531 hbufs = g_list_append (hbufs, hbuf);
1532 }
1533
1534 if (caps) {
1535 gst_caps_unref (caps);
1536 }
1537 }
1538
1539 /* The Skeleton BOS goes first - even before the video that went first before */
1540 if (mux->use_skeleton) {
1541 ogg_stream_init (&skeleton_stream, gst_ogg_mux_generate_serialno (mux));
1542 gst_ogg_mux_make_fishead (mux, &skeleton_stream);
1543 while (ogg_stream_flush (&skeleton_stream, &page) > 0) {
1544 GstBuffer *hbuf = gst_ogg_mux_buffer_from_page (mux, &page, FALSE);
1545 hbufs = g_list_append (hbufs, hbuf);
1546 }
1547 }
1548
1549 GST_LOG_OBJECT (mux, "creating next headers");
1550 walk = mux->collect->data;
1551 while (walk) {
1552 GstOggPadData *pad;
1553 GstPad *thepad;
1554
1555 pad = (GstOggPadData *) walk->data;
1556 thepad = pad->collect.pad;
1557
1558 walk = walk->next;
1559
1560 if (mux->use_skeleton)
1561 gst_ogg_mux_make_fisbone (mux, &skeleton_stream, pad);
1562
1563 GST_LOG_OBJECT (mux, "looping over headers for pad %s:%s",
1564 GST_DEBUG_PAD_NAME (thepad));
1565
1566 hwalk = pad->map.headers;
1567 while (hwalk) {
1568 GstBuffer *buf = GST_BUFFER (hwalk->data);
1569 ogg_packet packet;
1570 ogg_page page;
1571 GstMapInfo map;
1572
1573 hwalk = hwalk->next;
1574
1575 /* create a packet from the buffer */
1576 gst_buffer_map (buf, &map, GST_MAP_READ);
1577 packet.packet = map.data;
1578 packet.bytes = map.size;
1579
1580 gst_ogg_mux_create_header_packet (&packet, pad);
1581
1582 /* swap the packet in */
1583 ogg_stream_packetin (&pad->map.stream, &packet);
1584 gst_buffer_unmap (buf, &map);
1585 gst_buffer_unref (buf);
1586
1587 /* if last header, flush page */
1588 if (hwalk == NULL) {
1589 GST_LOG_OBJECT (mux,
1590 "flushing page as packet %" G_GUINT64_FORMAT " is first or "
1591 "last packet", (guint64) packet.packetno);
1592 while (ogg_stream_flush (&pad->map.stream, &page)) {
1593 GstBuffer *hbuf = gst_ogg_mux_buffer_from_page (mux, &page, FALSE);
1594
1595 GST_LOG_OBJECT (mux, "swapped out page");
1596 hbufs = g_list_append (hbufs, hbuf);
1597 }
1598 } else {
1599 GST_LOG_OBJECT (mux, "try to swap out page");
1600 /* just try to swap out a page then */
1601 while (ogg_stream_pageout (&pad->map.stream, &page) > 0) {
1602 GstBuffer *hbuf = gst_ogg_mux_buffer_from_page (mux, &page, FALSE);
1603
1604 GST_LOG_OBJECT (mux, "swapped out page");
1605 hbufs = g_list_append (hbufs, hbuf);
1606 }
1607 }
1608 }
1609 g_list_free (pad->map.headers);
1610 pad->map.headers = NULL;
1611 }
1612
1613 if (mux->use_skeleton) {
1614 /* flush accumulated fisbones, the fistail must be on a separate page */
1615 while (ogg_stream_flush (&skeleton_stream, &page) > 0) {
1616 GstBuffer *hbuf = gst_ogg_mux_buffer_from_page (mux, &page, FALSE);
1617 hbufs = g_list_append (hbufs, hbuf);
1618 }
1619 gst_ogg_mux_make_fistail (mux, &skeleton_stream);
1620 while (ogg_stream_flush (&skeleton_stream, &page) > 0) {
1621 GstBuffer *hbuf = gst_ogg_mux_buffer_from_page (mux, &page, FALSE);
1622 hbufs = g_list_append (hbufs, hbuf);
1623 }
1624 ogg_stream_clear (&skeleton_stream);
1625 }
1626
1627 /* hbufs holds all buffers for the headers now */
1628
1629 /* create caps with the buffers */
1630 /* FIXME: should prefer media type audio/ogg, video/ogg, etc. depending on
1631 * what we create, if acceptable downstream (instead of defaulting to
1632 * application/ogg because that's the first in the template caps) */
1633 caps = gst_pad_get_allowed_caps (mux->srcpad);
1634 if (caps) {
1635 if (!gst_caps_is_fixed (caps))
1636 caps = gst_caps_fixate (caps);
1637 }
1638 if (!caps)
1639 caps = gst_caps_new_empty_simple ("application/ogg");
1640
1641 caps = gst_ogg_mux_set_header_on_caps (caps, hbufs);
1642 gst_pad_set_caps (mux->srcpad, caps);
1643 gst_caps_unref (caps);
1644
1645 /* Send segment event */
1646 {
1647 GstSegment segment;
1648 gst_segment_init (&segment, GST_FORMAT_TIME);
1649 gst_pad_push_event (mux->srcpad, gst_event_new_segment (&segment));
1650 }
1651
1652 /* and send the buffers */
1653 while (hbufs != NULL) {
1654 GstBuffer *buf = GST_BUFFER (hbufs->data);
1655
1656 hbufs = g_list_delete_link (hbufs, hbufs);
1657
1658 if ((ret = gst_ogg_mux_push_buffer (mux, buf, NULL)) != GST_FLOW_OK)
1659 break;
1660 }
1661 /* free any remaining nodes/buffers in case we couldn't push them */
1662 g_list_foreach (hbufs, (GFunc) gst_mini_object_unref, NULL);
1663 g_list_free (hbufs);
1664
1665 return ret;
1666 }
1667
1668 /* this function is called to process data on the best pending pad.
1669 *
1670 * basic idea:
1671 *
1672 * 1) store the selected pad and keep on pulling until we fill a
1673 * complete ogg page or the ogg page is filled above the max-delay
1674 * threshold. This is needed because the ogg spec says that
1675 * you should fill a complete page with data from the same logical
1676 * stream. When the page is filled, go back to 1).
1677 * 2) before filling a page, read ahead one more buffer to see if this
1678 * packet is the last of the stream. We need to do this because the ogg
1679 * spec mandates that the last packet should have the EOS flag set before
1680 * sending it to ogg. if pad->buffer is NULL we need to wait to find out
1681 * whether there are any more buffers.
1682 * 3) pages get queued on a per-pad queue. Every time a page is queued, a
1683 * dequeue is called, which will dequeue the oldest page on any pad, provided
1684 * that ALL pads have at least one marked page in the queue (or remaining
1685 * pads are at EOS)
1686 */
1687 static GstFlowReturn
gst_ogg_mux_process_best_pad(GstOggMux * ogg_mux,GstOggPadData * best)1688 gst_ogg_mux_process_best_pad (GstOggMux * ogg_mux, GstOggPadData * best)
1689 {
1690 GstFlowReturn ret = GST_FLOW_OK;
1691 gboolean delta_unit;
1692 gint64 granulepos = 0;
1693 GstClockTime timestamp, gp_time;
1694 GstBuffer *next_buf;
1695
1696 GST_LOG_OBJECT (ogg_mux, "best pad %" GST_PTR_FORMAT
1697 ", currently pulling from %" GST_PTR_FORMAT, best->collect.pad,
1698 ogg_mux->pulling ? ogg_mux->pulling->collect.pad : NULL);
1699
1700 if (ogg_mux->pulling) {
1701 next_buf = gst_collect_pads_peek (ogg_mux->collect,
1702 &ogg_mux->pulling->collect);
1703 if (next_buf) {
1704 ogg_mux->pulling->eos = FALSE;
1705 gst_buffer_unref (next_buf);
1706 } else if (!ogg_mux->pulling->map.is_sparse) {
1707 GST_DEBUG_OBJECT (ogg_mux->pulling->collect.pad, "setting eos to true");
1708 ogg_mux->pulling->eos = TRUE;
1709 }
1710 }
1711
1712 /* We could end up pushing from the best pad instead, so check that
1713 * as well */
1714 if (best && best != ogg_mux->pulling) {
1715 next_buf = gst_collect_pads_peek (ogg_mux->collect, &best->collect);
1716 if (next_buf) {
1717 best->eos = FALSE;
1718 gst_buffer_unref (next_buf);
1719 } else if (!best->map.is_sparse) {
1720 GST_DEBUG_OBJECT (best->collect.pad, "setting eos to true");
1721 best->eos = TRUE;
1722 }
1723 }
1724
1725 /* if we were already pulling from one pad, but the new "best" buffer is
1726 * from another pad, we need to check if we have reason to flush a page
1727 * for the pad we were pulling from before */
1728 if (ogg_mux->pulling && best &&
1729 ogg_mux->pulling != best && ogg_mux->pulling->buffer) {
1730 GstOggPadData *pad = ogg_mux->pulling;
1731 GstClockTime last_ts = GST_BUFFER_END_TIME (pad->buffer);
1732
1733 /* if the next packet in the current page is going to make the page
1734 * too long, we need to flush */
1735 if (last_ts > ogg_mux->next_ts + ogg_mux->max_delay) {
1736 ogg_page page;
1737
1738 GST_LOG_OBJECT (pad->collect.pad,
1739 GST_GP_FORMAT " stored packet %" G_GINT64_FORMAT
1740 " will make page too long, flushing",
1741 GST_BUFFER_OFFSET_END (pad->buffer),
1742 (gint64) pad->map.stream.packetno);
1743
1744 while (ogg_stream_flush (&pad->map.stream, &page)) {
1745 /* end time of this page is the timestamp of the next buffer */
1746 ogg_mux->pulling->timestamp_end = GST_BUFFER_TIMESTAMP (pad->buffer);
1747 /* Place page into the per-pad queue */
1748 ret = gst_ogg_mux_pad_queue_page (ogg_mux, pad, &page,
1749 pad->first_delta);
1750 /* increment the page number counter */
1751 pad->pageno++;
1752 /* mark other pages as delta */
1753 pad->first_delta = TRUE;
1754 }
1755 pad->new_page = TRUE;
1756 ogg_mux->pulling = NULL;
1757 }
1758 }
1759
1760 /* if we don't know which pad to pull on, use the best one */
1761 if (ogg_mux->pulling == NULL) {
1762 ogg_mux->pulling = best;
1763 GST_LOG_OBJECT (ogg_mux->pulling->collect.pad, "pulling from best pad");
1764
1765 /* remember timestamp and gp time of first buffer for this new pad */
1766 if (ogg_mux->pulling != NULL) {
1767 ogg_mux->next_ts = GST_BUFFER_TIMESTAMP (ogg_mux->pulling->buffer);
1768 GST_LOG_OBJECT (ogg_mux->pulling->collect.pad, "updated times, next ts %"
1769 GST_TIME_FORMAT, GST_TIME_ARGS (ogg_mux->next_ts));
1770 } else {
1771 GST_LOG_OBJECT (ogg_mux->srcpad, "sending EOS");
1772 /* no pad to pull on, send EOS */
1773 gst_pad_push_event (ogg_mux->srcpad, gst_event_new_eos ());
1774 return GST_FLOW_FLUSHING;
1775 }
1776 }
1777
1778 if (ogg_mux->need_headers) {
1779 ret = gst_ogg_mux_send_headers (ogg_mux);
1780 ogg_mux->need_headers = FALSE;
1781 }
1782
1783 /* we are pulling from a pad, continue to do so until a page
1784 * has been filled and queued */
1785 if (ogg_mux->pulling != NULL) {
1786 ogg_packet packet;
1787 ogg_page page;
1788 GstBuffer *buf, *tmpbuf;
1789 GstOggPadData *pad = ogg_mux->pulling;
1790 gint64 duration;
1791 gboolean force_flush;
1792 GstMapInfo map;
1793
1794 GST_LOG_OBJECT (ogg_mux->pulling->collect.pad, "pulling from pad");
1795
1796 /* now see if we have a buffer */
1797 buf = pad->buffer;
1798 if (buf == NULL) {
1799 GST_DEBUG_OBJECT (ogg_mux, "pad was EOS");
1800 ogg_mux->pulling = NULL;
1801 return GST_FLOW_OK;
1802 }
1803
1804 delta_unit = GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
1805 duration = GST_BUFFER_DURATION (buf);
1806
1807 /* if the current "next timestamp" on the pad is unset, then this is the
1808 * first packet on the new page. Update our pad's page timestamp */
1809 if (ogg_mux->pulling->timestamp == GST_CLOCK_TIME_NONE) {
1810 ogg_mux->pulling->timestamp = GST_BUFFER_TIMESTAMP (buf);
1811 GST_LOG_OBJECT (ogg_mux->pulling->collect.pad,
1812 "updated pad timestamp to %" GST_TIME_FORMAT,
1813 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
1814 }
1815 /* create a packet from the buffer */
1816 gst_buffer_map (buf, &map, GST_MAP_READ);
1817 packet.packet = map.data;
1818 packet.bytes = map.size;
1819 packet.granulepos = GST_BUFFER_OFFSET_END (buf);
1820 if (packet.granulepos == -1)
1821 packet.granulepos = 0;
1822 /* mark BOS and packet number */
1823 packet.b_o_s = (pad->packetno == 0);
1824 packet.packetno = pad->packetno++;
1825 GST_LOG_OBJECT (pad->collect.pad, GST_GP_FORMAT
1826 " packet %" G_GINT64_FORMAT " (%ld bytes) created from buffer",
1827 GST_GP_CAST (packet.granulepos), (gint64) packet.packetno,
1828 packet.bytes);
1829
1830 packet.e_o_s = ogg_mux->pulling->eos ? 1 : 0;
1831 tmpbuf = NULL;
1832
1833 /* we flush when we see a new keyframe */
1834 force_flush = (pad->prev_delta && !delta_unit)
1835 || pad->map.always_flush_page;
1836 if (duration != -1) {
1837 pad->duration += duration;
1838 /* if page duration exceeds max, flush page */
1839 if (pad->duration > ogg_mux->max_page_delay) {
1840 force_flush = TRUE;
1841 pad->duration = 0;
1842 }
1843 }
1844
1845 if (GST_BUFFER_IS_DISCONT (buf)) {
1846 if (pad->data_pushed) {
1847 GST_LOG_OBJECT (pad->collect.pad, "got discont");
1848 packet.packetno++;
1849 /* No public API for this; hack things in */
1850 pad->map.stream.pageno++;
1851 force_flush = TRUE;
1852 } else {
1853 GST_LOG_OBJECT (pad->collect.pad, "discont at stream start");
1854 }
1855 }
1856
1857 /* flush the currently built page if necessary */
1858 if (force_flush) {
1859 GST_LOG_OBJECT (pad->collect.pad,
1860 GST_GP_FORMAT " forced flush of page before this packet",
1861 GST_BUFFER_OFFSET_END (pad->buffer));
1862 while (ogg_stream_flush (&pad->map.stream, &page)) {
1863 /* end time of this page is the timestamp of the next buffer */
1864 ogg_mux->pulling->timestamp_end = GST_BUFFER_TIMESTAMP (pad->buffer);
1865 ret = gst_ogg_mux_pad_queue_page (ogg_mux, pad, &page,
1866 pad->first_delta);
1867
1868 /* increment the page number counter */
1869 pad->pageno++;
1870 /* mark other pages as delta */
1871 pad->first_delta = TRUE;
1872 }
1873 pad->new_page = TRUE;
1874 }
1875
1876 /* if this is the first packet of a new page figure out the delta flag */
1877 if (pad->new_page) {
1878 if (delta_unit) {
1879 /* mark the page as delta */
1880 pad->first_delta = TRUE;
1881 } else {
1882 /* got a keyframe */
1883 if (ogg_mux->delta_pad == pad) {
1884 /* if we get it on the pad with deltaunits,
1885 * we mark the page as non delta */
1886 pad->first_delta = FALSE;
1887 } else if (ogg_mux->delta_pad != NULL) {
1888 /* if there are pads with delta frames, we
1889 * must mark this one as delta */
1890 pad->first_delta = TRUE;
1891 } else {
1892 pad->first_delta = FALSE;
1893 }
1894 }
1895 pad->new_page = FALSE;
1896 }
1897
1898 /* save key unit to track delta->key unit transitions */
1899 pad->prev_delta = delta_unit;
1900
1901 /* swap the packet in */
1902 if (packet.e_o_s == 1)
1903 GST_DEBUG_OBJECT (pad->collect.pad, "swapping in EOS packet");
1904 if (packet.b_o_s == 1)
1905 GST_DEBUG_OBJECT (pad->collect.pad, "swapping in BOS packet");
1906
1907 ogg_stream_packetin (&pad->map.stream, &packet);
1908 gst_buffer_unmap (buf, &map);
1909 pad->data_pushed = TRUE;
1910
1911 gp_time = GST_BUFFER_OFFSET (pad->buffer);
1912 granulepos = GST_BUFFER_OFFSET_END (pad->buffer);
1913 timestamp = GST_BUFFER_TIMESTAMP (pad->buffer);
1914
1915 GST_LOG_OBJECT (pad->collect.pad,
1916 GST_GP_FORMAT " packet %" G_GINT64_FORMAT ", gp time %"
1917 GST_TIME_FORMAT ", timestamp %" GST_TIME_FORMAT " packetin'd",
1918 granulepos, (gint64) packet.packetno, GST_TIME_ARGS (gp_time),
1919 GST_TIME_ARGS (timestamp));
1920 /* don't need the old buffer anymore */
1921 gst_buffer_unref (pad->buffer);
1922 /* store new readahead buffer */
1923 pad->buffer = tmpbuf;
1924
1925 /* let ogg write out the pages now. The packet we got could end
1926 * up in more than one page so we need to write them all */
1927 if (ogg_stream_pageout (&pad->map.stream, &page) > 0) {
1928 /* we have a new page, so we need to timestamp it correctly.
1929 * if this fresh packet ends on this page, then the page's granulepos
1930 * comes from that packet, and we should set this buffer's timestamp */
1931
1932 GST_LOG_OBJECT (pad->collect.pad,
1933 GST_GP_FORMAT " packet %" G_GINT64_FORMAT ", time %"
1934 GST_TIME_FORMAT ") caused new page",
1935 granulepos, (gint64) packet.packetno, GST_TIME_ARGS (timestamp));
1936 GST_LOG_OBJECT (pad->collect.pad,
1937 GST_GP_FORMAT " new page %ld",
1938 GST_GP_CAST (ogg_page_granulepos (&page)), pad->map.stream.pageno);
1939
1940 if (ogg_page_granulepos (&page) == granulepos) {
1941 /* the packet we streamed in finishes on the current page,
1942 * because the page's granulepos is the granulepos of the last
1943 * packet completed on that page,
1944 * so update the timestamp that we will give to the page */
1945 GST_LOG_OBJECT (pad->collect.pad,
1946 GST_GP_FORMAT
1947 " packet finishes on current page, updating gp time to %"
1948 GST_TIME_FORMAT, granulepos, GST_TIME_ARGS (gp_time));
1949 pad->gp_time = gp_time;
1950 } else {
1951 GST_LOG_OBJECT (pad->collect.pad,
1952 GST_GP_FORMAT
1953 " packet spans beyond current page, keeping old gp time %"
1954 GST_TIME_FORMAT, granulepos, GST_TIME_ARGS (pad->gp_time));
1955 }
1956
1957 /* push the page */
1958 /* end time of this page is the timestamp of the next buffer */
1959 pad->timestamp_end = timestamp;
1960 ret = gst_ogg_mux_pad_queue_page (ogg_mux, pad, &page, pad->first_delta);
1961 pad->pageno++;
1962 /* mark next pages as delta */
1963 pad->first_delta = TRUE;
1964
1965 /* use an inner loop here to flush the remaining pages and
1966 * mark them as delta frames as well */
1967 while (ogg_stream_pageout (&pad->map.stream, &page) > 0) {
1968 if (ogg_page_granulepos (&page) == granulepos) {
1969 /* the page has taken up the new packet completely, which means
1970 * the packet ends the page and we can update the gp time
1971 * before pushing out */
1972 pad->gp_time = gp_time;
1973 }
1974
1975 /* we have a complete page now, we can push the page
1976 * and make sure to pull on a new pad the next time around */
1977 ret = gst_ogg_mux_pad_queue_page (ogg_mux, pad, &page,
1978 pad->first_delta);
1979 /* increment the page number counter */
1980 pad->pageno++;
1981 }
1982 /* need a new page as well */
1983 pad->new_page = TRUE;
1984 pad->duration = 0;
1985 /* we're done pulling on this pad, make sure to choose a new
1986 * pad for pulling in the next iteration */
1987 ogg_mux->pulling = NULL;
1988 }
1989
1990 /* Update the gp time, if necessary, since any future page will have at
1991 * least this gp time.
1992 */
1993 if (pad->gp_time < gp_time) {
1994 pad->gp_time = gp_time;
1995 GST_LOG_OBJECT (pad->collect.pad,
1996 "Updated running gp time of pad %" GST_PTR_FORMAT
1997 " to %" GST_TIME_FORMAT, pad->collect.pad, GST_TIME_ARGS (gp_time));
1998 }
1999 }
2000
2001 return ret;
2002 }
2003
2004 /* all_pads_eos:
2005 *
2006 * Checks if all pads are EOS'd by peeking.
2007 *
2008 * Returns TRUE if all pads are EOS.
2009 */
2010 static gboolean
all_pads_eos(GstCollectPads * pads)2011 all_pads_eos (GstCollectPads * pads)
2012 {
2013 GSList *walk;
2014
2015 walk = pads->data;
2016 while (walk) {
2017 GstOggPadData *oggpad = (GstOggPadData *) walk->data;
2018
2019 GST_DEBUG_OBJECT (oggpad->collect.pad,
2020 "oggpad %p eos %d", oggpad, oggpad->eos);
2021
2022 if (!oggpad->eos)
2023 return FALSE;
2024
2025 walk = g_slist_next (walk);
2026 }
2027
2028 return TRUE;
2029 }
2030
2031 static void
gst_ogg_mux_send_start_events(GstOggMux * ogg_mux,GstCollectPads * pads)2032 gst_ogg_mux_send_start_events (GstOggMux * ogg_mux, GstCollectPads * pads)
2033 {
2034 gchar s_id[32];
2035
2036 /* stream-start (FIXME: create id based on input ids) and
2037 * also do something with the group id */
2038 g_snprintf (s_id, sizeof (s_id), "oggmux-%08x", g_random_int ());
2039 gst_pad_push_event (ogg_mux->srcpad, gst_event_new_stream_start (s_id));
2040
2041 /* we'll send caps later, need to collect all headers first */
2042 }
2043
2044 /* This function is called when there is data on all pads.
2045 *
2046 * It finds a pad to pull on, this is done by looking at the buffers
2047 * to decide which one to use, and using the 'oldest' one first. It then calls
2048 * gst_ogg_mux_process_best_pad() to process as much data as possible.
2049 *
2050 * If all the pads have received EOS, it flushes out all data by continually
2051 * getting the best pad and calling gst_ogg_mux_process_best_pad() until they
2052 * are all empty, and then sends EOS.
2053 */
2054 static GstFlowReturn
gst_ogg_mux_collected(GstCollectPads * pads,GstOggMux * ogg_mux)2055 gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux)
2056 {
2057 GstOggPadData *best;
2058 GstFlowReturn ret;
2059 gboolean popped;
2060
2061 GST_LOG_OBJECT (ogg_mux, "collected");
2062
2063 if (ogg_mux->need_start_events) {
2064 gst_ogg_mux_send_start_events (ogg_mux, pads);
2065 ogg_mux->need_start_events = FALSE;
2066 }
2067
2068 /* queue buffers on all pads; find a buffer with the lowest timestamp */
2069 best = gst_ogg_mux_queue_pads (ogg_mux, &popped);
2070
2071 if (popped)
2072 return GST_FLOW_OK;
2073
2074 if (best == NULL) {
2075 /* No data, assume EOS */
2076 goto eos;
2077 }
2078
2079 /* This is not supposed to happen */
2080 g_return_val_if_fail (best->buffer != NULL, GST_FLOW_ERROR);
2081
2082 ret = gst_ogg_mux_process_best_pad (ogg_mux, best);
2083
2084 if (best->eos && all_pads_eos (pads))
2085 goto eos;
2086
2087 /* We might have used up a cached pad->buffer. If all streams
2088 * have a buffer ready in collectpads, collectpads will block at
2089 * next chain, and will never call collected again. So we make a
2090 * last call to _queue_pads now, to ensure that collectpads can
2091 * push to at least one pad (mostly for streams with a single
2092 * logical stream). */
2093 gst_ogg_mux_queue_pads (ogg_mux, &popped);
2094
2095 return ret;
2096
2097 eos:
2098 {
2099 GST_DEBUG_OBJECT (ogg_mux, "no data available, must be EOS");
2100 gst_pad_push_event (ogg_mux->srcpad, gst_event_new_eos ());
2101 return GST_FLOW_EOS;
2102 }
2103 }
2104
2105 static void
gst_ogg_mux_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)2106 gst_ogg_mux_get_property (GObject * object,
2107 guint prop_id, GValue * value, GParamSpec * pspec)
2108 {
2109 GstOggMux *ogg_mux;
2110
2111 ogg_mux = GST_OGG_MUX (object);
2112
2113 switch (prop_id) {
2114 case ARG_MAX_DELAY:
2115 g_value_set_uint64 (value, ogg_mux->max_delay);
2116 break;
2117 case ARG_MAX_PAGE_DELAY:
2118 g_value_set_uint64 (value, ogg_mux->max_page_delay);
2119 break;
2120 case ARG_MAX_TOLERANCE:
2121 g_value_set_uint64 (value, ogg_mux->max_tolerance);
2122 break;
2123 case ARG_SKELETON:
2124 g_value_set_boolean (value, ogg_mux->use_skeleton);
2125 break;
2126 default:
2127 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2128 break;
2129 }
2130 }
2131
2132 static void
gst_ogg_mux_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)2133 gst_ogg_mux_set_property (GObject * object,
2134 guint prop_id, const GValue * value, GParamSpec * pspec)
2135 {
2136 GstOggMux *ogg_mux;
2137
2138 ogg_mux = GST_OGG_MUX (object);
2139
2140 switch (prop_id) {
2141 case ARG_MAX_DELAY:
2142 ogg_mux->max_delay = g_value_get_uint64 (value);
2143 break;
2144 case ARG_MAX_PAGE_DELAY:
2145 ogg_mux->max_page_delay = g_value_get_uint64 (value);
2146 break;
2147 case ARG_MAX_TOLERANCE:
2148 ogg_mux->max_tolerance = g_value_get_uint64 (value);
2149 break;
2150 case ARG_SKELETON:
2151 ogg_mux->use_skeleton = g_value_get_boolean (value);
2152 break;
2153 default:
2154 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2155 break;
2156 }
2157 }
2158
2159 /* reset all variables in the ogg pads. */
2160 static void
gst_ogg_mux_init_collectpads(GstCollectPads * collect)2161 gst_ogg_mux_init_collectpads (GstCollectPads * collect)
2162 {
2163 GSList *walk;
2164
2165 walk = collect->data;
2166 while (walk) {
2167 GstOggPadData *oggpad = (GstOggPadData *) walk->data;
2168
2169 ogg_stream_clear (&oggpad->map.stream);
2170 ogg_stream_init (&oggpad->map.stream, oggpad->map.serialno);
2171 oggpad->packetno = 0;
2172 oggpad->pageno = 0;
2173 oggpad->eos = FALSE;
2174 /* we assume there will be some control data first for this pad */
2175 oggpad->state = GST_OGG_PAD_STATE_CONTROL;
2176 oggpad->new_page = TRUE;
2177 oggpad->first_delta = FALSE;
2178 oggpad->prev_delta = FALSE;
2179 oggpad->data_pushed = FALSE;
2180 oggpad->pagebuffers = g_queue_new ();
2181
2182 gst_segment_init (&oggpad->segment, GST_FORMAT_TIME);
2183
2184 walk = g_slist_next (walk);
2185 }
2186 }
2187
2188 /* Clear all buffers from the collectpads object */
2189 static void
gst_ogg_mux_clear_collectpads(GstCollectPads * collect)2190 gst_ogg_mux_clear_collectpads (GstCollectPads * collect)
2191 {
2192 GSList *walk;
2193
2194 for (walk = collect->data; walk; walk = g_slist_next (walk)) {
2195 GstOggPadData *oggpad = (GstOggPadData *) walk->data;
2196 GstBuffer *buf;
2197
2198 ogg_stream_clear (&oggpad->map.stream);
2199
2200 while ((buf = g_queue_pop_head (oggpad->pagebuffers)) != NULL) {
2201 GST_LOG ("flushing buffer : %p", buf);
2202 gst_buffer_unref (buf);
2203 }
2204 g_queue_free (oggpad->pagebuffers);
2205 oggpad->pagebuffers = NULL;
2206
2207 if (oggpad->buffer) {
2208 gst_buffer_unref (oggpad->buffer);
2209 oggpad->buffer = NULL;
2210 }
2211
2212 if (oggpad->tags) {
2213 gst_tag_list_unref (oggpad->tags);
2214 oggpad->tags = NULL;
2215 }
2216
2217 gst_segment_init (&oggpad->segment, GST_FORMAT_TIME);
2218 }
2219 }
2220
2221 static GstStateChangeReturn
gst_ogg_mux_change_state(GstElement * element,GstStateChange transition)2222 gst_ogg_mux_change_state (GstElement * element, GstStateChange transition)
2223 {
2224 GstOggMux *ogg_mux;
2225 GstStateChangeReturn ret;
2226
2227 ogg_mux = GST_OGG_MUX (element);
2228
2229 switch (transition) {
2230 case GST_STATE_CHANGE_NULL_TO_READY:
2231 break;
2232 case GST_STATE_CHANGE_READY_TO_PAUSED:
2233 gst_ogg_mux_clear (ogg_mux);
2234 gst_ogg_mux_init_collectpads (ogg_mux->collect);
2235 gst_collect_pads_start (ogg_mux->collect);
2236 break;
2237 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
2238 break;
2239 case GST_STATE_CHANGE_PAUSED_TO_READY:
2240 gst_collect_pads_stop (ogg_mux->collect);
2241 break;
2242 default:
2243 break;
2244 }
2245
2246 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
2247
2248 switch (transition) {
2249 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
2250 break;
2251 case GST_STATE_CHANGE_PAUSED_TO_READY:
2252 gst_ogg_mux_clear_collectpads (ogg_mux->collect);
2253 break;
2254 case GST_STATE_CHANGE_READY_TO_NULL:
2255 break;
2256 default:
2257 break;
2258 }
2259
2260 return ret;
2261 }
2262