1 /* OGG muxer plugin for GStreamer
2 * Copyright (C) 2004 Wim Taymans <wim@fluendo.com>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
25 #include <gst/base/gstcollectpads.h>
28 /* memcpy - if someone knows a way to get rid of it, please speak up
29 * note: the ogg docs even say you need this... */
33 GST_DEBUG_CATEGORY_STATIC (gst_ogg_mux_debug);
34 #define GST_CAT_DEFAULT gst_ogg_mux_debug
36 #define GST_TYPE_OGG_MUX (gst_ogg_mux_get_type())
37 #define GST_OGG_MUX(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OGG_MUX, GstOggMux))
38 #define GST_OGG_MUX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OGG_MUX, GstOggMux))
39 #define GST_IS_OGG_MUX(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OGG_MUX))
40 #define GST_IS_OGG_MUX_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OGG_MUX))
42 #define GST_GP_FORMAT "[gp %8" G_GINT64_FORMAT "]"
44 typedef struct _GstOggMux GstOggMux;
45 typedef struct _GstOggMuxClass GstOggMuxClass;
49 GST_OGG_PAD_STATE_CONTROL = 0,
50 GST_OGG_PAD_STATE_DATA = 1
54 /* all information needed for one ogg stream */
57 GstCollectData collect; /* we extend the CollectData */
59 GstBuffer *buffer; /* the queued buffer for this pad */
62 ogg_stream_state stream;
63 gint64 packetno; /* number of next packet */
64 gint64 pageno; /* number of next page */
65 guint64 duration; /* duration of current page */
68 GstClockTime timestamp; /* timestamp for granulepos of last complete
69 packet on this page */
71 GstOggPadState state; /* state of the pad */
75 GQueue *pagebuffers; /* List of pages in buffers ready for pushing */
77 gboolean new_page; /* starting a new page */
78 gboolean first_delta; /* was the first packet in the page a delta */
79 gboolean prev_delta; /* was the previous buffer a delta frame */
91 GstCollectPads *collect;
93 /* the pad we are currently using to fill a page */
96 /* next timestamp for the page */
99 /* offset in stream */
103 gboolean need_headers;
106 guint64 max_page_delay;
108 GstOggPad *delta_pad; /* when a delta frame is detected on a stream, we mark
109 pages as delta frames up to the page that has the
116 GST_OGG_FLAG_BOS = GST_ELEMENT_FLAG_LAST,
121 struct _GstOggMuxClass
123 GstElementClass parent_class;
126 /* elementfactory information */
127 static GstElementDetails gst_ogg_mux_details = GST_ELEMENT_DETAILS ("ogg muxer",
129 "mux ogg streams (info about ogg: http://xiph.org)",
130 "Wim Taymans <wim@fluendo.com>");
132 /* OggMux signals and args */
139 /* set to 0.5 seconds by default */
140 #define DEFAULT_MAX_DELAY G_GINT64_CONSTANT(500000000)
141 #define DEFAULT_MAX_PAGE_DELAY G_GINT64_CONSTANT(500000000)
149 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
152 GST_STATIC_CAPS ("application/ogg")
155 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink_%d",
158 GST_STATIC_CAPS ("video/x-theora; "
159 "audio/x-vorbis; audio/x-flac; audio/x-speex; "
160 "application/x-ogm-video; application/x-ogm-audio")
163 static void gst_ogg_mux_base_init (gpointer g_class);
164 static void gst_ogg_mux_class_init (GstOggMuxClass * klass);
165 static void gst_ogg_mux_init (GstOggMux * ogg_mux);
166 static void gst_ogg_mux_finalize (GObject * object);
169 gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux);
170 static gboolean gst_ogg_mux_handle_src_event (GstPad * pad, GstEvent * event);
171 static GstPad *gst_ogg_mux_request_new_pad (GstElement * element,
172 GstPadTemplate * templ, const gchar * name);
173 static void gst_ogg_mux_set_property (GObject * object,
174 guint prop_id, const GValue * value, GParamSpec * pspec);
175 static void gst_ogg_mux_get_property (GObject * object,
176 guint prop_id, GValue * value, GParamSpec * pspec);
177 static GstStateChangeReturn gst_ogg_mux_change_state (GstElement * element,
178 GstStateChange transition);
180 static GstElementClass *parent_class = NULL;
182 /*static guint gst_ogg_mux_signals[LAST_SIGNAL] = { 0 }; */
185 gst_ogg_mux_get_type (void)
187 static GType ogg_mux_type = 0;
190 static const GTypeInfo ogg_mux_info = {
191 sizeof (GstOggMuxClass),
192 gst_ogg_mux_base_init,
194 (GClassInitFunc) gst_ogg_mux_class_init,
199 (GInstanceInitFunc) gst_ogg_mux_init,
203 g_type_register_static (GST_TYPE_ELEMENT, "GstOggMux", &ogg_mux_info,
210 gst_ogg_mux_base_init (gpointer g_class)
212 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
214 gst_element_class_add_pad_template (element_class,
215 gst_static_pad_template_get (&src_factory));
216 gst_element_class_add_pad_template (element_class,
217 gst_static_pad_template_get (&sink_factory));
219 gst_element_class_set_details (element_class, &gst_ogg_mux_details);
223 gst_ogg_mux_class_init (GstOggMuxClass * klass)
225 GObjectClass *gobject_class;
226 GstElementClass *gstelement_class;
228 gobject_class = (GObjectClass *) klass;
229 gstelement_class = (GstElementClass *) klass;
231 parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
233 gobject_class->finalize = gst_ogg_mux_finalize;
234 gobject_class->get_property = gst_ogg_mux_get_property;
235 gobject_class->set_property = gst_ogg_mux_set_property;
237 gstelement_class->request_new_pad = gst_ogg_mux_request_new_pad;
239 g_object_class_install_property (gobject_class, ARG_MAX_DELAY,
240 g_param_spec_uint64 ("max-delay", "Max delay",
241 "Maximum delay in multiplexing streams", 0, G_MAXUINT64,
242 DEFAULT_MAX_DELAY, (GParamFlags) G_PARAM_READWRITE));
243 g_object_class_install_property (gobject_class, ARG_MAX_PAGE_DELAY,
244 g_param_spec_uint64 ("max-page-delay", "Max page delay",
245 "Maximum delay for sending out a page", 0, G_MAXUINT64,
246 DEFAULT_MAX_PAGE_DELAY, (GParamFlags) G_PARAM_READWRITE));
248 gstelement_class->change_state = gst_ogg_mux_change_state;
253 static const GstEventMask *
254 gst_ogg_mux_get_sink_event_masks (GstPad * pad)
256 static const GstEventMask gst_ogg_mux_sink_event_masks[] = {
258 {GST_EVENT_DISCONTINUOUS, 0},
262 return gst_ogg_mux_sink_event_masks;
267 gst_ogg_mux_clear (GstOggMux * ogg_mux)
269 ogg_mux->pulling = NULL;
270 ogg_mux->need_headers = TRUE;
271 ogg_mux->max_delay = DEFAULT_MAX_DELAY;
272 ogg_mux->max_page_delay = DEFAULT_MAX_PAGE_DELAY;
273 ogg_mux->delta_pad = NULL;
277 gst_ogg_mux_init (GstOggMux * ogg_mux)
279 GstElementClass *klass = GST_ELEMENT_GET_CLASS (ogg_mux);
282 gst_pad_new_from_template (gst_element_class_get_pad_template (klass,
284 gst_pad_set_event_function (ogg_mux->srcpad, gst_ogg_mux_handle_src_event);
285 gst_element_add_pad (GST_ELEMENT (ogg_mux), ogg_mux->srcpad);
287 GST_OBJECT_FLAG_SET (GST_ELEMENT (ogg_mux), GST_OGG_FLAG_BOS);
289 /* seed random number generator for creation of serial numbers */
292 ogg_mux->collect = gst_collect_pads_new ();
293 gst_collect_pads_set_function (ogg_mux->collect,
294 (GstCollectPadsFunction) gst_ogg_mux_collected, ogg_mux);
296 gst_ogg_mux_clear (ogg_mux);
300 gst_ogg_mux_finalize (GObject * object)
304 ogg_mux = GST_OGG_MUX (object);
306 if (ogg_mux->collect) {
307 gst_object_unref (ogg_mux->collect);
308 ogg_mux->collect = NULL;
311 G_OBJECT_CLASS (parent_class)->finalize (object);
314 static GstPadLinkReturn
315 gst_ogg_mux_sinkconnect (GstPad * pad, GstPad * peer)
320 ogg_mux = GST_OGG_MUX (gst_pad_get_parent (pad));
322 name = gst_pad_get_name (pad);
324 GST_DEBUG_OBJECT (ogg_mux, "sinkconnect triggered on %s", name);
328 return GST_PAD_LINK_OK;
332 gst_ogg_mux_request_new_pad (GstElement * element,
333 GstPadTemplate * templ, const gchar * req_name)
337 GstElementClass *klass;
339 g_return_val_if_fail (templ != NULL, NULL);
341 if (templ->direction != GST_PAD_SINK)
342 goto wrong_direction;
344 g_return_val_if_fail (GST_IS_OGG_MUX (element), NULL);
345 ogg_mux = GST_OGG_MUX (element);
347 klass = GST_ELEMENT_GET_CLASS (element);
349 if (templ != gst_element_class_get_pad_template (klass, "sink_%d"))
356 if (req_name == NULL || strlen (req_name) < 6) {
357 /* no name given when requesting the pad, use random serial number */
360 /* parse serial number from requested padname */
361 serial = atoi (&req_name[5]);
363 /* create new pad with the name */
364 name = g_strdup_printf ("sink_%d", serial);
365 newpad = gst_pad_new_from_template (templ, name);
368 /* construct our own wrapper data structure for the pad to
369 * keep track of its status */
373 oggpad = (GstOggPad *)
374 gst_collect_pads_add_pad (ogg_mux->collect, newpad,
377 oggpad->serial = serial;
378 ogg_stream_init (&oggpad->stream, serial);
379 oggpad->packetno = 0;
382 /* we assume there will be some control data first for this pad */
383 oggpad->state = GST_OGG_PAD_STATE_CONTROL;
384 oggpad->new_page = TRUE;
385 oggpad->first_delta = FALSE;
386 oggpad->prev_delta = FALSE;
387 /* TODO: delete this queue (and the things contained within) later,
388 * possibly when doing gst_collect_pads_remove_pad() (which we don't seem
391 oggpad->pagebuffers = g_queue_new ();
395 /* setup some pad functions */
396 gst_pad_set_link_function (newpad, gst_ogg_mux_sinkconnect);
397 /* dd the pad to the element */
398 gst_element_add_pad (element, newpad);
405 g_warning ("ogg_mux: request pad that is not a SINK pad\n");
410 g_warning ("ogg_mux: this is not our template!\n");
417 gst_ogg_mux_handle_src_event (GstPad * pad, GstEvent * event)
422 ogg_mux = GST_OGG_MUX (gst_pad_get_parent (pad));
424 type = event ? GST_EVENT_TYPE (event) : GST_EVENT_UNKNOWN;
428 /* disable seeking for now */
434 return gst_pad_event_default (pad, event);
438 gst_ogg_mux_buffer_from_page (GstOggMux * mux, ogg_page * page, gboolean delta)
442 /* allocate space for header and body */
443 buffer = gst_buffer_new_and_alloc (page->header_len + page->body_len);
444 memcpy (GST_BUFFER_DATA (buffer), page->header, page->header_len);
445 memcpy (GST_BUFFER_DATA (buffer) + page->header_len,
446 page->body, page->body_len);
448 /* next_ts was the timestamp of the first buffer put in this page */
449 GST_BUFFER_TIMESTAMP (buffer) = mux->next_ts;
450 GST_BUFFER_OFFSET (buffer) = mux->offset;
451 mux->offset += GST_BUFFER_SIZE (buffer);
452 /* Here we set granulepos as our OFFSET_END to give easy direct access to
453 * this value later. Before we push it, we reset this to OFFSET + SIZE
454 * (see gst_ogg_mux_push_buffer). */
455 GST_BUFFER_OFFSET_END (buffer) = ogg_page_granulepos (page);
457 GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT);
463 gst_ogg_mux_push_buffer (GstOggMux * mux, GstBuffer * buffer)
465 GST_BUFFER_OFFSET_END (buffer) = GST_BUFFER_OFFSET (buffer) +
466 GST_BUFFER_SIZE (buffer);
468 return gst_pad_push (mux->srcpad, buffer);
471 /* if all queues have at least one page, dequeue the page with the lowest
474 gst_ogg_mux_dequeue_page (GstOggMux * mux, GstFlowReturn * flowret)
477 GstOggPad *opad = NULL; /* "oldest" pad */
478 GstClockTime oldest = GST_CLOCK_TIME_NONE;
479 GstBuffer *buf = NULL;
480 gboolean ret = FALSE;
482 *flowret = GST_FLOW_OK;
484 walk = mux->collect->data;
486 GstOggPad *pad = (GstOggPad *) walk->data;
488 /* We need each queue to either be at EOS, or have one or more pages
489 * available with a set granulepos (i.e. not -1), otherwise we don't have
490 * enough data yet to determine which stream needs to go next for correct
492 if (pad->pagebuffers->length == 0) {
494 GST_LOG_OBJECT (pad, "pad is EOS, skipping for dequeue decision");
496 GST_LOG_OBJECT (pad, "no pages in this queue, can't dequeue");
500 /* We then need to check for a non-negative granulepos */
502 gboolean valid = FALSE;
504 for (i = 0; i < pad->pagebuffers->length; i++) {
505 buf = g_queue_peek_nth (pad->pagebuffers, i);
506 /* Here we check the OFFSET_END, which is actually temporarily the
507 * granulepos value for this buffer */
508 if (GST_BUFFER_OFFSET_END (buf) != -1) {
514 GST_LOG_OBJECT (pad, "No page timestamps in queue, can't dequeue");
519 walk = g_slist_next (walk);
522 walk = mux->collect->data;
524 GstOggPad *pad = (GstOggPad *) walk->data;
526 /* any page with a granulepos of -1 can be pushed immediately.
527 * TODO: it CAN be, but it seems silly to do so? */
528 buf = g_queue_peek_head (pad->pagebuffers);
529 while (buf && GST_BUFFER_OFFSET_END (buf) == -1) {
530 GST_LOG_OBJECT (pad, GST_GP_FORMAT " pushing page", -1);
531 g_queue_pop_head (pad->pagebuffers);
532 *flowret = gst_ogg_mux_push_buffer (mux, buf);
533 buf = g_queue_peek_head (pad->pagebuffers);
538 /* if no oldest buffer yet, take this one */
539 if (oldest == GST_CLOCK_TIME_NONE) {
540 oldest = GST_BUFFER_TIMESTAMP (buf);
543 /* if we have an oldest, compare with this one */
544 if (GST_BUFFER_TIMESTAMP (buf) < oldest) {
545 oldest = GST_BUFFER_TIMESTAMP (buf);
550 walk = g_slist_next (walk);
553 if (oldest != GST_CLOCK_TIME_NONE) {
555 buf = g_queue_pop_head (opad->pagebuffers);
556 GST_LOG_OBJECT (opad,
557 GST_GP_FORMAT " pushing oldest page (time %" GST_TIME_FORMAT ")",
558 GST_BUFFER_OFFSET_END (buf),
559 GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
560 *flowret = gst_ogg_mux_push_buffer (mux, buf);
567 /* put the given page on a per-pad queue, timestamping it correctly.
568 * after that, dequeue and push as many pages as possible
569 * before calling me, make sure that the the pad's timestamp matches
570 * the page's granulepos */
572 gst_ogg_mux_pad_queue_page (GstOggMux * mux, GstOggPad * pad, ogg_page * page,
575 GstBuffer *buffer = gst_ogg_mux_buffer_from_page (mux, page, delta);
578 /* take the timestamp of the last completed packet on this page */
579 GST_BUFFER_TIMESTAMP (buffer) = pad->timestamp;
580 g_queue_push_tail (pad->pagebuffers, buffer);
581 GST_LOG_OBJECT (pad, GST_GP_FORMAT " queued buffer page (time %" GST_TIME_FORMAT "), %d page buffers queued", ogg_page_granulepos (page), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), pad->pagebuffers->length); /* no g_queue_get_length in 2.2 */
583 while (gst_ogg_mux_dequeue_page (mux, &ret)) {
584 if (ret != GST_FLOW_OK)
592 * Given two pads, compare the buffers queued on it and return 0 if they have
593 * an equal priority, 1 if the new pad is better, -1 if the old pad is better
596 gst_ogg_mux_compare_pads (GstOggMux * ogg_mux, GstOggPad * old, GstOggPad * new)
598 guint64 oldtime, newtime;
600 /* if the old pad doesn't contain anything or is even NULL, return
601 * the new pad as best candidate and vice versa */
602 if (old == NULL || old->buffer == NULL)
604 if (new == NULL || new->buffer == NULL)
607 /* no timestamp on old buffer, it must go first */
608 oldtime = GST_BUFFER_TIMESTAMP (old->buffer);
609 if (oldtime == GST_CLOCK_TIME_NONE)
612 /* no timestamp on new buffer, it must go first */
613 newtime = GST_BUFFER_TIMESTAMP (new->buffer);
614 if (newtime == GST_CLOCK_TIME_NONE)
617 /* old buffer has higher timestamp, new one should go first */
618 if (newtime < oldtime)
620 /* new buffer has higher timestamp, old one should go first */
621 else if (newtime > oldtime)
624 /* buffers with equal timestamps, prefer the pad that has the
625 * least number of pages muxed */
626 if (new->pageno < old->pageno)
628 else if (new->pageno > old->pageno)
632 /* same priority if all of the above failed */
636 /* make sure a buffer is queued on all pads, returns a pointer to an oggpad
637 * that holds the best buffer or NULL when no pad was usable.
638 * "best" means the buffer marked with the lowest timestamp */
640 gst_ogg_mux_queue_pads (GstOggMux * ogg_mux)
642 GstOggPad *bestpad = NULL, *still_hungry = NULL;
645 /* try to make sure we have a buffer from each usable pad first */
646 walk = ogg_mux->collect->data;
649 GstCollectData *data;
651 data = (GstCollectData *) walk->data;
652 pad = (GstOggPad *) data;
654 walk = g_slist_next (walk);
656 GST_DEBUG_OBJECT (ogg_mux, "looking at pad %p", pad);
658 /* try to get a new buffer for this pad if needed and possible */
659 if (pad->buffer == NULL) {
663 buf = gst_collect_pads_pop (ogg_mux->collect, data);
664 GST_DEBUG_OBJECT (ogg_mux, "popping buffer %p", buf);
666 /* On EOS we get a NULL buffer */
668 incaps = GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_IN_CAPS);
669 /* if we need headers */
670 if (pad->state == GST_OGG_PAD_STATE_CONTROL) {
671 /* and we have one */
673 GST_DEBUG_OBJECT (ogg_mux,
674 "got incaps buffer in control state, ignoring");
676 gst_buffer_unref (buf);
679 GST_DEBUG_OBJECT (ogg_mux,
680 "got data buffer in control state, switching " "to data mode");
681 /* this is a data buffer so switch to data state */
682 pad->state = GST_OGG_PAD_STATE_DATA;
686 GST_DEBUG_OBJECT (ogg_mux, "EOS on pad");
693 /* we should have a buffer now, see if it is the best pad to
696 if (gst_ogg_mux_compare_pads (ogg_mux, bestpad, pad) > 0) {
697 GST_DEBUG_OBJECT (ogg_mux, "best pad now %p", pad);
700 } else if (!pad->eos) {
701 GST_DEBUG_OBJECT (ogg_mux, "hungry pad %p", pad);
707 /* drop back into collectpads... */
714 gst_ogg_mux_get_headers (GstOggPad * pad)
718 GstStructure *structure;
722 thepad = pad->collect.pad;
724 ogg_mux = GST_OGG_MUX (GST_PAD_PARENT (thepad));
726 GST_LOG_OBJECT (thepad, "getting headers");
728 caps = gst_pad_get_negotiated_caps (thepad);
730 const GValue *streamheader;
732 structure = gst_caps_get_structure (caps, 0);
733 streamheader = gst_structure_get_value (structure, "streamheader");
734 if (streamheader != NULL) {
735 GST_LOG_OBJECT (thepad, "got header");
736 if (G_VALUE_TYPE (streamheader) == GST_TYPE_ARRAY) {
737 GArray *bufarr = g_value_peek_pointer (streamheader);
740 GST_LOG_OBJECT (thepad, "got fixed list");
742 for (i = 0; i < bufarr->len; i++) {
743 GValue *bufval = &g_array_index (bufarr, GValue, i);
745 GST_LOG_OBJECT (thepad, "item %d", i);
746 if (G_VALUE_TYPE (bufval) == GST_TYPE_BUFFER) {
747 GstBuffer *buf = g_value_peek_pointer (bufval);
749 GST_LOG_OBJECT (thepad, "adding item %d to header list", i);
751 gst_buffer_ref (buf);
752 res = g_list_append (res, buf);
756 GST_LOG_OBJECT (thepad, "streamheader is not fixed list");
759 GST_LOG_OBJECT (thepad, "caps done have streamheader");
762 GST_LOG_OBJECT (thepad, "got empty caps as negotiated format");
768 gst_ogg_mux_set_header_on_caps (GstCaps * caps, GList * buffers)
770 GstStructure *structure;
771 GValue array = { 0 };
772 GList *walk = buffers;
774 caps = gst_caps_make_writable (caps);
776 structure = gst_caps_get_structure (caps, 0);
778 /* put buffers in a fixed list */
779 g_value_init (&array, GST_TYPE_ARRAY);
782 GstBuffer *buf = GST_BUFFER (walk->data);
783 GValue value = { 0 };
788 GST_LOG ("Setting IN_CAPS on buffer of length %d", GST_BUFFER_SIZE (buf));
789 GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_IN_CAPS);
791 g_value_init (&value, GST_TYPE_BUFFER);
792 gst_value_set_buffer (&value, buf);
793 gst_value_array_append_value (&array, &value);
794 g_value_unset (&value);
796 gst_structure_set_value (structure, "streamheader", &array);
797 g_value_unset (&array);
803 * For each pad we need to write out one (small) header in one
804 * page that allows decoders to identify the type of the stream.
805 * After that we need to write out all extra info for the decoders.
806 * In the case of a codec that also needs data as configuration, we can
807 * find that info in the streamcaps.
808 * After writing the headers we must start a new page for the data.
811 gst_ogg_mux_send_headers (GstOggMux * mux)
814 GList *hbufs, *hwalk;
821 GST_LOG_OBJECT (mux, "collecting headers");
823 walk = mux->collect->data;
828 pad = (GstOggPad *) walk->data;
829 thepad = pad->collect.pad;
831 walk = g_slist_next (walk);
833 GST_LOG_OBJECT (mux, "looking at pad %s:%s", GST_DEBUG_PAD_NAME (thepad));
835 /* if the pad has no buffer, we don't care */
836 if (pad->buffer == NULL)
839 /* now figure out the headers */
840 pad->headers = gst_ogg_mux_get_headers (pad);
843 GST_LOG_OBJECT (mux, "creating first headers");
844 walk = mux->collect->data;
852 pad = (GstOggPad *) walk->data;
853 thepad = pad->collect.pad;
859 GST_LOG_OBJECT (mux, "looping over headers for pad %s:%s",
860 GST_DEBUG_PAD_NAME (thepad));
863 buf = GST_BUFFER (pad->headers->data);
864 pad->headers = g_list_remove (pad->headers, buf);
865 } else if (pad->buffer) {
867 gst_buffer_ref (buf);
869 /* fixme -- should be caught in the previous list traversal. */
870 GST_OBJECT_LOCK (pad);
871 g_critical ("No headers or buffers on pad %s:%s",
872 GST_DEBUG_PAD_NAME (pad));
873 GST_OBJECT_UNLOCK (pad);
877 /* create a packet from the buffer */
878 packet.packet = GST_BUFFER_DATA (buf);
879 packet.bytes = GST_BUFFER_SIZE (buf);
880 packet.granulepos = GST_BUFFER_OFFSET_END (buf);
881 if (packet.granulepos == -1)
882 packet.granulepos = 0;
883 /* mark BOS and packet number */
884 packet.b_o_s = (pad->packetno == 0);
885 packet.packetno = pad->packetno++;
889 /* swap the packet in */
890 ogg_stream_packetin (&pad->stream, &packet);
891 gst_buffer_unref (buf);
893 GST_LOG_OBJECT (mux, "flushing page with first packet");
894 while (ogg_stream_flush (&pad->stream, &page)) {
895 GstBuffer *hbuf = gst_ogg_mux_buffer_from_page (mux, &page, FALSE);
897 GST_LOG_OBJECT (mux, "swapped out page");
898 hbufs = g_list_append (hbufs, hbuf);
902 GST_LOG_OBJECT (mux, "creating next headers");
903 walk = mux->collect->data;
908 pad = (GstOggPad *) walk->data;
909 thepad = pad->collect.pad;
913 GST_LOG_OBJECT (mux, "looping over headers for pad %s:%s",
914 GST_DEBUG_PAD_NAME (thepad));
916 hwalk = pad->headers;
918 GstBuffer *buf = GST_BUFFER (hwalk->data);
924 /* create a packet from the buffer */
925 packet.packet = GST_BUFFER_DATA (buf);
926 packet.bytes = GST_BUFFER_SIZE (buf);
927 packet.granulepos = GST_BUFFER_OFFSET_END (buf);
928 if (packet.granulepos == -1)
929 packet.granulepos = 0;
930 /* mark BOS and packet number */
931 packet.b_o_s = (pad->packetno == 0);
932 packet.packetno = pad->packetno++;
936 /* swap the packet in */
937 ogg_stream_packetin (&pad->stream, &packet);
938 gst_buffer_unref (buf);
940 /* if last header, flush page */
943 "flushing page as packet %d is first or last packet",
945 while (ogg_stream_flush (&pad->stream, &page)) {
946 GstBuffer *hbuf = gst_ogg_mux_buffer_from_page (mux, &page, FALSE);
948 GST_LOG_OBJECT (mux, "swapped out page");
949 hbufs = g_list_append (hbufs, hbuf);
952 GST_LOG_OBJECT (mux, "try to swap out page");
953 /* just try to swap out a page then */
954 while (ogg_stream_pageout (&pad->stream, &page) > 0) {
955 GstBuffer *hbuf = gst_ogg_mux_buffer_from_page (mux, &page, FALSE);
957 GST_LOG_OBJECT (mux, "swapped out page");
958 hbufs = g_list_append (hbufs, hbuf);
962 g_list_free (pad->headers);
965 /* hbufs holds all buffers for the headers now */
967 /* create caps with the buffers */
968 caps = gst_pad_get_caps (mux->srcpad);
970 caps = gst_ogg_mux_set_header_on_caps (caps, hbufs);
971 gst_pad_set_caps (mux->srcpad, caps);
972 gst_caps_unref (caps);
974 /* and send the buffers */
977 GstBuffer *buf = GST_BUFFER (hwalk->data);
981 if ((ret = gst_ogg_mux_push_buffer (mux, buf)) != GST_FLOW_OK)
989 /* this function is called when there is data on all pads.
993 * 1) find a pad to pull on, this is done by looking at the buffers
994 * to decide which one to use, we use the 'oldest' one first.
995 * 2) store the selected pad and keep on pulling until we fill a
996 * complete ogg page or the ogg page is filled above the max-delay
997 * threshold. This is needed because the ogg spec says that
998 * you should fill a complete page with data from the same logical
999 * stream. When the page is filled, go back to 1).
1000 * 3) before filling a page, read ahead one more buffer to see if this
1001 * packet is the last of the stream. We need to do this because the ogg
1002 * spec mandates that the last packet should have the EOS flag set before
1003 * sending it to ogg. FIXME: Apparently we're allowed to send empty 'nil'
1004 * pages with the EOS flag set for EOS, so we could do this. Not sure how
1005 * that works, though. TODO: 'read ahead one more buffer' is a bit funky
1006 * with collectpads. Rethink this.
1007 * 4) pages get queued on a per-pad queue. Every time a page is queued, a
1008 * dequeue is called, which will dequeue the oldest page on any pad, provided
1009 * that ALL pads have at least one marked page in the queue (or remaining
1012 static GstFlowReturn
1013 gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux)
1016 gboolean delta_unit;
1018 gint64 granulepos = 0;
1019 GstClockTime timestamp;
1021 GST_DEBUG_OBJECT (ogg_mux, "collected");
1023 /* queue buffers on all pads; find a buffer with the lowest timestamp */
1024 best = gst_ogg_mux_queue_pads (ogg_mux);
1025 if (best && !best->buffer)
1028 GST_DEBUG_OBJECT (ogg_mux, "best pad %p, pulling %p", best, ogg_mux->pulling);
1030 if (!best) { /* EOS : FIXME !! We need to handle EOS correctly, and set EOS
1031 flags on the ogg pages. */
1032 GST_DEBUG_OBJECT (ogg_mux, "Pushing EOS");
1033 gst_pad_push_event (ogg_mux->srcpad, gst_event_new_eos ());
1034 return GST_FLOW_WRONG_STATE;
1037 /* if we were already pulling from one pad, but the new "best" buffer is
1038 * from another pad, we need to check if we have reason to flush a page
1039 * for the pad we were pulling from before */
1040 if (ogg_mux->pulling && best &&
1041 ogg_mux->pulling != best && ogg_mux->pulling->buffer) {
1042 GstOggPad *pad = ogg_mux->pulling;
1044 GstClockTime last_ts =
1045 GST_BUFFER_TIMESTAMP (pad->buffer) + GST_BUFFER_DURATION (pad->buffer);
1047 /* if the next packet in the current page is going to make the page
1048 * too long, we need to flush */
1049 if (last_ts > ogg_mux->next_ts + ogg_mux->max_delay) {
1052 while (ogg_stream_flush (&pad->stream, &page)) {
1053 /* Place page into the per-pad queue */
1054 ret = gst_ogg_mux_pad_queue_page (ogg_mux, pad, &page,
1056 /* increment the page number counter */
1058 /* mark other pages as delta */
1059 pad->first_delta = TRUE;
1061 pad->new_page = TRUE;
1062 ogg_mux->pulling = NULL;
1066 if (ogg_mux->need_headers) {
1067 ret = gst_ogg_mux_send_headers (ogg_mux);
1068 ogg_mux->need_headers = FALSE;
1071 /* if we don't know which pad to pull on, use the best one */
1072 if (ogg_mux->pulling == NULL) {
1073 ogg_mux->pulling = best;
1074 GST_DEBUG_OBJECT (ogg_mux, "pulling now %p", ogg_mux->pulling);
1075 /* remember timestamp of first buffer for this new pad */
1076 if (ogg_mux->pulling != NULL) {
1077 ogg_mux->next_ts = GST_BUFFER_TIMESTAMP (ogg_mux->pulling->buffer);
1079 /* no pad to pull on, send EOS */
1080 gst_pad_push_event (ogg_mux->srcpad, gst_event_new_eos ());
1081 return GST_FLOW_WRONG_STATE;
1085 /* we are pulling from a pad, continue to do so until a page
1086 * has been filled and queued */
1087 if (ogg_mux->pulling != NULL) {
1090 GstBuffer *buf, *tmpbuf;
1091 GstOggPad *pad = ogg_mux->pulling;
1093 gboolean force_flush;
1095 GST_DEBUG_OBJECT (ogg_mux, "pulling now %p", ogg_mux->pulling);
1097 /* now see if we have a buffer */
1100 GST_DEBUG_OBJECT (ogg_mux, "pad was EOS");
1101 ogg_mux->pulling = NULL;
1105 delta_unit = GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
1106 duration = GST_BUFFER_DURATION (buf);
1108 /* create a packet from the buffer */
1109 packet.packet = GST_BUFFER_DATA (buf);
1110 packet.bytes = GST_BUFFER_SIZE (buf);
1111 packet.granulepos = GST_BUFFER_OFFSET_END (buf);
1112 if (packet.granulepos == -1)
1113 packet.granulepos = 0;
1114 /* mark BOS and packet number */
1115 packet.b_o_s = (pad->packetno == 0);
1116 packet.packetno = pad->packetno++;
1121 /* we flush when we see a new keyframe */
1122 force_flush = (pad->prev_delta && !delta_unit);
1123 if (duration != -1) {
1124 pad->duration += duration;
1125 /* if page duration exceeds max, flush page */
1126 if (pad->duration > ogg_mux->max_page_delay) {
1132 /* flush the currently built page if neccesary */
1134 while (ogg_stream_flush (&pad->stream, &page)) {
1135 ret = gst_ogg_mux_pad_queue_page (ogg_mux, pad, &page,
1137 /* increment the page number counter */
1139 /* mark other pages as delta */
1140 pad->first_delta = TRUE;
1142 pad->new_page = TRUE;
1145 /* if this is the first packet of a new page figure out the delta flag */
1146 if (pad->new_page) {
1148 /* This page is a delta frame */
1149 if (ogg_mux->delta_pad == NULL) {
1150 /* we got a delta unit on this pad */
1151 ogg_mux->delta_pad = pad;
1153 /* mark the page as delta */
1154 pad->first_delta = TRUE;
1156 /* got a keyframe */
1157 if (ogg_mux->delta_pad == pad) {
1158 /* if we get it on the pad with deltaunits,
1159 * we mark the page as non delta */
1160 pad->first_delta = FALSE;
1161 } else if (ogg_mux->delta_pad != NULL) {
1162 /* if there are pads with delta frames, we
1163 * must mark this one as delta */
1164 pad->first_delta = TRUE;
1166 pad->first_delta = FALSE;
1169 pad->new_page = FALSE;
1172 /* save key unit to track delta->key unit transitions */
1173 pad->prev_delta = delta_unit;
1175 /* swap the packet in */
1176 if (packet.e_o_s == 1)
1177 GST_DEBUG_OBJECT (pad, "swapping in EOS packet");
1178 if (packet.b_o_s == 1)
1179 GST_DEBUG_OBJECT (pad, "swapping in BOS packet");
1181 ogg_stream_packetin (&pad->stream, &packet);
1183 granulepos = GST_BUFFER_OFFSET_END (pad->buffer);
1184 timestamp = GST_BUFFER_TIMESTAMP (pad->buffer);
1186 /* don't need the old buffer anymore */
1187 gst_buffer_unref (pad->buffer);
1188 /* store new readahead buffer */
1189 pad->buffer = tmpbuf;
1191 /* let ogg write out the pages now. The packet we got could end
1192 * up in more than one page so we need to write them all */
1193 if (ogg_stream_pageout (&pad->stream, &page) > 0) {
1194 if (ogg_page_granulepos (&page) == granulepos) {
1195 /* the packet we streamed in finishes on the page,
1196 * because the page's granulepos is the granulepos of the last
1197 * packet completed on that page,
1198 * so update the timestamp that we will give to the page */
1199 pad->timestamp = timestamp;
1200 GST_DEBUG_OBJECT (pad, "Timestamp of pad is %" GST_TIME_FORMAT
1201 ", granulepos is %lld", GST_TIME_ARGS (timestamp), granulepos);
1205 ret = gst_ogg_mux_pad_queue_page (ogg_mux, pad, &page, pad->first_delta);
1207 /* mark next pages as delta */
1208 pad->first_delta = TRUE;
1210 /* use an inner loop here to flush the remaining pages and
1211 * mark them as delta frames as well */
1212 while (ogg_stream_pageout (&pad->stream, &page) > 0) {
1213 if (ogg_page_granulepos (&page) == granulepos) {
1214 /* the page has taken up the new packet completely, which means
1215 * the packet ends the page and we can update the timestamp
1216 * before pushing out */
1217 pad->timestamp = timestamp;
1220 /* we have a complete page now, we can push the page
1221 * and make sure to pull on a new pad the next time around */
1222 ret = gst_ogg_mux_pad_queue_page (ogg_mux, pad, &page,
1224 /* increment the page number counter */
1227 /* need a new page as well */
1228 pad->new_page = TRUE;
1230 /* we're done pulling on this pad, make sure to choose a new
1231 * pad for pulling in the next iteration */
1232 ogg_mux->pulling = NULL;
1235 /* Update the timestamp, if neccesary, since and future page will have at
1236 * least this timestamp.
1238 if (pad->timestamp < timestamp) {
1239 pad->timestamp = timestamp;
1240 GST_DEBUG_OBJECT (pad, "Updated timestamp of pad to %" GST_TIME_FORMAT,
1241 GST_TIME_ARGS (timestamp));
1249 gst_ogg_mux_get_property (GObject * object,
1250 guint prop_id, GValue * value, GParamSpec * pspec)
1254 ogg_mux = GST_OGG_MUX (object);
1258 g_value_set_uint64 (value, ogg_mux->max_delay);
1260 case ARG_MAX_PAGE_DELAY:
1261 g_value_set_uint64 (value, ogg_mux->max_page_delay);
1264 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1270 gst_ogg_mux_set_property (GObject * object,
1271 guint prop_id, const GValue * value, GParamSpec * pspec)
1275 ogg_mux = GST_OGG_MUX (object);
1279 ogg_mux->max_delay = g_value_get_uint64 (value);
1281 case ARG_MAX_PAGE_DELAY:
1282 ogg_mux->max_page_delay = g_value_get_uint64 (value);
1285 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1291 gst_ogg_mux_clear_collectpads (GstCollectPads * collect)
1295 walk = collect->data;
1297 GstOggPad *pad = (GstOggPad *) walk->data;
1300 ogg_stream_clear (&pad->stream);
1302 while ((buf = g_queue_pop_head (pad->pagebuffers)) != NULL) {
1303 gst_buffer_unref (buf);
1305 g_queue_free (pad->pagebuffers);
1307 gst_collect_pads_remove_pad (collect, ((GstCollectData *) pad)->pad);
1308 walk = collect->data;
1312 static GstStateChangeReturn
1313 gst_ogg_mux_change_state (GstElement * element, GstStateChange transition)
1316 GstStateChangeReturn ret;
1318 ogg_mux = GST_OGG_MUX (element);
1320 switch (transition) {
1321 case GST_STATE_CHANGE_NULL_TO_READY:
1323 case GST_STATE_CHANGE_READY_TO_PAUSED:
1324 ogg_mux->next_ts = 0;
1325 ogg_mux->offset = 0;
1326 ogg_mux->pulling = NULL;
1327 gst_collect_pads_start (ogg_mux->collect);
1328 gst_ogg_mux_clear (ogg_mux);
1330 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1332 case GST_STATE_CHANGE_PAUSED_TO_READY:
1333 gst_collect_pads_stop (ogg_mux->collect);
1339 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1341 switch (transition) {
1342 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
1344 case GST_STATE_CHANGE_READY_TO_NULL:
1345 gst_ogg_mux_clear_collectpads (ogg_mux->collect);
1355 gst_ogg_mux_plugin_init (GstPlugin * plugin)
1357 GST_DEBUG_CATEGORY_INIT (gst_ogg_mux_debug, "oggmux", 0, "ogg muxer");
1359 return gst_element_register (plugin, "oggmux", GST_RANK_NONE,