From c9bc7a4844d9bdee7c63794c83153a9535e1688b Mon Sep 17 00:00:00 2001 From: Thomas Vander Stichele Date: Sun, 5 Mar 2006 22:57:58 +0000 Subject: [PATCH] ogg muxing of vorbis and theora now has pages ordered correctly again, even with delays. Original commit message from CVS: ogg muxing of vorbis and theora now has pages ordered correctly again, even with delays. * ext/ogg/README: updated with some examples * ext/theora/theoraenc.c: (granulepos_to_timestamp), (granulepos_add), (theora_buffer_from_packet): * ext/vorbis/vorbisenc.c: (granulepos_to_timestamp_offset), (granulepos_to_timestamp), (gst_vorbisenc_buffer_from_packet), (gst_vorbisenc_chain): implement strategy from ext/ogg/README * ext/ogg/gstoggmux.c: (gst_ogg_mux_buffer_from_page), (gst_ogg_mux_push_buffer), (gst_ogg_mux_dequeue_page), (gst_ogg_mux_pad_queue_page), (gst_ogg_mux_compare_pads), (gst_ogg_mux_queue_pads), (gst_ogg_mux_collected): Fix muxer so that oggz-validate is happy with all streams; except for no eos mark, and the BOS page ordering * tests/check/pipelines/theoraenc.c: (check_buffer_is_header), (check_buffer_granulepos): * tests/check/pipelines/vorbisenc.c: (check_buffer_granulepos): update tests to check for OFFSET being set as requested fixed type of granulepos, it's not a ClockTime --- ChangeLog | 22 +++ ext/ogg/README | 23 ++++ ext/ogg/gstoggmux.c | 214 +++++++++++++++++------------- ext/theora/theoraenc.c | 27 +++- ext/vorbis/vorbisenc.c | 32 ++++- tests/check/pipelines/theoraenc.c | 22 ++- tests/check/pipelines/vorbisenc.c | 22 ++- 7 files changed, 256 insertions(+), 106 deletions(-) diff --git a/ChangeLog b/ChangeLog index 88190f46c9..bf6f53fc2f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,25 @@ +2006-03-05 Thomas Vander Stichele + + * ext/ogg/README: + updated with some examples + * ext/theora/theoraenc.c: (granulepos_to_timestamp), + (granulepos_add), (theora_buffer_from_packet): + * ext/vorbis/vorbisenc.c: (granulepos_to_timestamp_offset), + (granulepos_to_timestamp), (gst_vorbisenc_buffer_from_packet), + (gst_vorbisenc_chain): + implement strategy from ext/ogg/README + * ext/ogg/gstoggmux.c: (gst_ogg_mux_buffer_from_page), + (gst_ogg_mux_push_buffer), (gst_ogg_mux_dequeue_page), + (gst_ogg_mux_pad_queue_page), (gst_ogg_mux_compare_pads), + (gst_ogg_mux_queue_pads), (gst_ogg_mux_collected): + Fix muxer so that oggz-validate is happy with all streams; + except for no eos mark, and the BOS page ordering + * tests/check/pipelines/theoraenc.c: (check_buffer_is_header), + (check_buffer_granulepos): + * tests/check/pipelines/vorbisenc.c: (check_buffer_granulepos): + update tests to check for OFFSET being set as requested + fixed type of granulepos, it's not a ClockTime + 2006-03-05 Julien MOUTTE * sys/xvimage/xvimagesink.c: (gst_xvimagesink_xvimage_new), diff --git a/ext/ogg/README b/ext/ogg/README index 79f71e2a77..ed783c0ee6 100644 --- a/ext/ogg/README +++ b/ext/ogg/README @@ -341,4 +341,27 @@ In practice TODO ---- - decide on a proper mechanism for communicating extra per-buffer fields +- the ogg muxer sets timestamp and duration on outgoing ogg pages based on + timestamp/duration of incoming ogg packets. + Note that: + - since the ogg muxer *has* to output pages sorted by gp time, representing + end time of the page, this means that the buffer's timestamps are not + necessarily monotonically increasing + - timestamp + duration of buffers don't match up; the duration represents + the length of the ogg page *for that stream*. Hence, for a normal + two-stream file, the sum of all durations is twice the length of the + muxed file. +TESTING +------- +Proper muxing can be tested by generating test files with command lines like: +- video and audio start from 0: +gst-launch -v videotestsrc ! theoraenc ! oggmux audiotestsrc ! audioconvert ! vorbisenc ! identity ! oggmux0. oggmux0. ! filesink location=test.ogg + +- video starts after audio: +gst-launch -v videotestsrc timestamp-offset=500000000 ! theoraenc ! oggmux audiotestsrc ! audioconvert ! vorbisenc ! identity ! oggmux0. oggmux0. ! filesink location=test.ogg + +- audio starts after video: +gst-launch -v videotestsrc ! theoraenc ! oggmux audiotestsrc timestamp-offset=500000000 ! audioconvert ! vorbisenc ! identity ! oggmux0. oggmux0. ! filesink location=test.ogg + +The resulting files can be verified with oggz-validate for correctness. diff --git a/ext/ogg/gstoggmux.c b/ext/ogg/gstoggmux.c index f3fced896f..37d9331a9e 100644 --- a/ext/ogg/gstoggmux.c +++ b/ext/ogg/gstoggmux.c @@ -73,10 +73,13 @@ typedef struct guint64 duration; /* duration of current page */ gboolean eos; gint64 offset; - GstClockTime timestamp; /* start timestamp of last complete packet on - this page */ - GstClockTime timestamp_end; /* end timestamp of last complete packet on this - page == granulepos time. */ + GstClockTime timestamp; /* timestamp of the first packet on the next + * page to be dequeued */ + GstClockTime timestamp_end; /* end timestamp of last complete packet on + the next page to be dequeued */ + GstClockTime gp_time; /* time corresponding to the gp value of the + last complete packet on the next page to be + dequeued */ GstOggPadState state; /* state of the pad */ @@ -489,10 +492,6 @@ gst_ogg_mux_buffer_from_page (GstOggMux * mux, ogg_page * page, gboolean delta) memcpy (GST_BUFFER_DATA (buffer) + page->header_len, page->body, page->body_len); - /* next_ts was the timestamp of the first buffer put in this page */ - GST_BUFFER_TIMESTAMP (buffer) = mux->next_ts; - GST_BUFFER_OFFSET (buffer) = mux->offset; - mux->offset += GST_BUFFER_SIZE (buffer); /* Here we set granulepos as our OFFSET_END to give easy direct access to * this value later. Before we push it, we reset this to OFFSET + SIZE * (see gst_ogg_mux_push_buffer). */ @@ -500,14 +499,19 @@ gst_ogg_mux_buffer_from_page (GstOggMux * mux, ogg_page * page, gboolean delta) if (delta) GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT); + GST_LOG_OBJECT (mux, GST_GP_FORMAT + " created buffer %p from ogg page", ogg_page_granulepos (page)); + return buffer; } static GstFlowReturn gst_ogg_mux_push_buffer (GstOggMux * mux, GstBuffer * buffer) { - GST_BUFFER_OFFSET_END (buffer) = GST_BUFFER_OFFSET (buffer) + - GST_BUFFER_SIZE (buffer); + /* fix up OFFSET and OFFSET_END again */ + GST_BUFFER_OFFSET (buffer) = mux->offset; + mux->offset += GST_BUFFER_SIZE (buffer); + GST_BUFFER_OFFSET_END (buffer) = mux->offset; return gst_pad_push (mux->srcpad, buffer); } @@ -584,18 +588,18 @@ gst_ogg_mux_dequeue_page (GstOggMux * mux, GstFlowReturn * flowret) if (buf) { /* if no oldest buffer yet, take this one */ if (oldest == GST_CLOCK_TIME_NONE) { - GST_LOG_OBJECT (mux, "no oldest yet, taking from pad %" - GST_PTR_FORMAT " with timestamp %" GST_TIME_FORMAT, - pad->collect.pad, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf))); - oldest = GST_BUFFER_END_TIME (buf); + GST_LOG_OBJECT (mux, "no oldest yet, taking buffer %p from pad %" + GST_PTR_FORMAT " with gp time %" GST_TIME_FORMAT, + buf, pad->collect.pad, GST_TIME_ARGS (GST_BUFFER_OFFSET (buf))); + oldest = GST_BUFFER_OFFSET (buf); opad = pad; } else { /* if we have an oldest, compare with this one */ - if (GST_BUFFER_END_TIME (buf) < oldest) { - GST_LOG_OBJECT (mux, "older buffer, taking from pad %" - GST_PTR_FORMAT " with timestamp %" GST_TIME_FORMAT, - pad->collect.pad, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf))); - oldest = GST_BUFFER_END_TIME (buf); + if (GST_BUFFER_OFFSET (buf) < oldest) { + GST_LOG_OBJECT (mux, "older buffer %p, taking from pad %" + GST_PTR_FORMAT " with gp time %" GST_TIME_FORMAT, + buf, pad->collect.pad, GST_TIME_ARGS (GST_BUFFER_OFFSET (buf))); + oldest = GST_BUFFER_OFFSET (buf); opad = pad; } } @@ -607,8 +611,9 @@ gst_ogg_mux_dequeue_page (GstOggMux * mux, GstFlowReturn * flowret) g_assert (opad); buf = g_queue_pop_head (opad->pagebuffers); GST_LOG_OBJECT (opad->collect.pad, - GST_GP_FORMAT " pushing oldest page (end time %" GST_TIME_FORMAT ")", - GST_BUFFER_OFFSET_END (buf), GST_TIME_ARGS (GST_BUFFER_END_TIME (buf))); + GST_GP_FORMAT " pushing oldest page buffer %p (granulepos time %" + GST_TIME_FORMAT ")", GST_BUFFER_OFFSET_END (buf), buf, + GST_TIME_ARGS (GST_BUFFER_OFFSET (buf))); *flowret = gst_ogg_mux_push_buffer (mux, buf); ret = TRUE; } @@ -616,26 +621,41 @@ gst_ogg_mux_dequeue_page (GstOggMux * mux, GstFlowReturn * flowret) return ret; } -/* put the given page on a per-pad queue, timestamping it correctly. - * after that, dequeue and push as many pages as possible - * before calling me, make sure that the the pad's timestamp matches - * the page's granulepos */ +/* put the given ogg page on a per-pad queue, timestamping it correctly. + * after that, dequeue and push as many pages as possible. + * Caller should make sure: + * pad->timestamp was set with the timestamp of the first packet put + * on the page + * pad->timestamp_end was set with the timestamp + duration of the last packet + * put on the page + * pad->gp_time was set with the time matching the gp of the last + * packet put on the page + * + * will also reset timestamp and timestamp_end, so caller func can restart + * counting. + */ static GstFlowReturn gst_ogg_mux_pad_queue_page (GstOggMux * mux, GstOggPad * pad, ogg_page * page, gboolean delta) { - GstBuffer *buffer = gst_ogg_mux_buffer_from_page (mux, page, delta); GstFlowReturn ret; + GstBuffer *buffer = gst_ogg_mux_buffer_from_page (mux, page, delta); - /* take the timestamp of the last completed packet on this page */ + /* take the timestamp of the first packet on this page */ GST_BUFFER_TIMESTAMP (buffer) = pad->timestamp; GST_BUFFER_DURATION (buffer) = pad->timestamp_end - pad->timestamp; + /* take the gp time of the last completed packet on this page */ + GST_BUFFER_OFFSET (buffer) = pad->gp_time; + /* the next page will start where the current page's end time leaves off */ pad->timestamp = pad->timestamp_end; g_queue_push_tail (pad->pagebuffers, buffer); - GST_LOG_OBJECT (pad->collect.pad, GST_GP_FORMAT " queued buffer page (time %" - GST_TIME_FORMAT "), %d page buffers queued", ogg_page_granulepos (page), + GST_LOG_OBJECT (pad->collect.pad, GST_GP_FORMAT + " queued buffer page %p (gp time %" + GST_TIME_FORMAT ", timestamp %" GST_TIME_FORMAT + "), %d page buffers queued", ogg_page_granulepos (page), + buffer, GST_TIME_ARGS (GST_BUFFER_OFFSET (buffer)), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)), g_queue_get_length (pad->pagebuffers)); @@ -648,43 +668,49 @@ gst_ogg_mux_pad_queue_page (GstOggMux * mux, GstOggPad * pad, ogg_page * page, } /* - * Given two pads, compare the buffers queued on it and return 0 if they have - * an equal priority, 1 if the new pad is better, -1 if the old pad is better + * Given two pads, compare the buffers queued on it. + * Returns: + * 0 if they have an equal priority + * -1 if the first is better + * 1 if the second is better + * Priority decided by: a) validity, b) older timestamp, c) smaller number + * of muxed pages */ static gint -gst_ogg_mux_compare_pads (GstOggMux * ogg_mux, GstOggPad * old, GstOggPad * new) +gst_ogg_mux_compare_pads (GstOggMux * ogg_mux, GstOggPad * first, + GstOggPad * second) { - guint64 oldtime, newtime; + guint64 firsttime, secondtime; - /* if the old pad doesn't contain anything or is even NULL, return - * the new pad as best candidate and vice versa */ - if (old == NULL || old->buffer == NULL) + /* if the first pad doesn't contain anything or is even NULL, return + * the second pad as best candidate and vice versa */ + if (first == NULL || first->buffer == NULL) return 1; - if (new == NULL || new->buffer == NULL) + if (second == NULL || second->buffer == NULL) return -1; - /* no timestamp on old buffer, it must go first */ - oldtime = GST_BUFFER_TIMESTAMP (old->buffer); - if (oldtime == GST_CLOCK_TIME_NONE) + /* no timestamp on first buffer, it must go first */ + firsttime = GST_BUFFER_TIMESTAMP (first->buffer); + if (firsttime == GST_CLOCK_TIME_NONE) return -1; - /* no timestamp on new buffer, it must go first */ - newtime = GST_BUFFER_TIMESTAMP (new->buffer); - if (newtime == GST_CLOCK_TIME_NONE) + /* no timestamp on second buffer, it must go first */ + secondtime = GST_BUFFER_TIMESTAMP (second->buffer); + if (secondtime == GST_CLOCK_TIME_NONE) return 1; - /* old buffer has higher timestamp, new one should go first */ - if (newtime < oldtime) + /* first buffer has higher timestamp, second one should go first */ + if (secondtime < firsttime) return 1; - /* new buffer has higher timestamp, old one should go first */ - else if (newtime > oldtime) + /* second buffer has higher timestamp, first one should go first */ + else if (secondtime > firsttime) return -1; else { /* buffers with equal timestamps, prefer the pad that has the * least number of pages muxed */ - if (new->pageno < old->pageno) + if (second->pageno < first->pageno) return 1; - else if (new->pageno > old->pageno) + else if (second->pageno > first->pageno) return -1; } @@ -712,8 +738,7 @@ gst_ogg_mux_queue_pads (GstOggMux * ogg_mux) walk = g_slist_next (walk); - GST_LOG_OBJECT (ogg_mux, "looking at pad %" GST_PTR_FORMAT " (oggpad %p)", - data->pad, pad); + GST_LOG_OBJECT (data->pad, "looking at pad for buffer"); /* try to get a new buffer for this pad if needed and possible */ if (pad->buffer == NULL) { @@ -721,7 +746,7 @@ gst_ogg_mux_queue_pads (GstOggMux * ogg_mux) gboolean incaps; buf = gst_collect_pads_pop (ogg_mux->collect, data); - GST_LOG_OBJECT (ogg_mux, "popping buffer %" GST_PTR_FORMAT, buf); + GST_LOG_OBJECT (data->pad, "popped buffer %" GST_PTR_FORMAT, buf); /* On EOS we get a NULL buffer */ if (buf != NULL) { @@ -743,7 +768,7 @@ gst_ogg_mux_queue_pads (GstOggMux * ogg_mux) } } } else { - GST_DEBUG_OBJECT (ogg_mux, "EOS on pad"); + GST_DEBUG_OBJECT (data->pad, "EOS on pad"); pad->eos = TRUE; } @@ -754,14 +779,12 @@ gst_ogg_mux_queue_pads (GstOggMux * ogg_mux) * pull on */ if (pad->buffer) { if (gst_ogg_mux_compare_pads (ogg_mux, bestpad, pad) > 0) { - GST_LOG_OBJECT (ogg_mux, "best pad now %" GST_PTR_FORMAT - " (oggpad %p)", data->pad, pad); + GST_LOG_OBJECT (data->pad, "new best pad"); bestpad = pad; } } else if (!pad->eos) { - GST_LOG_OBJECT (ogg_mux, "hungry pad %" GST_PTR_FORMAT - " (oggpad %p)", data->pad, pad); + GST_LOG_OBJECT (data->pad, "hungry pad"); still_hungry = pad; } } @@ -1079,7 +1102,7 @@ gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux) gboolean delta_unit; GstFlowReturn ret; gint64 granulepos = 0; - GstClockTime timestamp, timestamp_end; + GstClockTime timestamp, gp_time; GST_LOG_OBJECT (ogg_mux, "collected"); @@ -1098,8 +1121,9 @@ gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux) return GST_FLOW_WRONG_STATE; } - GST_LOG_OBJECT (ogg_mux, "best pad %" GST_PTR_FORMAT " (oggpad %p)" - " pulling %" GST_PTR_FORMAT, best->collect.pad, best, ogg_mux->pulling); + GST_LOG_OBJECT (ogg_mux, "best pad %" GST_PTR_FORMAT + ", currently pulling from %" GST_PTR_FORMAT, best->collect.pad, + ogg_mux->pulling); /* if we were already pulling from one pad, but the new "best" buffer is * from another pad, we need to check if we have reason to flush a page @@ -1121,6 +1145,8 @@ gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux) GST_BUFFER_OFFSET_END (pad->buffer), pad->stream.packetno); while (ogg_stream_flush (&pad->stream, &page)) { + /* end time of this page is the timestamp of the next buffer */ + ogg_mux->pulling->timestamp_end = GST_BUFFER_TIMESTAMP (pad->buffer); /* Place page into the per-pad queue */ ret = gst_ogg_mux_pad_queue_page (ogg_mux, pad, &page, pad->first_delta); @@ -1137,12 +1163,13 @@ gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux) /* if we don't know which pad to pull on, use the best one */ if (ogg_mux->pulling == NULL) { ogg_mux->pulling = best; - GST_LOG_OBJECT (ogg_mux, "pulling now %" GST_PTR_FORMAT " (oggpad %p)", - ogg_mux->pulling->collect.pad, ogg_mux->pulling); + GST_LOG_OBJECT (ogg_mux->pulling->collect.pad, "pulling from best pad"); - /* remember timestamp of first buffer for this new pad */ + /* remember timestamp and gp time of first buffer for this new pad */ if (ogg_mux->pulling != NULL) { ogg_mux->next_ts = GST_BUFFER_TIMESTAMP (ogg_mux->pulling->buffer); + GST_LOG_OBJECT (ogg_mux->pulling->collect.pad, "updated times, next ts %" + GST_TIME_FORMAT, GST_TIME_ARGS (ogg_mux->next_ts)); } else { /* no pad to pull on, send EOS */ gst_pad_push_event (ogg_mux->srcpad, gst_event_new_eos ()); @@ -1165,8 +1192,7 @@ gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux) gint64 duration; gboolean force_flush; - GST_LOG_OBJECT (ogg_mux, "pulling now %" GST_PTR_FORMAT " (oggpad %p)", - ogg_mux->pulling->collect.pad, ogg_mux->pulling); + GST_LOG_OBJECT (ogg_mux->pulling->collect.pad, "pulling from pad"); /* now see if we have a buffer */ buf = pad->buffer; @@ -1179,6 +1205,14 @@ gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux) delta_unit = GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT); duration = GST_BUFFER_DURATION (buf); + /* if the current "next timestamp" on the pad is unset, then this is the + * first packet on the new page. Update our pad's page timestamp */ + if (ogg_mux->pulling->timestamp == GST_CLOCK_TIME_NONE) { + ogg_mux->pulling->timestamp = GST_BUFFER_TIMESTAMP (buf); + GST_LOG_OBJECT (ogg_mux->pulling->collect.pad, + "updated pad timestamp to %" GST_TIME_FORMAT, + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf))); + } /* create a packet from the buffer */ packet.packet = GST_BUFFER_DATA (buf); packet.bytes = GST_BUFFER_SIZE (buf); @@ -1206,12 +1240,14 @@ gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux) } } - /* flush the currently built page if neccesary */ + /* flush the currently built page if necessary */ if (force_flush) { GST_LOG_OBJECT (pad->collect.pad, - GST_GP_FORMAT " forcing flush because of keyframe", + GST_GP_FORMAT " forced flush of page before this packet", GST_BUFFER_OFFSET_END (pad->buffer)); while (ogg_stream_flush (&pad->stream, &page)) { + /* end time of this page is the timestamp of the next buffer */ + ogg_mux->pulling->timestamp_end = GST_BUFFER_TIMESTAMP (pad->buffer); ret = gst_ogg_mux_pad_queue_page (ogg_mux, pad, &page, pad->first_delta); @@ -1261,14 +1297,15 @@ gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux) ogg_stream_packetin (&pad->stream, &packet); + gp_time = GST_BUFFER_OFFSET (pad->buffer); granulepos = GST_BUFFER_OFFSET_END (pad->buffer); timestamp = GST_BUFFER_TIMESTAMP (pad->buffer); - timestamp_end = GST_BUFFER_END_TIME (pad->buffer); GST_LOG_OBJECT (pad->collect.pad, - GST_GP_FORMAT " packet %" G_GINT64_FORMAT ", time %" - GST_TIME_FORMAT ") packetin'd", - granulepos, packet.packetno, GST_TIME_ARGS (timestamp)); + GST_GP_FORMAT " packet %" G_GINT64_FORMAT ", gp time %" + GST_TIME_FORMAT ", timestamp %" GST_TIME_FORMAT " packetin'd", + granulepos, packet.packetno, GST_TIME_ARGS (gp_time), + GST_TIME_ARGS (timestamp)); /* don't need the old buffer anymore */ gst_buffer_unref (pad->buffer); /* store new readahead buffer */ @@ -1290,24 +1327,25 @@ gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux) pad->stream.pageno); if (ogg_page_granulepos (&page) == granulepos) { - /* the packet we streamed in finishes on the page, + /* the packet we streamed in finishes on the current page, * because the page's granulepos is the granulepos of the last * packet completed on that page, * so update the timestamp that we will give to the page */ GST_LOG_OBJECT (pad->collect.pad, GST_GP_FORMAT - " packet finishes on new page, updating timestamp to %" - GST_TIME_FORMAT, granulepos, GST_TIME_ARGS (timestamp)); - pad->timestamp = timestamp; - pad->timestamp_end = timestamp_end; + " packet finishes on current page, updating gp time to %" + GST_TIME_FORMAT, granulepos, GST_TIME_ARGS (gp_time)); + pad->gp_time = gp_time; } else { GST_LOG_OBJECT (pad->collect.pad, GST_GP_FORMAT - " packet spans across new page, keeping old timestamp %" - GST_TIME_FORMAT, granulepos, GST_TIME_ARGS (pad->timestamp)); + " packet spans beyond current page, keeping old gp time %" + GST_TIME_FORMAT, granulepos, GST_TIME_ARGS (pad->gp_time)); } /* push the page */ + /* end time of this page is the timestamp of the next buffer */ + pad->timestamp_end = timestamp; ret = gst_ogg_mux_pad_queue_page (ogg_mux, pad, &page, pad->first_delta); pad->pageno++; /* mark next pages as delta */ @@ -1318,10 +1356,9 @@ gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux) while (ogg_stream_pageout (&pad->stream, &page) > 0) { if (ogg_page_granulepos (&page) == granulepos) { /* the page has taken up the new packet completely, which means - * the packet ends the page and we can update the timestamp + * the packet ends the page and we can update the gp time * before pushing out */ - pad->timestamp = timestamp; - pad->timestamp_end = timestamp_end; + pad->gp_time = gp_time; } /* we have a complete page now, we can push the page @@ -1339,15 +1376,14 @@ gst_ogg_mux_collected (GstCollectPads * pads, GstOggMux * ogg_mux) ogg_mux->pulling = NULL; } - /* Update the timestamp, if necessary, since any future page will have at - * least this timestamp. + /* Update the gp time, if necessary, since any future page will have at + * least this gp time. */ - if (pad->timestamp < timestamp_end) { - pad->timestamp = timestamp_end; - pad->timestamp_end = timestamp_end; - GST_LOG_OBJECT (ogg_mux, "Updated timestamp of pad %" GST_PTR_FORMAT - " (oggpad %p) to %" GST_TIME_FORMAT, pad->collect.pad, pad, - GST_TIME_ARGS (timestamp_end)); + if (pad->gp_time < gp_time) { + pad->gp_time = gp_time; + GST_LOG_OBJECT (pad->collect.pad, + "Updated running gp time of pad %" GST_PTR_FORMAT + " to %" GST_TIME_FORMAT, pad->collect.pad, GST_TIME_ARGS (gp_time)); } } diff --git a/ext/theora/theoraenc.c b/ext/theora/theoraenc.c index b7c4c1e740..a54a02b658 100644 --- a/ext/theora/theoraenc.c +++ b/ext/theora/theoraenc.c @@ -130,6 +130,25 @@ enum /* FILL ME */ }; +/* this function does a straight granulepos -> timestamp conversion */ +static GstClockTime +granulepos_to_timestamp (GstTheoraEnc * theoraenc, ogg_int64_t granulepos) +{ + guint64 iframe, pframe; + int shift = theoraenc->granule_shift; + + if (granulepos < 0) + return GST_CLOCK_TIME_NONE; + + iframe = granulepos >> shift; + pframe = granulepos - (iframe << shift); + + /* num and den are 32 bit, so we can safely multiply with GST_SECOND */ + return gst_util_uint64_scale ((guint64) (iframe + pframe), + GST_SECOND * theoraenc->info.fps_denominator, + theoraenc->info.fps_numerator); +} + static GstElementDetails theora_enc_details = { "TheoraEnc", "Codec/Encoder/Video", @@ -365,7 +384,7 @@ theora_enc_sink_setcaps (GstPad * pad, GstCaps * caps) static guint64 granulepos_add (guint64 granulepos, guint64 addend, gint shift) { - GstClockTime iframe, pframe; + guint64 iframe, pframe; iframe = granulepos >> shift; pframe = granulepos - (iframe << shift); @@ -388,10 +407,14 @@ theora_buffer_from_packet (GstTheoraEnc * enc, ogg_packet * packet, goto no_buffer; memcpy (GST_BUFFER_DATA (buf), packet->packet, packet->bytes); - GST_BUFFER_OFFSET (buf) = enc->bytes_out; + /* see ext/ogg/README; OFFSET_END takes "our" granulepos, OFFSET its + * time representation */ GST_BUFFER_OFFSET_END (buf) = granulepos_add (packet->granulepos, enc->granulepos_offset, enc->granule_shift); + GST_BUFFER_OFFSET (buf) = granulepos_to_timestamp (enc, + GST_BUFFER_OFFSET_END (buf)); + GST_BUFFER_TIMESTAMP (buf) = timestamp + enc->timestamp_offset; GST_BUFFER_DURATION (buf) = duration; diff --git a/ext/vorbis/vorbisenc.c b/ext/vorbis/vorbisenc.c index 4def90866a..f02439203f 100644 --- a/ext/vorbis/vorbisenc.c +++ b/ext/vorbis/vorbisenc.c @@ -93,8 +93,11 @@ enum static GstFlowReturn gst_vorbisenc_output_buffers (GstVorbisEnc * vorbisenc); +/* this function takes into account the granulepos_offset and the subgranule + * time offset */ static GstClockTime -granulepos_to_clocktime (GstVorbisEnc * vorbisenc, ogg_int64_t granulepos) +granulepos_to_timestamp_offset (GstVorbisEnc * vorbisenc, + ogg_int64_t granulepos) { if (granulepos >= 0) return gst_util_uint64_scale ((guint64) granulepos @@ -103,6 +106,16 @@ granulepos_to_clocktime (GstVorbisEnc * vorbisenc, ogg_int64_t granulepos) return GST_CLOCK_TIME_NONE; } +/* this function does a straight granulepos -> timestamp conversion */ +static GstClockTime +granulepos_to_timestamp (GstVorbisEnc * vorbisenc, ogg_int64_t granulepos) +{ + if (granulepos >= 0) + return gst_util_uint64_scale ((guint64) granulepos, + GST_SECOND, vorbisenc->frequency); + return GST_CLOCK_TIME_NONE; +} + #if 0 static const GstFormat * gst_vorbisenc_get_formats (GstPad * pad) @@ -807,18 +820,23 @@ gst_vorbisenc_buffer_from_packet (GstVorbisEnc * vorbisenc, ogg_packet * packet) outbuf = gst_buffer_new_and_alloc (packet->bytes); memcpy (GST_BUFFER_DATA (outbuf), packet->packet, packet->bytes); - GST_BUFFER_OFFSET (outbuf) = vorbisenc->bytes_out; + /* see ext/ogg/README; OFFSET_END takes "our" granulepos, OFFSET its + * time representation */ GST_BUFFER_OFFSET_END (outbuf) = packet->granulepos + vorbisenc->granulepos_offset; + GST_BUFFER_OFFSET (outbuf) = granulepos_to_timestamp (vorbisenc, + GST_BUFFER_OFFSET_END (outbuf)); GST_BUFFER_TIMESTAMP (outbuf) = vorbisenc->next_ts; - /* need to pass in an unadjusted granulepos here */ - vorbisenc->next_ts = granulepos_to_clocktime (vorbisenc, packet->granulepos); - + /* update the next timestamp, taking granulepos_offset and subgranule offset + * into account */ + vorbisenc->next_ts = + granulepos_to_timestamp_offset (vorbisenc, packet->granulepos); GST_BUFFER_DURATION (outbuf) = vorbisenc->next_ts - GST_BUFFER_TIMESTAMP (outbuf); - GST_DEBUG ("encoded buffer of %d bytes", GST_BUFFER_SIZE (outbuf)); + GST_LOG_OBJECT (vorbisenc, "encoded buffer of %d bytes", + GST_BUFFER_SIZE (outbuf)); return outbuf; } @@ -1004,7 +1022,7 @@ gst_vorbisenc_chain (GstPad * pad, GstBuffer * buffer) (GST_BUFFER_TIMESTAMP (buffer), vorbisenc->frequency, GST_SECOND); vorbisenc->subgranule_offset = 0; vorbisenc->subgranule_offset = - vorbisenc->next_ts - granulepos_to_clocktime (vorbisenc, 0); + vorbisenc->next_ts - granulepos_to_timestamp_offset (vorbisenc, 0); vorbisenc->header_sent = TRUE; } diff --git a/tests/check/pipelines/theoraenc.c b/tests/check/pipelines/theoraenc.c index c69eda3455..f59f009547 100644 --- a/tests/check/pipelines/theoraenc.c +++ b/tests/check/pipelines/theoraenc.c @@ -154,12 +154,24 @@ check_buffer_duration (GstBuffer * buffer, GstClockTime duration) } static void -check_buffer_granulepos (GstBuffer * buffer, GstClockTime granulepos) +check_buffer_granulepos (GstBuffer * buffer, gint64 granulepos) { + GstClockTime clocktime; + fail_unless (GST_BUFFER_OFFSET_END (buffer) == granulepos, "expected granulepos %" G_GUINT64_FORMAT ", but got granulepos %" G_GUINT64_FORMAT, granulepos, GST_BUFFER_OFFSET_END (buffer)); + + /* contrary to what we record as TIMESTAMP, we can use OFFSET to check + * the granulepos correctly here */ + clocktime = gst_util_uint64_scale (GST_BUFFER_OFFSET_END (buffer), GST_SECOND, + FRAMERATE); + + fail_unless (clocktime == GST_BUFFER_OFFSET (buffer), + "expected OFFSET set to clocktime %" GST_TIME_FORMAT + ", but got %" GST_TIME_FORMAT, + GST_TIME_ARGS (clocktime), GST_TIME_ARGS (GST_BUFFER_OFFSET (buffer))); } /* this check is here to check that the granulepos we derive from the @@ -172,7 +184,7 @@ static void check_buffer_granulepos_from_starttime (GstBuffer * buffer, GstClockTime starttime) { - GstClockTime granulepos, expected, framecount; + gint64 granulepos, expected, framecount; granulepos = GST_BUFFER_OFFSET_END (buffer); framecount = granulepos >> GRANULEPOS_SHIFT; @@ -239,7 +251,8 @@ GST_START_TEST (test_granulepos_offset) gst_buffer_unref (buffer); { - GstClockTime next_timestamp, last_granulepos; + GstClockTime next_timestamp; + gint64 last_granulepos; /* first buffer should have timestamp of TIMESTAMP_OFFSET, granulepos to * match the timestamp of the end of the last sample in the output buffer. @@ -332,7 +345,8 @@ GST_START_TEST (test_continuity) gst_buffer_unref (buffer); { - GstClockTime next_timestamp, last_granulepos; + GstClockTime next_timestamp; + gint64 last_granulepos; /* first buffer should have timestamp of TIMESTAMP_OFFSET, granulepos to * match the timestamp of the end of the last sample in the output buffer. diff --git a/tests/check/pipelines/vorbisenc.c b/tests/check/pipelines/vorbisenc.c index 6d4e041f2e..5914447140 100644 --- a/tests/check/pipelines/vorbisenc.c +++ b/tests/check/pipelines/vorbisenc.c @@ -124,12 +124,24 @@ check_buffer_duration (GstBuffer * buffer, GstClockTime duration) } static void -check_buffer_granulepos (GstBuffer * buffer, GstClockTime granulepos) +check_buffer_granulepos (GstBuffer * buffer, gint64 granulepos) { + GstClockTime clocktime; + fail_unless (GST_BUFFER_OFFSET_END (buffer) == granulepos, "expected granulepos %" G_GUINT64_FORMAT ", but got granulepos %" G_GUINT64_FORMAT, granulepos, GST_BUFFER_OFFSET_END (buffer)); + + /* contrary to what we record as TIMESTAMP, we can use OFFSET to check + * the granulepos correctly here */ + clocktime = gst_util_uint64_scale (GST_BUFFER_OFFSET_END (buffer), 44100, + GST_SECOND); + + fail_unless (clocktime == GST_BUFFER_OFFSET (buffer), + "expected OFFSET set to clocktime %" GST_TIME_FORMAT + ", but got %" GST_TIME_FORMAT, + GST_TIME_ARGS (clocktime), GST_TIME_ARGS (GST_BUFFER_OFFSET (buffer))); } /* this check is here to check that the granulepos we derive from the timestamp @@ -140,7 +152,7 @@ check_buffer_granulepos (GstBuffer * buffer, GstClockTime granulepos) static void check_buffer_granulepos_from_endtime (GstBuffer * buffer, GstClockTime endtime) { - GstClockTime granulepos, expected; + gint64 granulepos, expected; granulepos = GST_BUFFER_OFFSET_END (buffer); expected = gst_util_uint64_scale (endtime, 44100, GST_SECOND); @@ -202,7 +214,8 @@ GST_START_TEST (test_granulepos_offset) gst_buffer_unref (buffer); { - GstClockTime next_timestamp, last_granulepos; + GstClockTime next_timestamp; + gint64 last_granulepos; /* first buffer should have timestamp of TIMESTAMP_OFFSET, granulepos to * match the timestamp of the end of the last sample in the output buffer. @@ -291,7 +304,8 @@ GST_START_TEST (test_timestamps) gst_buffer_unref (buffer); { - GstClockTime next_timestamp, last_granulepos; + GstClockTime next_timestamp; + gint64 last_granulepos; /* first buffer has timestamp 0 */ buffer = get_buffer (bin, pad);