• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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