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