From f89ba82f2998b50c42aa04fd5d507e7e137a69d8 Mon Sep 17 00:00:00 2001 From: Thiago Santos Date: Fri, 7 Feb 2014 01:49:26 -0300 Subject: [PATCH] qtmux: improve support for sparse streams Do not try to use subsequent buffer timestamps to calculate sparse streams durations because the stream is sparse and the buffers might not be 'time adjacent'. So rely on the duration and give the option to the pad to provide custom 'empty' buffers to represent the gaps in the stream, this can vary on how the data is represented. Right now, the only sparse stream supported is tx3g subtitles. --- gst/isomp4/gstqtmux.c | 123 +++++++++++++++++++++++++++++++----------- gst/isomp4/gstqtmux.h | 5 ++ 2 files changed, 98 insertions(+), 30 deletions(-) diff --git a/gst/isomp4/gstqtmux.c b/gst/isomp4/gstqtmux.c index 424f951b73..89401dcda4 100644 --- a/gst/isomp4/gstqtmux.c +++ b/gst/isomp4/gstqtmux.c @@ -378,10 +378,12 @@ gst_qt_mux_pad_reset (GstQTPad * qtpad) qtpad->last_dts = 0; qtpad->first_ts = GST_CLOCK_TIME_NONE; qtpad->prepare_buf_func = NULL; + qtpad->create_empty_buffer = NULL; qtpad->avg_bitrate = 0; qtpad->max_bitrate = 0; qtpad->total_duration = 0; qtpad->total_bytes = 0; + qtpad->sparse = FALSE; qtpad->buf_head = 0; qtpad->buf_tail = 0; @@ -583,6 +585,17 @@ gst_qt_mux_prepare_tx3g_buffer (GstQTPad * qtpad, GstBuffer * buf, return newbuf; } +static GstBuffer * +gst_qt_mux_create_empty_tx3g_buffer (GstQTPad * qtpad, gint64 duration) +{ + guint8 *data; + + data = g_malloc (2); + GST_WRITE_UINT16_BE (data, 0); + + return gst_buffer_new_wrapped (data, 2);; +} + static void gst_qt_mux_add_mp4_tag (GstQTMux * qtmux, const GstTagList * list, const char *tag, const char *tag2, guint32 fourcc) @@ -2185,6 +2198,41 @@ check_and_subtract_ts (GstQTMux * qtmux, GstClockTime * ts_a, GstClockTime ts_b) } } + +static GstFlowReturn +gst_qt_mux_register_and_push_sample (GstQTMux * qtmux, GstQTPad * pad, + GstBuffer * buffer, gboolean is_last_buffer, guint nsamples, + gint64 last_dts, gint64 scaled_duration, guint sample_size, + guint chunk_offset, gboolean sync, gboolean do_pts, gint64 pts_offset) +{ + GstFlowReturn ret = GST_FLOW_OK; + + /* note that a new chunk is started each time (not fancy but works) */ + if (qtmux->moov_recov_file) { + if (!atoms_recov_write_trak_samples (qtmux->moov_recov_file, pad->trak, + nsamples, (gint32) scaled_duration, sample_size, chunk_offset, sync, + do_pts, pts_offset)) { + GST_WARNING_OBJECT (qtmux, "Failed to write sample information to " + "recovery file, disabling recovery"); + fclose (qtmux->moov_recov_file); + qtmux->moov_recov_file = NULL; + } + } + + if (qtmux->fragment_sequence) { + /* ensure that always sync samples are marked as such */ + ret = gst_qt_mux_pad_fragment_add_buffer (qtmux, pad, buffer, + is_last_buffer, nsamples, last_dts, (gint32) scaled_duration, + sample_size, !pad->sync || sync, pts_offset); + } else { + atom_trak_add_samples (pad->trak, nsamples, (gint32) scaled_duration, + sample_size, chunk_offset, sync, pts_offset); + ret = gst_qt_mux_send_buffer (qtmux, buffer, &qtmux->mdat_size, TRUE); + } + + return ret; +} + /* * Here we push the buffer and update the tables in the track atoms */ @@ -2304,14 +2352,17 @@ gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf) * the duration based on the difference in DTS or PTS, falling back * to DURATION if the other two don't exist, such as with the last * sample before EOS. */ - if (last_buf && buf && GST_BUFFER_DTS_IS_VALID (buf) - && GST_BUFFER_DTS_IS_VALID (last_buf)) - duration = GST_BUFFER_DTS (buf) - GST_BUFFER_DTS (last_buf); - else if (last_buf && buf && GST_BUFFER_PTS_IS_VALID (buf) - && GST_BUFFER_PTS_IS_VALID (last_buf)) - duration = GST_BUFFER_PTS (buf) - GST_BUFFER_PTS (last_buf); - else - duration = GST_BUFFER_DURATION (last_buf); + duration = GST_BUFFER_DURATION (last_buf); + if (!pad->sparse) { + if (last_buf && buf && GST_BUFFER_DTS_IS_VALID (buf) + && GST_BUFFER_DTS_IS_VALID (last_buf)) + duration = GST_BUFFER_DTS (buf) - GST_BUFFER_DTS (last_buf); + else if (last_buf && buf && GST_BUFFER_PTS_IS_VALID (buf) + && GST_BUFFER_PTS_IS_VALID (last_buf)) + duration = GST_BUFFER_PTS (buf) - GST_BUFFER_PTS (last_buf); + } + + gst_buffer_replace (&pad->last_buf, buf); /* for computing the avg bitrate */ if (G_LIKELY (last_buf)) { @@ -2319,8 +2370,6 @@ gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf) pad->total_duration += duration; } - gst_buffer_replace (&pad->last_buf, buf); - last_dts = gst_util_uint64_scale_round (pad->last_dts, atom_trak_get_timescale (pad->trak), GST_SECOND); @@ -2425,32 +2474,44 @@ gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf) } /* now we go and register this buffer/sample all over */ - /* note that a new chunk is started each time (not fancy but works) */ - if (qtmux->moov_recov_file) { - if (!atoms_recov_write_trak_samples (qtmux->moov_recov_file, pad->trak, - nsamples, (gint32) scaled_duration, sample_size, chunk_offset, sync, - do_pts, pts_offset)) { - GST_WARNING_OBJECT (qtmux, "Failed to write sample information to " - "recovery file, disabling recovery"); - fclose (qtmux->moov_recov_file); - qtmux->moov_recov_file = NULL; + ret = gst_qt_mux_register_and_push_sample (qtmux, pad, last_buf, + buf == NULL, nsamples, last_dts, scaled_duration, sample_size, + chunk_offset, sync, do_pts, pts_offset); + + /* if this is sparse and we have a next buffer, check if there is any gap + * between them to insert an empty sample */ + if (pad->sparse && buf) { + if (pad->create_empty_buffer) { + GstBuffer *empty_buf; + gint64 empty_duration = + GST_BUFFER_TIMESTAMP (buf) - (GST_BUFFER_TIMESTAMP (last_buf) + + duration); + gint64 empty_duration_scaled; + + empty_buf = pad->create_empty_buffer (pad, empty_duration); + + empty_duration_scaled = gst_util_uint64_scale_round (empty_duration, + atom_trak_get_timescale (pad->trak), GST_SECOND); + + pad->total_bytes += gst_buffer_get_size (empty_buf); + pad->total_duration += duration; + + ret = + gst_qt_mux_register_and_push_sample (qtmux, pad, empty_buf, FALSE, 1, + last_dts + scaled_duration, empty_duration_scaled, + gst_buffer_get_size (empty_buf), qtmux->mdat_size, sync, do_pts, 0); + } else { + /* our only case currently is tx3g subtitles, so there is no reason to fill this yet */ + g_assert_not_reached (); + GST_WARNING_OBJECT (qtmux, + "no empty buffer creation function found for pad %s", + GST_PAD_NAME (pad->collect.pad)); } } if (buf) gst_buffer_unref (buf); - if (qtmux->fragment_sequence) { - /* ensure that always sync samples are marked as such */ - ret = gst_qt_mux_pad_fragment_add_buffer (qtmux, pad, last_buf, - buf == NULL, nsamples, last_dts, (gint32) scaled_duration, sample_size, - !pad->sync || sync, pts_offset); - } else { - atom_trak_add_samples (pad->trak, nsamples, (gint32) scaled_duration, - sample_size, chunk_offset, sync, pts_offset); - ret = gst_qt_mux_send_buffer (qtmux, last_buf, &qtmux->mdat_size, TRUE); - } - exit: return ret; @@ -3233,6 +3294,7 @@ gst_qt_mux_subtitle_sink_set_caps (GstQTPad * qtpad, GstCaps * caps) subtitle_sample_entry_init (&entry); qtpad->is_out_of_order = FALSE; qtpad->sync = FALSE; + qtpad->sparse = TRUE; qtpad->prepare_buf_func = NULL; structure = gst_caps_get_structure (caps, 0); @@ -3242,6 +3304,7 @@ gst_qt_mux_subtitle_sink_set_caps (GstQTPad * qtpad, GstCaps * caps) if (format && strcmp (format, "utf8") == 0) { entry.fourcc = FOURCC_tx3g; qtpad->prepare_buf_func = gst_qt_mux_prepare_tx3g_buffer; + qtpad->create_empty_buffer = gst_qt_mux_create_empty_tx3g_buffer; } } diff --git a/gst/isomp4/gstqtmux.h b/gst/isomp4/gstqtmux.h index 277283ffca..336803bc3b 100644 --- a/gst/isomp4/gstqtmux.h +++ b/gst/isomp4/gstqtmux.h @@ -79,6 +79,7 @@ typedef GstBuffer * (*GstQTPadPrepareBufferFunc) (GstQTPad * pad, GstBuffer * buf, GstQTMux * qtmux); typedef gboolean (*GstQTPadSetCapsFunc) (GstQTPad * pad, GstCaps * caps); +typedef GstBuffer * (*GstQTPadCreateEmptyBufferFunc) (GstQTPad * pad, gint64 duration); #define QTMUX_NO_OF_TS 10 @@ -96,6 +97,9 @@ struct _GstQTPad guint sample_size; /* make sync table entry */ gboolean sync; + /* if it is a sparse stream + * (meaning we can't use PTS differences to compute duration) */ + gboolean sparse; /* bitrates */ guint32 avg_bitrate, max_bitrate; @@ -129,6 +133,7 @@ struct _GstQTPad /* if nothing is set, it won't be called */ GstQTPadPrepareBufferFunc prepare_buf_func; GstQTPadSetCapsFunc set_caps; + GstQTPadCreateEmptyBufferFunc create_empty_buffer; }; typedef enum _GstQTMuxState