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